diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000000000..66b28b3485d86 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,32 @@ +# +# An auto defined `clippy` feature was introduced, +# but it was found to clash with user defined features, +# so was renamed to `cargo-clippy`. +# +# If you want standard clippy run: +# RUSTFLAGS= cargo clippy +[target.'cfg(feature = "cargo-clippy")'] +rustflags = [ + "-Aclippy::all", + "-Dclippy::correctness", + "-Aclippy::if-same-then-else", + "-Aclippy::clone-double-ref", + "-Dclippy::complexity", + "-Aclippy::zero-prefixed-literal", # 00_1000_000 + "-Aclippy::type_complexity", # raison d'etre + "-Aclippy::nonminimal-bool", # maybe + "-Aclippy::borrowed-box", # Reasonable to fix this one + "-Aclippy::too-many-arguments", # (Turning this on would lead to) + "-Aclippy::unnecessary_cast", # Types may change + "-Aclippy::identity-op", # One case where we do 0 + + "-Aclippy::useless_conversion", # Types may change + "-Aclippy::unit_arg", # styalistic. + "-Aclippy::option-map-unit-fn", # styalistic + "-Aclippy::bind_instead_of_map", # styalistic + "-Aclippy::erasing_op", # E.g. 0 * DOLLARS + "-Aclippy::eq_op", # In tests we test equality. + "-Aclippy::while_immutable_condition", # false positives + "-Aclippy::needless_option_as_deref", # false positives + "-Aclippy::derivable_impls", # false positives + "-Aclippy::stable_sort_primitive", # prefer stable sort +] diff --git a/.dockerignore b/.dockerignore index 39dbc05c97e16..c58599e3fb72b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,3 +4,4 @@ doc Dockerfile .dockerignore .local +.env* diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000000..c99a3070231d8 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,15 @@ +# You can easily exclude big automated code changes by running +# +# git config blame.ignoreRevsFile .git-blame-ignore-revs +# +# in your local repository. It will work also in IDE integrations. +# +# On versions of Git 2.20 and later comments (#), empty lines, and any leading and +# trailing whitespace is ignored. Everything but a SHA-1 per line will error out on +# older versions. +# +# You should add new commit hashes to this file when you create or find such big +# automated refactorings while reading code history. If you only know the short hash, +# use `git rev-parse 1d5abf01` to expand it to the full SHA1 hash needed in this file. + +1d5abf01abafdb6c15bcd0172f5de09fd87c5fbf # Run cargo fmt on the whole code base (#9394) diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml new file mode 100644 index 0000000000000..ae40df08eca76 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -0,0 +1,34 @@ +name: Bug Report +description: Let us know about an issue you experienced with this software +# labels: ["some existing label","another one"] +body: + - type: checkboxes + attributes: + label: Is there an existing issue? + description: Please search to see if an issue already exists and leave a comment that you also experienced this issue or add your specifics that are related to an existing issue. + options: + - label: I have searched the existing issues + required: true + - type: checkboxes + attributes: + label: Experiencing problems? Have you tried our Stack Exchange first? + description: Please search to see if an post already exists, and ask if not. Please do not file support issues here. + options: + - label: This is not a support question. + required: true + - type: textarea + id: bug + attributes: + label: Description of bug + # description: What seems to be the problem? + # placeholder: Describe the problem. + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Provide the steps that led to the discovery of the issue. + # placeholder: Describe what you were doing so we can reproduce the problem. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000..e422e317411f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +blank_issues_enabled: true +contact_links: + - name: Support & Troubleshooting with the Substrate Stack Exchange Community + url: https://substrate.stackexchange.com + about: | + For general problems with Substrate or related technologies, please search here first + for solutions, by keyword and tags. If you discover no solution, please then ask and questions in our community! We highly encourage everyone also share their understanding by answering questions for others. diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml new file mode 100644 index 0000000000000..92b2fea3e88da --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -0,0 +1,52 @@ +name: Feature Request +description: Submit your requests and suggestions to improve! +body: + - type: checkboxes + attributes: + label: Is there an existing issue? + description: Please search to see if an issue already exists and leave a comment that you also experienced this issue or add your specifics that are related to an existing issue. + options: + - label: I have searched the existing issues + required: true + - type: checkboxes + attributes: + label: Experiencing problems? Have you tried our Stack Exchange first? + description: Please search to see if an post already exists, and ask if not. Please do not file support issues here. + options: + - label: This is not a support question. + required: true + - type: textarea + id: content + attributes: + label: Motivation + description: Please give precedence as to what lead you to file this issue. + # placeholder: Describe ... + validations: + required: false + - type: textarea + id: content + attributes: + label: Request + description: Please describe what is needed. + # placeholder: Describe what you would like to see added or changed. + validations: + required: true + - type: textarea + id: content + attributes: + label: Solution + description: If possible, please describe what a solution could be. + # placeholder: Describe what you would like to see added or changed. + validations: + required: false + - type: dropdown + id: help + attributes: + label: Are you willing to help with this request? + multiple: true + options: + - Yes! + - No. + - Maybe (please elaborate above) + validations: + required: true diff --git a/.github/allowed-actions.js b/.github/allowed-actions.js deleted file mode 100644 index 4fb894758060d..0000000000000 --- a/.github/allowed-actions.js +++ /dev/null @@ -1,7 +0,0 @@ -// This is a whitelist of GitHub Actions that are approved for use in this project. -// If a new or existing workflow file is updated to use an action or action version -// not listed here, CI will fail. - -module.exports = [ - 'gaurav-nelson/github-action-markdown-link-check@7481451f70251762f149d69596e3e276ebf2b236', // gaurav-nelson/github-action-markdown-link-check@v1.0.8 -] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a321729dcbc81..cca9219e6c5e1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,8 @@ updates: labels: ["A2-insubstantial", "B0-silent", "C1-low 📌"] schedule: interval: "daily" + - package-ecosystem: github-actions + directory: '/' + labels: ["A2-insubstantial", "B0-silent", "C1-low 📌", "E3-dependencies"] + schedule: + interval: daily diff --git a/.github/pr-custom-review.yml b/.github/pr-custom-review.yml new file mode 100644 index 0000000000000..769ac1e110d33 --- /dev/null +++ b/.github/pr-custom-review.yml @@ -0,0 +1,27 @@ +# 🔒 PROTECTED: Changes to locks-review-team should be approved by the current locks-review-team +locks-review-team: locks-review +team-leads-team: polkadot-review +action-review-team: ci + +rules: + - name: Core developers + check_type: changed_files + condition: + include: .* + # excluding files from 'CI team' rules + exclude: ^\.gitlab-ci\.yml|^scripts/ci/.*|^\.github/.* + min_approvals: 2 + teams: + - core-devs + + - name: CI team + check_type: changed_files + condition: + include: ^\.gitlab-ci\.yml|^scripts/ci/.*|^\.github/.* + min_approvals: 2 + teams: + - ci + +prevent-review-request: + teams: + - core-devs diff --git a/.github/workflows/auto-label-issues.yml b/.github/workflows/auto-label-issues.yml index cd889b5941989..2633bf55f0789 100644 --- a/.github/workflows/auto-label-issues.yml +++ b/.github/workflows/auto-label-issues.yml @@ -8,10 +8,10 @@ on: jobs: label-new-issues: - runs-on: ubuntu-latest + runs-on: ubuntu-latest steps: - name: Label drafts - uses: andymckay/labeler@master + uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4 if: github.event.issue.author_association == 'NONE' with: add-labels: 'Z0-unconfirmed' diff --git a/.github/workflows/auto-label-prs.yml b/.github/workflows/auto-label-prs.yml index f0b8e9b343e29..50539b80b98b7 100644 --- a/.github/workflows/auto-label-prs.yml +++ b/.github/workflows/auto-label-prs.yml @@ -8,13 +8,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Label drafts - uses: andymckay/labeler@master + uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4 if: github.event.pull_request.draft == true with: add-labels: 'A3-inprogress' remove-labels: 'A0-pleasereview' - name: Label PRs - uses: andymckay/labeler@master + uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4 if: github.event.pull_request.draft == false && ! contains(github.event.pull_request.labels.*.name, 'A2-insubstantial') with: add-labels: 'A0-pleasereview' diff --git a/.github/workflows/check-labels.yml b/.github/workflows/check-labels.yml index 7180e7b509662..de204ce9d3776 100644 --- a/.github/workflows/check-labels.yml +++ b/.github/workflows/check-labels.yml @@ -8,13 +8,13 @@ jobs: check-labels: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} - name: Check labels - run: bash ${{ github.workspace }}/.maintain/github/check_labels.sh + run: bash ${{ github.workspace }}/scripts/ci/github/check_labels.sh env: GITHUB_PR: ${{ github.event.pull_request.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/md-link-check.yml b/.github/workflows/md-link-check.yml index 868569911d471..e1387f6da13f7 100644 --- a/.github/workflows/md-link-check.yml +++ b/.github/workflows/md-link-check.yml @@ -12,8 +12,8 @@ jobs: markdown-link-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: gaurav-nelson/github-action-markdown-link-check@7481451f70251762f149d69596e3e276ebf2b236 + - uses: actions/checkout@v3 + - uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 # 1.0.14 with: use-quiet-mode: 'yes' config-file: '.github/workflows/mlc_config.json' diff --git a/.github/workflows/monthly-tag.yml b/.github/workflows/monthly-tag.yml index 8736a341cecf9..6bec03d27e7be 100644 --- a/.github/workflows/monthly-tag.yml +++ b/.github/workflows/monthly-tag.yml @@ -16,7 +16,7 @@ jobs: echo "::set-output name=new::$(date +'monthly-%Y-%m')" echo "::set-output name=old::$(date -d'1 month ago' +'monthly-%Y-%m')" - name: Checkout branch "master" - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: 'master' fetch-depth: 0 @@ -29,10 +29,10 @@ jobs: echo "" >> Changelog.md echo "## Changes since last snapshot (${{ steps.tags.outputs.old }})" >> Changelog.md echo "" >> Changelog.md - ./.maintain/gitlab/generate_changelog.sh ${{ steps.tags.outputs.old }} >> Changelog.md + ./scripts/ci/github/generate_changelog.sh ${{ steps.tags.outputs.old }} >> Changelog.md - name: Release snapshot id: release-snapshot - uses: actions/create-release@latest + uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4 latest version, repo archived env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/polkadot-companion-labels.yml b/.github/workflows/polkadot-companion-labels.yml deleted file mode 100644 index 3c3987b5f4d56..0000000000000 --- a/.github/workflows/polkadot-companion-labels.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Check Polkadot Companion and Label - -on: - pull_request: - types: [opened, synchronize] - -jobs: - check_status: - runs-on: ubuntu-latest - steps: - - name: Monitor the status of the gitlab-check-companion-build job - uses: s3krit/await-status-action@v1.0.1 - id: 'check-companion-status' - with: - authToken: ${{ secrets.GITHUB_TOKEN }} - ref: ${{ github.event.pull_request.head.sha }} - contexts: 'continuous-integration/gitlab-check-polkadot-companion-build' - timeout: 1800 - notPresentTimeout: 3600 # It can take quite a while before the job starts on Gitlab when the CI queue is large - failureStates: failure - interruptedStates: error # Error = job was probably cancelled. We don't want to label the PR in that case - pollInterval: 30 - - name: Label success - uses: andymckay/labeler@master - if: steps.check-companion-status.outputs.result == 'success' - with: - remove-labels: 'A7-needspolkadotpr' - - name: Label failure - uses: andymckay/labeler@master - if: steps.check-companion-status.outputs.result == 'failure' - with: - add-labels: 'A7-needspolkadotpr' diff --git a/.github/workflows/pr-custom-review.yml b/.github/workflows/pr-custom-review.yml new file mode 100644 index 0000000000000..8e40c9ee72989 --- /dev/null +++ b/.github/workflows/pr-custom-review.yml @@ -0,0 +1,42 @@ +name: Assign reviewers + +on: + pull_request: + branches: + - master + - main + types: + - opened + - reopened + - synchronize + - review_requested + - review_request_removed + - ready_for_review + - converted_to_draft + pull_request_review: + +jobs: + pr-custom-review: + runs-on: ubuntu-latest + steps: + - name: Skip if pull request is in Draft + # `if: github.event.pull_request.draft == true` should be kept here, at + # the step level, rather than at the job level. The latter is not + # recommended because when the PR is moved from "Draft" to "Ready to + # review" the workflow will immediately be passing (since it was skipped), + # even though it hasn't actually ran, since it takes a few seconds for + # the workflow to start. This is also disclosed in: + # https://github.community/t/dont-run-actions-on-draft-pull-requests/16817/17 + # That scenario would open an opportunity for the check to be bypassed: + # 1. Get your PR approved + # 2. Move it to Draft + # 3. Push whatever commits you want + # 4. Move it to "Ready for review"; now the workflow is passing (it was + # skipped) and "Check reviews" is also passing (it won't be updated + # until the workflow is finished) + if: github.event.pull_request.draft == true + run: exit 1 + - name: pr-custom-review + uses: paritytech/pr-custom-review@action-v3 + with: + checks-reviews-api: http://pcr.parity-prod.parity.io/api/v1/check_reviews diff --git a/.github/workflows/release-tagging.yml b/.github/workflows/release-tagging.yml index c55fc13a626e0..f7fa913c69709 100644 --- a/.github/workflows/release-tagging.yml +++ b/.github/workflows/release-tagging.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Set Git tag - uses: s3krit/walking-tag-action@master + uses: s3krit/walking-tag-action@d04f7a53b72ceda4e20283736ce3627011275178 # latest version from master with: TAG_NAME: release TAG_MESSAGE: Latest release diff --git a/.gitignore b/.gitignore index 0486a1a716e5c..5cd013e054e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ rls*.log .local **/hfuzz_target/ **/hfuzz_workspace/ -.cargo/ .cargo-remote.toml *.bin *.iml +scripts/ci/node-template-release/Cargo.lock diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ecafc9338a587..25d61cf349615 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,15 @@ # substrate # # pipelines can be triggered manually in the web - +# +# Currently the file is divided into subfiles. Each stage has a different file which +# can be found here: scripts/ci/gitlab/pipeline/.yml +# +# Instead of YAML anchors "extends" is used. +# Useful links: +# https://docs.gitlab.com/ee/ci/yaml/index.html#extends +# https://docs.gitlab.com/ee/ci/yaml/yaml_optimization.html#reference-tags +# # SAMPLE JOB TEMPLATE - This is not a complete example but is enough to build a # simple CI job. For full documentation, visit https://docs.gitlab.com/ee/ci/yaml/ # @@ -18,38 +26,46 @@ # script: # - echo "List of shell commands to run in your job" # - echo "You can also just specify a script here, like so:" -# - ./.maintain/gitlab/my_amazing_script.sh +# - ./scripts/ci/gitlab/my_amazing_script.sh stages: - check - test - build - publish + - zombienet - deploy + - notify workflow: rules: - if: $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH -variables: &default-vars +variables: GIT_STRATEGY: fetch GIT_DEPTH: 100 CARGO_INCREMENTAL: 0 DOCKER_OS: "debian:stretch" ARCH: "x86_64" - CI_IMAGE: "paritytech/ci-linux:production" - # FIXME set to release - CARGO_UNLEASH_INSTALL_PARAMS: "--version 1.0.0-alpha.12" - CARGO_UNLEASH_PKG_DEF: "--skip node node-* pallet-template pallet-example pallet-example-* subkey chain-spec-builder" - VAULT_SERVER_URL: "https://vault.parity-mgmt-vault.parity.io" - VAULT_AUTH_PATH: "gitlab-parity-io-jwt" - VAULT_AUTH_ROLE: "cicd_gitlab_parity_${CI_PROJECT_NAME}" + # staging image with rust 1.65 and nightly-2022-11-16 + CI_IMAGE: "paritytech/ci-linux@sha256:786869e731963b3cc0a4aa9deb83367ed9e87a6ae48b6eb029d62b0cab4d87c1" + BUILDAH_IMAGE: "quay.io/buildah/stable:v1.27" + RUSTY_CACHIER_SINGLE_BRANCH: master + RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.22" default: + retry: + max: 2 + when: + - runner_system_failure + - unknown_failure + - api_failure + interruptible: true cache: {} -.collect-artifacts: &collect-artifacts +.collect-artifacts: artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" when: on_success @@ -57,47 +73,83 @@ default: paths: - artifacts/ -.kubernetes-env: &kubernetes-env - retry: - max: 2 - when: - - runner_system_failure - - unknown_failure - - api_failure - interruptible: true +.collect-artifacts-short: + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: on_success + expire_in: 3 hours + paths: + - artifacts/ + +.kubernetes-env: + image: "${CI_IMAGE}" tags: - kubernetes-parity-build -.rust-info-script: &rust-info-script - - rustup show - - cargo --version - - rustup +nightly show - - cargo +nightly --version - - sccache -s +.rust-info-script: + script: + - rustup show + - cargo --version + - rustup +nightly show + - cargo +nightly --version + +.pipeline-stopper-vars: + script: + - echo "Collecting env variables for the cancel-pipeline job" + - echo "FAILED_JOB_URL=${CI_JOB_URL}" > pipeline-stopper.env + - echo "FAILED_JOB_NAME=${CI_JOB_NAME}" >> pipeline-stopper.env + - echo "PR_NUM=${CI_COMMIT_REF_NAME}" >> pipeline-stopper.env + +.pipeline-stopper-artifacts: + artifacts: + reports: + dotenv: pipeline-stopper.env -.docker-env: &docker-env +.docker-env: image: "${CI_IMAGE}" before_script: - - *rust-info-script - retry: - max: 2 - when: - - runner_system_failure - - unknown_failure - - api_failure - interruptible: true + # TODO: remove unset invocation when we'll be free from 'ENV RUSTC_WRAPPER=sccache' & sccache itself in all images + - unset RUSTC_WRAPPER + - !reference [.rust-info-script, script] + - !reference [.rusty-cachier, before_script] + - !reference [.pipeline-stopper-vars, script] + after_script: + - !reference [.rusty-cachier, after_script] tags: - linux-docker -.test-refs: &test-refs +# rusty-cachier's hidden job. Parts of this job are used to instrument the pipeline's other real jobs with rusty-cachier +# Description of the commands is available here - https://gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client#description +.rusty-cachier: + before_script: + - curl -s https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client/-/raw/release/util/install.sh | bash + - rusty-cachier environment check --gracefully + - $(rusty-cachier environment inject) + - rusty-cachier project mtime + after_script: + - env RUSTY_CACHIER_SUPRESS_OUTPUT=true rusty-cachier snapshot destroy + +.test-refs: + rules: + - if: $CI_PIPELINE_SOURCE == "web" + - if: $CI_PIPELINE_SOURCE == "schedule" + - if: $CI_COMMIT_REF_NAME == "master" + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + +# handle the specific case where benches could store incorrect bench data because of the downstream staging runs +# exclude cargo-check-benches from such runs +.test-refs-check-benches: rules: + - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "parent_pipeline" && $CI_IMAGE =~ /staging$/ + when: never - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 -.test-refs-no-trigger: &test-refs-no-trigger +.test-refs-no-trigger: rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never @@ -108,7 +160,7 @@ default: - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ -.test-refs-no-trigger-prs-only: &test-refs-no-trigger-prs-only +.test-refs-no-trigger-prs-only: rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never @@ -116,27 +168,17 @@ default: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs -.test-refs-wasmer-sandbox: &test-refs-wasmer-sandbox +.publish-refs: rules: + - if: $CI_PIPELINE_SOURCE == "pipeline" + when: never - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - changes: - - client/executor/**/* - - frame/contracts/**/* - - primitives/sandbox/**/* - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - changes: - - client/executor/**/* - - frame/contracts/**/* - - primitives/sandbox/**/* - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - changes: - - client/executor/**/* - - frame/contracts/**/* - - primitives/sandbox/**/* -.build-refs: &build-refs +.build-refs: + # publish-refs + PRs rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never @@ -144,94 +186,41 @@ default: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs -.nightly-pipeline: &nightly-pipeline +.zombienet-refs: + extends: .build-refs + +.nightly-pipeline: rules: # this job runs only on nightly pipeline with the mentioned variable, against `master` branch - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "nightly" -.merge-ref-into-master-script: &merge-ref-into-master-script - - if [ $CI_COMMIT_REF_NAME != "master" ]; then - git fetch origin +master:master; - git fetch origin +$CI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME; - git checkout master; - git config user.email "ci@gitlab.parity.io"; - git merge $CI_COMMIT_REF_NAME --verbose --no-edit; - fi - -.cargo-check-benches-script: &cargo-check-benches-script - - mkdir -p artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA - - SKIP_WASM_BUILD=1 time cargo +nightly check --benches --all - - 'cargo run --release -p node-bench -- ::node::import::native::sr25519::transfer_keep_alive::paritydb::small --json - | tee artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::native::sr25519::transfer_keep_alive::paritydb::small.json' - - 'cargo run --release -p node-bench -- ::trie::read::small --json - | tee artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json' - - sccache -s - -#### Vault secrets -.vault-secrets: &vault-secrets - secrets: - DOCKER_HUB_USER: - vault: cicd/gitlab/parity/DOCKER_HUB_USER@kv - file: false - DOCKER_HUB_PASS: - vault: cicd/gitlab/parity/DOCKER_HUB_PASS@kv - file: false - GITHUB_PR_TOKEN: - vault: cicd/gitlab/parity/GITHUB_PR_TOKEN@kv - file: false - AWS_ACCESS_KEY_ID: - vault: cicd/gitlab/$CI_PROJECT_PATH/AWS_ACCESS_KEY_ID@kv - file: false - AWS_SECRET_ACCESS_KEY: - vault: cicd/gitlab/$CI_PROJECT_PATH/AWS_SECRET_ACCESS_KEY@kv - file: false - AWX_TOKEN: - vault: cicd/gitlab/$CI_PROJECT_PATH/AWX_TOKEN@kv - file: false - CRATES_TOKEN: - vault: cicd/gitlab/$CI_PROJECT_PATH/CRATES_TOKEN@kv - file: false - DOCKER_CHAOS_TOKEN: - vault: cicd/gitlab/$CI_PROJECT_PATH/DOCKER_CHAOS_TOKEN@kv - file: false - DOCKER_CHAOS_USER: - vault: cicd/gitlab/$CI_PROJECT_PATH/DOCKER_CHAOS_USER@kv - file: false - GITHUB_EMAIL: - vault: cicd/gitlab/$CI_PROJECT_PATH/GITHUB_EMAIL@kv - file: false - GITHUB_RELEASE_TOKEN: - vault: cicd/gitlab/$CI_PROJECT_PATH/GITHUB_RELEASE_TOKEN@kv - file: false - GITHUB_TOKEN: - vault: cicd/gitlab/$CI_PROJECT_PATH/GITHUB_TOKEN@kv - file: false - GITHUB_USER: - vault: cicd/gitlab/$CI_PROJECT_PATH/GITHUB_USER@kv - file: false - MATRIX_ACCESS_TOKEN: - vault: cicd/gitlab/$CI_PROJECT_PATH/MATRIX_ACCESS_TOKEN@kv - file: false - MATRIX_ROOM_ID: - vault: cicd/gitlab/$CI_PROJECT_PATH/MATRIX_ROOM_ID@kv - file: false - PIPELINE_TOKEN: - vault: cicd/gitlab/$CI_PROJECT_PATH/PIPELINE_TOKEN@kv - file: false - VALIDATOR_KEYS: - vault: cicd/gitlab/$CI_PROJECT_PATH/VALIDATOR_KEYS@kv - file: false - VALIDATOR_KEYS_CHAOS: - vault: cicd/gitlab/$CI_PROJECT_PATH/VALIDATOR_KEYS_CHAOS@kv - file: false +.crates-publishing-template: + stage: test + extends: .docker-env + # collect artifacts even on failure so that we know how the crates were generated (they'll be + # generated to the artifacts folder according to SPUB_TMP further down) + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: always + expire_in: 7 days + paths: + - artifacts/ + variables: + CRATESIO_API: https://crates.io/api + CRATESIO_CRATES_OWNER: parity-crate-owner + GH_API: https://api.github.com + REPO: substrate + REPO_OWNER: paritytech + SPUB_TMP: artifacts #### stage: .pre skip-if-draft: - image: paritytech/tools:latest - <<: *kubernetes-env - <<: *vault-secrets + extends: .kubernetes-env + variables: + CI_IMAGE: "paritytech/tools:latest" stage: .pre rules: - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs @@ -239,613 +228,143 @@ skip-if-draft: - echo "Commit message is ${CI_COMMIT_MESSAGE}" - echo "Ref is ${CI_COMMIT_REF_NAME}" - echo "pipeline source is ${CI_PIPELINE_SOURCE}" - - ./.maintain/gitlab/skip_if_draft.sh - -#### stage: check - -check-runtime: - stage: check - image: paritytech/tools:latest - <<: *kubernetes-env - <<: *vault-secrets - rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - variables: - <<: *default-vars - GITLAB_API: "https://gitlab.parity.io/api/v4" - GITHUB_API_PROJECT: "parity%2Finfrastructure%2Fgithub-api" - script: - - ./.maintain/gitlab/check_runtime.sh + - ./scripts/ci/gitlab/skip_if_draft.sh allow_failure: true -check-signed-tag: - stage: check - image: paritytech/tools:latest - <<: *kubernetes-env - <<: *vault-secrets - rules: - - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - script: - - ./.maintain/gitlab/check_signed.sh - -check-line-width: - stage: check - image: paritytech/tools:latest - <<: *kubernetes-env - rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - script: - - ./.maintain/gitlab/check_line_width.sh - allow_failure: true +include: + # check jobs + - scripts/ci/gitlab/pipeline/check.yml + # tests jobs + - scripts/ci/gitlab/pipeline/test.yml + # build jobs + - scripts/ci/gitlab/pipeline/build.yml + # publish jobs + - scripts/ci/gitlab/pipeline/publish.yml + # zombienet jobs + - scripts/ci/gitlab/pipeline/zombienet.yml -test-dependency-rules: - stage: check - image: paritytech/tools:latest - <<: *kubernetes-env - rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - script: - - .maintain/ensure-deps.sh +#### stage: deploy -test-prometheus-alerting-rules: - stage: check - image: paritytech/tools:latest - <<: *kubernetes-env +deploy-prometheus-alerting-rules: + stage: deploy + needs: + - job: test-prometheus-alerting-rules + artifacts: false + allow_failure: true + trigger: + project: parity/infrastructure/cloud-infra + variables: + SUBSTRATE_CI_COMMIT_NAME: "${CI_COMMIT_REF_NAME}" + SUBSTRATE_CI_COMMIT_REF: "${CI_COMMIT_SHORT_SHA}" + UPSTREAM_TRIGGER_PROJECT: "${CI_PROJECT_PATH}" rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never - - if: $CI_COMMIT_BRANCH + - if: $CI_COMMIT_REF_NAME == "master" changes: - .gitlab-ci.yml - - .maintain/monitoring/**/* - script: - - promtool check rules .maintain/monitoring/alerting-rules/alerting-rules.yaml - - cat .maintain/monitoring/alerting-rules/alerting-rules.yaml | - promtool test rules .maintain/monitoring/alerting-rules/alerting-rule-tests.yaml - -#### stage: test - -cargo-deny: - stage: test - <<: *docker-env - <<: *nightly-pipeline - script: - - cargo deny check --hide-inclusion-graph -c .maintain/deny.toml - after_script: - - echo "___The complete log is in the artifacts___" - - cargo deny check -c .maintain/deny.toml 2> deny.log - artifacts: - name: $CI_COMMIT_SHORT_SHA - expire_in: 3 days - when: always - paths: - - deny.log - # FIXME: Temorarily allow to fail. - allow_failure: true - -cargo-fmt: - stage: test - <<: *docker-env - <<: *test-refs - script: - - cargo +nightly fmt --all -- --check - -cargo-check-benches: - stage: test - <<: *docker-env - <<: *test-refs - <<: *collect-artifacts - before_script: - # merges in the master branch on PRs - - *merge-ref-into-master-script - - *rust-info-script - script: - - *cargo-check-benches-script - -node-bench-regression-guard: - # it's not belong to `build` semantically, but dag jobs can't depend on each other - # within the single stage - https://gitlab.com/gitlab-org/gitlab/-/issues/30632 - # more: https://github.com/paritytech/substrate/pull/8519#discussion_r608012402 - stage: build - <<: *docker-env - <<: *test-refs-no-trigger-prs-only - needs: - # this is a DAG - - job: cargo-check-benches - artifacts: true - # this does not like a DAG, just polls the artifact - - project: $CI_PROJECT_PATH - job: cargo-check-benches - ref: master - artifacts: true - variables: - CI_IMAGE: "paritytech/node-bench-regression-guard:latest" - before_script: [""] - script: - - 'node-bench-regression-guard --reference artifacts/benches/master-* - --compare-with artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA' + - ./scripts/ci/monitoring/**/* -cargo-check-subkey: - stage: test - <<: *docker-env - <<: *test-refs - script: - - cd ./bin/utils/subkey - - SKIP_WASM_BUILD=1 time cargo check --release - - sccache -s - -cargo-check-try-runtime: - stage: test - <<: *docker-env - <<: *test-refs - script: - - time cargo check --features try-runtime - - sccache -s - -cargo-check-wasmer-sandbox: - stage: test - <<: *docker-env - <<: *test-refs - script: - - time cargo check --features wasmer-sandbox - - sccache -s +#### stage: notify -test-deterministic-wasm: - stage: test - <<: *docker-env - <<: *test-refs - variables: - <<: *default-vars - WASM_BUILD_NO_COLOR: 1 - script: - # build runtime - - cargo build --verbose --release -p node-runtime - # make checksum - - sha256sum target/release/wbuild/node-runtime/target/wasm32-unknown-unknown/release/node_runtime.wasm > checksum.sha256 - # clean up – FIXME: can we reuse some of the artifacts? - - cargo clean - # build again - - cargo build --verbose --release -p node-runtime - # confirm checksum - - sha256sum -c checksum.sha256 - - sccache -s - -test-linux-stable: &test-linux - stage: test - <<: *docker-env - <<: *test-refs - variables: - <<: *default-vars - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - script: - # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests - - time cargo test --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml - - time cargo test -p frame-support-test --features=conditional-storage --manifest-path frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec - - SUBSTRATE_TEST_TIMEOUT=1 time cargo test -p substrate-test-utils --release --verbose --locked -- --ignored timeout - - sccache -s - -#unleash-check: - #stage: test - #<<: *docker-env - #<<: *test-refs-no-trigger - #script: - #- cargo install cargo-unleash ${CARGO_UNLEASH_INSTALL_PARAMS} - #- cargo unleash de-dev-deps - # Reuse build artifacts when running checks (cuts down check time by 3x) - # TODO: Implement this optimization in cargo-unleash rather than here - #- mkdir -p target/unleash - #- export CARGO_TARGET_DIR=target/unleash - #- cargo unleash check ${CARGO_UNLEASH_PKG_DEF} - # FIXME: this job must not fail, or unleash-to-crates-io will publish broken stuff - #allow_failure: true - -test-frame-examples-compile-to-wasm: - # into one job - stage: test - <<: *docker-env - <<: *test-refs +# This job notifies rusty-cachier about the latest commit with the cache. +# This info is later used for the cache distribution and an overlay creation. +# Note that we don't use any .rusty-cachier references as we assume that a pipeline has reached this stage with working rusty-cachier. +rusty-cachier-notify: + stage: notify + extends: .kubernetes-env variables: - <<: *default-vars - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y" - RUST_BACKTRACE: 1 - script: - - cd frame/example-offchain-worker/ - - cargo +nightly build --target=wasm32-unknown-unknown --no-default-features - - cd ../example - - cargo +nightly build --target=wasm32-unknown-unknown --no-default-features - - sccache -s - -test-linux-stable-int: - <<: *test-linux - stage: test - script: - - echo "___Logs will be partly shown at the end in case of failure.___" - - echo "___Full log will be saved to the job artifacts only in case of failure.___" - - WASM_BUILD_NO_COLOR=1 - RUST_LOG=sync=trace,consensus=trace,client=trace,state-db=trace,db=trace,forks=trace,state_db=trace,storage_cache=trace - time cargo test -p node-cli --release --verbose --locked -- --ignored - &> ${CI_COMMIT_SHORT_SHA}_int_failure.log - - sccache -s - after_script: - - awk '/FAILED|^error\[/,0' ${CI_COMMIT_SHORT_SHA}_int_failure.log - artifacts: - name: $CI_COMMIT_SHORT_SHA - when: on_failure - expire_in: 3 days - paths: - - ${CI_COMMIT_SHORT_SHA}_int_failure.log - -check-tracing: - stage: test - <<: *docker-env - <<: *test-refs + CI_IMAGE: paritytech/rusty-cachier-env:latest + GIT_STRATEGY: none + dependencies: [] script: - # with-tracing must be explicitly activated, we run a test to ensure this works as expected in both cases - - time cargo +nightly test --manifest-path primitives/tracing/Cargo.toml --no-default-features - - time cargo +nightly test --manifest-path primitives/tracing/Cargo.toml --no-default-features --features=with-tracing - - sccache -s + - curl -s https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client/-/raw/release/util/install.sh | bash + - rusty-cachier cache notify -test-full-crypto-feature: - stage: test - <<: *docker-env - <<: *test-refs - variables: - <<: *default-vars - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y" - RUST_BACKTRACE: 1 - script: - - cd primitives/core/ - - time cargo +nightly build --verbose --no-default-features --features full_crypto - - cd ../application-crypto - - time cargo +nightly build --verbose --no-default-features --features full_crypto - - sccache -s +#### stage: .post -test-wasmer-sandbox: - stage: test - <<: *docker-env - <<: *test-refs-wasmer-sandbox +# This job cancels the whole pipeline if any of provided jobs fail. +# In a DAG, every jobs chain is executed independently of others. The `fail_fast` principle suggests +# to fail the pipeline as soon as possible to shorten the feedback loop. +.cancel-pipeline-template: + stage: .post + rules: + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + when: on_failure variables: - <<: *default-vars - script: - - time cargo test --release --features runtime-benchmarks,wasmer-sandbox - - sccache -s - -cargo-check-macos: - stage: test - # shell runner on mac ignores the image set in *docker-env - <<: *docker-env - <<: *test-refs-no-trigger - script: - - SKIP_WASM_BUILD=1 time cargo check --release - - sccache -s - tags: - - osx - -#### stage: build + PROJECT_ID: "${CI_PROJECT_ID}" + PROJECT_NAME: "${CI_PROJECT_NAME}" + PIPELINE_ID: "${CI_PIPELINE_ID}" + FAILED_JOB_URL: "${FAILED_JOB_URL}" + FAILED_JOB_NAME: "${FAILED_JOB_NAME}" + PR_NUM: "${PR_NUM}" + trigger: + project: "parity/infrastructure/ci_cd/pipeline-stopper" + branch: "as-improve" -check-polkadot-companion-status: - stage: build - image: paritytech/tools:latest - <<: *kubernetes-env - <<: *vault-secrets +remove-cancel-pipeline-message: + stage: .post rules: - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - script: - - ./.maintain/gitlab/check_polkadot_companion_status.sh + variables: + PROJECT_ID: "${CI_PROJECT_ID}" + PROJECT_NAME: "${CI_PROJECT_NAME}" + PIPELINE_ID: "${CI_PIPELINE_ID}" + FAILED_JOB_URL: "https://gitlab.com" + FAILED_JOB_NAME: "nope" + PR_NUM: "${CI_COMMIT_REF_NAME}" + trigger: + project: "parity/infrastructure/ci_cd/pipeline-stopper" + branch: "as-improve" -check-polkadot-companion-build: - stage: build - <<: *docker-env - <<: *test-refs-no-trigger - <<: *vault-secrets +# need to copy jobs this way because otherwise gitlab will wait +# for all 3 jobs to finish instead of cancelling if one fails +cancel-pipeline-test-linux-stable1: + extends: .cancel-pipeline-template needs: - - job: test-linux-stable-int - artifacts: false - script: - - ./.maintain/gitlab/check_polkadot_companion_build.sh - after_script: - - cd polkadot && git rev-parse --abbrev-ref HEAD - allow_failure: true + - job: "test-linux-stable 1/3" -build-linux-substrate: &build-binary - stage: build - <<: *collect-artifacts - <<: *docker-env - <<: *build-refs +cancel-pipeline-test-linux-stable2: + extends: .cancel-pipeline-template needs: - - job: test-linux-stable - artifacts: false - before_script: - - mkdir -p ./artifacts/substrate/ - script: - - WASM_BUILD_NO_COLOR=1 time cargo build --release --verbose - - mv ./target/release/substrate ./artifacts/substrate/. - - echo -n "Substrate version = " - - if [ "${CI_COMMIT_TAG}" ]; then - echo "${CI_COMMIT_TAG}" | tee ./artifacts/substrate/VERSION; - else - ./artifacts/substrate/substrate --version | - sed -n -E 's/^substrate ([0-9.]+.*-[0-9a-f]{7,13})-.*$/\1/p' | - tee ./artifacts/substrate/VERSION; - fi - - sha256sum ./artifacts/substrate/substrate | tee ./artifacts/substrate/substrate.sha256 - - printf '\n# building node-template\n\n' - - ./.maintain/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz - - cp -r .maintain/docker/substrate.Dockerfile ./artifacts/substrate/ - - sccache -s - -build-linux-subkey: &build-subkey - stage: build - <<: *collect-artifacts - <<: *docker-env - <<: *build-refs - needs: - - job: cargo-check-subkey - artifacts: false - before_script: - - mkdir -p ./artifacts/subkey - script: - - cd ./bin/utils/subkey - - SKIP_WASM_BUILD=1 time cargo build --release --verbose - - cd - - - mv ./target/release/subkey ./artifacts/subkey/. - - echo -n "Subkey version = " - - ./artifacts/subkey/subkey --version | - sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | - tee ./artifacts/subkey/VERSION; - - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 - - cp -r .maintain/docker/subkey.Dockerfile ./artifacts/subkey/ - - sccache -s - -build-macos-subkey: - <<: *build-subkey - tags: - - osx + - job: "test-linux-stable 2/3" -build-rustdoc: - stage: build - <<: *docker-env - <<: *test-refs - variables: - <<: *default-vars - SKIP_WASM_BUILD: 1 - artifacts: - name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" - when: on_success - expire_in: 7 days - paths: - - ./crate-docs/ - script: - # FIXME: it fails with `RUSTDOCFLAGS="-Dwarnings"` - - RUSTDOCFLAGS="--html-in-header $(pwd)/.maintain/rustdoc-header.html" - time cargo +nightly doc --no-deps --workspace --all-features --verbose - - rm -f ./target/doc/.lock - - mv ./target/doc ./crate-docs - # FIXME: remove me after CI image gets nonroot - - chown -R nonroot:nonroot ./crate-docs - - echo "" > ./crate-docs/index.html - - sccache -s - -#### stage: publish - -.build-push-docker-image: &build-push-docker-image - <<: *build-refs - <<: *kubernetes-env - <<: *vault-secrets - image: quay.io/buildah/stable - variables: &docker-build-vars - <<: *default-vars - GIT_STRATEGY: none - DOCKERFILE: $PRODUCT.Dockerfile - IMAGE_NAME: docker.io/parity/$PRODUCT - before_script: - - cd ./artifacts/$PRODUCT/ - - VERSION="$(cat ./VERSION)" - - echo "${PRODUCT} version = ${VERSION}" - - test -z "${VERSION}" && exit 1 - script: - - test "$DOCKER_HUB_USER" -a "$DOCKER_HUB_PASS" || - ( echo "no docker credentials provided"; exit 1 ) - - buildah bud - --format=docker - --build-arg VCS_REF="${CI_COMMIT_SHA}" - --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" - --tag "$IMAGE_NAME:$VERSION" - --tag "$IMAGE_NAME:latest" - --file "$DOCKERFILE" . - - echo "$DOCKER_HUB_PASS" | - buildah login --username "$DOCKER_HUB_USER" --password-stdin docker.io - - buildah info - - buildah push --format=v2s2 "$IMAGE_NAME:$VERSION" - - buildah push --format=v2s2 "$IMAGE_NAME:latest" - after_script: - - buildah logout --all - # pass artifacts to the trigger-simnet job - - echo "SUBSTRATE_IMAGE_NAME=${IMAGE_NAME}" | tee -a ./artifacts/$PRODUCT/build.env - - IMAGE_TAG="$(cat ./artifacts/$PRODUCT/VERSION)" - - echo "SUBSTRATE_IMAGE_TAG=${IMAGE_TAG}" | tee -a ./artifacts/$PRODUCT/build.env - - cat ./artifacts/$PRODUCT/build.env - -publish-docker-substrate: - stage: publish - <<: *build-push-docker-image - <<: *build-refs +cancel-pipeline-test-linux-stable3: + extends: .cancel-pipeline-template needs: - - job: build-linux-substrate - artifacts: true - variables: - <<: *docker-build-vars - PRODUCT: substrate - artifacts: - reports: - # this artifact is used in trigger-simnet job - # https://docs.gitlab.com/ee/ci/multi_project_pipelines.html#with-variable-inheritance - dotenv: ./artifacts/substrate/build.env + - job: "test-linux-stable 3/3" -publish-docker-subkey: - stage: publish - <<: *build-push-docker-image - needs: - - job: build-linux-subkey - artifacts: true - variables: - <<: *docker-build-vars - PRODUCT: subkey - -publish-s3-release: - stage: publish - <<: *build-refs - <<: *kubernetes-env - <<: *vault-secrets +cancel-pipeline-cargo-check-benches1: + extends: .cancel-pipeline-template needs: - - job: build-linux-substrate - artifacts: true - - job: build-linux-subkey - artifacts: true - image: paritytech/awscli:latest - variables: - GIT_STRATEGY: none - BUCKET: "releases.parity.io" - PREFIX: "substrate/${ARCH}-${DOCKER_OS}" - script: - - aws s3 sync ./artifacts/ s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/substrate/VERSION)/ - - echo "update objects in latest path" - - aws s3 sync s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/substrate/VERSION)/ s3://${BUCKET}/${PREFIX}/latest/ - after_script: - - aws s3 ls s3://${BUCKET}/${PREFIX}/latest/ - --recursive --human-readable --summarize - -publish-rustdoc: - stage: publish - <<: *kubernetes-env - <<: *vault-secrets - image: paritytech/tools:latest - variables: - GIT_DEPTH: 100 - rules: - - if: $CI_PIPELINE_SOURCE == "pipeline" - when: never - - if: $CI_PIPELINE_SOURCE == "web" && $CI_COMMIT_REF_NAME == "master" - - if: $CI_COMMIT_REF_NAME == "master" - # `needs:` can be removed after CI image gets nonroot. In this case `needs:` stops other - # artifacts from being dowloaded by this job. + - job: "cargo-check-benches 1/2" + +cancel-pipeline-cargo-check-benches2: + extends: .cancel-pipeline-template needs: - - job: build-rustdoc - artifacts: true - script: - - rm -rf /tmp/* - # Set git config - - rm -rf .git/config - - git config user.email "devops-team@parity.io" - - git config user.name "${GITHUB_USER}" - - git config remote.origin.url "https://${GITHUB_TOKEN}@github.com/paritytech/substrate.git" - - git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" - - git fetch origin gh-pages - # Save README and docs - - cp -r ./crate-docs/ /tmp/doc/ - - cp README.md /tmp/doc/ - - git checkout gh-pages - # Remove everything and restore generated docs and README - - rm -rf ./* - - mv /tmp/doc/* . - # Upload files - - git add --all --force - # `git commit` has an exit code of > 0 if there is nothing to commit. - # This causes GitLab to exit immediately and marks this job failed. - # We don't want to mark the entire job failed if there's nothing to - # publish though, hence the `|| true`. - - git commit -m "Updated docs for ${CI_COMMIT_REF_NAME}" || - echo "___Nothing to commit___" - - git push origin gh-pages --force - after_script: - - rm -rf .git/ ./* + - job: "cargo-check-benches 2/2" -publish-draft-release: - stage: publish - <<: *vault-secrets - image: paritytech/tools:latest - rules: - - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - script: - - ./.maintain/gitlab/publish_draft_release.sh - allow_failure: true +cancel-pipeline-test-linux-stable-int: + extends: .cancel-pipeline-template + needs: + - job: test-linux-stable-int -unleash-to-crates-io: - stage: publish - <<: *docker-env - <<: *vault-secrets - rules: - - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ - # FIXME: wait until https://github.com/paritytech/cargo-unleash/issues/50 is fixed, also - # remove allow_failure: true on the check job - # - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - script: - - cargo install cargo-unleash ${CARGO_UNLEASH_INSTALL_PARAMS} - - cargo unleash em-dragons --no-check --owner github:paritytech:core-devs ${CARGO_UNLEASH_PKG_DEF} - allow_failure: true +cancel-pipeline-cargo-check-each-crate-1: + extends: .cancel-pipeline-template + needs: + - job: "cargo-check-each-crate 1/2" -#### stage: deploy +cancel-pipeline-cargo-check-each-crate-2: + extends: .cancel-pipeline-template + needs: + - job: "cargo-check-each-crate 2/2" -deploy-prometheus-alerting-rules: - stage: deploy +cancel-pipeline-cargo-check-each-crate-macos: + extends: .cancel-pipeline-template needs: - - job: test-prometheus-alerting-rules - artifacts: false - allow_failure: true - trigger: - project: parity/infrastructure/cloud-infra - variables: - SUBSTRATE_CI_COMMIT_NAME: "${CI_COMMIT_REF_NAME}" - SUBSTRATE_CI_COMMIT_REF: "${CI_COMMIT_SHORT_SHA}" - UPSTREAM_TRIGGER_PROJECT: "${CI_PROJECT_PATH}" - rules: - - if: $CI_PIPELINE_SOURCE == "pipeline" - when: never - - if: $CI_COMMIT_REF_NAME == "master" - changes: - - .gitlab-ci.yml - - .maintain/monitoring/**/* - -# Runs "quick" and "long" tests on nightly schedule and on commit / merge to master -# A "quick" test is a smoke test where basic check-expect tests run by -# checking values from metrics exposed by the app. -# A "long" test is the load testing where we send 50K transactions into the -# network and check if all completed successfully -simnet-tests: - stage: deploy - image: docker.io/paritytech/simnet:${SIMNET_REF} - <<: *kubernetes-env - <<: *vault-secrets - rules: - - if: $CI_PIPELINE_SOURCE == "pipeline" - when: never - - if: $CI_PIPELINE_SOURCE == "web" && $CI_COMMIT_REF_NAME == "master" - - if: $CI_COMMIT_REF_NAME == "master" + - job: cargo-check-each-crate-macos + +cancel-pipeline-check-tracing: + extends: .cancel-pipeline-template needs: - - job: publish-docker-substrate - # variables: - # `build.env` brings here `${SUBSTRATE_IMAGE_NAME}` and `${SUBSTRATE_IMAGE_TAG}` - # (`$VERSION` here, # i.e. `2643-0.8.29-5f689e0a-6b24dc54`). - # ${SIMNET_REF} is a gitlab variable - before_script: - - echo "Simnet Tests Config - docker.io/paritytech/simnet:${SIMNET_REF} - ${SUBSTRATE_IMAGE_NAME} ${SUBSTRATE_IAMGE_TAG}" - script: - - /home/nonroot/simnet/gurke/scripts/run-test-environment-manager.sh - --github-remote-dir="https://github.com/paritytech/substrate/tree/master/simnet_tests" - --config="simnet_tests/configs/default_local_testnet.toml" - --image="${SUBSTRATE_IMAGE_NAME}:${SUBSTRATE_IMAGE_TAG}" - retry: 2 - tags: - - parity-simnet + - job: check-tracing diff --git a/.maintain/Dockerfile b/.maintain/Dockerfile deleted file mode 100644 index 21a41720f7d65..0000000000000 --- a/.maintain/Dockerfile +++ /dev/null @@ -1,59 +0,0 @@ -# Note: We don't use Alpine and its packaged Rust/Cargo because they're too often out of date, -# preventing them from being used to build Substrate/Polkadot. - -FROM phusion/baseimage:0.11 as builder -LABEL maintainer="chevdor@gmail.com" -LABEL description="This is the build stage for Substrate. Here we create the binary." - -ENV DEBIAN_FRONTEND=noninteractive - -ARG PROFILE=release -WORKDIR /substrate - -COPY . /substrate - -RUN apt-get update && \ - apt-get dist-upgrade -y -o Dpkg::Options::="--force-confold" && \ - apt-get install -y cmake pkg-config libssl-dev git clang - -RUN curl https://sh.rustup.rs -sSf | sh -s -- -y && \ - export PATH="$PATH:$HOME/.cargo/bin" && \ - rustup toolchain install nightly && \ - rustup target add wasm32-unknown-unknown --toolchain nightly && \ - rustup default stable && \ - cargo build "--$PROFILE" - -# ===== SECOND STAGE ====== - -FROM phusion/baseimage:0.11 -LABEL maintainer="chevdor@gmail.com" -LABEL description="This is the 2nd stage: a very small image where we copy the Substrate binary." -ARG PROFILE=release - -RUN mv /usr/share/ca* /tmp && \ - rm -rf /usr/share/* && \ - mv /tmp/ca-certificates /usr/share/ && \ - useradd -m -u 1000 -U -s /bin/sh -d /substrate substrate && \ - mkdir -p /substrate/.local/share/substrate && \ - chown -R substrate:substrate /substrate/.local && \ - ln -s /substrate/.local/share/substrate /data - -COPY --from=builder /substrate/target/$PROFILE/substrate /usr/local/bin -COPY --from=builder /substrate/target/$PROFILE/subkey /usr/local/bin -COPY --from=builder /substrate/target/$PROFILE/node-rpc-client /usr/local/bin -COPY --from=builder /substrate/target/$PROFILE/node-template /usr/local/bin -COPY --from=builder /substrate/target/$PROFILE/chain-spec-builder /usr/local/bin - -# checks -RUN ldd /usr/local/bin/substrate && \ - /usr/local/bin/substrate --version - -# Shrinking -RUN rm -rf /usr/lib/python* && \ - rm -rf /usr/bin /usr/sbin /usr/share/man - -USER substrate -EXPOSE 30333 9933 9944 9615 -VOLUME ["/data"] - -CMD ["/usr/local/bin/substrate"] diff --git a/.maintain/docs-index-tpl.ejs b/.maintain/docs-index-tpl.ejs new file mode 100644 index 0000000000000..81c619a926b2b --- /dev/null +++ b/.maintain/docs-index-tpl.ejs @@ -0,0 +1,55 @@ +<% + const capFirst = s => (s && s[0].toUpperCase() + s.slice(1)) || ""; +%> + + + + + + + + <%= capFirst(repo_name) %> Rustdocs + + + + + + +
+
+

<%= capFirst(repo_name) %> Rustdocs

+
+
    + <%_ deploy_refs.split(/\s+/).forEach(ref => { _%> +
  • + <%- ref -%> + <%_ if (latest && latest.trim() !== '' && latest === ref) { _%> + (latest) + <%_ } _%> +
  • + <%_ }) _%> +
+
+
+
+ + diff --git a/.maintain/frame-weight-template.hbs b/.maintain/frame-weight-template.hbs index 045140d54dff7..9c9e297800869 100644 --- a/.maintain/frame-weight-template.hbs +++ b/.maintain/frame-weight-template.hbs @@ -1,28 +1,13 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 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. - +{{header}} //! Autogenerated weights for {{pallet}} //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} //! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` //! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} // Executed Command: -{{#each args as |arg|~}} +{{#each args as |arg|}} // {{arg}} {{/each}} @@ -35,80 +20,88 @@ use sp_std::marker::PhantomData; /// Weight functions needed for {{pallet}}. pub trait WeightInfo { - {{~#each benchmarks as |benchmark|}} + {{#each benchmarks as |benchmark|}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} {{c.name}}: u32, {{/each~}} ) -> Weight; - {{~/each}} + {{/each}} } /// Weights for {{pallet}} using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); -{{~#if (eq pallet "frame_system")}} +{{#if (eq pallet "frame_system")}} impl WeightInfo for SubstrateWeight { -{{~else}} +{{else}} impl WeightInfo for SubstrateWeight { -{{~/if}} - {{~#each benchmarks as |benchmark|}} - {{~#each benchmark.comments as |comment|}} +{{/if}} + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} // {{comment}} - {{~/each}} + {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} ) -> Weight { - ({{underscore benchmark.base_weight}} as Weight) - {{~#each benchmark.component_weight as |cw|}} + // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. + Weight::from_ref_time({{underscore benchmark.base_weight}}) + {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)) - {{~/each}} - {{~#if (ne benchmark.base_reads "0")}} - .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}} as Weight)) - {{~/if}} - {{~#each benchmark.component_reads as |cr|}} - .saturating_add(T::DbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight))) - {{~/each}} - {{~#if (ne benchmark.base_writes "0")}} - .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}} as Weight)) - {{~/if}} - {{~#each benchmark.component_writes as |cw|}} - .saturating_add(T::DbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))) - {{~/each}} + .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} } - {{~/each}} + {{/each}} } // For backwards compatibility and tests impl WeightInfo for () { - {{~#each benchmarks as |benchmark|}} - {{~#each benchmark.comments as |comment|}} + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} // {{comment}} - {{~/each}} + {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} ) -> Weight { - ({{underscore benchmark.base_weight}} as Weight) - {{~#each benchmark.component_weight as |cw|}} + // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. + Weight::from_ref_time({{underscore benchmark.base_weight}}) + {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)) - {{~/each}} - {{~#if (ne benchmark.base_reads "0")}} - .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}} as Weight)) - {{~/if}} - {{~#each benchmark.component_reads as |cr|}} - .saturating_add(RocksDbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight))) - {{~/each}} - {{~#if (ne benchmark.base_writes "0")}} - .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}} as Weight)) - {{~/if}} - {{~#each benchmark.component_writes as |cw|}} - .saturating_add(RocksDbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))) - {{~/each}} + .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}})) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(RocksDbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}})) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(RocksDbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} } - {{~/each}} + {{/each}} } diff --git a/.maintain/gitlab/check_line_width.sh b/.maintain/gitlab/check_line_width.sh deleted file mode 100755 index ebab3013e4b48..0000000000000 --- a/.maintain/gitlab/check_line_width.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/sh -# -# check if line width of rust source files is not beyond x characters -# -set -e -set -o pipefail - -BASE_ORIGIN="origin" -BASE_BRANCH_NAME="master" -LINE_WIDTH="120" -GOOD_LINE_WIDTH="100" -BASE_BRANCH="${BASE_ORIGIN}/${BASE_BRANCH_NAME}" -git fetch ${BASE_ORIGIN} ${BASE_BRANCH_NAME} --depth 100 -BASE_HASH=$(git merge-base ${BASE_BRANCH} HEAD) - -git diff --name-only ${BASE_HASH} -- \*.rs | ( while read file -do - if [ ! -f ${file} ]; - then - echo "Skipping removed file." - elif git diff ${BASE_HASH} -- ${file} | grep -q "^+.\{$(( $LINE_WIDTH + 1 ))\}" - then - if [ -z "${FAIL}" ] - then - echo "| error!" - echo "| Lines must not be longer than ${LINE_WIDTH} characters." - echo "| " - echo "| see more https://github.com/paritytech/substrate/blob/master/docs/STYLE_GUIDE.md" - echo "|" - FAIL="true" - fi - echo "| file: ${file}" - git diff ${BASE_HASH} -- ${file} \ - | grep -n "^+.\{$(( $LINE_WIDTH + 1))\}" - echo "|" - else - if git diff ${BASE_HASH} -- ${file} | grep -q "^+.\{$(( $GOOD_LINE_WIDTH + 1 ))\}" - then - if [ -z "${FAIL}" ] - then - echo "| warning!" - echo "| Lines should be longer than ${GOOD_LINE_WIDTH} characters only in exceptional circumstances!" - echo "| " - echo "| see more https://github.com/paritytech/substrate/blob/master/docs/STYLE_GUIDE.md" - echo "|" - fi - echo "| file: ${file}" - git diff ${BASE_HASH} -- ${file} | grep -n "^+.\{$(( $GOOD_LINE_WIDTH + 1 ))\}" - echo "|" - fi - fi -done - -test -z "${FAIL}" -) diff --git a/.maintain/gitlab/check_polkadot_companion_build.sh b/.maintain/gitlab/check_polkadot_companion_build.sh deleted file mode 100755 index 72bfaf7151522..0000000000000 --- a/.maintain/gitlab/check_polkadot_companion_build.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env sh -# -# check if a pr is compatible with polkadot companion pr or master if not -# available -# -# to override one that was just mentioned mark companion pr in the body of the -# polkadot pr like -# -# polkadot companion: paritytech/polkadot#567 -# - -set -e - -github_api_substrate_pull_url="https://api.github.com/repos/paritytech/substrate/pulls" -# use github api v3 in order to access the data without authentication -github_header="Authorization: token ${GITHUB_PR_TOKEN}" - -boldprint () { printf "|\n| \033[1m${@}\033[0m\n|\n" ; } -boldcat () { printf "|\n"; while read l; do printf "| \033[1m${l}\033[0m\n"; done; printf "|\n" ; } - - - -boldcat <<-EOT - - -check_polkadot_companion_build -============================== - -this job checks if there is a string in the description of the pr like - -polkadot companion: paritytech/polkadot#567 - - -it will then run cargo check from this polkadot's branch with substrate code -from this pull request. otherwise, it will uses master instead - - -EOT - -# Set the user name and email to make merging work -git config --global user.name 'CI system' -git config --global user.email '<>' - -# Merge master into our branch before building Polkadot to make sure we don't miss -# any commits that are required by Polkadot. -git fetch --depth 100 origin -git merge origin/master - -# Clone the current Polkadot master branch into ./polkadot. -# NOTE: we need to pull enough commits to be able to find a common -# ancestor for successfully performing merges below. -git clone --depth 20 https://github.com/paritytech/polkadot.git - -cd polkadot - -# either it's a pull request then check for a companion otherwise use -# polkadot:master -if expr match "${CI_COMMIT_REF_NAME}" '^[0-9]\+$' >/dev/null -then - boldprint "this is pull request no ${CI_COMMIT_REF_NAME}" - - pr_data_file="$(mktemp)" - # get the last reference to a pr in polkadot - curl -sSL -H "${github_header}" -o "${pr_data_file}" \ - "${github_api_substrate_pull_url}/${CI_COMMIT_REF_NAME}" - - pr_body="$(sed -n -r 's/^[[:space:]]+"body": (".*")[^"]+$/\1/p' "${pr_data_file}")" - - pr_companion="$(echo "${pr_body}" | sed -n -r \ - -e 's;^.*[Cc]ompanion.*paritytech/polkadot#([0-9]+).*$;\1;p' \ - -e 's;^.*[Cc]ompanion.*https://github.com/paritytech/polkadot/pull/([0-9]+).*$;\1;p' \ - | tail -n 1)" - - if [ "${pr_companion}" ] - then - boldprint "companion pr specified/detected: #${pr_companion}" - git fetch origin refs/pull/${pr_companion}/head:pr/${pr_companion} - git checkout pr/${pr_companion} - git merge origin/master - else - boldprint "no companion branch found - building polkadot:master" - fi - rm -f "${pr_data_file}" -else - boldprint "this is not a pull request - building polkadot:master" -fi - -# Patch all Substrate crates in Polkadot -diener patch --crates-to-patch ../ --substrate --path Cargo.toml - -# We need to update specifically our patched Substrate crates so that other -# crates that depend on them (e.g. Polkadot, BEEFY) use this unified version -# NOTE: There's no way to only update patched crates, so we use a heuristic -# of updating a crucial Substrate crate (`sp-core`) to minimize the impact of -# updating unrelated dependencies -cargo update -p sp-core - -# Test Polkadot pr or master branch with this Substrate commit. -time cargo test --workspace --release --verbose --features=runtime-benchmarks diff --git a/.maintain/gitlab/check_polkadot_companion_status.sh b/.maintain/gitlab/check_polkadot_companion_status.sh deleted file mode 100755 index e0412c7b7bec7..0000000000000 --- a/.maintain/gitlab/check_polkadot_companion_status.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/sh -# -# check for a polkadot companion pr and ensure it has approvals and is -# mergeable -# - -github_api_substrate_pull_url="https://api.github.com/repos/paritytech/substrate/pulls" -github_api_polkadot_pull_url="https://api.github.com/repos/paritytech/polkadot/pulls" -# use github api v3 in order to access the data without authentication -github_header="Authorization: token ${GITHUB_PR_TOKEN}" - -boldprint () { printf "|\n| \033[1m${@}\033[0m\n|\n" ; } -boldcat () { printf "|\n"; while read l; do printf "| \033[1m${l}\033[0m\n"; done; printf "|\n" ; } - - - -boldcat <<-EOT - - -check_polkadot_companion_status -=============================== - -this job checks if there is a string in the description of the pr like - -polkadot companion: paritytech/polkadot#567 - -and checks its status. - - -EOT - - -if ! [ "${CI_COMMIT_REF_NAME}" -gt 0 2>/dev/null ] -then - boldprint "this doesn't seem to be a pull request" - exit 1 -fi - -boldprint "this is pull request no ${CI_COMMIT_REF_NAME}" - -pr_body="$(curl -H "${github_header}" -s ${github_api_substrate_pull_url}/${CI_COMMIT_REF_NAME} \ - | sed -n -r 's/^[[:space:]]+"body": (".*")[^"]+$/\1/p')" - -# get companion if explicitly specified -pr_companion="$(echo "${pr_body}" | sed -n -r \ - -e 's;^.*[Cc]ompanion.*paritytech/polkadot#([0-9]+).*$;\1;p' \ - -e 's;^.*[Cc]ompanion.*https://github.com/paritytech/polkadot/pull/([0-9]+).*$;\1;p' \ - | tail -n 1)" - -if [ -z "${pr_companion}" ] -then - boldprint "no companion pr found" - exit 0 -fi - -boldprint "companion pr: #${pr_companion}" - -# check the status of that pull request - needs to be -# approved and mergable - -curl -H "${github_header}" -sS -o companion_pr.json \ - ${github_api_polkadot_pull_url}/${pr_companion} - -pr_head_sha=$(jq -r -e '.head.sha' < companion_pr.json) -boldprint "Polkadot PR's HEAD SHA: $pr_head_sha" - -curl -H "${github_header}" -sS -o companion_pr_reviews.json \ - ${github_api_polkadot_pull_url}/${pr_companion}/reviews - -# If there are any 'CHANGES_REQUESTED' reviews for the *current* review -jq -r -e '.[] | select(.state == "CHANGES_REQUESTED").commit_id' \ - < companion_pr_reviews.json > companion_pr_reviews_current.json -while IFS= read -r line; do - if [ "$line" = "$pr_head_sha" ]; then - boldprint "polkadot pr #${pr_companion} has CHANGES_REQUESTED for the latest commit" - exit 1 - fi -done < companion_pr_reviews_current.json - -# Then we check for at least 1 APPROVED -if [ -z "$(jq -r -e '.[].state | select(. == "APPROVED")' < companion_pr_reviews.json)" ]; then - boldprint "polkadot pr #${pr_companion} not APPROVED" - exit 1 -fi - -boldprint "polkadot pr #${pr_companion} state APPROVED" - -if jq -e .merged < companion_pr.json >/dev/null -then - boldprint "polkadot pr #${pr_companion} already merged" - exit 0 -fi - -if jq -e '.mergeable' < companion_pr.json >/dev/null -then - boldprint "polkadot pr #${pr_companion} mergeable" -else - boldprint "polkadot pr #${pr_companion} not mergeable" - exit 1 -fi - -exit 0 diff --git a/.maintain/rustdoc-header.html b/.maintain/rustdoc-header.html deleted file mode 100644 index a679d5e299da7..0000000000000 --- a/.maintain/rustdoc-header.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/.maintain/rustdocs-release.sh b/.maintain/rustdocs-release.sh new file mode 100755 index 0000000000000..2a1e141e63ad2 --- /dev/null +++ b/.maintain/rustdocs-release.sh @@ -0,0 +1,244 @@ +#!/usr/bin/env bash +# set -x + +# This script manages the deployment of Substrate rustdocs to https://paritytech.github.io/substrate/. +# - With `deploy` sub-command, it will checkout the passed-in branch/tag ref, build the rustdocs +# locally (this takes some time), update the `index.html` index page, and push it to remote +# `gh-pages` branch. So users running this command need to have write access to the remote +# `gh-pages` branch. This sub-command depends on [@substrate/index-tpl-crud](https://www.npmjs.com/package/@substrate/index-tpl-crud) +# to update the DOM of index.html page. +# - With `remove` sub-command, it will remove the deployed rustdocs from `gh-pages`, and update the +# index.html page as necessary. It may remove the `latest` symbolic link. +# +# Examples: +# # Showing help text +# rustdocs-release.sh -h +# +# # Deploy rustdocs of `monthly-2021-10` tag +# rustdocs-release.sh deploy monthly-2021-10 +# +# # In addition to the above, the `latest` symlink will point to this version of rustdocs +# rustdocs-release.sh deploy -l monthly-2021-10 +# +# # Remove the rustdocs of `monthly-2021-10` from `gh-pages`. +# rustdocs-release.sh remove monthly-2021-10 +# +# Dependencies: +# - @substrate/index-tpl-crud - https://www.npmjs.com/package/@substrate/index-tpl-crud +# + +# Script setting +# The git repo http URL +REMOTE_REPO="https://github.com/paritytech/substrate.git" +TMP_PREFIX="/tmp" # tmp location that the built doc is copied to. +DOC_INDEX_PAGE="sc_service/index.html" + +# Set to `true` if using cargo `nightly` toolchain to build the doc. +# Set to `false` to use the default cargo toolchain. This is preferred if you want to build +# the rustdocs with a pinned nightly version set to your default toolchain. +CARGO_NIGHTLY=false + +# Set the git remote name. Most of the time the default is `origin`. +GIT_REMOTE="origin" +LATEST=false + +# Setting the help text +declare -A HELP_TXT +HELP_TXT["deploy"]=$(cat <<-EOH +Build and deploy the rustdocs of the specified branch/tag to \`gh-pages\` branch. + + usage: $0 deploy [-l] + example: $0 deploy -l monthly-2021-10 + + options: + -l The \`latest\` path will be sym'linked to this rustdocs version +EOH +) + +HELP_TXT["remove"]=$(cat <<-EOH +Remove the rustdocs of the specified version from \`gh-pages\` branch. + + usage: $0 remove + example: $0 remove monthly-2021-10 +EOH +) + +set_and_check_rustdoc_ref() { + [[ -z "$1" ]] && { + echo -e "git branch_ref is not specified.\n" + echo "${HELP_TXT[$2]}" + exit 1 + } + BUILD_RUSTDOC_REF=$1 +} + +check_local_change() { + # Check there is no local changes before proceeding + [[ -n $(git status --porcelain) ]] \ + && echo "Local changes exist, please either discard or commit them as this command will change the current checkout branch." \ + && exit 1 +} + +build_rustdocs() { + # Build the docs + time cargo $($CARGO_NIGHTLY && echo "+nightly") doc --workspace --all-features --verbose \ + || { echo "Generate $1 rustdocs failed" && exit 1; } + rm -f target/doc/.lock + + # Moving the built doc to the tmp location + mv target/doc "${2}" + [[ -n "${DOC_INDEX_PAGE}" ]] \ + && echo "" > "${2}/index.html" +} + +upsert_index_page() { + # Check if `index-tpl-crud` exists + which index-tpl-crud &> /dev/null || yarn global add @substrate/index-tpl-crud + index-tpl-crud upsert $($1 && echo "-l") ./index.html "$2" +} + +rm_index_page() { + which index-tpl-crud &> /dev/null || yarn global add @substrate/index-tpl-crud + index-tpl-crud rm ./index.html "$1" +} + +git_add_commit_push() { + git add --all + git commit -m "$1" || echo "Nothing to commit" + git push "${GIT_REMOTE}" gh-pages --force +} + +import_gh_key() { + [[ -n $GITHUB_SSH_PRIV_KEY ]] && { + eval $(ssh-agent) + ssh-add - <<< $GITHUB_SSH_PRIV_KEY + } + + # Adding github.com as known_hosts + ssh-keygen -F github.com &>/dev/null || { + [[ -e ~/.ssh ]] || mkdir ~/.ssh + [[ -e ~/.ssh/known_hosts ]] || touch ~/.ssh/known_hosts + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + } +} + +deploy_main() { + check_local_change + import_gh_key + + CURRENT_GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + TMP_PROJECT_PATH="${TMP_PREFIX}/${PROJECT_NAME}" + DOC_PATH="${TMP_PROJECT_PATH}/${BUILD_RUSTDOC_REF}" + + # Build the tmp project path + rm -rf "${TMP_PROJECT_PATH}" && mkdir "${TMP_PROJECT_PATH}" + + # Copy .gitignore file to tmp + [[ -e "${PROJECT_PATH}/.gitignore" ]] && cp "${PROJECT_PATH}/.gitignore" "${TMP_PROJECT_PATH}" + + git fetch --all + git checkout -f ${BUILD_RUSTDOC_REF} || { echo "Checkout \`${BUILD_RUSTDOC_REF}\` error." && exit 1; } + build_rustdocs "${BUILD_RUSTDOC_REF}" "${DOC_PATH}" + + # git checkout `gh-pages` branch + git fetch "${GIT_REMOTE}" gh-pages + git checkout gh-pages + # Move the built back + [[ -e "${TMP_PROJECT_PATH}/.gitignore" ]] && cp -f "${TMP_PROJECT_PATH}/.gitignore" . + # Ensure the destination dir doesn't exist under current path. + rm -rf "${BUILD_RUSTDOC_REF}" + mv -f "${DOC_PATH}" "${BUILD_RUSTDOC_REF}" + + upsert_index_page $LATEST "${BUILD_RUSTDOC_REF}" + # Add the latest symlink + $LATEST && rm -rf latest && ln -sf "${BUILD_RUSTDOC_REF}" latest + + git_add_commit_push "___Deployed rustdocs of ${BUILD_RUSTDOC_REF}___" + # Clean up + # Remove the tmp asset created + rm -rf "${TMP_PROJECT_PATH}" + # Resume back previous checkout branch. + git checkout -f "$CURRENT_GIT_BRANCH" +} + +remove_main() { + check_local_change + import_gh_key + + CURRENT_GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + + # git checkout `gh-pages` branch + git fetch "${GIT_REMOTE}" gh-pages + git checkout gh-pages + + rm -rf "${BUILD_RUSTDOC_REF}" + rm_index_page "${BUILD_RUSTDOC_REF}" + # check if the destination of `latest` exists and rmove if not. + [[ -e "latest" ]] || rm latest + + git_add_commit_push "___Removed rustdocs of ${BUILD_RUSTDOC_REF}___" + + # Resume back previous checkout branch. + git checkout -f "$CURRENT_GIT_BRANCH" +} + +# ---- The script execution entry point starts here ---- + +# Arguments handling +SUBCMD=$1 +[[ $SUBCMD == "deploy" || $SUBCMD == "remove" ]] \ + || { echo "Please specify a subcommand of \`deploy\` or \`remove\`" && exit 1 ;} +shift + +# After removing the subcommand, there could only be 1 or 2 parameters afterward +[[ $# -lt 1 || $# -gt 2 ]] && { + echo "${HELP_TXT[${SUBCMD}]}" + exit 1 +} + +# Parsing options and argument for `deploy` subcommand +[[ $SUBCMD == "deploy" ]] && { + while getopts :lh opt; do + case $opt in + l) + LATEST=true + ;; + h) + echo "${HELP_TXT[$SUBCMD]}" + exit 0 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac + done +} +# Parsing options and argument for `remove` subcommand +[[ $SUBCMD == "remove" ]] && { + while getopts :h opt; do + case $opt in + h) + echo "${HELP_TXT[$SUBCMD]}" + exit 0 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac + done +} + +shift $(($OPTIND - 1)) +set_and_check_rustdoc_ref ${1:-''} $SUBCMD + +SCRIPT=$(realpath $0) +SCRIPT_PATH=$(dirname $SCRIPT) +PROJECT_PATH=$(dirname ${SCRIPT_PATH}) +PROJECT_NAME=$(basename "$PROJECT_PATH") + +pushd "${PROJECT_PATH}" &>/dev/null +[[ $SUBCMD == "deploy" ]] && deploy_main +[[ $SUBCMD == "remove" ]] && remove_main +popd &>/dev/null diff --git a/.maintain/update-rust-stable.sh b/.maintain/update-rust-stable.sh new file mode 100755 index 0000000000000..b253bb4105313 --- /dev/null +++ b/.maintain/update-rust-stable.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# Script for updating the UI tests for a new rust stable version. +# +# It needs to be called like this: +# +# update-rust-stable.sh 1.61 +# +# This will run all UI tests with the rust stable 1.61. The script +# requires that rustup is installed. +set -e + +if [ "$#" -ne 1 ]; then + echo "Please specify the rust version to use. E.g. update-rust-stable.sh 1.61" + exit +fi + +RUST_VERSION=$1 + +if ! command -v rustup &> /dev/null +then + echo "rustup needs to be installed" + exit +fi + +rustup install $RUST_VERSION +rustup component add rust-src --toolchain $RUST_VERSION + +# Ensure we run the ui tests +export RUN_UI_TESTS=1 +# We don't need any wasm files for ui tests +export SKIP_WASM_BUILD=1 +# Let trybuild overwrite the .stderr files +export TRYBUILD=overwrite + +# Run all the relevant UI tests +# +# Any new UI tests in different crates need to be added here as well. +rustup run $RUST_VERSION cargo test -p sp-runtime-interface ui +rustup run $RUST_VERSION cargo test -p sp-api-test ui +rustup run $RUST_VERSION cargo test -p frame-election-provider-solution-type ui +rustup run $RUST_VERSION cargo test -p frame-support-test ui diff --git a/Cargo.lock b/Cargo.lock index 7754e0ae6b62f..2f2b4aa7cf35d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,20 +14,11 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" -dependencies = [ - "gimli 0.23.0", -] - -[[package]] -name = "addr2line" -version = "0.15.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03345e98af8f3d786b6d9f656ccfa6ac316d954e92bc4841f0bba20789d5fb5a" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.24.0", + "gimli", ] [[package]] @@ -38,20 +29,20 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3e798aa0c8239776f54415bc06f3d74b1850f3f830b45c35cfc80556973f70" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ "generic-array 0.14.4", ] [[package]] name = "aes" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495ee669413bfbe9e8cace80f4d3d78e6d8c8d99579f97fb93bde351b185f2d4" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher", "cpufeatures", "opaque-debug 0.3.0", @@ -59,61 +50,52 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a930fd487faaa92a30afa92cc9dd1526a5cff67124abbbb1c617ce070f4dcf" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" dependencies = [ "aead", "aes", "cipher", "ctr", "ghash", - "subtle 2.4.0", + "subtle", ] [[package]] name = "ahash" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ "getrandom 0.2.3", "once_cell", - "version_check 0.9.2", + "version_check", ] [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] name = "anyhow" -version = "1.0.38" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" [[package]] name = "approx" @@ -131,19 +113,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "698b65a961a9d730fb45b6b0327e20207810c9f61ee421b082b27ba003f49e2b" [[package]] -name = "arrayref" -version = "0.3.6" +name = "array-bytes" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "6a913633b0c922e6b745072795f50d90ebea78ba31a57e2ac8c2fc7b50950949" [[package]] -name = "arrayvec" -version = "0.4.12" +name = "arrayref" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" -dependencies = [ - "nodrop", -] +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" @@ -153,9 +132,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a2f58b0bb10c380af2b26e57212856b8c9a59e0925b4c20f4a174a49734eaf7" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "asn1_der" @@ -165,11 +144,11 @@ checksum = "9d6e24d2cce90c53b948c46271bfb053e4bdc2db9b5d3f65e20f8cf28a1b7fc3" [[package]] name = "assert_cmd" -version = "1.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2475b58cd94eb4f70159f4fd8844ba3b807532fe3131b3373fae060bbe30396" +checksum = "ba45b8163c49ab5f972e59a8a5a03b6d2972619d486e19ec9fe744f7c2753d3c" dependencies = [ - "bstr", + "bstr 1.0.1", "doc-comment", "predicates", "predicates-core", @@ -183,16 +162,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "async-channel" version = "1.6.1" @@ -236,29 +205,28 @@ dependencies = [ [[package]] name = "async-io" -version = "1.3.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9315f8f07556761c3e48fec2e6b276004acf426e6dc068b2c2251854d65ee0fd" +checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" dependencies = [ "concurrent-queue", - "fastrand", "futures-lite", "libc", - "log 0.4.14", - "nb-connect", + "log", "once_cell", "parking", "polling", - "vec-arena", + "slab", + "socket2", "waker-fn", - "winapi 0.3.9", + "winapi", ] [[package]] name = "async-lock" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1996609732bde4a9988bc42125f55f2af5f3c36370e27c778d5191a4a1b63bfb" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" dependencies = [ "event-listener", ] @@ -273,63 +241,24 @@ dependencies = [ ] [[package]] -name = "async-process" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b21b63ab5a0db0369deb913540af2892750e42d949faacc7a61495ac418a1692" -dependencies = [ - "async-io", - "blocking", - "cfg-if 1.0.0", - "event-listener", - "futures-lite", - "libc", - "once_cell", - "signal-hook", - "winapi 0.3.9", -] - -[[package]] -name = "async-std" -version = "1.9.0" +name = "async-stream" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f06685bad74e0570f5213741bea82158279a4103d988e57bfada11ad230341" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" dependencies = [ - "async-attributes", - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "async-process", - "crossbeam-utils 0.8.3", - "futures-channel", + "async-stream-impl", "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log 0.4.14", - "memchr", - "num_cpus", - "once_cell", - "pin-project-lite 0.2.6", - "pin-utils", - "slab", - "wasm-bindgen-futures", ] [[package]] -name = "async-std-resolver" -version = "0.20.1" +name = "async-stream-impl" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665c56111e244fe38e7708ee10948a4356ad6a548997c21f5a63a0f4e0edc4d" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" dependencies = [ - "async-std", - "async-trait", - "futures-io", - "futures-util", - "pin-utils", - "trust-dns-resolver", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -340,48 +269,26 @@ checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" [[package]] name = "async-trait" -version = "0.1.51" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "asynchronous-codec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4401f0a3622dad2e0763fa79e0eb328bc70fb7dccfdd645341f00d671247d6" -dependencies = [ - "bytes 1.0.1", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite 0.2.6", -] - [[package]] name = "asynchronous-codec" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" dependencies = [ - "bytes 1.0.1", + "bytes", "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.6", -] - -[[package]] -name = "atomic" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281" -dependencies = [ - "autocfg 1.0.1", + "pin-project-lite 0.2.9", ] [[package]] @@ -398,48 +305,30 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - -[[package]] -name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.56" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" dependencies = [ - "addr2line 0.14.1", - "cfg-if 1.0.0", + "addr2line", + "cc", + "cfg-if", "libc", "miniz_oxide", - "object 0.23.0", + "object 0.27.1", "rustc-demangle", ] -[[package]] -name = "bae" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107f431ee3d8a8e45e6dd117adab769556ef463959e77bf6a4888d5fd500cf" -dependencies = [ - "heck", - "proc-macro-error 0.4.12", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "base-x" version = "0.2.8" @@ -447,66 +336,132 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" [[package]] -name = "base58" -version = "0.1.0" +name = "base16ct" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] -name = "base64" -version = "0.9.3" +name = "base58" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -dependencies = [ - "byteorder", - "safemem", -] +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" [[package]] name = "base64" -version = "0.10.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] -name = "base64" -version = "0.12.3" +name = "base64ct" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" [[package]] -name = "base64" -version = "0.13.0" +name = "beef" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" +dependencies = [ + "serde", +] [[package]] -name = "beef" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409" +name = "beefy-gadget" +version = "4.0.0-dev" +dependencies = [ + "array-bytes", + "async-trait", + "fnv", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-block-builder", + "sc-chain-spec", + "sc-client-api", + "sc-consensus", + "sc-finality-grandpa", + "sc-keystore", + "sc-network", + "sc-network-common", + "sc-network-gossip", + "sc-network-test", + "sc-utils", + "serde", + "sp-api", + "sp-application-crypto", + "sp-arithmetic", + "sp-beefy", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-finality-grandpa", + "sp-keyring", + "sp-keystore", + "sp-mmr-primitives", + "sp-runtime", + "sp-tracing", + "strum", + "substrate-prometheus-endpoint", + "substrate-test-runtime-client", + "tempfile", + "thiserror", + "tokio", + "wasm-timer", +] + +[[package]] +name = "beefy-gadget-rpc" +version = "4.0.0-dev" dependencies = [ + "beefy-gadget", + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-rpc", + "sc-utils", "serde", + "serde_json", + "sp-beefy", + "sp-core", + "sp-runtime", + "substrate-test-runtime-client", + "thiserror", + "tokio", +] + +[[package]] +name = "beefy-merkle-tree" +version = "4.0.0-dev" +dependencies = [ + "array-bytes", + "env_logger", + "log", + "sp-api", + "sp-beefy", + "sp-runtime", ] [[package]] name = "bincode" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d175dfa69e619905c4c3cdb7c3c203fa3bdd5d51184e3afdb2742c0280493772" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "byteorder", "serde", ] [[package]] name = "bindgen" -version = "0.59.1" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" dependencies = [ "bitflags", "cexpr", @@ -529,84 +484,58 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" -dependencies = [ - "funty", - "radium 0.5.3", - "tap", - "wyz", -] - -[[package]] -name = "bitvec" -version = "0.20.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f682656975d3a682daff957be4ddeb65d6ad656737cd821f2d00685ae466af1" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" dependencies = [ "funty", - "radium 0.6.2", + "radium", "tap", "wyz", ] [[package]] name = "blake2" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a5720225ef5daecf08657f23791354e1685a8c91a4c60c7f3d3b2892f978f4" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "blake2-rfc" -version = "0.2.18" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ - "arrayvec 0.4.12", - "constant_time_eq", + "digest 0.10.3", ] [[package]] name = "blake2b_simd" -version = "0.5.11" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" dependencies = [ "arrayref", - "arrayvec 0.5.2", + "arrayvec 0.7.2", "constant_time_eq", ] [[package]] name = "blake2s_simd" -version = "0.5.11" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" dependencies = [ "arrayref", - "arrayvec 0.5.2", + "arrayvec 0.7.2", "constant_time_eq", ] [[package]] name = "blake3" -version = "0.3.7" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9ff35b701f3914bdb8fad3368d822c766ef2858b2583198e41639b936f09d3f" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" dependencies = [ "arrayref", - "arrayvec 0.5.2", + "arrayvec 0.7.2", "cc", - "cfg-if 0.1.10", + "cfg-if", "constant_time_eq", - "crypto-mac 0.8.0", - "digest 0.9.0", ] [[package]] @@ -615,7 +544,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "block-padding 0.1.5", + "block-padding", "byte-tools", "byteorder", "generic-array 0.12.4", @@ -627,24 +556,26 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding 0.2.1", "generic-array 0.14.4", ] [[package]] -name = "block-padding" -version = "0.1.5" +name = "block-buffer" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" dependencies = [ - "byte-tools", + "generic-array 0.14.4", ] [[package]] name = "block-padding" -version = "0.2.1" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] [[package]] name = "blocking" @@ -678,6 +609,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bstr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd" +dependencies = [ + "memchr", + "once_cell", + "regex-automata", + "serde", +] + [[package]] name = "build-helper" version = "0.1.1" @@ -707,31 +650,26 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "0.4.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "iovec", -] - -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] -name = "bytes" -version = "1.0.1" +name = "bzip2-sys" +version = "0.1.11+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] [[package]] name = "cache-padded" @@ -759,14 +697,13 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.13.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081e3f0755c1f380c2d010481b6fa2e02973586d5f2b24eebb7a2a1d98b143d8" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 0.11.0", - "semver-parser 0.10.2", + "semver 1.0.4", "serde", "serde_json", ] @@ -782,27 +719,30 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.67" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] [[package]] name = "cexpr" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] -name = "cfg-if" -version = "0.1.10" +name = "cfg-expr" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" +dependencies = [ + "smallvec", +] [[package]] name = "cfg-if" @@ -810,13 +750,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chacha20" -version = "0.7.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher", "cpufeatures", "zeroize", @@ -824,9 +770,9 @@ dependencies = [ [[package]] name = "chacha20poly1305" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", @@ -839,14 +785,14 @@ dependencies = [ name = "chain-spec-builder" version = "2.0.0" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", + "clap 4.0.11", "node-cli", - "rand 0.7.3", + "rand 0.8.5", "sc-chain-spec", "sc-keystore", "sp-core", "sp-keystore", - "structopt", ] [[package]] @@ -859,18 +805,20 @@ dependencies = [ "num-integer", "num-traits", "time", - "winapi 0.3.9", + "winapi", ] [[package]] name = "cid" -version = "0.6.1" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff0e3bc0b6446b3f9663c1a6aba6ef06c5aeaa1bc92bd18077be337198ab9768" +checksum = "f6ed9c8b2d17acb8110c46f1da5bf4a696d745e1474a16db0cd2b49cd0249bf2" dependencies = [ + "core2", "multibase", - "multihash 0.13.2", - "unsigned-varint 0.5.1", + "multihash", + "serde", + "unsigned-varint", ] [[package]] @@ -884,11 +832,11 @@ dependencies = [ [[package]] name = "ckb-merkle-mountain-range" -version = "0.3.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e486fe53bb9f2ca0f58cb60e8679a5354fd6687a839942ef0a75967250289ca6" +checksum = "56ccb671c5921be8a84686e6212ca184cb1d7c51cadcdbfcbd1cc3f042f5dfb8" dependencies = [ - "cfg-if 0.1.10", + "cfg-if", ] [[package]] @@ -899,31 +847,85 @@ checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" dependencies = [ "glob", "libc", - "libloading 0.7.0", + "libloading", ] [[package]] name = "clap" -version = "2.33.3" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "ansi_term 0.11.0", - "atty", "bitflags", - "strsim 0.8.0", "textwrap", "unicode-width", - "vec_map", ] [[package]] -name = "cloudabi" -version = "0.0.3" +name = "clap" +version = "4.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +checksum = "4ed45cc2c62a3eff523e718d8576ba762c83a3146151093283ac62ae11933a73" dependencies = [ + "atty", "bitflags", + "clap_derive", + "clap_lex", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_complete" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0fba905b035a30d25c1b585bf1171690712fbb0ad3ac47214963aa4acc36c" +dependencies = [ + "clap 4.0.11", +] + +[[package]] +name = "clap_derive" +version = "4.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db342ce9fda24fb191e2ed4e102055a4d381c1086a06630174cd8da8d5d917ce" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "comfy-table" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121d8a5b0346092c18a4b2fd6f620d7a06f0eb7ac0a45860939a0884bc579c56" +dependencies = [ + "strum", + "strum_macros", + "unicode-width", ] [[package]] @@ -935,6 +937,12 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "const-oid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -943,9 +951,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core-foundation" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -953,9 +961,18 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] [[package]] name = "cpp_demangle" @@ -963,15 +980,15 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44919ecaf6f99e8e737bc239408931c9a01e9a6c74814fee8242dd2506b65390" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "glob", ] [[package]] name = "cpufeatures" -version = "0.1.5" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ "libc", ] @@ -984,161 +1001,100 @@ checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" [[package]] name = "cranelift-bforest" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9221545c0507dc08a62b2d8b5ffe8e17ac580b0a74d1813b496b8d70b070fbd0" -dependencies = [ - "cranelift-entity 0.68.0", -] - -[[package]] -name = "cranelift-bforest" -version = "0.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ca3560686e7c9c7ed7e0fe77469f2410ba5d7781b1acaa9adc8d8deea28e3e" -dependencies = [ - "cranelift-entity 0.74.0", -] - -[[package]] -name = "cranelift-codegen" -version = "0.68.0" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9936ea608b6cd176f107037f6adbb4deac933466fc7231154f96598b2d3ab1" +checksum = "b27bbd3e6c422cf6282b047bcdd51ecd9ca9f3497a3be0132ffa08e509b824b0" dependencies = [ - "byteorder", - "cranelift-bforest 0.68.0", - "cranelift-codegen-meta 0.68.0", - "cranelift-codegen-shared 0.68.0", - "cranelift-entity 0.68.0", - "gimli 0.22.0", - "log 0.4.14", - "regalloc", - "smallvec 1.6.1", - "target-lexicon 0.11.2", - "thiserror", + "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf9bf1ffffb6ce3d2e5ebc83549bd2436426c99b31cc550d521364cbe35d276" -dependencies = [ - "cranelift-bforest 0.74.0", - "cranelift-codegen-meta 0.74.0", - "cranelift-codegen-shared 0.74.0", - "cranelift-entity 0.74.0", - "gimli 0.24.0", - "log 0.4.14", - "regalloc", - "serde", - "smallvec 1.6.1", - "target-lexicon 0.12.0", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.68.0" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef2b2768568306540f4c8db3acce9105534d34c4a1e440529c1e702d7f8c8d7" +checksum = "872f5d4557a411b087bd731df6347c142ae1004e6467a144a7e33662e5715a01" dependencies = [ - "cranelift-codegen-shared 0.68.0", - "cranelift-entity 0.68.0", + "arrayvec 0.7.2", + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "cranelift-isle", + "gimli", + "log", + "regalloc2", + "smallvec", + "target-lexicon", ] [[package]] name = "cranelift-codegen-meta" -version = "0.74.0" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cc21936a5a6d07e23849ffe83e5c1f6f50305c074f4b2970ca50c13bf55b821" +checksum = "21b49fdebb29c62c1fc4da1eeebd609e9d530ecde24a9876def546275f73a244" dependencies = [ - "cranelift-codegen-shared 0.74.0", - "cranelift-entity 0.74.0", + "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6759012d6d19c4caec95793f052613e9d4113e925e7f14154defbac0f1d4c938" - -[[package]] -name = "cranelift-codegen-shared" -version = "0.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5b6ffaa87560bebe69a5446449da18090b126037920b0c1c6d5945f72faf6b" -dependencies = [ - "serde", -] - -[[package]] -name = "cranelift-entity" -version = "0.68.0" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86badbce14e15f52a45b666b38abe47b204969dd7f8fb7488cb55dd46b361fa6" -dependencies = [ - "serde", -] +checksum = "5fc0c091e2db055d4d7f6b7cec2d2ead286bcfaea3357c6a52c2a2613a8cb5ac" [[package]] name = "cranelift-entity" -version = "0.74.0" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6b4a8bef04f82e4296782646f733c641d09497df2fabf791323fefaa44c64c" +checksum = "354a9597be87996c9b278655e68b8447f65dd907256855ad773864edee8d985c" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.68.0" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b608bb7656c554d0a4cf8f50c7a10b857e80306f6ff829ad6d468a7e2323c8d8" +checksum = "0cd8dd3fb8b82c772f4172e87ae1677b971676fffa7c4e3398e3047e650a266b" dependencies = [ - "cranelift-codegen 0.68.0", - "log 0.4.14", - "smallvec 1.6.1", - "target-lexicon 0.11.2", + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", ] [[package]] -name = "cranelift-frontend" -version = "0.74.0" +name = "cranelift-isle" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b783b351f966fce33e3c03498cb116d16d97a8f9978164a60920bd0d3a99c" -dependencies = [ - "cranelift-codegen 0.74.0", - "log 0.4.14", - "smallvec 1.6.1", - "target-lexicon 0.12.0", -] +checksum = "b82527802b1f7d8da288adc28f1dc97ea52943f5871c041213f7b5035ac698a7" [[package]] name = "cranelift-native" -version = "0.74.0" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c88d3dd48021ff1e37e978a00098524abd3513444ae252c08d37b310b3d2a" +checksum = "c30ba8b910f1be023af0c39109cb28a8809734942a6b3eecbf2de8993052ea5e" dependencies = [ - "cranelift-codegen 0.74.0", - "target-lexicon 0.12.0", + "cranelift-codegen", + "libc", + "target-lexicon", ] [[package]] name = "cranelift-wasm" -version = "0.74.0" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb6d408e2da77cdbbd65466298d44c86ae71c1785d2ab0d8657753cdb4d9d89" +checksum = "776a8916d201894aca9637a20814f1e11abc62acd5cfbe0b4eb2e63922756971" dependencies = [ - "cranelift-codegen 0.74.0", - "cranelift-entity 0.74.0", - "cranelift-frontend 0.74.0", - "itertools 0.10.0", - "log 0.4.14", - "serde", - "smallvec 1.6.1", - "thiserror", - "wasmparser 0.78.2", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools", + "log", + "smallvec", + "wasmparser", + "wasmtime-types", ] [[package]] @@ -1147,21 +1103,22 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] name = "criterion" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23" +checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" dependencies = [ "atty", "cast", - "clap", + "clap 2.34.0", "criterion-plot", "csv", - "itertools 0.10.0", + "futures", + "itertools", "lazy_static", "num-traits", "oorandom", @@ -1173,17 +1130,18 @@ dependencies = [ "serde_derive", "serde_json", "tinytemplate", + "tokio", "walkdir", ] [[package]] name = "criterion-plot" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" +checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" dependencies = [ "cast", - "itertools 0.9.0", + "itertools", ] [[package]] @@ -1192,29 +1150,29 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils 0.8.3", + "cfg-if", + "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-epoch", - "crossbeam-utils 0.8.3", + "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils 0.8.3", + "cfg-if", + "crossbeam-utils", "lazy_static", "memoffset", "scopeguard", @@ -1222,50 +1180,60 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.7.2" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ - "autocfg 1.0.1", - "cfg-if 0.1.10", + "cfg-if", "lazy_static", ] [[package]] -name = "crossbeam-utils" -version = "0.8.3" +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +checksum = "9f2b443d17d49dad5ef0ede301c3179cc923b8822f3393b4d2c28c269dd4a122" dependencies = [ - "autocfg 1.0.1", - "cfg-if 1.0.0", - "lazy_static", + "generic-array 0.14.4", + "rand_core 0.6.2", + "subtle", + "zeroize", ] [[package]] -name = "crunchy" -version = "0.2.2" +name = "crypto-common" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array 0.14.4", + "typenum", +] [[package]] name = "crypto-mac" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", + "generic-array 0.14.4", + "subtle", ] [[package]] name = "crypto-mac" -version = "0.8.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ "generic-array 0.14.4", - "subtle 2.4.0", + "subtle", ] [[package]] @@ -1274,9 +1242,9 @@ version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ - "bstr", + "bstr 0.2.15", "csv-core", - "itoa", + "itoa 0.4.8", "ryu", "serde", ] @@ -1290,15 +1258,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ct-logs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" -dependencies = [ - "sct", -] - [[package]] name = "ctor" version = "0.1.19" @@ -1318,17 +1277,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "cuckoofilter" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b810a8449931679f64cd7eef1bbd0fa315801b6d5d9cdc1ace2804d6529eee18" -dependencies = [ - "byteorder", - "fnv", - "rand 0.7.3", -] - [[package]] name = "curve25519-dalek" version = "2.1.2" @@ -1338,7 +1286,7 @@ dependencies = [ "byteorder", "digest 0.8.1", "rand_core 0.5.1", - "subtle 2.4.0", + "subtle", "zeroize", ] @@ -1351,41 +1299,63 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.0", + "subtle", "zeroize", ] [[package]] -name = "darling" -version = "0.13.0" +name = "curve25519-dalek" +version = "4.0.0-pre.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" +checksum = "4033478fbf70d6acf2655ac70da91ee65852d69daf7a67bf7a2f518fb47aafcf" dependencies = [ - "darling_core", - "darling_macro", + "byteorder", + "digest 0.9.0", + "rand_core 0.6.2", + "subtle", + "zeroize", ] [[package]] -name = "darling_core" -version = "0.13.0" +name = "cxx" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" +checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" dependencies = [ - "fnv", - "ident_case", + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", "proc-macro2", "quote", - "strsim 0.10.0", + "scratch", "syn", ] [[package]] -name = "darling_macro" -version = "0.13.0" +name = "cxxbridge-flags" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" +checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" dependencies = [ - "darling_core", + "proc-macro2", "quote", "syn", ] @@ -1416,11 +1386,21 @@ dependencies = [ "syn", ] +[[package]] +name = "der" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derive_more" -version = "0.99.11" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", @@ -1434,10 +1414,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" [[package]] -name = "difference" -version = "2.0.0" +name = "difflib" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "digest" @@ -1457,11 +1437,22 @@ dependencies = [ "generic-array 0.14.4", ] +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.0", + "crypto-common", + "subtle", +] + [[package]] name = "directories" -version = "3.0.2" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e69600ff1703123957937708eb27f7a564e48885c537782722ed0ba3189ce1d7" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" dependencies = [ "dirs-sys", ] @@ -1472,7 +1463,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "dirs-sys-next", ] @@ -1484,7 +1475,7 @@ checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" dependencies = [ "libc", "redox_users", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1495,7 +1486,7 @@ checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1511,7 +1502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" dependencies = [ "byteorder", - "quick-error 1.2.3", + "quick-error", ] [[package]] @@ -1520,12 +1511,24 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "downcast-rs" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "dtoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5caaa75cbd2b960ff1e5392d2cfb1f44717fffe12fc1f32b7b5d1267f99732a6" + [[package]] name = "dyn-clonable" version = "0.9.0" @@ -1549,34 +1552,20 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" - -[[package]] -name = "dynasm" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc2d9a5e44da60059bd38db2d05cbb478619541b8c79890547861ec1e3194f0" -dependencies = [ - "bitflags", - "byteorder", - "lazy_static", - "proc-macro-error 1.0.4", - "proc-macro2", - "quote", - "syn", -] +checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" [[package]] -name = "dynasmrt" -version = "1.1.0" +name = "ecdsa" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42276e3f205fe63887cca255aa9a65a63fb72764c30b9a6252a7c7e46994f689" +checksum = "85789ce7dfbd0f0624c07ef653a08bb2ebf43d3e16531361f46d36dd54334fed" dependencies = [ - "byteorder", - "dynasm", - "memmap2", + "der", + "elliptic-curve", + "rfc6979", + "signature", ] [[package]] @@ -1598,7 +1587,21 @@ dependencies = [ "ed25519", "rand 0.7.3", "serde", - "sha2 0.9.3", + "sha2 0.9.8", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek 3.0.2", + "hashbrown 0.12.3", + "hex", + "rand_core 0.6.2", + "sha2 0.9.8", "zeroize", ] @@ -1609,81 +1612,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] -name = "enum-as-inner" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "enumflags2" -version = "0.6.4" +name = "elliptic-curve" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8d82922337cd23a15f88b70d8e4ef5f11da38dd7cdb55e84dd5de99695da0" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "enumflags2_derive", + "base16ct", + "crypto-bigint", + "der", + "digest 0.10.3", + "ff", + "generic-array 0.14.4", + "group", + "rand_core 0.6.2", + "sec1", + "subtle", + "zeroize", ] [[package]] -name = "enumflags2_derive" -version = "0.6.4" +name = "enum-as-inner" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ + "heck", "proc-macro2", "quote", "syn", ] [[package]] -name = "enumset" -version = "1.0.7" +name = "enumflags2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e76129da36102af021b8e5000dab2c1c30dbef85c1e482beeff8da5dde0e0b0" +checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae" dependencies = [ - "enumset_derive", + "enumflags2_derive", ] [[package]] -name = "enumset_derive" -version = "0.5.5" +name = "enumflags2_derive" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" +checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" dependencies = [ - "darling", "proc-macro2", "quote", "syn", ] -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime 1.3.0", - "log 0.4.14", - "regex", - "termcolor", -] - -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "log 0.4.14", - "regex", -] - [[package]] name = "env_logger" version = "0.9.0" @@ -1691,8 +1669,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", - "humantime 2.1.0", - "log 0.4.14", + "humantime", + "log", "regex", "termcolor", ] @@ -1703,24 +1681,15 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" -[[package]] -name = "erased-serde" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0465971a8cc1fa2455c8465aaa377131e1f1cf4983280f474a13e68793aa770c" -dependencies = [ - "serde", -] - [[package]] name = "errno" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1745,7 +1714,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e43f2f1833d64e33f15592464d6fdd70f349dda7b1a53088eb83cd94014008c5" dependencies = [ - "futures 0.3.16", + "futures", ] [[package]] @@ -1762,9 +1731,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.4.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -1778,50 +1747,72 @@ dependencies = [ "libc", ] +[[package]] +name = "ff" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" +dependencies = [ + "rand_core 0.6.2", + "subtle", +] + [[package]] name = "file-per-thread-logger" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fdbe0d94371f9ce939b555dd342d0686cc4c0cadbcd4b61d70af5ff97eb4126" +checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" dependencies = [ - "env_logger 0.7.1", - "log 0.4.14", + "env_logger", + "log", +] + +[[package]] +name = "filetime" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", ] [[package]] name = "finality-grandpa" -version = "0.14.4" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ac3ff5224ef91f3c97e03eb1de2db82743427e91aaa5ac635f454f0b164f5a" +checksum = "b22349c6a11563a202d95772a68e0fcf56119e74ea8a2a19cf2301460fcd0df5" dependencies = [ "either", - "futures 0.3.16", - "futures-timer 3.0.2", - "log 0.4.14", + "futures", + "futures-timer", + "log", "num-traits", "parity-scale-codec", - "parking_lot 0.11.1", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "scale-info", ] [[package]] name = "fixed-hash" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.4", + "rand 0.8.5", "rustc-hex", "static_assertions", ] [[package]] name = "fixedbitset" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" [[package]] name = "flate2" @@ -1829,7 +1820,7 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crc32fast", "libc", "libz-sys", @@ -1837,25 +1828,19 @@ dependencies = [ ] [[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" +name = "float-cmp" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" dependencies = [ - "foreign-types-shared", + "num-traits", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fork-tree" @@ -1871,23 +1856,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" dependencies = [ "matches", - "percent-encoding 2.1.0", + "percent-encoding", ] +[[package]] +name = "fragile" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85dcb89d2b10c5f6133de2efd8c11959ce9dbb46a2f7a4cab208c4eeda6ce1ab" + [[package]] name = "frame-benchmarking" version = "4.0.0-dev" dependencies = [ + "array-bytes", "frame-support", "frame-system", - "hex-literal", "linregress", - "log 0.4.14", + "log", "parity-scale-codec", - "paste 1.0.4", + "paste", + "rusty-fork", "scale-info", + "serde", "sp-api", + "sp-application-crypto", + "sp-core", "sp-io", + "sp-keystore", "sp-runtime", "sp-runtime-interface", "sp-std", @@ -1899,33 +1895,77 @@ name = "frame-benchmarking-cli" version = "4.0.0-dev" dependencies = [ "Inflector", + "array-bytes", "chrono", + "clap 4.0.11", + "comfy-table", "frame-benchmarking", "frame-support", + "frame-system", + "gethostname", "handlebars", + "hash-db", + "itertools", + "kvdb", + "lazy_static", "linked-hash-map", - "log 0.4.14", + "log", + "memory-db", "parity-scale-codec", + "rand 0.8.5", + "rand_pcg 0.3.1", + "sc-block-builder", "sc-cli", + "sc-client-api", "sc-client-db", "sc-executor", "sc-service", + "sc-sysinfo", "serde", + "serde_json", + "serde_nanos", + "sp-api", + "sp-blockchain", "sp-core", + "sp-database", "sp-externalities", + "sp-inherents", "sp-keystore", "sp-runtime", "sp-state-machine", - "structopt", + "sp-std", + "sp-storage", + "sp-trie", + "tempfile", + "thiserror", + "thousands", +] + +[[package]] +name = "frame-election-provider-solution-type" +version = "4.0.0-dev" +dependencies = [ + "frame-election-provider-support", + "frame-support", + "parity-scale-codec", + "proc-macro-crate", + "proc-macro2", + "quote", + "scale-info", + "sp-arithmetic", + "syn", + "trybuild", ] [[package]] name = "frame-election-provider-support" version = "4.0.0-dev" dependencies = [ + "frame-election-provider-solution-type", "frame-support", "frame-system", "parity-scale-codec", + "rand 0.7.3", "scale-info", "sp-arithmetic", "sp-core", @@ -1935,13 +1975,31 @@ dependencies = [ "sp-std", ] +[[package]] +name = "frame-election-solution-type-fuzzer" +version = "2.0.0-alpha.5" +dependencies = [ + "clap 4.0.11", + "frame-election-provider-solution-type", + "frame-election-provider-support", + "frame-support", + "honggfuzz", + "parity-scale-codec", + "rand 0.8.5", + "scale-info", + "sp-arithmetic", + "sp-npos-elections", + "sp-runtime", +] + [[package]] name = "frame-executive" version = "4.0.0-dev" dependencies = [ + "array-bytes", "frame-support", "frame-system", - "hex-literal", + "frame-try-runtime", "pallet-balances", "pallet-transaction-payment", "parity-scale-codec", @@ -1957,16 +2015,35 @@ dependencies = [ [[package]] name = "frame-metadata" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96616f82e069102b95a72c87de4c84d2f87ef7f0f20630e78ce3824436483110" +checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "parity-scale-codec", "scale-info", "serde", ] +[[package]] +name = "frame-remote-externalities" +version = "0.10.0-dev" +dependencies = [ + "env_logger", + "frame-support", + "log", + "pallet-elections-phragmen", + "parity-scale-codec", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-version", + "substrate-rpc-client", + "tokio", +] + [[package]] name = "frame-support" version = "4.0.0-dev" @@ -1977,17 +2054,20 @@ dependencies = [ "frame-support-procedural", "frame-system", "impl-trait-for-tuples", - "log 0.4.14", + "k256", + "log", "once_cell", "parity-scale-codec", - "parity-util-mem", - "paste 1.0.4", - "pretty_assertions 0.6.1", + "paste", + "pretty_assertions", "scale-info", "serde", - "smallvec 1.6.1", + "serde_json", + "smallvec", + "sp-api", "sp-arithmetic", "sp-core", + "sp-core-hashing-proc-macro", "sp-inherents", "sp-io", "sp-runtime", @@ -1995,6 +2075,8 @@ dependencies = [ "sp-state-machine", "sp-std", "sp-tracing", + "sp-weights", + "tt-call", ] [[package]] @@ -2002,7 +2084,9 @@ name = "frame-support-procedural" version = "4.0.0-dev" dependencies = [ "Inflector", + "cfg-expr", "frame-support-procedural-tools", + "itertools", "proc-macro2", "quote", "syn", @@ -2013,7 +2097,7 @@ name = "frame-support-procedural-tools" version = "4.0.0-dev" dependencies = [ "frame-support-procedural-tools-derive", - "proc-macro-crate 1.0.0", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -2036,7 +2120,7 @@ dependencies = [ "frame-support-test-pallet", "frame-system", "parity-scale-codec", - "pretty_assertions 0.6.1", + "pretty_assertions", "rustversion", "scale-info", "serde", @@ -2050,6 +2134,19 @@ dependencies = [ "trybuild", ] +[[package]] +name = "frame-support-test-compile-pass" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-version", +] + [[package]] name = "frame-support-test-pallet" version = "4.0.0-dev" @@ -2066,7 +2163,7 @@ version = "4.0.0-dev" dependencies = [ "criterion", "frame-support", - "log 0.4.14", + "log", "parity-scale-codec", "scale-info", "serde", @@ -2076,6 +2173,7 @@ dependencies = [ "sp-runtime", "sp-std", "sp-version", + "sp-weights", "substrate-test-runtime-client", ] @@ -2107,23 +2205,12 @@ name = "frame-try-runtime" version = "0.10.0-dev" dependencies = [ "frame-support", + "parity-scale-codec", "sp-api", "sp-runtime", "sp-std", ] -[[package]] -name = "fs-swap" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d47dad3685eceed8488986cad3d5027165ea5edb164331770e2059555f10a5" -dependencies = [ - "lazy_static", - "libc", - "libloading 0.5.2", - "winapi 0.3.9", -] - [[package]] name = "fs2" version = "0.4.3" @@ -2131,7 +2218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2140,45 +2227,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - -[[package]] -name = "futures" -version = "0.1.31" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.16" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -2191,9 +2250,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -2201,15 +2260,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" -version = "0.3.16" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -2219,9 +2278,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-lite" @@ -2234,18 +2293,16 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "waker-fn", ] [[package]] name = "futures-macro" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ - "autocfg 1.0.1", - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -2253,9 +2310,9 @@ dependencies = [ [[package]] name = "futures-rustls" -version = "0.21.1" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1387e07917c711fb4ee4f48ea0adb04a3c9739e53ef85bf43ae1edc2937a8b" +checksum = "e01fe9932a224b72b45336d96040aa86386d674a31d0af27d800ea7bc8ca97fe" dependencies = [ "futures-io", "rustls", @@ -2264,21 +2321,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" - -[[package]] -name = "futures-timer" -version = "2.0.2" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-timer" @@ -2288,12 +2339,10 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ - "autocfg 1.0.1", - "futures 0.1.31", "futures-channel", "futures-core", "futures-io", @@ -2301,19 +2350,40 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gcc" version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" +[[package]] +name = "generate-bags" +version = "4.0.0-dev" +dependencies = [ + "chrono", + "frame-election-provider-support", + "frame-support", + "frame-system", + "git2", + "num-format", + "pallet-staking", + "sp-io", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -2330,7 +2400,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", - "version_check 0.9.2", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", ] [[package]] @@ -2339,9 +2419,11 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", + "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -2350,18 +2432,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ - "cfg-if 1.0.0", - "js-sys", + "cfg-if", "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] name = "ghash" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b442c439366184de619215247d24e908912b175e824a530253845ac4c251a5c1" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug 0.3.0", "polyval", @@ -2369,9 +2449,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.22.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" dependencies = [ "fallible-iterator", "indexmap", @@ -2379,21 +2459,17 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.23.0" +name = "git2" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" - -[[package]] -name = "gimli" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" -dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", -] +checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] [[package]] name = "glob" @@ -2408,23 +2484,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" dependencies = [ "aho-corasick", - "bstr", + "bstr 0.2.15", "fnv", - "log 0.4.14", + "log", "regex", ] [[package]] -name = "gloo-timers" -version = "0.2.1" +name = "group" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" dependencies = [ - "futures-channel", + "ff", + "rand_core 0.6.2", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", "futures-core", - "js-sys", - "wasm-bindgen", - "web-sys", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -2435,16 +2528,16 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" [[package]] name = "handlebars" -version = "3.5.3" +version = "4.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb0867bbc5a3da37a753e78021d5fcf8a4db00e18dd2dd90fd36e24190e162d" +checksum = "433e4ab33f1213cdc25b5fa45c76881240cfe79284cf2b395e8b9e312a30a2fd" dependencies = [ - "log 0.4.14", + "log", "pest", "pest_derive", - "quick-error 2.0.0", "serde", "serde_json", + "thiserror", ] [[package]] @@ -2464,27 +2557,24 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.9.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] name = "heck" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" -dependencies = [ - "unicode-segmentation", -] +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" @@ -2501,47 +2591,33 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-literal" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5af1f635ef1bc545d78392b136bfe1c9809e029023c84a3638a864a10b8819c8" - -[[package]] -name = "hex_fmt" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" - [[package]] name = "hmac" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ - "crypto-mac 0.7.0", - "digest 0.8.1", + "crypto-mac 0.8.0", + "digest 0.9.0", ] [[package]] name = "hmac" -version = "0.8.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.8.0", + "crypto-mac 0.11.1", "digest 0.9.0", ] [[package]] -name = "hmac-drbg" -version = "0.2.0" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.8.1", - "generic-array 0.12.4", - "hmac 0.7.1", + "digest 0.10.3", ] [[package]] @@ -2557,13 +2633,14 @@ dependencies = [ [[package]] name = "honggfuzz" -version = "0.5.54" +version = "0.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea09577d948a98a5f59b7c891e274c4fb35ad52f67782b3d0cb53b9c05301f1" +checksum = "848e9c511092e0daa0a35a63e8e6e475a3e8f870741448b9f6028d69b142f18e" dependencies = [ "arbitrary", "lazy_static", - "memmap", + "memmap2", + "rustc_version 0.4.0", ] [[package]] @@ -2574,36 +2651,42 @@ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", - "winapi 0.3.9", + "winapi", ] [[package]] name = "http" -version = "0.2.3" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.0.1", + "bytes", "fnv", - "itoa", + "itoa 1.0.4", ] [[package]] name = "http-body" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.0.1", + "bytes", "http", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" -version = "1.4.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -2611,15 +2694,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error 1.2.3", -] - [[package]] name = "humantime" version = "2.1.0" @@ -2628,40 +2702,22 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.10.16" +version = "0.14.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" +checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" dependencies = [ - "base64 0.9.3", - "httparse", - "language-tags", - "log 0.3.9", - "mime", - "num_cpus", - "time", - "traitobject", - "typeable", - "unicase 1.4.2", - "url 1.7.2", -] - -[[package]] -name = "hyper" -version = "0.14.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b61cf2d1aebcf6e6352c97b81dc2244ca29194be1b276f5d8ad5c6330fffb11" -dependencies = [ - "bytes 1.0.1", + "bytes", "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "httparse", "httpdate", - "itoa", - "pin-project-lite 0.2.6", - "socket2 0.4.0", + "itoa 1.0.4", + "pin-project-lite 0.2.9", + "socket2", "tokio", "tower-service", "tracing", @@ -2670,56 +2726,24 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" dependencies = [ - "ct-logs", - "futures-util", - "hyper 0.14.11", - "log 0.4.14", + "http", + "hyper", + "log", "rustls", "rustls-native-certs", "tokio", "tokio-rustls", - "webpki", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes 1.0.1", - "hyper 0.14.11", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", ] [[package]] name = "idna" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", @@ -2728,64 +2752,55 @@ dependencies = [ [[package]] name = "if-addrs" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28538916eb3f3976311f5dfbe67b5362d0add1293d0a9cad17debf86f8e3aa48" -dependencies = [ - "if-addrs-sys", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "if-addrs-sys" -version = "0.3.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de74b9dd780476e837e5eb5ab7c88b49ed304126e412030a0adba99c8efe79ea" +checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" dependencies = [ - "cc", "libc", + "winapi", ] [[package]] name = "if-watch" -version = "0.2.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6d52908d4ea4ab2bc22474ba149bf1011c8e2c3ebc1ff593ae28ac44f494b6" +checksum = "065c008e570a43c00de6aed9714035e5ea6a498c255323db9091722af6ee67dd" dependencies = [ "async-io", - "futures 0.3.16", - "futures-lite", + "core-foundation", + "fnv", + "futures", "if-addrs", "ipnet", - "libc", - "log 0.4.14", - "winapi 0.3.9", + "log", + "rtnetlink", + "system-configuration", + "windows", ] [[package]] name = "impl-codec" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ "parity-scale-codec", ] [[package]] name = "impl-serde" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" dependencies = [ "serde", ] [[package]] name = "impl-trait-for-tuples" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote", @@ -2794,22 +2809,28 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.6.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ - "autocfg 1.0.1", - "hashbrown 0.9.1", + "autocfg", + "hashbrown 0.11.2", "serde", ] +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + [[package]] name = "instant" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -2822,71 +2843,55 @@ dependencies = [ ] [[package]] -name = "intervalier" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fa110ec7b8f493f416eed552740d10e7030ad5f63b2308f82c9608ec2df275" -dependencies = [ - "futures 0.3.16", - "futures-timer 2.0.2", -] - -[[package]] -name = "iovec" -version = "0.1.4" +name = "io-lifetimes" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] +checksum = "24c3f4eff5495aee4c0399d7b6a0dc2b6e81be84242ffbfcf253ebacccc1d0cb" [[package]] name = "ip_network" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b746553d2f4a1ca26fab939943ddfb217a091f34f53571620a8e3d30691303" +checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" [[package]] name = "ipconfig" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" dependencies = [ - "socket2 0.3.19", + "socket2", "widestring", - "winapi 0.3.9", + "winapi", "winreg", ] [[package]] name = "ipnet" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itertools" -version = "0.9.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] [[package]] -name = "itertools" -version = "0.10.0" +name = "itoa" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" -dependencies = [ - "either", -] +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "0.4.7" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "jobserver" @@ -2899,286 +2904,293 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.50" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" +checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254" dependencies = [ "wasm-bindgen", ] [[package]] -name = "jsonrpc-client-transports" -version = "18.0.0" +name = "jsonrpsee" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b99d4207e2a04fb4581746903c2bb7eb376f88de9c699d0f3e10feeac0cd3a" +checksum = "7d291e3a5818a2384645fd9756362e6d89cf0541b0b916fa7702ea4a9833608e" dependencies = [ - "derive_more", - "futures 0.3.16", - "hyper 0.14.11", - "hyper-tls", - "jsonrpc-core", - "jsonrpc-pubsub", - "log 0.4.14", - "serde", - "serde_json", - "tokio", - "url 1.7.2", - "websocket", + "jsonrpsee-core", + "jsonrpsee-proc-macros", + "jsonrpsee-server", + "jsonrpsee-types", + "jsonrpsee-ws-client", + "tracing", ] [[package]] -name = "jsonrpc-core" -version = "18.0.0" +name = "jsonrpsee-client-transport" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" +checksum = "965de52763f2004bc91ac5bcec504192440f0b568a5d621c59d9dbd6f886c3fb" dependencies = [ - "futures 0.3.16", - "futures-executor", "futures-util", - "log 0.4.14", - "serde", - "serde_derive", - "serde_json", + "http", + "jsonrpsee-core", + "jsonrpsee-types", + "pin-project", + "rustls-native-certs", + "soketto", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", + "webpki-roots", ] [[package]] -name = "jsonrpc-core-client" -version = "18.0.0" +name = "jsonrpsee-core" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b51da17abecbdab3e3d4f26b01c5ec075e88d3abe3ab3b05dc9aa69392764ec0" +checksum = "a4e70b4439a751a5de7dd5ed55eacff78ebf4ffe0fc009cb1ebb11417f5b536b" dependencies = [ - "futures 0.3.16", - "jsonrpc-client-transports", + "anyhow", + "arrayvec 0.7.2", + "async-lock", + "async-trait", + "beef", + "futures-channel", + "futures-timer", + "futures-util", + "globset", + "hyper", + "jsonrpsee-types", + "parking_lot 0.12.1", + "rand 0.8.5", + "rustc-hash", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio", + "tracing", ] [[package]] -name = "jsonrpc-derive" -version = "18.0.0" +name = "jsonrpsee-proc-macros" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b939a78fa820cdfcb7ee7484466746a7377760970f6f9c6fe19f9edcc8a38d2" +checksum = "baa6da1e4199c10d7b1d0a6e5e8bd8e55f351163b6f4b3cbb044672a69bd4c1c" dependencies = [ - "proc-macro-crate 0.1.5", + "heck", + "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] -name = "jsonrpc-http-server" -version = "18.0.0" +name = "jsonrpsee-server" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" +checksum = "1fb69dad85df79527c019659a992498d03f8495390496da2f07e6c24c2b356fc" dependencies = [ - "futures 0.3.16", - "hyper 0.14.11", - "jsonrpc-core", - "jsonrpc-server-utils", - "log 0.4.14", - "net2", - "parking_lot 0.11.1", - "unicase 2.6.0", + "futures-channel", + "futures-util", + "http", + "hyper", + "jsonrpsee-core", + "jsonrpsee-types", + "serde", + "serde_json", + "soketto", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", ] [[package]] -name = "jsonrpc-ipc-server" -version = "18.0.0" +name = "jsonrpsee-types" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382bb0206323ca7cda3dcd7e245cea86d37d02457a02a975e3378fb149a48845" +checksum = "5bd522fe1ce3702fd94812965d7bb7a3364b1c9aba743944c5a00529aae80f8c" dependencies = [ - "futures 0.3.16", - "jsonrpc-core", - "jsonrpc-server-utils", - "log 0.4.14", - "parity-tokio-ipc", - "parking_lot 0.11.1", - "tower-service", + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", + "tracing", ] [[package]] -name = "jsonrpc-pubsub" -version = "18.0.0" +name = "jsonrpsee-ws-client" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011" +checksum = "0b83daeecfc6517cfe210df24e570fb06213533dfb990318fae781f4c7119dd9" dependencies = [ - "futures 0.3.16", - "jsonrpc-core", - "lazy_static", - "log 0.4.14", - "parking_lot 0.11.1", - "rand 0.7.3", - "serde", + "http", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", ] [[package]] -name = "jsonrpc-server-utils" -version = "18.0.0" +name = "k256" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" +checksum = "3636d281d46c3b64182eb3a0a42b7b483191a2ecc3f05301fa67403f7c9bc949" dependencies = [ - "bytes 1.0.1", - "futures 0.3.16", - "globset", - "jsonrpc-core", - "lazy_static", - "log 0.4.14", - "tokio", - "tokio-stream", - "tokio-util", - "unicase 2.6.0", + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2 0.10.2", ] [[package]] -name = "jsonrpc-ws-server" -version = "18.0.0" +name = "keccak" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f892c7d766369475ab7b0669f417906302d7c0fb521285c0a0c92e52e7c8e946" -dependencies = [ - "futures 0.3.16", - "jsonrpc-core", - "jsonrpc-server-utils", - "log 0.4.14", - "parity-ws", - "parking_lot 0.11.1", - "slab", -] +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" [[package]] -name = "jsonrpsee-proc-macros" -version = "0.3.0" +name = "keccak-hasher" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37924e16300e249a52a22cabb5632f846dc9760b39355f5e8bc70cd23dc6300" +checksum = "711adba9940a039f4374fc5724c0a5eaca84a2d558cce62256bfe26f0dbef05e" dependencies = [ - "Inflector", - "bae", - "proc-macro-crate 1.0.0", - "proc-macro2", - "quote", - "syn", + "hash-db", + "hash256-std-hasher", + "tiny-keccak", ] [[package]] -name = "jsonrpsee-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d67724d368c59e08b557a516cf8fcc51100e7a708850f502e1044b151fe89788" -dependencies = [ - "async-trait", - "beef", - "futures-channel", - "futures-util", - "hyper 0.14.11", - "log 0.4.14", - "serde", - "serde_json", - "soketto 0.6.0", - "thiserror", -] - -[[package]] -name = "jsonrpsee-ws-client" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2834b6e7f57ce9a4412ed4d6dc95125d2c8612e68f86b9d9a07369164e4198" -dependencies = [ - "async-trait", - "fnv", - "futures 0.3.16", - "jsonrpsee-types", - "log 0.4.14", - "pin-project 1.0.5", - "rustls", - "rustls-native-certs", - "serde", - "serde_json", - "soketto 0.6.0", - "thiserror", - "tokio", - "tokio-rustls", - "tokio-util", - "url 2.2.1", -] - -[[package]] -name = "keccak" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" - -[[package]] -name = "keccak-hasher" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711adba9940a039f4374fc5724c0a5eaca84a2d558cce62256bfe26f0dbef05e" -dependencies = [ - "hash-db", - "hash256-std-hasher", - "tiny-keccak", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +name = "kitchensink-runtime" +version = "3.0.0-dev" dependencies = [ - "log 0.4.14", + "frame-benchmarking", + "frame-election-provider-support", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "log", + "node-primitives", + "pallet-alliance", + "pallet-asset-tx-payment", + "pallet-assets", + "pallet-authority-discovery", + "pallet-authorship", + "pallet-babe", + "pallet-bags-list", + "pallet-balances", + "pallet-bounties", + "pallet-child-bounties", + "pallet-collective", + "pallet-contracts", + "pallet-contracts-primitives", + "pallet-conviction-voting", + "pallet-democracy", + "pallet-election-provider-multi-phase", + "pallet-election-provider-support-benchmarking", + "pallet-elections-phragmen", + "pallet-fast-unstake", + "pallet-grandpa", + "pallet-identity", + "pallet-im-online", + "pallet-indices", + "pallet-lottery", + "pallet-membership", + "pallet-message-queue", + "pallet-mmr", + "pallet-multisig", + "pallet-nis", + "pallet-nomination-pools", + "pallet-nomination-pools-benchmarking", + "pallet-nomination-pools-runtime-api", + "pallet-offences", + "pallet-offences-benchmarking", + "pallet-preimage", + "pallet-proxy", + "pallet-randomness-collective-flip", + "pallet-ranked-collective", + "pallet-recovery", + "pallet-referenda", + "pallet-remark", + "pallet-root-testing", + "pallet-scheduler", + "pallet-session", + "pallet-session-benchmarking", + "pallet-society", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-state-trie-migration", + "pallet-sudo", + "pallet-timestamp", + "pallet-tips", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-transaction-storage", + "pallet-treasury", + "pallet-uniques", + "pallet-utility", + "pallet-vesting", + "pallet-whitelist", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-authority-discovery", + "sp-block-builder", + "sp-consensus-babe", + "sp-core", + "sp-inherents", + "sp-io", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", + "sp-transaction-pool", + "sp-version", + "static_assertions", + "substrate-wasm-builder", ] [[package]] name = "kvdb" -version = "0.10.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a3f58dc069ec0e205a27f5b45920722a46faed802a0541538241af6228f512" +checksum = "e7d770dcb02bf6835887c3a979b5107a04ff4bbde97a5f0928d27404a155add9" dependencies = [ - "parity-util-mem", - "smallvec 1.6.1", + "smallvec", ] [[package]] name = "kvdb-memorydb" -version = "0.10.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b6b85fc643f5acd0bffb2cc8a6d150209379267af0d41db72170021841f9f5" +checksum = "bf7a85fe66f9ff9cd74e169fdd2c94c6e1e74c412c99a73b4df3200b5d3760b2" dependencies = [ "kvdb", - "parity-util-mem", - "parking_lot 0.11.1", + "parking_lot 0.12.1", ] [[package]] name = "kvdb-rocksdb" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1b6ea8f2536f504b645ad78419c8246550e19d2c3419a167080ce08edee35a" +checksum = "2182b8219fee6bd83aacaab7344e840179ae079d5216aa4e249b4d704646a844" dependencies = [ - "fs-swap", "kvdb", - "log 0.4.14", "num_cpus", - "owning_ref", - "parity-util-mem", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "regex", "rocksdb", - "smallvec 1.6.1", + "smallvec", ] -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - [[package]] name = "lazy_static" version = "1.4.0" @@ -3199,28 +3211,20 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" [[package]] name = "libc" -version = "0.2.95" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] -name = "libloading" -version = "0.5.2" +name = "libgit2-sys" +version = "0.13.2+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" dependencies = [ "cc", - "winapi 0.3.9", -] - -[[package]] -name = "libloading" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" -dependencies = [ - "cfg-if 1.0.0", - "winapi 0.3.9", + "libc", + "libz-sys", + "pkg-config", ] [[package]] @@ -3229,8 +3233,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ - "cfg-if 1.0.0", - "winapi 0.3.9", + "cfg-if", + "winapi", ] [[package]] @@ -3241,243 +3245,202 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "libp2p" -version = "0.39.1" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9004c06878ef8f3b4b4067e69a140d87ed20bf777287f82223e49713b36ee433" +checksum = "ec878fda12ebec479186b3914ebc48ff180fa4c51847e11a1a68bf65249e02c1" dependencies = [ - "atomic", - "bytes 1.0.1", - "futures 0.3.16", + "bytes", + "futures", + "futures-timer", + "getrandom 0.2.3", + "instant", "lazy_static", "libp2p-core", - "libp2p-deflate", "libp2p-dns", - "libp2p-floodsub", - "libp2p-gossipsub", "libp2p-identify", "libp2p-kad", "libp2p-mdns", + "libp2p-metrics", "libp2p-mplex", "libp2p-noise", "libp2p-ping", - "libp2p-plaintext", - "libp2p-pnet", - "libp2p-relay", "libp2p-request-response", "libp2p-swarm", "libp2p-swarm-derive", "libp2p-tcp", - "libp2p-uds", "libp2p-wasm-ext", "libp2p-websocket", "libp2p-yamux", "multiaddr", - "parking_lot 0.11.1", - "pin-project 1.0.5", - "smallvec 1.6.1", - "wasm-timer", + "parking_lot 0.12.1", + "pin-project", + "smallvec", ] [[package]] name = "libp2p-core" -version = "0.29.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af9b4abdeaa420593a297c8592f63fad4234f4b88dc9343b8fd8e736c35faa59" +checksum = "799676bb0807c788065e57551c6527d461ad572162b0519d1958946ff9e0539d" dependencies = [ "asn1_der", "bs58", "ed25519-dalek", "either", "fnv", - "futures 0.3.16", - "futures-timer 3.0.2", + "futures", + "futures-timer", + "instant", "lazy_static", - "libsecp256k1 0.5.0", - "log 0.4.14", + "log", "multiaddr", - "multihash 0.14.0", + "multihash", "multistream-select", - "parking_lot 0.11.1", - "pin-project 1.0.5", + "parking_lot 0.12.1", + "pin-project", "prost", "prost-build", - "rand 0.7.3", - "ring", + "rand 0.8.5", "rw-stream-sink", - "sha2 0.9.3", - "smallvec 1.6.1", + "sha2 0.10.2", + "smallvec", "thiserror", - "unsigned-varint 0.7.0", + "unsigned-varint", "void", "zeroize", ] -[[package]] -name = "libp2p-deflate" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66097fccc0b7f8579f90a03ea76ba6196332ea049fd07fd969490a06819dcdc8" -dependencies = [ - "flate2", - "futures 0.3.16", - "libp2p-core", -] - [[package]] name = "libp2p-dns" -version = "0.29.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ff08b3196b85a17f202d80589e93b1660a574af67275706657fdc762e42c32" +checksum = "2322c9fb40d99101def6a01612ee30500c89abbbecb6297b3cd252903a4c1720" dependencies = [ - "async-std-resolver", - "futures 0.3.16", + "futures", "libp2p-core", - "log 0.4.14", - "smallvec 1.6.1", + "log", + "parking_lot 0.12.1", + "smallvec", "trust-dns-resolver", ] -[[package]] -name = "libp2p-floodsub" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404eca8720967179dac7a5b4275eb91f904a53859c69ca8d018560ad6beb214f" -dependencies = [ - "cuckoofilter", - "fnv", - "futures 0.3.16", - "libp2p-core", - "libp2p-swarm", - "log 0.4.14", - "prost", - "prost-build", - "rand 0.7.3", - "smallvec 1.6.1", -] - -[[package]] -name = "libp2p-gossipsub" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1cc48709bcbc3a3321f08a73560b4bbb4166a7d56f6fdb615bc775f4f91058e" -dependencies = [ - "asynchronous-codec 0.6.0", - "base64 0.13.0", - "byteorder", - "bytes 1.0.1", - "fnv", - "futures 0.3.16", - "hex_fmt", - "libp2p-core", - "libp2p-swarm", - "log 0.4.14", - "prost", - "prost-build", - "rand 0.7.3", - "regex", - "sha2 0.9.3", - "smallvec 1.6.1", - "unsigned-varint 0.7.0", - "wasm-timer", -] - [[package]] name = "libp2p-identify" -version = "0.30.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7b61f6cf07664fb97016c318c4d4512b3dd4cc07238607f3f0163245f99008e" +checksum = "dcf9a121f699e8719bda2e6e9e9b6ddafc6cff4602471d6481c1067930ccb29b" dependencies = [ - "futures 0.3.16", + "asynchronous-codec", + "futures", + "futures-timer", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log", + "lru", "prost", "prost-build", - "smallvec 1.6.1", - "wasm-timer", + "prost-codec", + "smallvec", + "thiserror", + "void", ] [[package]] name = "libp2p-kad" -version = "0.31.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ed78489c87924235665a0ab345b298ee34dff0f7ad62c0ba6608b2144fb75e" +checksum = "6721c200e2021f6c3fab8b6cf0272ead8912d871610ee194ebd628cecf428f22" dependencies = [ - "arrayvec 0.5.2", - "asynchronous-codec 0.6.0", - "bytes 1.0.1", + "arrayvec 0.7.2", + "asynchronous-codec", + "bytes", "either", "fnv", - "futures 0.3.16", + "futures", + "futures-timer", + "instant", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log", "prost", "prost-build", - "rand 0.7.3", - "sha2 0.9.3", - "smallvec 1.6.1", + "rand 0.8.5", + "sha2 0.10.2", + "smallvec", + "thiserror", "uint", - "unsigned-varint 0.7.0", + "unsigned-varint", "void", - "wasm-timer", ] [[package]] name = "libp2p-mdns" -version = "0.31.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a29e6cbc2a24b8471b6567e580a0e8e7b70a6d0f0ea2be0844d1e842d7d4fa33" +checksum = "761704e727f7d68d58d7bc2231eafae5fc1b9814de24290f126df09d4bd37a15" dependencies = [ - "async-io", "data-encoding", "dns-parser", - "futures 0.3.16", + "futures", "if-watch", - "lazy_static", "libp2p-core", "libp2p-swarm", - "log 0.4.14", - "rand 0.8.4", - "smallvec 1.6.1", - "socket2 0.4.0", + "log", + "rand 0.8.5", + "smallvec", + "socket2", + "tokio", "void", ] +[[package]] +name = "libp2p-metrics" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee31b08e78b7b8bfd1c4204a9dd8a87b4fcdf6dafc57eb51701c1c264a81cb9" +dependencies = [ + "libp2p-core", + "libp2p-identify", + "libp2p-kad", + "libp2p-ping", + "libp2p-swarm", + "prometheus-client", +] + [[package]] name = "libp2p-mplex" -version = "0.29.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "313d9ea526c68df4425f580024e67a9d3ffd49f2c33de5154b1f5019816f7a99" +checksum = "692664acfd98652de739a8acbb0a0d670f1d67190a49be6b4395e22c37337d89" dependencies = [ - "asynchronous-codec 0.6.0", - "bytes 1.0.1", - "futures 0.3.16", + "asynchronous-codec", + "bytes", + "futures", "libp2p-core", - "log 0.4.14", + "log", "nohash-hasher", - "parking_lot 0.11.1", - "rand 0.7.3", - "smallvec 1.6.1", - "unsigned-varint 0.7.0", + "parking_lot 0.12.1", + "rand 0.8.5", + "smallvec", + "unsigned-varint", ] [[package]] name = "libp2p-noise" -version = "0.32.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1db7212f342b6ba7c981cc40e31f76e9e56cb48e65fa4c142ecaca5839523e" +checksum = "048155686bd81fe6cb5efdef0c6290f25ad32a0a42e8f4f72625cf6a505a206f" dependencies = [ - "bytes 1.0.1", + "bytes", "curve25519-dalek 3.0.2", - "futures 0.3.16", + "futures", "lazy_static", "libp2p-core", - "log 0.4.14", + "log", "prost", "prost-build", - "rand 0.8.4", - "sha2 0.9.3", + "rand 0.8.5", + "sha2 0.10.2", "snow", "static_assertions", "x25519-dalek", @@ -3486,155 +3449,92 @@ dependencies = [ [[package]] name = "libp2p-ping" -version = "0.30.0" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2482cfd9eb0b7a0baaf3e7b329dc4f2785181a161b1a47b7192f8d758f54a439" +checksum = "7228b9318d34689521349a86eb39a3c3a802c9efc99a0568062ffb80913e3f91" dependencies = [ - "futures 0.3.16", + "futures", + "futures-timer", + "instant", "libp2p-core", "libp2p-swarm", - "log 0.4.14", - "rand 0.7.3", + "log", + "rand 0.8.5", "void", - "wasm-timer", ] [[package]] -name = "libp2p-plaintext" -version = "0.29.0" +name = "libp2p-request-response" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b4783e5423870b9a5c199f65a7a3bc66d86ab56b2b9beebf3c338d889cf8e4" +checksum = "8827af16a017b65311a410bb626205a9ad92ec0473967618425039fa5231adc1" dependencies = [ - "asynchronous-codec 0.6.0", - "bytes 1.0.1", - "futures 0.3.16", + "async-trait", + "bytes", + "futures", + "instant", "libp2p-core", - "log 0.4.14", - "prost", - "prost-build", - "unsigned-varint 0.7.0", - "void", + "libp2p-swarm", + "log", + "rand 0.8.5", + "smallvec", + "unsigned-varint", ] [[package]] -name = "libp2p-pnet" -version = "0.21.0" +name = "libp2p-swarm" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cb4dd4b917e5b40ddefe49b96b07adcd8d342e0317011d175b7b2bb1dcc974" +checksum = "46d13df7c37807965d82930c0e4b04a659efcb6cca237373b206043db5398ecf" dependencies = [ - "futures 0.3.16", - "log 0.4.14", - "pin-project 1.0.5", - "rand 0.7.3", - "salsa20", - "sha3", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "log", + "pin-project", + "rand 0.8.5", + "smallvec", + "thiserror", + "void", ] [[package]] -name = "libp2p-relay" -version = "0.3.0" +name = "libp2p-swarm-derive" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0133f6cfd81cdc16e716de2982e012c62e6b9d4f12e41967b3ee361051c622aa" +checksum = "a0eddc4497a8b5a506013c40e8189864f9c3a00db2b25671f428ae9007f3ba32" dependencies = [ - "asynchronous-codec 0.6.0", - "bytes 1.0.1", - "futures 0.3.16", - "futures-timer 3.0.2", - "libp2p-core", - "libp2p-swarm", - "log 0.4.14", - "pin-project 1.0.5", - "prost", - "prost-build", - "rand 0.7.3", - "smallvec 1.6.1", - "unsigned-varint 0.7.0", - "void", - "wasm-timer", + "heck", + "quote", + "syn", ] [[package]] -name = "libp2p-request-response" -version = "0.12.0" +name = "libp2p-tcp" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06cdae44b6821466123af93cbcdec7c9e6ba9534a8af9cdc296446d39416d241" +checksum = "9839d96761491c6d3e238e70554b856956fca0ab60feb9de2cd08eed4473fa92" dependencies = [ - "async-trait", - "bytes 1.0.1", - "futures 0.3.16", + "futures", + "futures-timer", + "if-watch", + "libc", "libp2p-core", - "libp2p-swarm", - "log 0.4.14", - "lru", - "minicbor", - "rand 0.7.3", - "smallvec 1.6.1", - "unsigned-varint 0.7.0", - "wasm-timer", -] - -[[package]] -name = "libp2p-swarm" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7083861341e1555467863b4cd802bea1e8c4787c0f7b5110097d0f1f3248f9a9" -dependencies = [ - "either", - "futures 0.3.16", - "libp2p-core", - "log 0.4.14", - "rand 0.7.3", - "smallvec 1.6.1", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-swarm-derive" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8cb308d4fc854869f5abb54fdab0833d2cf670d407c745849dc47e6e08d79c" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "libp2p-tcp" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79edd26b6b4bb5feee210dcda562dca186940dfecb0024b979c3f50824b3bf28" -dependencies = [ - "async-io", - "futures 0.3.16", - "futures-timer 3.0.2", - "if-watch", - "ipnet", - "libc", - "libp2p-core", - "log 0.4.14", - "socket2 0.4.0", -] - -[[package]] -name = "libp2p-uds" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280e793440dd4e9f273d714f4497325c72cddb0fe85a49f9a03c88f41dd20182" -dependencies = [ - "async-std", - "futures 0.3.16", - "libp2p-core", - "log 0.4.14", + "log", + "socket2", + "tokio", ] [[package]] name = "libp2p-wasm-ext" -version = "0.29.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f553b7140fad3d7a76f50497b0ea591e26737d9607428a75509fc191e4d1b1f6" +checksum = "a17b5b8e7a73e379e47b1b77f8a82c4721e97eca01abcd18e9cd91a23ca6ce97" dependencies = [ - "futures 0.3.16", + "futures", "js-sys", "libp2p-core", "parity-send-wrapper", @@ -3644,126 +3544,96 @@ dependencies = [ [[package]] name = "libp2p-websocket" -version = "0.30.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf99dcbf5063e9d59087f61b1e85c686ceab2f5abedb472d32288065c0e5e27" +checksum = "3758ae6f89b2531a24b6d9f5776bda6a626b60a57600d7185d43dfa75ca5ecc4" dependencies = [ "either", - "futures 0.3.16", + "futures", "futures-rustls", "libp2p-core", - "log 0.4.14", + "log", + "parking_lot 0.12.1", "quicksink", "rw-stream-sink", - "soketto 0.4.2", - "url 2.2.1", + "soketto", + "url", "webpki-roots", ] [[package]] name = "libp2p-yamux" -version = "0.33.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214cc0dd9c37cbed27f0bb1eba0c41bbafdb93a8be5e9d6ae1e6b4b42cd044bf" +checksum = "30f079097a21ad017fc8139460630286f02488c8c13b26affb46623aa20d8845" dependencies = [ - "futures 0.3.16", + "futures", "libp2p-core", - "parking_lot 0.11.1", + "log", + "parking_lot 0.12.1", "thiserror", "yamux", ] [[package]] name = "librocksdb-sys" -version = "6.20.3" +version = "0.8.0+7.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c309a9d2470844aceb9a4a098cf5286154d20596868b75a6b36357d2bb9ca25d" +checksum = "611804e4666a25136fcc5f8cf425ab4d26c7f74ea245ffe92ea23b85b6420b5d" dependencies = [ "bindgen", + "bzip2-sys", "cc", "glob", "libc", + "libz-sys", + "tikv-jemalloc-sys", ] [[package]] name = "libsecp256k1" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" -dependencies = [ - "arrayref", - "crunchy", - "digest 0.8.1", - "hmac-drbg 0.2.0", - "rand 0.7.3", - "sha2 0.8.2", - "subtle 2.4.0", - "typenum", -] - -[[package]] -name = "libsecp256k1" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd1137239ab33b41aa9637a88a28249e5e70c40a42ccc92db7f12cc356c1fcd7" -dependencies = [ - "arrayref", - "base64 0.12.3", - "digest 0.9.0", - "hmac-drbg 0.3.0", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", - "rand 0.7.3", - "serde", - "sha2 0.9.3", - "typenum", -] - -[[package]] -name = "libsecp256k1" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" dependencies = [ "arrayref", - "base64 0.12.3", + "base64", "digest 0.9.0", - "hmac-drbg 0.3.0", + "hmac-drbg", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", - "rand 0.7.3", + "rand 0.8.5", "serde", - "sha2 0.9.3", + "sha2 0.9.8", "typenum", ] [[package]] name = "libsecp256k1-core" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.4.0", + "subtle", ] [[package]] name = "libsecp256k1-gen-ecmult" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" dependencies = [ "libsecp256k1-core", ] [[package]] name = "libsecp256k1-gen-genmult" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" dependencies = [ "libsecp256k1-core", ] @@ -3775,10 +3645,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" dependencies = [ "cc", + "libc", "pkg-config", "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -3796,76 +3676,63 @@ dependencies = [ [[package]] name = "linregress" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6e407dadb4ca4b31bc69c27aff00e7ca4534fdcee855159b039a7cebb5f395" +checksum = "d6c601a85f5ecd1aba625247bca0031585fb1c446461b142878a16f8245ddeb8" dependencies = [ "nalgebra", "statrs", ] [[package]] -name = "lite-json" -version = "0.1.3" +name = "linux-raw-sys" +version = "0.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0460d985423a026b4d9b828a7c6eed1bcf606f476322f3f9b507529686a61715" -dependencies = [ - "lite-parser", -] +checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" [[package]] -name = "lite-parser" -version = "0.1.2" +name = "lite-json" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c50092e40e0ccd1bf2015a10333fde0502ff95b832b0895dc1ca0d7ac6c52f6" +checksum = "cd0e787ffe1153141a0f6f6d759fdf1cc34b1226e088444523812fd412a5cca2" dependencies = [ - "paste 0.1.18", + "lite-parser", ] [[package]] -name = "lock_api" -version = "0.3.4" +name = "lite-parser" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +checksum = "c3d5f9dc37c52d889a21fd701983d02bb6a84f852c5140a6c80ef4557f7dc29e" dependencies = [ - "scopeguard", + "paste", ] [[package]] name = "lock_api" -version = "0.4.2" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] [[package]] name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -dependencies = [ - "log 0.4.14", -] - -[[package]] -name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", - "value-bag", + "cfg-if", ] [[package]] name = "lru" -version = "0.6.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea2d928b485416e8908cff2d97d621db22b27f7b3b6729e438bcf42c671ba91" +checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" dependencies = [ - "hashbrown 0.11.2", + "hashbrown 0.12.3", ] [[package]] @@ -3879,9 +3746,9 @@ dependencies = [ [[package]] name = "lz4" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac20ed6991e01bf6a2e68cc73df2b389707403662a8ba89f68511fb340f724c" +checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" dependencies = [ "libc", "lz4-sys", @@ -3889,9 +3756,9 @@ dependencies = [ [[package]] name = "lz4-sys" -version = "1.9.2" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca79aa95d8b3226213ad454d328369853be3a1382d89532a854f4d69640acae" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" dependencies = [ "cc", "libc", @@ -3906,12 +3773,6 @@ dependencies = [ "libc", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "match_cfg" version = "0.1.0" @@ -3942,62 +3803,54 @@ dependencies = [ "rawpointer", ] -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] -name = "memmap" -version = "0.7.0" +name = "memfd" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +checksum = "480b5a5de855d11ff13195950bdc8b98b5e942ef47afc447f6615cdcc4e15d80" dependencies = [ - "libc", - "winapi 0.3.9", + "rustix", ] [[package]] name = "memmap2" -version = "0.2.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e3e85b970d650e2ae6d70592474087051c11c54da7f7b4949725c5735fbcc6" +checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" dependencies = [ - "autocfg 1.0.1", + "autocfg", ] [[package]] name = "memory-db" -version = "0.27.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de006e09d04fc301a5f7e817b75aa49801c4479a8af753764416b085337ddcc5" +checksum = "5e0c7cba9ce19ac7ffd2053ac9f49843bbd3f4318feedfd74e85c19d5fb0ba66" dependencies = [ "hash-db", - "hashbrown 0.11.2", - "parity-util-mem", + "hashbrown 0.12.3", ] [[package]] name = "memory_units" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "merlin" @@ -4012,33 +3865,10 @@ dependencies = [ ] [[package]] -name = "mime" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" -dependencies = [ - "log 0.3.9", -] - -[[package]] -name = "minicbor" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea79ce4ab9f445ec6b71833a2290ac0a29c9dde0fa7cae4c481eecae021d9bd9" -dependencies = [ - "minicbor-derive", -] - -[[package]] -name = "minicbor-derive" -version = "0.6.2" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce18b5423c573a13e80cb3046ea0af6379ef725dc3af4886bdb8f4e5093068" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" @@ -4047,104 +3877,111 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", - "autocfg 1.0.1", + "autocfg", ] [[package]] name = "mio" -version = "0.6.23" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", "libc", - "log 0.4.14", - "miow 0.2.2", - "net2", - "slab", - "winapi 0.2.8", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.36.1", ] [[package]] -name = "mio" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +name = "mmr-gadget" +version = "4.0.0-dev" dependencies = [ - "libc", - "log 0.4.14", - "miow 0.3.6", - "ntapi", - "winapi 0.3.9", + "futures", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-block-builder", + "sc-client-api", + "sc-offchain", + "sp-api", + "sp-beefy", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-io", + "sp-mmr-primitives", + "sp-runtime", + "sp-tracing", + "substrate-test-runtime-client", + "tokio", ] [[package]] -name = "mio-extras" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +name = "mmr-rpc" +version = "4.0.0-dev" dependencies = [ - "lazycell", - "log 0.4.14", - "mio 0.6.23", - "slab", + "anyhow", + "jsonrpsee", + "parity-scale-codec", + "serde", + "serde_json", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-mmr-primitives", + "sp-runtime", ] [[package]] -name = "miow" -version = "0.2.2" +name = "mockall" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +checksum = "e2be9a9090bc1cac2930688fa9478092a64c6a92ddc6ae0692d46b37d9cab709" dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", ] [[package]] -name = "miow" -version = "0.3.6" +name = "mockall_derive" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" +checksum = "86d702a0530a0141cf4ed147cf5ec7be6f2c187d4e37fcbefc39cf34116bfe8f" dependencies = [ - "socket2 0.3.19", - "winapi 0.3.9", + "cfg-if", + "proc-macro2", + "quote", + "syn", ] -[[package]] -name = "more-asserts" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" - [[package]] name = "multiaddr" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ee4ea82141951ac6379f964f71b20876d43712bea8faf6dd1a375e08a46499" +checksum = "3c580bfdd8803cce319b047d239559a22f809094aaea4ac13902a1fdcfcd4261" dependencies = [ "arrayref", "bs58", "byteorder", "data-encoding", - "multihash 0.14.0", - "percent-encoding 2.1.0", + "multihash", + "percent-encoding", "serde", "static_assertions", - "unsigned-varint 0.7.0", - "url 2.2.1", + "unsigned-varint", + "url", ] [[package]] name = "multibase" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b78c60039650ff12e140ae867ef5299a58e19dded4d334c849dc7177083667e2" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" dependencies = [ "base-x", "data-encoding", @@ -4153,42 +3990,29 @@ dependencies = [ [[package]] name = "multihash" -version = "0.13.2" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dac63698b887d2d929306ea48b63760431ff8a24fac40ddb22f9c7f49fb7cab" +checksum = "e3db354f401db558759dfc1e568d010a5d4146f4d3f637be1275ec4a3cf09689" dependencies = [ "blake2b_simd", "blake2s_simd", "blake3", - "digest 0.9.0", - "generic-array 0.14.4", + "core2", + "digest 0.10.3", "multihash-derive", - "sha2 0.9.3", + "sha2 0.10.2", "sha3", - "unsigned-varint 0.5.1", -] - -[[package]] -name = "multihash" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "752a61cd890ff691b4411423d23816d5866dd5621e4d1c5687a53b94b5a979d8" -dependencies = [ - "digest 0.9.0", - "generic-array 0.14.4", - "multihash-derive", - "sha2 0.9.3", - "unsigned-varint 0.7.0", + "unsigned-varint", ] [[package]] name = "multihash-derive" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" +checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" dependencies = [ - "proc-macro-crate 1.0.0", - "proc-macro-error 1.0.4", + "proc-macro-crate", + "proc-macro-error", "proc-macro2", "quote", "syn", @@ -4203,16 +4027,16 @@ checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333" [[package]] name = "multistream-select" -version = "0.10.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d91ec0a2440aaff5f78ec35631a7027d50386c6163aa975f7caa0d5da4b6ff8" +checksum = "9bc41247ec209813e2fd414d6e16b9d94297dacf3cd613fa6ef09cd4d9755c10" dependencies = [ - "bytes 1.0.1", - "futures 0.3.16", - "log 0.4.14", - "pin-project 1.0.5", - "smallvec 1.6.1", - "unsigned-varint 0.7.0", + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint", ] [[package]] @@ -4225,9 +4049,9 @@ dependencies = [ "matrixmultiply", "nalgebra-macros", "num-complex", - "num-rational 0.4.0", + "num-rational", "num-traits", - "rand 0.8.4", + "rand 0.8.5", "rand_distr", "simba", "typenum", @@ -4246,82 +4070,121 @@ dependencies = [ [[package]] name = "names" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a8690bf09abf659851e58cd666c3d37ac6af07c2bd7a9e332cfba471715775" +checksum = "e7d66043b25d4a6cccb23619d10c19c25304b355a7dccd4a8e11423dd2382146" dependencies = [ - "rand 0.8.4", + "rand 0.8.5", ] [[package]] -name = "native-tls" -version = "0.2.8" +name = "netlink-packet-core" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" dependencies = [ - "lazy_static", + "anyhow", + "byteorder", "libc", - "log 0.4.14", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "netlink-packet-utils", ] [[package]] -name = "nb-connect" -version = "1.0.3" +name = "netlink-packet-route" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670361df1bc2399ee1ff50406a0d422587dd3bb0da596e1978fe8e05dabddf4f" +checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" dependencies = [ + "anyhow", + "bitflags", + "byteorder", "libc", - "socket2 0.3.19", + "netlink-packet-core", + "netlink-packet-utils", ] [[package]] -name = "net2" -version = "0.2.37" +name = "netlink-packet-utils" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +checksum = "25af9cf0dc55498b7bd94a1508af7a78706aa0ab715a73c5169273e03c84845e" dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", + "anyhow", + "byteorder", + "paste", + "thiserror", ] [[package]] -name = "nix" -version = "0.19.1" +name = "netlink-proto" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror", + "tokio", ] [[package]] -name = "node-bench" -version = "0.9.0-dev" +name = "netlink-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92b654097027250401127914afb37cb1f311df6610a9891ff07a757e94199027" dependencies = [ - "derive_more", - "fs_extra", - "futures 0.3.16", + "async-io", + "bytes", + "futures", + "libc", + "log", +] + +[[package]] +name = "nix" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "node-bench" +version = "0.9.0-dev" +dependencies = [ + "array-bytes", + "clap 4.0.11", + "derive_more", + "fs_extra", + "futures", "hash-db", - "hex", + "kitchensink-runtime", "kvdb", "kvdb-rocksdb", "lazy_static", - "log 0.4.14", + "log", "node-primitives", - "node-runtime", "node-testing", "parity-db", - "parity-util-mem", "rand 0.7.3", "sc-basic-authorship", "sc-client-api", @@ -4337,7 +4200,6 @@ dependencies = [ "sp-timestamp", "sp-tracing", "sp-trie", - "structopt", "tempfile", ] @@ -4345,27 +4207,36 @@ dependencies = [ name = "node-cli" version = "3.0.0-dev" dependencies = [ + "array-bytes", "assert_cmd", - "async-std", + "clap 4.0.11", + "clap_complete", + "criterion", "frame-benchmarking-cli", "frame-system", - "futures 0.3.16", - "hex-literal", - "log 0.4.14", - "nix", + "frame-system-rpc-runtime-api", + "futures", + "jsonrpsee", + "kitchensink-runtime", + "log", + "nix 0.23.1", "node-executor", "node-inspect", "node-primitives", "node-rpc", - "node-runtime", + "pallet-asset-tx-payment", + "pallet-assets", + "pallet-balances", "pallet-im-online", + "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec", "platforms", - "rand 0.7.3", + "rand 0.8.5", "regex", "sc-authority-discovery", "sc-basic-authorship", + "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", @@ -4379,18 +4250,22 @@ dependencies = [ "sc-finality-grandpa", "sc-keystore", "sc-network", + "sc-network-common", "sc-rpc", "sc-service", "sc-service-test", "sc-sync-state-rpc", + "sc-sysinfo", "sc-telemetry", "sc-transaction-pool", "sc-transaction-pool-api", "serde", "serde_json", - "soketto 0.4.2", + "soketto", + "sp-api", "sp-authority-discovery", "sp-authorship", + "sp-blockchain", "sp-consensus", "sp-consensus-babe", "sp-core", @@ -4403,12 +4278,14 @@ dependencies = [ "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", - "sp-trie", - "structopt", "substrate-build-script-utils", "substrate-frame-cli", + "substrate-rpc-client", "tempfile", + "tokio", + "tokio-util", "try-runtime-cli", + "wait-timeout", ] [[package]] @@ -4419,14 +4296,17 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "futures 0.3.16", + "futures", + "kitchensink-runtime", "node-primitives", - "node-runtime", "node-testing", "pallet-balances", "pallet-contracts", "pallet-im-online", + "pallet-root-testing", + "pallet-sudo", "pallet-timestamp", + "pallet-transaction-payment", "pallet-treasury", "parity-scale-codec", "sc-executor", @@ -4435,9 +4315,11 @@ dependencies = [ "sp-consensus-babe", "sp-core", "sp-externalities", + "sp-keyring", "sp-keystore", "sp-runtime", "sp-state-machine", + "sp-tracing", "sp-trie", "wat", ] @@ -4446,7 +4328,7 @@ dependencies = [ name = "node-inspect" version = "0.9.0-dev" dependencies = [ - "derive_more", + "clap 4.0.11", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -4455,7 +4337,7 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-runtime", - "structopt", + "thiserror", ] [[package]] @@ -4474,10 +4356,9 @@ dependencies = [ name = "node-rpc" version = "3.0.0-dev" dependencies = [ - "jsonrpc-core", + "jsonrpsee", + "mmr-rpc", "node-primitives", - "pallet-contracts-rpc", - "pallet-mmr-rpc", "pallet-transaction-payment-rpc", "sc-chain-spec", "sc-client-api", @@ -4488,6 +4369,7 @@ dependencies = [ "sc-finality-grandpa-rpc", "sc-rpc", "sc-rpc-api", + "sc-rpc-spec-v2", "sc-sync-state-rpc", "sc-transaction-pool-api", "sp-api", @@ -4498,107 +4380,30 @@ dependencies = [ "sp-keystore", "sp-runtime", "substrate-frame-rpc-system", + "substrate-state-trie-migration-rpc", ] [[package]] -name = "node-rpc-client" -version = "2.0.0" -dependencies = [ - "futures 0.3.16", - "jsonrpc-core-client", - "node-primitives", - "sc-rpc", - "sp-tracing", -] - -[[package]] -name = "node-runtime" -version = "3.0.0-dev" +name = "node-runtime-generate-bags" +version = "3.0.0" dependencies = [ - "frame-benchmarking", - "frame-election-provider-support", - "frame-executive", - "frame-support", - "frame-system", - "frame-system-benchmarking", - "frame-system-rpc-runtime-api", - "frame-try-runtime", - "hex-literal", - "log 0.4.14", - "node-primitives", - "pallet-assets", - "pallet-authority-discovery", - "pallet-authorship", - "pallet-babe", - "pallet-balances", - "pallet-bounties", - "pallet-collective", - "pallet-contracts", - "pallet-contracts-primitives", - "pallet-contracts-rpc-runtime-api", - "pallet-democracy", - "pallet-election-provider-multi-phase", - "pallet-elections-phragmen", - "pallet-gilt", - "pallet-grandpa", - "pallet-identity", - "pallet-im-online", - "pallet-indices", - "pallet-lottery", - "pallet-membership", - "pallet-mmr", - "pallet-multisig", - "pallet-offences", - "pallet-offences-benchmarking", - "pallet-proxy", - "pallet-randomness-collective-flip", - "pallet-recovery", - "pallet-scheduler", - "pallet-session", - "pallet-session-benchmarking", - "pallet-society", - "pallet-staking", - "pallet-staking-reward-curve", - "pallet-sudo", - "pallet-timestamp", - "pallet-tips", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "pallet-transaction-storage", - "pallet-treasury", - "pallet-uniques", - "pallet-utility", - "pallet-vesting", - "parity-scale-codec", - "scale-info", - "sp-api", - "sp-authority-discovery", - "sp-block-builder", - "sp-consensus-babe", - "sp-core", - "sp-inherents", - "sp-io", - "sp-keyring", - "sp-npos-elections", - "sp-offchain", - "sp-runtime", - "sp-session", - "sp-staking", - "sp-std", - "sp-transaction-pool", - "sp-version", - "static_assertions", - "substrate-wasm-builder", + "clap 4.0.11", + "generate-bags", + "kitchensink-runtime", ] [[package]] name = "node-template" -version = "3.0.0" +version = "4.0.0-dev" dependencies = [ + "clap 4.0.11", "frame-benchmarking", "frame-benchmarking-cli", - "jsonrpc-core", + "frame-system", + "futures", + "jsonrpsee", "node-template-runtime", + "pallet-transaction-payment", "pallet-transaction-payment-rpc", "sc-basic-authorship", "sc-cli", @@ -4621,16 +4426,18 @@ dependencies = [ "sp-consensus-aura", "sp-core", "sp-finality-grandpa", + "sp-inherents", + "sp-keyring", "sp-runtime", "sp-timestamp", - "structopt", "substrate-build-script-utils", "substrate-frame-rpc-system", + "try-runtime-cli", ] [[package]] name = "node-template-runtime" -version = "3.0.0" +version = "4.0.0-dev" dependencies = [ "frame-benchmarking", "frame-executive", @@ -4638,7 +4445,7 @@ dependencies = [ "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", - "hex-literal", + "frame-try-runtime", "pallet-aura", "pallet-balances", "pallet-grandpa", @@ -4670,11 +4477,13 @@ version = "3.0.0-dev" dependencies = [ "frame-system", "fs_extra", - "futures 0.3.16", - "log 0.4.14", + "futures", + "kitchensink-runtime", + "log", "node-executor", "node-primitives", - "node-runtime", + "pallet-asset-tx-payment", + "pallet-assets", "pallet-transaction-payment", "parity-scale-codec", "sc-block-builder", @@ -4697,12 +4506,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nohash-hasher" version = "0.2.0" @@ -4711,32 +4514,28 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "6.2.1" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ - "bitvec 0.19.5", - "funty", "memchr", - "version_check 0.9.2", + "minimal-lexical", + "version_check", ] [[package]] -name = "ntapi" -version = "0.3.6" +name = "normalize-line-endings" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi 0.3.9", -] +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num-bigint" -version = "0.2.6" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-integer", "num-traits", ] @@ -4751,34 +4550,33 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.44" +name = "num-format" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "54b862ff8df690cf089058c98b183676a7ed0f974cc08b426800093227cbff3b" dependencies = [ - "autocfg 1.0.1", - "num-traits", + "arrayvec 0.7.2", + "itoa 1.0.4", ] [[package]] -name = "num-rational" -version = "0.2.4" +name = "num-integer" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.0.1", - "num-bigint", - "num-integer", + "autocfg", "num-traits", ] [[package]] name = "num-rational" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ - "autocfg 1.0.1", + "autocfg", + "num-bigint", "num-integer", "num-traits", ] @@ -4789,15 +4587,15 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.1", + "autocfg", "libm", ] [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", @@ -4805,38 +4603,30 @@ dependencies = [ [[package]] name = "object" -version = "0.22.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" dependencies = [ - "crc32fast", - "indexmap", + "memchr", ] [[package]] name = "object" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" - -[[package]] -name = "object" -version = "0.24.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ "crc32fast", + "hashbrown 0.12.3", "indexmap", + "memchr", ] [[package]] name = "once_cell" -version = "1.7.2" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" -dependencies = [ - "parking_lot 0.11.1", -] +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "oorandom" @@ -4856,20 +4646,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - [[package]] name = "openssl-probe" version = "0.1.2" @@ -4877,17 +4653,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] -name = "openssl-sys" -version = "0.9.65" +name = "os_str_bytes" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d" -dependencies = [ - "autocfg 1.0.1", - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" [[package]] name = "output_vt100" @@ -4895,60 +4664,486 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] -name = "owning_ref" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +name = "pallet-alliance" +version = "4.0.0-dev" dependencies = [ - "stable_deref_trait", + "array-bytes", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-collective", + "pallet-identity", + "parity-scale-codec", + "scale-info", + "sha2 0.10.2", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-asset-tx-payment" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-authorship", + "pallet-balances", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-storage", +] + +[[package]] +name = "pallet-assets" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-atomic-swap" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-aura" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-aura", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-authority-discovery" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-authority-discovery", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-authorship" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-authorship", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-babe" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-balances", + "pallet-offences", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-babe", + "sp-consensus-vrf", + "sp-core", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-bags-list" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-tracing", +] + +[[package]] +name = "pallet-bags-list-fuzzer" +version = "4.0.0-dev" +dependencies = [ + "frame-election-provider-support", + "honggfuzz", + "pallet-bags-list", + "rand 0.8.5", +] + +[[package]] +name = "pallet-bags-list-remote-tests" +version = "4.0.0-dev" +dependencies = [ + "frame-election-provider-support", + "frame-remote-externalities", + "frame-support", + "frame-system", + "log", + "pallet-bags-list", + "pallet-staking", + "sp-core", + "sp-runtime", + "sp-std", + "sp-storage", + "sp-tracing", +] + +[[package]] +name = "pallet-balances" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-beefy" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "pallet-session", + "parity-scale-codec", + "scale-info", + "serde", + "sp-beefy", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-beefy-mmr" +version = "4.0.0-dev" +dependencies = [ + "array-bytes", + "beefy-merkle-tree", + "frame-support", + "frame-system", + "log", + "pallet-beefy", + "pallet-mmr", + "pallet-session", + "parity-scale-codec", + "scale-info", + "serde", + "sp-beefy", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-bounties" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-treasury", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-child-bounties" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-bounties", + "pallet-treasury", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-collective" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-contracts" +version = "4.0.0-dev" +dependencies = [ + "array-bytes", + "assert_matches", + "bitflags", + "env_logger", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-balances", + "pallet-contracts-primitives", + "pallet-contracts-proc-macro", + "pallet-randomness-collective-flip", + "pallet-timestamp", + "pallet-utility", + "parity-scale-codec", + "pretty_assertions", + "rand 0.8.5", + "rand_pcg 0.3.1", + "scale-info", + "serde", + "smallvec", + "sp-api", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-std", + "wasm-instrument 0.4.0", + "wasmi 0.20.0", + "wasmparser-nostd", + "wat", +] + +[[package]] +name = "pallet-contracts-primitives" +version = "7.0.0" +dependencies = [ + "bitflags", + "parity-scale-codec", + "sp-runtime", + "sp-std", + "sp-weights", +] + +[[package]] +name = "pallet-contracts-proc-macro" +version = "4.0.0-dev" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pallet-conviction-voting" +version = "4.0.0-dev" +dependencies = [ + "assert_matches", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-scheduler", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-democracy" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-preimage", + "pallet-scheduler", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", ] [[package]] -name = "pallet-assets" +name = "pallet-election-provider-multi-phase" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", + "log", "pallet-balances", + "pallet-election-provider-support-benchmarking", "parity-scale-codec", + "parking_lot 0.12.1", + "rand 0.7.3", "scale-info", + "sp-arithmetic", "sp-core", "sp-io", + "sp-npos-elections", "sp-runtime", "sp-std", + "sp-tracing", + "static_assertions", + "strum", ] [[package]] -name = "pallet-atomic-swap" +name = "pallet-election-provider-support-benchmarking" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-system", + "parity-scale-codec", + "sp-npos-elections", + "sp-runtime", +] + +[[package]] +name = "pallet-elections-phragmen" +version = "5.0.0-dev" +dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", "sp-io", + "sp-npos-elections", "sp-runtime", "sp-std", + "sp-tracing", + "substrate-test-utils", ] [[package]] -name = "pallet-aura" +name = "pallet-example-basic" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", - "pallet-timestamp", + "log", + "pallet-balances", "parity-scale-codec", "scale-info", - "sp-application-crypto", - "sp-consensus-aura", "sp-core", "sp-io", "sp-runtime", @@ -4956,47 +5151,57 @@ dependencies = [ ] [[package]] -name = "pallet-authority-discovery" +name = "pallet-example-offchain-worker" version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "pallet-session", + "lite-json", + "log", "parity-scale-codec", "scale-info", - "sp-application-crypto", - "sp-authority-discovery", "sp-core", "sp-io", + "sp-keystore", "sp-runtime", "sp-std", ] [[package]] -name = "pallet-authorship" +name = "pallet-fast-unstake" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", - "impl-trait-for-tuples", + "log", + "pallet-assets", + "pallet-balances", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-authorship", "sp-core", "sp-io", "sp-runtime", + "sp-staking", "sp-std", + "sp-tracing", + "substrate-test-utils", ] [[package]] -name = "pallet-babe" +name = "pallet-grandpa" version = "4.0.0-dev" dependencies = [ + "finality-grandpa", "frame-benchmarking", "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log", "pallet-authorship", "pallet-balances", "pallet-offences", @@ -5007,10 +5212,10 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-application-crypto", - "sp-consensus-babe", - "sp-consensus-vrf", "sp-core", + "sp-finality-grandpa", "sp-io", + "sp-keyring", "sp-runtime", "sp-session", "sp-staking", @@ -5018,14 +5223,14 @@ dependencies = [ ] [[package]] -name = "pallet-balances" +name = "pallet-identity" version = "4.0.0-dev" dependencies = [ + "enumflags2", "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", - "pallet-transaction-payment", + "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", @@ -5035,138 +5240,69 @@ dependencies = [ ] [[package]] -name = "pallet-bounties" +name = "pallet-im-online" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "pallet-balances", - "pallet-treasury", + "log", + "pallet-authorship", + "pallet-session", "parity-scale-codec", "scale-info", + "sp-application-crypto", "sp-core", "sp-io", "sp-runtime", + "sp-staking", "sp-std", ] [[package]] -name = "pallet-collective" +name = "pallet-indices" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", "sp-io", + "sp-keyring", "sp-runtime", "sp-std", ] [[package]] -name = "pallet-contracts" +name = "pallet-lottery" version = "4.0.0-dev" dependencies = [ - "assert_matches", - "bitflags", "frame-benchmarking", "frame-support", + "frame-support-test", "frame-system", - "hex-literal", - "libsecp256k1 0.3.5", - "log 0.4.14", "pallet-balances", - "pallet-contracts-primitives", - "pallet-contracts-proc-macro", - "pallet-randomness-collective-flip", - "pallet-timestamp", - "pallet-utility", "parity-scale-codec", - "pretty_assertions 0.7.2", - "pwasm-utils", - "rand 0.7.3", - "rand_pcg 0.2.1", "scale-info", - "serde", - "smallvec 1.6.1", "sp-core", "sp-io", "sp-runtime", - "sp-sandbox", - "sp-std", - "wasmi-validation", - "wat", -] - -[[package]] -name = "pallet-contracts-primitives" -version = "4.0.0-dev" -dependencies = [ - "bitflags", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-contracts-proc-macro" -version = "4.0.0-dev" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pallet-contracts-rpc" -version = "4.0.0-dev" -dependencies = [ - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "pallet-contracts-primitives", - "pallet-contracts-rpc-runtime-api", - "parity-scale-codec", - "serde", - "serde_json", - "sp-api", - "sp-blockchain", - "sp-core", - "sp-rpc", - "sp-runtime", -] - -[[package]] -name = "pallet-contracts-rpc-runtime-api" -version = "4.0.0-dev" -dependencies = [ - "pallet-contracts-primitives", - "parity-scale-codec", - "scale-info", - "sp-api", - "sp-runtime", "sp-std", ] [[package]] -name = "pallet-democracy" +name = "pallet-membership" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "pallet-balances", - "pallet-scheduler", + "log", "parity-scale-codec", "scale-info", - "serde", "sp-core", "sp-io", "sp-runtime", @@ -5174,73 +5310,69 @@ dependencies = [ ] [[package]] -name = "pallet-election-provider-multi-phase" -version = "4.0.0-dev" +name = "pallet-message-queue" +version = "7.0.0-dev" dependencies = [ "frame-benchmarking", - "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", - "pallet-balances", + "log", "parity-scale-codec", - "parking_lot 0.11.1", - "rand 0.7.3", + "rand 0.8.5", + "rand_distr", "scale-info", + "serde", "sp-arithmetic", "sp-core", "sp-io", - "sp-npos-elections", "sp-runtime", "sp-std", "sp-tracing", - "static_assertions", - "strum 0.21.0", - "strum_macros 0.21.1", + "sp-weights", ] [[package]] -name = "pallet-elections" +name = "pallet-mmr" version = "4.0.0-dev" dependencies = [ + "array-bytes", + "env_logger", + "frame-benchmarking", "frame-support", "frame-system", - "pallet-balances", + "itertools", "parity-scale-codec", "scale-info", "sp-core", "sp-io", + "sp-mmr-primitives", "sp-runtime", "sp-std", ] [[package]] -name = "pallet-elections-phragmen" -version = "5.0.0-dev" +name = "pallet-multisig" +version = "4.0.0-dev" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log", "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", "sp-io", - "sp-npos-elections", "sp-runtime", "sp-std", - "substrate-test-utils", ] [[package]] -name = "pallet-example" +name = "pallet-nicks" version = "4.0.0-dev" dependencies = [ - "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5251,231 +5383,197 @@ dependencies = [ ] [[package]] -name = "pallet-example-offchain-worker" +name = "pallet-nis" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", - "lite-json", - "log 0.4.14", + "pallet-balances", "parity-scale-codec", "scale-info", + "sp-arithmetic", "sp-core", "sp-io", - "sp-keystore", "sp-runtime", "sp-std", ] [[package]] -name = "pallet-example-parallel" -version = "3.0.0-dev" +name = "pallet-node-authorization" +version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", + "log", "parity-scale-codec", "scale-info", "sp-core", "sp-io", "sp-runtime", "sp-std", - "sp-tasks", ] [[package]] -name = "pallet-gilt" -version = "4.0.0-dev" +name = "pallet-nomination-pools" +version = "1.0.0" dependencies = [ - "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-balances", "parity-scale-codec", "scale-info", - "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", + "sp-staking", "sp-std", + "sp-tracing", ] [[package]] -name = "pallet-grandpa" -version = "4.0.0-dev" +name = "pallet-nomination-pools-benchmarking" +version = "1.0.0" dependencies = [ - "finality-grandpa", "frame-benchmarking", "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", - "pallet-authorship", + "pallet-bags-list", "pallet-balances", - "pallet-offences", - "pallet-session", + "pallet-nomination-pools", "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", "sp-core", - "sp-finality-grandpa", "sp-io", - "sp-keyring", "sp-runtime", - "sp-session", + "sp-runtime-interface", "sp-staking", "sp-std", ] [[package]] -name = "pallet-identity" -version = "4.0.0-dev" +name = "pallet-nomination-pools-fuzzer" +version = "2.0.0" dependencies = [ - "enumflags2", - "frame-benchmarking", "frame-support", "frame-system", - "pallet-balances", - "parity-scale-codec", - "scale-info", - "sp-core", + "honggfuzz", + "log", + "pallet-nomination-pools", + "rand 0.8.5", "sp-io", "sp-runtime", - "sp-std", + "sp-tracing", ] [[package]] -name = "pallet-im-online" -version = "4.0.0-dev" +name = "pallet-nomination-pools-runtime-api" +version = "1.0.0-dev" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log 0.4.14", - "pallet-authorship", - "pallet-session", "parity-scale-codec", - "scale-info", - "sp-application-crypto", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", + "sp-api", "sp-std", ] [[package]] -name = "pallet-indices" -version = "4.0.0-dev" +name = "pallet-nomination-pools-test-staking" +version = "1.0.0" dependencies = [ - "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", + "log", + "pallet-bags-list", "pallet-balances", + "pallet-nomination-pools", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-core", "sp-io", - "sp-keyring", "sp-runtime", + "sp-staking", "sp-std", + "sp-tracing", ] [[package]] -name = "pallet-lottery" +name = "pallet-offences" version = "4.0.0-dev" dependencies = [ - "frame-benchmarking", "frame-support", - "frame-support-test", "frame-system", + "log", "pallet-balances", "parity-scale-codec", "scale-info", + "serde", "sp-core", "sp-io", "sp-runtime", + "sp-staking", "sp-std", ] [[package]] -name = "pallet-membership" -version = "4.0.0-dev" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log 0.4.14", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-mmr" +name = "pallet-offences-benchmarking" version = "4.0.0-dev" dependencies = [ - "ckb-merkle-mountain-range", - "env_logger 0.9.0", "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", - "hex-literal", - "pallet-mmr-primitives", + "pallet-babe", + "pallet-balances", + "pallet-grandpa", + "pallet-im-online", + "pallet-offences", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-core", "sp-io", "sp-runtime", + "sp-staking", "sp-std", ] [[package]] -name = "pallet-mmr-primitives" +name = "pallet-preimage" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", - "hex-literal", - "log 0.4.14", + "log", + "pallet-balances", "parity-scale-codec", - "serde", - "sp-api", + "scale-info", "sp-core", + "sp-io", "sp-runtime", "sp-std", ] [[package]] -name = "pallet-mmr-rpc" -version = "3.0.0" -dependencies = [ - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "pallet-mmr-primitives", - "parity-scale-codec", - "serde", - "serde_json", - "sp-api", - "sp-blockchain", - "sp-core", - "sp-runtime", -] - -[[package]] -name = "pallet-multisig" +name = "pallet-proxy" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", "pallet-balances", + "pallet-utility", "parity-scale-codec", "scale-info", "sp-core", @@ -5485,13 +5583,13 @@ dependencies = [ ] [[package]] -name = "pallet-nicks" +name = "pallet-randomness-collective-flip" version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "pallet-balances", "parity-scale-codec", + "safe-mix", "scale-info", "sp-core", "sp-io", @@ -5500,14 +5598,16 @@ dependencies = [ ] [[package]] -name = "pallet-node-authorization" +name = "pallet-ranked-collective" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log", "parity-scale-codec", "scale-info", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", @@ -5515,60 +5615,53 @@ dependencies = [ ] [[package]] -name = "pallet-offences" +name = "pallet-recovery" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", "pallet-balances", "parity-scale-codec", "scale-info", - "serde", "sp-core", "sp-io", "sp-runtime", - "sp-staking", "sp-std", ] [[package]] -name = "pallet-offences-benchmarking" +name = "pallet-referenda" version = "4.0.0-dev" dependencies = [ + "assert_matches", "frame-benchmarking", - "frame-election-provider-support", "frame-support", "frame-system", - "pallet-babe", + "log", "pallet-balances", - "pallet-grandpa", - "pallet-im-online", - "pallet-offences", - "pallet-session", - "pallet-staking", - "pallet-staking-reward-curve", - "pallet-timestamp", + "pallet-preimage", + "pallet-scheduler", "parity-scale-codec", "scale-info", + "serde", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", - "sp-staking", "sp-std", ] [[package]] -name = "pallet-proxy" +name = "pallet-remark" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "pallet-balances", - "pallet-utility", "parity-scale-codec", "scale-info", + "serde", "sp-core", "sp-io", "sp-runtime", @@ -5576,27 +5669,32 @@ dependencies = [ ] [[package]] -name = "pallet-randomness-collective-flip" -version = "4.0.0-dev" +name = "pallet-root-offences" +version = "1.0.0-dev" dependencies = [ + "frame-election-provider-support", "frame-support", "frame-system", + "pallet-balances", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", - "safe-mix", "scale-info", "sp-core", "sp-io", "sp-runtime", + "sp-staking", "sp-std", ] [[package]] -name = "pallet-recovery" -version = "4.0.0-dev" +name = "pallet-root-testing" +version = "1.0.0-dev" dependencies = [ "frame-support", "frame-system", - "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", @@ -5612,13 +5710,15 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log", + "pallet-preimage", "parity-scale-codec", "scale-info", "sp-core", "sp-io", "sp-runtime", "sp-std", + "sp-weights", "substrate-test-utils", ] @@ -5644,7 +5744,7 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples", - "log 0.4.14", + "log", "pallet-timestamp", "parity-scale-codec", "scale-info", @@ -5705,8 +5805,9 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log", "pallet-authorship", + "pallet-bags-list", "pallet-balances", "pallet-session", "pallet-staking-reward-curve", @@ -5718,6 +5819,7 @@ dependencies = [ "sp-application-crypto", "sp-core", "sp-io", + "sp-npos-elections", "sp-runtime", "sp-staking", "sp-std", @@ -5729,7 +5831,7 @@ dependencies = [ name = "pallet-staking-reward-curve" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 1.0.0", + "proc-macro-crate", "proc-macro2", "quote", "sp-runtime", @@ -5740,10 +5842,35 @@ dependencies = [ name = "pallet-staking-reward-fn" version = "4.0.0-dev" dependencies = [ - "log 0.4.14", + "log", "sp-arithmetic", ] +[[package]] +name = "pallet-state-trie-migration" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-remote-externalities", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "parking_lot 0.12.1", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-tracing", + "substrate-state-trie-migration-rpc", + "thousands", + "tokio", + "zstd", +] + [[package]] name = "pallet-sudo" version = "4.0.0-dev" @@ -5760,7 +5887,7 @@ dependencies = [ [[package]] name = "pallet-template" -version = "3.0.0" +version = "4.0.0-dev" dependencies = [ "frame-benchmarking", "frame-support", @@ -5779,7 +5906,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log", "parity-scale-codec", "scale-info", "sp-core", @@ -5797,7 +5924,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log", "pallet-balances", "pallet-treasury", "parity-scale-codec", @@ -5821,7 +5948,6 @@ dependencies = [ "scale-info", "serde", "serde_json", - "smallvec 1.6.1", "sp-core", "sp-io", "sp-runtime", @@ -5832,9 +5958,7 @@ dependencies = [ name = "pallet-transaction-payment-rpc" version = "4.0.0-dev" dependencies = [ - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", + "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "sp-api", @@ -5842,6 +5966,7 @@ dependencies = [ "sp-core", "sp-rpc", "sp-runtime", + "sp-weights", ] [[package]] @@ -5852,16 +5977,18 @@ dependencies = [ "parity-scale-codec", "sp-api", "sp-runtime", + "sp-weights", ] [[package]] name = "pallet-transaction-storage" version = "4.0.0-dev" dependencies = [ + "array-bytes", "frame-benchmarking", "frame-support", "frame-system", - "hex-literal", + "log", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5899,6 +6026,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5916,6 +6044,9 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", + "pallet-collective", + "pallet-root-testing", + "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-core", @@ -5931,7 +6062,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5941,34 +6072,53 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-whitelist" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-preimage", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "parity-db" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241f9c5d25063080f2c02846221f13e1d0e5e18fa00c32c234aad585b744ee55" +checksum = "3a7511a0bec4a336b5929999d02b560d2439c993cccf98c26481484e811adc43" dependencies = [ - "blake2-rfc", + "blake2", "crc32fast", "fs2", "hex", "libc", - "log 0.4.14", + "log", "lz4", "memmap2", - "parking_lot 0.11.1", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "snap", ] [[package]] name = "parity-scale-codec" -version = "2.2.0" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8975095a2a03bbbdc70a74ab11a4f76a6d0b84680d87c68d722531b0ac28e8a9" +checksum = "04bc9583b5e01cc8c70d89acc9af14ef9b1c29ee3a0074b2a9eea8c0fa396690" dependencies = [ - "arrayvec 0.7.0", - "bitvec 0.20.2", + "arrayvec 0.7.2", + "bitvec", "byte-slice-cast", + "bytes", "impl-trait-for-tuples", "parity-scale-codec-derive", "serde", @@ -5976,11 +6126,11 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "2.2.0" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40dbbfef7f0a1143c5b06e0d76a6278e25dac0bc1af4be51a0fbb73f07e7ad09" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" dependencies = [ - "proc-macro-crate 1.0.0", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -5992,79 +6142,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" -[[package]] -name = "parity-tokio-ipc" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" -dependencies = [ - "futures 0.3.16", - "libc", - "log 0.4.14", - "rand 0.7.3", - "tokio", - "winapi 0.3.9", -] - -[[package]] -name = "parity-util-mem" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ad6f1acec69b95caf435bbd158d486e5a0a44fcf51531e84922c59ff09e8457" -dependencies = [ - "cfg-if 1.0.0", - "hashbrown 0.11.2", - "impl-trait-for-tuples", - "parity-util-mem-derive", - "parking_lot 0.11.1", - "primitive-types", - "smallvec 1.6.1", - "winapi 0.3.9", -] - -[[package]] -name = "parity-util-mem-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" -dependencies = [ - "proc-macro2", - "syn", - "synstructure", -] - [[package]] name = "parity-wasm" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ad52817c4d343339b3bc2e26861bd21478eda0b7509acf83505727000512ac" -dependencies = [ - "byteorder", -] - -[[package]] -name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" - -[[package]] -name = "parity-ws" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab8a461779bd022964cae2b4989fa9c99deb270bec162da2125ec03c09fcaa" -dependencies = [ - "byteorder", - "bytes 0.4.12", - "httparse", - "log 0.4.14", - "mio 0.6.23", - "mio-extras", - "rand 0.7.3", - "sha-1 0.8.2", - "slab", - "url 2.2.1", -] +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "parking" @@ -6074,121 +6156,74 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "parking_lot" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -dependencies = [ - "lock_api 0.3.4", - "parking_lot_core 0.6.2", - "rustc_version 0.2.3", -] - -[[package]] -name = "parking_lot" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" -dependencies = [ - "lock_api 0.3.4", - "parking_lot_core 0.7.2", -] - -[[package]] -name = "parking_lot" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.2", - "parking_lot_core 0.8.3", + "lock_api", + "parking_lot_core 0.8.5", ] [[package]] -name = "parking_lot_core" -version = "0.6.2" +name = "parking_lot" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall 0.1.57", - "rustc_version 0.2.3", - "smallvec 0.6.14", - "winapi 0.3.9", + "lock_api", + "parking_lot_core 0.9.1", ] [[package]] name = "parking_lot_core" -version = "0.7.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ - "cfg-if 0.1.10", - "cloudabi", + "cfg-if", + "instant", "libc", - "redox_syscall 0.1.57", - "smallvec 1.6.1", - "winapi 0.3.9", + "redox_syscall", + "smallvec", + "winapi", ] [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" dependencies = [ - "cfg-if 1.0.0", - "instant", + "cfg-if", "libc", - "redox_syscall 0.2.5", - "smallvec 1.6.1", - "winapi 0.3.9", -] - -[[package]] -name = "paste" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -dependencies = [ - "paste-impl", - "proc-macro-hack", + "redox_syscall", + "smallvec", + "windows-sys 0.32.0", ] [[package]] name = "paste" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d65c4d95931acda4498f675e332fcbdc9a06705cd07086c510e9b6009cd1c1" - -[[package]] -name = "paste-impl" -version = "0.1.18" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -dependencies = [ - "proc-macro-hack", -] +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "pbkdf2" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" dependencies = [ - "byteorder", - "crypto-mac 0.7.0", + "crypto-mac 0.8.0", ] [[package]] name = "pbkdf2" -version = "0.4.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" dependencies = [ - "crypto-mac 0.8.0", + "crypto-mac 0.11.1", ] [[package]] @@ -6197,12 +6232,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - [[package]] name = "percent-encoding" version = "2.1.0" @@ -6211,18 +6240,19 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" -version = "2.1.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "60b75706b9642ebcb34dab3bc7750f811609a0eb1dd8b88c2d15bf628c1c65b2" dependencies = [ "pest", "pest_generator", @@ -6230,9 +6260,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +checksum = "f4f9272122f5979a6511a749af9db9bfc810393f63119970d7085fed1c4ea0db" dependencies = [ "pest", "pest_meta", @@ -6243,20 +6273,20 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "4c8717927f9b79515e565a64fe46c38b8cd0427e64c40680b14a7365ab09ac8d" dependencies = [ - "maplit", + "once_cell", "pest", - "sha-1 0.8.2", + "sha1", ] [[package]] name = "petgraph" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" dependencies = [ "fixedbitset", "indexmap", @@ -6264,38 +6294,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" -dependencies = [ - "pin-project-internal 0.4.27", -] - -[[package]] -name = "pin-project" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fa8ebb90271c4477f144354485b8068bd8f6b78b428b01ba892ca26caf0b63" -dependencies = [ - "pin-project-internal 1.0.5", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.27" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ - "proc-macro2", - "quote", - "syn", + "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -6310,9 +6320,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -6320,6 +6330,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.19" @@ -6328,15 +6348,15 @@ checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "platforms" -version = "1.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989d43012e2ca1c4a02507c67282691a0a3207f9dc67cec596b43fe925b3d325" +checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" [[package]] name = "plotters" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca0ae5f169d0917a7c7f5a9c1a3d3d9598f18f529dd2b8373ed988efea307a" +checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" dependencies = [ "num-traits", "plotters-backend", @@ -6362,22 +6382,23 @@ dependencies = [ [[package]] name = "polling" -version = "2.0.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" +checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" dependencies = [ - "cfg-if 0.1.10", + "autocfg", + "cfg-if", "libc", - "log 0.4.14", - "wepoll-sys", - "winapi 0.3.9", + "log", + "wepoll-ffi", + "winapi", ] [[package]] name = "poly1305" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fcffab1f78ebbdf4b93b68c1ffebc24037eedf271edaca795732b24e5e4e349" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", "opaque-debug 0.3.0", @@ -6386,11 +6407,11 @@ dependencies = [ [[package]] name = "polyval" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ba6a405ef63530d6cb12802014b22f9c5751bd17cdcddbe9e46d5c8ae83287" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "opaque-debug 0.3.0", "universal-hash", @@ -6404,12 +6425,16 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "predicates" -version = "1.0.7" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb433456c1a57cc93554dea3ce40b4c19c4057e41c55d4a0f3d84ea71c325aa" +checksum = "ed6bd09a7f7e68f3f0bf710fb7ab9c4615a488b58b5f653382a687701e458c92" dependencies = [ - "difference", + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", "predicates-core", + "regex", ] [[package]] @@ -6430,23 +6455,11 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" -dependencies = [ - "ansi_term 0.11.0", - "ctor", - "difference", - "output_vt100", -] - -[[package]] -name = "pretty_assertions" -version = "0.7.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" +checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", "ctor", "diff", "output_vt100", @@ -6454,9 +6467,9 @@ dependencies = [ [[package]] name = "primitive-types" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" +checksum = "5cfd65aea0c5fa0bfcc7c9e7ca828c921ef778f43d325325ec84bda371bfa75a" dependencies = [ "fixed-hash", "impl-codec", @@ -6467,60 +6480,25 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro-crate" -version = "1.0.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", "toml", ] -[[package]] -name = "proc-macro-error" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" -dependencies = [ - "proc-macro-error-attr 0.4.12", - "proc-macro2", - "quote", - "syn", - "version_check 0.9.2", -] - [[package]] name = "proc-macro-error" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "proc-macro-error-attr 1.0.4", - "proc-macro2", - "quote", - "syn", - "version_check 0.9.2", -] - -[[package]] -name = "proc-macro-error-attr" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" -dependencies = [ + "proc-macro-error-attr", "proc-macro2", "quote", "syn", - "syn-mid", - "version_check 0.9.2", + "version_check", ] [[package]] @@ -6531,80 +6509,106 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "version_check 0.9.2", + "version_check", ] [[package]] -name = "proc-macro-hack" -version = "0.5.19" +name = "proc-macro2" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +dependencies = [ + "unicode-ident", +] [[package]] -name = "proc-macro-nested" -version = "0.1.7" +name = "prometheus" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" +checksum = "b7f64969ffd5dd8f39bd57a68ac53c163a095ed9d0fb707146da1b27025a3504" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.11.2", + "thiserror", +] [[package]] -name = "proc-macro2" -version = "1.0.29" +name = "prometheus-client" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +checksum = "3c473049631c233933d6286c88bbb7be30e62ec534cf99a9ae0079211f7fa603" dependencies = [ - "unicode-xid", + "dtoa", + "itoa 1.0.4", + "parking_lot 0.12.1", + "prometheus-client-derive-text-encode", ] [[package]] -name = "prometheus" -version = "0.11.0" +name = "prometheus-client-derive-text-encode" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8425533e7122f0c3cc7a37e6244b16ad3a2cc32ae7ac6276e2a75da0d9c200d" +checksum = "66a455fbcb954c1a7decf3c586e860fd7889cddf4b8e164be736dbac95a953cd" dependencies = [ - "cfg-if 1.0.0", - "fnv", - "lazy_static", - "parking_lot 0.11.1", - "regex", - "thiserror", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "prost" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5e2533f59d08fcf364fd374ebda0692a70bd6d7e66ef97f306f45c6c5d8020" +checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" dependencies = [ - "bytes 1.0.1", + "bytes", "prost-derive", ] [[package]] name = "prost-build" -version = "0.8.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355f634b43cdd80724ee7848f95770e7e70eefa6dcf14fea676216573b8fd603" +checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" dependencies = [ - "bytes 1.0.1", + "bytes", "heck", - "itertools 0.10.0", - "log 0.4.14", + "itertools", + "lazy_static", + "log", "multimap", "petgraph", "prost", "prost-types", + "regex", "tempfile", "which", ] +[[package]] +name = "prost-codec" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011ae9ff8359df7915f97302d591cdd9e0e27fbd5a4ddc5bd13b71079bb20987" +dependencies = [ + "asynchronous-codec", + "bytes", + "prost", + "thiserror", + "unsigned-varint", +] + [[package]] name = "prost-derive" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba" +checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" dependencies = [ "anyhow", - "itertools 0.10.0", + "itertools", "proc-macro2", "quote", "syn", @@ -6612,11 +6616,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.8.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "603bbd6394701d13f3f25aada59c7de9d35a6a5887cfc156181234a44002771b" +checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" dependencies = [ - "bytes 1.0.1", + "bytes", "prost", ] @@ -6629,38 +6633,19 @@ dependencies = [ "cc", ] -[[package]] -name = "pwasm-utils" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880b3384fb00b8f6ecccd5d358b93bd2201900ae3daad213791d1864f6441f5c" -dependencies = [ - "byteorder", - "log 0.4.14", - "parity-wasm 0.42.2", -] - [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" - [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "env_logger 0.8.4", - "log 0.4.14", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -6676,43 +6661,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.9" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] [[package]] name = "radium" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" - -[[package]] -name = "radium" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" - -[[package]] -name = "rand" -version = "0.6.5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.7", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg 0.1.2", - "rand_xorshift", - "winapi 0.3.9", -] +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" @@ -6724,30 +6684,19 @@ dependencies = [ "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc 0.2.0", + "rand_hc", "rand_pcg 0.2.1", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.0", "rand_core 0.6.2", - "rand_hc 0.3.0", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", ] [[package]] @@ -6770,21 +6719,6 @@ dependencies = [ "rand_core 0.6.2", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.5.1" @@ -6805,21 +6739,12 @@ dependencies = [ [[package]] name = "rand_distr" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "051b398806e42b9cd04ad9ec8f81e355d0a382c543ac6672c62f5a5b452ef142" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand 0.8.4", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", + "rand 0.8.5", ] [[package]] @@ -6831,59 +6756,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core 0.6.2", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi 0.3.9", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi 0.3.9", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.4.2", -] - [[package]] name = "rand_pcg" version = "0.2.1" @@ -6894,12 +6766,12 @@ dependencies = [ ] [[package]] -name = "rand_xorshift" -version = "0.1.1" +name = "rand_pcg" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core 0.3.1", + "rand_core 0.6.2", ] [[package]] @@ -6914,7 +6786,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" dependencies = [ - "autocfg 1.0.1", + "autocfg", "crossbeam-deque", "either", "rayon-core", @@ -6928,31 +6800,16 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.3", + "crossbeam-utils", "lazy_static", "num_cpus", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - [[package]] name = "redox_syscall" -version = "0.2.5" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] @@ -6964,7 +6821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom 0.2.3", - "redox_syscall 0.2.5", + "redox_syscall", ] [[package]] @@ -6988,27 +6845,26 @@ dependencies = [ ] [[package]] -name = "regalloc" -version = "0.0.31" +name = "regalloc2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" +checksum = "d43a209257d978ef079f3d446331d0f1794f5e0fc19b306a199983857833a779" dependencies = [ - "log 0.4.14", - "rustc-hash", - "serde", - "smallvec 1.6.1", + "fxhash", + "log", + "slice-group-by", + "smallvec", ] [[package]] name = "regex" -version = "1.4.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", ] [[package]] @@ -7023,40 +6879,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" - -[[package]] -name = "region" -version = "2.2.0" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" -dependencies = [ - "bitflags", - "libc", - "mach", - "winapi 0.3.9", -] - -[[package]] -name = "remote-externalities" -version = "0.10.0-dev" -dependencies = [ - "env_logger 0.9.0", - "jsonrpsee-proc-macros", - "jsonrpsee-ws-client", - "log 0.4.14", - "pallet-elections-phragmen", - "parity-scale-codec", - "serde", - "serde_json", - "sp-core", - "sp-io", - "sp-runtime", - "sp-version", - "tokio", -] +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "remove_dir_all" @@ -7064,7 +6889,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -7074,14 +6899,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" dependencies = [ "hostname", - "quick-error 1.2.3", + "quick-error", ] [[package]] -name = "retain_mut" -version = "0.1.3" +name = "rfc6979" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b" +checksum = "88c86280f057430a52f4861551b092a01b419b8eacefc7c995eacb9dc132fe32" +dependencies = [ + "crypto-bigint", + "hmac 0.12.1", + "zeroize", +] [[package]] name = "ring" @@ -7092,17 +6922,17 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", - "winapi 0.3.9", + "winapi", ] [[package]] name = "rocksdb" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a62eca5cacf2c8261128631bed9f045598d40bfbe4b29f5163f0f802f8f44a7" +checksum = "7e9562ea1d70c0cc63a34a22d977753b50cca91cc6b6527750463bd5dd8697bc" dependencies = [ "libc", "librocksdb-sys", @@ -7110,12 +6940,27 @@ dependencies = [ [[package]] name = "rpassword" -version = "5.0.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" +checksum = "26b763cb66df1c928432cc35053f8bd4cec3335d8559fc16010017d16b3c1680" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", +] + +[[package]] +name = "rtnetlink" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" +dependencies = [ + "async-global-executor", + "futures", + "log", + "netlink-packet-route", + "netlink-proto", + "nix 0.24.2", + "thiserror", ] [[package]] @@ -7147,21 +6992,34 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 0.11.0", + "semver 1.0.4", +] + +[[package]] +name = "rustix" +version = "0.35.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.36.1", ] [[package]] name = "rustls" -version = "0.19.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" dependencies = [ - "base64 0.13.0", - "log 0.4.14", + "log", "ring", "sct", "webpki", @@ -7169,30 +7027,50 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" dependencies = [ "openssl-probe", - "rustls", + "rustls-pemfile", "schannel", "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + [[package]] name = "rustversion" -version = "1.0.4" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "rusty-fork" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", +] [[package]] name = "rw-stream-sink" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" +checksum = "26338f5e09bb721b85b135ea05af7767c90b52f6de4f087d4f4a3a9d64e7dc04" dependencies = [ - "futures 0.3.16", - "pin-project 0.4.27", + "futures", + "pin-project", "static_assertions", ] @@ -7211,21 +7089,6 @@ dependencies = [ "rustc_version 0.2.3", ] -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - -[[package]] -name = "salsa20" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0" -dependencies = [ - "cipher", -] - [[package]] name = "same-file" version = "1.0.6" @@ -7237,9 +7100,9 @@ dependencies = [ [[package]] name = "sc-allocator" -version = "4.0.0-dev" +version = "4.1.0-dev" dependencies = [ - "log 0.4.14", + "log", "sp-core", "sp-wasm-interface", "thiserror", @@ -7250,19 +7113,18 @@ name = "sc-authority-discovery" version = "0.10.0-dev" dependencies = [ "async-trait", - "derive_more", - "futures 0.3.16", - "futures-timer 3.0.2", + "futures", + "futures-timer", "ip_network", "libp2p", - "log 0.4.14", + "log", "parity-scale-codec", "prost", "prost-build", "quickcheck", "rand 0.7.3", "sc-client-api", - "sc-network", + "sc-network-common", "sp-api", "sp-authority-discovery", "sp-blockchain", @@ -7272,17 +7134,18 @@ dependencies = [ "sp-tracing", "substrate-prometheus-endpoint", "substrate-test-runtime-client", + "thiserror", ] [[package]] name = "sc-basic-authorship" version = "0.10.0-dev" dependencies = [ - "futures 0.3.16", - "futures-timer 3.0.2", - "log 0.4.14", + "futures", + "futures-timer", + "log", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-proposer-metrics", @@ -7320,9 +7183,10 @@ name = "sc-chain-spec" version = "4.0.0-dev" dependencies = [ "impl-trait-for-tuples", + "memmap2", "parity-scale-codec", "sc-chain-spec-derive", - "sc-network", + "sc-network-common", "sc-telemetry", "serde", "serde_json", @@ -7334,7 +7198,7 @@ dependencies = [ name = "sc-chain-spec-derive" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 1.0.0", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -7344,20 +7208,24 @@ dependencies = [ name = "sc-cli" version = "0.10.0-dev" dependencies = [ + "array-bytes", "chrono", + "clap 4.0.11", "fdlimit", - "futures 0.3.16", - "hex", + "futures", + "futures-timer", "libp2p", - "log 0.4.14", + "log", "names", "parity-scale-codec", "rand 0.7.3", "regex", "rpassword", "sc-client-api", + "sc-client-db", "sc-keystore", "sc-network", + "sc-network-common", "sc-service", "sc-telemetry", "sc-tracing", @@ -7371,7 +7239,6 @@ dependencies = [ "sp-panic-handler", "sp-runtime", "sp-version", - "structopt", "tempfile", "thiserror", "tiny-bip39", @@ -7383,11 +7250,11 @@ name = "sc-client-api" version = "4.0.0-dev" dependencies = [ "fnv", - "futures 0.3.16", + "futures", "hash-db", - "log 0.4.14", + "log", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "sc-executor", "sc-transaction-pool-api", "sc-utils", @@ -7412,16 +7279,19 @@ dependencies = [ name = "sc-client-db" version = "0.10.0-dev" dependencies = [ + "criterion", "hash-db", + "kitchensink-runtime", "kvdb", "kvdb-memorydb", "kvdb-rocksdb", "linked-hash-map", - "log 0.4.14", + "log", "parity-db", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "quickcheck", + "rand 0.8.5", "sc-client-api", "sc-state-db", "sp-arithmetic", @@ -7441,11 +7311,12 @@ name = "sc-consensus" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.16", - "futures-timer 3.0.2", + "futures", + "futures-timer", "libp2p", - "log 0.4.14", - "parking_lot 0.11.1", + "log", + "mockall", + "parking_lot 0.12.1", "sc-client-api", "sc-utils", "serde", @@ -7465,12 +7336,10 @@ name = "sc-consensus-aura" version = "0.10.0-dev" dependencies = [ "async-trait", - "derive_more", - "futures 0.3.16", - "getrandom 0.2.3", - "log 0.4.14", + "futures", + "log", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -7496,6 +7365,8 @@ dependencies = [ "substrate-prometheus-endpoint", "substrate-test-runtime-client", "tempfile", + "thiserror", + "tokio", ] [[package]] @@ -7503,19 +7374,16 @@ name = "sc-consensus-babe" version = "0.10.0-dev" dependencies = [ "async-trait", - "derive_more", "fork-tree", - "futures 0.3.16", - "log 0.4.14", + "futures", + "log", "merlin", "num-bigint", - "num-rational 0.2.4", + "num-rational", "num-traits", "parity-scale-codec", - "parking_lot 0.11.1", - "rand 0.7.3", + "parking_lot 0.12.1", "rand_chacha 0.2.2", - "retain_mut", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -7538,6 +7406,7 @@ dependencies = [ "sp-core", "sp-inherents", "sp-io", + "sp-keyring", "sp-keystore", "sp-runtime", "sp-timestamp", @@ -7545,18 +7414,16 @@ dependencies = [ "sp-version", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "tempfile", + "thiserror", + "tokio", ] [[package]] name = "sc-consensus-babe-rpc" version = "0.10.0-dev" dependencies = [ - "derive_more", - "futures 0.3.16", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", + "futures", + "jsonrpsee", "sc-consensus", "sc-consensus-babe", "sc-consensus-epochs", @@ -7575,6 +7442,8 @@ dependencies = [ "sp-runtime", "substrate-test-runtime-client", "tempfile", + "thiserror", + "tokio", ] [[package]] @@ -7595,16 +7464,14 @@ version = "0.10.0-dev" dependencies = [ "assert_matches", "async-trait", - "derive_more", - "futures 0.3.16", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "log 0.4.14", + "futures", + "jsonrpsee", + "log", "parity-scale-codec", "sc-basic-authorship", "sc-client-api", "sc-consensus", + "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-epochs", "sc-transaction-pool", @@ -7613,6 +7480,7 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", + "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-slots", "sp-core", @@ -7623,6 +7491,7 @@ dependencies = [ "substrate-prometheus-endpoint", "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", + "thiserror", "tokio", ] @@ -7631,12 +7500,11 @@ name = "sc-consensus-pow" version = "0.10.0-dev" dependencies = [ "async-trait", - "derive_more", - "futures 0.3.16", - "futures-timer 3.0.2", - "log 0.4.14", + "futures", + "futures-timer", + "log", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "sc-client-api", "sc-consensus", "sp-api", @@ -7648,6 +7516,7 @@ dependencies = [ "sp-inherents", "sp-runtime", "substrate-prometheus-endpoint", + "thiserror", ] [[package]] @@ -7655,14 +7524,13 @@ name = "sc-consensus-slots" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.16", - "futures-timer 3.0.2", - "log 0.4.14", + "futures", + "futures-timer", + "log", "parity-scale-codec", "sc-client-api", "sc-consensus", "sc-telemetry", - "sp-api", "sp-arithmetic", "sp-blockchain", "sp-consensus", @@ -7671,7 +7539,6 @@ dependencies = [ "sp-inherents", "sp-runtime", "sp-state-machine", - "sp-timestamp", "substrate-test-runtime-client", "thiserror", ] @@ -7690,13 +7557,14 @@ dependencies = [ name = "sc-executor" version = "0.10.0-dev" dependencies = [ - "hex-literal", - "lazy_static", - "libsecp256k1 0.6.0", - "log 0.4.14", + "array-bytes", + "criterion", + "env_logger", + "lru", + "num_cpus", "parity-scale-codec", - "parking_lot 0.11.1", - "paste 1.0.4", + "parking_lot 0.12.1", + "paste", "regex", "sc-executor-common", "sc-executor-wasmi", @@ -7712,14 +7580,14 @@ dependencies = [ "sp-runtime", "sp-runtime-interface", "sp-state-machine", - "sp-tasks", "sp-trie", "sp-version", "sp-wasm-interface", "substrate-test-runtime", + "tempfile", "tracing", "tracing-subscriber", - "wasmi", + "wasmi 0.13.0", "wat", ] @@ -7727,53 +7595,44 @@ dependencies = [ name = "sc-executor-common" version = "0.10.0-dev" dependencies = [ - "derive_more", - "environmental", - "parity-scale-codec", - "pwasm-utils", "sc-allocator", - "sp-core", "sp-maybe-compressed-blob", - "sp-serializer", "sp-wasm-interface", "thiserror", - "wasmer", - "wasmer-compiler-singlepass", - "wasmi", + "wasm-instrument 0.3.0", + "wasmi 0.13.0", ] [[package]] name = "sc-executor-wasmi" version = "0.10.0-dev" dependencies = [ - "log 0.4.14", - "parity-scale-codec", + "log", "sc-allocator", "sc-executor-common", - "scoped-tls", - "sp-core", "sp-runtime-interface", "sp-wasm-interface", - "wasmi", + "wasmi 0.13.0", ] [[package]] name = "sc-executor-wasmtime" version = "0.10.0-dev" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", - "log 0.4.14", + "log", + "once_cell", "parity-scale-codec", - "parity-wasm 0.42.2", + "paste", + "rustix", "sc-allocator", "sc-executor-common", "sc-runtime-test", - "scoped-tls", - "sp-core", "sp-io", "sp-runtime-interface", "sp-wasm-interface", + "tempfile", "wasmtime", "wat", ] @@ -7782,27 +7641,31 @@ dependencies = [ name = "sc-finality-grandpa" version = "0.10.0-dev" dependencies = [ + "ahash", + "array-bytes", "assert_matches", "async-trait", - "derive_more", "dyn-clone", "finality-grandpa", "fork-tree", - "futures 0.3.16", - "futures-timer 3.0.2", - "log 0.4.14", + "futures", + "futures-timer", + "log", "parity-scale-codec", - "parking_lot 0.11.1", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "sc-block-builder", + "sc-chain-spec", "sc-client-api", "sc-consensus", "sc-keystore", "sc-network", + "sc-network-common", "sc-network-gossip", "sc-network-test", "sc-telemetry", "sc-utils", + "serde", "serde_json", "sp-api", "sp-application-crypto", @@ -7817,7 +7680,7 @@ dependencies = [ "sp-tracing", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "tempfile", + "thiserror", "tokio", ] @@ -7825,14 +7688,10 @@ dependencies = [ name = "sc-finality-grandpa-rpc" version = "0.10.0-dev" dependencies = [ - "derive_more", "finality-grandpa", - "futures 0.3.16", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "jsonrpc-pubsub", - "log 0.4.14", + "futures", + "jsonrpsee", + "log", "parity-scale-codec", "sc-block-builder", "sc-client-api", @@ -7846,19 +7705,20 @@ dependencies = [ "sp-keyring", "sp-runtime", "substrate-test-runtime-client", + "thiserror", + "tokio", ] [[package]] name = "sc-informant" version = "0.10.0-dev" dependencies = [ - "ansi_term 0.12.1", - "futures 0.3.16", - "futures-timer 3.0.2", - "log 0.4.14", - "parity-util-mem", + "ansi_term", + "futures", + "futures-timer", + "log", "sc-client-api", - "sc-network", + "sc-network-common", "sc-transaction-pool-api", "sp-blockchain", "sp-runtime", @@ -7868,78 +7728,59 @@ dependencies = [ name = "sc-keystore" version = "4.0.0-dev" dependencies = [ + "array-bytes", "async-trait", - "derive_more", - "hex", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "serde_json", "sp-application-crypto", "sp-core", "sp-keystore", "tempfile", -] - -[[package]] -name = "sc-light" -version = "4.0.0-dev" -dependencies = [ - "hash-db", - "parity-scale-codec", - "parking_lot 0.11.1", - "sc-client-api", - "sc-executor", - "sp-api", - "sp-blockchain", - "sp-core", - "sp-externalities", - "sp-runtime", - "sp-state-machine", + "thiserror", ] [[package]] name = "sc-network" version = "0.10.0-dev" dependencies = [ + "array-bytes", "assert_matches", - "async-std", "async-trait", - "asynchronous-codec 0.5.0", + "asynchronous-codec", "bitflags", - "bytes 1.0.1", + "bytes", "cid", - "derive_more", "either", "fnv", "fork-tree", - "futures 0.3.16", - "futures-timer 3.0.2", - "hex", + "futures", + "futures-timer", "ip_network", "libp2p", "linked-hash-map", "linked_hash_set", - "log 0.4.14", + "log", "lru", "parity-scale-codec", - "parking_lot 0.11.1", - "pin-project 1.0.5", + "parking_lot 0.12.1", + "pin-project", "prost", - "prost-build", - "quickcheck", "rand 0.7.3", "sc-block-builder", "sc-client-api", "sc-consensus", + "sc-network-common", + "sc-network-light", + "sc-network-sync", "sc-peerset", "sc-utils", "serde", "serde_json", - "smallvec 1.6.1", + "smallvec", "sp-arithmetic", "sp-blockchain", "sp-consensus", "sp-core", - "sp-finality-grandpa", "sp-runtime", "sp-test-primitives", "sp-tracing", @@ -7948,45 +7789,158 @@ dependencies = [ "substrate-test-runtime-client", "tempfile", "thiserror", - "unsigned-varint 0.6.0", - "void", + "tokio", + "tokio-util", + "unsigned-varint", "zeroize", ] +[[package]] +name = "sc-network-bitswap" +version = "0.10.0-dev" +dependencies = [ + "cid", + "futures", + "libp2p", + "log", + "prost", + "prost-build", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-network-common", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-runtime", + "substrate-test-runtime", + "substrate-test-runtime-client", + "thiserror", + "tokio", + "unsigned-varint", + "void", +] + +[[package]] +name = "sc-network-common" +version = "0.10.0-dev" +dependencies = [ + "async-trait", + "bitflags", + "bytes", + "futures", + "futures-timer", + "libp2p", + "linked_hash_set", + "parity-scale-codec", + "prost-build", + "sc-consensus", + "sc-peerset", + "serde", + "smallvec", + "sp-blockchain", + "sp-consensus", + "sp-finality-grandpa", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", +] + [[package]] name = "sc-network-gossip" version = "0.10.0-dev" dependencies = [ - "async-std", - "futures 0.3.16", - "futures-timer 3.0.2", + "ahash", + "futures", + "futures-timer", "libp2p", - "log 0.4.14", + "log", "lru", "quickcheck", - "sc-network", + "sc-network-common", + "sc-peerset", "sp-runtime", "substrate-prometheus-endpoint", "substrate-test-runtime-client", + "tokio", "tracing", ] +[[package]] +name = "sc-network-light" +version = "0.10.0-dev" +dependencies = [ + "array-bytes", + "futures", + "libp2p", + "log", + "parity-scale-codec", + "prost", + "prost-build", + "sc-client-api", + "sc-network-common", + "sc-peerset", + "sp-blockchain", + "sp-core", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-network-sync" +version = "0.10.0-dev" +dependencies = [ + "array-bytes", + "async-trait", + "fork-tree", + "futures", + "libp2p", + "log", + "lru", + "mockall", + "parity-scale-codec", + "prost", + "prost-build", + "quickcheck", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-network-common", + "sc-peerset", + "sc-utils", + "smallvec", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", + "sp-test-primitives", + "sp-tracing", + "substrate-prometheus-endpoint", + "substrate-test-runtime-client", + "thiserror", + "tokio", +] + [[package]] name = "sc-network-test" version = "0.8.0" dependencies = [ - "async-std", "async-trait", - "futures 0.3.16", - "futures-timer 3.0.2", + "futures", + "futures-timer", "libp2p", - "log 0.4.14", - "parking_lot 0.11.1", + "log", + "parking_lot 0.12.1", "rand 0.7.3", "sc-block-builder", "sc-client-api", "sc-consensus", "sc-network", + "sc-network-common", + "sc-network-light", + "sc-network-sync", "sc-service", "sp-blockchain", "sp-consensus", @@ -7996,29 +7950,50 @@ dependencies = [ "sp-tracing", "substrate-test-runtime", "substrate-test-runtime-client", + "tokio", +] + +[[package]] +name = "sc-network-transactions" +version = "0.10.0-dev" +dependencies = [ + "array-bytes", + "futures", + "hex", + "libp2p", + "log", + "parity-scale-codec", + "pin-project", + "sc-network-common", + "sc-peerset", + "sp-consensus", + "sp-runtime", + "substrate-prometheus-endpoint", ] [[package]] name = "sc-offchain" version = "4.0.0-dev" dependencies = [ - "bytes 1.0.1", + "array-bytes", + "bytes", "fnv", - "futures 0.3.16", - "futures-timer 3.0.2", - "hex", - "hyper 0.14.11", + "futures", + "futures-timer", + "hyper", "hyper-rustls", "lazy_static", - "log 0.4.14", + "libp2p", "num_cpus", + "once_cell", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "rand 0.7.3", "sc-block-builder", "sc-client-api", "sc-client-db", - "sc-network", + "sc-network-common", + "sc-peerset", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", @@ -8031,15 +8006,16 @@ dependencies = [ "substrate-test-runtime-client", "threadpool", "tokio", + "tracing", ] [[package]] name = "sc-peerset" version = "4.0.0-dev" dependencies = [ - "futures 0.3.16", + "futures", "libp2p", - "log 0.4.14", + "log", "rand 0.7.3", "sc-utils", "serde_json", @@ -8048,9 +8024,9 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" -version = "0.9.0" +version = "0.10.0-dev" dependencies = [ - "log 0.4.14", + "log", "substrate-prometheus-endpoint", ] @@ -8059,18 +8035,19 @@ name = "sc-rpc" version = "4.0.0-dev" dependencies = [ "assert_matches", - "futures 0.3.16", + "env_logger", + "futures", "hash-db", - "jsonrpc-core", - "jsonrpc-pubsub", + "jsonrpsee", "lazy_static", - "log 0.4.14", + "log", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "sc-block-builder", "sc-chain-spec", "sc-client-api", "sc-network", + "sc-network-common", "sc-rpc-api", "sc-tracing", "sc-transaction-pool", @@ -8089,45 +8066,63 @@ dependencies = [ "sp-session", "sp-version", "substrate-test-runtime-client", + "tokio", ] [[package]] name = "sc-rpc-api" version = "0.10.0-dev" dependencies = [ - "futures 0.3.16", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "jsonrpc-pubsub", - "log 0.4.14", + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-chain-spec", + "sc-transaction-pool-api", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-tracing", + "sp-version", + "thiserror", +] + +[[package]] +name = "sc-rpc-server" +version = "4.0.0-dev" +dependencies = [ + "futures", + "http", + "jsonrpsee", + "log", + "serde_json", + "substrate-prometheus-endpoint", + "tokio", + "tower", + "tower-http", +] + +[[package]] +name = "sc-rpc-spec-v2" +version = "0.10.0-dev" +dependencies = [ + "futures", + "hex", + "jsonrpsee", "parity-scale-codec", - "parking_lot 0.11.1", "sc-chain-spec", "sc-transaction-pool-api", "serde", "serde_json", + "sp-api", + "sp-blockchain", "sp-core", - "sp-rpc", "sp-runtime", - "sp-tracing", - "sp-version", "thiserror", -] - -[[package]] -name = "sc-rpc-server" -version = "4.0.0-dev" -dependencies = [ - "futures 0.3.16", - "jsonrpc-core", - "jsonrpc-http-server", - "jsonrpc-ipc-server", - "jsonrpc-pubsub", - "jsonrpc-ws-server", - "log 0.4.14", - "serde_json", - "substrate-prometheus-endpoint", "tokio", ] @@ -8138,9 +8133,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-sandbox", "sp-std", - "sp-tasks", "substrate-wasm-builder", ] @@ -8148,20 +8141,17 @@ dependencies = [ name = "sc-service" version = "0.10.0-dev" dependencies = [ - "async-std", "async-trait", "directories", "exit-future", - "futures 0.3.16", - "futures-timer 3.0.2", + "futures", + "futures-timer", "hash-db", - "jsonrpc-core", - "jsonrpc-pubsub", - "log 0.4.14", + "jsonrpsee", + "log", "parity-scale-codec", - "parity-util-mem", - "parking_lot 0.11.1", - "pin-project 1.0.5", + "parking_lot 0.12.1", + "pin-project", "rand 0.7.3", "sc-block-builder", "sc-chain-spec", @@ -8171,11 +8161,17 @@ dependencies = [ "sc-executor", "sc-informant", "sc-keystore", - "sc-light", "sc-network", + "sc-network-bitswap", + "sc-network-common", + "sc-network-light", + "sc-network-sync", + "sc-network-transactions", "sc-offchain", "sc-rpc", "sc-rpc-server", + "sc-rpc-spec-v2", + "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", @@ -8201,6 +8197,7 @@ dependencies = [ "sp-transaction-storage-proof", "sp-trie", "sp-version", + "static_init", "substrate-prometheus-endpoint", "substrate-test-runtime", "substrate-test-runtime-client", @@ -8215,19 +8212,19 @@ dependencies = [ name = "sc-service-test" version = "2.0.0" dependencies = [ + "array-bytes", "fdlimit", - "futures 0.3.16", - "hex-literal", - "log 0.4.14", + "futures", + "log", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-client-db", "sc-consensus", "sc-executor", - "sc-light", "sc-network", + "sc-network-common", "sc-service", "sc-transaction-pool-api", "sp-api", @@ -8235,6 +8232,7 @@ dependencies = [ "sp-consensus", "sp-core", "sp-externalities", + "sp-io", "sp-panic-handler", "sp-runtime", "sp-state-machine", @@ -8251,11 +8249,9 @@ dependencies = [ name = "sc-state-db" version = "0.10.0-dev" dependencies = [ - "log 0.4.14", + "log", "parity-scale-codec", - "parity-util-mem", - "parity-util-mem-derive", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "sc-client-api", "sp-core", ] @@ -8264,16 +8260,13 @@ dependencies = [ name = "sc-sync-state-rpc" version = "0.10.0-dev" dependencies = [ - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", + "jsonrpsee", "parity-scale-codec", "sc-chain-spec", "sc-client-api", "sc-consensus-babe", "sc-consensus-epochs", "sc-finality-grandpa", - "sc-rpc-api", "serde", "serde_json", "sp-blockchain", @@ -8281,16 +8274,35 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sc-sysinfo" +version = "6.0.0-dev" +dependencies = [ + "futures", + "libc", + "log", + "rand 0.7.3", + "rand_pcg 0.2.1", + "regex", + "sc-telemetry", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "sc-telemetry" version = "4.0.0-dev" dependencies = [ "chrono", - "futures 0.3.16", + "futures", "libp2p", - "log 0.4.14", - "parking_lot 0.11.1", - "pin-project 1.0.5", + "log", + "parking_lot 0.12.1", + "pin-project", "rand 0.7.3", "serde", "serde_json", @@ -8302,12 +8314,15 @@ dependencies = [ name = "sc-tracing" version = "4.0.0-dev" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", "atty", + "chrono", + "criterion", "lazy_static", - "log 0.4.14", + "libc", + "log", "once_cell", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "regex", "rustc-hash", "sc-client-api", @@ -8330,7 +8345,7 @@ dependencies = [ name = "sc-tracing-proc-macro" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 1.0.0", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -8340,17 +8355,16 @@ dependencies = [ name = "sc-transaction-pool" version = "4.0.0-dev" dependencies = [ + "array-bytes", "assert_matches", + "async-trait", "criterion", - "futures 0.3.16", - "hex", - "intervalier", + "futures", + "futures-timer", "linked-hash-map", - "log 0.4.14", + "log", "parity-scale-codec", - "parity-util-mem", - "parking_lot 0.11.1", - "retain_mut", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-transaction-pool-api", @@ -8374,10 +8388,11 @@ dependencies = [ name = "sc-transaction-pool-api" version = "4.0.0-dev" dependencies = [ - "derive_more", - "futures 0.3.16", - "log 0.4.14", + "async-trait", + "futures", + "log", "serde", + "serde_json", "sp-blockchain", "sp-runtime", "thiserror", @@ -8387,20 +8402,23 @@ dependencies = [ name = "sc-utils" version = "4.0.0-dev" dependencies = [ - "futures 0.3.16", - "futures-timer 3.0.2", + "futures", + "futures-timer", "lazy_static", + "log", + "parking_lot 0.12.1", "prometheus", + "tokio-test", ] [[package]] name = "scale-info" -version = "1.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55b744399c25532d63a0d2789b109df8d46fc93752d46b0782991a931a782f" +checksum = "333af15b02563b8182cd863f925bd31ef8fa86a0e095d30c091956057d436153" dependencies = [ - "bitvec 0.20.2", - "cfg-if 1.0.0", + "bitvec", + "cfg-if", "derive_more", "parity-scale-codec", "scale-info-derive", @@ -8409,11 +8427,11 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "1.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baeb2780690380592f86205aa4ee49815feb2acad8c2f59e6dd207148c3f1fcd" +checksum = "53f56acbd0743d29ffa08f911ab5397def774ad01bab3786804cf6ee057fb5e1" dependencies = [ - "proc-macro-crate 1.0.0", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -8426,7 +8444,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -8443,37 +8461,69 @@ dependencies = [ "rand 0.7.3", "rand_core 0.5.1", "sha2 0.8.2", - "subtle 2.4.0", + "subtle", "zeroize", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + [[package]] name = "sct" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", ] +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array 0.14.4", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7649a0b3ffb32636e60c7ce0d70511eda9c52c658cd0634e194d5a19943aeff" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7058dc8eaf3f2810d7828680320acda0b25a288f6d288e19278e249bbf74226b" +dependencies = [ + "cc", +] + [[package]] name = "secrecy" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0673d6a6449f5e7d12a1caf424fd9363e2af3a4953023ed455e3c4beef4597c0" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" dependencies = [ "zeroize", ] @@ -8507,7 +8557,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" dependencies = [ - "semver-parser 0.7.0", + "semver-parser", ] [[package]] @@ -8516,16 +8566,15 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser 0.7.0", + "semver-parser", ] [[package]] name = "semver" -version = "0.11.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" dependencies = [ - "semver-parser 0.10.2", "serde", ] @@ -8535,33 +8584,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - [[package]] name = "serde" -version = "1.0.126" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" -dependencies = [ - "serde", -] - [[package]] name = "serde_cbor" version = "0.11.1" @@ -8574,9 +8605,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.126" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -8585,25 +8616,22 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.68" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ - "itoa", + "itoa 1.0.4", "ryu", "serde", ] [[package]] -name = "sha-1" -version = "0.8.2" +name = "serde_nanos" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "e44969a61f5d316be20a42ff97816efb3b407a924d06824c3d8a49fa8450de0e" dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", + "serde", ] [[package]] @@ -8613,7 +8641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpuid-bool", "digest 0.9.0", "opaque-debug 0.3.0", @@ -8621,9 +8649,14 @@ dependencies = [ [[package]] name = "sha1" -version = "0.6.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] [[package]] name = "sha2" @@ -8639,27 +8672,36 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.3" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", - "cpuid-bool", + "cfg-if", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sha3" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", + "digest 0.10.3", "keccak", - "opaque-debug 0.3.0", ] [[package]] @@ -8677,16 +8719,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" -[[package]] -name = "signal-hook" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" version = "1.3.0" @@ -8698,9 +8730,13 @@ dependencies = [ [[package]] name = "signature" -version = "1.3.0" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0242b8e50dd9accdd56170e94ca1ebd223b098eb9c83539a6e367d0f36ae68" +checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" +dependencies = [ + "digest 0.10.3", + "rand_core 0.6.2", +] [[package]] name = "simba" @@ -8711,7 +8747,7 @@ dependencies = [ "approx", "num-complex", "num-traits", - "paste 1.0.4", + "paste", ] [[package]] @@ -8721,28 +8757,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] -name = "slog" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" -dependencies = [ - "erased-serde", -] - -[[package]] -name = "smallvec" -version = "0.6.14" +name = "slice-group-by" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] +checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" [[package]] name = "smallvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "snap" @@ -8752,72 +8776,46 @@ checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" [[package]] name = "snow" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6142f7c25e94f6fd25a32c3348ec230df9109b463f59c8c7acc4bd34936babb7" +checksum = "774d05a3edae07ce6d68ea6984f3c05e9bba8927e3dd591e3b479e5b03213d0d" dependencies = [ "aes-gcm", "blake2", "chacha20poly1305", - "rand 0.8.4", + "curve25519-dalek 4.0.0-pre.1", "rand_core 0.6.2", "ring", - "rustc_version 0.3.3", - "sha2 0.9.3", - "subtle 2.4.0", - "x25519-dalek", -] - -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi 0.3.9", + "rustc_version 0.4.0", + "sha2 0.10.2", + "subtle", ] [[package]] name = "socket2" -version = "0.4.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] name = "soketto" -version = "0.4.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c71ed3d54db0a699f4948e1bb3e45b450fa31fe602621dee6680361d569c88" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" dependencies = [ - "base64 0.12.3", - "bytes 0.5.6", + "base64", + "bytes", "flate2", - "futures 0.3.16", - "httparse", - "log 0.4.14", - "rand 0.7.3", - "sha-1 0.9.4", -] - -[[package]] -name = "soketto" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74e48087dbeed4833785c2f3352b59140095dc192dce966a3bfc155020a439f" -dependencies = [ - "base64 0.13.0", - "bytes 1.0.1", - "futures 0.3.16", + "futures", + "http", "httparse", - "log 0.4.14", - "rand 0.8.4", - "sha-1 0.9.4", + "log", + "rand 0.8.5", + "sha-1", ] [[package]] @@ -8825,7 +8823,7 @@ name = "sp-api" version = "4.0.0-dev" dependencies = [ "hash-db", - "log 0.4.14", + "log", "parity-scale-codec", "sp-api-proc-macro", "sp-core", @@ -8833,6 +8831,7 @@ dependencies = [ "sp-state-machine", "sp-std", "sp-test-primitives", + "sp-trie", "sp-version", "thiserror", ] @@ -8841,8 +8840,8 @@ dependencies = [ name = "sp-api-proc-macro" version = "4.0.0-dev" dependencies = [ - "blake2-rfc", - "proc-macro-crate 1.0.0", + "blake2", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -8853,8 +8852,8 @@ name = "sp-api-test" version = "2.0.1" dependencies = [ "criterion", - "futures 0.3.16", - "log 0.4.14", + "futures", + "log", "parity-scale-codec", "rustversion", "sc-block-builder", @@ -8871,7 +8870,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" -version = "4.0.0-dev" +version = "7.0.0" dependencies = [ "parity-scale-codec", "scale-info", @@ -8895,7 +8894,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" -version = "4.0.0-dev" +version = "6.0.0" dependencies = [ "criterion", "integer-sqrt", @@ -8905,6 +8904,7 @@ dependencies = [ "rand 0.7.3", "scale-info", "serde", + "sp-core", "sp-debug-derive", "sp-std", "static_assertions", @@ -8943,6 +8943,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "sp-beefy" +version = "4.0.0-dev" +dependencies = [ + "array-bytes", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-keystore", + "sp-mmr-primitives", + "sp-runtime", + "sp-std", +] + [[package]] name = "sp-block-builder" version = "4.0.0-dev" @@ -8958,11 +8976,11 @@ dependencies = [ name = "sp-blockchain" version = "4.0.0-dev" dependencies = [ - "futures 0.3.16", - "log 0.4.14", + "futures", + "log", "lru", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "sp-api", "sp-consensus", "sp-database", @@ -8976,9 +8994,9 @@ name = "sp-consensus" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.16", - "futures-timer 3.0.2", - "log 0.4.14", + "futures", + "futures-timer", + "log", "parity-scale-codec", "sp-core", "sp-inherents", @@ -9046,8 +9064,11 @@ version = "0.10.0-dev" dependencies = [ "parity-scale-codec", "scale-info", + "serde", "sp-arithmetic", "sp-runtime", + "sp-std", + "sp-timestamp", ] [[package]] @@ -9055,6 +9076,7 @@ name = "sp-consensus-vrf" version = "0.10.0-dev" dependencies = [ "parity-scale-codec", + "scale-info", "schnorrkel", "sp-core", "sp-runtime", @@ -9063,63 +9085,86 @@ dependencies = [ [[package]] name = "sp-core" -version = "4.0.0-dev" +version = "7.0.0" dependencies = [ + "array-bytes", "base58", - "blake2-rfc", + "bitflags", + "blake2", "byteorder", "criterion", "dyn-clonable", - "ed25519-dalek", - "futures 0.3.16", + "ed25519-zebra", + "futures", "hash-db", "hash256-std-hasher", - "hex", - "hex-literal", "impl-serde", "lazy_static", - "libsecp256k1 0.6.0", - "log 0.4.14", + "libsecp256k1", + "log", "merlin", "num-traits", "parity-scale-codec", - "parity-util-mem", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "primitive-types", "rand 0.7.3", "regex", "scale-info", "schnorrkel", + "secp256k1", "secrecy", "serde", "serde_json", - "sha2 0.9.3", + "sp-core-hashing", + "sp-core-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-runtime-interface", "sp-serializer", "sp-std", "sp-storage", + "ss58-registry", "substrate-bip39", "thiserror", "tiny-bip39", - "tiny-keccak", - "twox-hash", - "wasmi", + "wasmi 0.13.0", "zeroize", ] +[[package]] +name = "sp-core-hashing" +version = "5.0.0" +dependencies = [ + "blake2", + "byteorder", + "digest 0.10.3", + "sha2 0.10.2", + "sha3", + "sp-std", + "twox-hash", +] + +[[package]] +name = "sp-core-hashing-proc-macro" +version = "5.0.0" +dependencies = [ + "proc-macro2", + "quote", + "sp-core-hashing", + "syn", +] + [[package]] name = "sp-database" version = "4.0.0-dev" dependencies = [ "kvdb", - "parking_lot 0.11.1", + "parking_lot 0.12.1", ] [[package]] name = "sp-debug-derive" -version = "3.0.0" +version = "5.0.0" dependencies = [ "proc-macro2", "quote", @@ -9128,7 +9173,7 @@ dependencies = [ [[package]] name = "sp-externalities" -version = "0.10.0-dev" +version = "0.13.0" dependencies = [ "environmental", "parity-scale-codec", @@ -9141,7 +9186,7 @@ name = "sp-finality-grandpa" version = "4.0.0-dev" dependencies = [ "finality-grandpa", - "log 0.4.14", + "log", "parity-scale-codec", "scale-info", "serde", @@ -9158,7 +9203,7 @@ name = "sp-inherents" version = "4.0.0-dev" dependencies = [ "async-trait", - "futures 0.3.16", + "futures", "impl-trait-for-tuples", "parity-scale-codec", "sp-core", @@ -9169,18 +9214,20 @@ dependencies = [ [[package]] name = "sp-io" -version = "4.0.0-dev" +version = "7.0.0" dependencies = [ - "futures 0.3.16", + "bytes", + "ed25519-dalek", + "futures", "hash-db", - "libsecp256k1 0.6.0", - "log 0.4.14", + "libsecp256k1", + "log", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", + "secp256k1", "sp-core", "sp-externalities", "sp-keystore", - "sp-maybe-compressed-blob", "sp-runtime-interface", "sp-state-machine", "sp-std", @@ -9193,81 +9240,84 @@ dependencies = [ [[package]] name = "sp-keyring" -version = "4.0.0-dev" +version = "7.0.0" dependencies = [ "lazy_static", "sp-core", "sp-runtime", - "strum 0.20.0", + "strum", ] [[package]] name = "sp-keystore" -version = "0.10.0-dev" +version = "0.13.0" dependencies = [ "async-trait", - "derive_more", - "futures 0.3.16", + "futures", "merlin", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "rand 0.7.3", "rand_chacha 0.2.2", "schnorrkel", "serde", "sp-core", "sp-externalities", + "thiserror", ] [[package]] name = "sp-maybe-compressed-blob" -version = "4.0.0-dev" +version = "4.1.0-dev" dependencies = [ + "thiserror", "zstd", ] [[package]] -name = "sp-npos-elections" +name = "sp-mmr-primitives" version = "4.0.0-dev" dependencies = [ + "array-bytes", + "ckb-merkle-mountain-range", + "log", "parity-scale-codec", - "rand 0.7.3", "scale-info", "serde", - "sp-arithmetic", + "sp-api", "sp-core", - "sp-npos-elections-solution-type", + "sp-debug-derive", "sp-runtime", "sp-std", - "substrate-test-utils", + "thiserror", ] [[package]] -name = "sp-npos-elections-fuzzer" -version = "2.0.0-alpha.5" +name = "sp-npos-elections" +version = "4.0.0-dev" dependencies = [ - "honggfuzz", "parity-scale-codec", "rand 0.7.3", "scale-info", - "sp-npos-elections", + "serde", + "sp-arithmetic", + "sp-core", "sp-runtime", - "structopt", + "sp-std", + "substrate-test-utils", ] [[package]] -name = "sp-npos-elections-solution-type" -version = "4.0.0-dev" +name = "sp-npos-elections-fuzzer" +version = "2.0.0-alpha.5" dependencies = [ + "clap 4.0.11", + "honggfuzz", "parity-scale-codec", - "proc-macro-crate 1.0.0", - "proc-macro2", - "quote", + "rand 0.8.5", "scale-info", - "sp-arithmetic", "sp-npos-elections", - "syn", - "trybuild", + "sp-runtime", ] [[package]] @@ -9281,14 +9331,16 @@ dependencies = [ [[package]] name = "sp-panic-handler" -version = "3.0.0" +version = "5.0.0" dependencies = [ "backtrace", + "lazy_static", + "regex", ] [[package]] name = "sp-rpc" -version = "4.0.0-dev" +version = "6.0.0" dependencies = [ "rustc-hash", "serde", @@ -9298,15 +9350,14 @@ dependencies = [ [[package]] name = "sp-runtime" -version = "4.0.0-dev" +version = "7.0.0" dependencies = [ "either", "hash256-std-hasher", "impl-trait-for-tuples", - "log 0.4.14", + "log", "parity-scale-codec", - "parity-util-mem", - "paste 1.0.4", + "paste", "rand 0.7.3", "scale-info", "serde", @@ -9319,13 +9370,16 @@ dependencies = [ "sp-state-machine", "sp-std", "sp-tracing", + "sp-weights", "substrate-test-runtime-client", + "zstd", ] [[package]] name = "sp-runtime-interface" -version = "4.0.0-dev" +version = "7.0.0" dependencies = [ + "bytes", "impl-trait-for-tuples", "parity-scale-codec", "primitive-types", @@ -9346,10 +9400,10 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" -version = "4.0.0-dev" +version = "6.0.0" dependencies = [ "Inflector", - "proc-macro-crate 1.0.0", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -9375,6 +9429,7 @@ dependencies = [ name = "sp-runtime-interface-test-wasm" version = "2.0.0" dependencies = [ + "bytes", "sp-core", "sp-io", "sp-runtime-interface", @@ -9393,24 +9448,9 @@ dependencies = [ "substrate-wasm-builder", ] -[[package]] -name = "sp-sandbox" -version = "0.10.0-dev" -dependencies = [ - "assert_matches", - "log 0.4.14", - "parity-scale-codec", - "sp-core", - "sp-io", - "sp-std", - "sp-wasm-interface", - "wasmi", - "wat", -] - [[package]] name = "sp-serializer" -version = "3.0.0" +version = "4.0.0-dev" dependencies = [ "serde", "serde_json", @@ -9441,17 +9481,18 @@ dependencies = [ [[package]] name = "sp-state-machine" -version = "0.10.0-dev" +version = "0.13.0" dependencies = [ + "array-bytes", + "assert_matches", "hash-db", - "hex-literal", - "log 0.4.14", + "log", "num-traits", "parity-scale-codec", - "parking_lot 0.11.1", - "pretty_assertions 0.6.1", + "parking_lot 0.12.1", + "pretty_assertions", "rand 0.7.3", - "smallvec 1.6.1", + "smallvec", "sp-core", "sp-externalities", "sp-panic-handler", @@ -9466,11 +9507,11 @@ dependencies = [ [[package]] name = "sp-std" -version = "4.0.0-dev" +version = "5.0.0" [[package]] name = "sp-storage" -version = "4.0.0-dev" +version = "7.0.0" dependencies = [ "impl-serde", "parity-scale-codec", @@ -9480,25 +9521,11 @@ dependencies = [ "sp-std", ] -[[package]] -name = "sp-tasks" -version = "4.0.0-dev" -dependencies = [ - "log 0.4.14", - "parity-scale-codec", - "sp-core", - "sp-externalities", - "sp-io", - "sp-runtime-interface", - "sp-std", -] - [[package]] name = "sp-test-primitives" version = "2.0.0" dependencies = [ "parity-scale-codec", - "parity-util-mem", "serde", "sp-application-crypto", "sp-core", @@ -9510,8 +9537,8 @@ name = "sp-timestamp" version = "4.0.0-dev" dependencies = [ "async-trait", - "futures-timer 3.0.2", - "log 0.4.14", + "futures-timer", + "log", "parity-scale-codec", "sp-api", "sp-inherents", @@ -9522,15 +9549,9 @@ dependencies = [ [[package]] name = "sp-tracing" -version = "4.0.0-dev" +version = "6.0.0" dependencies = [ - "erased-serde", - "log 0.4.14", "parity-scale-codec", - "parking_lot 0.10.2", - "serde", - "serde_json", - "slog", "sp-std", "tracing", "tracing-core", @@ -9550,7 +9571,7 @@ name = "sp-transaction-storage-proof" version = "4.0.0-dev" dependencies = [ "async-trait", - "log 0.4.14", + "log", "parity-scale-codec", "scale-info", "sp-core", @@ -9562,17 +9583,25 @@ dependencies = [ [[package]] name = "sp-trie" -version = "4.0.0-dev" +version = "7.0.0" dependencies = [ + "ahash", + "array-bytes", "criterion", "hash-db", - "hex-literal", + "hashbrown 0.12.3", + "lazy_static", + "lru", "memory-db", + "nohash-hasher", "parity-scale-codec", + "parking_lot 0.12.1", "scale-info", "sp-core", "sp-runtime", "sp-std", + "thiserror", + "tracing", "trie-bench", "trie-db", "trie-root", @@ -9581,13 +9610,14 @@ dependencies = [ [[package]] name = "sp-version" -version = "4.0.0-dev" +version = "5.0.0" dependencies = [ "impl-serde", "parity-scale-codec", - "parity-wasm 0.42.2", + "parity-wasm", "scale-info", "serde", + "sp-core-hashing-proc-macro", "sp-runtime", "sp-std", "sp-version-proc-macro", @@ -9607,12 +9637,29 @@ dependencies = [ [[package]] name = "sp-wasm-interface" -version = "4.0.0-dev" +version = "7.0.0" +dependencies = [ + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "sp-std", + "wasmi 0.13.0", + "wasmtime", +] + +[[package]] +name = "sp-weights" +version = "4.0.0" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-core", + "sp-debug-derive", "sp-std", - "wasmi", ] [[package]] @@ -9622,123 +9669,135 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "spin" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" [[package]] -name = "static_assertions" -version = "1.1.0" +name = "spki" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] [[package]] -name = "statrs" -version = "0.15.0" +name = "ss58-registry" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05bdbb8e4e78216a85785a85d3ec3183144f98d0097b9281802c019bb07a6f05" +checksum = "37a9821878e1f13aba383aa40a86fb1b33c7265774ec91e32563cb1dd1577496" dependencies = [ - "approx", - "lazy_static", - "nalgebra", - "num-traits", - "rand 0.8.4", + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", ] [[package]] -name = "strsim" -version = "0.8.0" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] -name = "strsim" -version = "0.10.0" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "structopt" -version = "0.3.21" +name = "static_init" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c" +checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" dependencies = [ - "clap", - "lazy_static", - "structopt-derive", + "bitflags", + "cfg_aliases", + "libc", + "parking_lot 0.11.2", + "parking_lot_core 0.8.5", + "static_init_macro", + "winapi", ] [[package]] -name = "structopt-derive" -version = "0.4.14" +name = "static_init_macro" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" +checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ - "heck", - "proc-macro-error 1.0.4", + "cfg_aliases", + "memchr", "proc-macro2", "quote", "syn", ] [[package]] -name = "strum" -version = "0.20.0" +name = "statrs" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c" +checksum = "05bdbb8e4e78216a85785a85d3ec3183144f98d0097b9281802c019bb07a6f05" dependencies = [ - "strum_macros 0.20.1", + "approx", + "lazy_static", + "nalgebra", + "num-traits", + "rand 0.8.5", ] [[package]] -name = "strum" -version = "0.21.0" +name = "strsim" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "strum_macros" -version = "0.20.1" +name = "strum" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", + "strum_macros", ] [[package]] name = "strum_macros" -version = "0.21.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" dependencies = [ "heck", "proc-macro2", "quote", + "rustversion", "syn", ] [[package]] name = "subkey" -version = "2.0.1" +version = "2.0.2" dependencies = [ + "clap 4.0.11", "sc-cli", - "structopt", ] [[package]] name = "substrate-bip39" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bed6646a0159b9935b5d045611560eeef842b78d7adc3ba36f5ca325a13a0236" +checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" dependencies = [ - "hmac 0.7.1", - "pbkdf2 0.3.0", + "hmac 0.11.0", + "pbkdf2 0.8.0", "schnorrkel", - "sha2 0.8.2", + "sha2 0.9.8", "zeroize", ] @@ -9753,12 +9812,12 @@ dependencies = [ name = "substrate-frame-cli" version = "4.0.0-dev" dependencies = [ + "clap 4.0.11", "frame-support", "frame-system", "sc-cli", "sp-core", "sp-runtime", - "structopt", ] [[package]] @@ -9767,12 +9826,14 @@ version = "3.0.0" dependencies = [ "frame-support", "frame-system", - "futures 0.3.16", - "jsonrpc-client-transports", + "futures", + "jsonrpsee", "parity-scale-codec", "sc-rpc-api", "scale-info", "serde", + "sp-core", + "sp-runtime", "sp-storage", "tokio", ] @@ -9781,17 +9842,17 @@ dependencies = [ name = "substrate-frame-rpc-system" version = "4.0.0-dev" dependencies = [ + "assert_matches", "frame-system-rpc-runtime-api", - "futures 0.3.16", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "log 0.4.14", + "futures", + "jsonrpsee", + "log", "parity-scale-codec", "sc-client-api", "sc-rpc-api", "sc-transaction-pool", "sc-transaction-pool-api", + "serde_json", "sp-api", "sp-block-builder", "sp-blockchain", @@ -9799,34 +9860,68 @@ dependencies = [ "sp-runtime", "sp-tracing", "substrate-test-runtime-client", + "tokio", ] [[package]] name = "substrate-prometheus-endpoint" -version = "0.9.0" +version = "0.10.0-dev" dependencies = [ - "async-std", - "derive_more", "futures-util", - "hyper 0.14.11", - "log 0.4.14", + "hyper", + "log", "prometheus", + "thiserror", + "tokio", +] + +[[package]] +name = "substrate-rpc-client" +version = "0.10.0-dev" +dependencies = [ + "async-trait", + "jsonrpsee", + "log", + "sc-rpc-api", + "serde", + "sp-core", + "sp-runtime", "tokio", ] +[[package]] +name = "substrate-state-trie-migration-rpc" +version = "4.0.0-dev" +dependencies = [ + "jsonrpsee", + "log", + "parity-scale-codec", + "sc-client-api", + "sc-rpc-api", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", + "trie-db", +] + [[package]] name = "substrate-test-client" version = "2.0.1" dependencies = [ + "array-bytes", "async-trait", - "futures 0.3.16", - "hex", + "futures", "parity-scale-codec", "sc-client-api", "sc-client-db", "sc-consensus", "sc-executor", - "sc-light", "sc-offchain", "sc-service", "serde", @@ -9844,17 +9939,17 @@ dependencies = [ name = "substrate-test-runtime" version = "2.0.0" dependencies = [ - "cfg-if 1.0.0", + "beefy-merkle-tree", + "cfg-if", "frame-support", "frame-system", "frame-system-rpc-runtime-api", - "futures 0.3.16", - "log 0.4.14", + "futures", + "log", "memory-db", "pallet-babe", "pallet-timestamp", "parity-scale-codec", - "parity-util-mem", "sc-block-builder", "sc-executor", "sc-service", @@ -9862,6 +9957,7 @@ dependencies = [ "serde", "sp-api", "sp-application-crypto", + "sp-beefy", "sp-block-builder", "sp-consensus", "sp-consensus-aura", @@ -9890,12 +9986,11 @@ dependencies = [ name = "substrate-test-runtime-client" version = "2.0.0" dependencies = [ - "futures 0.3.16", + "futures", "parity-scale-codec", "sc-block-builder", "sc-client-api", "sc-consensus", - "sc-light", "sp-api", "sp-blockchain", "sp-consensus", @@ -9909,22 +10004,22 @@ dependencies = [ name = "substrate-test-runtime-transaction-pool" version = "2.0.0" dependencies = [ - "derive_more", - "futures 0.3.16", + "futures", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "sc-transaction-pool", "sc-transaction-pool-api", "sp-blockchain", "sp-runtime", "substrate-test-runtime-client", + "thiserror", ] [[package]] name = "substrate-test-utils" version = "4.0.0-dev" dependencies = [ - "futures 0.3.16", + "futures", "sc-service", "substrate-test-utils-derive", "tokio", @@ -9935,7 +10030,7 @@ dependencies = [ name = "substrate-test-utils-derive" version = "0.10.0-dev" dependencies = [ - "proc-macro-crate 1.0.0", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -9954,22 +10049,18 @@ dependencies = [ name = "substrate-wasm-builder" version = "5.0.0-dev" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", "build-helper", "cargo_metadata", + "filetime", "sp-maybe-compressed-blob", + "strum", "tempfile", "toml", "walkdir", - "wasm-gc-api", + "wasm-opt", ] -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - [[package]] name = "subtle" version = "2.4.0" @@ -9978,24 +10069,13 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" [[package]] name = "syn" -version = "1.0.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "syn-mid" -version = "0.5.3" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", - "syn", + "unicode-ident", ] [[package]] @@ -10011,110 +10091,59 @@ dependencies = [ ] [[package]] -name = "tap" -version = "1.0.1" +name = "system-configuration" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +checksum = "d75182f12f490e953596550b65ee31bda7c8e043d9386174b353bda50838c3fd" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] [[package]] -name = "target-lexicon" -version = "0.11.2" +name = "system-configuration-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422045212ea98508ae3d28025bc5aaa2bd4a9cdaecd442a08da2ee620ee9ea95" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] -name = "target-lexicon" -version = "0.12.0" +name = "tap" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ae3b39281e4b14b8123bdbaddd472b7dfe215e444181f2f9d2443c2444f834" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] -name = "tempfile" -version = "3.2.0" +name = "target-lexicon" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "rand 0.8.4", - "redox_syscall 0.2.5", - "remove_dir_all", - "winapi 0.3.9", -] +checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] -name = "termcolor" -version = "1.1.2" +name = "tempfile" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "test-runner" -version = "0.9.0" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ - "frame-system", - "futures 0.3.16", - "jsonrpc-core", - "log 0.4.14", - "num-traits", - "sc-basic-authorship", - "sc-cli", - "sc-client-api", - "sc-consensus", - "sc-consensus-babe", - "sc-consensus-manual-seal", - "sc-executor", - "sc-finality-grandpa", - "sc-informant", - "sc-network", - "sc-rpc", - "sc-rpc-server", - "sc-service", - "sc-transaction-pool", - "sc-transaction-pool-api", - "sp-api", - "sp-block-builder", - "sp-blockchain", - "sp-consensus", - "sp-consensus-babe", - "sp-core", - "sp-externalities", - "sp-finality-grandpa", - "sp-inherents", - "sp-keyring", - "sp-offchain", - "sp-runtime", - "sp-runtime-interface", - "sp-session", - "sp-state-machine", - "sp-transaction-pool", - "sp-wasm-interface", - "tokio", + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", ] [[package]] -name = "test-runner-example" -version = "0.1.0" +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ - "frame-benchmarking", - "frame-system", - "node-cli", - "node-primitives", - "node-runtime", - "pallet-transaction-payment", - "sc-consensus", - "sc-consensus-babe", - "sc-consensus-manual-seal", - "sc-executor", - "sc-finality-grandpa", - "sc-service", - "sp-consensus-babe", - "sp-keyring", - "sp-runtime", - "test-runner", + "winapi-util", ] [[package]] @@ -10128,29 +10157,35 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.26" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.26" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + [[package]] name = "thread_local" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ "once_cell", ] @@ -10164,6 +10199,17 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.1+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "931e876f91fed0827f863a2d153897790da0b24d882c721a79cb3beb0b903261" +dependencies = [ + "cc", + "fs_extra", + "libc", +] + [[package]] name = "time" version = "0.1.44" @@ -10172,14 +10218,14 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi 0.3.9", + "winapi", ] [[package]] name = "tiny-bip39" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9e44c4759bae7f1032e286a7ef990bd9ed23fe831b7eeba0beb97484c2e59b8" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" dependencies = [ "anyhow", "hmac 0.8.1", @@ -10187,9 +10233,10 @@ dependencies = [ "pbkdf2 0.4.0", "rand 0.7.3", "rustc-hash", - "sha2 0.9.3", + "sha2 0.9.8", "thiserror", "unicode-normalization", + "wasm-bindgen", "zeroize", ] @@ -10229,101 +10276,40 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.10.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" dependencies = [ - "autocfg 1.0.1", - "bytes 1.0.1", + "autocfg", + "bytes", "libc", "memchr", - "mio 0.7.13", + "mio", "num_cpus", - "once_cell", - "parking_lot 0.11.1", - "pin-project-lite 0.2.6", + "parking_lot 0.12.1", + "pin-project-lite 0.2.9", "signal-hook-registry", + "socket2", "tokio-macros", - "winapi 0.3.9", -] - -[[package]] -name = "tokio-codec" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.31", - "tokio-io", -] - -[[package]] -name = "tokio-executor" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" -dependencies = [ - "crossbeam-utils 0.7.2", - "futures 0.1.31", -] - -[[package]] -name = "tokio-io" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.31", - "log 0.4.14", + "winapi", ] [[package]] name = "tokio-macros" -version = "1.3.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" -dependencies = [ - "crossbeam-utils 0.7.2", - "futures 0.1.31", - "lazy_static", - "log 0.4.14", - "mio 0.6.23", - "num_cpus", - "parking_lot 0.9.0", - "slab", - "tokio-executor", - "tokio-io", - "tokio-sync", -] - [[package]] name = "tokio-rustls" -version = "0.22.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" dependencies = [ "rustls", "tokio", @@ -10337,69 +10323,82 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" dependencies = [ "futures-core", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "tokio", ] [[package]] -name = "tokio-sync" -version = "0.1.8" +name = "tokio-test" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" +checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ - "fnv", - "futures 0.1.31", + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", ] [[package]] -name = "tokio-tcp" -version = "0.1.4" +name = "tokio-util" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ - "bytes 0.4.12", - "futures 0.1.31", - "iovec", - "mio 0.6.23", - "tokio-io", - "tokio-reactor", + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite 0.2.9", + "tokio", + "tracing", ] [[package]] -name = "tokio-tls" -version = "0.2.1" +name = "toml" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "354b8cd83825b3c20217a9dc174d6a0c67441a2fae5c41bcb1ea6679f6ae0f7c" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ - "futures 0.1.31", - "native-tls", - "tokio-io", + "serde", ] [[package]] -name = "tokio-util" -version = "0.6.7" +name = "tower" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ - "bytes 1.0.1", - "futures-core", - "futures-io", - "futures-sink", - "log 0.4.14", - "pin-project-lite 0.2.6", - "tokio", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "toml" -version = "0.5.8" +name = "tower-http" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ - "serde", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite 0.2.9", + "tower-layer", + "tower-service", ] +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.1" @@ -10408,21 +10407,22 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.25" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ - "cfg-if 1.0.0", - "pin-project-lite 0.2.6", + "cfg-if", + "log", + "pin-project-lite 0.2.9", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" -version = "0.1.15" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -10431,11 +10431,12 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.17" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" dependencies = [ - "lazy_static", + "once_cell", + "valuable", ] [[package]] @@ -10444,18 +10445,18 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.5", + "pin-project", "tracing", ] [[package]] name = "tracing-log" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", - "log 0.4.14", + "log", "tracing-core", ] @@ -10471,19 +10472,20 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.2.19" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab69019741fca4d98be3c62d2b75254528b5432233fd8a4d2739fec20278de48" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", "chrono", "lazy_static", "matchers", + "parking_lot 0.11.2", "regex", "serde", "serde_json", "sharded-slab", - "smallvec 1.6.1", + "smallvec", "thread_local", "tracing", "tracing-core", @@ -10491,12 +10493,6 @@ dependencies = [ "tracing-serde", ] -[[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" - [[package]] name = "treeline" version = "0.1.0" @@ -10505,9 +10501,9 @@ checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" [[package]] name = "trie-bench" -version = "0.28.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edd9bdf0c2e08fd77c0fb2608179cac7ebed997ae18f58d47a2d96425ff51f0" +checksum = "c5b26bd2cdd7641c5beb476b314c0cb1f629832bf21a6235f545e2d47bc9d05a" dependencies = [ "criterion", "hash-db", @@ -10521,22 +10517,22 @@ dependencies = [ [[package]] name = "trie-db" -version = "0.22.6" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eac131e334e81b6b3be07399482042838adcd7957aa0010231d0813e39e02fa" +checksum = "004e1e8f92535694b4cb1444dc5a8073ecf0815e3357f729638b9f8fc4062908" dependencies = [ "hash-db", - "hashbrown 0.11.2", - "log 0.4.14", + "hashbrown 0.12.3", + "log", "rustc-hex", - "smallvec 1.6.1", + "smallvec", ] [[package]] name = "trie-root" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "652931506d2c1244d7217a70b99f56718a7b4161b37f04e7cd868072a99f68cd" +checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" dependencies = [ "hash-db", ] @@ -10553,44 +10549,46 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.20.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d57e219ba600dd96c2f6d82eb79645068e14edbc5c7e27514af40436b88150c" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" dependencies = [ "async-trait", - "cfg-if 1.0.0", + "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", "futures-io", "futures-util", - "idna 0.2.2", + "idna", "ipnet", "lazy_static", - "log 0.4.14", - "rand 0.8.4", - "smallvec 1.6.1", + "rand 0.8.5", + "smallvec", "thiserror", "tinyvec", - "url 2.2.1", + "tokio", + "tracing", + "url", ] [[package]] name = "trust-dns-resolver" -version = "0.20.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0437eea3a6da51acc1e946545ff53d5b8fb2611ff1c3bed58522dde100536ae" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "futures-util", "ipconfig", "lazy_static", - "log 0.4.14", "lru-cache", - "parking_lot 0.11.1", + "parking_lot 0.12.1", "resolv-conf", - "smallvec 1.6.1", + "smallvec", "thiserror", + "tokio", + "tracing", "trust-dns-proto", ] @@ -10604,58 +10602,68 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ - "log 0.4.14", + "clap 4.0.11", + "frame-remote-externalities", + "frame-try-runtime", + "log", "parity-scale-codec", - "remote-externalities", "sc-chain-spec", "sc-cli", "sc-executor", "sc-service", "serde", "sp-core", + "sp-externalities", + "sp-io", "sp-keystore", "sp-runtime", "sp-state-machine", - "structopt", + "sp-version", + "sp-weights", + "substrate-rpc-client", + "tokio", + "zstd", ] [[package]] name = "trybuild" -version = "1.0.43" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02c413315329fc96167f922b46fd0caa3a43f4697b7a7896b183c7142635832" +checksum = "0da18123d1316f5a65fc9b94e30a0fcf58afb1daff1b8e18f41dc30f5bfc38c8" dependencies = [ "dissimilar", "glob", - "lazy_static", + "once_cell", "serde", + "serde_derive", "serde_json", "termcolor", "toml", ] +[[package]] +name = "tt-call" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" + [[package]] name = "twox-hash" -version = "1.6.0" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 0.1.10", - "rand 0.7.3", + "cfg-if", + "digest 0.10.3", + "rand 0.8.5", "static_assertions", ] -[[package]] -name = "typeable" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" - [[package]] name = "typenum" -version = "1.12.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" @@ -10675,24 +10683,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "unicase" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -dependencies = [ - "version_check 0.1.5", -] - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check 0.9.2", -] - [[package]] name = "unicode-bidi" version = "0.3.4" @@ -10702,6 +10692,12 @@ dependencies = [ "matches", ] +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + [[package]] name = "unicode-normalization" version = "0.1.17" @@ -10711,12 +10707,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" - [[package]] name = "unicode-width" version = "0.1.8" @@ -10725,46 +10715,28 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "universal-hash" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ "generic-array 0.14.4", - "subtle 2.4.0", -] - -[[package]] -name = "unsigned-varint" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fdeedbf205afadfe39ae559b75c3240f24e257d0ca27e85f85cb82aa19ac35" - -[[package]] -name = "unsigned-varint" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35581ff83d4101e58b582e607120c7f5ffb17e632a980b1f38334d76b36908b2" -dependencies = [ - "asynchronous-codec 0.5.0", - "bytes 1.0.1", - "futures-io", - "futures-util", + "subtle", ] [[package]] name = "unsigned-varint" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8d425fafb8cd76bc3f22aace4af471d3156301d7508f2107e98fbeae10bc7f" +checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" dependencies = [ - "asynchronous-codec 0.6.0", - "bytes 1.0.1", + "asynchronous-codec", + "bytes", "futures-io", "futures-util", ] @@ -10775,17 +10747,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] - [[package]] name = "url" version = "2.2.1" @@ -10793,19 +10754,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" dependencies = [ "form_urlencoded", - "idna 0.2.2", + "idna", "matches", - "percent-encoding 2.1.0", + "percent-encoding", ] [[package]] -name = "value-bag" -version = "1.0.0-alpha.6" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b676010e055c99033117c2343b33a40a30b91fecd6c49055ac9cd2d6c305ab1" -dependencies = [ - "ctor", -] +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vcpkg" @@ -10819,18 +10777,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.2" @@ -10865,7 +10811,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", - "winapi 0.3.9", + "winapi", "winapi-util", ] @@ -10875,7 +10821,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.14", + "log", "try-lock", ] @@ -10891,25 +10837,31 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" -version = "0.2.73" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" +checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.73" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" +checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" dependencies = [ "bumpalo", "lazy_static", - "log 0.4.14", + "log", "proc-macro2", "quote", "syn", @@ -10922,7 +10874,7 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", @@ -10930,9 +10882,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.73" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" +checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -10940,9 +10892,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.73" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" +checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92" dependencies = [ "proc-macro2", "quote", @@ -10953,454 +10905,332 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" - -[[package]] -name = "wasm-gc-api" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c32691b6c7e6c14e7f8fd55361a9088b507aa49620fcd06c09b3a1082186b9" -dependencies = [ - "log 0.4.14", - "parity-wasm 0.32.0", - "rustc-demangle", -] - -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures 0.3.16", - "js-sys", - "parking_lot 0.11.1", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasmer" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70cfae554988d904d64ca17ab0e7cd652ee5c8a0807094819c1ea93eb9d6866" -dependencies = [ - "cfg-if 0.1.10", - "indexmap", - "more-asserts", - "target-lexicon 0.11.2", - "thiserror", - "wasmer-compiler", - "wasmer-compiler-cranelift", - "wasmer-derive", - "wasmer-engine", - "wasmer-engine-jit", - "wasmer-engine-native", - "wasmer-types", - "wasmer-vm", - "wat", - "winapi 0.3.9", -] - -[[package]] -name = "wasmer-compiler" -version = "1.0.2" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7732a9cab472bd921d5a0c422f45b3d03f62fa2c40a89e0770cef6d47e383e" -dependencies = [ - "enumset", - "serde", - "serde_bytes", - "smallvec 1.6.1", - "target-lexicon 0.11.2", - "thiserror", - "wasmer-types", - "wasmer-vm", - "wasmparser 0.65.0", -] +checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4" [[package]] -name = "wasmer-compiler-cranelift" -version = "1.0.2" +name = "wasm-instrument" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb9395f094e1d81534f4c5e330ed4cdb424e8df870d29ad585620284f5fddb" -dependencies = [ - "cranelift-codegen 0.68.0", - "cranelift-frontend 0.68.0", - "gimli 0.22.0", - "more-asserts", - "rayon", - "serde", - "smallvec 1.6.1", - "tracing", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", +checksum = "aa1dafb3e60065305741e83db35c6c2584bb3725b692b5b66148a38d72ace6cd" +dependencies = [ + "parity-wasm", ] [[package]] -name = "wasmer-compiler-singlepass" -version = "1.0.2" +name = "wasm-instrument" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426ae6ef0f606ca815510f3e2ef6f520e217514bfb7a664defe180b9a9e75d07" +checksum = "2a47ecb37b9734d1085eaa5ae1a81e60801fd8c28d4cabdd8aedb982021918bc" dependencies = [ - "byteorder", - "dynasm", - "dynasmrt", - "lazy_static", - "more-asserts", - "rayon", - "serde", - "smallvec 1.6.1", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", + "parity-wasm", ] [[package]] -name = "wasmer-derive" -version = "1.0.2" +name = "wasm-opt" +version = "0.110.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b86dcd2c3efdb8390728a2b56f762db07789aaa5aa872a9dc776ba3a7912ed" +checksum = "b68e8037b4daf711393f4be2056246d12d975651b14d581520ad5d1f19219cec" dependencies = [ - "proc-macro-error 1.0.4", - "proc-macro2", - "quote", - "syn", + "anyhow", + "libc", + "strum", + "strum_macros", + "tempfile", + "thiserror", + "wasm-opt-cxx-sys", + "wasm-opt-sys", ] [[package]] -name = "wasmer-engine" -version = "1.0.2" +name = "wasm-opt-cxx-sys" +version = "0.110.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe4667d6bd888f26ae8062a63a9379fa697415b4b4e380f33832e8418fd71b5" +checksum = "91adbad477e97bba3fbd21dd7bfb594e7ad5ceb9169ab1c93ab9cb0ada636b6f" dependencies = [ - "backtrace", - "bincode", - "lazy_static", - "memmap2", - "more-asserts", - "rustc-demangle", - "serde", - "serde_bytes", - "target-lexicon 0.11.2", - "thiserror", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", + "anyhow", + "cxx", + "cxx-build", + "wasm-opt-sys", ] [[package]] -name = "wasmer-engine-jit" -version = "1.0.2" +name = "wasm-opt-sys" +version = "0.110.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26770be802888011b4a3072f2a282fc2faa68aa48c71b3db6252a3937a85f3da" +checksum = "ec4fa5a322a4e6ac22fd141f498d56afbdbf9df5debeac32380d2dcaa3e06941" dependencies = [ - "bincode", - "cfg-if 0.1.10", - "region", - "serde", - "serde_bytes", - "wasmer-compiler", - "wasmer-engine", - "wasmer-types", - "wasmer-vm", - "winapi 0.3.9", + "anyhow", + "cc", + "cxx", + "cxx-build", + "regex", ] [[package]] -name = "wasmer-engine-native" -version = "1.0.2" +name = "wasm-timer" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb4083a6c69f2cd4b000b82a80717f37c6cc2e536aee3a8ffe9af3edc276a8b" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "bincode", - "cfg-if 0.1.10", - "leb128", - "libloading 0.6.7", - "serde", - "tempfile", - "tracing", - "wasmer-compiler", - "wasmer-engine", - "wasmer-object", - "wasmer-types", - "wasmer-vm", - "which", + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "wasmer-object" -version = "1.0.2" +name = "wasmi" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf8e0c12b82ff81ebecd30d7e118be5fec871d6de885a90eeb105df0a769a7b" +checksum = "fc13b3c219ca9aafeec59150d80d89851df02e0061bc357b4d66fc55a8d38787" dependencies = [ - "object 0.22.0", - "thiserror", - "wasmer-compiler", - "wasmer-types", + "parity-wasm", + "wasmi-validation", + "wasmi_core 0.2.0", ] [[package]] -name = "wasmer-types" -version = "1.0.2" +name = "wasmi" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f4ac28c2951cd792c18332f03da523ed06b170f5cf6bb5b1bdd7e36c2a8218" +checksum = "01bf50edb2ea9d922aa75a7bf3c15e26a6c9e2d18c56e862b49737a582901729" dependencies = [ - "cranelift-entity 0.68.0", - "serde", - "thiserror", + "spin 0.9.4", + "wasmi_arena", + "wasmi_core 0.5.0", + "wasmparser-nostd", ] [[package]] -name = "wasmer-vm" -version = "1.0.2" +name = "wasmi-validation" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7635ba0b6d2fd325f588d69a950ad9fa04dddbf6ad08b6b2a183146319bf6ae" +checksum = "91ff416ad1ff0c42e5a926ed5d5fab74c0f098749aa0ad8b2a34b982ce0e867b" dependencies = [ - "backtrace", - "cc", - "cfg-if 0.1.10", - "indexmap", - "libc", - "memoffset", - "more-asserts", - "region", - "serde", - "thiserror", - "wasmer-types", - "winapi 0.3.9", + "parity-wasm", ] [[package]] -name = "wasmi" -version = "0.9.0" +name = "wasmi_arena" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ea379cbb0b41f3a9f0bf7b47036d036aae7f43383d8cc487d4deccf40dee0a" + +[[package]] +name = "wasmi_core" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ee05bba3d1d994652079893941a2ef9324d2b58a63c31b40678fb7eddd7a5a" +checksum = "e0a088e8c4c59c6f2b9eae169bf86328adccc477c00b56d3661e3e9fb397b184" dependencies = [ "downcast-rs", - "errno", - "libc", "libm", "memory_units", - "num-rational 0.2.4", + "num-rational", "num-traits", - "parity-wasm 0.42.2", - "wasmi-validation", ] [[package]] -name = "wasmi-validation" -version = "0.4.0" +name = "wasmi_core" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb8e860796d8be48efef530b60eebf84e74a88bce107374fffb0da97d504b8" +checksum = "c5bf998ab792be85e20e771fe14182b4295571ad1d4f89d3da521c1bef5f597a" dependencies = [ - "parity-wasm 0.42.2", + "downcast-rs", + "libm", + "num-traits", ] [[package]] name = "wasmparser" -version = "0.65.0" +version = "0.89.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc2fe6350834b4e528ba0901e7aa405d78b89dc1fa3145359eb4de0e323fcf" +checksum = "ab5d3e08b13876f96dd55608d03cd4883a0545884932d5adf11925876c96daef" +dependencies = [ + "indexmap", +] [[package]] -name = "wasmparser" -version = "0.78.2" +name = "wasmparser-nostd" +version = "0.91.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" +checksum = "9c37f310b5a62bfd5ae7c0f1d8e6f98af16a5d6d84ba764e9c36439ec14e318b" +dependencies = [ + "indexmap-nostd", +] [[package]] name = "wasmtime" -version = "0.27.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b310b9d20fcf59385761d1ade7a3ef06aecc380e3d3172035b919eaf7465d9f7" +checksum = "8a10dc9784d8c3a33c970e3939180424955f08af2e7f20368ec02685a0e8f065" dependencies = [ "anyhow", - "backtrace", "bincode", - "cfg-if 1.0.0", - "cpp_demangle", + "cfg-if", "indexmap", - "lazy_static", "libc", - "log 0.4.14", - "paste 1.0.4", + "log", + "object 0.29.0", + "once_cell", + "paste", "psm", - "region", - "rustc-demangle", + "rayon", "serde", - "smallvec 1.6.1", - "target-lexicon 0.12.0", - "wasmparser 0.78.2", + "target-lexicon", + "wasmparser", "wasmtime-cache", + "wasmtime-cranelift", "wasmtime-environ", "wasmtime-jit", - "wasmtime-profiling", "wasmtime-runtime", - "winapi 0.3.9", + "windows-sys 0.36.1", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4dbdc6daf68528cad1275ac91e3f51848ce9824385facc94c759f529decdf8" +dependencies = [ + "cfg-if", ] [[package]] name = "wasmtime-cache" -version = "0.27.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d14d500d5c3dc5f5c097158feee123d64b3097f0d836a2a27dff9c761c73c843" +checksum = "9f507f3fa1ee1b2f9a83644e2514242b1dfe580782c0eb042f1ef70255bc4ffe" dependencies = [ "anyhow", - "base64 0.13.0", + "base64", "bincode", "directories-next", - "errno", "file-per-thread-logger", - "libc", - "log 0.4.14", + "log", + "rustix", "serde", - "sha2 0.9.3", + "sha2 0.9.8", "toml", - "winapi 0.3.9", + "windows-sys 0.36.1", "zstd", ] [[package]] name = "wasmtime-cranelift" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c525b39f062eada7db3c1298287b96dcb6e472b9f6b22501300b28d9fa7582f6" -dependencies = [ - "cranelift-codegen 0.74.0", - "cranelift-entity 0.74.0", - "cranelift-frontend 0.74.0", - "cranelift-wasm", - "target-lexicon 0.12.0", - "wasmparser 0.78.2", - "wasmtime-environ", -] - -[[package]] -name = "wasmtime-debug" -version = "0.27.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d2a763e7a6fc734218e0e463196762a4f409c483063d81e0e85f96343b2e0a" +checksum = "8f03cf79d982fc68e94ba0bea6a300a3b94621c4eb9705eece0a4f06b235a3b5" dependencies = [ "anyhow", - "gimli 0.24.0", - "more-asserts", - "object 0.24.0", - "target-lexicon 0.12.0", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log", + "object 0.29.0", + "target-lexicon", "thiserror", - "wasmparser 0.78.2", + "wasmparser", "wasmtime-environ", ] [[package]] name = "wasmtime-environ" -version = "0.27.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64d0c2d881c31b0d65c1f2695e022d71eb60b9fbdd336aacca28208b58eac90" +checksum = "5c587c62e91c5499df62012b87b88890d0eb470b2ffecc5964e9da967b70c77c" dependencies = [ - "cfg-if 1.0.0", - "cranelift-codegen 0.74.0", - "cranelift-entity 0.74.0", - "cranelift-wasm", - "gimli 0.24.0", + "anyhow", + "cranelift-entity", + "gimli", "indexmap", - "log 0.4.14", - "more-asserts", + "log", + "object 0.29.0", "serde", + "target-lexicon", "thiserror", - "wasmparser 0.78.2", + "wasmparser", + "wasmtime-types", ] [[package]] name = "wasmtime-jit" -version = "0.27.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4539ea734422b7c868107e2187d7746d8affbcaa71916d72639f53757ad707" +checksum = "047839b5dabeae5424a078c19b8cc897e5943a7fadc69e3d888b9c9a897666b3" dependencies = [ - "addr2line 0.15.1", + "addr2line", "anyhow", - "cfg-if 1.0.0", - "cranelift-codegen 0.74.0", - "cranelift-entity 0.74.0", - "cranelift-frontend 0.74.0", - "cranelift-native", - "cranelift-wasm", - "gimli 0.24.0", - "log 0.4.14", - "more-asserts", - "object 0.24.0", - "rayon", - "region", + "bincode", + "cfg-if", + "cpp_demangle", + "gimli", + "log", + "object 0.29.0", + "rustc-demangle", + "rustix", "serde", - "target-lexicon 0.12.0", + "target-lexicon", "thiserror", - "wasmparser 0.78.2", - "wasmtime-cranelift", - "wasmtime-debug", "wasmtime-environ", - "wasmtime-obj", - "wasmtime-profiling", + "wasmtime-jit-debug", "wasmtime-runtime", - "winapi 0.3.9", -] - -[[package]] -name = "wasmtime-obj" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1a8ff85246d091828e2225af521a6208ed28c997bb5c39eb697366dc2e2f2b" -dependencies = [ - "anyhow", - "more-asserts", - "object 0.24.0", - "target-lexicon 0.12.0", - "wasmtime-debug", - "wasmtime-environ", + "windows-sys 0.36.1", ] [[package]] -name = "wasmtime-profiling" -version = "0.27.0" +name = "wasmtime-jit-debug" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24364d522dcd67c897c8fffc42e5bdfc57207bbb6d7eeade0da9d4a7d70105b" +checksum = "b299569abf6f99b7b8e020afaf84a700e8636c6a42e242069267322cd5818235" dependencies = [ - "anyhow", - "cfg-if 1.0.0", - "lazy_static", - "libc", - "serde", - "target-lexicon 0.12.0", - "wasmtime-environ", - "wasmtime-runtime", + "object 0.29.0", + "once_cell", + "rustix", ] [[package]] name = "wasmtime-runtime" -version = "0.27.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51e57976e8a19a18a18e002c6eb12e5769554204238e47ff155fda1809ef0f7" +checksum = "ae79e0515160bd5abee5df50a16c4eb8db9f71b530fc988ae1d9ce34dcb8dd01" dependencies = [ "anyhow", - "backtrace", "cc", - "cfg-if 1.0.0", + "cfg-if", "indexmap", - "lazy_static", "libc", - "log 0.4.14", + "log", "mach", + "memfd", "memoffset", - "more-asserts", - "rand 0.8.4", - "region", + "paste", + "rand 0.8.5", + "rustix", "thiserror", + "wasmtime-asm-macros", "wasmtime-environ", - "winapi 0.3.9", + "wasmtime-jit-debug", + "windows-sys 0.36.1", +] + +[[package]] +name = "wasmtime-types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790cf43ee8e2d5dad1780af30f00d7a972b74725fb1e4f90c28d62733819b185" +dependencies = [ + "cranelift-entity", + "serde", + "thiserror", + "wasmparser", ] [[package]] @@ -11423,9 +11253,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.47" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3" +checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a" dependencies = [ "js-sys", "wasm-bindgen", @@ -11433,9 +11263,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.4" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ "ring", "untrusted", @@ -11443,59 +11273,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.21.0" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376" +checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" dependencies = [ "webpki", ] [[package]] -name = "websocket" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413b37840b9e27b340ce91b319ede10731de8c72f5bc4cb0206ec1ca4ce581d0" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.31", - "hyper 0.10.16", - "native-tls", - "rand 0.6.5", - "tokio-codec", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-tls", - "unicase 1.4.2", - "url 1.7.2", - "websocket-base", -] - -[[package]] -name = "websocket-base" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e3810f0d00c4dccb54c30a4eee815e703232819dec7b007db115791c42aa374" -dependencies = [ - "base64 0.10.1", - "bitflags", - "byteorder", - "bytes 0.4.12", - "futures 0.1.31", - "native-tls", - "rand 0.6.5", - "sha1", - "tokio-codec", - "tokio-io", - "tokio-tcp", - "tokio-tls", -] - -[[package]] -name = "wepoll-sys" -version = "3.0.1" +name = "wepoll-ffi" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" dependencies = [ "cc", ] @@ -11512,15 +11301,9 @@ dependencies = [ [[package]] name = "widestring" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" - -[[package]] -name = "winapi" -version = "0.2.8" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" [[package]] name = "winapi" @@ -11532,12 +11315,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -11550,7 +11327,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -11560,35 +11337,157 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "winreg" -version = "0.6.2" +name = "windows" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" dependencies = [ - "winapi 0.3.9", + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", ] [[package]] -name = "ws2_32-sys" -version = "0.2.1" +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc 0.32.0", + "windows_i686_gnu 0.32.0", + "windows_i686_msvc 0.32.0", + "windows_x86_64_gnu 0.32.0", + "windows_x86_64_msvc 0.32.0", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ - "winapi 0.2.8", - "winapi-build", + "winapi", ] [[package]] name = "wyz" -version = "0.2.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] [[package]] name = "x25519-dalek" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ "curve25519-dalek 3.0.2", "rand_core 0.5.1", @@ -11597,32 +11496,32 @@ dependencies = [ [[package]] name = "yamux" -version = "0.9.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" +checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" dependencies = [ - "futures 0.3.16", - "log 0.4.14", + "futures", + "log", "nohash-hasher", - "parking_lot 0.11.1", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "static_assertions", ] [[package]] name = "zeroize" -version = "1.4.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.0.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", @@ -11632,18 +11531,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.6.1+zstd.1.4.9" +version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de55e77f798f205d8561b8fe2ef57abfb6e0ff2abe7fd3c089e119cdb5631a3" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "3.0.1+zstd.1.4.9" +version = "5.0.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1387cabcd938127b30ce78c4bf00b30387dddf704e3f0881dbc4ff62b5566f8c" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" dependencies = [ "libc", "zstd-sys", @@ -11651,9 +11550,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.4.20+zstd.1.4.9" +version = "2.0.1+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd5b733d7cf2d9447e2c3e76a5589b4f5e5ae065c22a2bc0b023cbc331b6c8e" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index bca0c816217ee..eb78d5e104486 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,10 @@ members = [ "bin/node-template/runtime", "bin/node/bench", "bin/node/cli", - "bin/node/test-runner-example", "bin/node/executor", + "bin/node/inspect", "bin/node/primitives", "bin/node/rpc", - "bin/node/rpc-client", "bin/node/runtime", "bin/node/testing", "bin/utils/chain-spec-builder", @@ -19,6 +18,8 @@ members = [ "client/api", "client/authority-discovery", "client/basic-authorship", + "client/beefy", + "client/beefy/rpc", "client/block-builder", "client/chain-spec", "client/chain-spec/derive", @@ -39,11 +40,18 @@ members = [ "client/executor/wasmi", "client/executor/wasmtime", "client/finality-grandpa", + "client/finality-grandpa/rpc", "client/informant", "client/keystore", - "client/light", + "client/merkle-mountain-range", + "client/merkle-mountain-range/rpc", "client/network", + "client/network/transactions", "client/network-gossip", + "client/network/bitswap", + "client/network/common", + "client/network/light", + "client/network/sync", "client/network/test", "client/offchain", "client/peerset", @@ -52,9 +60,11 @@ members = [ "client/rpc", "client/rpc-api", "client/rpc-servers", + "client/rpc-spec-v2", "client/service", "client/service/test", "client/state-db", + "client/sysinfo", "client/sync-state-rpc", "client/telemetry", "client/tracing", @@ -62,29 +72,41 @@ members = [ "client/transaction-pool", "client/transaction-pool/api", "client/utils", + "frame/alliance", "frame/assets", "frame/atomic-swap", "frame/aura", "frame/authority-discovery", "frame/authorship", "frame/babe", + "frame/bags-list", + "frame/bags-list/fuzzer", + "frame/bags-list/remote-tests", "frame/balances", + "frame/beefy", + "frame/beefy-mmr", + "frame/beefy-mmr/primitives", "frame/benchmarking", "frame/bounties", + "frame/child-bounties", "frame/collective", "frame/contracts", - "frame/contracts/rpc", - "frame/contracts/rpc/runtime-api", + "frame/contracts/proc-macro", + "frame/contracts/primitives", + "frame/conviction-voting", "frame/democracy", + "frame/fast-unstake", "frame/try-runtime", - "frame/elections", + "frame/elections-phragmen", "frame/election-provider-multi-phase", "frame/election-provider-support", - "frame/example", - "frame/example-offchain-worker", - "frame/example-parallel", + "frame/election-provider-support/benchmarking", + "frame/election-provider-support/solution-type", + "frame/election-provider-support/solution-type/fuzzer", + "frame/examples/basic", + "frame/examples/offchain-worker", "frame/executive", - "frame/gilt", + "frame/nis", "frame/grandpa", "frame/identity", "frame/im-online", @@ -92,15 +114,24 @@ members = [ "frame/lottery", "frame/membership", "frame/merkle-mountain-range", - "frame/merkle-mountain-range/primitives", - "frame/merkle-mountain-range/rpc", "frame/multisig", "frame/nicks", "frame/node-authorization", "frame/offences", + "frame/offences/benchmarking", + "frame/preimage", "frame/proxy", + "frame/message-queue", + "frame/nomination-pools", + "frame/nomination-pools/fuzzer", + "frame/nomination-pools/benchmarking", + "frame/nomination-pools/test-staking", + "frame/nomination-pools/runtime-api", "frame/randomness-collective-flip", + "frame/ranked-collective", "frame/recovery", + "frame/referenda", + "frame/remark", "frame/scheduler", "frame/scored-pool", "frame/session", @@ -109,17 +140,23 @@ members = [ "frame/staking", "frame/staking/reward-curve", "frame/staking/reward-fn", + "frame/state-trie-migration", "frame/sudo", + "frame/root-offences", + "frame/root-testing", "frame/support", "frame/support/procedural", "frame/support/procedural/tools", "frame/support/procedural/tools/derive", "frame/support/test", + "frame/support/test/compile_pass", + "frame/support/test/pallet", "frame/system", "frame/system/benchmarking", "frame/system/rpc/runtime-api", "frame/timestamp", "frame/transaction-payment", + "frame/transaction-payment/asset-tx-payment", "frame/transaction-payment/rpc", "frame/transaction-payment/rpc/runtime-api", "frame/transaction-storage", @@ -128,6 +165,7 @@ members = [ "frame/uniques", "frame/utility", "frame/vesting", + "frame/whitelist", "primitives/api", "primitives/api/proc-macro", "primitives/api/test", @@ -137,14 +175,18 @@ members = [ "primitives/arithmetic/fuzzer", "primitives/authority-discovery", "primitives/authorship", + "primitives/beefy", "primitives/block-builder", "primitives/blockchain", "primitives/consensus/aura", "primitives/consensus/babe", "primitives/consensus/common", "primitives/consensus/pow", + "primitives/consensus/slots", "primitives/consensus/vrf", "primitives/core", + "primitives/core/hashing", + "primitives/core/hashing/proc-macro", "primitives/database", "primitives/debug-derive", "primitives/externalities", @@ -154,8 +196,8 @@ members = [ "primitives/keyring", "primitives/keystore", "primitives/maybe-compressed-blob", + "primitives/merkle-mountain-range", "primitives/npos-elections", - "primitives/npos-elections/solution-type", "primitives/npos-elections/fuzzer", "primitives/offchain", "primitives/panic-handler", @@ -166,14 +208,12 @@ members = [ "primitives/runtime-interface/test", "primitives/runtime-interface/test-wasm", "primitives/runtime-interface/test-wasm-deprecated", - "primitives/sandbox", "primitives/serializer", "primitives/session", "primitives/staking", "primitives/state-machine", "primitives/std", "primitives/storage", - "primitives/tasks", "primitives/test-primitives", "primitives/timestamp", "primitives/tracing", @@ -183,12 +223,13 @@ members = [ "primitives/version", "primitives/version/proc-macro", "primitives/wasm-interface", + "primitives/weights", + "test-utils", "test-utils/client", "test-utils/derive", "test-utils/runtime", "test-utils/runtime/client", "test-utils/runtime/transaction-pool", - "test-utils/test-runner", "test-utils/test-crate", "utils/build-script-utils", "utils/fork-tree", @@ -196,8 +237,12 @@ members = [ "utils/frame/remote-externalities", "utils/frame/frame-utilities-cli", "utils/frame/try-runtime/cli", + "utils/frame/rpc/state-trie-migration-rpc", "utils/frame/rpc/support", "utils/frame/rpc/system", + "utils/frame/generate-bags", + "utils/frame/generate-bags/node-runtime", + "utils/frame/rpc/client", "utils/prometheus", "utils/wasm-builder", ] @@ -219,7 +264,6 @@ members = [ # This list is ordered alphabetically. [profile.dev.package] blake2 = { opt-level = 3 } -blake2-rfc = { opt-level = 3 } blake2b_simd = { opt-level = 3 } chacha20poly1305 = { opt-level = 3 } cranelift-codegen = { opt-level = 3 } @@ -228,7 +272,7 @@ crc32fast = { opt-level = 3 } crossbeam-deque = { opt-level = 3 } crypto-mac = { opt-level = 3 } curve25519-dalek = { opt-level = 3 } -ed25519-dalek = { opt-level = 3 } +ed25519-zebra = { opt-level = 3 } flate2 = { opt-level = 3 } futures-channel = { opt-level = 3 } hashbrown = { opt-level = 3 } @@ -236,6 +280,7 @@ hash-db = { opt-level = 3 } hmac = { opt-level = 3 } httparse = { opt-level = 3 } integer-sqrt = { opt-level = 3 } +k256 = { opt-level = 3 } keccak = { opt-level = 3 } libm = { opt-level = 3 } librocksdb-sys = { opt-level = 3 } @@ -250,6 +295,7 @@ percent-encoding = { opt-level = 3 } primitive-types = { opt-level = 3 } ring = { opt-level = 3 } rustls = { opt-level = 3 } +secp256k1 = { opt-level = 3 } sha2 = { opt-level = 3 } sha3 = { opt-level = 3 } smallvec = { opt-level = 3 } @@ -260,6 +306,17 @@ wasmi = { opt-level = 3 } x25519-dalek = { opt-level = 3 } yamux = { opt-level = 3 } zeroize = { opt-level = 3 } + [profile.release] # Substrate runtime requires unwinding. -panic = "unwind" \ No newline at end of file +panic = "unwind" + +[profile.production] +inherits = "release" + +# Sacrifice compile speed for execution speed by using optimization flags: + +# https://doc.rust-lang.org/rustc/linker-plugin-lto.html +lto = "fat" +# https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units +codegen-units = 1 diff --git a/HEADER-APACHE2 b/HEADER-APACHE2 index f364f4bdf845a..58baa53894ca7 100644 --- a/HEADER-APACHE2 +++ b/HEADER-APACHE2 @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/HEADER-GPL3 b/HEADER-GPL3 index 0dd7e4f76028f..9412b5a70bb35 100644 --- a/HEADER-GPL3 +++ b/HEADER-GPL3 @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/Process.json b/Process.json deleted file mode 100644 index 540bd644311cf..0000000000000 --- a/Process.json +++ /dev/null @@ -1,29 +0,0 @@ -[{ - "project_name": "Networking", - "owner": "tomaka", - "matrix_room_id": "!vUADSGcyXmxhKLeDsW:matrix.parity.io" -}, -{ "project_name": "Client", - "owner": "gnunicorn", - "matrix_room_id": "!aenJixaHcSKbJOWxYk:matrix.parity.io" -}, -{ - "project_name": "Runtime", - "owner": "gavofyork", - "matrix_room_id": "!yBKstWVBkwzUkPslsp:matrix.parity.io" -}, -{ - "project_name": "Consensus", - "owner": "andresilva", - "matrix_room_id": "!XdNWDTfVNFVixljKZU:matrix.parity.io" -}, -{ - "project_name": "Smart Contracts", - "owner": "pepyakin", - "matrix_room_id": "!yBKstWVBkwzUkPslsp:matrix.parity.io" -}, -{ - "project_name": "Benchmarking and Weights", - "owner": "shawntabrizi", - "matrix_room_id": "!pZPWqCRLVtORZTEsEf:matrix.parity.io" -}] diff --git a/README.md b/README.md index 6288540548a0d..7d8c7e575581c 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,24 @@ -# Substrate · [![GitHub license](https://img.shields.io/badge/license-GPL3%2FApache2-blue)](#LICENSE) [![GitLab Status](https://gitlab.parity.io/parity/substrate/badges/master/pipeline.svg)](https://gitlab.parity.io/parity/substrate/pipelines) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](docs/CONTRIBUTING.adoc) [![Matrix](https://img.shields.io/matrix/substrate-technical:matrix.org)](https://matrix.to/#/#substrate-technical:matrix.org) - +# Substrate · [![GitHub license](https://img.shields.io/badge/license-GPL3%2FApache2-blue)](#LICENSE) [![GitLab Status](https://gitlab.parity.io/parity/substrate/badges/master/pipeline.svg)](https://gitlab.parity.io/parity/substrate/pipelines) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](docs/CONTRIBUTING.adoc) [![Stack Exchange](https://img.shields.io/badge/Substrate-Community%20&%20Support-24CC85?logo=stackexchange)](https://substrate.stackexchange.com/)

Substrate is a next-generation framework for blockchain innovation 🚀. -## Trying it out +## Getting Started + +Head to [docs.substrate.io](https://docs.substrate.io) and follow the [installation](https://docs.substrate.io/install/) instructions. +Then try out one of the [tutorials](https://docs.substrate.io/tutorials/). -Simply go to [substrate.dev](https://substrate.dev) and follow the -[installation](https://substrate.dev/docs/en/knowledgebase/getting-started/) instructions. You can -also try out one of the [tutorials](https://substrate.dev/en/tutorials). +## Community & Support + +Join the highly active and supportive community on the [Substrate Stack Exchange](https://substrate.stackexchange.com/) to ask questions about use and problems you run into using this software. +Please do report bugs and [issues here](https://github.com/paritytech/substrate/issues) for anything you suspect requires action in the source. ## Contributions & Code of Conduct -Please follow the contributions guidelines as outlined in [`docs/CONTRIBUTING.adoc`](docs/CONTRIBUTING.adoc). In all communications and contributions, this project follows the [Contributor Covenant Code of Conduct](docs/CODE_OF_CONDUCT.md). +Please follow the contributions guidelines as outlined in [`docs/CONTRIBUTING.adoc`](docs/CONTRIBUTING.adoc). +In all communications and contributions, this project follows the [Contributor Covenant Code of Conduct](docs/CODE_OF_CONDUCT.md). ## Security @@ -28,3 +32,4 @@ The security policy and procedures can be found in [`docs/SECURITY.md`](docs/SEC The reason for the split-licensing is to ensure that for the vast majority of teams using Substrate to create feature-chains, then all changes can be made entirely in Apache2-licensed code, allowing teams full freedom over what and how they release and giving licensing clarity to commercial teams. In the interests of the community, we require any deeper improvements made to Substrate's core logic (e.g. Substrate's internal consensus, crypto or database code) to be contributed back so everyone can benefit. + diff --git a/bin/node-template/README.md b/bin/node-template/README.md index cd977fac84493..0f6fd9450aeee 100644 --- a/bin/node-template/README.md +++ b/bin/node-template/README.md @@ -1,21 +1,32 @@ # Substrate Node Template +[![Try on playground](https://img.shields.io/badge/Playground-Node_Template-brightgreen?logo=Parity%20Substrate)](https://docs.substrate.io/playground/) [![Matrix](https://img.shields.io/matrix/substrate-technical:matrix.org)](https://matrix.to/#/#substrate-technical:matrix.org) + A fresh FRAME-based [Substrate](https://www.substrate.io/) node, ready for hacking :rocket: ## Getting Started -Follow these steps to get started with the Node Template :hammer_and_wrench: +Follow the steps below to get started with the Node Template, or get it up and running right from +your browser in just a few clicks using +the [Substrate Playground](https://docs.substrate.io/playground/) :hammer_and_wrench: + +### Using Nix + +Install [nix](https://nixos.org/) and optionally [direnv](https://github.com/direnv/direnv) and +[lorri](https://github.com/nix-community/lorri) for a fully plug and play experience for setting up +the development environment. To get all the correct dependencies activate direnv `direnv allow` and +lorri `lorri shell`. ### Rust Setup -First, complete the [basic Rust setup instructions](./doc/rust-setup.md). +First, complete the [basic Rust setup instructions](./docs/rust-setup.md). ### Run Use Rust's native `cargo` command to build and launch the template node: ```sh -cargo run --release -- --dev --tmp +cargo run --release -- --dev ``` ### Build @@ -44,7 +55,7 @@ node. ### Single-Node Development Chain -This command will start the single-node development chain with persistent state: +This command will start the single-node development chain with non-persistent state: ```bash ./target/release/node-template --dev @@ -62,10 +73,48 @@ Start the development chain with detailed logging: RUST_BACKTRACE=1 ./target/release/node-template -ldebug --dev ``` +> Development chain means that the state of our chain will be in a tmp folder while the nodes are +> running. Also, **alice** account will be authority and sudo account as declared in the +> [genesis state](https://github.com/substrate-developer-hub/substrate-node-template/blob/main/node/src/chain_spec.rs#L49). +> At the same time the following accounts will be pre-funded: +> - Alice +> - Bob +> - Alice//stash +> - Bob//stash + +In case of being interested in maintaining the chain' state between runs a base path must be added +so the db can be stored in the provided folder instead of a temporal one. We could use this folder +to store different chain databases, as a different folder will be created per different chain that +is ran. The following commands shows how to use a newly created folder as our db base path. + +```bash +// Create a folder to use as the db base path +$ mkdir my-chain-state + +// Use of that folder to store the chain state +$ ./target/release/node-template --dev --base-path ./my-chain-state/ + +// Check the folder structure created inside the base path after running the chain +$ ls ./my-chain-state +chains +$ ls ./my-chain-state/chains/ +dev +$ ls ./my-chain-state/chains/dev +db keystore network +``` + + +### Connect with Polkadot-JS Apps Front-end + +Once the node template is running locally, you can connect it with **Polkadot-JS Apps** front-end +to interact with your chain. [Click +here](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) connecting the Apps to your +local node template. + ### Multi-Node Local Testnet -If you want to see the multi-node consensus algorithm in action, refer to -[our Start a Private Network tutorial](https://substrate.dev/docs/en/tutorials/start-a-private-network/). +If you want to see the multi-node consensus algorithm in action, refer to our +[Simulate a network tutorial](https://docs.substrate.io/tutorials/get-started/simulate-network/). ## Template Structure @@ -77,34 +126,32 @@ directories. A blockchain node is an application that allows users to participate in a blockchain network. Substrate-based blockchain nodes expose a number of capabilities: -- Networking: Substrate nodes use the [`libp2p`](https://libp2p.io/) networking stack to allow the - nodes in the network to communicate with one another. -- Consensus: Blockchains must have a way to come to - [consensus](https://substrate.dev/docs/en/knowledgebase/advanced/consensus) on the state of the - network. Substrate makes it possible to supply custom consensus engines and also ships with - several consensus mechanisms that have been built on top of - [Web3 Foundation research](https://research.web3.foundation/en/latest/polkadot/NPoS/index.html). -- RPC Server: A remote procedure call (RPC) server is used to interact with Substrate nodes. +- Networking: Substrate nodes use the [`libp2p`](https://libp2p.io/) networking stack to allow the + nodes in the network to communicate with one another. +- Consensus: Blockchains must have a way to come to + [consensus](https://docs.substrate.io/main-docs/fundamentals/consensus/) on the state of the + network. Substrate makes it possible to supply custom consensus engines and also ships with + several consensus mechanisms that have been built on top of + [Web3 Foundation research](https://research.web3.foundation/en/latest/polkadot/NPoS/index.html). +- RPC Server: A remote procedure call (RPC) server is used to interact with Substrate nodes. There are several files in the `node` directory - take special note of the following: -- [`chain_spec.rs`](./node/src/chain_spec.rs): A - [chain specification](https://substrate.dev/docs/en/knowledgebase/integrate/chain-spec) is a - source code file that defines a Substrate chain's initial (genesis) state. Chain specifications - are useful for development and testing, and critical when architecting the launch of a - production chain. Take note of the `development_config` and `testnet_genesis` functions, which - are used to define the genesis state for the local development chain configuration. These - functions identify some - [well-known accounts](https://substrate.dev/docs/en/knowledgebase/integrate/subkey#well-known-keys) - and use them to configure the blockchain's initial state. -- [`service.rs`](./node/src/service.rs): This file defines the node implementation. Take note of - the libraries that this file imports and the names of the functions it invokes. In particular, - there are references to consensus-related topics, such as the - [longest chain rule](https://substrate.dev/docs/en/knowledgebase/advanced/consensus#longest-chain-rule), - the [Aura](https://substrate.dev/docs/en/knowledgebase/advanced/consensus#aura) block authoring - mechanism and the - [GRANDPA](https://substrate.dev/docs/en/knowledgebase/advanced/consensus#grandpa) finality - gadget. +- [`chain_spec.rs`](./node/src/chain_spec.rs): A + [chain specification](https://docs.substrate.io/main-docs/build/chain-spec/) is a + source code file that defines a Substrate chain's initial (genesis) state. Chain specifications + are useful for development and testing, and critical when architecting the launch of a + production chain. Take note of the `development_config` and `testnet_genesis` functions, which + are used to define the genesis state for the local development chain configuration. These + functions identify some + [well-known accounts](https://docs.substrate.io/reference/command-line-tools/subkey/) + and use them to configure the blockchain's initial state. +- [`service.rs`](./node/src/service.rs): This file defines the node implementation. Take note of + the libraries that this file imports and the names of the functions it invokes. In particular, + there are references to consensus-related topics, such as the + [block finalization and forks](https://docs.substrate.io/main-docs/fundamentals/consensus/#finalization-and-forks) + and other [consensus mechanisms](https://docs.substrate.io/main-docs/fundamentals/consensus/#default-consensus-models) + such as Aura for block authoring and GRANDPA for finality. After the node has been [built](#build), refer to the embedded documentation to learn more about the capabilities and configuration parameters that it exposes: @@ -116,27 +163,25 @@ capabilities and configuration parameters that it exposes: ### Runtime In Substrate, the terms -"[runtime](https://substrate.dev/docs/en/knowledgebase/getting-started/glossary#runtime)" and -"[state transition function](https://substrate.dev/docs/en/knowledgebase/getting-started/glossary#stf-state-transition-function)" +"runtime" and "state transition function" are analogous - they refer to the core logic of the blockchain that is responsible for validating blocks and executing the state changes they define. The Substrate project in this repository uses -the [FRAME](https://substrate.dev/docs/en/knowledgebase/runtime/frame) framework to construct a +[FRAME](https://docs.substrate.io/main-docs/fundamentals/runtime-intro/#frame) to construct a blockchain runtime. FRAME allows runtime developers to declare domain-specific logic in modules called "pallets". At the heart of FRAME is a helpful -[macro language](https://substrate.dev/docs/en/knowledgebase/runtime/macros) that makes it easy to +[macro language](https://docs.substrate.io/reference/frame-macros/) that makes it easy to create pallets and flexibly compose them to create blockchains that can address -[a variety of needs](https://www.substrate.io/substrate-users/). +[a variety of needs](https://substrate.io/ecosystem/projects/). Review the [FRAME runtime implementation](./runtime/src/lib.rs) included in this template and note the following: -- This file configures several pallets to include in the runtime. Each pallet configuration is - defined by a code block that begins with `impl $PALLET_NAME::Config for Runtime`. -- The pallets are composed into a single runtime by way of the - [`construct_runtime!`](https://crates.parity.io/frame_support/macro.construct_runtime.html) - macro, which is part of the core - [FRAME Support](https://substrate.dev/docs/en/knowledgebase/runtime/frame#support-library) - library. +- This file configures several pallets to include in the runtime. Each pallet configuration is + defined by a code block that begins with `impl $PALLET_NAME::Config for Runtime`. +- The pallets are composed into a single runtime by way of the + [`construct_runtime!`](https://crates.parity.io/frame_support/macro.construct_runtime.html) + macro, which is part of the core + FRAME Support [system](https://docs.substrate.io/reference/frame-pallets/#system-pallets) library. ### Pallets @@ -146,17 +191,17 @@ template pallet that is [defined in the `pallets`](./pallets/template/src/lib.rs A FRAME pallet is compromised of a number of blockchain primitives: -- Storage: FRAME defines a rich set of powerful - [storage abstractions](https://substrate.dev/docs/en/knowledgebase/runtime/storage) that makes - it easy to use Substrate's efficient key-value database to manage the evolving state of a - blockchain. -- Dispatchables: FRAME pallets define special types of functions that can be invoked (dispatched) - from outside of the runtime in order to update its state. -- Events: Substrate uses [events](https://substrate.dev/docs/en/knowledgebase/runtime/events) to - notify users of important changes in the runtime. -- Errors: When a dispatchable fails, it returns an error. -- Config: The `Config` configuration interface is used to define the types and parameters upon - which a FRAME pallet depends. +- Storage: FRAME defines a rich set of powerful + [storage abstractions](https://docs.substrate.io/main-docs/build/runtime-storage/) that makes + it easy to use Substrate's efficient key-value database to manage the evolving state of a + blockchain. +- Dispatchables: FRAME pallets define special types of functions that can be invoked (dispatched) + from outside of the runtime in order to update its state. +- Events: Substrate uses [events and errors](https://docs.substrate.io/main-docs/build/events-errors/) + to notify users of important changes in the runtime. +- Errors: When a dispatchable fails, it returns an error. +- Config: The `Config` configuration interface is used to define the types and parameters upon + which a FRAME pallet depends. ### Run in Docker @@ -170,7 +215,8 @@ Then run the following command to start a single node development chain. ``` This command will firstly compile your code, and then start a local development network. You can -also replace the default command (`cargo build --release && ./target/release/node-template --dev --ws-external`) +also replace the default command +(`cargo build --release && ./target/release/node-template --dev --ws-external`) by appending your own. A few useful ones are as follow. ```bash diff --git a/bin/node-template/docker-compose.yml b/bin/node-template/docker-compose.yml index cfc4437bbae41..bc1922f47d963 100644 --- a/bin/node-template/docker-compose.yml +++ b/bin/node-template/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.2" services: dev: container_name: node-template - image: paritytech/ci-linux:974ba3ac-20201006 + image: paritytech/ci-linux:production working_dir: /var/www/node-template ports: - "9944:9944" diff --git a/bin/node-template/docs/rust-setup.md b/bin/node-template/docs/rust-setup.md index 34f6e43e7f0dd..2755966e3ae0f 100644 --- a/bin/node-template/docs/rust-setup.md +++ b/bin/node-template/docs/rust-setup.md @@ -2,31 +2,21 @@ title: Installation --- -This page will guide you through the steps needed to prepare a computer for development with the -Substrate Node Template. Since Substrate is built with -[the Rust programming language](https://www.rust-lang.org/), the first thing you will need to do is -prepare the computer for Rust development - these steps will vary based on the computer's operating -system. Once Rust is configured, you will use its toolchains to interact with Rust projects; the -commands for Rust's toolchains will be the same for all supported, Unix-based operating systems. +This guide is for reference only, please check the latest information on getting starting with Substrate +[here](https://docs.substrate.io/main-docs/install/). -## Unix-Based Operating Systems +This page will guide you through the **2 steps** needed to prepare a computer for **Substrate** development. +Since Substrate is built with [the Rust programming language](https://www.rust-lang.org/), the first +thing you will need to do is prepare the computer for Rust development - these steps will vary based +on the computer's operating system. Once Rust is configured, you will use its toolchains to interact +with Rust projects; the commands for Rust's toolchains will be the same for all supported, +Unix-based operating systems. -Substrate development is easiest on Unix-based operating systems like macOS or Linux. The examples -in the Substrate [Tutorials](https://substrate.dev/tutorials) and [Recipes](https://substrate.dev/recipes/) -use Unix-style terminals to demonstrate how to interact with Substrate from the command line. - -### macOS +## Build dependencies -Open the Terminal application and execute the following commands: - -```bash -# Install Homebrew if necessary https://brew.sh/ -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" - -# Make sure Homebrew is up-to-date, install openssl and cmake -brew update -brew install openssl cmake -``` +Substrate development is easiest on Unix-based operating systems like macOS or Linux. The examples +in the [Substrate Docs](https://docs.substrate.io) use Unix-style terminals to demonstrate how to +interact with Substrate from the command line. ### Ubuntu/Debian @@ -35,7 +25,7 @@ Use a terminal shell to execute the following commands: ```bash sudo apt update # May prompt for location information -sudo apt install -y cmake pkg-config libssl-dev git build-essential clang libclang-dev curl +sudo apt install -y git clang curl libssl-dev llvm libudev-dev ``` ### Arch Linux @@ -43,39 +33,193 @@ sudo apt install -y cmake pkg-config libssl-dev git build-essential clang libcla Run these commands from a terminal: ```bash -pacman -Syu --needed --noconfirm cmake gcc openssl-1.0 pkgconf git clang -export OPENSSL_LIB_DIR="/usr/lib/openssl-1.0" -export OPENSSL_INCLUDE_DIR="/usr/include/openssl-1.0" +pacman -Syu --needed --noconfirm curl git clang ``` -### Fedora/RHEL/CentOS +### Fedora -Use a terminal to run the following commands: +Run these commands from a terminal: ```bash -# Update sudo dnf update -# Install packages -sudo dnf install cmake pkgconfig rocksdb rocksdb-devel llvm git libcurl libcurl-devel curl-devel clang +sudo dnf install clang curl git openssl-devel +``` + +### OpenSUSE + +Run these commands from a terminal: + +```bash +sudo zypper install clang curl git openssl-devel llvm-devel libudev-devel +``` + +### macOS + +> **Apple M1 ARM** +> If you have an Apple M1 ARM system on a chip, make sure that you have Apple Rosetta 2 +> installed through `softwareupdate --install-rosetta`. This is only needed to run the +> `protoc` tool during the build. The build itself and the target binaries would remain native. + +Open the Terminal application and execute the following commands: + +```bash +# Install Homebrew if necessary https://brew.sh/ +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" + +# Make sure Homebrew is up-to-date, install openssl +brew update +brew install openssl ``` -## Rust Developer Environment +### Windows + +**_PLEASE NOTE:_** Native Windows development of Substrate is _not_ very well supported! It is _highly_ +recommend to use [Windows Subsystem Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10) +(WSL) and follow the instructions for [Ubuntu/Debian](#ubuntudebian). +Please refer to the separate +[guide for native Windows development](https://docs.substrate.io/main-docs/install/windows/). -This project uses [`rustup`](https://rustup.rs/) to help manage the Rust toolchain. First install -and configure `rustup`: +## Rust developer environment + +This guide uses installer and the `rustup` tool to manage the Rust toolchain. +First install and configure `rustup`: ```bash # Install -curl https://sh.rustup.rs -sSf | sh +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Configure source ~/.cargo/env ``` -Finally, configure the Rust toolchain: +Configure the Rust toolchain to default to the latest stable version, add nightly and the nightly wasm target: ```bash rustup default stable +rustup update +rustup update nightly +rustup target add wasm32-unknown-unknown --toolchain nightly +``` + +## Test your set-up + +Now the best way to ensure that you have successfully prepared a computer for Substrate +development is to follow the steps in [our first Substrate tutorial](https://docs.substrate.io/tutorials/v3/create-your-first-substrate-chain/). + +## Troubleshooting Substrate builds + +Sometimes you can't get the Substrate node template +to compile out of the box. Here are some tips to help you work through that. + +### Rust configuration check + +To see what Rust toolchain you are presently using, run: + +```bash +rustup show +``` + +This will show something like this (Ubuntu example) output: + +```text +Default host: x86_64-unknown-linux-gnu +rustup home: /home/user/.rustup + +installed toolchains +-------------------- + +stable-x86_64-unknown-linux-gnu (default) +nightly-2020-10-06-x86_64-unknown-linux-gnu +nightly-x86_64-unknown-linux-gnu + +installed targets for active toolchain +-------------------------------------- + +wasm32-unknown-unknown +x86_64-unknown-linux-gnu + +active toolchain +---------------- + +stable-x86_64-unknown-linux-gnu (default) +rustc 1.50.0 (cb75ad5db 2021-02-10) +``` + +As you can see above, the default toolchain is stable, and the +`nightly-x86_64-unknown-linux-gnu` toolchain as well as its `wasm32-unknown-unknown` target is installed. +You also see that `nightly-2020-10-06-x86_64-unknown-linux-gnu` is installed, but is not used unless explicitly defined as illustrated in the [specify your nightly version](#specifying-nightly-version) +section. + +### WebAssembly compilation + +Substrate uses [WebAssembly](https://webassembly.org) (Wasm) to produce portable blockchain +runtimes. You will need to configure your Rust compiler to use +[`nightly` builds](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html) to allow you to +compile Substrate runtime code to the Wasm target. + +> There are upstream issues in Rust that need to be resolved before all of Substrate can use the stable Rust toolchain. +> [This is our tracking issue](https://github.com/paritytech/substrate/issues/1252) if you're curious as to why and how this will be resolved. + +#### Latest nightly for Substrate `master` + +Developers who are building Substrate _itself_ should always use the latest bug-free versions of +Rust stable and nightly. This is because the Substrate codebase follows the tip of Rust nightly, +which means that changes in Substrate often depend on upstream changes in the Rust nightly compiler. +To ensure your Rust compiler is always up to date, you should run: + +```bash +rustup update rustup update nightly -rustup update stable rustup target add wasm32-unknown-unknown --toolchain nightly ``` + +> NOTE: It may be necessary to occasionally rerun `rustup update` if a change in the upstream Substrate +> codebase depends on a new feature of the Rust compiler. When you do this, both your nightly +> and stable toolchains will be pulled to the most recent release, and for nightly, it is +> generally _not_ expected to compile WASM without error (although it very often does). +> Be sure to [specify your nightly version](#specifying-nightly-version) if you get WASM build errors +> from `rustup` and [downgrade nightly as needed](#downgrading-rust-nightly). + +#### Rust nightly toolchain + +If you want to guarantee that your build works on your computer as you update Rust and other +dependencies, you should use a specific Rust nightly version that is known to be +compatible with the version of Substrate they are using; this version will vary from project to +project and different projects may use different mechanisms to communicate this version to +developers. For instance, the Polkadot client specifies this information in its +[release notes](https://github.com/paritytech/polkadot/releases). + +```bash +# Specify the specific nightly toolchain in the date below: +rustup install nightly- +``` + +#### Wasm toolchain + +Now, configure the nightly version to work with the Wasm compilation target: + +```bash +rustup target add wasm32-unknown-unknown --toolchain nightly- +``` + +### Specifying nightly version + +Use the `WASM_BUILD_TOOLCHAIN` environment variable to specify the Rust nightly version a Substrate +project should use for Wasm compilation: + +```bash +WASM_BUILD_TOOLCHAIN=nightly- cargo build --release +``` + +> Note that this only builds _the runtime_ with the specified nightly. The rest of project will be +> compiled with **your default toolchain**, i.e. the latest installed stable toolchain. + +### Downgrading Rust nightly + +If your computer is configured to use the latest Rust nightly and you would like to downgrade to a +specific nightly version, follow these steps: + +```bash +rustup uninstall nightly +rustup install nightly- +rustup target add wasm32-unknown-unknown --toolchain nightly- +``` diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index 04d70b338ac04..2ea841093d0e2 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "node-template" -version = "3.0.0" -authors = ["Substrate DevHub "] +version = "4.0.0-dev" description = "A fresh FRAME-based Substrate node, ready for hacking." -edition = "2018" +authors = ["Substrate DevHub "] +homepage = "https://substrate.io/" +edition = "2021" license = "Unlicense" -build = "build.rs" -homepage = "https://substrate.dev" -repository = "https://github.com/substrate-developer-hub/substrate-node-template/" publish = false +repository = "https://github.com/substrate-developer-hub/substrate-node-template/" +build = "build.rs" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,12 +17,13 @@ targets = ["x86_64-unknown-linux-gnu"] name = "node-template" [dependencies] -structopt = "0.3.8" +clap = { version = "4.0.9", features = ["derive"] } +futures = { version = "0.3.21", features = ["thread-pool"]} -sc-cli = { version = "0.10.0-dev", path = "../../../client/cli", features = ["wasmtime"] } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sc-executor = { version = "0.10.0-dev", path = "../../../client/executor", features = ["wasmtime"] } -sc-service = { version = "0.10.0-dev", path = "../../../client/service", features = ["wasmtime"] } +sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } +sc-service = { version = "0.10.0-dev", path = "../../../client/service" } sc-telemetry = { version = "4.0.0-dev", path = "../../../client/telemetry" } sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } @@ -34,11 +35,15 @@ sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/commo sc-finality-grandpa = { version = "0.10.0-dev", path = "../../../client/finality-grandpa" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } +frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } +pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } # These dependencies are used for the node template's RPCs -jsonrpc-core = "18.0.0" +jsonrpsee = { version = "0.16.2", features = ["server"] } sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } @@ -52,13 +57,23 @@ pallet-transaction-payment-rpc = { version = "4.0.0-dev", path = "../../../frame frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } frame-benchmarking-cli = { version = "4.0.0-dev", path = "../../../utils/frame/benchmarking-cli" } -node-template-runtime = { version = "3.0.0", path = "../runtime" } +# Local Dependencies +node-template-runtime = { version = "4.0.0-dev", path = "../runtime" } + +# CLI-specific dependencies +try-runtime-cli = { version = "0.10.0-dev", optional = true, path = "../../../utils/frame/try-runtime/cli" } [build-dependencies] substrate-build-script-utils = { version = "3.0.0", path = "../../../utils/build-script-utils" } [features] default = [] +# Dependencies that are only required if runtime benchmarking should be build. runtime-benchmarks = [ "node-template-runtime/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-benchmarking-cli/runtime-benchmarks", ] +# Enable features that allow the runtime to be tried and debugged. Name might be subject to change +# in the near future. +try-runtime = ["node-template-runtime/try-runtime", "try-runtime-cli/try-runtime"] diff --git a/bin/node-template/node/src/benchmarking.rs b/bin/node-template/node/src/benchmarking.rs new file mode 100644 index 0000000000000..480e547c9c73c --- /dev/null +++ b/bin/node-template/node/src/benchmarking.rs @@ -0,0 +1,182 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Setup code for [`super::command`] which would otherwise bloat that module. +//! +//! Should only be used for benchmarking as it may break in other contexts. + +use crate::service::FullClient; + +use node_template_runtime as runtime; +use runtime::{AccountId, Balance, BalancesCall, SystemCall}; +use sc_cli::Result; +use sc_client_api::BlockBackend; +use sp_core::{Encode, Pair}; +use sp_inherents::{InherentData, InherentDataProvider}; +use sp_keyring::Sr25519Keyring; +use sp_runtime::{OpaqueExtrinsic, SaturatedConversion}; + +use std::{sync::Arc, time::Duration}; + +/// Generates extrinsics for the `benchmark overhead` command. +/// +/// Note: Should only be used for benchmarking. +pub struct RemarkBuilder { + client: Arc, +} + +impl RemarkBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc) -> Self { + Self { client } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder { + fn pallet(&self) -> &str { + "system" + } + + fn extrinsic(&self) -> &str { + "remark" + } + + fn build(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( + self.client.as_ref(), + acc, + SystemCall::remark { remark: vec![] }.into(), + nonce, + ) + .into(); + + Ok(extrinsic) + } +} + +/// Generates `Balances::TransferKeepAlive` extrinsics for the benchmarks. +/// +/// Note: Should only be used for benchmarking. +pub struct TransferKeepAliveBuilder { + client: Arc, + dest: AccountId, + value: Balance, +} + +impl TransferKeepAliveBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc, dest: AccountId, value: Balance) -> Self { + Self { client, dest, value } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { + fn pallet(&self) -> &str { + "balances" + } + + fn extrinsic(&self) -> &str { + "transfer_keep_alive" + } + + fn build(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( + self.client.as_ref(), + acc, + BalancesCall::transfer_keep_alive { + dest: self.dest.clone().into(), + value: self.value.into(), + } + .into(), + nonce, + ) + .into(); + + Ok(extrinsic) + } +} + +/// Create a transaction using the given `call`. +/// +/// Note: Should only be used for benchmarking. +pub fn create_benchmark_extrinsic( + client: &FullClient, + sender: sp_core::sr25519::Pair, + call: runtime::RuntimeCall, + nonce: u32, +) -> runtime::UncheckedExtrinsic { + let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); + let best_hash = client.chain_info().best_hash; + let best_block = client.chain_info().best_number; + + let period = runtime::BlockHashCount::get() + .checked_next_power_of_two() + .map(|c| c / 2) + .unwrap_or(2) as u64; + let extra: runtime::SignedExtra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(sp_runtime::generic::Era::mortal( + period, + best_block.saturated_into(), + )), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ); + + let raw_payload = runtime::SignedPayload::from_raw( + call.clone(), + extra.clone(), + ( + (), + runtime::VERSION.spec_version, + runtime::VERSION.transaction_version, + genesis_hash, + best_hash, + (), + (), + (), + ), + ); + let signature = raw_payload.using_encoded(|e| sender.sign(e)); + + runtime::UncheckedExtrinsic::new_signed( + call.clone(), + sp_runtime::AccountId32::from(sender.public()).into(), + runtime::Signature::Sr25519(signature.clone()), + extra.clone(), + ) +} + +/// Generates inherent data for the `benchmark overhead` command. +/// +/// Note: Should only be used for benchmarking. +pub fn inherent_benchmark_data() -> Result { + let mut inherent_data = InherentData::new(); + let d = Duration::from_millis(0); + let timestamp = sp_timestamp::InherentDataProvider::new(d.into()); + + futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data)) + .map_err(|e| format!("creating inherent data: {:?}", e))?; + Ok(inherent_data) +} diff --git a/bin/node-template/node/src/chain_spec.rs b/bin/node-template/node/src/chain_spec.rs index 7009b3be5c279..ef34ec369a77f 100644 --- a/bin/node-template/node/src/chain_spec.rs +++ b/bin/node-template/node/src/chain_spec.rs @@ -68,6 +68,7 @@ pub fn development_config() -> Result { None, // Protocol ID None, + None, // Properties None, // Extensions @@ -117,6 +118,7 @@ pub fn local_testnet_config() -> Result { None, // Properties None, + None, // Extensions None, )) @@ -134,7 +136,6 @@ fn testnet_genesis( system: SystemConfig { // Add Wasm runtime to storage. code: wasm_binary.to_vec(), - changes_trie_config: Default::default(), }, balances: BalancesConfig { // Configure endowed accounts with initial balance of 1 << 60. @@ -148,7 +149,8 @@ fn testnet_genesis( }, sudo: SudoConfig { // Assign network admin rights. - key: root_key, + key: Some(root_key), }, + transaction_payment: Default::default(), } } diff --git a/bin/node-template/node/src/cli.rs b/bin/node-template/node/src/cli.rs index 8b551051c1b19..dd610477ac469 100644 --- a/bin/node-template/node/src/cli.rs +++ b/bin/node-template/node/src/cli.rs @@ -1,19 +1,20 @@ use sc_cli::RunCmd; -use structopt::StructOpt; -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Parser)] pub struct Cli { - #[structopt(subcommand)] + #[command(subcommand)] pub subcommand: Option, - #[structopt(flatten)] + #[clap(flatten)] pub run: RunCmd, } -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Subcommand)] pub enum Subcommand { /// Key management cli utilities + #[command(subcommand)] Key(sc_cli::KeySubcommand), + /// Build a chain specification. BuildSpec(sc_cli::BuildSpecCmd), @@ -35,7 +36,18 @@ pub enum Subcommand { /// Revert the chain to a previous state. Revert(sc_cli::RevertCmd), - /// The custom benchmark subcommmand benchmarking runtime pallets. - #[structopt(name = "benchmark", about = "Benchmark runtime pallets.")] + /// Sub-commands concerned with benchmarking. + #[command(subcommand)] Benchmark(frame_benchmarking_cli::BenchmarkCmd), + + /// Try some command against runtime state. + #[cfg(feature = "try-runtime")] + TryRuntime(try_runtime_cli::TryRuntimeCmd), + + /// Try some command against runtime state. Note: `try-runtime` feature must be enabled. + #[cfg(not(feature = "try-runtime"))] + TryRuntime, + + /// Db meta columns information. + ChainInfo(sc_cli::ChainInfoCmd), } diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index e948c3f53b716..6d293b7b85fcc 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -1,11 +1,14 @@ use crate::{ + benchmarking::{inherent_benchmark_data, RemarkBuilder, TransferKeepAliveBuilder}, chain_spec, cli::{Cli, Subcommand}, service, }; -use node_template_runtime::Block; -use sc_cli::{ChainSpec, Role, RuntimeVersion, SubstrateCli}; +use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE}; +use node_template_runtime::{Block, EXISTENTIAL_DEPOSIT}; +use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli}; use sc_service::PartialComponents; +use sp_keyring::Sr25519Keyring; impl SubstrateCli for Cli { fn impl_name() -> String { @@ -95,27 +98,105 @@ pub fn run() -> sc_cli::Result<()> { runner.async_run(|config| { let PartialComponents { client, task_manager, backend, .. } = service::new_partial(&config)?; - Ok((cmd.run(client, backend), task_manager)) + let aux_revert = Box::new(|client, _, blocks| { + sc_finality_grandpa::revert(client, blocks)?; + Ok(()) + }); + Ok((cmd.run(client, backend, Some(aux_revert)), task_manager)) }) }, - Some(Subcommand::Benchmark(cmd)) => - if cfg!(feature = "runtime-benchmarks") { - let runner = cli.create_runner(cmd)?; + Some(Subcommand::Benchmark(cmd)) => { + let runner = cli.create_runner(cmd)?; + + runner.sync_run(|config| { + // This switch needs to be in the client, since the client decides + // which sub-commands it wants to support. + match cmd { + BenchmarkCmd::Pallet(cmd) => { + if !cfg!(feature = "runtime-benchmarks") { + return Err( + "Runtime benchmarking wasn't enabled when building the node. \ + You can enable it with `--features runtime-benchmarks`." + .into(), + ) + } + + cmd.run::(config) + }, + BenchmarkCmd::Block(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; + cmd.run(client) + }, + #[cfg(not(feature = "runtime-benchmarks"))] + BenchmarkCmd::Storage(_) => Err( + "Storage benchmarking can be enabled with `--features runtime-benchmarks`." + .into(), + ), + #[cfg(feature = "runtime-benchmarks")] + BenchmarkCmd::Storage(cmd) => { + let PartialComponents { client, backend, .. } = + service::new_partial(&config)?; + let db = backend.expose_db(); + let storage = backend.expose_storage(); + + cmd.run(config, client, db, storage) + }, + BenchmarkCmd::Overhead(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; + let ext_builder = RemarkBuilder::new(client.clone()); - runner.sync_run(|config| cmd.run::(config)) - } else { - Err("Benchmarking wasn't enabled when building the node. You can enable it with \ - `--features runtime-benchmarks`." - .into()) - }, + cmd.run( + config, + client, + inherent_benchmark_data()?, + Vec::new(), + &ext_builder, + ) + }, + BenchmarkCmd::Extrinsic(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; + // Register the *Remark* and *TKA* builders. + let ext_factory = ExtrinsicFactory(vec![ + Box::new(RemarkBuilder::new(client.clone())), + Box::new(TransferKeepAliveBuilder::new( + client.clone(), + Sr25519Keyring::Alice.to_account_id(), + EXISTENTIAL_DEPOSIT, + )), + ]); + + cmd.run(client, inherent_benchmark_data()?, Vec::new(), &ext_factory) + }, + BenchmarkCmd::Machine(cmd) => + cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()), + } + }) + }, + #[cfg(feature = "try-runtime")] + Some(Subcommand::TryRuntime(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + // we don't need any of the components of new_partial, just a runtime, or a task + // manager to do `async_run`. + let registry = config.prometheus_config.as_ref().map(|cfg| &cfg.registry); + let task_manager = + sc_service::TaskManager::new(config.tokio_handle.clone(), registry) + .map_err(|e| sc_cli::Error::Service(sc_service::Error::Prometheus(e)))?; + Ok((cmd.run::(config), task_manager)) + }) + }, + #[cfg(not(feature = "try-runtime"))] + Some(Subcommand::TryRuntime) => Err("TryRuntime wasn't enabled when building the node. \ + You can enable it with `--features try-runtime`." + .into()), + Some(Subcommand::ChainInfo(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run::(&config)) + }, None => { let runner = cli.create_runner(&cli.run)?; runner.run_node_until_exit(|config| async move { - match config.role { - Role::Light => service::new_light(config), - _ => service::new_full(config), - } - .map_err(sc_cli::Error::Service) + service::new_full(config).map_err(sc_cli::Error::Service) }) }, } diff --git a/bin/node-template/node/src/main.rs b/bin/node-template/node/src/main.rs index 4449d28b9fa41..426cbabb6fbf7 100644 --- a/bin/node-template/node/src/main.rs +++ b/bin/node-template/node/src/main.rs @@ -4,6 +4,7 @@ mod chain_spec; #[macro_use] mod service; +mod benchmarking; mod cli; mod command; mod rpc; diff --git a/bin/node-template/node/src/rpc.rs b/bin/node-template/node/src/rpc.rs index d23b23178ec2a..981f375d0b462 100644 --- a/bin/node-template/node/src/rpc.rs +++ b/bin/node-template/node/src/rpc.rs @@ -7,13 +7,15 @@ use std::sync::Arc; +use jsonrpsee::RpcModule; use node_template_runtime::{opaque::Block, AccountId, Balance, Index}; -pub use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; +pub use sc_rpc_api::DenyUnsafe; + /// Full client dependencies. pub struct FullDeps { /// The client instance to use. @@ -25,7 +27,9 @@ pub struct FullDeps { } /// Instantiate all full RPC extensions. -pub fn create_full(deps: FullDeps) -> jsonrpc_core::IoHandler +pub fn create_full( + deps: FullDeps, +) -> Result, Box> where C: ProvideRuntimeApi, C: HeaderBackend + HeaderMetadata + 'static, @@ -35,20 +39,19 @@ where C::Api: BlockBuilder, P: TransactionPool + 'static, { - use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; - use substrate_frame_rpc_system::{FullSystem, SystemApi}; + use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; + use substrate_frame_rpc_system::{System, SystemApiServer}; - let mut io = jsonrpc_core::IoHandler::default(); + let mut module = RpcModule::new(()); let FullDeps { client, pool, deny_unsafe } = deps; - io.extend_with(SystemApi::to_delegate(FullSystem::new(client.clone(), pool, deny_unsafe))); - - io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(client.clone()))); + module.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; + module.merge(TransactionPayment::new(client).into_rpc())?; // Extend this RPC with a custom API by using the following syntax. // `YourRpcStruct` should have a reference to a client, which is needed // to call into the runtime. - // `io.extend_with(YourRpcTrait::to_delegate(YourRpcStruct::new(ReferenceToClient, ...)));` + // `module.merge(YourRpcTrait::into_rpc(YourRpcStruct::new(ReferenceToClient, ...)))?;` - io + Ok(module) } diff --git a/bin/node-template/node/src/service.rs b/bin/node-template/node/src/service.rs index 0f09ef436acad..ee8464688c79c 100644 --- a/bin/node-template/node/src/service.rs +++ b/bin/node-template/node/src/service.rs @@ -1,14 +1,13 @@ //! Service and ServiceFactory implementation. Specialized wrapper over substrate service. use node_template_runtime::{self, opaque::Block, RuntimeApi}; -use sc_client_api::{ExecutorProvider, RemoteBackend}; +use sc_client_api::BlockBackend; use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams}; pub use sc_executor::NativeElseWasmExecutor; use sc_finality_grandpa::SharedVoterState; use sc_keystore::LocalKeystore; use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; use sc_telemetry::{Telemetry, TelemetryWorker}; -use sp_consensus::SlotData; use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; use std::{sync::Arc, time::Duration}; @@ -16,7 +15,12 @@ use std::{sync::Arc, time::Duration}; pub struct ExecutorDispatch; impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { + /// Only enable the benchmarking host functions when we actually want to benchmark. + #[cfg(feature = "runtime-benchmarks")] type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + /// Otherwise we only use the default Substrate host functions. + #[cfg(not(feature = "runtime-benchmarks"))] + type ExtendHostFunctions = (); fn dispatch(method: &str, data: &[u8]) -> Option> { node_template_runtime::api::dispatch(method, data) @@ -27,7 +31,7 @@ impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { } } -type FullClient = +pub(crate) type FullClient = sc_service::TFullClient>; type FullBackend = sc_service::TFullBackend; type FullSelectChain = sc_consensus::LongestChain; @@ -55,7 +59,7 @@ pub fn new_partial( ServiceError, > { if config.keystore_remote.is_some() { - return Err(ServiceError::Other(format!("Remote Keystores are not supported."))) + return Err(ServiceError::Other("Remote Keystores are not supported.".into())) } let telemetry = config @@ -73,18 +77,19 @@ pub fn new_partial( config.wasm_method, config.default_heap_pages, config.max_runtime_instances, + config.runtime_cache_size, ); let (client, backend, keystore_container, task_manager) = sc_service::new_full_parts::( - &config, + config, telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), executor, )?; let client = Arc::new(client); let telemetry = telemetry.map(|(worker, telemetry)| { - task_manager.spawn_handle().spawn("telemetry", worker.run()); + task_manager.spawn_handle().spawn("telemetry", None, worker.run()); telemetry }); @@ -105,10 +110,10 @@ pub fn new_partial( telemetry.as_ref().map(|x| x.handle()), )?; - let slot_duration = sc_consensus_aura::slot_duration(&*client)?.slot_duration(); + let slot_duration = sc_consensus_aura::slot_duration(&*client)?; let import_queue = - sc_consensus_aura::import_queue::(ImportQueueParams { + sc_consensus_aura::import_queue::(ImportQueueParams { block_import: grandpa_block_import.clone(), justification_import: Some(Box::new(grandpa_block_import.clone())), client: client.clone(), @@ -116,20 +121,18 @@ pub fn new_partial( let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); let slot = - sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_duration( + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( *timestamp, slot_duration, ); - Ok((timestamp, slot)) + Ok((slot, timestamp)) }, spawner: &task_manager.spawn_essential_handle(), - can_author_with: sp_consensus::CanAuthorWithNativeVersion::new( - client.executor().clone(), - ), registry: config.prometheus_registry(), check_for_equivocation: Default::default(), telemetry: telemetry.as_ref().map(|x| x.handle()), + compatibility_mode: Default::default(), })?; Ok(sc_service::PartialComponents { @@ -174,21 +177,28 @@ pub fn new_full(mut config: Configuration) -> Result ))), }; } + let grandpa_protocol_name = sc_finality_grandpa::protocol_standard_name( + &client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"), + &config.chain_spec, + ); - config.network.extra_sets.push(sc_finality_grandpa::grandpa_peers_set_config()); + config + .network + .extra_sets + .push(sc_finality_grandpa::grandpa_peers_set_config(grandpa_protocol_name.clone())); let warp_sync = Arc::new(sc_finality_grandpa::warp_proof::NetworkProvider::new( backend.clone(), grandpa_link.shared_authority_set().clone(), + Vec::default(), )); - let (network, system_rpc_tx, network_starter) = + let (network, system_rpc_tx, tx_handler_controller, network_starter) = sc_service::build_network(sc_service::BuildNetworkParams { config: &config, client: client.clone(), transaction_pool: transaction_pool.clone(), spawn_handle: task_manager.spawn_handle(), import_queue, - on_demand: None, block_announce_validator_builder: None, warp_sync: Some(warp_sync), })?; @@ -216,8 +226,7 @@ pub fn new_full(mut config: Configuration) -> Result Box::new(move |deny_unsafe, _| { let deps = crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; - - Ok(crate::rpc::create_full(deps)) + crate::rpc::create_full(deps).map_err(Into::into) }) }; @@ -227,11 +236,10 @@ pub fn new_full(mut config: Configuration) -> Result keystore: keystore_container.sync_keystore(), task_manager: &mut task_manager, transaction_pool: transaction_pool.clone(), - rpc_extensions_builder, - on_demand: None, - remote_blockchain: None, + rpc_builder: rpc_extensions_builder, backend, system_rpc_tx, + tx_handler_controller, config, telemetry: telemetry.as_mut(), })?; @@ -245,16 +253,12 @@ pub fn new_full(mut config: Configuration) -> Result telemetry.as_ref().map(|x| x.handle()), ); - let can_author_with = - sp_consensus::CanAuthorWithNativeVersion::new(client.executor().clone()); - let slot_duration = sc_consensus_aura::slot_duration(&*client)?; - let raw_slot_duration = slot_duration.slot_duration(); - let aura = sc_consensus_aura::start_aura::( + let aura = sc_consensus_aura::start_aura::( StartAuraParams { slot_duration, - client: client.clone(), + client, select_chain, block_import, proposer_factory, @@ -262,47 +266,50 @@ pub fn new_full(mut config: Configuration) -> Result let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); let slot = - sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_duration( + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( *timestamp, - raw_slot_duration, + slot_duration, ); - Ok((timestamp, slot)) + Ok((slot, timestamp)) }, force_authoring, backoff_authoring_blocks, keystore: keystore_container.sync_keystore(), - can_author_with, sync_oracle: network.clone(), justification_sync_link: network.clone(), block_proposal_slot_portion: SlotProportion::new(2f32 / 3f32), max_block_proposal_slot_portion: None, telemetry: telemetry.as_ref().map(|x| x.handle()), + compatibility_mode: Default::default(), }, )?; // the AURA authoring task is considered essential, i.e. if it // fails we take down the service with it. - task_manager.spawn_essential_handle().spawn_blocking("aura", aura); + task_manager + .spawn_essential_handle() + .spawn_blocking("aura", Some("block-authoring"), aura); } - // if the node isn't actively participating in consensus then it doesn't - // need a keystore, regardless of which protocol we use below. - let keystore = - if role.is_authority() { Some(keystore_container.sync_keystore()) } else { None }; - - let grandpa_config = sc_finality_grandpa::Config { - // FIXME #1578 make this available through chainspec - gossip_duration: Duration::from_millis(333), - justification_period: 512, - name: Some(name), - observer_enabled: false, - keystore, - local_role: role, - telemetry: telemetry.as_ref().map(|x| x.handle()), - }; - if enable_grandpa { + // if the node isn't actively participating in consensus then it doesn't + // need a keystore, regardless of which protocol we use below. + let keystore = + if role.is_authority() { Some(keystore_container.sync_keystore()) } else { None }; + + let grandpa_config = sc_finality_grandpa::Config { + // FIXME #1578 make this available through chainspec + gossip_duration: Duration::from_millis(333), + justification_period: 512, + name: Some(name), + observer_enabled: false, + keystore, + local_role: role, + telemetry: telemetry.as_ref().map(|x| x.handle()), + protocol_name: grandpa_protocol_name, + }; + // start the full GRANDPA voter // NOTE: non-authorities could run the GRANDPA observer protocol, but at // this point the full voter should provide better guarantees of block @@ -323,6 +330,7 @@ pub fn new_full(mut config: Configuration) -> Result // if it fails we take down the service with it. task_manager.spawn_essential_handle().spawn_blocking( "grandpa-voter", + None, sc_finality_grandpa::run_grandpa_voter(grandpa_config)?, ); } @@ -330,143 +338,3 @@ pub fn new_full(mut config: Configuration) -> Result network_starter.start_network(); Ok(task_manager) } - -/// Builds a new service for a light client. -pub fn new_light(mut config: Configuration) -> Result { - let telemetry = config - .telemetry_endpoints - .clone() - .filter(|x| !x.is_empty()) - .map(|endpoints| -> Result<_, sc_telemetry::Error> { - let worker = TelemetryWorker::new(16)?; - let telemetry = worker.handle().new_telemetry(endpoints); - Ok((worker, telemetry)) - }) - .transpose()?; - - let executor = NativeElseWasmExecutor::::new( - config.wasm_method, - config.default_heap_pages, - config.max_runtime_instances, - ); - - let (client, backend, keystore_container, mut task_manager, on_demand) = - sc_service::new_light_parts::( - &config, - telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), - executor, - )?; - - let mut telemetry = telemetry.map(|(worker, telemetry)| { - task_manager.spawn_handle().spawn("telemetry", worker.run()); - telemetry - }); - - config.network.extra_sets.push(sc_finality_grandpa::grandpa_peers_set_config()); - - let select_chain = sc_consensus::LongestChain::new(backend.clone()); - - let transaction_pool = Arc::new(sc_transaction_pool::BasicPool::new_light( - config.transaction_pool.clone(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), - on_demand.clone(), - )); - - let (grandpa_block_import, grandpa_link) = sc_finality_grandpa::block_import( - client.clone(), - &(client.clone() as Arc<_>), - select_chain.clone(), - telemetry.as_ref().map(|x| x.handle()), - )?; - - let slot_duration = sc_consensus_aura::slot_duration(&*client)?.slot_duration(); - - let import_queue = - sc_consensus_aura::import_queue::(ImportQueueParams { - block_import: grandpa_block_import.clone(), - justification_import: Some(Box::new(grandpa_block_import.clone())), - client: client.clone(), - create_inherent_data_providers: move |_, ()| async move { - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); - - let slot = - sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_duration( - *timestamp, - slot_duration, - ); - - Ok((timestamp, slot)) - }, - spawner: &task_manager.spawn_essential_handle(), - can_author_with: sp_consensus::NeverCanAuthor, - registry: config.prometheus_registry(), - check_for_equivocation: Default::default(), - telemetry: telemetry.as_ref().map(|x| x.handle()), - })?; - - let warp_sync = Arc::new(sc_finality_grandpa::warp_proof::NetworkProvider::new( - backend.clone(), - grandpa_link.shared_authority_set().clone(), - )); - - let (network, system_rpc_tx, network_starter) = - sc_service::build_network(sc_service::BuildNetworkParams { - config: &config, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - spawn_handle: task_manager.spawn_handle(), - import_queue, - on_demand: Some(on_demand.clone()), - block_announce_validator_builder: None, - warp_sync: Some(warp_sync), - })?; - - if config.offchain_worker.enabled { - sc_service::build_offchain_workers( - &config, - task_manager.spawn_handle(), - client.clone(), - network.clone(), - ); - } - - let enable_grandpa = !config.disable_grandpa; - if enable_grandpa { - let name = config.network.node_name.clone(); - - let config = sc_finality_grandpa::Config { - gossip_duration: std::time::Duration::from_millis(333), - justification_period: 512, - name: Some(name), - observer_enabled: false, - keystore: None, - local_role: config.role.clone(), - telemetry: telemetry.as_ref().map(|x| x.handle()), - }; - - task_manager.spawn_handle().spawn_blocking( - "grandpa-observer", - sc_finality_grandpa::run_grandpa_observer(config, grandpa_link, network.clone())?, - ); - } - - sc_service::spawn_tasks(sc_service::SpawnTasksParams { - remote_blockchain: Some(backend.remote_blockchain()), - transaction_pool, - task_manager: &mut task_manager, - on_demand: Some(on_demand), - rpc_extensions_builder: Box::new(|_, _| Ok(())), - config, - client, - keystore: keystore_container.sync_keystore(), - backend, - network, - system_rpc_tx, - telemetry: telemetry.as_mut(), - })?; - - network_starter.start_network(); - Ok(task_manager) -} diff --git a/bin/node-template/pallets/template/Cargo.toml b/bin/node-template/pallets/template/Cargo.toml index b3eb747625b4f..7c04838cae319 100644 --- a/bin/node-template/pallets/template/Cargo.toml +++ b/bin/node-template/pallets/template/Cargo.toml @@ -1,41 +1,39 @@ [package] -authors = ['Substrate DevHub '] -edition = '2018' -name = 'pallet-template' -version = "3.0.0" -license = "Unlicense" -homepage = "https://substrate.dev" -repository = "https://github.com/substrate-developer-hub/substrate-node-template/" +name = "pallet-template" +version = "4.0.0-dev" description = "FRAME pallet template for defining custom runtime logic." -readme = "README.md" +authors = ["Substrate DevHub "] +homepage = "https://substrate.io" +edition = "2021" +license = "Unlicense" publish = false +repository = "https://github.com/substrate-developer-hub/substrate-node-template/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -frame-support = { default-features = false, version = "4.0.0-dev", path = "../../../../frame/support" } -frame-system = { default-features = false, version = "4.0.0-dev", path = "../../../../frame/system" } -frame-benchmarking = { default-features = false, version = "4.0.0-dev", path = "../../../../frame/benchmarking", optional = true } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../../../frame/benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../../../frame/support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../../frame/system" } [dev-dependencies] -sp-core = { default-features = false, version = "4.0.0-dev", path = "../../../../primitives/core" } -sp-io = { default-features = false, version = "4.0.0-dev", path = "../../../../primitives/io" } -sp-runtime = { default-features = false, version = "4.0.0-dev", path = "../../../../primitives/runtime" } +sp-core = { version = "7.0.0", default-features = false, path = "../../../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../../primitives/runtime" } [features] -default = ['std'] +default = ["std"] std = [ - 'codec/std', - 'scale-info/std', - 'frame-support/std', - 'frame-system/std', - 'frame-benchmarking/std', + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", ] - -runtime-benchmarks = ["frame-benchmarking"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/bin/node-template/pallets/template/src/benchmarking.rs b/bin/node-template/pallets/template/src/benchmarking.rs index 2117c048cfbdb..d496a9fc89b1a 100644 --- a/bin/node-template/pallets/template/src/benchmarking.rs +++ b/bin/node-template/pallets/template/src/benchmarking.rs @@ -4,7 +4,7 @@ use super::*; #[allow(unused)] use crate::Pallet as Template; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +use frame_benchmarking::{benchmarks, whitelisted_caller}; use frame_system::RawOrigin; benchmarks! { @@ -15,6 +15,6 @@ benchmarks! { verify { assert_eq!(Something::::get(), Some(s)); } -} -impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/bin/node-template/pallets/template/src/lib.rs b/bin/node-template/pallets/template/src/lib.rs index ee3ca695b64da..4630e344add31 100644 --- a/bin/node-template/pallets/template/src/lib.rs +++ b/bin/node-template/pallets/template/src/lib.rs @@ -2,7 +2,7 @@ /// Edit this file to define custom logic or remove it if it is not needed. /// Learn more about FRAME and the core library of Substrate FRAME pallets: -/// +/// pub use pallet::*; #[cfg(test)] @@ -16,36 +16,36 @@ mod benchmarking; #[frame_support::pallet] pub mod pallet { - use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - // The pallet's runtime storage items. - // https://substrate.dev/docs/en/knowledgebase/runtime/storage + // https://docs.substrate.io/main-docs/build/runtime-storage/ #[pallet::storage] #[pallet::getter(fn something)] // Learn more about declaring storage items: - // https://substrate.dev/docs/en/knowledgebase/runtime/storage#declaring-storage-items + // https://docs.substrate.io/main-docs/build/runtime-storage/#declaring-storage-items pub type Something = StorageValue<_, u32>; // Pallets use events to inform users when important changes are made. - // https://substrate.dev/docs/en/knowledgebase/runtime/events + // https://docs.substrate.io/main-docs/build/events-errors/ #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Event documentation should end with an array that provides descriptive names for event /// parameters. [something, who] - SomethingStored(u32, T::AccountId), + SomethingStored { something: u32, who: T::AccountId }, } // Errors inform users that something went wrong. @@ -64,31 +64,33 @@ pub mod pallet { impl Pallet { /// An example dispatchable that takes a singles value as a parameter, writes the value to /// storage and emits an event. This function must be dispatched by a signed extrinsic. - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + #[pallet::call_index(0)] + #[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())] pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { // Check that the extrinsic was signed and get the signer. // This function will return an error if the extrinsic is not signed. - // https://substrate.dev/docs/en/knowledgebase/runtime/origin + // https://docs.substrate.io/main-docs/build/origins/ let who = ensure_signed(origin)?; // Update storage. >::put(something); // Emit an event. - Self::deposit_event(Event::SomethingStored(something, who)); + Self::deposit_event(Event::SomethingStored { something, who }); // Return a successful DispatchResultWithPostInfo Ok(()) } /// An example dispatchable that may throw a custom error. - #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))] + #[pallet::call_index(1)] + #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1).ref_time())] pub fn cause_error(origin: OriginFor) -> DispatchResult { let _who = ensure_signed(origin)?; // Read a value from storage. match >::get() { // Return an error if the value has not been set. - None => Err(Error::::NoneValue)?, + None => return Err(Error::::NoneValue.into()), Some(old) => { // Increment the value read from storage; will error in the event of overflow. let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; diff --git a/bin/node-template/pallets/template/src/mock.rs b/bin/node-template/pallets/template/src/mock.rs index 4532d3d09b497..989681fa59a00 100644 --- a/bin/node-template/pallets/template/src/mock.rs +++ b/bin/node-template/pallets/template/src/mock.rs @@ -1,5 +1,5 @@ use crate as pallet_template; -use frame_support::parameter_types; +use frame_support::traits::{ConstU16, ConstU64}; use frame_system as system; use sp_core::H256; use sp_runtime::{ @@ -17,23 +17,18 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - TemplateModule: pallet_template::{Pallet, Call, Storage, Event}, + System: frame_system, + TemplateModule: pallet_template, } ); -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; -} - impl system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -41,20 +36,21 @@ impl system::Config for Test { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; + type SS58Prefix = ConstU16<42>; type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_template::Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; } // Build genesis storage according to the mock runtime. diff --git a/bin/node-template/pallets/template/src/tests.rs b/bin/node-template/pallets/template/src/tests.rs index 2205658601721..7c2b853ee4dc5 100644 --- a/bin/node-template/pallets/template/src/tests.rs +++ b/bin/node-template/pallets/template/src/tests.rs @@ -1,13 +1,17 @@ -use crate::{mock::*, Error}; +use crate::{mock::*, Error, Event}; use frame_support::{assert_noop, assert_ok}; #[test] fn it_works_for_default_value() { new_test_ext().execute_with(|| { + // Go past genesis block so events get deposited + System::set_block_number(1); // Dispatch a signed extrinsic. - assert_ok!(TemplateModule::do_something(Origin::signed(1), 42)); + assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); // Read pallet storage and assert an expected result. assert_eq!(TemplateModule::something(), Some(42)); + // Assert that the correct event was deposited + System::assert_last_event(Event::SomethingStored { something: 42, who: 1 }.into()); }); } @@ -15,6 +19,9 @@ fn it_works_for_default_value() { fn correct_error_for_none_value() { new_test_ext().execute_with(|| { // Ensure the expected error is thrown when no value is present. - assert_noop!(TemplateModule::cause_error(Origin::signed(1)), Error::::NoneValue); + assert_noop!( + TemplateModule::cause_error(RuntimeOrigin::signed(1)), + Error::::NoneValue + ); }); } diff --git a/bin/node-template/runtime/Cargo.toml b/bin/node-template/runtime/Cargo.toml index 47e67af2b9ae1..1a3c5bd84223b 100644 --- a/bin/node-template/runtime/Cargo.toml +++ b/bin/node-template/runtime/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "node-template-runtime" -version = "3.0.0" +version = "4.0.0-dev" +description = "A fresh FRAME-based Substrate node, ready for hacking." authors = ["Substrate DevHub "] -edition = "2018" +homepage = "https://substrate.io/" +edition = "2021" license = "Unlicense" -homepage = "https://substrate.dev" -repository = "https://github.com/substrate-developer-hub/substrate-node-template/" publish = false +repository = "https://github.com/substrate-developer-hub/substrate-node-template/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } pallet-aura = { version = "4.0.0-dev", default-features = false, path = "../../../frame/aura" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../../frame/balances" } @@ -22,20 +23,21 @@ pallet-grandpa = { version = "4.0.0-dev", default-features = false, path = "../. pallet-randomness-collective-flip = { version = "4.0.0-dev", default-features = false, path = "../../../frame/randomness-collective-flip" } pallet-sudo = { version = "4.0.0-dev", default-features = false, path = "../../../frame/sudo" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system" } +frame-try-runtime = { version = "0.10.0-dev", default-features = false, path = "../../../frame/try-runtime", optional = true } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../../frame/timestamp" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } frame-executive = { version = "4.0.0-dev", default-features = false, path = "../../../frame/executive" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } -sp-block-builder = { path = "../../../primitives/block-builder", default-features = false, version = "4.0.0-dev"} +sp-block-builder = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/block-builder"} sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/consensus/aura" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" } -sp-inherents = { path = "../../../primitives/inherents", default-features = false, version = "4.0.0-dev"} +sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/inherents"} sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/offchain" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/transaction-pool" } -sp-version = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/version" } +sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } # Used for the node template's RPCs frame-system-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system/rpc/runtime-api/" } @@ -44,9 +46,9 @@ pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", default-fe # Used for runtime benchmarking frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/benchmarking", optional = true } frame-system-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system/benchmarking", optional = true } -hex-literal = { version = "0.3.1", optional = true } -pallet-template = { version = "3.0.0", default-features = false, path = "../pallets/template" } +# Local Dependencies +pallet-template = { version = "4.0.0-dev", default-features = false, path = "../pallets/template" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } @@ -54,12 +56,16 @@ substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-bu [features] default = ["std"] std = [ + "frame-try-runtime?/std", + "frame-system-benchmarking?/std", + "frame-benchmarking?/std", "codec/std", "scale-info/std", "frame-executive/std", "frame-support/std", "frame-system-rpc-runtime-api/std", "frame-system/std", + "frame-try-runtime/std", "pallet-aura/std", "pallet-balances/std", "pallet-grandpa/std", @@ -82,13 +88,27 @@ std = [ "sp-version/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", - "frame-system-benchmarking", + "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", - "hex-literal", "pallet-balances/runtime-benchmarks", + "pallet-grandpa/runtime-benchmarks", "pallet-template/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] +try-runtime = [ + "frame-try-runtime/try-runtime", + "frame-executive/try-runtime", + "frame-system/try-runtime", + "frame-support/try-runtime", + "pallet-aura/try-runtime", + "pallet-balances/try-runtime", + "pallet-grandpa/try-runtime", + "pallet-randomness-collective-flip/try-runtime", + "pallet-sudo/try-runtime", + "pallet-template/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", +] diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index eecc93e166666..938282c662b5c 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -14,7 +14,9 @@ use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, NumberFor, Verify}, + traits::{ + AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, NumberFor, One, Verify, + }, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, MultiSignature, }; @@ -26,16 +28,21 @@ use sp_version::RuntimeVersion; // A few exports that help ease life for downstream crates. pub use frame_support::{ construct_runtime, parameter_types, - traits::{KeyOwnerProofSystem, Randomness, StorageInfo}, + traits::{ + ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo, + }, weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, + }, IdentityFee, Weight, }, StorageValue, }; +pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; pub use pallet_timestamp::Call as TimestampCall; -use pallet_transaction_payment::CurrencyAdapter; +use pallet_transaction_payment::{ConstFeeMultiplier, CurrencyAdapter, Multiplier}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Permill}; @@ -86,8 +93,8 @@ pub mod opaque { } } -// To learn more about runtime versioning and what each of the following value means: -// https://substrate.dev/docs/en/knowledgebase/runtime/upgrades#runtime-versioning +// To learn more about runtime versioning, see: +// https://docs.substrate.io/main-docs/build/upgrade#runtime-versioning #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node-template"), @@ -102,6 +109,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, + state_version: 1, }; /// This determines the average expected block time that we are targeting. @@ -130,11 +138,14 @@ pub fn native_version() -> NativeVersion { const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); parameter_types! { - pub const Version: RuntimeVersion = VERSION; pub const BlockHashCount: BlockNumber = 2400; + pub const Version: RuntimeVersion = VERSION; /// We allow for 2 seconds of compute with a 6 second average block time. - pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights - ::with_sensible_defaults(2 * WEIGHT_PER_SECOND, NORMAL_DISPATCH_RATIO); + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::with_sensible_defaults( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + NORMAL_DISPATCH_RATIO, + ); pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength ::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); pub const SS58Prefix: u8 = 42; @@ -152,7 +163,7 @@ impl frame_system::Config for Runtime { /// The identifier used to distinguish between accounts. type AccountId = AccountId; /// The aggregated dispatch type that is available for extrinsics. - type Call = Call; + type RuntimeCall = RuntimeCall; /// The lookup mechanism to get account ID from whatever is passed in dispatchers. type Lookup = AccountIdLookup; /// The index type for storing how many extrinsics an account has signed. @@ -166,9 +177,9 @@ impl frame_system::Config for Runtime { /// The header type. type Header = generic::Header; /// The ubiquitous event type. - type Event = Event; + type RuntimeEvent = RuntimeEvent; /// The ubiquitous origin type. - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; /// Maximum number of block number to block hash mappings to keep (oldest pruned first). type BlockHashCount = BlockHashCount; /// The weight of database operations that the runtime can invoke. @@ -191,23 +202,19 @@ impl frame_system::Config for Runtime { type SS58Prefix = SS58Prefix; /// The set code logic, just the default since we're not a parachain. type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_randomness_collective_flip::Config for Runtime {} -parameter_types! { - pub const MaxAuthorities: u32 = 32; -} - impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); - type MaxAuthorities = MaxAuthorities; + type MaxAuthorities = ConstU32<32>; } impl pallet_grandpa::Config for Runtime { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; type KeyOwnerProofSystem = (); @@ -222,77 +229,75 @@ impl pallet_grandpa::Config for Runtime { type HandleEquivocation = (); type WeightInfo = (); -} - -parameter_types! { - pub const MinimumPeriod: u64 = SLOT_DURATION / 2; + type MaxAuthorities = ConstU32<32>; } impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = (); } -parameter_types! { - pub const ExistentialDeposit: u128 = 500; - pub const MaxLocks: u32 = 50; -} +/// Existential deposit. +pub const EXISTENTIAL_DEPOSIT: u128 = 500; impl pallet_balances::Config for Runtime { - type MaxLocks = MaxLocks; + type MaxLocks = ConstU32<50>; type MaxReserves = (); type ReserveIdentifier = [u8; 8]; /// The type for recording an account's balance. type Balance = Balance; /// The ubiquitous event type. - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU128; type AccountStore = System; type WeightInfo = pallet_balances::weights::SubstrateWeight; } parameter_types! { - pub const TransactionByteFee: Balance = 1; + pub FeeMultiplier: Multiplier = Multiplier::one(); } impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = CurrencyAdapter; - type TransactionByteFee = TransactionByteFee; + type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; - type FeeMultiplierUpdate = (); + type LengthToFee = IdentityFee; + type FeeMultiplierUpdate = ConstFeeMultiplier; } impl pallet_sudo::Config for Runtime { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; } /// Configure the pallet-template in pallets/template. impl pallet_template::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; } // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( - pub enum Runtime where + pub struct Runtime + where Block = Block, NodeBlock = opaque::Block, - UncheckedExtrinsic = UncheckedExtrinsic + UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - Aura: pallet_aura::{Pallet, Config}, - Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, - Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event}, + System: frame_system, + RandomnessCollectiveFlip: pallet_randomness_collective_flip, + Timestamp: pallet_timestamp, + Aura: pallet_aura, + Grandpa: pallet_grandpa, + Balances: pallet_balances, + TransactionPayment: pallet_transaction_payment, + Sudo: pallet_sudo, // Include the custom logic from the pallet-template in the runtime. - TemplateModule: pallet_template::{Pallet, Call, Storage, Event}, + TemplateModule: pallet_template, } ); @@ -304,6 +309,7 @@ pub type Header = generic::Header; pub type Block = generic::Block; /// The SignedExtension to the basic transaction logic. pub type SignedExtra = ( + frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, frame_system::CheckGenesis, @@ -312,17 +318,36 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, ); + /// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +/// The payload being signed in transactions. +pub type SignedPayload = generic::SignedPayload; /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< Runtime, Block, frame_system::ChainContext, Runtime, - AllPallets, + AllPalletsWithSystem, >; +#[cfg(feature = "runtime-benchmarks")] +#[macro_use] +extern crate frame_benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + define_benchmarks!( + [frame_benchmarking, BaselineBench::] + [frame_system, SystemBench::] + [pallet_balances, Balances] + [pallet_timestamp, Timestamp] + [pallet_template, TemplateModule] + ); +} + impl_runtime_apis! { impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { @@ -454,59 +479,119 @@ impl_runtime_apis! { } } + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + } + #[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark for Runtime { fn benchmark_metadata(extra: bool) -> ( Vec, Vec, ) { - use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList}; + use frame_benchmarking::{baseline, Benchmarking, BenchmarkList}; use frame_support::traits::StorageInfoTrait; use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; let mut list = Vec::::new(); - - list_benchmark!(list, extra, frame_system, SystemBench::); - list_benchmark!(list, extra, pallet_balances, Balances); - list_benchmark!(list, extra, pallet_timestamp, Timestamp); - list_benchmark!(list, extra, pallet_template, TemplateModule); + list_benchmarks!(list, extra); let storage_info = AllPalletsWithSystem::storage_info(); - return (list, storage_info) + (list, storage_info) } fn dispatch_benchmark( config: frame_benchmarking::BenchmarkConfig ) -> Result, sp_runtime::RuntimeString> { - use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey}; + use frame_benchmarking::{baseline, Benchmarking, BenchmarkBatch, TrackedStorageKey}; use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + impl frame_system_benchmarking::Config for Runtime {} + impl baseline::Config for Runtime {} - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); + add_benchmarks!(params, batches); - add_benchmark!(params, batches, frame_system, SystemBench::); - add_benchmark!(params, batches, pallet_balances, Balances); - add_benchmark!(params, batches, pallet_timestamp, Timestamp); - add_benchmark!(params, batches, pallet_template, TemplateModule); - - if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } Ok(batches) } } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade() -> (Weight, Weight) { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. If any of the pre/post migration checks fail, we shall stop + // right here and right now. + let weight = Executive::try_runtime_upgrade().unwrap(); + (weight, BlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + select: frame_try_runtime::TryStateSelect + ) -> Weight { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, select).expect("execute-block failed") + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::traits::WhitelistedStorageKeys; + use sp_core::hexdisplay::HexDisplay; + use std::collections::HashSet; + + #[test] + fn check_whitelist() { + let whitelist: HashSet = AllPalletsWithSystem::whitelisted_storage_keys() + .iter() + .map(|e| HexDisplay::from(&e.key).to_string()) + .collect(); + + // Block Number + assert!( + whitelist.contains("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac") + ); + // Total Issuance + assert!( + whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80") + ); + // Execution Phase + assert!( + whitelist.contains("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a") + ); + // Event Count + assert!( + whitelist.contains("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850") + ); + // System Events + assert!( + whitelist.contains("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7") + ); + } } diff --git a/bin/node-template/shell.nix b/bin/node-template/shell.nix new file mode 100644 index 0000000000000..c08005c1630e9 --- /dev/null +++ b/bin/node-template/shell.nix @@ -0,0 +1,35 @@ +let + mozillaOverlay = + import (builtins.fetchGit { + url = "https://github.com/mozilla/nixpkgs-mozilla.git"; + rev = "57c8084c7ef41366993909c20491e359bbb90f54"; + }); + pinned = builtins.fetchGit { + # Descriptive name to make the store path easier to identify + url = "https://github.com/nixos/nixpkgs/"; + # Commit hash for nixos-unstable as of 2020-04-26 + # `git ls-remote https://github.com/nixos/nixpkgs nixos-unstable` + ref = "refs/heads/nixos-unstable"; + rev = "1fe6ed37fd9beb92afe90671c0c2a662a03463dd"; + }; + nixpkgs = import pinned { overlays = [ mozillaOverlay ]; }; + toolchain = with nixpkgs; (rustChannelOf { date = "2021-09-14"; channel = "nightly"; }); + rust-wasm = toolchain.rust.override { + targets = [ "wasm32-unknown-unknown" ]; + }; +in +with nixpkgs; pkgs.mkShell { + buildInputs = [ + clang + pkg-config + rust-wasm + ] ++ stdenv.lib.optionals stdenv.isDarwin [ + darwin.apple_sdk.frameworks.Security + ]; + + LIBCLANG_PATH = "${llvmPackages.libclang}/lib"; + PROTOC = "${protobuf}/bin/protoc"; + RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library/"; + ROCKSDB_LIB_DIR = "${rocksdb}/lib"; + +} diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index b19a71966fb87..265de731de690 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -3,42 +3,42 @@ name = "node-bench" version = "0.9.0-dev" authors = ["Parity Technologies "] description = "Substrate node integration benchmarks." -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -log = "0.4.8" +array-bytes = "4.1" +clap = { version = "4.0.9", features = ["derive"] } +log = "0.4.17" node-primitives = { version = "2.0.0", path = "../primitives" } node-testing = { version = "3.0.0-dev", path = "../testing" } -node-runtime = { version = "3.0.0-dev", path = "../runtime" } +kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api/" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.10.0-dev", path = "../../../primitives/state-machine" } -serde = "1.0.126" -serde_json = "1.0.68" -structopt = "0.3" -derive_more = "0.99.2" -kvdb = "0.10.0" -kvdb-rocksdb = "0.14.0" -sp-trie = { version = "4.0.0-dev", path = "../../../primitives/trie" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } +serde = "1.0.136" +serde_json = "1.0.85" +derive_more = { version = "0.99.17", default-features = false, features = ["display"] } +kvdb = "0.13.0" +kvdb-rocksdb = "0.17.0" +sp-trie = { version = "7.0.0", path = "../../../primitives/trie" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sc-basic-authorship = { version = "0.10.0-dev", path = "../../../client/basic-authorship" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/timestamp" } -sp-tracing = { version = "4.0.0-dev", path = "../../../primitives/tracing" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } hash-db = "0.15.2" tempfile = "3.1.0" fs_extra = "1" -hex = "0.4.0" rand = { version = "0.7.2", features = ["small_rng"] } lazy_static = "1.4.0" -parity-util-mem = { version = "0.10.0", default-features = false, features = [ - "primitive-types", -] } -parity-db = { version = "0.3" } +parity-db = "0.4.2" sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } -futures = { version = "0.3.4", features = ["thread-pool"] } +futures = { version = "0.3.21", features = ["thread-pool"] } diff --git a/bin/node/bench/src/common.rs b/bin/node/bench/src/common.rs index d04d79e9907af..839e26f9f6d13 100644 --- a/bin/node/bench/src/common.rs +++ b/bin/node/bench/src/common.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/bin/node/bench/src/construct.rs b/bin/node/bench/src/construct.rs index 1532e02bd3ef6..2e20a7acb1e38 100644 --- a/bin/node/bench/src/construct.rs +++ b/bin/node/bench/src/construct.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -30,8 +30,8 @@ use std::{borrow::Cow, collections::HashMap, pin::Pin, sync::Arc}; use node_primitives::Block; use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes, Profile}; use sc_transaction_pool_api::{ - ImportNotificationStream, PoolFuture, PoolStatus, TransactionFor, TransactionSource, - TransactionStatusStreamFor, TxHash, + ImportNotificationStream, PoolFuture, PoolStatus, ReadyTransactions, TransactionFor, + TransactionSource, TransactionStatusStreamFor, TxHash, }; use sp_consensus::{Environment, Proposer}; use sp_inherents::InherentDataProvider; @@ -150,8 +150,10 @@ impl core::Benchmark for ConstructionBenchmark { ) .expect("Proposer initialization failed"); + let inherent_data = futures::executor::block_on(timestamp_provider.create_inherent_data()) + .expect("Create inherent data failed"); let _block = futures::executor::block_on(proposer.propose( - timestamp_provider.create_inherent_data().expect("Create inherent data failed"), + inherent_data, Default::default(), std::time::Duration::from_secs(20), None, @@ -216,6 +218,19 @@ impl sc_transaction_pool_api::InPoolTransaction for PoolTransaction { #[derive(Clone, Debug)] pub struct Transactions(Vec>); +pub struct TransactionsIterator(std::vec::IntoIter>); + +impl Iterator for TransactionsIterator { + type Item = Arc; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl ReadyTransactions for TransactionsIterator { + fn report_invalid(&mut self, _tx: &Self::Item) {} +} impl sc_transaction_pool_api::TransactionPool for Transactions { type Block = Block; @@ -257,16 +272,17 @@ impl sc_transaction_pool_api::TransactionPool for Transactions { _at: NumberFor, ) -> Pin< Box< - dyn Future> + Send>> - + Send, + dyn Future< + Output = Box> + Send>, + > + Send, >, > { - let iter: Box> + Send> = - Box::new(self.0.clone().into_iter()); + let iter: Box> + Send> = + Box::new(TransactionsIterator(self.0.clone().into_iter())); Box::pin(futures::future::ready(iter)) } - fn ready(&self) -> Box> + Send> { + fn ready(&self) -> Box> + Send> { unimplemented!() } diff --git a/bin/node/bench/src/core.rs b/bin/node/bench/src/core.rs index 56c0f3526a4dc..b6ad3ecd80068 100644 --- a/bin/node/bench/src/core.rs +++ b/bin/node/bench/src/core.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/bin/node/bench/src/generator.rs b/bin/node/bench/src/generator.rs index e3aa1192b5d1f..76bd3a3240c51 100644 --- a/bin/node/bench/src/generator.rs +++ b/bin/node/bench/src/generator.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ use std::{collections::HashMap, sync::Arc}; use kvdb::KeyValueDB; use node_primitives::Hash; -use sp_trie::{trie_types::TrieDBMut, TrieMut}; +use sp_trie::{trie_types::TrieDBMutBuilderV1, TrieMut}; use crate::simple_trie::SimpleTrie; @@ -37,14 +37,16 @@ pub fn generate_trie( let (db, overlay) = { let mut overlay = HashMap::new(); overlay.insert( - hex::decode("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314") - .expect("null key is valid"), + array_bytes::hex2bytes( + "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314", + ) + .expect("null key is valid"), Some(vec![0]), ); let mut trie = SimpleTrie { db, overlay: &mut overlay }; { - let mut trie_db = TrieDBMut::new(&mut trie, &mut root); - + let mut trie_db = + TrieDBMutBuilderV1::::new(&mut trie, &mut root).build(); for (key, value) in key_values { trie_db.insert(&key, &value).expect("trie insertion failed"); } diff --git a/bin/node/bench/src/import.rs b/bin/node/bench/src/import.rs index 5bbf1ddf3b73e..28a322834271c 100644 --- a/bin/node/bench/src/import.rs +++ b/bin/node/bench/src/import.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -34,7 +34,7 @@ use std::borrow::Cow; use node_primitives::Block; use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes, Profile}; -use sc_client_api::backend::Backend; +use sc_client_api::{backend::Backend, HeaderBackend}; use sp_runtime::generic::BlockId; use sp_state_machine::InspectState; @@ -127,31 +127,39 @@ impl core::Benchmark for ImportBenchmark { context.import_block(self.block.clone()); let elapsed = start.elapsed(); + let hash = context + .client + .expect_block_hash_from_id(&BlockId::number(1)) + .expect("Block 1 was imported; qed"); + // Sanity checks. context .client - .state_at(&BlockId::number(1)) + .state_at(hash) .expect("state_at failed for block#1") .inspect_state(|| { match self.block_type { BlockType::RandomTransfersKeepAlive => { - // should be 5 per signed extrinsic + 1 per unsigned + // should be 8 per signed extrinsic + 1 per unsigned // we have 1 unsigned and the rest are signed in the block - // those 5 events per signed are: - // - new account (RawEvent::NewAccount) as we always transfer fund to - // non-existant account - // - endowed (RawEvent::Endowed) for this new account - // - successful transfer (RawEvent::Transfer) for this transfer operation - // - deposit event for charging transaction fee + // those 8 events per signed are: + // - transaction paid for the transaction payment + // - withdraw (Balances::Withdraw) for charging the transaction fee + // - new account (System::NewAccount) as we always transfer fund to + // non-existent account + // - endowed (Balances::Endowed) for this new account + // - successful transfer (Event::Transfer) for this transfer operation + // - 2x deposit (Balances::Deposit and Treasury::Deposit) for depositing + // the transaction fee into the treasury // - extrinsic success assert_eq!( - node_runtime::System::events().len(), - (self.block.extrinsics.len() - 1) * 5 + 1, + kitchensink_runtime::System::events().len(), + (self.block.extrinsics.len() - 1) * 8 + 1, ); }, BlockType::Noop => { assert_eq!( - node_runtime::System::events().len(), + kitchensink_runtime::System::events().len(), // should be 2 per signed extrinsic + 1 per unsigned // we have 1 unsigned and the rest are signed in the block // those 2 events per signed are: diff --git a/bin/node/bench/src/main.rs b/bin/node/bench/src/main.rs index 4b006b387d0ea..8a5d99640eb1b 100644 --- a/bin/node/bench/src/main.rs +++ b/bin/node/bench/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -28,7 +28,7 @@ mod tempdb; mod trie; mod txpool; -use structopt::StructOpt; +use clap::Parser; use node_testing::bench::{BlockType, DatabaseType as BenchDataBaseType, KeyTypes, Profile}; @@ -42,19 +42,19 @@ use crate::{ txpool::PoolBenchmarkDescription, }; -#[derive(Debug, StructOpt)] -#[structopt(name = "node-bench", about = "Node integration benchmarks")] +#[derive(Debug, Parser)] +#[command(name = "node-bench", about = "Node integration benchmarks")] struct Opt { /// Show list of all available benchmarks. /// /// Will output ("name", "path"). Benchmarks can then be filtered by path. - #[structopt(short, long)] + #[arg(short, long)] list: bool, /// Machine readable json output. /// /// This also suppresses all regular output (except to stderr) - #[structopt(short, long)] + #[arg(short, long)] json: bool, /// Filter benchmarks. @@ -63,7 +63,7 @@ struct Opt { filter: Option, /// Number of transactions for block import with `custom` size. - #[structopt(long)] + #[arg(long)] transactions: Option, /// Mode @@ -72,12 +72,12 @@ struct Opt { /// /// "profile" mode adds pauses between measurable runs, /// so that actual interval can be selected in the profiler of choice. - #[structopt(short, long, default_value = "regular")] + #[arg(short, long, default_value = "regular")] mode: BenchmarkMode, } fn main() { - let opt = Opt::from_args(); + let opt = Opt::parse(); if !opt.json { sp_tracing::try_init_simple(); @@ -85,7 +85,7 @@ fn main() { let mut import_benchmarks = Vec::new(); - for profile in [Profile::Wasm, Profile::Native].iter() { + for profile in [Profile::Wasm, Profile::Native] { for size in [ SizeType::Empty, SizeType::Small, @@ -93,25 +93,14 @@ fn main() { SizeType::Large, SizeType::Full, SizeType::Custom(opt.transactions.unwrap_or(0)), - ] - .iter() - { + ] { for block_type in [ BlockType::RandomTransfersKeepAlive, BlockType::RandomTransfersReaping, BlockType::Noop, - ] - .iter() - { - for database_type in - [BenchDataBaseType::RocksDb, BenchDataBaseType::ParityDb].iter() - { - import_benchmarks.push(( - profile, - size.clone(), - block_type.clone(), - database_type, - )); + ] { + for database_type in [BenchDataBaseType::RocksDb, BenchDataBaseType::ParityDb] { + import_benchmarks.push((profile, size, block_type, database_type)); } } } @@ -120,11 +109,11 @@ fn main() { let benchmarks = matrix!( (profile, size, block_type, database_type) in import_benchmarks.into_iter() => ImportBenchmarkDescription { - profile: *profile, + profile, key_types: KeyTypes::Sr25519, - size: size, - block_type: block_type, - database_type: *database_type, + size, + block_type, + database_type, }, (size, db_type) in [ diff --git a/bin/node/bench/src/simple_trie.rs b/bin/node/bench/src/simple_trie.rs index 651772c71575f..aa9c96a1cbd3f 100644 --- a/bin/node/bench/src/simple_trie.rs +++ b/bin/node/bench/src/simple_trie.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -33,7 +33,7 @@ pub struct SimpleTrie<'a> { impl<'a> AsHashDB for SimpleTrie<'a> { fn as_hash_db(&self) -> &dyn hash_db::HashDB { - &*self + self } fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn HashDB + 'b) { diff --git a/bin/node/bench/src/state_sizes.rs b/bin/node/bench/src/state_sizes.rs index 27112ed42d455..5387850666b6e 100644 --- a/bin/node/bench/src/state_sizes.rs +++ b/bin/node/bench/src/state_sizes.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ // along with this program. If not, see . /// Kusama value size distribution -pub const KUSAMA_STATE_DISTRIBUTION: &'static [(u32, u32)] = &[ +pub const KUSAMA_STATE_DISTRIBUTION: &[(u32, u32)] = &[ (32, 35), (33, 20035), (34, 5369), diff --git a/bin/node/bench/src/tempdb.rs b/bin/node/bench/src/tempdb.rs index 518c0dd96127c..82895ddfab69d 100644 --- a/bin/node/bench/src/tempdb.rs +++ b/bin/node/bench/src/tempdb.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,11 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use kvdb::{DBTransaction, KeyValueDB}; +use kvdb::{DBKeyValue, DBTransaction, KeyValueDB}; use kvdb_rocksdb::{Database, DatabaseConfig}; use std::{io, path::PathBuf, sync::Arc}; -#[derive(Debug, Clone, Copy, derive_more::Display)] +#[derive(Clone, Copy, Debug)] pub enum DatabaseType { RocksDb, ParityDb, @@ -29,7 +29,6 @@ pub enum DatabaseType { pub struct TempDatabase(tempfile::TempDir); struct ParityDbWrapper(parity_db::Db); -parity_util_mem::malloc_size_of_is_0!(ParityDbWrapper); impl KeyValueDB for ParityDbWrapper { /// Get a value by key. @@ -38,7 +37,7 @@ impl KeyValueDB for ParityDbWrapper { } /// Get a value by partial key. Only works for flushed data. - fn get_by_prefix(&self, _col: u32, _prefix: &[u8]) -> Option> { + fn get_by_prefix(&self, _col: u32, _prefix: &[u8]) -> io::Result>> { unimplemented!() } @@ -56,7 +55,7 @@ impl KeyValueDB for ParityDbWrapper { } /// Iterate over flushed data for a given column. - fn iter<'a>(&'a self, _col: u32) -> Box, Box<[u8]>)> + 'a> { + fn iter<'a>(&'a self, _col: u32) -> Box> + 'a> { unimplemented!() } @@ -65,12 +64,7 @@ impl KeyValueDB for ParityDbWrapper { &'a self, _col: u32, _prefix: &'a [u8], - ) -> Box, Box<[u8]>)> + 'a> { - unimplemented!() - } - - /// Attempt to replace this database with a new one located at the given path. - fn restore(&self, _new_db: &str) -> io::Result<()> { + ) -> Box> + 'a> { unimplemented!() } } diff --git a/bin/node/bench/src/trie.rs b/bin/node/bench/src/trie.rs index a17e386ca879b..de49a6fe7b6da 100644 --- a/bin/node/bench/src/trie.rs +++ b/bin/node/bench/src/trie.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,7 +23,7 @@ use kvdb::KeyValueDB; use lazy_static::lazy_static; use rand::Rng; use sp_state_machine::Backend as _; -use sp_trie::{trie_types::TrieDBMut, TrieMut as _}; +use sp_trie::{trie_types::TrieDBMutBuilderV1, TrieMut as _}; use std::{borrow::Cow, collections::HashMap, sync::Arc}; use node_primitives::Hash; @@ -155,7 +155,7 @@ impl core::BenchmarkDescription for TrieReadBenchmarkDescription { fn name(&self) -> Cow<'static, str> { format!( - "Trie read benchmark({} database ({} keys), db_type: {})", + "Trie read benchmark({:?} database ({} keys), db_type: {:?})", self.database_size, pretty_print(self.database_size.keys()), self.database_type, @@ -180,7 +180,7 @@ impl core::Benchmark for TrieReadBenchmark { let storage: Arc> = Arc::new(Storage(db.open(self.database_type))); - let trie_backend = sp_state_machine::TrieBackend::new(storage, self.root); + let trie_backend = sp_state_machine::TrieBackendBuilder::new(storage, self.root).build(); for (warmup_key, warmup_value) in self.warmup_keys.iter() { let value = trie_backend .storage(&warmup_key[..]) @@ -260,7 +260,7 @@ impl core::BenchmarkDescription for TrieWriteBenchmarkDescription { fn name(&self) -> Cow<'static, str> { format!( - "Trie write benchmark({} database ({} keys), db_type = {})", + "Trie write benchmark({:?} database ({} keys), db_type = {:?})", self.database_size, pretty_print(self.database_size.keys()), self.database_type, @@ -282,12 +282,11 @@ impl core::Benchmark for TrieWriteBenchmark { let mut db = self.database.clone(); let kvdb = db.open(self.database_type); - let mut new_root = self.root.clone(); + let mut new_root = self.root; let mut overlay = HashMap::new(); let mut trie = SimpleTrie { db: kvdb.clone(), overlay: &mut overlay }; - let mut trie_db_mut = - TrieDBMut::from_existing(&mut trie, &mut new_root).expect("Failed to create TrieDBMut"); + let mut trie_db_mut = TrieDBMutBuilderV1::from_existing(&mut trie, &mut new_root).build(); for (warmup_key, warmup_value) in self.warmup_keys.iter() { let value = trie_db_mut diff --git a/bin/node/bench/src/txpool.rs b/bin/node/bench/src/txpool.rs index b0db734534855..322dc352e8972 100644 --- a/bin/node/bench/src/txpool.rs +++ b/bin/node/bench/src/txpool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 6a12af4b278b7..4ee4bcd033921 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -4,11 +4,12 @@ version = "3.0.0-dev" authors = ["Parity Technologies "] description = "Generic Substrate node implementation in Rust." build = "build.rs" -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" default-run = "substrate" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.wasm-pack.profile.release] # `wasm-opt` has some problems on linux, see @@ -34,25 +35,27 @@ crate-type = ["cdylib", "rlib"] [dependencies] # third-party dependencies -codec = { package = "parity-scale-codec", version = "2.0.0" } -serde = { version = "1.0.126", features = ["derive"] } -futures = "0.3.16" -hex-literal = "0.3.1" -log = "0.4.8" -rand = "0.7.2" -structopt = { version = "0.3.8", optional = true } +array-bytes = "4.1" +clap = { version = "4.0.9", features = ["derive"], optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0" } +serde = { version = "1.0.136", features = ["derive"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } +futures = "0.3.21" +log = "0.4.17" +rand = "0.8" # primitives sp-authority-discovery = { version = "4.0.0-dev", path = "../../../primitives/authority-discovery" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } grandpa-primitives = { version = "4.0.0-dev", package = "sp-finality-grandpa", path = "../../../primitives/finality-grandpa" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } sp-authorship = { version = "4.0.0-dev", path = "../../../primitives/authorship" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-keyring = { version = "4.0.0-dev", path = "../../../primitives/keyring" } -sp-keystore = { version = "0.10.0-dev", path = "../../../primitives/keystore" } +sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-transaction-pool = { version = "4.0.0-dev", path = "../../../primitives/transaction-pool" } sp-transaction-storage-proof = { version = "4.0.0-dev", path = "../../../primitives/transaction-storage-proof" } @@ -64,6 +67,7 @@ sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/commo sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } sc-network = { version = "0.10.0-dev", path = "../../../client/network" } +sc-network-common = { version = "0.10.0-dev", path = "../../../client/network/common" } sc-consensus-slots = { version = "0.10.0-dev", path = "../../../client/consensus/slots" } sc-consensus-babe = { version = "0.10.0-dev", path = "../../../client/consensus/babe" } sc-consensus-uncles = { version = "0.10.0-dev", path = "../../../client/consensus/uncles" } @@ -75,14 +79,18 @@ sc-telemetry = { version = "4.0.0-dev", path = "../../../client/telemetry" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sc-authority-discovery = { version = "0.10.0-dev", path = "../../../client/authority-discovery" } sc-sync-state-rpc = { version = "0.10.0-dev", path = "../../../client/sync-state-rpc" } +sc-sysinfo = { version = "6.0.0-dev", path = "../../../client/sysinfo" } # frame dependencies frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } +frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../frame/system/rpc/runtime-api" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } +pallet-assets = { version = "4.0.0-dev", path = "../../../frame/assets/" } +pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment/" } pallet-im-online = { version = "4.0.0-dev", default-features = false, path = "../../../frame/im-online" } # node-specific dependencies -node-runtime = { version = "3.0.0-dev", path = "../runtime" } +kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } node-rpc = { version = "3.0.0-dev", path = "../rpc" } node-primitives = { version = "2.0.0", path = "../primitives" } node-executor = { version = "3.0.0-dev", path = "../executor" } @@ -92,20 +100,7 @@ sc-cli = { version = "0.10.0-dev", optional = true, path = "../../../client/cli" frame-benchmarking-cli = { version = "4.0.0-dev", optional = true, path = "../../../utils/frame/benchmarking-cli" } node-inspect = { version = "0.9.0-dev", optional = true, path = "../inspect" } try-runtime-cli = { version = "0.10.0-dev", optional = true, path = "../../../utils/frame/try-runtime/cli" } - -[target.'cfg(target_arch="x86_64")'.dependencies] -node-executor = { version = "3.0.0-dev", path = "../executor", features = [ - "wasmtime", -] } -sc-cli = { version = "0.10.0-dev", optional = true, path = "../../../client/cli", features = [ - "wasmtime", -] } -sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service", features = [ - "wasmtime", -] } -sp-trie = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/trie", features = [ - "memory-tracker", -] } +serde_json = "1.0.85" [dev-dependencies] sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } @@ -114,43 +109,60 @@ sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/commo sc-consensus-babe = { version = "0.10.0-dev", path = "../../../client/consensus/babe" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../../../client/consensus/epochs" } sc-service-test = { version = "2.0.0", path = "../../../client/service/test" } -sp-tracing = { version = "4.0.0-dev", path = "../../../primitives/tracing" } -futures = "0.3.16" +sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +futures = "0.3.21" tempfile = "3.1.0" -assert_cmd = "1.0" -nix = "0.19" +assert_cmd = "2.0.2" +nix = "0.23" serde_json = "1.0" -regex = "1" -platforms = "1.1" -async-std = { version = "1.6.5", features = ["attributes"] } -soketto = "0.4.2" +regex = "1.6.0" +platforms = "2.0" +soketto = "0.7.1" +criterion = { version = "0.3.5", features = ["async_tokio"] } +tokio = { version = "1.22.0", features = ["macros", "time", "parking_lot"] } +tokio-util = { version = "0.7.4", features = ["compat"] } +wait-timeout = "0.2" +substrate-rpc-client = { path = "../../../utils/frame/rpc/client" } +pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } [build-dependencies] -structopt = { version = "0.3.8", optional = true } +clap = { version = "4.0.9", optional = true } +clap_complete = { version = "4.0.2", optional = true } node-inspect = { version = "0.9.0-dev", optional = true, path = "../inspect" } frame-benchmarking-cli = { version = "4.0.0-dev", optional = true, path = "../../../utils/frame/benchmarking-cli" } substrate-build-script-utils = { version = "3.0.0", optional = true, path = "../../../utils/build-script-utils" } substrate-frame-cli = { version = "4.0.0-dev", optional = true, path = "../../../utils/frame/frame-utilities-cli" } try-runtime-cli = { version = "0.10.0-dev", optional = true, path = "../../../utils/frame/try-runtime/cli" } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli", optional = true } +pallet-balances = { version = "4.0.0-dev", path = "../../../frame/balances" } [features] -default = [ "cli" ] +default = ["cli"] cli = [ - "node-executor/wasmi-errno", "node-inspect", "sc-cli", "frame-benchmarking-cli", "substrate-frame-cli", - "sc-service/db", - "structopt", + "sc-service/rocksdb", + "clap", + "clap_complete", "substrate-build-script-utils", "try-runtime-cli", ] runtime-benchmarks = [ - "node-runtime/runtime-benchmarks", - "frame-benchmarking-cli", + "kitchensink-runtime/runtime-benchmarks", + "frame-benchmarking-cli/runtime-benchmarks" ] # Enable features that allow the runtime to be tried and debugged. Name might be subject to change # in the near future. -try-runtime = ["node-runtime/try-runtime", "try-runtime-cli"] +try-runtime = ["kitchensink-runtime/try-runtime", "try-runtime-cli/try-runtime"] + +[[bench]] +name = "transaction_pool" +harness = false + +[[bench]] +name = "block_production" +harness = false diff --git a/bin/node/cli/benches/block_production.rs b/bin/node/cli/benches/block_production.rs new file mode 100644 index 0000000000000..4fcebb123d9e3 --- /dev/null +++ b/bin/node/cli/benches/block_production.rs @@ -0,0 +1,243 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; + +use kitchensink_runtime::{constants::currency::*, BalancesCall}; +use node_cli::service::{create_extrinsic, FullClient}; +use sc_block_builder::{BlockBuilderProvider, BuiltBlock, RecordProof}; +use sc_client_api::execution_extensions::ExecutionStrategies; +use sc_consensus::{ + block_import::{BlockImportParams, ForkChoiceStrategy}, + BlockImport, StateAction, +}; +use sc_service::{ + config::{ + BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, + PruningMode, WasmExecutionMethod, WasmtimeInstantiationStrategy, + }, + BasePath, Configuration, Role, +}; +use sp_blockchain::{ApplyExtrinsicFailed::Validity, Error::ApplyExtrinsicFailed}; +use sp_consensus::BlockOrigin; +use sp_keyring::Sr25519Keyring; +use sp_runtime::{ + generic::BlockId, + transaction_validity::{InvalidTransaction, TransactionValidityError}, + AccountId32, MultiAddress, OpaqueExtrinsic, +}; +use tokio::runtime::Handle; + +fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { + let base_path = BasePath::new_temp_dir() + .expect("getting the base path of a temporary path doesn't fail; qed"); + let root = base_path.path().to_path_buf(); + + let network_config = NetworkConfiguration::new( + Sr25519Keyring::Alice.to_seed(), + "network/test/0.1", + Default::default(), + None, + ); + + let spec = Box::new(node_cli::chain_spec::development_config()); + + // NOTE: We enforce the use of the WASM runtime to benchmark block production using WASM. + let execution_strategy = sc_client_api::ExecutionStrategy::AlwaysWasm; + + let config = Configuration { + impl_name: "BenchmarkImpl".into(), + impl_version: "1.0".into(), + // We don't use the authority role since that would start producing blocks + // in the background which would mess with our benchmark. + role: Role::Full, + tokio_handle, + transaction_pool: Default::default(), + network: network_config, + keystore: KeystoreConfig::InMemory, + keystore_remote: Default::default(), + database: DatabaseSource::RocksDb { path: root.join("db"), cache_size: 128 }, + trie_cache_maximum_size: Some(64 * 1024 * 1024), + state_pruning: Some(PruningMode::ArchiveAll), + blocks_pruning: BlocksPruning::KeepAll, + chain_spec: spec, + wasm_method: WasmExecutionMethod::Compiled { + instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + }, + execution_strategies: ExecutionStrategies { + syncing: execution_strategy, + importing: execution_strategy, + block_construction: execution_strategy, + offchain_worker: execution_strategy, + other: execution_strategy, + }, + rpc_http: None, + rpc_ws: None, + rpc_ipc: None, + rpc_ws_max_connections: None, + rpc_cors: None, + rpc_methods: Default::default(), + rpc_max_payload: None, + rpc_max_request_size: None, + rpc_max_response_size: None, + rpc_id_provider: None, + rpc_max_subs_per_conn: None, + ws_max_out_buffer_capacity: None, + prometheus_config: None, + telemetry_endpoints: None, + default_heap_pages: None, + offchain_worker: OffchainWorkerConfig { enabled: true, indexing_enabled: false }, + force_authoring: false, + disable_grandpa: false, + dev_key_seed: Some(Sr25519Keyring::Alice.to_seed()), + tracing_targets: None, + tracing_receiver: Default::default(), + max_runtime_instances: 8, + runtime_cache_size: 2, + announce_block: true, + base_path: Some(base_path), + informant_output_format: Default::default(), + wasm_runtime_overrides: None, + }; + + node_cli::service::new_full_base(config, false, |_, _| ()) + .expect("creating a full node doesn't fail") +} + +fn extrinsic_set_time(now: u64) -> OpaqueExtrinsic { + kitchensink_runtime::UncheckedExtrinsic { + signature: None, + function: kitchensink_runtime::RuntimeCall::Timestamp(pallet_timestamp::Call::set { now }), + } + .into() +} + +fn import_block( + mut client: &FullClient, + built: BuiltBlock< + node_primitives::Block, + >::StateBackend, + >, +) { + let mut params = BlockImportParams::new(BlockOrigin::File, built.block.header); + params.state_action = + StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes(built.storage_changes)); + params.fork_choice = Some(ForkChoiceStrategy::LongestChain); + futures::executor::block_on(client.import_block(params, Default::default())) + .expect("importing a block doesn't fail"); +} + +fn prepare_benchmark(client: &FullClient) -> (usize, Vec) { + const MINIMUM_PERIOD_FOR_BLOCKS: u64 = 1500; + + let mut max_transfer_count = 0; + let mut extrinsics = Vec::new(); + let mut block_builder = client.new_block(Default::default()).unwrap(); + + // Every block needs one timestamp extrinsic. + let extrinsic_set_time = extrinsic_set_time(1 + MINIMUM_PERIOD_FOR_BLOCKS); + block_builder.push(extrinsic_set_time.clone()).unwrap(); + extrinsics.push(extrinsic_set_time); + + // Creating those is surprisingly costly, so let's only do it once and later just `clone` them. + let src = Sr25519Keyring::Alice.pair(); + let dst: MultiAddress = Sr25519Keyring::Bob.to_account_id().into(); + + // Add as many tranfer extrinsics as possible into a single block. + for nonce in 0.. { + let extrinsic: OpaqueExtrinsic = create_extrinsic( + client, + src.clone(), + BalancesCall::transfer { dest: dst.clone(), value: 1 * DOLLARS }, + Some(nonce), + ) + .into(); + + match block_builder.push(extrinsic.clone()) { + Ok(_) => {}, + Err(ApplyExtrinsicFailed(Validity(TransactionValidityError::Invalid( + InvalidTransaction::ExhaustsResources, + )))) => break, + Err(error) => panic!("{}", error), + } + + extrinsics.push(extrinsic); + max_transfer_count += 1; + } + + (max_transfer_count, extrinsics) +} + +fn block_production(c: &mut Criterion) { + sp_tracing::try_init_simple(); + + let runtime = tokio::runtime::Runtime::new().expect("creating tokio runtime doesn't fail; qed"); + let tokio_handle = runtime.handle().clone(); + + let node = new_node(tokio_handle.clone()); + let client = &*node.client; + + // Buliding the very first block is around ~30x slower than any subsequent one, + // so let's make sure it's built and imported before we benchmark anything. + let mut block_builder = client.new_block(Default::default()).unwrap(); + block_builder.push(extrinsic_set_time(1)).unwrap(); + import_block(client, block_builder.build().unwrap()); + + let (max_transfer_count, extrinsics) = prepare_benchmark(&client); + log::info!("Maximum transfer count: {}", max_transfer_count); + + let mut group = c.benchmark_group("Block production"); + + group.sample_size(10); + group.throughput(Throughput::Elements(max_transfer_count as u64)); + + let block_id = BlockId::Hash(client.chain_info().best_hash); + + group.bench_function(format!("{} transfers (no proof)", max_transfer_count), |b| { + b.iter_batched( + || extrinsics.clone(), + |extrinsics| { + let mut block_builder = + client.new_block_at(&block_id, Default::default(), RecordProof::No).unwrap(); + for extrinsic in extrinsics { + block_builder.push(extrinsic).unwrap(); + } + block_builder.build().unwrap() + }, + BatchSize::SmallInput, + ) + }); + + group.bench_function(format!("{} transfers (with proof)", max_transfer_count), |b| { + b.iter_batched( + || extrinsics.clone(), + |extrinsics| { + let mut block_builder = + client.new_block_at(&block_id, Default::default(), RecordProof::Yes).unwrap(); + for extrinsic in extrinsics { + block_builder.push(extrinsic).unwrap(); + } + block_builder.build().unwrap() + }, + BatchSize::SmallInput, + ) + }); +} + +criterion_group!(benches, block_production); +criterion_main!(benches); diff --git a/bin/node/cli/benches/transaction_pool.rs b/bin/node/cli/benches/transaction_pool.rs new file mode 100644 index 0000000000000..a8839642ddc26 --- /dev/null +++ b/bin/node/cli/benches/transaction_pool.rs @@ -0,0 +1,278 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use std::time::Duration; + +use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; +use futures::{future, StreamExt}; +use kitchensink_runtime::{constants::currency::*, BalancesCall, SudoCall}; +use node_cli::service::{create_extrinsic, fetch_nonce, FullClient, TransactionPool}; +use node_primitives::AccountId; +use sc_client_api::execution_extensions::ExecutionStrategies; +use sc_service::{ + config::{ + BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, + PruningMode, TransactionPoolOptions, WasmExecutionMethod, + }, + BasePath, Configuration, Role, +}; +use sc_transaction_pool::PoolLimit; +use sc_transaction_pool_api::{TransactionPool as _, TransactionSource, TransactionStatus}; +use sp_core::{crypto::Pair, sr25519}; +use sp_keyring::Sr25519Keyring; +use sp_runtime::{generic::BlockId, OpaqueExtrinsic}; +use tokio::runtime::Handle; + +fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { + let base_path = BasePath::new_temp_dir().expect("Creates base path"); + let root = base_path.path().to_path_buf(); + + let network_config = NetworkConfiguration::new( + Sr25519Keyring::Alice.to_seed(), + "network/test/0.1", + Default::default(), + None, + ); + + let spec = Box::new(node_cli::chain_spec::development_config()); + + let config = Configuration { + impl_name: "BenchmarkImpl".into(), + impl_version: "1.0".into(), + role: Role::Authority, + tokio_handle, + transaction_pool: TransactionPoolOptions { + ready: PoolLimit { count: 100_000, total_bytes: 100 * 1024 * 1024 }, + future: PoolLimit { count: 100_000, total_bytes: 100 * 1024 * 1024 }, + reject_future_transactions: false, + ban_time: Duration::from_secs(30 * 60), + }, + network: network_config, + keystore: KeystoreConfig::InMemory, + keystore_remote: Default::default(), + database: DatabaseSource::RocksDb { path: root.join("db"), cache_size: 128 }, + trie_cache_maximum_size: Some(64 * 1024 * 1024), + state_pruning: Some(PruningMode::ArchiveAll), + blocks_pruning: BlocksPruning::KeepAll, + chain_spec: spec, + wasm_method: WasmExecutionMethod::Interpreted, + // NOTE: we enforce the use of the native runtime to make the errors more debuggable + execution_strategies: ExecutionStrategies { + syncing: sc_client_api::ExecutionStrategy::NativeWhenPossible, + importing: sc_client_api::ExecutionStrategy::NativeWhenPossible, + block_construction: sc_client_api::ExecutionStrategy::NativeWhenPossible, + offchain_worker: sc_client_api::ExecutionStrategy::NativeWhenPossible, + other: sc_client_api::ExecutionStrategy::NativeWhenPossible, + }, + rpc_http: None, + rpc_ws: None, + rpc_ipc: None, + rpc_ws_max_connections: None, + rpc_cors: None, + rpc_methods: Default::default(), + rpc_max_payload: None, + rpc_max_request_size: None, + rpc_max_response_size: None, + rpc_id_provider: None, + rpc_max_subs_per_conn: None, + ws_max_out_buffer_capacity: None, + prometheus_config: None, + telemetry_endpoints: None, + default_heap_pages: None, + offchain_worker: OffchainWorkerConfig { enabled: true, indexing_enabled: false }, + force_authoring: false, + disable_grandpa: false, + dev_key_seed: Some(Sr25519Keyring::Alice.to_seed()), + tracing_targets: None, + tracing_receiver: Default::default(), + max_runtime_instances: 8, + runtime_cache_size: 2, + announce_block: true, + base_path: Some(base_path), + informant_output_format: Default::default(), + wasm_runtime_overrides: None, + }; + + node_cli::service::new_full_base(config, false, |_, _| ()).expect("Creates node") +} + +fn create_accounts(num: usize) -> Vec { + (0..num) + .map(|i| { + Pair::from_string(&format!("{}/{}", Sr25519Keyring::Alice.to_seed(), i), None) + .expect("Creates account pair") + }) + .collect() +} + +/// Create the extrinsics that will initialize the accounts from the sudo account (Alice). +/// +/// `start_nonce` is the current nonce of Alice. +fn create_account_extrinsics( + client: &FullClient, + accounts: &[sr25519::Pair], +) -> Vec { + let start_nonce = fetch_nonce(client, Sr25519Keyring::Alice.pair()); + + accounts + .iter() + .enumerate() + .flat_map(|(i, a)| { + vec![ + // Reset the nonce by removing any funds + create_extrinsic( + client, + Sr25519Keyring::Alice.pair(), + SudoCall::sudo { + call: Box::new( + BalancesCall::set_balance { + who: AccountId::from(a.public()).into(), + new_free: 0, + new_reserved: 0, + } + .into(), + ), + }, + Some(start_nonce + (i as u32) * 2), + ), + // Give back funds + create_extrinsic( + client, + Sr25519Keyring::Alice.pair(), + SudoCall::sudo { + call: Box::new( + BalancesCall::set_balance { + who: AccountId::from(a.public()).into(), + new_free: 1_000_000 * DOLLARS, + new_reserved: 0, + } + .into(), + ), + }, + Some(start_nonce + (i as u32) * 2 + 1), + ), + ] + }) + .map(OpaqueExtrinsic::from) + .collect() +} + +fn create_benchmark_extrinsics( + client: &FullClient, + accounts: &[sr25519::Pair], + extrinsics_per_account: usize, +) -> Vec { + accounts + .iter() + .flat_map(|account| { + (0..extrinsics_per_account).map(move |nonce| { + create_extrinsic( + client, + account.clone(), + BalancesCall::transfer { + dest: Sr25519Keyring::Bob.to_account_id().into(), + value: 1 * DOLLARS, + }, + Some(nonce as u32), + ) + }) + }) + .map(OpaqueExtrinsic::from) + .collect() +} + +async fn submit_tx_and_wait_for_inclusion( + tx_pool: &TransactionPool, + tx: OpaqueExtrinsic, + client: &FullClient, + wait_for_finalized: bool, +) { + let best_hash = client.chain_info().best_hash; + + let mut watch = tx_pool + .submit_and_watch(&BlockId::Hash(best_hash), TransactionSource::External, tx.clone()) + .await + .expect("Submits tx to pool") + .fuse(); + + loop { + match watch.select_next_some().await { + TransactionStatus::Finalized(_) => break, + TransactionStatus::InBlock(_) if !wait_for_finalized => break, + _ => {}, + } + } +} + +fn transaction_pool_benchmarks(c: &mut Criterion) { + sp_tracing::try_init_simple(); + + let runtime = tokio::runtime::Runtime::new().expect("Creates tokio runtime"); + let tokio_handle = runtime.handle().clone(); + + let node = new_node(tokio_handle.clone()); + + let account_num = 10; + let extrinsics_per_account = 2000; + let accounts = create_accounts(account_num); + + let mut group = c.benchmark_group("Transaction pool"); + + group.sample_size(10); + group.throughput(Throughput::Elements(account_num as u64 * extrinsics_per_account as u64)); + + let mut counter = 1; + group.bench_function( + format!("{} transfers from {} accounts", account_num * extrinsics_per_account, account_num), + move |b| { + b.iter_batched( + || { + let prepare_extrinsics = create_account_extrinsics(&node.client, &accounts); + + runtime.block_on(future::join_all(prepare_extrinsics.into_iter().map(|tx| { + submit_tx_and_wait_for_inclusion( + &node.transaction_pool, + tx, + &node.client, + true, + ) + }))); + + create_benchmark_extrinsics(&node.client, &accounts, extrinsics_per_account) + }, + |extrinsics| { + runtime.block_on(future::join_all(extrinsics.into_iter().map(|tx| { + submit_tx_and_wait_for_inclusion( + &node.transaction_pool, + tx, + &node.client, + false, + ) + }))); + + println!("Finished {}", counter); + counter += 1; + }, + BatchSize::SmallInput, + ) + }, + ); +} + +criterion_group!(benches, transaction_pool_benchmarks); +criterion_main!(benches); diff --git a/bin/node/cli/bin/main.rs b/bin/node/cli/bin/main.rs index cf32a7cf28860..3ae295754561c 100644 --- a/bin/node/cli/bin/main.rs +++ b/bin/node/cli/bin/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/bin/node/cli/build.rs b/bin/node/cli/build.rs index 90aec2222c9ec..e8142b297f1b2 100644 --- a/bin/node/cli/build.rs +++ b/bin/node/cli/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -25,7 +25,8 @@ fn main() { mod cli { include!("src/cli.rs"); - use sc_cli::structopt::clap::Shell; + use clap::{CommandFactory, ValueEnum}; + use clap_complete::{generate_to, Shell}; use std::{env, fs, path::Path}; use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; @@ -37,9 +38,8 @@ mod cli { } /// Build shell completion scripts for all known shells - /// Full list in https://github.com/kbknapp/clap-rs/blob/e9d0562a1dc5dfe731ed7c767e6cee0af08f0cf9/src/app/parser.rs#L123 fn build_shell_completion() { - for shell in &[Shell::Bash, Shell::Fish, Shell::Zsh, Shell::Elvish, Shell::PowerShell] { + for shell in Shell::value_variants() { build_completion(shell); } } @@ -61,6 +61,6 @@ mod cli { fs::create_dir(&path).ok(); - Cli::clap().gen_completions("substrate-node", *shell, &path); + let _ = generate_to(*shell, &mut Cli::command(), "substrate-node", &path); } } diff --git a/bin/node/cli/src/benchmarking.rs b/bin/node/cli/src/benchmarking.rs new file mode 100644 index 0000000000000..16ea9109d0c1f --- /dev/null +++ b/bin/node/cli/src/benchmarking.rs @@ -0,0 +1,122 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Setup code for [`super::command`] which would otherwise bloat that module. +//! +//! Should only be used for benchmarking as it may break in other contexts. + +use crate::service::{create_extrinsic, FullClient}; + +use kitchensink_runtime::{BalancesCall, SystemCall}; +use node_primitives::{AccountId, Balance}; +use sc_cli::Result; +use sp_inherents::{InherentData, InherentDataProvider}; +use sp_keyring::Sr25519Keyring; +use sp_runtime::OpaqueExtrinsic; + +use std::{sync::Arc, time::Duration}; + +/// Generates `System::Remark` extrinsics for the benchmarks. +/// +/// Note: Should only be used for benchmarking. +pub struct RemarkBuilder { + client: Arc, +} + +impl RemarkBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc) -> Self { + Self { client } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder { + fn pallet(&self) -> &str { + "system" + } + + fn extrinsic(&self) -> &str { + "remark" + } + + fn build(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_extrinsic( + self.client.as_ref(), + acc, + SystemCall::remark { remark: vec![] }, + Some(nonce), + ) + .into(); + + Ok(extrinsic) + } +} + +/// Generates `Balances::TransferKeepAlive` extrinsics for the benchmarks. +/// +/// Note: Should only be used for benchmarking. +pub struct TransferKeepAliveBuilder { + client: Arc, + dest: AccountId, + value: Balance, +} + +impl TransferKeepAliveBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc, dest: AccountId, value: Balance) -> Self { + Self { client, dest, value } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { + fn pallet(&self) -> &str { + "balances" + } + + fn extrinsic(&self) -> &str { + "transfer_keep_alive" + } + + fn build(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_extrinsic( + self.client.as_ref(), + acc, + BalancesCall::transfer_keep_alive { + dest: self.dest.clone().into(), + value: self.value.into(), + }, + Some(nonce), + ) + .into(); + + Ok(extrinsic) + } +} + +/// Generates inherent data for the `benchmark overhead` command. +pub fn inherent_benchmark_data() -> Result { + let mut inherent_data = InherentData::new(); + let d = Duration::from_millis(0); + let timestamp = sp_timestamp::InherentDataProvider::new(d.into()); + + futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data)) + .map_err(|e| format!("creating inherent data: {:?}", e))?; + Ok(inherent_data) +} diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 352e007a891ba..1e4e806fd2736 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,12 +19,12 @@ //! Substrate chain configurations. use grandpa_primitives::AuthorityId as GrandpaId; -use hex_literal::hex; -use node_runtime::{ +use kitchensink_runtime::{ constants::currency::*, wasm_binary_unwrap, AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, Block, CouncilConfig, DemocracyConfig, ElectionsConfig, GrandpaConfig, - ImOnlineConfig, IndicesConfig, SessionConfig, SessionKeys, SocietyConfig, StakerStatus, - StakingConfig, SudoConfig, SystemConfig, TechnicalCommitteeConfig, MAX_NOMINATIONS, + ImOnlineConfig, IndicesConfig, MaxNominations, NominationPoolsConfig, SessionConfig, + SessionKeys, SocietyConfig, StakerStatus, StakingConfig, SudoConfig, SystemConfig, + TechnicalCommitteeConfig, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sc_chain_spec::ChainSpecExtension; @@ -39,8 +39,8 @@ use sp_runtime::{ Perbill, }; +pub use kitchensink_runtime::GenesisConfig; pub use node_primitives::{AccountId, Balance, Signature}; -pub use node_runtime::GenesisConfig; type AccountPublic = ::Signer; @@ -97,84 +97,83 @@ fn staging_testnet_config_genesis() -> GenesisConfig { )> = vec![ ( // 5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy - hex!["9c7a2ee14e565db0c69f78c7b4cd839fbf52b607d867e9e9c5a79042898a0d12"].into(), + array_bytes::hex_n_into_unchecked("9c7a2ee14e565db0c69f78c7b4cd839fbf52b607d867e9e9c5a79042898a0d12"), // 5EnCiV7wSHeNhjW3FSUwiJNkcc2SBkPLn5Nj93FmbLtBjQUq - hex!["781ead1e2fa9ccb74b44c19d29cb2a7a4b5be3972927ae98cd3877523976a276"].into(), + array_bytes::hex_n_into_unchecked("781ead1e2fa9ccb74b44c19d29cb2a7a4b5be3972927ae98cd3877523976a276"), // 5Fb9ayurnxnaXj56CjmyQLBiadfRCqUbL2VWNbbe1nZU6wiC - hex!["9becad03e6dcac03cee07edebca5475314861492cdfc96a2144a67bbe9699332"] + array_bytes::hex2array_unchecked("9becad03e6dcac03cee07edebca5475314861492cdfc96a2144a67bbe9699332") .unchecked_into(), // 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8 - hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"] + array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106") .unchecked_into(), // 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8 - hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"] + array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106") .unchecked_into(), // 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8 - hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"] + array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106") .unchecked_into(), ), ( // 5ERawXCzCWkjVq3xz1W5KGNtVx2VdefvZ62Bw1FEuZW4Vny2 - hex!["68655684472b743e456907b398d3a44c113f189e56d1bbfd55e889e295dfde78"].into(), + array_bytes::hex_n_into_unchecked("68655684472b743e456907b398d3a44c113f189e56d1bbfd55e889e295dfde78"), // 5Gc4vr42hH1uDZc93Nayk5G7i687bAQdHHc9unLuyeawHipF - hex!["c8dc79e36b29395413399edaec3e20fcca7205fb19776ed8ddb25d6f427ec40e"].into(), + array_bytes::hex_n_into_unchecked("c8dc79e36b29395413399edaec3e20fcca7205fb19776ed8ddb25d6f427ec40e"), // 5EockCXN6YkiNCDjpqqnbcqd4ad35nU4RmA1ikM4YeRN4WcE - hex!["7932cff431e748892fa48e10c63c17d30f80ca42e4de3921e641249cd7fa3c2f"] + array_bytes::hex2array_unchecked("7932cff431e748892fa48e10c63c17d30f80ca42e4de3921e641249cd7fa3c2f") .unchecked_into(), // 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ - hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"] + array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e") .unchecked_into(), // 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ - hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"] + array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e") .unchecked_into(), // 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ - hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"] + array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e") .unchecked_into(), ), ( // 5DyVtKWPidondEu8iHZgi6Ffv9yrJJ1NDNLom3X9cTDi98qp - hex!["547ff0ab649283a7ae01dbc2eb73932eba2fb09075e9485ff369082a2ff38d65"].into(), + array_bytes::hex_n_into_unchecked("547ff0ab649283a7ae01dbc2eb73932eba2fb09075e9485ff369082a2ff38d65"), // 5FeD54vGVNpFX3PndHPXJ2MDakc462vBCD5mgtWRnWYCpZU9 - hex!["9e42241d7cd91d001773b0b616d523dd80e13c6c2cab860b1234ef1b9ffc1526"].into(), + array_bytes::hex_n_into_unchecked("9e42241d7cd91d001773b0b616d523dd80e13c6c2cab860b1234ef1b9ffc1526"), // 5E1jLYfLdUQKrFrtqoKgFrRvxM3oQPMbf6DfcsrugZZ5Bn8d - hex!["5633b70b80a6c8bb16270f82cca6d56b27ed7b76c8fd5af2986a25a4788ce440"] + array_bytes::hex2array_unchecked("5633b70b80a6c8bb16270f82cca6d56b27ed7b76c8fd5af2986a25a4788ce440") .unchecked_into(), // 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH - hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"] + array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a") .unchecked_into(), // 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH - hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"] + array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a") .unchecked_into(), // 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH - hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"] + array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a") .unchecked_into(), ), ( // 5HYZnKWe5FVZQ33ZRJK1rG3WaLMztxWrrNDb1JRwaHHVWyP9 - hex!["f26cdb14b5aec7b2789fd5ca80f979cef3761897ae1f37ffb3e154cbcc1c2663"].into(), + array_bytes::hex_n_into_unchecked("f26cdb14b5aec7b2789fd5ca80f979cef3761897ae1f37ffb3e154cbcc1c2663"), // 5EPQdAQ39WQNLCRjWsCk5jErsCitHiY5ZmjfWzzbXDoAoYbn - hex!["66bc1e5d275da50b72b15de072a2468a5ad414919ca9054d2695767cf650012f"].into(), + array_bytes::hex_n_into_unchecked("66bc1e5d275da50b72b15de072a2468a5ad414919ca9054d2695767cf650012f"), // 5DMa31Hd5u1dwoRKgC4uvqyrdK45RHv3CpwvpUC1EzuwDit4 - hex!["3919132b851ef0fd2dae42a7e734fe547af5a6b809006100f48944d7fae8e8ef"] + array_bytes::hex2array_unchecked("3919132b851ef0fd2dae42a7e734fe547af5a6b809006100f48944d7fae8e8ef") .unchecked_into(), // 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x - hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"] + array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378") .unchecked_into(), // 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x - hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"] + array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378") .unchecked_into(), // 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x - hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"] + array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378") .unchecked_into(), ), ]; // generated with secret: subkey inspect "$secret"/fir - let root_key: AccountId = hex![ + let root_key: AccountId = array_bytes::hex_n_into_unchecked( // 5Ff3iXP75ruzroPWRP2FYBHWnmGGBSb63857BgnzCoXNxfPo - "9ee5e5bdc0ec239eb164f865ecc345ce4c88e76ee002e0f7e318097347471809" - ] - .into(); + "9ee5e5bdc0ec239eb164f865ecc345ce4c88e76ee002e0f7e318097347471809", + ); let endowed_accounts: Vec = vec![root_key.clone()]; @@ -196,6 +195,7 @@ pub fn staging_testnet_config() -> ChainSpec { ), None, None, + None, Default::default(), ) } @@ -265,7 +265,7 @@ pub fn testnet_genesis( .map(|x| &x.0) .chain(initial_nominators.iter()) .for_each(|x| { - if !endowed_accounts.contains(&x) { + if !endowed_accounts.contains(x) { endowed_accounts.push(x.clone()) } }); @@ -277,7 +277,7 @@ pub fn testnet_genesis( .map(|x| (x.0.clone(), x.1.clone(), STASH, StakerStatus::Validator)) .chain(initial_nominators.iter().map(|x| { use rand::{seq::SliceRandom, Rng}; - let limit = (MAX_NOMINATIONS as usize).min(initial_authorities.len()); + let limit = (MaxNominations::get() as usize).min(initial_authorities.len()); let count = rng.gen::() % limit; let nominations = initial_authorities .as_slice() @@ -295,10 +295,7 @@ pub fn testnet_genesis( const STASH: Balance = ENDOWMENT / 1000; GenesisConfig { - system: SystemConfig { - code: wasm_binary_unwrap().to_vec(), - changes_trie_config: Default::default(), - }, + system: SystemConfig { code: wasm_binary_unwrap().to_vec() }, balances: BalancesConfig { balances: endowed_accounts.iter().cloned().map(|x| (x, ENDOWMENT)).collect(), }, @@ -341,10 +338,10 @@ pub fn testnet_genesis( .collect(), phantom: Default::default(), }, - sudo: SudoConfig { key: root_key }, + sudo: SudoConfig { key: Some(root_key) }, babe: BabeConfig { authorities: vec![], - epoch_config: Some(node_runtime::BABE_GENESIS_EPOCH_CONFIG), + epoch_config: Some(kitchensink_runtime::BABE_GENESIS_EPOCH_CONFIG), }, im_online: ImOnlineConfig { keys: vec![] }, authority_discovery: AuthorityDiscoveryConfig { keys: vec![] }, @@ -361,8 +358,20 @@ pub fn testnet_genesis( max_members: 999, }, vesting: Default::default(), - gilt: Default::default(), + assets: pallet_assets::GenesisConfig { + // This asset is used by the NIS pallet as counterpart currency. + assets: vec![(9, get_account_id_from_seed::("Alice"), true, 1)], + ..Default::default() + }, transaction_storage: Default::default(), + transaction_payment: Default::default(), + alliance: Default::default(), + alliance_motion: Default::default(), + nomination_pools: NominationPoolsConfig { + min_create_bond: 10 * DOLLARS, + min_join_bond: 1 * DOLLARS, + ..Default::default() + }, } } @@ -386,6 +395,7 @@ pub fn development_config() -> ChainSpec { None, None, None, + None, Default::default(), ) } @@ -410,6 +420,7 @@ pub fn local_testnet_config() -> ChainSpec { None, None, None, + None, Default::default(), ) } @@ -417,7 +428,7 @@ pub fn local_testnet_config() -> ChainSpec { #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::service::{new_full_base, new_light_base, NewFullBase}; + use crate::service::{new_full_base, NewFullBase}; use sc_service_test; use sp_runtime::BuildStorage; @@ -441,6 +452,7 @@ pub(crate) mod tests { None, None, None, + None, Default::default(), ) } @@ -456,6 +468,7 @@ pub(crate) mod tests { None, None, None, + None, Default::default(), ) } @@ -465,28 +478,16 @@ pub(crate) mod tests { fn test_connectivity() { sp_tracing::try_init_simple(); - sc_service_test::connectivity( - integration_test_config_with_two_authorities(), - |config| { - let NewFullBase { task_manager, client, network, transaction_pool, .. } = - new_full_base(config, |_, _| ())?; - Ok(sc_service_test::TestNetComponents::new( - task_manager, - client, - network, - transaction_pool, - )) - }, - |config| { - let (keep_alive, _, client, network, transaction_pool) = new_light_base(config)?; - Ok(sc_service_test::TestNetComponents::new( - keep_alive, - client, - network, - transaction_pool, - )) - }, - ); + sc_service_test::connectivity(integration_test_config_with_two_authorities(), |config| { + let NewFullBase { task_manager, client, network, transaction_pool, .. } = + new_full_base(config, false, |_, _| ())?; + Ok(sc_service_test::TestNetComponents::new( + task_manager, + client, + network, + transaction_pool, + )) + }); } #[test] diff --git a/bin/node/cli/src/cli.rs b/bin/node/cli/src/cli.rs index 850581748fde3..bb7f8a4c60aa9 100644 --- a/bin/node/cli/src/cli.rs +++ b/bin/node/cli/src/cli.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,35 +16,41 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use sc_cli::{KeySubcommand, RunCmd, SignCmd, VanityCmd, VerifyCmd}; -use structopt::StructOpt; - /// An overarching CLI command definition. -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Parser)] pub struct Cli { /// Possible subcommand with parameters. - #[structopt(subcommand)] + #[command(subcommand)] pub subcommand: Option, + #[allow(missing_docs)] - #[structopt(flatten)] - pub run: RunCmd, + #[clap(flatten)] + pub run: sc_cli::RunCmd, + + /// Disable automatic hardware benchmarks. + /// + /// By default these benchmarks are automatically ran at startup and measure + /// the CPU speed, the memory bandwidth and the disk speed. + /// + /// The results are then printed out in the logs, and also sent as part of + /// telemetry, if telemetry is enabled. + #[arg(long)] + pub no_hardware_benchmarks: bool, } /// Possible subcommands of the main binary. -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Subcommand)] pub enum Subcommand { - /// Key management cli utilities - Key(KeySubcommand), - /// The custom inspect subcommmand for decoding blocks and extrinsics. - #[structopt( + #[command( name = "inspect", about = "Decode given block or extrinsic using current native runtime." )] Inspect(node_inspect::cli::InspectCmd), - /// The custom benchmark subcommmand benchmarking runtime pallets. - #[structopt(name = "benchmark", about = "Benchmark runtime pallets.")] + /// Sub-commands concerned with benchmarking. + /// The pallet benchmarking moved to the `pallet` sub-command. + #[command(subcommand)] Benchmark(frame_benchmarking_cli::BenchmarkCmd), /// Try some command against runtime state. @@ -55,14 +61,18 @@ pub enum Subcommand { #[cfg(not(feature = "try-runtime"))] TryRuntime, + /// Key management cli utilities + #[command(subcommand)] + Key(sc_cli::KeySubcommand), + /// Verify a signature for a message, provided on STDIN, with a given (public or secret) key. - Verify(VerifyCmd), + Verify(sc_cli::VerifyCmd), /// Generate a seed that provides a vanity address. - Vanity(VanityCmd), + Vanity(sc_cli::VanityCmd), /// Sign a message, with a given (secret) key. - Sign(SignCmd), + Sign(sc_cli::SignCmd), /// Build a chain specification. BuildSpec(sc_cli::BuildSpecCmd), @@ -84,4 +94,7 @@ pub enum Subcommand { /// Revert the chain to a previous state. Revert(sc_cli::RevertCmd), + + /// Db meta columns information. + ChainInfo(sc_cli::ChainInfoCmd), } diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index 17375094f2a1b..108d7743843b6 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,11 +16,21 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{chain_spec, service, service::new_partial, Cli, Subcommand}; +use super::benchmarking::{inherent_benchmark_data, RemarkBuilder, TransferKeepAliveBuilder}; +use crate::{ + chain_spec, service, + service::{new_partial, FullClient}, + Cli, Subcommand, +}; +use frame_benchmarking_cli::*; +use kitchensink_runtime::{ExistentialDeposit, RuntimeApi}; use node_executor::ExecutorDispatch; -use node_runtime::{Block, RuntimeApi}; -use sc_cli::{ChainSpec, Result, Role, RuntimeVersion, SubstrateCli}; +use node_primitives::Block; +use sc_cli::{ChainSpec, Result, RuntimeVersion, SubstrateCli}; use sc_service::PartialComponents; +use sp_keyring::Sr25519Keyring; + +use std::sync::Arc; impl SubstrateCli for Cli { fn impl_name() -> String { @@ -65,7 +75,7 @@ impl SubstrateCli for Cli { } fn native_runtime_version(_: &Box) -> &'static RuntimeVersion { - &node_runtime::VERSION + &kitchensink_runtime::VERSION } } @@ -77,11 +87,8 @@ pub fn run() -> Result<()> { None => { let runner = cli.create_runner(&cli.run)?; runner.run_node_until_exit(|config| async move { - match config.role { - Role::Light => service::new_light(config), - _ => service::new_full(config), - } - .map_err(sc_cli::Error::Service) + service::new_full(config, cli.no_hardware_benchmarks) + .map_err(sc_cli::Error::Service) }) }, Some(Subcommand::Inspect(cmd)) => { @@ -89,16 +96,81 @@ pub fn run() -> Result<()> { runner.sync_run(|config| cmd.run::(config)) }, - Some(Subcommand::Benchmark(cmd)) => - if cfg!(feature = "runtime-benchmarks") { - let runner = cli.create_runner(cmd)?; - - runner.sync_run(|config| cmd.run::(config)) - } else { - Err("Benchmarking wasn't enabled when building the node. \ - You can enable it with `--features runtime-benchmarks`." - .into()) - }, + Some(Subcommand::Benchmark(cmd)) => { + let runner = cli.create_runner(cmd)?; + + runner.sync_run(|config| { + // This switch needs to be in the client, since the client decides + // which sub-commands it wants to support. + match cmd { + BenchmarkCmd::Pallet(cmd) => { + if !cfg!(feature = "runtime-benchmarks") { + return Err( + "Runtime benchmarking wasn't enabled when building the node. \ + You can enable it with `--features runtime-benchmarks`." + .into(), + ) + } + + cmd.run::(config) + }, + BenchmarkCmd::Block(cmd) => { + // ensure that we keep the task manager alive + let partial = new_partial(&config)?; + cmd.run(partial.client) + }, + #[cfg(not(feature = "runtime-benchmarks"))] + BenchmarkCmd::Storage(_) => Err( + "Storage benchmarking can be enabled with `--features runtime-benchmarks`." + .into(), + ), + #[cfg(feature = "runtime-benchmarks")] + BenchmarkCmd::Storage(cmd) => { + // ensure that we keep the task manager alive + let partial = new_partial(&config)?; + let db = partial.backend.expose_db(); + let storage = partial.backend.expose_storage(); + + cmd.run(config, partial.client, db, storage) + }, + BenchmarkCmd::Overhead(cmd) => { + // ensure that we keep the task manager alive + let partial = new_partial(&config)?; + let ext_builder = RemarkBuilder::new(partial.client.clone()); + + cmd.run( + config, + partial.client, + inherent_benchmark_data()?, + Vec::new(), + &ext_builder, + ) + }, + BenchmarkCmd::Extrinsic(cmd) => { + // ensure that we keep the task manager alive + let partial = service::new_partial(&config)?; + // Register the *Remark* and *TKA* builders. + let ext_factory = ExtrinsicFactory(vec![ + Box::new(RemarkBuilder::new(partial.client.clone())), + Box::new(TransferKeepAliveBuilder::new( + partial.client.clone(), + Sr25519Keyring::Alice.to_account_id(), + ExistentialDeposit::get(), + )), + ]); + + cmd.run( + partial.client, + inherent_benchmark_data()?, + Vec::new(), + &ext_factory, + ) + }, + BenchmarkCmd::Machine(cmd) => + cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()), + } + }) + }, Some(Subcommand::Key(cmd)) => cmd.run(&cli), Some(Subcommand::Sign(cmd)) => cmd.run(), Some(Subcommand::Verify(cmd)) => cmd.run(), @@ -145,7 +217,12 @@ pub fn run() -> Result<()> { let runner = cli.create_runner(cmd)?; runner.async_run(|config| { let PartialComponents { client, task_manager, backend, .. } = new_partial(&config)?; - Ok((cmd.run(client, backend), task_manager)) + let aux_revert = Box::new(|client: Arc, backend, blocks| { + sc_consensus_babe::revert(client.clone(), backend, blocks)?; + grandpa::revert(client, blocks)?; + Ok(()) + }); + Ok((cmd.run(client, backend, Some(aux_revert)), task_manager)) }) }, #[cfg(feature = "try-runtime")] @@ -166,5 +243,9 @@ pub fn run() -> Result<()> { Some(Subcommand::TryRuntime) => Err("TryRuntime wasn't enabled when building the node. \ You can enable it with `--features try-runtime`." .into()), + Some(Subcommand::ChainInfo(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run::(&config)) + }, } } diff --git a/bin/node/cli/src/lib.rs b/bin/node/cli/src/lib.rs index 1a4c1b0eab8db..13c074268e50f 100644 --- a/bin/node/cli/src/lib.rs +++ b/bin/node/cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -33,7 +33,9 @@ pub mod chain_spec; #[macro_use] -mod service; +pub mod service; +#[cfg(feature = "cli")] +mod benchmarking; #[cfg(feature = "cli")] mod cli; #[cfg(feature = "cli")] diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index acc7df5b1e5a3..e7b825a8e5ef1 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,28 +20,110 @@ //! Service implementation. Specialized wrapper over substrate service. +use codec::Encode; +use frame_system_rpc_runtime_api::AccountNonceApi; use futures::prelude::*; +use kitchensink_runtime::RuntimeApi; use node_executor::ExecutorDispatch; use node_primitives::Block; -use node_runtime::RuntimeApi; -use sc_client_api::{ExecutorProvider, RemoteBackend}; +use sc_client_api::BlockBackend; use sc_consensus_babe::{self, SlotProportion}; use sc_executor::NativeElseWasmExecutor; -use sc_network::{Event, NetworkService}; +use sc_network::NetworkService; +use sc_network_common::{protocol::event::Event, service::NetworkEventStream}; use sc_service::{config::Configuration, error::Error as ServiceError, RpcHandlers, TaskManager}; use sc_telemetry::{Telemetry, TelemetryWorker}; -use sp_runtime::traits::Block as BlockT; +use sp_api::ProvideRuntimeApi; +use sp_core::crypto::Pair; +use sp_runtime::{generic, traits::Block as BlockT, SaturatedConversion}; use std::sync::Arc; -type FullClient = +/// The full client type definition. +pub type FullClient = sc_service::TFullClient>; type FullBackend = sc_service::TFullBackend; type FullSelectChain = sc_consensus::LongestChain; type FullGrandpaBlockImport = grandpa::GrandpaBlockImport; -type LightClient = - sc_service::TLightClient>; +/// The transaction pool type defintion. +pub type TransactionPool = sc_transaction_pool::FullPool; + +/// Fetch the nonce of the given `account` from the chain state. +/// +/// Note: Should only be used for tests. +pub fn fetch_nonce(client: &FullClient, account: sp_core::sr25519::Pair) -> u32 { + let best_hash = client.chain_info().best_hash; + client + .runtime_api() + .account_nonce(&generic::BlockId::Hash(best_hash), account.public().into()) + .expect("Fetching account nonce works; qed") +} + +/// Create a transaction using the given `call`. +/// +/// The transaction will be signed by `sender`. If `nonce` is `None` it will be fetched from the +/// state of the best block. +/// +/// Note: Should only be used for tests. +pub fn create_extrinsic( + client: &FullClient, + sender: sp_core::sr25519::Pair, + function: impl Into, + nonce: Option, +) -> kitchensink_runtime::UncheckedExtrinsic { + let function = function.into(); + let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); + let best_hash = client.chain_info().best_hash; + let best_block = client.chain_info().best_number; + let nonce = nonce.unwrap_or_else(|| fetch_nonce(client, sender.clone())); + + let period = kitchensink_runtime::BlockHashCount::get() + .checked_next_power_of_two() + .map(|c| c / 2) + .unwrap_or(2) as u64; + let tip = 0; + let extra: kitchensink_runtime::SignedExtra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(generic::Era::mortal( + period, + best_block.saturated_into(), + )), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_asset_tx_payment::ChargeAssetTxPayment::::from( + tip, None, + ), + ); + + let raw_payload = kitchensink_runtime::SignedPayload::from_raw( + function.clone(), + extra.clone(), + ( + (), + kitchensink_runtime::VERSION.spec_version, + kitchensink_runtime::VERSION.transaction_version, + genesis_hash, + best_hash, + (), + (), + (), + ), + ); + let signature = raw_payload.using_encoded(|e| sender.sign(e)); + + kitchensink_runtime::UncheckedExtrinsic::new_signed( + function, + sp_runtime::AccountId32::from(sender.public()).into(), + kitchensink_runtime::Signature::Sr25519(signature), + extra, + ) +} + +/// Creates a new partial node. pub fn new_partial( config: &Configuration, ) -> Result< @@ -55,7 +137,7 @@ pub fn new_partial( impl Fn( node_rpc::DenyUnsafe, sc_rpc::SubscriptionTaskExecutor, - ) -> Result, + ) -> Result, sc_service::Error>, ( sc_consensus_babe::BabeBlockImport, grandpa::LinkHalf, @@ -82,18 +164,19 @@ pub fn new_partial( config.wasm_method, config.default_heap_pages, config.max_runtime_instances, + config.runtime_cache_size, ); let (client, backend, keystore_container, task_manager) = sc_service::new_full_parts::( - &config, + config, telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), executor, )?; let client = Arc::new(client); let telemetry = telemetry.map(|(worker, telemetry)| { - task_manager.spawn_handle().spawn("telemetry", worker.run()); + task_manager.spawn_handle().spawn("telemetry", None, worker.run()); telemetry }); @@ -116,7 +199,7 @@ pub fn new_partial( let justification_import = grandpa_block_import.clone(); let (block_import, babe_link) = sc_consensus_babe::block_import( - sc_consensus_babe::Config::get_or_compute(&*client)?, + sc_consensus_babe::configuration(&*client)?, grandpa_block_import, client.clone(), )?; @@ -132,7 +215,7 @@ pub fn new_partial( let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); let slot = - sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_duration( + sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_slot_duration( *timestamp, slot_duration, ); @@ -140,11 +223,10 @@ pub fn new_partial( let uncles = sp_authorship::InherentDataProvider::<::Header>::check_inherents(); - Ok((timestamp, slot, uncles)) + Ok((slot, timestamp, uncles)) }, &task_manager.spawn_essential_handle(), config.prometheus_registry(), - sp_consensus::CanAuthorWithNativeVersion::new(client.executor().clone()), telemetry.as_ref().map(|x| x.handle()), )?; @@ -156,7 +238,7 @@ pub fn new_partial( let justification_stream = grandpa_link.justification_stream(); let shared_authority_set = grandpa_link.shared_authority_set().clone(); let shared_voter_state = grandpa::SharedVoterState::empty(); - let rpc_setup = shared_voter_state.clone(); + let shared_voter_state2 = shared_voter_state.clone(); let finality_proof_provider = grandpa::FinalityProofProvider::new_for_service( backend.clone(), @@ -172,6 +254,7 @@ pub fn new_partial( let keystore = keystore_container.sync_keystore(); let chain_spec = config.chain_spec.cloned_box(); + let rpc_backend = backend.clone(); let rpc_extensions_builder = move |deny_unsafe, subscription_executor| { let deps = node_rpc::FullDeps { client: client.clone(), @@ -193,10 +276,10 @@ pub fn new_partial( }, }; - node_rpc::create_full(deps).map_err(Into::into) + node_rpc::create_full(deps, rpc_backend.clone()).map_err(Into::into) }; - (rpc_extensions_builder, rpc_setup) + (rpc_extensions_builder, shared_voter_state2) }; Ok(sc_service::PartialComponents { @@ -211,21 +294,38 @@ pub fn new_partial( }) } +/// Result of [`new_full_base`]. pub struct NewFullBase { + /// The task manager of the node. pub task_manager: TaskManager, + /// The client instance of the node. pub client: Arc, + /// The networking service of the node. pub network: Arc::Hash>>, - pub transaction_pool: Arc>, + /// The transaction pool of the node. + pub transaction_pool: Arc, + /// The rpc handlers of the node. + pub rpc_handlers: RpcHandlers, } /// Creates a full service from the configuration. pub fn new_full_base( mut config: Configuration, + disable_hardware_benchmarks: bool, with_startup_data: impl FnOnce( &sc_consensus_babe::BabeBlockImport, &sc_consensus_babe::BabeLink, ), ) -> Result { + let hwbench = if !disable_hardware_benchmarks { + config.database.path().map(|database_path| { + let _ = std::fs::create_dir_all(&database_path); + sc_sysinfo::gather_hwbench(Some(database_path)) + }) + } else { + None + }; + let sc_service::PartialComponents { client, backend, @@ -234,26 +334,33 @@ pub fn new_full_base( keystore_container, select_chain, transaction_pool, - other: (rpc_extensions_builder, import_setup, rpc_setup, mut telemetry), + other: (rpc_builder, import_setup, rpc_setup, mut telemetry), } = new_partial(&config)?; let shared_voter_state = rpc_setup; let auth_disc_publish_non_global_ips = config.network.allow_non_globals_in_dht; + let grandpa_protocol_name = grandpa::protocol_standard_name( + &client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"), + &config.chain_spec, + ); - config.network.extra_sets.push(grandpa::grandpa_peers_set_config()); + config + .network + .extra_sets + .push(grandpa::grandpa_peers_set_config(grandpa_protocol_name.clone())); let warp_sync = Arc::new(grandpa::warp_proof::NetworkProvider::new( backend.clone(), import_setup.1.shared_authority_set().clone(), + Vec::default(), )); - let (network, system_rpc_tx, network_starter) = + let (network, system_rpc_tx, tx_handler_controller, network_starter) = sc_service::build_network(sc_service::BuildNetworkParams { config: &config, client: client.clone(), transaction_pool: transaction_pool.clone(), spawn_handle: task_manager.spawn_handle(), import_queue, - on_demand: None, block_announce_validator_builder: None, warp_sync: Some(warp_sync), })?; @@ -275,21 +382,33 @@ pub fn new_full_base( let enable_grandpa = !config.disable_grandpa; let prometheus_registry = config.prometheus_registry().cloned(); - let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { + let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { config, - backend: backend.clone(), + backend, client: client.clone(), keystore: keystore_container.sync_keystore(), network: network.clone(), - rpc_extensions_builder: Box::new(rpc_extensions_builder), + rpc_builder: Box::new(rpc_builder), transaction_pool: transaction_pool.clone(), task_manager: &mut task_manager, - on_demand: None, - remote_blockchain: None, system_rpc_tx, + tx_handler_controller, telemetry: telemetry.as_mut(), })?; + if let Some(hwbench) = hwbench { + sc_sysinfo::print_hwbench(&hwbench); + + if let Some(ref mut telemetry) = telemetry { + let telemetry_handle = telemetry.handle(); + task_manager.spawn_handle().spawn( + "telemetry_hwbench", + None, + sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), + ); + } + } + let (block_import, grandpa_link, babe_link) = import_setup; (with_startup_data)(&block_import, &babe_link); @@ -303,9 +422,6 @@ pub fn new_full_base( telemetry.as_ref().map(|x| x.handle()), ); - let can_author_with = - sp_consensus::CanAuthorWithNativeVersion::new(client.executor().clone()); - let client_clone = client.clone(); let slot_duration = babe_link.config().slot_duration(); let babe_config = sc_consensus_babe::BabeParams { @@ -327,7 +443,7 @@ pub fn new_full_base( let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); let slot = - sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_duration( + sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_slot_duration( *timestamp, slot_duration, ); @@ -338,20 +454,23 @@ pub fn new_full_base( &parent, )?; - Ok((timestamp, slot, uncles, storage_proof)) + Ok((slot, timestamp, uncles, storage_proof)) } }, force_authoring, backoff_authoring_blocks, babe_link, - can_author_with, block_proposal_slot_portion: SlotProportion::new(0.5), max_block_proposal_slot_portion: None, telemetry: telemetry.as_ref().map(|x| x.handle()), }; let babe = sc_consensus_babe::start_babe(babe_config)?; - task_manager.spawn_essential_handle().spawn_blocking("babe-proposer", babe); + task_manager.spawn_essential_handle().spawn_blocking( + "babe-proposer", + Some("block-authoring"), + babe, + ); } // Spawn authority discovery module. @@ -378,9 +497,11 @@ pub fn new_full_base( prometheus_registry.clone(), ); - task_manager - .spawn_handle() - .spawn("authority-discovery-worker", authority_discovery_worker.run()); + task_manager.spawn_handle().spawn( + "authority-discovery-worker", + Some("networking"), + authority_discovery_worker.run(), + ); } // if the node isn't actively participating in consensus then it doesn't @@ -397,6 +518,7 @@ pub fn new_full_base( keystore, local_role: role, telemetry: telemetry.as_ref().map(|x| x.handle()), + protocol_name: grandpa_protocol_name, }; if enable_grandpa { @@ -418,204 +540,35 @@ pub fn new_full_base( // the GRANDPA voter task is considered infallible, i.e. // if it fails we take down the service with it. - task_manager - .spawn_essential_handle() - .spawn_blocking("grandpa-voter", grandpa::run_grandpa_voter(grandpa_config)?); - } - - network_starter.start_network(); - Ok(NewFullBase { task_manager, client, network, transaction_pool }) -} - -/// Builds a new service for a full client. -pub fn new_full(config: Configuration) -> Result { - new_full_base(config, |_, _| ()).map(|NewFullBase { task_manager, .. }| task_manager) -} - -pub fn new_light_base( - mut config: Configuration, -) -> Result< - ( - TaskManager, - RpcHandlers, - Arc, - Arc::Hash>>, - Arc< - sc_transaction_pool::LightPool>, - >, - ), - ServiceError, -> { - let telemetry = config - .telemetry_endpoints - .clone() - .filter(|x| !x.is_empty()) - .map(|endpoints| -> Result<_, sc_telemetry::Error> { - let worker = TelemetryWorker::new(16)?; - let telemetry = worker.handle().new_telemetry(endpoints); - Ok((worker, telemetry)) - }) - .transpose()?; - - let executor = NativeElseWasmExecutor::::new( - config.wasm_method, - config.default_heap_pages, - config.max_runtime_instances, - ); - - let (client, backend, keystore_container, mut task_manager, on_demand) = - sc_service::new_light_parts::( - &config, - telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), - executor, - )?; - - let mut telemetry = telemetry.map(|(worker, telemetry)| { - task_manager.spawn_handle().spawn("telemetry", worker.run()); - telemetry - }); - - config.network.extra_sets.push(grandpa::grandpa_peers_set_config()); - - let select_chain = sc_consensus::LongestChain::new(backend.clone()); - - let transaction_pool = Arc::new(sc_transaction_pool::BasicPool::new_light( - config.transaction_pool.clone(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), - on_demand.clone(), - )); - - let (grandpa_block_import, grandpa_link) = grandpa::block_import( - client.clone(), - &(client.clone() as Arc<_>), - select_chain.clone(), - telemetry.as_ref().map(|x| x.handle()), - )?; - let justification_import = grandpa_block_import.clone(); - - let (babe_block_import, babe_link) = sc_consensus_babe::block_import( - sc_consensus_babe::Config::get_or_compute(&*client)?, - grandpa_block_import, - client.clone(), - )?; - - let slot_duration = babe_link.config().slot_duration(); - let import_queue = sc_consensus_babe::import_queue( - babe_link, - babe_block_import, - Some(Box::new(justification_import)), - client.clone(), - select_chain.clone(), - move |_, ()| async move { - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); - - let slot = - sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_duration( - *timestamp, - slot_duration, - ); - - let uncles = - sp_authorship::InherentDataProvider::<::Header>::check_inherents(); - - Ok((timestamp, slot, uncles)) - }, - &task_manager.spawn_essential_handle(), - config.prometheus_registry(), - sp_consensus::NeverCanAuthor, - telemetry.as_ref().map(|x| x.handle()), - )?; - - let warp_sync = Arc::new(grandpa::warp_proof::NetworkProvider::new( - backend.clone(), - grandpa_link.shared_authority_set().clone(), - )); - - let (network, system_rpc_tx, network_starter) = - sc_service::build_network(sc_service::BuildNetworkParams { - config: &config, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - spawn_handle: task_manager.spawn_handle(), - import_queue, - on_demand: Some(on_demand.clone()), - block_announce_validator_builder: None, - warp_sync: Some(warp_sync), - })?; - - let enable_grandpa = !config.disable_grandpa; - if enable_grandpa { - let name = config.network.node_name.clone(); - - let config = grandpa::Config { - gossip_duration: std::time::Duration::from_millis(333), - justification_period: 512, - name: Some(name), - observer_enabled: false, - keystore: None, - local_role: config.role.clone(), - telemetry: telemetry.as_ref().map(|x| x.handle()), - }; - - task_manager.spawn_handle().spawn_blocking( - "grandpa-observer", - grandpa::run_grandpa_observer(config, grandpa_link, network.clone())?, + task_manager.spawn_essential_handle().spawn_blocking( + "grandpa-voter", + None, + grandpa::run_grandpa_voter(grandpa_config)?, ); } - if config.offchain_worker.enabled { - sc_service::build_offchain_workers( - &config, - task_manager.spawn_handle(), - client.clone(), - network.clone(), - ); - } - - let light_deps = node_rpc::LightDeps { - remote_blockchain: backend.remote_blockchain(), - fetcher: on_demand.clone(), - client: client.clone(), - pool: transaction_pool.clone(), - }; - - let rpc_extensions = node_rpc::create_light(light_deps); - - let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { - on_demand: Some(on_demand), - remote_blockchain: Some(backend.remote_blockchain()), - rpc_extensions_builder: Box::new(sc_service::NoopRpcExtensionBuilder(rpc_extensions)), - client: client.clone(), - transaction_pool: transaction_pool.clone(), - keystore: keystore_container.sync_keystore(), - config, - backend, - system_rpc_tx, - network: network.clone(), - task_manager: &mut task_manager, - telemetry: telemetry.as_mut(), - })?; - network_starter.start_network(); - Ok((task_manager, rpc_handlers, client, network, transaction_pool)) + Ok(NewFullBase { task_manager, client, network, transaction_pool, rpc_handlers }) } -/// Builds a new service for a light client. -pub fn new_light(config: Configuration) -> Result { - new_light_base(config).map(|(task_manager, _, _, _, _)| task_manager) +/// Builds a new service for a full client. +pub fn new_full( + config: Configuration, + disable_hardware_benchmarks: bool, +) -> Result { + new_full_base(config, disable_hardware_benchmarks, |_, _| ()) + .map(|NewFullBase { task_manager, .. }| task_manager) } #[cfg(test)] mod tests { - use crate::service::{new_full_base, new_light_base, NewFullBase}; + use crate::service::{new_full_base, NewFullBase}; use codec::Encode; - use node_primitives::{Block, DigestItem, Signature}; - use node_runtime::{ + use kitchensink_runtime::{ constants::{currency::CENTS, time::SLOT_DURATION}, - Address, BalancesCall, Call, UncheckedExtrinsic, + Address, BalancesCall, RuntimeCall, UncheckedExtrinsic, }; + use node_primitives::{Block, DigestItem, Signature}; use sc_client_api::BlockBackend; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; use sc_consensus_babe::{BabeIntermediate, CompatibleDigestItem, INTERMEDIATE_KEY}; @@ -624,7 +577,7 @@ mod tests { use sc_service_test::TestNetNode; use sc_transaction_pool_api::{ChainEvent, MaintainedTransactionPool}; use sp_consensus::{BlockOrigin, Environment, Proposer}; - use sp_core::{crypto::Pair as CryptoPair, Public, H256}; + use sp_core::{crypto::Pair as CryptoPair, Public}; use sp_inherents::InherentDataProvider; use sp_keyring::AccountKeyring; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; @@ -635,7 +588,7 @@ mod tests { RuntimeAppPublic, }; use sp_timestamp; - use std::{borrow::Cow, convert::TryInto, sync::Arc}; + use std::sync::Arc; type AccountPublic = ::Signer; @@ -671,6 +624,7 @@ mod tests { let NewFullBase { task_manager, client, network, transaction_pool, .. } = new_full_base( config, + false, |block_import: &sc_consensus_babe::BabeBlockImport, babe_link: &sc_consensus_babe::BabeLink| { setup_handles = Some((block_import.clone(), babe_link.clone())); @@ -685,15 +639,6 @@ mod tests { ); Ok((node, setup_handles.unwrap())) }, - |config| { - let (keep_alive, _, client, network, transaction_pool) = new_light_base(config)?; - Ok(sc_service_test::TestNetComponents::new( - keep_alive, - client, - network, - transaction_pool, - )) - }, |service, &mut (ref mut block_import, ref babe_link)| { let parent_id = BlockId::number(service.client().chain_info().best_number); let parent_header = service.client().header(&parent_id).unwrap().unwrap(); @@ -712,7 +657,7 @@ mod tests { None, ); - let mut digest = Digest::::default(); + let mut digest = Digest::default(); // even though there's only one authority some slots might be empty, // so we must keep trying the next slots until we can claim one. @@ -733,7 +678,7 @@ mod tests { .epoch_changes() .shared_data() .epoch_data(&epoch_descriptor, |slot| { - sc_consensus_babe::Epoch::genesis(&babe_link.config(), slot) + sc_consensus_babe::Epoch::genesis(babe_link.config(), slot) }) .unwrap(); @@ -747,14 +692,16 @@ mod tests { slot += 1; }; - let inherent_data = ( - sp_timestamp::InherentDataProvider::new( - std::time::Duration::from_millis(SLOT_DURATION * slot).into(), - ), - sp_consensus_babe::inherents::InherentDataProvider::new(slot.into()), + let inherent_data = futures::executor::block_on( + ( + sp_timestamp::InherentDataProvider::new( + std::time::Duration::from_millis(SLOT_DURATION * slot).into(), + ), + sp_consensus_babe::inherents::InherentDataProvider::new(slot.into()), + ) + .create_inherent_data(), ) - .create_inherent_data() - .expect("Creates inherent data"); + .expect("Creates inherent data"); digest.push(::babe_pre_digest(babe_pre_digest)); @@ -789,9 +736,9 @@ mod tests { let mut params = BlockImportParams::new(BlockOrigin::File, new_header); params.post_digests.push(item); params.body = Some(new_body); - params.intermediates.insert( - Cow::from(INTERMEDIATE_KEY), - Box::new(BabeIntermediate:: { epoch_descriptor }) as Box<_>, + params.insert_intermediate( + INTERMEDIATE_KEY, + BabeIntermediate:: { epoch_descriptor }, ); params.fork_choice = Some(ForkChoiceStrategy::LongestChain); @@ -810,29 +757,33 @@ mod tests { }; let signer = charlie.clone(); - let function = - Call::Balances(BalancesCall::transfer { dest: to.into(), value: amount }); + let function = RuntimeCall::Balances(BalancesCall::transfer { + dest: to.into(), + value: amount, + }); + let check_non_zero_sender = frame_system::CheckNonZeroSender::new(); let check_spec_version = frame_system::CheckSpecVersion::new(); let check_tx_version = frame_system::CheckTxVersion::new(); let check_genesis = frame_system::CheckGenesis::new(); let check_era = frame_system::CheckEra::from(Era::Immortal); let check_nonce = frame_system::CheckNonce::from(index); let check_weight = frame_system::CheckWeight::new(); - let payment = pallet_transaction_payment::ChargeTransactionPayment::from(0); + let tx_payment = pallet_asset_tx_payment::ChargeAssetTxPayment::from(0, None); let extra = ( + check_non_zero_sender, check_spec_version, check_tx_version, check_genesis, check_era, check_nonce, check_weight, - payment, + tx_payment, ); let raw_payload = SignedPayload::from_raw( function, extra, - (spec_version, transaction_version, genesis_hash, genesis_hash, (), (), ()), + ((), spec_version, transaction_version, genesis_hash, genesis_hash, (), (), ()), ); let signature = raw_payload.using_encoded(|payload| signer.sign(payload)); let (function, extra, _) = raw_payload.deconstruct(); @@ -852,7 +803,7 @@ mod tests { crate::chain_spec::tests::integration_test_config_with_two_authorities(), |config| { let NewFullBase { task_manager, client, network, transaction_pool, .. } = - new_full_base(config, |_, _| ())?; + new_full_base(config, false, |_, _| ())?; Ok(sc_service_test::TestNetComponents::new( task_manager, client, @@ -860,15 +811,6 @@ mod tests { transaction_pool, )) }, - |config| { - let (keep_alive, _, client, network, transaction_pool) = new_light_base(config)?; - Ok(sc_service_test::TestNetComponents::new( - keep_alive, - client, - network, - transaction_pool, - )) - }, vec!["//Alice".into(), "//Bob".into()], ) } diff --git a/bin/node/cli/tests/benchmark_block_works.rs b/bin/node/cli/tests/benchmark_block_works.rs new file mode 100644 index 0000000000000..65cb474ea88d6 --- /dev/null +++ b/bin/node/cli/tests/benchmark_block_works.rs @@ -0,0 +1,47 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +// Unix only since it uses signals from [`common::run_node_for_a_while`]. +#![cfg(unix)] + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; +use tempfile::tempdir; + +pub mod common; + +/// `benchmark block` works for the dev runtime using the wasm executor. +#[tokio::test] +async fn benchmark_block_works() { + let base_dir = tempdir().expect("could not create a temp dir"); + + common::run_node_for_a_while(base_dir.path(), &["--dev", "--no-hardware-benchmarks"]).await; + + // Invoke `benchmark block` with all options to make sure that they are valid. + let status = Command::new(cargo_bin("substrate")) + .args(["benchmark", "block", "--dev"]) + .arg("-d") + .arg(base_dir.path()) + .args(["--from", "1", "--to", "1"]) + .args(["--repeat", "1"]) + .args(["--execution", "wasm", "--wasm-execution", "compiled"]) + .status() + .unwrap(); + + assert!(status.success()) +} diff --git a/bin/node/cli/tests/benchmark_extrinsic_works.rs b/bin/node/cli/tests/benchmark_extrinsic_works.rs new file mode 100644 index 0000000000000..69800ad3c6c13 --- /dev/null +++ b/bin/node/cli/tests/benchmark_extrinsic_works.rs @@ -0,0 +1,46 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; +use tempfile::tempdir; + +/// Tests that the `benchmark extrinsic` command works for +/// remark and transfer_keep_alive within the substrate dev runtime. +#[test] +fn benchmark_extrinsic_works() { + benchmark_extrinsic("system", "remark"); + benchmark_extrinsic("balances", "transfer_keep_alive"); +} + +/// Checks that the `benchmark extrinsic` command works for the given pallet and extrinsic. +fn benchmark_extrinsic(pallet: &str, extrinsic: &str) { + let base_dir = tempdir().expect("could not create a temp dir"); + + let status = Command::new(cargo_bin("substrate")) + .args(&["benchmark", "extrinsic", "--dev"]) + .arg("-d") + .arg(base_dir.path()) + .args(&["--pallet", pallet, "--extrinsic", extrinsic]) + // Run with low repeats for faster execution. + .args(["--warmup=10", "--repeat=10", "--max-ext-per-block=10"]) + .status() + .unwrap(); + + assert!(status.success()); +} diff --git a/bin/node/cli/tests/benchmark_machine_works.rs b/bin/node/cli/tests/benchmark_machine_works.rs new file mode 100644 index 0000000000000..193e6b701ec94 --- /dev/null +++ b/bin/node/cli/tests/benchmark_machine_works.rs @@ -0,0 +1,73 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; + +/// Tests that the `benchmark machine` command works for the substrate dev runtime. +#[test] +fn benchmark_machine_works() { + let status = Command::new(cargo_bin("substrate")) + .args(["benchmark", "machine", "--dev"]) + .args([ + "--verify-duration", + "0.1", + "--disk-duration", + "0.1", + "--memory-duration", + "0.1", + "--hash-duration", + "0.1", + ]) + // Make it succeed. + .args(["--allow-fail"]) + .status() + .unwrap(); + + assert!(status.success()); +} + +/// Test that the hardware does not meet the requirements. +/// +/// This is most likely to succeed since it uses a test profile. +#[test] +#[cfg(debug_assertions)] +fn benchmark_machine_fails_with_slow_hardware() { + let output = Command::new(cargo_bin("substrate")) + .args(["benchmark", "machine", "--dev"]) + .args([ + "--verify-duration", + "1.0", + "--disk-duration", + "2", + "--hash-duration", + "1.0", + "--memory-duration", + "1.0", + "--tolerance", + "0", + ]) + .output() + .unwrap(); + + // Command should have failed. + assert!(!output.status.success()); + // An `UnmetRequirement` error should have been printed. + let log = String::from_utf8_lossy(&output.stderr).to_string(); + assert!(log.contains("UnmetRequirement")); +} diff --git a/bin/node/cli/tests/benchmark_overhead_works.rs b/bin/node/cli/tests/benchmark_overhead_works.rs new file mode 100644 index 0000000000000..44dcebfbc0c35 --- /dev/null +++ b/bin/node/cli/tests/benchmark_overhead_works.rs @@ -0,0 +1,46 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; +use tempfile::tempdir; + +/// Tests that the `benchmark overhead` command works for the substrate dev runtime. +#[test] +fn benchmark_overhead_works() { + let tmp_dir = tempdir().expect("could not create a temp dir"); + let base_path = tmp_dir.path(); + + // Only put 10 extrinsics into the block otherwise it takes forever to build it + // especially for a non-release build. + let status = Command::new(cargo_bin("substrate")) + .args(&["benchmark", "overhead", "--dev", "-d"]) + .arg(base_path) + .arg("--weight-path") + .arg(base_path) + .args(["--warmup", "10", "--repeat", "10"]) + .args(["--add", "100", "--mul", "1.2", "--metric", "p75"]) + .args(["--max-ext-per-block", "10"]) + .status() + .unwrap(); + assert!(status.success()); + + // Weight files have been created. + assert!(base_path.join("block_weights.rs").exists()); + assert!(base_path.join("extrinsic_weights.rs").exists()); +} diff --git a/bin/node/cli/tests/benchmark_pallet_works.rs b/bin/node/cli/tests/benchmark_pallet_works.rs new file mode 100644 index 0000000000000..bf29c0e308bcb --- /dev/null +++ b/bin/node/cli/tests/benchmark_pallet_works.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; + +pub mod common; + +/// `benchmark pallet` works for the different combinations of `steps` and `repeat`. +#[test] +fn benchmark_pallet_works() { + // Some invalid combinations: + benchmark_pallet(0, 10, false); + benchmark_pallet(1, 10, false); + // ... and some valid: + benchmark_pallet(2, 1, true); + benchmark_pallet(50, 20, true); + benchmark_pallet(20, 50, true); +} + +fn benchmark_pallet(steps: u32, repeat: u32, should_work: bool) { + let output = Command::new(cargo_bin("substrate")) + .args(["benchmark", "pallet", "--dev"]) + // Use the `addition` benchmark since is the fastest. + .args(["--pallet", "frame-benchmarking", "--extrinsic", "addition"]) + .args(["--steps", &format!("{}", steps), "--repeat", &format!("{}", repeat)]) + .output() + .unwrap(); + + if output.status.success() != should_work { + let log = String::from_utf8_lossy(&output.stderr).to_string(); + panic!("Test failed:\n{}", log); + } +} diff --git a/bin/node/cli/tests/benchmark_storage_works.rs b/bin/node/cli/tests/benchmark_storage_works.rs new file mode 100644 index 0000000000000..82d1c943ae7aa --- /dev/null +++ b/bin/node/cli/tests/benchmark_storage_works.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use assert_cmd::cargo::cargo_bin; +use std::{ + path::Path, + process::{Command, ExitStatus}, +}; +use tempfile::tempdir; + +/// Tests that the `benchmark storage` command works for the dev runtime. +#[test] +fn benchmark_storage_works() { + let tmp_dir = tempdir().expect("could not create a temp dir"); + let base_path = tmp_dir.path(); + + // Benchmarking the storage works and creates the correct weight file. + assert!(benchmark_storage("rocksdb", base_path).success()); + assert!(base_path.join("rocksdb_weights.rs").exists()); + + assert!(benchmark_storage("paritydb", base_path).success()); + assert!(base_path.join("paritydb_weights.rs").exists()); +} + +fn benchmark_storage(db: &str, base_path: &Path) -> ExitStatus { + Command::new(cargo_bin("substrate")) + .args(&["benchmark", "storage", "--dev"]) + .arg("--db") + .arg(db) + .arg("--weight-path") + .arg(base_path) + .args(["--state-version", "1"]) + .args(["--warmups", "0"]) + .args(["--add", "100", "--mul", "1.2", "--metric", "p75"]) + .arg("--include-child-trees") + .status() + .unwrap() +} diff --git a/bin/node/cli/tests/build_spec_works.rs b/bin/node/cli/tests/build_spec_works.rs index 6d863ea7f949d..aecabed60c849 100644 --- a/bin/node/cli/tests/build_spec_works.rs +++ b/bin/node/cli/tests/build_spec_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/bin/node/cli/tests/check_block_works.rs b/bin/node/cli/tests/check_block_works.rs index 707fd217e33e8..d4afc530bbcb3 100644 --- a/bin/node/cli/tests/check_block_works.rs +++ b/bin/node/cli/tests/check_block_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -24,14 +24,14 @@ use tempfile::tempdir; pub mod common; -#[test] -fn check_block_works() { +#[tokio::test] +async fn check_block_works() { let base_path = tempdir().expect("could not create a temp dir"); - common::run_node_for_a_while(base_path.path(), &["--dev"]); + common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; let status = Command::new(cargo_bin("substrate")) - .args(&["check-block", "--dev", "--pruning", "archive", "-d"]) + .args(&["check-block", "--dev", "-d"]) .arg(base_path.path()) .arg("1") .status() diff --git a/bin/node/cli/tests/common.rs b/bin/node/cli/tests/common.rs index 54b9c749bf1de..358c09779d59a 100644 --- a/bin/node/cli/tests/common.rs +++ b/bin/node/cli/tests/common.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,58 +23,154 @@ use nix::{ sys::signal::{kill, Signal::SIGINT}, unistd::Pid, }; +use node_primitives::{Hash, Header}; use std::{ - convert::TryInto, + io::{BufRead, BufReader, Read}, + ops::{Deref, DerefMut}, path::Path, - process::{Child, Command, ExitStatus}, - thread, + process::{self, Child, Command, ExitStatus}, time::Duration, }; +use tokio::time::timeout; /// Wait for the given `child` the given number of `secs`. /// /// Returns the `Some(exit status)` or `None` if the process did not finish in the given time. -pub fn wait_for(child: &mut Child, secs: usize) -> Option { - for i in 0..secs { - match child.try_wait().unwrap() { - Some(status) => { - if i > 5 { - eprintln!("Child process took {} seconds to exit gracefully", i); - } - return Some(status) - }, - None => thread::sleep(Duration::from_secs(1)), +pub fn wait_for(child: &mut Child, secs: u64) -> Result { + let result = wait_timeout::ChildExt::wait_timeout(child, Duration::from_secs(5.min(secs))) + .map_err(|_| ())?; + if let Some(exit_status) = result { + Ok(exit_status) + } else { + if secs > 5 { + eprintln!("Child process taking over 5 seconds to exit gracefully"); + let result = wait_timeout::ChildExt::wait_timeout(child, Duration::from_secs(secs - 5)) + .map_err(|_| ())?; + if let Some(exit_status) = result { + return Ok(exit_status) + } } + eprintln!("Took too long to exit (> {} seconds). Killing...", secs); + let _ = child.kill(); + child.wait().unwrap(); + Err(()) } - eprintln!("Took too long to exit (> {} seconds). Killing...", secs); - let _ = child.kill(); - child.wait().unwrap(); +} - None +/// Wait for at least n blocks to be finalized within a specified time. +pub async fn wait_n_finalized_blocks( + n: usize, + timeout_secs: u64, + url: &str, +) -> Result<(), tokio::time::error::Elapsed> { + timeout(Duration::from_secs(timeout_secs), wait_n_finalized_blocks_from(n, url)).await } -/// Run the node for a while (30 seconds) -pub fn run_node_for_a_while(base_path: &Path, args: &[&str]) { - let mut cmd = Command::new(cargo_bin("substrate")); +/// Wait for at least n blocks to be finalized from a specified node +pub async fn wait_n_finalized_blocks_from(n: usize, url: &str) { + use substrate_rpc_client::{ws_client, ChainApi}; + + let mut built_blocks = std::collections::HashSet::new(); + let mut interval = tokio::time::interval(Duration::from_secs(2)); + let rpc = ws_client(url).await.unwrap(); + + loop { + if let Ok(block) = ChainApi::<(), Hash, Header, ()>::finalized_head(&rpc).await { + built_blocks.insert(block); + if built_blocks.len() > n { + break + } + }; + interval.tick().await; + } +} + +/// Run the node for a while (3 blocks) +pub async fn run_node_for_a_while(base_path: &Path, args: &[&str]) { + let mut cmd = Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .args(args) + .arg("-d") + .arg(base_path) + .spawn() + .unwrap(); + + let stderr = cmd.stderr.take().unwrap(); - let mut cmd = cmd.args(args).arg("-d").arg(base_path).spawn().unwrap(); + let mut child = KillChildOnDrop(cmd); + + let (ws_url, _) = find_ws_url_from_output(stderr); // Let it produce some blocks. - thread::sleep(Duration::from_secs(30)); - assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); + let _ = wait_n_finalized_blocks(3, 30, &ws_url).await; + + assert!(child.try_wait().unwrap().is_none(), "the process should still be running"); // Stop the process - kill(Pid::from_raw(cmd.id().try_into().unwrap()), SIGINT).unwrap(); - assert!(wait_for(&mut cmd, 40).map(|x| x.success()).unwrap_or_default()); + kill(Pid::from_raw(child.id().try_into().unwrap()), SIGINT).unwrap(); + assert!(wait_for(&mut child, 40).map(|x| x.success()).unwrap()); } /// Run the node asserting that it fails with an error pub fn run_node_assert_fail(base_path: &Path, args: &[&str]) { let mut cmd = Command::new(cargo_bin("substrate")); - let mut cmd = cmd.args(args).arg("-d").arg(base_path).spawn().unwrap(); + let mut child = KillChildOnDrop(cmd.args(args).arg("-d").arg(base_path).spawn().unwrap()); - // Let it produce some blocks. - thread::sleep(Duration::from_secs(10)); - assert!(cmd.try_wait().unwrap().is_some(), "the process should not be running anymore"); + // Let it produce some blocks, but it should die within 10 seconds. + assert_ne!( + wait_timeout::ChildExt::wait_timeout(&mut *child, Duration::from_secs(10)).unwrap(), + None, + "the process should not be running anymore" + ); +} + +pub struct KillChildOnDrop(pub Child); + +impl Drop for KillChildOnDrop { + fn drop(&mut self) { + let _ = self.0.kill(); + } +} + +impl Deref for KillChildOnDrop { + type Target = Child; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for KillChildOnDrop { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Read the WS address from the output. +/// +/// This is hack to get the actual binded sockaddr because +/// substrate assigns a random port if the specified port was already binded. +pub fn find_ws_url_from_output(read: impl Read + Send) -> (String, String) { + let mut data = String::new(); + + let ws_url = BufReader::new(read) + .lines() + .find_map(|line| { + let line = + line.expect("failed to obtain next line from stdout for WS address discovery"); + data.push_str(&line); + + // does the line contain our port (we expect this specific output from substrate). + let sock_addr = match line.split_once("Running JSON-RPC WS server: addr=") { + None => return None, + Some((_, after)) => after.split_once(",").unwrap().0, + }; + + Some(format!("ws://{}", sock_addr)) + }) + .expect("We should get a WebSocket address"); + + (ws_url, data) } diff --git a/bin/node/cli/tests/database_role_subdir_migration.rs b/bin/node/cli/tests/database_role_subdir_migration.rs deleted file mode 100644 index 516908111ae72..0000000000000 --- a/bin/node/cli/tests/database_role_subdir_migration.rs +++ /dev/null @@ -1,115 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use sc_client_db::{ - light::LightStorage, DatabaseSettings, DatabaseSource, KeepBlocks, PruningMode, - TransactionStorageMode, -}; -use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper}; -use tempfile::tempdir; - -pub mod common; - -#[test] -#[cfg(unix)] -fn database_role_subdir_migration() { - type Block = RawBlock>; - - let base_path = tempdir().expect("could not create a temp dir"); - let path = base_path.path().join("chains/dev/db"); - // create a dummy database dir - { - let _old_db = LightStorage::::new(DatabaseSettings { - state_cache_size: 0, - state_cache_child_ratio: None, - state_pruning: PruningMode::ArchiveAll, - source: DatabaseSource::RocksDb { path: path.to_path_buf(), cache_size: 128 }, - keep_blocks: KeepBlocks::All, - transaction_storage: TransactionStorageMode::BlockBody, - }) - .unwrap(); - } - - assert!(path.join("db_version").exists()); - assert!(!path.join("light").exists()); - - // start a light client - common::run_node_for_a_while( - base_path.path(), - &[ - "--dev", - "--light", - "--port", - "30335", - "--rpc-port", - "44444", - "--ws-port", - "44445", - "--no-prometheus", - ], - ); - - // check if the database dir had been migrated - assert!(!path.join("db_version").exists()); - assert!(path.join("light/db_version").exists()); -} - -#[test] -#[cfg(unix)] -fn database_role_subdir_migration_fail_on_different_role() { - type Block = RawBlock>; - - let base_path = tempdir().expect("could not create a temp dir"); - let path = base_path.path().join("chains/dev/db"); - - // create a database with the old layout - { - let _old_db = LightStorage::::new(DatabaseSettings { - state_cache_size: 0, - state_cache_child_ratio: None, - state_pruning: PruningMode::ArchiveAll, - source: DatabaseSource::RocksDb { path: path.to_path_buf(), cache_size: 128 }, - keep_blocks: KeepBlocks::All, - transaction_storage: TransactionStorageMode::BlockBody, - }) - .unwrap(); - } - - assert!(path.join("db_version").exists()); - assert!(!path.join("light/db_version").exists()); - - // start a client with a different role (full), it should fail and not change any files on disk - common::run_node_assert_fail( - &base_path.path(), - &[ - "--dev", - "--port", - "30334", - "--rpc-port", - "44446", - "--ws-port", - "44447", - "--no-prometheus", - ], - ); - - // check if the files are unchanged - assert!(path.join("db_version").exists()); - assert!(!path.join("light/db_version").exists()); - assert!(!path.join("full/db_version").exists()); -} diff --git a/bin/node/cli/tests/export_import_flow.rs b/bin/node/cli/tests/export_import_flow.rs index 7cbaa152699b4..750b4f7acc121 100644 --- a/bin/node/cli/tests/export_import_flow.rs +++ b/bin/node/cli/tests/export_import_flow.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -79,9 +79,9 @@ impl<'a> ExportImportRevertExecutor<'a> { // Adding "--binary" if need be. let arguments: Vec<&str> = match format_opt { FormatOpt::Binary => { - vec![&sub_command_str, "--dev", "--pruning", "archive", "--binary", "-d"] + vec![&sub_command_str, "--dev", "--binary", "-d"] }, - FormatOpt::Json => vec![&sub_command_str, "--dev", "--pruning", "archive", "-d"], + FormatOpt::Json => vec![&sub_command_str, "--dev", "-d"], }; let tmp: TempDir; @@ -161,7 +161,7 @@ impl<'a> ExportImportRevertExecutor<'a> { /// Runs the `revert` command. fn run_revert(&self) { let output = Command::new(cargo_bin("substrate")) - .args(&["revert", "--dev", "--pruning", "archive", "-d"]) + .args(&["revert", "--dev", "-d"]) .arg(&self.base_path.path()) .output() .unwrap(); @@ -182,13 +182,13 @@ impl<'a> ExportImportRevertExecutor<'a> { } } -#[test] -fn export_import_revert() { +#[tokio::test] +async fn export_import_revert() { let base_path = tempdir().expect("could not create a temp dir"); let exported_blocks_file = base_path.path().join("exported_blocks"); let db_path = base_path.path().join("db"); - common::run_node_for_a_while(base_path.path(), &["--dev"]); + common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; let mut executor = ExportImportRevertExecutor::new(&base_path, &exported_blocks_file, &db_path); diff --git a/bin/node/cli/tests/inspect_works.rs b/bin/node/cli/tests/inspect_works.rs index 2a89801547a4b..849fb913a18d0 100644 --- a/bin/node/cli/tests/inspect_works.rs +++ b/bin/node/cli/tests/inspect_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -24,14 +24,14 @@ use tempfile::tempdir; pub mod common; -#[test] -fn inspect_works() { +#[tokio::test] +async fn inspect_works() { let base_path = tempdir().expect("could not create a temp dir"); - common::run_node_for_a_while(base_path.path(), &["--dev"]); + common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; let status = Command::new(cargo_bin("substrate")) - .args(&["inspect", "--dev", "--pruning", "archive", "-d"]) + .args(&["inspect", "--dev", "-d"]) .arg(base_path.path()) .args(&["block", "1"]) .status() diff --git a/bin/node/cli/tests/purge_chain_works.rs b/bin/node/cli/tests/purge_chain_works.rs index 0f16a51e5d0a4..811762a714a9d 100644 --- a/bin/node/cli/tests/purge_chain_works.rs +++ b/bin/node/cli/tests/purge_chain_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,12 +22,12 @@ use tempfile::tempdir; pub mod common; -#[test] +#[tokio::test] #[cfg(unix)] -fn purge_chain_works() { +async fn purge_chain_works() { let base_path = tempdir().expect("could not create a temp dir"); - common::run_node_for_a_while(base_path.path(), &["--dev"]); + common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; let status = Command::new(cargo_bin("substrate")) .args(&["purge-chain", "--dev", "-d"]) diff --git a/bin/node/cli/tests/running_the_node_and_interrupt.rs b/bin/node/cli/tests/running_the_node_and_interrupt.rs index 03a1826f2f080..6d4a4b40425c4 100644 --- a/bin/node/cli/tests/running_the_node_and_interrupt.rs +++ b/bin/node/cli/tests/running_the_node_and_interrupt.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,7 +17,6 @@ // along with this program. If not, see . #![cfg(unix)] - use assert_cmd::cargo::cargo_bin; use nix::{ sys::signal::{ @@ -26,78 +25,72 @@ use nix::{ }, unistd::Pid, }; -use sc_service::Deref; -use std::{ - convert::TryInto, - ops::DerefMut, - process::{Child, Command}, - thread, - time::Duration, -}; +use std::process::{self, Child, Command}; use tempfile::tempdir; pub mod common; -#[test] -fn running_the_node_works_and_can_be_interrupted() { - fn run_command_and_kill(signal: Signal) { +#[tokio::test] +async fn running_the_node_works_and_can_be_interrupted() { + async fn run_command_and_kill(signal: Signal) { let base_path = tempdir().expect("could not create a temp dir"); - let mut cmd = Command::new(cargo_bin("substrate")) - .args(&["--dev", "-d"]) - .arg(base_path.path()) - .spawn() - .unwrap(); + let mut cmd = common::KillChildOnDrop( + Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .args(&["--dev", "-d"]) + .arg(base_path.path()) + .arg("--db=paritydb") + .arg("--no-hardware-benchmarks") + .spawn() + .unwrap(), + ); + + let stderr = cmd.stderr.take().unwrap(); - thread::sleep(Duration::from_secs(20)); + let (ws_url, _) = common::find_ws_url_from_output(stderr); + + common::wait_n_finalized_blocks(3, 30, &ws_url) + .await + .expect("Blocks are produced in time"); assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); kill(Pid::from_raw(cmd.id().try_into().unwrap()), signal).unwrap(); assert_eq!( common::wait_for(&mut cmd, 30).map(|x| x.success()), - Some(true), + Ok(true), "the process must exit gracefully after signal {}", signal, ); + // Check if the database was closed gracefully. If it was not, + // there may exist a ref cycle that prevents the Client from being dropped properly. + // + // parity-db only writes the stats file on clean shutdown. + let stats_file = base_path.path().join("chains/dev/paritydb/full/stats.txt"); + assert!(std::path::Path::exists(&stats_file)); } - run_command_and_kill(SIGINT); - run_command_and_kill(SIGTERM); -} - -struct KillChildOnDrop(Child); - -impl Drop for KillChildOnDrop { - fn drop(&mut self) { - let _ = self.0.kill(); - } -} - -impl Deref for KillChildOnDrop { - type Target = Child; - - fn deref(&self) -> &Self::Target { - &self.0 - } + run_command_and_kill(SIGINT).await; + run_command_and_kill(SIGTERM).await; } -impl DerefMut for KillChildOnDrop { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -#[test] -fn running_two_nodes_with_the_same_ws_port_should_work() { +#[tokio::test] +async fn running_two_nodes_with_the_same_ws_port_should_work() { fn start_node() -> Child { Command::new(cargo_bin("substrate")) - .args(&["--dev", "--tmp", "--ws-port=45789"]) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .args(&["--dev", "--tmp", "--ws-port=45789", "--no-hardware-benchmarks"]) .spawn() .unwrap() } - let mut first_node = KillChildOnDrop(start_node()); - let mut second_node = KillChildOnDrop(start_node()); + let mut first_node = common::KillChildOnDrop(start_node()); + let mut second_node = common::KillChildOnDrop(start_node()); + + let stderr = first_node.stderr.take().unwrap(); + let (ws_url, _) = common::find_ws_url_from_output(stderr); - thread::sleep(Duration::from_secs(30)); + common::wait_n_finalized_blocks(3, 30, &ws_url).await.unwrap(); assert!(first_node.try_wait().unwrap().is_none(), "The first node should still be running"); assert!(second_node.try_wait().unwrap().is_none(), "The second node should still be running"); @@ -107,12 +100,12 @@ fn running_two_nodes_with_the_same_ws_port_should_work() { assert_eq!( common::wait_for(&mut first_node, 30).map(|x| x.success()), - Some(true), + Ok(true), "The first node must exit gracefully", ); assert_eq!( common::wait_for(&mut second_node, 30).map(|x| x.success()), - Some(true), + Ok(true), "The second node must exit gracefully", ); } diff --git a/bin/node/cli/tests/telemetry.rs b/bin/node/cli/tests/telemetry.rs index 78a306284c4ac..98cf0b3af32b2 100644 --- a/bin/node/cli/tests/telemetry.rs +++ b/bin/node/cli/tests/telemetry.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,12 +21,12 @@ use nix::{ sys::signal::{kill, Signal::SIGINT}, unistd::Pid, }; -use std::{convert::TryInto, process}; +use std::process; pub mod common; pub mod websocket_server; -#[async_std::test] +#[tokio::test] async fn telemetry_works() { let config = websocket_server::Config { capacity: 1, @@ -38,7 +38,7 @@ async fn telemetry_works() { let addr = server.local_addr().unwrap(); - let server_task = async_std::task::spawn(async move { + let server_task = tokio::spawn(async move { loop { use websocket_server::Event; match server.next_event().await { @@ -71,13 +71,14 @@ async fn telemetry_works() { let mut substrate = substrate .args(&["--dev", "--tmp", "--telemetry-url"]) .arg(format!("ws://{} 10", addr)) + .arg("--no-hardware-benchmarks") .stdout(process::Stdio::piped()) .stderr(process::Stdio::piped()) .stdin(process::Stdio::null()) .spawn() .unwrap(); - server_task.await; + server_task.await.expect("server task panicked"); assert!(substrate.try_wait().unwrap().is_none(), "the process should still be running"); diff --git a/bin/node/cli/tests/temp_base_path_works.rs b/bin/node/cli/tests/temp_base_path_works.rs index c107740b9b0a5..98422a21f5308 100644 --- a/bin/node/cli/tests/temp_base_path_works.rs +++ b/bin/node/cli/tests/temp_base_path_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -25,41 +25,39 @@ use nix::{ }; use regex::Regex; use std::{ - convert::TryInto, io::Read, path::PathBuf, process::{Command, Stdio}, - thread, - time::Duration, }; pub mod common; -#[test] -fn temp_base_path_works() { +#[tokio::test] +async fn temp_base_path_works() { let mut cmd = Command::new(cargo_bin("substrate")); + let mut child = common::KillChildOnDrop( + cmd.args(&["--dev", "--tmp", "--no-hardware-benchmarks"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(), + ); - let mut cmd = cmd - .args(&["--dev", "--tmp"]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); + let mut stderr = child.stderr.take().unwrap(); + let (ws_url, mut data) = common::find_ws_url_from_output(&mut stderr); // Let it produce some blocks. - thread::sleep(Duration::from_secs(30)); - assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); + common::wait_n_finalized_blocks(3, 30, &ws_url).await.unwrap(); + assert!(child.try_wait().unwrap().is_none(), "the process should still be running"); // Stop the process - kill(Pid::from_raw(cmd.id().try_into().unwrap()), SIGINT).unwrap(); - assert!(common::wait_for(&mut cmd, 40).map(|x| x.success()).unwrap_or_default()); + kill(Pid::from_raw(child.id().try_into().unwrap()), SIGINT).unwrap(); + assert!(common::wait_for(&mut child, 40).map(|x| x.success()).unwrap_or_default()); // Ensure the database has been deleted - let mut stderr = String::new(); - cmd.stderr.unwrap().read_to_string(&mut stderr).unwrap(); + stderr.read_to_string(&mut data).unwrap(); let re = Regex::new(r"Database: .+ at (\S+)").unwrap(); - let db_path = - PathBuf::from(re.captures(stderr.as_str()).unwrap().get(1).unwrap().as_str().to_string()); + let db_path = PathBuf::from(re.captures(data.as_str()).unwrap().get(1).unwrap().as_str()); assert!(!db_path.exists()); } diff --git a/bin/node/cli/tests/version.rs b/bin/node/cli/tests/version.rs index 5ed3a9a8800c8..f5a6f3a53d598 100644 --- a/bin/node/cli/tests/version.rs +++ b/bin/node/cli/tests/version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,13 +17,11 @@ // along with this program. If not, see . use assert_cmd::cargo::cargo_bin; -use platforms::*; use regex::Regex; use std::process::Command; fn expected_regex() -> Regex { - Regex::new(r"^substrate (\d+\.\d+\.\d+(?:-.+?)?)-([a-f\d]+|unknown)-(.+?)-(.+?)(?:-(.+))?$") - .unwrap() + Regex::new(r"^substrate (.+)-([a-f\d]+)$").unwrap() } #[test] @@ -37,33 +35,17 @@ fn version_is_full() { let captures = expected.captures(output.as_str()).expect("could not parse version in output"); assert_eq!(&captures[1], env!("CARGO_PKG_VERSION")); - assert_eq!(&captures[3], TARGET_ARCH.as_str()); - assert_eq!(&captures[4], TARGET_OS.as_str()); - assert_eq!(captures.get(5).map(|x| x.as_str()), TARGET_ENV.map(|x| x.as_str())); } #[test] fn test_regex_matches_properly() { let expected = expected_regex(); - let captures = expected.captures("substrate 2.0.0-da487d19d-x86_64-linux-gnu").unwrap(); + let captures = expected.captures("substrate 2.0.0-da487d19d").unwrap(); assert_eq!(&captures[1], "2.0.0"); assert_eq!(&captures[2], "da487d19d"); - assert_eq!(&captures[3], "x86_64"); - assert_eq!(&captures[4], "linux"); - assert_eq!(captures.get(5).map(|x| x.as_str()), Some("gnu")); - let captures = expected.captures("substrate 2.0.0-alpha.5-da487d19d-x86_64-linux-gnu").unwrap(); + let captures = expected.captures("substrate 2.0.0-alpha.5-da487d19d").unwrap(); assert_eq!(&captures[1], "2.0.0-alpha.5"); assert_eq!(&captures[2], "da487d19d"); - assert_eq!(&captures[3], "x86_64"); - assert_eq!(&captures[4], "linux"); - assert_eq!(captures.get(5).map(|x| x.as_str()), Some("gnu")); - - let captures = expected.captures("substrate 2.0.0-alpha.5-da487d19d-x86_64-linux").unwrap(); - assert_eq!(&captures[1], "2.0.0-alpha.5"); - assert_eq!(&captures[2], "da487d19d"); - assert_eq!(&captures[3], "x86_64"); - assert_eq!(&captures[4], "linux"); - assert_eq!(captures.get(5).map(|x| x.as_str()), None); } diff --git a/bin/node/cli/tests/websocket_server.rs b/bin/node/cli/tests/websocket_server.rs index 658b8de463454..1e7450995230c 100644 --- a/bin/node/cli/tests/websocket_server.rs +++ b/bin/node/cli/tests/websocket_server.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,11 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use async_std::net::{TcpListener, TcpStream}; use core::pin::Pin; use futures::prelude::*; use soketto::handshake::{server::Response, Server}; use std::{io, net::SocketAddr}; +use tokio::net::{TcpListener, TcpStream}; +use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; /// Configuration for a [`WsServer`]. pub struct Config { @@ -71,8 +72,12 @@ pub struct WsServer { negotiating: stream::FuturesUnordered< Pin< Box< - dyn Future, Box>> - + Send, + dyn Future< + Output = Result< + Server<'static, Compat>, + Box, + >, + > + Send, >, >, >, @@ -120,15 +125,15 @@ impl WsServer { let pending_incoming = self.pending_incoming.take().expect("no pending socket"); self.negotiating.push(Box::pin(async move { - let mut server = Server::new(pending_incoming); + let mut server = Server::new(pending_incoming.compat()); let websocket_key = match server.receive_request().await { - Ok(req) => req.into_key(), + Ok(req) => req.key(), Err(err) => return Err(Box::new(err) as Box<_>), }; match server - .send_response(&{ Response::Accept { key: &websocket_key, protocol: None } }) + .send_response(&{ Response::Accept { key: websocket_key, protocol: None } }) .await { Ok(()) => {}, diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index f283a913915f3..8b3add78a9a26 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -3,46 +3,50 @@ name = "node-executor" version = "3.0.0-dev" authors = ["Parity Technologies "] description = "Substrate node implementation in Rust." -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } -scale-info = { version = "1.0", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0" } +scale-info = { version = "2.1.1", features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } node-primitives = { version = "2.0.0", path = "../primitives" } -node-runtime = { version = "3.0.0-dev", path = "../runtime" } +kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", path = "../../../primitives/keystore" } -sp-state-machine = { version = "0.10.0-dev", path = "../../../primitives/state-machine" } -sp-trie = { version = "4.0.0-dev", path = "../../../primitives/trie" } -frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +sp-trie = { version = "7.0.0", path = "../../../primitives/trie" } [dev-dependencies] criterion = "0.3.0" +futures = "0.3.21" +wat = "1.0" frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } node-testing = { version = "3.0.0-dev", path = "../testing" } pallet-balances = { version = "4.0.0-dev", path = "../../../frame/balances" } pallet-contracts = { version = "4.0.0-dev", path = "../../../frame/contracts" } pallet-im-online = { version = "4.0.0-dev", path = "../../../frame/im-online" } +pallet-sudo = { version = "4.0.0-dev", path = "../../../frame/sudo" } pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } pallet-treasury = { version = "4.0.0-dev", path = "../../../frame/treasury" } -sp-application-crypto = { version = "4.0.0-dev", path = "../../../primitives/application-crypto" } +pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } +sp-application-crypto = { version = "7.0.0", path = "../../../primitives/application-crypto" } +pallet-root-testing = { version = "1.0.0-dev", path = "../../../frame/root-testing" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sp-externalities = { version = "0.10.0-dev", path = "../../../primitives/externalities" } -wat = "1.0" -futures = "0.3.9" +sp-externalities = { version = "0.13.0", path = "../../../primitives/externalities" } +sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } [features] -wasmtime = ["sc-executor/wasmtime"] -wasmi-errno = ["sc-executor/wasmi-errno"] stress-test = [] [[bench]] diff --git a/bin/node/executor/benches/bench.rs b/bin/node/executor/benches/bench.rs index 1a39c9decb321..4cbd95471b86b 100644 --- a/bin/node/executor/benches/bench.rs +++ b/bin/node/executor/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,18 +18,20 @@ use codec::{Decode, Encode}; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use frame_support::Hashable; +use kitchensink_runtime::{ + constants::currency::*, Block, BuildStorage, CheckedExtrinsic, GenesisConfig, Header, + RuntimeCall, UncheckedExtrinsic, +}; use node_executor::ExecutorDispatch; use node_primitives::{BlockNumber, Hash}; -use node_runtime::{ - constants::currency::*, Block, BuildStorage, Call, CheckedExtrinsic, GenesisConfig, Header, - UncheckedExtrinsic, -}; use node_testing::keyring::*; -use sc_executor::{Externalities, NativeElseWasmExecutor, RuntimeVersionOf, WasmExecutionMethod}; +use sc_executor::{ + Externalities, NativeElseWasmExecutor, RuntimeVersionOf, WasmExecutionMethod, + WasmtimeInstantiationStrategy, +}; use sp_core::{ storage::well_known_keys, traits::{CodeExecutor, RuntimeCode}, - NativeOrEncoded, NeverNativeValue, }; use sp_runtime::traits::BlakeTwo256; use sp_state_machine::TestExternalities as CoreTestExternalities; @@ -39,7 +41,7 @@ criterion_main!(benches); /// The wasm runtime code. pub fn compact_code_unwrap() -> &'static [u8] { - node_runtime::WASM_BINARY.expect( + kitchensink_runtime::WASM_BINARY.expect( "Development wasm binary is not available. Testing is only supported with the flag \ disabled.", ) @@ -47,13 +49,13 @@ pub fn compact_code_unwrap() -> &'static [u8] { const GENESIS_HASH: [u8; 32] = [69u8; 32]; -const TRANSACTION_VERSION: u32 = node_runtime::VERSION.transaction_version; +const TRANSACTION_VERSION: u32 = kitchensink_runtime::VERSION.transaction_version; -const SPEC_VERSION: u32 = node_runtime::VERSION.spec_version; +const SPEC_VERSION: u32 = kitchensink_runtime::VERSION.spec_version; const HEAP_PAGES: u64 = 20; -type TestExternalities = CoreTestExternalities; +type TestExternalities = CoreTestExternalities; #[derive(Debug)] enum ExecutionMethod { @@ -83,14 +85,14 @@ fn construct_block( parent_hash: Hash, extrinsics: Vec, ) -> (Vec, Hash) { - use sp_trie::{trie_types::Layout, TrieConfiguration}; + use sp_trie::{LayoutV0, TrieConfiguration}; // sign extrinsics. let extrinsics = extrinsics.into_iter().map(sign).collect::>(); // calculate the header fields that we can. let extrinsics_root = - Layout::::ordered_trie_root(extrinsics.iter().map(Encode::encode)) + LayoutV0::::ordered_trie_root(extrinsics.iter().map(Encode::encode)) .to_fixed_bytes() .into(); @@ -110,46 +112,24 @@ fn construct_block( // execute the block to get the real header. executor - .call:: _>( - ext, - &runtime_code, - "Core_initialize_block", - &header.encode(), - true, - None, - ) + .call(ext, &runtime_code, "Core_initialize_block", &header.encode(), true) .0 .unwrap(); for i in extrinsics.iter() { executor - .call:: _>( - ext, - &runtime_code, - "BlockBuilder_apply_extrinsic", - &i.encode(), - true, - None, - ) + .call(ext, &runtime_code, "BlockBuilder_apply_extrinsic", &i.encode(), true) .0 .unwrap(); } - let header = match executor - .call:: _>( - ext, - &runtime_code, - "BlockBuilder_finalize_block", - &[0u8; 0], - true, - None, - ) - .0 - .unwrap() - { - NativeOrEncoded::Native(_) => unreachable!(), - NativeOrEncoded::Encoded(h) => Header::decode(&mut &h[..]).unwrap(), - }; + let header = Header::decode( + &mut &executor + .call(ext, &runtime_code, "BlockBuilder_finalize_block", &[0u8; 0], true) + .0 + .unwrap()[..], + ) + .unwrap(); let hash = header.blake2_256(); (Block { header, extrinsics }.encode(), hash.into()) @@ -162,11 +142,11 @@ fn test_blocks( let mut test_ext = new_test_ext(genesis_config); let mut block1_extrinsics = vec![CheckedExtrinsic { signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set { now: 0 }), + function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: 0 }), }]; block1_extrinsics.extend((0..20).map(|i| CheckedExtrinsic { signed: Some((alice(), signed_extra(i, 0))), - function: Call::Balances(pallet_balances::Call::transfer { + function: RuntimeCall::Balances(pallet_balances::Call::transfer { dest: bob().into(), value: 1 * DOLLARS, }), @@ -182,19 +162,20 @@ fn bench_execute_block(c: &mut Criterion) { let execution_methods = vec![ ExecutionMethod::Native, ExecutionMethod::Wasm(WasmExecutionMethod::Interpreted), - #[cfg(feature = "wasmtime")] - ExecutionMethod::Wasm(WasmExecutionMethod::Compiled), + ExecutionMethod::Wasm(WasmExecutionMethod::Compiled { + instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + }), ]; for strategy in execution_methods { group.bench_function(format!("{:?}", strategy), |b| { - let genesis_config = node_testing::genesis::config(false, Some(compact_code_unwrap())); + let genesis_config = node_testing::genesis::config(Some(compact_code_unwrap())); let (use_native, wasm_method) = match strategy { ExecutionMethod::Native => (true, WasmExecutionMethod::Interpreted), ExecutionMethod::Wasm(wasm_method) => (false, wasm_method), }; - let executor = NativeElseWasmExecutor::new(wasm_method, None, 8); + let executor = NativeElseWasmExecutor::new(wasm_method, None, 8, 2); let runtime_code = RuntimeCode { code_fetcher: &sp_core::traits::WrappedRuntimeCode(compact_code_unwrap().into()), hash: vec![1, 2, 3], @@ -214,13 +195,12 @@ fn bench_execute_block(c: &mut Criterion) { |test_ext| { for block in blocks.iter() { executor - .call:: _>( + .call( &mut test_ext.ext(), &runtime_code, "Core_execute_block", &block.0, use_native, - None, ) .0 .unwrap(); diff --git a/bin/node/executor/src/lib.rs b/bin/node/executor/src/lib.rs index 9a7a0c4d3c110..c4edf5ad22f47 100644 --- a/bin/node/executor/src/lib.rs +++ b/bin/node/executor/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,10 +28,10 @@ impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; fn dispatch(method: &str, data: &[u8]) -> Option> { - node_runtime::api::dispatch(method, data) + kitchensink_runtime::api::dispatch(method, data) } fn native_version() -> sc_executor::NativeVersion { - node_runtime::native_version() + kitchensink_runtime::native_version() } } diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs index c1ab5e5a0fe13..fc4e138faafc2 100644 --- a/bin/node/executor/tests/basic.rs +++ b/bin/node/executor/tests/basic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,21 +17,22 @@ use codec::{Decode, Encode, Joiner}; use frame_support::{ + dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo}, traits::Currency, - weights::{DispatchClass, DispatchInfo, GetDispatchInfo}, + weights::Weight, }; use frame_system::{self, AccountInfo, EventRecord, Phase}; -use sp_core::{storage::well_known_keys, traits::Externalities, NeverNativeValue}; +use sp_core::{storage::well_known_keys, traits::Externalities}; use sp_runtime::{ traits::Hash as HashT, transaction_validity::InvalidTransaction, ApplyExtrinsicResult, }; -use node_primitives::{Balance, Hash}; -use node_runtime::{ +use kitchensink_runtime::{ constants::{currency::*, time::SLOT_DURATION}, - Balances, Block, Call, CheckedExtrinsic, Event, Header, Runtime, System, TransactionPayment, - UncheckedExtrinsic, + Balances, CheckedExtrinsic, Header, Runtime, RuntimeCall, RuntimeEvent, System, + TransactionPayment, UncheckedExtrinsic, }; +use node_primitives::{Balance, Hash}; use node_testing::keyring::*; use wat; @@ -44,7 +45,7 @@ use self::common::{sign, *}; /// have to execute provided wasm code instead of the native equivalent. This trick is used to /// test code paths that differ between native and wasm versions. pub fn bloaty_code_unwrap() -> &'static [u8] { - node_runtime::WASM_BINARY_BLOATY.expect( + kitchensink_runtime::WASM_BINARY_BLOATY.expect( "Development wasm binary is not available. \ Testing is only supported with the flag disabled.", ) @@ -67,7 +68,7 @@ fn transfer_fee(extrinsic: &E) -> Balance { fn xt() -> UncheckedExtrinsic { sign(CheckedExtrinsic { signed: Some((alice(), signed_extra(0, 0))), - function: Call::Balances(default_transfer_call()), + function: RuntimeCall::Balances(default_transfer_call()), }) } @@ -78,17 +79,17 @@ fn set_heap_pages(ext: &mut E, heap_pages: u64) { fn changes_trie_block() -> (Vec, Hash) { let time = 42 * 1000; construct_block( - &mut new_test_ext(compact_code_unwrap(), true), + &mut new_test_ext(compact_code_unwrap()), 1, GENESIS_HASH.into(), vec![ CheckedExtrinsic { signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set { now: time }), + function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time }), }, CheckedExtrinsic { signed: Some((alice(), signed_extra(0, 0))), - function: Call::Balances(pallet_balances::Call::transfer { + function: RuntimeCall::Balances(pallet_balances::Call::transfer { dest: bob().into(), value: 69 * DOLLARS, }), @@ -102,7 +103,7 @@ fn changes_trie_block() -> (Vec, Hash) { /// are not guaranteed to be deterministic) and to ensure that the correct state is propagated /// from block1's execution to block2 to derive the correct storage_root. fn blocks() -> ((Vec, Hash), (Vec, Hash)) { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); let time1 = 42 * 1000; let block1 = construct_block( &mut t, @@ -111,11 +112,11 @@ fn blocks() -> ((Vec, Hash), (Vec, Hash)) { vec![ CheckedExtrinsic { signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set { now: time1 }), + function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time1 }), }, CheckedExtrinsic { signed: Some((alice(), signed_extra(0, 0))), - function: Call::Balances(pallet_balances::Call::transfer { + function: RuntimeCall::Balances(pallet_balances::Call::transfer { dest: bob().into(), value: 69 * DOLLARS, }), @@ -127,22 +128,22 @@ fn blocks() -> ((Vec, Hash), (Vec, Hash)) { let block2 = construct_block( &mut t, 2, - block1.1.clone(), + block1.1, vec![ CheckedExtrinsic { signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set { now: time2 }), + function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time2 }), }, CheckedExtrinsic { signed: Some((bob(), signed_extra(0, 0))), - function: Call::Balances(pallet_balances::Call::transfer { + function: RuntimeCall::Balances(pallet_balances::Call::transfer { dest: alice().into(), value: 5 * DOLLARS, }), }, CheckedExtrinsic { signed: Some((alice(), signed_extra(1, 0))), - function: Call::Balances(pallet_balances::Call::transfer { + function: RuntimeCall::Balances(pallet_balances::Call::transfer { dest: bob().into(), value: 15 * DOLLARS, }), @@ -160,17 +161,17 @@ fn blocks() -> ((Vec, Hash), (Vec, Hash)) { fn block_with_size(time: u64, nonce: u32, size: usize) -> (Vec, Hash) { construct_block( - &mut new_test_ext(compact_code_unwrap(), false), + &mut new_test_ext(compact_code_unwrap()), 1, GENESIS_HASH.into(), vec![ CheckedExtrinsic { signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set { now: time * 1000 }), + function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time * 1000 }), }, CheckedExtrinsic { signed: Some((alice(), signed_extra(nonce, 0))), - function: Call::System(frame_system::Call::remark { remark: vec![0; size] }), + function: RuntimeCall::System(frame_system::Call::remark { remark: vec![0; size] }), }, ], (time * 1000 / SLOT_DURATION).into(), @@ -179,7 +180,7 @@ fn block_with_size(time: u64, nonce: u32, size: usize) -> (Vec, Hash) { #[test] fn panic_execution_with_foreign_code_gives_error() { - let mut t = new_test_ext(bloaty_code_unwrap(), false); + let mut t = new_test_ext(bloaty_code_unwrap()); t.insert( >::hashed_key_for(alice()), (69u128, 0u32, 0u128, 0u128, 0u128).encode(), @@ -187,31 +188,20 @@ fn panic_execution_with_foreign_code_gives_error() { t.insert(>::hashed_key().to_vec(), 69_u128.encode()); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - true, - None, - ) - .0; + let r = + executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), true) + .0; assert!(r.is_ok()); - let v = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - true, - None, - ) - .0 - .unwrap(); - let r = ApplyExtrinsicResult::decode(&mut &v.as_encoded()[..]).unwrap(); + let v = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), true) + .0 + .unwrap(); + let r = ApplyExtrinsicResult::decode(&mut &v[..]).unwrap(); assert_eq!(r, Err(InvalidTransaction::Payment.into())); } #[test] fn bad_extrinsic_with_native_equivalent_code_gives_error() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); t.insert( >::hashed_key_for(alice()), (0u32, 0u32, 0u32, 69u128, 0u128, 0u128, 0u128).encode(), @@ -219,31 +209,20 @@ fn bad_extrinsic_with_native_equivalent_code_gives_error() { t.insert(>::hashed_key().to_vec(), 69_u128.encode()); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - true, - None, - ) - .0; + let r = + executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), true) + .0; assert!(r.is_ok()); - let v = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - true, - None, - ) - .0 - .unwrap(); - let r = ApplyExtrinsicResult::decode(&mut &v.as_encoded()[..]).unwrap(); + let v = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), true) + .0 + .unwrap(); + let r = ApplyExtrinsicResult::decode(&mut &v[..]).unwrap(); assert_eq!(r, Err(InvalidTransaction::Payment.into())); } #[test] fn successful_execution_with_native_equivalent_code_gives_ok() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); t.insert( >::hashed_key_for(alice()), AccountInfo::<::Index, _> { @@ -266,26 +245,14 @@ fn successful_execution_with_native_equivalent_code_gives_ok() { ); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - true, - None, - ) - .0; + let r = + executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), true) + .0; assert!(r.is_ok()); let fees = t.execute_with(|| transfer_fee(&xt())); - let r = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - true, - None, - ) - .0; + let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), true).0; assert!(r.is_ok()); t.execute_with(|| { @@ -296,7 +263,7 @@ fn successful_execution_with_native_equivalent_code_gives_ok() { #[test] fn successful_execution_with_foreign_code_gives_ok() { - let mut t = new_test_ext(bloaty_code_unwrap(), false); + let mut t = new_test_ext(bloaty_code_unwrap()); t.insert( >::hashed_key_for(alice()), AccountInfo::<::Index, _> { @@ -319,26 +286,14 @@ fn successful_execution_with_foreign_code_gives_ok() { ); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - true, - None, - ) - .0; + let r = + executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), true) + .0; assert!(r.is_ok()); let fees = t.execute_with(|| transfer_fee(&xt())); - let r = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - true, - None, - ) - .0; + let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), true).0; assert!(r.is_ok()); t.execute_with(|| { @@ -349,27 +304,28 @@ fn successful_execution_with_foreign_code_gives_ok() { #[test] fn full_native_block_import_works() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); let (block1, block2) = blocks(); let mut alice_last_known_balance: Balance = Default::default(); let mut fees = t.execute_with(|| transfer_fee(&xt())); - let transfer_weight = default_transfer_call().get_dispatch_info().weight; + let transfer_weight = default_transfer_call().get_dispatch_info().weight.saturating_add( + ::BlockWeights::get() + .get(DispatchClass::Normal) + .base_extrinsic, + ); let timestamp_weight = pallet_timestamp::Call::set:: { now: Default::default() } .get_dispatch_info() - .weight; + .weight + .saturating_add( + ::BlockWeights::get() + .get(DispatchClass::Mandatory) + .base_extrinsic, + ); - executor_call:: _>( - &mut t, - "Core_execute_block", - &block1.0, - true, - None, - ) - .0 - .unwrap(); + executor_call(&mut t, "Core_execute_block", &block1.0, true).0.unwrap(); t.execute_with(|| { assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees); @@ -378,33 +334,63 @@ fn full_native_block_import_works() { let events = vec![ EventRecord { phase: Phase::ApplyExtrinsic(0), - event: Event::System(frame_system::Event::ExtrinsicSuccess(DispatchInfo { - weight: timestamp_weight, - class: DispatchClass::Mandatory, - ..Default::default() - })), + event: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: timestamp_weight, + class: DispatchClass::Mandatory, + ..Default::default() + }, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: RuntimeEvent::Balances(pallet_balances::Event::Withdraw { + who: alice().into(), + amount: fees, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: alice().into(), + to: bob().into(), + amount: 69 * DOLLARS, + }), topics: vec![], }, EventRecord { phase: Phase::ApplyExtrinsic(1), - event: Event::Balances(pallet_balances::Event::Transfer( - alice().into(), - bob().into(), - 69 * DOLLARS, - )), + event: RuntimeEvent::Balances(pallet_balances::Event::Deposit { + who: pallet_treasury::Pallet::::account_id(), + amount: fees * 8 / 10, + }), topics: vec![], }, EventRecord { phase: Phase::ApplyExtrinsic(1), - event: Event::Treasury(pallet_treasury::Event::Deposit(fees * 8 / 10)), + event: RuntimeEvent::Treasury(pallet_treasury::Event::Deposit { + value: fees * 8 / 10, + }), topics: vec![], }, EventRecord { phase: Phase::ApplyExtrinsic(1), - event: Event::System(frame_system::Event::ExtrinsicSuccess(DispatchInfo { - weight: transfer_weight, - ..Default::default() - })), + event: RuntimeEvent::TransactionPayment( + pallet_transaction_payment::Event::TransactionFeePaid { + who: alice().into(), + actual_fee: fees, + tip: 0, + }, + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess { + dispatch_info: DispatchInfo { weight: transfer_weight, ..Default::default() }, + }), topics: vec![], }, ]; @@ -413,15 +399,7 @@ fn full_native_block_import_works() { fees = t.execute_with(|| transfer_fee(&xt())); - executor_call:: _>( - &mut t, - "Core_execute_block", - &block2.0, - true, - None, - ) - .0 - .unwrap(); + executor_call(&mut t, "Core_execute_block", &block2.0, true).0.unwrap(); t.execute_with(|| { assert_eq!( @@ -432,55 +410,113 @@ fn full_native_block_import_works() { let events = vec![ EventRecord { phase: Phase::ApplyExtrinsic(0), - event: Event::System(frame_system::Event::ExtrinsicSuccess(DispatchInfo { - weight: timestamp_weight, - class: DispatchClass::Mandatory, - ..Default::default() - })), + event: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: timestamp_weight, + class: DispatchClass::Mandatory, + ..Default::default() + }, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: RuntimeEvent::Balances(pallet_balances::Event::Withdraw { + who: bob().into(), + amount: fees, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: bob().into(), + to: alice().into(), + amount: 5 * DOLLARS, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: RuntimeEvent::Balances(pallet_balances::Event::Deposit { + who: pallet_treasury::Pallet::::account_id(), + amount: fees * 8 / 10, + }), topics: vec![], }, EventRecord { phase: Phase::ApplyExtrinsic(1), - event: Event::Balances(pallet_balances::Event::Transfer( - bob().into(), - alice().into(), - 5 * DOLLARS, - )), + event: RuntimeEvent::Treasury(pallet_treasury::Event::Deposit { + value: fees * 8 / 10, + }), topics: vec![], }, EventRecord { phase: Phase::ApplyExtrinsic(1), - event: Event::Treasury(pallet_treasury::Event::Deposit(fees * 8 / 10)), + event: RuntimeEvent::TransactionPayment( + pallet_transaction_payment::Event::TransactionFeePaid { + who: bob().into(), + actual_fee: fees, + tip: 0, + }, + ), topics: vec![], }, EventRecord { phase: Phase::ApplyExtrinsic(1), - event: Event::System(frame_system::Event::ExtrinsicSuccess(DispatchInfo { - weight: transfer_weight, - ..Default::default() - })), + event: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess { + dispatch_info: DispatchInfo { weight: transfer_weight, ..Default::default() }, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(2), + event: RuntimeEvent::Balances(pallet_balances::Event::Withdraw { + who: alice().into(), + amount: fees, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(2), + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: alice().into(), + to: bob().into(), + amount: 15 * DOLLARS, + }), topics: vec![], }, EventRecord { phase: Phase::ApplyExtrinsic(2), - event: Event::Balances(pallet_balances::Event::Transfer( - alice().into(), - bob().into(), - 15 * DOLLARS, - )), + event: RuntimeEvent::Balances(pallet_balances::Event::Deposit { + who: pallet_treasury::Pallet::::account_id(), + amount: fees * 8 / 10, + }), topics: vec![], }, EventRecord { phase: Phase::ApplyExtrinsic(2), - event: Event::Treasury(pallet_treasury::Event::Deposit(fees * 8 / 10)), + event: RuntimeEvent::Treasury(pallet_treasury::Event::Deposit { + value: fees * 8 / 10, + }), topics: vec![], }, EventRecord { phase: Phase::ApplyExtrinsic(2), - event: Event::System(frame_system::Event::ExtrinsicSuccess(DispatchInfo { - weight: transfer_weight, - ..Default::default() - })), + event: RuntimeEvent::TransactionPayment( + pallet_transaction_payment::Event::TransactionFeePaid { + who: alice().into(), + actual_fee: fees, + tip: 0, + }, + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(2), + event: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess { + dispatch_info: DispatchInfo { weight: transfer_weight, ..Default::default() }, + }), topics: vec![], }, ]; @@ -490,22 +526,14 @@ fn full_native_block_import_works() { #[test] fn full_wasm_block_import_works() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); let (block1, block2) = blocks(); let mut alice_last_known_balance: Balance = Default::default(); let mut fees = t.execute_with(|| transfer_fee(&xt())); - executor_call:: _>( - &mut t, - "Core_execute_block", - &block1.0, - false, - None, - ) - .0 - .unwrap(); + executor_call(&mut t, "Core_execute_block", &block1.0, false).0.unwrap(); t.execute_with(|| { assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees); @@ -515,15 +543,7 @@ fn full_wasm_block_import_works() { fees = t.execute_with(|| transfer_fee(&xt())); - executor_call:: _>( - &mut t, - "Core_execute_block", - &block2.0, - false, - None, - ) - .0 - .unwrap(); + executor_call(&mut t, "Core_execute_block", &block2.0, false).0.unwrap(); t.execute_with(|| { assert_eq!( @@ -636,36 +656,36 @@ fn deploying_wasm_contract_should_work() { let addr = pallet_contracts::Pallet::::contract_address(&charlie(), &transfer_ch, &[]); - let subsistence = pallet_contracts::Pallet::::subsistence_threshold(); - let time = 42 * 1000; let b = construct_block( - &mut new_test_ext(compact_code_unwrap(), false), + &mut new_test_ext(compact_code_unwrap()), 1, GENESIS_HASH.into(), vec![ CheckedExtrinsic { signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set { now: time }), + function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time }), }, CheckedExtrinsic { signed: Some((charlie(), signed_extra(0, 0))), - function: Call::Contracts( - pallet_contracts::Call::instantiate_with_code:: { - endowment: 1000 * DOLLARS + subsistence, - gas_limit: 500_000_000, - code: transfer_code, - data: Vec::new(), - salt: Vec::new(), - }, - ), + function: RuntimeCall::Contracts(pallet_contracts::Call::instantiate_with_code::< + Runtime, + > { + value: 0, + gas_limit: Weight::from_ref_time(500_000_000), + storage_deposit_limit: None, + code: transfer_code, + data: Vec::new(), + salt: Vec::new(), + }), }, CheckedExtrinsic { signed: Some((charlie(), signed_extra(1, 0))), - function: Call::Contracts(pallet_contracts::Call::call:: { + function: RuntimeCall::Contracts(pallet_contracts::Call::call:: { dest: sp_runtime::MultiAddress::Id(addr.clone()), value: 10, - gas_limit: 500_000_000, + gas_limit: Weight::from_ref_time(500_000_000), + storage_deposit_limit: None, data: vec![0x00, 0x01, 0x02, 0x03], }), }, @@ -673,73 +693,59 @@ fn deploying_wasm_contract_should_work() { (time / SLOT_DURATION).into(), ); - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); - executor_call:: _>(&mut t, "Core_execute_block", &b.0, false, None) - .0 - .unwrap(); + executor_call(&mut t, "Core_execute_block", &b.0, false).0.unwrap(); t.execute_with(|| { // Verify that the contract does exist by querying some of its storage items // It does not matter that the storage item itself does not exist. - assert!(&pallet_contracts::Pallet::::get_storage(addr, Default::default()).is_ok()); + assert!(&pallet_contracts::Pallet::::get_storage( + addr, + pallet_contracts::StorageKey::::default().to_vec() + ) + .is_ok()); }); } #[test] fn wasm_big_block_import_fails() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); set_heap_pages(&mut t.ext(), 4); - let result = executor_call:: _>( - &mut t, - "Core_execute_block", - &block_with_size(42, 0, 120_000).0, - false, - None, - ) - .0; + let result = + executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0, false).0; assert!(result.is_err()); // Err(Wasmi(Trap(Trap { kind: Host(AllocatorOutOfSpace) }))) } #[test] fn native_big_block_import_succeeds() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); - executor_call:: _>( - &mut t, - "Core_execute_block", - &block_with_size(42, 0, 120_000).0, - true, - None, - ) - .0 - .unwrap(); + executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0, true) + .0 + .unwrap(); } #[test] fn native_big_block_import_fails_on_fallback() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); // We set the heap pages to 8 because we know that should give an OOM in WASM with the given // block. set_heap_pages(&mut t.ext(), 8); - assert!(executor_call:: _>( - &mut t, - "Core_execute_block", - &block_with_size(42, 0, 120_000).0, - false, - None, - ) - .0 - .is_err()); + assert!( + executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0, false,) + .0 + .is_err() + ); } #[test] fn panic_execution_gives_error() { - let mut t = new_test_ext(bloaty_code_unwrap(), false); + let mut t = new_test_ext(bloaty_code_unwrap()); t.insert( >::hashed_key_for(alice()), AccountInfo::<::Index, _> { @@ -751,32 +757,24 @@ fn panic_execution_gives_error() { t.insert(>::hashed_key().to_vec(), 0_u128.encode()); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = executor_call:: _>( + let r = executor_call( &mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), false, - None, ) .0; assert!(r.is_ok()); - let r = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - false, - None, - ) - .0 - .unwrap() - .into_encoded(); + let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), false) + .0 + .unwrap(); let r = ApplyExtrinsicResult::decode(&mut &r[..]).unwrap(); assert_eq!(r, Err(InvalidTransaction::Payment.into())); } #[test] fn successful_execution_gives_ok() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); t.insert( >::hashed_key_for(alice()), AccountInfo::<::Index, _> { @@ -799,12 +797,11 @@ fn successful_execution_gives_ok() { ); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = executor_call:: _>( + let r = executor_call( &mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), false, - None, ) .0; assert!(r.is_ok()); @@ -814,16 +811,9 @@ fn successful_execution_gives_ok() { let fees = t.execute_with(|| transfer_fee(&xt())); - let r = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - false, - None, - ) - .0 - .unwrap() - .into_encoded(); + let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), false) + .0 + .unwrap(); ApplyExtrinsicResult::decode(&mut &r[..]) .unwrap() .expect("Extrinsic could not be applied") @@ -835,44 +825,6 @@ fn successful_execution_gives_ok() { }); } -#[test] -fn full_native_block_import_works_with_changes_trie() { - let block1 = changes_trie_block(); - let block_data = block1.0; - let block = Block::decode(&mut &block_data[..]).unwrap(); - - let mut t = new_test_ext(compact_code_unwrap(), true); - executor_call:: _>( - &mut t, - "Core_execute_block", - &block.encode(), - true, - None, - ) - .0 - .unwrap(); - - assert!(t.ext().storage_changes_root(&GENESIS_HASH).unwrap().is_some()); -} - -#[test] -fn full_wasm_block_import_works_with_changes_trie() { - let block1 = changes_trie_block(); - - let mut t = new_test_ext(compact_code_unwrap(), true); - executor_call:: _>( - &mut t, - "Core_execute_block", - &block1.0, - false, - None, - ) - .0 - .unwrap(); - - assert!(t.ext().storage_changes_root(&GENESIS_HASH).unwrap().is_some()); -} - #[test] fn should_import_block_with_test_client() { use node_testing::client::{ diff --git a/bin/node/executor/tests/common.rs b/bin/node/executor/tests/common.rs index d1c24c83c836d..803ec78329eea 100644 --- a/bin/node/executor/tests/common.rs +++ b/bin/node/executor/tests/common.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,6 @@ use sp_core::{ crypto::KeyTypeId, sr25519::Signature, traits::{CodeExecutor, RuntimeCode}, - NativeOrEncoded, NeverNativeValue, }; use sp_runtime::{ traits::{BlakeTwo256, Header as HeaderT}, @@ -35,12 +34,12 @@ use sp_runtime::{ }; use sp_state_machine::TestExternalities as CoreTestExternalities; -use node_executor::ExecutorDispatch; -use node_primitives::{BlockNumber, Hash}; -use node_runtime::{ +use kitchensink_runtime::{ constants::currency::*, Block, BuildStorage, CheckedExtrinsic, Header, Runtime, UncheckedExtrinsic, }; +use node_executor::ExecutorDispatch; +use node_primitives::{BlockNumber, Hash}; use node_testing::keyring::*; use sp_externalities::Externalities; @@ -69,7 +68,7 @@ impl AppCrypto for TestAuthorityId { /// making the binary slimmer. There is a convention to use compact version of the runtime /// as canonical. pub fn compact_code_unwrap() -> &'static [u8] { - node_runtime::WASM_BINARY.expect( + kitchensink_runtime::WASM_BINARY.expect( "Development wasm binary is not available. Testing is only supported with the flag \ disabled.", ) @@ -77,11 +76,11 @@ pub fn compact_code_unwrap() -> &'static [u8] { pub const GENESIS_HASH: [u8; 32] = [69u8; 32]; -pub const SPEC_VERSION: u32 = node_runtime::VERSION.spec_version; +pub const SPEC_VERSION: u32 = kitchensink_runtime::VERSION.spec_version; -pub const TRANSACTION_VERSION: u32 = node_runtime::VERSION.transaction_version; +pub const TRANSACTION_VERSION: u32 = kitchensink_runtime::VERSION.transaction_version; -pub type TestExternalities = CoreTestExternalities; +pub type TestExternalities = CoreTestExternalities; pub fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic { node_testing::keyring::sign(xt, SPEC_VERSION, TRANSACTION_VERSION, GENESIS_HASH) @@ -96,20 +95,15 @@ pub fn from_block_number(n: u32) -> Header { } pub fn executor() -> NativeElseWasmExecutor { - NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8) + NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8, 2) } -pub fn executor_call< - R: Decode + Encode + PartialEq, - NC: FnOnce() -> std::result::Result> - + std::panic::UnwindSafe, ->( +pub fn executor_call( t: &mut TestExternalities, method: &str, data: &[u8], use_native: bool, - native_call: Option, -) -> (Result>, bool) { +) -> (Result>, bool) { let mut t = t.ext(); let code = t.storage(sp_core::storage::well_known_keys::CODE).unwrap(); @@ -119,18 +113,15 @@ pub fn executor_call< hash: sp_core::blake2_256(&code).to_vec(), heap_pages: heap_pages.and_then(|hp| Decode::decode(&mut &hp[..]).ok()), }; - - executor().call::(&mut t, &runtime_code, method, data, use_native, native_call) + sp_tracing::try_init_simple(); + executor().call(&mut t, &runtime_code, method, data, use_native) } -pub fn new_test_ext(code: &[u8], support_changes_trie: bool) -> TestExternalities { - let mut ext = TestExternalities::new_with_code( +pub fn new_test_ext(code: &[u8]) -> TestExternalities { + let ext = TestExternalities::new_with_code( code, - node_testing::genesis::config(support_changes_trie, Some(code)) - .build_storage() - .unwrap(), + node_testing::genesis::config(Some(code)).build_storage().unwrap(), ); - ext.changes_trie_storage().insert(0, GENESIS_HASH.into(), Default::default()); ext } @@ -145,7 +136,7 @@ pub fn construct_block( extrinsics: Vec, babe_slot: Slot, ) -> (Vec, Hash) { - use sp_trie::{trie_types::Layout, TrieConfiguration}; + use sp_trie::{LayoutV1 as Layout, TrieConfiguration}; // sign extrinsics. let extrinsics = extrinsics.into_iter().map(sign).collect::>(); @@ -174,29 +165,15 @@ pub fn construct_block( }; // execute the block to get the real header. - executor_call:: _>( - env, - "Core_initialize_block", - &header.encode(), - true, - None, - ) - .0 - .unwrap(); + executor_call(env, "Core_initialize_block", &header.encode(), true).0.unwrap(); for extrinsic in extrinsics.iter() { // Try to apply the `extrinsic`. It should be valid, in the sense that it passes // all pre-inclusion checks. - let r = executor_call:: _>( - env, - "BlockBuilder_apply_extrinsic", - &extrinsic.encode(), - true, - None, - ) - .0 - .expect("application of an extrinsic failed") - .into_encoded(); + let r = executor_call(env, "BlockBuilder_apply_extrinsic", &extrinsic.encode(), true) + .0 + .expect("application of an extrinsic failed"); + match ApplyExtrinsicResult::decode(&mut &r[..]) .expect("apply result deserialization failed") { @@ -205,19 +182,10 @@ pub fn construct_block( } } - let header = match executor_call:: _>( - env, - "BlockBuilder_finalize_block", - &[0u8; 0], - true, - None, + let header = Header::decode( + &mut &executor_call(env, "BlockBuilder_finalize_block", &[0u8; 0], true).0.unwrap()[..], ) - .0 - .unwrap() - { - NativeOrEncoded::Native(_) => unreachable!(), - NativeOrEncoded::Encoded(h) => Header::decode(&mut &h[..]).unwrap(), - }; + .unwrap(); let hash = header.blake2_256(); (Block { header, extrinsics }.encode(), hash.into()) diff --git a/bin/node/executor/tests/fees.rs b/bin/node/executor/tests/fees.rs index 379cdda5b76a3..3c696d595040b 100644 --- a/bin/node/executor/tests/fees.rs +++ b/bin/node/executor/tests/fees.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,18 +17,17 @@ use codec::{Encode, Joiner}; use frame_support::{ + dispatch::GetDispatchInfo, traits::Currency, - weights::{ - constants::ExtrinsicBaseWeight, GetDispatchInfo, IdentityFee, WeightToFeePolynomial, - }, + weights::{constants::ExtrinsicBaseWeight, IdentityFee, WeightToFee}, }; -use node_primitives::Balance; -use node_runtime::{ +use kitchensink_runtime::{ constants::{currency::*, time::SLOT_DURATION}, - Balances, Call, CheckedExtrinsic, Multiplier, Runtime, TransactionByteFee, TransactionPayment, + Balances, CheckedExtrinsic, Multiplier, Runtime, RuntimeCall, TransactionByteFee, + TransactionPayment, }; +use node_primitives::Balance; use node_testing::keyring::*; -use sp_core::NeverNativeValue; use sp_runtime::{traits::One, Perbill}; pub mod common; @@ -36,7 +35,7 @@ use self::common::{sign, *}; #[test] fn fee_multiplier_increases_and_decreases_on_big_weight() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); // initial fee multiplier must be one. let mut prev_multiplier = Multiplier::one(); @@ -45,7 +44,7 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { assert_eq!(TransactionPayment::next_fee_multiplier(), prev_multiplier); }); - let mut tt = new_test_ext(compact_code_unwrap(), false); + let mut tt = new_test_ext(compact_code_unwrap()); let time1 = 42 * 1000; // big one in terms of weight. @@ -56,12 +55,14 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { vec![ CheckedExtrinsic { signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set { now: time1 }), + function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time1 }), }, CheckedExtrinsic { signed: Some((charlie(), signed_extra(0, 0))), - function: Call::System(frame_system::Call::fill_block { - ratio: Perbill::from_percent(60), + function: RuntimeCall::Sudo(pallet_sudo::Call::sudo { + call: Box::new(RuntimeCall::RootTesting( + pallet_root_testing::Call::fill_block { ratio: Perbill::from_percent(60) }, + )), }), }, ], @@ -73,15 +74,15 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { let block2 = construct_block( &mut tt, 2, - block1.1.clone(), + block1.1, vec![ CheckedExtrinsic { signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set { now: time2 }), + function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time2 }), }, CheckedExtrinsic { signed: Some((charlie(), signed_extra(1, 0))), - function: Call::System(frame_system::Call::remark { remark: vec![0; 1] }), + function: RuntimeCall::System(frame_system::Call::remark { remark: vec![0; 1] }), }, ], (time2 / SLOT_DURATION).into(), @@ -94,15 +95,7 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { ); // execute a big block. - executor_call:: _>( - &mut t, - "Core_execute_block", - &block1.0, - true, - None, - ) - .0 - .unwrap(); + executor_call(&mut t, "Core_execute_block", &block1.0, true).0.unwrap(); // weight multiplier is increased for next block. t.execute_with(|| { @@ -113,15 +106,7 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { }); // execute a big block. - executor_call:: _>( - &mut t, - "Core_execute_block", - &block2.0, - true, - None, - ) - .0 - .unwrap(); + executor_call(&mut t, "Core_execute_block", &block2.0, true).0.unwrap(); // weight multiplier is increased for next block. t.execute_with(|| { @@ -151,7 +136,7 @@ fn transaction_fee_is_correct() { // - 1 MILLICENTS in substrate node. // - 1 milli-dot based on current polkadot runtime. // (this baed on assigning 0.1 CENT to the cheapest tx with `weight = 100`) - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); t.insert(>::hashed_key_for(alice()), new_account_info(100)); t.insert(>::hashed_key_for(bob()), new_account_info(10)); t.insert( @@ -163,27 +148,15 @@ fn transaction_fee_is_correct() { let tip = 1_000_000; let xt = sign(CheckedExtrinsic { signed: Some((alice(), signed_extra(0, tip))), - function: Call::Balances(default_transfer_call()), + function: RuntimeCall::Balances(default_transfer_call()), }); - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - true, - None, - ) - .0; + let r = + executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), true) + .0; assert!(r.is_ok()); - let r = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt.clone()), - true, - None, - ) - .0; + let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt.clone()), true).0; assert!(r.is_ok()); t.execute_with(|| { @@ -197,18 +170,18 @@ fn transaction_fee_is_correct() { let mut balance_alice = (100 - 69) * DOLLARS; let base_weight = ExtrinsicBaseWeight::get(); - let base_fee = IdentityFee::::calc(&base_weight); + let base_fee = IdentityFee::::weight_to_fee(&base_weight); let length_fee = TransactionByteFee::get() * (xt.clone().encode().len() as Balance); balance_alice -= length_fee; let weight = default_transfer_call().get_dispatch_info().weight; - let weight_fee = IdentityFee::::calc(&weight); + let weight_fee = IdentityFee::::weight_to_fee(&weight); // we know that weight to fee multiplier is effect-less in block 1. // current weight of transfer = 200_000_000 // Linear weight to fee is 1:1 right now (1 weight = 1 unit of balance) - assert_eq!(weight_fee, weight as Balance); + assert_eq!(weight_fee, weight.ref_time() as Balance); balance_alice -= base_fee; balance_alice -= weight_fee; balance_alice -= tip; @@ -226,9 +199,9 @@ fn block_weight_capacity_report() { use node_primitives::Index; // execution ext. - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); // setup ext. - let mut tt = new_test_ext(compact_code_unwrap(), false); + let mut tt = new_test_ext(compact_code_unwrap()); let factor = 50; let mut time = 10; @@ -241,7 +214,10 @@ fn block_weight_capacity_report() { let mut xts = (0..num_transfers) .map(|i| CheckedExtrinsic { signed: Some((charlie(), signed_extra(nonce + i as Index, 0))), - function: Call::Balances(pallet_balances::Call::transfer(bob().into(), 0)), + function: RuntimeCall::Balances(pallet_balances::Call::transfer { + dest: bob().into(), + value: 0, + }), }) .collect::>(); @@ -249,7 +225,7 @@ fn block_weight_capacity_report() { 0, CheckedExtrinsic { signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(time * 1000)), + function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time * 1000 }), }, ); @@ -271,14 +247,7 @@ fn block_weight_capacity_report() { len / 1024 / 1024, ); - let r = executor_call:: _>( - &mut t, - "Core_execute_block", - &block.0, - true, - None, - ) - .0; + let r = executor_call(&mut t, "Core_execute_block", &block.0, true).0; println!(" || Result = {:?}", r); assert!(r.is_ok()); @@ -300,9 +269,9 @@ fn block_length_capacity_report() { use node_primitives::Index; // execution ext. - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); // setup ext. - let mut tt = new_test_ext(compact_code_unwrap(), false); + let mut tt = new_test_ext(compact_code_unwrap()); let factor = 256 * 1024; let mut time = 10; @@ -319,11 +288,13 @@ fn block_length_capacity_report() { vec![ CheckedExtrinsic { signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(time * 1000)), + function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { + now: time * 1000, + }), }, CheckedExtrinsic { signed: Some((charlie(), signed_extra(nonce, 0))), - function: Call::System(frame_system::Call::remark { + function: RuntimeCall::System(frame_system::Call::remark { remark: vec![0u8; (block_number * factor) as usize], }), }, @@ -339,14 +310,7 @@ fn block_length_capacity_report() { len / 1024 / 1024, ); - let r = executor_call:: _>( - &mut t, - "Core_execute_block", - &block.0, - true, - None, - ) - .0; + let r = executor_call(&mut t, "Core_execute_block", &block.0, true).0; println!(" || Result = {:?}", r); assert!(r.is_ok()); diff --git a/bin/node/executor/tests/submit_transaction.rs b/bin/node/executor/tests/submit_transaction.rs index 19ca8e5677c43..be43f3c78674f 100644 --- a/bin/node/executor/tests/submit_transaction.rs +++ b/bin/node/executor/tests/submit_transaction.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,9 +17,10 @@ use codec::Decode; use frame_system::offchain::{SendSignedTransaction, Signer, SubmitTransaction}; -use node_runtime::{Executive, Indices, Runtime, UncheckedExtrinsic}; +use kitchensink_runtime::{Executive, Indices, Runtime, UncheckedExtrinsic}; use sp_application_crypto::AppKey; use sp_core::offchain::{testing::TestTransactionPoolExt, TransactionPoolExt}; +use sp_keyring::sr25519::Keyring::Alice; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; use std::sync::Arc; @@ -28,12 +29,13 @@ use self::common::*; #[test] fn should_submit_unsigned_transaction() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); let (pool, state) = TestTransactionPoolExt::new(); t.register_extension(TransactionPoolExt::new(pool)); t.execute_with(|| { - let signature = Default::default(); + let signature = + pallet_im_online::sr25519::AuthoritySignature::try_from(vec![0; 64]).unwrap(); let heartbeat_data = pallet_im_online::Heartbeat { block_number: 1, network_state: Default::default(), @@ -56,7 +58,7 @@ const PHRASE: &str = "news slush supreme milk chapter athlete soap sausage put c #[test] fn should_submit_signed_transaction() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); let (pool, state) = TestTransactionPoolExt::new(); t.register_extension(TransactionPoolExt::new(pool)); @@ -85,7 +87,7 @@ fn should_submit_signed_transaction() { let results = Signer::::all_accounts().send_signed_transaction(|_| { pallet_balances::Call::transfer { - dest: Default::default(), + dest: Alice.to_account_id().into(), value: Default::default(), } }); @@ -99,7 +101,7 @@ fn should_submit_signed_transaction() { #[test] fn should_submit_signed_twice_from_the_same_account() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); let (pool, state) = TestTransactionPoolExt::new(); t.register_extension(TransactionPoolExt::new(pool)); @@ -122,7 +124,7 @@ fn should_submit_signed_twice_from_the_same_account() { let result = Signer::::any_account().send_signed_transaction(|_| { pallet_balances::Call::transfer { - dest: Default::default(), + dest: Alice.to_account_id().into(), value: Default::default(), } }); @@ -134,7 +136,7 @@ fn should_submit_signed_twice_from_the_same_account() { let result = Signer::::any_account().send_signed_transaction(|_| { pallet_balances::Call::transfer { - dest: Default::default(), + dest: Alice.to_account_id().into(), value: Default::default(), } }); @@ -146,7 +148,7 @@ fn should_submit_signed_twice_from_the_same_account() { let s = state.read(); fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce { let extra = tx.signature.unwrap().2; - extra.4 + extra.5 } let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap()); let nonce2 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[1]).unwrap()); @@ -156,7 +158,7 @@ fn should_submit_signed_twice_from_the_same_account() { #[test] fn should_submit_signed_twice_from_all_accounts() { - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); let (pool, state) = TestTransactionPoolExt::new(); t.register_extension(TransactionPoolExt::new(pool)); @@ -172,7 +174,7 @@ fn should_submit_signed_twice_from_all_accounts() { t.execute_with(|| { let results = Signer::::all_accounts() .send_signed_transaction(|_| { - pallet_balances::Call::transfer { dest: Default::default(), value: Default::default() } + pallet_balances::Call::transfer { dest: Alice.to_account_id().into(), value: Default::default() } }); let len = results.len(); @@ -183,7 +185,7 @@ fn should_submit_signed_twice_from_all_accounts() { // submit another one from the same account. The nonce should be incremented. let results = Signer::::all_accounts() .send_signed_transaction(|_| { - pallet_balances::Call::transfer { dest: Default::default(), value: Default::default() } + pallet_balances::Call::transfer { dest: Alice.to_account_id().into(), value: Default::default() } }); let len = results.len(); @@ -195,7 +197,7 @@ fn should_submit_signed_twice_from_all_accounts() { let s = state.read(); fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce { let extra = tx.signature.unwrap().2; - extra.4 + extra.5 } let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap()); let nonce2 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[1]).unwrap()); @@ -220,7 +222,7 @@ fn submitted_transaction_should_be_valid() { transaction_validity::{TransactionSource, TransactionTag}, }; - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); let (pool, state) = TestTransactionPoolExt::new(); t.register_extension(TransactionPoolExt::new(pool)); @@ -237,7 +239,7 @@ fn submitted_transaction_should_be_valid() { let results = Signer::::all_accounts().send_signed_transaction(|_| { pallet_balances::Call::transfer { - dest: Default::default(), + dest: Alice.to_account_id().into(), value: Default::default(), } }); @@ -249,7 +251,7 @@ fn submitted_transaction_should_be_valid() { // check that transaction is valid, but reset environment storage, // since CreateTransaction increments the nonce let tx0 = state.read().transactions[0].clone(); - let mut t = new_test_ext(compact_code_unwrap(), false); + let mut t = new_test_ext(compact_code_unwrap()); t.execute_with(|| { let source = TransactionSource::External; let extrinsic = UncheckedExtrinsic::decode(&mut &*tx0).unwrap(); diff --git a/bin/node/inspect/Cargo.toml b/bin/node/inspect/Cargo.toml index 1570e5dbf8e44..a7343b3ca827f 100644 --- a/bin/node/inspect/Cargo.toml +++ b/bin/node/inspect/Cargo.toml @@ -2,22 +2,24 @@ name = "node-inspect" version = "0.9.0-dev" authors = ["Parity Technologies "] -edition = "2018" +description = "Substrate node block inspection tool." +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } -derive_more = "0.99" +clap = { version = "4.0.9", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0" } +thiserror = "1.0" sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -structopt = "0.3.8" +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/bin/node/inspect/src/cli.rs b/bin/node/inspect/src/cli.rs index c054fedaf57c4..fa6344ac346e9 100644 --- a/bin/node/inspect/src/cli.rs +++ b/bin/node/inspect/src/cli.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,27 +19,25 @@ //! Structs to easily compose inspect sub-command for CLI. use sc_cli::{ImportParams, SharedParams}; -use std::fmt::Debug; -use structopt::StructOpt; /// The `inspect` command used to print decoded chain data. -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Parser)] pub struct InspectCmd { #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(subcommand)] pub command: InspectSubCmd, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: SharedParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub import_params: ImportParams, } /// A possible inspect sub-commands. -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Subcommand)] pub enum InspectSubCmd { /// Decode block with native version of runtime and print out the details. Block { @@ -48,7 +46,7 @@ pub enum InspectSubCmd { /// Can be either a block hash (no 0x prefix) or a number to retrieve existing block, /// or a 0x-prefixed bytes hex string, representing SCALE encoding of /// a block. - #[structopt(value_name = "HASH or NUMBER or BYTES")] + #[arg(value_name = "HASH or NUMBER or BYTES")] input: String, }, /// Decode extrinsic with native version of runtime and print out the details. @@ -58,7 +56,7 @@ pub enum InspectSubCmd { /// Can be either a block hash (no 0x prefix) or number and the index, in the form /// of `{block}:{index}` or a 0x-prefixed bytes hex string, /// representing SCALE encoding of an extrinsic. - #[structopt(value_name = "BLOCK:INDEX or BYTES")] + #[arg(value_name = "BLOCK:INDEX or BYTES")] input: String, }, } diff --git a/bin/node/inspect/src/command.rs b/bin/node/inspect/src/command.rs index 9bf69511689c4..ce164e0768fbc 100644 --- a/bin/node/inspect/src/command.rs +++ b/bin/node/inspect/src/command.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -41,6 +41,7 @@ impl InspectCmd { config.wasm_method, config.default_heap_pages, config.max_runtime_instances, + config.runtime_cache_size, ); let client = new_full_client::(&config, None, executor)?; diff --git a/bin/node/inspect/src/lib.rs b/bin/node/inspect/src/lib.rs index 30e7250ea2c6c..528dce14f46a5 100644 --- a/bin/node/inspect/src/lib.rs +++ b/bin/node/inspect/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. // -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // // This program is free software: you can redistribute it and/or modify @@ -78,26 +78,19 @@ impl PrettyPrinter for DebugPrinter { } /// Aggregated error for `Inspector` operations. -#[derive(Debug, derive_more::From, derive_more::Display)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// Could not decode Block or Extrinsic. - Codec(codec::Error), + #[error(transparent)] + Codec(#[from] codec::Error), /// Error accessing blockchain DB. - Blockchain(sp_blockchain::Error), + #[error(transparent)] + Blockchain(#[from] sp_blockchain::Error), /// Given block has not been found. + #[error("{0}")] NotFound(String), } -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match *self { - Self::Codec(ref e) => Some(e), - Self::Blockchain(ref e) => Some(e), - Self::NotFound(_) => None, - } - } -} - /// A helper trait to access block headers and bodies. pub trait ChainAccess: HeaderBackend + BlockBackend {} @@ -147,10 +140,11 @@ impl> Inspector BlockAddress::Bytes(bytes) => TBlock::decode(&mut &*bytes)?, BlockAddress::Number(number) => { let id = BlockId::number(number); + let hash = self.chain.expect_block_hash_from_id(&id)?; let not_found = format!("Could not find block {:?}", id); let body = self .chain - .block_body(&id)? + .block_body(hash)? .ok_or_else(|| Error::NotFound(not_found.clone()))?; let header = self.chain.header(id)?.ok_or_else(|| Error::NotFound(not_found.clone()))?; @@ -161,7 +155,7 @@ impl> Inspector let not_found = format!("Could not find block {:?}", id); let body = self .chain - .block_body(&id)? + .block_body(hash)? .ok_or_else(|| Error::NotFound(not_found.clone()))?; let header = self.chain.header(id)?.ok_or_else(|| Error::NotFound(not_found.clone()))?; @@ -261,7 +255,7 @@ impl FromStr for ExtrinsicAddres let index = it .next() - .ok_or_else(|| format!("Extrinsic index missing: example \"5:0\""))? + .ok_or("Extrinsic index missing: example \"5:0\"")? .parse() .map_err(|e| format!("Invalid index format: {}", e))?; @@ -303,7 +297,7 @@ mod tests { let b2 = ExtrinsicAddress::from_str("0 0"); let b3 = ExtrinsicAddress::from_str("0x0012345f"); - assert_eq!(e0, Err("Extrinsic index missing: example \"5:0\"".into())); + assert_eq!(e0, Ok(ExtrinsicAddress::Bytes(vec![0x12, 0x34]))); assert_eq!( b0, Ok(ExtrinsicAddress::Block( @@ -312,7 +306,7 @@ mod tests { )) ); assert_eq!(b1, Ok(ExtrinsicAddress::Block(BlockAddress::Number(1234), 0))); - assert_eq!(b2, Ok(ExtrinsicAddress::Block(BlockAddress::Number(0), 0))); + assert_eq!(b2, Ok(ExtrinsicAddress::Bytes(vec![0, 0]))); assert_eq!(b3, Ok(ExtrinsicAddress::Bytes(vec![0, 0x12, 0x34, 0x5f]))); } } diff --git a/bin/node/primitives/Cargo.toml b/bin/node/primitives/Cargo.toml index 12ec57e4d55b6..1aa0a8f0e27a3 100644 --- a/bin/node/primitives/Cargo.toml +++ b/bin/node/primitives/Cargo.toml @@ -2,30 +2,32 @@ name = "node-primitives" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +description = "Substrate node low-level primitives." +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system" } -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/application-crypto" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../../primitives/application-crypto" } +sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-system/std", + "scale-info/std", "sp-application-crypto/std", "sp-core/std", "sp-runtime/std", diff --git a/bin/node/primitives/src/lib.rs b/bin/node/primitives/src/lib.rs index dade598c704d2..feb9ee60d311b 100644 --- a/bin/node/primitives/src/lib.rs +++ b/bin/node/primitives/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,41 +57,10 @@ pub type Hash = sp_core::H256; pub type Timestamp = u64; /// Digest item type. -pub type DigestItem = generic::DigestItem; +pub type DigestItem = generic::DigestItem; /// Header type. pub type Header = generic::Header; /// Block type. pub type Block = generic::Block; /// Block ID. pub type BlockId = generic::BlockId; - -/// App-specific crypto used for reporting equivocation/misbehavior in BABE and -/// GRANDPA. Any rewards for misbehavior reporting will be paid out to this -/// account. -pub mod report { - use super::{Signature, Verify}; - use frame_system::offchain::AppCrypto; - use sp_core::crypto::{key_types, KeyTypeId}; - - /// Key type for the reporting module. Used for reporting BABE and GRANDPA - /// equivocations. - pub const KEY_TYPE: KeyTypeId = key_types::REPORTING; - - mod app { - use sp_application_crypto::{app_crypto, sr25519}; - app_crypto!(sr25519, super::KEY_TYPE); - } - - /// Identity of the equivocation/misbehavior reporter. - pub type ReporterId = app::Public; - - /// An `AppCrypto` type to allow submitting signed transactions using the reporting - /// application key as signer. - pub struct ReporterAppCrypto; - - impl AppCrypto<::Signer, Signature> for ReporterAppCrypto { - type RuntimeAppPublic = ReporterId; - type GenericSignature = sp_core::sr25519::Signature; - type GenericPublic = sp_core::sr25519::Public; - } -} diff --git a/bin/node/rpc-client/Cargo.toml b/bin/node/rpc-client/Cargo.toml deleted file mode 100644 index a5255769158a4..0000000000000 --- a/bin/node/rpc-client/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "node-rpc-client" -version = "2.0.0" -authors = ["Parity Technologies "] -edition = "2018" -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -futures = "0.3.16" -jsonrpc-core-client = { version = "18.0.0", default-features = false, features = [ - "http", -] } -node-primitives = { version = "2.0.0", path = "../primitives" } -sp-tracing = { version = "4.0.0-dev", path = "../../../primitives/tracing" } -sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } diff --git a/bin/node/rpc-client/src/main.rs b/bin/node/rpc-client/src/main.rs deleted file mode 100644 index 6d0b88799f54c..0000000000000 --- a/bin/node/rpc-client/src/main.rs +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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. - -#![warn(missing_docs)] - -//! Example substrate RPC client code. -//! -//! This module shows how you can write a Rust RPC client that connects to a running -//! substrate node and use statically typed RPC wrappers. - -use futures::{Future, TryFutureExt}; -use jsonrpc_core_client::{transports::http, RpcError}; -use node_primitives::Hash; -use sc_rpc::author::{hash::ExtrinsicOrHash, AuthorClient}; - -fn main() -> Result<(), RpcError> { - sp_tracing::try_init_simple(); - - futures::executor::block_on(async { - let uri = "http://localhost:9933"; - - http::connect(uri) - .and_then(|client: AuthorClient| remove_all_extrinsics(client)) - .await - }) -} - -/// Remove all pending extrinsics from the node. -/// -/// The example code takes `AuthorClient` and first: -/// 1. Calls the `pending_extrinsics` method to get all extrinsics in the pool. -/// 2. Then calls `remove_extrinsic` passing the obtained raw extrinsics. -/// -/// As the result of running the code the entire content of the transaction pool is going -/// to be removed and the extrinsics are going to be temporarily banned. -fn remove_all_extrinsics( - client: AuthorClient, -) -> impl Future> { - client - .pending_extrinsics() - .and_then(move |pending| { - client.remove_extrinsic( - pending.into_iter().map(|tx| ExtrinsicOrHash::Extrinsic(tx.into())).collect(), - ) - }) - .map_ok(|removed| { - println!("Removed extrinsics: {:?}", removed); - }) -} diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index 0cb606f79f086..f34922a287dfe 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -2,36 +2,39 @@ name = "node-rpc" version = "3.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +description = "Substrate node rpc methods." +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpc-core = "18.0.0" +jsonrpsee = { version = "0.16.2", features = ["server"] } node-primitives = { version = "2.0.0", path = "../primitives" } -pallet-contracts-rpc = { version = "4.0.0-dev", path = "../../../frame/contracts/rpc/" } -pallet-mmr-rpc = { version = "3.0.0", path = "../../../frame/merkle-mountain-range/rpc/" } pallet-transaction-payment-rpc = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/rpc/" } +mmr-rpc = { version = "4.0.0-dev", path = "../../../client/merkle-mountain-range/rpc/" } +sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sc-consensus-babe = { version = "0.10.0-dev", path = "../../../client/consensus/babe" } sc-consensus-babe-rpc = { version = "0.10.0-dev", path = "../../../client/consensus/babe/rpc" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../../../client/consensus/epochs" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } sc-finality-grandpa = { version = "0.10.0-dev", path = "../../../client/finality-grandpa" } sc-finality-grandpa-rpc = { version = "0.10.0-dev", path = "../../../client/finality-grandpa/rpc" } -sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } +sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } +sc-rpc-spec-v2 = { version = "0.10.0-dev", path = "../../../client/rpc-spec-v2" } sc-sync-state-rpc = { version = "0.10.0-dev", path = "../../../client/sync-state-rpc" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-keystore = { version = "0.10.0-dev", path = "../../../primitives/keystore" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } substrate-frame-rpc-system = { version = "4.0.0-dev", path = "../../../utils/frame/rpc/system" } +substrate-state-trie-migration-rpc = { version = "4.0.0-dev", path = "../../../utils/frame/rpc/state-trie-migration-rpc/" } diff --git a/bin/node/rpc/src/lib.rs b/bin/node/rpc/src/lib.rs index 2f7862d3d2644..0dc5ba4039b00 100644 --- a/bin/node/rpc/src/lib.rs +++ b/bin/node/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,18 +29,18 @@ //! be placed here or imported from corresponding FRAME RPC definitions. #![warn(missing_docs)] +#![warn(unused_crate_dependencies)] use std::sync::Arc; +use jsonrpsee::RpcModule; use node_primitives::{AccountId, Balance, Block, BlockNumber, Hash, Index}; use sc_client_api::AuxStore; -use sc_consensus_babe::{Config, Epoch}; -use sc_consensus_babe_rpc::BabeRpcHandler; +use sc_consensus_babe::{BabeConfiguration, Epoch}; use sc_consensus_epochs::SharedEpochChanges; use sc_finality_grandpa::{ FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState, }; -use sc_finality_grandpa_rpc::GrandpaRpcHandler; use sc_rpc::SubscriptionTaskExecutor; pub use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; @@ -51,22 +51,10 @@ use sp_consensus::SelectChain; use sp_consensus_babe::BabeApi; use sp_keystore::SyncCryptoStorePtr; -/// Light client extra dependencies. -pub struct LightDeps { - /// The client instance to use. - pub client: Arc, - /// Transaction pool instance. - pub pool: Arc

, - /// Remote access to the blockchain (async). - pub remote_blockchain: Arc>, - /// Fetcher instance. - pub fetcher: Arc, -} - /// Extra dependencies for BABE. pub struct BabeDeps { /// BABE protocol config. - pub babe_config: Config, + pub babe_config: BabeConfiguration, /// BABE pending epoch changes. pub shared_epoch_changes: SharedEpochChanges, /// The keystore that manages the keys of the node. @@ -105,15 +93,14 @@ pub struct FullDeps { pub grandpa: GrandpaDeps, } -/// A IO handler that uses all Full RPC extensions. -pub type IoHandler = jsonrpc_core::IoHandler; - /// Instantiate all Full RPC extensions. pub fn create_full( deps: FullDeps, -) -> Result, Box> + backend: Arc, +) -> Result, Box> where C: ProvideRuntimeApi + + sc_client_api::BlockBackend + HeaderBackend + AuxStore + HeaderMetadata @@ -121,8 +108,7 @@ where + Send + 'static, C::Api: substrate_frame_rpc_system::AccountNonceApi, - C::Api: pallet_contracts_rpc::ContractsRuntimeApi, - C::Api: pallet_mmr_rpc::MmrRuntimeApi::Hash>, + C::Api: mmr_rpc::MmrRuntimeApi::Hash, BlockNumber>, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, C::Api: BabeApi, C::Api: BlockBuilder, @@ -131,12 +117,17 @@ where B: sc_client_api::Backend + Send + Sync + 'static, B::State: sc_client_api::backend::StateBackend>, { - use pallet_contracts_rpc::{Contracts, ContractsApi}; - use pallet_mmr_rpc::{Mmr, MmrApi}; - use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; - use substrate_frame_rpc_system::{FullSystem, SystemApi}; - - let mut io = jsonrpc_core::IoHandler::default(); + use mmr_rpc::{Mmr, MmrApiServer}; + use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; + use sc_consensus_babe_rpc::{Babe, BabeApiServer}; + use sc_finality_grandpa_rpc::{Grandpa, GrandpaApiServer}; + use sc_rpc::dev::{Dev, DevApiServer}; + use sc_rpc_spec_v2::chain_spec::{ChainSpec, ChainSpecApiServer}; + use sc_sync_state_rpc::{SyncState, SyncStateApiServer}; + use substrate_frame_rpc_system::{System, SystemApiServer}; + use substrate_state_trie_migration_rpc::{StateMigration, StateMigrationApiServer}; + + let mut io = RpcModule::new(()); let FullDeps { client, pool, select_chain, chain_spec, deny_unsafe, babe, grandpa } = deps; let BabeDeps { keystore, babe_config, shared_epoch_changes } = babe; @@ -148,61 +139,46 @@ where finality_provider, } = grandpa; - io.extend_with(SystemApi::to_delegate(FullSystem::new(client.clone(), pool, deny_unsafe))); + let chain_name = chain_spec.name().to_string(); + let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); + let properties = chain_spec.properties(); + io.merge(ChainSpec::new(chain_name, genesis_hash, properties).into_rpc())?; + + io.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; // Making synchronous calls in light client freezes the browser currently, // more context: https://github.com/paritytech/substrate/pull/3480 // These RPCs should use an asynchronous caller instead. - io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone()))); - io.extend_with(MmrApi::to_delegate(Mmr::new(client.clone()))); - io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(client.clone()))); - io.extend_with(sc_consensus_babe_rpc::BabeApi::to_delegate(BabeRpcHandler::new( - client.clone(), - shared_epoch_changes.clone(), - keystore, - babe_config, - select_chain, - deny_unsafe, - ))); - io.extend_with(sc_finality_grandpa_rpc::GrandpaApi::to_delegate(GrandpaRpcHandler::new( - shared_authority_set.clone(), - shared_voter_state, - justification_stream, - subscription_executor, - finality_provider, - ))); - - io.extend_with(sc_sync_state_rpc::SyncStateRpcApi::to_delegate( - sc_sync_state_rpc::SyncStateRpcHandler::new( - chain_spec, - client, - shared_authority_set, - shared_epoch_changes, + io.merge(Mmr::new(client.clone()).into_rpc())?; + io.merge(TransactionPayment::new(client.clone()).into_rpc())?; + io.merge( + Babe::new( + client.clone(), + shared_epoch_changes.clone(), + keystore, + babe_config, + select_chain, deny_unsafe, - )?, - )); + ) + .into_rpc(), + )?; + io.merge( + Grandpa::new( + subscription_executor, + shared_authority_set.clone(), + shared_voter_state, + justification_stream, + finality_provider, + ) + .into_rpc(), + )?; + + io.merge( + SyncState::new(chain_spec, client.clone(), shared_authority_set, shared_epoch_changes)? + .into_rpc(), + )?; + + io.merge(StateMigration::new(client.clone(), backend, deny_unsafe).into_rpc())?; + io.merge(Dev::new(client, deny_unsafe).into_rpc())?; Ok(io) } - -/// Instantiate all Light RPC extensions. -pub fn create_light(deps: LightDeps) -> jsonrpc_core::IoHandler -where - C: sp_blockchain::HeaderBackend, - C: Send + Sync + 'static, - F: sc_client_api::light::Fetcher + 'static, - P: TransactionPool + 'static, - M: jsonrpc_core::Metadata + Default, -{ - use substrate_frame_rpc_system::{LightSystem, SystemApi}; - - let LightDeps { client, pool, remote_blockchain, fetcher } = deps; - let mut io = jsonrpc_core::IoHandler::default(); - io.extend_with(SystemApi::::to_delegate(LightSystem::new( - client, - remote_blockchain, - fetcher, - pool, - ))); - - io -} diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index dafd9db8bab96..477545c9ac332 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -1,12 +1,14 @@ [package] -name = "node-runtime" +name = "kitchensink-runtime" version = "3.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +description = "Substrate node kitchensink runtime." +edition = "2021" build = "build.rs" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -14,14 +16,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # third-party dependencies -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", "max-encoded-len", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } static_assertions = "1.1.0" -hex-literal = { version = "0.3.1", optional = true } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.17", default-features = false } # primitives sp-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/authority-discovery" } @@ -30,17 +31,15 @@ sp-block-builder = { path = "../../../primitives/block-builder", default-feature sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/inherents" } node-primitives = { version = "2.0.0", default-features = false, path = "../primitives" } sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/offchain" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } +sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } -sp-keyring = { version = "4.0.0-dev", optional = true, path = "../../../primitives/keyring" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/transaction-pool" } -sp-version = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/version" } -sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/io" } +sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } +sp-io = { version = "7.0.0", default-features = false, path = "../../../primitives/io" } # frame dependencies frame-executive = { version = "4.0.0-dev", default-features = false, path = "../../../frame/executive" } @@ -51,39 +50,52 @@ frame-system-benchmarking = { version = "4.0.0-dev", default-features = false, p frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-support" } frame-system-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system/rpc/runtime-api/" } frame-try-runtime = { version = "0.10.0-dev", default-features = false, path = "../../../frame/try-runtime", optional = true } +pallet-alliance = { version = "4.0.0-dev", default-features = false, path = "../../../frame/alliance" } pallet-assets = { version = "4.0.0-dev", default-features = false, path = "../../../frame/assets" } pallet-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/authority-discovery" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../../../frame/authorship" } pallet-babe = { version = "4.0.0-dev", default-features = false, path = "../../../frame/babe" } +pallet-bags-list = { version = "4.0.0-dev", default-features = false, path = "../../../frame/bags-list" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../../frame/balances" } pallet-bounties = { version = "4.0.0-dev", default-features = false, path = "../../../frame/bounties" } +pallet-child-bounties = { version = "4.0.0-dev", default-features = false, path = "../../../frame/child-bounties" } pallet-collective = { version = "4.0.0-dev", default-features = false, path = "../../../frame/collective" } pallet-contracts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/contracts" } -pallet-contracts-primitives = { version = "4.0.0-dev", default-features = false, path = "../../../frame/contracts/common/" } -pallet-contracts-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/contracts/rpc/runtime-api/" } +pallet-contracts-primitives = { version = "7.0.0", default-features = false, path = "../../../frame/contracts/primitives/" } +pallet-conviction-voting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/conviction-voting" } pallet-democracy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/democracy" } pallet-election-provider-multi-phase = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-multi-phase" } +pallet-election-provider-support-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-support/benchmarking", optional = true } pallet-elections-phragmen = { version = "5.0.0-dev", default-features = false, path = "../../../frame/elections-phragmen" } -pallet-gilt = { version = "4.0.0-dev", default-features = false, path = "../../../frame/gilt" } +pallet-fast-unstake = { version = "4.0.0-dev", default-features = false, path = "../../../frame/fast-unstake" } +pallet-nis = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nis" } pallet-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../../frame/grandpa" } pallet-im-online = { version = "4.0.0-dev", default-features = false, path = "../../../frame/im-online" } pallet-indices = { version = "4.0.0-dev", default-features = false, path = "../../../frame/indices" } pallet-identity = { version = "4.0.0-dev", default-features = false, path = "../../../frame/identity" } pallet-lottery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/lottery" } pallet-membership = { version = "4.0.0-dev", default-features = false, path = "../../../frame/membership" } +pallet-message-queue = { version = "7.0.0-dev", default-features = false, path = "../../../frame/message-queue" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } +pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} +pallet-nomination-pools-benchmarking = { version = "1.0.0", default-features = false, optional = true, path = "../../../frame/nomination-pools/benchmarking" } +pallet-nomination-pools-runtime-api = { version = "1.0.0-dev", default-features = false, path = "../../../frame/nomination-pools/runtime-api" } pallet-offences = { version = "4.0.0-dev", default-features = false, path = "../../../frame/offences" } pallet-offences-benchmarking = { version = "4.0.0-dev", path = "../../../frame/offences/benchmarking", default-features = false, optional = true } +pallet-preimage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/preimage" } pallet-proxy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/proxy" } pallet-randomness-collective-flip = { version = "4.0.0-dev", default-features = false, path = "../../../frame/randomness-collective-flip" } +pallet-ranked-collective = { version = "4.0.0-dev", default-features = false, path = "../../../frame/ranked-collective" } pallet-recovery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/recovery" } -pallet-session = { version = "4.0.0-dev", features = [ - "historical", -], path = "../../../frame/session", default-features = false } +pallet-referenda = { version = "4.0.0-dev", default-features = false, path = "../../../frame/referenda" } +pallet-remark = { version = "4.0.0-dev", default-features = false, path = "../../../frame/remark" } +pallet-root-testing = { version = "1.0.0-dev", default-features = false, path = "../../../frame/root-testing" } +pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = "../../../frame/session", default-features = false } pallet-session-benchmarking = { version = "4.0.0-dev", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/reward-curve" } +pallet-state-trie-migration = { version = "4.0.0-dev", default-features = false, path = "../../../frame/state-trie-migration" } pallet-scheduler = { version = "4.0.0-dev", default-features = false, path = "../../../frame/scheduler" } pallet-society = { version = "4.0.0-dev", default-features = false, path = "../../../frame/society" } pallet-sudo = { version = "4.0.0-dev", default-features = false, path = "../../../frame/sudo" } @@ -93,9 +105,11 @@ pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../ pallet-utility = { version = "4.0.0-dev", default-features = false, path = "../../../frame/utility" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/rpc/runtime-api/" } +pallet-asset-tx-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/asset-tx-payment/" } pallet-transaction-storage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-storage" } pallet-uniques = { version = "4.0.0-dev", default-features = false, path = "../../../frame/uniques" } pallet-vesting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/vesting" } +pallet-whitelist = { version = "4.0.0-dev", default-features = false, path = "../../../frame/whitelist" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } @@ -104,12 +118,19 @@ substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-bu default = ["std"] with-tracing = ["frame-executive/with-tracing"] std = [ + "pallet-whitelist/std", + "pallet-offences-benchmarking?/std", + "pallet-election-provider-support-benchmarking?/std", + "pallet-asset-tx-payment/std", + "frame-system-benchmarking?/std", + "frame-election-provider-support/std", "sp-authority-discovery/std", "pallet-assets/std", "pallet-authority-discovery/std", "pallet-authorship/std", "sp-consensus-babe/std", "pallet-babe/std", + "pallet-bags-list/std", "pallet-balances/std", "pallet-bounties/std", "sp-block-builder/std", @@ -118,38 +139,45 @@ std = [ "pallet-collective/std", "pallet-contracts/std", "pallet-contracts-primitives/std", - "pallet-contracts-rpc-runtime-api/std", + "pallet-conviction-voting/std", "pallet-democracy/std", "pallet-elections-phragmen/std", + "pallet-fast-unstake/std", "frame-executive/std", - "pallet-gilt/std", + "pallet-nis/std", "pallet-grandpa/std", "pallet-im-online/std", "pallet-indices/std", "sp-inherents/std", "pallet-lottery/std", "pallet-membership/std", + "pallet-message-queue/std", "pallet-mmr/std", "pallet-multisig/std", + "pallet-nomination-pools/std", + "pallet-nomination-pools-runtime-api/std", + "pallet-nomination-pools-benchmarking?/std", "pallet-identity/std", "pallet-scheduler/std", "node-primitives/std", "sp-offchain/std", "pallet-offences/std", + "pallet-preimage/std", "pallet-proxy/std", "sp-core/std", "pallet-randomness-collective-flip/std", "sp-std/std", "pallet-session/std", + "pallet-session-benchmarking?/std", "sp-api/std", "sp-runtime/std", "sp-staking/std", "pallet-staking/std", - "sp-keyring", + "pallet-state-trie-migration/std", "sp-session/std", "pallet-sudo/std", "frame-support/std", - "frame-benchmarking/std", + "frame-benchmarking?/std", "frame-system-rpc-runtime-api/std", "frame-system/std", "pallet-election-provider-multi-phase/std", @@ -163,41 +191,62 @@ std = [ "pallet-utility/std", "sp-version/std", "pallet-society/std", + "pallet-ranked-collective/std", + "pallet-referenda/std", + "pallet-remark/std", + "pallet-root-testing/std", "pallet-recovery/std", "pallet-uniques/std", "pallet-vesting/std", "log/std", - "frame-try-runtime/std", - "sp-npos-elections/std", - "sp-io/std" + "frame-try-runtime?/std", + "sp-io/std", + "pallet-child-bounties/std", + "pallet-alliance/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-election-provider-multi-phase/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "pallet-alliance/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-babe/runtime-benchmarks", + "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-bounties/runtime-benchmarks", + "pallet-child-bounties/runtime-benchmarks", "pallet-collective/runtime-benchmarks", "pallet-contracts/runtime-benchmarks", + "pallet-conviction-voting/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", + "pallet-election-provider-multi-phase/runtime-benchmarks", + "pallet-election-provider-support-benchmarking/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", - "pallet-gilt/runtime-benchmarks", + "pallet-fast-unstake/runtime-benchmarks", + "pallet-nis/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-identity/runtime-benchmarks", "pallet-im-online/runtime-benchmarks", "pallet-indices/runtime-benchmarks", "pallet-lottery/runtime-benchmarks", "pallet-membership/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", + "pallet-nomination-pools-benchmarking/runtime-benchmarks", + "pallet-offences-benchmarking/runtime-benchmarks", + "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", + "pallet-ranked-collective/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", + "pallet-recovery/runtime-benchmarks", + "pallet-remark/runtime-benchmarks", + "pallet-session-benchmarking/runtime-benchmarks", "pallet-society/runtime-benchmarks", "pallet-staking/runtime-benchmarks", + "pallet-state-trie-migration/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-tips/runtime-benchmarks", "pallet-transaction-storage/runtime-benchmarks", @@ -205,52 +254,64 @@ runtime-benchmarks = [ "pallet-utility/runtime-benchmarks", "pallet-uniques/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", - "pallet-offences-benchmarking", - "pallet-session-benchmarking", - "frame-system-benchmarking", - "hex-literal", + "pallet-whitelist/runtime-benchmarks", + "frame-system-benchmarking/runtime-benchmarks", ] try-runtime = [ + "frame-try-runtime/try-runtime", "frame-executive/try-runtime", - "frame-try-runtime", "frame-system/try-runtime", + "frame-support/try-runtime", + "pallet-alliance/try-runtime", "pallet-assets/try-runtime", "pallet-authority-discovery/try-runtime", "pallet-authorship/try-runtime", "pallet-babe/try-runtime", + "pallet-bags-list/try-runtime", "pallet-balances/try-runtime", "pallet-bounties/try-runtime", + "pallet-child-bounties/try-runtime", "pallet-collective/try-runtime", "pallet-contracts/try-runtime", + "pallet-conviction-voting/try-runtime", "pallet-democracy/try-runtime", + "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", + "pallet-fast-unstake/try-runtime", + "pallet-nis/try-runtime", "pallet-grandpa/try-runtime", "pallet-im-online/try-runtime", "pallet-indices/try-runtime", + "pallet-identity/try-runtime", "pallet-lottery/try-runtime", "pallet-membership/try-runtime", + "pallet-message-queue/try-runtime", "pallet-mmr/try-runtime", "pallet-multisig/try-runtime", - "pallet-identity/try-runtime", - "pallet-scheduler/try-runtime", + "pallet-nomination-pools/try-runtime", "pallet-offences/try-runtime", + "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", "pallet-randomness-collective-flip/try-runtime", + "pallet-ranked-collective/try-runtime", + "pallet-recovery/try-runtime", + "pallet-referenda/try-runtime", + "pallet-remark/try-runtime", + "pallet-root-testing/try-runtime", "pallet-session/try-runtime", "pallet-staking/try-runtime", + "pallet-state-trie-migration/try-runtime", + "pallet-scheduler/try-runtime", + "pallet-society/try-runtime", "pallet-sudo/try-runtime", - "pallet-election-provider-multi-phase/try-runtime", "pallet-timestamp/try-runtime", "pallet-tips/try-runtime", - "pallet-transaction-payment/try-runtime", "pallet-treasury/try-runtime", "pallet-utility/try-runtime", - "pallet-society/try-runtime", - "pallet-recovery/try-runtime", + "pallet-transaction-payment/try-runtime", + "pallet-asset-tx-payment/try-runtime", + "pallet-transaction-storage/try-runtime", "pallet-uniques/try-runtime", "pallet-vesting/try-runtime", - "pallet-gilt/try-runtime", + "pallet-whitelist/try-runtime", ] -# Make contract callable functions marked as __unstable__ available. Do not enable -# on live chains as those are subject to change. -contracts-unstable-interface = ["pallet-contracts/unstable-interface"] diff --git a/bin/node/runtime/build.rs b/bin/node/runtime/build.rs index a1c4b2d892cfe..b773ed9cf6fb7 100644 --- a/bin/node/runtime/build.rs +++ b/bin/node/runtime/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/bin/node/runtime/src/constants.rs b/bin/node/runtime/src/constants.rs index 7533025a70b00..23fb13cfb0492 100644 --- a/bin/node/runtime/src/constants.rs +++ b/bin/node/runtime/src/constants.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index e315a45e698ce..b3f58ea5d24ab 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,97 @@ //! Some configurable implementations as associated type for the substrate runtime. -use crate::{Authorship, Balances, NegativeImbalance}; -use frame_support::traits::{Currency, OnUnbalanced}; +use crate::{ + AccountId, AllianceMotion, Assets, Authorship, Balances, Hash, NegativeImbalance, Runtime, + RuntimeCall, +}; +use frame_support::{ + pallet_prelude::*, + traits::{ + fungibles::{Balanced, CreditOf}, + Currency, OnUnbalanced, + }, +}; +use pallet_alliance::{IdentityVerifier, ProposalIndex, ProposalProvider}; +use pallet_asset_tx_payment::HandleCredit; +use sp_std::prelude::*; pub struct Author; impl OnUnbalanced for Author { fn on_nonzero_unbalanced(amount: NegativeImbalance) { - Balances::resolve_creating(&Authorship::author(), amount); + if let Some(author) = Authorship::author() { + Balances::resolve_creating(&author, amount); + } + } +} + +/// A `HandleCredit` implementation that naively transfers the fees to the block author. +/// Will drop and burn the assets in case the transfer fails. +pub struct CreditToBlockAuthor; +impl HandleCredit for CreditToBlockAuthor { + fn handle_credit(credit: CreditOf) { + if let Some(author) = pallet_authorship::Pallet::::author() { + // Drop the result which will trigger the `OnDrop` of the imbalance in case of error. + let _ = Assets::resolve(&author, credit); + } + } +} + +pub struct AllianceIdentityVerifier; +impl IdentityVerifier for AllianceIdentityVerifier { + fn has_identity(who: &AccountId, fields: u64) -> bool { + crate::Identity::has_identity(who, fields) + } + + fn has_good_judgement(who: &AccountId) -> bool { + use pallet_identity::Judgement; + if let Some(judgements) = + crate::Identity::identity(who).map(|registration| registration.judgements) + { + judgements + .iter() + .any(|(_, j)| matches!(j, Judgement::KnownGood | Judgement::Reasonable)) + } else { + false + } + } + + fn super_account_id(who: &AccountId) -> Option { + crate::Identity::super_of(who).map(|parent| parent.0) + } +} + +pub struct AllianceProposalProvider; +impl ProposalProvider for AllianceProposalProvider { + fn propose_proposal( + who: AccountId, + threshold: u32, + proposal: Box, + length_bound: u32, + ) -> Result<(u32, u32), DispatchError> { + AllianceMotion::do_propose_proposed(who, threshold, proposal, length_bound) + } + + fn vote_proposal( + who: AccountId, + proposal: Hash, + index: ProposalIndex, + approve: bool, + ) -> Result { + AllianceMotion::do_vote(who, proposal, index, approve) + } + + fn close_proposal( + proposal_hash: Hash, + proposal_index: ProposalIndex, + proposal_weight_bound: Weight, + length_bound: u32, + ) -> DispatchResultWithPostInfo { + AllianceMotion::do_close(proposal_hash, proposal_index, proposal_weight_bound, length_bound) + } + + fn proposal_of(proposal_hash: Hash) -> Option { + AllianceMotion::proposal_of(proposal_hash) } } @@ -38,10 +122,13 @@ mod multiplier_tests { use crate::{ constants::{currency::*, time::*}, - AdjustmentVariable, MinimumMultiplier, Runtime, RuntimeBlockWeights as BlockWeights, - System, TargetBlockFullness, TransactionPayment, + AdjustmentVariable, MaximumMultiplier, MinimumMultiplier, Runtime, + RuntimeBlockWeights as BlockWeights, System, TargetBlockFullness, TransactionPayment, + }; + use frame_support::{ + dispatch::DispatchClass, + weights::{Weight, WeightToFee}, }; - use frame_support::weights::{DispatchClass, Weight, WeightToFeePolynomial}; fn max_normal() -> Weight { BlockWeights::get() @@ -65,6 +152,7 @@ mod multiplier_tests { TargetBlockFullness, AdjustmentVariable, MinimumMultiplier, + MaximumMultiplier, >::convert(fm) } @@ -76,13 +164,13 @@ mod multiplier_tests { let previous_float = previous_float.max(min_multiplier().into_inner() as f64 / accuracy); // maximum tx weight - let m = max_normal() as f64; + let m = max_normal().ref_time() as f64; // block weight always truncated to max weight - let block_weight = (block_weight as f64).min(m); + let block_weight = (block_weight.ref_time() as f64).min(m); let v: f64 = AdjustmentVariable::get().to_float(); // Ideal saturation in terms of weight - let ss = target() as f64; + let ss = target().ref_time() as f64; // Current saturation in terms of weight let s = block_weight; @@ -110,12 +198,12 @@ mod multiplier_tests { fn truth_value_update_poc_works() { let fm = Multiplier::saturating_from_rational(1, 2); let test_set = vec![ - (0, fm.clone()), - (100, fm.clone()), - (1000, fm.clone()), - (target(), fm.clone()), - (max_normal() / 2, fm.clone()), - (max_normal(), fm.clone()), + (Weight::zero(), fm), + (Weight::from_ref_time(100), fm), + (Weight::from_ref_time(1000), fm), + (target(), fm), + (max_normal() / 2, fm), + (max_normal(), fm), ]; test_set.into_iter().for_each(|(w, fm)| { run_with_system_weight(w, || { @@ -133,7 +221,7 @@ mod multiplier_tests { fn multiplier_can_grow_from_zero() { // if the min is too small, then this will not change, and we are doomed forever. // the weight is 1/100th bigger than target. - run_with_system_weight(target() * 101 / 100, || { + run_with_system_weight(target().set_ref_time(target().ref_time() * 101 / 100), || { let next = runtime_multiplier_update(min_multiplier()); assert!(next > min_multiplier(), "{:?} !>= {:?}", next, min_multiplier()); }) @@ -142,7 +230,7 @@ mod multiplier_tests { #[test] fn multiplier_cannot_go_below_limit() { // will not go any further below even if block is empty. - run_with_system_weight(0, || { + run_with_system_weight(Weight::zero(), || { let next = runtime_multiplier_update(min_multiplier()); assert_eq!(next, min_multiplier()); }) @@ -160,7 +248,7 @@ mod multiplier_tests { // 1 < 0.00001 * k * 0.1875 // 10^9 / 1875 < k // k > 533_333 ~ 18,5 days. - run_with_system_weight(0, || { + run_with_system_weight(Weight::zero(), || { // start from 1, the default. let mut fm = Multiplier::one(); let mut iterations: u64 = 0; @@ -196,7 +284,8 @@ mod multiplier_tests { // `cargo test congested_chain_simulation -- --nocapture` to get some insight. // almost full. The entire quota of normal transactions is taken. - let block_weight = BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap() - 100; + let block_weight = BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap() - + Weight::from_ref_time(100); // Default substrate weight. let tx_weight = frame_support::weights::constants::ExtrinsicBaseWeight::get(); @@ -216,7 +305,9 @@ mod multiplier_tests { fm = next; iterations += 1; let fee = - ::WeightToFee::calc(&tx_weight); + ::WeightToFee::weight_to_fee( + &tx_weight, + ); let adjusted_fee = fm.saturating_mul_acc_int(fee); println!( "iteration {}, new fm = {:?}. Fee at this point is: {} units / {} millicents, \ @@ -318,27 +409,27 @@ mod multiplier_tests { #[test] fn weight_to_fee_should_not_overflow_on_large_weights() { - let kb = 1024 as Weight; - let mb = kb * kb; + let kb = Weight::from_ref_time(1024); + let mb = 1024u64 * kb; let max_fm = Multiplier::saturating_from_integer(i128::MAX); // check that for all values it can compute, correctly. vec![ - 0, - 1, - 10, - 1000, + Weight::zero(), + Weight::from_ref_time(1), + Weight::from_ref_time(10), + Weight::from_ref_time(1000), kb, - 10 * kb, - 100 * kb, + 10u64 * kb, + 100u64 * kb, mb, - 10 * mb, - 2147483647, - 4294967295, + 10u64 * mb, + Weight::from_ref_time(2147483647), + Weight::from_ref_time(4294967295), BlockWeights::get().max_block / 2, BlockWeights::get().max_block, - Weight::max_value() / 2, - Weight::max_value(), + Weight::MAX / 2, + Weight::MAX, ] .into_iter() .for_each(|i| { @@ -351,7 +442,7 @@ mod multiplier_tests { // Some values that are all above the target and will cause an increase. let t = target(); - vec![t + 100, t * 2, t * 4].into_iter().for_each(|i| { + vec![t + Weight::from_ref_time(100), t * 2, t * 4].into_iter().for_each(|i| { run_with_system_weight(i, || { let fm = runtime_multiplier_update(max_fm); // won't grow. The convert saturates everything. diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 7c6475bd18d6a..00d2a54d1e774 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,54 +19,61 @@ //! The Substrate runtime. This can be compiled with `#[no_std]`, ready for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. -#![recursion_limit = "256"] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 512. +#![recursion_limit = "512"] use codec::{Decode, Encode, MaxEncodedLen}; +use frame_election_provider_support::{ + onchain, BalancingConfig, ElectionDataProvider, SequentialPhragmen, VoteWeight, +}; use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, + dispatch::DispatchClass, + pallet_prelude::Get, + parameter_types, traits::{ - Currency, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, - Nothing, OnUnbalanced, U128CurrencyToVote, + fungible::ItemOf, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, + Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, + KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, + WithdrawReasons, }, weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, - DispatchClass, IdentityFee, Weight, + constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, + }, + ConstantMultiplier, IdentityFee, Weight, }, PalletId, RuntimeDebug, }; use frame_system::{ limits::{BlockLength, BlockWeights}, - EnsureOneOf, EnsureRoot, + EnsureRoot, EnsureRootWithSuccess, EnsureSigned, EnsureWithSuccess, }; pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment}; -use pallet_contracts::weights::WeightInfo; +use pallet_election_provider_multi_phase::SolutionAccuracyOf; use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; -use pallet_session::historical as pallet_session_historical; +use pallet_nis::WithMaximumOf; +use pallet_session::historical::{self as pallet_session_historical}; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use sp_api::impl_runtime_apis; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; -use sp_core::{ - crypto::KeyTypeId, - u32_trait::{_1, _2, _3, _4, _5}, - OpaqueMetadata, -}; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_inherents::{CheckInherentsResult, InherentData}; use sp_runtime::{ create_runtime_str, curve::PiecewiseLinear, generic, impl_opaque_keys, traits::{ - self, BlakeTwo256, Block as BlockT, ConvertInto, NumberFor, OpaqueKeys, + self, BlakeTwo256, Block as BlockT, Bounded, ConvertInto, NumberFor, OpaqueKeys, SaturatedConversion, StaticLookup, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, FixedPointNumber, Perbill, Percent, Permill, Perquintill, + ApplyExtrinsicResult, FixedPointNumber, FixedU128, Perbill, Percent, Permill, Perquintill, }; use sp_std::prelude::*; #[cfg(any(feature = "std", test))] @@ -81,17 +88,24 @@ pub use pallet_balances::Call as BalancesCall; #[cfg(any(feature = "std", test))] pub use pallet_staking::StakerStatus; #[cfg(any(feature = "std", test))] +pub use pallet_sudo::Call as SudoCall; +#[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; -use impls::Author; +#[cfg(not(feature = "runtime-benchmarks"))] +use impls::AllianceIdentityVerifier; +use impls::{AllianceProposalProvider, Author, CreditToBlockAuthor}; /// Constant values used within the runtime. pub mod constants; use constants::{currency::*, time::*}; use sp_runtime::generic::Era; +/// Generated voter bag information. +mod voter_bags; + // Make the WASM binary available. #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); @@ -116,10 +130,11 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 267, - impl_version: 1, + spec_version: 268, + impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, + state_version: 1, }; /// The BABE epoch configuration at genesis. @@ -159,8 +174,9 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); /// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used /// by Operational extrinsics. const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); -/// We allow for 2 seconds of compute with a 6 second average block time. -const MAXIMUM_BLOCK_WEIGHT: Weight = 2 * WEIGHT_PER_SECOND; +/// We allow for 2 seconds of compute with a 6 second average block time, with maximum proof size. +const MAXIMUM_BLOCK_WEIGHT: Weight = + Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), u64::MAX); parameter_types! { pub const BlockHashCount: BlockNumber = 2400; @@ -185,7 +201,6 @@ parameter_types! { }) .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) .build_or_panic(); - pub const SS58Prefix: u16 = 42; } const_assert!(NORMAL_DISPATCH_RATIO.deconstruct() >= AVERAGE_ON_INITIALIZE_RATIO.deconstruct()); @@ -195,8 +210,8 @@ impl frame_system::Config for Runtime { type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; type DbWeight = RocksDbWeight; - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = Index; type BlockNumber = BlockNumber; type Hash = Hash; @@ -204,7 +219,7 @@ impl frame_system::Config for Runtime { type AccountId = AccountId; type Lookup = Indices; type Header = generic::Header; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; type Version = Version; type PalletInfo = PalletInfo; @@ -212,15 +227,17 @@ impl frame_system::Config for Runtime { type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = frame_system::weights::SubstrateWeight; - type SS58Prefix = SS58Prefix; + type SS58Prefix = ConstU16<42>; type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet_randomness_collective_flip::Config for Runtime {} impl pallet_utility::Config for Runtime { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; type WeightInfo = pallet_utility::weights::SubstrateWeight; } @@ -229,16 +246,15 @@ parameter_types! { pub const DepositBase: Balance = deposit(1, 88); // Additional storage item size of 32 bytes. pub const DepositFactor: Balance = deposit(0, 32); - pub const MaxSignatories: u16 = 100; } impl pallet_multisig::Config for Runtime { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type Currency = Balances; type DepositBase = DepositBase; type DepositFactor = DepositFactor; - type MaxSignatories = MaxSignatories; + type MaxSignatories = ConstU32<100>; type WeightInfo = pallet_multisig::weights::SubstrateWeight; } @@ -247,10 +263,8 @@ parameter_types! { pub const ProxyDepositBase: Balance = deposit(1, 8); // Additional storage item size of 33 bytes. pub const ProxyDepositFactor: Balance = deposit(0, 33); - pub const MaxProxies: u16 = 32; pub const AnnouncementDepositBase: Balance = deposit(1, 8); pub const AnnouncementDepositFactor: Balance = deposit(0, 66); - pub const MaxPending: u16 = 32; } /// The type used to represent the kinds of proxying allowed. @@ -278,25 +292,28 @@ impl Default for ProxyType { Self::Any } } -impl InstanceFilter for ProxyType { - fn filter(&self, c: &Call) -> bool { +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { match self { ProxyType::Any => true, ProxyType::NonTransfer => !matches!( c, - Call::Balances(..) | - Call::Assets(..) | Call::Uniques(..) | - Call::Vesting(pallet_vesting::Call::vested_transfer { .. }) | - Call::Indices(pallet_indices::Call::transfer { .. }) + RuntimeCall::Balances(..) | + RuntimeCall::Assets(..) | + RuntimeCall::Uniques(..) | + RuntimeCall::Vesting(pallet_vesting::Call::vested_transfer { .. }) | + RuntimeCall::Indices(pallet_indices::Call::transfer { .. }) ), ProxyType::Governance => matches!( c, - Call::Democracy(..) | - Call::Council(..) | Call::Society(..) | - Call::TechnicalCommittee(..) | - Call::Elections(..) | Call::Treasury(..) + RuntimeCall::Democracy(..) | + RuntimeCall::Council(..) | + RuntimeCall::Society(..) | + RuntimeCall::TechnicalCommittee(..) | + RuntimeCall::Elections(..) | + RuntimeCall::Treasury(..) ), - ProxyType::Staking => matches!(c, Call::Staking(..)), + ProxyType::Staking => matches!(c, RuntimeCall::Staking(..)), } } fn is_superset(&self, o: &Self) -> bool { @@ -311,15 +328,15 @@ impl InstanceFilter for ProxyType { } impl pallet_proxy::Config for Runtime { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type Currency = Balances; type ProxyType = ProxyType; type ProxyDepositBase = ProxyDepositBase; type ProxyDepositFactor = ProxyDepositFactor; - type MaxProxies = MaxProxies; + type MaxProxies = ConstU32<32>; type WeightInfo = pallet_proxy::weights::SubstrateWeight; - type MaxPending = MaxPending; + type MaxPending = ConstU32<32>; type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; @@ -328,18 +345,35 @@ impl pallet_proxy::Config for Runtime { parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block; - pub const MaxScheduledPerBlock: u32 = 50; } impl pallet_scheduler::Config for Runtime { - type Event = Event; - type Origin = Origin; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; type PalletsOrigin = OriginCaller; - type Call = Call; + type RuntimeCall = RuntimeCall; type MaximumWeight = MaximumSchedulerWeight; type ScheduleOrigin = EnsureRoot; - type MaxScheduledPerBlock = MaxScheduledPerBlock; + type MaxScheduledPerBlock = ConstU32<512>; type WeightInfo = pallet_scheduler::weights::SubstrateWeight; + type OriginPrivilegeCmp = EqualPrivilegeOnly; + type Preimages = Preimage; +} + +parameter_types! { + pub const PreimageMaxSize: u32 = 4096 * 1024; + pub const PreimageBaseDeposit: Balance = 1 * DOLLARS; + // One cent: $10,000 / MB + pub const PreimageByteDeposit: Balance = 1 * CENTS; +} + +impl pallet_preimage::Config for Runtime { + type WeightInfo = pallet_preimage::weights::SubstrateWeight; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type ManagerOrigin = EnsureRoot; + type BaseDeposit = PreimageBaseDeposit; + type ByteDeposit = PreimageByteDeposit; } parameter_types! { @@ -373,6 +407,7 @@ impl pallet_babe::Config for Runtime { pallet_babe::EquivocationHandler; type WeightInfo = (); + type MaxAuthorities = MaxAuthorities; } parameter_types! { @@ -383,7 +418,7 @@ impl pallet_indices::Config for Runtime { type AccountIndex = AccountIndex; type Currency = Balances; type Deposit = IndexDeposit; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_indices::weights::SubstrateWeight; } @@ -401,7 +436,7 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type Balance = Balance; type DustRemoval = (); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; type AccountStore = frame_system::Pallet; type WeightInfo = pallet_balances::weights::SubstrateWeight; @@ -409,17 +444,35 @@ impl pallet_balances::Config for Runtime { parameter_types! { pub const TransactionByteFee: Balance = 10 * MILLICENTS; + pub const OperationalFeeMultiplier: u8 = 5; pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 100_000); pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000_000u128); + pub MaximumMultiplier: Multiplier = Bounded::max_value(); } impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = CurrencyAdapter; - type TransactionByteFee = TransactionByteFee; + type OperationalFeeMultiplier = OperationalFeeMultiplier; type WeightToFee = IdentityFee; - type FeeMultiplierUpdate = - TargetedFeeAdjustment; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = TargetedFeeAdjustment< + Self, + TargetBlockFullness, + AdjustmentVariable, + MinimumMultiplier, + MaximumMultiplier, + >; +} + +impl pallet_asset_tx_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Fungibles = Assets; + type OnChargeAssetTransaction = pallet_asset_tx_payment::FungiblesAdapter< + pallet_assets::BalanceToAssetBalance, + CreditToBlockAuthor, + >; } parameter_types! { @@ -453,12 +506,8 @@ impl_opaque_keys! { } } -parameter_types! { - pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); -} - impl pallet_session::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ValidatorId = ::AccountId; type ValidatorIdOf = pallet_staking::StashOf; type ShouldEndSession = Babe; @@ -466,7 +515,6 @@ impl pallet_session::Config for Runtime { type SessionManager = pallet_session::historical::NoteHistoricalRoot; type SessionHandler = ::KeyTypeIdProviders; type Keys = SessionKeys; - type DisabledValidatorsThreshold = DisabledValidatorsThreshold; type WeightInfo = pallet_session::weights::SubstrateWeight; } @@ -488,44 +536,64 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const SessionsPerEra: sp_staking::SessionIndex = 6; - pub const BondingDuration: pallet_staking::EraIndex = 24 * 28; - pub const SlashDeferDuration: pallet_staking::EraIndex = 24 * 7; // 1/4 the bonding duration. + pub const BondingDuration: sp_staking::EraIndex = 24 * 28; + pub const SlashDeferDuration: sp_staking::EraIndex = 24 * 7; // 1/4 the bonding duration. pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const MaxNominatorRewardedPerValidator: u32 = 256; + pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); pub OffchainRepeat: BlockNumber = 5; + pub HistoryDepth: u32 = 84; } -use frame_election_provider_support::onchain; -impl onchain::Config for Runtime { - type Accuracy = Perbill; - type DataProvider = Staking; +pub struct StakingBenchmarkingConfig; +impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { + type MaxNominators = ConstU32<1000>; + type MaxValidators = ConstU32<1000>; } impl pallet_staking::Config for Runtime { - const MAX_NOMINATIONS: u32 = MAX_NOMINATIONS; + type MaxNominations = MaxNominations; type Currency = Balances; + type CurrencyBalance = Balance; type UnixTime = Timestamp; type CurrencyToVote = U128CurrencyToVote; type RewardRemainder = Treasury; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Slash = Treasury; // send the slashed funds to the treasury. type Reward = (); // rewards are minted from the void type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SlashDeferDuration = SlashDeferDuration; /// A super-majority of the council can cancel the slash. - type SlashCancelOrigin = EnsureOneOf< - AccountId, + type SlashCancelOrigin = EitherOfDiverse< EnsureRoot, - pallet_collective::EnsureProportionAtLeast<_3, _4, AccountId, CouncilCollective>, + pallet_collective::EnsureProportionAtLeast, >; type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; - type GenesisElectionProvider = onchain::OnChainSequentialPhragmen; + type GenesisElectionProvider = onchain::OnChainExecution; + type VoterList = VoterList; + // This a placeholder, to be introduced in the next PR as an instance of bags-list + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = HistoryDepth; + type OnStakerSlash = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; + type BenchmarkingConfig = StakingBenchmarkingConfig; +} + +impl pallet_fast_unstake::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ControlOrigin = frame_system::EnsureRoot; + type BatchSize = ConstU32<128>; + type Deposit = ConstU128<{ DOLLARS }>; + type Currency = Balances; + type Staking = Staking; + type WeightInfo = (); } parameter_types! { @@ -534,12 +602,11 @@ parameter_types! { pub const UnsignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; // signed config - pub const SignedMaxSubmissions: u32 = 10; pub const SignedRewardBase: Balance = 1 * DOLLARS; pub const SignedDepositBase: Balance = 1 * DOLLARS; pub const SignedDepositByte: Balance = 1 * CENTS; - pub SolutionImprovementThreshold: Perbill = Perbill::from_rational(1u32, 10_000); + pub BetterUnsignedThreshold: Perbill = Perbill::from_rational(1u32, 10_000); // miner configs pub const MultiPhaseUnsignedPriority: TransactionPriority = StakingUnsignedPriority::get() - 1u64; @@ -554,28 +621,40 @@ parameter_types! { .get(DispatchClass::Normal); } -sp_npos_elections::generate_solution_type!( +frame_election_provider_support::generate_solution_type!( #[compact] pub struct NposSolution16::< VoterIndex = u32, TargetIndex = u16, Accuracy = sp_runtime::PerU16, + MaxVoters = MaxElectingVoters, >(16) ); -pub const MAX_NOMINATIONS: u32 = ::LIMIT as u32; - -/// The numbers configured here should always be more than the the maximum limits of staking pallet -/// to ensure election snapshot will not run out of memory. -pub struct BenchmarkConfig; -impl pallet_election_provider_multi_phase::BenchmarkingConfig for BenchmarkConfig { - const VOTERS: [u32; 2] = [5_000, 10_000]; - const TARGETS: [u32; 2] = [1_000, 2_000]; - const ACTIVE_VOTERS: [u32; 2] = [1000, 4_000]; - const DESIRED_TARGETS: [u32; 2] = [400, 800]; - const SNAPSHOT_MAXIMUM_VOTERS: u32 = 25_000; - const MINER_MAXIMUM_VOTERS: u32 = 15_000; - const MAXIMUM_TARGETS: u32 = 2000; +parameter_types! { + pub MaxNominations: u32 = ::LIMIT as u32; + pub MaxElectingVoters: u32 = 40_000; + pub MaxElectableTargets: u16 = 10_000; + // OnChain values are lower. + pub MaxOnChainElectingVoters: u32 = 5000; + pub MaxOnChainElectableTargets: u16 = 1250; + // The maximum winners that can be elected by the Election pallet which is equivalent to the + // maximum active validators the staking pallet can have. + pub MaxActiveValidators: u32 = 1000; +} + +/// The numbers configured here could always be more than the the maximum limits of staking pallet +/// to ensure election snapshot will not run out of memory. For now, we set them to smaller values +/// since the staking is bounded and the weight pipeline takes hours for this single pallet. +pub struct ElectionProviderBenchmarkConfig; +impl pallet_election_provider_multi_phase::BenchmarkingConfig for ElectionProviderBenchmarkConfig { + const VOTERS: [u32; 2] = [1000, 2000]; + const TARGETS: [u32; 2] = [500, 1000]; + const ACTIVE_VOTERS: [u32; 2] = [500, 800]; + const DESIRED_TARGETS: [u32; 2] = [200, 400]; + const SNAPSHOT_MAXIMUM_VOTERS: u32 = 1000; + const MINER_MAXIMUM_VOTERS: u32 = 1000; + const MAXIMUM_TARGETS: u32 = 300; } /// Maximum number of iterations for balancing that will be executed in the embedded OCW @@ -584,14 +663,12 @@ pub const MINER_MAX_ITERATIONS: u32 = 10; /// A source of random balance for NposSolver, which is meant to be run by the OCW election miner. pub struct OffchainRandomBalancing; -impl frame_support::pallet_prelude::Get> - for OffchainRandomBalancing -{ - fn get() -> Option<(usize, sp_npos_elections::ExtendedBalance)> { +impl Get> for OffchainRandomBalancing { + fn get() -> Option { use sp_runtime::traits::TrailingZeroInput; - let iters = match MINER_MAX_ITERATIONS { + let iterations = match MINER_MAX_ITERATIONS { 0 => 0, - max @ _ => { + max => { let seed = sp_io::offchain::random_seed(); let random = ::decode(&mut TrailingZeroInput::new(&seed)) .expect("input is padded with zeroes; qed") % @@ -600,59 +677,257 @@ impl frame_support::pallet_prelude::Get, + >; + type DataProvider = ::DataProvider; + type WeightInfo = frame_election_provider_support::weights::SubstrateWeight; + type MaxWinners = ::MaxWinners; + type VotersBound = MaxOnChainElectingVoters; + type TargetsBound = MaxOnChainElectableTargets; +} + +impl pallet_election_provider_multi_phase::MinerConfig for Runtime { + type AccountId = AccountId; + type MaxLength = MinerMaxLength; + type MaxWeight = MinerMaxWeight; + type Solution = NposSolution16; + type MaxVotesPerVoter = + <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter; + + // The unsigned submissions have to respect the weight of the submit_unsigned call, thus their + // weight estimate function is wired to this call's weight. + fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight { + < + ::WeightInfo + as + pallet_election_provider_multi_phase::WeightInfo + >::submit_unsigned(v, t, a, d) } } impl pallet_election_provider_multi_phase::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; type EstimateCallFee = TransactionPayment; type SignedPhase = SignedPhase; type UnsignedPhase = UnsignedPhase; - type SolutionImprovementThreshold = SolutionImprovementThreshold; + type BetterUnsignedThreshold = BetterUnsignedThreshold; + type BetterSignedThreshold = (); type OffchainRepeat = OffchainRepeat; - type MinerMaxWeight = MinerMaxWeight; - type MinerMaxLength = MinerMaxLength; type MinerTxPriority = MultiPhaseUnsignedPriority; - type SignedMaxSubmissions = SignedMaxSubmissions; + type MinerConfig = Self; + type SignedMaxSubmissions = ConstU32<10>; type SignedRewardBase = SignedRewardBase; type SignedDepositBase = SignedDepositBase; type SignedDepositByte = SignedDepositByte; + type SignedMaxRefunds = ConstU32<3>; type SignedDepositWeight = (); type SignedMaxWeight = MinerMaxWeight; type SlashHandler = (); // burn slashes type RewardHandler = (); // nothing to do upon rewards type DataProvider = Staking; - type Solution = NposSolution16; - type Fallback = pallet_election_provider_multi_phase::NoFallback; - type Solver = frame_election_provider_support::SequentialPhragmen< - AccountId, - pallet_election_provider_multi_phase::SolutionAccuracyOf, - OffchainRandomBalancing, - >; - type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight; + type Fallback = onchain::OnChainExecution; + type GovernanceFallback = onchain::OnChainExecution; + type Solver = SequentialPhragmen, OffchainRandomBalancing>; type ForceOrigin = EnsureRootOrHalfCouncil; - type BenchmarkingConfig = BenchmarkConfig; + type MaxElectableTargets = MaxElectableTargets; + type MaxWinners = MaxActiveValidators; + type MaxElectingVoters = MaxElectingVoters; + type BenchmarkingConfig = ElectionProviderBenchmarkConfig; + type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight; +} + +parameter_types! { + pub const BagThresholds: &'static [u64] = &voter_bags::THRESHOLDS; +} + +type VoterBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + /// The voter bags-list is loosely kept up to date, and the real source of truth for the score + /// of each node is the staking pallet. + type ScoreProvider = Staking; + type BagThresholds = BagThresholds; + type Score = VoteWeight; + type WeightInfo = pallet_bags_list::weights::SubstrateWeight; +} + +parameter_types! { + pub const PostUnbondPoolsWindow: u32 = 4; + pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/nopls"); + pub const MaxPointsToBalance: u8 = 10; +} + +use sp_runtime::traits::Convert; +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(balance: Balance) -> sp_core::U256 { + sp_core::U256::from(balance) + } +} +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap_or(Balance::max_value()) + } +} + +impl pallet_nomination_pools::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type RewardCounter = FixedU128; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type Staking = Staking; + type PostUnbondingPoolsWindow = PostUnbondPoolsWindow; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; + type PalletId = NominationPoolsPalletId; + type MaxPointsToBalance = MaxPointsToBalance; +} + +parameter_types! { + pub const VoteLockingPeriod: BlockNumber = 30 * DAYS; +} + +impl pallet_conviction_voting::Config for Runtime { + type WeightInfo = pallet_conviction_voting::weights::SubstrateWeight; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type VoteLockingPeriod = VoteLockingPeriod; + type MaxVotes = ConstU32<512>; + type MaxTurnout = frame_support::traits::TotalIssuanceOf; + type Polls = Referenda; +} + +parameter_types! { + pub const AlarmInterval: BlockNumber = 1; + pub const SubmissionDeposit: Balance = 100 * DOLLARS; + pub const UndecidingTimeout: BlockNumber = 28 * DAYS; +} + +pub struct TracksInfo; +impl pallet_referenda::TracksInfo for TracksInfo { + type Id = u16; + type RuntimeOrigin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo)] { + static DATA: [(u16, pallet_referenda::TrackInfo); 1] = [( + 0u16, + pallet_referenda::TrackInfo { + name: "root", + max_deciding: 1, + decision_deposit: 10, + prepare_period: 4, + decision_period: 4, + confirm_period: 2, + min_enactment_period: 4, + min_approval: pallet_referenda::Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), + }, + min_support: pallet_referenda::Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(100), + }, + }, + )]; + &DATA[..] + } + fn track_for(id: &Self::RuntimeOrigin) -> Result { + if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) { + match system_origin { + frame_system::RawOrigin::Root => Ok(0), + _ => Err(()), + } + } else { + Err(()) + } + } } +pallet_referenda::impl_tracksinfo_get!(TracksInfo, Balance, BlockNumber); + +impl pallet_referenda::Config for Runtime { + type WeightInfo = pallet_referenda::weights::SubstrateWeight; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; + type Currency = pallet_balances::Pallet; + type SubmitOrigin = EnsureSigned; + type CancelOrigin = EnsureRoot; + type KillOrigin = EnsureRoot; + type Slash = (); + type Votes = pallet_conviction_voting::VotesOf; + type Tally = pallet_conviction_voting::TallyOf; + type SubmissionDeposit = SubmissionDeposit; + type MaxQueued = ConstU32<100>; + type UndecidingTimeout = UndecidingTimeout; + type AlarmInterval = AlarmInterval; + type Tracks = TracksInfo; + type Preimages = Preimage; +} + +impl pallet_referenda::Config for Runtime { + type WeightInfo = pallet_referenda::weights::SubstrateWeight; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; + type Currency = pallet_balances::Pallet; + type SubmitOrigin = EnsureSigned; + type CancelOrigin = EnsureRoot; + type KillOrigin = EnsureRoot; + type Slash = (); + type Votes = pallet_ranked_collective::Votes; + type Tally = pallet_ranked_collective::TallyOf; + type SubmissionDeposit = SubmissionDeposit; + type MaxQueued = ConstU32<100>; + type UndecidingTimeout = UndecidingTimeout; + type AlarmInterval = AlarmInterval; + type Tracks = TracksInfo; + type Preimages = Preimage; +} + +impl pallet_ranked_collective::Config for Runtime { + type WeightInfo = pallet_ranked_collective::weights::SubstrateWeight; + type RuntimeEvent = RuntimeEvent; + type PromoteOrigin = EnsureRootWithSuccess>; + type DemoteOrigin = EnsureRootWithSuccess>; + type Polls = RankedPolls; + type MinRankOfClass = traits::Identity; + type VoteWeight = pallet_ranked_collective::Geometric; +} + +impl pallet_remark::Config for Runtime { + type WeightInfo = pallet_remark::weights::SubstrateWeight; + type RuntimeEvent = RuntimeEvent; +} + +impl pallet_root_testing::Config for Runtime {} parameter_types! { pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; pub const VotingPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; pub const FastTrackVotingPeriod: BlockNumber = 3 * 24 * 60 * MINUTES; - pub const InstantAllowed: bool = true; pub const MinimumDeposit: Balance = 100 * DOLLARS; pub const EnactmentPeriod: BlockNumber = 30 * 24 * 60 * MINUTES; pub const CooloffPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; - // One cent: $10,000 / MB - pub const PreimageByteDeposit: Balance = 1 * CENTS; - pub const MaxVotes: u32 = 100; pub const MaxProposals: u32 = 100; } impl pallet_democracy::Config for Runtime { - type Proposal = Call; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; type EnactmentPeriod = EnactmentPeriod; type LaunchPeriod = LaunchPeriod; @@ -661,45 +936,45 @@ impl pallet_democracy::Config for Runtime { type MinimumDeposit = MinimumDeposit; /// A straight majority of the council can decide what their next motion is. type ExternalOrigin = - pallet_collective::EnsureProportionAtLeast<_1, _2, AccountId, CouncilCollective>; + pallet_collective::EnsureProportionAtLeast; /// A super-majority can have the next scheduled referendum be a straight majority-carries vote. type ExternalMajorityOrigin = - pallet_collective::EnsureProportionAtLeast<_3, _4, AccountId, CouncilCollective>; + pallet_collective::EnsureProportionAtLeast; /// A unanimous council can have the next scheduled referendum be a straight default-carries /// (NTB) vote. type ExternalDefaultOrigin = - pallet_collective::EnsureProportionAtLeast<_1, _1, AccountId, CouncilCollective>; + pallet_collective::EnsureProportionAtLeast; /// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote /// be tabled immediately and with a shorter voting/enactment period. type FastTrackOrigin = - pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId, TechnicalCollective>; + pallet_collective::EnsureProportionAtLeast; type InstantOrigin = - pallet_collective::EnsureProportionAtLeast<_1, _1, AccountId, TechnicalCollective>; - type InstantAllowed = InstantAllowed; + pallet_collective::EnsureProportionAtLeast; + type InstantAllowed = frame_support::traits::ConstBool; type FastTrackVotingPeriod = FastTrackVotingPeriod; // To cancel a proposal which has been passed, 2/3 of the council must agree to it. type CancellationOrigin = - pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId, CouncilCollective>; + pallet_collective::EnsureProportionAtLeast; // To cancel a proposal before it has been passed, the technical committee must be unanimous or // Root must agree. - type CancelProposalOrigin = EnsureOneOf< - AccountId, + type CancelProposalOrigin = EitherOfDiverse< EnsureRoot, - pallet_collective::EnsureProportionAtLeast<_1, _1, AccountId, TechnicalCollective>, + pallet_collective::EnsureProportionAtLeast, >; type BlacklistOrigin = EnsureRoot; // Any single technical committee member may veto a coming council proposal, however they can // only do it once and it lasts only for the cool-off period. type VetoOrigin = pallet_collective::EnsureMember; type CooloffPeriod = CooloffPeriod; - type PreimageByteDeposit = PreimageByteDeposit; - type OperationalPreimageOrigin = pallet_collective::EnsureMember; type Slash = Treasury; type Scheduler = Scheduler; type PalletsOrigin = OriginCaller; - type MaxVotes = MaxVotes; + type MaxVotes = ConstU32<100>; type WeightInfo = pallet_democracy::weights::SubstrateWeight; type MaxProposals = MaxProposals; + type Preimages = Preimage; + type MaxDeposits = ConstU32<100>; + type MaxBlacklisted = ConstU32<100>; } parameter_types! { @@ -710,9 +985,9 @@ parameter_types! { type CouncilCollective = pallet_collective::Instance1; impl pallet_collective::Config for Runtime { - type Origin = Origin; - type Proposal = Call; - type Event = Event; + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; type MotionDuration = CouncilMotionDuration; type MaxProposals = CouncilMaxProposals; type MaxMembers = CouncilMaxMembers; @@ -729,6 +1004,8 @@ parameter_types! { pub const TermDuration: BlockNumber = 7 * DAYS; pub const DesiredMembers: u32 = 13; pub const DesiredRunnersUp: u32 = 7; + pub const MaxVoters: u32 = 10 * 1000; + pub const MaxCandidates: u32 = 1000; pub const ElectionsPhragmenPalletId: LockIdentifier = *b"phrelect"; } @@ -736,7 +1013,7 @@ parameter_types! { const_assert!(DesiredMembers::get() <= CouncilMaxMembers::get()); impl pallet_elections_phragmen::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type PalletId = ElectionsPhragmenPalletId; type Currency = Balances; type ChangeMembers = Council; @@ -752,6 +1029,8 @@ impl pallet_elections_phragmen::Config for Runtime { type DesiredMembers = DesiredMembers; type DesiredRunnersUp = DesiredRunnersUp; type TermDuration = TermDuration; + type MaxVoters = MaxVoters; + type MaxCandidates = MaxCandidates; type WeightInfo = pallet_elections_phragmen::weights::SubstrateWeight; } @@ -763,9 +1042,9 @@ parameter_types! { type TechnicalCollective = pallet_collective::Instance2; impl pallet_collective::Config for Runtime { - type Origin = Origin; - type Proposal = Call; - type Event = Event; + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; type MotionDuration = TechnicalMotionDuration; type MaxProposals = TechnicalMaxProposals; type MaxMembers = TechnicalMaxMembers; @@ -773,13 +1052,12 @@ impl pallet_collective::Config for Runtime { type WeightInfo = pallet_collective::weights::SubstrateWeight; } -type EnsureRootOrHalfCouncil = EnsureOneOf< - AccountId, +type EnsureRootOrHalfCouncil = EitherOfDiverse< EnsureRoot, - pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>, + pallet_collective::EnsureProportionMoreThan, >; impl pallet_membership::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type AddOrigin = EnsureRootOrHalfCouncil; type RemoveOrigin = EnsureRootOrHalfCouncil; type SwapOrigin = EnsureRootOrHalfCouncil; @@ -800,55 +1078,95 @@ parameter_types! { pub const TipFindersFee: Percent = Percent::from_percent(20); pub const TipReportDepositBase: Balance = 1 * DOLLARS; pub const DataDepositPerByte: Balance = 1 * CENTS; - pub const BountyDepositBase: Balance = 1 * DOLLARS; - pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS; pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); - pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS; - pub const MaximumReasonLength: u32 = 16384; - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); - pub const BountyValueMinimum: Balance = 5 * DOLLARS; + pub const MaximumReasonLength: u32 = 300; pub const MaxApprovals: u32 = 100; + pub const MaxBalance: Balance = Balance::max_value(); } impl pallet_treasury::Config for Runtime { type PalletId = TreasuryPalletId; type Currency = Balances; - type ApproveOrigin = EnsureOneOf< - AccountId, + type ApproveOrigin = EitherOfDiverse< EnsureRoot, - pallet_collective::EnsureProportionAtLeast<_3, _5, AccountId, CouncilCollective>, + pallet_collective::EnsureProportionAtLeast, >; - type RejectOrigin = EnsureOneOf< - AccountId, + type RejectOrigin = EitherOfDiverse< EnsureRoot, - pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>, + pallet_collective::EnsureProportionMoreThan, >; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type OnSlash = (); type ProposalBond = ProposalBond; type ProposalBondMinimum = ProposalBondMinimum; + type ProposalBondMaximum = (); type SpendPeriod = SpendPeriod; type Burn = Burn; type BurnDestination = (); type SpendFunds = Bounties; type WeightInfo = pallet_treasury::weights::SubstrateWeight; type MaxApprovals = MaxApprovals; + type SpendOrigin = EnsureWithSuccess, AccountId, MaxBalance>; +} + +parameter_types! { + pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); + pub const BountyValueMinimum: Balance = 5 * DOLLARS; + pub const BountyDepositBase: Balance = 1 * DOLLARS; + pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50); + pub const CuratorDepositMin: Balance = 1 * DOLLARS; + pub const CuratorDepositMax: Balance = 100 * DOLLARS; + pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS; + pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS; } impl pallet_bounties::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type BountyDepositBase = BountyDepositBase; type BountyDepositPayoutDelay = BountyDepositPayoutDelay; type BountyUpdatePeriod = BountyUpdatePeriod; - type BountyCuratorDeposit = BountyCuratorDeposit; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMin = CuratorDepositMin; + type CuratorDepositMax = CuratorDepositMax; type BountyValueMinimum = BountyValueMinimum; type DataDepositPerByte = DataDepositPerByte; type MaximumReasonLength = MaximumReasonLength; type WeightInfo = pallet_bounties::weights::SubstrateWeight; + type ChildBountyManager = ChildBounties; +} + +parameter_types! { + /// Allocate at most 20% of each block for message processing. + /// + /// Is set to 20% since the scheduler can already consume a maximum of 80%. + pub MessageQueueServiceWeight: Option = Some(Perbill::from_percent(20) * RuntimeBlockWeights::get().max_block); +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + /// NOTE: Always set this to `NoopMessageProcessor` for benchmarking. + type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor; + type Size = u32; + type QueueChangeHandler = (); + type HeapSize = ConstU32<{ 64 * 1024 }>; + type MaxStale = ConstU32<128>; + type ServiceWeight = MessageQueueServiceWeight; +} + +parameter_types! { + pub const ChildBountyValueMinimum: Balance = 1 * DOLLARS; +} + +impl pallet_child_bounties::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaxActiveChildBountyCount = ConstU32<5>; + type ChildBountyValueMinimum = ChildBountyValueMinimum; + type WeightInfo = pallet_child_bounties::weights::SubstrateWeight; } impl pallet_tips::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DataDepositPerByte = DataDepositPerByte; type MaximumReasonLength = MaximumReasonLength; type Tippers = Elections; @@ -859,20 +1177,16 @@ impl pallet_tips::Config for Runtime { } parameter_types! { - pub ContractDeposit: Balance = deposit( - 1, - >::contract_info_size(), - ); + pub const DepositPerItem: Balance = deposit(1, 0); + pub const DepositPerByte: Balance = deposit(0, 1); pub const MaxValueSize: u32 = 16 * 1024; + pub const DeletionQueueDepth: u32 = 128; // The lazy deletion runs inside on_initialize. - pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO * - RuntimeBlockWeights::get().max_block; - // The weight needed for decoding the queue should be less or equal than a fifth - // of the overall weight dedicated to the lazy deletion. - pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / ( - ::WeightInfo::on_initialize_per_queue_item(1) - - ::WeightInfo::on_initialize_per_queue_item(0) - )) / 5) as u32; + pub DeletionWeightLimit: Weight = RuntimeBlockWeights::get() + .per_class + .get(DispatchClass::Normal) + .max_total + .unwrap_or(RuntimeBlockWeights::get().max_block); pub Schedule: pallet_contracts::Schedule = Default::default(); } @@ -880,8 +1194,8 @@ impl pallet_contracts::Config for Runtime { type Time = Timestamp; type Randomness = RandomnessCollectiveFlip; type Currency = Balances; - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; /// The safest default is to allow no calls at all. /// /// Runtimes should whitelist dispatchables that are allowed to be called from contracts @@ -889,7 +1203,8 @@ impl pallet_contracts::Config for Runtime { /// change because that would break already deployed contracts. The `Call` structure itself /// is not allowed to change the indices of existing pallets, too. type CallFilter = Nothing; - type ContractDeposit = ContractDeposit; + type DepositPerItem = DepositPerItem; + type DepositPerByte = DepositPerByte; type CallStack = [pallet_contracts::Frame; 31]; type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; @@ -897,11 +1212,16 @@ impl pallet_contracts::Config for Runtime { type DeletionQueueDepth = DeletionQueueDepth; type DeletionWeightLimit = DeletionWeightLimit; type Schedule = Schedule; + type AddressGenerator = pallet_contracts::DefaultAddressGenerator; + type MaxCodeLen = ConstU32<{ 128 * 1024 }>; + type MaxStorageKeyLen = ConstU32<128>; + type UnsafeUnstableInterface = ConstBool; + type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; } impl pallet_sudo::Config for Runtime { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; } parameter_types! { @@ -909,18 +1229,21 @@ parameter_types! { /// We prioritize im-online heartbeats over election solution submission. pub const StakingUnsignedPriority: TransactionPriority = TransactionPriority::max_value() / 2; pub const MaxAuthorities: u32 = 100; + pub const MaxKeys: u32 = 10_000; + pub const MaxPeerInHeartbeats: u32 = 10_000; + pub const MaxPeerDataEncodingSize: u32 = 1_000; } impl frame_system::offchain::CreateSignedTransaction for Runtime where - Call: From, + RuntimeCall: From, { fn create_transaction>( - call: Call, + call: RuntimeCall, public: ::Signer, account: AccountId, nonce: Index, - ) -> Option<(Call, ::SignaturePayload)> { + ) -> Option<(RuntimeCall, ::SignaturePayload)> { let tip = 0; // take the biggest period possible. let period = @@ -932,13 +1255,14 @@ where .saturating_sub(1); let era = Era::mortal(period, current_block); let extra = ( + frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), frame_system::CheckEra::::from(era), frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + pallet_asset_tx_payment::ChargeAssetTxPayment::::from(tip, None), ); let raw_payload = SignedPayload::new(call, extra) .map_err(|e| { @@ -948,7 +1272,7 @@ where let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?; let address = Indices::unlookup(account); let (call, extra, _) = raw_payload.deconstruct(); - Some((call, (address, signature.into(), extra))) + Some((call, (address, signature, extra))) } } @@ -959,24 +1283,27 @@ impl frame_system::offchain::SigningTypes for Runtime { impl frame_system::offchain::SendTransactionTypes for Runtime where - Call: From, + RuntimeCall: From, { type Extrinsic = UncheckedExtrinsic; - type OverarchingCall = Call; + type OverarchingCall = RuntimeCall; } impl pallet_im_online::Config for Runtime { type AuthorityId = ImOnlineId; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type NextSessionRotation = Babe; type ValidatorSet = Historical; type ReportUnresponsiveness = Offences; type UnsignedPriority = ImOnlineUnsignedPriority; type WeightInfo = pallet_im_online::weights::SubstrateWeight; + type MaxKeys = MaxKeys; + type MaxPeerInHeartbeats = MaxPeerInHeartbeats; + type MaxPeerDataEncodingSize = MaxPeerDataEncodingSize; } impl pallet_offences::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type IdentificationTuple = pallet_session::historical::IdentificationTuple; type OnOffenceHandler = Staking; } @@ -986,8 +1313,7 @@ impl pallet_authority_discovery::Config for Runtime { } impl pallet_grandpa::Config for Runtime { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; type KeyOwnerProofSystem = Historical; @@ -1006,6 +1332,7 @@ impl pallet_grandpa::Config for Runtime { >; type WeightInfo = (); + type MaxAuthorities = MaxAuthorities; } parameter_types! { @@ -1018,7 +1345,7 @@ parameter_types! { } impl pallet_identity::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; type BasicDeposit = BasicDeposit; type FieldDeposit = FieldDeposit; @@ -1040,8 +1367,9 @@ parameter_types! { } impl pallet_recovery::Config for Runtime { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_recovery::weights::SubstrateWeight; + type RuntimeCall = RuntimeCall; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; @@ -1062,7 +1390,7 @@ parameter_types! { } impl pallet_society::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type PalletId = SocietyPalletId; type Currency = Balances; type Randomness = RandomnessCollectiveFlip; @@ -1074,7 +1402,7 @@ impl pallet_society::Config for Runtime { type RotationPeriod = RotationPeriod; type MaxLockDuration = MaxLockDuration; type FounderSetOrigin = - pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>; + pallet_collective::EnsureProportionMoreThan; type SuspensionJudgementOrigin = pallet_society::EnsureFounder; type MaxCandidateIntake = MaxCandidateIntake; type ChallengePeriod = ChallengePeriod; @@ -1082,14 +1410,17 @@ impl pallet_society::Config for Runtime { parameter_types! { pub const MinVestedTransfer: Balance = 100 * DOLLARS; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); } impl pallet_vesting::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; type BlockNumberToBalance = ConvertInto; type MinVestedTransfer = MinVestedTransfer; type WeightInfo = pallet_vesting::weights::SubstrateWeight; + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; // `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the // highest number of schedules that encodes less than 2^10. const MAX_VESTING_SCHEDULES: u32 = 28; @@ -1099,7 +1430,7 @@ impl pallet_mmr::Config for Runtime { const INDEXING_PREFIX: &'static [u8] = b"mmr"; type Hashing = ::Hashing; type Hash = ::Hash; - type LeafData = frame_system::Pallet; + type LeafData = pallet_mmr::ParentNumberAndHash; type OnNewRoot = (); type WeightInfo = (); } @@ -1112,10 +1443,10 @@ parameter_types! { impl pallet_lottery::Config for Runtime { type PalletId = LotteryPalletId; - type Call = Call; + type RuntimeCall = RuntimeCall; type Currency = Balances; type Randomness = RandomnessCollectiveFlip; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ManagerOrigin = EnsureRoot; type MaxCalls = MaxCalls; type ValidateCall = Lottery; @@ -1132,12 +1463,15 @@ parameter_types! { } impl pallet_assets::Config for Runtime { - type Event = Event; - type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type Balance = u128; type AssetId = u32; + type AssetIdParameter = codec::Compact; type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = EnsureRoot; type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = ConstU128; type MetadataDepositBase = MetadataDepositBase; type MetadataDepositPerByte = MetadataDepositPerByte; type ApprovalDeposit = ApprovalDeposit; @@ -1145,6 +1479,9 @@ impl pallet_assets::Config for Runtime { type Freezer = (); type Extra = (); type WeightInfo = pallet_assets::weights::SubstrateWeight; + type RemoveItemsLimit = ConstU32<1000>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); } parameter_types! { @@ -1152,45 +1489,54 @@ parameter_types! { pub const QueueCount: u32 = 300; pub const MaxQueueLen: u32 = 1000; pub const FifoQueueLen: u32 = 500; - pub const Period: BlockNumber = 30 * DAYS; - pub const MinFreeze: Balance = 100 * DOLLARS; + pub const NisBasePeriod: BlockNumber = 30 * DAYS; + pub const MinBid: Balance = 100 * DOLLARS; + pub const MinReceipt: Perquintill = Perquintill::from_percent(1); pub const IntakePeriod: BlockNumber = 10; - pub const MaxIntakeBids: u32 = 10; + pub MaxIntakeWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 10; + pub const ThawThrottle: (Perquintill, BlockNumber) = (Perquintill::from_percent(25), 5); + pub Target: Perquintill = Perquintill::zero(); + pub const NisPalletId: PalletId = PalletId(*b"py/nis "); } -impl pallet_gilt::Config for Runtime { - type Event = Event; +impl pallet_nis::Config for Runtime { + type WeightInfo = pallet_nis::weights::SubstrateWeight; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; type CurrencyBalance = Balance; - type AdminOrigin = frame_system::EnsureRoot; + type FundOrigin = frame_system::EnsureSigned; + type Counterpart = ItemOf, AccountId>; + type CounterpartAmount = WithMaximumOf>; type Deficit = (); - type Surplus = (); type IgnoredIssuance = IgnoredIssuance; + type Target = Target; + type PalletId = NisPalletId; type QueueCount = QueueCount; type MaxQueueLen = MaxQueueLen; type FifoQueueLen = FifoQueueLen; - type Period = Period; - type MinFreeze = MinFreeze; + type BasePeriod = NisBasePeriod; + type MinBid = MinBid; + type MinReceipt = MinReceipt; type IntakePeriod = IntakePeriod; - type MaxIntakeBids = MaxIntakeBids; - type WeightInfo = pallet_gilt::weights::SubstrateWeight; + type MaxIntakeWeight = MaxIntakeWeight; + type ThawThrottle = ThawThrottle; } parameter_types! { - pub const ClassDeposit: Balance = 100 * DOLLARS; - pub const InstanceDeposit: Balance = 1 * DOLLARS; + pub const CollectionDeposit: Balance = 100 * DOLLARS; + pub const ItemDeposit: Balance = 1 * DOLLARS; pub const KeyLimit: u32 = 32; pub const ValueLimit: u32 = 256; } impl pallet_uniques::Config for Runtime { - type Event = Event; - type ClassId = u32; - type InstanceId = u32; + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; type Currency = Balances; type ForceOrigin = frame_system::EnsureRoot; - type ClassDeposit = ClassDeposit; - type InstanceDeposit = InstanceDeposit; + type CollectionDeposit = CollectionDeposit; + type ItemDeposit = ItemDeposit; type MetadataDepositBase = MetadataDepositBase; type AttributeDepositBase = MetadataDepositBase; type DepositPerByte = MetadataDepositPerByte; @@ -1198,14 +1544,115 @@ impl pallet_uniques::Config for Runtime { type KeyLimit = KeyLimit; type ValueLimit = ValueLimit; type WeightInfo = pallet_uniques::weights::SubstrateWeight; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Locker = (); } impl pallet_transaction_storage::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; - type Call = Call; + type RuntimeCall = RuntimeCall; type FeeDestination = (); type WeightInfo = pallet_transaction_storage::weights::SubstrateWeight; + type MaxBlockTransactions = + ConstU32<{ pallet_transaction_storage::DEFAULT_MAX_BLOCK_TRANSACTIONS }>; + type MaxTransactionSize = + ConstU32<{ pallet_transaction_storage::DEFAULT_MAX_TRANSACTION_SIZE }>; +} + +impl pallet_whitelist::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WhitelistOrigin = EnsureRoot; + type DispatchWhitelistedOrigin = EnsureRoot; + type Preimages = Preimage; + type WeightInfo = pallet_whitelist::weights::SubstrateWeight; +} + +parameter_types! { + pub const MigrationSignedDepositPerItem: Balance = 1 * CENTS; + pub const MigrationSignedDepositBase: Balance = 20 * DOLLARS; + pub const MigrationMaxKeyLen: u32 = 512; +} + +impl pallet_state_trie_migration::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ControlOrigin = EnsureRoot; + type Currency = Balances; + type MaxKeyLen = MigrationMaxKeyLen; + type SignedDepositPerItem = MigrationSignedDepositPerItem; + type SignedDepositBase = MigrationSignedDepositBase; + // Warning: this is not advised, as it might allow the chain to be temporarily DOS-ed. + // Preferably, if the chain's governance/maintenance team is planning on using a specific + // account for the migration, put it here to make sure only that account can trigger the signed + // migrations. + type SignedFilter = EnsureSigned; + type WeightInfo = (); +} + +const ALLIANCE_MOTION_DURATION_IN_BLOCKS: BlockNumber = 5 * DAYS; + +parameter_types! { + pub const AllianceMotionDuration: BlockNumber = ALLIANCE_MOTION_DURATION_IN_BLOCKS; + pub const AllianceMaxProposals: u32 = 100; + pub const AllianceMaxMembers: u32 = 100; +} + +type AllianceCollective = pallet_collective::Instance3; +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = AllianceMotionDuration; + type MaxProposals = AllianceMaxProposals; + type MaxMembers = AllianceMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type WeightInfo = pallet_collective::weights::SubstrateWeight; +} + +parameter_types! { + pub const MaxFellows: u32 = AllianceMaxMembers::get(); + pub const MaxAllies: u32 = 100; + pub const AllyDeposit: Balance = 10 * DOLLARS; + pub const RetirementPeriod: BlockNumber = ALLIANCE_MOTION_DURATION_IN_BLOCKS + (1 * DAYS); +} + +impl pallet_alliance::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Proposal = RuntimeCall; + type AdminOrigin = EitherOfDiverse< + EnsureRoot, + pallet_collective::EnsureProportionMoreThan, + >; + type MembershipManager = EitherOfDiverse< + EnsureRoot, + pallet_collective::EnsureProportionMoreThan, + >; + type AnnouncementOrigin = EitherOfDiverse< + EnsureRoot, + pallet_collective::EnsureProportionMoreThan, + >; + type Currency = Balances; + type Slashed = Treasury; + type InitializeMembers = AllianceMotion; + type MembershipChanged = AllianceMotion; + #[cfg(not(feature = "runtime-benchmarks"))] + type IdentityVerifier = AllianceIdentityVerifier; + #[cfg(feature = "runtime-benchmarks")] + type IdentityVerifier = (); + type ProposalProvider = AllianceProposalProvider; + type MaxProposals = AllianceMaxProposals; + type MaxFellows = MaxFellows; + type MaxAllies = MaxAllies; + type MaxUnscrupulousItems = ConstU32<100>; + type MaxWebsiteUrlLength = ConstU32<255>; + type MaxAnnouncementsCount = ConstU32<100>; + type MaxMembersCount = AllianceMaxMembers; + type AllyDeposit = AllyDeposit; + type WeightInfo = pallet_alliance::weights::SubstrateWeight; + type RetirementPeriod = RetirementPeriod; } construct_runtime!( @@ -1214,46 +1661,65 @@ construct_runtime!( NodeBlock = node_primitives::Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Utility: pallet_utility::{Pallet, Call, Event}, - Babe: pallet_babe::{Pallet, Call, Storage, Config, ValidateUnsigned}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - Authorship: pallet_authorship::{Pallet, Call, Storage, Inherent}, - Indices: pallet_indices::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, - ElectionProviderMultiPhase: pallet_election_provider_multi_phase::{Pallet, Call, Storage, Event, ValidateUnsigned}, - Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, - Session: pallet_session::{Pallet, Call, Storage, Event, Config}, - Democracy: pallet_democracy::{Pallet, Call, Storage, Config, Event}, - Council: pallet_collective::::{Pallet, Call, Storage, Origin, Event, Config}, - TechnicalCommittee: pallet_collective::::{Pallet, Call, Storage, Origin, Event, Config}, - Elections: pallet_elections_phragmen::{Pallet, Call, Storage, Event, Config}, - TechnicalMembership: pallet_membership::::{Pallet, Call, Storage, Event, Config}, - Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event, ValidateUnsigned}, - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, - Contracts: pallet_contracts::{Pallet, Call, Storage, Event}, - Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event}, - ImOnline: pallet_im_online::{Pallet, Call, Storage, Event, ValidateUnsigned, Config}, - AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config}, - Offences: pallet_offences::{Pallet, Storage, Event}, + System: frame_system, + Utility: pallet_utility, + Babe: pallet_babe, + Timestamp: pallet_timestamp, + // Authorship must be before session in order to note author in the correct session and era + // for im-online and staking. + Authorship: pallet_authorship, + Indices: pallet_indices, + Balances: pallet_balances, + TransactionPayment: pallet_transaction_payment, + AssetTxPayment: pallet_asset_tx_payment, + ElectionProviderMultiPhase: pallet_election_provider_multi_phase, + Staking: pallet_staking, + Session: pallet_session, + Democracy: pallet_democracy, + Council: pallet_collective::, + TechnicalCommittee: pallet_collective::, + Elections: pallet_elections_phragmen, + TechnicalMembership: pallet_membership::, + Grandpa: pallet_grandpa, + Treasury: pallet_treasury, + Contracts: pallet_contracts, + Sudo: pallet_sudo, + ImOnline: pallet_im_online, + AuthorityDiscovery: pallet_authority_discovery, + Offences: pallet_offences, Historical: pallet_session_historical::{Pallet}, - RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, - Identity: pallet_identity::{Pallet, Call, Storage, Event}, - Society: pallet_society::{Pallet, Call, Storage, Event, Config}, - Recovery: pallet_recovery::{Pallet, Call, Storage, Event}, - Vesting: pallet_vesting::{Pallet, Call, Storage, Event, Config}, - Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, - Proxy: pallet_proxy::{Pallet, Call, Storage, Event}, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event}, - Bounties: pallet_bounties::{Pallet, Call, Storage, Event}, - Tips: pallet_tips::{Pallet, Call, Storage, Event}, - Assets: pallet_assets::{Pallet, Call, Storage, Event}, - Mmr: pallet_mmr::{Pallet, Storage}, - Lottery: pallet_lottery::{Pallet, Call, Storage, Event}, - Gilt: pallet_gilt::{Pallet, Call, Storage, Event, Config}, - Uniques: pallet_uniques::{Pallet, Call, Storage, Event}, - TransactionStorage: pallet_transaction_storage::{Pallet, Call, Storage, Inherent, Config, Event}, + RandomnessCollectiveFlip: pallet_randomness_collective_flip, + Identity: pallet_identity, + Society: pallet_society, + Recovery: pallet_recovery, + Vesting: pallet_vesting, + Scheduler: pallet_scheduler, + Preimage: pallet_preimage, + Proxy: pallet_proxy, + Multisig: pallet_multisig, + Bounties: pallet_bounties, + Tips: pallet_tips, + Assets: pallet_assets, + Mmr: pallet_mmr, + Lottery: pallet_lottery, + Nis: pallet_nis, + Uniques: pallet_uniques, + TransactionStorage: pallet_transaction_storage, + VoterList: pallet_bags_list::, + StateTrieMigration: pallet_state_trie_migration, + ChildBounties: pallet_child_bounties, + Referenda: pallet_referenda, + Remark: pallet_remark, + RootTesting: pallet_root_testing, + ConvictionVoting: pallet_conviction_voting, + Whitelist: pallet_whitelist, + AllianceMotion: pallet_collective::, + Alliance: pallet_alliance, + NominationPools: pallet_nomination_pools, + RankedPolls: pallet_referenda::, + RankedCollective: pallet_ranked_collective, + FastUnstake: pallet_fast_unstake, + MessageQueue: pallet_message_queue, } ); @@ -1273,30 +1739,41 @@ pub type BlockId = generic::BlockId; /// /// [`sign`]: <../../testing/src/keyring.rs.html> pub type SignedExtra = ( + frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, frame_system::CheckGenesis, frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, + pallet_asset_tx_payment::ChargeAssetTxPayment, ); + /// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; /// The payload being signed in transactions. -pub type SignedPayload = generic::SignedPayload; +pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. -pub type CheckedExtrinsic = generic::CheckedExtrinsic; +pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< Runtime, Block, frame_system::ChainContext, Runtime, - AllPallets, - (), + AllPalletsWithSystem, + Migrations, >; +// All migrations executed on runtime upgrade as a nested tuple of types implementing +// `OnRuntimeUpgrade`. +type Migrations = ( + pallet_nomination_pools::migration::v2::MigrateToV2, + pallet_alliance::migration::Migration, + pallet_contracts::Migration, +); + /// MMR helper types. mod mmr { use super::Runtime; @@ -1307,6 +1784,63 @@ mod mmr { pub type Hashing = ::Hashing; } +#[cfg(feature = "runtime-benchmarks")] +#[macro_use] +extern crate frame_benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + define_benchmarks!( + [frame_benchmarking, BaselineBench::] + [pallet_alliance, Alliance] + [pallet_assets, Assets] + [pallet_babe, Babe] + [pallet_bags_list, VoterList] + [pallet_balances, Balances] + [pallet_bounties, Bounties] + [pallet_child_bounties, ChildBounties] + [pallet_collective, Council] + [pallet_conviction_voting, ConvictionVoting] + [pallet_contracts, Contracts] + [pallet_democracy, Democracy] + [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] + [pallet_election_provider_support_benchmarking, EPSBench::] + [pallet_elections_phragmen, Elections] + [pallet_fast_unstake, FastUnstake] + [pallet_nis, Nis] + [pallet_grandpa, Grandpa] + [pallet_identity, Identity] + [pallet_im_online, ImOnline] + [pallet_indices, Indices] + [pallet_lottery, Lottery] + [pallet_membership, TechnicalMembership] + [pallet_message_queue, MessageQueue] + [pallet_mmr, Mmr] + [pallet_multisig, Multisig] + [pallet_nomination_pools, NominationPoolsBench::] + [pallet_offences, OffencesBench::] + [pallet_preimage, Preimage] + [pallet_proxy, Proxy] + [pallet_ranked_collective, RankedCollective] + [pallet_referenda, Referenda] + [pallet_recovery, Recovery] + [pallet_remark, Remark] + [pallet_scheduler, Scheduler] + [pallet_session, SessionBench::] + [pallet_staking, Staking] + [pallet_state_trie_migration, StateTrieMigration] + [frame_system, SystemBench::] + [pallet_timestamp, Timestamp] + [pallet_tips, Tips] + [pallet_transaction_storage, TransactionStorage] + [pallet_treasury, Treasury] + [pallet_uniques, Uniques] + [pallet_utility, Utility] + [pallet_vesting, Vesting] + [pallet_whitelist, Whitelist] + ); +} + impl_runtime_apis! { impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { @@ -1398,20 +1932,22 @@ impl_runtime_apis! { } } + impl pallet_nomination_pools_runtime_api::NominationPoolsApi for Runtime { + fn pending_rewards(member_account: AccountId) -> Balance { + NominationPools::pending_rewards(member_account).unwrap_or_default() + } + } + impl sp_consensus_babe::BabeApi for Runtime { - fn configuration() -> sp_consensus_babe::BabeGenesisConfiguration { - // The choice of `c` parameter (where `1 - c` represents the - // probability of a slot being empty), is done in accordance to the - // slot duration and expected target block time, for safely - // resisting network delays of maximum two seconds. - // - sp_consensus_babe::BabeGenesisConfiguration { + fn configuration() -> sp_consensus_babe::BabeConfiguration { + let epoch_config = Babe::epoch_config().unwrap_or(BABE_GENESIS_EPOCH_CONFIG); + sp_consensus_babe::BabeConfiguration { slot_duration: Babe::slot_duration(), epoch_length: EpochDuration::get(), - c: BABE_GENESIS_EPOCH_CONFIG.c, - genesis_authorities: Babe::authorities(), + c: epoch_config.c, + authorities: Babe::authorities().to_vec(), randomness: Babe::randomness(), - allowed_slots: BABE_GENESIS_EPOCH_CONFIG.allowed_slots, + allowed_slots: epoch_config.allowed_slots, } } @@ -1463,38 +1999,75 @@ impl_runtime_apis! { } } - impl pallet_contracts_rpc_runtime_api::ContractsApi< - Block, AccountId, Balance, BlockNumber, Hash, - > - for Runtime + impl pallet_contracts::ContractsApi for Runtime { fn call( origin: AccountId, dest: AccountId, value: Balance, - gas_limit: u64, + gas_limit: Option, + storage_deposit_limit: Option, input_data: Vec, - ) -> pallet_contracts_primitives::ContractExecResult { - Contracts::bare_call(origin, dest, value, gas_limit, input_data, true) + ) -> pallet_contracts_primitives::ContractExecResult { + let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); + Contracts::bare_call( + origin, + dest, + value, + gas_limit, + storage_deposit_limit, + input_data, + true, + pallet_contracts::Determinism::Deterministic, + ) } fn instantiate( origin: AccountId, - endowment: Balance, - gas_limit: u64, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, code: pallet_contracts_primitives::Code, data: Vec, salt: Vec, - ) -> pallet_contracts_primitives::ContractInstantiateResult + ) -> pallet_contracts_primitives::ContractInstantiateResult { - Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true) + let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); + Contracts::bare_instantiate( + origin, + value, + gas_limit, + storage_deposit_limit, + code, + data, + salt, + true + ) + } + + fn upload_code( + origin: AccountId, + code: Vec, + storage_deposit_limit: Option, + determinism: pallet_contracts::Determinism, + ) -> pallet_contracts_primitives::CodeUploadResult + { + Contracts::bare_upload_code( + origin, + code, + storage_deposit_limit, + determinism, + ) } fn get_storage( address: AccountId, - key: [u8; 32], + key: Vec, ) -> pallet_contracts_primitives::GetStorageResult { - Contracts::get_storage(address, key) + Contracts::get_storage( + address, + key + ) } } @@ -1510,34 +2083,64 @@ impl_runtime_apis! { } } + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info(call: RuntimeCall, len: u32) -> RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details(call: RuntimeCall, len: u32) -> FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + } + impl pallet_mmr::primitives::MmrApi< Block, mmr::Hash, + BlockNumber, > for Runtime { - fn generate_proof(leaf_index: u64) - -> Result<(mmr::EncodableOpaqueLeaf, mmr::Proof), mmr::Error> - { - Mmr::generate_proof(leaf_index) - .map(|(leaf, proof)| (mmr::EncodableOpaqueLeaf::from_leaf(&leaf), proof)) + fn mmr_root() -> Result { + Ok(Mmr::mmr_root()) + } + + fn mmr_leaf_count() -> Result { + Ok(Mmr::mmr_leaves()) } - fn verify_proof(leaf: mmr::EncodableOpaqueLeaf, proof: mmr::Proof) + fn generate_proof( + block_numbers: Vec, + best_known_block_number: Option, + ) -> Result<(Vec, mmr::Proof), mmr::Error> { + Mmr::generate_proof(block_numbers, best_known_block_number).map( + |(leaves, proof)| { + ( + leaves + .into_iter() + .map(|leaf| mmr::EncodableOpaqueLeaf::from_leaf(&leaf)) + .collect(), + proof, + ) + }, + ) + } + + fn verify_proof(leaves: Vec, proof: mmr::Proof) -> Result<(), mmr::Error> { - let leaf: mmr::Leaf = leaf - .into_opaque_leaf() + let leaves = leaves.into_iter().map(|leaf| + leaf.into_opaque_leaf() .try_decode() - .ok_or(mmr::Error::Verify)?; - Mmr::verify_leaf(leaf, proof) + .ok_or(mmr::Error::Verify)).collect::, mmr::Error>>()?; + Mmr::verify_leaves(leaves, proof) } fn verify_proof_stateless( root: mmr::Hash, - leaf: mmr::EncodableOpaqueLeaf, + leaves: Vec, proof: mmr::Proof ) -> Result<(), mmr::Error> { - let node = mmr::DataOrHash::Data(leaf.into_opaque_leaf()); - pallet_mmr::verify_leaf_proof::(root, node, proof) + let nodes = leaves.into_iter().map(|leaf|mmr::DataOrHash::Data(leaf.into_opaque_leaf())).collect(); + pallet_mmr::verify_leaves_proof::(root, nodes, proof) } } @@ -1555,9 +2158,29 @@ impl_runtime_apis! { #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade() -> Result<(Weight, Weight), sp_runtime::RuntimeString> { - let weight = Executive::try_runtime_upgrade()?; - Ok((weight, RuntimeBlockWeights::get().max_block)) + fn on_runtime_upgrade() -> (Weight, Weight) { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. If any of the pre/post migration checks fail, we shall stop + // right here and right now. + let weight = Executive::try_runtime_upgrade().unwrap(); + (weight, RuntimeBlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + select: frame_try_runtime::TryStateSelect + ) -> Weight { + log::info!( + target: "node-runtime", + "try-runtime: executing block {:?} / root checks: {:?} / try-state-select: {:?}", + block.header.hash(), + state_root_check, + select, + ); + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, select).unwrap() } } @@ -1567,7 +2190,7 @@ impl_runtime_apis! { Vec, Vec, ) { - use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList}; + use frame_benchmarking::{baseline, Benchmarking, BenchmarkList}; use frame_support::traits::StorageInfoTrait; // Trying to add benchmarks directly to the Session Pallet caused cyclic dependency @@ -1575,116 +2198,53 @@ impl_runtime_apis! { // which is why we need these two lines below. use pallet_session_benchmarking::Pallet as SessionBench; use pallet_offences_benchmarking::Pallet as OffencesBench; + use pallet_election_provider_support_benchmarking::Pallet as EPSBench; use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; let mut list = Vec::::new(); - - list_benchmark!(list, extra, pallet_assets, Assets); - list_benchmark!(list, extra, pallet_babe, Babe); - list_benchmark!(list, extra, pallet_balances, Balances); - list_benchmark!(list, extra, pallet_bounties, Bounties); - list_benchmark!(list, extra, pallet_collective, Council); - list_benchmark!(list, extra, pallet_contracts, Contracts); - list_benchmark!(list, extra, pallet_democracy, Democracy); - list_benchmark!(list, extra, pallet_election_provider_multi_phase, ElectionProviderMultiPhase); - list_benchmark!(list, extra, pallet_elections_phragmen, Elections); - list_benchmark!(list, extra, pallet_gilt, Gilt); - list_benchmark!(list, extra, pallet_grandpa, Grandpa); - list_benchmark!(list, extra, pallet_identity, Identity); - list_benchmark!(list, extra, pallet_im_online, ImOnline); - list_benchmark!(list, extra, pallet_indices, Indices); - list_benchmark!(list, extra, pallet_lottery, Lottery); - list_benchmark!(list, extra, pallet_membership, TechnicalMembership); - list_benchmark!(list, extra, pallet_mmr, Mmr); - list_benchmark!(list, extra, pallet_multisig, Multisig); - list_benchmark!(list, extra, pallet_offences, OffencesBench::); - list_benchmark!(list, extra, pallet_proxy, Proxy); - list_benchmark!(list, extra, pallet_scheduler, Scheduler); - list_benchmark!(list, extra, pallet_session, SessionBench::); - list_benchmark!(list, extra, pallet_staking, Staking); - list_benchmark!(list, extra, frame_system, SystemBench::); - list_benchmark!(list, extra, pallet_timestamp, Timestamp); - list_benchmark!(list, extra, pallet_tips, Tips); - list_benchmark!(list, extra, pallet_transaction_storage, TransactionStorage); - list_benchmark!(list, extra, pallet_treasury, Treasury); - list_benchmark!(list, extra, pallet_uniques, Uniques); - list_benchmark!(list, extra, pallet_utility, Utility); - list_benchmark!(list, extra, pallet_vesting, Vesting); + list_benchmarks!(list, extra); let storage_info = AllPalletsWithSystem::storage_info(); - return (list, storage_info) + (list, storage_info) } fn dispatch_benchmark( config: frame_benchmarking::BenchmarkConfig ) -> Result, sp_runtime::RuntimeString> { - use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey}; + use frame_benchmarking::{baseline, Benchmarking, BenchmarkBatch, TrackedStorageKey}; // Trying to add benchmarks directly to the Session Pallet caused cyclic dependency // issues. To get around that, we separated the Session benchmarks into its own crate, // which is why we need these two lines below. use pallet_session_benchmarking::Pallet as SessionBench; use pallet_offences_benchmarking::Pallet as OffencesBench; + use pallet_election_provider_support_benchmarking::Pallet as EPSBench; use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; impl pallet_session_benchmarking::Config for Runtime {} impl pallet_offences_benchmarking::Config for Runtime {} + impl pallet_election_provider_support_benchmarking::Config for Runtime {} impl frame_system_benchmarking::Config for Runtime {} + impl baseline::Config for Runtime {} + impl pallet_nomination_pools_benchmarking::Config for Runtime {} + + use frame_support::traits::WhitelistedStorageKeys; + let mut whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - // System BlockWeight - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96").to_vec().into(), - // Treasury Account - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95ecffd7b6c0f78751baa9d281e0bfa3a6d6f646c70792f74727372790000000000000000000000000000000000000000").to_vec().into(), - ]; + // Treasury Account + // TODO: this is manual for now, someday we might be able to use a + // macro for this particular key + let treasury_key = frame_system::Account::::hashed_key_for(Treasury::account_id()); + whitelist.push(treasury_key.to_vec().into()); let mut batches = Vec::::new(); let params = (&config, &whitelist); - - add_benchmark!(params, batches, pallet_assets, Assets); - add_benchmark!(params, batches, pallet_babe, Babe); - add_benchmark!(params, batches, pallet_balances, Balances); - add_benchmark!(params, batches, pallet_bounties, Bounties); - add_benchmark!(params, batches, pallet_collective, Council); - add_benchmark!(params, batches, pallet_contracts, Contracts); - add_benchmark!(params, batches, pallet_democracy, Democracy); - add_benchmark!(params, batches, pallet_election_provider_multi_phase, ElectionProviderMultiPhase); - add_benchmark!(params, batches, pallet_elections_phragmen, Elections); - add_benchmark!(params, batches, pallet_gilt, Gilt); - add_benchmark!(params, batches, pallet_grandpa, Grandpa); - add_benchmark!(params, batches, pallet_identity, Identity); - add_benchmark!(params, batches, pallet_im_online, ImOnline); - add_benchmark!(params, batches, pallet_indices, Indices); - add_benchmark!(params, batches, pallet_lottery, Lottery); - add_benchmark!(params, batches, pallet_membership, TechnicalMembership); - add_benchmark!(params, batches, pallet_mmr, Mmr); - add_benchmark!(params, batches, pallet_multisig, Multisig); - add_benchmark!(params, batches, pallet_offences, OffencesBench::); - add_benchmark!(params, batches, pallet_proxy, Proxy); - add_benchmark!(params, batches, pallet_scheduler, Scheduler); - add_benchmark!(params, batches, pallet_session, SessionBench::); - add_benchmark!(params, batches, pallet_staking, Staking); - add_benchmark!(params, batches, frame_system, SystemBench::); - add_benchmark!(params, batches, pallet_timestamp, Timestamp); - add_benchmark!(params, batches, pallet_tips, Tips); - add_benchmark!(params, batches, pallet_transaction_storage, TransactionStorage); - add_benchmark!(params, batches, pallet_treasury, Treasury); - add_benchmark!(params, batches, pallet_uniques, Uniques); - add_benchmark!(params, batches, pallet_utility, Utility); - add_benchmark!(params, batches, pallet_vesting, Vesting); - - if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } + add_benchmarks!(params, batches); Ok(batches) } } @@ -1693,14 +2253,51 @@ impl_runtime_apis! { #[cfg(test)] mod tests { use super::*; + use frame_election_provider_support::NposSolution; + use frame_support::traits::WhitelistedStorageKeys; use frame_system::offchain::CreateSignedTransaction; + use sp_core::hexdisplay::HexDisplay; use sp_runtime::UpperOf; + use std::collections::HashSet; + + #[test] + fn check_whitelist() { + let whitelist: HashSet = AllPalletsWithSystem::whitelisted_storage_keys() + .iter() + .map(|e| HexDisplay::from(&e.key).to_string()) + .collect(); + + // Block Number + assert!( + whitelist.contains("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac") + ); + // Total Issuance + assert!( + whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80") + ); + // Execution Phase + assert!( + whitelist.contains("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a") + ); + // Event Count + assert!( + whitelist.contains("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850") + ); + // System Events + assert!( + whitelist.contains("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7") + ); + // System BlockWeight + assert!( + whitelist.contains("26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96") + ); + } #[test] fn validate_transaction_submitter_bounds() { fn is_submit_signed_transaction() where - T: CreateSignedTransaction, + T: CreateSignedTransaction, { } @@ -1709,8 +2306,9 @@ mod tests { #[test] fn perbill_as_onchain_accuracy() { - type OnChainAccuracy = ::Accuracy; - let maximum_chain_accuracy: Vec> = (0..MAX_NOMINATIONS) + type OnChainAccuracy = + <::Solution as NposSolution>::Accuracy; + let maximum_chain_accuracy: Vec> = (0..MaxNominations::get()) .map(|_| >::from(OnChainAccuracy::one().deconstruct())) .collect(); let _: UpperOf = @@ -1719,11 +2317,13 @@ mod tests { #[test] fn call_size() { + let size = core::mem::size_of::(); assert!( - core::mem::size_of::() <= 200, - "size of Call is more than 200 bytes: some calls have too big arguments, use Box to reduce the - size of Call. + size <= 208, + "size of RuntimeCall {} is more than 208 bytes: some calls have too big arguments, use Box to reduce the + size of RuntimeCall. If the limit is too strong, maybe consider increase the limit to 300.", + size, ); } } diff --git a/bin/node/runtime/src/voter_bags.rs b/bin/node/runtime/src/voter_bags.rs new file mode 100644 index 0000000000000..eb540c27abcc7 --- /dev/null +++ b/bin/node/runtime/src/voter_bags.rs @@ -0,0 +1,238 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 bag thresholds. +//! +//! Generated on 2022-08-15T19:26:59.939787+00:00 +//! Arguments +//! Total issuance: 100000000000000 +//! Minimum balance: 100000000000000 +//! for the node runtime. + +/// Existential weight for this runtime. +#[cfg(any(test, feature = "std"))] +#[allow(unused)] +pub const EXISTENTIAL_WEIGHT: u64 = 100_000_000_000_000; + +/// Constant ratio between bags for this runtime. +#[cfg(any(test, feature = "std"))] +#[allow(unused)] +pub const CONSTANT_RATIO: f64 = 1.0628253590743408; + +/// Upper thresholds delimiting the bag list. +pub const THRESHOLDS: [u64; 200] = [ + 100_000_000_000_000, + 106_282_535_907_434, + 112_959_774_389_150, + 120_056_512_776_105, + 127_599_106_300_477, + 135_615_565_971_369, + 144_135_662_599_590, + 153_191_037_357_827, + 162_815_319_286_803, + 173_044_250_183_800, + 183_915_817_337_347, + 195_470_394_601_017, + 207_750_892_330_229, + 220_802_916_738_890, + 234_674_939_267_673, + 249_418_476_592_914, + 265_088_281_944_639, + 281_742_548_444_211, + 299_443_125_216_738, + 318_255_747_080_822, + 338_250_278_668_647, + 359_500_973_883_001, + 382_086_751_654_776, + 406_091_489_025_036, + 431_604_332_640_068, + 458_720_029_816_222, + 487_539_280_404_019, + 518_169_110_758_247, + 550_723_271_202_866, + 585_322_658_466_782, + 622_095_764_659_305, + 661_179_154_452_653, + 702_717_972_243_610, + 746_866_481_177_808, + 793_788_636_038_393, + 843_658_692_126_636, + 896_661_852_395_681, + 952_994_955_240_703, + 1_012_867_205_499_736, + 1_076_500_951_379_881, + 1_144_132_510_194_192, + 1_216_013_045_975_769, + 1_292_409_502_228_280, + 1_373_605_593_276_862, + 1_459_902_857_901_004, + 1_551_621_779_162_291, + 1_649_102_974_585_730, + 1_752_708_461_114_642, + 1_862_822_999_536_805, + 1_979_855_523_374_646, + 2_104_240_657_545_975, + 2_236_440_332_435_128, + 2_376_945_499_368_703, + 2_526_277_953_866_680, + 2_684_992_273_439_945, + 2_853_677_877_130_641, + 3_032_961_214_443_876, + 3_223_508_091_799_862, + 3_426_026_145_146_232, + 3_641_267_467_913_124, + 3_870_031_404_070_482, + 4_113_167_516_660_186, + 4_371_578_742_827_277, + 4_646_224_747_067_156, + 4_938_125_485_141_739, + 5_248_364_991_899_922, + 5_578_095_407_069_235, + 5_928_541_253_969_291, + 6_301_003_987_036_955, + 6_696_866_825_051_405, + 7_117_599_888_008_300, + 7_564_765_656_719_910, + 8_040_024_775_416_580, + 8_545_142_218_898_723, + 9_081_993_847_142_344, + 9_652_573_371_700_016, + 10_258_999_759_768_490, + 10_903_525_103_419_522, + 11_588_542_983_217_942, + 12_316_597_357_287_042, + 13_090_392_008_832_678, + 13_912_800_587_211_472, + 14_786_877_279_832_732, + 15_715_868_154_526_436, + 16_703_223_214_499_558, + 17_752_609_210_649_358, + 18_867_923_258_814_856, + 20_053_307_312_537_008, + 21_313_163_545_075_252, + 22_652_170_697_804_756, + 24_075_301_455_707_600, + 25_587_840_914_485_432, + 27_195_406_207_875_088, + 28_903_967_368_057_400, + 30_719_869_496_628_636, + 32_649_856_328_471_220, + 34_701_095_276_033_064, + 36_881_204_047_022_752, + 39_198_278_934_370_992, + 41_660_924_883_519_016, + 44_278_287_448_695_240, + 47_060_086_756_856_400, + 50_016_653_605_425_536, + 53_158_967_827_883_320, + 56_498_699_069_691_424, + 60_048_250_125_977_912, + 63_820_803_001_928_304, + 67_830_367_866_937_216, + 72_091_835_084_322_176, + 76_621_030_509_822_880, + 81_434_774_264_248_528, + 86_550_943_198_537_824, + 91_988_537_283_208_848, + 97_767_750_168_749_840, + 103_910_044_178_992_000, + 110_438_230_015_967_792, + 117_376_551_472_255_616, + 124_750_775_465_407_920, + 132_588_287_728_824_640, + 140_918_194_514_440_064, + 149_771_430_684_917_568, + 159_180_874_596_775_264, + 169_181_470_201_085_280, + 179_810_356_815_193_344, + 191_107_007_047_393_216, + 203_113_373_386_768_288, + 215_874_044_002_592_672, + 229_436_408_331_885_600, + 243_850_833_070_063_392, + 259_170_849_218_267_264, + 275_453_350_882_006_752, + 292_758_806_559_399_232, + 311_151_483_703_668_992, + 330_699_687_393_865_920, + 351_476_014_000_157_824, + 373_557_620_785_735_808, + 397_026_512_446_556_096, + 421_969_845_653_044_224, + 448_480_252_724_740_928, + 476_656_185_639_923_904, + 506_602_281_657_757_760, + 538_429_751_910_786_752, + 572_256_794_410_890_176, + 608_209_033_002_485_632, + 646_419_983_893_124_352, + 687_031_551_494_039_552, + 730_194_555_412_054_016, + 776_069_290_549_944_960, + 824_826_122_395_314_176, + 876_646_119_708_695_936, + 931_721_726_960_522_368, + 990_257_479_014_182_144, + 1_052_470_760_709_299_712, + 1_118_592_614_166_106_112, + 1_188_868_596_808_997_376, + 1_263_559_693_295_730_432, + 1_342_943_284_738_898_688, + 1_427_314_178_819_094_784, + 1_516_985_704_615_302_400, + 1_612_290_876_218_400_768, + 1_713_583_629_449_105_408, + 1_821_240_136_273_157_632, + 1_935_660_201_795_120_128, + 2_057_268_749_018_809_600, + 2_186_517_396_888_336_384, + 2_323_886_137_470_138_880, + 2_469_885_118_504_583_168, + 2_625_056_537_947_004_416, + 2_789_976_657_533_970_944, + 2_965_257_942_852_572_160, + 3_151_551_337_860_326_400, + 3_349_548_682_302_620_672, + 3_559_985_281_005_267_968, + 3_783_642_634_583_792_128, + 4_021_351_341_710_503_936, + 4_273_994_183_717_548_544, + 4_542_509_402_991_247_872, + 4_827_894_187_332_742_144, + 5_131_208_373_224_844_288, + 5_453_578_381_757_959_168, + 5_796_201_401_831_965_696, + 6_160_349_836_169_256_960, + 6_547_376_026_650_146_816, + 6_958_717_276_519_173_120, + 7_395_901_188_113_309_696, + 7_860_551_335_934_872_576, + 8_354_393_296_137_270_272, + 8_879_261_054_815_360_000, + 9_437_103_818_898_946_048, + 10_029_993_254_943_105_024, + 10_660_131_182_698_121_216, + 11_329_857_752_030_707_712, + 12_041_660_133_563_240_448, + 12_798_181_755_305_525_248, + 13_602_232_119_581_272_064, + 14_456_797_236_706_498_560, + 15_365_050_714_167_523_328, + 16_330_365_542_480_556_032, + 17_356_326_621_502_140_416, + 18_446_744_073_709_551_615, +]; diff --git a/bin/node/test-runner-example/Cargo.toml b/bin/node/test-runner-example/Cargo.toml deleted file mode 100644 index 96c4c2047ac4f..0000000000000 --- a/bin/node/test-runner-example/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "test-runner-example" -version = "0.1.0" -authors = ["Parity Technologies "] -edition = "2018" -publish = false - -[dependencies] -test-runner = { path = "../../../test-utils/test-runner" } - -frame-system = { path = "../../../frame/system" } -frame-benchmarking = { path = "../../../frame/benchmarking" } -pallet-transaction-payment = { path = "../../../frame/transaction-payment" } - -node-runtime = { path = "../runtime" } -node-primitives = { path = "../primitives" } -node-cli = { path = "../cli" } - -grandpa = { package = "sc-finality-grandpa", path = "../../../client/finality-grandpa" } -sp-consensus-babe = { path = "../../../primitives/consensus/babe" } -sc-consensus-babe = { path = "../../../client/consensus/babe" } -sc-consensus-manual-seal = { path = "../../../client/consensus/manual-seal" } -sc-service = { default-features = false, path = "../../../client/service" } -sc-executor = { path = "../../../client/executor" } -sc-consensus = { path = "../../../client/consensus/common" } - -sp-runtime = { path = "../../../primitives/runtime" } -sp-keyring = { path = "../../../primitives/keyring" } diff --git a/bin/node/test-runner-example/src/lib.rs b/bin/node/test-runner-example/src/lib.rs deleted file mode 100644 index 0de7f5a4e2b70..0000000000000 --- a/bin/node/test-runner-example/src/lib.rs +++ /dev/null @@ -1,131 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . -#![deny(unused_extern_crates, missing_docs)] - -//! Basic example of end to end runtime tests. - -use grandpa::GrandpaBlockImport; -use sc_consensus_babe::BabeBlockImport; -use sc_consensus_manual_seal::consensus::babe::SlotTimestampProvider; -use sc_executor::NativeElseWasmExecutor; -use sc_service::{TFullBackend, TFullClient}; -use sp_runtime::generic::Era; -use test_runner::{ChainInfo, SignatureVerificationOverride}; - -type BlockImport = BabeBlockImport>; - -/// A unit struct which implements `NativeExecutionDispatch` feeding in the -/// hard-coded runtime. -pub struct ExecutorDispatch; - -impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { - type ExtendHostFunctions = - (frame_benchmarking::benchmarking::HostFunctions, SignatureVerificationOverride); - - fn dispatch(method: &str, data: &[u8]) -> Option> { - node_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - node_runtime::native_version() - } -} - -/// ChainInfo implementation. -struct NodeTemplateChainInfo; - -impl ChainInfo for NodeTemplateChainInfo { - type Block = node_primitives::Block; - type ExecutorDispatch = ExecutorDispatch; - type Runtime = node_runtime::Runtime; - type RuntimeApi = node_runtime::RuntimeApi; - type SelectChain = sc_consensus::LongestChain, Self::Block>; - type BlockImport = BlockImport< - Self::Block, - TFullBackend, - TFullClient>, - Self::SelectChain, - >; - type SignedExtras = node_runtime::SignedExtra; - type InherentDataProviders = - (SlotTimestampProvider, sp_consensus_babe::inherents::InherentDataProvider); - - fn signed_extras( - from: ::AccountId, - ) -> Self::SignedExtras { - ( - frame_system::CheckSpecVersion::::new(), - frame_system::CheckTxVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckMortality::::from(Era::Immortal), - frame_system::CheckNonce::::from( - frame_system::Pallet::::account_nonce(from), - ), - frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(0), - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use node_cli::chain_spec::development_config; - use sp_keyring::sr25519::Keyring::Alice; - use sp_runtime::{traits::IdentifyAccount, MultiSigner}; - use test_runner::{build_runtime, client_parts, ConfigOrChainSpec, Node}; - - #[test] - fn test_runner() { - let tokio_runtime = build_runtime().unwrap(); - let (rpc, task_manager, client, pool, command_sink, backend) = - client_parts::(ConfigOrChainSpec::ChainSpec( - Box::new(development_config()), - tokio_runtime.handle().clone(), - )) - .unwrap(); - let node = Node::::new( - rpc, - task_manager, - client, - pool, - command_sink, - backend, - ); - - tokio_runtime.block_on(async { - // seals blocks - node.seal_blocks(1).await; - // submit extrinsics - let alice = MultiSigner::from(Alice.public()).into_account(); - let _hash = node - .submit_extrinsic( - frame_system::Call::remark { remark: (b"hello world").to_vec() }, - Some(alice), - ) - .await - .unwrap(); - - // look ma, I can read state. - let _events = - node.with_state(|| frame_system::Pallet::::events()); - // get access to the underlying client. - let _client = node.client(); - }) - } -} diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index d05d815121f88..0154a778457fc 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -3,48 +3,45 @@ name = "node-testing" version = "3.0.0-dev" authors = ["Parity Technologies "] description = "Test utilities for Substrate node." -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -publish = true +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sc-service = { version = "0.10.0-dev", features = [ - "test-helpers", - "db", -], path = "../../../client/service" } -sc-client-db = { version = "0.10.0-dev", path = "../../../client/db/", features = [ - "kvdb-rocksdb", - "parity-db", -] } -sc-client-api = { version = "4.0.0-dev", path = "../../../client/api/" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -codec = { package = "parity-scale-codec", version = "2.0.0" } -sp-keyring = { version = "4.0.0-dev", path = "../../../primitives/keyring" } +codec = { package = "parity-scale-codec", version = "3.0.0" } +fs_extra = "1" +futures = "0.3.21" +log = "0.4.17" +tempfile = "3.1.0" +frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } node-executor = { version = "3.0.0-dev", path = "../executor" } node-primitives = { version = "2.0.0", path = "../primitives" } -node-runtime = { version = "3.0.0-dev", path = "../runtime" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-io = { version = "4.0.0-dev", path = "../../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sc-executor = { version = "0.10.0-dev", path = "../../../client/executor", features = [ - "wasmtime", -] } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } -substrate-test-client = { version = "2.0.0", path = "../../../test-utils/client" } +kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } +pallet-assets = { version = "4.0.0-dev", path = "../../../frame/assets" } +pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } +sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } +sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } +sc-client-db = { version = "0.10.0-dev", features = ["rocksdb"], path = "../../../client/db" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } +sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } +sc-service = { version = "0.10.0-dev", features = [ + "test-helpers", + "rocksdb", +], path = "../../../client/service" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/timestamp" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } -sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } -sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -log = "0.4.8" -tempfile = "3.1.0" -fs_extra = "1" -futures = "0.3.1" +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/timestamp" } +substrate-test-client = { version = "2.0.0", path = "../../../test-utils/client" } diff --git a/bin/node/testing/src/bench.rs b/bin/node/testing/src/bench.rs index cf0a463cc3e99..59f1fa94c9b20 100644 --- a/bin/node/testing/src/bench.rs +++ b/bin/node/testing/src/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -34,11 +34,11 @@ use crate::{ }; use codec::{Decode, Encode}; use futures::executor; -use node_primitives::Block; -use node_runtime::{ - constants::currency::DOLLARS, AccountId, BalancesCall, Call, CheckedExtrinsic, MinimumPeriod, - Signature, SystemCall, UncheckedExtrinsic, +use kitchensink_runtime::{ + constants::currency::DOLLARS, AccountId, BalancesCall, CheckedExtrinsic, MinimumPeriod, + RuntimeCall, Signature, SystemCall, UncheckedExtrinsic, }; +use node_primitives::Block; use sc_block_builder::BlockBuilderProvider; use sc_client_api::{ execution_extensions::{ExecutionExtensions, ExecutionStrategies}, @@ -46,7 +46,7 @@ use sc_client_api::{ }; use sc_client_db::PruningMode; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, ImportedAux}; -use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod}; +use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod, WasmtimeInstantiationStrategy}; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; use sp_consensus::BlockOrigin; @@ -243,11 +243,21 @@ impl TaskExecutor { } impl SpawnNamed for TaskExecutor { - fn spawn(&self, _: &'static str, future: futures::future::BoxFuture<'static, ()>) { + fn spawn( + &self, + _: &'static str, + _: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { self.pool.spawn_ok(future); } - fn spawn_blocking(&self, _: &'static str, future: futures::future::BoxFuture<'static, ()>) { + fn spawn_blocking( + &self, + _: &'static str, + _: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { self.pool.spawn_ok(future); } } @@ -270,8 +280,7 @@ impl<'a> BlockContentIterator<'a> { let genesis_hash = client .block_hash(Zero::zero()) .expect("Database error?") - .expect("Genesis block always exists; qed") - .into(); + .expect("Genesis block always exists; qed"); BlockContentIterator { iteration: 0, content, keyring, runtime_version, genesis_hash } } @@ -295,23 +304,25 @@ impl<'a> Iterator for BlockContentIterator<'a> { CheckedExtrinsic { signed: Some(( sender, - signed_extra(0, node_runtime::ExistentialDeposit::get() + 1), + signed_extra(0, kitchensink_runtime::ExistentialDeposit::get() + 1), )), function: match self.content.block_type { BlockType::RandomTransfersKeepAlive => - Call::Balances(BalancesCall::transfer_keep_alive { + RuntimeCall::Balances(BalancesCall::transfer_keep_alive { dest: sp_runtime::MultiAddress::Id(receiver), - value: node_runtime::ExistentialDeposit::get() + 1, + value: kitchensink_runtime::ExistentialDeposit::get() + 1, }), BlockType::RandomTransfersReaping => { - Call::Balances(BalancesCall::transfer { + RuntimeCall::Balances(BalancesCall::transfer { dest: sp_runtime::MultiAddress::Id(receiver), // Transfer so that ending balance would be 1 less than existential // deposit so that we kill the sender account. - value: 100 * DOLLARS - (node_runtime::ExistentialDeposit::get() - 1), + value: 100 * DOLLARS - + (kitchensink_runtime::ExistentialDeposit::get() - 1), }) }, - BlockType::Noop => Call::System(SystemCall::remark { remark: Vec::new() }), + BlockType::Noop => + RuntimeCall::System(SystemCall::remark { remark: Vec::new() }), }, }, self.runtime_version.spec_version, @@ -378,19 +389,24 @@ impl BenchDb { keyring: &BenchKeyring, ) -> (Client, std::sync::Arc, TaskExecutor) { let db_config = sc_client_db::DatabaseSettings { - state_cache_size: 16 * 1024 * 1024, - state_cache_child_ratio: Some((0, 100)), - state_pruning: PruningMode::ArchiveAll, + trie_cache_maximum_size: Some(16 * 1024 * 1024), + state_pruning: Some(PruningMode::ArchiveAll), source: database_type.into_settings(dir.into()), - keep_blocks: sc_client_db::KeepBlocks::All, - transaction_storage: sc_client_db::TransactionStorageMode::BlockBody, + blocks_pruning: sc_client_db::BlocksPruning::KeepAll, }; let task_executor = TaskExecutor::new(); let backend = sc_service::new_db_backend(db_config).expect("Should not fail"); let client = sc_service::new_client( backend.clone(), - NativeElseWasmExecutor::new(WasmExecutionMethod::Compiled, None, 8), + NativeElseWasmExecutor::new( + WasmExecutionMethod::Compiled { + instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + }, + None, + 8, + 2, + ), &keyring.generate_genesis(), None, None, @@ -560,15 +576,13 @@ impl BenchKeyring { genesis_hash, ); let key = self.accounts.get(&signed).expect("Account id not found in keyring"); - let signature = payload - .using_encoded(|b| { - if b.len() > 256 { - key.sign(&sp_io::hashing::blake2_256(b)) - } else { - key.sign(b) - } - }) - .into(); + let signature = payload.using_encoded(|b| { + if b.len() > 256 { + key.sign(&sp_io::hashing::blake2_256(b)) + } else { + key.sign(b) + } + }); UncheckedExtrinsic { signature: Some((sp_runtime::MultiAddress::Id(signed), signature, extra)), function: payload.0, @@ -579,10 +593,9 @@ impl BenchKeyring { } /// Generate genesis with accounts from this keyring endowed with some balance. - pub fn generate_genesis(&self) -> node_runtime::GenesisConfig { + pub fn generate_genesis(&self) -> kitchensink_runtime::GenesisConfig { crate::genesis::config_endowed( - false, - Some(node_runtime::wasm_binary_unwrap()), + Some(kitchensink_runtime::wasm_binary_unwrap()), self.collect_account_ids(), ) } diff --git a/bin/node/testing/src/client.rs b/bin/node/testing/src/client.rs index 8bd75834c5496..590304bdd52a5 100644 --- a/bin/node/testing/src/client.rs +++ b/bin/node/testing/src/client.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,14 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Utilities to build a `TestClient` for `node-runtime`. +//! Utilities to build a `TestClient` for `kitchensink-runtime`. use sc_service::client; use sp_runtime::BuildStorage; /// Re-export test-client utilities. pub use substrate_test_client::*; -/// Call executor for `node-runtime` `TestClient`. +/// Call executor for `kitchensink-runtime` `TestClient`. pub type ExecutorDispatch = sc_executor::NativeElseWasmExecutor; /// Default backend type. @@ -34,21 +34,19 @@ pub type Client = client::Client< Backend, client::LocalCallExecutor, node_primitives::Block, - node_runtime::RuntimeApi, + kitchensink_runtime::RuntimeApi, >; -/// Transaction for node-runtime. +/// Transaction for kitchensink-runtime. pub type Transaction = sc_client_api::backend::TransactionFor; /// Genesis configuration parameters for `TestClient`. #[derive(Default)] -pub struct GenesisParameters { - support_changes_trie: bool, -} +pub struct GenesisParameters; impl substrate_test_client::GenesisInit for GenesisParameters { fn genesis_storage(&self) -> Storage { - crate::genesis::config(self.support_changes_trie, None).build_storage().unwrap() + crate::genesis::config(None).build_storage().unwrap() } } diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index 50c1e6f9d20be..b207fc7f98ab4 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,27 +19,22 @@ //! Genesis Configuration. use crate::keyring::*; -use node_runtime::{ - constants::currency::*, wasm_binary_unwrap, AccountId, BabeConfig, BalancesConfig, - GenesisConfig, GrandpaConfig, IndicesConfig, SessionConfig, SocietyConfig, StakerStatus, - StakingConfig, SystemConfig, BABE_GENESIS_EPOCH_CONFIG, +use kitchensink_runtime::{ + constants::currency::*, wasm_binary_unwrap, AccountId, AssetsConfig, BabeConfig, + BalancesConfig, GenesisConfig, GrandpaConfig, IndicesConfig, SessionConfig, SocietyConfig, + StakerStatus, StakingConfig, SystemConfig, BABE_GENESIS_EPOCH_CONFIG, }; -use sp_core::ChangesTrieConfiguration; use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; use sp_runtime::Perbill; /// Create genesis runtime configuration for tests. -pub fn config(support_changes_trie: bool, code: Option<&[u8]>) -> GenesisConfig { - config_endowed(support_changes_trie, code, Default::default()) +pub fn config(code: Option<&[u8]>) -> GenesisConfig { + config_endowed(code, Default::default()) } /// Create genesis runtime configuration for tests with some extra /// endowed accounts. -pub fn config_endowed( - support_changes_trie: bool, - code: Option<&[u8]>, - extra_endowed: Vec, -) -> GenesisConfig { +pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> GenesisConfig { let mut endowed = vec![ (alice(), 111 * DOLLARS), (bob(), 100 * DOLLARS), @@ -53,22 +48,17 @@ pub fn config_endowed( GenesisConfig { system: SystemConfig { - changes_trie_config: if support_changes_trie { - Some(ChangesTrieConfiguration { digest_interval: 2, digest_levels: 2 }) - } else { - None - }, code: code.map(|x| x.to_vec()).unwrap_or_else(|| wasm_binary_unwrap().to_vec()), }, indices: IndicesConfig { indices: vec![] }, balances: BalancesConfig { balances: endowed }, session: SessionConfig { keys: vec![ - (dave(), alice(), to_session_keys(&Ed25519Keyring::Alice, &Sr25519Keyring::Alice)), - (eve(), bob(), to_session_keys(&Ed25519Keyring::Bob, &Sr25519Keyring::Bob)), + (alice(), dave(), to_session_keys(&Ed25519Keyring::Alice, &Sr25519Keyring::Alice)), + (bob(), eve(), to_session_keys(&Ed25519Keyring::Bob, &Sr25519Keyring::Bob)), ( - ferdie(), charlie(), + ferdie(), to_session_keys(&Ed25519Keyring::Charlie, &Sr25519Keyring::Charlie), ), ], @@ -98,7 +88,11 @@ pub fn config_endowed( treasury: Default::default(), society: SocietyConfig { members: vec![alice(), bob()], pot: 0, max_members: 999 }, vesting: Default::default(), - gilt: Default::default(), + assets: AssetsConfig { assets: vec![(9, alice(), true, 1)], ..Default::default() }, transaction_storage: Default::default(), + transaction_payment: Default::default(), + alliance: Default::default(), + alliance_motion: Default::default(), + nomination_pools: Default::default(), } } diff --git a/bin/node/testing/src/keyring.rs b/bin/node/testing/src/keyring.rs index 4e2d88b4bba33..88d9dc56b0532 100644 --- a/bin/node/testing/src/keyring.rs +++ b/bin/node/testing/src/keyring.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,8 +19,8 @@ //! Test accounts. use codec::Encode; +use kitchensink_runtime::{CheckedExtrinsic, SessionKeys, SignedExtra, UncheckedExtrinsic}; use node_primitives::{AccountId, Balance, Index}; -use node_runtime::{CheckedExtrinsic, SessionKeys, SignedExtra, UncheckedExtrinsic}; use sp_keyring::{AccountKeyring, Ed25519Keyring, Sr25519Keyring}; use sp_runtime::generic::Era; @@ -70,13 +70,14 @@ pub fn to_session_keys( /// Returns transaction extra. pub fn signed_extra(nonce: Index, extra_fee: Balance) -> SignedExtra { ( + frame_system::CheckNonZeroSender::new(), frame_system::CheckSpecVersion::new(), frame_system::CheckTxVersion::new(), frame_system::CheckGenesis::new(), frame_system::CheckEra::from(Era::mortal(256, 0)), frame_system::CheckNonce::from(nonce), frame_system::CheckWeight::new(), - pallet_transaction_payment::ChargeTransactionPayment::from(extra_fee), + pallet_asset_tx_payment::ChargeAssetTxPayment::from(extra_fee, None), ) } diff --git a/bin/node/testing/src/lib.rs b/bin/node/testing/src/lib.rs index a3392bcb29d5d..ce3471e5b1f83 100644 --- a/bin/node/testing/src/lib.rs +++ b/bin/node/testing/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/bin/utils/chain-spec-builder/Cargo.toml b/bin/utils/chain-spec-builder/Cargo.toml index 5bdf01badc3f4..e1d720f673aea 100644 --- a/bin/utils/chain-spec-builder/Cargo.toml +++ b/bin/utils/chain-spec-builder/Cargo.toml @@ -2,10 +2,10 @@ name = "chain-spec-builder" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" build = "build.rs" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" publish = false @@ -15,10 +15,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] ansi_term = "0.12.1" -sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } +clap = { version = "4.0.9", features = ["derive"] } +rand = "0.8" node-cli = { version = "3.0.0-dev", path = "../../node/cli" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", path = "../../../primitives/keystore" } -rand = "0.7.2" -structopt = "0.3.8" +sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } +sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } diff --git a/bin/utils/chain-spec-builder/build.rs b/bin/utils/chain-spec-builder/build.rs index 57424f016f3e5..b700b28e322ca 100644 --- a/bin/utils/chain-spec-builder/build.rs +++ b/bin/utils/chain-spec-builder/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/bin/utils/chain-spec-builder/src/main.rs b/bin/utils/chain-spec-builder/src/main.rs index bf5f1a149578e..7dfcaed773f40 100644 --- a/bin/utils/chain-spec-builder/src/main.rs +++ b/bin/utils/chain-spec-builder/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,65 +23,65 @@ use std::{ }; use ansi_term::Style; +use clap::Parser; use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; -use structopt::StructOpt; use node_cli::chain_spec::{self, AccountId}; use sc_keystore::LocalKeystore; use sp_core::{ - crypto::{Public, Ss58Codec}, + crypto::{ByteArray, Ss58Codec}, sr25519, }; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; /// A utility to easily create a testnet chain spec definition with a given set /// of authorities and endowed accounts and/or generate random accounts. -#[derive(StructOpt)] -#[structopt(rename_all = "kebab-case")] +#[derive(Parser)] +#[command(rename_all = "kebab-case")] enum ChainSpecBuilder { /// Create a new chain spec with the given authorities, endowed and sudo /// accounts. New { /// Authority key seed. - #[structopt(long, short, required = true)] + #[arg(long, short, required = true)] authority_seeds: Vec, /// Active nominators (SS58 format), each backing a random subset of the aforementioned /// authorities. - #[structopt(long, short, default_value = "0")] + #[arg(long, short, default_value = "0")] nominator_accounts: Vec, /// Endowed account address (SS58 format). - #[structopt(long, short)] + #[arg(long, short)] endowed_accounts: Vec, /// Sudo account address (SS58 format). - #[structopt(long, short)] + #[arg(long, short)] sudo_account: String, /// The path where the chain spec should be saved. - #[structopt(long, short, default_value = "./chain_spec.json")] + #[arg(long, short, default_value = "./chain_spec.json")] chain_spec_path: PathBuf, }, /// Create a new chain spec with the given number of authorities and endowed /// accounts. Random keys will be generated as required. Generate { /// The number of authorities. - #[structopt(long, short)] + #[arg(long, short)] authorities: usize, /// The number of nominators backing the aforementioned authorities. /// /// Will nominate a random subset of `authorities`. - #[structopt(long, short, default_value = "0")] + #[arg(long, short, default_value_t = 0)] nominators: usize, /// The number of endowed accounts. - #[structopt(long, short, default_value = "0")] + #[arg(long, short, default_value_t = 0)] endowed: usize, /// The path where the chain spec should be saved. - #[structopt(long, short, default_value = "./chain_spec.json")] + #[arg(long, short, default_value = "./chain_spec.json")] chain_spec_path: PathBuf, /// Path to use when saving generated keystores for each authority. /// /// At this path, a new folder will be created for each authority's /// keystore named `auth-$i` where `i` is the authority index, i.e. /// `auth-0`, `auth-1`, etc. - #[structopt(long, short)] + #[arg(long, short)] keystore_path: Option, }, } @@ -155,14 +155,15 @@ fn generate_chain_spec( None, None, None, + None, Default::default(), ); - chain_spec.as_json(false).map_err(|err| err) + chain_spec.as_json(false) } fn generate_authority_keys_and_store(seeds: &[String], keystore_path: &Path) -> Result<(), String> { - for (n, seed) in seeds.into_iter().enumerate() { + for (n, seed) in seeds.iter().enumerate() { let keystore: SyncCryptoStorePtr = Arc::new( LocalKeystore::open(keystore_path.join(format!("auth-{}", n)), None) .map_err(|err| err.to_string())?, @@ -235,13 +236,15 @@ fn main() -> Result<(), String> { the chain spec builder binary in `--release` mode.\n", ); - let builder = ChainSpecBuilder::from_args(); + let builder = ChainSpecBuilder::parse(); let chain_spec_path = builder.chain_spec_path().to_path_buf(); let (authority_seeds, nominator_accounts, endowed_accounts, sudo_account) = match builder { ChainSpecBuilder::Generate { authorities, nominators, endowed, keystore_path, .. } => { let authorities = authorities.max(1); - let rand_str = || -> String { OsRng.sample_iter(&Alphanumeric).take(32).collect() }; + let rand_str = || -> String { + OsRng.sample_iter(&Alphanumeric).take(32).map(char::from).collect() + }; let authority_seeds = (0..authorities).map(|_| rand_str()).collect::>(); let nominator_seeds = (0..nominators).map(|_| rand_str()).collect::>(); diff --git a/bin/utils/subkey/Cargo.toml b/bin/utils/subkey/Cargo.toml index 9bd38a21a664b..77c323821a508 100644 --- a/bin/utils/subkey/Cargo.toml +++ b/bin/utils/subkey/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "subkey" -version = "2.0.1" +version = "2.0.2" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" publish = false @@ -17,5 +17,5 @@ path = "src/main.rs" name = "subkey" [dependencies] +clap = { version = "4.0.9", features = ["derive"] } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } -structopt = "0.3.14" diff --git a/bin/utils/subkey/README.md b/bin/utils/subkey/README.md index fbb486247a770..e762a42c3e79e 100644 --- a/bin/utils/subkey/README.md +++ b/bin/utils/subkey/README.md @@ -18,15 +18,37 @@ If you save any output of `subkey` into a file, make sure to apply proper permis The following guide explains *some* of the `subkey` commands. For the full list and the most up to date documentation, make sure to check the integrated help with `subkey --help`. +### Install with Cargo + +You will need to have the Substrate build dependencies to install Subkey. Use the following two commands to install the dependencies and Subkey, respectively: + +Command: + +```bash +# Use the `--fast` flag to get the dependencies without needing to install the Substrate and Subkey binary +curl https://getsubstrate.io -sSf | bash -s -- --fast +# Install only `subkey`, at a specific version of the subkey crate +cargo install --force subkey --git https://github.com/paritytech/substrate --version --locked +``` + +### Run in a container + +```bash +# Use `--pull=always` with the `latest` tag, or specify a version in a tag +docker run -it --pull=always docker.io/parity/subkey:latest +``` + ### Generate a random account Generating a new key is as simple as running: - subkey generate +```bash +subkey generate +``` The output looks similar to: -``` +```text Secret phrase `hotel forest jar hover kite book view eight stuff angle legend defense` is account: Secret seed: 0xa05c75731970cc7868a2fb7cb577353cd5b31f62dccced92c441acd8fee0c92d Public key (hex): 0xfec70cfbf1977c6965b5af10a4534a6a35d548eb14580594d0bc543286892515 @@ -47,9 +69,10 @@ The output above also show the **public key** and the **Account ID**. Those are The **SS58 address** (or **Public Address**) of a new account is a reprensentation of the public keys of an account for a given network (for instance Kusama or Polkadot). -You can read more about the SS58 format in the [substrate wiki](https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58)) and see the list of reserved prefixes in the [Polkadot wiki](https://wiki.polkadot.network/docs/build-ss58-registry). +You can read more about the SS58 format in the [Substrate Docs](https://docs.substrate.io/v3/advanced/ss58/) and see the list of reserved prefixes in the [SS58 Registry](https://github.com/paritytech/ss58-registry). For instance, considering the previous seed `0xa05c75731970cc7868a2fb7cb577353cd5b31f62dccced92c441acd8fee0c92d` the SS58 addresses are: + - Polkadot: `16m4J167Mptt8UXL8aGSAi7U2FnPpPxZHPrCgMG9KJzVoFqM` - Kusama: `JLNozAv8QeLSbLFwe2UvWeKKE4yvmDbfGxTuiYkF2BUMx4M` @@ -58,12 +81,14 @@ For instance, considering the previous seed `0xa05c75731970cc7868a2fb7cb577353cd `subkey` can calso generate the output as *json*. This is useful for automation. command: -``` + +```bash subkey generate --output-type json ``` output: -``` + +```json { "accountId": "0xfec70cfbf1977c6965b5af10a4534a6a35d548eb14580594d0bc543286892515", "publicKey": "0xfec70cfbf1977c6965b5af10a4534a6a35d548eb14580594d0bc543286892515", @@ -76,12 +101,14 @@ output: So if you only want to get the `secretSeed` for instance, you can use: command: -``` + +```bash subkey generate --output-type json | jq -r .secretSeed ``` output: -``` + +```text 0xa05c75731970cc7868a2fb7cb577353cd5b31f62dccced92c441acd8fee0c92d ``` @@ -89,10 +116,13 @@ output: `subkey` supports an additional user-defined secret that will be appended to the seed. Let's see the following example: - subkey generate --password extra_secret +```bash +subkey generate --password extra_secret +``` output: -``` + +```text Secret phrase `soup lyrics media market way crouch elevator put moon useful question wide` is account: Secret seed: 0xe7cfd179d6537a676cb94bac3b5c5c9cb1550e846ac4541040d077dfbac2e7fd Public key (hex): 0xf6a233c3e1de1a2ae0486100b460b3ce3d7231ddfe9dadabbd35ab968c70905d @@ -102,11 +132,15 @@ Secret phrase `soup lyrics media market way crouch elevator put moon useful ques Using the `inspect` command (see more details below), we see that knowning only the **secret seed** is no longer sufficient to recover the account: - subkey inspect "soup lyrics media market way crouch elevator put moon useful question wide" +```bash +subkey inspect "soup lyrics media market way crouch elevator put moon useful question wide" +``` which recovers the account `5Fe4sqj2K4fRuzEGvToi4KATqZfiDU7TqynjXG6PZE2dxwyh` and not `5He5pZpc7AJ8evPuab37vJF6KkFDqq9uDq2WXh877Qw6iaVC` as we expected. The additional user-defined **password** (`extra_secret` in our example) is now required to fully recover the account. Let's inspect the the previous mnemonic, this time passing also the required `password` as shown below: - subkey inspect --password extra_secret "soup lyrics media market way crouch elevator put moon useful question wide" +```bash +subkey inspect --password extra_secret "soup lyrics media market way crouch elevator put moon useful question wide" +``` This time, we properly recovered `5He5pZpc7AJ8evPuab37vJF6KkFDqq9uDq2WXh877Qw6iaVC`. @@ -116,23 +150,29 @@ If you have *some data* about a key, `subkey inpsect` will help you discover mor If you have **secrets** that you would like to verify for instance, you can use: - subkey inspect < mnemonic | seed > +```bash +subkey inspect < mnemonic | seed > +``` If you have only **public data**, you can see a subset of the information: - subkey inspect --public < pubkey | address > +```bash +subkey inspect --public < pubkey | address > +``` **NOTE**: While you will be able to recover the secret seed from the mnemonic, the opposite is not possible. **NOTE**: For obvious reasons, the **secrets** cannot be recovered from passing **public data** such as `pubkey` or `address` as input. command: -``` + +```bash subkey inspect 0xa05c75731970cc7868a2fb7cb577353cd5b31f62dccced92c441acd8fee0c92d ``` output: -``` + +```text Secret Key URI `0xa05c75731970cc7868a2fb7cb577353cd5b31f62dccced92c441acd8fee0c92d` is account: Secret seed: 0xa05c75731970cc7868a2fb7cb577353cd5b31f62dccced92c441acd8fee0c92d Public key (hex): 0xfec70cfbf1977c6965b5af10a4534a6a35d548eb14580594d0bc543286892515 @@ -144,17 +184,23 @@ Secret Key URI `0xa05c75731970cc7868a2fb7cb577353cd5b31f62dccced92c441acd8fee0c9 `subkey` allows using a **secret key** to sign a random message. The signature can then be verified by anyone using your **public key**: - echo -n | subkey sign --suri +```bash +echo -n | subkey sign --suri +``` example: - MESSAGE=hello - SURI=0xa05c75731970cc7868a2fb7cb577353cd5b31f62dccced92c441acd8fee0c92d - echo -n $MESSAGE | subkey sign --suri $SURI +```text +MESSAGE=hello +SURI=0xa05c75731970cc7868a2fb7cb577353cd5b31f62dccced92c441acd8fee0c92d +echo -n $MESSAGE | subkey sign --suri $SURI +``` output: - 9201af3788ad4f986b800853c79da47155f2e08fde2070d866be4c27ab060466fea0623dc2b51f4392f4c61f25381a62848dd66c5d8217fae3858e469ebd668c +```text +9201af3788ad4f986b800853c79da47155f2e08fde2070d866be4c27ab060466fea0623dc2b51f4392f4c61f25381a62848dd66c5d8217fae3858e469ebd668c +``` **NOTE**: Each run of the `sign` command will yield a different output. While each signature is different, they are all valid. @@ -162,34 +208,44 @@ output: Given a message, a signature and an address, `subkey` can verify whether the **message** has been digitally signed by the holder (or one of the holders) of the **private key** for the given **address**: - echo -n | subkey verify

+```bash +echo -n | subkey verify
+``` example: - MESSAGE=hello - URI=0xfec70cfbf1977c6965b5af10a4534a6a35d548eb14580594d0bc543286892515 - SIGNATURE=9201af3788ad4f986b800853c79da47155f2e08fde2070d866be4c27ab060466fea0623dc2b51f4392f4c61f25381a62848dd66c5d8217fae3858e469ebd668c - echo -n $MESSAGE | subkey verify $SIGNATURE $URI +```bash +MESSAGE=hello +URI=0xfec70cfbf1977c6965b5af10a4534a6a35d548eb14580594d0bc543286892515 +SIGNATURE=9201af3788ad4f986b800853c79da47155f2e08fde2070d866be4c27ab060466fea0623dc2b51f4392f4c61f25381a62848dd66c5d8217fae3858e469ebd668c +echo -n $MESSAGE | subkey verify $SIGNATURE $URI +``` output: - Signature verifies correctly. +```text +Signature verifies correctly. +``` A failure looks like: - Error: SignatureInvalid +```text +Error: SignatureInvalid +``` ### Using the vanity generator You can use the included vanity generator to find a seed that provides an address which includes the desired pattern. Be warned, depending on your hardware this may take a while. command: -``` + +```bash subkey vanity --network polkadot --pattern bob ``` output: -``` + +```text Generating key containing pattern 'bob' best: 190 == top: 189 Secret Key URI `0x8c9a73097f235b84021a446bc2826a00c690ea0be3e0d81a84931cb4146d6691` is account: diff --git a/bin/utils/subkey/src/lib.rs b/bin/utils/subkey/src/lib.rs index 5052d1b104c2c..280b90848fbf5 100644 --- a/bin/utils/subkey/src/lib.rs +++ b/bin/utils/subkey/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,21 +16,22 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use clap::Parser; use sc_cli::{ Error, GenerateCmd, GenerateNodeKeyCmd, InspectKeyCmd, InspectNodeKeyCmd, SignCmd, VanityCmd, VerifyCmd, }; -use structopt::StructOpt; -#[derive(Debug, StructOpt)] -#[structopt( +#[derive(Debug, Parser)] +#[command( name = "subkey", author = "Parity Team ", - about = "Utility for generating and restoring with Substrate keys" + about = "Utility for generating and restoring with Substrate keys", + version )] pub enum Subkey { - /// Generate a random node libp2p key, save it to file or print it to stdout - /// and print its peer ID to stderr. + /// Generate a random node key, write it to a file or stdout and write the + /// corresponding peer-id to stderr GenerateNodeKey(GenerateNodeKeyCmd), /// Generate a random account @@ -39,7 +40,7 @@ pub enum Subkey { /// Gets a public key and a SS58 address from the provided Secret URI Inspect(InspectKeyCmd), - /// Print the peer ID corresponding to the node key in the given file + /// Load a node key from a file or stdin and print the corresponding peer-id InspectNodeKey(InspectNodeKeyCmd), /// Sign a message, with a given (secret) key. @@ -54,7 +55,7 @@ pub enum Subkey { /// Run the subkey command, given the appropriate runtime. pub fn run() -> Result<(), Error> { - match Subkey::from_args() { + match Subkey::parse() { Subkey::GenerateNodeKey(cmd) => cmd.run(), Subkey::Generate(cmd) => cmd.run(), Subkey::Inspect(cmd) => cmd.run(), diff --git a/bin/utils/subkey/src/main.rs b/bin/utils/subkey/src/main.rs index 2a0f0850713fa..271388549bcf9 100644 --- a/bin/utils/subkey/src/main.rs +++ b/bin/utils/subkey/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Subkey utility, based on node_runtime. +//! Subkey utility, based on kitchensink_runtime. fn main() -> Result<(), sc_cli::Error> { subkey::run() diff --git a/client/allocator/Cargo.toml b/client/allocator/Cargo.toml index 5ebab6cf9d61a..729decb5ebb3f 100644 --- a/client/allocator/Cargo.toml +++ b/client/allocator/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sc-allocator" -version = "4.0.0-dev" +version = "4.1.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Collection of allocator implementations." documentation = "https://docs.rs/sc-allocator" @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-wasm-interface = { version = "4.0.0-dev", path = "../../primitives/wasm-interface" } -log = "0.4.11" -thiserror = "1.0.21" +log = "0.4.17" +thiserror = "1.0.30" +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-wasm-interface = { version = "7.0.0", path = "../../primitives/wasm-interface" } diff --git a/client/allocator/src/error.rs b/client/allocator/src/error.rs index 9b9a55325f758..d9fc483224adf 100644 --- a/client/allocator/src/error.rs +++ b/client/allocator/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 741f4012cdcbe..e81d1b79e74ed 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -71,7 +71,6 @@ use crate::Error; pub use sp_core::MAX_POSSIBLE_ALLOCATION; use sp_wasm_interface::{Pointer, WordSize}; use std::{ - convert::{TryFrom, TryInto}, mem, ops::{Index, IndexMut, Range}, }; @@ -91,7 +90,7 @@ fn error(msg: &'static str) -> Error { Error::Other(msg) } -const LOG_TARGET: &'static str = "wasm-heap"; +const LOG_TARGET: &str = "wasm-heap"; // The minimum possible allocation size is chosen to be 8 bytes because in that case we would have // easier time to provide the guaranteed alignment of 8. @@ -217,7 +216,6 @@ impl Link { /// | 0 | next element link | /// +--------------+-------------------+ /// ``` -/// /// ## Occupied header /// ```ignore /// 64 32 0 @@ -316,27 +314,50 @@ impl IndexMut for FreeLists { } } +/// Memory allocation stats gathered during the lifetime of the allocator. +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct AllocationStats { + /// The current number of bytes allocated. + /// + /// This represents how many bytes are allocated *right now*. + pub bytes_allocated: u32, + + /// The peak number of bytes ever allocated. + /// + /// This is the maximum the `bytes_allocated` ever reached. + pub bytes_allocated_peak: u32, + + /// The sum of every allocation ever made. + /// + /// This increases every time a new allocation is made. + pub bytes_allocated_sum: u128, + + /// The amount of address space (in bytes) used by the allocator. + /// + /// This is calculated as the difference between the allocator's bumper + /// and the heap base. + /// + /// Currently the bumper's only ever incremented, so this is simultaneously + /// the current value as well as the peak value. + pub address_space_used: u32, +} + /// An implementation of freeing bump allocator. /// /// Refer to the module-level documentation for further details. pub struct FreeingBumpHeapAllocator { + original_heap_base: u32, bumper: u32, free_lists: FreeLists, - total_size: u32, poisoned: bool, - max_total_size: u32, - max_bumper: u32, last_observed_memory_size: u32, + stats: AllocationStats, } impl Drop for FreeingBumpHeapAllocator { fn drop(&mut self) { - log::debug!( - target: LOG_TARGET, - "allocator being destroyed, max_total_size {}, max_bumper {}", - self.max_total_size, - self.max_bumper, - ) + log::debug!(target: LOG_TARGET, "allocator dropped: {:?}", self.stats) } } @@ -350,13 +371,12 @@ impl FreeingBumpHeapAllocator { let aligned_heap_base = (heap_base + ALIGNMENT - 1) / ALIGNMENT * ALIGNMENT; FreeingBumpHeapAllocator { + original_heap_base: aligned_heap_base, bumper: aligned_heap_base, free_lists: FreeLists::new(), - total_size: 0, poisoned: false, - max_total_size: 0, - max_bumper: aligned_heap_base, last_observed_memory_size: 0, + stats: AllocationStats::default(), } } @@ -414,22 +434,13 @@ impl FreeingBumpHeapAllocator { // Write the order in the occupied header. Header::Occupied(order).write_into(mem, header_ptr)?; - self.total_size += order.size() + HEADER_SIZE; - - log::trace!( - target: LOG_TARGET, - "after allocation, total_size = {}, bumper = {}.", - self.total_size, - self.bumper, - ); + self.stats.bytes_allocated += order.size() + HEADER_SIZE; + self.stats.bytes_allocated_sum += u128::from(order.size() + HEADER_SIZE); + self.stats.bytes_allocated_peak = + std::cmp::max(self.stats.bytes_allocated_peak, self.stats.bytes_allocated); + self.stats.address_space_used = self.bumper - self.original_heap_base; - // update trackers if needed. - if self.total_size > self.max_total_size { - self.max_total_size = self.total_size; - } - if self.bumper > self.max_bumper { - self.max_bumper = self.bumper; - } + log::trace!(target: LOG_TARGET, "after allocation: {:?}", self.stats); bomb.disarm(); Ok(Pointer::new(header_ptr + HEADER_SIZE)) @@ -471,21 +482,23 @@ impl FreeingBumpHeapAllocator { let prev_head = self.free_lists.replace(order, Link::Ptr(header_ptr)); Header::Free(prev_head).write_into(mem, header_ptr)?; - // Do the total_size book keeping. - self.total_size = self - .total_size + self.stats.bytes_allocated = self + .stats + .bytes_allocated .checked_sub(order.size() + HEADER_SIZE) - .ok_or_else(|| error("Unable to subtract from total heap size without overflow"))?; - log::trace!( - "after deallocation, total_size = {}, bumper = {}.", - self.total_size, - self.bumper, - ); + .ok_or_else(|| error("underflow of the currently allocated bytes count"))?; + + log::trace!("after deallocation: {:?}", self.stats); bomb.disarm(); Ok(()) } + /// Returns the allocation stats for this allocator. + pub fn stats(&self) -> AllocationStats { + self.stats.clone() + } + /// Increases the `bumper` by `size`. /// /// Returns the `bumper` from before the increase. Returns an `Error::AllocatorOutOfSpace` if @@ -793,13 +806,13 @@ mod tests { let ptr1 = heap.allocate(&mut mem[..], 32).unwrap(); assert_eq!(ptr1, to_pointer(HEADER_SIZE)); heap.deallocate(&mut mem[..], ptr1).expect("failed freeing ptr1"); - assert_eq!(heap.total_size, 0); + assert_eq!(heap.stats.bytes_allocated, 0); assert_eq!(heap.bumper, 40); let ptr2 = heap.allocate(&mut mem[..], 16).unwrap(); assert_eq!(ptr2, to_pointer(48)); heap.deallocate(&mut mem[..], ptr2).expect("failed freeing ptr2"); - assert_eq!(heap.total_size, 0); + assert_eq!(heap.stats.bytes_allocated, 0); assert_eq!(heap.bumper, 64); // when @@ -827,7 +840,7 @@ mod tests { heap.allocate(&mut mem[..], 9).unwrap(); // then - assert_eq!(heap.total_size, HEADER_SIZE + 16); + assert_eq!(heap.stats.bytes_allocated, HEADER_SIZE + 16); } #[test] @@ -842,7 +855,7 @@ mod tests { heap.deallocate(&mut mem[..], ptr).unwrap(); // then - assert_eq!(heap.total_size, 0); + assert_eq!(heap.stats.bytes_allocated, 0); } #[test] @@ -858,7 +871,7 @@ mod tests { } // then - assert_eq!(heap.total_size, 0); + assert_eq!(heap.stats.bytes_allocated, 0); } #[test] diff --git a/client/allocator/src/lib.rs b/client/allocator/src/lib.rs index 4493db3c7d146..2fe63a1ec392c 100644 --- a/client/allocator/src/lib.rs +++ b/client/allocator/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,4 +26,4 @@ mod error; mod freeing_bump; pub use error::Error; -pub use freeing_bump::FreeingBumpHeapAllocator; +pub use freeing_bump::{AllocationStats, FreeingBumpHeapAllocator}; diff --git a/client/api/Cargo.toml b/client/api/Cargo.toml index 772f22e822eb2..c57a1e7221ad7 100644 --- a/client/api/Cargo.toml +++ b/client/api/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-client-api" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate client interfaces." documentation = "https://docs.rs/sc-client-api" @@ -14,31 +14,31 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ - "derive", +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ + "derive", ] } -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sc-executor = { version = "0.10.0-dev", path = "../executor" } -sp-externalities = { version = "0.10.0-dev", path = "../../primitives/externalities" } fnv = "1.0.6" -futures = "0.3.1" +futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } +log = "0.4.17" +parking_lot = "0.12.1" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-executor = { version = "0.10.0-dev", path = "../executor" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -log = "0.4.8" -parking_lot = "0.11.1" +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", default-features = false, path = "../../primitives/keystore" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-state-machine = { version = "0.10.0-dev", path = "../../primitives/state-machine" } -sp-trie = { version = "4.0.0-dev", path = "../../primitives/trie" } -sp-storage = { version = "4.0.0-dev", path = "../../primitives/storage" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.9.0", path = "../../utils/prometheus" } +sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" } +sp-keystore = { version = "0.13.0", default-features = false, path = "../../primitives/keystore" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } +sp-storage = { version = "7.0.0", path = "../../primitives/storage" } +sp-trie = { version = "7.0.0", path = "../../primitives/trie" } [dev-dependencies] +thiserror = "1.0.30" sp-test-primitives = { version = "2.0.0", path = "../../primitives/test-primitives" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } -thiserror = "1.0.21" diff --git a/client/api/src/backend.rs b/client/api/src/backend.rs index 8b5bd50ffa614..79cc0d7a16bcc 100644 --- a/client/api/src/backend.rs +++ b/client/api/src/backend.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,29 +20,24 @@ use crate::{ blockchain::{well_known_cache_keys, Backend as BlockchainBackend}, - light::RemoteBlockchain, UsageInfo, }; use parking_lot::RwLock; use sp_blockchain; use sp_consensus::BlockOrigin; -use sp_core::{offchain::OffchainStorage, ChangesTrieConfigurationRange}; +use sp_core::offchain::OffchainStorage; use sp_runtime::{ - generic::BlockId, traits::{Block as BlockT, HashFor, NumberFor}, - Justification, Justifications, Storage, + Justification, Justifications, StateVersion, Storage, }; use sp_state_machine::{ - ChangesTrieState, ChangesTrieStorage as StateChangesTrieStorage, ChangesTrieTransaction, - ChildStorageCollection, IndexOperation, OffchainChangesCollection, StorageCollection, -}; -use sp_storage::{ChildInfo, PrefixedStorageKey, StorageData, StorageKey}; -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, + backend::AsTrieBackend, ChildStorageCollection, IndexOperation, OffchainChangesCollection, + StorageCollection, }; +use sp_storage::{ChildInfo, StorageData, StorageKey}; +use std::collections::{HashMap, HashSet}; -pub use sp_state_machine::Backend as StateBackend; +pub use sp_state_machine::{Backend as StateBackend, KeyValueStates}; use std::marker::PhantomData; /// Extracts the state backend type for the given backend. @@ -75,14 +70,28 @@ pub struct ImportSummary { pub tree_route: Option>, } -/// Import operation wrapper +/// Finalization operation summary. +/// +/// Contains information about the block that just got finalized, +/// including tree heads that became stale at the moment of finalization. +pub struct FinalizeSummary { + /// Last finalized block header. + pub header: Block::Header, + /// Blocks that were finalized. + /// The last entry is the one that has been explicitly finalized. + pub finalized: Vec, + /// Heads that became stale during this finalization operation. + pub stale_heads: Vec, +} + +/// Import operation wrapper. pub struct ClientImportOperation> { /// DB Operation. pub op: B::BlockImportOperation, /// Summary of imported block. pub notify_imported: Option>, - /// A list of hashes of blocks that got finalized. - pub notify_finalized: Vec, + /// Summary of finalized block. + pub notify_finalized: Option>, } /// Helper function to apply auxiliary data insertion into an operation. @@ -171,10 +180,15 @@ pub trait BlockImportOperation { &mut self, storage: Storage, commit: bool, + state_version: StateVersion, ) -> sp_blockchain::Result; /// Inject storage data into the database replacing any existing data. - fn reset_storage(&mut self, storage: Storage) -> sp_blockchain::Result; + fn reset_storage( + &mut self, + storage: Storage, + state_version: StateVersion, + ) -> sp_blockchain::Result; /// Set storage changes. fn update_storage( @@ -191,12 +205,6 @@ pub trait BlockImportOperation { Ok(()) } - /// Inject changes trie data into the database. - fn update_changes_trie( - &mut self, - update: ChangesTrieTransaction, NumberFor>, - ) -> sp_blockchain::Result<()>; - /// Insert auxiliary keys. /// /// Values are `None` if should be deleted. @@ -207,13 +215,13 @@ pub trait BlockImportOperation { /// Mark a block as finalized. fn mark_finalized( &mut self, - id: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()>; /// Mark a block as new head. If both block import and set head are specified, set head /// overrides block import's best block rule. - fn mark_head(&mut self, id: BlockId) -> sp_blockchain::Result<()>; + fn mark_head(&mut self, hash: Block::Hash) -> sp_blockchain::Result<()>; /// Add a transaction index operation. fn update_transaction_index(&mut self, index: Vec) @@ -243,7 +251,7 @@ pub trait Finalizer> { fn apply_finality( &self, operation: &mut ClientImportOperation, - id: BlockId, + block: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()>; @@ -263,13 +271,17 @@ pub trait Finalizer> { /// while performing major synchronization work. fn finalize_block( &self, - id: BlockId, + block: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()>; } /// Provides access to an auxiliary database. +/// +/// This is a simple global database not aware of forks. Can be used for storing auxiliary +/// information like total block weight/difficulty for fork resolution purposes as a common use +/// case. pub trait AuxStore { /// Insert auxiliary data into key-value store. /// @@ -344,102 +356,80 @@ where /// Provides acess to storage primitives pub trait StorageProvider> { - /// Given a `BlockId` and a key, return the value under the key in that block. + /// Given a block's `Hash` and a key, return the value under the key in that block. fn storage( &self, - id: &BlockId, + hash: Block::Hash, key: &StorageKey, ) -> sp_blockchain::Result>; - /// Given a `BlockId` and a key prefix, return the matching storage keys in that block. + /// Given a block's `Hash` and a key prefix, return the matching storage keys in that block. fn storage_keys( &self, - id: &BlockId, + hash: Block::Hash, key_prefix: &StorageKey, ) -> sp_blockchain::Result>; - /// Given a `BlockId` and a key, return the value under the hash in that block. + /// Given a block's `Hash` and a key, return the value under the hash in that block. fn storage_hash( &self, - id: &BlockId, + hash: Block::Hash, key: &StorageKey, ) -> sp_blockchain::Result>; - /// Given a `BlockId` and a key prefix, return the matching child storage keys and values in - /// that block. + /// Given a block's `Hash` and a key prefix, return the matching child storage keys and values + /// in that block. fn storage_pairs( &self, - id: &BlockId, + hash: Block::Hash, key_prefix: &StorageKey, ) -> sp_blockchain::Result>; - /// Given a `BlockId` and a key prefix, return a `KeyIterator` iterates matching storage keys in - /// that block. + /// Given a block's `Hash` and a key prefix, return a `KeyIterator` iterates matching storage + /// keys in that block. fn storage_keys_iter<'a>( &self, - id: &BlockId, + hash: Block::Hash, prefix: Option<&'a StorageKey>, start_key: Option<&StorageKey>, ) -> sp_blockchain::Result>; - /// Given a `BlockId`, a key and a child storage key, return the value under the key in that - /// block. + /// Given a block's `Hash`, a key and a child storage key, return the value under the key in + /// that block. fn child_storage( &self, - id: &BlockId, + hash: Block::Hash, child_info: &ChildInfo, key: &StorageKey, ) -> sp_blockchain::Result>; - /// Given a `BlockId`, a key prefix, and a child storage key, return the matching child storage - /// keys. + /// Given a block's `Hash`, a key prefix, and a child storage key, return the matching child + /// storage keys. fn child_storage_keys( &self, - id: &BlockId, + hash: Block::Hash, child_info: &ChildInfo, key_prefix: &StorageKey, ) -> sp_blockchain::Result>; - /// Given a `BlockId` and a key `prefix` and a child storage key, + /// Given a block's `Hash` and a key `prefix` and a child storage key, /// return a `KeyIterator` that iterates matching storage keys in that block. fn child_storage_keys_iter<'a>( &self, - id: &BlockId, + hash: Block::Hash, child_info: ChildInfo, prefix: Option<&'a StorageKey>, start_key: Option<&StorageKey>, ) -> sp_blockchain::Result>; - /// Given a `BlockId`, a key and a child storage key, return the hash under the key in that + /// Given a block's `Hash`, a key and a child storage key, return the hash under the key in that /// block. fn child_storage_hash( &self, - id: &BlockId, + hash: Block::Hash, child_info: &ChildInfo, key: &StorageKey, ) -> sp_blockchain::Result>; - - /// Get longest range within [first; last] that is possible to use in `key_changes` - /// and `key_changes_proof` calls. - /// Range could be shortened from the beginning if some changes tries have been pruned. - /// Returns Ok(None) if changes tries are not supported. - fn max_key_changes_range( - &self, - first: NumberFor, - last: BlockId, - ) -> sp_blockchain::Result, BlockId)>>; - - /// Get pairs of (block, extrinsic) where key has been changed at given blocks range. - /// Works only for runtimes that are supporting changes tries. - /// - /// Changes are returned in descending order (i.e. last block comes first). - fn key_changes( - &self, - first: NumberFor, - last: BlockId, - storage_key: Option<&PrefixedStorageKey>, - key: &StorageKey, - ) -> sp_blockchain::Result, u32)>>; } /// Client backend. @@ -458,7 +448,12 @@ pub trait Backend: AuxStore + Send + Sync { /// Associated blockchain backend type. type Blockchain: BlockchainBackend; /// Associated state backend type. - type State: StateBackend> + Send; + type State: StateBackend> + + Send + + AsTrieBackend< + HashFor, + TrieBackendStorage = >>::TrieBackendStorage, + >; /// Offchain workers local storage. type OffchainStorage: OffchainStorage; @@ -471,7 +466,7 @@ pub trait Backend: AuxStore + Send + Sync { fn begin_state_operation( &self, operation: &mut Self::BlockImportOperation, - block: BlockId, + block: Block::Hash, ) -> sp_blockchain::Result<()>; /// Commit block insertion. @@ -480,21 +475,21 @@ pub trait Backend: AuxStore + Send + Sync { transaction: Self::BlockImportOperation, ) -> sp_blockchain::Result<()>; - /// Finalize block with given Id. + /// Finalize block with given `hash`. /// /// This should only be called if the parent of the given block has been finalized. fn finalize_block( &self, - block: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()>; - /// Append justification to the block with the given Id. + /// Append justification to the block with the given `hash`. /// /// This should only be called for blocks that are already finalized. fn append_justification( &self, - block: BlockId, + hash: Block::Hash, justification: Justification, ) -> sp_blockchain::Result<()>; @@ -504,23 +499,21 @@ pub trait Backend: AuxStore + Send + Sync { /// Returns current usage statistics. fn usage_info(&self) -> Option; - /// Returns reference to changes trie storage. - fn changes_trie_storage(&self) -> Option<&dyn PrunableStateChangesTrieStorage>; - /// Returns a handle to offchain storage. fn offchain_storage(&self) -> Option; /// Returns true if state for given block is available. - fn have_state_at(&self, hash: &Block::Hash, _number: NumberFor) -> bool { - self.state_at(BlockId::Hash(hash.clone())).is_ok() + fn have_state_at(&self, hash: Block::Hash, _number: NumberFor) -> bool { + self.state_at(hash).is_ok() } /// Returns state backend with post-state of given block. - fn state_at(&self, block: BlockId) -> sp_blockchain::Result; + fn state_at(&self, hash: Block::Hash) -> sp_blockchain::Result; /// Attempts to revert the chain by `n` blocks. If `revert_finalized` is set it will attempt to /// revert past any finalized block, this is unsafe and can potentially leave the node in an - /// inconsistent state. + /// inconsistent state. All blocks higher than the best block are also reverted and not counting + /// towards `n`. /// /// Returns the number of blocks that were successfully reverted and the list of finalized /// blocks that has been reverted. @@ -531,7 +524,7 @@ pub trait Backend: AuxStore + Send + Sync { ) -> sp_blockchain::Result<(NumberFor, HashSet)>; /// Discard non-best, unfinalized leaf block. - fn remove_leaf_block(&self, hash: &Block::Hash) -> sp_blockchain::Result<()>; + fn remove_leaf_block(&self, hash: Block::Hash) -> sp_blockchain::Result<()>; /// Insert auxiliary data into key-value store. fn insert_aux< @@ -559,74 +552,10 @@ pub trait Backend: AuxStore + Send + Sync { /// something that the import of a block would interfere with, e.g. importing /// a new block or calculating the best head. fn get_import_lock(&self) -> &RwLock<()>; -} -/// Changes trie storage that supports pruning. -pub trait PrunableStateChangesTrieStorage: - StateChangesTrieStorage, NumberFor> -{ - /// Get reference to StateChangesTrieStorage. - fn storage(&self) -> &dyn StateChangesTrieStorage, NumberFor>; - /// Get configuration at given block. - fn configuration_at( - &self, - at: &BlockId, - ) -> sp_blockchain::Result, Block::Hash>>; - /// Get end block (inclusive) of oldest pruned max-level (or skewed) digest trie blocks range. - /// It is guaranteed that we have no any changes tries before (and including) this block. - /// It is guaranteed that all existing changes tries after this block are not yet pruned (if - /// created). - fn oldest_pruned_digest_range_end(&self) -> NumberFor; + /// Tells whether the backend requires full-sync mode. + fn requires_full_sync(&self) -> bool; } /// Mark for all Backend implementations, that are making use of state data, stored locally. pub trait LocalBackend: Backend {} - -/// Mark for all Backend implementations, that are fetching required state data from remote nodes. -pub trait RemoteBackend: Backend { - /// Returns true if the state for given block is available locally. - fn is_local_state_available(&self, block: &BlockId) -> bool; - - /// Returns reference to blockchain backend. - /// - /// Returned backend either resolves blockchain data - /// locally, or prepares request to fetch that data from remote node. - fn remote_blockchain(&self) -> Arc>; -} - -/// Return changes tries state at given block. -pub fn changes_tries_state_at_block<'a, Block: BlockT>( - block: &BlockId, - maybe_storage: Option<&'a dyn PrunableStateChangesTrieStorage>, -) -> sp_blockchain::Result, NumberFor>>> { - let storage = match maybe_storage { - Some(storage) => storage, - None => return Ok(None), - }; - - let config_range = storage.configuration_at(block)?; - match config_range.config { - Some(config) => - Ok(Some(ChangesTrieState::new(config, config_range.zero.0, storage.storage()))), - None => Ok(None), - } -} - -/// Provide CHT roots. These are stored on a light client and generated dynamically on a full -/// client. -pub trait ProvideChtRoots { - /// Get headers CHT root for given block. Returns None if the block is not a part of any CHT. - fn header_cht_root( - &self, - cht_size: NumberFor, - block: NumberFor, - ) -> sp_blockchain::Result>; - - /// Get changes trie CHT root for given block. Returns None if the block is not a part of any - /// CHT. - fn changes_trie_cht_root( - &self, - cht_size: NumberFor, - block: NumberFor, - ) -> sp_blockchain::Result>; -} diff --git a/client/api/src/call_executor.rs b/client/api/src/call_executor.rs index 22af495c06542..7a42385010c68 100644 --- a/client/api/src/call_executor.rs +++ b/client/api/src/call_executor.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,16 +18,13 @@ //! A method call executor interface. -use codec::{Decode, Encode}; -use sc_executor::RuntimeVersion; -use sp_core::NativeOrEncoded; -use sp_externalities::Extensions; +use sc_executor::{RuntimeVersion, RuntimeVersionOf}; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; -use sp_state_machine::{ExecutionManager, ExecutionStrategy, OverlayedChanges, StorageProof}; -use std::{cell::RefCell, panic::UnwindSafe, result}; +use sp_state_machine::{ExecutionStrategy, OverlayedChanges, StorageProof}; +use std::cell::RefCell; use crate::execution_extensions::ExecutionExtensions; -use sp_api::{ProofRecorder, StorageTransactionCache}; +use sp_api::{ExecutionContext, ProofRecorder, StorageTransactionCache}; /// Executor Provider pub trait ExecutorProvider { @@ -42,13 +39,16 @@ pub trait ExecutorProvider { } /// Method call executor. -pub trait CallExecutor { +pub trait CallExecutor: RuntimeVersionOf { /// Externalities error type. type Error: sp_state_machine::Error; /// The backend used by the node. type Backend: crate::backend::Backend; + /// Returns the [`ExecutionExtensions`]. + fn execution_extensions(&self) -> &ExecutionExtensions; + /// Execute a call to a contract on top of state in a block of given hash. /// /// No changes are made. @@ -58,7 +58,6 @@ pub trait CallExecutor { method: &str, call_data: &[u8], strategy: ExecutionStrategy, - extensions: Option, ) -> Result, sp_blockchain::Error>; /// Execute a contextual call on top of state in a block of a given hash. @@ -66,14 +65,7 @@ pub trait CallExecutor { /// No changes are made. /// Before executing the method, passed header is installed as the current header /// of the execution context. - fn contextual_call< - EM: Fn( - Result, Self::Error>, - Result, Self::Error>, - ) -> Result, Self::Error>, - R: Encode + Decode + PartialEq, - NC: FnOnce() -> result::Result + UnwindSafe, - >( + fn contextual_call( &self, at: &BlockId, method: &str, @@ -84,13 +76,9 @@ pub trait CallExecutor { StorageTransactionCache>::State>, >, >, - execution_manager: ExecutionManager, - native_call: Option, proof_recorder: &Option>, - extensions: Option, - ) -> sp_blockchain::Result> - where - ExecutionManager: Clone; + context: ExecutionContext, + ) -> sp_blockchain::Result>; /// Extract RuntimeVersion of given block /// diff --git a/client/api/src/cht.rs b/client/api/src/cht.rs deleted file mode 100644 index ee7854b5d8297..0000000000000 --- a/client/api/src/cht.rs +++ /dev/null @@ -1,474 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Canonical hash trie definitions and helper functions. -//! -//! Each CHT is a trie mapping block numbers to canonical hash. -//! One is generated for every `SIZE` blocks, allowing us to discard those blocks in -//! favor of the trie root. When the "ancient" blocks need to be accessed, we simply -//! request an inclusion proof of a specific block number against the trie with the -//! root hash. A correct proof implies that the claimed block is identical to the one -//! we discarded. - -use codec::Encode; -use hash_db; -use sp_trie; - -use sp_core::{convert_hash, H256}; -use sp_runtime::traits::{AtLeast32Bit, Header as HeaderT, One, Zero}; -use sp_state_machine::{ - prove_read_on_trie_backend, read_proof_check, read_proof_check_on_proving_backend, - Backend as StateBackend, InMemoryBackend, MemoryDB, StorageProof, TrieBackend, -}; - -use sp_blockchain::{Error as ClientError, Result as ClientResult}; - -/// The size of each CHT. This value is passed to every CHT-related function from -/// production code. Other values are passed from tests. -const SIZE: u32 = 2048; - -/// Gets default CHT size. -pub fn size>() -> N { - SIZE.into() -} - -/// Returns Some(cht_number) if CHT is need to be built when the block with given number is -/// canonized. -pub fn is_build_required(cht_size: N, block_num: N) -> Option -where - N: Clone + AtLeast32Bit, -{ - let block_cht_num = block_to_cht_number(cht_size.clone(), block_num.clone())?; - let two = N::one() + N::one(); - if block_cht_num < two { - return None - } - let cht_start = start_number(cht_size, block_cht_num.clone()); - if cht_start != block_num { - return None - } - - Some(block_cht_num - two) -} - -/// Returns Some(max_cht_number) if CHT has ever been built given maximal canonical block number. -pub fn max_cht_number(cht_size: N, max_canonical_block: N) -> Option -where - N: Clone + AtLeast32Bit, -{ - let max_cht_number = block_to_cht_number(cht_size, max_canonical_block)?; - let two = N::one() + N::one(); - if max_cht_number < two { - return None - } - Some(max_cht_number - two) -} - -/// Compute a CHT root from an iterator of block hashes. Fails if shorter than -/// SIZE items. The items are assumed to proceed sequentially from `start_number(cht_num)`. -/// Discards the trie's nodes. -pub fn compute_root( - cht_size: Header::Number, - cht_num: Header::Number, - hashes: I, -) -> ClientResult -where - Header: HeaderT, - Hasher: hash_db::Hasher, - Hasher::Out: Ord, - I: IntoIterator>>, -{ - use sp_trie::TrieConfiguration; - Ok(sp_trie::trie_types::Layout::::trie_root(build_pairs::( - cht_size, cht_num, hashes, - )?)) -} - -/// Build CHT-based header proof. -pub fn build_proof( - cht_size: Header::Number, - cht_num: Header::Number, - blocks: BlocksI, - hashes: HashesI, -) -> ClientResult -where - Header: HeaderT, - Hasher: hash_db::Hasher, - Hasher::Out: Ord + codec::Codec, - BlocksI: IntoIterator, - HashesI: IntoIterator>>, -{ - let transaction = build_pairs::(cht_size, cht_num, hashes)? - .into_iter() - .map(|(k, v)| (k, Some(v))) - .collect::>(); - let storage = InMemoryBackend::::default().update(vec![(None, transaction)]); - let trie_storage = storage - .as_trie_backend() - .expect("InMemoryState::as_trie_backend always returns Some; qed"); - prove_read_on_trie_backend( - trie_storage, - blocks.into_iter().map(|number| encode_cht_key(number)), - ) - .map_err(ClientError::from_state) -} - -/// Check CHT-based header proof. -pub fn check_proof( - local_root: Header::Hash, - local_number: Header::Number, - remote_hash: Header::Hash, - remote_proof: StorageProof, -) -> ClientResult<()> -where - Header: HeaderT, - Hasher: hash_db::Hasher, - Hasher::Out: Ord + codec::Codec, -{ - do_check_proof::( - local_root, - local_number, - remote_hash, - move |local_root, local_cht_key| { - read_proof_check::( - local_root, - remote_proof, - ::std::iter::once(local_cht_key), - ) - .map(|mut map| map.remove(local_cht_key).expect("checked proof of local_cht_key; qed")) - .map_err(ClientError::from_state) - }, - ) -} - -/// Check CHT-based header proof on pre-created proving backend. -pub fn check_proof_on_proving_backend( - local_root: Header::Hash, - local_number: Header::Number, - remote_hash: Header::Hash, - proving_backend: &TrieBackend, Hasher>, -) -> ClientResult<()> -where - Header: HeaderT, - Hasher: hash_db::Hasher, - Hasher::Out: Ord + codec::Codec, -{ - do_check_proof::( - local_root, - local_number, - remote_hash, - |_, local_cht_key| { - read_proof_check_on_proving_backend::(proving_backend, local_cht_key) - .map_err(ClientError::from_state) - }, - ) -} - -/// Check CHT-based header proof using passed checker function. -fn do_check_proof( - local_root: Header::Hash, - local_number: Header::Number, - remote_hash: Header::Hash, - checker: F, -) -> ClientResult<()> -where - Header: HeaderT, - Hasher: hash_db::Hasher, - Hasher::Out: Ord, - F: FnOnce(Hasher::Out, &[u8]) -> ClientResult>>, -{ - let root: Hasher::Out = convert_hash(&local_root); - let local_cht_key = encode_cht_key(local_number); - let local_cht_value = checker(root, &local_cht_key)?; - let local_cht_value = local_cht_value.ok_or_else(|| ClientError::InvalidCHTProof)?; - let local_hash = - decode_cht_value(&local_cht_value).ok_or_else(|| ClientError::InvalidCHTProof)?; - match &local_hash[..] == remote_hash.as_ref() { - true => Ok(()), - false => Err(ClientError::InvalidCHTProof.into()), - } -} - -/// Group ordered blocks by CHT number and call functor with blocks of each group. -pub fn for_each_cht_group( - cht_size: Header::Number, - blocks: I, - mut functor: F, - mut functor_param: P, -) -> ClientResult<()> -where - Header: HeaderT, - I: IntoIterator, - F: FnMut(P, Header::Number, Vec) -> ClientResult

, -{ - let mut current_cht_num = None; - let mut current_cht_blocks = Vec::new(); - for block in blocks { - let new_cht_num = block_to_cht_number(cht_size, block).ok_or_else(|| { - ClientError::Backend(format!("Cannot compute CHT root for the block #{}", block)) - })?; - - let advance_to_next_cht = current_cht_num.is_some() && current_cht_num != Some(new_cht_num); - if advance_to_next_cht { - let current_cht_num = current_cht_num.expect( - "advance_to_next_cht is true; - it is true only when current_cht_num is Some; qed", - ); - assert!( - new_cht_num > current_cht_num, - "for_each_cht_group only supports ordered iterators" - ); - - functor_param = - functor(functor_param, current_cht_num, std::mem::take(&mut current_cht_blocks))?; - } - - current_cht_blocks.push(block); - current_cht_num = Some(new_cht_num); - } - - if let Some(current_cht_num) = current_cht_num { - functor(functor_param, current_cht_num, std::mem::take(&mut current_cht_blocks))?; - } - - Ok(()) -} - -/// Build pairs for computing CHT. -fn build_pairs( - cht_size: Header::Number, - cht_num: Header::Number, - hashes: I, -) -> ClientResult, Vec)>> -where - Header: HeaderT, - I: IntoIterator>>, -{ - let start_num = start_number(cht_size, cht_num); - let mut pairs = Vec::new(); - let mut hash_index = Header::Number::zero(); - for hash in hashes.into_iter() { - let hash = - hash?.ok_or_else(|| ClientError::from(ClientError::MissingHashRequiredForCHT))?; - pairs.push((encode_cht_key(start_num + hash_index).to_vec(), encode_cht_value(hash))); - hash_index += Header::Number::one(); - if hash_index == cht_size { - break - } - } - - if hash_index == cht_size { - Ok(pairs) - } else { - Err(ClientError::MissingHashRequiredForCHT) - } -} - -/// Get the starting block of a given CHT. -/// CHT 0 includes block 1...SIZE, -/// CHT 1 includes block SIZE + 1 ... 2*SIZE -/// More generally: CHT N includes block (1 + N*SIZE)...((N+1)*SIZE). -/// This is because the genesis hash is assumed to be known -/// and including it would be redundant. -pub fn start_number(cht_size: N, cht_num: N) -> N { - (cht_num * cht_size) + N::one() -} - -/// Get the ending block of a given CHT. -pub fn end_number(cht_size: N, cht_num: N) -> N { - (cht_num + N::one()) * cht_size -} - -/// Convert a block number to a CHT number. -/// Returns `None` for `block_num` == 0, `Some` otherwise. -pub fn block_to_cht_number(cht_size: N, block_num: N) -> Option { - if block_num == N::zero() { - None - } else { - Some((block_num - N::one()) / cht_size) - } -} - -/// Convert header number into CHT key. -pub fn encode_cht_key(number: N) -> Vec { - number.encode() -} - -/// Convert header hash into CHT value. -fn encode_cht_value>(hash: Hash) -> Vec { - hash.as_ref().to_vec() -} - -/// Convert CHT value into block header hash. -pub fn decode_cht_value(value: &[u8]) -> Option { - match value.len() { - 32 => Some(H256::from_slice(&value[0..32])), - _ => None, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_runtime::{generic, traits::BlakeTwo256}; - - type Header = generic::Header; - - #[test] - fn is_build_required_works() { - assert_eq!(is_build_required(SIZE, 0u32.into()), None); - assert_eq!(is_build_required(SIZE, 1u32.into()), None); - assert_eq!(is_build_required(SIZE, SIZE), None); - assert_eq!(is_build_required(SIZE, SIZE + 1), None); - assert_eq!(is_build_required(SIZE, 2 * SIZE), None); - assert_eq!(is_build_required(SIZE, 2 * SIZE + 1), Some(0)); - assert_eq!(is_build_required(SIZE, 2 * SIZE + 2), None); - assert_eq!(is_build_required(SIZE, 3 * SIZE), None); - assert_eq!(is_build_required(SIZE, 3 * SIZE + 1), Some(1)); - assert_eq!(is_build_required(SIZE, 3 * SIZE + 2), None); - } - - #[test] - fn max_cht_number_works() { - assert_eq!(max_cht_number(SIZE, 0u32.into()), None); - assert_eq!(max_cht_number(SIZE, 1u32.into()), None); - assert_eq!(max_cht_number(SIZE, SIZE), None); - assert_eq!(max_cht_number(SIZE, SIZE + 1), None); - assert_eq!(max_cht_number(SIZE, 2 * SIZE), None); - assert_eq!(max_cht_number(SIZE, 2 * SIZE + 1), Some(0)); - assert_eq!(max_cht_number(SIZE, 2 * SIZE + 2), Some(0)); - assert_eq!(max_cht_number(SIZE, 3 * SIZE), Some(0)); - assert_eq!(max_cht_number(SIZE, 3 * SIZE + 1), Some(1)); - assert_eq!(max_cht_number(SIZE, 3 * SIZE + 2), Some(1)); - } - - #[test] - fn start_number_works() { - assert_eq!(start_number(SIZE, 0u32), 1u32); - assert_eq!(start_number(SIZE, 1u32), SIZE + 1); - assert_eq!(start_number(SIZE, 2u32), SIZE + SIZE + 1); - } - - #[test] - fn end_number_works() { - assert_eq!(end_number(SIZE, 0u32), SIZE); - assert_eq!(end_number(SIZE, 1u32), SIZE + SIZE); - assert_eq!(end_number(SIZE, 2u32), SIZE + SIZE + SIZE); - } - - #[test] - fn build_pairs_fails_when_no_enough_blocks() { - assert!(build_pairs::( - SIZE as _, - 0, - ::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1)))).take(SIZE as usize / 2) - ) - .is_err()); - } - - #[test] - fn build_pairs_fails_when_missing_block() { - assert!(build_pairs::( - SIZE as _, - 0, - ::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1)))) - .take(SIZE as usize / 2) - .chain(::std::iter::once(Ok(None))) - .chain( - ::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(2)))) - .take(SIZE as usize / 2 - 1) - ) - ) - .is_err()); - } - - #[test] - fn compute_root_works() { - assert!(compute_root::( - SIZE as _, - 42, - ::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1)))).take(SIZE as usize) - ) - .is_ok()); - } - - #[test] - #[should_panic] - fn build_proof_panics_when_querying_wrong_block() { - assert!(build_proof::( - SIZE as _, - 0, - vec![(SIZE * 1000) as u64], - ::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1)))).take(SIZE as usize) - ) - .is_err()); - } - - #[test] - fn build_proof_works() { - assert!(build_proof::( - SIZE as _, - 0, - vec![(SIZE / 2) as u64], - ::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1)))).take(SIZE as usize) - ) - .is_ok()); - } - - #[test] - #[should_panic] - fn for_each_cht_group_panics() { - let cht_size = SIZE as u64; - let _ = for_each_cht_group::( - cht_size, - vec![cht_size * 5, cht_size * 2], - |_, _, _| Ok(()), - (), - ); - } - - #[test] - fn for_each_cht_group_works() { - let cht_size = SIZE as u64; - let _ = for_each_cht_group::( - cht_size, - vec![ - cht_size * 2 + 1, - cht_size * 2 + 2, - cht_size * 2 + 5, - cht_size * 4 + 1, - cht_size * 4 + 7, - cht_size * 6 + 1, - ], - |_, cht_num, blocks| { - match cht_num { - 2 => assert_eq!( - blocks, - vec![cht_size * 2 + 1, cht_size * 2 + 2, cht_size * 2 + 5] - ), - 4 => assert_eq!(blocks, vec![cht_size * 4 + 1, cht_size * 4 + 7]), - 6 => assert_eq!(blocks, vec![cht_size * 6 + 1]), - _ => unreachable!(), - } - - Ok(()) - }, - (), - ); - } -} diff --git a/client/api/src/client.rs b/client/api/src/client.rs index 21f8aecad0536..05e3163dcc7bd 100644 --- a/client/api/src/client.rs +++ b/client/api/src/client.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -25,9 +25,10 @@ use sp_runtime::{ traits::{Block as BlockT, NumberFor}, Justifications, }; -use std::{collections::HashSet, convert::TryFrom, fmt, sync::Arc}; +use std::{collections::HashSet, fmt, sync::Arc}; + +use crate::{blockchain::Info, notifications::StorageEventStream, FinalizeSummary, ImportSummary}; -use crate::{blockchain::Info, notifications::StorageEventStream}; use sc_transaction_pool_api::ChainEvent; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_blockchain; @@ -76,12 +77,40 @@ pub trait BlockchainEvents { ) -> sp_blockchain::Result>; } +/// List of operations to be performed on storage aux data. +/// First tuple element is the encoded data key. +/// Second tuple element is the encoded optional data to write. +/// If `None`, the key and the associated data are deleted from storage. +pub type AuxDataOperations = Vec<(Vec, Option>)>; + +/// Callback invoked before committing the operations created during block import. +/// This gives the opportunity to perform auxiliary pre-commit actions and optionally +/// enqueue further storage write operations to be atomically performed on commit. +pub type OnImportAction = + Box) -> AuxDataOperations) + Send>; + +/// Callback invoked before committing the operations created during block finalization. +/// This gives the opportunity to perform auxiliary pre-commit actions and optionally +/// enqueue further storage write operations to be atomically performed on commit. +pub type OnFinalityAction = + Box) -> AuxDataOperations) + Send>; + +/// Interface to perform auxiliary actions before committing a block import or +/// finality operation. +pub trait PreCommitActions { + /// Actions to be performed on block import. + fn register_import_action(&self, op: OnImportAction); + + /// Actions to be performed on block finalization. + fn register_finality_action(&self, op: OnFinalityAction); +} + /// Interface for fetching block data. pub trait BlockBackend { /// Get block body by ID. Returns `None` if the body is not stored. fn block_body( &self, - id: &BlockId, + hash: Block::Hash, ) -> sp_blockchain::Result::Extrinsic>>>; /// Get all indexed transactions for a block, @@ -89,10 +118,7 @@ pub trait BlockBackend { /// /// Note that this will only fetch transactions /// that are indexed by the runtime with `storage_index_transaction`. - fn block_indexed_body( - &self, - id: &BlockId, - ) -> sp_blockchain::Result>>>; + fn block_indexed_body(&self, hash: Block::Hash) -> sp_blockchain::Result>>>; /// Get full block by id. fn block(&self, id: &BlockId) -> sp_blockchain::Result>>; @@ -102,7 +128,7 @@ pub trait BlockBackend { -> sp_blockchain::Result; /// Get block justifications for the block with the given id. - fn justifications(&self, id: &BlockId) -> sp_blockchain::Result>; + fn justifications(&self, hash: Block::Hash) -> sp_blockchain::Result>; /// Get block hash by number. fn block_hash(&self, number: NumberFor) -> sp_blockchain::Result>; @@ -111,12 +137,15 @@ pub trait BlockBackend { /// /// Note that this will only fetch transactions /// that are indexed by the runtime with `storage_index_transaction`. - fn indexed_transaction(&self, hash: &Block::Hash) -> sp_blockchain::Result>>; + fn indexed_transaction(&self, hash: Block::Hash) -> sp_blockchain::Result>>; /// Check if transaction index exists. - fn has_indexed_transaction(&self, hash: &Block::Hash) -> sp_blockchain::Result { + fn has_indexed_transaction(&self, hash: Block::Hash) -> sp_blockchain::Result { Ok(self.indexed_transaction(hash)?.is_some()) } + + /// Tells whether the current client configuration requires full-sync mode. + fn requires_full_sync(&self) -> bool; } /// Provide a list of potential uncle headers for a given block. @@ -168,17 +197,6 @@ impl fmt::Display for MemorySize { } } -/// Memory statistics for state db. -#[derive(Default, Clone, Debug)] -pub struct StateDbMemoryInfo { - /// Memory usage of the non-canonical overlay - pub non_canonical: MemorySize, - /// Memory usage of the pruning window. - pub pruning: Option, - /// Memory usage of the pinned blocks. - pub pinned: MemorySize, -} - /// Memory statistics for client instance. #[derive(Default, Clone, Debug)] pub struct MemoryInfo { @@ -186,8 +204,6 @@ pub struct MemoryInfo { pub state_cache: MemorySize, /// Size of backend database cache. pub database_cache: MemorySize, - /// Size of the state db. - pub state_db: StateDbMemoryInfo, } /// I/O statistics for client instance. @@ -235,13 +251,9 @@ impl fmt::Display for UsageInfo { write!( f, "caches: ({} state, {} db overlay), \ - state db: ({} non-canonical, {} pruning, {} pinned), \ i/o: ({} tx, {} write, {} read, {} avg tx, {}/{} key cache reads/total, {} trie nodes writes)", self.memory.state_cache, self.memory.database_cache, - self.memory.state_db.non_canonical, - self.memory.state_db.pruning.unwrap_or_default(), - self.memory.state_db.pinned, self.io.transactions, self.io.bytes_written, self.io.bytes_read, @@ -273,10 +285,16 @@ pub struct BlockImportNotification { /// Summary of a finalized block. #[derive(Clone, Debug)] pub struct FinalityNotification { - /// Imported block header hash. + /// Finalized block header hash. pub hash: Block::Hash, - /// Imported block header. + /// Finalized block header. pub header: Block::Header, + /// Path from the old finalized to new finalized parent (implicitly finalized blocks). + /// + /// This maps to the range `(old_finalized, new_finalized)`. + pub tree_route: Arc<[Block::Hash]>, + /// Stale branches heads. + pub stale_heads: Arc<[Block::Hash]>, } impl TryFrom> for ChainEvent { @@ -293,6 +311,30 @@ impl TryFrom> for ChainEvent { impl From> for ChainEvent { fn from(n: FinalityNotification) -> Self { - Self::Finalized { hash: n.hash } + Self::Finalized { hash: n.hash, tree_route: n.tree_route } + } +} + +impl From> for FinalityNotification { + fn from(mut summary: FinalizeSummary) -> Self { + let hash = summary.finalized.pop().unwrap_or_default(); + FinalityNotification { + hash, + header: summary.header, + tree_route: Arc::from(summary.finalized), + stale_heads: Arc::from(summary.stale_heads), + } + } +} + +impl From> for BlockImportNotification { + fn from(summary: ImportSummary) -> Self { + BlockImportNotification { + hash: summary.hash, + origin: summary.origin, + header: summary.header, + is_new_best: summary.is_new_best, + tree_route: summary.tree_route.map(Arc::new), + } } } diff --git a/client/api/src/execution_extensions.rs b/client/api/src/execution_extensions.rs index ec44294b8a96c..58c085a29a945 100644 --- a/client/api/src/execution_extensions.rs +++ b/client/api/src/execution_extensions.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -29,12 +29,18 @@ use sp_core::{ offchain::{self, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}, ExecutionContext, }; -use sp_externalities::Extensions; +use sp_externalities::{Extension, Extensions}; use sp_keystore::{KeystoreExt, SyncCryptoStorePtr}; -use sp_runtime::{generic::BlockId, traits}; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, NumberFor}, +}; pub use sp_state_machine::ExecutionStrategy; use sp_state_machine::{DefaultHandler, ExecutionManager}; -use std::sync::{Arc, Weak}; +use std::{ + marker::PhantomData, + sync::{Arc, Weak}, +}; /// Execution strategies settings. #[derive(Debug, Clone)] @@ -63,18 +69,81 @@ impl Default for ExecutionStrategies { } } -/// Generate the starting set of ExternalitiesExtensions based upon the given capabilities -pub trait ExtensionsFactory: Send + Sync { - /// Make `Extensions` for given `Capabilities`. - fn extensions_for(&self, capabilities: offchain::Capabilities) -> Extensions; +/// Generate the starting set of [`Extensions`]. +/// +/// These [`Extensions`] are passed to the environment a runtime is executed in. +pub trait ExtensionsFactory: Send + Sync { + /// Create [`Extensions`] for the given input. + /// + /// - `block_hash`: The hash of the block in the context that extensions will be used. + /// - `block_number`: The number of the block in the context that extensions will be used. + /// - `capabilities`: The capabilities + fn extensions_for( + &self, + block_hash: Block::Hash, + block_number: NumberFor, + capabilities: offchain::Capabilities, + ) -> Extensions; } -impl ExtensionsFactory for () { - fn extensions_for(&self, _capabilities: offchain::Capabilities) -> Extensions { +impl ExtensionsFactory for () { + fn extensions_for( + &self, + _: Block::Hash, + _: NumberFor, + _capabilities: offchain::Capabilities, + ) -> Extensions { Extensions::new() } } +impl> ExtensionsFactory for Vec { + fn extensions_for( + &self, + block_hash: Block::Hash, + block_number: NumberFor, + capabilities: offchain::Capabilities, + ) -> Extensions { + let mut exts = Extensions::new(); + exts.extend(self.iter().map(|e| e.extensions_for(block_hash, block_number, capabilities))); + exts + } +} + +/// An [`ExtensionsFactory`] that registers an [`Extension`] before a certain block. +pub struct ExtensionBeforeBlock { + before: NumberFor, + _marker: PhantomData Ext>, +} + +impl ExtensionBeforeBlock { + /// Create the extension factory. + /// + /// - `before`: The block number until the extension should be registered. + pub fn new(before: NumberFor) -> Self { + Self { before, _marker: PhantomData } + } +} + +impl ExtensionsFactory + for ExtensionBeforeBlock +{ + fn extensions_for( + &self, + _: Block::Hash, + block_number: NumberFor, + _: offchain::Capabilities, + ) -> Extensions { + let mut exts = Extensions::new(); + + if block_number < self.before { + exts.register(Ext::default()); + } + + exts + } +} + /// Create a Offchain DB accessor object. pub trait DbExternalitiesFactory: Send + Sync { /// Create [`offchain::DbExternalities`] instance. @@ -92,7 +161,7 @@ impl DbExternaliti /// This crate aggregates extensions available for the offchain calls /// and is responsible for producing a correct `Extensions` object. /// for each call, based on required `Capabilities`. -pub struct ExecutionExtensions { +pub struct ExecutionExtensions { strategies: ExecutionStrategies, keystore: Option, offchain_db: Option>, @@ -103,10 +172,10 @@ pub struct ExecutionExtensions { // That's also the reason why it's being registered lazily instead of // during initialization. transaction_pool: RwLock>>>, - extensions_factory: RwLock>, + extensions_factory: RwLock>>, } -impl Default for ExecutionExtensions { +impl Default for ExecutionExtensions { fn default() -> Self { Self { strategies: Default::default(), @@ -118,7 +187,7 @@ impl Default for ExecutionExtensions { } } -impl ExecutionExtensions { +impl ExecutionExtensions { /// Create new `ExecutionExtensions` given a `keystore` and `ExecutionStrategies`. pub fn new( strategies: ExecutionStrategies, @@ -142,8 +211,8 @@ impl ExecutionExtensions { } /// Set the new extensions_factory - pub fn set_extensions_factory(&self, maker: Box) { - *self.extensions_factory.write() = maker; + pub fn set_extensions_factory(&self, maker: impl ExtensionsFactory + 'static) { + *self.extensions_factory.write() = Box::new(maker); } /// Register transaction pool extension. @@ -151,33 +220,41 @@ impl ExecutionExtensions { where T: OffchainSubmitTransaction + 'static, { - *self.transaction_pool.write() = Some(Arc::downgrade(&pool) as _); + *self.transaction_pool.write() = Some(Arc::downgrade(pool) as _); } /// Based on the execution context and capabilities it produces /// the extensions object to support desired set of APIs. - pub fn extensions(&self, at: &BlockId, context: ExecutionContext) -> Extensions { + pub fn extensions( + &self, + block_hash: Block::Hash, + block_number: NumberFor, + context: ExecutionContext, + ) -> Extensions { let capabilities = context.capabilities(); - let mut extensions = self.extensions_factory.read().extensions_for(capabilities); + let mut extensions = + self.extensions_factory + .read() + .extensions_for(block_hash, block_number, capabilities); - if capabilities.has(offchain::Capability::Keystore) { + if capabilities.contains(offchain::Capabilities::KEYSTORE) { if let Some(ref keystore) = self.keystore { extensions.register(KeystoreExt(keystore.clone())); } } - if capabilities.has(offchain::Capability::TransactionPool) { + if capabilities.contains(offchain::Capabilities::TRANSACTION_POOL) { if let Some(pool) = self.transaction_pool.read().as_ref().and_then(|x| x.upgrade()) { - extensions - .register(TransactionPoolExt( - Box::new(TransactionPoolAdapter { at: *at, pool }) as _, - )); + extensions.register(TransactionPoolExt(Box::new(TransactionPoolAdapter { + at: BlockId::Hash(block_hash), + pool, + }) as _)); } } - if capabilities.has(offchain::Capability::OffchainDbRead) || - capabilities.has(offchain::Capability::OffchainDbWrite) + if capabilities.contains(offchain::Capabilities::OFFCHAIN_DB_READ) || + capabilities.contains(offchain::Capabilities::OFFCHAIN_DB_WRITE) { if let Some(offchain_db) = self.offchain_db.as_ref() { extensions.register(OffchainDbExt::new(offchain::LimitedExternalities::new( @@ -201,31 +278,32 @@ impl ExecutionExtensions { /// /// Based on the execution context and capabilities it produces /// the right manager and extensions object to support desired set of APIs. - pub fn manager_and_extensions( + pub fn manager_and_extensions( &self, - at: &BlockId, + block_hash: Block::Hash, + block_number: NumberFor, context: ExecutionContext, - ) -> (ExecutionManager>, Extensions) { + ) -> (ExecutionManager>, Extensions) { let manager = match context { ExecutionContext::BlockConstruction => self.strategies.block_construction.get_manager(), ExecutionContext::Syncing => self.strategies.syncing.get_manager(), ExecutionContext::Importing => self.strategies.importing.get_manager(), - ExecutionContext::OffchainCall(Some((_, capabilities))) if capabilities.has_all() => + ExecutionContext::OffchainCall(Some((_, capabilities))) if capabilities.is_all() => self.strategies.offchain_worker.get_manager(), ExecutionContext::OffchainCall(_) => self.strategies.other.get_manager(), }; - (manager, self.extensions(at, context)) + (manager, self.extensions(block_hash, block_number, context)) } } /// A wrapper type to pass `BlockId` to the actual transaction pool. -struct TransactionPoolAdapter { +struct TransactionPoolAdapter { at: BlockId, pool: Arc>, } -impl offchain::TransactionPool for TransactionPoolAdapter { +impl offchain::TransactionPool for TransactionPoolAdapter { fn submit_transaction(&mut self, data: Vec) -> Result<(), ()> { let xt = match Block::Extrinsic::decode(&mut &*data) { Ok(xt) => xt, diff --git a/client/api/src/in_mem.rs b/client/api/src/in_mem.rs index e8fce19f8124e..5a3e25ab5987b 100644 --- a/client/api/src/in_mem.rs +++ b/client/api/src/in_mem.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -26,11 +26,11 @@ use sp_core::{ use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, HashFor, Header as HeaderT, NumberFor, Zero}, - Justification, Justifications, Storage, + Justification, Justifications, StateVersion, Storage, }; use sp_state_machine::{ - Backend as StateBackend, ChangesTrieTransaction, ChildStorageCollection, InMemoryBackend, - IndexOperation, StorageCollection, + Backend as StateBackend, ChildStorageCollection, InMemoryBackend, IndexOperation, + StorageCollection, }; use std::{ collections::{HashMap, HashSet}, @@ -39,10 +39,10 @@ use std::{ }; use crate::{ - backend::{self, NewBlockState, ProvideChtRoots}, + backend::{self, NewBlockState}, blockchain::{self, well_known_cache_keys::Id as CacheKeyId, BlockStatus, HeaderBackend}, leaves::LeafSet, - light, UsageInfo, + UsageInfo, }; struct PendingBlock { @@ -109,7 +109,6 @@ struct BlockchainStorage { finalized_number: NumberFor, genesis_hash: Block::Hash, header_cht_roots: HashMap, Block::Hash>, - changes_trie_cht_roots: HashMap, Block::Hash>, leaves: LeafSet>, aux: HashMap, Vec>, } @@ -152,7 +151,6 @@ impl Blockchain { finalized_number: Zero::zero(), genesis_hash: Default::default(), header_cht_roots: HashMap::new(), - changes_trie_cht_roots: HashMap::new(), leaves: LeafSet::new(), aux: HashMap::new(), })); @@ -168,23 +166,19 @@ impl Blockchain { body: Option::Extrinsic>>, new_state: NewBlockState, ) -> sp_blockchain::Result<()> { - let number = header.number().clone(); + let number = *header.number(); if new_state.is_best() { self.apply_head(&header)?; } { let mut storage = self.storage.write(); - storage - .leaves - .import(hash.clone(), number.clone(), header.parent_hash().clone()); - storage - .blocks - .insert(hash.clone(), StoredBlock::new(header, body, justifications)); + storage.leaves.import(hash, number, *header.parent_hash()); + storage.blocks.insert(hash, StoredBlock::new(header, body, justifications)); if let NewBlockState::Final = new_state { storage.finalized_hash = hash; - storage.finalized_number = number.clone(); + storage.finalized_number = number; } if number == Zero::zero() { @@ -229,10 +223,10 @@ impl Blockchain { } /// Set an existing block as head. - pub fn set_head(&self, id: BlockId) -> sp_blockchain::Result<()> { + pub fn set_head(&self, hash: Block::Hash) -> sp_blockchain::Result<()> { let header = self - .header(id)? - .ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", id)))?; + .header(BlockId::Hash(hash))? + .ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", hash)))?; self.apply_head(&header) } @@ -268,30 +262,25 @@ impl Blockchain { } } - storage.best_hash = hash.clone(); - storage.best_number = number.clone(); - storage.hashes.insert(number.clone(), hash.clone()); + storage.best_hash = hash; + storage.best_number = *number; + storage.hashes.insert(*number, hash); Ok(()) } fn finalize_header( &self, - id: BlockId, + block: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()> { - let hash = match self.header(id)? { - Some(h) => h.hash(), - None => return Err(sp_blockchain::Error::UnknownBlock(format!("{}", id))), - }; - let mut storage = self.storage.write(); - storage.finalized_hash = hash; + storage.finalized_hash = block; if justification.is_some() { let block = storage .blocks - .get_mut(&hash) + .get_mut(&block) .expect("hash was fetched from a block in the db; qed"); let block_justifications = match block { @@ -306,10 +295,9 @@ impl Blockchain { fn append_justification( &self, - id: BlockId, + hash: Block::Hash, justification: Justification, ) -> sp_blockchain::Result<()> { - let hash = self.expect_block_hash_from_id(&id)?; let mut storage = self.storage.write(); let block = storage @@ -364,11 +352,12 @@ impl HeaderBackend for Blockchain { finalized_hash: storage.finalized_hash, finalized_number: storage.finalized_number, finalized_state: if storage.finalized_hash != Default::default() { - Some((storage.finalized_hash.clone(), storage.finalized_number)) + Some((storage.finalized_hash, storage.finalized_number)) } else { None }, number_leaves: storage.leaves.count(), + block_gap: None, } } @@ -416,61 +405,58 @@ impl HeaderMetadata for Blockchain { impl blockchain::Backend for Blockchain { fn body( &self, - id: BlockId, + hash: Block::Hash, ) -> sp_blockchain::Result::Extrinsic>>> { - Ok(self.id(id).and_then(|hash| { - self.storage - .read() - .blocks - .get(&hash) - .and_then(|b| b.extrinsics().map(|x| x.to_vec())) - })) + Ok(self + .storage + .read() + .blocks + .get(&hash) + .and_then(|b| b.extrinsics().map(|x| x.to_vec()))) } - fn justifications(&self, id: BlockId) -> sp_blockchain::Result> { - Ok(self.id(id).and_then(|hash| { - self.storage - .read() - .blocks - .get(&hash) - .and_then(|b| b.justifications().map(|x| x.clone())) - })) + fn justifications(&self, hash: Block::Hash) -> sp_blockchain::Result> { + Ok(self.storage.read().blocks.get(&hash).and_then(|b| b.justifications().cloned())) } fn last_finalized(&self) -> sp_blockchain::Result { - Ok(self.storage.read().finalized_hash.clone()) - } - - fn cache(&self) -> Option>> { - None + Ok(self.storage.read().finalized_hash) } fn leaves(&self) -> sp_blockchain::Result> { Ok(self.storage.read().leaves.hashes()) } + fn displaced_leaves_after_finalizing( + &self, + block_number: NumberFor, + ) -> sp_blockchain::Result> { + Ok(self + .storage + .read() + .leaves + .displaced_by_finalize_height(block_number) + .leaves() + .cloned() + .collect::>()) + } + fn children(&self, _parent_hash: Block::Hash) -> sp_blockchain::Result> { unimplemented!() } - fn indexed_transaction(&self, _hash: &Block::Hash) -> sp_blockchain::Result>> { + fn indexed_transaction(&self, _hash: Block::Hash) -> sp_blockchain::Result>> { unimplemented!("Not supported by the in-mem backend.") } fn block_indexed_body( &self, - _id: BlockId, + _hash: Block::Hash, ) -> sp_blockchain::Result>>> { unimplemented!("Not supported by the in-mem backend.") } } -impl blockchain::ProvideCache for Blockchain { - fn cache(&self) -> Option>> { - None - } -} - impl backend::AuxStore for Blockchain { fn insert_aux< 'a, @@ -498,82 +484,6 @@ impl backend::AuxStore for Blockchain { } } -impl light::Storage for Blockchain -where - Block::Hash: From<[u8; 32]>, -{ - fn import_header( - &self, - header: Block::Header, - _cache: HashMap>, - state: NewBlockState, - aux_ops: Vec<(Vec, Option>)>, - ) -> sp_blockchain::Result<()> { - let hash = header.hash(); - self.insert(hash, header, None, None, state)?; - - self.write_aux(aux_ops); - Ok(()) - } - - fn set_head(&self, id: BlockId) -> sp_blockchain::Result<()> { - Blockchain::set_head(self, id) - } - - fn last_finalized(&self) -> sp_blockchain::Result { - Ok(self.storage.read().finalized_hash.clone()) - } - - fn finalize_header(&self, id: BlockId) -> sp_blockchain::Result<()> { - Blockchain::finalize_header(self, id, None) - } - - fn cache(&self) -> Option>> { - None - } - - fn usage_info(&self) -> Option { - None - } -} - -impl ProvideChtRoots for Blockchain { - fn header_cht_root( - &self, - _cht_size: NumberFor, - block: NumberFor, - ) -> sp_blockchain::Result> { - self.storage - .read() - .header_cht_roots - .get(&block) - .cloned() - .ok_or_else(|| { - sp_blockchain::Error::Backend(format!("Header CHT for block {} not exists", block)) - }) - .map(Some) - } - - fn changes_trie_cht_root( - &self, - _cht_size: NumberFor, - block: NumberFor, - ) -> sp_blockchain::Result> { - self.storage - .read() - .changes_trie_cht_roots - .get(&block) - .cloned() - .ok_or_else(|| { - sp_blockchain::Error::Backend(format!( - "Changes trie CHT for block {} not exists", - block - )) - }) - .map(Some) - } -} - /// In-memory operation. pub struct BlockImportOperation { pending_block: Option>, @@ -581,8 +491,8 @@ pub struct BlockImportOperation { new_state: Option<> as StateBackend>>::Transaction>, aux: Vec<(Vec, Option>)>, - finalized_blocks: Vec<(BlockId, Option)>, - set_head: Option>, + finalized_blocks: Vec<(Block::Hash, Option)>, + set_head: Option, } impl BlockImportOperation @@ -593,10 +503,11 @@ where &mut self, storage: Storage, commit: bool, + state_version: StateVersion, ) -> sp_blockchain::Result { check_genesis_storage(&storage)?; - let child_delta = storage.children_default.iter().map(|(_storage_key, child_content)| { + let child_delta = storage.children_default.values().map(|child_content| { ( &child_content.child_info, child_content.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), @@ -606,6 +517,7 @@ where let (root, transaction) = self.old_state.full_storage_root( storage.top.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), child_delta, + state_version, ); if commit { @@ -649,23 +561,21 @@ where Ok(()) } - fn update_changes_trie( - &mut self, - _update: ChangesTrieTransaction, NumberFor>, - ) -> sp_blockchain::Result<()> { - Ok(()) - } - fn set_genesis_state( &mut self, storage: Storage, commit: bool, + state_version: StateVersion, ) -> sp_blockchain::Result { - self.apply_storage(storage, commit) + self.apply_storage(storage, commit, state_version) } - fn reset_storage(&mut self, storage: Storage) -> sp_blockchain::Result { - self.apply_storage(storage, true) + fn reset_storage( + &mut self, + storage: Storage, + state_version: StateVersion, + ) -> sp_blockchain::Result { + self.apply_storage(storage, true, state_version) } fn insert_aux(&mut self, ops: I) -> sp_blockchain::Result<()> @@ -686,16 +596,16 @@ where fn mark_finalized( &mut self, - block: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()> { - self.finalized_blocks.push((block, justification)); + self.finalized_blocks.push((hash, justification)); Ok(()) } - fn mark_head(&mut self, block: BlockId) -> sp_blockchain::Result<()> { + fn mark_head(&mut self, hash: Block::Hash) -> sp_blockchain::Result<()> { assert!(self.pending_block.is_none(), "Only one set block per operation is allowed"); - self.set_head = Some(block); + self.set_head = Some(hash); Ok(()) } @@ -767,7 +677,7 @@ where type OffchainStorage = OffchainStorage; fn begin_operation(&self) -> sp_blockchain::Result { - let old_state = self.state_at(BlockId::Hash(Default::default()))?; + let old_state = self.state_at(Default::default())?; Ok(BlockImportOperation { pending_block: None, old_state, @@ -781,7 +691,7 @@ where fn begin_state_operation( &self, operation: &mut Self::BlockImportOperation, - block: BlockId, + block: Block::Hash, ) -> sp_blockchain::Result<()> { operation.old_state = self.state_at(block)?; Ok(()) @@ -823,18 +733,18 @@ where fn finalize_block( &self, - block: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()> { - self.blockchain.finalize_header(block, justification) + self.blockchain.finalize_header(hash, justification) } fn append_justification( &self, - block: BlockId, + hash: Block::Hash, justification: Justification, ) -> sp_blockchain::Result<()> { - self.blockchain.append_justification(block, justification) + self.blockchain.append_justification(hash, justification) } fn blockchain(&self) -> &Self::Blockchain { @@ -845,24 +755,20 @@ where None } - fn changes_trie_storage(&self) -> Option<&dyn backend::PrunableStateChangesTrieStorage> { - None - } - fn offchain_storage(&self) -> Option { None } - fn state_at(&self, block: BlockId) -> sp_blockchain::Result { - match block { - BlockId::Hash(h) if h == Default::default() => return Ok(Self::State::default()), - _ => {}, + fn state_at(&self, hash: Block::Hash) -> sp_blockchain::Result { + if hash == Default::default() { + return Ok(Self::State::default()) } - self.blockchain - .id(block) - .and_then(|id| self.states.read().get(&id).cloned()) - .ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", block))) + self.states + .read() + .get(&hash) + .cloned() + .ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", hash))) } fn revert( @@ -873,45 +779,33 @@ where Ok((Zero::zero(), HashSet::new())) } - fn remove_leaf_block(&self, _hash: &Block::Hash) -> sp_blockchain::Result<()> { + fn remove_leaf_block(&self, _hash: Block::Hash) -> sp_blockchain::Result<()> { Ok(()) } fn get_import_lock(&self) -> &RwLock<()> { &self.import_lock } -} - -impl backend::LocalBackend for Backend where Block::Hash: Ord {} - -impl backend::RemoteBackend for Backend -where - Block::Hash: Ord, -{ - fn is_local_state_available(&self, block: &BlockId) -> bool { - self.blockchain - .expect_block_number_from_id(block) - .map(|num| num.is_zero()) - .unwrap_or(false) - } - fn remote_blockchain(&self) -> Arc> { - unimplemented!() + fn requires_full_sync(&self) -> bool { + false } } +impl backend::LocalBackend for Backend where Block::Hash: Ord {} + /// Check that genesis storage is valid. pub fn check_genesis_storage(storage: &Storage) -> sp_blockchain::Result<()> { if storage.top.iter().any(|(k, _)| well_known_keys::is_child_storage_key(k)) { - return Err(sp_blockchain::Error::InvalidState.into()) + return Err(sp_blockchain::Error::InvalidState) } if storage .children_default .keys() - .any(|child_key| !well_known_keys::is_child_storage_key(&child_key)) + .any(|child_key| !well_known_keys::is_child_storage_key(child_key)) { - return Err(sp_blockchain::Error::InvalidState.into()) + return Err(sp_blockchain::Error::InvalidState) } Ok(()) @@ -920,7 +814,7 @@ pub fn check_genesis_storage(storage: &Storage) -> sp_blockchain::Result<()> { #[cfg(test)] mod tests { use crate::{in_mem::Blockchain, NewBlockState}; - use sp_api::{BlockId, HeaderT}; + use sp_api::HeaderT; use sp_blockchain::Backend; use sp_runtime::{ConsensusEngineId, Justifications}; use substrate_test_runtime::{Block, Header, H256}; @@ -967,26 +861,24 @@ mod tests { fn append_and_retrieve_justifications() { let blockchain = test_blockchain(); let last_finalized = blockchain.last_finalized().unwrap(); - let block = BlockId::Hash(last_finalized); - blockchain.append_justification(block, (ID2, vec![4])).unwrap(); + blockchain.append_justification(last_finalized, (ID2, vec![4])).unwrap(); let justifications = { let mut just = Justifications::from((ID1, vec![3])); just.append((ID2, vec![4])); just }; - assert_eq!(blockchain.justifications(block).unwrap(), Some(justifications)); + assert_eq!(blockchain.justifications(last_finalized).unwrap(), Some(justifications)); } #[test] fn store_duplicate_justifications_is_forbidden() { let blockchain = test_blockchain(); let last_finalized = blockchain.last_finalized().unwrap(); - let block = BlockId::Hash(last_finalized); - blockchain.append_justification(block, (ID2, vec![0])).unwrap(); + blockchain.append_justification(last_finalized, (ID2, vec![0])).unwrap(); assert!(matches!( - blockchain.append_justification(block, (ID2, vec![1])), + blockchain.append_justification(last_finalized, (ID2, vec![1])), Err(sp_blockchain::Error::BadJustification(_)), )); } diff --git a/client/api/src/leaves.rs b/client/api/src/leaves.rs index 80216bc4664bd..cdcb80a110b74 100644 --- a/client/api/src/leaves.rs +++ b/client/api/src/leaves.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -32,32 +32,36 @@ struct LeafSetItem { number: Reverse, } -/// A displaced leaf after import. -#[must_use = "Displaced items from the leaf set must be handled."] -pub struct ImportDisplaced { - new_hash: H, - displaced: LeafSetItem, +/// Inserted and removed leaves after an import action. +pub struct ImportOutcome { + inserted: LeafSetItem, + removed: Option, } -/// Displaced leaves after finalization. -#[must_use = "Displaced items from the leaf set must be handled."] -pub struct FinalizationDisplaced { - leaves: BTreeMap, Vec>, +/// Inserted and removed leaves after a remove action. +pub struct RemoveOutcome { + inserted: Option, + removed: LeafSetItem, } -impl FinalizationDisplaced { +/// Removed leaves after a finalization action. +pub struct FinalizationOutcome { + removed: BTreeMap, Vec>, +} + +impl FinalizationOutcome { /// Merge with another. This should only be used for displaced items that /// are produced within one transaction of each other. pub fn merge(&mut self, mut other: Self) { // this will ignore keys that are in duplicate, however // if these are actually produced correctly via the leaf-set within // one transaction, then there will be no overlap in the keys. - self.leaves.append(&mut other.leaves); + self.removed.append(&mut other.removed); } /// Iterate over all displaced leaves. - pub fn leaves(&self) -> impl IntoIterator { - self.leaves.values().flatten() + pub fn leaves(&self) -> impl Iterator { + self.removed.values().flatten() } } @@ -67,8 +71,6 @@ impl FinalizationDisplaced { #[derive(Debug, Clone, PartialEq, Eq)] pub struct LeafSet { storage: BTreeMap, Vec>, - pending_added: Vec<(H, N)>, - pending_removed: Vec, } impl LeafSet @@ -78,7 +80,7 @@ where { /// Construct a new, blank leaf set. pub fn new() -> Self { - Self { storage: BTreeMap::new(), pending_added: Vec::new(), pending_removed: Vec::new() } + Self { storage: BTreeMap::new() } } /// Read the leaf list from the DB, using given prefix for keys. @@ -97,32 +99,56 @@ where }, None => {}, } - Ok(Self { storage, pending_added: Vec::new(), pending_removed: Vec::new() }) + Ok(Self { storage }) } - /// update the leaf list on import. returns a displaced leaf if there was one. - pub fn import(&mut self, hash: H, number: N, parent_hash: H) -> Option> { - // avoid underflow for genesis. - let displaced = if number != N::zero() { - let new_number = Reverse(number.clone() - N::one()); - let was_displaced = self.remove_leaf(&new_number, &parent_hash); - - if was_displaced { - self.pending_removed.push(parent_hash.clone()); - Some(ImportDisplaced { - new_hash: hash.clone(), - displaced: LeafSetItem { hash: parent_hash, number: new_number }, - }) - } else { - None - } + /// Update the leaf list on import. + pub fn import(&mut self, hash: H, number: N, parent_hash: H) -> ImportOutcome { + let number = Reverse(number); + + let removed = if number.0 != N::zero() { + let parent_number = Reverse(number.0.clone() - N::one()); + self.remove_leaf(&parent_number, &parent_hash).then(|| parent_hash) } else { None }; - self.insert_leaf(Reverse(number.clone()), hash.clone()); - self.pending_added.push((hash, number)); - displaced + self.insert_leaf(number.clone(), hash.clone()); + + ImportOutcome { inserted: LeafSetItem { hash, number }, removed } + } + + /// Update the leaf list on removal. + /// + /// Note that the leaves set structure doesn't have the information to decide if the + /// leaf we're removing is the last children of the parent. Follows that this method requires + /// the caller to check this condition and optionally pass the `parent_hash` if `hash` is + /// its last child. + /// + /// Returns `None` if no modifications are applied. + pub fn remove( + &mut self, + hash: H, + number: N, + parent_hash: Option, + ) -> Option> { + let number = Reverse(number); + + if !self.remove_leaf(&number, &hash) { + return None + } + + let inserted = parent_hash.and_then(|parent_hash| { + if number.0 != N::zero() { + let parent_number = Reverse(number.0.clone() - N::one()); + self.insert_leaf(parent_number, parent_hash.clone()); + Some(parent_hash) + } else { + None + } + }); + + Some(RemoveOutcome { inserted, removed: LeafSetItem { hash, number } }) } /// Note a block height finalized, displacing all leaves with number less than the finalized @@ -132,17 +158,33 @@ where /// same number as the finalized block, but with different hashes, the current behavior /// is simpler and our assumptions about how finalization works means that those leaves /// will be pruned soon afterwards anyway. - pub fn finalize_height(&mut self, number: N) -> FinalizationDisplaced { + pub fn finalize_height(&mut self, number: N) -> FinalizationOutcome { let boundary = if number == N::zero() { - return FinalizationDisplaced { leaves: BTreeMap::new() } + return FinalizationOutcome { removed: BTreeMap::new() } } else { number - N::one() }; let below_boundary = self.storage.split_off(&Reverse(boundary)); - self.pending_removed - .extend(below_boundary.values().flat_map(|h| h.iter()).cloned()); - FinalizationDisplaced { leaves: below_boundary } + FinalizationOutcome { removed: below_boundary } + } + + /// The same as [`Self::finalize_height`], but it only simulates the operation. + /// + /// This means that no changes are done. + /// + /// Returns the leaves that would be displaced by finalizing the given block. + pub fn displaced_by_finalize_height(&self, number: N) -> FinalizationOutcome { + let boundary = if number == N::zero() { + return FinalizationOutcome { removed: BTreeMap::new() } + } else { + number - N::one() + }; + + let below_boundary = self.storage.range(&Reverse(boundary)..); + FinalizationOutcome { + removed: below_boundary.map(|(k, v)| (k.clone(), v.clone())).collect(), + } } /// Undo all pending operations. @@ -170,8 +212,6 @@ where self.remove_leaf(number, hash), "item comes from an iterator over storage; qed", ); - - self.pending_removed.push(hash.clone()); } } @@ -185,7 +225,6 @@ where // this is an invariant of regular block import. if !leaves_contains_best { self.insert_leaf(best_number.clone(), best_hash.clone()); - self.pending_added.push((best_hash, best_number.0)); } } @@ -195,9 +234,9 @@ where self.storage.iter().flat_map(|(_, hashes)| hashes.iter()).cloned().collect() } - /// Number of known leaves + /// Number of known leaves. pub fn count(&self) -> usize { - self.storage.len() + self.storage.values().map(|level| level.len()).sum() } /// Write the leaf list to the database transaction. @@ -209,8 +248,6 @@ where ) { let leaves: Vec<_> = self.storage.iter().map(|(n, h)| (n.0.clone(), h.clone())).collect(); tx.set_from_vec(column, prefix, leaves.encode()); - self.pending_added.clear(); - self.pending_removed.clear(); } /// Check if given block is a leaf. @@ -224,7 +261,7 @@ where self.storage.entry(number).or_insert_with(Vec::new).push(hash); } - // returns true if this leaf was contained, false otherwise. + // Returns true if this leaf was contained, false otherwise. fn remove_leaf(&mut self, number: &Reverse, hash: &H) -> bool { let mut empty = false; let removed = self.storage.get_mut(number).map_or(false, |leaves| { @@ -251,6 +288,11 @@ where removed } + + /// Returns the highest leaf and all hashes associated to it. + pub fn highest_leaf(&self) -> Option<(N, &[H])> { + self.storage.iter().next().map(|(k, v)| (k.0.clone(), &v[..])) + } } /// Helper for undoing operations. @@ -263,23 +305,30 @@ where H: Clone + PartialEq + Decode + Encode, N: std::fmt::Debug + Clone + AtLeast32Bit + Decode + Encode, { - /// Undo an imported block by providing the displaced leaf. - pub fn undo_import(&mut self, displaced: ImportDisplaced) { - let new_number = Reverse(displaced.displaced.number.0.clone() + N::one()); - self.inner.remove_leaf(&new_number, &displaced.new_hash); - self.inner.insert_leaf(new_number, displaced.displaced.hash); + /// Undo an imported block by providing the import operation outcome. + /// No additional operations should be performed between import and undo. + pub fn undo_import(&mut self, outcome: ImportOutcome) { + if let Some(removed_hash) = outcome.removed { + let removed_number = Reverse(outcome.inserted.number.0.clone() - N::one()); + self.inner.insert_leaf(removed_number, removed_hash); + } + self.inner.remove_leaf(&outcome.inserted.number, &outcome.inserted.hash); } - /// Undo a finalization operation by providing the displaced leaves. - pub fn undo_finalization(&mut self, mut displaced: FinalizationDisplaced) { - self.inner.storage.append(&mut displaced.leaves); + /// Undo a removed block by providing the displaced leaf. + /// No additional operations should be performed between remove and undo. + pub fn undo_remove(&mut self, outcome: RemoveOutcome) { + if let Some(inserted_hash) = outcome.inserted { + let inserted_number = Reverse(outcome.removed.number.0.clone() - N::one()); + self.inner.remove_leaf(&inserted_number, &inserted_hash); + } + self.inner.insert_leaf(outcome.removed.number, outcome.removed.hash); } -} -impl<'a, H: 'a, N: 'a> Drop for Undo<'a, H, N> { - fn drop(&mut self) { - self.inner.pending_added.clear(); - self.inner.pending_removed.clear(); + /// Undo a finalization operation by providing the displaced leaves. + /// No additional operations should be performed between finalization and undo. + pub fn undo_finalization(&mut self, mut outcome: FinalizationOutcome) { + self.inner.storage.append(&mut outcome.removed); } } @@ -289,7 +338,7 @@ mod tests { use std::sync::Arc; #[test] - fn it_works() { + fn import_works() { let mut set = LeafSet::new(); set.import(0u32, 0u32, 0u32); @@ -297,15 +346,104 @@ mod tests { set.import(2_1, 2, 1_1); set.import(3_1, 3, 2_1); + assert_eq!(set.count(), 1); assert!(set.contains(3, 3_1)); assert!(!set.contains(2, 2_1)); assert!(!set.contains(1, 1_1)); assert!(!set.contains(0, 0)); set.import(2_2, 2, 1_1); + set.import(1_2, 1, 0); + set.import(2_3, 2, 1_2); + + assert_eq!(set.count(), 3); + assert!(set.contains(3, 3_1)); + assert!(set.contains(2, 2_2)); + assert!(set.contains(2, 2_3)); + // Finally test the undo feature + + let outcome = set.import(2_4, 2, 1_1); + assert_eq!(outcome.inserted.hash, 2_4); + assert_eq!(outcome.removed, None); + assert_eq!(set.count(), 4); + assert!(set.contains(2, 2_4)); + + set.undo().undo_import(outcome); + assert_eq!(set.count(), 3); + assert!(set.contains(3, 3_1)); + assert!(set.contains(2, 2_2)); + assert!(set.contains(2, 2_3)); + + let outcome = set.import(3_2, 3, 2_3); + assert_eq!(outcome.inserted.hash, 3_2); + assert_eq!(outcome.removed, Some(2_3)); + assert_eq!(set.count(), 3); + assert!(set.contains(3, 3_2)); + + set.undo().undo_import(outcome); + assert_eq!(set.count(), 3); assert!(set.contains(3, 3_1)); assert!(set.contains(2, 2_2)); + assert!(set.contains(2, 2_3)); + } + + #[test] + fn removal_works() { + let mut set = LeafSet::new(); + set.import(10_1u32, 10u32, 0u32); + set.import(11_1, 11, 10_1); + set.import(11_2, 11, 10_1); + set.import(12_1, 12, 11_1); + + let outcome = set.remove(12_1, 12, Some(11_1)).unwrap(); + assert_eq!(outcome.removed.hash, 12_1); + assert_eq!(outcome.inserted, Some(11_1)); + assert_eq!(set.count(), 2); + assert!(set.contains(11, 11_1)); + assert!(set.contains(11, 11_2)); + + let outcome = set.remove(11_1, 11, None).unwrap(); + assert_eq!(outcome.removed.hash, 11_1); + assert_eq!(outcome.inserted, None); + assert_eq!(set.count(), 1); + assert!(set.contains(11, 11_2)); + + let outcome = set.remove(11_2, 11, Some(10_1)).unwrap(); + assert_eq!(outcome.removed.hash, 11_2); + assert_eq!(outcome.inserted, Some(10_1)); + assert_eq!(set.count(), 1); + assert!(set.contains(10, 10_1)); + + set.undo().undo_remove(outcome); + assert_eq!(set.count(), 1); + assert!(set.contains(11, 11_2)); + } + + #[test] + fn finalization_works() { + let mut set = LeafSet::new(); + set.import(9_1u32, 9u32, 0u32); + set.import(10_1, 10, 9_1); + set.import(10_2, 10, 9_1); + set.import(11_1, 11, 10_1); + set.import(11_2, 11, 10_1); + set.import(12_1, 12, 11_2); + + let outcome = set.finalize_height(11); + assert_eq!(set.count(), 2); + assert!(set.contains(11, 11_1)); + assert!(set.contains(12, 12_1)); + assert_eq!( + outcome.removed, + [(Reverse(10), vec![10_2])].into_iter().collect::>(), + ); + + set.undo().undo_finalization(outcome); + assert_eq!(set.count(), 3); + assert!(set.contains(11, 11_1)); + assert!(set.contains(12, 12_1)); + assert!(set.contains(10, 10_2)); } #[test] @@ -372,19 +510,4 @@ mod tests { let set2 = LeafSet::read_from_db(&*db, 0, PREFIX).unwrap(); assert_eq!(set, set2); } - - #[test] - fn undo_finalization() { - let mut set = LeafSet::new(); - set.import(10_1u32, 10u32, 0u32); - set.import(11_1, 11, 10_2); - set.import(11_2, 11, 10_2); - set.import(12_1, 12, 11_123); - - let displaced = set.finalize_height(11); - assert!(!set.contains(10, 10_1)); - - set.undo().undo_finalization(displaced); - assert!(set.contains(10, 10_1)); - } } diff --git a/client/api/src/lib.rs b/client/api/src/lib.rs index 16935b1e846cf..3d21f12f6940b 100644 --- a/client/api/src/lib.rs +++ b/client/api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,25 +21,22 @@ pub mod backend; pub mod call_executor; -pub mod cht; pub mod client; pub mod execution_extensions; pub mod in_mem; pub mod leaves; -pub mod light; pub mod notifications; pub mod proof_provider; pub use backend::*; pub use call_executor::*; pub use client::*; -pub use light::*; pub use notifications::*; pub use proof_provider::*; pub use sp_blockchain as blockchain; pub use sp_blockchain::HeaderBackend; -pub use sp_state_machine::{ExecutionStrategy, StorageProof}; +pub use sp_state_machine::{CompactProof, ExecutionStrategy, StorageProof}; pub use sp_storage::{ChildInfo, PrefixedStorageKey, StorageData, StorageKey}; /// Usage Information Provider interface @@ -60,10 +57,10 @@ pub mod utils { /// represent the current block `hash` and its `parent hash`, if given the /// function that's returned will assume that `hash` isn't part of the local DB /// yet, and all searches in the DB will instead reference the parent. - pub fn is_descendent_of<'a, Block: BlockT, T>( - client: &'a T, + pub fn is_descendent_of( + client: &T, current: Option<(Block::Hash, Block::Hash)>, - ) -> impl Fn(&Block::Hash, &Block::Hash) -> Result + 'a + ) -> impl Fn(&Block::Hash, &Block::Hash) -> Result + '_ where T: HeaderBackend + HeaderMetadata, { diff --git a/client/api/src/light.rs b/client/api/src/light.rs deleted file mode 100644 index 8638ddf741f30..0000000000000 --- a/client/api/src/light.rs +++ /dev/null @@ -1,372 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Substrate light client interfaces - -use std::{ - collections::{BTreeMap, HashMap}, - future::Future, - sync::Arc, -}; - -use crate::{ - backend::{AuxStore, NewBlockState}, - ProvideChtRoots, UsageInfo, -}; -use sp_blockchain::{ - well_known_cache_keys, Cache as BlockchainCache, Error as ClientError, HeaderBackend, - HeaderMetadata, Result as ClientResult, -}; -use sp_core::{storage::PrefixedStorageKey, ChangesTrieConfigurationRange}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, NumberFor}, -}; -use sp_state_machine::StorageProof; - -/// Remote call request. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct RemoteCallRequest { - /// Call at state of given block. - pub block: Header::Hash, - /// Header of block at which call is performed. - pub header: Header, - /// Method to call. - pub method: String, - /// Call data. - pub call_data: Vec, - /// Number of times to retry request. None means that default RETRY_COUNT is used. - pub retry_count: Option, -} - -/// Remote canonical header request. -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct RemoteHeaderRequest { - /// The root of CHT this block is included in. - pub cht_root: Header::Hash, - /// Number of the header to query. - pub block: Header::Number, - /// Number of times to retry request. None means that default RETRY_COUNT is used. - pub retry_count: Option, -} - -/// Remote storage read request. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct RemoteReadRequest { - /// Read at state of given block. - pub block: Header::Hash, - /// Header of block at which read is performed. - pub header: Header, - /// Storage key to read. - pub keys: Vec>, - /// Number of times to retry request. None means that default RETRY_COUNT is used. - pub retry_count: Option, -} - -/// Remote storage read child request. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct RemoteReadChildRequest { - /// Read at state of given block. - pub block: Header::Hash, - /// Header of block at which read is performed. - pub header: Header, - /// Storage key for child. - pub storage_key: PrefixedStorageKey, - /// Child storage key to read. - pub keys: Vec>, - /// Number of times to retry request. None means that default RETRY_COUNT is used. - pub retry_count: Option, -} - -/// Remote key changes read request. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RemoteChangesRequest { - /// All changes trie configurations that are valid within [first_block; last_block]. - pub changes_trie_configs: Vec>, - /// Query changes from range of blocks, starting (and including) with this hash... - pub first_block: (Header::Number, Header::Hash), - /// ...ending (and including) with this hash. Should come after first_block and - /// be the part of the same fork. - pub last_block: (Header::Number, Header::Hash), - /// Only use digests from blocks up to this hash. Should be last_block OR come - /// after this block and be the part of the same fork. - pub max_block: (Header::Number, Header::Hash), - /// Known changes trie roots for the range of blocks [tries_roots.0..max_block]. - /// Proofs for roots of ascendants of tries_roots.0 are provided by the remote node. - pub tries_roots: (Header::Number, Header::Hash, Vec), - /// Optional Child Storage key to read. - pub storage_key: Option, - /// Storage key to read. - pub key: Vec, - /// Number of times to retry request. None means that default RETRY_COUNT is used. - pub retry_count: Option, -} - -/// Key changes read proof. -#[derive(Debug, PartialEq, Eq)] -pub struct ChangesProof { - /// Max block that has been used in changes query. - pub max_block: Header::Number, - /// All touched nodes of all changes tries. - pub proof: Vec>, - /// All changes tries roots that have been touched AND are missing from - /// the requester' node. It is a map of block number => changes trie root. - pub roots: BTreeMap, - /// The proofs for all changes tries roots that have been touched AND are - /// missing from the requester' node. It is a map of CHT number => proof. - pub roots_proof: StorageProof, -} - -/// Remote block body request -#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] -pub struct RemoteBodyRequest { - /// Header of the requested block body - pub header: Header, - /// Number of times to retry request. None means that default RETRY_COUNT is used. - pub retry_count: Option, -} - -/// Light client data fetcher. Implementations of this trait must check if remote data -/// is correct (see FetchedDataChecker) and return already checked data. -pub trait Fetcher: Send + Sync { - /// Remote header future. - type RemoteHeaderResult: Future> - + Unpin - + Send - + 'static; - /// Remote storage read future. - type RemoteReadResult: Future, Option>>, ClientError>> - + Unpin - + Send - + 'static; - /// Remote call result future. - type RemoteCallResult: Future, ClientError>> + Unpin + Send + 'static; - /// Remote changes result future. - type RemoteChangesResult: Future, u32)>, ClientError>> - + Unpin - + Send - + 'static; - /// Remote block body result future. - type RemoteBodyResult: Future, ClientError>> - + Unpin - + Send - + 'static; - - /// Fetch remote header. - fn remote_header( - &self, - request: RemoteHeaderRequest, - ) -> Self::RemoteHeaderResult; - /// Fetch remote storage value. - fn remote_read(&self, request: RemoteReadRequest) -> Self::RemoteReadResult; - /// Fetch remote storage child value. - fn remote_read_child( - &self, - request: RemoteReadChildRequest, - ) -> Self::RemoteReadResult; - /// Fetch remote call result. - fn remote_call(&self, request: RemoteCallRequest) -> Self::RemoteCallResult; - /// Fetch remote changes ((block number, extrinsic index)) where given key has been changed - /// at a given blocks range. - fn remote_changes( - &self, - request: RemoteChangesRequest, - ) -> Self::RemoteChangesResult; - /// Fetch remote block body - fn remote_body(&self, request: RemoteBodyRequest) -> Self::RemoteBodyResult; -} - -/// Light client remote data checker. -/// -/// Implementations of this trait should not use any prunable blockchain data -/// except that is passed to its methods. -pub trait FetchChecker: Send + Sync { - /// Check remote header proof. - fn check_header_proof( - &self, - request: &RemoteHeaderRequest, - header: Option, - remote_proof: StorageProof, - ) -> ClientResult; - /// Check remote storage read proof. - fn check_read_proof( - &self, - request: &RemoteReadRequest, - remote_proof: StorageProof, - ) -> ClientResult, Option>>>; - /// Check remote storage read proof. - fn check_read_child_proof( - &self, - request: &RemoteReadChildRequest, - remote_proof: StorageProof, - ) -> ClientResult, Option>>>; - /// Check remote method execution proof. - fn check_execution_proof( - &self, - request: &RemoteCallRequest, - remote_proof: StorageProof, - ) -> ClientResult>; - /// Check remote changes query proof. - fn check_changes_proof( - &self, - request: &RemoteChangesRequest, - proof: ChangesProof, - ) -> ClientResult, u32)>>; - /// Check remote body proof. - fn check_body_proof( - &self, - request: &RemoteBodyRequest, - body: Vec, - ) -> ClientResult>; -} - -/// Light client blockchain storage. -pub trait Storage: - AuxStore - + HeaderBackend - + HeaderMetadata - + ProvideChtRoots -{ - /// Store new header. Should refuse to revert any finalized blocks. - /// - /// Takes new authorities, the leaf state of the new block, and - /// any auxiliary storage updates to place in the same operation. - fn import_header( - &self, - header: Block::Header, - cache: HashMap>, - state: NewBlockState, - aux_ops: Vec<(Vec, Option>)>, - ) -> ClientResult<()>; - - /// Set an existing block as new best block. - fn set_head(&self, block: BlockId) -> ClientResult<()>; - - /// Mark historic header as finalized. - fn finalize_header(&self, block: BlockId) -> ClientResult<()>; - - /// Get last finalized header. - fn last_finalized(&self) -> ClientResult; - - /// Get storage cache. - fn cache(&self) -> Option>>; - - /// Get storage usage statistics. - fn usage_info(&self) -> Option; -} - -/// Remote header. -#[derive(Debug)] -pub enum LocalOrRemote { - /// When data is available locally, it is returned. - Local(Data), - /// When data is unavailable locally, the request to fetch it from remote node is returned. - Remote(Request), - /// When data is unknown. - Unknown, -} - -/// Futures-based blockchain backend that either resolves blockchain data -/// locally, or fetches required data from remote node. -pub trait RemoteBlockchain: Send + Sync { - /// Get block header. - fn header( - &self, - id: BlockId, - ) -> ClientResult>>; -} - -/// Returns future that resolves header either locally, or remotely. -pub fn future_header>( - blockchain: &dyn RemoteBlockchain, - fetcher: &F, - id: BlockId, -) -> impl Future, ClientError>> { - use futures::future::{ready, Either, FutureExt}; - - match blockchain.header(id) { - Ok(LocalOrRemote::Remote(request)) => - Either::Left(fetcher.remote_header(request).then(|header| ready(header.map(Some)))), - Ok(LocalOrRemote::Unknown) => Either::Right(ready(Ok(None))), - Ok(LocalOrRemote::Local(local_header)) => Either::Right(ready(Ok(Some(local_header)))), - Err(err) => Either::Right(ready(Err(err))), - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use futures::future::Ready; - use parking_lot::Mutex; - use sp_blockchain::Error as ClientError; - use sp_test_primitives::{Block, Extrinsic, Header}; - - #[derive(Debug, thiserror::Error)] - #[error("Not implemented on test node")] - struct MockError; - - impl Into for MockError { - fn into(self) -> ClientError { - ClientError::Application(Box::new(self)) - } - } - - pub type OkCallFetcher = Mutex>; - - fn not_implemented_in_tests() -> Ready> { - futures::future::ready(Err(MockError.into())) - } - - impl Fetcher for OkCallFetcher { - type RemoteHeaderResult = Ready>; - type RemoteReadResult = Ready, Option>>, ClientError>>; - type RemoteCallResult = Ready, ClientError>>; - type RemoteChangesResult = Ready, u32)>, ClientError>>; - type RemoteBodyResult = Ready, ClientError>>; - - fn remote_header(&self, _request: RemoteHeaderRequest

) -> Self::RemoteHeaderResult { - not_implemented_in_tests() - } - - fn remote_read(&self, _request: RemoteReadRequest
) -> Self::RemoteReadResult { - not_implemented_in_tests() - } - - fn remote_read_child( - &self, - _request: RemoteReadChildRequest
, - ) -> Self::RemoteReadResult { - not_implemented_in_tests() - } - - fn remote_call(&self, _request: RemoteCallRequest
) -> Self::RemoteCallResult { - futures::future::ready(Ok((*self.lock()).clone())) - } - - fn remote_changes( - &self, - _request: RemoteChangesRequest
, - ) -> Self::RemoteChangesResult { - not_implemented_in_tests() - } - - fn remote_body(&self, _request: RemoteBodyRequest
) -> Self::RemoteBodyResult { - not_implemented_in_tests() - } - } -} diff --git a/client/api/src/notifications.rs b/client/api/src/notifications.rs index 1346afd5e54d2..9fcc381f9697e 100644 --- a/client/api/src/notifications.rs +++ b/client/api/src/notifications.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,30 +20,60 @@ use std::{ collections::{HashMap, HashSet}, + pin::Pin, sync::Arc, + task::Poll, }; -use fnv::{FnvHashMap, FnvHashSet}; -use prometheus_endpoint::{register, CounterVec, Opts, Registry, U64}; -use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use futures::Stream; + +use prometheus_endpoint::Registry as PrometheusRegistry; + +use sc_utils::pubsub::{Hub, Receiver}; use sp_core::storage::{StorageData, StorageKey}; use sp_runtime::traits::Block as BlockT; +mod registry; + +use registry::Registry; + +#[cfg(test)] +mod tests; + +/// A type of a message delivered to the subscribers +#[derive(Debug)] +pub struct StorageNotification { + /// The hash of the block + pub block: Hash, + + /// The set of changes + pub changes: StorageChangeSet, +} + /// Storage change set #[derive(Debug)] pub struct StorageChangeSet { - changes: Arc)>>, - child_changes: Arc)>)>>, - filter: Option>, - child_filters: Option>>>, + changes: Arc<[(StorageKey, Option)]>, + child_changes: Arc<[(StorageKey, Vec<(StorageKey, Option)>)]>, + filter: Keys, + child_filters: ChildKeys, } +/// Manages storage listeners. +#[derive(Debug)] +pub struct StorageNotifications(Hub, Registry>); + +/// Type that implements `futures::Stream` of storage change events. +pub struct StorageEventStream(Receiver, Registry>); + +type Keys = Option>; +type ChildKeys = Option>>>; + impl StorageChangeSet { /// Convert the change set into iterator over storage items. - pub fn iter<'a>( - &'a self, - ) -> impl Iterator, &'a StorageKey, Option<&'a StorageData>)> + 'a - { + pub fn iter( + &self, + ) -> impl Iterator, &StorageKey, Option<&StorageData>)> + '_ { let top = self .changes .iter() @@ -73,481 +103,49 @@ impl StorageChangeSet { } } -/// Type that implements `futures::Stream` of storage change events. -pub type StorageEventStream = TracingUnboundedReceiver<(H, StorageChangeSet)>; - -type SubscriberId = u64; - -type SubscribersGauge = CounterVec; - -/// Manages storage listeners. -#[derive(Debug)] -pub struct StorageNotifications { - metrics: Option, - next_id: SubscriberId, - wildcard_listeners: FnvHashSet, - listeners: HashMap>, - child_listeners: HashMap< - StorageKey, - (HashMap>, FnvHashSet), - >, - sinks: FnvHashMap< - SubscriberId, - ( - TracingUnboundedSender<(Block::Hash, StorageChangeSet)>, - Option>, - Option>>>, - ), - >, -} - -impl Default for StorageNotifications { - fn default() -> Self { - Self { - metrics: Default::default(), - next_id: Default::default(), - wildcard_listeners: Default::default(), - listeners: Default::default(), - child_listeners: Default::default(), - sinks: Default::default(), - } +impl Stream for StorageEventStream { + type Item = StorageNotification; + fn poll_next( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + Stream::poll_next(Pin::new(&mut self.get_mut().0), cx) } } impl StorageNotifications { /// Initialize a new StorageNotifications /// optionally pass a prometheus registry to send subscriber metrics to - pub fn new(prometheus_registry: Option) -> Self { - let metrics = prometheus_registry.and_then(|r| { - CounterVec::new( - Opts::new( - "storage_notification_subscribers", - "Number of subscribers in storage notification sytem", - ), - &["action"], // added | removed - ) - .and_then(|g| register(g, &r)) - .ok() - }); + pub fn new(prometheus_registry: Option) -> Self { + let registry = Registry::new(prometheus_registry); + let hub = Hub::new_with_registry("mpsc_storage_notification_items", registry); - StorageNotifications { - metrics, - next_id: Default::default(), - wildcard_listeners: Default::default(), - listeners: Default::default(), - child_listeners: Default::default(), - sinks: Default::default(), - } + StorageNotifications(hub) } + /// Trigger notification to all listeners. /// /// Note the changes are going to be filtered by listener's filter key. /// In fact no event might be sent if clients are not interested in the changes. pub fn trigger( - &mut self, + &self, hash: &Block::Hash, changeset: impl Iterator, Option>)>, child_changeset: impl Iterator< Item = (Vec, impl Iterator, Option>)>), >, ) { - let has_wildcard = !self.wildcard_listeners.is_empty(); - - // early exit if no listeners - if !has_wildcard && self.listeners.is_empty() && self.child_listeners.is_empty() { - return - } - - let mut subscribers = self.wildcard_listeners.clone(); - let mut changes = Vec::new(); - let mut child_changes = Vec::new(); - - // Collect subscribers and changes - for (k, v) in changeset { - let k = StorageKey(k); - let listeners = self.listeners.get(&k); - - if let Some(ref listeners) = listeners { - subscribers.extend(listeners.iter()); - } - - if has_wildcard || listeners.is_some() { - changes.push((k, v.map(StorageData))); - } - } - for (sk, changeset) in child_changeset { - let sk = StorageKey(sk); - if let Some((cl, cw)) = self.child_listeners.get(&sk) { - let mut changes = Vec::new(); - for (k, v) in changeset { - let k = StorageKey(k); - let listeners = cl.get(&k); - - if let Some(ref listeners) = listeners { - subscribers.extend(listeners.iter()); - } - - subscribers.extend(cw.iter()); - - if !cw.is_empty() || listeners.is_some() { - changes.push((k, v.map(StorageData))); - } - } - if !changes.is_empty() { - child_changes.push((sk, changes)); - } - } - } - - // Don't send empty notifications - if changes.is_empty() && child_changes.is_empty() { - return - } - - let changes = Arc::new(changes); - let child_changes = Arc::new(child_changes); - // Trigger the events - - let to_remove = self - .sinks - .iter() - .filter_map(|(subscriber, &(ref sink, ref filter, ref child_filters))| { - let should_remove = { - if subscribers.contains(subscriber) { - sink.unbounded_send(( - hash.clone(), - StorageChangeSet { - changes: changes.clone(), - child_changes: child_changes.clone(), - filter: filter.clone(), - child_filters: child_filters.clone(), - }, - )) - .is_err() - } else { - sink.is_closed() - } - }; - - if should_remove { - Some(subscriber.clone()) - } else { - None - } - }) - .collect::>(); - - for sub_id in to_remove { - self.remove_subscriber(sub_id); - } - } - - fn remove_subscriber_from( - subscriber: &SubscriberId, - filters: &Option>, - listeners: &mut HashMap>, - wildcards: &mut FnvHashSet, - ) { - match filters { - None => { - wildcards.remove(subscriber); - }, - Some(filters) => - for key in filters.iter() { - let remove_key = match listeners.get_mut(key) { - Some(ref mut set) => { - set.remove(subscriber); - set.is_empty() - }, - None => false, - }; - - if remove_key { - listeners.remove(key); - } - }, - } - } - - fn remove_subscriber(&mut self, subscriber: SubscriberId) { - if let Some((_, filters, child_filters)) = self.sinks.remove(&subscriber) { - Self::remove_subscriber_from( - &subscriber, - &filters, - &mut self.listeners, - &mut self.wildcard_listeners, - ); - if let Some(child_filters) = child_filters.as_ref() { - for (c_key, filters) in child_filters { - if let Some((listeners, wildcards)) = self.child_listeners.get_mut(&c_key) { - Self::remove_subscriber_from( - &subscriber, - &filters, - &mut *listeners, - &mut *wildcards, - ); - - if listeners.is_empty() && wildcards.is_empty() { - self.child_listeners.remove(&c_key); - } - } - } - } - if let Some(m) = self.metrics.as_ref() { - m.with_label_values(&[&"removed"]).inc(); - } - } - } - - fn listen_from( - current_id: SubscriberId, - filter_keys: &Option>, - listeners: &mut HashMap>, - wildcards: &mut FnvHashSet, - ) -> Option> { - match filter_keys { - None => { - wildcards.insert(current_id); - None - }, - Some(keys) => Some( - keys.as_ref() - .iter() - .map(|key| { - listeners - .entry(key.clone()) - .or_insert_with(Default::default) - .insert(current_id); - key.clone() - }) - .collect(), - ), - } + self.0.send((hash, changeset, child_changeset)) } /// Start listening for particular storage keys. pub fn listen( - &mut self, + &self, filter_keys: Option<&[StorageKey]>, filter_child_keys: Option<&[(StorageKey, Option>)]>, ) -> StorageEventStream { - self.next_id += 1; - let current_id = self.next_id; - - // add subscriber for every key - let keys = Self::listen_from( - current_id, - &filter_keys, - &mut self.listeners, - &mut self.wildcard_listeners, - ); - let child_keys = filter_child_keys.map(|filter_child_keys| { - filter_child_keys - .iter() - .map(|(c_key, o_keys)| { - let (c_listeners, c_wildcards) = - self.child_listeners.entry(c_key.clone()).or_insert_with(Default::default); - - ( - c_key.clone(), - Self::listen_from(current_id, o_keys, &mut *c_listeners, &mut *c_wildcards), - ) - }) - .collect() - }); - - // insert sink - let (tx, rx) = tracing_unbounded("mpsc_storage_notification_items"); - self.sinks.insert(current_id, (tx, keys, child_keys)); - - if let Some(m) = self.metrics.as_ref() { - m.with_label_values(&[&"added"]).inc(); - } - - rx - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper, H256 as Hash}; - use std::iter::{empty, Empty}; - - type TestChangeSet = ( - Vec<(StorageKey, Option)>, - Vec<(StorageKey, Vec<(StorageKey, Option)>)>, - ); - - #[cfg(test)] - impl From for StorageChangeSet { - fn from(changes: TestChangeSet) -> Self { - // warning hardcoded child trie wildcard to test upon - let child_filters = Some( - [(StorageKey(vec![4]), None), (StorageKey(vec![5]), None)] - .iter() - .cloned() - .collect(), - ); - StorageChangeSet { - changes: Arc::new(changes.0), - child_changes: Arc::new(changes.1), - filter: None, - child_filters, - } - } - } - - #[cfg(test)] - impl PartialEq for StorageChangeSet { - fn eq(&self, other: &Self) -> bool { - self.iter().eq(other.iter()) - } - } - - type Block = RawBlock>; - - #[test] - fn triggering_change_should_notify_wildcard_listeners() { - // given - let mut notifications = StorageNotifications::::default(); - let child_filter = [(StorageKey(vec![4]), None)]; - let mut recv = - futures::executor::block_on_stream(notifications.listen(None, Some(&child_filter[..]))); - - // when - let changeset = vec![(vec![2], Some(vec![3])), (vec![3], None)]; - let c_changeset_1 = vec![(vec![5], Some(vec![4])), (vec![6], None)]; - let c_changeset = vec![(vec![4], c_changeset_1)]; - notifications.trigger( - &Hash::from_low_u64_be(1), - changeset.into_iter(), - c_changeset.into_iter().map(|(a, b)| (a, b.into_iter())), - ); - - // then - assert_eq!( - recv.next().unwrap(), - ( - Hash::from_low_u64_be(1), - ( - vec![ - (StorageKey(vec![2]), Some(StorageData(vec![3]))), - (StorageKey(vec![3]), None), - ], - vec![( - StorageKey(vec![4]), - vec![ - (StorageKey(vec![5]), Some(StorageData(vec![4]))), - (StorageKey(vec![6]), None), - ] - )] - ) - .into() - ) - ); - } - - #[test] - fn should_only_notify_interested_listeners() { - // given - let mut notifications = StorageNotifications::::default(); - let child_filter = [(StorageKey(vec![4]), Some(vec![StorageKey(vec![5])]))]; - let mut recv1 = futures::executor::block_on_stream( - notifications.listen(Some(&[StorageKey(vec![1])]), None), - ); - let mut recv2 = futures::executor::block_on_stream( - notifications.listen(Some(&[StorageKey(vec![2])]), None), - ); - let mut recv3 = futures::executor::block_on_stream( - notifications.listen(Some(&[]), Some(&child_filter)), - ); - - // when - let changeset = vec![(vec![2], Some(vec![3])), (vec![1], None)]; - let c_changeset_1 = vec![(vec![5], Some(vec![4])), (vec![6], None)]; - - let c_changeset = vec![(vec![4], c_changeset_1)]; - notifications.trigger( - &Hash::from_low_u64_be(1), - changeset.into_iter(), - c_changeset.into_iter().map(|(a, b)| (a, b.into_iter())), - ); - - // then - assert_eq!( - recv1.next().unwrap(), - (Hash::from_low_u64_be(1), (vec![(StorageKey(vec![1]), None),], vec![]).into()) - ); - assert_eq!( - recv2.next().unwrap(), - ( - Hash::from_low_u64_be(1), - (vec![(StorageKey(vec![2]), Some(StorageData(vec![3]))),], vec![]).into() - ) - ); - assert_eq!( - recv3.next().unwrap(), - ( - Hash::from_low_u64_be(1), - ( - vec![], - vec![( - StorageKey(vec![4]), - vec![(StorageKey(vec![5]), Some(StorageData(vec![4])))] - ),] - ) - .into() - ) - ); - } - - #[test] - fn should_cleanup_subscribers_if_dropped() { - // given - let mut notifications = StorageNotifications::::default(); - { - let child_filter = [(StorageKey(vec![4]), Some(vec![StorageKey(vec![5])]))]; - let _recv1 = futures::executor::block_on_stream( - notifications.listen(Some(&[StorageKey(vec![1])]), None), - ); - let _recv2 = futures::executor::block_on_stream( - notifications.listen(Some(&[StorageKey(vec![2])]), None), - ); - let _recv3 = futures::executor::block_on_stream(notifications.listen(None, None)); - let _recv4 = - futures::executor::block_on_stream(notifications.listen(None, Some(&child_filter))); - assert_eq!(notifications.listeners.len(), 2); - assert_eq!(notifications.wildcard_listeners.len(), 2); - assert_eq!(notifications.child_listeners.len(), 1); - } - - // when - let changeset = vec![(vec![2], Some(vec![3])), (vec![1], None)]; - let c_changeset = empty::<(_, Empty<_>)>(); - notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter(), c_changeset); - - // then - assert_eq!(notifications.listeners.len(), 0); - assert_eq!(notifications.wildcard_listeners.len(), 0); - assert_eq!(notifications.child_listeners.len(), 0); - } - - #[test] - fn should_not_send_empty_notifications() { - // given - let mut recv = { - let mut notifications = StorageNotifications::::default(); - let recv = futures::executor::block_on_stream(notifications.listen(None, None)); - - // when - let changeset = vec![]; - let c_changeset = empty::<(_, Empty<_>)>(); - notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter(), c_changeset); - recv - }; + let receiver = self.0.subscribe(registry::SubscribeOp { filter_keys, filter_child_keys }); - // then - assert_eq!(recv.next(), None); + StorageEventStream(receiver) } } diff --git a/client/api/src/notifications/registry.rs b/client/api/src/notifications/registry.rs new file mode 100644 index 0000000000000..882d6ed40be67 --- /dev/null +++ b/client/api/src/notifications/registry.rs @@ -0,0 +1,365 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; + +use sp_core::hexdisplay::HexDisplay; + +use fnv::{FnvHashMap, FnvHashSet}; +use prometheus_endpoint::{register, CounterVec, Opts, U64}; + +use sc_utils::{ + id_sequence::SeqID as SubscriberId, + pubsub::{Dispatch, Subscribe, Unsubscribe}, +}; + +type SubscribersGauge = CounterVec; + +/// A command to subscribe with the specified filters. +/// +/// Used by the implementation of [`Subscribe`] trait for [`Registry]. +pub(super) struct SubscribeOp<'a> { + pub filter_keys: Option<&'a [StorageKey]>, + pub filter_child_keys: Option<&'a [(StorageKey, Option>)]>, +} + +#[derive(Debug, Default)] +pub(super) struct Registry { + pub(super) metrics: Option, + pub(super) wildcard_listeners: FnvHashSet, + pub(super) listeners: HashMap>, + pub(super) child_listeners: HashMap< + StorageKey, + (HashMap>, FnvHashSet), + >, + pub(super) sinks: FnvHashMap, +} + +#[derive(Debug)] +pub(super) struct SubscriberSink { + subs_id: SubscriberId, + keys: Keys, + child_keys: ChildKeys, + was_triggered: bool, +} + +impl Drop for SubscriberSink { + fn drop(&mut self) { + if !self.was_triggered { + log::trace!( + target: "storage_notifications", + "Listener was never triggered: id={}, keys={:?}, child_keys={:?}", + self.subs_id, + PrintKeys(&self.keys), + PrintChildKeys(&self.child_keys), + ); + } + } +} + +impl SubscriberSink { + fn new(subs_id: SubscriberId, keys: Keys, child_keys: ChildKeys) -> Self { + Self { subs_id, keys, child_keys, was_triggered: false } + } +} + +impl Registry { + pub(super) fn new(prometheus_registry: Option) -> Self { + let metrics = prometheus_registry.and_then(|r| { + CounterVec::new( + Opts::new( + "substrate_storage_notification_subscribers", + "Number of subscribers in storage notification sytem", + ), + &["action"], // added | removed + ) + .and_then(|g| register(g, &r)) + .ok() + }); + + Registry { metrics, ..Default::default() } + } +} + +impl Unsubscribe for Registry { + fn unsubscribe(&mut self, subs_id: SubscriberId) { + self.remove_subscriber(subs_id); + } +} + +impl<'a> Subscribe> for Registry { + fn subscribe(&mut self, subs_op: SubscribeOp<'a>, subs_id: SubscriberId) { + let SubscribeOp { filter_keys, filter_child_keys } = subs_op; + + let keys = Self::listen_from( + subs_id, + filter_keys.as_ref(), + &mut self.listeners, + &mut self.wildcard_listeners, + ); + + let child_keys = filter_child_keys.map(|filter_child_keys| { + filter_child_keys + .iter() + .map(|(c_key, o_keys)| { + let (c_listeners, c_wildcards) = + self.child_listeners.entry(c_key.clone()).or_default(); + + ( + c_key.clone(), + Self::listen_from( + subs_id, + o_keys.as_ref(), + &mut *c_listeners, + &mut *c_wildcards, + ), + ) + }) + .collect() + }); + + if let Some(m) = self.metrics.as_ref() { + m.with_label_values(&["added"]).inc(); + } + + if self + .sinks + .insert(subs_id, SubscriberSink::new(subs_id, keys, child_keys)) + .is_some() + { + log::warn!("The `subscribe`-method has been passed a non-unique subs_id (in `sc-client-api::notifications`)"); + } + } +} + +impl<'a, Hash, CS, CCS, CCSI> Dispatch<(&'a Hash, CS, CCS)> for Registry +where + Hash: Clone, + CS: Iterator, Option>)>, + CCS: Iterator, CCSI)>, + CCSI: Iterator, Option>)>, +{ + type Item = StorageNotification; + type Ret = (); + + fn dispatch(&mut self, message: (&'a Hash, CS, CCS), dispatch: F) -> Self::Ret + where + F: FnMut(&SubscriberId, Self::Item), + { + let (hash, changeset, child_changeset) = message; + self.trigger(hash, changeset, child_changeset, dispatch); + } +} + +impl Registry { + pub(super) fn trigger( + &mut self, + hash: &Hash, + changeset: impl Iterator, Option>)>, + child_changeset: impl Iterator< + Item = (Vec, impl Iterator, Option>)>), + >, + mut dispatch: F, + ) where + Hash: Clone, + F: FnMut(&SubscriberId, StorageNotification), + { + let has_wildcard = !self.wildcard_listeners.is_empty(); + + // early exit if no listeners + if !has_wildcard && self.listeners.is_empty() && self.child_listeners.is_empty() { + return + } + + let mut subscribers = self.wildcard_listeners.clone(); + let mut changes = Vec::new(); + let mut child_changes = Vec::new(); + + // Collect subscribers and changes + for (k, v) in changeset { + let k = StorageKey(k); + let listeners = self.listeners.get(&k); + + if let Some(listeners) = listeners { + subscribers.extend(listeners.iter()); + } + + if has_wildcard || listeners.is_some() { + changes.push((k, v.map(StorageData))); + } + } + for (sk, changeset) in child_changeset { + let sk = StorageKey(sk); + if let Some((cl, cw)) = self.child_listeners.get(&sk) { + let mut changes = Vec::new(); + for (k, v) in changeset { + let k = StorageKey(k); + let listeners = cl.get(&k); + + if let Some(listeners) = listeners { + subscribers.extend(listeners.iter()); + } + + subscribers.extend(cw.iter()); + + if !cw.is_empty() || listeners.is_some() { + changes.push((k, v.map(StorageData))); + } + } + if !changes.is_empty() { + child_changes.push((sk, changes)); + } + } + } + + // Don't send empty notifications + if changes.is_empty() && child_changes.is_empty() { + return + } + + let changes = Arc::<[_]>::from(changes); + let child_changes = Arc::<[_]>::from(child_changes); + + // Trigger the events + self.sinks.iter_mut().for_each(|(subs_id, sink)| { + if subscribers.contains(subs_id) { + sink.was_triggered = true; + + let storage_change_set = StorageChangeSet { + changes: changes.clone(), + child_changes: child_changes.clone(), + filter: sink.keys.clone(), + child_filters: sink.child_keys.clone(), + }; + + let notification = + StorageNotification { block: hash.clone(), changes: storage_change_set }; + + dispatch(subs_id, notification); + } + }); + } +} + +impl Registry { + fn remove_subscriber(&mut self, subscriber: SubscriberId) -> Option<(Keys, ChildKeys)> { + let sink = self.sinks.remove(&subscriber)?; + + Self::remove_subscriber_from( + subscriber, + &sink.keys, + &mut self.listeners, + &mut self.wildcard_listeners, + ); + if let Some(child_filters) = &sink.child_keys { + for (c_key, filters) in child_filters { + if let Some((listeners, wildcards)) = self.child_listeners.get_mut(c_key) { + Self::remove_subscriber_from( + subscriber, + filters, + &mut *listeners, + &mut *wildcards, + ); + + if listeners.is_empty() && wildcards.is_empty() { + self.child_listeners.remove(c_key); + } + } + } + } + if let Some(m) = self.metrics.as_ref() { + m.with_label_values(&["removed"]).inc(); + } + + Some((sink.keys.clone(), sink.child_keys.clone())) + } + + fn remove_subscriber_from( + subscriber: SubscriberId, + filters: &Keys, + listeners: &mut HashMap>, + wildcards: &mut FnvHashSet, + ) { + match filters { + None => { + wildcards.remove(&subscriber); + }, + Some(filters) => + for key in filters.iter() { + let remove_key = match listeners.get_mut(key) { + Some(ref mut set) => { + set.remove(&subscriber); + set.is_empty() + }, + None => false, + }; + + if remove_key { + listeners.remove(key); + } + }, + } + } + + fn listen_from( + current_id: SubscriberId, + filter_keys: Option>, + listeners: &mut HashMap>, + wildcards: &mut FnvHashSet, + ) -> Keys { + match filter_keys { + None => { + wildcards.insert(current_id); + None + }, + Some(keys) => Some( + keys.as_ref() + .iter() + .map(|key| { + listeners.entry(key.clone()).or_default().insert(current_id); + key.clone() + }) + .collect(), + ), + } + } +} + +pub(super) struct PrintKeys<'a>(pub &'a Keys); +impl<'a> std::fmt::Debug for PrintKeys<'a> { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(keys) = self.0 { + fmt.debug_list().entries(keys.iter().map(HexDisplay::from)).finish() + } else { + write!(fmt, "None") + } + } +} + +pub(super) struct PrintChildKeys<'a>(pub &'a ChildKeys); +impl<'a> std::fmt::Debug for PrintChildKeys<'a> { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(map) = self.0 { + fmt.debug_map() + .entries(map.iter().map(|(key, values)| (HexDisplay::from(key), PrintKeys(values)))) + .finish() + } else { + write!(fmt, "None") + } + } +} diff --git a/client/api/src/notifications/tests.rs b/client/api/src/notifications/tests.rs new file mode 100644 index 0000000000000..2c728de7428dd --- /dev/null +++ b/client/api/src/notifications/tests.rs @@ -0,0 +1,221 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; + +use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper, H256 as Hash}; +use std::iter::{empty, Empty}; + +type TestChangeSet = ( + Vec<(StorageKey, Option)>, + Vec<(StorageKey, Vec<(StorageKey, Option)>)>, +); + +impl From for StorageChangeSet { + fn from(changes: TestChangeSet) -> Self { + // warning hardcoded child trie wildcard to test upon + let child_filters = Some( + [(StorageKey(vec![4]), None), (StorageKey(vec![5]), None)] + .iter() + .cloned() + .collect(), + ); + StorageChangeSet { + changes: From::from(changes.0), + child_changes: From::from(changes.1), + filter: None, + child_filters, + } + } +} + +impl PartialEq for StorageChangeSet { + fn eq(&self, other: &Self) -> bool { + self.iter().eq(other.iter()) + } +} + +type Block = RawBlock>; + +#[test] +fn triggering_change_should_notify_wildcard_listeners() { + // given + let notifications = StorageNotifications::::new(None); + let child_filter = [(StorageKey(vec![4]), None)]; + let mut recv = + futures::executor::block_on_stream(notifications.listen(None, Some(&child_filter[..]))); + + // when + let changeset = vec![(vec![2], Some(vec![3])), (vec![3], None)]; + let c_changeset_1 = vec![(vec![5], Some(vec![4])), (vec![6], None)]; + let c_changeset = vec![(vec![4], c_changeset_1)]; + notifications.trigger( + &Hash::from_low_u64_be(1), + changeset.into_iter(), + c_changeset.into_iter().map(|(a, b)| (a, b.into_iter())), + ); + + // then + assert_eq!( + recv.next().map(StorageNotification::into_fields).unwrap(), + ( + Hash::from_low_u64_be(1), + ( + vec![ + (StorageKey(vec![2]), Some(StorageData(vec![3]))), + (StorageKey(vec![3]), None), + ], + vec![( + StorageKey(vec![4]), + vec![ + (StorageKey(vec![5]), Some(StorageData(vec![4]))), + (StorageKey(vec![6]), None), + ] + )] + ) + .into() + ) + ); +} + +#[test] +fn should_only_notify_interested_listeners() { + // given + let notifications = StorageNotifications::::new(None); + let child_filter = [(StorageKey(vec![4]), Some(vec![StorageKey(vec![5])]))]; + let mut recv1 = futures::executor::block_on_stream( + notifications.listen(Some(&[StorageKey(vec![1])]), None), + ); + let mut recv2 = futures::executor::block_on_stream( + notifications.listen(Some(&[StorageKey(vec![2])]), None), + ); + let mut recv3 = + futures::executor::block_on_stream(notifications.listen(Some(&[]), Some(&child_filter))); + + // when + let changeset = vec![(vec![2], Some(vec![3])), (vec![1], None)]; + let c_changeset_1 = vec![(vec![5], Some(vec![4])), (vec![6], None)]; + + let c_changeset = vec![(vec![4], c_changeset_1)]; + notifications.trigger( + &Hash::from_low_u64_be(1), + changeset.into_iter(), + c_changeset.into_iter().map(|(a, b)| (a, b.into_iter())), + ); + + // then + assert_eq!( + recv1.next().map(StorageNotification::into_fields).unwrap(), + (Hash::from_low_u64_be(1), (vec![(StorageKey(vec![1]), None),], vec![]).into()) + ); + assert_eq!( + recv2.next().map(StorageNotification::into_fields).unwrap(), + ( + Hash::from_low_u64_be(1), + (vec![(StorageKey(vec![2]), Some(StorageData(vec![3]))),], vec![]).into() + ) + ); + assert_eq!( + recv3.next().map(StorageNotification::into_fields).unwrap(), + ( + Hash::from_low_u64_be(1), + ( + vec![], + vec![( + StorageKey(vec![4]), + vec![(StorageKey(vec![5]), Some(StorageData(vec![4])))] + ),] + ) + .into() + ) + ); +} + +#[test] +fn should_cleanup_subscribers_if_dropped() { + // given + let notifications = StorageNotifications::::new(None); + { + let child_filter = [(StorageKey(vec![4]), Some(vec![StorageKey(vec![5])]))]; + let _recv1 = futures::executor::block_on_stream( + notifications.listen(Some(&[StorageKey(vec![1])]), None), + ); + let _recv2 = futures::executor::block_on_stream( + notifications.listen(Some(&[StorageKey(vec![2])]), None), + ); + let _recv3 = futures::executor::block_on_stream(notifications.listen(None, None)); + let _recv4 = + futures::executor::block_on_stream(notifications.listen(None, Some(&child_filter))); + assert_eq!(notifications.map_registry(|r| r.listeners.len()), 2); + assert_eq!(notifications.map_registry(|r| r.wildcard_listeners.len()), 2); + assert_eq!(notifications.map_registry(|r| r.child_listeners.len()), 1); + } + + // when + let changeset = vec![(vec![2], Some(vec![3])), (vec![1], None)]; + let c_changeset = empty::<(_, Empty<_>)>(); + notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter(), c_changeset); + + // then + assert_eq!(notifications.map_registry(|r| r.listeners.len()), 0); + assert_eq!(notifications.map_registry(|r| r.wildcard_listeners.len()), 0); + assert_eq!(notifications.map_registry(|r| r.child_listeners.len()), 0); +} + +#[test] +fn should_cleanup_subscriber_if_stream_is_dropped() { + let notifications = StorageNotifications::::new(None); + let stream = notifications.listen(None, None); + assert_eq!(notifications.map_registry(|r| r.sinks.len()), 1); + std::mem::drop(stream); + assert_eq!(notifications.map_registry(|r| r.sinks.len()), 0); +} + +#[test] +fn should_not_send_empty_notifications() { + // given + let mut recv = { + let notifications = StorageNotifications::::new(None); + let recv = futures::executor::block_on_stream(notifications.listen(None, None)); + + // when + let changeset = vec![]; + let c_changeset = empty::<(_, Empty<_>)>(); + notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter(), c_changeset); + recv + }; + + // then + assert_eq!(recv.next().map(StorageNotification::into_fields), None); +} + +impl StorageNotifications { + fn map_registry(&self, map: MapF) -> Ret + where + MapF: FnOnce(&Registry) -> Ret, + { + self.0.map_registry_for_tests(map) + } +} + +impl StorageNotification { + fn into_fields(self) -> (H, StorageChangeSet) { + let Self { block, changes } = self; + (block, changes) + } +} diff --git a/client/api/src/proof_provider.rs b/client/api/src/proof_provider.rs index 79444f0069232..01e35df1dec1c 100644 --- a/client/api/src/proof_provider.rs +++ b/client/api/src/proof_provider.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,16 +17,17 @@ // along with this program. If not, see . //! Proof utilities -use crate::{ChangesProof, StorageProof}; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; -use sp_storage::{ChildInfo, PrefixedStorageKey, StorageKey}; +use crate::{CompactProof, StorageProof}; +use sp_runtime::traits::Block as BlockT; +use sp_state_machine::{KeyValueStates, KeyValueStorageLevel}; +use sp_storage::ChildInfo; /// Interface for providing block proving utilities. pub trait ProofProvider { /// Reads storage value at a given block + key, returning read proof. fn read_proof( &self, - id: &BlockId, + hash: Block::Hash, keys: &mut dyn Iterator, ) -> sp_blockchain::Result; @@ -34,7 +35,7 @@ pub trait ProofProvider { /// read proof. fn read_child_proof( &self, - id: &BlockId, + hash: Block::Hash, child_info: &ChildInfo, keys: &mut dyn Iterator, ) -> sp_blockchain::Result; @@ -45,57 +46,48 @@ pub trait ProofProvider { /// No changes are made. fn execution_proof( &self, - id: &BlockId, + hash: Block::Hash, method: &str, call_data: &[u8], ) -> sp_blockchain::Result<(Vec, StorageProof)>; - /// Reads given header and generates CHT-based header proof. - fn header_proof( - &self, - id: &BlockId, - ) -> sp_blockchain::Result<(Block::Header, StorageProof)>; - - /// Get proof for computation of (block, extrinsic) pairs where key has been changed at given - /// blocks range. `min` is the hash of the first block, which changes trie root is known to the - /// requester - when we're using changes tries from ascendants of this block, we should provide - /// proofs for changes tries roots `max` is the hash of the last block known to the requester - - /// we can't use changes tries from descendants of this block. - /// Works only for runtimes that are supporting changes tries. - fn key_changes_proof( - &self, - first: Block::Hash, - last: Block::Hash, - min: Block::Hash, - max: Block::Hash, - storage_key: Option<&PrefixedStorageKey>, - key: &StorageKey, - ) -> sp_blockchain::Result>; - /// Given a `BlockId` iterate over all storage values starting at `start_key` exclusively, - /// building proofs until size limit is reached. Returns combined proof and the number of - /// collected keys. + /// Given a `Hash` iterate over all storage values starting at `start_keys`. + /// Last `start_keys` element contains last accessed key value. + /// With multiple `start_keys`, first `start_keys` element is + /// the current storage key of of the last accessed child trie. + /// at last level the value to start at exclusively. + /// Proofs is build until size limit is reached and always include at + /// least one key following `start_keys`. + /// Returns combined proof and the numbers of collected keys. fn read_proof_collection( &self, - id: &BlockId, - start_key: &[u8], + hash: Block::Hash, + start_keys: &[Vec], size_limit: usize, - ) -> sp_blockchain::Result<(StorageProof, u32)>; + ) -> sp_blockchain::Result<(CompactProof, u32)>; - /// Given a `BlockId` iterate over all storage values starting at `start_key`. + /// Given a `Hash` iterate over all storage values starting at `start_key`. /// Returns collected keys and values. + /// Returns the collected keys values content of the top trie followed by the + /// collected keys values of child tries. + /// Only child tries with their root part of the collected content or + /// related to `start_key` are attached. + /// For each collected state a boolean indicates if state reach + /// end. fn storage_collection( &self, - id: &BlockId, - start_key: &[u8], + hash: Block::Hash, + start_key: &[Vec], size_limit: usize, - ) -> sp_blockchain::Result, Vec)>>; + ) -> sp_blockchain::Result>; /// Verify read storage proof for a set of keys. - /// Returns collected key-value pairs and a flag indicating if iteration is complete. + /// Returns collected key-value pairs and a the nested state + /// depth of current iteration or 0 if completed. fn verify_range_proof( &self, root: Block::Hash, - proof: StorageProof, - start_key: &[u8], - ) -> sp_blockchain::Result<(Vec<(Vec, Vec)>, bool)>; + proof: CompactProof, + start_keys: &[Vec], + ) -> sp_blockchain::Result<(KeyValueStates, usize)>; } diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 8d5ed20730f0c..0da79bd70ff44 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -2,10 +2,10 @@ name = "sc-authority-discovery" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" build = "build.rs" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate authority discovery." readme = "README.md" @@ -14,30 +14,30 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [build-dependencies] -prost-build = "0.8" +prost-build = "0.11" [dependencies] -async-trait = "0.1" -codec = { package = "parity-scale-codec", default-features = false, version = "2.0.0" } -derive_more = "0.99.2" -futures = "0.3.9" +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +futures = "0.3.21" futures-timer = "3.0.1" -ip_network = "0.4.0" -libp2p = { version = "0.39.1", default-features = false, features = ["kad"] } -log = "0.4.8" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.9.0" } -prost = "0.8" +ip_network = "0.4.1" +libp2p = { version = "0.49.0", default-features = false, features = ["kad"] } +log = "0.4.17" +prost = "0.11" rand = "0.7.2" +thiserror = "1.0" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-network = { version = "0.10.0-dev", path = "../network" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-authority-discovery = { version = "4.0.0-dev", path = "../../primitives/authority-discovery" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", path = "../../primitives/keystore" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +async-trait = "0.1.56" [dev-dependencies] -quickcheck = "1.0.3" -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } +quickcheck = { version = "1.0.3", default-features = false } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/authority-discovery/README.md b/client/authority-discovery/README.md index 54c51d5ba04f4..042e8f5982cd0 100644 --- a/client/authority-discovery/README.md +++ b/client/authority-discovery/README.md @@ -1,4 +1,4 @@ -Substrate authority discovery. +# Substrate authority discovery This crate enables Substrate authorities to discover and directly connect to other authorities. It is split into two components the [`Worker`] and the @@ -6,4 +6,4 @@ other authorities. It is split into two components the [`Worker`] and the See [`Worker`] and [`Service`] for more documentation. -License: GPL-3.0-or-later WITH Classpath-exception-2.0 \ No newline at end of file +License: GPL-3.0-or-later WITH Classpath-exception-2.0 diff --git a/client/authority-discovery/build.rs b/client/authority-discovery/build.rs index c44fe8578ba25..00d45f07ae159 100644 --- a/client/authority-discovery/build.rs +++ b/client/authority-discovery/build.rs @@ -1,3 +1,7 @@ fn main() { - prost_build::compile_protos(&["src/worker/schema/dht.proto"], &["src/worker/schema"]).unwrap(); + prost_build::compile_protos( + &["src/worker/schema/dht-v1.proto", "src/worker/schema/dht-v2.proto"], + &["src/worker/schema"], + ) + .unwrap(); } diff --git a/client/authority-discovery/src/error.rs b/client/authority-discovery/src/error.rs index b271f7b9d62bb..285a2714b81f5 100644 --- a/client/authority-discovery/src/error.rs +++ b/client/authority-discovery/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -24,32 +24,56 @@ use sp_core::crypto::CryptoTypePublicPair; pub type Result = std::result::Result; /// Error type for the authority discovery module. -#[derive(Debug, derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] pub enum Error { - /// Received dht value found event with records with different keys. + #[error("Received dht value found event with records with different keys.")] ReceivingDhtValueFoundEventWithDifferentKeys, - /// Received dht value found event with no records. + + #[error("Received dht value found event with no records.")] ReceivingDhtValueFoundEventWithNoRecords, - /// Failed to verify a dht payload with the given signature. + + #[error("Failed to verify a dht payload with the given signature.")] VerifyingDhtPayload, - /// Failed to hash the authority id to be used as a dht key. - HashingAuthorityId(libp2p::core::multiaddr::multihash::Error), - /// Failed calling into the Substrate runtime. - CallingRuntime(sp_blockchain::Error), - /// Received a dht record with a key that does not match any in-flight awaited keys. + + #[error("Failed to hash the authority id to be used as a dht key.")] + HashingAuthorityId(#[from] libp2p::core::multiaddr::multihash::Error), + + #[error("Failed calling into the Substrate runtime: {0}")] + CallingRuntime(#[from] sp_blockchain::Error), + + #[error("Received a dht record with a key that does not match any in-flight awaited keys.")] ReceivingUnexpectedRecord, - /// Failed to encode a protobuf payload. - EncodingProto(prost::EncodeError), - /// Failed to decode a protobuf payload. - DecodingProto(prost::DecodeError), - /// Failed to encode or decode scale payload. - EncodingDecodingScale(codec::Error), - /// Failed to parse a libp2p multi address. - ParsingMultiaddress(libp2p::core::multiaddr::Error), - /// Failed to sign using a specific public key. + + #[error("Failed to encode a protobuf payload.")] + EncodingProto(#[from] prost::EncodeError), + + #[error("Failed to decode a protobuf payload.")] + DecodingProto(#[from] prost::DecodeError), + + #[error("Failed to encode or decode scale payload.")] + EncodingDecodingScale(#[from] codec::Error), + + #[error("Failed to parse a libp2p multi address.")] + ParsingMultiaddress(#[from] libp2p::core::multiaddr::Error), + + #[error("Failed to parse a libp2p key.")] + ParsingLibp2pIdentity(#[from] libp2p::identity::error::DecodingError), + + #[error("Failed to sign using a specific public key.")] MissingSignature(CryptoTypePublicPair), - /// Failed to sign using all public keys. + + #[error("Failed to sign using all public keys.")] Signing, - /// Failed to register Prometheus metric. - Prometheus(prometheus_endpoint::PrometheusError), + + #[error("Failed to register Prometheus metric.")] + Prometheus(#[from] prometheus_endpoint::PrometheusError), + + #[error("Received authority record that contains addresses with multiple peer ids")] + ReceivingDhtValueFoundEventWithDifferentPeerIds, + + #[error("Received authority record without any addresses having a peer id")] + ReceivingDhtValueFoundEventWithNoPeerIds, + + #[error("Received authority record without a valid signature for the remote peer id.")] + MissingPeerIdSignature, } diff --git a/client/authority-discovery/src/interval.rs b/client/authority-discovery/src/interval.rs index f4e7c43e60d21..5ddf81fbccc34 100644 --- a/client/authority-discovery/src/interval.rs +++ b/client/authority-discovery/src/interval.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/authority-discovery/src/lib.rs b/client/authority-discovery/src/lib.rs index 800f683aa0aef..db3802b168fe5 100644 --- a/client/authority-discovery/src/lib.rs +++ b/client/authority-discovery/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ #![warn(missing_docs)] #![recursion_limit = "1024"] + //! Substrate authority discovery. //! //! This crate enables Substrate authorities to discover and directly connect to @@ -28,20 +29,20 @@ pub use crate::{ service::Service, - worker::{NetworkProvider, Role, Worker}, + worker::{AuthorityDiscovery, NetworkProvider, Role, Worker}, }; -use std::{sync::Arc, time::Duration}; +use std::{collections::HashSet, sync::Arc, time::Duration}; use futures::{ channel::{mpsc, oneshot}, Stream, }; -use sc_client_api::blockchain::HeaderBackend; -use sc_network::{DhtEvent, Multiaddr, PeerId}; -use sp_api::ProvideRuntimeApi; -use sp_authority_discovery::{AuthorityDiscoveryApi, AuthorityId}; +use libp2p::{Multiaddr, PeerId}; +use sc_network_common::protocol::event::DhtEvent; +use sp_authority_discovery::AuthorityId; +use sp_blockchain::HeaderBackend; use sp_runtime::traits::Block as BlockT; mod error; @@ -58,11 +59,13 @@ pub struct WorkerConfig { /// /// By default this is set to 1 hour. pub max_publish_interval: Duration, + /// Interval at which the keystore is queried. If the keys have changed, unconditionally /// re-publish its addresses on the DHT. /// /// By default this is set to 1 minute. pub keystore_refresh_interval: Duration, + /// The maximum interval in which the node will query the DHT for new entries. /// /// By default this is set to 10 minutes. @@ -75,6 +78,11 @@ pub struct WorkerConfig { /// /// Defaults to `true` to avoid the surprise factor. pub publish_non_global_ips: bool, + + /// Reject authority discovery records that are not signed by their network identity (PeerId) + /// + /// Defaults to `false` to provide compatibility with old versions + pub strict_record_validation: bool, } impl Default for WorkerConfig { @@ -95,6 +103,7 @@ impl Default for WorkerConfig { // `authority_discovery_dht_event_received`. max_query_interval: Duration::from_secs(10 * 60), publish_non_global_ips: true, + strict_record_validation: false, } } } @@ -112,8 +121,7 @@ pub fn new_worker_and_service( where Block: BlockT + Unpin + 'static, Network: NetworkProvider, - Client: ProvideRuntimeApi + Send + Sync + 'static + HeaderBackend, - >::Api: AuthorityDiscoveryApi, + Client: AuthorityDiscovery + Send + Sync + 'static + HeaderBackend, DhtEventStream: Stream + Unpin, { new_worker_and_service_with_config( @@ -140,8 +148,7 @@ pub fn new_worker_and_service_with_config + Send + Sync + 'static + HeaderBackend, - >::Api: AuthorityDiscoveryApi, + Client: AuthorityDiscovery + HeaderBackend + 'static, DhtEventStream: Stream + Unpin, { let (to_worker, from_service) = mpsc::channel(0); @@ -156,7 +163,7 @@ where /// Message send from the [`Service`] to the [`Worker`]. pub(crate) enum ServicetoWorkerMsg { /// See [`Service::get_addresses_by_authority_id`]. - GetAddressesByAuthorityId(AuthorityId, oneshot::Sender>>), - /// See [`Service::get_authority_id_by_peer_id`]. - GetAuthorityIdByPeerId(PeerId, oneshot::Sender>), + GetAddressesByAuthorityId(AuthorityId, oneshot::Sender>>), + /// See [`Service::get_authority_ids_by_peer_id`]. + GetAuthorityIdsByPeerId(PeerId, oneshot::Sender>>), } diff --git a/client/authority-discovery/src/service.rs b/client/authority-discovery/src/service.rs index 2e5ae66e4dd4a..df09b6ea43216 100644 --- a/client/authority-discovery/src/service.rs +++ b/client/authority-discovery/src/service.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::fmt::Debug; +use std::{collections::HashSet, fmt::Debug}; use crate::ServicetoWorkerMsg; @@ -25,7 +25,7 @@ use futures::{ SinkExt, }; -use sc_network::{Multiaddr, PeerId}; +use libp2p::{Multiaddr, PeerId}; use sp_authority_discovery::AuthorityId; /// Service to interact with the [`crate::Worker`]. @@ -62,7 +62,7 @@ impl Service { pub async fn get_addresses_by_authority_id( &mut self, authority: AuthorityId, - ) -> Option> { + ) -> Option> { let (tx, rx) = oneshot::channel(); self.to_worker @@ -78,11 +78,14 @@ impl Service { /// /// Returns `None` if no entry was present or connection to the /// [`crate::Worker`] failed. - pub async fn get_authority_id_by_peer_id(&mut self, peer_id: PeerId) -> Option { + pub async fn get_authority_ids_by_peer_id( + &mut self, + peer_id: PeerId, + ) -> Option> { let (tx, rx) = oneshot::channel(); self.to_worker - .send(ServicetoWorkerMsg::GetAuthorityIdByPeerId(peer_id, tx)) + .send(ServicetoWorkerMsg::GetAuthorityIdsByPeerId(peer_id, tx)) .await .ok()?; diff --git a/client/authority-discovery/src/tests.rs b/client/authority-discovery/src/tests.rs index 3784b4c834266..208440b7ab1ea 100644 --- a/client/authority-discovery/src/tests.rs +++ b/client/authority-discovery/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -29,7 +29,7 @@ use libp2p::core::{ multiaddr::{Multiaddr, Protocol}, PeerId, }; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; use sp_authority_discovery::AuthorityId; use sp_core::crypto::key_types; @@ -73,12 +73,38 @@ fn get_addresses_and_authority_id() { pool.run_until(async { assert_eq!( - Some(vec![remote_addr]), + Some(HashSet::from([remote_addr])), service.get_addresses_by_authority_id(remote_authority_id.clone()).await, ); assert_eq!( - Some(remote_authority_id), - service.get_authority_id_by_peer_id(remote_peer_id).await, + Some(HashSet::from([remote_authority_id])), + service.get_authority_ids_by_peer_id(remote_peer_id).await, ); }); } + +#[test] +fn cryptos_are_compatible() { + use sp_core::crypto::Pair; + + let libp2p_secret = libp2p::identity::Keypair::generate_ed25519(); + let libp2p_public = libp2p_secret.public(); + + let sp_core_secret = { + let libp2p::identity::Keypair::Ed25519(libp2p_ed_secret) = libp2p_secret.clone(); + sp_core::ed25519::Pair::from_seed_slice(&libp2p_ed_secret.secret().as_ref()).unwrap() + }; + let sp_core_public = sp_core_secret.public(); + + let message = b"we are more powerful than not to be better"; + + let libp2p_signature = libp2p_secret.sign(message).unwrap(); + let sp_core_signature = sp_core_secret.sign(message); // no error expected... + + assert!(sp_core::ed25519::Pair::verify( + &sp_core::ed25519::Signature::from_slice(&libp2p_signature).unwrap(), + message, + &sp_core_public + )); + assert!(libp2p_public.verify(message, sp_core_signature.as_ref())); +} diff --git a/client/authority-discovery/src/worker.rs b/client/authority-discovery/src/worker.rs index a689d0bafd262..4121b64e00b9b 100644 --- a/client/authority-discovery/src/worker.rs +++ b/client/authority-discovery/src/worker.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,12 +19,11 @@ use crate::{ error::{Error, Result}, interval::ExpIncInterval, - ServicetoWorkerMsg, + ServicetoWorkerMsg, WorkerConfig, }; use std::{ collections::{HashMap, HashSet}, - convert::TryInto, marker::PhantomData, sync::Arc, time::Duration, @@ -33,23 +32,27 @@ use std::{ use futures::{channel::mpsc, future, stream::Fuse, FutureExt, Stream, StreamExt}; use addr_cache::AddrCache; -use async_trait::async_trait; use codec::Decode; use ip_network::IpNetwork; use libp2p::{ core::multiaddr, - multihash::{Hasher, Multihash}, + multihash::{Multihash, MultihashDigest}, + Multiaddr, PeerId, }; use log::{debug, error, log_enabled}; use prometheus_endpoint::{register, Counter, CounterVec, Gauge, Opts, U64}; use prost::Message; use rand::{seq::SliceRandom, thread_rng}; -use sc_client_api::blockchain::HeaderBackend; -use sc_network::{DhtEvent, ExHashT, Multiaddr, NetworkStateInfo, PeerId}; -use sp_api::ProvideRuntimeApi; +use sc_network_common::{ + protocol::event::DhtEvent, + service::{KademliaKey, NetworkDHTProvider, NetworkSigner, NetworkStateInfo, Signature}, +}; +use sp_api::{ApiError, ProvideRuntimeApi}; use sp_authority_discovery::{ AuthorityDiscoveryApi, AuthorityId, AuthorityPair, AuthoritySignature, }; +use sp_blockchain::HeaderBackend; + use sp_core::crypto::{key_types, CryptoTypePublicPair, Pair}; use sp_keystore::CryptoStore; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; @@ -57,12 +60,15 @@ use sp_runtime::{generic::BlockId, traits::Block as BlockT}; mod addr_cache; /// Dht payload schemas generated from Protobuf definitions via Prost crate in build.rs. mod schema { - include!(concat!(env!("OUT_DIR"), "/authority_discovery.rs")); + #[cfg(test)] + mod tests; + + include!(concat!(env!("OUT_DIR"), "/authority_discovery_v2.rs")); } #[cfg(test)] pub mod tests; -const LOG_TARGET: &'static str = "sub-authority-discovery"; +const LOG_TARGET: &str = "sub-authority-discovery"; /// Maximum number of addresses cached per authority. Additional addresses are discarded. const MAX_ADDRESSES_PER_AUTHORITY: usize = 10; @@ -111,6 +117,7 @@ pub struct Worker { client: Arc, network: Arc, + /// Channel we receive Dht events on. dht_event_rx: DhtEventStream, @@ -124,6 +131,8 @@ pub struct Worker { latest_published_keys: HashSet, /// Same value as in the configuration. publish_non_global_ips: bool, + /// Same value as in the configuration. + strict_record_validation: bool, /// Interval at which to request addresses of authorities, refilling the pending lookups queue. query_interval: ExpIncInterval, @@ -131,7 +140,7 @@ pub struct Worker { /// Queue of throttled lookups pending to be passed to the network. pending_lookups: Vec, /// Set of in-flight lookups. - in_flight_lookups: HashMap, + in_flight_lookups: HashMap, addr_cache: addr_cache::AddrCache, @@ -142,12 +151,35 @@ pub struct Worker { phantom: PhantomData, } +/// Wrapper for [`AuthorityDiscoveryApi`](sp_authority_discovery::AuthorityDiscoveryApi). Can be +/// be implemented by any struct without dependency on the runtime. +#[async_trait::async_trait] +pub trait AuthorityDiscovery { + /// Retrieve authority identifiers of the current and next authority set. + async fn authorities(&self, at: Block::Hash) + -> std::result::Result, ApiError>; +} + +#[async_trait::async_trait] +impl AuthorityDiscovery for T +where + T: ProvideRuntimeApi + Send + Sync, + T::Api: AuthorityDiscoveryApi, + Block: BlockT, +{ + async fn authorities( + &self, + at: Block::Hash, + ) -> std::result::Result, ApiError> { + self.runtime_api().authorities(&BlockId::Hash(at)) + } +} + impl Worker where Block: BlockT + Unpin + 'static, Network: NetworkProvider, - Client: ProvideRuntimeApi + Send + Sync + 'static + HeaderBackend, - >::Api: AuthorityDiscoveryApi, + Client: AuthorityDiscovery + HeaderBackend + 'static, DhtEventStream: Stream + Unpin, { /// Construct a [`Worker`]. @@ -158,7 +190,7 @@ where dht_event_rx: DhtEventStream, role: Role, prometheus_registry: Option, - config: crate::WorkerConfig, + config: WorkerConfig, ) -> Self { // When a node starts up publishing and querying might fail due to various reasons, for // example due to being not yet fully bootstrapped on the DHT. Thus one should retry rather @@ -181,7 +213,7 @@ where Some(registry) => match Metrics::register(®istry) { Ok(metrics) => Some(metrics), Err(e) => { - error!(target: LOG_TARGET, "Failed to register metrics: {:?}", e); + error!(target: LOG_TARGET, "Failed to register metrics: {}", e); None }, }, @@ -197,6 +229,7 @@ where publish_if_changed_interval, latest_published_keys: HashSet::new(), publish_non_global_ips: config.publish_non_global_ips, + strict_record_validation: config.strict_record_validation, query_interval, pending_lookups: Vec::new(), in_flight_lookups: HashMap::new(), @@ -235,7 +268,7 @@ where if let Err(e) = self.publish_ext_addresses(only_if_changed).await { error!( target: LOG_TARGET, - "Failed to publish external addresses: {:?}", e, + "Failed to publish external addresses: {}", e, ); } }, @@ -244,7 +277,7 @@ where if let Err(e) = self.refill_pending_lookups_queue().await { error!( target: LOG_TARGET, - "Failed to request addresses of authorities: {:?}", e, + "Failed to request addresses of authorities: {}", e, ); } }, @@ -259,9 +292,9 @@ where self.addr_cache.get_addresses_by_authority_id(&authority).map(Clone::clone), ); }, - ServicetoWorkerMsg::GetAuthorityIdByPeerId(peer_id, sender) => { + ServicetoWorkerMsg::GetAuthorityIdsByPeerId(peer_id, sender) => { let _ = sender - .send(self.addr_cache.get_authority_id_by_peer_id(&peer_id).map(Clone::clone)); + .send(self.addr_cache.get_authority_ids_by_peer_id(&peer_id).map(Clone::clone)); }, } } @@ -313,7 +346,7 @@ where return Ok(()) } - let addresses = self.addresses_to_publish().map(|a| a.to_vec()).collect::>(); + let addresses = serialize_addresses(self.addresses_to_publish()); if let Some(metrics) = &self.metrics { metrics.publish.inc(); @@ -322,32 +355,21 @@ where .set(addresses.len().try_into().unwrap_or(std::u64::MAX)); } - let mut serialized_addresses = vec![]; - schema::AuthorityAddresses { addresses } - .encode(&mut serialized_addresses) - .map_err(Error::EncodingProto)?; + let serialized_record = serialize_authority_record(addresses)?; + let peer_signature = sign_record_with_peer_id(&serialized_record, self.network.as_ref())?; let keys_vec = keys.iter().cloned().collect::>(); - let signatures = key_store - .sign_with_all( - key_types::AUTHORITY_DISCOVERY, - keys_vec.clone(), - serialized_addresses.as_slice(), - ) - .await - .map_err(|_| Error::Signing)?; - for (sign_result, key) in signatures.into_iter().zip(keys_vec.iter()) { - let mut signed_addresses = vec![]; + let kv_pairs = sign_record_with_authority_ids( + serialized_record, + Some(peer_signature), + key_store.as_ref(), + keys_vec, + ) + .await?; - // Verify that all signatures exist for all provided keys. - let signature = - sign_result.ok().flatten().ok_or_else(|| Error::MissingSignature(key.clone()))?; - schema::SignedAuthorityAddresses { addresses: serialized_addresses.clone(), signature } - .encode(&mut signed_addresses) - .map_err(Error::EncodingProto)?; - - self.network.put_value(hash_authority_id(key.1.as_ref()), signed_addresses); + for (key, value) in kv_pairs.into_iter() { + self.network.put_value(key, value); } self.latest_published_keys = keys; @@ -356,7 +378,7 @@ where } async fn refill_pending_lookups_queue(&mut self) -> Result<()> { - let id = BlockId::hash(self.client.info().best_hash); + let best_hash = self.client.info().best_hash; let local_keys = match &self.role { Role::PublishAndDiscover(key_store) => key_store @@ -369,12 +391,12 @@ where let mut authorities = self .client - .runtime_api() - .authorities(&id) + .authorities(best_hash) + .await .map_err(|e| Error::CallingRuntime(e.into()))? .into_iter() .filter(|id| !local_keys.contains(id.as_ref())) - .collect(); + .collect::>(); self.addr_cache.retain_ids(&authorities); @@ -430,7 +452,7 @@ where metrics.handle_value_found_event_failure.inc(); } - debug!(target: LOG_TARGET, "Failed to handle Dht value found event: {:?}", e); + debug!(target: LOG_TARGET, "Failed to handle Dht value found event: {}", e); } }, DhtEvent::ValueNotFound(hash) => { @@ -469,20 +491,10 @@ where } } - fn handle_dht_value_found_event( - &mut self, - values: Vec<(libp2p::kad::record::Key, Vec)>, - ) -> Result<()> { + fn handle_dht_value_found_event(&mut self, values: Vec<(KademliaKey, Vec)>) -> Result<()> { // Ensure `values` is not empty and all its keys equal. - let remote_key = values - .iter() - .fold(Ok(None), |acc, (key, _)| match acc { - Ok(None) => Ok(Some(key.clone())), - Ok(Some(ref prev_key)) if prev_key != key => - Err(Error::ReceivingDhtValueFoundEventWithDifferentKeys), - x @ Ok(_) => x, - Err(e) => Err(e), - })? + let remote_key = single(values.iter().map(|(key, _)| key.clone())) + .map_err(|_| Error::ReceivingDhtValueFoundEventWithDifferentKeys)? .ok_or(Error::ReceivingDhtValueFoundEventWithNoRecords)?; let authority_id: AuthorityId = self @@ -495,18 +507,18 @@ where let remote_addresses: Vec = values .into_iter() .map(|(_k, v)| { - let schema::SignedAuthorityAddresses { signature, addresses } = - schema::SignedAuthorityAddresses::decode(v.as_slice()) + let schema::SignedAuthorityRecord { record, auth_signature, peer_signature } = + schema::SignedAuthorityRecord::decode(v.as_slice()) .map_err(Error::DecodingProto)?; - let signature = AuthoritySignature::decode(&mut &signature[..]) + let auth_signature = AuthoritySignature::decode(&mut &auth_signature[..]) .map_err(Error::EncodingDecodingScale)?; - if !AuthorityPair::verify(&signature, &addresses, &authority_id) { + if !AuthorityPair::verify(&auth_signature, &record, &authority_id) { return Err(Error::VerifyingDhtPayload) } - let addresses = schema::AuthorityAddresses::decode(addresses.as_slice()) + let addresses: Vec = schema::AuthorityRecord::decode(record.as_slice()) .map(|a| a.addresses) .map_err(Error::DecodingProto)? .into_iter() @@ -514,32 +526,49 @@ where .collect::>() .map_err(Error::ParsingMultiaddress)?; + let get_peer_id = |a: &Multiaddr| match a.iter().last() { + Some(multiaddr::Protocol::P2p(key)) => PeerId::from_multihash(key).ok(), + _ => None, + }; + + // Ignore [`Multiaddr`]s without [`PeerId`] or with own addresses. + let addresses: Vec = addresses + .into_iter() + .filter(|a| get_peer_id(a).filter(|p| *p != local_peer_id).is_some()) + .collect(); + + let remote_peer_id = single(addresses.iter().map(get_peer_id)) + .map_err(|_| Error::ReceivingDhtValueFoundEventWithDifferentPeerIds)? // different peer_id in records + .flatten() + .ok_or(Error::ReceivingDhtValueFoundEventWithNoPeerIds)?; // no records with peer_id in them + + // At this point we know all the valid multiaddresses from the record, know that + // each of them belong to the same PeerId, we just need to check if the record is + // properly signed by the owner of the PeerId + + if let Some(peer_signature) = peer_signature { + let public_key = libp2p::identity::PublicKey::from_protobuf_encoding( + &peer_signature.public_key, + ) + .map_err(Error::ParsingLibp2pIdentity)?; + let signature = Signature { public_key, bytes: peer_signature.signature }; + + if !signature.verify(record, &remote_peer_id) { + return Err(Error::VerifyingDhtPayload) + } + } else if self.strict_record_validation { + return Err(Error::MissingPeerIdSignature) + } else { + debug!( + target: LOG_TARGET, + "Received unsigned authority discovery record from {}", authority_id + ); + } Ok(addresses) }) .collect::>>>()? .into_iter() .flatten() - // Ignore [`Multiaddr`]s without [`PeerId`] and own addresses. - .filter(|addr| { - addr.iter().any(|protocol| { - // Parse to PeerId first as Multihashes of old and new PeerId - // representation don't equal. - // - // See https://github.com/libp2p/rust-libp2p/issues/555 for - // details. - if let multiaddr::Protocol::P2p(hash) = protocol { - let peer_id = match PeerId::from_multihash(hash) { - Ok(peer_id) => peer_id, - Err(_) => return false, // Discard address. - }; - - // Discard if equal to local peer id, keep if it differs. - return !(peer_id == local_peer_id) - } - - false // `protocol` is not a [`Protocol::P2p`], let's keep looking. - }) - }) .take(MAX_ADDRESSES_PER_AUTHORITY) .collect(); @@ -548,7 +577,7 @@ where if let Some(metrics) = &self.metrics { metrics .known_authorities_count - .set(self.addr_cache.num_ids().try_into().unwrap_or(std::u64::MAX)); + .set(self.addr_cache.num_authority_ids().try_into().unwrap_or(std::u64::MAX)); } } Ok(()) @@ -569,20 +598,17 @@ where .into_iter() .collect::>(); - let id = BlockId::hash(client.info().best_hash); + let best_hash = client.info().best_hash; let authorities = client - .runtime_api() - .authorities(&id) + .authorities(best_hash) + .await .map_err(|e| Error::CallingRuntime(e.into()))? .into_iter() - .map(std::convert::Into::into) + .map(Into::into) .collect::>(); - let intersection = local_pub_keys - .intersection(&authorities) - .cloned() - .map(std::convert::Into::into) - .collect(); + let intersection = + local_pub_keys.intersection(&authorities).cloned().map(Into::into).collect(); Ok(intersection) } @@ -590,32 +616,84 @@ where /// NetworkProvider provides [`Worker`] with all necessary hooks into the /// underlying Substrate networking. Using this trait abstraction instead of -/// [`sc_network::NetworkService`] directly is necessary to unit test [`Worker`]. -#[async_trait] -pub trait NetworkProvider: NetworkStateInfo { - /// Start putting a value in the Dht. - fn put_value(&self, key: libp2p::kad::record::Key, value: Vec); - - /// Start getting a value from the Dht. - fn get_value(&self, key: &libp2p::kad::record::Key); +/// `sc_network::NetworkService` directly is necessary to unit test [`Worker`]. +pub trait NetworkProvider: NetworkDHTProvider + NetworkStateInfo + NetworkSigner {} + +impl NetworkProvider for T where T: NetworkDHTProvider + NetworkStateInfo + NetworkSigner {} + +fn hash_authority_id(id: &[u8]) -> KademliaKey { + KademliaKey::new(&libp2p::multihash::Code::Sha2_256.digest(id).digest()) } -#[async_trait::async_trait] -impl NetworkProvider for sc_network::NetworkService +// Makes sure all values are the same and returns it +// +// Returns Err(_) if not all values are equal. Returns Ok(None) if there are +// no values. +fn single(values: impl IntoIterator) -> std::result::Result, ()> where - B: BlockT + 'static, - H: ExHashT, + T: PartialEq, { - fn put_value(&self, key: libp2p::kad::record::Key, value: Vec) { - self.put_value(key, value) - } - fn get_value(&self, key: &libp2p::kad::record::Key) { - self.get_value(key) - } + values.into_iter().try_fold(None, |acc, item| match acc { + None => Ok(Some(item)), + Some(ref prev) if *prev != item => Err(()), + Some(x) => Ok(Some(x)), + }) +} + +fn serialize_addresses(addresses: impl Iterator) -> Vec> { + addresses.map(|a| a.to_vec()).collect() +} + +fn serialize_authority_record(addresses: Vec>) -> Result> { + let mut serialized_record = vec![]; + schema::AuthorityRecord { addresses } + .encode(&mut serialized_record) + .map_err(Error::EncodingProto)?; + Ok(serialized_record) } -fn hash_authority_id(id: &[u8]) -> libp2p::kad::record::Key { - libp2p::kad::record::Key::new(&libp2p::multihash::Sha2_256::digest(id)) +fn sign_record_with_peer_id( + serialized_record: &[u8], + network: &impl NetworkSigner, +) -> Result { + let signature = network + .sign_with_local_identity(serialized_record) + .map_err(|_| Error::Signing)?; + let public_key = signature.public_key.to_protobuf_encoding(); + let signature = signature.bytes; + Ok(schema::PeerSignature { signature, public_key }) +} + +async fn sign_record_with_authority_ids( + serialized_record: Vec, + peer_signature: Option, + key_store: &dyn CryptoStore, + keys: Vec, +) -> Result)>> { + let signatures = key_store + .sign_with_all(key_types::AUTHORITY_DISCOVERY, keys.clone(), &serialized_record) + .await + .map_err(|_| Error::Signing)?; + + let mut result = vec![]; + for (sign_result, key) in signatures.into_iter().zip(keys.iter()) { + let mut signed_record = vec![]; + + // Verify that all signatures exist for all provided keys. + let auth_signature = + sign_result.ok().flatten().ok_or_else(|| Error::MissingSignature(key.clone()))?; + schema::SignedAuthorityRecord { + record: serialized_record.clone(), + auth_signature, + peer_signature: peer_signature.clone(), + } + .encode(&mut signed_record) + .map_err(Error::EncodingProto)?; + + result.push((hash_authority_id(key.1.as_ref()), signed_record)); + } + + Ok(result) } /// Prometheus metrics for a [`Worker`]. @@ -635,14 +713,14 @@ impl Metrics { Ok(Self { publish: register( Counter::new( - "authority_discovery_times_published_total", + "substrate_authority_discovery_times_published_total", "Number of times authority discovery has published external addresses.", )?, registry, )?, amount_addresses_last_published: register( Gauge::new( - "authority_discovery_amount_external_addresses_last_published", + "substrate_authority_discovery_amount_external_addresses_last_published", "Number of external addresses published when authority discovery last \ published addresses.", )?, @@ -650,7 +728,7 @@ impl Metrics { )?, requests: register( Counter::new( - "authority_discovery_authority_addresses_requested_total", + "substrate_authority_discovery_authority_addresses_requested_total", "Number of times authority discovery has requested external addresses of a \ single authority.", )?, @@ -658,7 +736,7 @@ impl Metrics { )?, requests_pending: register( Gauge::new( - "authority_discovery_authority_address_requests_pending", + "substrate_authority_discovery_authority_address_requests_pending", "Number of pending authority address requests.", )?, registry, @@ -666,7 +744,7 @@ impl Metrics { dht_event_received: register( CounterVec::new( Opts::new( - "authority_discovery_dht_event_received", + "substrate_authority_discovery_dht_event_received", "Number of dht events received by authority discovery.", ), &["name"], @@ -675,14 +753,14 @@ impl Metrics { )?, handle_value_found_event_failure: register( Counter::new( - "authority_discovery_handle_value_found_event_failure", + "substrate_authority_discovery_handle_value_found_event_failure", "Number of times handling a dht value found event failed.", )?, registry, )?, known_authorities_count: register( Gauge::new( - "authority_discovery_known_authorities_count", + "substrate_authority_discovery_known_authorities_count", "Number of authorities known by authority discovery.", )?, registry, diff --git a/client/authority-discovery/src/worker/addr_cache.rs b/client/authority-discovery/src/worker/addr_cache.rs index e770297f6f3be..19bbbf0b62e7e 100644 --- a/client/authority-discovery/src/worker/addr_cache.rs +++ b/client/authority-discovery/src/worker/addr_cache.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,80 +16,96 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use libp2p::core::multiaddr::{Multiaddr, Protocol}; -use std::collections::HashMap; - -use sc_network::PeerId; +use libp2p::{ + core::multiaddr::{Multiaddr, Protocol}, + PeerId, +}; use sp_authority_discovery::AuthorityId; +use std::collections::{hash_map::Entry, HashMap, HashSet}; -/// Cache for [`AuthorityId`] -> [`Vec`] and [`PeerId`] -> [`AuthorityId`] mappings. +/// Cache for [`AuthorityId`] -> [`HashSet`] and [`PeerId`] -> [`HashSet`] +/// mappings. pub(super) struct AddrCache { - // The addresses found in `authority_id_to_addresses` are guaranteed to always match - // the peerids found in `peer_id_to_authority_id`. In other words, these two hashmaps - // are similar to a bi-directional map. - authority_id_to_addresses: HashMap>, - peer_id_to_authority_id: HashMap, + /// The addresses found in `authority_id_to_addresses` are guaranteed to always match + /// the peerids found in `peer_id_to_authority_ids`. In other words, these two hashmaps + /// are similar to a bi-directional map. + /// + /// Since we may store the mapping across several sessions, a single + /// `PeerId` might correspond to multiple `AuthorityId`s. However, + /// it's not expected that a single `AuthorityId` can have multiple `PeerId`s. + authority_id_to_addresses: HashMap>, + peer_id_to_authority_ids: HashMap>, } impl AddrCache { pub fn new() -> Self { AddrCache { authority_id_to_addresses: HashMap::new(), - peer_id_to_authority_id: HashMap::new(), + peer_id_to_authority_ids: HashMap::new(), } } /// Inserts the given [`AuthorityId`] and [`Vec`] pair for future lookups by /// [`AuthorityId`] or [`PeerId`]. - pub fn insert(&mut self, authority_id: AuthorityId, mut addresses: Vec) { - addresses.sort_unstable_by(|a, b| a.as_ref().cmp(b.as_ref())); + pub fn insert(&mut self, authority_id: AuthorityId, addresses: Vec) { + let addresses = addresses.into_iter().collect::>(); + let peer_ids = addresses_to_peer_ids(&addresses); + + if peer_ids.is_empty() { + log::debug!( + target: super::LOG_TARGET, + "Authority({:?}) provides no addresses or addresses without peer ids. Adresses: {:?}", + authority_id, + addresses, + ); - // Insert into `self.peer_id_to_authority_id`. - let peer_ids = addresses - .iter() - .map(|a| peer_id_from_multiaddr(a)) - .filter_map(|peer_id| peer_id); - for peer_id in peer_ids.clone() { - let former_auth = - match self.peer_id_to_authority_id.insert(peer_id, authority_id.clone()) { - Some(a) if a != authority_id => a, - _ => continue, - }; - - // PeerId was associated to a different authority id before. - // Remove corresponding authority from `self.authority_id_to_addresses`. - let former_auth_addrs = match self.authority_id_to_addresses.get_mut(&former_auth) { - Some(a) => a, - None => { - debug_assert!(false); - continue - }, - }; - former_auth_addrs.retain(|a| peer_id_from_multiaddr(a).map_or(true, |p| p != peer_id)); + return + } else if peer_ids.len() > 1 { + log::warn!( + target: super::LOG_TARGET, + "Authority({:?}) can be reached through multiple peer ids: {:?}", + authority_id, + peer_ids + ); } - // Insert into `self.authority_id_to_addresses`. - for former_addr in self - .authority_id_to_addresses - .insert(authority_id.clone(), addresses.clone()) - .unwrap_or_default() - { - // Must remove from `self.peer_id_to_authority_id` any PeerId formerly associated - // to that authority but that can't be found in its new addresses. - - let peer_id = match peer_id_from_multiaddr(&former_addr) { - Some(p) => p, - None => continue, - }; + let old_addresses = self.authority_id_to_addresses.insert(authority_id.clone(), addresses); + let old_peer_ids = addresses_to_peer_ids(&old_addresses.unwrap_or_default()); - if !peer_ids.clone().any(|p| p == peer_id) { - self.peer_id_to_authority_id.remove(&peer_id); + // Add the new peer ids + peer_ids.difference(&old_peer_ids).for_each(|new_peer_id| { + self.peer_id_to_authority_ids + .entry(*new_peer_id) + .or_default() + .insert(authority_id.clone()); + }); + + // Remove the old peer ids + self.remove_authority_id_from_peer_ids(&authority_id, old_peer_ids.difference(&peer_ids)); + } + + /// Remove the given `authority_id` from the `peer_id` to `authority_ids` mapping. + /// + /// If a `peer_id` doesn't have any `authority_id` assigned anymore, it is removed. + fn remove_authority_id_from_peer_ids<'a>( + &mut self, + authority_id: &AuthorityId, + peer_ids: impl Iterator, + ) { + peer_ids.for_each(|peer_id| { + if let Entry::Occupied(mut e) = self.peer_id_to_authority_ids.entry(*peer_id) { + e.get_mut().remove(authority_id); + + // If there are no more entries, remove the peer id. + if e.get().is_empty() { + e.remove(); + } } - } + }) } /// Returns the number of authority IDs in the cache. - pub fn num_ids(&self) -> usize { + pub fn num_authority_ids(&self) -> usize { self.authority_id_to_addresses.len() } @@ -97,18 +113,21 @@ impl AddrCache { pub fn get_addresses_by_authority_id( &self, authority_id: &AuthorityId, - ) -> Option<&Vec> { - self.authority_id_to_addresses.get(&authority_id) + ) -> Option<&HashSet> { + self.authority_id_to_addresses.get(authority_id) } - /// Returns the [`AuthorityId`] for the given [`PeerId`]. - pub fn get_authority_id_by_peer_id(&self, peer_id: &PeerId) -> Option<&AuthorityId> { - self.peer_id_to_authority_id.get(peer_id) + /// Returns the [`AuthorityId`]s for the given [`PeerId`]. + /// + /// As the authority id can change between sessions, one [`PeerId`] can be mapped to + /// multiple authority ids. + pub fn get_authority_ids_by_peer_id(&self, peer_id: &PeerId) -> Option<&HashSet> { + self.peer_id_to_authority_ids.get(peer_id) } /// Removes all [`PeerId`]s and [`Multiaddr`]s from the cache that are not related to the given /// [`AuthorityId`]s. - pub fn retain_ids(&mut self, authority_ids: &Vec) { + pub fn retain_ids(&mut self, authority_ids: &[AuthorityId]) { // The below logic could be replaced by `BtreeMap::drain_filter` once it stabilized. let authority_ids_to_remove = self .authority_id_to_addresses @@ -120,19 +139,18 @@ impl AddrCache { for authority_id_to_remove in authority_ids_to_remove { // Remove other entries from `self.authority_id_to_addresses`. - let addresses = self.authority_id_to_addresses.remove(&authority_id_to_remove); - - // Remove other entries from `self.peer_id_to_authority_id`. - let peer_ids = addresses - .iter() - .flatten() - .map(|a| peer_id_from_multiaddr(a)) - .filter_map(|peer_id| peer_id); - for peer_id in peer_ids { - if let Some(id) = self.peer_id_to_authority_id.remove(&peer_id) { - debug_assert_eq!(authority_id_to_remove, id); - } - } + let addresses = if let Some(addresses) = + self.authority_id_to_addresses.remove(&authority_id_to_remove) + { + addresses + } else { + continue + }; + + self.remove_authority_id_from_peer_ids( + &authority_id_to_remove, + addresses_to_peer_ids(&addresses).iter(), + ); } } } @@ -147,6 +165,10 @@ fn peer_id_from_multiaddr(addr: &Multiaddr) -> Option { }) } +fn addresses_to_peer_ids(addresses: &HashSet) -> HashSet { + addresses.iter().filter_map(peer_id_from_multiaddr).collect::>() +} + #[cfg(test)] mod tests { use super::*; @@ -226,27 +248,27 @@ mod tests { cache.insert(third.0.clone(), vec![third.1.clone()]); assert_eq!( - Some(&vec![third.1.clone()]), + Some(&HashSet::from([third.1.clone()])), cache.get_addresses_by_authority_id(&third.0), - "Expect `get_addresses_by_authority_id` to return addresses of third authority." + "Expect `get_addresses_by_authority_id` to return addresses of third authority.", ); assert_eq!( - Some(&third.0), - cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()), - "Expect `get_authority_id_by_peer_id` to return `AuthorityId` of third authority." + Some(&HashSet::from([third.0.clone()])), + cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()), + "Expect `get_authority_id_by_peer_id` to return `AuthorityId` of third authority.", ); - cache.retain_ids(&vec![first.0, second.0]); + cache.retain_ids(&vec![first.0.clone(), second.0]); assert_eq!( None, cache.get_addresses_by_authority_id(&third.0), - "Expect `get_addresses_by_authority_id` to not return `None` for third authority." + "Expect `get_addresses_by_authority_id` to not return `None` for third authority.", ); assert_eq!( None, - cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()), - "Expect `get_authority_id_by_peer_id` to return `None` for third authority." + cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()), + "Expect `get_authority_id_by_peer_id` to return `None` for third authority.", ); TestResult::passed() @@ -282,44 +304,47 @@ mod tests { assert_eq!( None, - cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr1).unwrap()) + cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr1).unwrap()) ); assert_eq!( - Some(&authority1), - cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap()) + Some(&HashSet::from([authority1.clone()])), + cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap()) ); assert_eq!( - Some(&authority1), - cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap()) + Some(&HashSet::from([authority1.clone()])), + cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap()) ); assert_eq!( - Some(&authority1), - cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr4).unwrap()) + Some(&HashSet::from([authority1.clone()])), + cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr4).unwrap()) ); cache.insert(authority2.clone(), vec![multiaddr2.clone()]); assert_eq!( - Some(&authority2), - cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap()) + Some(&HashSet::from([authority2.clone(), authority1.clone()])), + cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap()) ); assert_eq!( - Some(&authority1), - cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap()) + Some(&HashSet::from([authority1.clone()])), + cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap()) ); - assert_eq!(cache.get_addresses_by_authority_id(&authority1).unwrap().len(), 2); + assert_eq!(cache.get_addresses_by_authority_id(&authority1).unwrap().len(), 3); cache.insert(authority2.clone(), vec![multiaddr2.clone(), multiaddr3.clone()]); assert_eq!( - Some(&authority2), - cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap()) + Some(&HashSet::from([authority2.clone(), authority1.clone()])), + cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap()) + ); + assert_eq!( + Some(&HashSet::from([authority2.clone(), authority1.clone()])), + cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap()) ); assert_eq!( - Some(&authority2), - cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap()) + &HashSet::from([multiaddr2.clone(), multiaddr3.clone(), multiaddr4.clone()]), + cache.get_addresses_by_authority_id(&authority1).unwrap(), ); - assert!(cache.get_addresses_by_authority_id(&authority1).unwrap().is_empty()); TestResult::passed() } @@ -328,4 +353,31 @@ mod tests { .max_tests(10) .quickcheck(property as fn(_, _, _, _, _) -> TestResult) } + + /// As the runtime gives us the current + next authority ids, it can happen that some + /// authority changed its session keys. Changing the sessions keys leads to having two + /// authority ids that map to the same `PeerId` & addresses. + #[test] + fn adding_two_authority_ids_for_the_same_peer_id() { + let mut addr_cache = AddrCache::new(); + + let peer_id = PeerId::random(); + let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into())); + + let authority_id0 = AuthorityPair::generate().0.public(); + let authority_id1 = AuthorityPair::generate().0.public(); + + addr_cache.insert(authority_id0.clone(), vec![addr.clone()]); + addr_cache.insert(authority_id1.clone(), vec![addr.clone()]); + + assert_eq!(2, addr_cache.num_authority_ids()); + assert_eq!( + &HashSet::from([addr.clone()]), + addr_cache.get_addresses_by_authority_id(&authority_id0).unwrap() + ); + assert_eq!( + &HashSet::from([addr]), + addr_cache.get_addresses_by_authority_id(&authority_id1).unwrap() + ); + } } diff --git a/client/authority-discovery/src/worker/schema/dht.proto b/client/authority-discovery/src/worker/schema/dht-v1.proto similarity index 90% rename from client/authority-discovery/src/worker/schema/dht.proto rename to client/authority-discovery/src/worker/schema/dht-v1.proto index 9dbe9d559f4b1..0ef628888c093 100644 --- a/client/authority-discovery/src/worker/schema/dht.proto +++ b/client/authority-discovery/src/worker/schema/dht-v1.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package authority_discovery; +package authority_discovery_v1; // First we need to serialize the addresses in order to be able to sign them. message AuthorityAddresses { @@ -11,4 +11,4 @@ message AuthorityAddresses { message SignedAuthorityAddresses { bytes addresses = 1; bytes signature = 2; -} +} \ No newline at end of file diff --git a/client/authority-discovery/src/worker/schema/dht-v2.proto b/client/authority-discovery/src/worker/schema/dht-v2.proto new file mode 100644 index 0000000000000..fdbadb4266306 --- /dev/null +++ b/client/authority-discovery/src/worker/schema/dht-v2.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package authority_discovery_v2; + +// First we need to serialize the addresses in order to be able to sign them. +message AuthorityRecord { + // Possibly multiple `MultiAddress`es through which the node can be + repeated bytes addresses = 1; +} + +message PeerSignature { + bytes signature = 1; + bytes public_key = 2; +} + +// Then we need to serialize the authority record and signature to send them over the wire. +message SignedAuthorityRecord { + bytes record = 1; + bytes auth_signature = 2; + // Even if there are multiple `record.addresses`, all of them have the same peer id. + // Old versions are missing this field. It is optional in order to provide compatibility both ways. + PeerSignature peer_signature = 3; +} diff --git a/client/authority-discovery/src/worker/schema/tests.rs b/client/authority-discovery/src/worker/schema/tests.rs new file mode 100644 index 0000000000000..60147d6762e50 --- /dev/null +++ b/client/authority-discovery/src/worker/schema/tests.rs @@ -0,0 +1,89 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +mod schema_v1 { + include!(concat!(env!("OUT_DIR"), "/authority_discovery_v1.rs")); +} + +use super::*; +use libp2p::{multiaddr::Multiaddr, PeerId}; +use prost::Message; + +#[test] +fn v2_decodes_v1() { + let peer_id = PeerId::random(); + let multiaddress: Multiaddr = + format!("/ip4/127.0.0.1/tcp/3003/p2p/{}", peer_id).parse().unwrap(); + let vec_addresses = vec![multiaddress.to_vec()]; + let vec_auth_signature = b"Totally valid signature, I promise!".to_vec(); + + let addresses_v1 = schema_v1::AuthorityAddresses { addresses: vec_addresses.clone() }; + let mut vec_addresses_v1 = vec![]; + addresses_v1.encode(&mut vec_addresses_v1).unwrap(); + let signed_addresses_v1 = schema_v1::SignedAuthorityAddresses { + addresses: vec_addresses_v1.clone(), + signature: vec_auth_signature.clone(), + }; + let mut vec_signed_addresses_v1 = vec![]; + signed_addresses_v1.encode(&mut vec_signed_addresses_v1).unwrap(); + + let signed_record_v2_decoded = + SignedAuthorityRecord::decode(vec_signed_addresses_v1.as_slice()).unwrap(); + + assert_eq!(&signed_record_v2_decoded.record, &vec_addresses_v1); + assert_eq!(&signed_record_v2_decoded.auth_signature, &vec_auth_signature); + assert_eq!(&signed_record_v2_decoded.peer_signature, &None); + + let record_v2_decoded = AuthorityRecord::decode(vec_addresses_v1.as_slice()).unwrap(); + assert_eq!(&record_v2_decoded.addresses, &vec_addresses); +} + +#[test] +fn v1_decodes_v2() { + let peer_secret = libp2p::identity::Keypair::generate_ed25519(); + let peer_public = peer_secret.public(); + let peer_id = peer_public.to_peer_id(); + let multiaddress: Multiaddr = + format!("/ip4/127.0.0.1/tcp/3003/p2p/{}", peer_id).parse().unwrap(); + let vec_addresses = vec![multiaddress.to_vec()]; + let vec_auth_signature = b"Totally valid signature, I promise!".to_vec(); + let vec_peer_signature = b"Surprisingly hard to crack crypto".to_vec(); + + let record_v2 = AuthorityRecord { addresses: vec_addresses.clone() }; + let mut vec_record_v2 = vec![]; + record_v2.encode(&mut vec_record_v2).unwrap(); + let vec_peer_public = peer_public.to_protobuf_encoding(); + let peer_signature_v2 = + PeerSignature { public_key: vec_peer_public, signature: vec_peer_signature }; + let signed_record_v2 = SignedAuthorityRecord { + record: vec_record_v2.clone(), + auth_signature: vec_auth_signature.clone(), + peer_signature: Some(peer_signature_v2.clone()), + }; + let mut vec_signed_record_v2 = vec![]; + signed_record_v2.encode(&mut vec_signed_record_v2).unwrap(); + + let signed_addresses_v1_decoded = + schema_v1::SignedAuthorityAddresses::decode(vec_signed_record_v2.as_slice()).unwrap(); + + assert_eq!(&signed_addresses_v1_decoded.addresses, &vec_record_v2); + assert_eq!(&signed_addresses_v1_decoded.signature, &vec_auth_signature); + + let addresses_v2_decoded = AuthorityRecord::decode(vec_record_v2.as_slice()).unwrap(); + assert_eq!(&addresses_v2_decoded.addresses, &vec_addresses); +} diff --git a/client/authority-discovery/src/worker/tests.rs b/client/authority-discovery/src/worker/tests.rs index f10d2751ccd35..8e7a221877574 100644 --- a/client/authority-discovery/src/worker/tests.rs +++ b/client/authority-discovery/src/worker/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,14 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::worker::schema; - use std::{ + collections::HashSet, sync::{Arc, Mutex}, task::Poll, }; -use async_trait::async_trait; use futures::{ channel::mpsc::{self, channel}, executor::{block_on, LocalPool}, @@ -31,11 +29,12 @@ use futures::{ sink::SinkExt, task::LocalSpawn, }; -use libp2p::{core::multiaddr, kad, PeerId}; +use libp2p::{core::multiaddr, identity::Keypair, PeerId}; use prometheus_endpoint::prometheus::default_registry; +use sc_client_api::HeaderBackend; +use sc_network_common::service::{KademliaKey, Signature, SigningError}; use sp_api::{ApiRef, ProvideRuntimeApi}; -use sp_core::crypto::Public; use sp_keystore::{testing::KeyStore, CryptoStore}; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use substrate_test_runtime_client::runtime::Block; @@ -50,7 +49,7 @@ pub(crate) struct TestApi { impl ProvideRuntimeApi for TestApi { type Api = RuntimeApi; - fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { + fn runtime_api(&self) -> ApiRef<'_, Self::Api> { RuntimeApi { authorities: self.authorities.clone() }.into() } } @@ -73,6 +72,7 @@ impl HeaderBackend for TestApi { genesis_hash: Default::default(), number_leaves: Default::default(), finalized_state: None, + block_gap: None, } } @@ -112,17 +112,18 @@ sp_api::mock_impl_runtime_apis! { #[derive(Debug)] pub enum TestNetworkEvent { - GetCalled(kad::record::Key), - PutCalled(kad::record::Key, Vec), + GetCalled(KademliaKey), + PutCalled(KademliaKey, Vec), } pub struct TestNetwork { peer_id: PeerId, + identity: Keypair, external_addresses: Vec, // Whenever functions on `TestNetwork` are called, the function arguments are added to the // vectors below. - pub put_value_call: Arc)>>>, - pub get_value_call: Arc>>, + pub put_value_call: Arc)>>>, + pub get_value_call: Arc>>, event_sender: mpsc::UnboundedSender, event_receiver: Option>, } @@ -136,8 +137,10 @@ impl TestNetwork { impl Default for TestNetwork { fn default() -> Self { let (tx, rx) = mpsc::unbounded(); + let identity = Keypair::generate_ed25519(); TestNetwork { - peer_id: PeerId::random(), + peer_id: identity.public().to_peer_id(), + identity, external_addresses: vec!["/ip6/2001:db8::/tcp/30333".parse().unwrap()], put_value_call: Default::default(), get_value_call: Default::default(), @@ -147,16 +150,24 @@ impl Default for TestNetwork { } } -#[async_trait] -impl NetworkProvider for TestNetwork { - fn put_value(&self, key: kad::record::Key, value: Vec) { +impl NetworkSigner for TestNetwork { + fn sign_with_local_identity( + &self, + msg: impl AsRef<[u8]>, + ) -> std::result::Result { + Signature::sign_message(msg, &self.identity) + } +} + +impl NetworkDHTProvider for TestNetwork { + fn put_value(&self, key: KademliaKey, value: Vec) { self.put_value_call.lock().unwrap().push((key.clone(), value.clone())); self.event_sender .clone() .unbounded_send(TestNetworkEvent::PutCalled(key, value)) .unwrap(); } - fn get_value(&self, key: &kad::record::Key) { + fn get_value(&self, key: &KademliaKey) { self.get_value_call.lock().unwrap().push(key.clone()); self.event_sender .clone() @@ -175,35 +186,39 @@ impl NetworkStateInfo for TestNetwork { } } -async fn build_dht_event( +struct TestSigner<'a> { + keypair: &'a Keypair, +} + +impl<'a> NetworkSigner for TestSigner<'a> { + fn sign_with_local_identity( + &self, + msg: impl AsRef<[u8]>, + ) -> std::result::Result { + Signature::sign_message(msg, self.keypair) + } +} + +async fn build_dht_event( addresses: Vec, public_key: AuthorityId, - key_store: &KeyStore, -) -> (libp2p::kad::record::Key, Vec) { - let mut serialized_addresses = vec![]; - schema::AuthorityAddresses { addresses: addresses.into_iter().map(|a| a.to_vec()).collect() } - .encode(&mut serialized_addresses) - .map_err(Error::EncodingProto) - .unwrap(); - - let signature = key_store - .sign_with( - key_types::AUTHORITY_DISCOVERY, - &public_key.clone().into(), - serialized_addresses.as_slice(), - ) - .await - .unwrap() - .unwrap(); - - let mut signed_addresses = vec![]; - schema::SignedAuthorityAddresses { addresses: serialized_addresses.clone(), signature } - .encode(&mut signed_addresses) - .unwrap(); - - let key = hash_authority_id(&public_key.to_raw_vec()); - let value = signed_addresses; - (key, value) + key_store: &dyn CryptoStore, + network: Option<&Signer>, +) -> Vec<(KademliaKey, Vec)> { + let serialized_record = + serialize_authority_record(serialize_addresses(addresses.into_iter())).unwrap(); + + let peer_signature = network.map(|n| sign_record_with_peer_id(&serialized_record, n).unwrap()); + let kv_pairs = sign_record_with_authority_ids( + serialized_record, + peer_signature, + key_store, + vec![public_key.into()], + ) + .await + .unwrap(); + // There is always a single item in it, because we signed it with a single key + kv_pairs } #[test] @@ -302,7 +317,7 @@ fn publish_discover_cycle() { let dht_event = { let (key, value) = network.put_value_call.lock().unwrap().pop().unwrap(); - sc_network::DhtEvent::ValueFound(vec![(key, value)]) + DhtEvent::ValueFound(vec![(key, value)]) }; // Node B discovering node A's address. @@ -451,13 +466,14 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() { // Make previously triggered lookup succeed. let dht_event = { - let (key, value) = build_dht_event( + let kv_pairs = build_dht_event::( vec![remote_multiaddr.clone()], remote_public_key.clone(), &remote_key_store, + None, ) .await; - sc_network::DhtEvent::ValueFound(vec![(key, value)]) + DhtEvent::ValueFound(kv_pairs) }; dht_event_tx.send(dht_event).await.expect("Channel has capacity of 1."); @@ -468,101 +484,194 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() { .send(ServicetoWorkerMsg::GetAddressesByAuthorityId(remote_public_key, sender)) .await .expect("Channel has capacity of 1."); - assert_eq!(Some(vec![remote_multiaddr]), addresses.await.unwrap()); + assert_eq!(Some(HashSet::from([remote_multiaddr])), addresses.await.unwrap()); }); } -#[test] -fn limit_number_of_addresses_added_to_cache_per_authority() { - let remote_key_store = KeyStore::new(); - let remote_public = - block_on(remote_key_store.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)) - .unwrap(); +struct DhtValueFoundTester { + pub remote_key_store: KeyStore, + pub remote_authority_public: sp_core::sr25519::Public, + pub remote_node_key: Keypair, + pub local_worker: Option< + Worker< + TestApi, + TestNetwork, + sp_runtime::generic::Block< + sp_runtime::generic::Header, + substrate_test_runtime_client::runtime::Extrinsic, + >, + std::pin::Pin>>, + >, + >, +} - let addresses = (0..100) - .map(|_| { - let peer_id = PeerId::random(); - let address: Multiaddr = "/ip6/2001:db8:0:0:0:0:0:1/tcp/30333".parse().unwrap(); - address.with(multiaddr::Protocol::P2p(peer_id.into())) - }) - .collect(); +impl DhtValueFoundTester { + fn new() -> Self { + let remote_key_store = KeyStore::new(); + let remote_authority_public = + block_on(remote_key_store.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)) + .unwrap(); - let dht_event = block_on(build_dht_event(addresses, remote_public.into(), &remote_key_store)); + let remote_node_key = Keypair::generate_ed25519(); + Self { remote_key_store, remote_authority_public, remote_node_key, local_worker: None } + } - let (_dht_event_tx, dht_event_rx) = channel(1); + fn multiaddr_with_peer_id(&self, idx: u16) -> Multiaddr { + let peer_id = self.remote_node_key.public().to_peer_id(); + let address: Multiaddr = + format!("/ip6/2001:db8:0:0:0:0:0:{:x}/tcp/30333", idx).parse().unwrap(); - let (_to_worker, from_service) = mpsc::channel(0); - let mut worker = Worker::new( - from_service, - Arc::new(TestApi { authorities: vec![remote_public.into()] }), - Arc::new(TestNetwork::default()), - Box::pin(dht_event_rx), - Role::Discover, + address.with(multiaddr::Protocol::P2p(peer_id.into())) + } + + fn process_value_found( + &mut self, + strict_record_validation: bool, + values: Vec<(KademliaKey, Vec)>, + ) -> Option<&HashSet> { + let (_dht_event_tx, dht_event_rx) = channel(1); + let local_test_api = + Arc::new(TestApi { authorities: vec![self.remote_authority_public.into()] }); + let local_network: Arc = Arc::new(Default::default()); + let local_key_store = KeyStore::new(); + + let (_to_worker, from_service) = mpsc::channel(0); + let mut local_worker = Worker::new( + from_service, + local_test_api, + local_network.clone(), + Box::pin(dht_event_rx), + Role::PublishAndDiscover(Arc::new(local_key_store)), + None, + WorkerConfig { strict_record_validation, ..Default::default() }, + ); + + block_on(local_worker.refill_pending_lookups_queue()).unwrap(); + local_worker.start_new_lookups(); + + drop(local_worker.handle_dht_value_found_event(values)); + + self.local_worker = Some(local_worker); + + self.local_worker + .as_ref() + .map(|w| { + w.addr_cache.get_addresses_by_authority_id(&self.remote_authority_public.into()) + }) + .unwrap() + } +} + +#[test] +fn limit_number_of_addresses_added_to_cache_per_authority() { + let mut tester = DhtValueFoundTester::new(); + assert!(MAX_ADDRESSES_PER_AUTHORITY < 100); + let addresses = (1..100).map(|i| tester.multiaddr_with_peer_id(i)).collect(); + let kv_pairs = block_on(build_dht_event::( + addresses, + tester.remote_authority_public.into(), + &tester.remote_key_store, None, - Default::default(), - ); + )); + + let cached_remote_addresses = tester.process_value_found(false, kv_pairs); + assert_eq!(MAX_ADDRESSES_PER_AUTHORITY, cached_remote_addresses.unwrap().len()); +} - block_on(worker.refill_pending_lookups_queue()).unwrap(); - worker.start_new_lookups(); +#[test] +fn strict_accept_address_with_peer_signature() { + let mut tester = DhtValueFoundTester::new(); + let addr = tester.multiaddr_with_peer_id(1); + let kv_pairs = block_on(build_dht_event( + vec![addr.clone()], + tester.remote_authority_public.into(), + &tester.remote_key_store, + Some(&TestSigner { keypair: &tester.remote_node_key }), + )); + + let cached_remote_addresses = tester.process_value_found(true, kv_pairs); - worker.handle_dht_value_found_event(vec![dht_event]).unwrap(); assert_eq!( - MAX_ADDRESSES_PER_AUTHORITY, - worker - .addr_cache - .get_addresses_by_authority_id(&remote_public.into()) - .unwrap() - .len(), + Some(&HashSet::from([addr])), + cached_remote_addresses, + "Expect worker to only cache `Multiaddr`s with `PeerId`s.", ); } #[test] -fn do_not_cache_addresses_without_peer_id() { - let remote_key_store = KeyStore::new(); - let remote_public = - block_on(remote_key_store.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)) - .unwrap(); +fn reject_address_with_rogue_peer_signature() { + let mut tester = DhtValueFoundTester::new(); + let rogue_remote_node_key = Keypair::generate_ed25519(); + let kv_pairs = block_on(build_dht_event( + vec![tester.multiaddr_with_peer_id(1)], + tester.remote_authority_public.into(), + &tester.remote_key_store, + Some(&TestSigner { keypair: &rogue_remote_node_key }), + )); - let multiaddr_with_peer_id = { - let peer_id = PeerId::random(); - let address: Multiaddr = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333".parse().unwrap(); + let cached_remote_addresses = tester.process_value_found(false, kv_pairs); - address.with(multiaddr::Protocol::P2p(peer_id.into())) - }; - - let multiaddr_without_peer_id: Multiaddr = - "/ip6/2001:db8:0:0:0:0:0:1/tcp/30333".parse().unwrap(); + assert!( + cached_remote_addresses.is_none(), + "Expected worker to ignore record signed by a different key.", + ); +} - let dht_event = block_on(build_dht_event( - vec![multiaddr_with_peer_id.clone(), multiaddr_without_peer_id], - remote_public.into(), - &remote_key_store, +#[test] +fn reject_address_with_invalid_peer_signature() { + let mut tester = DhtValueFoundTester::new(); + let mut kv_pairs = block_on(build_dht_event( + vec![tester.multiaddr_with_peer_id(1)], + tester.remote_authority_public.into(), + &tester.remote_key_store, + Some(&TestSigner { keypair: &tester.remote_node_key }), )); + // tamper with the signature + let mut record = schema::SignedAuthorityRecord::decode(kv_pairs[0].1.as_slice()).unwrap(); + record.peer_signature.as_mut().map(|p| p.signature[1] = !p.signature[1]); + record.encode(&mut kv_pairs[0].1).unwrap(); - let (_dht_event_tx, dht_event_rx) = channel(1); - let local_test_api = Arc::new(TestApi { authorities: vec![remote_public.into()] }); - let local_network: Arc = Arc::new(Default::default()); - let local_key_store = KeyStore::new(); + let cached_remote_addresses = tester.process_value_found(false, kv_pairs); - let (_to_worker, from_service) = mpsc::channel(0); - let mut local_worker = Worker::new( - from_service, - local_test_api, - local_network.clone(), - Box::pin(dht_event_rx), - Role::PublishAndDiscover(Arc::new(local_key_store)), - None, - Default::default(), + assert!( + cached_remote_addresses.is_none(), + "Expected worker to ignore record with tampered signature.", ); +} + +#[test] +fn reject_address_without_peer_signature() { + let mut tester = DhtValueFoundTester::new(); + let kv_pairs = block_on(build_dht_event::( + vec![tester.multiaddr_with_peer_id(1)], + tester.remote_authority_public.into(), + &tester.remote_key_store, + None, + )); + + let cached_remote_addresses = tester.process_value_found(true, kv_pairs); - block_on(local_worker.refill_pending_lookups_queue()).unwrap(); - local_worker.start_new_lookups(); + assert!(cached_remote_addresses.is_none(), "Expected worker to ignore unsigned record.",); +} + +#[test] +fn do_not_cache_addresses_without_peer_id() { + let mut tester = DhtValueFoundTester::new(); + let multiaddr_with_peer_id = tester.multiaddr_with_peer_id(1); + let multiaddr_without_peer_id: Multiaddr = + "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333".parse().unwrap(); + let kv_pairs = block_on(build_dht_event::( + vec![multiaddr_with_peer_id.clone(), multiaddr_without_peer_id], + tester.remote_authority_public.into(), + &tester.remote_key_store, + None, + )); - local_worker.handle_dht_value_found_event(vec![dht_event]).unwrap(); + let cached_remote_addresses = tester.process_value_found(false, kv_pairs); assert_eq!( - Some(&vec![multiaddr_with_peer_id]), - local_worker.addr_cache.get_addresses_by_authority_id(&remote_public.into()), + Some(&HashSet::from([multiaddr_with_peer_id])), + cached_remote_addresses, "Expect worker to only cache `Multiaddr`s with `PeerId`s.", ); } @@ -695,10 +804,14 @@ fn lookup_throttling() { let remote_hash = network.get_value_call.lock().unwrap().pop().unwrap(); let remote_key: AuthorityId = remote_hash_to_key.get(&remote_hash).unwrap().clone(); let dht_event = { - let (key, value) = - build_dht_event(vec![remote_multiaddr.clone()], remote_key, &remote_key_store) - .await; - sc_network::DhtEvent::ValueFound(vec![(key, value)]) + let kv_pairs = build_dht_event::( + vec![remote_multiaddr.clone()], + remote_key, + &remote_key_store, + None, + ) + .await; + DhtEvent::ValueFound(kv_pairs) }; dht_event_tx.send(dht_event).await.expect("Channel has capacity of 1."); @@ -712,7 +825,7 @@ fn lookup_throttling() { // Make second one fail. let remote_hash = network.get_value_call.lock().unwrap().pop().unwrap(); - let dht_event = sc_network::DhtEvent::ValueNotFound(remote_hash); + let dht_event = DhtEvent::ValueNotFound(remote_hash); dht_event_tx.send(dht_event).await.expect("Channel has capacity of 1."); // Assert worker to trigger another lookup. diff --git a/client/basic-authorship/Cargo.toml b/client/basic-authorship/Cargo.toml index 469df55cf0233..09b5c47394491 100644 --- a/client/basic-authorship/Cargo.toml +++ b/client/basic-authorship/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-basic-authorship" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Basic implementation of block-authoring logic." readme = "README.md" @@ -13,24 +13,24 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } -futures = "0.3.9" +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.8" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.9.0"} +log = "0.4.17" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-proposer-metrics = { version = "0.10.0-dev", path = "../proposer-metrics" } +sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../client/transaction-pool/api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../client/transaction-pool/api" } -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sc-proposer-metrics = { version = "0.9.0", path = "../proposer-metrics" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] +parking_lot = "0.12.1" sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -parking_lot = "0.11.1" diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 144a3ab6850ff..b69294bf6ccb0 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ // FIXME #1021 move this into sp-consensus -use codec::{Decode, Encode}; +use codec::Encode; use futures::{ channel::oneshot, future, @@ -34,19 +34,18 @@ use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_INFO}; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_blockchain::{ApplyExtrinsicFailed::Validity, Error::ApplyExtrinsicFailed, HeaderBackend}; -use sp_consensus::{ - evaluation, DisableProofRecording, EnableProofRecording, ProofRecording, Proposal, -}; +use sp_consensus::{DisableProofRecording, EnableProofRecording, ProofRecording, Proposal}; use sp_core::traits::SpawnNamed; use sp_inherents::InherentData; use sp_runtime::{ generic::BlockId, - traits::{BlakeTwo256, Block as BlockT, DigestFor, Hash as HashT, Header as HeaderT}, + traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT}, + Digest, Percent, SaturatedConversion, }; use std::{marker::PhantomData, pin::Pin, sync::Arc, time}; use prometheus_endpoint::Registry as PrometheusRegistry; -use sc_proposer_metrics::MetricsLink as PrometheusMetrics; +use sc_proposer_metrics::{EndProposingReason, MetricsLink as PrometheusMetrics}; /// Default block size limit in bytes used by [`Proposer`]. /// @@ -57,6 +56,8 @@ use sc_proposer_metrics::MetricsLink as PrometheusMetrics; /// transferred to other nodes. pub const DEFAULT_BLOCK_SIZE_LIMIT: usize = 4 * 1024 * 1024 + 512; +const DEFAULT_SOFT_DEADLINE_PERCENT: Percent = Percent::from_percent(50); + /// [`Proposer`] factory. pub struct ProposerFactory { spawn_handle: Box, @@ -71,6 +72,14 @@ pub struct ProposerFactory { /// If no `block_size_limit` is passed to [`sp_consensus::Proposer::propose`], this block size /// limit will be used. default_block_size_limit: usize, + /// Soft deadline percentage of hard deadline. + /// + /// The value is used to compute soft deadline during block production. + /// The soft deadline indicates where we should stop attempting to add transactions + /// to the block, which exhaust resources. After soft deadline is reached, + /// we switch to a fixed-amount mode, in which after we see `MAX_SKIPPED_TRANSACTIONS` + /// transactions which exhaust resrouces, we will conclude that the block is full. + soft_deadline_percent: Percent, telemetry: Option, /// When estimating the block size, should the proof be included? include_proof_in_block_size_estimation: bool, @@ -95,6 +104,7 @@ impl ProposerFactory { transaction_pool, metrics: PrometheusMetrics::new(prometheus), default_block_size_limit: DEFAULT_BLOCK_SIZE_LIMIT, + soft_deadline_percent: DEFAULT_SOFT_DEADLINE_PERCENT, telemetry, client, include_proof_in_block_size_estimation: false, @@ -123,6 +133,7 @@ impl ProposerFactory { transaction_pool, metrics: PrometheusMetrics::new(prometheus), default_block_size_limit: DEFAULT_BLOCK_SIZE_LIMIT, + soft_deadline_percent: DEFAULT_SOFT_DEADLINE_PERCENT, telemetry, include_proof_in_block_size_estimation: true, _phantom: PhantomData, @@ -146,6 +157,22 @@ impl ProposerFactory { pub fn set_default_block_size_limit(&mut self, limit: usize) { self.default_block_size_limit = limit; } + + /// Set soft deadline percentage. + /// + /// The value is used to compute soft deadline during block production. + /// The soft deadline indicates where we should stop attempting to add transactions + /// to the block, which exhaust resources. After soft deadline is reached, + /// we switch to a fixed-amount mode, in which after we see `MAX_SKIPPED_TRANSACTIONS` + /// transactions which exhaust resrouces, we will conclude that the block is full. + /// + /// Setting the value too low will significantly limit the amount of transactions + /// we try in case they exhaust resources. Setting the value too high can + /// potentially open a DoS vector, where many "exhaust resources" transactions + /// are being tried with no success, hence block producer ends up creating an empty block. + pub fn set_soft_deadline(&mut self, percent: Percent) { + self.soft_deadline_percent = percent; + } } impl ProposerFactory @@ -176,13 +203,13 @@ where let proposer = Proposer::<_, _, _, _, PR> { spawn_handle: self.spawn_handle.clone(), client: self.client.clone(), - parent_hash, parent_id: id, parent_number: *parent_header.number(), transaction_pool: self.transaction_pool.clone(), now, metrics: self.metrics.clone(), default_block_size_limit: self.default_block_size_limit, + soft_deadline_percent: self.soft_deadline_percent, telemetry: self.telemetry.clone(), _phantom: PhantomData, include_proof_in_block_size_estimation: self.include_proof_in_block_size_estimation, @@ -220,7 +247,6 @@ where pub struct Proposer { spawn_handle: Box, client: Arc, - parent_hash: ::Hash, parent_id: BlockId, parent_number: <::Header as HeaderT>::Number, transaction_pool: Arc, @@ -228,6 +254,7 @@ pub struct Proposer { metrics: PrometheusMetrics, default_block_size_limit: usize, include_proof_in_block_size_estimation: bool, + soft_deadline_percent: Percent, telemetry: Option, _phantom: PhantomData<(B, PR)>, } @@ -261,7 +288,7 @@ where fn propose( self, inherent_data: InherentData, - inherent_digests: DigestFor, + inherent_digests: Digest, max_duration: time::Duration, block_size_limit: Option, ) -> Self::Proposal { @@ -270,6 +297,7 @@ where spawn_handle.spawn_blocking( "basic-authorship-proposer", + None, Box::pin(async move { // leave some time for evaluation and block finalization (33%) let deadline = (self.now)() + max_duration - max_duration / 3; @@ -286,6 +314,11 @@ where } } +/// If the block is full we will attempt to push at most +/// this number of transactions before quitting for real. +/// It allows us to increase block utilization. +const MAX_SKIPPED_TRANSACTIONS: usize = 8; + impl Proposer where A: TransactionPool, @@ -304,20 +337,28 @@ where async fn propose_with( self, inherent_data: InherentData, - inherent_digests: DigestFor, + inherent_digests: Digest, deadline: time::Instant, block_size_limit: Option, ) -> Result, PR::Proof>, sp_blockchain::Error> { - /// If the block is full we will attempt to push at most - /// this number of transactions before quitting for real. - /// It allows us to increase block utilization. - const MAX_SKIPPED_TRANSACTIONS: usize = 8; - + let propose_with_start = time::Instant::now(); let mut block_builder = self.client.new_block_at(&self.parent_id, inherent_digests, PR::ENABLED)?; - for inherent in block_builder.create_inherents(inherent_data)? { + let create_inherents_start = time::Instant::now(); + let inherents = block_builder.create_inherents(inherent_data)?; + let create_inherents_end = time::Instant::now(); + + self.metrics.report(|metrics| { + metrics.create_inherents_time.observe( + create_inherents_end + .saturating_duration_since(create_inherents_start) + .as_secs_f64(), + ); + }); + + for inherent in inherents { match block_builder.push(inherent) { Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { warn!("⚠️ Dropping non-mandatory inherent from overweight block.") @@ -326,7 +367,7 @@ where error!( "❌️ Mandatory inherent extrinsic returned error. Block cannot be produced." ); - Err(ApplyExtrinsicFailed(Validity(e)))? + return Err(ApplyExtrinsicFailed(Validity(e))) }, Err(e) => { warn!("❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); @@ -336,6 +377,12 @@ where } // proceed with transactions + // We calculate soft deadline used only in case we start skipping transactions. + let now = (self.now)(); + let left = deadline.saturating_duration_since(now); + let left_micros: u64 = left.as_micros().saturated_into(); + let soft_deadline = + now + time::Duration::from_micros(self.soft_deadline_percent.mul_floor(left_micros)); let block_timer = time::Instant::now(); let mut skipped = 0; let mut unqueue_invalid = Vec::new(); @@ -344,7 +391,7 @@ where let mut t2 = futures_timer::Delay::new(deadline.saturating_duration_since((self.now)()) / 8).fuse(); - let pending_iterator = select! { + let mut pending_iterator = select! { res = t1 => res, _ = t2 => { log::warn!( @@ -361,15 +408,21 @@ where debug!("Attempting to push transactions from the pool."); debug!("Pool status: {:?}", self.transaction_pool.status()); let mut transaction_pushed = false; - let mut hit_block_size_limit = false; - for pending_tx in pending_iterator { - if (self.now)() > deadline { + let end_reason = loop { + let pending_tx = if let Some(pending_tx) = pending_iterator.next() { + pending_tx + } else { + break EndProposingReason::NoMoreTransactions + }; + + let now = (self.now)(); + if now > deadline { debug!( "Consensus deadline reached when pushing block transactions, \ proceeding with proposing." ); - break + break EndProposingReason::HitDeadline } let pending_tx_data = pending_tx.data().clone(); @@ -378,6 +431,7 @@ where let block_size = block_builder.estimate_block_size(self.include_proof_in_block_size_estimation); if block_size + pending_tx_data.encoded_size() > block_size_limit { + pending_iterator.report_invalid(&pending_tx); if skipped < MAX_SKIPPED_TRANSACTIONS { skipped += 1; debug!( @@ -386,10 +440,16 @@ where MAX_SKIPPED_TRANSACTIONS - skipped, ); continue + } else if now < soft_deadline { + debug!( + "Transaction would overflow the block size limit, \ + but we still have time before the soft deadline, so \ + we will try a bit more." + ); + continue } else { debug!("Reached block size limit, proceeding with proposing."); - hit_block_size_limit = true; - break + break EndProposingReason::HitBlockSizeLimit } } @@ -400,18 +460,25 @@ where debug!("[{:?}] Pushed to the block.", pending_tx_hash); }, Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { + pending_iterator.report_invalid(&pending_tx); if skipped < MAX_SKIPPED_TRANSACTIONS { skipped += 1; debug!( "Block seems full, but will try {} more transactions before quitting.", MAX_SKIPPED_TRANSACTIONS - skipped, ); + } else if (self.now)() < soft_deadline { + debug!( + "Block seems full, but we still have time before the soft deadline, \ + so we will try a bit more before quitting." + ); } else { - debug!("Block is full, proceed with proposing."); - break + debug!("Reached block weight limit, proceeding with proposing."); + break EndProposingReason::HitBlockWeightLimit } }, Err(e) if skipped > 0 => { + pending_iterator.report_invalid(&pending_tx); trace!( "[{:?}] Ignoring invalid transaction when skipping: {}", pending_tx_hash, @@ -419,13 +486,14 @@ where ); }, Err(e) => { + pending_iterator.report_invalid(&pending_tx); debug!("[{:?}] Invalid transaction: {}", pending_tx_hash, e); unqueue_invalid.push(pending_tx_hash); }, } - } + }; - if hit_block_size_limit && !transaction_pushed { + if matches!(end_reason, EndProposingReason::HitBlockSizeLimit) && !transaction_pushed { warn!( "Hit block size limit of `{}` without including any transaction!", block_size_limit, @@ -439,17 +507,20 @@ where self.metrics.report(|metrics| { metrics.number_of_transactions.set(block.extrinsics().len() as u64); metrics.block_constructed.observe(block_timer.elapsed().as_secs_f64()); + + metrics.report_end_proposing_reason(end_reason); }); info!( - "🎁 Prepared block for proposing at {} [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]", + "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]", block.header().number(), + block_timer.elapsed().as_millis(), ::Hash::from(block.header().hash()), block.header().parent_hash(), block.extrinsics().len(), block.extrinsics() .iter() - .map(|xt| format!("{}", BlakeTwo256::hash_of(xt))) + .map(|xt| BlakeTwo256::hash_of(xt).to_string()) .collect::>() .join(", ") ); @@ -461,18 +532,16 @@ where "hash" => ?::Hash::from(block.header().hash()), ); - if Decode::decode(&mut block.encode().as_slice()).as_ref() != Ok(&block) { - error!("Failed to verify block encoding/decoding"); - } - - if let Err(err) = - evaluation::evaluate_initial(&block, &self.parent_hash, self.parent_number) - { - error!("Failed to evaluate authored block: {:?}", err); - } - let proof = PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?; + + let propose_with_end = time::Instant::now(); + self.metrics.report(|metrics| { + metrics.create_block_proposal_time.observe( + propose_with_end.saturating_duration_since(propose_with_start).as_secs_f64(), + ); + }); + Ok(Proposal { block, proof, storage_changes }) } } @@ -489,6 +558,7 @@ mod tests { use sp_api::Core; use sp_blockchain::HeaderBackend; use sp_consensus::{BlockOrigin, Environment, Proposer}; + use sp_core::Pair; use sp_runtime::traits::NumberFor; use substrate_test_runtime_client::{ prelude::*, @@ -503,11 +573,24 @@ mod tests { amount: Default::default(), nonce, from: AccountKeyring::Alice.into(), - to: Default::default(), + to: AccountKeyring::Bob.into(), } .into_signed_tx() } + fn exhausts_resources_extrinsic_from(who: usize) -> Extrinsic { + let pair = AccountKeyring::numeric(who); + let transfer = Transfer { + // increase the amount to bump priority + amount: 1, + nonce: 0, + from: pair.public(), + to: AccountKeyring::Bob.into(), + }; + let signature = pair.sign(&transfer.encode()).into(); + Extrinsic::Transfer { transfer, signature, exhaust_resources_when_not_first: true } + } + fn chain_event(header: B::Header) -> ChainEvent where NumberFor: From, @@ -553,7 +636,7 @@ mod tests { return value.1 } let old = value.1; - let new = old + time::Duration::from_secs(2); + let new = old + time::Duration::from_secs(1); *value = (true, new); old }), @@ -653,14 +736,9 @@ mod tests { let api = client.runtime_api(); api.execute_block(&block_id, proposal.block).unwrap(); - let state = backend.state_at(block_id).unwrap(); - let changes_trie_state = - backend::changes_tries_state_at_block(&block_id, backend.changes_trie_storage()) - .unwrap(); + let state = backend.state_at(genesis_hash).unwrap(); - let storage_changes = api - .into_storage_changes(&state, changes_trie_state.as_ref(), genesis_hash) - .unwrap(); + let storage_changes = api.into_storage_changes(&state, genesis_hash).unwrap(); assert_eq!( proposal.storage_changes.transaction_storage_root, @@ -691,14 +769,14 @@ mod tests { amount: Default::default(), nonce: 2, from: AccountKeyring::Alice.into(), - to: Default::default(), + to: AccountKeyring::Bob.into(), }.into_resources_exhausting_tx(), extrinsic(3), Transfer { amount: Default::default(), nonce: 4, from: AccountKeyring::Alice.into(), - to: Default::default(), + to: AccountKeyring::Bob.into(), }.into_resources_exhausting_tx(), extrinsic(5), extrinsic(6), @@ -718,7 +796,7 @@ mod tests { ); // when - let deadline = time::Duration::from_secs(9); + let deadline = time::Duration::from_secs(900); let block = block_on(proposer.propose(Default::default(), Default::default(), deadline, None)) .map(|r| r.block) @@ -726,8 +804,8 @@ mod tests { // then // block should have some extrinsics although we have some more in the pool. - assert_eq!(block.extrinsics().len(), expected_block_extrinsics); assert_eq!(txpool.ready().count(), expected_pool_transactions); + assert_eq!(block.extrinsics().len(), expected_block_extrinsics); block }; @@ -740,6 +818,7 @@ mod tests { .expect("there should be header"), )), ); + assert_eq!(txpool.ready().count(), 7); // let's create one block and import it let block = propose_block(&client, 0, 2, 7); @@ -753,6 +832,7 @@ mod tests { .expect("there should be header"), )), ); + assert_eq!(txpool.ready().count(), 5); // now let's make sure that we can still make some progress let block = propose_block(&client, 1, 2, 5); @@ -775,10 +855,18 @@ mod tests { .expect("header get error") .expect("there should be header"); - let extrinsics_num = 4; - let extrinsics = (0..extrinsics_num) - .map(|v| Extrinsic::IncludeData(vec![v as u8; 10])) - .collect::>(); + let extrinsics_num = 5; + let extrinsics = std::iter::once( + Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Bob.into(), + amount: 100, + nonce: 0, + } + .into_signed_tx(), + ) + .chain((0..extrinsics_num - 1).map(|v| Extrinsic::IncludeData(vec![v as u8; 10]))) + .collect::>(); let block_limit = genesis_header.encoded_size() + extrinsics @@ -842,7 +930,146 @@ mod tests { .unwrap(); // The block limit didn't changed, but we now include the proof in the estimation of the - // block size and thus, one less transaction should fit into the limit. - assert_eq!(block.extrinsics().len(), extrinsics_num - 2); + // block size and thus, only the `Transfer` will fit into the block. It reads more data + // than we have reserved in the block limit. + assert_eq!(block.extrinsics().len(), 1); + } + + #[test] + fn should_keep_adding_transactions_after_exhausts_resources_before_soft_deadline() { + // given + let client = Arc::new(substrate_test_runtime_client::new()); + let spawner = sp_core::testing::TaskExecutor::new(); + let txpool = BasicPool::new_full( + Default::default(), + true.into(), + None, + spawner.clone(), + client.clone(), + ); + + block_on( + txpool.submit_at( + &BlockId::number(0), + SOURCE, + // add 2 * MAX_SKIPPED_TRANSACTIONS that exhaust resources + (0..MAX_SKIPPED_TRANSACTIONS * 2) + .into_iter() + .map(|i| exhausts_resources_extrinsic_from(i)) + // and some transactions that are okay. + .chain((0..MAX_SKIPPED_TRANSACTIONS).into_iter().map(|i| extrinsic(i as _))) + .collect(), + ), + ) + .unwrap(); + + block_on( + txpool.maintain(chain_event( + client + .header(&BlockId::Number(0u64)) + .expect("header get error") + .expect("there should be header"), + )), + ); + assert_eq!(txpool.ready().count(), MAX_SKIPPED_TRANSACTIONS * 3); + + let mut proposer_factory = + ProposerFactory::new(spawner.clone(), client.clone(), txpool.clone(), None, None); + + let cell = Mutex::new(time::Instant::now()); + let proposer = proposer_factory.init_with_now( + &client.header(&BlockId::number(0)).unwrap().unwrap(), + Box::new(move || { + let mut value = cell.lock(); + let old = *value; + *value = old + time::Duration::from_secs(1); + old + }), + ); + + // when + // give it enough time so that deadline is never triggered. + let deadline = time::Duration::from_secs(900); + let block = + block_on(proposer.propose(Default::default(), Default::default(), deadline, None)) + .map(|r| r.block) + .unwrap(); + + // then block should have all non-exhaust resources extrinsics (+ the first one). + assert_eq!(block.extrinsics().len(), MAX_SKIPPED_TRANSACTIONS + 1); + } + + #[test] + fn should_only_skip_up_to_some_limit_after_soft_deadline() { + // given + let client = Arc::new(substrate_test_runtime_client::new()); + let spawner = sp_core::testing::TaskExecutor::new(); + let txpool = BasicPool::new_full( + Default::default(), + true.into(), + None, + spawner.clone(), + client.clone(), + ); + + block_on( + txpool.submit_at( + &BlockId::number(0), + SOURCE, + (0..MAX_SKIPPED_TRANSACTIONS + 2) + .into_iter() + .map(|i| exhausts_resources_extrinsic_from(i)) + // and some transactions that are okay. + .chain((0..MAX_SKIPPED_TRANSACTIONS).into_iter().map(|i| extrinsic(i as _))) + .collect(), + ), + ) + .unwrap(); + + block_on( + txpool.maintain(chain_event( + client + .header(&BlockId::Number(0u64)) + .expect("header get error") + .expect("there should be header"), + )), + ); + assert_eq!(txpool.ready().count(), MAX_SKIPPED_TRANSACTIONS * 2 + 2); + + let mut proposer_factory = + ProposerFactory::new(spawner.clone(), client.clone(), txpool.clone(), None, None); + + let deadline = time::Duration::from_secs(600); + let cell = Arc::new(Mutex::new((0, time::Instant::now()))); + let cell2 = cell.clone(); + let proposer = proposer_factory.init_with_now( + &client.header(&BlockId::number(0)).unwrap().unwrap(), + Box::new(move || { + let mut value = cell.lock(); + let (called, old) = *value; + // add time after deadline is calculated internally (hence 1) + let increase = if called == 1 { + // we start after the soft_deadline should have already been reached. + deadline / 2 + } else { + // but we make sure to never reach the actual deadline + time::Duration::from_millis(0) + }; + *value = (called + 1, old + increase); + old + }), + ); + + let block = + block_on(proposer.propose(Default::default(), Default::default(), deadline, None)) + .map(|r| r.block) + .unwrap(); + + // then the block should have no transactions despite some in the pool + assert_eq!(block.extrinsics().len(), 1); + assert!( + cell2.lock().0 > MAX_SKIPPED_TRANSACTIONS, + "Not enough calls to current time, which indicates the test might have ended because of deadline, not soft deadline" + ); } } diff --git a/client/basic-authorship/src/lib.rs b/client/basic-authorship/src/lib.rs index 2b2fe554efdff..4a26ebd9df970 100644 --- a/client/basic-authorship/src/lib.rs +++ b/client/basic-authorship/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml new file mode 100644 index 0000000000000..d0b36a9255f76 --- /dev/null +++ b/client/beefy/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "beefy-gadget" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository = "https://github.com/paritytech/substrate" +description = "BEEFY Client gadget for substrate" +homepage = "https://substrate.io" + +[dependencies] +array-bytes = "4.1" +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +fnv = "1.0.6" +futures = "0.3" +futures-timer = "3.0.1" +log = "0.4" +parking_lot = "0.12.1" +thiserror = "1.0" +wasm-timer = "0.2.5" +beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy", package = "sp-beefy" } +prometheus = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } +sc-finality-grandpa = { version = "0.10.0-dev", path = "../../client/finality-grandpa" } +sc-keystore = { version = "4.0.0-dev", path = "../keystore" } +sc-network = { version = "0.10.0-dev", path = "../network" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } +sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-application-crypto = { version = "7.0.0", path = "../../primitives/application-crypto" } +sp-arithmetic = { version = "6.0.0", path = "../../primitives/arithmetic" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +sp-mmr-primitives = { version = "4.0.0-dev", path = "../../primitives/merkle-mountain-range" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } + +[dev-dependencies] +serde = "1.0.136" +strum = { version = "0.24.1", features = ["derive"] } +tempfile = "3.1.0" +tokio = "1.22.0" +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sc-network-test = { version = "0.8.0", path = "../network/test" } +sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } +sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml new file mode 100644 index 0000000000000..f5b5770153477 --- /dev/null +++ b/client/beefy/rpc/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "beefy-gadget-rpc" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository = "https://github.com/paritytech/substrate" +description = "RPC for the BEEFY Client gadget for substrate" +homepage = "https://substrate.io" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +futures = "0.3.21" +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +log = "0.4" +parking_lot = "0.12.1" +serde = { version = "1.0.136", features = ["derive"] } +thiserror = "1.0" +beefy-gadget = { version = "4.0.0-dev", path = "../." } +beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy", package = "sp-beefy" } +sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } +sc-utils = { version = "4.0.0-dev", path = "../../utils" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } + +[dev-dependencies] +serde_json = "1.0.85" +sc-rpc = { version = "4.0.0-dev", features = [ + "test-helpers", +], path = "../../rpc" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } +tokio = { version = "1.22.0", features = ["macros"] } diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs new file mode 100644 index 0000000000000..59a133b86214e --- /dev/null +++ b/client/beefy/rpc/src/lib.rs @@ -0,0 +1,303 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! RPC API for BEEFY. + +#![warn(missing_docs)] + +use parking_lot::RwLock; +use std::sync::Arc; + +use sc_rpc::SubscriptionTaskExecutor; +use sp_runtime::traits::Block as BlockT; + +use futures::{task::SpawnError, FutureExt, StreamExt}; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::{error::CallError, ErrorObject, SubscriptionResult}, + SubscriptionSink, +}; +use log::warn; + +use beefy_gadget::communication::notification::{ + BeefyBestBlockStream, BeefyVersionedFinalityProofStream, +}; + +mod notification; + +#[derive(Debug, thiserror::Error)] +/// Top-level error type for the RPC handler +pub enum Error { + /// The BEEFY RPC endpoint is not ready. + #[error("BEEFY RPC endpoint not ready")] + EndpointNotReady, + /// The BEEFY RPC background task failed to spawn. + #[error("BEEFY RPC background task failed to spawn")] + RpcTaskFailure(#[from] SpawnError), +} + +/// The error codes returned by jsonrpc. +pub enum ErrorCode { + /// Returned when BEEFY RPC endpoint is not ready. + NotReady = 1, + /// Returned on BEEFY RPC background task failure. + TaskFailure = 2, +} + +impl From for ErrorCode { + fn from(error: Error) -> Self { + match error { + Error::EndpointNotReady => ErrorCode::NotReady, + Error::RpcTaskFailure(_) => ErrorCode::TaskFailure, + } + } +} + +impl From for JsonRpseeError { + fn from(error: Error) -> Self { + let message = error.to_string(); + let code = ErrorCode::from(error); + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + code as i32, + message, + None::<()>, + ))) + } +} + +// Provides RPC methods for interacting with BEEFY. +#[rpc(client, server)] +pub trait BeefyApi { + /// Returns the block most recently finalized by BEEFY, alongside its justification. + #[subscription( + name = "beefy_subscribeJustifications" => "beefy_justifications", + unsubscribe = "beefy_unsubscribeJustifications", + item = Notification, + )] + fn subscribe_justifications(&self); + + /// Returns hash of the latest BEEFY finalized block as seen by this client. + /// + /// The latest BEEFY block might not be available if the BEEFY gadget is not running + /// in the network or if the client is still initializing or syncing with the network. + /// In such case an error would be returned. + #[method(name = "beefy_getFinalizedHead")] + async fn latest_finalized(&self) -> RpcResult; +} + +/// Implements the BeefyApi RPC trait for interacting with BEEFY. +pub struct Beefy { + finality_proof_stream: BeefyVersionedFinalityProofStream, + beefy_best_block: Arc>>, + executor: SubscriptionTaskExecutor, +} + +impl Beefy +where + Block: BlockT, +{ + /// Creates a new Beefy Rpc handler instance. + pub fn new( + finality_proof_stream: BeefyVersionedFinalityProofStream, + best_block_stream: BeefyBestBlockStream, + executor: SubscriptionTaskExecutor, + ) -> Result { + let beefy_best_block = Arc::new(RwLock::new(None)); + + let stream = best_block_stream.subscribe(); + let closure_clone = beefy_best_block.clone(); + let future = stream.for_each(move |best_beefy| { + let async_clone = closure_clone.clone(); + async move { *async_clone.write() = Some(best_beefy) } + }); + + executor.spawn("substrate-rpc-subscription", Some("rpc"), future.map(drop).boxed()); + Ok(Self { finality_proof_stream, beefy_best_block, executor }) + } +} + +#[async_trait] +impl BeefyApiServer + for Beefy +where + Block: BlockT, +{ + fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult { + let stream = self + .finality_proof_stream + .subscribe() + .map(|vfp| notification::EncodedVersionedFinalityProof::new::(vfp)); + + let fut = async move { + sink.pipe_from_stream(stream).await; + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) + } + + async fn latest_finalized(&self) -> RpcResult { + self.beefy_best_block + .read() + .as_ref() + .cloned() + .ok_or(Error::EndpointNotReady) + .map_err(Into::into) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use beefy_gadget::{ + communication::notification::BeefyVersionedFinalityProofSender, + justification::BeefyVersionedFinalityProof, + }; + use beefy_primitives::{known_payloads, Payload, SignedCommitment}; + use codec::{Decode, Encode}; + use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule}; + use sp_runtime::traits::{BlakeTwo256, Hash}; + use substrate_test_runtime_client::runtime::Block; + + fn setup_io_handler() -> (RpcModule>, BeefyVersionedFinalityProofSender) { + let (_, stream) = BeefyBestBlockStream::::channel(); + setup_io_handler_with_best_block_stream(stream) + } + + fn setup_io_handler_with_best_block_stream( + best_block_stream: BeefyBestBlockStream, + ) -> (RpcModule>, BeefyVersionedFinalityProofSender) { + let (finality_proof_sender, finality_proof_stream) = + BeefyVersionedFinalityProofStream::::channel(); + + let handler = + Beefy::new(finality_proof_stream, best_block_stream, sc_rpc::testing::test_executor()) + .expect("Setting up the BEEFY RPC handler works"); + + (handler.into_rpc(), finality_proof_sender) + } + + #[tokio::test] + async fn uninitialized_rpc_handler() { + let (rpc, _) = setup_io_handler(); + let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; + let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#.to_string(); + let (response, _) = rpc.raw_json_request(&request).await.unwrap(); + + assert_eq!(expected_response, response.result); + } + + #[tokio::test] + async fn latest_finalized_rpc() { + let (sender, stream) = BeefyBestBlockStream::::channel(); + let (io, _) = setup_io_handler_with_best_block_stream(stream); + + let hash = BlakeTwo256::hash(b"42"); + let r: Result<(), ()> = sender.notify(|| Ok(hash)); + r.unwrap(); + + // Verify RPC `beefy_getFinalizedHead` returns expected hash. + let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; + let expected = "{\ + \"jsonrpc\":\"2.0\",\ + \"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\",\ + \"id\":1\ + }" + .to_string(); + let not_ready = "{\ + \"jsonrpc\":\"2.0\",\ + \"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"},\ + \"id\":1\ + }" + .to_string(); + + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); + while std::time::Instant::now() < deadline { + let (response, _) = io.raw_json_request(request).await.expect("RPC requests work"); + if response.result != not_ready { + assert_eq!(response.result, expected); + // Success + return + } + std::thread::sleep(std::time::Duration::from_millis(50)) + } + + panic!( + "Deadline reached while waiting for best BEEFY block to update. Perhaps the background task is broken?" + ); + } + + #[tokio::test] + async fn subscribe_and_unsubscribe_with_wrong_id() { + let (rpc, _) = setup_io_handler(); + // Subscribe call. + let _sub = rpc + .subscribe("beefy_subscribeJustifications", EmptyParams::new()) + .await + .unwrap(); + + // Unsubscribe with wrong ID + let (response, _) = rpc + .raw_json_request( + r#"{"jsonrpc":"2.0","method":"beefy_unsubscribeJustifications","params":["FOO"],"id":1}"#, + ) + .await + .unwrap(); + let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; + + assert_eq!(response.result, expected); + } + + fn create_finality_proof() -> BeefyVersionedFinalityProof { + let payload = + Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode()); + BeefyVersionedFinalityProof::::V1(SignedCommitment { + commitment: beefy_primitives::Commitment { + payload, + block_number: 5, + validator_set_id: 0, + }, + signatures: vec![], + }) + } + + #[tokio::test] + async fn subscribe_and_listen_to_one_justification() { + let (rpc, finality_proof_sender) = setup_io_handler(); + + // Subscribe + let mut sub = rpc + .subscribe("beefy_subscribeJustifications", EmptyParams::new()) + .await + .unwrap(); + + // Notify with finality_proof + let finality_proof = create_finality_proof(); + let r: Result<(), ()> = finality_proof_sender.notify(|| Ok(finality_proof.clone())); + r.unwrap(); + + // Inspect what we received + let (bytes, recv_sub_id) = sub.next::().await.unwrap().unwrap(); + let recv_finality_proof: BeefyVersionedFinalityProof = + Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(&recv_sub_id, sub.subscription_id()); + assert_eq!(recv_finality_proof, finality_proof); + } +} diff --git a/client/beefy/rpc/src/notification.rs b/client/beefy/rpc/src/notification.rs new file mode 100644 index 0000000000000..a815425644d52 --- /dev/null +++ b/client/beefy/rpc/src/notification.rs @@ -0,0 +1,39 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use codec::Encode; +use serde::{Deserialize, Serialize}; + +use sp_runtime::traits::Block as BlockT; + +/// An encoded finality proof proving that the given header has been finalized. +/// The given bytes should be the SCALE-encoded representation of a +/// `beefy_primitives::VersionedFinalityProof`. +#[derive(Clone, Serialize, Deserialize)] +pub struct EncodedVersionedFinalityProof(sp_core::Bytes); + +impl EncodedVersionedFinalityProof { + pub fn new( + finality_proof: beefy_gadget::justification::BeefyVersionedFinalityProof, + ) -> Self + where + Block: BlockT, + { + EncodedVersionedFinalityProof(finality_proof.encode().into()) + } +} diff --git a/client/beefy/src/aux_schema.rs b/client/beefy/src/aux_schema.rs new file mode 100644 index 0000000000000..9d6a4292f32d4 --- /dev/null +++ b/client/beefy/src/aux_schema.rs @@ -0,0 +1,107 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Schema for BEEFY state persisted in the aux-db. + +use crate::worker::PersistedState; +use codec::{Decode, Encode}; +use log::{info, trace}; +use sc_client_api::{backend::AuxStore, Backend}; +use sp_blockchain::{Error as ClientError, Result as ClientResult}; +use sp_runtime::traits::Block as BlockT; + +const VERSION_KEY: &[u8] = b"beefy_auxschema_version"; +const WORKER_STATE: &[u8] = b"beefy_voter_state"; + +const CURRENT_VERSION: u32 = 1; + +pub(crate) fn write_current_version(backend: &B) -> ClientResult<()> { + info!(target: "beefy", "🥩 write aux schema version {:?}", CURRENT_VERSION); + AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[]) +} + +/// Write voter state. +pub(crate) fn write_voter_state( + backend: &B, + state: &PersistedState, +) -> ClientResult<()> { + trace!(target: "beefy", "🥩 persisting {:?}", state); + backend.insert_aux(&[(WORKER_STATE, state.encode().as_slice())], &[]) +} + +fn load_decode(backend: &B, key: &[u8]) -> ClientResult> { + match backend.get_aux(key)? { + None => Ok(None), + Some(t) => T::decode(&mut &t[..]) + .map_err(|e| ClientError::Backend(format!("BEEFY DB is corrupted: {}", e))) + .map(Some), + } +} + +/// Load or initialize persistent data from backend. +pub(crate) fn load_persistent(backend: &BE) -> ClientResult>> +where + B: BlockT, + BE: Backend, +{ + let version: Option = load_decode(backend, VERSION_KEY)?; + + match version { + None => (), + Some(1) => return load_decode::<_, PersistedState>(backend, WORKER_STATE), + other => + return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))), + } + + // No persistent state found in DB. + Ok(None) +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::tests::BeefyTestNet; + use sc_network_test::TestNetFactory; + use tokio::runtime::Runtime; + + // also used in tests.rs + pub fn verify_persisted_version>(backend: &BE) -> bool { + let version: u32 = load_decode(backend, VERSION_KEY).unwrap().unwrap(); + version == CURRENT_VERSION + } + + #[test] + fn should_load_persistent_sanity_checks() { + let runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let backend = net.peer(0).client().as_backend(); + + // version not available in db -> None + assert_eq!(load_persistent(&*backend).unwrap(), None); + + // populate version in db + write_current_version(&*backend).unwrap(); + // verify correct version is retrieved + assert_eq!(load_decode(&*backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); + + // version is available in db but state isn't -> None + assert_eq!(load_persistent(&*backend).unwrap(), None); + + // full `PersistedState` load is tested in `tests.rs`. + } +} diff --git a/client/beefy/src/communication/gossip.rs b/client/beefy/src/communication/gossip.rs new file mode 100644 index 0000000000000..bbc35ac8e526e --- /dev/null +++ b/client/beefy/src/communication/gossip.rs @@ -0,0 +1,470 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use std::{collections::BTreeMap, sync::Arc, time::Duration}; + +use sc_network::PeerId; +use sc_network_gossip::{MessageIntent, ValidationResult, Validator, ValidatorContext}; +use sp_core::hashing::twox_64; +use sp_runtime::traits::{Block, Hash, Header, NumberFor}; + +use codec::{Decode, Encode}; +use log::{debug, trace}; +use parking_lot::{Mutex, RwLock}; +use wasm_timer::Instant; + +use crate::{communication::peers::KnownPeers, keystore::BeefyKeystore}; +use beefy_primitives::{ + crypto::{Public, Signature}, + VoteMessage, +}; + +// Timeout for rebroadcasting messages. +const REBROADCAST_AFTER: Duration = Duration::from_secs(60 * 5); + +/// Gossip engine messages topic +pub(crate) fn topic() -> B::Hash +where + B: Block, +{ + <::Hashing as Hash>::hash(b"beefy") +} + +/// A type that represents hash of the message. +pub type MessageHash = [u8; 8]; + +struct KnownVotes { + last_done: Option>, + live: BTreeMap, fnv::FnvHashSet>, +} + +impl KnownVotes { + pub fn new() -> Self { + Self { last_done: None, live: BTreeMap::new() } + } + + /// Create new round votes set if not already present. + fn insert(&mut self, round: NumberFor) { + self.live.entry(round).or_default(); + } + + /// Remove `round` and older from live set, update `last_done` accordingly. + fn conclude(&mut self, round: NumberFor) { + self.live.retain(|&number, _| number > round); + self.last_done = self.last_done.max(Some(round)); + } + + /// Return true if `round` is newer than previously concluded rounds. + /// + /// Latest concluded round is still considered alive to allow proper gossiping for it. + fn is_live(&self, round: &NumberFor) -> bool { + Some(*round) >= self.last_done + } + + /// Add new _known_ `hash` to the round's known votes. + fn add_known(&mut self, round: &NumberFor, hash: MessageHash) { + self.live.get_mut(round).map(|known| known.insert(hash)); + } + + /// Check if `hash` is already part of round's known votes. + fn is_known(&self, round: &NumberFor, hash: &MessageHash) -> bool { + self.live.get(round).map(|known| known.contains(hash)).unwrap_or(false) + } +} + +/// BEEFY gossip validator +/// +/// Validate BEEFY gossip messages and limit the number of live BEEFY voting rounds. +/// +/// Allows messages for 'rounds >= last concluded' to flow, everything else gets +/// rejected/expired. +/// +///All messaging is handled in a single BEEFY global topic. +pub(crate) struct GossipValidator +where + B: Block, +{ + topic: B::Hash, + known_votes: RwLock>, + next_rebroadcast: Mutex, + known_peers: Arc>>, +} + +impl GossipValidator +where + B: Block, +{ + pub fn new(known_peers: Arc>>) -> GossipValidator { + GossipValidator { + topic: topic::(), + known_votes: RwLock::new(KnownVotes::new()), + next_rebroadcast: Mutex::new(Instant::now() + REBROADCAST_AFTER), + known_peers, + } + } + + /// Note a voting round. + /// + /// Noting round will start a live `round`. + pub(crate) fn note_round(&self, round: NumberFor) { + debug!(target: "beefy", "🥩 About to note gossip round #{}", round); + self.known_votes.write().insert(round); + } + + /// Conclude a voting round. + /// + /// This can be called once round is complete so we stop gossiping for it. + pub(crate) fn conclude_round(&self, round: NumberFor) { + debug!(target: "beefy", "🥩 About to drop gossip round #{}", round); + self.known_votes.write().conclude(round); + } +} + +impl Validator for GossipValidator +where + B: Block, +{ + fn peer_disconnected(&self, _context: &mut dyn ValidatorContext, who: &PeerId) { + self.known_peers.lock().remove(who); + } + + fn validate( + &self, + _context: &mut dyn ValidatorContext, + sender: &PeerId, + mut data: &[u8], + ) -> ValidationResult { + if let Ok(msg) = VoteMessage::, Public, Signature>::decode(&mut data) { + let msg_hash = twox_64(data); + let round = msg.commitment.block_number; + + // Verify general usefulness of the message. + // We are going to discard old votes right away (without verification) + // Also we keep track of already received votes to avoid verifying duplicates. + { + let known_votes = self.known_votes.read(); + + if !known_votes.is_live(&round) { + return ValidationResult::Discard + } + + if known_votes.is_known(&round, &msg_hash) { + return ValidationResult::ProcessAndKeep(self.topic) + } + } + + if BeefyKeystore::verify(&msg.id, &msg.signature, &msg.commitment.encode()) { + self.known_votes.write().add_known(&round, msg_hash); + self.known_peers.lock().note_vote_for(*sender, round); + return ValidationResult::ProcessAndKeep(self.topic) + } else { + // TODO: report peer + debug!(target: "beefy", "🥩 Bad signature on message: {:?}, from: {:?}", msg, sender); + } + } + + ValidationResult::Discard + } + + fn message_expired<'a>(&'a self) -> Box bool + 'a> { + let known_votes = self.known_votes.read(); + Box::new(move |_topic, mut data| { + let msg = match VoteMessage::, Public, Signature>::decode(&mut data) { + Ok(vote) => vote, + Err(_) => return true, + }; + + let round = msg.commitment.block_number; + let expired = !known_votes.is_live(&round); + + trace!(target: "beefy", "🥩 Message for round #{} expired: {}", round, expired); + + expired + }) + } + + fn message_allowed<'a>( + &'a self, + ) -> Box bool + 'a> { + let do_rebroadcast = { + let now = Instant::now(); + let mut next_rebroadcast = self.next_rebroadcast.lock(); + if now >= *next_rebroadcast { + *next_rebroadcast = now + REBROADCAST_AFTER; + true + } else { + false + } + }; + + let known_votes = self.known_votes.read(); + Box::new(move |_who, intent, _topic, mut data| { + if let MessageIntent::PeriodicRebroadcast = intent { + return do_rebroadcast + } + + let msg = match VoteMessage::, Public, Signature>::decode(&mut data) { + Ok(vote) => vote, + Err(_) => return false, + }; + + let round = msg.commitment.block_number; + let allowed = known_votes.is_live(&round); + + trace!(target: "beefy", "🥩 Message for round #{} allowed: {}", round, allowed); + + allowed + }) + } +} + +#[cfg(test)] +mod tests { + use sc_keystore::LocalKeystore; + use sc_network_test::Block; + use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; + + use crate::keystore::{tests::Keyring, BeefyKeystore}; + use beefy_primitives::{ + crypto::Signature, known_payloads, Commitment, MmrRootHash, Payload, VoteMessage, KEY_TYPE, + }; + + use super::*; + + #[test] + fn known_votes_insert_remove() { + let mut kv = KnownVotes::::new(); + + kv.insert(1); + kv.insert(1); + kv.insert(2); + assert_eq!(kv.live.len(), 2); + + let mut kv = KnownVotes::::new(); + kv.insert(1); + kv.insert(2); + kv.insert(3); + + assert!(kv.last_done.is_none()); + kv.conclude(2); + assert_eq!(kv.live.len(), 1); + assert!(!kv.live.contains_key(&2)); + assert_eq!(kv.last_done, Some(2)); + + kv.conclude(1); + assert_eq!(kv.last_done, Some(2)); + + kv.conclude(3); + assert_eq!(kv.last_done, Some(3)); + assert!(kv.live.is_empty()); + } + + #[test] + fn note_and_drop_round_works() { + let gv = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); + + gv.note_round(1u64); + + assert!(gv.known_votes.read().is_live(&1u64)); + + gv.note_round(3u64); + gv.note_round(7u64); + gv.note_round(10u64); + + assert_eq!(gv.known_votes.read().live.len(), 4); + + gv.conclude_round(7u64); + + let votes = gv.known_votes.read(); + + // rounds 1 and 3 are outdated, don't gossip anymore + assert!(!votes.is_live(&1u64)); + assert!(!votes.is_live(&3u64)); + // latest concluded round is still gossiped + assert!(votes.is_live(&7u64)); + // round 10 is alive and in-progress + assert!(votes.is_live(&10u64)); + } + + #[test] + fn note_same_round_twice() { + let gv = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); + + gv.note_round(3u64); + gv.note_round(7u64); + gv.note_round(10u64); + + assert_eq!(gv.known_votes.read().live.len(), 3); + + // note round #7 again -> should not change anything + gv.note_round(7u64); + + let votes = gv.known_votes.read(); + + assert_eq!(votes.live.len(), 3); + + assert!(votes.is_live(&3u64)); + assert!(votes.is_live(&7u64)); + assert!(votes.is_live(&10u64)); + } + + struct TestContext; + impl ValidatorContext for TestContext { + fn broadcast_topic(&mut self, _topic: B::Hash, _force: bool) { + todo!() + } + + fn broadcast_message(&mut self, _topic: B::Hash, _message: Vec, _force: bool) { + todo!() + } + + fn send_message(&mut self, _who: &sc_network::PeerId, _message: Vec) { + todo!() + } + + fn send_topic(&mut self, _who: &sc_network::PeerId, _topic: B::Hash, _force: bool) { + todo!() + } + } + + fn sign_commitment(who: &Keyring, commitment: &Commitment) -> Signature { + let store: SyncCryptoStorePtr = std::sync::Arc::new(LocalKeystore::in_memory()); + SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&who.to_seed())).unwrap(); + let beefy_keystore: BeefyKeystore = Some(store).into(); + + beefy_keystore.sign(&who.public(), &commitment.encode()).unwrap() + } + + fn dummy_vote(block_number: u64) -> VoteMessage { + let payload = Payload::from_single_entry( + known_payloads::MMR_ROOT_ID, + MmrRootHash::default().encode(), + ); + let commitment = Commitment { payload, block_number, validator_set_id: 0 }; + let signature = sign_commitment(&Keyring::Alice, &commitment); + + VoteMessage { commitment, id: Keyring::Alice.public(), signature } + } + + #[test] + fn should_avoid_verifying_signatures_twice() { + let gv = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); + let sender = sc_network::PeerId::random(); + let mut context = TestContext; + + let vote = dummy_vote(3); + + gv.note_round(3u64); + gv.note_round(7u64); + gv.note_round(10u64); + + // first time the cache should be populated + let res = gv.validate(&mut context, &sender, &vote.encode()); + + assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); + assert_eq!( + gv.known_votes.read().live.get(&vote.commitment.block_number).map(|x| x.len()), + Some(1) + ); + + // second time we should hit the cache + let res = gv.validate(&mut context, &sender, &vote.encode()); + + assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); + + // next we should quickly reject if the round is not live + gv.conclude_round(7_u64); + + assert!(!gv.known_votes.read().is_live(&vote.commitment.block_number)); + + let res = gv.validate(&mut context, &sender, &vote.encode()); + + assert!(matches!(res, ValidationResult::Discard)); + } + + #[test] + fn messages_allowed_and_expired() { + let gv = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); + let sender = sc_network::PeerId::random(); + let topic = Default::default(); + let intent = MessageIntent::Broadcast; + + // note round 2 and 3, then conclude 2 + gv.note_round(2u64); + gv.note_round(3u64); + gv.conclude_round(2u64); + let mut allowed = gv.message_allowed(); + let mut expired = gv.message_expired(); + + // check bad vote format + assert!(!allowed(&sender, intent, &topic, &mut [0u8; 16])); + assert!(expired(topic, &mut [0u8; 16])); + + // inactive round 1 -> expired + let vote = dummy_vote(1); + let mut encoded_vote = vote.encode(); + assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(expired(topic, &mut encoded_vote)); + + // active round 2 -> !expired - concluded but still gossiped + let vote = dummy_vote(2); + let mut encoded_vote = vote.encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(!expired(topic, &mut encoded_vote)); + + // in progress round 3 -> !expired + let vote = dummy_vote(3); + let mut encoded_vote = vote.encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(!expired(topic, &mut encoded_vote)); + + // unseen round 4 -> !expired + let vote = dummy_vote(3); + let mut encoded_vote = vote.encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(!expired(topic, &mut encoded_vote)); + } + + #[test] + fn messages_rebroadcast() { + let gv = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); + let sender = sc_network::PeerId::random(); + let topic = Default::default(); + + let vote = dummy_vote(1); + let mut encoded_vote = vote.encode(); + + // re-broadcasting only allowed at `REBROADCAST_AFTER` intervals + let intent = MessageIntent::PeriodicRebroadcast; + let mut allowed = gv.message_allowed(); + + // rebroadcast not allowed so soon after GossipValidator creation + assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); + + // hack the inner deadline to be `now` + *gv.next_rebroadcast.lock() = Instant::now(); + + // still not allowed on old `allowed` closure result + assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); + + // renew closure result + let mut allowed = gv.message_allowed(); + // rebroadcast should be allowed now + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + } +} diff --git a/client/beefy/src/communication/mod.rs b/client/beefy/src/communication/mod.rs new file mode 100644 index 0000000000000..91798d4ae0d33 --- /dev/null +++ b/client/beefy/src/communication/mod.rs @@ -0,0 +1,113 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Communication streams for the BEEFY networking protocols. + +pub mod notification; +pub mod request_response; + +pub(crate) mod gossip; +pub(crate) mod peers; + +pub(crate) mod beefy_protocol_name { + use array_bytes::bytes2hex; + use sc_network::ProtocolName; + + /// BEEFY votes gossip protocol name suffix. + const GOSSIP_NAME: &str = "/beefy/1"; + /// BEEFY justifications protocol name suffix. + const JUSTIFICATIONS_NAME: &str = "/beefy/justifications/1"; + + /// Name of the votes gossip protocol used by BEEFY. + /// + /// Must be registered towards the networking in order for BEEFY voter to properly function. + pub fn gossip_protocol_name>( + genesis_hash: Hash, + fork_id: Option<&str>, + ) -> ProtocolName { + let genesis_hash = genesis_hash.as_ref(); + if let Some(fork_id) = fork_id { + format!("/{}/{}{}", bytes2hex("", genesis_hash), fork_id, GOSSIP_NAME).into() + } else { + format!("/{}{}", bytes2hex("", genesis_hash), GOSSIP_NAME).into() + } + } + + /// Name of the BEEFY justifications request-response protocol. + pub fn justifications_protocol_name>( + genesis_hash: Hash, + fork_id: Option<&str>, + ) -> ProtocolName { + let genesis_hash = genesis_hash.as_ref(); + if let Some(fork_id) = fork_id { + format!("/{}/{}{}", bytes2hex("", genesis_hash), fork_id, JUSTIFICATIONS_NAME).into() + } else { + format!("/{}{}", bytes2hex("", genesis_hash), JUSTIFICATIONS_NAME).into() + } + } +} + +/// Returns the configuration value to put in +/// [`sc_network::config::NetworkConfiguration::extra_sets`]. +/// For standard protocol name see [`beefy_protocol_name::gossip_protocol_name`]. +pub fn beefy_peers_set_config( + gossip_protocol_name: sc_network::ProtocolName, +) -> sc_network_common::config::NonDefaultSetConfig { + let mut cfg = + sc_network_common::config::NonDefaultSetConfig::new(gossip_protocol_name, 1024 * 1024); + cfg.allow_non_reserved(25, 25); + cfg +} + +#[cfg(test)] +mod tests { + use super::*; + + use sp_core::H256; + + #[test] + fn beefy_protocols_names() { + use beefy_protocol_name::{gossip_protocol_name, justifications_protocol_name}; + // Create protocol name using random genesis hash. + let genesis_hash = H256::random(); + let genesis_hex = array_bytes::bytes2hex("", genesis_hash.as_ref()); + + let expected_gossip_name = format!("/{}/beefy/1", genesis_hex); + let gossip_proto_name = gossip_protocol_name(&genesis_hash, None); + assert_eq!(gossip_proto_name.to_string(), expected_gossip_name); + + let expected_justif_name = format!("/{}/beefy/justifications/1", genesis_hex); + let justif_proto_name = justifications_protocol_name(&genesis_hash, None); + assert_eq!(justif_proto_name.to_string(), expected_justif_name); + + // Create protocol name using hardcoded genesis hash. Verify exact representation. + let genesis_hash = [ + 50, 4, 60, 123, 58, 106, 216, 246, 194, 188, 139, 193, 33, 212, 202, 171, 9, 55, 123, + 94, 8, 43, 12, 251, 187, 57, 173, 19, 188, 74, 205, 147, + ]; + let genesis_hex = "32043c7b3a6ad8f6c2bc8bc121d4caab09377b5e082b0cfbbb39ad13bc4acd93"; + + let expected_gossip_name = format!("/{}/beefy/1", genesis_hex); + let gossip_proto_name = gossip_protocol_name(&genesis_hash, None); + assert_eq!(gossip_proto_name.to_string(), expected_gossip_name); + + let expected_justif_name = format!("/{}/beefy/justifications/1", genesis_hex); + let justif_proto_name = justifications_protocol_name(&genesis_hash, None); + assert_eq!(justif_proto_name.to_string(), expected_justif_name); + } +} diff --git a/client/beefy/src/communication/notification.rs b/client/beefy/src/communication/notification.rs new file mode 100644 index 0000000000000..c673115e487f3 --- /dev/null +++ b/client/beefy/src/communication/notification.rs @@ -0,0 +1,55 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use sc_utils::notification::{NotificationSender, NotificationStream, TracingKeyStr}; +use sp_runtime::traits::Block as BlockT; + +use crate::justification::BeefyVersionedFinalityProof; + +/// The sending half of the notifications channel(s) used to send +/// notifications about best BEEFY block from the gadget side. +pub type BeefyBestBlockSender = NotificationSender<::Hash>; + +/// The receiving half of a notifications channel used to receive +/// notifications about best BEEFY blocks determined on the gadget side. +pub type BeefyBestBlockStream = + NotificationStream<::Hash, BeefyBestBlockTracingKey>; + +/// The sending half of the notifications channel(s) used to send notifications +/// about versioned finality proof generated at the end of a BEEFY round. +pub type BeefyVersionedFinalityProofSender = + NotificationSender>; + +/// The receiving half of a notifications channel used to receive notifications +/// about versioned finality proof generated at the end of a BEEFY round. +pub type BeefyVersionedFinalityProofStream = + NotificationStream, BeefyVersionedFinalityProofTracingKey>; + +/// Provides tracing key for BEEFY best block stream. +#[derive(Clone)] +pub struct BeefyBestBlockTracingKey; +impl TracingKeyStr for BeefyBestBlockTracingKey { + const TRACING_KEY: &'static str = "mpsc_beefy_best_block_notification_stream"; +} + +/// Provides tracing key for BEEFY versioned finality proof stream. +#[derive(Clone)] +pub struct BeefyVersionedFinalityProofTracingKey; +impl TracingKeyStr for BeefyVersionedFinalityProofTracingKey { + const TRACING_KEY: &'static str = "mpsc_beefy_versioned_finality_proof_notification_stream"; +} diff --git a/client/beefy/src/communication/peers.rs b/client/beefy/src/communication/peers.rs new file mode 100644 index 0000000000000..d7927ea72e661 --- /dev/null +++ b/client/beefy/src/communication/peers.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Logic for keeping track of BEEFY peers. + +// TODO (issue #12296): replace this naive peer tracking with generic one that infers data +// from multiple network protocols. + +use sc_network::PeerId; +use sp_runtime::traits::{Block, NumberFor, Zero}; +use std::collections::{HashMap, VecDeque}; + +struct PeerData { + last_voted_on: NumberFor, +} + +impl Default for PeerData { + fn default() -> Self { + PeerData { last_voted_on: Zero::zero() } + } +} + +/// Keep a simple map of connected peers +/// and the most recent voting round they participated in. +pub struct KnownPeers { + live: HashMap>, +} + +impl KnownPeers { + pub fn new() -> Self { + Self { live: HashMap::new() } + } + + /// Note vote round number for `peer`. + pub fn note_vote_for(&mut self, peer: PeerId, round: NumberFor) { + let data = self.live.entry(peer).or_default(); + data.last_voted_on = round.max(data.last_voted_on); + } + + /// Remove connected `peer`. + pub fn remove(&mut self, peer: &PeerId) { + self.live.remove(peer); + } + + /// Return _filtered and cloned_ list of peers that have voted on `block` or higher. + pub fn at_least_at_block(&self, block: NumberFor) -> VecDeque { + self.live + .iter() + .filter_map(|(k, v)| (v.last_voted_on >= block).then_some(k)) + .cloned() + .collect() + } + + /// Answer whether `peer` is part of `KnownPeers` set. + pub fn contains(&self, peer: &PeerId) -> bool { + self.live.contains_key(peer) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_track_known_peers_progress() { + let (alice, bob, charlie) = (PeerId::random(), PeerId::random(), PeerId::random()); + let mut peers = KnownPeers::::new(); + assert!(peers.live.is_empty()); + + // 'Tracked' Bob seen voting for 5. + peers.note_vote_for(bob, 5); + // Previously unseen Charlie now seen voting for 10. + peers.note_vote_for(charlie, 10); + + assert_eq!(peers.live.len(), 2); + assert!(!peers.contains(&alice)); + assert!(peers.contains(&bob)); + assert!(peers.contains(&charlie)); + + // Get peers at block >= 5 + let at_5 = peers.at_least_at_block(5); + // Should be Bob and Charlie + assert_eq!(at_5.len(), 2); + assert!(at_5.contains(&bob)); + assert!(at_5.contains(&charlie)); + + // 'Tracked' Alice seen voting for 10. + peers.note_vote_for(alice, 10); + + // Get peers at block >= 9 + let at_9 = peers.at_least_at_block(9); + // Should be Charlie and Alice + assert_eq!(at_9.len(), 2); + assert!(at_9.contains(&charlie)); + assert!(at_9.contains(&alice)); + + // Remove Alice + peers.remove(&alice); + assert_eq!(peers.live.len(), 2); + assert!(!peers.contains(&alice)); + + // Get peers at block >= 9 + let at_9 = peers.at_least_at_block(9); + // Now should be just Charlie + assert_eq!(at_9.len(), 1); + assert!(at_9.contains(&charlie)); + } +} diff --git a/client/beefy/src/communication/request_response/incoming_requests_handler.rs b/client/beefy/src/communication/request_response/incoming_requests_handler.rs new file mode 100644 index 0000000000000..9f02b7162b54c --- /dev/null +++ b/client/beefy/src/communication/request_response/incoming_requests_handler.rs @@ -0,0 +1,198 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Helper for handling (i.e. answering) BEEFY justifications requests from a remote peer. + +use beefy_primitives::BEEFY_ENGINE_ID; +use codec::Decode; +use futures::{ + channel::{mpsc, oneshot}, + StreamExt, +}; +use log::{debug, trace}; +use sc_client_api::BlockBackend; +use sc_network::{config as netconfig, config::RequestResponseConfig, PeerId, ReputationChange}; +use sc_network_common::protocol::ProtocolName; +use sp_runtime::traits::Block; +use std::{marker::PhantomData, sync::Arc}; + +use crate::communication::request_response::{ + on_demand_justifications_protocol_config, Error, JustificationRequest, +}; + +/// A request coming in, including a sender for sending responses. +#[derive(Debug)] +pub(crate) struct IncomingRequest { + /// `PeerId` of sending peer. + pub peer: PeerId, + /// The sent request. + pub payload: JustificationRequest, + /// Sender for sending response back. + pub pending_response: oneshot::Sender, +} + +impl IncomingRequest { + /// Create new `IncomingRequest`. + pub fn new( + peer: PeerId, + payload: JustificationRequest, + pending_response: oneshot::Sender, + ) -> Self { + Self { peer, payload, pending_response } + } + + /// Try building from raw network request. + /// + /// This function will fail if the request cannot be decoded and will apply passed in + /// reputation changes in that case. + /// + /// Params: + /// - The raw request to decode + /// - Reputation changes to apply for the peer in case decoding fails. + pub fn try_from_raw( + raw: netconfig::IncomingRequest, + reputation_changes: Vec, + ) -> Result { + let netconfig::IncomingRequest { payload, peer, pending_response } = raw; + let payload = match JustificationRequest::decode(&mut payload.as_ref()) { + Ok(payload) => payload, + Err(err) => { + let response = netconfig::OutgoingResponse { + result: Err(()), + reputation_changes, + sent_feedback: None, + }; + if let Err(_) = pending_response.send(response) { + return Err(Error::DecodingErrorNoReputationChange(peer, err)) + } + return Err(Error::DecodingError(peer, err)) + }, + }; + Ok(Self::new(peer, payload, pending_response)) + } +} + +/// Receiver for incoming BEEFY justifications requests. +/// +/// Takes care of decoding and handling of invalid encoded requests. +pub(crate) struct IncomingRequestReceiver { + raw: mpsc::Receiver, +} + +impl IncomingRequestReceiver { + pub fn new(inner: mpsc::Receiver) -> Self { + Self { raw: inner } + } + + /// Try to receive the next incoming request. + /// + /// Any received request will be decoded, on decoding errors the provided reputation changes + /// will be applied and an error will be reported. + pub async fn recv(&mut self, reputation_changes: F) -> Result, Error> + where + B: Block, + F: FnOnce() -> Vec, + { + let req = match self.raw.next().await { + None => return Err(Error::RequestChannelExhausted), + Some(raw) => IncomingRequest::::try_from_raw(raw, reputation_changes())?, + }; + Ok(req) + } +} + +/// Handler for incoming BEEFY justifications requests from a remote peer. +pub struct BeefyJustifsRequestHandler { + pub(crate) request_receiver: IncomingRequestReceiver, + pub(crate) justif_protocol_name: ProtocolName, + pub(crate) client: Arc, + pub(crate) _block: PhantomData, +} + +impl BeefyJustifsRequestHandler +where + B: Block, + Client: BlockBackend + Send + Sync, +{ + /// Create a new [`BeefyJustifsRequestHandler`]. + pub fn new>( + genesis_hash: Hash, + fork_id: Option<&str>, + client: Arc, + ) -> (Self, RequestResponseConfig) { + let (request_receiver, config) = + on_demand_justifications_protocol_config(genesis_hash, fork_id); + let justif_protocol_name = config.name.clone(); + + (Self { request_receiver, justif_protocol_name, client, _block: PhantomData }, config) + } + + /// Network request-response protocol name used by this handler. + pub fn protocol_name(&self) -> ProtocolName { + self.justif_protocol_name.clone() + } + + // Sends back justification response if justification found in client backend. + fn handle_request(&self, request: IncomingRequest) -> Result<(), Error> { + // TODO (issue #12293): validate `request` and change peer reputation for invalid requests. + + let maybe_encoded_proof = if let Some(hash) = + self.client.block_hash(request.payload.begin).map_err(Error::Client)? + { + self.client + .justifications(hash) + .map_err(Error::Client)? + .and_then(|justifs| justifs.get(BEEFY_ENGINE_ID).cloned()) + // No BEEFY justification present. + .ok_or(()) + } else { + Err(()) + }; + + request + .pending_response + .send(netconfig::OutgoingResponse { + result: maybe_encoded_proof, + reputation_changes: Vec::new(), + sent_feedback: None, + }) + .map_err(|_| Error::SendResponse) + } + + /// Run [`BeefyJustifsRequestHandler`]. + pub async fn run(mut self) { + trace!(target: "beefy::sync", "🥩 Running BeefyJustifsRequestHandler"); + + while let Ok(request) = self.request_receiver.recv(|| vec![]).await { + let peer = request.peer; + match self.handle_request(request) { + Ok(()) => { + debug!( + target: "beefy::sync", + "🥩 Handled BEEFY justification request from {:?}.", peer + ) + }, + Err(e) => { + // TODO (issue #12293): apply reputation changes here based on error type. + debug!( + target: "beefy::sync", + "🥩 Failed to handle BEEFY justification request from {:?}: {}", peer, e, + ) + }, + } + } + } +} diff --git a/client/beefy/src/communication/request_response/mod.rs b/client/beefy/src/communication/request_response/mod.rs new file mode 100644 index 0000000000000..c83bb9d57e91b --- /dev/null +++ b/client/beefy/src/communication/request_response/mod.rs @@ -0,0 +1,101 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Request/response protocol for syncing BEEFY justifications. + +mod incoming_requests_handler; +pub(crate) mod outgoing_requests_engine; + +pub use incoming_requests_handler::BeefyJustifsRequestHandler; + +use futures::channel::mpsc; +use std::time::Duration; + +use codec::{Decode, Encode, Error as CodecError}; +use sc_network::{config::RequestResponseConfig, PeerId}; +use sp_runtime::traits::{Block, NumberFor}; + +use crate::communication::beefy_protocol_name::justifications_protocol_name; +use incoming_requests_handler::IncomingRequestReceiver; + +// 10 seems reasonable, considering justifs are explicitly requested only +// for mandatory blocks, by nodes that are syncing/catching-up. +const JUSTIF_CHANNEL_SIZE: usize = 10; + +const MAX_RESPONSE_SIZE: u64 = 1024 * 1024; +const JUSTIF_REQUEST_TIMEOUT: Duration = Duration::from_secs(3); + +/// Get the configuration for the BEEFY justifications Request/response protocol. +/// +/// Returns a receiver for messages received on this protocol and the requested +/// `ProtocolConfig`. +/// +/// Consider using [`BeefyJustifsRequestHandler`] instead of this low-level function. +pub(crate) fn on_demand_justifications_protocol_config>( + genesis_hash: Hash, + fork_id: Option<&str>, +) -> (IncomingRequestReceiver, RequestResponseConfig) { + let name = justifications_protocol_name(genesis_hash, fork_id); + let fallback_names = vec![]; + let (tx, rx) = mpsc::channel(JUSTIF_CHANNEL_SIZE); + let rx = IncomingRequestReceiver::new(rx); + let cfg = RequestResponseConfig { + name, + fallback_names, + max_request_size: 32, + max_response_size: MAX_RESPONSE_SIZE, + // We are connected to all validators: + request_timeout: JUSTIF_REQUEST_TIMEOUT, + inbound_queue: Some(tx), + }; + (rx, cfg) +} + +/// BEEFY justification request. +#[derive(Debug, Clone, Encode, Decode)] +pub struct JustificationRequest { + /// Start collecting proofs from this block. + pub begin: NumberFor, +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Client(#[from] sp_blockchain::Error), + + #[error(transparent)] + RuntimeApi(#[from] sp_api::ApiError), + + /// Decoding failed, we were able to change the peer's reputation accordingly. + #[error("Decoding request failed for peer {0}.")] + DecodingError(PeerId, #[source] CodecError), + + /// Decoding failed, but sending reputation change failed. + #[error("Decoding request failed for peer {0}, and changing reputation failed.")] + DecodingErrorNoReputationChange(PeerId, #[source] CodecError), + + /// Incoming request stream exhausted. Should only happen on shutdown. + #[error("Incoming request channel got closed.")] + RequestChannelExhausted, + + #[error("Failed to send response.")] + SendResponse, + + #[error("Received invalid response.")] + InvalidResponse, +} diff --git a/client/beefy/src/communication/request_response/outgoing_requests_engine.rs b/client/beefy/src/communication/request_response/outgoing_requests_engine.rs new file mode 100644 index 0000000000000..00ee7610dd4f0 --- /dev/null +++ b/client/beefy/src/communication/request_response/outgoing_requests_engine.rs @@ -0,0 +1,229 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Generating request logic for request/response protocol for syncing BEEFY justifications. + +use beefy_primitives::{crypto::AuthorityId, ValidatorSet}; +use codec::Encode; +use futures::channel::{oneshot, oneshot::Canceled}; +use log::{debug, warn}; +use parking_lot::Mutex; +use sc_network::{PeerId, ProtocolName}; +use sc_network_common::{ + request_responses::{IfDisconnected, RequestFailure}, + service::NetworkRequest, +}; +use sp_runtime::traits::{Block, NumberFor}; +use std::{collections::VecDeque, result::Result, sync::Arc}; + +use crate::{ + communication::request_response::{Error, JustificationRequest}, + justification::{decode_and_verify_finality_proof, BeefyVersionedFinalityProof}, + KnownPeers, +}; + +/// Response type received from network. +type Response = Result, RequestFailure>; +/// Used to receive a response from the network. +type ResponseReceiver = oneshot::Receiver; + +#[derive(Clone, Debug)] +struct RequestInfo { + block: NumberFor, + active_set: ValidatorSet, +} + +enum State { + Idle, + AwaitingResponse(PeerId, RequestInfo, ResponseReceiver), +} + +pub struct OnDemandJustificationsEngine { + network: Arc, + protocol_name: ProtocolName, + + live_peers: Arc>>, + peers_cache: VecDeque, + + state: State, +} + +impl OnDemandJustificationsEngine { + pub fn new( + network: Arc, + protocol_name: ProtocolName, + live_peers: Arc>>, + ) -> Self { + Self { + network, + protocol_name, + live_peers, + peers_cache: VecDeque::new(), + state: State::Idle, + } + } + + fn reset_peers_cache_for_block(&mut self, block: NumberFor) { + // TODO (issue #12296): replace peer selection with generic one that involves all protocols. + self.peers_cache = self.live_peers.lock().at_least_at_block(block); + } + + fn try_next_peer(&mut self) -> Option { + // TODO (issue #12296): replace peer selection with generic one that involves all protocols. + let live = self.live_peers.lock(); + while let Some(peer) = self.peers_cache.pop_front() { + if live.contains(&peer) { + return Some(peer) + } + } + None + } + + fn request_from_peer(&mut self, peer: PeerId, req_info: RequestInfo) { + debug!( + target: "beefy::sync", + "🥩 requesting justif #{:?} from peer {:?}", + req_info.block, + peer, + ); + + let payload = JustificationRequest:: { begin: req_info.block }.encode(); + + let (tx, rx) = oneshot::channel(); + + self.network.start_request( + peer, + self.protocol_name.clone(), + payload, + tx, + IfDisconnected::ImmediateError, + ); + + self.state = State::AwaitingResponse(peer, req_info, rx); + } + + /// Start new justification request for `block`, if no other request is in progress. + /// + /// `active_set` will be used to verify validity of potential responses. + pub fn request(&mut self, block: NumberFor, active_set: ValidatorSet) { + // ignore new requests while there's already one pending + if matches!(self.state, State::AwaitingResponse(_, _, _)) { + return + } + self.reset_peers_cache_for_block(block); + + // Start the requests engine - each unsuccessful received response will automatically + // trigger a new request to the next peer in the `peers_cache` until there are none left. + if let Some(peer) = self.try_next_peer() { + self.request_from_peer(peer, RequestInfo { block, active_set }); + } else { + debug!(target: "beefy::sync", "🥩 no good peers to request justif #{:?} from", block); + } + } + + /// Cancel any pending request for block numbers smaller or equal to `block`. + pub fn cancel_requests_older_than(&mut self, block: NumberFor) { + match &self.state { + State::AwaitingResponse(_, req_info, _) if req_info.block <= block => { + debug!( + target: "beefy::sync", "🥩 cancel pending request for justification #{:?}", + req_info.block + ); + self.state = State::Idle; + }, + _ => (), + } + } + + fn process_response( + &mut self, + peer: PeerId, + req_info: &RequestInfo, + response: Result, + ) -> Result, Error> { + response + .map_err(|e| { + debug!( + target: "beefy::sync", + "🥩 for on demand justification #{:?}, peer {:?} hung up: {:?}", + req_info.block, peer, e + ); + Error::InvalidResponse + })? + .map_err(|e| { + debug!( + target: "beefy::sync", + "🥩 for on demand justification #{:?}, peer {:?} error: {:?}", + req_info.block, peer, e + ); + Error::InvalidResponse + }) + .and_then(|encoded| { + decode_and_verify_finality_proof::( + &encoded[..], + req_info.block, + &req_info.active_set, + ) + .map_err(|e| { + debug!( + target: "beefy::sync", + "🥩 for on demand justification #{:?}, peer {:?} responded with invalid proof: {:?}", + req_info.block, peer, e + ); + Error::InvalidResponse + }) + }) + } + + pub async fn next(&mut self) -> Option> { + let (peer, req_info, resp) = match &mut self.state { + State::Idle => { + futures::pending!(); + // Doesn't happen as 'futures::pending!()' is an 'await' barrier that never passes. + return None + }, + State::AwaitingResponse(peer, req_info, receiver) => { + let resp = receiver.await; + (*peer, req_info.clone(), resp) + }, + }; + // We received the awaited response. Our 'receiver' will never generate any other response, + // meaning we're done with current state. Move the engine to `State::Idle`. + self.state = State::Idle; + + let block = req_info.block; + self.process_response(peer, &req_info, resp) + .map_err(|_| { + // No valid justification received, try next peer in our set. + if let Some(peer) = self.try_next_peer() { + self.request_from_peer(peer, req_info); + } else { + warn!(target: "beefy::sync", "🥩 ran out of peers to request justif #{:?} from", block); + } + }) + .map(|proof| { + debug!( + target: "beefy::sync", + "🥩 received valid on-demand justif #{:?} from {:?}", + block, peer + ); + proof + }) + .ok() + } +} diff --git a/client/beefy/src/error.rs b/client/beefy/src/error.rs new file mode 100644 index 0000000000000..dd5fd649d52ce --- /dev/null +++ b/client/beefy/src/error.rs @@ -0,0 +1,35 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! BEEFY gadget specific errors +//! +//! Used for BEEFY gadget interal error handling only + +use std::fmt::Debug; + +#[derive(Debug, thiserror::Error, PartialEq)] +pub enum Error { + #[error("Backend: {0}")] + Backend(String), + #[error("Keystore error: {0}")] + Keystore(String), + #[error("Signature error: {0}")] + Signature(String), + #[error("Session uninitialized")] + UninitSession, +} diff --git a/client/beefy/src/import.rs b/client/beefy/src/import.rs new file mode 100644 index 0000000000000..0ed50d0ec8c98 --- /dev/null +++ b/client/beefy/src/import.rs @@ -0,0 +1,166 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use beefy_primitives::{BeefyApi, BEEFY_ENGINE_ID}; +use log::debug; +use std::{collections::HashMap, sync::Arc}; + +use sp_api::{ProvideRuntimeApi, TransactionFor}; +use sp_blockchain::well_known_cache_keys; +use sp_consensus::Error as ConsensusError; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Header as HeaderT, NumberFor}, + EncodedJustification, +}; + +use sc_client_api::backend::Backend; +use sc_consensus::{BlockCheckParams, BlockImport, BlockImportParams, ImportResult}; + +use crate::{ + communication::notification::BeefyVersionedFinalityProofSender, + justification::{decode_and_verify_finality_proof, BeefyVersionedFinalityProof}, +}; + +/// A block-import handler for BEEFY. +/// +/// This scans each imported block for BEEFY justifications and verifies them. +/// Wraps a `inner: BlockImport` and ultimately defers to it. +/// +/// When using BEEFY, the block import worker should be using this block import object. +pub struct BeefyBlockImport { + backend: Arc, + runtime: Arc, + inner: I, + justification_sender: BeefyVersionedFinalityProofSender, +} + +impl Clone for BeefyBlockImport { + fn clone(&self) -> Self { + BeefyBlockImport { + backend: self.backend.clone(), + runtime: self.runtime.clone(), + inner: self.inner.clone(), + justification_sender: self.justification_sender.clone(), + } + } +} + +impl BeefyBlockImport { + /// Create a new BeefyBlockImport. + pub fn new( + backend: Arc, + runtime: Arc, + inner: I, + justification_sender: BeefyVersionedFinalityProofSender, + ) -> BeefyBlockImport { + BeefyBlockImport { backend, runtime, inner, justification_sender } + } +} + +impl BeefyBlockImport +where + Block: BlockT, + BE: Backend, + Runtime: ProvideRuntimeApi, + Runtime::Api: BeefyApi + Send, +{ + fn decode_and_verify( + &self, + encoded: &EncodedJustification, + number: NumberFor, + hash: ::Hash, + ) -> Result, ConsensusError> { + let block_id = BlockId::hash(hash); + let validator_set = self + .runtime + .runtime_api() + .validator_set(&block_id) + .map_err(|e| ConsensusError::ClientImport(e.to_string()))? + .ok_or_else(|| ConsensusError::ClientImport("Unknown validator set".to_string()))?; + + decode_and_verify_finality_proof::(&encoded[..], number, &validator_set) + } +} + +#[async_trait::async_trait] +impl BlockImport for BeefyBlockImport +where + Block: BlockT, + BE: Backend, + I: BlockImport< + Block, + Error = ConsensusError, + Transaction = sp_api::TransactionFor, + > + Send + + Sync, + Runtime: ProvideRuntimeApi + Send + Sync, + Runtime::Api: BeefyApi, +{ + type Error = ConsensusError; + type Transaction = TransactionFor; + + async fn import_block( + &mut self, + mut block: BlockImportParams, + new_cache: HashMap>, + ) -> Result { + let hash = block.post_hash(); + let number = *block.header.number(); + + let beefy_encoded = block.justifications.as_mut().and_then(|just| { + let encoded = just.get(BEEFY_ENGINE_ID).cloned(); + // Remove BEEFY justification from the list before giving to `inner`; we send it to the + // voter (beefy-gadget) and it will append it to the backend after block is finalized. + just.remove(BEEFY_ENGINE_ID); + encoded + }); + + // Run inner block import. + let inner_import_result = self.inner.import_block(block, new_cache).await?; + + match (beefy_encoded, &inner_import_result) { + (Some(encoded), ImportResult::Imported(_)) => { + if let Ok(proof) = self.decode_and_verify(&encoded, number, hash) { + // The proof is valid and the block is imported and final, we can import. + debug!(target: "beefy", "🥩 import justif {:?} for block number {:?}.", proof, number); + // Send the justification to the BEEFY voter for processing. + self.justification_sender + .notify(|| Ok::<_, ()>(proof)) + .expect("forwards closure result; the closure always returns Ok; qed."); + } else { + debug!( + target: "beefy", + "🥩 error decoding justification: {:?} for imported block {:?}", + encoded, number, + ); + } + }, + _ => (), + } + + Ok(inner_import_result) + } + + async fn check_block( + &mut self, + block: BlockCheckParams, + ) -> Result { + self.inner.check_block(block).await + } +} diff --git a/client/beefy/src/justification.rs b/client/beefy/src/justification.rs new file mode 100644 index 0000000000000..7243c692727f0 --- /dev/null +++ b/client/beefy/src/justification.rs @@ -0,0 +1,188 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::keystore::BeefyKeystore; +use beefy_primitives::{ + crypto::{AuthorityId, Signature}, + ValidatorSet, VersionedFinalityProof, +}; +use codec::{Decode, Encode}; +use sp_consensus::Error as ConsensusError; +use sp_runtime::traits::{Block as BlockT, NumberFor}; + +/// A finality proof with matching BEEFY authorities' signatures. +pub type BeefyVersionedFinalityProof = + beefy_primitives::VersionedFinalityProof, Signature>; + +/// Decode and verify a Beefy FinalityProof. +pub(crate) fn decode_and_verify_finality_proof( + encoded: &[u8], + target_number: NumberFor, + validator_set: &ValidatorSet, +) -> Result, ConsensusError> { + let proof = >::decode(&mut &*encoded) + .map_err(|_| ConsensusError::InvalidJustification)?; + verify_with_validator_set::(target_number, validator_set, &proof).map(|_| proof) +} + +/// Verify the Beefy finality proof against the validator set at the block it was generated. +fn verify_with_validator_set( + target_number: NumberFor, + validator_set: &ValidatorSet, + proof: &BeefyVersionedFinalityProof, +) -> Result<(), ConsensusError> { + 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) + } + + // 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| BeefyKeystore::verify(id, sig, &message[..])) + .unwrap_or(false) + }) + .count(); + if valid_signatures >= crate::round::threshold(validator_set.len()) { + Ok(()) + } else { + Err(ConsensusError::InvalidJustification) + } + }, + } +} + +#[cfg(test)] +pub(crate) mod tests { + use beefy_primitives::{ + known_payloads, Commitment, Payload, SignedCommitment, VersionedFinalityProof, + }; + use substrate_test_runtime_client::runtime::Block; + + use super::*; + use crate::{keystore::tests::Keyring, tests::make_beefy_ids}; + + pub(crate) fn new_finality_proof( + block_num: NumberFor, + validator_set: &ValidatorSet, + keys: &[Keyring], + ) -> BeefyVersionedFinalityProof { + let commitment = Commitment { + payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), + block_number: block_num, + validator_set_id: validator_set.id(), + }; + let message = commitment.encode(); + let signatures = keys.iter().map(|key| Some(key.sign(&message))).collect(); + VersionedFinalityProof::V1(SignedCommitment { commitment, signatures }) + } + + #[test] + fn should_verify_with_validator_set() { + let keys = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + + // build valid justification + let block_num = 42; + let proof = new_finality_proof(block_num, &validator_set, keys); + + let good_proof = proof.clone().into(); + // should verify successfully + verify_with_validator_set::(block_num, &validator_set, &good_proof).unwrap(); + + // wrong block number -> should fail verification + let good_proof = proof.clone().into(); + match verify_with_validator_set::(block_num + 1, &validator_set, &good_proof) { + Err(ConsensusError::InvalidJustification) => (), + _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + }; + + // wrong validator set id -> should fail verification + let good_proof = proof.clone().into(); + let other = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); + match verify_with_validator_set::(block_num, &other, &good_proof) { + Err(ConsensusError::InvalidJustification) => (), + _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + }; + + // wrong signatures length -> should fail verification + let mut bad_proof = proof.clone(); + // change length of signatures + let bad_signed_commitment = match bad_proof { + VersionedFinalityProof::V1(ref mut sc) => sc, + }; + bad_signed_commitment.signatures.pop().flatten().unwrap(); + match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { + Err(ConsensusError::InvalidJustification) => (), + _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + }; + + // not enough signatures -> should fail verification + let mut bad_proof = proof.clone(); + let bad_signed_commitment = match bad_proof { + VersionedFinalityProof::V1(ref mut sc) => sc, + }; + // remove a signature (but same length) + *bad_signed_commitment.signatures.first_mut().unwrap() = None; + match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { + Err(ConsensusError::InvalidJustification) => (), + _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + }; + + // not enough _correct_ signatures -> should fail verification + let mut bad_proof = proof.clone(); + let bad_signed_commitment = match bad_proof { + VersionedFinalityProof::V1(ref mut sc) => sc, + }; + // change a signature to a different key + *bad_signed_commitment.signatures.first_mut().unwrap() = + Some(Keyring::Dave.sign(&bad_signed_commitment.commitment.encode())); + match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { + Err(ConsensusError::InvalidJustification) => (), + _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + }; + } + + #[test] + fn should_decode_and_verify_finality_proof() { + let keys = &[Keyring::Alice, Keyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let block_num = 1; + + // build valid justification + let proof = new_finality_proof(block_num, &validator_set, keys); + let versioned_proof: BeefyVersionedFinalityProof = proof.into(); + let encoded = versioned_proof.encode(); + + // should successfully decode and verify + let verified = + decode_and_verify_finality_proof::(&encoded, block_num, &validator_set).unwrap(); + assert_eq!(verified, versioned_proof); + } +} diff --git a/client/beefy/src/keystore.rs b/client/beefy/src/keystore.rs new file mode 100644 index 0000000000000..886c00fc5d817 --- /dev/null +++ b/client/beefy/src/keystore.rs @@ -0,0 +1,375 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use sp_application_crypto::RuntimeAppPublic; +use sp_core::keccak_256; +use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_runtime::traits::Keccak256; + +use log::warn; + +use beefy_primitives::{ + crypto::{Public, Signature}, + BeefyVerify, KEY_TYPE, +}; + +use crate::error; + +/// A BEEFY specific keystore implemented as a `Newtype`. This is basically a +/// wrapper around [`sp_keystore::SyncCryptoStore`] and allows to customize +/// common cryptographic functionality. +pub(crate) struct BeefyKeystore(Option); + +impl BeefyKeystore { + /// Check if the keystore contains a private key for one of the public keys + /// contained in `keys`. A public key with a matching private key is known + /// as a local authority id. + /// + /// Return the public key for which we also do have a private key. If no + /// matching private key is found, `None` will be returned. + pub fn authority_id(&self, keys: &[Public]) -> Option { + let store = self.0.clone()?; + + // we do check for multiple private keys as a key store sanity check. + let public: Vec = keys + .iter() + .filter(|k| SyncCryptoStore::has_keys(&*store, &[(k.to_raw_vec(), KEY_TYPE)])) + .cloned() + .collect(); + + if public.len() > 1 { + warn!(target: "beefy", "🥩 Multiple private keys found for: {:?} ({})", public, public.len()); + } + + public.get(0).cloned() + } + + /// Sign `message` with the `public` key. + /// + /// Note that `message` usually will be pre-hashed before being signed. + /// + /// Return the message signature or an error in case of failure. + pub fn sign(&self, public: &Public, message: &[u8]) -> Result { + let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; + + let msg = keccak_256(message); + let public = public.as_ref(); + + let sig = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, public, &msg) + .map_err(|e| error::Error::Keystore(e.to_string()))? + .ok_or_else(|| error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()))?; + + // check that `sig` has the expected result type + let sig = sig.clone().try_into().map_err(|_| { + error::Error::Signature(format!("invalid signature {:?} for key {:?}", sig, public)) + })?; + + Ok(sig) + } + + /// Returns a vector of [`beefy_primitives::crypto::Public`] keys which are currently supported + /// (i.e. found in the keystore). + pub fn public_keys(&self) -> Result, error::Error> { + let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; + + let pk: Vec = SyncCryptoStore::ecdsa_public_keys(&*store, KEY_TYPE) + .drain(..) + .map(Public::from) + .collect(); + + Ok(pk) + } + + /// Use the `public` key to verify that `sig` is a valid signature for `message`. + /// + /// Return `true` if the signature is authentic, `false` otherwise. + pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool { + BeefyVerify::::verify(sig, message, public) + } +} + +impl From> for BeefyKeystore { + fn from(store: Option) -> BeefyKeystore { + BeefyKeystore(store) + } +} + +#[cfg(test)] +pub mod tests { + use std::sync::Arc; + + use sc_keystore::LocalKeystore; + use sp_core::{ecdsa, keccak_256, Pair}; + use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; + + use beefy_primitives::{crypto, KEY_TYPE}; + + use super::BeefyKeystore; + use crate::error::Error; + + /// Set of test accounts using [`beefy_primitives::crypto`] types. + #[allow(missing_docs)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumIter)] + pub(crate) enum Keyring { + Alice, + Bob, + Charlie, + Dave, + Eve, + Ferdie, + One, + Two, + } + + impl Keyring { + /// Sign `msg`. + pub fn sign(self, msg: &[u8]) -> crypto::Signature { + let msg = keccak_256(msg); + ecdsa::Pair::from(self).sign_prehashed(&msg).into() + } + + /// Return key pair. + pub fn pair(self) -> crypto::Pair { + ecdsa::Pair::from_string(self.to_seed().as_str(), None).unwrap().into() + } + + /// Return public key. + pub fn public(self) -> crypto::Public { + self.pair().public() + } + + /// Return seed string. + pub fn to_seed(self) -> String { + format!("//{}", self) + } + } + + impl From for crypto::Pair { + fn from(k: Keyring) -> Self { + k.pair() + } + } + + impl From for ecdsa::Pair { + fn from(k: Keyring) -> Self { + k.pair().into() + } + } + + fn keystore() -> SyncCryptoStorePtr { + Arc::new(LocalKeystore::in_memory()) + } + + #[test] + fn verify_should_work() { + let msg = keccak_256(b"I am Alice!"); + let sig = Keyring::Alice.sign(b"I am Alice!"); + + assert!(ecdsa::Pair::verify_prehashed( + &sig.clone().into(), + &msg, + &Keyring::Alice.public().into(), + )); + + // different public key -> fail + assert!(!ecdsa::Pair::verify_prehashed( + &sig.clone().into(), + &msg, + &Keyring::Bob.public().into(), + )); + + let msg = keccak_256(b"I am not Alice!"); + + // different msg -> fail + assert!( + !ecdsa::Pair::verify_prehashed(&sig.into(), &msg, &Keyring::Alice.public().into(),) + ); + } + + #[test] + fn pair_works() { + let want = crypto::Pair::from_string("//Alice", None).expect("Pair failed").to_raw_vec(); + let got = Keyring::Alice.pair().to_raw_vec(); + assert_eq!(want, got); + + let want = crypto::Pair::from_string("//Bob", None).expect("Pair failed").to_raw_vec(); + let got = Keyring::Bob.pair().to_raw_vec(); + assert_eq!(want, got); + + let want = crypto::Pair::from_string("//Charlie", None).expect("Pair failed").to_raw_vec(); + let got = Keyring::Charlie.pair().to_raw_vec(); + assert_eq!(want, got); + + let want = crypto::Pair::from_string("//Dave", None).expect("Pair failed").to_raw_vec(); + let got = Keyring::Dave.pair().to_raw_vec(); + assert_eq!(want, got); + + let want = crypto::Pair::from_string("//Eve", None).expect("Pair failed").to_raw_vec(); + let got = Keyring::Eve.pair().to_raw_vec(); + assert_eq!(want, got); + + let want = crypto::Pair::from_string("//Ferdie", None).expect("Pair failed").to_raw_vec(); + let got = Keyring::Ferdie.pair().to_raw_vec(); + assert_eq!(want, got); + + let want = crypto::Pair::from_string("//One", None).expect("Pair failed").to_raw_vec(); + let got = Keyring::One.pair().to_raw_vec(); + assert_eq!(want, got); + + let want = crypto::Pair::from_string("//Two", None).expect("Pair failed").to_raw_vec(); + let got = Keyring::Two.pair().to_raw_vec(); + assert_eq!(want, got); + } + + #[test] + fn authority_id_works() { + let store = keystore(); + + let alice: crypto::Public = + SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Alice.to_seed())) + .ok() + .unwrap() + .into(); + + let bob = Keyring::Bob.public(); + let charlie = Keyring::Charlie.public(); + + let store: BeefyKeystore = Some(store).into(); + + let mut keys = vec![bob, charlie]; + + let id = store.authority_id(keys.as_slice()); + assert!(id.is_none()); + + keys.push(alice.clone()); + + let id = store.authority_id(keys.as_slice()).unwrap(); + assert_eq!(id, alice); + } + + #[test] + fn sign_works() { + let store = keystore(); + + let alice: crypto::Public = + SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Alice.to_seed())) + .ok() + .unwrap() + .into(); + + let store: BeefyKeystore = Some(store).into(); + + let msg = b"are you involved or commited?"; + + let sig1 = store.sign(&alice, msg).unwrap(); + let sig2 = Keyring::Alice.sign(msg); + + assert_eq!(sig1, sig2); + } + + #[test] + fn sign_error() { + let store = keystore(); + + let _ = + SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Bob.to_seed())) + .ok() + .unwrap(); + + let store: BeefyKeystore = Some(store).into(); + + let alice = Keyring::Alice.public(); + + let msg = b"are you involved or commited?"; + let sig = store.sign(&alice, msg).err().unwrap(); + let err = Error::Signature("ecdsa_sign_prehashed() failed".to_string()); + + assert_eq!(sig, err); + } + + #[test] + fn sign_no_keystore() { + let store: BeefyKeystore = None.into(); + + let alice = Keyring::Alice.public(); + let msg = b"are you involved or commited"; + + let sig = store.sign(&alice, msg).err().unwrap(); + let err = Error::Keystore("no Keystore".to_string()); + assert_eq!(sig, err); + } + + #[test] + fn verify_works() { + let store = keystore(); + + let alice: crypto::Public = + SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Alice.to_seed())) + .ok() + .unwrap() + .into(); + + let store: BeefyKeystore = Some(store).into(); + + // `msg` and `sig` match + let msg = b"are you involved or commited?"; + let sig = store.sign(&alice, msg).unwrap(); + assert!(BeefyKeystore::verify(&alice, &sig, msg)); + + // `msg and `sig` don't match + let msg = b"you are just involved"; + assert!(!BeefyKeystore::verify(&alice, &sig, msg)); + } + + // Note that we use keys with and without a seed for this test. + #[test] + fn public_keys_works() { + const TEST_TYPE: sp_application_crypto::KeyTypeId = + sp_application_crypto::KeyTypeId(*b"test"); + + let store = keystore(); + + let add_key = |key_type, seed: Option<&str>| { + SyncCryptoStore::ecdsa_generate_new(&*store, key_type, seed).unwrap() + }; + + // test keys + let _ = add_key(TEST_TYPE, Some(Keyring::Alice.to_seed().as_str())); + let _ = add_key(TEST_TYPE, Some(Keyring::Bob.to_seed().as_str())); + + let _ = add_key(TEST_TYPE, None); + let _ = add_key(TEST_TYPE, None); + + // BEEFY keys + let _ = add_key(KEY_TYPE, Some(Keyring::Dave.to_seed().as_str())); + let _ = add_key(KEY_TYPE, Some(Keyring::Eve.to_seed().as_str())); + + let key1: crypto::Public = add_key(KEY_TYPE, None).into(); + let key2: crypto::Public = add_key(KEY_TYPE, None).into(); + + let store: BeefyKeystore = Some(store).into(); + + let keys = store.public_keys().ok().unwrap(); + + assert!(keys.len() == 4); + assert!(keys.contains(&Keyring::Dave.public())); + assert!(keys.contains(&Keyring::Eve.public())); + assert!(keys.contains(&key1)); + assert!(keys.contains(&key2)); + } +} diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs new file mode 100644 index 0000000000000..a057a9fdc597d --- /dev/null +++ b/client/beefy/src/lib.rs @@ -0,0 +1,489 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{ + communication::{ + notification::{ + BeefyBestBlockSender, BeefyBestBlockStream, BeefyVersionedFinalityProofSender, + BeefyVersionedFinalityProofStream, + }, + peers::KnownPeers, + request_response::{ + outgoing_requests_engine::OnDemandJustificationsEngine, BeefyJustifsRequestHandler, + }, + }, + import::BeefyBlockImport, + round::Rounds, + worker::PersistedState, +}; +use beefy_primitives::{ + crypto::AuthorityId, BeefyApi, MmrRootHash, PayloadProvider, ValidatorSet, BEEFY_ENGINE_ID, + GENESIS_AUTHORITY_SET_ID, +}; +use futures::{stream::Fuse, StreamExt}; +use log::{debug, error, info}; +use parking_lot::Mutex; +use prometheus::Registry; +use sc_client_api::{Backend, BlockBackend, BlockchainEvents, FinalityNotifications, Finalizer}; +use sc_consensus::BlockImport; +use sc_network::ProtocolName; +use sc_network_common::service::NetworkRequest; +use sc_network_gossip::{GossipEngine, Network as GossipNetwork}; +use sp_api::{HeaderT, NumberFor, ProvideRuntimeApi}; +use sp_blockchain::{ + Backend as BlockchainBackend, Error as ClientError, HeaderBackend, Result as ClientResult, +}; +use sp_consensus::{Error as ConsensusError, SyncOracle}; +use sp_keystore::SyncCryptoStorePtr; +use sp_mmr_primitives::MmrApi; +use sp_runtime::{ + generic::BlockId, + traits::{Block, One, Zero}, +}; +use std::{collections::VecDeque, marker::PhantomData, sync::Arc}; + +mod aux_schema; +mod error; +mod keystore; +mod metrics; +mod round; +mod worker; + +pub mod communication; +pub mod import; +pub mod justification; + +pub use communication::beefy_protocol_name::{ + gossip_protocol_name, justifications_protocol_name as justifs_protocol_name, +}; + +#[cfg(test)] +mod tests; + +/// A convenience BEEFY client trait that defines all the type bounds a BEEFY client +/// has to satisfy. Ideally that should actually be a trait alias. Unfortunately as +/// of today, Rust does not allow a type alias to be used as a trait bound. Tracking +/// issue is . +pub trait Client: + BlockchainEvents + HeaderBackend + Finalizer + Send + Sync +where + B: Block, + BE: Backend, +{ + // empty +} + +impl Client for T +where + B: Block, + BE: Backend, + T: BlockchainEvents + + HeaderBackend + + Finalizer + + ProvideRuntimeApi + + Send + + Sync, +{ + // empty +} + +/// Links between the block importer, the background voter and the RPC layer, +/// to be used by the voter. +#[derive(Clone)] +pub struct BeefyVoterLinks { + // BlockImport -> Voter links + /// Stream of BEEFY signed commitments from block import to voter. + pub from_block_import_justif_stream: BeefyVersionedFinalityProofStream, + + // Voter -> RPC links + /// Sends BEEFY signed commitments from voter to RPC. + pub to_rpc_justif_sender: BeefyVersionedFinalityProofSender, + /// Sends BEEFY best block hashes from voter to RPC. + pub to_rpc_best_block_sender: BeefyBestBlockSender, +} + +/// Links used by the BEEFY RPC layer, from the BEEFY background voter. +#[derive(Clone)] +pub struct BeefyRPCLinks { + /// Stream of signed commitments coming from the voter. + pub from_voter_justif_stream: BeefyVersionedFinalityProofStream, + /// Stream of BEEFY best block hashes coming from the voter. + pub from_voter_best_beefy_stream: BeefyBestBlockStream, +} + +/// Make block importer and link half necessary to tie the background voter to it. +pub fn beefy_block_import_and_links( + wrapped_block_import: I, + backend: Arc, + runtime: Arc, +) -> (BeefyBlockImport, BeefyVoterLinks, BeefyRPCLinks) +where + B: Block, + BE: Backend, + I: BlockImport> + + Send + + Sync, + RuntimeApi: ProvideRuntimeApi + Send + Sync, + RuntimeApi::Api: BeefyApi, +{ + // Voter -> RPC links + let (to_rpc_justif_sender, from_voter_justif_stream) = + BeefyVersionedFinalityProofStream::::channel(); + let (to_rpc_best_block_sender, from_voter_best_beefy_stream) = + BeefyBestBlockStream::::channel(); + + // BlockImport -> Voter links + let (to_voter_justif_sender, from_block_import_justif_stream) = + BeefyVersionedFinalityProofStream::::channel(); + + // BlockImport + let import = + BeefyBlockImport::new(backend, runtime, wrapped_block_import, to_voter_justif_sender); + let voter_links = BeefyVoterLinks { + from_block_import_justif_stream, + to_rpc_justif_sender, + to_rpc_best_block_sender, + }; + let rpc_links = BeefyRPCLinks { from_voter_best_beefy_stream, from_voter_justif_stream }; + + (import, voter_links, rpc_links) +} + +/// BEEFY gadget network parameters. +pub struct BeefyNetworkParams { + /// Network implementing gossip, requests and sync-oracle. + pub network: Arc, + /// Chain specific BEEFY gossip protocol name. See + /// [`communication::beefy_protocol_name::gossip_protocol_name`]. + pub gossip_protocol_name: ProtocolName, + /// Chain specific BEEFY on-demand justifications protocol name. See + /// [`communication::beefy_protocol_name::justifications_protocol_name`]. + pub justifications_protocol_name: ProtocolName, + + pub _phantom: PhantomData, +} + +/// BEEFY gadget initialization parameters. +pub struct BeefyParams { + /// BEEFY client + pub client: Arc, + /// Client Backend + pub backend: Arc, + /// BEEFY Payload provider + pub payload_provider: P, + /// Runtime Api Provider + pub runtime: Arc, + /// Local key store + pub key_store: Option, + /// BEEFY voter network params + pub network_params: BeefyNetworkParams, + /// Minimal delta between blocks, BEEFY should vote for + pub min_block_delta: u32, + /// Prometheus metric registry + pub prometheus_registry: Option, + /// Links between the block importer, the background voter and the RPC layer. + pub links: BeefyVoterLinks, + /// Handler for incoming BEEFY justifications requests from a remote peer. + pub on_demand_justifications_handler: BeefyJustifsRequestHandler, +} + +/// Start the BEEFY gadget. +/// +/// This is a thin shim around running and awaiting a BEEFY worker. +pub async fn start_beefy_gadget(beefy_params: BeefyParams) +where + B: Block, + BE: Backend, + C: Client + BlockBackend, + P: PayloadProvider, + R: ProvideRuntimeApi, + R::Api: BeefyApi + MmrApi>, + N: GossipNetwork + NetworkRequest + SyncOracle + Send + Sync + 'static, +{ + let BeefyParams { + client, + backend, + payload_provider, + runtime, + key_store, + network_params, + min_block_delta, + prometheus_registry, + links, + on_demand_justifications_handler, + } = beefy_params; + + let BeefyNetworkParams { network, gossip_protocol_name, justifications_protocol_name, .. } = + network_params; + + let known_peers = Arc::new(Mutex::new(KnownPeers::new())); + let gossip_validator = + Arc::new(communication::gossip::GossipValidator::new(known_peers.clone())); + let mut gossip_engine = sc_network_gossip::GossipEngine::new( + network.clone(), + gossip_protocol_name, + gossip_validator.clone(), + None, + ); + + // The `GossipValidator` adds and removes known peers based on valid votes and network events. + let on_demand_justifications = OnDemandJustificationsEngine::new( + network.clone(), + justifications_protocol_name, + known_peers, + ); + + let metrics = + prometheus_registry.as_ref().map(metrics::Metrics::register).and_then( + |result| match result { + Ok(metrics) => { + debug!(target: "beefy", "🥩 Registered metrics"); + Some(metrics) + }, + Err(err) => { + debug!(target: "beefy", "🥩 Failed to register metrics: {:?}", err); + None + }, + }, + ); + + // Subscribe to finality notifications and justifications before waiting for runtime pallet and + // reuse the streams, so we don't miss notifications while waiting for pallet to be available. + let mut finality_notifications = client.finality_notification_stream().fuse(); + let block_import_justif = links.from_block_import_justif_stream.subscribe().fuse(); + + // Wait for BEEFY pallet to be active before starting voter. + let persisted_state = + match wait_for_runtime_pallet(&*runtime, &mut gossip_engine, &mut finality_notifications) + .await + .and_then(|best_grandpa| { + load_or_init_voter_state(&*backend, &*runtime, best_grandpa, min_block_delta) + }) { + Ok(state) => state, + Err(e) => { + error!(target: "beefy", "Error: {:?}. Terminating.", e); + return + }, + }; + + let worker_params = worker::WorkerParams { + backend, + payload_provider, + network, + key_store: key_store.into(), + gossip_engine, + gossip_validator, + on_demand_justifications, + links, + metrics, + persisted_state, + }; + + let worker = worker::BeefyWorker::<_, _, _, _>::new(worker_params); + + futures::future::join( + worker.run(block_import_justif, finality_notifications), + on_demand_justifications_handler.run(), + ) + .await; +} + +fn load_or_init_voter_state( + backend: &BE, + runtime: &R, + best_grandpa: ::Header, + min_block_delta: u32, +) -> ClientResult> +where + B: Block, + BE: Backend, + R: ProvideRuntimeApi, + R::Api: BeefyApi, +{ + // Initialize voter state from AUX DB or from pallet genesis. + if let Some(mut state) = crate::aux_schema::load_persistent(backend)? { + // Overwrite persisted state with current best GRANDPA block. + state.set_best_grandpa(best_grandpa); + // Overwrite persisted data with newly provided `min_block_delta`. + state.set_min_block_delta(min_block_delta); + info!(target: "beefy", "🥩 Loading BEEFY voter state from db: {:?}.", state); + Ok(state) + } else { + initialize_voter_state(backend, runtime, best_grandpa, min_block_delta) + } +} + +// If no persisted state present, walk back the chain from first GRANDPA notification to either: +// - latest BEEFY finalized block, or if none found on the way, +// - BEEFY pallet genesis; +// Enqueue any BEEFY mandatory blocks (session boundaries) found on the way, for voter to finalize. +fn initialize_voter_state( + backend: &BE, + runtime: &R, + best_grandpa: ::Header, + min_block_delta: u32, +) -> ClientResult> +where + B: Block, + BE: Backend, + R: ProvideRuntimeApi, + R::Api: BeefyApi, +{ + // Walk back the imported blocks and initialize voter either, at the last block with + // a BEEFY justification, or at pallet genesis block; voter will resume from there. + let blockchain = backend.blockchain(); + let mut sessions = VecDeque::new(); + let mut header = best_grandpa.clone(); + let state = loop { + if let Some(true) = blockchain + .justifications(header.hash()) + .ok() + .flatten() + .map(|justifs| justifs.get(BEEFY_ENGINE_ID).is_some()) + { + info!( + target: "beefy", + "🥩 Initialize BEEFY voter at last BEEFY finalized block: {:?}.", + *header.number() + ); + let best_beefy = *header.number(); + // If no session boundaries detected so far, just initialize new rounds here. + if sessions.is_empty() { + let active_set = expect_validator_set(runtime, BlockId::hash(header.hash()))?; + let mut rounds = Rounds::new(best_beefy, active_set); + // Mark the round as already finalized. + rounds.conclude(best_beefy); + sessions.push_front(rounds); + } + let state = + PersistedState::checked_new(best_grandpa, best_beefy, sessions, min_block_delta) + .ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?; + break state + } + + if *header.number() == One::one() { + // We've reached chain genesis, initialize voter here. + let genesis_num = *header.number(); + let genesis_set = expect_validator_set(runtime, BlockId::hash(header.hash())) + .and_then(genesis_set_sanity_check)?; + info!( + target: "beefy", + "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ + Starting voting rounds at block {:?}, genesis validator set {:?}.", + genesis_num, genesis_set, + ); + + sessions.push_front(Rounds::new(genesis_num, genesis_set)); + break PersistedState::checked_new(best_grandpa, Zero::zero(), sessions, min_block_delta) + .ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))? + } + + if let Some(active) = worker::find_authorities_change::(&header) { + info!(target: "beefy", "🥩 Marking block {:?} as BEEFY Mandatory.", *header.number()); + sessions.push_front(Rounds::new(*header.number(), active)); + } + + // Check if state is still available if we move up the chain. + let parent_hash = *header.parent_hash(); + runtime + .runtime_api() + .validator_set(&BlockId::hash(parent_hash)) + .ok() + .flatten() + .ok_or_else(|| { + let msg = format!("{}. Could not initialize BEEFY voter.", parent_hash); + error!(target: "beefy", "🥩 {}", msg); + ClientError::Consensus(sp_consensus::Error::StateUnavailable(msg)) + })?; + + // Move up the chain. + header = blockchain.expect_header(BlockId::Hash(parent_hash))?; + }; + + aux_schema::write_current_version(backend)?; + aux_schema::write_voter_state(backend, &state)?; + Ok(state) +} + +/// Wait for BEEFY runtime pallet to be available, return active validator set. +/// Should be called only once during worker initialization. +async fn wait_for_runtime_pallet( + runtime: &R, + mut gossip_engine: &mut GossipEngine, + finality: &mut Fuse>, +) -> ClientResult<::Header> +where + B: Block, + R: ProvideRuntimeApi, + R::Api: BeefyApi, +{ + info!(target: "beefy", "🥩 BEEFY gadget waiting for BEEFY pallet to become available..."); + loop { + futures::select! { + notif = finality.next() => { + let notif = match notif { + Some(notif) => notif, + None => break + }; + let at = BlockId::hash(notif.header.hash()); + if let Some(active) = runtime.runtime_api().validator_set(&at).ok().flatten() { + // Beefy pallet available, return best grandpa at the time. + info!( + target: "beefy", "🥩 BEEFY pallet available: block {:?} validator set {:?}", + notif.header.number(), active + ); + return Ok(notif.header) + } + }, + _ = gossip_engine => { + break + } + } + } + let err_msg = "🥩 Gossip engine has unexpectedly terminated.".into(); + error!(target: "beefy", "{}", err_msg); + Err(ClientError::Backend(err_msg)) +} + +fn genesis_set_sanity_check( + active: ValidatorSet, +) -> ClientResult> { + if active.id() == GENESIS_AUTHORITY_SET_ID { + Ok(active) + } else { + error!(target: "beefy", "🥩 Unexpected ID for genesis validator set {:?}.", active); + Err(ClientError::Backend("BEEFY Genesis sanity check failed.".into())) + } +} + +fn expect_validator_set( + runtime: &R, + at: BlockId, +) -> ClientResult> +where + B: Block, + R: ProvideRuntimeApi, + R::Api: BeefyApi, +{ + runtime + .runtime_api() + .validator_set(&at) + .ok() + .flatten() + .ok_or_else(|| ClientError::Backend("BEEFY pallet expected to be active.".into())) +} diff --git a/client/beefy/src/metrics.rs b/client/beefy/src/metrics.rs new file mode 100644 index 0000000000000..71e34e24c4fa0 --- /dev/null +++ b/client/beefy/src/metrics.rs @@ -0,0 +1,107 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! BEEFY Prometheus metrics definition + +use prometheus::{register, Counter, Gauge, PrometheusError, Registry, U64}; + +/// BEEFY metrics exposed through Prometheus +pub(crate) struct Metrics { + /// Current active validator set id + pub beefy_validator_set_id: Gauge, + /// Total number of votes sent by this node + pub beefy_votes_sent: Counter, + /// Most recent concluded voting round + pub beefy_round_concluded: Gauge, + /// Best block finalized by BEEFY + pub beefy_best_block: Gauge, + /// Next block BEEFY should vote on + pub beefy_should_vote_on: Gauge, + /// Number of sessions with lagging signed commitment on mandatory block + pub beefy_lagging_sessions: Counter, +} + +impl Metrics { + pub(crate) fn register(registry: &Registry) -> Result { + Ok(Self { + beefy_validator_set_id: register( + Gauge::new( + "substrate_beefy_validator_set_id", + "Current BEEFY active validator set id.", + )?, + registry, + )?, + beefy_votes_sent: register( + Counter::new("substrate_beefy_votes_sent", "Number of votes sent by this node")?, + registry, + )?, + beefy_round_concluded: register( + Gauge::new( + "substrate_beefy_round_concluded", + "Voting round, that has been concluded", + )?, + registry, + )?, + beefy_best_block: register( + Gauge::new("substrate_beefy_best_block", "Best block finalized by BEEFY")?, + registry, + )?, + beefy_should_vote_on: register( + Gauge::new("substrate_beefy_should_vote_on", "Next block, BEEFY should vote on")?, + registry, + )?, + beefy_lagging_sessions: register( + Counter::new( + "substrate_beefy_lagging_sessions", + "Number of sessions with lagging signed commitment on mandatory block", + )?, + registry, + )?, + }) + } +} + +// Note: we use the `format` macro to convert an expr into a `u64`. This will fail, +// if expr does not derive `Display`. +#[macro_export] +macro_rules! metric_set { + ($self:ident, $m:ident, $v:expr) => {{ + let val: u64 = format!("{}", $v).parse().unwrap(); + + if let Some(metrics) = $self.metrics.as_ref() { + metrics.$m.set(val); + } + }}; +} + +#[macro_export] +macro_rules! metric_inc { + ($self:ident, $m:ident) => {{ + if let Some(metrics) = $self.metrics.as_ref() { + metrics.$m.inc(); + } + }}; +} + +#[cfg(test)] +#[macro_export] +macro_rules! metric_get { + ($self:ident, $m:ident) => {{ + $self.metrics.as_ref().map(|metrics| metrics.$m.clone()) + }}; +} diff --git a/client/beefy/src/round.rs b/client/beefy/src/round.rs new file mode 100644 index 0000000000000..48d3d087299d0 --- /dev/null +++ b/client/beefy/src/round.rs @@ -0,0 +1,454 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use beefy_primitives::{ + crypto::{Public, Signature}, + ValidatorSet, ValidatorSetId, +}; +use codec::{Decode, Encode}; +use log::{debug, trace}; +use sp_runtime::traits::{Block, NumberFor}; +use std::{collections::BTreeMap, hash::Hash}; + +/// Tracks for each round which validators have voted/signed and +/// whether the local `self` validator has voted/signed. +/// +/// Does not do any validation on votes or signatures, layers above need to handle that (gossip). +#[derive(Debug, Decode, Default, Encode, PartialEq)] +struct RoundTracker { + self_vote: bool, + votes: BTreeMap, +} + +impl RoundTracker { + fn add_vote(&mut self, vote: (Public, Signature), self_vote: bool) -> bool { + if self.votes.contains_key(&vote.0) { + return false + } + + self.self_vote = self.self_vote || self_vote; + self.votes.insert(vote.0, vote.1); + true + } + + fn has_self_vote(&self) -> bool { + self.self_vote + } + + fn is_done(&self, threshold: usize) -> bool { + self.votes.len() >= threshold + } +} + +/// Minimum size of `authorities` subset that produced valid signatures for a block to finalize. +pub fn threshold(authorities: usize) -> usize { + let faulty = authorities.saturating_sub(1) / 3; + authorities - faulty +} + +/// Keeps track of all voting rounds (block numbers) within a session. +/// Only round numbers > `best_done` are of interest, all others are considered stale. +/// +/// Does not do any validation on votes or signatures, layers above need to handle that (gossip). +#[derive(Debug, Decode, Encode, PartialEq)] +pub(crate) struct Rounds { + rounds: BTreeMap<(Payload, NumberFor), RoundTracker>, + session_start: NumberFor, + validator_set: ValidatorSet, + mandatory_done: bool, + best_done: Option>, +} + +impl Rounds +where + P: Ord + Hash + Clone, + B: Block, +{ + pub(crate) fn new(session_start: NumberFor, validator_set: ValidatorSet) -> Self { + Rounds { + rounds: BTreeMap::new(), + session_start, + validator_set, + mandatory_done: false, + best_done: None, + } + } + + pub(crate) fn validator_set(&self) -> &ValidatorSet { + &self.validator_set + } + + pub(crate) fn validator_set_id(&self) -> ValidatorSetId { + self.validator_set.id() + } + + pub(crate) fn validators(&self) -> &[Public] { + self.validator_set.validators() + } + + pub(crate) fn session_start(&self) -> NumberFor { + self.session_start + } + + pub(crate) fn mandatory_done(&self) -> bool { + self.mandatory_done + } + + pub(crate) fn should_self_vote(&self, round: &(P, NumberFor)) -> bool { + Some(round.1) > self.best_done && + self.rounds.get(round).map(|tracker| !tracker.has_self_vote()).unwrap_or(true) + } + + pub(crate) fn add_vote( + &mut self, + round: &(P, NumberFor), + vote: (Public, Signature), + self_vote: bool, + ) -> bool { + let num = round.1; + if num < self.session_start || Some(num) <= self.best_done { + debug!(target: "beefy", "🥩 received vote for old stale round {:?}, ignoring", num); + false + } else if !self.validators().iter().any(|id| vote.0 == *id) { + debug!( + target: "beefy", + "🥩 received vote {:?} from validator that is not in the validator set, ignoring", + vote + ); + false + } else { + self.rounds.entry(round.clone()).or_default().add_vote(vote, self_vote) + } + } + + pub(crate) fn should_conclude( + &mut self, + round: &(P, NumberFor), + ) -> Option>> { + let done = self + .rounds + .get(round) + .map(|tracker| tracker.is_done(threshold(self.validator_set.len()))) + .unwrap_or(false); + trace!(target: "beefy", "🥩 Round #{} done: {}", round.1, done); + + if done { + let signatures = self.rounds.remove(round)?.votes; + Some( + self.validators() + .iter() + .map(|authority_id| signatures.get(authority_id).cloned()) + .collect(), + ) + } else { + None + } + } + + pub(crate) fn conclude(&mut self, round_num: NumberFor) { + // Remove this and older (now stale) rounds. + self.rounds.retain(|&(_, number), _| number > round_num); + self.mandatory_done = self.mandatory_done || round_num == self.session_start; + self.best_done = self.best_done.max(Some(round_num)); + debug!(target: "beefy", "🥩 Concluded round #{}", round_num); + } +} + +#[cfg(test)] +mod tests { + use sc_network_test::Block; + use sp_core::H256; + + use beefy_primitives::{crypto::Public, ValidatorSet}; + + use super::{threshold, Block as BlockT, Hash, RoundTracker, Rounds}; + use crate::keystore::tests::Keyring; + + impl Rounds + where + P: Ord + Hash + Clone, + B: BlockT, + { + pub(crate) fn test_set_mandatory_done(&mut self, done: bool) { + self.mandatory_done = done; + } + } + + #[test] + fn round_tracker() { + let mut rt = RoundTracker::default(); + let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")); + let threshold = 2; + + // self vote not added yet + assert!(!rt.has_self_vote()); + + // adding new vote allowed + assert!(rt.add_vote(bob_vote.clone(), false)); + // adding existing vote not allowed + assert!(!rt.add_vote(bob_vote, false)); + + // self vote still not added yet + assert!(!rt.has_self_vote()); + + // vote is not done + assert!(!rt.is_done(threshold)); + + let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); + // adding new vote (self vote this time) allowed + assert!(rt.add_vote(alice_vote, true)); + + // self vote registered + assert!(rt.has_self_vote()); + // vote is now done + assert!(rt.is_done(threshold)); + } + + #[test] + fn vote_threshold() { + assert_eq!(threshold(1), 1); + assert_eq!(threshold(2), 2); + assert_eq!(threshold(3), 3); + assert_eq!(threshold(4), 3); + assert_eq!(threshold(100), 67); + assert_eq!(threshold(300), 201); + } + + #[test] + fn new_rounds() { + sp_tracing::try_init_simple(); + + let validators = ValidatorSet::::new( + vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + 42, + ) + .unwrap(); + + let session_start = 1u64.into(); + let rounds = Rounds::::new(session_start, validators); + + assert_eq!(42, rounds.validator_set_id()); + assert_eq!(1, rounds.session_start()); + assert_eq!( + &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + rounds.validators() + ); + } + + #[test] + fn add_and_conclude_votes() { + sp_tracing::try_init_simple(); + + let validators = ValidatorSet::::new( + vec![ + Keyring::Alice.public(), + Keyring::Bob.public(), + Keyring::Charlie.public(), + Keyring::Eve.public(), + ], + Default::default(), + ) + .unwrap(); + let round = (H256::from_low_u64_le(1), 1); + + let session_start = 1u64.into(); + let mut rounds = Rounds::::new(session_start, validators); + + // no self vote yet, should self vote + assert!(rounds.should_self_vote(&round)); + + // add 1st good vote + assert!(rounds.add_vote( + &round, + (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), + true + )); + // round not concluded + assert!(rounds.should_conclude(&round).is_none()); + // self vote already present, should not self vote + assert!(!rounds.should_self_vote(&round)); + + // double voting not allowed + assert!(!rounds.add_vote( + &round, + (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), + true + )); + + // invalid vote (Dave is not a validator) + assert!(!rounds.add_vote( + &round, + (Keyring::Dave.public(), Keyring::Dave.sign(b"I am committed")), + false + )); + assert!(rounds.should_conclude(&round).is_none()); + + // add 2nd good vote + assert!(rounds.add_vote( + &round, + (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")), + false + )); + // round not concluded + assert!(rounds.should_conclude(&round).is_none()); + + // add 3rd good vote + assert!(rounds.add_vote( + &round, + (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")), + false + )); + // round concluded + assert!(rounds.should_conclude(&round).is_some()); + rounds.conclude(round.1); + + // Eve is a validator, but round was concluded, adding vote disallowed + assert!(!rounds.add_vote( + &round, + (Keyring::Eve.public(), Keyring::Eve.sign(b"I am committed")), + false + )); + } + + #[test] + fn old_rounds_not_accepted() { + sp_tracing::try_init_simple(); + + let validators = ValidatorSet::::new( + vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + 42, + ) + .unwrap(); + let alice = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); + + let session_start = 10u64.into(); + let mut rounds = Rounds::::new(session_start, validators); + + let mut vote = (H256::from_low_u64_le(1), 9); + // add vote for previous session, should fail + assert!(!rounds.add_vote(&vote, alice.clone(), true)); + // no votes present + assert!(rounds.rounds.is_empty()); + + // simulate 11 was concluded + rounds.best_done = Some(11); + // add votes for current session, but already concluded rounds, should fail + vote.1 = 10; + assert!(!rounds.add_vote(&vote, alice.clone(), true)); + vote.1 = 11; + assert!(!rounds.add_vote(&vote, alice.clone(), true)); + // no votes present + assert!(rounds.rounds.is_empty()); + + // add good vote + vote.1 = 12; + assert!(rounds.add_vote(&vote, alice, true)); + // good vote present + assert_eq!(rounds.rounds.len(), 1); + } + + #[test] + fn multiple_rounds() { + sp_tracing::try_init_simple(); + + let validators = ValidatorSet::::new( + vec![ + Keyring::Alice.public(), + Keyring::Bob.public(), + Keyring::Charlie.public(), + Keyring::Dave.public(), + ], + Default::default(), + ) + .unwrap(); + + let session_start = 1u64.into(); + let mut rounds = Rounds::::new(session_start, validators); + + // round 1 + assert!(rounds.add_vote( + &(H256::from_low_u64_le(1), 1), + (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), + true, + )); + assert!(rounds.add_vote( + &(H256::from_low_u64_le(1), 1), + (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")), + false, + )); + assert!(rounds.add_vote( + &(H256::from_low_u64_le(1), 1), + (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")), + false, + )); + + // round 2 + assert!(rounds.add_vote( + &(H256::from_low_u64_le(2), 2), + (Keyring::Alice.public(), Keyring::Alice.sign(b"I am again committed")), + true, + )); + assert!(rounds.add_vote( + &(H256::from_low_u64_le(2), 2), + (Keyring::Bob.public(), Keyring::Bob.sign(b"I am again committed")), + false, + )); + assert!(rounds.add_vote( + &(H256::from_low_u64_le(2), 2), + (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am again committed")), + false, + )); + + // round 3 + assert!(rounds.add_vote( + &(H256::from_low_u64_le(3), 3), + (Keyring::Alice.public(), Keyring::Alice.sign(b"I am still committed")), + true, + )); + assert!(rounds.add_vote( + &(H256::from_low_u64_le(3), 3), + (Keyring::Bob.public(), Keyring::Bob.sign(b"I am still committed")), + false, + )); + assert!(rounds.add_vote( + &(H256::from_low_u64_le(3), 3), + (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am still committed")), + false, + )); + assert_eq!(3, rounds.rounds.len()); + + // conclude unknown round + assert!(rounds.should_conclude(&(H256::from_low_u64_le(5), 5)).is_none()); + assert_eq!(3, rounds.rounds.len()); + + // conclude round 2 + let signatures = rounds.should_conclude(&(H256::from_low_u64_le(2), 2)).unwrap(); + rounds.conclude(2); + assert_eq!(1, rounds.rounds.len()); + + assert_eq!( + signatures, + vec![ + Some(Keyring::Alice.sign(b"I am again committed")), + Some(Keyring::Bob.sign(b"I am again committed")), + Some(Keyring::Charlie.sign(b"I am again committed")), + None + ] + ); + } +} diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs new file mode 100644 index 0000000000000..f6ab0dd1020f1 --- /dev/null +++ b/client/beefy/src/tests.rs @@ -0,0 +1,1154 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Tests and test helpers for BEEFY. + +use crate::{ + aux_schema::{load_persistent, tests::verify_persisted_version}, + beefy_block_import_and_links, + communication::request_response::{ + on_demand_justifications_protocol_config, BeefyJustifsRequestHandler, + }, + gossip_protocol_name, + justification::*, + keystore::tests::Keyring as BeefyKeyring, + load_or_init_voter_state, wait_for_runtime_pallet, BeefyRPCLinks, BeefyVoterLinks, KnownPeers, + PersistedState, +}; +use beefy_primitives::{ + crypto::{AuthorityId, Signature}, + known_payloads, + mmr::MmrRootProvider, + BeefyApi, Commitment, ConsensusLog, MmrRootHash, Payload, SignedCommitment, ValidatorSet, + VersionedFinalityProof, BEEFY_ENGINE_ID, KEY_TYPE as BeefyKeyType, +}; +use futures::{future, stream::FuturesUnordered, Future, StreamExt}; +use parking_lot::Mutex; +use sc_client_api::{Backend as BackendT, BlockchainEvents, FinalityNotifications, HeaderBackend}; +use sc_consensus::{ + BlockImport, BlockImportParams, BoxJustificationImport, ForkChoiceStrategy, ImportResult, + ImportedAux, +}; +use sc_network::{config::RequestResponseConfig, ProtocolName}; +use sc_network_test::{ + Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient, + PeersFullClient, TestNetFactory, WithRuntime, +}; +use sc_utils::notification::NotificationReceiver; +use serde::{Deserialize, Serialize}; +use sp_api::{ApiRef, ProvideRuntimeApi}; +use sp_consensus::BlockOrigin; +use sp_core::H256; +use sp_keystore::{testing::KeyStore as TestKeystore, SyncCryptoStore, SyncCryptoStorePtr}; +use sp_mmr_primitives::{EncodableOpaqueLeaf, Error as MmrError, MmrApi, Proof}; +use sp_runtime::{ + codec::Encode, + generic::BlockId, + traits::{Header as HeaderT, NumberFor}, + BuildStorage, DigestItem, Justifications, Storage, +}; +use std::{collections::HashMap, marker::PhantomData, sync::Arc, task::Poll}; +use substrate_test_runtime_client::{runtime::Header, ClientExt}; +use tokio::{ + runtime::{Handle, Runtime}, + time::Duration, +}; + +const GENESIS_HASH: H256 = H256::zero(); +fn beefy_gossip_proto_name() -> ProtocolName { + gossip_protocol_name(GENESIS_HASH, None) +} + +const GOOD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0xbf); +const BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x42); + +type BeefyBlockImport = crate::BeefyBlockImport< + Block, + substrate_test_runtime_client::Backend, + two_validators::TestApi, + BlockImportAdapter>, +>; + +pub(crate) type BeefyValidatorSet = ValidatorSet; +pub(crate) type BeefyPeer = Peer; + +#[derive(Debug, Serialize, Deserialize)] +struct Genesis(std::collections::BTreeMap); +impl BuildStorage for Genesis { + fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { + storage + .top + .extend(self.0.iter().map(|(a, b)| (a.clone().into_bytes(), b.clone().into_bytes()))); + Ok(()) + } +} + +#[derive(Default)] +pub(crate) struct PeerData { + pub(crate) beefy_rpc_links: Mutex>>, + pub(crate) beefy_voter_links: Mutex>>, + pub(crate) beefy_justif_req_handler: + Mutex>>, +} + +pub(crate) struct BeefyTestNet { + rt_handle: Handle, + peers: Vec, +} + +impl WithRuntime for BeefyTestNet { + fn with_runtime(rt_handle: Handle) -> Self { + BeefyTestNet { rt_handle, peers: Vec::new() } + } + fn rt_handle(&self) -> &Handle { + &self.rt_handle + } +} + +impl BeefyTestNet { + pub(crate) fn new(rt_handle: Handle, n_authority: usize) -> Self { + let mut net = BeefyTestNet::with_runtime(rt_handle); + + for i in 0..n_authority { + let (rx, cfg) = on_demand_justifications_protocol_config(GENESIS_HASH, None); + let justif_protocol_name = cfg.name.clone(); + + net.add_authority_peer(vec![cfg]); + + let client = net.peers[i].client().as_client(); + let justif_handler = BeefyJustifsRequestHandler { + request_receiver: rx, + justif_protocol_name, + client, + _block: PhantomData, + }; + *net.peers[i].data.beefy_justif_req_handler.lock() = Some(justif_handler); + } + net + } + + pub(crate) fn add_authority_peer(&mut self, req_resp_cfgs: Vec) { + self.add_full_peer_with_config(FullPeerConfig { + notifications_protocols: vec![beefy_gossip_proto_name()], + request_response_protocols: req_resp_cfgs, + is_authority: true, + ..Default::default() + }); + } + + pub(crate) fn generate_blocks_and_sync( + &mut self, + count: usize, + session_length: u64, + validator_set: &BeefyValidatorSet, + include_mmr_digest: bool, + runtime: &mut Runtime, + ) { + self.peer(0).generate_blocks(count, BlockOrigin::File, |builder| { + let mut block = builder.build().unwrap().block; + + if include_mmr_digest { + let block_num = *block.header.number(); + let num_byte = block_num.to_le_bytes().into_iter().next().unwrap(); + let mmr_root = MmrRootHash::repeat_byte(num_byte); + add_mmr_digest(&mut block.header, mmr_root); + } + + if *block.header.number() % session_length == 0 { + add_auth_change_digest(&mut block.header, validator_set.clone()); + } + + block + }); + runtime.block_on(self.wait_until_sync()); + } +} + +impl TestNetFactory for BeefyTestNet { + type Verifier = PassThroughVerifier; + type BlockImport = BeefyBlockImport; + type PeerData = PeerData; + + fn make_verifier(&self, _client: PeersClient, _: &PeerData) -> Self::Verifier { + PassThroughVerifier::new(false) // use non-instant finality. + } + + fn make_block_import( + &self, + client: PeersClient, + ) -> ( + BlockImportAdapter, + Option>, + Self::PeerData, + ) { + let inner = BlockImportAdapter::new(client.clone()); + let (block_import, voter_links, rpc_links) = beefy_block_import_and_links( + inner, + client.as_backend(), + Arc::new(two_validators::TestApi {}), + ); + let peer_data = PeerData { + beefy_rpc_links: Mutex::new(Some(rpc_links)), + beefy_voter_links: Mutex::new(Some(voter_links)), + ..Default::default() + }; + (BlockImportAdapter::new(block_import), None, peer_data) + } + + fn peer(&mut self, i: usize) -> &mut BeefyPeer { + &mut self.peers[i] + } + + fn peers(&self) -> &Vec { + &self.peers + } + + fn mut_peers)>(&mut self, closure: F) { + closure(&mut self.peers); + } + + fn add_full_peer(&mut self) { + // `add_authority_peer()` used instead. + unimplemented!() + } +} + +macro_rules! create_test_api { + ( $api_name:ident, mmr_root: $mmr_root:expr, $($inits:expr),+ ) => { + pub(crate) mod $api_name { + use super::*; + + #[derive(Clone, Default)] + pub(crate) struct TestApi {} + + // compiler gets confused and warns us about unused inner + #[allow(dead_code)] + pub(crate) struct RuntimeApi { + inner: TestApi, + } + + impl ProvideRuntimeApi for TestApi { + type Api = RuntimeApi; + fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { + RuntimeApi { inner: self.clone() }.into() + } + } + sp_api::mock_impl_runtime_apis! { + impl BeefyApi for RuntimeApi { + fn validator_set() -> Option { + BeefyValidatorSet::new(make_beefy_ids(&[$($inits),+]), 0) + } + } + + impl MmrApi> for RuntimeApi { + fn mmr_root() -> Result { + Ok($mmr_root) + } + + fn generate_proof( + _block_numbers: Vec, + _best_known_block_number: Option + ) -> Result<(Vec, Proof), MmrError> { + unimplemented!() + } + + fn verify_proof(_leaves: Vec, _proof: Proof) -> Result<(), MmrError> { + unimplemented!() + } + + fn verify_proof_stateless( + _root: MmrRootHash, + _leaves: Vec, + _proof: Proof + ) -> Result<(), MmrError> { + unimplemented!() + } + } + } + } + }; +} + +create_test_api!(two_validators, mmr_root: GOOD_MMR_ROOT, BeefyKeyring::Alice, BeefyKeyring::Bob); +create_test_api!( + four_validators, + mmr_root: GOOD_MMR_ROOT, + BeefyKeyring::Alice, + BeefyKeyring::Bob, + BeefyKeyring::Charlie, + BeefyKeyring::Dave +); +create_test_api!( + bad_four_validators, + mmr_root: BAD_MMR_ROOT, + BeefyKeyring::Alice, + BeefyKeyring::Bob, + BeefyKeyring::Charlie, + BeefyKeyring::Dave +); + +fn add_mmr_digest(header: &mut Header, mmr_hash: MmrRootHash) { + header.digest_mut().push(DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::MmrRoot(mmr_hash).encode(), + )); +} + +fn add_auth_change_digest(header: &mut Header, new_auth_set: BeefyValidatorSet) { + header.digest_mut().push(DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::AuthoritiesChange(new_auth_set).encode(), + )); +} + +pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { + keys.iter().map(|&key| key.public().into()).collect() +} + +pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStorePtr { + let keystore = Arc::new(TestKeystore::new()); + SyncCryptoStore::ecdsa_generate_new(&*keystore, BeefyKeyType, Some(&authority.to_seed())) + .expect("Creates authority key"); + keystore +} + +fn voter_init_setup( + net: &mut BeefyTestNet, + finality: &mut futures::stream::Fuse>, +) -> sp_blockchain::Result> { + let backend = net.peer(0).client().as_backend(); + let api = Arc::new(crate::tests::two_validators::TestApi {}); + let known_peers = Arc::new(Mutex::new(KnownPeers::new())); + let gossip_validator = + Arc::new(crate::communication::gossip::GossipValidator::new(known_peers)); + let mut gossip_engine = sc_network_gossip::GossipEngine::new( + net.peer(0).network_service().clone(), + "/beefy/whatever", + gossip_validator, + None, + ); + let best_grandpa = + futures::executor::block_on(wait_for_runtime_pallet(&*api, &mut gossip_engine, finality)) + .unwrap(); + load_or_init_voter_state(&*backend, &*api, best_grandpa, 1) +} + +// Spawns beefy voters. Returns a future to spawn on the runtime. +fn initialize_beefy( + net: &mut BeefyTestNet, + peers: Vec<(usize, &BeefyKeyring, Arc)>, + min_block_delta: u32, +) -> impl Future +where + API: ProvideRuntimeApi + Default + Sync + Send, + API::Api: BeefyApi + MmrApi>, +{ + let tasks = FuturesUnordered::new(); + + for (peer_id, key, api) in peers.into_iter() { + let peer = &net.peers[peer_id]; + + let keystore = create_beefy_keystore(*key); + + let (_, _, peer_data) = net.make_block_import(peer.client().clone()); + let PeerData { beefy_rpc_links, beefy_voter_links, .. } = peer_data; + + let beefy_voter_links = beefy_voter_links.lock().take(); + *peer.data.beefy_rpc_links.lock() = beefy_rpc_links.lock().take(); + *peer.data.beefy_voter_links.lock() = beefy_voter_links.clone(); + + let on_demand_justif_handler = peer.data.beefy_justif_req_handler.lock().take().unwrap(); + + let network_params = crate::BeefyNetworkParams { + network: peer.network_service().clone(), + gossip_protocol_name: beefy_gossip_proto_name(), + justifications_protocol_name: on_demand_justif_handler.protocol_name(), + _phantom: PhantomData, + }; + let payload_provider = MmrRootProvider::new(api.clone()); + + let beefy_params = crate::BeefyParams { + client: peer.client().as_client(), + backend: peer.client().as_backend(), + payload_provider, + runtime: api.clone(), + key_store: Some(keystore), + network_params, + links: beefy_voter_links.unwrap(), + min_block_delta, + prometheus_registry: None, + on_demand_justifications_handler: on_demand_justif_handler, + }; + let task = crate::start_beefy_gadget::<_, _, _, _, _, _>(beefy_params); + + fn assert_send(_: &T) {} + assert_send(&task); + tasks.push(task); + } + + tasks.for_each(|_| async move {}) +} + +fn block_until(future: impl Future + Unpin, net: &Arc>, runtime: &mut Runtime) { + let drive_to_completion = futures::future::poll_fn(|cx| { + net.lock().poll(cx); + Poll::<()>::Pending + }); + runtime.block_on(future::select(future, drive_to_completion)); +} + +fn run_for(duration: Duration, net: &Arc>, runtime: &mut Runtime) { + let sleep = runtime.spawn(async move { tokio::time::sleep(duration).await }); + block_until(sleep, net, runtime); +} + +pub(crate) fn get_beefy_streams( + net: &mut BeefyTestNet, + // peer index and key + peers: impl Iterator, +) -> (Vec>, Vec>>) +{ + let mut best_block_streams = Vec::new(); + let mut versioned_finality_proof_streams = Vec::new(); + peers.for_each(|(index, _)| { + let beefy_rpc_links = net.peer(index).data.beefy_rpc_links.lock().clone().unwrap(); + let BeefyRPCLinks { from_voter_justif_stream, from_voter_best_beefy_stream } = + beefy_rpc_links; + best_block_streams.push(from_voter_best_beefy_stream.subscribe()); + versioned_finality_proof_streams.push(from_voter_justif_stream.subscribe()); + }); + (best_block_streams, versioned_finality_proof_streams) +} + +fn wait_for_best_beefy_blocks( + streams: Vec>, + net: &Arc>, + runtime: &mut Runtime, + expected_beefy_blocks: &[u64], +) { + let mut wait_for = Vec::new(); + let len = expected_beefy_blocks.len(); + streams.into_iter().enumerate().for_each(|(i, stream)| { + let mut expected = expected_beefy_blocks.iter(); + wait_for.push(Box::pin(stream.take(len).for_each(move |best_beefy_hash| { + let expected = expected.next(); + async move { + let block_id = BlockId::hash(best_beefy_hash); + let header = + net.lock().peer(i).client().as_client().expect_header(block_id).unwrap(); + let best_beefy = *header.number(); + + assert_eq!(expected, Some(best_beefy).as_ref()); + } + }))); + }); + let wait_for = futures::future::join_all(wait_for); + block_until(wait_for, net, runtime); +} + +fn wait_for_beefy_signed_commitments( + streams: Vec>>, + net: &Arc>, + runtime: &mut Runtime, + expected_commitment_block_nums: &[u64], +) { + let mut wait_for = Vec::new(); + let len = expected_commitment_block_nums.len(); + streams.into_iter().for_each(|stream| { + let mut expected = expected_commitment_block_nums.iter(); + wait_for.push(Box::pin(stream.take(len).for_each(move |versioned_finality_proof| { + let expected = expected.next(); + async move { + let signed_commitment = match versioned_finality_proof { + beefy_primitives::VersionedFinalityProof::V1(sc) => sc, + }; + let commitment_block_num = signed_commitment.commitment.block_number; + assert_eq!(expected, Some(commitment_block_num).as_ref()); + // TODO: also verify commitment payload, validator set id, and signatures. + } + }))); + }); + let wait_for = futures::future::join_all(wait_for); + block_until(wait_for, net, runtime); +} + +fn streams_empty_after_timeout( + streams: Vec>, + net: &Arc>, + runtime: &mut Runtime, + timeout: Option, +) where + T: std::fmt::Debug, + T: std::cmp::PartialEq, +{ + if let Some(timeout) = timeout { + run_for(timeout, net, runtime); + } + streams.into_iter().for_each(|mut stream| { + runtime.block_on(future::poll_fn(move |cx| { + assert_eq!(stream.poll_next_unpin(cx), Poll::Pending); + Poll::Ready(()) + })); + }); +} + +fn finalize_block_and_wait_for_beefy( + net: &Arc>, + // peer index and key + peers: impl Iterator + Clone, + runtime: &mut Runtime, + finalize_targets: &[u64], + expected_beefy: &[u64], +) { + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); + + for block in finalize_targets { + peers.clone().for_each(|(index, _)| { + let client = net.lock().peer(index).client().as_client(); + let finalize = client.expect_block_hash_from_id(&BlockId::number(*block)).unwrap(); + client.finalize_block(finalize, None).unwrap(); + }) + } + + if expected_beefy.is_empty() { + // run for quarter second then verify no new best beefy block available + let timeout = Some(Duration::from_millis(250)); + streams_empty_after_timeout(best_blocks, &net, runtime, timeout); + streams_empty_after_timeout(versioned_finality_proof, &net, runtime, None); + } else { + // run until expected beefy blocks are received + wait_for_best_beefy_blocks(best_blocks, &net, runtime, expected_beefy); + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, runtime, expected_beefy); + } +} + +#[test] +fn beefy_finalizing_blocks() { + sp_tracing::try_init_simple(); + + let mut runtime = Runtime::new().unwrap(); + let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); + let session_len = 10; + let min_block_delta = 4; + + let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); + + let api = Arc::new(two_validators::TestApi {}); + let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); + runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); + + // push 42 blocks including `AuthorityChange` digests every 10 blocks. + net.generate_blocks_and_sync(42, session_len, &validator_set, true, &mut runtime); + + let net = Arc::new(Mutex::new(net)); + + // Minimum BEEFY block delta is 4. + + let peers = peers.into_iter().enumerate(); + // finalize block #5 -> BEEFY should finalize #1 (mandatory) and #5 from diff-power-of-two rule. + finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[1, 5], &[1, 5]); + + // GRANDPA finalize #10 -> BEEFY finalize #10 (mandatory) + finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[10], &[10]); + + // GRANDPA finalize #18 -> BEEFY finalize #14, then #18 (diff-power-of-two rule) + finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[18], &[14, 18]); + + // GRANDPA finalize #20 -> BEEFY finalize #20 (mandatory) + finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[20], &[20]); + + // GRANDPA finalize #21 -> BEEFY finalize nothing (yet) because min delta is 4 + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[21], &[]); +} + +#[test] +fn lagging_validators() { + sp_tracing::try_init_simple(); + + let mut runtime = Runtime::new().unwrap(); + let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); + let session_len = 30; + let min_block_delta = 1; + + let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); + let api = Arc::new(two_validators::TestApi {}); + let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); + runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); + + // push 62 blocks including `AuthorityChange` digests every 30 blocks. + net.generate_blocks_and_sync(62, session_len, &validator_set, true, &mut runtime); + + let net = Arc::new(Mutex::new(net)); + + let peers = peers.into_iter().enumerate(); + // finalize block #15 -> BEEFY should finalize #1 (mandatory) and #9, #13, #14, #15 from + // diff-power-of-two rule. + finalize_block_and_wait_for_beefy( + &net, + peers.clone(), + &mut runtime, + &[1, 15], + &[1, 9, 13, 14, 15], + ); + + // Alice finalizes #25, Bob lags behind + let finalize = net + .lock() + .peer(0) + .client() + .as_client() + .expect_block_hash_from_id(&BlockId::number(25)) + .unwrap(); + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); + net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); + // verify nothing gets finalized by BEEFY + let timeout = Some(Duration::from_millis(250)); + streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); + streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); + + // Bob catches up and also finalizes #25 + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); + net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); + // expected beefy finalizes block #17 from diff-power-of-two + wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[23, 24, 25]); + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[23, 24, 25]); + + // Both finalize #30 (mandatory session) and #32 -> BEEFY finalize #30 (mandatory), #31, #32 + finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[30, 32], &[30, 31, 32]); + + // Verify that session-boundary votes get buffered by client and only processed once + // session-boundary block is GRANDPA-finalized (this guarantees authenticity for the new session + // validator set). + + // Alice finalizes session-boundary mandatory block #60, Bob lags behind + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); + let finalize = net + .lock() + .peer(0) + .client() + .as_client() + .expect_block_hash_from_id(&BlockId::number(60)) + .unwrap(); + net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); + // verify nothing gets finalized by BEEFY + let timeout = Some(Duration::from_millis(250)); + streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); + streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); + + // Bob catches up and also finalizes #60 (and should have buffered Alice's vote on #60) + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); + net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); + // verify beefy skips intermediary votes, and successfully finalizes mandatory block #60 + wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[60]); + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[60]); +} + +#[test] +fn correct_beefy_payload() { + sp_tracing::try_init_simple(); + + let mut runtime = Runtime::new().unwrap(); + let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie, BeefyKeyring::Dave]; + let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); + let session_len = 20; + let min_block_delta = 2; + + let mut net = BeefyTestNet::new(runtime.handle().clone(), 4); + + // Alice, Bob, Charlie will vote on good payloads + let good_api = Arc::new(four_validators::TestApi {}); + let good_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie] + .iter() + .enumerate() + .map(|(id, key)| (id, key, good_api.clone())) + .collect(); + runtime.spawn(initialize_beefy(&mut net, good_peers, min_block_delta)); + + // Dave will vote on bad mmr roots + let bad_api = Arc::new(bad_four_validators::TestApi {}); + let bad_peers = vec![(3, &BeefyKeyring::Dave, bad_api)]; + runtime.spawn(initialize_beefy(&mut net, bad_peers, min_block_delta)); + + // push 12 blocks + net.generate_blocks_and_sync(12, session_len, &validator_set, false, &mut runtime); + + let net = Arc::new(Mutex::new(net)); + let peers = peers.into_iter().enumerate(); + // with 3 good voters and 1 bad one, consensus should happen and best blocks produced. + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[1, 10], &[1, 9]); + + let (best_blocks, versioned_finality_proof) = + get_beefy_streams(&mut net.lock(), [(0, BeefyKeyring::Alice)].into_iter()); + + // now 2 good validators and 1 bad one are voting + let hashof11 = net + .lock() + .peer(0) + .client() + .as_client() + .expect_block_hash_from_id(&BlockId::number(11)) + .unwrap(); + net.lock().peer(0).client().as_client().finalize_block(hashof11, None).unwrap(); + net.lock().peer(1).client().as_client().finalize_block(hashof11, None).unwrap(); + net.lock().peer(3).client().as_client().finalize_block(hashof11, None).unwrap(); + + // verify consensus is _not_ reached + let timeout = Some(Duration::from_millis(250)); + streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); + streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); + + // 3rd good validator catches up and votes as well + let (best_blocks, versioned_finality_proof) = + get_beefy_streams(&mut net.lock(), [(0, BeefyKeyring::Alice)].into_iter()); + net.lock().peer(2).client().as_client().finalize_block(hashof11, None).unwrap(); + + // verify consensus is reached + wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[11]); + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[11]); +} + +#[test] +fn beefy_importing_blocks() { + use futures::{future::poll_fn, task::Poll}; + use sc_block_builder::BlockBuilderProvider; + use sc_client_api::BlockBackend; + + sp_tracing::try_init_simple(); + + let runtime = Runtime::new().unwrap(); + + let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); + + let client = net.peer(0).client().clone(); + let (mut block_import, _, peer_data) = net.make_block_import(client.clone()); + let PeerData { beefy_voter_links, .. } = peer_data; + let justif_stream = beefy_voter_links.lock().take().unwrap().from_block_import_justif_stream; + + let params = |block: Block, justifications: Option| { + let mut import = BlockImportParams::new(BlockOrigin::File, block.header); + import.justifications = justifications; + import.body = Some(block.extrinsics); + import.finalized = true; + import.fork_choice = Some(ForkChoiceStrategy::LongestChain); + import + }; + + let full_client = client.as_client(); + let parent_id = BlockId::Number(0); + let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); + let block = builder.build().unwrap().block; + let hashof1 = block.header.hash(); + + // Import without justifications. + let mut justif_recv = justif_stream.subscribe(); + assert_eq!( + runtime + .block_on(block_import.import_block(params(block.clone(), None), HashMap::new())) + .unwrap(), + ImportResult::Imported(ImportedAux { is_new_best: true, ..Default::default() }), + ); + assert_eq!( + runtime + .block_on(block_import.import_block(params(block, None), HashMap::new())) + .unwrap(), + ImportResult::AlreadyInChain + ); + // Verify no BEEFY justifications present: + { + // none in backend, + assert_eq!( + full_client + .justifications(hashof1) + .unwrap() + .and_then(|j| j.get(BEEFY_ENGINE_ID).cloned()), + None + ); + // and none sent to BEEFY worker. + runtime.block_on(poll_fn(move |cx| { + assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); + Poll::Ready(()) + })); + } + + // Import with valid justification. + let parent_id = BlockId::Number(1); + let block_num = 2; + let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys); + let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); + let encoded = versioned_proof.encode(); + let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); + + let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); + let block = builder.build().unwrap().block; + let hashof2 = block.header.hash(); + let mut justif_recv = justif_stream.subscribe(); + assert_eq!( + runtime + .block_on(block_import.import_block(params(block, justif), HashMap::new())) + .unwrap(), + ImportResult::Imported(ImportedAux { + bad_justification: false, + is_new_best: true, + ..Default::default() + }), + ); + // Verify BEEFY justification successfully imported: + { + // still not in backend (worker is responsible for appending to backend), + assert_eq!( + full_client + .justifications(hashof2) + .unwrap() + .and_then(|j| j.get(BEEFY_ENGINE_ID).cloned()), + None + ); + // but sent to BEEFY worker + // (worker will append it to backend when all previous mandatory justifs are there as well). + runtime.block_on(poll_fn(move |cx| { + match justif_recv.poll_next_unpin(cx) { + Poll::Ready(Some(_justification)) => (), + v => panic!("unexpected value: {:?}", v), + } + Poll::Ready(()) + })); + } + + // Import with invalid justification (incorrect validator set). + let parent_id = BlockId::Number(2); + let block_num = 3; + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); + let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys); + let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); + let encoded = versioned_proof.encode(); + let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); + + let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); + let block = builder.build().unwrap().block; + let hashof3 = block.header.hash(); + let mut justif_recv = justif_stream.subscribe(); + assert_eq!( + runtime + .block_on(block_import.import_block(params(block, justif), HashMap::new())) + .unwrap(), + ImportResult::Imported(ImportedAux { + // Still `false` because we don't want to fail import on bad BEEFY justifications. + bad_justification: false, + is_new_best: true, + ..Default::default() + }), + ); + // Verify bad BEEFY justifications was not imported: + { + // none in backend, + assert_eq!( + full_client + .justifications(hashof3) + .unwrap() + .and_then(|j| j.get(BEEFY_ENGINE_ID).cloned()), + None + ); + // and none sent to BEEFY worker. + runtime.block_on(poll_fn(move |cx| { + assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); + Poll::Ready(()) + })); + } +} + +#[test] +fn voter_initialization() { + sp_tracing::try_init_simple(); + // Regression test for voter initialization where finality notifications were dropped + // after waiting for BEEFY pallet availability. + + let mut runtime = Runtime::new().unwrap(); + let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); + let session_len = 5; + // Should vote on all mandatory blocks no matter the `min_block_delta`. + let min_block_delta = 10; + + let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); + let api = Arc::new(two_validators::TestApi {}); + let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); + runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); + + // push 26 blocks + net.generate_blocks_and_sync(26, session_len, &validator_set, false, &mut runtime); + let net = Arc::new(Mutex::new(net)); + + // Finalize multiple blocks at once to get a burst of finality notifications right from start. + // Need to finalize at least one block in each session, choose randomly. + // Expect voters to pick up all of them and BEEFY-finalize the mandatory blocks of each session. + finalize_block_and_wait_for_beefy( + &net, + peers.into_iter().enumerate(), + &mut runtime, + &[1, 6, 10, 17, 24, 26], + &[1, 5, 10, 15, 20, 25], + ); +} + +#[test] +fn on_demand_beefy_justification_sync() { + sp_tracing::try_init_simple(); + + let mut runtime = Runtime::new().unwrap(); + let all_peers = + [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie, BeefyKeyring::Dave]; + let validator_set = ValidatorSet::new(make_beefy_ids(&all_peers), 0).unwrap(); + let session_len = 5; + let min_block_delta = 5; + + let mut net = BeefyTestNet::new(runtime.handle().clone(), 4); + + // Alice, Bob, Charlie start first and make progress through voting. + let api = Arc::new(four_validators::TestApi {}); + let fast_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]; + let voting_peers = + fast_peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); + runtime.spawn(initialize_beefy(&mut net, voting_peers, min_block_delta)); + + // Dave will start late and have to catch up using on-demand justification requests (since + // in this test there is no block import queue to automatically import justifications). + let dave = vec![(3, &BeefyKeyring::Dave, api)]; + // Instantiate but don't run Dave, yet. + let dave_task = initialize_beefy(&mut net, dave, min_block_delta); + let dave_index = 3; + + // push 30 blocks + net.generate_blocks_and_sync(30, session_len, &validator_set, false, &mut runtime); + + let fast_peers = fast_peers.into_iter().enumerate(); + let net = Arc::new(Mutex::new(net)); + // With 3 active voters and one inactive, consensus should happen and blocks BEEFY-finalized. + // Need to finalize at least one block in each session, choose randomly. + finalize_block_and_wait_for_beefy( + &net, + fast_peers.clone(), + &mut runtime, + &[1, 6, 10, 17, 24], + &[1, 5, 10, 15, 20], + ); + + // Spawn Dave, he's now way behind voting and can only catch up through on-demand justif sync. + runtime.spawn(dave_task); + // give Dave a chance to spawn and init. + run_for(Duration::from_millis(400), &net, &mut runtime); + + let (dave_best_blocks, _) = + get_beefy_streams(&mut net.lock(), [(dave_index, BeefyKeyring::Dave)].into_iter()); + let client = net.lock().peer(dave_index).client().as_client(); + let hashof1 = client.expect_block_hash_from_id(&BlockId::number(1)).unwrap(); + client.finalize_block(hashof1, None).unwrap(); + // Give Dave task some cpu cycles to process the finality notification, + run_for(Duration::from_millis(100), &net, &mut runtime); + // freshly spun up Dave now needs to listen for gossip to figure out the state of his peers. + + // Have the other peers do some gossip so Dave finds out about their progress. + finalize_block_and_wait_for_beefy(&net, fast_peers, &mut runtime, &[25], &[25]); + + // Now verify Dave successfully finalized #1 (through on-demand justification request). + wait_for_best_beefy_blocks(dave_best_blocks, &net, &mut runtime, &[1]); + + // Give Dave all tasks some cpu cycles to burn through their events queues, + run_for(Duration::from_millis(100), &net, &mut runtime); + // then verify Dave catches up through on-demand justification requests. + finalize_block_and_wait_for_beefy( + &net, + [(dave_index, BeefyKeyring::Dave)].into_iter(), + &mut runtime, + &[6, 10, 17, 24, 26], + &[5, 10, 15, 20, 25], + ); + + let all_peers = all_peers.into_iter().enumerate(); + // Now that Dave has caught up, sanity check voting works for all of them. + finalize_block_and_wait_for_beefy(&net, all_peers, &mut runtime, &[30], &[30]); +} + +#[test] +fn should_initialize_voter_at_genesis() { + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let backend = net.peer(0).client().as_backend(); + + // push 15 blocks with `AuthorityChange` digests every 10 blocks + net.generate_blocks_and_sync(15, 10, &validator_set, false, &mut runtime); + + let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); + + // finalize 13 without justifications + let hashof13 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(13)).unwrap(); + net.peer(0).client().as_client().finalize_block(hashof13, None).unwrap(); + + // load persistent state - nothing in DB, should init at session boundary + let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + + // Test initialization at session boundary. + // verify voter initialized with two sessions starting at blocks 1 and 10 + let sessions = persisted_state.voting_oracle().sessions(); + assert_eq!(sessions.len(), 2); + assert_eq!(sessions[0].session_start(), 1); + assert_eq!(sessions[1].session_start(), 10); + let rounds = persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), 1); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + + // verify next vote target is mandatory block 1 + assert_eq!(persisted_state.best_beefy_block(), 0); + assert_eq!(persisted_state.best_grandpa_block(), 13); + assert_eq!( + persisted_state + .voting_oracle() + .voting_target(persisted_state.best_beefy_block(), 13), + Some(1) + ); + + // verify state also saved to db + assert!(verify_persisted_version(&*backend)); + let state = load_persistent(&*backend).unwrap().unwrap(); + assert_eq!(state, persisted_state); +} + +#[test] +fn should_initialize_voter_when_last_final_is_session_boundary() { + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let backend = net.peer(0).client().as_backend(); + + // push 15 blocks with `AuthorityChange` digests every 10 blocks + net.generate_blocks_and_sync(15, 10, &validator_set, false, &mut runtime); + + let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); + + // finalize 13 without justifications + let hashof13 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(13)).unwrap(); + net.peer(0).client().as_client().finalize_block(hashof13, None).unwrap(); + + // import/append BEEFY justification for session boundary block 10 + let commitment = Commitment { + payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), + block_number: 10, + validator_set_id: validator_set.id(), + }; + let justif = VersionedFinalityProof::<_, Signature>::V1(SignedCommitment { + commitment, + signatures: vec![None], + }); + let hashof10 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(10)).unwrap(); + backend + .append_justification(hashof10, (BEEFY_ENGINE_ID, justif.encode())) + .unwrap(); + + // Test corner-case where session boundary == last beefy finalized, + // expect rounds initialized at last beefy finalized 10. + + // load persistent state - nothing in DB, should init at session boundary + let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + + // verify voter initialized with single session starting at block 10 + assert_eq!(persisted_state.voting_oracle().sessions().len(), 1); + let rounds = persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), 10); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + + // verify block 10 is correctly marked as finalized + assert_eq!(persisted_state.best_beefy_block(), 10); + assert_eq!(persisted_state.best_grandpa_block(), 13); + // verify next vote target is diff-power-of-two block 12 + assert_eq!( + persisted_state + .voting_oracle() + .voting_target(persisted_state.best_beefy_block(), 13), + Some(12) + ); + + // verify state also saved to db + assert!(verify_persisted_version(&*backend)); + let state = load_persistent(&*backend).unwrap().unwrap(); + assert_eq!(state, persisted_state); +} + +#[test] +fn should_initialize_voter_at_latest_finalized() { + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let backend = net.peer(0).client().as_backend(); + + // push 15 blocks with `AuthorityChange` digests every 10 blocks + net.generate_blocks_and_sync(15, 10, &validator_set, false, &mut runtime); + + let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); + + // finalize 13 without justifications + let hashof13 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(13)).unwrap(); + net.peer(0).client().as_client().finalize_block(hashof13, None).unwrap(); + + // import/append BEEFY justification for block 12 + let commitment = Commitment { + payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), + block_number: 12, + validator_set_id: validator_set.id(), + }; + let justif = VersionedFinalityProof::<_, Signature>::V1(SignedCommitment { + commitment, + signatures: vec![None], + }); + let hashof12 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(12)).unwrap(); + backend + .append_justification(hashof12, (BEEFY_ENGINE_ID, justif.encode())) + .unwrap(); + + // Test initialization at last BEEFY finalized. + + // load persistent state - nothing in DB, should init at last BEEFY finalized + let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + + // verify voter initialized with single session starting at block 12 + assert_eq!(persisted_state.voting_oracle().sessions().len(), 1); + let rounds = persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), 12); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + + // verify next vote target is 13 + assert_eq!(persisted_state.best_beefy_block(), 12); + assert_eq!(persisted_state.best_grandpa_block(), 13); + assert_eq!( + persisted_state + .voting_oracle() + .voting_target(persisted_state.best_beefy_block(), 13), + Some(13) + ); + + // verify state also saved to db + assert!(verify_persisted_version(&*backend)); + let state = load_persistent(&*backend).unwrap().unwrap(); + assert_eq!(state, persisted_state); +} diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs new file mode 100644 index 0000000000000..bba3563a8f70e --- /dev/null +++ b/client/beefy/src/worker.rs @@ -0,0 +1,1523 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{ + communication::{ + gossip::{topic, GossipValidator}, + request_response::outgoing_requests_engine::OnDemandJustificationsEngine, + }, + error::Error, + justification::BeefyVersionedFinalityProof, + keystore::BeefyKeystore, + metric_inc, metric_set, + metrics::Metrics, + round::Rounds, + BeefyVoterLinks, +}; +use beefy_primitives::{ + crypto::{AuthorityId, Signature}, + Commitment, ConsensusLog, Payload, PayloadProvider, SignedCommitment, ValidatorSet, + VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, +}; +use codec::{Codec, Decode, Encode}; +use futures::{stream::Fuse, FutureExt, StreamExt}; +use log::{debug, error, info, log_enabled, trace, warn}; +use sc_client_api::{Backend, FinalityNotification, FinalityNotifications, HeaderBackend}; +use sc_network_common::service::{NetworkEventStream, NetworkRequest}; +use sc_network_gossip::GossipEngine; +use sc_utils::notification::NotificationReceiver; +use sp_api::BlockId; +use sp_arithmetic::traits::{AtLeast32Bit, Saturating}; +use sp_consensus::SyncOracle; +use sp_runtime::{ + generic::OpaqueDigestItemId, + traits::{Block, ConstU32, Header, NumberFor, Zero}, + BoundedVec, SaturatedConversion, +}; +use std::{ + collections::{BTreeMap, BTreeSet, VecDeque}, + fmt::Debug, + marker::PhantomData, + sync::Arc, +}; +/// Bound for the number of buffered future voting rounds. +const MAX_BUFFERED_VOTE_ROUNDS: usize = 600; +/// Bound for the number of buffered votes per round number. +const MAX_BUFFERED_VOTES_PER_ROUND: u32 = 1000; +/// Bound for the number of pending justifications - use 2400 - the max number +/// of justifications possible in a single session. +const MAX_BUFFERED_JUSTIFICATIONS: usize = 2400; + +pub(crate) enum RoundAction { + Drop, + Process, + Enqueue, +} + +/// Responsible for the voting strategy. +/// It chooses which incoming votes to accept and which votes to generate. +/// Keeps track of voting seen for current and future rounds. +#[derive(Debug, Decode, Encode, PartialEq)] +pub(crate) struct VoterOracle { + /// Queue of known sessions. Keeps track of voting rounds (block numbers) within each session. + /// + /// There are three voter states coresponding to three queue states: + /// 1. voter uninitialized: queue empty, + /// 2. up-to-date - all mandatory blocks leading up to current GRANDPA finalized: + /// queue has ONE element, the 'current session' where `mandatory_done == true`, + /// 3. lagging behind GRANDPA: queue has [1, N] elements, where all `mandatory_done == false`. + /// In this state, everytime a session gets its mandatory block BEEFY finalized, it's + /// popped off the queue, eventually getting to state `2. up-to-date`. + sessions: VecDeque>, + /// Min delta in block numbers between two blocks, BEEFY should vote on. + min_block_delta: u32, +} + +impl VoterOracle { + /// Verify provided `sessions` satisfies requirements, then build `VoterOracle`. + pub fn checked_new( + sessions: VecDeque>, + min_block_delta: u32, + ) -> Option { + let mut prev_start = Zero::zero(); + let mut prev_validator_id = None; + // verifies the + let mut validate = || -> bool { + if sessions.is_empty() { + return false + } + for (idx, session) in sessions.iter().enumerate() { + if session.validators().is_empty() { + return false + } + if session.session_start() <= prev_start { + return false + } + #[cfg(not(test))] + if let Some(prev_id) = prev_validator_id { + if session.validator_set_id() <= prev_id { + return false + } + } + if idx != 0 && session.mandatory_done() { + return false + } + prev_start = session.session_start(); + prev_validator_id = Some(session.validator_set_id()); + } + true + }; + if validate() { + Some(VoterOracle { + sessions, + // Always target at least one block better than current best beefy. + min_block_delta: min_block_delta.max(1), + }) + } else { + error!(target: "beefy", "🥩 Invalid sessions queue: {:?}.", sessions); + None + } + } + + // Return reference to rounds pertaining to first session in the queue. + // Voting will always happen at the head of the queue. + fn active_rounds(&self) -> Option<&Rounds> { + self.sessions.front() + } + + // Return mutable reference to rounds pertaining to first session in the queue. + // Voting will always happen at the head of the queue. + fn active_rounds_mut(&mut self) -> Option<&mut Rounds> { + self.sessions.front_mut() + } + + // Prune the sessions queue to keep the Oracle in one of the expected three states. + // + // To be called on each BEEFY finality and on each new rounds/session addition. + fn try_prune(&mut self) { + if self.sessions.len() > 1 { + // when there's multiple sessions, only keep the `!mandatory_done()` ones. + self.sessions.retain(|s| !s.mandatory_done()) + } + } + + /// Add new observed session to the Oracle. + pub fn add_session(&mut self, rounds: Rounds) { + self.sessions.push_back(rounds); + // Once we add a new session we can drop/prune previous session if it's been finalized. + self.try_prune(); + } + + /// Finalize a particular block. + pub fn finalize(&mut self, block: NumberFor) -> Result<(), Error> { + // Conclude voting round for this block. + self.active_rounds_mut().ok_or(Error::UninitSession)?.conclude(block); + // Prune any now "finalized" sessions from queue. + self.try_prune(); + Ok(()) + } + + /// Return current pending mandatory block, if any, plus its active validator set. + pub fn mandatory_pending(&self) -> Option<(NumberFor, ValidatorSet)> { + self.sessions.front().and_then(|round| { + if round.mandatory_done() { + None + } else { + Some((round.session_start(), round.validator_set().clone())) + } + }) + } + + /// Return `(A, B)` tuple representing inclusive [A, B] interval of votes to accept. + pub fn accepted_interval( + &self, + best_grandpa: NumberFor, + ) -> Result<(NumberFor, NumberFor), Error> { + let rounds = self.sessions.front().ok_or(Error::UninitSession)?; + + if rounds.mandatory_done() { + // There's only one session active and its mandatory is done. + // Accept any GRANDPA finalized vote. + Ok((rounds.session_start(), best_grandpa.into())) + } else { + // There's at least one session with mandatory not done. + // Only accept votes for the mandatory block in the front of queue. + Ok((rounds.session_start(), rounds.session_start())) + } + } + + /// Utility function to quickly decide what to do for each round. + pub fn triage_round( + &self, + round: NumberFor, + best_grandpa: NumberFor, + ) -> Result { + let (start, end) = self.accepted_interval(best_grandpa)?; + if start <= round && round <= end { + Ok(RoundAction::Process) + } else if round > end { + Ok(RoundAction::Enqueue) + } else { + Ok(RoundAction::Drop) + } + } + + /// Return `Some(number)` if we should be voting on block `number`, + /// return `None` if there is no block we should vote on. + pub fn voting_target( + &self, + best_beefy: NumberFor, + best_grandpa: NumberFor, + ) -> Option> { + let rounds = if let Some(r) = self.sessions.front() { + r + } else { + debug!(target: "beefy", "🥩 No voting round started"); + return None + }; + + // `target` is guaranteed > `best_beefy` since `min_block_delta` is at least `1`. + let target = + vote_target(best_grandpa, best_beefy, rounds.session_start(), self.min_block_delta); + trace!( + target: "beefy", + "🥩 best beefy: #{:?}, best finalized: #{:?}, current_vote_target: {:?}", + best_beefy, + best_grandpa, + target + ); + target + } +} + +pub(crate) struct WorkerParams { + pub backend: Arc, + pub payload_provider: P, + pub network: N, + pub key_store: BeefyKeystore, + pub gossip_engine: GossipEngine, + pub gossip_validator: Arc>, + pub on_demand_justifications: OnDemandJustificationsEngine, + pub links: BeefyVoterLinks, + pub metrics: Option, + pub persisted_state: PersistedState, +} + +#[derive(Debug, Decode, Encode, PartialEq)] +pub(crate) struct PersistedState { + /// Best block we received a GRANDPA finality for. + best_grandpa_block_header: ::Header, + /// Best block a BEEFY voting round has been concluded for. + best_beefy_block: NumberFor, + /// Chooses which incoming votes to accept and which votes to generate. + /// Keeps track of voting seen for current and future rounds. + voting_oracle: VoterOracle, +} + +impl PersistedState { + pub fn checked_new( + grandpa_header: ::Header, + best_beefy: NumberFor, + sessions: VecDeque>, + min_block_delta: u32, + ) -> Option { + VoterOracle::checked_new(sessions, min_block_delta).map(|voting_oracle| PersistedState { + best_grandpa_block_header: grandpa_header, + best_beefy_block: best_beefy, + voting_oracle, + }) + } + + pub(crate) fn set_min_block_delta(&mut self, min_block_delta: u32) { + self.voting_oracle.min_block_delta = min_block_delta.max(1); + } + + pub(crate) fn set_best_grandpa(&mut self, best_grandpa: ::Header) { + self.best_grandpa_block_header = best_grandpa; + } +} + +/// A BEEFY worker plays the BEEFY protocol +pub(crate) struct BeefyWorker { + // utilities + backend: Arc, + payload_provider: P, + network: N, + key_store: BeefyKeystore, + + // communication + gossip_engine: GossipEngine, + gossip_validator: Arc>, + on_demand_justifications: OnDemandJustificationsEngine, + + // channels + /// Links between the block importer, the background voter and the RPC layer. + links: BeefyVoterLinks, + + // voter state + /// BEEFY client metrics. + metrics: Option, + /// Buffer holding votes for future processing. + pending_votes: BTreeMap< + NumberFor, + BoundedVec< + VoteMessage, AuthorityId, Signature>, + ConstU32, + >, + >, + /// Buffer holding justifications for future processing. + pending_justifications: BTreeMap, BeefyVersionedFinalityProof>, + /// Persisted voter state. + persisted_state: PersistedState, +} + +impl BeefyWorker +where + B: Block + Codec, + BE: Backend, + P: PayloadProvider, + N: NetworkEventStream + NetworkRequest + SyncOracle + Send + Sync + Clone + 'static, +{ + /// Return a new BEEFY worker instance. + /// + /// Note that a BEEFY worker is only fully functional if a corresponding + /// BEEFY pallet has been deployed on-chain. + /// + /// The BEEFY pallet is needed in order to keep track of the BEEFY authority set. + pub(crate) fn new(worker_params: WorkerParams) -> Self { + let WorkerParams { + backend, + payload_provider, + key_store, + network, + gossip_engine, + gossip_validator, + on_demand_justifications, + links, + metrics, + persisted_state, + } = worker_params; + + BeefyWorker { + backend, + payload_provider, + network, + key_store, + gossip_engine, + gossip_validator, + on_demand_justifications, + links, + metrics, + pending_votes: BTreeMap::new(), + pending_justifications: BTreeMap::new(), + persisted_state, + } + } + + fn best_grandpa_block(&self) -> NumberFor { + *self.persisted_state.best_grandpa_block_header.number() + } + + fn best_beefy_block(&self) -> NumberFor { + self.persisted_state.best_beefy_block + } + + fn voting_oracle(&self) -> &VoterOracle { + &self.persisted_state.voting_oracle + } + + fn active_rounds(&mut self) -> Option<&Rounds> { + self.persisted_state.voting_oracle.active_rounds() + } + + /// Verify `active` validator set for `block` against the key store + /// + /// We want to make sure that we have _at least one_ key in our keystore that + /// is part of the validator set, that's because if there are no local keys + /// then we can't perform our job as a validator. + /// + /// Note that for a non-authority node there will be no keystore, and we will + /// return an error and don't check. The error can usually be ignored. + fn verify_validator_set( + &self, + block: &NumberFor, + active: &ValidatorSet, + ) -> Result<(), Error> { + let active: BTreeSet<&AuthorityId> = active.validators().iter().collect(); + + let public_keys = self.key_store.public_keys()?; + let store: BTreeSet<&AuthorityId> = public_keys.iter().collect(); + + if store.intersection(&active).count() == 0 { + let msg = "no authority public key found in store".to_string(); + debug!(target: "beefy", "🥩 for block {:?} {}", block, msg); + Err(Error::Keystore(msg)) + } else { + Ok(()) + } + } + + /// Handle session changes by starting new voting round for mandatory blocks. + fn init_session_at( + &mut self, + validator_set: ValidatorSet, + new_session_start: NumberFor, + ) { + debug!(target: "beefy", "🥩 New active validator set: {:?}", validator_set); + + // BEEFY should finalize a mandatory block during each session. + if let Some(active_session) = self.active_rounds() { + if !active_session.mandatory_done() { + debug!( + target: "beefy", "🥩 New session {} while active session {} is still lagging.", + validator_set.id(), + active_session.validator_set_id(), + ); + metric_inc!(self, beefy_lagging_sessions); + } + } + + if log_enabled!(target: "beefy", log::Level::Debug) { + // verify the new validator set - only do it if we're also logging the warning + let _ = self.verify_validator_set(&new_session_start, &validator_set); + } + + let id = validator_set.id(); + self.persisted_state + .voting_oracle + .add_session(Rounds::new(new_session_start, validator_set)); + metric_set!(self, beefy_validator_set_id, id); + info!( + target: "beefy", + "🥩 New Rounds for validator set id: {:?} with session_start {:?}", + id, new_session_start + ); + } + + fn handle_finality_notification(&mut self, notification: &FinalityNotification) { + debug!(target: "beefy", "🥩 Finality notification: {:?}", notification); + let header = ¬ification.header; + + if *header.number() > self.best_grandpa_block() { + // update best GRANDPA finalized block we have seen + self.persisted_state.best_grandpa_block_header = header.clone(); + + // Check all (newly) finalized blocks for new session(s). + let backend = self.backend.clone(); + for header in notification + .tree_route + .iter() + .map(|hash| { + backend + .blockchain() + .expect_header(BlockId::hash(*hash)) + .expect("just finalized block should be available; qed.") + }) + .chain(std::iter::once(header.clone())) + { + if let Some(new_validator_set) = find_authorities_change::(&header) { + self.init_session_at(new_validator_set, *header.number()); + } + } + } + } + + /// Based on [VoterOracle] this vote is either processed here or enqueued for later. + fn triage_incoming_vote( + &mut self, + vote: VoteMessage, AuthorityId, Signature>, + ) -> Result<(), Error> { + let block_num = vote.commitment.block_number; + let best_grandpa = self.best_grandpa_block(); + match self.voting_oracle().triage_round(block_num, best_grandpa)? { + RoundAction::Process => self.handle_vote( + (vote.commitment.payload, vote.commitment.block_number), + (vote.id, vote.signature), + false, + )?, + RoundAction::Enqueue => { + debug!(target: "beefy", "🥩 Buffer vote for round: {:?}.", block_num); + if self.pending_votes.len() < MAX_BUFFERED_VOTE_ROUNDS { + let votes_vec = self.pending_votes.entry(block_num).or_default(); + if votes_vec.try_push(vote).is_err() { + warn!(target: "beefy", "🥩 Buffer vote dropped for round: {:?}", block_num) + } + } else { + error!(target: "beefy", "🥩 Buffer justification dropped for round: {:?}.", block_num); + } + }, + RoundAction::Drop => (), + }; + Ok(()) + } + + /// Based on [VoterOracle] this justification is either processed here or enqueued for later. + /// + /// Expects `justification` to be valid. + fn triage_incoming_justif( + &mut self, + justification: BeefyVersionedFinalityProof, + ) -> Result<(), Error> { + let signed_commitment = match justification { + VersionedFinalityProof::V1(ref sc) => sc, + }; + let block_num = signed_commitment.commitment.block_number; + let best_grandpa = self.best_grandpa_block(); + match self.voting_oracle().triage_round(block_num, best_grandpa)? { + RoundAction::Process => { + debug!(target: "beefy", "🥩 Process justification for round: {:?}.", block_num); + self.finalize(justification)? + }, + RoundAction::Enqueue => { + debug!(target: "beefy", "🥩 Buffer justification for round: {:?}.", block_num); + if self.pending_justifications.len() < MAX_BUFFERED_JUSTIFICATIONS { + self.pending_justifications.entry(block_num).or_insert(justification); + } else { + error!(target: "beefy", "🥩 Buffer justification dropped for round: {:?}.", block_num); + } + }, + RoundAction::Drop => (), + }; + Ok(()) + } + + fn handle_vote( + &mut self, + round: (Payload, NumberFor), + vote: (AuthorityId, Signature), + self_vote: bool, + ) -> Result<(), Error> { + self.gossip_validator.note_round(round.1); + + let rounds = self + .persisted_state + .voting_oracle + .active_rounds_mut() + .ok_or(Error::UninitSession)?; + + if rounds.add_vote(&round, vote, self_vote) { + if let Some(signatures) = rounds.should_conclude(&round) { + self.gossip_validator.conclude_round(round.1); + + let block_num = round.1; + let commitment = Commitment { + payload: round.0, + block_number: block_num, + validator_set_id: rounds.validator_set_id(), + }; + + let finality_proof = + VersionedFinalityProof::V1(SignedCommitment { commitment, signatures }); + + metric_set!(self, beefy_round_concluded, block_num); + + info!(target: "beefy", "🥩 Round #{} concluded, finality_proof: {:?}.", round.1, finality_proof); + + // We created the `finality_proof` and know to be valid. + // New state is persisted after finalization. + self.finalize(finality_proof)?; + } else { + let mandatory_round = self + .voting_oracle() + .mandatory_pending() + .map(|p| p.0 == round.1) + .unwrap_or(false); + // Persist state after handling self vote to avoid double voting in case + // of voter restarts. + // Also persist state after handling mandatory block vote. + if self_vote || mandatory_round { + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) + .map_err(|e| Error::Backend(e.to_string()))?; + } + } + } + Ok(()) + } + + /// Provide BEEFY finality for block based on `finality_proof`: + /// 1. Prune now-irrelevant past sessions from the oracle, + /// 2. Set BEEFY best block, + /// 3. Persist voter state, + /// 4. Send best block hash and `finality_proof` to RPC worker. + /// + /// Expects `finality proof` to be valid. + fn finalize(&mut self, finality_proof: BeefyVersionedFinalityProof) -> Result<(), Error> { + let block_num = match finality_proof { + VersionedFinalityProof::V1(ref sc) => sc.commitment.block_number, + }; + + // Finalize inner round and update voting_oracle state. + self.persisted_state.voting_oracle.finalize(block_num)?; + + if block_num > self.best_beefy_block() { + // Set new best BEEFY block number. + self.persisted_state.best_beefy_block = block_num; + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) + .map_err(|e| Error::Backend(e.to_string()))?; + + metric_set!(self, beefy_best_block, block_num); + + self.on_demand_justifications.cancel_requests_older_than(block_num); + + if let Err(e) = self + .backend + .blockchain() + .expect_block_hash_from_id(&BlockId::Number(block_num)) + .and_then(|hash| { + self.links + .to_rpc_best_block_sender + .notify(|| Ok::<_, ()>(hash)) + .expect("forwards closure result; the closure always returns Ok; qed."); + + self.backend + .append_justification(hash, (BEEFY_ENGINE_ID, finality_proof.encode())) + }) { + error!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, finality_proof); + } + + self.links + .to_rpc_justif_sender + .notify(|| Ok::<_, ()>(finality_proof)) + .expect("forwards closure result; the closure always returns Ok; qed."); + } else { + debug!(target: "beefy", "🥩 Can't set best beefy to older: {}", block_num); + } + Ok(()) + } + + /// Handle previously buffered justifications and votes that now land in the voting interval. + fn try_pending_justif_and_votes(&mut self) -> Result<(), Error> { + let best_grandpa = self.best_grandpa_block(); + let _ph = PhantomData::::default(); + + fn to_process_for( + pending: &mut BTreeMap, T>, + (start, end): (NumberFor, NumberFor), + _: PhantomData, + ) -> BTreeMap, T> { + // These are still pending. + let still_pending = pending.split_off(&end.saturating_add(1u32.into())); + // These can be processed. + let to_handle = pending.split_off(&start); + // The rest can be dropped. + *pending = still_pending; + // Return ones to process. + to_handle + } + // Interval of blocks for which we can process justifications and votes right now. + let mut interval = self.voting_oracle().accepted_interval(best_grandpa)?; + + // Process pending justifications. + if !self.pending_justifications.is_empty() { + let justifs_to_handle = to_process_for(&mut self.pending_justifications, interval, _ph); + for (num, justification) in justifs_to_handle.into_iter() { + debug!(target: "beefy", "🥩 Handle buffered justification for: {:?}.", num); + if let Err(err) = self.finalize(justification) { + error!(target: "beefy", "🥩 Error finalizing block: {}", err); + } + } + // Possibly new interval after processing justifications. + interval = self.voting_oracle().accepted_interval(best_grandpa)?; + } + + // Process pending votes. + if !self.pending_votes.is_empty() { + let votes_to_handle = to_process_for(&mut self.pending_votes, interval, _ph); + for (num, votes) in votes_to_handle.into_iter() { + debug!(target: "beefy", "🥩 Handle buffered votes for: {:?}.", num); + for v in votes.into_iter() { + if let Err(err) = self.handle_vote( + (v.commitment.payload, v.commitment.block_number), + (v.id, v.signature), + false, + ) { + error!(target: "beefy", "🥩 Error handling buffered vote: {}", err); + }; + } + } + } + Ok(()) + } + + /// Decide if should vote, then vote.. or don't.. + fn try_to_vote(&mut self) -> Result<(), Error> { + // Vote if there's now a new vote target. + if let Some(target) = self + .voting_oracle() + .voting_target(self.best_beefy_block(), self.best_grandpa_block()) + { + metric_set!(self, beefy_should_vote_on, target); + self.do_vote(target)?; + } + Ok(()) + } + + /// Create and gossip Signed Commitment for block number `target_number`. + /// + /// Also handle this self vote by calling `self.handle_vote()` for it. + fn do_vote(&mut self, target_number: NumberFor) -> Result<(), Error> { + debug!(target: "beefy", "🥩 Try voting on {}", target_number); + + // Most of the time we get here, `target` is actually `best_grandpa`, + // avoid getting header from backend in that case. + let target_header = if target_number == self.best_grandpa_block() { + self.persisted_state.best_grandpa_block_header.clone() + } else { + self.backend + .blockchain() + .expect_header(BlockId::Number(target_number)) + .map_err(|err| { + let err_msg = format!( + "Couldn't get header for block #{:?} (error: {:?}), skipping vote..", + target_number, err + ); + Error::Backend(err_msg) + })? + }; + let target_hash = target_header.hash(); + + let payload = if let Some(hash) = self.payload_provider.payload(&target_header) { + hash + } else { + warn!(target: "beefy", "🥩 No MMR root digest found for: {:?}", target_hash); + return Ok(()) + }; + + let rounds = self + .persisted_state + .voting_oracle + .active_rounds_mut() + .ok_or(Error::UninitSession)?; + if !rounds.should_self_vote(&(payload.clone(), target_number)) { + debug!(target: "beefy", "🥩 Don't double vote for block number: {:?}", target_number); + return Ok(()) + } + let (validators, validator_set_id) = (rounds.validators(), rounds.validator_set_id()); + + let authority_id = if let Some(id) = self.key_store.authority_id(validators) { + debug!(target: "beefy", "🥩 Local authority id: {:?}", id); + id + } else { + debug!(target: "beefy", "🥩 Missing validator id - can't vote for: {:?}", target_hash); + return Ok(()) + }; + + let commitment = Commitment { payload, block_number: target_number, validator_set_id }; + let encoded_commitment = commitment.encode(); + + let signature = match self.key_store.sign(&authority_id, &encoded_commitment) { + Ok(sig) => sig, + Err(err) => { + warn!(target: "beefy", "🥩 Error signing commitment: {:?}", err); + return Ok(()) + }, + }; + + trace!( + target: "beefy", + "🥩 Produced signature using {:?}, is_valid: {:?}", + authority_id, + BeefyKeystore::verify(&authority_id, &signature, &encoded_commitment) + ); + + let message = VoteMessage { commitment, id: authority_id, signature }; + + let encoded_message = message.encode(); + + metric_inc!(self, beefy_votes_sent); + + debug!(target: "beefy", "🥩 Sent vote message: {:?}", message); + + if let Err(err) = self.handle_vote( + (message.commitment.payload, message.commitment.block_number), + (message.id, message.signature), + true, + ) { + error!(target: "beefy", "🥩 Error handling self vote: {}", err); + } + + self.gossip_engine.gossip_message(topic::(), encoded_message, false); + + Ok(()) + } + + fn process_new_state(&mut self) { + // Handle pending justifications and/or votes for now GRANDPA finalized blocks. + if let Err(err) = self.try_pending_justif_and_votes() { + debug!(target: "beefy", "🥩 {}", err); + } + + // Don't bother voting or requesting justifications during major sync. + if !self.network.is_major_syncing() { + // There were external events, 'state' is changed, author a vote if needed/possible. + if let Err(err) = self.try_to_vote() { + debug!(target: "beefy", "🥩 {}", err); + } + // If the current target is a mandatory block, + // make sure there's also an on-demand justification request out for it. + if let Some((block, active)) = self.voting_oracle().mandatory_pending() { + // This only starts new request if there isn't already an active one. + self.on_demand_justifications.request(block, active); + } + } + } + + /// Main loop for BEEFY worker. + /// + /// Wait for BEEFY runtime pallet to be available, then start the main async loop + /// which is driven by finality notifications and gossiped votes. + pub(crate) async fn run( + mut self, + mut block_import_justif: Fuse>>, + mut finality_notifications: Fuse>, + ) { + info!(target: "beefy", "🥩 run BEEFY worker, best grandpa: #{:?}.", self.best_grandpa_block()); + + let mut votes = Box::pin( + self.gossip_engine + .messages_for(topic::()) + .filter_map(|notification| async move { + trace!(target: "beefy", "🥩 Got vote message: {:?}", notification); + + VoteMessage::, AuthorityId, Signature>::decode( + &mut ¬ification.message[..], + ) + .ok() + }) + .fuse(), + ); + + loop { + // Act on changed 'state'. + self.process_new_state(); + + let mut gossip_engine = &mut self.gossip_engine; + // Wait for, and handle external events. + // The branches below only change 'state', actual voting happens afterwards, + // based on the new resulting 'state'. + futures::select_biased! { + // Use `select_biased!` to prioritize order below. + // Make sure to pump gossip engine. + _ = gossip_engine => { + error!(target: "beefy", "🥩 Gossip engine has terminated, closing worker."); + return; + }, + // Process finality notifications first since these drive the voter. + notification = finality_notifications.next() => { + if let Some(notification) = notification { + self.handle_finality_notification(¬ification); + } else { + error!(target: "beefy", "🥩 Finality stream terminated, closing worker."); + return; + } + }, + // Process incoming justifications as these can make some in-flight votes obsolete. + justif = self.on_demand_justifications.next().fuse() => { + if let Some(justif) = justif { + if let Err(err) = self.triage_incoming_justif(justif) { + debug!(target: "beefy", "🥩 {}", err); + } + } + }, + justif = block_import_justif.next() => { + if let Some(justif) = justif { + // Block import justifications have already been verified to be valid + // by `BeefyBlockImport`. + if let Err(err) = self.triage_incoming_justif(justif) { + debug!(target: "beefy", "🥩 {}", err); + } + } else { + error!(target: "beefy", "🥩 Block import stream terminated, closing worker."); + return; + } + }, + // Finally process incoming votes. + vote = votes.next() => { + if let Some(vote) = vote { + // Votes have already been verified to be valid by the gossip validator. + if let Err(err) = self.triage_incoming_vote(vote) { + debug!(target: "beefy", "🥩 {}", err); + } + } else { + error!(target: "beefy", "🥩 Votes gossiping stream terminated, closing worker."); + return; + } + }, + } + } + } +} + +/// Scan the `header` digest log for a BEEFY validator set change. Return either the new +/// validator set or `None` in case no validator set change has been signaled. +pub(crate) fn find_authorities_change(header: &B::Header) -> Option> +where + B: Block, +{ + let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); + + let filter = |log: ConsensusLog| match log { + ConsensusLog::AuthoritiesChange(validator_set) => Some(validator_set), + _ => None, + }; + header.digest().convert_first(|l| l.try_to(id).and_then(filter)) +} + +/// Calculate next block number to vote on. +/// +/// Return `None` if there is no voteable target yet. +fn vote_target(best_grandpa: N, best_beefy: N, session_start: N, min_delta: u32) -> Option +where + N: AtLeast32Bit + Copy + Debug, +{ + // if the mandatory block (session_start) does not have a beefy justification yet, + // we vote on it + let target = if best_beefy < session_start { + debug!( + target: "beefy", + "🥩 vote target - mandatory block: #{:?}", + session_start, + ); + session_start + } else { + let diff = best_grandpa.saturating_sub(best_beefy) + 1u32.into(); + let diff = diff.saturated_into::() / 2; + let target = best_beefy + min_delta.max(diff.next_power_of_two()).into(); + trace!( + target: "beefy", + "🥩 vote target - diff: {:?}, next_power_of_two: {:?}, target block: #{:?}", + diff, + diff.next_power_of_two(), + target, + ); + + target + }; + + // Don't vote for targets until they've been finalized + // (`target` can be > `best_grandpa` when `min_delta` is big enough). + if target > best_grandpa { + None + } else { + Some(target) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::{ + communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, + keystore::tests::Keyring, + tests::{ + create_beefy_keystore, get_beefy_streams, make_beefy_ids, two_validators::TestApi, + BeefyPeer, BeefyTestNet, + }, + BeefyRPCLinks, KnownPeers, + }; + use beefy_primitives::{known_payloads, mmr::MmrRootProvider}; + use futures::{future::poll_fn, task::Poll}; + use parking_lot::Mutex; + use sc_client_api::{Backend as BackendT, HeaderBackend}; + use sc_network::NetworkService; + use sc_network_test::TestNetFactory; + use sp_api::HeaderT; + use sp_blockchain::Backend as BlockchainBackendT; + use sp_runtime::traits::{One, Zero}; + use substrate_test_runtime_client::{ + runtime::{Block, Digest, DigestItem, Header, H256}, + Backend, + }; + use tokio::runtime::Runtime; + + impl PersistedState { + pub fn voting_oracle(&self) -> &VoterOracle { + &self.voting_oracle + } + + pub fn active_round(&self) -> Option<&Rounds> { + self.voting_oracle.active_rounds() + } + + pub fn best_beefy_block(&self) -> NumberFor { + self.best_beefy_block + } + + pub fn best_grandpa_block(&self) -> NumberFor { + *self.best_grandpa_block_header.number() + } + } + + impl VoterOracle { + pub fn sessions(&self) -> &VecDeque> { + &self.sessions + } + } + + fn create_beefy_worker( + peer: &BeefyPeer, + key: &Keyring, + min_block_delta: u32, + genesis_validator_set: ValidatorSet, + ) -> BeefyWorker< + Block, + Backend, + MmrRootProvider, + Arc>, + > { + let keystore = create_beefy_keystore(*key); + + let (to_rpc_justif_sender, from_voter_justif_stream) = + BeefyVersionedFinalityProofStream::::channel(); + let (to_rpc_best_block_sender, from_voter_best_beefy_stream) = + BeefyBestBlockStream::::channel(); + let (_, from_block_import_justif_stream) = + BeefyVersionedFinalityProofStream::::channel(); + + let beefy_rpc_links = + BeefyRPCLinks { from_voter_justif_stream, from_voter_best_beefy_stream }; + *peer.data.beefy_rpc_links.lock() = Some(beefy_rpc_links); + + let links = BeefyVoterLinks { + from_block_import_justif_stream, + to_rpc_justif_sender, + to_rpc_best_block_sender, + }; + + let backend = peer.client().as_backend(); + let api = Arc::new(TestApi {}); + let network = peer.network_service().clone(); + let known_peers = Arc::new(Mutex::new(KnownPeers::new())); + let gossip_validator = Arc::new(GossipValidator::new(known_peers.clone())); + let gossip_engine = + GossipEngine::new(network.clone(), "/beefy/1", gossip_validator.clone(), None); + let on_demand_justifications = OnDemandJustificationsEngine::new( + network.clone(), + "/beefy/justifs/1".into(), + known_peers, + ); + let at = BlockId::number(Zero::zero()); + let genesis_header = backend.blockchain().expect_header(at).unwrap(); + let persisted_state = PersistedState::checked_new( + genesis_header, + Zero::zero(), + vec![Rounds::new(One::one(), genesis_validator_set)].into(), + min_block_delta, + ) + .unwrap(); + let payload_provider = MmrRootProvider::new(api); + let worker_params = crate::worker::WorkerParams { + backend, + payload_provider, + key_store: Some(keystore).into(), + links, + gossip_engine, + gossip_validator, + metrics: None, + network, + on_demand_justifications, + persisted_state, + }; + BeefyWorker::<_, _, _, _>::new(worker_params) + } + + #[test] + fn vote_on_min_block_delta() { + let t = vote_target(1u32, 1, 1, 4); + assert_eq!(None, t); + let t = vote_target(2u32, 1, 1, 4); + assert_eq!(None, t); + let t = vote_target(4u32, 2, 1, 4); + assert_eq!(None, t); + let t = vote_target(6u32, 2, 1, 4); + assert_eq!(Some(6), t); + + let t = vote_target(9u32, 4, 1, 4); + assert_eq!(Some(8), t); + + let t = vote_target(10u32, 10, 1, 8); + assert_eq!(None, t); + let t = vote_target(12u32, 10, 1, 8); + assert_eq!(None, t); + let t = vote_target(18u32, 10, 1, 8); + assert_eq!(Some(18), t); + } + + #[test] + fn vote_on_power_of_two() { + let t = vote_target(1008u32, 1000, 1, 4); + assert_eq!(Some(1004), t); + + let t = vote_target(1016u32, 1000, 1, 4); + assert_eq!(Some(1008), t); + + let t = vote_target(1032u32, 1000, 1, 4); + assert_eq!(Some(1016), t); + + let t = vote_target(1064u32, 1000, 1, 4); + assert_eq!(Some(1032), t); + + let t = vote_target(1128u32, 1000, 1, 4); + assert_eq!(Some(1064), t); + + let t = vote_target(1256u32, 1000, 1, 4); + assert_eq!(Some(1128), t); + + let t = vote_target(1512u32, 1000, 1, 4); + assert_eq!(Some(1256), t); + + let t = vote_target(1024u32, 1, 1, 4); + assert_eq!(Some(513), t); + } + + #[test] + fn vote_on_target_block() { + let t = vote_target(1008u32, 1002, 1, 4); + assert_eq!(Some(1006), t); + let t = vote_target(1010u32, 1002, 1, 4); + assert_eq!(Some(1006), t); + + let t = vote_target(1016u32, 1006, 1, 4); + assert_eq!(Some(1014), t); + let t = vote_target(1022u32, 1006, 1, 4); + assert_eq!(Some(1014), t); + + let t = vote_target(1032u32, 1012, 1, 4); + assert_eq!(Some(1028), t); + let t = vote_target(1044u32, 1012, 1, 4); + assert_eq!(Some(1028), t); + + let t = vote_target(1064u32, 1014, 1, 4); + assert_eq!(Some(1046), t); + let t = vote_target(1078u32, 1014, 1, 4); + assert_eq!(Some(1046), t); + + let t = vote_target(1128u32, 1008, 1, 4); + assert_eq!(Some(1072), t); + let t = vote_target(1136u32, 1008, 1, 4); + assert_eq!(Some(1072), t); + } + + #[test] + fn vote_on_mandatory_block() { + let t = vote_target(1008u32, 1002, 1004, 4); + assert_eq!(Some(1004), t); + let t = vote_target(1016u32, 1006, 1007, 4); + assert_eq!(Some(1007), t); + let t = vote_target(1064u32, 1014, 1063, 4); + assert_eq!(Some(1063), t); + let t = vote_target(1320u32, 1012, 1234, 4); + assert_eq!(Some(1234), t); + + let t = vote_target(1128u32, 1008, 1008, 4); + assert_eq!(Some(1072), t); + } + + #[test] + fn should_vote_target() { + let mut oracle = VoterOracle:: { min_block_delta: 1, sessions: VecDeque::new() }; + + // rounds not initialized -> should vote: `None` + assert_eq!(oracle.voting_target(0, 1), None); + + let keys = &[Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + + oracle.add_session(Rounds::new(1, validator_set.clone())); + + // under min delta + oracle.min_block_delta = 4; + assert_eq!(oracle.voting_target(1, 1), None); + assert_eq!(oracle.voting_target(2, 5), None); + + // vote on min delta + assert_eq!(oracle.voting_target(4, 9), Some(8)); + oracle.min_block_delta = 8; + assert_eq!(oracle.voting_target(10, 18), Some(18)); + + // vote on power of two + oracle.min_block_delta = 1; + assert_eq!(oracle.voting_target(1000, 1008), Some(1004)); + assert_eq!(oracle.voting_target(1000, 1016), Some(1008)); + + // nothing new to vote on + assert_eq!(oracle.voting_target(1000, 1000), None); + + // vote on mandatory + oracle.sessions.clear(); + oracle.add_session(Rounds::new(1000, validator_set.clone())); + assert_eq!(oracle.voting_target(0, 1008), Some(1000)); + oracle.sessions.clear(); + oracle.add_session(Rounds::new(1001, validator_set.clone())); + assert_eq!(oracle.voting_target(1000, 1008), Some(1001)); + } + + #[test] + fn test_oracle_accepted_interval() { + let keys = &[Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + + let mut oracle = VoterOracle:: { min_block_delta: 1, sessions: VecDeque::new() }; + + // rounds not initialized -> should accept votes: `None` + assert!(oracle.accepted_interval(1).is_err()); + + let session_one = 1; + oracle.add_session(Rounds::new(session_one, validator_set.clone())); + // mandatory not done, only accept mandatory + for i in 0..15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_one, session_one))); + } + + // add more sessions, nothing changes + let session_two = 11; + let session_three = 21; + oracle.add_session(Rounds::new(session_two, validator_set.clone())); + oracle.add_session(Rounds::new(session_three, validator_set.clone())); + // mandatory not done, should accept mandatory for session_one + for i in session_three..session_three + 15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_one, session_one))); + } + + // simulate finish mandatory for session one, prune oracle + oracle.sessions.front_mut().unwrap().test_set_mandatory_done(true); + oracle.try_prune(); + // session_one pruned, should accept mandatory for session_two + for i in session_three..session_three + 15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_two, session_two))); + } + + // simulate finish mandatory for session two, prune oracle + oracle.sessions.front_mut().unwrap().test_set_mandatory_done(true); + oracle.try_prune(); + // session_two pruned, should accept mandatory for session_three + for i in session_three..session_three + 15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_three, session_three))); + } + + // simulate finish mandatory for session three + oracle.sessions.front_mut().unwrap().test_set_mandatory_done(true); + // verify all other blocks in this session are now open to voting + for i in session_three..session_three + 15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_three, i))); + } + // pruning does nothing in this case + oracle.try_prune(); + for i in session_three..session_three + 15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_three, i))); + } + + // adding new session automatically prunes "finalized" previous session + let session_four = 31; + oracle.add_session(Rounds::new(session_four, validator_set.clone())); + assert_eq!(oracle.sessions.front().unwrap().session_start(), session_four); + assert_eq!(oracle.accepted_interval(session_four + 10), Ok((session_four, session_four))); + } + + #[test] + fn extract_authorities_change_digest() { + let mut header = Header::new( + 1u32.into(), + Default::default(), + Default::default(), + Default::default(), + Digest::default(), + ); + + // verify empty digest shows nothing + assert!(find_authorities_change::(&header).is_none()); + + let peers = &[Keyring::One, Keyring::Two]; + let id = 42; + let validator_set = ValidatorSet::new(make_beefy_ids(peers), id).unwrap(); + header.digest_mut().push(DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::AuthoritiesChange(validator_set.clone()).encode(), + )); + + // verify validator set is correctly extracted from digest + let extracted = find_authorities_change::(&header); + assert_eq!(extracted, Some(validator_set)); + } + + #[test] + fn keystore_vs_validator_set() { + let keys = &[Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); + + // keystore doesn't contain other keys than validators' + assert_eq!(worker.verify_validator_set(&1, &validator_set), Ok(())); + + // unknown `Bob` key + let keys = &[Keyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let err_msg = "no authority public key found in store".to_string(); + let expected = Err(Error::Keystore(err_msg)); + assert_eq!(worker.verify_validator_set(&1, &validator_set), expected); + + // worker has no keystore + worker.key_store = None.into(); + let expected_err = Err(Error::Keystore("no Keystore".into())); + assert_eq!(worker.verify_validator_set(&1, &validator_set), expected_err); + } + + #[test] + fn should_finalize_correctly() { + let keys = [Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(&keys), 0).unwrap(); + let runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let backend = net.peer(0).client().as_backend(); + let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); + // remove default session, will manually add custom one. + worker.persisted_state.voting_oracle.sessions.clear(); + + let keys = keys.iter().cloned().enumerate(); + let (mut best_block_streams, mut finality_proofs) = + get_beefy_streams(&mut net, keys.clone()); + let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); + let mut finality_proof = finality_proofs.drain(..).next().unwrap(); + + let create_finality_proof = |block_num: NumberFor| { + let commitment = Commitment { + payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), + block_number: block_num, + validator_set_id: validator_set.id(), + }; + VersionedFinalityProof::V1(SignedCommitment { commitment, signatures: vec![None] }) + }; + + // no 'best beefy block' or finality proofs + assert_eq!(worker.best_beefy_block(), 0); + runtime.block_on(poll_fn(move |cx| { + assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); + assert_eq!(finality_proof.poll_next_unpin(cx), Poll::Pending); + Poll::Ready(()) + })); + + // unknown hash for block #1 + let (mut best_block_streams, mut finality_proofs) = + get_beefy_streams(&mut net, keys.clone()); + let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); + let mut finality_proof = finality_proofs.drain(..).next().unwrap(); + let justif = create_finality_proof(1); + // create new session at block #1 + worker + .persisted_state + .voting_oracle + .add_session(Rounds::new(1, validator_set.clone())); + // try to finalize block #1 + worker.finalize(justif.clone()).unwrap(); + // verify block finalized + assert_eq!(worker.best_beefy_block(), 1); + runtime.block_on(poll_fn(move |cx| { + // unknown hash -> nothing streamed + assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); + // commitment streamed + match finality_proof.poll_next_unpin(cx) { + // expect justification + Poll::Ready(Some(received)) => assert_eq!(received, justif), + v => panic!("unexpected value: {:?}", v), + } + Poll::Ready(()) + })); + + // generate 2 blocks, try again expect success + let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); + let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); + net.peer(0).push_blocks(2, false); + // finalize 1 and 2 without justifications + let hashof1 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(1)).unwrap(); + let hashof2 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(2)).unwrap(); + backend.finalize_block(hashof1, None).unwrap(); + backend.finalize_block(hashof2, None).unwrap(); + + let justif = create_finality_proof(2); + // create new session at block #2 + worker.persisted_state.voting_oracle.add_session(Rounds::new(2, validator_set)); + worker.finalize(justif).unwrap(); + // verify old session pruned + assert_eq!(worker.voting_oracle().sessions.len(), 1); + // new session starting at #2 is in front + assert_eq!(worker.active_rounds().unwrap().session_start(), 2); + // verify block finalized + assert_eq!(worker.best_beefy_block(), 2); + runtime.block_on(poll_fn(move |cx| { + match best_block_stream.poll_next_unpin(cx) { + // expect Some(hash-of-block-2) + Poll::Ready(Some(hash)) => { + let block_num = net.peer(0).client().as_client().number(hash).unwrap(); + assert_eq!(block_num, Some(2)); + }, + v => panic!("unexpected value: {:?}", v), + } + Poll::Ready(()) + })); + + // check BEEFY justifications are also appended to backend + let justifs = backend.blockchain().justifications(hashof2).unwrap().unwrap(); + assert!(justifs.get(BEEFY_ENGINE_ID).is_some()) + } + + #[test] + fn should_init_session() { + let keys = &[Keyring::Alice, Keyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); + + let worker_rounds = worker.active_rounds().unwrap(); + assert_eq!(worker_rounds.session_start(), 1); + assert_eq!(worker_rounds.validators(), validator_set.validators()); + assert_eq!(worker_rounds.validator_set_id(), validator_set.id()); + + // new validator set + let keys = &[Keyring::Bob]; + let new_validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); + + worker.init_session_at(new_validator_set.clone(), 11); + // Since mandatory is not done for old rounds, we still get those. + let rounds = worker.persisted_state.voting_oracle.active_rounds_mut().unwrap(); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + // Let's finalize mandatory. + rounds.test_set_mandatory_done(true); + worker.persisted_state.voting_oracle.try_prune(); + // Now we should get the next round. + let rounds = worker.active_rounds().unwrap(); + // Expect new values. + assert_eq!(rounds.session_start(), 11); + assert_eq!(rounds.validators(), new_validator_set.validators()); + assert_eq!(rounds.validator_set_id(), new_validator_set.id()); + } + + #[test] + fn should_triage_votes_and_process_later() { + let keys = &[Keyring::Alice, Keyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); + // remove default session, will manually add custom one. + worker.persisted_state.voting_oracle.sessions.clear(); + + fn new_vote( + block_number: NumberFor, + ) -> VoteMessage, AuthorityId, Signature> { + let commitment = Commitment { + payload: Payload::from_single_entry(*b"BF", vec![]), + block_number, + validator_set_id: 0, + }; + VoteMessage { + commitment, + id: Keyring::Alice.public(), + signature: Keyring::Alice.sign(b"I am committed"), + } + } + + // best grandpa is 20 + let best_grandpa_header = Header::new( + 20u32.into(), + Default::default(), + Default::default(), + Default::default(), + Digest::default(), + ); + + worker + .persisted_state + .voting_oracle + .add_session(Rounds::new(10, validator_set.clone())); + worker.persisted_state.best_grandpa_block_header = best_grandpa_header; + + // triage votes for blocks 10..13 + worker.triage_incoming_vote(new_vote(10)).unwrap(); + worker.triage_incoming_vote(new_vote(11)).unwrap(); + worker.triage_incoming_vote(new_vote(12)).unwrap(); + // triage votes for blocks 20..23 + worker.triage_incoming_vote(new_vote(20)).unwrap(); + worker.triage_incoming_vote(new_vote(21)).unwrap(); + worker.triage_incoming_vote(new_vote(22)).unwrap(); + + // vote for 10 should have been handled, while the rest buffered for later processing + let mut votes = worker.pending_votes.values(); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 11); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 12); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 20); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 21); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 22); + assert!(votes.next().is_none()); + + // simulate mandatory done, and retry buffered votes + worker + .persisted_state + .voting_oracle + .active_rounds_mut() + .unwrap() + .test_set_mandatory_done(true); + worker.try_pending_justif_and_votes().unwrap(); + // all blocks <= grandpa finalized should have been handled, rest still buffered + let mut votes = worker.pending_votes.values(); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 21); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 22); + } +} diff --git a/client/block-builder/Cargo.toml b/client/block-builder/Cargo.toml index 6fef8498134eb..2516374864bc1 100644 --- a/client/block-builder/Cargo.toml +++ b/client/block-builder/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-block-builder" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate block builder" readme = "README.md" @@ -12,19 +12,18 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -sp-state-machine = { version = "0.10.0-dev", path = "../../primitives/state-machine" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", +] } +sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } -codec = { package = "parity-scale-codec", version = "2.0.0", features = [ - "derive", -] } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } [dev-dependencies] substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } diff --git a/client/block-builder/src/lib.rs b/client/block-builder/src/lib.rs index e89421edfb168..b6c2ac3ba5d68 100644 --- a/client/block-builder/src/lib.rs +++ b/client/block-builder/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -35,7 +35,9 @@ use sp_blockchain::{ApplyExtrinsicFailed, Error}; use sp_core::ExecutionContext; use sp_runtime::{ generic::BlockId, - traits::{Block as BlockT, DigestFor, Hash, HashFor, Header as HeaderT, NumberFor, One}, + legacy, + traits::{Block as BlockT, Hash, HashFor, Header as HeaderT, NumberFor, One}, + Digest, }; pub use sp_block_builder::BlockBuilder as BlockBuilderApi; @@ -119,14 +121,14 @@ where fn new_block_at>( &self, parent: &BlockId, - inherent_digests: DigestFor, + inherent_digests: Digest, record_proof: R, ) -> sp_blockchain::Result>; /// Create a new block, built on the head of the chain. fn new_block( &self, - inherent_digests: DigestFor, + inherent_digests: Digest, ) -> sp_blockchain::Result>; } @@ -134,6 +136,7 @@ where pub struct BlockBuilder<'a, Block: BlockT, A: ProvideRuntimeApi, B> { extrinsics: Vec, api: ApiRef<'a, A::Api>, + version: u32, block_id: BlockId, parent_hash: Block::Hash, backend: &'a B, @@ -159,7 +162,7 @@ where parent_hash: Block::Hash, parent_number: NumberFor, record_proof: RecordProof, - inherent_digests: DigestFor, + inherent_digests: Digest, backend: &'a B, ) -> Result { let header = <::Header as HeaderT>::new( @@ -182,10 +185,15 @@ where api.initialize_block_with_context(&block_id, ExecutionContext::BlockConstruction, &header)?; + let version = api + .api_version::>(&block_id)? + .ok_or_else(|| Error::VersionInvalid("BlockBuilderApi".to_string()))?; + Ok(Self { parent_hash, extrinsics: Vec::new(), api, + version, block_id, backend, estimated_header_size, @@ -198,13 +206,26 @@ where pub fn push(&mut self, xt: ::Extrinsic) -> Result<(), Error> { let block_id = &self.block_id; let extrinsics = &mut self.extrinsics; + let version = self.version; self.api.execute_in_transaction(|api| { - match api.apply_extrinsic_with_context( - block_id, - ExecutionContext::BlockConstruction, - xt.clone(), - ) { + let res = if version < 6 { + #[allow(deprecated)] + api.apply_extrinsic_before_version_6_with_context( + block_id, + ExecutionContext::BlockConstruction, + xt.clone(), + ) + .map(legacy::byte_sized_error::convert_to_latest) + } else { + api.apply_extrinsic_with_context( + block_id, + ExecutionContext::BlockConstruction, + xt.clone(), + ) + }; + + match res { Ok(Ok(_)) => { extrinsics.push(xt); TransactionOutcome::Commit(Ok(())) @@ -231,22 +252,18 @@ where header.extrinsics_root().clone(), HashFor::::ordered_trie_root( self.extrinsics.iter().map(Encode::encode).collect(), + sp_runtime::StateVersion::V0, ), ); let proof = self.api.extract_proof(); - let state = self.backend.state_at(self.block_id)?; - let changes_trie_state = backend::changes_tries_state_at_block( - &self.block_id, - self.backend.changes_trie_storage(), - )?; - let parent_hash = self.parent_hash; + let state = self.backend.state_at(self.parent_hash)?; let storage_changes = self .api - .into_storage_changes(&state, changes_trie_state.as_ref(), parent_hash) - .map_err(|e| sp_blockchain::Error::StorageChanges(e))?; + .into_storage_changes(&state, self.parent_hash) + .map_err(sp_blockchain::Error::StorageChanges)?; Ok(BuiltBlock { block: ::new(header, self.extrinsics), diff --git a/client/chain-spec/Cargo.toml b/client/chain-spec/Cargo.toml index 8af2996e968d8..3756a7783763b 100644 --- a/client/chain-spec/Cargo.toml +++ b/client/chain-spec/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-chain-spec" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate chain configurations." readme = "README.md" @@ -13,12 +13,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0" } +impl-trait-for-tuples = "0.2.2" +memmap2 = "0.5.0" +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.85" sc-chain-spec-derive = { version = "4.0.0-dev", path = "./derive" } -impl-trait-for-tuples = "0.2.1" -sc-network = { version = "0.10.0-dev", path = "../network" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -serde = { version = "1.0.126", features = ["derive"] } -serde_json = "1.0.68" -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -codec = { package = "parity-scale-codec", version = "2.0.0" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } diff --git a/client/chain-spec/derive/Cargo.toml b/client/chain-spec/derive/Cargo.toml index b210fa1320e04..d733bda9b1692 100644 --- a/client/chain-spec/derive/Cargo.toml +++ b/client/chain-spec/derive/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-chain-spec-derive" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Macros to derive chain spec extension traits implementation." @@ -15,9 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro-crate = "1.0.0" -proc-macro2 = "1.0.29" -quote = "1.0.3" -syn = "1.0.58" - -[dev-dependencies] +proc-macro-crate = "1.1.3" +proc-macro2 = "1.0.37" +quote = "1.0.10" +syn = "1.0.98" diff --git a/client/chain-spec/derive/src/impls.rs b/client/chain-spec/derive/src/impls.rs index 8c56430e81d02..7af403d46ad10 100644 --- a/client/chain-spec/derive/src/impls.rs +++ b/client/chain-spec/derive/src/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -52,7 +52,7 @@ pub fn extension_derive(ast: &DeriveInput) -> proc_macro::TokenStream { use std::any::{Any, TypeId}; match TypeId::of::() { - #( x if x == TypeId::of::<#field_types>() => Any::downcast_ref(&self.#field_names) ),*, + #( x if x == TypeId::of::<#field_types>() => ::downcast_ref(&self.#field_names) ),*, _ => None, } } diff --git a/client/chain-spec/derive/src/lib.rs b/client/chain-spec/derive/src/lib.rs index 53f0c69491ecd..75356a2250465 100644 --- a/client/chain-spec/derive/src/lib.rs +++ b/client/chain-spec/derive/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/chain-spec/res/chain_spec.json b/client/chain-spec/res/chain_spec.json index 673f35d507919..c3365a9192f6e 100644 --- a/client/chain-spec/res/chain_spec.json +++ b/client/chain-spec/res/chain_spec.json @@ -19,7 +19,6 @@ ["wss://telemetry.polkadot.io/submit/", 0] ], "protocolId": "fir", - "consensusEngine": null, "genesis": { "raw": [ { diff --git a/client/chain-spec/res/chain_spec2.json b/client/chain-spec/res/chain_spec2.json index 950a7fc827494..00b9d603ae29e 100644 --- a/client/chain-spec/res/chain_spec2.json +++ b/client/chain-spec/res/chain_spec2.json @@ -19,7 +19,6 @@ ["wss://telemetry.polkadot.io/submit/", 0] ], "protocolId": "fir", - "consensusEngine": null, "myProperty": "Test Extension", "genesis": { "raw": [ diff --git a/client/chain-spec/src/chain_spec.rs b/client/chain-spec/src/chain_spec.rs index fcdb053c47c16..0b951200b069a 100644 --- a/client/chain-spec/src/chain_spec.rs +++ b/client/chain-spec/src/chain_spec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ #![warn(missing_docs)] use crate::{extension::GetExtension, ChainType, Properties, RuntimeGenesis}; -use sc_network::config::MultiaddrWithPeerId; +use sc_network_common::config::MultiaddrWithPeerId; use sc_telemetry::TelemetryEndpoints; use serde::{Deserialize, Serialize}; use serde_json as json; @@ -29,7 +29,7 @@ use sp_core::{ Bytes, }; use sp_runtime::BuildStorage; -use std::{borrow::Cow, collections::HashMap, fs::File, path::PathBuf, sync::Arc}; +use std::{borrow::Cow, collections::BTreeMap, fs::File, path::PathBuf, sync::Arc}; enum GenesisSource { File(PathBuf), @@ -58,9 +58,19 @@ impl GenesisSource { match self { Self::File(path) => { - let file = - File::open(path).map_err(|e| format!("Error opening spec file: {}", e))?; - let genesis: GenesisContainer = json::from_reader(file) + let file = File::open(path).map_err(|e| { + format!("Error opening spec file at `{}`: {}", path.display(), e) + })?; + // SAFETY: `mmap` is fundamentally unsafe since technically the file can change + // underneath us while it is mapped; in practice it's unlikely to be a + // problem + let bytes = unsafe { + memmap2::Mmap::map(&file).map_err(|e| { + format!("Error mmaping spec file `{}`: {}", path.display(), e) + })? + }; + + let genesis: GenesisContainer = json::from_slice(&bytes) .map_err(|e| format!("Error parsing spec file: {}", e))?; Ok(genesis.genesis) }, @@ -99,34 +109,31 @@ impl GenesisSource { } impl BuildStorage for ChainSpec { - fn build_storage(&self) -> Result { + fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { match self.genesis.resolve()? { - Genesis::Runtime(gc) => gc.build_storage(), - Genesis::Raw(RawGenesis { top: map, children_default: children_map }) => Ok(Storage { - top: map.into_iter().map(|(k, v)| (k.0, v.0)).collect(), - children_default: children_map - .into_iter() - .map(|(storage_key, child_content)| { - let child_info = ChildInfo::new_default(storage_key.0.as_slice()); - ( - storage_key.0, - StorageChild { - data: child_content.into_iter().map(|(k, v)| (k.0, v.0)).collect(), - child_info, - }, - ) - }) - .collect(), - }), + Genesis::Runtime(gc) => gc.assimilate_storage(storage), + Genesis::Raw(RawGenesis { top: map, children_default: children_map }) => { + storage.top.extend(map.into_iter().map(|(k, v)| (k.0, v.0))); + children_map.into_iter().for_each(|(k, v)| { + let child_info = ChildInfo::new_default(k.0.as_slice()); + storage + .children_default + .entry(k.0) + .or_insert_with(|| StorageChild { data: Default::default(), child_info }) + .data + .extend(v.into_iter().map(|(k, v)| (k.0, v.0))); + }); + Ok(()) + }, + // The `StateRootHash` variant exists as a way to keep note that other clients support + // it, but Substrate itself isn't capable of loading chain specs with just a hash at the + // moment. + Genesis::StateRootHash(_) => Err("Genesis storage in hash format not supported".into()), } } - - fn assimilate_storage(&self, _: &mut Storage) -> Result<(), String> { - Err("`assimilate_storage` not implemented for `ChainSpec`.".into()) - } } -pub type GenesisStorage = HashMap; +pub type GenesisStorage = BTreeMap; /// Raw storage content for genesis block. #[derive(Serialize, Deserialize)] @@ -134,7 +141,7 @@ pub type GenesisStorage = HashMap; #[serde(deny_unknown_fields)] pub struct RawGenesis { pub top: GenesisStorage, - pub children_default: HashMap, + pub children_default: BTreeMap, } #[derive(Serialize, Deserialize)] @@ -143,6 +150,8 @@ pub struct RawGenesis { enum Genesis { Runtime(G), Raw(RawGenesis), + /// State root hash of the genesis storage. + StateRootHash(StorageData), } /// A configuration of a client. Does not include runtime storage initialization. @@ -157,19 +166,27 @@ struct ClientSpec { boot_nodes: Vec, telemetry_endpoints: Option, protocol_id: Option, + /// Arbitrary string. Nodes will only synchronize with other nodes that have the same value + /// in their `fork_id`. This can be used in order to segregate nodes in cases when multiple + /// chains have the same genesis hash. + #[serde(default = "Default::default", skip_serializing_if = "Option::is_none")] + fork_id: Option, properties: Option, #[serde(flatten)] extensions: E, // Never used, left only for backward compatibility. + #[serde(default, skip_serializing)] + #[allow(unused)] consensus_engine: (), #[serde(skip_serializing)] + #[allow(unused)] genesis: serde::de::IgnoredAny, - /// Mapping from `block_hash` to `wasm_code`. + /// Mapping from `block_number` to `wasm_code`. /// - /// The given `wasm_code` will be used to substitute the on-chain wasm code from the given - /// block hash onwards. + /// The given `wasm_code` will be used to substitute the on-chain wasm code starting with the + /// given block number until the `spec_version` on chain changes. #[serde(default)] - code_substitutes: HashMap, + code_substitutes: BTreeMap, } /// A type denoting empty extensions. @@ -212,7 +229,12 @@ impl ChainSpec { /// Network protocol id. pub fn protocol_id(&self) -> Option<&str> { - self.client_spec.protocol_id.as_ref().map(String::as_str) + self.client_spec.protocol_id.as_deref() + } + + /// Optional network fork identifier. + pub fn fork_id(&self) -> Option<&str> { + self.client_spec.fork_id.as_deref() } /// Additional loosly-typed properties of the chain. @@ -246,6 +268,7 @@ impl ChainSpec { boot_nodes: Vec, telemetry_endpoints: Option, protocol_id: Option<&str>, + fork_id: Option<&str>, properties: Option, extensions: E, ) -> Self { @@ -256,11 +279,12 @@ impl ChainSpec { boot_nodes, telemetry_endpoints, protocol_id: protocol_id.map(str::to_owned), + fork_id: fork_id.map(str::to_owned), properties, extensions, consensus_engine: (), genesis: Default::default(), - code_substitutes: HashMap::new(), + code_substitutes: BTreeMap::new(), }; ChainSpec { client_spec, genesis: GenesisSource::Factory(Arc::new(constructor)) } @@ -283,9 +307,20 @@ impl ChainSpec { /// Parse json file into a `ChainSpec` pub fn from_json_file(path: PathBuf) -> Result { - let file = File::open(&path).map_err(|e| format!("Error opening spec file: {}", e))?; + // We mmap the file into memory first, as this is *a lot* faster than using + // `serde_json::from_reader`. See https://github.com/serde-rs/json/issues/160 + let file = File::open(&path) + .map_err(|e| format!("Error opening spec file `{}`: {}", path.display(), e))?; + + // SAFETY: `mmap` is fundamentally unsafe since technically the file can change + // underneath us while it is mapped; in practice it's unlikely to be a problem + let bytes = unsafe { + memmap2::Mmap::map(&file) + .map_err(|e| format!("Error mmaping spec file `{}`: {}", path.display(), e))? + }; + let client_spec = - json::from_reader(file).map_err(|e| format!("Error parsing spec file: {}", e))?; + json::from_slice(&bytes).map_err(|e| format!("Error parsing spec file: {}", e))?; Ok(ChainSpec { client_spec, genesis: GenesisSource::File(path) }) } } @@ -362,6 +397,10 @@ where ChainSpec::protocol_id(self) } + fn fork_id(&self) -> Option<&str> { + ChainSpec::fork_id(self) + } + fn properties(&self) -> Properties { ChainSpec::properties(self) } @@ -394,7 +433,7 @@ where self.genesis = GenesisSource::Storage(storage); } - fn code_substitutes(&self) -> std::collections::HashMap> { + fn code_substitutes(&self) -> std::collections::BTreeMap> { self.client_spec .code_substitutes .iter() @@ -408,7 +447,7 @@ mod tests { use super::*; #[derive(Debug, Serialize, Deserialize)] - struct Genesis(HashMap); + struct Genesis(BTreeMap); impl BuildStorage for Genesis { fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { @@ -433,12 +472,28 @@ mod tests { assert_eq!(spec2.chain_type(), ChainType::Live) } - #[derive(Debug, Serialize, Deserialize)] + #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] struct Extension1 { my_property: String, } + impl crate::Extension for Extension1 { + type Forks = Option<()>; + + fn get(&self) -> Option<&T> { + None + } + + fn get_any(&self, _: std::any::TypeId) -> &dyn std::any::Any { + self + } + + fn get_any_mut(&mut self, _: std::any::TypeId) -> &mut dyn std::any::Any { + self + } + } + type TestSpec2 = ChainSpec; #[test] @@ -450,4 +505,35 @@ mod tests { assert_eq!(spec.extensions().my_property, "Test Extension"); } + + #[test] + fn chain_spec_raw_output_should_be_deterministic() { + let mut spec = TestSpec2::from_json_bytes(Cow::Owned( + include_bytes!("../res/chain_spec2.json").to_vec(), + )) + .unwrap(); + + let mut storage = spec.build_storage().unwrap(); + + // Add some extra data, so that storage "sorting" is tested. + let extra_data = &[("random_key", "val"), ("r@nd0m_key", "val"), ("aaarandom_key", "val")]; + storage + .top + .extend(extra_data.iter().map(|(k, v)| (k.as_bytes().to_vec(), v.as_bytes().to_vec()))); + crate::ChainSpec::set_storage(&mut spec, storage); + + let json = spec.as_json(true).unwrap(); + + // Check multiple times that decoding and encoding the chain spec leads always to the same + // output. + for _ in 0..10 { + assert_eq!( + json, + TestSpec2::from_json_bytes(json.as_bytes().to_vec()) + .unwrap() + .as_json(true) + .unwrap() + ); + } + } } diff --git a/client/chain-spec/src/extension.rs b/client/chain-spec/src/extension.rs index 4b59232cf5770..c0b3e15a2df4c 100644 --- a/client/chain-spec/src/extension.rs +++ b/client/chain-spec/src/extension.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/chain-spec/src/lib.rs b/client/chain-spec/src/lib.rs index 334d8f8b3d7ac..9d2cc728b8288 100644 --- a/client/chain-spec/src/lib.rs +++ b/client/chain-spec/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -53,19 +53,19 @@ //! //! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup)] //! pub struct ClientParams { -//! max_block_size: usize, -//! max_extrinsic_size: usize, +//! max_block_size: usize, +//! max_extrinsic_size: usize, //! } //! //! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup)] //! pub struct PoolParams { -//! max_transaction_size: usize, +//! max_transaction_size: usize, //! } //! //! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup, ChainSpecExtension)] //! pub struct Extension { -//! pub client: ClientParams, -//! pub pool: PoolParams, +//! pub client: ClientParams, +//! pub pool: PoolParams, //! } //! //! pub type BlockNumber = u64; @@ -88,24 +88,92 @@ //! //! #[derive(Clone, Debug, Serialize, Deserialize, ChainSpecGroup)] //! pub struct ClientParams { -//! max_block_size: usize, -//! max_extrinsic_size: usize, +//! max_block_size: usize, +//! max_extrinsic_size: usize, //! } //! //! #[derive(Clone, Debug, Serialize, Deserialize, ChainSpecGroup)] //! pub struct PoolParams { -//! max_transaction_size: usize, +//! max_transaction_size: usize, //! } //! //! #[derive(Clone, Debug, Serialize, Deserialize, ChainSpecExtension)] //! pub struct Extension { -//! pub client: ClientParams, -//! #[forks] -//! pub pool: Forks, +//! pub client: ClientParams, +//! #[forks] +//! pub pool: Forks, //! } //! //! pub type MyChainSpec = GenericChainSpec; //! ``` +//! +//! # Substrate chain specification format +//! +//! The Substrate chain specification is a `json` file that describes the basics of a chain. Most +//! importantly it lays out the genesis storage which leads to the genesis hash. The default +//! Substrate chain specification format is the following: +//! +//! ```json +//! // The human readable name of the chain. +//! "name": "Flaming Fir", +//! // The id of the chain. +//! "id": "flamingfir9", +//! // The chain type of this chain. +//! // Possible values are `Live`, `Development`, `Local`. +//! "chainType": "Live", +//! // A list of multi addresses that belong to boot nodes of the chain. +//! "bootNodes": [ +//! "/dns/0.flamingfir.paritytech.net/tcp/30333/p2p/12D3KooWLK2gMLhWsYJzjW3q35zAs9FDDVqfqVfVuskiGZGRSMvR", +//! ], +//! // Optional list of "multi address, verbosity" of telemetry endpoints. +//! // The verbosity goes from `0` to `9`. With `0` being the mode with the lowest verbosity. +//! "telemetryEndpoints": [ +//! [ +//! "/dns/telemetry.polkadot.io/tcp/443/x-parity-wss/%2Fsubmit%2F", +//! 0 +//! ] +//! ], +//! // Optional networking protocol id that identifies the chain. +//! "protocolId": "fir9", +//! // Optional fork id. Should most likely be left empty. +//! // Can be used to signal a fork on the network level when two chains have the +//! // same genesis hash. +//! "forkId": "random_fork", +//! // Custom properties. +//! "properties": { +//! "tokenDecimals": 15, +//! "tokenSymbol": "FIR" +//! }, +//! // Deprecated field. Should be ignored. +//! "consensusEngine": null, +//! // The genesis declaration of the chain. +//! // +//! // `runtime`, `raw`, `stateRootHash` denote the type of the genesis declaration. +//! // +//! // These declarations are in the following formats: +//! // - `runtime` is a `json` object that can be parsed by a compatible `GenesisConfig`. This +//! // `GenesisConfig` is declared by a runtime and opaque to the node. +//! // - `raw` is a `json` object with two fields `top` and `children_default`. Each of these +//! // fields is a map of `key => value`. These key/value pairs represent the genesis storage. +//! // - `stateRootHash` is a single hex encoded hash that represents the genesis hash. The hash +//! // type depends on the hash used by the chain. +//! // +//! "genesis": { "runtime": {} }, +//! /// Optional map of `block_number` to `wasm_code`. +//! /// +//! /// The given `wasm_code` will be used to substitute the on-chain wasm code starting with the +//! /// given block number until the `spec_version` on-chain changes. The given `wasm_code` should +//! /// be as close as possible to the on-chain wasm code. A substitute should be used to fix a bug +//! /// that can not be fixed with a runtime upgrade, if for example the runtime is constantly +//! /// panicking. Introducing new runtime apis isn't supported, because the node +//! /// will read the runtime version from the on-chain wasm code. Use this functionality only when +//! /// there is no other way around it and only patch the problematic bug, the rest should be done +//! /// with a on-chain runtime upgrade. +//! "codeSubstitutes": [], +//! ``` +//! +//! The chain spec can be extended with other fields that are opaque to the default chain spec. +//! Specific node implementations will need to be able to deserialize these extensions. mod chain_spec; mod extension; @@ -116,7 +184,7 @@ pub use extension::{ }; pub use sc_chain_spec_derive::{ChainSpecExtension, ChainSpecGroup}; -use sc_network::config::MultiaddrWithPeerId; +use sc_network_common::config::MultiaddrWithPeerId; use sc_telemetry::TelemetryEndpoints; use serde::{de::DeserializeOwned, Serialize}; use sp_core::storage::Storage; @@ -165,6 +233,8 @@ pub trait ChainSpec: BuildStorage + Send + Sync { fn telemetry_endpoints(&self) -> &Option; /// Network protocol id. fn protocol_id(&self) -> Option<&str>; + /// Optional network fork identifier. `None` by default. + fn fork_id(&self) -> Option<&str>; /// Additional loosly-typed properties of the chain. /// /// Returns an empty JSON object if 'properties' not defined in config @@ -186,7 +256,7 @@ pub trait ChainSpec: BuildStorage + Send + Sync { /// This will be used as storage at genesis. fn set_storage(&mut self, storage: Storage); /// Returns code substitutes that should be used for the on chain wasm. - fn code_substitutes(&self) -> std::collections::HashMap>; + fn code_substitutes(&self) -> std::collections::BTreeMap>; } impl std::fmt::Debug for dyn ChainSpec { diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index e7a0330e76e0c..fd84ff4d4574b 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -3,9 +3,9 @@ name = "sc-cli" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Substrate CLI interface." -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,42 +13,44 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.11" -regex = "1.4.2" -tokio = { version = "1.10", features = [ "signal", "rt-multi-thread" ] } -futures = "0.3.9" +array-bytes = "4.1" +chrono = "0.4.10" +clap = { version = "4.0.9", features = ["derive", "string"] } fdlimit = "0.2.1" -libp2p = "0.39.1" -parity-scale-codec = "2.0.0" -hex = "0.4.2" +futures = "0.3.21" +libp2p = "0.49.0" +log = "0.4.17" +names = { version = "0.13.0", default-features = false } +parity-scale-codec = "3.0.0" rand = "0.7.3" -tiny-bip39 = "0.8.0" -serde_json = "1.0.68" -sc-keystore = { version = "4.0.0-dev", path = "../keystore" } -sp-panic-handler = { version = "3.0.0", path = "../../primitives/panic-handler" } +regex = "1.6.0" +rpassword = "7.0.0" +serde = "1.0.136" +serde_json = "1.0.85" +thiserror = "1.0.30" +tiny-bip39 = "0.8.2" +tokio = { version = "1.22.0", features = ["signal", "rt-multi-thread", "parking_lot"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../db" } +sc-keystore = { version = "4.0.0-dev", path = "../keystore" } sc-network = { version = "0.10.0-dev", path = "../network" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-version = { version = "4.0.0-dev", path = "../../primitives/version" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", path = "../../primitives/keystore" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../service" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -sp-keyring = { version = "4.0.0-dev", path = "../../primitives/keyring" } -names = { version = "0.12.0", default-features = false } -structopt = "0.3.8" sc-tracing = { version = "4.0.0-dev", path = "../tracing" } -chrono = "0.4.10" -serde = "1.0.126" -thiserror = "1.0.21" -rpassword = "5.0.0" +sc-utils = { version = "4.0.0-dev", path = "../utils" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +sp-panic-handler = { version = "5.0.0", path = "../../primitives/panic-handler" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } [dev-dependencies] tempfile = "3.1.0" +futures-timer = "3.0.1" [features] -wasmtime = [ - "sc-service/wasmtime", -] +default = ["rocksdb"] +rocksdb = ["sc-client-db/rocksdb"] diff --git a/client/cli/src/arg_enums.rs b/client/cli/src/arg_enums.rs index 5221500f08b33..20f68bc7fb55e 100644 --- a/client/cli/src/arg_enums.rs +++ b/client/cli/src/arg_enums.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -15,15 +15,47 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// NOTE: we allow missing docs here because arg_enum! creates the function variants without doc -#![allow(missing_docs)] -use structopt::clap::arg_enum; +//! Definitions of [`ValueEnum`] types. + +use clap::ValueEnum; + +/// The instantiation strategy to use in compiled mode. +#[derive(Debug, Clone, Copy, ValueEnum)] +#[value(rename_all = "kebab-case")] +pub enum WasmtimeInstantiationStrategy { + /// Pool the instances to avoid initializing everything from scratch + /// on each instantiation. Use copy-on-write memory when possible. + PoolingCopyOnWrite, + + /// Recreate the instance from scratch on every instantiation. + /// Use copy-on-write memory when possible. + RecreateInstanceCopyOnWrite, + + /// Pool the instances to avoid initializing everything from scratch + /// on each instantiation. + Pooling, + + /// Recreate the instance from scratch on every instantiation. Very slow. + RecreateInstance, + + /// Legacy instance reuse mechanism. DEPRECATED. Will be removed in the future. + /// + /// Should only be used in case of encountering any issues with the new default + /// instantiation strategy. + LegacyInstanceReuse, +} + +/// The default [`WasmtimeInstantiationStrategy`]. +pub const DEFAULT_WASMTIME_INSTANTIATION_STRATEGY: WasmtimeInstantiationStrategy = + WasmtimeInstantiationStrategy::PoolingCopyOnWrite; /// How to execute Wasm runtime code. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, ValueEnum)] +#[value(rename_all = "kebab-case")] pub enum WasmExecutionMethod { /// Uses an interpreter. + #[clap(name = "interpreted-i-know-what-i-do")] Interpreted, /// Uses a compiled runtime. Compiled, @@ -38,60 +70,40 @@ impl std::fmt::Display for WasmExecutionMethod { } } -impl std::str::FromStr for WasmExecutionMethod { - type Err = String; - - fn from_str(s: &str) -> Result { - if s.eq_ignore_ascii_case("interpreted-i-know-what-i-do") { - Ok(Self::Interpreted) - } else if s.eq_ignore_ascii_case("compiled") { - #[cfg(feature = "wasmtime")] - { - Ok(Self::Compiled) - } - #[cfg(not(feature = "wasmtime"))] - { - Err(format!("`Compiled` variant requires the `wasmtime` feature to be enabled")) - } - } else { - Err(format!("Unknown variant `{}`, known variants: {:?}", s, Self::variants())) - } - } -} - -impl WasmExecutionMethod { - /// Returns all the variants of this enum to be shown in the cli. - pub fn variants() -> &'static [&'static str] { - let variants = &["interpreted-i-know-what-i-do", "compiled"]; - if cfg!(feature = "wasmtime") { - variants - } else { - &variants[..1] - } +/// Converts the execution method and instantiation strategy command line arguments +/// into an execution method which can be used internally. +pub fn execution_method_from_cli( + execution_method: WasmExecutionMethod, + _instantiation_strategy: WasmtimeInstantiationStrategy, +) -> sc_service::config::WasmExecutionMethod { + match execution_method { + WasmExecutionMethod::Interpreted => sc_service::config::WasmExecutionMethod::Interpreted, + WasmExecutionMethod::Compiled => sc_service::config::WasmExecutionMethod::Compiled { + instantiation_strategy: match _instantiation_strategy { + WasmtimeInstantiationStrategy::PoolingCopyOnWrite => + sc_service::config::WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite => + sc_service::config::WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite, + WasmtimeInstantiationStrategy::Pooling => + sc_service::config::WasmtimeInstantiationStrategy::Pooling, + WasmtimeInstantiationStrategy::RecreateInstance => + sc_service::config::WasmtimeInstantiationStrategy::RecreateInstance, + WasmtimeInstantiationStrategy::LegacyInstanceReuse => + sc_service::config::WasmtimeInstantiationStrategy::LegacyInstanceReuse, + }, + }, } } -impl Into for WasmExecutionMethod { - fn into(self) -> sc_service::config::WasmExecutionMethod { - match self { - WasmExecutionMethod::Interpreted => - sc_service::config::WasmExecutionMethod::Interpreted, - #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => sc_service::config::WasmExecutionMethod::Compiled, - #[cfg(not(feature = "wasmtime"))] - WasmExecutionMethod::Compiled => panic!( - "Substrate must be compiled with \"wasmtime\" feature for compiled Wasm execution" - ), - } - } -} +/// The default [`WasmExecutionMethod`]. +pub const DEFAULT_WASM_EXECUTION_METHOD: WasmExecutionMethod = WasmExecutionMethod::Compiled; -arg_enum! { - #[allow(missing_docs)] - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - pub enum TracingReceiver { - Log, - } +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)] +#[value(rename_all = "kebab-case")] +pub enum TracingReceiver { + /// Output the tracing records using the log. + Log, } impl Into for TracingReceiver { @@ -102,44 +114,48 @@ impl Into for TracingReceiver { } } -arg_enum! { - #[allow(missing_docs)] - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - pub enum NodeKeyType { - Ed25519 - } +/// The type of the node key. +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)] +#[value(rename_all = "kebab-case")] +pub enum NodeKeyType { + /// Use ed25519. + Ed25519, } -arg_enum! { - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - pub enum CryptoScheme { - Ed25519, - Sr25519, - Ecdsa, - } +/// The crypto scheme to use. +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)] +#[value(rename_all = "kebab-case")] +pub enum CryptoScheme { + /// Use ed25519. + Ed25519, + /// Use sr25519. + Sr25519, + /// Use + Ecdsa, } -arg_enum! { - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - pub enum OutputType { - Json, - Text, - } +/// The type of the output format. +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)] +#[value(rename_all = "kebab-case")] +pub enum OutputType { + /// Output as json. + Json, + /// Output as text. + Text, } -arg_enum! { - /// How to execute blocks - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum ExecutionStrategy { - // Execute with native build (if available, WebAssembly otherwise). - Native, - // Only execute with the WebAssembly build. - Wasm, - // Execute with both native (where available) and WebAssembly builds. - Both, - // Execute with the native build if possible; if it fails, then execute with WebAssembly. - NativeElseWasm, - } +/// How to execute blocks +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)] +#[value(rename_all = "kebab-case")] +pub enum ExecutionStrategy { + /// Execute with native build (if available, WebAssembly otherwise). + Native, + /// Only execute with the WebAssembly build. + Wasm, + /// Execute with both native (where available) and WebAssembly builds. + Both, + /// Execute with the native build if possible; if it fails, then execute with WebAssembly. + NativeElseWasm, } impl Into for ExecutionStrategy { @@ -153,31 +169,18 @@ impl Into for ExecutionStrategy { } } -impl ExecutionStrategy { - /// Returns the variant as `'&static str`. - pub fn as_str(&self) -> &'static str { - match self { - Self::Native => "Native", - Self::Wasm => "Wasm", - Self::Both => "Both", - Self::NativeElseWasm => "NativeElseWasm", - } - } -} - -arg_enum! { - /// Available RPC methods. - #[allow(missing_docs)] - #[derive(Debug, Copy, Clone, PartialEq)] - pub enum RpcMethods { - // Expose every RPC method only when RPC is listening on `localhost`, - // otherwise serve only safe RPC methods. - Auto, - // Allow only a safe subset of RPC methods. - Safe, - // Expose every RPC method (even potentially unsafe ones). - Unsafe, - } +/// Available RPC methods. +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, PartialEq, ValueEnum)] +#[value(rename_all = "kebab-case")] +pub enum RpcMethods { + /// Expose every RPC method only when RPC is listening on `localhost`, + /// otherwise serve only safe RPC methods. + Auto, + /// Allow only a safe subset of RPC methods. + Safe, + /// Expose every RPC method (even potentially unsafe ones). + Unsafe, } impl Into for RpcMethods { @@ -191,65 +194,60 @@ impl Into for RpcMethods { } /// Database backend -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, PartialEq, Copy, clap::ValueEnum)] +#[value(rename_all = "lower")] pub enum Database { /// Facebooks RocksDB + #[cfg(feature = "rocksdb")] RocksDb, /// ParityDb. ParityDb, /// Detect whether there is an existing database. Use it, if there is, if not, create new - /// instance of paritydb + /// instance of ParityDb Auto, -} - -impl std::str::FromStr for Database { - type Err = String; - - fn from_str(s: &str) -> Result { - if s.eq_ignore_ascii_case("rocksdb") { - Ok(Self::RocksDb) - } else if s.eq_ignore_ascii_case("paritydb-experimental") { - Ok(Self::ParityDb) - } else if s.eq_ignore_ascii_case("auto") { - Ok(Self::Auto) - } else { - Err(format!("Unknown variant `{}`, known variants: {:?}", s, Self::variants())) - } - } + /// ParityDb. + #[value(name = "paritydb-experimental")] + ParityDbDeprecated, } impl Database { /// Returns all the variants of this enum to be shown in the cli. - pub fn variants() -> &'static [&'static str] { - &["rocksdb", "paritydb-experimental", "auto"] + pub const fn variants() -> &'static [&'static str] { + &[ + #[cfg(feature = "rocksdb")] + "rocksdb", + "paritydb", + "paritydb-experimental", + "auto", + ] } } -arg_enum! { - /// Whether off-chain workers are enabled. - #[allow(missing_docs)] - #[derive(Debug, Clone)] - pub enum OffchainWorkerEnabled { - Always, - Never, - WhenValidating, - } +/// Whether off-chain workers are enabled. +#[allow(missing_docs)] +#[derive(Debug, Clone, ValueEnum)] +#[value(rename_all = "kebab-case")] +pub enum OffchainWorkerEnabled { + /// Always have offchain worker enabled. + Always, + /// Never enable the offchain worker. + Never, + /// Only enable the offchain worker when running as validator. + WhenValidating, } -arg_enum! { - /// Syncing mode. - #[allow(missing_docs)] - #[derive(Debug, Clone, Copy)] - pub enum SyncMode { - // Full sync. Donwnload end verify all blocks. - Full, - // Download blocks without executing them. Download latest state with proofs. - Fast, - // Download blocks without executing them. Download latest state without proofs. - FastUnsafe, - // Prove finality and download the latest state. - Warp, - } +/// Syncing mode. +#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)] +#[value(rename_all = "kebab-case")] +pub enum SyncMode { + /// Full sync. Download end verify all blocks. + Full, + /// Download blocks without executing them. Download latest state with proofs. + Fast, + /// Download blocks without executing them. Download latest state without proofs. + FastUnsafe, + /// Prove finality and download the latest state. + Warp, } impl Into for SyncMode { @@ -266,14 +264,14 @@ impl Into for SyncMode { } /// Default value for the `--execution-syncing` parameter. -pub const DEFAULT_EXECUTION_SYNCING: ExecutionStrategy = ExecutionStrategy::NativeElseWasm; +pub const DEFAULT_EXECUTION_SYNCING: ExecutionStrategy = ExecutionStrategy::Wasm; /// Default value for the `--execution-import-block` parameter. -pub const DEFAULT_EXECUTION_IMPORT_BLOCK: ExecutionStrategy = ExecutionStrategy::NativeElseWasm; +pub const DEFAULT_EXECUTION_IMPORT_BLOCK: ExecutionStrategy = ExecutionStrategy::Wasm; /// Default value for the `--execution-import-block` parameter when the node is a validator. pub const DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR: ExecutionStrategy = ExecutionStrategy::Wasm; /// Default value for the `--execution-block-construction` parameter. pub const DEFAULT_EXECUTION_BLOCK_CONSTRUCTION: ExecutionStrategy = ExecutionStrategy::Wasm; /// Default value for the `--execution-offchain-worker` parameter. -pub const DEFAULT_EXECUTION_OFFCHAIN_WORKER: ExecutionStrategy = ExecutionStrategy::Native; +pub const DEFAULT_EXECUTION_OFFCHAIN_WORKER: ExecutionStrategy = ExecutionStrategy::Wasm; /// Default value for the `--execution-other` parameter. -pub const DEFAULT_EXECUTION_OTHER: ExecutionStrategy = ExecutionStrategy::Native; +pub const DEFAULT_EXECUTION_OTHER: ExecutionStrategy = ExecutionStrategy::Wasm; diff --git a/client/cli/src/commands/build_spec_cmd.rs b/client/cli/src/commands/build_spec_cmd.rs index 75fdf07643ee2..5ab3ce9e88a09 100644 --- a/client/cli/src/commands/build_spec_cmd.rs +++ b/client/cli/src/commands/build_spec_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,6 +21,7 @@ use crate::{ params::{NodeKeyParams, SharedParams}, CliConfiguration, }; +use clap::Parser; use log::info; use sc_network::config::build_multiaddr; use sc_service::{ @@ -28,28 +29,27 @@ use sc_service::{ ChainSpec, }; use std::io::Write; -use structopt::StructOpt; /// The `build-spec` command used to build a specification. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Parser)] pub struct BuildSpecCmd { /// Force raw genesis storage output. - #[structopt(long = "raw")] + #[arg(long)] pub raw: bool, /// Disable adding the default bootnode to the specification. /// /// By default the `/ip4/127.0.0.1/tcp/30333/p2p/NODE_PEER_ID` bootnode is added to the /// specification when no bootnode exists. - #[structopt(long = "disable-default-bootnode")] + #[arg(long)] pub disable_default_bootnode: bool, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: SharedParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub node_key_params: NodeKeyParams, } @@ -65,7 +65,7 @@ impl BuildSpecCmd { if spec.boot_nodes().is_empty() && !self.disable_default_bootnode { let keys = network_config.node_key.into_keypair()?; - let peer_id = keys.public().into_peer_id(); + let peer_id = keys.public().to_peer_id(); let addr = MultiaddrWithPeerId { multiaddr: build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(30333u16)], peer_id, diff --git a/client/cli/src/commands/chain_info_cmd.rs b/client/cli/src/commands/chain_info_cmd.rs new file mode 100644 index 0000000000000..cbc22cc4d52d9 --- /dev/null +++ b/client/cli/src/commands/chain_info_cmd.rs @@ -0,0 +1,102 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{CliConfiguration, DatabaseParams, PruningParams, Result as CliResult, SharedParams}; +use parity_scale_codec::{Decode, Encode}; +use sc_client_api::{backend::Backend as BackendT, blockchain::HeaderBackend}; +use sp_blockchain::Info; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use std::{fmt::Debug, io}; + +/// The `chain-info` subcommand used to output db meta columns information. +#[derive(Debug, Clone, clap::Parser)] +pub struct ChainInfoCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub pruning_params: PruningParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub database_params: DatabaseParams, +} + +/// Serializable `chain-info` subcommand output. +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, serde::Serialize)] +struct ChainInfo { + /// Best block hash. + best_hash: B::Hash, + /// Best block number. + best_number: <::Header as HeaderT>::Number, + /// Genesis block hash. + genesis_hash: B::Hash, + /// The head of the finalized chain. + finalized_hash: B::Hash, + /// Last finalized block number. + finalized_number: <::Header as HeaderT>::Number, +} + +impl From> for ChainInfo { + fn from(info: Info) -> Self { + ChainInfo:: { + best_hash: info.best_hash, + best_number: info.best_number, + genesis_hash: info.genesis_hash, + finalized_hash: info.finalized_hash, + finalized_number: info.finalized_number, + } + } +} + +impl ChainInfoCmd { + /// Run the `chain-info` subcommand + pub fn run(&self, config: &sc_service::Configuration) -> CliResult<()> + where + B: BlockT, + { + let db_config = sc_client_db::DatabaseSettings { + trie_cache_maximum_size: config.trie_cache_maximum_size, + state_pruning: config.state_pruning.clone(), + source: config.database.clone(), + blocks_pruning: config.blocks_pruning, + }; + let backend = sc_service::new_db_backend::(db_config)?; + let info: ChainInfo = backend.blockchain().info().into(); + let mut out = io::stdout(); + serde_json::to_writer_pretty(&mut out, &info) + .map_err(|e| format!("Error writing JSON: {}", e))?; + Ok(()) + } +} + +impl CliConfiguration for ChainInfoCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } + + fn pruning_params(&self) -> Option<&PruningParams> { + Some(&self.pruning_params) + } + + fn database_params(&self) -> Option<&DatabaseParams> { + Some(&self.database_params) + } +} diff --git a/client/cli/src/commands/check_block_cmd.rs b/client/cli/src/commands/check_block_cmd.rs index 07a76319dca3f..3e25eab2c4350 100644 --- a/client/cli/src/commands/check_block_cmd.rs +++ b/client/cli/src/commands/check_block_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,30 +21,30 @@ use crate::{ params::{BlockNumberOrHash, ImportParams, SharedParams}, CliConfiguration, }; -use sc_client_api::{BlockBackend, UsageProvider}; +use clap::Parser; +use sc_client_api::{BlockBackend, HeaderBackend}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use std::{fmt::Debug, str::FromStr, sync::Arc}; -use structopt::StructOpt; /// The `check-block` command used to validate blocks. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Parser)] pub struct CheckBlockCmd { /// Block hash or number - #[structopt(value_name = "HASH or NUMBER")] + #[arg(value_name = "HASH or NUMBER")] pub input: BlockNumberOrHash, /// The default number of 64KB pages to ever allocate for Wasm execution. /// /// Don't alter this unless you know what you're doing. - #[structopt(long = "default-heap-pages", value_name = "COUNT")] + #[arg(long, value_name = "COUNT")] pub default_heap_pages: Option, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: SharedParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub import_params: ImportParams, } @@ -53,7 +53,7 @@ impl CheckBlockCmd { pub async fn run(&self, client: Arc, import_queue: IQ) -> error::Result<()> where B: BlockT + for<'de> serde::Deserialize<'de>, - C: BlockBackend + UsageProvider + Send + Sync + 'static, + C: BlockBackend + HeaderBackend + Send + Sync + 'static, IQ: sc_service::ImportQueue + 'static, B::Hash: FromStr, ::Err: Debug, diff --git a/client/cli/src/commands/export_blocks_cmd.rs b/client/cli/src/commands/export_blocks_cmd.rs index ca3069442a1d3..e2f83200e511c 100644 --- a/client/cli/src/commands/export_blocks_cmd.rs +++ b/client/cli/src/commands/export_blocks_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,46 +21,46 @@ use crate::{ params::{DatabaseParams, GenericNumber, PruningParams, SharedParams}, CliConfiguration, }; +use clap::Parser; use log::info; use sc_client_api::{BlockBackend, UsageProvider}; use sc_service::{chain_ops::export_blocks, config::DatabaseSource}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use std::{fmt::Debug, fs, io, path::PathBuf, str::FromStr, sync::Arc}; -use structopt::StructOpt; /// The `export-blocks` command used to export blocks. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Parser)] pub struct ExportBlocksCmd { /// Output file name or stdout if unspecified. - #[structopt(parse(from_os_str))] + #[arg()] pub output: Option, /// Specify starting block number. /// /// Default is 1. - #[structopt(long = "from", value_name = "BLOCK")] + #[arg(long, value_name = "BLOCK")] pub from: Option, /// Specify last block number. /// /// Default is best block. - #[structopt(long = "to", value_name = "BLOCK")] + #[arg(long, value_name = "BLOCK")] pub to: Option, /// Use binary output rather than JSON. - #[structopt(long)] + #[arg(long)] pub binary: bool, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: SharedParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub pruning_params: PruningParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub database_params: DatabaseParams, } @@ -76,7 +76,7 @@ impl ExportBlocksCmd { C: BlockBackend + UsageProvider + 'static, <::Number as FromStr>::Err: Debug, { - if let DatabaseSource::RocksDb { ref path, .. } = database_config { + if let Some(path) = database_config.path() { info!("DB path: {}", path.display()); } diff --git a/client/cli/src/commands/export_state_cmd.rs b/client/cli/src/commands/export_state_cmd.rs index 36eabd2c24f5c..04bce0c1d707a 100644 --- a/client/cli/src/commands/export_state_cmd.rs +++ b/client/cli/src/commands/export_state_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,30 +18,34 @@ use crate::{ error, - params::{BlockNumberOrHash, PruningParams, SharedParams}, + params::{BlockNumberOrHash, DatabaseParams, PruningParams, SharedParams}, CliConfiguration, }; +use clap::Parser; use log::info; -use sc_client_api::{StorageProvider, UsageProvider}; +use sc_client_api::{HeaderBackend, StorageProvider, UsageProvider}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use std::{fmt::Debug, io::Write, str::FromStr, sync::Arc}; -use structopt::StructOpt; /// The `export-state` command used to export the state of a given block into /// a chain spec. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Parser)] pub struct ExportStateCmd { /// Block hash or number. - #[structopt(value_name = "HASH or NUMBER")] + #[arg(value_name = "HASH or NUMBER")] pub input: Option, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: SharedParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub pruning_params: PruningParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub database_params: DatabaseParams, } impl ExportStateCmd { @@ -53,7 +57,7 @@ impl ExportStateCmd { ) -> error::Result<()> where B: BlockT, - C: UsageProvider + StorageProvider, + C: UsageProvider + StorageProvider + HeaderBackend, BA: sc_client_api::backend::Backend, B::Hash: FromStr, ::Err: Debug, @@ -61,7 +65,11 @@ impl ExportStateCmd { { info!("Exporting raw state..."); let block_id = self.input.as_ref().map(|b| b.parse()).transpose()?; - let raw_state = sc_service::chain_ops::export_raw_state(client, block_id)?; + let hash = match block_id { + Some(id) => client.expect_block_hash_from_id(&id)?, + None => client.usage_info().chain.best_hash, + }; + let raw_state = sc_service::chain_ops::export_raw_state(client, hash)?; input_spec.set_storage(raw_state); info!("Generating new chain spec..."); @@ -81,4 +89,8 @@ impl CliConfiguration for ExportStateCmd { fn pruning_params(&self) -> Option<&PruningParams> { Some(&self.pruning_params) } + + fn database_params(&self) -> Option<&DatabaseParams> { + Some(&self.database_params) + } } diff --git a/client/cli/src/commands/generate.rs b/client/cli/src/commands/generate.rs index 7032ebd72e0c7..461cb98bc2e51 100644 --- a/client/cli/src/commands/generate.rs +++ b/client/cli/src/commands/generate.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,30 +21,30 @@ use crate::{ NetworkSchemeFlag, OutputTypeFlag, }; use bip39::{Language, Mnemonic, MnemonicType}; -use structopt::StructOpt; +use clap::Parser; /// The `generate` command -#[derive(Debug, StructOpt, Clone)] -#[structopt(name = "generate", about = "Generate a random account")] +#[derive(Debug, Clone, Parser)] +#[command(name = "generate", about = "Generate a random account")] pub struct GenerateCmd { /// The number of words in the phrase to generate. One of 12 (default), 15, 18, 21 and 24. - #[structopt(long, short = "w", value_name = "WORDS")] + #[arg(short = 'w', long, value_name = "WORDS")] words: Option, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub keystore_params: KeystoreParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub network_scheme: NetworkSchemeFlag, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub output_scheme: OutputTypeFlag, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub crypto_scheme: CryptoSchemeFlag, } @@ -61,16 +61,11 @@ impl GenerateCmd { }; let mnemonic = Mnemonic::new(words, Language::English); let password = self.keystore_params.read_password()?; - let output = self.output_scheme.output_type.clone(); + let output = self.output_scheme.output_type; with_crypto_scheme!( self.crypto_scheme.scheme, - print_from_uri( - mnemonic.phrase(), - password, - self.network_scheme.network.clone(), - output, - ) + print_from_uri(mnemonic.phrase(), password, self.network_scheme.network, output) ); Ok(()) } @@ -78,12 +73,11 @@ impl GenerateCmd { #[cfg(test)] mod tests { - use super::GenerateCmd; - use structopt::StructOpt; + use super::*; #[test] fn generate() { - let generate = GenerateCmd::from_iter(&["generate", "--password", "12345"]); + let generate = GenerateCmd::parse_from(&["generate", "--password", "12345"]); assert!(generate.run().is_ok()) } } diff --git a/client/cli/src/commands/generate_node_key.rs b/client/cli/src/commands/generate_node_key.rs index 74a4197f36621..e84b4a71d6d72 100644 --- a/client/cli/src/commands/generate_node_key.rs +++ b/client/cli/src/commands/generate_node_key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,39 +18,54 @@ //! Implementation of the `generate-node-key` subcommand use crate::Error; +use clap::Parser; use libp2p::identity::{ed25519 as libp2p_ed25519, PublicKey}; -use std::{fs, path::PathBuf}; -use structopt::StructOpt; +use std::{ + fs, + io::{self, Write}, + path::PathBuf, +}; /// The `generate-node-key` command -#[derive(Debug, StructOpt)] -#[structopt( +#[derive(Debug, Parser)] +#[command( name = "generate-node-key", - about = "Generate a random node libp2p key, save it to \ - file or print it to stdout and print its peer ID to stderr" + about = "Generate a random node key, write it to a file or stdout \ + and write the corresponding peer-id to stderr" )] pub struct GenerateNodeKeyCmd { /// Name of file to save secret key to. /// /// If not given, the secret key is printed to stdout. - #[structopt(long)] + #[arg(long)] file: Option, + + /// The output is in raw binary format. + /// + /// If not given, the output is written as an hex encoded string. + #[arg(long)] + bin: bool, } impl GenerateNodeKeyCmd { /// Run the command pub fn run(&self) -> Result<(), Error> { let keypair = libp2p_ed25519::Keypair::generate(); + let secret = keypair.secret(); - let peer_id = PublicKey::Ed25519(keypair.public()).into_peer_id(); - let secret_hex = hex::encode(secret.as_ref()); + + let file_data = if self.bin { + secret.as_ref().to_owned() + } else { + array_bytes::bytes2hex("", secret.as_ref()).into_bytes() + }; match &self.file { - Some(file) => fs::write(file, secret_hex)?, - None => print!("{}", secret_hex), + Some(file) => fs::write(file, file_data)?, + None => io::stdout().lock().write_all(&file_data)?, } - eprintln!("{}", peer_id); + eprintln!("{}", PublicKey::Ed25519(keypair.public()).to_peer_id()); Ok(()) } @@ -66,10 +81,10 @@ mod tests { fn generate_node_key() { let mut file = Builder::new().prefix("keyfile").tempfile().unwrap(); let file_path = file.path().display().to_string(); - let generate = GenerateNodeKeyCmd::from_iter(&["generate-node-key", "--file", &file_path]); + let generate = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", &file_path]); assert!(generate.run().is_ok()); let mut buf = String::new(); assert!(file.read_to_string(&mut buf).is_ok()); - assert!(hex::decode(buf).is_ok()); + assert!(array_bytes::hex2bytes(&buf).is_ok()); } } diff --git a/client/cli/src/commands/import_blocks_cmd.rs b/client/cli/src/commands/import_blocks_cmd.rs index 9b211b88d5563..debc697242ddd 100644 --- a/client/cli/src/commands/import_blocks_cmd.rs +++ b/client/cli/src/commands/import_blocks_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,7 +21,8 @@ use crate::{ params::{ImportParams, SharedParams}, CliConfiguration, }; -use sc_client_api::UsageProvider; +use clap::Parser; +use sc_client_api::HeaderBackend; use sc_service::chain_ops::import_blocks; use sp_runtime::traits::Block as BlockT; use std::{ @@ -31,31 +32,30 @@ use std::{ path::PathBuf, sync::Arc, }; -use structopt::StructOpt; /// The `import-blocks` command used to import blocks. -#[derive(Debug, StructOpt)] +#[derive(Debug, Parser)] pub struct ImportBlocksCmd { /// Input file or stdin if unspecified. - #[structopt(parse(from_os_str))] + #[arg()] pub input: Option, /// The default number of 64KB pages to ever allocate for Wasm execution. /// /// Don't alter this unless you know what you're doing. - #[structopt(long = "default-heap-pages", value_name = "COUNT")] + #[arg(long, value_name = "COUNT")] pub default_heap_pages: Option, /// Try importing blocks from binary format rather than JSON. - #[structopt(long)] + #[arg(long)] pub binary: bool, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: SharedParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub import_params: ImportParams, } @@ -68,17 +68,13 @@ impl ImportBlocksCmd { /// Run the import-blocks command pub async fn run(&self, client: Arc, import_queue: IQ) -> error::Result<()> where - C: UsageProvider + Send + Sync + 'static, + C: HeaderBackend + Send + Sync + 'static, B: BlockT + for<'de> serde::Deserialize<'de>, IQ: sc_service::ImportQueue + 'static, { - let file: Box = match &self.input { + let file: Box = match &self.input { Some(filename) => Box::new(fs::File::open(filename)?), - None => { - let mut buffer = Vec::new(); - io::stdin().read_to_end(&mut buffer)?; - Box::new(io::Cursor::new(buffer)) - }, + None => Box::new(io::stdin()), }; import_blocks(client, import_queue, file, false, self.binary) diff --git a/client/cli/src/commands/insert_key.rs b/client/cli/src/commands/insert_key.rs index 05055dc53c1e2..7d66a680df8c0 100644 --- a/client/cli/src/commands/insert_key.rs +++ b/client/cli/src/commands/insert_key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,40 +18,40 @@ //! Implementation of the `insert` subcommand use crate::{ - utils, with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, SharedParams, SubstrateCli, + utils, with_crypto_scheme, CryptoScheme, Error, KeystoreParams, SharedParams, SubstrateCli, }; +use clap::Parser; use sc_keystore::LocalKeystore; use sc_service::config::{BasePath, KeystoreConfig}; use sp_core::crypto::{KeyTypeId, SecretString}; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; -use std::{convert::TryFrom, sync::Arc}; -use structopt::StructOpt; +use std::sync::Arc; /// The `insert` command -#[derive(Debug, StructOpt, Clone)] -#[structopt(name = "insert", about = "Insert a key to the keystore of a node.")] +#[derive(Debug, Clone, Parser)] +#[command(name = "insert", about = "Insert a key to the keystore of a node.")] pub struct InsertKeyCmd { /// The secret key URI. /// If the value is a file, the file content is used as URI. /// If not given, you will be prompted for the URI. - #[structopt(long)] + #[arg(long)] suri: Option, /// Key type, examples: "gran", or "imon" - #[structopt(long)] + #[arg(long)] key_type: String, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: SharedParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub keystore_params: KeystoreParams, - #[allow(missing_docs)] - #[structopt(flatten)] - pub crypto_scheme: CryptoSchemeFlag, + /// The cryptography scheme that should be used to generate the key out of the given URI. + #[arg(long, value_name = "SCHEME", value_enum, ignore_case = true)] + pub scheme: CryptoScheme, } impl InsertKeyCmd { @@ -60,7 +60,7 @@ impl InsertKeyCmd { let suri = utils::read_uri(self.suri.as_ref())?; let base_path = self .shared_params - .base_path() + .base_path()? .unwrap_or_else(|| BasePath::from_project("", "", &C::executable_name())); let chain_id = self.shared_params.chain_id(self.shared_params.is_dev()); let chain_spec = cli.load_spec(&chain_id)?; @@ -68,10 +68,7 @@ impl InsertKeyCmd { let (keystore, public) = match self.keystore_params.keystore_config(&config_dir)? { (_, KeystoreConfig::Path { path, password }) => { - let public = with_crypto_scheme!( - self.crypto_scheme.scheme, - to_vec(&suri, password.clone()) - )?; + let public = with_crypto_scheme!(self.scheme, to_vec(&suri, password.clone()))?; let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::open(path, password)?); (keystore, public) }, @@ -97,8 +94,7 @@ fn to_vec(uri: &str, pass: Option) -> Result, /// Is the given `uri` a hex encoded public key? - #[structopt(long)] + #[arg(long)] public: bool, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub keystore_params: KeystoreParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub network_scheme: NetworkSchemeFlag, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub output_scheme: OutputTypeFlag, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub crypto_scheme: CryptoSchemeFlag, + + /// Expect that `--uri` has the given public key/account-id. + /// + /// If `--uri` has any derivations, the public key is checked against the base `uri`, i.e. the + /// `uri` without any derivation applied. However, if `uri` has a password or there is one + /// given by `--password`, it will be used to decrypt `uri` before comparing the public + /// key/account-id. + /// + /// If there is no derivation in `--uri`, the public key will be checked against the public key + /// of `--uri` directly. + #[arg(long, conflicts_with = "public")] + pub expect_public: Option, } impl InspectKeyCmd { @@ -72,18 +87,25 @@ impl InspectKeyCmd { self.crypto_scheme.scheme, print_from_public( &uri, - self.network_scheme.network.clone(), - self.output_scheme.output_type.clone(), + self.network_scheme.network, + self.output_scheme.output_type, ) )?; } else { + if let Some(ref expect_public) = self.expect_public { + with_crypto_scheme!( + self.crypto_scheme.scheme, + expect_public_from_phrase(expect_public, &uri, password.as_ref()) + )?; + } + with_crypto_scheme!( self.crypto_scheme.scheme, print_from_uri( &uri, password, - self.network_scheme.network.clone(), - self.output_scheme.output_type.clone(), + self.network_scheme.network, + self.output_scheme.output_type, ) ); } @@ -92,10 +114,50 @@ impl InspectKeyCmd { } } +/// Checks that `expect_public` is the public key of `suri`. +/// +/// If `suri` has any derivations, `expect_public` is checked against the public key of the "bare" +/// `suri`, i.e. without any derivations. +/// +/// Returns an error if the public key does not match. +fn expect_public_from_phrase( + expect_public: &str, + suri: &str, + password: Option<&SecretString>, +) -> Result<(), Error> { + let secret_uri = SecretUri::from_str(suri).map_err(|e| format!("{:?}", e))?; + let expected_public = if let Some(public) = expect_public.strip_prefix("0x") { + let hex_public = array_bytes::hex2bytes(public) + .map_err(|_| format!("Invalid expected public key hex: `{}`", expect_public))?; + Pair::Public::try_from(&hex_public) + .map_err(|_| format!("Invalid expected public key: `{}`", expect_public))? + } else { + Pair::Public::from_string_with_version(expect_public) + .map_err(|_| format!("Invalid expected account id: `{}`", expect_public))? + .0 + }; + + let pair = Pair::from_string_with_seed( + secret_uri.phrase.expose_secret().as_str(), + password + .or_else(|| secret_uri.password.as_ref()) + .map(|p| p.expose_secret().as_str()), + ) + .map_err(|_| format!("Invalid secret uri: {}", suri))? + .0; + + if pair.public() == expected_public { + Ok(()) + } else { + Err(format!("Expected public ({}) key does not match.", expect_public).into()) + } +} + #[cfg(test)] mod tests { use super::*; - use structopt::StructOpt; + use sp_core::crypto::{ByteArray, Pair}; + use sp_runtime::traits::IdentifyAccount; #[test] fn inspect() { @@ -103,10 +165,10 @@ mod tests { "remember fiber forum demise paper uniform squirrel feel access exclude casual effort"; let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5"; - let inspect = InspectKeyCmd::from_iter(&["inspect-key", words, "--password", "12345"]); + let inspect = InspectKeyCmd::parse_from(&["inspect-key", words, "--password", "12345"]); assert!(inspect.run().is_ok()); - let inspect = InspectKeyCmd::from_iter(&["inspect-key", seed]); + let inspect = InspectKeyCmd::parse_from(&["inspect-key", seed]); assert!(inspect.run().is_ok()); } @@ -114,7 +176,93 @@ mod tests { fn inspect_public_key() { let public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069"; - let inspect = InspectKeyCmd::from_iter(&["inspect-key", "--public", public]); + let inspect = InspectKeyCmd::parse_from(&["inspect-key", "--public", public]); assert!(inspect.run().is_ok()); } + + #[test] + fn inspect_with_expected_public_key() { + let check_cmd = |seed, expected_public, success| { + let inspect = InspectKeyCmd::parse_from(&[ + "inspect-key", + "--expect-public", + expected_public, + seed, + ]); + let res = inspect.run(); + + if success { + assert!(res.is_ok()); + } else { + assert!(res.unwrap_err().to_string().contains(&format!( + "Expected public ({}) key does not match.", + expected_public + ))); + } + }; + + let seed = + "remember fiber forum demise paper uniform squirrel feel access exclude casual effort"; + let invalid_public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069"; + let valid_public = sp_core::sr25519::Pair::from_string_with_seed(seed, None) + .expect("Valid") + .0 + .public(); + let valid_public_hex = array_bytes::bytes2hex("0x", valid_public.as_slice()); + let valid_accountid = format!("{}", valid_public.into_account()); + + // It should fail with the invalid public key + check_cmd(seed, invalid_public, false); + + // It should work with the valid public key & account id + check_cmd(seed, &valid_public_hex, true); + check_cmd(seed, &valid_accountid, true); + + let password = "test12245"; + let seed_with_password = format!("{}///{}", seed, password); + let valid_public_with_password = + sp_core::sr25519::Pair::from_string_with_seed(&seed_with_password, Some(password)) + .expect("Valid") + .0 + .public(); + let valid_public_hex_with_password = + array_bytes::bytes2hex("0x", valid_public_with_password.as_slice()); + let valid_accountid_with_password = + format!("{}", &valid_public_with_password.into_account()); + + // Only the public key that corresponds to the seed with password should be accepted. + check_cmd(&seed_with_password, &valid_public_hex, false); + check_cmd(&seed_with_password, &valid_accountid, false); + + check_cmd(&seed_with_password, &valid_public_hex_with_password, true); + check_cmd(&seed_with_password, &valid_accountid_with_password, true); + + let seed_with_password_and_derivation = format!("{}//test//account///{}", seed, password); + + let valid_public_with_password_and_derivation = + sp_core::sr25519::Pair::from_string_with_seed( + &seed_with_password_and_derivation, + Some(password), + ) + .expect("Valid") + .0 + .public(); + let valid_public_hex_with_password_and_derivation = + array_bytes::bytes2hex("0x", valid_public_with_password_and_derivation.as_slice()); + + // They should still be valid, because we check the base secret key. + check_cmd(&seed_with_password_and_derivation, &valid_public_hex_with_password, true); + check_cmd(&seed_with_password_and_derivation, &valid_accountid_with_password, true); + + // And these should be invalid. + check_cmd(&seed_with_password_and_derivation, &valid_public_hex, false); + check_cmd(&seed_with_password_and_derivation, &valid_accountid, false); + + // The public of the derived account should fail. + check_cmd( + &seed_with_password_and_derivation, + &valid_public_hex_with_password_and_derivation, + false, + ); + } } diff --git a/client/cli/src/commands/inspect_node_key.rs b/client/cli/src/commands/inspect_node_key.rs index 92a71f8975052..9300007cb6bf2 100644 --- a/client/cli/src/commands/inspect_node_key.rs +++ b/client/cli/src/commands/inspect_node_key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,39 +17,65 @@ //! Implementation of the `inspect-node-key` subcommand -use crate::{Error, NetworkSchemeFlag}; +use crate::Error; +use clap::Parser; use libp2p::identity::{ed25519, PublicKey}; -use std::{fs, path::PathBuf}; -use structopt::StructOpt; +use std::{ + fs, + io::{self, Read}, + path::PathBuf, +}; /// The `inspect-node-key` command -#[derive(Debug, StructOpt)] -#[structopt( +#[derive(Debug, Parser)] +#[command( name = "inspect-node-key", - about = "Print the peer ID corresponding to the node key in the given file." + about = "Load a node key from a file or stdin and print the corresponding peer-id." )] pub struct InspectNodeKeyCmd { /// Name of file to read the secret key from. - #[structopt(long)] - file: PathBuf, + /// + /// If not given, the secret key is read from stdin (up to EOF). + #[arg(long)] + file: Option, - #[allow(missing_docs)] - #[structopt(flatten)] - pub network_scheme: NetworkSchemeFlag, + /// The input is in raw binary format. + /// + /// If not given, the input is read as an hex encoded string. + #[arg(long)] + bin: bool, + + /// This argument is deprecated and has no effect for this command. + #[deprecated(note = "Network identifier is not used for node-key inspection")] + #[arg(short = 'n', long = "network", value_name = "NETWORK", ignore_case = true)] + pub network_scheme: Option, } impl InspectNodeKeyCmd { /// runs the command pub fn run(&self) -> Result<(), Error> { - let mut file_content = - hex::decode(fs::read(&self.file)?).map_err(|_| "failed to decode secret as hex")?; + let mut file_data = match &self.file { + Some(file) => fs::read(&file)?, + None => { + let mut buf = Vec::with_capacity(64); + io::stdin().lock().read_to_end(&mut buf)?; + buf + }, + }; + + if !self.bin { + // With hex input, give to the user a bit of tolerance about whitespaces + let keyhex = String::from_utf8_lossy(&file_data); + file_data = array_bytes::hex2bytes(keyhex.trim()) + .map_err(|_| "failed to decode secret as hex")?; + } + let secret = - ed25519::SecretKey::from_bytes(&mut file_content).map_err(|_| "Bad node key file")?; + ed25519::SecretKey::from_bytes(&mut file_data).map_err(|_| "Bad node key file")?; let keypair = ed25519::Keypair::from(secret); - let peer_id = PublicKey::Ed25519(keypair.public()).into_peer_id(); - println!("{}", peer_id); + println!("{}", PublicKey::Ed25519(keypair.public()).to_peer_id()); Ok(()) } @@ -63,11 +89,11 @@ mod tests { fn inspect_node_key() { let path = tempfile::tempdir().unwrap().into_path().join("node-id").into_os_string(); let path = path.to_str().unwrap(); - let cmd = GenerateNodeKeyCmd::from_iter(&["generate-node-key", "--file", path.clone()]); + let cmd = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", path.clone()]); assert!(cmd.run().is_ok()); - let cmd = InspectNodeKeyCmd::from_iter(&["inspect-node-key", "--file", path]); + let cmd = InspectNodeKeyCmd::parse_from(&["inspect-node-key", "--file", path]); assert!(cmd.run().is_ok()); } } diff --git a/client/cli/src/commands/key.rs b/client/cli/src/commands/key.rs index 8e1103a8ca512..59cca554bfa37 100644 --- a/client/cli/src/commands/key.rs +++ b/client/cli/src/commands/key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,19 +17,17 @@ //! Key related CLI utilities -use crate::{Error, SubstrateCli}; -use structopt::StructOpt; - use super::{ generate::GenerateCmd, generate_node_key::GenerateNodeKeyCmd, insert_key::InsertKeyCmd, inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd, }; +use crate::{Error, SubstrateCli}; /// Key utilities for the cli. -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Subcommand)] pub enum KeySubcommand { - /// Generate a random node libp2p key, save it to file or print it to stdout - /// and print its peer ID to stderr. + /// Generate a random node key, write it to a file or stdout and write the + /// corresponding peer-id to stderr GenerateNodeKey(GenerateNodeKeyCmd), /// Generate a random account @@ -38,7 +36,7 @@ pub enum KeySubcommand { /// Gets a public key and a SS58 address from the provided Secret URI Inspect(InspectKeyCmd), - /// Print the peer ID corresponding to the node key in the given file + /// Load a node key from a file or stdin and print the corresponding peer-id InspectNodeKey(InspectNodeKeyCmd), /// Insert a key to the keystore of a node. diff --git a/client/cli/src/commands/mod.rs b/client/cli/src/commands/mod.rs index 9e7c5689b49c8..8e84afa34e24a 100644 --- a/client/cli/src/commands/mod.rs +++ b/client/cli/src/commands/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . mod build_spec_cmd; +mod chain_info_cmd; mod check_block_cmd; mod export_blocks_cmd; mod export_state_cmd; @@ -35,7 +36,7 @@ mod vanity; mod verify; pub use self::{ - build_spec_cmd::BuildSpecCmd, check_block_cmd::CheckBlockCmd, + 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, insert_key::InsertKeyCmd, inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd, diff --git a/client/cli/src/commands/purge_chain_cmd.rs b/client/cli/src/commands/purge_chain_cmd.rs index e1bdb3a03cc59..9a3aeee50e944 100644 --- a/client/cli/src/commands/purge_chain_cmd.rs +++ b/client/cli/src/commands/purge_chain_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,27 +21,27 @@ use crate::{ params::{DatabaseParams, SharedParams}, CliConfiguration, }; +use clap::Parser; use sc_service::DatabaseSource; use std::{ fmt::Debug, fs, io::{self, Write}, }; -use structopt::StructOpt; /// The `purge-chain` command used to remove the whole chain. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Parser)] pub struct PurgeChainCmd { /// Skip interactive prompt by answering yes automatically. - #[structopt(short = "y")] + #[arg(short = 'y')] pub yes: bool, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: SharedParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub database_params: DatabaseParams, } @@ -60,7 +60,7 @@ impl PurgeChainCmd { io::stdin().read_line(&mut input)?; let input = input.trim(); - match input.chars().nth(0) { + match input.chars().next() { Some('y') | Some('Y') => {}, _ => { println!("Aborted"); diff --git a/client/cli/src/commands/revert_cmd.rs b/client/cli/src/commands/revert_cmd.rs index 9ad49a03aa5fd..8477630cf9404 100644 --- a/client/cli/src/commands/revert_cmd.rs +++ b/client/cli/src/commands/revert_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,31 +21,40 @@ use crate::{ params::{GenericNumber, PruningParams, SharedParams}, CliConfiguration, }; +use clap::Parser; use sc_client_api::{Backend, UsageProvider}; use sc_service::chain_ops::revert_chain; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use std::{fmt::Debug, str::FromStr, sync::Arc}; -use structopt::StructOpt; /// The `revert` command used revert the chain to a previous state. -#[derive(Debug, StructOpt)] +#[derive(Debug, Parser)] pub struct RevertCmd { /// Number of blocks to revert. - #[structopt(default_value = "256")] + #[arg(default_value = "256")] pub num: GenericNumber, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: SharedParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub pruning_params: PruningParams, } +/// Revert handler for auxiliary data (e.g. consensus). +type AuxRevertHandler = + Box, Arc, NumberFor) -> error::Result<()>>; + impl RevertCmd { /// Run the revert command - pub async fn run(&self, client: Arc, backend: Arc) -> error::Result<()> + pub async fn run( + &self, + client: Arc, + backend: Arc, + aux_revert: Option>, + ) -> error::Result<()> where B: BlockT, BA: Backend, @@ -53,6 +62,9 @@ impl RevertCmd { <<::Header as HeaderT>::Number as FromStr>::Err: Debug, { let blocks = self.num.parse()?; + if let Some(aux_revert) = aux_revert { + aux_revert(client.clone(), backend.clone(), blocks)?; + } revert_chain(client, backend, blocks)?; Ok(()) diff --git a/client/cli/src/commands/run_cmd.rs b/client/cli/src/commands/run_cmd.rs index 98f2090c6f446..35181d83f805f 100644 --- a/client/cli/src/commands/run_cmd.rs +++ b/client/cli/src/commands/run_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -25,6 +25,7 @@ use crate::{ }, CliConfiguration, }; +use clap::Parser; use regex::Regex; use sc_service::{ config::{BasePath, PrometheusConfig, TransactionPoolOptions}, @@ -32,55 +33,50 @@ use sc_service::{ }; use sc_telemetry::TelemetryEndpoints; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use structopt::StructOpt; /// The `run` command used to run a node. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Parser)] pub struct RunCmd { /// Enable validator mode. /// /// The node will be started with the authority role and actively /// participate in any consensus task that it can (e.g. depending on /// availability of local keys). - #[structopt(long)] + #[arg(long)] pub validator: bool, /// Disable GRANDPA voter when running in validator mode, otherwise disable the GRANDPA /// observer. - #[structopt(long)] + #[arg(long)] pub no_grandpa: bool, - /// Experimental: Run in light client mode. - #[structopt(long = "light")] - pub light: bool, - /// Listen to all RPC interfaces. /// /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC /// proxy server to filter out dangerous methods. More details: - /// . + /// . /// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks. - #[structopt(long = "rpc-external")] + #[arg(long)] pub rpc_external: bool, /// Listen to all RPC interfaces. /// /// Same as `--rpc-external`. - #[structopt(long)] + #[arg(long)] pub unsafe_rpc_external: bool, /// RPC methods to expose. /// - /// - `Unsafe`: Exposes every RPC method. - /// - `Safe`: Exposes only a safe subset of RPC methods, denying unsafe RPC methods. - /// - `Auto`: Acts as `Safe` if RPC is served externally, e.g. when `--{rpc,ws}-external` is - /// passed, otherwise acts as `Unsafe`. - #[structopt( + /// - `unsafe`: Exposes every RPC method. + /// - `safe`: Exposes only a safe subset of RPC methods, denying unsafe RPC methods. + /// - `auto`: Acts as `safe` if RPC is served externally, e.g. when `--{rpc,ws}-external` is + /// passed, otherwise acts as `unsafe`. + #[arg( long, value_name = "METHOD SET", - possible_values = &RpcMethods::variants(), - case_insensitive = true, - default_value = "Auto", + value_enum, + ignore_case = true, + default_value_t = RpcMethods::Auto, verbatim_doc_comment )] pub rpc_methods: RpcMethods, @@ -89,73 +85,92 @@ pub struct RunCmd { /// /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC /// proxy server to filter out dangerous methods. More details: - /// . + /// . /// Use `--unsafe-ws-external` to suppress the warning if you understand the risks. - #[structopt(long = "ws-external")] + #[arg(long)] pub ws_external: bool, /// Listen to all Websocket interfaces. /// /// Same as `--ws-external` but doesn't warn you about it. - #[structopt(long = "unsafe-ws-external")] + #[arg(long)] pub unsafe_ws_external: bool, - /// Set the the maximum RPC payload size for both requests and responses (both http and ws), in - /// megabytes. Default is 15MiB. - #[structopt(long = "rpc-max-payload")] + /// DEPRECATED, this has no affect anymore. Use `rpc_max_request_size` or + /// `rpc_max_response_size` instead. + #[arg(long)] pub rpc_max_payload: Option, + /// Set the the maximum RPC request payload size for both HTTP and WS in megabytes. + /// Default is 15MiB. + #[arg(long)] + pub rpc_max_request_size: Option, + + /// Set the the maximum RPC response payload size for both HTTP and WS in megabytes. + /// Default is 15MiB. + #[arg(long)] + pub rpc_max_response_size: Option, + + /// Set the the maximum concurrent subscriptions per connection. + /// Default is 1024. + #[arg(long)] + pub rpc_max_subscriptions_per_connection: Option, + /// Expose Prometheus exporter on all interfaces. /// /// Default is local. - #[structopt(long = "prometheus-external")] + #[arg(long)] pub prometheus_external: bool, - /// Specify IPC RPC server path - #[structopt(long = "ipc-path", value_name = "PATH")] + /// DEPRECATED, IPC support has been removed. + #[arg(long, value_name = "PATH")] pub ipc_path: Option, /// Specify HTTP RPC server TCP port. - #[structopt(long = "rpc-port", value_name = "PORT")] + #[arg(long, value_name = "PORT")] pub rpc_port: Option, /// Specify WebSockets RPC server TCP port. - #[structopt(long = "ws-port", value_name = "PORT")] + #[arg(long, value_name = "PORT")] pub ws_port: Option, /// Maximum number of WS RPC server connections. - #[structopt(long = "ws-max-connections", value_name = "COUNT")] + #[arg(long, value_name = "COUNT")] pub ws_max_connections: Option, + /// DEPRECATED, this has no affect anymore. Use `rpc_max_response_size` instead. + #[arg(long)] + pub ws_max_out_buffer_capacity: Option, + /// Specify browser Origins allowed to access the HTTP & WS RPC servers. /// /// A comma-separated list of origins (protocol://domain or special `null` /// value). Value of `all` will disable origin validation. Default is to /// allow localhost and origins. When running in /// --dev mode the default is to allow all origins. - #[structopt(long = "rpc-cors", value_name = "ORIGINS", parse(try_from_str = parse_cors))] + #[arg(long, value_name = "ORIGINS", value_parser = parse_cors)] pub rpc_cors: Option, /// Specify Prometheus exporter TCP Port. - #[structopt(long = "prometheus-port", value_name = "PORT")] + #[arg(long, value_name = "PORT")] pub prometheus_port: Option, /// Do not expose a Prometheus exporter endpoint. /// /// Prometheus metric endpoint is enabled by default. - #[structopt(long = "no-prometheus")] + #[arg(long)] pub no_prometheus: bool, /// The human-readable name for this node. /// /// The node name will be reported to the telemetry server, if enabled. - #[structopt(long = "name", value_name = "NAME")] + #[arg(long, value_name = "NAME")] pub name: Option, /// Disable connecting to the Substrate telemetry server. /// /// Telemetry is on by default on global chains. - #[structopt(long = "no-telemetry")] + #[arg(long)] pub no_telemetry: bool, /// The URL of the telemetry server to connect to. @@ -164,76 +179,80 @@ pub struct RunCmd { /// telemetry endpoints. Verbosity levels range from 0-9, with 0 denoting /// the least verbosity. /// Expected format is 'URL VERBOSITY', e.g. `--telemetry-url 'wss://foo/bar 0'`. - #[structopt(long = "telemetry-url", value_name = "URL VERBOSITY", parse(try_from_str = parse_telemetry_endpoints))] + #[arg(long = "telemetry-url", value_name = "URL VERBOSITY", value_parser = parse_telemetry_endpoints)] pub telemetry_endpoints: Vec<(String, u8)>, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub offchain_worker_params: OffchainWorkerParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: SharedParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub import_params: ImportParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub network_params: NetworkParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub pool_config: TransactionPoolParams, /// Shortcut for `--name Alice --validator` with session keys for `Alice` added to keystore. - #[structopt(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])] + #[arg(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])] pub alice: bool, /// Shortcut for `--name Bob --validator` with session keys for `Bob` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])] + #[arg(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])] pub bob: bool, /// Shortcut for `--name Charlie --validator` with session keys for `Charlie` added to /// keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])] + #[arg(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])] pub charlie: bool, /// Shortcut for `--name Dave --validator` with session keys for `Dave` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "eve", "ferdie", "one", "two"])] + #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "eve", "ferdie", "one", "two"])] pub dave: bool, /// Shortcut for `--name Eve --validator` with session keys for `Eve` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "ferdie", "one", "two"])] + #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "ferdie", "one", "two"])] pub eve: bool, /// Shortcut for `--name Ferdie --validator` with session keys for `Ferdie` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "one", "two"])] + #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "one", "two"])] pub ferdie: bool, /// Shortcut for `--name One --validator` with session keys for `One` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "two"])] + #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "two"])] pub one: bool, /// Shortcut for `--name Two --validator` with session keys for `Two` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "one"])] + #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "one"])] pub two: bool, /// Enable authoring even when offline. - #[structopt(long = "force-authoring")] + #[arg(long)] pub force_authoring: bool, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub keystore_params: KeystoreParams, /// The size of the instances cache for each runtime. /// /// The default value is 8 and the values higher than 256 are ignored. - #[structopt(long)] + #[arg(long)] pub max_runtime_instances: Option, + /// Maximum number of different runtimes that can be cached. + #[arg(long, default_value_t = 2)] + pub runtime_cache_size: u8, + /// Run a temporary node. /// /// A temporary directory will be created to store the configuration and will be deleted @@ -241,7 +260,9 @@ pub struct RunCmd { /// /// Note: the directory is random per process execution. This directory is used as base path /// which includes: database, node key and keystore. - #[structopt(long, conflicts_with = "base-path")] + /// + /// When `--dev` is given and no explicit `--base-path`, this option is implied. + #[arg(long, conflicts_with = "base_path")] pub tmp: bool, } @@ -312,7 +333,7 @@ impl CliConfiguration for RunCmd { fn dev_key_seed(&self, is_dev: bool) -> Result> { Ok(self.get_keyring().map(|a| format!("//{}", a)).or_else(|| { - if is_dev && !self.light { + if is_dev { Some("//Alice".into()) } else { None @@ -338,16 +359,9 @@ impl CliConfiguration for RunCmd { fn role(&self, is_dev: bool) -> Result { let keyring = self.get_keyring(); - let is_light = self.light; - let is_authority = (self.validator || is_dev || keyring.is_some()) && !is_light; + let is_authority = self.validator || is_dev || keyring.is_some(); - Ok(if is_light { - sc_service::Role::Light - } else if is_authority { - sc_service::Role::Authority - } else { - sc_service::Role::Full - }) + Ok(if is_authority { sc_service::Role::Authority } else { sc_service::Role::Full }) } fn force_authoring(&self) -> Result { @@ -355,17 +369,24 @@ impl CliConfiguration for RunCmd { Ok(self.shared_params.dev || self.force_authoring) } - fn prometheus_config(&self, default_listen_port: u16) -> Result> { + fn prometheus_config( + &self, + default_listen_port: u16, + chain_spec: &Box, + ) -> Result> { Ok(if self.no_prometheus { None } else { let interface = if self.prometheus_external { Ipv4Addr::UNSPECIFIED } else { Ipv4Addr::LOCALHOST }; - Some(PrometheusConfig::new_with_default_registry(SocketAddr::new( - interface.into(), - self.prometheus_port.unwrap_or(default_listen_port), - ))) + Some(PrometheusConfig::new_with_default_registry( + SocketAddr::new( + interface.into(), + self.prometheus_port.unwrap_or(default_listen_port), + ), + chain_spec.id().into(), + )) }) } @@ -432,19 +453,44 @@ impl CliConfiguration for RunCmd { Ok(self.rpc_max_payload) } - fn transaction_pool(&self) -> Result { - Ok(self.pool_config.transaction_pool()) + fn rpc_max_request_size(&self) -> Result> { + Ok(self.rpc_max_request_size) + } + + fn rpc_max_response_size(&self) -> Result> { + Ok(self.rpc_max_response_size) + } + + fn rpc_max_subscriptions_per_connection(&self) -> Result> { + Ok(self.rpc_max_subscriptions_per_connection) + } + + fn ws_max_out_buffer_capacity(&self) -> Result> { + Ok(self.ws_max_out_buffer_capacity) + } + + fn transaction_pool(&self, is_dev: bool) -> Result { + Ok(self.pool_config.transaction_pool(is_dev)) } fn max_runtime_instances(&self) -> Result> { Ok(self.max_runtime_instances.map(|x| x.min(256))) } + fn runtime_cache_size(&self) -> Result { + Ok(self.runtime_cache_size) + } + fn base_path(&self) -> Result> { Ok(if self.tmp { Some(BasePath::new_temp_dir()?) } else { - self.shared_params().base_path() + match self.shared_params().base_path()? { + Some(r) => Some(r), + // If `dev` is enabled, we use the temp base path. + None if self.shared_params().is_dev() => Some(BasePath::new_temp_dir()?), + None => None, + } }) } } @@ -510,7 +556,7 @@ impl std::error::Error for TelemetryParsingError {} impl std::fmt::Display for TelemetryParsingError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &*self { + match self { TelemetryParsingError::MissingVerbosity => write!(f, "Verbosity level missing"), TelemetryParsingError::VerbosityParsingError(e) => write!(f, "{}", e), } @@ -532,8 +578,7 @@ fn parse_telemetry_endpoints(s: &str) -> std::result::Result<(String, u8), Telem /// CORS setting /// -/// The type is introduced to overcome `Option>` -/// handling of `structopt`. +/// The type is introduced to overcome `Option>` handling of `clap`. #[derive(Clone, Debug)] pub enum Cors { /// All hosts allowed. @@ -552,7 +597,7 @@ impl From for Option> { } /// Parse cors origins. -fn parse_cors(s: &str) -> std::result::Result> { +fn parse_cors(s: &str) -> Result { let mut is_all = false; let mut origins = Vec::new(); for part in s.split(',') { @@ -565,7 +610,11 @@ fn parse_cors(s: &str) -> std::result::Result> } } - Ok(if is_all { Cors::All } else { Cors::List(origins) }) + if is_all { + Ok(Cors::All) + } else { + Ok(Cors::List(origins)) + } } #[cfg(test)] diff --git a/client/cli/src/commands/sign.rs b/client/cli/src/commands/sign.rs index 20aacd9bf0020..2c3ff3a1575fd 100644 --- a/client/cli/src/commands/sign.rs +++ b/client/cli/src/commands/sign.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,34 +18,34 @@ //! Implementation of the `sign` subcommand use crate::{error, utils, with_crypto_scheme, CryptoSchemeFlag, KeystoreParams}; +use clap::Parser; use sp_core::crypto::SecretString; -use structopt::StructOpt; /// The `sign` command -#[derive(Debug, StructOpt, Clone)] -#[structopt(name = "sign", about = "Sign a message, with a given (secret) key")] +#[derive(Debug, Clone, Parser)] +#[command(name = "sign", about = "Sign a message, with a given (secret) key")] pub struct SignCmd { /// The secret key URI. /// If the value is a file, the file content is used as URI. /// If not given, you will be prompted for the URI. - #[structopt(long)] + #[arg(long)] suri: Option, /// Message to sign, if not provided you will be prompted to /// pass the message via STDIN - #[structopt(long)] + #[arg(long)] message: Option, /// The message on STDIN is hex-encoded data - #[structopt(long)] + #[arg(long)] hex: bool, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub keystore_params: KeystoreParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub crypto_scheme: CryptoSchemeFlag, } @@ -70,19 +70,18 @@ fn sign( message: Vec, ) -> error::Result { let pair = utils::pair_from_suri::

(suri, password)?; - Ok(format!("{}", hex::encode(pair.sign(&message)))) + Ok(array_bytes::bytes2hex("", pair.sign(&message).as_ref())) } #[cfg(test)] mod test { - use super::SignCmd; - use structopt::StructOpt; + use super::*; #[test] fn sign() { let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5"; - let sign = SignCmd::from_iter(&[ + let sign = SignCmd::parse_from(&[ "sign", "--suri", seed, diff --git a/client/cli/src/commands/utils.rs b/client/cli/src/commands/utils.rs index 864d7e920f81a..1ce2b23221691 100644 --- a/client/cli/src/commands/utils.rs +++ b/client/cli/src/commands/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,12 +23,15 @@ use crate::{ }; use serde_json::json; use sp_core::{ - crypto::{ExposeSecret, SecretString, Ss58AddressFormat, Ss58Codec, Zeroize}, + crypto::{ + unwrap_or_default_ss58_version, ExposeSecret, SecretString, Ss58AddressFormat, Ss58Codec, + Zeroize, + }, hexdisplay::HexDisplay, Pair, }; use sp_runtime::{traits::IdentifyAccount, MultiSigner}; -use std::{convert::TryFrom, io::Read, path::PathBuf}; +use std::{io::Read, path::PathBuf}; /// Public key type for Runtime pub type PublicFor

=

::Public; @@ -45,7 +48,7 @@ pub fn read_uri(uri: Option<&String>) -> error::Result { uri.into() } } else { - rpassword::read_password_from_tty(Some("URI: "))? + rpassword::prompt_password("URI: ")? }; Ok(uri) @@ -70,14 +73,16 @@ pub fn print_from_uri( Pair::Public: Into, { let password = password.as_ref().map(|s| s.expose_secret().as_str()); - if let Ok((pair, seed)) = Pair::from_phrase(uri, password.clone()) { + let network_id = String::from(unwrap_or_default_ss58_version(network_override)); + if let Ok((pair, seed)) = Pair::from_phrase(uri, password) { let public_key = pair.public(); - let network_override = network_override.unwrap_or_default(); + let network_override = unwrap_or_default_ss58_version(network_override); match output { OutputType::Json => { let json = json!({ "secretPhrase": uri, + "networkId": network_id, "secretSeed": format_seed::(seed), "publicKey": format_public_key::(public_key.clone()), "ss58PublicKey": public_key.to_ss58check_with_version(network_override), @@ -92,12 +97,14 @@ pub fn print_from_uri( OutputType::Text => { println!( "Secret phrase: {}\n \ + Network ID: {}\n \ Secret seed: {}\n \ Public key (hex): {}\n \ Account ID: {}\n \ Public key (SS58): {}\n \ SS58 Address: {}", uri, + network_id, format_seed::(seed), format_public_key::(public_key.clone()), format_account_id::(public_key.clone()), @@ -106,14 +113,15 @@ pub fn print_from_uri( ); }, } - } else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password.clone()) { + } else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) { let public_key = pair.public(); - let network_override = network_override.unwrap_or_default(); + let network_override = unwrap_or_default_ss58_version(network_override); match output { OutputType::Json => { let json = json!({ "secretKeyUri": uri, + "networkId": network_id, "secretSeed": if let Some(seed) = seed { format_seed::(seed) } else { "n/a".into() }, "publicKey": format_public_key::(public_key.clone()), "ss58PublicKey": public_key.to_ss58check_with_version(network_override), @@ -128,12 +136,14 @@ pub fn print_from_uri( OutputType::Text => { println!( "Secret Key URI `{}` is account:\n \ + Network ID: {} \n \ Secret seed: {}\n \ Public key (hex): {}\n \ Account ID: {}\n \ Public key (SS58): {}\n \ SS58 Address: {}", uri, + network_id, if let Some(seed) = seed { format_seed::(seed) } else { "n/a".into() }, format_public_key::(public_key.clone()), format_account_id::(public_key.clone()), @@ -164,7 +174,7 @@ pub fn print_from_uri( OutputType::Text => { println!( "Public Key URI `{}` is account:\n \ - Network ID/version: {}\n \ + Network ID/Version: {}\n \ Public key (hex): {}\n \ Account ID: {}\n \ Public key (SS58): {}\n \ @@ -193,12 +203,12 @@ where Pair: sp_core::Pair, Pair::Public: Into, { - let public = decode_hex(public_str)?; + let public = array_bytes::hex2bytes(public_str)?; let public_key = Pair::Public::try_from(&public) .map_err(|_| "Failed to construct public key from given hex")?; - let network_override = network_override.unwrap_or_default(); + let network_override = unwrap_or_default_ss58_version(network_override); match output { OutputType::Json => { @@ -214,7 +224,7 @@ where }, OutputType::Text => { println!( - "Network ID/version: {}\n \ + "Network ID/Version: {}\n \ Public key (hex): {}\n \ Account ID: {}\n \ Public key (SS58): {}\n \ @@ -263,26 +273,17 @@ where format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref())) } -/// helper method for decoding hex -pub fn decode_hex>(message: T) -> Result, Error> { - let mut message = message.as_ref(); - if message[..2] == [b'0', b'x'] { - message = &message[2..] - } - Ok(hex::decode(message)?) -} - /// checks if message is Some, otherwise reads message from stdin and optionally decodes hex pub fn read_message(msg: Option<&String>, should_decode: bool) -> Result, Error> { let mut message = vec![]; match msg { Some(m) => { - message = decode_hex(m)?; + message = array_bytes::hex2bytes(m.as_str())?; }, None => { std::io::stdin().lock().read_to_end(&mut message)?; if should_decode { - message = decode_hex(&message)?; + message = array_bytes::hex2bytes(array_bytes::hex_bytes2hex_str(&message)?)?; } }, } diff --git a/client/cli/src/commands/vanity.rs b/client/cli/src/commands/vanity.rs index daeb81e86a1a1..ae0007ac7964d 100644 --- a/client/cli/src/commands/vanity.rs +++ b/client/cli/src/commands/vanity.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,30 +21,30 @@ use crate::{ error, utils, with_crypto_scheme, CryptoSchemeFlag, NetworkSchemeFlag, OutputTypeFlag, }; +use clap::Parser; use rand::{rngs::OsRng, RngCore}; -use sp_core::crypto::{Ss58AddressFormat, Ss58Codec}; +use sp_core::crypto::{unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec}; use sp_runtime::traits::IdentifyAccount; -use structopt::StructOpt; use utils::print_from_uri; /// The `vanity` command -#[derive(Debug, StructOpt, Clone)] -#[structopt(name = "vanity", about = "Generate a seed that provides a vanity address")] +#[derive(Debug, Clone, Parser)] +#[command(name = "vanity", about = "Generate a seed that provides a vanity address")] pub struct VanityCmd { /// Desired pattern - #[structopt(long, parse(try_from_str = assert_non_empty_string))] + #[arg(long, value_parser = assert_non_empty_string)] pattern: String, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] network_scheme: NetworkSchemeFlag, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] output_scheme: OutputTypeFlag, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] crypto_scheme: CryptoSchemeFlag, } @@ -53,7 +53,10 @@ impl VanityCmd { pub fn run(&self) -> error::Result<()> { let formated_seed = with_crypto_scheme!( self.crypto_scheme.scheme, - generate_key(&self.pattern, self.network_scheme.network.clone().unwrap_or_default()), + generate_key( + &self.pattern, + unwrap_or_default_ss58_version(self.network_scheme.network) + ), )?; with_crypto_scheme!( @@ -61,8 +64,8 @@ impl VanityCmd { print_from_uri( &formated_seed, None, - self.network_scheme.network.clone(), - self.output_scheme.output_type.clone(), + self.network_scheme.network, + self.output_scheme.output_type, ), ); Ok(()) @@ -95,7 +98,7 @@ where let p = Pair::from_seed(&seed); let ss58 = p.public().into_account().to_ss58check_with_version(network_override); - let score = calculate_score(&desired, &ss58); + let score = calculate_score(desired, &ss58); if score > best || desired.len() < 2 { best = score; if best >= top { @@ -114,20 +117,20 @@ where fn good_waypoint(done: u64) -> u64 { match done { 0..=1_000_000 => 100_000, - 0..=10_000_000 => 1_000_000, - 0..=100_000_000 => 10_000_000, - _ => 100_000_000, + 1_000_001..=10_000_000 => 1_000_000, + 10_000_001..=100_000_000 => 10_000_000, + 100_000_001.. => 100_000_000, } } fn next_seed(seed: &mut [u8]) { - for i in 0..seed.len() { - match seed[i] { + for s in seed { + match s { 255 => { - seed[i] = 0; + *s = 0; }, _ => { - seed[i] += 1; + *s += 1; break }, } @@ -159,21 +162,23 @@ fn assert_non_empty_string(pattern: &str) -> Result { #[cfg(test)] mod tests { use super::*; - use sp_core::{crypto::Ss58Codec, sr25519, Pair}; - use structopt::StructOpt; + use sp_core::{ + crypto::{default_ss58_version, Ss58AddressFormatRegistry, Ss58Codec}, + sr25519, Pair, + }; #[cfg(feature = "bench")] use test::Bencher; #[test] fn vanity() { - let vanity = VanityCmd::from_iter(&["vanity", "--pattern", "j"]); + let vanity = VanityCmd::parse_from(&["vanity", "--pattern", "j"]); assert!(vanity.run().is_ok()); } #[test] fn test_generation_with_single_char() { - let seed = generate_key::("ab", Default::default()).unwrap(); - assert!(sr25519::Pair::from_seed_slice(&hex::decode(&seed[2..]).unwrap()) + let seed = generate_key::("ab", default_ss58_version()).unwrap(); + assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed)) .unwrap() .public() .to_ss58check() @@ -182,11 +187,13 @@ mod tests { #[test] fn generate_key_respects_network_override() { - let seed = generate_key::("ab", Ss58AddressFormat::PolkadotAccount).unwrap(); - assert!(sr25519::Pair::from_seed_slice(&hex::decode(&seed[2..]).unwrap()) + let seed = + generate_key::("ab", Ss58AddressFormatRegistry::PolkadotAccount.into()) + .unwrap(); + assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed)) .unwrap() .public() - .to_ss58check_with_version(Ss58AddressFormat::PolkadotAccount) + .to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into()) .contains("ab")); } diff --git a/client/cli/src/commands/verify.rs b/client/cli/src/commands/verify.rs index 760793374242e..82554fbf268fa 100644 --- a/client/cli/src/commands/verify.rs +++ b/client/cli/src/commands/verify.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,12 +19,12 @@ //! implementation of the `verify` subcommand use crate::{error, utils, with_crypto_scheme, CryptoSchemeFlag}; -use sp_core::{crypto::Ss58Codec, Public}; -use structopt::StructOpt; +use clap::Parser; +use sp_core::crypto::{ByteArray, Ss58Codec}; /// The `verify` command -#[derive(Debug, StructOpt, Clone)] -#[structopt( +#[derive(Debug, Clone, Parser)] +#[command( name = "verify", about = "Verify a signature for a message, provided on STDIN, with a given (public or secret) key" )] @@ -39,15 +39,15 @@ pub struct VerifyCmd { /// Message to verify, if not provided you will be prompted to /// pass the message via STDIN - #[structopt(long)] + #[arg(long)] message: Option, /// The message on STDIN is hex-encoded data - #[structopt(long)] + #[arg(long)] hex: bool, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub crypto_scheme: CryptoSchemeFlag, } @@ -55,9 +55,9 @@ impl VerifyCmd { /// Run the command pub fn run(&self) -> error::Result<()> { let message = utils::read_message(self.message.as_ref(), self.hex)?; - let sig_data = utils::decode_hex(&self.sig)?; + let sig_data = array_bytes::hex2bytes(&self.sig)?; let uri = utils::read_uri(self.uri.as_ref())?; - let uri = if uri.starts_with("0x") { &uri[2..] } else { &uri }; + let uri = if let Some(uri) = uri.strip_prefix("0x") { uri } else { &uri }; with_crypto_scheme!(self.crypto_scheme.scheme, verify(sig_data, message, uri)) } @@ -66,19 +66,14 @@ impl VerifyCmd { fn verify(sig_data: Vec, message: Vec, uri: &str) -> error::Result<()> where Pair: sp_core::Pair, - Pair::Signature: Default + AsMut<[u8]>, + Pair::Signature: for<'a> TryFrom<&'a [u8]>, { - let mut signature = Pair::Signature::default(); - if sig_data.len() != signature.as_ref().len() { - return Err(error::Error::SignatureInvalidLength { - read: sig_data.len(), - expected: signature.as_ref().len(), - }) - } - signature.as_mut().copy_from_slice(&sig_data); + let signature = + Pair::Signature::try_from(&sig_data).map_err(|_| error::Error::SignatureFormatInvalid)?; - let pubkey = if let Ok(pubkey_vec) = hex::decode(uri) { + let pubkey = if let Ok(pubkey_vec) = array_bytes::hex2bytes(uri) { Pair::Public::from_slice(pubkey_vec.as_slice()) + .map_err(|_| error::Error::KeyFormatInvalid)? } else { Pair::Public::from_string(uri)? }; diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index 59fc6bd438a1c..77689708a231f 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -31,7 +31,7 @@ use sc_service::{ NodeKeyConfig, OffchainWorkerConfig, PrometheusConfig, PruningMode, Role, RpcMethods, TelemetryEndpoints, TransactionPoolOptions, WasmExecutionMethod, }, - ChainSpec, KeepBlocks, TracingReceiver, TransactionStorageMode, + BlocksPruning, ChainSpec, TracingReceiver, }; use sc_tracing::logging::LoggerBuilder; use std::{net::SocketAddr, path::PathBuf}; @@ -40,7 +40,7 @@ use std::{net::SocketAddr, path::PathBuf}; pub(crate) const NODE_NAME_MAX_LENGTH: usize = 64; /// Default sub directory to store network config. -pub(crate) const DEFAULT_NETWORK_CONFIG_PATH: &'static str = "network"; +pub(crate) const DEFAULT_NETWORK_CONFIG_PATH: &str = "network"; /// The recommended open file descriptor limit to be configured for the process. const RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT: u64 = 10_000; @@ -125,7 +125,7 @@ pub trait CliConfiguration: Sized { /// /// By default this is retrieved from `SharedParams`. fn base_path(&self) -> Result> { - Ok(self.shared_params().base_path()) + self.shared_params().base_path() } /// Returns `true` if the node is for development or not @@ -145,7 +145,7 @@ pub trait CliConfiguration: Sized { /// Get the transaction pool options /// /// By default this is `TransactionPoolOptions::default()`. - fn transaction_pool(&self) -> Result { + fn transaction_pool(&self, _is_dev: bool) -> Result { Ok(Default::default()) } @@ -198,14 +198,6 @@ pub trait CliConfiguration: Sized { Ok(self.database_params().map(|x| x.database_cache_size()).unwrap_or_default()) } - /// Get the database transaction storage scheme. - fn database_transaction_storage(&self) -> Result { - Ok(self - .database_params() - .map(|x| x.transaction_storage()) - .unwrap_or(TransactionStorageMode::BlockBody)) - } - /// Get the database backend variant. /// /// By default this is retrieved from `DatabaseParams` if it is available. Otherwise its `None`. @@ -219,53 +211,51 @@ pub trait CliConfiguration: Sized { base_path: &PathBuf, cache_size: usize, database: Database, - role: &Role, ) -> Result { - let role_dir = match role { - Role::Light => "light", - Role::Full | Role::Authority => "full", - }; + let role_dir = "full"; let rocksdb_path = base_path.join("db").join(role_dir); let paritydb_path = base_path.join("paritydb").join(role_dir); Ok(match database { + #[cfg(feature = "rocksdb")] Database::RocksDb => DatabaseSource::RocksDb { path: rocksdb_path, cache_size }, - Database::ParityDb => DatabaseSource::ParityDb { path: rocksdb_path }, + Database::ParityDb => DatabaseSource::ParityDb { path: paritydb_path }, + Database::ParityDbDeprecated => { + eprintln!( + "WARNING: \"paritydb-experimental\" database setting is deprecated and will be removed in future releases. \ + Please update your setup to use the new value: \"paritydb\"." + ); + DatabaseSource::ParityDb { path: paritydb_path } + }, Database::Auto => DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size }, }) } - /// Get the state cache size. + /// Get the trie cache maximum size. /// /// By default this is retrieved from `ImportParams` if it is available. Otherwise its `0`. - fn state_cache_size(&self) -> Result { - Ok(self.import_params().map(|x| x.state_cache_size()).unwrap_or_default()) - } - - /// Get the state cache child ratio (if any). - /// - /// By default this is `None`. - fn state_cache_child_ratio(&self) -> Result> { - Ok(Default::default()) + /// If `None` is returned the trie cache is disabled. + fn trie_cache_maximum_size(&self) -> Result> { + Ok(self.import_params().map(|x| x.trie_cache_maximum_size()).unwrap_or_default()) } /// Get the state pruning mode. /// /// By default this is retrieved from `PruningMode` if it is available. Otherwise its /// `PruningMode::default()`. - fn state_pruning(&self, unsafe_pruning: bool, role: &Role) -> Result { + fn state_pruning(&self) -> Result> { self.pruning_params() - .map(|x| x.state_pruning(unsafe_pruning, role)) + .map(|x| x.state_pruning()) .unwrap_or_else(|| Ok(Default::default())) } /// Get the block pruning mode. /// /// By default this is retrieved from `block_pruning` if it is available. Otherwise its - /// `KeepBlocks::All`. - fn keep_blocks(&self) -> Result { + /// `BlocksPruning::KeepFinalized`. + fn blocks_pruning(&self) -> Result { self.pruning_params() - .map(|x| x.keep_blocks()) - .unwrap_or_else(|| Ok(KeepBlocks::All)) + .map(|x| x.blocks_pruning()) + .unwrap_or_else(|| Ok(BlocksPruning::KeepFinalized)) } /// Get the chain ID (string). @@ -360,10 +350,34 @@ pub trait CliConfiguration: Sized { Ok(None) } + /// Get maximum RPC request payload size. + fn rpc_max_request_size(&self) -> Result> { + Ok(None) + } + + /// Get maximum RPC response payload size. + fn rpc_max_response_size(&self) -> Result> { + Ok(None) + } + + /// Get maximum number of subscriptions per connection. + fn rpc_max_subscriptions_per_connection(&self) -> Result> { + Ok(None) + } + + /// Get maximum WS output buffer capacity. + fn ws_max_out_buffer_capacity(&self) -> Result> { + Ok(None) + } + /// Get the prometheus configuration (`None` if disabled) /// /// By default this is `None`. - fn prometheus_config(&self, _default_listen_port: u16) -> Result> { + fn prometheus_config( + &self, + _default_listen_port: u16, + _chain_spec: &Box, + ) -> Result> { Ok(None) } @@ -447,6 +461,13 @@ pub trait CliConfiguration: Sized { Ok(Default::default()) } + /// Get maximum different runtimes in cache + /// + /// By default this is `2`. + fn runtime_cache_size(&self) -> Result { + Ok(2) + } + /// Activate or not the automatic announcing of blocks after import /// /// By default this is `false`. @@ -469,22 +490,30 @@ pub trait CliConfiguration: Sized { let config_dir = base_path.config_dir(chain_spec.id()); let net_config_dir = config_dir.join(DEFAULT_NETWORK_CONFIG_PATH); let client_id = C::client_id(); - let database_cache_size = self.database_cache_size()?.unwrap_or(128); - let database = self.database()?.unwrap_or(Database::RocksDb); + let database_cache_size = self.database_cache_size()?.unwrap_or(1024); + let database = self.database()?.unwrap_or( + #[cfg(feature = "rocksdb")] + { + Database::RocksDb + }, + #[cfg(not(feature = "rocksdb"))] + { + Database::ParityDb + }, + ); let node_key = self.node_key(&net_config_dir)?; let role = self.role(is_dev)?; let max_runtime_instances = self.max_runtime_instances()?.unwrap_or(8); let is_validator = role.is_authority(); let (keystore_remote, keystore) = self.keystore_config(&config_dir)?; let telemetry_endpoints = self.telemetry_endpoints(&chain_spec)?; - - let unsafe_pruning = self.import_params().map(|p| p.unsafe_pruning).unwrap_or(false); + let runtime_cache_size = self.runtime_cache_size()?; Ok(Configuration { impl_name: C::impl_name(), impl_version: C::impl_version(), tokio_handle, - transaction_pool: self.transaction_pool()?, + transaction_pool: self.transaction_pool(is_dev)?, network: self.network_config( &chain_spec, is_dev, @@ -497,12 +526,10 @@ pub trait CliConfiguration: Sized { )?, keystore_remote, keystore, - database: self.database_config(&config_dir, database_cache_size, database, &role)?, - state_cache_size: self.state_cache_size()?, - state_cache_child_ratio: self.state_cache_child_ratio()?, - state_pruning: self.state_pruning(unsafe_pruning, &role)?, - keep_blocks: self.keep_blocks()?, - transaction_storage: self.database_transaction_storage()?, + database: self.database_config(&config_dir, database_cache_size, database)?, + trie_cache_maximum_size: self.trie_cache_maximum_size()?, + state_pruning: self.state_pruning()?, + blocks_pruning: self.blocks_pruning()?, wasm_method: self.wasm_method()?, wasm_runtime_overrides: self.wasm_runtime_overrides(), execution_strategies: self.execution_strategies(is_dev, is_validator)?, @@ -513,7 +540,13 @@ pub trait CliConfiguration: Sized { rpc_ws_max_connections: self.rpc_ws_max_connections()?, rpc_cors: self.rpc_cors(is_dev)?, rpc_max_payload: self.rpc_max_payload()?, - prometheus_config: self.prometheus_config(DCV::prometheus_listen_port())?, + rpc_max_request_size: self.rpc_max_request_size()?, + rpc_max_response_size: self.rpc_max_response_size()?, + rpc_id_provider: None, + rpc_max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?, + ws_max_out_buffer_capacity: self.ws_max_out_buffer_capacity()?, + prometheus_config: self + .prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?, telemetry_endpoints, default_heap_pages: self.default_heap_pages()?, offchain_worker: self.offchain_worker(&role)?, @@ -522,13 +555,13 @@ pub trait CliConfiguration: Sized { dev_key_seed: self.dev_key_seed(is_dev)?, tracing_targets: self.tracing_targets()?, tracing_receiver: self.tracing_receiver()?, - disable_log_reloading: self.is_log_filter_reloading_disabled()?, chain_spec, max_runtime_instances, announce_block: self.announce_block()?, role, base_path: Some(base_path), informant_output_format: Default::default(), + runtime_cache_size, }) } @@ -542,9 +575,14 @@ pub trait CliConfiguration: Sized { Ok(self.shared_params().log_filters().join(",")) } - /// Is log reloading disabled (enabled by default) - fn is_log_filter_reloading_disabled(&self) -> Result { - Ok(self.shared_params().is_log_filter_reloading_disabled()) + /// Should the detailed log output be enabled. + fn detailed_log_output(&self) -> Result { + Ok(self.shared_params().detailed_log_output()) + } + + /// Is log reloading enabled? + fn enable_log_reloading(&self) -> Result { + Ok(self.shared_params().enable_log_reloading()) } /// Should the log color output be disabled? @@ -557,13 +595,45 @@ pub trait CliConfiguration: Sized { /// This method: /// /// 1. Sets the panic handler + /// 2. Optionally customize logger/profiling /// 2. Initializes the logger /// 3. Raises the FD limit - fn init(&self) -> Result<()> { - sp_panic_handler::set(&C::support_url(), &C::impl_version()); + /// + /// The `logger_hook` closure is executed before the logger is constructed + /// and initialized. It is useful for setting up a custom profiler. + /// + /// Example: + /// ``` + /// use sc_tracing::{SpanDatum, TraceEvent}; + /// struct TestProfiler; + /// + /// impl sc_tracing::TraceHandler for TestProfiler { + /// fn handle_span(&self, sd: &SpanDatum) {} + /// fn handle_event(&self, _event: &TraceEvent) {} + /// }; + /// + /// fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () { + /// |logger_builder, config| { + /// logger_builder.with_custom_profiling(Box::new(TestProfiler{})); + /// } + /// } + /// ``` + fn init( + &self, + support_url: &String, + impl_version: &String, + logger_hook: F, + config: &Configuration, + ) -> Result<()> + where + F: FnOnce(&mut LoggerBuilder, &Configuration), + { + sp_panic_handler::set(support_url, impl_version); let mut logger = LoggerBuilder::new(self.log_filters()?); - logger.with_log_reloading(!self.is_log_filter_reloading_disabled()?); + logger + .with_log_reloading(self.enable_log_reloading()?) + .with_detailed_output(self.detailed_log_output()?); if let Some(tracing_targets) = self.tracing_targets()? { let tracing_receiver = self.tracing_receiver()?; @@ -574,6 +644,9 @@ pub trait CliConfiguration: Sized { logger.with_colors(false); } + // Call hook for custom profiling setup. + logger_hook(&mut logger, config); + logger.init()?; if let Some(new_limit) = fdlimit::raise_fd_limit() { diff --git a/client/cli/src/error.rs b/client/cli/src/error.rs index c5784b2018172..a0f843e73bf53 100644 --- a/client/cli/src/error.rs +++ b/client/cli/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -31,7 +31,7 @@ pub enum Error { Io(#[from] std::io::Error), #[error(transparent)] - Cli(#[from] structopt::clap::Error), + Cli(#[from] clap::Error), #[error(transparent)] Service(#[from] sc_service::Error), @@ -51,13 +51,11 @@ pub enum Error { #[error("Invalid URI; expecting either a secret URI or a public URI.")] InvalidUri(crypto::PublicError), - #[error("Signature has an invalid length. Read {read} bytes, expected {expected} bytes")] - SignatureInvalidLength { - /// Amount of signature bytes read. - read: usize, - /// Expected number of signature bytes. - expected: usize, - }, + #[error("Signature is an invalid format.")] + SignatureFormatInvalid, + + #[error("Key is an invalid format.")] + KeyFormatInvalid, #[error("Unknown key type, must be a known 4-character sequence")] KeyTypeInvalid, @@ -71,8 +69,8 @@ pub enum Error { #[error("Key storage issue encountered")] KeyStorage(#[from] sc_keystore::Error), - #[error("Invalid hexadecimal string data")] - HexDataConversion(#[from] hex::FromHexError), + #[error("Invalid hexadecimal string data, {0:?}")] + HexDataConversion(array_bytes::Error), /// Application specific error chain sequence forwarder. #[error(transparent)] @@ -82,20 +80,26 @@ pub enum Error { GlobalLoggerError(#[from] sc_tracing::logging::Error), } -impl std::convert::From<&str> for Error { +impl From<&str> for Error { fn from(s: &str) -> Error { Error::Input(s.to_string()) } } -impl std::convert::From for Error { +impl From for Error { fn from(s: String) -> Error { Error::Input(s) } } -impl std::convert::From for Error { +impl From for Error { fn from(e: crypto::PublicError) -> Error { Error::InvalidUri(e) } } + +impl From for Error { + fn from(e: array_bytes::Error) -> Error { + Error::HexDataConversion(e) + } +} diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index bb1bff94145f7..b6d0d47e52c1f 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,6 +22,9 @@ #![warn(unused_extern_crates)] #![warn(unused_imports)] +use clap::{CommandFactory, FromArgMatches, Parser}; +use sc_service::Configuration; + pub mod arg_enums; mod commands; mod config; @@ -30,38 +33,32 @@ mod params; mod runner; pub use arg_enums::*; +pub use clap; pub use commands::*; pub use config::*; pub use error::*; pub use params::*; pub use runner::*; -use sc_service::Configuration; pub use sc_service::{ChainSpec, Role}; pub use sc_tracing::logging::LoggerBuilder; pub use sp_version::RuntimeVersion; -use std::io::Write; -pub use structopt; -use structopt::{ - clap::{self, AppSettings}, - StructOpt, -}; /// Substrate client CLI /// -/// This trait needs to be defined on the root structopt of the application. It will provide the -/// implementation name, version, executable name, description, author, support_url, copyright start -/// year and most importantly: how to load the chain spec. -/// -/// StructOpt must not be in scope to use from_args (or the similar methods). This trait provides -/// its own implementation that will fill the necessary field based on the trait's functions. +/// This trait needs to be implemented on the root CLI struct of the application. It will provide +/// the implementation `name`, `version`, `executable name`, `description`, `author`, `support_url`, +/// `copyright start year` and most importantly: how to load the chain spec. pub trait SubstrateCli: Sized { /// Implementation name. fn impl_name() -> String; /// Implementation version. /// - /// By default this will look like this: 2.0.0-b950f731c-x86_64-linux-gnu where the hash is the - /// short commit hash of the commit of in the Git repository. + /// By default this will look like this: + /// + /// `2.0.0-b950f731c` + /// + /// Where the hash is the short commit hash of the commit of in the Git repository. fn impl_version() -> String; /// Executable file name. @@ -92,119 +89,91 @@ pub trait SubstrateCli: Sized { fn load_spec(&self, id: &str) -> std::result::Result, String>; /// Helper function used to parse the command line arguments. This is the equivalent of - /// `structopt`'s `from_iter()` except that it takes a `VersionInfo` argument to provide the - /// name of the application, author, "about" and version. It will also set - /// `AppSettings::GlobalVersion`. + /// [`clap::Parser::parse()`]. /// - /// To allow running the node without subcommand, tt also sets a few more settings: - /// `AppSettings::ArgsNegateSubcommands` and `AppSettings::SubcommandsNegateReqs`. + /// To allow running the node without subcommand, it also sets a few more settings: + /// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`], + /// [`clap::Command::subcommand_negates_reqs`]. /// - /// Gets the struct from the command line arguments. Print the + /// Creates `Self` from the command line arguments. Print the /// error message and quit the program in case of failure. fn from_args() -> Self where - Self: StructOpt + Sized, + Self: Parser + Sized, { ::from_iter(&mut std::env::args_os()) } /// Helper function used to parse the command line arguments. This is the equivalent of - /// `structopt`'s `from_iter()` except that it takes a `VersionInfo` argument to provide the - /// name of the application, author, "about" and version. It will also set - /// `AppSettings::GlobalVersion`. + /// [`clap::Parser::parse_from`]. /// /// To allow running the node without subcommand, it also sets a few more settings: - /// `AppSettings::ArgsNegateSubcommands` and `AppSettings::SubcommandsNegateReqs`. + /// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`], + /// [`clap::Command::subcommand_negates_reqs`]. /// - /// Gets the struct from any iterator such as a `Vec` of your making. + /// Creates `Self` from any iterator over arguments. /// Print the error message and quit the program in case of failure. fn from_iter(iter: I) -> Self where - Self: StructOpt + Sized, + Self: Parser + Sized, I: IntoIterator, I::Item: Into + Clone, { - let app = ::clap(); + let app = ::command(); let mut full_version = Self::impl_version(); - full_version.push_str("\n"); + full_version.push('\n'); let name = Self::executable_name(); let author = Self::author(); let about = Self::description(); let app = app .name(name) - .author(author.as_str()) - .about(about.as_str()) - .version(full_version.as_str()) - .settings(&[ - AppSettings::GlobalVersion, - AppSettings::ArgsNegateSubcommands, - AppSettings::SubcommandsNegateReqs, - AppSettings::ColoredHelp, - ]); - - let matches = match app.get_matches_from_safe(iter) { - Ok(matches) => matches, - Err(mut e) => { - // To support pipes, we can not use `writeln!` as any error - // results in a "broken pipe" error. - // - // Instead we write directly to `stdout` and ignore any error - // as we exit afterwards anyway. - e.message.extend("\n".chars()); - - if e.use_stderr() { - let _ = std::io::stderr().write_all(e.message.as_bytes()); - std::process::exit(1); - } else { - let _ = std::io::stdout().write_all(e.message.as_bytes()); - std::process::exit(0); - } - }, - }; - - ::from_clap(&matches) + .author(author) + .about(about) + .version(full_version) + .propagate_version(true) + .args_conflicts_with_subcommands(true) + .subcommand_negates_reqs(true); + + let matches = app.try_get_matches_from(iter).unwrap_or_else(|e| e.exit()); + + ::from_arg_matches(&matches).unwrap_or_else(|e| e.exit()) } /// Helper function used to parse the command line arguments. This is the equivalent of - /// `structopt`'s `from_iter()` except that it takes a `VersionInfo` argument to provide the - /// name of the application, author, "about" and version. It will also set - /// `AppSettings::GlobalVersion`. + /// [`clap::Parser::try_parse_from`] /// /// To allow running the node without subcommand, it also sets a few more settings: - /// `AppSettings::ArgsNegateSubcommands` and `AppSettings::SubcommandsNegateReqs`. + /// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`], + /// [`clap::Command::subcommand_negates_reqs`]. /// - /// Gets the struct from any iterator such as a `Vec` of your making. + /// Creates `Self` from any iterator over arguments. /// Print the error message and quit the program in case of failure. /// /// **NOTE:** This method WILL NOT exit when `--help` or `--version` (or short versions) are /// used. It will return a [`clap::Error`], where the [`clap::Error::kind`] is a - /// [`clap::ErrorKind::HelpDisplayed`] or [`clap::ErrorKind::VersionDisplayed`] respectively. - /// You must call [`clap::Error::exit`] or perform a [`std::process::exit`]. - fn try_from_iter(iter: I) -> clap::Result + /// [`clap::error::ErrorKind::DisplayHelp`] or [`clap::error::ErrorKind::DisplayVersion`] + /// respectively. You must call [`clap::Error::exit`] or perform a [`std::process::exit`]. + fn try_from_iter(iter: I) -> clap::error::Result where - Self: StructOpt + Sized, + Self: Parser + Sized, I: IntoIterator, I::Item: Into + Clone, { - let app = ::clap(); + let app = ::command(); let mut full_version = Self::impl_version(); - full_version.push_str("\n"); + full_version.push('\n'); let name = Self::executable_name(); let author = Self::author(); let about = Self::description(); - let app = app - .name(name) - .author(author.as_str()) - .about(about.as_str()) - .version(full_version.as_str()); + let app = app.name(name).author(author).about(about).version(full_version); - let matches = app.get_matches_from_safe(iter)?; + let matches = app.try_get_matches_from(iter)?; - Ok(::from_clap(&matches)) + ::from_arg_matches(&matches) } /// Returns the client ID: `{impl_name}/v{impl_version}` @@ -223,11 +192,50 @@ pub trait SubstrateCli: Sized { /// Create a runner for the command provided in argument. This will create a Configuration and /// a tokio runtime - fn create_runner(&self, command: &T) -> error::Result> { - command.init::()?; - Runner::new(self, command) + fn create_runner, DVC: DefaultConfigurationValues>( + &self, + command: &T, + ) -> error::Result> { + let tokio_runtime = build_runtime()?; + let config = command.create_configuration(self, tokio_runtime.handle().clone())?; + + command.init(&Self::support_url(), &Self::impl_version(), |_, _| {}, &config)?; + Runner::new(config, tokio_runtime) } + /// Create a runner for the command provided in argument. The `logger_hook` can be used to setup + /// a custom profiler or update the logger configuration before it is initialized. + /// + /// Example: + /// ``` + /// use sc_tracing::{SpanDatum, TraceEvent}; + /// struct TestProfiler; + /// + /// impl sc_tracing::TraceHandler for TestProfiler { + /// fn handle_span(&self, sd: &SpanDatum) {} + /// fn handle_event(&self, _event: &TraceEvent) {} + /// }; + /// + /// fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () { + /// |logger_builder, config| { + /// logger_builder.with_custom_profiling(Box::new(TestProfiler{})); + /// } + /// } + /// ``` + fn create_runner_with_logger_hook( + &self, + command: &T, + logger_hook: F, + ) -> error::Result> + where + F: FnOnce(&mut LoggerBuilder, &Configuration), + { + let tokio_runtime = build_runtime()?; + let config = command.create_configuration(self, tokio_runtime.handle().clone())?; + + command.init(&Self::support_url(), &Self::impl_version(), logger_hook, &config)?; + Runner::new(config, tokio_runtime) + } /// Native runtime version. fn native_runtime_version(chain_spec: &Box) -> &'static RuntimeVersion; } diff --git a/client/cli/src/params/database_params.rs b/client/cli/src/params/database_params.rs index 4d6cf5f1d3674..fdd3622580a6d 100644 --- a/client/cli/src/params/database_params.rs +++ b/client/cli/src/params/database_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,34 +17,18 @@ // along with this program. If not, see . use crate::arg_enums::Database; -use sc_service::TransactionStorageMode; -use structopt::StructOpt; +use clap::Args; /// Parameters for block import. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, PartialEq, Args)] pub struct DatabaseParams { /// Select database backend to use. - #[structopt( - long, - alias = "db", - value_name = "DB", - case_insensitive = true, - possible_values = &Database::variants(), - )] + #[arg(long, alias = "db", value_name = "DB", ignore_case = true, value_enum)] pub database: Option, /// Limit the memory the database cache can use. - #[structopt(long = "db-cache", value_name = "MiB")] + #[arg(long = "db-cache", value_name = "MiB")] pub database_cache_size: Option, - - /// Enable storage chain mode - /// - /// This changes the storage format for blocks bodies. - /// If this is enabled, each transaction is stored separately in the - /// transaction database column and is only referenced by hash - /// in the block body column. - #[structopt(long)] - pub storage_chain: bool, } impl DatabaseParams { @@ -57,13 +41,4 @@ impl DatabaseParams { pub fn database_cache_size(&self) -> Option { self.database_cache_size } - - /// Transaction storage scheme. - pub fn transaction_storage(&self) -> TransactionStorageMode { - if self.storage_chain { - TransactionStorageMode::StorageChain - } else { - TransactionStorageMode::BlockBody - } - } } diff --git a/client/cli/src/params/import_params.rs b/client/cli/src/params/import_params.rs index 3c87e91c220f7..b7ccbf1c8ed55 100644 --- a/client/cli/src/params/import_params.rs +++ b/client/cli/src/params/import_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,75 +18,100 @@ use crate::{ arg_enums::{ - ExecutionStrategy, WasmExecutionMethod, DEFAULT_EXECUTION_BLOCK_CONSTRUCTION, - DEFAULT_EXECUTION_IMPORT_BLOCK, DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR, - DEFAULT_EXECUTION_OFFCHAIN_WORKER, DEFAULT_EXECUTION_OTHER, DEFAULT_EXECUTION_SYNCING, + ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy, + DEFAULT_EXECUTION_BLOCK_CONSTRUCTION, DEFAULT_EXECUTION_IMPORT_BLOCK, + DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR, DEFAULT_EXECUTION_OFFCHAIN_WORKER, + DEFAULT_EXECUTION_OTHER, DEFAULT_EXECUTION_SYNCING, + DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, }, params::{DatabaseParams, PruningParams}, }; +use clap::Args; use sc_client_api::execution_extensions::ExecutionStrategies; use std::path::PathBuf; -use structopt::StructOpt; - -#[cfg(feature = "wasmtime")] -const WASM_METHOD_DEFAULT: &str = "Compiled"; - -#[cfg(not(feature = "wasmtime"))] -const WASM_METHOD_DEFAULT: &str = "interpreted-i-know-what-i-do"; /// Parameters for block import. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Args)] pub struct ImportParams { #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub pruning_params: PruningParams, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub database_params: DatabaseParams, - /// Force start with unsafe pruning settings. - /// - /// When running as a validator it is highly recommended to disable state - /// pruning (i.e. 'archive') which is the default. The node will refuse to - /// start as a validator if pruning is enabled unless this option is set. - #[structopt(long = "unsafe-pruning")] - pub unsafe_pruning: bool, - /// Method for executing Wasm runtime code. - #[structopt( + #[arg( long = "wasm-execution", value_name = "METHOD", - possible_values = &WasmExecutionMethod::variants(), - case_insensitive = true, - default_value = WASM_METHOD_DEFAULT + value_enum, + ignore_case = true, + default_value_t = DEFAULT_WASM_EXECUTION_METHOD, )] pub wasm_method: WasmExecutionMethod, + /// The WASM instantiation method to use. + /// + /// Only has an effect when `wasm-execution` is set to `compiled`. + /// + /// The copy-on-write strategies are only supported on Linux. + /// If the copy-on-write variant of a strategy is unsupported + /// the executor will fall back to the non-CoW equivalent. + /// + /// The fastest (and the default) strategy available is `pooling-copy-on-write`. + /// + /// The `legacy-instance-reuse` strategy is deprecated and will + /// be removed in the future. It should only be used in case of + /// issues with the default instantiation strategy. + #[arg( + long, + value_name = "STRATEGY", + default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, + value_enum, + )] + pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, + /// Specify the path where local WASM runtimes are stored. /// /// These runtimes will override on-chain runtimes when the version matches. - #[structopt(long, value_name = "PATH", parse(from_os_str))] + #[arg(long, value_name = "PATH")] pub wasm_runtime_overrides: Option, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub execution_strategies: ExecutionStrategiesParams, /// Specify the state cache size. - #[structopt(long = "state-cache-size", value_name = "Bytes", default_value = "67108864")] - pub state_cache_size: usize, + /// + /// Providing `0` will disable the cache. + #[arg(long, value_name = "Bytes", default_value_t = 67108864)] + pub trie_cache_size: usize, + + /// DEPRECATED + /// + /// Switch to `--trie-cache-size`. + #[arg(long)] + state_cache_size: Option, } impl ImportParams { - /// Specify the state cache size. - pub fn state_cache_size(&self) -> usize { - self.state_cache_size + /// Specify the trie cache maximum size. + pub fn trie_cache_maximum_size(&self) -> Option { + if self.state_cache_size.is_some() { + eprintln!("`--state-cache-size` was deprecated. Please switch to `--trie-cache-size`."); + } + + if self.trie_cache_size == 0 { + None + } else { + Some(self.trie_cache_size) + } } /// Get the WASM execution method from the parameters pub fn wasm_method(&self) -> sc_service::config::WasmExecutionMethod { - self.wasm_method.into() + crate::execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy) } /// Enable overriding on-chain WASM with locally-stored WASM @@ -127,68 +152,43 @@ impl ImportParams { } /// Execution strategies parameters. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Args)] pub struct ExecutionStrategiesParams { /// The means of execution used when calling into the runtime for importing blocks as /// part of an initial sync. - #[structopt( - long = "execution-syncing", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - )] + #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] pub execution_syncing: Option, /// The means of execution used when calling into the runtime for general block import /// (including locally authored blocks). - #[structopt( - long = "execution-import-block", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - )] + #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] pub execution_import_block: Option, /// The means of execution used when calling into the runtime while constructing blocks. - #[structopt( - long = "execution-block-construction", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - )] + #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] pub execution_block_construction: Option, /// The means of execution used when calling into the runtime while using an off-chain worker. - #[structopt( - long = "execution-offchain-worker", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - )] + #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] pub execution_offchain_worker: Option, /// The means of execution used when calling into the runtime while not syncing, importing or /// constructing blocks. - #[structopt( - long = "execution-other", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - )] + #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] pub execution_other: Option, /// The execution strategy that should be used by all execution contexts. - #[structopt( - long = "execution", + #[arg( + long, value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, + value_enum, + ignore_case = true, conflicts_with_all = &[ - "execution-other", - "execution-offchain-worker", - "execution-block-construction", - "execution-import-block", - "execution-syncing", + "execution_other", + "execution_offchain_worker", + "execution_block_construction", + "execution_import_block", + "execution_syncing", ] )] pub execution: Option, diff --git a/client/cli/src/params/keystore_params.rs b/client/cli/src/params/keystore_params.rs index 951f61bd1bc5f..d6c3c35d82418 100644 --- a/client/cli/src/params/keystore_params.rs +++ b/client/cli/src/params/keystore_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,50 +17,46 @@ // along with this program. If not, see . use crate::{error, error::Result}; +use clap::Args; use sc_service::config::KeystoreConfig; use sp_core::crypto::SecretString; use std::{ fs, path::{Path, PathBuf}, }; -use structopt::StructOpt; /// default sub directory for the key store -const DEFAULT_KEYSTORE_CONFIG_PATH: &'static str = "keystore"; +const DEFAULT_KEYSTORE_CONFIG_PATH: &str = "keystore"; /// Parameters of the keystore -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Args)] pub struct KeystoreParams { /// Specify custom URIs to connect to for keystore-services - #[structopt(long = "keystore-uri")] + #[arg(long)] pub keystore_uri: Option, /// Specify custom keystore path. - #[structopt(long = "keystore-path", value_name = "PATH", parse(from_os_str))] + #[arg(long, value_name = "PATH")] pub keystore_path: Option, /// Use interactive shell for entering the password used by the keystore. - #[structopt( - long = "password-interactive", - conflicts_with_all = &[ "password", "password-filename" ] - )] + #[arg(long, conflicts_with_all = &["password", "password_filename"])] pub password_interactive: bool, /// Password used by the keystore. This allows appending an extra user-defined secret to the /// seed. - #[structopt( - long = "password", - parse(try_from_str = secret_string_from_str), - conflicts_with_all = &[ "password-interactive", "password-filename" ] + #[arg( + long, + value_parser = secret_string_from_str, + conflicts_with_all = &["password_interactive", "password_filename"] )] pub password: Option, /// File that contains the password used by the keystore. - #[structopt( - long = "password-filename", + #[arg( + long, value_name = "PATH", - parse(from_os_str), - conflicts_with_all = &[ "password-interactive", "password" ] + conflicts_with_all = &["password_interactive", "password"] )] pub password_filename: Option, } @@ -97,7 +93,7 @@ impl KeystoreParams { let (password_interactive, password) = (self.password_interactive, self.password.clone()); let pass = if password_interactive { - let password = rpassword::read_password_from_tty(Some("Key password: "))?; + let password = rpassword::prompt_password("Key password: ")?; Some(SecretString::new(password)) } else { password @@ -108,6 +104,5 @@ impl KeystoreParams { } fn input_keystore_password() -> Result { - rpassword::read_password_from_tty(Some("Keystore password: ")) - .map_err(|e| format!("{:?}", e).into()) + rpassword::prompt_password("Keystore password: ").map_err(|e| format!("{:?}", e).into()) } diff --git a/client/cli/src/params/mod.rs b/client/cli/src/params/mod.rs index dac832a1f897c..3197deb101bcc 100644 --- a/client/cli/src/params/mod.rs +++ b/client/cli/src/params/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -26,13 +26,13 @@ mod shared_params; mod transaction_pool_params; use crate::arg_enums::{CryptoScheme, OutputType}; -use sp_core::crypto::Ss58AddressFormat; +use clap::Args; +use sp_core::crypto::{Ss58AddressFormat, Ss58AddressFormatRegistry}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, NumberFor}, }; -use std::{convert::TryFrom, fmt::Debug, str::FromStr}; -use structopt::StructOpt; +use std::{fmt::Debug, str::FromStr}; pub use crate::params::{ database_params::*, import_params::*, keystore_params::*, network_params::*, @@ -40,6 +40,17 @@ pub use crate::params::{ transaction_pool_params::*, }; +/// Parse Ss58AddressFormat +pub fn parse_ss58_address_format(x: &str) -> Result { + match Ss58AddressFormatRegistry::try_from(x) { + Ok(format_registry) => Ok(format_registry.into()), + Err(_) => Err(format!( + "Unable to parse variant. Known variants: {:?}", + Ss58AddressFormat::all_names() + )), + } +} + /// Wrapper type of `String` that holds an unsigned integer of arbitrary size, formatted as a /// decimal. #[derive(Debug, Clone)] @@ -79,8 +90,8 @@ impl FromStr for BlockNumberOrHash { type Err = String; fn from_str(block_number: &str) -> Result { - if block_number.starts_with("0x") { - if let Some(pos) = &block_number[2..].chars().position(|c| !c.is_ascii_hexdigit()) { + if let Some(rest) = block_number.strip_prefix("0x") { + if let Some(pos) = rest.chars().position(|c| !c.is_ascii_hexdigit()) { Err(format!( "Expected block hash, found illegal hex character at position: {}", 2 + pos, @@ -115,44 +126,31 @@ impl BlockNumberOrHash { } /// Optional flag for specifying crypto algorithm -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Args)] pub struct CryptoSchemeFlag { /// cryptography scheme - #[structopt( - long, - value_name = "SCHEME", - possible_values = &CryptoScheme::variants(), - case_insensitive = true, - default_value = "Sr25519" - )] + #[arg(long, value_name = "SCHEME", value_enum, ignore_case = true, default_value_t = CryptoScheme::Sr25519)] pub scheme: CryptoScheme, } /// Optional flag for specifying output type -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Args)] pub struct OutputTypeFlag { /// output format - #[structopt( - long, - value_name = "FORMAT", - possible_values = &OutputType::variants(), - case_insensitive = true, - default_value = "Text" - )] + #[arg(long, value_name = "FORMAT", value_enum, ignore_case = true, default_value_t = OutputType::Text)] pub output_type: OutputType, } /// Optional flag for specifying network scheme -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Args)] pub struct NetworkSchemeFlag { /// network address format - #[structopt( + #[arg( + short = 'n', long, value_name = "NETWORK", - short = "n", - possible_values = &Ss58AddressFormat::all_names()[..], - parse(try_from_str = Ss58AddressFormat::try_from), - case_insensitive = true, + ignore_case = true, + value_parser = parse_ss58_address_format, )] pub network: Option, } diff --git a/client/cli/src/params/network_params.rs b/client/cli/src/params/network_params.rs index 6eaf068fdaecd..5580dea45bde6 100644 --- a/client/cli/src/params/network_params.rs +++ b/client/cli/src/params/network_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,28 +17,27 @@ // along with this program. If not, see . use crate::{arg_enums::SyncMode, params::node_key_params::NodeKeyParams}; +use clap::Args; use sc_network::{ - config::{ - NetworkConfiguration, NodeKeyConfig, NonReservedPeerMode, SetConfig, TransportConfig, - }, + config::{NetworkConfiguration, NodeKeyConfig}, multiaddr::Protocol, }; +use sc_network_common::config::{NonReservedPeerMode, SetConfig, TransportConfig}; use sc_service::{ config::{Multiaddr, MultiaddrWithPeerId}, ChainSpec, ChainType, }; use std::{borrow::Cow, path::PathBuf}; -use structopt::StructOpt; /// Parameters used to create the network configuration. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Args)] pub struct NetworkParams { /// Specify a list of bootnodes. - #[structopt(long = "bootnodes", value_name = "ADDR")] + #[arg(long, value_name = "ADDR", num_args = 1..)] pub bootnodes: Vec, /// Specify a list of reserved node addresses. - #[structopt(long = "reserved-nodes", value_name = "ADDR")] + #[arg(long, value_name = "ADDR", num_args = 1..)] pub reserved_nodes: Vec, /// Whether to only synchronize the chain with reserved nodes. @@ -49,12 +48,12 @@ pub struct NetworkParams { /// In particular, if you are a validator your node might still connect to other /// validator nodes and collator nodes regardless of whether they are defined as /// reserved nodes. - #[structopt(long = "reserved-only")] + #[arg(long)] pub reserved_only: bool, /// The public address that other nodes will use to connect to it. /// This can be used if there's a proxy in front of this node. - #[structopt(long, value_name = "PUBLIC_ADDR")] + #[arg(long, value_name = "PUBLIC_ADDR", num_args = 1..)] pub public_addr: Vec, /// Listen on this multiaddress. @@ -62,57 +61,61 @@ pub struct NetworkParams { /// By default: /// If `--validator` is passed: `/ip4/0.0.0.0/tcp/` and `/ip6/[::]/tcp/`. /// Otherwise: `/ip4/0.0.0.0/tcp//ws` and `/ip6/[::]/tcp//ws`. - #[structopt(long = "listen-addr", value_name = "LISTEN_ADDR")] + #[arg(long, value_name = "LISTEN_ADDR", num_args = 1..)] pub listen_addr: Vec, /// Specify p2p protocol TCP port. - #[structopt(long = "port", value_name = "PORT", conflicts_with_all = &[ "listen-addr" ])] + #[arg(long, value_name = "PORT", conflicts_with_all = &[ "listen_addr" ])] pub port: Option, /// Always forbid connecting to private IPv4 addresses (as specified in /// [RFC1918](https://tools.ietf.org/html/rfc1918)), unless the address was passed with /// `--reserved-nodes` or `--bootnodes`. Enabled by default for chains marked as "live" in /// their chain specifications. - #[structopt(long = "no-private-ipv4", conflicts_with_all = &["allow-private-ipv4"])] + #[arg(long, conflicts_with_all = &["allow_private_ipv4"])] pub no_private_ipv4: bool, /// Always accept connecting to private IPv4 addresses (as specified in /// [RFC1918](https://tools.ietf.org/html/rfc1918)). Enabled by default for chains marked as /// "local" in their chain specifications, or when `--dev` is passed. - #[structopt(long = "allow-private-ipv4", conflicts_with_all = &["no-private-ipv4"])] + #[arg(long, conflicts_with_all = &["no_private_ipv4"])] pub allow_private_ipv4: bool, /// Specify the number of outgoing connections we're trying to maintain. - #[structopt(long = "out-peers", value_name = "COUNT", default_value = "25")] + #[arg(long, value_name = "COUNT", default_value_t = 15)] pub out_peers: u32, - /// Specify the maximum number of incoming connections we're accepting. - #[structopt(long = "in-peers", value_name = "COUNT", default_value = "25")] + /// Maximum number of inbound full nodes peers. + #[arg(long, value_name = "COUNT", default_value_t = 25)] pub in_peers: u32, + /// Maximum number of inbound light nodes peers. + #[arg(long, value_name = "COUNT", default_value_t = 100)] + pub in_peers_light: u32, + /// Disable mDNS discovery. /// /// By default, the network will use mDNS to discover other nodes on the /// local network. This disables it. Automatically implied when using --dev. - #[structopt(long = "no-mdns")] + #[arg(long)] pub no_mdns: bool, /// Maximum number of peers from which to ask for the same blocks in parallel. /// /// This allows downloading announced blocks from multiple peers. Decrease to save /// traffic and risk increased latency. - #[structopt(long = "max-parallel-downloads", value_name = "COUNT", default_value = "5")] + #[arg(long, value_name = "COUNT", default_value_t = 5)] pub max_parallel_downloads: u32, #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub node_key_params: NodeKeyParams, /// Enable peer discovery on local networks. /// /// By default this option is `true` for `--dev` or when the chain type is /// `Local`/`Development` and false otherwise. - #[structopt(long)] + #[arg(long)] pub discover_local: bool, /// Require iterative Kademlia DHT queries to use disjoint paths for increased resiliency in @@ -120,21 +123,27 @@ pub struct NetworkParams { /// /// See the S/Kademlia paper for more information on the high level design as well as its /// security improvements. - #[structopt(long)] + #[arg(long)] pub kademlia_disjoint_query_paths: bool, /// Join the IPFS network and serve transactions over bitswap protocol. - #[structopt(long)] + #[arg(long)] pub ipfs_server: bool, /// Blockchain syncing mode. /// - /// - `Full`: Download and validate full blockchain history. - /// - /// - `Fast`: Download blocks and the latest state only. - /// - /// - `FastUnsafe`: Same as `Fast`, but skip downloading state proofs. - #[structopt(long, value_name = "SYNC_MODE", default_value = "Full")] + /// - `full`: Download and validate full blockchain history. + /// - `fast`: Download blocks and the latest state only. + /// - `fast-unsafe`: Same as `fast`, but skip downloading state proofs. + /// - `warp`: Download the latest state and proof. + #[arg( + long, + value_enum, + value_name = "SYNC_MODE", + default_value_t = SyncMode::Full, + ignore_case = true, + verbatim_doc_comment + )] pub sync: SyncMode, } @@ -154,7 +163,7 @@ impl NetworkParams { let port = self.port.unwrap_or(default_listen_port); let listen_addresses = if self.listen_addr.is_empty() { - if is_validator { + if is_validator || is_dev { vec![ Multiaddr::empty() .with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into())) @@ -203,7 +212,7 @@ impl NetworkParams { boot_nodes, net_config_path, default_peers_set: SetConfig { - in_peers: self.in_peers, + in_peers: self.in_peers + self.in_peers_light, out_peers: self.out_peers, reserved_nodes: self.reserved_nodes.clone(), non_reserved_mode: if self.reserved_only { @@ -212,6 +221,7 @@ impl NetworkParams { NonReservedPeerMode::Accept }, }, + default_peers_set_num_full: self.in_peers + self.out_peers, listen_addresses, public_addresses, extra_sets: Vec::new(), @@ -233,3 +243,55 @@ impl NetworkParams { } } } + +#[cfg(test)] +mod tests { + use super::*; + use clap::Parser; + + #[derive(Parser)] + struct Cli { + #[clap(flatten)] + network_params: NetworkParams, + } + + #[test] + fn reserved_nodes_multiple_values_and_occurrences() { + let params = Cli::try_parse_from([ + "", + "--reserved-nodes", + "/ip4/0.0.0.0/tcp/501/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS", + "/ip4/0.0.0.0/tcp/502/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS", + "--reserved-nodes", + "/ip4/0.0.0.0/tcp/503/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS", + ]) + .expect("Parses network params"); + + let expected = vec![ + MultiaddrWithPeerId::try_from( + "/ip4/0.0.0.0/tcp/501/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS" + .to_string(), + ) + .unwrap(), + MultiaddrWithPeerId::try_from( + "/ip4/0.0.0.0/tcp/502/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS" + .to_string(), + ) + .unwrap(), + MultiaddrWithPeerId::try_from( + "/ip4/0.0.0.0/tcp/503/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS" + .to_string(), + ) + .unwrap(), + ]; + + assert_eq!(expected, params.network_params.reserved_nodes); + } + + #[test] + fn sync_ingores_case() { + let params = Cli::try_parse_from(["", "--sync", "wArP"]).expect("Parses network params"); + + assert_eq!(SyncMode::Warp, params.network_params.sync); + } +} diff --git a/client/cli/src/params/node_key_params.rs b/client/cli/src/params/node_key_params.rs index 41f9033d282d1..2346455c26a37 100644 --- a/client/cli/src/params/node_key_params.rs +++ b/client/cli/src/params/node_key_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,10 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use clap::Args; use sc_network::config::{identity::ed25519, NodeKeyConfig}; use sp_core::H256; use std::{path::PathBuf, str::FromStr}; -use structopt::StructOpt; use crate::{arg_enums::NodeKeyType, error}; @@ -30,7 +30,7 @@ const NODE_KEY_ED25519_FILE: &str = "secret_ed25519"; /// Parameters used to create the `NodeKeyConfig`, which determines the keypair /// used for libp2p networking. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Args)] pub struct NodeKeyParams { /// The secret key to use for libp2p networking. /// @@ -46,7 +46,7 @@ pub struct NodeKeyParams { /// WARNING: Secrets provided as command-line arguments are easily exposed. /// Use of this option should be limited to development and testing. To use /// an externally managed secret key, use `--node-key-file` instead. - #[structopt(long = "node-key", value_name = "KEY")] + #[arg(long, value_name = "KEY")] pub node_key: Option, /// The type of secret key to use for libp2p networking. @@ -66,13 +66,7 @@ pub struct NodeKeyParams { /// /// The node's secret key determines the corresponding public key and hence the /// node's peer ID in the context of libp2p. - #[structopt( - long = "node-key-type", - value_name = "TYPE", - possible_values = &NodeKeyType::variants(), - case_insensitive = true, - default_value = "Ed25519" - )] + #[arg(long, value_name = "TYPE", value_enum, ignore_case = true, default_value_t = NodeKeyType::Ed25519)] pub node_key_type: NodeKeyType, /// The file from which to read the node's secret key to use for libp2p networking. @@ -85,7 +79,7 @@ pub struct NodeKeyParams { /// /// If the file does not exist, it is created with a newly generated secret key of /// the chosen type. - #[structopt(long = "node-key-file", value_name = "FILE")] + #[arg(long, value_name = "FILE")] pub node_key_file: Option, } @@ -118,7 +112,7 @@ fn invalid_node_key(e: impl std::fmt::Display) -> error::Error { /// Parse a Ed25519 secret key from a hex string into a `sc_network::Secret`. fn parse_ed25519_secret(hex: &str) -> error::Result { - H256::from_str(&hex).map_err(invalid_node_key).and_then(|bytes| { + H256::from_str(hex).map_err(invalid_node_key).and_then(|bytes| { ed25519::SecretKey::from_bytes(bytes) .map(sc_network::config::Secret::Input) .map_err(invalid_node_key) @@ -128,14 +122,15 @@ fn parse_ed25519_secret(hex: &str) -> error::Result error::Result<()> { - NodeKeyType::variants().iter().try_for_each(|t| { - let node_key_type = NodeKeyType::from_str(t).unwrap(); + NodeKeyType::value_variants().iter().try_for_each(|t| { + let node_key_type = *t; let sk = match node_key_type { NodeKeyType::Ed25519 => ed25519::SecretKey::generate().as_ref().to_vec(), }; @@ -181,7 +176,7 @@ mod tests { let file = tmp.path().join("mysecret").to_path_buf(); let key = ed25519::SecretKey::generate(); - fs::write(&file, hex::encode(key.as_ref())).expect("Writes secret key"); + fs::write(&file, array_bytes::bytes2hex("", key.as_ref())).expect("Writes secret key"); check_key(file.clone(), &key); fs::write(&file, &key).expect("Writes secret key"); @@ -194,8 +189,8 @@ mod tests { where F: Fn(NodeKeyParams) -> error::Result<()>, { - NodeKeyType::variants().iter().try_for_each(|t| { - let node_key_type = NodeKeyType::from_str(t).unwrap(); + 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 }) }) } diff --git a/client/cli/src/params/offchain_worker_params.rs b/client/cli/src/params/offchain_worker_params.rs index 685328ef17795..a660320fde255 100644 --- a/client/cli/src/params/offchain_worker_params.rs +++ b/client/cli/src/params/offchain_worker_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,24 +23,24 @@ //! targeted at handling input parameter parsing providing //! a reasonable abstraction. +use clap::{ArgAction, Args}; use sc_network::config::Role; use sc_service::config::OffchainWorkerConfig; -use structopt::StructOpt; use crate::{error, OffchainWorkerEnabled}; /// Offchain worker related parameters. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Args)] pub struct OffchainWorkerParams { /// Should execute offchain workers on every block. /// /// By default it's only enabled for nodes that are authoring new blocks. - #[structopt( + #[arg( long = "offchain-worker", value_name = "ENABLED", - possible_values = &OffchainWorkerEnabled::variants(), - case_insensitive = true, - default_value = "WhenValidating" + value_enum, + ignore_case = true, + default_value_t = OffchainWorkerEnabled::WhenValidating )] pub enabled: OffchainWorkerEnabled, @@ -48,7 +48,7 @@ pub struct OffchainWorkerParams { /// /// Enables a runtime to write directly to a offchain workers /// DB during block import. - #[structopt(long = "enable-offchain-indexing", value_name = "ENABLE_OFFCHAIN_INDEXING")] + #[arg(long = "enable-offchain-indexing", value_name = "ENABLE_OFFCHAIN_INDEXING", default_value_t = false, action = ArgAction::Set)] pub indexing_enabled: bool, } diff --git a/client/cli/src/params/pruning_params.rs b/client/cli/src/params/pruning_params.rs index 28c7fa301cc60..7e50f53d7169a 100644 --- a/client/cli/src/params/pruning_params.rs +++ b/client/cli/src/params/pruning_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,58 +17,99 @@ // along with this program. If not, see . use crate::error; -use sc_service::{KeepBlocks, PruningMode, Role}; -use structopt::StructOpt; +use clap::Args; +use sc_service::{BlocksPruning, PruningMode}; /// Parameters to define the pruning mode -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, PartialEq, Args)] pub struct PruningParams { - /// Specify the state pruning mode, a number of blocks to keep or 'archive'. + /// Specify the state pruning mode. /// - /// Default is to keep all block states if the node is running as a - /// validator (i.e. 'archive'), otherwise state is only kept for the last - /// 256 blocks. - #[structopt(long = "pruning", value_name = "PRUNING_MODE")] - pub pruning: Option, - /// Specify the number of finalized blocks to keep in the database. + /// This mode specifies when the block's state (ie, storage) + /// should be pruned (ie, removed) from the database. /// - /// Default is to keep all blocks. - #[structopt(long, value_name = "COUNT")] - pub keep_blocks: Option, + /// Possible values: + /// 'archive' Keep the state of all blocks. + /// 'archive-canonical' Keep only the state of finalized blocks. + /// number Keep the state of the last number of finalized blocks. + #[arg(alias = "pruning", long, value_name = "PRUNING_MODE", default_value = "256")] + pub state_pruning: DatabasePruningMode, + /// Specify the blocks pruning mode. + /// + /// This mode specifies when the block's body (including justifications) + /// should be pruned (ie, removed) from the database. + /// + /// Possible values: + /// 'archive' Keep all blocks. + /// 'archive-canonical' Keep only finalized blocks. + /// number Keep the last `number` of finalized blocks. + #[arg( + alias = "keep-blocks", + long, + value_name = "PRUNING_MODE", + default_value = "archive-canonical" + )] + pub blocks_pruning: DatabasePruningMode, } impl PruningParams { /// Get the pruning value from the parameters - pub fn state_pruning(&self, unsafe_pruning: bool, role: &Role) -> error::Result { - // by default we disable pruning if the node is an authority (i.e. - // `ArchiveAll`), otherwise we keep state for the last 256 blocks. if the - // node is an authority and pruning is enabled explicitly, then we error - // unless `unsafe_pruning` is set. - Ok(match &self.pruning { - Some(ref s) if s == "archive" => PruningMode::ArchiveAll, - None if role.is_authority() => PruningMode::ArchiveAll, - None => PruningMode::default(), - Some(s) => { - if role.is_authority() && !unsafe_pruning { - return Err(error::Error::Input( - "Validators should run with state pruning disabled (i.e. archive). \ - You can ignore this check with `--unsafe-pruning`." - .to_string(), - )) - } - - PruningMode::keep_blocks(s.parse().map_err(|_| { - error::Error::Input("Invalid pruning mode specified".to_string()) - })?) - }, - }) + pub fn state_pruning(&self) -> error::Result> { + Ok(Some(self.state_pruning.into())) } /// Get the block pruning value from the parameters - pub fn keep_blocks(&self) -> error::Result { - Ok(match self.keep_blocks { - Some(n) => KeepBlocks::Some(n), - None => KeepBlocks::All, - }) + pub fn blocks_pruning(&self) -> error::Result { + Ok(self.blocks_pruning.into()) + } +} + +/// Specifies the pruning mode of the database. +/// +/// This specifies when the block's data (either state via `--state-pruning` +/// or body via `--blocks-pruning`) should be pruned (ie, removed) from +/// the database. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DatabasePruningMode { + /// Keep the data of all blocks. + Archive, + /// Keep only the data of finalized blocks. + ArchiveCanonical, + /// Keep the data of the last number of finalized blocks. + Custom(u32), +} + +impl std::str::FromStr for DatabasePruningMode { + type Err = String; + + fn from_str(input: &str) -> Result { + match input { + "archive" => Ok(Self::Archive), + "archive-canonical" => Ok(Self::ArchiveCanonical), + bc => bc + .parse() + .map_err(|_| "Invalid pruning mode specified".to_string()) + .map(Self::Custom), + } + } +} + +impl Into for DatabasePruningMode { + fn into(self) -> PruningMode { + match self { + DatabasePruningMode::Archive => PruningMode::ArchiveAll, + DatabasePruningMode::ArchiveCanonical => PruningMode::ArchiveCanonical, + DatabasePruningMode::Custom(n) => PruningMode::blocks_pruning(n), + } + } +} + +impl Into for DatabasePruningMode { + fn into(self) -> BlocksPruning { + match self { + DatabasePruningMode::Archive => BlocksPruning::KeepAll, + DatabasePruningMode::ArchiveCanonical => BlocksPruning::KeepFinalized, + DatabasePruningMode::Custom(n) => BlocksPruning::Some(n), + } } } diff --git a/client/cli/src/params/shared_params.rs b/client/cli/src/params/shared_params.rs index 41472387d2639..5cbb6dbad54a3 100644 --- a/client/cli/src/params/shared_params.rs +++ b/client/cli/src/params/shared_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,66 +17,78 @@ // along with this program. If not, see . use crate::arg_enums::TracingReceiver; +use clap::Args; use sc_service::config::BasePath; use std::path::PathBuf; -use structopt::StructOpt; /// Shared parameters used by all `CoreParams`. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, PartialEq, Args)] pub struct SharedParams { /// Specify the chain specification. /// /// It can be one of the predefined ones (dev, local, or staging) or it can be a path to a file /// with the chainspec (such as one exported by the `build-spec` subcommand). - #[structopt(long, value_name = "CHAIN_SPEC")] + #[arg(long, value_name = "CHAIN_SPEC")] pub chain: Option, /// Specify the development chain. - #[structopt(long, conflicts_with_all = &["chain"])] + /// + /// This flag sets `--chain=dev`, `--force-authoring`, `--rpc-cors=all`, + /// `--alice`, and `--tmp` flags, unless explicitly overridden. + #[arg(long, conflicts_with_all = &["chain"])] pub dev: bool, /// Specify custom base path. - #[structopt(long, short = "d", value_name = "PATH", parse(from_os_str))] + #[arg(long, short = 'd', value_name = "PATH")] pub base_path: Option, - /// Sets a custom logging filter. Syntax is =, e.g. -lsync=debug. + /// Sets a custom logging filter. Syntax is `=`, e.g. -lsync=debug. /// /// Log levels (least to most verbose) are error, warn, info, debug, and trace. - /// By default, all targets log `info`. The global log level can be set with -l. - #[structopt(short = "l", long, value_name = "LOG_PATTERN")] + /// By default, all targets log `info`. The global log level can be set with `-l`. + #[arg(short = 'l', long, value_name = "LOG_PATTERN", num_args = 1..)] pub log: Vec, + /// Enable detailed log output. + /// + /// This includes displaying the log target, log level and thread name. + /// + /// This is automatically enabled when something is logged with any higher level than `info`. + #[arg(long)] + pub detailed_log_output: bool, + /// Disable log color output. - #[structopt(long)] + #[arg(long)] pub disable_log_color: bool, - /// Disable feature to dynamically update and reload the log filter. + /// Enable feature to dynamically update and reload the log filter. + /// + /// Be aware that enabling this feature can lead to a performance decrease up to factor six or + /// more. Depending on the global logging level the performance decrease changes. /// - /// By default this feature is enabled, however it leads to a small performance decrease. /// The `system_addLogFilter` and `system_resetLogFilter` RPCs will have no effect with this - /// option set. - #[structopt(long = "disable-log-reloading")] - pub disable_log_reloading: bool, + /// option not being set. + #[arg(long)] + pub enable_log_reloading: bool, - /// Sets a custom profiling filter. Syntax is the same as for logging: = - #[structopt(long = "tracing-targets", value_name = "TARGETS")] + /// Sets a custom profiling filter. Syntax is the same as for logging: `=`. + #[arg(long, value_name = "TARGETS")] pub tracing_targets: Option, /// Receiver to process tracing messages. - #[structopt( - long = "tracing-receiver", - value_name = "RECEIVER", - possible_values = &TracingReceiver::variants(), - case_insensitive = true, - default_value = "Log" - )] + #[arg(long, value_name = "RECEIVER", value_enum, ignore_case = true, default_value_t = TracingReceiver::Log)] pub tracing_receiver: TracingReceiver, } impl SharedParams { /// Specify custom base path. - pub fn base_path(&self) -> Option { - self.base_path.clone().map(Into::into) + pub fn base_path(&self) -> Result, crate::Error> { + match &self.base_path { + Some(r) => Ok(Some(r.clone().into())), + // If `dev` is enabled, we use the temp base path. + None if self.is_dev() => Ok(Some(BasePath::new_temp_dir()?)), + None => Ok(None), + } } /// Specify the development chain. @@ -102,19 +114,24 @@ impl SharedParams { &self.log } + /// Should the detailed log output be enabled. + pub fn detailed_log_output(&self) -> bool { + self.detailed_log_output + } + /// Should the log color output be disabled? pub fn disable_log_color(&self) -> bool { self.disable_log_color } - /// Is log reloading disabled - pub fn is_log_filter_reloading_disabled(&self) -> bool { - self.disable_log_reloading + /// Is log reloading enabled + pub fn enable_log_reloading(&self) -> bool { + self.enable_log_reloading } /// Receiver to process tracing messages. pub fn tracing_receiver(&self) -> sc_service::TracingReceiver { - self.tracing_receiver.clone().into() + self.tracing_receiver.into() } /// Comma separated list of targets for tracing. diff --git a/client/cli/src/params/transaction_pool_params.rs b/client/cli/src/params/transaction_pool_params.rs index feea19c97c2d6..6b3a2d8a97a01 100644 --- a/client/cli/src/params/transaction_pool_params.rs +++ b/client/cli/src/params/transaction_pool_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,24 +16,28 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use clap::Args; use sc_service::config::TransactionPoolOptions; -use structopt::StructOpt; /// Parameters used to create the pool configuration. -#[derive(Debug, StructOpt, Clone)] +#[derive(Debug, Clone, Args)] pub struct TransactionPoolParams { /// Maximum number of transactions in the transaction pool. - #[structopt(long = "pool-limit", value_name = "COUNT", default_value = "8192")] + #[arg(long, value_name = "COUNT", default_value_t = 8192)] pub pool_limit: usize, /// Maximum number of kilobytes of all transactions stored in the pool. - #[structopt(long = "pool-kbytes", value_name = "COUNT", default_value = "20480")] + #[arg(long, value_name = "COUNT", default_value_t = 20480)] pub pool_kbytes: usize, + + /// How long a transaction is banned for, if it is considered invalid. Defaults to 1800s. + #[arg(long, value_name = "SECONDS")] + pub tx_ban_seconds: Option, } impl TransactionPoolParams { /// Fill the given `PoolConfiguration` by looking at the cli parameters. - pub fn transaction_pool(&self) -> TransactionPoolOptions { + pub fn transaction_pool(&self, is_dev: bool) -> TransactionPoolOptions { let mut opts = TransactionPoolOptions::default(); // ready queue @@ -45,6 +49,14 @@ impl TransactionPoolParams { opts.future.count = self.pool_limit / factor; opts.future.total_bytes = self.pool_kbytes * 1024 / factor; + opts.ban_time = if let Some(ban_seconds) = self.tx_ban_seconds { + std::time::Duration::from_secs(ban_seconds) + } else if is_dev { + std::time::Duration::from_secs(0) + } else { + std::time::Duration::from_secs(30 * 60) + }; + opts } } diff --git a/client/cli/src/runner.rs b/client/cli/src/runner.rs index 6f03e02a12d05..d4191feddfa90 100644 --- a/client/cli/src/runner.rs +++ b/client/cli/src/runner.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,13 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{error::Error as CliError, CliConfiguration, Result, SubstrateCli}; +use crate::{error::Error as CliError, Result, SubstrateCli}; use chrono::prelude::*; use futures::{future, future::FutureExt, pin_mut, select, Future}; use log::info; use sc_service::{Configuration, Error as ServiceError, TaskManager}; use sc_utils::metrics::{TOKIO_THREADS_ALIVE, TOKIO_THREADS_TOTAL}; -use std::marker::PhantomData; +use std::{marker::PhantomData, time::Duration}; #[cfg(target_family = "unix")] async fn main(func: F) -> std::result::Result<(), E> @@ -98,7 +98,7 @@ where pin_mut!(f); tokio_runtime.block_on(main(f))?; - tokio_runtime.block_on(task_manager.clean_shutdown()); + drop(task_manager); Ok(()) } @@ -112,15 +112,8 @@ pub struct Runner { impl Runner { /// Create a new runtime with the command provided in argument - pub fn new(cli: &C, command: &T) -> Result> { - let tokio_runtime = build_runtime()?; - let runtime_handle = tokio_runtime.handle().clone(); - - Ok(Runner { - config: command.create_configuration(cli, runtime_handle)?, - tokio_runtime, - phantom: PhantomData, - }) + pub fn new(config: Configuration, tokio_runtime: tokio::runtime::Runtime) -> Result> { + Ok(Runner { config, tokio_runtime, phantom: PhantomData }) } /// Log information about the node itself. @@ -132,7 +125,7 @@ impl Runner { /// 2020-06-03 16:14:21 ✌️ version 2.0.0-rc3-f4940588c-x86_64-linux-gnu /// 2020-06-03 16:14:21 ❤️ by Parity Technologies , 2017-2020 /// 2020-06-03 16:14:21 📋 Chain specification: Flaming Fir - /// 2020-06-03 16:14:21 🏷 Node name: jolly-rod-7462 + /// 2020-06-03 16:14:21 🏷 Node name: jolly-rod-7462 /// 2020-06-03 16:14:21 👤 Role: FULL /// 2020-06-03 16:14:21 💾 Database: RocksDb at /tmp/c/chains/flamingfir7/db /// 2020-06-03 16:14:21 ⛓ Native runtime: node-251 (substrate-node-1.tx1.au10) @@ -152,10 +145,19 @@ impl Runner { E: std::error::Error + Send + Sync + 'static + From, { self.print_node_infos(); + let mut task_manager = self.tokio_runtime.block_on(initialize(self.config))?; let res = self.tokio_runtime.block_on(main(task_manager.future().fuse())); - self.tokio_runtime.block_on(task_manager.clean_shutdown()); - Ok(res?) + // We need to drop the task manager here to inform all tasks that they should shut down. + // + // This is important to be done before we instruct the tokio runtime to shutdown. Otherwise + // the tokio runtime will wait the full 60 seconds for all tasks to stop. + drop(task_manager); + + // Give all futures 60 seconds to shutdown, before tokio "leaks" them. + self.tokio_runtime.shutdown_timeout(Duration::from_secs(60)); + + res.map_err(Into::into) } /// A helper function that runs a command with the configuration of this node. @@ -200,7 +202,7 @@ pub fn print_node_infos(config: &Configuration) { info!("✌️ version {}", C::impl_version()); info!("❤️ by {}, {}-{}", C::author(), C::copyright_start_year(), Local::today().year()); info!("📋 Chain specification: {}", config.chain_spec.name()); - info!("🏷 Node name: {}", config.network.node_name); + info!("🏷 Node name: {}", config.network.node_name); info!("👤 Role: {}", config.display_role()); info!( "💾 Database: {} at {}", @@ -212,3 +214,208 @@ pub fn print_node_infos(config: &Configuration) { ); info!("⛓ Native runtime: {}", C::native_runtime_version(&config.chain_spec)); } + +#[cfg(test)] +mod tests { + use std::{ + path::PathBuf, + sync::atomic::{AtomicU64, Ordering}, + }; + + use sc_network::config::NetworkConfiguration; + use sc_service::{Arc, ChainType, GenericChainSpec, NoExtension}; + use sp_runtime::create_runtime_str; + use sp_version::create_apis_vec; + + use super::*; + + struct Cli; + + impl SubstrateCli for Cli { + fn author() -> String { + "test".into() + } + + fn impl_name() -> String { + "yep".into() + } + + fn impl_version() -> String { + "version".into() + } + + fn description() -> String { + "desc".into() + } + + fn support_url() -> String { + "no.pe".into() + } + + fn copyright_start_year() -> i32 { + 2042 + } + + fn load_spec( + &self, + _: &str, + ) -> std::result::Result, String> { + Err("nope".into()) + } + + fn native_runtime_version( + _: &Box, + ) -> &'static sp_version::RuntimeVersion { + const VERSION: sp_version::RuntimeVersion = sp_version::RuntimeVersion { + spec_name: create_runtime_str!("spec"), + impl_name: create_runtime_str!("name"), + authoring_version: 0, + spec_version: 0, + impl_version: 0, + apis: create_apis_vec!([]), + transaction_version: 2, + state_version: 0, + }; + + &VERSION + } + } + + fn create_runner() -> Runner { + let runtime = build_runtime().unwrap(); + + let runner = Runner::new( + Configuration { + impl_name: "spec".into(), + impl_version: "3".into(), + role: sc_service::Role::Authority, + tokio_handle: runtime.handle().clone(), + transaction_pool: Default::default(), + network: NetworkConfiguration::new_memory(), + keystore: sc_service::config::KeystoreConfig::InMemory, + keystore_remote: None, + database: sc_client_db::DatabaseSource::ParityDb { path: PathBuf::from("db") }, + trie_cache_maximum_size: None, + state_pruning: None, + blocks_pruning: sc_client_db::BlocksPruning::KeepAll, + chain_spec: Box::new(GenericChainSpec::from_genesis( + "test", + "test_id", + ChainType::Development, + || unimplemented!("Not required in tests"), + Vec::new(), + None, + None, + None, + None, + NoExtension::None, + )), + wasm_method: Default::default(), + wasm_runtime_overrides: None, + execution_strategies: Default::default(), + rpc_http: None, + rpc_ws: None, + rpc_ipc: None, + rpc_ws_max_connections: None, + rpc_cors: None, + rpc_methods: Default::default(), + rpc_max_payload: None, + rpc_max_request_size: None, + rpc_max_response_size: None, + rpc_id_provider: None, + rpc_max_subs_per_conn: None, + ws_max_out_buffer_capacity: None, + prometheus_config: None, + telemetry_endpoints: None, + default_heap_pages: None, + offchain_worker: Default::default(), + force_authoring: false, + disable_grandpa: false, + dev_key_seed: None, + tracing_targets: None, + tracing_receiver: Default::default(), + max_runtime_instances: 8, + announce_block: true, + base_path: None, + informant_output_format: Default::default(), + runtime_cache_size: 2, + }, + runtime, + ) + .unwrap(); + + runner + } + + #[test] + fn ensure_run_until_exit_informs_tasks_to_end() { + let runner = create_runner(); + + let counter = Arc::new(AtomicU64::new(0)); + let counter2 = counter.clone(); + + runner + .run_node_until_exit(move |cfg| async move { + let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap(); + let (sender, receiver) = futures::channel::oneshot::channel(); + + // We need to use `spawn_blocking` here so that we get a dedicated thread for our + // future. This is important for this test, as otherwise tokio can just "drop" the + // future. + task_manager.spawn_handle().spawn_blocking("test", None, async move { + let _ = sender.send(()); + loop { + counter2.fetch_add(1, Ordering::Relaxed); + futures_timer::Delay::new(Duration::from_millis(50)).await; + } + }); + + task_manager.spawn_essential_handle().spawn_blocking("test2", None, async { + // Let's stop this essential task directly when our other task started. + // It will signal that the task manager should end. + let _ = receiver.await; + }); + + Ok::<_, sc_service::Error>(task_manager) + }) + .unwrap_err(); + + let count = counter.load(Ordering::Relaxed); + + // Ensure that our counting task was running for less than 30 seconds. + // It should be directly killed, but for CI and whatever we are being a little bit more + // "relaxed". + assert!((count as u128) < (Duration::from_secs(30).as_millis() / 50)); + } + + /// This test ensures that `run_node_until_exit` aborts waiting for "stuck" tasks after 60 + /// seconds, aka doesn't wait until they are finished (which may never happen). + #[test] + fn ensure_run_until_exit_is_not_blocking_indefinitely() { + let runner = create_runner(); + + runner + .run_node_until_exit(move |cfg| async move { + let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap(); + let (sender, receiver) = futures::channel::oneshot::channel(); + + // We need to use `spawn_blocking` here so that we get a dedicated thread for our + // future. This future is more blocking code that will never end. + task_manager.spawn_handle().spawn_blocking("test", None, async move { + let _ = sender.send(()); + loop { + std::thread::sleep(Duration::from_secs(30)); + } + }); + + task_manager.spawn_essential_handle().spawn_blocking("test2", None, async { + // Let's stop this essential task directly when our other task started. + // It will signal that the task manager should end. + let _ = receiver.await; + }); + + Ok::<_, sc_service::Error>(task_manager) + }) + .unwrap_err(); + } +} diff --git a/client/consensus/aura/Cargo.toml b/client/consensus/aura/Cargo.toml index 75595779427bb..47aee0ec084eb 100644 --- a/client/consensus/aura/Cargo.toml +++ b/client/consensus/aura/Cargo.toml @@ -3,9 +3,9 @@ name = "sc-consensus-aura" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Aura consensus algorithm for substrate" -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,39 +13,37 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "4.0.0-dev", path = "../../../primitives/application-crypto" } -sp-consensus-aura = { version = "0.10.0-dev", path = "../../../primitives/consensus/aura" } -sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" +log = "0.4.17" +thiserror = "1.0" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } -codec = { package = "parity-scale-codec", version = "2.0.0" } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } -derive_more = "0.99.2" -futures = "0.3.9" -sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -log = "0.4.8" -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sc-consensus-slots = { version = "0.10.0-dev", path = "../slots" } -sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sp-keystore = { version = "0.10.0-dev", path = "../../../primitives/keystore" } sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.9.0" } -async-trait = "0.1.50" -# We enable it only for web-wasm check -# See https://docs.rs/getrandom/0.2.1/getrandom/#webassembly-support -getrandom = { version = "0.2", features = ["js"], optional = true } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-application-crypto = { version = "7.0.0", path = "../../../primitives/application-crypto" } +sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-consensus-aura = { version = "0.10.0-dev", path = "../../../primitives/consensus/aura" } +sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } [dev-dependencies] -sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -sp-keyring = { version = "4.0.0-dev", path = "../../../primitives/keyring" } -sp-tracing = { version = "4.0.0-dev", path = "../../../primitives/tracing" } +parking_lot = "0.12.1" +tempfile = "3.1.0" sc-keystore = { version = "4.0.0-dev", path = "../../keystore" } sc-network = { version = "0.10.0-dev", path = "../../network" } sc-network-test = { version = "0.8.0", path = "../../network/test" } +sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } +sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -tempfile = "3.1.0" -parking_lot = "0.11.1" +tokio = { version = "1.22.0" } diff --git a/client/consensus/aura/src/import_queue.rs b/client/consensus/aura/src/import_queue.rs index a4dbe5012ea19..07f982542c95b 100644 --- a/client/consensus/aura/src/import_queue.rs +++ b/client/consensus/aura/src/import_queue.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,7 +18,9 @@ //! Module implementing the logic for verifying and importing AuRa blocks. -use crate::{aura_err, authorities, find_pre_digest, slot_author, AuthorityId, Error}; +use crate::{ + aura_err, authorities, find_pre_digest, slot_author, AuthorityId, CompatibilityMode, Error, +}; use codec::{Codec, Decode, Encode}; use log::{debug, info, trace}; use prometheus_endpoint::Registry; @@ -31,21 +33,16 @@ use sc_consensus_slots::{check_equivocation, CheckedHeader, InherentDataProvider use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE}; use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_block_builder::BlockBuilder as BlockBuilderApi; -use sp_blockchain::{ - well_known_cache_keys::{self, Id as CacheKeyId}, - HeaderBackend, ProvideCache, -}; -use sp_consensus::{CanAuthorWith, Error as ConsensusError}; -use sp_consensus_aura::{ - digests::CompatibleDigestItem, inherents::AuraInherentData, AuraApi, ConsensusLog, - AURA_ENGINE_ID, -}; +use sp_blockchain::{well_known_cache_keys::Id as CacheKeyId, HeaderBackend}; +use sp_consensus::Error as ConsensusError; +use sp_consensus_aura::{digests::CompatibleDigestItem, inherents::AuraInherentData, AuraApi}; use sp_consensus_slots::Slot; use sp_core::{crypto::Pair, ExecutionContext}; use sp_inherents::{CreateInherentDataProviders, InherentDataProvider as _}; use sp_runtime::{ - generic::{BlockId, OpaqueDigestItemId}, - traits::{Block as BlockT, DigestItemFor, Header}, + generic::BlockId, + traits::{Block as BlockT, Header, NumberFor}, + DigestItem, }; use std::{fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc}; @@ -61,14 +58,13 @@ fn check_header( hash: B::Hash, authorities: &[AuthorityId

], check_for_equivocation: CheckForEquivocation, -) -> Result)>, Error> +) -> Result, Error> where - DigestItemFor: CompatibleDigestItem, P::Signature: Codec, C: sc_client_api::backend::AuxStore, P::Public: Encode + Decode + PartialEq + Clone, { - let seal = header.digest_mut().pop().ok_or_else(|| Error::HeaderUnsealed(hash))?; + let seal = header.digest_mut().pop().ok_or(Error::HeaderUnsealed(hash))?; let sig = seal.as_aura_seal().ok_or_else(|| aura_err(Error::HeaderBadSeal(hash)))?; @@ -81,7 +77,7 @@ where // check the signature is valid under the expected authority and // chain state. let expected_author = - slot_author::

(slot, &authorities).ok_or_else(|| Error::SlotAuthorNotFound)?; + slot_author::

(slot, authorities).ok_or(Error::SlotAuthorNotFound)?; let pre_hash = header.hash(); @@ -109,38 +105,37 @@ where } /// A verifier for Aura blocks. -pub struct AuraVerifier { +pub struct AuraVerifier { client: Arc, phantom: PhantomData

, create_inherent_data_providers: CIDP, - can_author_with: CAW, check_for_equivocation: CheckForEquivocation, telemetry: Option, + compatibility_mode: CompatibilityMode, } -impl AuraVerifier { +impl AuraVerifier { pub(crate) fn new( client: Arc, create_inherent_data_providers: CIDP, - can_author_with: CAW, check_for_equivocation: CheckForEquivocation, telemetry: Option, + compatibility_mode: CompatibilityMode, ) -> Self { Self { client, create_inherent_data_providers, - can_author_with, check_for_equivocation, telemetry, + compatibility_mode, phantom: PhantomData, } } } -impl AuraVerifier +impl AuraVerifier where P: Send + Sync + 'static, - CAW: Send + Sync + 'static, CIDP: Send, { async fn check_inherents( @@ -154,19 +149,8 @@ where where C: ProvideRuntimeApi, C::Api: BlockBuilderApi, - CAW: CanAuthorWith, CIDP: CreateInherentDataProviders, { - if let Err(e) = self.can_author_with.can_author_with(&block_id) { - debug!( - target: "aura", - "Skipping `check_inherents` as authoring version is not compatible: {}", - e, - ); - - return Ok(()) - } - let inherent_res = self .client .runtime_api() @@ -187,20 +171,13 @@ where } #[async_trait::async_trait] -impl Verifier for AuraVerifier +impl Verifier for AuraVerifier> where - C: ProvideRuntimeApi - + Send - + Sync - + sc_client_api::backend::AuxStore - + ProvideCache - + BlockOf, + C: ProvideRuntimeApi + Send + Sync + sc_client_api::backend::AuxStore, C::Api: BlockBuilderApi + AuraApi> + ApiExt, - DigestItemFor: CompatibleDigestItem, P: Pair + Send + Sync + 'static, P::Public: Send + Sync + Hash + Eq + Clone + Decode + Encode + Debug + 'static, P::Signature: Encode + Decode, - CAW: CanAuthorWith + Send + Sync + 'static, CIDP: CreateInherentDataProviders + Send + Sync, CIDP::InherentDataProviders: InherentDataProviderExt + Send + Sync, { @@ -210,8 +187,13 @@ where ) -> Result<(BlockImportParams, Option)>>), String> { let hash = block.header.hash(); let parent_hash = *block.header.parent_hash(); - let authorities = authorities(self.client.as_ref(), &BlockId::Hash(parent_hash)) - .map_err(|e| format!("Could not fetch authorities at {:?}: {:?}", parent_hash, e))?; + let authorities = authorities( + self.client.as_ref(), + parent_hash, + *block.header.number(), + &self.compatibility_mode, + ) + .map_err(|e| format!("Could not fetch authorities at {:?}: {}", parent_hash, e))?; let create_inherent_data_providers = self .create_inherent_data_providers @@ -221,6 +203,7 @@ where let mut inherent_data = create_inherent_data_providers .create_inherent_data() + .await .map_err(Error::::Inherent)?; let slot_now = create_inherent_data_providers.slot(); @@ -247,15 +230,16 @@ where inherent_data.aura_replace_inherent_data(slot); - // skip the inherents verification if the runtime API is old. - if self - .client - .runtime_api() - .has_api_with::, _>( - &BlockId::Hash(parent_hash), - |v| v >= 2, - ) - .map_err(|e| format!("{:?}", e))? + // skip the inherents verification if the runtime API is old or not expected to + // exist. + if !block.state_action.skip_execution_checks() && + self.client + .runtime_api() + .has_api_with::, _>( + &BlockId::Hash(parent_hash), + |v| v >= 2, + ) + .map_err(|e| e.to_string())? { self.check_inherents( new_block.clone(), @@ -280,28 +264,12 @@ where "pre_header" => ?pre_header, ); - // Look for an authorities-change log. - let maybe_keys = pre_header - .digest() - .logs() - .iter() - .filter_map(|l| { - l.try_to::>>(OpaqueDigestItemId::Consensus( - &AURA_ENGINE_ID, - )) - }) - .find_map(|l| match l { - ConsensusLog::AuthoritiesChange(a) => - Some(vec![(well_known_cache_keys::AUTHORITIES, a.encode())]), - _ => None, - }); - block.header = pre_header; block.post_digests.push(seal); block.fork_choice = Some(ForkChoiceStrategy::LongestChain); block.post_hash = Some(hash); - Ok((block, maybe_keys)) + Ok((block, None)) }, CheckedHeader::Deferred(a, b) => { debug!(target: "aura", "Checking {:?} failed; {:?}, {:?}.", hash, a, b); @@ -344,7 +312,7 @@ impl Default for CheckForEquivocation { } /// Parameters of [`import_queue`]. -pub struct ImportQueueParams<'a, Block, I, C, S, CAW, CIDP> { +pub struct ImportQueueParams<'a, Block: BlockT, I, C, S, CIDP> { /// The block import to use. pub block_import: I, /// The justification import. @@ -357,16 +325,18 @@ pub struct ImportQueueParams<'a, Block, I, C, S, CAW, CIDP> { pub spawner: &'a S, /// The prometheus registry. pub registry: Option<&'a Registry>, - /// Can we author with the current node? - pub can_author_with: CAW, /// Should we check for equivocation? pub check_for_equivocation: CheckForEquivocation, /// Telemetry instance used to report telemetry metrics. pub telemetry: Option, + /// Compatibility mode that should be used. + /// + /// If in doubt, use `Default::default()`. + pub compatibility_mode: CompatibilityMode>, } /// Start an import queue for the Aura consensus algorithm. -pub fn import_queue<'a, P, Block, I, C, S, CAW, CIDP>( +pub fn import_queue( ImportQueueParams { block_import, justification_import, @@ -374,10 +344,10 @@ pub fn import_queue<'a, P, Block, I, C, S, CAW, CIDP>( create_inherent_data_providers, spawner, registry, - can_author_with, check_for_equivocation, telemetry, - }: ImportQueueParams<'a, Block, I, C, S, CAW, CIDP>, + compatibility_mode, + }: ImportQueueParams, ) -> Result, sp_consensus::Error> where Block: BlockT, @@ -385,7 +355,6 @@ where C: 'static + ProvideRuntimeApi + BlockOf - + ProvideCache + Send + Sync + AuxStore @@ -395,55 +364,55 @@ where + Send + Sync + 'static, - DigestItemFor: CompatibleDigestItem, P: Pair + Send + Sync + 'static, P::Public: Clone + Eq + Send + Sync + Hash + Debug + Encode + Decode, P::Signature: Encode + Decode, S: sp_core::traits::SpawnEssentialNamed, - CAW: CanAuthorWith + Send + Sync + 'static, CIDP: CreateInherentDataProviders + Sync + Send + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send + Sync, { let verifier = build_verifier::(BuildVerifierParams { client, create_inherent_data_providers, - can_author_with, check_for_equivocation, telemetry, + compatibility_mode, }); Ok(BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry)) } /// Parameters of [`build_verifier`]. -pub struct BuildVerifierParams { +pub struct BuildVerifierParams { /// The client to interact with the chain. pub client: Arc, /// Something that can create the inherent data providers. pub create_inherent_data_providers: CIDP, - /// Can we author with the current node? - pub can_author_with: CAW, /// Should we check for equivocation? pub check_for_equivocation: CheckForEquivocation, /// Telemetry instance used to report telemetry metrics. pub telemetry: Option, + /// Compatibility mode that should be used. + /// + /// If in doubt, use `Default::default()`. + pub compatibility_mode: CompatibilityMode, } /// Build the [`AuraVerifier`] -pub fn build_verifier( +pub fn build_verifier( BuildVerifierParams { client, create_inherent_data_providers, - can_author_with, check_for_equivocation, telemetry, - }: BuildVerifierParams, -) -> AuraVerifier { + compatibility_mode, + }: BuildVerifierParams, +) -> AuraVerifier { AuraVerifier::<_, P, _, _>::new( client, create_inherent_data_providers, - can_author_with, check_for_equivocation, telemetry, + compatibility_mode, ) } diff --git a/client/consensus/aura/src/lib.rs b/client/consensus/aura/src/lib.rs index 946e0b90c4dd4..46b9124f9077f 100644 --- a/client/consensus/aura/src/lib.rs +++ b/client/consensus/aura/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -30,14 +30,7 @@ //! //! NOTE: Aura itself is designed to be generic over the crypto used. #![forbid(missing_docs, unsafe_code)] -use std::{ - convert::{TryFrom, TryInto}, - fmt::Debug, - hash::Hash, - marker::PhantomData, - pin::Pin, - sync::Arc, -}; +use std::{fmt::Debug, hash::Hash, marker::PhantomData, pin::Pin, sync::Arc}; use futures::prelude::*; use log::{debug, trace}; @@ -47,22 +40,22 @@ use codec::{Codec, Decode, Encode}; use sc_client_api::{backend::AuxStore, BlockOf, UsageProvider}; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, StateAction}; use sc_consensus_slots::{ - BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SlotInfo, StorageChanges, + BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SimpleSlotWorkerToSlotWorker, + SlotInfo, StorageChanges, }; use sc_telemetry::TelemetryHandle; -use sp_api::ProvideRuntimeApi; +use sp_api::{Core, ProvideRuntimeApi}; use sp_application_crypto::{AppKey, AppPublic}; -use sp_blockchain::{HeaderBackend, ProvideCache, Result as CResult}; -use sp_consensus::{ - BlockOrigin, CanAuthorWith, Environment, Error as ConsensusError, Proposer, SelectChain, -}; +use sp_blockchain::{HeaderBackend, Result as CResult}; +use sp_consensus::{BlockOrigin, Environment, Error as ConsensusError, Proposer, SelectChain}; use sp_consensus_slots::Slot; -use sp_core::crypto::{Pair, Public}; +use sp_core::crypto::{ByteArray, Pair, Public}; use sp_inherents::CreateInherentDataProviders; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ generic::BlockId, - traits::{Block as BlockT, DigestItemFor, Header, Member, NumberFor, Zero}, + traits::{Block as BlockT, Header, Member, NumberFor, Zero}, + DigestItem, }; mod import_queue; @@ -76,15 +69,49 @@ pub use sp_consensus::SyncOracle; pub use sp_consensus_aura::{ digests::CompatibleDigestItem, inherents::{InherentDataProvider, InherentType as AuraInherent, INHERENT_IDENTIFIER}, - AuraApi, ConsensusLog, AURA_ENGINE_ID, + AuraApi, ConsensusLog, SlotDuration, AURA_ENGINE_ID, }; type AuthorityId

=

::Public; -/// Slot duration type for Aura. -pub type SlotDuration = sc_consensus_slots::SlotDuration; +/// Run `AURA` in a compatibility mode. +/// +/// This is required for when the chain was launched and later there +/// was a consensus breaking change. +#[derive(Debug, Clone)] +pub enum CompatibilityMode { + /// Don't use any compatibility mode. + None, + /// Call `initialize_block` before doing any runtime calls. + /// + /// Previously the node would execute `initialize_block` before fetchting the authorities + /// from the runtime. This behaviour changed in: + /// + /// By calling `initialize_block` before fetching the authorities, on a block that + /// would enact a new validator set, the block would already be build/sealed by an + /// authority of the new set. With this mode disabled (the default) a block that enacts a new + /// set isn't sealed/built by an authority of the new set, however to make new nodes be able to + /// sync old chains this compatibility mode exists. + UseInitializeBlock { + /// The block number until this compatibility mode should be executed. The first runtime + /// call in the context of the `until` block (importing it/building it) will disable the + /// compatibility mode (i.e. at `until` the default rules will apply). When enabling this + /// compatibility mode the `until` block should be a future block on which all nodes will + /// have upgraded to a release that includes the updated compatibility mode configuration. + /// At `until` block there will be a hard fork when the authority set changes, between the + /// old nodes (running with `initialize_block`, i.e. without the compatibility mode + /// configuration) and the new nodes. + until: N, + }, +} + +impl Default for CompatibilityMode { + fn default() -> Self { + Self::None + } +} -/// Get type of `SlotDuration` for Aura. +/// Get the slot duration for Aura. pub fn slot_duration(client: &C) -> CResult where A: Codec, @@ -92,7 +119,8 @@ where C: AuxStore + ProvideRuntimeApi + UsageProvider, C::Api: AuraApi, { - SlotDuration::get_or_compute(client, |a, b| a.slot_duration(b).map_err(Into::into)) + let best_block_id = BlockId::Hash(client.usage_info().chain.best_hash); + client.runtime_api().slot_duration(&best_block_id).map_err(|err| err.into()) } /// Get slot author for given block along with authorities. @@ -115,7 +143,7 @@ fn slot_author(slot: Slot, authorities: &[AuthorityId

]) -> Option<&A } /// Parameters of [`start_aura`]. -pub struct StartAuraParams { +pub struct StartAuraParams { /// The duration of a slot. pub slot_duration: SlotDuration, /// The client to interact with the chain. @@ -138,8 +166,6 @@ pub struct StartAuraParams { pub backoff_authoring_blocks: Option, /// The keystore used by the node. pub keystore: SyncCryptoStorePtr, - /// Can we author a block with this node? - pub can_author_with: CAW, /// The proportion of the slot dedicated to proposing. /// /// The block proposing will be limited to this proportion of the slot from the starting of the @@ -151,10 +177,14 @@ pub struct StartAuraParams { pub max_block_proposal_slot_portion: Option, /// Telemetry instance used to report telemetry metrics. pub telemetry: Option, + /// Compatibility mode that should be used. + /// + /// If in doubt, use `Default::default()`. + pub compatibility_mode: CompatibilityMode, } /// Start the aura worker. The returned future should be run in a futures executor. -pub fn start_aura( +pub fn start_aura( StartAuraParams { slot_duration, client, @@ -167,18 +197,18 @@ pub fn start_aura( force_authoring, backoff_authoring_blocks, keystore, - can_author_with, block_proposal_slot_portion, max_block_proposal_slot_portion, telemetry, - }: StartAuraParams, + compatibility_mode, + }: StartAuraParams>, ) -> Result, sp_consensus::Error> where P: Pair + Send + Sync, P::Public: AppPublic + Hash + Member + Encode + Decode, P::Signature: TryFrom> + Hash + Member + Encode + Decode, B: BlockT, - C: ProvideRuntimeApi + BlockOf + ProvideCache + AuxStore + HeaderBackend + Send + Sync, + C: ProvideRuntimeApi + BlockOf + AuxStore + HeaderBackend + Send + Sync, C::Api: AuraApi>, SC: SelectChain, I: BlockImport> + Send + Sync + 'static, @@ -186,14 +216,13 @@ where PF::Proposer: Proposer>, SO: SyncOracle + Send + Sync + Clone, L: sc_consensus::JustificationSyncLink, - CIDP: CreateInherentDataProviders + Send, + CIDP: CreateInherentDataProviders + Send + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send, BS: BackoffAuthoringBlocksStrategy> + Send + Sync + 'static, - CAW: CanAuthorWith + Send, Error: std::error::Error + Send + From + 'static, { let worker = build_aura_worker::(BuildAuraWorkerParams { - client: client.clone(), + client, block_import, proposer_factory, keystore, @@ -204,20 +233,20 @@ where telemetry, block_proposal_slot_portion, max_block_proposal_slot_portion, + compatibility_mode, }); Ok(sc_consensus_slots::start_slot_worker( slot_duration, select_chain, - worker, + SimpleSlotWorkerToSlotWorker(worker), sync_oracle, create_inherent_data_providers, - can_author_with, )) } /// Parameters of [`build_aura_worker`]. -pub struct BuildAuraWorkerParams { +pub struct BuildAuraWorkerParams { /// The client to interact with the chain. pub client: Arc, /// The block import. @@ -245,6 +274,10 @@ pub struct BuildAuraWorkerParams { pub max_block_proposal_slot_portion: Option, /// Telemetry instance used to report telemetry metrics. pub telemetry: Option, + /// Compatibility mode that should be used. + /// + /// If in doubt, use `Default::default()`. + pub compatibility_mode: CompatibilityMode, } /// Build the aura worker. @@ -263,11 +296,20 @@ pub fn build_aura_worker( max_block_proposal_slot_portion, telemetry, force_authoring, - }: BuildAuraWorkerParams, -) -> impl sc_consensus_slots::SlotWorker>::Proof> + compatibility_mode, + }: BuildAuraWorkerParams>, +) -> impl sc_consensus_slots::SimpleSlotWorker< + B, + Proposer = PF::Proposer, + BlockImport = I, + SyncOracle = SO, + JustificationSyncLink = L, + Claim = P::Public, + AuxData = Vec>, +> where B: BlockT, - C: ProvideRuntimeApi + BlockOf + ProvideCache + AuxStore + HeaderBackend + Send + Sync, + C: ProvideRuntimeApi + BlockOf + AuxStore + HeaderBackend + Send + Sync, C::Api: AuraApi>, PF: Environment + Send + Sync + 'static, PF::Proposer: Proposer>, @@ -292,11 +334,12 @@ where telemetry, block_proposal_slot_portion, max_block_proposal_slot_portion, + compatibility_mode, _key_type: PhantomData::

, } } -struct AuraWorker { +struct AuraWorker { client: Arc, block_import: I, env: E, @@ -308,15 +351,16 @@ struct AuraWorker { block_proposal_slot_portion: SlotProportion, max_block_proposal_slot_portion: Option, telemetry: Option, + compatibility_mode: CompatibilityMode, _key_type: PhantomData

, } #[async_trait::async_trait] impl sc_consensus_slots::SimpleSlotWorker - for AuraWorker + for AuraWorker> where B: BlockT, - C: ProvideRuntimeApi + BlockOf + ProvideCache + HeaderBackend + Sync, + C: ProvideRuntimeApi + BlockOf + HeaderBackend + Sync, C::Api: AuraApi>, E: Environment + Send + Sync, E::Proposer: Proposer>, @@ -336,7 +380,7 @@ where Pin> + Send + 'static>>; type Proposer = E::Proposer; type Claim = P::Public; - type EpochData = Vec>; + type AuxData = Vec>; fn logging_target(&self) -> &'static str { "aura" @@ -346,15 +390,20 @@ where &mut self.block_import } - fn epoch_data( + fn aux_data( &self, header: &B::Header, _slot: Slot, - ) -> Result { - authorities(self.client.as_ref(), &BlockId::Hash(header.hash())) + ) -> Result { + authorities( + self.client.as_ref(), + header.hash(), + *header.number() + 1u32.into(), + &self.compatibility_mode, + ) } - fn authorities_len(&self, epoch_data: &Self::EpochData) -> Option { + fn authorities_len(&self, epoch_data: &Self::AuxData) -> Option { Some(epoch_data.len()) } @@ -362,7 +411,7 @@ where &self, _header: &B::Header, slot: Slot, - epoch_data: &Self::EpochData, + epoch_data: &Self::AuxData, ) -> Option { let expected_author = slot_author::

(slot, epoch_data); expected_author.and_then(|p| { @@ -377,66 +426,55 @@ where }) } - fn pre_digest_data( - &self, - slot: Slot, - _claim: &Self::Claim, - ) -> Vec> { - vec![ as CompatibleDigestItem>::aura_pre_digest(slot)] + fn pre_digest_data(&self, slot: Slot, _claim: &Self::Claim) -> Vec { + vec![>::aura_pre_digest(slot)] } - fn block_import_params( + async fn block_import_params( &self, - ) -> Box< - dyn Fn( - B::Header, - &B::Hash, - Vec, - StorageChanges, B>, - Self::Claim, - Self::EpochData, - ) -> Result< - sc_consensus::BlockImportParams>, - sp_consensus::Error, - > + Send - + 'static, + header: B::Header, + header_hash: &B::Hash, + body: Vec, + storage_changes: StorageChanges<>::Transaction, B>, + public: Self::Claim, + _epoch: Self::AuxData, + ) -> Result< + sc_consensus::BlockImportParams>::Transaction>, + sp_consensus::Error, > { - let keystore = self.keystore.clone(); - Box::new(move |header, header_hash, body, storage_changes, public, _epoch| { - // sign the pre-sealed hash of the block and then - // add it to a digest item. - let public_type_pair = public.to_public_crypto_pair(); - let public = public.to_raw_vec(); - let signature = SyncCryptoStore::sign_with( - &*keystore, - as AppKey>::ID, - &public_type_pair, - header_hash.as_ref(), + // sign the pre-sealed hash of the block and then + // add it to a digest item. + let public_type_pair = public.to_public_crypto_pair(); + let public = public.to_raw_vec(); + let signature = SyncCryptoStore::sign_with( + &*self.keystore, + as AppKey>::ID, + &public_type_pair, + header_hash.as_ref(), + ) + .map_err(|e| sp_consensus::Error::CannotSign(public.clone(), e.to_string()))? + .ok_or_else(|| { + sp_consensus::Error::CannotSign( + public.clone(), + "Could not find key in keystore.".into(), ) - .map_err(|e| sp_consensus::Error::CannotSign(public.clone(), e.to_string()))? - .ok_or_else(|| { - sp_consensus::Error::CannotSign( - public.clone(), - "Could not find key in keystore.".into(), - ) - })?; - let signature = signature - .clone() - .try_into() - .map_err(|_| sp_consensus::Error::InvalidSignature(signature, public))?; - - let signature_digest_item = - as CompatibleDigestItem>::aura_seal(signature); - - let mut import_block = BlockImportParams::new(BlockOrigin::Own, header); - import_block.post_digests.push(signature_digest_item); - import_block.body = Some(body); - import_block.state_action = - StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes(storage_changes)); - import_block.fork_choice = Some(ForkChoiceStrategy::LongestChain); - - Ok(import_block) - }) + })?; + let signature = signature + .clone() + .try_into() + .map_err(|_| sp_consensus::Error::InvalidSignature(signature, public))?; + + let signature_digest_item = + >::aura_seal(signature); + + let mut import_block = BlockImportParams::new(BlockOrigin::Own, header); + import_block.post_digests.push(signature_digest_item); + import_block.body = Some(body); + import_block.state_action = + StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes(storage_changes)); + import_block.fork_choice = Some(ForkChoiceStrategy::LongestChain); + + Ok(import_block) } fn force_authoring(&self) -> bool { @@ -467,11 +505,10 @@ where } fn proposer(&mut self, block: &B::Header) -> Self::CreateProposer { - Box::pin( - self.env - .init(block) - .map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e)).into()), - ) + self.env + .init(block) + .map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e))) + .boxed() } fn telemetry(&self) -> Option { @@ -497,34 +534,46 @@ fn aura_err(error: Error) -> Error { error } -#[derive(derive_more::Display, Debug)] -enum Error { - #[display(fmt = "Multiple Aura pre-runtime headers")] +/// Aura Errors +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Multiple Aura pre-runtime headers + #[error("Multiple Aura pre-runtime headers")] MultipleHeaders, - #[display(fmt = "No Aura pre-runtime digest found")] + /// No Aura pre-runtime digest found + #[error("No Aura pre-runtime digest found")] NoDigestFound, - #[display(fmt = "Header {:?} is unsealed", _0)] + /// Header is unsealed + #[error("Header {0:?} is unsealed")] HeaderUnsealed(B::Hash), - #[display(fmt = "Header {:?} has a bad seal", _0)] + /// Header has a bad seal + #[error("Header {0:?} has a bad seal")] HeaderBadSeal(B::Hash), - #[display(fmt = "Slot Author not found")] + /// Slot Author not found + #[error("Slot Author not found")] SlotAuthorNotFound, - #[display(fmt = "Bad signature on {:?}", _0)] + /// Bad signature + #[error("Bad signature on {0:?}")] BadSignature(B::Hash), + /// Client Error + #[error(transparent)] Client(sp_blockchain::Error), - #[display(fmt = "Unknown inherent error for identifier: {}", "String::from_utf8_lossy(_0)")] + /// Unknown inherent error for identifier + #[error("Unknown inherent error for identifier: {}", String::from_utf8_lossy(.0))] UnknownInherentError(sp_inherents::InherentIdentifier), - #[display(fmt = "Inherent error: {}", _0)] + /// Inherents Error + #[error("Inherent error: {0}")] Inherent(sp_inherents::Error), } -impl std::convert::From> for String { +impl From> for String { fn from(error: Error) -> String { error.to_string() } } -fn find_pre_digest(header: &B::Header) -> Result> { +/// Get pre-digests from the header +pub fn find_pre_digest(header: &B::Header) -> Result> { if header.number().is_zero() { return Ok(0.into()) } @@ -533,7 +582,7 @@ fn find_pre_digest(header: &B::Header) -> Result::as_aura_pre_digest(log), pre_digest.is_some()) { - (Some(_), true) => Err(aura_err(Error::MultipleHeaders))?, + (Some(_), true) => return Err(aura_err(Error::MultipleHeaders)), (None, _) => trace!(target: "aura", "Ignoring digest not meant for us"), (s, false) => pre_digest = s, } @@ -541,41 +590,66 @@ fn find_pre_digest(header: &B::Header) -> Result(client: &C, at: &BlockId) -> Result, ConsensusError> +fn authorities( + client: &C, + parent_hash: B::Hash, + context_block_number: NumberFor, + compatibility_mode: &CompatibilityMode>, +) -> Result, ConsensusError> where A: Codec + Debug, B: BlockT, - C: ProvideRuntimeApi + BlockOf + ProvideCache, + C: ProvideRuntimeApi, C::Api: AuraApi, { - client - .runtime_api() - .authorities(at) + let runtime_api = client.runtime_api(); + + match compatibility_mode { + CompatibilityMode::None => {}, + // Use `initialize_block` until we hit the block that should disable the mode. + CompatibilityMode::UseInitializeBlock { until } => + if *until > context_block_number { + runtime_api + .initialize_block( + &BlockId::Hash(parent_hash), + &B::Header::new( + context_block_number, + Default::default(), + Default::default(), + parent_hash, + Default::default(), + ), + ) + .map_err(|_| sp_consensus::Error::InvalidAuthoritiesSet)?; + }, + } + + runtime_api + .authorities(&BlockId::Hash(parent_hash)) .ok() - .ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet.into()) + .ok_or(sp_consensus::Error::InvalidAuthoritiesSet) } #[cfg(test)] mod tests { use super::*; - use futures::executor; use parking_lot::Mutex; use sc_block_builder::BlockBuilderProvider; use sc_client_api::BlockchainEvents; use sc_consensus::BoxJustificationImport; use sc_consensus_slots::{BackoffAuthoringOnFinalizedHeadLagging, SimpleSlotWorker}; use sc_keystore::LocalKeystore; - use sc_network::config::ProtocolConfig; use sc_network_test::{Block as TestBlock, *}; use sp_application_crypto::key_types::AURA; - use sp_consensus::{ - AlwaysCanAuthor, DisableProofRecording, NoNetwork as DummyOracle, Proposal, SlotData, - }; + use sp_consensus::{DisableProofRecording, NoNetwork as DummyOracle, Proposal}; use sp_consensus_aura::sr25519::AuthorityPair; use sp_inherents::InherentData; use sp_keyring::sr25519::Keyring; - use sp_runtime::traits::{Block as BlockT, DigestFor, Header as _}; - use sp_timestamp::InherentDataProvider as TimestampInherentDataProvider; + use sp_runtime::{ + traits::{Block as BlockT, Header as _}, + Digest, + }; + use sp_timestamp::Timestamp; use std::{ task::Poll, time::{Duration, Instant}, @@ -584,6 +658,9 @@ mod tests { runtime::{Header, H256}, TestClient, }; + use tokio::runtime::{Handle, Runtime}; + + const SLOT_DURATION_MS: u64 = 1000; type Error = sp_blockchain::Error; @@ -611,7 +688,7 @@ mod tests { fn propose( self, _: InherentData, - digests: DigestFor, + digests: Digest, _: Duration, _: Option, ) -> Self::Proposal { @@ -625,65 +702,57 @@ mod tests { } } - const SLOT_DURATION: u64 = 1000; - type AuraVerifier = import_queue::AuraVerifier< PeersFullClient, AuthorityPair, - AlwaysCanAuthor, Box< dyn CreateInherentDataProviders< TestBlock, (), - InherentDataProviders = (TimestampInherentDataProvider, InherentDataProvider), + InherentDataProviders = (InherentDataProvider,), >, >, + u64, >; type AuraPeer = Peer<(), PeersClient>; pub struct AuraTestNet { + rt_handle: Handle, peers: Vec, } + impl WithRuntime for AuraTestNet { + fn with_runtime(rt_handle: Handle) -> Self { + AuraTestNet { rt_handle, peers: Vec::new() } + } + fn rt_handle(&self) -> &Handle { + &self.rt_handle + } + } + impl TestNetFactory for AuraTestNet { type Verifier = AuraVerifier; type PeerData = (); type BlockImport = PeersClient; - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - AuraTestNet { peers: Vec::new() } - } + fn make_verifier(&self, client: PeersClient, _peer_data: &()) -> Self::Verifier { + let client = client.as_client(); + let slot_duration = slot_duration(&*client).expect("slot duration available"); - fn make_verifier( - &self, - client: PeersClient, - _cfg: &ProtocolConfig, - _peer_data: &(), - ) -> Self::Verifier { - match client { - PeersClient::Full(client, _) => { - let slot_duration = slot_duration(&*client).expect("slot duration available"); - - assert_eq!(slot_duration.slot_duration().as_millis() as u64, SLOT_DURATION); - import_queue::AuraVerifier::new( - client, - Box::new(|_, _| async { - let timestamp = TimestampInherentDataProvider::from_system_time(); - let slot = InherentDataProvider::from_timestamp_and_duration( - *timestamp, - Duration::from_secs(6), - ); - - Ok((timestamp, slot)) - }), - AlwaysCanAuthor, - CheckForEquivocation::Yes, - None, - ) - }, - PeersClient::Light(_, _) => unreachable!("No (yet) tests for light client + Aura"), - } + assert_eq!(slot_duration.as_millis() as u64, SLOT_DURATION_MS); + import_queue::AuraVerifier::new( + client, + Box::new(|_, _| async { + let slot = InherentDataProvider::from_timestamp_and_slot_duration( + Timestamp::current(), + SlotDuration::from_millis(SLOT_DURATION_MS), + ); + Ok((slot,)) + }), + CheckForEquivocation::Yes, + None, + CompatibilityMode::None, + ) } fn make_block_import( @@ -712,7 +781,8 @@ mod tests { #[test] fn authoring_blocks() { sp_tracing::try_init_simple(); - let net = AuraTestNet::new(3); + let runtime = Runtime::new().unwrap(); + let net = AuraTestNet::new(runtime.handle().clone(), 3); let peers = &[(0, Keyring::Alice), (1, Keyring::Bob), (2, Keyring::Charlie)]; @@ -724,7 +794,7 @@ mod tests { for (peer_id, key) in peers { let mut net = net.lock(); let peer = net.peer(*peer_id); - let client = peer.client().as_full().expect("full clients are created").clone(); + let client = peer.client().as_client(); let select_chain = peer.select_chain().expect("full client has a select chain"); let keystore_path = tempfile::tempdir().expect("Creates keystore path"); let keystore = Arc::new( @@ -748,7 +818,7 @@ mod tests { let slot_duration = slot_duration(&*client).expect("slot duration available"); aura_futures.push( - start_aura::(StartAuraParams { + start_aura::(StartAuraParams { slot_duration, block_import: client.clone(), select_chain, @@ -757,29 +827,28 @@ mod tests { sync_oracle: DummyOracle, justification_sync_link: (), create_inherent_data_providers: |_, _| async { - let timestamp = TimestampInherentDataProvider::from_system_time(); - let slot = InherentDataProvider::from_timestamp_and_duration( - *timestamp, - Duration::from_secs(6), + let slot = InherentDataProvider::from_timestamp_and_slot_duration( + Timestamp::current(), + SlotDuration::from_millis(SLOT_DURATION_MS), ); - Ok((timestamp, slot)) + Ok((slot,)) }, force_authoring: false, backoff_authoring_blocks: Some( BackoffAuthoringOnFinalizedHeadLagging::default(), ), keystore, - can_author_with: sp_consensus::AlwaysCanAuthor, block_proposal_slot_portion: SlotProportion::new(0.5), max_block_proposal_slot_portion: None, telemetry: None, + compatibility_mode: CompatibilityMode::None, }) .expect("Starts aura"), ); } - executor::block_on(future::select( + runtime.block_on(future::select( future::poll_fn(move |cx| { net.lock().poll(cx); Poll::<()>::Pending @@ -794,7 +863,8 @@ mod tests { assert_eq!(client.chain_info().best_number, 0); assert_eq!( - authorities(&client, &BlockId::Number(0)).unwrap(), + authorities(&client, client.chain_info().best_hash, 1, &CompatibilityMode::None) + .unwrap(), vec![ Keyring::Alice.public().into(), Keyring::Bob.public().into(), @@ -805,7 +875,8 @@ mod tests { #[test] fn current_node_authority_should_claim_slot() { - let net = AuraTestNet::new(4); + let runtime = Runtime::new().unwrap(); + let net = AuraTestNet::new(runtime.handle().clone(), 4); let mut authorities = vec![ Keyring::Alice.public().into(), @@ -823,7 +894,7 @@ mod tests { let mut net = net.lock(); let peer = net.peer(3); - let client = peer.client().as_full().expect("full clients are created").clone(); + let client = peer.client().as_client(); let environ = DummyFactory(client.clone()); let worker = AuraWorker { @@ -831,7 +902,7 @@ mod tests { block_import: client, env: environ, keystore: keystore.into(), - sync_oracle: DummyOracle.clone(), + sync_oracle: DummyOracle, justification_sync_link: (), force_authoring: false, backoff_authoring_blocks: Some(BackoffAuthoringOnFinalizedHeadLagging::default()), @@ -839,6 +910,7 @@ mod tests { _key_type: PhantomData::, block_proposal_slot_portion: SlotProportion::new(0.5), max_block_proposal_slot_portion: None, + compatibility_mode: Default::default(), }; let head = Header::new( @@ -848,19 +920,20 @@ mod tests { Default::default(), Default::default(), ); - assert!(executor::block_on(worker.claim_slot(&head, 0.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 1.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 2.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 3.into(), &authorities)).is_some()); - assert!(executor::block_on(worker.claim_slot(&head, 4.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 5.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 6.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 7.into(), &authorities)).is_some()); + assert!(runtime.block_on(worker.claim_slot(&head, 0.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 1.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 2.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 3.into(), &authorities)).is_some()); + assert!(runtime.block_on(worker.claim_slot(&head, 4.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 5.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 6.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 7.into(), &authorities)).is_some()); } #[test] fn on_slot_returns_correct_block() { - let net = AuraTestNet::new(4); + let runtime = Runtime::new().unwrap(); + let net = AuraTestNet::new(runtime.handle().clone(), 4); let keystore_path = tempfile::tempdir().expect("Creates keystore path"); let keystore = LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore."); @@ -875,7 +948,7 @@ mod tests { let mut net = net.lock(); let peer = net.peer(3); - let client = peer.client().as_full().expect("full clients are created").clone(); + let client = peer.client().as_client(); let environ = DummyFactory(client.clone()); let mut worker = AuraWorker { @@ -883,7 +956,7 @@ mod tests { block_import: client.clone(), env: environ, keystore: keystore.into(), - sync_oracle: DummyOracle.clone(), + sync_oracle: DummyOracle, justification_sync_link: (), force_authoring: false, backoff_authoring_blocks: Option::<()>::None, @@ -891,20 +964,21 @@ mod tests { _key_type: PhantomData::, block_proposal_slot_portion: SlotProportion::new(0.5), max_block_proposal_slot_portion: None, + compatibility_mode: Default::default(), }; let head = client.header(&BlockId::Number(0)).unwrap().unwrap(); - let res = executor::block_on(worker.on_slot(SlotInfo { - slot: 0.into(), - timestamp: 0.into(), - ends_at: Instant::now() + Duration::from_secs(100), - inherent_data: InherentData::new(), - duration: Duration::from_millis(1000), - chain_head: head, - block_size_limit: None, - })) - .unwrap(); + let res = runtime + .block_on(worker.on_slot(SlotInfo { + slot: 0.into(), + ends_at: Instant::now() + Duration::from_secs(100), + create_inherent_data: Box::new(()), + duration: Duration::from_millis(1000), + chain_head: head, + block_size_limit: None, + })) + .unwrap(); // The returned block should be imported and we should be able to get its header by now. assert!(client.header(&BlockId::Hash(res.block.hash())).unwrap().is_some()); diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index 65dfc57133206..c39802ba237ae 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -3,9 +3,9 @@ name = "sc-consensus-babe" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "BABE consensus algorithm for substrate" -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-consensus-babe" readme = "README.md" @@ -14,51 +14,48 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", features = [ - "derive", -] } -sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-application-crypto = { version = "4.0.0-dev", path = "../../../primitives/application-crypto" } -sp-keystore = { version = "0.10.0-dev", path = "../../../primitives/keystore" } -num-bigint = "0.2.3" -num-rational = "0.2.2" +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +futures = "0.3.21" +log = "0.4.17" +merlin = "2.0" +num-bigint = "0.4.3" +num-rational = "0.4.1" num-traits = "0.2.8" -serde = { version = "1.0.126", features = ["derive"] } -sp-version = { version = "4.0.0-dev", path = "../../../primitives/version" } -sp-io = { version = "4.0.0-dev", path = "../../../primitives/io" } -sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } -sc-keystore = { version = "4.0.0-dev", path = "../../keystore" } +parking_lot = "0.12.1" +schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } +serde = { version = "1.0.136", features = ["derive"] } +thiserror = "1.0" +fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../epochs" } +sc-consensus-slots = { version = "0.10.0-dev", path = "../slots" } +sc-keystore = { version = "4.0.0-dev", path = "../../keystore" } +sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-application-crypto = { version = "7.0.0", path = "../../../primitives/application-crypto" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } sp-consensus-vrf = { version = "0.10.0-dev", path = "../../../primitives/consensus/vrf" } -sc-consensus-slots = { version = "0.10.0-dev", path = "../slots" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.9.0" } -futures = "0.3.9" -parking_lot = "0.11.1" -log = "0.4.8" -schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } -rand = "0.7.2" -merlin = "2.0" -derive_more = "0.99.2" -retain_mut = "0.1.3" -async-trait = "0.1.50" +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-version = { version = "5.0.0", path = "../../../primitives/version" } [dev-dependencies] -sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -sp-tracing = { version = "4.0.0-dev", path = "../../../primitives/tracing" } +rand_chacha = "0.2.2" +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } sc-network = { version = "0.10.0-dev", path = "../../network" } sc-network-test = { version = "0.8.0", path = "../../network/test" } +sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } -rand_chacha = "0.2.2" -tempfile = "3.1.0" +tokio = "1.22.0" diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index 8d5625705a48c..4f5aaf85494b9 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -3,9 +3,9 @@ name = "sc-consensus-babe-rpc" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "RPC extensions for the BABE consensus algorithm" -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,28 +13,27 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +futures = "0.3.21" +serde = { version = "1.0.136", features = ["derive"] } +thiserror = "1.0" sc-consensus-babe = { version = "0.10.0-dev", path = "../" } -sc-rpc-api = { version = "0.10.0-dev", path = "../../../rpc-api" } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -sp-consensus-babe = { version = "0.10.0-dev", path = "../../../../primitives/consensus/babe" } -serde = { version = "1.0.126", features=["derive"] } -sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } -sp-runtime = { version = "4.0.0-dev", path = "../../../../primitives/runtime" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../../epochs" } -futures = "0.3.16" -derive_more = "0.99.2" +sc-rpc-api = { version = "0.10.0-dev", path = "../../../rpc-api" } sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } +sp-application-crypto = { version = "7.0.0", path = "../../../../primitives/application-crypto" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../../primitives/consensus/common" } -sp-core = { version = "4.0.0-dev", path = "../../../../primitives/core" } -sp-application-crypto = { version = "4.0.0-dev", path = "../../../../primitives/application-crypto" } -sp-keystore = { version = "0.10.0-dev", path = "../../../../primitives/keystore" } +sp-consensus-babe = { version = "0.10.0-dev", path = "../../../../primitives/consensus/babe" } +sp-core = { version = "7.0.0", path = "../../../../primitives/core" } +sp-keystore = { version = "0.13.0", path = "../../../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] +serde_json = "1.0.85" +tempfile = "3.1.0" +tokio = "1.22.0" sc-consensus = { version = "0.10.0-dev", path = "../../../consensus/common" } -serde_json = "1.0.68" -sp-keyring = { version = "4.0.0-dev", path = "../../../../primitives/keyring" } sc-keystore = { version = "4.0.0-dev", path = "../../../keystore" } +sp-keyring = { version = "7.0.0", path = "../../../../primitives/keyring" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } -tempfile = "3.1.0" diff --git a/client/consensus/babe/rpc/src/lib.rs b/client/consensus/babe/rpc/src/lib.rs index 285cfe543cee8..288f852a5c989 100644 --- a/client/consensus/babe/rpc/src/lib.rs +++ b/client/consensus/babe/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,10 +18,14 @@ //! RPC api for babe. -use futures::{FutureExt, TryFutureExt}; -use jsonrpc_core::Error as RpcError; -use jsonrpc_derive::rpc; -use sc_consensus_babe::{authorship, Config, Epoch}; +use futures::TryFutureExt; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::{error::CallError, ErrorObject}, +}; + +use sc_consensus_babe::{authorship, Epoch}; use sc_consensus_epochs::{descendent_query, Epoch as EpochT, SharedEpochChanges}; use sc_rpc_api::DenyUnsafe; use serde::{Deserialize, Serialize}; @@ -29,25 +33,25 @@ use sp_api::{BlockId, ProvideRuntimeApi}; use sp_application_crypto::AppKey; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use sp_consensus::{Error as ConsensusError, SelectChain}; -use sp_consensus_babe::{digests::PreDigest, AuthorityId, BabeApi as BabeRuntimeApi}; -use sp_core::crypto::Public; +use sp_consensus_babe::{ + digests::PreDigest, AuthorityId, BabeApi as BabeRuntimeApi, BabeConfiguration, +}; +use sp_core::crypto::ByteArray; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::traits::{Block as BlockT, Header as _}; use std::{collections::HashMap, sync::Arc}; -type FutureResult = jsonrpc_core::BoxFuture>; - /// Provides rpc methods for interacting with Babe. -#[rpc] +#[rpc(client, server)] pub trait BabeApi { /// Returns data about which slots (primary or secondary) can be claimed in the current epoch /// with the keys in the keystore. - #[rpc(name = "babe_epochAuthorship")] - fn epoch_authorship(&self) -> FutureResult>; + #[method(name = "babe_epochAuthorship")] + async fn epoch_authorship(&self) -> RpcResult>; } -/// Implements the BabeRpc trait for interacting with Babe. -pub struct BabeRpcHandler { +/// Provides RPC methods for interacting with Babe. +pub struct Babe { /// shared reference to the client. client: Arc, /// shared reference to EpochChanges @@ -55,20 +59,20 @@ pub struct BabeRpcHandler { /// shared reference to the Keystore keystore: SyncCryptoStorePtr, /// config (actually holds the slot duration) - babe_config: Config, + babe_config: BabeConfiguration, /// The SelectChain strategy select_chain: SC, /// Whether to deny unsafe calls deny_unsafe: DenyUnsafe, } -impl BabeRpcHandler { - /// Creates a new instance of the BabeRpc handler. +impl Babe { + /// Creates a new instance of the Babe Rpc handler. pub fn new( client: Arc, shared_epoch_changes: SharedEpochChanges, keystore: SyncCryptoStorePtr, - babe_config: Config, + babe_config: BabeConfiguration, select_chain: SC, deny_unsafe: DenyUnsafe, ) -> Self { @@ -76,7 +80,8 @@ impl BabeRpcHandler { } } -impl BabeApi for BabeRpcHandler +#[async_trait] +impl BabeApiServer for Babe where B: BlockT, C: ProvideRuntimeApi @@ -86,71 +91,63 @@ where C::Api: BabeRuntimeApi, SC: SelectChain + Clone + 'static, { - fn epoch_authorship(&self) -> FutureResult> { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return async move { Err(err.into()) }.boxed() - } - - let (babe_config, keystore, shared_epoch, client, select_chain) = ( - self.babe_config.clone(), - self.keystore.clone(), - self.shared_epoch_changes.clone(), - self.client.clone(), - self.select_chain.clone(), - ); - - async move { - let header = select_chain.best_chain().map_err(Error::Consensus).await?; - let epoch_start = client - .runtime_api() - .current_epoch_start(&BlockId::Hash(header.hash())) - .map_err(|err| Error::StringError(format!("{:?}", err)))?; - let epoch = - epoch_data(&shared_epoch, &client, &babe_config, *epoch_start, &select_chain) - .await?; - let (epoch_start, epoch_end) = (epoch.start_slot(), epoch.end_slot()); - - let mut claims: HashMap = HashMap::new(); - - let keys = { - epoch - .authorities - .iter() - .enumerate() - .filter_map(|(i, a)| { - if SyncCryptoStore::has_keys( - &*keystore, - &[(a.0.to_raw_vec(), AuthorityId::ID)], - ) { - Some((a.0.clone(), i)) - } else { - None - } - }) - .collect::>() - }; - - for slot in *epoch_start..*epoch_end { - if let Some((claim, key)) = - authorship::claim_slot_using_keys(slot.into(), &epoch, &keystore, &keys) - { - match claim { - PreDigest::Primary { .. } => { - claims.entry(key).or_default().primary.push(slot); - }, - PreDigest::SecondaryPlain { .. } => { - claims.entry(key).or_default().secondary.push(slot); - }, - PreDigest::SecondaryVRF { .. } => { - claims.entry(key).or_default().secondary_vrf.push(slot.into()); - }, - }; - } + async fn epoch_authorship(&self) -> RpcResult> { + self.deny_unsafe.check_if_safe()?; + let header = self.select_chain.best_chain().map_err(Error::Consensus).await?; + let epoch_start = self + .client + .runtime_api() + .current_epoch_start(&BlockId::Hash(header.hash())) + .map_err(|err| Error::StringError(format!("{:?}", err)))?; + + let epoch = epoch_data( + &self.shared_epoch_changes, + &self.client, + &self.babe_config, + *epoch_start, + &self.select_chain, + ) + .await?; + let (epoch_start, epoch_end) = (epoch.start_slot(), epoch.end_slot()); + let mut claims: HashMap = HashMap::new(); + + let keys = { + epoch + .authorities + .iter() + .enumerate() + .filter_map(|(i, a)| { + if SyncCryptoStore::has_keys( + &*self.keystore, + &[(a.0.to_raw_vec(), AuthorityId::ID)], + ) { + Some((a.0.clone(), i)) + } else { + None + } + }) + .collect::>() + }; + + for slot in *epoch_start..*epoch_end { + if let Some((claim, key)) = + authorship::claim_slot_using_keys(slot.into(), &epoch, &self.keystore, &keys) + { + match claim { + PreDigest::Primary { .. } => { + claims.entry(key).or_default().primary.push(slot); + }, + PreDigest::SecondaryPlain { .. } => { + claims.entry(key).or_default().secondary.push(slot); + }, + PreDigest::SecondaryVRF { .. } => { + claims.entry(key).or_default().secondary_vrf.push(slot.into()); + }, + }; } - - Ok(claims) } - .boxed() + + Ok(claims) } } @@ -166,21 +163,23 @@ pub struct EpochAuthorship { } /// Errors encountered by the RPC -#[derive(Debug, derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// Consensus error - Consensus(ConsensusError), + #[error(transparent)] + Consensus(#[from] ConsensusError), /// Errors that can be formatted as a String + #[error("{0}")] StringError(String), } -impl From for jsonrpc_core::Error { +impl From for JsonRpseeError { fn from(error: Error) -> Self { - jsonrpc_core::Error { - message: format!("{}", error), - code: jsonrpc_core::ErrorCode::ServerError(1234), - data: None, - } + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + 1234, + error.to_string(), + None::<()>, + ))) } } @@ -188,7 +187,7 @@ impl From for jsonrpc_core::Error { async fn epoch_data( epoch_changes: &SharedEpochChanges, client: &Arc, - babe_config: &Config, + babe_config: &BabeConfiguration, slot: u64, select_chain: &SC, ) -> Result @@ -203,11 +202,11 @@ where .epoch_data_for_child_of( descendent_query(&**client), &parent.hash(), - parent.number().clone(), + *parent.number(), slot.into(), - |slot| Epoch::genesis(&babe_config, slot), + |slot| Epoch::genesis(babe_config, slot), ) - .map_err(|e| Error::Consensus(ConsensusError::ChainLookup(format!("{:?}", e))))? + .map_err(|e| Error::Consensus(ConsensusError::ChainLookup(e.to_string())))? .ok_or(Error::Consensus(ConsensusError::InvalidAuthoritiesSet)) } @@ -224,8 +223,7 @@ mod tests { TestClientBuilderExt, }; - use jsonrpc_core::IoHandler; - use sc_consensus_babe::{block_import, AuthorityPair, Config}; + use sc_consensus_babe::{block_import, AuthorityPair}; use std::sync::Arc; /// creates keystore backed by a temp file @@ -241,53 +239,43 @@ mod tests { (keystore, keystore_path) } - fn test_babe_rpc_handler( + fn test_babe_rpc_module( deny_unsafe: DenyUnsafe, - ) -> BabeRpcHandler> { + ) -> Babe> { let builder = TestClientBuilder::new(); let (client, longest_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); - let config = Config::get_or_compute(&*client).expect("config available"); + let config = sc_consensus_babe::configuration(&*client).expect("config available"); let (_, link) = block_import(config.clone(), client.clone(), client.clone()) .expect("can initialize block-import"); let epoch_changes = link.epoch_changes().clone(); let keystore = create_temp_keystore::(Sr25519Keyring::Alice).0; - BabeRpcHandler::new( - client.clone(), - epoch_changes, - keystore, - config, - longest_chain, - deny_unsafe, - ) + Babe::new(client.clone(), epoch_changes, keystore, config, longest_chain, deny_unsafe) } - #[test] - fn epoch_authorship_works() { - let handler = test_babe_rpc_handler(DenyUnsafe::No); - let mut io = IoHandler::new(); + #[tokio::test] + async fn epoch_authorship_works() { + let babe_rpc = test_babe_rpc_module(DenyUnsafe::No); + let api = babe_rpc.into_rpc(); - io.extend_with(BabeApi::to_delegate(handler)); let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#; - let response = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#; + let (response, _) = api.raw_json_request(request).await.unwrap(); + let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#; - assert_eq!(Some(response.into()), io.handle_request_sync(request)); + assert_eq!(&response.result, expected); } - #[test] - fn epoch_authorship_is_unsafe() { - let handler = test_babe_rpc_handler(DenyUnsafe::Yes); - let mut io = IoHandler::new(); - - io.extend_with(BabeApi::to_delegate(handler)); - let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#; + #[tokio::test] + async fn epoch_authorship_is_unsafe() { + let babe_rpc = test_babe_rpc_module(DenyUnsafe::Yes); + let api = babe_rpc.into_rpc(); - let response = io.handle_request_sync(request).unwrap(); - let mut response: serde_json::Value = serde_json::from_str(&response).unwrap(); - let error: RpcError = serde_json::from_value(response["error"].take()).unwrap(); + let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params":[],"id":1}"#; + let (response, _) = api.raw_json_request(request).await.unwrap(); + let expected = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"#; - assert_eq!(error, RpcError::method_not_found()) + assert_eq!(&response.result, expected); } } diff --git a/client/consensus/babe/src/authorship.rs b/client/consensus/babe/src/authorship.rs index 609f96c83c194..b39153faa6d1a 100644 --- a/client/consensus/babe/src/authorship.rs +++ b/client/consensus/babe/src/authorship.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -27,7 +27,7 @@ use sp_consensus_babe::{ make_transcript, make_transcript_data, AuthorityId, BabeAuthorityWeight, Slot, BABE_VRF_PREFIX, }; use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; -use sp_core::{blake2_256, crypto::Public, U256}; +use sp_core::{blake2_256, crypto::ByteArray, U256}; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; /// Calculates the primary selection threshold for a given authority, taking @@ -41,6 +41,14 @@ pub(super) fn calculate_primary_threshold( use num_rational::BigRational; use num_traits::{cast::ToPrimitive, identities::One}; + // Prevent div by zero and out of bounds access. + // While Babe's pallet implementation that ships with FRAME performs a sanity check over + // configuration parameters, this is not sufficient to guarantee that `c.1` is non-zero + // (i.e. third party implementations are possible). + if c.1 == 0 || authority_index >= authorities.len() { + return 0 + } + let c = c.0 as f64 / c.1 as f64; let theta = authorities[authority_index].1 as f64 / @@ -77,7 +85,7 @@ pub(super) fn calculate_primary_threshold( qed.", ); - ((BigUint::one() << 128) * numer / denom).to_u128().expect( + ((BigUint::one() << 128usize) * numer / denom).to_u128().expect( "returns None if the underlying value cannot be represented with 128 bits; \ we start with 2^128 which is one more than can be represented with 128 bits; \ we multiple by p which is defined in [0, 1); \ @@ -202,15 +210,15 @@ pub fn claim_slot_using_keys( keystore: &SyncCryptoStorePtr, keys: &[(AuthorityId, usize)], ) -> Option<(PreDigest, AuthorityId)> { - claim_primary_slot(slot, epoch, epoch.config.c, keystore, &keys).or_else(|| { + claim_primary_slot(slot, epoch, epoch.config.c, keystore, keys).or_else(|| { if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() || epoch.config.allowed_slots.is_secondary_vrf_slots_allowed() { claim_secondary_slot( slot, - &epoch, + epoch, keys, - &keystore, + keystore, epoch.config.allowed_slots.is_secondary_vrf_slots_allowed(), ) } else { @@ -235,12 +243,6 @@ fn claim_primary_slot( for (authority_id, authority_index) in keys { let transcript = make_transcript(randomness, slot, *epoch_index); let transcript_data = make_transcript_data(randomness, slot, *epoch_index); - // Compute the threshold we will use. - // - // We already checked that authorities contains `key.public()`, so it can't - // be empty. Therefore, this division in `calculate_threshold` is safe. - let threshold = calculate_primary_threshold(c, authorities, *authority_index); - let result = SyncCryptoStore::sr25519_vrf_sign( &**keystore, AuthorityId::ID, @@ -253,6 +255,8 @@ fn claim_primary_slot( Ok(inout) => inout, Err(_) => continue, }; + + let threshold = calculate_primary_threshold(c, authorities, *authority_index); if check_primary_threshold(&inout, threshold) { let pre_digest = PreDigest::Primary(PrimaryPreDigest { slot, @@ -306,7 +310,7 @@ mod tests { assert!(claim_slot(10.into(), &epoch, &keystore).is_none()); - epoch.authorities.push((valid_public_key.clone().into(), 10)); + epoch.authorities.push((valid_public_key.into(), 10)); assert_eq!(claim_slot(10.into(), &epoch, &keystore).unwrap().1, valid_public_key.into()); } } diff --git a/client/consensus/babe/src/aux_schema.rs b/client/consensus/babe/src/aux_schema.rs index b18220c3e360a..fef84bda86974 100644 --- a/client/consensus/babe/src/aux_schema.rs +++ b/client/consensus/babe/src/aux_schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,14 +23,17 @@ use log::info; use crate::{migration::EpochV0, Epoch}; use sc_client_api::backend::AuxStore; -use sc_consensus_epochs::{migration::EpochChangesForV0, EpochChangesFor, SharedEpochChanges}; +use sc_consensus_epochs::{ + migration::{EpochChangesV0For, EpochChangesV1For}, + EpochChangesFor, SharedEpochChanges, +}; use sp_blockchain::{Error as ClientError, Result as ClientResult}; -use sp_consensus_babe::{BabeBlockWeight, BabeGenesisConfiguration}; +use sp_consensus_babe::{BabeBlockWeight, BabeConfiguration}; use sp_runtime::traits::Block as BlockT; const BABE_EPOCH_CHANGES_VERSION: &[u8] = b"babe_epoch_changes_version"; const BABE_EPOCH_CHANGES_KEY: &[u8] = b"babe_epoch_changes"; -const BABE_EPOCH_CHANGES_CURRENT_VERSION: u32 = 2; +const BABE_EPOCH_CHANGES_CURRENT_VERSION: u32 = 3; /// The aux storage key used to store the block weight of the given block hash. pub fn block_weight_key(block_hash: H) -> Vec { @@ -54,17 +57,22 @@ where /// Load or initialize persistent epoch change data from backend. pub fn load_epoch_changes( backend: &B, - config: &BabeGenesisConfiguration, + config: &BabeConfiguration, ) -> ClientResult> { let version = load_decode::<_, u32>(backend, BABE_EPOCH_CHANGES_VERSION)?; let maybe_epoch_changes = match version { None => - load_decode::<_, EpochChangesForV0>(backend, BABE_EPOCH_CHANGES_KEY)? + load_decode::<_, EpochChangesV0For>(backend, BABE_EPOCH_CHANGES_KEY)? .map(|v0| v0.migrate().map(|_, _, epoch| epoch.migrate(config))), Some(1) => - load_decode::<_, EpochChangesFor>(backend, BABE_EPOCH_CHANGES_KEY)? - .map(|v1| v1.map(|_, _, epoch| epoch.migrate(config))), + load_decode::<_, EpochChangesV1For>(backend, BABE_EPOCH_CHANGES_KEY)? + .map(|v1| v1.migrate().map(|_, _, epoch| epoch.migrate(config))), + Some(2) => { + // v2 still uses `EpochChanges` v1 format but with a different `Epoch` type. + load_decode::<_, EpochChangesV1For>(backend, BABE_EPOCH_CHANGES_KEY)? + .map(|v2| v2.migrate()) + }, Some(BABE_EPOCH_CHANGES_CURRENT_VERSION) => load_decode::<_, EpochChangesFor>(backend, BABE_EPOCH_CHANGES_KEY)?, Some(other) => @@ -135,7 +143,7 @@ mod test { use sc_consensus_epochs::{EpochHeader, PersistedEpoch, PersistedEpochHeader}; use sc_network_test::Block as TestBlock; use sp_consensus::Error as ConsensusError; - use sp_consensus_babe::{AllowedSlots, BabeGenesisConfiguration}; + use sp_consensus_babe::AllowedSlots; use sp_core::H256; use sp_runtime::traits::NumberFor; use substrate_test_runtime_client; @@ -164,7 +172,7 @@ mod test { .insert_aux( &[( BABE_EPOCH_CHANGES_KEY, - &EpochChangesForV0::::from_raw(v0_tree).encode()[..], + &EpochChangesV0For::::from_raw(v0_tree).encode()[..], )], &[], ) @@ -174,11 +182,11 @@ mod test { let epoch_changes = load_epoch_changes::( &client, - &BabeGenesisConfiguration { + &BabeConfiguration { slot_duration: 10, epoch_length: 4, c: (3, 10), - genesis_authorities: Vec::new(), + authorities: Vec::new(), randomness: Default::default(), allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots, }, @@ -202,6 +210,6 @@ mod test { client.insert_aux(values, &[]).unwrap(); }); - assert_eq!(load_decode::<_, u32>(&client, BABE_EPOCH_CHANGES_VERSION).unwrap(), Some(2)); + assert_eq!(load_decode::<_, u32>(&client, BABE_EPOCH_CHANGES_VERSION).unwrap(), Some(3)); } } diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index a0b6bde025b3f..84b6893648f49 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -67,7 +67,12 @@ #![warn(missing_docs)] use std::{ - borrow::Cow, collections::HashMap, convert::TryInto, pin::Pin, sync::Arc, time::Duration, u64, + collections::{HashMap, HashSet}, + future::Future, + pin::Pin, + sync::Arc, + task::{Context, Poll}, + time::Duration, }; use codec::{Decode, Encode}; @@ -81,10 +86,12 @@ use futures::{ use log::{debug, info, log, trace, warn}; use parking_lot::Mutex; use prometheus_endpoint::Registry; -use retain_mut::RetainMut; use schnorrkel::SignatureError; -use sc_client_api::{backend::AuxStore, BlockchainEvents, ProvideUncles, UsageProvider}; +use sc_client_api::{ + backend::AuxStore, AuxDataOperations, Backend as BackendT, BlockchainEvents, + FinalityNotification, PreCommitActions, ProvideUncles, UsageProvider, +}; use sc_consensus::{ block_import::{ BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, @@ -100,24 +107,25 @@ use sc_consensus_slots::{ SlotInfo, StorageChanges, }; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE}; -use sp_api::{ApiExt, NumberFor, ProvideRuntimeApi}; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_application_crypto::AppKey; use sp_block_builder::BlockBuilder as BlockBuilderApi; use sp_blockchain::{ - Error as ClientError, HeaderBackend, HeaderMetadata, ProvideCache, Result as ClientResult, + Backend as _, Error as ClientError, ForkBackend, HeaderBackend, HeaderMetadata, + Result as ClientResult, }; use sp_consensus::{ - BlockOrigin, CacheKeyId, CanAuthorWith, Environment, Error as ConsensusError, Proposer, - SelectChain, SlotData, + BlockOrigin, CacheKeyId, Environment, Error as ConsensusError, Proposer, SelectChain, }; use sp_consensus_babe::inherents::BabeInherentData; use sp_consensus_slots::Slot; -use sp_core::{crypto::Public, ExecutionContext}; +use sp_core::{crypto::ByteArray, ExecutionContext}; use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider}; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ generic::{BlockId, OpaqueDigestItemId}, - traits::{Block as BlockT, DigestItemFor, Header, Zero}, + traits::{Block as BlockT, Header, NumberFor, SaturatedConversion, Zero}, + DigestItem, }; pub use sc_consensus_slots::SlotProportion; @@ -128,8 +136,7 @@ pub use sp_consensus_babe::{ PrimaryPreDigest, SecondaryPlainPreDigest, }, AuthorityId, AuthorityPair, AuthoritySignature, BabeApi, BabeAuthorityWeight, BabeBlockWeight, - BabeEpochConfiguration, BabeGenesisConfiguration, ConsensusLog, BABE_ENGINE_ID, - VRF_OUTPUT_LENGTH, + BabeConfiguration, BabeEpochConfiguration, ConsensusLog, BABE_ENGINE_ID, VRF_OUTPUT_LENGTH, }; pub use aux_schema::load_block_weight as block_weight; @@ -202,12 +209,12 @@ impl From for Epoch { impl Epoch { /// Create the genesis epoch (epoch #0). This is defined to start at the slot of /// the first block, so that has to be provided. - pub fn genesis(genesis_config: &BabeGenesisConfiguration, slot: Slot) -> Epoch { + pub fn genesis(genesis_config: &BabeConfiguration, slot: Slot) -> Epoch { Epoch { epoch_index: 0, start_slot: slot, duration: genesis_config.epoch_length, - authorities: genesis_config.genesis_authorities.clone(), + authorities: genesis_config.authorities.clone(), randomness: genesis_config.randomness, config: BabeEpochConfiguration { c: genesis_config.c, @@ -218,99 +225,98 @@ impl Epoch { } /// Errors encountered by the babe authorship task. -#[derive(derive_more::Display, Debug)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// Multiple BABE pre-runtime digests - #[display(fmt = "Multiple BABE pre-runtime digests, rejecting!")] + #[error("Multiple BABE pre-runtime digests, rejecting!")] MultiplePreRuntimeDigests, /// No BABE pre-runtime digest found - #[display(fmt = "No BABE pre-runtime digest found")] + #[error("No BABE pre-runtime digest found")] NoPreRuntimeDigest, /// Multiple BABE epoch change digests - #[display(fmt = "Multiple BABE epoch change digests, rejecting!")] + #[error("Multiple BABE epoch change digests, rejecting!")] MultipleEpochChangeDigests, /// Multiple BABE config change digests - #[display(fmt = "Multiple BABE config change digests, rejecting!")] + #[error("Multiple BABE config change digests, rejecting!")] MultipleConfigChangeDigests, /// Could not extract timestamp and slot - #[display(fmt = "Could not extract timestamp and slot: {:?}", _0)] + #[error("Could not extract timestamp and slot: {0}")] Extraction(sp_consensus::Error), /// Could not fetch epoch - #[display(fmt = "Could not fetch epoch at {:?}", _0)] + #[error("Could not fetch epoch at {0:?}")] FetchEpoch(B::Hash), /// Header rejected: too far in the future - #[display(fmt = "Header {:?} rejected: too far in the future", _0)] + #[error("Header {0:?} rejected: too far in the future")] TooFarInFuture(B::Hash), /// Parent unavailable. Cannot import - #[display(fmt = "Parent ({}) of {} unavailable. Cannot import", _0, _1)] + #[error("Parent ({0}) of {1} unavailable. Cannot import")] ParentUnavailable(B::Hash, B::Hash), /// Slot number must increase - #[display(fmt = "Slot number must increase: parent slot: {}, this slot: {}", _0, _1)] + #[error("Slot number must increase: parent slot: {0}, this slot: {1}")] SlotMustIncrease(Slot, Slot), /// Header has a bad seal - #[display(fmt = "Header {:?} has a bad seal", _0)] + #[error("Header {0:?} has a bad seal")] HeaderBadSeal(B::Hash), /// Header is unsealed - #[display(fmt = "Header {:?} is unsealed", _0)] + #[error("Header {0:?} is unsealed")] HeaderUnsealed(B::Hash), /// Slot author not found - #[display(fmt = "Slot author not found")] + #[error("Slot author not found")] SlotAuthorNotFound, /// Secondary slot assignments are disabled for the current epoch. - #[display(fmt = "Secondary slot assignments are disabled for the current epoch.")] + #[error("Secondary slot assignments are disabled for the current epoch.")] SecondarySlotAssignmentsDisabled, /// Bad signature - #[display(fmt = "Bad signature on {:?}", _0)] + #[error("Bad signature on {0:?}")] BadSignature(B::Hash), /// Invalid author: Expected secondary author - #[display(fmt = "Invalid author: Expected secondary author: {:?}, got: {:?}.", _0, _1)] + #[error("Invalid author: Expected secondary author: {0:?}, got: {1:?}.")] InvalidAuthor(AuthorityId, AuthorityId), /// No secondary author expected. - #[display(fmt = "No secondary author expected.")] + #[error("No secondary author expected.")] NoSecondaryAuthorExpected, /// VRF verification of block by author failed - #[display( - fmt = "VRF verification of block by author {:?} failed: threshold {} exceeded", - _0, - _1 - )] + #[error("VRF verification of block by author {0:?} failed: threshold {1} exceeded")] VRFVerificationOfBlockFailed(AuthorityId, u128), /// VRF verification failed - #[display(fmt = "VRF verification failed: {:?}", _0)] + #[error("VRF verification failed: {0:?}")] VRFVerificationFailed(SignatureError), /// Could not fetch parent header - #[display(fmt = "Could not fetch parent header: {:?}", _0)] + #[error("Could not fetch parent header: {0}")] FetchParentHeader(sp_blockchain::Error), /// Expected epoch change to happen. - #[display(fmt = "Expected epoch change to happen at {:?}, s{}", _0, _1)] + #[error("Expected epoch change to happen at {0:?}, s{1}")] ExpectedEpochChange(B::Hash, Slot), /// Unexpected config change. - #[display(fmt = "Unexpected config change")] + #[error("Unexpected config change")] UnexpectedConfigChange, /// Unexpected epoch change - #[display(fmt = "Unexpected epoch change")] + #[error("Unexpected epoch change")] UnexpectedEpochChange, /// Parent block has no associated weight - #[display(fmt = "Parent block of {} has no associated weight", _0)] + #[error("Parent block of {0} has no associated weight")] ParentBlockNoAssociatedWeight(B::Hash), /// Check inherents error - #[display(fmt = "Checking inherents failed: {}", _0)] + #[error("Checking inherents failed: {0}")] CheckInherents(sp_inherents::Error), /// Unhandled check inherents error - #[display(fmt = "Checking inherents unhandled error: {}", "String::from_utf8_lossy(_0)")] + #[error("Checking inherents unhandled error: {}", String::from_utf8_lossy(.0))] CheckInherentsUnhandled(sp_inherents::InherentIdentifier), /// Create inherents error. - #[display(fmt = "Creating inherents failed: {}", _0)] + #[error("Creating inherents failed: {0}")] CreateInherents(sp_inherents::Error), /// Client error + #[error(transparent)] Client(sp_blockchain::Error), /// Runtime Api error. + #[error(transparent)] RuntimeApi(sp_api::ApiError), /// Fork tree error + #[error(transparent)] ForkTree(Box>), } -impl std::convert::From> for String { +impl From> for String { fn from(error: Error) -> String { error.to_string() } @@ -330,65 +336,40 @@ pub struct BabeIntermediate { /// Intermediate key for Babe engine. pub static INTERMEDIATE_KEY: &[u8] = b"babe1"; -/// A slot duration. Create with `get_or_compute`. -// FIXME: Once Rust has higher-kinded types, the duplication between this -// and `super::babe::Config` can be eliminated. -// https://github.com/paritytech/substrate/issues/2434 -#[derive(Clone)] -pub struct Config(sc_consensus_slots::SlotDuration); - -impl Config { - /// Either fetch the slot duration from disk or compute it from the genesis - /// state. - pub fn get_or_compute(client: &C) -> ClientResult - where - C: AuxStore + ProvideRuntimeApi + UsageProvider, - C::Api: BabeApi, - { - trace!(target: "babe", "Getting slot duration"); - match sc_consensus_slots::SlotDuration::get_or_compute(client, |a, b| { - let has_api_v1 = a.has_api_with::, _>(&b, |v| v == 1)?; - let has_api_v2 = a.has_api_with::, _>(&b, |v| v == 2)?; - - if has_api_v1 { - #[allow(deprecated)] - { - Ok(a.configuration_before_version_2(b)?.into()) - } - } else if has_api_v2 { - a.configuration(b).map_err(Into::into) - } else { - Err(sp_blockchain::Error::VersionInvalid( - "Unsupported or invalid BabeApi version".to_string(), - )) - } - }) - .map(Self) - { - Ok(s) => Ok(s), - Err(s) => { - warn!(target: "babe", "Failed to get slot duration"); - Err(s) - }, - } - } - - /// Get the inner slot duration - pub fn slot_duration(&self) -> Duration { - self.0.slot_duration() - } -} +/// Read configuration from the runtime state at current best block. +pub fn configuration(client: &C) -> ClientResult +where + C: AuxStore + ProvideRuntimeApi + UsageProvider, + C::Api: BabeApi, +{ + let block_id = if client.usage_info().chain.finalized_state.is_some() { + BlockId::Hash(client.usage_info().chain.best_hash) + } else { + debug!(target: "babe", "No finalized state is available. Reading config from genesis"); + BlockId::Hash(client.usage_info().chain.genesis_hash) + }; -impl std::ops::Deref for Config { - type Target = BabeGenesisConfiguration; + let runtime_api = client.runtime_api(); + let version = runtime_api.api_version::>(&block_id)?; - fn deref(&self) -> &BabeGenesisConfiguration { - &*self.0 - } + let config = match version { + Some(1) => { + #[allow(deprecated)] + { + runtime_api.configuration_before_version_2(&block_id)?.into() + } + }, + Some(2) => runtime_api.configuration(&block_id)?, + _ => + return Err(sp_blockchain::Error::VersionInvalid( + "Unsupported or invalid BabeApi version".to_string(), + )), + }; + Ok(config) } /// Parameters for BABE. -pub struct BabeParams { +pub struct BabeParams { /// The keystore that manages the keys of the node. pub keystore: SyncCryptoStorePtr, @@ -424,9 +405,6 @@ pub struct BabeParams { /// The source of timestamps for relative slots pub babe_link: BabeLink, - /// Checks if the current native implementation can author with a runtime at a given block. - pub can_author_with: CAW, - /// The proportion of the slot dedicated to proposing. /// /// The block proposing will be limited to this proportion of the slot from the starting of the @@ -443,7 +421,7 @@ pub struct BabeParams { } /// Start the babe worker. -pub fn start_babe( +pub fn start_babe( BabeParams { keystore, client, @@ -456,18 +434,17 @@ pub fn start_babe( force_authoring, backoff_authoring_blocks, babe_link, - can_author_with, block_proposal_slot_portion, max_block_proposal_slot_portion, telemetry, - }: BabeParams, + }: BabeParams, ) -> Result, sp_consensus::Error> where B: BlockT, C: ProvideRuntimeApi - + ProvideCache + ProvideUncles + BlockchainEvents + + PreCommitActions + HeaderBackend + HeaderMetadata + Send @@ -486,12 +463,10 @@ where CIDP: CreateInherentDataProviders + Send + Sync + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send, BS: BackoffAuthoringBlocksStrategy> + Send + Sync + 'static, - CAW: CanAuthorWith + Send + Sync + 'static, Error: std::error::Error + Send + From + From + 'static, { const HANDLE_BUFFER_SIZE: usize = 1024; - let config = babe_link.config; let slot_notification_sinks = Arc::new(Mutex::new(Vec::new())); let worker = BabeSlotWorker { @@ -505,41 +480,90 @@ where keystore, epoch_changes: babe_link.epoch_changes.clone(), slot_notification_sinks: slot_notification_sinks.clone(), - config: config.clone(), + config: babe_link.config.clone(), block_proposal_slot_portion, max_block_proposal_slot_portion, telemetry, }; info!(target: "babe", "👶 Starting BABE Authorship worker"); - let inner = sc_consensus_slots::start_slot_worker( - config.0.clone(), + + let slot_worker = sc_consensus_slots::start_slot_worker( + babe_link.config.slot_duration(), select_chain, - worker, + sc_consensus_slots::SimpleSlotWorkerToSlotWorker(worker), sync_oracle, create_inherent_data_providers, - can_author_with, ); let (worker_tx, worker_rx) = channel(HANDLE_BUFFER_SIZE); let answer_requests = - answer_requests(worker_rx, config.0, client, babe_link.epoch_changes.clone()); + answer_requests(worker_rx, babe_link.config, client, babe_link.epoch_changes); + + let inner = future::select(Box::pin(slot_worker), Box::pin(answer_requests)); Ok(BabeWorker { - inner: Box::pin(future::join(inner, answer_requests).map(|_| ())), + inner: Box::pin(inner.map(|_| ())), slot_notification_sinks, handle: BabeWorkerHandle(worker_tx), }) } +// Remove obsolete block's weight data by leveraging finality notifications. +// This includes data for all finalized blocks (excluding the most recent one) +// and all stale branches. +fn aux_storage_cleanup + HeaderBackend, Block: BlockT>( + client: &C, + notification: &FinalityNotification, +) -> AuxDataOperations { + let mut hashes = HashSet::new(); + + let first = notification.tree_route.first().unwrap_or(¬ification.hash); + match client.header_metadata(*first) { + Ok(meta) => { + hashes.insert(meta.parent); + }, + Err(err) => warn!( + target: "babe", + "Failed to lookup metadata for block `{:?}`: {}", + first, + err, + ), + } + + // Cleans data for finalized block's ancestors + hashes.extend( + notification + .tree_route + .iter() + // Ensure we don't prune latest finalized block. + // This should not happen, but better be safe than sorry! + .filter(|h| **h != notification.hash), + ); + + // Cleans data for stale forks. + let stale_forks = match client.expand_forks(¬ification.stale_heads) { + Ok(stale_forks) => stale_forks, + Err((stale_forks, e)) => { + warn!(target: "babe", "{:?}", e,); + stale_forks + }, + }; + hashes.extend(stale_forks.iter()); + + hashes + .into_iter() + .map(|val| (aux_schema::block_weight_key(val), None)) + .collect() +} + async fn answer_requests( mut request_rx: Receiver>, - genesis_config: sc_consensus_slots::SlotDuration, + config: BabeConfiguration, client: Arc, epoch_changes: SharedEpochChanges, ) where C: ProvideRuntimeApi - + ProvideCache + ProvideUncles + BlockchainEvents + HeaderBackend @@ -561,13 +585,11 @@ async fn answer_requests( slot_number, ) .map_err(|e| Error::::ForkTree(Box::new(e)))? - .ok_or_else(|| Error::::FetchEpoch(parent_hash))?; + .ok_or(Error::::FetchEpoch(parent_hash))?; let viable_epoch = epoch_changes - .viable_epoch(&epoch_descriptor, |slot| { - Epoch::genesis(&genesis_config, slot) - }) - .ok_or_else(|| Error::::FetchEpoch(parent_hash))?; + .viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&config, slot)) + .ok_or(Error::::FetchEpoch(parent_hash))?; Ok(sp_consensus_babe::Epoch { epoch_index: viable_epoch.as_ref().epoch_index, @@ -615,7 +637,7 @@ impl BabeWorkerHandle { /// Worker for Babe which implements `Future`. This must be polled. #[must_use] pub struct BabeWorker { - inner: Pin + Send + 'static>>, + inner: Pin + Send + 'static>>, slot_notification_sinks: SlotNotificationSinks, handle: BabeWorkerHandle, } @@ -639,13 +661,10 @@ impl BabeWorker { } } -impl futures::Future for BabeWorker { +impl Future for BabeWorker { type Output = (); - fn poll( - mut self: Pin<&mut Self>, - cx: &mut futures::task::Context, - ) -> futures::task::Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { self.inner.as_mut().poll(cx) } } @@ -666,7 +685,7 @@ struct BabeSlotWorker { keystore: SyncCryptoStorePtr, epoch_changes: SharedEpochChanges, slot_notification_sinks: SlotNotificationSinks, - config: Config, + config: BabeConfiguration, block_proposal_slot_portion: SlotProportion, max_block_proposal_slot_portion: Option, telemetry: Option, @@ -677,10 +696,7 @@ impl sc_consensus_slots::SimpleSlotWorker for BabeSlotWorker where B: BlockT, - C: ProvideRuntimeApi - + ProvideCache - + HeaderBackend - + HeaderMetadata, + C: ProvideRuntimeApi + HeaderBackend + HeaderMetadata, C::Api: BabeApi, E: Environment + Sync, E::Proposer: Proposer>, @@ -690,7 +706,6 @@ where BS: BackoffAuthoringBlocksStrategy> + Sync, Error: std::error::Error + Send + From + From + 'static, { - type EpochData = ViableEpochDescriptor, Epoch>; type Claim = (PreDigest, AuthorityId); type SyncOracle = SO; type JustificationSyncLink = L; @@ -698,6 +713,7 @@ where Pin> + Send + 'static>>; type Proposer = E::Proposer; type BlockImport = I; + type AuxData = ViableEpochDescriptor, Epoch>; fn logging_target(&self) -> &'static str { "babe" @@ -707,27 +723,23 @@ where &mut self.block_import } - fn epoch_data( - &self, - parent: &B::Header, - slot: Slot, - ) -> Result { + fn aux_data(&self, parent: &B::Header, slot: Slot) -> Result { self.epoch_changes .shared_data() .epoch_descriptor_for_child_of( descendent_query(&*self.client), &parent.hash(), - parent.number().clone(), + *parent.number(), slot, ) - .map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))? + .map_err(|e| ConsensusError::ChainLookup(e.to_string()))? .ok_or(sp_consensus::Error::InvalidAuthoritiesSet) } - fn authorities_len(&self, epoch_descriptor: &Self::EpochData) -> Option { + fn authorities_len(&self, epoch_descriptor: &Self::AuxData) -> Option { self.epoch_changes .shared_data() - .viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot)) + .viable_epoch(epoch_descriptor, |slot| Epoch::genesis(&self.config, slot)) .map(|epoch| epoch.as_ref().authorities.len()) } @@ -742,7 +754,7 @@ where slot, self.epoch_changes .shared_data() - .viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot))? + .viable_epoch(epoch_descriptor, |slot| Epoch::genesis(&self.config, slot))? .as_ref(), &self.keystore, ); @@ -760,83 +772,67 @@ where slot: Slot, epoch_descriptor: &ViableEpochDescriptor, Epoch>, ) { - self.slot_notification_sinks.lock().retain_mut(|sink| { - match sink.try_send((slot, epoch_descriptor.clone())) { - Ok(()) => true, - Err(e) => - if e.is_full() { - warn!(target: "babe", "Trying to notify a slot but the channel is full"); - true - } else { - false - }, - } + let sinks = &mut self.slot_notification_sinks.lock(); + sinks.retain_mut(|sink| match sink.try_send((slot, epoch_descriptor.clone())) { + Ok(()) => true, + Err(e) => + if e.is_full() { + warn!(target: "babe", "Trying to notify a slot but the channel is full"); + true + } else { + false + }, }); } - fn pre_digest_data( - &self, - _slot: Slot, - claim: &Self::Claim, - ) -> Vec> { - vec![ as CompatibleDigestItem>::babe_pre_digest(claim.0.clone())] + fn pre_digest_data(&self, _slot: Slot, claim: &Self::Claim) -> Vec { + vec![::babe_pre_digest(claim.0.clone())] } - fn block_import_params( + async fn block_import_params( &self, - ) -> Box< - dyn Fn( - B::Header, - &B::Hash, - Vec, - StorageChanges, - Self::Claim, - Self::EpochData, - ) -> Result, sp_consensus::Error> - + Send - + 'static, + header: B::Header, + header_hash: &B::Hash, + body: Vec, + storage_changes: StorageChanges<>::Transaction, B>, + (_, public): Self::Claim, + epoch_descriptor: Self::AuxData, + ) -> Result< + sc_consensus::BlockImportParams>::Transaction>, + sp_consensus::Error, > { - let keystore = self.keystore.clone(); - Box::new( - move |header, header_hash, body, storage_changes, (_, public), epoch_descriptor| { - // sign the pre-sealed hash of the block and then - // add it to a digest item. - let public_type_pair = public.clone().into(); - let public = public.to_raw_vec(); - let signature = SyncCryptoStore::sign_with( - &*keystore, - ::ID, - &public_type_pair, - header_hash.as_ref(), - ) - .map_err(|e| sp_consensus::Error::CannotSign(public.clone(), e.to_string()))? - .ok_or_else(|| { - sp_consensus::Error::CannotSign( - public.clone(), - "Could not find key in keystore.".into(), - ) - })?; - let signature: AuthoritySignature = signature - .clone() - .try_into() - .map_err(|_| sp_consensus::Error::InvalidSignature(signature, public))?; - let digest_item = - as CompatibleDigestItem>::babe_seal(signature.into()); - - let mut import_block = BlockImportParams::new(BlockOrigin::Own, header); - import_block.post_digests.push(digest_item); - import_block.body = Some(body); - import_block.state_action = StateAction::ApplyChanges( - sc_consensus::StorageChanges::Changes(storage_changes), - ); - import_block.intermediates.insert( - Cow::from(INTERMEDIATE_KEY), - Box::new(BabeIntermediate:: { epoch_descriptor }) as Box<_>, - ); - - Ok(import_block) - }, + // sign the pre-sealed hash of the block and then + // add it to a digest item. + let public_type_pair = public.clone().into(); + let public = public.to_raw_vec(); + let signature = SyncCryptoStore::sign_with( + &*self.keystore, + ::ID, + &public_type_pair, + header_hash.as_ref(), ) + .map_err(|e| sp_consensus::Error::CannotSign(public.clone(), e.to_string()))? + .ok_or_else(|| { + sp_consensus::Error::CannotSign( + public.clone(), + "Could not find key in keystore.".into(), + ) + })?; + let signature: AuthoritySignature = signature + .clone() + .try_into() + .map_err(|_| sp_consensus::Error::InvalidSignature(signature, public))?; + let digest_item = ::babe_seal(signature); + + let mut import_block = BlockImportParams::new(BlockOrigin::Own, header); + import_block.post_digests.push(digest_item); + import_block.body = Some(body); + import_block.state_action = + StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes(storage_changes)); + import_block + .insert_intermediate(INTERMEDIATE_KEY, BabeIntermediate:: { epoch_descriptor }); + + Ok(import_block) } fn force_authoring(&self) -> bool { @@ -880,7 +876,7 @@ where self.telemetry.clone() } - fn proposing_remaining_duration(&self, slot_info: &SlotInfo) -> std::time::Duration { + fn proposing_remaining_duration(&self, slot_info: &SlotInfo) -> Duration { let parent_slot = find_pre_digest::(&slot_info.chain_head).ok().map(|d| d.slot()); sc_consensus_slots::proposing_remaining_duration( @@ -921,10 +917,7 @@ pub fn find_pre_digest(header: &B::Header) -> Result( header: &B::Header, -) -> Result, Error> -where - DigestItemFor: CompatibleDigestItem, -{ +) -> Result, Error> { let mut epoch_digest: Option<_> = None; for log in header.digest().logs() { trace!(target: "babe", "Checking log {:?}, looking for epoch change digest.", log); @@ -943,10 +936,7 @@ where /// Extract the BABE config change digest from the given header, if it exists. fn find_next_config_digest( header: &B::Header, -) -> Result, Error> -where - DigestItemFor: CompatibleDigestItem, -{ +) -> Result, Error> { let mut config_digest: Option<_> = None; for log in header.digest().logs() { trace!(target: "babe", "Checking log {:?}, looking for epoch change digest.", log); @@ -966,7 +956,7 @@ where #[derive(Clone)] pub struct BabeLink { epoch_changes: SharedEpochChanges, - config: Config, + config: BabeConfiguration, } impl BabeLink { @@ -976,29 +966,27 @@ impl BabeLink { } /// Get the config of this link. - pub fn config(&self) -> &Config { + pub fn config(&self) -> &BabeConfiguration { &self.config } } /// A verifier for Babe blocks. -pub struct BabeVerifier { +pub struct BabeVerifier { client: Arc, select_chain: SelectChain, create_inherent_data_providers: CIDP, - config: Config, + config: BabeConfiguration, epoch_changes: SharedEpochChanges, - can_author_with: CAW, telemetry: Option, } -impl BabeVerifier +impl BabeVerifier where Block: BlockT, Client: AuxStore + HeaderBackend + HeaderMetadata + ProvideRuntimeApi, Client::Api: BlockBuilderApi + BabeApi, SelectChain: sp_consensus::SelectChain, - CAW: CanAuthorWith, CIDP: CreateInherentDataProviders, { async fn check_inherents( @@ -1009,16 +997,6 @@ where create_inherent_data_providers: CIDP::InherentDataProviders, execution_context: ExecutionContext, ) -> Result<(), Error> { - if let Err(e) = self.can_author_with.can_author_with(&block_id) { - debug!( - target: "babe", - "Skipping `check_inherents` as authoring version is not compatible: {}", - e, - ); - - return Ok(()) - } - let inherent_res = self .client .runtime_api() @@ -1123,8 +1101,8 @@ type BlockVerificationResult = Result<(BlockImportParams, Option)>>), String>; #[async_trait::async_trait] -impl Verifier - for BabeVerifier +impl Verifier + for BabeVerifier where Block: BlockT, Client: HeaderMetadata @@ -1132,11 +1110,9 @@ where + ProvideRuntimeApi + Send + Sync - + AuxStore - + ProvideCache, + + AuxStore, Client::Api: BlockBuilderApi + BabeApi, SelectChain: sp_consensus::SelectChain, - CAW: CanAuthorWith + Send + Sync, CIDP: CreateInherentDataProviders + Send + Sync, CIDP::InherentDataProviders: InherentDataProviderExt + Send + Sync, { @@ -1190,10 +1166,10 @@ where pre_digest.slot(), ) .map_err(|e| Error::::ForkTree(Box::new(e)))? - .ok_or_else(|| Error::::FetchEpoch(parent_hash))?; + .ok_or(Error::::FetchEpoch(parent_hash))?; let viable_epoch = epoch_changes .viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot)) - .ok_or_else(|| Error::::FetchEpoch(parent_hash))?; + .ok_or(Error::::FetchEpoch(parent_hash))?; // We add one to the current slot to allow for some small drift. // FIXME #1019 in the future, alter this queue to allow deferring of headers @@ -1228,27 +1204,30 @@ where ) .await { - warn!(target: "babe", "Error checking/reporting BABE equivocation: {:?}", err); + warn!(target: "babe", "Error checking/reporting BABE equivocation: {}", err); } - // if the body is passed through, we need to use the runtime - // to check that the internally-set timestamp in the inherents - // actually matches the slot set in the seal. if let Some(inner_body) = block.body { - let mut inherent_data = create_inherent_data_providers - .create_inherent_data() - .map_err(Error::::CreateInherents)?; - inherent_data.babe_replace_inherent_data(slot); let new_block = Block::new(pre_header.clone(), inner_body); - - self.check_inherents( - new_block.clone(), - BlockId::Hash(parent_hash), - inherent_data, - create_inherent_data_providers, - block.origin.into(), - ) - .await?; + if !block.state_action.skip_execution_checks() { + // if the body is passed through and the block was executed, + // we need to use the runtime to check that the internally-set + // timestamp in the inherents actually matches the slot set in the seal. + let mut inherent_data = create_inherent_data_providers + .create_inherent_data() + .await + .map_err(Error::::CreateInherents)?; + inherent_data.babe_replace_inherent_data(slot); + + self.check_inherents( + new_block.clone(), + BlockId::Hash(parent_hash), + inherent_data, + create_inherent_data_providers, + block.origin.into(), + ) + .await?; + } let (_, inner_body) = new_block.deconstruct(); block.body = Some(inner_body); @@ -1264,9 +1243,9 @@ where block.header = pre_header; block.post_digests.push(verified_info.seal); - block.intermediates.insert( - Cow::from(INTERMEDIATE_KEY), - Box::new(BabeIntermediate:: { epoch_descriptor }) as Box<_>, + block.insert_intermediate( + INTERMEDIATE_KEY, + BabeIntermediate:: { epoch_descriptor }, ); block.post_hash = Some(hash); @@ -1298,7 +1277,7 @@ pub struct BabeBlockImport { inner: I, client: Arc, epoch_changes: SharedEpochChanges, - config: Config, + config: BabeConfiguration, } impl Clone for BabeBlockImport { @@ -1317,7 +1296,7 @@ impl BabeBlockImport { client: Arc, epoch_changes: SharedEpochChanges, block_import: I, - config: Config, + config: BabeConfiguration, ) -> Self { BabeBlockImport { client, inner: block_import, epoch_changes, config } } @@ -1332,7 +1311,6 @@ where + HeaderMetadata + AuxStore + ProvideRuntimeApi - + ProvideCache + Send + Sync, Client::Api: BabeApi + ApiExt, @@ -1399,7 +1377,6 @@ where + HeaderMetadata + AuxStore + ProvideRuntimeApi - + ProvideCache + Send + Sync, Client::Api: BabeApi + ApiExt, @@ -1420,7 +1397,7 @@ where match self.client.status(BlockId::Hash(hash)) { Ok(sp_blockchain::BlockStatus::InChain) => { // When re-importing existing block strip away intermediates. - let _ = block.take_intermediate::>(INTERMEDIATE_KEY); + let _ = block.remove_intermediate::>(INTERMEDIATE_KEY); block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); return self.inner.import_block(block, new_cache).await.map_err(Into::into) }, @@ -1489,7 +1466,7 @@ where }; let intermediate = - block.take_intermediate::>(INTERMEDIATE_KEY)?; + block.remove_intermediate::>(INTERMEDIATE_KEY)?; let epoch_descriptor = intermediate.epoch_descriptor; let first_in_epoch = parent_slot < epoch_descriptor.start_slot(); @@ -1578,13 +1555,17 @@ where *block.header.parent_hash(), next_epoch, ) - .map_err(|e| ConsensusError::ClientImport(format!("{:?}", e)))?; - + .map_err(|e| { + ConsensusError::ClientImport(format!( + "Error importing epoch changes: {}", + e + )) + })?; Ok(()) }; if let Err(e) = prune_and_import() { - debug!(target: "babe", "Failed to launch next epoch: {:?}", e); + debug!(target: "babe", "Failed to launch next epoch: {}", e); *epoch_changes = old_epoch_changes.expect("set `Some` above and not taken; qed"); return Err(e) @@ -1615,7 +1596,7 @@ where parent_weight } else { aux_schema::load_block_weight(&*self.client, last_best) - .map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))? + .map_err(|e| ConsensusError::ChainLookup(e.to_string()))? .ok_or_else(|| { ConsensusError::ChainLookup( "No block weight for parent header.".to_string(), @@ -1667,11 +1648,14 @@ where Client: HeaderBackend + HeaderMetadata, { let info = client.info(); + if info.block_gap.is_none() { + epoch_changes.clear_gap(); + } let finalized_slot = { let finalized_header = client .header(BlockId::Hash(info.finalized_hash)) - .map_err(|e| ConsensusError::ClientImport(format!("{:?}", e)))? + .map_err(|e| ConsensusError::ClientImport(e.to_string()))? .expect( "best finalized hash was given by client; finalized headers must exist in db; qed", ); @@ -1688,7 +1672,7 @@ where info.finalized_number, finalized_slot, ) - .map_err(|e| ConsensusError::ClientImport(format!("{:?}", e)))?; + .map_err(|e| ConsensusError::ClientImport(e.to_string()))?; Ok(()) } @@ -1699,12 +1683,16 @@ where /// Also returns a link object used to correctly instantiate the import queue /// and background worker. pub fn block_import( - config: Config, + config: BabeConfiguration, wrapped_block_import: I, client: Arc, ) -> ClientResult<(BabeBlockImport, BabeLink)> where - Client: AuxStore + HeaderBackend + HeaderMetadata, + Client: AuxStore + + HeaderBackend + + HeaderMetadata + + PreCommitActions + + 'static, { let epoch_changes = aux_schema::load_epoch_changes::(&*client, &config)?; let link = BabeLink { epoch_changes: epoch_changes.clone(), config: config.clone() }; @@ -1714,6 +1702,16 @@ where // startup rather than waiting until importing the next epoch change block. prune_finalized(client.clone(), &mut epoch_changes.shared_data())?; + let client_weak = Arc::downgrade(&client); + let on_finality = move |summary: &FinalityNotification| { + if let Some(client) = client_weak.upgrade() { + aux_storage_cleanup(client.as_ref(), summary) + } else { + Default::default() + } + }; + client.register_finality_action(Box::new(on_finality)); + let import = BabeBlockImport::new(client, epoch_changes, wrapped_block_import, config); Ok((import, link)) @@ -1728,7 +1726,7 @@ where /// /// The block import object provided must be the `BabeBlockImport` or a wrapper /// of it, otherwise crucial import logic will be omitted. -pub fn import_queue( +pub fn import_queue( babe_link: BabeLink, block_import: Inner, justification_import: Option>, @@ -1737,7 +1735,6 @@ pub fn import_queue( create_inherent_data_providers: CIDP, spawner: &impl sp_core::traits::SpawnEssentialNamed, registry: Option<&Registry>, - can_author_with: CAW, telemetry: Option, ) -> ClientResult> where @@ -1749,7 +1746,6 @@ where + Sync + 'static, Client: ProvideRuntimeApi - + ProvideCache + HeaderBackend + HeaderMetadata + AuxStore @@ -1758,7 +1754,6 @@ where + 'static, Client::Api: BlockBuilderApi + BabeApi + ApiExt, SelectChain: sp_consensus::SelectChain + 'static, - CAW: CanAuthorWith + Send + Sync + 'static, CIDP: CreateInherentDataProviders + Send + Sync + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send + Sync, { @@ -1767,10 +1762,86 @@ where create_inherent_data_providers, config: babe_link.config, epoch_changes: babe_link.epoch_changes, - can_author_with, telemetry, client, }; Ok(BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry)) } + +/// Reverts protocol aux data to at most the last finalized block. +/// In particular, epoch-changes and block weights announced after the revert +/// point are removed. +pub fn revert( + client: Arc, + backend: Arc, + blocks: NumberFor, +) -> ClientResult<()> +where + Block: BlockT, + Client: AuxStore + + HeaderMetadata + + HeaderBackend + + ProvideRuntimeApi + + UsageProvider, + Client::Api: BabeApi, + Backend: BackendT, +{ + let best_number = client.info().best_number; + let finalized = client.info().finalized_number; + + let revertible = blocks.min(best_number - finalized); + if revertible == Zero::zero() { + return Ok(()) + } + + let revert_up_to_number = best_number - revertible; + let revert_up_to_hash = client.hash(revert_up_to_number)?.ok_or(ClientError::Backend( + format!("Unexpected hash lookup failure for block number: {}", revert_up_to_number), + ))?; + + // Revert epoch changes tree. + + // This config is only used on-genesis. + let config = configuration(&*client)?; + let epoch_changes = aux_schema::load_epoch_changes::(&*client, &config)?; + let mut epoch_changes = epoch_changes.shared_data(); + + if revert_up_to_number == Zero::zero() { + // Special case, no epoch changes data were present on genesis. + *epoch_changes = EpochChangesFor::::default(); + } else { + epoch_changes.revert(descendent_query(&*client), revert_up_to_hash, revert_up_to_number); + } + + // Remove block weights added after the revert point. + + let mut weight_keys = HashSet::with_capacity(revertible.saturated_into()); + + let leaves = backend.blockchain().leaves()?.into_iter().filter(|&leaf| { + sp_blockchain::tree_route(&*client, revert_up_to_hash, leaf) + .map(|route| route.retracted().is_empty()) + .unwrap_or_default() + }); + + for leaf in leaves { + let mut hash = leaf; + loop { + let meta = client.header_metadata(hash)?; + if meta.number <= revert_up_to_number || + !weight_keys.insert(aux_schema::block_weight_key(hash)) + { + // We've reached the revert point or an already processed branch, stop here. + break + } + hash = meta.parent; + } + } + + let weight_keys: Vec<_> = weight_keys.iter().map(|val| val.as_slice()).collect(); + + // Write epoch changes and remove weights in one shot. + aux_schema::write_epoch_changes::(&epoch_changes, |values| { + client.insert_aux(values, weight_keys.iter()) + }) +} diff --git a/client/consensus/babe/src/migration.rs b/client/consensus/babe/src/migration.rs index a248c9da24db8..23413aa6a7b1b 100644 --- a/client/consensus/babe/src/migration.rs +++ b/client/consensus/babe/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ // along with this program. If not, see . use crate::{ - AuthorityId, BabeAuthorityWeight, BabeEpochConfiguration, BabeGenesisConfiguration, Epoch, + AuthorityId, BabeAuthorityWeight, BabeConfiguration, BabeEpochConfiguration, Epoch, NextEpochDescriptor, VRF_OUTPUT_LENGTH, }; use codec::{Decode, Encode}; @@ -64,7 +64,7 @@ impl EpochT for EpochV0 { impl EpochV0 { /// Migrate the sturct to current epoch version. - pub fn migrate(self, config: &BabeGenesisConfiguration) -> Epoch { + pub fn migrate(self, config: &BabeConfiguration) -> Epoch { Epoch { epoch_index: self.epoch_index, start_slot: self.start_slot, diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index c033f4535be0b..7f51eb2c51977 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,38 +18,40 @@ //! BABE testsuite -// FIXME #2532: need to allow deprecated until refactor is done -// https://github.com/paritytech/substrate/issues/2532 -#![allow(deprecated)] use super::*; use authorship::claim_slot; -use futures::executor::block_on; use log::debug; -use rand::RngCore; -use rand_chacha::{rand_core::SeedableRng, ChaChaRng}; +use rand_chacha::{ + rand_core::{RngCore, SeedableRng}, + ChaChaRng, +}; use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; -use sc_client_api::{backend::TransactionFor, BlockchainEvents}; +use sc_client_api::{backend::TransactionFor, BlockchainEvents, Finalizer}; use sc_consensus::{BoxBlockImport, BoxJustificationImport}; use sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging; -use sc_keystore::LocalKeystore; -use sc_network::config::ProtocolConfig; use sc_network_test::{Block as TestBlock, *}; use sp_application_crypto::key_types::BABE; -use sp_consensus::{AlwaysCanAuthor, DisableProofRecording, NoNetwork as DummyOracle, Proposal}; +use sp_consensus::{DisableProofRecording, NoNetwork as DummyOracle, Proposal}; use sp_consensus_babe::{ inherents::InherentDataProvider, make_transcript, make_transcript_data, AllowedSlots, AuthorityPair, Slot, }; +use sp_consensus_slots::SlotDuration; use sp_core::crypto::Pair; -use sp_keystore::{vrf::make_transcript as transcript_from_data, SyncCryptoStore}; +use sp_keyring::Sr25519Keyring; +use sp_keystore::{ + testing::KeyStore as TestKeyStore, vrf::make_transcript as transcript_from_data, + SyncCryptoStore, +}; use sp_runtime::{ - generic::DigestItem, - traits::{Block as BlockT, DigestFor}, + generic::{Digest, DigestItem}, + traits::Block as BlockT, }; -use sp_timestamp::InherentDataProvider as TimestampInherentDataProvider; +use sp_timestamp::Timestamp; use std::{cell::RefCell, task::Poll, time::Duration}; +use tokio::runtime::{Handle, Runtime}; -type Item = DigestItem; +type Item = DigestItem; type Error = sp_blockchain::Error; @@ -71,11 +73,13 @@ type Mutator = Arc; type BabeBlockImport = PanickingBlockImport>>; +const SLOT_DURATION_MS: u64 = 1000; + #[derive(Clone)] struct DummyFactory { client: Arc, epoch_changes: SharedEpochChanges, - config: Config, + config: BabeConfiguration, mutator: Mutator, } @@ -108,7 +112,7 @@ impl Environment for DummyFactory { impl DummyProposer { fn propose_with( &mut self, - pre_digests: DigestFor, + pre_digests: Digest, ) -> future::Ready< Result< Proposal< @@ -156,7 +160,7 @@ impl DummyProposer { // that will re-check the randomness logic off-chain. let digest_data = ConsensusLog::NextEpochData(NextEpochDescriptor { authorities: epoch.authorities.clone(), - randomness: epoch.randomness.clone(), + randomness: epoch.randomness, }) .encode(); let digest = DigestItem::Consensus(BABE_ENGINE_ID, digest_data); @@ -181,7 +185,7 @@ impl Proposer for DummyProposer { fn propose( mut self, _: InherentData, - pre_digests: DigestFor, + pre_digests: Digest, _: Duration, _: Option, ) -> Self::Proposal { @@ -224,9 +228,19 @@ where type BabePeer = Peer, BabeBlockImport>; pub struct BabeTestNet { + rt_handle: Handle, peers: Vec, } +impl WithRuntime for BabeTestNet { + fn with_runtime(rt_handle: Handle) -> Self { + BabeTestNet { rt_handle, peers: Vec::new() } + } + fn rt_handle(&self) -> &Handle { + &self.rt_handle + } +} + type TestHeader = ::Header; type TestSelectChain = @@ -237,12 +251,11 @@ pub struct TestVerifier { TestBlock, PeersFullClient, TestSelectChain, - AlwaysCanAuthor, Box< dyn CreateInherentDataProviders< TestBlock, (), - InherentDataProviders = (TimestampInherentDataProvider, InherentDataProvider), + InherentDataProviders = (InherentDataProvider,), >, >, >, @@ -281,12 +294,6 @@ impl TestNetFactory for BabeTestNet { type PeerData = Option; type BlockImport = BabeBlockImport; - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - debug!(target: "babe", "Creating test network from config"); - BabeTestNet { peers: Vec::new() } - } - fn make_block_import( &self, client: PeersClient, @@ -295,9 +302,9 @@ impl TestNetFactory for BabeTestNet { Option>, Option, ) { - let client = client.as_full().expect("only full clients are tested"); + let client = client.as_client(); - let config = Config::get_or_compute(&*client).expect("config available"); + let config = crate::configuration(&*client).expect("config available"); let (block_import, link) = crate::block_import(config, client.clone(), client.clone()) .expect("can initialize block-import"); @@ -312,15 +319,10 @@ impl TestNetFactory for BabeTestNet { ) } - fn make_verifier( - &self, - client: PeersClient, - _cfg: &ProtocolConfig, - maybe_link: &Option, - ) -> Self::Verifier { + fn make_verifier(&self, client: PeersClient, maybe_link: &Option) -> Self::Verifier { use substrate_test_runtime_client::DefaultTestClientBuilderExt; - let client = client.as_full().expect("only full clients are used in test"); + let client = client.as_client(); trace!(target: "babe", "Creating a verifier"); // ensure block import and verifier are linked correctly. @@ -335,17 +337,14 @@ impl TestNetFactory for BabeTestNet { client: client.clone(), select_chain: longest_chain, create_inherent_data_providers: Box::new(|_, _| async { - let timestamp = TimestampInherentDataProvider::from_system_time(); - let slot = InherentDataProvider::from_timestamp_and_duration( - *timestamp, - Duration::from_secs(6), + let slot = InherentDataProvider::from_timestamp_and_slot_duration( + Timestamp::current(), + SlotDuration::from_millis(SLOT_DURATION_MS), ); - - Ok((timestamp, slot)) + Ok((slot,)) }), config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), - can_author_with: AlwaysCanAuthor, telemetry: None, }, mutator: MUTATOR.with(|m| m.borrow().clone()), @@ -371,39 +370,43 @@ impl TestNetFactory for BabeTestNet { #[should_panic] fn rejects_empty_block() { sp_tracing::try_init_simple(); - let mut net = BabeTestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 3); let block_builder = |builder: BlockBuilder<_, _, _>| builder.build().unwrap().block; net.mut_peers(|peer| { peer[0].generate_blocks(1, BlockOrigin::NetworkInitialSync, block_builder); }) } +fn create_keystore(authority: Sr25519Keyring) -> SyncCryptoStorePtr { + let keystore = Arc::new(TestKeyStore::new()); + SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some(&authority.to_seed())) + .expect("Generates authority key"); + keystore +} + fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static) { sp_tracing::try_init_simple(); let mutator = Arc::new(mutator) as Mutator; MUTATOR.with(|m| *m.borrow_mut() = mutator.clone()); - let net = BabeTestNet::new(3); - let peers = &[(0, "//Alice"), (1, "//Bob"), (2, "//Charlie")]; + let runtime = Runtime::new().unwrap(); + let net = BabeTestNet::new(runtime.handle().clone(), 3); + + let peers = [Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie]; let net = Arc::new(Mutex::new(net)); let mut import_notifications = Vec::new(); let mut babe_futures = Vec::new(); - let mut keystore_paths = Vec::new(); - for (peer_id, seed) in peers { + for (peer_id, auth_id) in peers.iter().enumerate() { let mut net = net.lock(); - let peer = net.peer(*peer_id); - let client = peer.client().as_full().expect("Only full clients are used in tests").clone(); + let peer = net.peer(peer_id); + let client = peer.client().as_client(); let select_chain = peer.select_chain().expect("Full client has select_chain"); - let keystore_path = tempfile::tempdir().expect("Creates keystore path"); - let keystore: SyncCryptoStorePtr = - Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore")); - SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some(seed)) - .expect("Generates authority key"); - keystore_paths.push(keystore_path); + let keystore = create_keystore(*auth_id); let mut got_own = false; let mut got_other = false; @@ -448,19 +451,16 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static env: environ, sync_oracle: DummyOracle, create_inherent_data_providers: Box::new(|_, _| async { - let timestamp = TimestampInherentDataProvider::from_system_time(); - let slot = InherentDataProvider::from_timestamp_and_duration( - *timestamp, - Duration::from_secs(6), + let slot = InherentDataProvider::from_timestamp_and_slot_duration( + Timestamp::current(), + SlotDuration::from_millis(SLOT_DURATION_MS), ); - - Ok((timestamp, slot)) + Ok((slot,)) }), force_authoring: false, backoff_authoring_blocks: Some(BackoffAuthoringOnFinalizedHeadLagging::default()), babe_link: data.link.clone(), keystore, - can_author_with: sp_consensus::AlwaysCanAuthor, justification_sync_link: (), block_proposal_slot_portion: SlotProportion::new(0.5), max_block_proposal_slot_portion: None, @@ -469,7 +469,7 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static .expect("Starts babe"), ); } - block_on(future::select( + runtime.block_on(future::select( futures::future::poll_fn(move |cx| { let mut net = net.lock(); net.poll(cx); @@ -554,16 +554,14 @@ fn sig_is_not_pre_digest() { #[test] fn can_author_block() { sp_tracing::try_init_simple(); - let keystore_path = tempfile::tempdir().expect("Creates keystore path"); - let keystore: SyncCryptoStorePtr = - Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore")); - let public = SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some("//Alice")) - .expect("Generates authority pair"); + + let authority = Sr25519Keyring::Alice; + let keystore = create_keystore(authority); let mut i = 0; let epoch = Epoch { start_slot: 0.into(), - authorities: vec![(public.into(), 1)], + authorities: vec![(authority.public().into(), 1)], randomness: [0; 32], epoch_index: 1, duration: 100, @@ -573,11 +571,11 @@ fn can_author_block() { }, }; - let mut config = crate::BabeGenesisConfiguration { + let mut config = crate::BabeConfiguration { slot_duration: 1000, epoch_length: 100, c: (3, 10), - genesis_authorities: Vec::new(), + authorities: Vec::new(), randomness: [0; 32], allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots, }; @@ -608,8 +606,9 @@ fn propose_and_import_block( slot: Option, proposer_factory: &mut DummyFactory, block_import: &mut BoxBlockImport, -) -> sp_core::H256 { - let mut proposer = futures::executor::block_on(proposer_factory.init(parent)).unwrap(); + runtime: &Runtime, +) -> Hash { + let mut proposer = runtime.block_on(proposer_factory.init(parent)).unwrap(); let slot = slot.unwrap_or_else(|| { let parent_pre_digest = find_pre_digest::(parent).unwrap(); @@ -625,7 +624,7 @@ fn propose_and_import_block( let parent_hash = parent.hash(); - let mut block = futures::executor::block_on(proposer.propose_with(pre_digest)).unwrap().block; + let mut block = runtime.block_on(proposer.propose_with(pre_digest)).unwrap().block; let epoch_descriptor = proposer_factory .epoch_changes @@ -658,12 +657,11 @@ fn propose_and_import_block( let mut import = BlockImportParams::new(BlockOrigin::Own, block.header); import.post_digests.push(seal); import.body = Some(block.extrinsics); - import.intermediates.insert( - Cow::from(INTERMEDIATE_KEY), - Box::new(BabeIntermediate:: { epoch_descriptor }) as Box<_>, - ); + import + .insert_intermediate(INTERMEDIATE_KEY, BabeIntermediate:: { epoch_descriptor }); import.fork_choice = Some(ForkChoiceStrategy::LongestChain); - let import_result = block_on(block_import.import_block(import, Default::default())).unwrap(); + let import_result = + runtime.block_on(block_import.import_block(import, Default::default())).unwrap(); match import_result { ImportResult::Imported(_) => {}, @@ -673,13 +671,38 @@ fn propose_and_import_block( post_hash } +// Propose and import n valid BABE blocks that are built on top of the given parent. +// The proposer takes care of producing epoch change digests according to the epoch +// duration (which is set to 6 slots in the test runtime). +fn propose_and_import_blocks( + client: &PeersFullClient, + proposer_factory: &mut DummyFactory, + block_import: &mut BoxBlockImport, + parent_id: BlockId, + n: usize, + runtime: &Runtime, +) -> Vec { + let mut hashes = Vec::with_capacity(n); + let mut parent_header = client.header(&parent_id).unwrap().unwrap(); + + for _ in 0..n { + let block_hash = + propose_and_import_block(&parent_header, None, proposer_factory, block_import, runtime); + hashes.push(block_hash); + parent_header = client.header(&BlockId::Hash(block_hash)).unwrap().unwrap(); + } + + hashes +} + #[test] fn importing_block_one_sets_genesis_epoch() { - let mut net = BabeTestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); - let client = peer.client().as_full().expect("Only full clients are used in tests").clone(); + let client = peer.client().as_client(); let mut proposer_factory = DummyFactory { client: client.clone(), @@ -697,6 +720,7 @@ fn importing_block_one_sets_genesis_epoch() { Some(999.into()), &mut proposer_factory, &mut block_import, + &runtime, ); let genesis_epoch = Epoch::genesis(&data.link.config, 999.into()); @@ -713,15 +737,15 @@ fn importing_block_one_sets_genesis_epoch() { } #[test] -fn importing_epoch_change_block_prunes_tree() { - use sc_client_api::Finalizer; - - let mut net = BabeTestNet::new(1); +fn revert_prunes_epoch_changes_and_removes_weights() { + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); - let client = peer.client().as_full().expect("Only full clients are used in tests").clone(); + let client = peer.client().as_client(); + let backend = peer.client().as_backend(); let mut block_import = data.block_import.lock().take().expect("import set up during init"); let epoch_changes = data.link.epoch_changes.clone(); @@ -732,26 +756,148 @@ fn importing_epoch_change_block_prunes_tree() { mutator: Arc::new(|_, _| ()), }; - // This is just boilerplate code for proposing and importing n valid BABE - // blocks that are built on top of the given parent. The proposer takes care - // of producing epoch change digests according to the epoch duration (which - // is set to 6 slots in the test runtime). - let mut propose_and_import_blocks = |parent_id, n| { - let mut hashes = Vec::new(); - let mut parent_header = client.header(&parent_id).unwrap().unwrap(); - - for _ in 0..n { - let block_hash = propose_and_import_block( - &parent_header, - None, - &mut proposer_factory, - &mut block_import, - ); - hashes.push(block_hash); - parent_header = client.header(&BlockId::Hash(block_hash)).unwrap().unwrap(); - } + let mut propose_and_import_blocks_wrap = |parent_id, n| { + propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + parent_id, + n, + &runtime, + ) + }; + + // Test scenario. + // Information for epoch 19 is produced on three different forks at block #13. + // One branch starts before the revert point (epoch data should be maintained). + // One branch starts after the revert point (epoch data should be removed). + // + // *----------------- F(#13) --#18 < fork #2 + // / + // A(#1) ---- B(#7) ----#8----+-----#12----- C(#13) ---- D(#19) ------#21 < canon + // \ ^ \ + // \ revert *---- G(#13) ---- H(#19) ---#20 < fork #3 + // \ to #10 + // *-----E(#7)---#11 < fork #1 + let canon = propose_and_import_blocks_wrap(BlockId::Number(0), 21); + let fork1 = propose_and_import_blocks_wrap(BlockId::Hash(canon[0]), 10); + let fork2 = propose_and_import_blocks_wrap(BlockId::Hash(canon[7]), 10); + let fork3 = propose_and_import_blocks_wrap(BlockId::Hash(canon[11]), 8); - hashes + // We should be tracking a total of 9 epochs in the fork tree + assert_eq!(epoch_changes.shared_data().tree().iter().count(), 8); + // And only one root + assert_eq!(epoch_changes.shared_data().tree().roots().count(), 1); + + // Revert canon chain to block #10 (best(21) - 11) + revert(client.clone(), backend, 11).expect("revert should work for baked test scenario"); + + // Load and check epoch changes. + + let actual_nodes = + aux_schema::load_epoch_changes::(&*client, &data.link.config) + .expect("load epoch changes") + .shared_data() + .tree() + .iter() + .map(|(h, _, _)| *h) + .collect::>(); + + let expected_nodes = vec![ + canon[0], // A + canon[6], // B + fork2[4], // F + fork1[5], // E + ]; + + assert_eq!(actual_nodes, expected_nodes); + + let weight_data_check = |hashes: &[Hash], expected: bool| { + hashes.iter().all(|hash| { + aux_schema::load_block_weight(&*client, hash).unwrap().is_some() == expected + }) + }; + assert!(weight_data_check(&canon[..10], true)); + assert!(weight_data_check(&canon[10..], false)); + assert!(weight_data_check(&fork1, true)); + assert!(weight_data_check(&fork2, true)); + assert!(weight_data_check(&fork3, false)); +} + +#[test] +fn revert_not_allowed_for_finalized() { + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); + + let peer = net.peer(0); + let data = peer.data.as_ref().expect("babe link set up during initialization"); + + let client = peer.client().as_client(); + let backend = peer.client().as_backend(); + let mut block_import = data.block_import.lock().take().expect("import set up during init"); + + let mut proposer_factory = DummyFactory { + client: client.clone(), + config: data.link.config.clone(), + epoch_changes: data.link.epoch_changes.clone(), + mutator: Arc::new(|_, _| ()), + }; + + let mut propose_and_import_blocks_wrap = |parent_id, n| { + propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + parent_id, + n, + &runtime, + ) + }; + + let canon = propose_and_import_blocks_wrap(BlockId::Number(0), 3); + + // Finalize best block + client.finalize_block(canon[2], None, false).unwrap(); + + // Revert canon chain to last finalized block + revert(client.clone(), backend, 100).expect("revert should work for baked test scenario"); + + let weight_data_check = |hashes: &[Hash], expected: bool| { + hashes.iter().all(|hash| { + aux_schema::load_block_weight(&*client, hash).unwrap().is_some() == expected + }) + }; + assert!(weight_data_check(&canon, true)); +} + +#[test] +fn importing_epoch_change_block_prunes_tree() { + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); + + let peer = net.peer(0); + let data = peer.data.as_ref().expect("babe link set up during initialization"); + + let client = peer.client().as_client(); + let mut block_import = data.block_import.lock().take().expect("import set up during init"); + let epoch_changes = data.link.epoch_changes.clone(); + + let mut proposer_factory = DummyFactory { + client: client.clone(), + config: data.link.config.clone(), + epoch_changes: data.link.epoch_changes.clone(), + mutator: Arc::new(|_, _| ()), + }; + + let mut propose_and_import_blocks_wrap = |parent_id, n| { + propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + parent_id, + n, + &runtime, + ) }; // This is the block tree that we're going to use in this test. Each node @@ -766,12 +912,12 @@ fn importing_epoch_change_block_prunes_tree() { // Create and import the canon chain and keep track of fork blocks (A, C, D) // from the diagram above. - let canon_hashes = propose_and_import_blocks(BlockId::Number(0), 30); + let canon = propose_and_import_blocks_wrap(BlockId::Number(0), 30); // Create the forks - let fork_1 = propose_and_import_blocks(BlockId::Hash(canon_hashes[0]), 10); - let fork_2 = propose_and_import_blocks(BlockId::Hash(canon_hashes[12]), 15); - let fork_3 = propose_and_import_blocks(BlockId::Hash(canon_hashes[18]), 10); + let fork_1 = propose_and_import_blocks_wrap(BlockId::Hash(canon[0]), 10); + let fork_2 = propose_and_import_blocks_wrap(BlockId::Hash(canon[12]), 15); + let fork_3 = propose_and_import_blocks_wrap(BlockId::Hash(canon[18]), 10); // We should be tracking a total of 9 epochs in the fork tree assert_eq!(epoch_changes.shared_data().tree().iter().count(), 9); @@ -781,62 +927,43 @@ fn importing_epoch_change_block_prunes_tree() { // We finalize block #13 from the canon chain, so on the next epoch // change the tree should be pruned, to not contain F (#7). - client.finalize_block(BlockId::Hash(canon_hashes[12]), None, false).unwrap(); - propose_and_import_blocks(BlockId::Hash(client.chain_info().best_hash), 7); + client.finalize_block(canon[12], None, false).unwrap(); + propose_and_import_blocks_wrap(BlockId::Hash(client.chain_info().best_hash), 7); - // at this point no hashes from the first fork must exist on the tree - assert!(!epoch_changes - .shared_data() - .tree() - .iter() - .map(|(h, _, _)| h) - .any(|h| fork_1.contains(h)),); + let nodes: Vec<_> = epoch_changes.shared_data().tree().iter().map(|(h, _, _)| *h).collect(); - // but the epoch changes from the other forks must still exist - assert!(epoch_changes - .shared_data() - .tree() - .iter() - .map(|(h, _, _)| h) - .any(|h| fork_2.contains(h))); + // no hashes from the first fork must exist on the tree + assert!(!nodes.iter().any(|h| fork_1.contains(h))); - assert!(epoch_changes - .shared_data() - .tree() - .iter() - .map(|(h, _, _)| h) - .any(|h| fork_3.contains(h)),); + // but the epoch changes from the other forks must still exist + assert!(nodes.iter().any(|h| fork_2.contains(h))); + assert!(nodes.iter().any(|h| fork_3.contains(h))); // finalizing block #25 from the canon chain should prune out the second fork - client.finalize_block(BlockId::Hash(canon_hashes[24]), None, false).unwrap(); - propose_and_import_blocks(BlockId::Hash(client.chain_info().best_hash), 8); + client.finalize_block(canon[24], None, false).unwrap(); + propose_and_import_blocks_wrap(BlockId::Hash(client.chain_info().best_hash), 8); - // at this point no hashes from the second fork must exist on the tree - assert!(!epoch_changes - .shared_data() - .tree() - .iter() - .map(|(h, _, _)| h) - .any(|h| fork_2.contains(h)),); + let nodes: Vec<_> = epoch_changes.shared_data().tree().iter().map(|(h, _, _)| *h).collect(); - // while epoch changes from the last fork should still exist - assert!(epoch_changes - .shared_data() - .tree() - .iter() - .map(|(h, _, _)| h) - .any(|h| fork_3.contains(h)),); + // no hashes from the other forks must exist on the tree + assert!(!nodes.iter().any(|h| fork_2.contains(h))); + assert!(!nodes.iter().any(|h| fork_3.contains(h))); + + // Check that we contain the nodes that we care about + assert!(nodes.iter().any(|h| *h == canon[18])); + assert!(nodes.iter().any(|h| *h == canon[24])); } #[test] #[should_panic] fn verify_slots_are_strictly_increasing() { - let mut net = BabeTestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); - let client = peer.client().as_full().expect("Only full clients are used in tests").clone(); + let client = peer.client().as_client(); let mut block_import = data.block_import.lock().take().expect("import set up during init"); let mut proposer_factory = DummyFactory { @@ -854,27 +981,32 @@ fn verify_slots_are_strictly_increasing() { Some(999.into()), &mut proposer_factory, &mut block_import, + &runtime, ); let b1 = client.header(&BlockId::Hash(b1)).unwrap().unwrap(); // we should fail to import this block since the slot number didn't increase. // we will panic due to the `PanickingBlockImport` defined above. - propose_and_import_block(&b1, Some(999.into()), &mut proposer_factory, &mut block_import); + propose_and_import_block( + &b1, + Some(999.into()), + &mut proposer_factory, + &mut block_import, + &runtime, + ); } #[test] fn babe_transcript_generation_match() { sp_tracing::try_init_simple(); - let keystore_path = tempfile::tempdir().expect("Creates keystore path"); - let keystore: SyncCryptoStorePtr = - Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore")); - let public = SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some("//Alice")) - .expect("Generates authority pair"); + + let authority = Sr25519Keyring::Alice; + let _keystore = create_keystore(authority); let epoch = Epoch { start_slot: 0.into(), - authorities: vec![(public.into(), 1)], + authorities: vec![(authority.public().into(), 1)], randomness: [0; 32], epoch_index: 1, duration: 100, @@ -894,3 +1026,85 @@ fn babe_transcript_generation_match() { }; debug_assert!(test(orig_transcript) == test(transcript_from_data(new_transcript))); } + +#[test] +fn obsolete_blocks_aux_data_cleanup() { + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); + + let peer = net.peer(0); + let data = peer.data.as_ref().expect("babe link set up during initialization"); + let client = peer.client().as_client(); + + // Register the handler (as done by `babe_start`) + let client_clone = client.clone(); + let on_finality = move |summary: &FinalityNotification| { + aux_storage_cleanup(client_clone.as_ref(), summary) + }; + client.register_finality_action(Box::new(on_finality)); + + let mut proposer_factory = DummyFactory { + client: client.clone(), + config: data.link.config.clone(), + epoch_changes: data.link.epoch_changes.clone(), + mutator: Arc::new(|_, _| ()), + }; + + let mut block_import = data.block_import.lock().take().expect("import set up during init"); + + let mut propose_and_import_blocks_wrap = |parent_id, n| { + propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + parent_id, + n, + &runtime, + ) + }; + + let aux_data_check = |hashes: &[Hash], expected: bool| { + hashes.iter().all(|hash| { + aux_schema::load_block_weight(&*peer.client().as_backend(), hash) + .unwrap() + .is_some() == expected + }) + }; + + // Create the following test scenario: + // + // /--- --B3 --- B4 ( < fork2 ) + // G --- A1 --- A2 --- A3 --- A4 ( < fork1 ) + // \-----C4 --- C5 ( < fork3 ) + + let fork1_hashes = propose_and_import_blocks_wrap(BlockId::Number(0), 4); + let fork2_hashes = propose_and_import_blocks_wrap(BlockId::Number(0), 2); + let fork3_hashes = propose_and_import_blocks_wrap(BlockId::Number(3), 2); + + // Check that aux data is present for all but the genesis block. + assert!(aux_data_check(&[client.chain_info().genesis_hash], false)); + assert!(aux_data_check(&fork1_hashes, true)); + assert!(aux_data_check(&fork2_hashes, true)); + assert!(aux_data_check(&fork3_hashes, true)); + + // Finalize A3 + client.finalize_block(fork1_hashes[2], None, true).unwrap(); + + // Wiped: A1, A2 + assert!(aux_data_check(&fork1_hashes[..2], false)); + // Present: A3, A4 + assert!(aux_data_check(&fork1_hashes[2..], true)); + // Wiped: B3, B4 + assert!(aux_data_check(&fork2_hashes, false)); + // Present C4, C5 + assert!(aux_data_check(&fork3_hashes, true)); + + client.finalize_block(fork1_hashes[3], None, true).unwrap(); + + // Wiped: A3 + assert!(aux_data_check(&fork1_hashes[2..3], false)); + // Present: A4 + assert!(aux_data_check(&fork1_hashes[3..], true)); + // Present C4, C5 + assert!(aux_data_check(&fork3_hashes, true)); +} diff --git a/client/consensus/babe/src/verification.rs b/client/consensus/babe/src/verification.rs index af118312dd07c..53ec3002e6a85 100644 --- a/client/consensus/babe/src/verification.rs +++ b/client/consensus/babe/src/verification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -31,8 +31,8 @@ use sp_consensus_babe::{ make_transcript, AuthorityId, AuthorityPair, AuthoritySignature, }; use sp_consensus_slots::Slot; -use sp_core::{Pair, Public}; -use sp_runtime::traits::{DigestItemFor, Header}; +use sp_core::{ByteArray, Pair}; +use sp_runtime::{traits::Header, DigestItem}; /// BABE verification parameters pub(super) struct VerificationParams<'a, B: 'a + BlockT> { @@ -61,10 +61,7 @@ pub(super) struct VerificationParams<'a, B: 'a + BlockT> { /// with each having different validation logic. pub(super) fn check_header( params: VerificationParams, -) -> Result>, Error> -where - DigestItemFor: CompatibleDigestItem, -{ +) -> Result, Error> { let VerificationParams { mut header, pre_digest, slot_now, epoch } = params; let authorities = &epoch.authorities; @@ -102,7 +99,7 @@ where primary.slot, ); - check_primary_header::(pre_hash, primary, sig, &epoch, epoch.config.c)?; + check_primary_header::(pre_hash, primary, sig, epoch, epoch.config.c)?; }, PreDigest::SecondaryPlain(secondary) if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() => @@ -113,8 +110,8 @@ where secondary.slot, ); - check_secondary_plain_header::(pre_hash, secondary, sig, &epoch)?; - } + check_secondary_plain_header::(pre_hash, secondary, sig, epoch)?; + }, PreDigest::SecondaryVRF(secondary) if epoch.config.allowed_slots.is_secondary_vrf_slots_allowed() => { @@ -124,8 +121,8 @@ where secondary.slot, ); - check_secondary_vrf_header::(pre_hash, secondary, sig, &epoch)?; - } + check_secondary_vrf_header::(pre_hash, secondary, sig, epoch)?; + }, _ => return Err(babe_err(Error::SecondarySlotAssignmentsDisabled)), } @@ -137,9 +134,9 @@ where Ok(CheckedHeader::Checked(header, info)) } -pub(super) struct VerifiedHeaderInfo { - pub(super) pre_digest: DigestItemFor, - pub(super) seal: DigestItemFor, +pub(super) struct VerifiedHeaderInfo { + pub(super) pre_digest: DigestItem, + pub(super) seal: DigestItem, pub(super) author: AuthorityId, } @@ -156,7 +153,7 @@ fn check_primary_header( ) -> Result<(), Error> { let author = &epoch.authorities[pre_digest.authority_index as usize].0; - if AuthorityPair::verify(&signature, pre_hash, &author) { + if AuthorityPair::verify(&signature, pre_hash, author) { let (inout, _) = { let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch.epoch_index); @@ -194,7 +191,7 @@ fn check_secondary_plain_header( // chain state. let expected_author = secondary_slot_author(pre_digest.slot, &epoch.authorities, epoch.randomness) - .ok_or_else(|| Error::NoSecondaryAuthorExpected)?; + .ok_or(Error::NoSecondaryAuthorExpected)?; let author = &epoch.authorities[pre_digest.authority_index as usize].0; @@ -220,7 +217,7 @@ fn check_secondary_vrf_header( // chain state. let expected_author = secondary_slot_author(pre_digest.slot, &epoch.authorities, epoch.randomness) - .ok_or_else(|| Error::NoSecondaryAuthorExpected)?; + .ok_or(Error::NoSecondaryAuthorExpected)?; let author = &epoch.authorities[pre_digest.authority_index as usize].0; diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index 6829bd2c6d8b5..b61c6a4334285 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-consensus" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Collection of common consensus specific imlementations for Substrate (client)" readme = "README.md" @@ -13,23 +13,24 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -thiserror = "1.0.21" -libp2p = { version = "0.39.1", default-features = false } -log = "0.4.8" -futures = { version = "0.3.1", features = ["thread-pool"] } +async-trait = "0.1.57" +futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" +libp2p = { version = "0.49.0", default-features = false } +log = "0.4.17" +mockall = "0.11.2" +parking_lot = "0.12.1" +serde = { version = "1.0", features = ["derive"] } +thiserror = "1.0.30" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { path = "../../../primitives/core", version = "4.0.0-dev" } -sp-consensus = { path = "../../../primitives/consensus/common", version = "0.10.0-dev" } -sp-state-machine = { version = "0.10.0-dev", path = "../../../primitives/state-machine" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } sc-utils = { version = "4.0.0-dev", path = "../../utils" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -parking_lot = "0.11.1" -serde = { version = "1.0", features = ["derive"] } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.9.0" } -async-trait = "0.1.42" +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } [dev-dependencies] sp-test-primitives = { version = "2.0.0", path = "../../../primitives/test-primitives" } diff --git a/client/consensus/common/src/block_import.rs b/client/consensus/common/src/block_import.rs index 6d411dd9afbf1..f888176addd2d 100644 --- a/client/consensus/common/src/block_import.rs +++ b/client/consensus/common/src/block_import.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,8 +20,8 @@ use serde::{Deserialize, Serialize}; use sp_runtime::{ - traits::{Block as BlockT, DigestItemFor, HashFor, Header as HeaderT, NumberFor}, - Justification, Justifications, + traits::{Block as BlockT, HashFor, Header as HeaderT, NumberFor}, + DigestItem, Justification, Justifications, }; use std::{any::Any, borrow::Cow, collections::HashMap, sync::Arc}; @@ -62,8 +62,7 @@ impl ImportResult { /// `clear_justification_requests`, `needs_justification`, /// `bad_justification` set to false. pub fn imported(is_new_best: bool) -> ImportResult { - let mut aux = ImportedAux::default(); - aux.is_new_best = is_new_best; + let aux = ImportedAux { is_new_best, ..Default::default() }; ImportResult::Imported(aux) } @@ -74,7 +73,7 @@ impl ImportResult { &self, hash: &B::Hash, number: NumberFor, - justification_sync_link: &mut dyn JustificationSyncLink, + justification_sync_link: &dyn JustificationSyncLink, ) where B: BlockT, { @@ -122,7 +121,7 @@ pub struct BlockCheckParams { /// Precomputed storage. pub enum StorageChanges { /// Changes coming from block execution. - Changes(sp_state_machine::StorageChanges, NumberFor>), + Changes(sp_state_machine::StorageChanges>), /// Whole new state. Import(ImportedState), } @@ -133,7 +132,7 @@ pub struct ImportedState { /// Target block hash. pub block: B::Hash, /// State keys and values. - pub state: Vec<(Vec, Vec)>, + pub state: sp_state_machine::KeyValueStates, } impl std::fmt::Debug for ImportedState { @@ -154,6 +153,18 @@ pub enum StateAction { Skip, } +impl StateAction { + /// Check if execution checks that require runtime calls should be skipped. + pub fn skip_execution_checks(&self) -> bool { + match self { + StateAction::ApplyChanges(_) | + StateAction::Execute | + StateAction::ExecuteIfPossible => false, + StateAction::Skip => true, + } + } +} + /// Data required to import a Block. #[non_exhaustive] pub struct BlockImportParams { @@ -175,7 +186,7 @@ pub struct BlockImportParams { pub justifications: Option, /// Digest items that have been added after the runtime for external /// work, like a consensus signature. - pub post_digests: Vec>, + pub post_digests: Vec, /// The body of the block. pub body: Option>, /// Indexed transaction body of the block. @@ -190,8 +201,9 @@ pub struct BlockImportParams { /// rejects block import if there are still intermediate values that remain unhandled. pub intermediates: HashMap, Box>, /// Auxiliary consensus data produced by the block. - /// Contains a list of key-value pairs. If values are `None`, the keys - /// will be deleted. + /// Contains a list of key-value pairs. If values are `None`, the keys will be deleted. These + /// changes will be applied to `AuxStore` database all as one batch, which is more efficient + /// than updating `AuxStore` directly. pub auxiliary: Vec<(Vec, Option>)>, /// Fork choice strategy of this import. This should only be set by a /// synchronous import, otherwise it may race against other imports. @@ -282,18 +294,23 @@ impl BlockImportParams { } } - /// Take intermediate by given key, and remove it from the processing list. - pub fn take_intermediate(&mut self, key: &[u8]) -> Result, Error> { + /// Insert intermediate by given key. + pub fn insert_intermediate(&mut self, key: &'static [u8], value: T) { + self.intermediates.insert(Cow::from(key), Box::new(value)); + } + + /// Remove and return intermediate by given key. + pub fn remove_intermediate(&mut self, key: &[u8]) -> Result { let (k, v) = self.intermediates.remove_entry(key).ok_or(Error::NoIntermediate)?; - v.downcast::().or_else(|v| { + v.downcast::().map(|v| *v).map_err(|v| { self.intermediates.insert(k, v); - Err(Error::InvalidIntermediate) + Error::InvalidIntermediate }) } /// Get a reference to a given intermediate. - pub fn intermediate(&self, key: &[u8]) -> Result<&T, Error> { + pub fn get_intermediate(&self, key: &[u8]) -> Result<&T, Error> { self.intermediates .get(key) .ok_or(Error::NoIntermediate)? @@ -302,7 +319,7 @@ impl BlockImportParams { } /// Get a mutable reference to a given intermediate. - pub fn intermediate_mut(&mut self, key: &[u8]) -> Result<&mut T, Error> { + pub fn get_intermediate_mut(&mut self, key: &[u8]) -> Result<&mut T, Error> { self.intermediates .get_mut(key) .ok_or(Error::NoIntermediate)? @@ -433,10 +450,10 @@ impl JustificationSyncLink for () { impl> JustificationSyncLink for Arc { fn request_justification(&self, hash: &B::Hash, number: NumberFor) { - L::request_justification(&*self, hash, number); + L::request_justification(self, hash, number); } fn clear_justification_requests(&self) { - L::clear_justification_requests(&*self); + L::clear_justification_requests(self); } } diff --git a/client/consensus/common/src/import_queue.rs b/client/consensus/common/src/import_queue.rs index 3f2126ccadf66..d49b240ef3489 100644 --- a/client/consensus/common/src/import_queue.rs +++ b/client/consensus/common/src/import_queue.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -53,6 +53,7 @@ pub type DefaultImportQueue = mod basic_queue; pub mod buffered_link; +pub mod mock; /// Shared block import struct used by the queue. pub type BoxBlockImport = @@ -62,8 +63,8 @@ pub type BoxBlockImport = pub type BoxJustificationImport = Box + Send + Sync>; -/// Maps to the Origin used by the network. -pub type Origin = libp2p::PeerId; +/// Maps to the RuntimeOrigin used by the network. +pub type RuntimeOrigin = libp2p::PeerId; /// Block data used by the queue. #[derive(Debug, PartialEq, Eq, Clone)] @@ -79,7 +80,7 @@ pub struct IncomingBlock { /// Justification(s) if requested. pub justifications: Option, /// The peer, we received this from - pub origin: Option, + pub origin: Option, /// Allow importing the block skipping state verification if parent state is missing. pub allow_missing_state: bool, /// Skip block execution and state verification. @@ -105,24 +106,38 @@ pub trait Verifier: Send + Sync { /// Blocks import queue API. /// /// The `import_*` methods can be called in order to send elements for the import queue to verify. -/// Afterwards, call `poll_actions` to determine how to respond to these elements. -pub trait ImportQueue: Send { +pub trait ImportQueueService: Send { /// Import bunch of blocks. fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>); + /// Import block justifications. fn import_justifications( &mut self, - who: Origin, + who: RuntimeOrigin, hash: B::Hash, number: NumberFor, justifications: Justifications, ); - /// Polls for actions to perform on the network. - /// +} + +#[async_trait::async_trait] +pub trait ImportQueue: Send { + /// Get a copy of the handle to [`ImportQueueService`]. + fn service(&self) -> Box>; + + /// Get a reference to the handle to [`ImportQueueService`]. + fn service_ref(&mut self) -> &mut dyn ImportQueueService; + /// This method should behave in a way similar to `Future::poll`. It can register the current /// task and notify later when more actions are ready to be polled. To continue the comparison, /// it is as if this method always returned `Poll::Pending`. fn poll_actions(&mut self, cx: &mut futures::task::Context, link: &mut dyn Link); + + /// Start asynchronous runner for import queue. + /// + /// Takes an object implementing [`Link`] which allows the import queue to + /// influece the synchronization process. + async fn run(self, link: Box>); } /// Hooks that the verification queue can use to influence the synchronization @@ -140,7 +155,7 @@ pub trait Link: Send { /// Justification import result. fn justification_imported( &mut self, - _who: Origin, + _who: RuntimeOrigin, _hash: &B::Hash, _number: NumberFor, _success: bool, @@ -155,27 +170,50 @@ pub trait Link: Send { #[derive(Debug, PartialEq)] pub enum BlockImportStatus { /// Imported known block. - ImportedKnown(N, Option), + ImportedKnown(N, Option), /// Imported unknown block. - ImportedUnknown(N, ImportedAux, Option), + ImportedUnknown(N, ImportedAux, Option), +} + +impl BlockImportStatus { + /// Returns the imported block number. + pub fn number(&self) -> &N { + match self { + BlockImportStatus::ImportedKnown(n, _) | + BlockImportStatus::ImportedUnknown(n, _, _) => n, + } + } } /// Block import error. -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum BlockImportError { /// Block missed header, can't be imported - IncompleteHeader(Option), + #[error("block is missing a header (origin = {0:?})")] + IncompleteHeader(Option), + /// Block verification failed, can't be imported - VerificationFailed(Option, String), + #[error("block verification failed (origin = {0:?}): {1}")] + VerificationFailed(Option, String), + /// Block is known to be Bad - BadBlock(Option), + #[error("bad block (origin = {0:?})")] + BadBlock(Option), + /// Parent state is missing. + #[error("block is missing parent state")] MissingState, + /// Block has an unknown parent + #[error("block has an unknown parent")] UnknownParent, + /// Block import has been cancelled. This can happen if the parent block fails to be imported. + #[error("import has been cancelled")] Cancelled, + /// Other error. + #[error("consensus error: {0}")] Other(ConsensusError), } @@ -219,17 +257,17 @@ pub(crate) async fn import_single_block_metered< trace!(target: "sync", "Header {} has {:?} logs", block.hash, header.digest().logs().len()); - let number = header.number().clone(); + let number = *header.number(); let hash = block.hash; - let parent_hash = header.parent_hash().clone(); + let parent_hash = *header.parent_hash(); let import_handler = |import| match import { Ok(ImportResult::AlreadyInChain) => { trace!(target: "sync", "Block already in chain {}: {:?}", number, hash); - Ok(BlockImportStatus::ImportedKnown(number, peer.clone())) + Ok(BlockImportStatus::ImportedKnown(number, peer)) }, Ok(ImportResult::Imported(aux)) => - Ok(BlockImportStatus::ImportedUnknown(number, aux, peer.clone())), + Ok(BlockImportStatus::ImportedUnknown(number, aux, peer)), Ok(ImportResult::MissingState) => { debug!(target: "sync", "Parent state is missing for {}: {:?}, parent: {:?}", number, hash, parent_hash); @@ -242,10 +280,10 @@ pub(crate) async fn import_single_block_metered< }, Ok(ImportResult::KnownBad) => { debug!(target: "sync", "Peer gave us a bad block {}: {:?}", number, hash); - Err(BlockImportError::BadBlock(peer.clone())) + Err(BlockImportError::BadBlock(peer)) }, Err(e) => { - debug!(target: "sync", "Error importing block {}: {:?}: {:?}", number, hash, e); + debug!(target: "sync", "Error importing block {}: {:?}: {}", number, hash, e); Err(BlockImportError::Other(e)) }, }; @@ -293,7 +331,7 @@ pub(crate) async fn import_single_block_metered< if let Some(metrics) = metrics.as_ref() { metrics.report_verification(false, started.elapsed()); } - BlockImportError::VerificationFailed(peer.clone(), msg) + BlockImportError::VerificationFailed(peer, msg) })?; if let Some(metrics) = metrics.as_ref() { diff --git a/client/consensus/common/src/import_queue/basic_queue.rs b/client/consensus/common/src/import_queue/basic_queue.rs index 9042c8798be4f..20e8d262cacda 100644 --- a/client/consensus/common/src/import_queue/basic_queue.rs +++ b/client/consensus/common/src/import_queue/basic_queue.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -34,7 +34,8 @@ use crate::{ import_queue::{ buffered_link::{self, BufferedLinkReceiver, BufferedLinkSender}, import_single_block_metered, BlockImportError, BlockImportStatus, BoxBlockImport, - BoxJustificationImport, ImportQueue, IncomingBlock, Link, Origin, Verifier, + BoxJustificationImport, ImportQueue, ImportQueueService, IncomingBlock, Link, + RuntimeOrigin, Verifier, }, metrics::Metrics, }; @@ -42,10 +43,8 @@ use crate::{ /// Interface to a basic block import queue that is importing blocks sequentially in a separate /// task, with plugable verification. pub struct BasicQueue { - /// Channel to send justification import messages to the background task. - justification_sender: TracingUnboundedSender>, - /// Channel to send block import messages to the background task. - block_import_sender: TracingUnboundedSender>, + /// Handle for sending justification and block import messages to the background task. + handle: BasicQueueHandle, /// Results coming from the worker task. result_port: BufferedLinkReceiver, _phantom: PhantomData, @@ -54,8 +53,7 @@ pub struct BasicQueue { impl Drop for BasicQueue { fn drop(&mut self) { // Flush the queue and close the receiver to terminate the future. - self.justification_sender.close_channel(); - self.block_import_sender.close_channel(); + self.handle.close(); self.result_port.close(); } } @@ -89,13 +87,43 @@ impl BasicQueue { metrics, ); - spawner.spawn_essential_blocking("basic-block-import-worker", future.boxed()); + spawner.spawn_essential_blocking( + "basic-block-import-worker", + Some("block-import"), + future.boxed(), + ); - Self { justification_sender, block_import_sender, result_port, _phantom: PhantomData } + Self { + handle: BasicQueueHandle::new(justification_sender, block_import_sender), + result_port, + _phantom: PhantomData, + } } } -impl ImportQueue for BasicQueue { +#[derive(Clone)] +struct BasicQueueHandle { + /// Channel to send justification import messages to the background task. + justification_sender: TracingUnboundedSender>, + /// Channel to send block import messages to the background task. + block_import_sender: TracingUnboundedSender>, +} + +impl BasicQueueHandle { + pub fn new( + justification_sender: TracingUnboundedSender>, + block_import_sender: TracingUnboundedSender>, + ) -> Self { + Self { justification_sender, block_import_sender } + } + + pub fn close(&mut self) { + self.justification_sender.close_channel(); + self.block_import_sender.close_channel(); + } +} + +impl ImportQueueService for BasicQueueHandle { fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>) { if blocks.is_empty() { return @@ -116,7 +144,7 @@ impl ImportQueue for BasicQueue fn import_justifications( &mut self, - who: Origin, + who: RuntimeOrigin, hash: B::Hash, number: NumberFor, justifications: Justifications, @@ -134,12 +162,39 @@ impl ImportQueue for BasicQueue } } } +} + +#[async_trait::async_trait] +impl ImportQueue for BasicQueue { + /// Get handle to [`ImportQueueService`]. + fn service(&self) -> Box> { + Box::new(self.handle.clone()) + } + + /// Get a reference to the handle to [`ImportQueueService`]. + fn service_ref(&mut self) -> &mut dyn ImportQueueService { + &mut self.handle + } + /// Poll actions from network. fn poll_actions(&mut self, cx: &mut Context, link: &mut dyn Link) { if self.result_port.poll_actions(cx, link).is_err() { log::error!(target: "sync", "poll_actions: Background import task is no longer alive"); } } + + /// Start asynchronous runner for import queue. + /// + /// Takes an object implementing [`Link`] which allows the import queue to + /// influece the synchronization process. + async fn run(mut self, mut link: Box>) { + loop { + if let Err(_) = self.result_port.next_action(&mut *link).await { + log::error!(target: "sync", "poll_actions: Background import task is no longer alive"); + return + } + } + } } /// Messages destinated to the background worker. @@ -148,7 +203,7 @@ mod worker_messages { pub struct ImportBlocks(pub BlockOrigin, pub Vec>); pub struct ImportJustification( - pub Origin, + pub RuntimeOrigin, pub B::Hash, pub NumberFor, pub Justification, @@ -285,7 +340,7 @@ impl BlockImportWorker { async fn import_justification( &mut self, - who: Origin, + who: RuntimeOrigin, hash: B::Hash, number: NumberFor, justification: Justification, @@ -299,11 +354,11 @@ impl BlockImportWorker { .map_err(|e| { debug!( target: "sync", - "Justification import failed with {:?} for hash: {:?} number: {:?} coming from node: {:?}", - e, + "Justification import failed for hash = {:?} with number = {:?} coming from node = {:?} with error: {}", hash, number, who, + e, ); e }) @@ -370,7 +425,7 @@ async fn import_many_blocks, Transaction: Send + 'stat }, }; - let block_number = block.header.as_ref().map(|h| h.number().clone()); + let block_number = block.header.as_ref().map(|h| *h.number()); let block_hash = block.hash; let import_result = if has_error { Err(BlockImportError::Cancelled) @@ -378,7 +433,7 @@ async fn import_many_blocks, Transaction: Send + 'stat // The actual import. import_single_block_metered( import_handle, - blocks_origin.clone(), + blocks_origin, block, verifier, metrics.clone(), @@ -526,12 +581,12 @@ mod tests { fn justification_imported( &mut self, - _who: Origin, + _who: RuntimeOrigin, hash: &Hash, _number: BlockNumber, _success: bool, ) { - self.events.push(Event::JustificationImported(hash.clone())) + self.events.push(Event::JustificationImported(*hash)) } } diff --git a/client/consensus/common/src/import_queue/buffered_link.rs b/client/consensus/common/src/import_queue/buffered_link.rs index 87ea6dde5c473..e6d3b212fdbac 100644 --- a/client/consensus/common/src/import_queue/buffered_link.rs +++ b/client/consensus/common/src/import_queue/buffered_link.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -38,7 +38,7 @@ //! }); //! ``` -use crate::import_queue::{Link, Origin}; +use crate::import_queue::{Link, RuntimeOrigin}; use futures::prelude::*; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_runtime::traits::{Block as BlockT, NumberFor}; @@ -80,9 +80,9 @@ impl Clone for BufferedLinkSender { } /// Internal buffered message. -enum BlockImportWorkerMsg { +pub enum BlockImportWorkerMsg { BlocksProcessed(usize, usize, Vec<(BlockImportResult, B::Hash)>), - JustificationImported(Origin, B::Hash, NumberFor, bool), + JustificationImported(RuntimeOrigin, B::Hash, NumberFor, bool), RequestJustification(B::Hash, NumberFor), } @@ -100,19 +100,19 @@ impl Link for BufferedLinkSender { fn justification_imported( &mut self, - who: Origin, + who: RuntimeOrigin, hash: &B::Hash, number: NumberFor, success: bool, ) { - let msg = BlockImportWorkerMsg::JustificationImported(who, hash.clone(), number, success); + let msg = BlockImportWorkerMsg::JustificationImported(who, *hash, number, success); let _ = self.tx.unbounded_send(msg); } fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { let _ = self .tx - .unbounded_send(BlockImportWorkerMsg::RequestJustification(hash.clone(), number)); + .unbounded_send(BlockImportWorkerMsg::RequestJustification(*hash, number)); } } @@ -122,6 +122,18 @@ pub struct BufferedLinkReceiver { } impl BufferedLinkReceiver { + /// Send action for the synchronization to perform. + pub fn send_actions(&mut self, msg: BlockImportWorkerMsg, link: &mut dyn Link) { + match msg { + BlockImportWorkerMsg::BlocksProcessed(imported, count, results) => + link.blocks_processed(imported, count, results), + BlockImportWorkerMsg::JustificationImported(who, hash, number, success) => + link.justification_imported(who, &hash, number, success), + BlockImportWorkerMsg::RequestJustification(hash, number) => + link.request_justification(&hash, number), + } + } + /// Polls for the buffered link actions. Any enqueued action will be propagated to the link /// passed as parameter. /// @@ -138,15 +150,17 @@ impl BufferedLinkReceiver { Poll::Pending => break Ok(()), }; - match msg { - BlockImportWorkerMsg::BlocksProcessed(imported, count, results) => - link.blocks_processed(imported, count, results), - BlockImportWorkerMsg::JustificationImported(who, hash, number, success) => - link.justification_imported(who, &hash, number, success), - BlockImportWorkerMsg::RequestJustification(hash, number) => - link.request_justification(&hash, number), - } + self.send_actions(msg, &mut *link); + } + } + + /// Poll next element from import queue and send the corresponding action command over the link. + pub async fn next_action(&mut self, link: &mut dyn Link) -> Result<(), ()> { + if let Some(msg) = self.rx.next().await { + self.send_actions(msg, link); + return Ok(()) } + Err(()) } /// Close the channel. diff --git a/client/consensus/common/src/import_queue/mock.rs b/client/consensus/common/src/import_queue/mock.rs new file mode 100644 index 0000000000000..67deee9514a1c --- /dev/null +++ b/client/consensus/common/src/import_queue/mock.rs @@ -0,0 +1,46 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; + +mockall::mock! { + pub ImportQueueHandle {} + + impl ImportQueueService for ImportQueueHandle { + fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>); + fn import_justifications( + &mut self, + who: RuntimeOrigin, + hash: B::Hash, + number: NumberFor, + justifications: Justifications, + ); + } +} + +mockall::mock! { + pub ImportQueue {} + + #[async_trait::async_trait] + impl ImportQueue for ImportQueue { + fn service(&self) -> Box>; + fn service_ref(&mut self) -> &mut dyn ImportQueueService; + fn poll_actions<'a>(&mut self, cx: &mut futures::task::Context<'a>, link: &mut dyn Link); + async fn run(self, link: Box>); + } +} diff --git a/client/consensus/common/src/lib.rs b/client/consensus/common/src/lib.rs index 640bad237e882..f291cc2704818 100644 --- a/client/consensus/common/src/lib.rs +++ b/client/consensus/common/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/consensus/common/src/longest_chain.rs b/client/consensus/common/src/longest_chain.rs index b1f7f94f9eb28..941cd4b944766 100644 --- a/client/consensus/common/src/longest_chain.rs +++ b/client/consensus/common/src/longest_chain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -79,23 +79,24 @@ where Block: BlockT, { async fn leaves(&self) -> Result::Hash>, ConsensusError> { - LongestChain::leaves(self).map_err(|e| ConsensusError::ChainLookup(e.to_string()).into()) + LongestChain::leaves(self).map_err(|e| ConsensusError::ChainLookup(e.to_string())) } async fn best_chain(&self) -> Result<::Header, ConsensusError> { - LongestChain::best_block_header(&self) - .map_err(|e| ConsensusError::ChainLookup(e.to_string()).into()) + LongestChain::best_block_header(self) + .map_err(|e| ConsensusError::ChainLookup(e.to_string())) } async fn finality_target( &self, target_hash: Block::Hash, maybe_max_number: Option>, - ) -> Result, ConsensusError> { + ) -> Result { let import_lock = self.backend.get_import_lock(); self.backend .blockchain() .best_containing(target_hash, maybe_max_number, import_lock) - .map_err(|e| ConsensusError::ChainLookup(e.to_string()).into()) + .map(|maybe_hash| maybe_hash.unwrap_or(target_hash)) + .map_err(|e| ConsensusError::ChainLookup(e.to_string())) } } diff --git a/client/consensus/common/src/metrics.rs b/client/consensus/common/src/metrics.rs index ade45e3ffb687..8314049238b88 100644 --- a/client/consensus/common/src/metrics.rs +++ b/client/consensus/common/src/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -41,28 +41,34 @@ impl Metrics { Ok(Self { import_queue_processed: register( CounterVec::new( - Opts::new("import_queue_processed_total", "Blocks processed by import queue"), + Opts::new( + "substrate_import_queue_processed_total", + "Blocks processed by import queue", + ), &["result"], // 'success or failure )?, registry, )?, block_verification_time: register( HistogramVec::new( - HistogramOpts::new("block_verification_time", "Time taken to verify blocks"), + HistogramOpts::new( + "substrate_block_verification_time", + "Time taken to verify blocks", + ), &["result"], )?, registry, )?, block_verification_and_import_time: register( Histogram::with_opts(HistogramOpts::new( - "block_verification_and_import_time", + "substrate_block_verification_and_import_time", "Time taken to verify and import blocks", ))?, registry, )?, justification_import_time: register( Histogram::with_opts(HistogramOpts::new( - "justification_import_time", + "substrate_justification_import_time", "Time taken to import justifications", ))?, registry, diff --git a/client/consensus/common/src/shared_data.rs b/client/consensus/common/src/shared_data.rs index 7a25660e08aac..fb04966b5db61 100644 --- a/client/consensus/common/src/shared_data.rs +++ b/client/consensus/common/src/shared_data.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -167,6 +167,13 @@ struct SharedDataInner { /// // As we don't know the order of the threads, we need to check for both combinations /// assert!(*data == "hello world321" || *data == "hello world312"); /// ``` +/// +/// # Deadlock +/// +/// Be aware that this data structure doesn't give you any guarantees that you can not create a +/// deadlock. If you use [`release_mutex`](SharedDataLocked::release_mutex) followed by a call +/// to [`shared_data`](Self::shared_data) in the same thread will make your program dead lock. +/// The same applies when you are using a single threaded executor. pub struct SharedData { inner: Arc>>, cond_var: Arc, diff --git a/client/consensus/epochs/Cargo.toml b/client/consensus/epochs/Cargo.toml index 78e5cc31ea07e..c88b5c52ba18e 100644 --- a/client/consensus/epochs/Cargo.toml +++ b/client/consensus/epochs/Cargo.toml @@ -3,9 +3,9 @@ name = "sc-consensus-epochs" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Generic epochs-based utilities for consensus" -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,9 +13,9 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } -sp-runtime = { path = "../../../primitives/runtime" , version = "4.0.0-dev"} +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../common" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sc-client-api = { path = "../../api" , version = "4.0.0-dev"} -sc-consensus = { path = "../common" , version = "0.10.0-dev"} +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/client/consensus/epochs/src/lib.rs b/client/consensus/epochs/src/lib.rs index f3cfc55bae69b..c213a49b8e4e4 100644 --- a/client/consensus/epochs/src/lib.rs +++ b/client/consensus/epochs/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ pub mod migration; use codec::{Decode, Encode}; -use fork_tree::ForkTree; +use fork_tree::{FilterAction, ForkTree}; use sc_client_api::utils::is_descendent_of; use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; use sp_runtime::traits::{Block as BlockT, NumberFor, One, Zero}; @@ -78,11 +78,11 @@ where /// /// Once an epoch is created, it must have a known `start_slot` and `end_slot`, which cannot be /// changed. Consensus engine may modify any other data in the epoch, if needed. -pub trait Epoch { +pub trait Epoch: std::fmt::Debug { /// Descriptor for the next epoch. type NextEpochDescriptor; /// Type of the slot number. - type Slot: Ord + Copy; + type Slot: Ord + Copy + std::fmt::Debug; /// The starting slot of the epoch. fn start_slot(&self) -> Self::Slot; @@ -228,14 +228,21 @@ impl ViableEpochDescriptor { } /// Persisted epoch stored in EpochChanges. -#[derive(Clone, Encode, Decode)] -pub enum PersistedEpoch { +#[derive(Clone, Encode, Decode, Debug)] +pub enum PersistedEpoch { /// Genesis persisted epoch data. epoch_0, epoch_1. Genesis(E, E), /// Regular persisted epoch data. epoch_n. Regular(E), } +impl PersistedEpoch { + /// Returns if this is a genesis epoch. + pub fn is_genesis(&self) -> bool { + matches!(self, Self::Genesis(_, _)) + } +} + impl<'a, E: Epoch> From<&'a PersistedEpoch> for PersistedEpochHeader { fn from(epoch: &'a PersistedEpoch) -> Self { match epoch { @@ -246,8 +253,23 @@ impl<'a, E: Epoch> From<&'a PersistedEpoch> for PersistedEpochHeader { } } +impl PersistedEpoch { + /// Map the epoch to a different type using a conversion function. + pub fn map(self, h: &Hash, n: &Number, f: &mut F) -> PersistedEpoch + where + B: Epoch, + F: FnMut(&Hash, &Number, E) -> B, + { + match self { + PersistedEpoch::Genesis(epoch_0, epoch_1) => + PersistedEpoch::Genesis(f(h, n, epoch_0), f(h, n, epoch_1)), + PersistedEpoch::Regular(epoch_n) => PersistedEpoch::Regular(f(h, n, epoch_n)), + } + } +} + /// Persisted epoch header stored in ForkTree. -#[derive(Encode, Decode, PartialEq, Eq)] +#[derive(Encode, Decode, PartialEq, Eq, Debug)] pub enum PersistedEpochHeader { /// Genesis persisted epoch header. epoch_0, epoch_1. Genesis(EpochHeader, EpochHeader), @@ -264,6 +286,25 @@ impl Clone for PersistedEpochHeader { } } +impl PersistedEpochHeader { + /// Map the epoch header to a different type. + pub fn map(self) -> PersistedEpochHeader + where + B: Epoch, + { + match self { + PersistedEpochHeader::Genesis(epoch_0, epoch_1) => PersistedEpochHeader::Genesis( + EpochHeader { start_slot: epoch_0.start_slot, end_slot: epoch_0.end_slot }, + EpochHeader { start_slot: epoch_1.start_slot, end_slot: epoch_1.end_slot }, + ), + PersistedEpochHeader::Regular(epoch_n) => PersistedEpochHeader::Regular(EpochHeader { + start_slot: epoch_n.start_slot, + end_slot: epoch_n.end_slot, + }), + } + } +} + /// A fresh, incremented epoch to import into the underlying fork-tree. /// /// Create this with `ViableEpoch::increment`. @@ -279,6 +320,106 @@ impl AsRef for IncrementedEpoch { } } +/// A pair of epochs for the gap block download validation. +/// Block gap is created after the warp sync is complete. Blocks +/// are imported both at the tip of the chain and at the start of the gap. +/// This holds a pair of epochs that are required to validate headers +/// at the start of the gap. Since gap download does not allow forks we don't +/// need to keep a tree of epochs. +#[derive(Clone, Encode, Decode, Debug)] +pub struct GapEpochs { + current: (Hash, Number, PersistedEpoch), + next: Option<(Hash, Number, E)>, +} + +impl GapEpochs +where + Hash: Copy + PartialEq + std::fmt::Debug, + Number: Copy + PartialEq + std::fmt::Debug, + E: Epoch, +{ + /// Check if given slot matches one of the gap epochs. + /// Returns epoch identifier if it does. + fn matches( + &self, + slot: E::Slot, + ) -> Option<(Hash, Number, EpochHeader, EpochIdentifierPosition)> { + match &self.current { + (_, _, PersistedEpoch::Genesis(epoch_0, _)) + if slot >= epoch_0.start_slot() && slot < epoch_0.end_slot() => + return Some(( + self.current.0, + self.current.1, + epoch_0.into(), + EpochIdentifierPosition::Genesis0, + )), + (_, _, PersistedEpoch::Genesis(_, epoch_1)) + if slot >= epoch_1.start_slot() && slot < epoch_1.end_slot() => + return Some(( + self.current.0, + self.current.1, + epoch_1.into(), + EpochIdentifierPosition::Genesis1, + )), + (_, _, PersistedEpoch::Regular(epoch_n)) + if slot >= epoch_n.start_slot() && slot < epoch_n.end_slot() => + return Some(( + self.current.0, + self.current.1, + epoch_n.into(), + EpochIdentifierPosition::Regular, + )), + _ => {}, + }; + match &self.next { + Some((h, n, epoch_n)) if slot >= epoch_n.start_slot() && slot < epoch_n.end_slot() => + Some((*h, *n, epoch_n.into(), EpochIdentifierPosition::Regular)), + _ => None, + } + } + + /// Returns epoch data if it matches given identifier. + pub fn epoch(&self, id: &EpochIdentifier) -> Option<&E> { + match (&self.current, &self.next) { + ((h, n, e), _) if h == &id.hash && n == &id.number => match e { + PersistedEpoch::Genesis(ref epoch_0, _) + if id.position == EpochIdentifierPosition::Genesis0 => + Some(epoch_0), + PersistedEpoch::Genesis(_, ref epoch_1) + if id.position == EpochIdentifierPosition::Genesis1 => + Some(epoch_1), + PersistedEpoch::Regular(ref epoch_n) + if id.position == EpochIdentifierPosition::Regular => + Some(epoch_n), + _ => None, + }, + (_, Some((h, n, e))) + if h == &id.hash && + n == &id.number && id.position == EpochIdentifierPosition::Regular => + Some(e), + _ => None, + } + } + + /// Import a new gap epoch, potentially replacing an old epoch. + fn import(&mut self, slot: E::Slot, hash: Hash, number: Number, epoch: E) -> Result<(), E> { + match (&mut self.current, &mut self.next) { + ((_, _, PersistedEpoch::Genesis(_, epoch_1)), _) if slot == epoch_1.end_slot() => { + self.next = Some((hash, number, epoch)); + Ok(()) + }, + (_, Some((_, _, epoch_n))) if slot == epoch_n.end_slot() => { + let (cur_h, cur_n, cur_epoch) = + self.next.take().expect("Already matched as `Some`"); + self.current = (cur_h, cur_n, PersistedEpoch::Regular(cur_epoch)); + self.next = Some((hash, number, epoch)); + Ok(()) + }, + _ => Err(epoch), + } + } +} + /// Tree of all epoch changes across all *seen* forks. Data stored in tree is /// the hash and block number of the block signaling the epoch change, and the /// epoch that was signalled at that block. @@ -294,10 +435,14 @@ impl AsRef for IncrementedEpoch { /// same DAG entry, pinned to a specific block #1. /// /// Further epochs (epoch_2, ..., epoch_n) each get their own entry. -#[derive(Clone, Encode, Decode)] +/// +/// Also maintains a pair of epochs for the start of the gap, +/// as long as there's an active gap download after a warp sync. +#[derive(Clone, Encode, Decode, Debug)] pub struct EpochChanges { inner: ForkTree>, epochs: BTreeMap<(Hash, Number), PersistedEpoch>, + gap: Option>, } // create a fake header hash which hasn't been included in the chain. @@ -315,14 +460,14 @@ where Number: Ord, { fn default() -> Self { - EpochChanges { inner: ForkTree::new(), epochs: BTreeMap::new() } + EpochChanges { inner: ForkTree::new(), epochs: BTreeMap::new(), gap: None } } } impl EpochChanges where - Hash: PartialEq + Ord + AsRef<[u8]> + AsMut<[u8]> + Copy, - Number: Ord + One + Zero + Add + Sub + Copy, + Hash: PartialEq + Ord + AsRef<[u8]> + AsMut<[u8]> + Copy + std::fmt::Debug, + Number: Ord + One + Zero + Add + Sub + Copy + std::fmt::Debug, { /// Create a new epoch change. pub fn new() -> Self { @@ -335,6 +480,11 @@ where self.inner.rebalance() } + /// Clear gap epochs if any. + pub fn clear_gap(&mut self) { + self.gap = None; + } + /// Map the epoch changes from one storing data to a different one. pub fn map(self, mut f: F) -> EpochChanges where @@ -342,31 +492,15 @@ where F: FnMut(&Hash, &Number, E) -> B, { EpochChanges { - inner: self.inner.map(&mut |_, _, header| match header { - PersistedEpochHeader::Genesis(epoch_0, epoch_1) => PersistedEpochHeader::Genesis( - EpochHeader { start_slot: epoch_0.start_slot, end_slot: epoch_0.end_slot }, - EpochHeader { start_slot: epoch_1.start_slot, end_slot: epoch_1.end_slot }, - ), - PersistedEpochHeader::Regular(epoch_n) => - PersistedEpochHeader::Regular(EpochHeader { - start_slot: epoch_n.start_slot, - end_slot: epoch_n.end_slot, - }), + inner: self.inner.map(&mut |_, _, header: PersistedEpochHeader| header.map()), + gap: self.gap.map(|GapEpochs { current: (h, n, header), next }| GapEpochs { + current: (h, n, header.map(&h, &n, &mut f)), + next: next.map(|(h, n, e)| (h, n, f(&h, &n, e))), }), epochs: self .epochs .into_iter() - .map(|((hash, number), epoch)| { - let bepoch = match epoch { - PersistedEpoch::Genesis(epoch_0, epoch_1) => PersistedEpoch::Genesis( - f(&hash, &number, epoch_0), - f(&hash, &number, epoch_1), - ), - PersistedEpoch::Regular(epoch_n) => - PersistedEpoch::Regular(f(&hash, &number, epoch_n)), - }; - ((hash, number), bepoch) - }) + .map(|((hash, number), epoch)| ((hash, number), epoch.map(&hash, &number, &mut f))) .collect(), } } @@ -384,13 +518,13 @@ where let is_descendent_of = descendent_of_builder.build_is_descendent_of(None); let predicate = |epoch: &PersistedEpochHeader| match *epoch { - PersistedEpochHeader::Genesis(_, ref epoch_1) => slot >= epoch_1.end_slot, - PersistedEpochHeader::Regular(ref epoch_n) => slot >= epoch_n.end_slot, + PersistedEpochHeader::Genesis(_, ref epoch_1) => epoch_1.start_slot <= slot, + PersistedEpochHeader::Regular(ref epoch_n) => epoch_n.start_slot <= slot, }; - // prune any epochs which could not be _live_ as of the children of the + // Prune any epochs which could not be _live_ as of the children of the // finalized block, i.e. re-root the fork tree to the oldest ancestor of - // (hash, number) where epoch.end_slot() >= finalized_slot + // (hash, number) where `epoch.start_slot() <= finalized_slot`. let removed = self.inner.prune(hash, &number, &is_descendent_of, &predicate)?; for (hash, number, _) in removed { @@ -402,6 +536,9 @@ where /// Get a reference to an epoch with given identifier. pub fn epoch(&self, id: &EpochIdentifier) -> Option<&E> { + if let Some(e) = &self.gap.as_ref().and_then(|gap| gap.epoch(id)) { + return Some(e) + } self.epochs.get(&(id.hash, id.number)).and_then(|v| match v { PersistedEpoch::Genesis(ref epoch_0, _) if id.position == EpochIdentifierPosition::Genesis0 => @@ -429,7 +566,7 @@ where ViableEpochDescriptor::UnimportedGenesis(slot) => Some(ViableEpoch::UnimportedGenesis(make_genesis(*slot))), ViableEpochDescriptor::Signaled(identifier, _) => - self.epoch(&identifier).map(ViableEpoch::Signaled), + self.epoch(identifier).map(ViableEpoch::Signaled), } } @@ -462,7 +599,7 @@ where ViableEpochDescriptor::UnimportedGenesis(slot) => Some(ViableEpoch::UnimportedGenesis(make_genesis(*slot))), ViableEpochDescriptor::Signaled(identifier, _) => - self.epoch_mut(&identifier).map(ViableEpoch::Signaled), + self.epoch_mut(identifier).map(ViableEpoch::Signaled), } } @@ -481,7 +618,7 @@ where { match descriptor { ViableEpochDescriptor::UnimportedGenesis(slot) => Some(make_genesis(*slot)), - ViableEpochDescriptor::Signaled(identifier, _) => self.epoch(&identifier).cloned(), + ViableEpochDescriptor::Signaled(identifier, _) => self.epoch(identifier).cloned(), } } @@ -523,6 +660,20 @@ where parent_number: Number, slot: E::Slot, ) -> Result>, fork_tree::Error> { + if parent_number == Zero::zero() { + // need to insert the genesis epoch. + return Ok(Some(ViableEpochDescriptor::UnimportedGenesis(slot))) + } + + if let Some(gap) = &self.gap { + if let Some((hash, number, hdr, position)) = gap.matches(slot) { + return Ok(Some(ViableEpochDescriptor::Signaled( + EpochIdentifier { position, hash, number }, + hdr, + ))) + } + } + // find_node_where will give you the node in the fork-tree which is an ancestor // of the `parent_hash` by default. if the last epoch was signalled at the parent_hash, // then it won't be returned. we need to create a new fake chain head hash which @@ -532,11 +683,6 @@ where let is_descendent_of = descendent_of_builder.build_is_descendent_of(Some((fake_head_hash, *parent_hash))); - if parent_number == Zero::zero() { - // need to insert the genesis epoch. - return Ok(Some(ViableEpochDescriptor::UnimportedGenesis(slot))) - } - // We want to find the deepest node in the tree which is an ancestor // of our block and where the start slot of the epoch was before the // slot of our block. The genesis special-case doesn't need to look @@ -598,24 +744,39 @@ where ) -> Result<(), fork_tree::Error> { let is_descendent_of = descendent_of_builder.build_is_descendent_of(Some((hash, parent_hash))); - let header = PersistedEpochHeader::::from(&epoch.0); + let slot = epoch.as_ref().start_slot(); + let IncrementedEpoch(mut epoch) = epoch; + let header = PersistedEpochHeader::::from(&epoch); + + if let Some(gap) = &mut self.gap { + if let PersistedEpoch::Regular(e) = epoch { + epoch = match gap.import(slot, hash, number, e) { + Ok(()) => return Ok(()), + Err(e) => PersistedEpoch::Regular(e), + } + } + } else if epoch.is_genesis() && + !self.epochs.is_empty() && + !self.epochs.values().any(|e| e.is_genesis()) + { + // There's a genesis epoch imported when we already have an active epoch. + // This happens after the warp sync as the ancient blocks download start. + // We need to start tracking gap epochs here. + self.gap = Some(GapEpochs { current: (hash, number, epoch), next: None }); + return Ok(()) + } let res = self.inner.import(hash, number, header, &is_descendent_of); match res { Ok(_) | Err(fork_tree::Error::Duplicate) => { - self.epochs.insert((hash, number), epoch.0); + self.epochs.insert((hash, number), epoch); Ok(()) }, Err(e) => Err(e), } } - /// Return the inner fork tree. - pub fn tree(&self) -> &ForkTree> { - &self.inner - } - /// Reset to a specified pair of epochs, as if they were announced at blocks `parent_hash` and /// `hash`. pub fn reset(&mut self, parent_hash: Hash, hash: Hash, number: Number, current: E, next: E) { @@ -635,6 +796,42 @@ where }); self.epochs.insert((hash, number), persisted); } + + /// Revert to a specified block given its `hash` and `number`. + /// This removes all the epoch changes information that were announced by + /// all the given block descendents. + pub fn revert>( + &mut self, + descendent_of_builder: D, + hash: Hash, + number: Number, + ) { + let is_descendent_of = descendent_of_builder.build_is_descendent_of(None); + + let filter = |node_hash: &Hash, node_num: &Number, _: &PersistedEpochHeader| { + if number >= *node_num && + (is_descendent_of(node_hash, &hash).unwrap_or_default() || *node_hash == hash) + { + // Continue the search in this subtree. + FilterAction::KeepNode + } else if number < *node_num && is_descendent_of(&hash, node_hash).unwrap_or_default() { + // Found a node to be removed. + FilterAction::Remove + } else { + // Not a parent or child of the one we're looking for, stop processing this branch. + FilterAction::KeepTree + } + }; + + self.inner.drain_filter(filter).for_each(|(h, n, _)| { + self.epochs.remove(&(h, n)); + }); + } + + /// Return the inner fork tree (mostly useful for testing) + pub fn tree(&self) -> &ForkTree> { + &self.inner + } } /// Type alias to produce the epoch-changes tree from a block type. @@ -866,7 +1063,7 @@ mod tests { let incremented_epoch = epoch_changes .viable_epoch(&genesis_epoch_a_descriptor, &make_genesis) .unwrap() - .increment(next_descriptor.clone()); + .increment(next_descriptor); epoch_changes .import(&is_descendent_of, *b"A", 1, *b"0", incremented_epoch) @@ -883,7 +1080,7 @@ mod tests { let incremented_epoch = epoch_changes .viable_epoch(&genesis_epoch_x_descriptor, &make_genesis) .unwrap() - .increment(next_descriptor.clone()); + .increment(next_descriptor); epoch_changes .import(&is_descendent_of, *b"X", 1, *b"0", incremented_epoch) @@ -916,4 +1113,453 @@ mod tests { assert!(epoch_for_x_child_before_genesis.is_none()); } } + + #[test] + fn prune_removes_stale_nodes() { + // +---D +-------F + // | | + // 0---A---B--(x)--C--(y)--G + // | | + // +---H +-------E + // + // Test parameters: + // - epoch duration: 100 + // + // We are going to prune the tree at: + // - 'x', a node between B and C + // - 'y', a node between C and G + + let is_descendent_of = |base: &Hash, block: &Hash| -> Result { + match (base, block) { + (b"0", _) => Ok(true), + (b"A", b) => Ok(b != b"0"), + (b"B", b) => Ok(b != b"0" && b != b"A" && b != b"D"), + (b"C", b) => Ok(b == b"F" || b == b"G" || b == b"y"), + (b"x", b) => Ok(b == b"C" || b == b"F" || b == b"G" || b == b"y"), + (b"y", b) => Ok(b == b"G"), + _ => Ok(false), + } + }; + + let mut epoch_changes = EpochChanges::new(); + + let mut import_at = |slot, hash: &Hash, number, parent_hash, parent_number| { + let make_genesis = |slot| Epoch { start_slot: slot, duration: 100 }; + // Get epoch descriptor valid for 'slot' + let epoch_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, parent_hash, parent_number, slot) + .unwrap() + .unwrap(); + // Increment it + let next_epoch_desc = epoch_changes + .viable_epoch(&epoch_descriptor, &make_genesis) + .unwrap() + .increment(()); + // Assign it to hash/number + epoch_changes + .import(&is_descendent_of, *hash, number, *parent_hash, next_epoch_desc) + .unwrap(); + }; + + import_at(100, b"A", 10, b"0", 0); + import_at(200, b"B", 20, b"A", 10); + import_at(300, b"C", 30, b"B", 20); + import_at(200, b"D", 20, b"A", 10); + import_at(300, b"E", 30, b"B", 20); + import_at(400, b"F", 40, b"C", 30); + import_at(400, b"G", 40, b"C", 30); + import_at(100, b"H", 10, b"0", 0); + + let mut nodes: Vec<_> = epoch_changes.tree().iter().map(|(h, _, _)| h).collect(); + nodes.sort(); + assert_eq!(nodes, vec![b"A", b"B", b"C", b"D", b"E", b"F", b"G", b"H"]); + + // Finalize block 'x' @ number 25, slot 230 + // This should prune all nodes imported by blocks with a number < 25 that are not + // ancestors of 'x' and all nodes before the one holding the epoch information + // to which 'x' belongs to (i.e. before A). + + epoch_changes.prune_finalized(&is_descendent_of, b"x", 25, 230).unwrap(); + + let mut nodes: Vec<_> = epoch_changes.tree().iter().map(|(h, _, _)| h).collect(); + nodes.sort(); + assert_eq!(nodes, vec![b"A", b"B", b"C", b"E", b"F", b"G"]); + + // Finalize block y @ number 35, slot 330 + // This should prune all nodes imported by blocks with a number < 35 that are not + // ancestors of 'y' and all nodes before the one holding the epoch information + // to which 'y' belongs to (i.e. before B). + + epoch_changes.prune_finalized(&is_descendent_of, b"y", 35, 330).unwrap(); + + let mut nodes: Vec<_> = epoch_changes.tree().iter().map(|(h, _, _)| h).collect(); + nodes.sort(); + assert_eq!(nodes, vec![b"B", b"C", b"F", b"G"]); + } + + #[test] + fn near_genesis_prune_works() { + // [X]: announces next epoch change (i.e. adds a node in the epoch changes tree) + // + // 0--[A]--B--C--D--E--[G]--H--I--J--K--[L] + // + + // \--[F] + + let is_descendent_of = |base: &Hash, block: &Hash| -> Result { + match (block, base) { + | (b"A", b"0") | + (b"B", b"0" | b"A") | + (b"C", b"0" | b"A" | b"B") | + (b"D", b"0" | b"A" | b"B" | b"C") | + (b"E", b"0" | b"A" | b"B" | b"C" | b"D") | + (b"F", b"0" | b"A" | b"B" | b"C" | b"D" | b"E") | + (b"G", b"0" | b"A" | b"B" | b"C" | b"D" | b"E") | + (b"H", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G") | + (b"I", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H") | + (b"J", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H" | b"I") | + (b"K", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H" | b"I" | b"J") | + ( + b"L", + b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H" | b"I" | b"J" | b"K", + ) => Ok(true), + _ => Ok(false), + } + }; + + let mut epoch_changes = EpochChanges::new(); + + let epoch = Epoch { start_slot: 278183811, duration: 5 }; + let epoch = PersistedEpoch::Genesis(epoch.clone(), epoch.increment(())); + + epoch_changes + .import(&is_descendent_of, *b"A", 1, Default::default(), IncrementedEpoch(epoch)) + .unwrap(); + + let import_at = |epoch_changes: &mut EpochChanges<_, _, Epoch>, + slot, + hash: &Hash, + number, + parent_hash, + parent_number| { + let make_genesis = |slot| Epoch { start_slot: slot, duration: 5 }; + // Get epoch descriptor valid for 'slot' + let epoch_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, parent_hash, parent_number, slot) + .unwrap() + .unwrap(); + // Increment it + let next_epoch_desc = epoch_changes + .viable_epoch(&epoch_descriptor, &make_genesis) + .unwrap() + .increment(()); + // Assign it to hash/number + epoch_changes + .import(&is_descendent_of, *hash, number, *parent_hash, next_epoch_desc) + .unwrap(); + }; + + // Should not prune anything + epoch_changes.prune_finalized(&is_descendent_of, b"C", 3, 278183813).unwrap(); + + import_at(&mut epoch_changes, 278183816, b"G", 6, b"E", 5); + import_at(&mut epoch_changes, 278183816, b"F", 6, b"E", 5); + + // Should not prune anything since we are on epoch0 + epoch_changes.prune_finalized(&is_descendent_of, b"C", 3, 278183813).unwrap(); + let mut list: Vec<_> = epoch_changes.inner.iter().map(|e| e.0).collect(); + list.sort(); + assert_eq!(list, vec![b"A", b"F", b"G"]); + + import_at(&mut epoch_changes, 278183821, b"L", 11, b"K", 10); + + // Should prune any fork of our ancestor not in the canonical chain (i.e. "F") + epoch_changes.prune_finalized(&is_descendent_of, b"J", 9, 278183819).unwrap(); + let mut list: Vec<_> = epoch_changes.inner.iter().map(|e| e.0).collect(); + list.sort(); + assert_eq!(list, vec![b"A", b"G", b"L"]); + } + + /// Test that ensures that the gap is not enabled when we import multiple genesis blocks. + #[test] + fn gap_is_not_enabled_when_multiple_genesis_epochs_are_imported() { + // X + // / + // 0 - A + // + let is_descendent_of = |base: &Hash, block: &Hash| -> Result { + match (base, *block) { + (b"0", _) => Ok(true), + _ => Ok(false), + } + }; + + let duration = 100; + + let make_genesis = |slot| Epoch { start_slot: slot, duration }; + + let mut epoch_changes = EpochChanges::new(); + let next_descriptor = (); + + // insert genesis epoch for A + { + let genesis_epoch_a_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 100) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&genesis_epoch_a_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor); + + epoch_changes + .import(&is_descendent_of, *b"A", 1, *b"0", incremented_epoch) + .unwrap(); + } + + // insert genesis epoch for X + { + let genesis_epoch_x_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 1000) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&genesis_epoch_x_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor); + + epoch_changes + .import(&is_descendent_of, *b"X", 1, *b"0", incremented_epoch) + .unwrap(); + } + + // Clearing the gap should be a no-op. + epoch_changes.clear_gap(); + + // Check that both epochs are available. + let epoch_a = epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"A", 1, 101, &make_genesis) + .unwrap() + .unwrap(); + + let epoch_x = epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"X", 1, 1001, &make_genesis) + .unwrap() + .unwrap(); + + assert!(epoch_a != epoch_x) + } + + #[test] + fn gap_epochs_advance() { + // 0 - 1 - 2 - 3 - .... 42 - 43 + let is_descendent_of = |base: &Hash, block: &Hash| -> Result { + match (base, *block) { + (b"0", _) => Ok(true), + (b"1", b) => Ok(b == *b"0"), + (b"2", b) => Ok(b == *b"1"), + (b"3", b) => Ok(b == *b"2"), + _ => Ok(false), + } + }; + + let duration = 100; + + let make_genesis = |slot| Epoch { start_slot: slot, duration }; + + let mut epoch_changes = EpochChanges::new(); + let next_descriptor = (); + + let epoch42 = Epoch { start_slot: 42, duration: 100 }; + let epoch43 = Epoch { start_slot: 43, duration: 100 }; + epoch_changes.reset(*b"0", *b"1", 4200, epoch42, epoch43); + assert!(epoch_changes.gap.is_none()); + + // Import a new genesis epoch, this should crate the gap. + let genesis_epoch_a_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 100) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&genesis_epoch_a_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor); + + epoch_changes + .import(&is_descendent_of, *b"1", 1, *b"0", incremented_epoch) + .unwrap(); + assert!(epoch_changes.gap.is_some()); + + let genesis_epoch = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 100) + .unwrap() + .unwrap(); + + assert_eq!(genesis_epoch, ViableEpochDescriptor::UnimportedGenesis(100)); + + // Import more epochs and check that gap advances. + let import_epoch_1 = + epoch_changes.viable_epoch(&genesis_epoch, &make_genesis).unwrap().increment(()); + + let epoch_1 = import_epoch_1.as_ref().clone(); + epoch_changes + .import(&is_descendent_of, *b"1", 1, *b"0", import_epoch_1) + .unwrap(); + let genesis_epoch_data = epoch_changes.epoch_data(&genesis_epoch, &make_genesis).unwrap(); + let end_slot = genesis_epoch_data.end_slot(); + let x = epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"1", 1, end_slot, &make_genesis) + .unwrap() + .unwrap(); + + assert_eq!(x, epoch_1); + assert_eq!(epoch_changes.gap.as_ref().unwrap().current.0, *b"1"); + assert!(epoch_changes.gap.as_ref().unwrap().next.is_none()); + + let epoch_1_desriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"1", 1, end_slot) + .unwrap() + .unwrap(); + let epoch_1 = epoch_changes.epoch_data(&epoch_1_desriptor, &make_genesis).unwrap(); + let import_epoch_2 = epoch_changes + .viable_epoch(&epoch_1_desriptor, &make_genesis) + .unwrap() + .increment(()); + let epoch_2 = import_epoch_2.as_ref().clone(); + epoch_changes + .import(&is_descendent_of, *b"2", 2, *b"1", import_epoch_2) + .unwrap(); + + let end_slot = epoch_1.end_slot(); + let x = epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"2", 2, end_slot, &make_genesis) + .unwrap() + .unwrap(); + assert_eq!(epoch_changes.gap.as_ref().unwrap().current.0, *b"1"); + assert_eq!(epoch_changes.gap.as_ref().unwrap().next.as_ref().unwrap().0, *b"2"); + assert_eq!(x, epoch_2); + + let epoch_2_desriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"2", 2, end_slot) + .unwrap() + .unwrap(); + let import_epoch_3 = epoch_changes + .viable_epoch(&epoch_2_desriptor, &make_genesis) + .unwrap() + .increment(()); + epoch_changes + .import(&is_descendent_of, *b"3", 3, *b"2", import_epoch_3) + .unwrap(); + + assert_eq!(epoch_changes.gap.as_ref().unwrap().current.0, *b"2"); + + epoch_changes.clear_gap(); + assert!(epoch_changes.gap.is_none()); + } + + /// Test that ensures that the gap is not enabled when there's still genesis + /// epochs imported, regardless of whether there are already other further + /// epochs imported descending from such genesis epochs. + #[test] + fn gap_is_not_enabled_when_at_least_one_genesis_epoch_is_still_imported() { + // A (#1) - B (#201) + // / + // 0 - C (#1) + // + // The epoch duration is 100 slots, each of these blocks represents + // an epoch change block. block B starts a new epoch at #201 since the + // genesis epoch spans two epochs. + + let is_descendent_of = |base: &Hash, block: &Hash| -> Result { + match (base, block) { + (b"0", _) => Ok(true), + (b"A", b"B") => Ok(true), + _ => Ok(false), + } + }; + + let duration = 100; + let make_genesis = |slot| Epoch { start_slot: slot, duration }; + let mut epoch_changes = EpochChanges::new(); + let next_descriptor = (); + + // insert genesis epoch for A at slot 1 + { + let genesis_epoch_a_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 1) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&genesis_epoch_a_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor); + + epoch_changes + .import(&is_descendent_of, *b"A", 1, *b"0", incremented_epoch) + .unwrap(); + } + + // insert regular epoch for B at slot 201, descending from A + { + let epoch_b_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"A", 1, 201) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&epoch_b_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor); + + epoch_changes + .import(&is_descendent_of, *b"B", 201, *b"A", incremented_epoch) + .unwrap(); + } + + // insert genesis epoch for C at slot 1000 + { + let genesis_epoch_x_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 1000) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&genesis_epoch_x_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor); + + epoch_changes + .import(&is_descendent_of, *b"C", 1, *b"0", incremented_epoch) + .unwrap(); + } + + // Clearing the gap should be a no-op. + epoch_changes.clear_gap(); + + // Check that all three epochs are available. + let epoch_a = epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"A", 1, 10, &make_genesis) + .unwrap() + .unwrap(); + + let epoch_b = epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"B", 201, 201, &make_genesis) + .unwrap() + .unwrap(); + + assert!(epoch_a != epoch_b); + + // the genesis epoch A will span slots [1, 200] with epoch B starting at slot 201 + assert_eq!(epoch_b.start_slot(), 201); + + let epoch_c = epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"C", 1, 1001, &make_genesis) + .unwrap() + .unwrap(); + + assert!(epoch_a != epoch_c); + } } diff --git a/client/consensus/epochs/src/migration.rs b/client/consensus/epochs/src/migration.rs index 49e08240df8c3..c4ed47e9c1c05 100644 --- a/client/consensus/epochs/src/migration.rs +++ b/client/consensus/epochs/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -30,9 +30,19 @@ pub struct EpochChangesV0 { inner: ForkTree>, } -/// Type alias for legacy definition of epoch changes. -pub type EpochChangesForV0 = +/// Legacy definition of epoch changes. +#[derive(Clone, Encode, Decode)] +pub struct EpochChangesV1 { + inner: ForkTree>, + epochs: BTreeMap<(Hash, Number), PersistedEpoch>, +} + +/// Type alias for v0 definition of epoch changes. +pub type EpochChangesV0For = EpochChangesV0<::Hash, NumberFor, Epoch>; +/// Type alias for v1 and v2 definition of epoch changes. +pub type EpochChangesV1For = + EpochChangesV1<::Hash, NumberFor, Epoch>; impl EpochChangesV0 where @@ -54,6 +64,17 @@ where header }); - EpochChanges { inner, epochs } + EpochChanges { inner, epochs, gap: None } + } +} + +impl EpochChangesV1 +where + Hash: PartialEq + Ord + Copy, + Number: Ord + Copy, +{ + /// Migrate the type into current epoch changes definition. + pub fn migrate(self) -> EpochChanges { + EpochChanges { inner: self.inner, epochs: self.epochs, gap: None } } } diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index d9ae8521c12f6..fb89445a97002 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -3,9 +3,9 @@ name = "sc-consensus-manual-seal" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Manual sealing engine for Substrate" -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,39 +13,36 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -derive_more = "0.99.2" -futures = "0.3.9" -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -log = "0.4.8" -codec = { package = "parity-scale-codec", version = "2.0.0" } -serde = { version = "1.0", features = ["derive"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } assert_matches = "1.3.0" -async-trait = "0.1.50" - -sc-client-api = { path = "../../api", version = "4.0.0-dev" } +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" +log = "0.4.17" +serde = { version = "1.0", features = ["derive"] } +thiserror = "1.0" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } -sc-consensus-babe = { path = "../../consensus/babe", version = "0.10.0-dev" } -sc-consensus-epochs = { path = "../../consensus/epochs", version = "0.10.0-dev" } -sp-consensus-babe = { path = "../../../primitives/consensus/babe", version = "0.10.0-dev" } - -sc-transaction-pool = { path = "../../transaction-pool", version = "4.0.0-dev" } -sp-blockchain = { path = "../../../primitives/blockchain", version = "4.0.0-dev" } -sp-consensus = { path = "../../../primitives/consensus/common", version = "0.10.0-dev" } -sp-consensus-slots = { path = "../../../primitives/consensus/slots", version = "0.10.0-dev" } -sp-inherents = { path = "../../../primitives/inherents", version = "4.0.0-dev" } -sp-runtime = { path = "../../../primitives/runtime", version = "4.0.0-dev" } -sp-core = { path = "../../../primitives/core", version = "4.0.0-dev" } -sp-keystore = { path = "../../../primitives/keystore", version = "0.10.0-dev" } -sp-api = { path = "../../../primitives/api", version = "4.0.0-dev" } -sc-transaction-pool-api = { path = "../../../client/transaction-pool/api", version = "4.0.0-dev" } -sp-timestamp = { path = "../../../primitives/timestamp", version = "4.0.0-dev" } - -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.9.0" } +sc-consensus-aura = { version = "0.10.0-dev", path = "../../consensus/aura" } +sc-consensus-babe = { version = "0.10.0-dev", path = "../../consensus/babe" } +sc-consensus-epochs = { version = "0.10.0-dev", path = "../../consensus/epochs" } +sc-transaction-pool = { version = "4.0.0-dev", path = "../../transaction-pool" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-consensus-aura = { version = "0.10.0-dev", path = "../../../primitives/consensus/aura" } +sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } +sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } [dev-dependencies] -tokio = { version = "1.10.0", features = ["rt-multi-thread", "macros"] } -sc-basic-authorship = { path = "../../basic-authorship", version = "0.10.0-dev" } -substrate-test-runtime-client = { path = "../../../test-utils/runtime/client", version = "2.0.0" } -substrate-test-runtime-transaction-pool = { path = "../../../test-utils/runtime/transaction-pool", version = "2.0.0" } +tokio = { version = "1.22.0", features = ["rt-multi-thread", "macros"] } +sc-basic-authorship = { version = "0.10.0-dev", path = "../../basic-authorship" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } +substrate-test-runtime-transaction-pool = { version = "2.0.0", path = "../../../test-utils/runtime/transaction-pool" } diff --git a/client/consensus/manual-seal/src/consensus.rs b/client/consensus/manual-seal/src/consensus.rs index 33a4c8616f6d2..a812bb028c7f2 100644 --- a/client/consensus/manual-seal/src/consensus.rs +++ b/client/consensus/manual-seal/src/consensus.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,9 +21,11 @@ use super::Error; use sc_consensus::BlockImportParams; use sp_inherents::InherentData; -use sp_runtime::traits::{Block as BlockT, DigestFor}; +use sp_runtime::{traits::Block as BlockT, Digest}; +pub mod aura; pub mod babe; +pub mod timestamp; /// Consensus data provider, manual seal uses this trait object for authoring blocks valid /// for any runtime. @@ -31,18 +33,18 @@ pub trait ConsensusDataProvider: Send + Sync { /// Block import transaction type type Transaction; + /// The proof type. + type Proof; + /// Attempt to create a consensus digest. - fn create_digest( - &self, - parent: &B::Header, - inherents: &InherentData, - ) -> Result, Error>; + fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result; - /// set up the neccessary import params. + /// Set up the necessary import params. fn append_block_import( &self, parent: &B::Header, params: &mut BlockImportParams, inherents: &InherentData, + proof: Self::Proof, ) -> Result<(), Error>; } diff --git a/client/consensus/manual-seal/src/consensus/aura.rs b/client/consensus/manual-seal/src/consensus/aura.rs new file mode 100644 index 0000000000000..065b78732cdc3 --- /dev/null +++ b/client/consensus/manual-seal/src/consensus/aura.rs @@ -0,0 +1,101 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Aura consensus data provider, This allows manual seal author blocks that are valid for +//! runtimes that expect the aura-specific digests. + +use crate::{ConsensusDataProvider, Error}; +use sc_client_api::{AuxStore, UsageProvider}; +use sc_consensus::BlockImportParams; +use sp_api::{ProvideRuntimeApi, TransactionFor}; +use sp_blockchain::{HeaderBackend, HeaderMetadata}; +use sp_consensus_aura::{ + digests::CompatibleDigestItem, + sr25519::{AuthorityId, AuthoritySignature}, + AuraApi, Slot, SlotDuration, +}; +use sp_inherents::InherentData; +use sp_runtime::{traits::Block as BlockT, Digest, DigestItem}; +use sp_timestamp::TimestampInherentData; +use std::{marker::PhantomData, sync::Arc}; + +/// Consensus data provider for Aura. +pub struct AuraConsensusDataProvider { + // slot duration + slot_duration: SlotDuration, + // phantom data for required generics + _phantom: PhantomData<(B, C, P)>, +} + +impl AuraConsensusDataProvider +where + B: BlockT, + C: AuxStore + ProvideRuntimeApi + UsageProvider, + C::Api: AuraApi, +{ + /// Creates a new instance of the [`AuraConsensusDataProvider`], requires that `client` + /// implements [`sp_consensus_aura::AuraApi`] + pub fn new(client: Arc) -> Self { + let slot_duration = sc_consensus_aura::slot_duration(&*client) + .expect("slot_duration is always present; qed."); + + Self { slot_duration, _phantom: PhantomData } + } +} + +impl ConsensusDataProvider for AuraConsensusDataProvider +where + B: BlockT, + C: AuxStore + + HeaderBackend + + HeaderMetadata + + UsageProvider + + ProvideRuntimeApi, + C::Api: AuraApi, + P: Send + Sync, +{ + type Transaction = TransactionFor; + type Proof = P; + + fn create_digest( + &self, + _parent: &B::Header, + inherents: &InherentData, + ) -> Result { + let timestamp = + inherents.timestamp_inherent_data()?.expect("Timestamp is always present; qed"); + + // we always calculate the new slot number based on the current time-stamp and the slot + // duration. + let digest_item = >::aura_pre_digest( + Slot::from_timestamp(timestamp, self.slot_duration), + ); + + Ok(Digest { logs: vec![digest_item] }) + } + + fn append_block_import( + &self, + _parent: &B::Header, + _params: &mut BlockImportParams, + _inherents: &InherentData, + _proof: Self::Proof, + ) -> Result<(), Error> { + Ok(()) + } +} diff --git a/client/consensus/manual-seal/src/consensus/babe.rs b/client/consensus/manual-seal/src/consensus/babe.rs index 1d3afe392d62f..206f5163a13cd 100644 --- a/client/consensus/manual-seal/src/consensus/babe.rs +++ b/client/consensus/manual-seal/src/consensus/babe.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,25 +16,21 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! BABE consensus data provider +//! BABE consensus data provider, This allows manual seal author blocks that are valid for runtimes +//! that expect babe-specific digests. use super::ConsensusDataProvider; use crate::Error; use codec::Encode; use sc_client_api::{AuxStore, UsageProvider}; use sc_consensus_babe::{ - authorship, find_pre_digest, BabeIntermediate, CompatibleDigestItem, Config, Epoch, - INTERMEDIATE_KEY, + authorship, find_pre_digest, BabeIntermediate, CompatibleDigestItem, Epoch, INTERMEDIATE_KEY, }; use sc_consensus_epochs::{ descendent_query, EpochHeader, SharedEpochChanges, ViableEpochDescriptor, }; use sp_keystore::SyncCryptoStorePtr; -use std::{ - borrow::Cow, - sync::{atomic, Arc}, - time::SystemTime, -}; +use std::{marker::PhantomData, sync::Arc}; use sc_consensus::{BlockImportParams, ForkChoiceStrategy, Verifier}; use sp_api::{ProvideRuntimeApi, TransactionFor}; @@ -43,19 +39,20 @@ use sp_consensus::CacheKeyId; use sp_consensus_babe::{ digests::{NextEpochDescriptor, PreDigest, SecondaryPlainPreDigest}, inherents::BabeInherentData, - AuthorityId, BabeApi, BabeAuthorityWeight, ConsensusLog, BABE_ENGINE_ID, + AuthorityId, BabeApi, BabeAuthorityWeight, BabeConfiguration, ConsensusLog, BABE_ENGINE_ID, }; use sp_consensus_slots::Slot; -use sp_inherents::{InherentData, InherentDataProvider, InherentIdentifier}; +use sp_inherents::InherentData; use sp_runtime::{ generic::{BlockId, Digest}, - traits::{Block as BlockT, DigestFor, DigestItemFor, Header, Zero}, + traits::{Block as BlockT, Header}, + DigestItem, }; -use sp_timestamp::{InherentType, TimestampInherentData, INHERENT_IDENTIFIER}; +use sp_timestamp::TimestampInherentData; /// Provides BABE-compatible predigests and BlockImportParams. /// Intended for use with BABE runtimes. -pub struct BabeConsensusDataProvider { +pub struct BabeConsensusDataProvider { /// shared reference to keystore keystore: SyncCryptoStorePtr, @@ -66,10 +63,14 @@ pub struct BabeConsensusDataProvider { epoch_changes: SharedEpochChanges, /// BABE config, gotten from the runtime. - config: Config, + /// NOTE: This is used to fetch `slot_duration` and `epoch_length` in the + /// `ConsensusDataProvider` implementation. Correct as far as these values + /// are not changed during an epoch change. + config: BabeConfiguration, /// Authorities to be used for this babe chain. authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, + _phantom: PhantomData

, } /// Verifier to be used for babe chains @@ -116,24 +117,22 @@ where .epoch_descriptor_for_child_of( descendent_query(&*self.client), &parent.hash(), - parent.number().clone(), + *parent.number(), pre_digest.slot(), ) .map_err(|e| format!("failed to fetch epoch_descriptor: {}", e))? - .ok_or_else(|| format!("{:?}", sp_consensus::Error::InvalidAuthoritiesSet))?; + .ok_or_else(|| format!("{}", sp_consensus::Error::InvalidAuthoritiesSet))?; // drop the lock drop(epoch_changes); - import_params.intermediates.insert( - Cow::from(INTERMEDIATE_KEY), - Box::new(BabeIntermediate:: { epoch_descriptor }) as Box<_>, - ); + import_params + .insert_intermediate(INTERMEDIATE_KEY, BabeIntermediate:: { epoch_descriptor }); Ok((import_params, None)) } } -impl BabeConsensusDataProvider +impl BabeConsensusDataProvider where B: BlockT, C: AuxStore @@ -153,9 +152,16 @@ where return Err(Error::StringError("Cannot supply empty authority set!".into())) } - let config = Config::get_or_compute(&*client)?; + let config = sc_consensus_babe::configuration(&*client)?; - Ok(Self { config, client, keystore, epoch_changes, authorities }) + Ok(Self { + config, + client, + keystore, + epoch_changes, + authorities, + _phantom: Default::default(), + }) } fn epoch(&self, parent: &B::Header, slot: Slot) -> Result { @@ -164,11 +170,11 @@ where .epoch_descriptor_for_child_of( descendent_query(&*self.client), &parent.hash(), - parent.number().clone(), + *parent.number(), slot, ) .map_err(|e| Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)))? - .ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?; + .ok_or(sp_consensus::Error::InvalidAuthoritiesSet)?; let epoch = epoch_changes .viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot)) @@ -181,7 +187,7 @@ where } } -impl ConsensusDataProvider for BabeConsensusDataProvider +impl ConsensusDataProvider for BabeConsensusDataProvider where B: BlockT, C: AuxStore @@ -190,14 +196,12 @@ where + UsageProvider + ProvideRuntimeApi, C::Api: BabeApi, + P: Send + Sync, { type Transaction = TransactionFor; + type Proof = P; - fn create_digest( - &self, - parent: &B::Header, - inherents: &InherentData, - ) -> Result, Error> { + fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result { let slot = inherents .babe_inherent_data()? .ok_or_else(|| Error::StringError("No babe inherent data".into()))?; @@ -207,7 +211,7 @@ where let logs = if let Some((predigest, _)) = authorship::claim_slot(slot, &epoch, &self.keystore) { - vec![ as CompatibleDigestItem>::babe_pre_digest(predigest)] + vec![::babe_pre_digest(predigest)] } else { // well we couldn't claim a slot because this is an existing chain and we're not in the // authorities. we need to tell BabeBlockImport that the epoch has changed, and we put @@ -220,19 +224,19 @@ where .epoch_descriptor_for_child_of( descendent_query(&*self.client), &parent.hash(), - parent.number().clone(), + *parent.number(), slot, ) .map_err(|e| { Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)) })? - .ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?; + .ok_or(sp_consensus::Error::InvalidAuthoritiesSet)?; match epoch_descriptor { ViableEpochDescriptor::Signaled(identifier, _epoch_header) => { let epoch_mut = epoch_changes .epoch_mut(&identifier) - .ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?; + .ok_or(sp_consensus::Error::InvalidAuthoritiesSet)?; // mutate the current epoch epoch_mut.authorities = self.authorities.clone(); @@ -240,17 +244,17 @@ where let next_epoch = ConsensusLog::NextEpochData(NextEpochDescriptor { authorities: self.authorities.clone(), // copy the old randomness - randomness: epoch_mut.randomness.clone(), + randomness: epoch_mut.randomness, }); vec![ - DigestItemFor::::PreRuntime(BABE_ENGINE_ID, predigest.encode()), - DigestItemFor::::Consensus(BABE_ENGINE_ID, next_epoch.encode()), + DigestItem::PreRuntime(BABE_ENGINE_ID, predigest.encode()), + DigestItem::Consensus(BABE_ENGINE_ID, next_epoch.encode()), ] }, ViableEpochDescriptor::UnimportedGenesis(_) => { // since this is the genesis, secondary predigest works for now. - vec![DigestItemFor::::PreRuntime(BABE_ENGINE_ID, predigest.encode())] + vec![DigestItem::PreRuntime(BABE_ENGINE_ID, predigest.encode())] }, } }; @@ -263,6 +267,7 @@ where parent: &B::Header, params: &mut BlockImportParams, inherents: &InherentData, + _proof: Self::Proof, ) -> Result<(), Error> { let slot = inherents .babe_inherent_data()? @@ -272,32 +277,34 @@ where .epoch_descriptor_for_child_of( descendent_query(&*self.client), &parent.hash(), - parent.number().clone(), + *parent.number(), slot, ) .map_err(|e| Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)))? - .ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?; + .ok_or(sp_consensus::Error::InvalidAuthoritiesSet)?; // drop the lock drop(epoch_changes); // a quick check to see if we're in the authorities let epoch = self.epoch(parent, slot)?; let (authority, _) = self.authorities.first().expect("authorities is non-emptyp; qed"); - let has_authority = epoch.authorities.iter().find(|(id, _)| *id == *authority).is_some(); + let has_authority = epoch.authorities.iter().any(|(id, _)| *id == *authority); if !has_authority { log::info!(target: "manual-seal", "authority not found"); let timestamp = inherents .timestamp_inherent_data()? .ok_or_else(|| Error::StringError("No timestamp inherent data".into()))?; - let slot = *timestamp / self.config.slot_duration; + + let slot = Slot::from_timestamp(timestamp, self.config.slot_duration()); + // manually hard code epoch descriptor epoch_descriptor = match epoch_descriptor { ViableEpochDescriptor::Signaled(identifier, _header) => ViableEpochDescriptor::Signaled( identifier, EpochHeader { - start_slot: slot.into(), - end_slot: (slot * self.config.epoch_length).into(), + start_slot: slot, + end_slot: (*slot * self.config.epoch_length).into(), }, ), _ => unreachable!( @@ -306,75 +313,8 @@ where }; } - params.intermediates.insert( - Cow::from(INTERMEDIATE_KEY), - Box::new(BabeIntermediate:: { epoch_descriptor }) as Box<_>, - ); + params.insert_intermediate(INTERMEDIATE_KEY, BabeIntermediate:: { epoch_descriptor }); Ok(()) } } - -/// Provide duration since unix epoch in millisecond for timestamp inherent. -/// Mocks the timestamp inherent to always produce the timestamp for the next babe slot. -pub struct SlotTimestampProvider { - time: atomic::AtomicU64, - slot_duration: u64, -} - -impl SlotTimestampProvider { - /// Create a new mocked time stamp provider. - pub fn new(client: Arc) -> Result - where - B: BlockT, - C: AuxStore + HeaderBackend + ProvideRuntimeApi + UsageProvider, - C::Api: BabeApi, - { - let slot_duration = Config::get_or_compute(&*client)?.slot_duration; - let info = client.info(); - - // looks like this isn't the first block, rehydrate the fake time. - // otherwise we'd be producing blocks for older slots. - let time = if info.best_number != Zero::zero() { - let header = client.header(BlockId::Hash(info.best_hash))?.unwrap(); - let slot = find_pre_digest::(&header).unwrap().slot(); - // add the slot duration so there's no collision of slots - (*slot * slot_duration) + slot_duration - } else { - // this is the first block, use the correct time. - let now = SystemTime::now(); - now.duration_since(SystemTime::UNIX_EPOCH) - .map_err(|err| Error::StringError(format!("{}", err)))? - .as_millis() as u64 - }; - - Ok(Self { time: atomic::AtomicU64::new(time), slot_duration }) - } - - /// Get the current slot number - pub fn slot(&self) -> u64 { - self.time.load(atomic::Ordering::SeqCst) / self.slot_duration - } -} - -#[async_trait::async_trait] -impl InherentDataProvider for SlotTimestampProvider { - fn provide_inherent_data( - &self, - inherent_data: &mut InherentData, - ) -> Result<(), sp_inherents::Error> { - // we update the time here. - let duration: InherentType = - self.time.fetch_add(self.slot_duration, atomic::Ordering::SeqCst).into(); - inherent_data.put_data(INHERENT_IDENTIFIER, &duration)?; - Ok(()) - } - - async fn try_handle_error( - &self, - _: &InherentIdentifier, - _: &[u8], - ) -> Option> { - None - } -} diff --git a/client/consensus/manual-seal/src/consensus/timestamp.rs b/client/consensus/manual-seal/src/consensus/timestamp.rs new file mode 100644 index 0000000000000..2cb9887a81f40 --- /dev/null +++ b/client/consensus/manual-seal/src/consensus/timestamp.rs @@ -0,0 +1,164 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Mocked timestamp inherent, allows for manual seal to create blocks for runtimes +//! that expect this inherent. + +use crate::Error; +use sc_client_api::{AuxStore, UsageProvider}; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_consensus_aura::{ + sr25519::{AuthorityId, AuthoritySignature}, + AuraApi, +}; +use sp_consensus_babe::BabeApi; +use sp_consensus_slots::{Slot, SlotDuration}; +use sp_inherents::{InherentData, InherentDataProvider, InherentIdentifier}; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Zero}, +}; +use sp_timestamp::{InherentType, INHERENT_IDENTIFIER}; +use std::{ + sync::{atomic, Arc}, + time::SystemTime, +}; + +/// Provide duration since unix epoch in millisecond for timestamp inherent. +/// Mocks the timestamp inherent to always produce a valid timestamp for the next slot. +/// +/// This works by either fetching the `slot_number` from the most recent header and dividing +/// that value by `slot_duration` in order to fork chains that expect this inherent. +/// +/// It produces timestamp inherents that are increased by `slot_duration` whenever +/// `provide_inherent_data` is called. +pub struct SlotTimestampProvider { + // holds the unix millisecond timestamp for the most recent block + unix_millis: atomic::AtomicU64, + // configured slot_duration in the runtime + slot_duration: SlotDuration, +} + +impl SlotTimestampProvider { + /// Create a new mocked time stamp provider, for babe. + pub fn new_babe(client: Arc) -> Result + where + B: BlockT, + C: AuxStore + HeaderBackend + ProvideRuntimeApi + UsageProvider, + C::Api: BabeApi, + { + let slot_duration = sc_consensus_babe::configuration(&*client)?.slot_duration(); + + let time = Self::with_header(&client, slot_duration, |header| { + let slot_number = *sc_consensus_babe::find_pre_digest::(&header) + .map_err(|err| format!("{}", err))? + .slot(); + Ok(slot_number) + })?; + + Ok(Self { unix_millis: atomic::AtomicU64::new(time), slot_duration }) + } + + /// Create a new mocked time stamp provider, for aura + pub fn new_aura(client: Arc) -> Result + where + B: BlockT, + C: AuxStore + HeaderBackend + ProvideRuntimeApi + UsageProvider, + C::Api: AuraApi, + { + let slot_duration = sc_consensus_aura::slot_duration(&*client)?; + + let time = Self::with_header(&client, slot_duration, |header| { + let slot_number = *sc_consensus_aura::find_pre_digest::(&header) + .map_err(|err| format!("{}", err))?; + Ok(slot_number) + })?; + + Ok(Self { unix_millis: atomic::AtomicU64::new(time), slot_duration }) + } + + fn with_header( + client: &Arc, + slot_duration: SlotDuration, + func: F, + ) -> Result + where + B: BlockT, + C: AuxStore + HeaderBackend + UsageProvider, + F: Fn(B::Header) -> Result, + { + let info = client.info(); + + // looks like this isn't the first block, rehydrate the fake time. + // otherwise we'd be producing blocks for older slots. + let time = if info.best_number != Zero::zero() { + let header = client + .header(BlockId::Hash(info.best_hash))? + .ok_or_else(|| "best header not found in the db!".to_string())?; + let slot = func(header)?; + // add the slot duration so there's no collision of slots + (slot * slot_duration.as_millis() as u64) + slot_duration.as_millis() as u64 + } else { + // this is the first block, use the correct time. + let now = SystemTime::now(); + now.duration_since(SystemTime::UNIX_EPOCH) + .map_err(|err| Error::StringError(format!("{}", err)))? + .as_millis() as u64 + }; + + Ok(time) + } + + /// Get the current slot number + pub fn slot(&self) -> Slot { + Slot::from_timestamp( + self.unix_millis.load(atomic::Ordering::SeqCst).into(), + self.slot_duration, + ) + } + + /// Gets the current time stamp. + pub fn timestamp(&self) -> sp_timestamp::Timestamp { + sp_timestamp::Timestamp::new(self.unix_millis.load(atomic::Ordering::SeqCst)) + } +} + +#[async_trait::async_trait] +impl InherentDataProvider for SlotTimestampProvider { + async fn provide_inherent_data( + &self, + inherent_data: &mut InherentData, + ) -> Result<(), sp_inherents::Error> { + // we update the time here. + let new_time: InherentType = self + .unix_millis + .fetch_add(self.slot_duration.as_millis() as u64, atomic::Ordering::SeqCst) + .into(); + inherent_data.put_data(INHERENT_IDENTIFIER, &new_time)?; + Ok(()) + } + + async fn try_handle_error( + &self, + _: &InherentIdentifier, + _: &[u8], + ) -> Option> { + None + } +} diff --git a/client/consensus/manual-seal/src/error.rs b/client/consensus/manual-seal/src/error.rs index 8585e6a70d644..a056c541c3cef 100644 --- a/client/consensus/manual-seal/src/error.rs +++ b/client/consensus/manual-seal/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,6 +20,10 @@ //! This is suitable for a testing environment. use futures::channel::{mpsc::SendError, oneshot}; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; use sc_consensus::ImportResult; use sp_blockchain::Error as BlockchainError; use sp_consensus::Error as ConsensusError; @@ -27,56 +31,67 @@ use sp_inherents::Error as InherentsError; /// Error code for rpc mod codes { - pub const SERVER_SHUTTING_DOWN: i64 = 10_000; - pub const BLOCK_IMPORT_FAILED: i64 = 11_000; - pub const EMPTY_TRANSACTION_POOL: i64 = 12_000; - pub const BLOCK_NOT_FOUND: i64 = 13_000; - pub const CONSENSUS_ERROR: i64 = 14_000; - pub const INHERENTS_ERROR: i64 = 15_000; - pub const BLOCKCHAIN_ERROR: i64 = 16_000; - pub const UNKNOWN_ERROR: i64 = 20_000; + pub const SERVER_SHUTTING_DOWN: i32 = 10_000; + pub const BLOCK_IMPORT_FAILED: i32 = 11_000; + pub const EMPTY_TRANSACTION_POOL: i32 = 12_000; + pub const BLOCK_NOT_FOUND: i32 = 13_000; + pub const CONSENSUS_ERROR: i32 = 14_000; + pub const INHERENTS_ERROR: i32 = 15_000; + pub const BLOCKCHAIN_ERROR: i32 = 16_000; + pub const UNKNOWN_ERROR: i32 = 20_000; } /// errors encountered by background block authorship task -#[derive(Debug, derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// An error occurred while importing the block - #[display(fmt = "Block import failed: {:?}", _0)] + #[error("Block import failed: {0:?}")] BlockImportError(ImportResult), /// Transaction pool is empty, cannot create a block - #[display(fmt = "Transaction pool is empty, set create_empty to true,\ - if you want to create empty blocks")] + #[error( + "Transaction pool is empty, set create_empty to true, if you want to create empty blocks" + )] EmptyTransactionPool, /// encountered during creation of Proposer. - #[display(fmt = "Consensus Error: {}", _0)] - ConsensusError(ConsensusError), + #[error("Consensus Error: {0}")] + ConsensusError(#[from] ConsensusError), /// Failed to create Inherents data - #[display(fmt = "Inherents Error: {}", _0)] - InherentError(InherentsError), + #[error("Inherents Error: {0}")] + InherentError(#[from] InherentsError), /// error encountered during finalization - #[display(fmt = "Finalization Error: {}", _0)] - BlockchainError(BlockchainError), + #[error("Finalization Error: {0}")] + BlockchainError(#[from] BlockchainError), /// Supplied parent_hash doesn't exist in chain - #[display(fmt = "Supplied parent_hash: {} doesn't exist in chain", _0)] - #[from(ignore)] + #[error("Supplied parent_hash: {0} doesn't exist in chain")] BlockNotFound(String), /// Some string error - #[display(fmt = "{}", _0)] - #[from(ignore)] + #[error("{0}")] StringError(String), /// send error - #[display(fmt = "Consensus process is terminating")] - Canceled(oneshot::Canceled), + #[error("Consensus process is terminating")] + Canceled(#[from] oneshot::Canceled), /// send error - #[display(fmt = "Consensus process is terminating")] - SendError(SendError), + #[error("Consensus process is terminating")] + SendError(#[from] SendError), /// Some other error. - #[display(fmt = "Other error: {}", _0)] - Other(Box), + #[error("Other error: {0}")] + Other(Box), +} + +impl From for Error { + fn from(err: ImportResult) -> Self { + Error::BlockImportError(err) + } +} + +impl From for Error { + fn from(s: String) -> Self { + Error::StringError(s) + } } impl Error { - fn to_code(&self) -> i64 { + fn to_code(&self) -> i32 { use Error::*; match self { BlockImportError(_) => codes::BLOCK_IMPORT_FAILED, @@ -91,12 +106,8 @@ impl Error { } } -impl std::convert::From for jsonrpc_core::Error { - fn from(error: Error) -> Self { - jsonrpc_core::Error { - code: jsonrpc_core::ErrorCode::ServerError(error.to_code()), - message: format!("{}", error), - data: None, - } +impl From for JsonRpseeError { + fn from(err: Error) -> Self { + CallError::Custom(ErrorObject::owned(err.to_code(), err.to_string(), None::<()>)).into() } } diff --git a/client/consensus/manual-seal/src/finalize_block.rs b/client/consensus/manual-seal/src/finalize_block.rs index a5ddf1d162f7a..cee4d59b6d6e5 100644 --- a/client/consensus/manual-seal/src/finalize_block.rs +++ b/client/consensus/manual-seal/src/finalize_block.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ use crate::rpc; use sc_client_api::backend::{Backend as ClientBackend, Finalizer}; -use sp_runtime::{generic::BlockId, traits::Block as BlockT, Justification}; +use sp_runtime::{traits::Block as BlockT, Justification}; use std::{marker::PhantomData, sync::Arc}; /// params for block finalization. @@ -46,9 +46,9 @@ where { let FinalizeBlockParams { hash, mut sender, justification, finalizer, .. } = params; - match finalizer.finalize_block(BlockId::Hash(hash), justification, true) { + match finalizer.finalize_block(hash, justification, true) { Err(e) => { - log::warn!("Failed to finalize block {:?}", e); + log::warn!("Failed to finalize block {}", e); rpc::send_result(&mut sender, Err(e.into())) }, Ok(()) => { diff --git a/client/consensus/manual-seal/src/lib.rs b/client/consensus/manual-seal/src/lib.rs index 390c23fe032f1..09ab139b91c73 100644 --- a/client/consensus/manual-seal/src/lib.rs +++ b/client/consensus/manual-seal/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -81,7 +81,7 @@ where } /// Params required to start the instant sealing authorship task. -pub struct ManualSealParams, TP, SC, CS, CIDP> { +pub struct ManualSealParams, TP, SC, CS, CIDP, P> { /// Block import instance for well. importing blocks. pub block_import: BI, @@ -103,14 +103,14 @@ pub struct ManualSealParams, TP, SC, C /// Digest provider for inclusion in blocks. pub consensus_data_provider: - Option>>>, + Option>>>, /// Something that can create the inherent data providers. pub create_inherent_data_providers: CIDP, } /// Params required to start the manual sealing authorship task. -pub struct InstantSealParams, TP, SC, CIDP> { +pub struct InstantSealParams, TP, SC, CIDP, P> { /// Block import instance for well. importing blocks. pub block_import: BI, @@ -128,14 +128,14 @@ pub struct InstantSealParams, TP, SC, /// Digest provider for inclusion in blocks. pub consensus_data_provider: - Option>>>, + Option>>>, /// Something that can create the inherent data providers. pub create_inherent_data_providers: CIDP, } /// Creates the background authorship task for the manual seal engine. -pub async fn run_manual_seal( +pub async fn run_manual_seal( ManualSealParams { mut block_import, mut env, @@ -145,7 +145,7 @@ pub async fn run_manual_seal( select_chain, consensus_data_provider, create_inherent_data_providers, - }: ManualSealParams, + }: ManualSealParams, ) where B: BlockT + 'static, BI: BlockImport> @@ -155,12 +155,13 @@ pub async fn run_manual_seal( C: HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, CB: ClientBackend + 'static, E: Environment + 'static, - E::Proposer: Proposer>, + E::Proposer: Proposer>, CS: Stream::Hash>> + Unpin + 'static, SC: SelectChain + 'static, TransactionFor: 'static, TP: TransactionPool, CIDP: CreateInherentDataProviders, + P: Send + Sync + 'static, { while let Some(command) = commands_stream.next().await { match command { @@ -173,7 +174,7 @@ pub async fn run_manual_seal( env: &mut env, select_chain: &select_chain, block_import: &mut block_import, - consensus_data_provider: consensus_data_provider.as_ref().map(|p| &**p), + consensus_data_provider: consensus_data_provider.as_deref(), pool: pool.clone(), client: client.clone(), create_inherent_data_providers: &create_inherent_data_providers, @@ -198,7 +199,7 @@ pub async fn run_manual_seal( /// runs the background authorship task for the instant seal engine. /// instant-seal creates a new block for every transaction imported into /// the transaction pool. -pub async fn run_instant_seal( +pub async fn run_instant_seal( InstantSealParams { block_import, env, @@ -207,7 +208,7 @@ pub async fn run_instant_seal( select_chain, consensus_data_provider, create_inherent_data_providers, - }: InstantSealParams, + }: InstantSealParams, ) where B: BlockT + 'static, BI: BlockImport> @@ -217,11 +218,12 @@ pub async fn run_instant_seal( C: HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, CB: ClientBackend + 'static, E: Environment + 'static, - E::Proposer: Proposer>, + E::Proposer: Proposer>, SC: SelectChain + 'static, TransactionFor: 'static, TP: TransactionPool, CIDP: CreateInherentDataProviders, + P: Send + Sync + 'static, { // instant-seal creates blocks as soon as transactions are imported // into the transaction pool. @@ -245,15 +247,69 @@ pub async fn run_instant_seal( .await } +/// Runs the background authorship task for the instant seal engine. +/// instant-seal creates a new block for every transaction imported into +/// the transaction pool. +/// +/// This function will finalize the block immediately as well. If you don't +/// want this behavior use `run_instant_seal` instead. +pub async fn run_instant_seal_and_finalize( + InstantSealParams { + block_import, + env, + client, + pool, + select_chain, + consensus_data_provider, + create_inherent_data_providers, + }: InstantSealParams, +) where + B: BlockT + 'static, + BI: BlockImport> + + Send + + Sync + + 'static, + C: HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + CB: ClientBackend + 'static, + E: Environment + 'static, + E::Proposer: Proposer>, + SC: SelectChain + 'static, + TransactionFor: 'static, + TP: TransactionPool, + CIDP: CreateInherentDataProviders, + P: Send + Sync + 'static, +{ + // Creates and finalizes blocks as soon as transactions are imported + // into the transaction pool. + let commands_stream = pool.import_notification_stream().map(|_| EngineCommand::SealNewBlock { + create_empty: false, + finalize: true, + parent_hash: None, + sender: None, + }); + + run_manual_seal(ManualSealParams { + block_import, + env, + client, + pool, + commands_stream, + select_chain, + consensus_data_provider, + create_inherent_data_providers, + }) + .await +} + #[cfg(test)] mod tests { use super::*; use sc_basic_authorship::ProposerFactory; - use sc_client_api::BlockBackend; use sc_consensus::ImportedAux; - use sc_transaction_pool::{BasicPool, Options, RevalidationType}; + use sc_transaction_pool::{BasicPool, FullChainApi, Options, RevalidationType}; use sc_transaction_pool_api::{MaintainedTransactionPool, TransactionPool, TransactionSource}; - use sp_runtime::generic::BlockId; + use sp_inherents::InherentData; + use sp_runtime::generic::{BlockId, Digest, DigestItem}; use substrate_test_runtime_client::{ AccountKeyring::*, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt, }; @@ -265,12 +321,44 @@ mod tests { const SOURCE: TransactionSource = TransactionSource::External; + struct TestDigestProvider { + _client: Arc, + } + impl ConsensusDataProvider for TestDigestProvider + where + B: BlockT, + C: ProvideRuntimeApi + Send + Sync, + { + type Transaction = TransactionFor; + type Proof = (); + + fn create_digest( + &self, + _parent: &B::Header, + _inherents: &InherentData, + ) -> Result { + Ok(Digest { logs: vec![] }) + } + + fn append_block_import( + &self, + _parent: &B::Header, + params: &mut BlockImportParams, + _inherents: &InherentData, + _proof: Self::Proof, + ) -> Result<(), Error> { + params.post_digests.push(DigestItem::Other(vec![1])); + Ok(()) + } + } + #[tokio::test] async fn instant_seal() { let builder = TestClientBuilder::new(); let (client, select_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); let spawner = sp_core::testing::TaskExecutor::new(); + let genesis_hash = client.header(&BlockId::Number(0)).unwrap().unwrap().hash(); let pool = Arc::new(BasicPool::with_revalidation_type( Options::default(), true.into(), @@ -279,6 +367,8 @@ mod tests { RevalidationType::Full, spawner.clone(), 0, + genesis_hash, + genesis_hash, )); let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None); // this test checks that blocks are created as soon as transactions are imported into the @@ -321,7 +411,7 @@ mod tests { assert_eq!( created_block, CreatedBlock { - hash: created_block.hash.clone(), + hash: created_block.hash, aux: ImportedAux { header_only: false, clear_justification_requests: false, @@ -341,6 +431,7 @@ mod tests { let (client, select_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); let spawner = sp_core::testing::TaskExecutor::new(); + let genesis_hash = client.header(&BlockId::Number(0)).unwrap().unwrap().hash(); let pool = Arc::new(BasicPool::with_revalidation_type( Options::default(), true.into(), @@ -349,6 +440,8 @@ mod tests { RevalidationType::Full, spawner.clone(), 0, + genesis_hash, + genesis_hash, )); let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None); // this test checks that blocks are created as soon as an engine command is sent over the @@ -388,7 +481,7 @@ mod tests { assert_eq!( created_block, CreatedBlock { - hash: created_block.hash.clone(), + hash: created_block.hash, aux: ImportedAux { header_only: false, clear_justification_requests: false, @@ -408,8 +501,8 @@ mod tests { }) .await .unwrap(); - // assert that the background task returns ok - assert_eq!(rx.await.unwrap().unwrap(), ()); + // check that the background task returns ok: + rx.await.unwrap().unwrap(); } #[tokio::test] @@ -417,8 +510,13 @@ mod tests { let builder = TestClientBuilder::new(); let (client, select_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); - let pool_api = api(); + let pool_api = Arc::new(FullChainApi::new( + client.clone(), + None, + &sp_core::testing::TaskExecutor::new(), + )); let spawner = sp_core::testing::TaskExecutor::new(); + let genesis_hash = client.header(&BlockId::Number(0)).unwrap().unwrap().hash(); let pool = Arc::new(BasicPool::with_revalidation_type( Options::default(), true.into(), @@ -427,6 +525,8 @@ mod tests { RevalidationType::Full, spawner.clone(), 0, + genesis_hash, + genesis_hash, )); let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None); // this test checks that blocks are created as soon as an engine command is sent over the @@ -462,13 +562,12 @@ mod tests { .await .unwrap(); let created_block = rx.await.unwrap().unwrap(); - pool_api.increment_nonce(Alice.into()); // assert that the background task returns ok assert_eq!( created_block, CreatedBlock { - hash: created_block.hash.clone(), + hash: created_block.hash, aux: ImportedAux { header_only: false, clear_justification_requests: false, @@ -478,8 +577,7 @@ mod tests { } } ); - let block = client.block(&BlockId::Number(1)).unwrap().unwrap().block; - pool_api.add_block(block, true); + assert!(pool.submit_one(&BlockId::Number(1), SOURCE, uxt(Alice, 1)).await.is_ok()); let header = client.header(&BlockId::Number(1)).expect("db error").expect("imported above"); @@ -500,9 +598,6 @@ mod tests { .await .is_ok()); assert_matches::assert_matches!(rx1.await.expect("should be no error receiving"), Ok(_)); - let block = client.block(&BlockId::Number(2)).unwrap().unwrap().block; - pool_api.add_block(block, true); - pool_api.increment_nonce(Alice.into()); assert!(pool.submit_one(&BlockId::Number(1), SOURCE, uxt(Bob, 0)).await.is_ok()); let (tx2, rx2) = futures::channel::oneshot::channel(); @@ -519,4 +614,56 @@ mod tests { // assert that fork block is in the db assert!(client.header(&BlockId::Hash(imported.hash)).unwrap().is_some()) } + + #[tokio::test] + async fn manual_seal_post_hash() { + let builder = TestClientBuilder::new(); + let (client, select_chain) = builder.build_with_longest_chain(); + let client = Arc::new(client); + let spawner = sp_core::testing::TaskExecutor::new(); + let genesis_hash = client.header(&BlockId::Number(0)).unwrap().unwrap().hash(); + let pool = Arc::new(BasicPool::with_revalidation_type( + Options::default(), + true.into(), + api(), + None, + RevalidationType::Full, + spawner.clone(), + 0, + genesis_hash, + genesis_hash, + )); + let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None); + + let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024); + let future = run_manual_seal(ManualSealParams { + block_import: client.clone(), + env, + client: client.clone(), + pool: pool.clone(), + commands_stream, + select_chain, + // use a provider that pushes some post digest data + consensus_data_provider: Some(Box::new(TestDigestProvider { _client: client.clone() })), + create_inherent_data_providers: |_, _| async { Ok(()) }, + }); + std::thread::spawn(|| { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(future); + }); + let (tx, rx) = futures::channel::oneshot::channel(); + sink.send(EngineCommand::SealNewBlock { + parent_hash: None, + sender: Some(tx), + create_empty: true, + finalize: false, + }) + .await + .unwrap(); + let created_block = rx.await.unwrap().unwrap(); + + // assert that the background task returned the actual header hash + let header = client.header(&BlockId::Number(1)).unwrap().unwrap(); + assert_eq!(header.hash(), created_block.hash); + } } diff --git a/client/consensus/manual-seal/src/rpc.rs b/client/consensus/manual-seal/src/rpc.rs index 6755879ceedd6..b9bb06551f818 100644 --- a/client/consensus/manual-seal/src/rpc.rs +++ b/client/consensus/manual-seal/src/rpc.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,21 +18,21 @@ //! RPC interface for the `ManualSeal` Engine. -pub use self::gen_client::Client as ManualSealClient; +use crate::error::Error; use futures::{ channel::{mpsc, oneshot}, - FutureExt, SinkExt, TryFutureExt, + SinkExt, +}; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, }; -use jsonrpc_core::Error; -use jsonrpc_derive::rpc; use sc_consensus::ImportedAux; use serde::{Deserialize, Serialize}; use sp_runtime::EncodedJustification; -/// Future's type for jsonrpc -type FutureResult = jsonrpc_core::BoxFuture>; -/// sender passed to the authorship task to report errors or successes. -pub type Sender = Option>>; +/// Sender passed to the authorship task to report errors or successes. +pub type Sender = Option>>; /// Message sent to the background authorship task, usually by RPC. pub enum EngineCommand { @@ -65,27 +65,27 @@ pub enum EngineCommand { } /// RPC trait that provides methods for interacting with the manual-seal authorship task over rpc. -#[rpc] +#[rpc(client, server)] pub trait ManualSealApi { /// Instructs the manual-seal authorship task to create a new block - #[rpc(name = "engine_createBlock")] - fn create_block( + #[method(name = "engine_createBlock")] + async fn create_block( &self, create_empty: bool, finalize: bool, parent_hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Instructs the manual-seal authorship task to finalize a block - #[rpc(name = "engine_finalizeBlock")] - fn finalize_block( + #[method(name = "engine_finalizeBlock")] + async fn finalize_block( &self, hash: Hash, justification: Option, - ) -> FutureResult; + ) -> RpcResult; } -/// A struct that implements the [`ManualSealApi`]. +/// A struct that implements the [`ManualSealApiServer`]. pub struct ManualSeal { import_block_channel: mpsc::Sender>, } @@ -106,44 +106,43 @@ impl ManualSeal { } } -impl ManualSealApi for ManualSeal { - fn create_block( +#[async_trait] +impl ManualSealApiServer for ManualSeal { + async fn create_block( &self, create_empty: bool, finalize: bool, parent_hash: Option, - ) -> FutureResult> { + ) -> RpcResult> { let mut sink = self.import_block_channel.clone(); - async move { - let (sender, receiver) = oneshot::channel(); - let command = EngineCommand::SealNewBlock { - create_empty, - finalize, - parent_hash, - sender: Some(sender), - }; - sink.send(command).await?; - receiver.await? + let (sender, receiver) = oneshot::channel(); + // NOTE: this sends a Result over the channel. + let command = EngineCommand::SealNewBlock { + create_empty, + finalize, + parent_hash, + sender: Some(sender), + }; + + sink.send(command).await?; + + match receiver.await { + Ok(Ok(rx)) => Ok(rx), + Ok(Err(e)) => Err(e.into()), + Err(e) => Err(JsonRpseeError::to_call_error(e)), } - .map_err(Error::from) - .boxed() } - fn finalize_block( + async fn finalize_block( &self, hash: Hash, justification: Option, - ) -> FutureResult { + ) -> RpcResult { let mut sink = self.import_block_channel.clone(); - async move { - let (sender, receiver) = oneshot::channel(); - sink.send(EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification }) - .await?; - - receiver.await?.map(|_| true) - } - .map_err(Error::from) - .boxed() + let (sender, receiver) = oneshot::channel(); + let command = EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification }; + sink.send(command).await?; + receiver.await.map(|_| true).map_err(|e| JsonRpseeError::to_call_error(e)) } } @@ -155,7 +154,10 @@ pub fn send_result( ) { if let Some(sender) = sender.take() { if let Err(err) = sender.send(result) { - log::warn!("Server is shutting down: {:?}", err) + match err { + Ok(value) => log::warn!("Server is shutting down: {:?}", value), + Err(error) => log::warn!("Server is shutting down with error: {}", error), + } } } else { // instant seal doesn't report errors over rpc, simply log them. diff --git a/client/consensus/manual-seal/src/seal_block.rs b/client/consensus/manual-seal/src/seal_block.rs index 502705b411621..25c091bf460ec 100644 --- a/client/consensus/manual-seal/src/seal_block.rs +++ b/client/consensus/manual-seal/src/seal_block.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -36,7 +36,7 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; pub const MAX_PROPOSAL_DURATION: u64 = 10; /// params for sealing a new block -pub struct SealBlockParams<'a, B: BlockT, BI, SC, C: ProvideRuntimeApi, E, TP, CIDP> { +pub struct SealBlockParams<'a, B: BlockT, BI, SC, C: ProvideRuntimeApi, E, TP, CIDP, P> { /// if true, empty blocks(without extrinsics) will be created. /// otherwise, will return Error::EmptyTransactionPool. pub create_empty: bool, @@ -56,7 +56,7 @@ pub struct SealBlockParams<'a, B: BlockT, BI, SC, C: ProvideRuntimeApi, E, TP pub select_chain: &'a SC, /// Digest provider for inclusion in blocks. pub consensus_data_provider: - Option<&'a dyn ConsensusDataProvider>>, + Option<&'a dyn ConsensusDataProvider>>, /// block import object pub block_import: &'a mut BI, /// Something that can create the inherent data providers. @@ -64,7 +64,7 @@ pub struct SealBlockParams<'a, B: BlockT, BI, SC, C: ProvideRuntimeApi, E, TP } /// seals a new block with the given params -pub async fn seal_block( +pub async fn seal_block( SealBlockParams { create_empty, finalize, @@ -77,7 +77,7 @@ pub async fn seal_block( create_inherent_data_providers, consensus_data_provider: digest_provider, mut sender, - }: SealBlockParams<'_, B, BI, SC, C, E, TP, CIDP>, + }: SealBlockParams<'_, B, BI, SC, C, E, TP, CIDP, P>, ) where B: BlockT, BI: BlockImport> @@ -86,11 +86,12 @@ pub async fn seal_block( + 'static, C: HeaderBackend + ProvideRuntimeApi, E: Environment, - E::Proposer: Proposer>, + E::Proposer: Proposer>, TP: TransactionPool, SC: SelectChain, TransactionFor: 'static, CIDP: CreateInherentDataProviders, + P: Send + Sync + 'static, { let future = async { if pool.status().ready == 0 && !create_empty { @@ -112,12 +113,9 @@ pub async fn seal_block( .await .map_err(|e| Error::Other(e))?; - let inherent_data = inherent_data_providers.create_inherent_data()?; + let inherent_data = inherent_data_providers.create_inherent_data().await?; - let proposer = env - .init(&parent) - .map_err(|err| Error::StringError(format!("{:?}", err))) - .await?; + let proposer = env.init(&parent).map_err(|err| Error::StringError(err.to_string())).await?; let inherents_len = inherent_data.len(); let digest = if let Some(digest_provider) = digest_provider { @@ -133,7 +131,7 @@ pub async fn seal_block( Duration::from_secs(MAX_PROPOSAL_DURATION), None, ) - .map_err(|err| Error::StringError(format!("{:?}", err))) + .map_err(|err| Error::StringError(err.to_string())) .await?; if proposal.block.extrinsics().len() == inherents_len && !create_empty { @@ -141,6 +139,7 @@ pub async fn seal_block( } let (header, body) = proposal.block.deconstruct(); + let proof = proposal.proof; let mut params = BlockImportParams::new(BlockOrigin::Own, header.clone()); params.body = Some(body); params.finalized = finalize; @@ -150,12 +149,17 @@ pub async fn seal_block( )); if let Some(digest_provider) = digest_provider { - digest_provider.append_block_import(&parent, &mut params, &inherent_data)?; + digest_provider.append_block_import(&parent, &mut params, &inherent_data, proof)?; } + // Make sure we return the same post-hash that will be calculated when importing the block + // This is important in case the digest_provider added any signature, seal, ect. + let mut post_header = header.clone(); + post_header.digest_mut().logs.extend(params.post_digests.iter().cloned()); + match block_import.import_block(params, HashMap::new()).await? { ImportResult::Imported(aux) => - Ok(CreatedBlock { hash: ::Header::hash(&header), aux }), + Ok(CreatedBlock { hash: ::Header::hash(&post_header), aux }), other => Err(other.into()), } }; diff --git a/client/consensus/pow/Cargo.toml b/client/consensus/pow/Cargo.toml index c71e11aef275e..480d9b23b06a3 100644 --- a/client/consensus/pow/Cargo.toml +++ b/client/consensus/pow/Cargo.toml @@ -3,9 +3,9 @@ name = "sc-consensus-pow" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "PoW consensus algorithm for substrate" -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,21 +13,21 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", features = ["derive"] } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +futures = "0.3.21" +futures-timer = "3.0.1" +log = "0.4.17" +parking_lot = "0.12.1" +thiserror = "1.0" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } -sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-consensus-pow = { version = "0.10.0-dev", path = "../../../primitives/consensus/pow" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -log = "0.4.8" -futures = "0.3.16" -futures-timer = "3.0.1" -parking_lot = "0.11.1" -derive_more = "0.99.2" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.9.0"} -async-trait = "0.1.50" +sp-consensus-pow = { version = "0.10.0-dev", path = "../../../primitives/consensus/pow" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index 1f5781434ef71..ac7ce3b411333 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -27,9 +27,9 @@ //! started via the [`start_mining_worker`] function. It returns a worker //! handle together with a future. The future must be pulled. Through //! the worker handle, you can pull the metadata needed to start the -//! mining process via [`MiningWorker::metadata`], and then do the actual +//! mining process via [`MiningHandle::metadata`], and then do the actual //! mining on a standalone thread. Finally, when a seal is found, call -//! [`MiningWorker::submit`] to build the block. +//! [`MiningHandle::submit`] to build the block. //! //! The auxiliary storage for PoW engine only stores the total difficulty. //! For other storage requirements for particular PoW algorithm (such as @@ -41,13 +41,12 @@ mod worker; -pub use crate::worker::{MiningBuild, MiningMetadata, MiningWorker}; +pub use crate::worker::{MiningBuild, MiningHandle, MiningMetadata}; use crate::worker::UntilImportedOrTimeout; use codec::{Decode, Encode}; use futures::{Future, StreamExt}; use log::*; -use parking_lot::Mutex; use prometheus_endpoint::Registry; use sc_client_api::{self, backend::AuxStore, BlockOf, BlockchainEvents}; use sc_consensus::{ @@ -56,10 +55,8 @@ use sc_consensus::{ }; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder as BlockBuilderApi; -use sp_blockchain::{well_known_cache_keys::Id as CacheKeyId, HeaderBackend, ProvideCache}; -use sp_consensus::{ - CanAuthorWith, Environment, Error as ConsensusError, Proposer, SelectChain, SyncOracle, -}; +use sp_blockchain::{well_known_cache_keys::Id as CacheKeyId, HeaderBackend}; +use sp_consensus::{Environment, Error as ConsensusError, Proposer, SelectChain, SyncOracle}; use sp_consensus_pow::{Seal, TotalDifficulty, POW_ENGINE_ID}; use sp_core::ExecutionContext; use sp_inherents::{CreateInherentDataProviders, InherentDataProvider}; @@ -68,60 +65,62 @@ use sp_runtime::{ traits::{Block as BlockT, Header as HeaderT}, RuntimeString, }; -use std::{ - borrow::Cow, cmp::Ordering, collections::HashMap, marker::PhantomData, sync::Arc, - time::Duration, -}; +use std::{cmp::Ordering, collections::HashMap, marker::PhantomData, sync::Arc, time::Duration}; -#[derive(derive_more::Display, Debug)] +#[derive(Debug, thiserror::Error)] pub enum Error { - #[display(fmt = "Header uses the wrong engine {:?}", _0)] + #[error("Header uses the wrong engine {0:?}")] WrongEngine([u8; 4]), - #[display(fmt = "Header {:?} is unsealed", _0)] + #[error("Header {0:?} is unsealed")] HeaderUnsealed(B::Hash), - #[display(fmt = "PoW validation error: invalid seal")] + #[error("PoW validation error: invalid seal")] InvalidSeal, - #[display(fmt = "PoW validation error: preliminary verification failed")] + #[error("PoW validation error: preliminary verification failed")] FailedPreliminaryVerify, - #[display(fmt = "Rejecting block too far in future")] + #[error("Rejecting block too far in future")] TooFarInFuture, - #[display(fmt = "Fetching best header failed using select chain: {:?}", _0)] + #[error("Fetching best header failed using select chain: {0}")] BestHeaderSelectChain(ConsensusError), - #[display(fmt = "Fetching best header failed: {:?}", _0)] + #[error("Fetching best header failed: {0}")] BestHeader(sp_blockchain::Error), - #[display(fmt = "Best header does not exist")] + #[error("Best header does not exist")] NoBestHeader, - #[display(fmt = "Block proposing error: {:?}", _0)] + #[error("Block proposing error: {0}")] BlockProposingError(String), - #[display(fmt = "Fetch best hash failed via select chain: {:?}", _0)] + #[error("Fetch best hash failed via select chain: {0}")] BestHashSelectChain(ConsensusError), - #[display(fmt = "Error with block built on {:?}: {:?}", _0, _1)] + #[error("Error with block built on {0:?}: {1}")] BlockBuiltError(B::Hash, ConsensusError), - #[display(fmt = "Creating inherents failed: {}", _0)] + #[error("Creating inherents failed: {0}")] CreateInherents(sp_inherents::Error), - #[display(fmt = "Checking inherents failed: {}", _0)] + #[error("Checking inherents failed: {0}")] CheckInherents(sp_inherents::Error), - #[display( - fmt = "Checking inherents unknown error for identifier: {:?}", - "String::from_utf8_lossy(_0)" + #[error( + "Checking inherents unknown error for identifier: {}", + String::from_utf8_lossy(.0) )] CheckInherentsUnknownError(sp_inherents::InherentIdentifier), - #[display(fmt = "Multiple pre-runtime digests")] + #[error("Multiple pre-runtime digests")] MultiplePreRuntimeDigests, + #[error(transparent)] Client(sp_blockchain::Error), + #[error(transparent)] Codec(codec::Error), + #[error("{0}")] Environment(String), + #[error("{0}")] Runtime(RuntimeString), + #[error("{0}")] Other(String), } -impl std::convert::From> for String { +impl From> for String { fn from(error: Error) -> String { error.to_string() } } -impl std::convert::From> for ConsensusError { +impl From> for ConsensusError { fn from(error: Error) -> ConsensusError { ConsensusError::ClientImport(error.to_string()) } @@ -210,18 +209,17 @@ pub trait PowAlgorithm { } /// A block importer for PoW. -pub struct PowBlockImport { +pub struct PowBlockImport { algorithm: Algorithm, inner: I, select_chain: S, client: Arc, create_inherent_data_providers: Arc, check_inherents_after: <::Header as HeaderT>::Number, - can_author_with: CAW, } -impl Clone - for PowBlockImport +impl Clone + for PowBlockImport { fn clone(&self) -> Self { Self { @@ -230,21 +228,19 @@ impl Clone select_chain: self.select_chain.clone(), client: self.client.clone(), create_inherent_data_providers: self.create_inherent_data_providers.clone(), - check_inherents_after: self.check_inherents_after.clone(), - can_author_with: self.can_author_with.clone(), + check_inherents_after: self.check_inherents_after, } } } -impl PowBlockImport +impl PowBlockImport where B: BlockT, I: BlockImport> + Send + Sync, I::Error: Into, - C: ProvideRuntimeApi + Send + Sync + HeaderBackend + AuxStore + ProvideCache + BlockOf, + C: ProvideRuntimeApi + Send + Sync + HeaderBackend + AuxStore + BlockOf, C::Api: BlockBuilderApi, Algorithm: PowAlgorithm, - CAW: CanAuthorWith, CIDP: CreateInherentDataProviders, { /// Create a new block import suitable to be used in PoW @@ -255,7 +251,6 @@ where check_inherents_after: <::Header as HeaderT>::Number, select_chain: S, create_inherent_data_providers: CIDP, - can_author_with: CAW, ) -> Self { Self { inner, @@ -264,7 +259,6 @@ where check_inherents_after, select_chain, create_inherent_data_providers: Arc::new(create_inherent_data_providers), - can_author_with, } } @@ -279,18 +273,9 @@ where return Ok(()) } - if let Err(e) = self.can_author_with.can_author_with(&block_id) { - debug!( - target: "pow", - "Skipping `check_inherents` as authoring version is not compatible: {}", - e, - ); - - return Ok(()) - } - let inherent_data = inherent_data_providers .create_inherent_data() + .await .map_err(|e| Error::CreateInherents(e))?; let inherent_res = self @@ -313,18 +298,16 @@ where } #[async_trait::async_trait] -impl BlockImport - for PowBlockImport +impl BlockImport for PowBlockImport where B: BlockT, I: BlockImport> + Send + Sync, I::Error: Into, S: SelectChain, - C: ProvideRuntimeApi + Send + Sync + HeaderBackend + AuxStore + ProvideCache + BlockOf, + C: ProvideRuntimeApi + Send + Sync + HeaderBackend + AuxStore + BlockOf, C::Api: BlockBuilderApi, Algorithm: PowAlgorithm + Send + Sync, Algorithm::Difficulty: 'static + Send, - CAW: CanAuthorWith + Send + Sync, CIDP: CreateInherentDataProviders + Send + Sync, { type Error = ConsensusError; @@ -346,7 +329,7 @@ where .select_chain .best_chain() .await - .map_err(|e| format!("Fetch best chain failed via select chain: {:?}", e))?; + .map_err(|e| format!("Fetch best chain failed via select chain: {}", e))?; let best_hash = best_header.hash(); let parent_hash = *block.header.parent_hash(); @@ -356,23 +339,25 @@ where if let Some(inner_body) = block.body.take() { let check_block = B::new(block.header.clone(), inner_body); - self.check_inherents( - check_block.clone(), - BlockId::Hash(parent_hash), - self.create_inherent_data_providers - .create_inherent_data_providers(parent_hash, ()) - .await?, - block.origin.into(), - ) - .await?; + if !block.state_action.skip_execution_checks() { + self.check_inherents( + check_block.clone(), + BlockId::Hash(parent_hash), + self.create_inherent_data_providers + .create_inherent_data_providers(parent_hash, ()) + .await?, + block.origin.into(), + ) + .await?; + } block.body = Some(check_block.deconstruct().1); } let inner_seal = fetch_seal::(block.post_digests.last(), block.header.hash())?; - let intermediate = - block.take_intermediate::>(INTERMEDIATE_KEY)?; + let intermediate = block + .remove_intermediate::>(INTERMEDIATE_KEY)?; let difficulty = match intermediate.difficulty { Some(difficulty) => difficulty, @@ -426,10 +411,7 @@ impl PowVerifier { Self { algorithm, _marker: PhantomData } } - fn check_header( - &self, - mut header: B::Header, - ) -> Result<(B::Header, DigestItem), Error> + fn check_header(&self, mut header: B::Header) -> Result<(B::Header, DigestItem), Error> where Algorithm: PowAlgorithm, { @@ -471,9 +453,7 @@ where let intermediate = PowIntermediate:: { difficulty: None }; block.header = checked_header; block.post_digests.push(seal); - block - .intermediates - .insert(Cow::from(INTERMEDIATE_KEY), Box::new(intermediate) as Box<_>); + block.insert_intermediate(INTERMEDIATE_KEY, intermediate); block.post_hash = Some(hash); Ok((block, None)) @@ -511,21 +491,20 @@ where /// /// `pre_runtime` is a parameter that allows a custom additional pre-runtime digest to be inserted /// for blocks being built. This can encode authorship information, or just be a graffiti. -pub fn start_mining_worker( +pub fn start_mining_worker( block_import: BoxBlockImport>, client: Arc, select_chain: S, algorithm: Algorithm, mut env: E, - mut sync_oracle: SO, + sync_oracle: SO, justification_sync_link: L, pre_runtime: Option>, create_inherent_data_providers: CIDP, timeout: Duration, build_time: Duration, - can_author_with: CAW, ) -> ( - Arc>::Proof>>>, + MiningHandle>::Proof>, impl Future, ) where @@ -540,15 +519,9 @@ where SO: SyncOracle + Clone + Send + Sync + 'static, L: sc_consensus::JustificationSyncLink, CIDP: CreateInherentDataProviders, - CAW: CanAuthorWith + Clone + Send + 'static, { let mut timer = UntilImportedOrTimeout::new(client.import_notification_stream(), timeout); - let worker = Arc::new(Mutex::new(MiningWorker { - build: None, - algorithm: algorithm.clone(), - block_import, - justification_sync_link, - })); + let worker = MiningHandle::new(algorithm.clone(), block_import, justification_sync_link); let worker_ret = worker.clone(); let task = async move { @@ -559,7 +532,7 @@ where if sync_oracle.is_major_syncing() { debug!(target: "pow", "Skipping proposal due to sync."); - worker.lock().on_major_syncing(); + worker.on_major_syncing(); continue } @@ -569,7 +542,7 @@ where warn!( target: "pow", "Unable to pull new block for authoring. \ - Select best chain error: {:?}", + Select best chain error: {}", err ); continue @@ -577,17 +550,7 @@ where }; let best_hash = best_header.hash(); - if let Err(err) = can_author_with.can_author_with(&BlockId::Hash(best_hash)) { - warn!( - target: "pow", - "Skipping proposal `can_author_with` returned: {} \ - Probably a node update is required!", - err, - ); - continue - } - - if worker.lock().best_hash() == Some(best_hash) { + if worker.best_hash() == Some(best_hash) { continue } @@ -600,7 +563,7 @@ where warn!( target: "pow", "Unable to propose new block for authoring. \ - Fetch difficulty failed: {:?}", + Fetch difficulty failed: {}", err, ); continue @@ -616,27 +579,27 @@ where warn!( target: "pow", "Unable to propose new block for authoring. \ - Creating inherent data providers failed: {:?}", + Creating inherent data providers failed: {}", err, ); continue }, }; - let inherent_data = match inherent_data_providers.create_inherent_data() { + let inherent_data = match inherent_data_providers.create_inherent_data().await { Ok(r) => r, Err(e) => { warn!( target: "pow", "Unable to propose new block for authoring. \ - Creating inherent data failed: {:?}", + Creating inherent data failed: {}", e, ); continue }, }; - let mut inherent_digest = Digest::::default(); + let mut inherent_digest = Digest::default(); if let Some(pre_runtime) = &pre_runtime { inherent_digest.push(DigestItem::PreRuntime(POW_ENGINE_ID, pre_runtime.to_vec())); } @@ -656,21 +619,19 @@ where }, }; - let proposal = match proposer - .propose(inherent_data, inherent_digest, build_time.clone(), None) - .await - { - Ok(x) => x, - Err(err) => { - warn!( - target: "pow", - "Unable to propose new block for authoring. \ - Creating proposal failed: {:?}", - err, - ); - continue - }, - }; + let proposal = + match proposer.propose(inherent_data, inherent_digest, build_time, None).await { + Ok(x) => x, + Err(err) => { + warn!( + target: "pow", + "Unable to propose new block for authoring. \ + Creating proposal failed: {}", + err, + ); + continue + }, + }; let build = MiningBuild:: { metadata: MiningMetadata { @@ -682,7 +643,7 @@ where proposal, }; - worker.lock().on_build(build); + worker.on_build(build); } }; @@ -708,17 +669,14 @@ fn find_pre_digest(header: &B::Header) -> Result>, Err } /// Fetch PoW seal. -fn fetch_seal( - digest: Option<&DigestItem>, - hash: B::Hash, -) -> Result, Error> { +fn fetch_seal(digest: Option<&DigestItem>, hash: B::Hash) -> Result, Error> { match digest { Some(DigestItem::Seal(id, seal)) => if id == &POW_ENGINE_ID { Ok(seal.clone()) } else { - return Err(Error::::WrongEngine(*id).into()) + Err(Error::::WrongEngine(*id)) }, - _ => return Err(Error::::HeaderUnsealed(hash).into()), + _ => Err(Error::::HeaderUnsealed(hash)), } } diff --git a/client/consensus/pow/src/worker.rs b/client/consensus/pow/src/worker.rs index c0ca16ccad3aa..a00da6e7022fb 100644 --- a/client/consensus/pow/src/worker.rs +++ b/client/consensus/pow/src/worker.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,6 +22,7 @@ use futures::{ }; use futures_timer::Delay; use log::*; +use parking_lot::Mutex; use sc_client_api::ImportNotifications; use sc_consensus::{BlockImportParams, BoxBlockImport, StateAction, StorageChanges}; use sp_consensus::{BlockOrigin, Proposal}; @@ -30,7 +31,15 @@ use sp_runtime::{ traits::{Block as BlockT, Header as HeaderT}, DigestItem, }; -use std::{borrow::Cow, collections::HashMap, pin::Pin, time::Duration}; +use std::{ + collections::HashMap, + pin::Pin, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + time::Duration, +}; use crate::{PowAlgorithm, PowIntermediate, Seal, INTERMEDIATE_KEY, POW_ENGINE_ID}; @@ -60,21 +69,26 @@ pub struct MiningBuild< pub proposal: Proposal, Proof>, } +/// Version of the mining worker. +#[derive(Eq, PartialEq, Clone, Copy)] +pub struct Version(usize); + /// Mining worker that exposes structs to query the current mining build and submit mined blocks. -pub struct MiningWorker< +pub struct MiningHandle< Block: BlockT, Algorithm: PowAlgorithm, C: sp_api::ProvideRuntimeApi, L: sc_consensus::JustificationSyncLink, Proof, > { - pub(crate) build: Option>, - pub(crate) algorithm: Algorithm, - pub(crate) block_import: BoxBlockImport>, - pub(crate) justification_sync_link: L, + version: Arc, + algorithm: Arc, + justification_sync_link: Arc, + build: Arc>>>, + block_import: Arc>>>, } -impl MiningWorker +impl MiningHandle where Block: BlockT, C: sp_api::ProvideRuntimeApi, @@ -83,35 +97,65 @@ where L: sc_consensus::JustificationSyncLink, sp_api::TransactionFor: Send + 'static, { - /// Get the current best hash. `None` if the worker has just started or the client is doing - /// major syncing. - pub fn best_hash(&self) -> Option { - self.build.as_ref().map(|b| b.metadata.best_hash) + fn increment_version(&self) { + self.version.fetch_add(1, Ordering::SeqCst); } - pub(crate) fn on_major_syncing(&mut self) { - self.build = None; + pub(crate) fn new( + algorithm: Algorithm, + block_import: BoxBlockImport>, + justification_sync_link: L, + ) -> Self { + Self { + version: Arc::new(AtomicUsize::new(0)), + algorithm: Arc::new(algorithm), + justification_sync_link: Arc::new(justification_sync_link), + build: Arc::new(Mutex::new(None)), + block_import: Arc::new(Mutex::new(block_import)), + } } - pub(crate) fn on_build(&mut self, build: MiningBuild) { - self.build = Some(build); + pub(crate) fn on_major_syncing(&self) { + let mut build = self.build.lock(); + *build = None; + self.increment_version(); + } + + pub(crate) fn on_build(&self, value: MiningBuild) { + let mut build = self.build.lock(); + *build = Some(value); + self.increment_version(); + } + + /// Get the version of the mining worker. + /// + /// This returns type `Version` which can only compare equality. If `Version` is unchanged, then + /// it can be certain that `best_hash` and `metadata` were not changed. + pub fn version(&self) -> Version { + Version(self.version.load(Ordering::SeqCst)) + } + + /// Get the current best hash. `None` if the worker has just started or the client is doing + /// major syncing. + pub fn best_hash(&self) -> Option { + self.build.lock().as_ref().map(|b| b.metadata.best_hash) } /// Get a copy of the current mining metadata, if available. pub fn metadata(&self) -> Option> { - self.build.as_ref().map(|b| b.metadata.clone()) + self.build.lock().as_ref().map(|b| b.metadata.clone()) } /// Submit a mined seal. The seal will be validated again. Returns true if the submission is /// successful. - pub async fn submit(&mut self, seal: Seal) -> bool { - if let Some(build) = self.build.take() { + pub async fn submit(&self, seal: Seal) -> bool { + if let Some(metadata) = self.metadata() { match self.algorithm.verify( - &BlockId::Hash(build.metadata.best_hash), - &build.metadata.pre_hash, - build.metadata.pre_runtime.as_ref().map(|v| &v[..]), + &BlockId::Hash(metadata.best_hash), + &metadata.pre_hash, + metadata.pre_runtime.as_ref().map(|v| &v[..]), &seal, - build.metadata.difficulty, + metadata.difficulty, ) { Ok(true) => (), Ok(false) => { @@ -124,61 +168,95 @@ where Err(err) => { warn!( target: "pow", - "Unable to import mined block: {:?}", + "Unable to import mined block: {}", err, ); return false }, } + } else { + warn!( + target: "pow", + "Unable to import mined block: metadata does not exist", + ); + return false + } - let seal = DigestItem::Seal(POW_ENGINE_ID, seal); - let (header, body) = build.proposal.block.deconstruct(); - - let mut import_block = BlockImportParams::new(BlockOrigin::Own, header); - import_block.post_digests.push(seal); - import_block.body = Some(body); - import_block.state_action = - StateAction::ApplyChanges(StorageChanges::Changes(build.proposal.storage_changes)); - - let intermediate = PowIntermediate:: { - difficulty: Some(build.metadata.difficulty), - }; - - import_block - .intermediates - .insert(Cow::from(INTERMEDIATE_KEY), Box::new(intermediate) as Box<_>); - - let header = import_block.post_header(); - match self.block_import.import_block(import_block, HashMap::default()).await { - Ok(res) => { - res.handle_justification( - &header.hash(), - *header.number(), - &mut self.justification_sync_link, - ); - - info!( - target: "pow", - "✅ Successfully mined block on top of: {}", - build.metadata.best_hash - ); - true - }, - Err(err) => { - warn!( - target: "pow", - "Unable to import mined block: {:?}", - err, - ); - false - }, + let build = if let Some(build) = { + let mut build = self.build.lock(); + let value = build.take(); + if value.is_some() { + self.increment_version(); } + value + } { + build } else { warn!( target: "pow", "Unable to import mined block: build does not exist", ); - false + return false + }; + + let seal = DigestItem::Seal(POW_ENGINE_ID, seal); + let (header, body) = build.proposal.block.deconstruct(); + + let mut import_block = BlockImportParams::new(BlockOrigin::Own, header); + import_block.post_digests.push(seal); + import_block.body = Some(body); + import_block.state_action = + StateAction::ApplyChanges(StorageChanges::Changes(build.proposal.storage_changes)); + + let intermediate = PowIntermediate:: { + difficulty: Some(build.metadata.difficulty), + }; + import_block.insert_intermediate(INTERMEDIATE_KEY, intermediate); + + let header = import_block.post_header(); + let mut block_import = self.block_import.lock(); + + match block_import.import_block(import_block, HashMap::default()).await { + Ok(res) => { + res.handle_justification( + &header.hash(), + *header.number(), + &self.justification_sync_link, + ); + + info!( + target: "pow", + "✅ Successfully mined block on top of: {}", + build.metadata.best_hash + ); + true + }, + Err(err) => { + warn!( + target: "pow", + "Unable to import mined block: {}", + err, + ); + false + }, + } + } +} + +impl Clone for MiningHandle +where + Block: BlockT, + Algorithm: PowAlgorithm, + C: sp_api::ProvideRuntimeApi, + L: sc_consensus::JustificationSyncLink, +{ + fn clone(&self) -> Self { + Self { + version: self.version.clone(), + algorithm: self.algorithm.clone(), + justification_sync_link: self.justification_sync_link.clone(), + build: self.build.clone(), + block_import: self.block_import.clone(), } } } @@ -213,7 +291,7 @@ impl Stream for UntilImportedOrTimeout { } } - let timeout = self.timeout.clone(); + let timeout = self.timeout; let inner_delay = self.inner_delay.get_or_insert_with(|| Delay::new(timeout)); match Future::poll(Pin::new(inner_delay), cx) { diff --git a/client/consensus/slots/Cargo.toml b/client/consensus/slots/Cargo.toml index 4c0142829bb5c..4bb9387cf5596 100644 --- a/client/consensus/slots/Cargo.toml +++ b/client/consensus/slots/Cargo.toml @@ -3,10 +3,10 @@ name = "sc-consensus-slots" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Generic slots-based utilities for consensus" -edition = "2018" +edition = "2021" build = "build.rs" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -14,25 +14,23 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" +futures-timer = "3.0.1" +log = "0.4.17" +thiserror = "1.0.30" sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-arithmetic = { version = "4.0.0-dev", path = "../../../primitives/arithmetic" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.10.0-dev", path = "../../../primitives/state-machine" } -sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } +sp-arithmetic = { version = "6.0.0", path = "../../../primitives/arithmetic" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -futures = "0.3.9" -futures-timer = "3.0.1" -log = "0.4.11" -thiserror = "1.0.21" -async-trait = "0.1.50" +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } [dev-dependencies] substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/consensus/slots/build.rs b/client/consensus/slots/build.rs index 57424f016f3e5..b700b28e322ca 100644 --- a/client/consensus/slots/build.rs +++ b/client/consensus/slots/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/consensus/slots/src/aux_schema.rs b/client/consensus/slots/src/aux_schema.rs index c2fe3f6f4e6bb..c1d01500ffe47 100644 --- a/client/consensus/slots/src/aux_schema.rs +++ b/client/consensus/slots/src/aux_schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -63,7 +63,7 @@ where P: Clone + Encode + Decode + PartialEq, { // We don't check equivocations for old headers out of our capacity. - if slot_now.saturating_sub(*slot) > Slot::from(MAX_SLOT_CAPACITY) { + if slot_now.saturating_sub(*slot) > MAX_SLOT_CAPACITY { return Ok(None) } @@ -89,8 +89,8 @@ where // 1) signed by the same voter, if prev_signer == signer { // 2) with different hash - if header.hash() != prev_header.hash() { - return Ok(Some(EquivocationProof { + return if header.hash() != prev_header.hash() { + Ok(Some(EquivocationProof { slot, offender: signer.clone(), first_header: prev_header.clone(), @@ -100,7 +100,7 @@ where // We don't need to continue in case of duplicated header, // since it's already saved and a possible equivocation // would have been detected before. - return Ok(None) + Ok(None) } } } diff --git a/client/consensus/slots/src/lib.rs b/client/consensus/slots/src/lib.rs index bfaa388014ef0..bc68797dc734e 100644 --- a/client/consensus/slots/src/lib.rs +++ b/client/consensus/slots/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -32,29 +32,27 @@ pub use aux_schema::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND}; pub use slots::SlotInfo; use slots::Slots; -use codec::{Decode, Encode}; use futures::{future::Either, Future, TryFutureExt}; use futures_timer::Delay; -use log::{debug, error, info, warn}; +use log::{debug, info, warn}; use sc_consensus::{BlockImport, JustificationSyncLink}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO, CONSENSUS_WARN}; -use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_arithmetic::traits::BaseArithmetic; -use sp_consensus::{CanAuthorWith, Proposer, SelectChain, SlotData, SyncOracle}; -use sp_consensus_slots::Slot; +use sp_consensus::{Proposal, Proposer, SelectChain, SyncOracle}; +use sp_consensus_slots::{Slot, SlotDuration}; use sp_inherents::CreateInherentDataProviders; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, HashFor, Header as HeaderT, NumberFor}, +use sp_runtime::traits::{Block as BlockT, HashFor, Header as HeaderT}; +use std::{ + fmt::Debug, + ops::Deref, + time::{Duration, Instant}, }; -use sp_timestamp::Timestamp; -use std::{fmt::Debug, ops::Deref, time::Duration}; /// The changes that need to applied to the storage to create the state for a block. /// /// See [`sp_state_machine::StorageChanges`] for more information. pub type StorageChanges = - sp_state_machine::StorageChanges, NumberFor>; + sp_state_machine::StorageChanges>; /// The result of [`SlotWorker::on_slot`]. #[derive(Debug, Clone)] @@ -105,10 +103,10 @@ pub trait SimpleSlotWorker { type Proposer: Proposer + Send; /// Data associated with a slot claim. - type Claim: Send + 'static; + type Claim: Send + Sync + 'static; - /// Epoch data necessary for authoring. - type EpochData: Send + Sync + 'static; + /// Auxiliary data necessary for authoring. + type AuxData: Send + Sync + 'static; /// The logging target to use when logging messages. fn logging_target(&self) -> &'static str; @@ -116,56 +114,44 @@ pub trait SimpleSlotWorker { /// A handle to a `BlockImport`. fn block_import(&mut self) -> &mut Self::BlockImport; - /// Returns the epoch data necessary for authoring. For time-dependent epochs, - /// use the provided slot number as a canonical source of time. - fn epoch_data( + /// Returns the auxiliary data necessary for authoring. + fn aux_data( &self, header: &B::Header, slot: Slot, - ) -> Result; + ) -> Result; - /// Returns the number of authorities given the epoch data. + /// Returns the number of authorities. /// None indicate that the authorities information is incomplete. - fn authorities_len(&self, epoch_data: &Self::EpochData) -> Option; + fn authorities_len(&self, aux_data: &Self::AuxData) -> Option; /// Tries to claim the given slot, returning an object with claim data if successful. async fn claim_slot( &self, header: &B::Header, slot: Slot, - epoch_data: &Self::EpochData, + aux_data: &Self::AuxData, ) -> Option; /// Notifies the given slot. Similar to `claim_slot`, but will be called no matter whether we /// need to author blocks or not. - fn notify_slot(&self, _header: &B::Header, _slot: Slot, _epoch_data: &Self::EpochData) {} + fn notify_slot(&self, _header: &B::Header, _slot: Slot, _aux_data: &Self::AuxData) {} /// Return the pre digest data to include in a block authored with the given claim. - fn pre_digest_data( - &self, - slot: Slot, - claim: &Self::Claim, - ) -> Vec>; + fn pre_digest_data(&self, slot: Slot, claim: &Self::Claim) -> Vec; /// Returns a function which produces a `BlockImportParams`. - fn block_import_params( + async fn block_import_params( &self, - ) -> Box< - dyn Fn( - B::Header, - &B::Hash, - Vec, - StorageChanges<>::Transaction, B>, - Self::Claim, - Self::EpochData, - ) -> Result< - sc_consensus::BlockImportParams< - B, - >::Transaction, - >, - sp_consensus::Error, - > + Send - + 'static, + header: B::Header, + header_hash: &B::Hash, + body: Vec, + storage_changes: StorageChanges<>::Transaction, B>, + public: Self::Claim, + epoch: Self::AuxData, + ) -> Result< + sc_consensus::BlockImportParams>::Transaction>, + sp_consensus::Error, >; /// Whether to force authoring if offline. @@ -196,6 +182,108 @@ pub trait SimpleSlotWorker { /// Remaining duration for proposing. fn proposing_remaining_duration(&self, slot_info: &SlotInfo) -> Duration; + /// Propose a block by `Proposer`. + async fn propose( + &mut self, + proposer: Self::Proposer, + claim: &Self::Claim, + slot_info: SlotInfo, + proposing_remaining: Delay, + ) -> Option< + Proposal< + B, + >::Transaction, + >::Proof, + >, + > { + let slot = slot_info.slot; + let telemetry = self.telemetry(); + let logging_target = self.logging_target(); + + let inherent_data = Self::create_inherent_data(&slot_info, &logging_target).await?; + + let proposing_remaining_duration = self.proposing_remaining_duration(&slot_info); + let logs = self.pre_digest_data(slot, claim); + + // deadline our production to 98% of the total time left for proposing. As we deadline + // the proposing below to the same total time left, the 2% margin should be enough for + // the result to be returned. + let proposing = proposer + .propose( + inherent_data, + sp_runtime::generic::Digest { logs }, + proposing_remaining_duration.mul_f32(0.98), + None, + ) + .map_err(|e| sp_consensus::Error::ClientImport(e.to_string())); + + let proposal = match futures::future::select(proposing, proposing_remaining).await { + Either::Left((Ok(p), _)) => p, + Either::Left((Err(err), _)) => { + warn!(target: logging_target, "Proposing failed: {}", err); + + return None + }, + Either::Right(_) => { + info!( + target: logging_target, + "⌛️ Discarding proposal for slot {}; block production took too long", slot, + ); + // If the node was compiled with debug, tell the user to use release optimizations. + #[cfg(build_type = "debug")] + info!( + target: logging_target, + "👉 Recompile your node in `--release` mode to mitigate this problem.", + ); + telemetry!( + telemetry; + CONSENSUS_INFO; + "slots.discarding_proposal_took_too_long"; + "slot" => *slot, + ); + + return None + }, + }; + + Some(proposal) + } + + /// Calls `create_inherent_data` and handles errors. + async fn create_inherent_data( + slot_info: &SlotInfo, + logging_target: &str, + ) -> Option { + let remaining_duration = slot_info.ends_at.saturating_duration_since(Instant::now()); + let delay = Delay::new(remaining_duration); + let cid = slot_info.create_inherent_data.create_inherent_data(); + let inherent_data = match futures::future::select(delay, cid).await { + Either::Right((Ok(data), _)) => data, + Either::Right((Err(err), _)) => { + warn!( + target: logging_target, + "Unable to create inherent data for block {:?}: {}", + slot_info.chain_head.hash(), + err, + ); + + return None + }, + Either::Left(_) => { + warn!( + target: logging_target, + "Creating inherent data took more time than we had left for slot {} for block {:?}.", + slot_info.slot, + slot_info.chain_head.hash(), + ); + + return None + }, + }; + + Some(inherent_data) + } + /// Implements [`SlotWorker::on_slot`]. async fn on_slot( &mut self, @@ -204,7 +292,7 @@ pub trait SimpleSlotWorker { where Self: Sync, { - let (timestamp, slot) = (slot_info.timestamp, slot_info.slot); + let slot = slot_info.slot; let telemetry = self.telemetry(); let logging_target = self.logging_target(); @@ -221,12 +309,12 @@ pub trait SimpleSlotWorker { Delay::new(proposing_remaining_duration) }; - let epoch_data = match self.epoch_data(&slot_info.chain_head, slot) { - Ok(epoch_data) => epoch_data, + let aux_data = match self.aux_data(&slot_info.chain_head, slot) { + Ok(aux_data) => aux_data, Err(err) => { warn!( target: logging_target, - "Unable to fetch epoch data at block {:?}: {:?}", + "Unable to fetch auxiliary data for block {:?}: {}", slot_info.chain_head.hash(), err, ); @@ -243,9 +331,9 @@ pub trait SimpleSlotWorker { }, }; - self.notify_slot(&slot_info.chain_head, slot, &epoch_data); + self.notify_slot(&slot_info.chain_head, slot, &aux_data); - let authorities_len = self.authorities_len(&epoch_data); + let authorities_len = self.authorities_len(&aux_data); if !self.force_authoring() && self.sync_oracle().is_offline() && @@ -262,34 +350,20 @@ pub trait SimpleSlotWorker { return None } - let claim = self.claim_slot(&slot_info.chain_head, slot, &epoch_data).await?; + let claim = self.claim_slot(&slot_info.chain_head, slot, &aux_data).await?; if self.should_backoff(slot, &slot_info.chain_head) { return None } - debug!( - target: self.logging_target(), - "Starting authorship at slot {}; timestamp = {}", - slot, - *timestamp, - ); + debug!(target: logging_target, "Starting authorship at slot: {slot}"); - telemetry!( - telemetry; - CONSENSUS_DEBUG; - "slots.starting_authorship"; - "slot_num" => *slot, - "timestamp" => *timestamp, - ); + telemetry!(telemetry; CONSENSUS_DEBUG; "slots.starting_authorship"; "slot_num" => slot); let proposer = match self.proposer(&slot_info.chain_head).await { Ok(p) => p, Err(err) => { - warn!( - target: logging_target, - "Unable to author block in slot {:?}: {:?}", slot, err, - ); + warn!(target: logging_target, "Unable to author block in slot {slot:?}: {err}"); telemetry!( telemetry; @@ -303,51 +377,7 @@ pub trait SimpleSlotWorker { }, }; - let logs = self.pre_digest_data(slot, &claim); - - // deadline our production to 98% of the total time left for proposing. As we deadline - // the proposing below to the same total time left, the 2% margin should be enough for - // the result to be returned. - let proposing = proposer - .propose( - slot_info.inherent_data, - sp_runtime::generic::Digest { logs }, - proposing_remaining_duration.mul_f32(0.98), - None, - ) - .map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e))); - - let proposal = match futures::future::select(proposing, proposing_remaining).await { - Either::Left((Ok(p), _)) => p, - Either::Left((Err(err), _)) => { - warn!(target: logging_target, "Proposing failed: {:?}", err); - - return None - }, - Either::Right(_) => { - info!( - target: logging_target, - "⌛️ Discarding proposal for slot {}; block production took too long", slot, - ); - // If the node was compiled with debug, tell the user to use release optimizations. - #[cfg(build_type = "debug")] - info!( - target: logging_target, - "👉 Recompile your node in `--release` mode to mitigate this problem.", - ); - telemetry!( - telemetry; - CONSENSUS_INFO; - "slots.discarding_proposal_took_too_long"; - "slot" => *slot, - ); - - return None - }, - }; - - let block_import_params_maker = self.block_import_params(); - let block_import = self.block_import(); + let proposal = self.propose(proposer, &claim, slot_info, proposing_remaining).await?; let (block, storage_proof) = (proposal.block, proposal.proof); let (header, body) = block.deconstruct(); @@ -355,17 +385,20 @@ pub trait SimpleSlotWorker { let header_hash = header.hash(); let parent_hash = *header.parent_hash(); - let block_import_params = match block_import_params_maker( - header, - &header_hash, - body.clone(), - proposal.storage_changes, - claim, - epoch_data, - ) { + let block_import_params = match self + .block_import_params( + header, + &header_hash, + body.clone(), + proposal.storage_changes, + claim, + aux_data, + ) + .await + { Ok(bi) => bi, Err(err) => { - warn!(target: logging_target, "Failed to create block import params: {:?}", err); + warn!(target: logging_target, "Failed to create block import params: {}", err); return None }, @@ -389,7 +422,7 @@ pub trait SimpleSlotWorker { ); let header = block_import_params.post_header(); - match block_import.import_block(block_import_params, Default::default()).await { + match self.block_import().import_block(block_import_params, Default::default()).await { Ok(res) => { res.handle_justification( &header.hash(), @@ -400,7 +433,7 @@ pub trait SimpleSlotWorker { Err(err) => { warn!( target: logging_target, - "Error with block built on {:?}: {:?}", parent_hash, err, + "Error with block built on {:?}: {}", parent_hash, err, ); telemetry!( @@ -417,90 +450,82 @@ pub trait SimpleSlotWorker { } } +/// A type that implements [`SlotWorker`] for a type that implements [`SimpleSlotWorker`]. +/// +/// This is basically a workaround for Rust not supporting specialization. Otherwise we could +/// implement [`SlotWorker`] for any `T` that implements [`SimpleSlotWorker`], but currently +/// that would prevent downstream users to implement [`SlotWorker`] for their own types. +pub struct SimpleSlotWorkerToSlotWorker(pub T); + #[async_trait::async_trait] -impl + Send + Sync> - SlotWorker>::Proof> for T +impl + Send + Sync, B: BlockT> + SlotWorker>::Proof> for SimpleSlotWorkerToSlotWorker { async fn on_slot( &mut self, slot_info: SlotInfo, ) -> Option>::Proof>> { - SimpleSlotWorker::on_slot(self, slot_info).await + self.0.on_slot(slot_info).await } } /// Slot specific extension that the inherent data provider needs to implement. pub trait InherentDataProviderExt { - /// The current timestamp that will be found in the - /// [`InherentData`](`sp_inherents::InherentData`). - fn timestamp(&self) -> Timestamp; - /// The current slot that will be found in the [`InherentData`](`sp_inherents::InherentData`). fn slot(&self) -> Slot; } /// Small macro for implementing `InherentDataProviderExt` for inherent data provider tuple. macro_rules! impl_inherent_data_provider_ext_tuple { - ( T, S $(, $TN:ident)* $( , )?) => { - impl InherentDataProviderExt for (T, S, $($TN),*) + ( S $(, $TN:ident)* $( , )?) => { + impl InherentDataProviderExt for (S, $($TN),*) where - T: Deref, S: Deref, { - fn timestamp(&self) -> Timestamp { - *self.0.deref() - } - fn slot(&self) -> Slot { - *self.1.deref() + *self.0.deref() } } } } -impl_inherent_data_provider_ext_tuple!(T, S); -impl_inherent_data_provider_ext_tuple!(T, S, A); -impl_inherent_data_provider_ext_tuple!(T, S, A, B); -impl_inherent_data_provider_ext_tuple!(T, S, A, B, C); -impl_inherent_data_provider_ext_tuple!(T, S, A, B, C, D); -impl_inherent_data_provider_ext_tuple!(T, S, A, B, C, D, E); -impl_inherent_data_provider_ext_tuple!(T, S, A, B, C, D, E, F); -impl_inherent_data_provider_ext_tuple!(T, S, A, B, C, D, E, F, G); -impl_inherent_data_provider_ext_tuple!(T, S, A, B, C, D, E, F, G, H); -impl_inherent_data_provider_ext_tuple!(T, S, A, B, C, D, E, F, G, H, I); -impl_inherent_data_provider_ext_tuple!(T, S, A, B, C, D, E, F, G, H, I, J); +impl_inherent_data_provider_ext_tuple!(S); +impl_inherent_data_provider_ext_tuple!(S, A); +impl_inherent_data_provider_ext_tuple!(S, A, B); +impl_inherent_data_provider_ext_tuple!(S, A, B, C); +impl_inherent_data_provider_ext_tuple!(S, A, B, C, D); +impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E); +impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E, F); +impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E, F, G); +impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E, F, G, H); +impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E, F, G, H, I); +impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E, F, G, H, I, J); /// Start a new slot worker. /// /// Every time a new slot is triggered, `worker.on_slot` is called and the future it returns is /// polled until completion, unless we are major syncing. -pub async fn start_slot_worker( - slot_duration: SlotDuration, +pub async fn start_slot_worker( + slot_duration: SlotDuration, client: C, mut worker: W, - mut sync_oracle: SO, + sync_oracle: SO, create_inherent_data_providers: CIDP, - can_author_with: CAW, ) where B: BlockT, C: SelectChain, W: SlotWorker, SO: SyncOracle + Send, - T: SlotData + Clone, - CIDP: CreateInherentDataProviders + Send, + CIDP: CreateInherentDataProviders + Send + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send, - CAW: CanAuthorWith + Send, { - let SlotDuration(slot_duration) = slot_duration; - - let mut slots = - Slots::new(slot_duration.slot_duration(), create_inherent_data_providers, client); + let mut slots = Slots::new(slot_duration.as_duration(), create_inherent_data_providers, client); loop { let slot_info = match slots.next_slot().await { Ok(r) => r, Err(e) => { - warn!(target: "slots", "Error while polling for next slot: {:?}", e); + warn!(target: "slots", "Error while polling for next slot: {}", e); return }, }; @@ -510,19 +535,7 @@ pub async fn start_slot_worker( continue } - if let Err(err) = - can_author_with.can_author_with(&BlockId::Hash(slot_info.chain_head.hash())) - { - warn!( - target: "slots", - "Unable to author block in slot {},. `can_author_with` returned: {} \ - Probably a node update is required!", - slot_info.slot, - err, - ); - } else { - let _ = worker.on_slot(slot_info).await; - } + let _ = worker.on_slot(slot_info).await; } } @@ -538,89 +551,6 @@ pub enum CheckedHeader { Checked(H, S), } -#[derive(Debug, thiserror::Error)] -#[allow(missing_docs)] -pub enum Error -where - T: Debug, -{ - #[error("Slot duration is invalid: {0:?}")] - SlotDurationInvalid(SlotDuration), -} - -/// A slot duration. Create with [`get_or_compute`](Self::get_or_compute). -// The internal member should stay private here to maintain invariants of -// `get_or_compute`. -#[derive(Clone, Copy, Debug, Encode, Decode, Hash, PartialOrd, Ord, PartialEq, Eq)] -pub struct SlotDuration(T); - -impl Deref for SlotDuration { - type Target = T; - fn deref(&self) -> &T { - &self.0 - } -} - -impl SlotData for SlotDuration { - fn slot_duration(&self) -> std::time::Duration { - self.0.slot_duration() - } - - const SLOT_KEY: &'static [u8] = T::SLOT_KEY; -} - -impl SlotDuration { - /// Either fetch the slot duration from disk or compute it from the - /// genesis state. - /// - /// `slot_key` is marked as `'static`, as it should really be a - /// compile-time constant. - pub fn get_or_compute(client: &C, cb: CB) -> sp_blockchain::Result - where - C: sc_client_api::backend::AuxStore + sc_client_api::UsageProvider, - C: ProvideRuntimeApi, - CB: FnOnce(ApiRef, &BlockId) -> sp_blockchain::Result, - T: SlotData + Encode + Decode + Debug, - { - let slot_duration = match client.get_aux(T::SLOT_KEY)? { - Some(v) => ::decode(&mut &v[..]).map(SlotDuration).map_err(|_| { - sp_blockchain::Error::Backend({ - error!(target: "slots", "slot duration kept in invalid format"); - "slot duration kept in invalid format".to_string() - }) - }), - None => { - let best_hash = client.usage_info().chain.best_hash; - let slot_duration = cb(client.runtime_api(), &BlockId::hash(best_hash))?; - - info!( - "⏱ Loaded block-time = {:?} from block {:?}", - slot_duration.slot_duration(), - best_hash, - ); - - slot_duration - .using_encoded(|s| client.insert_aux(&[(T::SLOT_KEY, &s[..])], &[]))?; - - Ok(SlotDuration(slot_duration)) - }, - }?; - - if slot_duration.slot_duration() == Default::default() { - return Err(sp_blockchain::Error::Application(Box::new(Error::SlotDurationInvalid( - slot_duration, - )))) - } - - Ok(slot_duration) - } - - /// Returns slot data value. - pub fn get(&self) -> T { - self.0.clone() - } -} - /// A unit type wrapper to express the proportion of a slot. pub struct SlotProportion(f32); @@ -702,7 +632,7 @@ pub fn proposing_remaining_duration( // if we defined a maximum portion of the slot for proposal then we must make sure the // lenience doesn't go over it let lenient_proposing_duration = - if let Some(ref max_block_proposal_slot_portion) = max_block_proposal_slot_portion { + if let Some(max_block_proposal_slot_portion) = max_block_proposal_slot_portion { std::cmp::min( lenient_proposing_duration, slot_info.duration.mul_f32(max_block_proposal_slot_portion.get()), @@ -713,10 +643,10 @@ pub fn proposing_remaining_duration( debug!( target: log_target, - "No block for {} slots. Applying {} lenience, total proposing duration: {}", + "No block for {} slots. Applying {} lenience, total proposing duration: {}ms", slot_info.slot.saturating_sub(parent_slot + 1), slot_lenience_type.as_str(), - lenient_proposing_duration.as_secs(), + lenient_proposing_duration.as_millis(), ); lenient_proposing_duration @@ -848,7 +778,9 @@ where return false } - let unfinalized_block_length = chain_head_number - finalized_number; + // There can be race between getting the finalized number and getting the best number. + // So, better be safe than sorry. + let unfinalized_block_length = chain_head_number.saturating_sub(finalized_number); let interval = unfinalized_block_length.saturating_sub(self.unfinalized_slack) / self.authoring_bias; let interval = interval.min(self.max_interval); @@ -886,7 +818,7 @@ impl BackoffAuthoringBlocksStrategy for () { #[cfg(test)] mod test { use super::*; - use sp_api::NumberFor; + use sp_runtime::traits::NumberFor; use std::time::{Duration, Instant}; use substrate_test_runtime_client::runtime::{Block, Header}; @@ -896,8 +828,7 @@ mod test { super::slots::SlotInfo { slot: slot.into(), duration: SLOT_DURATION, - timestamp: Default::default(), - inherent_data: Default::default(), + create_inherent_data: Box::new(()), ends_at: Instant::now() + SLOT_DURATION, chain_head: Header::new( 1, @@ -1229,7 +1160,7 @@ mod test { // But lets assert all distances, which we expect to grow linearly until `max_interval + 1` let expected_intervals: Vec<_> = - (0..497).map(|i| (i / 2).max(1).min(expected_distance)).collect(); + (0..497).map(|i| (i / 2).clamp(1, expected_distance)).collect(); assert_eq!(intervals, expected_intervals); } diff --git a/client/consensus/slots/src/slots.rs b/client/consensus/slots/src/slots.rs index c2ed986e1e7f8..f8f366d89c82c 100644 --- a/client/consensus/slots/src/slots.rs +++ b/client/consensus/slots/src/slots.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,7 +22,7 @@ use super::{InherentDataProviderExt, Slot}; use sp_consensus::{Error, SelectChain}; -use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider}; +use sp_inherents::{CreateInherentDataProviders, InherentDataProvider}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use futures_timer::Delay; @@ -50,12 +50,10 @@ pub fn time_until_next_slot(slot_duration: Duration) -> Duration { pub struct SlotInfo { /// The slot number as found in the inherent data. pub slot: Slot, - /// Current timestamp as found in the inherent data. - pub timestamp: sp_timestamp::Timestamp, /// The instant at which the slot ends. pub ends_at: Instant, - /// The inherent data. - pub inherent_data: InherentData, + /// The inherent data provider. + pub create_inherent_data: Box, /// Slot duration. pub duration: Duration, /// The chain header this slot is based on. @@ -72,16 +70,14 @@ impl SlotInfo { /// `ends_at` is calculated using `timestamp` and `duration`. pub fn new( slot: Slot, - timestamp: sp_timestamp::Timestamp, - inherent_data: InherentData, + create_inherent_data: Box, duration: Duration, chain_head: B::Header, block_size_limit: Option, ) -> Self { Self { slot, - timestamp, - inherent_data, + create_inherent_data, duration, chain_head, block_size_limit, @@ -91,34 +87,38 @@ impl SlotInfo { } /// A stream that returns every time there is a new slot. -pub(crate) struct Slots { +pub(crate) struct Slots { last_slot: Slot, slot_duration: Duration, inner_delay: Option, create_inherent_data_providers: IDP, - client: C, + select_chain: SC, _phantom: std::marker::PhantomData, } -impl Slots { +impl Slots { /// Create a new `Slots` stream. - pub fn new(slot_duration: Duration, create_inherent_data_providers: IDP, client: C) -> Self { + pub fn new( + slot_duration: Duration, + create_inherent_data_providers: IDP, + select_chain: SC, + ) -> Self { Slots { last_slot: 0.into(), slot_duration, inner_delay: None, create_inherent_data_providers, - client, + select_chain, _phantom: Default::default(), } } } -impl Slots +impl Slots where Block: BlockT, - C: SelectChain, - IDP: CreateInherentDataProviders, + SC: SelectChain, + IDP: CreateInherentDataProviders + 'static, IDP::InherentDataProviders: crate::InherentDataProviderExt, { /// Returns a future that fires when the next slot starts. @@ -142,15 +142,12 @@ where // reschedule delay for next slot. self.inner_delay = Some(Delay::new(ends_in)); - - let ends_at = Instant::now() + ends_in; - - let chain_head = match self.client.best_chain().await { + let chain_head = match self.select_chain.best_chain().await { Ok(x) => x, Err(e) => { log::warn!( target: "slots", - "Unable to author block in slot. No best block header: {:?}", + "Unable to author block in slot. No best block header: {}", e, ); // Let's try at the next slot.. @@ -164,16 +161,7 @@ where .create_inherent_data_providers(chain_head.hash(), ()) .await?; - if Instant::now() > ends_at { - log::warn!( - target: "slots", - "Creating inherent data providers took more time than we had left for the slot.", - ); - } - - let timestamp = inherent_data_providers.timestamp(); let slot = inherent_data_providers.slot(); - let inherent_data = inherent_data_providers.create_inherent_data()?; // never yield the same slot twice. if slot > self.last_slot { @@ -181,8 +169,7 @@ where break Ok(SlotInfo::new( slot, - timestamp, - inherent_data, + Box::new(inherent_data_providers), self.slot_duration, chain_head, None, diff --git a/client/consensus/uncles/Cargo.toml b/client/consensus/uncles/Cargo.toml index 7e821db197b3c..0be48659f9c77 100644 --- a/client/consensus/uncles/Cargo.toml +++ b/client/consensus/uncles/Cargo.toml @@ -3,9 +3,9 @@ name = "sc-consensus-uncles" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Generic uncle inclusion utilities for consensus" -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +thiserror = "1.0.30" sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } sp-authorship = { version = "4.0.0-dev", path = "../../../primitives/authorship" } -thiserror = "1.0.21" +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/client/consensus/uncles/src/lib.rs b/client/consensus/uncles/src/lib.rs index 368a994cfe520..d03c2f8aa6b49 100644 --- a/client/consensus/uncles/src/lib.rs +++ b/client/consensus/uncles/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index 1d3d76ee7a55a..ee879a161edfe 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-client-db" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Client backend that uses RocksDB database as storage." readme = "README.md" @@ -13,37 +13,46 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -parking_lot = "0.11.1" -log = "0.4.8" -kvdb = "0.10.0" -kvdb-rocksdb = { version = "0.14.0", optional = true } -kvdb-memorydb = "0.10.0" -linked-hash-map = "0.5.4" -hash-db = "0.15.2" -codec = { package = "parity-scale-codec", version = "2.0.0", features = [ - "derive", +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", ] } - +hash-db = "0.15.2" +kvdb = "0.13.0" +kvdb-memorydb = "0.13.0" +kvdb-rocksdb = { version = "0.17.0", optional = true } +linked-hash-map = "0.5.4" +log = "0.4.17" +parity-db = "0.4.2" +parking_lot = "0.12.1" sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-arithmetic = { version = "4.0.0-dev", path = "../../primitives/arithmetic" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sp-state-machine = { version = "0.10.0-dev", path = "../../primitives/state-machine" } sc-state-db = { version = "0.10.0-dev", path = "../state-db" } -sp-trie = { version = "4.0.0-dev", path = "../../primitives/trie" } +sp-arithmetic = { version = "6.0.0", path = "../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } -parity-db = { version = "0.3.1", optional = true } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } +sp-trie = { version = "7.0.0", path = "../../primitives/trie" } [dev-dependencies] -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } +criterion = "0.3.3" +kvdb-rocksdb = "0.17.0" +rand = "0.8.4" +tempfile = "3.1.0" +quickcheck = { version = "1.0.3", default-features = false } +kitchensink-runtime = { path = "../../bin/node/runtime" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -quickcheck = "1.0.3" -kvdb-rocksdb = "0.14.0" -tempfile = "3" [features] default = [] test-helpers = [] -with-kvdb-rocksdb = ["kvdb-rocksdb"] -with-parity-db = ["parity-db"] +runtime-benchmarks = [] +rocksdb = ["kvdb-rocksdb"] + +[[bench]] +name = "state_access" +harness = false + +[lib] +bench = false diff --git a/client/db/benches/state_access.rs b/client/db/benches/state_access.rs new file mode 100644 index 0000000000000..bab79fe7c90db --- /dev/null +++ b/client/db/benches/state_access.rs @@ -0,0 +1,311 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use rand::{distributions::Uniform, rngs::StdRng, Rng, SeedableRng}; +use sc_client_api::{Backend as _, BlockImportOperation, NewBlockState, StateBackend}; +use sc_client_db::{Backend, BlocksPruning, DatabaseSettings, DatabaseSource, PruningMode}; +use sp_core::H256; +use sp_runtime::{ + testing::{Block as RawBlock, ExtrinsicWrapper, Header}, + StateVersion, Storage, +}; +use tempfile::TempDir; + +pub(crate) type Block = RawBlock>; + +fn insert_blocks(db: &Backend, storage: Vec<(Vec, Vec)>) -> H256 { + let mut op = db.begin_operation().unwrap(); + let mut header = Header { + number: 0, + parent_hash: Default::default(), + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + header.state_root = op + .set_genesis_state( + Storage { + top: vec![( + sp_core::storage::well_known_keys::CODE.to_vec(), + kitchensink_runtime::wasm_binary_unwrap().to_vec(), + )] + .into_iter() + .collect(), + children_default: Default::default(), + }, + true, + StateVersion::V1, + ) + .unwrap(); + + op.set_block_data(header.clone(), Some(vec![]), None, None, NewBlockState::Best) + .unwrap(); + + db.commit_operation(op).unwrap(); + + let mut number = 1; + let mut parent_hash = header.hash(); + + for i in 0..10 { + let mut op = db.begin_operation().unwrap(); + + db.begin_state_operation(&mut op, parent_hash).unwrap(); + + let mut header = Header { + number, + parent_hash, + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + let changes = storage + .iter() + .skip(i * 100_000) + .take(100_000) + .map(|(k, v)| (k.clone(), Some(v.clone()))) + .collect::>(); + + let (state_root, tx) = db.state_at(parent_hash).unwrap().storage_root( + changes.iter().map(|(k, v)| (k.as_slice(), v.as_deref())), + StateVersion::V1, + ); + header.state_root = state_root; + + op.update_db_storage(tx).unwrap(); + op.update_storage(changes.clone(), Default::default()).unwrap(); + + op.set_block_data(header.clone(), Some(vec![]), None, None, NewBlockState::Best) + .unwrap(); + + db.commit_operation(op).unwrap(); + + number += 1; + parent_hash = header.hash(); + } + + parent_hash +} + +enum BenchmarkConfig { + NoCache, + TrieNodeCache, +} + +fn create_backend(config: BenchmarkConfig, temp_dir: &TempDir) -> Backend { + let path = temp_dir.path().to_owned(); + + let trie_cache_maximum_size = match config { + BenchmarkConfig::NoCache => None, + BenchmarkConfig::TrieNodeCache => Some(2 * 1024 * 1024 * 1024), + }; + + let settings = DatabaseSettings { + trie_cache_maximum_size, + state_pruning: Some(PruningMode::ArchiveAll), + source: DatabaseSource::ParityDb { path }, + blocks_pruning: BlocksPruning::KeepAll, + }; + + Backend::new(settings, 100).expect("Creates backend") +} + +/// Generate the storage that will be used for the benchmark +/// +/// Returns the `Vec` and the `Vec<(key, value)>` +fn generate_storage() -> (Vec>, Vec<(Vec, Vec)>) { + let mut rng = StdRng::seed_from_u64(353893213); + + let mut storage = Vec::new(); + let mut keys = Vec::new(); + + for _ in 0..1_000_000 { + let key_len: usize = rng.gen_range(32..128); + let key = (&mut rng) + .sample_iter(Uniform::new_inclusive(0, 255)) + .take(key_len) + .collect::>(); + + let value_len: usize = rng.gen_range(20..60); + let value = (&mut rng) + .sample_iter(Uniform::new_inclusive(0, 255)) + .take(value_len) + .collect::>(); + + keys.push(key.clone()); + storage.push((key, value)); + } + + (keys, storage) +} + +fn state_access_benchmarks(c: &mut Criterion) { + sp_tracing::try_init_simple(); + + let (keys, storage) = generate_storage(); + let path = TempDir::new().expect("Creates temporary directory"); + + let block_hash = { + let backend = create_backend(BenchmarkConfig::NoCache, &path); + insert_blocks(&backend, storage.clone()) + }; + + let mut group = c.benchmark_group("Reading entire state"); + group.sample_size(20); + + let mut bench_multiple_values = |config, desc, multiplier| { + let backend = create_backend(config, &path); + + group.bench_function(desc, |b| { + b.iter_batched( + || backend.state_at(block_hash).expect("Creates state"), + |state| { + for key in keys.iter().cycle().take(keys.len() * multiplier) { + let _ = state.storage(&key).expect("Doesn't fail").unwrap(); + } + }, + BatchSize::SmallInput, + ) + }); + }; + + bench_multiple_values( + BenchmarkConfig::TrieNodeCache, + "with trie node cache and reading each key once", + 1, + ); + bench_multiple_values(BenchmarkConfig::NoCache, "no cache and reading each key once", 1); + + bench_multiple_values( + BenchmarkConfig::TrieNodeCache, + "with trie node cache and reading 4 times each key in a row", + 4, + ); + bench_multiple_values( + BenchmarkConfig::NoCache, + "no cache and reading 4 times each key in a row", + 4, + ); + + group.finish(); + + let mut group = c.benchmark_group("Reading a single value"); + + let mut bench_single_value = |config, desc, multiplier| { + let backend = create_backend(config, &path); + + group.bench_function(desc, |b| { + b.iter_batched( + || backend.state_at(block_hash).expect("Creates state"), + |state| { + for key in keys.iter().take(1).cycle().take(multiplier) { + let _ = state.storage(&key).expect("Doesn't fail").unwrap(); + } + }, + BatchSize::SmallInput, + ) + }); + }; + + bench_single_value( + BenchmarkConfig::TrieNodeCache, + "with trie node cache and reading the key once", + 1, + ); + bench_single_value(BenchmarkConfig::NoCache, "no cache and reading the key once", 1); + + bench_single_value( + BenchmarkConfig::TrieNodeCache, + "with trie node cache and reading 4 times each key in a row", + 4, + ); + bench_single_value( + BenchmarkConfig::NoCache, + "no cache and reading 4 times each key in a row", + 4, + ); + + group.finish(); + + let mut group = c.benchmark_group("Hashing a value"); + + let mut bench_single_value = |config, desc, multiplier| { + let backend = create_backend(config, &path); + + group.bench_function(desc, |b| { + b.iter_batched( + || backend.state_at(block_hash).expect("Creates state"), + |state| { + for key in keys.iter().take(1).cycle().take(multiplier) { + let _ = state.storage_hash(&key).expect("Doesn't fail").unwrap(); + } + }, + BatchSize::SmallInput, + ) + }); + }; + + bench_single_value( + BenchmarkConfig::TrieNodeCache, + "with trie node cache and hashing the key once", + 1, + ); + bench_single_value(BenchmarkConfig::NoCache, "no cache and hashing the key once", 1); + + bench_single_value( + BenchmarkConfig::TrieNodeCache, + "with trie node cache and hashing 4 times each key in a row", + 4, + ); + bench_single_value( + BenchmarkConfig::NoCache, + "no cache and hashing 4 times each key in a row", + 4, + ); + + group.finish(); + + let mut group = c.benchmark_group("Hashing `:code`"); + + let mut bench_single_value = |config, desc| { + let backend = create_backend(config, &path); + + group.bench_function(desc, |b| { + b.iter_batched( + || backend.state_at(block_hash).expect("Creates state"), + |state| { + let _ = state + .storage_hash(sp_core::storage::well_known_keys::CODE) + .expect("Doesn't fail") + .unwrap(); + }, + BatchSize::SmallInput, + ) + }); + }; + + bench_single_value(BenchmarkConfig::TrieNodeCache, "with trie node cache"); + bench_single_value(BenchmarkConfig::NoCache, "no cache"); + + group.finish(); +} + +criterion_group!(benches, state_access_benchmarks); +criterion_main!(benches); diff --git a/client/db/src/bench.rs b/client/db/src/bench.rs index d46aca8e8ff78..13d91fff0b555 100644 --- a/client/db/src/bench.rs +++ b/client/db/src/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,13 +18,7 @@ //! State backend that's useful for benchmarking -use std::{ - cell::{Cell, RefCell}, - collections::HashMap, - sync::Arc, -}; - -use crate::storage_cache::{new_shared_cache, CachingState, SharedCache}; +use crate::{DbState, DbStateBuilder}; use hash_db::{Hasher, Prefix}; use kvdb::{DBTransaction, KeyValueDB}; use linked_hash_map::LinkedHashMap; @@ -34,45 +28,37 @@ use sp_core::{ }; use sp_runtime::{ traits::{Block as BlockT, HashFor}, - Storage, + StateVersion, Storage, }; use sp_state_machine::{ - backend::Backend as StateBackend, ChildStorageCollection, DBValue, ProofRecorder, - StorageCollection, + backend::Backend as StateBackend, ChildStorageCollection, DBValue, StorageCollection, +}; +use sp_trie::{ + cache::{CacheSize, SharedTrieCache}, + prefixed_key, MemoryDB, +}; +use std::{ + cell::{Cell, RefCell}, + collections::HashMap, + sync::Arc, }; -use sp_trie::{prefixed_key, MemoryDB}; - -type DbState = - sp_state_machine::TrieBackend>>, HashFor>; -type State = CachingState, B>; +type State = DbState; struct StorageDb { db: Arc, - proof_recorder: Option>, _block: std::marker::PhantomData, } impl sp_state_machine::Storage> for StorageDb { fn get(&self, key: &Block::Hash, prefix: Prefix) -> Result, String> { let prefixed_key = prefixed_key::>(key, prefix); - if let Some(recorder) = &self.proof_recorder { - if let Some(v) = recorder.get(&key) { - return Ok(v.clone()) - } - let backend_value = self - .db - .get(0, &prefixed_key) - .map_err(|e| format!("Database backend error: {:?}", e))?; - recorder.record(key.clone(), backend_value.clone()); - Ok(backend_value) - } else { - self.db - .get(0, &prefixed_key) - .map_err(|e| format!("Database backend error: {:?}", e)) - } + self.db + .get(0, &prefixed_key) + .map_err(|e| format!("Database backend error: {:?}", e)) } } + /// State that manages the backend database reference. Allows runtime to control the database. pub struct BenchmarkingState { root: Cell, @@ -81,7 +67,6 @@ pub struct BenchmarkingState { db: Cell>>, genesis: HashMap, (Vec, i32)>, record: Cell>>, - shared_cache: SharedCache, // shared cache is always empty /// Key tracker for keys in the main trie. /// We track the total number of reads and writes to these keys, /// not de-duplicated for repeats. @@ -92,9 +77,10 @@ pub struct BenchmarkingState { /// not de-duplicated for repeats. child_key_tracker: RefCell, LinkedHashMap, TrackedStorageKey>>>, whitelist: RefCell>, - proof_recorder: Option>, + proof_recorder: Option>>, proof_recorder_root: Cell, enable_tracking: bool, + shared_trie_cache: SharedTrieCache>, } impl BenchmarkingState { @@ -105,30 +91,32 @@ impl BenchmarkingState { record_proof: bool, enable_tracking: bool, ) -> Result { + let state_version = sp_runtime::StateVersion::default(); let mut root = B::Hash::default(); let mut mdb = MemoryDB::>::default(); - sp_state_machine::TrieDBMut::>::new(&mut mdb, &mut root); + sp_trie::trie_types::TrieDBMutBuilderV1::>::new(&mut mdb, &mut root).build(); let mut state = BenchmarkingState { state: RefCell::new(None), db: Cell::new(None), - root: Cell::new(root.clone()), + root: Cell::new(root), genesis: Default::default(), genesis_root: Default::default(), record: Default::default(), - shared_cache: new_shared_cache(0, (1, 10)), main_key_tracker: Default::default(), child_key_tracker: Default::default(), whitelist: Default::default(), proof_recorder: record_proof.then(Default::default), - proof_recorder_root: Cell::new(root.clone()), + proof_recorder_root: Cell::new(root), enable_tracking, + // Enable the cache, but do not sync anything to the shared state. + shared_trie_cache: SharedTrieCache::new(CacheSize::Maximum(0)), }; state.add_whitelist_to_tracker(); state.reopen()?; - let child_delta = genesis.children_default.iter().map(|(_storage_key, child_content)| { + let child_delta = genesis.children_default.values().map(|child_content| { ( &child_content.child_info, child_content.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), @@ -138,9 +126,10 @@ impl BenchmarkingState { state.state.borrow_mut().as_mut().unwrap().full_storage_root( genesis.top.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), child_delta, + state_version, ); state.genesis = transaction.clone().drain(); - state.genesis_root = root.clone(); + state.genesis_root = root; state.commit(root, transaction, Vec::new(), Vec::new())?; state.record.take(); Ok(state) @@ -157,16 +146,13 @@ impl BenchmarkingState { recorder.reset(); self.proof_recorder_root.set(self.root.get()); } - let storage_db = Arc::new(StorageDb:: { - db, - proof_recorder: self.proof_recorder.clone(), - _block: Default::default(), - }); - *self.state.borrow_mut() = Some(State::new( - DbState::::new(storage_db, self.root.get()), - self.shared_cache.clone(), - None, - )); + let storage_db = Arc::new(StorageDb:: { db, _block: Default::default() }); + *self.state.borrow_mut() = Some( + DbStateBuilder::::new(storage_db, self.root.get()) + .with_optional_recorder(self.proof_recorder.clone()) + .with_cache(self.shared_trie_cache.local_cache()) + .build(), + ); Ok(()) } @@ -198,9 +184,7 @@ impl BenchmarkingState { let mut main_key_tracker = self.main_key_tracker.borrow_mut(); let key_tracker = if let Some(childtrie) = childtrie { - child_key_tracker - .entry(childtrie.to_vec()) - .or_insert_with(|| LinkedHashMap::new()) + child_key_tracker.entry(childtrie.to_vec()).or_insert_with(LinkedHashMap::new) } else { &mut main_key_tracker }; @@ -241,9 +225,7 @@ impl BenchmarkingState { let mut main_key_tracker = self.main_key_tracker.borrow_mut(); let key_tracker = if let Some(childtrie) = childtrie { - child_key_tracker - .entry(childtrie.to_vec()) - .or_insert_with(|| LinkedHashMap::new()) + child_key_tracker.entry(childtrie.to_vec()).or_insert_with(LinkedHashMap::new) } else { &mut main_key_tracker }; @@ -325,6 +307,19 @@ impl StateBackend> for BenchmarkingState { .child_storage(child_info, key) } + fn child_storage_hash( + &self, + child_info: &ChildInfo, + key: &[u8], + ) -> Result, Self::Error> { + self.add_read_key(Some(child_info.storage_key()), key); + self.state + .borrow() + .as_ref() + .ok_or_else(state_err)? + .child_storage_hash(child_info, key) + } + fn exists_storage(&self, key: &[u8]) -> Result { self.add_read_key(None, key); self.state.borrow().as_ref().ok_or_else(state_err)?.exists_storage(key) @@ -394,10 +389,11 @@ impl StateBackend> for BenchmarkingState { &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { if let Some(ref state) = *self.state.borrow() { - state.apply_to_keys_while(child_info, prefix, f) + state.apply_to_keys_while(child_info, prefix, start_at, f) } } @@ -415,6 +411,7 @@ impl StateBackend> for BenchmarkingState { fn storage_root<'a>( &self, delta: impl Iterator)>, + state_version: StateVersion, ) -> (B::Hash, Self::Transaction) where B::Hash: Ord, @@ -422,13 +419,14 @@ impl StateBackend> for BenchmarkingState { self.state .borrow() .as_ref() - .map_or(Default::default(), |s| s.storage_root(delta)) + .map_or(Default::default(), |s| s.storage_root(delta, state_version)) } fn child_storage_root<'a>( &self, child_info: &ChildInfo, delta: impl Iterator)>, + state_version: StateVersion, ) -> (B::Hash, bool, Self::Transaction) where B::Hash: Ord, @@ -436,7 +434,7 @@ impl StateBackend> for BenchmarkingState { self.state .borrow() .as_ref() - .map_or(Default::default(), |s| s.child_storage_root(child_info, delta)) + .map_or(Default::default(), |s| s.child_storage_root(child_info, delta, state_version)) } fn pairs(&self) -> Vec<(Vec, Vec)> { @@ -512,7 +510,7 @@ impl StateBackend> for BenchmarkingState { self.db.set(Some(db)); } - self.root.set(self.genesis_root.clone()); + self.root.set(self.genesis_root); self.reopen()?; self.wipe_tracker(); Ok(()) @@ -602,7 +600,9 @@ impl StateBackend> for BenchmarkingState { fn proof_size(&self) -> Option { self.proof_recorder.as_ref().map(|recorder| { let proof_size = recorder.estimate_encoded_size() as u32; + let proof = recorder.to_storage_proof(); + let proof_recorder_root = self.proof_recorder_root.get(); if proof_recorder_root == Default::default() || proof_size == 1 { // empty trie @@ -667,7 +667,6 @@ mod test { assert_eq!(rw_tracker.1, 0); assert_eq!(rw_tracker.2, 2); assert_eq!(rw_tracker.3, 0); - drop(rw_tracker); bench_state.wipe().unwrap(); } } diff --git a/client/db/src/cache/list_cache.rs b/client/db/src/cache/list_cache.rs deleted file mode 100644 index 795cb8f901183..0000000000000 --- a/client/db/src/cache/list_cache.rs +++ /dev/null @@ -1,2351 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! List-based cache. -//! -//! Maintains several lists, containing nodes that are inserted whenever -//! cached value at new block differs from the value at previous block. -//! Example: -//! B1(a) <--- B2(b) <--- B3(b) <--- B4(c) -//! N1(b) <-------------- N2(c) -//! -//! There's single list for all finalized blocks and >= 0 lists for unfinalized -//! blocks. -//! When new non-final block is inserted (with value that differs from the value -//! at parent), it starts new unfinalized fork. -//! When new final block is inserted (with value that differs from the value at -//! parent), new entry is appended to the finalized fork. -//! When existing non-final block is finalized (with value that differs from the -//! value at parent), new entry is appended to the finalized fork AND unfinalized -//! fork is dropped. -//! -//! Entries from abandoned unfinalized forks (forks that are forking from block B -//! which is ascendant of the best finalized block) are deleted when block F with -//! number B.number (i.e. 'parallel' canon block) is finalized. -//! -//! Finalized entry E1 is pruned when block B is finalized so that: -//! EntryAt(B.number - prune_depth).points_to(E1) - -use std::collections::{BTreeMap, BTreeSet}; - -use log::warn; - -use sp_blockchain::{Error as ClientError, Result as ClientResult}; -use sp_runtime::traits::{Block as BlockT, Bounded, CheckedSub, NumberFor, Zero}; - -use crate::cache::{ - list_entry::{Entry, StorageEntry}, - list_storage::{Metadata, Storage, StorageTransaction}, - CacheItemT, ComplexBlockId, EntryType, -}; - -/// Pruning strategy. -#[derive(Debug, Clone, Copy)] -pub enum PruningStrategy { - /// Prune entries when they're too far behind best finalized block. - ByDepth(N), - /// Do not prune old entries at all. - NeverPrune, -} - -/// List-based cache. -pub struct ListCache> { - /// Cache storage. - storage: S, - /// Pruning strategy. - pruning_strategy: PruningStrategy>, - /// Best finalized block. - best_finalized_block: ComplexBlockId, - /// Best finalized entry (if exists). - best_finalized_entry: Option>, - /// All unfinalized 'forks'. - unfinalized: Vec>, -} - -/// All possible list cache operations that could be performed after transaction is committed. -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] -pub enum CommitOperation { - /// New block is appended to the fork without changing the cached value. - AppendNewBlock(usize, ComplexBlockId), - /// New block is appended to the fork with the different value. - AppendNewEntry(usize, Entry), - /// New fork is added with the given head entry. - AddNewFork(Entry), - /// New block is finalized and possibly: - /// - new entry is finalized AND/OR - /// - some forks are destroyed - BlockFinalized(ComplexBlockId, Option>, BTreeSet), - /// When best block is reverted - contains the forks that have to be updated - /// (they're either destroyed, or their best entry is updated to earlier block). - BlockReverted(BTreeMap>>), -} - -/// A set of commit operations. -#[derive(Debug)] -pub struct CommitOperations { - operations: Vec>, -} - -/// Single fork of list-based cache. -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] -pub struct Fork { - /// The best block of this fork. We do not save this field in the database to avoid - /// extra updates => it could be None after restart. It will be either filled when - /// the block is appended to this fork, or the whole fork will be abandoned when the - /// block from the other fork is finalized - best_block: Option>, - /// The head entry of this fork. - head: Entry, -} - -/// Outcome of Fork::try_append_or_fork. -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] -pub enum ForkAppendResult { - /// New entry should be appended to the end of the fork. - Append, - /// New entry should be forked from the fork, starting with entry at given block. - Fork(ComplexBlockId), -} - -impl> ListCache { - /// Create new db list cache entry. - pub fn new( - storage: S, - pruning_strategy: PruningStrategy>, - best_finalized_block: ComplexBlockId, - ) -> ClientResult { - let (best_finalized_entry, unfinalized) = - storage.read_meta().and_then(|meta| read_forks(&storage, meta))?; - - Ok(ListCache { - storage, - pruning_strategy, - best_finalized_block, - best_finalized_entry, - unfinalized, - }) - } - - /// Get reference to the storage. - pub fn storage(&self) -> &S { - &self.storage - } - - /// Get unfinalized forks reference. - #[cfg(test)] - pub fn unfinalized(&self) -> &[Fork] { - &self.unfinalized - } - - /// Get value valid at block. - pub fn value_at_block( - &self, - at: &ComplexBlockId, - ) -> ClientResult, Option>, T)>> { - let head = if at.number <= self.best_finalized_block.number { - // if the block is older than the best known finalized block - // => we're should search for the finalized value - - // BUT since we're not guaranteeing to provide correct values for forks - // behind the finalized block, check if the block is finalized first - if !chain::is_finalized_block(&self.storage, &at, Bounded::max_value())? { - return Err(ClientError::NotInFinalizedChain) - } - - self.best_finalized_entry.as_ref() - } else if self.unfinalized.is_empty() { - // there are no unfinalized entries - // => we should search for the finalized value - self.best_finalized_entry.as_ref() - } else { - // there are unfinalized entries - // => find the fork containing given block and read from this fork - // IF there's no matching fork, ensure that this isn't a block from a fork that has - // forked behind the best finalized block and search at finalized fork - - match self.find_unfinalized_fork(&at)? { - Some(fork) => Some(&fork.head), - None => match self.best_finalized_entry.as_ref() { - Some(best_finalized_entry) - if chain::is_connected_to_block( - &self.storage, - &at, - &best_finalized_entry.valid_from, - )? => - Some(best_finalized_entry), - _ => None, - }, - } - }; - - match head { - Some(head) => head - .search_best_before(&self.storage, at.number) - .map(|e| e.map(|e| (e.0.valid_from, e.1, e.0.value))), - None => Ok(None), - } - } - - /// When new block is inserted into database. - /// - /// None passed as value means that the value has not changed since previous block. - pub fn on_block_insert>( - &self, - tx: &mut Tx, - parent: ComplexBlockId, - block: ComplexBlockId, - value: Option, - entry_type: EntryType, - operations: &mut CommitOperations, - ) -> ClientResult<()> { - Ok(operations - .append(self.do_on_block_insert(tx, parent, block, value, entry_type, operations)?)) - } - - /// When previously inserted block is finalized. - pub fn on_block_finalize>( - &self, - tx: &mut Tx, - parent: ComplexBlockId, - block: ComplexBlockId, - operations: &mut CommitOperations, - ) -> ClientResult<()> { - Ok(operations.append(self.do_on_block_finalize(tx, parent, block, operations)?)) - } - - /// When block is reverted. - pub fn on_block_revert>( - &self, - tx: &mut Tx, - reverted_block: &ComplexBlockId, - operations: &mut CommitOperations, - ) -> ClientResult<()> { - Ok(operations.append(Some(self.do_on_block_revert(tx, reverted_block)?))) - } - - /// When transaction is committed. - pub fn on_transaction_commit(&mut self, ops: CommitOperations) { - for op in ops.operations { - match op { - CommitOperation::AppendNewBlock(index, best_block) => { - let mut fork = self.unfinalized.get_mut(index).expect( - "ListCache is a crate-private type; - internal clients of ListCache are committing transaction while cache is locked; - CommitOperation holds valid references while cache is locked; qed", - ); - fork.best_block = Some(best_block); - }, - CommitOperation::AppendNewEntry(index, entry) => { - let mut fork = self.unfinalized.get_mut(index).expect( - "ListCache is a crate-private type; - internal clients of ListCache are committing transaction while cache is locked; - CommitOperation holds valid references while cache is locked; qed", - ); - fork.best_block = Some(entry.valid_from.clone()); - fork.head = entry; - }, - CommitOperation::AddNewFork(entry) => { - self.unfinalized - .push(Fork { best_block: Some(entry.valid_from.clone()), head: entry }); - }, - CommitOperation::BlockFinalized(block, finalizing_entry, forks) => { - self.best_finalized_block = block; - if let Some(finalizing_entry) = finalizing_entry { - self.best_finalized_entry = Some(finalizing_entry); - } - for fork_index in forks.iter().rev() { - self.unfinalized.remove(*fork_index); - } - }, - CommitOperation::BlockReverted(forks) => { - for (fork_index, updated_fork) in forks.into_iter().rev() { - match updated_fork { - Some(updated_fork) => self.unfinalized[fork_index] = updated_fork, - None => { - self.unfinalized.remove(fork_index); - }, - } - } - }, - } - } - } - - fn do_on_block_insert>( - &self, - tx: &mut Tx, - parent: ComplexBlockId, - block: ComplexBlockId, - value: Option, - entry_type: EntryType, - operations: &CommitOperations, - ) -> ClientResult>> { - // this guarantee is currently provided by LightStorage && we're relying on it here - let prev_operation = operations.operations.last(); - debug_assert!( - entry_type != EntryType::Final || - self.unfinalized.is_empty() || - self.best_finalized_block.hash == parent.hash || - match prev_operation { - Some(&CommitOperation::BlockFinalized(ref best_finalized_block, _, _)) => - best_finalized_block.hash == parent.hash, - _ => false, - } - ); - - // we do not store any values behind finalized - if block.number != Zero::zero() && self.best_finalized_block.number >= block.number { - return Ok(None) - } - - // if the block is not final, it is possibly appended to/forking from existing unfinalized - // fork - let is_final = entry_type == EntryType::Final || entry_type == EntryType::Genesis; - if !is_final { - let mut fork_and_action = None; - - // when value hasn't changed and block isn't final, there's nothing we need to do - if value.is_none() { - return Ok(None) - } - - // first: try to find fork that is known to has the best block we're appending to - for (index, fork) in self.unfinalized.iter().enumerate() { - if fork.try_append(&parent) { - fork_and_action = Some((index, ForkAppendResult::Append)); - break - } - } - - // if not found, check cases: - // - we're appending to the fork for the first time after restart; - // - we're forking existing unfinalized fork from the middle; - if fork_and_action.is_none() { - let best_finalized_entry_block = - self.best_finalized_entry.as_ref().map(|f| f.valid_from.number); - for (index, fork) in self.unfinalized.iter().enumerate() { - if let Some(action) = - fork.try_append_or_fork(&self.storage, &parent, best_finalized_entry_block)? - { - fork_and_action = Some((index, action)); - break - } - } - } - - // if we have found matching unfinalized fork => early exit - match fork_and_action { - // append to unfinalized fork - Some((index, ForkAppendResult::Append)) => { - let new_storage_entry = match self.unfinalized[index].head.try_update(value) { - Some(new_storage_entry) => new_storage_entry, - None => return Ok(Some(CommitOperation::AppendNewBlock(index, block))), - }; - - tx.insert_storage_entry(&block, &new_storage_entry); - let operation = - CommitOperation::AppendNewEntry(index, new_storage_entry.into_entry(block)); - tx.update_meta( - self.best_finalized_entry.as_ref(), - &self.unfinalized, - &operation, - ); - return Ok(Some(operation)) - }, - // fork from the middle of unfinalized fork - Some((_, ForkAppendResult::Fork(prev_valid_from))) => { - // it is possible that we're inserting extra (but still required) fork here - let new_storage_entry = StorageEntry { - prev_valid_from: Some(prev_valid_from), - value: value.expect("checked above that !value.is_none(); qed"), - }; - - tx.insert_storage_entry(&block, &new_storage_entry); - let operation = - CommitOperation::AddNewFork(new_storage_entry.into_entry(block)); - tx.update_meta( - self.best_finalized_entry.as_ref(), - &self.unfinalized, - &operation, - ); - return Ok(Some(operation)) - }, - None => (), - } - } - - // if we're here, then one of following is true: - // - either we're inserting final block => all ancestors are already finalized AND the only - // thing we can do is to try to update last finalized entry - // - either we're inserting non-final blocks that has no ancestors in any known unfinalized - // forks - - let new_storage_entry = match self.best_finalized_entry.as_ref() { - Some(best_finalized_entry) => best_finalized_entry.try_update(value), - None if value.is_some() => Some(StorageEntry { - prev_valid_from: None, - value: value.expect("value.is_some(); qed"), - }), - None => None, - }; - - if !is_final { - return Ok(match new_storage_entry { - Some(new_storage_entry) => { - tx.insert_storage_entry(&block, &new_storage_entry); - let operation = - CommitOperation::AddNewFork(new_storage_entry.into_entry(block)); - tx.update_meta( - self.best_finalized_entry.as_ref(), - &self.unfinalized, - &operation, - ); - Some(operation) - }, - None => None, - }) - } - - // cleanup database from abandoned unfinalized forks and obsolete finalized entries - let abandoned_forks = self.destroy_abandoned_forks(tx, &block, prev_operation); - self.prune_finalized_entries(tx, &block); - - match new_storage_entry { - Some(new_storage_entry) => { - tx.insert_storage_entry(&block, &new_storage_entry); - let operation = CommitOperation::BlockFinalized( - block.clone(), - Some(new_storage_entry.into_entry(block)), - abandoned_forks, - ); - tx.update_meta(self.best_finalized_entry.as_ref(), &self.unfinalized, &operation); - Ok(Some(operation)) - }, - None => Ok(Some(CommitOperation::BlockFinalized(block, None, abandoned_forks))), - } - } - - fn do_on_block_finalize>( - &self, - tx: &mut Tx, - parent: ComplexBlockId, - block: ComplexBlockId, - operations: &CommitOperations, - ) -> ClientResult>> { - // this guarantee is currently provided by db backend && we're relying on it here - let prev_operation = operations.operations.last(); - debug_assert!( - self.best_finalized_block.hash == parent.hash || - match prev_operation { - Some(&CommitOperation::BlockFinalized(ref best_finalized_block, _, _)) => - best_finalized_block.hash == parent.hash, - _ => false, - } - ); - - // there could be at most one entry that is finalizing - let finalizing_entry = - self.storage.read_entry(&block)?.map(|entry| entry.into_entry(block.clone())); - - // cleanup database from abandoned unfinalized forks and obsolete finalized entries - let abandoned_forks = self.destroy_abandoned_forks(tx, &block, prev_operation); - self.prune_finalized_entries(tx, &block); - - let operation = CommitOperation::BlockFinalized(block, finalizing_entry, abandoned_forks); - tx.update_meta(self.best_finalized_entry.as_ref(), &self.unfinalized, &operation); - - Ok(Some(operation)) - } - - fn do_on_block_revert>( - &self, - tx: &mut Tx, - reverted_block: &ComplexBlockId, - ) -> ClientResult> { - // can't revert finalized blocks - debug_assert!(self.best_finalized_block.number < reverted_block.number); - - // iterate all unfinalized forks and truncate/destroy if required - let mut updated = BTreeMap::new(); - for (index, fork) in self.unfinalized.iter().enumerate() { - // we only need to truncate fork if its head is ancestor of truncated block - if fork.head.valid_from.number < reverted_block.number { - continue - } - - // we only need to truncate fork if its head is connected to truncated block - if !chain::is_connected_to_block(&self.storage, reverted_block, &fork.head.valid_from)? - { - continue - } - - let updated_fork = fork.truncate( - &self.storage, - tx, - reverted_block.number, - self.best_finalized_block.number, - )?; - updated.insert(index, updated_fork); - } - - // schedule commit operation and update meta - let operation = CommitOperation::BlockReverted(updated); - tx.update_meta(self.best_finalized_entry.as_ref(), &self.unfinalized, &operation); - - Ok(operation) - } - - /// Prune old finalized entries. - fn prune_finalized_entries>( - &self, - tx: &mut Tx, - block: &ComplexBlockId, - ) { - let prune_depth = match self.pruning_strategy { - PruningStrategy::ByDepth(prune_depth) => prune_depth, - PruningStrategy::NeverPrune => return, - }; - - let mut do_pruning = || -> ClientResult<()> { - // calculate last ancient block number - let ancient_block = match block.number.checked_sub(&prune_depth) { - Some(number) => match self.storage.read_id(number)? { - Some(hash) => ComplexBlockId::new(hash, number), - None => return Ok(()), - }, - None => return Ok(()), - }; - - // if there's an entry at this block: - // - remove reference from this entry to the previous entry - // - destroy fork starting with previous entry - let current_entry = match self.storage.read_entry(&ancient_block)? { - Some(current_entry) => current_entry, - None => return Ok(()), - }; - let first_entry_to_truncate = match current_entry.prev_valid_from { - Some(prev_valid_from) => prev_valid_from, - None => return Ok(()), - }; - - // truncate ancient entry - tx.insert_storage_entry( - &ancient_block, - &StorageEntry { prev_valid_from: None, value: current_entry.value }, - ); - - // destroy 'fork' ending with previous entry - destroy_fork(first_entry_to_truncate, &self.storage, tx, None) - }; - - if let Err(error) = do_pruning() { - warn!(target: "db", "Failed to prune ancient cache entries: {}", error); - } - } - - /// Try to destroy abandoned forks (forked before best finalized block) when block is finalized. - fn destroy_abandoned_forks>( - &self, - tx: &mut Tx, - block: &ComplexBlockId, - prev_operation: Option<&CommitOperation>, - ) -> BTreeSet { - // if some block has been finalized already => take it into account - let prev_abandoned_forks = match prev_operation { - Some(&CommitOperation::BlockFinalized(_, _, ref abandoned_forks)) => - Some(abandoned_forks), - _ => None, - }; - - let mut destroyed = prev_abandoned_forks.cloned().unwrap_or_else(|| BTreeSet::new()); - let live_unfinalized = self.unfinalized.iter().enumerate().filter(|(idx, _)| { - prev_abandoned_forks - .map(|prev_abandoned_forks| !prev_abandoned_forks.contains(idx)) - .unwrap_or(true) - }); - for (index, fork) in live_unfinalized { - if fork.head.valid_from.number == block.number { - destroyed.insert(index); - if fork.head.valid_from.hash != block.hash { - if let Err(error) = fork.destroy(&self.storage, tx, Some(block.number)) { - warn!(target: "db", "Failed to destroy abandoned unfinalized cache fork: {}", error); - } - } - } - } - - destroyed - } - - /// Search unfinalized fork where given block belongs. - fn find_unfinalized_fork( - &self, - block: &ComplexBlockId, - ) -> ClientResult>> { - for unfinalized in &self.unfinalized { - if unfinalized.matches(&self.storage, block)? { - return Ok(Some(&unfinalized)) - } - } - - Ok(None) - } -} - -impl Fork { - /// Get reference to the head entry of this fork. - pub fn head(&self) -> &Entry { - &self.head - } - - /// Check if the block is the part of the fork. - pub fn matches>( - &self, - storage: &S, - block: &ComplexBlockId, - ) -> ClientResult { - let range = self.head.search_best_range_before(storage, block.number)?; - match range { - None => Ok(false), - Some((begin, end)) => - chain::is_connected_to_range(storage, block, (&begin, end.as_ref())), - } - } - - /// Try to append NEW block to the fork. This method will only 'work' (return true) when block - /// is actually appended to the fork AND the best known block of the fork is known (i.e. some - /// block has been already appended to this fork after last restart). - pub fn try_append(&self, parent: &ComplexBlockId) -> bool { - // when the best block of the fork is known, the check is trivial - // - // most of calls will hopefully end here, because best_block is only unknown - // after restart and until new block is appended to the fork - self.best_block.as_ref() == Some(parent) - } - - /// Try to append new block to the fork OR fork it. - pub fn try_append_or_fork>( - &self, - storage: &S, - parent: &ComplexBlockId, - best_finalized_entry_block: Option>, - ) -> ClientResult>> { - // try to find entries that are (possibly) surrounding the parent block - let range = self.head.search_best_range_before(storage, parent.number)?; - let begin = match range { - Some((begin, _)) => begin, - None => return Ok(None), - }; - - // check if the parent is connected to the beginning of the range - if !chain::is_connected_to_block(storage, parent, &begin)? { - return Ok(None) - } - - // the block is connected to the begin-entry. If begin is the head entry - // => we need to append new block to the fork - if begin == self.head.valid_from { - return Ok(Some(ForkAppendResult::Append)) - } - - // the parent block belongs to this fork AND it is located after last finalized entry - // => we need to make a new fork - if best_finalized_entry_block.map(|f| begin.number > f).unwrap_or(true) { - return Ok(Some(ForkAppendResult::Fork(begin))) - } - - Ok(None) - } - - /// Destroy fork by deleting all unfinalized entries. - pub fn destroy, Tx: StorageTransaction>( - &self, - storage: &S, - tx: &mut Tx, - best_finalized_block: Option>, - ) -> ClientResult<()> { - destroy_fork(self.head.valid_from.clone(), storage, tx, best_finalized_block) - } - - /// Truncate fork by deleting all entries that are descendants of given block. - pub fn truncate, Tx: StorageTransaction>( - &self, - storage: &S, - tx: &mut Tx, - reverting_block: NumberFor, - best_finalized_block: NumberFor, - ) -> ClientResult>> { - let mut current = self.head.valid_from.clone(); - loop { - // read pointer to previous entry - let entry = storage.require_entry(¤t)?; - - // truncation stops when we have reached the ancestor of truncated block - if current.number < reverting_block { - // if we have reached finalized block => destroy fork - if chain::is_finalized_block(storage, ¤t, best_finalized_block)? { - return Ok(None) - } - - // else fork needs to be updated - return Ok(Some(Fork { best_block: None, head: entry.into_entry(current) })) - } - - tx.remove_storage_entry(¤t); - - // truncation also stops when there are no more entries in the list - current = match entry.prev_valid_from { - Some(prev_valid_from) => prev_valid_from, - None => return Ok(None), - }; - } - } -} - -impl Default for CommitOperations { - fn default() -> Self { - CommitOperations { operations: Vec::new() } - } -} - -// This should never be allowed for non-test code to avoid revealing its internals. -#[cfg(test)] -impl From>> - for CommitOperations -{ - fn from(operations: Vec>) -> Self { - CommitOperations { operations } - } -} - -impl CommitOperations { - /// Append operation to the set. - fn append(&mut self, new_operation: Option>) { - let new_operation = match new_operation { - Some(new_operation) => new_operation, - None => return, - }; - - let last_operation = match self.operations.pop() { - Some(last_operation) => last_operation, - None => { - self.operations.push(new_operation); - return - }, - }; - - // we are able (and obliged to) to merge two consequent block finalization operations - match last_operation { - CommitOperation::BlockFinalized( - old_finalized_block, - old_finalized_entry, - old_abandoned_forks, - ) => match new_operation { - CommitOperation::BlockFinalized( - new_finalized_block, - new_finalized_entry, - new_abandoned_forks, - ) => { - self.operations.push(CommitOperation::BlockFinalized( - new_finalized_block, - new_finalized_entry, - new_abandoned_forks, - )); - }, - _ => { - self.operations.push(CommitOperation::BlockFinalized( - old_finalized_block, - old_finalized_entry, - old_abandoned_forks, - )); - self.operations.push(new_operation); - }, - }, - _ => { - self.operations.push(last_operation); - self.operations.push(new_operation); - }, - } - } -} - -/// Destroy fork by deleting all unfinalized entries. -pub fn destroy_fork< - Block: BlockT, - T: CacheItemT, - S: Storage, - Tx: StorageTransaction, ->( - head_valid_from: ComplexBlockId, - storage: &S, - tx: &mut Tx, - best_finalized_block: Option>, -) -> ClientResult<()> { - let mut current = head_valid_from; - loop { - // optionally: deletion stops when we found entry at finalized block - if let Some(best_finalized_block) = best_finalized_block { - if chain::is_finalized_block(storage, ¤t, best_finalized_block)? { - return Ok(()) - } - } - - // read pointer to previous entry - let entry = storage.require_entry(¤t)?; - tx.remove_storage_entry(¤t); - - // deletion stops when there are no more entries in the list - current = match entry.prev_valid_from { - Some(prev_valid_from) => prev_valid_from, - None => return Ok(()), - }; - } -} - -/// Blockchain related functions. -mod chain { - use super::*; - use sp_runtime::traits::Header as HeaderT; - - /// Is the block1 connected both ends of the range. - pub fn is_connected_to_range>( - storage: &S, - block: &ComplexBlockId, - range: (&ComplexBlockId, Option<&ComplexBlockId>), - ) -> ClientResult { - let (begin, end) = range; - Ok(is_connected_to_block(storage, block, begin)? && - match end { - Some(end) => is_connected_to_block(storage, block, end)?, - None => true, - }) - } - - /// Is the block1 directly connected (i.e. part of the same fork) to block2? - pub fn is_connected_to_block>( - storage: &S, - block1: &ComplexBlockId, - block2: &ComplexBlockId, - ) -> ClientResult { - let (begin, end) = if *block1 > *block2 { (block2, block1) } else { (block1, block2) }; - let mut current = storage - .read_header(&end.hash)? - .ok_or_else(|| ClientError::UnknownBlock(format!("{}", end.hash)))?; - while *current.number() > begin.number { - current = storage - .read_header(current.parent_hash())? - .ok_or_else(|| ClientError::UnknownBlock(format!("{}", current.parent_hash())))?; - } - - Ok(begin.hash == current.hash()) - } - - /// Returns true if the given block is finalized. - pub fn is_finalized_block>( - storage: &S, - block: &ComplexBlockId, - best_finalized_block: NumberFor, - ) -> ClientResult { - if block.number > best_finalized_block { - return Ok(false) - } - - storage.read_id(block.number).map(|hash| hash.as_ref() == Some(&block.hash)) - } -} - -/// Read list cache forks at blocks IDs. -fn read_forks>( - storage: &S, - meta: Metadata, -) -> ClientResult<(Option>, Vec>)> { - let finalized = match meta.finalized { - Some(finalized) => Some(storage.require_entry(&finalized)?.into_entry(finalized)), - None => None, - }; - - let unfinalized = meta - .unfinalized - .into_iter() - .map(|unfinalized| { - storage.require_entry(&unfinalized).map(|storage_entry| Fork { - best_block: None, - head: storage_entry.into_entry(unfinalized), - }) - }) - .collect::>()?; - - Ok((finalized, unfinalized)) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::cache::list_storage::tests::{DummyStorage, DummyTransaction, FaultyStorage}; - use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper, Header}; - use substrate_test_runtime_client::runtime::H256; - - type Block = RawBlock>; - - fn test_id(number: u64) -> ComplexBlockId { - ComplexBlockId::new(H256::from_low_u64_be(number), number) - } - - fn correct_id(number: u64) -> ComplexBlockId { - ComplexBlockId::new(test_header(number).hash(), number) - } - - fn fork_id(fork_nonce: u64, fork_from: u64, number: u64) -> ComplexBlockId { - ComplexBlockId::new(fork_header(fork_nonce, fork_from, number).hash(), number) - } - - fn test_header(number: u64) -> Header { - Header { - parent_hash: if number == 0 { - Default::default() - } else { - test_header(number - 1).hash() - }, - number, - state_root: Default::default(), - extrinsics_root: Default::default(), - digest: Default::default(), - } - } - - fn fork_header(fork_nonce: u64, fork_from: u64, number: u64) -> Header { - if fork_from == number { - test_header(number) - } else { - Header { - parent_hash: fork_header(fork_nonce, fork_from, number - 1).hash(), - number, - state_root: H256::from_low_u64_be(1 + fork_nonce), - extrinsics_root: Default::default(), - digest: Default::default(), - } - } - } - - #[test] - fn list_value_at_block_works() { - // when block is earlier than best finalized block AND it is not finalized - // --- 50 --- - // ----------> [100] - assert!(ListCache::<_, u64, _>::new( - DummyStorage::new(), - PruningStrategy::ByDepth(1024), - test_id(100) - ) - .unwrap() - .value_at_block(&test_id(50)) - .is_err()); - // when block is earlier than best finalized block AND it is finalized AND value is some - // [30] ---- 50 ---> [100] - assert_eq!( - ListCache::new( - DummyStorage::new() - .with_meta(Some(test_id(100)), Vec::new()) - .with_id(50, H256::from_low_u64_be(50)) - .with_entry( - test_id(100), - StorageEntry { prev_valid_from: Some(test_id(30)), value: 100 } - ) - .with_entry(test_id(30), StorageEntry { prev_valid_from: None, value: 30 }), - PruningStrategy::ByDepth(1024), - test_id(100) - ) - .unwrap() - .value_at_block(&test_id(50)) - .unwrap(), - Some((test_id(30), Some(test_id(100)), 30)) - ); - // when block is the best finalized block AND value is some - // ---> [100] - assert_eq!( - ListCache::new( - DummyStorage::new() - .with_meta(Some(test_id(100)), Vec::new()) - .with_id(100, H256::from_low_u64_be(100)) - .with_entry( - test_id(100), - StorageEntry { prev_valid_from: Some(test_id(30)), value: 100 } - ) - .with_entry(test_id(30), StorageEntry { prev_valid_from: None, value: 30 }), - PruningStrategy::ByDepth(1024), - test_id(100) - ) - .unwrap() - .value_at_block(&test_id(100)) - .unwrap(), - Some((test_id(100), None, 100)) - ); - // when block is parallel to the best finalized block - // ---- 100 - // ---> [100] - assert!(ListCache::new( - DummyStorage::new() - .with_meta(Some(test_id(100)), Vec::new()) - .with_id(50, H256::from_low_u64_be(50)) - .with_entry( - test_id(100), - StorageEntry { prev_valid_from: Some(test_id(30)), value: 100 } - ) - .with_entry(test_id(30), StorageEntry { prev_valid_from: None, value: 30 }), - PruningStrategy::ByDepth(1024), - test_id(100) - ) - .unwrap() - .value_at_block(&ComplexBlockId::new(H256::from_low_u64_be(2), 100)) - .is_err()); - - // when block is later than last finalized block AND there are no forks AND finalized value - // is Some ---> [100] --- 200 - assert_eq!( - ListCache::new( - DummyStorage::new() - .with_meta(Some(test_id(100)), Vec::new()) - .with_id(50, H256::from_low_u64_be(50)) - .with_entry( - test_id(100), - StorageEntry { prev_valid_from: Some(test_id(30)), value: 100 } - ), - PruningStrategy::ByDepth(1024), - test_id(100) - ) - .unwrap() - .value_at_block(&test_id(200)) - .unwrap(), - Some((test_id(100), None, 100)) - ); - - // when block is later than last finalized block AND there are no matching forks - // AND block is connected to finalized block AND finalized value is Some - // --- 3 - // ---> [2] /---------> [4] - assert_eq!( - ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![correct_id(4)]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_entry( - correct_id(4), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 4 } - ) - .with_header(test_header(2)) - .with_header(test_header(3)) - .with_header(test_header(4)) - .with_header(fork_header(0, 2, 3)), - PruningStrategy::ByDepth(1024), - test_id(2) - ) - .unwrap() - .value_at_block(&fork_id(0, 2, 3)) - .unwrap(), - Some((correct_id(2), None, 2)) - ); - // when block is later than last finalized block AND there are no matching forks - // AND block is not connected to finalized block - // --- 2 --- 3 - // 1 /---> [2] ---------> [4] - assert_eq!( - ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![correct_id(4)]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_entry( - correct_id(4), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 4 } - ) - .with_header(test_header(1)) - .with_header(test_header(2)) - .with_header(test_header(3)) - .with_header(test_header(4)) - .with_header(fork_header(0, 1, 3)) - .with_header(fork_header(0, 1, 2)), - PruningStrategy::ByDepth(1024), - test_id(2) - ) - .unwrap() - .value_at_block(&fork_id(0, 1, 3)) - .unwrap(), - None - ); - - // when block is later than last finalized block AND it appends to unfinalized fork from the - // end AND unfinalized value is Some - // ---> [2] ---> [4] ---> 5 - assert_eq!( - ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![correct_id(4)]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_entry( - correct_id(4), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 4 } - ) - .with_header(test_header(4)) - .with_header(test_header(5)), - PruningStrategy::ByDepth(1024), - test_id(2) - ) - .unwrap() - .value_at_block(&correct_id(5)) - .unwrap(), - Some((correct_id(4), None, 4)) - ); - // when block is later than last finalized block AND it does not fits unfinalized fork - // AND it is connected to the finalized block AND finalized value is Some - // ---> [2] ----------> [4] - // \--- 3 - assert_eq!( - ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![correct_id(4)]) - .with_entry( - correct_id(4), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 4 } - ) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_header(test_header(2)) - .with_header(test_header(3)) - .with_header(test_header(4)) - .with_header(fork_header(0, 2, 3)), - PruningStrategy::ByDepth(1024), - test_id(2) - ) - .unwrap() - .value_at_block(&fork_id(0, 2, 3)) - .unwrap(), - Some((correct_id(2), None, 2)) - ); - } - - #[test] - fn list_on_block_insert_works() { - let nfin = EntryType::NonFinal; - let fin = EntryType::Final; - - // when trying to insert block < finalized number - let mut ops = Default::default(); - assert!(ListCache::new(DummyStorage::new(), PruningStrategy::ByDepth(1024), test_id(100)) - .unwrap() - .do_on_block_insert( - &mut DummyTransaction::new(), - test_id(49), - test_id(50), - Some(50), - nfin, - &mut ops, - ) - .unwrap() - .is_none()); - // when trying to insert block @ finalized number - assert!(ListCache::new(DummyStorage::new(), PruningStrategy::ByDepth(1024), test_id(100)) - .unwrap() - .do_on_block_insert( - &mut DummyTransaction::new(), - test_id(99), - test_id(100), - Some(100), - nfin, - &Default::default(), - ) - .unwrap() - .is_none()); - - // when trying to insert non-final block AND it appends to the best block of unfinalized - // fork AND new value is the same as in the fork' best block - let mut cache = ListCache::new( - DummyStorage::new() - .with_meta(None, vec![test_id(4)]) - .with_entry(test_id(4), StorageEntry { prev_valid_from: None, value: 4 }), - PruningStrategy::ByDepth(1024), - test_id(2), - ) - .unwrap(); - cache.unfinalized[0].best_block = Some(test_id(4)); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - test_id(4), - test_id(5), - Some(4), - nfin, - &Default::default() - ) - .unwrap(), - Some(CommitOperation::AppendNewBlock(0, test_id(5))), - ); - assert!(tx.inserted_entries().is_empty()); - assert!(tx.removed_entries().is_empty()); - assert!(tx.updated_meta().is_none()); - // when trying to insert non-final block AND it appends to the best block of unfinalized - // fork AND new value is the same as in the fork' best block - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - test_id(4), - test_id(5), - Some(5), - nfin, - &Default::default() - ) - .unwrap(), - Some(CommitOperation::AppendNewEntry(0, Entry { valid_from: test_id(5), value: 5 })), - ); - assert_eq!(*tx.inserted_entries(), vec![test_id(5).hash].into_iter().collect()); - assert!(tx.removed_entries().is_empty()); - assert_eq!( - *tx.updated_meta(), - Some(Metadata { finalized: None, unfinalized: vec![test_id(5)] }) - ); - - // when trying to insert non-final block AND it is the first block that appends to the best - // block of unfinalized fork AND new value is the same as in the fork' best block - let cache = ListCache::new( - DummyStorage::new() - .with_meta(None, vec![correct_id(4)]) - .with_entry(correct_id(4), StorageEntry { prev_valid_from: None, value: 4 }) - .with_header(test_header(4)), - PruningStrategy::ByDepth(1024), - test_id(2), - ) - .unwrap(); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - correct_id(4), - correct_id(5), - Some(4), - nfin, - &Default::default(), - ) - .unwrap(), - Some(CommitOperation::AppendNewBlock(0, correct_id(5))), - ); - assert!(tx.inserted_entries().is_empty()); - assert!(tx.removed_entries().is_empty()); - assert!(tx.updated_meta().is_none()); - // when trying to insert non-final block AND it is the first block that appends to the best - // block of unfinalized fork AND new value is the same as in the fork' best block - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - correct_id(4), - correct_id(5), - Some(5), - nfin, - &Default::default(), - ) - .unwrap(), - Some(CommitOperation::AppendNewEntry(0, Entry { valid_from: correct_id(5), value: 5 })), - ); - assert_eq!(*tx.inserted_entries(), vec![correct_id(5).hash].into_iter().collect()); - assert!(tx.removed_entries().is_empty()); - assert_eq!( - *tx.updated_meta(), - Some(Metadata { finalized: None, unfinalized: vec![correct_id(5)] }) - ); - - // when trying to insert non-final block AND it forks unfinalized fork - let cache = ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![correct_id(4)]) - .with_entry( - correct_id(4), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 4 }, - ) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_header(test_header(2)) - .with_header(test_header(3)) - .with_header(test_header(4)), - PruningStrategy::ByDepth(1024), - correct_id(2), - ) - .unwrap(); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - correct_id(3), - fork_id(0, 3, 4), - Some(14), - nfin, - &Default::default() - ) - .unwrap(), - Some(CommitOperation::AddNewFork(Entry { valid_from: fork_id(0, 3, 4), value: 14 })), - ); - assert_eq!(*tx.inserted_entries(), vec![fork_id(0, 3, 4).hash].into_iter().collect()); - assert!(tx.removed_entries().is_empty()); - assert_eq!( - *tx.updated_meta(), - Some(Metadata { - finalized: Some(correct_id(2)), - unfinalized: vec![correct_id(4), fork_id(0, 3, 4)] - }) - ); - - // when trying to insert non-final block AND there are no unfinalized forks - // AND value is the same as last finalized - let cache = ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }), - PruningStrategy::ByDepth(1024), - correct_id(2), - ) - .unwrap(); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - correct_id(2), - correct_id(3), - Some(2), - nfin, - &Default::default() - ) - .unwrap(), - None, - ); - assert!(tx.inserted_entries().is_empty()); - assert!(tx.removed_entries().is_empty()); - assert!(tx.updated_meta().is_none()); - // when trying to insert non-final block AND there are no unfinalized forks - // AND value differs from last finalized - let cache = ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }), - PruningStrategy::ByDepth(1024), - correct_id(2), - ) - .unwrap(); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - correct_id(2), - correct_id(3), - Some(3), - nfin, - &Default::default() - ) - .unwrap(), - Some(CommitOperation::AddNewFork(Entry { valid_from: correct_id(3), value: 3 })), - ); - assert_eq!(*tx.inserted_entries(), vec![correct_id(3).hash].into_iter().collect()); - assert!(tx.removed_entries().is_empty()); - assert_eq!( - *tx.updated_meta(), - Some(Metadata { finalized: Some(correct_id(2)), unfinalized: vec![correct_id(3)] }) - ); - - // when inserting finalized entry AND there are no previous finalized entries - let cache = - ListCache::new(DummyStorage::new(), PruningStrategy::ByDepth(1024), correct_id(2)) - .unwrap(); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - correct_id(2), - correct_id(3), - Some(3), - fin, - &Default::default() - ) - .unwrap(), - Some(CommitOperation::BlockFinalized( - correct_id(3), - Some(Entry { valid_from: correct_id(3), value: 3 }), - Default::default(), - )), - ); - assert_eq!(*tx.inserted_entries(), vec![correct_id(3).hash].into_iter().collect()); - assert!(tx.removed_entries().is_empty()); - assert_eq!( - *tx.updated_meta(), - Some(Metadata { finalized: Some(correct_id(3)), unfinalized: vec![] }) - ); - // when inserting finalized entry AND value is the same as in previous finalized - let cache = ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }), - PruningStrategy::ByDepth(1024), - correct_id(2), - ) - .unwrap(); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - correct_id(2), - correct_id(3), - Some(2), - fin, - &Default::default() - ) - .unwrap(), - Some(CommitOperation::BlockFinalized(correct_id(3), None, Default::default())), - ); - assert!(tx.inserted_entries().is_empty()); - assert!(tx.removed_entries().is_empty()); - assert!(tx.updated_meta().is_none()); - // when inserting finalized entry AND value differs from previous finalized - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - correct_id(2), - correct_id(3), - Some(3), - fin, - &Default::default() - ) - .unwrap(), - Some(CommitOperation::BlockFinalized( - correct_id(3), - Some(Entry { valid_from: correct_id(3), value: 3 }), - Default::default(), - )), - ); - assert_eq!(*tx.inserted_entries(), vec![correct_id(3).hash].into_iter().collect()); - assert!(tx.removed_entries().is_empty()); - assert_eq!( - *tx.updated_meta(), - Some(Metadata { finalized: Some(correct_id(3)), unfinalized: vec![] }) - ); - - // inserting finalized entry removes abandoned fork EVEN if new entry is not inserted - let cache = ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![fork_id(0, 1, 3)]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_entry(fork_id(0, 1, 3), StorageEntry { prev_valid_from: None, value: 13 }), - PruningStrategy::ByDepth(1024), - correct_id(2), - ) - .unwrap(); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_insert( - &mut tx, - correct_id(2), - correct_id(3), - Some(2), - fin, - &Default::default() - ) - .unwrap(), - Some(CommitOperation::BlockFinalized( - correct_id(3), - None, - vec![0].into_iter().collect() - )), - ); - } - - #[test] - fn list_on_block_finalized_works() { - // finalization does not finalizes entry if it does not exists - let cache = ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![correct_id(5)]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 5 }, - ), - PruningStrategy::ByDepth(1024), - correct_id(2), - ) - .unwrap(); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_finalize(&mut tx, correct_id(2), correct_id(3), &Default::default()) - .unwrap(), - Some(CommitOperation::BlockFinalized(correct_id(3), None, Default::default())), - ); - assert!(tx.inserted_entries().is_empty()); - assert!(tx.removed_entries().is_empty()); - assert_eq!( - *tx.updated_meta(), - Some(Metadata { finalized: Some(correct_id(2)), unfinalized: vec![correct_id(5)] }), - ); - // finalization finalizes entry - let cache = ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![correct_id(5)]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 5 }, - ), - PruningStrategy::ByDepth(1024), - correct_id(4), - ) - .unwrap(); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_finalize(&mut tx, correct_id(4), correct_id(5), &Default::default()) - .unwrap(), - Some(CommitOperation::BlockFinalized( - correct_id(5), - Some(Entry { valid_from: correct_id(5), value: 5 }), - vec![0].into_iter().collect(), - )), - ); - assert!(tx.inserted_entries().is_empty()); - assert!(tx.removed_entries().is_empty()); - assert_eq!( - *tx.updated_meta(), - Some(Metadata { finalized: Some(correct_id(5)), unfinalized: vec![] }) - ); - // finalization removes abandoned forks - let cache = ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![fork_id(0, 1, 3)]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_entry(fork_id(0, 1, 3), StorageEntry { prev_valid_from: None, value: 13 }), - PruningStrategy::ByDepth(1024), - correct_id(2), - ) - .unwrap(); - let mut tx = DummyTransaction::new(); - assert_eq!( - cache - .do_on_block_finalize(&mut tx, correct_id(2), correct_id(3), &Default::default()) - .unwrap(), - Some(CommitOperation::BlockFinalized( - correct_id(3), - None, - vec![0].into_iter().collect() - )), - ); - } - - #[test] - fn list_transaction_commit_works() { - let mut cache = ListCache::new( - DummyStorage::new() - .with_meta(Some(correct_id(2)), vec![correct_id(5), correct_id(6)]) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 5 }, - ) - .with_entry( - correct_id(6), - StorageEntry { prev_valid_from: Some(correct_id(5)), value: 6 }, - ), - PruningStrategy::ByDepth(1024), - correct_id(2), - ) - .unwrap(); - - // when new block is appended to unfinalized fork - cache.on_transaction_commit(vec![CommitOperation::AppendNewBlock(0, correct_id(6))].into()); - assert_eq!(cache.unfinalized[0].best_block, Some(correct_id(6))); - // when new entry is appended to unfinalized fork - cache.on_transaction_commit( - vec![CommitOperation::AppendNewEntry(0, Entry { valid_from: correct_id(7), value: 7 })] - .into(), - ); - assert_eq!(cache.unfinalized[0].best_block, Some(correct_id(7))); - assert_eq!(cache.unfinalized[0].head, Entry { valid_from: correct_id(7), value: 7 }); - // when new fork is added - cache.on_transaction_commit( - vec![CommitOperation::AddNewFork(Entry { valid_from: correct_id(10), value: 10 })] - .into(), - ); - assert_eq!(cache.unfinalized[2].best_block, Some(correct_id(10))); - assert_eq!(cache.unfinalized[2].head, Entry { valid_from: correct_id(10), value: 10 }); - // when block is finalized + entry is finalized + unfinalized forks are deleted - cache.on_transaction_commit( - vec![CommitOperation::BlockFinalized( - correct_id(20), - Some(Entry { valid_from: correct_id(20), value: 20 }), - vec![0, 1, 2].into_iter().collect(), - )] - .into(), - ); - assert_eq!(cache.best_finalized_block, correct_id(20)); - assert_eq!( - cache.best_finalized_entry, - Some(Entry { valid_from: correct_id(20), value: 20 }) - ); - assert!(cache.unfinalized.is_empty()); - } - - #[test] - fn list_find_unfinalized_fork_works() { - // ----------> [3] - // --- [2] ---------> 4 ---> [5] - assert_eq!( - ListCache::new( - DummyStorage::new() - .with_meta(None, vec![fork_id(0, 1, 3), correct_id(5)]) - .with_entry( - fork_id(0, 1, 3), - StorageEntry { prev_valid_from: Some(correct_id(1)), value: 13 } - ) - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 5 } - ) - .with_entry(correct_id(2), StorageEntry { prev_valid_from: None, value: 2 }) - .with_header(test_header(2)) - .with_header(test_header(3)) - .with_header(test_header(4)) - .with_header(test_header(5)), - PruningStrategy::ByDepth(1024), - correct_id(0) - ) - .unwrap() - .find_unfinalized_fork((&correct_id(4)).into()) - .unwrap() - .unwrap() - .head - .valid_from, - correct_id(5) - ); - // --- [2] ---------------> [5] - // ----------> [3] ---> 4 - assert_eq!( - ListCache::new( - DummyStorage::new() - .with_meta(None, vec![correct_id(5), fork_id(0, 1, 3)]) - .with_entry( - fork_id(0, 1, 3), - StorageEntry { prev_valid_from: Some(correct_id(1)), value: 13 } - ) - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 5 } - ) - .with_entry( - correct_id(2), - StorageEntry { prev_valid_from: Some(correct_id(1)), value: 2 } - ) - .with_header(test_header(2)) - .with_header(test_header(3)) - .with_header(test_header(4)) - .with_header(test_header(5)) - .with_header(fork_header(0, 1, 2)) - .with_header(fork_header(0, 1, 3)) - .with_header(fork_header(0, 1, 4)), - PruningStrategy::ByDepth(1024), - correct_id(0) - ) - .unwrap() - .find_unfinalized_fork((&fork_id(0, 1, 4)).into()) - .unwrap() - .unwrap() - .head - .valid_from, - fork_id(0, 1, 3) - ); - // --- [2] ---------------> [5] - // ----------> [3] - // -----------------> 4 - assert!(ListCache::new( - DummyStorage::new() - .with_meta(None, vec![correct_id(5), fork_id(0, 1, 3)]) - .with_entry( - fork_id(0, 1, 3), - StorageEntry { prev_valid_from: Some(correct_id(1)), value: 13 } - ) - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(2)), value: 5 } - ) - .with_entry( - correct_id(2), - StorageEntry { prev_valid_from: Some(correct_id(1)), value: 2 } - ) - .with_header(test_header(2)) - .with_header(test_header(3)) - .with_header(test_header(4)) - .with_header(test_header(5)) - .with_header(fork_header(0, 1, 3)) - .with_header(fork_header(0, 1, 4)) - .with_header(fork_header(1, 1, 2)) - .with_header(fork_header(1, 1, 3)) - .with_header(fork_header(1, 1, 4)), - PruningStrategy::ByDepth(1024), - correct_id(0) - ) - .unwrap() - .find_unfinalized_fork((&fork_id(1, 1, 4)).into()) - .unwrap() - .is_none()); - } - - #[test] - fn fork_matches_works() { - // when block is not within list range - let storage = DummyStorage::new() - .with_entry( - test_id(100), - StorageEntry { prev_valid_from: Some(test_id(50)), value: 100 }, - ) - .with_entry(test_id(50), StorageEntry { prev_valid_from: None, value: 50 }); - assert_eq!( - Fork::<_, u64> { best_block: None, head: Entry { valid_from: test_id(100), value: 0 } } - .matches(&storage, (&test_id(20)).into()) - .unwrap(), - false - ); - // when block is not connected to the begin block - let storage = DummyStorage::new() - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(3)), value: 100 }, - ) - .with_entry(correct_id(3), StorageEntry { prev_valid_from: None, value: 200 }) - .with_header(test_header(5)) - .with_header(test_header(4)) - .with_header(test_header(3)) - .with_header(fork_header(0, 2, 4)) - .with_header(fork_header(0, 2, 3)); - assert_eq!( - Fork::<_, u64> { - best_block: None, - head: Entry { valid_from: correct_id(5), value: 100 } - } - .matches(&storage, (&fork_id(0, 2, 4)).into()) - .unwrap(), - false - ); - // when block is not connected to the end block - let storage = DummyStorage::new() - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(3)), value: 100 }, - ) - .with_entry(correct_id(3), StorageEntry { prev_valid_from: None, value: 200 }) - .with_header(test_header(5)) - .with_header(test_header(4)) - .with_header(test_header(3)) - .with_header(fork_header(0, 3, 4)); - assert_eq!( - Fork::<_, u64> { - best_block: None, - head: Entry { valid_from: correct_id(5), value: 100 } - } - .matches(&storage, (&fork_id(0, 3, 4)).into()) - .unwrap(), - false - ); - // when block is connected to the begin block AND end is open - let storage = DummyStorage::new() - .with_entry(correct_id(5), StorageEntry { prev_valid_from: None, value: 100 }) - .with_header(test_header(5)) - .with_header(test_header(6)); - assert_eq!( - Fork::<_, u64> { - best_block: None, - head: Entry { valid_from: correct_id(5), value: 100 } - } - .matches(&storage, (&correct_id(6)).into()) - .unwrap(), - true - ); - // when block is connected to the begin block AND to the end block - let storage = DummyStorage::new() - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(3)), value: 100 }, - ) - .with_entry(correct_id(3), StorageEntry { prev_valid_from: None, value: 200 }) - .with_header(test_header(5)) - .with_header(test_header(4)) - .with_header(test_header(3)); - assert_eq!( - Fork::<_, u64> { - best_block: None, - head: Entry { valid_from: correct_id(5), value: 100 } - } - .matches(&storage, (&correct_id(4)).into()) - .unwrap(), - true - ); - } - - #[test] - fn fork_try_append_works() { - // when best block is unknown - assert_eq!( - Fork::<_, u64> { best_block: None, head: Entry { valid_from: test_id(100), value: 0 } } - .try_append(&test_id(100)), - false - ); - // when best block is known but different - assert_eq!( - Fork::<_, u64> { best_block: None, head: Entry { valid_from: test_id(100), value: 0 } } - .try_append(&test_id(101)), - false - ); - // when best block is known and the same - assert_eq!( - Fork::<_, u64> { - best_block: Some(test_id(100)), - head: Entry { valid_from: test_id(100), value: 0 } - } - .try_append(&test_id(100)), - true - ); - } - - #[test] - fn fork_try_append_or_fork_works() { - // when there's no entry before parent - let storage = DummyStorage::new() - .with_entry( - test_id(100), - StorageEntry { prev_valid_from: Some(test_id(50)), value: 100 }, - ) - .with_entry(test_id(50), StorageEntry { prev_valid_from: None, value: 50 }); - assert_eq!( - Fork::<_, u64> { best_block: None, head: Entry { valid_from: test_id(100), value: 0 } } - .try_append_or_fork(&storage, &test_id(30), None) - .unwrap(), - None - ); - // when parent does not belong to the fork - let storage = DummyStorage::new() - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(3)), value: 100 }, - ) - .with_entry(correct_id(3), StorageEntry { prev_valid_from: None, value: 200 }) - .with_header(test_header(5)) - .with_header(test_header(4)) - .with_header(test_header(3)) - .with_header(fork_header(0, 2, 4)) - .with_header(fork_header(0, 2, 3)); - assert_eq!( - Fork::<_, u64> { - best_block: None, - head: Entry { valid_from: correct_id(5), value: 100 } - } - .try_append_or_fork(&storage, &fork_id(0, 2, 4), None) - .unwrap(), - None - ); - // when the entry before parent is the head entry - let storage = DummyStorage::new() - .with_entry( - ComplexBlockId::new(test_header(5).hash(), 5), - StorageEntry { prev_valid_from: Some(correct_id(3)), value: 100 }, - ) - .with_header(test_header(6)) - .with_header(test_header(5)); - assert_eq!( - Fork::<_, u64> { - best_block: None, - head: Entry { valid_from: correct_id(5), value: 100 } - } - .try_append_or_fork(&storage, &correct_id(6), None) - .unwrap(), - Some(ForkAppendResult::Append) - ); - // when the parent located after last finalized entry - let storage = DummyStorage::new() - .with_entry( - correct_id(6), - StorageEntry { prev_valid_from: Some(correct_id(3)), value: 100 }, - ) - .with_entry(correct_id(3), StorageEntry { prev_valid_from: None, value: 200 }) - .with_header(test_header(6)) - .with_header(test_header(5)) - .with_header(test_header(4)) - .with_header(test_header(3)) - .with_header(fork_header(0, 4, 5)); - assert_eq!( - Fork::<_, u64> { - best_block: None, - head: Entry { valid_from: correct_id(6), value: 100 } - } - .try_append_or_fork(&storage, &fork_id(0, 4, 5), None) - .unwrap(), - Some(ForkAppendResult::Fork(ComplexBlockId::new(test_header(3).hash(), 3))) - ); - // when the parent located before last finalized entry - let storage = DummyStorage::new() - .with_entry( - correct_id(6), - StorageEntry { prev_valid_from: Some(correct_id(3)), value: 100 }, - ) - .with_entry(correct_id(3), StorageEntry { prev_valid_from: None, value: 200 }) - .with_header(test_header(6)) - .with_header(test_header(5)) - .with_header(test_header(4)) - .with_header(test_header(3)) - .with_header(fork_header(0, 4, 5)); - assert_eq!( - Fork::<_, u64> { - best_block: None, - head: Entry { valid_from: correct_id(6), value: 100 } - } - .try_append_or_fork(&storage, &fork_id(0, 4, 5), Some(3)) - .unwrap(), - None - ); - } - - #[test] - fn fork_destroy_works() { - // when we reached finalized entry without iterations - let storage = DummyStorage::new().with_id(100, H256::from_low_u64_be(100)); - let mut tx = DummyTransaction::new(); - Fork::<_, u64> { best_block: None, head: Entry { valid_from: test_id(100), value: 0 } } - .destroy(&storage, &mut tx, Some(200)) - .unwrap(); - assert!(tx.removed_entries().is_empty()); - // when we reach finalized entry with iterations - let storage = DummyStorage::new() - .with_id(10, H256::from_low_u64_be(10)) - .with_entry( - test_id(100), - StorageEntry { prev_valid_from: Some(test_id(50)), value: 100 }, - ) - .with_entry(test_id(50), StorageEntry { prev_valid_from: Some(test_id(20)), value: 50 }) - .with_entry(test_id(20), StorageEntry { prev_valid_from: Some(test_id(10)), value: 20 }) - .with_entry(test_id(10), StorageEntry { prev_valid_from: Some(test_id(5)), value: 10 }) - .with_entry(test_id(5), StorageEntry { prev_valid_from: Some(test_id(3)), value: 5 }) - .with_entry(test_id(3), StorageEntry { prev_valid_from: None, value: 0 }); - let mut tx = DummyTransaction::new(); - Fork::<_, u64> { best_block: None, head: Entry { valid_from: test_id(100), value: 0 } } - .destroy(&storage, &mut tx, Some(200)) - .unwrap(); - assert_eq!( - *tx.removed_entries(), - vec![test_id(100).hash, test_id(50).hash, test_id(20).hash] - .into_iter() - .collect() - ); - // when we reach beginning of fork before finalized block - let storage = DummyStorage::new() - .with_id(10, H256::from_low_u64_be(10)) - .with_entry( - test_id(100), - StorageEntry { prev_valid_from: Some(test_id(50)), value: 100 }, - ) - .with_entry(test_id(50), StorageEntry { prev_valid_from: None, value: 50 }); - let mut tx = DummyTransaction::new(); - Fork::<_, u64> { best_block: None, head: Entry { valid_from: test_id(100), value: 0 } } - .destroy(&storage, &mut tx, Some(200)) - .unwrap(); - assert_eq!( - *tx.removed_entries(), - vec![test_id(100).hash, test_id(50).hash].into_iter().collect() - ); - } - - #[test] - fn is_connected_to_block_fails() { - // when storage returns error - assert!(chain::is_connected_to_block::<_, u64, _>( - &FaultyStorage, - (&test_id(1)).into(), - &test_id(100), - ) - .is_err(),); - // when there's no header in the storage - assert!(chain::is_connected_to_block::<_, u64, _>( - &DummyStorage::new(), - (&test_id(1)).into(), - &test_id(100), - ) - .is_err(),); - } - - #[test] - fn is_connected_to_block_works() { - // when without iterations we end up with different block - assert_eq!( - chain::is_connected_to_block::<_, u64, _>( - &DummyStorage::new().with_header(test_header(1)), - (&test_id(1)).into(), - &correct_id(1) - ) - .unwrap(), - false - ); - // when with ASC iterations we end up with different block - assert_eq!( - chain::is_connected_to_block::<_, u64, _>( - &DummyStorage::new() - .with_header(test_header(0)) - .with_header(test_header(1)) - .with_header(test_header(2)), - (&test_id(0)).into(), - &correct_id(2) - ) - .unwrap(), - false - ); - // when with DESC iterations we end up with different block - assert_eq!( - chain::is_connected_to_block::<_, u64, _>( - &DummyStorage::new() - .with_header(test_header(0)) - .with_header(test_header(1)) - .with_header(test_header(2)), - (&correct_id(2)).into(), - &test_id(0) - ) - .unwrap(), - false - ); - // when without iterations we end up with the same block - assert_eq!( - chain::is_connected_to_block::<_, u64, _>( - &DummyStorage::new().with_header(test_header(1)), - (&correct_id(1)).into(), - &correct_id(1) - ) - .unwrap(), - true - ); - // when with ASC iterations we end up with the same block - assert_eq!( - chain::is_connected_to_block::<_, u64, _>( - &DummyStorage::new() - .with_header(test_header(0)) - .with_header(test_header(1)) - .with_header(test_header(2)), - (&correct_id(0)).into(), - &correct_id(2) - ) - .unwrap(), - true - ); - // when with DESC iterations we end up with the same block - assert_eq!( - chain::is_connected_to_block::<_, u64, _>( - &DummyStorage::new() - .with_header(test_header(0)) - .with_header(test_header(1)) - .with_header(test_header(2)), - (&correct_id(2)).into(), - &correct_id(0) - ) - .unwrap(), - true - ); - } - - #[test] - fn is_finalized_block_fails() { - // when storage returns error - assert!(chain::is_finalized_block::<_, u64, _>(&FaultyStorage, &test_id(1), 100).is_err()); - } - - #[test] - fn is_finalized_block_works() { - // when number of block is larger than last finalized block - assert_eq!( - chain::is_finalized_block::<_, u64, _>(&DummyStorage::new(), &test_id(100), 1).unwrap(), - false - ); - // when there's no hash for this block number in the database - assert_eq!( - chain::is_finalized_block::<_, u64, _>(&DummyStorage::new(), &test_id(1), 100).unwrap(), - false - ); - // when there's different hash for this block number in the database - assert_eq!( - chain::is_finalized_block::<_, u64, _>( - &DummyStorage::new().with_id(1, H256::from_low_u64_be(2)), - &test_id(1), - 100 - ) - .unwrap(), - false - ); - // when there's the same hash for this block number in the database - assert_eq!( - chain::is_finalized_block::<_, u64, _>( - &DummyStorage::new().with_id(1, H256::from_low_u64_be(1)), - &test_id(1), - 100 - ) - .unwrap(), - true - ); - } - - #[test] - fn read_forks_fails() { - // when storage returns error during finalized entry read - assert!(read_forks::( - &FaultyStorage, - Metadata { finalized: Some(test_id(1)), unfinalized: vec![] } - ) - .is_err()); - // when storage returns error during unfinalized entry read - assert!(read_forks::( - &FaultyStorage, - Metadata { finalized: None, unfinalized: vec![test_id(1)] } - ) - .is_err()); - // when finalized entry is not found - assert!(read_forks::( - &DummyStorage::new(), - Metadata { finalized: Some(test_id(1)), unfinalized: vec![] } - ) - .is_err()); - // when unfinalized entry is not found - assert!(read_forks::( - &DummyStorage::new(), - Metadata { finalized: None, unfinalized: vec![test_id(1)] } - ) - .is_err()); - } - - #[test] - fn read_forks_works() { - let storage = DummyStorage::new() - .with_entry(test_id(10), StorageEntry { prev_valid_from: Some(test_id(1)), value: 11 }) - .with_entry(test_id(20), StorageEntry { prev_valid_from: Some(test_id(2)), value: 0 }) - .with_entry(test_id(30), StorageEntry { prev_valid_from: None, value: 33 }); - let expected = ( - Some(Entry { valid_from: test_id(10), value: 11 }), - vec![ - Fork { best_block: None, head: Entry { valid_from: test_id(20), value: 0 } }, - Fork { best_block: None, head: Entry { valid_from: test_id(30), value: 33 } }, - ], - ); - - assert_eq!( - expected, - read_forks( - &storage, - Metadata { - finalized: Some(test_id(10)), - unfinalized: vec![test_id(20), test_id(30)], - } - ) - .unwrap() - ); - } - - #[test] - fn ancient_entries_are_pruned_when_pruning_enabled() { - fn do_test(strategy: PruningStrategy) { - let cache = ListCache::new( - DummyStorage::new() - .with_id(10, H256::from_low_u64_be(10)) - .with_id(20, H256::from_low_u64_be(20)) - .with_id(30, H256::from_low_u64_be(30)) - .with_entry(test_id(10), StorageEntry { prev_valid_from: None, value: 10 }) - .with_entry( - test_id(20), - StorageEntry { prev_valid_from: Some(test_id(10)), value: 20 }, - ) - .with_entry( - test_id(30), - StorageEntry { prev_valid_from: Some(test_id(20)), value: 30 }, - ), - strategy, - test_id(9), - ) - .unwrap(); - let mut tx = DummyTransaction::new(); - - // when finalizing entry #10: no entries pruned - cache.prune_finalized_entries(&mut tx, &test_id(10)); - assert!(tx.removed_entries().is_empty()); - assert!(tx.inserted_entries().is_empty()); - // when finalizing entry #19: no entries pruned - cache.prune_finalized_entries(&mut tx, &test_id(19)); - assert!(tx.removed_entries().is_empty()); - assert!(tx.inserted_entries().is_empty()); - // when finalizing entry #20: no entries pruned - cache.prune_finalized_entries(&mut tx, &test_id(20)); - assert!(tx.removed_entries().is_empty()); - assert!(tx.inserted_entries().is_empty()); - // when finalizing entry #30: entry 10 pruned + entry 20 is truncated (if pruning is - // enabled) - cache.prune_finalized_entries(&mut tx, &test_id(30)); - match strategy { - PruningStrategy::NeverPrune => { - assert!(tx.removed_entries().is_empty()); - assert!(tx.inserted_entries().is_empty()); - }, - PruningStrategy::ByDepth(_) => { - assert_eq!(*tx.removed_entries(), vec![test_id(10).hash].into_iter().collect()); - assert_eq!( - *tx.inserted_entries(), - vec![test_id(20).hash].into_iter().collect() - ); - }, - } - } - - do_test(PruningStrategy::ByDepth(10)); - do_test(PruningStrategy::NeverPrune) - } - - #[test] - fn revert_block_works() { - // 1 -> (2) -> 3 -> 4 -> 5 - // \ - // -> 5'' - // \ - // -> (3') -> 4' -> 5' - let mut cache = ListCache::new( - DummyStorage::new() - .with_meta( - Some(correct_id(1)), - vec![correct_id(5), fork_id(1, 2, 5), fork_id(2, 4, 5)], - ) - .with_id(1, correct_id(1).hash) - .with_entry(correct_id(1), StorageEntry { prev_valid_from: None, value: 1 }) - .with_entry( - correct_id(3), - StorageEntry { prev_valid_from: Some(correct_id(1)), value: 3 }, - ) - .with_entry( - correct_id(4), - StorageEntry { prev_valid_from: Some(correct_id(3)), value: 4 }, - ) - .with_entry( - correct_id(5), - StorageEntry { prev_valid_from: Some(correct_id(4)), value: 5 }, - ) - .with_entry( - fork_id(1, 2, 4), - StorageEntry { prev_valid_from: Some(correct_id(1)), value: 14 }, - ) - .with_entry( - fork_id(1, 2, 5), - StorageEntry { prev_valid_from: Some(fork_id(1, 2, 4)), value: 15 }, - ) - .with_entry( - fork_id(2, 4, 5), - StorageEntry { prev_valid_from: Some(correct_id(4)), value: 25 }, - ) - .with_header(test_header(1)) - .with_header(test_header(2)) - .with_header(test_header(3)) - .with_header(test_header(4)) - .with_header(test_header(5)) - .with_header(fork_header(1, 2, 3)) - .with_header(fork_header(1, 2, 4)) - .with_header(fork_header(1, 2, 5)) - .with_header(fork_header(2, 4, 5)), - PruningStrategy::ByDepth(1024), - correct_id(1), - ) - .unwrap(); - - // when 5 is reverted: entry 5 is truncated - let op = cache.do_on_block_revert(&mut DummyTransaction::new(), &correct_id(5)).unwrap(); - assert_eq!( - op, - CommitOperation::BlockReverted( - vec![( - 0, - Some(Fork { - best_block: None, - head: Entry { valid_from: correct_id(4), value: 4 } - }) - ),] - .into_iter() - .collect() - ) - ); - cache.on_transaction_commit(vec![op].into()); - - // when 3 is reverted: entries 4+5' are truncated - let op = cache.do_on_block_revert(&mut DummyTransaction::new(), &correct_id(3)).unwrap(); - assert_eq!( - op, - CommitOperation::BlockReverted(vec![(0, None), (2, None),].into_iter().collect()) - ); - cache.on_transaction_commit(vec![op].into()); - - // when 2 is reverted: entries 4'+5' are truncated - let op = cache.do_on_block_revert(&mut DummyTransaction::new(), &correct_id(2)).unwrap(); - assert_eq!(op, CommitOperation::BlockReverted(vec![(0, None),].into_iter().collect())); - cache.on_transaction_commit(vec![op].into()); - } - - #[test] - fn append_commit_operation_works() { - let mut ops = CommitOperations::default(); - ops.append(None); - assert_eq!(ops.operations, Vec::new()); - - ops.append(Some(CommitOperation::BlockFinalized( - test_id(10), - Some(Entry { valid_from: test_id(10), value: 10 }), - vec![5].into_iter().collect(), - ))); - assert_eq!( - ops.operations, - vec![CommitOperation::BlockFinalized( - test_id(10), - Some(Entry { valid_from: test_id(10), value: 10 }), - vec![5].into_iter().collect(), - )], - ); - - ops.append(Some(CommitOperation::BlockFinalized( - test_id(20), - Some(Entry { valid_from: test_id(20), value: 20 }), - vec![5, 6].into_iter().collect(), - ))); - - assert_eq!( - ops.operations, - vec![CommitOperation::BlockFinalized( - test_id(20), - Some(Entry { valid_from: test_id(20), value: 20 }), - vec![5, 6].into_iter().collect(), - )], - ); - } -} diff --git a/client/db/src/cache/list_entry.rs b/client/db/src/cache/list_entry.rs deleted file mode 100644 index 7cee7a5146260..0000000000000 --- a/client/db/src/cache/list_entry.rs +++ /dev/null @@ -1,187 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! List-cache storage entries. - -use codec::{Decode, Encode}; -use sp_blockchain::Result as ClientResult; -use sp_runtime::traits::{Block as BlockT, NumberFor}; - -use crate::cache::{list_storage::Storage, CacheItemT, ComplexBlockId}; - -/// Single list-based cache entry. -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] -pub struct Entry { - /// first block, when this value became actual. - pub valid_from: ComplexBlockId, - /// Value stored at this entry. - pub value: T, -} - -/// Internal representation of the single list-based cache entry. The entry points to the -/// previous entry in the cache, allowing us to traverse back in time in list-style. -#[derive(Debug, Encode, Decode)] -#[cfg_attr(test, derive(Clone, PartialEq))] -pub struct StorageEntry { - /// None if valid from the beginning. - pub prev_valid_from: Option>, - /// Value stored at this entry. - pub value: T, -} - -impl Entry { - /// Returns Some if the entry should be updated with the new value. - pub fn try_update(&self, value: Option) -> Option> { - match value { - Some(value) => match self.value == value { - true => None, - false => - Some(StorageEntry { prev_valid_from: Some(self.valid_from.clone()), value }), - }, - None => None, - } - } - - /// Wrapper that calls search_before to get range where the given block fits. - pub fn search_best_range_before>( - &self, - storage: &S, - block: NumberFor, - ) -> ClientResult, Option>)>> { - Ok(self - .search_best_before(storage, block)? - .map(|(entry, next)| (entry.valid_from, next))) - } - - /// Searches the list, ending with THIS entry for the best entry preceding (or at) - /// given block number. - /// If the entry is found, result is the entry and the block id of next entry (if exists). - /// NOTE that this function does not check that the passed block is actually linked to - /// the blocks it found. - pub fn search_best_before>( - &self, - storage: &S, - block: NumberFor, - ) -> ClientResult, Option>)>> { - // we're looking for the best value - let mut next = None; - let mut current = self.valid_from.clone(); - if block >= self.valid_from.number { - let value = self.value.clone(); - return Ok(Some((Entry { valid_from: current, value }, next))) - } - - // else - travel back in time - loop { - let entry = storage.require_entry(¤t)?; - if block >= current.number { - return Ok(Some((Entry { valid_from: current, value: entry.value }, next))) - } - - next = Some(current); - current = match entry.prev_valid_from { - Some(prev_valid_from) => prev_valid_from, - None => return Ok(None), - }; - } - } -} - -impl StorageEntry { - /// Converts storage entry into an entry, valid from given block. - pub fn into_entry(self, valid_from: ComplexBlockId) -> Entry { - Entry { valid_from, value: self.value } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::cache::list_storage::tests::{DummyStorage, FaultyStorage}; - use substrate_test_runtime_client::runtime::{Block, H256}; - - fn test_id(number: u64) -> ComplexBlockId { - ComplexBlockId::new(H256::from_low_u64_be(number), number) - } - - #[test] - fn entry_try_update_works() { - // when trying to update with None value - assert_eq!(Entry::<_, u64> { valid_from: test_id(1), value: 42 }.try_update(None), None); - // when trying to update with the same Some value - assert_eq!(Entry { valid_from: test_id(1), value: 1 }.try_update(Some(1)), None); - // when trying to update with different Some value - assert_eq!( - Entry { valid_from: test_id(1), value: 1 }.try_update(Some(2)), - Some(StorageEntry { prev_valid_from: Some(test_id(1)), value: 2 }) - ); - } - - #[test] - fn entry_search_best_before_fails() { - // when storage returns error - assert!(Entry::<_, u64> { valid_from: test_id(100), value: 42 } - .search_best_before(&FaultyStorage, 50) - .is_err()); - } - - #[test] - fn entry_search_best_before_works() { - // when block is better than our best block - assert_eq!( - Entry::<_, u64> { valid_from: test_id(100), value: 100 } - .search_best_before(&DummyStorage::new(), 150) - .unwrap(), - Some((Entry::<_, u64> { valid_from: test_id(100), value: 100 }, None)) - ); - // when block is found between two entries - assert_eq!( - Entry::<_, u64> { valid_from: test_id(100), value: 100 } - .search_best_before( - &DummyStorage::new() - .with_entry( - test_id(100), - StorageEntry { prev_valid_from: Some(test_id(50)), value: 100 } - ) - .with_entry( - test_id(50), - StorageEntry { prev_valid_from: Some(test_id(30)), value: 50 } - ), - 75 - ) - .unwrap(), - Some((Entry::<_, u64> { valid_from: test_id(50), value: 50 }, Some(test_id(100)))) - ); - // when block is not found - assert_eq!( - Entry::<_, u64> { valid_from: test_id(100), value: 100 } - .search_best_before( - &DummyStorage::new() - .with_entry( - test_id(100), - StorageEntry { prev_valid_from: Some(test_id(50)), value: 100 } - ) - .with_entry(test_id(50), StorageEntry { prev_valid_from: None, value: 50 }), - 30 - ) - .unwrap(), - None - ); - } -} diff --git a/client/db/src/cache/list_storage.rs b/client/db/src/cache/list_storage.rs deleted file mode 100644 index bb47b8dab5a7f..0000000000000 --- a/client/db/src/cache/list_storage.rs +++ /dev/null @@ -1,441 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! List-cache storage definition and implementation. - -use std::sync::Arc; - -use crate::utils::{self, meta_keys}; -use codec::{Decode, Encode}; -use sp_blockchain::{Error as ClientError, Result as ClientResult}; -use sp_database::{Database, Transaction}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, NumberFor}, -}; - -use crate::{ - cache::{ - list_cache::{CommitOperation, Fork}, - list_entry::{Entry, StorageEntry}, - CacheItemT, ComplexBlockId, - }, - DbHash, -}; - -/// Single list-cache metadata. -#[derive(Debug)] -#[cfg_attr(test, derive(Clone, PartialEq))] -pub struct Metadata { - /// Block at which best finalized entry is stored. - pub finalized: Option>, - /// A set of blocks at which best unfinalized entries are stored. - pub unfinalized: Vec>, -} - -/// Readonly list-cache storage trait. -pub trait Storage { - /// Reads hash of the block at given number. - fn read_id(&self, at: NumberFor) -> ClientResult>; - - /// Reads header of the block with given hash. - fn read_header(&self, at: &Block::Hash) -> ClientResult>; - - /// Reads cache metadata: best finalized entry (if some) and the list. - fn read_meta(&self) -> ClientResult>; - - /// Reads cache entry from the storage. - fn read_entry( - &self, - at: &ComplexBlockId, - ) -> ClientResult>>; - - /// Reads referenced (and thus existing) cache entry from the storage. - fn require_entry(&self, at: &ComplexBlockId) -> ClientResult> { - self.read_entry(at).and_then(|entry| { - entry.ok_or_else(|| { - ClientError::from(ClientError::Backend(format!( - "Referenced cache entry at {:?} is not found", - at - ))) - }) - }) - } -} - -/// List-cache storage transaction. -pub trait StorageTransaction { - /// Insert storage entry at given block. - fn insert_storage_entry(&mut self, at: &ComplexBlockId, entry: &StorageEntry); - - /// Delete storage entry at given block. - fn remove_storage_entry(&mut self, at: &ComplexBlockId); - - /// Update metadata of the cache. - fn update_meta( - &mut self, - best_finalized_entry: Option<&Entry>, - unfinalized: &[Fork], - operation: &CommitOperation, - ); -} - -/// A set of columns used by the DbStorage. -#[derive(Debug)] -pub struct DbColumns { - /// Column holding cache meta. - pub meta: u32, - /// Column holding the mapping of { block number => block hash } for blocks of the best chain. - pub key_lookup: u32, - /// Column holding the mapping of { block hash => block header }. - pub header: u32, - /// Column holding cache entries. - pub cache: u32, -} - -/// Database-backed list cache storage. -pub struct DbStorage { - name: Vec, - meta_key: Vec, - db: Arc>, - columns: DbColumns, -} - -impl DbStorage { - /// Create new database-backed list cache storage. - pub fn new(name: Vec, db: Arc>, columns: DbColumns) -> Self { - let meta_key = meta::key(&name); - DbStorage { name, meta_key, db, columns } - } - - /// Get reference to the database. - pub fn db(&self) -> &Arc> { - &self.db - } - - /// Get reference to the database columns. - pub fn columns(&self) -> &DbColumns { - &self.columns - } - - /// Encode block id for storing as a key in cache column. - /// We append prefix to the actual encoding to allow several caches - /// store entries in the same column. - pub fn encode_block_id(&self, block: &ComplexBlockId) -> Vec { - let mut encoded = self.name.clone(); - encoded.extend(block.hash.as_ref()); - encoded - } -} - -impl Storage for DbStorage { - fn read_id(&self, at: NumberFor) -> ClientResult> { - utils::read_header::( - &*self.db, - self.columns.key_lookup, - self.columns.header, - BlockId::Number(at), - ) - .map(|maybe_header| maybe_header.map(|header| header.hash())) - } - - fn read_header(&self, at: &Block::Hash) -> ClientResult> { - utils::read_header::( - &*self.db, - self.columns.key_lookup, - self.columns.header, - BlockId::Hash(*at), - ) - } - - fn read_meta(&self) -> ClientResult> { - match self.db.get(self.columns.meta, &self.meta_key) { - Some(meta) => meta::decode(&*meta), - None => Ok(Metadata { finalized: None, unfinalized: Vec::new() }), - } - } - - fn read_entry( - &self, - at: &ComplexBlockId, - ) -> ClientResult>> { - match self.db.get(self.columns.cache, &self.encode_block_id(at)) { - Some(entry) => StorageEntry::::decode(&mut &entry[..]) - .map_err(|_| ClientError::Backend("Failed to decode cache entry".into())) - .map(Some), - None => Ok(None), - } - } -} - -/// Database-backed list cache storage transaction. -pub struct DbStorageTransaction<'a> { - storage: &'a DbStorage, - tx: &'a mut Transaction, -} - -impl<'a> DbStorageTransaction<'a> { - /// Create new database transaction. - pub fn new(storage: &'a DbStorage, tx: &'a mut Transaction) -> Self { - DbStorageTransaction { storage, tx } - } -} - -impl<'a, Block: BlockT, T: CacheItemT> StorageTransaction for DbStorageTransaction<'a> { - fn insert_storage_entry(&mut self, at: &ComplexBlockId, entry: &StorageEntry) { - self.tx.set_from_vec( - self.storage.columns.cache, - &self.storage.encode_block_id(at), - entry.encode(), - ); - } - - fn remove_storage_entry(&mut self, at: &ComplexBlockId) { - self.tx.remove(self.storage.columns.cache, &self.storage.encode_block_id(at)); - } - - fn update_meta( - &mut self, - best_finalized_entry: Option<&Entry>, - unfinalized: &[Fork], - operation: &CommitOperation, - ) { - self.tx.set_from_vec( - self.storage.columns.meta, - &self.storage.meta_key, - meta::encode(best_finalized_entry, unfinalized, operation), - ); - } -} - -/// Metadata related functions. -mod meta { - use super::*; - - /// Convert cache name into cache metadata key. - pub fn key(name: &[u8]) -> Vec { - let mut key_name = meta_keys::CACHE_META_PREFIX.to_vec(); - key_name.extend_from_slice(name); - key_name - } - - /// Encode cache metadata 'applying' commit operation before encoding. - pub fn encode( - best_finalized_entry: Option<&Entry>, - unfinalized: &[Fork], - op: &CommitOperation, - ) -> Vec { - let mut finalized = best_finalized_entry.as_ref().map(|entry| &entry.valid_from); - let mut unfinalized = - unfinalized.iter().map(|fork| &fork.head().valid_from).collect::>(); - - match op { - CommitOperation::AppendNewBlock(_, _) => (), - CommitOperation::AppendNewEntry(index, ref entry) => { - unfinalized[*index] = &entry.valid_from; - }, - CommitOperation::AddNewFork(ref entry) => { - unfinalized.push(&entry.valid_from); - }, - CommitOperation::BlockFinalized(_, ref finalizing_entry, ref forks) => { - if let Some(finalizing_entry) = finalizing_entry.as_ref() { - finalized = Some(&finalizing_entry.valid_from); - } - for fork_index in forks.iter().rev() { - unfinalized.remove(*fork_index); - } - }, - CommitOperation::BlockReverted(ref forks) => { - for (fork_index, updated_fork) in forks.iter().rev() { - match updated_fork { - Some(updated_fork) => - unfinalized[*fork_index] = &updated_fork.head().valid_from, - None => { - unfinalized.remove(*fork_index); - }, - } - } - }, - } - - (finalized, unfinalized).encode() - } - - /// Decode meta information. - pub fn decode(encoded: &[u8]) -> ClientResult> { - let input = &mut &*encoded; - let finalized: Option> = Decode::decode(input).map_err(|_| { - ClientError::from(ClientError::Backend("Error decoding cache meta".into())) - })?; - let unfinalized: Vec> = Decode::decode(input).map_err(|_| { - ClientError::from(ClientError::Backend("Error decoding cache meta".into())) - })?; - - Ok(Metadata { finalized, unfinalized }) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use std::collections::{HashMap, HashSet}; - - pub struct FaultyStorage; - - impl Storage for FaultyStorage { - fn read_id(&self, _at: NumberFor) -> ClientResult> { - Err(ClientError::Backend("TestError".into())) - } - - fn read_header(&self, _at: &Block::Hash) -> ClientResult> { - Err(ClientError::Backend("TestError".into())) - } - - fn read_meta(&self) -> ClientResult> { - Err(ClientError::Backend("TestError".into())) - } - - fn read_entry( - &self, - _at: &ComplexBlockId, - ) -> ClientResult>> { - Err(ClientError::Backend("TestError".into())) - } - } - - pub struct DummyStorage { - meta: Metadata, - ids: HashMap, Block::Hash>, - headers: HashMap, - entries: HashMap>, - } - - impl DummyStorage { - pub fn new() -> Self { - DummyStorage { - meta: Metadata { finalized: None, unfinalized: Vec::new() }, - ids: HashMap::new(), - headers: HashMap::new(), - entries: HashMap::new(), - } - } - - pub fn with_meta( - mut self, - finalized: Option>, - unfinalized: Vec>, - ) -> Self { - self.meta.finalized = finalized; - self.meta.unfinalized = unfinalized; - self - } - - pub fn with_id(mut self, at: NumberFor, id: Block::Hash) -> Self { - self.ids.insert(at, id); - self - } - - pub fn with_header(mut self, header: Block::Header) -> Self { - self.headers.insert(header.hash(), header); - self - } - - pub fn with_entry( - mut self, - at: ComplexBlockId, - entry: StorageEntry, - ) -> Self { - self.entries.insert(at.hash, entry); - self - } - } - - impl Storage for DummyStorage { - fn read_id(&self, at: NumberFor) -> ClientResult> { - Ok(self.ids.get(&at).cloned()) - } - - fn read_header(&self, at: &Block::Hash) -> ClientResult> { - Ok(self.headers.get(&at).cloned()) - } - - fn read_meta(&self) -> ClientResult> { - Ok(self.meta.clone()) - } - - fn read_entry( - &self, - at: &ComplexBlockId, - ) -> ClientResult>> { - Ok(self.entries.get(&at.hash).cloned()) - } - } - - pub struct DummyTransaction { - updated_meta: Option>, - inserted_entries: HashSet, - removed_entries: HashSet, - } - - impl DummyTransaction { - pub fn new() -> Self { - DummyTransaction { - updated_meta: None, - inserted_entries: HashSet::new(), - removed_entries: HashSet::new(), - } - } - - pub fn inserted_entries(&self) -> &HashSet { - &self.inserted_entries - } - - pub fn removed_entries(&self) -> &HashSet { - &self.removed_entries - } - - pub fn updated_meta(&self) -> &Option> { - &self.updated_meta - } - } - - impl StorageTransaction for DummyTransaction { - fn insert_storage_entry( - &mut self, - at: &ComplexBlockId, - _entry: &StorageEntry, - ) { - self.inserted_entries.insert(at.hash); - } - - fn remove_storage_entry(&mut self, at: &ComplexBlockId) { - self.removed_entries.insert(at.hash); - } - - fn update_meta( - &mut self, - best_finalized_entry: Option<&Entry>, - unfinalized: &[Fork], - operation: &CommitOperation, - ) { - self.updated_meta = Some( - meta::decode(&meta::encode(best_finalized_entry, unfinalized, operation)).unwrap(), - ); - } - } -} diff --git a/client/db/src/cache/mod.rs b/client/db/src/cache/mod.rs deleted file mode 100644 index 5502896aced2c..0000000000000 --- a/client/db/src/cache/mod.rs +++ /dev/null @@ -1,413 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! DB-backed cache of blockchain data. - -use parking_lot::RwLock; -use std::{ - collections::{hash_map::Entry, HashMap}, - sync::Arc, -}; - -use crate::{ - utils::{self, COLUMN_META}, - DbHash, -}; -use codec::{Decode, Encode}; -use sc_client_api::blockchain::{ - well_known_cache_keys::{self, Id as CacheKeyId}, - Cache as BlockchainCache, -}; -use sp_blockchain::{HeaderMetadataCache, Result as ClientResult}; -use sp_database::{Database, Transaction}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}, -}; - -use self::list_cache::{ListCache, PruningStrategy}; - -mod list_cache; -mod list_entry; -mod list_storage; - -/// Minimal post-finalization age of finalized blocks before they'll pruned. -const PRUNE_DEPTH: u32 = 1024; - -/// The type of entry that is inserted to the cache. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum EntryType { - /// Non-final entry. - NonFinal, - /// Final entry. - Final, - /// Genesis entry (inserted during cache initialization). - Genesis, -} - -/// Block identifier that holds both hash and number. -#[derive(Clone, Debug, Encode, Decode, PartialEq)] -pub struct ComplexBlockId { - /// Hash of the block. - pub(crate) hash: Block::Hash, - /// Number of the block. - pub(crate) number: NumberFor, -} - -impl ComplexBlockId { - /// Create new complex block id. - pub fn new(hash: Block::Hash, number: NumberFor) -> Self { - ComplexBlockId { hash, number } - } -} - -impl ::std::cmp::PartialOrd for ComplexBlockId { - fn partial_cmp(&self, other: &ComplexBlockId) -> Option<::std::cmp::Ordering> { - self.number.partial_cmp(&other.number) - } -} - -/// All cache items must implement this trait. -pub trait CacheItemT: Clone + Decode + Encode + PartialEq {} - -impl CacheItemT for T where T: Clone + Decode + Encode + PartialEq {} - -/// Database-backed blockchain data cache. -pub struct DbCache { - cache_at: HashMap, self::list_storage::DbStorage>>, - header_metadata_cache: Arc>, - db: Arc>, - key_lookup_column: u32, - header_column: u32, - cache_column: u32, - genesis_hash: Block::Hash, - best_finalized_block: ComplexBlockId, -} - -impl DbCache { - /// Create new cache. - pub fn new( - db: Arc>, - header_metadata_cache: Arc>, - key_lookup_column: u32, - header_column: u32, - cache_column: u32, - genesis_hash: Block::Hash, - best_finalized_block: ComplexBlockId, - ) -> Self { - Self { - cache_at: HashMap::new(), - db, - header_metadata_cache, - key_lookup_column, - header_column, - cache_column, - genesis_hash, - best_finalized_block, - } - } - - /// Set genesis block hash. - pub fn set_genesis_hash(&mut self, genesis_hash: Block::Hash) { - self.genesis_hash = genesis_hash; - } - - /// Begin cache transaction. - pub fn transaction<'a>( - &'a mut self, - tx: &'a mut Transaction, - ) -> DbCacheTransaction<'a, Block> { - DbCacheTransaction { - cache: self, - tx, - cache_at_ops: HashMap::new(), - best_finalized_block: None, - } - } - - /// Begin cache transaction with given ops. - pub fn transaction_with_ops<'a>( - &'a mut self, - tx: &'a mut Transaction, - ops: DbCacheTransactionOps, - ) -> DbCacheTransaction<'a, Block> { - DbCacheTransaction { - cache: self, - tx, - cache_at_ops: ops.cache_at_ops, - best_finalized_block: ops.best_finalized_block, - } - } - - /// Run post-commit cache operations. - pub fn commit(&mut self, ops: DbCacheTransactionOps) -> ClientResult<()> { - for (name, ops) in ops.cache_at_ops.into_iter() { - self.get_cache(name)?.on_transaction_commit(ops); - } - if let Some(best_finalized_block) = ops.best_finalized_block { - self.best_finalized_block = best_finalized_block; - } - Ok(()) - } - - /// Creates `ListCache` with the given name or returns a reference to the existing. - pub(crate) fn get_cache( - &mut self, - name: CacheKeyId, - ) -> ClientResult<&mut ListCache, self::list_storage::DbStorage>> { - get_cache_helper( - &mut self.cache_at, - name, - &self.db, - self.key_lookup_column, - self.header_column, - self.cache_column, - &self.best_finalized_block, - ) - } -} - -// This helper is needed because otherwise the borrow checker will require to -// clone all parameters outside of the closure. -fn get_cache_helper<'a, Block: BlockT>( - cache_at: &'a mut HashMap, self::list_storage::DbStorage>>, - name: CacheKeyId, - db: &Arc>, - key_lookup: u32, - header: u32, - cache: u32, - best_finalized_block: &ComplexBlockId, -) -> ClientResult<&'a mut ListCache, self::list_storage::DbStorage>> { - match cache_at.entry(name) { - Entry::Occupied(entry) => Ok(entry.into_mut()), - Entry::Vacant(entry) => { - let cache = ListCache::new( - self::list_storage::DbStorage::new( - name.to_vec(), - db.clone(), - self::list_storage::DbColumns { meta: COLUMN_META, key_lookup, header, cache }, - ), - cache_pruning_strategy(name), - best_finalized_block.clone(), - )?; - Ok(entry.insert(cache)) - }, - } -} - -/// Cache operations that are to be committed after database transaction is committed. -#[derive(Default)] -pub struct DbCacheTransactionOps { - cache_at_ops: HashMap>>, - best_finalized_block: Option>, -} - -impl DbCacheTransactionOps { - /// Empty transaction ops. - pub fn empty() -> DbCacheTransactionOps { - DbCacheTransactionOps { cache_at_ops: HashMap::new(), best_finalized_block: None } - } -} - -/// Database-backed blockchain data cache transaction valid for single block import. -pub struct DbCacheTransaction<'a, Block: BlockT> { - cache: &'a mut DbCache, - tx: &'a mut Transaction, - cache_at_ops: HashMap>>, - best_finalized_block: Option>, -} - -impl<'a, Block: BlockT> DbCacheTransaction<'a, Block> { - /// Convert transaction into post-commit operations set. - pub fn into_ops(self) -> DbCacheTransactionOps { - DbCacheTransactionOps { - cache_at_ops: self.cache_at_ops, - best_finalized_block: self.best_finalized_block, - } - } - - /// When new block is inserted into database. - pub fn on_block_insert( - mut self, - parent: ComplexBlockId, - block: ComplexBlockId, - data_at: HashMap>, - entry_type: EntryType, - ) -> ClientResult { - // prepare list of caches that are not update - // (we might still need to do some cache maintenance in this case) - let missed_caches = self - .cache - .cache_at - .keys() - .filter(|cache| !data_at.contains_key(*cache)) - .cloned() - .collect::>(); - - let mut insert_op = |name: CacheKeyId, - value: Option>| - -> Result<(), sp_blockchain::Error> { - let cache = self.cache.get_cache(name)?; - let cache_ops = self.cache_at_ops.entry(name).or_default(); - cache.on_block_insert( - &mut self::list_storage::DbStorageTransaction::new(cache.storage(), &mut self.tx), - parent.clone(), - block.clone(), - value, - entry_type, - cache_ops, - )?; - - Ok(()) - }; - - data_at.into_iter().try_for_each(|(name, data)| insert_op(name, Some(data)))?; - missed_caches.into_iter().try_for_each(|name| insert_op(name, None))?; - - match entry_type { - EntryType::Final | EntryType::Genesis => self.best_finalized_block = Some(block), - EntryType::NonFinal => (), - } - - Ok(self) - } - - /// When previously inserted block is finalized. - pub fn on_block_finalize( - mut self, - parent: ComplexBlockId, - block: ComplexBlockId, - ) -> ClientResult { - for (name, cache) in self.cache.cache_at.iter() { - let cache_ops = self.cache_at_ops.entry(*name).or_default(); - cache.on_block_finalize( - &mut self::list_storage::DbStorageTransaction::new(cache.storage(), &mut self.tx), - parent.clone(), - block.clone(), - cache_ops, - )?; - } - - self.best_finalized_block = Some(block); - - Ok(self) - } - - /// When block is reverted. - pub fn on_block_revert(mut self, reverted_block: &ComplexBlockId) -> ClientResult { - for (name, cache) in self.cache.cache_at.iter() { - let cache_ops = self.cache_at_ops.entry(*name).or_default(); - cache.on_block_revert( - &mut self::list_storage::DbStorageTransaction::new(cache.storage(), &mut self.tx), - reverted_block, - cache_ops, - )?; - } - - Ok(self) - } -} - -/// Synchronous implementation of database-backed blockchain data cache. -pub struct DbCacheSync(pub RwLock>); - -impl BlockchainCache for DbCacheSync { - fn initialize(&self, key: &CacheKeyId, data: Vec) -> ClientResult<()> { - let mut cache = self.0.write(); - let genesis_hash = cache.genesis_hash; - let cache_contents = vec![(*key, data)].into_iter().collect(); - let db = cache.db.clone(); - let mut dbtx = Transaction::new(); - let tx = cache.transaction(&mut dbtx); - let tx = tx.on_block_insert( - ComplexBlockId::new(Default::default(), Zero::zero()), - ComplexBlockId::new(genesis_hash, Zero::zero()), - cache_contents, - EntryType::Genesis, - )?; - let tx_ops = tx.into_ops(); - db.commit(dbtx)?; - cache.commit(tx_ops)?; - - Ok(()) - } - - fn get_at( - &self, - key: &CacheKeyId, - at: &BlockId, - ) -> ClientResult< - Option<((NumberFor, Block::Hash), Option<(NumberFor, Block::Hash)>, Vec)>, - > { - let mut cache = self.0.write(); - let header_metadata_cache = cache.header_metadata_cache.clone(); - let cache = cache.get_cache(*key)?; - let storage = cache.storage(); - let db = storage.db(); - let columns = storage.columns(); - let at = match *at { - BlockId::Hash(hash) => match header_metadata_cache.header_metadata(hash) { - Some(metadata) => ComplexBlockId::new(hash, metadata.number), - None => { - let header = utils::require_header::( - &**db, - columns.key_lookup, - columns.header, - BlockId::Hash(hash.clone()), - )?; - ComplexBlockId::new(hash, *header.number()) - }, - }, - BlockId::Number(number) => { - let hash = utils::require_header::( - &**db, - columns.key_lookup, - columns.header, - BlockId::Number(number.clone()), - )? - .hash(); - ComplexBlockId::new(hash, number) - }, - }; - - cache.value_at_block(&at).map(|block_and_value| { - block_and_value.map(|(begin_block, end_block, value)| { - ( - (begin_block.number, begin_block.hash), - end_block.map(|end_block| (end_block.number, end_block.hash)), - value, - ) - }) - }) - } -} - -/// Get pruning strategy for given cache. -fn cache_pruning_strategy>(cache: CacheKeyId) -> PruningStrategy { - // the cache is mostly used to store data from consensus engines - // this kind of data is only required for non-finalized blocks - // => by default we prune finalized cached entries - - match cache { - // we need to keep changes tries configurations forever (or at least until changes tries, - // that were built using this configuration, are pruned) to make it possible to refer - // to old changes tries - well_known_cache_keys::CHANGES_TRIE_CONFIG => PruningStrategy::NeverPrune, - _ => PruningStrategy::ByDepth(PRUNE_DEPTH.into()), - } -} diff --git a/client/db/src/changes_tries_storage.rs b/client/db/src/changes_tries_storage.rs deleted file mode 100644 index 3a3c5918535f9..0000000000000 --- a/client/db/src/changes_tries_storage.rs +++ /dev/null @@ -1,1168 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! DB-backed changes tries storage. - -use crate::{ - cache::{ - ComplexBlockId, DbCache, DbCacheSync, DbCacheTransactionOps, EntryType as CacheEntryType, - }, - utils::{self, meta_keys, Meta}, - Database, DbHash, -}; -use codec::{Decode, Encode}; -use hash_db::Prefix; -use parking_lot::RwLock; -use sc_client_api::backend::PrunableStateChangesTrieStorage; -use sp_blockchain::{ - well_known_cache_keys, Cache as BlockchainCache, Error as ClientError, HeaderMetadataCache, - Result as ClientResult, -}; -use sp_core::{ - convert_hash, storage::PrefixedStorageKey, ChangesTrieConfiguration, - ChangesTrieConfigurationRange, -}; -use sp_database::Transaction; -use sp_runtime::{ - generic::{BlockId, ChangesTrieSignal, DigestItem}, - traits::{Block as BlockT, CheckedSub, HashFor, Header as HeaderT, NumberFor, One, Zero}, -}; -use sp_state_machine::{ChangesTrieBuildCache, ChangesTrieCacheAction}; -use sp_trie::MemoryDB; -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; - -/// Extract new changes trie configuration (if available) from the header. -pub fn extract_new_configuration( - header: &Header, -) -> Option<&Option> { - header - .digest() - .log(DigestItem::as_changes_trie_signal) - .and_then(ChangesTrieSignal::as_new_configuration) -} - -/// Opaque configuration cache transaction. During its lifetime, no-one should modify cache. This is -/// currently guaranteed because import lock is held during block import/finalization. -pub struct DbChangesTrieStorageTransaction { - /// Cache operations that must be performed after db transaction is committed. - cache_ops: DbCacheTransactionOps, - /// New configuration (if changed at current block). - new_config: Option>, -} - -impl DbChangesTrieStorageTransaction { - /// Consume self and return transaction with given new configuration. - pub fn with_new_config(mut self, new_config: Option>) -> Self { - self.new_config = new_config; - self - } -} - -impl From> for DbChangesTrieStorageTransaction { - fn from(cache_ops: DbCacheTransactionOps) -> Self { - DbChangesTrieStorageTransaction { cache_ops, new_config: None } - } -} - -/// Changes tries storage. -/// -/// Stores all tries in separate DB column. -/// Lock order: meta, tries_meta, cache, build_cache. -pub struct DbChangesTrieStorage { - db: Arc>, - meta_column: u32, - changes_tries_column: u32, - key_lookup_column: u32, - header_column: u32, - meta: Arc, Block::Hash>>>, - tries_meta: RwLock>, - min_blocks_to_keep: Option, - /// The cache stores all ever existing changes tries configurations. - cache: DbCacheSync, - /// Build cache is a map of block => set of storage keys changed at this block. - /// They're used to build digest blocks - instead of reading+parsing tries from db - /// we just use keys sets from the cache. - build_cache: RwLock>>, -} - -/// Persistent struct that contains all the changes tries metadata. -#[derive(Decode, Encode, Debug)] -struct ChangesTriesMeta { - /// Oldest unpruned max-level (or skewed) digest trie blocks range. - /// The range is inclusive from both sides. - /// Is None only if: - /// 1) we haven't yet finalized any blocks (except genesis) - /// 2) if best_finalized_block - min_blocks_to_keep points to the range where changes tries are - /// disabled 3) changes tries pruning is disabled - pub oldest_digest_range: Option<(NumberFor, NumberFor)>, - /// End block (inclusive) of oldest pruned max-level (or skewed) digest trie blocks range. - /// It is guaranteed that we have no any changes tries before (and including) this block. - /// It is guaranteed that all existing changes tries after this block are not yet pruned (if - /// created). - pub oldest_pruned_digest_range_end: NumberFor, -} - -impl DbChangesTrieStorage { - /// Create new changes trie storage. - pub fn new( - db: Arc>, - header_metadata_cache: Arc>, - meta_column: u32, - changes_tries_column: u32, - key_lookup_column: u32, - header_column: u32, - cache_column: u32, - meta: Arc, Block::Hash>>>, - min_blocks_to_keep: Option, - ) -> ClientResult { - let (finalized_hash, finalized_number, genesis_hash) = { - let meta = meta.read(); - (meta.finalized_hash, meta.finalized_number, meta.genesis_hash) - }; - let tries_meta = read_tries_meta(&*db, meta_column)?; - Ok(Self { - db: db.clone(), - meta_column, - changes_tries_column, - key_lookup_column, - header_column, - meta, - min_blocks_to_keep, - cache: DbCacheSync(RwLock::new(DbCache::new( - db.clone(), - header_metadata_cache, - key_lookup_column, - header_column, - cache_column, - genesis_hash, - ComplexBlockId::new(finalized_hash, finalized_number), - ))), - build_cache: RwLock::new(ChangesTrieBuildCache::new()), - tries_meta: RwLock::new(tries_meta), - }) - } - - /// Commit new changes trie. - pub fn commit( - &self, - tx: &mut Transaction, - mut changes_trie: MemoryDB>, - parent_block: ComplexBlockId, - block: ComplexBlockId, - new_header: &Block::Header, - finalized: bool, - new_configuration: Option>, - cache_tx: Option>, - ) -> ClientResult> { - // insert changes trie, associated with block, into DB - for (key, (val, _)) in changes_trie.drain() { - tx.set(self.changes_tries_column, key.as_ref(), &val); - } - - // if configuration has not been changed AND block is not finalized => nothing to do here - let new_configuration = match new_configuration { - Some(new_configuration) => new_configuration, - None if !finalized => return Ok(DbCacheTransactionOps::empty().into()), - None => - return self.finalize( - tx, - parent_block.hash, - block.hash, - block.number, - Some(new_header), - cache_tx, - ), - }; - - // update configuration cache - let mut cache_at = HashMap::new(); - cache_at.insert(well_known_cache_keys::CHANGES_TRIE_CONFIG, new_configuration.encode()); - Ok(DbChangesTrieStorageTransaction::from(match cache_tx { - Some(cache_tx) => self - .cache - .0 - .write() - .transaction_with_ops(tx, cache_tx.cache_ops) - .on_block_insert( - parent_block, - block, - cache_at, - if finalized { CacheEntryType::Final } else { CacheEntryType::NonFinal }, - )? - .into_ops(), - None => self - .cache - .0 - .write() - .transaction(tx) - .on_block_insert( - parent_block, - block, - cache_at, - if finalized { CacheEntryType::Final } else { CacheEntryType::NonFinal }, - )? - .into_ops(), - }) - .with_new_config(Some(new_configuration))) - } - - /// Called when block is finalized. - pub fn finalize( - &self, - tx: &mut Transaction, - parent_block_hash: Block::Hash, - block_hash: Block::Hash, - block_num: NumberFor, - new_header: Option<&Block::Header>, - cache_tx: Option>, - ) -> ClientResult> { - // prune obsolete changes tries - self.prune(tx, block_hash, block_num, new_header.clone(), cache_tx.as_ref())?; - - // if we have inserted the block that we're finalizing in the same transaction - // => then we have already finalized it from the commit() call - if cache_tx.is_some() { - if let Some(new_header) = new_header { - if new_header.hash() == block_hash { - return Ok(cache_tx.expect("guarded by cache_tx.is_some(); qed")) - } - } - } - - // and finalize configuration cache entries - let block = ComplexBlockId::new(block_hash, block_num); - let parent_block_num = block_num.checked_sub(&One::one()).unwrap_or_else(|| Zero::zero()); - let parent_block = ComplexBlockId::new(parent_block_hash, parent_block_num); - Ok(match cache_tx { - Some(cache_tx) => DbChangesTrieStorageTransaction::from( - self.cache - .0 - .write() - .transaction_with_ops(tx, cache_tx.cache_ops) - .on_block_finalize(parent_block, block)? - .into_ops(), - ) - .with_new_config(cache_tx.new_config), - None => DbChangesTrieStorageTransaction::from( - self.cache - .0 - .write() - .transaction(tx) - .on_block_finalize(parent_block, block)? - .into_ops(), - ), - }) - } - - /// When block is reverted. - pub fn revert( - &self, - tx: &mut Transaction, - block: &ComplexBlockId, - ) -> ClientResult> { - Ok(self.cache.0.write().transaction(tx).on_block_revert(block)?.into_ops().into()) - } - - /// When transaction has been committed. - pub fn post_commit(&self, tx: Option>) { - if let Some(tx) = tx { - self.cache.0.write().commit(tx.cache_ops).expect( - "only fails if cache with given name isn't loaded yet; cache is already loaded \ - because there is tx; qed", - ); - } - } - - /// Commit changes into changes trie build cache. - pub fn commit_build_cache( - &self, - cache_update: ChangesTrieCacheAction>, - ) { - self.build_cache.write().perform(cache_update); - } - - /// Prune obsolete changes tries. - fn prune( - &self, - tx: &mut Transaction, - block_hash: Block::Hash, - block_num: NumberFor, - new_header: Option<&Block::Header>, - cache_tx: Option<&DbChangesTrieStorageTransaction>, - ) -> ClientResult<()> { - // never prune on archive nodes - let min_blocks_to_keep = match self.min_blocks_to_keep { - Some(min_blocks_to_keep) => min_blocks_to_keep, - None => return Ok(()), - }; - - let mut tries_meta = self.tries_meta.write(); - let mut next_digest_range_start = block_num; - loop { - // prune oldest digest if it is known - // it could be unknown if: - // 1) either we're finalizing block#1 - // 2) or we are (or were) in period where changes tries are disabled - if let Some((begin, end)) = tries_meta.oldest_digest_range { - if block_num <= end || block_num - end <= min_blocks_to_keep.into() { - break - } - - tries_meta.oldest_pruned_digest_range_end = end; - sp_state_machine::prune_changes_tries( - &*self, - begin, - end, - &sp_state_machine::ChangesTrieAnchorBlockId { - hash: convert_hash(&block_hash), - number: block_num, - }, - |node| tx.remove(self.changes_tries_column, node.as_ref()), - ); - - next_digest_range_start = end + One::one(); - } - - // proceed to the next configuration range - let next_digest_range_start_hash = match block_num == next_digest_range_start { - true => block_hash, - false => utils::require_header::( - &*self.db, - self.key_lookup_column, - self.header_column, - BlockId::Number(next_digest_range_start), - )? - .hash(), - }; - - let config_for_new_block = new_header - .map(|header| *header.number() == next_digest_range_start) - .unwrap_or(false); - let next_config = match cache_tx { - Some(cache_tx) if config_for_new_block && cache_tx.new_config.is_some() => { - let config = cache_tx.new_config.clone().expect("guarded by is_some(); qed"); - Ok(ChangesTrieConfigurationRange { - zero: (block_num, block_hash), - end: None, - config, - }) - }, - _ if config_for_new_block => self.configuration_at(&BlockId::Hash( - *new_header - .expect("config_for_new_block is only true when new_header is passed; qed") - .parent_hash(), - )), - _ => self.configuration_at(&BlockId::Hash(next_digest_range_start_hash)), - }; - let next_config = match next_config { - Ok(next_config) => next_config, - Err(ClientError::UnknownBlock(_)) => break, // No block means nothing to prune. - Err(e) => return Err(e), - }; - if let Some(config) = next_config.config { - let mut oldest_digest_range = config - .next_max_level_digest_range(next_config.zero.0, next_digest_range_start) - .unwrap_or_else(|| (next_digest_range_start, next_digest_range_start)); - - if let Some(end) = next_config.end { - if end.0 < oldest_digest_range.1 { - oldest_digest_range.1 = end.0; - } - } - - tries_meta.oldest_digest_range = Some(oldest_digest_range); - continue - } - - tries_meta.oldest_digest_range = None; - break - } - - write_tries_meta(tx, self.meta_column, &*tries_meta); - Ok(()) - } -} - -impl PrunableStateChangesTrieStorage for DbChangesTrieStorage { - fn storage( - &self, - ) -> &dyn sp_state_machine::ChangesTrieStorage, NumberFor> { - self - } - - fn configuration_at( - &self, - at: &BlockId, - ) -> ClientResult, Block::Hash>> { - self.cache - .get_at(&well_known_cache_keys::CHANGES_TRIE_CONFIG, at)? - .and_then(|(zero, end, encoded)| { - Decode::decode(&mut &encoded[..]) - .ok() - .map(|config| ChangesTrieConfigurationRange { zero, end, config }) - }) - .ok_or_else(|| ClientError::ErrorReadingChangesTriesConfig) - } - - fn oldest_pruned_digest_range_end(&self) -> NumberFor { - self.tries_meta.read().oldest_pruned_digest_range_end - } -} - -impl sp_state_machine::ChangesTrieRootsStorage, NumberFor> - for DbChangesTrieStorage -{ - fn build_anchor( - &self, - hash: Block::Hash, - ) -> Result>, String> { - utils::read_header::( - &*self.db, - self.key_lookup_column, - self.header_column, - BlockId::Hash(hash), - ) - .map_err(|e| e.to_string()) - .and_then(|maybe_header| { - maybe_header - .map(|header| sp_state_machine::ChangesTrieAnchorBlockId { - hash, - number: *header.number(), - }) - .ok_or_else(|| format!("Unknown header: {}", hash)) - }) - } - - fn root( - &self, - anchor: &sp_state_machine::ChangesTrieAnchorBlockId>, - block: NumberFor, - ) -> Result, String> { - // check API requirement: we can't get NEXT block(s) based on anchor - if block > anchor.number { - return Err(format!( - "Can't get changes trie root at {} using anchor at {}", - block, anchor.number - )) - } - - // we need to get hash of the block to resolve changes trie root - let block_id = if block <= self.meta.read().finalized_number { - // if block is finalized, we could just read canonical hash - BlockId::Number(block) - } else { - // the block is not finalized - let mut current_num = anchor.number; - let mut current_hash: Block::Hash = convert_hash(&anchor.hash); - let maybe_anchor_header: Block::Header = utils::require_header::( - &*self.db, - self.key_lookup_column, - self.header_column, - BlockId::Number(current_num), - ) - .map_err(|e| e.to_string())?; - if maybe_anchor_header.hash() == current_hash { - // if anchor is canonicalized, then the block is also canonicalized - BlockId::Number(block) - } else { - // else (block is not finalized + anchor is not canonicalized): - // => we should find the required block hash by traversing - // back from the anchor to the block with given number - while current_num != block { - let current_header: Block::Header = utils::require_header::( - &*self.db, - self.key_lookup_column, - self.header_column, - BlockId::Hash(current_hash), - ) - .map_err(|e| e.to_string())?; - - current_hash = *current_header.parent_hash(); - current_num = current_num - One::one(); - } - - BlockId::Hash(current_hash) - } - }; - - Ok(utils::require_header::( - &*self.db, - self.key_lookup_column, - self.header_column, - block_id, - ) - .map_err(|e| e.to_string())? - .digest() - .log(DigestItem::as_changes_trie_root) - .cloned()) - } -} - -impl sp_state_machine::ChangesTrieStorage, NumberFor> - for DbChangesTrieStorage -where - Block: BlockT, -{ - fn as_roots_storage( - &self, - ) -> &dyn sp_state_machine::ChangesTrieRootsStorage, NumberFor> { - self - } - - fn with_cached_changed_keys( - &self, - root: &Block::Hash, - functor: &mut dyn FnMut(&HashMap, HashSet>>), - ) -> bool { - self.build_cache.read().with_changed_keys(root, functor) - } - - fn get(&self, key: &Block::Hash, _prefix: Prefix) -> Result>, String> { - Ok(self.db.get(self.changes_tries_column, key.as_ref())) - } -} - -/// Read changes tries metadata from database. -fn read_tries_meta( - db: &dyn Database, - meta_column: u32, -) -> ClientResult> { - match db.get(meta_column, meta_keys::CHANGES_TRIES_META) { - Some(h) => Decode::decode(&mut &h[..]).map_err(|err| { - ClientError::Backend(format!("Error decoding changes tries metadata: {}", err)) - }), - None => Ok(ChangesTriesMeta { - oldest_digest_range: None, - oldest_pruned_digest_range_end: Zero::zero(), - }), - } -} - -/// Write changes tries metadata from database. -fn write_tries_meta( - tx: &mut Transaction, - meta_column: u32, - meta: &ChangesTriesMeta, -) { - tx.set_from_vec(meta_column, meta_keys::CHANGES_TRIES_META, meta.encode()); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - tests::{insert_header, prepare_changes, Block}, - Backend, - }; - use hash_db::EMPTY_PREFIX; - use sc_client_api::backend::{ - Backend as ClientBackend, BlockImportOperation, NewBlockState, - PrunableStateChangesTrieStorage, - }; - use sp_blockchain::HeaderBackend as BlockchainHeaderBackend; - use sp_core::H256; - use sp_runtime::{ - testing::{Digest, Header}, - traits::{BlakeTwo256, Hash}, - }; - use sp_state_machine::{ChangesTrieRootsStorage, ChangesTrieStorage}; - - fn changes(number: u64) -> Option, Vec)>> { - Some(vec![(number.to_le_bytes().to_vec(), number.to_le_bytes().to_vec())]) - } - - fn insert_header_with_configuration_change( - backend: &Backend, - number: u64, - parent_hash: H256, - changes: Option, Vec)>>, - new_configuration: Option, - ) -> H256 { - let mut digest = Digest::default(); - let mut changes_trie_update = Default::default(); - if let Some(changes) = changes { - let (root, update) = prepare_changes(changes); - digest.push(DigestItem::ChangesTrieRoot(root)); - changes_trie_update = update; - } - digest.push(DigestItem::ChangesTrieSignal(ChangesTrieSignal::NewConfiguration( - new_configuration, - ))); - - let header = Header { - number, - parent_hash, - state_root: BlakeTwo256::trie_root(Vec::new()), - digest, - extrinsics_root: Default::default(), - }; - let header_hash = header.hash(); - - let block_id = if number == 0 { - BlockId::Hash(Default::default()) - } else { - BlockId::Number(number - 1) - }; - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, block_id).unwrap(); - op.set_block_data(header, None, None, None, NewBlockState::Best).unwrap(); - op.update_changes_trie((changes_trie_update, ChangesTrieCacheAction::Clear)) - .unwrap(); - backend.commit_operation(op).unwrap(); - - header_hash - } - - #[test] - fn changes_trie_storage_works() { - let backend = Backend::::new_test(1000, 100); - backend.changes_tries_storage.meta.write().finalized_number = 1000; - - let check_changes = |backend: &Backend, - block: u64, - changes: Vec<(Vec, Vec)>| { - let (changes_root, mut changes_trie_update) = prepare_changes(changes); - let anchor = sp_state_machine::ChangesTrieAnchorBlockId { - hash: backend.blockchain().header(BlockId::Number(block)).unwrap().unwrap().hash(), - number: block, - }; - assert_eq!(backend.changes_tries_storage.root(&anchor, block), Ok(Some(changes_root))); - - let storage = backend.changes_tries_storage.storage(); - for (key, (val, _)) in changes_trie_update.drain() { - assert_eq!(storage.get(&key, EMPTY_PREFIX), Ok(Some(val))); - } - }; - - let changes0 = vec![(b"key_at_0".to_vec(), b"val_at_0".to_vec())]; - let changes1 = vec![ - (b"key_at_1".to_vec(), b"val_at_1".to_vec()), - (b"another_key_at_1".to_vec(), b"another_val_at_1".to_vec()), - ]; - let changes2 = vec![(b"key_at_2".to_vec(), b"val_at_2".to_vec())]; - - let block0 = insert_header( - &backend, - 0, - Default::default(), - Some(changes0.clone()), - Default::default(), - ); - let block1 = insert_header(&backend, 1, block0, Some(changes1.clone()), Default::default()); - let _ = insert_header(&backend, 2, block1, Some(changes2.clone()), Default::default()); - - // check that the storage contains tries for all blocks - check_changes(&backend, 0, changes0); - check_changes(&backend, 1, changes1); - check_changes(&backend, 2, changes2); - } - - #[test] - fn changes_trie_storage_works_with_forks() { - let backend = Backend::::new_test(1000, 100); - - let changes0 = vec![(b"k0".to_vec(), b"v0".to_vec())]; - let changes1 = vec![(b"k1".to_vec(), b"v1".to_vec())]; - let changes2 = vec![(b"k2".to_vec(), b"v2".to_vec())]; - let block0 = insert_header( - &backend, - 0, - Default::default(), - Some(changes0.clone()), - Default::default(), - ); - let block1 = insert_header(&backend, 1, block0, Some(changes1.clone()), Default::default()); - let block2 = insert_header(&backend, 2, block1, Some(changes2.clone()), Default::default()); - - let changes2_1_0 = vec![(b"k3".to_vec(), b"v3".to_vec())]; - let changes2_1_1 = vec![(b"k4".to_vec(), b"v4".to_vec())]; - let block2_1_0 = - insert_header(&backend, 3, block2, Some(changes2_1_0.clone()), Default::default()); - let block2_1_1 = - insert_header(&backend, 4, block2_1_0, Some(changes2_1_1.clone()), Default::default()); - - let changes2_2_0 = vec![(b"k5".to_vec(), b"v5".to_vec())]; - let changes2_2_1 = vec![(b"k6".to_vec(), b"v6".to_vec())]; - let block2_2_0 = - insert_header(&backend, 3, block2, Some(changes2_2_0.clone()), Default::default()); - let block2_2_1 = - insert_header(&backend, 4, block2_2_0, Some(changes2_2_1.clone()), Default::default()); - - // finalize block1 - backend.changes_tries_storage.meta.write().finalized_number = 1; - - // branch1: when asking for finalized block hash - let (changes1_root, _) = prepare_changes(changes1); - let anchor = sp_state_machine::ChangesTrieAnchorBlockId { hash: block2_1_1, number: 4 }; - assert_eq!(backend.changes_tries_storage.root(&anchor, 1), Ok(Some(changes1_root))); - - // branch2: when asking for finalized block hash - let anchor = sp_state_machine::ChangesTrieAnchorBlockId { hash: block2_2_1, number: 4 }; - assert_eq!(backend.changes_tries_storage.root(&anchor, 1), Ok(Some(changes1_root))); - - // branch1: when asking for non-finalized block hash (search by traversal) - let (changes2_1_0_root, _) = prepare_changes(changes2_1_0); - let anchor = sp_state_machine::ChangesTrieAnchorBlockId { hash: block2_1_1, number: 4 }; - assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_1_0_root))); - - // branch2: when asking for non-finalized block hash (search using canonicalized hint) - let (changes2_2_0_root, _) = prepare_changes(changes2_2_0); - let anchor = sp_state_machine::ChangesTrieAnchorBlockId { hash: block2_2_1, number: 4 }; - assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_2_0_root))); - - // finalize first block of branch2 (block2_2_0) - backend.changes_tries_storage.meta.write().finalized_number = 3; - - // branch2: when asking for finalized block of this branch - assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_2_0_root))); - - // branch1: when asking for finalized block of other branch - // => result is incorrect (returned for the block of branch1), but this is expected, - // because the other fork is abandoned (forked before finalized header) - let anchor = sp_state_machine::ChangesTrieAnchorBlockId { hash: block2_1_1, number: 4 }; - assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_2_0_root))); - } - - #[test] - fn changes_tries_are_pruned_on_finalization() { - let mut backend = Backend::::new_test(1000, 100); - backend.changes_tries_storage.min_blocks_to_keep = Some(8); - - let parent_hash = |number| { - if number == 0 { - Default::default() - } else { - backend - .blockchain() - .header(BlockId::Number(number - 1)) - .unwrap() - .unwrap() - .hash() - } - }; - - let insert_regular_header = |with_changes, number| { - insert_header( - &backend, - number, - parent_hash(number), - if with_changes { changes(number) } else { None }, - Default::default(), - ); - }; - - let is_pruned = |number| { - let trie_root = backend - .blockchain() - .header(BlockId::Number(number)) - .unwrap() - .unwrap() - .digest() - .log(DigestItem::as_changes_trie_root) - .cloned(); - match trie_root { - Some(trie_root) => - backend.changes_tries_storage.get(&trie_root, EMPTY_PREFIX).unwrap().is_none(), - None => true, - } - }; - - let finalize_block = |number| { - let header = backend.blockchain().header(BlockId::Number(number)).unwrap().unwrap(); - let mut tx = Transaction::new(); - let cache_ops = backend - .changes_tries_storage - .finalize(&mut tx, *header.parent_hash(), header.hash(), number, None, None) - .unwrap(); - backend.storage.db.commit(tx).unwrap(); - backend.changes_tries_storage.post_commit(Some(cache_ops)); - }; - - // configuration ranges: - // (0; 6] - None - // [7; 17] - Some(2^2): D2 is built at #10, #14; SD is built at #17 - // [18; 21] - None - // [22; 32] - Some(8^1): D1 is built at #29; SD is built at #32 - // [33; ... - Some(1) - let config_at_6 = Some(ChangesTrieConfiguration::new(2, 2)); - let config_at_17 = None; - let config_at_21 = Some(ChangesTrieConfiguration::new(8, 1)); - let config_at_32 = Some(ChangesTrieConfiguration::new(1, 0)); - - (0..6).for_each(|number| insert_regular_header(false, number)); - insert_header_with_configuration_change(&backend, 6, parent_hash(6), None, config_at_6); - (7..17).for_each(|number| insert_regular_header(true, number)); - insert_header_with_configuration_change( - &backend, - 17, - parent_hash(17), - changes(17), - config_at_17, - ); - (18..21).for_each(|number| insert_regular_header(false, number)); - insert_header_with_configuration_change(&backend, 21, parent_hash(21), None, config_at_21); - (22..32).for_each(|number| insert_regular_header(true, number)); - insert_header_with_configuration_change( - &backend, - 32, - parent_hash(32), - changes(32), - config_at_32, - ); - (33..50).for_each(|number| insert_regular_header(true, number)); - - // when only genesis is finalized, nothing is pruned - (0..=6).for_each(|number| assert!(is_pruned(number))); - (7..=17).for_each(|number| assert!(!is_pruned(number))); - (18..=21).for_each(|number| assert!(is_pruned(number))); - (22..50).for_each(|number| assert!(!is_pruned(number))); - - // when blocks [1; 18] are finalized, nothing is pruned - (1..=18).for_each(|number| finalize_block(number)); - (0..=6).for_each(|number| assert!(is_pruned(number))); - (7..=17).for_each(|number| assert!(!is_pruned(number))); - (18..=21).for_each(|number| assert!(is_pruned(number))); - (22..50).for_each(|number| assert!(!is_pruned(number))); - - // when block 19 is finalized, changes tries for blocks [7; 10] are pruned - finalize_block(19); - (0..=10).for_each(|number| assert!(is_pruned(number))); - (11..=17).for_each(|number| assert!(!is_pruned(number))); - (18..=21).for_each(|number| assert!(is_pruned(number))); - (22..50).for_each(|number| assert!(!is_pruned(number))); - - // when blocks [20; 22] are finalized, nothing is pruned - (20..=22).for_each(|number| finalize_block(number)); - (0..=10).for_each(|number| assert!(is_pruned(number))); - (11..=17).for_each(|number| assert!(!is_pruned(number))); - (18..=21).for_each(|number| assert!(is_pruned(number))); - (22..50).for_each(|number| assert!(!is_pruned(number))); - - // when block 23 is finalized, changes tries for blocks [11; 14] are pruned - finalize_block(23); - (0..=14).for_each(|number| assert!(is_pruned(number))); - (15..=17).for_each(|number| assert!(!is_pruned(number))); - (18..=21).for_each(|number| assert!(is_pruned(number))); - (22..50).for_each(|number| assert!(!is_pruned(number))); - - // when blocks [24; 25] are finalized, nothing is pruned - (24..=25).for_each(|number| finalize_block(number)); - (0..=14).for_each(|number| assert!(is_pruned(number))); - (15..=17).for_each(|number| assert!(!is_pruned(number))); - (18..=21).for_each(|number| assert!(is_pruned(number))); - (22..50).for_each(|number| assert!(!is_pruned(number))); - - // when block 26 is finalized, changes tries for blocks [15; 17] are pruned - finalize_block(26); - (0..=21).for_each(|number| assert!(is_pruned(number))); - (22..50).for_each(|number| assert!(!is_pruned(number))); - - // when blocks [27; 37] are finalized, nothing is pruned - (27..=37).for_each(|number| finalize_block(number)); - (0..=21).for_each(|number| assert!(is_pruned(number))); - (22..50).for_each(|number| assert!(!is_pruned(number))); - - // when block 38 is finalized, changes tries for blocks [22; 29] are pruned - finalize_block(38); - (0..=29).for_each(|number| assert!(is_pruned(number))); - (30..50).for_each(|number| assert!(!is_pruned(number))); - - // when blocks [39; 40] are finalized, nothing is pruned - (39..=40).for_each(|number| finalize_block(number)); - (0..=29).for_each(|number| assert!(is_pruned(number))); - (30..50).for_each(|number| assert!(!is_pruned(number))); - - // when block 41 is finalized, changes tries for blocks [30; 32] are pruned - finalize_block(41); - (0..=32).for_each(|number| assert!(is_pruned(number))); - (33..50).for_each(|number| assert!(!is_pruned(number))); - - // when block 42 is finalized, changes trie for block 33 is pruned - finalize_block(42); - (0..=33).for_each(|number| assert!(is_pruned(number))); - (34..50).for_each(|number| assert!(!is_pruned(number))); - - // when block 43 is finalized, changes trie for block 34 is pruned - finalize_block(43); - (0..=34).for_each(|number| assert!(is_pruned(number))); - (35..50).for_each(|number| assert!(!is_pruned(number))); - } - - #[test] - fn changes_tries_configuration_is_updated_on_block_insert() { - let backend = Backend::::new_test(1000, 100); - - // configurations at blocks - let config_at_1 = Some(ChangesTrieConfiguration { digest_interval: 4, digest_levels: 2 }); - let config_at_3 = Some(ChangesTrieConfiguration { digest_interval: 8, digest_levels: 1 }); - let config_at_5 = None; - let config_at_7 = Some(ChangesTrieConfiguration { digest_interval: 8, digest_levels: 1 }); - - // insert some blocks - let block0 = insert_header(&backend, 0, Default::default(), None, Default::default()); - let block1 = - insert_header_with_configuration_change(&backend, 1, block0, None, config_at_1.clone()); - let block2 = insert_header(&backend, 2, block1, None, Default::default()); - let block3 = - insert_header_with_configuration_change(&backend, 3, block2, None, config_at_3.clone()); - let block4 = insert_header(&backend, 4, block3, None, Default::default()); - let block5 = - insert_header_with_configuration_change(&backend, 5, block4, None, config_at_5.clone()); - let block6 = insert_header(&backend, 6, block5, None, Default::default()); - let block7 = - insert_header_with_configuration_change(&backend, 7, block6, None, config_at_7.clone()); - - // test configuration cache - let storage = &backend.changes_tries_storage; - assert_eq!( - storage.configuration_at(&BlockId::Hash(block1)).unwrap().config, - config_at_1.clone(), - ); - assert_eq!( - storage.configuration_at(&BlockId::Hash(block2)).unwrap().config, - config_at_1.clone(), - ); - assert_eq!( - storage.configuration_at(&BlockId::Hash(block3)).unwrap().config, - config_at_3.clone(), - ); - assert_eq!( - storage.configuration_at(&BlockId::Hash(block4)).unwrap().config, - config_at_3.clone(), - ); - assert_eq!( - storage.configuration_at(&BlockId::Hash(block5)).unwrap().config, - config_at_5.clone(), - ); - assert_eq!( - storage.configuration_at(&BlockId::Hash(block6)).unwrap().config, - config_at_5.clone(), - ); - assert_eq!( - storage.configuration_at(&BlockId::Hash(block7)).unwrap().config, - config_at_7.clone(), - ); - } - - #[test] - fn test_finalize_several_configuration_change_blocks_in_single_operation() { - let mut backend = Backend::::new_test(10, 10); - backend.changes_tries_storage.min_blocks_to_keep = Some(8); - - let configs = - (0..=7).map(|i| Some(ChangesTrieConfiguration::new(2, i))).collect::>(); - - // insert unfinalized headers - let block0 = insert_header_with_configuration_change( - &backend, - 0, - Default::default(), - None, - configs[0].clone(), - ); - let block1 = insert_header_with_configuration_change( - &backend, - 1, - block0, - changes(1), - configs[1].clone(), - ); - let block2 = insert_header_with_configuration_change( - &backend, - 2, - block1, - changes(2), - configs[2].clone(), - ); - - let side_config2_1 = Some(ChangesTrieConfiguration::new(3, 2)); - let side_config2_2 = Some(ChangesTrieConfiguration::new(3, 3)); - let block2_1 = insert_header_with_configuration_change( - &backend, - 2, - block1, - changes(8), - side_config2_1.clone(), - ); - let _ = insert_header_with_configuration_change( - &backend, - 3, - block2_1, - changes(9), - side_config2_2.clone(), - ); - - // insert finalized header => 4 headers are finalized at once - let header3 = Header { - number: 3, - parent_hash: block2, - state_root: Default::default(), - digest: Digest { - logs: vec![DigestItem::ChangesTrieSignal(ChangesTrieSignal::NewConfiguration( - configs[3].clone(), - ))], - }, - extrinsics_root: Default::default(), - }; - let block3 = header3.hash(); - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(block2)).unwrap(); - op.mark_finalized(BlockId::Hash(block1), None).unwrap(); - op.mark_finalized(BlockId::Hash(block2), None).unwrap(); - op.set_block_data(header3, None, None, None, NewBlockState::Final).unwrap(); - backend.commit_operation(op).unwrap(); - - // insert more unfinalized headers - let block4 = insert_header_with_configuration_change( - &backend, - 4, - block3, - changes(4), - configs[4].clone(), - ); - let block5 = insert_header_with_configuration_change( - &backend, - 5, - block4, - changes(5), - configs[5].clone(), - ); - let block6 = insert_header_with_configuration_change( - &backend, - 6, - block5, - changes(6), - configs[6].clone(), - ); - - // insert finalized header => 4 headers are finalized at once - let header7 = Header { - number: 7, - parent_hash: block6, - state_root: Default::default(), - digest: Digest { - logs: vec![DigestItem::ChangesTrieSignal(ChangesTrieSignal::NewConfiguration( - configs[7].clone(), - ))], - }, - extrinsics_root: Default::default(), - }; - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(block6)).unwrap(); - op.mark_finalized(BlockId::Hash(block4), None).unwrap(); - op.mark_finalized(BlockId::Hash(block5), None).unwrap(); - op.mark_finalized(BlockId::Hash(block6), None).unwrap(); - op.set_block_data(header7, None, None, None, NewBlockState::Final).unwrap(); - backend.commit_operation(op).unwrap(); - } - - #[test] - fn changes_tries_configuration_is_reverted() { - let backend = Backend::::new_test(10, 10); - - let config0 = Some(ChangesTrieConfiguration::new(2, 5)); - let block0 = - insert_header_with_configuration_change(&backend, 0, Default::default(), None, config0); - let config1 = Some(ChangesTrieConfiguration::new(2, 6)); - let block1 = - insert_header_with_configuration_change(&backend, 1, block0, changes(0), config1); - let just1 = Some((*b"TEST", vec![42])); - backend.finalize_block(BlockId::Number(1), just1).unwrap(); - let config2 = Some(ChangesTrieConfiguration::new(2, 7)); - let block2 = - insert_header_with_configuration_change(&backend, 2, block1, changes(1), config2); - let config2_1 = Some(ChangesTrieConfiguration::new(2, 8)); - let _ = - insert_header_with_configuration_change(&backend, 3, block2, changes(10), config2_1); - let config2_2 = Some(ChangesTrieConfiguration::new(2, 9)); - let block2_2 = - insert_header_with_configuration_change(&backend, 3, block2, changes(20), config2_2); - let config2_3 = Some(ChangesTrieConfiguration::new(2, 10)); - let _ = - insert_header_with_configuration_change(&backend, 4, block2_2, changes(30), config2_3); - - // before truncate there are 2 unfinalized forks - block2_1+block2_3 - assert_eq!( - backend - .changes_tries_storage - .cache - .0 - .write() - .get_cache(well_known_cache_keys::CHANGES_TRIE_CONFIG) - .unwrap() - .unfinalized() - .iter() - .map(|fork| fork.head().valid_from.number) - .collect::>(), - vec![3, 4], - ); - - // after truncating block2_3 - there are 2 unfinalized forks - block2_1+block2_2 - backend.revert(1, false).unwrap(); - assert_eq!( - backend - .changes_tries_storage - .cache - .0 - .write() - .get_cache(well_known_cache_keys::CHANGES_TRIE_CONFIG) - .unwrap() - .unfinalized() - .iter() - .map(|fork| fork.head().valid_from.number) - .collect::>(), - vec![3, 3], - ); - - // after truncating block2_1 && block2_2 - there are still two unfinalized forks (cache impl - // specifics), the 1st one points to the block #3 because it isn't truncated - backend.revert(1, false).unwrap(); - assert_eq!( - backend - .changes_tries_storage - .cache - .0 - .write() - .get_cache(well_known_cache_keys::CHANGES_TRIE_CONFIG) - .unwrap() - .unfinalized() - .iter() - .map(|fork| fork.head().valid_from.number) - .collect::>(), - vec![3, 2], - ); - - // after truncating block2 - there are no unfinalized forks - backend.revert(1, false).unwrap(); - assert!(backend - .changes_tries_storage - .cache - .0 - .write() - .get_cache(well_known_cache_keys::CHANGES_TRIE_CONFIG) - .unwrap() - .unfinalized() - .iter() - .map(|fork| fork.head().valid_from.number) - .collect::>() - .is_empty(),); - } -} diff --git a/client/db/src/children.rs b/client/db/src/children.rs index c11e4204997d1..538e51851f081 100644 --- a/client/db/src/children.rs +++ b/client/db/src/children.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 66adb64c0109e..426876f5cba8c 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -28,20 +28,15 @@ #![warn(missing_docs)] -pub mod light; pub mod offchain; -#[cfg(any(feature = "with-kvdb-rocksdb", test))] pub mod bench; -mod cache; -mod changes_tries_storage; mod children; -#[cfg(feature = "with-parity-db")] mod parity_db; +mod record_stats_state; mod stats; -mod storage_cache; -#[cfg(any(feature = "with-kvdb-rocksdb", test))] +#[cfg(any(feature = "rocksdb", test))] mod upgrade; mod utils; @@ -56,21 +51,19 @@ use std::{ }; use crate::{ - changes_tries_storage::{DbChangesTrieStorage, DbChangesTrieStorageTransaction}, + record_stats_state::RecordStatsState, stats::StateUsageStats, - storage_cache::{new_shared_cache, CachingState, SharedCache, SyncingCachingState}, utils::{meta_keys, read_db, read_meta, DatabaseType, Meta}, }; use codec::{Decode, Encode}; use hash_db::Prefix; use sc_client_api::{ - backend::{NewBlockState, ProvideChtRoots, PrunableStateChangesTrieStorage}, - cht, - leaves::{FinalizationDisplaced, LeafSet}, + backend::NewBlockState, + leaves::{FinalizationOutcome, LeafSet}, utils::is_descendent_of, IoInfo, MemoryInfo, MemorySize, UsageInfo, }; -use sc_state_db::StateDb; +use sc_state_db::{IsPruned, StateDb}; use sp_arithmetic::traits::Saturating; use sp_blockchain::{ well_known_cache_keys, Backend as _, CachedHeaderMetadata, Error as ClientError, HeaderBackend, @@ -79,52 +72,59 @@ use sp_blockchain::{ use sp_core::{ offchain::OffchainOverlayedChange, storage::{well_known_keys, ChildInfo}, - ChangesTrieConfiguration, }; use sp_database::Transaction; use sp_runtime::{ - generic::{BlockId, DigestItem}, + generic::BlockId, traits::{ Block as BlockT, Hash, HashFor, Header as HeaderT, NumberFor, One, SaturatedConversion, Zero, }, - Justification, Justifications, Storage, + Justification, Justifications, StateVersion, Storage, }; use sp_state_machine::{ - backend::Backend as StateBackend, ChangesTrieCacheAction, ChangesTrieTransaction, + backend::{AsTrieBackend, Backend as StateBackend}, ChildStorageCollection, DBValue, IndexOperation, OffchainChangesCollection, StateMachineStats, StorageCollection, UsageInfo as StateUsageInfo, }; -use sp_trie::{prefixed_key, MemoryDB, PrefixedMemoryDB}; +use sp_trie::{cache::SharedTrieCache, prefixed_key, MemoryDB, PrefixedMemoryDB}; // Re-export the Database trait so that one can pass an implementation of it. pub use sc_state_db::PruningMode; pub use sp_database::Database; -#[cfg(any(feature = "with-kvdb-rocksdb", test))] pub use bench::BenchmarkingState; -const MIN_BLOCKS_TO_KEEP_CHANGES_TRIES_FOR: u32 = 32768; const CACHE_HEADERS: usize = 8; -/// Default value for storage cache child ratio. -const DEFAULT_CHILD_RATIO: (usize, usize) = (1, 10); - /// DB-backed patricia trie state, transaction type is an overlay of changes to commit. pub type DbState = sp_state_machine::TrieBackend>>, HashFor>; +/// Builder for [`DbState`]. +pub type DbStateBuilder = sp_state_machine::TrieBackendBuilder< + Arc>>, + HashFor, +>; + +/// Length of a [`DbHash`]. const DB_HASH_LEN: usize = 32; + /// Hash type that this backend uses for the database. pub type DbHash = sp_core::H256; -/// This is used as block body when storage-chain mode is enabled. +/// An extrinsic entry in the database. #[derive(Debug, Encode, Decode)] -struct ExtrinsicHeader { - /// Hash of the indexed part - indexed_hash: DbHash, // Zero hash if there's no indexed data - /// The rest of the data. - data: Vec, +enum DbExtrinsic { + /// Extrinsic that contains indexed data. + Indexed { + /// Hash of the indexed part. + hash: DbHash, + /// Extrinsic header. + header: Vec, + }, + /// Complete extrinsic data. + Full(B::Extrinsic), } /// A reference tracking state. @@ -178,6 +178,14 @@ impl StateBackend> for RefTrackingState { self.state.child_storage(child_info, key) } + fn child_storage_hash( + &self, + child_info: &ChildInfo, + key: &[u8], + ) -> Result, Self::Error> { + self.state.child_storage_hash(child_info, key) + } + fn exists_storage(&self, key: &[u8]) -> Result { self.state.exists_storage(key) } @@ -226,9 +234,10 @@ impl StateBackend> for RefTrackingState { &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { - self.state.apply_to_keys_while(child_info, prefix, f) + self.state.apply_to_keys_while(child_info, prefix, start_at, f) } fn for_child_keys_with_prefix( @@ -243,22 +252,24 @@ impl StateBackend> for RefTrackingState { fn storage_root<'a>( &self, delta: impl Iterator)>, + state_version: StateVersion, ) -> (B::Hash, Self::Transaction) where B::Hash: Ord, { - self.state.storage_root(delta) + self.state.storage_root(delta, state_version) } fn child_storage_root<'a>( &self, child_info: &ChildInfo, delta: impl Iterator)>, + state_version: StateVersion, ) -> (B::Hash, bool, Self::Transaction) where B::Hash: Ord, { - self.state.child_storage_root(child_info, delta) + self.state.child_storage_root(child_info, delta, state_version) } fn pairs(&self) -> Vec<(Vec, Vec)> { @@ -273,12 +284,6 @@ impl StateBackend> for RefTrackingState { self.state.child_keys(child_info, prefix) } - fn as_trie_backend( - &self, - ) -> Option<&sp_state_machine::TrieBackend>> { - self.state.as_trie_backend() - } - fn register_overlay_stats(&self, stats: &StateMachineStats) { self.state.register_overlay_stats(stats); } @@ -288,41 +293,43 @@ impl StateBackend> for RefTrackingState { } } +impl AsTrieBackend> for RefTrackingState { + type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; + + fn as_trie_backend( + &self, + ) -> &sp_state_machine::TrieBackend> { + &self.state.as_trie_backend() + } +} + /// Database settings. pub struct DatabaseSettings { - /// State cache size. - pub state_cache_size: usize, - /// Ratio of cache size dedicated to child tries. - pub state_cache_child_ratio: Option<(usize, usize)>, - /// State pruning mode. - pub state_pruning: PruningMode, + /// The maximum trie cache size in bytes. + /// + /// If `None` is given, the cache is disabled. + pub trie_cache_maximum_size: Option, + /// Requested state pruning mode. + pub state_pruning: Option, /// Where to find the database. pub source: DatabaseSource, /// Block pruning mode. - pub keep_blocks: KeepBlocks, - /// Block body/Transaction storage scheme. - pub transaction_storage: TransactionStorageMode, + /// + /// NOTE: only finalized blocks are subject for removal! + pub blocks_pruning: BlocksPruning, } /// Block pruning settings. -#[derive(Debug, Clone, Copy)] -pub enum KeepBlocks { - /// Keep full block history. - All, +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BlocksPruning { + /// Keep full block history, of every block that was ever imported. + KeepAll, + /// Keep full finalized block history. + KeepFinalized, /// Keep N recent finalized blocks. Some(u32), } -/// Block body storage scheme. -#[derive(Debug, Clone, Copy)] -pub enum TransactionStorageMode { - /// Store block body as an encoded list of full transactions in the BODY column - BlockBody, - /// Store a list of hashes in the BODY column and each transaction individually - /// in the TRANSACTION column. - StorageChain, -} - /// Where to find the database.. #[derive(Debug, Clone)] pub enum DatabaseSource { @@ -337,6 +344,7 @@ pub enum DatabaseSource { cache_size: usize, }, /// Load a RocksDB database from a given path. Recommended for most uses. + #[cfg(feature = "rocksdb")] RocksDb { /// Path to the database. path: PathBuf, @@ -351,7 +359,13 @@ pub enum DatabaseSource { }, /// Use a custom already-open database. - Custom(Arc>), + Custom { + /// the handle to the custom storage + db: Arc>, + + /// if set, the `create` flag will be required to open such datasource + require_create_flag: bool, + }, } impl DatabaseSource { @@ -362,9 +376,11 @@ impl DatabaseSource { // // IIUC this is needed for polkadot to create its own dbs, so until it can use parity db // I would think rocksdb, but later parity-db. - DatabaseSource::Auto { paritydb_path, .. } => Some(&paritydb_path), - DatabaseSource::RocksDb { path, .. } | DatabaseSource::ParityDb { path } => Some(&path), - DatabaseSource::Custom(..) => None, + DatabaseSource::Auto { paritydb_path, .. } => Some(paritydb_path), + #[cfg(feature = "rocksdb")] + DatabaseSource::RocksDb { path, .. } => Some(path), + DatabaseSource::ParityDb { path } => Some(path), + DatabaseSource::Custom { .. } => None, } } @@ -375,12 +391,16 @@ impl DatabaseSource { *paritydb_path = p.into(); true }, - DatabaseSource::RocksDb { ref mut path, .. } | + #[cfg(feature = "rocksdb")] + DatabaseSource::RocksDb { ref mut path, .. } => { + *path = p.into(); + true + }, DatabaseSource::ParityDb { ref mut path } => { *path = p.into(); true }, - DatabaseSource::Custom(..) => false, + DatabaseSource::Custom { .. } => false, } } } @@ -389,9 +409,10 @@ impl std::fmt::Display for DatabaseSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = match self { DatabaseSource::Auto { .. } => "Auto", + #[cfg(feature = "rocksdb")] DatabaseSource::RocksDb { .. } => "RocksDb", DatabaseSource::ParityDb { .. } => "ParityDb", - DatabaseSource::Custom(_) => "Custom", + DatabaseSource::Custom { .. } => "Custom", }; write!(f, "{}", name) } @@ -406,13 +427,12 @@ pub(crate) mod columns { pub const HEADER: u32 = 4; pub const BODY: u32 = 5; pub const JUSTIFICATIONS: u32 = 6; - pub const CHANGES_TRIE: u32 = 7; pub const AUX: u32 = 8; /// Offchain workers local storage pub const OFFCHAIN: u32 = 9; - pub const CACHE: u32 = 10; /// Transactions pub const TRANSACTION: u32 = 11; + pub const BODY_INDEX: u32 = 12; } struct PendingBlock { @@ -424,10 +444,11 @@ struct PendingBlock { } // wrapper that implements trait required for state_db -struct StateMetaDb<'a>(&'a dyn Database); +#[derive(Clone)] +struct StateMetaDb(Arc>); -impl<'a> sc_state_db::MetaDb for StateMetaDb<'a> { - type Error = io::Error; +impl sc_state_db::MetaDb for StateMetaDb { + type Error = sp_database::error::DatabaseError; fn get_meta(&self, key: &[u8]) -> Result>, Self::Error> { Ok(self.0.get(columns::STATE_META, key)) @@ -460,14 +481,10 @@ pub struct BlockchainDb { leaves: RwLock>>, header_metadata_cache: Arc>, header_cache: Mutex>>, - transaction_storage: TransactionStorageMode, } impl BlockchainDb { - fn new( - db: Arc>, - transaction_storage: TransactionStorageMode, - ) -> ClientResult { + fn new(db: Arc>) -> ClientResult { let meta = read_meta::(&*db, columns::HEADER)?; let leaves = LeafSet::read_from_db(&*db, columns::META, meta_keys::LEAF_PREFIX)?; Ok(BlockchainDb { @@ -476,7 +493,6 @@ impl BlockchainDb { meta: Arc::new(RwLock::new(meta)), header_metadata_cache: Arc::new(HeaderMetadataCache::default()), header_cache: Default::default(), - transaction_storage, }) } @@ -485,7 +501,6 @@ impl BlockchainDb { let mut meta = self.meta.write(); if number.is_zero() { meta.genesis_hash = hash; - meta.finalized_hash = hash; } if is_best { @@ -495,18 +510,16 @@ impl BlockchainDb { if is_finalized { if with_state { - meta.finalized_state = Some((hash.clone(), number)); + meta.finalized_state = Some((hash, number)); } meta.finalized_number = number; meta.finalized_hash = hash; } } - // Get block changes trie root, if available. - fn changes_trie_root(&self, block: BlockId) -> ClientResult> { - self.header(block).map(|header| { - header.and_then(|header| header.digest().log(DigestItem::as_changes_trie_root).cloned()) - }) + fn update_block_gap(&self, gap: Option<(NumberFor, NumberFor)>) { + let mut meta = self.meta.write(); + meta.block_gap = gap; } } @@ -520,7 +533,7 @@ impl sc_client_api::blockchain::HeaderBackend for Blockcha } let header = utils::read_header(&*self.db, columns::KEY_LOOKUP, columns::HEADER, id)?; - cache_header(&mut cache, h.clone(), header.clone()); + cache_header(&mut cache, *h, header.clone()); Ok(header) }, BlockId::Number(_) => @@ -536,8 +549,9 @@ impl sc_client_api::blockchain::HeaderBackend for Blockcha genesis_hash: meta.genesis_hash, finalized_hash: meta.finalized_hash, finalized_number: meta.finalized_number, - finalized_state: meta.finalized_state.clone(), + finalized_state: meta.finalized_state, number_leaves: self.leaves.read().count(), + block_gap: meta.block_gap, } } @@ -558,72 +572,83 @@ impl sc_client_api::blockchain::HeaderBackend for Blockcha fn hash(&self, number: NumberFor) -> ClientResult> { self.header(BlockId::Number(number)) - .and_then(|maybe_header| match maybe_header { - Some(header) => Ok(Some(header.hash().clone())), - None => Ok(None), - }) + .map(|maybe_header| maybe_header.map(|header| header.hash())) } } impl sc_client_api::blockchain::Backend for BlockchainDb { - fn body(&self, id: BlockId) -> ClientResult>> { - let body = match read_db(&*self.db, columns::KEY_LOOKUP, columns::BODY, id)? { - Some(body) => body, - None => return Ok(None), - }; - match self.transaction_storage { - TransactionStorageMode::BlockBody => match Decode::decode(&mut &body[..]) { - Ok(body) => Ok(Some(body)), + fn body(&self, hash: Block::Hash) -> ClientResult>> { + if let Some(body) = + read_db(&*self.db, columns::KEY_LOOKUP, columns::BODY, BlockId::Hash::(hash))? + { + // Plain body + match Decode::decode(&mut &body[..]) { + Ok(body) => return Ok(Some(body)), Err(err) => return Err(sp_blockchain::Error::Backend(format!( "Error decoding body: {}", err ))), - }, - TransactionStorageMode::StorageChain => { - match Vec::::decode(&mut &body[..]) { - Ok(index) => { - let extrinsics: ClientResult> = index - .into_iter() - .map(|ExtrinsicHeader { indexed_hash, data }| { - let decode_result = if indexed_hash != Default::default() { - match self.db.get(columns::TRANSACTION, indexed_hash.as_ref()) { - Some(t) => { - let mut input = - utils::join_input(data.as_ref(), t.as_ref()); - Block::Extrinsic::decode(&mut input) - }, - None => - return Err(sp_blockchain::Error::Backend(format!( - "Missing indexed transaction {:?}", - indexed_hash - ))), - } - } else { - Block::Extrinsic::decode(&mut data.as_ref()) + } + } + + if let Some(index) = read_db( + &*self.db, + columns::KEY_LOOKUP, + columns::BODY_INDEX, + BlockId::Hash::(hash), + )? { + match Vec::>::decode(&mut &index[..]) { + Ok(index) => { + let mut body = Vec::new(); + for ex in index { + match ex { + DbExtrinsic::Indexed { hash, header } => { + match self.db.get(columns::TRANSACTION, hash.as_ref()) { + Some(t) => { + let mut input = + utils::join_input(header.as_ref(), t.as_ref()); + let ex = Block::Extrinsic::decode(&mut input).map_err( + |err| { + sp_blockchain::Error::Backend(format!( + "Error decoding indexed extrinsic: {}", + err + )) + }, + )?; + body.push(ex); + }, + None => + return Err(sp_blockchain::Error::Backend(format!( + "Missing indexed transaction {:?}", + hash + ))), }; - decode_result.map_err(|err| { - sp_blockchain::Error::Backend(format!( - "Error decoding extrinsic: {}", - err - )) - }) - }) - .collect(); - Ok(Some(extrinsics?)) - }, - Err(err) => - return Err(sp_blockchain::Error::Backend(format!( - "Error decoding body list: {}", - err - ))), - } - }, + }, + DbExtrinsic::Full(ex) => { + body.push(ex); + }, + } + } + return Ok(Some(body)) + }, + Err(err) => + return Err(sp_blockchain::Error::Backend(format!( + "Error decoding body list: {}", + err + ))), + } } + Ok(None) } - fn justifications(&self, id: BlockId) -> ClientResult> { - match read_db(&*self.db, columns::KEY_LOOKUP, columns::JUSTIFICATIONS, id)? { + fn justifications(&self, hash: Block::Hash) -> ClientResult> { + match read_db( + &*self.db, + columns::KEY_LOOKUP, + columns::JUSTIFICATIONS, + BlockId::::Hash(hash), + )? { Some(justifications) => match Decode::decode(&mut &justifications[..]) { Ok(justifications) => Ok(Some(justifications)), Err(err) => @@ -637,71 +662,71 @@ impl sc_client_api::blockchain::Backend for BlockchainDb ClientResult { - Ok(self.meta.read().finalized_hash.clone()) - } - - fn cache(&self) -> Option>> { - None + Ok(self.meta.read().finalized_hash) } fn leaves(&self) -> ClientResult> { Ok(self.leaves.read().hashes()) } + fn displaced_leaves_after_finalizing( + &self, + block_number: NumberFor, + ) -> ClientResult> { + Ok(self + .leaves + .read() + .displaced_by_finalize_height(block_number) + .leaves() + .cloned() + .collect::>()) + } + fn children(&self, parent_hash: Block::Hash) -> ClientResult> { children::read_children(&*self.db, columns::META, meta_keys::CHILDREN_PREFIX, parent_hash) } - fn indexed_transaction(&self, hash: &Block::Hash) -> ClientResult>> { + fn indexed_transaction(&self, hash: Block::Hash) -> ClientResult>> { Ok(self.db.get(columns::TRANSACTION, hash.as_ref())) } - fn has_indexed_transaction(&self, hash: &Block::Hash) -> ClientResult { + fn has_indexed_transaction(&self, hash: Block::Hash) -> ClientResult { Ok(self.db.contains(columns::TRANSACTION, hash.as_ref())) } - fn block_indexed_body(&self, id: BlockId) -> ClientResult>>> { - match self.transaction_storage { - TransactionStorageMode::BlockBody => Ok(None), - TransactionStorageMode::StorageChain => { - let body = match read_db(&*self.db, columns::KEY_LOOKUP, columns::BODY, id)? { - Some(body) => body, - None => return Ok(None), - }; - match Vec::::decode(&mut &body[..]) { - Ok(index) => { - let mut transactions = Vec::new(); - for ExtrinsicHeader { indexed_hash, .. } in index.into_iter() { - if indexed_hash != Default::default() { - match self.db.get(columns::TRANSACTION, indexed_hash.as_ref()) { - Some(t) => transactions.push(t), - None => - return Err(sp_blockchain::Error::Backend(format!( - "Missing indexed transaction {:?}", - indexed_hash - ))), - } - } + fn block_indexed_body(&self, hash: Block::Hash) -> ClientResult>>> { + let body = match read_db( + &*self.db, + columns::KEY_LOOKUP, + columns::BODY_INDEX, + BlockId::::Hash(hash), + )? { + Some(body) => body, + None => return Ok(None), + }; + match Vec::>::decode(&mut &body[..]) { + Ok(index) => { + let mut transactions = Vec::new(); + for ex in index.into_iter() { + if let DbExtrinsic::Indexed { hash, .. } = ex { + match self.db.get(columns::TRANSACTION, hash.as_ref()) { + Some(t) => transactions.push(t), + None => + return Err(sp_blockchain::Error::Backend(format!( + "Missing indexed transaction {:?}", + hash + ))), } - Ok(Some(transactions)) - }, - Err(err) => - return Err(sp_blockchain::Error::Backend(format!( - "Error decoding body list: {}", - err - ))), + } } + Ok(Some(transactions)) }, + Err(err) => + Err(sp_blockchain::Error::Backend(format!("Error decoding body list: {}", err))), } } } -impl sc_client_api::blockchain::ProvideCache for BlockchainDb { - fn cache(&self) -> Option>> { - None - } -} - impl HeaderMetadata for BlockchainDb { type Error = sp_blockchain::Error; @@ -739,76 +764,17 @@ impl HeaderMetadata for BlockchainDb { } } -impl ProvideChtRoots for BlockchainDb { - fn header_cht_root( - &self, - cht_size: NumberFor, - block: NumberFor, - ) -> sp_blockchain::Result> { - let cht_number = match cht::block_to_cht_number(cht_size, block) { - Some(number) => number, - None => return Ok(None), - }; - - let cht_start: NumberFor = cht::start_number(cht::size(), cht_number); - - let mut current_num = cht_start; - let cht_range = ::std::iter::from_fn(|| { - let old_current_num = current_num; - current_num = current_num + One::one(); - Some(old_current_num) - }); - - cht::compute_root::, _>( - cht::size(), - cht_number, - cht_range.map(|num| self.hash(num)), - ) - .map(Some) - } - - fn changes_trie_cht_root( - &self, - cht_size: NumberFor, - block: NumberFor, - ) -> sp_blockchain::Result> { - let cht_number = match cht::block_to_cht_number(cht_size, block) { - Some(number) => number, - None => return Ok(None), - }; - - let cht_start: NumberFor = cht::start_number(cht::size(), cht_number); - - let mut current_num = cht_start; - let cht_range = ::std::iter::from_fn(|| { - let old_current_num = current_num; - current_num = current_num + One::one(); - Some(old_current_num) - }); - - cht::compute_root::, _>( - cht::size(), - cht_number, - cht_range.map(|num| self.changes_trie_root(BlockId::Number(num))), - ) - .map(Some) - } -} - /// Database transaction pub struct BlockImportOperation { - old_state: SyncingCachingState, Block>, + old_state: RecordStatsState, Block>, db_updates: PrefixedMemoryDB>, storage_updates: StorageCollection, child_storage_updates: ChildStorageCollection, offchain_storage_updates: OffchainChangesCollection, - changes_trie_updates: MemoryDB>, - changes_trie_build_cache_update: Option>>, - changes_trie_config_update: Option>, pending_block: Option>, aux_ops: Vec<(Vec, Option>)>, - finalized_blocks: Vec<(BlockId, Option)>, - set_head: Option>, + finalized_blocks: Vec<(Block::Hash, Option)>, + set_head: Option, commit_state: bool, index_ops: Vec, } @@ -840,37 +806,29 @@ impl BlockImportOperation { } } - fn apply_new_state(&mut self, storage: Storage) -> ClientResult { - if storage.top.keys().any(|k| well_known_keys::is_child_storage_key(&k)) { - return Err(sp_blockchain::Error::InvalidState.into()) + fn apply_new_state( + &mut self, + storage: Storage, + state_version: StateVersion, + ) -> ClientResult { + if storage.top.keys().any(|k| well_known_keys::is_child_storage_key(k)) { + return Err(sp_blockchain::Error::InvalidState) } - let child_delta = storage.children_default.iter().map(|(_storage_key, child_content)| { + let child_delta = storage.children_default.values().map(|child_content| { ( &child_content.child_info, child_content.data.iter().map(|(k, v)| (&k[..], Some(&v[..]))), ) }); - let mut changes_trie_config = None; let (root, transaction) = self.old_state.full_storage_root( - storage.top.iter().map(|(k, v)| { - if &k[..] == well_known_keys::CHANGES_TRIE_CONFIG { - changes_trie_config = Some(Decode::decode(&mut &v[..])); - } - (&k[..], Some(&v[..])) - }), + storage.top.iter().map(|(k, v)| (&k[..], Some(&v[..]))), child_delta, + state_version, ); - let changes_trie_config = match changes_trie_config { - Some(Ok(c)) => Some(c), - Some(Err(_)) => return Err(sp_blockchain::Error::InvalidState.into()), - None => None, - }; - self.db_updates = transaction; - self.changes_trie_config_update = Some(changes_trie_config); Ok(root) } } @@ -878,7 +836,7 @@ impl BlockImportOperation { impl sc_client_api::backend::BlockImportOperation for BlockImportOperation { - type State = SyncingCachingState, Block>; + type State = RecordStatsState, Block>; fn state(&self) -> ClientResult> { Ok(Some(&self.old_state)) @@ -893,11 +851,6 @@ impl sc_client_api::backend::BlockImportOperation leaf_state: NewBlockState, ) -> ClientResult<()> { assert!(self.pending_block.is_none(), "Only one block per operation is allowed"); - if let Some(changes_trie_config_update) = - changes_tries_storage::extract_new_configuration(&header) - { - self.changes_trie_config_update = Some(changes_trie_config_update.clone()); - } self.pending_block = Some(PendingBlock { header, body, indexed_body, justifications, leaf_state }); Ok(()) @@ -912,27 +865,27 @@ impl sc_client_api::backend::BlockImportOperation Ok(()) } - fn reset_storage(&mut self, storage: Storage) -> ClientResult { - let root = self.apply_new_state(storage)?; + fn reset_storage( + &mut self, + storage: Storage, + state_version: StateVersion, + ) -> ClientResult { + let root = self.apply_new_state(storage, state_version)?; self.commit_state = true; Ok(root) } - fn set_genesis_state(&mut self, storage: Storage, commit: bool) -> ClientResult { - let root = self.apply_new_state(storage)?; + fn set_genesis_state( + &mut self, + storage: Storage, + commit: bool, + state_version: StateVersion, + ) -> ClientResult { + let root = self.apply_new_state(storage, state_version)?; self.commit_state = commit; Ok(root) } - fn update_changes_trie( - &mut self, - update: ChangesTrieTransaction, NumberFor>, - ) -> ClientResult<()> { - self.changes_trie_updates = update.0; - self.changes_trie_build_cache_update = Some(update.1); - Ok(()) - } - fn insert_aux(&mut self, ops: I) -> ClientResult<()> where I: IntoIterator, Option>)>, @@ -961,16 +914,16 @@ impl sc_client_api::backend::BlockImportOperation fn mark_finalized( &mut self, - block: BlockId, + block: Block::Hash, justification: Option, ) -> ClientResult<()> { self.finalized_blocks.push((block, justification)); Ok(()) } - fn mark_head(&mut self, block: BlockId) -> ClientResult<()> { + fn mark_head(&mut self, hash: Block::Hash) -> ClientResult<()> { assert!(self.set_head.is_none(), "Only one set head per operation is allowed"); - self.set_head = Some(block); + self.set_head = Some(hash); Ok(()) } @@ -982,7 +935,7 @@ impl sc_client_api::backend::BlockImportOperation struct StorageDb { pub db: Arc>, - pub state_db: StateDb>, + pub state_db: StateDb, StateMetaDb>, prefix_keys: bool, } @@ -1031,7 +984,8 @@ impl EmptyStorage { pub fn new() -> Self { let mut root = Block::Hash::default(); let mut mdb = MemoryDB::>::default(); - sp_state_machine::TrieDBMut::>::new(&mut mdb, &mut root); + // both triedbmut are the same on empty storage. + sp_trie::trie_types::TrieDBMutBuilderV1::>::new(&mut mdb, &mut root).build(); EmptyStorage(root) } } @@ -1070,13 +1024,14 @@ impl FrozenForDuration { F: FnOnce() -> T, { let mut lock = self.value.lock(); - if lock.at.elapsed() > self.duration || lock.value.is_none() { + let now = std::time::Instant::now(); + if now.saturating_duration_since(lock.at) > self.duration || lock.value.is_none() { let new_value = f(); - lock.at = std::time::Instant::now(); + lock.at = now; lock.value = Some(new_value.clone()); new_value } else { - lock.value.as_ref().expect("checked with lock above").clone() + lock.value.as_ref().expect("Checked with in branch above; qed").clone() } } } @@ -1088,106 +1043,131 @@ impl FrozenForDuration { pub struct Backend { storage: Arc>, offchain_storage: offchain::LocalStorage, - changes_tries_storage: DbChangesTrieStorage, blockchain: BlockchainDb, canonicalization_delay: u64, - shared_cache: SharedCache, import_lock: Arc>, is_archive: bool, - keep_blocks: KeepBlocks, - transaction_storage: TransactionStorageMode, + blocks_pruning: BlocksPruning, io_stats: FrozenForDuration<(kvdb::IoStats, StateUsageInfo)>, state_usage: Arc, genesis_state: RwLock>>>, + shared_trie_cache: Option>>, } impl Backend { /// Create a new instance of database backend. /// /// The pruning window is how old a block must be before the state is pruned. - pub fn new(config: DatabaseSettings, canonicalization_delay: u64) -> ClientResult { - let db = crate::utils::open_database::(&config, DatabaseType::Full)?; - Self::from_database(db as Arc<_>, canonicalization_delay, &config) + pub fn new(db_config: DatabaseSettings, canonicalization_delay: u64) -> ClientResult { + use utils::OpenDbError; + + let db_source = &db_config.source; + + let (needs_init, db) = + match crate::utils::open_database::(db_source, DatabaseType::Full, false) { + Ok(db) => (false, db), + Err(OpenDbError::DoesNotExist) => { + let db = + crate::utils::open_database::(db_source, DatabaseType::Full, true)?; + (true, db) + }, + Err(as_is) => return Err(as_is.into()), + }; + + Self::from_database(db as Arc<_>, canonicalization_delay, &db_config, needs_init) } /// Create new memory-backed client backend for tests. #[cfg(any(test, feature = "test-helpers"))] - pub fn new_test(keep_blocks: u32, canonicalization_delay: u64) -> Self { - Self::new_test_with_tx_storage( - keep_blocks, - canonicalization_delay, - TransactionStorageMode::BlockBody, - ) + pub fn new_test(blocks_pruning: u32, canonicalization_delay: u64) -> Self { + Self::new_test_with_tx_storage(BlocksPruning::Some(blocks_pruning), canonicalization_delay) } /// Create new memory-backed client backend for tests. #[cfg(any(test, feature = "test-helpers"))] pub fn new_test_with_tx_storage( - keep_blocks: u32, + blocks_pruning: BlocksPruning, canonicalization_delay: u64, - transaction_storage: TransactionStorageMode, ) -> Self { let db = kvdb_memorydb::create(crate::utils::NUM_COLUMNS); let db = sp_database::as_database(db); + let state_pruning = match blocks_pruning { + BlocksPruning::KeepAll => PruningMode::ArchiveAll, + BlocksPruning::KeepFinalized => PruningMode::ArchiveCanonical, + BlocksPruning::Some(n) => PruningMode::blocks_pruning(n), + }; let db_setting = DatabaseSettings { - state_cache_size: 16777216, - state_cache_child_ratio: Some((50, 100)), - state_pruning: PruningMode::keep_blocks(keep_blocks), - source: DatabaseSource::Custom(db), - keep_blocks: KeepBlocks::Some(keep_blocks), - transaction_storage, + trie_cache_maximum_size: Some(16 * 1024 * 1024), + state_pruning: Some(state_pruning), + source: DatabaseSource::Custom { db, require_create_flag: true }, + blocks_pruning, }; Self::new(db_setting, canonicalization_delay).expect("failed to create test-db") } + /// Expose the Database that is used by this backend. + /// The second argument is the Column that stores the State. + /// + /// Should only be needed for benchmarking. + #[cfg(any(feature = "runtime-benchmarks"))] + pub fn expose_db(&self) -> (Arc>, sp_database::ColumnId) { + (self.storage.db.clone(), columns::STATE) + } + + /// Expose the Storage that is used by this backend. + /// + /// Should only be needed for benchmarking. + #[cfg(any(feature = "runtime-benchmarks"))] + pub fn expose_storage(&self) -> Arc>> { + self.storage.clone() + } + fn from_database( db: Arc>, canonicalization_delay: u64, config: &DatabaseSettings, + should_init: bool, ) -> ClientResult { - let is_archive_pruning = config.state_pruning.is_archive(); - let blockchain = BlockchainDb::new(db.clone(), config.transaction_storage.clone())?; - let meta = blockchain.meta.clone(); - let map_e = |e: sc_state_db::Error| sp_blockchain::Error::from_state_db(e); - let state_db: StateDb<_, _> = StateDb::new( - config.state_pruning.clone(), + let mut db_init_transaction = Transaction::new(); + + let requested_state_pruning = config.state_pruning.clone(); + let state_meta_db = StateMetaDb(db.clone()); + let map_e = sp_blockchain::Error::from_state_db; + + let (state_db_init_commit_set, state_db) = StateDb::open( + state_meta_db, + requested_state_pruning, !db.supports_ref_counting(), - &StateMetaDb(&*db), + should_init, ) .map_err(map_e)?; + + apply_state_commit(&mut db_init_transaction, state_db_init_commit_set); + + let state_pruning_used = state_db.pruning_mode(); + let is_archive_pruning = state_pruning_used.is_archive(); + let blockchain = BlockchainDb::new(db.clone())?; + let storage_db = StorageDb { db: db.clone(), state_db, prefix_keys: !db.supports_ref_counting() }; + let offchain_storage = offchain::LocalStorage::new(db.clone()); - let changes_tries_storage = DbChangesTrieStorage::new( - db, - blockchain.header_metadata_cache.clone(), - columns::META, - columns::CHANGES_TRIE, - columns::KEY_LOOKUP, - columns::HEADER, - columns::CACHE, - meta, - if is_archive_pruning { None } else { Some(MIN_BLOCKS_TO_KEEP_CHANGES_TRIES_FOR) }, - )?; let backend = Backend { storage: Arc::new(storage_db), offchain_storage, - changes_tries_storage, blockchain, canonicalization_delay, - shared_cache: new_shared_cache( - config.state_cache_size, - config.state_cache_child_ratio.unwrap_or(DEFAULT_CHILD_RATIO), - ), import_lock: Default::default(), is_archive: is_archive_pruning, io_stats: FrozenForDuration::new(std::time::Duration::from_secs(1)), state_usage: Arc::new(StateUsageStats::new()), - keep_blocks: config.keep_blocks.clone(), - transaction_storage: config.transaction_storage.clone(), + blocks_pruning: config.blocks_pruning, genesis_state: RwLock::new(None), + shared_trie_cache: config.trie_cache_maximum_size.map(|maximum_size| { + SharedTrieCache::new(sp_trie::cache::CacheSize::Maximum(maximum_size)) + }), }; // Older DB versions have no last state key. Check if the state is available and set it. @@ -1196,7 +1176,7 @@ impl Backend { info.finalized_hash != Default::default() && sc_client_api::Backend::have_state_at( &backend, - &info.finalized_hash, + info.finalized_hash, info.finalized_number, ) { backend.blockchain.update_meta(MetaUpdate { @@ -1207,6 +1187,9 @@ impl Backend { with_state: true, }); } + + db.commit(db_init_transaction)?; + Ok(backend) } @@ -1234,7 +1217,7 @@ impl Backend { (meta.best_number - best_number).saturated_into::() > self.canonicalization_delay { - return Err(sp_blockchain::Error::SetHeadTooOld.into()) + return Err(sp_blockchain::Error::SetHeadTooOld) } let parent_exists = @@ -1253,16 +1236,16 @@ impl Backend { (&r.number, &r.hash) ); - return Err(::sp_blockchain::Error::NotInFinalizedChain.into()) + return Err(sp_blockchain::Error::NotInFinalizedChain) } - retracted.push(r.hash.clone()); + retracted.push(r.hash); utils::remove_number_to_key_mapping(transaction, columns::KEY_LOOKUP, r.number)?; } // canonicalize: set the number lookup to map to this block's hash. for e in tree_route.enacted() { - enacted.push(e.hash.clone()); + enacted.push(e.hash); utils::insert_number_to_key_mapping( transaction, columns::KEY_LOOKUP, @@ -1298,8 +1281,7 @@ impl Backend { "Last finalized {:?} not parent of {:?}", last_finalized, header.hash() - )) - .into()) + ))) } Ok(()) } @@ -1307,27 +1289,18 @@ impl Backend { fn finalize_block_with_transaction( &self, transaction: &mut Transaction, - hash: &Block::Hash, + hash: Block::Hash, header: &Block::Header, last_finalized: Option, justification: Option, - changes_trie_cache_ops: &mut Option>, - finalization_displaced: &mut Option>>, + finalization_displaced: &mut Option>>, ) -> ClientResult> { // TODO: ensure best chain contains this block. let number = *header.number(); self.ensure_sequential_finalization(header, last_finalized)?; - let with_state = sc_client_api::Backend::have_state_at(self, &hash, number); + let with_state = sc_client_api::Backend::have_state_at(self, hash, number); - self.note_finalized( - transaction, - false, - header, - *hash, - changes_trie_cache_ops, - finalization_displaced, - with_state, - )?; + self.note_finalized(transaction, header, hash, finalization_displaced, with_state)?; if let Some(justification) = justification { transaction.set_from_vec( @@ -1336,7 +1309,7 @@ impl Backend { Justifications::from(justification).encode(), ); } - Ok(MetaUpdate { hash: *hash, number, is_best: false, is_finalized: true, with_state }) + Ok(MetaUpdate { hash, number, is_best: false, is_finalized: true, with_state }) } // performs forced canonicalization with a delay after importing a non-finalized block. @@ -1367,13 +1340,15 @@ impl Backend { )) })? }; - if !sc_client_api::Backend::have_state_at(self, &hash, new_canonical.saturated_into()) { + if !sc_client_api::Backend::have_state_at(self, hash, new_canonical.saturated_into()) { return Ok(()) } trace!(target: "db", "Canonicalize block #{} ({:?})", new_canonical, hash); let commit = self.storage.state_db.canonicalize_block(&hash).map_err( - |e: sc_state_db::Error| sp_blockchain::Error::from_state_db(e), + sp_blockchain::Error::from_state_db::< + sc_state_db::Error, + >, )?; apply_state_commit(transaction, commit); } @@ -1388,68 +1363,63 @@ impl Backend { operation.apply_offchain(&mut transaction); let mut meta_updates = Vec::with_capacity(operation.finalized_blocks.len()); - let mut last_finalized_hash = self.blockchain.meta.read().finalized_hash; - let mut last_finalized_num = self.blockchain.meta.read().finalized_number; - let best_num = self.blockchain.meta.read().best_number; + let (best_num, mut last_finalized_hash, mut last_finalized_num, mut block_gap) = { + let meta = self.blockchain.meta.read(); + (meta.best_number, meta.finalized_hash, meta.finalized_number, meta.block_gap) + }; - let mut changes_trie_cache_ops = None; - for (block, justification) in operation.finalized_blocks { - let block_hash = self.blockchain.expect_block_hash_from_id(&block)?; + for (block_hash, justification) in operation.finalized_blocks { let block_header = self.blockchain.expect_header(BlockId::Hash(block_hash))?; meta_updates.push(self.finalize_block_with_transaction( &mut transaction, - &block_hash, + block_hash, &block_header, Some(last_finalized_hash), justification, - &mut changes_trie_cache_ops, &mut finalization_displaced_leaves, )?); last_finalized_hash = block_hash; - last_finalized_num = block_header.number().clone(); + last_finalized_num = *block_header.number(); } let imported = if let Some(pending_block) = operation.pending_block { let hash = pending_block.header.hash(); let parent_hash = *pending_block.header.parent_hash(); - let number = pending_block.header.number().clone(); + let number = *pending_block.header.number(); + let highest_leaf = self + .blockchain + .leaves + .read() + .highest_leaf() + .map(|(n, _)| n) + .unwrap_or(Zero::zero()); let existing_header = - number <= best_num && self.blockchain.header(BlockId::hash(hash))?.is_some(); + number <= highest_leaf && self.blockchain.header(BlockId::hash(hash))?.is_some(); // blocks are keyed by number + hash. let lookup_key = utils::number_and_hash_to_lookup_key(number, hash)?; - let (enacted, retracted) = if pending_block.leaf_state.is_best() { - self.set_head_with_transaction(&mut transaction, parent_hash, (number, hash))? - } else { - (Default::default(), Default::default()) + if pending_block.leaf_state.is_best() { + self.set_head_with_transaction(&mut transaction, parent_hash, (number, hash))?; }; utils::insert_hash_to_key_mapping(&mut transaction, columns::KEY_LOOKUP, number, hash)?; transaction.set_from_vec(columns::HEADER, &lookup_key, pending_block.header.encode()); if let Some(body) = pending_block.body { - match self.transaction_storage { - TransactionStorageMode::BlockBody => { - transaction.set_from_vec(columns::BODY, &lookup_key, body.encode()); - }, - TransactionStorageMode::StorageChain => { - let body = - apply_index_ops::(&mut transaction, body, operation.index_ops); - transaction.set_from_vec(columns::BODY, &lookup_key, body); - }, + // If we have any index operations we save block in the new format with indexed + // extrinsic headers Otherwise we save the body as a single blob. + if operation.index_ops.is_empty() { + transaction.set_from_vec(columns::BODY, &lookup_key, body.encode()); + } else { + let body = + apply_index_ops::(&mut transaction, body, operation.index_ops); + transaction.set_from_vec(columns::BODY_INDEX, &lookup_key, body); } } if let Some(body) = pending_block.indexed_body { - match self.transaction_storage { - TransactionStorageMode::BlockBody => { - debug!(target: "db", "Commit: ignored indexed block body"); - }, - TransactionStorageMode::StorageChain => { - apply_indexed_body::(&mut transaction, body); - }, - } + apply_indexed_body::(&mut transaction, body); } if let Some(justifications) = pending_block.justifications { transaction.set_from_vec( @@ -1460,18 +1430,8 @@ impl Backend { } if number.is_zero() { - transaction.set_from_vec( - columns::META, - meta_keys::FINALIZED_BLOCK, - lookup_key.clone(), - ); transaction.set(columns::META, meta_keys::GENESIS_HASH, hash.as_ref()); - // for tests, because config is set from within the reset_storage - if operation.changes_trie_config_update.is_none() { - operation.changes_trie_config_update = Some(None); - } - if operation.commit_state { transaction.set_from_vec(columns::META, meta_keys::FINALIZED_STATE, lookup_key); } else { @@ -1479,7 +1439,7 @@ impl Backend { // memory to bootstrap consensus. It is queried for an initial list of // authorities, etc. *self.genesis_state.write() = Some(Arc::new(DbGenesisStorage::new( - pending_block.header.state_root().clone(), + *pending_block.header.state_root(), operation.db_updates.clone(), ))); } @@ -1493,10 +1453,7 @@ impl Backend { let mut removal: u64 = 0; let mut bytes_removal: u64 = 0; for (mut key, (val, rc)) in operation.db_updates.drain() { - if !self.storage.prefix_keys { - // Strip prefix - key.drain(0..key.len() - DB_HASH_LEN); - }; + self.storage.db.sanitize_key(&mut key); if rc > 0 { ops += 1; bytes += key.len() as u64 + val.len() as u64; @@ -1541,15 +1498,17 @@ impl Backend { let commit = self .storage .state_db - .insert_block(&hash, number_u64, &pending_block.header.parent_hash(), changeset) - .map_err(|e: sc_state_db::Error| { + .insert_block(&hash, number_u64, pending_block.header.parent_hash(), changeset) + .map_err(|e: sc_state_db::Error| { sp_blockchain::Error::from_state_db(e) })?; apply_state_commit(&mut transaction, commit); if number <= last_finalized_num { // Canonicalize in the db when re-importing existing blocks with state. let commit = self.storage.state_db.canonicalize_block(&hash).map_err( - |e: sc_state_db::Error| sp_blockchain::Error::from_state_db(e), + sp_blockchain::Error::from_state_db::< + sc_state_db::Error, + >, )?; apply_state_commit(&mut transaction, commit); meta_updates.push(MetaUpdate { @@ -1565,30 +1524,36 @@ impl Backend { let finalized = number_u64 == 0 || pending_block.leaf_state.is_final(); finalized } else { - number.is_zero() || pending_block.leaf_state.is_final() + (number.is_zero() && last_finalized_num.is_zero()) || + pending_block.leaf_state.is_final() }; let header = &pending_block.header; let is_best = pending_block.leaf_state.is_best(); - let changes_trie_updates = operation.changes_trie_updates; - debug!(target: "db", - "DB Commit {:?} ({}), best={}, state={}, existing={}", - hash, number, is_best, operation.commit_state, existing_header, + debug!( + target: "db", + "DB Commit {:?} ({}), best={}, state={}, existing={}, finalized={}", + hash, + number, + is_best, + operation.commit_state, + existing_header, + finalized, ); self.state_usage.merge_sm(operation.old_state.usage_info()); + // release state reference so that it can be finalized - let cache = operation.old_state.into_cache_changes(); + // VERY IMPORTANT + drop(operation.old_state); if finalized { // TODO: ensure best chain contains this block. self.ensure_sequential_finalization(header, Some(last_finalized_hash))?; self.note_finalized( &mut transaction, - true, header, hash, - &mut changes_trie_cache_ops, &mut finalization_displaced_leaves, operation.commit_state, )?; @@ -1598,22 +1563,8 @@ impl Backend { } if !existing_header { - let changes_trie_config_update = operation.changes_trie_config_update; - changes_trie_cache_ops = Some(self.changes_tries_storage.commit( - &mut transaction, - changes_trie_updates, - cache::ComplexBlockId::new( - *header.parent_hash(), - if number.is_zero() { Zero::zero() } else { number - One::one() }, - ), - cache::ComplexBlockId::new(hash, number), - header, - finalized, - changes_trie_config_update, - changes_trie_cache_ops, - )?); - - { + // Add a new leaf if the block has the potential to be finalized. + if number > last_finalized_num || last_finalized_num.is_zero() { let mut leaves = self.blockchain.leaves.write(); leaves.import(hash, number, parent_hash); leaves.prepare_transaction( @@ -1639,6 +1590,41 @@ impl Backend { children, ); } + + if let Some((mut start, end)) = block_gap { + if number == start { + start += One::one(); + utils::insert_number_to_key_mapping( + &mut transaction, + columns::KEY_LOOKUP, + number, + hash, + )?; + } + if start > end { + transaction.remove(columns::META, meta_keys::BLOCK_GAP); + block_gap = None; + debug!(target: "db", "Removed block gap."); + } else { + block_gap = Some((start, end)); + debug!(target: "db", "Update block gap. {:?}", block_gap); + transaction.set( + columns::META, + meta_keys::BLOCK_GAP, + &(start, end).encode(), + ); + } + } else if number > best_num + One::one() && + number > One::one() && self + .blockchain + .header(BlockId::hash(parent_hash))? + .is_none() + { + let gap = (best_num + One::one(), number - One::one()); + transaction.set(columns::META, meta_keys::BLOCK_GAP, &gap.encode()); + block_gap = Some(gap); + debug!(target: "db", "Detected block gap {:?}", block_gap); + } } meta_updates.push(MetaUpdate { @@ -1648,23 +1634,21 @@ impl Backend { is_finalized: finalized, with_state: operation.commit_state, }); - Some((pending_block.header, number, hash, enacted, retracted, is_best, cache)) + Some((pending_block.header, hash)) } else { None }; - let cache_update = if let Some(set_head) = operation.set_head { - if let Some(header) = - sc_client_api::blockchain::HeaderBackend::header(&self.blockchain, set_head)? - { + if let Some(set_head) = operation.set_head { + if let Some(header) = sc_client_api::blockchain::HeaderBackend::header( + &self.blockchain, + BlockId::Hash(set_head), + )? { let number = header.number(); let hash = header.hash(); - let (enacted, retracted) = self.set_head_with_transaction( - &mut transaction, - hash.clone(), - (number.clone(), hash.clone()), - )?; + self.set_head_with_transaction(&mut transaction, hash, (*number, hash))?; + meta_updates.push(MetaUpdate { hash, number: *number, @@ -1672,50 +1656,30 @@ impl Backend { is_finalized: false, with_state: false, }); - Some((enacted, retracted)) } else { return Err(sp_blockchain::Error::UnknownBlock(format!( "Cannot set head {:?}", set_head ))) } - } else { - None - }; + } self.storage.db.commit(transaction)?; // Apply all in-memory state changes. // Code beyond this point can't fail. - if let Some((header, number, hash, enacted, retracted, is_best, mut cache)) = imported { + if let Some((header, hash)) = imported { trace!(target: "db", "DB Commit done {:?}", hash); let header_metadata = CachedHeaderMetadata::from(&header); self.blockchain.insert_header_metadata(header_metadata.hash, header_metadata); cache_header(&mut self.blockchain.header_cache.lock(), hash, Some(header)); - cache.sync_cache( - &enacted, - &retracted, - operation.storage_updates, - operation.child_storage_updates, - Some(hash), - Some(number), - is_best, - ); - } - - if let Some(changes_trie_build_cache_update) = operation.changes_trie_build_cache_update { - self.changes_tries_storage.commit_build_cache(changes_trie_build_cache_update); - } - self.changes_tries_storage.post_commit(changes_trie_cache_ops); - - if let Some((enacted, retracted)) = cache_update { - self.shared_cache.write().sync(&enacted, &retracted); } for m in meta_updates { self.blockchain.update_meta(m); } + self.blockchain.update_block_gap(block_gap); Ok(()) } @@ -1726,22 +1690,20 @@ impl Backend { fn note_finalized( &self, transaction: &mut Transaction, - is_inserted: bool, f_header: &Block::Header, f_hash: Block::Hash, - changes_trie_cache_ops: &mut Option>, - displaced: &mut Option>>, + displaced: &mut Option>>, with_state: bool, ) -> ClientResult<()> { - let f_num = f_header.number().clone(); + let f_num = *f_header.number(); - let lookup_key = utils::number_and_hash_to_lookup_key(f_num, f_hash.clone())?; + let lookup_key = utils::number_and_hash_to_lookup_key(f_num, f_hash)?; if with_state { transaction.set_from_vec(columns::META, meta_keys::FINALIZED_STATE, lookup_key.clone()); } transaction.set_from_vec(columns::META, meta_keys::FINALIZED_BLOCK, lookup_key); - if sc_client_api::Backend::have_state_at(self, &f_hash, f_num) && + if sc_client_api::Backend::have_state_at(self, f_hash, f_num) && self.storage .state_db .best_canonical() @@ -1749,23 +1711,13 @@ impl Backend { .unwrap_or(true) { let commit = self.storage.state_db.canonicalize_block(&f_hash).map_err( - |e: sc_state_db::Error| sp_blockchain::Error::from_state_db(e), + sp_blockchain::Error::from_state_db::< + sc_state_db::Error, + >, )?; apply_state_commit(transaction, commit); } - if !f_num.is_zero() { - let new_changes_trie_cache_ops = self.changes_tries_storage.finalize( - transaction, - *f_header.parent_hash(), - f_hash, - f_num, - if is_inserted { Some(&f_header) } else { None }, - changes_trie_cache_ops.take(), - )?; - *changes_trie_cache_ops = Some(new_changes_trie_cache_ops); - } - let new_displaced = self.blockchain.leaves.write().finalize_height(f_num); self.prune_blocks(transaction, f_num, &new_displaced)?; match displaced { @@ -1780,34 +1732,49 @@ impl Backend { &self, transaction: &mut Transaction, finalized: NumberFor, - displaced: &FinalizationDisplaced>, + displaced: &FinalizationOutcome>, ) -> ClientResult<()> { - if let KeepBlocks::Some(keep_blocks) = self.keep_blocks { - // Always keep the last finalized block - let keep = std::cmp::max(keep_blocks, 1); - if finalized >= keep.into() { - let number = finalized.saturating_sub(keep.into()); - self.prune_block(transaction, BlockId::::number(number))?; - } + match self.blocks_pruning { + BlocksPruning::KeepAll => {}, + BlocksPruning::Some(blocks_pruning) => { + // Always keep the last finalized block + let keep = std::cmp::max(blocks_pruning, 1); + if finalized >= keep.into() { + let number = finalized.saturating_sub(keep.into()); + self.prune_block(transaction, BlockId::::number(number))?; + } + self.prune_displaced_branches(transaction, finalized, displaced)?; + }, + BlocksPruning::KeepFinalized => { + self.prune_displaced_branches(transaction, finalized, displaced)?; + }, + } + Ok(()) + } - // Also discard all blocks from displaced branches - for h in displaced.leaves() { - let mut number = finalized; - let mut hash = h.clone(); - // Follow displaced chains back until we reach a finalized block. - // Since leaves are discarded due to finality, they can't have parents - // that are canonical, but not yet finalized. So we stop deletig as soon as - // we reach canonical chain. - while self.blockchain.hash(number)? != Some(hash.clone()) { - let id = BlockId::::hash(hash.clone()); - match self.blockchain.header(id)? { - Some(header) => { - self.prune_block(transaction, id)?; - number = header.number().saturating_sub(One::one()); - hash = header.parent_hash().clone(); - }, - None => break, - } + fn prune_displaced_branches( + &self, + transaction: &mut Transaction, + finalized: NumberFor, + displaced: &FinalizationOutcome>, + ) -> ClientResult<()> { + // Discard all blocks from displaced branches + for h in displaced.leaves() { + let mut number = finalized; + let mut hash = *h; + // Follow displaced chains back until we reach a finalized block. + // Since leaves are discarded due to finality, they can't have parents + // that are canonical, but not yet finalized. So we stop deleting as soon as + // we reach canonical chain. + while self.blockchain.hash(number)? != Some(hash) { + let id = BlockId::::hash(hash); + match self.blockchain.header(id)? { + Some(header) => { + self.prune_block(transaction, id)?; + number = header.number().saturating_sub(One::one()); + hash = *header.parent_hash(); + }, + None => break, } } } @@ -1819,51 +1786,55 @@ impl Backend { transaction: &mut Transaction, id: BlockId, ) -> ClientResult<()> { - match read_db(&*self.storage.db, columns::KEY_LOOKUP, columns::BODY, id)? { - Some(body) => { - debug!(target: "db", "Removing block #{}", id); - utils::remove_from_db( - transaction, - &*self.storage.db, - columns::KEY_LOOKUP, - columns::BODY, - id, - )?; - match self.transaction_storage { - TransactionStorageMode::BlockBody => {}, - TransactionStorageMode::StorageChain => { - match Vec::::decode(&mut &body[..]) { - Ok(body) => - for ExtrinsicHeader { indexed_hash, .. } in body { - if indexed_hash != Default::default() { - transaction.release(columns::TRANSACTION, indexed_hash); - } - }, - Err(err) => - return Err(sp_blockchain::Error::Backend(format!( - "Error decoding body list: {}", - err - ))), + debug!(target: "db", "Removing block #{}", id); + utils::remove_from_db( + transaction, + &*self.storage.db, + columns::KEY_LOOKUP, + columns::BODY, + id, + )?; + utils::remove_from_db( + transaction, + &*self.storage.db, + columns::KEY_LOOKUP, + columns::JUSTIFICATIONS, + id, + )?; + if let Some(index) = + read_db(&*self.storage.db, columns::KEY_LOOKUP, columns::BODY_INDEX, id)? + { + utils::remove_from_db( + transaction, + &*self.storage.db, + columns::KEY_LOOKUP, + columns::BODY_INDEX, + id, + )?; + match Vec::>::decode(&mut &index[..]) { + Ok(index) => + for ex in index { + if let DbExtrinsic::Indexed { hash, .. } = ex { + transaction.release(columns::TRANSACTION, hash); } }, - } - }, - None => return Ok(()), + Err(err) => + return Err(sp_blockchain::Error::Backend(format!( + "Error decoding body list: {}", + err + ))), + } } Ok(()) } - fn empty_state(&self) -> ClientResult, Block>> { + fn empty_state(&self) -> ClientResult, Block>> { let root = EmptyStorage::::new().0; // Empty trie - let db_state = DbState::::new(self.storage.clone(), root); + let db_state = DbStateBuilder::::new(self.storage.clone(), root) + .with_optional_cache(self.shared_trie_cache.as_ref().map(|c| c.local_cache())) + .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), None); - let caching_state = CachingState::new(state, self.shared_cache.clone(), None); - Ok(SyncingCachingState::new( - caching_state, - self.state_usage.clone(), - self.blockchain.meta.clone(), - self.import_lock.clone(), - )) + Ok(RecordStatsState::new(state, None, self.state_usage.clone())) } } @@ -1890,7 +1861,7 @@ fn apply_index_ops( body: Vec, ops: Vec, ) -> Vec { - let mut extrinsic_headers: Vec = Vec::with_capacity(body.len()); + let mut extrinsic_index: Vec> = Vec::with_capacity(body.len()); let mut index_map = HashMap::new(); let mut renewed_map = HashMap::new(); for op in ops { @@ -1904,37 +1875,44 @@ fn apply_index_ops( } } for (index, extrinsic) in body.into_iter().enumerate() { - let extrinsic = extrinsic.encode(); - let extrinsic_header = if let Some(hash) = renewed_map.get(&(index as u32)) { + let db_extrinsic = if let Some(hash) = renewed_map.get(&(index as u32)) { // Bump ref counter + let extrinsic = extrinsic.encode(); transaction.reference(columns::TRANSACTION, DbHash::from_slice(hash.as_ref())); - ExtrinsicHeader { indexed_hash: hash.clone(), data: extrinsic } + DbExtrinsic::Indexed { hash: *hash, header: extrinsic } } else { match index_map.get(&(index as u32)) { - Some((hash, size)) if *size as usize <= extrinsic.len() => { - let offset = extrinsic.len() - *size as usize; - transaction.store( - columns::TRANSACTION, - DbHash::from_slice(hash.as_ref()), - extrinsic[offset..].to_vec(), - ); - ExtrinsicHeader { - indexed_hash: DbHash::from_slice(hash.as_ref()), - data: extrinsic[..offset].to_vec(), + Some((hash, size)) => { + let encoded = extrinsic.encode(); + if *size as usize <= encoded.len() { + let offset = encoded.len() - *size as usize; + transaction.store( + columns::TRANSACTION, + DbHash::from_slice(hash.as_ref()), + encoded[offset..].to_vec(), + ); + DbExtrinsic::Indexed { + hash: DbHash::from_slice(hash.as_ref()), + header: encoded[..offset].to_vec(), + } + } else { + // Invalid indexed slice. Just store full data and don't index anything. + DbExtrinsic::Full(extrinsic) } }, - _ => ExtrinsicHeader { indexed_hash: Default::default(), data: extrinsic }, + _ => DbExtrinsic::Full(extrinsic), } }; - extrinsic_headers.push(extrinsic_header); + extrinsic_index.push(db_extrinsic); } debug!( target: "db", - "DB transaction index: {} inserted, {} renewed", + "DB transaction index: {} inserted, {} renewed, {} full", index_map.len(), - renewed_map.len() + renewed_map.len(), + extrinsic_index.len() - index_map.len() - renewed_map.len(), ); - extrinsic_headers.encode() + extrinsic_index.encode() } fn apply_indexed_body(transaction: &mut Transaction, body: Vec>) { @@ -1978,23 +1956,17 @@ where impl sc_client_api::backend::Backend for Backend { type BlockImportOperation = BlockImportOperation; type Blockchain = BlockchainDb; - type State = SyncingCachingState, Block>; + type State = RecordStatsState, Block>; type OffchainStorage = offchain::LocalStorage; fn begin_operation(&self) -> ClientResult { - let mut old_state = self.empty_state()?; - old_state.disable_syncing(); - Ok(BlockImportOperation { pending_block: None, - old_state, + old_state: self.empty_state()?, db_updates: PrefixedMemoryDB::default(), storage_updates: Default::default(), child_storage_updates: Default::default(), offchain_storage_updates: Default::default(), - changes_trie_config_update: None, - changes_trie_updates: MemoryDB::default(), - changes_trie_build_cache_update: None, aux_ops: Vec::new(), finalized_blocks: Vec::new(), set_head: None, @@ -2006,14 +1978,13 @@ impl sc_client_api::backend::Backend for Backend { fn begin_state_operation( &self, operation: &mut Self::BlockImportOperation, - block: BlockId, + block: Block::Hash, ) -> ClientResult<()> { - if block.is_pre_genesis() { + if block == Default::default() { operation.old_state = self.empty_state()?; } else { operation.old_state = self.state_at(block)?; } - operation.old_state.disable_syncing(); operation.commit_state = true; Ok(()) @@ -2023,52 +1994,47 @@ impl sc_client_api::backend::Backend for Backend { let usage = operation.old_state.usage_info(); self.state_usage.merge_sm(usage); - match self.try_commit_operation(operation) { - Ok(_) => { - self.storage.state_db.apply_pending(); - Ok(()) - }, - e @ Err(_) => { - self.storage.state_db.revert_pending(); - e - }, + if let Err(e) = self.try_commit_operation(operation) { + let state_meta_db = StateMetaDb(self.storage.db.clone()); + self.storage + .state_db + .reset(state_meta_db) + .map_err(sp_blockchain::Error::from_state_db)?; + Err(e) + } else { + Ok(()) } } fn finalize_block( &self, - block: BlockId, + hash: Block::Hash, justification: Option, ) -> ClientResult<()> { let mut transaction = Transaction::new(); - let hash = self.blockchain.expect_block_hash_from_id(&block)?; - let header = self.blockchain.expect_header(block)?; + let header = self.blockchain.expect_header(BlockId::Hash(hash))?; let mut displaced = None; - let mut changes_trie_cache_ops = None; let m = self.finalize_block_with_transaction( &mut transaction, - &hash, + hash, &header, None, justification, - &mut changes_trie_cache_ops, &mut displaced, )?; self.storage.db.commit(transaction)?; self.blockchain.update_meta(m); - self.changes_tries_storage.post_commit(changes_trie_cache_ops); Ok(()) } fn append_justification( &self, - block: BlockId, + hash: Block::Hash, justification: Justification, ) -> ClientResult<()> { let mut transaction: Transaction = Transaction::new(); - let hash = self.blockchain.expect_block_hash_from_id(&block)?; - let header = self.blockchain.expect_header(block)?; + let header = self.blockchain.expect_header(BlockId::Hash(hash))?; let number = *header.number(); // Check if the block is finalized first. @@ -2083,7 +2049,7 @@ impl sc_client_api::backend::Backend for Backend { } let justifications = if let Some(mut stored_justifications) = - self.blockchain.justifications(block)? + self.blockchain.justifications(hash)? { if !stored_justifications.append(justification) { return Err(ClientError::BadJustification("Duplicate consensus engine ID".into())) @@ -2104,10 +2070,6 @@ impl sc_client_api::backend::Backend for Backend { Ok(()) } - fn changes_trie_storage(&self) -> Option<&dyn PrunableStateChangesTrieStorage> { - Some(&self.changes_tries_storage) - } - fn offchain_storage(&self) -> Option { Some(self.offchain_storage.clone()) } @@ -2121,12 +2083,12 @@ impl sc_client_api::backend::Backend for Backend { ) }); let database_cache = MemorySize::from_bytes(0); - let state_cache = - MemorySize::from_bytes((*&self.shared_cache).read().used_storage_cache_size()); - let state_db = self.storage.state_db.memory_info(); + let state_cache = MemorySize::from_bytes( + self.shared_trie_cache.as_ref().map_or(0, |c| c.used_memory_size()), + ); Some(UsageInfo { - memory: MemoryInfo { state_cache, database_cache, state_db }, + memory: MemoryInfo { state_cache, database_cache }, io: IoInfo { transactions: io_stats.transactions, bytes_read: io_stats.bytes_read, @@ -2150,39 +2112,48 @@ impl sc_client_api::backend::Backend for Backend { ) -> ClientResult<(NumberFor, HashSet)> { let mut reverted_finalized = HashSet::new(); - let mut best_number = self.blockchain.info().best_number; - let mut best_hash = self.blockchain.info().best_hash; + let info = self.blockchain.info(); + + let highest_leaf = self + .blockchain + .leaves + .read() + .highest_leaf() + .and_then(|(n, h)| h.last().map(|h| (n, *h))); + + let best_number = info.best_number; + let best_hash = info.best_hash; - let finalized = self.blockchain.info().finalized_number; + let finalized = info.finalized_number; let revertible = best_number - finalized; let n = if !revert_finalized && revertible < n { revertible } else { n }; + let (n, mut number_to_revert, mut hash_to_revert) = match highest_leaf { + Some((l_n, l_h)) => (n + (l_n - best_number), l_n, l_h), + None => (n, best_number, best_hash), + }; + let mut revert_blocks = || -> ClientResult> { for c in 0..n.saturated_into::() { - if best_number.is_zero() { + if number_to_revert.is_zero() { return Ok(c.saturated_into::>()) } let mut transaction = Transaction::new(); - let removed_number = best_number; let removed = - self.blockchain.header(BlockId::Number(best_number))?.ok_or_else(|| { + self.blockchain.header(BlockId::Hash(hash_to_revert))?.ok_or_else(|| { sp_blockchain::Error::UnknownBlock(format!( - "Error reverting to {}. Block hash not found.", - best_number + "Error reverting to {}. Block header not found.", + hash_to_revert, )) })?; let removed_hash = removed.hash(); - let prev_number = best_number.saturating_sub(One::one()); - let prev_hash = self.blockchain.hash(prev_number)?.ok_or_else(|| { - sp_blockchain::Error::UnknownBlock(format!( - "Error reverting to {}. Block hash not found.", - best_number - )) - })?; + let prev_number = number_to_revert.saturating_sub(One::one()); + let prev_hash = + if prev_number == best_number { best_hash } else { *removed.parent_hash() }; - if !self.have_state_at(&prev_hash, prev_number) { + if !self.have_state_at(prev_hash, prev_number) { return Ok(c.saturated_into::>()) } @@ -2190,16 +2161,14 @@ impl sc_client_api::backend::Backend for Backend { Some(commit) => { apply_state_commit(&mut transaction, commit); - best_number = prev_number; - best_hash = prev_hash; + number_to_revert = prev_number; + hash_to_revert = prev_hash; - let update_finalized = best_number < finalized; + let update_finalized = number_to_revert < finalized; - let key = - utils::number_and_hash_to_lookup_key(best_number.clone(), &best_hash)?; - let changes_trie_cache_ops = self.changes_tries_storage.revert( - &mut transaction, - &cache::ComplexBlockId::new(removed.hash(), removed_number), + let key = utils::number_and_hash_to_lookup_key( + number_to_revert, + &hash_to_revert, )?; if update_finalized { transaction.set_from_vec( @@ -2210,12 +2179,14 @@ impl sc_client_api::backend::Backend for Backend { reverted_finalized.insert(removed_hash); if let Some((hash, _)) = self.blockchain.info().finalized_state { - if hash == best_hash { - if !best_number.is_zero() && - self.have_state_at(&prev_hash, best_number - One::one()) - { + if hash == hash_to_revert { + if !number_to_revert.is_zero() && + self.have_state_at( + prev_hash, + number_to_revert - One::one(), + ) { let lookup_key = utils::number_and_hash_to_lookup_key( - best_number - One::one(), + number_to_revert - One::one(), prev_hash, )?; transaction.set_from_vec( @@ -2236,14 +2207,16 @@ impl sc_client_api::backend::Backend for Backend { &mut transaction, columns::META, meta_keys::CHILDREN_PREFIX, - best_hash, + hash_to_revert, ); self.storage.db.commit(transaction)?; - self.changes_tries_storage.post_commit(Some(changes_trie_cache_ops)); + + let is_best = number_to_revert < best_number; + self.blockchain.update_meta(MetaUpdate { - hash: best_hash, - number: best_number, - is_best: true, + hash: hash_to_revert, + number: number_to_revert, + is_best, is_finalized: update_finalized, with_state: false, }); @@ -2261,7 +2234,7 @@ impl sc_client_api::backend::Backend for Backend { let mut transaction = Transaction::new(); let mut leaves = self.blockchain.leaves.write(); - leaves.revert(best_hash, best_number); + leaves.revert(hash_to_revert, number_to_revert); leaves.prepare_transaction(&mut transaction, columns::META, meta_keys::LEAF_PREFIX); self.storage.db.commit(transaction)?; @@ -2273,15 +2246,15 @@ impl sc_client_api::backend::Backend for Backend { Ok((reverted, reverted_finalized)) } - fn remove_leaf_block(&self, hash: &Block::Hash) -> ClientResult<()> { + fn remove_leaf_block(&self, hash: Block::Hash) -> ClientResult<()> { let best_hash = self.blockchain.info().best_hash; - if best_hash == *hash { + if best_hash == hash { return Err(sp_blockchain::Error::Backend(format!("Can't remove best block {:?}", hash))) } - let hdr = self.blockchain.header_metadata(hash.clone())?; - if !self.have_state_at(&hash, hdr.number) { + let hdr = self.blockchain.header_metadata(hash)?; + if !self.have_state_at(hash, hdr.number) { return Err(sp_blockchain::Error::UnknownBlock(format!( "State already discarded for {:?}", hash @@ -2289,7 +2262,7 @@ impl sc_client_api::backend::Backend for Backend { } let mut leaves = self.blockchain.leaves.write(); - if !leaves.contains(hdr.number, *hash) { + if !leaves.contains(hdr.number, hash) { return Err(sp_blockchain::Error::Backend(format!( "Can't remove non-leaf block {:?}", hash @@ -2297,19 +2270,45 @@ impl sc_client_api::backend::Backend for Backend { } let mut transaction = Transaction::new(); - if let Some(commit) = self.storage.state_db.remove(hash) { + if let Some(commit) = self.storage.state_db.remove(&hash) { apply_state_commit(&mut transaction, commit); } transaction.remove(columns::KEY_LOOKUP, hash.as_ref()); - let changes_trie_cache_ops = self - .changes_tries_storage - .revert(&mut transaction, &cache::ComplexBlockId::new(*hash, hdr.number))?; - self.changes_tries_storage.post_commit(Some(changes_trie_cache_ops)); - leaves.revert(hash.clone(), hdr.number); + let children: Vec<_> = self + .blockchain() + .children(hdr.parent)? + .into_iter() + .filter(|child_hash| *child_hash != hash) + .collect(); + let parent_leaf = if children.is_empty() { + children::remove_children( + &mut transaction, + columns::META, + meta_keys::CHILDREN_PREFIX, + hdr.parent, + ); + Some(hdr.parent) + } else { + children::write_children( + &mut transaction, + columns::META, + meta_keys::CHILDREN_PREFIX, + hdr.parent, + children, + ); + None + }; + + let remove_outcome = leaves.remove(hash, hdr.number, parent_leaf); leaves.prepare_transaction(&mut transaction, columns::META, meta_keys::LEAF_PREFIX); - self.storage.db.commit(transaction)?; - self.blockchain().remove_header_metadata(*hash); + if let Err(e) = self.storage.db.commit(transaction) { + if let Some(outcome) = remove_outcome { + leaves.undo().undo_remove(outcome); + } + return Err(e.into()) + } + self.blockchain().remove_header_metadata(hash); Ok(()) } @@ -2317,63 +2316,41 @@ impl sc_client_api::backend::Backend for Backend { &self.blockchain } - fn state_at(&self, block: BlockId) -> ClientResult { - use sc_client_api::blockchain::HeaderBackend as BcHeaderBackend; - - let is_genesis = match &block { - BlockId::Number(n) if n.is_zero() => true, - BlockId::Hash(h) if h == &self.blockchain.meta.read().genesis_hash => true, - _ => false, - }; - if is_genesis { + fn state_at(&self, hash: Block::Hash) -> ClientResult { + if hash == self.blockchain.meta.read().genesis_hash { if let Some(genesis_state) = &*self.genesis_state.read() { - let root = genesis_state.root.clone(); - let db_state = DbState::::new(genesis_state.clone(), root); + let root = genesis_state.root; + let db_state = DbStateBuilder::::new(genesis_state.clone(), root) + .with_optional_cache(self.shared_trie_cache.as_ref().map(|c| c.local_cache())) + .build(); + let state = RefTrackingState::new(db_state, self.storage.clone(), None); - let caching_state = CachingState::new(state, self.shared_cache.clone(), None); - let mut state = SyncingCachingState::new( - caching_state, - self.state_usage.clone(), - self.blockchain.meta.clone(), - self.import_lock.clone(), - ); - state.disable_syncing(); - return Ok(state) + return Ok(RecordStatsState::new(state, None, self.state_usage.clone())) } } - let hash = match block { - BlockId::Hash(h) => h, - BlockId::Number(n) => self.blockchain.hash(n)?.ok_or_else(|| { - sp_blockchain::Error::UnknownBlock(format!("Unknown block number {}", n)) - })?, - }; - match self.blockchain.header_metadata(hash) { Ok(ref hdr) => { - if !self.have_state_at(&hash, hdr.number) { - return Err(sp_blockchain::Error::UnknownBlock(format!( - "State already discarded for {:?}", - block - ))) - } - if let Ok(()) = self.storage.state_db.pin(&hash) { + let hint = || { + sc_state_db::NodeDb::get(self.storage.as_ref(), hdr.state_root.as_ref()) + .unwrap_or(None) + .is_some() + }; + if let Ok(()) = + self.storage.state_db.pin(&hash, hdr.number.saturated_into::(), hint) + { let root = hdr.state_root; - let db_state = DbState::::new(self.storage.clone(), root); - let state = - RefTrackingState::new(db_state, self.storage.clone(), Some(hash.clone())); - let caching_state = - CachingState::new(state, self.shared_cache.clone(), Some(hash)); - Ok(SyncingCachingState::new( - caching_state, - self.state_usage.clone(), - self.blockchain.meta.clone(), - self.import_lock.clone(), - )) + let db_state = DbStateBuilder::::new(self.storage.clone(), root) + .with_optional_cache( + self.shared_trie_cache.as_ref().map(|c| c.local_cache()), + ) + .build(); + let state = RefTrackingState::new(db_state, self.storage.clone(), Some(hash)); + Ok(RecordStatsState::new(state, Some(hash), self.state_usage.clone())) } else { Err(sp_blockchain::Error::UnknownBlock(format!( "State already discarded for {:?}", - block + hash ))) } }, @@ -2381,9 +2358,9 @@ impl sc_client_api::backend::Backend for Backend { } } - fn have_state_at(&self, hash: &Block::Hash, number: NumberFor) -> bool { + fn have_state_at(&self, hash: Block::Hash, number: NumberFor) -> bool { if self.is_archive { - match self.blockchain.header_metadata(hash.clone()) { + match self.blockchain.header_metadata(hash) { Ok(header) => sp_state_machine::Storage::get( self.storage.as_ref(), &header.state_root, @@ -2394,12 +2371,32 @@ impl sc_client_api::backend::Backend for Backend { _ => false, } } else { - !self.storage.state_db.is_pruned(hash, number.saturated_into::()) + match self.storage.state_db.is_pruned(&hash, number.saturated_into::()) { + IsPruned::Pruned => false, + IsPruned::NotPruned => true, + IsPruned::MaybePruned => match self.blockchain.header_metadata(hash) { + Ok(header) => sp_state_machine::Storage::get( + self.storage.as_ref(), + &header.state_root, + (&[], None), + ) + .unwrap_or(None) + .is_some(), + _ => false, + }, + } } } fn get_import_lock(&self) -> &RwLock<()> { - &*self.import_lock + &self.import_lock + } + + fn requires_full_sync(&self) -> bool { + matches!( + self.storage.state_db.pruning_mode(), + PruningMode::ArchiveAll | PruningMode::ArchiveCanonical + ) } } @@ -2417,32 +2414,16 @@ pub(crate) mod tests { use sp_blockchain::{lowest_common_ancestor, tree_route}; use sp_core::H256; use sp_runtime::{ - generic::DigestItem, testing::{Block as RawBlock, ExtrinsicWrapper, Header}, traits::{BlakeTwo256, Hash}, - ConsensusEngineId, + ConsensusEngineId, StateVersion, }; - use sp_state_machine::{TrieDBMut, TrieMut}; const CONS0_ENGINE_ID: ConsensusEngineId = *b"CON0"; const CONS1_ENGINE_ID: ConsensusEngineId = *b"CON1"; pub(crate) type Block = RawBlock>; - pub fn prepare_changes(changes: Vec<(Vec, Vec)>) -> (H256, MemoryDB) { - let mut changes_root = H256::default(); - let mut changes_trie_update = MemoryDB::::default(); - { - let mut trie = - TrieDBMut::::new(&mut changes_trie_update, &mut changes_root); - for (key, value) in changes { - trie.insert(&key, &value).unwrap(); - } - } - - (changes_root, changes_trie_update) - } - pub fn insert_header( backend: &Backend, number: u64, @@ -2451,50 +2432,62 @@ pub(crate) mod tests { extrinsics_root: H256, ) -> H256 { insert_block(backend, number, parent_hash, changes, extrinsics_root, Vec::new(), None) + .unwrap() } pub fn insert_block( backend: &Backend, number: u64, parent_hash: H256, - changes: Option, Vec)>>, + _changes: Option, Vec)>>, extrinsics_root: H256, body: Vec>, transaction_index: Option>, - ) -> H256 { + ) -> Result { use sp_runtime::testing::Digest; - let mut digest = Digest::default(); - let mut changes_trie_update = Default::default(); - if let Some(changes) = changes { - let (root, update) = prepare_changes(changes); - digest.push(DigestItem::ChangesTrieRoot(root)); - changes_trie_update = update; - } + let digest = Digest::default(); let header = Header { number, parent_hash, - state_root: BlakeTwo256::trie_root(Vec::new()), + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), digest, extrinsics_root, }; let header_hash = header.hash(); - let block_id = if number == 0 { - BlockId::Hash(Default::default()) - } else { - BlockId::Number(number - 1) - }; + let block_hash = if number == 0 { Default::default() } else { parent_hash }; let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, block_id).unwrap(); + backend.begin_state_operation(&mut op, block_hash).unwrap(); op.set_block_data(header, Some(body), None, None, NewBlockState::Best).unwrap(); if let Some(index) = transaction_index { op.update_transaction_index(index).unwrap(); } - op.update_changes_trie((changes_trie_update, ChangesTrieCacheAction::Clear)) - .unwrap(); - backend.commit_operation(op).unwrap(); + backend.commit_operation(op)?; + + Ok(header_hash) + } + + pub fn insert_header_no_head( + backend: &Backend, + number: u64, + parent_hash: H256, + extrinsics_root: H256, + ) -> H256 { + use sp_runtime::testing::Digest; + let digest = Digest::default(); + let header = Header { + number, + parent_hash, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest, + extrinsics_root, + }; + let header_hash = header.hash(); + let mut op = backend.begin_operation().unwrap(); + op.set_block_data(header, None, None, None, NewBlockState::Normal).unwrap(); + backend.commit_operation(op).unwrap(); header_hash } @@ -2506,21 +2499,17 @@ pub(crate) mod tests { assert!(db.blockchain().hash(i).unwrap().is_none()); { - let id = if i == 0 { - BlockId::Hash(Default::default()) + let hash = if i == 0 { + Default::default() } else { - BlockId::Number(i - 1) + db.blockchain.hash(i - 1).unwrap().unwrap() }; let mut op = db.begin_operation().unwrap(); - db.begin_state_operation(&mut op, id).unwrap(); + db.begin_state_operation(&mut op, hash).unwrap(); let header = Header { number: i, - parent_hash: if i == 0 { - Default::default() - } else { - db.blockchain.hash(i - 1).unwrap().unwrap() - }, + parent_hash: hash, state_root: Default::default(), digest: Default::default(), extrinsics_root: Default::default(), @@ -2538,12 +2527,10 @@ pub(crate) mod tests { let backend = Backend::::new( DatabaseSettings { - state_cache_size: 16777216, - state_cache_child_ratio: Some((50, 100)), - state_pruning: PruningMode::keep_blocks(1), - source: DatabaseSource::Custom(backing), - keep_blocks: KeepBlocks::All, - transaction_storage: TransactionStorageMode::BlockBody, + trie_cache_maximum_size: Some(16 * 1024 * 1024), + state_pruning: Some(PruningMode::blocks_pruning(1)), + source: DatabaseSource::Custom { db: backing, require_create_flag: false }, + blocks_pruning: BlocksPruning::KeepFinalized, }, 0, ) @@ -2556,6 +2543,10 @@ pub(crate) mod tests { #[test] fn set_state_data() { + set_state_data_inner(StateVersion::V0); + set_state_data_inner(StateVersion::V1); + } + fn set_state_data_inner(state_version: StateVersion) { let db = Backend::::new_test(2, 0); let hash = { let mut op = db.begin_operation().unwrap(); @@ -2571,22 +2562,25 @@ pub(crate) mod tests { header.state_root = op .old_state - .storage_root(storage.iter().map(|(x, y)| (&x[..], Some(&y[..])))) + .storage_root(storage.iter().map(|(x, y)| (&x[..], Some(&y[..]))), state_version) .0 .into(); let hash = header.hash(); - op.reset_storage(Storage { - top: storage.into_iter().collect(), - children_default: Default::default(), - }) + op.reset_storage( + Storage { + top: storage.into_iter().collect(), + children_default: Default::default(), + }, + state_version, + ) .unwrap(); op.set_block_data(header.clone(), Some(vec![]), None, None, NewBlockState::Best) .unwrap(); db.commit_operation(op).unwrap(); - let state = db.state_at(BlockId::Number(0)).unwrap(); + let state = db.state_at(hash).unwrap(); assert_eq!(state.storage(&[1, 3, 5]).unwrap(), Some(vec![2, 4, 6])); assert_eq!(state.storage(&[1, 2, 3]).unwrap(), Some(vec![9, 9, 9])); @@ -2597,7 +2591,7 @@ pub(crate) mod tests { { let mut op = db.begin_operation().unwrap(); - db.begin_state_operation(&mut op, BlockId::Number(0)).unwrap(); + db.begin_state_operation(&mut op, hash).unwrap(); let mut header = Header { number: 1, parent_hash: hash, @@ -2608,19 +2602,20 @@ pub(crate) mod tests { let storage = vec![(vec![1, 3, 5], None), (vec![5, 5, 5], Some(vec![4, 5, 6]))]; - let (root, overlay) = op - .old_state - .storage_root(storage.iter().map(|(k, v)| (&k[..], v.as_ref().map(|v| &v[..])))); + let (root, overlay) = op.old_state.storage_root( + storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))), + state_version, + ); op.update_db_storage(overlay).unwrap(); header.state_root = root.into(); op.update_storage(storage, Vec::new()).unwrap(); - op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best) + op.set_block_data(header.clone(), Some(vec![]), None, None, NewBlockState::Best) .unwrap(); db.commit_operation(op).unwrap(); - let state = db.state_at(BlockId::Number(1)).unwrap(); + let state = db.state_at(header.hash()).unwrap(); assert_eq!(state.storage(&[1, 3, 5]).unwrap(), None); assert_eq!(state.storage(&[1, 2, 3]).unwrap(), Some(vec![9, 9, 9])); @@ -2631,14 +2626,13 @@ pub(crate) mod tests { #[test] fn delete_only_when_negative_rc() { sp_tracing::try_init_simple(); + let state_version = StateVersion::default(); let key; let backend = Backend::::new_test(1, 0); let hash = { let mut op = backend.begin_operation().unwrap(); - backend - .begin_state_operation(&mut op, BlockId::Hash(Default::default())) - .unwrap(); + backend.begin_state_operation(&mut op, Default::default()).unwrap(); let mut header = Header { number: 0, parent_hash: Default::default(), @@ -2647,13 +2641,14 @@ pub(crate) mod tests { extrinsics_root: Default::default(), }; - header.state_root = op.old_state.storage_root(std::iter::empty()).0.into(); + header.state_root = + op.old_state.storage_root(std::iter::empty(), state_version).0.into(); let hash = header.hash(); - op.reset_storage(Storage { - top: Default::default(), - children_default: Default::default(), - }) + op.reset_storage( + Storage { top: Default::default(), children_default: Default::default() }, + state_version, + ) .unwrap(); key = op.db_updates.insert(EMPTY_PREFIX, b"hello"); @@ -2672,9 +2667,9 @@ pub(crate) mod tests { hash }; - let hash = { + let hashof1 = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Number(0)).unwrap(); + backend.begin_state_operation(&mut op, hash).unwrap(); let mut header = Header { number: 1, parent_hash: hash, @@ -2687,7 +2682,7 @@ pub(crate) mod tests { header.state_root = op .old_state - .storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y)))) + .storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y))), state_version) .0 .into(); let hash = header.hash(); @@ -2709,12 +2704,12 @@ pub(crate) mod tests { hash }; - let hash = { + let hashof2 = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Number(1)).unwrap(); + backend.begin_state_operation(&mut op, hashof1).unwrap(); let mut header = Header { number: 2, - parent_hash: hash, + parent_hash: hashof1, state_root: Default::default(), digest: Default::default(), extrinsics_root: Default::default(), @@ -2724,7 +2719,7 @@ pub(crate) mod tests { header.state_root = op .old_state - .storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y)))) + .storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y))), state_version) .0 .into(); let hash = header.hash(); @@ -2743,12 +2738,12 @@ pub(crate) mod tests { hash }; - { + let hashof3 = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Number(2)).unwrap(); + backend.begin_state_operation(&mut op, hashof2).unwrap(); let mut header = Header { number: 3, - parent_hash: hash, + parent_hash: hashof2, state_root: Default::default(), digest: Default::default(), extrinsics_root: Default::default(), @@ -2758,9 +2753,10 @@ pub(crate) mod tests { header.state_root = op .old_state - .storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y)))) + .storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y))), state_version) .0 .into(); + let hash = header.hash(); op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best) .unwrap(); @@ -2771,11 +2767,12 @@ pub(crate) mod tests { .db .get(columns::STATE, &sp_trie::prefixed_key::(&key, EMPTY_PREFIX)) .is_none()); - } + hash + }; - backend.finalize_block(BlockId::Number(1), None).unwrap(); - backend.finalize_block(BlockId::Number(2), None).unwrap(); - backend.finalize_block(BlockId::Number(3), None).unwrap(); + backend.finalize_block(hashof1, None).unwrap(); + backend.finalize_block(hashof2, None).unwrap(); + backend.finalize_block(hashof3, None).unwrap(); assert!(backend .storage .db @@ -2798,6 +2795,14 @@ pub(crate) mod tests { let b1 = insert_header(&backend, 1, block0, None, H256::from([1; 32])); let b2 = insert_header(&backend, 2, b1, None, Default::default()); + { + let tree_route = tree_route(blockchain, a1, a1).unwrap(); + + assert_eq!(tree_route.common_block().hash, a1); + assert!(tree_route.retracted().is_empty()); + assert!(tree_route.enacted().is_empty()); + } + { let tree_route = tree_route(blockchain, a3, b2).unwrap(); @@ -2998,8 +3003,8 @@ pub(crate) mod tests { vec![block2_a, block2_b, block2_c, block1_c] ); - backend.finalize_block(BlockId::hash(block1_a), None).unwrap(); - backend.finalize_block(BlockId::hash(block2_a), None).unwrap(); + backend.finalize_block(block1_a, None).unwrap(); + backend.finalize_block(block2_a, None).unwrap(); // leaves at same height stay. Leaves at lower heights pruned. assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2_a, block2_b, block2_c]); @@ -3023,13 +3028,13 @@ pub(crate) mod tests { let backend = Backend::::new_test(10, 10); let block0 = insert_header(&backend, 0, Default::default(), None, Default::default()); - let _ = insert_header(&backend, 1, block0, None, Default::default()); + let block1 = insert_header(&backend, 1, block0, None, Default::default()); let justification = Some((CONS0_ENGINE_ID, vec![1, 2, 3])); - backend.finalize_block(BlockId::Number(1), justification.clone()).unwrap(); + backend.finalize_block(block1, justification.clone()).unwrap(); assert_eq!( - backend.blockchain().justifications(BlockId::Number(1)).unwrap(), + backend.blockchain().justifications(block1).unwrap(), justification.map(Justifications::from), ); } @@ -3041,17 +3046,17 @@ pub(crate) mod tests { let backend = Backend::::new_test(10, 10); let block0 = insert_header(&backend, 0, Default::default(), None, Default::default()); - let _ = insert_header(&backend, 1, block0, None, Default::default()); + let block1 = insert_header(&backend, 1, block0, None, Default::default()); let just0 = (CONS0_ENGINE_ID, vec![1, 2, 3]); - backend.finalize_block(BlockId::Number(1), Some(just0.clone().into())).unwrap(); + backend.finalize_block(block1, Some(just0.clone().into())).unwrap(); let just1 = (CONS1_ENGINE_ID, vec![4, 5]); - backend.append_justification(BlockId::Number(1), just1.clone()).unwrap(); + backend.append_justification(block1, just1.clone()).unwrap(); let just2 = (CONS1_ENGINE_ID, vec![6, 7]); assert!(matches!( - backend.append_justification(BlockId::Number(1), just2), + backend.append_justification(block1, just2), Err(ClientError::BadJustification(_)) )); @@ -3060,10 +3065,7 @@ pub(crate) mod tests { just.append(just1); just }; - assert_eq!( - backend.blockchain().justifications(BlockId::Number(1)).unwrap(), - Some(justifications), - ); + assert_eq!(backend.blockchain().justifications(block1).unwrap(), Some(justifications),); } #[test] @@ -3077,29 +3079,28 @@ pub(crate) mod tests { let block4 = insert_header(&backend, 4, block3, None, Default::default()); { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(block0)).unwrap(); - op.mark_finalized(BlockId::Hash(block1), None).unwrap(); - op.mark_finalized(BlockId::Hash(block2), None).unwrap(); + backend.begin_state_operation(&mut op, block0).unwrap(); + op.mark_finalized(block1, None).unwrap(); + op.mark_finalized(block2, None).unwrap(); backend.commit_operation(op).unwrap(); } { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(block2)).unwrap(); - op.mark_finalized(BlockId::Hash(block3), None).unwrap(); - op.mark_finalized(BlockId::Hash(block4), None).unwrap(); + backend.begin_state_operation(&mut op, block2).unwrap(); + op.mark_finalized(block3, None).unwrap(); + op.mark_finalized(block4, None).unwrap(); backend.commit_operation(op).unwrap(); } } #[test] fn storage_hash_is_cached_correctly() { + let state_version = StateVersion::default(); let backend = Backend::::new_test(10, 10); let hash0 = { let mut op = backend.begin_operation().unwrap(); - backend - .begin_state_operation(&mut op, BlockId::Hash(Default::default())) - .unwrap(); + backend.begin_state_operation(&mut op, Default::default()).unwrap(); let mut header = Header { number: 0, parent_hash: Default::default(), @@ -3112,15 +3113,18 @@ pub(crate) mod tests { header.state_root = op .old_state - .storage_root(storage.iter().map(|(x, y)| (&x[..], Some(&y[..])))) + .storage_root(storage.iter().map(|(x, y)| (&x[..], Some(&y[..]))), state_version) .0 .into(); let hash = header.hash(); - op.reset_storage(Storage { - top: storage.into_iter().collect(), - children_default: Default::default(), - }) + op.reset_storage( + Storage { + top: storage.into_iter().collect(), + children_default: Default::default(), + }, + state_version, + ) .unwrap(); op.set_block_data(header.clone(), Some(vec![]), None, None, NewBlockState::Best) .unwrap(); @@ -3130,15 +3134,11 @@ pub(crate) mod tests { hash }; - let block0_hash = backend - .state_at(BlockId::Hash(hash0)) - .unwrap() - .storage_hash(&b"test"[..]) - .unwrap(); + let block0_hash = backend.state_at(hash0).unwrap().storage_hash(&b"test"[..]).unwrap(); let hash1 = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Number(0)).unwrap(); + backend.begin_state_operation(&mut op, hash0).unwrap(); let mut header = Header { number: 1, parent_hash: hash0, @@ -3149,9 +3149,10 @@ pub(crate) mod tests { let storage = vec![(b"test".to_vec(), Some(b"test2".to_vec()))]; - let (root, overlay) = op - .old_state - .storage_root(storage.iter().map(|(k, v)| (&k[..], v.as_ref().map(|v| &v[..])))); + let (root, overlay) = op.old_state.storage_root( + storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))), + state_version, + ); op.update_db_storage(overlay).unwrap(); header.state_root = root.into(); let hash = header.hash(); @@ -3168,16 +3169,11 @@ pub(crate) mod tests { { let header = backend.blockchain().header(BlockId::Hash(hash1)).unwrap().unwrap(); let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(hash0)).unwrap(); op.set_block_data(header, None, None, None, NewBlockState::Best).unwrap(); backend.commit_operation(op).unwrap(); } - let block1_hash = backend - .state_at(BlockId::Hash(hash1)) - .unwrap() - .storage_hash(&b"test"[..]) - .unwrap(); + let block1_hash = backend.state_at(hash1).unwrap().storage_hash(&b"test"[..]).unwrap(); assert_ne!(block0_hash, block1_hash); } @@ -3191,85 +3187,156 @@ pub(crate) mod tests { let block2 = insert_header(&backend, 2, block1, None, Default::default()); { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(block0)).unwrap(); - op.mark_finalized(BlockId::Hash(block2), None).unwrap(); + backend.begin_state_operation(&mut op, block0).unwrap(); + op.mark_finalized(block2, None).unwrap(); backend.commit_operation(op).unwrap_err(); } } #[test] - fn header_cht_root_works() { - use sc_client_api::ProvideChtRoots; + fn prune_blocks_on_finalize() { + let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(2), 0); + let mut blocks = Vec::new(); + let mut prev_hash = Default::default(); + for i in 0..5 { + let hash = insert_block( + &backend, + i, + prev_hash, + None, + Default::default(), + vec![i.into()], + None, + ) + .unwrap(); + blocks.push(hash); + prev_hash = hash; + } - let backend = Backend::::new_test(10, 10); + { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); + for i in 1..5 { + op.mark_finalized(blocks[i], None).unwrap(); + } + backend.commit_operation(op).unwrap(); + } + let bc = backend.blockchain(); + assert_eq!(None, bc.body(blocks[0]).unwrap()); + assert_eq!(None, bc.body(blocks[1]).unwrap()); + assert_eq!(None, bc.body(blocks[2]).unwrap()); + assert_eq!(Some(vec![3.into()]), bc.body(blocks[3]).unwrap()); + assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); + } - // insert 1 + SIZE + SIZE + 1 blocks so that CHT#0 is created - let mut prev_hash = - insert_header(&backend, 0, Default::default(), None, Default::default()); - let cht_size: u64 = cht::size(); - for i in 1..1 + cht_size + cht_size + 1 { - prev_hash = insert_header(&backend, i, prev_hash, None, Default::default()); + #[test] + fn prune_blocks_on_finalize_in_keep_all() { + let backend = Backend::::new_test_with_tx_storage(BlocksPruning::KeepAll, 0); + let mut blocks = Vec::new(); + let mut prev_hash = Default::default(); + for i in 0..5 { + let hash = insert_block( + &backend, + i, + prev_hash, + None, + Default::default(), + vec![i.into()], + None, + ) + .unwrap(); + blocks.push(hash); + prev_hash = hash; } - let blockchain = backend.blockchain(); + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); + for i in 1..3 { + op.mark_finalized(blocks[i], None).unwrap(); + } + backend.commit_operation(op).unwrap(); - let cht_root_1 = blockchain - .header_cht_root(cht_size, cht::start_number(cht_size, 0)) - .unwrap() - .unwrap(); - let cht_root_2 = blockchain - .header_cht_root(cht_size, cht::start_number(cht_size, 0) + cht_size / 2) - .unwrap() - .unwrap(); - let cht_root_3 = blockchain - .header_cht_root(cht_size, cht::end_number(cht_size, 0)) - .unwrap() - .unwrap(); - assert_eq!(cht_root_1, cht_root_2); - assert_eq!(cht_root_2, cht_root_3); + let bc = backend.blockchain(); + assert_eq!(Some(vec![0.into()]), bc.body(blocks[0]).unwrap()); + assert_eq!(Some(vec![1.into()]), bc.body(blocks[1]).unwrap()); + assert_eq!(Some(vec![2.into()]), bc.body(blocks[2]).unwrap()); + assert_eq!(Some(vec![3.into()]), bc.body(blocks[3]).unwrap()); + assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); } #[test] - fn prune_blocks_on_finalize() { - for storage in &[TransactionStorageMode::BlockBody, TransactionStorageMode::StorageChain] { - let backend = Backend::::new_test_with_tx_storage(2, 0, *storage); - let mut blocks = Vec::new(); - let mut prev_hash = Default::default(); - for i in 0..5 { - let hash = insert_block( - &backend, - i, - prev_hash, - None, - Default::default(), - vec![i.into()], - None, - ); - blocks.push(hash); - prev_hash = hash; - } + fn prune_blocks_on_finalize_with_fork_in_keep_all() { + let backend = Backend::::new_test_with_tx_storage(BlocksPruning::KeepAll, 10); + let mut blocks = Vec::new(); + let mut prev_hash = Default::default(); + for i in 0..5 { + let hash = insert_block( + &backend, + i, + prev_hash, + None, + Default::default(), + vec![i.into()], + None, + ) + .unwrap(); + blocks.push(hash); + prev_hash = hash; + } - { - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); - for i in 1..5 { - op.mark_finalized(BlockId::Hash(blocks[i]), None).unwrap(); - } - backend.commit_operation(op).unwrap(); - } - let bc = backend.blockchain(); - assert_eq!(None, bc.body(BlockId::hash(blocks[0])).unwrap()); - assert_eq!(None, bc.body(BlockId::hash(blocks[1])).unwrap()); - assert_eq!(None, bc.body(BlockId::hash(blocks[2])).unwrap()); - assert_eq!(Some(vec![3.into()]), bc.body(BlockId::hash(blocks[3])).unwrap()); - assert_eq!(Some(vec![4.into()]), bc.body(BlockId::hash(blocks[4])).unwrap()); + // insert a fork at block 2 + let fork_hash_root = insert_block( + &backend, + 2, + blocks[1], + None, + sp_core::H256::random(), + vec![2.into()], + None, + ) + .unwrap(); + insert_block( + &backend, + 3, + fork_hash_root, + None, + H256::random(), + vec![3.into(), 11.into()], + None, + ) + .unwrap(); + + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); + op.mark_head(blocks[4]).unwrap(); + backend.commit_operation(op).unwrap(); + + let bc = backend.blockchain(); + assert_eq!(Some(vec![2.into()]), bc.body(fork_hash_root).unwrap()); + + for i in 1..5 { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, blocks[i]).unwrap(); + op.mark_finalized(blocks[i], None).unwrap(); + backend.commit_operation(op).unwrap(); + } + + assert_eq!(Some(vec![0.into()]), bc.body(blocks[0]).unwrap()); + assert_eq!(Some(vec![1.into()]), bc.body(blocks[1]).unwrap()); + assert_eq!(Some(vec![2.into()]), bc.body(blocks[2]).unwrap()); + assert_eq!(Some(vec![3.into()]), bc.body(blocks[3]).unwrap()); + assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); + + assert_eq!(Some(vec![2.into()]), bc.body(fork_hash_root).unwrap()); + assert_eq!(bc.info().best_number, 4); + for i in 0..5 { + assert!(bc.hash(i).unwrap().is_some()); } } #[test] fn prune_blocks_on_finalize_with_fork() { - let backend = - Backend::::new_test_with_tx_storage(2, 10, TransactionStorageMode::StorageChain); + let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(2), 10); let mut blocks = Vec::new(); let mut prev_hash = Default::default(); for i in 0..5 { @@ -3281,7 +3348,8 @@ pub(crate) mod tests { Default::default(), vec![i.into()], None, - ); + ) + .unwrap(); blocks.push(hash); prev_hash = hash; } @@ -3295,7 +3363,8 @@ pub(crate) mod tests { sp_core::H256::random(), vec![2.into()], None, - ); + ) + .unwrap(); insert_block( &backend, 3, @@ -3304,31 +3373,110 @@ pub(crate) mod tests { H256::random(), vec![3.into(), 11.into()], None, - ); + ) + .unwrap(); let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); - op.mark_head(BlockId::Hash(blocks[4])).unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); + op.mark_head(blocks[4]).unwrap(); backend.commit_operation(op).unwrap(); for i in 1..5 { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); - op.mark_finalized(BlockId::Hash(blocks[i]), None).unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); + op.mark_finalized(blocks[i], None).unwrap(); backend.commit_operation(op).unwrap(); } let bc = backend.blockchain(); - assert_eq!(None, bc.body(BlockId::hash(blocks[0])).unwrap()); - assert_eq!(None, bc.body(BlockId::hash(blocks[1])).unwrap()); - assert_eq!(None, bc.body(BlockId::hash(blocks[2])).unwrap()); - assert_eq!(Some(vec![3.into()]), bc.body(BlockId::hash(blocks[3])).unwrap()); - assert_eq!(Some(vec![4.into()]), bc.body(BlockId::hash(blocks[4])).unwrap()); + assert_eq!(None, bc.body(blocks[0]).unwrap()); + assert_eq!(None, bc.body(blocks[1]).unwrap()); + assert_eq!(None, bc.body(blocks[2]).unwrap()); + assert_eq!(Some(vec![3.into()]), bc.body(blocks[3]).unwrap()); + assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); + } + + #[test] + fn indexed_data_block_body() { + let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(1), 10); + + let x0 = ExtrinsicWrapper::from(0u64).encode(); + let x1 = ExtrinsicWrapper::from(1u64).encode(); + let x0_hash = as sp_core::Hasher>::hash(&x0[1..]); + let x1_hash = as sp_core::Hasher>::hash(&x1[1..]); + let index = vec![ + IndexOperation::Insert { + extrinsic: 0, + hash: x0_hash.as_ref().to_vec(), + size: (x0.len() - 1) as u32, + }, + IndexOperation::Insert { + extrinsic: 1, + hash: x1_hash.as_ref().to_vec(), + size: (x1.len() - 1) as u32, + }, + ]; + let hash = insert_block( + &backend, + 0, + Default::default(), + None, + Default::default(), + vec![0u64.into(), 1u64.into()], + Some(index), + ) + .unwrap(); + let bc = backend.blockchain(); + assert_eq!(bc.indexed_transaction(x0_hash).unwrap().unwrap(), &x0[1..]); + assert_eq!(bc.indexed_transaction(x1_hash).unwrap().unwrap(), &x1[1..]); + + let hashof0 = bc.info().genesis_hash; + // Push one more blocks and make sure block is pruned and transaction index is cleared. + let block1 = + insert_block(&backend, 1, hash, None, Default::default(), vec![], None).unwrap(); + backend.finalize_block(block1, None).unwrap(); + assert_eq!(bc.body(hashof0).unwrap(), None); + assert_eq!(bc.indexed_transaction(x0_hash).unwrap(), None); + assert_eq!(bc.indexed_transaction(x1_hash).unwrap(), None); + } + + #[test] + fn index_invalid_size() { + let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(1), 10); + + let x0 = ExtrinsicWrapper::from(0u64).encode(); + let x1 = ExtrinsicWrapper::from(1u64).encode(); + let x0_hash = as sp_core::Hasher>::hash(&x0[..]); + let x1_hash = as sp_core::Hasher>::hash(&x1[..]); + let index = vec![ + IndexOperation::Insert { + extrinsic: 0, + hash: x0_hash.as_ref().to_vec(), + size: (x0.len()) as u32, + }, + IndexOperation::Insert { + extrinsic: 1, + hash: x1_hash.as_ref().to_vec(), + size: (x1.len() + 1) as u32, + }, + ]; + insert_block( + &backend, + 0, + Default::default(), + None, + Default::default(), + vec![0u64.into(), 1u64.into()], + Some(index), + ) + .unwrap(); + let bc = backend.blockchain(); + assert_eq!(bc.indexed_transaction(x0_hash).unwrap().unwrap(), &x0[..]); + assert_eq!(bc.indexed_transaction(x1_hash).unwrap(), None); } #[test] fn renew_transaction_storage() { - let backend = - Backend::::new_test_with_tx_storage(2, 10, TransactionStorageMode::StorageChain); + let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(2), 10); let mut blocks = Vec::new(); let mut prev_hash = Default::default(); let x1 = ExtrinsicWrapper::from(0u64).encode(); @@ -3353,29 +3501,29 @@ pub(crate) mod tests { Default::default(), vec![i.into()], Some(index), - ); + ) + .unwrap(); blocks.push(hash); prev_hash = hash; } for i in 1..10 { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); - op.mark_finalized(BlockId::Hash(blocks[i]), None).unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); + op.mark_finalized(blocks[i], None).unwrap(); backend.commit_operation(op).unwrap(); let bc = backend.blockchain(); if i < 6 { - assert!(bc.indexed_transaction(&x1_hash).unwrap().is_some()); + assert!(bc.indexed_transaction(x1_hash).unwrap().is_some()); } else { - assert!(bc.indexed_transaction(&x1_hash).unwrap().is_none()); + assert!(bc.indexed_transaction(x1_hash).unwrap().is_none()); } } } #[test] fn remove_leaf_block_works() { - let backend = - Backend::::new_test_with_tx_storage(2, 10, TransactionStorageMode::StorageChain); + let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(2), 10); let mut blocks = Vec::new(); let mut prev_hash = Default::default(); for i in 0..2 { @@ -3387,12 +3535,27 @@ pub(crate) mod tests { Default::default(), vec![i.into()], None, - ); + ) + .unwrap(); blocks.push(hash); prev_hash = hash; } - // insert a fork at block 2, which becomes best block + for i in 0..2 { + let hash = insert_block( + &backend, + 2, + blocks[1], + None, + sp_core::H256::random(), + vec![i.into()], + None, + ) + .unwrap(); + blocks.push(hash); + } + + // insert a fork at block 1, which becomes best block let best_hash = insert_block( &backend, 1, @@ -3401,12 +3564,38 @@ pub(crate) mod tests { sp_core::H256::random(), vec![42.into()], None, - ); - assert!(backend.remove_leaf_block(&best_hash).is_err()); - assert!(backend.have_state_at(&prev_hash, 1)); - backend.remove_leaf_block(&prev_hash).unwrap(); - assert_eq!(None, backend.blockchain().header(BlockId::hash(prev_hash.clone())).unwrap()); - assert!(!backend.have_state_at(&prev_hash, 1)); + ) + .unwrap(); + + assert_eq!(backend.blockchain().info().best_hash, best_hash); + assert!(backend.remove_leaf_block(best_hash).is_err()); + + assert_eq!(backend.blockchain().leaves().unwrap(), vec![blocks[2], blocks[3], best_hash]); + assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![blocks[2], blocks[3]]); + + assert!(backend.have_state_at(blocks[3], 2)); + assert!(backend.blockchain().header(BlockId::hash(blocks[3])).unwrap().is_some()); + backend.remove_leaf_block(blocks[3]).unwrap(); + assert!(!backend.have_state_at(blocks[3], 2)); + assert!(backend.blockchain().header(BlockId::hash(blocks[3])).unwrap().is_none()); + assert_eq!(backend.blockchain().leaves().unwrap(), vec![blocks[2], best_hash]); + assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![blocks[2]]); + + assert!(backend.have_state_at(blocks[2], 2)); + assert!(backend.blockchain().header(BlockId::hash(blocks[2])).unwrap().is_some()); + backend.remove_leaf_block(blocks[2]).unwrap(); + assert!(!backend.have_state_at(blocks[2], 2)); + assert!(backend.blockchain().header(BlockId::hash(blocks[2])).unwrap().is_none()); + assert_eq!(backend.blockchain().leaves().unwrap(), vec![best_hash, blocks[1]]); + assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![]); + + assert!(backend.have_state_at(blocks[1], 1)); + assert!(backend.blockchain().header(BlockId::hash(blocks[1])).unwrap().is_some()); + backend.remove_leaf_block(blocks[1]).unwrap(); + assert!(!backend.have_state_at(blocks[1], 1)); + assert!(backend.blockchain().header(BlockId::hash(blocks[1])).unwrap().is_none()); + assert_eq!(backend.blockchain().leaves().unwrap(), vec![best_hash]); + assert_eq!(backend.blockchain().children(blocks[0]).unwrap(), vec![best_hash]); } #[test] @@ -3425,7 +3614,7 @@ pub(crate) mod tests { let header = Header { number: 1, parent_hash: block0, - state_root: BlakeTwo256::trie_root(Vec::new()), + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), digest: Default::default(), extrinsics_root: Default::default(), }; @@ -3437,7 +3626,7 @@ pub(crate) mod tests { let header = Header { number: 2, parent_hash: block1, - state_root: BlakeTwo256::trie_root(Vec::new()), + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), digest: Default::default(), extrinsics_root: Default::default(), }; @@ -3460,7 +3649,7 @@ pub(crate) mod tests { let header = Header { number: 1, parent_hash: block0, - state_root: BlakeTwo256::trie_root(Vec::new()), + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), digest: Default::default(), extrinsics_root: Default::default(), }; @@ -3471,4 +3660,144 @@ pub(crate) mod tests { assert_eq!(backend.blockchain().info().finalized_hash, block1); } + + #[test] + fn test_import_existing_state_fails() { + let backend: Backend = Backend::new_test(10, 10); + let genesis = + insert_block(&backend, 0, Default::default(), None, Default::default(), vec![], None) + .unwrap(); + + insert_block(&backend, 1, genesis, None, Default::default(), vec![], None).unwrap(); + let err = insert_block(&backend, 1, genesis, None, Default::default(), vec![], None) + .err() + .unwrap(); + match err { + sp_blockchain::Error::StateDatabase(m) if m == "Block already exists" => (), + e @ _ => panic!("Unexpected error {:?}", e), + } + } + + #[test] + fn test_leaves_not_created_for_ancient_blocks() { + let backend: Backend = Backend::new_test(10, 10); + let block0 = insert_header(&backend, 0, Default::default(), None, Default::default()); + + let block1_a = insert_header(&backend, 1, block0, None, Default::default()); + let block2_a = insert_header(&backend, 2, block1_a, None, Default::default()); + backend.finalize_block(block1_a, None).unwrap(); + assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2_a]); + + // Insert a fork prior to finalization point. Leave should not be created. + insert_header_no_head(&backend, 1, block0, [1; 32].into()); + assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2_a]); + } + + #[test] + fn revert_non_best_blocks() { + let backend = Backend::::new_test(10, 10); + + let genesis = + insert_block(&backend, 0, Default::default(), None, Default::default(), vec![], None) + .unwrap(); + + let block1 = + insert_block(&backend, 1, genesis, None, Default::default(), vec![], None).unwrap(); + + let block2 = + insert_block(&backend, 2, block1, None, Default::default(), vec![], None).unwrap(); + + let block3 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block1).unwrap(); + let header = Header { + number: 3, + parent_hash: block2, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Normal) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() + }; + + let block4 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block2).unwrap(); + let header = Header { + number: 4, + parent_hash: block3, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Normal) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() + }; + + let block3_fork = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block2).unwrap(); + let header = Header { + number: 3, + parent_hash: block2, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest: Default::default(), + extrinsics_root: H256::from_low_u64_le(42), + }; + + op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Normal) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() + }; + + assert!(backend.have_state_at(block1, 1)); + assert!(backend.have_state_at(block2, 2)); + assert!(backend.have_state_at(block3, 3)); + assert!(backend.have_state_at(block4, 4)); + assert!(backend.have_state_at(block3_fork, 3)); + + assert_eq!(backend.blockchain.leaves().unwrap(), vec![block4, block3_fork]); + assert_eq!(4, backend.blockchain.leaves.read().highest_leaf().unwrap().0); + + assert_eq!(3, backend.revert(1, false).unwrap().0); + + assert!(backend.have_state_at(block1, 1)); + assert!(!backend.have_state_at(block2, 2)); + assert!(!backend.have_state_at(block3, 3)); + assert!(!backend.have_state_at(block4, 4)); + assert!(!backend.have_state_at(block3_fork, 3)); + + assert_eq!(backend.blockchain.leaves().unwrap(), vec![block1]); + assert_eq!(1, backend.blockchain.leaves.read().highest_leaf().unwrap().0); + } + + #[test] + fn test_no_duplicated_leaves_allowed() { + let backend: Backend = Backend::new_test(10, 10); + let block0 = insert_header(&backend, 0, Default::default(), None, Default::default()); + let block1 = insert_header(&backend, 1, block0, None, Default::default()); + // Add block 2 not as the best block + let block2 = insert_header_no_head(&backend, 2, block1, Default::default()); + assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2]); + assert_eq!(backend.blockchain().info().best_hash, block1); + + // Add block 2 as the best block + let block2 = insert_header(&backend, 2, block1, None, Default::default()); + assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2]); + assert_eq!(backend.blockchain().info().best_hash, block2); + } } diff --git a/client/db/src/light.rs b/client/db/src/light.rs deleted file mode 100644 index bf2da5c61d058..0000000000000 --- a/client/db/src/light.rs +++ /dev/null @@ -1,1328 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! RocksDB-based light client blockchain storage. - -use parking_lot::RwLock; -use std::{collections::HashMap, convert::TryInto, sync::Arc}; - -use crate::{ - cache::{ComplexBlockId, DbCache, DbCacheSync, EntryType as CacheEntryType}, - utils::{self, block_id_to_lookup_key, meta_keys, read_db, read_meta, DatabaseType, Meta}, - DatabaseSettings, DbHash, FrozenForDuration, -}; -use codec::{Decode, Encode}; -use log::{debug, trace, warn}; -use sc_client_api::{ - backend::{AuxStore, NewBlockState, ProvideChtRoots}, - blockchain::{BlockStatus, Cache as BlockchainCache, Info as BlockchainInfo}, - cht, Storage, UsageInfo, -}; -use sp_blockchain::{ - well_known_cache_keys, CachedHeaderMetadata, Error as ClientError, - HeaderBackend as BlockchainHeaderBackend, HeaderMetadata, HeaderMetadataCache, - Result as ClientResult, -}; -use sp_database::{Database, Transaction}; -use sp_runtime::{ - generic::{BlockId, DigestItem}, - traits::{Block as BlockT, HashFor, Header as HeaderT, NumberFor, One, Zero}, -}; - -pub(crate) mod columns { - pub const META: u32 = crate::utils::COLUMN_META; - pub const KEY_LOOKUP: u32 = 1; - pub const HEADER: u32 = 2; - pub const CACHE: u32 = 3; - pub const CHT: u32 = 4; - pub const AUX: u32 = 5; -} - -/// Prefix for headers CHT. -const HEADER_CHT_PREFIX: u8 = 0; -/// Prefix for changes tries roots CHT. -const CHANGES_TRIE_CHT_PREFIX: u8 = 1; - -/// Light blockchain storage. Stores most recent headers + CHTs for older headers. -/// Locks order: meta, cache. -pub struct LightStorage { - db: Arc>, - meta: RwLock, Block::Hash>>, - cache: Arc>, - header_metadata_cache: Arc>, - io_stats: FrozenForDuration, -} - -impl LightStorage { - /// Create new storage with given settings. - pub fn new(config: DatabaseSettings) -> ClientResult { - let db = crate::utils::open_database::(&config, DatabaseType::Light)?; - Self::from_kvdb(db as Arc<_>) - } - - /// Create new memory-backed `LightStorage` for tests. - #[cfg(any(test, feature = "test-helpers"))] - pub fn new_test() -> Self { - let db = Arc::new(sp_database::MemDb::default()); - Self::from_kvdb(db as Arc<_>).expect("failed to create test-db") - } - - fn from_kvdb(db: Arc>) -> ClientResult { - let meta = read_meta::(&*db, columns::HEADER)?; - let header_metadata_cache = Arc::new(HeaderMetadataCache::default()); - let cache = DbCache::new( - db.clone(), - header_metadata_cache.clone(), - columns::KEY_LOOKUP, - columns::HEADER, - columns::CACHE, - meta.genesis_hash, - ComplexBlockId::new(meta.finalized_hash, meta.finalized_number), - ); - - Ok(LightStorage { - db, - meta: RwLock::new(meta), - cache: Arc::new(DbCacheSync(RwLock::new(cache))), - header_metadata_cache, - io_stats: FrozenForDuration::new(std::time::Duration::from_secs(1)), - }) - } - - #[cfg(test)] - pub(crate) fn cache(&self) -> &DbCacheSync { - &self.cache - } - - fn update_meta( - &self, - hash: Block::Hash, - number: NumberFor, - is_best: bool, - is_finalized: bool, - ) { - let mut meta = self.meta.write(); - - if number.is_zero() { - meta.genesis_hash = hash; - meta.finalized_hash = hash; - } - - if is_best { - meta.best_number = number; - meta.best_hash = hash; - } - - if is_finalized { - meta.finalized_number = number; - meta.finalized_hash = hash; - } - } -} - -impl BlockchainHeaderBackend for LightStorage -where - Block: BlockT, -{ - fn header(&self, id: BlockId) -> ClientResult> { - utils::read_header(&*self.db, columns::KEY_LOOKUP, columns::HEADER, id) - } - - fn info(&self) -> BlockchainInfo { - let meta = self.meta.read(); - BlockchainInfo { - best_hash: meta.best_hash, - best_number: meta.best_number, - genesis_hash: meta.genesis_hash.clone(), - finalized_hash: meta.finalized_hash, - finalized_number: meta.finalized_number, - finalized_state: if meta.finalized_hash != Default::default() { - Some((meta.genesis_hash, Zero::zero())) - } else { - None - }, - number_leaves: 1, - } - } - - fn status(&self, id: BlockId) -> ClientResult { - let exists = match id { - BlockId::Hash(_) => - read_db(&*self.db, columns::KEY_LOOKUP, columns::HEADER, id)?.is_some(), - BlockId::Number(n) => n <= self.meta.read().best_number, - }; - match exists { - true => Ok(BlockStatus::InChain), - false => Ok(BlockStatus::Unknown), - } - } - - fn number(&self, hash: Block::Hash) -> ClientResult>> { - if let Some(lookup_key) = - block_id_to_lookup_key::(&*self.db, columns::KEY_LOOKUP, BlockId::Hash(hash))? - { - let number = utils::lookup_key_to_number(&lookup_key)?; - Ok(Some(number)) - } else { - Ok(None) - } - } - - fn hash(&self, number: NumberFor) -> ClientResult> { - Ok(self.header(BlockId::Number(number))?.map(|header| header.hash().clone())) - } -} - -impl HeaderMetadata for LightStorage { - type Error = ClientError; - - fn header_metadata( - &self, - hash: Block::Hash, - ) -> Result, Self::Error> { - self.header_metadata_cache.header_metadata(hash).map_or_else( - || { - self.header(BlockId::hash(hash))? - .map(|header| { - let header_metadata = CachedHeaderMetadata::from(&header); - self.header_metadata_cache - .insert_header_metadata(header_metadata.hash, header_metadata.clone()); - header_metadata - }) - .ok_or_else(|| { - ClientError::UnknownBlock(format!("header not found in db: {}", hash)) - }) - }, - Ok, - ) - } - - fn insert_header_metadata(&self, hash: Block::Hash, metadata: CachedHeaderMetadata) { - self.header_metadata_cache.insert_header_metadata(hash, metadata) - } - - fn remove_header_metadata(&self, hash: Block::Hash) { - self.header_metadata_cache.remove_header_metadata(hash); - } -} - -impl LightStorage { - // Get block changes trie root, if available. - fn changes_trie_root(&self, block: BlockId) -> ClientResult> { - self.header(block).map(|header| { - header.and_then(|header| header.digest().log(DigestItem::as_changes_trie_root).cloned()) - }) - } - - /// Handle setting head within a transaction. `route_to` should be the last - /// block that existed in the database. `best_to` should be the best block - /// to be set. - /// - /// In the case where the new best block is a block to be imported, `route_to` - /// should be the parent of `best_to`. In the case where we set an existing block - /// to be best, `route_to` should equal to `best_to`. - fn set_head_with_transaction( - &self, - transaction: &mut Transaction, - route_to: Block::Hash, - best_to: (NumberFor, Block::Hash), - ) -> ClientResult<()> { - let lookup_key = utils::number_and_hash_to_lookup_key(best_to.0, &best_to.1)?; - - // handle reorg. - let meta = self.meta.read(); - if meta.best_hash != Default::default() { - let tree_route = sp_blockchain::tree_route(self, meta.best_hash, route_to)?; - - // update block number to hash lookup entries. - for retracted in tree_route.retracted() { - if retracted.hash == meta.finalized_hash { - // TODO: can we recover here? - warn!( - "Safety failure: reverting finalized block {:?}", - (&retracted.number, &retracted.hash) - ); - } - - utils::remove_number_to_key_mapping( - transaction, - columns::KEY_LOOKUP, - retracted.number, - )?; - } - - for enacted in tree_route.enacted() { - utils::insert_number_to_key_mapping( - transaction, - columns::KEY_LOOKUP, - enacted.number, - enacted.hash, - )?; - } - } - - transaction.set_from_vec(columns::META, meta_keys::BEST_BLOCK, lookup_key); - utils::insert_number_to_key_mapping( - transaction, - columns::KEY_LOOKUP, - best_to.0, - best_to.1, - )?; - - Ok(()) - } - - // Note that a block is finalized. Only call with child of last finalized block. - fn note_finalized( - &self, - transaction: &mut Transaction, - header: &Block::Header, - hash: Block::Hash, - ) -> ClientResult<()> { - let meta = self.meta.read(); - if &meta.finalized_hash != header.parent_hash() { - return Err(::sp_blockchain::Error::NonSequentialFinalization(format!( - "Last finalized {:?} not parent of {:?}", - meta.finalized_hash, hash - )) - .into()) - } - - let lookup_key = utils::number_and_hash_to_lookup_key(header.number().clone(), hash)?; - transaction.set_from_vec(columns::META, meta_keys::FINALIZED_BLOCK, lookup_key); - - // build new CHT(s) if required - if let Some(new_cht_number) = cht::is_build_required(cht::size(), *header.number()) { - let new_cht_start: NumberFor = cht::start_number(cht::size(), new_cht_number); - - let mut current_num = new_cht_start; - let cht_range = ::std::iter::from_fn(|| { - let old_current_num = current_num; - current_num = current_num + One::one(); - Some(old_current_num) - }); - - let new_header_cht_root = cht::compute_root::, _>( - cht::size(), - new_cht_number, - cht_range.map(|num| self.hash(num)), - )?; - transaction.set( - columns::CHT, - &cht_key(HEADER_CHT_PREFIX, new_cht_start)?, - new_header_cht_root.as_ref(), - ); - - // if the header includes changes trie root, let's build a changes tries roots CHT - if header.digest().log(DigestItem::as_changes_trie_root).is_some() { - let mut current_num = new_cht_start; - let cht_range = std::iter::from_fn(|| { - let old_current_num = current_num; - current_num = current_num + One::one(); - Some(old_current_num) - }); - let new_changes_trie_cht_root = - cht::compute_root::, _>( - cht::size(), - new_cht_number, - cht_range.map(|num| self.changes_trie_root(BlockId::Number(num))), - )?; - transaction.set( - columns::CHT, - &cht_key(CHANGES_TRIE_CHT_PREFIX, new_cht_start)?, - new_changes_trie_cht_root.as_ref(), - ); - } - - // prune headers that are replaced with CHT - let mut prune_block = new_cht_start; - let new_cht_end = cht::end_number(cht::size(), new_cht_number); - trace!(target: "db", "Replacing blocks [{}..{}] with CHT#{}", - new_cht_start, new_cht_end, new_cht_number); - - while prune_block <= new_cht_end { - if let Some(hash) = self.hash(prune_block)? { - let lookup_key = block_id_to_lookup_key::(&*self.db, columns::KEY_LOOKUP, BlockId::Number(prune_block))? - .expect("retrieved hash for `prune_block` right above. therefore retrieving lookup key must succeed. q.e.d."); - utils::remove_key_mappings( - transaction, - columns::KEY_LOOKUP, - prune_block, - hash, - )?; - transaction.remove(columns::HEADER, &lookup_key); - } - prune_block += One::one(); - } - } - - Ok(()) - } - - /// Read CHT root of given type for the block. - fn read_cht_root( - &self, - cht_type: u8, - cht_size: NumberFor, - block: NumberFor, - ) -> ClientResult> { - let no_cht_for_block = || ClientError::Backend(format!("Missing CHT for block {}", block)); - - let meta = self.meta.read(); - let max_cht_number = cht::max_cht_number(cht_size, meta.finalized_number); - let cht_number = cht::block_to_cht_number(cht_size, block).ok_or_else(no_cht_for_block)?; - match max_cht_number { - Some(max_cht_number) if cht_number <= max_cht_number => (), - _ => return Ok(None), - } - - let cht_start = cht::start_number(cht_size, cht_number); - self.db - .get(columns::CHT, &cht_key(cht_type, cht_start)?) - .ok_or_else(no_cht_for_block) - .and_then(|hash| Block::Hash::decode(&mut &*hash).map_err(|_| no_cht_for_block())) - .map(Some) - } -} - -impl AuxStore for LightStorage -where - Block: BlockT, -{ - fn insert_aux< - 'a, - 'b: 'a, - 'c: 'a, - I: IntoIterator, - D: IntoIterator, - >( - &self, - insert: I, - delete: D, - ) -> ClientResult<()> { - let mut transaction = Transaction::new(); - for (k, v) in insert { - transaction.set(columns::AUX, k, v); - } - for k in delete { - transaction.remove(columns::AUX, k); - } - self.db.commit(transaction)?; - - Ok(()) - } - - fn get_aux(&self, key: &[u8]) -> ClientResult>> { - Ok(self.db.get(columns::AUX, key)) - } -} - -impl Storage for LightStorage -where - Block: BlockT, -{ - fn import_header( - &self, - header: Block::Header, - mut cache_at: HashMap>, - leaf_state: NewBlockState, - aux_ops: Vec<(Vec, Option>)>, - ) -> ClientResult<()> { - let mut transaction = Transaction::new(); - - let hash = header.hash(); - let number = *header.number(); - let parent_hash = *header.parent_hash(); - - for (key, maybe_val) in aux_ops { - match maybe_val { - Some(val) => transaction.set_from_vec(columns::AUX, &key, val), - None => transaction.remove(columns::AUX, &key), - } - } - - // blocks are keyed by number + hash. - let lookup_key = utils::number_and_hash_to_lookup_key(number, &hash)?; - - if leaf_state.is_best() { - self.set_head_with_transaction(&mut transaction, parent_hash, (number, hash))?; - } - - utils::insert_hash_to_key_mapping(&mut transaction, columns::KEY_LOOKUP, number, hash)?; - transaction.set_from_vec(columns::HEADER, &lookup_key, header.encode()); - - let header_metadata = CachedHeaderMetadata::from(&header); - self.header_metadata_cache - .insert_header_metadata(header.hash().clone(), header_metadata); - - let is_genesis = number.is_zero(); - if is_genesis { - self.cache.0.write().set_genesis_hash(hash); - transaction.set(columns::META, meta_keys::GENESIS_HASH, hash.as_ref()); - } - - let finalized = match leaf_state { - _ if is_genesis => true, - NewBlockState::Final => true, - _ => false, - }; - - if finalized { - self.note_finalized(&mut transaction, &header, hash)?; - } - - // update changes trie configuration cache - if !cache_at.contains_key(&well_known_cache_keys::CHANGES_TRIE_CONFIG) { - if let Some(new_configuration) = - crate::changes_tries_storage::extract_new_configuration(&header) - { - cache_at - .insert(well_known_cache_keys::CHANGES_TRIE_CONFIG, new_configuration.encode()); - } - } - - { - let mut cache = self.cache.0.write(); - let cache_ops = cache - .transaction(&mut transaction) - .on_block_insert( - ComplexBlockId::new( - *header.parent_hash(), - if number.is_zero() { Zero::zero() } else { number - One::one() }, - ), - ComplexBlockId::new(hash, number), - cache_at, - if finalized { CacheEntryType::Final } else { CacheEntryType::NonFinal }, - )? - .into_ops(); - - debug!("Light DB Commit {:?} ({})", hash, number); - - self.db.commit(transaction)?; - cache.commit(cache_ops).expect( - "only fails if cache with given name isn't loaded yet; cache is already loaded \ - because there are cache_ops; qed", - ); - } - - self.update_meta(hash, number, leaf_state.is_best(), finalized); - - Ok(()) - } - - fn set_head(&self, id: BlockId) -> ClientResult<()> { - if let Some(header) = self.header(id)? { - let hash = header.hash(); - let number = header.number(); - - let mut transaction = Transaction::new(); - self.set_head_with_transaction( - &mut transaction, - hash.clone(), - (number.clone(), hash.clone()), - )?; - self.db.commit(transaction)?; - self.update_meta(hash, header.number().clone(), true, false); - - Ok(()) - } else { - Err(ClientError::UnknownBlock(format!("Cannot set head {:?}", id))) - } - } - - fn finalize_header(&self, id: BlockId) -> ClientResult<()> { - if let Some(header) = self.header(id)? { - let mut transaction = Transaction::new(); - let hash = header.hash(); - let number = *header.number(); - self.note_finalized(&mut transaction, &header, hash.clone())?; - { - let mut cache = self.cache.0.write(); - let cache_ops = cache - .transaction(&mut transaction) - .on_block_finalize( - ComplexBlockId::new( - *header.parent_hash(), - if number.is_zero() { Zero::zero() } else { number - One::one() }, - ), - ComplexBlockId::new(hash, number), - )? - .into_ops(); - - self.db.commit(transaction)?; - cache.commit(cache_ops).expect( - "only fails if cache with given name isn't loaded yet; cache is already loaded \ - because there are cache_ops; qed", - ); - } - self.update_meta(hash, header.number().clone(), false, true); - - Ok(()) - } else { - Err(ClientError::UnknownBlock(format!("Cannot finalize block {:?}", id))) - } - } - - fn last_finalized(&self) -> ClientResult { - Ok(self.meta.read().finalized_hash.clone()) - } - - fn cache(&self) -> Option>> { - Some(self.cache.clone()) - } - - fn usage_info(&self) -> Option { - use sc_client_api::{IoInfo, MemoryInfo, MemorySize}; - - // TODO: reimplement IO stats - let database_cache = MemorySize::from_bytes(0); - let io_stats = self.io_stats.take_or_else(|| kvdb::IoStats::empty()); - - Some(UsageInfo { - memory: MemoryInfo { - database_cache, - state_cache: Default::default(), - state_db: Default::default(), - }, - io: IoInfo { - transactions: io_stats.transactions, - bytes_read: io_stats.bytes_read, - bytes_written: io_stats.bytes_written, - writes: io_stats.writes, - reads: io_stats.reads, - average_transaction_size: io_stats.avg_transaction_size() as u64, - // Light client does not track those - state_reads: 0, - state_writes: 0, - state_reads_cache: 0, - state_writes_cache: 0, - state_writes_nodes: 0, - }, - }) - } -} - -impl ProvideChtRoots for LightStorage -where - Block: BlockT, -{ - fn header_cht_root( - &self, - cht_size: NumberFor, - block: NumberFor, - ) -> ClientResult> { - self.read_cht_root(HEADER_CHT_PREFIX, cht_size, block) - } - - fn changes_trie_cht_root( - &self, - cht_size: NumberFor, - block: NumberFor, - ) -> ClientResult> { - self.read_cht_root(CHANGES_TRIE_CHT_PREFIX, cht_size, block) - } -} - -/// Build the key for inserting header-CHT at given block. -fn cht_key>(cht_type: u8, block: N) -> ClientResult<[u8; 5]> { - let mut key = [cht_type; 5]; - key[1..].copy_from_slice(&utils::number_index_key(block)?); - Ok(key) -} - -#[cfg(test)] -pub(crate) mod tests { - use super::*; - use sc_client_api::cht; - use sp_blockchain::{lowest_common_ancestor, tree_route}; - use sp_core::ChangesTrieConfiguration; - use sp_runtime::{ - generic::{ChangesTrieSignal, DigestItem}, - testing::{Block as RawBlock, ExtrinsicWrapper, Header, H256 as Hash}, - }; - - type Block = RawBlock>; - type AuthorityId = sp_core::ed25519::Public; - - pub fn default_header(parent: &Hash, number: u64) -> Header { - Header { - number: number.into(), - parent_hash: *parent, - state_root: Hash::random(), - digest: Default::default(), - extrinsics_root: Default::default(), - } - } - - fn header_with_changes_trie(parent: &Hash, number: u64) -> Header { - let mut header = default_header(parent, number); - header - .digest - .logs - .push(DigestItem::ChangesTrieRoot([(number % 256) as u8; 32].into())); - header - } - - fn header_with_extrinsics_root(parent: &Hash, number: u64, extrinsics_root: Hash) -> Header { - let mut header = default_header(parent, number); - header.extrinsics_root = extrinsics_root; - header - } - - pub fn insert_block Header>( - db: &LightStorage, - cache: HashMap>, - mut header: F, - ) -> Hash { - let header = header(); - let hash = header.hash(); - db.import_header(header, cache, NewBlockState::Best, Vec::new()).unwrap(); - hash - } - - fn insert_final_block Header>( - db: &LightStorage, - cache: HashMap>, - header: F, - ) -> Hash { - let header = header(); - let hash = header.hash(); - db.import_header(header, cache, NewBlockState::Final, Vec::new()).unwrap(); - hash - } - - fn insert_non_best_block Header>( - db: &LightStorage, - cache: HashMap>, - header: F, - ) -> Hash { - let header = header(); - let hash = header.hash(); - db.import_header(header, cache, NewBlockState::Normal, Vec::new()).unwrap(); - hash - } - - #[test] - fn returns_known_header() { - let db = LightStorage::new_test(); - let known_hash = - insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0)); - let header_by_hash = db.header(BlockId::Hash(known_hash)).unwrap().unwrap(); - let header_by_number = db.header(BlockId::Number(0)).unwrap().unwrap(); - assert_eq!(header_by_hash, header_by_number); - } - - #[test] - fn does_not_return_unknown_header() { - let db = LightStorage::::new_test(); - assert!(db.header(BlockId::Hash(Hash::from_low_u64_be(1))).unwrap().is_none()); - assert!(db.header(BlockId::Number(0)).unwrap().is_none()); - } - - #[test] - fn returns_info() { - let db = LightStorage::new_test(); - let genesis_hash = - insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0)); - let info = db.info(); - assert_eq!(info.best_hash, genesis_hash); - assert_eq!(info.best_number, 0); - assert_eq!(info.genesis_hash, genesis_hash); - let best_hash = insert_block(&db, HashMap::new(), || default_header(&genesis_hash, 1)); - let info = db.info(); - assert_eq!(info.best_hash, best_hash); - assert_eq!(info.best_number, 1); - assert_eq!(info.genesis_hash, genesis_hash); - } - - #[test] - fn returns_block_status() { - let db = LightStorage::new_test(); - let genesis_hash = - insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0)); - assert_eq!(db.status(BlockId::Hash(genesis_hash)).unwrap(), BlockStatus::InChain); - assert_eq!(db.status(BlockId::Number(0)).unwrap(), BlockStatus::InChain); - assert_eq!( - db.status(BlockId::Hash(Hash::from_low_u64_be(1))).unwrap(), - BlockStatus::Unknown - ); - assert_eq!(db.status(BlockId::Number(1)).unwrap(), BlockStatus::Unknown); - } - - #[test] - fn returns_block_hash() { - let db = LightStorage::new_test(); - let genesis_hash = - insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0)); - assert_eq!(db.hash(0).unwrap(), Some(genesis_hash)); - assert_eq!(db.hash(1).unwrap(), None); - } - - #[test] - fn import_header_works() { - let raw_db = Arc::new(sp_database::MemDb::default()); - let db = LightStorage::from_kvdb(raw_db.clone()).unwrap(); - - let genesis_hash = - insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0)); - assert_eq!(raw_db.count(columns::HEADER), 1); - assert_eq!(raw_db.count(columns::KEY_LOOKUP), 2); - - let _ = insert_block(&db, HashMap::new(), || default_header(&genesis_hash, 1)); - assert_eq!(raw_db.count(columns::HEADER), 2); - assert_eq!(raw_db.count(columns::KEY_LOOKUP), 4); - } - - #[test] - fn finalized_ancient_headers_are_replaced_with_cht() { - fn insert_headers Header>( - header_producer: F, - ) -> (Arc, LightStorage) { - let raw_db = Arc::new(sp_database::MemDb::default()); - let db = LightStorage::from_kvdb(raw_db.clone()).unwrap(); - let cht_size: u64 = cht::size(); - let ucht_size: usize = cht_size as _; - - // insert genesis block header (never pruned) - let mut prev_hash = - insert_final_block(&db, HashMap::new(), || header_producer(&Default::default(), 0)); - - // insert SIZE blocks && ensure that nothing is pruned - - for number in 0..cht::size() { - prev_hash = - insert_block(&db, HashMap::new(), || header_producer(&prev_hash, 1 + number)); - } - assert_eq!(raw_db.count(columns::HEADER), 1 + ucht_size); - assert_eq!(raw_db.count(columns::CHT), 0); - - // insert next SIZE blocks && ensure that nothing is pruned - for number in 0..(cht_size as _) { - prev_hash = insert_block(&db, HashMap::new(), || { - header_producer(&prev_hash, 1 + cht_size + number) - }); - } - assert_eq!(raw_db.count(columns::HEADER), 1 + ucht_size + ucht_size); - assert_eq!(raw_db.count(columns::CHT), 0); - - // insert block #{2 * cht::size() + 1} && check that new CHT is created + headers of - // this CHT are pruned nothing is yet finalized, so nothing is pruned. - prev_hash = insert_block(&db, HashMap::new(), || { - header_producer(&prev_hash, 1 + cht_size + cht_size) - }); - assert_eq!(raw_db.count(columns::HEADER), 2 + ucht_size + ucht_size); - assert_eq!(raw_db.count(columns::CHT), 0); - - // now finalize the block. - for i in (0..(ucht_size + ucht_size)).map(|i| i + 1) { - db.finalize_header(BlockId::Number(i as _)).unwrap(); - } - db.finalize_header(BlockId::Hash(prev_hash)).unwrap(); - (raw_db, db) - } - - // when headers are created without changes tries roots - let (raw_db, db) = insert_headers(default_header); - let cht_size: u64 = cht::size(); - assert_eq!(raw_db.count(columns::HEADER), (1 + cht_size + 1) as usize); - assert_eq!(raw_db.count(columns::KEY_LOOKUP), (2 * (1 + cht_size + 1)) as usize); - assert_eq!(raw_db.count(columns::CHT), 1); - assert!((0..cht_size as _).all(|i| db.header(BlockId::Number(1 + i)).unwrap().is_none())); - assert!(db.header_cht_root(cht_size, cht_size / 2).unwrap().is_some()); - assert!(db.header_cht_root(cht_size, cht_size + cht_size / 2).unwrap().is_none()); - assert!(db.changes_trie_cht_root(cht_size, cht_size / 2).is_err()); - assert!(db.changes_trie_cht_root(cht_size, cht_size + cht_size / 2).unwrap().is_none()); - - // when headers are created with changes tries roots - let (raw_db, db) = insert_headers(header_with_changes_trie); - assert_eq!(raw_db.count(columns::HEADER), (1 + cht_size + 1) as usize); - assert_eq!(raw_db.count(columns::CHT), 2); - assert!((0..cht_size as _).all(|i| db.header(BlockId::Number(1 + i)).unwrap().is_none())); - assert!(db.header_cht_root(cht_size, cht_size / 2).unwrap().is_some()); - assert!(db.header_cht_root(cht_size, cht_size + cht_size / 2).unwrap().is_none()); - assert!(db.changes_trie_cht_root(cht_size, cht_size / 2).unwrap().is_some()); - assert!(db.changes_trie_cht_root(cht_size, cht_size + cht_size / 2).unwrap().is_none()); - } - - #[test] - fn get_cht_fails_for_genesis_block() { - assert!(LightStorage::::new_test().header_cht_root(cht::size(), 0).is_err()); - } - - #[test] - fn get_cht_fails_for_non_existent_cht() { - let cht_size: u64 = cht::size(); - assert!(LightStorage::::new_test() - .header_cht_root(cht_size, cht_size / 2) - .unwrap() - .is_none()); - } - - #[test] - fn get_cht_works() { - let db = LightStorage::new_test(); - - // insert 1 + SIZE + SIZE + 1 blocks so that CHT#0 is created - let mut prev_hash = insert_final_block(&db, HashMap::new(), || { - header_with_changes_trie(&Default::default(), 0) - }); - let cht_size: u64 = cht::size(); - let ucht_size: usize = cht_size as _; - for i in 1..1 + ucht_size + ucht_size + 1 { - prev_hash = insert_block(&db, HashMap::new(), || { - header_with_changes_trie(&prev_hash, i as u64) - }); - db.finalize_header(BlockId::Hash(prev_hash)).unwrap(); - } - - let cht_root_1 = - db.header_cht_root(cht_size, cht::start_number(cht_size, 0)).unwrap().unwrap(); - let cht_root_2 = db - .header_cht_root(cht_size, cht::start_number(cht_size, 0) + cht_size / 2) - .unwrap() - .unwrap(); - let cht_root_3 = - db.header_cht_root(cht_size, cht::end_number(cht_size, 0)).unwrap().unwrap(); - assert_eq!(cht_root_1, cht_root_2); - assert_eq!(cht_root_2, cht_root_3); - - let cht_root_1 = db - .changes_trie_cht_root(cht_size, cht::start_number(cht_size, 0)) - .unwrap() - .unwrap(); - let cht_root_2 = db - .changes_trie_cht_root(cht_size, cht::start_number(cht_size, 0) + cht_size / 2) - .unwrap() - .unwrap(); - let cht_root_3 = db - .changes_trie_cht_root(cht_size, cht::end_number(cht_size, 0)) - .unwrap() - .unwrap(); - assert_eq!(cht_root_1, cht_root_2); - assert_eq!(cht_root_2, cht_root_3); - } - - #[test] - fn tree_route_works() { - let db = LightStorage::new_test(); - let block0 = insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0)); - - // fork from genesis: 3 prong. - let a1 = insert_block(&db, HashMap::new(), || default_header(&block0, 1)); - let a2 = insert_block(&db, HashMap::new(), || default_header(&a1, 2)); - let a3 = insert_block(&db, HashMap::new(), || default_header(&a2, 3)); - - // fork from genesis: 2 prong. - let b1 = insert_block(&db, HashMap::new(), || { - header_with_extrinsics_root(&block0, 1, Hash::from([1; 32])) - }); - let b2 = insert_block(&db, HashMap::new(), || default_header(&b1, 2)); - - { - let tree_route = tree_route(&db, a3, b2).unwrap(); - - assert_eq!(tree_route.common_block().hash, block0); - assert_eq!( - tree_route.retracted().iter().map(|r| r.hash).collect::>(), - vec![a3, a2, a1] - ); - assert_eq!( - tree_route.enacted().iter().map(|r| r.hash).collect::>(), - vec![b1, b2] - ); - } - - { - let tree_route = tree_route(&db, a1, a3).unwrap(); - - assert_eq!(tree_route.common_block().hash, a1); - assert!(tree_route.retracted().is_empty()); - assert_eq!( - tree_route.enacted().iter().map(|r| r.hash).collect::>(), - vec![a2, a3] - ); - } - - { - let tree_route = tree_route(&db, a3, a1).unwrap(); - - assert_eq!(tree_route.common_block().hash, a1); - assert_eq!( - tree_route.retracted().iter().map(|r| r.hash).collect::>(), - vec![a3, a2] - ); - assert!(tree_route.enacted().is_empty()); - } - - { - let tree_route = tree_route(&db, a2, a2).unwrap(); - - assert_eq!(tree_route.common_block().hash, a2); - assert!(tree_route.retracted().is_empty()); - assert!(tree_route.enacted().is_empty()); - } - } - - #[test] - fn lowest_common_ancestor_works() { - let db = LightStorage::new_test(); - let block0 = insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0)); - - // fork from genesis: 3 prong. - let a1 = insert_block(&db, HashMap::new(), || default_header(&block0, 1)); - let a2 = insert_block(&db, HashMap::new(), || default_header(&a1, 2)); - let a3 = insert_block(&db, HashMap::new(), || default_header(&a2, 3)); - - // fork from genesis: 2 prong. - let b1 = insert_block(&db, HashMap::new(), || { - header_with_extrinsics_root(&block0, 1, Hash::from([1; 32])) - }); - let b2 = insert_block(&db, HashMap::new(), || default_header(&b1, 2)); - - { - let lca = lowest_common_ancestor(&db, a3, b2).unwrap(); - - assert_eq!(lca.hash, block0); - assert_eq!(lca.number, 0); - } - - { - let lca = lowest_common_ancestor(&db, a1, a3).unwrap(); - - assert_eq!(lca.hash, a1); - assert_eq!(lca.number, 1); - } - - { - let lca = lowest_common_ancestor(&db, a3, a1).unwrap(); - - assert_eq!(lca.hash, a1); - assert_eq!(lca.number, 1); - } - - { - let lca = lowest_common_ancestor(&db, a2, a3).unwrap(); - - assert_eq!(lca.hash, a2); - assert_eq!(lca.number, 2); - } - - { - let lca = lowest_common_ancestor(&db, a2, a1).unwrap(); - - assert_eq!(lca.hash, a1); - assert_eq!(lca.number, 1); - } - - { - let lca = lowest_common_ancestor(&db, a2, a2).unwrap(); - - assert_eq!(lca.hash, a2); - assert_eq!(lca.number, 2); - } - } - - #[test] - fn authorities_are_cached() { - let db = LightStorage::new_test(); - - fn run_checks( - db: &LightStorage, - max: u64, - checks: &[(u64, Option>)], - ) { - for (at, expected) in checks.iter().take_while(|(at, _)| *at <= max) { - let actual = authorities(db.cache(), BlockId::Number(*at)); - assert_eq!(*expected, actual); - } - } - - fn same_authorities() -> HashMap> { - HashMap::new() - } - - fn make_authorities( - authorities: Vec, - ) -> HashMap> { - let mut map = HashMap::new(); - map.insert(well_known_cache_keys::AUTHORITIES, authorities.encode()); - map - } - - fn authorities( - cache: &dyn BlockchainCache, - at: BlockId, - ) -> Option> { - cache - .get_at(&well_known_cache_keys::AUTHORITIES, &at) - .unwrap_or(None) - .and_then(|(_, _, val)| Decode::decode(&mut &val[..]).ok()) - } - - let auth1 = || AuthorityId::from_raw([1u8; 32]); - let auth2 = || AuthorityId::from_raw([2u8; 32]); - let auth3 = || AuthorityId::from_raw([3u8; 32]); - let auth4 = || AuthorityId::from_raw([4u8; 32]); - let auth5 = || AuthorityId::from_raw([5u8; 32]); - let auth6 = || AuthorityId::from_raw([6u8; 32]); - - let (hash2, hash6) = { - // first few blocks are instantly finalized - // B0(None) -> B1(None) -> B2(1) -> B3(1) -> B4(1, 2) -> B5(1, 2) -> B6(1, 2) - let checks = vec![ - (0, None), - (1, None), - (2, Some(vec![auth1()])), - (3, Some(vec![auth1()])), - (4, Some(vec![auth1(), auth2()])), - (5, Some(vec![auth1(), auth2()])), - (6, Some(vec![auth1(), auth2()])), - ]; - - let hash0 = insert_final_block(&db, same_authorities(), || { - default_header(&Default::default(), 0) - }); - run_checks(&db, 0, &checks); - let hash1 = insert_final_block(&db, same_authorities(), || default_header(&hash0, 1)); - run_checks(&db, 1, &checks); - let hash2 = insert_final_block(&db, make_authorities(vec![auth1()]), || { - default_header(&hash1, 2) - }); - run_checks(&db, 2, &checks); - let hash3 = insert_final_block(&db, make_authorities(vec![auth1()]), || { - default_header(&hash2, 3) - }); - run_checks(&db, 3, &checks); - let hash4 = insert_final_block(&db, make_authorities(vec![auth1(), auth2()]), || { - default_header(&hash3, 4) - }); - run_checks(&db, 4, &checks); - let hash5 = insert_final_block(&db, make_authorities(vec![auth1(), auth2()]), || { - default_header(&hash4, 5) - }); - run_checks(&db, 5, &checks); - let hash6 = insert_final_block(&db, same_authorities(), || default_header(&hash5, 6)); - run_checks(&db, 6, &checks); - - (hash2, hash6) - }; - - { - // some older non-best blocks are inserted - // ... -> B2(1) -> B2_1(1) -> B2_2(2) - // => the cache ignores all writes before best finalized block - let hash2_1 = insert_non_best_block(&db, make_authorities(vec![auth1()]), || { - default_header(&hash2, 3) - }); - assert_eq!(None, authorities(db.cache(), BlockId::Hash(hash2_1))); - let hash2_2 = - insert_non_best_block(&db, make_authorities(vec![auth1(), auth2()]), || { - default_header(&hash2_1, 4) - }); - assert_eq!(None, authorities(db.cache(), BlockId::Hash(hash2_2))); - } - - let (hash7, hash8, hash6_1, hash6_2, hash6_1_1, hash6_1_2) = { - // inserting non-finalized blocks - // B6(None) -> B7(3) -> B8(3) - // \> B6_1(4) -> B6_2(4) - // \> B6_1_1(5) - // \> B6_1_2(6) -> B6_1_3(7) - - let hash7 = - insert_block(&db, make_authorities(vec![auth3()]), || default_header(&hash6, 7)); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6)), Some(vec![auth1(), auth2()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash7)), Some(vec![auth3()])); - let hash8 = - insert_block(&db, make_authorities(vec![auth3()]), || default_header(&hash7, 8)); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6)), Some(vec![auth1(), auth2()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash7)), Some(vec![auth3()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash8)), Some(vec![auth3()])); - let hash6_1 = - insert_block(&db, make_authorities(vec![auth4()]), || default_header(&hash6, 7)); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6)), Some(vec![auth1(), auth2()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash7)), Some(vec![auth3()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash8)), Some(vec![auth3()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1)), Some(vec![auth4()])); - let hash6_1_1 = insert_non_best_block(&db, make_authorities(vec![auth5()]), || { - default_header(&hash6_1, 8) - }); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6)), Some(vec![auth1(), auth2()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash7)), Some(vec![auth3()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash8)), Some(vec![auth3()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1)), Some(vec![auth4()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1_1)), Some(vec![auth5()])); - let hash6_1_2 = insert_non_best_block(&db, make_authorities(vec![auth6()]), || { - default_header(&hash6_1, 8) - }); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6)), Some(vec![auth1(), auth2()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash7)), Some(vec![auth3()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash8)), Some(vec![auth3()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1)), Some(vec![auth4()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1_1)), Some(vec![auth5()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1_2)), Some(vec![auth6()])); - let hash6_2 = - insert_block(&db, make_authorities(vec![auth4()]), || default_header(&hash6_1, 8)); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6)), Some(vec![auth1(), auth2()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash7)), Some(vec![auth3()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash8)), Some(vec![auth3()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1)), Some(vec![auth4()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1_1)), Some(vec![auth5()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1_2)), Some(vec![auth6()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_2)), Some(vec![auth4()])); - - (hash7, hash8, hash6_1, hash6_2, hash6_1_1, hash6_1_2) - }; - - { - // finalize block hash6_1 - db.finalize_header(BlockId::Hash(hash6_1)).unwrap(); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6)), Some(vec![auth1(), auth2()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash7)), None); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash8)), None); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1)), Some(vec![auth4()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1_1)), Some(vec![auth5()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1_2)), Some(vec![auth6()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_2)), Some(vec![auth4()])); - // finalize block hash6_2 - db.finalize_header(BlockId::Hash(hash6_2)).unwrap(); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6)), Some(vec![auth1(), auth2()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash7)), None); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash8)), None); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1)), Some(vec![auth4()])); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1_1)), None); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_1_2)), None); - assert_eq!(authorities(db.cache(), BlockId::Hash(hash6_2)), Some(vec![auth4()])); - } - } - - #[test] - fn database_is_reopened() { - let db = LightStorage::new_test(); - let hash0 = - insert_final_block(&db, HashMap::new(), || default_header(&Default::default(), 0)); - assert_eq!(db.info().best_hash, hash0); - assert_eq!(db.header(BlockId::Hash(hash0)).unwrap().unwrap().hash(), hash0); - - let db = db.db; - let db = LightStorage::from_kvdb(db).unwrap(); - assert_eq!(db.info().best_hash, hash0); - assert_eq!(db.header(BlockId::Hash::(hash0)).unwrap().unwrap().hash(), hash0); - } - - #[test] - fn aux_store_works() { - let db = LightStorage::::new_test(); - - // insert aux1 + aux2 using direct store access - db.insert_aux(&[(&[1][..], &[101][..]), (&[2][..], &[102][..])], ::std::iter::empty()) - .unwrap(); - - // check aux values - assert_eq!(db.get_aux(&[1]).unwrap(), Some(vec![101])); - assert_eq!(db.get_aux(&[2]).unwrap(), Some(vec![102])); - assert_eq!(db.get_aux(&[3]).unwrap(), None); - - // delete aux1 + insert aux3 using import operation - db.import_header( - default_header(&Default::default(), 0), - HashMap::new(), - NewBlockState::Best, - vec![(vec![3], Some(vec![103])), (vec![1], None)], - ) - .unwrap(); - - // check aux values - assert_eq!(db.get_aux(&[1]).unwrap(), None); - assert_eq!(db.get_aux(&[2]).unwrap(), Some(vec![102])); - assert_eq!(db.get_aux(&[3]).unwrap(), Some(vec![103])); - } - - #[test] - fn cache_can_be_initialized_after_genesis_inserted() { - let (genesis_hash, storage) = { - let db = LightStorage::::new_test(); - - // before cache is initialized => Err - assert!(db.cache().get_at(b"test", &BlockId::Number(0)).is_err()); - - // insert genesis block (no value for cache is provided) - let mut genesis_hash = None; - insert_block(&db, HashMap::new(), || { - let header = default_header(&Default::default(), 0); - genesis_hash = Some(header.hash()); - header - }); - - // after genesis is inserted => None - assert_eq!(db.cache().get_at(b"test", &BlockId::Number(0)).unwrap(), None); - - // initialize cache - db.cache().initialize(b"test", vec![42]).unwrap(); - - // after genesis is inserted + cache is initialized => Some - assert_eq!( - db.cache().get_at(b"test", &BlockId::Number(0)).unwrap(), - Some(((0, genesis_hash.unwrap()), None, vec![42])), - ); - - (genesis_hash, db.db) - }; - - // restart && check that after restart value is read from the cache - let db = - LightStorage::::from_kvdb(storage as Arc<_>).expect("failed to create test-db"); - assert_eq!( - db.cache().get_at(b"test", &BlockId::Number(0)).unwrap(), - Some(((0, genesis_hash.unwrap()), None, vec![42])), - ); - } - - #[test] - fn changes_trie_configuration_is_tracked_on_light_client() { - let db = LightStorage::::new_test(); - - let new_config = Some(ChangesTrieConfiguration::new(2, 2)); - - // insert block#0 && block#1 (no value for cache is provided) - let hash0 = insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0)); - assert_eq!( - db.cache() - .get_at(&well_known_cache_keys::CHANGES_TRIE_CONFIG, &BlockId::Number(0)) - .unwrap() - .map(|(_, _, v)| ChangesTrieConfiguration::decode(&mut &v[..]).unwrap()), - None, - ); - - // insert configuration at block#1 (starts from block#2) - insert_block(&db, HashMap::new(), || { - let mut header = default_header(&hash0, 1); - header.digest_mut().push(DigestItem::ChangesTrieSignal( - ChangesTrieSignal::NewConfiguration(new_config.clone()), - )); - header - }); - assert_eq!( - db.cache() - .get_at(&well_known_cache_keys::CHANGES_TRIE_CONFIG, &BlockId::Number(1)) - .unwrap() - .map(|(_, _, v)| Option::::decode(&mut &v[..]).unwrap()), - Some(new_config), - ); - } -} diff --git a/client/db/src/offchain.rs b/client/db/src/offchain.rs index c31273ff07c63..030a410981b26 100644 --- a/client/db/src/offchain.rs +++ b/client/db/src/offchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -92,7 +92,7 @@ impl sp_core::offchain::OffchainStorage for LocalStorage { { let _key_guard = key_lock.lock(); let val = self.db.get(columns::OFFCHAIN, &key); - is_set = val.as_ref().map(|x| &**x) == old_value; + is_set = val.as_deref() == old_value; if is_set { self.set(prefix, item_key, new_value) @@ -104,7 +104,7 @@ impl sp_core::offchain::OffchainStorage for LocalStorage { { drop(key_lock); let key_lock = locks.get_mut(&key); - if let Some(_) = key_lock.and_then(Arc::get_mut) { + if key_lock.and_then(Arc::get_mut).is_some() { locks.remove(&key); } } @@ -114,7 +114,7 @@ impl sp_core::offchain::OffchainStorage for LocalStorage { /// Concatenate the prefix and key to create an offchain key in the db. pub(crate) fn concatenate_prefix_and_key(prefix: &[u8], key: &[u8]) -> Vec { - prefix.iter().chain(key.into_iter()).cloned().collect() + prefix.iter().chain(key.iter()).cloned().collect() } #[cfg(test)] diff --git a/client/db/src/parity_db.rs b/client/db/src/parity_db.rs index 1b645ca9fb2b9..4adacbf6f041c 100644 --- a/client/db/src/parity_db.rs +++ b/client/db/src/parity_db.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . use crate::{ - columns, light, + columns, utils::{DatabaseType, NUM_COLUMNS}, }; /// A `Database` adapter for parity-db. @@ -38,20 +38,22 @@ pub fn open>( path: &std::path::Path, db_type: DatabaseType, create: bool, + upgrade: bool, ) -> parity_db::Result>> { let mut config = parity_db::Options::with_columns(path, NUM_COLUMNS as u8); match db_type { DatabaseType::Full => { - let indexes = [ + let compressed = [ columns::STATE, columns::HEADER, columns::BODY, + columns::BODY_INDEX, columns::TRANSACTION, columns::JUSTIFICATIONS, ]; - for i in indexes { + for i in compressed { let mut column = &mut config.columns[i as usize]; column.compression = parity_db::CompressionType::Lz4; } @@ -60,13 +62,21 @@ pub fn open>( state_col.ref_counted = true; state_col.preimage = true; state_col.uniform = true; - }, - DatabaseType::Light => { - config.columns[light::columns::HEADER as usize].compression = - parity_db::CompressionType::Lz4; + + let mut tx_col = &mut config.columns[columns::TRANSACTION as usize]; + tx_col.ref_counted = true; + tx_col.preimage = true; + tx_col.uniform = true; }, } + if upgrade { + log::info!("Upgrading database metadata."); + if let Some(meta) = parity_db::Options::load_metadata(path)? { + config.write_metadata_with_version(path, &meta.salt, Some(meta.version))?; + } + } + let db = if create { parity_db::Db::open_or_create(&config)? } else { @@ -76,15 +86,58 @@ pub fn open>( Ok(std::sync::Arc::new(DbAdapter(db))) } +fn ref_counted_column(col: u32) -> bool { + col == columns::TRANSACTION || col == columns::STATE +} + impl> Database for DbAdapter { fn commit(&self, transaction: Transaction) -> Result<(), DatabaseError> { - handle_err(self.0.commit(transaction.0.into_iter().map(|change| match change { - Change::Set(col, key, value) => (col as u8, key, Some(value)), - Change::Remove(col, key) => (col as u8, key, None), - _ => unimplemented!(), - }))); - - Ok(()) + let mut not_ref_counted_column = Vec::new(); + let result = self.0.commit(transaction.0.into_iter().filter_map(|change| { + Some(match change { + Change::Set(col, key, value) => (col as u8, key, Some(value)), + Change::Remove(col, key) => (col as u8, key, None), + Change::Store(col, key, value) => + if ref_counted_column(col) { + (col as u8, key.as_ref().to_vec(), Some(value)) + } else { + if !not_ref_counted_column.contains(&col) { + not_ref_counted_column.push(col); + } + return None + }, + Change::Reference(col, key) => { + if ref_counted_column(col) { + // FIXME accessing value is not strictly needed, optimize this in parity-db. + let value = >::get(self, col, key.as_ref()); + (col as u8, key.as_ref().to_vec(), value) + } else { + if !not_ref_counted_column.contains(&col) { + not_ref_counted_column.push(col); + } + return None + } + }, + Change::Release(col, key) => + if ref_counted_column(col) { + (col as u8, key.as_ref().to_vec(), None) + } else { + if !not_ref_counted_column.contains(&col) { + not_ref_counted_column.push(col); + } + return None + }, + }) + })); + + if not_ref_counted_column.len() > 0 { + return Err(DatabaseError(Box::new(parity_db::Error::InvalidInput(format!( + "Ref counted operation on non ref counted columns {:?}", + not_ref_counted_column + ))))) + } + + result.map_err(|e| DatabaseError(Box::new(e))) } fn get(&self, col: ColumnId, key: &[u8]) -> Option> { @@ -102,4 +155,8 @@ impl> Database for DbAdapter { fn supports_ref_counting(&self) -> bool { true } + + fn sanitize_key(&self, key: &mut Vec) { + let _prefix = key.drain(0..key.len() - crate::DB_HASH_LEN); + } } diff --git a/client/db/src/record_stats_state.rs b/client/db/src/record_stats_state.rs new file mode 100644 index 0000000000000..0b51d3fef2127 --- /dev/null +++ b/client/db/src/record_stats_state.rs @@ -0,0 +1,230 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Provides [`RecordStatsState`] for recording stats about state access. + +use crate::stats::StateUsageStats; +use sp_core::storage::ChildInfo; +use sp_runtime::{ + traits::{Block as BlockT, HashFor}, + StateVersion, +}; +use sp_state_machine::{ + backend::{AsTrieBackend, Backend as StateBackend}, + TrieBackend, +}; +use std::sync::Arc; + +/// State abstraction for recording stats about state access. +pub struct RecordStatsState { + /// Usage statistics + usage: StateUsageStats, + /// State machine registered stats + overlay_stats: sp_state_machine::StateMachineStats, + /// Backing state. + state: S, + /// The hash of the block is state belongs to. + block_hash: Option, + /// The usage statistics of the backend. These will be updated on drop. + state_usage: Arc, +} + +impl std::fmt::Debug for RecordStatsState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Block {:?}", self.block_hash) + } +} + +impl Drop for RecordStatsState { + fn drop(&mut self) { + self.state_usage.merge_sm(self.usage.take()); + } +} + +impl>, B: BlockT> RecordStatsState { + /// Create a new instance wrapping generic State and shared cache. + pub(crate) fn new( + state: S, + block_hash: Option, + state_usage: Arc, + ) -> Self { + RecordStatsState { + usage: StateUsageStats::new(), + overlay_stats: sp_state_machine::StateMachineStats::default(), + state, + block_hash, + state_usage, + } + } +} + +impl>, B: BlockT> StateBackend> for RecordStatsState { + type Error = S::Error; + type Transaction = S::Transaction; + type TrieBackendStorage = S::TrieBackendStorage; + + fn storage(&self, key: &[u8]) -> Result>, Self::Error> { + let value = self.state.storage(key)?; + self.usage.tally_key_read(key, value.as_ref(), false); + Ok(value) + } + + fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { + self.state.storage_hash(key) + } + + fn child_storage( + &self, + child_info: &ChildInfo, + key: &[u8], + ) -> Result>, Self::Error> { + let key = (child_info.storage_key().to_vec(), key.to_vec()); + let value = self.state.child_storage(child_info, &key.1)?; + + // just pass it through the usage counter + let value = self.usage.tally_child_key_read(&key, value, false); + + Ok(value) + } + + fn child_storage_hash( + &self, + child_info: &ChildInfo, + key: &[u8], + ) -> Result, Self::Error> { + self.state.child_storage_hash(child_info, key) + } + + fn exists_storage(&self, key: &[u8]) -> Result { + self.state.exists_storage(key) + } + + fn exists_child_storage( + &self, + child_info: &ChildInfo, + key: &[u8], + ) -> Result { + self.state.exists_child_storage(child_info, key) + } + + fn apply_to_key_values_while, Vec) -> bool>( + &self, + child_info: Option<&ChildInfo>, + prefix: Option<&[u8]>, + start_at: Option<&[u8]>, + f: F, + allow_missing: bool, + ) -> Result { + self.state + .apply_to_key_values_while(child_info, prefix, start_at, f, allow_missing) + } + + fn apply_to_keys_while bool>( + &self, + child_info: Option<&ChildInfo>, + prefix: Option<&[u8]>, + start_at: Option<&[u8]>, + f: F, + ) { + self.state.apply_to_keys_while(child_info, prefix, start_at, f) + } + + fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { + self.state.next_storage_key(key) + } + + fn next_child_storage_key( + &self, + child_info: &ChildInfo, + key: &[u8], + ) -> Result>, Self::Error> { + self.state.next_child_storage_key(child_info, key) + } + + fn for_keys_with_prefix(&self, prefix: &[u8], f: F) { + self.state.for_keys_with_prefix(prefix, f) + } + + fn for_key_values_with_prefix(&self, prefix: &[u8], f: F) { + self.state.for_key_values_with_prefix(prefix, f) + } + + fn for_child_keys_with_prefix( + &self, + child_info: &ChildInfo, + prefix: &[u8], + f: F, + ) { + self.state.for_child_keys_with_prefix(child_info, prefix, f) + } + + fn storage_root<'a>( + &self, + delta: impl Iterator)>, + state_version: StateVersion, + ) -> (B::Hash, Self::Transaction) + where + B::Hash: Ord, + { + self.state.storage_root(delta, state_version) + } + + fn child_storage_root<'a>( + &self, + child_info: &ChildInfo, + delta: impl Iterator)>, + state_version: StateVersion, + ) -> (B::Hash, bool, Self::Transaction) + where + B::Hash: Ord, + { + self.state.child_storage_root(child_info, delta, state_version) + } + + fn pairs(&self) -> Vec<(Vec, Vec)> { + self.state.pairs() + } + + fn keys(&self, prefix: &[u8]) -> Vec> { + self.state.keys(prefix) + } + + fn child_keys(&self, child_info: &ChildInfo, prefix: &[u8]) -> Vec> { + self.state.child_keys(child_info, prefix) + } + + fn register_overlay_stats(&self, stats: &sp_state_machine::StateMachineStats) { + self.overlay_stats.add(stats); + } + + fn usage_info(&self) -> sp_state_machine::UsageInfo { + let mut info = self.usage.take(); + info.include_state_machine_states(&self.overlay_stats); + info + } +} + +impl> + AsTrieBackend>, B: BlockT> AsTrieBackend> + for RecordStatsState +{ + type TrieBackendStorage = >>::TrieBackendStorage; + + fn as_trie_backend(&self) -> &TrieBackend> { + self.state.as_trie_backend() + } +} diff --git a/client/db/src/stats.rs b/client/db/src/stats.rs index 9223142ef5aba..f6c14568236e9 100644 --- a/client/db/src/stats.rs +++ b/client/db/src/stats.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/db/src/storage_cache.rs b/client/db/src/storage_cache.rs index a895324a2e7b9..474599e5d74e8 100644 --- a/client/db/src/storage_cache.rs +++ b/client/db/src/storage_cache.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -26,7 +26,10 @@ use linked_hash_map::{Entry, LinkedHashMap}; use log::trace; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use sp_core::{hexdisplay::HexDisplay, storage::ChildInfo}; -use sp_runtime::traits::{Block as BlockT, HashFor, Header, NumberFor}; +use sp_runtime::{ + traits::{Block as BlockT, HashFor, Header, NumberFor}, + StateVersion, +}; use sp_state_machine::{ backend::Backend as StateBackend, ChildStorageCollection, StorageCollection, StorageKey, StorageValue, TrieBackend, @@ -56,15 +59,11 @@ pub struct Cache { struct LRUMap(LinkedHashMap, usize, usize); -/// Internal trait similar to `heapsize` but using -/// a simply estimation. +/// Internal trait similar to `heapsize` but using a simple estimation. /// -/// This should not be made public, it is implementation -/// detail trait. If it need to become public please -/// consider using `malloc_size_of`. +/// This should not be made public, it is an implementation detail trait. trait EstimateSize { - /// Return a size estimation of additional size needed - /// to cache this struct (in bytes). + /// Return a size estimation of the additional size needed to cache this struct (in bytes). fn estimate_size(&self) -> usize; } @@ -357,9 +356,9 @@ impl CacheChanges { // Same block comitted twice with different state changes. // Treat it as reenacted/retracted. if is_best { - enacted.push(commit_hash.clone()); + enacted.push(*commit_hash); } else { - retracted.to_mut().push(commit_hash.clone()); + retracted.to_mut().push(*commit_hash); } } } @@ -368,7 +367,7 @@ impl CacheChanges { // Propagate cache only if committing on top of the latest canonical state // blocks are ordered by number and only one block with a given number is marked as // canonical (contributed to canonical state cache) - if let Some(_) = self.parent_hash { + if self.parent_hash.is_some() { let mut local_cache = self.local_cache.write(); if is_best { trace!( @@ -420,9 +419,9 @@ impl CacheChanges { storage: modifications, child_storage: child_modifications, number: *number, - hash: hash.clone(), + hash: *hash, is_canon: is_best, - parent: parent.clone(), + parent: *parent, }; let insert_at = cache .modifications @@ -561,7 +560,7 @@ impl>, B: BlockT> StateBackend> for Cachin let cache = self.cache.shared_cache.upgradable_read(); if Self::is_allowed(Some(key), None, &self.cache.parent_hash, &cache.modifications) { let mut cache = RwLockUpgradableReadGuard::upgrade(cache); - if let Some(entry) = cache.lru_hashes.get(key).map(|a| a.0.clone()) { + if let Some(entry) = cache.lru_hashes.get(key).map(|a| a.0) { trace!("Found hash in shared cache: {:?}", HexDisplay::from(&key)); return Ok(entry) } @@ -636,9 +635,10 @@ impl>, B: BlockT> StateBackend> for Cachin &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { - self.state.apply_to_keys_while(child_info, prefix, f) + self.state.apply_to_keys_while(child_info, prefix, start_at, f) } fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { @@ -673,22 +673,24 @@ impl>, B: BlockT> StateBackend> for Cachin fn storage_root<'a>( &self, delta: impl Iterator)>, + state_version: StateVersion, ) -> (B::Hash, Self::Transaction) where B::Hash: Ord, { - self.state.storage_root(delta) + self.state.storage_root(delta, state_version) } fn child_storage_root<'a>( &self, child_info: &ChildInfo, delta: impl Iterator)>, + state_version: StateVersion, ) -> (B::Hash, bool, Self::Transaction) where B::Hash: Ord, { - self.state.child_storage_root(child_info, delta) + self.state.child_storage_root(child_info, delta, state_version) } fn pairs(&self) -> Vec<(Vec, Vec)> { @@ -834,9 +836,10 @@ impl>, B: BlockT> StateBackend> &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { - self.caching_state().apply_to_keys_while(child_info, prefix, f) + self.caching_state().apply_to_keys_while(child_info, prefix, start_at, f) } fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { @@ -871,22 +874,24 @@ impl>, B: BlockT> StateBackend> fn storage_root<'a>( &self, delta: impl Iterator)>, + state_version: StateVersion, ) -> (B::Hash, Self::Transaction) where B::Hash: Ord, { - self.caching_state().storage_root(delta) + self.caching_state().storage_root(delta, state_version) } fn child_storage_root<'a>( &self, child_info: &ChildInfo, delta: impl Iterator)>, + state_version: StateVersion, ) -> (B::Hash, bool, Self::Transaction) where B::Hash: Ord, { - self.caching_state().child_storage_root(child_info, delta) + self.caching_state().child_storage_root(child_info, delta, state_version) } fn pairs(&self) -> Vec<(Vec, Vec)> { @@ -927,7 +932,7 @@ impl Drop for SyncingCachingState { let _lock = self.lock.read(); self.state_usage.merge_sm(caching_state.usage.take()); - if let Some(hash) = caching_state.cache.parent_hash.clone() { + if let Some(hash) = caching_state.cache.parent_hash { let is_best = self.meta.read().best_hash == hash; caching_state.cache.sync_cache(&[], &[], vec![], vec![], None, None, is_best); } @@ -1182,7 +1187,10 @@ mod tests { let shared = new_shared_cache::(256 * 1024, (0, 1)); let mut backend = InMemoryBackend::::default(); - backend.insert(std::iter::once((None, vec![(key.clone(), Some(vec![1]))]))); + backend.insert( + std::iter::once((None, vec![(key.clone(), Some(vec![1]))])), + Default::default(), + ); let mut s = CachingState::new(backend.clone(), shared.clone(), Some(root_parent)); s.cache.sync_cache( @@ -1211,7 +1219,7 @@ mod tests { let mut s = CachingState::new( InMemoryBackend::::default(), shared.clone(), - Some(root_parent.clone()), + Some(root_parent), ); let key = H256::random()[..].to_vec(); @@ -1295,14 +1303,14 @@ mod tests { let mut s = CachingState::new( InMemoryBackend::::default(), shared.clone(), - Some(root_parent.clone()), + Some(root_parent), ); s.cache.sync_cache( &[], &[], vec![(key.clone(), Some(vec![2]))], vec![], - Some(h0.clone()), + Some(h0), Some(0), true, ); @@ -1418,6 +1426,7 @@ mod qc { #[derive(Debug, Clone)] struct Node { hash: H256, + #[allow(unused)] parent: H256, state: KeyMap, changes: KeySet, diff --git a/client/db/src/upgrade.rs b/client/db/src/upgrade.rs index 0f3578ad99a37..51750bf689759 100644 --- a/client/db/src/upgrade.rs +++ b/client/db/src/upgrade.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -30,14 +30,15 @@ use kvdb_rocksdb::{Database, DatabaseConfig}; use sp_runtime::traits::Block as BlockT; /// Version file name. -const VERSION_FILE_NAME: &'static str = "db_version"; +const VERSION_FILE_NAME: &str = "db_version"; /// Current db version. -const CURRENT_VERSION: u32 = 3; +const CURRENT_VERSION: u32 = 4; /// Number of columns in v1. const V1_NUM_COLUMNS: u32 = 11; const V2_NUM_COLUMNS: u32 = 12; +const V3_NUM_COLUMNS: u32 = 12; /// Database upgrade errors. #[derive(Debug)] @@ -68,7 +69,7 @@ impl fmt::Display for UpgradeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { UpgradeError::UnknownDatabaseVersion => { - write!(f, "Database version cannot be read from exisiting db_version file") + write!(f, "Database version cannot be read from existing db_version file") }, UpgradeError::MissingDatabaseVersionFile => write!(f, "Missing database version file"), UpgradeError::UnsupportedVersion(version) => { @@ -92,9 +93,16 @@ pub fn upgrade_db(db_path: &Path, db_type: DatabaseType) -> Upgra 0 => return Err(UpgradeError::UnsupportedVersion(db_version)), 1 => { migrate_1_to_2::(db_path, db_type)?; - migrate_2_to_3::(db_path, db_type)? + migrate_2_to_3::(db_path, db_type)?; + migrate_3_to_4::(db_path, db_type)?; + }, + 2 => { + migrate_2_to_3::(db_path, db_type)?; + migrate_3_to_4::(db_path, db_type)?; + }, + 3 => { + migrate_3_to_4::(db_path, db_type)?; }, - 2 => migrate_2_to_3::(db_path, db_type)?, CURRENT_VERSION => (), _ => return Err(UpgradeError::FutureDatabaseVersion(db_version)), } @@ -107,7 +115,7 @@ pub fn upgrade_db(db_path: &Path, db_type: DatabaseType) -> Upgra /// 2) transactions column is added; fn migrate_1_to_2(db_path: &Path, _db_type: DatabaseType) -> UpgradeResult<()> { let db_cfg = DatabaseConfig::with_columns(V1_NUM_COLUMNS); - let db = Database::open(&db_cfg, db_path)?; + let mut db = Database::open(&db_cfg, db_path)?; db.add_column().map_err(Into::into) } @@ -118,7 +126,10 @@ fn migrate_2_to_3(db_path: &Path, _db_type: DatabaseType) -> Upgr let db = Database::open(&db_cfg, db_path)?; // Get all the keys we need to update - let keys: Vec<_> = db.iter(columns::JUSTIFICATIONS).map(|entry| entry.0).collect(); + let keys: Vec<_> = db + .iter(columns::JUSTIFICATIONS) + .map(|r| r.map(|e| e.0)) + .collect::>()?; // Read and update each entry let mut transaction = db.transaction(); @@ -139,6 +150,15 @@ fn migrate_2_to_3(db_path: &Path, _db_type: DatabaseType) -> Upgr Ok(()) } +/// Migration from version3 to version4: +/// 1) the number of columns has changed from 12 to 13; +/// 2) BODY_INDEX column is added; +fn migrate_3_to_4(db_path: &Path, _db_type: DatabaseType) -> UpgradeResult<()> { + let db_cfg = DatabaseConfig::with_columns(V3_NUM_COLUMNS); + let mut db = Database::open(&db_cfg, db_path)?; + db.add_column().map_err(Into::into) +} + /// Reads current database version from the file at given path. /// If the file does not exist returns 0. fn current_version(path: &Path) -> UpgradeResult { @@ -173,10 +193,7 @@ fn version_file_path(path: &Path) -> PathBuf { #[cfg(test)] mod tests { use super::*; - use crate::{ - tests::Block, DatabaseSettings, DatabaseSource, KeepBlocks, TransactionStorageMode, - }; - use sc_state_db::PruningMode; + use crate::{tests::Block, DatabaseSource}; fn create_db(db_path: &Path, version: Option) { if let Some(version) = version { @@ -188,17 +205,12 @@ mod tests { fn open_database(db_path: &Path, db_type: DatabaseType) -> sp_blockchain::Result<()> { crate::utils::open_database::( - &DatabaseSettings { - state_cache_size: 0, - state_cache_child_ratio: None, - state_pruning: PruningMode::ArchiveAll, - source: DatabaseSource::RocksDb { path: db_path.to_owned(), cache_size: 128 }, - keep_blocks: KeepBlocks::All, - transaction_storage: TransactionStorageMode::BlockBody, - }, + &DatabaseSource::RocksDb { path: db_path.to_owned(), cache_size: 128 }, db_type, + true, ) .map(|_| ()) + .map_err(|e| sp_blockchain::Error::Backend(e.to_string())) } #[test] @@ -229,4 +241,16 @@ mod tests { assert_eq!(current_version(&db_path).unwrap(), CURRENT_VERSION); } } + + #[test] + fn upgrade_to_4_works() { + let db_type = DatabaseType::Full; + for version_from_file in &[None, Some(1), Some(2), Some(3)] { + let db_dir = tempfile::TempDir::new().unwrap(); + let db_path = db_dir.path().join(db_type.as_str()); + create_db(&db_path, *version_from_file); + open_database(&db_path, db_type).unwrap(); + assert_eq!(current_version(&db_path).unwrap(), CURRENT_VERSION); + } + } } diff --git a/client/db/src/utils.rs b/client/db/src/utils.rs index ea22c774f463e..567950d089e1b 100644 --- a/client/db/src/utils.rs +++ b/client/db/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,11 +19,11 @@ //! Db-based backend utility structures and functions, used by both //! full and light storages. -use std::{convert::TryInto, fmt, fs, io, path::Path, sync::Arc}; +use std::{fmt, fs, io, path::Path, sync::Arc}; use log::{debug, info}; -use crate::{Database, DatabaseSettings, DatabaseSource, DbHash}; +use crate::{Database, DatabaseSource, DbHash}; use codec::Decode; use sp_database::Transaction; use sp_runtime::{ @@ -34,13 +34,7 @@ use sp_trie::DBValue; /// Number of columns in the db. Must be the same for both full && light dbs. /// Otherwise RocksDb will fail to open database && check its type. -#[cfg(any( - feature = "with-kvdb-rocksdb", - feature = "with-parity-db", - feature = "test-helpers", - test -))] -pub const NUM_COLUMNS: u32 = 12; +pub const NUM_COLUMNS: u32 = 13; /// Meta column. The set of keys in the column is shared by full && light storages. pub const COLUMN_META: u32 = 0; @@ -54,10 +48,8 @@ pub mod meta_keys { pub const FINALIZED_BLOCK: &[u8; 5] = b"final"; /// Last finalized state key. pub const FINALIZED_STATE: &[u8; 6] = b"fstate"; - /// Meta information prefix for list-based caches. - pub const CACHE_META_PREFIX: &[u8; 5] = b"cache"; - /// Meta information for changes tries key. - pub const CHANGES_TRIES_META: &[u8; 5] = b"ctrie"; + /// Block gap. + pub const BLOCK_GAP: &[u8; 3] = b"gap"; /// Genesis block hash. pub const GENESIS_HASH: &[u8; 3] = b"gen"; /// Leaves prefix list key. @@ -81,6 +73,8 @@ pub struct Meta { pub genesis_hash: H, /// Finalized state, if any pub finalized_state: Option<(H, N)>, + /// Block gap, start and end inclusive, if any. + pub block_gap: Option<(N, N)>, } /// A block lookup key: used for canonical lookup from block number to hash @@ -91,8 +85,6 @@ pub type NumberIndexKey = [u8; 4]; pub enum DatabaseType { /// Full node database. Full, - /// Light node database. - Light, } /// Convert block number into short lookup key (LE representation) for @@ -120,19 +112,6 @@ where Ok(lookup_key) } -/// Convert block lookup key into block number. -/// all block lookup keys start with the block number. -pub fn lookup_key_to_number(key: &[u8]) -> sp_blockchain::Result -where - N: From, -{ - if key.len() < 4 { - return Err(sp_blockchain::Error::Backend("Invalid block key".into())) - } - Ok((key[0] as u32) << 24 | (key[1] as u32) << 16 | (key[2] as u32) << 8 | (key[3] as u32)) - .map(Into::into) -} - /// Delete number to hash mapping in DB transaction. pub fn remove_number_to_key_mapping>( transaction: &mut Transaction, @@ -143,18 +122,6 @@ pub fn remove_number_to_key_mapping>( Ok(()) } -/// Remove key mappings. -pub fn remove_key_mappings, H: AsRef<[u8]>>( - transaction: &mut Transaction, - key_lookup_col: u32, - number: N, - hash: H, -) -> sp_blockchain::Result<()> { - remove_number_to_key_mapping(transaction, key_lookup_col, number)?; - transaction.remove(key_lookup_col, hash.as_ref()); - Ok(()) -} - /// Place a number mapping into the database. This maps number to current perceived /// block hash at that position. pub fn insert_number_to_key_mapping + Clone, H: AsRef<[u8]>>( @@ -204,41 +171,43 @@ where }) } -fn backend_err(feat: &'static str) -> sp_blockchain::Error { - sp_blockchain::Error::Backend(feat.to_string()) -} - /// Opens the configured database. pub fn open_database( - config: &DatabaseSettings, + db_source: &DatabaseSource, db_type: DatabaseType, -) -> sp_blockchain::Result>> { + create: bool, +) -> OpenDbResult { // Maybe migrate (copy) the database to a type specific subdirectory to make it // possible that light and full databases coexist // NOTE: This function can be removed in a few releases - maybe_migrate_to_type_subdir::(&config.source, db_type).map_err(|e| { - sp_blockchain::Error::Backend(format!("Error in migration to role subdirectory: {}", e)) - })?; + maybe_migrate_to_type_subdir::(db_source, db_type)?; - open_database_at::(&config.source, db_type) + open_database_at::(db_source, db_type, create) } fn open_database_at( - source: &DatabaseSource, + db_source: &DatabaseSource, db_type: DatabaseType, -) -> sp_blockchain::Result>> { - let db: Arc> = match &source { - DatabaseSource::ParityDb { path } => open_parity_db::(&path, db_type, true)?, + create: bool, +) -> OpenDbResult { + let db: Arc> = match &db_source { + DatabaseSource::ParityDb { path } => open_parity_db::(path, db_type, create)?, + #[cfg(feature = "rocksdb")] DatabaseSource::RocksDb { path, cache_size } => - open_kvdb_rocksdb::(&path, db_type, true, *cache_size)?, - DatabaseSource::Custom(db) => db.clone(), + open_kvdb_rocksdb::(path, db_type, create, *cache_size)?, + DatabaseSource::Custom { db, require_create_flag } => { + if *require_create_flag && !create { + return Err(OpenDbError::DoesNotExist) + } + db.clone() + }, DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size } => { // check if rocksdb exists first, if not, open paritydb - match open_kvdb_rocksdb::(&rocksdb_path, db_type, false, *cache_size) { + match open_kvdb_rocksdb::(rocksdb_path, db_type, false, *cache_size) { Ok(db) => db, Err(OpenDbError::NotEnabled(_)) | Err(OpenDbError::DoesNotExist) => - open_parity_db::(&paritydb_path, db_type, true)?, - Err(_) => return Err(backend_err("cannot open rocksdb. corrupted database")), + open_parity_db::(paritydb_path, db_type, create)?, + Err(as_is) => return Err(as_is), } }, }; @@ -248,12 +217,17 @@ fn open_database_at( } #[derive(Debug)] -enum OpenDbError { +pub enum OpenDbError { // constructed only when rocksdb and paritydb are disabled #[allow(dead_code)] NotEnabled(&'static str), DoesNotExist, Internal(String), + DatabaseError(sp_database::error::DatabaseError), + UnexpectedDbType { + expected: DatabaseType, + found: Vec, + }, } type OpenDbResult = Result>, OpenDbError>; @@ -261,11 +235,22 @@ type OpenDbResult = Result>, OpenDbError>; impl fmt::Display for OpenDbError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - OpenDbError::Internal(e) => write!(f, "{}", e.to_string()), + OpenDbError::Internal(e) => write!(f, "{}", e), OpenDbError::DoesNotExist => write!(f, "Database does not exist at given location"), OpenDbError::NotEnabled(feat) => { write!(f, "`{}` feature not enabled, database can not be opened", feat) }, + OpenDbError::DatabaseError(db_error) => { + write!(f, "Database Error: {}", db_error) + }, + OpenDbError::UnexpectedDbType { expected, found } => { + write!( + f, + "Unexpected DB-Type. Expected: {:?}, Found: {:?}", + expected.as_str().as_bytes(), + found + ) + }, } } } @@ -276,10 +261,9 @@ impl From for sp_blockchain::Error { } } -#[cfg(feature = "with-parity-db")] impl From for OpenDbError { fn from(err: parity_db::Error) -> Self { - if err.to_string().contains("use open_or_create") { + if matches!(err, parity_db::Error::DatabaseNotFound) { OpenDbError::DoesNotExist } else { OpenDbError::Internal(err.to_string()) @@ -297,22 +281,19 @@ impl From for OpenDbError { } } -#[cfg(feature = "with-parity-db")] fn open_parity_db(path: &Path, db_type: DatabaseType, create: bool) -> OpenDbResult { - let db = crate::parity_db::open(path, db_type, create)?; - Ok(db) -} - -#[cfg(not(feature = "with-parity-db"))] -fn open_parity_db( - _path: &Path, - _db_type: DatabaseType, - _create: bool, -) -> OpenDbResult { - Err(OpenDbError::NotEnabled("with-parity-db")) + match crate::parity_db::open(path, db_type, create, false) { + Ok(db) => Ok(db), + Err(parity_db::Error::InvalidConfiguration(_)) => { + log::warn!("Invalid parity db configuration, attempting database metadata update."); + // Try to update the database with the new config + Ok(crate::parity_db::open(path, db_type, create, true)?) + }, + Err(e) => Err(e.into()), + } } -#[cfg(any(feature = "with-kvdb-rocksdb", test))] +#[cfg(any(feature = "rocksdb", test))] fn open_kvdb_rocksdb( path: &Path, db_type: DatabaseType, @@ -320,7 +301,7 @@ fn open_kvdb_rocksdb( cache_size: usize, ) -> OpenDbResult { // first upgrade database to required version - match crate::upgrade::upgrade_db::(&path, db_type) { + match crate::upgrade::upgrade_db::(path, db_type) { // in case of missing version file, assume that database simply does not exist at given // location Ok(_) | Err(crate::upgrade::UpgradeError::MissingDatabaseVersionFile) => (), @@ -353,18 +334,6 @@ fn open_kvdb_rocksdb( other_col_budget, ); }, - DatabaseType::Light => { - let col_budget = cache_size / (NUM_COLUMNS as usize); - for i in 0..NUM_COLUMNS { - memory_budget.insert(i, col_budget); - } - log::trace!( - target: "db", - "Open RocksDB light database at {:?}, column cache: {} MiB", - path, - col_budget, - ); - }, } db_config.memory_budget = memory_budget; @@ -374,7 +343,7 @@ fn open_kvdb_rocksdb( Ok(sp_database::as_database(db)) } -#[cfg(not(any(feature = "with-kvdb-rocksdb", test)))] +#[cfg(not(any(feature = "rocksdb", test)))] fn open_kvdb_rocksdb( _path: &Path, _db_type: DatabaseType, @@ -388,20 +357,19 @@ fn open_kvdb_rocksdb( pub fn check_database_type( db: &dyn Database, db_type: DatabaseType, -) -> sp_blockchain::Result<()> { +) -> Result<(), OpenDbError> { match db.get(COLUMN_META, meta_keys::TYPE) { Some(stored_type) => if db_type.as_str().as_bytes() != &*stored_type { - return Err(sp_blockchain::Error::Backend(format!( - "Unexpected database type. Expected: {}", - db_type.as_str() - )) - .into()) + return Err(OpenDbError::UnexpectedDbType { + expected: db_type, + found: stored_type.to_owned(), + }) }, None => { let mut transaction = Transaction::new(); transaction.set(COLUMN_META, meta_keys::TYPE, db_type.as_str().as_bytes()); - db.commit(transaction)?; + db.commit(transaction).map_err(OpenDbError::DatabaseError)?; }, } @@ -411,7 +379,7 @@ pub fn check_database_type( fn maybe_migrate_to_type_subdir( source: &DatabaseSource, db_type: DatabaseType, -) -> io::Result<()> { +) -> Result<(), OpenDbError> { if let Some(p) = source.path() { let mut basedir = p.to_path_buf(); basedir.pop(); @@ -420,21 +388,20 @@ fn maybe_migrate_to_type_subdir( // See if there's a file identifying a rocksdb or paritydb folder in the parent dir and // the target path ends in a role specific directory if (basedir.join("db_version").exists() || basedir.join("metadata").exists()) && - (p.ends_with(DatabaseType::Full.as_str()) || - p.ends_with(DatabaseType::Light.as_str())) + (p.ends_with(DatabaseType::Full.as_str())) { // Try to open the database to check if the current `DatabaseType` matches the type of // database stored in the target directory and close the database on success. let mut old_source = source.clone(); old_source.set_path(&basedir); - open_database_at::(&old_source, db_type) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + open_database_at::(&old_source, db_type, false)?; info!( "Migrating database to a database-type-based subdirectory: '{:?}' -> '{:?}'", basedir, basedir.join(db_type.as_str()) ); + let mut tmp_dir = basedir.clone(); tmp_dir.pop(); tmp_dir.push("tmp"); @@ -458,9 +425,9 @@ pub fn read_db( where Block: BlockT, { - block_id_to_lookup_key(db, col_index, id).and_then(|key| match key { - Some(key) => Ok(db.get(col, key.as_ref())), - None => Ok(None), + block_id_to_lookup_key(db, col_index, id).map(|key| match key { + Some(key) => db.get(col, key.as_ref()), + None => None, }) } @@ -475,9 +442,10 @@ pub fn remove_from_db( where Block: BlockT, { - block_id_to_lookup_key(db, col_index, id).and_then(|key| match key { - Some(key) => Ok(transaction.remove(col, key.as_ref())), - None => Ok(()), + block_id_to_lookup_key(db, col_index, id).map(|key| { + if let Some(key) = key { + transaction.remove(col, key.as_ref()); + } }) } @@ -491,24 +459,12 @@ pub fn read_header( match read_db(db, col_index, col, id)? { Some(header) => match Block::Header::decode(&mut &header[..]) { Ok(header) => Ok(Some(header)), - Err(_) => return Err(sp_blockchain::Error::Backend("Error decoding header".into())), + Err(_) => Err(sp_blockchain::Error::Backend("Error decoding header".into())), }, None => Ok(None), } } -/// Required header from the database. -pub fn require_header( - db: &dyn Database, - col_index: u32, - col: u32, - id: BlockId, -) -> sp_blockchain::Result { - read_header(db, col_index, col, id).and_then(|header| { - header.ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("Require header: {}", id))) - }) -} - /// Read meta from the database. pub fn read_meta( db: &dyn Database, @@ -527,6 +483,7 @@ where finalized_number: Zero::zero(), genesis_hash: Default::default(), finalized_state: None, + block_gap: None, }), }; @@ -541,7 +498,7 @@ where "Opened blockchain db, fetched {} = {:?} ({})", desc, hash, - header.number() + header.number(), ); Ok((hash, *header.number())) } else { @@ -558,6 +515,10 @@ where } else { None }; + let block_gap = db + .get(COLUMN_META, meta_keys::BLOCK_GAP) + .and_then(|d| Decode::decode(&mut d.as_slice()).ok()); + debug!(target: "db", "block_gap={:?}", block_gap); Ok(Meta { best_hash, @@ -566,6 +527,7 @@ where finalized_number, genesis_hash, finalized_state, + block_gap, }) } @@ -588,7 +550,6 @@ impl DatabaseType { pub fn as_str(&self) -> &'static str { match *self { DatabaseType::Full => "full", - DatabaseType::Light => "light", } } } @@ -620,14 +581,12 @@ impl<'a, 'b> codec::Input for JoinInput<'a, 'b> { #[cfg(test)] mod tests { use super::*; - use crate::{KeepBlocks, TransactionStorageMode}; use codec::Input; - use sc_state_db::PruningMode; use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper}; use std::path::PathBuf; type Block = RawBlock>; - #[cfg(any(feature = "with-kvdb-rocksdb", test))] + #[cfg(any(feature = "rocksdb", test))] #[test] fn database_type_subdir_migration() { type Block = RawBlock>; @@ -641,42 +600,29 @@ mod tests { let old_db_path = base_path.path().join("chains/dev/db"); source.set_path(&old_db_path); - let settings = db_settings(source.clone()); { - let db_res = open_database::(&settings, db_type); + let db_res = open_database::(&source, db_type, true); assert!(db_res.is_ok(), "New database should be created."); assert!(old_db_path.join(db_check_file).exists()); assert!(!old_db_path.join(db_type.as_str()).join("db_version").exists()); } source.set_path(&old_db_path.join(db_type.as_str())); - let settings = db_settings(source); - let db_res = open_database::(&settings, db_type); + + let db_res = open_database::(&source, db_type, true); assert!(db_res.is_ok(), "Reopening the db with the same role should work"); // check if the database dir had been migrated assert!(!old_db_path.join(db_check_file).exists()); assert!(old_db_path.join(db_type.as_str()).join(db_check_file).exists()); } - check_dir_for_db_type( - DatabaseType::Light, - DatabaseSource::RocksDb { path: PathBuf::new(), cache_size: 128 }, - "db_version", - ); check_dir_for_db_type( DatabaseType::Full, DatabaseSource::RocksDb { path: PathBuf::new(), cache_size: 128 }, "db_version", ); - #[cfg(feature = "with-parity-db")] - check_dir_for_db_type( - DatabaseType::Light, - DatabaseSource::ParityDb { path: PathBuf::new() }, - "metadata", - ); - #[cfg(feature = "with-parity-db")] check_dir_for_db_type( DatabaseType::Full, DatabaseSource::ParityDb { path: PathBuf::new() }, @@ -689,9 +635,8 @@ mod tests { let old_db_path = base_path.path().join("chains/dev/db"); let source = DatabaseSource::RocksDb { path: old_db_path.clone(), cache_size: 128 }; - let settings = db_settings(source); { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "New database should be created."); // check if the database dir had been migrated @@ -699,16 +644,8 @@ mod tests { assert!(!old_db_path.join("light/db_version").exists()); assert!(!old_db_path.join("full/db_version").exists()); } - let source = DatabaseSource::RocksDb { - path: old_db_path.join(DatabaseType::Light.as_str()), - cache_size: 128, - }; - let settings = db_settings(source); - let db_res = open_database::(&settings, DatabaseType::Light); - assert!(db_res.is_err(), "Opening a light database in full role should fail"); // assert nothing was changed assert!(old_db_path.join("db_version").exists()); - assert!(!old_db_path.join("light/db_version").exists()); assert!(!old_db_path.join("full/db_version").exists()); } } @@ -725,7 +662,6 @@ mod tests { #[test] fn database_type_as_str_works() { assert_eq!(DatabaseType::Full.as_str(), "full"); - assert_eq!(DatabaseType::Light.as_str(), "light"); } #[test] @@ -749,19 +685,6 @@ mod tests { assert_eq!(joined.remaining_len().unwrap(), Some(0)); } - fn db_settings(source: DatabaseSource) -> DatabaseSettings { - DatabaseSettings { - state_cache_size: 0, - state_cache_child_ratio: None, - state_pruning: PruningMode::ArchiveAll, - source, - keep_blocks: KeepBlocks::All, - transaction_storage: TransactionStorageMode::BlockBody, - } - } - - #[cfg(feature = "with-parity-db")] - #[cfg(any(feature = "with-kvdb-rocksdb", test))] #[test] fn test_open_database_auto_new() { let db_dir = tempfile::TempDir::new().unwrap(); @@ -773,37 +696,40 @@ mod tests { rocksdb_path: rocksdb_path.clone(), cache_size: 128, }; - let mut settings = db_settings(source); // it should create new auto (paritydb) database { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "New database should be created."); } // it should reopen existing auto (pairtydb) database { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "Existing parity database should be reopened"); } // it should fail to open existing auto (pairtydb) database { - settings.source = DatabaseSource::RocksDb { path: rocksdb_path, cache_size: 128 }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::RocksDb { path: rocksdb_path, cache_size: 128 }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "New database should be opened."); } // it should reopen existing auto (pairtydb) database { - settings.source = DatabaseSource::ParityDb { path: paritydb_path }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::ParityDb { path: paritydb_path }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "Existing parity database should be reopened"); } } - #[cfg(feature = "with-parity-db")] - #[cfg(any(feature = "with-kvdb-rocksdb", test))] #[test] fn test_open_database_rocksdb_new() { let db_dir = tempfile::TempDir::new().unwrap(); @@ -812,42 +738,48 @@ mod tests { let rocksdb_path = db_path.join("rocksdb_path"); let source = DatabaseSource::RocksDb { path: rocksdb_path.clone(), cache_size: 128 }; - let mut settings = db_settings(source); // it should create new rocksdb database { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "New rocksdb database should be created"); } // it should reopen existing auto (rocksdb) database { - settings.source = DatabaseSource::Auto { - paritydb_path: paritydb_path.clone(), - rocksdb_path: rocksdb_path.clone(), - cache_size: 128, - }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::Auto { + paritydb_path: paritydb_path.clone(), + rocksdb_path: rocksdb_path.clone(), + cache_size: 128, + }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "Existing rocksdb database should be reopened"); } // it should fail to open existing auto (rocksdb) database { - settings.source = DatabaseSource::ParityDb { path: paritydb_path }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::ParityDb { path: paritydb_path }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "New paritydb database should be created"); } // it should reopen existing auto (pairtydb) database { - settings.source = DatabaseSource::RocksDb { path: rocksdb_path, cache_size: 128 }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::RocksDb { path: rocksdb_path, cache_size: 128 }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "Existing rocksdb database should be reopened"); } } - #[cfg(feature = "with-parity-db")] - #[cfg(any(feature = "with-kvdb-rocksdb", test))] #[test] fn test_open_database_paritydb_new() { let db_dir = tempfile::TempDir::new().unwrap(); @@ -856,32 +788,36 @@ mod tests { let rocksdb_path = db_path.join("rocksdb_path"); let source = DatabaseSource::ParityDb { path: paritydb_path.clone() }; - let mut settings = db_settings(source); // it should create new paritydb database { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "New database should be created."); } // it should reopen existing pairtydb database { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "Existing parity database should be reopened"); } // it should fail to open existing pairtydb database { - settings.source = - DatabaseSource::RocksDb { path: rocksdb_path.clone(), cache_size: 128 }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::RocksDb { path: rocksdb_path.clone(), cache_size: 128 }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "New rocksdb database should be created"); } // it should reopen existing auto (pairtydb) database { - settings.source = DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size: 128 }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size: 128 }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "Existing parity database should be reopened"); } } diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index b7e2595b8e169..83d4801d1c92b 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-executor" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "A crate that provides means of executing/dispatching calls into the runtime." documentation = "https://docs.rs/sc-executor" @@ -14,45 +14,48 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-tasks = { version = "4.0.0-dev", path = "../../primitives/tasks" } -sp-trie = { version = "4.0.0-dev", path = "../../primitives/trie" } -sp-version = { version = "4.0.0-dev", path = "../../primitives/version" } -sp-panic-handler = { version = "3.0.0", path = "../../primitives/panic-handler" } -wasmi = "0.9.0" -lazy_static = "1.4.0" -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-wasm-interface = { version = "4.0.0-dev", path = "../../primitives/wasm-interface" } -sp-runtime-interface = { version = "4.0.0-dev", path = "../../primitives/runtime-interface" } -sp-externalities = { version = "0.10.0-dev", path = "../../primitives/externalities" } +lru = "0.8.1" +parking_lot = "0.12.1" +tracing = "0.1.29" +wasmi = "0.13" + +codec = { package = "parity-scale-codec", version = "3.0.0" } sc-executor-common = { version = "0.10.0-dev", path = "common" } sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" } -sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime", optional = true } -parking_lot = "0.11.1" -log = "0.4.8" -libsecp256k1 = "0.6" +sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-panic-handler = { version = "5.0.0", path = "../../primitives/panic-handler" } +sp-runtime-interface = { version = "7.0.0", path = "../../primitives/runtime-interface" } +sp-trie = { version = "7.0.0", path = "../../primitives/trie" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-wasm-interface = { version = "7.0.0", path = "../../primitives/wasm-interface" } [dev-dependencies] +array-bytes = "4.1" wat = "1.0" -hex-literal = "0.3.1" sc-runtime-test = { version = "2.0.0", path = "runtime-test" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } -sp-state-machine = { version = "0.10.0-dev", path = "../../primitives/state-machine" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sp-maybe-compressed-blob = { version = "4.0.0-dev", path = "../../primitives/maybe-compressed-blob" } +sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/maybe-compressed-blob" } sc-tracing = { version = "4.0.0-dev", path = "../tracing" } -tracing = "0.1.25" tracing-subscriber = "0.2.19" paste = "1.0" -regex = "1" +regex = "1.6.0" +criterion = "0.3" +env_logger = "0.9" +num_cpus = "1.13.1" +tempfile = "3.3.0" + +[[bench]] +name = "bench" +harness = false [features] default = ["std"] # This crate does not have `no_std` support, we just require this for tests std = [] wasm-extern-trace = [] -wasmtime = ["sc-executor-wasmtime"] -wasmi-errno = ["wasmi/errno"] -wasmer-sandbox = ["sc-executor-common/wasmer-sandbox"] diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs new file mode 100644 index 0000000000000..a282cdfbdd334 --- /dev/null +++ b/client/executor/benches/bench.rs @@ -0,0 +1,260 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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 criterion::{criterion_group, criterion_main, Criterion}; + +use codec::Encode; + +use sc_executor_common::{ + runtime_blob::RuntimeBlob, + wasm_runtime::{WasmInstance, WasmModule}, +}; +use sc_executor_wasmtime::InstantiationStrategy; +use sc_runtime_test::wasm_binary_unwrap as test_runtime; +use sp_wasm_interface::HostFunctions as _; +use std::sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + Arc, +}; + +#[derive(Clone)] +enum Method { + Interpreted, + Compiled { instantiation_strategy: InstantiationStrategy, precompile: bool }, +} + +// This is just a bog-standard Kusama runtime with an extra +// `test_empty_return` and `test_dirty_plenty_memory` functions +// copy-pasted from the test runtime. +fn kusama_runtime() -> &'static [u8] { + include_bytes!("kusama_runtime.wasm") +} + +fn initialize( + _tmpdir: &mut Option, + runtime: &[u8], + method: Method, +) -> Arc { + let blob = RuntimeBlob::uncompress_if_needed(runtime).unwrap(); + let host_functions = sp_io::SubstrateHostFunctions::host_functions(); + let heap_pages = 2048; + let allow_missing_func_imports = true; + + match method { + Method::Interpreted => sc_executor_wasmi::create_runtime( + blob, + heap_pages, + host_functions, + allow_missing_func_imports, + ) + .map(|runtime| -> Arc { Arc::new(runtime) }), + Method::Compiled { instantiation_strategy, precompile } => { + let config = sc_executor_wasmtime::Config { + allow_missing_func_imports, + cache_path: None, + semantics: sc_executor_wasmtime::Semantics { + extra_heap_pages: heap_pages, + instantiation_strategy, + deterministic_stack_limit: None, + canonicalize_nans: false, + parallel_compilation: true, + max_memory_size: None, + }, + }; + + if precompile { + let precompiled_blob = + sc_executor_wasmtime::prepare_runtime_artifact(blob, &config.semantics) + .unwrap(); + + // Create a fresh temporary directory to make absolutely sure + // we'll use the right module. + *_tmpdir = Some(tempfile::tempdir().unwrap()); + let tmpdir = _tmpdir.as_ref().unwrap(); + + let path = tmpdir.path().join("module.bin"); + std::fs::write(&path, &precompiled_blob).unwrap(); + unsafe { + sc_executor_wasmtime::create_runtime_from_artifact::< + sp_io::SubstrateHostFunctions, + >(&path, config) + } + } else { + sc_executor_wasmtime::create_runtime::(blob, config) + } + .map(|runtime| -> Arc { Arc::new(runtime) }) + }, + } + .unwrap() +} + +fn run_benchmark( + c: &mut Criterion, + benchmark_name: &str, + thread_count: usize, + runtime: &dyn WasmModule, + testcase: impl Fn(&mut Box) + Copy + Send + 'static, +) { + c.bench_function(benchmark_name, |b| { + // Here we deliberately start a bunch of extra threads which will just + // keep on independently instantiating the runtime over and over again. + // + // We don't really have to measure how much time those take since the + // work done is essentially the same on each thread, and what we're + // interested in here is only how those extra threads affect the execution + // on the current thread. + // + // In an ideal case assuming we have enough CPU cores those extra threads + // shouldn't affect the main thread's runtime at all, however in practice + // they're not completely independent. There might be per-process + // locks in the kernel which are briefly held during instantiation, etc., + // and how much those affect the execution here is what we want to measure. + let is_benchmark_running = Arc::new(AtomicBool::new(true)); + let threads_running = Arc::new(AtomicUsize::new(0)); + let aux_threads: Vec<_> = (0..thread_count - 1) + .map(|_| { + let mut instance = runtime.new_instance().unwrap(); + let is_benchmark_running = is_benchmark_running.clone(); + let threads_running = threads_running.clone(); + std::thread::spawn(move || { + threads_running.fetch_add(1, Ordering::SeqCst); + while is_benchmark_running.load(Ordering::Relaxed) { + testcase(&mut instance); + } + }) + }) + .collect(); + + while threads_running.load(Ordering::SeqCst) != (thread_count - 1) { + std::thread::yield_now(); + } + + let mut instance = runtime.new_instance().unwrap(); + b.iter(|| testcase(&mut instance)); + + is_benchmark_running.store(false, Ordering::SeqCst); + for thread in aux_threads { + thread.join().unwrap(); + } + }); +} + +fn bench_call_instance(c: &mut Criterion) { + let _ = env_logger::try_init(); + + let strategies = [ + ( + "legacy_instance_reuse", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::LegacyInstanceReuse, + precompile: false, + }, + ), + ( + "recreate_instance_vanilla", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::RecreateInstance, + precompile: false, + }, + ), + ( + "recreate_instance_cow_fresh", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::RecreateInstanceCopyOnWrite, + precompile: false, + }, + ), + ( + "recreate_instance_cow_precompiled", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::RecreateInstanceCopyOnWrite, + precompile: true, + }, + ), + ( + "pooling_vanilla", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::Pooling, + precompile: false, + }, + ), + ( + "pooling_cow_fresh", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::PoolingCopyOnWrite, + precompile: false, + }, + ), + ( + "pooling_cow_precompiled", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::PoolingCopyOnWrite, + precompile: true, + }, + ), + ("interpreted", Method::Interpreted), + ]; + + let runtimes = [("kusama_runtime", kusama_runtime()), ("test_runtime", test_runtime())]; + + let thread_counts = [1, 2, 4, 8, 16]; + + fn test_call_empty_function(instance: &mut Box) { + instance.call_export("test_empty_return", &[0]).unwrap(); + } + + fn test_dirty_1mb_of_memory(instance: &mut Box) { + instance.call_export("test_dirty_plenty_memory", &(0, 16).encode()).unwrap(); + } + + let testcases = [ + ("call_empty_function", test_call_empty_function as fn(&mut Box)), + ("dirty_1mb_of_memory", test_dirty_1mb_of_memory), + ]; + + let num_cpus = num_cpus::get_physical(); + let mut tmpdir = None; + + for (strategy_name, strategy) in strategies { + for (runtime_name, runtime) in runtimes { + let runtime = initialize(&mut tmpdir, runtime, strategy.clone()); + + for (testcase_name, testcase) in testcases { + for thread_count in thread_counts { + if thread_count > num_cpus { + // If there are not enough cores available the benchmark is pointless. + continue + } + + let benchmark_name = format!( + "{}_from_{}_with_{}_on_{}_threads", + testcase_name, runtime_name, strategy_name, thread_count + ); + + run_benchmark(c, &benchmark_name, thread_count, &*runtime, testcase); + } + } + } + } +} + +criterion_group! { + name = benches; + config = Criterion::default(); + targets = bench_call_instance +} +criterion_main!(benches); diff --git a/client/executor/benches/kusama_runtime.wasm b/client/executor/benches/kusama_runtime.wasm new file mode 100755 index 0000000000000..28adce9623400 Binary files /dev/null and b/client/executor/benches/kusama_runtime.wasm differ diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index c4fc8c27f7544..648e937d371ff 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-executor-common" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "A set of common definitions that are needed for defining execution engines." documentation = "https://docs.rs/sc-executor-common/" @@ -14,24 +14,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -derive_more = "0.99.2" -pwasm-utils = "0.18.0" -codec = { package = "parity-scale-codec", version = "2.0.0" } -wasmi = "0.9.0" -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sc-allocator = { version = "4.0.0-dev", path = "../../allocator" } -sp-wasm-interface = { version = "4.0.0-dev", path = "../../../primitives/wasm-interface" } -sp-maybe-compressed-blob = { version = "4.0.0-dev", path = "../../../primitives/maybe-compressed-blob" } -sp-serializer = { version = "3.0.0", path = "../../../primitives/serializer" } -thiserror = "1.0.21" -environmental = "1.1.3" - -wasmer = { version = "1.0", optional = true } -wasmer-compiler-singlepass = { version = "1.0", optional = true } +thiserror = "1.0.30" +wasm-instrument = "0.3" +wasmi = "0.13" +sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } +sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" } +sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } [features] default = [] -wasmer-sandbox = [ - "wasmer", - "wasmer-compiler-singlepass", -] diff --git a/client/executor/common/src/error.rs b/client/executor/common/src/error.rs index 6ad4802e57a8b..c35a874b7796d 100644 --- a/client/executor/common/src/error.rs +++ b/client/executor/common/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,7 +18,6 @@ //! Rust executor possible errors. -use sp_serializer; use wasmi; /// Result type alias. @@ -28,12 +27,6 @@ pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { - #[error("Unserializable data encountered")] - InvalidData(#[from] sp_serializer::Error), - - #[error(transparent)] - Trap(#[from] wasmi::Trap), - #[error(transparent)] Wasmi(#[from] wasmi::Error), @@ -43,9 +36,6 @@ pub enum Error { #[error("Method not found: '{0}'")] MethodNotFound(String), - #[error("Invalid Code (expected single byte): '{0}'")] - InvalidCode(String), - #[error("On-chain runtime does not specify version")] VersionInvalid, @@ -108,6 +98,12 @@ pub enum Error { #[error("Invalid initializer expression provided {0}")] InvalidInitializerExpression(String), + + #[error("Execution aborted due to panic: {0}")] + AbortedDueToPanic(MessageWithBacktrace), + + #[error("Execution aborted due to trap: {0}")] + AbortedDueToTrap(MessageWithBacktrace), } impl wasmi::HostError for Error {} @@ -125,28 +121,73 @@ impl From for Error { } /// Type for errors occurring during Wasm runtime construction. -#[derive(Debug, derive_more::Display)] +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] pub enum WasmError { - /// Code could not be read from the state. + #[error("Code could not be read from the state.")] CodeNotFound, - /// Failure to reinitialize runtime instance from snapshot. + + #[error("Failure to reinitialize runtime instance from snapshot.")] ApplySnapshotFailed, + /// Failure to erase the wasm memory. /// /// Depending on the implementation might mean failure of allocating memory. + #[error("Failure to erase the wasm memory: {0}")] ErasingFailed(String), - /// Wasm code failed validation. + + #[error("Wasm code failed validation.")] InvalidModule, - /// Wasm code could not be deserialized. + + #[error("Wasm code could not be deserialized.")] CantDeserializeWasm, - /// The module does not export a linear memory named `memory`. + + #[error("The module does not export a linear memory named `memory`.")] InvalidMemory, - /// The number of heap pages requested is disallowed by the module. + + #[error("The number of heap pages requested is disallowed by the module.")] InvalidHeapPages, + /// Instantiation error. + #[error("{0}")] Instantiation(String), + /// Other error happenend. + #[error("{0}")] Other(String), } -impl std::error::Error for WasmError {} +/// An error message with an attached backtrace. +#[derive(Debug)] +pub struct MessageWithBacktrace { + /// The error message. + pub message: String, + + /// The backtrace associated with the error message. + pub backtrace: Option, +} + +impl std::fmt::Display for MessageWithBacktrace { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.write_str(&self.message)?; + if let Some(ref backtrace) = self.backtrace { + fmt.write_str("\nWASM backtrace:\n")?; + backtrace.backtrace_string.fmt(fmt)?; + } + + Ok(()) + } +} + +/// A WASM backtrace. +#[derive(Debug)] +pub struct Backtrace { + /// The string containing the backtrace. + pub backtrace_string: String, +} + +impl std::fmt::Display for Backtrace { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.write_str(&self.backtrace_string) + } +} diff --git a/client/executor/common/src/lib.rs b/client/executor/common/src/lib.rs index 99b927e062038..79bb74b62a41e 100644 --- a/client/executor/common/src/lib.rs +++ b/client/executor/common/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,6 +23,5 @@ pub mod error; pub mod runtime_blob; -pub mod sandbox; pub mod util; pub mod wasm_runtime; diff --git a/client/executor/common/src/runtime_blob/data_segments_snapshot.rs b/client/executor/common/src/runtime_blob/data_segments_snapshot.rs index 5c3fedbdc963e..e65fc32f637a6 100644 --- a/client/executor/common/src/runtime_blob/data_segments_snapshot.rs +++ b/client/executor/common/src/runtime_blob/data_segments_snapshot.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,8 +18,8 @@ use super::RuntimeBlob; use crate::error::{self, Error}; -use pwasm_utils::parity_wasm::elements::Instruction; use std::mem; +use wasm_instrument::parity_wasm::elements::Instruction; /// This is a snapshot of data segments specialzied for a particular instantiation. /// @@ -39,7 +39,7 @@ impl DataSegmentsSnapshot { .map(|mut segment| { // Just replace contents of the segment since the segments will be discarded later // anyway. - let contents = mem::replace(segment.value_mut(), vec![]); + let contents = mem::take(segment.value_mut()); let init_expr = match segment.offset() { Some(offset) => offset.code(), diff --git a/client/executor/common/src/runtime_blob/globals_snapshot.rs b/client/executor/common/src/runtime_blob/globals_snapshot.rs index 6a29ff8bae365..207fa751bc408 100644 --- a/client/executor/common/src/runtime_blob/globals_snapshot.rs +++ b/client/executor/common/src/runtime_blob/globals_snapshot.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -34,14 +34,14 @@ pub trait InstanceGlobals { /// Get a handle to a global by it's export name. /// /// The requested export is must exist in the exported list, and it should be a mutable global. - fn get_global(&self, export_name: &str) -> Self::Global; + fn get_global(&mut self, export_name: &str) -> Self::Global; /// Get the current value of the global. - fn get_global_value(&self, global: &Self::Global) -> sp_wasm_interface::Value; + fn get_global_value(&mut self, global: &Self::Global) -> sp_wasm_interface::Value; /// Update the current value of the global. /// /// The global behind the handle is guaranteed to be mutable and the value to be the same type /// as the global. - fn set_global_value(&self, global: &Self::Global, value: sp_wasm_interface::Value); + fn set_global_value(&mut self, global: &Self::Global, value: sp_wasm_interface::Value); } /// A set of exposed mutable globals. @@ -79,7 +79,10 @@ impl GlobalsSnapshot { /// /// This function panics if the instance doesn't correspond to the module from which the /// [`ExposedMutableGlobalsSet`] was collected. - pub fn take(mutable_globals: &ExposedMutableGlobalsSet, instance: &Instance) -> Self + pub fn take( + mutable_globals: &ExposedMutableGlobalsSet, + instance: &mut Instance, + ) -> Self where Instance: InstanceGlobals, { @@ -98,7 +101,7 @@ impl GlobalsSnapshot { /// Apply the snapshot to the given instance. /// /// This instance must be the same that was used for creation of this snapshot. - pub fn apply(&self, instance: &Instance) + pub fn apply(&self, instance: &mut Instance) where Instance: InstanceGlobals, { diff --git a/client/executor/common/src/runtime_blob/mod.rs b/client/executor/common/src/runtime_blob/mod.rs index 1af2708d3eb47..4b163bbaaf329 100644 --- a/client/executor/common/src/runtime_blob/mod.rs +++ b/client/executor/common/src/runtime_blob/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 6fb9303e07758..08df4b32d59eb 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,9 +17,12 @@ // along with this program. If not, see . use crate::error::WasmError; -use pwasm_utils::{ +use wasm_instrument::{ export_mutable_globals, - parity_wasm::elements::{deserialize_buffer, serialize, DataSegment, Internal, Module}, + parity_wasm::elements::{ + deserialize_buffer, serialize, DataSegment, ExportEntry, External, Internal, MemorySection, + MemoryType, Module, Section, + }, }; /// A bunch of information collected from a WebAssembly module. @@ -84,7 +87,7 @@ impl RuntimeBlob { /// depth of the wasm operand stack. pub fn inject_stack_depth_metering(self, stack_depth_limit: u32) -> Result { let injected_module = - pwasm_utils::stack_height::inject_limiter(self.raw_module, stack_depth_limit).map_err( + wasm_instrument::inject_stack_limiter(self.raw_module, stack_depth_limit).map_err( |e| WasmError::Other(format!("cannot inject the stack limiter: {:?}", e)), )?; @@ -104,10 +107,87 @@ impl RuntimeBlob { .unwrap_or_default() } + /// Converts a WASM memory import into a memory section and exports it. + /// + /// Does nothing if there's no memory import. + /// + /// May return an error in case the WASM module is invalid. + pub fn convert_memory_import_into_export(&mut self) -> Result<(), WasmError> { + let import_section = match self.raw_module.import_section_mut() { + Some(import_section) => import_section, + None => return Ok(()), + }; + + let import_entries = import_section.entries_mut(); + for index in 0..import_entries.len() { + let entry = &import_entries[index]; + let memory_ty = match entry.external() { + External::Memory(memory_ty) => *memory_ty, + _ => continue, + }; + + let memory_name = entry.field().to_owned(); + import_entries.remove(index); + + self.raw_module + .insert_section(Section::Memory(MemorySection::with_entries(vec![memory_ty]))) + .map_err(|error| { + WasmError::Other(format!( + "can't convert a memory import into an export: failed to insert a new memory section: {}", + error + )) + })?; + + if self.raw_module.export_section_mut().is_none() { + // A module without an export section is somewhat unrealistic, but let's do this + // just in case to cover all of our bases. + self.raw_module + .insert_section(Section::Export(Default::default())) + .expect("an export section can be always inserted if it doesn't exist; qed"); + } + self.raw_module + .export_section_mut() + .expect("export section already existed or we just added it above, so it always exists; qed") + .entries_mut() + .push(ExportEntry::new(memory_name, Internal::Memory(0))); + + break + } + + Ok(()) + } + + /// Increases the number of memory pages requested by the WASM blob by + /// the given amount of `extra_heap_pages`. + /// + /// Will return an error in case there is no memory section present, + /// or if the memory section is empty. + /// + /// Only modifies the initial size of the memory; the maximum is unmodified + /// unless it's smaller than the initial size, in which case it will be increased + /// so that it's at least as big as the initial size. + pub fn add_extra_heap_pages_to_memory_section( + &mut self, + extra_heap_pages: u32, + ) -> Result<(), WasmError> { + let memory_section = self + .raw_module + .memory_section_mut() + .ok_or_else(|| WasmError::Other("no memory section found".into()))?; + + if memory_section.entries().is_empty() { + return Err(WasmError::Other("memory section is empty".into())) + } + for memory_ty in memory_section.entries_mut() { + let min = memory_ty.limits().initial().saturating_add(extra_heap_pages); + let max = memory_ty.limits().maximum().map(|max| std::cmp::max(min, max)); + *memory_ty = MemoryType::new(min, max); + } + Ok(()) + } + /// Returns an iterator of all globals which were exported by [`expose_mutable_globals`]. - pub(super) fn exported_internal_global_names<'module>( - &'module self, - ) -> impl Iterator { + pub(super) fn exported_internal_global_names(&self) -> impl Iterator { let exports = self.raw_module.export_section().map(|es| es.entries()).unwrap_or(&[]); exports.iter().filter_map(|export| match export.internal() { Internal::Global(_) if export.field().starts_with("exported_internal_global") => diff --git a/client/executor/common/src/sandbox.rs b/client/executor/common/src/sandbox.rs deleted file mode 100644 index b627294241252..0000000000000 --- a/client/executor/common/src/sandbox.rs +++ /dev/null @@ -1,1090 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! This module implements sandboxing support in the runtime. -//! -//! Sandboxing is backed by wasmi and wasmer, depending on the configuration. - -use crate::{ - error::{Error, Result}, - util, -}; -use codec::{Decode, Encode}; -use sp_core::sandbox as sandbox_primitives; -use sp_wasm_interface::{FunctionContext, Pointer, WordSize}; -use std::{collections::HashMap, rc::Rc}; -use wasmi::{ - memory_units::Pages, Externals, ImportResolver, MemoryInstance, Module, ModuleInstance, - RuntimeArgs, RuntimeValue, Trap, TrapKind, -}; - -#[cfg(feature = "wasmer-sandbox")] -use crate::util::wasmer::MemoryWrapper as WasmerMemoryWrapper; -use crate::util::wasmi::MemoryWrapper as WasmiMemoryWrapper; - -environmental::environmental!(SandboxContextStore: trait SandboxContext); - -/// Index of a function inside the supervisor. -/// -/// This is a typically an index in the default table of the supervisor, however -/// the exact meaning of this index is depends on the implementation of dispatch function. -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct SupervisorFuncIndex(usize); - -impl From for usize { - fn from(index: SupervisorFuncIndex) -> Self { - index.0 - } -} - -/// Index of a function within guest index space. -/// -/// This index is supposed to be used as index for `Externals`. -#[derive(Copy, Clone, Debug, PartialEq)] -struct GuestFuncIndex(usize); - -/// This struct holds a mapping from guest index space to supervisor. -struct GuestToSupervisorFunctionMapping { - /// Position of elements in this vector are interpreted - /// as indices of guest functions and are mapped to - /// corresponding supervisor function indices. - funcs: Vec, -} - -impl GuestToSupervisorFunctionMapping { - /// Create an empty function mapping - fn new() -> GuestToSupervisorFunctionMapping { - GuestToSupervisorFunctionMapping { funcs: Vec::new() } - } - - /// Add a new supervisor function to the mapping. - /// Returns a newly assigned guest function index. - fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex { - let idx = self.funcs.len(); - self.funcs.push(supervisor_func); - GuestFuncIndex(idx) - } - - /// Find supervisor function index by its corresponding guest function index - fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option { - self.funcs.get(guest_func_idx.0).cloned() - } -} - -/// Holds sandbox function and memory imports and performs name resolution -struct Imports { - /// Maps qualified function name to its guest function index - func_map: HashMap<(Vec, Vec), GuestFuncIndex>, - - /// Maps qualified field name to its memory reference - memories_map: HashMap<(Vec, Vec), Memory>, -} - -impl Imports { - fn func_by_name(&self, module_name: &str, func_name: &str) -> Option { - self.func_map - .get(&(module_name.as_bytes().to_owned(), func_name.as_bytes().to_owned())) - .cloned() - } - - fn memory_by_name(&self, module_name: &str, memory_name: &str) -> Option { - self.memories_map - .get(&(module_name.as_bytes().to_owned(), memory_name.as_bytes().to_owned())) - .cloned() - } -} - -impl ImportResolver for Imports { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &::wasmi::Signature, - ) -> std::result::Result { - let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { - wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) - })?; - - Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx.0)) - } - - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - _memory_type: &::wasmi::MemoryDescriptor, - ) -> std::result::Result { - let mem = self.memory_by_name(module_name, field_name).ok_or_else(|| { - wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) - })?; - - let wrapper = mem.as_wasmi().ok_or_else(|| { - wasmi::Error::Instantiation(format!( - "Unsupported non-wasmi export {}:{}", - module_name, field_name - )) - })?; - - // Here we use inner memory reference only to resolve - // the imports without accessing the memory contents. - let mem = unsafe { wrapper.clone_inner() }; - - Ok(mem) - } - - fn resolve_global( - &self, - module_name: &str, - field_name: &str, - _global_type: &::wasmi::GlobalDescriptor, - ) -> std::result::Result { - Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) - } - - fn resolve_table( - &self, - module_name: &str, - field_name: &str, - _table_type: &::wasmi::TableDescriptor, - ) -> std::result::Result { - Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) - } -} - -/// The sandbox context used to execute sandboxed functions. -pub trait SandboxContext { - /// Invoke a function in the supervisor environment. - /// - /// This first invokes the dispatch thunk function, passing in the function index of the - /// desired function to call and serialized arguments. The thunk calls the desired function - /// with the deserialized arguments, then serializes the result into memory and returns - /// reference. The pointer to and length of the result in linear memory is encoded into an - /// `i64`, with the upper 32 bits representing the pointer and the lower 32 bits representing - /// the length. - /// - /// # Errors - /// - /// Returns `Err` if the dispatch_thunk function has an incorrect signature or traps during - /// execution. - fn invoke( - &mut self, - invoke_args_ptr: Pointer, - invoke_args_len: WordSize, - state: u32, - func_idx: SupervisorFuncIndex, - ) -> Result; - - /// Returns the supervisor context. - fn supervisor_context(&mut self) -> &mut dyn FunctionContext; -} - -/// Implementation of [`Externals`] that allows execution of guest module with -/// [externals][`Externals`] that might refer functions defined by supervisor. -/// -/// [`Externals`]: ../wasmi/trait.Externals.html -pub struct GuestExternals<'a> { - /// Instance of sandboxed module to be dispatched - sandbox_instance: &'a SandboxInstance, - - /// External state passed to guest environment, see the `instantiate` function - state: u32, -} - -/// Construct trap error from specified message -fn trap(msg: &'static str) -> Trap { - TrapKind::Host(Box::new(Error::Other(msg.into()))).into() -} - -fn deserialize_result( - mut serialized_result: &[u8], -) -> std::result::Result, Trap> { - use self::sandbox_primitives::HostError; - use sp_wasm_interface::ReturnValue; - let result_val = std::result::Result::::decode(&mut serialized_result) - .map_err(|_| trap("Decoding Result failed!"))?; - - match result_val { - Ok(return_value) => Ok(match return_value { - ReturnValue::Unit => None, - ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)), - }), - Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")), - } -} - -impl<'a> Externals for GuestExternals<'a> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> std::result::Result, Trap> { - SandboxContextStore::with(|sandbox_context| { - // Make `index` typesafe again. - let index = GuestFuncIndex(index); - - // Convert function index from guest to supervisor space - let func_idx = self.sandbox_instance - .guest_to_supervisor_mapping - .func_by_guest_index(index) - .expect( - "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; - `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; - `func_by_guest_index` called with `index` can't return `None`; - qed" - ); - - // Serialize arguments into a byte vector. - let invoke_args_data: Vec = args - .as_ref() - .iter() - .cloned() - .map(sp_wasm_interface::Value::from) - .collect::>() - .encode(); - - let state = self.state; - - // Move serialized arguments inside the memory, invoke dispatch thunk and - // then free allocated memory. - let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = sandbox_context - .supervisor_context() - .allocate_memory(invoke_args_len) - .map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?; - - let deallocate = |supervisor_context: &mut dyn FunctionContext, ptr, fail_msg| { - supervisor_context.deallocate_memory(ptr).map_err(|_| trap(fail_msg)) - }; - - if sandbox_context - .supervisor_context() - .write_memory(invoke_args_ptr, &invoke_args_data) - .is_err() - { - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Failed dealloction after failed write of invoke arguments", - )?; - return Err(trap("Can't write invoke args into memory")) - } - - let result = sandbox_context.invoke( - invoke_args_ptr, - invoke_args_len, - state, - func_idx, - ); - - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Can't deallocate memory for dispatch thunk's invoke arguments", - )?; - let result = result?; - - // dispatch_thunk returns pointer to serialized arguments. - // Unpack pointer and len of the serialized result data. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = result as u64; - let ptr = (v as u64 >> 32) as u32; - let len = (v & 0xFFFFFFFF) as u32; - (Pointer::new(ptr), len) - }; - - let serialized_result_val = sandbox_context - .supervisor_context() - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| trap("Can't read the serialized result from dispatch thunk")); - - deallocate( - sandbox_context.supervisor_context(), - serialized_result_val_ptr, - "Can't deallocate memory for dispatch thunk's result", - ) - .and_then(|_| serialized_result_val) - .and_then(|serialized_result_val| deserialize_result(&serialized_result_val)) - }).expect("SandboxContextStore is set when invoking sandboxed functions; qed") - } -} - -fn with_guest_externals(sandbox_instance: &SandboxInstance, state: u32, f: F) -> R -where - F: FnOnce(&mut GuestExternals) -> R, -{ - f(&mut GuestExternals { sandbox_instance, state }) -} - -/// Module instance in terms of selected backend -enum BackendInstance { - /// Wasmi module instance - Wasmi(wasmi::ModuleRef), - - /// Wasmer module instance - #[cfg(feature = "wasmer-sandbox")] - Wasmer(wasmer::Instance), -} - -/// Sandboxed instance of a wasm module. -/// -/// It's primary purpose is to [`invoke`] exported functions on it. -/// -/// All imports of this instance are specified at the creation time and -/// imports are implemented by the supervisor. -/// -/// Hence, in order to invoke an exported function on a sandboxed module instance, -/// it's required to provide supervisor externals: it will be used to execute -/// code in the supervisor context. -/// -/// This is generic over a supervisor function reference type. -/// -/// [`invoke`]: #method.invoke -pub struct SandboxInstance { - backend_instance: BackendInstance, - guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, -} - -impl SandboxInstance { - /// Invoke an exported function by a name. - /// - /// `supervisor_externals` is required to execute the implementations - /// of the syscalls that published to a sandboxed module instance. - /// - /// The `state` parameter can be used to provide custom data for - /// these syscall implementations. - pub fn invoke( - &self, - - // function to call that is exported from the module - export_name: &str, - - // arguments passed to the function - args: &[RuntimeValue], - - // arbitraty context data of the call - state: u32, - - sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result, wasmi::Error> { - match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => - with_guest_externals(self, state, |guest_externals| { - let wasmi_result = SandboxContextStore::using(sandbox_context, || { - wasmi_instance.invoke_export(export_name, args, guest_externals) - })?; - - Ok(wasmi_result) - }), - - #[cfg(feature = "wasmer-sandbox")] - BackendInstance::Wasmer(wasmer_instance) => { - let function = wasmer_instance - .exports - .get_function(export_name) - .map_err(|error| wasmi::Error::Function(error.to_string()))?; - - let args: Vec = args - .iter() - .map(|v| match *v { - RuntimeValue::I32(val) => wasmer::Val::I32(val), - RuntimeValue::I64(val) => wasmer::Val::I64(val), - RuntimeValue::F32(val) => wasmer::Val::F32(val.into()), - RuntimeValue::F64(val) => wasmer::Val::F64(val.into()), - }) - .collect(); - - let wasmer_result = SandboxContextStore::using(sandbox_context, || { - function.call(&args).map_err(|error| wasmi::Error::Function(error.to_string())) - })?; - - if wasmer_result.len() > 1 { - return Err(wasmi::Error::Function( - "multiple return types are not supported yet".into(), - )) - } - - wasmer_result - .first() - .map(|wasm_value| { - let wasmer_value = match *wasm_value { - wasmer::Val::I32(val) => RuntimeValue::I32(val), - wasmer::Val::I64(val) => RuntimeValue::I64(val), - wasmer::Val::F32(val) => RuntimeValue::F32(val.into()), - wasmer::Val::F64(val) => RuntimeValue::F64(val.into()), - _ => - return Err(wasmi::Error::Function(format!( - "Unsupported return value: {:?}", - wasm_value, - ))), - }; - - Ok(wasmer_value) - }) - .transpose() - }, - } - } - - /// Get the value from a global with the given `name`. - /// - /// Returns `Some(_)` if the global could be found. - pub fn get_global_val(&self, name: &str) -> Option { - match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => { - let wasmi_global = wasmi_instance.export_by_name(name)?.as_global()?.get(); - - Some(wasmi_global.into()) - }, - - #[cfg(feature = "wasmer-sandbox")] - BackendInstance::Wasmer(wasmer_instance) => { - use sp_wasm_interface::Value; - - let global = wasmer_instance.exports.get_global(name).ok()?; - let wasmtime_value = match global.get() { - wasmer::Val::I32(val) => Value::I32(val), - wasmer::Val::I64(val) => Value::I64(val), - wasmer::Val::F32(val) => Value::F32(f32::to_bits(val)), - wasmer::Val::F64(val) => Value::F64(f64::to_bits(val)), - _ => None?, - }; - - Some(wasmtime_value) - }, - } - } -} - -/// Error occurred during instantiation of a sandboxed module. -pub enum InstantiationError { - /// Something wrong with the environment definition. It either can't - /// be decoded, have a reference to a non-existent or torn down memory instance. - EnvironmentDefinitionCorrupted, - /// Provided module isn't recognized as a valid webassembly binary. - ModuleDecoding, - /// Module is a well-formed webassembly binary but could not be instantiated. This could - /// happen because, e.g. the module imports entries not provided by the environment. - Instantiation, - /// Module is well-formed, instantiated and linked, but while executing the start function - /// a trap was generated. - StartTrapped, -} - -fn decode_environment_definition( - mut raw_env_def: &[u8], - memories: &[Option], -) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> { - let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut raw_env_def) - .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?; - - let mut func_map = HashMap::new(); - let mut memories_map = HashMap::new(); - let mut guest_to_supervisor_mapping = GuestToSupervisorFunctionMapping::new(); - - for entry in &env_def.entries { - let module = entry.module_name.clone(); - let field = entry.field_name.clone(); - - match entry.entity { - sandbox_primitives::ExternEntity::Function(func_idx) => { - let externals_idx = - guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize)); - func_map.insert((module, field), externals_idx); - }, - sandbox_primitives::ExternEntity::Memory(memory_idx) => { - let memory_ref = memories - .get(memory_idx as usize) - .cloned() - .ok_or_else(|| InstantiationError::EnvironmentDefinitionCorrupted)? - .ok_or_else(|| InstantiationError::EnvironmentDefinitionCorrupted)?; - memories_map.insert((module, field), memory_ref); - }, - } - } - - Ok((Imports { func_map, memories_map }, guest_to_supervisor_mapping)) -} - -/// An environment in which the guest module is instantiated. -pub struct GuestEnvironment { - /// Function and memory imports of the guest module - imports: Imports, - - /// Supervisor functinons mapped to guest index space - guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, -} - -impl GuestEnvironment { - /// Decodes an environment definition from the given raw bytes. - /// - /// Returns `Err` if the definition cannot be decoded. - pub fn decode

( - store: &Store
, - raw_env_def: &[u8], - ) -> std::result::Result { - let (imports, guest_to_supervisor_mapping) = - decode_environment_definition(raw_env_def, &store.memories)?; - Ok(Self { imports, guest_to_supervisor_mapping }) - } -} - -/// An unregistered sandboxed instance. -/// -/// To finish off the instantiation the user must call `register`. -#[must_use] -pub struct UnregisteredInstance { - sandbox_instance: Rc, -} - -impl UnregisteredInstance { - /// Finalizes instantiation of this module. - pub fn register
(self, store: &mut Store
, dispatch_thunk: DT) -> u32 { - // At last, register the instance. - store.register_sandbox_instance(self.sandbox_instance, dispatch_thunk) - } -} - -/// Sandbox backend to use -pub enum SandboxBackend { - /// Wasm interpreter - Wasmi, - - /// Wasmer environment - #[cfg(feature = "wasmer-sandbox")] - Wasmer, - - /// Use wasmer backend if available. Fall back to wasmi otherwise. - TryWasmer, -} - -/// Memory reference in terms of a selected backend -#[derive(Clone, Debug)] -pub enum Memory { - /// Wasmi memory reference - Wasmi(WasmiMemoryWrapper), - - /// Wasmer memory refernce - #[cfg(feature = "wasmer-sandbox")] - Wasmer(WasmerMemoryWrapper), -} - -impl Memory { - /// View as wasmi memory - pub fn as_wasmi(&self) -> Option { - match self { - Memory::Wasmi(memory) => Some(memory.clone()), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(_) => None, - } - } - - /// View as wasmer memory - #[cfg(feature = "wasmer-sandbox")] - pub fn as_wasmer(&self) -> Option { - match self { - Memory::Wasmer(memory) => Some(memory.clone()), - Memory::Wasmi(_) => None, - } - } -} - -impl util::MemoryTransfer for Memory { - fn read(&self, source_addr: Pointer, size: usize) -> Result> { - match self { - Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read(source_addr, size), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read(source_addr, size), - } - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { - match self { - Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination), - } - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { - match self { - Memory::Wasmi(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source), - } - } -} - -/// Wasmer specific context -#[cfg(feature = "wasmer-sandbox")] -struct WasmerBackend { - store: wasmer::Store, -} - -/// Information specific to a particular execution backend -enum BackendContext { - /// Wasmi specific context - Wasmi, - - /// Wasmer specific context - #[cfg(feature = "wasmer-sandbox")] - Wasmer(WasmerBackend), -} - -impl BackendContext { - pub fn new(backend: SandboxBackend) -> BackendContext { - match backend { - SandboxBackend::Wasmi => BackendContext::Wasmi, - - #[cfg(not(feature = "wasmer-sandbox"))] - SandboxBackend::TryWasmer => BackendContext::Wasmi, - - #[cfg(feature = "wasmer-sandbox")] - SandboxBackend::Wasmer | SandboxBackend::TryWasmer => { - let compiler = wasmer_compiler_singlepass::Singlepass::default(); - - BackendContext::Wasmer(WasmerBackend { - store: wasmer::Store::new(&wasmer::JIT::new(compiler).engine()), - }) - }, - } - } -} - -/// This struct keeps track of all sandboxed components. -/// -/// This is generic over a supervisor function reference type. -pub struct Store
{ - /// Stores the instance and the dispatch thunk associated to per instance. - /// - /// Instances are `Some` until torn down. - instances: Vec, DT)>>, - /// Memories are `Some` until torn down. - memories: Vec>, - backend_context: BackendContext, -} - -impl Store
{ - /// Create a new empty sandbox store. - pub fn new(backend: SandboxBackend) -> Self { - Store { - instances: Vec::new(), - memories: Vec::new(), - backend_context: BackendContext::new(backend), - } - } - - /// Create a new memory instance and return it's index. - /// - /// # Errors - /// - /// Returns `Err` if the memory couldn't be created. - /// Typically happens if `initial` is more than `maximum`. - pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result { - let memories = &mut self.memories; - let backend_context = &self.backend_context; - - let maximum = match maximum { - sandbox_primitives::MEM_UNLIMITED => None, - specified_limit => Some(specified_limit), - }; - - let memory = match &backend_context { - BackendContext::Wasmi => Memory::Wasmi(WasmiMemoryWrapper::new(MemoryInstance::alloc( - Pages(initial as usize), - maximum.map(|m| Pages(m as usize)), - )?)), - - #[cfg(feature = "wasmer-sandbox")] - BackendContext::Wasmer(context) => { - let ty = wasmer::MemoryType::new(initial, maximum, false); - Memory::Wasmer(WasmerMemoryWrapper::new( - wasmer::Memory::new(&context.store, ty) - .map_err(|_| Error::InvalidMemoryReference)?, - )) - }, - }; - - let mem_idx = memories.len(); - memories.push(Some(memory.clone())); - - Ok(mem_idx as u32) - } - - /// Returns `SandboxInstance` by `instance_idx`. - /// - /// # Errors - /// - /// Returns `Err` If `instance_idx` isn't a valid index of an instance or - /// instance is already torndown. - pub fn instance(&self, instance_idx: u32) -> Result> { - self.instances - .get(instance_idx as usize) - .ok_or_else(|| "Trying to access a non-existent instance")? - .as_ref() - .map(|v| v.0.clone()) - .ok_or_else(|| "Trying to access a torndown instance".into()) - } - - /// Returns dispatch thunk by `instance_idx`. - /// - /// # Errors - /// - /// Returns `Err` If `instance_idx` isn't a valid index of an instance or - /// instance is already torndown. - pub fn dispatch_thunk(&self, instance_idx: u32) -> Result
{ - self.instances - .get(instance_idx as usize) - .as_ref() - .ok_or_else(|| "Trying to access a non-existent instance")? - .as_ref() - .map(|v| v.1.clone()) - .ok_or_else(|| "Trying to access a torndown instance".into()) - } - - /// Returns reference to a memory instance by `memory_idx`. - /// - /// # Errors - /// - /// Returns `Err` If `memory_idx` isn't a valid index of an memory or - /// if memory has been torn down. - pub fn memory(&self, memory_idx: u32) -> Result { - self.memories - .get(memory_idx as usize) - .cloned() - .ok_or_else(|| "Trying to access a non-existent sandboxed memory")? - .ok_or_else(|| "Trying to access a torndown sandboxed memory".into()) - } - - /// Tear down the memory at the specified index. - /// - /// # Errors - /// - /// Returns `Err` if `memory_idx` isn't a valid index of an memory or - /// if it has been torn down. - pub fn memory_teardown(&mut self, memory_idx: u32) -> Result<()> { - match self.memories.get_mut(memory_idx as usize) { - None => Err("Trying to teardown a non-existent sandboxed memory".into()), - Some(None) => Err("Double teardown of a sandboxed memory".into()), - Some(memory) => { - *memory = None; - Ok(()) - }, - } - } - - /// Tear down the instance at the specified index. - /// - /// # Errors - /// - /// Returns `Err` if `instance_idx` isn't a valid index of an instance or - /// if it has been torn down. - pub fn instance_teardown(&mut self, instance_idx: u32) -> Result<()> { - match self.instances.get_mut(instance_idx as usize) { - None => Err("Trying to teardown a non-existent instance".into()), - Some(None) => Err("Double teardown of an instance".into()), - Some(instance) => { - *instance = None; - Ok(()) - }, - } - } - - /// Instantiate a guest module and return it's index in the store. - /// - /// The guest module's code is specified in `wasm`. Environment that will be available to - /// guest module is specified in `guest_env`. A dispatch thunk is used as function that - /// handle calls from guests. `state` is an opaque pointer to caller's arbitrary context - /// normally created by `sp_sandbox::Instance` primitive. - /// - /// Note: Due to borrowing constraints dispatch thunk is now propagated using DTH - /// - /// Returns uninitialized sandboxed module instance or an instantiation error. - pub fn instantiate( - &mut self, - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result { - let sandbox_instance = match self.backend_context { - BackendContext::Wasmi => - Self::instantiate_wasmi(wasm, guest_env, state, sandbox_context)?, - - #[cfg(feature = "wasmer-sandbox")] - BackendContext::Wasmer(ref context) => - Self::instantiate_wasmer(&context, wasm, guest_env, state, sandbox_context)?, - }; - - Ok(UnregisteredInstance { sandbox_instance }) - } -} - -// Private routines -impl
Store
{ - fn register_sandbox_instance( - &mut self, - sandbox_instance: Rc, - dispatch_thunk: DT, - ) -> u32 { - let instance_idx = self.instances.len(); - self.instances.push(Some((sandbox_instance, dispatch_thunk))); - instance_idx as u32 - } - - fn instantiate_wasmi( - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result, InstantiationError> { - let wasmi_module = - Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; - let wasmi_instance = ModuleInstance::new(&wasmi_module, &guest_env.imports) - .map_err(|_| InstantiationError::Instantiation)?; - - let sandbox_instance = Rc::new(SandboxInstance { - // In general, it's not a very good idea to use `.not_started_instance()` for - // anything but for extracting memory and tables. But in this particular case, we - // are extracting for the purpose of running `start` function which should be ok. - backend_instance: BackendInstance::Wasmi(wasmi_instance.not_started_instance().clone()), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - }); - - with_guest_externals(&sandbox_instance, state, |guest_externals| { - SandboxContextStore::using(sandbox_context, || { - wasmi_instance - .run_start(guest_externals) - .map_err(|_| InstantiationError::StartTrapped) - }) - - // Note: no need to run start on wasmtime instance, since it's done - // automatically - })?; - - Ok(sandbox_instance) - } - - #[cfg(feature = "wasmer-sandbox")] - fn instantiate_wasmer( - context: &WasmerBackend, - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result, InstantiationError> { - let module = wasmer::Module::new(&context.store, wasm) - .map_err(|_| InstantiationError::ModuleDecoding)?; - - type Exports = HashMap; - let mut exports_map = Exports::new(); - - for import in module.imports().into_iter() { - match import.ty() { - // Nothing to do here - wasmer::ExternType::Global(_) | wasmer::ExternType::Table(_) => (), - - wasmer::ExternType::Memory(_) => { - let exports = exports_map - .entry(import.module().to_string()) - .or_insert(wasmer::Exports::new()); - - let memory = guest_env - .imports - .memory_by_name(import.module(), import.name()) - .ok_or(InstantiationError::ModuleDecoding)?; - - let mut wasmer_memory_ref = memory.as_wasmer().expect( - "memory is created by wasmer; \ - exported by the same module and backend; \ - thus the operation can't fail; \ - qed", - ); - - // This is safe since we're only instantiating the module and populating - // the export table, so no memory access can happen at this time. - // All subsequent memory accesses should happen through the wrapper, - // that enforces the memory access protocol. - let wasmer_memory = unsafe { wasmer_memory_ref.clone_inner() }; - - exports.insert(import.name(), wasmer::Extern::Memory(wasmer_memory)); - }, - - wasmer::ExternType::Function(func_ty) => { - let guest_func_index = - guest_env.imports.func_by_name(import.module(), import.name()); - - let guest_func_index = if let Some(index) = guest_func_index { - index - } else { - // Missing import (should we abort here?) - continue - }; - - let supervisor_func_index = guest_env - .guest_to_supervisor_mapping - .func_by_guest_index(guest_func_index) - .ok_or(InstantiationError::ModuleDecoding)?; - - let function = Self::wasmer_dispatch_function( - supervisor_func_index, - &context.store, - func_ty, - state, - ); - - let exports = exports_map - .entry(import.module().to_string()) - .or_insert(wasmer::Exports::new()); - - exports.insert(import.name(), wasmer::Extern::Function(function)); - }, - } - } - - let mut import_object = wasmer::ImportObject::new(); - for (module_name, exports) in exports_map.into_iter() { - import_object.register(module_name, exports); - } - - let instance = SandboxContextStore::using(sandbox_context, || { - wasmer::Instance::new(&module, &import_object).map_err(|error| match error { - wasmer::InstantiationError::Link(_) => InstantiationError::Instantiation, - wasmer::InstantiationError::Start(_) => InstantiationError::StartTrapped, - wasmer::InstantiationError::HostEnvInitialization(_) => - InstantiationError::EnvironmentDefinitionCorrupted, - }) - })?; - - Ok(Rc::new(SandboxInstance { - backend_instance: BackendInstance::Wasmer(instance), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - })) - } - - #[cfg(feature = "wasmer-sandbox")] - fn wasmer_dispatch_function( - supervisor_func_index: SupervisorFuncIndex, - store: &wasmer::Store, - func_ty: &wasmer::FunctionType, - state: u32, - ) -> wasmer::Function { - wasmer::Function::new(store, func_ty, move |params| { - SandboxContextStore::with(|sandbox_context| { - use sp_wasm_interface::Value; - - // Serialize arguments into a byte vector. - let invoke_args_data = params - .iter() - .map(|val| match val { - wasmer::Val::I32(val) => Ok(Value::I32(*val)), - wasmer::Val::I64(val) => Ok(Value::I64(*val)), - wasmer::Val::F32(val) => Ok(Value::F32(f32::to_bits(*val))), - wasmer::Val::F64(val) => Ok(Value::F64(f64::to_bits(*val))), - _ => Err(wasmer::RuntimeError::new(format!( - "Unsupported function argument: {:?}", - val - ))), - }) - .collect::, _>>()? - .encode(); - - // Move serialized arguments inside the memory, invoke dispatch thunk and - // then free allocated memory. - let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = sandbox_context - .supervisor_context() - .allocate_memory(invoke_args_len) - .map_err(|_| { - wasmer::RuntimeError::new( - "Can't allocate memory in supervisor for the arguments", - ) - })?; - - let deallocate = |fe: &mut dyn FunctionContext, ptr, fail_msg| { - fe.deallocate_memory(ptr).map_err(|_| wasmer::RuntimeError::new(fail_msg)) - }; - - if sandbox_context - .supervisor_context() - .write_memory(invoke_args_ptr, &invoke_args_data) - .is_err() - { - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Failed dealloction after failed write of invoke arguments", - )?; - - return Err(wasmer::RuntimeError::new("Can't write invoke args into memory")) - } - - // Perform the actuall call - let serialized_result = sandbox_context - .invoke(invoke_args_ptr, invoke_args_len, state, supervisor_func_index) - .map_err(|e| wasmer::RuntimeError::new(e.to_string()))?; - - // dispatch_thunk returns pointer to serialized arguments. - // Unpack pointer and len of the serialized result data. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = serialized_result as u64; - let ptr = (v as u64 >> 32) as u32; - let len = (v & 0xFFFFFFFF) as u32; - (Pointer::new(ptr), len) - }; - - let serialized_result_val = sandbox_context - .supervisor_context() - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| { - wasmer::RuntimeError::new( - "Can't read the serialized result from dispatch thunk", - ) - }); - - let deserialized_result = deallocate( - sandbox_context.supervisor_context(), - serialized_result_val_ptr, - "Can't deallocate memory for dispatch thunk's result", - ) - .and_then(|_| serialized_result_val) - .and_then(|serialized_result_val| { - deserialize_result(&serialized_result_val) - .map_err(|e| wasmer::RuntimeError::new(e.to_string())) - })?; - - if let Some(value) = deserialized_result { - Ok(vec![match value { - RuntimeValue::I32(val) => wasmer::Val::I32(val), - RuntimeValue::I64(val) => wasmer::Val::I64(val), - RuntimeValue::F32(val) => wasmer::Val::F32(val.into()), - RuntimeValue::F64(val) => wasmer::Val::F64(val.into()), - }]) - } else { - Ok(vec![]) - } - }) - .expect("SandboxContextStore is set when invoking sandboxed functions; qed") - }) - } -} diff --git a/client/executor/common/src/util.rs b/client/executor/common/src/util.rs index 3ea29540f98ee..fbae01b556fb1 100644 --- a/client/executor/common/src/util.rs +++ b/client/executor/common/src/util.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,7 +18,7 @@ //! Utilities used by all backends -use crate::error::{Error, Result}; +use crate::error::Result; use sp_wasm_interface::Pointer; use std::ops::Range; @@ -50,192 +50,3 @@ pub trait MemoryTransfer { /// Returns an error if the write would go out of the memory bounds. fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()>; } - -/// Safe wrapper over wasmi memory reference -pub mod wasmi { - use super::*; - - /// Wasmi provides direct access to its memory using slices. - /// - /// This wrapper limits the scope where the slice can be taken to - #[derive(Debug, Clone)] - pub struct MemoryWrapper(::wasmi::MemoryRef); - - impl MemoryWrapper { - /// Take ownership of the memory region and return a wrapper object - pub fn new(memory: ::wasmi::MemoryRef) -> Self { - Self(memory) - } - - /// Clone the underlying memory object - /// - /// # Safety - /// - /// The sole purpose of `MemoryRef` is to protect the memory from uncontrolled - /// access. By returning the memory object "as is" we bypass all of the checks. - /// - /// Intended to use only during module initialization. - pub unsafe fn clone_inner(&self) -> ::wasmi::MemoryRef { - self.0.clone() - } - } - - impl super::MemoryTransfer for MemoryWrapper { - fn read(&self, source_addr: Pointer, size: usize) -> Result> { - self.0.with_direct_access(|source| { - let range = checked_range(source_addr.into(), size, source.len()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - Ok(Vec::from(&source[range])) - }) - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { - self.0.with_direct_access(|source| { - let range = checked_range(source_addr.into(), destination.len(), source.len()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - destination.copy_from_slice(&source[range]); - Ok(()) - }) - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { - self.0.with_direct_access_mut(|destination| { - let range = checked_range(dest_addr.into(), source.len(), destination.len()) - .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; - - destination[range].copy_from_slice(source); - Ok(()) - }) - } - } -} - -// Routines specific to Wasmer runtime. Since sandbox can be invoked from both -/// wasmi and wasmtime runtime executors, we need to have a way to deal with sanbox -/// backends right from the start. -#[cfg(feature = "wasmer-sandbox")] -pub mod wasmer { - use super::checked_range; - use crate::error::{Error, Result}; - use sp_wasm_interface::Pointer; - use std::{cell::RefCell, convert::TryInto, rc::Rc}; - - /// In order to enforce memory access protocol to the backend memory - /// we wrap it with `RefCell` and encapsulate all memory operations. - #[derive(Debug, Clone)] - pub struct MemoryWrapper { - buffer: Rc>, - } - - impl MemoryWrapper { - /// Take ownership of the memory region and return a wrapper object - pub fn new(memory: wasmer::Memory) -> Self { - Self { buffer: Rc::new(RefCell::new(memory)) } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// Wasmer doesn't provide comprehensive documentation about the exact behavior of the data - /// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since - /// growing, we cannot guarantee the lifetime of the returned slice reference. - unsafe fn memory_as_slice(memory: &wasmer::Memory) -> &[u8] { - let ptr = memory.data_ptr() as *const _; - let len: usize = - memory.data_size().try_into().expect("data size should fit into usize"); - - if len == 0 { - &[] - } else { - core::slice::from_raw_parts(ptr, len) - } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is - /// returned it must be ensured that only one mutable and no shared references to memory - /// exists at the same time. - unsafe fn memory_as_slice_mut(memory: &wasmer::Memory) -> &mut [u8] { - let ptr = memory.data_ptr(); - let len: usize = - memory.data_size().try_into().expect("data size should fit into usize"); - - if len == 0 { - &mut [] - } else { - core::slice::from_raw_parts_mut(ptr, len) - } - } - - /// Clone the underlying memory object - /// - /// # Safety - /// - /// The sole purpose of `MemoryRef` is to protect the memory from uncontrolled - /// access. By returning the memory object "as is" we bypass all of the checks. - /// - /// Intended to use only during module initialization. - /// - /// # Panics - /// - /// Will panic if `MemoryRef` is currently in use. - pub unsafe fn clone_inner(&mut self) -> wasmer::Memory { - // We take exclusive lock to ensure that we're the only one here - self.buffer.borrow_mut().clone() - } - } - - impl super::MemoryTransfer for MemoryWrapper { - fn read(&self, source_addr: Pointer, size: usize) -> Result> { - let memory = self.buffer.borrow(); - - let data_size = memory.data_size().try_into().expect("data size does not fit"); - - let range = checked_range(source_addr.into(), size, data_size) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - let mut buffer = vec![0; range.len()]; - self.read_into(source_addr, &mut buffer)?; - - Ok(buffer) - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { - unsafe { - let memory = self.buffer.borrow(); - - // This should be safe since we don't grow up memory while caching this reference - // and we give up the reference before returning from this function. - let source = Self::memory_as_slice(&memory); - - let range = checked_range(source_addr.into(), destination.len(), source.len()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - destination.copy_from_slice(&source[range]); - Ok(()) - } - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { - unsafe { - let memory = self.buffer.borrow_mut(); - - // This should be safe since we don't grow up memory while caching this reference - // and we give up the reference before returning from this function. - let destination = Self::memory_as_slice_mut(&memory); - - let range = checked_range(dest_addr.into(), source.len(), destination.len()) - .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; - - &mut destination[range].copy_from_slice(source); - Ok(()) - } - } - } -} diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index eb73909d9234f..d0cc8926144be 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,6 +21,8 @@ use crate::error::Error; use sp_wasm_interface::Value; +pub use sc_allocator::AllocationStats; + /// A method to be used to find the entrypoint when calling into the runtime /// /// Contains variants on how to resolve wasm function that will be invoked. @@ -78,21 +80,34 @@ pub trait WasmInstance: Send { /// Before execution, instance is reset. /// /// Returns the encoded result on success. - fn call(&self, method: InvokeMethod, data: &[u8]) -> Result, Error>; + fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result, Error> { + self.call_with_allocation_stats(method, data).0 + } + + /// Call a method on this WASM instance. + /// + /// Before execution, instance is reset. + /// + /// Returns the encoded result on success. + fn call_with_allocation_stats( + &mut self, + method: InvokeMethod, + data: &[u8], + ) -> (Result, Error>, Option); /// Call an exported method on this WASM instance. /// /// Before execution, instance is reset. /// /// Returns the encoded result on success. - fn call_export(&self, method: &str, data: &[u8]) -> Result, Error> { + fn call_export(&mut self, method: &str, data: &[u8]) -> Result, Error> { self.call(method.into(), data) } /// Get the value from a global with the given `name`. /// /// This method is only suitable for getting immutable globals. - fn get_global_const(&self, name: &str) -> Result, Error>; + fn get_global_const(&mut self, name: &str) -> Result, Error>; /// **Testing Only**. This function returns the base address of the linear memory. /// diff --git a/client/executor/runtime-test/Cargo.toml b/client/executor/runtime-test/Cargo.toml index a4fbc88cf5662..eadf547823bb2 100644 --- a/client/executor/runtime-test/Cargo.toml +++ b/client/executor/runtime-test/Cargo.toml @@ -2,34 +2,30 @@ name = "sc-runtime-test" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" build = "build.rs" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" publish = false -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } -sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/sandbox" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } -sp-tasks = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/tasks" } +sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, features = ["improved_panic_error_reporting"], path = "../../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } [features] -default = [ "std" ] +default = ["std"] std = [ "sp-core/std", "sp-io/std", "sp-runtime/std", - "sp-sandbox/std", "sp-std/std", - "sp-tasks/std", ] diff --git a/client/executor/runtime-test/build.rs b/client/executor/runtime-test/build.rs index 9456d6bc90f4c..27f931a542d4a 100644 --- a/client/executor/runtime-test/build.rs +++ b/client/executor/runtime-test/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/executor/runtime-test/src/lib.rs b/client/executor/runtime-test/src/lib.rs index c9f7d6b1e2970..fc98d1909d00d 100644 --- a/client/executor/runtime-test/src/lib.rs +++ b/client/executor/runtime-test/src/lib.rs @@ -29,8 +29,6 @@ use sp_runtime::{ print, traits::{BlakeTwo256, Hash}, }; -#[cfg(not(feature = "std"))] -use sp_sandbox::Value; extern "C" { #[allow(dead_code)] @@ -54,416 +52,288 @@ static mut MUTABLE_STATIC: u64 = 32; static mut MUTABLE_STATIC_BSS: u64 = 0; sp_core::wasm_export_functions! { - fn test_calling_missing_external() { - unsafe { missing_external() } - } + fn test_calling_missing_external() { + unsafe { missing_external() } + } - fn test_calling_yet_another_missing_external() { - unsafe { yet_another_missing_external() } - } + fn test_calling_yet_another_missing_external() { + unsafe { yet_another_missing_external() } + } - fn test_data_in(input: Vec) -> Vec { - print("set_storage"); - storage::set(b"input", &input); + fn test_data_in(input: Vec) -> Vec { + print("set_storage"); + storage::set(b"input", &input); - print("storage"); - let foo = storage::get(b"foo").unwrap(); + print("storage"); + let foo = storage::get(b"foo").unwrap(); - print("set_storage"); - storage::set(b"baz", &foo); + print("set_storage"); + storage::set(b"baz", &foo); - print("finished!"); - b"all ok!".to_vec() - } + print("finished!"); + b"all ok!".to_vec() + } - fn test_clear_prefix(input: Vec) -> Vec { - storage::clear_prefix(&input, None); - b"all ok!".to_vec() - } - - fn test_empty_return() {} - - fn test_dirty_plenty_memory(heap_base: u32, heap_pages: u32) { - // This piece of code will dirty multiple pages of memory. The number of pages is given by - // the `heap_pages`. It's unit is a wasm page (64KiB). The first page to be cleared - // is a wasm page that that follows the one that holds the `heap_base` address. - // - // This function dirties the **host** pages. I.e. we dirty 4KiB at a time and it will take - // 16 writes to process a single wasm page. - - let mut heap_ptr = heap_base as usize; - - // Find the next wasm page boundary. - let heap_ptr = round_up_to(heap_ptr, 65536); - - // Make it an actual pointer - let heap_ptr = heap_ptr as *mut u8; - - // Traverse the host pages and make each one dirty - let host_pages = heap_pages as usize * 16; - for i in 0..host_pages { - unsafe { - // technically this is an UB, but there is no way Rust can find this out. - heap_ptr.add(i * 4096).write(0); - } - } - - fn round_up_to(n: usize, divisor: usize) -> usize { - (n + divisor - 1) / divisor - } - } - - fn test_exhaust_heap() -> Vec { Vec::with_capacity(16777216) } - - fn test_fp_f32add(a: [u8; 4], b: [u8; 4]) -> [u8; 4] { - let a = f32::from_le_bytes(a); - let b = f32::from_le_bytes(b); - f32::to_le_bytes(a + b) - } - - fn test_panic() { panic!("test panic") } - - fn test_conditional_panic(input: Vec) -> Vec { - if input.len() > 0 { - panic!("test panic") - } - - input - } - - fn test_blake2_256(input: Vec) -> Vec { - blake2_256(&input).to_vec() - } - - fn test_blake2_128(input: Vec) -> Vec { - blake2_128(&input).to_vec() - } - - fn test_sha2_256(input: Vec) -> Vec { - sha2_256(&input).to_vec() - } - - fn test_twox_256(input: Vec) -> Vec { - twox_256(&input).to_vec() - } - - fn test_twox_128(input: Vec) -> Vec { - twox_128(&input).to_vec() - } - - fn test_ed25519_verify(input: Vec) -> bool { - let mut pubkey = [0; 32]; - let mut sig = [0; 64]; - - pubkey.copy_from_slice(&input[0..32]); - sig.copy_from_slice(&input[32..96]); - - let msg = b"all ok!"; - ed25519_verify(&ed25519::Signature(sig), &msg[..], &ed25519::Public(pubkey)) - } - - fn test_sr25519_verify(input: Vec) -> bool { - let mut pubkey = [0; 32]; - let mut sig = [0; 64]; - - pubkey.copy_from_slice(&input[0..32]); - sig.copy_from_slice(&input[32..96]); - - let msg = b"all ok!"; - sr25519_verify(&sr25519::Signature(sig), &msg[..], &sr25519::Public(pubkey)) - } - - fn test_ordered_trie_root() -> Vec { - BlakeTwo256::ordered_trie_root( - vec![ - b"zero"[..].into(), - b"one"[..].into(), - b"two"[..].into(), - ], - ).as_ref().to_vec() - } - - fn test_sandbox(code: Vec) -> bool { - execute_sandboxed(&code, &[]).is_ok() - } - - fn test_sandbox_args(code: Vec) -> bool { - execute_sandboxed( - &code, - &[ - Value::I32(0x12345678), - Value::I64(0x1234567887654321), - ], - ).is_ok() - } - - fn test_sandbox_return_val(code: Vec) -> bool { - let ok = match execute_sandboxed( - &code, - &[ - Value::I32(0x1336), - ] - ) { - Ok(sp_sandbox::ReturnValue::Value(Value::I32(0x1337))) => true, - _ => false, - }; - - ok - } - - fn test_sandbox_instantiate(code: Vec) -> u8 { - let env_builder = sp_sandbox::EnvironmentDefinitionBuilder::new(); - let code = match sp_sandbox::Instance::new(&code, &env_builder, &mut ()) { - Ok(_) => 0, - Err(sp_sandbox::Error::Module) => 1, - Err(sp_sandbox::Error::Execution) => 2, - Err(sp_sandbox::Error::OutOfBounds) => 3, - }; - - code - } - - fn test_sandbox_get_global_val(code: Vec) -> i64 { - let env_builder = sp_sandbox::EnvironmentDefinitionBuilder::new(); - let instance = if let Ok(i) = sp_sandbox::Instance::new(&code, &env_builder, &mut ()) { - i - } else { - return 20; - }; - - match instance.get_global_val("test_global") { - Some(sp_sandbox::Value::I64(val)) => val, - None => 30, - val => 40, - } - } - - fn test_offchain_index_set() { - sp_io::offchain_index::set(b"k", b"v"); - } - - fn test_offchain_local_storage() -> bool { - let kind = sp_core::offchain::StorageKind::PERSISTENT; - assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), None); - sp_io::offchain::local_storage_set(kind, b"test", b"asd"); - assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"asd".to_vec())); - - let res = sp_io::offchain::local_storage_compare_and_set( - kind, - b"test", - Some(b"asd".to_vec()), - b"", - ); - assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"".to_vec())); - res - } - - fn test_offchain_local_storage_with_none() { - let kind = sp_core::offchain::StorageKind::PERSISTENT; - assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), None); - - let res = sp_io::offchain::local_storage_compare_and_set(kind, b"test", None, b"value"); - assert_eq!(res, true); - assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"value".to_vec())); - } - - fn test_offchain_http() -> bool { - use sp_core::offchain::HttpRequestStatus; - let run = || -> Option<()> { - let id = sp_io::offchain::http_request_start( - "POST", - "http://localhost:12345", - &[], - ).ok()?; - sp_io::offchain::http_request_add_header(id, "X-Auth", "test").ok()?; - sp_io::offchain::http_request_write_body(id, &[1, 2, 3, 4], None).ok()?; - sp_io::offchain::http_request_write_body(id, &[], None).ok()?; - let status = sp_io::offchain::http_response_wait(&[id], None); - assert!(status == vec![HttpRequestStatus::Finished(200)], "Expected Finished(200) status."); - let headers = sp_io::offchain::http_response_headers(id); - assert_eq!(headers, vec![(b"X-Auth".to_vec(), b"hello".to_vec())]); - let mut buffer = vec![0; 64]; - let read = sp_io::offchain::http_response_read_body(id, &mut buffer, None).ok()?; - assert_eq!(read, 3); - assert_eq!(&buffer[0..read as usize], &[1, 2, 3]); - let read = sp_io::offchain::http_response_read_body(id, &mut buffer, None).ok()?; - assert_eq!(read, 0); - - Some(()) - }; - - run().is_some() - } - - fn test_enter_span() -> u64 { - wasm_tracing::enter_span(Default::default()) - } - - fn test_exit_span(span_id: u64) { - wasm_tracing::exit(span_id) - } - - fn test_nested_spans() { - sp_io::init_tracing(); - let span_id = wasm_tracing::enter_span(Default::default()); - { - sp_io::init_tracing(); - let span_id = wasm_tracing::enter_span(Default::default()); - wasm_tracing::exit(span_id); - } - wasm_tracing::exit(span_id); - } - - fn returns_mutable_static() -> u64 { - unsafe { - MUTABLE_STATIC += 1; - MUTABLE_STATIC - } - } - - fn returns_mutable_static_bss() -> u64 { - unsafe { - MUTABLE_STATIC_BSS += 1; - MUTABLE_STATIC_BSS - } - } - - fn allocates_huge_stack_array(trap: bool) -> Vec { - // Allocate a stack frame that is approx. 75% of the stack (assuming it is 1MB). - // This will just decrease (stacks in wasm32-u-u grow downwards) the stack - // pointer. This won't trap on the current compilers. - let mut data = [0u8; 1024 * 768]; - - // Then make sure we actually write something to it. - // - // If: - // 1. the stack area is placed at the beginning of the linear memory space, and - // 2. the stack pointer points to out-of-bounds area, and - // 3. a write is performed around the current stack pointer. - // - // then a trap should happen. - // - for (i, v) in data.iter_mut().enumerate() { - *v = i as u8; // deliberate truncation - } - - if trap { - // There is a small chance of this to be pulled up in theory. In practice - // the probability of that is rather low. - panic!() - } - - data.to_vec() - } - - // Check that the heap at `heap_base + offset` don't contains the test message. - // After the check succeeds the test message is written into the heap. - // - // It is expected that the given pointer is not allocated. - fn check_and_set_in_heap(heap_base: u32, offset: u32) { - let test_message = b"Hello invalid heap memory"; - let ptr = unsafe { (heap_base + offset) as *mut u8 }; - - let message_slice = unsafe { sp_std::slice::from_raw_parts_mut(ptr, test_message.len()) }; - - assert_ne!(test_message, message_slice); - message_slice.copy_from_slice(test_message); - } - - fn test_spawn() { - let data = vec![1u8, 2u8]; - let data_new = sp_tasks::spawn(tasks::incrementer, data).join(); - - assert_eq!(data_new, vec![2u8, 3u8]); - } - - fn test_nested_spawn() { - let data = vec![7u8, 13u8]; - let data_new = sp_tasks::spawn(tasks::parallel_incrementer, data).join(); - - assert_eq!(data_new, vec![10u8, 16u8]); - } - - fn test_panic_in_spawned() { - sp_tasks::spawn(tasks::panicker, vec![]).join(); - } -} + fn test_clear_prefix(input: Vec) -> Vec { + storage::clear_prefix(&input, None); + b"all ok!".to_vec() + } -#[cfg(not(feature = "std"))] -mod tasks { - use sp_std::prelude::*; + fn test_empty_return() {} + + fn test_dirty_plenty_memory(heap_base: u32, heap_pages: u32) { + // This piece of code will dirty multiple pages of memory. The number of pages is given by + // the `heap_pages`. It's unit is a wasm page (64KiB). The first page to be cleared + // is a wasm page that that follows the one that holds the `heap_base` address. + // + // This function dirties the **host** pages. I.e. we dirty 4KiB at a time and it will take + // 16 writes to process a single wasm page. + + let heap_ptr = heap_base as usize; + + // Find the next wasm page boundary. + let heap_ptr = round_up_to(heap_ptr, 65536); - pub fn incrementer(data: Vec) -> Vec { - data.into_iter().map(|v| v + 1).collect() + // Make it an actual pointer + let heap_ptr = heap_ptr as *mut u8; + + // Traverse the host pages and make each one dirty + let host_pages = heap_pages as usize * 16; + for i in 0..host_pages { + unsafe { + // technically this is an UB, but there is no way Rust can find this out. + heap_ptr.add(i * 4096).write(0); + } + } + + fn round_up_to(n: usize, divisor: usize) -> usize { + (n + divisor - 1) / divisor + } } - pub fn panicker(_: Vec) -> Vec { - panic!() + fn test_allocate_vec(size: u32) -> Vec { + Vec::with_capacity(size as usize) } - pub fn parallel_incrementer(data: Vec) -> Vec { - let first = data.into_iter().map(|v| v + 2).collect::>(); - let second = sp_tasks::spawn(incrementer, first).join(); - second + fn test_fp_f32add(a: [u8; 4], b: [u8; 4]) -> [u8; 4] { + let a = f32::from_le_bytes(a); + let b = f32::from_le_bytes(b); + f32::to_le_bytes(a + b) } -} -#[cfg(not(feature = "std"))] -fn execute_sandboxed( - code: &[u8], - args: &[Value], -) -> Result { - struct State { - counter: u32, - } - - fn env_assert( - _e: &mut State, - args: &[Value], - ) -> Result { - if args.len() != 1 { - return Err(sp_sandbox::HostError) + fn test_panic() { panic!("test panic") } + + fn test_conditional_panic(input: Vec) -> Vec { + if input.len() > 0 { + panic!("test panic") } - let condition = args[0].as_i32().ok_or_else(|| sp_sandbox::HostError)?; - if condition != 0 { - Ok(sp_sandbox::ReturnValue::Unit) - } else { - Err(sp_sandbox::HostError) + + input + } + + fn test_blake2_256(input: Vec) -> Vec { + blake2_256(&input).to_vec() + } + + fn test_blake2_128(input: Vec) -> Vec { + blake2_128(&input).to_vec() + } + + fn test_sha2_256(input: Vec) -> Vec { + sha2_256(&input).to_vec() + } + + fn test_twox_256(input: Vec) -> Vec { + twox_256(&input).to_vec() + } + + fn test_twox_128(input: Vec) -> Vec { + twox_128(&input).to_vec() + } + + fn test_ed25519_verify(input: Vec) -> bool { + let mut pubkey = [0; 32]; + let mut sig = [0; 64]; + + pubkey.copy_from_slice(&input[0..32]); + sig.copy_from_slice(&input[32..96]); + + let msg = b"all ok!"; + ed25519_verify(&ed25519::Signature(sig), &msg[..], &ed25519::Public(pubkey)) + } + + fn test_sr25519_verify(input: Vec) -> bool { + let mut pubkey = [0; 32]; + let mut sig = [0; 64]; + + pubkey.copy_from_slice(&input[0..32]); + sig.copy_from_slice(&input[32..96]); + + let msg = b"all ok!"; + sr25519_verify(&sr25519::Signature(sig), &msg[..], &sr25519::Public(pubkey)) + } + + fn test_ordered_trie_root() -> Vec { + BlakeTwo256::ordered_trie_root( + vec![ + b"zero"[..].into(), + b"one"[..].into(), + b"two"[..].into(), + ], + sp_core::storage::StateVersion::V1, + ).as_ref().to_vec() + } + + fn test_offchain_index_set() { + sp_io::offchain_index::set(b"k", b"v"); + } + + fn test_offchain_local_storage() -> bool { + let kind = sp_core::offchain::StorageKind::PERSISTENT; + assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), None); + sp_io::offchain::local_storage_set(kind, b"test", b"asd"); + assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"asd".to_vec())); + + let res = sp_io::offchain::local_storage_compare_and_set( + kind, + b"test", + Some(b"asd".to_vec()), + b"", + ); + assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"".to_vec())); + res + } + + fn test_offchain_local_storage_with_none() { + let kind = sp_core::offchain::StorageKind::PERSISTENT; + assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), None); + + let res = sp_io::offchain::local_storage_compare_and_set(kind, b"test", None, b"value"); + assert_eq!(res, true); + assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"value".to_vec())); + } + + fn test_offchain_http() -> bool { + use sp_core::offchain::HttpRequestStatus; + let run = || -> Option<()> { + let id = sp_io::offchain::http_request_start( + "POST", + "http://localhost:12345", + &[], + ).ok()?; + sp_io::offchain::http_request_add_header(id, "X-Auth", "test").ok()?; + sp_io::offchain::http_request_write_body(id, &[1, 2, 3, 4], None).ok()?; + sp_io::offchain::http_request_write_body(id, &[], None).ok()?; + let status = sp_io::offchain::http_response_wait(&[id], None); + assert!(status == vec![HttpRequestStatus::Finished(200)], "Expected Finished(200) status."); + let headers = sp_io::offchain::http_response_headers(id); + assert_eq!(headers, vec![(b"X-Auth".to_vec(), b"hello".to_vec())]); + let mut buffer = vec![0; 64]; + let read = sp_io::offchain::http_response_read_body(id, &mut buffer, None).ok()?; + assert_eq!(read, 3); + assert_eq!(&buffer[0..read as usize], &[1, 2, 3]); + let read = sp_io::offchain::http_response_read_body(id, &mut buffer, None).ok()?; + assert_eq!(read, 0); + + Some(()) + }; + + run().is_some() + } + + fn test_enter_span() -> u64 { + wasm_tracing::enter_span(Default::default()) + } + + fn test_exit_span(span_id: u64) { + wasm_tracing::exit(span_id) + } + + fn test_nested_spans() { + sp_io::init_tracing(); + let span_id = wasm_tracing::enter_span(Default::default()); + { + sp_io::init_tracing(); + let span_id = wasm_tracing::enter_span(Default::default()); + wasm_tracing::exit(span_id); } + wasm_tracing::exit(span_id); } - fn env_inc_counter( - e: &mut State, - args: &[Value], - ) -> Result { - if args.len() != 1 { - return Err(sp_sandbox::HostError) + + fn returns_mutable_static() -> u64 { + unsafe { + MUTABLE_STATIC += 1; + MUTABLE_STATIC } - let inc_by = args[0].as_i32().ok_or_else(|| sp_sandbox::HostError)?; - e.counter += inc_by as u32; - Ok(sp_sandbox::ReturnValue::Value(Value::I32(e.counter as i32))) - } - - let mut state = State { counter: 0 }; - - let env_builder = { - let mut env_builder = sp_sandbox::EnvironmentDefinitionBuilder::new(); - env_builder.add_host_func("env", "assert", env_assert); - env_builder.add_host_func("env", "inc_counter", env_inc_counter); - let memory = match sp_sandbox::Memory::new(1, Some(16)) { - Ok(m) => m, - Err(_) => unreachable!( - " - Memory::new() can return Err only if parameters are borked; \ - We passing params here explicitly and they're correct; \ - Memory::new() can't return a Error qed" - ), - }; - env_builder.add_memory("env", "memory", memory); - env_builder - }; + } - let mut instance = sp_sandbox::Instance::new(code, &env_builder, &mut state)?; - let result = instance.invoke("call", args, &mut state); + fn returns_mutable_static_bss() -> u64 { + unsafe { + MUTABLE_STATIC_BSS += 1; + MUTABLE_STATIC_BSS + } + } - result.map_err(|_| sp_sandbox::HostError) + fn allocates_huge_stack_array(trap: bool) -> Vec { + // Allocate a stack frame that is approx. 75% of the stack (assuming it is 1MB). + // This will just decrease (stacks in wasm32-u-u grow downwards) the stack + // pointer. This won't trap on the current compilers. + let mut data = [0u8; 1024 * 768]; + + // Then make sure we actually write something to it. + // + // If: + // 1. the stack area is placed at the beginning of the linear memory space, and + // 2. the stack pointer points to out-of-bounds area, and + // 3. a write is performed around the current stack pointer. + // + // then a trap should happen. + // + for (i, v) in data.iter_mut().enumerate() { + *v = i as u8; // deliberate truncation + } + + if trap { + // There is a small chance of this to be pulled up in theory. In practice + // the probability of that is rather low. + panic!() + } + + data.to_vec() + } + + // Check that the heap at `heap_base + offset` don't contains the test message. + // After the check succeeds the test message is written into the heap. + // + // It is expected that the given pointer is not allocated. + fn check_and_set_in_heap(heap_base: u32, offset: u32) { + let test_message = b"Hello invalid heap memory"; + let ptr = (heap_base + offset) as *mut u8; + + let message_slice = unsafe { sp_std::slice::from_raw_parts_mut(ptr, test_message.len()) }; + + assert_ne!(test_message, message_slice); + message_slice.copy_from_slice(test_message); + } + + fn test_return_i8() -> i8 { + -66 + } + + fn test_take_i8(value: i8) { + assert_eq!(value, -66); + } + + fn test_abort_on_panic() { + sp_io::panic_handler::abort_on_panic("test_abort_on_panic called"); + } + + fn test_unreachable_intrinsic() { + core::arch::wasm32::unreachable() + } + + fn test_return_value() -> u64 { + // Mainly a test that the macro is working when we have a return statement here. + return 1234; + } } diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index 7e0696973dc77..efac4e74bb8e2 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,11 +18,6 @@ //! Tests that are only relevant for Linux. -// Constrain this only to wasmtime for the time being. Without this rustc will complain on unused -// imports and items. The alternative is to plop `cfg(feature = wasmtime)` everywhere which seems -// borthersome. -#![cfg(feature = "wasmtime")] - use super::mk_test_runtime; use crate::WasmExecutionMethod; use codec::Encode as _; @@ -38,9 +33,15 @@ fn memory_consumption_compiled() { // For that we make a series of runtime calls, probing the RSS for the VMA matching the linear // memory. After the call we expect RSS to be equal to 0. - let runtime = mk_test_runtime(WasmExecutionMethod::Compiled, 1024); + let runtime = mk_test_runtime( + WasmExecutionMethod::Compiled { + instantiation_strategy: + sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse, + }, + 1024, + ); - let instance = runtime.new_instance().unwrap(); + let mut instance = runtime.new_instance().unwrap(); let heap_base = instance .get_global_const("__heap_base") .expect("`__heap_base` is valid") diff --git a/client/executor/src/integration_tests/linux/smaps.rs b/client/executor/src/integration_tests/linux/smaps.rs index b23a188b93a26..665155ff81ea8 100644 --- a/client/executor/src/integration_tests/linux/smaps.rs +++ b/client/executor/src/integration_tests/linux/smaps.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index dabead4799dc8..25b999f115363 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,11 +18,9 @@ #[cfg(target_os = "linux")] mod linux; -mod sandbox; use codec::{Decode, Encode}; -use hex_literal::hex; -use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; +use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; use sc_runtime_test::wasm_binary_unwrap; use sp_core::{ blake2_128, blake2_256, ed25519, map, @@ -33,14 +31,13 @@ use sp_core::{ }; use sp_runtime::traits::BlakeTwo256; use sp_state_machine::TestExternalities as CoreTestExternalities; -use sp_trie::{trie_types::Layout, TrieConfiguration}; -use sp_wasm_interface::HostFunctions as _; +use sp_trie::{LayoutV1 as Layout, TrieConfiguration}; use std::sync::Arc; use tracing_subscriber::layer::SubscriberExt; use crate::WasmExecutionMethod; -pub type TestExternalities = CoreTestExternalities; +pub type TestExternalities = CoreTestExternalities; type HostFunctions = sp_io::SubstrateHostFunctions; /// Simple macro that runs a given method as test with the available wasm execution methods. @@ -54,9 +51,38 @@ macro_rules! test_wasm_execution { } #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled>]() { - $method_name(WasmExecutionMethod::Compiled); + fn [<$method_name _compiled_recreate_instance_cow>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite + }); + } + + #[test] + fn [<$method_name _compiled_recreate_instance_vanilla>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance + }); + } + + #[test] + fn [<$method_name _compiled_pooling_cow>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite + }); + } + + #[test] + fn [<$method_name _compiled_pooling_vanilla>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling + }); + } + + #[test] + fn [<$method_name _compiled_legacy_instance_reuse>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse + }); } } }; @@ -76,16 +102,11 @@ fn call_in_wasm( call_data: &[u8], execution_method: WasmExecutionMethod, ext: &mut E, -) -> Result, String> { - let executor = crate::WasmExecutor::new( - execution_method, - Some(1024), - HostFunctions::host_functions(), - 8, - None, - ); +) -> Result, Error> { + let executor = + crate::WasmExecutor::::new(execution_method, Some(1024), 8, None, 2); executor.uncached_call( - RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), + RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(), ext, true, function, @@ -107,25 +128,15 @@ fn call_not_existing_function(wasm_method: WasmExecutionMethod) { let mut ext = TestExternalities::default(); let mut ext = ext.ext(); - match call_in_wasm( - "test_calling_missing_external", - &[], - wasm_method, - &mut ext, - ) { - Ok(_) => panic!("was expected an `Err`"), - Err(e) => { - match wasm_method { - WasmExecutionMethod::Interpreted => assert_eq!( - &format!("{:?}", e), - "\"Trap: Trap { kind: Host(Other(\\\"Function `missing_external` is only a stub. Calling a stub is not allowed.\\\")) }\"" - ), - #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => assert!( - format!("{:?}", e).contains("Wasm execution trapped: call to a missing function env:missing_external") - ), - } - } + match call_in_wasm("test_calling_missing_external", &[], wasm_method, &mut ext).unwrap_err() { + Error::AbortedDueToTrap(error) => { + let expected = match wasm_method { + WasmExecutionMethod::Interpreted => "Other: Function `missing_external` is only a stub. Calling a stub is not allowed.", + WasmExecutionMethod::Compiled { .. } => "call to a missing function env:missing_external" + }; + assert_eq!(error.message, expected); + }, + error => panic!("unexpected error: {:?}", error), } } @@ -134,25 +145,17 @@ fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) { let mut ext = TestExternalities::default(); let mut ext = ext.ext(); - match call_in_wasm( - "test_calling_yet_another_missing_external", - &[], - wasm_method, - &mut ext, - ) { - Ok(_) => panic!("was expected an `Err`"), - Err(e) => { - match wasm_method { - WasmExecutionMethod::Interpreted => assert_eq!( - &format!("{:?}", e), - "\"Trap: Trap { kind: Host(Other(\\\"Function `yet_another_missing_external` is only a stub. Calling a stub is not allowed.\\\")) }\"" - ), - #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => assert!( - format!("{:?}", e).contains("Wasm execution trapped: call to a missing function env:yet_another_missing_external") - ), - } - } + match call_in_wasm("test_calling_yet_another_missing_external", &[], wasm_method, &mut ext) + .unwrap_err() + { + Error::AbortedDueToTrap(error) => { + let expected = match wasm_method { + WasmExecutionMethod::Interpreted => "Other: Function `yet_another_missing_external` is only a stub. Calling a stub is not allowed.", + WasmExecutionMethod::Compiled { .. } => "call to a missing function env:yet_another_missing_external" + }; + assert_eq!(error.message, expected); + }, + error => panic!("unexpected error: {:?}", error), } } @@ -174,21 +177,22 @@ fn panicking_should_work(wasm_method: WasmExecutionMethod) { test_wasm_execution!(storage_should_work); fn storage_should_work(wasm_method: WasmExecutionMethod) { let mut ext = TestExternalities::default(); + // Test value must be bigger than 32 bytes + // to test the trie versioning. + let value = vec![7u8; 60]; { let mut ext = ext.ext(); ext.set_storage(b"foo".to_vec(), b"bar".to_vec()); - let output = - call_in_wasm("test_data_in", &b"Hello world".to_vec().encode(), wasm_method, &mut ext) - .unwrap(); + let output = call_in_wasm("test_data_in", &value.encode(), wasm_method, &mut ext).unwrap(); assert_eq!(output, b"all ok!".to_vec().encode()); } let expected = TestExternalities::new(sp_core::storage::Storage { top: map![ - b"input".to_vec() => b"Hello world".to_vec(), + b"input".to_vec() => value, b"foo".to_vec() => b"bar".to_vec(), b"baz".to_vec() => b"bar".to_vec() ], @@ -263,16 +267,18 @@ fn sha2_256_should_work(wasm_method: WasmExecutionMethod) { let mut ext = ext.ext(); assert_eq!( call_in_wasm("test_sha2_256", &[0], wasm_method, &mut ext,).unwrap(), - hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - .to_vec() - .encode(), + array_bytes::hex2bytes_unchecked( + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ) + .encode(), ); assert_eq!( call_in_wasm("test_sha2_256", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,) .unwrap(), - hex!("c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a") - .to_vec() - .encode(), + array_bytes::hex2bytes_unchecked( + "c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a" + ) + .encode(), ); } @@ -282,16 +288,18 @@ fn twox_256_should_work(wasm_method: WasmExecutionMethod) { let mut ext = ext.ext(); assert_eq!( call_in_wasm("test_twox_256", &[0], wasm_method, &mut ext,).unwrap(), - hex!("99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a") - .to_vec() - .encode(), + array_bytes::hex2bytes_unchecked( + "99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a" + ) + .encode(), ); assert_eq!( call_in_wasm("test_twox_256", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,) .unwrap(), - hex!("b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74") - .to_vec() - .encode(), + array_bytes::hex2bytes_unchecked( + "b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74" + ) + .encode(), ); } @@ -301,12 +309,12 @@ fn twox_128_should_work(wasm_method: WasmExecutionMethod) { let mut ext = ext.ext(); assert_eq!( call_in_wasm("test_twox_128", &[0], wasm_method, &mut ext,).unwrap(), - hex!("99e9d85137db46ef4bbea33613baafd5").to_vec().encode(), + array_bytes::hex2bytes_unchecked("99e9d85137db46ef4bbea33613baafd5").encode(), ); assert_eq!( call_in_wasm("test_twox_128", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,) .unwrap(), - hex!("b27dfd7f223f177f2a13647b533599af").to_vec().encode(), + array_bytes::hex2bytes_unchecked("b27dfd7f223f177f2a13647b533599af").encode(), ); } @@ -427,36 +435,48 @@ test_wasm_execution!(should_trap_when_heap_exhausted); fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { let mut ext = TestExternalities::default(); - let executor = crate::WasmExecutor::new( + let executor = crate::WasmExecutor::::new( wasm_method, Some(17), // `17` is the initial number of pages compiled into the binary. - HostFunctions::host_functions(), 8, None, + 2, ); let err = executor .uncached_call( - RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), + RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(), &mut ext.ext(), true, - "test_exhaust_heap", - &[0], + "test_allocate_vec", + &16777216_u32.encode(), ) .unwrap_err(); - assert!(err.contains("Allocator ran out of space")); + match err { + Error::AbortedDueToTrap(error) + if matches!(wasm_method, WasmExecutionMethod::Compiled { .. }) => + { + assert_eq!( + error.message, + r#"host code panicked while being called by the runtime: Failed to allocate memory: "Allocator ran out of space""# + ); + }, + Error::RuntimePanicked(error) if wasm_method == WasmExecutionMethod::Interpreted => { + assert_eq!(error, r#"Failed to allocate memory: "Allocator ran out of space""#); + }, + error => panic!("unexpected error: {:?}", error), + } } fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: u64) -> Arc { - let blob = RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]) + let blob = RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()) .expect("failed to create a runtime blob out of test runtime"); - crate::wasm_runtime::create_wasm_runtime_with_code( + crate::wasm_runtime::create_wasm_runtime_with_code::( wasm_method, pages, blob, - HostFunctions::host_functions(), true, None, ) @@ -467,7 +487,7 @@ test_wasm_execution!(returns_mutable_static); fn returns_mutable_static(wasm_method: WasmExecutionMethod) { let runtime = mk_test_runtime(wasm_method, 1024); - let instance = runtime.new_instance().unwrap(); + let mut instance = runtime.new_instance().unwrap(); let res = instance.call_export("returns_mutable_static", &[0]).unwrap(); assert_eq!(33, u64::decode(&mut &res[..]).unwrap()); @@ -482,7 +502,7 @@ test_wasm_execution!(returns_mutable_static_bss); fn returns_mutable_static_bss(wasm_method: WasmExecutionMethod) { let runtime = mk_test_runtime(wasm_method, 1024); - let instance = runtime.new_instance().unwrap(); + let mut instance = runtime.new_instance().unwrap(); let res = instance.call_export("returns_mutable_static_bss", &[0]).unwrap(); assert_eq!(1, u64::decode(&mut &res[..]).unwrap()); @@ -508,7 +528,7 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) { const REQUIRED_MEMORY_PAGES: u64 = 32; let runtime = mk_test_runtime(wasm_method, REQUIRED_MEMORY_PAGES); - let instance = runtime.new_instance().unwrap(); + let mut instance = runtime.new_instance().unwrap(); // On the first invocation we allocate approx. 768KB (75%) of stack and then trap. let res = instance.call_export("allocates_huge_stack_array", &true.encode()); @@ -522,7 +542,7 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) { test_wasm_execution!(interpreted_only heap_is_reset_between_calls); fn heap_is_reset_between_calls(wasm_method: WasmExecutionMethod) { let runtime = mk_test_runtime(wasm_method, 1024); - let instance = runtime.new_instance().unwrap(); + let mut instance = runtime.new_instance().unwrap(); let heap_base = instance .get_global_const("__heap_base") @@ -540,12 +560,12 @@ fn heap_is_reset_between_calls(wasm_method: WasmExecutionMethod) { test_wasm_execution!(parallel_execution); fn parallel_execution(wasm_method: WasmExecutionMethod) { - let executor = std::sync::Arc::new(crate::WasmExecutor::new( + let executor = std::sync::Arc::new(crate::WasmExecutor::::new( wasm_method, Some(1024), - HostFunctions::host_functions(), 8, None, + 2, )); let threads: Vec<_> = (0..8) .map(|_| { @@ -556,14 +576,14 @@ fn parallel_execution(wasm_method: WasmExecutionMethod) { assert_eq!( executor .uncached_call( - RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), + RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(), &mut ext, true, "test_twox_128", &[0], ) .unwrap(), - hex!("99e9d85137db46ef4bbea33613baafd5").to_vec().encode(), + array_bytes::hex2bytes_unchecked("99e9d85137db46ef4bbea33613baafd5").encode() ); }) }) @@ -582,11 +602,11 @@ fn wasm_tracing_should_work(wasm_method: WasmExecutionMethod) { struct TestTraceHandler(Arc>>); impl sc_tracing::TraceHandler for TestTraceHandler { - fn handle_span(&self, sd: SpanDatum) { - self.0.lock().unwrap().push(sd); + fn handle_span(&self, sd: &SpanDatum) { + self.0.lock().unwrap().push(sd.clone()); } - fn handle_event(&self, _event: TraceEvent) {} + fn handle_event(&self, _event: &TraceEvent) {} } let traces = Arc::new(Mutex::new(Vec::new())); @@ -626,29 +646,138 @@ fn wasm_tracing_should_work(wasm_method: WasmExecutionMethod) { assert_eq!(len, 2); } -test_wasm_execution!(spawning_runtime_instance_should_work); -fn spawning_runtime_instance_should_work(wasm_method: WasmExecutionMethod) { +test_wasm_execution!(memory_is_cleared_between_invocations); +fn memory_is_cleared_between_invocations(wasm_method: WasmExecutionMethod) { + // This is based on the code generated by compiling a runtime *without* + // the `-C link-arg=--import-memory` using the following code and then + // disassembling the resulting blob with `wasm-dis`: + // + // ``` + // #[no_mangle] + // #[cfg(not(feature = "std"))] + // pub fn returns_no_bss_mutable_static(_: *mut u8, _: usize) -> u64 { + // static mut COUNTER: usize = 0; + // let output = unsafe { + // COUNTER += 1; + // COUNTER as u64 + // }; + // sp_core::to_substrate_wasm_fn_return_value(&output) + // } + // ``` + // + // This results in the BSS section to *not* be emitted, hence the executor has no way + // of knowing about the `static` variable's existence, so this test will fail if the linear + // memory is not properly cleared between invocations. + let binary = wat::parse_str(r#" + (module + (type $i32_=>_i32 (func (param i32) (result i32))) + (type $i32_i32_=>_i64 (func (param i32 i32) (result i64))) + (import "env" "ext_allocator_malloc_version_1" (func $ext_allocator_malloc_version_1 (param i32) (result i32))) + (global $__stack_pointer (mut i32) (i32.const 1048576)) + (global $global$1 i32 (i32.const 1048580)) + (global $global$2 i32 (i32.const 1048592)) + (memory $0 17) + (export "memory" (memory $0)) + (export "returns_no_bss_mutable_static" (func $returns_no_bss_mutable_static)) + (export "__data_end" (global $global$1)) + (export "__heap_base" (global $global$2)) + (func $returns_no_bss_mutable_static (param $0 i32) (param $1 i32) (result i64) + (local $2 i32) + (local $3 i32) + (i32.store offset=1048576 + (i32.const 0) + (local.tee $2 + (i32.add + (i32.load offset=1048576 (i32.const 0)) + (i32.const 1) + ) + ) + ) + (i64.store + (local.tee $3 + (call $ext_allocator_malloc_version_1 (i32.const 8)) + ) + (i64.extend_i32_u (local.get $2)) + ) + (i64.or + (i64.extend_i32_u (local.get $3)) + (i64.const 34359738368) + ) + ) + )"#).unwrap(); + + let runtime = crate::wasm_runtime::create_wasm_runtime_with_code::( + wasm_method, + 1024, + RuntimeBlob::uncompress_if_needed(&binary[..]).unwrap(), + true, + None, + ) + .unwrap(); + + let mut instance = runtime.new_instance().unwrap(); + let res = instance.call_export("returns_no_bss_mutable_static", &[0]).unwrap(); + assert_eq!(1, u64::decode(&mut &res[..]).unwrap()); + + let res = instance.call_export("returns_no_bss_mutable_static", &[0]).unwrap(); + assert_eq!(1, u64::decode(&mut &res[..]).unwrap()); +} + +test_wasm_execution!(return_i8); +fn return_i8(wasm_method: WasmExecutionMethod) { + let mut ext = TestExternalities::default(); + let mut ext = ext.ext(); + + assert_eq!( + call_in_wasm("test_return_i8", &[], wasm_method, &mut ext).unwrap(), + (-66_i8).encode() + ); +} + +test_wasm_execution!(take_i8); +fn take_i8(wasm_method: WasmExecutionMethod) { let mut ext = TestExternalities::default(); let mut ext = ext.ext(); - call_in_wasm("test_spawn", &[], wasm_method, &mut ext).unwrap(); + call_in_wasm("test_take_i8", &(-66_i8).encode(), wasm_method, &mut ext).unwrap(); } -test_wasm_execution!(spawning_runtime_instance_nested_should_work); -fn spawning_runtime_instance_nested_should_work(wasm_method: WasmExecutionMethod) { +test_wasm_execution!(abort_on_panic); +fn abort_on_panic(wasm_method: WasmExecutionMethod) { let mut ext = TestExternalities::default(); let mut ext = ext.ext(); - call_in_wasm("test_nested_spawn", &[], wasm_method, &mut ext).unwrap(); + match call_in_wasm("test_abort_on_panic", &[], wasm_method, &mut ext).unwrap_err() { + Error::AbortedDueToPanic(error) => assert_eq!(error.message, "test_abort_on_panic called"), + error => panic!("unexpected error: {:?}", error), + } } -test_wasm_execution!(panic_in_spawned_instance_panics_on_joining_its_result); -fn panic_in_spawned_instance_panics_on_joining_its_result(wasm_method: WasmExecutionMethod) { +test_wasm_execution!(unreachable_intrinsic); +fn unreachable_intrinsic(wasm_method: WasmExecutionMethod) { let mut ext = TestExternalities::default(); let mut ext = ext.ext(); - let error_result = - call_in_wasm("test_panic_in_spawned", &[], wasm_method, &mut ext).unwrap_err(); + match call_in_wasm("test_unreachable_intrinsic", &[], wasm_method, &mut ext).unwrap_err() { + Error::AbortedDueToTrap(error) => { + let expected = match wasm_method { + WasmExecutionMethod::Interpreted => "unreachable", + WasmExecutionMethod::Compiled { .. } => + "wasm trap: wasm `unreachable` instruction executed", + }; + assert_eq!(error.message, expected); + }, + error => panic!("unexpected error: {:?}", error), + } +} - assert!(format!("{}", error_result).contains("Spawned task")); +test_wasm_execution!(return_value); +fn return_value(wasm_method: WasmExecutionMethod) { + let mut ext = TestExternalities::default(); + let mut ext = ext.ext(); + + assert_eq!( + call_in_wasm("test_return_value", &[], wasm_method, &mut ext).unwrap(), + (1234u64).encode() + ); } diff --git a/client/executor/src/integration_tests/sandbox.rs b/client/executor/src/integration_tests/sandbox.rs deleted file mode 100644 index aacd493297cc8..0000000000000 --- a/client/executor/src/integration_tests/sandbox.rs +++ /dev/null @@ -1,293 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use super::{call_in_wasm, TestExternalities}; -use crate::{test_wasm_execution, WasmExecutionMethod}; - -use codec::Encode; - -test_wasm_execution!(sandbox_should_work); -fn sandbox_should_work(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - (import "env" "inc_counter" (func $inc_counter (param i32) (result i32))) - (func (export "call") - (drop - (call $inc_counter (i32.const 5)) - ) - - (call $inc_counter (i32.const 3)) - ;; current counter value is on the stack - - ;; check whether current == 8 - i32.const 8 - i32.eq - - call $assert - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!(call_in_wasm("test_sandbox", &code, wasm_method, &mut ext).unwrap(), true.encode()); -} - -test_wasm_execution!(sandbox_trap); -fn sandbox_trap(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - (func (export "call") - i32.const 0 - call $assert - ) - ) - "#, - ) - .unwrap(); - - assert_eq!(call_in_wasm("test_sandbox", &code, wasm_method, &mut ext).unwrap(), vec![0]); -} - -test_wasm_execution!(start_called); -fn start_called(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - (import "env" "inc_counter" (func $inc_counter (param i32) (result i32))) - - ;; Start function - (start $start) - (func $start - ;; Increment counter by 1 - (drop - (call $inc_counter (i32.const 1)) - ) - ) - - (func (export "call") - ;; Increment counter by 1. The current value is placed on the stack. - (call $inc_counter (i32.const 1)) - - ;; Counter is incremented twice by 1, once there and once in `start` func. - ;; So check the returned value is equal to 2. - i32.const 2 - i32.eq - call $assert - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!(call_in_wasm("test_sandbox", &code, wasm_method, &mut ext).unwrap(), true.encode()); -} - -test_wasm_execution!(invoke_args); -fn invoke_args(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - - (func (export "call") (param $x i32) (param $y i64) - ;; assert that $x = 0x12345678 - (call $assert - (i32.eq - (get_local $x) - (i32.const 0x12345678) - ) - ) - - (call $assert - (i64.eq - (get_local $y) - (i64.const 0x1234567887654321) - ) - ) - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm("test_sandbox_args", &code, wasm_method, &mut ext,).unwrap(), - true.encode(), - ); -} - -test_wasm_execution!(return_val); -fn return_val(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (func (export "call") (param $x i32) (result i32) - (i32.add - (get_local $x) - (i32.const 1) - ) - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm("test_sandbox_return_val", &code, wasm_method, &mut ext,).unwrap(), - true.encode(), - ); -} - -test_wasm_execution!(unlinkable_module); -fn unlinkable_module(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "non-existent" (func)) - - (func (export "call") - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm("test_sandbox_instantiate", &code, wasm_method, &mut ext,).unwrap(), - 1u8.encode(), - ); -} - -test_wasm_execution!(corrupted_module); -fn corrupted_module(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - // Corrupted wasm file - let code = vec![0u8, 0, 0, 0, 1, 0, 0, 0].encode(); - - assert_eq!( - call_in_wasm("test_sandbox_instantiate", &code, wasm_method, &mut ext,).unwrap(), - 1u8.encode(), - ); -} - -test_wasm_execution!(start_fn_ok); -fn start_fn_ok(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (func (export "call") - ) - - (func $start - ) - - (start $start) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm("test_sandbox_instantiate", &code, wasm_method, &mut ext,).unwrap(), - 0u8.encode(), - ); -} - -test_wasm_execution!(start_fn_traps); -fn start_fn_traps(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (func (export "call") - ) - - (func $start - unreachable - ) - - (start $start) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm("test_sandbox_instantiate", &code, wasm_method, &mut ext,).unwrap(), - 2u8.encode(), - ); -} - -test_wasm_execution!(get_global_val_works); -fn get_global_val_works(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (global (export "test_global") i64 (i64.const 500)) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm("test_sandbox_get_global_val", &code, wasm_method, &mut ext,).unwrap(), - 500i64.encode(), - ); -} diff --git a/client/executor/src/lib.rs b/client/executor/src/lib.rs index 041db87bc82ab..0670b840949d7 100644 --- a/client/executor/src/lib.rs +++ b/client/executor/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -49,7 +49,8 @@ pub use sp_wasm_interface; pub use wasm_runtime::{read_embedded_version, WasmExecutionMethod}; pub use wasmi; -pub use sc_executor_common::{error, sandbox}; +pub use sc_executor_common::error; +pub use sc_executor_wasmtime::InstantiationStrategy as WasmtimeInstantiationStrategy; /// Extracts the runtime version of a given runtime code. pub trait RuntimeVersionOf { @@ -67,23 +68,22 @@ mod tests { use sc_executor_common::runtime_blob::RuntimeBlob; use sc_runtime_test::wasm_binary_unwrap; use sp_io::TestExternalities; - use sp_wasm_interface::HostFunctions; #[test] fn call_in_interpreted_wasm_works() { let mut ext = TestExternalities::default(); let mut ext = ext.ext(); - let executor = WasmExecutor::new( + let executor = WasmExecutor::::new( WasmExecutionMethod::Interpreted, Some(8), - sp_io::SubstrateHostFunctions::host_functions(), 8, None, + 2, ); let res = executor .uncached_call( - RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), + RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(), &mut ext, true, "test_empty_return", diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index 38dba55b5f87c..0eabffb8c87df 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,30 +23,20 @@ use crate::{ }; use std::{ - collections::HashMap, + marker::PhantomData, panic::{AssertUnwindSafe, UnwindSafe}, path::PathBuf, - result, - sync::{ - atomic::{AtomicU64, Ordering}, - mpsc, Arc, - }, + sync::Arc, }; -use codec::{Decode, Encode}; -use log::trace; +use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{AllocationStats, WasmInstance, WasmModule}, }; -use sp_core::{ - traits::{CodeExecutor, Externalities, RuntimeCode, RuntimeSpawn, RuntimeSpawnExt}, - NativeOrEncoded, -}; -use sp_externalities::ExternalitiesExt as _; -use sp_tasks::new_async_externalities; +use sp_core::traits::{CodeExecutor, Externalities, RuntimeCode}; use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; -use sp_wasm_interface::{Function, HostFunctions}; +use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions}; /// Default num of pages for the heap const DEFAULT_HEAP_PAGES: u64 = 2048; @@ -91,24 +81,38 @@ pub trait NativeExecutionDispatch: Send + Sync { /// An abstraction over Wasm code executor. Supports selecting execution backend and /// manages runtime cache. -#[derive(Clone)] -pub struct WasmExecutor { +pub struct WasmExecutor { /// Method used to execute fallback Wasm code. method: WasmExecutionMethod, /// The number of 64KB pages to allocate for Wasm execution. default_heap_pages: u64, - /// The host functions registered with this instance. - host_functions: Arc>, /// WASM runtime cache. cache: Arc, - /// The size of the instances cache. - max_runtime_instances: usize, /// The path to a directory which the executor can leverage for a file cache, e.g. put there /// compiled artifacts. cache_path: Option, + /// Ignore missing function imports. + allow_missing_host_functions: bool, + phantom: PhantomData, } -impl WasmExecutor { +impl Clone for WasmExecutor { + fn clone(&self) -> Self { + Self { + method: self.method, + default_heap_pages: self.default_heap_pages, + cache: self.cache.clone(), + cache_path: self.cache_path.clone(), + allow_missing_host_functions: self.allow_missing_host_functions, + phantom: self.phantom, + } + } +} + +impl WasmExecutor +where + H: HostFunctions, +{ /// Create new instance. /// /// # Parameters @@ -118,31 +122,39 @@ impl WasmExecutor { /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. /// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided. /// - /// `host_functions` - The set of host functions to be available for import provided by this - /// executor. - /// /// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse. /// /// `cache_path` - A path to a directory where the executor can place its files for purposes of /// caching. This may be important in cases when there are many different modules with the /// compiled execution method is used. + /// + /// `runtime_cache_size` - The capacity of runtime cache. pub fn new( method: WasmExecutionMethod, default_heap_pages: Option, - host_functions: Vec<&'static dyn Function>, max_runtime_instances: usize, cache_path: Option, + runtime_cache_size: u8, ) -> Self { WasmExecutor { method, default_heap_pages: default_heap_pages.unwrap_or(DEFAULT_HEAP_PAGES), - host_functions: Arc::new(host_functions), - cache: Arc::new(RuntimeCache::new(max_runtime_instances, cache_path.clone())), - max_runtime_instances, + cache: Arc::new(RuntimeCache::new( + max_runtime_instances, + cache_path.clone(), + runtime_cache_size, + )), cache_path, + allow_missing_host_functions: false, + phantom: PhantomData, } } + /// Ignore missing function imports if set true. + pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) { + self.allow_missing_host_functions = allow_missing_host_functions + } + /// Execute the given closure `f` with the latest runtime (based on `runtime_code`). /// /// The closure `f` is expected to return `Err(_)` when there happened a `panic!` in native code @@ -156,28 +168,26 @@ impl WasmExecutor { /// runtime is invalidated on any `panic!` to prevent a poisoned state. `ext` is already /// implicitly handled as unwind safe, as we store it in a global variable while executing the /// native runtime. - fn with_instance( + pub fn with_instance( &self, runtime_code: &RuntimeCode, ext: &mut dyn Externalities, - allow_missing_host_functions: bool, f: F, ) -> Result where F: FnOnce( AssertUnwindSafe<&Arc>, - AssertUnwindSafe<&dyn WasmInstance>, + AssertUnwindSafe<&mut dyn WasmInstance>, Option<&RuntimeVersion>, AssertUnwindSafe<&mut dyn Externalities>, ) -> Result>, { - match self.cache.with_instance( + match self.cache.with_instance::( runtime_code, ext, self.method, self.default_heap_pages, - &*self.host_functions, - allow_missing_host_functions, + self.allow_missing_host_functions, |module, instance, version, ext| { let module = AssertUnwindSafe(module); let instance = AssertUnwindSafe(instance); @@ -192,10 +202,10 @@ impl WasmExecutor { /// Perform a call into the given runtime. /// - /// The runtime is passed as a [`RuntimeBlob`]. The runtime will be isntantiated with the + /// The runtime is passed as a [`RuntimeBlob`]. The runtime will be instantiated with the /// parameters this `WasmExecutor` was initialized with. /// - /// In case of problems with during creation of the runtime or instantation, a `Err` is + /// In case of problems with during creation of the runtime or instantiation, a `Err` is /// returned. that describes the message. #[doc(hidden)] // We use this function for tests across multiple crates. pub fn uncached_call( @@ -205,41 +215,84 @@ impl WasmExecutor { allow_missing_host_functions: bool, export_name: &str, call_data: &[u8], - ) -> std::result::Result, String> { - let module = crate::wasm_runtime::create_wasm_runtime_with_code( + ) -> std::result::Result, Error> { + self.uncached_call_impl( + runtime_blob, + ext, + allow_missing_host_functions, + export_name, + call_data, + &mut None, + ) + } + + /// Same as `uncached_call`, except it also returns allocation statistics. + #[doc(hidden)] // We use this function in tests. + pub fn uncached_call_with_allocation_stats( + &self, + runtime_blob: RuntimeBlob, + ext: &mut dyn Externalities, + allow_missing_host_functions: bool, + export_name: &str, + call_data: &[u8], + ) -> (std::result::Result, Error>, Option) { + let mut allocation_stats = None; + let result = self.uncached_call_impl( + runtime_blob, + ext, + allow_missing_host_functions, + export_name, + call_data, + &mut allocation_stats, + ); + (result, allocation_stats) + } + + fn uncached_call_impl( + &self, + runtime_blob: RuntimeBlob, + ext: &mut dyn Externalities, + allow_missing_host_functions: bool, + export_name: &str, + call_data: &[u8], + allocation_stats_out: &mut Option, + ) -> std::result::Result, Error> { + let module = crate::wasm_runtime::create_wasm_runtime_with_code::( self.method, self.default_heap_pages, runtime_blob, - self.host_functions.to_vec(), allow_missing_host_functions, self.cache_path.as_deref(), ) - .map_err(|e| format!("Failed to create module: {:?}", e))?; + .map_err(|e| format!("Failed to create module: {}", e))?; - let instance = module - .new_instance() - .map_err(|e| format!("Failed to create instance: {:?}", e))?; + let instance = + module.new_instance().map_err(|e| format!("Failed to create instance: {}", e))?; - let instance = AssertUnwindSafe(instance); + let mut instance = AssertUnwindSafe(instance); let mut ext = AssertUnwindSafe(ext); - let module = AssertUnwindSafe(module); + let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out); with_externalities_safe(&mut **ext, move || { - preregister_builtin_ext(module.clone()); - instance.call_export(export_name, call_data) + let (result, allocation_stats) = + instance.call_with_allocation_stats(export_name.into(), call_data); + **allocation_stats_out = allocation_stats; + result }) .and_then(|r| r) - .map_err(|e| e.to_string()) } } -impl sp_core::traits::ReadRuntimeVersion for WasmExecutor { +impl sp_core::traits::ReadRuntimeVersion for WasmExecutor +where + H: HostFunctions, +{ fn read_runtime_version( &self, wasm_code: &[u8], ext: &mut dyn Externalities, ) -> std::result::Result, String> { - let runtime_blob = RuntimeBlob::uncompress_if_needed(&wasm_code) + let runtime_blob = RuntimeBlob::uncompress_if_needed(wasm_code) .map_err(|e| format!("Failed to create runtime blob: {:?}", e))?; if let Some(version) = crate::wasm_runtime::read_embedded_version(&runtime_blob) @@ -250,7 +303,7 @@ impl sp_core::traits::ReadRuntimeVersion for WasmExecutor { } // If the blob didn't have embedded runtime version section, we fallback to the legacy - // way of fetching the verison: i.e. instantiating the given instance and calling + // way of fetching the version: i.e. instantiating the given instance and calling // `Core_version` on it. self.uncached_call( @@ -264,46 +317,48 @@ impl sp_core::traits::ReadRuntimeVersion for WasmExecutor { "Core_version", &[], ) + .map_err(|e| e.to_string()) } } -impl CodeExecutor for WasmExecutor { +impl CodeExecutor for WasmExecutor +where + H: HostFunctions, +{ type Error = Error; - fn call< - R: Decode + Encode + PartialEq, - NC: FnOnce() -> result::Result> + UnwindSafe, - >( + fn call( &self, ext: &mut dyn Externalities, runtime_code: &RuntimeCode, method: &str, data: &[u8], _use_native: bool, - _native_call: Option, - ) -> (Result>, bool) { - let result = self.with_instance( - runtime_code, - ext, - false, - |module, instance, _onchain_version, mut ext| { - with_externalities_safe(&mut **ext, move || { - preregister_builtin_ext(module.clone()); - instance.call_export(method, data).map(NativeOrEncoded::Encoded) - }) - }, + ) -> (Result>, bool) { + tracing::trace!( + target: "executor", + %method, + "Executing function", ); + + let result = + self.with_instance(runtime_code, ext, |_, mut instance, _onchain_version, mut ext| { + with_externalities_safe(&mut **ext, move || instance.call_export(method, data)) + }); (result, false) } } -impl RuntimeVersionOf for WasmExecutor { +impl RuntimeVersionOf for WasmExecutor +where + H: HostFunctions, +{ fn runtime_version( &self, ext: &mut dyn Externalities, runtime_code: &RuntimeCode, ) -> Result { - self.with_instance(runtime_code, ext, false, |_module, _instance, version, _ext| { + self.with_instance(runtime_code, ext, |_module, _instance, version, _ext| { Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) }) } @@ -311,13 +366,17 @@ impl RuntimeVersionOf for WasmExecutor { /// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence /// and dispatch to native code when possible, falling back on `WasmExecutor` when not. -pub struct NativeElseWasmExecutor { +pub struct NativeElseWasmExecutor +where + D: NativeExecutionDispatch, +{ /// Dummy field to avoid the compiler complaining about us not using `D`. - _dummy: std::marker::PhantomData, + _dummy: PhantomData, /// Native runtime version info. native_version: NativeVersion, /// Fallback wasm executor. - wasm: WasmExecutor, + wasm: + WasmExecutor>, } impl NativeElseWasmExecutor { @@ -329,39 +388,35 @@ impl NativeElseWasmExecutor { /// /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. /// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided. + /// + /// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse. + /// + /// `runtime_cache_size` - The capacity of runtime cache. pub fn new( fallback_method: WasmExecutionMethod, default_heap_pages: Option, max_runtime_instances: usize, + runtime_cache_size: u8, ) -> Self { - let extended = D::ExtendHostFunctions::host_functions(); - let mut host_functions = sp_io::SubstrateHostFunctions::host_functions() - .into_iter() - // filter out any host function overrides provided. - .filter(|host_fn| { - extended - .iter() - .find(|ext_host_fn| host_fn.name() == ext_host_fn.name()) - .is_none() - }) - .collect::>(); - - // Add the custom host functions provided by the user. - host_functions.extend(extended); - let wasm_executor = WasmExecutor::new( + let wasm = WasmExecutor::new( fallback_method, default_heap_pages, - host_functions, max_runtime_instances, None, + runtime_cache_size, ); NativeElseWasmExecutor { _dummy: Default::default(), native_version: D::native_version(), - wasm: wasm_executor, + wasm, } } + + /// Ignore missing function imports if set true. + pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) { + self.wasm.allow_missing_host_functions = allow_missing_host_functions + } } impl RuntimeVersionOf for NativeElseWasmExecutor { @@ -370,10 +425,9 @@ impl RuntimeVersionOf for NativeElseWasmExecutor ext: &mut dyn Externalities, runtime_code: &RuntimeCode, ) -> Result { - self.wasm - .with_instance(runtime_code, ext, false, |_module, _instance, version, _ext| { - Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) - }) + self.wasm.with_instance(runtime_code, ext, |_module, _instance, version, _ext| { + Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) + }) } } @@ -383,202 +437,56 @@ impl GetNativeVersion for NativeElseWasmExecutor } } -/// Helper inner struct to implement `RuntimeSpawn` extension. -pub struct RuntimeInstanceSpawn { - module: Arc, - tasks: parking_lot::Mutex>>>, - counter: AtomicU64, - scheduler: Box, -} - -impl RuntimeSpawn for RuntimeInstanceSpawn { - fn spawn_call(&self, dispatcher_ref: u32, func: u32, data: Vec) -> u64 { - let new_handle = self.counter.fetch_add(1, Ordering::Relaxed); - - let (sender, receiver) = mpsc::channel(); - self.tasks.lock().insert(new_handle, receiver); - - let module = self.module.clone(); - let scheduler = self.scheduler.clone(); - self.scheduler.spawn( - "executor-extra-runtime-instance", - Box::pin(async move { - let module = AssertUnwindSafe(module); - - let async_ext = match new_async_externalities(scheduler.clone()) { - Ok(val) => val, - Err(e) => { - log::error!( - target: "executor", - "Failed to setup externalities for async context: {}", - e, - ); - - // This will drop sender and receiver end will panic - return - }, - }; - - let mut async_ext = match async_ext.with_runtime_spawn(Box::new( - RuntimeInstanceSpawn::new(module.clone(), scheduler), - )) { - Ok(val) => val, - Err(e) => { - log::error!( - target: "executor", - "Failed to setup runtime extension for async externalities: {}", - e, - ); - - // This will drop sender and receiver end will panic - return - }, - }; - - let result = with_externalities_safe(&mut async_ext, move || { - // FIXME: Should be refactored to shared "instance factory". - // Instantiating wasm here every time is suboptimal at the moment, shared - // pool of instances should be used. - // - // https://github.com/paritytech/substrate/issues/7354 - let instance = - module.new_instance().expect("Failed to create new instance from module"); - - instance - .call(InvokeMethod::TableWithWrapper { dispatcher_ref, func }, &data[..]) - .expect("Failed to invoke instance.") - }); - - match result { - Ok(output) => { - let _ = sender.send(output); - }, - Err(error) => { - // If execution is panicked, the `join` in the original runtime code will - // panic as well, since the sender is dropped without sending anything. - log::error!("Call error in spawned task: {:?}", error); - }, - } - }), - ); - - new_handle - } - - fn join(&self, handle: u64) -> Vec { - let receiver = self.tasks.lock().remove(&handle).expect("No task for the handle"); - let output = receiver.recv().expect("Spawned task panicked for the handle"); - output - } -} - -impl RuntimeInstanceSpawn { - pub fn new( - module: Arc, - scheduler: Box, - ) -> Self { - Self { module, scheduler, counter: 0.into(), tasks: HashMap::new().into() } - } - - fn with_externalities_and_module( - module: Arc, - mut ext: &mut dyn Externalities, - ) -> Option { - ext.extension::() - .map(move |task_ext| Self::new(module, task_ext.clone())) - } -} - -/// Pre-registers the built-in extensions to the currently effective externalities. -/// -/// Meant to be called each time before calling into the runtime. -fn preregister_builtin_ext(module: Arc) { - sp_externalities::with_externalities(move |mut ext| { - if let Some(runtime_spawn) = - RuntimeInstanceSpawn::with_externalities_and_module(module, ext) - { - if let Err(e) = ext.register_extension(RuntimeSpawnExt(Box::new(runtime_spawn))) { - trace!( - target: "executor", - "Failed to register `RuntimeSpawnExt` instance on externalities: {:?}", - e, - ) - } - } - }); -} - impl CodeExecutor for NativeElseWasmExecutor { type Error = Error; - fn call< - R: Decode + Encode + PartialEq, - NC: FnOnce() -> result::Result> + UnwindSafe, - >( + fn call( &self, ext: &mut dyn Externalities, runtime_code: &RuntimeCode, method: &str, data: &[u8], use_native: bool, - native_call: Option, - ) -> (Result>, bool) { + ) -> (Result>, bool) { + tracing::trace!( + target: "executor", + function = %method, + "Executing function", + ); + let mut used_native = false; let result = self.wasm.with_instance( runtime_code, ext, - false, - |module, instance, onchain_version, mut ext| { + |_, mut instance, onchain_version, mut ext| { let onchain_version = onchain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?; let can_call_with = onchain_version.can_call_with(&self.native_version.runtime_version); - match (use_native, can_call_with, native_call) { - (_, false, _) | (false, _, _) => { - if !can_call_with { - trace!( - target: "executor", - "Request for native execution failed (native: {}, chain: {})", - self.native_version.runtime_version, - onchain_version, - ); - } - - with_externalities_safe(&mut **ext, move || { - preregister_builtin_ext(module.clone()); - instance.call_export(method, data).map(NativeOrEncoded::Encoded) - }) - }, - (true, true, Some(call)) => { - trace!( + if use_native && can_call_with { + tracing::trace!( + target: "executor", + native = %self.native_version.runtime_version, + chain = %onchain_version, + "Request for native execution succeeded", + ); + + used_native = true; + Ok(with_externalities_safe(&mut **ext, move || D::dispatch(method, data))? + .ok_or_else(|| Error::MethodNotFound(method.to_owned()))) + } else { + if !can_call_with { + tracing::trace!( target: "executor", - "Request for native execution with native call succeeded \ - (native: {}, chain: {}).", - self.native_version.runtime_version, - onchain_version, + native = %self.native_version.runtime_version, + chain = %onchain_version, + "Request for native execution failed", ); + } - used_native = true; - let res = with_externalities_safe(&mut **ext, move || (call)()) - .and_then(|r| r.map(NativeOrEncoded::Native).map_err(Error::ApiError)); - - Ok(res) - }, - _ => { - trace!( - target: "executor", - "Request for native execution succeeded (native: {}, chain: {})", - self.native_version.runtime_version, - onchain_version - ); - - used_native = true; - Ok(with_externalities_safe(&mut **ext, move || D::dispatch(method, data))? - .map(NativeOrEncoded::Encoded) - .ok_or_else(|| Error::MethodNotFound(method.to_owned()))) - }, + with_externalities_safe(&mut **ext, move || instance.call_export(method, data)) } }, ); @@ -638,9 +546,23 @@ mod tests { WasmExecutionMethod::Interpreted, None, 8, + 2, ); + + fn extract_host_functions( + _: &WasmExecutor, + ) -> Vec<&'static dyn sp_wasm_interface::Function> + where + H: HostFunctions, + { + H::host_functions() + } + my_interface::HostFunctions::host_functions().iter().for_each(|function| { - assert_eq!(executor.wasm.host_functions.iter().filter(|f| f == &function).count(), 2); + assert_eq!( + extract_host_functions(&executor.wasm).iter().filter(|f| f == &function).count(), + 2 + ); }); my_interface::say_hello_world("hey"); diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index b11e3958dbc81..5576fff186bb2 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,6 +23,7 @@ use crate::error::{Error, WasmError}; use codec::Decode; +use lru::LruCache; use parking_lot::Mutex; use sc_executor_common::{ runtime_blob::RuntimeBlob, @@ -31,12 +32,13 @@ use sc_executor_common::{ use sp_core::traits::{Externalities, FetchRuntimeCode, RuntimeCode}; use sp_version::RuntimeVersion; use std::{ + num::NonZeroUsize, panic::AssertUnwindSafe, path::{Path, PathBuf}, sync::Arc, }; -use sp_wasm_interface::Function; +use sp_wasm_interface::HostFunctions; /// Specification of different methods of executing the runtime Wasm code. #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] @@ -44,8 +46,10 @@ pub enum WasmExecutionMethod { /// Uses the Wasmi interpreter. Interpreted, /// Uses the Wasmtime compiled runtime. - #[cfg(feature = "wasmtime")] - Compiled, + Compiled { + /// The instantiation strategy to use. + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy, + }, } impl Default for WasmExecutionMethod { @@ -54,29 +58,36 @@ impl Default for WasmExecutionMethod { } } -/// A Wasm runtime object along with its cached runtime version. -struct VersionedRuntime { +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +struct VersionedRuntimeId { /// Runtime code hash. code_hash: Vec, /// Wasm runtime type. wasm_method: WasmExecutionMethod, - /// Shared runtime that can spawn instances. - module: Arc, /// The number of WebAssembly heap pages this instance was created with. heap_pages: u64, +} + +/// A Wasm runtime object along with its cached runtime version. +struct VersionedRuntime { + /// Shared runtime that can spawn instances. + module: Arc, /// Runtime version according to `Core_version` if any. version: Option, + + // TODO: Remove this once the legacy instance reuse instantiation strategy + // for `wasmtime` is gone, as this only makes sense with that particular strategy. /// Cached instance pool. - instances: Vec>>>, + instances: Arc>>>>, } impl VersionedRuntime { /// Run the given closure `f` with an instance of this runtime. - fn with_instance<'c, R, F>(&self, ext: &mut dyn Externalities, f: F) -> Result + fn with_instance(&self, ext: &mut dyn Externalities, f: F) -> Result where F: FnOnce( &Arc, - &dyn WasmInstance, + &mut dyn WasmInstance, Option<&RuntimeVersion>, &mut dyn Externalities, ) -> Result, @@ -90,31 +101,31 @@ impl VersionedRuntime { match instance { Some((index, mut locked)) => { - let (instance, new_inst) = locked + let (mut instance, new_inst) = locked .take() .map(|r| Ok((r, false))) .unwrap_or_else(|| self.module.new_instance().map(|i| (i, true)))?; - let result = f(&self.module, &*instance, self.version.as_ref(), ext); + let result = f(&self.module, &mut *instance, self.version.as_ref(), ext); if let Err(e) = &result { if new_inst { - log::warn!( + tracing::warn!( target: "wasm-runtime", - "Fresh runtime instance failed with {:?}", - e, + error = %e, + "Fresh runtime instance failed", ) } else { - log::warn!( + tracing::warn!( target: "wasm-runtime", - "Evicting failed runtime instance: {:?}", - e, + error = %e, + "Evicting failed runtime instance", ); } } else { *locked = Some(instance); if new_inst { - log::debug!( + tracing::debug!( target: "wasm-runtime", "Allocated WASM instance {}/{}", index + 1, @@ -126,19 +137,17 @@ impl VersionedRuntime { result }, None => { - log::warn!(target: "wasm-runtime", "Ran out of free WASM instances"); + tracing::warn!(target: "wasm-runtime", "Ran out of free WASM instances"); // Allocate a new instance - let instance = self.module.new_instance()?; + let mut instance = self.module.new_instance()?; - f(&self.module, &*instance, self.version.as_ref(), ext) + f(&self.module, &mut *instance, self.version.as_ref(), ext) }, } } } -const MAX_RUNTIMES: usize = 2; - /// Cache for the runtimes. /// /// When an instance is requested for the first time it is added to this cache. Metadata is kept @@ -149,12 +158,12 @@ const MAX_RUNTIMES: usize = 2; /// the memory reset to the initial memory. So, one runtime instance is reused for every fetch /// request. /// -/// The size of cache is equal to `MAX_RUNTIMES`. +/// The size of cache is configurable via the cli option `--runtime-cache-size`. pub struct RuntimeCache { /// A cache of runtimes along with metadata. /// /// Runtimes sorted by recent usage. The most recently used is at the front. - runtimes: Mutex<[Option>; MAX_RUNTIMES]>, + runtimes: Mutex>>, /// The size of the instances cache for each runtime. max_runtime_instances: usize, cache_path: Option, @@ -163,13 +172,22 @@ pub struct RuntimeCache { impl RuntimeCache { /// Creates a new instance of a runtimes cache. /// - /// `max_runtime_instances` specifies the number of runtime instances preserved in an in-memory - /// cache. + /// `max_runtime_instances` specifies the number of instances per runtime preserved in an + /// in-memory cache. /// /// `cache_path` allows to specify an optional directory where the executor can store files /// for caching. - pub fn new(max_runtime_instances: usize, cache_path: Option) -> RuntimeCache { - RuntimeCache { runtimes: Default::default(), max_runtime_instances, cache_path } + /// + /// `runtime_cache_size` specifies the number of different runtimes versions preserved in an + /// in-memory cache, must always be at least 1. + pub fn new( + max_runtime_instances: usize, + cache_path: Option, + runtime_cache_size: u8, + ) -> RuntimeCache { + let cap = + NonZeroUsize::new(runtime_cache_size.max(1) as usize).expect("cache size is not zero"); + RuntimeCache { runtimes: Mutex::new(LruCache::new(cap)), max_runtime_instances, cache_path } } /// Prepares a WASM module instance and executes given function for it. @@ -177,43 +195,39 @@ impl RuntimeCache { /// This uses internal cache to find available instance or create a new one. /// # Parameters /// - /// `code` - Provides external code or tells the executor to fetch it from storage. - /// /// `runtime_code` - The runtime wasm code used setup the runtime. /// /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. /// /// `wasm_method` - Type of WASM backend to use. /// - /// `host_functions` - The host functions that should be registered for the Wasm runtime. - /// /// `allow_missing_func_imports` - Ignore missing function imports. /// - /// `max_runtime_instances` - The size of the instances cache. - /// /// `f` - Function to execute. /// + /// `H` - A compile-time list of host functions to expose to the runtime. + /// /// # Returns result of `f` wrapped in an additional result. /// In case of failure one of two errors can be returned: /// - /// `Err::InvalidCode` is returned for runtime code issues. + /// `Err::RuntimeConstruction` is returned for runtime construction issues. /// /// `Error::InvalidMemoryReference` is returned if no memory export with the /// identifier `memory` can be found in the runtime. - pub fn with_instance<'c, R, F>( + pub fn with_instance<'c, H, R, F>( &self, runtime_code: &'c RuntimeCode<'c>, ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, default_heap_pages: u64, - host_functions: &[&'static dyn Function], allow_missing_func_imports: bool, f: F, ) -> Result, Error> where + H: HostFunctions, F: FnOnce( &Arc, - &dyn WasmInstance, + &mut dyn WasmInstance, Option<&RuntimeVersion>, &mut dyn Externalities, ) -> Result, @@ -221,141 +235,114 @@ impl RuntimeCache { let code_hash = &runtime_code.hash; let heap_pages = runtime_code.heap_pages.unwrap_or(default_heap_pages); - let mut runtimes = self.runtimes.lock(); // this must be released prior to calling f - let pos = runtimes.iter().position(|r| { - r.as_ref().map_or(false, |r| { - r.wasm_method == wasm_method && - r.code_hash == *code_hash && - r.heap_pages == heap_pages - }) - }); + let versioned_runtime_id = + VersionedRuntimeId { code_hash: code_hash.clone(), heap_pages, wasm_method }; - let runtime = match pos { - Some(n) => runtimes[n] - .clone() - .expect("`position` only returns `Some` for entries that are `Some`"), - None => { - let code = runtime_code.fetch_runtime_code().ok_or(WasmError::CodeNotFound)?; + let mut runtimes = self.runtimes.lock(); // this must be released prior to calling f + let versioned_runtime = if let Some(versioned_runtime) = runtimes.get(&versioned_runtime_id) + { + versioned_runtime.clone() + } else { + let code = runtime_code.fetch_runtime_code().ok_or(WasmError::CodeNotFound)?; + + let time = std::time::Instant::now(); + + let result = create_versioned_wasm_runtime::( + &code, + ext, + wasm_method, + heap_pages, + allow_missing_func_imports, + self.max_runtime_instances, + self.cache_path.as_deref(), + ); + + match result { + Ok(ref result) => { + tracing::debug!( + target: "wasm-runtime", + "Prepared new runtime version {:?} in {} ms.", + result.version, + time.elapsed().as_millis(), + ); + }, + Err(ref err) => { + tracing::warn!(target: "wasm-runtime", error = ?err, "Cannot create a runtime"); + }, + } - let time = std::time::Instant::now(); + let versioned_runtime = Arc::new(result?); - let result = create_versioned_wasm_runtime( - &code, - code_hash.clone(), - ext, - wasm_method, - heap_pages, - host_functions.into(), - allow_missing_func_imports, - self.max_runtime_instances, - self.cache_path.as_deref(), - ); + // Save new versioned wasm runtime in cache + runtimes.put(versioned_runtime_id, versioned_runtime.clone()); - match result { - Ok(ref result) => { - log::debug!( - target: "wasm-runtime", - "Prepared new runtime version {:?} in {} ms.", - result.version, - time.elapsed().as_millis(), - ); - }, - Err(ref err) => { - log::warn!(target: "wasm-runtime", "Cannot create a runtime: {:?}", err); - }, - } - - Arc::new(result?) - }, + versioned_runtime }; - // Rearrange runtimes by last recently used. - match pos { - Some(0) => {}, - Some(n) => - for i in (1..n + 1).rev() { - runtimes.swap(i, i - 1); - }, - None => { - runtimes[MAX_RUNTIMES - 1] = Some(runtime.clone()); - for i in (1..MAX_RUNTIMES).rev() { - runtimes.swap(i, i - 1); - } - }, - } + // Lock must be released prior to calling f drop(runtimes); - Ok(runtime.with_instance(ext, f)) + Ok(versioned_runtime.with_instance(ext, f)) } } /// Create a wasm runtime with the given `code`. -pub fn create_wasm_runtime_with_code( +pub fn create_wasm_runtime_with_code( wasm_method: WasmExecutionMethod, heap_pages: u64, blob: RuntimeBlob, - host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, cache_path: Option<&Path>, -) -> Result, WasmError> { +) -> Result, WasmError> +where + H: HostFunctions, +{ match wasm_method { WasmExecutionMethod::Interpreted => { // Wasmi doesn't have any need in a cache directory. // // We drop the cache_path here to silence warnings that cache_path is not used if // compiling without the `wasmtime` flag. - drop(cache_path); + let _ = cache_path; sc_executor_wasmi::create_runtime( blob, heap_pages, - host_functions, + H::host_functions(), allow_missing_func_imports, ) .map(|runtime| -> Arc { Arc::new(runtime) }) }, - #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => sc_executor_wasmtime::create_runtime( - blob, - sc_executor_wasmtime::Config { - heap_pages: heap_pages as u32, - max_memory_pages: None, - allow_missing_func_imports, - cache_path: cache_path.map(ToOwned::to_owned), - semantics: sc_executor_wasmtime::Semantics { - fast_instance_reuse: true, - deterministic_stack_limit: None, - canonicalize_nans: false, + WasmExecutionMethod::Compiled { instantiation_strategy } => + sc_executor_wasmtime::create_runtime::( + blob, + sc_executor_wasmtime::Config { + allow_missing_func_imports, + cache_path: cache_path.map(ToOwned::to_owned), + semantics: sc_executor_wasmtime::Semantics { + extra_heap_pages: heap_pages, + instantiation_strategy, + deterministic_stack_limit: None, + canonicalize_nans: false, + parallel_compilation: true, + max_memory_size: None, + }, }, - }, - host_functions, - ) - .map(|runtime| -> Arc { Arc::new(runtime) }), + ) + .map(|runtime| -> Arc { Arc::new(runtime) }), } } fn decode_version(mut version: &[u8]) -> Result { - let v: RuntimeVersion = sp_api::OldRuntimeVersion::decode(&mut &version[..]) - .map_err(|_| { - WasmError::Instantiation( - "failed to decode \"Core_version\" result using old runtime version".into(), - ) - })? - .into(); - - let core_api_id = sp_core::hashing::blake2_64(b"Core"); - if v.has_api_with(&core_api_id, |v| v >= 3) { - sp_api::RuntimeVersion::decode(&mut version).map_err(|_| { - WasmError::Instantiation("failed to decode \"Core_version\" result".into()) - }) - } else { - Ok(v) - } + Decode::decode(&mut version).map_err(|_| { + WasmError::Instantiation( + "failed to decode \"Core_version\" result using old runtime version".into(), + ) + }) } fn decode_runtime_apis(apis: &[u8]) -> Result, WasmError> { use sp_api::RUNTIME_API_INFO_SIZE; - use std::convert::TryFrom; apis.chunks(RUNTIME_API_INFO_SIZE) .map(|chunk| { @@ -375,17 +362,25 @@ fn decode_runtime_apis(apis: &[u8]) -> Result, WasmError> { /// sections, `Err` will be returned. pub fn read_embedded_version(blob: &RuntimeBlob) -> Result, WasmError> { if let Some(mut version_section) = blob.custom_section_contents("runtime_version") { - // We do not use `decode_version` here because the runtime_version section is not supposed - // to ever contain a legacy version. Apart from that `decode_version` relies on presence - // of a special API in the `apis` field to treat the input as a non-legacy version. However - // the structure found in the `runtime_version` always contain an empty `apis` field. - // Therefore the version read will be mistakenly treated as an legacy one. - let mut decoded_version = sp_api::RuntimeVersion::decode(&mut version_section) - .map_err(|_| WasmError::Instantiation("failed to decode version section".into()))?; - - // Don't stop on this and check if there is a special section that encodes all runtime APIs. - if let Some(apis_section) = blob.custom_section_contents("runtime_apis") { - decoded_version.apis = decode_runtime_apis(apis_section)?.into(); + let apis = blob + .custom_section_contents("runtime_apis") + .map(decode_runtime_apis) + .transpose()? + .map(Into::into); + + let core_version = apis.as_ref().and_then(sp_version::core_version_from_apis); + // We do not use `RuntimeVersion::decode` here because that `decode_version` relies on + // presence of a special API in the `apis` field to treat the input as a non-legacy version. + // However the structure found in the `runtime_version` always contain an empty `apis` + // field. Therefore the version read will be mistakenly treated as an legacy one. + let mut decoded_version = sp_version::RuntimeVersion::decode_with_version_hint( + &mut version_section, + core_version, + ) + .map_err(|_| WasmError::Instantiation("failed to decode version section".into()))?; + + if let Some(apis) = apis { + decoded_version.apis = apis; } Ok(Some(decoded_version)) @@ -394,31 +389,31 @@ pub fn read_embedded_version(blob: &RuntimeBlob) -> Result( code: &[u8], - code_hash: Vec, ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, heap_pages: u64, - host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, max_instances: usize, cache_path: Option<&Path>, -) -> Result { +) -> Result +where + H: HostFunctions, +{ // The incoming code may be actually compressed. We decompress it here and then work with // the uncompressed code from now on. - let blob = sc_executor_common::runtime_blob::RuntimeBlob::uncompress_if_needed(&code)?; + let blob = sc_executor_common::runtime_blob::RuntimeBlob::uncompress_if_needed(code)?; // Use the runtime blob to scan if there is any metadata embedded into the wasm binary // pertaining to runtime version. We do it before consuming the runtime blob for creating the // runtime. let mut version: Option<_> = read_embedded_version(&blob)?; - let runtime = create_wasm_runtime_with_code( + let runtime = create_wasm_runtime_with_code::( wasm_method, heap_pages, blob, - host_functions, allow_missing_func_imports, cache_path, )?; @@ -449,7 +444,7 @@ fn create_versioned_wasm_runtime( let mut instances = Vec::with_capacity(max_instances); instances.resize_with(max_instances, || Mutex::new(None)); - Ok(VersionedRuntime { code_hash, module: runtime, version, heap_pages, wasm_method, instances }) + Ok(VersionedRuntime { module: runtime, version, instances: Arc::new(instances) }) } #[cfg(test)] @@ -457,9 +452,20 @@ mod tests { use super::*; use codec::Encode; use sp_api::{Core, RuntimeApiInfo}; + use sp_runtime::RuntimeString; use sp_wasm_interface::HostFunctions; use substrate_test_runtime::Block; + #[derive(Encode)] + pub struct OldRuntimeVersion { + pub spec_name: RuntimeString, + pub impl_name: RuntimeString, + pub authoring_version: u32, + pub spec_version: u32, + pub impl_version: u32, + pub apis: sp_version::ApisVec, + } + #[test] fn host_functions_are_equal() { let host_functions = sp_io::SubstrateHostFunctions::host_functions(); @@ -470,7 +476,7 @@ mod tests { #[test] fn old_runtime_version_decodes() { - let old_runtime_version = sp_api::OldRuntimeVersion { + let old_runtime_version = OldRuntimeVersion { spec_name: "test".into(), impl_name: "test".into(), authoring_version: 1, @@ -481,11 +487,12 @@ mod tests { let version = decode_version(&old_runtime_version.encode()).unwrap(); assert_eq!(1, version.transaction_version); + assert_eq!(0, version.state_version); } #[test] fn old_runtime_version_decodes_fails_with_version_3() { - let old_runtime_version = sp_api::OldRuntimeVersion { + let old_runtime_version = OldRuntimeVersion { spec_name: "test".into(), impl_name: "test".into(), authoring_version: 1, @@ -507,10 +514,27 @@ mod tests { impl_version: 1, apis: sp_api::create_apis_vec!([(>::ID, 3)]), transaction_version: 3, + state_version: 4, }; let version = decode_version(&old_runtime_version.encode()).unwrap(); assert_eq!(3, version.transaction_version); + assert_eq!(0, version.state_version); + + let old_runtime_version = sp_api::RuntimeVersion { + spec_name: "test".into(), + impl_name: "test".into(), + authoring_version: 1, + spec_version: 1, + impl_version: 1, + apis: sp_api::create_apis_vec!([(>::ID, 4)]), + transaction_version: 3, + state_version: 4, + }; + + let version = decode_version(&old_runtime_version.encode()).unwrap(); + assert_eq!(3, version.transaction_version); + assert_eq!(4, version.state_version); } #[test] @@ -520,15 +544,15 @@ mod tests { sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT, ) .expect("Decompressing works"); - let runtime_version = RuntimeVersion { spec_name: "test_replace".into(), impl_name: "test_replace".into(), authoring_version: 100, spec_version: 100, impl_version: 100, - apis: sp_api::create_apis_vec!([(>::ID, 3)]), + apis: sp_api::create_apis_vec!([(>::ID, 4)]), transaction_version: 100, + state_version: 1, }; let embedded = sp_version::embed::embed_runtime_version(&wasm, runtime_version.clone()) diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index 324b2bdd0baeb..4235440023c89 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-executor-wasmi" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "This crate provides an implementation of `WasmRuntime` that is baked by wasmi." documentation = "https://docs.rs/sc-executor-wasmi" @@ -14,12 +14,9 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.8" -wasmi = "0.9.0" -codec = { package = "parity-scale-codec", version = "2.0.0" } +log = "0.4.17" +wasmi = "0.13" +sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } -sc-allocator = { version = "4.0.0-dev", path = "../../allocator" } -sp-wasm-interface = { version = "4.0.0-dev", path = "../../../primitives/wasm-interface" } -sp-runtime-interface = { version = "4.0.0-dev", path = "../../../primitives/runtime-interface" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -scoped-tls = "1.0" +sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" } +sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 6052662fa7ccf..6eb38146946b8 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,21 +18,9 @@ //! This crate provides an implementation of `WasmModule` that is baked by wasmi. -use codec::{Decode, Encode}; +use std::{cell::RefCell, str, sync::Arc}; + use log::{debug, error, trace}; -use sc_executor_common::{ - error::{Error, WasmError}, - runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, - sandbox, - util::MemoryTransfer, - wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, -}; -use sp_core::sandbox as sandbox_primitives; -use sp_runtime_interface::unpack_ptr_and_len; -use sp_wasm_interface::{ - Function, FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, WordSize, -}; -use std::{cell::RefCell, rc::Rc, str, sync::Arc}; use wasmi::{ memory_units::Pages, FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, @@ -40,75 +28,43 @@ use wasmi::{ TableRef, }; +use sc_allocator::AllocationStats; +use sc_executor_common::{ + error::{Error, MessageWithBacktrace, WasmError}, + runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, + wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, +}; +use sp_runtime_interface::unpack_ptr_and_len; +use sp_wasm_interface::{Function, FunctionContext, Pointer, Result as WResult, WordSize}; + struct FunctionExecutor { - sandbox_store: Rc>>, heap: RefCell, memory: MemoryRef, - table: Option, host_functions: Arc>, allow_missing_func_imports: bool, missing_functions: Arc>, + panic_message: Option, } impl FunctionExecutor { fn new( m: MemoryRef, heap_base: u32, - t: Option, host_functions: Arc>, allow_missing_func_imports: bool, missing_functions: Arc>, ) -> Result { Ok(FunctionExecutor { - sandbox_store: Rc::new(RefCell::new(sandbox::Store::new( - sandbox::SandboxBackend::Wasmi, - ))), heap: RefCell::new(sc_allocator::FreeingBumpHeapAllocator::new(heap_base)), memory: m, - table: t, host_functions, allow_missing_func_imports, missing_functions, + panic_message: None, }) } } -struct SandboxContext<'a> { - executor: &'a mut FunctionExecutor, - dispatch_thunk: wasmi::FuncRef, -} - -impl<'a> sandbox::SandboxContext for SandboxContext<'a> { - fn invoke( - &mut self, - invoke_args_ptr: Pointer, - invoke_args_len: WordSize, - state: u32, - func_idx: sandbox::SupervisorFuncIndex, - ) -> Result { - let result = wasmi::FuncInstance::invoke( - &self.dispatch_thunk, - &[ - RuntimeValue::I32(u32::from(invoke_args_ptr) as i32), - RuntimeValue::I32(invoke_args_len as i32), - RuntimeValue::I32(state as i32), - RuntimeValue::I32(usize::from(func_idx) as i32), - ], - self.executor, - ); - - match result { - Ok(Some(RuntimeValue::I64(val))) => Ok(val), - Ok(_) => return Err("Supervisor function returned unexpected result!".into()), - Err(err) => Err(Error::Trap(err)), - } - } - - fn supervisor_context(&mut self) -> &mut dyn FunctionContext { - self.executor - } -} - impl FunctionContext for FunctionExecutor { fn read_memory_into(&self, address: Pointer, dest: &mut [u8]) -> WResult<()> { self.memory.get_into(address.into(), dest).map_err(|e| e.to_string()) @@ -130,182 +86,8 @@ impl FunctionContext for FunctionExecutor { .with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string())) } - fn sandbox(&mut self) -> &mut dyn Sandbox { - self - } -} - -impl Sandbox for FunctionExecutor { - fn memory_get( - &mut self, - memory_id: MemoryId, - offset: WordSize, - buf_ptr: Pointer, - buf_len: WordSize, - ) -> WResult { - let sandboxed_memory = - self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - - let len = buf_len as usize; - - let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { - Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if let Err(_) = self.memory.set(buf_ptr.into(), &buffer) { - return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_primitives::ERR_OK) - } - - fn memory_set( - &mut self, - memory_id: MemoryId, - offset: WordSize, - val_ptr: Pointer, - val_len: WordSize, - ) -> WResult { - let sandboxed_memory = - self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - - let len = val_len as usize; - - let buffer = match self.memory.get(val_ptr.into(), len) { - Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if let Err(_) = sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer) { - return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_primitives::ERR_OK) - } - - fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> { - self.sandbox_store - .borrow_mut() - .memory_teardown(memory_id) - .map_err(|e| e.to_string()) - } - - fn memory_new(&mut self, initial: u32, maximum: u32) -> WResult { - self.sandbox_store - .borrow_mut() - .new_memory(initial, maximum) - .map_err(|e| e.to_string()) - } - - fn invoke( - &mut self, - instance_id: u32, - export_name: &str, - mut args: &[u8], - return_val: Pointer, - return_val_len: WordSize, - state: u32, - ) -> WResult { - trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); - - // Deserialize arguments and convert them into wasmi types. - let args = Vec::::decode(&mut args) - .map_err(|_| "Can't decode serialized arguments for the invocation")? - .into_iter() - .map(Into::into) - .collect::>(); - - let instance = - self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?; - - let dispatch_thunk = self - .sandbox_store - .borrow() - .dispatch_thunk(instance_id) - .map_err(|e| e.to_string())?; - - match instance.invoke( - export_name, - &args, - state, - &mut SandboxContext { dispatch_thunk, executor: self }, - ) { - Ok(None) => Ok(sandbox_primitives::ERR_OK), - Ok(Some(val)) => { - // Serialize return value and write it back into the memory. - sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { - if val.len() > return_val_len as usize { - Err("Return value buffer is too small")?; - } - self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?; - Ok(sandbox_primitives::ERR_OK) - }) - }, - Err(_) => Ok(sandbox_primitives::ERR_EXECUTION), - } - } - - fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> { - self.sandbox_store - .borrow_mut() - .instance_teardown(instance_id) - .map_err(|e| e.to_string()) - } - - fn instance_new( - &mut self, - dispatch_thunk_id: u32, - wasm: &[u8], - raw_env_def: &[u8], - state: u32, - ) -> WResult { - // Extract a dispatch thunk from instance's table by the specified index. - let dispatch_thunk = { - let table = self - .table - .as_ref() - .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?; - table - .get(dispatch_thunk_id) - .map_err(|_| "dispatch_thunk_idx is out of the table bounds")? - .ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")? - }; - - let guest_env = - match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) { - Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), - }; - - let store = self.sandbox_store.clone(); - let result = store.borrow_mut().instantiate( - wasm, - guest_env, - state, - &mut SandboxContext { executor: self, dispatch_thunk: dispatch_thunk.clone() }, - ); - - let instance_idx_or_err_code = - match result.map(|i| i.register(&mut store.borrow_mut(), dispatch_thunk)) { - Ok(instance_idx) => instance_idx, - Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, - Err(_) => sandbox_primitives::ERR_MODULE, - }; - - Ok(instance_idx_or_err_code) - } - - fn get_global_val( - &self, - instance_idx: u32, - name: &str, - ) -> WResult> { - self.sandbox_store - .borrow() - .instance(instance_idx) - .map(|i| i.get_global_val(name)) - .map_err(|e| e.to_string()) + fn register_panic_error_message(&mut self, message: &str) { + self.panic_message = Some(message.to_owned()); } } @@ -451,9 +233,9 @@ impl wasmi::Externals for FunctionExecutor { fn get_mem_instance(module: &ModuleRef) -> Result { Ok(module .export_by_name("memory") - .ok_or_else(|| Error::InvalidMemoryReference)? + .ok_or(Error::InvalidMemoryReference)? .as_memory() - .ok_or_else(|| Error::InvalidMemoryReference)? + .ok_or(Error::InvalidMemoryReference)? .clone()) } @@ -462,9 +244,9 @@ fn get_mem_instance(module: &ModuleRef) -> Result { fn get_heap_base(module: &ModuleRef) -> Result { let heap_base_val = module .export_by_name("__heap_base") - .ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)? + .ok_or(Error::HeapBaseNotFoundOrInvalid)? .as_global() - .ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)? + .ok_or(Error::HeapBaseNotFoundOrInvalid)? .get(); match heap_base_val { @@ -482,6 +264,7 @@ fn call_in_wasm_module( host_functions: Arc>, allow_missing_func_imports: bool, missing_functions: Arc>, + allocation_stats: &mut Option, ) -> Result, Error> { // Initialize FunctionExecutor. let table: Option = module_instance @@ -492,7 +275,6 @@ fn call_in_wasm_module( let mut function_executor = FunctionExecutor::new( memory.clone(), heap_base, - table.clone(), host_functions, allow_missing_func_imports, missing_functions, @@ -502,12 +284,31 @@ fn call_in_wasm_module( let offset = function_executor.allocate_memory(data.len() as u32)?; function_executor.write_memory(offset, data)?; + fn convert_trap(executor: &mut FunctionExecutor, trap: wasmi::Trap) -> Error { + if let Some(message) = executor.panic_message.take() { + Error::AbortedDueToPanic(MessageWithBacktrace { message, backtrace: None }) + } else { + Error::AbortedDueToTrap(MessageWithBacktrace { + message: trap.to_string(), + backtrace: None, + }) + } + } + let result = match method { - InvokeMethod::Export(method) => module_instance.invoke_export( - method, - &[I32(u32::from(offset) as i32), I32(data.len() as i32)], - &mut function_executor, - ), + InvokeMethod::Export(method) => module_instance + .invoke_export( + method, + &[I32(u32::from(offset) as i32), I32(data.len() as i32)], + &mut function_executor, + ) + .map_err(|error| { + if let wasmi::Error::Trap(trap) = error { + convert_trap(&mut function_executor, trap) + } else { + error.into() + } + }), InvokeMethod::Table(func_ref) => { let func = table .ok_or(Error::NoTable)? @@ -518,7 +319,7 @@ fn call_in_wasm_module( &[I32(u32::from(offset) as i32), I32(data.len() as i32)], &mut function_executor, ) - .map_err(Into::into) + .map_err(|trap| convert_trap(&mut function_executor, trap)) }, InvokeMethod::TableWithWrapper { dispatcher_ref, func } => { let dispatcher = table @@ -531,14 +332,17 @@ fn call_in_wasm_module( &[I32(func as _), I32(u32::from(offset) as i32), I32(data.len() as i32)], &mut function_executor, ) - .map_err(Into::into) + .map_err(|trap| convert_trap(&mut function_executor, trap)) }, }; + *allocation_stats = Some(function_executor.heap.borrow().stats()); + match result { Ok(Some(I64(r))) => { let (ptr, length) = unpack_ptr_and_len(r as u64); - memory.get(ptr.into(), length as usize).map_err(|_| Error::Runtime) + #[allow(deprecated)] + memory.get(ptr, length as usize).map_err(|_| Error::Runtime) }, Err(e) => { trace!( @@ -546,7 +350,7 @@ fn call_in_wasm_module( "Failed to execute code with {} pages", memory.current_size().0, ); - Err(e.into()) + Err(e) }, _ => Err(Error::InvalidReturn), } @@ -735,8 +539,13 @@ pub struct WasmiInstance { // `self.instance` unsafe impl Send for WasmiInstance {} -impl WasmInstance for WasmiInstance { - fn call(&self, method: InvokeMethod, data: &[u8]) -> Result, Error> { +impl WasmiInstance { + fn call_impl( + &mut self, + method: InvokeMethod, + data: &[u8], + allocation_stats: &mut Option, + ) -> Result, Error> { // We reuse a single wasm instance for multiple calls and a previous call (if any) // altered the state. Therefore, we need to restore the instance to original state. @@ -764,10 +573,23 @@ impl WasmInstance for WasmiInstance { self.host_functions.clone(), self.allow_missing_func_imports, self.missing_functions.clone(), + allocation_stats, ) } +} + +impl WasmInstance for WasmiInstance { + fn call_with_allocation_stats( + &mut self, + method: InvokeMethod, + data: &[u8], + ) -> (Result, Error>, Option) { + let mut allocation_stats = None; + let result = self.call_impl(method, data, &mut allocation_stats); + (result, allocation_stats) + } - fn get_global_const(&self, name: &str) -> Result, Error> { + fn get_global_const(&mut self, name: &str) -> Result, Error> { match self.instance.export_by_name(name) { Some(global) => Ok(Some( global diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 3158cdecc3263..7e38929f05a13 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-executor-wasmtime" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Defines a `WasmRuntime` that uses the Wasmtime JIT to execute." readme = "README.md" @@ -13,23 +13,38 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -libc = "0.2.90" cfg-if = "1.0" -log = "0.4.8" -scoped-tls = "1.0" -parity-wasm = "0.42.0" -codec = { package = "parity-scale-codec", version = "2.0.0" } -sc-executor-common = { version = "0.10.0-dev", path = "../common" } -sp-wasm-interface = { version = "4.0.0-dev", path = "../../../primitives/wasm-interface" } -sp-runtime-interface = { version = "4.0.0-dev", path = "../../../primitives/runtime-interface" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sc-allocator = { version = "4.0.0-dev", path = "../../allocator" } -wasmtime = { version = "0.27.0", default-features = false, features = [ - "cache", - "parallel-compilation", +libc = "0.2.121" +log = "0.4.17" + +# When bumping wasmtime do not forget to also bump rustix +# to exactly the same version as used by wasmtime! +wasmtime = { version = "1.0.0", default-features = false, features = [ + "cache", + "cranelift", + "jitdump", + "parallel-compilation", + "memory-init-cow", + "pooling-allocator", ] } +sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } +sc-executor-common = { version = "0.10.0-dev", path = "../common" } +sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" } +sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } + +# Here we include the rustix crate in the exactly same semver-compatible version as used by +# wasmtime and enable its 'use-libc' flag. +# +# By default rustix directly calls the appropriate syscalls completely bypassing libc; +# this doesn't have any actual benefits for us besides making it harder to debug memory +# problems (since then `mmap` etc. cannot be easily hooked into). +rustix = { version = "0.35.9", default-features = false, features = ["std", "mm", "fs", "param", "use-libc"] } +once_cell = "1.12.0" [dev-dependencies] -sc-runtime-test = { version = "2.0.0", path = "../runtime-test" } -sp-io = { version = "4.0.0-dev", path = "../../../primitives/io" } wat = "1.0" +sc-runtime-test = { version = "2.0.0", path = "../runtime-test" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } +tempfile = "3.3.0" +paste = "1.0" +codec = { package = "parity-scale-codec", version = "3.0.0" } diff --git a/client/rpc-api/src/errors.rs b/client/executor/wasmtime/build.rs similarity index 69% rename from client/rpc-api/src/errors.rs rename to client/executor/wasmtime/build.rs index 8e4883a4cc20c..c514b0041bc34 100644 --- a/client/rpc-api/src/errors.rs +++ b/client/executor/wasmtime/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,13 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use log::warn; +use std::env; -pub fn internal(e: E) -> jsonrpc_core::Error { - warn!("Unknown error: {:?}", e); - jsonrpc_core::Error { - code: jsonrpc_core::ErrorCode::InternalError, - message: "Unknown error occurred".into(), - data: Some(format!("{:?}", e).into()), +fn main() { + if let Ok(profile) = env::var("PROFILE") { + println!("cargo:rustc-cfg=build_type=\"{}\"", profile); } } diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index 8453ec3954354..0d9eac875170b 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,65 +19,50 @@ //! This module defines `HostState` and `HostContext` structs which provide logic and state //! required for execution of host. -use crate::instance_wrapper::InstanceWrapper; -use codec::{Decode, Encode}; -use log::trace; -use sc_allocator::FreeingBumpHeapAllocator; -use sc_executor_common::{ - error::Result, - sandbox::{self, SupervisorFuncIndex}, - util::MemoryTransfer, -}; -use sp_core::sandbox as sandbox_primitives; -use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize}; -use std::{cell::RefCell, rc::Rc}; -use wasmtime::{Func, Val}; +use wasmtime::Caller; + +use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; +use sp_wasm_interface::{Pointer, WordSize}; + +use crate::{runtime::StoreData, util}; /// The state required to construct a HostContext context. The context only lasts for one host /// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make /// many different host calls that must share state. pub struct HostState { - /// We need some interior mutability here since the host state is shared between all host - /// function handlers and the wasmtime backend's `impl WasmRuntime`. - /// - /// Furthermore, because of recursive calls (e.g. runtime can create and call an sandboxed - /// instance which in turn can call the runtime back) we have to be very careful with borrowing - /// those. - /// - /// Basically, most of the interactions should do temporary borrow immediately releasing the - /// borrow after performing necessary queries/changes. - sandbox_store: Rc>>, - allocator: RefCell, - instance: Rc, + allocator: FreeingBumpHeapAllocator, + panic_message: Option, } impl HostState { /// Constructs a new `HostState`. - pub fn new(allocator: FreeingBumpHeapAllocator, instance: Rc) -> Self { - HostState { - sandbox_store: Rc::new(RefCell::new(sandbox::Store::new( - sandbox::SandboxBackend::TryWasmer, - ))), - allocator: RefCell::new(allocator), - instance, - } + pub fn new(allocator: FreeingBumpHeapAllocator) -> Self { + HostState { allocator, panic_message: None } + } + + /// Takes the error message out of the host state, leaving a `None` in its place. + pub fn take_panic_message(&mut self) -> Option { + self.panic_message.take() } - /// Materialize `HostContext` that can be used to invoke a substrate host `dyn Function`. - pub fn materialize<'a>(&'a self) -> HostContext<'a> { - HostContext(self) + pub(crate) fn allocation_stats(&self) -> AllocationStats { + self.allocator.stats() } } /// A `HostContext` implements `FunctionContext` for making host calls from a Wasmtime /// runtime. The `HostContext` exists only for the lifetime of the call and borrows state from /// a longer-living `HostState`. -pub struct HostContext<'a>(&'a HostState); +pub(crate) struct HostContext<'a> { + pub(crate) caller: Caller<'a, StoreData>, +} -impl<'a> std::ops::Deref for HostContext<'a> { - type Target = HostState; - fn deref(&self) -> &HostState { - self.0 +impl<'a> HostContext<'a> { + fn host_state_mut(&mut self) -> &mut HostState { + self.caller + .data_mut() + .host_state_mut() + .expect("host state is not empty when calling a function in wasm; qed") } } @@ -87,256 +72,34 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { address: Pointer, dest: &mut [u8], ) -> sp_wasm_interface::Result<()> { - self.instance.read_memory_into(address, dest).map_err(|e| e.to_string()) + util::read_memory_into(&self.caller, address, dest).map_err(|e| e.to_string()) } fn write_memory(&mut self, address: Pointer, data: &[u8]) -> sp_wasm_interface::Result<()> { - self.instance.write_memory_from(address, data).map_err(|e| e.to_string()) + util::write_memory_from(&mut self.caller, address, data).map_err(|e| e.to_string()) } fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { - self.instance - .allocate(&mut *self.allocator.borrow_mut(), size) + let memory = self.caller.data().memory(); + let (memory, data) = memory.data_and_store_mut(&mut self.caller); + data.host_state_mut() + .expect("host state is not empty when calling a function in wasm; qed") + .allocator + .allocate(memory, size) .map_err(|e| e.to_string()) } fn deallocate_memory(&mut self, ptr: Pointer) -> sp_wasm_interface::Result<()> { - self.instance - .deallocate(&mut *self.allocator.borrow_mut(), ptr) + let memory = self.caller.data().memory(); + let (memory, data) = memory.data_and_store_mut(&mut self.caller); + data.host_state_mut() + .expect("host state is not empty when calling a function in wasm; qed") + .allocator + .deallocate(memory, ptr) .map_err(|e| e.to_string()) } - fn sandbox(&mut self) -> &mut dyn Sandbox { - self - } -} - -impl<'a> Sandbox for HostContext<'a> { - fn memory_get( - &mut self, - memory_id: MemoryId, - offset: WordSize, - buf_ptr: Pointer, - buf_len: WordSize, - ) -> sp_wasm_interface::Result { - let sandboxed_memory = - self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - - let len = buf_len as usize; - - let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { - Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if let Err(_) = self.instance.write_memory_from(buf_ptr, &buffer) { - return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_primitives::ERR_OK) - } - - fn memory_set( - &mut self, - memory_id: MemoryId, - offset: WordSize, - val_ptr: Pointer, - val_len: WordSize, - ) -> sp_wasm_interface::Result { - let sandboxed_memory = - self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - - let len = val_len as usize; - - let buffer = match self.instance.read_memory(val_ptr, len) { - Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if let Err(_) = sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer) { - return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_primitives::ERR_OK) - } - - fn memory_teardown(&mut self, memory_id: MemoryId) -> sp_wasm_interface::Result<()> { - self.sandbox_store - .borrow_mut() - .memory_teardown(memory_id) - .map_err(|e| e.to_string()) - } - - fn memory_new(&mut self, initial: u32, maximum: u32) -> sp_wasm_interface::Result { - self.sandbox_store - .borrow_mut() - .new_memory(initial, maximum) - .map_err(|e| e.to_string()) - } - - fn invoke( - &mut self, - instance_id: u32, - export_name: &str, - args: &[u8], - return_val: Pointer, - return_val_len: u32, - state: u32, - ) -> sp_wasm_interface::Result { - trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); - - // Deserialize arguments and convert them into wasmi types. - let args = Vec::::decode(&mut &args[..]) - .map_err(|_| "Can't decode serialized arguments for the invocation")? - .into_iter() - .map(Into::into) - .collect::>(); - - let instance = - self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?; - - let dispatch_thunk = self - .sandbox_store - .borrow() - .dispatch_thunk(instance_id) - .map_err(|e| e.to_string())?; - - let result = instance.invoke( - export_name, - &args, - state, - &mut SandboxContext { host_context: self, dispatch_thunk }, - ); - - match result { - Ok(None) => Ok(sandbox_primitives::ERR_OK), - Ok(Some(val)) => { - // Serialize return value and write it back into the memory. - sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { - if val.len() > return_val_len as usize { - Err("Return value buffer is too small")?; - } - ::write_memory(self, return_val, val) - .map_err(|_| "can't write return value")?; - Ok(sandbox_primitives::ERR_OK) - }) - }, - Err(_) => Ok(sandbox_primitives::ERR_EXECUTION), - } - } - - fn instance_teardown(&mut self, instance_id: u32) -> sp_wasm_interface::Result<()> { - self.sandbox_store - .borrow_mut() - .instance_teardown(instance_id) - .map_err(|e| e.to_string()) - } - - fn instance_new( - &mut self, - dispatch_thunk_id: u32, - wasm: &[u8], - raw_env_def: &[u8], - state: u32, - ) -> sp_wasm_interface::Result { - // Extract a dispatch thunk from the instance's table by the specified index. - let dispatch_thunk = { - let table_item = self - .instance - .table() - .as_ref() - .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")? - .get(dispatch_thunk_id); - - table_item - .ok_or_else(|| "dispatch_thunk_id is out of bounds")? - .funcref() - .ok_or_else(|| "dispatch_thunk_idx should be a funcref")? - .ok_or_else(|| "dispatch_thunk_idx should point to actual func")? - .clone() - }; - - let guest_env = - match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) { - Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), - }; - - let store = self.sandbox_store.clone(); - let store = &mut store.borrow_mut(); - let result = store - .instantiate( - wasm, - guest_env, - state, - &mut SandboxContext { host_context: self, dispatch_thunk: dispatch_thunk.clone() }, - ) - .map(|i| i.register(store, dispatch_thunk)); - - let instance_idx_or_err_code = match result { - Ok(instance_idx) => instance_idx, - Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, - Err(_) => sandbox_primitives::ERR_MODULE, - }; - - Ok(instance_idx_or_err_code as u32) - } - - fn get_global_val( - &self, - instance_idx: u32, - name: &str, - ) -> sp_wasm_interface::Result> { - self.sandbox_store - .borrow() - .instance(instance_idx) - .map(|i| i.get_global_val(name)) - .map_err(|e| e.to_string()) - } -} - -struct SandboxContext<'a, 'b> { - host_context: &'a mut HostContext<'b>, - dispatch_thunk: Func, -} - -impl<'a, 'b> sandbox::SandboxContext for SandboxContext<'a, 'b> { - fn invoke( - &mut self, - invoke_args_ptr: Pointer, - invoke_args_len: WordSize, - state: u32, - func_idx: SupervisorFuncIndex, - ) -> Result { - let result = self.dispatch_thunk.call(&[ - Val::I32(u32::from(invoke_args_ptr) as i32), - Val::I32(invoke_args_len as i32), - Val::I32(state as i32), - Val::I32(usize::from(func_idx) as i32), - ]); - match result { - Ok(ret_vals) => { - let ret_val = if ret_vals.len() != 1 { - return Err(format!( - "Supervisor function returned {} results, expected 1", - ret_vals.len() - ) - .into()) - } else { - &ret_vals[0] - }; - - if let Some(ret_val) = ret_val.i64() { - Ok(ret_val) - } else { - return Err("Supervisor function returned unexpected result!".into()) - } - }, - Err(err) => Err(err.to_string().into()), - } - } - - fn supervisor_context(&mut self) -> &mut dyn FunctionContext { - self.host_context + fn register_panic_error_message(&mut self, message: &str) { + self.host_state_mut().panic_message = Some(message.to_owned()); } } diff --git a/client/executor/wasmtime/src/imports.rs b/client/executor/wasmtime/src/imports.rs index b27fb944bc030..c80952a2541ce 100644 --- a/client/executor/wasmtime/src/imports.rs +++ b/client/executor/wasmtime/src/imports.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,35 +16,25 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{state_holder, util}; +use crate::{host::HostContext, runtime::StoreData}; use sc_executor_common::error::WasmError; -use sp_wasm_interface::{Function, ValueType}; -use std::any::Any; -use wasmtime::{ - Extern, ExternType, Func, FuncType, ImportType, Limits, Memory, MemoryType, Module, Store, - Trap, Val, -}; - -pub struct Imports { - /// Contains the index into `externs` where the memory import is stored if any. `None` if there - /// is none. - pub memory_import_index: Option, - pub externs: Vec, -} - -/// Goes over all imports of a module and prepares a vector of `Extern`s that can be used for -/// instantiation of the module. Returns an error if there are imports that cannot be satisfied. -pub fn resolve_imports( - store: &Store, +use sp_wasm_interface::{FunctionContext, HostFunctions}; +use std::collections::HashMap; +use wasmtime::{ExternType, FuncType, ImportType, Linker, Module, Trap}; + +/// Goes over all imports of a module and prepares the given linker for instantiation of the module. +/// Returns an error if there are imports that cannot be satisfied. +pub(crate) fn prepare_imports( + linker: &mut Linker, module: &Module, - host_functions: &[&'static dyn Function], - heap_pages: u32, allow_missing_func_imports: bool, -) -> Result { - let mut externs = vec![]; - let mut memory_import_index = None; +) -> Result<(), WasmError> +where + H: HostFunctions, +{ + let mut pending_func_imports = HashMap::new(); for import_ty in module.imports() { - let name = import_name(&import_ty)?; + let name = import_ty.name(); if import_ty.module() != "env" { return Err(WasmError::Other(format!( @@ -54,230 +44,80 @@ pub fn resolve_imports( ))) } - let resolved = match name { - "memory" => { - memory_import_index = Some(externs.len()); - resolve_memory_import(store, &import_ty, heap_pages)? + match import_ty.ty() { + ExternType::Func(func_ty) => { + pending_func_imports.insert(name.to_owned(), (import_ty, func_ty)); }, _ => - resolve_func_import(store, &import_ty, host_functions, allow_missing_func_imports)?, + return Err(WasmError::Other(format!( + "host doesn't provide any non function imports: {}:{}", + import_ty.module(), + name, + ))), }; - externs.push(resolved); } - Ok(Imports { memory_import_index, externs }) -} - -/// When the module linking proposal is supported the import's name can be `None`. -/// Because we are not using this proposal we could safely unwrap the name. -/// However, we opt for an error in order to avoid panics at all costs. -fn import_name<'a, 'b: 'a>(import: &'a ImportType<'b>) -> Result<&'a str, WasmError> { - let name = import.name().ok_or_else(|| { - WasmError::Other("The module linking proposal is not supported.".to_owned()) - })?; - Ok(name) -} -fn resolve_memory_import( - store: &Store, - import_ty: &ImportType, - heap_pages: u32, -) -> Result { - let requested_memory_ty = match import_ty.ty() { - ExternType::Memory(memory_ty) => memory_ty, - _ => + let mut registry = Registry { linker, pending_func_imports }; + H::register_static(&mut registry)?; + + if !registry.pending_func_imports.is_empty() { + if allow_missing_func_imports { + for (name, (import_ty, func_ty)) in registry.pending_func_imports { + let error = format!("call to a missing function {}:{}", import_ty.module(), name); + log::debug!("Missing import: '{}' {:?}", name, func_ty); + linker + .func_new("env", &name, func_ty.clone(), move |_, _, _| { + Err(Trap::new(error.clone())) + }) + .expect("adding a missing import stub can only fail when the item already exists, and it is missing here; qed"); + } + } else { + let mut names = Vec::new(); + for (name, (import_ty, _)) in registry.pending_func_imports { + names.push(format!("'{}:{}'", import_ty.module(), name)); + } + let names = names.join(", "); return Err(WasmError::Other(format!( - "this import must be of memory type: {}:{}", - import_ty.module(), - import_name(&import_ty)?, - ))), - }; - - // Increment the min (a.k.a initial) number of pages by `heap_pages` and check if it exceeds the - // maximum specified by the import. - let initial = requested_memory_ty.limits().min().saturating_add(heap_pages); - if let Some(max) = requested_memory_ty.limits().max() { - if initial > max { - return Err(WasmError::Other(format!( - "incremented number of pages by heap_pages (total={}) is more than maximum requested\ - by the runtime wasm module {}", - initial, - max, + "runtime requires function imports which are not present on the host: {}", + names ))) } } - let memory_ty = MemoryType::new(Limits::new(initial, requested_memory_ty.limits().max())); - let memory = Memory::new(store, memory_ty).map_err(|e| { - WasmError::Other(format!( - "failed to create a memory during resolving of memory import: {}", - e, - )) - })?; - Ok(Extern::Memory(memory)) -} - -fn resolve_func_import( - store: &Store, - import_ty: &ImportType, - host_functions: &[&'static dyn Function], - allow_missing_func_imports: bool, -) -> Result { - let name = import_name(&import_ty)?; - - let func_ty = match import_ty.ty() { - ExternType::Func(func_ty) => func_ty, - _ => - return Err(WasmError::Other(format!( - "host doesn't provide any non function imports besides 'memory': {}:{}", - import_ty.module(), - name, - ))), - }; - - let host_func = match host_functions.iter().find(|host_func| host_func.name() == name) { - Some(host_func) => host_func, - None if allow_missing_func_imports => - return Ok(MissingHostFuncHandler::new(import_ty)?.into_extern(store, &func_ty)), - None => - return Err(WasmError::Other(format!( - "host doesn't provide such function: {}:{}", - import_ty.module(), - name, - ))), - }; - if &func_ty != &wasmtime_func_sig(*host_func) { - return Err(WasmError::Other(format!( - "signature mismatch for: {}:{}", - import_ty.module(), - name, - ))) - } - - Ok(HostFuncHandler::new(*host_func).into_extern(store)) + Ok(()) } -/// This structure implements `Callable` and acts as a bridge between wasmtime and -/// substrate host functions. -struct HostFuncHandler { - host_func: &'static dyn Function, +struct Registry<'a, 'b> { + linker: &'a mut Linker, + pending_func_imports: HashMap, FuncType)>, } -fn call_static( - static_func: &'static dyn Function, - wasmtime_params: &[Val], - wasmtime_results: &mut [Val], -) -> Result<(), wasmtime::Trap> { - let unwind_result = state_holder::with_context(|host_ctx| { - let mut host_ctx = host_ctx.expect( - "host functions can be called only from wasm instance; - wasm instance is always called initializing context; - therefore host_ctx cannot be None; - qed - ", - ); - // `from_wasmtime_val` panics if it encounters a value that doesn't fit into the values - // available in substrate. - // - // This, however, cannot happen since the signature of this function is created from - // a `dyn Function` signature of which cannot have a non substrate value by definition. - let mut params = wasmtime_params.iter().cloned().map(util::from_wasmtime_val); +impl<'a, 'b> sp_wasm_interface::HostFunctionRegistry for Registry<'a, 'b> { + type State = StoreData; + type Error = WasmError; + type FunctionContext = HostContext<'a>; - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - static_func.execute(&mut host_ctx, &mut params) - })) - }); - - let execution_result = match unwind_result { - Ok(execution_result) => execution_result, - Err(err) => return Err(Trap::new(stringify_panic_payload(err))), - }; - - match execution_result { - Ok(Some(ret_val)) => { - debug_assert!( - wasmtime_results.len() == 1, - "wasmtime function signature, therefore the number of results, should always \ - correspond to the number of results returned by the host function", - ); - wasmtime_results[0] = util::into_wasmtime_val(ret_val); - Ok(()) - }, - Ok(None) => { - debug_assert!( - wasmtime_results.len() == 0, - "wasmtime function signature, therefore the number of results, should always \ - correspond to the number of results returned by the host function", - ); - Ok(()) - }, - Err(msg) => Err(Trap::new(msg)), - } -} - -impl HostFuncHandler { - fn new(host_func: &'static dyn Function) -> Self { - Self { host_func } - } - - fn into_extern(self, store: &Store) -> Extern { - let host_func = self.host_func; - let func_ty = wasmtime_func_sig(self.host_func); - let func = Func::new(store, func_ty, move |_, params, result| { - call_static(host_func, params, result) - }); - Extern::Func(func) - } -} - -/// A `Callable` handler for missing functions. -struct MissingHostFuncHandler { - module: String, - name: String, -} - -impl MissingHostFuncHandler { - fn new(import_ty: &ImportType) -> Result { - Ok(Self { - module: import_ty.module().to_string(), - name: import_name(import_ty)?.to_string(), - }) - } - - fn into_extern(self, store: &Store, func_ty: &FuncType) -> Extern { - let Self { module, name } = self; - let func = Func::new(store, func_ty.clone(), move |_, _, _| { - Err(Trap::new(format!("call to a missing function {}:{}", module, name))) - }); - Extern::Func(func) + fn with_function_context( + caller: wasmtime::Caller, + callback: impl FnOnce(&mut dyn FunctionContext) -> R, + ) -> R { + callback(&mut HostContext { caller }) } -} - -fn wasmtime_func_sig(func: &dyn Function) -> wasmtime::FuncType { - let signature = func.signature(); - let params = signature.args.iter().cloned().map(into_wasmtime_val_type); - - let results = signature.return_value.iter().cloned().map(into_wasmtime_val_type); - wasmtime::FuncType::new(params, results) -} - -fn into_wasmtime_val_type(val_ty: ValueType) -> wasmtime::ValType { - match val_ty { - ValueType::I32 => wasmtime::ValType::I32, - ValueType::I64 => wasmtime::ValType::I64, - ValueType::F32 => wasmtime::ValType::F32, - ValueType::F64 => wasmtime::ValType::F64, - } -} + fn register_static( + &mut self, + fn_name: &str, + func: impl wasmtime::IntoFunc, + ) -> Result<(), Self::Error> { + if self.pending_func_imports.remove(fn_name).is_some() { + self.linker.func_wrap("env", fn_name, func).map_err(|error| { + WasmError::Other(format!( + "failed to register host function '{}' with the WASM linker: {:#}", + fn_name, error + )) + })?; + } -/// Attempt to convert a opaque panic payload to a string. -fn stringify_panic_payload(payload: Box) -> String { - match payload.downcast::<&'static str>() { - Ok(msg) => msg.to_string(), - Err(payload) => match payload.downcast::() { - Ok(msg) => *msg, - // At least we tried... - Err(_) => "Box".to_string(), - }, + Ok(()) } } diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index f66d62f673d90..feded4008068d 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,20 +19,16 @@ //! Defines data and logic needed for interaction with an WebAssembly instance of a substrate //! runtime module. -use crate::{ - imports::Imports, - util::{from_wasmtime_val, into_wasmtime_val}, -}; - +use crate::runtime::{Store, StoreData}; use sc_executor_common::{ - error::{Error, Result}, - runtime_blob, - util::checked_range, + error::{Backtrace, Error, MessageWithBacktrace, Result, WasmError}, wasm_runtime::InvokeMethod, }; use sp_wasm_interface::{Pointer, Value, WordSize}; -use std::{marker, slice}; -use wasmtime::{Extern, Func, Global, Instance, Memory, Module, Store, Table, Val}; +use wasmtime::{ + AsContext, AsContextMut, Engine, Extern, Func, Global, Instance, InstancePre, Memory, Table, + Val, +}; /// Invoked entrypoint format. pub enum EntryPointType { @@ -58,38 +54,71 @@ pub struct EntryPoint { impl EntryPoint { /// Call this entry point. - pub fn call(&self, data_ptr: Pointer, data_len: WordSize) -> Result { + pub(crate) fn call( + &self, + store: &mut Store, + data_ptr: Pointer, + data_len: WordSize, + ) -> Result { let data_ptr = u32::from(data_ptr); let data_len = u32::from(data_len); - fn handle_trap(err: wasmtime::Trap) -> Error { - Error::from(format!("Wasm execution trapped: {}", err)) - } - match self.call_type { EntryPointType::Direct { ref entrypoint } => - entrypoint.call((data_ptr, data_len)).map_err(handle_trap), + entrypoint.call(&mut *store, (data_ptr, data_len)), EntryPointType::Wrapped { func, ref dispatcher } => - dispatcher.call((func, data_ptr, data_len)).map_err(handle_trap), + dispatcher.call(&mut *store, (func, data_ptr, data_len)), } + .map_err(|trap| { + let host_state = store + .data_mut() + .host_state + .as_mut() + .expect("host state cannot be empty while a function is being called; qed"); + + // The logic to print out a backtrace is somewhat complicated, + // so let's get wasmtime to print it out for us. + let mut backtrace_string = trap.to_string(); + let suffix = "\nwasm backtrace:"; + if let Some(index) = backtrace_string.find(suffix) { + // Get rid of the error message and just grab the backtrace, + // since we're storing the error message ourselves separately. + backtrace_string.replace_range(0..index + suffix.len(), ""); + } + + let backtrace = Backtrace { backtrace_string }; + if let Some(error) = host_state.take_panic_message() { + Error::AbortedDueToPanic(MessageWithBacktrace { + message: error, + backtrace: Some(backtrace), + }) + } else { + Error::AbortedDueToTrap(MessageWithBacktrace { + message: trap.display_reason().to_string(), + backtrace: Some(backtrace), + }) + } + }) } - pub fn direct(func: wasmtime::Func) -> std::result::Result { + pub fn direct( + func: wasmtime::Func, + ctx: impl AsContext, + ) -> std::result::Result { let entrypoint = func - .typed::<(u32, u32), u64>() - .map_err(|_| "Invalid signature for direct entry point")? - .clone(); + .typed::<(u32, u32), u64, _>(ctx) + .map_err(|_| "Invalid signature for direct entry point")?; Ok(Self { call_type: EntryPointType::Direct { entrypoint } }) } pub fn wrapped( dispatcher: wasmtime::Func, func: u32, + ctx: impl AsContext, ) -> std::result::Result { let dispatcher = dispatcher - .typed::<(u32, u32, u32), u64>() - .map_err(|_| "Invalid signature for wrapped entry point")? - .clone(); + .typed::<(u32, u32, u32), u64, _>(ctx) + .map_err(|_| "Invalid signature for wrapped entry point")?; Ok(Self { call_type: EntryPointType::Wrapped { func, dispatcher } }) } } @@ -100,18 +129,8 @@ impl EntryPoint { /// routines. pub struct InstanceWrapper { instance: Instance, - - // The memory instance of the `instance`. - // - // It is important to make sure that we don't make any copies of this to make it easier to - // proof See `memory_as_slice` and `memory_as_slice_mut`. memory: Memory, - - /// Indirect functions table of the module - table: Option, - - // Make this struct explicitly !Send & !Sync. - _not_send_nor_sync: marker::PhantomData<*const ()>, + store: Store, } fn extern_memory(extern_: &Extern) -> Option<&Memory> { @@ -142,62 +161,76 @@ fn extern_func(extern_: &Extern) -> Option<&Func> { } } -impl InstanceWrapper { - /// Create a new instance wrapper from the given wasm module. - pub fn new(store: &Store, module: &Module, imports: &Imports, heap_pages: u32) -> Result { - let instance = Instance::new(store, module, &imports.externs) - .map_err(|e| Error::from(format!("cannot instantiate: {}", e)))?; - - let memory = match imports.memory_import_index { - Some(memory_idx) => extern_memory(&imports.externs[memory_idx]) - .expect("only memory can be at the `memory_idx`; qed") - .clone(), - None => { - let memory = get_linear_memory(&instance)?; - if !memory.grow(heap_pages).is_ok() { - return Err("failed to increase the linear memory size".into()) - } - memory - }, - }; +pub(crate) fn create_store(engine: &wasmtime::Engine, max_memory_size: Option) -> Store { + let limits = if let Some(max_memory_size) = max_memory_size { + wasmtime::StoreLimitsBuilder::new().memory_size(max_memory_size).build() + } else { + Default::default() + }; + + let mut store = + Store::new(engine, StoreData { limits, host_state: None, memory: None, table: None }); + if max_memory_size.is_some() { + store.limiter(|s| &mut s.limits); + } + store +} - Ok(Self { - table: get_table(&instance), - instance, - memory, - _not_send_nor_sync: marker::PhantomData, - }) +impl InstanceWrapper { + pub(crate) fn new( + engine: &Engine, + instance_pre: &InstancePre, + max_memory_size: Option, + ) -> Result { + let mut store = create_store(engine, max_memory_size); + let instance = instance_pre.instantiate(&mut store).map_err(|error| { + WasmError::Other(format!( + "failed to instantiate a new WASM module instance: {:#}", + error, + )) + })?; + + let memory = get_linear_memory(&instance, &mut store)?; + let table = get_table(&instance, &mut store); + + store.data_mut().memory = Some(memory); + store.data_mut().table = table; + + Ok(InstanceWrapper { instance, memory, store }) } /// Resolves a substrate entrypoint by the given name. /// /// An entrypoint must have a signature `(i32, i32) -> i64`, otherwise this function will return /// an error. - pub fn resolve_entrypoint(&self, method: InvokeMethod) -> Result { + pub fn resolve_entrypoint(&mut self, method: InvokeMethod) -> Result { Ok(match method { InvokeMethod::Export(method) => { // Resolve the requested method and verify that it has a proper signature. - let export = self.instance.get_export(method).ok_or_else(|| { - Error::from(format!("Exported method {} is not found", method)) - })?; + let export = + self.instance.get_export(&mut self.store, method).ok_or_else(|| { + Error::from(format!("Exported method {} is not found", method)) + })?; let func = extern_func(&export) - .ok_or_else(|| Error::from(format!("Export {} is not a function", method)))? - .clone(); - EntryPoint::direct(func).map_err(|_| { + .ok_or_else(|| Error::from(format!("Export {} is not a function", method)))?; + EntryPoint::direct(*func, &self.store).map_err(|_| { Error::from(format!("Exported function '{}' has invalid signature.", method)) })? }, InvokeMethod::Table(func_ref) => { - let table = - self.instance.get_table("__indirect_function_table").ok_or(Error::NoTable)?; - let val = table.get(func_ref).ok_or(Error::NoTableEntryWithIndex(func_ref))?; + let table = self + .instance + .get_table(&mut self.store, "__indirect_function_table") + .ok_or(Error::NoTable)?; + let val = table + .get(&mut self.store, func_ref) + .ok_or(Error::NoTableEntryWithIndex(func_ref))?; let func = val .funcref() .ok_or(Error::TableElementIsNotAFunction(func_ref))? - .ok_or(Error::FunctionRefIsNull(func_ref))? - .clone(); + .ok_or(Error::FunctionRefIsNull(func_ref))?; - EntryPoint::direct(func).map_err(|_| { + EntryPoint::direct(*func, &self.store).map_err(|_| { Error::from(format!( "Function @{} in exported table has invalid signature for direct call.", func_ref, @@ -205,18 +238,19 @@ impl InstanceWrapper { })? }, InvokeMethod::TableWithWrapper { dispatcher_ref, func } => { - let table = - self.instance.get_table("__indirect_function_table").ok_or(Error::NoTable)?; + let table = self + .instance + .get_table(&mut self.store, "__indirect_function_table") + .ok_or(Error::NoTable)?; let val = table - .get(dispatcher_ref) + .get(&mut self.store, dispatcher_ref) .ok_or(Error::NoTableEntryWithIndex(dispatcher_ref))?; let dispatcher = val .funcref() .ok_or(Error::TableElementIsNotAFunction(dispatcher_ref))? - .ok_or(Error::FunctionRefIsNull(dispatcher_ref))? - .clone(); + .ok_or(Error::FunctionRefIsNull(dispatcher_ref))?; - EntryPoint::wrapped(dispatcher, func).map_err(|_| { + EntryPoint::wrapped(*dispatcher, func, &self.store).map_err(|_| { Error::from(format!( "Function @{} in exported table has invalid signature for wrapped call.", dispatcher_ref, @@ -226,25 +260,20 @@ impl InstanceWrapper { }) } - /// Returns an indirect function table of this instance. - pub fn table(&self) -> Option<&Table> { - self.table.as_ref() - } - /// Reads `__heap_base: i32` global variable and returns it. /// /// If it doesn't exist, not a global or of not i32 type returns an error. - pub fn extract_heap_base(&self) -> Result { + pub fn extract_heap_base(&mut self) -> Result { let heap_base_export = self .instance - .get_export("__heap_base") + .get_export(&mut self.store, "__heap_base") .ok_or_else(|| Error::from("__heap_base is not found"))?; let heap_base_global = extern_global(&heap_base_export) .ok_or_else(|| Error::from("__heap_base is not a global"))?; let heap_base = heap_base_global - .get() + .get(&mut self.store) .i32() .ok_or_else(|| Error::from("__heap_base is not a i32"))?; @@ -252,15 +281,15 @@ impl InstanceWrapper { } /// Get the value from a global with the given `name`. - pub fn get_global_val(&self, name: &str) -> Result> { - let global = match self.instance.get_export(name) { + pub fn get_global_val(&mut self, name: &str) -> Result> { + let global = match self.instance.get_export(&mut self.store, name) { Some(global) => global, None => return Ok(None), }; let global = extern_global(&global).ok_or_else(|| format!("`{}` is not a global", name))?; - match global.get() { + match global.get(&mut self.store) { Val::I32(val) => Ok(Some(Value::I32(val))), Val::I64(val) => Ok(Some(Value::I64(val))), Val::F32(val) => Ok(Some(Value::F32(val))), @@ -268,25 +297,29 @@ impl InstanceWrapper { _ => Err("Unknown value type".into()), } } + + /// Get a global with the given `name`. + pub fn get_global(&mut self, name: &str) -> Option { + self.instance.get_global(&mut self.store, name) + } } /// Extract linear memory instance from the given instance. -fn get_linear_memory(instance: &Instance) -> Result { +fn get_linear_memory(instance: &Instance, ctx: impl AsContextMut) -> Result { let memory_export = instance - .get_export("memory") + .get_export(ctx, "memory") .ok_or_else(|| Error::from("memory is not exported under `memory` name"))?; - let memory = extern_memory(&memory_export) - .ok_or_else(|| Error::from("the `memory` export should have memory type"))? - .clone(); + let memory = *extern_memory(&memory_export) + .ok_or_else(|| Error::from("the `memory` export should have memory type"))?; Ok(memory) } /// Extract the table from the given instance if any. -fn get_table(instance: &Instance) -> Option
{ +fn get_table(instance: &Instance, ctx: &mut Store) -> Option
{ instance - .get_export("__indirect_function_table") + .get_export(ctx, "__indirect_function_table") .as_ref() .and_then(extern_table) .cloned() @@ -294,132 +327,16 @@ fn get_table(instance: &Instance) -> Option
{ /// Functions related to memory. impl InstanceWrapper { - /// Read data from a slice of memory into a newly allocated buffer. - /// - /// Returns an error if the read would go out of the memory bounds. - pub fn read_memory(&self, source_addr: Pointer, size: usize) -> Result> { - let range = checked_range(source_addr.into(), size, self.memory.data_size()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - let mut buffer = vec![0; range.len()]; - self.read_memory_into(source_addr, &mut buffer)?; - - Ok(buffer) - } - - /// Read data from the instance memory into a slice. - /// - /// Returns an error if the read would go out of the memory bounds. - pub fn read_memory_into(&self, source_addr: Pointer, dest: &mut [u8]) -> Result<()> { - unsafe { - // This should be safe since we don't grow up memory while caching this reference and - // we give up the reference before returning from this function. - let memory = self.memory_as_slice(); - - let range = checked_range(source_addr.into(), dest.len(), memory.len()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - dest.copy_from_slice(&memory[range]); - Ok(()) - } - } - - /// Write data to the instance memory from a slice. - /// - /// Returns an error if the write would go out of the memory bounds. - pub fn write_memory_from(&self, dest_addr: Pointer, data: &[u8]) -> Result<()> { - unsafe { - // This should be safe since we don't grow up memory while caching this reference and - // we give up the reference before returning from this function. - let memory = self.memory_as_slice_mut(); - - let range = checked_range(dest_addr.into(), data.len(), memory.len()) - .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; - memory[range].copy_from_slice(data); - Ok(()) - } - } - - /// Allocate some memory of the given size. Returns pointer to the allocated memory region. - /// - /// Returns `Err` in case memory cannot be allocated. Refer to the allocator documentation - /// to get more details. - pub fn allocate( - &self, - allocator: &mut sc_allocator::FreeingBumpHeapAllocator, - size: WordSize, - ) -> Result> { - unsafe { - // This should be safe since we don't grow up memory while caching this reference and - // we give up the reference before returning from this function. - let memory = self.memory_as_slice_mut(); - - allocator.allocate(memory, size).map_err(Into::into) - } - } - - /// Deallocate the memory pointed by the given pointer. - /// - /// Returns `Err` in case the given memory region cannot be deallocated. - pub fn deallocate( - &self, - allocator: &mut sc_allocator::FreeingBumpHeapAllocator, - ptr: Pointer, - ) -> Result<()> { - unsafe { - // This should be safe since we don't grow up memory while caching this reference and - // we give up the reference before returning from this function. - let memory = self.memory_as_slice_mut(); - - allocator.deallocate(memory, ptr).map_err(Into::into) - } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// Wasmtime doesn't provide comprehensive documentation about the exact behavior of the data - /// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since - /// growing, we cannot guarantee the lifetime of the returned slice reference. - unsafe fn memory_as_slice(&self) -> &[u8] { - let ptr = self.memory.data_ptr() as *const _; - let len = self.memory.data_size(); - - if len == 0 { - &[] - } else { - slice::from_raw_parts(ptr, len) - } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is - /// returned it must be ensured that only one mutable and no shared references to memory exists - /// at the same time. - unsafe fn memory_as_slice_mut(&self) -> &mut [u8] { - let ptr = self.memory.data_ptr(); - let len = self.memory.data_size(); - - if len == 0 { - &mut [] - } else { - slice::from_raw_parts_mut(ptr, len) - } - } - /// Returns the pointer to the first byte of the linear memory for this instance. pub fn base_ptr(&self) -> *const u8 { - self.memory.data_ptr() + self.memory.data_ptr(&self.store) } - /// Removes physical backing from the allocated linear memory. This leads to returning the - /// memory back to the system. While the memory is zeroed this is considered as a side-effect - /// and is not relied upon. Thus this function acts as a hint. - pub fn decommit(&self) { - if self.memory.data_size() == 0 { + /// If possible removes physical backing from the allocated linear memory which + /// leads to returning the memory back to the system; this also zeroes the memory + /// as a side-effect. + pub fn decommit(&mut self) { + if self.memory.data_size(&self.store) == 0 { return } @@ -428,8 +345,8 @@ impl InstanceWrapper { use std::sync::Once; unsafe { - let ptr = self.memory.data_ptr(); - let len = self.memory.data_size(); + let ptr = self.memory.data_ptr(&self.store); + let len = self.memory.data_size(&self.store); // Linux handles MADV_DONTNEED reliably. The result is that the given area // is unmapped and will be zeroed on the next pagefault. @@ -441,29 +358,65 @@ impl InstanceWrapper { std::io::Error::last_os_error(), ); }); + } else { + return; + } + } + } else if #[cfg(target_os = "macos")] { + use std::sync::Once; + + unsafe { + let ptr = self.memory.data_ptr(&self.store); + let len = self.memory.data_size(&self.store); + + // On MacOS we can simply overwrite memory mapping. + if libc::mmap( + ptr as _, + len, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ) == libc::MAP_FAILED { + static LOGGED: Once = Once::new(); + LOGGED.call_once(|| { + log::warn!( + "Failed to decommit WASM instance memory through mmap: {}", + std::io::Error::last_os_error(), + ); + }); + } else { + return; } } } } - } -} - -impl runtime_blob::InstanceGlobals for InstanceWrapper { - type Global = wasmtime::Global; - fn get_global(&self, export_name: &str) -> Self::Global { - self.instance - .get_global(export_name) - .expect("get_global is guaranteed to be called with an export name of a global; qed") + // If we're on an unsupported OS or the memory couldn't have been + // decommited for some reason then just manually zero it out. + self.memory.data_mut(self.store.as_context_mut()).fill(0); } - fn get_global_value(&self, global: &Self::Global) -> Value { - from_wasmtime_val(global.get()) + pub(crate) fn store(&self) -> &Store { + &self.store } - fn set_global_value(&self, global: &Self::Global, value: Value) { - global.set(into_wasmtime_val(value)).expect( - "the value is guaranteed to be of the same value; the global is guaranteed to be mutable; qed", - ); + pub(crate) fn store_mut(&mut self) -> &mut Store { + &mut self.store } } + +#[test] +fn decommit_works() { + let engine = wasmtime::Engine::default(); + let code = wat::parse_str("(module (memory (export \"memory\") 1 4))").unwrap(); + let module = wasmtime::Module::new(&engine, code).unwrap(); + let linker = wasmtime::Linker::new(&engine); + let mut store = create_store(&engine, None); + let instance_pre = linker.instantiate_pre(&mut store, &module).unwrap(); + let mut wrapper = InstanceWrapper::new(&engine, &instance_pre, None).unwrap(); + unsafe { *wrapper.memory.data_ptr(&wrapper.store) = 42 }; + assert_eq!(unsafe { *wrapper.memory.data_ptr(&wrapper.store) }, 42); + wrapper.decommit(); + assert_eq!(unsafe { *wrapper.memory.data_ptr(&wrapper.store) }, 0); +} diff --git a/client/executor/wasmtime/src/lib.rs b/client/executor/wasmtime/src/lib.rs index 62b0b205f6de6..4d0e4c37c947e 100644 --- a/client/executor/wasmtime/src/lib.rs +++ b/client/executor/wasmtime/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,12 +16,21 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -/// ! Defines a `WasmRuntime` that uses the Wasmtime JIT to execute. +//! Defines a `WasmRuntime` that uses the Wasmtime JIT to execute. +//! +//! You can choose a profiling strategy at runtime with +//! environment variable `WASMTIME_PROFILING_STRATEGY`: +//! +//! | `WASMTIME_PROFILING_STRATEGY` | Effect | +//! |-------------|-------------------------| +//! | undefined | No profiling | +//! | `"jitdump"` | jitdump profiling | +//! | other value | No profiling (warning) | + mod host; mod imports; mod instance_wrapper; mod runtime; -mod state_holder; mod util; #[cfg(test)] @@ -29,5 +38,5 @@ mod tests; pub use runtime::{ create_runtime, create_runtime_from_artifact, prepare_runtime_artifact, Config, - DeterministicStackLimit, Semantics, + DeterministicStackLimit, InstantiationStrategy, Semantics, }; diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index f6878ec5ee6e1..b124fd627dc69 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,29 +20,58 @@ use crate::{ host::HostState, - imports::{resolve_imports, Imports}, instance_wrapper::{EntryPoint, InstanceWrapper}, - state_holder, + util::{self, replace_strategy_if_broken}, }; -use sc_allocator::FreeingBumpHeapAllocator; +use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; use sc_executor_common::{ error::{Result, WasmError}, - runtime_blob::{DataSegmentsSnapshot, ExposedMutableGlobalsSet, GlobalsSnapshot, RuntimeBlob}, + runtime_blob::{ + self, DataSegmentsSnapshot, ExposedMutableGlobalsSet, GlobalsSnapshot, RuntimeBlob, + }, wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; -use sp_wasm_interface::{Function, Pointer, Value, WordSize}; +use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; use std::{ path::{Path, PathBuf}, - rc::Rc, - sync::Arc, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, }; -use wasmtime::{Engine, Store}; +use wasmtime::{Engine, Memory, StoreLimits, Table}; + +pub(crate) struct StoreData { + /// The limits we apply to the store. We need to store it here to return a reference to this + /// object when we have the limits enabled. + pub(crate) limits: StoreLimits, + /// This will only be set when we call into the runtime. + pub(crate) host_state: Option, + /// This will be always set once the store is initialized. + pub(crate) memory: Option, + /// This will be set only if the runtime actually contains a table. + pub(crate) table: Option
, +} + +impl StoreData { + /// Returns a mutable reference to the host state. + pub fn host_state_mut(&mut self) -> Option<&mut HostState> { + self.host_state.as_mut() + } + + /// Returns the host memory. + pub fn memory(&self) -> Memory { + self.memory.expect("memory is always set; qed") + } +} + +pub(crate) type Store = wasmtime::Store; enum Strategy { - FastInstanceReuse { - instance_wrapper: Rc, + LegacyInstanceReuse { + instance_wrapper: InstanceWrapper, globals_snapshot: GlobalsSnapshot, data_segments_snapshot: Arc, heap_base: u32, @@ -51,15 +80,38 @@ enum Strategy { } struct InstanceCreator { - store: Store, - module: Arc, - imports: Arc, - heap_pages: u32, + engine: wasmtime::Engine, + instance_pre: Arc>, + max_memory_size: Option, } impl InstanceCreator { - fn instantiate(&self) -> Result { - InstanceWrapper::new(&self.store, &*self.module, &*self.imports, self.heap_pages) + fn instantiate(&mut self) -> Result { + InstanceWrapper::new(&self.engine, &self.instance_pre, self.max_memory_size) + } +} + +struct InstanceGlobals<'a> { + instance: &'a mut InstanceWrapper, +} + +impl<'a> runtime_blob::InstanceGlobals for InstanceGlobals<'a> { + type Global = wasmtime::Global; + + fn get_global(&mut self, export_name: &str) -> Self::Global { + self.instance + .get_global(export_name) + .expect("get_global is guaranteed to be called with an export name of a global; qed") + } + + fn get_global_value(&mut self, global: &Self::Global) -> Value { + util::from_wasmtime_val(global.get(&mut self.instance.store_mut())) + } + + fn set_global_value(&mut self, global: &Self::Global, value: Value) { + global.set(&mut self.instance.store_mut(), util::into_wasmtime_val(value)).expect( + "the value is guaranteed to be of the same value; the global is guaranteed to be mutable; qed", + ); } } @@ -72,69 +124,44 @@ struct InstanceSnapshotData { /// A `WasmModule` implementation using wasmtime to compile the runtime module to machine code /// and execute the compiled code. pub struct WasmtimeRuntime { - module: Arc, - snapshot_data: Option, + engine: wasmtime::Engine, + instance_pre: Arc>, + instantiation_strategy: InternalInstantiationStrategy, config: Config, - host_functions: Vec<&'static dyn Function>, - engine: Engine, -} - -impl WasmtimeRuntime { - /// Creates the store respecting the set limits. - fn new_store(&self) -> Store { - match self.config.max_memory_pages { - Some(max_memory_pages) => Store::new_with_limits( - &self.engine, - wasmtime::StoreLimitsBuilder::new().memory_pages(max_memory_pages).build(), - ), - None => Store::new(&self.engine), - } - } } impl WasmModule for WasmtimeRuntime { fn new_instance(&self) -> Result> { - let store = self.new_store(); + let strategy = match self.instantiation_strategy { + InternalInstantiationStrategy::LegacyInstanceReuse(ref snapshot_data) => { + let mut instance_wrapper = InstanceWrapper::new( + &self.engine, + &self.instance_pre, + self.config.semantics.max_memory_size, + )?; + let heap_base = instance_wrapper.extract_heap_base()?; - // Scan all imports, find the matching host functions, and create stubs that adapt arguments - // and results. - // - // NOTE: Attentive reader may notice that this could've been moved in `WasmModule` creation. - // However, I am not sure if that's a good idea since it would be pushing our luck - // further by assuming that `Store` not only `Send` but also `Sync`. - let imports = resolve_imports( - &store, - &self.module, - &self.host_functions, - self.config.heap_pages, - self.config.allow_missing_func_imports, - )?; - - let strategy = if let Some(ref snapshot_data) = self.snapshot_data { - let instance_wrapper = - InstanceWrapper::new(&store, &self.module, &imports, self.config.heap_pages)?; - let heap_base = instance_wrapper.extract_heap_base()?; - - // This function panics if the instance was created from a runtime blob different from - // which the mutable globals were collected. Here, it is easy to see that there is only - // a single runtime blob and thus it's the same that was used for both creating the - // instance and collecting the mutable globals. - let globals_snapshot = - GlobalsSnapshot::take(&snapshot_data.mutable_globals, &instance_wrapper); - - Strategy::FastInstanceReuse { - instance_wrapper: Rc::new(instance_wrapper), - globals_snapshot, - data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(), - heap_base, - } - } else { - Strategy::RecreateInstance(InstanceCreator { - imports: Arc::new(imports), - module: self.module.clone(), - store, - heap_pages: self.config.heap_pages, - }) + // This function panics if the instance was created from a runtime blob different + // from which the mutable globals were collected. Here, it is easy to see that there + // is only a single runtime blob and thus it's the same that was used for both + // creating the instance and collecting the mutable globals. + let globals_snapshot = GlobalsSnapshot::take( + &snapshot_data.mutable_globals, + &mut InstanceGlobals { instance: &mut instance_wrapper }, + ); + + Strategy::LegacyInstanceReuse { + instance_wrapper, + globals_snapshot, + data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(), + heap_base, + } + }, + InternalInstantiationStrategy::Builtin => Strategy::RecreateInstance(InstanceCreator { + engine: self.engine.clone(), + instance_pre: self.instance_pre.clone(), + max_memory_size: self.config.semantics.max_memory_size, + }), }; Ok(Box::new(WasmtimeInstance { strategy })) @@ -147,15 +174,16 @@ pub struct WasmtimeInstance { strategy: Strategy, } -// This is safe because `WasmtimeInstance` does not leak reference to `self.imports` -// and all imports don't reference anything, other than host functions and memory -unsafe impl Send for WasmtimeInstance {} - -impl WasmInstance for WasmtimeInstance { - fn call(&self, method: InvokeMethod, data: &[u8]) -> Result> { - match &self.strategy { - Strategy::FastInstanceReuse { - instance_wrapper, +impl WasmtimeInstance { + fn call_impl( + &mut self, + method: InvokeMethod, + data: &[u8], + allocation_stats: &mut Option, + ) -> Result> { + match &mut self.strategy { + Strategy::LegacyInstanceReuse { + ref mut instance_wrapper, globals_snapshot, data_segments_snapshot, heap_base, @@ -163,13 +191,17 @@ impl WasmInstance for WasmtimeInstance { let entrypoint = instance_wrapper.resolve_entrypoint(method)?; data_segments_snapshot.apply(|offset, contents| { - instance_wrapper.write_memory_from(Pointer::new(offset), contents) + util::write_memory_from( + instance_wrapper.store_mut(), + Pointer::new(offset), + contents, + ) })?; - globals_snapshot.apply(&**instance_wrapper); + globals_snapshot.apply(&mut InstanceGlobals { instance: instance_wrapper }); let allocator = FreeingBumpHeapAllocator::new(*heap_base); let result = - perform_call(data, Rc::clone(&instance_wrapper), entrypoint, allocator); + perform_call(data, instance_wrapper, entrypoint, allocator, allocation_stats); // Signal to the OS that we are done with the linear memory and that it can be // reclaimed. @@ -177,22 +209,34 @@ impl WasmInstance for WasmtimeInstance { result }, - Strategy::RecreateInstance(instance_creator) => { - let instance_wrapper = instance_creator.instantiate()?; + Strategy::RecreateInstance(ref mut instance_creator) => { + let mut instance_wrapper = instance_creator.instantiate()?; let heap_base = instance_wrapper.extract_heap_base()?; let entrypoint = instance_wrapper.resolve_entrypoint(method)?; let allocator = FreeingBumpHeapAllocator::new(heap_base); - perform_call(data, Rc::new(instance_wrapper), entrypoint, allocator) + perform_call(data, &mut instance_wrapper, entrypoint, allocator, allocation_stats) }, } } +} - fn get_global_const(&self, name: &str) -> Result> { - match &self.strategy { - Strategy::FastInstanceReuse { instance_wrapper, .. } => +impl WasmInstance for WasmtimeInstance { + fn call_with_allocation_stats( + &mut self, + method: InvokeMethod, + data: &[u8], + ) -> (Result>, Option) { + let mut allocation_stats = None; + let result = self.call_impl(method, data, &mut allocation_stats); + (result, allocation_stats) + } + + fn get_global_const(&mut self, name: &str) -> Result> { + match &mut self.strategy { + Strategy::LegacyInstanceReuse { instance_wrapper, .. } => instance_wrapper.get_global_val(name), - Strategy::RecreateInstance(instance_creator) => + Strategy::RecreateInstance(ref mut instance_creator) => instance_creator.instantiate()?.get_global_val(name), } } @@ -204,7 +248,7 @@ impl WasmInstance for WasmtimeInstance { // associated with it. None }, - Strategy::FastInstanceReuse { instance_wrapper, .. } => + Strategy::LegacyInstanceReuse { instance_wrapper, .. } => Some(instance_wrapper.base_ptr()), } } @@ -221,12 +265,12 @@ fn setup_wasmtime_caching( let wasmtime_cache_root = cache_path.join("wasmtime"); fs::create_dir_all(&wasmtime_cache_root) - .map_err(|err| format!("cannot create the dirs to cache: {:?}", err))?; + .map_err(|err| format!("cannot create the dirs to cache: {}", err))?; // Canonicalize the path after creating the directories. let wasmtime_cache_root = wasmtime_cache_root .canonicalize() - .map_err(|err| format!("failed to canonicalize the path: {:?}", err))?; + .map_err(|err| format!("failed to canonicalize the path: {}", err))?; // Write the cache config file let cache_config_path = wasmtime_cache_root.join("cache-config.toml"); @@ -239,11 +283,11 @@ directory = \"{cache_dir}\" cache_dir = wasmtime_cache_root.display() ); fs::write(&cache_config_path, config_content) - .map_err(|err| format!("cannot write the cache config: {:?}", err))?; + .map_err(|err| format!("cannot write the cache config: {}", err))?; config .cache_config_load(cache_config_path) - .map_err(|err| format!("failed to parse the config: {:?}", err))?; + .map_err(|err| format!("failed to parse the config: {:#}", err))?; Ok(()) } @@ -253,13 +297,34 @@ fn common_config(semantics: &Semantics) -> std::result::Result wasmtime::ProfilingStrategy::JitDump, + None => wasmtime::ProfilingStrategy::None, + Some(_) => { + // Remember if we have already logged a warning due to an unknown profiling strategy. + static UNKNOWN_PROFILING_STRATEGY: AtomicBool = AtomicBool::new(false); + // Make sure that the warning will not be relogged regularly. + if !UNKNOWN_PROFILING_STRATEGY.swap(true, Ordering::Relaxed) { + log::warn!("WASMTIME_PROFILING_STRATEGY is set to unknown value, ignored."); + } + wasmtime::ProfilingStrategy::None + }, + }; + config.profiler(profiler); + + let native_stack_max = match semantics.deterministic_stack_limit { + Some(DeterministicStackLimit { native_stack_max, .. }) => native_stack_max, + + // In `wasmtime` 0.35 the default stack size limit was changed from 1MB to 512KB. + // + // This broke at least one parachain which depended on the original 1MB limit, + // so here we restore it to what it was originally. + None => 1024 * 1024, + }; + + config.max_wasm_stack(native_stack_max as usize); + + config.parallel_compilation(semantics.parallel_compilation); // Be clear and specific about the extensions we support. If an update brings new features // they should be introduced here as well. @@ -268,8 +333,62 @@ fn common_config(semantics: &Semantics) -> std::result::Result (true, true), + InstantiationStrategy::Pooling => (true, false), + InstantiationStrategy::RecreateInstanceCopyOnWrite => (false, true), + InstantiationStrategy::RecreateInstance => (false, false), + InstantiationStrategy::LegacyInstanceReuse => (false, false), + }; + + config.memory_init_cow(use_cow); + config.memory_guaranteed_dense_image_size( + semantics.max_memory_size.map(|max| max as u64).unwrap_or(u64::MAX), + ); + + if use_pooling { + const WASM_PAGE_SIZE: u64 = 65536; + const MAX_WASM_PAGES: u64 = 0x10000; + + let memory_pages = if let Some(max_memory_size) = semantics.max_memory_size { + let max_memory_size = max_memory_size as u64; + let mut pages = max_memory_size / WASM_PAGE_SIZE; + if max_memory_size % WASM_PAGE_SIZE != 0 { + pages += 1; + } + + std::cmp::min(MAX_WASM_PAGES, pages) + } else { + MAX_WASM_PAGES + }; + + config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling { + strategy: wasmtime::PoolingAllocationStrategy::ReuseAffinity, + + // Pooling needs a bunch of hard limits to be set; if we go over + // any of these then the instantiation will fail. + instance_limits: wasmtime::InstanceLimits { + // Current minimum values for kusama (as of 2022-04-14): + // size: 32384 + // table_elements: 1249 + // memory_pages: 2070 + size: 64 * 1024, + table_elements: 3072, + memory_pages, + + // We can only have a single of those. + tables: 1, + memories: 1, + + // This determines how many instances of the module can be + // instantiated in parallel from the same `Module`. + count: 32, + }, + }); + } Ok(config) } @@ -292,13 +411,14 @@ fn common_config(semantics: &Semantics) -> std::result::Result, + pub max_memory_size: Option, +} +#[derive(Clone)] +pub struct Config { /// The WebAssembly standard requires all imports of an instantiated module to be resolved, - /// othewise, the instantiation fails. If this option is set to `true`, then this behavior is + /// otherwise, the instantiation fails. If this option is set to `true`, then this behavior is /// overriden and imports that are requested by the module and not provided by the host /// functions will be resolved using stubs. These stubs will trap upon a call. pub allow_missing_func_imports: bool, @@ -395,66 +548,73 @@ pub struct Config { enum CodeSupplyMode<'a> { /// The runtime is instantiated using the given runtime blob. - Verbatim { - // Rationale to take the `RuntimeBlob` here is so that the client will be able to reuse - // the blob e.g. if they did a prevalidation. If they didn't they can pass a `RuntimeBlob` - // instance and it will be used anyway in most cases, because we are going to do at least - // some instrumentations for both anticipated paths: substrate execution and PVF execution. - // - // Should there raise a need in performing no instrumentation and the client doesn't need - // to do any checks, then we can provide a `Cow` like semantics here: if we need the blob - // and the user got `RuntimeBlob` then extract it, or otherwise create it from the given - // bytecode. - blob: RuntimeBlob, - }, + Fresh(RuntimeBlob), - /// The code is supplied in a form of a compiled artifact. + /// The runtime is instantiated using a precompiled module. /// /// This assumes that the code is already prepared for execution and the same `Config` was /// used. - Artifact { compiled_artifact: &'a [u8] }, + /// + /// We use a `Path` here instead of simply passing a byte slice to allow `wasmtime` to + /// map the runtime's linear memory on supported platforms in a copy-on-write fashion. + Precompiled(&'a Path), } /// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to /// machine code, which can be computationally heavy. -pub fn create_runtime( +/// +/// The `H` generic parameter is used to statically pass a set of host functions which are exposed +/// to the runtime. +pub fn create_runtime( blob: RuntimeBlob, config: Config, - host_functions: Vec<&'static dyn Function>, -) -> std::result::Result { - // SAFETY: this is safe because it doesn't use `CodeSupplyMode::Artifact`. - unsafe { do_create_runtime(CodeSupplyMode::Verbatim { blob }, config, host_functions) } +) -> std::result::Result +where + H: HostFunctions, +{ + // SAFETY: this is safe because it doesn't use `CodeSupplyMode::Precompiled`. + unsafe { do_create_runtime::(CodeSupplyMode::Fresh(blob), config) } } -/// The same as [`create_runtime`] but takes a precompiled artifact, which makes this function -/// considerably faster than [`create_runtime`]. +/// The same as [`create_runtime`] but takes a path to a precompiled artifact, +/// which makes this function considerably faster than [`create_runtime`]. /// /// # Safety /// -/// The caller must ensure that the compiled artifact passed here was produced by -/// [`prepare_runtime_artifact`]. Otherwise, there is a risk of arbitrary code execution with all -/// implications. +/// The caller must ensure that the compiled artifact passed here was: +/// 1) produced by [`prepare_runtime_artifact`], +/// 2) written to the disk as a file, +/// 3) was not modified, +/// 4) will not be modified while any runtime using this artifact is alive, or is being +/// instantiated. /// -/// It is ok though if the `compiled_artifact` was created by code of another version or with +/// Failure to adhere to these requirements might lead to crashes and arbitrary code execution. +/// +/// It is ok though if the compiled artifact was created by code of another version or with /// different configuration flags. In such case the caller will receive an `Err` deterministically. -pub unsafe fn create_runtime_from_artifact( - compiled_artifact: &[u8], +pub unsafe fn create_runtime_from_artifact( + compiled_artifact_path: &Path, config: Config, - host_functions: Vec<&'static dyn Function>, -) -> std::result::Result { - do_create_runtime(CodeSupplyMode::Artifact { compiled_artifact }, config, host_functions) +) -> std::result::Result +where + H: HostFunctions, +{ + do_create_runtime::(CodeSupplyMode::Precompiled(compiled_artifact_path), config) } /// # Safety /// /// This is only unsafe if called with [`CodeSupplyMode::Artifact`]. See /// [`create_runtime_from_artifact`] to get more details. -unsafe fn do_create_runtime( +unsafe fn do_create_runtime( code_supply_mode: CodeSupplyMode<'_>, - config: Config, - host_functions: Vec<&'static dyn Function>, -) -> std::result::Result { - // Create the engine, store and finally the module from the given code. + mut config: Config, +) -> std::result::Result +where + H: HostFunctions, +{ + replace_strategy_if_broken(&mut config.semantics.instantiation_strategy); + let mut wasmtime_config = common_config(&config.semantics)?; if let Some(ref cache_path) = config.cache_path { if let Err(reason) = setup_wasmtime_caching(cache_path, &mut wasmtime_config) { @@ -466,44 +626,76 @@ unsafe fn do_create_runtime( } let engine = Engine::new(&wasmtime_config) - .map_err(|e| WasmError::Other(format!("cannot create the engine for runtime: {}", e)))?; - - let (module, snapshot_data) = match code_supply_mode { - CodeSupplyMode::Verbatim { blob } => { - let blob = instrument(blob, &config.semantics)?; - - if config.semantics.fast_instance_reuse { - let data_segments_snapshot = DataSegmentsSnapshot::take(&blob).map_err(|e| { - WasmError::Other(format!("cannot take data segments snapshot: {}", e)) - })?; - let data_segments_snapshot = Arc::new(data_segments_snapshot); - - let mutable_globals = ExposedMutableGlobalsSet::collect(&blob); - - let module = wasmtime::Module::new(&engine, &blob.serialize()) - .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; - - (module, Some(InstanceSnapshotData { data_segments_snapshot, mutable_globals })) - } else { - let module = wasmtime::Module::new(&engine, &blob.serialize()) - .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; - (module, None) + .map_err(|e| WasmError::Other(format!("cannot create the wasmtime engine: {:#}", e)))?; + + let (module, instantiation_strategy) = match code_supply_mode { + CodeSupplyMode::Fresh(blob) => { + let blob = prepare_blob_for_compilation(blob, &config.semantics)?; + let serialized_blob = blob.clone().serialize(); + + let module = wasmtime::Module::new(&engine, &serialized_blob) + .map_err(|e| WasmError::Other(format!("cannot create module: {:#}", e)))?; + + match config.semantics.instantiation_strategy { + InstantiationStrategy::LegacyInstanceReuse => { + let data_segments_snapshot = + DataSegmentsSnapshot::take(&blob).map_err(|e| { + WasmError::Other(format!("cannot take data segments snapshot: {}", e)) + })?; + let data_segments_snapshot = Arc::new(data_segments_snapshot); + let mutable_globals = ExposedMutableGlobalsSet::collect(&blob); + + ( + module, + InternalInstantiationStrategy::LegacyInstanceReuse(InstanceSnapshotData { + data_segments_snapshot, + mutable_globals, + }), + ) + }, + InstantiationStrategy::Pooling | + InstantiationStrategy::PoolingCopyOnWrite | + InstantiationStrategy::RecreateInstance | + InstantiationStrategy::RecreateInstanceCopyOnWrite => + (module, InternalInstantiationStrategy::Builtin), } }, - CodeSupplyMode::Artifact { compiled_artifact } => { - // SAFETY: The unsafity of `deserialize` is covered by this function. The + CodeSupplyMode::Precompiled(compiled_artifact_path) => { + if let InstantiationStrategy::LegacyInstanceReuse = + config.semantics.instantiation_strategy + { + return Err(WasmError::Other("the legacy instance reuse instantiation strategy is incompatible with precompiled modules".into())); + } + + // SAFETY: The unsafety of `deserialize_file` is covered by this function. The // responsibilities to maintain the invariants are passed to the caller. - let module = wasmtime::Module::deserialize(&engine, compiled_artifact) - .map_err(|e| WasmError::Other(format!("cannot deserialize module: {}", e)))?; + // + // See [`create_runtime_from_artifact`] for more details. + let module = wasmtime::Module::deserialize_file(&engine, compiled_artifact_path) + .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:#}", e)))?; - (module, None) + (module, InternalInstantiationStrategy::Builtin) }, }; - Ok(WasmtimeRuntime { module: Arc::new(module), snapshot_data, config, host_functions, engine }) + let mut linker = wasmtime::Linker::new(&engine); + crate::imports::prepare_imports::(&mut linker, &module, config.allow_missing_func_imports)?; + + let mut store = + crate::instance_wrapper::create_store(module.engine(), config.semantics.max_memory_size); + let instance_pre = linker + .instantiate_pre(&mut store, &module) + .map_err(|e| WasmError::Other(format!("cannot preinstantiate module: {:#}", e)))?; + + Ok(WasmtimeRuntime { + engine, + instance_pre: Arc::new(instance_pre), + instantiation_strategy, + config, + }) } -fn instrument( +fn prepare_blob_for_compilation( mut blob: RuntimeBlob, semantics: &Semantics, ) -> std::result::Result { @@ -511,11 +703,25 @@ fn instrument( blob = blob.inject_stack_depth_metering(logical_max)?; } - // If enabled, this should happen after all other passes that may introduce global variables. - if semantics.fast_instance_reuse { + if let InstantiationStrategy::LegacyInstanceReuse = semantics.instantiation_strategy { + // When this strategy is used this must be called after all other passes which may introduce + // new global variables, otherwise they will not be reset when we call into the runtime + // again. blob.expose_mutable_globals(); } + // We don't actually need the memory to be imported so we can just convert any memory + // import into an export with impunity. This simplifies our code since `wasmtime` will + // now automatically take care of creating the memory for us, and it is also necessary + // to enable `wasmtime`'s instance pooling. (Imported memories are ineligible for pooling.) + blob.convert_memory_import_into_export()?; + blob.add_extra_heap_pages_to_memory_section( + semantics + .extra_heap_pages + .try_into() + .map_err(|e| WasmError::Other(format!("invalid `extra_heap_pages`: {}", e)))?, + )?; + Ok(blob) } @@ -525,42 +731,60 @@ pub fn prepare_runtime_artifact( blob: RuntimeBlob, semantics: &Semantics, ) -> std::result::Result, WasmError> { - let blob = instrument(blob, semantics)?; + let mut semantics = semantics.clone(); + replace_strategy_if_broken(&mut semantics.instantiation_strategy); + + let blob = prepare_blob_for_compilation(blob, &semantics)?; - let engine = Engine::new(&common_config(semantics)?) - .map_err(|e| WasmError::Other(format!("cannot create the engine: {}", e)))?; + let engine = Engine::new(&common_config(&semantics)?) + .map_err(|e| WasmError::Other(format!("cannot create the engine: {:#}", e)))?; engine .precompile_module(&blob.serialize()) - .map_err(|e| WasmError::Other(format!("cannot precompile module: {}", e))) + .map_err(|e| WasmError::Other(format!("cannot precompile module: {:#}", e))) } fn perform_call( data: &[u8], - instance_wrapper: Rc, + instance_wrapper: &mut InstanceWrapper, entrypoint: EntryPoint, mut allocator: FreeingBumpHeapAllocator, + allocation_stats: &mut Option, ) -> Result> { - let (data_ptr, data_len) = inject_input_data(&instance_wrapper, &mut allocator, data)?; + let (data_ptr, data_len) = inject_input_data(instance_wrapper, &mut allocator, data)?; + + let host_state = HostState::new(allocator); + + // Set the host state before calling into wasm. + instance_wrapper.store_mut().data_mut().host_state = Some(host_state); + + let ret = entrypoint + .call(instance_wrapper.store_mut(), data_ptr, data_len) + .map(unpack_ptr_and_len); + + // Reset the host state + let host_state = instance_wrapper.store_mut().data_mut().host_state.take().expect( + "the host state is always set before calling into WASM so it can't be None here; qed", + ); + *allocation_stats = Some(host_state.allocation_stats()); - let host_state = HostState::new(allocator, instance_wrapper.clone()); - let ret = state_holder::with_initialized_state(&host_state, || -> Result<_> { - Ok(unpack_ptr_and_len(entrypoint.call(data_ptr, data_len)?)) - }); let (output_ptr, output_len) = ret?; - let output = extract_output_data(&instance_wrapper, output_ptr, output_len)?; + let output = extract_output_data(instance_wrapper, output_ptr, output_len)?; Ok(output) } fn inject_input_data( - instance: &InstanceWrapper, + instance: &mut InstanceWrapper, allocator: &mut FreeingBumpHeapAllocator, data: &[u8], ) -> Result<(Pointer, WordSize)> { + let mut ctx = instance.store_mut(); + let memory = ctx.data().memory(); + let memory = memory.data_mut(&mut ctx); let data_len = data.len() as WordSize; - let data_ptr = instance.allocate(allocator, data_len)?; - instance.write_memory_from(data_ptr, data)?; + let data_ptr = allocator.allocate(memory, data_len)?; + util::write_memory_from(instance.store_mut(), data_ptr, data)?; Ok((data_ptr, data_len)) } @@ -570,6 +794,6 @@ fn extract_output_data( output_len: u32, ) -> Result> { let mut output = vec![0; output_len as usize]; - instance.read_memory_into(Pointer::new(output_ptr), &mut output)?; + util::read_memory_into(instance.store(), Pointer::new(output_ptr), &mut output)?; Ok(output) } diff --git a/client/executor/wasmtime/src/state_holder.rs b/client/executor/wasmtime/src/state_holder.rs deleted file mode 100644 index 0e2684cd25130..0000000000000 --- a/client/executor/wasmtime/src/state_holder.rs +++ /dev/null @@ -1,45 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use crate::host::{HostContext, HostState}; - -scoped_tls::scoped_thread_local!(static HOST_STATE: HostState); - -/// Provide `HostState` for the runtime method call and execute the given function `f`. -/// -/// During the execution of the provided function `with_context` will be callable. -pub fn with_initialized_state(s: &HostState, f: F) -> R -where - F: FnOnce() -> R, -{ - HOST_STATE.set(s, f) -} - -/// Create a `HostContext` from the contained `HostState` and execute the given function `f`. -/// -/// This function is only callable within closure passed to `init_state`. Otherwise, the passed -/// context will be `None`. -pub fn with_context(f: F) -> R -where - F: FnOnce(Option) -> R, -{ - if !HOST_STATE.is_set() { - return f(None) - } - HOST_STATE.with(|state| f(Some(state.materialize()))) -} diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index 366352d7f5c39..9126cb336bde6 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,58 +17,118 @@ // along with this program. If not, see . use codec::{Decode as _, Encode as _}; -use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; +use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; use sc_runtime_test::wasm_binary_unwrap; -use std::sync::Arc; + +use crate::InstantiationStrategy; type HostFunctions = sp_io::SubstrateHostFunctions; +#[macro_export] +macro_rules! test_wasm_execution { + (@no_legacy_instance_reuse $method_name:ident) => { + paste::item! { + #[test] + fn [<$method_name _recreate_instance_cow>]() { + $method_name( + InstantiationStrategy::RecreateInstanceCopyOnWrite + ); + } + + #[test] + fn [<$method_name _recreate_instance_vanilla>]() { + $method_name( + InstantiationStrategy::RecreateInstance + ); + } + + #[test] + fn [<$method_name _pooling_cow>]() { + $method_name( + InstantiationStrategy::PoolingCopyOnWrite + ); + } + + #[test] + fn [<$method_name _pooling_vanilla>]() { + $method_name( + InstantiationStrategy::Pooling + ); + } + } + }; + + ($method_name:ident) => { + test_wasm_execution!(@no_legacy_instance_reuse $method_name); + + paste::item! { + #[test] + fn [<$method_name _legacy_instance_reuse>]() { + $method_name( + InstantiationStrategy::LegacyInstanceReuse + ); + } + } + }; +} + struct RuntimeBuilder { - code: Option<&'static str>, - fast_instance_reuse: bool, + code: Option, + instantiation_strategy: InstantiationStrategy, canonicalize_nans: bool, deterministic_stack: bool, - heap_pages: u32, - max_memory_pages: Option, + extra_heap_pages: u64, + max_memory_size: Option, + precompile_runtime: bool, + tmpdir: Option, } impl RuntimeBuilder { - /// Returns a new builder that won't use the fast instance reuse mechanism, but instead will - /// create a new runtime instance each time. - fn new_on_demand() -> Self { + fn new(instantiation_strategy: InstantiationStrategy) -> Self { Self { code: None, - fast_instance_reuse: false, + instantiation_strategy, canonicalize_nans: false, deterministic_stack: false, - heap_pages: 1024, - max_memory_pages: None, + extra_heap_pages: 1024, + max_memory_size: None, + precompile_runtime: false, + tmpdir: None, } } - fn use_wat(&mut self, code: &'static str) { + fn use_wat(mut self, code: String) -> Self { self.code = Some(code); + self } - fn canonicalize_nans(&mut self, canonicalize_nans: bool) { + fn canonicalize_nans(mut self, canonicalize_nans: bool) -> Self { self.canonicalize_nans = canonicalize_nans; + self } - fn deterministic_stack(&mut self, deterministic_stack: bool) { + fn deterministic_stack(mut self, deterministic_stack: bool) -> Self { self.deterministic_stack = deterministic_stack; + self } - fn max_memory_pages(&mut self, max_memory_pages: Option) { - self.max_memory_pages = max_memory_pages; + fn precompile_runtime(mut self, precompile_runtime: bool) -> Self { + self.precompile_runtime = precompile_runtime; + self } - fn build(self) -> Arc { + fn max_memory_size(mut self, max_memory_size: Option) -> Self { + self.max_memory_size = max_memory_size; + self + } + + fn build(&mut self) -> impl WasmModule + '_ { let blob = { let wasm: Vec; let wasm = match self.code { None => wasm_binary_unwrap(), - Some(wat) => { + Some(ref wat) => { wasm = wat::parse_str(wat).expect("wat parsing failed"); &wasm }, @@ -78,45 +138,127 @@ impl RuntimeBuilder { .expect("failed to create a runtime blob out of test runtime") }; - let rt = crate::create_runtime( - blob, - crate::Config { - heap_pages: self.heap_pages, - max_memory_pages: self.max_memory_pages, - allow_missing_func_imports: true, - cache_path: None, - semantics: crate::Semantics { - fast_instance_reuse: self.fast_instance_reuse, - deterministic_stack_limit: match self.deterministic_stack { - true => Some(crate::DeterministicStackLimit { - logical_max: 65536, - native_stack_max: 256 * 1024 * 1024, - }), - false => None, - }, - canonicalize_nans: self.canonicalize_nans, + let config = crate::Config { + allow_missing_func_imports: true, + cache_path: None, + semantics: crate::Semantics { + instantiation_strategy: self.instantiation_strategy, + deterministic_stack_limit: match self.deterministic_stack { + true => Some(crate::DeterministicStackLimit { + logical_max: 65536, + native_stack_max: 256 * 1024 * 1024, + }), + false => None, }, + canonicalize_nans: self.canonicalize_nans, + parallel_compilation: true, + extra_heap_pages: self.extra_heap_pages, + max_memory_size: self.max_memory_size, }, - { - use sp_wasm_interface::HostFunctions as _; - HostFunctions::host_functions() - }, - ) - .expect("cannot create runtime"); + }; + + if self.precompile_runtime { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("runtime.bin"); - Arc::new(rt) as Arc + // Delay the removal of the temporary directory until we're dropped. + self.tmpdir = Some(dir); + + let artifact = crate::prepare_runtime_artifact(blob, &config.semantics).unwrap(); + std::fs::write(&path, artifact).unwrap(); + unsafe { crate::create_runtime_from_artifact::(&path, config) } + } else { + crate::create_runtime::(blob, config) + } + .expect("cannot create runtime") } } -#[test] -fn test_nan_canonicalization() { - let runtime = { - let mut builder = RuntimeBuilder::new_on_demand(); - builder.canonicalize_nans(true); - builder.build() - }; +fn deep_call_stack_wat(depth: usize) -> String { + format!( + r#" + (module + (memory $0 32) + (export "memory" (memory $0)) + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "overflow") call 0) + + (func $overflow (param $0 i32) + (block $label$1 + (br_if $label$1 + (i32.ge_u + (local.get $0) + (i32.const {depth}) + ) + ) + (call $overflow + (i32.add + (local.get $0) + (i32.const 1) + ) + ) + ) + ) + + (func (export "main") + (param i32 i32) (result i64) + (call $overflow (i32.const 0)) + (i64.const 0) + ) + ) + "# + ) +} + +// These two tests ensure that the `wasmtime`'s stack size limit and the amount of +// stack space used by a single stack frame doesn't suddenly change without us noticing. +// +// If they do (e.g. because we've pulled in a new version of `wasmtime`) we want to know +// that it did, regardless of how small the change was. +// +// If these tests starting failing it doesn't necessarily mean that something is broken; +// what it means is that one (or multiple) of the following has to be done: +// a) the tests may need to be updated for the new call depth, +// b) the stack limit may need to be changed to maintain backwards compatibility, +// c) the root cause of the new call depth limit determined, and potentially fixed, +// d) the new call depth limit may need to be validated to ensure it doesn't prevent any +// existing chain from syncing (if it was effectively decreased) + +// We need two limits here since depending on whether the code is compiled in debug +// or in release mode the maximum call depth is slightly different. +const CALL_DEPTH_LOWER_LIMIT: usize = 65478; +const CALL_DEPTH_UPPER_LIMIT: usize = 65514; + +test_wasm_execution!(test_consume_under_1mb_of_stack_does_not_trap); +fn test_consume_under_1mb_of_stack_does_not_trap(instantiation_strategy: InstantiationStrategy) { + let wat = deep_call_stack_wat(CALL_DEPTH_LOWER_LIMIT); + let mut builder = RuntimeBuilder::new(instantiation_strategy).use_wat(wat); + let runtime = builder.build(); + let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); + instance.call_export("main", &[]).unwrap(); +} + +test_wasm_execution!(test_consume_over_1mb_of_stack_does_trap); +fn test_consume_over_1mb_of_stack_does_trap(instantiation_strategy: InstantiationStrategy) { + let wat = deep_call_stack_wat(CALL_DEPTH_UPPER_LIMIT + 1); + let mut builder = RuntimeBuilder::new(instantiation_strategy).use_wat(wat); + let runtime = builder.build(); + let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); + match instance.call_export("main", &[]).unwrap_err() { + Error::AbortedDueToTrap(error) => { + let expected = "wasm trap: call stack exhausted"; + assert_eq!(error.message, expected); + }, + error => panic!("unexpected error: {:?}", error), + } +} - let instance = runtime.new_instance().expect("failed to instantiate a runtime"); +test_wasm_execution!(test_nan_canonicalization); +fn test_nan_canonicalization(instantiation_strategy: InstantiationStrategy) { + let mut builder = RuntimeBuilder::new(instantiation_strategy).canonicalize_nans(true); + let runtime = builder.build(); + + let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); /// A NaN with canonical payload bits. const CANONICAL_NAN_BITS: u32 = 0x7fc00000; @@ -149,161 +291,267 @@ fn test_nan_canonicalization() { assert_eq!(res, CANONICAL_NAN_BITS); } -#[test] -fn test_stack_depth_reaching() { +test_wasm_execution!(test_stack_depth_reaching); +fn test_stack_depth_reaching(instantiation_strategy: InstantiationStrategy) { const TEST_GUARD_PAGE_SKIP: &str = include_str!("test-guard-page-skip.wat"); - let runtime = { - let mut builder = RuntimeBuilder::new_on_demand(); - builder.use_wat(TEST_GUARD_PAGE_SKIP); - builder.deterministic_stack(true); - builder.build() - }; - let instance = runtime.new_instance().expect("failed to instantiate a runtime"); + let mut builder = RuntimeBuilder::new(instantiation_strategy) + .use_wat(TEST_GUARD_PAGE_SKIP.to_string()) + .deterministic_stack(true); - let err = instance.call_export("test-many-locals", &[]).unwrap_err(); + let runtime = builder.build(); + let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); - assert!( - format!("{:?}", err).starts_with("Other(\"Wasm execution trapped: wasm trap: unreachable") - ); + match instance.call_export("test-many-locals", &[]).unwrap_err() { + Error::AbortedDueToTrap(error) => { + let expected = "wasm trap: wasm `unreachable` instruction executed"; + assert_eq!(error.message, expected); + }, + error => panic!("unexpected error: {:?}", error), + } } -#[test] -fn test_max_memory_pages() { +test_wasm_execution!(test_max_memory_pages_imported_memory_without_precompilation); +fn test_max_memory_pages_imported_memory_without_precompilation( + instantiation_strategy: InstantiationStrategy, +) { + test_max_memory_pages(instantiation_strategy, true, false); +} + +test_wasm_execution!(test_max_memory_pages_exported_memory_without_precompilation); +fn test_max_memory_pages_exported_memory_without_precompilation( + instantiation_strategy: InstantiationStrategy, +) { + test_max_memory_pages(instantiation_strategy, false, false); +} + +test_wasm_execution!(@no_legacy_instance_reuse test_max_memory_pages_imported_memory_with_precompilation); +fn test_max_memory_pages_imported_memory_with_precompilation( + instantiation_strategy: InstantiationStrategy, +) { + test_max_memory_pages(instantiation_strategy, true, true); +} + +test_wasm_execution!(@no_legacy_instance_reuse test_max_memory_pages_exported_memory_with_precompilation); +fn test_max_memory_pages_exported_memory_with_precompilation( + instantiation_strategy: InstantiationStrategy, +) { + test_max_memory_pages(instantiation_strategy, false, true); +} + +fn test_max_memory_pages( + instantiation_strategy: InstantiationStrategy, + import_memory: bool, + precompile_runtime: bool, +) { fn try_instantiate( - max_memory_pages: Option, - wat: &'static str, + max_memory_size: Option, + wat: String, + instantiation_strategy: InstantiationStrategy, + precompile_runtime: bool, ) -> Result<(), Box> { - let runtime = { - let mut builder = RuntimeBuilder::new_on_demand(); - builder.use_wat(wat); - builder.max_memory_pages(max_memory_pages); - builder.build() - }; - let instance = runtime.new_instance()?; + let mut builder = RuntimeBuilder::new(instantiation_strategy) + .use_wat(wat) + .max_memory_size(max_memory_size) + .precompile_runtime(precompile_runtime); + + let runtime = builder.build(); + let mut instance = runtime.new_instance()?; let _ = instance.call_export("main", &[])?; Ok(()) } + fn memory(initial: u32, maximum: Option, import: bool) -> String { + let memory = if let Some(maximum) = maximum { + format!("(memory $0 {} {})", initial, maximum) + } else { + format!("(memory $0 {})", initial) + }; + + if import { + format!("(import \"env\" \"memory\" {})", memory) + } else { + format!("{}\n(export \"memory\" (memory $0))", memory) + } + } + + const WASM_PAGE_SIZE: usize = 65536; + // check the old behavior if preserved. That is, if no limit is set we allow 4 GiB of memory. try_instantiate( None, - r#" - (module - ;; we want to allocate the maximum number of pages supported in wasm for this test. - ;; - ;; However, due to a bug in wasmtime (I think wasmi is also affected) it is only possible - ;; to allocate 65536 - 1 pages. - ;; - ;; Then, during creation of the Substrate Runtime instance, 1024 (heap_pages) pages are - ;; mounted. - ;; - ;; Thus 65535 = 64511 + 1024 - (import "env" "memory" (memory 64511)) - - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + (i64.const 0) + ) ) - ) - "#, + "#, + /* + We want to allocate the maximum number of pages supported in wasm for this test. + However, due to a bug in wasmtime (I think wasmi is also affected) it is only possible + to allocate 65536 - 1 pages. + + Then, during creation of the Substrate Runtime instance, 1024 (heap_pages) pages are + mounted. + + Thus 65535 = 64511 + 1024 + */ + memory(64511, None, import_memory) + ), + instantiation_strategy, + precompile_runtime, ) .unwrap(); // max is not specified, therefore it's implied to be 65536 pages (4 GiB). // - // max_memory_pages = 1 (initial) + 1024 (heap_pages) + // max_memory_size = (1 (initial) + 1024 (heap_pages)) * WASM_PAGE_SIZE try_instantiate( - Some(1 + 1024), - r#" - (module - - (import "env" "memory" (memory 1)) ;; <- 1 initial, max is not specified - - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) + Some((1 + 1024) * WASM_PAGE_SIZE), + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + (i64.const 0) + ) ) - ) - "#, + "#, + // 1 initial, max is not specified. + memory(1, None, import_memory) + ), + instantiation_strategy, + precompile_runtime, ) .unwrap(); // max is specified explicitly to 2048 pages. try_instantiate( - Some(1 + 1024), - r#" - (module - - (import "env" "memory" (memory 1 2048)) ;; <- max is 2048 - - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) + Some((1 + 1024) * WASM_PAGE_SIZE), + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + (i64.const 0) + ) ) - ) - "#, + "#, + // Max is 2048. + memory(1, Some(2048), import_memory) + ), + instantiation_strategy, + precompile_runtime, ) .unwrap(); // memory grow should work as long as it doesn't exceed 1025 pages in total. try_instantiate( - Some(0 + 1024 + 25), - r#" - (module - (import "env" "memory" (memory 0)) ;; <- zero starting pages. - - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - - ;; assert(memory.grow returns != -1) - (if - (i32.eq - (memory.grow - (i32.const 25) + Some((0 + 1024 + 25) * WASM_PAGE_SIZE), + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + + ;; assert(memory.grow returns != -1) + (if + (i32.eq + (memory.grow + (i32.const 25) + ) + (i32.const -1) ) - (i32.const -1) + (unreachable) ) - (unreachable) - ) - (i64.const 0) + (i64.const 0) + ) ) - ) - "#, + "#, + // Zero starting pages. + memory(0, None, import_memory) + ), + instantiation_strategy, + precompile_runtime, ) .unwrap(); // We start with 1025 pages and try to grow at least one. try_instantiate( - Some(1 + 1024), - r#" - (module - (import "env" "memory" (memory 1)) ;; <- initial=1, meaning after heap pages mount the - ;; total will be already 1025 - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - - ;; assert(memory.grow returns == -1) - (if - (i32.ne - (memory.grow - (i32.const 1) + Some((1 + 1024) * WASM_PAGE_SIZE), + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + + ;; assert(memory.grow returns == -1) + (if + (i32.ne + (memory.grow + (i32.const 1) + ) + (i32.const -1) ) - (i32.const -1) + (unreachable) ) - (unreachable) - ) - (i64.const 0) + (i64.const 0) + ) ) - ) - "#, + "#, + // Initial=1, meaning after heap pages mount the total will be already 1025. + memory(1, None, import_memory) + ), + instantiation_strategy, + precompile_runtime, ) .unwrap(); } + +// This test takes quite a while to execute in a debug build (over 6 minutes on a TR 3970x) +// so it's ignored by default unless it was compiled with `--release`. +#[cfg_attr(build_type = "debug", ignore)] +#[test] +fn test_instances_without_reuse_are_not_leaked() { + let runtime = crate::create_runtime::( + RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(), + crate::Config { + allow_missing_func_imports: true, + cache_path: None, + semantics: crate::Semantics { + instantiation_strategy: InstantiationStrategy::RecreateInstance, + deterministic_stack_limit: None, + canonicalize_nans: false, + parallel_compilation: true, + extra_heap_pages: 2048, + max_memory_size: None, + }, + }, + ) + .unwrap(); + + // As long as the `wasmtime`'s `Store` lives the instances spawned through it + // will live indefinitely. Currently it has a maximum limit of 10k instances, + // so let's spawn 10k + 1 of them to make sure our code doesn't keep the `Store` + // alive longer than it is necessary. (And since we disabled instance reuse + // a new instance will be spawned on each call.) + let mut instance = runtime.new_instance().unwrap(); + for _ in 0..10001 { + instance.call_export("test_empty_return", &[0]).unwrap(); + } +} diff --git a/client/executor/wasmtime/src/util.rs b/client/executor/wasmtime/src/util.rs index 2c135fe7a343b..15f62e475033a 100644 --- a/client/executor/wasmtime/src/util.rs +++ b/client/executor/wasmtime/src/util.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,7 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use sp_wasm_interface::Value; +use crate::{runtime::StoreData, InstantiationStrategy}; +use sc_executor_common::{ + error::{Error, Result}, + util::checked_range, +}; +use sp_wasm_interface::{Pointer, Value}; +use wasmtime::{AsContext, AsContextMut}; /// Converts a [`wasmtime::Val`] into a substrate runtime interface [`Value`]. /// @@ -41,3 +47,127 @@ pub fn into_wasmtime_val(value: Value) -> wasmtime::Val { Value::F64(f_bits) => wasmtime::Val::F64(f_bits), } } + +/// Read data from the instance memory into a slice. +/// +/// Returns an error if the read would go out of the memory bounds. +pub(crate) fn read_memory_into( + ctx: impl AsContext, + address: Pointer, + dest: &mut [u8], +) -> Result<()> { + let memory = ctx.as_context().data().memory().data(&ctx); + + let range = checked_range(address.into(), dest.len(), memory.len()) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + dest.copy_from_slice(&memory[range]); + Ok(()) +} + +/// Write data to the instance memory from a slice. +/// +/// Returns an error if the write would go out of the memory bounds. +pub(crate) fn write_memory_from( + mut ctx: impl AsContextMut, + address: Pointer, + data: &[u8], +) -> Result<()> { + let memory = ctx.as_context().data().memory(); + let memory = memory.data_mut(&mut ctx); + + let range = checked_range(address.into(), data.len(), memory.len()) + .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; + memory[range].copy_from_slice(data); + Ok(()) +} + +/// Checks whether the `madvise(MADV_DONTNEED)` works as expected. +/// +/// In certain environments (e.g. when running under the QEMU user-mode emulator) +/// this syscall is broken. +#[cfg(target_os = "linux")] +fn is_madvise_working() -> std::result::Result { + let page_size = rustix::param::page_size(); + + unsafe { + // Allocate two memory pages. + let pointer = rustix::mm::mmap_anonymous( + std::ptr::null_mut(), + 2 * page_size, + rustix::mm::ProtFlags::READ | rustix::mm::ProtFlags::WRITE, + rustix::mm::MapFlags::PRIVATE, + ) + .map_err(|error| format!("mmap failed: {}", error))?; + + // Dirty them both. + std::ptr::write_volatile(pointer.cast::(), b'A'); + std::ptr::write_volatile(pointer.cast::().add(page_size), b'B'); + + // Clear the first page. + let result_madvise = + rustix::mm::madvise(pointer, page_size, rustix::mm::Advice::LinuxDontNeed) + .map_err(|error| format!("madvise failed: {}", error)); + + // Fetch the values. + let value_1 = std::ptr::read_volatile(pointer.cast::()); + let value_2 = std::ptr::read_volatile(pointer.cast::().add(page_size)); + + let result_munmap = rustix::mm::munmap(pointer, 2 * page_size) + .map_err(|error| format!("munmap failed: {}", error)); + + result_madvise?; + result_munmap?; + + // Verify that the first page was cleared, while the second one was not. + Ok(value_1 == 0 && value_2 == b'B') + } +} + +#[cfg(test)] +#[cfg(target_os = "linux")] +#[test] +fn test_is_madvise_working_check_does_not_fail() { + assert!(is_madvise_working().is_ok()); +} + +/// Checks whether a given instantiation strategy can be safely used, and replaces +/// it with a slower (but sound) alternative if it isn't. +#[cfg(target_os = "linux")] +pub(crate) fn replace_strategy_if_broken(strategy: &mut InstantiationStrategy) { + let replacement_strategy = match *strategy { + // These strategies don't need working `madvise`. + InstantiationStrategy::Pooling | InstantiationStrategy::RecreateInstance => return, + + // These strategies require a working `madvise` to be sound. + InstantiationStrategy::PoolingCopyOnWrite => InstantiationStrategy::Pooling, + InstantiationStrategy::RecreateInstanceCopyOnWrite | + InstantiationStrategy::LegacyInstanceReuse => InstantiationStrategy::RecreateInstance, + }; + + use once_cell::sync::OnceCell; + static IS_OK: OnceCell = OnceCell::new(); + + let is_ok = IS_OK.get_or_init(|| { + let is_ok = match is_madvise_working() { + Ok(is_ok) => is_ok, + Err(error) => { + // This should never happen. + log::warn!("Failed to check whether `madvise(MADV_DONTNEED)` works: {}", error); + false + } + }; + + if !is_ok { + log::warn!("You're running on a system with a broken `madvise(MADV_DONTNEED)` implementation. This will result in lower performance."); + } + + is_ok + }); + + if !is_ok { + *strategy = replacement_strategy; + } +} + +#[cfg(not(target_os = "linux"))] +pub(crate) fn replace_strategy_if_broken(_: &mut InstantiationStrategy) {} diff --git a/client/finality-grandpa-warp-sync/Cargo.toml b/client/finality-grandpa-warp-sync/Cargo.toml deleted file mode 100644 index a444125fdfa11..0000000000000 --- a/client/finality-grandpa-warp-sync/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -description = "A request-response protocol for handling grandpa warp sync requests" -name = "sc-finality-grandpa-warp-sync" -version = "0.10.0-dev" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -authors = ["Parity Technologies "] -edition = "2018" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } -derive_more = "0.99.11" -futures = "0.3.8" -log = "0.4.11" -prost = "0.8" -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-finality-grandpa = { version = "0.10.0-dev", path = "../finality-grandpa" } -sc-network = { version = "0.10.0-dev", path = "../network" } -sc-service = { version = "0.10.0-dev", path = "../service" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } - -[dev-dependencies] -finality-grandpa = { version = "0.14.4" } -rand = "0.8" -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sc-consensus = { version = "0.10.0-dev", path = "../../client/consensus/common" } -sp-keyring = { version = "4.0.0-dev", path = "../../primitives/keyring" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 7fdd91e557ab7..0d5b8eaca5bec 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-finality-grandpa" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Integration of the GRANDPA finality gadget into substrate." documentation = "https://docs.rs/sc-finality-grandpa" @@ -13,49 +13,49 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -derive_more = "0.99.2" +ahash = "0.7.6" +array-bytes = "4.1" +async-trait = "0.1.57" dyn-clone = "1.0" -fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } -futures = "0.3.9" +finality-grandpa = { version = "0.16.0", features = ["derive-codec"] } +futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.8" -parking_lot = "0.11.1" +log = "0.4.17" +parity-scale-codec = { version = "3.0.0", features = ["derive"] } +parking_lot = "0.12.1" rand = "0.8.4" -parity-scale-codec = { version = "2.0.0", features = ["derive"] } -sp-application-crypto = { version = "4.0.0-dev", path = "../../primitives/application-crypto" } -sp-arithmetic = { version = "4.0.0-dev", path = "../../primitives/arithmetic" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +serde_json = "1.0.85" +thiserror = "1.0" +fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", path = "../../primitives/keystore" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } -serde_json = "1.0.68" -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } +sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-application-crypto = { version = "7.0.0", path = "../../primitives/application-crypto" } +sp-arithmetic = { version = "6.0.0", path = "../../primitives/arithmetic" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.9.0" } -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -finality-grandpa = { version = "0.14.4", features = ["derive-codec"] } -async-trait = "0.1.50" +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3.0" -finality-grandpa = { version = "0.14.1", features = [ - "derive-codec", - "test-helpers", -] } +finality-grandpa = { version = "0.16.0", features = ["derive-codec", "test-helpers"] } +serde = "1.0.136" +tokio = "1.22.0" sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-test = { version = "0.8.0", path = "../network/test" } -sp-keyring = { version = "4.0.0-dev", path = "../../primitives/keyring" } +sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } -tokio = "1.10" -tempfile = "3.1.0" diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index d2976ee71275f..252c5e3871a64 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -4,35 +4,34 @@ version = "0.10.0-dev" authors = ["Parity Technologies "] description = "RPC extensions for the GRANDPA finality gadget" repository = "https://github.com/paritytech/substrate/" -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" readme = "README.md" +homepage = "https://substrate.io" [dependencies] -sc-finality-grandpa = { version = "0.10.0-dev", path = "../" } -sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -finality-grandpa = { version = "0.14.4", features = ["derive-codec"] } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -jsonrpc-pubsub = "18.0.0" +finality-grandpa = { version = "0.16.0", features = ["derive-codec"] } futures = "0.3.16" +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +log = "0.4.8" +parity-scale-codec = { version = "3.0.0", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] } serde_json = "1.0.50" -log = "0.4.8" -derive_more = "0.99.2" -parity-scale-codec = { version = "2.0.0", features = ["derive"] } +thiserror = "1.0" sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-finality-grandpa = { version = "0.10.0-dev", path = "../" } +sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } [dev-dependencies] sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } -sc-rpc = { version = "4.0.0-dev", path = "../../rpc", features = [ - "test-helpers", -] } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } +sc-rpc = { version = "4.0.0-dev", features = [ + "test-helpers", +], path = "../../rpc" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } -sp-keyring = { version = "4.0.0-dev", path = "../../../primitives/keyring" } +sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } +tokio = { version = "1.22.0", features = ["macros"] } diff --git a/client/finality-grandpa/rpc/src/error.rs b/client/finality-grandpa/rpc/src/error.rs index c812b78f3fd8e..197c0b8a72102 100644 --- a/client/finality-grandpa/rpc/src/error.rs +++ b/client/finality-grandpa/rpc/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,21 +16,26 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#[derive(derive_more::Display, derive_more::From)] +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; + +#[derive(Debug, thiserror::Error)] /// Top-level error type for the RPC handler pub enum Error { /// The GRANDPA RPC endpoint is not ready. - #[display(fmt = "GRANDPA RPC endpoint not ready")] + #[error("GRANDPA RPC endpoint not ready")] EndpointNotReady, /// GRANDPA reports the authority set id to be larger than 32-bits. - #[display(fmt = "GRANDPA reports authority set id unreasonably large")] + #[error("GRANDPA reports authority set id unreasonably large")] AuthoritySetIdReportedAsUnreasonablyLarge, /// GRANDPA reports voter state with round id or weights larger than 32-bits. - #[display(fmt = "GRANDPA reports voter state as unreasonably large")] + #[error("GRANDPA reports voter state as unreasonably large")] VoterStateReportsUnreasonablyLargeNumbers, /// GRANDPA prove finality failed. - #[display(fmt = "GRANDPA prove finality rpc failed: {}", _0)] - ProveFinalityFailed(sc_finality_grandpa::FinalityProofError), + #[error("GRANDPA prove finality rpc failed: {0}")] + ProveFinalityFailed(#[from] sc_finality_grandpa::FinalityProofError), } /// The error codes returned by jsonrpc. @@ -56,15 +61,15 @@ impl From for ErrorCode { } } -impl From for jsonrpc_core::Error { +impl From for JsonRpseeError { fn from(error: Error) -> Self { - let message = format!("{}", error); + let message = error.to_string(); let code = ErrorCode::from(error); - jsonrpc_core::Error { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + code as i32, message, - code: jsonrpc_core::ErrorCode::ServerError(code as i64), - data: None, - } + None::<()>, + ))) } } diff --git a/client/finality-grandpa/rpc/src/finality.rs b/client/finality-grandpa/rpc/src/finality.rs index 62e3502fc7180..f2be6d674b2f1 100644 --- a/client/finality-grandpa/rpc/src/finality.rs +++ b/client/finality-grandpa/rpc/src/finality.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index b8b8b2d956463..dfdad666ba8f3 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,167 +19,135 @@ //! RPC API for GRANDPA. #![warn(missing_docs)] -use futures::{task::Spawn, FutureExt, SinkExt, StreamExt, TryFutureExt}; -use jsonrpc_derive::rpc; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; +use futures::{FutureExt, StreamExt}; use log::warn; use std::sync::Arc; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::SubscriptionResult, + SubscriptionSink, +}; + mod error; mod finality; mod notification; mod report; use sc_finality_grandpa::GrandpaJustificationStream; +use sc_rpc::SubscriptionTaskExecutor; use sp_runtime::traits::{Block as BlockT, NumberFor}; use finality::{EncodedFinalityProof, RpcFinalityProofProvider}; use notification::JustificationNotification; use report::{ReportAuthoritySet, ReportVoterState, ReportedRoundStates}; -type FutureResult = jsonrpc_core::BoxFuture>; - /// Provides RPC methods for interacting with GRANDPA. -#[rpc] +#[rpc(client, server)] pub trait GrandpaApi { - /// RPC Metadata - type Metadata; - /// Returns the state of the current best round state as well as the /// ongoing background rounds. - #[rpc(name = "grandpa_roundState")] - fn round_state(&self) -> FutureResult; + #[method(name = "grandpa_roundState")] + async fn round_state(&self) -> RpcResult; /// Returns the block most recently finalized by Grandpa, alongside /// side its justification. - #[pubsub( - subscription = "grandpa_justifications", - subscribe, - name = "grandpa_subscribeJustifications" + #[subscription( + name = "grandpa_subscribeJustifications" => "grandpa_justifications", + unsubscribe = "grandpa_unsubscribeJustifications", + item = Notification )] - fn subscribe_justifications( - &self, - metadata: Self::Metadata, - subscriber: Subscriber, - ); - - /// Unsubscribe from receiving notifications about recently finalized blocks. - #[pubsub( - subscription = "grandpa_justifications", - unsubscribe, - name = "grandpa_unsubscribeJustifications" - )] - fn unsubscribe_justifications( - &self, - metadata: Option, - id: SubscriptionId, - ) -> jsonrpc_core::Result; + fn subscribe_justifications(&self); /// Prove finality for the given block number by returning the Justification for the last block /// in the set and all the intermediary headers to link them together. - #[rpc(name = "grandpa_proveFinality")] - fn prove_finality(&self, block: Number) -> FutureResult>; + #[method(name = "grandpa_proveFinality")] + async fn prove_finality(&self, block: Number) -> RpcResult>; } -/// Implements the GrandpaApi RPC trait for interacting with GRANDPA. -pub struct GrandpaRpcHandler { +/// Provides RPC methods for interacting with GRANDPA. +pub struct Grandpa { + executor: SubscriptionTaskExecutor, authority_set: AuthoritySet, voter_state: VoterState, justification_stream: GrandpaJustificationStream, - manager: SubscriptionManager, finality_proof_provider: Arc, } - impl - GrandpaRpcHandler + Grandpa { - /// Creates a new GrandpaRpcHandler instance. - pub fn new( + /// Prepare a new [`Grandpa`] Rpc handler. + pub fn new( + executor: SubscriptionTaskExecutor, authority_set: AuthoritySet, voter_state: VoterState, justification_stream: GrandpaJustificationStream, - executor: E, finality_proof_provider: Arc, - ) -> Self - where - E: Spawn + Sync + Send + 'static, - { - let manager = SubscriptionManager::new(Arc::new(executor)); - Self { authority_set, voter_state, justification_stream, manager, finality_proof_provider } + ) -> Self { + Self { executor, authority_set, voter_state, justification_stream, finality_proof_provider } } } +#[async_trait] impl - GrandpaApi> - for GrandpaRpcHandler + GrandpaApiServer> + for Grandpa where VoterState: ReportVoterState + Send + Sync + 'static, AuthoritySet: ReportAuthoritySet + Send + Sync + 'static, Block: BlockT, ProofProvider: RpcFinalityProofProvider + Send + Sync + 'static, { - type Metadata = sc_rpc::Metadata; - - fn round_state(&self) -> FutureResult { - let round_states = ReportedRoundStates::from(&self.authority_set, &self.voter_state); - let future = async move { round_states }.boxed(); - future.map_err(jsonrpc_core::Error::from).boxed() + async fn round_state(&self) -> RpcResult { + ReportedRoundStates::from(&self.authority_set, &self.voter_state).map_err(Into::into) } - fn subscribe_justifications( - &self, - _metadata: Self::Metadata, - subscriber: Subscriber, - ) { - let stream = self - .justification_stream - .subscribe() - .map(|x| Ok(Ok::<_, jsonrpc_core::Error>(JustificationNotification::from(x)))); - - self.manager.add(subscriber, |sink| { - stream - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - .map(|_| ()) - }); - } + fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult { + let stream = self.justification_stream.subscribe().map( + |x: sc_finality_grandpa::GrandpaJustification| { + JustificationNotification::from(x) + }, + ); - fn unsubscribe_justifications( - &self, - _metadata: Option, - id: SubscriptionId, - ) -> jsonrpc_core::Result { - Ok(self.manager.cancel(id)) + let fut = async move { + sink.pipe_from_stream(stream).await; + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) } - fn prove_finality( + async fn prove_finality( &self, block: NumberFor, - ) -> FutureResult> { - let result = self.finality_proof_provider.rpc_prove_finality(block); - let future = async move { result }.boxed(); - future + ) -> RpcResult> { + self.finality_proof_provider + .rpc_prove_finality(block) .map_err(|e| { warn!("Error proving finality: {}", e); error::Error::ProveFinalityFailed(e) }) - .map_err(jsonrpc_core::Error::from) - .boxed() + .map_err(Into::into) } } #[cfg(test)] mod tests { use super::*; - use jsonrpc_core::{types::Params, Notification, Output}; use std::{collections::HashSet, convert::TryInto, sync::Arc}; + use jsonrpsee::{ + types::{EmptyServerParams as EmptyParams, SubscriptionId}, + RpcModule, + }; use parity_scale_codec::{Decode, Encode}; use sc_block_builder::{BlockBuilder, RecordProof}; use sc_finality_grandpa::{ report, AuthorityId, FinalityProof, GrandpaJustification, GrandpaJustificationSender, }; use sp_blockchain::HeaderBackend; - use sp_core::crypto::Public; + use sp_core::{crypto::ByteArray, testing::TaskExecutor}; use sp_keyring::Ed25519Keyring; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use substrate_test_runtime_client::{ @@ -196,8 +164,8 @@ mod tests { } fn voters() -> HashSet { - let voter_id_1 = AuthorityId::from_slice(&[1; 32]); - let voter_id_2 = AuthorityId::from_slice(&[2; 32]); + let voter_id_1 = AuthorityId::from_slice(&[1; 32]).unwrap(); + let voter_id_2 = AuthorityId::from_slice(&[2; 32]).unwrap(); vec![voter_id_1, voter_id_2].into_iter().collect() } @@ -245,7 +213,7 @@ mod tests { impl ReportVoterState for TestVoterState { fn get(&self) -> Option> { - let voter_id_1 = AuthorityId::from_slice(&[1; 32]); + let voter_id_1 = AuthorityId::from_slice(&[1; 32]).unwrap(); let voters_best: HashSet<_> = vec![voter_id_1].into_iter().collect(); let best_round_state = sc_finality_grandpa::report::RoundState { @@ -274,7 +242,10 @@ mod tests { fn setup_io_handler( voter_state: VoterState, - ) -> (jsonrpc_core::MetaIoHandler, GrandpaJustificationSender) + ) -> ( + RpcModule>, + GrandpaJustificationSender, + ) where VoterState: ReportVoterState + Send + Sync + 'static, { @@ -284,120 +255,80 @@ mod tests { fn setup_io_handler_with_finality_proofs( voter_state: VoterState, finality_proof: Option>, - ) -> (jsonrpc_core::MetaIoHandler, GrandpaJustificationSender) + ) -> ( + RpcModule>, + GrandpaJustificationSender, + ) where VoterState: ReportVoterState + Send + Sync + 'static, { let (justification_sender, justification_stream) = GrandpaJustificationStream::channel(); let finality_proof_provider = Arc::new(TestFinalityProofProvider { finality_proof }); + let executor = Arc::new(TaskExecutor::default()); - let handler = GrandpaRpcHandler::new( + let rpc = Grandpa::new( + executor, TestAuthoritySet, voter_state, justification_stream, - sc_rpc::testing::TaskExecutor, finality_proof_provider, - ); - - let mut io = jsonrpc_core::MetaIoHandler::default(); - io.extend_with(GrandpaApi::to_delegate(handler)); + ) + .into_rpc(); - (io, justification_sender) + (rpc, justification_sender) } - #[test] - fn uninitialized_rpc_handler() { - let (io, _) = setup_io_handler(EmptyVoterState); - - let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":1}"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":1}"#; + #[tokio::test] + async fn uninitialized_rpc_handler() { + let (rpc, _) = setup_io_handler(EmptyVoterState); + let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":0}"#.to_string(); + let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; + let (response, _) = rpc.raw_json_request(&request).await.unwrap(); - let meta = sc_rpc::Metadata::default(); - assert_eq!(Some(response.into()), io.handle_request_sync(request, meta)); + assert_eq!(expected_response, response.result); } - #[test] - fn working_rpc_handler() { - let (io, _) = setup_io_handler(TestVoterState); - - let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":1}"#; - let response = "{\"jsonrpc\":\"2.0\",\"result\":{\ - \"background\":[{\ - \"precommits\":{\"currentWeight\":100,\"missing\":[]},\ - \"prevotes\":{\"currentWeight\":100,\"missing\":[]},\ - \"round\":1,\"thresholdWeight\":67,\"totalWeight\":100\ - }],\ + #[tokio::test] + async fn working_rpc_handler() { + let (rpc, _) = setup_io_handler(TestVoterState); + let expected_response = "{\"jsonrpc\":\"2.0\",\"result\":{\ + \"setId\":1,\ \"best\":{\ - \"precommits\":{\"currentWeight\":0,\"missing\":[\"5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT\",\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]},\ + \"round\":2,\"totalWeight\":100,\"thresholdWeight\":67,\ \"prevotes\":{\"currentWeight\":50,\"missing\":[\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]},\ - \"round\":2,\"thresholdWeight\":67,\"totalWeight\":100\ + \"precommits\":{\"currentWeight\":0,\"missing\":[\"5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT\",\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]}\ },\ - \"setId\":1\ - },\"id\":1}"; - - let meta = sc_rpc::Metadata::default(); - assert_eq!(io.handle_request_sync(request, meta), Some(response.into())); - } - - fn setup_session() -> (sc_rpc::Metadata, futures::channel::mpsc::UnboundedReceiver) { - let (tx, rx) = futures::channel::mpsc::unbounded(); - let meta = sc_rpc::Metadata::new(tx); - (meta, rx) - } - - #[test] - fn subscribe_and_unsubscribe_to_justifications() { - let (io, _) = setup_io_handler(TestVoterState); - let (meta, _) = setup_session(); - - // Subscribe - let sub_request = - r#"{"jsonrpc":"2.0","method":"grandpa_subscribeJustifications","params":[],"id":1}"#; - let resp = io.handle_request_sync(sub_request, meta.clone()); - let resp: Output = serde_json::from_str(&resp.unwrap()).unwrap(); - - let sub_id = match resp { - Output::Success(success) => success.result, - _ => panic!(), - }; - - // Unsubscribe - let unsub_req = format!( - "{{\"jsonrpc\":\"2.0\",\"method\":\"grandpa_unsubscribeJustifications\",\"params\":[{}],\"id\":1}}", - sub_id - ); - assert_eq!( - io.handle_request_sync(&unsub_req, meta.clone()), - Some(r#"{"jsonrpc":"2.0","result":true,"id":1}"#.into()), - ); + \"background\":[{\ + \"round\":1,\"totalWeight\":100,\"thresholdWeight\":67,\ + \"prevotes\":{\"currentWeight\":100,\"missing\":[]},\ + \"precommits\":{\"currentWeight\":100,\"missing\":[]}\ + }]\ + },\"id\":0}".to_string(); - // Unsubscribe again and fail - assert_eq!( - io.handle_request_sync(&unsub_req, meta), - Some("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid subscription id.\"},\"id\":1}".into()), - ); + let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; + let (response, _) = rpc.raw_json_request(&request).await.unwrap(); + assert_eq!(expected_response, response.result); } - #[test] - fn subscribe_and_unsubscribe_with_wrong_id() { - let (io, _) = setup_io_handler(TestVoterState); - let (meta, _) = setup_session(); - - // Subscribe - let sub_request = - r#"{"jsonrpc":"2.0","method":"grandpa_subscribeJustifications","params":[],"id":1}"#; - let resp = io.handle_request_sync(sub_request, meta.clone()); - let resp: Output = serde_json::from_str(&resp.unwrap()).unwrap(); - assert!(matches!(resp, Output::Success(_))); + #[tokio::test] + async fn subscribe_and_unsubscribe_with_wrong_id() { + let (rpc, _) = setup_io_handler(TestVoterState); + // Subscribe call. + let _sub = rpc + .subscribe("grandpa_subscribeJustifications", EmptyParams::new()) + .await + .unwrap(); // Unsubscribe with wrong ID - assert_eq!( - io.handle_request_sync( + let (response, _) = rpc + .raw_json_request( r#"{"jsonrpc":"2.0","method":"grandpa_unsubscribeJustifications","params":["FOO"],"id":1}"#, - meta.clone() - ), - Some("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid subscription id.\"},\"id\":1}".into()) - ); + ) + .await + .unwrap(); + let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; + + assert_eq!(response.result, expected); } fn create_justification() -> GrandpaJustification { @@ -454,60 +385,41 @@ mod tests { justification } - #[test] - fn subscribe_and_listen_to_one_justification() { - let (io, justification_sender) = setup_io_handler(TestVoterState); - let (meta, receiver) = setup_session(); - - // Subscribe - let sub_request = - r#"{"jsonrpc":"2.0","method":"grandpa_subscribeJustifications","params":[],"id":1}"#; + #[tokio::test] + async fn subscribe_and_listen_to_one_justification() { + let (rpc, justification_sender) = setup_io_handler(TestVoterState); - let resp = io.handle_request_sync(sub_request, meta.clone()); - let mut resp: serde_json::Value = serde_json::from_str(&resp.unwrap()).unwrap(); - let sub_id: String = serde_json::from_value(resp["result"].take()).unwrap(); + let mut sub = rpc + .subscribe("grandpa_subscribeJustifications", EmptyParams::new()) + .await + .unwrap(); // Notify with a header and justification let justification = create_justification(); - justification_sender.notify(|| Ok(justification.clone())).unwrap(); + justification_sender.notify(|| Ok::<_, ()>(justification.clone())).unwrap(); // Inspect what we received - let recv = futures::executor::block_on(receiver.take(1).collect::>()); - let recv: Notification = serde_json::from_str(&recv[0]).unwrap(); - let mut json_map = match recv.params { - Params::Map(json_map) => json_map, - _ => panic!(), - }; - - let recv_sub_id: String = serde_json::from_value(json_map["subscription"].take()).unwrap(); - let recv_justification: sp_core::Bytes = - serde_json::from_value(json_map["result"].take()).unwrap(); + let (recv_justification, recv_sub_id): (sp_core::Bytes, SubscriptionId) = + sub.next().await.unwrap().unwrap(); let recv_justification: GrandpaJustification = Decode::decode(&mut &recv_justification[..]).unwrap(); - assert_eq!(recv.method, "grandpa_justifications"); - assert_eq!(recv_sub_id, sub_id); + assert_eq!(&recv_sub_id, sub.subscription_id()); assert_eq!(recv_justification, justification); } - #[test] - fn prove_finality_with_test_finality_proof_provider() { + #[tokio::test] + async fn prove_finality_with_test_finality_proof_provider() { let finality_proof = FinalityProof { block: header(42).hash(), justification: create_justification().encode(), unknown_headers: vec![header(2)], }; - let (io, _) = + let (rpc, _) = setup_io_handler_with_finality_proofs(TestVoterState, Some(finality_proof.clone())); - let request = - "{\"jsonrpc\":\"2.0\",\"method\":\"grandpa_proveFinality\",\"params\":[42],\"id\":1}"; - - let meta = sc_rpc::Metadata::default(); - let resp = io.handle_request_sync(request, meta); - let mut resp: serde_json::Value = serde_json::from_str(&resp.unwrap()).unwrap(); - let result: sp_core::Bytes = serde_json::from_value(resp["result"].take()).unwrap(); - let finality_proof_rpc: FinalityProof
= Decode::decode(&mut &result[..]).unwrap(); + let bytes: sp_core::Bytes = rpc.call("grandpa_proveFinality", [42]).await.unwrap(); + let finality_proof_rpc: FinalityProof
= Decode::decode(&mut &bytes[..]).unwrap(); assert_eq!(finality_proof_rpc, finality_proof); } } diff --git a/client/finality-grandpa/rpc/src/notification.rs b/client/finality-grandpa/rpc/src/notification.rs index 68944e903e0fb..6fb1383579359 100644 --- a/client/finality-grandpa/rpc/src/notification.rs +++ b/client/finality-grandpa/rpc/src/notification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/finality-grandpa/rpc/src/report.rs b/client/finality-grandpa/rpc/src/report.rs index fef8f22659953..8c04ca28ef870 100644 --- a/client/finality-grandpa/rpc/src/report.rs +++ b/client/finality-grandpa/rpc/src/report.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -87,13 +87,11 @@ impl RoundState { round_state: &report::RoundState, voters: &HashSet, ) -> Result { - use std::convert::TryInto; - let prevotes = &round_state.prevote_ids; - let missing_prevotes = voters.difference(&prevotes).cloned().collect(); + let missing_prevotes = voters.difference(prevotes).cloned().collect(); let precommits = &round_state.precommit_ids; - let missing_precommits = voters.difference(&precommits).cloned().collect(); + let missing_precommits = voters.difference(precommits).cloned().collect(); Ok(Self { round: round.try_into()?, @@ -130,8 +128,6 @@ impl ReportedRoundStates { AuthoritySet: ReportAuthoritySet, VoterState: ReportVoterState, { - use std::convert::TryFrom; - let voter_state = voter_state.get().ok_or(Error::EndpointNotReady)?; let (set_id, current_voters) = authority_set.get(); diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index 6e5dfdd05e624..0803e6b3c2931 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ use std::{cmp::Ord, fmt::Debug, ops::Add}; use finality_grandpa::voter_set::VoterSet; -use fork_tree::ForkTree; +use fork_tree::{FilterAction, ForkTree}; use log::debug; use parity_scale_codec::{Decode, Encode}; use parking_lot::MappedMutexGuard; @@ -32,23 +32,22 @@ use sp_finality_grandpa::{AuthorityId, AuthorityList}; use crate::SetId; /// Error type returned on operations on the `AuthoritySet`. -#[derive(Debug, derive_more::Display)] +#[derive(Debug, thiserror::Error)] pub enum Error { - #[display(fmt = "Invalid authority set, either empty or with an authority weight set to 0.")] + #[error("Invalid authority set, either empty or with an authority weight set to 0.")] InvalidAuthoritySet, - #[display(fmt = "Client error during ancestry lookup: {}", _0)] + #[error("Client error during ancestry lookup: {0}")] Client(E), - #[display(fmt = "Duplicate authority set change.")] + #[error("Duplicate authority set change.")] DuplicateAuthoritySetChange, - #[display(fmt = "Multiple pending forced authority set changes are not allowed.")] + #[error("Multiple pending forced authority set changes are not allowed.")] MultiplePendingForcedAuthoritySetChanges, - #[display( - fmt = "A pending forced authority set change could not be applied since it must be applied \ - after the pending standard change at #{}", - _0 + #[error( + "A pending forced authority set change could not be applied since it must be applied \ + after the pending standard change at #{0}" )] ForcedAuthoritySetChangeDependencyUnsatisfied(N), - #[display(fmt = "Invalid operation in the pending changes tree: {}", _0)] + #[error("Invalid operation in the pending changes tree: {0}")] ForkTree(fork_tree::Error), } @@ -168,7 +167,7 @@ pub struct AuthoritySet { /// Track at which blocks the set id changed. This is useful when we need to prove finality for /// a given block since we can figure out what set the block belongs to and when the set /// started/ended. - authority_set_changes: AuthoritySetChanges, + pub(crate) authority_set_changes: AuthoritySetChanges, } impl AuthoritySet @@ -221,6 +220,37 @@ where pub(crate) fn current(&self) -> (u64, &[(AuthorityId, u64)]) { (self.set_id, &self.current_authorities[..]) } + + /// Revert to a specified block given its `hash` and `number`. + /// This removes all the authority set changes that were announced after + /// the revert point. + /// Revert point is identified by `number` and `hash`. + pub(crate) fn revert(&mut self, hash: H, number: N, is_descendent_of: &F) + where + F: Fn(&H, &H) -> Result, + { + let filter = |node_hash: &H, node_num: &N, _: &PendingChange| { + if number >= *node_num && + (is_descendent_of(node_hash, &hash).unwrap_or_default() || *node_hash == hash) + { + // Continue the search in this subtree. + FilterAction::KeepNode + } else if number < *node_num && is_descendent_of(&hash, node_hash).unwrap_or_default() { + // Found a node to be removed. + FilterAction::Remove + } else { + // Not a parent or child of the one we're looking for, stop processing this branch. + FilterAction::KeepTree + } + }; + + // Remove standard changes. + let _ = self.pending_standard_changes.drain_filter(&filter); + + // Remove forced changes. + self.pending_forced_changes + .retain(|change| !is_descendent_of(&hash, &change.canon_hash).unwrap_or_default()); + } } impl AuthoritySet @@ -527,8 +557,7 @@ where fork_tree::FinalizationResult::Changed(change) => { status.changed = true; - let pending_forced_changes = - std::mem::replace(&mut self.pending_forced_changes, Vec::new()); + let pending_forced_changes = std::mem::take(&mut self.pending_forced_changes); // we will keep all forced changes for any later blocks and that are a // descendent of the finalized block (i.e. they are part of this branch). @@ -714,6 +743,17 @@ impl AuthoritySetChanges { } } + pub(crate) fn insert(&mut self, block_number: N) { + let idx = self + .0 + .binary_search_by_key(&block_number, |(_, n)| n.clone()) + .unwrap_or_else(|b| b); + + let set_id = if idx == 0 { 0 } else { self.0[idx - 1].0 + 1 }; + assert!(idx == self.0.len() || self.0[idx].0 != set_id); + self.0.insert(idx, (set_id, block_number)); + } + /// Returns an iterator over all historical authority set changes starting at the given block /// number (excluded). The iterator yields a tuple representing the set id and the block number /// of the last block in that set. @@ -742,7 +782,7 @@ impl AuthoritySetChanges { #[cfg(test)] mod tests { use super::*; - use sp_core::crypto::Public; + use sp_core::crypto::{ByteArray, UncheckedFrom}; fn static_is_descendent_of(value: bool) -> impl Fn(&A, &A) -> Result { move |_, _| Ok(value) @@ -757,7 +797,7 @@ mod tests { #[test] fn current_limit_filters_min() { - let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]), 1)]; + let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 1)]; let mut authorities = AuthoritySet { current_authorities: current_authorities.clone(), @@ -791,7 +831,7 @@ mod tests { #[test] fn changes_iterated_in_pre_order() { - let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]), 1)]; + let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 1)]; let mut authorities = AuthoritySet { current_authorities: current_authorities.clone(), @@ -883,8 +923,8 @@ mod tests { authority_set_changes: AuthoritySetChanges::empty(), }; - let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 5)]; - let set_b = vec![(AuthorityId::from_slice(&[2; 32]), 5)]; + let set_a = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 5)]; + let set_b = vec![(AuthorityId::from_slice(&[2; 32]).unwrap(), 5)]; // two competing changes at the same height on different forks let change_a = PendingChange { @@ -966,8 +1006,8 @@ mod tests { authority_set_changes: AuthoritySetChanges::empty(), }; - let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 5)]; - let set_c = vec![(AuthorityId::from_slice(&[2; 32]), 5)]; + let set_a = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 5)]; + let set_c = vec![(AuthorityId::from_slice(&[2; 32]).unwrap(), 5)]; // two competing changes at the same height on different forks let change_a = PendingChange { @@ -1046,7 +1086,7 @@ mod tests { authority_set_changes: AuthoritySetChanges::empty(), }; - let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 5)]; + let set_a = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 5)]; let change_a = PendingChange { next_authorities: set_a.clone(), @@ -1117,8 +1157,8 @@ mod tests { authority_set_changes: AuthoritySetChanges::empty(), }; - let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 5)]; - let set_b = vec![(AuthorityId::from_slice(&[2; 32]), 5)]; + let set_a = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 5)]; + let set_b = vec![(AuthorityId::from_slice(&[2; 32]).unwrap(), 5)]; let change_a = PendingChange { next_authorities: set_a.clone(), @@ -1217,7 +1257,7 @@ mod tests { authority_set_changes: AuthoritySetChanges::empty(), }; - let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 5)]; + let set_a = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 5)]; // we create a forced change with no delay let change_a = PendingChange { @@ -1242,7 +1282,7 @@ mod tests { #[test] fn forced_changes_blocked_by_standard_changes() { - let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 1)]; + let set_a = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 1)]; let mut authorities = AuthoritySet { current_authorities: set_a.clone(), @@ -1367,7 +1407,7 @@ mod tests { #[test] fn next_change_works() { - let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]), 1)]; + let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 1)]; let mut authorities = AuthoritySet { current_authorities: current_authorities.clone(), @@ -1482,8 +1522,10 @@ mod tests { None, ); - let invalid_authorities_weight = - vec![(AuthorityId::from_slice(&[1; 32]), 5), (AuthorityId::from_slice(&[2; 32]), 0)]; + let invalid_authorities_weight = vec![ + (AuthorityId::from_slice(&[1; 32]).unwrap(), 5), + (AuthorityId::from_slice(&[2; 32]).unwrap(), 0), + ]; // authority weight of zero is invalid assert_eq!(AuthoritySet::<(), ()>::genesis(invalid_authorities_weight.clone()), None); @@ -1499,7 +1541,8 @@ mod tests { ); let mut authority_set = - AuthoritySet::<(), u64>::genesis(vec![(AuthorityId::from_slice(&[1; 32]), 5)]).unwrap(); + AuthoritySet::<(), u64>::genesis(vec![(AuthorityId::unchecked_from([1; 32]), 5)]) + .unwrap(); let invalid_change_empty_authorities = PendingChange { next_authorities: vec![], @@ -1539,7 +1582,7 @@ mod tests { #[test] fn cleans_up_stale_forced_changes_when_applying_standard_change() { - let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]), 1)]; + let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 1)]; let mut authorities = AuthoritySet { current_authorities: current_authorities.clone(), @@ -1632,6 +1675,18 @@ mod tests { assert_eq!(authorities.pending_forced_changes.first().unwrap().canon_hash, "D"); } + #[test] + fn authority_set_changes_insert() { + let mut authority_set_changes = AuthoritySetChanges::empty(); + authority_set_changes.append(0, 41); + authority_set_changes.append(1, 81); + authority_set_changes.append(4, 121); + + authority_set_changes.insert(101); + assert_eq!(authority_set_changes.get_set_id(100), AuthoritySetChangeId::Set(2, 101)); + assert_eq!(authority_set_changes.get_set_id(101), AuthoritySetChangeId::Set(2, 101)); + } + #[test] fn authority_set_changes_for_complete_data() { let mut authority_set_changes = AuthoritySetChanges::empty(); diff --git a/client/finality-grandpa/src/aux_schema.rs b/client/finality-grandpa/src/aux_schema.rs index bad01e6dfc62f..235453ea35df1 100644 --- a/client/finality-grandpa/src/aux_schema.rs +++ b/client/finality-grandpa/src/aux_schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -100,7 +100,7 @@ where // previously we only supported at most one pending change per fork &|_, _| Ok(false), ) { - warn!(target: "afg", "Error migrating pending authority set change: {:?}.", err); + warn!(target: "afg", "Error migrating pending authority set change: {}", err); warn!(target: "afg", "Node is in a potentially inconsistent state."); } } @@ -191,7 +191,7 @@ where "state is for completed round; completed rounds must have a prevote ghost; qed.", ); - let mut current_rounds = CurrentRounds::new(); + let mut current_rounds = CurrentRounds::::new(); current_rounds.insert(last_round_number + 1, HasVoted::No); let set_state = VoterSetState::Live { @@ -255,7 +255,7 @@ where let base = set_state.prevote_ghost .expect("state is for completed round; completed rounds must have a prevote ghost; qed."); - let mut current_rounds = CurrentRounds::new(); + let mut current_rounds = CurrentRounds::::new(); current_rounds.insert(last_round_number + 1, HasVoted::No); VoterSetState::Live { @@ -430,7 +430,7 @@ where // reset. let set_state = VoterSetState::::live( new_set.set_id, - &set, + set, (new_set.canon_hash, new_set.canon_number), ); let encoded = set_state.encode(); @@ -498,15 +498,19 @@ pub(crate) fn load_authorities( #[cfg(test)] mod test { use super::*; - use sp_core::H256; + use sp_core::{crypto::UncheckedFrom, H256}; use sp_finality_grandpa::AuthorityId; - use substrate_test_runtime_client; + use substrate_test_runtime_client::{self, runtime::Block}; + + fn dummy_id() -> AuthorityId { + AuthorityId::unchecked_from([1; 32]) + } #[test] fn load_decode_from_v0_migrates_data_format() { let client = substrate_test_runtime_client::new(); - let authorities = vec![(AuthorityId::default(), 100)]; + let authorities = vec![(dummy_id(), 100)]; let set_id = 3; let round_number: RoundNumber = 42; let round_state = RoundState:: { @@ -570,7 +574,7 @@ mod test { .unwrap(), ); - let mut current_rounds = CurrentRounds::new(); + let mut current_rounds = CurrentRounds::::new(); current_rounds.insert(round_number + 1, HasVoted::No); assert_eq!( @@ -595,7 +599,7 @@ mod test { fn load_decode_from_v1_migrates_data_format() { let client = substrate_test_runtime_client::new(); - let authorities = vec![(AuthorityId::default(), 100)]; + let authorities = vec![(dummy_id(), 100)]; let set_id = 3; let round_number: RoundNumber = 42; let round_state = RoundState:: { @@ -663,7 +667,7 @@ mod test { .unwrap(), ); - let mut current_rounds = CurrentRounds::new(); + let mut current_rounds = CurrentRounds::::new(); current_rounds.insert(round_number + 1, HasVoted::No); assert_eq!( @@ -688,7 +692,7 @@ mod test { fn load_decode_from_v2_migrates_data_format() { let client = substrate_test_runtime_client::new(); - let authorities = vec![(AuthorityId::default(), 100)]; + let authorities = vec![(dummy_id(), 100)]; let set_id = 3; { diff --git a/client/finality-grandpa/src/communication/gossip.rs b/client/finality-grandpa/src/communication/gossip.rs index 2e50a3bac01d9..218b4b668c10f 100644 --- a/client/finality-grandpa/src/communication/gossip.rs +++ b/client/finality-grandpa/src/communication/gossip.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -35,7 +35,8 @@ //! impolite to send messages about r+1 or later. "future-round" messages can //! be dropped and ignored. //! -//! It is impolite to send a neighbor packet which moves backwards in protocol state. +//! It is impolite to send a neighbor packet which moves backwards or does not progress +//! protocol state. //! //! This is beneficial if it conveys some progress in the protocol state of the peer. //! @@ -84,23 +85,24 @@ //! //! We only send polite messages to peers, -use parity_scale_codec::{Decode, Encode}; -use sc_network::{ObservedRole, PeerId, ReputationChange}; -use sc_network_gossip::{MessageIntent, ValidatorContext}; -use sp_finality_grandpa::AuthorityId; -use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; - +use ahash::{AHashMap, AHashSet}; use log::{debug, trace}; +use parity_scale_codec::{Decode, Encode}; use prometheus_endpoint::{register, CounterVec, Opts, PrometheusError, Registry, U64}; use rand::seq::SliceRandom; +use sc_network::{PeerId, ReputationChange}; +use sc_network_common::protocol::role::ObservedRole; +use sc_network_gossip::{MessageIntent, ValidatorContext}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use sp_finality_grandpa::AuthorityId; +use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; -use super::{benefit, cost, Round, SetId}; +use super::{benefit, cost, Round, SetId, NEIGHBOR_REBROADCAST_PERIOD}; use crate::{environment, CatchUp, CompactCommit, SignedMessage}; use std::{ - collections::{HashMap, HashSet, VecDeque}, + collections::{HashSet, VecDeque}, time::{Duration, Instant}, }; @@ -147,14 +149,15 @@ enum Consider { /// A view of protocol state. #[derive(Debug)] struct View { - round: Round, // the current round we are at. - set_id: SetId, // the current voter set id. - last_commit: Option, // commit-finalized block height, if any. + round: Round, // the current round we are at. + set_id: SetId, // the current voter set id. + last_commit: Option, // commit-finalized block height, if any. + last_update: Option, // last time we heard from peer, used for spamming detection. } impl Default for View { fn default() -> Self { - View { round: Round(1), set_id: SetId(0), last_commit: None } + View { round: Round(1), set_id: SetId(0), last_commit: None, last_update: None } } } @@ -224,7 +227,12 @@ impl LocalView { /// Converts the local view to a `View` discarding round and set id /// information about the last commit. fn as_view(&self) -> View<&N> { - View { round: self.round, set_id: self.set_id, last_commit: self.last_commit_height() } + View { + round: self.round, + set_id: self.set_id, + last_commit: self.last_commit_height(), + last_update: None, + } } /// Update the set ID. implies a reset to round 1. @@ -260,7 +268,7 @@ const KEEP_RECENT_ROUNDS: usize = 3; struct KeepTopics { current_set: SetId, rounds: VecDeque<(Round, SetId)>, - reverse_map: HashMap, SetId)>, + reverse_map: AHashMap, SetId)>, } impl KeepTopics { @@ -268,7 +276,7 @@ impl KeepTopics { KeepTopics { current_set: SetId(0), rounds: VecDeque::with_capacity(KEEP_RECENT_ROUNDS + 2), - reverse_map: HashMap::new(), + reverse_map: Default::default(), } } @@ -290,7 +298,7 @@ impl KeepTopics { let _ = self.rounds.pop_front(); } - let mut map = HashMap::with_capacity(KEEP_RECENT_ROUNDS + 3); + let mut map = AHashMap::with_capacity(KEEP_RECENT_ROUNDS + 3); map.insert(super::global_topic::(self.current_set.0), (None, self.current_set)); for &(round, set) in &self.rounds { @@ -349,7 +357,7 @@ pub(super) struct VoteMessage { /// The voter set ID this message is from. pub(super) set_id: SetId, /// The message itself. - pub(super) message: SignedMessage, + pub(super) message: SignedMessage, } /// Network level commit message with topic information. @@ -360,7 +368,7 @@ pub(super) struct FullCommitMessage { /// The voter set ID this message is from. pub(super) set_id: SetId, /// The compact commit message. - pub(super) message: CompactCommit, + pub(super) message: CompactCommit, } /// V1 neighbor packet. Neighbor packets are sent from nodes to their peers @@ -405,7 +413,7 @@ pub(super) struct FullCatchUpMessage { /// The voter set ID this message is from. pub(super) set_id: SetId, /// The compact commit message. - pub(super) message: CatchUp, + pub(super) message: CatchUp, } /// Misbehavior that peers can perform. @@ -416,6 +424,8 @@ pub(super) struct FullCatchUpMessage { pub(super) enum Misbehavior { // invalid neighbor message, considering the last one. InvalidViewChange, + // duplicate neighbor message. + DuplicateNeighborMessage, // could not decode neighbor message. bytes-length of the packet. UndecodablePacket(i32), // Bad catch up message (invalid signatures). @@ -437,6 +447,7 @@ impl Misbehavior { match *self { InvalidViewChange => cost::INVALID_VIEW_CHANGE, + DuplicateNeighborMessage => cost::DUPLICATE_NEIGHBOR_MESSAGE, UndecodablePacket(bytes) => ReputationChange::new( bytes.saturating_mul(cost::PER_UNDECODABLE_BYTE), "Grandpa: Bad packet", @@ -477,40 +488,42 @@ impl PeerInfo { /// The peers we're connected to in gossip. struct Peers { - inner: HashMap>, + inner: AHashMap>, /// The randomly picked set of `LUCKY_PEERS` we'll gossip to in the first stage of round /// gossiping. - first_stage_peers: HashSet, + first_stage_peers: AHashSet, /// The randomly picked set of peers we'll gossip to in the second stage of gossiping if the /// first stage didn't allow us to spread the voting data enough to conclude the round. This /// set should have size `sqrt(connected_peers)`. second_stage_peers: HashSet, /// The randomly picked set of `LUCKY_PEERS` light clients we'll gossip commit messages to. lucky_light_peers: HashSet, + /// Neighbor packet rebroadcast period --- we reduce the reputation of peers sending duplicate + /// packets too often. + neighbor_rebroadcast_period: Duration, } -impl Default for Peers { - fn default() -> Self { +impl Peers { + fn new(neighbor_rebroadcast_period: Duration) -> Self { Peers { - inner: HashMap::new(), - first_stage_peers: HashSet::new(), - second_stage_peers: HashSet::new(), - lucky_light_peers: HashSet::new(), + inner: Default::default(), + first_stage_peers: Default::default(), + second_stage_peers: Default::default(), + lucky_light_peers: Default::default(), + neighbor_rebroadcast_period, } } -} -impl Peers { fn new_peer(&mut self, who: PeerId, role: ObservedRole) { match role { ObservedRole::Authority if self.first_stage_peers.len() < LUCKY_PEERS => { - self.first_stage_peers.insert(who.clone()); + self.first_stage_peers.insert(who); }, ObservedRole::Authority if self.second_stage_peers.len() < LUCKY_PEERS => { - self.second_stage_peers.insert(who.clone()); + self.second_stage_peers.insert(who); }, ObservedRole::Light if self.lucky_light_peers.len() < LUCKY_PEERS => { - self.lucky_light_peers.insert(who.clone()); + self.lucky_light_peers.insert(who); }, _ => {}, } @@ -546,10 +559,23 @@ impl Peers { return Err(Misbehavior::InvalidViewChange) } + let now = Instant::now(); + let duplicate_packet = (update.set_id, update.round, Some(&update.commit_finalized_height)) == + (peer.view.set_id, peer.view.round, peer.view.last_commit.as_ref()); + + if duplicate_packet { + if let Some(last_update) = peer.view.last_update { + if now < last_update + self.neighbor_rebroadcast_period / 2 { + return Err(Misbehavior::DuplicateNeighborMessage) + } + } + } + peer.view = View { round: update.round, set_id: update.set_id, last_commit: Some(update.commit_finalized_height), + last_update: Some(now), }; trace!(target: "afg", "Peer {} updated view. Now at {:?}, {:?}", @@ -590,11 +616,8 @@ impl Peers { // - third set: LUCKY_PEERS random light client peers let shuffled_peers = { - let mut peers = self - .inner - .iter() - .map(|(peer_id, info)| (*peer_id, info.clone())) - .collect::>(); + let mut peers = + self.inner.iter().map(|(peer_id, info)| (*peer_id, info)).collect::>(); peers.shuffle(&mut rand::thread_rng()); peers @@ -608,15 +631,14 @@ impl Peers { } }); - let mut first_stage_peers = HashSet::new(); + let mut first_stage_peers = AHashSet::new(); let mut second_stage_peers = HashSet::new(); // we start by allocating authorities to the first stage set and when the minimum of // `LUCKY_PEERS / 2` is filled we start allocating to the second stage set. let half_lucky = LUCKY_PEERS / 2; let one_and_a_half_lucky = LUCKY_PEERS + half_lucky; - let mut n_authorities_added = 0; - for peer_id in shuffled_authorities { + for (n_authorities_added, peer_id) in shuffled_authorities.enumerate() { if n_authorities_added < half_lucky { first_stage_peers.insert(*peer_id); } else if n_authorities_added < one_and_a_half_lucky { @@ -624,8 +646,6 @@ impl Peers { } else { break } - - n_authorities_added += 1; } // fill up first and second sets with remaining peers (either full or authorities) @@ -734,11 +754,7 @@ type MaybeMessage = Option<(Vec, NeighborPacket> impl Inner { fn new(config: crate::Config) -> Self { - let catch_up_config = if config.local_role.is_light() { - // if we are a light client we shouldn't be issuing any catch-up requests - // as we don't participate in the full GRANDPA protocol - CatchUpConfig::disabled() - } else if config.observer_enabled { + let catch_up_config = if config.observer_enabled { if config.local_role.is_authority() { // since the observer protocol is enabled, we will only issue // catch-up requests if we are an authority (and only to other @@ -757,7 +773,7 @@ impl Inner { Inner { local_view: None, - peers: Peers::default(), + peers: Peers::new(NEIGHBOR_REBROADCAST_PERIOD), live_topics: KeepTopics::new(), next_rebroadcast: Instant::now() + REBROADCAST_AFTER, authorities: Vec::new(), @@ -767,13 +783,16 @@ impl Inner { } } - /// Note a round in the current set has started. + /// Note a round in the current set has started. Does nothing if the last + /// call to the function was with the same `round`. fn note_round(&mut self, round: Round) -> MaybeMessage { { let local_view = match self.local_view { None => return None, Some(ref mut v) => if v.round == round { + // Do not send neighbor packets out if `round` has not changed --- + // such behavior is punishable. return None } else { v @@ -802,7 +821,7 @@ impl Inner { Some(ref mut v) => if v.set_id == set_id { let diff_authorities = self.authorities.iter().collect::>() != - authorities.iter().collect(); + authorities.iter().collect::>(); if diff_authorities { debug!(target: "afg", @@ -812,6 +831,8 @@ impl Inner { ); self.authorities = authorities; } + // Do not send neighbor packets out if the `set_id` has not changed --- + // such behavior is punishable. return None } else { v @@ -825,7 +846,9 @@ impl Inner { self.multicast_neighbor_packet() } - /// Note that we've imported a commit finalizing a given block. + /// Note that we've imported a commit finalizing a given block. Does nothing if the last + /// call to the function was with the same or higher `finalized` number. + /// `set_id` & `round` are the ones the commit message is from. fn note_commit_finalized( &mut self, round: Round, @@ -1080,7 +1103,7 @@ impl Inner { let (base_hash, base_number) = last_completed_round.base; - let catch_up = CatchUp:: { + let catch_up = CatchUp:: { round_number: last_completed_round.number, prevotes, precommits, @@ -1106,7 +1129,7 @@ impl Inner { // won't be able to reply since they don't follow the full GRANDPA // protocol and therefore might not have the vote data available. if let (Some(peer), Some(local_view)) = (self.peers.peer(who), &self.local_view) { - if self.catch_up_config.request_allowed(&peer) && + if self.catch_up_config.request_allowed(peer) && peer.view.set_id == local_view.set_id && peer.view.round.0.saturating_sub(CATCH_UP_THRESHOLD) > local_view.round.0 { @@ -1198,7 +1221,7 @@ impl Inner { return (false, None) } else { // report peer for timeout - Some((peer.clone(), cost::CATCH_UP_REQUEST_TIMEOUT)) + Some((*peer, cost::CATCH_UP_REQUEST_TIMEOUT)) } }, PendingCatchUp::Processing { instant, .. } => { @@ -1212,7 +1235,7 @@ impl Inner { }; self.pending_catch_up = PendingCatchUp::Requesting { - who: who.clone(), + who: *who, request: catch_up_request.clone(), instant: Instant::now(), }; @@ -1237,10 +1260,6 @@ impl Inner { None => return false, }; - if self.config.local_role.is_light() { - return false - } - if round_elapsed < round_duration.mul_f32(PROPAGATION_SOME) { self.peers.first_stage_peers.contains(who) } else if round_elapsed < round_duration.mul_f32(PROPAGATION_ALL) { @@ -1272,10 +1291,6 @@ impl Inner { None => return false, }; - if self.config.local_role.is_light() { - return false - } - if round_elapsed < round_duration.mul_f32(PROPAGATION_ALL) { self.peers.first_stage_peers.contains(who) || self.peers.second_stage_peers.contains(who) || @@ -1299,7 +1314,7 @@ impl Metrics { messages_validated: register( CounterVec::new( Opts::new( - "finality_grandpa_communication_gossip_validator_messages", + "substrate_finality_grandpa_communication_gossip_validator_messages", "Number of messages validated by the finality grandpa gossip validator.", ), &["message", "action"], @@ -1374,6 +1389,8 @@ impl GossipValidator { } /// Note that we've imported a commit finalizing a given block. + /// `set_id` & `round` are the ones the commit message is from and not necessarily + /// the latest set ID & round started. pub(super) fn note_commit_finalized( &self, round: Round, @@ -1491,7 +1508,7 @@ impl sc_network_gossip::Validator for GossipValidator sc_network_gossip::Validator for GossipValidator { - self.report(who.clone(), cb); + self.report(*who, cb); context.broadcast_message(topic, data.to_vec(), false); sc_network_gossip::ValidationResult::ProcessAndKeep(topic) }, Action::ProcessAndDiscard(topic, cb) => { - self.report(who.clone(), cb); + self.report(*who, cb); sc_network_gossip::ValidationResult::ProcessAndDiscard(topic) }, Action::Discard(cb) => { - self.report(who.clone(), cb); + self.report(*who, cb); sc_network_gossip::ValidationResult::Discard }, } @@ -1575,7 +1592,7 @@ impl sc_network_gossip::Validator for GossipValidator return false, Some(x) => x, }; @@ -1586,11 +1603,9 @@ impl sc_network_gossip::Validator for GossipValidator crate::Config { @@ -1682,6 +1699,7 @@ mod tests { local_role: Role::Authority, observer_enabled: true, telemetry: None, + protocol_name: communication::grandpa_protocol_name::NAME.into(), } } @@ -1691,7 +1709,7 @@ mod tests { let base = (H256::zero(), 0); - let voters = vec![(AuthorityId::from_slice(&[1; 32]), 1)]; + let voters = vec![(AuthorityId::unchecked_from([1; 32]), 1)]; let voters = AuthoritySet::genesis(voters).unwrap(); let set_state = VoterSetState::live(0, &voters, base); @@ -1701,7 +1719,12 @@ mod tests { #[test] fn view_vote_rules() { - let view = View { round: Round(100), set_id: SetId(1), last_commit: Some(1000u64) }; + let view = View { + round: Round(100), + set_id: SetId(1), + last_commit: Some(1000u64), + last_update: None, + }; assert_eq!(view.consider_vote(Round(98), SetId(1)), Consider::RejectPast); assert_eq!(view.consider_vote(Round(1), SetId(0)), Consider::RejectPast); @@ -1718,7 +1741,12 @@ mod tests { #[test] fn view_global_message_rules() { - let view = View { round: Round(100), set_id: SetId(2), last_commit: Some(1000u64) }; + let view = View { + round: Round(100), + set_id: SetId(2), + last_commit: Some(1000u64), + last_update: None, + }; assert_eq!(view.consider_global(SetId(3), 1), Consider::RejectFuture); assert_eq!(view.consider_global(SetId(3), 1000), Consider::RejectFuture); @@ -1736,7 +1764,7 @@ mod tests { #[test] fn unknown_peer_cannot_be_updated() { - let mut peers = Peers::default(); + let mut peers = Peers::new(NEIGHBOR_REBROADCAST_PERIOD); let id = PeerId::random(); let update = @@ -1746,7 +1774,7 @@ mod tests { assert!(res.unwrap().is_none()); // connect & disconnect. - peers.new_peer(id.clone(), ObservedRole::Authority); + peers.new_peer(id, ObservedRole::Authority); peers.peer_disconnected(&id); let res = peers.update_peer_state(&id, update.clone()); @@ -1767,30 +1795,38 @@ mod tests { let update4 = NeighborPacket { round: Round(3), set_id: SetId(11), commit_finalized_height: 80 }; - let mut peers = Peers::default(); + // Use shorter rebroadcast period to safely roll the clock back in the last test + // and don't hit the system boot time on systems with unsigned time. + const SHORT_NEIGHBOR_REBROADCAST_PERIOD: Duration = Duration::from_secs(1); + let mut peers = Peers::new(SHORT_NEIGHBOR_REBROADCAST_PERIOD); let id = PeerId::random(); - peers.new_peer(id.clone(), ObservedRole::Authority); + peers.new_peer(id, ObservedRole::Authority); - let mut check_update = move |update: NeighborPacket<_>| { + let check_update = |peers: &mut Peers<_>, update: NeighborPacket<_>| { let view = peers.update_peer_state(&id, update.clone()).unwrap().unwrap(); assert_eq!(view.round, update.round); assert_eq!(view.set_id, update.set_id); assert_eq!(view.last_commit, Some(update.commit_finalized_height)); }; - check_update(update1); - check_update(update2); - check_update(update3); - check_update(update4); + check_update(&mut peers, update1); + check_update(&mut peers, update2); + check_update(&mut peers, update3); + check_update(&mut peers, update4.clone()); + + // Allow duplicate neighbor packets if enough time has passed. + peers.inner.get_mut(&id).unwrap().view.last_update = + Some(Instant::now() - SHORT_NEIGHBOR_REBROADCAST_PERIOD); + check_update(&mut peers, update4); } #[test] fn invalid_view_change() { - let mut peers = Peers::default(); + let mut peers = Peers::new(NEIGHBOR_REBROADCAST_PERIOD); let id = PeerId::random(); - peers.new_peer(id.clone(), ObservedRole::Authority); + peers.new_peer(id, ObservedRole::Authority); peers .update_peer_state( @@ -1800,29 +1836,41 @@ mod tests { .unwrap() .unwrap(); - let mut check_update = move |update: NeighborPacket<_>| { + let mut check_update = move |update: NeighborPacket<_>, misbehavior| { let err = peers.update_peer_state(&id, update.clone()).unwrap_err(); - assert_eq!(err, Misbehavior::InvalidViewChange); + assert_eq!(err, misbehavior); }; // round moves backwards. - check_update(NeighborPacket { - round: Round(9), - set_id: SetId(10), - commit_finalized_height: 10, - }); - // commit finalized height moves backwards. - check_update(NeighborPacket { - round: Round(10), - set_id: SetId(10), - commit_finalized_height: 9, - }); + check_update( + NeighborPacket { round: Round(9), set_id: SetId(10), commit_finalized_height: 10 }, + Misbehavior::InvalidViewChange, + ); // set ID moves backwards. - check_update(NeighborPacket { - round: Round(10), - set_id: SetId(9), - commit_finalized_height: 10, - }); + check_update( + NeighborPacket { round: Round(10), set_id: SetId(9), commit_finalized_height: 10 }, + Misbehavior::InvalidViewChange, + ); + // commit finalized height moves backwards. + check_update( + NeighborPacket { round: Round(10), set_id: SetId(10), commit_finalized_height: 9 }, + Misbehavior::InvalidViewChange, + ); + // duplicate packet without grace period. + check_update( + NeighborPacket { round: Round(10), set_id: SetId(10), commit_finalized_height: 10 }, + Misbehavior::DuplicateNeighborMessage, + ); + // commit finalized height moves backwards while round moves forward. + check_update( + NeighborPacket { round: Round(11), set_id: SetId(10), commit_finalized_height: 9 }, + Misbehavior::InvalidViewChange, + ); + // commit finalized height moves backwards while set ID moves forward. + check_update( + NeighborPacket { round: Round(10), set_id: SetId(11), commit_finalized_height: 9 }, + Misbehavior::InvalidViewChange, + ); } #[test] @@ -1843,13 +1891,13 @@ mod tests { // messages from old rounds are expired. for round_num in 1u64..last_kept_round { - let topic = crate::communication::round_topic::(round_num, 1); + let topic = communication::round_topic::(round_num, 1); assert!(is_expired(topic, &[1, 2, 3])); } // messages from not-too-old rounds are not expired. for round_num in last_kept_round..10 { - let topic = crate::communication::round_topic::(round_num, 1); + let topic = communication::round_topic::(round_num, 1); assert!(!is_expired(topic, &[1, 2, 3])); } } @@ -1861,7 +1909,7 @@ mod tests { let (val, _) = GossipValidator::::new(config(), voter_set_state(), None, None); let set_id = 1; - let auth = AuthorityId::from_slice(&[1u8; 32]); + let auth = AuthorityId::unchecked_from([1u8; 32]); let peer = PeerId::random(); val.note_set(SetId(set_id), vec![auth.clone()], |_, _| {}); @@ -1873,13 +1921,13 @@ mod tests { &VoteMessage { round: Round(1), set_id: SetId(set_id), - message: SignedMessage:: { + message: SignedMessage::
{ message: finality_grandpa::Message::Prevote(finality_grandpa::Prevote { target_hash: Default::default(), target_number: 10, }), - signature: Default::default(), - id: AuthorityId::from_slice(&[2u8; 32]), + signature: UncheckedFrom::unchecked_from([1; 64]), + id: UncheckedFrom::unchecked_from([2u8; 32]), }, }, ); @@ -1889,12 +1937,12 @@ mod tests { &VoteMessage { round: Round(1), set_id: SetId(set_id), - message: SignedMessage:: { + message: SignedMessage::
{ message: finality_grandpa::Message::Prevote(finality_grandpa::Prevote { target_hash: Default::default(), target_number: 10, }), - signature: Default::default(), + signature: UncheckedFrom::unchecked_from([1; 64]), id: auth.clone(), }, }, @@ -1909,7 +1957,7 @@ mod tests { let (val, _) = GossipValidator::::new(config(), voter_set_state(), None, None); let set_id = 1; - let auth = AuthorityId::from_slice(&[1u8; 32]); + let auth = AuthorityId::unchecked_from([1u8; 32]); let peer = PeerId::random(); val.note_set(SetId(set_id), vec![auth.clone()], |_, _| {}); @@ -1960,7 +2008,7 @@ mod tests { votes: Default::default(), }); - let mut current_rounds = environment::CurrentRounds::new(); + let mut current_rounds = environment::CurrentRounds::::new(); current_rounds.insert(3, environment::HasVoted::No); let set_state = @@ -1972,7 +2020,7 @@ mod tests { let (val, _) = GossipValidator::::new(config(), set_state.clone(), None, None); let set_id = 1; - let auth = AuthorityId::from_slice(&[1u8; 32]); + let auth = AuthorityId::unchecked_from([1u8; 32]); let peer = PeerId::random(); val.note_set(SetId(set_id), vec![auth.clone()], |_, _| {}); @@ -1981,7 +2029,7 @@ mod tests { // add the peer making the request to the validator, // otherwise it is discarded let mut inner = val.inner.write(); - inner.peers.new_peer(peer.clone(), ObservedRole::Authority); + inner.peers.new_peer(peer, ObservedRole::Authority); let res = inner.handle_catch_up_request( &peer, @@ -2022,7 +2070,7 @@ mod tests { // add the peer making the request to the validator, // otherwise it is discarded let peer = PeerId::random(); - val.inner.write().peers.new_peer(peer.clone(), ObservedRole::Authority); + val.inner.write().peers.new_peer(peer, ObservedRole::Authority); let send_request = |set_id, round| { let mut inner = val.inner.write(); @@ -2077,7 +2125,7 @@ mod tests { // add the peer making the request to the validator, // otherwise it is discarded. let peer = PeerId::random(); - val.inner.write().peers.new_peer(peer.clone(), ObservedRole::Authority); + val.inner.write().peers.new_peer(peer, ObservedRole::Authority); let import_neighbor_message = |set_id, round| { let (_, _, catch_up_request, _) = val.inner.write().import_neighbor_message( @@ -2147,7 +2195,7 @@ mod tests { // add the peer making the request to the validator, // otherwise it is discarded. let peer = PeerId::random(); - val.inner.write().peers.new_peer(peer.clone(), ObservedRole::Authority); + val.inner.write().peers.new_peer(peer, ObservedRole::Authority); // importing a neighbor message from a peer in the same set in a later // round should lead to a catch up request but since they're disabled @@ -2175,11 +2223,8 @@ mod tests { let peer_authority = PeerId::random(); let peer_full = PeerId::random(); - val.inner - .write() - .peers - .new_peer(peer_authority.clone(), ObservedRole::Authority); - val.inner.write().peers.new_peer(peer_full.clone(), ObservedRole::Full); + val.inner.write().peers.new_peer(peer_authority, ObservedRole::Authority); + val.inner.write().peers.new_peer(peer_full, ObservedRole::Full); let import_neighbor_message = |peer| { let (_, _, catch_up_request, _) = val.inner.write().import_neighbor_message( @@ -2228,7 +2273,7 @@ mod tests { // add the peer making the requests to the validator, otherwise it is // discarded. let peer_full = PeerId::random(); - val.inner.write().peers.new_peer(peer_full.clone(), ObservedRole::Full); + val.inner.write().peers.new_peer(peer_full, ObservedRole::Full); let (_, _, catch_up_request, _) = val.inner.write().import_neighbor_message( &peer_full, @@ -2265,7 +2310,7 @@ mod tests { // we accept messages from rounds 9, 10 and 11 // therefore neither of those should be considered expired for round in &[9, 10, 11] { - assert!(!is_expired(crate::communication::round_topic::(*round, 1), &[])) + assert!(!is_expired(communication::round_topic::(*round, 1), &[])) } } @@ -2288,12 +2333,9 @@ mod tests { full_nodes.resize_with(30, || PeerId::random()); for i in 0..30 { - val.inner - .write() - .peers - .new_peer(authorities[i].clone(), ObservedRole::Authority); + val.inner.write().peers.new_peer(authorities[i], ObservedRole::Authority); - val.inner.write().peers.new_peer(full_nodes[i].clone(), ObservedRole::Full); + val.inner.write().peers.new_peer(full_nodes[i], ObservedRole::Full); } let test = |rounds_elapsed, peers| { @@ -2313,7 +2355,7 @@ mod tests { if message_allowed( peer, MessageIntent::Broadcast, - &crate::communication::round_topic::(1, 0), + &communication::round_topic::(1, 0), &[], ) { allowed += 1; @@ -2372,12 +2414,12 @@ mod tests { // add a new light client as peer let light_peer = PeerId::random(); - val.inner.write().peers.new_peer(light_peer.clone(), ObservedRole::Light); + val.inner.write().peers.new_peer(light_peer, ObservedRole::Light); assert!(!val.message_allowed()( &light_peer, MessageIntent::Broadcast, - &crate::communication::round_topic::(1, 0), + &communication::round_topic::(1, 0), &[], )); @@ -2391,7 +2433,7 @@ mod tests { assert!(!val.message_allowed()( &light_peer, MessageIntent::Broadcast, - &crate::communication::round_topic::(1, 0), + &communication::round_topic::(1, 0), &[], )); @@ -2415,8 +2457,8 @@ mod tests { auth_data: Vec::new(), }; - crate::communication::gossip::GossipMessage::::Commit( - crate::communication::gossip::FullCommitMessage { + communication::gossip::GossipMessage::::Commit( + communication::gossip::FullCommitMessage { round: Round(2), set_id: SetId(0), message: commit, @@ -2429,7 +2471,7 @@ mod tests { assert!(val.message_allowed()( &light_peer, MessageIntent::Broadcast, - &crate::communication::global_topic::(0), + &communication::global_topic::(0), &commit, )); } @@ -2444,7 +2486,7 @@ mod tests { // add a new peer at set id 1 let peer1 = PeerId::random(); - val.inner.write().peers.new_peer(peer1.clone(), ObservedRole::Authority); + val.inner.write().peers.new_peer(peer1, ObservedRole::Authority); val.inner .write() @@ -2457,7 +2499,7 @@ mod tests { // peer2 will default to set id 0 let peer2 = PeerId::random(); - val.inner.write().peers.new_peer(peer2.clone(), ObservedRole::Authority); + val.inner.write().peers.new_peer(peer2, ObservedRole::Authority); // create a commit for round 1 of set id 1 // targeting a block at height 2 @@ -2469,8 +2511,8 @@ mod tests { auth_data: Vec::new(), }; - crate::communication::gossip::GossipMessage::::Commit( - crate::communication::gossip::FullCommitMessage { + communication::gossip::GossipMessage::::Commit( + communication::gossip::FullCommitMessage { round: Round(1), set_id: SetId(1), message: commit, @@ -2488,7 +2530,7 @@ mod tests { assert!(message_allowed( &peer1, MessageIntent::Broadcast, - &crate::communication::global_topic::(1), + &communication::global_topic::(1), &commit, )); @@ -2497,7 +2539,7 @@ mod tests { assert!(!message_allowed( &peer2, MessageIntent::Broadcast, - &crate::communication::global_topic::(1), + &communication::global_topic::(1), &commit, )); } @@ -2514,8 +2556,8 @@ mod tests { auth_data: Vec::new(), }; - crate::communication::gossip::GossipMessage::::Commit( - crate::communication::gossip::FullCommitMessage { + communication::gossip::GossipMessage::::Commit( + communication::gossip::FullCommitMessage { round: Round(round), set_id: SetId(set_id), message: commit, @@ -2535,27 +2577,26 @@ mod tests { // a commit message for round 1 that finalizes the same height as we // have observed previously should not be expired - assert!( - !message_expired(crate::communication::global_topic::(1), &commit(1, 1, 2),) - ); + assert!(!message_expired(communication::global_topic::(1), &commit(1, 1, 2),)); // it should be expired if it is for a lower block - assert!(message_expired(crate::communication::global_topic::(1), &commit(1, 1, 1))); + assert!(message_expired(communication::global_topic::(1), &commit(1, 1, 1))); // or the same block height but from the previous round - assert!(message_expired(crate::communication::global_topic::(1), &commit(0, 1, 2))); + assert!(message_expired(communication::global_topic::(1), &commit(0, 1, 2))); } #[test] fn allow_noting_different_authorities_for_same_set() { let (val, _) = GossipValidator::::new(config(), voter_set_state(), None, None); - let a1 = vec![AuthorityId::from_slice(&[0; 32])]; + let a1 = vec![UncheckedFrom::unchecked_from([0; 32])]; val.note_set(SetId(1), a1.clone(), |_, _| {}); assert_eq!(val.inner().read().authorities, a1); - let a2 = vec![AuthorityId::from_slice(&[1; 32]), AuthorityId::from_slice(&[2; 32])]; + let a2 = + vec![UncheckedFrom::unchecked_from([1; 32]), UncheckedFrom::unchecked_from([2; 32])]; val.note_set(SetId(1), a2.clone(), |_, _| {}); assert_eq!(val.inner().read().authorities, a2); diff --git a/client/finality-grandpa/src/communication/mod.rs b/client/finality-grandpa/src/communication/mod.rs index c370e1d642d7d..75a7697812c6c 100644 --- a/client/finality-grandpa/src/communication/mod.rs +++ b/client/finality-grandpa/src/communication/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -37,6 +37,7 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, + time::Duration, }; use finality_grandpa::{ @@ -45,7 +46,7 @@ use finality_grandpa::{ Message::{Precommit, Prevote, PrimaryPropose}, }; use parity_scale_codec::{Decode, Encode}; -use sc_network::{NetworkService, ReputationChange}; +use sc_network::ReputationChange; use sc_network_gossip::{GossipEngine, Network as GossipNetwork}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO}; use sp_keystore::SyncCryptoStorePtr; @@ -58,6 +59,7 @@ use crate::{ use gossip::{ FullCatchUpMessage, FullCommitMessage, GossipMessage, GossipValidator, PeerReport, VoteMessage, }; +use sc_network_common::service::{NetworkBlock, NetworkSyncForkRequest}; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_finality_grandpa::{AuthorityId, AuthoritySignature, RoundNumber, SetId as SetIdNumber}; @@ -67,9 +69,32 @@ mod periodic; #[cfg(test)] pub(crate) mod tests; -/// Name of the notifications protocol used by Grandpa. Must be registered towards the networking -/// in order for Grandpa to properly function. -pub const GRANDPA_PROTOCOL_NAME: &'static str = "/paritytech/grandpa/1"; +// How often to rebroadcast neighbor packets, in cases where no new packets are created. +pub(crate) const NEIGHBOR_REBROADCAST_PERIOD: Duration = Duration::from_secs(2 * 60); + +pub mod grandpa_protocol_name { + use sc_chain_spec::ChainSpec; + use sc_network_common::protocol::ProtocolName; + + pub(crate) const NAME: &str = "/grandpa/1"; + /// Old names for the notifications protocol, used for backward compatibility. + pub(crate) const LEGACY_NAMES: [&str; 1] = ["/paritytech/grandpa/1"]; + + /// Name of the notifications protocol used by GRANDPA. + /// + /// Must be registered towards the networking in order for GRANDPA to properly function. + pub fn standard_name>( + genesis_hash: &Hash, + chain_spec: &Box, + ) -> ProtocolName { + let genesis_hash = genesis_hash.as_ref(); + let chain_prefix = match chain_spec.fork_id() { + Some(fork_id) => format!("/{}/{}", array_bytes::bytes2hex("", genesis_hash), fork_id), + None => format!("/{}", array_bytes::bytes2hex("", genesis_hash)), + }; + format!("{}{}", chain_prefix, NAME).into() + } +} // cost scalars for reporting peers. mod cost { @@ -82,6 +107,8 @@ mod cost { pub(super) const UNKNOWN_VOTER: Rep = Rep::new(-150, "Grandpa: Unknown voter"); pub(super) const INVALID_VIEW_CHANGE: Rep = Rep::new(-500, "Grandpa: Invalid view change"); + pub(super) const DUPLICATE_NEIGHBOR_MESSAGE: Rep = + Rep::new(-500, "Grandpa: Duplicate neighbor message without grace period"); pub(super) const PER_UNDECODABLE_BYTE: i32 = -5; pub(super) const PER_SIGNATURE_CHECKED: i32 = -25; pub(super) const PER_BLOCK_LOADED: i32 = -10; @@ -138,34 +165,26 @@ const TELEMETRY_VOTERS_LIMIT: usize = 10; /// /// Something that provides both the capabilities needed for the `gossip_network::Network` trait as /// well as the ability to set a fork sync request for a particular block. -pub trait Network: GossipNetwork + Clone + Send + 'static { - /// Notifies the sync service to try and sync the given block from the given - /// peers. - /// - /// If the given vector of peers is empty then the underlying implementation - /// should make a best effort to fetch the block from any peers it is - /// connected to (NOTE: this assumption will change in the future #3629). - fn set_sync_fork_request( - &self, - peers: Vec, - hash: Block::Hash, - number: NumberFor, - ); +pub trait Network: + NetworkSyncForkRequest> + + NetworkBlock> + + GossipNetwork + + Clone + + Send + + 'static +{ } -impl Network for Arc> +impl Network for T where - B: BlockT, - H: sc_network::ExHashT, + Block: BlockT, + T: NetworkSyncForkRequest> + + NetworkBlock> + + GossipNetwork + + Clone + + Send + + 'static, { - fn set_sync_fork_request( - &self, - peers: Vec, - hash: B::Hash, - number: NumberFor, - ) { - NetworkService::set_sync_fork_request(self, peers, hash, number) - } } /// Create a unique topic for a round and set-id combo. @@ -220,13 +239,14 @@ impl> NetworkBridge { prometheus_registry: Option<&Registry>, telemetry: Option, ) -> Self { + let protocol = config.protocol_name.clone(); let (validator, report_stream) = GossipValidator::new(config, set_state.clone(), prometheus_registry, telemetry.clone()); let validator = Arc::new(validator); let gossip_engine = Arc::new(Mutex::new(GossipEngine::new( service.clone(), - GRANDPA_PROTOCOL_NAME, + protocol, validator.clone(), prometheus_registry, ))); @@ -265,7 +285,7 @@ impl> NetworkBridge { } let (neighbor_packet_worker, neighbor_packet_sender) = - periodic::NeighborPacketWorker::new(); + periodic::NeighborPacketWorker::new(NEIGHBOR_REBROADCAST_PERIOD); NetworkBridge { service, @@ -299,9 +319,9 @@ impl> NetworkBridge { round: Round, set_id: SetId, voters: Arc>, - has_voted: HasVoted, - ) -> (impl Stream> + Unpin, OutgoingMessages) { - self.note_round(round, set_id, &*voters); + has_voted: HasVoted, + ) -> (impl Stream> + Unpin, OutgoingMessages) { + self.note_round(round, set_id, &voters); let keystore = keystore.and_then(|ks| { let id = ks.local_id(); @@ -448,7 +468,7 @@ impl> NetworkBridge { hash: B::Hash, number: NumberFor, ) { - Network::set_sync_fork_request(&self.service, peers, hash, number) + self.service.set_sync_fork_request(peers, hash, number) } } @@ -618,9 +638,9 @@ fn incoming_global( .filter_map(move |(notification, msg)| { future::ready(match msg { GossipMessage::Commit(msg) => - process_commit(msg, notification, &gossip_engine, &gossip_validator, &*voters), + process_commit(msg, notification, &gossip_engine, &gossip_validator, &voters), GossipMessage::CatchUp(msg) => - process_catch_up(msg, notification, &gossip_engine, &gossip_validator, &*voters), + process_catch_up(msg, notification, &gossip_engine, &gossip_validator, &voters), _ => { debug!(target: "afg", "Skipping unknown message type"); None @@ -662,15 +682,15 @@ pub(crate) struct OutgoingMessages { round: RoundNumber, set_id: SetIdNumber, keystore: Option, - sender: mpsc::Sender>, + sender: mpsc::Sender>, network: Arc>>, - has_voted: HasVoted, + has_voted: HasVoted, telemetry: Option, } impl Unpin for OutgoingMessages {} -impl Sink> for OutgoingMessages { +impl Sink> for OutgoingMessages { type Error = Error; fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { @@ -681,7 +701,10 @@ impl Sink> for OutgoingMessages { }) } - fn start_send(mut self: Pin<&mut Self>, mut msg: Message) -> Result<(), Self::Error> { + fn start_send( + mut self: Pin<&mut Self>, + mut msg: Message, + ) -> Result<(), Self::Error> { // if we've voted on this round previously under the same key, send that vote instead match &mut msg { finality_grandpa::Message::PrimaryPropose(ref mut vote) => { @@ -771,7 +794,7 @@ impl Sink> for OutgoingMessages { // checks a compact commit. returns the cost associated with processing it if // the commit was bad. fn check_compact_commit( - msg: &CompactCommit, + msg: &CompactCommit, voters: &VoterSet, round: Round, set_id: SetId, @@ -839,7 +862,7 @@ fn check_compact_commit( // checks a catch up. returns the cost associated with processing it if // the catch up was bad. fn check_catch_up( - msg: &CatchUp, + msg: &CatchUp, voters: &VoterSet, set_id: SetId, telemetry: Option, @@ -857,7 +880,7 @@ fn check_catch_up( let mut total_weight = 0; for id in votes { - if let Some(weight) = voters.get(&id).map(|info| info.weight()) { + if let Some(weight) = voters.get(id).map(|info| info.weight()) { total_weight += weight.get(); if total_weight > full_threshold { return Err(cost::MALFORMED_CATCH_UP) @@ -889,7 +912,7 @@ fn check_catch_up( ) -> Result where B: BlockT, - I: Iterator, &'a AuthorityId, &'a AuthoritySignature)>, + I: Iterator, &'a AuthorityId, &'a AuthoritySignature)>, { use crate::communication::gossip::Misbehavior; @@ -983,7 +1006,7 @@ impl CommitsOut { } } -impl Sink<(RoundNumber, Commit)> for CommitsOut { +impl Sink<(RoundNumber, Commit)> for CommitsOut { type Error = Error; fn poll_ready(self: Pin<&mut Self>, _: &mut Context) -> Poll> { @@ -992,7 +1015,7 @@ impl Sink<(RoundNumber, Commit)> for CommitsOut { fn start_send( self: Pin<&mut Self>, - input: (RoundNumber, Commit), + input: (RoundNumber, Commit), ) -> Result<(), Self::Error> { if !self.is_voter { return Ok(()) @@ -1014,7 +1037,7 @@ impl Sink<(RoundNumber, Commit)> for CommitsOut { .map(|signed| (signed.precommit, (signed.signature, signed.id))) .unzip(); - let compact_commit = CompactCommit:: { + let compact_commit = CompactCommit:: { target_hash: commit.target_hash, target_number: commit.target_number, precommits, diff --git a/client/finality-grandpa/src/communication/periodic.rs b/client/finality-grandpa/src/communication/periodic.rs index 77e55ad652f6c..c001796b5ca5d 100644 --- a/client/finality-grandpa/src/communication/periodic.rs +++ b/client/finality-grandpa/src/communication/periodic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -32,9 +32,6 @@ use super::gossip::{GossipMessage, NeighborPacket}; use sc_network::PeerId; use sp_runtime::traits::{Block as BlockT, NumberFor}; -// How often to rebroadcast, in cases where no new packets are created. -const REBROADCAST_AFTER: Duration = Duration::from_secs(2 * 60); - /// A sender used to send neighbor packets to a background job. #[derive(Clone)] pub(super) struct NeighborPacketSender( @@ -60,6 +57,7 @@ impl NeighborPacketSender { /// implementation). Periodically it sends out the last packet in cases where no new ones arrive. pub(super) struct NeighborPacketWorker { last: Option<(Vec, NeighborPacket>)>, + rebroadcast_period: Duration, delay: Delay, rx: TracingUnboundedReceiver<(Vec, NeighborPacket>)>, } @@ -67,13 +65,16 @@ pub(super) struct NeighborPacketWorker { impl Unpin for NeighborPacketWorker {} impl NeighborPacketWorker { - pub(super) fn new() -> (Self, NeighborPacketSender) { + pub(super) fn new(rebroadcast_period: Duration) -> (Self, NeighborPacketSender) { let (tx, rx) = tracing_unbounded::<(Vec, NeighborPacket>)>( "mpsc_grandpa_neighbor_packet_worker", ); - let delay = Delay::new(REBROADCAST_AFTER); + let delay = Delay::new(rebroadcast_period); - (NeighborPacketWorker { last: None, delay, rx }, NeighborPacketSender(tx)) + ( + NeighborPacketWorker { last: None, rebroadcast_period, delay, rx }, + NeighborPacketSender(tx), + ) } } @@ -85,7 +86,7 @@ impl Stream for NeighborPacketWorker { match this.rx.poll_next_unpin(cx) { Poll::Ready(None) => return Poll::Ready(None), Poll::Ready(Some((to, packet))) => { - this.delay.reset(REBROADCAST_AFTER); + this.delay.reset(this.rebroadcast_period); this.last = Some((to.clone(), packet.clone())); return Poll::Ready(Some((to, GossipMessage::::from(packet)))) @@ -98,7 +99,7 @@ impl Stream for NeighborPacketWorker { // Getting this far here implies that the timer fired. - this.delay.reset(REBROADCAST_AFTER); + this.delay.reset(this.rebroadcast_period); // Make sure the underlying task is scheduled for wake-up. // diff --git a/client/finality-grandpa/src/communication/tests.rs b/client/finality-grandpa/src/communication/tests.rs index 1fac0230b2a84..eab7bb2df50cf 100644 --- a/client/finality-grandpa/src/communication/tests.rs +++ b/client/finality-grandpa/src/communication/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,10 +22,18 @@ use super::{ gossip::{self, GossipValidator}, Round, SetId, VoterSet, }; -use crate::{communication::GRANDPA_PROTOCOL_NAME, environment::SharedVoterSetState}; +use crate::{communication::grandpa_protocol_name, environment::SharedVoterSetState}; use futures::prelude::*; use parity_scale_codec::Encode; -use sc_network::{config::Role, Event as NetworkEvent, ObservedRole, PeerId}; +use sc_network::{config::Role, Multiaddr, PeerId, ReputationChange}; +use sc_network_common::{ + config::MultiaddrWithPeerId, + protocol::{event::Event as NetworkEvent, role::ObservedRole, ProtocolName}, + service::{ + NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, + NetworkSyncForkRequest, NotificationSender, NotificationSenderError, + }, +}; use sc_network_gossip::Validator; use sc_network_test::{Block, Hash}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; @@ -33,7 +41,7 @@ use sp_finality_grandpa::AuthorityList; use sp_keyring::Ed25519Keyring; use sp_runtime::traits::NumberFor; use std::{ - borrow::Cow, + collections::HashSet, pin::Pin, sync::Arc, task::{Context, Poll}, @@ -42,8 +50,8 @@ use std::{ #[derive(Debug)] pub(crate) enum Event { EventStream(TracingUnboundedSender), - WriteNotification(sc_network::PeerId, Vec), - Report(sc_network::PeerId, sc_network::ReputationChange), + WriteNotification(PeerId, Vec), + Report(PeerId, ReputationChange), Announce(Hash), } @@ -52,57 +60,130 @@ pub(crate) struct TestNetwork { sender: TracingUnboundedSender, } -impl sc_network_gossip::Network for TestNetwork { - fn event_stream(&self) -> Pin + Send>> { - let (tx, rx) = tracing_unbounded("test"); - let _ = self.sender.unbounded_send(Event::EventStream(tx)); - Box::pin(rx) +impl NetworkPeers for TestNetwork { + fn set_authorized_peers(&self, _peers: HashSet) { + unimplemented!(); } - fn report_peer(&self, who: sc_network::PeerId, cost_benefit: sc_network::ReputationChange) { + fn set_authorized_only(&self, _reserved_only: bool) { + unimplemented!(); + } + + fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) { + unimplemented!(); + } + + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { let _ = self.sender.unbounded_send(Event::Report(who, cost_benefit)); } - fn add_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} + fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) {} + + fn accept_unreserved_peers(&self) { + unimplemented!(); + } - fn remove_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} + fn deny_unreserved_peers(&self) { + unimplemented!(); + } - fn disconnect_peer(&self, _: PeerId, _: Cow<'static, str>) {} + fn add_reserved_peer(&self, _peer: MultiaddrWithPeerId) -> Result<(), String> { + unimplemented!(); + } - fn write_notification(&self, who: PeerId, _: Cow<'static, str>, message: Vec) { - let _ = self.sender.unbounded_send(Event::WriteNotification(who, message)); + fn remove_reserved_peer(&self, _peer_id: PeerId) { + unimplemented!(); } - fn announce(&self, block: Hash, _associated_data: Option>) { - let _ = self.sender.unbounded_send(Event::Announce(block)); + fn set_reserved_peers( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn add_peers_to_reserved_set( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_peers_from_reserved_set(&self, _protocol: ProtocolName, _peers: Vec) {} + + fn add_to_peers_set( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_from_peers_set(&self, _protocol: ProtocolName, _peers: Vec) { + unimplemented!(); + } + + fn sync_num_connected(&self) -> usize { + unimplemented!(); } } -impl super::Network for TestNetwork { - fn set_sync_fork_request( +impl NetworkEventStream for TestNetwork { + fn event_stream( &self, - _peers: Vec, - _hash: Hash, - _number: NumberFor, - ) { + _name: &'static str, + ) -> Pin + Send>> { + let (tx, rx) = tracing_unbounded("test"); + let _ = self.sender.unbounded_send(Event::EventStream(tx)); + Box::pin(rx) + } +} + +impl NetworkNotification for TestNetwork { + fn write_notification(&self, target: PeerId, _protocol: ProtocolName, message: Vec) { + let _ = self.sender.unbounded_send(Event::WriteNotification(target, message)); + } + + fn notification_sender( + &self, + _target: PeerId, + _protocol: ProtocolName, + ) -> Result, NotificationSenderError> { + unimplemented!(); + } +} + +impl NetworkBlock> for TestNetwork { + fn announce_block(&self, hash: Hash, _data: Option>) { + let _ = self.sender.unbounded_send(Event::Announce(hash)); + } + + fn new_best_block_imported(&self, _hash: Hash, _number: NumberFor) { + unimplemented!(); } } +impl NetworkSyncForkRequest> for TestNetwork { + fn set_sync_fork_request(&self, _peers: Vec, _hash: Hash, _number: NumberFor) {} +} + impl sc_network_gossip::ValidatorContext for TestNetwork { fn broadcast_topic(&mut self, _: Hash, _: bool) {} fn broadcast_message(&mut self, _: Hash, _: Vec, _: bool) {} - fn send_message(&mut self, who: &sc_network::PeerId, data: Vec) { - >::write_notification( + fn send_message(&mut self, who: &PeerId, data: Vec) { + ::write_notification( self, - who.clone(), - GRANDPA_PROTOCOL_NAME.into(), + *who, + grandpa_protocol_name::NAME.into(), data, ); } - fn send_topic(&mut self, _: &sc_network::PeerId, _: Hash, _: bool) {} + fn send_topic(&mut self, _: &PeerId, _: Hash, _: bool) {} } pub(crate) struct Tester { @@ -148,6 +229,7 @@ fn config() -> crate::Config { local_role: Role::Authority, observer_enabled: true, telemetry: None, + protocol_name: grandpa_protocol_name::NAME.into(), } } @@ -155,13 +237,13 @@ fn config() -> crate::Config { fn voter_set_state() -> SharedVoterSetState { use crate::{authorities::AuthoritySet, environment::VoterSetState}; use finality_grandpa::round::State as RoundState; - use sp_core::{crypto::Public, H256}; + use sp_core::{crypto::ByteArray, H256}; use sp_finality_grandpa::AuthorityId; let state = RoundState::genesis((H256::zero(), 0)); let base = state.prevote_ghost.unwrap(); - let voters = vec![(AuthorityId::from_slice(&[1; 32]), 1)]; + let voters = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 1)]; let voters = AuthoritySet::genesis(voters).unwrap(); let set_state = VoterSetState::live(0, &voters, base); @@ -198,7 +280,7 @@ pub(crate) fn make_test_network() -> (impl Future, TestNetwork) } fn make_ids(keys: &[Ed25519Keyring]) -> AuthorityList { - keys.iter().map(|key| key.clone().public().into()).map(|id| (id, 1)).collect() + keys.iter().map(|&key| key.public().into()).map(|id| (id, 1)).collect() } struct NoopContext; @@ -206,8 +288,8 @@ struct NoopContext; impl sc_network_gossip::ValidatorContext for NoopContext { fn broadcast_topic(&mut self, _: Hash, _: bool) {} fn broadcast_message(&mut self, _: Hash, _: Vec, _: bool) {} - fn send_message(&mut self, _: &sc_network::PeerId, _: Vec) {} - fn send_topic(&mut self, _: &sc_network::PeerId, _: Hash, _: bool) {} + fn send_message(&mut self, _: &PeerId, _: Vec) {} + fn send_topic(&mut self, _: &PeerId, _: Hash, _: bool) {} } #[test] @@ -223,8 +305,7 @@ fn good_commit_leads_to_relay() { let target_hash: Hash = [1; 32].into(); let target_number = 500; - let precommit = - finality_grandpa::Precommit { target_hash: target_hash.clone(), target_number }; + let precommit = finality_grandpa::Precommit { target_hash, target_number }; let payload = sp_finality_grandpa::localized_payload( round, set_id, @@ -251,7 +332,7 @@ fn good_commit_leads_to_relay() { }) .encode(); - let id = sc_network::PeerId::random(); + let id = PeerId::random(); let global_topic = super::global_topic::(set_id); let test = make_test_network() @@ -280,30 +361,30 @@ fn good_commit_leads_to_relay() { // asking for global communication will cause the test network // to send us an event asking us for a stream. use it to // send a message. - let sender_id = id.clone(); + let sender_id = id; let send_message = tester.filter_network_events(move |event| match event { Event::EventStream(sender) => { // Add the sending peer and send the commit let _ = sender.unbounded_send(NetworkEvent::NotificationStreamOpened { - remote: sender_id.clone(), - protocol: GRANDPA_PROTOCOL_NAME.into(), + remote: sender_id, + protocol: grandpa_protocol_name::NAME.into(), negotiated_fallback: None, role: ObservedRole::Full, }); let _ = sender.unbounded_send(NetworkEvent::NotificationsReceived { - remote: sender_id.clone(), + remote: sender_id, messages: vec![( - GRANDPA_PROTOCOL_NAME.into(), + grandpa_protocol_name::NAME.into(), commit_to_send.clone().into(), )], }); // Add a random peer which will be the recipient of this message - let receiver_id = sc_network::PeerId::random(); + let receiver_id = PeerId::random(); let _ = sender.unbounded_send(NetworkEvent::NotificationStreamOpened { - remote: receiver_id.clone(), - protocol: GRANDPA_PROTOCOL_NAME.into(), + remote: receiver_id, + protocol: grandpa_protocol_name::NAME.into(), negotiated_fallback: None, role: ObservedRole::Full, }); @@ -321,7 +402,10 @@ fn good_commit_leads_to_relay() { sender.unbounded_send(NetworkEvent::NotificationsReceived { remote: receiver_id, - messages: vec![(GRANDPA_PROTOCOL_NAME.into(), msg.encode().into())], + messages: vec![( + grandpa_protocol_name::NAME.into(), + msg.encode().into(), + )], }) }; @@ -371,8 +455,7 @@ fn bad_commit_leads_to_report() { let target_hash: Hash = [1; 32].into(); let target_number = 500; - let precommit = - finality_grandpa::Precommit { target_hash: target_hash.clone(), target_number }; + let precommit = finality_grandpa::Precommit { target_hash, target_number }; let payload = sp_finality_grandpa::localized_payload( round, set_id, @@ -399,7 +482,7 @@ fn bad_commit_leads_to_report() { }) .encode(); - let id = sc_network::PeerId::random(); + let id = PeerId::random(); let global_topic = super::global_topic::(set_id); let test = make_test_network() @@ -428,19 +511,19 @@ fn bad_commit_leads_to_report() { // asking for global communication will cause the test network // to send us an event asking us for a stream. use it to // send a message. - let sender_id = id.clone(); + let sender_id = id; let send_message = tester.filter_network_events(move |event| match event { Event::EventStream(sender) => { let _ = sender.unbounded_send(NetworkEvent::NotificationStreamOpened { - remote: sender_id.clone(), - protocol: GRANDPA_PROTOCOL_NAME.into(), + remote: sender_id, + protocol: grandpa_protocol_name::NAME.into(), negotiated_fallback: None, role: ObservedRole::Full, }); let _ = sender.unbounded_send(NetworkEvent::NotificationsReceived { - remote: sender_id.clone(), + remote: sender_id, messages: vec![( - GRANDPA_PROTOCOL_NAME.into(), + grandpa_protocol_name::NAME.into(), commit_to_send.clone().into(), )], }); @@ -480,7 +563,7 @@ fn bad_commit_leads_to_report() { #[test] fn peer_with_higher_view_leads_to_catch_up_request() { - let id = sc_network::PeerId::random(); + let id = PeerId::random(); let (tester, mut net) = make_test_network(); let test = tester @@ -531,3 +614,46 @@ fn peer_with_higher_view_leads_to_catch_up_request() { futures::executor::block_on(test); } + +fn local_chain_spec() -> Box { + use sc_chain_spec::{ChainSpec, GenericChainSpec}; + use serde::{Deserialize, Serialize}; + use sp_runtime::{BuildStorage, Storage}; + + #[derive(Debug, Serialize, Deserialize)] + struct Genesis(std::collections::BTreeMap); + impl BuildStorage for Genesis { + fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { + storage.top.extend( + self.0.iter().map(|(a, b)| (a.clone().into_bytes(), b.clone().into_bytes())), + ); + Ok(()) + } + } + let chain_spec = GenericChainSpec::::from_json_bytes( + &include_bytes!("../../../chain-spec/res/chain_spec.json")[..], + ) + .unwrap(); + chain_spec.cloned_box() +} + +#[test] +fn grandpa_protocol_name() { + let chain_spec = local_chain_spec(); + + // Create protocol name using random genesis hash. + let genesis_hash = sp_core::H256::random(); + let expected = format!("/{}/grandpa/1", array_bytes::bytes2hex("", genesis_hash.as_ref())); + let proto_name = grandpa_protocol_name::standard_name(&genesis_hash, &chain_spec); + assert_eq!(proto_name.to_string(), expected); + + // Create protocol name using hardcoded genesis hash. Verify exact representation. + let genesis_hash = [ + 53, 79, 112, 97, 119, 217, 39, 202, 147, 138, 225, 38, 88, 182, 215, 185, 110, 88, 8, 53, + 125, 210, 158, 151, 50, 113, 102, 59, 245, 199, 221, 240, + ]; + let expected = + "/354f706177d927ca938ae12658b6d7b96e5808357dd29e973271663bf5c7ddf0/grandpa/1".to_string(); + let proto_name = grandpa_protocol_name::standard_name(&genesis_hash, &chain_spec); + assert_eq!(proto_name.to_string(), expected); +} diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index f27a530ed2f40..f235c3a86c04e 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -81,7 +81,7 @@ pub struct CompletedRound { /// The target block base used for voting in the round. pub base: (Block::Hash, NumberFor), /// All the votes observed in the round. - pub votes: Vec>, + pub votes: Vec>, } // Data about last completed rounds within a single voter set. Stores @@ -170,7 +170,7 @@ impl CompletedRounds { /// A map with voter status information for currently live rounds, /// which votes have we cast and what are they. -pub type CurrentRounds = BTreeMap>; +pub type CurrentRounds = BTreeMap::Header>>; /// The state of the current voter set, whether it is currently active or not /// and information related to the previously completed rounds. Current round @@ -214,7 +214,7 @@ impl VoterSetState { authority_set, ); - let mut current_rounds = CurrentRounds::new(); + let mut current_rounds = CurrentRounds::::new(); current_rounds.insert(1, HasVoted::No); VoterSetState::Live { completed_rounds, current_rounds } @@ -258,27 +258,27 @@ impl VoterSetState { /// Whether we've voted already during a prior run of the program. #[derive(Clone, Debug, Decode, Encode, PartialEq)] -pub enum HasVoted { +pub enum HasVoted { /// Has not voted already in this round. No, /// Has voted in this round. - Yes(AuthorityId, Vote), + Yes(AuthorityId, Vote
), } /// The votes cast by this voter already during a prior run of the program. #[derive(Debug, Clone, Decode, Encode, PartialEq)] -pub enum Vote { +pub enum Vote { /// Has cast a proposal. - Propose(PrimaryPropose), + Propose(PrimaryPropose
), /// Has cast a prevote. - Prevote(Option>, Prevote), + Prevote(Option>, Prevote
), /// Has cast a precommit (implies prevote.) - Precommit(Option>, Prevote, Precommit), + Precommit(Option>, Prevote
, Precommit
), } -impl HasVoted { +impl HasVoted
{ /// Returns the proposal we should vote with (if any.) - pub fn propose(&self) -> Option<&PrimaryPropose> { + pub fn propose(&self) -> Option<&PrimaryPropose
> { match self { HasVoted::Yes(_, Vote::Propose(propose)) => Some(propose), HasVoted::Yes(_, Vote::Prevote(propose, _)) | @@ -288,7 +288,7 @@ impl HasVoted { } /// Returns the prevote we should vote with (if any.) - pub fn prevote(&self) -> Option<&Prevote> { + pub fn prevote(&self) -> Option<&Prevote
> { match self { HasVoted::Yes(_, Vote::Prevote(_, prevote)) | HasVoted::Yes(_, Vote::Precommit(_, prevote, _)) => Some(prevote), @@ -297,7 +297,7 @@ impl HasVoted { } /// Returns the precommit we should vote with (if any.) - pub fn precommit(&self) -> Option<&Precommit> { + pub fn precommit(&self) -> Option<&Precommit
> { match self { HasVoted::Yes(_, Vote::Precommit(_, _, precommit)) => Some(precommit), _ => None, @@ -368,7 +368,7 @@ impl SharedVoterSetState { } /// Return vote status information for the current round. - pub(crate) fn has_voted(&self, round: RoundNumber) -> HasVoted { + pub(crate) fn has_voted(&self, round: RoundNumber) -> HasVoted { match &*self.inner.read() { VoterSetState::Live { current_rounds, .. } => current_rounds .get(&round) @@ -404,19 +404,19 @@ impl Metrics { ) -> Result { Ok(Self { finality_grandpa_round: register( - Gauge::new("finality_grandpa_round", "Highest completed GRANDPA round.")?, + Gauge::new("substrate_finality_grandpa_round", "Highest completed GRANDPA round.")?, registry, )?, finality_grandpa_prevotes: register( Counter::new( - "finality_grandpa_prevotes_total", + "substrate_finality_grandpa_prevotes_total", "Total number of GRANDPA prevotes cast locally.", )?, registry, )?, finality_grandpa_precommits: register( Counter::new( - "finality_grandpa_precommits_total", + "substrate_finality_grandpa_precommits_total", "Total number of GRANDPA precommits cast locally.", )?, registry, @@ -451,7 +451,7 @@ impl, SC, VR> Environment) -> Result>, Error>, { self.voter_set_state.with(|voter_set_state| { - if let Some(set_state) = f(&voter_set_state)? { + if let Some(set_state) = f(voter_set_state)? { *voter_set_state = set_state; if let Some(metrics) = self.metrics.as_ref() { @@ -609,7 +609,7 @@ where let tree_route = match tree_route_res { Ok(tree_route) => tree_route, Err(e) => { - debug!(target: "afg", "Encountered error computing ancestry between block {:?} and base {:?}: {:?}", + debug!(target: "afg", "Encountered error computing ancestry between block {:?} and base {:?}: {}", block, base, e); return Err(GrandpaError::NotDescendent) @@ -771,7 +771,7 @@ where fn proposed( &self, round: RoundNumber, - propose: PrimaryPropose, + propose: PrimaryPropose, ) -> Result<(), Self::Error> { let local_id = match self.voter_set_state.voting_on(round) { Some(id) => id, @@ -811,13 +811,17 @@ where Ok(()) } - fn prevoted(&self, round: RoundNumber, prevote: Prevote) -> Result<(), Self::Error> { + fn prevoted( + &self, + round: RoundNumber, + prevote: Prevote, + ) -> Result<(), Self::Error> { let local_id = match self.voter_set_state.voting_on(round) { Some(id) => id, None => return Ok(()), }; - let report_prevote_metrics = |prevote: &Prevote| { + let report_prevote_metrics = |prevote: &Prevote| { telemetry!( self.telemetry; CONSENSUS_DEBUG; @@ -873,14 +877,14 @@ where fn precommitted( &self, round: RoundNumber, - precommit: Precommit, + precommit: Precommit, ) -> Result<(), Self::Error> { let local_id = match self.voter_set_state.voting_on(round) { Some(id) => id, None => return Ok(()), }; - let report_precommit_metrics = |precommit: &Precommit| { + let report_precommit_metrics = |precommit: &Precommit| { telemetry!( self.telemetry; CONSENSUS_DEBUG; @@ -987,11 +991,9 @@ where let mut current_rounds = current_rounds.clone(); current_rounds.remove(&round); - // NOTE: this condition should always hold as GRANDPA rounds are always + // NOTE: this entry should always exist as GRANDPA rounds are always // started in increasing order, still it's better to play it safe. - if !current_rounds.contains_key(&(round + 1)) { - current_rounds.insert(round + 1, HasVoted::No); - } + current_rounds.entry(round + 1).or_insert(HasVoted::No); let set_state = VoterSetState::::Live { completed_rounds, current_rounds }; @@ -1046,7 +1048,7 @@ where .votes .extend(historical_votes.seen().iter().skip(n_existing_votes).cloned()); already_completed.state = state; - crate::aux_schema::write_concluded_round(&*self.client, &already_completed)?; + crate::aux_schema::write_concluded_round(&*self.client, already_completed)?; } let set_state = VoterSetState::::Live { @@ -1067,7 +1069,7 @@ where hash: Block::Hash, number: NumberFor, round: RoundNumber, - commit: Commit, + commit: Commit, ) -> Result<(), Self::Error> { finalize_block( self.client.clone(), @@ -1094,33 +1096,41 @@ where fn prevote_equivocation( &self, _round: RoundNumber, - equivocation: finality_grandpa::Equivocation, Self::Signature>, + equivocation: finality_grandpa::Equivocation< + Self::Id, + Prevote, + Self::Signature, + >, ) { warn!(target: "afg", "Detected prevote equivocation in the finality worker: {:?}", equivocation); if let Err(err) = self.report_equivocation(equivocation.into()) { - warn!(target: "afg", "Error reporting prevote equivocation: {:?}", err); + warn!(target: "afg", "Error reporting prevote equivocation: {}", err); } } fn precommit_equivocation( &self, _round: RoundNumber, - equivocation: finality_grandpa::Equivocation, Self::Signature>, + equivocation: finality_grandpa::Equivocation< + Self::Id, + Precommit, + Self::Signature, + >, ) { warn!(target: "afg", "Detected precommit equivocation in the finality worker: {:?}", equivocation); if let Err(err) = self.report_equivocation(equivocation.into()) { - warn!(target: "afg", "Error reporting precommit equivocation: {:?}", err); + warn!(target: "afg", "Error reporting precommit equivocation: {}", err); } } } pub(crate) enum JustificationOrCommit { Justification(GrandpaJustification), - Commit((RoundNumber, Commit)), + Commit((RoundNumber, Commit)), } -impl From<(RoundNumber, Commit)> for JustificationOrCommit { - fn from(commit: (RoundNumber, Commit)) -> JustificationOrCommit { +impl From<(RoundNumber, Commit)> for JustificationOrCommit { + fn from(commit: (RoundNumber, Commit)) -> JustificationOrCommit { JustificationOrCommit::Commit(commit) } } @@ -1165,7 +1175,7 @@ where debug!(target: "afg", "Finding best chain containing block {:?} with number limit {:?}", block, limit); let result = match select_chain.finality_target(block, None).await { - Ok(Some(best_hash)) => { + Ok(best_hash) => { let best_header = client .header(BlockId::Hash(best_hash))? .expect("Header known to exist after `finality_target` call; qed"); @@ -1223,12 +1233,8 @@ where }) .or_else(|| Some((target_header.hash(), *target_header.number()))) }, - Ok(None) => { - debug!(target: "afg", "Encountered error finding best chain containing {:?}: couldn't find target block", block); - None - }, Err(e) => { - debug!(target: "afg", "Encountered error finding best chain containing {:?}: {:?}", block, e); + warn!(target: "afg", "Encountered error finding best chain containing {:?}: {}", block, e); None }, }; @@ -1297,7 +1303,7 @@ where ) { if let Some(sender) = justification_sender { if let Err(err) = sender.notify(justification) { - warn!(target: "afg", "Error creating justification for subscriber: {:?}", err); + warn!(target: "afg", "Error creating justification for subscriber: {}", err); } } } @@ -1346,9 +1352,9 @@ where // ideally some handle to a synchronization oracle would be used // to avoid unconditionally notifying. client - .apply_finality(import_op, BlockId::Hash(hash), persisted_justification, true) + .apply_finality(import_op, hash, persisted_justification, true) .map_err(|e| { - warn!(target: "afg", "Error applying finality to block {:?}: {:?}", (hash, number), e); + warn!(target: "afg", "Error applying finality to block {:?}: {}", (hash, number), e); e })?; diff --git a/client/finality-grandpa/src/finality_proof.rs b/client/finality-grandpa/src/finality_proof.rs index 1e20c2edc3a6e..453b41bc63468 100644 --- a/client/finality-grandpa/src/finality_proof.rs +++ b/client/finality-grandpa/src/finality_proof.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -132,17 +132,18 @@ pub struct FinalityProof { } /// Errors occurring when trying to prove finality -#[derive(Debug, derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] pub enum FinalityProofError { /// The requested block has not yet been finalized. - #[display(fmt = "Block not yet finalized")] + #[error("Block not yet finalized")] BlockNotYetFinalized, /// The requested block is not covered by authority set changes. Likely this means the block is /// in the latest authority set, and the subscription API is more appropriate. - #[display(fmt = "Block not covered by authority set changes")] + #[error("Block not covered by authority set changes")] BlockNotInAuthoritySetChanges, /// Errors originating from the client. - Client(sp_blockchain::Error), + #[error(transparent)] + Client(#[from] sp_blockchain::Error), } fn prove_finality( @@ -182,7 +183,9 @@ where } }, AuthoritySetChangeId::Set(_, last_block_for_set) => { - let last_block_for_set_id = BlockId::Number(last_block_for_set); + let last_block_for_set_id = backend + .blockchain() + .expect_block_hash_from_id(&BlockId::Number(last_block_for_set))?; let justification = if let Some(grandpa_justification) = backend .blockchain() .justifications(last_block_for_set_id)? @@ -236,15 +239,15 @@ where } #[cfg(test)] -pub(crate) mod tests { +mod tests { use super::*; use crate::{authorities::AuthoritySetChanges, BlockNumberOps, ClientError, SetId}; use futures::executor::block_on; use sc_block_builder::BlockBuilderProvider; use sc_client_api::{apply_aux, LockImportRun}; use sp_consensus::BlockOrigin; - use sp_core::crypto::Public; - use sp_finality_grandpa::{AuthorityId, GRANDPA_ENGINE_ID as ID}; + use sp_core::crypto::UncheckedFrom; + use sp_finality_grandpa::GRANDPA_ENGINE_ID as ID; use sp_keyring::Ed25519Keyring; use substrate_test_runtime_client::{ runtime::{Block, Header, H256}, @@ -270,6 +273,7 @@ pub(crate) mod tests { let justification: GrandpaJustification = Decode::decode(&mut &proof.justification[..]) .map_err(|_| ClientError::JustificationDecode)?; + justification.verify(current_set_id, ¤t_authorities)?; Ok(proof) @@ -307,7 +311,8 @@ pub(crate) mod tests { } for block in to_finalize { - client.finalize_block(BlockId::Number(*block), None).unwrap(); + let hash = blocks[*block as usize - 1].hash(); + client.finalize_block(hash, None).unwrap(); } (client, backend, blocks) } @@ -350,7 +355,7 @@ pub(crate) mod tests { // When we can't decode proof from Vec check_finality_proof::( 1, - vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)], + vec![(UncheckedFrom::unchecked_from([3u8; 32]), 1u64)], vec![42], ) .unwrap_err(); @@ -361,7 +366,7 @@ pub(crate) mod tests { // When decoded proof has zero length check_finality_proof::( 1, - vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)], + vec![(UncheckedFrom::unchecked_from([3u8; 32]), 1u64)], Vec::>::new().encode(), ) .unwrap_err(); @@ -369,7 +374,7 @@ pub(crate) mod tests { #[test] fn finality_proof_check_fails_with_incomplete_justification() { - let (client, _, blocks) = test_blockchain(8, &[4, 5, 8]); + let (_, _, blocks) = test_blockchain(8, &[4, 5, 8]); // Create a commit without precommits let commit = finality_grandpa::Commit { @@ -377,7 +382,14 @@ pub(crate) mod tests { target_number: *blocks[7].header().number(), precommits: Vec::new(), }; - let grandpa_just = GrandpaJustification::from_commit(&client, 8, commit).unwrap(); + + let grandpa_just: GrandpaJustification = + sp_finality_grandpa::GrandpaJustification::
{ + round: 8, + votes_ancestries: Vec::new(), + commit, + } + .into(); let finality_proof = FinalityProof { block: header(2).hash(), @@ -387,7 +399,7 @@ pub(crate) mod tests { check_finality_proof::( 1, - vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)], + vec![(UncheckedFrom::unchecked_from([3u8; 32]), 1u64)], finality_proof.encode(), ) .unwrap_err(); @@ -480,7 +492,7 @@ pub(crate) mod tests { let grandpa_just8 = GrandpaJustification::from_commit(&client, round, commit).unwrap(); client - .finalize_block(BlockId::Number(8), Some((ID, grandpa_just8.encode().clone()))) + .finalize_block(block8.hash(), Some((ID, grandpa_just8.encode().clone()))) .unwrap(); // Authority set change at block 8, so the justification stored there will be used in the diff --git a/client/finality-grandpa/src/import.rs b/client/finality-grandpa/src/import.rs index f663bfe94afdf..3715287eea31f 100644 --- a/client/finality-grandpa/src/import.rs +++ b/client/finality-grandpa/src/import.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,7 +19,7 @@ use std::{collections::HashMap, marker::PhantomData, sync::Arc}; use log::debug; -use parity_scale_codec::{Decode, Encode}; +use parity_scale_codec::Decode; use sc_client_api::{backend::Backend, utils::is_descendent_of}; use sc_consensus::{ @@ -35,7 +35,7 @@ use sp_core::hashing::twox_128; use sp_finality_grandpa::{ConsensusLog, GrandpaApi, ScheduledChange, SetId, GRANDPA_ENGINE_ID}; use sp_runtime::{ generic::{BlockId, OpaqueDigestItemId}, - traits::{Block as BlockT, DigestFor, Header as HeaderT, NumberFor, Zero}, + traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}, Justification, }; @@ -89,7 +89,6 @@ impl JustificationImport for GrandpaBlockImport where NumberFor: finality_grandpa::BlockNumberOps, - DigestFor: Encode, BE: Backend, Client: ClientForGrandpa, SC: SelectChain, @@ -118,10 +117,10 @@ where ) .await } else { - Ok(Some(pending_change.canon_hash)) + Ok(pending_change.canon_hash) }; - if let Ok(Some(hash)) = effective_block_hash { + if let Ok(hash) = effective_block_hash { if let Ok(Some(header)) = self.inner.header(BlockId::Hash(hash)) { if *header.number() == pending_change.effective_number() { out.push((header.hash(), *header.number())); @@ -229,7 +228,6 @@ pub fn find_forced_change( impl GrandpaBlockImport where NumberFor: finality_grandpa::BlockNumberOps, - DigestFor: Encode, BE: Backend, Client: ClientForGrandpa, Client::Api: GrandpaApi, @@ -286,7 +284,7 @@ where impl<'a, H, N> InnerGuard<'a, H, N> { fn as_mut(&mut self) -> &mut AuthoritySet { - &mut **self.guard.as_mut().expect("only taken on deconstruction; qed") + self.guard.as_mut().expect("only taken on deconstruction; qed") } fn set_old(&mut self, old: AuthoritySet) { @@ -426,13 +424,15 @@ where } /// Read current set id form a given state. - fn current_set_id(&self, id: &BlockId) -> Result { + fn current_set_id(&self, hash: Block::Hash) -> Result { + let id = &BlockId::hash(hash); let runtime_version = self.inner.runtime_api().version(id).map_err(|e| { ConsensusError::ClientImport(format!( "Unable to retrieve current runtime version. {}", e )) })?; + if runtime_version .api_version(&>::ID) .map_or(false, |v| v < 3) @@ -442,7 +442,7 @@ where for prefix in ["GrandpaFinality", "Grandpa"] { let k = [twox_128(prefix.as_bytes()), twox_128(b"CurrentSetId")].concat(); if let Ok(Some(id)) = - self.inner.storage(&id, &sc_client_api::StorageKey(k.to_vec())) + self.inner.storage(hash, &sc_client_api::StorageKey(k.to_vec())) { if let Ok(id) = SetId::decode(&mut id.0.as_ref()) { return Ok(id) @@ -453,7 +453,7 @@ where } else { self.inner .runtime_api() - .current_set_id(&id) + .current_set_id(id) .map_err(|e| ConsensusError::ClientImport(e.to_string())) } } @@ -475,13 +475,12 @@ where // finality proofs and that the state is correct and final. // So we can read the authority list and set id from the state. self.authority_set_hard_forks.clear(); - let block_id = BlockId::hash(hash); let authorities = self .inner .runtime_api() - .grandpa_authorities(&block_id) + .grandpa_authorities(&BlockId::hash(hash)) .map_err(|e| ConsensusError::ClientImport(e.to_string()))?; - let set_id = self.current_set_id(&block_id)?; + let set_id = self.current_set_id(hash)?; let authority_set = AuthoritySet::new( authorities.clone(), set_id, @@ -515,7 +514,6 @@ where impl BlockImport for GrandpaBlockImport where NumberFor: finality_grandpa::BlockNumberOps, - DigestFor: Encode, BE: Backend, Client: ClientForGrandpa, Client::Api: GrandpaApi, @@ -551,6 +549,32 @@ where return self.import_state(block, new_cache).await } + if number <= self.inner.info().finalized_number { + // Importing an old block. Just save justifications and authority set changes + if self.check_new_change(&block.header, hash).is_some() { + if block.justifications.is_none() { + return Err(ConsensusError::ClientImport( + "Justification required when importing \ + an old block with authority set change." + .into(), + )) + } + assert!(block.justifications.is_some()); + let mut authority_set = self.authority_set.inner_locked(); + authority_set.authority_set_changes.insert(number); + crate::aux_schema::update_authority_set::( + &authority_set, + None, + |insert| { + block + .auxiliary + .extend(insert.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec())))) + }, + ); + } + return (&*self.inner).import_block(block, new_cache).await + } + // on initial sync we will restrict logging under info to avoid spam. let initial_sync = block.origin == BlockOrigin::NetworkInitialSync; @@ -575,7 +599,7 @@ where Err(e) => { debug!( target: "afg", - "Restoring old authority set after block import error: {:?}", + "Restoring old authority set after block import error: {}", e, ); pending_changes.revert(); @@ -640,8 +664,12 @@ where import_res.unwrap_or_else(|err| { if needs_justification { - debug!(target: "afg", "Imported block #{} that enacts authority set change with \ - invalid justification: {:?}, requesting justification from peers.", number, err); + debug!( + target: "afg", + "Requesting justification from peers due to imported block #{} that enacts authority set change with invalid justification: {}", + number, + err + ); imported_aux.bad_justification = true; imported_aux.needs_justification = true; } @@ -705,7 +733,7 @@ impl GrandpaBlockImport { - round: u64, - pub(crate) commit: Commit, - votes_ancestries: Vec, + /// The GRANDPA justification for block finality. + pub justification: sp_finality_grandpa::GrandpaJustification, + _block: PhantomData, +} + +impl From> + for GrandpaJustification +{ + fn from(justification: sp_finality_grandpa::GrandpaJustification) -> Self { + Self { justification, _block: Default::default() } + } +} + +impl Into> + for GrandpaJustification +{ + fn into(self) -> sp_finality_grandpa::GrandpaJustification { + self.justification + } } impl GrandpaJustification { @@ -53,8 +70,8 @@ impl GrandpaJustification { pub fn from_commit( client: &Arc, round: u64, - commit: Commit, - ) -> Result, Error> + commit: Commit, + ) -> Result where C: HeaderBackend, { @@ -66,16 +83,33 @@ impl GrandpaJustification { Err(Error::Client(ClientError::BadJustification(msg))) }; + // we pick the precommit for the lowest block as the base that + // should serve as the root block for populating ancestry (i.e. + // collect all headers from all precommit blocks to the base) + let (base_hash, base_number) = match commit + .precommits + .iter() + .map(|signed| &signed.precommit) + .min_by_key(|precommit| precommit.target_number) + .map(|precommit| (precommit.target_hash, precommit.target_number)) + { + None => return error(), + Some(base) => base, + }; + for signed in commit.precommits.iter() { let mut current_hash = signed.precommit.target_hash; loop { - if current_hash == commit.target_hash { + if current_hash == base_hash { break } match client.header(BlockId::Hash(current_hash))? { Some(current_header) => { - if *current_header.number() <= commit.target_number { + // NOTE: this should never happen as we pick the lowest block + // as base and only traverse backwards from the other blocks + // in the commit. but better be safe to avoid an unbound loop. + if *current_header.number() <= base_number { return error() } @@ -83,6 +117,7 @@ impl GrandpaJustification { if votes_ancestries_hashes.insert(current_hash) { votes_ancestries.push(current_header); } + current_hash = parent_hash; }, _ => return error(), @@ -90,7 +125,7 @@ impl GrandpaJustification { } } - Ok(GrandpaJustification { round, commit, votes_ancestries }) + Ok(sp_finality_grandpa::GrandpaJustification { round, commit, votes_ancestries }.into()) } /// Decode a GRANDPA justification and validate the commit and the votes' @@ -100,15 +135,17 @@ impl GrandpaJustification { finalized_target: (Block::Hash, NumberFor), set_id: u64, voters: &VoterSet, - ) -> Result, ClientError> + ) -> Result where NumberFor: finality_grandpa::BlockNumberOps, { let justification = GrandpaJustification::::decode(&mut &*encoded) .map_err(|_| ClientError::JustificationDecode)?; - if (justification.commit.target_hash, justification.commit.target_number) != - finalized_target + if ( + justification.justification.commit.target_hash, + justification.justification.commit.target_number, + ) != finalized_target { let msg = "invalid commit target in grandpa justification".to_string(); Err(ClientError::BadJustification(msg)) @@ -139,24 +176,43 @@ impl GrandpaJustification { { use finality_grandpa::Chain; - let ancestry_chain = AncestryChain::::new(&self.votes_ancestries); + let ancestry_chain = AncestryChain::::new(&self.justification.votes_ancestries); - match finality_grandpa::validate_commit(&self.commit, voters, &ancestry_chain) { - Ok(ref result) if result.ghost().is_some() => {}, + match finality_grandpa::validate_commit(&self.justification.commit, voters, &ancestry_chain) + { + Ok(ref result) if result.is_valid() => {}, _ => { let msg = "invalid commit in grandpa justification".to_string(); return Err(ClientError::BadJustification(msg)) }, } + // we pick the precommit for the lowest block as the base that + // should serve as the root block for populating ancestry (i.e. + // collect all headers from all precommit blocks to the base) + let base_hash = self + .justification + .commit + .precommits + .iter() + .map(|signed| &signed.precommit) + .min_by_key(|precommit| precommit.target_number) + .map(|precommit| precommit.target_hash) + .expect( + "can only fail if precommits is empty; \ + commit has been validated above; \ + valid commits must include precommits; \ + qed.", + ); + let mut buf = Vec::new(); let mut visited_hashes = HashSet::new(); - for signed in self.commit.precommits.iter() { + for signed in self.justification.commit.precommits.iter() { if !sp_finality_grandpa::check_message_signature_with_buffer( &finality_grandpa::Message::Precommit(signed.precommit.clone()), &signed.id, &signed.signature, - self.round, + self.justification.round, set_id, &mut buf, ) { @@ -165,11 +221,11 @@ impl GrandpaJustification { )) } - if self.commit.target_hash == signed.precommit.target_hash { + if base_hash == signed.precommit.target_hash { continue } - match ancestry_chain.ancestry(self.commit.target_hash, signed.precommit.target_hash) { + match ancestry_chain.ancestry(base_hash, signed.precommit.target_hash) { Ok(route) => { // ancestry starts from parent hash but the precommit target hash has been // visited @@ -185,8 +241,12 @@ impl GrandpaJustification { } } - let ancestry_hashes = - self.votes_ancestries.iter().map(|h: &Block::Header| h.hash()).collect(); + let ancestry_hashes: HashSet<_> = self + .justification + .votes_ancestries + .iter() + .map(|h: &Block::Header| h.hash()) + .collect(); if visited_hashes != ancestry_hashes { return Err(ClientError::BadJustification( @@ -200,7 +260,7 @@ impl GrandpaJustification { /// The target block number and hash that this justifications proves finality for. pub fn target(&self) -> (NumberFor, Block::Hash) { - (self.commit.target_number, self.commit.target_hash) + (self.justification.commit.target_number, self.justification.commit.target_hash) } } diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index 452659ced6a70..c1b4962d04a12 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -58,26 +58,28 @@ use futures::{prelude::*, StreamExt}; use log::{debug, error, info}; -use parity_scale_codec::{Decode, Encode}; +use parity_scale_codec::Decode; use parking_lot::RwLock; use prometheus_endpoint::{PrometheusError, Registry}; use sc_client_api::{ backend::{AuxStore, Backend}, + utils::is_descendent_of, BlockchainEvents, CallExecutor, ExecutionStrategy, ExecutorProvider, Finalizer, LockImportRun, StorageProvider, TransactionFor, }; use sc_consensus::BlockImport; +use sc_network_common::protocol::ProtocolName; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver}; use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppKey; -use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; +use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult}; use sp_consensus::SelectChain; -use sp_core::crypto::Public; +use sp_core::crypto::ByteArray; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ generic::BlockId, - traits::{Block as BlockT, DigestFor, NumberFor, Zero}, + traits::{Block as BlockT, NumberFor, Zero}, }; pub use finality_grandpa::BlockNumberOps; @@ -123,6 +125,7 @@ pub mod warp_proof; pub use authorities::{AuthoritySet, AuthoritySetChanges, SharedAuthoritySet}; pub use aux_schema::best_justification; +pub use communication::grandpa_protocol_name::standard_name as protocol_standard_name; pub use finality_grandpa::voter::report; pub use finality_proof::{FinalityProof, FinalityProofError, FinalityProofProvider}; pub use import::{find_forced_change, find_scheduled_change, GrandpaBlockImport}; @@ -141,72 +144,35 @@ use sp_finality_grandpa::{AuthorityList, AuthoritySignature, SetId}; use until_imported::UntilGlobalMessageBlocksImported; // Re-export these two because it's just so damn convenient. -pub use sp_finality_grandpa::{AuthorityId, AuthorityPair, GrandpaApi, ScheduledChange}; +pub use sp_finality_grandpa::{ + AuthorityId, AuthorityPair, CatchUp, Commit, CompactCommit, GrandpaApi, Message, Precommit, + Prevote, PrimaryPropose, ScheduledChange, SignedMessage, +}; use std::marker::PhantomData; #[cfg(test)] mod tests; -/// A GRANDPA message for a substrate chain. -pub type Message = finality_grandpa::Message<::Hash, NumberFor>; - -/// A signed message. -pub type SignedMessage = finality_grandpa::SignedMessage< - ::Hash, - NumberFor, - AuthoritySignature, - AuthorityId, ->; - -/// A primary propose message for this chain's block type. -pub type PrimaryPropose = - finality_grandpa::PrimaryPropose<::Hash, NumberFor>; -/// A prevote message for this chain's block type. -pub type Prevote = finality_grandpa::Prevote<::Hash, NumberFor>; -/// A precommit message for this chain's block type. -pub type Precommit = finality_grandpa::Precommit<::Hash, NumberFor>; -/// A catch up message for this chain's block type. -pub type CatchUp = finality_grandpa::CatchUp< - ::Hash, - NumberFor, - AuthoritySignature, - AuthorityId, ->; -/// A commit message for this chain's block type. -pub type Commit = finality_grandpa::Commit< - ::Hash, - NumberFor, - AuthoritySignature, - AuthorityId, ->; -/// A compact commit message for this chain's block type. -pub type CompactCommit = finality_grandpa::CompactCommit< - ::Hash, - NumberFor, - AuthoritySignature, - AuthorityId, ->; /// A global communication input stream for commits and catch up messages. Not /// exposed publicly, used internally to simplify types in the communication /// layer. -type CommunicationIn = finality_grandpa::voter::CommunicationIn< +type CommunicationIn = voter::CommunicationIn< ::Hash, NumberFor, AuthoritySignature, AuthorityId, >; - /// Global communication input stream for commits and catch up messages, with /// the hash type not being derived from the block, useful for forcing the hash /// to some type (e.g. `H256`) when the compiler can't do the inference. type CommunicationInH = - finality_grandpa::voter::CommunicationIn, AuthoritySignature, AuthorityId>; + voter::CommunicationIn, AuthoritySignature, AuthorityId>; /// Global communication sink for commits with the hash type not being derived /// from the block, useful for forcing the hash to some type (e.g. `H256`) when /// the compiler can't do the inference. type CommunicationOutH = - finality_grandpa::voter::CommunicationOut, AuthoritySignature, AuthorityId>; + voter::CommunicationOut, AuthoritySignature, AuthorityId>; /// Shared voter state for querying. pub struct SharedVoterState { @@ -230,7 +196,7 @@ impl SharedVoterState { } /// Get the inner `VoterState` instance. - pub fn voter_state(&self) -> Option> { + pub fn voter_state(&self) -> Option> { self.inner.read().as_ref().map(|vs| vs.get()) } } @@ -263,47 +229,52 @@ pub struct Config { pub keystore: Option, /// TelemetryHandle instance. pub telemetry: Option, + /// Chain specific GRANDPA protocol name. See [`crate::protocol_standard_name`]. + pub protocol_name: ProtocolName, } impl Config { fn name(&self) -> &str { - self.name.as_ref().map(|s| s.as_str()).unwrap_or("") + self.name.as_deref().unwrap_or("") } } /// Errors that can occur while voting in GRANDPA. -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// An error within grandpa. - Grandpa(GrandpaError), + #[error("grandpa error: {0}")] + Grandpa(#[from] GrandpaError), + /// A network error. + #[error("network error: {0}")] Network(String), + /// A blockchain error. + #[error("blockchain error: {0}")] Blockchain(String), + /// Could not complete a round on disk. - Client(ClientError), + #[error("could not complete a round on disk: {0}")] + Client(#[from] ClientError), + /// Could not sign outgoing message + #[error("could not sign outgoing message: {0}")] Signing(String), + /// An invariant has been violated (e.g. not finalizing pending change blocks in-order) + #[error("safety invariant has been violated: {0}")] Safety(String), + /// A timer failed to fire. + #[error("a timer failed to fire: {0}")] Timer(io::Error), + /// A runtime api request failed. + #[error("runtime API request failed: {0}")] RuntimeApi(sp_api::ApiError), } -impl From for Error { - fn from(e: GrandpaError) -> Self { - Error::Grandpa(e) - } -} - -impl From for Error { - fn from(e: ClientError) -> Self { - Error::Client(e) - } -} - /// Something which can determine if a block is known. pub(crate) trait BlockStatus { /// Return `Ok(Some(number))` or `Ok(None)` depending on whether the block @@ -319,7 +290,7 @@ where { fn block_number(&self, hash: Block::Hash) -> Result>, Error> { self.block_number_from_id(&BlockId::Hash(hash)) - .map_err(|e| Error::Blockchain(format!("{:?}", e))) + .map_err(|e| Error::Blockchain(e.to_string())) } } @@ -456,7 +427,7 @@ impl ::std::error::Error for CommandOrError impl fmt::Display for CommandOrError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - CommandOrError::Error(ref e) => write!(f, "{:?}", e), + CommandOrError::Error(ref e) => write!(f, "{}", e), CommandOrError::VoterCommand(ref cmd) => write!(f, "{}", cmd), } } @@ -506,7 +477,6 @@ where "GrandpaApi_grandpa_authorities", &[], ExecutionStrategy::NativeElseWasm, - None, ) .and_then(|call_result| { Decode::decode(&mut &call_result[..]).map_err(|err| { @@ -541,6 +511,24 @@ where ) } +/// A descriptor for an authority set hard fork. These are authority set changes +/// that are not signalled by the runtime and instead are defined off-chain +/// (hence the hard fork). +pub struct AuthoritySetHardFork { + /// The new authority set id. + pub set_id: SetId, + /// The block hash and number at which the hard fork should be applied. + pub block: (Block::Hash, NumberFor), + /// The authorities in the new set. + pub authorities: AuthorityList, + /// The latest block number that was finalized before this authority set + /// hard fork. When defined, the authority set change will be forced, i.e. + /// the node won't wait for the block above to be finalized before enacting + /// the change, and the given finalized number will be used as a base for + /// voting. + pub last_finalized: Option>, +} + /// Make block importer and link half necessary to tie the background voter to /// it. A vector of authority set hard forks can be passed, any authority set /// change signaled at the given block (either already signalled or in a further @@ -550,7 +538,7 @@ pub fn block_import_with_authority_set_hard_forks client: Arc, genesis_authorities_provider: &dyn GenesisAuthoritySetProvider, select_chain: SC, - authority_set_hard_forks: Vec<(SetId, (Block::Hash, NumberFor), AuthorityList)>, + authority_set_hard_forks: Vec>, telemetry: Option, ) -> Result<(GrandpaBlockImport, LinkHalf), ClientError> where @@ -580,19 +568,24 @@ where let (justification_sender, justification_stream) = GrandpaJustificationStream::channel(); - // create pending change objects with 0 delay and enacted on finality - // (i.e. standard changes) for each authority set hard fork. + // create pending change objects with 0 delay for each authority set hard fork. let authority_set_hard_forks = authority_set_hard_forks .into_iter() - .map(|(set_id, (hash, number), authorities)| { + .map(|fork| { + let delay_kind = if let Some(last_finalized) = fork.last_finalized { + authorities::DelayKind::Best { median_last_finalized: last_finalized } + } else { + authorities::DelayKind::Finalized + }; + ( - set_id, + fork.set_id, authorities::PendingChange { - next_authorities: authorities, + next_authorities: fork.authorities, delay: Zero::zero(), - canon_hash: hash, - canon_height: number, - delay_kind: authorities::DelayKind::Finalized, + canon_hash: fork.block.0, + canon_height: fork.block.1, + delay_kind, }, ) }) @@ -691,17 +684,22 @@ pub struct GrandpaParams { /// Returns the configuration value to put in /// [`sc_network::config::NetworkConfiguration::extra_sets`]. -pub fn grandpa_peers_set_config() -> sc_network::config::NonDefaultSetConfig { - sc_network::config::NonDefaultSetConfig { - notifications_protocol: communication::GRANDPA_PROTOCOL_NAME.into(), - fallback_names: Vec::new(), +/// For standard protocol name see [`crate::protocol_standard_name`]. +pub fn grandpa_peers_set_config( + protocol_name: ProtocolName, +) -> sc_network_common::config::NonDefaultSetConfig { + use communication::grandpa_protocol_name; + sc_network_common::config::NonDefaultSetConfig { + notifications_protocol: protocol_name, + fallback_names: grandpa_protocol_name::LEGACY_NAMES.iter().map(|&n| n.into()).collect(), // Notifications reach ~256kiB in size at the time of writing on Kusama and Polkadot. max_notification_size: 1024 * 1024, - set_config: sc_network::config::SetConfig { + handshake: None, + set_config: sc_network_common::config::SetConfig { in_peers: 0, out_peers: 0, reserved_nodes: Vec::new(), - non_reserved_mode: sc_network::config::NonReservedPeerMode::Deny, + non_reserved_mode: sc_network_common::config::NonReservedPeerMode::Deny, }, } } @@ -718,7 +716,6 @@ where SC: SelectChain + 'static, VR: VotingRule + Clone + 'static, NumberFor: BlockNumberOps, - DigestFor: Encode, C: ClientForGrandpa + 'static, C::Api: GrandpaApi, { @@ -764,8 +761,8 @@ where let events = telemetry_on_connect.for_each(move |_| { let current_authorities = authorities.current_authorities(); let set_id = authorities.set_id(); - let authority_id = local_authority_id(¤t_authorities, conf.keystore.as_ref()) - .unwrap_or_default(); + let maybe_authority_id = + local_authority_id(¤t_authorities, conf.keystore.as_ref()); let authorities = current_authorities.iter().map(|(id, _)| id.to_string()).collect::>(); @@ -779,7 +776,7 @@ where telemetry; CONSENSUS_INFO; "afg.authority_set"; - "authority_id" => authority_id.to_string(), + "authority_id" => maybe_authority_id.map_or("".into(), |s| s.to_string()), "authority_set_id" => ?set_id, "authorities" => authorities, ); @@ -809,7 +806,7 @@ where Ok(()) => error!(target: "afg", "GRANDPA voter future has concluded naturally, this should be unreachable." ), - Err(e) => error!(target: "afg", "GRANDPA voter error: {:?}", e), + Err(e) => error!(target: "afg", "GRANDPA voter error: {}", e), }); // Make sure that `telemetry_task` doesn't accidentally finish and kill grandpa. @@ -918,8 +915,9 @@ where fn rebuild_voter(&mut self) { debug!(target: "afg", "{}: Starting new voter with set ID {}", self.env.config.name(), self.env.set_id); - let authority_id = local_authority_id(&self.env.voters, self.env.config.keystore.as_ref()) - .unwrap_or_default(); + let maybe_authority_id = + local_authority_id(&self.env.voters, self.env.config.keystore.as_ref()); + let authority_id = maybe_authority_id.map_or("".into(), |s| s.to_string()); telemetry!( self.telemetry; @@ -927,7 +925,7 @@ where "afg.starting_new_voter"; "name" => ?self.env.config.name(), "set_id" => ?self.env.set_id, - "authority_id" => authority_id.to_string(), + "authority_id" => authority_id, ); let chain_info = self.env.client.info(); @@ -944,7 +942,7 @@ where "afg.authority_set"; "number" => ?chain_info.finalized_number, "hash" => ?chain_info.finalized_hash, - "authority_id" => authority_id.to_string(), + "authority_id" => authority_id, "authority_set_id" => ?self.env.set_id, "authorities" => authorities, ); @@ -1129,3 +1127,54 @@ fn local_authority_id( .map(|(p, _)| p.clone()) }) } + +/// Reverts protocol aux data to at most the last finalized block. +/// In particular, standard and forced authority set changes announced after the +/// revert point are removed. +pub fn revert(client: Arc, blocks: NumberFor) -> ClientResult<()> +where + Block: BlockT, + Client: AuxStore + HeaderMetadata + HeaderBackend, +{ + let best_number = client.info().best_number; + let finalized = client.info().finalized_number; + + let revertible = blocks.min(best_number - finalized); + if revertible == Zero::zero() { + return Ok(()) + } + + let number = best_number - revertible; + let hash = client + .block_hash_from_id(&BlockId::Number(number))? + .ok_or(ClientError::Backend(format!( + "Unexpected hash lookup failure for block number: {}", + number + )))?; + + let info = client.info(); + + let persistent_data: PersistentData = + aux_schema::load_persistent(&*client, info.genesis_hash, Zero::zero(), || { + const MSG: &str = "Unexpected missing grandpa data during revert"; + Err(ClientError::Application(Box::from(MSG))) + })?; + + let shared_authority_set = persistent_data.authority_set; + let mut authority_set = shared_authority_set.inner(); + + let is_descendent_of = is_descendent_of(&*client, None); + authority_set.revert(hash, number, &is_descendent_of); + + // The following has the side effect to properly reset the current voter state. + let (set_id, set_ref) = authority_set.current(); + let new_set = Some(NewAuthoritySet { + canon_hash: info.finalized_hash, + canon_number: info.finalized_number, + set_id, + authorities: set_ref.to_vec(), + }); + aux_schema::update_authority_set::(&authority_set, new_set.as_ref(), |values| { + client.insert_aux(values, None) + }) +} diff --git a/client/finality-grandpa/src/notification.rs b/client/finality-grandpa/src/notification.rs index 85d581bd5065e..1d6e25e55dc65 100644 --- a/client/finality-grandpa/src/notification.rs +++ b/client/finality-grandpa/src/notification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,61 +16,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use parking_lot::Mutex; -use std::sync::Arc; +use sc_utils::notification::{NotificationSender, NotificationStream, TracingKeyStr}; -use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; -use sp_runtime::traits::Block as BlockT; - -use crate::{justification::GrandpaJustification, Error}; - -// Stream of justifications returned when subscribing. -type JustificationStream = TracingUnboundedReceiver>; - -// Sending endpoint for notifying about justifications. -type JustificationSender = TracingUnboundedSender>; - -// Collection of channel sending endpoints shared with the receiver side so they can register -// themselves. -type SharedJustificationSenders = Arc>>>; +use crate::justification::GrandpaJustification; /// The sending half of the Grandpa justification channel(s). /// /// Used to send notifications about justifications generated /// at the end of a Grandpa round. -#[derive(Clone)] -pub struct GrandpaJustificationSender { - subscribers: SharedJustificationSenders, -} - -impl GrandpaJustificationSender { - /// The `subscribers` should be shared with a corresponding - /// `GrandpaJustificationStream`. - fn new(subscribers: SharedJustificationSenders) -> Self { - Self { subscribers } - } - - /// Send out a notification to all subscribers that a new justification - /// is available for a block. - pub fn notify( - &self, - justification: impl FnOnce() -> Result, Error>, - ) -> Result<(), Error> { - let mut subscribers = self.subscribers.lock(); - - // do an initial prune on closed subscriptions - subscribers.retain(|n| !n.is_closed()); - - // if there's no subscribers we avoid creating - // the justification which is a costly operation - if !subscribers.is_empty() { - let justification = justification()?; - subscribers.retain(|n| n.unbounded_send(justification.clone()).is_ok()); - } - - Ok(()) - } -} +pub type GrandpaJustificationSender = NotificationSender>; /// The receiving half of the Grandpa justification channel. /// @@ -78,33 +32,12 @@ impl GrandpaJustificationSender { /// at the end of a Grandpa round. /// The `GrandpaJustificationStream` entity stores the `SharedJustificationSenders` /// so it can be used to add more subscriptions. -#[derive(Clone)] -pub struct GrandpaJustificationStream { - subscribers: SharedJustificationSenders, -} - -impl GrandpaJustificationStream { - /// Creates a new pair of receiver and sender of justification notifications. - pub fn channel() -> (GrandpaJustificationSender, Self) { - let subscribers = Arc::new(Mutex::new(vec![])); - let receiver = GrandpaJustificationStream::new(subscribers.clone()); - let sender = GrandpaJustificationSender::new(subscribers.clone()); - (sender, receiver) - } +pub type GrandpaJustificationStream = + NotificationStream, GrandpaJustificationsTracingKey>; - /// Create a new receiver of justification notifications. - /// - /// The `subscribers` should be shared with a corresponding - /// `GrandpaJustificationSender`. - fn new(subscribers: SharedJustificationSenders) -> Self { - Self { subscribers } - } - - /// Subscribe to a channel through which justifications are sent - /// at the end of each Grandpa voting round. - pub fn subscribe(&self) -> JustificationStream { - let (sender, receiver) = tracing_unbounded("mpsc_justification_notification_stream"); - self.subscribers.lock().push(sender); - receiver - } +/// Provides tracing key for GRANDPA justifications stream. +#[derive(Clone)] +pub struct GrandpaJustificationsTracingKey; +impl TracingKeyStr for GrandpaJustificationsTracingKey { + const TRACING_KEY: &'static str = "mpsc_grandpa_justification_notification_stream"; } diff --git a/client/finality-grandpa/src/observer.rs b/client/finality-grandpa/src/observer.rs index 70a94cd504726..9bcb03c0555c2 100644 --- a/client/finality-grandpa/src/observer.rs +++ b/client/finality-grandpa/src/observer.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -63,7 +63,7 @@ where base: Block::Hash, block: Block::Hash, ) -> Result, GrandpaError> { - environment::ancestry(&self.client, base, block) + environment::ancestry(self.client, base, block) } } @@ -116,7 +116,7 @@ where Err(e) => return future::err(e.into()), }; - if validation_result.ghost().is_some() { + if validation_result.is_valid() { let finalized_hash = commit.target_hash; let finalized_number = commit.target_number; @@ -193,17 +193,17 @@ where ); let observer_work = ObserverWork::new( - client.clone(), + client, network, persistent_data, config.keystore, voter_commands_rx, Some(justification_sender), - telemetry.clone(), + telemetry, ); let observer_work = observer_work.map_ok(|_| ()).map_err(|e| { - warn!("GRANDPA Observer failed: {:?}", e); + warn!("GRANDPA Observer failed: {}", e); }); Ok(observer_work.map(drop)) @@ -289,7 +289,7 @@ where network.note_round( crate::communication::Round(round), crate::communication::SetId(set_id), - &*voters, + &voters, ) } }; diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 1aef7cd1b017a..6b577fd712930 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,17 +21,16 @@ use super::*; use assert_matches::assert_matches; use environment::HasVoted; -use futures::executor::block_on; use futures_timer::Delay; use parking_lot::{Mutex, RwLock}; use sc_consensus::{ BlockImport, BlockImportParams, BoxJustificationImport, ForkChoiceStrategy, ImportResult, ImportedAux, }; -use sc_network::config::{ProtocolConfig, Role}; +use sc_network::config::Role; use sc_network_test::{ Block, BlockImportAdapter, FullPeerConfig, Hash, PassThroughVerifier, Peer, PeersClient, - PeersFullClient, TestClient, TestNetFactory, + PeersFullClient, TestClient, TestNetFactory, WithRuntime, }; use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_blockchain::Result; @@ -41,8 +40,9 @@ use sp_finality_grandpa::{ AuthorityList, EquivocationProof, GrandpaApi, OpaqueKeyOwnershipProof, GRANDPA_ENGINE_ID, }; use sp_keyring::Ed25519Keyring; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_keystore::{testing::KeyStore as TestKeyStore, SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ + codec::Encode, generic::{BlockId, DigestItem}, traits::{Block as BlockT, Header as HeaderT}, Justifications, @@ -55,9 +55,9 @@ use substrate_test_runtime_client::runtime::BlockNumber; use tokio::runtime::{Handle, Runtime}; use authorities::AuthoritySet; -use sc_block_builder::BlockBuilderProvider; +use communication::grandpa_protocol_name; +use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; use sc_consensus::LongestChain; -use sc_keystore::LocalKeystore; use sp_application_crypto::key_types::GRANDPA; type TestLinkHalf = @@ -74,12 +74,23 @@ type GrandpaBlockImport = crate::GrandpaBlockImport< struct GrandpaTestNet { peers: Vec, test_config: TestApi, + rt_handle: Handle, +} + +impl WithRuntime for GrandpaTestNet { + fn with_runtime(rt_handle: Handle) -> Self { + GrandpaTestNet { peers: Vec::new(), test_config: TestApi::default(), rt_handle } + } + fn rt_handle(&self) -> &Handle { + &self.rt_handle + } } impl GrandpaTestNet { - fn new(test_config: TestApi, n_authority: usize, n_full: usize) -> Self { - let mut net = - GrandpaTestNet { peers: Vec::with_capacity(n_authority + n_full), test_config }; + fn new(test_config: TestApi, n_authority: usize, n_full: usize, rt_handle: Handle) -> Self { + let mut net = GrandpaTestNet::with_runtime(rt_handle); + net.peers = Vec::with_capacity(n_authority + n_full); + net.test_config = test_config; for _ in 0..n_authority { net.add_authority_peer(); @@ -96,7 +107,7 @@ impl GrandpaTestNet { impl GrandpaTestNet { fn add_authority_peer(&mut self) { self.add_full_peer_with_config(FullPeerConfig { - notifications_protocols: vec![communication::GRANDPA_PROTOCOL_NAME.into()], + notifications_protocols: vec![grandpa_protocol_name::NAME.into()], is_authority: true, ..Default::default() }) @@ -108,30 +119,15 @@ impl TestNetFactory for GrandpaTestNet { type PeerData = PeerData; type BlockImport = GrandpaBlockImport; - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - GrandpaTestNet { peers: Vec::new(), test_config: Default::default() } - } - - fn default_config() -> ProtocolConfig { - // This is unused. - ProtocolConfig::default() - } - fn add_full_peer(&mut self) { self.add_full_peer_with_config(FullPeerConfig { - notifications_protocols: vec![communication::GRANDPA_PROTOCOL_NAME.into()], + notifications_protocols: vec![grandpa_protocol_name::NAME.into()], is_authority: false, ..Default::default() }) } - fn make_verifier( - &self, - _client: PeersClient, - _cfg: &ProtocolConfig, - _: &PeerData, - ) -> Self::Verifier { + fn make_verifier(&self, _client: PeersClient, _: &PeerData) -> Self::Verifier { PassThroughVerifier::new(false) // use non-instant finality. } @@ -139,26 +135,16 @@ impl TestNetFactory for GrandpaTestNet { &self, client: PeersClient, ) -> (BlockImportAdapter, Option>, PeerData) { - match client { - PeersClient::Full(ref client, ref backend) => { - let (import, link) = block_import( - client.clone(), - &self.test_config, - LongestChain::new(backend.clone()), - None, - ) - .expect("Could not create block import for fresh peer."); - let justification_import = Box::new(import.clone()); - ( - BlockImportAdapter::new(import), - Some(justification_import), - Mutex::new(Some(link)), - ) - }, - PeersClient::Light(..) => { - panic!("Light client is not used in tests."); - }, - } + let (client, backend) = (client.as_client(), client.as_backend()); + let (import, link) = block_import( + client.clone(), + &self.test_config, + LongestChain::new(backend.clone()), + None, + ) + .expect("Could not create block import for fresh peer."); + let justification_import = Box::new(import.clone()); + (BlockImportAdapter::new(import), Some(justification_import), Mutex::new(Some(link))) } fn peer(&mut self, i: usize) -> &mut GrandpaPeer { @@ -192,7 +178,7 @@ pub(crate) struct RuntimeApi { impl ProvideRuntimeApi for TestApi { type Api = RuntimeApi; - fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { + fn runtime_api(&self) -> ApiRef<'_, Self::Api> { RuntimeApi { inner: self.clone() }.into() } } @@ -232,17 +218,14 @@ impl GenesisAuthoritySetProvider for TestApi { const TEST_GOSSIP_DURATION: Duration = Duration::from_millis(500); fn make_ids(keys: &[Ed25519Keyring]) -> AuthorityList { - keys.iter().map(|key| key.clone().public().into()).map(|id| (id, 1)).collect() + keys.iter().map(|&key| key.public().into()).map(|id| (id, 1)).collect() } -fn create_keystore(authority: Ed25519Keyring) -> (SyncCryptoStorePtr, tempfile::TempDir) { - let keystore_path = tempfile::tempdir().expect("Creates keystore path"); - let keystore = - Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore")); +fn create_keystore(authority: Ed25519Keyring) -> SyncCryptoStorePtr { + let keystore = Arc::new(TestKeyStore::new()); SyncCryptoStore::ed25519_generate_new(&*keystore, GRANDPA, Some(&authority.to_seed())) .expect("Creates authority key"); - - (keystore, keystore_path) + keystore } fn block_until_complete( @@ -265,7 +248,7 @@ fn initialize_grandpa( let voters = stream::FuturesUnordered::new(); for (peer_id, key) in peers.iter().enumerate() { - let (keystore, _) = create_keystore(*key); + let keystore = create_keystore(*key); let (net_service, link) = { // temporary needed for some reason @@ -283,6 +266,7 @@ fn initialize_grandpa( local_role: Role::Authority, observer_enabled: true, telemetry: None, + protocol_name: grandpa_protocol_name::NAME.into(), }, link, network: net_service, @@ -384,13 +368,15 @@ fn finalize_3_voters_no_observers() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0, runtime.handle().clone()); runtime.spawn(initialize_grandpa(&mut net, peers)); net.peer(0).push_blocks(20, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); + let hashof20 = net.peer(0).client().info().best_hash; for i in 0..3 { assert_eq!(net.peer(i).client().info().best_number, 20, "Peer #{} failed to sync", i); + assert_eq!(net.peer(i).client().info().best_hash, hashof20, "Peer #{} failed to sync", i); } let net = Arc::new(Mutex::new(net)); @@ -398,12 +384,7 @@ fn finalize_3_voters_no_observers() { // normally there's no justification for finalized blocks assert!( - net.lock() - .peer(0) - .client() - .justifications(&BlockId::Number(20)) - .unwrap() - .is_none(), + net.lock().peer(0).client().justifications(hashof20).unwrap().is_none(), "Extra justification for block#1", ); } @@ -415,7 +396,7 @@ fn finalize_3_voters_1_full_observer() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1, runtime.handle().clone()); runtime.spawn(initialize_grandpa(&mut net, peers)); runtime.spawn({ @@ -432,6 +413,7 @@ fn finalize_3_voters_1_full_observer() { local_role: Role::Authority, observer_enabled: true, telemetry: None, + protocol_name: grandpa_protocol_name::NAME.into(), }, link, network: net_service, @@ -466,11 +448,11 @@ fn finalize_3_voters_1_full_observer() { // all peers should have stored the justification for the best finalized block #20 for peer_id in 0..4 { - let client = net.lock().peers[peer_id].client().as_full().unwrap(); + let client = net.lock().peers[peer_id].client().as_client(); let justification = crate::aux_schema::best_justification::<_, Block>(&*client).unwrap().unwrap(); - assert_eq!(justification.commit.target_number, 20); + assert_eq!(justification.justification.commit.target_number, 20); } } @@ -496,15 +478,12 @@ fn transition_3_voters_twice_1_full_observer() { let genesis_voters = make_ids(peers_a); let api = TestApi::new(genesis_voters); - let net = Arc::new(Mutex::new(GrandpaTestNet::new(api, 8, 1))); - let mut runtime = Runtime::new().unwrap(); + let net = Arc::new(Mutex::new(GrandpaTestNet::new(api, 8, 1, runtime.handle().clone()))); - let mut keystore_paths = Vec::new(); let mut voters = Vec::new(); for (peer_id, local_key) in all_peers.clone().into_iter().enumerate() { - let (keystore, keystore_path) = create_keystore(local_key); - keystore_paths.push(keystore_path); + let keystore = create_keystore(local_key); let (net_service, link) = { let net = net.lock(); @@ -522,6 +501,7 @@ fn transition_3_voters_twice_1_full_observer() { local_role: Role::Authority, observer_enabled: true, telemetry: None, + protocol_name: grandpa_protocol_name::NAME.into(), }, link, network: net_service, @@ -536,10 +516,10 @@ fn transition_3_voters_twice_1_full_observer() { } net.lock().peer(0).push_blocks(1, false); - net.lock().block_until_sync(); + runtime.block_on(net.lock().wait_until_sync()); for (i, peer) in net.lock().peers().iter().enumerate() { - let full_client = peer.client().as_full().expect("only full clients are used in test"); + let full_client = peer.client().as_client(); assert_eq!(full_client.chain_info().best_number, 1, "Peer #{} failed to sync", i); let set: AuthoritySet = @@ -552,7 +532,7 @@ fn transition_3_voters_twice_1_full_observer() { { let net = net.clone(); let client = net.lock().peers[0].client().clone(); - let peers_c = peers_c.clone(); + let peers_c = *peers_c; // wait for blocks to be finalized before generating new ones let block_production = client @@ -614,7 +594,7 @@ fn transition_3_voters_twice_1_full_observer() { .take_while(|n| future::ready(n.header.number() < &30)) .for_each(move |_| future::ready(())) .map(move |()| { - let full_client = client.as_full().expect("only full clients are used in test"); + let full_client = client.as_client(); let set: AuthoritySet = crate::aux_schema::load_authorities(&*full_client).unwrap(); @@ -636,10 +616,12 @@ fn justification_is_generated_periodically() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0, runtime.handle().clone()); runtime.spawn(initialize_grandpa(&mut net, peers)); net.peer(0).push_blocks(32, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); + + let hashof32 = net.peer(0).client().info().best_hash; let net = Arc::new(Mutex::new(net)); run_to_completion(&mut runtime, 32, net.clone(), peers); @@ -647,13 +629,7 @@ fn justification_is_generated_periodically() { // when block#32 (justification_period) is finalized, justification // is required => generated for i in 0..3 { - assert!(net - .lock() - .peer(i) - .client() - .justifications(&BlockId::Number(32)) - .unwrap() - .is_some()); + assert!(net.lock().peer(i).client().justifications(hashof32).unwrap().is_some()); } } @@ -666,14 +642,14 @@ fn sync_justifications_on_change_blocks() { // 4 peers, 3 of them are authorities and participate in grandpa let api = TestApi::new(voters); - let mut net = GrandpaTestNet::new(api, 3, 1); + let mut net = GrandpaTestNet::new(api, 3, 1, runtime.handle().clone()); let voters = initialize_grandpa(&mut net, peers_a); // add 20 blocks net.peer(0).push_blocks(20, false); // at block 21 we do add a transition which is instant - net.peer(0).generate_blocks(1, BlockOrigin::File, |builder| { + let hashof21 = net.peer(0).generate_blocks(1, BlockOrigin::File, |builder| { let mut block = builder.build().unwrap().block; add_scheduled_change( &mut block, @@ -684,7 +660,7 @@ fn sync_justifications_on_change_blocks() { // add more blocks on top of it (until we have 25) net.peer(0).push_blocks(4, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); for i in 0..4 { assert_eq!(net.peer(i).client().info().best_number, 25, "Peer #{} failed to sync", i); @@ -697,25 +673,12 @@ fn sync_justifications_on_change_blocks() { // the first 3 peers are grandpa voters and therefore have already finalized // block 21 and stored a justification for i in 0..3 { - assert!(net - .lock() - .peer(i) - .client() - .justifications(&BlockId::Number(21)) - .unwrap() - .is_some()); + assert!(net.lock().peer(i).client().justifications(hashof21).unwrap().is_some()); } // the last peer should get the justification by syncing from other peers futures::executor::block_on(futures::future::poll_fn(move |cx| { - if net - .lock() - .peer(3) - .client() - .justifications(&BlockId::Number(21)) - .unwrap() - .is_none() - { + if net.lock().peer(3).client().justifications(hashof21).unwrap().is_none() { net.lock().poll(cx); Poll::Pending } else { @@ -747,7 +710,7 @@ fn finalizes_multiple_pending_changes_in_order() { // but all of them will be part of the voter set eventually so they should be // all added to the network as authorities let api = TestApi::new(genesis_voters); - let mut net = GrandpaTestNet::new(api, 6, 0); + let mut net = GrandpaTestNet::new(api, 6, 0, runtime.handle().clone()); runtime.spawn(initialize_grandpa(&mut net, all_peers)); // add 20 blocks @@ -779,7 +742,7 @@ fn finalizes_multiple_pending_changes_in_order() { // add more blocks on top of it (until we have 30) net.peer(0).push_blocks(4, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); // all peers imported both change blocks for i in 0..6 { @@ -806,7 +769,7 @@ fn force_change_to_new_set() { let api = TestApi::new(make_ids(genesis_authorities)); let voters = make_ids(peers_a); - let mut net = GrandpaTestNet::new(api, 3, 0); + let mut net = GrandpaTestNet::new(api, 3, 0, runtime.handle().clone()); let voters_future = initialize_grandpa(&mut net, peers_a); let net = Arc::new(Mutex::new(net)); @@ -830,12 +793,12 @@ fn force_change_to_new_set() { }); net.lock().peer(0).push_blocks(25, false); - net.lock().block_until_sync(); + runtime.block_on(net.lock().wait_until_sync()); for (i, peer) in net.lock().peers().iter().enumerate() { assert_eq!(peer.client().info().best_number, 26, "Peer #{} failed to sync", i); - let full_client = peer.client().as_full().expect("only full clients are used in test"); + let full_client = peer.client().as_client(); let set: AuthoritySet = crate::aux_schema::load_authorities(&*full_client).unwrap(); @@ -856,12 +819,13 @@ fn allows_reimporting_change_blocks() { let peers_b = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob]; let voters = make_ids(peers_a); let api = TestApi::new(voters); - let mut net = GrandpaTestNet::new(api.clone(), 3, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(api.clone(), 3, 0, runtime.handle().clone()); let client = net.peer(0).client().clone(); let (mut block_import, ..) = net.make_block_import(client.clone()); - let full_client = client.as_full().unwrap(); + let full_client = client.as_client(); let builder = full_client .new_block_at(&BlockId::Number(0), Default::default(), false) .unwrap(); @@ -881,7 +845,7 @@ fn allows_reimporting_change_blocks() { }; assert_eq!( - block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false, @@ -892,7 +856,7 @@ fn allows_reimporting_change_blocks() { ); assert_eq!( - block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), ImportResult::AlreadyInChain ); } @@ -903,12 +867,13 @@ fn test_bad_justification() { let peers_b = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob]; let voters = make_ids(peers_a); let api = TestApi::new(voters); - let mut net = GrandpaTestNet::new(api.clone(), 3, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(api.clone(), 3, 0, runtime.handle().clone()); let client = net.peer(0).client().clone(); let (mut block_import, ..) = net.make_block_import(client.clone()); - let full_client = client.as_full().expect("only full clients are used in test"); + let full_client = client.as_client(); let builder = full_client .new_block_at(&BlockId::Number(0), Default::default(), false) .unwrap(); @@ -930,7 +895,7 @@ fn test_bad_justification() { }; assert_eq!( - block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false, @@ -941,7 +906,7 @@ fn test_bad_justification() { ); assert_eq!( - block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), ImportResult::AlreadyInChain ); } @@ -953,7 +918,6 @@ fn voter_persists_its_votes() { sp_tracing::try_init_simple(); let mut runtime = Runtime::new().unwrap(); - let mut keystore_paths = Vec::new(); // we have two authorities but we'll only be running the voter for alice // we are going to be listening for the prevotes it casts @@ -961,16 +925,12 @@ fn voter_persists_its_votes() { let voters = make_ids(peers); // alice has a chain with 20 blocks - let mut net = GrandpaTestNet::new(TestApi::new(voters.clone()), 2, 0); + let mut net = GrandpaTestNet::new(TestApi::new(voters.clone()), 2, 0, runtime.handle().clone()); // create the communication layer for bob, but don't start any // voter. instead we'll listen for the prevote that alice casts // and cast our own manually - let bob_keystore = { - let (keystore, keystore_path) = create_keystore(peers[1]); - keystore_paths.push(keystore_path); - keystore - }; + let bob_keystore = create_keystore(peers[1]); let bob_network = { let config = Config { gossip_duration: TEST_GOSSIP_DURATION, @@ -980,6 +940,7 @@ fn voter_persists_its_votes() { local_role: Role::Authority, observer_enabled: true, telemetry: None, + protocol_name: grandpa_protocol_name::NAME.into(), }; let set_state = { @@ -1002,7 +963,7 @@ fn voter_persists_its_votes() { // spawn two voters for alice. // half-way through the test, we stop one and start the other. let (alice_voter1, abort) = future::abortable({ - let (keystore, _) = create_keystore(peers[0]); + let keystore = create_keystore(peers[0]); let (net_service, link) = { // temporary needed for some reason @@ -1019,6 +980,7 @@ fn voter_persists_its_votes() { local_role: Role::Authority, observer_enabled: true, telemetry: None, + protocol_name: grandpa_protocol_name::NAME.into(), }, link, network: net_service, @@ -1035,7 +997,7 @@ fn voter_persists_its_votes() { peers: &[Ed25519Keyring], net: Arc>, ) -> impl Future + Send { - let (keystore, _) = create_keystore(peers[0]); + let keystore = create_keystore(peers[0]); let mut net = net.lock(); // we add a new peer to the test network and we'll use @@ -1059,6 +1021,7 @@ fn voter_persists_its_votes() { local_role: Role::Authority, observer_enabled: true, telemetry: None, + protocol_name: grandpa_protocol_name::NAME.into(), }, link, network: net_service, @@ -1082,7 +1045,7 @@ fn voter_persists_its_votes() { runtime.spawn(alice_voter1); net.peer(0).push_blocks(20, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert_eq!(net.peer(0).client().info().best_number, 20, "Peer #{} failed to sync", 0); @@ -1148,7 +1111,7 @@ fn voter_persists_its_votes() { .await; let block_30_hash = - net.lock().peer(0).client().as_full().unwrap().hash(30).unwrap().unwrap(); + net.lock().peer(0).client().as_client().hash(30).unwrap().unwrap(); // we restart alice's voter abort.abort(); @@ -1211,7 +1174,7 @@ fn finalize_3_voters_1_light_observer() { let authorities = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(authorities); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1, runtime.handle().clone()); let voters = initialize_grandpa(&mut net, authorities); let observer = observer::run_grandpa_observer( Config { @@ -1222,13 +1185,14 @@ fn finalize_3_voters_1_light_observer() { local_role: Role::Full, observer_enabled: true, telemetry: None, + protocol_name: grandpa_protocol_name::NAME.into(), }, net.peers[3].data.lock().take().expect("link initialized at startup; qed"), net.peers[3].network_service().clone(), ) .unwrap(); net.peer(0).push_blocks(20, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); for i in 0..4 { assert_eq!(net.peer(i).client().info().best_number, 20, "Peer #{} failed to sync", i); @@ -1249,7 +1213,7 @@ fn voter_catches_up_to_latest_round_when_behind() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob]; let voters = make_ids(peers); - let net = GrandpaTestNet::new(TestApi::new(voters), 2, 0); + let net = GrandpaTestNet::new(TestApi::new(voters), 2, 0, runtime.handle().clone()); let net = Arc::new(Mutex::new(net)); let mut finality_notifications = Vec::new(); @@ -1268,6 +1232,7 @@ fn voter_catches_up_to_latest_round_when_behind() { local_role: Role::Authority, observer_enabled: true, telemetry: None, + protocol_name: grandpa_protocol_name::NAME.into(), }, link, network: net.lock().peer(peer_id).network_service().clone(), @@ -1280,8 +1245,6 @@ fn voter_catches_up_to_latest_round_when_behind() { Box::pin(run_grandpa_voter(grandpa_params).expect("all in order with client and network")) }; - let mut keystore_paths = Vec::new(); - // spawn authorities for (peer_id, key) in peers.iter().enumerate() { let (client, link) = { @@ -1298,8 +1261,7 @@ fn voter_catches_up_to_latest_round_when_behind() { .for_each(move |_| future::ready(())), ); - let (keystore, keystore_path) = create_keystore(*key); - keystore_paths.push(keystore_path); + let keystore = create_keystore(*key); let voter = voter(Some(keystore), peer_id, link, net.clone()); @@ -1307,7 +1269,7 @@ fn voter_catches_up_to_latest_round_when_behind() { } net.lock().peer(0).push_blocks(50, false); - net.lock().block_until_sync(); + runtime.block_on(net.lock().wait_until_sync()); // wait for them to finalize block 50. since they'll vote on 3/4 of the // unfinalized chain it will take at least 4 rounds to do it. @@ -1385,6 +1347,7 @@ where local_role: Role::Authority, observer_enabled: true, telemetry: None, + protocol_name: grandpa_protocol_name::NAME.into(), }; let network = @@ -1414,7 +1377,8 @@ fn grandpa_environment_respects_voting_rules() { let peers = &[Ed25519Keyring::Alice]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0, runtime.handle().clone()); let peer = net.peer(0); let network_service = peer.network_service().clone(); let link = peer.data.lock().take().unwrap(); @@ -1444,7 +1408,8 @@ fn grandpa_environment_respects_voting_rules() { // the unrestricted environment should just return the best block assert_eq!( - block_on(unrestricted_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(unrestricted_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1454,7 +1419,8 @@ fn grandpa_environment_respects_voting_rules() { // both the other environments should return block 16, which is 3/4 of the // way in the unfinalized chain assert_eq!( - block_on(three_quarters_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(three_quarters_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1462,7 +1428,8 @@ fn grandpa_environment_respects_voting_rules() { ); assert_eq!( - block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1470,11 +1437,17 @@ fn grandpa_environment_respects_voting_rules() { ); // we finalize block 19 with block 21 being the best block - peer.client().finalize_block(BlockId::Number(19), None, false).unwrap(); + let hashof19 = peer + .client() + .as_client() + .expect_block_hash_from_id(&BlockId::Number(19)) + .unwrap(); + peer.client().finalize_block(hashof19, None, false).unwrap(); // the 3/4 environment should propose block 21 for voting assert_eq!( - block_on(three_quarters_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(three_quarters_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1484,7 +1457,8 @@ fn grandpa_environment_respects_voting_rules() { // while the default environment will always still make sure we don't vote // on the best block (2 behind) assert_eq!( - block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1492,13 +1466,19 @@ fn grandpa_environment_respects_voting_rules() { ); // we finalize block 21 with block 21 being the best block - peer.client().finalize_block(BlockId::Number(21), None, false).unwrap(); + let hashof21 = peer + .client() + .as_client() + .expect_block_hash_from_id(&BlockId::Number(21)) + .unwrap(); + peer.client().finalize_block(hashof21, None, false).unwrap(); // even though the default environment will always try to not vote on the // best block, there's a hard rule that we can't cast any votes lower than // the given base (#21). assert_eq!( - block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1513,12 +1493,13 @@ fn grandpa_environment_never_overwrites_round_voter_state() { let peers = &[Ed25519Keyring::Alice]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0, runtime.handle().clone()); let peer = net.peer(0); let network_service = peer.network_service().clone(); let link = peer.data.lock().take().unwrap(); - let (keystore, _keystore_path) = create_keystore(peers[0]); + let keystore = create_keystore(peers[0]); let environment = test_environment(&link, Some(keystore), network_service.clone(), ()); let round_state = || finality_grandpa::round::State::genesis(Default::default()); @@ -1568,6 +1549,74 @@ fn grandpa_environment_never_overwrites_round_voter_state() { assert_matches!(get_current_round(2).unwrap(), HasVoted::Yes(_, _)); } +#[test] +fn justification_with_equivocation() { + use sp_application_crypto::Pair; + + // we have 100 authorities + let pairs = (0..100).map(|n| AuthorityPair::from_seed(&[n; 32])).collect::>(); + let voters = pairs.iter().map(AuthorityPair::public).map(|id| (id, 1)).collect::>(); + let api = TestApi::new(voters.clone()); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(api.clone(), 1, 0, runtime.handle().clone()); + + // we create a basic chain with 3 blocks (no forks) + net.peer(0).push_blocks(3, false); + + let client = net.peer(0).client().clone(); + let block1 = client.header(&BlockId::Number(1)).ok().flatten().unwrap(); + let block2 = client.header(&BlockId::Number(2)).ok().flatten().unwrap(); + let block3 = client.header(&BlockId::Number(3)).ok().flatten().unwrap(); + + let set_id = 0; + let justification = { + let round = 1; + + let make_precommit = |target_hash, target_number, pair: &AuthorityPair| { + let precommit = finality_grandpa::Precommit { target_hash, target_number }; + + let msg = finality_grandpa::Message::Precommit(precommit.clone()); + let encoded = sp_finality_grandpa::localized_payload(round, set_id, &msg); + + let precommit = finality_grandpa::SignedPrecommit { + precommit: precommit.clone(), + signature: pair.sign(&encoded[..]), + id: pair.public(), + }; + + precommit + }; + + let mut precommits = Vec::new(); + + // we have 66/100 votes for block #3 and therefore do not have threshold to finalize + for pair in pairs.iter().take(66) { + let precommit = make_precommit(block3.hash(), *block3.number(), pair); + precommits.push(precommit); + } + + // we create an equivocation for the 67th validator targetting blocks #1 and #2. + // this should be accounted as "voting for all blocks" and therefore block #3 will + // have 67/100 votes, reaching finality threshold. + { + precommits.push(make_precommit(block1.hash(), *block1.number(), &pairs[66])); + precommits.push(make_precommit(block2.hash(), *block2.number(), &pairs[66])); + } + + let commit = finality_grandpa::Commit { + target_hash: block3.hash(), + target_number: *block3.number(), + precommits, + }; + + GrandpaJustification::from_commit(&client.as_client(), round, commit).unwrap() + }; + + // the justification should include the minimal necessary vote ancestry and + // the commit should be valid + assert!(justification.verify(set_id, &voters).is_ok()); +} + #[test] fn imports_justification_for_regular_blocks_on_import() { // NOTE: this is a regression test since initially we would only import @@ -1576,12 +1625,13 @@ fn imports_justification_for_regular_blocks_on_import() { let peers = &[Ed25519Keyring::Alice]; let voters = make_ids(peers); let api = TestApi::new(voters); - let mut net = GrandpaTestNet::new(api.clone(), 1, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(api.clone(), 1, 0, runtime.handle().clone()); let client = net.peer(0).client().clone(); let (mut block_import, ..) = net.make_block_import(client.clone()); - let full_client = client.as_full().expect("only full clients are used in test"); + let full_client = client.as_client(); let builder = full_client .new_block_at(&BlockId::Number(0), Default::default(), false) .unwrap(); @@ -1625,7 +1675,7 @@ fn imports_justification_for_regular_blocks_on_import() { import.fork_choice = Some(ForkChoiceStrategy::LongestChain); assert_eq!( - block_on(block_import.import_block(import, HashMap::new())).unwrap(), + runtime.block_on(block_import.import_block(import, HashMap::new())).unwrap(), ImportResult::Imported(ImportedAux { needs_justification: false, clear_justification_requests: false, @@ -1636,7 +1686,7 @@ fn imports_justification_for_regular_blocks_on_import() { ); // the justification should be imported and available from the client - assert!(client.justifications(&BlockId::Hash(block_hash)).unwrap().is_some()); + assert!(client.justifications(block_hash).unwrap().is_some()); } #[test] @@ -1646,12 +1696,14 @@ fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() { let alice = Ed25519Keyring::Alice; let voters = make_ids(&[alice]); + let runtime = Runtime::new().unwrap(); + let environment = { - let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0, runtime.handle().clone()); let peer = net.peer(0); let network_service = peer.network_service().clone(); let link = peer.data.lock().take().unwrap(); - let (keystore, _keystore_path) = create_keystore(alice); + let keystore = create_keystore(alice); test_environment(&link, Some(keystore), network_service.clone(), ()) }; @@ -1680,7 +1732,133 @@ fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() { // if we set the equivocation offender to another id for which we don't have // keys it should work - equivocation.identity = Default::default(); + equivocation.identity = TryFrom::try_from(&[1; 32][..]).unwrap(); let equivocation_proof = sp_finality_grandpa::Equivocation::Prevote(equivocation); assert!(environment.report_equivocation(equivocation_proof).is_ok()); } + +#[test] +fn revert_prunes_authority_changes() { + sp_tracing::try_init_simple(); + let runtime = Runtime::new().unwrap(); + + let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; + + type TestBlockBuilder<'a> = + BlockBuilder<'a, Block, PeersFullClient, substrate_test_runtime_client::Backend>; + let edit_block = |builder: TestBlockBuilder| { + let mut block = builder.build().unwrap().block; + add_scheduled_change( + &mut block, + ScheduledChange { next_authorities: make_ids(peers), delay: 0 }, + ); + block + }; + + let api = TestApi::new(make_ids(peers)); + + let mut net = GrandpaTestNet::new(api, 3, 0, runtime.handle().clone()); + runtime.spawn(initialize_grandpa(&mut net, peers)); + + let peer = net.peer(0); + let client = peer.client().as_client(); + + // Test scenario: (X) = auth-change, 24 = revert-point + // + // +---------(27) + // / + // 0---(21)---23---24---25---(28)---30 + // ^ \ + // revert-point +------(29) + + // Construct canonical chain + + // add 20 blocks + peer.push_blocks(20, false); + // at block 21 we add an authority transition + peer.generate_blocks(1, BlockOrigin::File, edit_block); + // add more blocks on top of it (until we have 24) + peer.push_blocks(3, false); + // add more blocks on top of it (until we have 27) + peer.push_blocks(3, false); + // at block 28 we add an authority transition + peer.generate_blocks(1, BlockOrigin::File, edit_block); + // add more blocks on top of it (until we have 30) + peer.push_blocks(2, false); + + // Fork before revert point + + // add more blocks on top of block 23 (until we have 26) + let hash = peer.generate_blocks_at( + BlockId::Number(23), + 3, + BlockOrigin::File, + |builder| { + let mut block = builder.build().unwrap().block; + block.header.digest_mut().push(DigestItem::Other(vec![1])); + block + }, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ); + // at block 27 of the fork add an authority transition + peer.generate_blocks_at( + BlockId::Hash(hash), + 1, + BlockOrigin::File, + edit_block, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ); + + // Fork after revert point + + // add more block on top of block 25 (until we have 28) + let hash = peer.generate_blocks_at( + BlockId::Number(25), + 3, + BlockOrigin::File, + |builder| { + let mut block = builder.build().unwrap().block; + block.header.digest_mut().push(DigestItem::Other(vec![2])); + block + }, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ); + // at block 29 of the fork add an authority transition + peer.generate_blocks_at( + BlockId::Hash(hash), + 1, + BlockOrigin::File, + edit_block, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ); + + revert(client.clone(), 6).unwrap(); + + let persistent_data: PersistentData = aux_schema::load_persistent( + &*client, + client.info().genesis_hash, + Zero::zero(), + || unreachable!(), + ) + .unwrap(); + let changes_num: Vec<_> = persistent_data + .authority_set + .inner() + .pending_standard_changes + .iter() + .map(|(_, n, _)| *n) + .collect(); + assert_eq!(changes_num, [21, 27]); +} diff --git a/client/finality-grandpa/src/until_imported.rs b/client/finality-grandpa/src/until_imported.rs index deb6577264347..df0b63348e94b 100644 --- a/client/finality-grandpa/src/until_imported.rs +++ b/client/finality-grandpa/src/until_imported.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -100,7 +100,7 @@ impl Metrics { Ok(Self { global_waiting_messages: register( Gauge::new( - "finality_grandpa_until_imported_waiting_messages_number", + "substrate_finality_grandpa_until_imported_waiting_messages_number", "Number of finality grandpa messages waiting within the until imported queue.", )?, registry, @@ -354,7 +354,7 @@ fn warn_authority_wrong_target(hash: H, id: AuthorityId) ); } -impl BlockUntilImported for SignedMessage { +impl BlockUntilImported for SignedMessage { type Blocked = Self; fn needs_waiting>( @@ -389,8 +389,13 @@ impl BlockUntilImported for SignedMessage { /// Helper type definition for the stream which waits until vote targets for /// signed messages are imported. -pub(crate) type UntilVoteTargetImported = - UntilImported>; +pub(crate) type UntilVoteTargetImported = UntilImported< + Block, + BlockStatus, + BlockSyncRequester, + I, + SignedMessage<::Header>, +>; /// This blocks a global message import, i.e. a commit or catch up messages, /// until all blocks referenced in its votes are known. @@ -563,6 +568,7 @@ mod tests { use sc_client_api::BlockImportNotification; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; use sp_consensus::BlockOrigin; + use sp_core::crypto::UncheckedFrom; use substrate_test_runtime_client::runtime::{Block, Hash, Header}; #[derive(Clone)] @@ -586,7 +592,7 @@ mod tests { fn import_header(&self, header: Header) { let hash = header.hash(); - let number = header.number().clone(); + let number = *header.number(); self.known_blocks.lock().insert(hash, number); self.sender @@ -607,7 +613,7 @@ mod tests { impl BlockStatusT for TestBlockStatus { fn block_number(&self, hash: Hash) -> Result, Error> { - Ok(self.inner.lock().get(&hash).map(|x| x.clone())) + Ok(self.inner.lock().get(&hash).map(|x| *x)) } } @@ -645,7 +651,7 @@ mod tests { // unwrap the commit from `CommunicationIn` returning its fields in a tuple, // panics if the given message isn't a commit - fn unapply_commit(msg: CommunicationIn) -> (u64, CompactCommit) { + fn unapply_commit(msg: CommunicationIn) -> (u64, CompactCommit
) { match msg { voter::CommunicationIn::Commit(round, commit, ..) => (round, commit), _ => panic!("expected commit"), @@ -654,7 +660,7 @@ mod tests { // unwrap the catch up from `CommunicationIn` returning its inner representation, // panics if the given message isn't a catch up - fn unapply_catch_up(msg: CommunicationIn) -> CatchUp { + fn unapply_catch_up(msg: CommunicationIn) -> CatchUp
{ match msg { voter::CommunicationIn::CatchUp(catch_up, ..) => catch_up, _ => panic!("expected catch up"), @@ -739,7 +745,7 @@ mod tests { let h2 = make_header(6); let h3 = make_header(7); - let unknown_commit = CompactCommit:: { + let unknown_commit = CompactCommit::
{ target_hash: h1.hash(), target_number: 5, precommits: vec![ @@ -767,7 +773,7 @@ mod tests { let h2 = make_header(6); let h3 = make_header(7); - let known_commit = CompactCommit:: { + let known_commit = CompactCommit::
{ target_hash: h1.hash(), target_number: 5, precommits: vec![ @@ -796,8 +802,8 @@ mod tests { let h3 = make_header(7); let signed_prevote = |header: &Header| finality_grandpa::SignedPrevote { - id: Default::default(), - signature: Default::default(), + id: UncheckedFrom::unchecked_from([1; 32]), + signature: UncheckedFrom::unchecked_from([1; 64]), prevote: finality_grandpa::Prevote { target_hash: header.hash(), target_number: *header.number(), @@ -805,8 +811,8 @@ mod tests { }; let signed_precommit = |header: &Header| finality_grandpa::SignedPrecommit { - id: Default::default(), - signature: Default::default(), + id: UncheckedFrom::unchecked_from([1; 32]), + signature: UncheckedFrom::unchecked_from([1; 64]), precommit: finality_grandpa::Precommit { target_hash: header.hash(), target_number: *header.number(), @@ -844,8 +850,8 @@ mod tests { let h3 = make_header(7); let signed_prevote = |header: &Header| finality_grandpa::SignedPrevote { - id: Default::default(), - signature: Default::default(), + id: UncheckedFrom::unchecked_from([1; 32]), + signature: UncheckedFrom::unchecked_from([1; 64]), prevote: finality_grandpa::Prevote { target_hash: header.hash(), target_number: *header.number(), @@ -853,8 +859,8 @@ mod tests { }; let signed_precommit = |header: &Header| finality_grandpa::SignedPrecommit { - id: Default::default(), - signature: Default::default(), + id: UncheckedFrom::unchecked_from([1; 32]), + signature: UncheckedFrom::unchecked_from([1; 64]), precommit: finality_grandpa::Precommit { target_hash: header.hash(), target_number: *header.number(), @@ -909,7 +915,7 @@ mod tests { // we create a commit message, with precommits for blocks 6 and 7 which // we haven't imported. - let unknown_commit = CompactCommit:: { + let unknown_commit = CompactCommit::
{ target_hash: h1.hash(), target_number: 5, precommits: vec![ diff --git a/client/finality-grandpa/src/voting_rule.rs b/client/finality-grandpa/src/voting_rule.rs index b974afe0d352e..fb7754fc0169a 100644 --- a/client/finality-grandpa/src/voting_rule.rs +++ b/client/finality-grandpa/src/voting_rule.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -80,10 +80,16 @@ where } /// A custom voting rule that guarantees that our vote is always behind the best -/// block by at least N blocks. In the best case our vote is exactly N blocks -/// behind the best block. +/// block by at least N blocks, unless the base number is < N blocks behind the +/// best, in which case it votes for the base. +/// +/// In the best case our vote is exactly N blocks +/// behind the best block, but if there is a scenario where either +/// >34% of validators run without this rule or the fork-choice rule +/// can prioritize shorter chains over longer ones, the vote may be +/// closer to the best block than N. #[derive(Clone)] -pub struct BeforeBestBlockBy(N); +pub struct BeforeBestBlockBy(pub N); impl VotingRule for BeforeBestBlockBy> where Block: BlockT, @@ -92,7 +98,7 @@ where fn restrict_vote( &self, backend: Arc, - _base: &Block::Header, + base: &Block::Header, best_target: &Block::Header, current_target: &Block::Header, ) -> VotingRuleResult { @@ -102,6 +108,12 @@ where return Box::pin(async { None }) } + // Constrain to the base number, if that's the minimal + // vote that can be placed. + if *base.number() + self.0 > *best_target.number() { + return Box::pin(std::future::ready(Some((base.hash(), *base.number())))) + } + // find the target number restricted by this rule let target_number = best_target.number().saturating_sub(self.0); @@ -113,7 +125,7 @@ where let current_target = current_target.clone(); // find the block at the given target height - Box::pin(std::future::ready(find_target(&*backend, target_number.clone(), ¤t_target))) + Box::pin(std::future::ready(find_target(&*backend, target_number, ¤t_target))) } } @@ -393,4 +405,34 @@ mod tests { // only one of the rules is applied. assert_eq!(number, 150); } + + #[test] + fn before_best_by_has_cutoff_at_base() { + let rule = BeforeBestBlockBy(2); + + let mut client = Arc::new(TestClientBuilder::new().build()); + + for _ in 0..5 { + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + + futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); + } + + let best = client.header(&BlockId::Hash(client.info().best_hash)).unwrap().unwrap(); + let best_number = *best.number(); + + for i in 0u32..5 { + let base = client.header(&BlockId::Number(i.into())).unwrap().unwrap(); + let (_, number) = futures::executor::block_on(rule.restrict_vote( + client.clone(), + &base, + &best, + &best, + )) + .unwrap(); + + let expected = std::cmp::max(best_number - 2, *base.number()); + assert_eq!(number, expected, "best = {}, lag = 2, base = {}", best_number, i); + } + } } diff --git a/client/finality-grandpa/src/warp_proof.rs b/client/finality-grandpa/src/warp_proof.rs index 34eaa49cdf360..c9f762fc7d593 100644 --- a/client/finality-grandpa/src/warp_proof.rs +++ b/client/finality-grandpa/src/warp_proof.rs @@ -19,11 +19,11 @@ use sp_runtime::codec::{self, Decode, Encode}; use crate::{ - best_justification, find_scheduled_change, AuthoritySetChanges, BlockNumberOps, - GrandpaJustification, SharedAuthoritySet, + best_justification, find_scheduled_change, AuthoritySetChanges, AuthoritySetHardFork, + BlockNumberOps, GrandpaJustification, SharedAuthoritySet, }; use sc_client_api::Backend as ClientBackend; -use sc_network::warp_request_handler::{EncodedProof, VerificationResult, WarpSyncProvider}; +use sc_network_common::sync::warp::{EncodedProof, VerificationResult, WarpSyncProvider}; use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend}; use sp_finality_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID}; use sp_runtime::{ @@ -31,29 +31,28 @@ use sp_runtime::{ traits::{Block as BlockT, Header as HeaderT, NumberFor, One}, }; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; /// Warp proof processing error. -#[derive(Debug, derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// Decoding error. - #[display(fmt = "Failed to decode block hash: {}.", _0)] - DecodeScale(codec::Error), + #[error("Failed to decode block hash: {0}.")] + DecodeScale(#[from] codec::Error), /// Client backend error. - Client(sp_blockchain::Error), + #[error("{0}")] + Client(#[from] sp_blockchain::Error), /// Invalid request data. - #[from(ignore)] + #[error("{0}")] InvalidRequest(String), /// Invalid warp proof. - #[from(ignore)] + #[error("{0}")] InvalidProof(String), /// Missing header or authority set change data. - #[display(fmt = "Missing required data to be able to answer request.")] + #[error("Missing required data to be able to answer request.")] MissingData, } -impl std::error::Error for Error {} - /// The maximum size in bytes of the `WarpSyncProof`. pub(super) const MAX_WARP_SYNC_PROOF_SIZE: usize = 8 * 1024 * 1024; @@ -131,7 +130,7 @@ impl WarpSyncProof { } let justification = blockchain - .justifications(BlockId::Number(*last_block))? + .justifications(header.hash())? .and_then(|just| just.into_justification(GRANDPA_ENGINE_ID)) .expect( "header is last in set and contains standard change signal; \ @@ -194,6 +193,7 @@ impl WarpSyncProof { &self, set_id: SetId, authorities: AuthorityList, + hard_forks: &HashMap<(Block::Hash, NumberFor), (SetId, AuthorityList)>, ) -> Result<(SetId, AuthorityList), Error> where NumberFor: BlockNumberOps, @@ -202,26 +202,34 @@ impl WarpSyncProof { let mut current_authorities = authorities; for (fragment_num, proof) in self.proofs.iter().enumerate() { - proof - .justification - .verify(current_set_id, ¤t_authorities) - .map_err(|err| Error::InvalidProof(err.to_string()))?; - - if proof.justification.target().1 != proof.header.hash() { - return Err(Error::InvalidProof( - "Mismatch between header and justification".to_owned(), - )) - } + let hash = proof.header.hash(); + let number = *proof.header.number(); + + if let Some((set_id, list)) = hard_forks.get(&(hash, number)) { + current_set_id = *set_id; + current_authorities = list.clone(); + } else { + proof + .justification + .verify(current_set_id, ¤t_authorities) + .map_err(|err| Error::InvalidProof(err.to_string()))?; + + if proof.justification.target().1 != hash { + return Err(Error::InvalidProof( + "Mismatch between header and justification".to_owned(), + )) + } - if let Some(scheduled_change) = find_scheduled_change::(&proof.header) { - current_authorities = scheduled_change.next_authorities; - current_set_id += 1; - } else if fragment_num != self.proofs.len() - 1 || !self.is_finished { - // Only the last fragment of the last proof message is allowed to be missing - // the authority set change. - return Err(Error::InvalidProof( - "Header is missing authority set change digest".to_string(), - )) + if let Some(scheduled_change) = find_scheduled_change::(&proof.header) { + current_authorities = scheduled_change.next_authorities; + current_set_id += 1; + } else if fragment_num != self.proofs.len() - 1 || !self.is_finished { + // Only the last fragment of the last proof message is allowed to be missing the + // authority set change. + return Err(Error::InvalidProof( + "Header is missing authority set change digest".to_string(), + )) + } } } Ok((current_set_id, current_authorities)) @@ -235,6 +243,7 @@ where { backend: Arc, authority_set: SharedAuthoritySet>, + hard_forks: HashMap<(Block::Hash, NumberFor), (SetId, AuthorityList)>, } impl> NetworkProvider @@ -245,8 +254,16 @@ where pub fn new( backend: Arc, authority_set: SharedAuthoritySet>, + hard_forks: Vec>, ) -> Self { - NetworkProvider { backend, authority_set } + NetworkProvider { + backend, + authority_set, + hard_forks: hard_forks + .into_iter() + .map(|fork| (fork.block, (fork.set_id, fork.authorities))) + .collect(), + } } } @@ -283,7 +300,7 @@ where .map(|p| p.header.clone()) .ok_or_else(|| "Empty proof".to_string())?; let (next_set_id, next_authorities) = - proof.verify(set_id, authorities).map_err(Box::new)?; + proof.verify(set_id, authorities, &self.hard_forks).map_err(Box::new)?; if proof.is_finished { Ok(VerificationResult::::Complete(next_set_id, next_authorities, last_header)) } else { @@ -310,7 +327,7 @@ mod tests { use sp_consensus::BlockOrigin; use sp_finality_grandpa::GRANDPA_ENGINE_ID; use sp_keyring::Ed25519Keyring; - use sp_runtime::{generic::BlockId, traits::Header as _}; + use sp_runtime::traits::Header as _; use std::sync::Arc; use substrate_test_runtime_client::{ ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, TestClientBuilder, @@ -395,10 +412,7 @@ mod tests { let justification = GrandpaJustification::from_commit(&client, 42, commit).unwrap(); client - .finalize_block( - BlockId::Hash(target_hash), - Some((GRANDPA_ENGINE_ID, justification.encode())), - ) + .finalize_block(target_hash, Some((GRANDPA_ENGINE_ID, justification.encode()))) .unwrap(); authority_set_changes.push((current_set_id, n)); @@ -417,7 +431,8 @@ mod tests { WarpSyncProof::generate(&*backend, genesis_hash, &authority_set_changes).unwrap(); // verifying the proof should yield the last set id and authorities - let (new_set_id, new_authorities) = warp_sync_proof.verify(0, genesis_authorities).unwrap(); + let (new_set_id, new_authorities) = + warp_sync_proof.verify(0, genesis_authorities, &Default::default()).unwrap(); let expected_authorities = current_authorities .iter() diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index 88d02f81ad5b3..a432c2ada5c61 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -3,9 +3,9 @@ name = "sc-informant" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Substrate informant." -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -14,12 +14,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] ansi_term = "0.12.1" -futures = "0.3.9" +futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.8" -parity-util-mem = { version = "0.10.0", default-features = false, features = ["primitive-types"] } +log = "0.4.17" sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-network = { version = "0.10.0-dev", path = "../network" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } diff --git a/client/informant/src/display.rs b/client/informant/src/display.rs index 1f23856101aa3..3d585a9985134 100644 --- a/client/informant/src/display.rs +++ b/client/informant/src/display.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,13 +20,15 @@ use crate::OutputFormat; use ansi_term::Colour; use log::info; use sc_client_api::ClientInfo; -use sc_network::{NetworkStatus, SyncState}; -use sp_runtime::traits::{Block as BlockT, CheckedDiv, NumberFor, Saturating, Zero}; -use std::{ - convert::{TryFrom, TryInto}, - fmt, - time::Instant, +use sc_network_common::{ + service::NetworkStatus, + sync::{ + warp::{WarpSyncPhase, WarpSyncProgress}, + SyncState, + }, }; +use sp_runtime::traits::{Block as BlockT, CheckedDiv, NumberFor, Saturating, Zero}; +use std::{fmt, time::Instant}; /// State of the informant display system. /// @@ -34,7 +36,7 @@ use std::{ /// like: /// /// > Syncing 5.4 bps, target=#531028 (4 peers), best: #90683 (0x4ca8…51b8), -/// > finalized #360 (0x6f24…a38b), ⬇ 5.5kiB/s ⬆ 0.9kiB/s +/// > finalized #360 (0x6f24…a38b), ⬇ 5.5kiB/s ⬆ 0.9kiB/s /// /// # Usage /// @@ -91,36 +93,37 @@ impl InformantDisplay { (diff_bytes_inbound, diff_bytes_outbound) }; - let (level, status, target) = match ( - net_status.sync_state, - net_status.best_seen_block, - net_status.state_sync, - net_status.warp_sync, - ) { - (_, _, _, Some(warp)) => ( - "⏩", - "Warping".into(), - format!( - ", {}, ({:.2}) Mib", - warp.phase, - (warp.total_bytes as f32) / (1024f32 * 1024f32) + let (level, status, target) = + match (net_status.sync_state, net_status.state_sync, net_status.warp_sync) { + ( + _, + _, + Some(WarpSyncProgress { phase: WarpSyncPhase::DownloadingBlocks(n), .. }), + ) => ("⏩", "Block history".into(), format!(", #{}", n)), + (_, _, Some(warp)) => ( + "⏩", + "Warping".into(), + format!( + ", {}, {:.2} Mib", + warp.phase, + (warp.total_bytes as f32) / (1024f32 * 1024f32) + ), ), - ), - (_, _, Some(state), _) => ( - "⚙️ ", - "Downloading state".into(), - format!( - ", {}%, ({:.2}) Mib", - state.percentage, - (state.size as f32) / (1024f32 * 1024f32) + (_, Some(state), _) => ( + "⚙️ ", + "Downloading state".into(), + format!( + ", {}%, {:.2} Mib", + state.percentage, + (state.size as f32) / (1024f32 * 1024f32) + ), ), - ), - (SyncState::Idle, _, _, _) => ("💤", "Idle".into(), "".into()), - (SyncState::Downloading, None, _, _) => - ("⚙️ ", format!("Preparing{}", speed), "".into()), - (SyncState::Downloading, Some(n), None, _) => - ("⚙️ ", format!("Syncing{}", speed), format!(", target=#{}", n)), - }; + (SyncState::Idle, _, _) => ("💤", "Idle".into(), "".into()), + (SyncState::Downloading { target }, _, _) => + ("⚙️ ", format!("Syncing{}", speed), format!(", target=#{target}")), + (SyncState::Importing { target }, _, _) => + ("⚙️ ", format!("Preparing{}", speed), format!(", target=#{target}")), + }; if self.format.enable_color { info!( diff --git a/client/informant/src/lib.rs b/client/informant/src/lib.rs index f421dbbb7e564..b03b92686e2aa 100644 --- a/client/informant/src/lib.rs +++ b/client/informant/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,10 +22,8 @@ use ansi_term::Colour; use futures::prelude::*; use futures_timer::Delay; use log::{debug, info, trace}; -use parity_util_mem::MallocSizeOf; use sc_client_api::{BlockchainEvents, UsageProvider}; -use sc_network::NetworkService; -use sc_transaction_pool_api::TransactionPool; +use sc_network_common::service::NetworkStatusProvider; use sp_blockchain::HeaderMetadata; use sp_runtime::traits::{Block as BlockT, Header}; use std::{collections::VecDeque, fmt::Display, sync::Arc, time::Duration}; @@ -53,15 +51,11 @@ impl Default for OutputFormat { } /// Builds the informant and returns a `Future` that drives the informant. -pub async fn build( - client: Arc, - network: Arc::Hash>>, - pool: Arc

, - format: OutputFormat, -) where +pub async fn build(client: Arc, network: N, format: OutputFormat) +where + N: NetworkStatusProvider, C: UsageProvider + HeaderMetadata + BlockchainEvents, >::Error: Display, - P: TransactionPool + MallocSizeOf, { let mut display = display::InformantDisplay::new(format.clone()); @@ -82,11 +76,6 @@ pub async fn build( "Usage statistics not displayed as backend does not provide it", ) } - trace!( - target: "usage", - "Subsystems memory [txpool: {} kB]", - parity_util_mem::malloc_size(&*pool) / 1024, - ); display.display(&info, net_status); future::ready(()) }); @@ -116,7 +105,7 @@ where if let Some((ref last_num, ref last_hash)) = last_best { if n.header.parent_hash() != last_hash && n.is_new_best { let maybe_ancestor = - sp_blockchain::lowest_common_ancestor(&*client, last_hash.clone(), n.hash); + sp_blockchain::lowest_common_ancestor(&*client, *last_hash, n.hash); match maybe_ancestor { Ok(ref ancestor) if ancestor.hash != *last_hash => info!( @@ -135,13 +124,13 @@ where } if n.is_new_best { - last_best = Some((n.header.number().clone(), n.hash.clone())); + last_best = Some((*n.header.number(), n.hash)); } // If we already printed a message for a given block recently, // we should not print it again. if !last_blocks.contains(&n.hash) { - last_blocks.push_back(n.hash.clone()); + last_blocks.push_back(n.hash); if last_blocks.len() > max_blocks_to_track { last_blocks.pop_front(); diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index 17c651a91decd..8766ee80157e9 100644 --- a/client/keystore/Cargo.toml +++ b/client/keystore/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-keystore" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Keystore (and session key management) for ed25519 based chains like Polkadot." documentation = "https://docs.rs/sc-keystore" @@ -13,16 +13,15 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -async-trait = "0.1.50" -derive_more = "0.99.2" -sp-application-crypto = { version = "4.0.0-dev", path = "../../primitives/application-crypto" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", path = "../../primitives/keystore" } -hex = "0.4.0" -parking_lot = "0.11.1" -serde_json = "1.0.68" +array-bytes = "4.1" +async-trait = "0.1.57" +parking_lot = "0.12.1" +serde_json = "1.0.85" +thiserror = "1.0" +sp-application-crypto = { version = "7.0.0", path = "../../primitives/application-crypto" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } [dev-dependencies] tempfile = "3.1.0" diff --git a/client/keystore/src/lib.rs b/client/keystore/src/lib.rs index 5e29f691997e6..cf94a16f08d86 100644 --- a/client/keystore/src/lib.rs +++ b/client/keystore/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -28,26 +28,31 @@ mod local; pub use local::LocalKeystore; /// Keystore error. -#[derive(Debug, derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// IO error. - Io(io::Error), + #[error(transparent)] + Io(#[from] io::Error), /// JSON error. - Json(serde_json::Error), + #[error(transparent)] + Json(#[from] serde_json::Error), /// Invalid password. - #[display(fmt = "Invalid password")] - InvalidPassword, + #[error( + "Requested public key and public key of the loaded private key do not match. \n + This means either that the keystore password is incorrect or that the private key was stored under a wrong public key." + )] + PublicKeyMismatch, /// Invalid BIP39 phrase - #[display(fmt = "Invalid recovery phrase (BIP39) data")] + #[error("Invalid recovery phrase (BIP39) data")] InvalidPhrase, /// Invalid seed - #[display(fmt = "Invalid seed")] + #[error("Invalid seed")] InvalidSeed, /// Public key type is not supported - #[display(fmt = "Key crypto type is not supported")] + #[error("Key crypto type is not supported")] KeyNotSupported(KeyTypeId), /// Keystore unavailable - #[display(fmt = "Keystore unavailable")] + #[error("Keystore unavailable")] Unavailable, } @@ -58,7 +63,7 @@ impl From for TraitError { fn from(error: Error) -> Self { match error { Error::KeyNotSupported(id) => TraitError::KeyNotSupported(id), - Error::InvalidSeed | Error::InvalidPhrase | Error::InvalidPassword => + Error::InvalidSeed | Error::InvalidPhrase | Error::PublicKeyMismatch => TraitError::ValidationError(error.to_string()), Error::Unavailable => TraitError::Unavailable, Error::Io(e) => TraitError::Other(e.to_string()), @@ -66,13 +71,3 @@ impl From for TraitError { } } } - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::Io(ref err) => Some(err), - Error::Json(ref err) => Some(err), - _ => None, - } - } -} diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index e5c8ff14af095..54ff6a5b164a8 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,9 @@ use async_trait::async_trait; use parking_lot::RwLock; use sp_application_crypto::{ecdsa, ed25519, sr25519, AppKey, AppPair, IsWrappedBy}; use sp_core::{ - crypto::{CryptoTypePublicPair, ExposeSecret, KeyTypeId, Pair as PairT, Public, SecretString}, + crypto::{ + ByteArray, CryptoTypePublicPair, ExposeSecret, KeyTypeId, Pair as PairT, SecretString, + }, sr25519::{Pair as Sr25519Pair, Public as Sr25519Public}, Encode, }; @@ -189,30 +191,36 @@ impl SyncCryptoStore for LocalKeystore { ) -> std::result::Result>, TraitError> { match key.0 { ed25519::CRYPTO_ID => { - let pub_key = ed25519::Public::from_slice(key.1.as_slice()); + let pub_key = ed25519::Public::from_slice(key.1.as_slice()).map_err(|()| { + TraitError::Other("Corrupted public key - Invalid size".into()) + })?; let key_pair = self .0 .read() .key_pair_by_type::(&pub_key, id) - .map_err(|e| TraitError::from(e))?; + .map_err(TraitError::from)?; key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, sr25519::CRYPTO_ID => { - let pub_key = sr25519::Public::from_slice(key.1.as_slice()); + let pub_key = sr25519::Public::from_slice(key.1.as_slice()).map_err(|()| { + TraitError::Other("Corrupted public key - Invalid size".into()) + })?; let key_pair = self .0 .read() .key_pair_by_type::(&pub_key, id) - .map_err(|e| TraitError::from(e))?; + .map_err(TraitError::from)?; key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, ecdsa::CRYPTO_ID => { - let pub_key = ecdsa::Public::from_slice(key.1.as_slice()); + let pub_key = ecdsa::Public::from_slice(key.1.as_slice()).map_err(|()| { + TraitError::Other("Corrupted public key - Invalid size".into()) + })?; let key_pair = self .0 .read() .key_pair_by_type::(&pub_key, id) - .map_err(|e| TraitError::from(e))?; + .map_err(TraitError::from)?; key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, _ => Err(TraitError::KeyNotSupported(id)), @@ -223,7 +231,11 @@ impl SyncCryptoStore for LocalKeystore { self.0 .read() .raw_public_keys(key_type) - .map(|v| v.into_iter().map(|k| sr25519::Public::from_slice(k.as_slice())).collect()) + .map(|v| { + v.into_iter() + .filter_map(|k| sr25519::Public::from_slice(k.as_slice()).ok()) + .collect() + }) .unwrap_or_default() } @@ -246,7 +258,11 @@ impl SyncCryptoStore for LocalKeystore { self.0 .read() .raw_public_keys(key_type) - .map(|v| v.into_iter().map(|k| ed25519::Public::from_slice(k.as_slice())).collect()) + .map(|v| { + v.into_iter() + .filter_map(|k| ed25519::Public::from_slice(k.as_slice()).ok()) + .collect() + }) .unwrap_or_default() } @@ -269,7 +285,11 @@ impl SyncCryptoStore for LocalKeystore { self.0 .read() .raw_public_keys(key_type) - .map(|v| v.into_iter().map(|k| ecdsa::Public::from_slice(k.as_slice())).collect()) + .map(|v| { + v.into_iter() + .filter_map(|k| ecdsa::Public::from_slice(k.as_slice()).ok()) + .collect() + }) .unwrap_or_default() } @@ -300,7 +320,7 @@ impl SyncCryptoStore for LocalKeystore { fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { public_keys .iter() - .all(|(p, t)| self.0.read().key_phrase_by_type(&p, *t).ok().flatten().is_some()) + .all(|(p, t)| self.0.read().key_phrase_by_type(p, *t).ok().flatten().is_some()) } fn sr25519_vrf_sign( @@ -364,8 +384,7 @@ impl KeystoreInner { let path = path.into(); fs::create_dir_all(&path)?; - let instance = Self { path: Some(path), additional: HashMap::new(), password }; - Ok(instance) + Ok(Self { path: Some(path), additional: HashMap::new(), password }) } /// Get the password for this store. @@ -397,10 +416,9 @@ impl KeystoreInner { /// Places it into the file system store, if a path is configured. fn insert_unknown(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<()> { if let Some(path) = self.key_file_path(public, key_type) { - let mut file = File::create(path).map_err(Error::Io)?; - serde_json::to_writer(&file, &suri).map_err(Error::Json)?; - file.flush().map_err(Error::Io)?; + Self::write_to_file(path, suri)?; } + Ok(()) } @@ -411,15 +429,29 @@ impl KeystoreInner { fn generate_by_type(&mut self, key_type: KeyTypeId) -> Result { let (pair, phrase, _) = Pair::generate_with_phrase(self.password()); if let Some(path) = self.key_file_path(pair.public().as_slice(), key_type) { - let mut file = File::create(path)?; - serde_json::to_writer(&file, &phrase)?; - file.flush()?; + Self::write_to_file(path, &phrase)?; } else { self.insert_ephemeral_pair(&pair, &phrase, key_type); } + Ok(pair) } + /// Write the given `data` to `file`. + fn write_to_file(file: PathBuf, data: &str) -> Result<()> { + let mut file = File::create(file)?; + + #[cfg(target_family = "unix")] + { + use std::os::unix::fs::PermissionsExt; + file.set_permissions(fs::Permissions::from_mode(0o600))?; + } + + serde_json::to_writer(&file, data)?; + file.flush()?; + Ok(()) + } + /// Create a new key from seed. /// /// Does not place it into the file system store. @@ -471,7 +503,7 @@ impl KeystoreInner { if &pair.public() == public { Ok(Some(pair)) } else { - Err(Error::InvalidPassword) + Err(Error::PublicKeyMismatch) } } @@ -480,8 +512,8 @@ impl KeystoreInner { /// Returns `None` if the keystore only exists in-memory and there isn't any path to provide. fn key_file_path(&self, public: &[u8], key_type: KeyTypeId) -> Option { let mut buf = self.path.as_ref()?.clone(); - let key_type = hex::encode(key_type.0); - let key = hex::encode(public); + let key_type = array_bytes::bytes2hex("", &key_type.0); + let key = array_bytes::bytes2hex("", public); buf.push(key_type + key.as_str()); Some(buf) } @@ -502,9 +534,9 @@ impl KeystoreInner { // skip directories and non-unicode file names (hex is unicode) if let Some(name) = path.file_name().and_then(|n| n.to_str()) { - match hex::decode(name) { + match array_bytes::hex2bytes(name) { Ok(ref hex) if hex.len() > 4 => { - if &hex[0..4] != &id.0 { + if hex[0..4] != id.0 { continue } let public = hex[4..].to_vec(); @@ -549,8 +581,9 @@ mod tests { } fn public_keys(&self) -> Result> { - self.raw_public_keys(Public::ID) - .map(|v| v.into_iter().map(|k| Public::from_slice(k.as_slice())).collect()) + self.raw_public_keys(Public::ID).map(|v| { + v.into_iter().filter_map(|k| Public::from_slice(k.as_slice()).ok()).collect() + }) } fn generate(&mut self) -> Result { @@ -706,7 +739,7 @@ mod tests { let temp_dir = TempDir::new().unwrap(); let store = LocalKeystore::open(temp_dir.path(), None).unwrap(); - let file_name = temp_dir.path().join(hex::encode(&SR25519.0[..2])); + let file_name = temp_dir.path().join(array_bytes::bytes2hex("", &SR25519.0[..2])); fs::write(file_name, "test").expect("Invalid file is written"); assert!(SyncCryptoStore::sr25519_public_keys(&store, SR25519).is_empty()); @@ -735,4 +768,20 @@ mod tests { SyncCryptoStore::sr25519_generate_new(&store, TEST_KEY_TYPE, None).unwrap(); assert_eq!(SyncCryptoStore::sr25519_public_keys(&store, TEST_KEY_TYPE).len(), 2); } + + #[test] + #[cfg(target_family = "unix")] + fn uses_correct_file_permissions_on_unix() { + use std::os::unix::fs::PermissionsExt; + + let temp_dir = TempDir::new().unwrap(); + let store = LocalKeystore::open(temp_dir.path(), None).unwrap(); + + let public = SyncCryptoStore::sr25519_generate_new(&store, TEST_KEY_TYPE, None).unwrap(); + + let path = store.0.read().key_file_path(public.as_ref(), TEST_KEY_TYPE).unwrap(); + let permissions = File::open(path).unwrap().metadata().unwrap().permissions(); + + assert_eq!(0o100600, permissions.mode()); + } } diff --git a/client/light/Cargo.toml b/client/light/Cargo.toml deleted file mode 100644 index b10f7646bf9bd..0000000000000 --- a/client/light/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -description = "components for a light client" -name = "sc-light" -version = "4.0.0-dev" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -authors = ["Parity Technologies "] -edition = "2018" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -documentation = "https://docs.rs/sc-light" -readme = "README.md" - -[dependencies] -parking_lot = "0.11.1" -hash-db = "0.15.2" -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sp-externalities = { version = "0.10.0-dev", path = "../../primitives/externalities" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-state-machine = { version = "0.10.0-dev", path = "../../primitives/state-machine" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -codec = { package = "parity-scale-codec", version = "2.0.0" } -sc-executor = { version = "0.10.0-dev", path = "../executor" } - -[features] -default = [] diff --git a/client/light/README.md b/client/light/README.md deleted file mode 100644 index 1ba1f155b1652..0000000000000 --- a/client/light/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Light client components. - -License: GPL-3.0-or-later WITH Classpath-exception-2.0 \ No newline at end of file diff --git a/client/light/src/backend.rs b/client/light/src/backend.rs deleted file mode 100644 index 3091dce625a3f..0000000000000 --- a/client/light/src/backend.rs +++ /dev/null @@ -1,578 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Light client backend. Only stores headers and justifications of blocks. -//! Everything else is requested from full nodes on demand. - -use parking_lot::RwLock; -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; - -use codec::{Decode, Encode}; - -use super::blockchain::Blockchain; -use hash_db::Hasher; -use sc_client_api::{ - backend::{ - AuxStore, Backend as ClientBackend, BlockImportOperation, NewBlockState, - PrunableStateChangesTrieStorage, RemoteBackend, - }, - blockchain::{well_known_cache_keys, HeaderBackend as BlockchainHeaderBackend}, - in_mem::check_genesis_storage, - light::Storage as BlockchainStorage, - UsageInfo, -}; -use sp_blockchain::{Error as ClientError, Result as ClientResult}; -use sp_core::{ - offchain::storage::InMemOffchainStorage, - storage::{well_known_keys, ChildInfo}, - ChangesTrieConfiguration, -}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, HashFor, Header, NumberFor, Zero}, - Justification, Justifications, Storage, -}; -use sp_state_machine::{ - Backend as StateBackend, ChangesTrieTransaction, ChildStorageCollection, InMemoryBackend, - IndexOperation, StorageCollection, TrieBackend, -}; - -const IN_MEMORY_EXPECT_PROOF: &str = - "InMemory state backend has Void error type and always succeeds; qed"; - -/// Light client backend. -pub struct Backend { - blockchain: Arc>, - genesis_state: RwLock>>, - import_lock: RwLock<()>, -} - -/// Light block (header and justification) import operation. -pub struct ImportOperation { - header: Option, - cache: HashMap>, - leaf_state: NewBlockState, - aux_ops: Vec<(Vec, Option>)>, - finalized_blocks: Vec>, - set_head: Option>, - storage_update: Option>>, - changes_trie_config_update: Option>, - _phantom: std::marker::PhantomData, -} - -/// Either in-memory genesis state, or locally-unavailable state. -pub enum GenesisOrUnavailableState { - /// Genesis state - storage values are stored in-memory. - Genesis(InMemoryBackend), - /// We know that state exists, but all calls will fail with error, because it - /// isn't locally available. - Unavailable, -} - -impl Backend { - /// Create new light backend. - pub fn new(blockchain: Arc>) -> Self { - Self { blockchain, genesis_state: RwLock::new(None), import_lock: Default::default() } - } - - /// Get shared blockchain reference. - pub fn blockchain(&self) -> &Arc> { - &self.blockchain - } -} - -impl AuxStore for Backend { - fn insert_aux< - 'a, - 'b: 'a, - 'c: 'a, - I: IntoIterator, - D: IntoIterator, - >( - &self, - insert: I, - delete: D, - ) -> ClientResult<()> { - self.blockchain.storage().insert_aux(insert, delete) - } - - fn get_aux(&self, key: &[u8]) -> ClientResult>> { - self.blockchain.storage().get_aux(key) - } -} - -impl ClientBackend for Backend> -where - Block: BlockT, - S: BlockchainStorage, - Block::Hash: Ord, -{ - type BlockImportOperation = ImportOperation; - type Blockchain = Blockchain; - type State = GenesisOrUnavailableState>; - type OffchainStorage = InMemOffchainStorage; - - fn begin_operation(&self) -> ClientResult { - Ok(ImportOperation { - header: None, - cache: Default::default(), - leaf_state: NewBlockState::Normal, - aux_ops: Vec::new(), - finalized_blocks: Vec::new(), - set_head: None, - storage_update: None, - changes_trie_config_update: None, - _phantom: Default::default(), - }) - } - - fn begin_state_operation( - &self, - _operation: &mut Self::BlockImportOperation, - _block: BlockId, - ) -> ClientResult<()> { - Ok(()) - } - - fn commit_operation(&self, mut operation: Self::BlockImportOperation) -> ClientResult<()> { - if !operation.finalized_blocks.is_empty() { - for block in operation.finalized_blocks { - self.blockchain.storage().finalize_header(block)?; - } - } - - if let Some(header) = operation.header { - let is_genesis_import = header.number().is_zero(); - if let Some(new_config) = operation.changes_trie_config_update { - operation - .cache - .insert(well_known_cache_keys::CHANGES_TRIE_CONFIG, new_config.encode()); - } - self.blockchain.storage().import_header( - header, - operation.cache, - operation.leaf_state, - operation.aux_ops, - )?; - - // when importing genesis block => remember its state - if is_genesis_import { - *self.genesis_state.write() = operation.storage_update.take(); - } - } else { - for (key, maybe_val) in operation.aux_ops { - match maybe_val { - Some(val) => self - .blockchain - .storage() - .insert_aux(&[(&key[..], &val[..])], std::iter::empty())?, - None => - self.blockchain.storage().insert_aux(std::iter::empty(), &[&key[..]])?, - } - } - } - - if let Some(set_head) = operation.set_head { - self.blockchain.storage().set_head(set_head)?; - } - - Ok(()) - } - - fn finalize_block( - &self, - block: BlockId, - _justification: Option, - ) -> ClientResult<()> { - self.blockchain.storage().finalize_header(block) - } - - fn append_justification( - &self, - _block: BlockId, - _justification: Justification, - ) -> ClientResult<()> { - Ok(()) - } - - fn blockchain(&self) -> &Blockchain { - &self.blockchain - } - - fn usage_info(&self) -> Option { - self.blockchain.storage().usage_info() - } - - fn changes_trie_storage(&self) -> Option<&dyn PrunableStateChangesTrieStorage> { - None - } - - fn offchain_storage(&self) -> Option { - None - } - - fn state_at(&self, block: BlockId) -> ClientResult { - let block_number = self.blockchain.expect_block_number_from_id(&block)?; - - // special case for genesis block - if block_number.is_zero() { - if let Some(genesis_state) = self.genesis_state.read().clone() { - return Ok(GenesisOrUnavailableState::Genesis(genesis_state)) - } - } - - // else return unavailable state. We do not return error here, because error - // would mean that we do not know this state at all. But we know that it exists - Ok(GenesisOrUnavailableState::Unavailable) - } - - fn revert( - &self, - _n: NumberFor, - _revert_finalized: bool, - ) -> ClientResult<(NumberFor, HashSet)> { - Err(ClientError::NotAvailableOnLightClient) - } - - fn remove_leaf_block(&self, _hash: &Block::Hash) -> ClientResult<()> { - Err(ClientError::NotAvailableOnLightClient) - } - - fn get_import_lock(&self) -> &RwLock<()> { - &self.import_lock - } -} - -impl RemoteBackend for Backend> -where - Block: BlockT, - S: BlockchainStorage + 'static, - Block::Hash: Ord, -{ - fn is_local_state_available(&self, block: &BlockId) -> bool { - self.genesis_state.read().is_some() && - self.blockchain - .expect_block_number_from_id(block) - .map(|num| num.is_zero()) - .unwrap_or(false) - } - - fn remote_blockchain(&self) -> Arc> { - self.blockchain.clone() - } -} - -impl BlockImportOperation for ImportOperation -where - Block: BlockT, - S: BlockchainStorage, - Block::Hash: Ord, -{ - type State = GenesisOrUnavailableState>; - - fn state(&self) -> ClientResult> { - // None means 'locally-stateless' backend - Ok(None) - } - - fn set_block_data( - &mut self, - header: Block::Header, - _body: Option>, - _indexed_body: Option>>, - _justifications: Option, - state: NewBlockState, - ) -> ClientResult<()> { - self.leaf_state = state; - self.header = Some(header); - Ok(()) - } - - fn update_cache(&mut self, cache: HashMap>) { - self.cache = cache; - } - - fn update_db_storage( - &mut self, - _update: >>::Transaction, - ) -> ClientResult<()> { - // we're not storing anything locally => ignore changes - Ok(()) - } - - fn update_changes_trie( - &mut self, - _update: ChangesTrieTransaction, NumberFor>, - ) -> ClientResult<()> { - // we're not storing anything locally => ignore changes - Ok(()) - } - - fn set_genesis_state(&mut self, input: Storage, commit: bool) -> ClientResult { - check_genesis_storage(&input)?; - - // changes trie configuration - let changes_trie_config = input - .top - .iter() - .find(|(k, _)| &k[..] == well_known_keys::CHANGES_TRIE_CONFIG) - .map(|(_, v)| { - Decode::decode(&mut &v[..]) - .expect("changes trie configuration is encoded properly at genesis") - }); - self.changes_trie_config_update = Some(changes_trie_config); - - // this is only called when genesis block is imported => shouldn't be performance bottleneck - let mut storage: HashMap, _> = HashMap::new(); - storage.insert(None, input.top); - - // create a list of children keys to re-compute roots for - let child_delta = input - .children_default - .iter() - .map(|(_storage_key, storage_child)| (&storage_child.child_info, std::iter::empty())); - - // make sure to persist the child storage - for (_child_key, storage_child) in input.children_default.clone() { - storage.insert(Some(storage_child.child_info), storage_child.data); - } - - let storage_update = InMemoryBackend::from(storage); - let (storage_root, _) = storage_update.full_storage_root(std::iter::empty(), child_delta); - if commit { - self.storage_update = Some(storage_update); - } - - Ok(storage_root) - } - - fn reset_storage(&mut self, _input: Storage) -> ClientResult { - Err(ClientError::NotAvailableOnLightClient) - } - - fn insert_aux(&mut self, ops: I) -> ClientResult<()> - where - I: IntoIterator, Option>)>, - { - self.aux_ops.append(&mut ops.into_iter().collect()); - Ok(()) - } - - fn update_storage( - &mut self, - _update: StorageCollection, - _child_update: ChildStorageCollection, - ) -> ClientResult<()> { - // we're not storing anything locally => ignore changes - Ok(()) - } - - fn mark_finalized( - &mut self, - block: BlockId, - _justifications: Option, - ) -> ClientResult<()> { - self.finalized_blocks.push(block); - Ok(()) - } - - fn mark_head(&mut self, block: BlockId) -> ClientResult<()> { - self.set_head = Some(block); - Ok(()) - } - - fn update_transaction_index( - &mut self, - _index: Vec, - ) -> sp_blockchain::Result<()> { - // noop for the light client - Ok(()) - } -} - -impl std::fmt::Debug for GenesisOrUnavailableState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => state.fmt(f), - GenesisOrUnavailableState::Unavailable => write!(f, "Unavailable"), - } - } -} - -impl StateBackend for GenesisOrUnavailableState -where - H::Out: Ord + codec::Codec, -{ - type Error = ClientError; - type Transaction = as StateBackend>::Transaction; - type TrieBackendStorage = as StateBackend>::TrieBackendStorage; - - fn storage(&self, key: &[u8]) -> ClientResult>> { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => - Ok(state.storage(key).expect(IN_MEMORY_EXPECT_PROOF)), - GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient), - } - } - - fn child_storage(&self, child_info: &ChildInfo, key: &[u8]) -> ClientResult>> { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => - Ok(state.child_storage(child_info, key).expect(IN_MEMORY_EXPECT_PROOF)), - GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient), - } - } - - fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => - Ok(state.next_storage_key(key).expect(IN_MEMORY_EXPECT_PROOF)), - GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient), - } - } - - fn next_child_storage_key( - &self, - child_info: &ChildInfo, - key: &[u8], - ) -> Result>, Self::Error> { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => - Ok(state.next_child_storage_key(child_info, key).expect(IN_MEMORY_EXPECT_PROOF)), - GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient), - } - } - - fn for_keys_with_prefix(&self, prefix: &[u8], action: A) { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => - state.for_keys_with_prefix(prefix, action), - GenesisOrUnavailableState::Unavailable => (), - } - } - - fn for_key_values_with_prefix(&self, prefix: &[u8], action: A) { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => - state.for_key_values_with_prefix(prefix, action), - GenesisOrUnavailableState::Unavailable => (), - } - } - - fn apply_to_key_values_while, Vec) -> bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - action: A, - allow_missing: bool, - ) -> ClientResult { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => Ok(state - .apply_to_key_values_while(child_info, prefix, start_at, action, allow_missing) - .expect(IN_MEMORY_EXPECT_PROOF)), - GenesisOrUnavailableState::Unavailable => Err(ClientError::NotAvailableOnLightClient), - } - } - - fn apply_to_keys_while bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - action: A, - ) { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => - state.apply_to_keys_while(child_info, prefix, action), - GenesisOrUnavailableState::Unavailable => (), - } - } - - fn for_child_keys_with_prefix( - &self, - child_info: &ChildInfo, - prefix: &[u8], - action: A, - ) { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => - state.for_child_keys_with_prefix(child_info, prefix, action), - GenesisOrUnavailableState::Unavailable => (), - } - } - - fn storage_root<'a>( - &self, - delta: impl Iterator)>, - ) -> (H::Out, Self::Transaction) - where - H::Out: Ord, - { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => state.storage_root(delta), - GenesisOrUnavailableState::Unavailable => Default::default(), - } - } - - fn child_storage_root<'a>( - &self, - child_info: &ChildInfo, - delta: impl Iterator)>, - ) -> (H::Out, bool, Self::Transaction) - where - H::Out: Ord, - { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => { - let (root, is_equal, _) = state.child_storage_root(child_info, delta); - (root, is_equal, Default::default()) - }, - GenesisOrUnavailableState::Unavailable => (H::Out::default(), true, Default::default()), - } - } - - fn pairs(&self) -> Vec<(Vec, Vec)> { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => state.pairs(), - GenesisOrUnavailableState::Unavailable => Vec::new(), - } - } - - fn keys(&self, prefix: &[u8]) -> Vec> { - match *self { - GenesisOrUnavailableState::Genesis(ref state) => state.keys(prefix), - GenesisOrUnavailableState::Unavailable => Vec::new(), - } - } - - fn register_overlay_stats(&self, _stats: &sp_state_machine::StateMachineStats) {} - - fn usage_info(&self) -> sp_state_machine::UsageInfo { - sp_state_machine::UsageInfo::empty() - } - - fn as_trie_backend(&self) -> Option<&TrieBackend> { - match self { - GenesisOrUnavailableState::Genesis(ref state) => state.as_trie_backend(), - GenesisOrUnavailableState::Unavailable => None, - } - } -} diff --git a/client/light/src/blockchain.rs b/client/light/src/blockchain.rs deleted file mode 100644 index e88c724193697..0000000000000 --- a/client/light/src/blockchain.rs +++ /dev/null @@ -1,219 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Light client blockchain backend. Only stores headers and justifications of recent -//! blocks. CHT roots are stored for headers of ancient blocks. - -use std::sync::Arc; - -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}, - Justifications, -}; - -use crate::fetcher::RemoteHeaderRequest; -pub use sc_client_api::{ - backend::{AuxStore, NewBlockState, ProvideChtRoots}, - blockchain::{ - well_known_cache_keys, Backend as BlockchainBackend, BlockStatus, Cache as BlockchainCache, - HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo, ProvideCache, - }, - cht, - light::{LocalOrRemote, RemoteBlockchain, Storage}, -}; -use sp_blockchain::{ - CachedHeaderMetadata, Error as ClientError, HeaderMetadata, Result as ClientResult, -}; - -/// Light client blockchain. -pub struct Blockchain { - storage: S, -} - -impl Blockchain { - /// Create new light blockchain backed with given storage. - pub fn new(storage: S) -> Self { - Self { storage } - } - - /// Get storage reference. - pub fn storage(&self) -> &S { - &self.storage - } -} - -impl BlockchainHeaderBackend for Blockchain -where - Block: BlockT, - S: Storage, -{ - fn header(&self, id: BlockId) -> ClientResult> { - match RemoteBlockchain::header(self, id)? { - LocalOrRemote::Local(header) => Ok(Some(header)), - LocalOrRemote::Remote(_) => Err(ClientError::NotAvailableOnLightClient), - LocalOrRemote::Unknown => Ok(None), - } - } - - fn info(&self) -> BlockchainInfo { - self.storage.info() - } - - fn status(&self, id: BlockId) -> ClientResult { - self.storage.status(id) - } - - fn number(&self, hash: Block::Hash) -> ClientResult>> { - self.storage.number(hash) - } - - fn hash( - &self, - number: <::Header as HeaderT>::Number, - ) -> ClientResult> { - self.storage.hash(number) - } -} - -impl HeaderMetadata for Blockchain -where - Block: BlockT, - S: Storage, -{ - type Error = ClientError; - - fn header_metadata( - &self, - hash: Block::Hash, - ) -> Result, Self::Error> { - self.storage.header_metadata(hash) - } - - fn insert_header_metadata(&self, hash: Block::Hash, metadata: CachedHeaderMetadata) { - self.storage.insert_header_metadata(hash, metadata) - } - - fn remove_header_metadata(&self, hash: Block::Hash) { - self.storage.remove_header_metadata(hash) - } -} - -impl BlockchainBackend for Blockchain -where - Block: BlockT, - S: Storage, -{ - fn body(&self, _id: BlockId) -> ClientResult>> { - Err(ClientError::NotAvailableOnLightClient) - } - - fn justifications(&self, _id: BlockId) -> ClientResult> { - Err(ClientError::NotAvailableOnLightClient) - } - - fn last_finalized(&self) -> ClientResult { - self.storage.last_finalized() - } - - fn cache(&self) -> Option>> { - self.storage.cache() - } - - fn leaves(&self) -> ClientResult> { - Err(ClientError::NotAvailableOnLightClient) - } - - fn children(&self, _parent_hash: Block::Hash) -> ClientResult> { - Err(ClientError::NotAvailableOnLightClient) - } - - fn indexed_transaction(&self, _hash: &Block::Hash) -> ClientResult>> { - Err(ClientError::NotAvailableOnLightClient) - } - - fn block_indexed_body( - &self, - _id: BlockId, - ) -> sp_blockchain::Result>>> { - Err(ClientError::NotAvailableOnLightClient) - } -} - -impl, Block: BlockT> ProvideCache for Blockchain { - fn cache(&self) -> Option>> { - self.storage.cache() - } -} - -impl RemoteBlockchain for Blockchain -where - S: Storage, -{ - fn header( - &self, - id: BlockId, - ) -> ClientResult>> { - // first, try to read header from local storage - if let Some(local_header) = self.storage.header(id)? { - return Ok(LocalOrRemote::Local(local_header)) - } - - // we need to know block number to check if it's a part of CHT - let number = match id { - BlockId::Hash(hash) => match self.storage.number(hash)? { - Some(number) => number, - None => return Ok(LocalOrRemote::Unknown), - }, - BlockId::Number(number) => number, - }; - - // if the header is genesis (never pruned), non-canonical, or from future => return - if number.is_zero() || self.storage.status(BlockId::Number(number))? == BlockStatus::Unknown - { - return Ok(LocalOrRemote::Unknown) - } - - Ok(LocalOrRemote::Remote(RemoteHeaderRequest { - cht_root: match self.storage.header_cht_root(cht::size(), number)? { - Some(cht_root) => cht_root, - None => return Ok(LocalOrRemote::Unknown), - }, - block: number, - retry_count: None, - })) - } -} - -impl, Block: BlockT> ProvideChtRoots for Blockchain { - fn header_cht_root( - &self, - cht_size: NumberFor, - block: NumberFor, - ) -> sp_blockchain::Result> { - self.storage().header_cht_root(cht_size, block) - } - - fn changes_trie_cht_root( - &self, - cht_size: NumberFor, - block: NumberFor, - ) -> sp_blockchain::Result> { - self.storage().changes_trie_cht_root(cht_size, block) - } -} diff --git a/client/light/src/call_executor.rs b/client/light/src/call_executor.rs deleted file mode 100644 index a0776131e406d..0000000000000 --- a/client/light/src/call_executor.rs +++ /dev/null @@ -1,206 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Methods that light client could use to execute runtime calls. - -use std::{cell::RefCell, panic::UnwindSafe, result, sync::Arc}; - -use codec::{Decode, Encode}; -use hash_db::Hasher; -use sp_core::{ - convert_hash, - traits::{CodeExecutor, SpawnNamed}, - NativeOrEncoded, -}; -use sp_externalities::Extensions; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as HeaderT}, -}; -use sp_state_machine::{ - create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionManager, - ExecutionStrategy, OverlayedChanges, StorageProof, -}; - -use sp_api::{ProofRecorder, StorageTransactionCache}; - -use sp_blockchain::{Error as ClientError, Result as ClientResult}; - -use sc_client_api::{ - backend::RemoteBackend, call_executor::CallExecutor, light::RemoteCallRequest, -}; -use sc_executor::RuntimeVersion; - -/// Call executor that is able to execute calls only on genesis state. -/// -/// Trying to execute call on non-genesis state leads to error. -pub struct GenesisCallExecutor { - backend: Arc, - local: L, -} - -impl GenesisCallExecutor { - /// Create new genesis call executor. - pub fn new(backend: Arc, local: L) -> Self { - Self { backend, local } - } -} - -impl Clone for GenesisCallExecutor { - fn clone(&self) -> Self { - GenesisCallExecutor { backend: self.backend.clone(), local: self.local.clone() } - } -} - -impl CallExecutor for GenesisCallExecutor -where - Block: BlockT, - B: RemoteBackend, - Local: CallExecutor, -{ - type Error = ClientError; - - type Backend = B; - - fn call( - &self, - id: &BlockId, - method: &str, - call_data: &[u8], - strategy: ExecutionStrategy, - extensions: Option, - ) -> ClientResult> { - if self.backend.is_local_state_available(id) { - self.local.call(id, method, call_data, strategy, extensions) - } else { - Err(ClientError::NotAvailableOnLightClient) - } - } - - fn contextual_call< - EM: Fn( - Result, Self::Error>, - Result, Self::Error>, - ) -> Result, Self::Error>, - R: Encode + Decode + PartialEq, - NC: FnOnce() -> result::Result + UnwindSafe, - >( - &self, - at: &BlockId, - method: &str, - call_data: &[u8], - changes: &RefCell, - _: Option<&RefCell>>, - _manager: ExecutionManager, - native_call: Option, - recorder: &Option>, - extensions: Option, - ) -> ClientResult> - where - ExecutionManager: Clone, - { - // there's no actual way/need to specify native/wasm execution strategy on light node - // => we can safely ignore passed values - - if self.backend.is_local_state_available(at) { - CallExecutor::contextual_call::< - fn( - Result, Local::Error>, - Result, Local::Error>, - ) -> Result, Local::Error>, - _, - NC, - >( - &self.local, - at, - method, - call_data, - changes, - None, - ExecutionManager::NativeWhenPossible, - native_call, - recorder, - extensions, - ) - } else { - Err(ClientError::NotAvailableOnLightClient) - } - } - - fn prove_execution( - &self, - at: &BlockId, - method: &str, - call_data: &[u8], - ) -> ClientResult<(Vec, StorageProof)> { - if self.backend.is_local_state_available(at) { - self.local.prove_execution(at, method, call_data) - } else { - Err(ClientError::NotAvailableOnLightClient) - } - } - - fn runtime_version(&self, id: &BlockId) -> ClientResult { - if self.backend.is_local_state_available(id) { - self.local.runtime_version(id) - } else { - Err(ClientError::NotAvailableOnLightClient) - } - } -} - -/// Check remote contextual execution proof using given backend. -/// -/// Proof should include the method execution proof. -pub fn check_execution_proof( - executor: &E, - spawn_handle: Box, - request: &RemoteCallRequest

, - remote_proof: StorageProof, -) -> ClientResult> -where - Header: HeaderT, - E: CodeExecutor + Clone + 'static, - H: Hasher, - H::Out: Ord + codec::Codec + 'static, -{ - let local_state_root = request.header.state_root(); - let root: H::Out = convert_hash(&local_state_root); - - // prepare execution environment - let mut changes = OverlayedChanges::default(); - let trie_backend = create_proof_check_backend(root, remote_proof)?; - - // TODO: Remove when solved: https://github.com/paritytech/substrate/issues/5047 - let backend_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&trie_backend); - let runtime_code = backend_runtime_code - .runtime_code() - .map_err(|_e| ClientError::RuntimeCodeMissing)?; - - // execute method - execution_proof_check_on_trie_backend::( - &trie_backend, - &mut changes, - executor, - spawn_handle, - &request.method, - &request.call_data, - &runtime_code, - ) - .map_err(Into::into) -} diff --git a/client/light/src/fetcher.rs b/client/light/src/fetcher.rs deleted file mode 100644 index 5740e407a5e89..0000000000000 --- a/client/light/src/fetcher.rs +++ /dev/null @@ -1,366 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Light client data fetcher. Fetches requested data from remote full nodes. - -use std::{ - collections::{BTreeMap, HashMap}, - marker::PhantomData, - sync::Arc, -}; - -use codec::{Decode, Encode}; -use hash_db::{HashDB, Hasher, EMPTY_PREFIX}; -use sp_blockchain::{Error as ClientError, Result as ClientResult}; -use sp_core::{ - convert_hash, - storage::{ChildInfo, ChildType}, - traits::{CodeExecutor, SpawnNamed}, -}; -use sp_runtime::traits::{ - AtLeast32Bit, Block as BlockT, CheckedConversion, Hash, HashFor, Header as HeaderT, NumberFor, -}; -pub use sp_state_machine::StorageProof; -use sp_state_machine::{ - key_changes_proof_check_with_db, read_child_proof_check, read_proof_check, - ChangesTrieAnchorBlockId, ChangesTrieConfigurationRange, ChangesTrieRootsStorage, - InMemoryChangesTrieStorage, TrieBackend, -}; - -use crate::{blockchain::Blockchain, call_executor::check_execution_proof}; -pub use sc_client_api::{ - cht, - light::{ - ChangesProof, FetchChecker, Fetcher, RemoteBodyRequest, RemoteCallRequest, - RemoteChangesRequest, RemoteHeaderRequest, RemoteReadChildRequest, RemoteReadRequest, - Storage as BlockchainStorage, - }, -}; - -/// Remote data checker. -pub struct LightDataChecker> { - blockchain: Arc>, - executor: E, - spawn_handle: Box, - _marker: PhantomData, -} - -impl> LightDataChecker { - /// Create new light data checker. - pub fn new( - blockchain: Arc>, - executor: E, - spawn_handle: Box, - ) -> Self { - Self { blockchain, executor, spawn_handle, _marker: PhantomData } - } - - /// Check remote changes query proof assuming that CHT-s are of given size. - pub fn check_changes_proof_with_cht_size( - &self, - request: &RemoteChangesRequest, - remote_proof: ChangesProof, - cht_size: NumberFor, - ) -> ClientResult, u32)>> { - // since we need roots of all changes tries for the range begin..max - // => remote node can't use max block greater that one that we have passed - if remote_proof.max_block > request.max_block.0 || - remote_proof.max_block < request.last_block.0 - { - return Err(ClientError::ChangesTrieAccessFailed(format!( - "Invalid max_block used by the remote node: {}. Local: {}..{}..{}", - remote_proof.max_block, - request.first_block.0, - request.last_block.0, - request.max_block.0, - )) - .into()) - } - - // check if remote node has responded with extra changes trie roots proofs - // all changes tries roots must be in range [request.first_block.0; request.tries_roots.0) - let is_extra_first_root = remote_proof - .roots - .keys() - .next() - .map(|first_root| { - *first_root < request.first_block.0 || *first_root >= request.tries_roots.0 - }) - .unwrap_or(false); - let is_extra_last_root = remote_proof - .roots - .keys() - .next_back() - .map(|last_root| *last_root >= request.tries_roots.0) - .unwrap_or(false); - if is_extra_first_root || is_extra_last_root { - return Err(ClientError::ChangesTrieAccessFailed(format!( - "Extra changes tries roots proofs provided by the remote node: [{:?}..{:?}]. Expected in range: [{}; {})", - remote_proof.roots.keys().next(), remote_proof.roots.keys().next_back(), - request.first_block.0, request.tries_roots.0, - )).into()); - } - - // if request has been composed when some required headers were already pruned - // => remote node has sent us CHT-based proof of required changes tries roots - // => check that this proof is correct before proceeding with changes proof - let remote_max_block = remote_proof.max_block; - let remote_roots = remote_proof.roots; - let remote_roots_proof = remote_proof.roots_proof; - let remote_proof = remote_proof.proof; - if !remote_roots.is_empty() { - self.check_changes_tries_proof(cht_size, &remote_roots, remote_roots_proof)?; - } - - // and now check the key changes proof + get the changes - let mut result = Vec::new(); - let proof_storage = InMemoryChangesTrieStorage::with_proof(remote_proof); - for config_range in &request.changes_trie_configs { - let result_range = key_changes_proof_check_with_db::, _>( - ChangesTrieConfigurationRange { - config: config_range - .config - .as_ref() - .ok_or(ClientError::ChangesTriesNotSupported)?, - zero: config_range.zero.0, - end: config_range.end.map(|(n, _)| n), - }, - &RootsStorage { - roots: (request.tries_roots.0, &request.tries_roots.2), - prev_roots: &remote_roots, - }, - &proof_storage, - request.first_block.0, - &ChangesTrieAnchorBlockId { - hash: convert_hash(&request.last_block.1), - number: request.last_block.0, - }, - remote_max_block, - request.storage_key.as_ref(), - &request.key, - ) - .map_err(|err| ClientError::ChangesTrieAccessFailed(err))?; - result.extend(result_range); - } - - Ok(result) - } - - /// Check CHT-based proof for changes tries roots. - pub fn check_changes_tries_proof( - &self, - cht_size: NumberFor, - remote_roots: &BTreeMap, B::Hash>, - remote_roots_proof: StorageProof, - ) -> ClientResult<()> { - // all the checks are sharing the same storage - let storage = remote_roots_proof.into_memory_db(); - - // remote_roots.keys() are sorted => we can use this to group changes tries roots - // that are belongs to the same CHT - let blocks = remote_roots.keys().cloned(); - cht::for_each_cht_group::( - cht_size, - blocks, - |mut storage, _, cht_blocks| { - // get local changes trie CHT root for given CHT - // it should be there, because it is never pruned AND request has been composed - // when required header has been pruned (=> replaced with CHT) - let first_block = cht_blocks - .first() - .cloned() - .expect("for_each_cht_group never calls callback with empty groups"); - let local_cht_root = self - .blockchain - .storage() - .changes_trie_cht_root(cht_size, first_block)? - .ok_or(ClientError::InvalidCHTProof)?; - - // check changes trie root for every block within CHT range - for block in cht_blocks { - // check if the proofs storage contains the root - // normally this happens in when the proving backend is created, but since - // we share the storage for multiple checks, do it here - if !storage.contains(&local_cht_root, EMPTY_PREFIX) { - return Err(ClientError::InvalidCHTProof.into()) - } - - // check proof for single changes trie root - let proving_backend = TrieBackend::new(storage, local_cht_root); - let remote_changes_trie_root = remote_roots[&block]; - cht::check_proof_on_proving_backend::>( - local_cht_root, - block, - remote_changes_trie_root, - &proving_backend, - )?; - - // and return the storage to use in following checks - storage = proving_backend.into_storage(); - } - - Ok(storage) - }, - storage, - ) - } -} - -impl FetchChecker for LightDataChecker -where - Block: BlockT, - E: CodeExecutor + Clone + 'static, - S: BlockchainStorage, -{ - fn check_header_proof( - &self, - request: &RemoteHeaderRequest, - remote_header: Option, - remote_proof: StorageProof, - ) -> ClientResult { - let remote_header = - remote_header.ok_or_else(|| ClientError::from(ClientError::InvalidCHTProof))?; - let remote_header_hash = remote_header.hash(); - cht::check_proof::>( - request.cht_root, - request.block, - remote_header_hash, - remote_proof, - ) - .map(|_| remote_header) - } - - fn check_read_proof( - &self, - request: &RemoteReadRequest, - remote_proof: StorageProof, - ) -> ClientResult, Option>>> { - read_proof_check::, _>( - convert_hash(request.header.state_root()), - remote_proof, - request.keys.iter(), - ) - .map_err(|e| ClientError::from(e)) - } - - fn check_read_child_proof( - &self, - request: &RemoteReadChildRequest, - remote_proof: StorageProof, - ) -> ClientResult, Option>>> { - let child_info = match ChildType::from_prefixed_key(&request.storage_key) { - Some((ChildType::ParentKeyId, storage_key)) => ChildInfo::new_default(storage_key), - None => return Err(ClientError::InvalidChildType), - }; - read_child_proof_check::, _>( - convert_hash(request.header.state_root()), - remote_proof, - &child_info, - request.keys.iter(), - ) - .map_err(|e| ClientError::from(e)) - } - - fn check_execution_proof( - &self, - request: &RemoteCallRequest, - remote_proof: StorageProof, - ) -> ClientResult> { - check_execution_proof::<_, _, HashFor>( - &self.executor, - self.spawn_handle.clone(), - request, - remote_proof, - ) - } - - fn check_changes_proof( - &self, - request: &RemoteChangesRequest, - remote_proof: ChangesProof, - ) -> ClientResult, u32)>> { - self.check_changes_proof_with_cht_size(request, remote_proof, cht::size()) - } - - fn check_body_proof( - &self, - request: &RemoteBodyRequest, - body: Vec, - ) -> ClientResult> { - // TODO: #2621 - let extrinsics_root = - HashFor::::ordered_trie_root(body.iter().map(Encode::encode).collect()); - if *request.header.extrinsics_root() == extrinsics_root { - Ok(body) - } else { - Err(ClientError::ExtrinsicRootInvalid { - received: request.header.extrinsics_root().to_string(), - expected: extrinsics_root.to_string(), - }) - } - } -} - -/// A view of BTreeMap as a changes trie roots storage. -struct RootsStorage<'a, Number: AtLeast32Bit, Hash: 'a> { - roots: (Number, &'a [Hash]), - prev_roots: &'a BTreeMap, -} - -impl<'a, H, Number, Hash> ChangesTrieRootsStorage for RootsStorage<'a, Number, Hash> -where - H: Hasher, - Number: std::fmt::Display - + std::hash::Hash - + Clone - + AtLeast32Bit - + Encode - + Decode - + Send - + Sync - + 'static, - Hash: 'a + Send + Sync + Clone + AsRef<[u8]>, -{ - fn build_anchor( - &self, - _hash: H::Out, - ) -> Result, String> { - Err("build_anchor is only called when building block".into()) - } - - fn root( - &self, - _anchor: &ChangesTrieAnchorBlockId, - block: Number, - ) -> Result, String> { - // we can't ask for roots from parallel forks here => ignore anchor - let root = if block < self.roots.0 { - self.prev_roots.get(&Number::unique_saturated_from(block)).cloned() - } else { - let index: Option = - block.checked_sub(&self.roots.0).and_then(|index| index.checked_into()); - index.and_then(|index| self.roots.1.get(index as usize).cloned()) - }; - - Ok(root.map(|root| { - let mut hasher_root: H::Out = Default::default(); - hasher_root.as_mut().copy_from_slice(root.as_ref()); - hasher_root - })) - } -} diff --git a/client/light/src/lib.rs b/client/light/src/lib.rs deleted file mode 100644 index 0c874326ef2e0..0000000000000 --- a/client/light/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Light client components. - -use sp_core::traits::{CodeExecutor, SpawnNamed}; -use sp_runtime::traits::{Block as BlockT, HashFor}; -use std::sync::Arc; - -pub mod backend; -pub mod blockchain; -pub mod call_executor; -pub mod fetcher; - -pub use backend::*; -pub use blockchain::*; -pub use call_executor::*; -pub use fetcher::*; - -/// Create an instance of fetch data checker. -pub fn new_fetch_checker>( - blockchain: Arc>, - executor: E, - spawn_handle: Box, -) -> LightDataChecker -where - E: CodeExecutor, -{ - LightDataChecker::new(blockchain, executor, spawn_handle) -} - -/// Create an instance of light client blockchain backend. -pub fn new_light_blockchain>(storage: S) -> Arc> { - Arc::new(Blockchain::new(storage)) -} - -/// Create an instance of light client backend. -pub fn new_light_backend(blockchain: Arc>) -> Arc>> -where - B: BlockT, - S: BlockchainStorage, -{ - Arc::new(Backend::new(blockchain)) -} diff --git a/client/merkle-mountain-range/Cargo.toml b/client/merkle-mountain-range/Cargo.toml new file mode 100644 index 0000000000000..4fb423cee83bc --- /dev/null +++ b/client/merkle-mountain-range/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "mmr-gadget" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository = "https://github.com/paritytech/substrate" +description = "MMR Client gadget for substrate" +homepage = "https://substrate.io" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3" +log = "0.4" +beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy", package = "sp-beefy" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-mmr-primitives = { version = "4.0.0-dev", path = "../../primitives/merkle-mountain-range" } +sc-offchain = { version = "4.0.0-dev", path = "../offchain" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } + +[dev-dependencies] +parking_lot = "0.12.1" +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +tokio = "1.17.0" diff --git a/client/merkle-mountain-range/rpc/Cargo.toml b/client/merkle-mountain-range/rpc/Cargo.toml new file mode 100644 index 0000000000000..dcc5e49c52051 --- /dev/null +++ b/client/merkle-mountain-range/rpc/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "mmr-rpc" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Node-specific RPC methods for interaction with Merkle Mountain Range pallet." + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0" } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +serde = { version = "1.0.136", features = ["derive"] } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-mmr-primitives = { version = "4.0.0-dev", path = "../../../primitives/merkle-mountain-range" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +anyhow = "1" + +[dev-dependencies] +serde_json = "1.0.85" diff --git a/client/merkle-mountain-range/rpc/src/lib.rs b/client/merkle-mountain-range/rpc/src/lib.rs new file mode 100644 index 0000000000000..6e520b3bf130e --- /dev/null +++ b/client/merkle-mountain-range/rpc/src/lib.rs @@ -0,0 +1,361 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +#![warn(missing_docs)] +#![warn(unused_crate_dependencies)] + +//! Node-specific RPC methods for interaction with Merkle Mountain Range pallet. + +use std::{marker::PhantomData, sync::Arc}; + +use codec::{Codec, Decode, Encode}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorObject}, +}; +use serde::{Deserialize, Serialize}; + +use sp_api::{NumberFor, ProvideRuntimeApi}; +use sp_blockchain::HeaderBackend; +use sp_core::Bytes; +use sp_mmr_primitives::{Error as MmrError, Proof}; +use sp_runtime::{generic::BlockId, traits::Block as BlockT}; + +pub use sp_mmr_primitives::MmrApi as MmrRuntimeApi; + +const RUNTIME_ERROR: i32 = 8000; +const MMR_ERROR: i32 = 8010; + +/// Retrieved MMR leaves and their proof. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct LeavesProof { + /// Block hash the proof was generated for. + pub block_hash: BlockHash, + /// SCALE-encoded vector of `LeafData`. + pub leaves: Bytes, + /// SCALE-encoded proof data. See [sp_mmr_primitives::Proof]. + pub proof: Bytes, +} + +impl LeavesProof { + /// Create new `LeavesProof` from a given vector of `Leaf` and a + /// [sp_mmr_primitives::Proof]. + pub fn new( + block_hash: BlockHash, + leaves: Vec, + proof: Proof, + ) -> Self + where + Leaf: Encode, + MmrHash: Encode, + { + Self { block_hash, leaves: Bytes(leaves.encode()), proof: Bytes(proof.encode()) } + } +} + +/// MMR RPC methods. +#[rpc(client, server)] +pub trait MmrApi { + /// Get the MMR root hash for the current best block. + #[method(name = "mmr_root")] + fn mmr_root(&self, at: Option) -> RpcResult; + + /// Generate an MMR proof for the given `block_numbers`. + /// + /// This method calls into a runtime with MMR pallet included and attempts to generate + /// an MMR proof for the set of blocks that have the given `block_numbers` with the MMR root at + /// `best_known_block_number`. `best_known_block_number` must be larger than all the + /// `block_numbers` for the function to succeed. + /// + /// Optionally via `at`, a block hash at which the runtime should be queried can be specified. + /// Optionally via `best_known_block_number`, the proof can be generated using the MMR's state + /// at a specific best block. Note that if `best_known_block_number` is provided, then also + /// specifying the block hash via `at` isn't super-useful here, unless you're generating proof + /// using non-finalized blocks where there are several competing forks. That's because MMR state + /// will be fixed to the state with `best_known_block_number`, which already points to + /// some historical block. + /// + /// Returns the (full) leaves and a proof for these leaves (compact encoding, i.e. hash of + /// the leaves). Both parameters are SCALE-encoded. + /// The order of entries in the `leaves` field of the returned struct + /// is the same as the order of the entries in `block_numbers` supplied + #[method(name = "mmr_generateProof")] + fn generate_proof( + &self, + block_numbers: Vec, + best_known_block_number: Option, + at: Option, + ) -> RpcResult>; + + /// Verify an MMR `proof`. + /// + /// This method calls into a runtime with MMR pallet included and attempts to verify + /// an MMR proof. + /// + /// Returns `true` if the proof is valid, else returns the verification error. + #[method(name = "mmr_verifyProof")] + fn verify_proof(&self, proof: LeavesProof) -> RpcResult; + + /// Verify an MMR `proof` statelessly given an `mmr_root`. + /// + /// This method calls into a runtime with MMR pallet included and attempts to verify + /// an MMR proof against a provided MMR root. + /// + /// Returns `true` if the proof is valid, else returns the verification error. + #[method(name = "mmr_verifyProofStateless")] + fn verify_proof_stateless( + &self, + mmr_root: MmrHash, + proof: LeavesProof, + ) -> RpcResult; +} + +/// MMR RPC methods. +pub struct Mmr { + client: Arc, + _marker: PhantomData, +} + +impl Mmr { + /// Create new `Mmr` with the given reference to the client. + pub fn new(client: Arc) -> Self { + Self { client, _marker: Default::default() } + } +} + +#[async_trait] +impl MmrApiServer<::Hash, NumberFor, MmrHash> + for Mmr +where + Block: BlockT, + Client: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, + Client::Api: MmrRuntimeApi>, + MmrHash: Codec + Send + Sync + 'static, +{ + fn mmr_root(&self, at: Option<::Hash>) -> RpcResult { + let block_hash = at.unwrap_or_else(|| + // If the block hash is not supplied assume the best block. + self.client.info().best_hash); + let api = self.client.runtime_api(); + let mmr_root = api + .mmr_root(&BlockId::Hash(block_hash)) + .map_err(runtime_error_into_rpc_error)? + .map_err(mmr_error_into_rpc_error)?; + Ok(mmr_root) + } + + fn generate_proof( + &self, + block_numbers: Vec>, + best_known_block_number: Option>, + at: Option<::Hash>, + ) -> RpcResult::Hash>> { + let api = self.client.runtime_api(); + let block_hash = at.unwrap_or_else(|| + // If the block hash is not supplied assume the best block. + self.client.info().best_hash); + + let (leaves, proof) = api + .generate_proof_with_context( + &BlockId::hash(block_hash), + sp_core::ExecutionContext::OffchainCall(None), + block_numbers, + best_known_block_number, + ) + .map_err(runtime_error_into_rpc_error)? + .map_err(mmr_error_into_rpc_error)?; + + Ok(LeavesProof::new(block_hash, leaves, proof)) + } + + fn verify_proof(&self, proof: LeavesProof<::Hash>) -> RpcResult { + let api = self.client.runtime_api(); + + let leaves = Decode::decode(&mut &proof.leaves.0[..]) + .map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?; + + let decoded_proof = Decode::decode(&mut &proof.proof.0[..]) + .map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?; + + api.verify_proof_with_context( + &BlockId::hash(proof.block_hash), + sp_core::ExecutionContext::OffchainCall(None), + leaves, + decoded_proof, + ) + .map_err(runtime_error_into_rpc_error)? + .map_err(mmr_error_into_rpc_error)?; + + Ok(true) + } + + fn verify_proof_stateless( + &self, + mmr_root: MmrHash, + proof: LeavesProof<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + + let leaves = Decode::decode(&mut &proof.leaves.0[..]) + .map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?; + + let decoded_proof = Decode::decode(&mut &proof.proof.0[..]) + .map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?; + + api.verify_proof_stateless( + &BlockId::hash(proof.block_hash), + mmr_root, + leaves, + decoded_proof, + ) + .map_err(runtime_error_into_rpc_error)? + .map_err(mmr_error_into_rpc_error)?; + + Ok(true) + } +} + +/// Converts an mmr-specific error into a [`CallError`]. +fn mmr_error_into_rpc_error(err: MmrError) -> CallError { + let error_code = MMR_ERROR + + match err { + MmrError::LeafNotFound => 1, + MmrError::GenerateProof => 2, + MmrError::Verify => 3, + MmrError::InvalidNumericOp => 4, + MmrError::InvalidBestKnownBlock => 5, + _ => 0, + }; + + CallError::Custom(ErrorObject::owned(error_code, err.to_string(), Some(format!("{:?}", err)))) +} + +/// Converts a runtime trap into a [`CallError`]. +fn runtime_error_into_rpc_error(err: impl std::fmt::Debug) -> CallError { + CallError::Custom(ErrorObject::owned( + RUNTIME_ERROR, + "Runtime trapped", + Some(format!("{:?}", err)), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::H256; + + #[test] + fn should_serialize_leaf_proof() { + // given + let leaf = vec![1_u8, 2, 3, 4]; + let proof = Proof { + leaf_indices: vec![1], + leaf_count: 9, + items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], + }; + + let leaf_proof = LeavesProof::new(H256::repeat_byte(0), vec![leaf], proof); + + // when + let actual = serde_json::to_string(&leaf_proof).unwrap(); + + // then + assert_eq!( + actual, + r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaves":"0x041001020304","proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"# + ); + } + + #[test] + fn should_serialize_leaves_proof() { + // given + let leaf_a = vec![1_u8, 2, 3, 4]; + let leaf_b = vec![2_u8, 2, 3, 4]; + let proof = Proof { + leaf_indices: vec![1, 2], + leaf_count: 9, + items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], + }; + + let leaf_proof = LeavesProof::new(H256::repeat_byte(0), vec![leaf_a, leaf_b], proof); + + // when + let actual = serde_json::to_string(&leaf_proof).unwrap(); + + // then + assert_eq!( + actual, + r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaves":"0x0810010203041002020304","proof":"0x080100000000000000020000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"# + ); + } + + #[test] + fn should_deserialize_leaf_proof() { + // given + let expected = LeavesProof { + block_hash: H256::repeat_byte(0), + leaves: Bytes(vec![vec![1_u8, 2, 3, 4]].encode()), + proof: Bytes( + Proof { + leaf_indices: vec![1], + leaf_count: 9, + items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], + } + .encode(), + ), + }; + + // when + let actual: LeavesProof = serde_json::from_str(r#"{ + "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "leaves":"0x041001020304", + "proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202" + }"#).unwrap(); + + // then + assert_eq!(actual, expected); + } + + #[test] + fn should_deserialize_leaves_proof() { + // given + let expected = LeavesProof { + block_hash: H256::repeat_byte(0), + leaves: Bytes(vec![vec![1_u8, 2, 3, 4], vec![2_u8, 2, 3, 4]].encode()), + proof: Bytes( + Proof { + leaf_indices: vec![1, 2], + leaf_count: 9, + items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], + } + .encode(), + ), + }; + + // when + let actual: LeavesProof = serde_json::from_str(r#"{ + "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "leaves":"0x0810010203041002020304", + "proof":"0x080100000000000000020000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202" + }"#).unwrap(); + + // then + assert_eq!(actual, expected); + } +} diff --git a/client/merkle-mountain-range/src/aux_schema.rs b/client/merkle-mountain-range/src/aux_schema.rs new file mode 100644 index 0000000000000..907deb0bde239 --- /dev/null +++ b/client/merkle-mountain-range/src/aux_schema.rs @@ -0,0 +1,228 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Schema for MMR-gadget state persisted in the aux-db. + +use crate::LOG_TARGET; +use codec::{Decode, Encode}; +use log::{info, trace}; +use sc_client_api::backend::AuxStore; +use sp_blockchain::{Error as ClientError, Result as ClientResult}; +use sp_runtime::traits::{Block, NumberFor}; + +const VERSION_KEY: &[u8] = b"mmr_auxschema_version"; +const GADGET_STATE: &[u8] = b"mmr_gadget_state"; + +const CURRENT_VERSION: u32 = 1; +pub(crate) type PersistedState = NumberFor; + +pub(crate) fn write_current_version(backend: &B) -> ClientResult<()> { + info!(target: LOG_TARGET, "write aux schema version {:?}", CURRENT_VERSION); + AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[]) +} + +/// Write gadget state. +pub(crate) fn write_gadget_state( + backend: &BE, + state: &PersistedState, +) -> ClientResult<()> { + trace!(target: LOG_TARGET, "persisting {:?}", state); + backend.insert_aux(&[(GADGET_STATE, state.encode().as_slice())], &[]) +} + +fn load_decode(backend: &B, key: &[u8]) -> ClientResult> { + match backend.get_aux(key)? { + None => Ok(None), + Some(t) => T::decode(&mut &t[..]) + .map_err(|e| ClientError::Backend(format!("MMR aux DB is corrupted: {}", e))) + .map(Some), + } +} + +/// Load or initialize persistent data from backend. +pub(crate) fn load_persistent(backend: &BE) -> ClientResult>> +where + B: Block, + BE: AuxStore, +{ + let version: Option = load_decode(backend, VERSION_KEY)?; + + match version { + None => (), + Some(1) => return load_decode::<_, PersistedState>(backend, GADGET_STATE), + other => + return Err(ClientError::Backend(format!("Unsupported MMR aux DB version: {:?}", other))), + } + + // No persistent state found in DB. + Ok(None) +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::test_utils::{ + run_test_with_mmr_gadget_pre_post_using_client, MmrBlock, MockClient, OffchainKeyType, + }; + use parking_lot::Mutex; + use sp_core::offchain::{DbExternalities, StorageKind}; + use sp_mmr_primitives::utils::NodesUtils; + use sp_runtime::generic::BlockId; + use std::{sync::Arc, time::Duration}; + use substrate_test_runtime_client::{runtime::Block, Backend}; + + #[test] + fn should_load_persistent_sanity_checks() { + let client = MockClient::new(); + let backend = &*client.backend; + + // version not available in db -> None + assert_eq!(load_persistent::(backend).unwrap(), None); + + // populate version in db + write_current_version(backend).unwrap(); + // verify correct version is retrieved + assert_eq!(load_decode(backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); + + // version is available in db but state isn't -> None + assert_eq!(load_persistent::(backend).unwrap(), None); + } + + #[test] + fn should_persist_progress_across_runs() { + sp_tracing::try_init_simple(); + + let client = Arc::new(MockClient::new()); + let backend = client.backend.clone(); + + // version not available in db -> None + assert_eq!(load_decode::>(&*backend, VERSION_KEY).unwrap(), None); + // state not available in db -> None + assert_eq!(load_persistent::(&*backend).unwrap(), None); + // run the gadget while importing and finalizing 3 blocks + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |_| async {}, + |client| async move { + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Number(1), b"a2", Some(1)).await; + let a3 = client.import_block(&BlockId::Number(2), b"a3", Some(2)).await; + client.finalize_block(a3.hash(), Some(3)); + tokio::time::sleep(Duration::from_millis(200)).await; + // a1, a2, a3 were canonicalized + client.assert_canonicalized(&[&a1, &a2, &a3]); + }, + ); + + // verify previous progress was persisted and run the gadget again + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |client| async move { + let backend = &*client.backend; + // check there is both version and best canon available in db before running gadget + assert_eq!(load_decode(backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); + assert_eq!(load_persistent::(backend).unwrap(), Some(3)); + }, + |client| async move { + let a4 = client.import_block(&BlockId::Number(3), b"a4", Some(3)).await; + let a5 = client.import_block(&BlockId::Number(4), b"a5", Some(4)).await; + let a6 = client.import_block(&BlockId::Number(5), b"a6", Some(5)).await; + client.finalize_block(a6.hash(), Some(6)); + tokio::time::sleep(Duration::from_millis(200)).await; + + // a4, a5, a6 were canonicalized + client.assert_canonicalized(&[&a4, &a5, &a6]); + // check persisted best canon was updated + assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(6)); + }, + ); + } + + #[test] + fn should_resume_from_persisted_state() { + sp_tracing::try_init_simple(); + + let client = Arc::new(MockClient::new()); + let blocks = Arc::new(Mutex::new(Vec::::new())); + let blocks_clone = blocks.clone(); + + // run the gadget while importing and finalizing 3 blocks + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |_| async {}, + |client| async move { + let mut blocks = blocks_clone.lock(); + blocks.push(client.import_block(&BlockId::Number(0), b"a1", Some(0)).await); + blocks.push(client.import_block(&BlockId::Number(1), b"a2", Some(1)).await); + blocks.push(client.import_block(&BlockId::Number(2), b"a3", Some(2)).await); + client.finalize_block(blocks.last().unwrap().hash(), Some(3)); + tokio::time::sleep(Duration::from_millis(200)).await; + // a1, a2, a3 were canonicalized + let slice: Vec<&MmrBlock> = blocks.iter().collect(); + client.assert_canonicalized(&slice); + + // now manually move them back to non-canon/temp location + let mut offchain_db = client.offchain_db(); + for mmr_block in slice { + for node in NodesUtils::right_branch_ending_in_leaf(mmr_block.leaf_idx.unwrap()) + { + let canon_key = mmr_block.get_offchain_key(node, OffchainKeyType::Canon); + let val = offchain_db + .local_storage_get(StorageKind::PERSISTENT, &canon_key) + .unwrap(); + offchain_db.local_storage_clear(StorageKind::PERSISTENT, &canon_key); + + let temp_key = mmr_block.get_offchain_key(node, OffchainKeyType::Temp); + offchain_db.local_storage_set(StorageKind::PERSISTENT, &temp_key, &val); + } + } + }, + ); + + let blocks_clone = blocks.clone(); + // verify new gadget continues from block 4 and ignores 1, 2, 3 based on persisted state + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |client| async move { + let blocks = blocks_clone.lock(); + let slice: Vec<&MmrBlock> = blocks.iter().collect(); + + // verify persisted state says a1, a2, a3 were canonicalized, + assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(3)); + // but actually they are NOT canon (we manually reverted them earlier). + client.assert_not_canonicalized(&slice); + }, + |client| async move { + let a4 = client.import_block(&BlockId::Number(3), b"a4", Some(3)).await; + let a5 = client.import_block(&BlockId::Number(4), b"a5", Some(4)).await; + let a6 = client.import_block(&BlockId::Number(5), b"a6", Some(5)).await; + client.finalize_block(a6.hash(), Some(6)); + tokio::time::sleep(Duration::from_millis(200)).await; + + let block_1_to_3 = blocks.lock(); + let slice: Vec<&MmrBlock> = block_1_to_3.iter().collect(); + // verify a1, a2, a3 are still NOT canon (skipped by gadget based on data in aux db) + client.assert_not_canonicalized(&slice); + // but a4, a5, a6 were canonicalized + client.assert_canonicalized(&[&a4, &a5, &a6]); + // check persisted best canon was updated + assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(6)); + }, + ); + } +} diff --git a/client/merkle-mountain-range/src/lib.rs b/client/merkle-mountain-range/src/lib.rs new file mode 100644 index 0000000000000..401a5d5d4d56b --- /dev/null +++ b/client/merkle-mountain-range/src/lib.rs @@ -0,0 +1,274 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! # MMR offchain gadget +//! +//! The MMR offchain gadget is run alongside `pallet-mmr` to assist it with offchain +//! canonicalization of finalized MMR leaves and nodes. +//! The gadget should only be run on nodes that have Indexing API enabled (otherwise +//! `pallet-mmr` cannot write to offchain and this gadget has nothing to do). +//! +//! The runtime `pallet-mmr` creates one new MMR leaf per block and all inner MMR parent nodes +//! generated by the MMR when adding said leaf. MMR nodes are stored both in: +//! - on-chain storage - hashes only; not full leaf content; +//! - off-chain storage - via Indexing API, full leaf content (and all internal nodes as well) is +//! saved to the Off-chain DB using a key derived from `parent_hash` and node index in MMR. The +//! `parent_hash` is also used within the key to avoid conflicts and overwrites on forks (leaf +//! data is only allowed to reference data coming from parent block). +//! +//! This gadget is driven by block finality and in responsible for pruning stale forks from +//! offchain db, and moving finalized forks under a "canonical" key based solely on node `pos` +//! in the MMR. + +#![warn(missing_docs)] + +mod aux_schema; +mod offchain_mmr; +#[cfg(test)] +pub mod test_utils; + +use crate::offchain_mmr::OffchainMmr; +use beefy_primitives::MmrRootHash; +use futures::StreamExt; +use log::{debug, error, trace, warn}; +use sc_client_api::{Backend, BlockchainEvents, FinalityNotifications}; +use sc_offchain::OffchainDb; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::{HeaderBackend, HeaderMetadata}; +use sp_mmr_primitives::{utils, LeafIndex, MmrApi}; +use sp_runtime::{ + generic::BlockId, + traits::{Block, Header, NumberFor}, +}; +use std::{marker::PhantomData, sync::Arc}; + +/// Logging target for the mmr gadget. +pub const LOG_TARGET: &str = "mmr"; + +struct OffchainMmrBuilder, C> { + backend: Arc, + client: Arc, + offchain_db: OffchainDb, + indexing_prefix: Vec, + + _phantom: PhantomData, +} + +impl OffchainMmrBuilder +where + B: Block, + BE: Backend, + C: ProvideRuntimeApi + HeaderBackend + HeaderMetadata, + C::Api: MmrApi>, +{ + async fn try_build( + self, + finality_notifications: &mut FinalityNotifications, + ) -> Option> { + while let Some(notification) = finality_notifications.next().await { + let best_block = *notification.header.number(); + match self.client.runtime_api().mmr_leaf_count(&BlockId::number(best_block)) { + Ok(Ok(mmr_leaf_count)) => { + debug!( + target: LOG_TARGET, + "pallet-mmr detected at block {:?} with mmr size {:?}", + best_block, + mmr_leaf_count + ); + match utils::first_mmr_block_num::(best_block, mmr_leaf_count) { + Ok(first_mmr_block) => { + debug!( + target: LOG_TARGET, + "pallet-mmr genesis computed at block {:?}", first_mmr_block, + ); + let best_canonicalized = + match offchain_mmr::load_or_init_best_canonicalized::( + &*self.backend, + first_mmr_block, + ) { + Ok(best) => best, + Err(e) => { + error!( + target: LOG_TARGET, + "Error loading state from aux db: {:?}", e + ); + return None + }, + }; + let mut offchain_mmr = OffchainMmr { + backend: self.backend, + client: self.client, + offchain_db: self.offchain_db, + indexing_prefix: self.indexing_prefix, + first_mmr_block, + best_canonicalized, + }; + // We need to make sure all blocks leading up to current notification + // have also been canonicalized. + offchain_mmr.canonicalize_catch_up(¬ification); + // We have to canonicalize and prune the blocks in the finality + // notification that lead to building the offchain-mmr as well. + offchain_mmr.canonicalize_and_prune(notification); + return Some(offchain_mmr) + }, + Err(e) => { + error!( + target: LOG_TARGET, + "Error calculating the first mmr block: {:?}", e + ); + }, + } + }, + _ => { + trace!( + target: LOG_TARGET, + "Waiting for MMR pallet to become available... (best finalized {:?})", + notification.header.number() + ); + }, + } + } + + error!( + target: LOG_TARGET, + "Finality notifications stream closed unexpectedly. \ + Couldn't build the canonicalization engine", + ); + None + } +} + +/// A MMR Gadget. +pub struct MmrGadget, C> { + finality_notifications: FinalityNotifications, + + _phantom: PhantomData<(B, BE, C)>, +} + +impl MmrGadget +where + B: Block, + ::Number: Into, + BE: Backend, + C: BlockchainEvents + HeaderBackend + HeaderMetadata + ProvideRuntimeApi, + C::Api: MmrApi>, +{ + async fn run(mut self, builder: OffchainMmrBuilder) { + let mut offchain_mmr = match builder.try_build(&mut self.finality_notifications).await { + Some(offchain_mmr) => offchain_mmr, + None => return, + }; + + while let Some(notification) = self.finality_notifications.next().await { + offchain_mmr.canonicalize_and_prune(notification); + } + } + + /// Create and run the MMR gadget. + pub async fn start(client: Arc, backend: Arc, indexing_prefix: Vec) { + let offchain_db = match backend.offchain_storage() { + Some(offchain_storage) => OffchainDb::new(offchain_storage), + None => { + warn!( + target: LOG_TARGET, + "Can't spawn a MmrGadget for a node without offchain storage." + ); + return + }, + }; + + let mmr_gadget = MmrGadget:: { + finality_notifications: client.finality_notification_stream(), + + _phantom: Default::default(), + }; + mmr_gadget + .run(OffchainMmrBuilder { + backend, + client, + offchain_db, + indexing_prefix, + _phantom: Default::default(), + }) + .await + } +} + +#[cfg(test)] +mod tests { + use crate::test_utils::run_test_with_mmr_gadget; + use sp_runtime::generic::BlockId; + use std::time::Duration; + + #[test] + fn mmr_first_block_is_computed_correctly() { + // Check the case where the first block is also the first block with MMR. + run_test_with_mmr_gadget(|client| async move { + // G -> A1 -> A2 + // | + // | -> first mmr block + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", Some(1)).await; + + client.finalize_block(a1.hash(), Some(1)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: a1 + client.assert_canonicalized(&[&a1]); + client.assert_not_pruned(&[&a2]); + }); + + // Check the case where the first block with MMR comes later. + run_test_with_mmr_gadget(|client| async move { + // G -> A1 -> A2 -> A3 -> A4 -> A5 -> A6 + // | + // | -> first mmr block + + let a1 = client.import_block(&BlockId::Number(0), b"a1", None).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", None).await; + let a3 = client.import_block(&BlockId::Hash(a2.hash()), b"a3", None).await; + let a4 = client.import_block(&BlockId::Hash(a3.hash()), b"a4", Some(0)).await; + let a5 = client.import_block(&BlockId::Hash(a4.hash()), b"a5", Some(1)).await; + let a6 = client.import_block(&BlockId::Hash(a5.hash()), b"a6", Some(2)).await; + + client.finalize_block(a5.hash(), Some(2)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: a4, a5 + client.assert_canonicalized(&[&a4, &a5]); + client.assert_not_pruned(&[&a6]); + }); + } + + #[test] + fn does_not_panic_on_invalid_num_mmr_blocks() { + run_test_with_mmr_gadget(|client| async move { + // G -> A1 + // | + // | -> first mmr block + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + + // Simulate the case where the runtime says that there are 2 mmr_blocks when in fact + // there is only 1. + client.finalize_block(a1.hash(), Some(2)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: - + client.assert_not_canonicalized(&[&a1]); + }); + } +} diff --git a/client/merkle-mountain-range/src/offchain_mmr.rs b/client/merkle-mountain-range/src/offchain_mmr.rs new file mode 100644 index 0000000000000..988b3ffef882a --- /dev/null +++ b/client/merkle-mountain-range/src/offchain_mmr.rs @@ -0,0 +1,361 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Logic for canonicalizing MMR offchain entries for finalized forks, +//! and for pruning MMR offchain entries for stale forks. + +#![warn(missing_docs)] + +use crate::{aux_schema, LOG_TARGET}; +use log::{debug, error, info, warn}; +use sc_client_api::{AuxStore, Backend, FinalityNotification}; +use sc_offchain::OffchainDb; +use sp_blockchain::{CachedHeaderMetadata, ForkBackend, HeaderBackend, HeaderMetadata}; +use sp_core::offchain::{DbExternalities, StorageKind}; +use sp_mmr_primitives::{utils, utils::NodesUtils, NodeIndex}; +use sp_runtime::{ + traits::{Block, NumberFor, One}, + Saturating, +}; +use std::{collections::VecDeque, sync::Arc}; + +pub(crate) fn load_or_init_best_canonicalized( + backend: &BE, + first_mmr_block: NumberFor, +) -> sp_blockchain::Result> +where + BE: AuxStore, + B: Block, +{ + // Initialize gadget best_canon from AUX DB or from pallet genesis. + if let Some(best) = aux_schema::load_persistent::(backend)? { + info!(target: LOG_TARGET, "Loading MMR best canonicalized state from db: {:?}.", best); + Ok(best) + } else { + let best = first_mmr_block.saturating_sub(One::one()); + info!( + target: LOG_TARGET, + "Loading MMR from pallet genesis on what appears to be the first startup: {:?}.", best + ); + aux_schema::write_current_version(backend)?; + aux_schema::write_gadget_state::(backend, &best)?; + Ok(best) + } +} + +/// `OffchainMMR` exposes MMR offchain canonicalization and pruning logic. +pub struct OffchainMmr, C> { + pub backend: Arc, + pub client: Arc, + pub offchain_db: OffchainDb, + pub indexing_prefix: Vec, + pub first_mmr_block: NumberFor, + pub best_canonicalized: NumberFor, +} + +impl OffchainMmr +where + C: HeaderBackend + HeaderMetadata, + BE: Backend, + B: Block, +{ + fn node_temp_offchain_key(&self, pos: NodeIndex, parent_hash: B::Hash) -> Vec { + NodesUtils::node_temp_offchain_key::(&self.indexing_prefix, pos, parent_hash) + } + + fn node_canon_offchain_key(&self, pos: NodeIndex) -> Vec { + NodesUtils::node_canon_offchain_key(&self.indexing_prefix, pos) + } + + fn header_metadata_or_log( + &self, + hash: B::Hash, + action: &str, + ) -> Option> { + match self.client.header_metadata(hash) { + Ok(header) => Some(header), + _ => { + debug!( + target: LOG_TARGET, + "Block {} not found. Couldn't {} associated branch.", hash, action + ); + None + }, + } + } + + fn right_branch_ending_in_block_or_log( + &self, + block_num: NumberFor, + action: &str, + ) -> Option> { + match utils::block_num_to_leaf_index::(block_num, self.first_mmr_block) { + Ok(leaf_idx) => { + let branch = NodesUtils::right_branch_ending_in_leaf(leaf_idx); + debug!( + target: LOG_TARGET, + "Nodes to {} for block {}: {:?}", action, block_num, branch + ); + Some(branch) + }, + Err(e) => { + error!( + target: LOG_TARGET, + "Error converting block number {} to leaf index: {:?}. \ + Couldn't {} associated branch.", + block_num, + e, + action + ); + None + }, + } + } + + fn prune_branch(&mut self, block_hash: &B::Hash) { + let action = "prune"; + let header = match self.header_metadata_or_log(*block_hash, action) { + Some(header) => header, + _ => return, + }; + + // We prune the leaf associated with the provided block and all the nodes added by that + // leaf. + let stale_nodes = match self.right_branch_ending_in_block_or_log(header.number, action) { + Some(nodes) => nodes, + None => { + // If we can't convert the block number to a leaf index, the chain state is probably + // corrupted. We only log the error, hoping that the chain state will be fixed. + return + }, + }; + + for pos in stale_nodes { + let temp_key = self.node_temp_offchain_key(pos, header.parent); + self.offchain_db.local_storage_clear(StorageKind::PERSISTENT, &temp_key); + debug!(target: LOG_TARGET, "Pruned elem at pos {} with temp key {:?}", pos, temp_key); + } + } + + fn canonicalize_branch(&mut self, block_hash: B::Hash) { + let action = "canonicalize"; + let header = match self.header_metadata_or_log(block_hash, action) { + Some(header) => header, + _ => return, + }; + + // Don't canonicalize branches corresponding to blocks for which the MMR pallet + // wasn't yet initialized. + if header.number < self.first_mmr_block { + return + } + + // We "canonicalize" the leaf associated with the provided block + // and all the nodes added by that leaf. + let to_canon_nodes = match self.right_branch_ending_in_block_or_log(header.number, action) { + Some(nodes) => nodes, + None => { + // If we can't convert the block number to a leaf index, the chain state is probably + // corrupted. We only log the error, hoping that the chain state will be fixed. + self.best_canonicalized = header.number; + return + }, + }; + + for pos in to_canon_nodes { + let temp_key = self.node_temp_offchain_key(pos, header.parent); + if let Some(elem) = + self.offchain_db.local_storage_get(StorageKind::PERSISTENT, &temp_key) + { + let canon_key = self.node_canon_offchain_key(pos); + self.offchain_db.local_storage_set(StorageKind::PERSISTENT, &canon_key, &elem); + self.offchain_db.local_storage_clear(StorageKind::PERSISTENT, &temp_key); + debug!( + target: LOG_TARGET, + "Moved elem at pos {} from temp key {:?} to canon key {:?}", + pos, + temp_key, + canon_key + ); + } else { + debug!( + target: LOG_TARGET, + "Couldn't canonicalize elem at pos {} using temp key {:?}", pos, temp_key + ); + } + } + if self.best_canonicalized != header.number.saturating_sub(One::one()) { + warn!( + target: LOG_TARGET, + "Detected canonicalization skip: best {:?} current {:?}.", + self.best_canonicalized, + header.number, + ); + } + self.best_canonicalized = header.number; + } + + /// In case of missed finality notifications (node restarts for example), + /// make sure to also canon everything leading up to `notification.tree_route`. + pub fn canonicalize_catch_up(&mut self, notification: &FinalityNotification) { + let first = notification.tree_route.first().unwrap_or(¬ification.hash); + if let Some(mut header) = self.header_metadata_or_log(*first, "canonicalize") { + let mut to_canon = VecDeque::<::Hash>::new(); + // Walk up the chain adding all blocks newer than `self.best_canonicalized`. + loop { + header = match self.header_metadata_or_log(header.parent, "canonicalize") { + Some(header) => header, + _ => break, + }; + if header.number <= self.best_canonicalized { + break + } + to_canon.push_front(header.hash); + } + // Canonicalize all blocks leading up to current finality notification. + for hash in to_canon.drain(..) { + self.canonicalize_branch(hash); + } + if let Err(e) = + aux_schema::write_gadget_state::(&*self.backend, &self.best_canonicalized) + { + debug!(target: LOG_TARGET, "error saving state: {:?}", e); + } + } + } + + /// Move leafs and nodes added by finalized blocks in offchain db from _fork-aware key_ to + /// _canonical key_. + /// Prune leafs and nodes added by stale blocks in offchain db from _fork-aware key_. + pub fn canonicalize_and_prune(&mut self, notification: FinalityNotification) { + // Move offchain MMR nodes for finalized blocks to canonical keys. + for hash in notification.tree_route.iter().chain(std::iter::once(¬ification.hash)) { + self.canonicalize_branch(*hash); + } + if let Err(e) = + aux_schema::write_gadget_state::(&*self.backend, &self.best_canonicalized) + { + debug!(target: LOG_TARGET, "error saving state: {:?}", e); + } + + // Remove offchain MMR nodes for stale forks. + let stale_forks = self.client.expand_forks(¬ification.stale_heads).unwrap_or_else( + |(stale_forks, e)| { + warn!(target: LOG_TARGET, "{:?}", e); + stale_forks + }, + ); + for hash in stale_forks.iter() { + self.prune_branch(hash); + } + } +} + +#[cfg(test)] +mod tests { + use crate::test_utils::{run_test_with_mmr_gadget, run_test_with_mmr_gadget_pre_post}; + use parking_lot::Mutex; + use sp_runtime::generic::BlockId; + use std::{sync::Arc, time::Duration}; + + #[test] + fn canonicalize_and_prune_works_correctly() { + run_test_with_mmr_gadget(|client| async move { + // -> D4 -> D5 + // G -> A1 -> A2 -> A3 -> A4 + // -> B1 -> B2 -> B3 + // -> C1 + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", Some(1)).await; + let a3 = client.import_block(&BlockId::Hash(a2.hash()), b"a3", Some(2)).await; + let a4 = client.import_block(&BlockId::Hash(a3.hash()), b"a4", Some(3)).await; + + let b1 = client.import_block(&BlockId::Number(0), b"b1", Some(0)).await; + let b2 = client.import_block(&BlockId::Hash(b1.hash()), b"b2", Some(1)).await; + let b3 = client.import_block(&BlockId::Hash(b2.hash()), b"b3", Some(2)).await; + + let c1 = client.import_block(&BlockId::Number(0), b"c1", Some(0)).await; + + let d4 = client.import_block(&BlockId::Hash(a3.hash()), b"d4", Some(3)).await; + let d5 = client.import_block(&BlockId::Hash(d4.hash()), b"d5", Some(4)).await; + + client.finalize_block(a3.hash(), Some(3)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: a1, a2, a3 + client.assert_canonicalized(&[&a1, &a2, &a3]); + // expected stale heads: c1 + // expected pruned heads because of temp key collision: b1 + client.assert_pruned(&[&c1, &b1]); + + client.finalize_block(d5.hash(), None); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: d4, d5, + client.assert_canonicalized(&[&d4, &d5]); + // expected stale heads: b1, b2, b3, a4 + client.assert_pruned(&[&b1, &b2, &b3, &a4]); + }) + } + + #[test] + fn canonicalize_catchup_works_correctly() { + let mmr_blocks = Arc::new(Mutex::new(vec![])); + let mmr_blocks_ref = mmr_blocks.clone(); + run_test_with_mmr_gadget_pre_post( + |client| async move { + // G -> A1 -> A2 + // | | + // | | -> finalized without gadget (missed notification) + // | + // | -> first mmr block + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", Some(1)).await; + + client.finalize_block(a2.hash(), Some(2)); + + { + let mut mmr_blocks = mmr_blocks_ref.lock(); + mmr_blocks.push(a1); + mmr_blocks.push(a2); + } + }, + |client| async move { + // G -> A1 -> A2 -> A3 -> A4 + // | | | | + // | | | | -> finalized after starting gadget + // | | | + // | | | -> gadget start + // | | + // | | -> finalized before starting gadget (missed notification) + // | + // | -> first mmr block + let blocks = mmr_blocks.lock(); + let a1 = blocks[0].clone(); + let a2 = blocks[1].clone(); + let a3 = client.import_block(&BlockId::Hash(a2.hash()), b"a3", Some(2)).await; + let a4 = client.import_block(&BlockId::Hash(a3.hash()), b"a4", Some(3)).await; + + client.finalize_block(a4.hash(), Some(4)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: a1, a2 _and_ a3, a4. + client.assert_canonicalized(&[&a1, &a2, &a3, &a4]); + }, + ) + } +} diff --git a/client/merkle-mountain-range/src/test_utils.rs b/client/merkle-mountain-range/src/test_utils.rs new file mode 100644 index 0000000000000..f345fb52578ab --- /dev/null +++ b/client/merkle-mountain-range/src/test_utils.rs @@ -0,0 +1,354 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::MmrGadget; +use parking_lot::Mutex; +use sc_block_builder::BlockBuilderProvider; +use sc_client_api::{ + Backend as BackendT, BlockchainEvents, FinalityNotifications, ImportNotifications, + StorageEventStream, StorageKey, +}; +use sc_offchain::OffchainDb; +use sp_api::{ApiRef, ProvideRuntimeApi}; +use sp_blockchain::{BlockStatus, CachedHeaderMetadata, HeaderBackend, HeaderMetadata, Info}; +use sp_consensus::BlockOrigin; +use sp_core::{ + offchain::{DbExternalities, StorageKind}, + H256, +}; +use sp_mmr_primitives as mmr; +use sp_mmr_primitives::{utils::NodesUtils, LeafIndex, NodeIndex}; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Header as HeaderT}, +}; +use std::{future::Future, sync::Arc, time::Duration}; +use substrate_test_runtime_client::{ + runtime::{Block, BlockNumber, Hash, Header}, + Backend, BlockBuilderExt, Client, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, + TestClientBuilder, TestClientBuilderExt, +}; +use tokio::runtime::Runtime; + +type MmrHash = H256; + +pub(crate) struct MockRuntimeApiData { + pub(crate) num_blocks: BlockNumber, +} + +#[derive(Clone)] +pub(crate) struct MockRuntimeApi { + pub(crate) data: Arc>, +} + +impl MockRuntimeApi { + pub(crate) const INDEXING_PREFIX: &'static [u8] = b"mmr_test"; +} + +#[derive(Clone, Debug)] +pub(crate) struct MmrBlock { + pub(crate) block: Block, + pub(crate) leaf_idx: Option, + pub(crate) leaf_data: Vec, +} + +#[derive(Clone, Copy)] +pub enum OffchainKeyType { + Temp, + Canon, +} + +impl MmrBlock { + pub fn hash(&self) -> Hash { + self.block.hash() + } + + pub fn parent_hash(&self) -> Hash { + *self.block.header.parent_hash() + } + + pub fn get_offchain_key(&self, node: NodeIndex, key_type: OffchainKeyType) -> Vec { + match key_type { + OffchainKeyType::Temp => NodesUtils::node_temp_offchain_key::
( + MockRuntimeApi::INDEXING_PREFIX, + node, + self.parent_hash(), + ), + OffchainKeyType::Canon => + NodesUtils::node_canon_offchain_key(MockRuntimeApi::INDEXING_PREFIX, node), + } + } +} + +pub(crate) struct MockClient { + pub(crate) client: Mutex>, + pub(crate) backend: Arc, + pub(crate) runtime_api_params: Arc>, +} + +impl MockClient { + pub(crate) fn new() -> Self { + let client_builder = TestClientBuilder::new().enable_offchain_indexing_api(); + let (client, backend) = client_builder.build_with_backend(); + MockClient { + client: Mutex::new(client), + backend, + runtime_api_params: Arc::new(Mutex::new(MockRuntimeApiData { num_blocks: 0 })), + } + } + + pub(crate) fn offchain_db(&self) -> OffchainDb<>::OffchainStorage> { + OffchainDb::new(self.backend.offchain_storage().unwrap()) + } + + pub async fn import_block( + &self, + at: &BlockId, + name: &[u8], + maybe_leaf_idx: Option, + ) -> MmrBlock { + let mut client = self.client.lock(); + + let mut block_builder = client.new_block_at(at, Default::default(), false).unwrap(); + // Make sure the block has a different hash than its siblings + block_builder + .push_storage_change(b"name".to_vec(), Some(name.to_vec())) + .unwrap(); + let block = block_builder.build().unwrap().block; + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + let parent_hash = *block.header.parent_hash(); + // Simulate writing MMR nodes in offchain storage + if let Some(leaf_idx) = maybe_leaf_idx { + let mut offchain_db = self.offchain_db(); + for node in NodesUtils::right_branch_ending_in_leaf(leaf_idx) { + let temp_key = NodesUtils::node_temp_offchain_key::
( + MockRuntimeApi::INDEXING_PREFIX, + node, + parent_hash, + ); + offchain_db.local_storage_set( + StorageKind::PERSISTENT, + &temp_key, + parent_hash.as_ref(), + ) + } + } + + MmrBlock { block, leaf_idx: maybe_leaf_idx, leaf_data: parent_hash.as_ref().to_vec() } + } + + pub fn finalize_block(&self, hash: Hash, maybe_num_mmr_blocks: Option) { + let client = self.client.lock(); + if let Some(num_mmr_blocks) = maybe_num_mmr_blocks { + self.runtime_api_params.lock().num_blocks = num_mmr_blocks; + } + + client.finalize_block(hash, None).unwrap(); + } + + pub fn check_offchain_storage( + &self, + key_type: OffchainKeyType, + blocks: &[&MmrBlock], + mut f: F, + ) where + F: FnMut(Option>, &MmrBlock), + { + let mut offchain_db = self.offchain_db(); + for mmr_block in blocks { + for node in NodesUtils::right_branch_ending_in_leaf(mmr_block.leaf_idx.unwrap()) { + let temp_key = mmr_block.get_offchain_key(node, key_type); + let val = offchain_db.local_storage_get(StorageKind::PERSISTENT, &temp_key); + f(val, mmr_block); + } + } + } + + pub fn assert_pruned(&self, blocks: &[&MmrBlock]) { + self.check_offchain_storage(OffchainKeyType::Temp, blocks, |val, _block| { + assert!(val.is_none()); + }) + } + + pub fn assert_not_pruned(&self, blocks: &[&MmrBlock]) { + self.check_offchain_storage(OffchainKeyType::Temp, blocks, |val, block| { + assert_eq!(val.as_ref(), Some(&block.leaf_data)); + }) + } + + pub fn assert_canonicalized(&self, blocks: &[&MmrBlock]) { + self.check_offchain_storage(OffchainKeyType::Canon, blocks, |val, block| { + assert_eq!(val.as_ref(), Some(&block.leaf_data)); + }); + + self.assert_pruned(blocks); + } + + pub fn assert_not_canonicalized(&self, blocks: &[&MmrBlock]) { + self.check_offchain_storage(OffchainKeyType::Canon, blocks, |val, _block| { + assert!(val.is_none()); + }); + + self.assert_not_pruned(blocks); + } +} + +impl HeaderMetadata for MockClient { + type Error = as HeaderMetadata>::Error; + + fn header_metadata(&self, hash: Hash) -> Result, Self::Error> { + self.client.lock().header_metadata(hash) + } + + fn insert_header_metadata(&self, _hash: Hash, _header_metadata: CachedHeaderMetadata) { + todo!() + } + + fn remove_header_metadata(&self, _hash: Hash) { + todo!() + } +} + +impl HeaderBackend for MockClient { + fn header(&self, id: BlockId) -> sc_client_api::blockchain::Result> { + self.client.lock().header(&id) + } + + fn info(&self) -> Info { + self.client.lock().info() + } + + fn status(&self, id: BlockId) -> sc_client_api::blockchain::Result { + self.client.lock().status(id) + } + + fn number(&self, hash: Hash) -> sc_client_api::blockchain::Result> { + self.client.lock().number(hash) + } + + fn hash(&self, number: BlockNumber) -> sc_client_api::blockchain::Result> { + self.client.lock().hash(number) + } +} + +impl BlockchainEvents for MockClient { + fn import_notification_stream(&self) -> ImportNotifications { + unimplemented!() + } + + fn finality_notification_stream(&self) -> FinalityNotifications { + self.client.lock().finality_notification_stream() + } + + fn storage_changes_notification_stream( + &self, + _filter_keys: Option<&[StorageKey]>, + _child_filter_keys: Option<&[(StorageKey, Option>)]>, + ) -> sc_client_api::blockchain::Result> { + unimplemented!() + } +} + +impl ProvideRuntimeApi for MockClient { + type Api = MockRuntimeApi; + + fn runtime_api(&self) -> ApiRef<'_, Self::Api> { + MockRuntimeApi { data: self.runtime_api_params.clone() }.into() + } +} + +sp_api::mock_impl_runtime_apis! { + impl mmr::MmrApi for MockRuntimeApi { + fn mmr_root() -> Result { + Err(mmr::Error::PalletNotIncluded) + } + + fn mmr_leaf_count(&self) -> Result { + Ok(self.data.lock().num_blocks) + } + + fn generate_proof( + &self, + _block_numbers: Vec, + _best_known_block_number: Option, + ) -> Result<(Vec, mmr::Proof), mmr::Error> { + Err(mmr::Error::PalletNotIncluded) + } + + fn verify_proof(_leaves: Vec, _proof: mmr::Proof) + -> Result<(), mmr::Error> + { + Err(mmr::Error::PalletNotIncluded) + } + + fn verify_proof_stateless( + _root: MmrHash, + _leaves: Vec, + _proof: mmr::Proof + ) -> Result<(), mmr::Error> { + Err(mmr::Error::PalletNotIncluded) + } + } +} + +pub(crate) fn run_test_with_mmr_gadget(post_gadget: F) +where + F: FnOnce(Arc) -> Fut + 'static, + Fut: Future, +{ + run_test_with_mmr_gadget_pre_post(|_| async {}, post_gadget); +} + +pub(crate) fn run_test_with_mmr_gadget_pre_post(pre_gadget: F, post_gadget: G) +where + F: FnOnce(Arc) -> RetF + 'static, + G: FnOnce(Arc) -> RetG + 'static, + RetF: Future, + RetG: Future, +{ + let client = Arc::new(MockClient::new()); + run_test_with_mmr_gadget_pre_post_using_client(client, pre_gadget, post_gadget) +} + +pub(crate) fn run_test_with_mmr_gadget_pre_post_using_client( + client: Arc, + pre_gadget: F, + post_gadget: G, +) where + F: FnOnce(Arc) -> RetF + 'static, + G: FnOnce(Arc) -> RetG + 'static, + RetF: Future, + RetG: Future, +{ + let client_clone = client.clone(); + let runtime = Runtime::new().unwrap(); + runtime.block_on(async move { pre_gadget(client_clone).await }); + + let client_clone = client.clone(); + runtime.spawn(async move { + let backend = client_clone.backend.clone(); + MmrGadget::start(client_clone, backend, MockRuntimeApi::INDEXING_PREFIX.to_vec()).await + }); + + runtime.block_on(async move { + tokio::time::sleep(Duration::from_millis(200)).await; + + post_gadget(client).await + }); +} diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index c078e5b892fe2..c40a830f9219b 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -4,8 +4,8 @@ name = "sc-network-gossip" version = "0.10.0-dev" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" authors = ["Parity Technologies "] -edition = "2018" -homepage = "https://substrate.dev" +edition = "2021" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-gossip" readme = "README.md" @@ -13,19 +13,20 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -futures = "0.3.9" +ahash = "0.7.6" +futures = "0.3.21" futures-timer = "3.0.1" -libp2p = { version = "0.39.1", default-features = false } -log = "0.4.8" -lru = "0.6.6" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.9.0", path = "../../utils/prometheus" } -sc-network = { version = "0.10.0-dev", path = "../network" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -tracing = "0.1.25" +libp2p = { version = "0.49.0", default-features = false } +log = "0.4.17" +lru = "0.8.1" +tracing = "0.1.29" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } +sc-peerset = { version = "4.0.0-dev", path = "../peerset" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] -async-std = "1.6.5" -quickcheck = "1.0.3" +tokio = "1.22.0" +quickcheck = { version = "1.0.3", default-features = false } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/network-gossip/src/bridge.rs b/client/network-gossip/src/bridge.rs index 70b13983d8bd3..462677f53c5fd 100644 --- a/client/network-gossip/src/bridge.rs +++ b/client/network-gossip/src/bridge.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,7 +21,8 @@ use crate::{ Network, Validator, }; -use sc_network::{Event, ReputationChange}; +use sc_network_common::protocol::{event::Event, ProtocolName}; +use sc_peerset::ReputationChange; use futures::{ channel::mpsc::{channel, Receiver, Sender}, @@ -32,20 +33,19 @@ use log::trace; use prometheus_endpoint::Registry; use sp_runtime::traits::Block as BlockT; use std::{ - borrow::Cow, collections::{HashMap, VecDeque}, pin::Pin, sync::Arc, task::{Context, Poll}, }; -/// Wraps around an implementation of the `Network` crate and provides gossiping capabilities on +/// Wraps around an implementation of the [`Network`] trait and provides gossiping capabilities on /// top of it. pub struct GossipEngine { state_machine: ConsensusGossip, network: Box + Send>, periodic_maintenance_interval: futures_timer::Delay, - protocol: Cow<'static, str>, + protocol: ProtocolName, /// Incoming events from the network. network_event_stream: Pin + Send>>, @@ -53,10 +53,12 @@ pub struct GossipEngine { message_sinks: HashMap>>, /// Buffered messages (see [`ForwardingState`]). forwarding_state: ForwardingState, + + is_terminated: bool, } /// A gossip engine receives messages from the network via the `network_event_stream` and forwards -/// them to upper layers via the `message sinks`. In the scenario where messages have been received +/// them to upper layers via the `message_sinks`. In the scenario where messages have been received /// from the network but a subscribed message sink is not yet ready to receive the messages, the /// messages are buffered. To model this process a gossip engine can be in two states. enum ForwardingState { @@ -75,7 +77,7 @@ impl GossipEngine { /// Create a new instance. pub fn new + Send + Clone + 'static>( network: N, - protocol: impl Into>, + protocol: impl Into, validator: Arc>, metrics_registry: Option<&Registry>, ) -> Self @@ -83,7 +85,7 @@ impl GossipEngine { B: 'static, { let protocol = protocol.into(); - let network_event_stream = network.event_stream(); + let network_event_stream = network.event_stream("network-gossip"); GossipEngine { state_machine: ConsensusGossip::new(validator, protocol.clone(), metrics_registry), @@ -94,6 +96,8 @@ impl GossipEngine { network_event_stream, message_sinks: HashMap::new(), forwarding_state: ForwardingState::Idle, + + is_terminated: false, } } @@ -147,7 +151,7 @@ impl GossipEngine { /// Send addressed message to the given peers. The message is not kept or multicast /// later on. - pub fn send_message(&mut self, who: Vec, data: Vec) { + pub fn send_message(&mut self, who: Vec, data: Vec) { for who in &who { self.state_machine.send_message(&mut *self.network, who, data.clone()); } @@ -158,7 +162,7 @@ impl GossipEngine { /// Note: this method isn't strictly related to gossiping and should eventually be moved /// somewhere else. pub fn announce(&self, block: B::Hash, associated_data: Option>) { - self.network.announce(block, associated_data); + self.network.announce_block(block, associated_data); } } @@ -177,7 +181,10 @@ impl Future for GossipEngine { this.network.add_set_reserved(remote, this.protocol.clone()); }, Event::SyncDisconnected { remote } => { - this.network.remove_set_reserved(remote, this.protocol.clone()); + this.network.remove_peers_from_reserved_set( + this.protocol.clone(), + vec![remote], + ); }, Event::NotificationStreamOpened { remote, protocol, role, .. } => { if protocol != this.protocol { @@ -214,7 +221,10 @@ impl Future for GossipEngine { Event::Dht(_) => {}, }, // The network event stream closed. Do the same for [`GossipValidator`]. - Poll::Ready(None) => return Poll::Ready(()), + Poll::Ready(None) => { + self.is_terminated = true; + return Poll::Ready(()) + }, Poll::Pending => break, } }, @@ -288,22 +298,36 @@ impl Future for GossipEngine { } } +impl futures::future::FusedFuture for GossipEngine { + fn is_terminated(&self) -> bool { + self.is_terminated + } +} + #[cfg(test)] mod tests { use super::*; - use crate::{ValidationResult, ValidatorContext}; - use async_std::task::spawn; + use crate::{multiaddr::Multiaddr, ValidationResult, ValidatorContext}; use futures::{ channel::mpsc::{unbounded, UnboundedSender}, executor::{block_on, block_on_stream}, future::poll_fn, }; use quickcheck::{Arbitrary, Gen, QuickCheck}; - use sc_network::ObservedRole; - use sp_runtime::{testing::H256, traits::Block as BlockT}; + use sc_network_common::{ + config::MultiaddrWithPeerId, + protocol::role::ObservedRole, + service::{ + NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, + NotificationSender, NotificationSenderError, + }, + }; + use sp_runtime::{ + testing::H256, + traits::{Block as BlockT, NumberFor}, + }; use std::{ - borrow::Cow, - convert::TryInto, + collections::HashSet, sync::{Arc, Mutex}, }; use substrate_test_runtime_client::runtime::Block; @@ -318,29 +342,109 @@ mod tests { event_senders: Vec>, } - impl Network for TestNetwork { - fn event_stream(&self) -> Pin + Send>> { + impl NetworkPeers for TestNetwork { + fn set_authorized_peers(&self, _peers: HashSet) { + unimplemented!(); + } + + fn set_authorized_only(&self, _reserved_only: bool) { + unimplemented!(); + } + + fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) { + unimplemented!(); + } + + fn report_peer(&self, _who: PeerId, _cost_benefit: ReputationChange) {} + + fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) { + unimplemented!(); + } + + fn accept_unreserved_peers(&self) { + unimplemented!(); + } + + fn deny_unreserved_peers(&self) { + unimplemented!(); + } + + fn add_reserved_peer(&self, _peer: MultiaddrWithPeerId) -> Result<(), String> { + unimplemented!(); + } + + fn remove_reserved_peer(&self, _peer_id: PeerId) { + unimplemented!(); + } + + fn set_reserved_peers( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn add_peers_to_reserved_set( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_peers_from_reserved_set(&self, _protocol: ProtocolName, _peers: Vec) {} + + fn add_to_peers_set( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_from_peers_set(&self, _protocol: ProtocolName, _peers: Vec) { + unimplemented!(); + } + + fn sync_num_connected(&self) -> usize { + unimplemented!(); + } + } + + impl NetworkEventStream for TestNetwork { + fn event_stream(&self, _name: &'static str) -> Pin + Send>> { let (tx, rx) = unbounded(); self.inner.lock().unwrap().event_senders.push(tx); Box::pin(rx) } + } - fn report_peer(&self, _: PeerId, _: ReputationChange) {} - - fn disconnect_peer(&self, _: PeerId, _: Cow<'static, str>) { + impl NetworkNotification for TestNetwork { + fn write_notification(&self, _target: PeerId, _protocol: ProtocolName, _message: Vec) { unimplemented!(); } - fn add_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} - - fn remove_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} + fn notification_sender( + &self, + _target: PeerId, + _protocol: ProtocolName, + ) -> Result, NotificationSenderError> { + unimplemented!(); + } + } - fn write_notification(&self, _: PeerId, _: Cow<'static, str>, _: Vec) { + impl NetworkBlock<::Hash, NumberFor> for TestNetwork { + fn announce_block(&self, _hash: ::Hash, _data: Option>) { unimplemented!(); } - fn announce(&self, _: B::Hash, _: Option>) { + fn new_best_block_imported( + &self, + _hash: ::Hash, + _number: NumberFor, + ) { unimplemented!(); } } @@ -385,10 +489,10 @@ mod tests { })) } - #[test] - fn keeps_multiple_subscribers_per_topic_updated_with_both_old_and_new_messages() { + #[tokio::test(flavor = "multi_thread")] + async fn keeps_multiple_subscribers_per_topic_updated_with_both_old_and_new_messages() { let topic = H256::default(); - let protocol = Cow::Borrowed("/my_protocol"); + let protocol = ProtocolName::from("/my_protocol"); let remote_peer = PeerId::random(); let network = TestNetwork::default(); @@ -404,7 +508,7 @@ mod tests { // Register the remote peer. event_sender .start_send(Event::NotificationStreamOpened { - remote: remote_peer.clone(), + remote: remote_peer, protocol: protocol.clone(), negotiated_fallback: None, role: ObservedRole::Authority, @@ -416,7 +520,7 @@ mod tests { .iter() .cloned() .map(|m| Event::NotificationsReceived { - remote: remote_peer.clone(), + remote: remote_peer, messages: vec![(protocol.clone(), m.into())], }) .collect::>(); @@ -436,8 +540,10 @@ mod tests { .start_send(events[1].clone()) .expect("Event stream is unbounded; qed."); - spawn(gossip_engine); + tokio::spawn(gossip_engine); + // Note: `block_on_stream()`-derived iterator will block the current thread, + // so we need a `multi_thread` `tokio::test` runtime flavor. let mut subscribers = subscribers.into_iter().map(|s| block_on_stream(s)).collect::>(); @@ -446,10 +552,7 @@ mod tests { for subscriber in subscribers.iter_mut() { assert_eq!( subscriber.next(), - Some(TopicNotification { - message: message.clone(), - sender: Some(remote_peer.clone()), - }), + Some(TopicNotification { message: message.clone(), sender: Some(remote_peer) }), ); } } @@ -508,7 +611,7 @@ mod tests { } fn prop(channels: Vec, notifications: Vec>) { - let protocol = Cow::Borrowed("/my_protocol"); + let protocol = ProtocolName::from("/my_protocol"); let remote_peer = PeerId::random(); let network = TestNetwork::default(); @@ -545,7 +648,7 @@ mod tests { // Create channels. let (txs, mut rxs) = channels .iter() - .map(|ChannelLengthAndTopic { length, topic }| (topic.clone(), channel(*length))) + .map(|ChannelLengthAndTopic { length, topic }| (*topic, channel(*length))) .fold((vec![], vec![]), |mut acc, (topic, (tx, rx))| { acc.0.push((topic, tx)); acc.1.push((topic, rx)); @@ -567,7 +670,7 @@ mod tests { // Register the remote peer. event_sender .start_send(Event::NotificationStreamOpened { - remote: remote_peer.clone(), + remote: remote_peer, protocol: protocol.clone(), negotiated_fallback: None, role: ObservedRole::Authority, @@ -594,10 +697,7 @@ mod tests { .collect(); event_sender - .start_send(Event::NotificationsReceived { - remote: remote_peer.clone(), - messages, - }) + .start_send(Event::NotificationsReceived { remote: remote_peer, messages }) .expect("Event stream is unbounded; qed."); } diff --git a/client/network-gossip/src/lib.rs b/client/network-gossip/src/lib.rs index 55c2fc820637e..1c8fb8ba05ce7 100644 --- a/client/network-gossip/src/lib.rs +++ b/client/network-gossip/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -26,32 +26,32 @@ //! message, assuming it is valid. //! //! Topics are a single 32-byte tag associated with a message, used to group those messages -//! in an opaque way. Consensus code can invoke `broadcast_topic` to attempt to send all messages -//! under a single topic to all peers who don't have them yet, and `send_topic` to -//! send all messages under a single topic to a specific peer. +//! in an opaque way. Consensus code can invoke [`ValidatorContext::broadcast_topic`] to +//! attempt to send all messages under a single topic to all peers who don't have them yet, and +//! [`ValidatorContext::send_topic`] to send all messages under a single topic to a specific peer. //! //! # Usage //! -//! - Implement the `Network` trait, representing the low-level networking primitives. It is already -//! implemented on `sc_network::NetworkService`. -//! - Implement the `Validator` trait. See the section below. +//! - Implement the [`Network`] trait, representing the low-level networking primitives. It is +//! already implemented on `sc_network::NetworkService`. +//! - Implement the [`Validator`] trait. See the section below. //! - Decide on a protocol name. Each gossiping protocol should have a different one. -//! - Build a `GossipEngine` using these three elements. -//! - Use the methods of the `GossipEngine` in order to send out messages and receive incoming +//! - Build a [`GossipEngine`] using these three elements. +//! - Use the methods of the [`GossipEngine`] in order to send out messages and receive incoming //! messages. //! -//! The `GossipEngine` will automatically use `Network::add_set_reserved` and -//! `Network::remove_set_reserved` to maintain a set of peers equal to the set of peers the -//! node is syncing from. See the documentation of `sc-network` for more explanations about the -//! concepts of peer sets. +//! The [`GossipEngine`] will automatically use [`Network::add_set_reserved`] and +//! [`NetworkPeers::remove_peers_from_reserved_set`] to maintain a set of peers equal to the set of +//! peers the node is syncing from. See the documentation of `sc-network` for more explanations +//! about the concepts of peer sets. //! //! # What is a validator? //! -//! The primary role of a `Validator` is to process incoming messages from peers, and decide +//! The primary role of a [`Validator`] is to process incoming messages from peers, and decide //! whether to discard them or process them. It also decides whether to re-broadcast the message. //! -//! The secondary role of the `Validator` is to check if a message is allowed to be sent to a given -//! peer. All messages, before being sent, will be checked against this filter. +//! The secondary role of the [`Validator`] is to check if a message is allowed to be sent to a +//! given peer. All messages, before being sent, will be checked against this filter. //! This enables the validator to use information it's aware of about connected peers to decide //! whether to send messages to them at any given moment in time - In particular, to wait until //! peers can accept and process the message before sending it. @@ -67,83 +67,36 @@ pub use self::{ validator::{DiscardAll, MessageIntent, ValidationResult, Validator, ValidatorContext}, }; -use futures::prelude::*; -use sc_network::{multiaddr, Event, ExHashT, NetworkService, PeerId, ReputationChange}; -use sp_runtime::traits::Block as BlockT; -use std::{borrow::Cow, iter, pin::Pin, sync::Arc}; +use libp2p::{multiaddr, PeerId}; +use sc_network_common::{ + protocol::ProtocolName, + service::{NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers}, +}; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::iter; mod bridge; mod state_machine; mod validator; /// Abstraction over a network. -pub trait Network { - /// Returns a stream of events representing what happens on the network. - fn event_stream(&self) -> Pin + Send>>; - - /// Adjust the reputation of a node. - fn report_peer(&self, peer_id: PeerId, reputation: ReputationChange); - - /// Adds the peer to the set of peers to be connected to with this protocol. - fn add_set_reserved(&self, who: PeerId, protocol: Cow<'static, str>); - - /// Removes the peer from the set of peers to be connected to with this protocol. - fn remove_set_reserved(&self, who: PeerId, protocol: Cow<'static, str>); - - /// Force-disconnect a peer. - fn disconnect_peer(&self, who: PeerId, protocol: Cow<'static, str>); - - /// Send a notification to a peer. - fn write_notification(&self, who: PeerId, protocol: Cow<'static, str>, message: Vec); - - /// Notify everyone we're connected to that we have the given block. - /// - /// Note: this method isn't strictly related to gossiping and should eventually be moved - /// somewhere else. - fn announce(&self, block: B::Hash, associated_data: Option>); -} - -impl Network for Arc> { - fn event_stream(&self) -> Pin + Send>> { - Box::pin(NetworkService::event_stream(self, "network-gossip")) - } - - fn report_peer(&self, peer_id: PeerId, reputation: ReputationChange) { - NetworkService::report_peer(self, peer_id, reputation); - } - - fn add_set_reserved(&self, who: PeerId, protocol: Cow<'static, str>) { +pub trait Network: + NetworkPeers + NetworkEventStream + NetworkNotification + NetworkBlock> +{ + fn add_set_reserved(&self, who: PeerId, protocol: ProtocolName) { let addr = iter::once(multiaddr::Protocol::P2p(who.into())).collect::(); - let result = - NetworkService::add_peers_to_reserved_set(self, protocol, iter::once(addr).collect()); + let result = self.add_peers_to_reserved_set(protocol, iter::once(addr).collect()); if let Err(err) = result { log::error!(target: "gossip", "add_set_reserved failed: {}", err); } } +} - fn remove_set_reserved(&self, who: PeerId, protocol: Cow<'static, str>) { - let addr = - iter::once(multiaddr::Protocol::P2p(who.into())).collect::(); - let result = NetworkService::remove_peers_from_reserved_set( - self, - protocol, - iter::once(addr).collect(), - ); - if let Err(err) = result { - log::error!(target: "gossip", "remove_set_reserved failed: {}", err); - } - } - - fn disconnect_peer(&self, who: PeerId, protocol: Cow<'static, str>) { - NetworkService::disconnect_peer(self, who, protocol) - } - - fn write_notification(&self, who: PeerId, protocol: Cow<'static, str>, message: Vec) { - NetworkService::write_notification(self, who, protocol, message) - } - - fn announce(&self, block: B::Hash, associated_data: Option>) { - NetworkService::announce_block(self, block, associated_data) - } +impl Network for T where + T: NetworkPeers + + NetworkEventStream + + NetworkNotification + + NetworkBlock> +{ } diff --git a/client/network-gossip/src/state_machine.rs b/client/network-gossip/src/state_machine.rs index 920b44d8c1e5a..001f2c6136a00 100644 --- a/client/network-gossip/src/state_machine.rs +++ b/client/network-gossip/src/state_machine.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,19 +18,13 @@ use crate::{MessageIntent, Network, ValidationResult, Validator, ValidatorContext}; +use ahash::AHashSet; use libp2p::PeerId; use lru::LruCache; use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; -use sc_network::ObservedRole; +use sc_network_common::protocol::{role::ObservedRole, ProtocolName}; use sp_runtime::traits::{Block as BlockT, Hash, HashFor}; -use std::{ - borrow::Cow, - collections::{HashMap, HashSet}, - iter, - sync::Arc, - time, - time::Instant, -}; +use std::{collections::HashMap, iter, num::NonZeroUsize, sync::Arc, time, time::Instant}; // FIXME: Add additional spam/DoS attack protection: https://github.com/paritytech/substrate/issues/1115 // NOTE: The current value is adjusted based on largest production network deployment (Kusama) and @@ -48,15 +42,15 @@ const REBROADCAST_INTERVAL: time::Duration = time::Duration::from_millis(750); pub(crate) const PERIODIC_MAINTENANCE_INTERVAL: time::Duration = time::Duration::from_millis(1100); mod rep { - use sc_network::ReputationChange as Rep; + use sc_peerset::ReputationChange as Rep; /// Reputation change when a peer sends us a gossip message that we didn't know about. - pub const GOSSIP_SUCCESS: Rep = Rep::new(1 << 4, "Successfull gossip"); + pub const GOSSIP_SUCCESS: Rep = Rep::new(1 << 4, "Successful gossip"); /// Reputation change when a peer sends us a gossip message that we already knew about. pub const DUPLICATE_GOSSIP: Rep = Rep::new(-(1 << 2), "Duplicate gossip"); } struct PeerConsensus { - known_messages: HashSet, + known_messages: AHashSet, } /// Topic stream message with sender. @@ -94,8 +88,7 @@ impl<'g, 'p, B: BlockT> ValidatorContext for NetworkContext<'g, 'p, B> { /// Send addressed message to a peer. fn send_message(&mut self, who: &PeerId, message: Vec) { - self.network - .write_notification(who.clone(), self.gossip.protocol.clone(), message); + self.network.write_notification(*who, self.gossip.protocol.clone(), message); } /// Send all messages with given topic to a peer. @@ -106,7 +99,7 @@ impl<'g, 'p, B: BlockT> ValidatorContext for NetworkContext<'g, 'p, B> { fn propagate<'a, B: BlockT, I>( network: &mut dyn Network, - protocol: Cow<'static, str>, + protocol: ProtocolName, messages: I, intent: MessageIntent, peers: &mut HashMap>, @@ -122,13 +115,13 @@ where for (message_hash, topic, message) in messages.clone() { let intent = match intent { MessageIntent::Broadcast { .. } => - if peer.known_messages.contains(&message_hash) { + if peer.known_messages.contains(message_hash) { continue } else { MessageIntent::Broadcast }, MessageIntent::PeriodicRebroadcast => { - if peer.known_messages.contains(&message_hash) { + if peer.known_messages.contains(message_hash) { MessageIntent::PeriodicRebroadcast } else { // peer doesn't know message, so the logic should treat it as an @@ -139,11 +132,11 @@ where other => other, }; - if !message_allowed(id, intent, &topic, &message) { + if !message_allowed(id, intent, topic, message) { continue } - peer.known_messages.insert(message_hash.clone()); + peer.known_messages.insert(*message_hash); tracing::trace!( target: "gossip", @@ -152,7 +145,7 @@ where ?message, "Propagating message", ); - network.write_notification(id.clone(), protocol.clone(), message.clone()); + network.write_notification(*id, protocol.clone(), message.clone()); } } } @@ -162,7 +155,7 @@ pub struct ConsensusGossip { peers: HashMap>, messages: Vec>, known_messages: LruCache, - protocol: Cow<'static, str>, + protocol: ProtocolName, validator: Arc>, next_broadcast: Instant, metrics: Option, @@ -172,7 +165,7 @@ impl ConsensusGossip { /// Create a new instance using the given validator. pub fn new( validator: Arc>, - protocol: Cow<'static, str>, + protocol: ProtocolName, metrics_registry: Option<&Registry>, ) -> Self { let metrics = match metrics_registry.map(Metrics::register) { @@ -187,7 +180,11 @@ impl ConsensusGossip { ConsensusGossip { peers: HashMap::new(), messages: Default::default(), - known_messages: LruCache::new(KNOWN_MESSAGES_CACHE_SIZE), + known_messages: { + let cap = NonZeroUsize::new(KNOWN_MESSAGES_CACHE_SIZE) + .expect("cache capacity is not zero"); + LruCache::new(cap) + }, protocol, validator, next_broadcast: Instant::now() + REBROADCAST_INTERVAL, @@ -204,7 +201,7 @@ impl ConsensusGossip { ?role, "Registering peer", ); - self.peers.insert(who.clone(), PeerConsensus { known_messages: HashSet::new() }); + self.peers.insert(who, PeerConsensus { known_messages: Default::default() }); let validator = self.validator.clone(); let mut context = NetworkContext { gossip: self, network }; @@ -218,7 +215,7 @@ impl ConsensusGossip { message: Vec, sender: Option, ) { - if self.known_messages.put(message_hash.clone(), ()).is_none() { + if self.known_messages.put(message_hash, ()).is_none() { self.messages.push(MessageEntry { message_hash, topic, message, sender }); if let Some(ref metrics) = self.metrics { @@ -324,10 +321,7 @@ impl ConsensusGossip { self.messages .iter() .filter(move |e| e.topic == topic) - .map(|entry| TopicNotification { - message: entry.message.clone(), - sender: entry.sender.clone(), - }) + .map(|entry| TopicNotification { message: entry.message.clone(), sender: entry.sender }) } /// Register incoming messages and return the ones that are new and valid (according to a gossip @@ -360,7 +354,7 @@ impl ConsensusGossip { protocol = %self.protocol, "Ignored already known message", ); - network.report_peer(who.clone(), rep::DUPLICATE_GOSSIP); + network.report_peer(who, rep::DUPLICATE_GOSSIP); continue } @@ -398,15 +392,13 @@ impl ConsensusGossip { }, }; - network.report_peer(who.clone(), rep::GOSSIP_SUCCESS); + network.report_peer(who, rep::GOSSIP_SUCCESS); peer.known_messages.insert(message_hash); - to_forward.push(( - topic, - TopicNotification { message: message.clone(), sender: Some(who.clone()) }, - )); + to_forward + .push((topic, TopicNotification { message: message.clone(), sender: Some(who) })); if keep { - self.register_message_hashed(message_hash, topic, message, Some(who.clone())); + self.register_message_hashed(message_hash, topic, message, Some(who)); } } @@ -436,7 +428,7 @@ impl ConsensusGossip { continue } - peer.known_messages.insert(entry.message_hash.clone()); + peer.known_messages.insert(entry.message_hash); tracing::trace!( target: "gossip", @@ -445,11 +437,7 @@ impl ConsensusGossip { ?entry.message, "Sending topic message", ); - network.write_notification( - who.clone(), - self.protocol.clone(), - entry.message.clone(), - ); + network.write_notification(*who, self.protocol.clone(), entry.message.clone()); } } } @@ -494,7 +482,7 @@ impl ConsensusGossip { ); peer.known_messages.insert(message_hash); - network.write_notification(who.clone(), self.protocol.clone(), message); + network.write_notification(*who, self.protocol.clone(), message); } } @@ -508,14 +496,14 @@ impl Metrics { Ok(Self { registered_messages: register( Counter::new( - "network_gossip_registered_messages_total", + "substrate_network_gossip_registered_messages_total", "Number of registered messages by the gossip service.", )?, registry, )?, expired_messages: register( Counter::new( - "network_gossip_expired_messages_total", + "substrate_network_gossip_expired_messages_total", "Number of expired messages by the gossip service.", )?, registry, @@ -527,11 +515,23 @@ impl Metrics { #[cfg(test)] mod tests { use super::*; + use crate::multiaddr::Multiaddr; use futures::prelude::*; - use sc_network::{Event, ReputationChange}; - use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper, H256}; + use sc_network_common::{ + config::MultiaddrWithPeerId, + protocol::event::Event, + service::{ + NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, + NotificationSender, NotificationSenderError, + }, + }; + use sc_peerset::ReputationChange; + use sp_runtime::{ + testing::{Block as RawBlock, ExtrinsicWrapper, H256}, + traits::NumberFor, + }; use std::{ - borrow::Cow, + collections::HashSet, pin::Pin, sync::{Arc, Mutex}, }; @@ -585,28 +585,108 @@ mod tests { peer_reports: Vec<(PeerId, ReputationChange)>, } - impl Network for NoOpNetwork { - fn event_stream(&self) -> Pin + Send>> { + impl NetworkPeers for NoOpNetwork { + fn set_authorized_peers(&self, _peers: HashSet) { + unimplemented!(); + } + + fn set_authorized_only(&self, _reserved_only: bool) { unimplemented!(); } - fn report_peer(&self, peer_id: PeerId, reputation_change: ReputationChange) { - self.inner.lock().unwrap().peer_reports.push((peer_id, reputation_change)); + fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) { + unimplemented!(); + } + + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { + self.inner.lock().unwrap().peer_reports.push((who, cost_benefit)); } - fn disconnect_peer(&self, _: PeerId, _: Cow<'static, str>) { + fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) { unimplemented!(); } - fn add_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} + fn accept_unreserved_peers(&self) { + unimplemented!(); + } - fn remove_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} + fn deny_unreserved_peers(&self) { + unimplemented!(); + } - fn write_notification(&self, _: PeerId, _: Cow<'static, str>, _: Vec) { + fn add_reserved_peer(&self, _peer: MultiaddrWithPeerId) -> Result<(), String> { unimplemented!(); } - fn announce(&self, _: B::Hash, _: Option>) { + fn remove_reserved_peer(&self, _peer_id: PeerId) { + unimplemented!(); + } + + fn set_reserved_peers( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn add_peers_to_reserved_set( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_peers_from_reserved_set(&self, _protocol: ProtocolName, _peers: Vec) {} + + fn add_to_peers_set( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_from_peers_set(&self, _protocol: ProtocolName, _peers: Vec) { + unimplemented!(); + } + + fn sync_num_connected(&self) -> usize { + unimplemented!(); + } + } + + impl NetworkEventStream for NoOpNetwork { + fn event_stream(&self, _name: &'static str) -> Pin + Send>> { + unimplemented!(); + } + } + + impl NetworkNotification for NoOpNetwork { + fn write_notification(&self, _target: PeerId, _protocol: ProtocolName, _message: Vec) { + unimplemented!(); + } + + fn notification_sender( + &self, + _target: PeerId, + _protocol: ProtocolName, + ) -> Result, NotificationSenderError> { + unimplemented!(); + } + } + + impl NetworkBlock<::Hash, NumberFor> for NoOpNetwork { + fn announce_block(&self, _hash: ::Hash, _data: Option>) { + unimplemented!(); + } + + fn new_best_block_imported( + &self, + _hash: ::Hash, + _number: NumberFor, + ) { unimplemented!(); } } @@ -724,7 +804,7 @@ mod tests { .on_incoming( &mut network, // Unregistered peer. - remote.clone(), + remote, vec![vec![1, 2, 3]], ); diff --git a/client/network-gossip/src/validator.rs b/client/network-gossip/src/validator.rs index 9a2652d03f642..77dcc3bdc8791 100644 --- a/client/network-gossip/src/validator.rs +++ b/client/network-gossip/src/validator.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use sc_network::{ObservedRole, PeerId}; +use libp2p::PeerId; +use sc_network_common::protocol::role::ObservedRole; use sp_runtime::traits::Block as BlockT; /// Validates consensus messages. diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 873c2a847a29a..1959e24bd680f 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -4,8 +4,8 @@ name = "sc-network" version = "0.10.0-dev" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" authors = ["Parity Technologies "] -edition = "2018" -homepage = "https://substrate.dev" +edition = "2021" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network" readme = "README.md" @@ -13,69 +13,60 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] -[build-dependencies] -prost-build = "0.8" - [dependencies] +array-bytes = "4.1" async-trait = "0.1" -async-std = "1.6.5" +asynchronous-codec = "0.6" bitflags = "1.3.2" -cid = "0.6.0" bytes = "1" -codec = { package = "parity-scale-codec", version = "2.0.0", features = [ - "derive", -] } -derive_more = "0.99.2" +cid = "0.8.4" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } either = "1.5.3" fnv = "1.0.6" -fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } -futures = "0.3.9" +futures = "0.3.21" futures-timer = "3.0.2" -asynchronous-codec = "0.5" -hex = "0.4.0" -ip_network = "0.4.0" -linked-hash-map = "0.5.4" +ip_network = "0.4.1" +libp2p = { version = "0.49.0", features = ["dns", "identify", "kad", "mdns", "mplex", "noise", "ping", "tcp", "tokio", "yamux", "websocket"] } linked_hash_set = "0.1.3" -lru = "0.6.6" -log = "0.4.8" -parking_lot = "0.11.1" -pin-project = "1.0.4" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.9.0", path = "../../utils/prometheus" } -prost = "0.8" +linked-hash-map = "0.5.4" +log = "0.4.17" +lru = "0.8.1" +parking_lot = "0.12.1" +pin-project = "1.0.12" +prost = "0.11" rand = "0.7.2" +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.85" +smallvec = "1.8.0" +thiserror = "1.0" +unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] } +zeroize = "1.4.3" +fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } +sc-network-common = { version = "0.10.0-dev", path = "./common" } sc-peerset = { version = "4.0.0-dev", path = "../peerset" } -serde = { version = "1.0.126", features = ["derive"] } -serde_json = "1.0.68" -smallvec = "1.5.0" -sp-arithmetic = { version = "4.0.0-dev", path = "../../primitives/arithmetic" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } +sp-arithmetic = { version = "6.0.0", path = "../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } -thiserror = "1" -unsigned-varint = { version = "0.6.0", features = [ - "futures", - "asynchronous_codec", -] } -void = "1.0.2" -zeroize = "1.4.1" -libp2p = "0.39.1" +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3" -libp2p = { version = "0.39.1", default-features = false } -quickcheck = "1.0.3" rand = "0.7.2" +tempfile = "3.1.0" +tokio = { version = "1.22.0", features = ["macros"] } +tokio-util = { version = "0.7.4", features = ["compat"] } +sc-network-light = { version = "0.10.0-dev", path = "./light" } +sc-network-sync = { version = "0.10.0-dev", path = "./sync" } sp-test-primitives = { version = "2.0.0", path = "../../primitives/test-primitives" } -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -tempfile = "3.1.0" [features] default = [] diff --git a/client/network/bitswap/Cargo.toml b/client/network/bitswap/Cargo.toml new file mode 100644 index 0000000000000..099b5cd5e88b2 --- /dev/null +++ b/client/network/bitswap/Cargo.toml @@ -0,0 +1,39 @@ +[package] +description = "Substrate bitswap protocol" +name = "sc-network-bitswap" +version = "0.10.0-dev" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors = ["Parity Technologies "] +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +documentation = "https://docs.rs/sc-network-bitswap" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[build-dependencies] +prost-build = "0.11" + +[dependencies] +cid = "0.8.6" +futures = "0.3.21" +libp2p = "0.49.0" +log = "0.4.17" +prost = "0.11" +thiserror = "1.0" +unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] } +void = "1.0.2" +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-network-common = { version = "0.10.0-dev", path = "../common" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } + +[dev-dependencies] +tokio = { version = "1.22.0", features = ["full"] } +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/network/bitswap/build.rs b/client/network/bitswap/build.rs new file mode 100644 index 0000000000000..671277230a774 --- /dev/null +++ b/client/network/bitswap/build.rs @@ -0,0 +1,5 @@ +const PROTOS: &[&str] = &["src/schema/bitswap.v1.2.0.proto"]; + +fn main() { + prost_build::compile_protos(PROTOS, &["src/schema"]).unwrap(); +} diff --git a/client/network/bitswap/src/lib.rs b/client/network/bitswap/src/lib.rs new file mode 100644 index 0000000000000..62a18b18c839d --- /dev/null +++ b/client/network/bitswap/src/lib.rs @@ -0,0 +1,524 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Bitswap server for Substrate. +//! +//! Allows querying transactions by hash over standard bitswap protocol +//! Only supports bitswap 1.2.0. +//! CID is expected to reference 256-bit Blake2b transaction hash. + +use cid::{self, Version}; +use futures::{channel::mpsc, StreamExt}; +use libp2p::core::PeerId; +use log::{debug, error, trace}; +use prost::Message; +use sc_client_api::BlockBackend; +use sc_network_common::{ + protocol::ProtocolName, + request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, +}; +use schema::bitswap::{ + message::{wantlist::WantType, Block as MessageBlock, BlockPresence, BlockPresenceType}, + Message as BitswapMessage, +}; +use sp_runtime::traits::Block as BlockT; +use std::{io, sync::Arc, time::Duration}; +use unsigned_varint::encode as varint_encode; + +mod schema; + +const LOG_TARGET: &str = "bitswap"; + +// Undocumented, but according to JS the bitswap messages have a max size of 512*1024 bytes +// https://github.com/ipfs/js-ipfs-bitswap/blob/ +// d8f80408aadab94c962f6b88f343eb9f39fa0fcc/src/decision-engine/index.js#L16 +// We set it to the same value as max substrate protocol message +const MAX_PACKET_SIZE: u64 = 16 * 1024 * 1024; + +/// Max number of queued responses before denying requests. +const MAX_REQUEST_QUEUE: usize = 20; + +/// Max number of blocks per wantlist +const MAX_WANTED_BLOCKS: usize = 16; + +/// Bitswap protocol name +const PROTOCOL_NAME: &'static str = "/ipfs/bitswap/1.2.0"; + +/// Prefix represents all metadata of a CID, without the actual content. +#[derive(PartialEq, Eq, Clone, Debug)] +struct Prefix { + /// The version of CID. + pub version: Version, + /// The codec of CID. + pub codec: u64, + /// The multihash type of CID. + pub mh_type: u64, + /// The multihash length of CID. + pub mh_len: u8, +} + +impl Prefix { + /// Convert the prefix to encoded bytes. + pub fn to_bytes(&self) -> Vec { + let mut res = Vec::with_capacity(4); + let mut buf = varint_encode::u64_buffer(); + let version = varint_encode::u64(self.version.into(), &mut buf); + res.extend_from_slice(version); + let mut buf = varint_encode::u64_buffer(); + let codec = varint_encode::u64(self.codec, &mut buf); + res.extend_from_slice(codec); + let mut buf = varint_encode::u64_buffer(); + let mh_type = varint_encode::u64(self.mh_type, &mut buf); + res.extend_from_slice(mh_type); + let mut buf = varint_encode::u64_buffer(); + let mh_len = varint_encode::u64(self.mh_len as u64, &mut buf); + res.extend_from_slice(mh_len); + res + } +} + +/// Bitswap request handler +pub struct BitswapRequestHandler { + client: Arc + Send + Sync>, + request_receiver: mpsc::Receiver, +} + +impl BitswapRequestHandler { + /// Create a new [`BitswapRequestHandler`]. + pub fn new(client: Arc + Send + Sync>) -> (Self, ProtocolConfig) { + let (tx, request_receiver) = mpsc::channel(MAX_REQUEST_QUEUE); + + let config = ProtocolConfig { + name: ProtocolName::from(PROTOCOL_NAME), + fallback_names: vec![], + max_request_size: MAX_PACKET_SIZE, + max_response_size: MAX_PACKET_SIZE, + request_timeout: Duration::from_secs(15), + inbound_queue: Some(tx), + }; + + (Self { client, request_receiver }, config) + } + + /// Run [`BitswapRequestHandler`]. + pub async fn run(mut self) { + while let Some(request) = self.request_receiver.next().await { + let IncomingRequest { peer, payload, pending_response } = request; + + match self.handle_message(&peer, &payload) { + Ok(response) => { + let response = OutgoingResponse { + result: Ok(response), + reputation_changes: Vec::new(), + sent_feedback: None, + }; + + match pending_response.send(response) { + Ok(()) => + trace!(target: LOG_TARGET, "Handled bitswap request from {peer}.",), + Err(_) => debug!( + target: LOG_TARGET, + "Failed to handle light client request from {peer}: {}", + BitswapError::SendResponse, + ), + } + }, + Err(err) => { + error!(target: LOG_TARGET, "Failed to process request from {peer}: {err}"); + + // TODO: adjust reputation? + + let response = OutgoingResponse { + result: Err(()), + reputation_changes: vec![], + sent_feedback: None, + }; + + if pending_response.send(response).is_err() { + debug!( + target: LOG_TARGET, + "Failed to handle bitswap request from {peer}: {}", + BitswapError::SendResponse, + ); + } + }, + } + } + } + + /// Handle received Bitswap request + fn handle_message( + &mut self, + peer: &PeerId, + payload: &Vec, + ) -> Result, BitswapError> { + let request = schema::bitswap::Message::decode(&payload[..])?; + + trace!(target: LOG_TARGET, "Received request: {:?} from {}", request, peer); + + let mut response = BitswapMessage::default(); + + let wantlist = match request.wantlist { + Some(wantlist) => wantlist, + None => { + debug!(target: LOG_TARGET, "Unexpected bitswap message from {}", peer); + return Err(BitswapError::InvalidWantList) + }, + }; + + if wantlist.entries.len() > MAX_WANTED_BLOCKS { + trace!(target: LOG_TARGET, "Ignored request: too many entries"); + return Err(BitswapError::TooManyEntries) + } + + for entry in wantlist.entries { + let cid = match cid::Cid::read_bytes(entry.block.as_slice()) { + Ok(cid) => cid, + Err(e) => { + trace!(target: LOG_TARGET, "Bad CID {:?}: {:?}", entry.block, e); + continue + }, + }; + + if cid.version() != cid::Version::V1 || + cid.hash().code() != u64::from(cid::multihash::Code::Blake2b256) || + cid.hash().size() != 32 + { + debug!(target: LOG_TARGET, "Ignoring unsupported CID {}: {}", peer, cid); + continue + } + + let mut hash = B::Hash::default(); + hash.as_mut().copy_from_slice(&cid.hash().digest()[0..32]); + let transaction = match self.client.indexed_transaction(hash) { + Ok(ex) => ex, + Err(e) => { + error!(target: LOG_TARGET, "Error retrieving transaction {}: {}", hash, e); + None + }, + }; + + match transaction { + Some(transaction) => { + trace!(target: LOG_TARGET, "Found CID {:?}, hash {:?}", cid, hash); + + if entry.want_type == WantType::Block as i32 { + let prefix = Prefix { + version: cid.version(), + codec: cid.codec(), + mh_type: cid.hash().code(), + mh_len: cid.hash().size(), + }; + response + .payload + .push(MessageBlock { prefix: prefix.to_bytes(), data: transaction }); + } else { + response.block_presences.push(BlockPresence { + r#type: BlockPresenceType::Have as i32, + cid: cid.to_bytes(), + }); + } + }, + None => { + trace!(target: LOG_TARGET, "Missing CID {:?}, hash {:?}", cid, hash); + + if entry.send_dont_have { + response.block_presences.push(BlockPresence { + r#type: BlockPresenceType::DontHave as i32, + cid: cid.to_bytes(), + }); + } + }, + } + } + + Ok(response.encode_to_vec()) + } +} + +/// Bitswap protocol error. +#[derive(Debug, thiserror::Error)] +pub enum BitswapError { + /// Protobuf decoding error. + #[error("Failed to decode request: {0}.")] + DecodeProto(#[from] prost::DecodeError), + + /// Protobuf encoding error. + #[error("Failed to encode response: {0}.")] + EncodeProto(#[from] prost::EncodeError), + + /// Client backend error. + #[error(transparent)] + Client(#[from] sp_blockchain::Error), + + /// Error parsing CID + #[error(transparent)] + BadCid(#[from] cid::Error), + + /// Packet read error. + #[error(transparent)] + Read(#[from] io::Error), + + /// Error sending response. + #[error("Failed to send response.")] + SendResponse, + + /// Message doesn't have a WANT list. + #[error("Invalid WANT list.")] + InvalidWantList, + + /// Too many blocks requested. + #[error("Too many block entries in the request.")] + TooManyEntries, +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::{channel::oneshot, SinkExt}; + use sc_block_builder::BlockBuilderProvider; + use schema::bitswap::{ + message::{wantlist::Entry, Wantlist}, + Message as BitswapMessage, + }; + use sp_consensus::BlockOrigin; + use sp_runtime::codec::Encode; + use substrate_test_runtime::Extrinsic; + use substrate_test_runtime_client::{self, prelude::*, TestClientBuilder}; + + #[tokio::test] + async fn undecodeable_message() { + let client = substrate_test_runtime_client::new(); + let (bitswap, config) = BitswapRequestHandler::new(Arc::new(client)); + + tokio::spawn(async move { bitswap.run().await }); + + let (tx, rx) = oneshot::channel(); + config + .inbound_queue + .unwrap() + .send(IncomingRequest { + peer: PeerId::random(), + payload: vec![0x13, 0x37, 0x13, 0x38], + pending_response: tx, + }) + .await + .unwrap(); + + if let Ok(OutgoingResponse { result, reputation_changes, sent_feedback }) = rx.await { + assert_eq!(result, Err(())); + assert_eq!(reputation_changes, Vec::new()); + assert!(sent_feedback.is_none()); + } else { + panic!("invalid event received"); + } + } + + #[tokio::test] + async fn empty_want_list() { + let client = substrate_test_runtime_client::new(); + let (bitswap, mut config) = BitswapRequestHandler::new(Arc::new(client)); + + tokio::spawn(async move { bitswap.run().await }); + + let (tx, rx) = oneshot::channel(); + config + .inbound_queue + .as_mut() + .unwrap() + .send(IncomingRequest { + peer: PeerId::random(), + payload: BitswapMessage { wantlist: None, ..Default::default() }.encode_to_vec(), + pending_response: tx, + }) + .await + .unwrap(); + + if let Ok(OutgoingResponse { result, reputation_changes, sent_feedback }) = rx.await { + assert_eq!(result, Err(())); + assert_eq!(reputation_changes, Vec::new()); + assert!(sent_feedback.is_none()); + } else { + panic!("invalid event received"); + } + + // Empty WANT list should not cause an error + let (tx, rx) = oneshot::channel(); + config + .inbound_queue + .unwrap() + .send(IncomingRequest { + peer: PeerId::random(), + payload: BitswapMessage { + wantlist: Some(Default::default()), + ..Default::default() + } + .encode_to_vec(), + pending_response: tx, + }) + .await + .unwrap(); + + if let Ok(OutgoingResponse { result, reputation_changes, sent_feedback }) = rx.await { + assert_eq!(result, Ok(BitswapMessage::default().encode_to_vec())); + assert_eq!(reputation_changes, Vec::new()); + assert!(sent_feedback.is_none()); + } else { + panic!("invalid event received"); + } + } + + #[tokio::test] + async fn too_long_want_list() { + let client = substrate_test_runtime_client::new(); + let (bitswap, config) = BitswapRequestHandler::new(Arc::new(client)); + + tokio::spawn(async move { bitswap.run().await }); + + let (tx, rx) = oneshot::channel(); + config + .inbound_queue + .unwrap() + .send(IncomingRequest { + peer: PeerId::random(), + payload: BitswapMessage { + wantlist: Some(Wantlist { + entries: (0..MAX_WANTED_BLOCKS + 1) + .map(|_| Entry::default()) + .collect::>(), + full: false, + }), + ..Default::default() + } + .encode_to_vec(), + pending_response: tx, + }) + .await + .unwrap(); + + if let Ok(OutgoingResponse { result, reputation_changes, sent_feedback }) = rx.await { + assert_eq!(result, Err(())); + assert_eq!(reputation_changes, Vec::new()); + assert!(sent_feedback.is_none()); + } else { + panic!("invalid event received"); + } + } + + #[tokio::test] + async fn transaction_not_found() { + let client = TestClientBuilder::with_tx_storage(u32::MAX).build(); + + let (bitswap, config) = BitswapRequestHandler::new(Arc::new(client)); + tokio::spawn(async move { bitswap.run().await }); + + let (tx, rx) = oneshot::channel(); + config + .inbound_queue + .unwrap() + .send(IncomingRequest { + peer: PeerId::random(), + payload: BitswapMessage { + wantlist: Some(Wantlist { + entries: vec![Entry { + block: cid::Cid::new_v1( + 0x70, + cid::multihash::Multihash::wrap( + u64::from(cid::multihash::Code::Blake2b256), + &[0u8; 32], + ) + .unwrap(), + ) + .to_bytes(), + ..Default::default() + }], + full: false, + }), + ..Default::default() + } + .encode_to_vec(), + pending_response: tx, + }) + .await + .unwrap(); + + if let Ok(OutgoingResponse { result, reputation_changes, sent_feedback }) = rx.await { + assert_eq!(result, Ok(vec![])); + assert_eq!(reputation_changes, Vec::new()); + assert!(sent_feedback.is_none()); + } else { + panic!("invalid event received"); + } + } + + #[tokio::test] + async fn transaction_found() { + let mut client = TestClientBuilder::with_tx_storage(u32::MAX).build(); + let mut block_builder = client.new_block(Default::default()).unwrap(); + + let ext = Extrinsic::Store(vec![0x13, 0x37, 0x13, 0x38]); + + block_builder.push(ext.clone()).unwrap(); + let block = block_builder.build().unwrap().block; + + client.import(BlockOrigin::File, block).await.unwrap(); + + let (bitswap, config) = BitswapRequestHandler::new(Arc::new(client)); + + tokio::spawn(async move { bitswap.run().await }); + + let (tx, rx) = oneshot::channel(); + config + .inbound_queue + .unwrap() + .send(IncomingRequest { + peer: PeerId::random(), + payload: BitswapMessage { + wantlist: Some(Wantlist { + entries: vec![Entry { + block: cid::Cid::new_v1( + 0x70, + cid::multihash::Multihash::wrap( + u64::from(cid::multihash::Code::Blake2b256), + &sp_core::hashing::blake2_256(&ext.encode()[2..]), + ) + .unwrap(), + ) + .to_bytes(), + ..Default::default() + }], + full: false, + }), + ..Default::default() + } + .encode_to_vec(), + pending_response: tx, + }) + .await + .unwrap(); + + if let Ok(OutgoingResponse { result, reputation_changes, sent_feedback }) = rx.await { + assert_eq!(reputation_changes, Vec::new()); + assert!(sent_feedback.is_none()); + + let response = + schema::bitswap::Message::decode(&result.expect("fetch to succeed")[..]).unwrap(); + assert_eq!(response.payload[0].data, vec![0x13, 0x37, 0x13, 0x38]); + } else { + panic!("invalid event received"); + } + } +} diff --git a/client/network/src/schema.rs b/client/network/bitswap/src/schema.rs similarity index 79% rename from client/network/src/schema.rs rename to client/network/bitswap/src/schema.rs index d4572fca7594c..362e59aca68f9 100644 --- a/client/network/src/schema.rs +++ b/client/network/bitswap/src/schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,13 +18,6 @@ //! Include sources generated from protobuf definitions. -pub mod v1 { - include!(concat!(env!("OUT_DIR"), "/api.v1.rs")); - pub mod light { - include!(concat!(env!("OUT_DIR"), "/api.v1.light.rs")); - } -} - -pub mod bitswap { +pub(crate) mod bitswap { include!(concat!(env!("OUT_DIR"), "/bitswap.message.rs")); } diff --git a/client/network/src/schema/bitswap.v1.2.0.proto b/client/network/bitswap/src/schema/bitswap.v1.2.0.proto similarity index 100% rename from client/network/src/schema/bitswap.v1.2.0.proto rename to client/network/bitswap/src/schema/bitswap.v1.2.0.proto diff --git a/client/network/build.rs b/client/network/build.rs deleted file mode 100644 index 6e5b83d4e58ae..0000000000000 --- a/client/network/build.rs +++ /dev/null @@ -1,6 +0,0 @@ -const PROTOS: &[&str] = - &["src/schema/api.v1.proto", "src/schema/light.v1.proto", "src/schema/bitswap.v1.2.0.proto"]; - -fn main() { - prost_build::compile_protos(PROTOS, &["src/schema"]).unwrap(); -} diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml new file mode 100644 index 0000000000000..545ae8a8af514 --- /dev/null +++ b/client/network/common/Cargo.toml @@ -0,0 +1,38 @@ +[package] +description = "Substrate network common" +name = "sc-network-common" +version = "0.10.0-dev" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors = ["Parity Technologies "] +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +documentation = "https://docs.rs/sc-network-sync" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[build-dependencies] +prost-build = "0.11" + +[dependencies] +async-trait = "0.1.57" +bitflags = "1.3.2" +bytes = "1" +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", +] } +futures = "0.3.21" +futures-timer = "3.0.2" +libp2p = { version = "0.49.0", features = [ "request-response", "kad" ] } +linked_hash_set = "0.1.3" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +smallvec = "1.8.0" +sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } +sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } +serde = { version = "1.0.136", features = ["derive"] } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +thiserror = "1.0" diff --git a/client/network/common/src/config.rs b/client/network/common/src/config.rs new file mode 100644 index 0000000000000..96c7c11ec2696 --- /dev/null +++ b/client/network/common/src/config.rs @@ -0,0 +1,333 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Configuration of the networking layer. + +use crate::protocol; + +use codec::Encode; +use libp2p::{multiaddr, Multiaddr, PeerId}; +use std::{fmt, str, str::FromStr}; + +/// Protocol name prefix, transmitted on the wire for legacy protocol names. +/// I.e., `dot` in `/dot/sync/2`. Should be unique for each chain. Always UTF-8. +/// Deprecated in favour of genesis hash & fork ID based protocol names. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct ProtocolId(smallvec::SmallVec<[u8; 6]>); + +impl<'a> From<&'a str> for ProtocolId { + fn from(bytes: &'a str) -> ProtocolId { + Self(bytes.as_bytes().into()) + } +} + +impl AsRef for ProtocolId { + fn as_ref(&self) -> &str { + str::from_utf8(&self.0[..]) + .expect("the only way to build a ProtocolId is through a UTF-8 String; qed") + } +} + +impl fmt::Debug for ProtocolId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self.as_ref(), f) + } +} + +/// Parses a string address and splits it into Multiaddress and PeerId, if +/// valid. +/// +/// # Example +/// +/// ``` +/// # use libp2p::{Multiaddr, PeerId}; +/// # use sc_network_common::config::parse_str_addr; +/// let (peer_id, addr) = parse_str_addr( +/// "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV" +/// ).unwrap(); +/// assert_eq!(peer_id, "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".parse::().unwrap()); +/// assert_eq!(addr, "/ip4/198.51.100.19/tcp/30333".parse::().unwrap()); +/// ``` +pub fn parse_str_addr(addr_str: &str) -> Result<(PeerId, Multiaddr), ParseErr> { + let addr: Multiaddr = addr_str.parse()?; + parse_addr(addr) +} + +/// Splits a Multiaddress into a Multiaddress and PeerId. +pub fn parse_addr(mut addr: Multiaddr) -> Result<(PeerId, Multiaddr), ParseErr> { + let who = match addr.pop() { + Some(multiaddr::Protocol::P2p(key)) => + PeerId::from_multihash(key).map_err(|_| ParseErr::InvalidPeerId)?, + _ => return Err(ParseErr::PeerIdMissing), + }; + + Ok((who, addr)) +} + +/// Address of a node, including its identity. +/// +/// This struct represents a decoded version of a multiaddress that ends with `/p2p/`. +/// +/// # Example +/// +/// ``` +/// # use libp2p::{Multiaddr, PeerId}; +/// # use sc_network_common::config::MultiaddrWithPeerId; +/// let addr: MultiaddrWithPeerId = +/// "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".parse().unwrap(); +/// assert_eq!(addr.peer_id.to_base58(), "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"); +/// assert_eq!(addr.multiaddr.to_string(), "/ip4/198.51.100.19/tcp/30333"); +/// ``` +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] +#[serde(try_from = "String", into = "String")] +pub struct MultiaddrWithPeerId { + /// Address of the node. + pub multiaddr: Multiaddr, + /// Its identity. + pub peer_id: PeerId, +} + +impl MultiaddrWithPeerId { + /// Concatenates the multiaddress and peer ID into one multiaddress containing both. + pub fn concat(&self) -> Multiaddr { + let proto = multiaddr::Protocol::P2p(From::from(self.peer_id)); + self.multiaddr.clone().with(proto) + } +} + +impl fmt::Display for MultiaddrWithPeerId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.concat(), f) + } +} + +impl FromStr for MultiaddrWithPeerId { + type Err = ParseErr; + + fn from_str(s: &str) -> Result { + let (peer_id, multiaddr) = parse_str_addr(s)?; + Ok(Self { peer_id, multiaddr }) + } +} + +impl From for String { + fn from(ma: MultiaddrWithPeerId) -> String { + format!("{}", ma) + } +} + +impl TryFrom for MultiaddrWithPeerId { + type Error = ParseErr; + fn try_from(string: String) -> Result { + string.parse() + } +} + +/// Error that can be generated by `parse_str_addr`. +#[derive(Debug)] +pub enum ParseErr { + /// Error while parsing the multiaddress. + MultiaddrParse(multiaddr::Error), + /// Multihash of the peer ID is invalid. + InvalidPeerId, + /// The peer ID is missing from the address. + PeerIdMissing, +} + +impl fmt::Display for ParseErr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MultiaddrParse(err) => write!(f, "{}", err), + Self::InvalidPeerId => write!(f, "Peer id at the end of the address is invalid"), + Self::PeerIdMissing => write!(f, "Peer id is missing from the address"), + } + } +} + +impl std::error::Error for ParseErr { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::MultiaddrParse(err) => Some(err), + Self::InvalidPeerId => None, + Self::PeerIdMissing => None, + } + } +} + +impl From for ParseErr { + fn from(err: multiaddr::Error) -> ParseErr { + Self::MultiaddrParse(err) + } +} + +/// Configuration for a set of nodes. +#[derive(Clone, Debug)] +pub struct SetConfig { + /// Maximum allowed number of incoming substreams related to this set. + pub in_peers: u32, + /// Number of outgoing substreams related to this set that we're trying to maintain. + pub out_peers: u32, + /// List of reserved node addresses. + pub reserved_nodes: Vec, + /// Whether nodes that aren't in [`SetConfig::reserved_nodes`] are accepted or automatically + /// refused. + pub non_reserved_mode: NonReservedPeerMode, +} + +impl Default for SetConfig { + fn default() -> Self { + Self { + in_peers: 25, + out_peers: 75, + reserved_nodes: Vec::new(), + non_reserved_mode: NonReservedPeerMode::Accept, + } + } +} + +/// Custom handshake for the notification protocol +#[derive(Debug, Clone)] +pub struct NotificationHandshake(Vec); + +impl NotificationHandshake { + /// Create new `NotificationHandshake` from an object that implements `Encode` + pub fn new(handshake: H) -> Self { + Self(handshake.encode()) + } + + /// Create new `NotificationHandshake` from raw bytes + pub fn from_bytes(bytes: Vec) -> Self { + Self(bytes) + } +} + +impl std::ops::Deref for NotificationHandshake { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Extension to [`SetConfig`] for sets that aren't the default set. +/// +/// > **Note**: As new fields might be added in the future, please consider using the `new` method +/// > and modifiers instead of creating this struct manually. +#[derive(Clone, Debug)] +pub struct NonDefaultSetConfig { + /// Name of the notifications protocols of this set. A substream on this set will be + /// considered established once this protocol is open. + /// + /// > **Note**: This field isn't present for the default set, as this is handled internally + /// > by the networking code. + pub notifications_protocol: protocol::ProtocolName, + /// If the remote reports that it doesn't support the protocol indicated in the + /// `notifications_protocol` field, then each of these fallback names will be tried one by + /// one. + /// + /// If a fallback is used, it will be reported in + /// `sc_network::protocol::event::Event::NotificationStreamOpened::negotiated_fallback` + pub fallback_names: Vec, + /// Handshake of the protocol + /// + /// NOTE: Currently custom handshakes are not fully supported. See issue #5685 for more + /// details. This field is temporarily used to allow moving the hardcoded block announcement + /// protocol out of `protocol.rs`. + pub handshake: Option, + /// Maximum allowed size of single notifications. + pub max_notification_size: u64, + /// Base configuration. + pub set_config: SetConfig, +} + +impl NonDefaultSetConfig { + /// Creates a new [`NonDefaultSetConfig`]. Zero slots and accepts only reserved nodes. + pub fn new(notifications_protocol: protocol::ProtocolName, max_notification_size: u64) -> Self { + Self { + notifications_protocol, + max_notification_size, + fallback_names: Vec::new(), + handshake: None, + set_config: SetConfig { + in_peers: 0, + out_peers: 0, + reserved_nodes: Vec::new(), + non_reserved_mode: NonReservedPeerMode::Deny, + }, + } + } + + /// Modifies the configuration to allow non-reserved nodes. + pub fn allow_non_reserved(&mut self, in_peers: u32, out_peers: u32) { + self.set_config.in_peers = in_peers; + self.set_config.out_peers = out_peers; + self.set_config.non_reserved_mode = NonReservedPeerMode::Accept; + } + + /// Add a node to the list of reserved nodes. + pub fn add_reserved(&mut self, peer: MultiaddrWithPeerId) { + self.set_config.reserved_nodes.push(peer); + } + + /// Add a list of protocol names used for backward compatibility. + /// + /// See the explanations in [`NonDefaultSetConfig::fallback_names`]. + pub fn add_fallback_names(&mut self, fallback_names: Vec) { + self.fallback_names.extend(fallback_names); + } +} + +/// Configuration for the transport layer. +#[derive(Clone, Debug)] +pub enum TransportConfig { + /// Normal transport mode. + Normal { + /// If true, the network will use mDNS to discover other libp2p nodes on the local network + /// and connect to them if they support the same chain. + enable_mdns: bool, + + /// If true, allow connecting to private IPv4 addresses (as defined in + /// [RFC1918](https://tools.ietf.org/html/rfc1918)). Irrelevant for addresses that have + /// been passed in `::sc_network::config::NetworkConfiguration::boot_nodes`. + allow_private_ipv4: bool, + }, + + /// Only allow connections within the same process. + /// Only addresses of the form `/memory/...` will be supported. + MemoryOnly, +} + +/// The policy for connections to non-reserved peers. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NonReservedPeerMode { + /// Accept them. This is the default. + Accept, + /// Deny them. + Deny, +} + +impl NonReservedPeerMode { + /// Attempt to parse the peer mode from a string. + pub fn parse(s: &str) -> Option { + match s { + "accept" => Some(Self::Accept), + "deny" => Some(Self::Deny), + _ => None, + } + } +} diff --git a/client/network/src/error.rs b/client/network/common/src/error.rs similarity index 65% rename from client/network/src/error.rs rename to client/network/common/src/error.rs index b8a31def7dc61..4326b1af52836 100644 --- a/client/network/src/error.rs +++ b/client/network/common/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,27 +18,27 @@ //! Substrate network possible errors. -use crate::config::TransportConfig; +use crate::{config::TransportConfig, protocol::ProtocolName}; use libp2p::{Multiaddr, PeerId}; -use std::{borrow::Cow, fmt}; +use std::fmt; /// Result type alias for the network. pub type Result = std::result::Result; /// Error type for the network. -#[derive(derive_more::Display, derive_more::From)] +#[derive(thiserror::Error)] pub enum Error { /// Io error - Io(std::io::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + /// Client error - Client(Box), + #[error(transparent)] + Client(#[from] Box), /// The same bootnode (based on address) is registered with two different peer ids. - #[display( - fmt = "The same bootnode (`{}`) is registered with two different peer ids: `{}` and `{}`", - address, - first_id, - second_id + #[error( + "The same bootnode (`{address}`) is registered with two different peer ids: `{first_id}` and `{second_id}`" )] DuplicateBootnode { /// The address of the bootnode. @@ -49,11 +49,11 @@ pub enum Error { second_id: PeerId, }, /// Prometheus metrics error. - Prometheus(prometheus_endpoint::PrometheusError), + #[error(transparent)] + Prometheus(#[from] prometheus_endpoint::PrometheusError), /// The network addresses are invalid because they don't match the transport. - #[display( - fmt = "The following addresses are invalid because they don't match the transport: {:?}", - addresses + #[error( + "The following addresses are invalid because they don't match the transport: {addresses:?}" )] AddressesForAnotherTransport { /// Transport used. @@ -62,10 +62,10 @@ pub enum Error { addresses: Vec, }, /// The same request-response protocol has been registered multiple times. - #[display(fmt = "Request-response protocol registered multiple times: {}", protocol)] + #[error("Request-response protocol registered multiple times: {protocol}")] DuplicateRequestResponseProtocol { /// Name of the protocol registered multiple times. - protocol: Cow<'static, str>, + protocol: ProtocolName, }, } @@ -75,16 +75,3 @@ impl fmt::Debug for Error { fmt::Display::fmt(self, f) } } - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Io(ref err) => Some(err), - Self::Client(ref err) => Some(err), - Self::Prometheus(ref err) => Some(err), - Self::DuplicateBootnode { .. } | - Self::AddressesForAnotherTransport { .. } | - Self::DuplicateRequestResponseProtocol { .. } => None, - } - } -} diff --git a/client/network/common/src/lib.rs b/client/network/common/src/lib.rs new file mode 100644 index 0000000000000..36e67f11e5cff --- /dev/null +++ b/client/network/common/src/lib.rs @@ -0,0 +1,34 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Common data structures of the networking layer. + +pub mod config; +pub mod error; +pub mod message; +pub mod protocol; +pub mod request_responses; +pub mod service; +pub mod sync; +pub mod utils; + +/// Minimum Requirements for a Hash within Networking +pub trait ExHashT: std::hash::Hash + Eq + std::fmt::Debug + Clone + Send + Sync + 'static {} + +impl ExHashT for T where T: std::hash::Hash + Eq + std::fmt::Debug + Clone + Send + Sync + 'static +{} diff --git a/client/network/common/src/message.rs b/client/network/common/src/message.rs new file mode 100644 index 0000000000000..930fe5ca52847 --- /dev/null +++ b/client/network/common/src/message.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Network packet message types. These get serialized and put into the lower level protocol +//! payload. + +/// A unique ID of a request. +pub type RequestId = u64; diff --git a/client/network/common/src/protocol.rs b/client/network/common/src/protocol.rs new file mode 100644 index 0000000000000..04bfaedbcac71 --- /dev/null +++ b/client/network/common/src/protocol.rs @@ -0,0 +1,147 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use std::{ + borrow::Borrow, + fmt, + hash::{Hash, Hasher}, + ops::Deref, + sync::Arc, +}; + +use libp2p::core::upgrade; + +pub mod event; +pub mod role; + +/// The protocol name transmitted on the wire. +#[derive(Debug, Clone)] +pub enum ProtocolName { + /// The protocol name as a static string. + Static(&'static str), + /// The protocol name as a dynamically allocated string. + OnHeap(Arc), +} + +impl From<&'static str> for ProtocolName { + fn from(name: &'static str) -> Self { + Self::Static(name) + } +} + +impl From> for ProtocolName { + fn from(name: Arc) -> Self { + Self::OnHeap(name) + } +} + +impl From for ProtocolName { + fn from(name: String) -> Self { + Self::OnHeap(Arc::from(name)) + } +} + +impl Deref for ProtocolName { + type Target = str; + + fn deref(&self) -> &str { + match self { + Self::Static(name) => name, + Self::OnHeap(name) => &name, + } + } +} + +impl Borrow for ProtocolName { + fn borrow(&self) -> &str { + self + } +} + +impl PartialEq for ProtocolName { + fn eq(&self, other: &Self) -> bool { + (self as &str) == (other as &str) + } +} + +impl Eq for ProtocolName {} + +impl Hash for ProtocolName { + fn hash(&self, state: &mut H) { + (self as &str).hash(state) + } +} + +impl fmt::Display for ProtocolName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self) + } +} + +impl upgrade::ProtocolName for ProtocolName { + fn protocol_name(&self) -> &[u8] { + (self as &str).as_bytes() + } +} + +#[cfg(test)] +mod tests { + use super::ProtocolName; + use std::{ + borrow::Borrow, + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + }; + + #[test] + fn protocol_name_keys_are_equivalent_to_str_keys() { + const PROTOCOL: &'static str = "/some/protocol/1"; + let static_protocol_name = ProtocolName::from(PROTOCOL); + let on_heap_protocol_name = ProtocolName::from(String::from(PROTOCOL)); + + assert_eq!(>::borrow(&static_protocol_name), PROTOCOL); + assert_eq!(>::borrow(&on_heap_protocol_name), PROTOCOL); + assert_eq!(static_protocol_name, on_heap_protocol_name); + + assert_eq!(hash(static_protocol_name), hash(PROTOCOL)); + assert_eq!(hash(on_heap_protocol_name), hash(PROTOCOL)); + } + + #[test] + fn different_protocol_names_do_not_compare_equal() { + const PROTOCOL1: &'static str = "/some/protocol/1"; + let static_protocol_name1 = ProtocolName::from(PROTOCOL1); + let on_heap_protocol_name1 = ProtocolName::from(String::from(PROTOCOL1)); + + const PROTOCOL2: &'static str = "/some/protocol/2"; + let static_protocol_name2 = ProtocolName::from(PROTOCOL2); + let on_heap_protocol_name2 = ProtocolName::from(String::from(PROTOCOL2)); + + assert_ne!(>::borrow(&static_protocol_name1), PROTOCOL2); + assert_ne!(>::borrow(&on_heap_protocol_name1), PROTOCOL2); + assert_ne!(static_protocol_name1, static_protocol_name2); + assert_ne!(static_protocol_name1, on_heap_protocol_name2); + assert_ne!(on_heap_protocol_name1, on_heap_protocol_name2); + } + + fn hash(x: T) -> u64 { + let mut hasher = DefaultHasher::new(); + x.hash(&mut hasher); + hasher.finish() + } +} diff --git a/client/network/src/protocol/event.rs b/client/network/common/src/protocol/event.rs similarity index 75% rename from client/network/src/protocol/event.rs rename to client/network/common/src/protocol/event.rs index e0b35647c7531..236913df1b120 100644 --- a/client/network/src/protocol/event.rs +++ b/client/network/common/src/protocol/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,9 +19,10 @@ //! Network event types. These are are not the part of the protocol, but rather //! events that happen on the network like DHT get/put results received. +use super::ProtocolName; +use crate::protocol::role::ObservedRole; use bytes::Bytes; use libp2p::{core::PeerId, kad::record::Key}; -use std::borrow::Cow; /// Events generated by DHT as a response to get_value and put_value requests. #[derive(Debug, Clone)] @@ -67,15 +68,15 @@ pub enum Event { remote: PeerId, /// The concerned protocol. Each protocol uses a different substream. /// This is always equal to the value of - /// [`crate::config::NonDefaultSetConfig::notifications_protocol`] of one of the + /// `sc_network::config::NonDefaultSetConfig::notifications_protocol` of one of the /// configured sets. - protocol: Cow<'static, str>, + protocol: ProtocolName, /// If the negotiation didn't use the main name of the protocol (the one in /// `notifications_protocol`), then this field contains which name has actually been /// used. /// Always contains a value equal to the value in - /// [`crate::config::NonDefaultSetConfig::fallback_names`]. - negotiated_fallback: Option>, + /// `sc_network::config::NonDefaultSetConfig::fallback_names`. + negotiated_fallback: Option, /// Role of the remote. role: ObservedRole, }, @@ -86,7 +87,7 @@ pub enum Event { /// Node we closed the substream with. remote: PeerId, /// The concerned protocol. Each protocol uses a different substream. - protocol: Cow<'static, str>, + protocol: ProtocolName, }, /// Received one or more messages from the given node using the given protocol. @@ -94,29 +95,6 @@ pub enum Event { /// Node we received the message from. remote: PeerId, /// Concerned protocol and associated message. - messages: Vec<(Cow<'static, str>, Bytes)>, + messages: Vec<(ProtocolName, Bytes)>, }, } - -/// Role that the peer sent to us during the handshake, with the addition of what our local node -/// knows about that peer. -/// -/// > **Note**: This enum is different from the `Role` enum. The `Role` enum indicates what a -/// > node says about itself, while `ObservedRole` is a `Role` merged with the -/// > information known locally about that node. -#[derive(Debug, Clone)] -pub enum ObservedRole { - /// Full node. - Full, - /// Light node. - Light, - /// Third-party authority. - Authority, -} - -impl ObservedRole { - /// Returns `true` for `ObservedRole::Light`. - pub fn is_light(&self) -> bool { - matches!(self, Self::Light) - } -} diff --git a/client/network/common/src/protocol/role.rs b/client/network/common/src/protocol/role.rs new file mode 100644 index 0000000000000..ed22830fd7170 --- /dev/null +++ b/client/network/common/src/protocol/role.rs @@ -0,0 +1,121 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use codec::{self, Encode, EncodeLike, Input, Output}; + +/// Role that the peer sent to us during the handshake, with the addition of what our local node +/// knows about that peer. +/// +/// > **Note**: This enum is different from the `Role` enum. The `Role` enum indicates what a +/// > node says about itself, while `ObservedRole` is a `Role` merged with the +/// > information known locally about that node. +#[derive(Debug, Clone)] +pub enum ObservedRole { + /// Full node. + Full, + /// Light node. + Light, + /// Third-party authority. + Authority, +} + +impl ObservedRole { + /// Returns `true` for `ObservedRole::Light`. + pub fn is_light(&self) -> bool { + matches!(self, Self::Light) + } +} + +/// Role of the local node. +#[derive(Debug, Clone)] +pub enum Role { + /// Regular full node. + Full, + /// Actual authority. + Authority, +} + +impl Role { + /// True for [`Role::Authority`]. + pub fn is_authority(&self) -> bool { + matches!(self, Self::Authority) + } +} + +impl std::fmt::Display for Role { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Full => write!(f, "FULL"), + Self::Authority => write!(f, "AUTHORITY"), + } + } +} + +bitflags::bitflags! { + /// Bitmask of the roles that a node fulfills. + pub struct Roles: u8 { + /// No network. + const NONE = 0b00000000; + /// Full node, does not participate in consensus. + const FULL = 0b00000001; + /// Light client node. + const LIGHT = 0b00000010; + /// Act as an authority + const AUTHORITY = 0b00000100; + } +} + +impl Roles { + /// Does this role represents a client that holds full chain data locally? + pub fn is_full(&self) -> bool { + self.intersects(Self::FULL | Self::AUTHORITY) + } + + /// Does this role represents a client that does not participates in the consensus? + pub fn is_authority(&self) -> bool { + *self == Self::AUTHORITY + } + + /// Does this role represents a client that does not hold full chain data locally? + pub fn is_light(&self) -> bool { + !self.is_full() + } +} + +impl<'a> From<&'a Role> for Roles { + fn from(roles: &'a Role) -> Self { + match roles { + Role::Full => Self::FULL, + Role::Authority => Self::AUTHORITY, + } + } +} + +impl Encode for Roles { + fn encode_to(&self, dest: &mut T) { + dest.push_byte(self.bits()) + } +} + +impl EncodeLike for Roles {} + +impl codec::Decode for Roles { + fn decode(input: &mut I) -> Result { + Self::from_bits(input.read_byte()?).ok_or_else(|| codec::Error::from("Invalid bytes")) + } +} diff --git a/client/network/common/src/request_responses.rs b/client/network/common/src/request_responses.rs new file mode 100644 index 0000000000000..1a8d48e11be53 --- /dev/null +++ b/client/network/common/src/request_responses.rs @@ -0,0 +1,155 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Collection of generic data structures for request-response protocols. + +use crate::protocol::ProtocolName; +use futures::channel::{mpsc, oneshot}; +use libp2p::{request_response::OutboundFailure, PeerId}; +use sc_peerset::ReputationChange; +use std::time::Duration; + +/// Configuration for a single request-response protocol. +#[derive(Debug, Clone)] +pub struct ProtocolConfig { + /// Name of the protocol on the wire. Should be something like `/foo/bar`. + pub name: ProtocolName, + + /// Fallback on the wire protocol names to support. + pub fallback_names: Vec, + + /// Maximum allowed size, in bytes, of a request. + /// + /// Any request larger than this value will be declined as a way to avoid allocating too + /// much memory for it. + pub max_request_size: u64, + + /// Maximum allowed size, in bytes, of a response. + /// + /// Any response larger than this value will be declined as a way to avoid allocating too + /// much memory for it. + pub max_response_size: u64, + + /// Duration after which emitted requests are considered timed out. + /// + /// If you expect the response to come back quickly, you should set this to a smaller duration. + pub request_timeout: Duration, + + /// Channel on which the networking service will send incoming requests. + /// + /// Every time a peer sends a request to the local node using this protocol, the networking + /// service will push an element on this channel. The receiving side of this channel then has + /// to pull this element, process the request, and send back the response to send back to the + /// peer. + /// + /// The size of the channel has to be carefully chosen. If the channel is full, the networking + /// service will discard the incoming request send back an error to the peer. Consequently, + /// the channel being full is an indicator that the node is overloaded. + /// + /// You can typically set the size of the channel to `T / d`, where `T` is the + /// `request_timeout` and `d` is the expected average duration of CPU and I/O it takes to + /// build a response. + /// + /// Can be `None` if the local node does not support answering incoming requests. + /// If this is `None`, then the local node will not advertise support for this protocol towards + /// other peers. If this is `Some` but the channel is closed, then the local node will + /// advertise support for this protocol, but any incoming request will lead to an error being + /// sent back. + pub inbound_queue: Option>, +} + +/// A single request received by a peer on a request-response protocol. +#[derive(Debug)] +pub struct IncomingRequest { + /// Who sent the request. + pub peer: PeerId, + + /// Request sent by the remote. Will always be smaller than + /// [`ProtocolConfig::max_request_size`]. + pub payload: Vec, + + /// Channel to send back the response. + /// + /// There are two ways to indicate that handling the request failed: + /// + /// 1. Drop `pending_response` and thus not changing the reputation of the peer. + /// + /// 2. Sending an `Err(())` via `pending_response`, optionally including reputation changes for + /// the given peer. + pub pending_response: oneshot::Sender, +} + +/// Response for an incoming request to be send by a request protocol handler. +#[derive(Debug)] +pub struct OutgoingResponse { + /// The payload of the response. + /// + /// `Err(())` if none is available e.g. due an error while handling the request. + pub result: Result, ()>, + + /// Reputation changes accrued while handling the request. To be applied to the reputation of + /// the peer sending the request. + pub reputation_changes: Vec, + + /// If provided, the `oneshot::Sender` will be notified when the request has been sent to the + /// peer. + /// + /// > **Note**: Operating systems typically maintain a buffer of a few dozen kilobytes of + /// > outgoing data for each TCP socket, and it is not possible for a user + /// > application to inspect this buffer. This channel here is not actually notified + /// > when the response has been fully sent out, but rather when it has fully been + /// > written to the buffer managed by the operating system. + pub sent_feedback: Option>, +} + +/// When sending a request, what to do on a disconnected recipient. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum IfDisconnected { + /// Try to connect to the peer. + TryConnect, + /// Just fail if the destination is not yet connected. + ImmediateError, +} + +/// Convenience functions for `IfDisconnected`. +impl IfDisconnected { + /// Shall we connect to a disconnected peer? + pub fn should_connect(self) -> bool { + match self { + Self::TryConnect => true, + Self::ImmediateError => false, + } + } +} + +/// Error in a request. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum RequestFailure { + #[error("We are not currently connected to the requested peer.")] + NotConnected, + #[error("Given protocol hasn't been registered.")] + UnknownProtocol, + #[error("Remote has closed the substream before answering, thereby signaling that it considers the request as valid, but refused to answer it.")] + Refused, + #[error("The remote replied, but the local node is no longer interested in the response.")] + Obsolete, + /// Problem on the network. + #[error("Problem on the network: {0}")] + Network(OutboundFailure), +} diff --git a/client/network/common/src/service.rs b/client/network/common/src/service.rs new file mode 100644 index 0000000000000..54d254eac384f --- /dev/null +++ b/client/network/common/src/service.rs @@ -0,0 +1,631 @@ +// This file is part of Substrate. +// +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program 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. +// +// This program 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 this program. If not, see . +// +// If you read this, you are very thorough, congratulations. + +use crate::{ + config::MultiaddrWithPeerId, + protocol::{event::Event, ProtocolName}, + request_responses::{IfDisconnected, RequestFailure}, + sync::{warp::WarpSyncProgress, StateDownloadProgress, SyncState}, +}; +use futures::{channel::oneshot, Stream}; +pub use libp2p::{identity::error::SigningError, kad::record::Key as KademliaKey}; +use libp2p::{Multiaddr, PeerId}; +use sc_peerset::ReputationChange; +pub use signature::Signature; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::{collections::HashSet, future::Future, pin::Pin, sync::Arc}; + +mod signature; + +/// Signer with network identity +pub trait NetworkSigner { + /// Signs the message with the `KeyPair` that defines the local [`PeerId`]. + fn sign_with_local_identity(&self, msg: impl AsRef<[u8]>) -> Result; +} + +impl NetworkSigner for Arc +where + T: ?Sized, + T: NetworkSigner, +{ + fn sign_with_local_identity(&self, msg: impl AsRef<[u8]>) -> Result { + T::sign_with_local_identity(self, msg) + } +} + +/// Provides access to the networking DHT. +pub trait NetworkDHTProvider { + /// Start getting a value from the DHT. + fn get_value(&self, key: &KademliaKey); + + /// Start putting a value in the DHT. + fn put_value(&self, key: KademliaKey, value: Vec); +} + +impl NetworkDHTProvider for Arc +where + T: ?Sized, + T: NetworkDHTProvider, +{ + fn get_value(&self, key: &KademliaKey) { + T::get_value(self, key) + } + + fn put_value(&self, key: KademliaKey, value: Vec) { + T::put_value(self, key, value) + } +} + +/// Provides an ability to set a fork sync request for a particular block. +pub trait NetworkSyncForkRequest { + /// Notifies the sync service to try and sync the given block from the given + /// peers. + /// + /// If the given vector of peers is empty then the underlying implementation + /// should make a best effort to fetch the block from any peers it is + /// connected to (NOTE: this assumption will change in the future #3629). + fn set_sync_fork_request(&self, peers: Vec, hash: BlockHash, number: BlockNumber); +} + +impl NetworkSyncForkRequest for Arc +where + T: ?Sized, + T: NetworkSyncForkRequest, +{ + fn set_sync_fork_request(&self, peers: Vec, hash: BlockHash, number: BlockNumber) { + T::set_sync_fork_request(self, peers, hash, number) + } +} + +/// Overview status of the network. +#[derive(Clone)] +pub struct NetworkStatus { + /// Current global sync state. + pub sync_state: SyncState>, + /// Target sync block number. + pub best_seen_block: Option>, + /// Number of peers participating in syncing. + pub num_sync_peers: u32, + /// Total number of connected peers + pub num_connected_peers: usize, + /// Total number of active peers. + pub num_active_peers: usize, + /// The total number of bytes received. + pub total_bytes_inbound: u64, + /// The total number of bytes sent. + pub total_bytes_outbound: u64, + /// State sync in progress. + pub state_sync: Option, + /// Warp sync in progress. + pub warp_sync: Option>, +} + +/// Provides high-level status information about network. +#[async_trait::async_trait] +pub trait NetworkStatusProvider { + /// High-level network status information. + /// + /// Returns an error if the `NetworkWorker` is no longer running. + async fn status(&self) -> Result, ()>; +} + +// Manual implementation to avoid extra boxing here +impl NetworkStatusProvider for Arc +where + T: ?Sized, + T: NetworkStatusProvider, +{ + fn status<'life0, 'async_trait>( + &'life0 self, + ) -> Pin, ()>> + Send + 'async_trait>> + where + 'life0: 'async_trait, + Self: 'async_trait, + { + T::status(self) + } +} + +/// Provides low-level API for manipulating network peers. +pub trait NetworkPeers { + /// Set authorized peers. + /// + /// Need a better solution to manage authorized peers, but now just use reserved peers for + /// prototyping. + fn set_authorized_peers(&self, peers: HashSet); + + /// Set authorized_only flag. + /// + /// Need a better solution to decide authorized_only, but now just use reserved_only flag for + /// prototyping. + fn set_authorized_only(&self, reserved_only: bool); + + /// Adds an address known to a node. + fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr); + + /// Report a given peer as either beneficial (+) or costly (-) according to the + /// given scalar. + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange); + + /// Disconnect from a node as soon as possible. + /// + /// This triggers the same effects as if the connection had closed itself spontaneously. + /// + /// See also [`NetworkPeers::remove_from_peers_set`], which has the same effect but also + /// prevents the local node from re-establishing an outgoing substream to this peer until it + /// is added again. + fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName); + + /// Connect to unreserved peers and allow unreserved peers to connect for syncing purposes. + fn accept_unreserved_peers(&self); + + /// Disconnect from unreserved peers and deny new unreserved peers to connect for syncing + /// purposes. + fn deny_unreserved_peers(&self); + + /// Adds a `PeerId` and its `Multiaddr` as reserved. + /// + /// Returns an `Err` if the given string is not a valid multiaddress + /// or contains an invalid peer ID (which includes the local peer ID). + fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String>; + + /// Removes a `PeerId` from the list of reserved peers. + fn remove_reserved_peer(&self, peer_id: PeerId); + + /// Sets the reserved set of a protocol to the given set of peers. + /// + /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also + /// consist of only `/p2p/`. + /// + /// The node will start establishing/accepting connections and substreams to/from peers in this + /// set, if it doesn't have any substream open with them yet. + /// + /// Note however, if a call to this function results in less peers on the reserved set, they + /// will not necessarily get disconnected (depending on available free slots in the peer set). + /// If you want to also disconnect those removed peers, you will have to call + /// `remove_from_peers_set` on those in addition to updating the reserved set. You can omit + /// this step if the peer set is in reserved only mode. + /// + /// Returns an `Err` if one of the given addresses is invalid or contains an + /// invalid peer ID (which includes the local peer ID). + fn set_reserved_peers( + &self, + protocol: ProtocolName, + peers: HashSet, + ) -> Result<(), String>; + + /// Add peers to a peer set. + /// + /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also + /// consist of only `/p2p/`. + /// + /// Returns an `Err` if one of the given addresses is invalid or contains an + /// invalid peer ID (which includes the local peer ID). + fn add_peers_to_reserved_set( + &self, + protocol: ProtocolName, + peers: HashSet, + ) -> Result<(), String>; + + /// Remove peers from a peer set. + fn remove_peers_from_reserved_set(&self, protocol: ProtocolName, peers: Vec); + + /// Add a peer to a set of peers. + /// + /// If the set has slots available, it will try to open a substream with this peer. + /// + /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also + /// consist of only `/p2p/`. + /// + /// Returns an `Err` if one of the given addresses is invalid or contains an + /// invalid peer ID (which includes the local peer ID). + fn add_to_peers_set( + &self, + protocol: ProtocolName, + peers: HashSet, + ) -> Result<(), String>; + + /// Remove peers from a peer set. + /// + /// If we currently have an open substream with this peer, it will soon be closed. + fn remove_from_peers_set(&self, protocol: ProtocolName, peers: Vec); + + /// Returns the number of peers in the sync peer set we're connected to. + fn sync_num_connected(&self) -> usize; +} + +// Manual implementation to avoid extra boxing here +impl NetworkPeers for Arc +where + T: ?Sized, + T: NetworkPeers, +{ + fn set_authorized_peers(&self, peers: HashSet) { + T::set_authorized_peers(self, peers) + } + + fn set_authorized_only(&self, reserved_only: bool) { + T::set_authorized_only(self, reserved_only) + } + + fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr) { + T::add_known_address(self, peer_id, addr) + } + + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { + T::report_peer(self, who, cost_benefit) + } + + fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) { + T::disconnect_peer(self, who, protocol) + } + + fn accept_unreserved_peers(&self) { + T::accept_unreserved_peers(self) + } + + fn deny_unreserved_peers(&self) { + T::deny_unreserved_peers(self) + } + + fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String> { + T::add_reserved_peer(self, peer) + } + + fn remove_reserved_peer(&self, peer_id: PeerId) { + T::remove_reserved_peer(self, peer_id) + } + + fn set_reserved_peers( + &self, + protocol: ProtocolName, + peers: HashSet, + ) -> Result<(), String> { + T::set_reserved_peers(self, protocol, peers) + } + + fn add_peers_to_reserved_set( + &self, + protocol: ProtocolName, + peers: HashSet, + ) -> Result<(), String> { + T::add_peers_to_reserved_set(self, protocol, peers) + } + + fn remove_peers_from_reserved_set(&self, protocol: ProtocolName, peers: Vec) { + T::remove_peers_from_reserved_set(self, protocol, peers) + } + + fn add_to_peers_set( + &self, + protocol: ProtocolName, + peers: HashSet, + ) -> Result<(), String> { + T::add_to_peers_set(self, protocol, peers) + } + + fn remove_from_peers_set(&self, protocol: ProtocolName, peers: Vec) { + T::remove_from_peers_set(self, protocol, peers) + } + + fn sync_num_connected(&self) -> usize { + T::sync_num_connected(self) + } +} + +/// Provides access to network-level event stream. +pub trait NetworkEventStream { + /// Returns a stream containing the events that happen on the network. + /// + /// If this method is called multiple times, the events are duplicated. + /// + /// The stream never ends (unless the `NetworkWorker` gets shut down). + /// + /// The name passed is used to identify the channel in the Prometheus metrics. Note that the + /// parameter is a `&'static str`, and not a `String`, in order to avoid accidentally having + /// an unbounded set of Prometheus metrics, which would be quite bad in terms of memory + fn event_stream(&self, name: &'static str) -> Pin + Send>>; +} + +impl NetworkEventStream for Arc +where + T: ?Sized, + T: NetworkEventStream, +{ + fn event_stream(&self, name: &'static str) -> Pin + Send>> { + T::event_stream(self, name) + } +} + +/// Trait for providing information about the local network state +pub trait NetworkStateInfo { + /// Returns the local external addresses. + fn external_addresses(&self) -> Vec; + + /// Returns the local Peer ID. + fn local_peer_id(&self) -> PeerId; +} + +impl NetworkStateInfo for Arc +where + T: ?Sized, + T: NetworkStateInfo, +{ + fn external_addresses(&self) -> Vec { + T::external_addresses(self) + } + + fn local_peer_id(&self) -> PeerId { + T::local_peer_id(self) + } +} + +/// Reserved slot in the notifications buffer, ready to accept data. +pub trait NotificationSenderReady { + /// Consumes this slots reservation and actually queues the notification. + /// + /// NOTE: Traits can't consume itself, but calling this method second time will return an error. + fn send(&mut self, notification: Vec) -> Result<(), NotificationSenderError>; +} + +/// A `NotificationSender` allows for sending notifications to a peer with a chosen protocol. +#[async_trait::async_trait] +pub trait NotificationSender: Send + Sync + 'static { + /// Returns a future that resolves when the `NotificationSender` is ready to send a + /// notification. + async fn ready(&self) + -> Result, NotificationSenderError>; +} + +/// Error returned by [`NetworkNotification::notification_sender`]. +#[derive(Debug, thiserror::Error)] +pub enum NotificationSenderError { + /// The notification receiver has been closed, usually because the underlying connection + /// closed. + /// + /// Some of the notifications most recently sent may not have been received. However, + /// the peer may still be connected and a new `NotificationSender` for the same + /// protocol obtained from [`NetworkNotification::notification_sender`]. + #[error("The notification receiver has been closed")] + Closed, + /// Protocol name hasn't been registered. + #[error("Protocol name hasn't been registered")] + BadProtocol, +} + +/// Provides ability to send network notifications. +pub trait NetworkNotification { + /// Appends a notification to the buffer of pending outgoing notifications with the given peer. + /// Has no effect if the notifications channel with this protocol name is not open. + /// + /// If the buffer of pending outgoing notifications with that peer is full, the notification + /// is silently dropped and the connection to the remote will start being shut down. This + /// happens if you call this method at a higher rate than the rate at which the peer processes + /// these notifications, or if the available network bandwidth is too low. + /// + /// For this reason, this method is considered soft-deprecated. You are encouraged to use + /// [`NetworkNotification::notification_sender`] instead. + /// + /// > **Note**: The reason why this is a no-op in the situation where we have no channel is + /// > that we don't guarantee message delivery anyway. Networking issues can cause + /// > connections to drop at any time, and higher-level logic shouldn't differentiate + /// > between the remote voluntarily closing a substream or a network error + /// > preventing the message from being delivered. + /// + /// The protocol must have been registered with + /// `crate::config::NetworkConfiguration::notifications_protocols`. + fn write_notification(&self, target: PeerId, protocol: ProtocolName, message: Vec); + + /// Obtains a [`NotificationSender`] for a connected peer, if it exists. + /// + /// A `NotificationSender` is scoped to a particular connection to the peer that holds + /// a receiver. With a `NotificationSender` at hand, sending a notification is done in two + /// steps: + /// + /// 1. [`NotificationSender::ready`] is used to wait for the sender to become ready + /// for another notification, yielding a [`NotificationSenderReady`] token. + /// 2. [`NotificationSenderReady::send`] enqueues the notification for sending. This operation + /// can only fail if the underlying notification substream or connection has suddenly closed. + /// + /// An error is returned by [`NotificationSenderReady::send`] if there exists no open + /// notifications substream with that combination of peer and protocol, or if the remote + /// has asked to close the notifications substream. If that happens, it is guaranteed that an + /// [`Event::NotificationStreamClosed`] has been generated on the stream returned by + /// [`NetworkEventStream::event_stream`]. + /// + /// If the remote requests to close the notifications substream, all notifications successfully + /// enqueued using [`NotificationSenderReady::send`] will finish being sent out before the + /// substream actually gets closed, but attempting to enqueue more notifications will now + /// return an error. It is however possible for the entire connection to be abruptly closed, + /// in which case enqueued notifications will be lost. + /// + /// The protocol must have been registered with + /// `crate::config::NetworkConfiguration::notifications_protocols`. + /// + /// # Usage + /// + /// This method returns a struct that allows waiting until there is space available in the + /// buffer of messages towards the given peer. If the peer processes notifications at a slower + /// rate than we send them, this buffer will quickly fill up. + /// + /// As such, you should never do something like this: + /// + /// ```ignore + /// // Do NOT do this + /// for peer in peers { + /// if let Ok(n) = network.notification_sender(peer, ...) { + /// if let Ok(s) = n.ready().await { + /// let _ = s.send(...); + /// } + /// } + /// } + /// ``` + /// + /// Doing so would slow down all peers to the rate of the slowest one. A malicious or + /// malfunctioning peer could intentionally process notifications at a very slow rate. + /// + /// Instead, you are encouraged to maintain your own buffer of notifications on top of the one + /// maintained by `sc-network`, and use `notification_sender` to progressively send out + /// elements from your buffer. If this additional buffer is full (which will happen at some + /// point if the peer is too slow to process notifications), appropriate measures can be taken, + /// such as removing non-critical notifications from the buffer or disconnecting the peer + /// using [`NetworkPeers::disconnect_peer`]. + /// + /// + /// Notifications Per-peer buffer + /// broadcast +-------> of notifications +--> `notification_sender` +--> Internet + /// ^ (not covered by + /// | sc-network) + /// + + /// Notifications should be dropped + /// if buffer is full + /// + /// + /// See also the `sc-network-gossip` crate for a higher-level way to send notifications. + fn notification_sender( + &self, + target: PeerId, + protocol: ProtocolName, + ) -> Result, NotificationSenderError>; +} + +impl NetworkNotification for Arc +where + T: ?Sized, + T: NetworkNotification, +{ + fn write_notification(&self, target: PeerId, protocol: ProtocolName, message: Vec) { + T::write_notification(self, target, protocol, message) + } + + fn notification_sender( + &self, + target: PeerId, + protocol: ProtocolName, + ) -> Result, NotificationSenderError> { + T::notification_sender(self, target, protocol) + } +} + +/// Provides ability to send network requests. +#[async_trait::async_trait] +pub trait NetworkRequest { + /// Sends a single targeted request to a specific peer. On success, returns the response of + /// the peer. + /// + /// Request-response protocols are a way to complement notifications protocols, but + /// notifications should remain the default ways of communicating information. For example, a + /// peer can announce something through a notification, after which the recipient can obtain + /// more information by performing a request. + /// As such, call this function with `IfDisconnected::ImmediateError` for `connect`. This way + /// you will get an error immediately for disconnected peers, instead of waiting for a + /// potentially very long connection attempt, which would suggest that something is wrong + /// anyway, as you are supposed to be connected because of the notification protocol. + /// + /// No limit or throttling of concurrent outbound requests per peer and protocol are enforced. + /// Such restrictions, if desired, need to be enforced at the call site(s). + /// + /// The protocol must have been registered through + /// `NetworkConfiguration::request_response_protocols`. + async fn request( + &self, + target: PeerId, + protocol: ProtocolName, + request: Vec, + connect: IfDisconnected, + ) -> Result, RequestFailure>; + + /// Variation of `request` which starts a request whose response is delivered on a provided + /// channel. + /// + /// Instead of blocking and waiting for a reply, this function returns immediately, sending + /// responses via the passed in sender. This alternative API exists to make it easier to + /// integrate with message passing APIs. + /// + /// Keep in mind that the connected receiver might receive a `Canceled` event in case of a + /// closing connection. This is expected behaviour. With `request` you would get a + /// `RequestFailure::Network(OutboundFailure::ConnectionClosed)` in that case. + fn start_request( + &self, + target: PeerId, + protocol: ProtocolName, + request: Vec, + tx: oneshot::Sender, RequestFailure>>, + connect: IfDisconnected, + ); +} + +// Manual implementation to avoid extra boxing here +impl NetworkRequest for Arc +where + T: ?Sized, + T: NetworkRequest, +{ + fn request<'life0, 'async_trait>( + &'life0 self, + target: PeerId, + protocol: ProtocolName, + request: Vec, + connect: IfDisconnected, + ) -> Pin, RequestFailure>> + Send + 'async_trait>> + where + 'life0: 'async_trait, + Self: 'async_trait, + { + T::request(self, target, protocol, request, connect) + } + + fn start_request( + &self, + target: PeerId, + protocol: ProtocolName, + request: Vec, + tx: oneshot::Sender, RequestFailure>>, + connect: IfDisconnected, + ) { + T::start_request(self, target, protocol, request, tx, connect) + } +} + +/// Provides ability to announce blocks to the network. +pub trait NetworkBlock { + /// Make sure an important block is propagated to peers. + /// + /// In chain-based consensus, we often need to make sure non-best forks are + /// at least temporarily synced. This function forces such an announcement. + fn announce_block(&self, hash: BlockHash, data: Option>); + + /// Inform the network service about new best imported block. + fn new_best_block_imported(&self, hash: BlockHash, number: BlockNumber); +} + +impl NetworkBlock for Arc +where + T: ?Sized, + T: NetworkBlock, +{ + fn announce_block(&self, hash: BlockHash, data: Option>) { + T::announce_block(self, hash, data) + } + + fn new_best_block_imported(&self, hash: BlockHash, number: BlockNumber) { + T::new_best_block_imported(self, hash, number) + } +} diff --git a/client/network/common/src/service/signature.rs b/client/network/common/src/service/signature.rs new file mode 100644 index 0000000000000..602ef3d82979a --- /dev/null +++ b/client/network/common/src/service/signature.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. +// +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program 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. +// +// This program 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 this program. If not, see . +// +// If you read this, you are very thorough, congratulations. + +use libp2p::{ + identity::{error::SigningError, Keypair, PublicKey}, + PeerId, +}; + +/// A result of signing a message with a network identity. Since `PeerId` is potentially a hash of a +/// `PublicKey`, you need to reveal the `PublicKey` next to the signature, so the verifier can check +/// if the signature was made by the entity that controls a given `PeerId`. +pub struct Signature { + /// The public key derived from the network identity that signed the message. + pub public_key: PublicKey, + /// The actual signature made for the message signed. + pub bytes: Vec, +} + +impl Signature { + /// Create a signature for a message with a given network identity. + pub fn sign_message( + message: impl AsRef<[u8]>, + keypair: &Keypair, + ) -> Result { + let public_key = keypair.public(); + let bytes = keypair.sign(message.as_ref())?; + Ok(Self { public_key, bytes }) + } + + /// Verify whether the signature was made for the given message by the entity that controls the + /// given `PeerId`. + pub fn verify(&self, message: impl AsRef<[u8]>, peer_id: &PeerId) -> bool { + *peer_id == self.public_key.to_peer_id() && + self.public_key.verify(message.as_ref(), &self.bytes) + } +} diff --git a/client/network/common/src/sync.rs b/client/network/common/src/sync.rs new file mode 100644 index 0000000000000..5e8219c550d19 --- /dev/null +++ b/client/network/common/src/sync.rs @@ -0,0 +1,398 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Abstract interfaces and data structures related to network sync. + +pub mod message; +pub mod metrics; +pub mod warp; + +use libp2p::PeerId; +use message::{BlockAnnounce, BlockData, BlockRequest, BlockResponse}; +use sc_consensus::{import_queue::RuntimeOrigin, IncomingBlock}; +use sp_consensus::BlockOrigin; +use sp_runtime::{ + traits::{Block as BlockT, NumberFor}, + Justifications, +}; +use std::{any::Any, fmt, fmt::Formatter, task::Poll}; +use warp::WarpSyncProgress; + +/// The sync status of a peer we are trying to sync with +#[derive(Debug)] +pub struct PeerInfo { + /// Their best block hash. + pub best_hash: Block::Hash, + /// Their best block number. + pub best_number: NumberFor, +} + +/// Reported sync state. +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum SyncState { + /// Initial sync is complete, keep-up sync is active. + Idle, + /// Actively catching up with the chain. + Downloading { target: BlockNumber }, + /// All blocks are downloaded and are being imported. + Importing { target: BlockNumber }, +} + +impl SyncState { + /// Are we actively catching up with the chain? + pub fn is_major_syncing(&self) -> bool { + !matches!(self, SyncState::Idle) + } +} + +/// Reported state download progress. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct StateDownloadProgress { + /// Estimated download percentage. + pub percentage: u32, + /// Total state size in bytes downloaded so far. + pub size: u64, +} + +/// Syncing status and statistics. +#[derive(Clone)] +pub struct SyncStatus { + /// Current global sync state. + pub state: SyncState>, + /// Target sync block number. + pub best_seen_block: Option>, + /// Number of peers participating in syncing. + pub num_peers: u32, + /// Number of blocks queued for import + pub queued_blocks: u32, + /// State sync status in progress, if any. + pub state_sync: Option, + /// Warp sync in progress, if any. + pub warp_sync: Option>, +} + +/// A peer did not behave as expected and should be reported. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BadPeer(pub PeerId, pub sc_peerset::ReputationChange); + +impl fmt::Display for BadPeer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Bad peer {}; Reputation change: {:?}", self.0, self.1) + } +} + +impl std::error::Error for BadPeer {} + +/// Result of [`ChainSync::on_block_data`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum OnBlockData { + /// The block should be imported. + Import(BlockOrigin, Vec>), + /// A new block request needs to be made to the given peer. + Request(PeerId, BlockRequest), + /// Continue processing events. + Continue, +} + +/// Result of [`ChainSync::on_block_justification`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum OnBlockJustification { + /// The justification needs no further handling. + Nothing, + /// The justification should be imported. + Import { + peer: PeerId, + hash: Block::Hash, + number: NumberFor, + justifications: Justifications, + }, +} + +/// Result of `ChainSync::on_state_data`. +#[derive(Debug)] +pub enum OnStateData { + /// The block and state that should be imported. + Import(BlockOrigin, IncomingBlock), + /// A new state request needs to be made to the given peer. + Continue, +} + +/// Block or justification request polled from `ChainSync` +#[derive(Debug)] +pub enum ImportResult { + BlockImport(BlockOrigin, Vec>), + JustificationImport(RuntimeOrigin, B::Hash, NumberFor, Justifications), +} + +/// Value polled from `ChainSync` +#[derive(Debug)] +pub enum PollResult { + Import(ImportResult), + Announce(PollBlockAnnounceValidation), +} + +/// Result of [`ChainSync::poll_block_announce_validation`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PollBlockAnnounceValidation { + /// The announcement failed at validation. + /// + /// The peer reputation should be decreased. + Failure { + /// Who sent the processed block announcement? + who: PeerId, + /// Should the peer be disconnected? + disconnect: bool, + }, + /// The announcement does not require further handling. + Nothing { + /// Who sent the processed block announcement? + who: PeerId, + /// Was this their new best block? + is_best: bool, + /// The announcement. + announce: BlockAnnounce, + }, + /// The announcement header should be imported. + ImportHeader { + /// Who sent the processed block announcement? + who: PeerId, + /// Was this their new best block? + is_best: bool, + /// The announcement. + announce: BlockAnnounce, + }, + /// The block announcement should be skipped. + Skip, +} + +/// Operation mode. +#[derive(Debug, PartialEq, Eq)] +pub enum SyncMode { + // Sync headers only + Light, + // Sync headers and block bodies + Full, + // Sync headers and the last finalied state + LightState { storage_chain_mode: bool, skip_proofs: bool }, + // Warp sync mode. + Warp, +} + +#[derive(Debug)] +pub struct Metrics { + pub queued_blocks: u32, + pub fork_targets: u32, + pub justifications: metrics::Metrics, +} + +#[derive(Debug)] +pub enum PeerRequest { + Block(BlockRequest), + State, + WarpProof, +} + +/// Wrapper for implementation-specific state request. +/// +/// NOTE: Implementation must be able to encode and decode it for network purposes. +pub struct OpaqueStateRequest(pub Box); + +impl fmt::Debug for OpaqueStateRequest { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("OpaqueStateRequest").finish() + } +} + +/// Wrapper for implementation-specific state response. +/// +/// NOTE: Implementation must be able to encode and decode it for network purposes. +pub struct OpaqueStateResponse(pub Box); + +impl fmt::Debug for OpaqueStateResponse { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("OpaqueStateResponse").finish() + } +} + +/// Wrapper for implementation-specific block request. +/// +/// NOTE: Implementation must be able to encode and decode it for network purposes. +pub struct OpaqueBlockRequest(pub Box); + +impl fmt::Debug for OpaqueBlockRequest { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("OpaqueBlockRequest").finish() + } +} + +/// Wrapper for implementation-specific block response. +/// +/// NOTE: Implementation must be able to encode and decode it for network purposes. +pub struct OpaqueBlockResponse(pub Box); + +impl fmt::Debug for OpaqueBlockResponse { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("OpaqueBlockResponse").finish() + } +} + +/// Something that represents the syncing strategy to download past and future blocks of the chain. +pub trait ChainSync: Send { + /// Returns the state of the sync of the given peer. + /// + /// Returns `None` if the peer is unknown. + fn peer_info(&self, who: &PeerId) -> Option>; + + /// Returns the current sync status. + fn status(&self) -> SyncStatus; + + /// Number of active forks requests. This includes + /// requests that are pending or could be issued right away. + fn num_sync_requests(&self) -> usize; + + /// Number of downloaded blocks. + fn num_downloaded_blocks(&self) -> usize; + + /// Returns the current number of peers stored within this state machine. + fn num_peers(&self) -> usize; + + /// Returns the number of peers we're connected to and that are being queried. + fn num_active_peers(&self) -> usize; + + /// Handle a new connected peer. + /// + /// Call this method whenever we connect to a new peer. + fn new_peer( + &mut self, + who: PeerId, + best_hash: Block::Hash, + best_number: NumberFor, + ) -> Result>, BadPeer>; + + /// Signal that a new best block has been imported. + fn update_chain_info(&mut self, best_hash: &Block::Hash, best_number: NumberFor); + + /// Schedule a justification request for the given block. + fn request_justification(&mut self, hash: &Block::Hash, number: NumberFor); + + /// Clear all pending justification requests. + fn clear_justification_requests(&mut self); + + /// Request syncing for the given block from given set of peers. + fn set_sync_fork_request( + &mut self, + peers: Vec, + hash: &Block::Hash, + number: NumberFor, + ); + + /// Handle a response from the remote to a block request that we made. + /// + /// `request` must be the original request that triggered `response`. + /// or `None` if data comes from the block announcement. + /// + /// If this corresponds to a valid block, this outputs the block that + /// must be imported in the import queue. + fn on_block_data( + &mut self, + who: &PeerId, + request: Option>, + response: BlockResponse, + ) -> Result, BadPeer>; + + /// Procss received block data. + fn process_block_response_data( + &mut self, + blocks_to_import: Result, BadPeer>, + ); + + /// Handle a response from the remote to a justification request that we made. + /// + /// `request` must be the original request that triggered `response`. + fn on_block_justification( + &mut self, + who: PeerId, + response: BlockResponse, + ) -> Result, BadPeer>; + + /// Call this when a justification has been processed by the import queue, + /// with or without errors. + fn on_justification_import( + &mut self, + hash: Block::Hash, + number: NumberFor, + success: bool, + ); + + /// Notify about finalization of the given block. + fn on_block_finalized(&mut self, hash: &Block::Hash, number: NumberFor); + + /// Push a block announce validation. + /// + /// It is required that [`ChainSync::poll_block_announce_validation`] is called + /// to check for finished block announce validations. + fn push_block_announce_validation( + &mut self, + who: PeerId, + hash: Block::Hash, + announce: BlockAnnounce, + is_best: bool, + ); + + /// Poll block announce validation. + /// + /// Block announce validations can be pushed by using + /// [`ChainSync::push_block_announce_validation`]. + /// + /// This should be polled until it returns [`Poll::Pending`]. + /// + /// If [`PollBlockAnnounceValidation::ImportHeader`] is returned, then the caller MUST try to + /// import passed header (call `on_block_data`). The network request isn't sent in this case. + fn poll_block_announce_validation<'a>( + &mut self, + cx: &mut std::task::Context<'a>, + ) -> Poll>; + + /// Call when a peer has disconnected. + /// Canceled obsolete block request may result in some blocks being ready for + /// import, so this functions checks for such blocks and returns them. + fn peer_disconnected(&mut self, who: &PeerId); + + /// Return some key metrics. + fn metrics(&self) -> Metrics; + + /// Access blocks from implementation-specific block response. + fn block_response_into_blocks( + &self, + request: &BlockRequest, + response: OpaqueBlockResponse, + ) -> Result>, String>; + + /// Advance the state of `ChainSync` + /// + /// Internally calls [`ChainSync::poll_block_announce_validation()`] and + /// this function should be polled until it returns [`Poll::Pending`] to + /// consume all pending events. + fn poll( + &mut self, + cx: &mut std::task::Context, + ) -> Poll>; + + /// Send block request to peer + fn send_block_request(&mut self, who: PeerId, request: BlockRequest); +} diff --git a/client/network/common/src/sync/message.rs b/client/network/common/src/sync/message.rs new file mode 100644 index 0000000000000..346f1dbce9bcc --- /dev/null +++ b/client/network/common/src/sync/message.rs @@ -0,0 +1,246 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Network packet message types. These get serialized and put into the lower level protocol +//! payload. + +use crate::protocol::role::Roles; + +use bitflags::bitflags; +use codec::{Decode, Encode, Error, Input, Output}; +pub use generic::{BlockAnnounce, FromBlock}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; + +/// Type alias for using the block request type using block type parameters. +pub type BlockRequest = + generic::BlockRequest<::Hash, <::Header as HeaderT>::Number>; + +/// Type alias for using the BlockData type using block type parameters. +pub type BlockData = + generic::BlockData<::Header, ::Hash, ::Extrinsic>; + +/// Type alias for using the BlockResponse type using block type parameters. +pub type BlockResponse = + generic::BlockResponse<::Header, ::Hash, ::Extrinsic>; + +// Bits of block data and associated artifacts to request. +bitflags! { + /// Node roles bitmask. + pub struct BlockAttributes: u8 { + /// Include block header. + const HEADER = 0b00000001; + /// Include block body. + const BODY = 0b00000010; + /// Include block receipt. + const RECEIPT = 0b00000100; + /// Include block message queue. + const MESSAGE_QUEUE = 0b00001000; + /// Include a justification for the block. + const JUSTIFICATION = 0b00010000; + /// Include indexed transactions for a block. + const INDEXED_BODY = 0b00100000; + } +} + +impl BlockAttributes { + /// Encodes attributes as big endian u32, compatible with SCALE-encoding (i.e the + /// significant byte has zero index). + pub fn to_be_u32(&self) -> u32 { + u32::from_be_bytes([self.bits(), 0, 0, 0]) + } + + /// Decodes attributes, encoded with the `encode_to_be_u32()` call. + pub fn from_be_u32(encoded: u32) -> Result { + Self::from_bits(encoded.to_be_bytes()[0]) + .ok_or_else(|| Error::from("Invalid BlockAttributes")) + } +} + +impl Encode for BlockAttributes { + fn encode_to(&self, dest: &mut T) { + dest.push_byte(self.bits()) + } +} + +impl codec::EncodeLike for BlockAttributes {} + +impl Decode for BlockAttributes { + fn decode(input: &mut I) -> Result { + Self::from_bits(input.read_byte()?).ok_or_else(|| Error::from("Invalid bytes")) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode)] +/// Block enumeration direction. +pub enum Direction { + /// Enumerate in ascending order (from child to parent). + Ascending = 0, + /// Enumerate in descending order (from parent to canonical child). + Descending = 1, +} + +/// Block state in the chain. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode)] +pub enum BlockState { + /// Block is not part of the best chain. + Normal, + /// Latest best block. + Best, +} + +/// Announcement summary used for debug logging. +#[derive(Debug)] +pub struct AnnouncementSummary { + pub block_hash: H::Hash, + pub number: H::Number, + pub parent_hash: H::Hash, + pub state: Option, +} + +impl BlockAnnounce { + pub fn summary(&self) -> AnnouncementSummary { + AnnouncementSummary { + block_hash: self.header.hash(), + number: *self.header.number(), + parent_hash: *self.header.parent_hash(), + state: self.state, + } + } +} + +/// Generic types. +pub mod generic { + use super::{BlockAttributes, BlockState, Direction}; + use crate::message::RequestId; + use codec::{Decode, Encode, Input, Output}; + use sp_runtime::{EncodedJustification, Justifications}; + + /// Block data sent in the response. + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + pub struct BlockData { + /// Block header hash. + pub hash: Hash, + /// Block header if requested. + pub header: Option
, + /// Block body if requested. + pub body: Option>, + /// Block body indexed transactions if requested. + pub indexed_body: Option>>, + /// Block receipt if requested. + pub receipt: Option>, + /// Block message queue if requested. + pub message_queue: Option>, + /// Justification if requested. + pub justification: Option, + /// Justifications if requested. + pub justifications: Option, + } + + /// Request block data from a peer. + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + pub struct BlockRequest { + /// Unique request id. + pub id: RequestId, + /// Bits of block data to request. + pub fields: BlockAttributes, + /// Start from this block. + pub from: FromBlock, + /// Sequence direction. + pub direction: Direction, + /// Maximum number of blocks to return. An implementation defined maximum is used when + /// unspecified. + pub max: Option, + } + + /// Identifies starting point of a block sequence. + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + pub enum FromBlock { + /// Start with given hash. + Hash(Hash), + /// Start with given block number. + Number(Number), + } + + /// Response to `BlockRequest` + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + pub struct BlockResponse { + /// Id of a request this response was made for. + pub id: RequestId, + /// Block data for the requested sequence. + pub blocks: Vec>, + } + + /// Announce a new complete relay chain block on the network. + #[derive(Debug, PartialEq, Eq, Clone)] + pub struct BlockAnnounce { + /// New block header. + pub header: H, + /// Block state. TODO: Remove `Option` and custom encoding when v4 becomes common. + pub state: Option, + /// Data associated with this block announcement, e.g. a candidate message. + pub data: Option>, + } + + // Custom Encode/Decode impl to maintain backwards compatibility with v3. + // This assumes that the packet contains nothing but the announcement message. + // TODO: Get rid of it once protocol v4 is common. + impl Encode for BlockAnnounce { + fn encode_to(&self, dest: &mut T) { + self.header.encode_to(dest); + if let Some(state) = &self.state { + state.encode_to(dest); + } + if let Some(data) = &self.data { + data.encode_to(dest) + } + } + } + + impl Decode for BlockAnnounce { + fn decode(input: &mut I) -> Result { + let header = H::decode(input)?; + let state = BlockState::decode(input).ok(); + let data = Vec::decode(input).ok(); + Ok(Self { header, state, data }) + } + } +} + +/// Handshake sent when we open a block announces substream. +#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] +pub struct BlockAnnouncesHandshake { + /// Roles of the node. + pub roles: Roles, + /// Best block number. + pub best_number: NumberFor, + /// Best block hash. + pub best_hash: B::Hash, + /// Genesis block hash. + pub genesis_hash: B::Hash, +} + +impl BlockAnnouncesHandshake { + pub fn build( + roles: Roles, + best_number: NumberFor, + best_hash: B::Hash, + genesis_hash: B::Hash, + ) -> Self { + Self { genesis_hash, roles, best_number, best_hash } + } +} diff --git a/client/network/common/src/sync/metrics.rs b/client/network/common/src/sync/metrics.rs new file mode 100644 index 0000000000000..15ff090a8ccac --- /dev/null +++ b/client/network/common/src/sync/metrics.rs @@ -0,0 +1,25 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +#[derive(Debug)] +pub struct Metrics { + pub pending_requests: u32, + pub active_requests: u32, + pub importing_requests: u32, + pub failed_requests: u32, +} diff --git a/client/network/common/src/sync/warp.rs b/client/network/common/src/sync/warp.rs new file mode 100644 index 0000000000000..c9b9037542388 --- /dev/null +++ b/client/network/common/src/sync/warp.rs @@ -0,0 +1,97 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use codec::{Decode, Encode}; +pub use sp_finality_grandpa::{AuthorityList, SetId}; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::fmt; + +/// Scale-encoded warp sync proof response. +pub struct EncodedProof(pub Vec); + +/// Warp sync request +#[derive(Encode, Decode, Debug)] +pub struct WarpProofRequest { + /// Start collecting proofs from this block. + pub begin: B::Hash, +} + +/// Proof verification result. +pub enum VerificationResult { + /// Proof is valid, but the target was not reached. + Partial(SetId, AuthorityList, Block::Hash), + /// Target finality is proved. + Complete(SetId, AuthorityList, Block::Header), +} + +/// Warp sync backend. Handles retrieveing and verifying warp sync proofs. +pub trait WarpSyncProvider: Send + Sync { + /// Generate proof starting at given block hash. The proof is accumulated until maximum proof + /// size is reached. + fn generate( + &self, + start: Block::Hash, + ) -> Result>; + /// Verify warp proof against current set of authorities. + fn verify( + &self, + proof: &EncodedProof, + set_id: SetId, + authorities: AuthorityList, + ) -> Result, Box>; + /// Get current list of authorities. This is supposed to be genesis authorities when starting + /// sync. + fn current_authorities(&self) -> AuthorityList; +} + +/// Reported warp sync phase. +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum WarpSyncPhase { + /// Waiting for peers to connect. + AwaitingPeers, + /// Downloading and verifying grandpa warp proofs. + DownloadingWarpProofs, + /// Downloading target block. + DownloadingTargetBlock, + /// Downloading state data. + DownloadingState, + /// Importing state. + ImportingState, + /// Downloading block history. + DownloadingBlocks(NumberFor), +} + +impl fmt::Display for WarpSyncPhase { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::AwaitingPeers => write!(f, "Waiting for peers"), + Self::DownloadingWarpProofs => write!(f, "Downloading finality proofs"), + Self::DownloadingTargetBlock => write!(f, "Downloading target block"), + Self::DownloadingState => write!(f, "Downloading state"), + Self::ImportingState => write!(f, "Importing state"), + Self::DownloadingBlocks(n) => write!(f, "Downloading block history (#{})", n), + } + } +} + +/// Reported warp sync progress. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct WarpSyncProgress { + /// Estimated download percentage. + pub phase: WarpSyncPhase, + /// Total bytes downloaded so far. + pub total_bytes: u64, +} diff --git a/client/network/src/utils.rs b/client/network/common/src/utils.rs similarity index 97% rename from client/network/src/utils.rs rename to client/network/common/src/utils.rs index b23b7e0c101e0..d0e61a0d0475d 100644 --- a/client/network/src/utils.rs +++ b/client/network/common/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/network/light/Cargo.toml b/client/network/light/Cargo.toml new file mode 100644 index 0000000000000..5b84a0adde20f --- /dev/null +++ b/client/network/light/Cargo.toml @@ -0,0 +1,33 @@ +[package] +description = "Substrate light network protocol" +name = "sc-network-light" +version = "0.10.0-dev" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors = ["Parity Technologies "] +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +documentation = "https://docs.rs/sc-network-light" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[build-dependencies] +prost-build = "0.11" + +[dependencies] +array-bytes = "4.1" +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", +] } +futures = "0.3.21" +libp2p = "0.49.0" +log = "0.4.16" +prost = "0.11" +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +thiserror = "1.0" diff --git a/client/network/light/build.rs b/client/network/light/build.rs new file mode 100644 index 0000000000000..9c44bcd293181 --- /dev/null +++ b/client/network/light/build.rs @@ -0,0 +1,5 @@ +const PROTOS: &[&str] = &["src/schema/light.v1.proto"]; + +fn main() { + prost_build::compile_protos(PROTOS, &["src/schema"]).unwrap(); +} diff --git a/client/network/light/src/lib.rs b/client/network/light/src/lib.rs new file mode 100644 index 0000000000000..2b7cf226f90dd --- /dev/null +++ b/client/network/light/src/lib.rs @@ -0,0 +1,22 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Light client data structures of the networking layer. + +pub mod light_client_requests; +mod schema; diff --git a/client/network/light/src/light_client_requests.rs b/client/network/light/src/light_client_requests.rs new file mode 100644 index 0000000000000..61b549d0f0984 --- /dev/null +++ b/client/network/light/src/light_client_requests.rs @@ -0,0 +1,59 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Helpers for outgoing and incoming light client requests. + +/// For incoming light client requests. +pub mod handler; + +use sc_network_common::{config::ProtocolId, request_responses::ProtocolConfig}; + +use std::time::Duration; + +/// Generate the light client protocol name from the genesis hash and fork id. +fn generate_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> String { + let genesis_hash = genesis_hash.as_ref(); + if let Some(fork_id) = fork_id { + format!("/{}/{}/light/2", array_bytes::bytes2hex("", genesis_hash), fork_id) + } else { + format!("/{}/light/2", array_bytes::bytes2hex("", genesis_hash)) + } +} + +/// Generate the legacy light client protocol name from chain specific protocol identifier. +fn generate_legacy_protocol_name(protocol_id: &ProtocolId) -> String { + format!("/{}/light/2", protocol_id.as_ref()) +} + +/// Generates a [`ProtocolConfig`] for the light client request protocol, refusing incoming +/// requests. +pub fn generate_protocol_config>( + protocol_id: &ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, +) -> ProtocolConfig { + ProtocolConfig { + name: generate_protocol_name(genesis_hash, fork_id).into(), + fallback_names: std::iter::once(generate_legacy_protocol_name(protocol_id).into()) + .collect(), + max_request_size: 1 * 1024 * 1024, + max_response_size: 16 * 1024 * 1024, + request_timeout: Duration::from_secs(15), + inbound_queue: None, + } +} diff --git a/client/network/src/light_client_requests/handler.rs b/client/network/light/src/light_client_requests/handler.rs similarity index 54% rename from client/network/src/light_client_requests/handler.rs rename to client/network/light/src/light_client_requests/handler.rs index 43504edddd73a..abf012b82f9db 100644 --- a/client/network/src/light_client_requests/handler.rs +++ b/client/network/light/src/light_client_requests/handler.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,51 +19,65 @@ //! Helper for incoming light client requests. //! //! Handle (i.e. answer) incoming light client requests from a remote peer received via -//! [`crate::request_responses::RequestResponsesBehaviour`] with +//! `crate::request_responses::RequestResponsesBehaviour` with //! [`LightClientRequestHandler`](handler::LightClientRequestHandler). -use crate::{ - chain::Client, - config::ProtocolId, - request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, - schema, PeerId, -}; +use crate::schema; use codec::{self, Decode, Encode}; use futures::{channel::mpsc, prelude::*}; +use libp2p::PeerId; use log::{debug, trace}; use prost::Message; -use sc_client_api::{light, StorageProof}; +use sc_client_api::{BlockBackend, ProofProvider}; +use sc_network_common::{ + config::ProtocolId, + request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, +}; use sc_peerset::ReputationChange; use sp_core::{ hexdisplay::HexDisplay, - storage::{ChildInfo, ChildType, PrefixedStorageKey, StorageKey}, + storage::{ChildInfo, ChildType, PrefixedStorageKey}, }; -use sp_runtime::{ - generic::BlockId, - traits::{Block, Zero}, -}; -use std::{collections::BTreeMap, sync::Arc}; +use sp_runtime::traits::Block; +use std::{marker::PhantomData, sync::Arc}; const LOG_TARGET: &str = "light-client-request-handler"; /// Handler for incoming light client requests from a remote peer. -pub struct LightClientRequestHandler { +pub struct LightClientRequestHandler { request_receiver: mpsc::Receiver, /// Blockchain client. - client: Arc>, + client: Arc, + _block: PhantomData, } -impl LightClientRequestHandler { - /// Create a new [`crate::block_request_handler::BlockRequestHandler`]. - pub fn new(protocol_id: &ProtocolId, client: Arc>) -> (Self, ProtocolConfig) { +impl LightClientRequestHandler +where + B: Block, + Client: BlockBackend + ProofProvider + Send + Sync + 'static, +{ + /// Create a new [`LightClientRequestHandler`]. + pub fn new( + protocol_id: &ProtocolId, + fork_id: Option<&str>, + client: Arc, + ) -> (Self, ProtocolConfig) { // For now due to lack of data on light client request handling in production systems, this // value is chosen to match the block request limit. let (tx, request_receiver) = mpsc::channel(20); - let mut protocol_config = super::generate_protocol_config(protocol_id); + let mut protocol_config = super::generate_protocol_config( + protocol_id, + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + fork_id, + ); protocol_config.inbound_queue = Some(tx); - (Self { client, request_receiver }, protocol_config) + (Self { client, request_receiver, _block: PhantomData::default() }, protocol_config) } /// Run [`LightClientRequestHandler`]. @@ -137,12 +151,8 @@ impl LightClientRequestHandler { self.on_remote_call_request(&peer, r)?, Some(schema::v1::light::request::Request::RemoteReadRequest(r)) => self.on_remote_read_request(&peer, r)?, - Some(schema::v1::light::request::Request::RemoteHeaderRequest(r)) => - self.on_remote_header_request(&peer, r)?, Some(schema::v1::light::request::Request::RemoteReadChildRequest(r)) => self.on_remote_read_child_request(&peer, r)?, - Some(schema::v1::light::request::Request::RemoteChangesRequest(r)) => - self.on_remote_changes_request(&peer, r)?, None => return Err(HandleRequestError::BadRequest("Remote request without request data.")), }; @@ -162,30 +172,23 @@ impl LightClientRequestHandler { let block = Decode::decode(&mut request.block.as_ref())?; - let proof = - match self - .client - .execution_proof(&BlockId::Hash(block), &request.method, &request.data) - { - Ok((_, proof)) => proof, - Err(e) => { - trace!( - "remote call request from {} ({} at {:?}) failed with: {}", - peer, - request.method, - request.block, - e, - ); - StorageProof::empty() - }, - }; - - let response = { - let r = schema::v1::light::RemoteCallResponse { proof: proof.encode() }; - schema::v1::light::response::Response::RemoteCallResponse(r) + let response = match self.client.execution_proof(block, &request.method, &request.data) { + Ok((_, proof)) => schema::v1::light::RemoteCallResponse { proof: Some(proof.encode()) }, + Err(e) => { + trace!( + "remote call request from {} ({} at {:?}) failed with: {}", + peer, + request.method, + request.block, + e, + ); + schema::v1::light::RemoteCallResponse { proof: None } + }, }; - Ok(schema::v1::light::Response { response: Some(response) }) + Ok(schema::v1::light::Response { + response: Some(schema::v1::light::response::Response::RemoteCallResponse(response)), + }) } fn on_remote_read_request( @@ -207,29 +210,24 @@ impl LightClientRequestHandler { let block = Decode::decode(&mut request.block.as_ref())?; - let proof = match self - .client - .read_proof(&BlockId::Hash(block), &mut request.keys.iter().map(AsRef::as_ref)) - { - Ok(proof) => proof, - Err(error) => { - trace!( - "remote read request from {} ({} at {:?}) failed with: {}", - peer, - fmt_keys(request.keys.first(), request.keys.last()), - request.block, - error, - ); - StorageProof::empty() - }, - }; - - let response = { - let r = schema::v1::light::RemoteReadResponse { proof: proof.encode() }; - schema::v1::light::response::Response::RemoteReadResponse(r) - }; + let response = + match self.client.read_proof(block, &mut request.keys.iter().map(AsRef::as_ref)) { + Ok(proof) => schema::v1::light::RemoteReadResponse { proof: Some(proof.encode()) }, + Err(error) => { + trace!( + "remote read request from {} ({} at {:?}) failed with: {}", + peer, + fmt_keys(request.keys.first(), request.keys.last()), + request.block, + error, + ); + schema::v1::light::RemoteReadResponse { proof: None } + }, + }; - Ok(schema::v1::light::Response { response: Some(response) }) + Ok(schema::v1::light::Response { + response: Some(schema::v1::light::response::Response::RemoteReadResponse(response)), + }) } fn on_remote_read_child_request( @@ -257,14 +255,14 @@ impl LightClientRequestHandler { Some((ChildType::ParentKeyId, storage_key)) => Ok(ChildInfo::new_default(storage_key)), None => Err(sp_blockchain::Error::InvalidChildStorageKey), }; - let proof = match child_info.and_then(|child_info| { + let response = match child_info.and_then(|child_info| { self.client.read_child_proof( - &BlockId::Hash(block), + block, &child_info, &mut request.keys.iter().map(AsRef::as_ref), ) }) { - Ok(proof) => proof, + Ok(proof) => schema::v1::light::RemoteReadResponse { proof: Some(proof.encode()) }, Err(error) => { trace!( "remote read child request from {} ({} {} at {:?}) failed with: {}", @@ -274,133 +272,30 @@ impl LightClientRequestHandler { request.block, error, ); - StorageProof::empty() - }, - }; - - let response = { - let r = schema::v1::light::RemoteReadResponse { proof: proof.encode() }; - schema::v1::light::response::Response::RemoteReadResponse(r) - }; - - Ok(schema::v1::light::Response { response: Some(response) }) - } - - fn on_remote_header_request( - &mut self, - peer: &PeerId, - request: &schema::v1::light::RemoteHeaderRequest, - ) -> Result { - trace!("Remote header proof request from {} ({:?}).", peer, request.block); - - let block = Decode::decode(&mut request.block.as_ref())?; - let (header, proof) = match self.client.header_proof(&BlockId::Number(block)) { - Ok((header, proof)) => (header.encode(), proof), - Err(error) => { - trace!( - "Remote header proof request from {} ({:?}) failed with: {}.", - peer, - request.block, - error - ); - (Default::default(), StorageProof::empty()) - }, - }; - - let response = { - let r = schema::v1::light::RemoteHeaderResponse { header, proof: proof.encode() }; - schema::v1::light::response::Response::RemoteHeaderResponse(r) - }; - - Ok(schema::v1::light::Response { response: Some(response) }) - } - - fn on_remote_changes_request( - &mut self, - peer: &PeerId, - request: &schema::v1::light::RemoteChangesRequest, - ) -> Result { - trace!( - "Remote changes proof request from {} for key {} ({:?}..{:?}).", - peer, - if !request.storage_key.is_empty() { - format!( - "{} : {}", - HexDisplay::from(&request.storage_key), - HexDisplay::from(&request.key) - ) - } else { - HexDisplay::from(&request.key).to_string() + schema::v1::light::RemoteReadResponse { proof: None } }, - request.first, - request.last, - ); - - let first = Decode::decode(&mut request.first.as_ref())?; - let last = Decode::decode(&mut request.last.as_ref())?; - let min = Decode::decode(&mut request.min.as_ref())?; - let max = Decode::decode(&mut request.max.as_ref())?; - let key = StorageKey(request.key.clone()); - let storage_key = if request.storage_key.is_empty() { - None - } else { - Some(PrefixedStorageKey::new_ref(&request.storage_key)) - }; - - let proof = - match self.client.key_changes_proof(first, last, min, max, storage_key, &key) { - Ok(proof) => proof, - Err(error) => { - trace!( - "Remote changes proof request from {} for key {} ({:?}..{:?}) failed with: {}.", - peer, - format!("{} : {}", HexDisplay::from(&request.storage_key), HexDisplay::from(&key.0)), - request.first, - request.last, - error, - ); - - light::ChangesProof:: { - max_block: Zero::zero(), - proof: Vec::new(), - roots: BTreeMap::new(), - roots_proof: StorageProof::empty(), - } - }, - }; - - let response = { - let r = schema::v1::light::RemoteChangesResponse { - max: proof.max_block.encode(), - proof: proof.proof, - roots: proof - .roots - .into_iter() - .map(|(k, v)| schema::v1::light::Pair { fst: k.encode(), snd: v.encode() }) - .collect(), - roots_proof: proof.roots_proof.encode(), - }; - schema::v1::light::response::Response::RemoteChangesResponse(r) }; - Ok(schema::v1::light::Response { response: Some(response) }) + Ok(schema::v1::light::Response { + response: Some(schema::v1::light::response::Response::RemoteReadResponse(response)), + }) } } -#[derive(derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] enum HandleRequestError { - #[display(fmt = "Failed to decode request: {}.", _0)] - DecodeProto(prost::DecodeError), - #[display(fmt = "Failed to encode response: {}.", _0)] - EncodeProto(prost::EncodeError), - #[display(fmt = "Failed to send response.")] + #[error("Failed to decode request: {0}.")] + DecodeProto(#[from] prost::DecodeError), + #[error("Failed to encode response: {0}.")] + EncodeProto(#[from] prost::EncodeError), + #[error("Failed to send response.")] SendResponse, /// A bad request has been received. - #[display(fmt = "bad request: {}", _0)] + #[error("bad request: {0}")] BadRequest(&'static str), /// Encoding or decoding of some data failed. - #[display(fmt = "codec error: {}", _0)] - Codec(codec::Error), + #[error("codec error: {0}")] + Codec(#[from] codec::Error), } fn fmt_keys(first: Option<&Vec>, last: Option<&Vec>) -> String { diff --git a/client/network/light/src/schema.rs b/client/network/light/src/schema.rs new file mode 100644 index 0000000000000..1fc91659a5bbb --- /dev/null +++ b/client/network/light/src/schema.rs @@ -0,0 +1,69 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Include sources generated from protobuf definitions. + +pub(crate) mod v1 { + pub(crate) mod light { + include!(concat!(env!("OUT_DIR"), "/api.v1.light.rs")); + } +} + +#[cfg(test)] +mod tests { + use prost::Message as _; + + #[test] + fn empty_proof_encodes_correctly() { + let encoded = super::v1::light::Response { + response: Some(super::v1::light::response::Response::RemoteReadResponse( + super::v1::light::RemoteReadResponse { proof: Some(Vec::new()) }, + )), + } + .encode_to_vec(); + + // Make sure that the response contains one field of number 2 and wire type 2 (message), + // then another field of number 2 and wire type 2 (bytes), then a length of 0. + assert_eq!(encoded, vec![(2 << 3) | 2, 2, (2 << 3) | 2, 0]); + } + + #[test] + fn no_proof_encodes_correctly() { + let encoded = super::v1::light::Response { + response: Some(super::v1::light::response::Response::RemoteReadResponse( + super::v1::light::RemoteReadResponse { proof: None }, + )), + } + .encode_to_vec(); + + // Make sure that the response contains one field of number 2 and wire type 2 (message). + assert_eq!(encoded, vec![(2 << 3) | 2, 0]); + } + + #[test] + fn proof_encodes_correctly() { + let encoded = super::v1::light::Response { + response: Some(super::v1::light::response::Response::RemoteReadResponse( + super::v1::light::RemoteReadResponse { proof: Some(vec![1, 2, 3, 4]) }, + )), + } + .encode_to_vec(); + + assert_eq!(encoded, vec![(2 << 3) | 2, 6, (2 << 3) | 2, 4, 1, 2, 3, 4]); + } +} diff --git a/client/network/light/src/schema/light.v1.proto b/client/network/light/src/schema/light.v1.proto new file mode 100644 index 0000000000000..a269ea73c2074 --- /dev/null +++ b/client/network/light/src/schema/light.v1.proto @@ -0,0 +1,67 @@ +// Schema definition for light client messages. + +syntax = "proto2"; + +package api.v1.light; + +// Enumerate all possible light client request messages. +message Request { + oneof request { + RemoteCallRequest remote_call_request = 1; + RemoteReadRequest remote_read_request = 2; + RemoteReadChildRequest remote_read_child_request = 4; + // Note: ids 3 and 5 were used in the past. It would be preferable to not re-use them. + } +} + +// Enumerate all possible light client response messages. +message Response { + oneof response { + RemoteCallResponse remote_call_response = 1; + RemoteReadResponse remote_read_response = 2; + // Note: ids 3 and 4 were used in the past. It would be preferable to not re-use them. + } +} + +// Remote call request. +message RemoteCallRequest { + // Block at which to perform call. + required bytes block = 2; + // Method name. + required string method = 3; + // Call data. + required bytes data = 4; +} + +// Remote call response. +message RemoteCallResponse { + // Execution proof. If missing, indicates that the remote couldn't answer, for example because + // the block is pruned. + optional bytes proof = 2; +} + +// Remote storage read request. +message RemoteReadRequest { + // Block at which to perform call. + required bytes block = 2; + // Storage keys. + repeated bytes keys = 3; +} + +// Remote read response. +message RemoteReadResponse { + // Read proof. If missing, indicates that the remote couldn't answer, for example because + // the block is pruned. + optional bytes proof = 2; +} + +// Remote storage read child request. +message RemoteReadChildRequest { + // Block at which to perform call. + required bytes block = 2; + // Child Storage key, this is relative + // to the child type storage location. + required bytes storage_key = 3; + // Storage keys. + repeated bytes keys = 6; +} diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index 08d061ee26b23..3a977edbca574 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,91 +17,59 @@ // along with this program. If not, see . use crate::{ - bitswap::Bitswap, - config::ProtocolId, discovery::{DiscoveryBehaviour, DiscoveryConfig, DiscoveryOut}, - light_client_requests, peer_info, - protocol::{message::Roles, CustomMessageOutcome, NotificationsSink, Protocol}, - request_responses, DhtEvent, ObservedRole, + peer_info, + protocol::{CustomMessageOutcome, NotificationsSink, Protocol}, + request_responses, }; use bytes::Bytes; -use codec::Encode; -use futures::{channel::oneshot, stream::StreamExt}; +use futures::channel::oneshot; use libp2p::{ core::{Multiaddr, PeerId, PublicKey}, - identify::IdentifyInfo, + identify::Info as IdentifyInfo, kad::record, - swarm::{toggle::Toggle, NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, NetworkBehaviour, }; -use log::debug; -use prost::Message; -use sc_consensus::import_queue::{IncomingBlock, Origin}; -use sp_consensus::BlockOrigin; -use sp_runtime::{ - traits::{Block as BlockT, NumberFor}, - Justifications, -}; -use std::{ - borrow::Cow, - collections::{HashSet, VecDeque}, - iter, - task::{Context, Poll}, - time::Duration, -}; -pub use crate::request_responses::{ - IfDisconnected, InboundFailure, OutboundFailure, RequestFailure, RequestId, ResponseFailure, +use sc_network_common::{ + protocol::{ + event::DhtEvent, + role::{ObservedRole, Roles}, + ProtocolName, + }, + request_responses::{IfDisconnected, ProtocolConfig, RequestFailure}, }; +use sc_peerset::{PeersetHandle, ReputationChange}; +use sp_blockchain::HeaderBackend; +use sp_runtime::traits::Block as BlockT; +use std::{collections::HashSet, time::Duration}; + +pub use crate::request_responses::{InboundFailure, OutboundFailure, RequestId, ResponseFailure}; /// General behaviour of the network. Combines all protocols together. #[derive(NetworkBehaviour)] -#[behaviour(out_event = "BehaviourOut", poll_method = "poll")] -pub struct Behaviour { +#[behaviour(out_event = "BehaviourOut")] +pub struct Behaviour +where + B: BlockT, + Client: HeaderBackend + 'static, +{ /// All the substrate-specific protocols. - substrate: Protocol, + substrate: Protocol, /// Periodically pings and identifies the nodes we are connected to, and store information in a /// cache. peer_info: peer_info::PeerInfoBehaviour, /// Discovers nodes of the network. discovery: DiscoveryBehaviour, - /// Bitswap server for blockchain data. - bitswap: Toggle>, - /// Generic request-reponse protocols. + /// Generic request-response protocols. request_responses: request_responses::RequestResponsesBehaviour, - - /// Queue of events to produce for the outside. - #[behaviour(ignore)] - events: VecDeque>, - - /// Light client request handling. - #[behaviour(ignore)] - light_client_request_sender: light_client_requests::sender::LightClientRequestSender, - - /// Protocol name used to send out block requests via - /// [`request_responses::RequestResponsesBehaviour`]. - #[behaviour(ignore)] - block_request_protocol_name: String, - - /// Protocol name used to send out state requests via - /// [`request_responses::RequestResponsesBehaviour`]. - #[behaviour(ignore)] - state_request_protocol_name: String, - - /// Protocol name used to send out warp sync requests via - /// [`request_responses::RequestResponsesBehaviour`]. - #[behaviour(ignore)] - warp_sync_protocol_name: Option, } /// Event generated by `Behaviour`. -pub enum BehaviourOut { - BlockImport(BlockOrigin, Vec>), - JustificationImport(Origin, B::Hash, NumberFor, Justifications), - +pub enum BehaviourOut { /// Started a random iterative Kademlia discovery query. - RandomKademliaStarted(ProtocolId), + RandomKademliaStarted, /// We have received a request from a peer and answered it. /// @@ -110,7 +78,7 @@ pub enum BehaviourOut { /// Peer which sent us a request. peer: PeerId, /// Protocol name of the request. - protocol: Cow<'static, str>, + protocol: ProtocolName, /// If `Ok`, contains the time elapsed between when we received the request and when we /// sent back the response. If `Err`, the error that happened. result: Result, @@ -123,13 +91,16 @@ pub enum BehaviourOut { /// Peer that we send a request to. peer: PeerId, /// Name of the protocol in question. - protocol: Cow<'static, str>, + protocol: ProtocolName, /// Duration the request took. duration: Duration, /// Result of the request. result: Result<(), RequestFailure>, }, + /// A request protocol handler issued reputation changes for the given peer. + ReputationChanges { peer: PeerId, changes: Vec }, + /// Opened a substream with the given node with the given notifications protocol. /// /// The protocol is always one of the notification protocols that have been registered. @@ -137,12 +108,12 @@ pub enum BehaviourOut { /// Node we opened the substream with. remote: PeerId, /// The concerned protocol. Each protocol uses a different substream. - protocol: Cow<'static, str>, + protocol: ProtocolName, /// If the negotiation didn't use the main name of the protocol (the one in /// `notifications_protocol`), then this field contains which name has actually been /// used. /// See also [`crate::Event::NotificationStreamOpened`]. - negotiated_fallback: Option>, + negotiated_fallback: Option, /// Object that permits sending notifications to the peer. notifications_sink: NotificationsSink, /// Role of the remote. @@ -158,7 +129,7 @@ pub enum BehaviourOut { /// Id of the peer we are connected to. remote: PeerId, /// The concerned protocol. Each protocol uses a different substream. - protocol: Cow<'static, str>, + protocol: ProtocolName, /// Replacement for the previous [`NotificationsSink`]. notifications_sink: NotificationsSink, }, @@ -169,7 +140,7 @@ pub enum BehaviourOut { /// Node we closed the substream with. remote: PeerId, /// The concerned protocol. Each protocol uses a different substream. - protocol: Cow<'static, str>, + protocol: ProtocolName, }, /// Received one or more messages from the given node using the given protocol. @@ -177,7 +148,7 @@ pub enum BehaviourOut { /// Node we received the message from. remote: PeerId, /// Concerned protocol and associated message. - messages: Vec<(Cow<'static, str>, Bytes)>, + messages: Vec<(ProtocolName, Bytes)>, }, /// Now connected to a new peer for syncing purposes. @@ -186,55 +157,48 @@ pub enum BehaviourOut { /// No longer connected to a peer for syncing purposes. SyncDisconnected(PeerId), + /// We have obtained identity information from a peer, including the addresses it is listening + /// on. + PeerIdentify { + /// Id of the peer that has been identified. + peer_id: PeerId, + /// Information about the peer. + info: IdentifyInfo, + }, + + /// We have learned about the existence of a node on the default set. + Discovered(PeerId), + /// Events generated by a DHT as a response to get_value or put_value requests as well as the /// request duration. Dht(DhtEvent, Duration), + + /// Ignored event generated by lower layers. + None, } -impl Behaviour { +impl Behaviour +where + B: BlockT, + Client: HeaderBackend + 'static, +{ /// Builds a new `Behaviour`. pub fn new( - substrate: Protocol, + substrate: Protocol, user_agent: String, local_public_key: PublicKey, - light_client_request_sender: light_client_requests::sender::LightClientRequestSender, disco_config: DiscoveryConfig, - block_request_protocol_config: request_responses::ProtocolConfig, - state_request_protocol_config: request_responses::ProtocolConfig, - warp_sync_protocol_config: Option, - bitswap: Option>, - light_client_request_protocol_config: request_responses::ProtocolConfig, - // All remaining request protocol configs. - mut request_response_protocols: Vec, + request_response_protocols: Vec, + peerset: PeersetHandle, ) -> Result { - // Extract protocol name and add to `request_response_protocols`. - let block_request_protocol_name = block_request_protocol_config.name.to_string(); - let state_request_protocol_name = state_request_protocol_config.name.to_string(); - let warp_sync_protocol_name = match warp_sync_protocol_config { - Some(config) => { - let name = config.name.to_string(); - request_response_protocols.push(config); - Some(name) - }, - None => None, - }; - request_response_protocols.push(block_request_protocol_config); - request_response_protocols.push(state_request_protocol_config); - request_response_protocols.push(light_client_request_protocol_config); - Ok(Self { substrate, peer_info: peer_info::PeerInfoBehaviour::new(user_agent, local_public_key), discovery: disco_config.finish(), - bitswap: bitswap.into(), request_responses: request_responses::RequestResponsesBehaviour::new( request_response_protocols.into_iter(), + peerset, )?, - light_client_request_sender, - events: VecDeque::new(), - block_request_protocol_name, - state_request_protocol_name, - warp_sync_protocol_name, }) } @@ -248,25 +212,20 @@ impl Behaviour { self.discovery.add_known_address(peer_id, addr) } - /// Returns the number of nodes in each Kademlia kbucket for each Kademlia instance. + /// Returns the number of nodes in each Kademlia kbucket. /// - /// Identifies Kademlia instances by their [`ProtocolId`] and kbuckets by the base 2 logarithm - /// of their lower bound. - pub fn num_entries_per_kbucket( - &mut self, - ) -> impl ExactSizeIterator)> { + /// Identifies kbuckets by the base 2 logarithm of their lower bound. + pub fn num_entries_per_kbucket(&mut self) -> Option> { self.discovery.num_entries_per_kbucket() } /// Returns the number of records in the Kademlia record stores. - pub fn num_kademlia_records(&mut self) -> impl ExactSizeIterator { + pub fn num_kademlia_records(&mut self) -> Option { self.discovery.num_kademlia_records() } /// Returns the total size in bytes of all the records in the Kademlia record stores. - pub fn kademlia_records_total_size( - &mut self, - ) -> impl ExactSizeIterator { + pub fn kademlia_records_total_size(&mut self) -> Option { self.discovery.kademlia_records_total_size() } @@ -293,18 +252,29 @@ impl Behaviour { } /// Returns a shared reference to the user protocol. - pub fn user_protocol(&self) -> &Protocol { + pub fn user_protocol(&self) -> &Protocol { &self.substrate } /// Returns a mutable reference to the user protocol. - pub fn user_protocol_mut(&mut self) -> &mut Protocol { + pub fn user_protocol_mut(&mut self) -> &mut Protocol { &mut self.substrate } + /// Add a self-reported address of a remote peer to the k-buckets of the supported + /// DHTs (`supported_protocols`). + pub fn add_self_reported_address_to_dht( + &mut self, + peer_id: &PeerId, + supported_protocols: &[impl AsRef<[u8]>], + addr: Multiaddr, + ) { + self.discovery.add_self_reported_address(peer_id, supported_protocols, addr); + } + /// Start querying a record from the DHT. Will later produce either a `ValueFound` or a /// `ValueNotFound` event. - pub fn get_value(&mut self, key: &record::Key) { + pub fn get_value(&mut self, key: record::Key) { self.discovery.get_value(key); } @@ -313,14 +283,6 @@ impl Behaviour { pub fn put_value(&mut self, key: record::Key, value: Vec) { self.discovery.put_value(key, value); } - - /// Issue a light client request. - pub fn light_client_request( - &mut self, - r: light_client_requests::sender::Request, - ) -> Result<(), light_client_requests::sender::SendRequestError> { - self.light_client_request_sender.request(r) - } } fn reported_roles_to_observed_role(roles: Roles) -> ObservedRole { @@ -333,225 +295,80 @@ fn reported_roles_to_observed_role(roles: Roles) -> ObservedRole { } } -impl NetworkBehaviourEventProcess for Behaviour { - fn inject_event(&mut self, event: void::Void) { - void::unreachable(event) - } -} - -impl NetworkBehaviourEventProcess> for Behaviour { - fn inject_event(&mut self, event: CustomMessageOutcome) { +impl From> for BehaviourOut { + fn from(event: CustomMessageOutcome) -> Self { match event { - CustomMessageOutcome::BlockImport(origin, blocks) => - self.events.push_back(BehaviourOut::BlockImport(origin, blocks)), - CustomMessageOutcome::JustificationImport(origin, hash, nb, justification) => self - .events - .push_back(BehaviourOut::JustificationImport(origin, hash, nb, justification)), - CustomMessageOutcome::BlockRequest { target, request, pending_response } => { - let mut buf = Vec::with_capacity(request.encoded_len()); - if let Err(err) = request.encode(&mut buf) { - log::warn!( - target: "sync", - "Failed to encode block request {:?}: {:?}", - request, err - ); - return - } - - self.request_responses.send_request( - &target, - &self.block_request_protocol_name, - buf, - pending_response, - IfDisconnected::ImmediateError, - ); - }, - CustomMessageOutcome::StateRequest { target, request, pending_response } => { - let mut buf = Vec::with_capacity(request.encoded_len()); - if let Err(err) = request.encode(&mut buf) { - log::warn!( - target: "sync", - "Failed to encode state request {:?}: {:?}", - request, err - ); - return - } - - self.request_responses.send_request( - &target, - &self.state_request_protocol_name, - buf, - pending_response, - IfDisconnected::ImmediateError, - ); - }, - CustomMessageOutcome::WarpSyncRequest { target, request, pending_response } => - match &self.warp_sync_protocol_name { - Some(name) => self.request_responses.send_request( - &target, - name, - request.encode(), - pending_response, - IfDisconnected::ImmediateError, - ), - None => { - log::warn!( - target: "sync", - "Trying to send warp sync request when no protocol is configured {:?}", - request, - ); - return - }, - }, CustomMessageOutcome::NotificationStreamOpened { remote, protocol, negotiated_fallback, roles, notifications_sink, - } => { - self.events.push_back(BehaviourOut::NotificationStreamOpened { - remote, - protocol, - negotiated_fallback, - role: reported_roles_to_observed_role(roles), - notifications_sink: notifications_sink.clone(), - }); - }, - CustomMessageOutcome::NotificationStreamReplaced { + } => BehaviourOut::NotificationStreamOpened { remote, protocol, + negotiated_fallback, + role: reported_roles_to_observed_role(roles), notifications_sink, - } => self.events.push_back(BehaviourOut::NotificationStreamReplaced { + }, + CustomMessageOutcome::NotificationStreamReplaced { remote, protocol, notifications_sink, - }), - CustomMessageOutcome::NotificationStreamClosed { remote, protocol } => self - .events - .push_back(BehaviourOut::NotificationStreamClosed { remote, protocol }), - CustomMessageOutcome::NotificationsReceived { remote, messages } => { - self.events.push_back(BehaviourOut::NotificationsReceived { remote, messages }); - }, - CustomMessageOutcome::PeerNewBest(peer_id, number) => { - self.light_client_request_sender.update_best_block(&peer_id, number); - }, - CustomMessageOutcome::SyncConnected(peer_id) => { - self.light_client_request_sender.inject_connected(peer_id); - self.events.push_back(BehaviourOut::SyncConnected(peer_id)) - }, - CustomMessageOutcome::SyncDisconnected(peer_id) => { - self.light_client_request_sender.inject_disconnected(peer_id); - self.events.push_back(BehaviourOut::SyncDisconnected(peer_id)) - }, - CustomMessageOutcome::None => {}, + } => BehaviourOut::NotificationStreamReplaced { remote, protocol, notifications_sink }, + CustomMessageOutcome::NotificationStreamClosed { remote, protocol } => + BehaviourOut::NotificationStreamClosed { remote, protocol }, + CustomMessageOutcome::NotificationsReceived { remote, messages } => + BehaviourOut::NotificationsReceived { remote, messages }, + CustomMessageOutcome::PeerNewBest(_peer_id, _number) => BehaviourOut::None, + CustomMessageOutcome::SyncConnected(peer_id) => BehaviourOut::SyncConnected(peer_id), + CustomMessageOutcome::SyncDisconnected(peer_id) => + BehaviourOut::SyncDisconnected(peer_id), + CustomMessageOutcome::None => BehaviourOut::None, } } } -impl NetworkBehaviourEventProcess for Behaviour { - fn inject_event(&mut self, event: request_responses::Event) { +impl From for BehaviourOut { + fn from(event: request_responses::Event) -> Self { match event { - request_responses::Event::InboundRequest { peer, protocol, result } => { - self.events.push_back(BehaviourOut::InboundRequest { peer, protocol, result }); - }, - request_responses::Event::RequestFinished { peer, protocol, duration, result } => { - self.events.push_back(BehaviourOut::RequestFinished { - peer, - protocol, - duration, - result, - }); - }, + request_responses::Event::InboundRequest { peer, protocol, result } => + BehaviourOut::InboundRequest { peer, protocol, result }, + request_responses::Event::RequestFinished { peer, protocol, duration, result } => + BehaviourOut::RequestFinished { peer, protocol, duration, result }, request_responses::Event::ReputationChanges { peer, changes } => - for change in changes { - self.substrate.report_peer(peer, change); - }, + BehaviourOut::ReputationChanges { peer, changes }, } } } -impl NetworkBehaviourEventProcess for Behaviour { - fn inject_event(&mut self, event: peer_info::PeerInfoEvent) { - let peer_info::PeerInfoEvent::Identified { - peer_id, - info: IdentifyInfo { protocol_version, agent_version, mut listen_addrs, protocols, .. }, - } = event; - - if listen_addrs.len() > 30 { - debug!( - target: "sub-libp2p", - "Node {:?} has reported more than 30 addresses; it is identified by {:?} and {:?}", - peer_id, protocol_version, agent_version - ); - listen_addrs.truncate(30); - } - - for addr in listen_addrs { - self.discovery.add_self_reported_address(&peer_id, protocols.iter(), addr); - } - self.substrate.add_default_set_discovered_nodes(iter::once(peer_id)); +impl From for BehaviourOut { + fn from(event: peer_info::PeerInfoEvent) -> Self { + let peer_info::PeerInfoEvent::Identified { peer_id, info } = event; + BehaviourOut::PeerIdentify { peer_id, info } } } -impl NetworkBehaviourEventProcess for Behaviour { - fn inject_event(&mut self, out: DiscoveryOut) { - match out { +impl From for BehaviourOut { + fn from(event: DiscoveryOut) -> Self { + match event { DiscoveryOut::UnroutablePeer(_peer_id) => { // Obtaining and reporting listen addresses for unroutable peers back // to Kademlia is handled by the `Identify` protocol, part of the - // `PeerInfoBehaviour`. See the `NetworkBehaviourEventProcess` - // implementation for `PeerInfoEvent`. - }, - DiscoveryOut::Discovered(peer_id) => { - self.substrate.add_default_set_discovered_nodes(iter::once(peer_id)); - }, - DiscoveryOut::ValueFound(results, duration) => { - self.events - .push_back(BehaviourOut::Dht(DhtEvent::ValueFound(results), duration)); - }, - DiscoveryOut::ValueNotFound(key, duration) => { - self.events.push_back(BehaviourOut::Dht(DhtEvent::ValueNotFound(key), duration)); + // `PeerInfoBehaviour`. See the `From` + // implementation. + BehaviourOut::None }, - DiscoveryOut::ValuePut(key, duration) => { - self.events.push_back(BehaviourOut::Dht(DhtEvent::ValuePut(key), duration)); - }, - DiscoveryOut::ValuePutFailed(key, duration) => { - self.events - .push_back(BehaviourOut::Dht(DhtEvent::ValuePutFailed(key), duration)); - }, - DiscoveryOut::RandomKademliaStarted(protocols) => - for protocol in protocols { - self.events.push_back(BehaviourOut::RandomKademliaStarted(protocol)); - }, - } - } -} - -impl Behaviour { - fn poll( - &mut self, - cx: &mut Context, - _: &mut impl PollParameters, - ) -> Poll>> { - use light_client_requests::sender::OutEvent; - while let Poll::Ready(Some(event)) = self.light_client_request_sender.poll_next_unpin(cx) { - match event { - OutEvent::SendRequest { target, request, pending_response, protocol_name } => - self.request_responses.send_request( - &target, - &protocol_name, - request, - pending_response, - IfDisconnected::ImmediateError, - ), - } + DiscoveryOut::Discovered(peer_id) => BehaviourOut::Discovered(peer_id), + DiscoveryOut::ValueFound(results, duration) => + BehaviourOut::Dht(DhtEvent::ValueFound(results), duration), + DiscoveryOut::ValueNotFound(key, duration) => + BehaviourOut::Dht(DhtEvent::ValueNotFound(key), duration), + DiscoveryOut::ValuePut(key, duration) => + BehaviourOut::Dht(DhtEvent::ValuePut(key), duration), + DiscoveryOut::ValuePutFailed(key, duration) => + BehaviourOut::Dht(DhtEvent::ValuePutFailed(key), duration), + DiscoveryOut::RandomKademliaStarted => BehaviourOut::RandomKademliaStarted, } - - if let Some(event) = self.events.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)) - } - - Poll::Pending } } diff --git a/client/network/src/bitswap.rs b/client/network/src/bitswap.rs deleted file mode 100644 index 6b53dce626505..0000000000000 --- a/client/network/src/bitswap.rs +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -//! Bitswap server for substrate. -//! -//! Allows querying transactions by hash over standard bitswap protocol -//! Only supports bitswap 1.2.0. -//! CID is expected to reference 256-bit Blake2b transaction hash. - -use crate::{ - chain::Client, - schema::bitswap::{ - message::{wantlist::WantType, Block as MessageBlock, BlockPresence, BlockPresenceType}, - Message as BitswapMessage, - }, -}; -use cid::Version; -use core::pin::Pin; -use futures::{ - io::{AsyncRead, AsyncWrite}, - Future, -}; -use libp2p::{ - core::{ - connection::ConnectionId, upgrade, InboundUpgrade, Multiaddr, OutboundUpgrade, PeerId, - UpgradeInfo, - }, - swarm::{ - IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, - OneShotHandler, PollParameters, ProtocolsHandler, - }, -}; -use log::{debug, error, trace}; -use prost::Message; -use sp_runtime::traits::Block as BlockT; -use std::{ - collections::VecDeque, - io, - sync::Arc, - task::{Context, Poll}, -}; -use unsigned_varint::encode as varint_encode; - -const LOG_TARGET: &str = "bitswap"; - -// Undocumented, but according to JS the bitswap messages have a max size of 512*1024 bytes -// https://github.com/ipfs/js-ipfs-bitswap/blob/ -// d8f80408aadab94c962f6b88f343eb9f39fa0fcc/src/decision-engine/index.js#L16 -// We set it to the same value as max substrate protocol message -const MAX_PACKET_SIZE: usize = 16 * 1024 * 1024; - -// Max number of queued responses before denying requests. -const MAX_RESPONSE_QUEUE: usize = 20; -// Max number of blocks per wantlist -const MAX_WANTED_BLOCKS: usize = 16; - -const PROTOCOL_NAME: &'static [u8] = b"/ipfs/bitswap/1.2.0"; - -type FutureResult = Pin> + Send>>; - -/// Bitswap protocol config -#[derive(Clone, Copy, Debug, Default)] -pub struct BitswapConfig; - -impl UpgradeInfo for BitswapConfig { - type Info = &'static [u8]; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once(PROTOCOL_NAME) - } -} - -impl InboundUpgrade for BitswapConfig -where - TSocket: AsyncRead + AsyncWrite + Send + Unpin + 'static, -{ - type Output = BitswapMessage; - type Error = BitswapError; - type Future = FutureResult; - - fn upgrade_inbound(self, mut socket: TSocket, _info: Self::Info) -> Self::Future { - Box::pin(async move { - let packet = upgrade::read_length_prefixed(&mut socket, MAX_PACKET_SIZE).await?; - let message: BitswapMessage = Message::decode(packet.as_slice())?; - Ok(message) - }) - } -} - -impl UpgradeInfo for BitswapMessage { - type Info = &'static [u8]; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once(PROTOCOL_NAME) - } -} - -impl OutboundUpgrade for BitswapMessage -where - TSocket: AsyncRead + AsyncWrite + Send + Unpin + 'static, -{ - type Output = (); - type Error = io::Error; - type Future = FutureResult; - - fn upgrade_outbound(self, mut socket: TSocket, _info: Self::Info) -> Self::Future { - Box::pin(async move { - let mut data = Vec::with_capacity(self.encoded_len()); - self.encode(&mut data)?; - upgrade::write_length_prefixed(&mut socket, data).await - }) - } -} - -/// Internal protocol handler event. -#[derive(Debug)] -pub enum HandlerEvent { - /// We received a `BitswapMessage` from a remote. - Request(BitswapMessage), - /// We successfully sent a `BitswapMessage`. - ResponseSent, -} - -impl From for HandlerEvent { - fn from(message: BitswapMessage) -> Self { - Self::Request(message) - } -} - -impl From<()> for HandlerEvent { - fn from(_: ()) -> Self { - Self::ResponseSent - } -} - -/// Prefix represents all metadata of a CID, without the actual content. -#[derive(PartialEq, Eq, Clone, Debug)] -struct Prefix { - /// The version of CID. - pub version: Version, - /// The codec of CID. - pub codec: u64, - /// The multihash type of CID. - pub mh_type: u64, - /// The multihash length of CID. - pub mh_len: u8, -} - -impl Prefix { - /// Convert the prefix to encoded bytes. - pub fn to_bytes(&self) -> Vec { - let mut res = Vec::with_capacity(4); - let mut buf = varint_encode::u64_buffer(); - let version = varint_encode::u64(self.version.into(), &mut buf); - res.extend_from_slice(version); - let mut buf = varint_encode::u64_buffer(); - let codec = varint_encode::u64(self.codec.into(), &mut buf); - res.extend_from_slice(codec); - let mut buf = varint_encode::u64_buffer(); - let mh_type = varint_encode::u64(self.mh_type.into(), &mut buf); - res.extend_from_slice(mh_type); - let mut buf = varint_encode::u64_buffer(); - let mh_len = varint_encode::u64(self.mh_len as u64, &mut buf); - res.extend_from_slice(mh_len); - res - } -} - -/// Network behaviour that handles sending and receiving IPFS blocks. -pub struct Bitswap { - client: Arc>, - ready_blocks: VecDeque<(PeerId, BitswapMessage)>, -} - -impl Bitswap { - /// Create a new instance of the bitswap protocol handler. - pub fn new(client: Arc>) -> Self { - Self { client, ready_blocks: Default::default() } - } -} - -impl NetworkBehaviour for Bitswap { - type ProtocolsHandler = OneShotHandler; - type OutEvent = void::Void; - - fn new_handler(&mut self) -> Self::ProtocolsHandler { - Default::default() - } - - fn addresses_of_peer(&mut self, _peer: &PeerId) -> Vec { - Vec::new() - } - - fn inject_connected(&mut self, _peer: &PeerId) {} - - fn inject_disconnected(&mut self, _peer: &PeerId) {} - - fn inject_event(&mut self, peer: PeerId, _connection: ConnectionId, message: HandlerEvent) { - let request = match message { - HandlerEvent::ResponseSent => return, - HandlerEvent::Request(msg) => msg, - }; - trace!(target: LOG_TARGET, "Received request: {:?} from {}", request, peer); - if self.ready_blocks.len() > MAX_RESPONSE_QUEUE { - debug!(target: LOG_TARGET, "Ignored request: queue is full"); - return - } - let mut response = BitswapMessage { - wantlist: None, - blocks: Default::default(), - payload: Default::default(), - block_presences: Default::default(), - pending_bytes: 0, - }; - let wantlist = match request.wantlist { - Some(wantlist) => wantlist, - None => { - debug!(target: LOG_TARGET, "Unexpected bitswap message from {}", peer); - return - }, - }; - if wantlist.entries.len() > MAX_WANTED_BLOCKS { - trace!(target: LOG_TARGET, "Ignored request: too many entries"); - return - } - for entry in wantlist.entries { - let cid = match cid::Cid::read_bytes(entry.block.as_slice()) { - Ok(cid) => cid, - Err(e) => { - trace!(target: LOG_TARGET, "Bad CID {:?}: {:?}", entry.block, e); - continue - }, - }; - if cid.version() != cid::Version::V1 || - cid.hash().code() != u64::from(cid::multihash::Code::Blake2b256) || - cid.hash().size() != 32 - { - debug!(target: LOG_TARGET, "Ignoring unsupported CID {}: {}", peer, cid); - continue - } - let mut hash = B::Hash::default(); - hash.as_mut().copy_from_slice(&cid.hash().digest()[0..32]); - let transaction = match self.client.indexed_transaction(&hash) { - Ok(ex) => ex, - Err(e) => { - error!(target: LOG_TARGET, "Error retrieving transaction {}: {}", hash, e); - None - }, - }; - match transaction { - Some(transaction) => { - trace!(target: LOG_TARGET, "Found CID {:?}, hash {:?}", cid, hash); - if entry.want_type == WantType::Block as i32 { - let prefix = Prefix { - version: cid.version(), - codec: cid.codec(), - mh_type: cid.hash().code(), - mh_len: cid.hash().size(), - }; - response - .payload - .push(MessageBlock { prefix: prefix.to_bytes(), data: transaction }); - } else { - response.block_presences.push(BlockPresence { - r#type: BlockPresenceType::Have as i32, - cid: cid.to_bytes(), - }); - } - }, - None => { - trace!(target: LOG_TARGET, "Missing CID {:?}, hash {:?}", cid, hash); - if entry.send_dont_have { - response.block_presences.push(BlockPresence { - r#type: BlockPresenceType::DontHave as i32, - cid: cid.to_bytes(), - }); - } - }, - } - } - trace!(target: LOG_TARGET, "Response: {:?}", response); - self.ready_blocks.push_back((peer, response)); - } - - fn poll(&mut self, _ctx: &mut Context, _: &mut impl PollParameters) -> Poll< - NetworkBehaviourAction< - <::Handler as ProtocolsHandler>::InEvent, - Self::OutEvent, - >, - >{ - if let Some((peer_id, message)) = self.ready_blocks.pop_front() { - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event: message, - }) - } - Poll::Pending - } -} - -/// Bitswap protocol error. -#[derive(derive_more::Display, derive_more::From)] -pub enum BitswapError { - /// Protobuf decoding error. - #[display(fmt = "Failed to decode request: {}.", _0)] - DecodeProto(prost::DecodeError), - /// Protobuf encoding error. - #[display(fmt = "Failed to encode response: {}.", _0)] - EncodeProto(prost::EncodeError), - /// Client backend error. - Client(sp_blockchain::Error), - /// Error parsing CID - BadCid(cid::Error), - /// Packet read error. - Read(io::Error), - /// Error sending response. - #[display(fmt = "Failed to send response.")] - SendResponse, -} diff --git a/client/network/src/chain.rs b/client/network/src/chain.rs deleted file mode 100644 index 7c131dd75370f..0000000000000 --- a/client/network/src/chain.rs +++ /dev/null @@ -1,48 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Blockchain access trait - -use sc_client_api::{BlockBackend, ProofProvider}; -pub use sc_client_api::{StorageData, StorageKey}; -pub use sc_consensus::ImportedState; -use sp_blockchain::{Error, HeaderBackend, HeaderMetadata}; -use sp_runtime::traits::{Block as BlockT, BlockIdTo}; - -/// Local client abstraction for the network. -pub trait Client: - HeaderBackend - + ProofProvider - + BlockIdTo - + BlockBackend - + HeaderMetadata - + Send - + Sync -{ -} - -impl Client for T where - T: HeaderBackend - + ProofProvider - + BlockIdTo - + BlockBackend - + HeaderMetadata - + Send - + Sync -{ -} diff --git a/client/network/src/config.rs b/client/network/src/config.rs index d08e29ef8589f..52993e2519400 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,37 +21,31 @@ //! The [`Params`] struct is the struct that must be passed in order to initialize the networking. //! See the documentation of [`Params`]. -pub use crate::{ - chain::Client, - on_demand_layer::{AlwaysBadChecker, OnDemand}, +pub use sc_network_common::{ + config::ProtocolId, + protocol::role::Role, request_responses::{ IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, }, - warp_request_handler::WarpSyncProvider, + sync::warp::WarpSyncProvider, + ExHashT, }; -pub use libp2p::{build_multiaddr, core::PublicKey, identity}; - -// Note: this re-export shouldn't be part of the public API of the crate and will be removed in -// the future. -#[doc(hidden)] -pub use crate::protocol::ProtocolConfig; -use crate::ExHashT; +pub use libp2p::{build_multiaddr, core::PublicKey, identity}; +use crate::ChainSyncInterface; use core::{fmt, iter}; -use futures::future; use libp2p::{ identity::{ed25519, Keypair}, - multiaddr, Multiaddr, PeerId, + multiaddr, Multiaddr, }; use prometheus_endpoint::Registry; -use sc_consensus::ImportQueue; -use sp_consensus::block_validation::BlockAnnounceValidator; +use sc_network_common::{ + config::{MultiaddrWithPeerId, NonDefaultSetConfig, SetConfig, TransportConfig}, + sync::ChainSync, +}; use sp_runtime::traits::Block as BlockT; use std::{ - borrow::Cow, - collections::HashMap, - convert::TryFrom, error::Error, fs, future::Future, @@ -59,332 +53,52 @@ use std::{ net::Ipv4Addr, path::{Path, PathBuf}, pin::Pin, - str, - str::FromStr, sync::Arc, }; use zeroize::Zeroize; /// Network initialization parameters. -pub struct Params { +pub struct Params +where + B: BlockT + 'static, +{ /// Assigned role for our node (full, light, ...). pub role: Role, - /// How to spawn background tasks. If you pass `None`, then a threads pool will be used by - /// default. - pub executor: Option + Send>>) + Send>>, - - /// How to spawn the background task dedicated to the transactions handler. - pub transactions_handler_executor: Box + Send>>) + Send>, + /// How to spawn background tasks. + pub executor: Box + Send>>) + Send>, /// Network layer configuration. pub network_config: NetworkConfiguration, /// Client that contains the blockchain. - pub chain: Arc>, + pub chain: Arc, - /// The `OnDemand` object acts as a "receiver" for block data requests from the client. - /// If `Some`, the network worker will process these requests and answer them. - /// Normally used only for light clients. - pub on_demand: Option>>, - - /// Pool of transactions. - /// - /// The network worker will fetch transactions from this object in order to propagate them on - /// the network. - pub transaction_pool: Arc>, - - /// Name of the protocol to use on the wire. Should be different for each chain. + /// Legacy name of the protocol to use on the wire. Should be different for each chain. pub protocol_id: ProtocolId, - /// Import queue to use. - /// - /// The import queue is the component that verifies that blocks received from other nodes are - /// valid. - pub import_queue: Box>, + /// Fork ID to distinguish protocols of different hard forks. Part of the standard protocol + /// name on the wire. + pub fork_id: Option, - /// Type to check incoming block announcements. - pub block_announce_validator: Box + Send>, + /// Instance of chain sync implementation. + pub chain_sync: Box>, + + /// Interface that can be used to delegate syncing-related function calls to `ChainSync` + pub chain_sync_service: Box>, /// Registry for recording prometheus metrics to. pub metrics_registry: Option, - /// Request response configuration for the block request protocol. - /// - /// [`RequestResponseConfig::name`] is used to tag outgoing block requests with the correct - /// protocol name. In addition all of [`RequestResponseConfig`] is used to handle incoming - /// block requests, if enabled. - /// - /// Can be constructed either via [`crate::block_request_handler::generate_protocol_config`] - /// allowing outgoing but not incoming requests, or constructed via - /// [`crate::block_request_handler::BlockRequestHandler::new`] allowing both outgoing and - /// incoming requests. - pub block_request_protocol_config: RequestResponseConfig, - - /// Request response configuration for the light client request protocol. - /// - /// Can be constructed either via - /// [`crate::light_client_requests::generate_protocol_config`] allowing outgoing but not - /// incoming requests, or constructed via - /// [`crate::light_client_requests::handler::LightClientRequestHandler::new`] allowing - /// both outgoing and incoming requests. - pub light_client_request_protocol_config: RequestResponseConfig, - - /// Request response configuration for the state request protocol. - /// - /// Can be constructed either via - /// [`crate::block_request_handler::generate_protocol_config`] allowing outgoing but not - /// incoming requests, or constructed via - /// [`crate::state_request_handler::StateRequestHandler::new`] allowing - /// both outgoing and incoming requests. - pub state_request_protocol_config: RequestResponseConfig, - - /// Optional warp sync protocol support. Include protocol config and sync provider. - pub warp_sync: Option<(Arc>, RequestResponseConfig)>, -} - -/// Role of the local node. -#[derive(Debug, Clone)] -pub enum Role { - /// Regular full node. - Full, - /// Regular light node. - Light, - /// Actual authority. - Authority, -} - -impl Role { - /// True for `Role::Authority` - pub fn is_authority(&self) -> bool { - matches!(self, Role::Authority { .. }) - } - - /// True for `Role::Light` - pub fn is_light(&self) -> bool { - matches!(self, Role::Light { .. }) - } -} - -impl fmt::Display for Role { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Full => write!(f, "FULL"), - Self::Light => write!(f, "LIGHT"), - Self::Authority { .. } => write!(f, "AUTHORITY"), - } - } -} - -/// Result of the transaction import. -#[derive(Clone, Copy, Debug)] -pub enum TransactionImport { - /// Transaction is good but already known by the transaction pool. - KnownGood, - /// Transaction is good and not yet known. - NewGood, - /// Transaction is invalid. - Bad, - /// Transaction import was not performed. - None, -} - -/// Future resolving to transaction import result. -pub type TransactionImportFuture = Pin + Send>>; - -/// Transaction pool interface -pub trait TransactionPool: Send + Sync { - /// Get transactions from the pool that are ready to be propagated. - fn transactions(&self) -> Vec<(H, B::Extrinsic)>; - /// Get hash of transaction. - fn hash_of(&self, transaction: &B::Extrinsic) -> H; - /// Import a transaction into the pool. - /// - /// This will return future. - fn import(&self, transaction: B::Extrinsic) -> TransactionImportFuture; - /// Notify the pool about transactions broadcast. - fn on_broadcasted(&self, propagations: HashMap>); - /// Get transaction by hash. - fn transaction(&self, hash: &H) -> Option; -} - -/// Dummy implementation of the [`TransactionPool`] trait for a transaction pool that is always -/// empty and discards all incoming transactions. -/// -/// Requires the "hash" type to implement the `Default` trait. -/// -/// Useful for testing purposes. -pub struct EmptyTransactionPool; - -impl TransactionPool for EmptyTransactionPool { - fn transactions(&self) -> Vec<(H, B::Extrinsic)> { - Vec::new() - } - - fn hash_of(&self, _transaction: &B::Extrinsic) -> H { - Default::default() - } - - fn import(&self, _transaction: B::Extrinsic) -> TransactionImportFuture { - Box::pin(future::ready(TransactionImport::KnownGood)) - } - - fn on_broadcasted(&self, _: HashMap>) {} - - fn transaction(&self, _h: &H) -> Option { - None - } -} - -/// Name of a protocol, transmitted on the wire. Should be unique for each chain. Always UTF-8. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct ProtocolId(smallvec::SmallVec<[u8; 6]>); - -impl<'a> From<&'a str> for ProtocolId { - fn from(bytes: &'a str) -> ProtocolId { - Self(bytes.as_bytes().into()) - } -} - -impl AsRef for ProtocolId { - fn as_ref(&self) -> &str { - str::from_utf8(&self.0[..]) - .expect("the only way to build a ProtocolId is through a UTF-8 String; qed") - } -} - -impl fmt::Debug for ProtocolId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self.as_ref(), f) - } -} - -/// Parses a string address and splits it into Multiaddress and PeerId, if -/// valid. -/// -/// # Example -/// -/// ``` -/// # use sc_network::{Multiaddr, PeerId, config::parse_str_addr}; -/// let (peer_id, addr) = parse_str_addr( -/// "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV" -/// ).unwrap(); -/// assert_eq!(peer_id, "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".parse::().unwrap()); -/// assert_eq!(addr, "/ip4/198.51.100.19/tcp/30333".parse::().unwrap()); -/// ``` -pub fn parse_str_addr(addr_str: &str) -> Result<(PeerId, Multiaddr), ParseErr> { - let addr: Multiaddr = addr_str.parse()?; - parse_addr(addr) -} - -/// Splits a Multiaddress into a Multiaddress and PeerId. -pub fn parse_addr(mut addr: Multiaddr) -> Result<(PeerId, Multiaddr), ParseErr> { - let who = match addr.pop() { - Some(multiaddr::Protocol::P2p(key)) => - PeerId::from_multihash(key).map_err(|_| ParseErr::InvalidPeerId)?, - _ => return Err(ParseErr::PeerIdMissing), - }; - - Ok((who, addr)) -} - -/// Address of a node, including its identity. -/// -/// This struct represents a decoded version of a multiaddress that ends with `/p2p/`. -/// -/// # Example -/// -/// ``` -/// # use sc_network::{Multiaddr, PeerId, config::MultiaddrWithPeerId}; -/// let addr: MultiaddrWithPeerId = -/// "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".parse().unwrap(); -/// assert_eq!(addr.peer_id.to_base58(), "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"); -/// assert_eq!(addr.multiaddr.to_string(), "/ip4/198.51.100.19/tcp/30333"); -/// ``` -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(try_from = "String", into = "String")] -pub struct MultiaddrWithPeerId { - /// Address of the node. - pub multiaddr: Multiaddr, - /// Its identity. - pub peer_id: PeerId, -} - -impl MultiaddrWithPeerId { - /// Concatenates the multiaddress and peer ID into one multiaddress containing both. - pub fn concat(&self) -> Multiaddr { - let proto = multiaddr::Protocol::P2p(From::from(self.peer_id)); - self.multiaddr.clone().with(proto) - } -} - -impl fmt::Display for MultiaddrWithPeerId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.concat(), f) - } -} - -impl FromStr for MultiaddrWithPeerId { - type Err = ParseErr; - - fn from_str(s: &str) -> Result { - let (peer_id, multiaddr) = parse_str_addr(s)?; - Ok(MultiaddrWithPeerId { peer_id, multiaddr }) - } -} - -impl From for String { - fn from(ma: MultiaddrWithPeerId) -> String { - format!("{}", ma) - } -} - -impl TryFrom for MultiaddrWithPeerId { - type Error = ParseErr; - fn try_from(string: String) -> Result { - string.parse() - } -} - -/// Error that can be generated by `parse_str_addr`. -#[derive(Debug)] -pub enum ParseErr { - /// Error while parsing the multiaddress. - MultiaddrParse(multiaddr::Error), - /// Multihash of the peer ID is invalid. - InvalidPeerId, - /// The peer ID is missing from the address. - PeerIdMissing, -} - -impl fmt::Display for ParseErr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::MultiaddrParse(err) => write!(f, "{}", err), - Self::InvalidPeerId => write!(f, "Peer id at the end of the address is invalid"), - Self::PeerIdMissing => write!(f, "Peer id is missing from the address"), - } - } -} + /// Block announce protocol configuration + pub block_announce_config: NonDefaultSetConfig, -impl std::error::Error for ParseErr { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::MultiaddrParse(err) => Some(err), - Self::InvalidPeerId => None, - Self::PeerIdMissing => None, - } - } + /// Request response protocol configurations + pub request_response_protocol_configs: Vec, } -impl From for ParseErr { - fn from(err: multiaddr::Error) -> ParseErr { - Self::MultiaddrParse(err) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] /// Sync operation mode. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum SyncMode { /// Full block download and verification. Full, @@ -399,6 +113,18 @@ pub enum SyncMode { Warp, } +impl SyncMode { + /// Returns if `self` is [`Self::Warp`]. + pub fn is_warp(&self) -> bool { + matches!(self, Self::Warp) + } + + /// Returns if `self` is [`Self::Fast`]. + pub fn is_fast(&self) -> bool { + matches!(self, Self::Fast { .. }) + } +} + impl Default for SyncMode { fn default() -> Self { Self::Full @@ -422,6 +148,11 @@ pub struct NetworkConfiguration { pub request_response_protocols: Vec, /// Configuration for the default set of nodes used for block syncing and transactions. pub default_peers_set: SetConfig, + /// Number of substreams to reserve for full nodes for block syncing and transactions. + /// Any other slot will be dedicated to light nodes. + /// + /// This value is implicitly capped to `default_set.out_peers + default_set.in_peers`. + pub default_peers_set_num_full: u32, /// Configuration for extra sets of nodes. pub extra_sets: Vec, /// Client identifier. Sent over the wire for debugging purposes. @@ -479,6 +210,7 @@ impl NetworkConfiguration { node_key: NodeKeyConfig, net_config_path: Option, ) -> Self { + let default_peers_set = SetConfig::default(); Self { net_config_path, listen_addresses: Vec::new(), @@ -486,7 +218,8 @@ impl NetworkConfiguration { boot_nodes: Vec::new(), node_key, request_response_protocols: Vec::new(), - default_peers_set: Default::default(), + default_peers_set_num_full: default_peers_set.in_peers + default_peers_set.out_peers, + default_peers_set, extra_sets: Vec::new(), client_version: client_version.into(), node_name: node_name.into(), @@ -532,125 +265,6 @@ impl NetworkConfiguration { } } -/// Configuration for a set of nodes. -#[derive(Clone, Debug)] -pub struct SetConfig { - /// Maximum allowed number of incoming substreams related to this set. - pub in_peers: u32, - /// Number of outgoing substreams related to this set that we're trying to maintain. - pub out_peers: u32, - /// List of reserved node addresses. - pub reserved_nodes: Vec, - /// Whether nodes that aren't in [`SetConfig::reserved_nodes`] are accepted or automatically - /// refused. - pub non_reserved_mode: NonReservedPeerMode, -} - -impl Default for SetConfig { - fn default() -> Self { - Self { - in_peers: 25, - out_peers: 75, - reserved_nodes: Vec::new(), - non_reserved_mode: NonReservedPeerMode::Accept, - } - } -} - -/// Extension to [`SetConfig`] for sets that aren't the default set. -/// -/// > **Note**: As new fields might be added in the future, please consider using the `new` method -/// > and modifiers instead of creating this struct manually. -#[derive(Clone, Debug)] -pub struct NonDefaultSetConfig { - /// Name of the notifications protocols of this set. A substream on this set will be - /// considered established once this protocol is open. - /// - /// > **Note**: This field isn't present for the default set, as this is handled internally - /// > by the networking code. - pub notifications_protocol: Cow<'static, str>, - /// If the remote reports that it doesn't support the protocol indicated in the - /// `notifications_protocol` field, then each of these fallback names will be tried one by - /// one. - /// - /// If a fallback is used, it will be reported in - /// [`crate::Event::NotificationStreamOpened::negotiated_fallback`]. - pub fallback_names: Vec>, - /// Maximum allowed size of single notifications. - pub max_notification_size: u64, - /// Base configuration. - pub set_config: SetConfig, -} - -impl NonDefaultSetConfig { - /// Creates a new [`NonDefaultSetConfig`]. Zero slots and accepts only reserved nodes. - pub fn new(notifications_protocol: Cow<'static, str>, max_notification_size: u64) -> Self { - Self { - notifications_protocol, - max_notification_size, - fallback_names: Vec::new(), - set_config: SetConfig { - in_peers: 0, - out_peers: 0, - reserved_nodes: Vec::new(), - non_reserved_mode: NonReservedPeerMode::Deny, - }, - } - } - - /// Modifies the configuration to allow non-reserved nodes. - pub fn allow_non_reserved(&mut self, in_peers: u32, out_peers: u32) { - self.set_config.in_peers = in_peers; - self.set_config.out_peers = out_peers; - self.set_config.non_reserved_mode = NonReservedPeerMode::Accept; - } - - /// Add a node to the list of reserved nodes. - pub fn add_reserved(&mut self, peer: MultiaddrWithPeerId) { - self.set_config.reserved_nodes.push(peer); - } -} - -/// Configuration for the transport layer. -#[derive(Clone, Debug)] -pub enum TransportConfig { - /// Normal transport mode. - Normal { - /// If true, the network will use mDNS to discover other libp2p nodes on the local network - /// and connect to them if they support the same chain. - enable_mdns: bool, - - /// If true, allow connecting to private IPv4 addresses (as defined in - /// [RFC1918](https://tools.ietf.org/html/rfc1918)). Irrelevant for addresses that have - /// been passed in [`NetworkConfiguration::boot_nodes`]. - allow_private_ipv4: bool, - }, - - /// Only allow connections within the same process. - /// Only addresses of the form `/memory/...` will be supported. - MemoryOnly, -} - -/// The policy for connections to non-reserved peers. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum NonReservedPeerMode { - /// Accept them. This is the default. - Accept, - /// Deny them. - Deny, -} - -impl NonReservedPeerMode { - /// Attempt to parse the peer mode from a string. - pub fn parse(s: &str) -> Option { - match s { - "accept" => Some(Self::Accept), - "deny" => Some(Self::Deny), - _ => None, - } - } -} - /// The configuration of a node's secret key, describing the type of key /// and how it is obtained. A node's identity keypair is the result of /// the evaluation of the node key configuration. @@ -716,7 +330,7 @@ impl NodeKeyConfig { f, |mut b| match String::from_utf8(b.to_vec()).ok().and_then(|s| { if s.len() == 64 { - hex::decode(&s).ok() + array_bytes::hex2bytes(&s).ok() } else { None } @@ -800,11 +414,8 @@ mod tests { } fn secret_bytes(kp: &Keypair) -> Vec { - match kp { - Keypair::Ed25519(p) => p.secret().as_ref().iter().cloned().collect(), - Keypair::Secp256k1(p) => p.secret().to_bytes().to_vec(), - _ => panic!("Unexpected keypair."), - } + let Keypair::Ed25519(p) = kp; + p.secret().as_ref().iter().cloned().collect() } #[test] diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index 71e46f73234c7..13b153be11d59 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -46,14 +46,14 @@ //! active mechanism that asks nodes for the addresses they are listening on. Whenever we learn //! of a node's address, you must call `add_self_reported_address`. -use crate::{config::ProtocolId, utils::LruHashSet}; +use array_bytes::bytes2hex; use futures::prelude::*; use futures_timer::Delay; use ip_network::IpNetwork; use libp2p::{ core::{ - connection::{ConnectionId, ListenerId}, - ConnectedPoint, Multiaddr, PeerId, PublicKey, + connection::ConnectionId, transport::ListenerId, ConnectedPoint, Multiaddr, PeerId, + PublicKey, }, kad::{ handler::KademliaHandlerProto, @@ -64,14 +64,16 @@ use libp2p::{ GetClosestPeersError, Kademlia, KademliaBucketInserts, KademliaConfig, KademliaEvent, QueryId, QueryResult, Quorum, Record, }, - mdns::{Mdns, MdnsConfig, MdnsEvent}, + mdns::{MdnsConfig, MdnsEvent, TokioMdns}, multiaddr::Protocol, swarm::{ - protocols_handler::multi::IntoMultiHandler, IntoProtocolsHandler, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, ProtocolsHandler, + behaviour::toggle::{Toggle, ToggleIntoConnectionHandler}, + ConnectionHandler, DialError, IntoConnectionHandler, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, }, }; -use log::{debug, error, info, trace, warn}; +use log::{debug, info, trace, warn}; +use sc_network_common::{config::ProtocolId, utils::LruHashSet}; use sp_core::hexdisplay::HexDisplay; use std::{ cmp, @@ -89,8 +91,8 @@ const MAX_KNOWN_EXTERNAL_ADDRESSES: usize = 32; /// `DiscoveryBehaviour` configuration. /// -/// Note: In order to discover nodes or load and store values via Kademlia one has to add at least -/// one protocol via [`DiscoveryConfig::add_protocol`]. +/// Note: In order to discover nodes or load and store values via Kademlia one has to add +/// Kademlia protocol via [`DiscoveryConfig::with_kademlia`]. pub struct DiscoveryConfig { local_peer_id: PeerId, permanent_addresses: Vec<(PeerId, Multiaddr)>, @@ -100,14 +102,14 @@ pub struct DiscoveryConfig { discovery_only_if_under_num: u64, enable_mdns: bool, kademlia_disjoint_query_paths: bool, - protocol_ids: HashSet, + kademlia_protocols: Vec>, } impl DiscoveryConfig { /// Create a default configuration with the given public key. pub fn new(local_public_key: PublicKey) -> Self { Self { - local_peer_id: local_public_key.into_peer_id(), + local_peer_id: local_public_key.to_peer_id(), permanent_addresses: Vec::new(), dht_random_walk: true, allow_private_ipv4: true, @@ -115,7 +117,7 @@ impl DiscoveryConfig { discovery_only_if_under_num: std::u64::MAX, enable_mdns: false, kademlia_disjoint_query_paths: false, - protocol_ids: HashSet::new(), + kademlia_protocols: Vec::new(), } } @@ -160,14 +162,18 @@ impl DiscoveryConfig { } /// Add discovery via Kademlia for the given protocol. - pub fn add_protocol(&mut self, id: ProtocolId) -> &mut Self { - if self.protocol_ids.contains(&id) { - warn!(target: "sub-libp2p", "Discovery already registered for protocol {:?}", id); - return self - } - - self.protocol_ids.insert(id); - + /// + /// Currently accepts `protocol_id`. This should be removed once all the nodes + /// are upgraded to genesis hash- and fork ID-based Kademlia protocol name. + pub fn with_kademlia>( + &mut self, + genesis_hash: Hash, + fork_id: Option<&str>, + protocol_id: &ProtocolId, + ) -> &mut Self { + self.kademlia_protocols = Vec::new(); + self.kademlia_protocols.push(kademlia_protocol_name(genesis_hash, fork_id)); + self.kademlia_protocols.push(legacy_kademlia_protocol_name(protocol_id)); self } @@ -189,37 +195,34 @@ impl DiscoveryConfig { discovery_only_if_under_num, enable_mdns, kademlia_disjoint_query_paths, - protocol_ids, + kademlia_protocols, } = self; - let kademlias = protocol_ids - .into_iter() - .map(|protocol_id| { - let proto_name = protocol_name_from_protocol_id(&protocol_id); - - let mut config = KademliaConfig::default(); - config.set_protocol_name(proto_name); - // By default Kademlia attempts to insert all peers into its routing table once a - // dialing attempt succeeds. In order to control which peer is added, disable the - // auto-insertion and instead add peers manually. - config.set_kbucket_inserts(KademliaBucketInserts::Manual); - config.disjoint_query_paths(kademlia_disjoint_query_paths); + let kademlia = if !kademlia_protocols.is_empty() { + let mut config = KademliaConfig::default(); + config.set_protocol_names(kademlia_protocols.into_iter().map(Into::into).collect()); + // By default Kademlia attempts to insert all peers into its routing table once a + // dialing attempt succeeds. In order to control which peer is added, disable the + // auto-insertion and instead add peers manually. + config.set_kbucket_inserts(KademliaBucketInserts::Manual); + config.disjoint_query_paths(kademlia_disjoint_query_paths); - let store = MemoryStore::new(local_peer_id); - let mut kad = Kademlia::with_config(local_peer_id, store, config); + let store = MemoryStore::new(local_peer_id); + let mut kad = Kademlia::with_config(local_peer_id, store, config); - for (peer_id, addr) in &permanent_addresses { - kad.add_address(peer_id, addr.clone()); - } + for (peer_id, addr) in &permanent_addresses { + kad.add_address(peer_id, addr.clone()); + } - (protocol_id, kad) - }) - .collect(); + Some(kad) + } else { + None + }; DiscoveryBehaviour { permanent_addresses, ephemeral_addresses: HashMap::new(), - kademlias, + kademlia: Toggle::from(kademlia), next_kad_random_query: if dht_random_walk { Some(Delay::new(Duration::new(0, 0))) } else { @@ -232,9 +235,15 @@ impl DiscoveryConfig { allow_private_ipv4, discovery_only_if_under_num, mdns: if enable_mdns { - MdnsWrapper::Instantiating(Mdns::new(MdnsConfig::default()).boxed()) + match TokioMdns::new(MdnsConfig::default()) { + Ok(mdns) => Some(mdns), + Err(err) => { + warn!(target: "sub-libp2p", "Failed to initialize mDNS: {:?}", err); + None + }, + } } else { - MdnsWrapper::Disabled + None }, allow_non_globals_in_dht, known_external_addresses: LruHashSet::new( @@ -253,10 +262,11 @@ pub struct DiscoveryBehaviour { /// Same as `permanent_addresses`, except that addresses that fail to reach a peer are /// removed. ephemeral_addresses: HashMap>, - /// Kademlia requests and answers. - kademlias: HashMap>, + /// Kademlia requests and answers. Even though it's wrapped in `Toggle`, currently + /// it's always enabled in `NetworkWorker::new()`. + kademlia: Toggle>, /// Discovers nodes on the local network. - mdns: MdnsWrapper, + mdns: Option, /// Stream that fires when we need to perform the next random Kademlia query. `None` if /// random walking is disabled. next_kad_random_query: Option, @@ -283,11 +293,11 @@ impl DiscoveryBehaviour { /// Returns the list of nodes that we know exist in the network. pub fn known_peers(&mut self) -> HashSet { let mut peers = HashSet::new(); - for k in self.kademlias.values_mut() { + if let Some(k) = self.kademlia.as_mut() { for b in k.kbuckets() { for e in b.iter() { if !peers.contains(e.node.key.preimage()) { - peers.insert(e.node.key.preimage().clone()); + peers.insert(*e.node.key.preimage()); } } } @@ -303,61 +313,61 @@ impl DiscoveryBehaviour { pub fn add_known_address(&mut self, peer_id: PeerId, addr: Multiaddr) { let addrs_list = self.ephemeral_addresses.entry(peer_id).or_default(); if !addrs_list.iter().any(|a| *a == addr) { - for k in self.kademlias.values_mut() { + if let Some(k) = self.kademlia.as_mut() { k.add_address(&peer_id, addr.clone()); } - self.pending_events.push_back(DiscoveryOut::Discovered(peer_id.clone())); + self.pending_events.push_back(DiscoveryOut::Discovered(peer_id)); addrs_list.push(addr); } } - /// Add a self-reported address of a remote peer to the k-buckets of the supported - /// DHTs (`supported_protocols`). + /// Add a self-reported address of a remote peer to the k-buckets of the DHT + /// if it has compatible `supported_protocols`. /// /// **Note**: It is important that you call this method. The discovery mechanism will not /// automatically add connecting peers to the Kademlia k-buckets. pub fn add_self_reported_address( &mut self, peer_id: &PeerId, - supported_protocols: impl Iterator>, + supported_protocols: &[impl AsRef<[u8]>], addr: Multiaddr, ) { - if !self.allow_non_globals_in_dht && !self.can_add_to_dht(&addr) { - trace!(target: "sub-libp2p", "Ignoring self-reported non-global address {} from {}.", addr, peer_id); - return - } - - let mut added = false; - for protocol in supported_protocols { - for kademlia in self.kademlias.values_mut() { - if protocol.as_ref() == kademlia.protocol_name() { - trace!( - target: "sub-libp2p", - "Adding self-reported address {} from {} to Kademlia DHT {}.", - addr, peer_id, String::from_utf8_lossy(kademlia.protocol_name()), - ); - kademlia.add_address(peer_id, addr.clone()); - added = true; - } + if let Some(kademlia) = self.kademlia.as_mut() { + if !self.allow_non_globals_in_dht && !Self::can_add_to_dht(&addr) { + trace!( + target: "sub-libp2p", + "Ignoring self-reported non-global address {} from {}.", addr, peer_id + ); + return } - } - if !added { - trace!( - target: "sub-libp2p", - "Ignoring self-reported address {} from {} as remote node is not part of any \ - Kademlia DHTs supported by the local node.", addr, peer_id, - ); + if let Some(matching_protocol) = supported_protocols + .iter() + .find(|p| kademlia.protocol_names().iter().any(|k| k.as_ref() == p.as_ref())) + { + trace!( + target: "sub-libp2p", + "Adding self-reported address {} from {} to Kademlia DHT {}.", + addr, peer_id, String::from_utf8_lossy(matching_protocol.as_ref()), + ); + kademlia.add_address(peer_id, addr.clone()); + } else { + trace!( + target: "sub-libp2p", + "Ignoring self-reported address {} from {} as remote node is not part of the \ + Kademlia DHT supported by the local node.", addr, peer_id, + ); + } } } /// Start fetching a record from the DHT. /// /// A corresponding `ValueFound` or `ValueNotFound` event will later be generated. - pub fn get_value(&mut self, key: &record::Key) { - for k in self.kademlias.values_mut() { - k.get_record(key, Quorum::One); + pub fn get_value(&mut self, key: record::Key) { + if let Some(k) = self.kademlia.as_mut() { + k.get_record(key.clone(), Quorum::One); } } @@ -366,7 +376,7 @@ impl DiscoveryBehaviour { /// /// A corresponding `ValuePut` or `ValuePutFailed` event will later be generated. pub fn put_value(&mut self, key: record::Key, value: Vec) { - for k in self.kademlias.values_mut() { + if let Some(k) = self.kademlia.as_mut() { if let Err(e) = k.put_record(Record::new(key.clone(), value.clone()), Quorum::All) { warn!(target: "sub-libp2p", "Libp2p => Failed to put record: {:?}", e); self.pending_events @@ -379,37 +389,27 @@ impl DiscoveryBehaviour { /// /// Identifies Kademlia instances by their [`ProtocolId`] and kbuckets by the base 2 logarithm /// of their lower bound. - pub fn num_entries_per_kbucket( - &mut self, - ) -> impl ExactSizeIterator)> { - self.kademlias.iter_mut().map(|(id, kad)| { - let buckets = kad - .kbuckets() + pub fn num_entries_per_kbucket(&mut self) -> Option> { + self.kademlia.as_mut().map(|kad| { + kad.kbuckets() .map(|bucket| (bucket.range().0.ilog2().unwrap_or(0), bucket.iter().count())) - .collect(); - (id, buckets) + .collect() }) } /// Returns the number of records in the Kademlia record stores. - pub fn num_kademlia_records(&mut self) -> impl ExactSizeIterator { + pub fn num_kademlia_records(&mut self) -> Option { // Note that this code is ok only because we use a `MemoryStore`. - self.kademlias.iter_mut().map(|(id, kad)| { - let num = kad.store_mut().records().count(); - (id, num) - }) + self.kademlia.as_mut().map(|kad| kad.store_mut().records().count()) } /// Returns the total size in bytes of all the records in the Kademlia record stores. - pub fn kademlia_records_total_size( - &mut self, - ) -> impl ExactSizeIterator { + pub fn kademlia_records_total_size(&mut self) -> Option { // Note that this code is ok only because we use a `MemoryStore`. If the records were // for example stored on disk, this would load every single one of them every single time. - self.kademlias.iter_mut().map(|(id, kad)| { - let size = kad.store_mut().records().fold(0, |tot, rec| tot + rec.value.len()); - (id, size) - }) + self.kademlia + .as_mut() + .map(|kad| kad.store_mut().records().fold(0, |tot, rec| tot + rec.value.len())) } /// Can the given `Multiaddr` be put into the DHT? @@ -418,7 +418,7 @@ impl DiscoveryBehaviour { // NB: Currently all DNS names are allowed and no check for TLD suffixes is done // because the set of valid domains is highly dynamic and would require frequent // updates, for example by utilising publicsuffix.org or IANA. - pub fn can_add_to_dht(&self, addr: &Multiaddr) -> bool { + pub fn can_add_to_dht(addr: &Multiaddr) -> bool { let ip = match addr.iter().next() { Some(Protocol::Ip4(ip)) => IpNetwork::from(ip), Some(Protocol::Ip6(ip)) => IpNetwork::from(ip), @@ -468,28 +468,18 @@ pub enum DiscoveryOut { /// Returning the corresponding key as well as the request duration. ValuePutFailed(record::Key, Duration), - /// Started a random Kademlia query for each DHT identified by the given `ProtocolId`s. + /// Started a random Kademlia query. /// /// Only happens if [`DiscoveryConfig::with_dht_random_walk`] has been configured to `true`. - RandomKademliaStarted(Vec), + RandomKademliaStarted, } impl NetworkBehaviour for DiscoveryBehaviour { - type ProtocolsHandler = IntoMultiHandler>; + type ConnectionHandler = ToggleIntoConnectionHandler>; type OutEvent = DiscoveryOut; - fn new_handler(&mut self) -> Self::ProtocolsHandler { - let iter = self - .kademlias - .iter_mut() - .map(|(p, k)| (p.clone(), NetworkBehaviour::new_handler(k))); - - IntoMultiHandler::try_from_iter(iter).expect( - "There can be at most one handler per `ProtocolId` and protocol names contain the \ - `ProtocolId` so no two protocol names in `self.kademlias` can be equal which is the \ - only error `try_from_iter` can return, therefore this call is guaranteed to succeed; \ - qed", - ) + fn new_handler(&mut self) -> Self::ConnectionHandler { + self.kademlia.new_handler() } fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { @@ -504,22 +494,17 @@ impl NetworkBehaviour for DiscoveryBehaviour { } { - let mut list_to_filter = Vec::new(); - for k in self.kademlias.values_mut() { - list_to_filter.extend(k.addresses_of_peer(peer_id)) - } + let mut list_to_filter = self.kademlia.addresses_of_peer(peer_id); - list_to_filter.extend(self.mdns.addresses_of_peer(peer_id)); + if let Some(ref mut mdns) = self.mdns { + list_to_filter.extend(mdns.addresses_of_peer(peer_id)); + } if !self.allow_private_ipv4 { - list_to_filter.retain(|addr| { - if let Some(Protocol::Ip4(addr)) = addr.iter().next() { - if addr.is_private() { - return false - } - } - - true + list_to_filter.retain(|addr| match addr.iter().next() { + Some(Protocol::Ip4(addr)) if !IpNetwork::from(addr).is_global() => false, + Some(Protocol::Ip6(addr)) if !IpNetwork::from(addr).is_global() => false, + _ => true, }); } @@ -531,22 +516,32 @@ impl NetworkBehaviour for DiscoveryBehaviour { list } + fn inject_address_change( + &mut self, + peer_id: &PeerId, + connection_id: &ConnectionId, + old: &ConnectedPoint, + new: &ConnectedPoint, + ) { + self.kademlia.inject_address_change(peer_id, connection_id, old, new) + } + fn inject_connection_established( &mut self, peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + failed_addresses: Option<&Vec>, + other_established: usize, ) { self.num_connections += 1; - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_connection_established(k, peer_id, conn, endpoint) - } - } - - fn inject_connected(&mut self, peer_id: &PeerId) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_connected(k, peer_id) - } + self.kademlia.inject_connection_established( + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ) } fn inject_connection_closed( @@ -554,346 +549,280 @@ impl NetworkBehaviour for DiscoveryBehaviour { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + handler: ::Handler, + remaining_established: usize, ) { self.num_connections -= 1; - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_connection_closed(k, peer_id, conn, endpoint) - } - } - - fn inject_disconnected(&mut self, peer_id: &PeerId) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_disconnected(k, peer_id) - } + self.kademlia.inject_connection_closed( + peer_id, + conn, + endpoint, + handler, + remaining_established, + ) } - fn inject_addr_reach_failure( + fn inject_dial_failure( &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - error: &dyn std::error::Error, + peer_id: Option, + handler: Self::ConnectionHandler, + error: &DialError, ) { if let Some(peer_id) = peer_id { - if let Some(list) = self.ephemeral_addresses.get_mut(peer_id) { - list.retain(|a| a != addr); + if let DialError::Transport(errors) = error { + if let Some(list) = self.ephemeral_addresses.get_mut(&peer_id) { + for (addr, _error) in errors { + list.retain(|a| a != addr); + } + } } } - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_addr_reach_failure(k, peer_id, addr, error) - } + self.kademlia.inject_dial_failure(peer_id, handler, error) } fn inject_event( &mut self, peer_id: PeerId, connection: ConnectionId, - (pid, event): <::Handler as ProtocolsHandler>::OutEvent, + event: <::Handler as ConnectionHandler>::OutEvent, ) { - if let Some(kad) = self.kademlias.get_mut(&pid) { - return kad.inject_event(peer_id, connection, event) - } - error!( - target: "sub-libp2p", - "inject_node_event: no kademlia instance registered for protocol {:?}", - pid, - ) + self.kademlia.inject_event(peer_id, connection, event) } fn inject_new_external_addr(&mut self, addr: &Multiaddr) { let new_addr = addr.clone().with(Protocol::P2p(self.local_peer_id.into())); - // NOTE: we might re-discover the same address multiple times - // in which case we just want to refrain from logging. - if self.known_external_addresses.insert(new_addr.clone()) { - info!( - target: "sub-libp2p", - "🔍 Discovered new external address for our node: {}", - new_addr, - ); + if Self::can_add_to_dht(addr) { + // NOTE: we might re-discover the same address multiple times + // in which case we just want to refrain from logging. + if self.known_external_addresses.insert(new_addr.clone()) { + info!( + target: "sub-libp2p", + "🔍 Discovered new external address for our node: {}", + new_addr, + ); + } } - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_new_external_addr(k, addr) - } + self.kademlia.inject_new_external_addr(addr) } fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { // We intentionally don't remove the element from `known_external_addresses` in order // to not print the log line again. - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_expired_external_addr(k, addr) - } + self.kademlia.inject_expired_external_addr(addr) } fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_expired_listen_addr(k, id, addr) - } - } - - fn inject_dial_failure(&mut self, peer_id: &PeerId) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_dial_failure(k, peer_id) - } + self.kademlia.inject_expired_listen_addr(id, addr) } fn inject_new_listener(&mut self, id: ListenerId) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_new_listener(k, id) - } + self.kademlia.inject_new_listener(id) } fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_new_listen_addr(k, id, addr) - } + self.kademlia.inject_new_listen_addr(id, addr) + } + + fn inject_listen_failure(&mut self, _: &Multiaddr, _: &Multiaddr, _: Self::ConnectionHandler) { + // NetworkBehaviour::inject_listen_failure on Kademlia does nothing. } fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_listener_error(k, id, err) - } + self.kademlia.inject_listener_error(id, err) } fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &io::Error>) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_listener_closed(k, id, reason) - } + self.kademlia.inject_listener_closed(id, reason) } fn poll( &mut self, cx: &mut Context, params: &mut impl PollParameters, - ) -> Poll< - NetworkBehaviourAction< - <::Handler as ProtocolsHandler>::InEvent, - Self::OutEvent, - >, - >{ + ) -> Poll> { // Immediately process the content of `discovered`. if let Some(ev) = self.pending_events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) } // Poll the stream that fires when we need to start a random Kademlia query. - if let Some(next_kad_random_query) = self.next_kad_random_query.as_mut() { - while let Poll::Ready(_) = next_kad_random_query.poll_unpin(cx) { - let actually_started = if self.num_connections < self.discovery_only_if_under_num { - let random_peer_id = PeerId::random(); - debug!( - target: "sub-libp2p", - "Libp2p <= Starting random Kademlia request for {:?}", - random_peer_id, - ); - for k in self.kademlias.values_mut() { - k.get_closest_peers(random_peer_id); + if let Some(kademlia) = self.kademlia.as_mut() { + if let Some(next_kad_random_query) = self.next_kad_random_query.as_mut() { + while next_kad_random_query.poll_unpin(cx).is_ready() { + let actually_started = + if self.num_connections < self.discovery_only_if_under_num { + let random_peer_id = PeerId::random(); + debug!( + target: "sub-libp2p", + "Libp2p <= Starting random Kademlia request for {:?}", + random_peer_id, + ); + kademlia.get_closest_peers(random_peer_id); + true + } else { + debug!( + target: "sub-libp2p", + "Kademlia paused due to high number of connections ({})", + self.num_connections + ); + false + }; + + // Schedule the next random query with exponentially increasing delay, + // capped at 60 seconds. + *next_kad_random_query = Delay::new(self.duration_to_next_kad); + self.duration_to_next_kad = + cmp::min(self.duration_to_next_kad * 2, Duration::from_secs(60)); + + if actually_started { + let ev = DiscoveryOut::RandomKademliaStarted; + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) } - true - } else { - debug!( - target: "sub-libp2p", - "Kademlia paused due to high number of connections ({})", - self.num_connections - ); - false - }; - - // Schedule the next random query with exponentially increasing delay, - // capped at 60 seconds. - *next_kad_random_query = Delay::new(self.duration_to_next_kad); - self.duration_to_next_kad = - cmp::min(self.duration_to_next_kad * 2, Duration::from_secs(60)); - - if actually_started { - let ev = DiscoveryOut::RandomKademliaStarted( - self.kademlias.keys().cloned().collect(), - ); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) } } } - // Poll Kademlias. - for (pid, kademlia) in &mut self.kademlias { - while let Poll::Ready(ev) = kademlia.poll(cx, params) { - match ev { - NetworkBehaviourAction::GenerateEvent(ev) => match ev { - KademliaEvent::RoutingUpdated { peer, .. } => { - let ev = DiscoveryOut::Discovered(peer); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - }, - KademliaEvent::UnroutablePeer { peer, .. } => { - let ev = DiscoveryOut::UnroutablePeer(peer); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - }, - KademliaEvent::RoutablePeer { peer, .. } => { - let ev = DiscoveryOut::Discovered(peer); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - }, - KademliaEvent::PendingRoutablePeer { .. } => { - // We are not interested in this event at the moment. + while let Poll::Ready(ev) = self.kademlia.poll(cx, params) { + match ev { + NetworkBehaviourAction::GenerateEvent(ev) => match ev { + KademliaEvent::RoutingUpdated { peer, .. } => { + let ev = DiscoveryOut::Discovered(peer); + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + }, + KademliaEvent::UnroutablePeer { peer, .. } => { + let ev = DiscoveryOut::UnroutablePeer(peer); + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + }, + KademliaEvent::RoutablePeer { peer, .. } => { + let ev = DiscoveryOut::Discovered(peer); + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + }, + KademliaEvent::PendingRoutablePeer { .. } | + KademliaEvent::InboundRequest { .. } => { + // We are not interested in this event at the moment. + }, + KademliaEvent::OutboundQueryCompleted { + result: QueryResult::GetClosestPeers(res), + .. + } => match res { + Err(GetClosestPeersError::Timeout { key, peers }) => { + debug!( + target: "sub-libp2p", + "Libp2p => Query for {:?} timed out with {} results", + HexDisplay::from(&key), peers.len(), + ); }, - KademliaEvent::OutboundQueryCompleted { - result: QueryResult::GetClosestPeers(res), - .. - } => match res { - Err(GetClosestPeersError::Timeout { key, peers }) => { + Ok(ok) => { + trace!( + target: "sub-libp2p", + "Libp2p => Query for {:?} yielded {:?} results", + HexDisplay::from(&ok.key), ok.peers.len(), + ); + if ok.peers.is_empty() && self.num_connections != 0 { debug!( target: "sub-libp2p", - "Libp2p => Query for {:?} timed out with {} results", - HexDisplay::from(&key), peers.len(), + "Libp2p => Random Kademlia query has yielded empty results", ); - }, + } + }, + }, + KademliaEvent::OutboundQueryCompleted { + result: QueryResult::GetRecord(res), + stats, + .. + } => { + let ev = match res { Ok(ok) => { + let results = ok + .records + .into_iter() + .map(|r| (r.record.key, r.record.value)) + .collect(); + + DiscoveryOut::ValueFound( + results, + stats.duration().unwrap_or_default(), + ) + }, + Err(e @ libp2p::kad::GetRecordError::NotFound { .. }) => { trace!( target: "sub-libp2p", - "Libp2p => Query for {:?} yielded {:?} results", - HexDisplay::from(&ok.key), ok.peers.len(), + "Libp2p => Failed to get record: {:?}", + e, ); - if ok.peers.is_empty() && self.num_connections != 0 { - debug!( - target: "sub-libp2p", - "Libp2p => Random Kademlia query has yielded empty results", - ); - } + DiscoveryOut::ValueNotFound( + e.into_key(), + stats.duration().unwrap_or_default(), + ) }, - }, - KademliaEvent::OutboundQueryCompleted { - result: QueryResult::GetRecord(res), - stats, - .. - } => { - let ev = match res { - Ok(ok) => { - let results = ok - .records - .into_iter() - .map(|r| (r.record.key, r.record.value)) - .collect(); - - DiscoveryOut::ValueFound( - results, - stats.duration().unwrap_or_else(Default::default), - ) - }, - Err(e @ libp2p::kad::GetRecordError::NotFound { .. }) => { - trace!( - target: "sub-libp2p", - "Libp2p => Failed to get record: {:?}", - e, - ); - DiscoveryOut::ValueNotFound( - e.into_key(), - stats.duration().unwrap_or_else(Default::default), - ) - }, - Err(e) => { - debug!( - target: "sub-libp2p", - "Libp2p => Failed to get record: {:?}", - e, - ); - DiscoveryOut::ValueNotFound( - e.into_key(), - stats.duration().unwrap_or_else(Default::default), - ) - }, - }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - }, - KademliaEvent::OutboundQueryCompleted { - result: QueryResult::PutRecord(res), - stats, - .. - } => { - let ev = match res { - Ok(ok) => DiscoveryOut::ValuePut( - ok.key, - stats.duration().unwrap_or_else(Default::default), - ), - Err(e) => { - debug!( - target: "sub-libp2p", - "Libp2p => Failed to put record: {:?}", - e, - ); - DiscoveryOut::ValuePutFailed( - e.into_key(), - stats.duration().unwrap_or_else(Default::default), - ) - }, - }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - }, - KademliaEvent::OutboundQueryCompleted { - result: QueryResult::RepublishRecord(res), - .. - } => match res { - Ok(ok) => debug!( - target: "sub-libp2p", - "Libp2p => Record republished: {:?}", - ok.key, - ), - Err(e) => debug!( - target: "sub-libp2p", - "Libp2p => Republishing of record {:?} failed with: {:?}", - e.key(), e, - ), - }, - // We never start any other type of query. - e => { - debug!(target: "sub-libp2p", "Libp2p => Unhandled Kademlia event: {:?}", e) - }, + Err(e) => { + debug!( + target: "sub-libp2p", + "Libp2p => Failed to get record: {:?}", + e, + ); + DiscoveryOut::ValueNotFound( + e.into_key(), + stats.duration().unwrap_or_default(), + ) + }, + }; + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) }, - NetworkBehaviourAction::DialAddress { address } => - return Poll::Ready(NetworkBehaviourAction::DialAddress { address }), - NetworkBehaviourAction::DialPeer { peer_id, condition } => - return Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition }), - NetworkBehaviourAction::NotifyHandler { peer_id, handler, event } => - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler, - event: (pid.clone(), event), - }), - NetworkBehaviourAction::ReportObservedAddr { address, score } => - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { - address, - score, - }), - NetworkBehaviourAction::CloseConnection { peer_id, connection } => - return Poll::Ready(NetworkBehaviourAction::CloseConnection { - peer_id, - connection, - }), - } - } - } - - // Poll mDNS. - while let Poll::Ready(ev) = self.mdns.poll(cx, params) { - match ev { - NetworkBehaviourAction::GenerateEvent(event) => match event { - MdnsEvent::Discovered(list) => { - if self.num_connections >= self.discovery_only_if_under_num { - continue - } - - self.pending_events - .extend(list.map(|(peer_id, _)| DiscoveryOut::Discovered(peer_id))); - if let Some(ev) = self.pending_events.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - } + KademliaEvent::OutboundQueryCompleted { + result: QueryResult::PutRecord(res), + stats, + .. + } => { + let ev = match res { + Ok(ok) => + DiscoveryOut::ValuePut(ok.key, stats.duration().unwrap_or_default()), + Err(e) => { + debug!( + target: "sub-libp2p", + "Libp2p => Failed to put record: {:?}", + e, + ); + DiscoveryOut::ValuePutFailed( + e.into_key(), + stats.duration().unwrap_or_default(), + ) + }, + }; + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + }, + KademliaEvent::OutboundQueryCompleted { + result: QueryResult::RepublishRecord(res), + .. + } => match res { + Ok(ok) => debug!( + target: "sub-libp2p", + "Libp2p => Record republished: {:?}", + ok.key, + ), + Err(e) => debug!( + target: "sub-libp2p", + "Libp2p => Republishing of record {:?} failed with: {:?}", + e.key(), e, + ), + }, + // We never start any other type of query. + KademliaEvent::OutboundQueryCompleted { result: e, .. } => { + warn!(target: "sub-libp2p", "Libp2p => Unhandled Kademlia event: {:?}", e) }, - MdnsEvent::Expired(_) => {}, }, - NetworkBehaviourAction::DialAddress { address } => - return Poll::Ready(NetworkBehaviourAction::DialAddress { address }), - NetworkBehaviourAction::DialPeer { peer_id, condition } => - return Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition }), - NetworkBehaviourAction::NotifyHandler { event, .. } => match event {}, /* `event` is an enum with no variant */ + NetworkBehaviourAction::Dial { opts, handler } => + return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }), + NetworkBehaviourAction::NotifyHandler { peer_id, handler, event } => + return Poll::Ready(NetworkBehaviourAction::NotifyHandler { + peer_id, + handler, + event, + }), NetworkBehaviourAction::ReportObservedAddr { address, score } => return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, @@ -907,78 +836,90 @@ impl NetworkBehaviour for DiscoveryBehaviour { } } + // Poll mDNS. + if let Some(ref mut mdns) = self.mdns { + while let Poll::Ready(ev) = mdns.poll(cx, params) { + match ev { + NetworkBehaviourAction::GenerateEvent(event) => match event { + MdnsEvent::Discovered(list) => { + if self.num_connections >= self.discovery_only_if_under_num { + continue + } + + self.pending_events + .extend(list.map(|(peer_id, _)| DiscoveryOut::Discovered(peer_id))); + if let Some(ev) = self.pending_events.pop_front() { + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + } + }, + MdnsEvent::Expired(_) => {}, + }, + NetworkBehaviourAction::Dial { .. } => { + unreachable!("mDNS never dials!"); + }, + NetworkBehaviourAction::NotifyHandler { event, .. } => match event {}, /* `event` is an enum with no variant */ + NetworkBehaviourAction::ReportObservedAddr { address, score } => + return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { + address, + score, + }), + NetworkBehaviourAction::CloseConnection { peer_id, connection } => + return Poll::Ready(NetworkBehaviourAction::CloseConnection { + peer_id, + connection, + }), + } + } + } + Poll::Pending } } -// NB: If this protocol name derivation is changed, check if -// `DiscoveryBehaviour::new_handler` is still correct. -fn protocol_name_from_protocol_id(id: &ProtocolId) -> Vec { +/// Legacy (fallback) Kademlia protocol name based on `protocol_id`. +fn legacy_kademlia_protocol_name(id: &ProtocolId) -> Vec { let mut v = vec![b'/']; v.extend_from_slice(id.as_ref().as_bytes()); v.extend_from_slice(b"/kad"); v } -/// [`Mdns::new`] returns a future. Instead of forcing [`DiscoveryConfig::finish`] and all its -/// callers to be async, lazily instantiate [`Mdns`]. -enum MdnsWrapper { - Instantiating(futures::future::BoxFuture<'static, std::io::Result>), - Ready(Mdns), - Disabled, -} - -impl MdnsWrapper { - fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { - match self { - Self::Instantiating(_) => Vec::new(), - Self::Ready(mdns) => mdns.addresses_of_peer(peer_id), - Self::Disabled => Vec::new(), - } - } - - fn poll( - &mut self, - cx: &mut Context<'_>, - params: &mut impl PollParameters, - ) -> Poll> { - loop { - match self { - Self::Instantiating(fut) => - *self = match futures::ready!(fut.as_mut().poll(cx)) { - Ok(mdns) => Self::Ready(mdns), - Err(err) => { - warn!(target: "sub-libp2p", "Failed to initialize mDNS: {:?}", err); - Self::Disabled - }, - }, - Self::Ready(mdns) => return mdns.poll(cx, params), - Self::Disabled => return Poll::Pending, - } - } +/// Kademlia protocol name based on `genesis_hash` and `fork_id`. +fn kademlia_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> Vec { + let genesis_hash_hex = bytes2hex("", genesis_hash.as_ref()); + if let Some(fork_id) = fork_id { + format!("/{}/{}/kad", genesis_hash_hex, fork_id).as_bytes().into() + } else { + format!("/{}/kad", genesis_hash_hex).as_bytes().into() } } #[cfg(test)] mod tests { - use super::{protocol_name_from_protocol_id, DiscoveryConfig, DiscoveryOut}; - use crate::config::ProtocolId; + use super::{ + kademlia_protocol_name, legacy_kademlia_protocol_name, DiscoveryConfig, DiscoveryOut, + }; use futures::prelude::*; use libp2p::{ core::{ transport::{MemoryTransport, Transport}, upgrade, }, - identity::Keypair, + identity::{ed25519, Keypair}, noise, swarm::{Swarm, SwarmEvent}, - yamux, Multiaddr, PeerId, + yamux, Multiaddr, }; + use sc_network_common::config::ProtocolId; + use sp_core::hash::H256; use std::{collections::HashSet, task::Poll}; #[test] fn discovery_working() { let mut first_swarm_peer_id_and_addr = None; + + let genesis_hash = H256::from_low_u64_be(1); + let fork_id = Some("test-fork-id"); let protocol_id = ProtocolId::from("dot"); // Build swarms whose behaviour is `DiscoveryBehaviour`, each aware of @@ -990,7 +931,7 @@ mod tests { let noise_keys = noise::Keypair::::new().into_authentic(&keypair).unwrap(); - let transport = MemoryTransport + let transport = MemoryTransport::new() .upgrade(upgrade::Version::V1) .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) .multiplex(yamux::YamuxConfig::default()) @@ -1003,18 +944,18 @@ mod tests { .allow_private_ipv4(true) .allow_non_globals_in_dht(true) .discovery_limit(50) - .add_protocol(protocol_id.clone()); + .with_kademlia(genesis_hash, fork_id, &protocol_id); config.finish() }; - let mut swarm = Swarm::new(transport, behaviour, keypair.public().into_peer_id()); + let mut swarm = Swarm::new(transport, behaviour, keypair.public().to_peer_id()); let listen_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); if i == 0 { first_swarm_peer_id_and_addr = - Some((keypair.public().into_peer_id(), listen_addr.clone())) + Some((keypair.public().to_peer_id(), listen_addr.clone())) } swarm.listen_on(listen_addr.clone()).unwrap(); @@ -1029,7 +970,7 @@ mod tests { // Skip the first swarm as all other swarms already know it. .skip(1) .filter(|p| *p != n) - .map(|p| Swarm::local_peer_id(&swarms[p].0).clone()) + .map(|p| *Swarm::local_peer_id(&swarms[p].0)) .collect::>() }) .collect::>(); @@ -1056,19 +997,25 @@ mod tests { } }) .unwrap(); + // Test both genesis hash-based and legacy + // protocol names. + let protocol_name = if swarm_n % 2 == 0 { + kademlia_protocol_name(genesis_hash, fork_id) + } else { + legacy_kademlia_protocol_name(&protocol_id) + }; swarms[swarm_n] .0 .behaviour_mut() .add_self_reported_address( &other, - [protocol_name_from_protocol_id(&protocol_id)] - .iter(), + &[protocol_name], addr, ); to_discover[swarm_n].remove(&other); }, - DiscoveryOut::RandomKademliaStarted(_) => {}, + DiscoveryOut::RandomKademliaStarted => {}, e => { panic!("Unexpected event: {:?}", e) }, @@ -1097,6 +1044,8 @@ mod tests { #[test] fn discovery_ignores_peers_with_unknown_protocols() { + let supported_genesis_hash = H256::from_low_u64_be(1); + let unsupported_genesis_hash = H256::from_low_u64_be(2); let supported_protocol_id = ProtocolId::from("a"); let unsupported_protocol_id = ProtocolId::from("b"); @@ -1107,21 +1056,37 @@ mod tests { .allow_private_ipv4(true) .allow_non_globals_in_dht(true) .discovery_limit(50) - .add_protocol(supported_protocol_id.clone()); + .with_kademlia(supported_genesis_hash, None, &supported_protocol_id); config.finish() }; - let remote_peer_id = PeerId::random(); - let remote_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); + let predictable_peer_id = |bytes: &[u8; 32]| { + Keypair::Ed25519(ed25519::Keypair::from( + ed25519::SecretKey::from_bytes(bytes.to_owned()).unwrap(), + )) + .public() + .to_peer_id() + }; + + let remote_peer_id = predictable_peer_id(b"00000000000000000000000000000001"); + let remote_addr: Multiaddr = "/memory/1".parse().unwrap(); + let another_peer_id = predictable_peer_id(b"00000000000000000000000000000002"); + let another_addr: Multiaddr = "/memory/2".parse().unwrap(); - // Add remote peer with unsupported protocol. + // Try adding remote peers with unsupported protocols. discovery.add_self_reported_address( &remote_peer_id, - [protocol_name_from_protocol_id(&unsupported_protocol_id)].iter(), + &[kademlia_protocol_name(unsupported_genesis_hash, None)], remote_addr.clone(), ); + discovery.add_self_reported_address( + &another_peer_id, + &[legacy_kademlia_protocol_name(&unsupported_protocol_id)], + another_addr.clone(), + ); - for kademlia in discovery.kademlias.values_mut() { + { + let kademlia = discovery.kademlia.as_mut().unwrap(); assert!( kademlia .kbucket(remote_peer_id) @@ -1129,75 +1094,34 @@ mod tests { .is_empty(), "Expect peer with unsupported protocol not to be added." ); - } - - // Add remote peer with supported protocol. - discovery.add_self_reported_address( - &remote_peer_id, - [protocol_name_from_protocol_id(&supported_protocol_id)].iter(), - remote_addr.clone(), - ); - - for kademlia in discovery.kademlias.values_mut() { - assert_eq!( - 1, + assert!( kademlia - .kbucket(remote_peer_id) + .kbucket(another_peer_id) .expect("Remote peer id not to be equal to local peer id.") - .num_entries(), - "Expect peer with supported protocol to be added." + .is_empty(), + "Expect peer with unsupported protocol not to be added." ); } - } - - #[test] - fn discovery_adds_peer_to_kademlia_of_same_protocol_only() { - let protocol_a = ProtocolId::from("a"); - let protocol_b = ProtocolId::from("b"); - - let mut discovery = { - let keypair = Keypair::generate_ed25519(); - let mut config = DiscoveryConfig::new(keypair.public()); - config - .allow_private_ipv4(true) - .allow_non_globals_in_dht(true) - .discovery_limit(50) - .add_protocol(protocol_a.clone()) - .add_protocol(protocol_b.clone()); - config.finish() - }; - - let remote_peer_id = PeerId::random(); - let remote_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); - // Add remote peer with `protocol_a` only. + // Add remote peers with supported protocols. discovery.add_self_reported_address( &remote_peer_id, - [protocol_name_from_protocol_id(&protocol_a)].iter(), + &[kademlia_protocol_name(supported_genesis_hash, None)], remote_addr.clone(), ); - - assert_eq!( - 1, - discovery - .kademlias - .get_mut(&protocol_a) - .expect("Kademlia instance to exist.") - .kbucket(remote_peer_id) - .expect("Remote peer id not to be equal to local peer id.") - .num_entries(), - "Expected remote peer to be added to `protocol_a` Kademlia instance.", + discovery.add_self_reported_address( + &another_peer_id, + &[legacy_kademlia_protocol_name(&supported_protocol_id)], + another_addr.clone(), ); - assert!( - discovery - .kademlias - .get_mut(&protocol_b) - .expect("Kademlia instance to exist.") - .kbucket(remote_peer_id) - .expect("Remote peer id not to be equal to local peer id.") - .is_empty(), - "Expected remote peer not to be added to `protocol_b` Kademlia instance.", - ); + { + let kademlia = discovery.kademlia.as_mut().unwrap(); + assert_eq!( + 2, + kademlia.kbuckets().fold(0, |acc, bucket| acc + bucket.num_entries()), + "Expect peers with supported protocol to be added." + ); + } } } diff --git a/client/network/src/gossip.rs b/client/network/src/gossip.rs deleted file mode 100644 index 0bc46b2164bcb..0000000000000 --- a/client/network/src/gossip.rs +++ /dev/null @@ -1,229 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Helper for sending rate-limited gossip messages. -//! -//! # Context -//! -//! The [`NetworkService`] struct provides a way to send notifications to a certain peer through -//! the [`NetworkService::notification_sender`] method. This method is quite low level and isn't -//! expected to be used directly. -//! -//! The [`QueuedSender`] struct provided by this module is built on top of -//! [`NetworkService::notification_sender`] and provides a cleaner way to send notifications. -//! -//! # Behaviour -//! -//! An instance of [`QueuedSender`] is specific to a certain combination of `PeerId` and -//! protocol name. It maintains a buffer of messages waiting to be sent out. The user of this API -//! is able to manipulate that queue, adding or removing obsolete messages. -//! -//! Creating a [`QueuedSender`] also returns a opaque `Future` whose responsibility it to -//! drain that queue and actually send the messages. If the substream with the given combination -//! of peer and protocol is closed, the queue is silently discarded. It is the role of the user -//! to track which peers we are connected to. -//! -//! In normal situations, messages sent through a [`QueuedSender`] will arrive in the same -//! order as they have been sent. -//! It is possible, in the situation of disconnects and reconnects, that messages arrive in a -//! different order. See also . -//! However, if multiple instances of [`QueuedSender`] exist for the same peer and protocol, or -//! if some other code uses the [`NetworkService`] to send notifications to this combination or -//! peer and protocol, then the notifications will be interleaved in an unpredictable way. -//! - -use crate::{ExHashT, NetworkService}; - -use async_std::sync::{Mutex, MutexGuard}; -use futures::prelude::*; -use futures::channel::mpsc::{channel, Receiver, Sender}; -use libp2p::PeerId; -use sp_runtime::traits::Block as BlockT; -use std::{ - borrow::Cow, - collections::VecDeque, - fmt, - sync::Arc, -}; - -#[cfg(test)] -mod tests; - -/// Notifications sender for a specific combination of network service, peer, and protocol. -pub struct QueuedSender { - /// Shared between the user-facing [`QueuedSender`] and the background future. - shared_message_queue: SharedMessageQueue, - /// Used to notify the background future to check for new messages in the message queue. - notify_background_future: Sender<()>, - /// Maximum number of elements in [`QueuedSender::shared_message_queue`]. - queue_size_limit: usize, -} - -impl QueuedSender { - /// Returns a new [`QueuedSender`] containing a queue of message for this specific - /// combination of peer and protocol. - /// - /// In addition to the [`QueuedSender`], also returns a `Future` whose role is to drive - /// the messages sending forward. - pub fn new( - service: Arc>, - peer_id: PeerId, - protocol: Cow<'static, str>, - queue_size_limit: usize, - messages_encode: F - ) -> (Self, impl Future + Send + 'static) - where - M: Send + 'static, - B: BlockT + 'static, - H: ExHashT, - F: Fn(M) -> Vec + Send + 'static, - { - let (notify_background_future, wait_for_sender) = channel(0); - - let shared_message_queue = Arc::new(Mutex::new( - VecDeque::with_capacity(queue_size_limit), - )); - - let background_future = create_background_future( - wait_for_sender, - service, - peer_id, - protocol, - shared_message_queue.clone(), - messages_encode - ); - - let sender = Self { - shared_message_queue, - notify_background_future, - queue_size_limit, - }; - - (sender, background_future) - } - - /// Locks the queue of messages towards this peer. - /// - /// The returned `Future` is expected to be ready quite quickly. - pub async fn lock_queue<'a>(&'a mut self) -> QueueGuard<'a, M> { - QueueGuard { - message_queue: self.shared_message_queue.lock().await, - queue_size_limit: self.queue_size_limit, - notify_background_future: &mut self.notify_background_future, - } - } - - /// Pushes a message to the queue, or discards it if the queue is full. - /// - /// The returned `Future` is expected to be ready quite quickly. - pub async fn queue_or_discard(&mut self, message: M) - where - M: Send + 'static - { - self.lock_queue().await.push_or_discard(message); - } -} - -impl fmt::Debug for QueuedSender { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("QueuedSender").finish() - } -} - -/// Locked queue of messages to the given peer. -/// -/// As long as this struct exists, the background future is asleep and the owner of the -/// [`QueueGuard`] is in total control of the message queue. Messages can only ever be sent out on -/// the network after the [`QueueGuard`] is dropped. -#[must_use] -pub struct QueueGuard<'a, M> { - message_queue: MutexGuard<'a, MessageQueue>, - /// Same as [`QueuedSender::queue_size_limit`]. - queue_size_limit: usize, - notify_background_future: &'a mut Sender<()>, -} - -impl<'a, M: Send + 'static> QueueGuard<'a, M> { - /// Pushes a message to the queue, or discards it if the queue is full. - /// - /// The message will only start being sent out after the [`QueueGuard`] is dropped. - pub fn push_or_discard(&mut self, message: M) { - if self.message_queue.len() < self.queue_size_limit { - self.message_queue.push_back(message); - } - } - - /// Calls `filter` for each message in the queue, and removes the ones for which `false` is - /// returned. - /// - /// > **Note**: The parameter of `filter` is a `&M` and not a `&mut M` (which would be - /// > better) because the underlying implementation relies on `VecDeque::retain`. - pub fn retain(&mut self, filter: impl FnMut(&M) -> bool) { - self.message_queue.retain(filter); - } -} - -impl<'a, M> Drop for QueueGuard<'a, M> { - fn drop(&mut self) { - // Notify background future to check for new messages in the message queue. - let _ = self.notify_background_future.try_send(()); - } -} - -type MessageQueue = VecDeque; - -/// [`MessageQueue`] shared between [`QueuedSender`] and background future. -type SharedMessageQueue = Arc>>; - -async fn create_background_future Vec>( - mut wait_for_sender: Receiver<()>, - service: Arc>, - peer_id: PeerId, - protocol: Cow<'static, str>, - shared_message_queue: SharedMessageQueue, - messages_encode: F, -) { - loop { - if wait_for_sender.next().await.is_none() { - return - } - - loop { - let mut queue_guard = shared_message_queue.lock().await; - let next_message = match queue_guard.pop_front() { - Some(msg) => msg, - None => break, - }; - drop(queue_guard); - - // Starting from below, we try to send the message. If an error happens when sending, - // the only sane option we have is to silently discard the message. - let sender = match service.notification_sender(peer_id.clone(), protocol.clone()) { - Ok(s) => s, - Err(_) => continue, - }; - - let ready = match sender.ready().await { - Ok(r) => r, - Err(_) => continue, - }; - - let _ = ready.send(messages_encode(next_message)); - } - } -} diff --git a/client/network/src/gossip/tests.rs b/client/network/src/gossip/tests.rs deleted file mode 100644 index 88c4160bc5066..0000000000000 --- a/client/network/src/gossip/tests.rs +++ /dev/null @@ -1,250 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use crate::block_request_handler::BlockRequestHandler; -use crate::state_request_handler::StateRequestHandler; -use crate::light_client_requests::handler::LightClientRequestHandler; -use crate::gossip::QueuedSender; -use crate::{config, Event, NetworkService, NetworkWorker}; - -use futures::prelude::*; -use sp_runtime::traits::{Block as BlockT, Header as _}; -use std::{borrow::Cow, sync::Arc, time::Duration}; -use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; - -type TestNetworkService = NetworkService< - substrate_test_runtime_client::runtime::Block, - substrate_test_runtime_client::runtime::Hash, ->; - -/// Builds a full node to be used for testing. Returns the node service and its associated events -/// stream. -/// -/// > **Note**: We return the events stream in order to not possibly lose events between the -/// > construction of the service and the moment the events stream is grabbed. -fn build_test_full_node(network_config: config::NetworkConfiguration) - -> (Arc, impl Stream) -{ - let client = Arc::new( - TestClientBuilder::with_default_backend() - .build_with_longest_chain() - .0, - ); - - #[derive(Clone)] - struct PassThroughVerifier(bool); - - #[async_trait::async_trait] - impl sc_consensus::Verifier for PassThroughVerifier { - async fn verify( - &mut self, - mut block: sp_consensus::BlockImportParams, - ) -> Result< - ( - sc_consensus::BlockImportParams, - Option)>>, - ), - String, - > { - let maybe_keys = block.header - .digest() - .log(|l| { - l.try_as_raw(sp_runtime::generic::OpaqueDigestItemId::Consensus(b"aura")) - .or_else(|| { - l.try_as_raw(sp_runtime::generic::OpaqueDigestItemId::Consensus(b"babe")) - }) - }) - .map(|blob| { - vec![( - sp_blockchain::well_known_cache_keys::AUTHORITIES, - blob.to_vec(), - )] - }); - - block.finalized = self.0; - block.fork_choice = Some(sc_consensus::ForkChoiceStrategy::LongestChain); - Ok((block, maybe_keys)) - } - } - - let import_queue = Box::new(sc_consensus::BasicQueue::new( - PassThroughVerifier(false), - Box::new(client.clone()), - None, - &sp_core::testing::TaskExecutor::new(), - None, - )); - - let protocol_id = config::ProtocolId::from("/test-protocol-name"); - - let block_request_protocol_config = { - let (handler, protocol_config) = BlockRequestHandler::new( - &protocol_id, - client.clone(), - 50, - ); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - let state_request_protocol_config = { - let (handler, protocol_config) = StateRequestHandler::new( - &protocol_id, - client.clone(), - 50, - ); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - let light_client_request_protocol_config = { - let (handler, protocol_config) = LightClientRequestHandler::new( - &protocol_id, - client.clone(), - ); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - let worker = NetworkWorker::new(config::Params { - role: config::Role::Full, - executor: None, - transactions_handler_executor: Box::new(|task| { async_std::task::spawn(task); }), - network_config, - chain: client.clone(), - on_demand: None, - transaction_pool: Arc::new(crate::config::EmptyTransactionPool), - protocol_id, - import_queue, - block_announce_validator: Box::new( - sp_consensus::block_validation::DefaultBlockAnnounceValidator, - ), - metrics_registry: None, - block_request_protocol_config, - state_request_protocol_config, - light_client_request_protocol_config, - warp_sync: None, - }) - .unwrap(); - - let service = worker.service().clone(); - let event_stream = service.event_stream("test"); - - async_std::task::spawn(async move { - futures::pin_mut!(worker); - let _ = worker.await; - }); - - (service, event_stream) -} - -const PROTOCOL_NAME: Cow<'static, str> = Cow::Borrowed("/foo"); - -/// Builds two nodes and their associated events stream. -/// The nodes are connected together and have the `PROTOCOL_NAME` protocol registered. -fn build_nodes_one_proto() - -> (Arc, impl Stream, Arc, impl Stream) -{ - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - - let (node1, events_stream1) = build_test_full_node(config::NetworkConfiguration { - extra_sets: vec![ - config::NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME, - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - set_config: Default::default() - } - ], - listen_addresses: vec![listen_addr.clone()], - transport: config::TransportConfig::MemoryOnly, - .. config::NetworkConfiguration::new_local() - }); - - let (node2, events_stream2) = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![], - extra_sets: vec![ - config::NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME, - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - set_config: config::SetConfig { - reserved_nodes: vec![config::MultiaddrWithPeerId { - multiaddr: listen_addr, - peer_id: node1.local_peer_id().clone(), - }], - .. Default::default() - }, - } - ], - transport: config::TransportConfig::MemoryOnly, - .. config::NetworkConfiguration::new_local() - }); - - (node1, events_stream1, node2, events_stream2) -} - -#[test] -fn basic_works() { - const NUM_NOTIFS: usize = 256; - - let (node1, mut events_stream1, node2, mut events_stream2) = build_nodes_one_proto(); - let node2_id = node2.local_peer_id().clone(); - - let receiver = async_std::task::spawn(async move { - let mut received_notifications = 0; - - while received_notifications < NUM_NOTIFS { - match events_stream2.next().await.unwrap() { - Event::NotificationStreamClosed { .. } => panic!(), - Event::NotificationsReceived { messages, .. } => { - for message in messages { - assert_eq!(message.0, PROTOCOL_NAME); - assert_eq!(message.1, &b"message"[..]); - received_notifications += 1; - } - } - _ => {} - }; - - if rand::random::() < 2 { - async_std::task::sleep(Duration::from_millis(rand::random::() % 750)).await; - } - } - }); - - async_std::task::block_on(async move { - let (mut sender, bg_future) = - QueuedSender::new(node1, node2_id, PROTOCOL_NAME, NUM_NOTIFS, |msg| msg); - async_std::task::spawn(bg_future); - - // Wait for the `NotificationStreamOpened`. - loop { - match events_stream1.next().await.unwrap() { - Event::NotificationStreamOpened { .. } => break, - _ => {} - }; - } - - for _ in 0..NUM_NOTIFS { - sender.queue_or_discard(b"message".to_vec()).await; - } - - receiver.await; - }); -} diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index 51bc370265ef0..f185458e0dace 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -103,8 +103,8 @@ //! protocol ID. //! //! > **Note**: It is possible for the same connection to be used for multiple chains. For example, -//! > one can use both the `/dot/sync/2` and `/sub/sync/2` protocols on the same -//! > connection, provided that the remote supports them. +//! > one can use both the `/dot/sync/2` and `/sub/sync/2` protocols on the same +//! > connection, provided that the remote supports them. //! //! Substrate uses the following standard libp2p protocols: //! @@ -245,41 +245,44 @@ //! More precise usage details are still being worked on and will likely change in the future. mod behaviour; -mod chain; mod discovery; -mod on_demand_layer; mod peer_info; mod protocol; mod request_responses; -mod schema; mod service; mod transport; -mod utils; -pub mod bitswap; -pub mod block_request_handler; pub mod config; -pub mod error; -pub mod light_client_requests; pub mod network_state; -pub mod state_request_handler; -pub mod transactions; -pub mod warp_request_handler; #[doc(inline)] pub use libp2p::{multiaddr, Multiaddr, PeerId}; -pub use protocol::{ - event::{DhtEvent, Event, ObservedRole}, - sync::{StateDownloadProgress, SyncState, WarpSyncPhase, WarpSyncProgress}, - PeerInfo, +pub use protocol::PeerInfo; +use sc_consensus::{JustificationSyncLink, Link}; +pub use sc_network_common::{ + protocol::{ + event::{DhtEvent, Event}, + role::ObservedRole, + ProtocolName, + }, + request_responses::{IfDisconnected, RequestFailure}, + service::{ + KademliaKey, NetworkBlock, NetworkDHTProvider, NetworkRequest, NetworkSigner, + NetworkStateInfo, NetworkStatus, NetworkStatusProvider, NetworkSyncForkRequest, Signature, + SigningError, + }, + sync::{ + warp::{WarpSyncPhase, WarpSyncProgress}, + StateDownloadProgress, SyncState, + }, }; pub use service::{ - IfDisconnected, NetworkService, NetworkWorker, NotificationSender, NotificationSenderReady, - OutboundFailure, RequestFailure, + DecodingError, Keypair, NetworkService, NetworkWorker, NotificationSender, + NotificationSenderReady, OutboundFailure, PublicKey, }; +use sp_runtime::traits::{Block as BlockT, NumberFor}; pub use sc_peerset::ReputationChange; -use sp_runtime::traits::{Block as BlockT, NumberFor}; /// The maximum allowed number of established connections per peer. /// @@ -293,40 +296,17 @@ const MAX_CONNECTIONS_PER_PEER: usize = 2; /// The maximum number of concurrent established connections that were incoming. const MAX_CONNECTIONS_ESTABLISHED_INCOMING: u32 = 10_000; -/// Minimum Requirements for a Hash within Networking -pub trait ExHashT: std::hash::Hash + Eq + std::fmt::Debug + Clone + Send + Sync + 'static {} - -impl ExHashT for T where T: std::hash::Hash + Eq + std::fmt::Debug + Clone + Send + Sync + 'static -{} - -/// Trait for providing information about the local network state -pub trait NetworkStateInfo { - /// Returns the local external addresses. - fn external_addresses(&self) -> Vec; - - /// Returns the local Peer ID. - fn local_peer_id(&self) -> PeerId; +/// Abstraction over syncing-related services +pub trait ChainSyncInterface: + NetworkSyncForkRequest> + JustificationSyncLink + Link + Send + Sync +{ } -/// Overview status of the network. -#[derive(Clone)] -pub struct NetworkStatus { - /// Current global sync state. - pub sync_state: SyncState, - /// Target sync block number. - pub best_seen_block: Option>, - /// Number of peers participating in syncing. - pub num_sync_peers: u32, - /// Total number of connected peers - pub num_connected_peers: usize, - /// Total number of active peers. - pub num_active_peers: usize, - /// The total number of bytes received. - pub total_bytes_inbound: u64, - /// The total number of bytes sent. - pub total_bytes_outbound: u64, - /// State sync in progress. - pub state_sync: Option, - /// Warp sync in progress. - pub warp_sync: Option, +impl ChainSyncInterface for T where + T: NetworkSyncForkRequest> + + JustificationSyncLink + + Link + + Send + + Sync +{ } diff --git a/client/network/src/light_client_requests.rs b/client/network/src/light_client_requests.rs deleted file mode 100644 index e18b783f219be..0000000000000 --- a/client/network/src/light_client_requests.rs +++ /dev/null @@ -1,315 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Helpers for outgoing and incoming light client requests. - -/// For incoming light client requests. -pub mod handler; -/// For outgoing light client requests. -pub mod sender; - -use crate::{config::ProtocolId, request_responses::ProtocolConfig}; - -use std::time::Duration; - -/// Generate the light client protocol name from chain specific protocol identifier. -fn generate_protocol_name(protocol_id: &ProtocolId) -> String { - let mut s = String::new(); - s.push_str("/"); - s.push_str(protocol_id.as_ref()); - s.push_str("/light/2"); - s -} - -/// Generates a [`ProtocolConfig`] for the light client request protocol, refusing incoming -/// requests. -pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { - ProtocolConfig { - name: generate_protocol_name(protocol_id).into(), - max_request_size: 1 * 1024 * 1024, - max_response_size: 16 * 1024 * 1024, - request_timeout: Duration::from_secs(15), - inbound_queue: None, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{config::ProtocolId, request_responses::IncomingRequest}; - - use assert_matches::assert_matches; - use futures::{ - channel::oneshot, - executor::{block_on, LocalPool}, - prelude::*, - task::Spawn, - }; - use libp2p::PeerId; - use sc_client_api::{ - light::{ - self, ChangesProof, RemoteBodyRequest, RemoteCallRequest, RemoteChangesRequest, - RemoteHeaderRequest, RemoteReadRequest, - }, - FetchChecker, RemoteReadChildRequest, StorageProof, - }; - use sp_blockchain::Error as ClientError; - use sp_core::storage::ChildInfo; - use sp_runtime::{ - generic::Header, - traits::{BlakeTwo256, Block as BlockT, NumberFor}, - }; - use std::{collections::HashMap, sync::Arc}; - - pub struct DummyFetchChecker { - pub ok: bool, - pub _mark: std::marker::PhantomData, - } - - impl FetchChecker for DummyFetchChecker { - fn check_header_proof( - &self, - _request: &RemoteHeaderRequest, - header: Option, - _remote_proof: StorageProof, - ) -> Result { - match self.ok { - true if header.is_some() => Ok(header.unwrap()), - _ => Err(ClientError::Backend("Test error".into())), - } - } - - fn check_read_proof( - &self, - request: &RemoteReadRequest, - _: StorageProof, - ) -> Result, Option>>, ClientError> { - match self.ok { - true => Ok(request.keys.iter().cloned().map(|k| (k, Some(vec![42]))).collect()), - false => Err(ClientError::Backend("Test error".into())), - } - } - - fn check_read_child_proof( - &self, - request: &RemoteReadChildRequest, - _: StorageProof, - ) -> Result, Option>>, ClientError> { - match self.ok { - true => Ok(request.keys.iter().cloned().map(|k| (k, Some(vec![42]))).collect()), - false => Err(ClientError::Backend("Test error".into())), - } - } - - fn check_execution_proof( - &self, - _: &RemoteCallRequest, - _: StorageProof, - ) -> Result, ClientError> { - match self.ok { - true => Ok(vec![42]), - false => Err(ClientError::Backend("Test error".into())), - } - } - - fn check_changes_proof( - &self, - _: &RemoteChangesRequest, - _: ChangesProof, - ) -> Result, u32)>, ClientError> { - match self.ok { - true => Ok(vec![(100u32.into(), 2)]), - false => Err(ClientError::Backend("Test error".into())), - } - } - - fn check_body_proof( - &self, - _: &RemoteBodyRequest, - body: Vec, - ) -> Result, ClientError> { - match self.ok { - true => Ok(body), - false => Err(ClientError::Backend("Test error".into())), - } - } - } - - pub fn protocol_id() -> ProtocolId { - ProtocolId::from("test") - } - - pub fn peerset() -> (sc_peerset::Peerset, sc_peerset::PeersetHandle) { - let cfg = sc_peerset::SetConfig { - in_peers: 128, - out_peers: 128, - bootnodes: Default::default(), - reserved_only: false, - reserved_nodes: Default::default(), - }; - sc_peerset::Peerset::from_config(sc_peerset::PeersetConfig { sets: vec![cfg] }) - } - - pub fn dummy_header() -> sp_test_primitives::Header { - sp_test_primitives::Header { - parent_hash: Default::default(), - number: 0, - state_root: Default::default(), - extrinsics_root: Default::default(), - digest: Default::default(), - } - } - - type Block = - sp_runtime::generic::Block, substrate_test_runtime::Extrinsic>; - - fn send_receive(request: sender::Request, pool: &LocalPool) { - let client = Arc::new(substrate_test_runtime_client::new()); - let (handler, protocol_config) = - handler::LightClientRequestHandler::new(&protocol_id(), client); - pool.spawner().spawn_obj(handler.run().boxed().into()).unwrap(); - - let (_peer_set, peer_set_handle) = peerset(); - let mut sender = sender::LightClientRequestSender::::new( - &protocol_id(), - Arc::new(crate::light_client_requests::tests::DummyFetchChecker { - ok: true, - _mark: std::marker::PhantomData, - }), - peer_set_handle, - ); - sender.inject_connected(PeerId::random()); - - sender.request(request).unwrap(); - let sender::OutEvent::SendRequest { pending_response, request, .. } = - block_on(sender.next()).unwrap(); - let (tx, rx) = oneshot::channel(); - block_on(protocol_config.inbound_queue.unwrap().send(IncomingRequest { - peer: PeerId::random(), - payload: request, - pending_response: tx, - })) - .unwrap(); - pool.spawner() - .spawn_obj( - async move { - pending_response.send(Ok(rx.await.unwrap().result.unwrap())).unwrap(); - } - .boxed() - .into(), - ) - .unwrap(); - - pool.spawner() - .spawn_obj(sender.for_each(|_| future::ready(())).boxed().into()) - .unwrap(); - } - - #[test] - fn send_receive_call() { - let chan = oneshot::channel(); - let request = light::RemoteCallRequest { - block: Default::default(), - header: dummy_header(), - method: "test".into(), - call_data: vec![], - retry_count: None, - }; - - let mut pool = LocalPool::new(); - send_receive(sender::Request::Call { request, sender: chan.0 }, &pool); - assert_eq!(vec![42], pool.run_until(chan.1).unwrap().unwrap()); - // ^--- from `DummyFetchChecker::check_execution_proof` - } - - #[test] - fn send_receive_read() { - let chan = oneshot::channel(); - let request = light::RemoteReadRequest { - header: dummy_header(), - block: Default::default(), - keys: vec![b":key".to_vec()], - retry_count: None, - }; - let mut pool = LocalPool::new(); - send_receive(sender::Request::Read { request, sender: chan.0 }, &pool); - assert_eq!( - Some(vec![42]), - pool.run_until(chan.1).unwrap().unwrap().remove(&b":key"[..]).unwrap() - ); - // ^--- from `DummyFetchChecker::check_read_proof` - } - - #[test] - fn send_receive_read_child() { - let chan = oneshot::channel(); - let child_info = ChildInfo::new_default(&b":child_storage:default:sub"[..]); - let request = light::RemoteReadChildRequest { - header: dummy_header(), - block: Default::default(), - storage_key: child_info.prefixed_storage_key(), - keys: vec![b":key".to_vec()], - retry_count: None, - }; - let mut pool = LocalPool::new(); - send_receive(sender::Request::ReadChild { request, sender: chan.0 }, &pool); - assert_eq!( - Some(vec![42]), - pool.run_until(chan.1).unwrap().unwrap().remove(&b":key"[..]).unwrap() - ); - // ^--- from `DummyFetchChecker::check_read_child_proof` - } - - #[test] - fn send_receive_header() { - sp_tracing::try_init_simple(); - let chan = oneshot::channel(); - let request = light::RemoteHeaderRequest { - cht_root: Default::default(), - block: 1, - retry_count: None, - }; - let mut pool = LocalPool::new(); - send_receive(sender::Request::Header { request, sender: chan.0 }, &pool); - // The remote does not know block 1: - assert_matches!(pool.run_until(chan.1).unwrap(), Err(ClientError::RemoteFetchFailed)); - } - - #[test] - fn send_receive_changes() { - let chan = oneshot::channel(); - let request = light::RemoteChangesRequest { - changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange { - zero: (0, Default::default()), - end: None, - config: Some(sp_core::ChangesTrieConfiguration::new(4, 2)), - }], - first_block: (1, Default::default()), - last_block: (100, Default::default()), - max_block: (100, Default::default()), - tries_roots: (1, Default::default(), Vec::new()), - key: Vec::new(), - storage_key: None, - retry_count: None, - }; - let mut pool = LocalPool::new(); - send_receive(sender::Request::Changes { request, sender: chan.0 }, &pool); - assert_eq!(vec![(100, 2)], pool.run_until(chan.1).unwrap().unwrap()); - // ^--- from `DummyFetchChecker::check_changes_proof` - } -} diff --git a/client/network/src/light_client_requests/sender.rs b/client/network/src/light_client_requests/sender.rs deleted file mode 100644 index 284db827594b4..0000000000000 --- a/client/network/src/light_client_requests/sender.rs +++ /dev/null @@ -1,1294 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Helper for outgoing light client requests. -//! -//! Call [`LightClientRequestSender::request`](sender::LightClientRequestSender::request) -//! to send out light client requests. It will: -//! -//! 1. Build the request. -//! -//! 2. Forward the request to [`crate::request_responses::RequestResponsesBehaviour`] via -//! [`OutEvent::SendRequest`](sender::OutEvent::SendRequest). -//! -//! 3. Wait for the response and forward the response via the [`futures::channel::oneshot::Sender`] -//! provided earlier with [`LightClientRequestSender::request`](sender::LightClientRequestSender:: -//! request). - -use crate::{ - config::ProtocolId, - protocol::message::BlockAttributes, - request_responses::{OutboundFailure, RequestFailure}, - schema, PeerId, -}; -use codec::{self, Decode, Encode}; -use futures::{channel::oneshot, future::BoxFuture, prelude::*, stream::FuturesUnordered}; -use prost::Message; -use sc_client_api::light::{self, RemoteBodyRequest}; -use sc_peerset::ReputationChange; -use sp_blockchain::Error as ClientError; -use sp_runtime::traits::{Block, Header, NumberFor}; -use std::{ - collections::{BTreeMap, HashMap, VecDeque}, - pin::Pin, - sync::Arc, - task::{Context, Poll}, -}; - -mod rep { - use super::*; - - /// Reputation change for a peer when a request timed out. - pub const TIMEOUT: ReputationChange = - ReputationChange::new(-(1 << 8), "light client request timeout"); - /// Reputation change for a peer when a request is refused. - pub const REFUSED: ReputationChange = - ReputationChange::new(-(1 << 8), "light client request refused"); -} - -/// Configuration options for [`LightClientRequestSender`]. -#[derive(Debug, Clone)] -struct Config { - max_pending_requests: usize, - light_protocol: String, - block_protocol: String, -} - -impl Config { - /// Create a new [`LightClientRequestSender`] configuration. - pub fn new(id: &ProtocolId) -> Self { - Self { - max_pending_requests: 128, - light_protocol: super::generate_protocol_name(id), - block_protocol: crate::block_request_handler::generate_protocol_name(id), - } - } -} - -/// State machine helping to send out light client requests. -pub struct LightClientRequestSender { - /// This behaviour's configuration. - config: Config, - /// Verifies that received responses are correct. - checker: Arc>, - /// Peer information (addresses, their best block, etc.) - peers: HashMap>, - /// Pending (local) requests. - pending_requests: VecDeque>, - /// Requests on their way to remote peers. - sent_requests: FuturesUnordered< - BoxFuture< - 'static, - (SentRequest, Result, RequestFailure>, oneshot::Canceled>), - >, - >, - /// Handle to use for reporting misbehaviour of peers. - peerset: sc_peerset::PeersetHandle, -} - -/// Augments a pending light client request with metadata. -#[derive(Debug)] -struct PendingRequest { - /// Remaining attempts. - attempts_left: usize, - /// The actual request. - request: Request, -} - -impl PendingRequest { - fn new(req: Request) -> Self { - Self { - // Number of retries + one for the initial attempt. - attempts_left: req.retries() + 1, - request: req, - } - } - - fn into_sent(self, peer_id: PeerId) -> SentRequest { - SentRequest { attempts_left: self.attempts_left, request: self.request, peer: peer_id } - } -} - -/// Augments a light client request with metadata that is currently being send to a remote. -#[derive(Debug)] -struct SentRequest { - /// Remaining attempts. - attempts_left: usize, - /// The actual request. - request: Request, - /// The peer that the request is send to. - peer: PeerId, -} - -impl SentRequest { - fn into_pending(self) -> PendingRequest { - PendingRequest { attempts_left: self.attempts_left, request: self.request } - } -} - -impl Unpin for LightClientRequestSender {} - -impl LightClientRequestSender -where - B: Block, -{ - /// Construct a new light client handler. - pub fn new( - id: &ProtocolId, - checker: Arc>, - peerset: sc_peerset::PeersetHandle, - ) -> Self { - Self { - config: Config::new(id), - checker, - peers: Default::default(), - pending_requests: Default::default(), - sent_requests: Default::default(), - peerset, - } - } - - /// We rely on external information about peers best blocks as we lack the - /// means to determine it ourselves. - pub fn update_best_block(&mut self, peer: &PeerId, num: NumberFor) { - if let Some(info) = self.peers.get_mut(peer) { - log::trace!("new best block for {:?}: {:?}", peer, num); - info.best_block = Some(num) - } - } - - /// Issue a new light client request. - pub fn request(&mut self, req: Request) -> Result<(), SendRequestError> { - if self.pending_requests.len() >= self.config.max_pending_requests { - return Err(SendRequestError::TooManyRequests) - } - self.pending_requests.push_back(PendingRequest::new(req)); - Ok(()) - } - - /// Remove the given peer. - /// - /// In-flight requests to the given peer might fail and be retried. See - /// [`::poll_next`]. - fn remove_peer(&mut self, peer: PeerId) { - self.peers.remove(&peer); - } - - /// Process a local request's response from remote. - /// - /// If successful, this will give us the actual, checked data we should be - /// sending back to the client, otherwise an error. - fn on_response( - &mut self, - peer: PeerId, - request: &Request, - response: Response, - ) -> Result, Error> { - log::trace!("response from {}", peer); - match response { - Response::Light(r) => self.on_response_light(request, r), - Response::Block(r) => self.on_response_block(request, r), - } - } - - fn on_response_light( - &mut self, - request: &Request, - response: schema::v1::light::Response, - ) -> Result, Error> { - use schema::v1::light::response::Response; - match response.response { - Some(Response::RemoteCallResponse(response)) => { - if let Request::Call { request, .. } = request { - let proof = Decode::decode(&mut response.proof.as_ref())?; - let reply = self.checker.check_execution_proof(request, proof)?; - Ok(Reply::VecU8(reply)) - } else { - Err(Error::UnexpectedResponse) - } - }, - Some(Response::RemoteReadResponse(response)) => match request { - Request::Read { request, .. } => { - let proof = Decode::decode(&mut response.proof.as_ref())?; - let reply = self.checker.check_read_proof(&request, proof)?; - Ok(Reply::MapVecU8OptVecU8(reply)) - }, - Request::ReadChild { request, .. } => { - let proof = Decode::decode(&mut response.proof.as_ref())?; - let reply = self.checker.check_read_child_proof(&request, proof)?; - Ok(Reply::MapVecU8OptVecU8(reply)) - }, - _ => Err(Error::UnexpectedResponse), - }, - Some(Response::RemoteChangesResponse(response)) => { - if let Request::Changes { request, .. } = request { - let max_block = Decode::decode(&mut response.max.as_ref())?; - let roots_proof = Decode::decode(&mut response.roots_proof.as_ref())?; - let roots = { - let mut r = BTreeMap::new(); - for pair in response.roots { - let k = Decode::decode(&mut pair.fst.as_ref())?; - let v = Decode::decode(&mut pair.snd.as_ref())?; - r.insert(k, v); - } - r - }; - let reply = self.checker.check_changes_proof( - &request, - light::ChangesProof { - max_block, - proof: response.proof, - roots, - roots_proof, - }, - )?; - Ok(Reply::VecNumberU32(reply)) - } else { - Err(Error::UnexpectedResponse) - } - }, - Some(Response::RemoteHeaderResponse(response)) => { - if let Request::Header { request, .. } = request { - let header = if response.header.is_empty() { - None - } else { - Some(Decode::decode(&mut response.header.as_ref())?) - }; - let proof = Decode::decode(&mut response.proof.as_ref())?; - let reply = self.checker.check_header_proof(&request, header, proof)?; - Ok(Reply::Header(reply)) - } else { - Err(Error::UnexpectedResponse) - } - }, - None => Err(Error::UnexpectedResponse), - } - } - - fn on_response_block( - &mut self, - request: &Request, - response: schema::v1::BlockResponse, - ) -> Result, Error> { - let request = if let Request::Body { request, .. } = &request { - request - } else { - return Err(Error::UnexpectedResponse) - }; - - let body: Vec<_> = match response.blocks.into_iter().next() { - Some(b) => b.body, - None => return Err(Error::UnexpectedResponse), - }; - - let body = body - .into_iter() - .map(|extrinsic| B::Extrinsic::decode(&mut &extrinsic[..])) - .collect::>()?; - - let body = self.checker.check_body_proof(&request, body)?; - Ok(Reply::Extrinsics(body)) - } - - /// Signal that the node is connected to the given peer. - pub fn inject_connected(&mut self, peer: PeerId) { - let prev_entry = self.peers.insert(peer, Default::default()); - debug_assert!( - prev_entry.is_none(), - "Expect `inject_connected` to be called for disconnected peer.", - ); - } - - /// Signal that the node disconnected from the given peer. - pub fn inject_disconnected(&mut self, peer: PeerId) { - self.remove_peer(peer) - } -} - -impl Stream for LightClientRequestSender { - type Item = OutEvent; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - // If we have received responses to previously sent requests, check them and pass them on. - while let Poll::Ready(Some((sent_request, request_result))) = - self.sent_requests.poll_next_unpin(cx) - { - if let Some(info) = self.peers.get_mut(&sent_request.peer) { - if info.status != PeerStatus::Busy { - // If we get here, something is wrong with our internal handling of peer status - // information. At any time, a single peer processes at most one request from - // us. A malicious peer should not be able to get us here. It is our own fault - // and must be fixed! - panic!("unexpected peer status {:?} for {}", info.status, sent_request.peer); - } - - info.status = PeerStatus::Idle; // Make peer available again. - } - - let request_result = match request_result { - Ok(r) => r, - Err(oneshot::Canceled) => { - log::debug!("Oneshot for request to peer {} was canceled.", sent_request.peer); - self.remove_peer(sent_request.peer); - self.peerset.report_peer( - sent_request.peer, - ReputationChange::new_fatal("no response from peer"), - ); - self.pending_requests.push_back(sent_request.into_pending()); - continue - }, - }; - - let decoded_request_result = request_result.map(|response| { - if sent_request.request.is_block_request() { - schema::v1::BlockResponse::decode(&response[..]).map(|r| Response::Block(r)) - } else { - schema::v1::light::Response::decode(&response[..]).map(|r| Response::Light(r)) - } - }); - - let response = match decoded_request_result { - Ok(Ok(response)) => response, - Ok(Err(e)) => { - log::debug!( - "Failed to decode response from peer {}: {:?}.", - sent_request.peer, - e - ); - self.remove_peer(sent_request.peer); - self.peerset.report_peer( - sent_request.peer, - ReputationChange::new_fatal("invalid response from peer"), - ); - self.pending_requests.push_back(sent_request.into_pending()); - continue - }, - Err(e) => { - log::debug!("Request to peer {} failed with {:?}.", sent_request.peer, e); - - match e { - RequestFailure::NotConnected => { - self.remove_peer(sent_request.peer); - self.pending_requests.push_back(sent_request.into_pending()); - }, - RequestFailure::UnknownProtocol => { - debug_assert!( - false, - "Light client and block request protocol should be known when \ - sending requests.", - ); - }, - RequestFailure::Refused => { - self.remove_peer(sent_request.peer); - self.peerset.report_peer(sent_request.peer, rep::REFUSED); - self.pending_requests.push_back(sent_request.into_pending()); - }, - RequestFailure::Obsolete => { - debug_assert!( - false, - "Can not receive `RequestFailure::Obsolete` after dropping the \ - response receiver.", - ); - self.pending_requests.push_back(sent_request.into_pending()); - }, - RequestFailure::Network(OutboundFailure::Timeout) => { - self.remove_peer(sent_request.peer); - self.peerset.report_peer(sent_request.peer, rep::TIMEOUT); - self.pending_requests.push_back(sent_request.into_pending()); - }, - RequestFailure::Network(OutboundFailure::UnsupportedProtocols) => { - self.remove_peer(sent_request.peer); - self.peerset.report_peer( - sent_request.peer, - ReputationChange::new_fatal( - "peer does not support light client or block request protocol", - ), - ); - self.pending_requests.push_back(sent_request.into_pending()); - }, - RequestFailure::Network(OutboundFailure::DialFailure) => { - self.remove_peer(sent_request.peer); - self.peerset.report_peer( - sent_request.peer, - ReputationChange::new_fatal("failed to dial peer"), - ); - self.pending_requests.push_back(sent_request.into_pending()); - }, - RequestFailure::Network(OutboundFailure::ConnectionClosed) => { - self.remove_peer(sent_request.peer); - self.peerset.report_peer( - sent_request.peer, - ReputationChange::new_fatal("connection to peer closed"), - ); - self.pending_requests.push_back(sent_request.into_pending()); - }, - } - - continue - }, - }; - - match self.on_response(sent_request.peer, &sent_request.request, response) { - Ok(reply) => sent_request.request.return_reply(Ok(reply)), - Err(Error::UnexpectedResponse) => { - log::debug!("Unexpected response from peer {}.", sent_request.peer); - self.remove_peer(sent_request.peer); - self.peerset.report_peer( - sent_request.peer, - ReputationChange::new_fatal("unexpected response from peer"), - ); - self.pending_requests.push_back(sent_request.into_pending()); - }, - Err(other) => { - log::debug!( - "error handling response from peer {}: {}", - sent_request.peer, - other - ); - self.remove_peer(sent_request.peer); - self.peerset.report_peer( - sent_request.peer, - ReputationChange::new_fatal("invalid response from peer"), - ); - self.pending_requests.push_back(sent_request.into_pending()) - }, - } - } - - // If we have a pending request to send, try to find an available peer and send it. - while let Some(mut pending_request) = self.pending_requests.pop_front() { - if pending_request.attempts_left == 0 { - pending_request.request.return_reply(Err(ClientError::RemoteFetchFailed)); - continue - } - - let protocol = if pending_request.request.is_block_request() { - self.config.block_protocol.clone() - } else { - self.config.light_protocol.clone() - }; - - // Out of all idle peers, find one who's best block is high enough, choose any idle peer - // if none exists. - let mut peer = None; - for (peer_id, peer_info) in self.peers.iter_mut() { - if peer_info.status == PeerStatus::Idle { - match peer_info.best_block { - Some(n) if n >= pending_request.request.required_block() => { - peer = Some((*peer_id, peer_info)); - break - }, - _ => peer = Some((*peer_id, peer_info)), - } - } - } - - // Break in case there is no idle peer. - let (peer_id, peer_info) = match peer { - Some((peer_id, peer_info)) => (peer_id, peer_info), - None => { - self.pending_requests.push_front(pending_request); - log::debug!("No peer available to send request to."); - - break - }, - }; - - let request_bytes = match pending_request.request.serialize_request() { - Ok(bytes) => bytes, - Err(error) => { - log::debug!("failed to serialize request: {}", error); - pending_request.request.return_reply(Err(ClientError::RemoteFetchFailed)); - continue - }, - }; - - let (tx, rx) = oneshot::channel(); - - peer_info.status = PeerStatus::Busy; - - pending_request.attempts_left -= 1; - - self.sent_requests - .push(async move { (pending_request.into_sent(peer_id), rx.await) }.boxed()); - - return Poll::Ready(Some(OutEvent::SendRequest { - target: peer_id, - request: request_bytes, - pending_response: tx, - protocol_name: protocol, - })) - } - - Poll::Pending - } -} - -/// Events returned by [`LightClientRequestSender`]. -#[derive(Debug)] -pub enum OutEvent { - /// Emit a request to be send out on the network e.g. via [`crate::request_responses`]. - SendRequest { - /// The remote peer to send the request to. - target: PeerId, - /// The encoded request. - request: Vec, - /// The [`oneshot::Sender`] channel to pass the response to. - pending_response: oneshot::Sender, RequestFailure>>, - /// The name of the protocol to use to send the request. - protocol_name: String, - }, -} - -/// Incoming response from remote. -#[derive(Debug, Clone)] -pub enum Response { - /// Incoming light response from remote. - Light(schema::v1::light::Response), - /// Incoming block response from remote. - Block(schema::v1::BlockResponse), -} - -/// Error returned by [`LightClientRequestSender::request`]. -#[derive(Debug, derive_more::Display, derive_more::From)] -pub enum SendRequestError { - /// There are currently too many pending request. - #[display(fmt = "too many pending requests")] - TooManyRequests, -} - -/// Error type to propagate errors internally. -#[derive(Debug, derive_more::Display, derive_more::From)] -enum Error { - /// The response type does not correspond to the issued request. - #[display(fmt = "unexpected response")] - UnexpectedResponse, - /// Encoding or decoding of some data failed. - #[display(fmt = "codec error: {}", _0)] - Codec(codec::Error), - /// The chain client errored. - #[display(fmt = "client error: {}", _0)] - Client(ClientError), -} - -/// The data to send back to the light client over the oneshot channel. -// It is unified here in order to be able to return it as a function -// result instead of delivering it to the client as a side effect of -// response processing. -#[derive(Debug)] -enum Reply { - VecU8(Vec), - VecNumberU32(Vec<(::Number, u32)>), - MapVecU8OptVecU8(HashMap, Option>>), - Header(B::Header), - Extrinsics(Vec), -} - -/// Information we have about some peer. -#[derive(Debug)] -struct PeerInfo { - best_block: Option>, - status: PeerStatus, -} - -impl Default for PeerInfo { - fn default() -> Self { - PeerInfo { best_block: None, status: PeerStatus::Idle } - } -} - -/// A peer is either idle or busy processing a request from us. -#[derive(Debug, Clone, PartialEq, Eq)] -enum PeerStatus { - /// The peer is available. - Idle, - /// We wait for the peer to return us a response for the given request ID. - Busy, -} - -/// The possible light client requests we support. -/// -/// The associated `oneshot::Sender` will be used to convey the result of -/// their request back to them (cf. `Reply`). -// This is modeled after light_dispatch.rs's `RequestData` which is not -// used because we currently only support a subset of those. -#[derive(Debug)] -pub enum Request { - /// Remote body request. - Body { - /// Request. - request: RemoteBodyRequest, - /// [`oneshot::Sender`] to return response. - sender: oneshot::Sender, ClientError>>, - }, - /// Remote header request. - Header { - /// Request. - request: light::RemoteHeaderRequest, - /// [`oneshot::Sender`] to return response. - sender: oneshot::Sender>, - }, - /// Remote read request. - Read { - /// Request. - request: light::RemoteReadRequest, - /// [`oneshot::Sender`] to return response. - sender: oneshot::Sender, Option>>, ClientError>>, - }, - /// Remote read child request. - ReadChild { - /// Request. - request: light::RemoteReadChildRequest, - /// [`oneshot::Sender`] to return response. - sender: oneshot::Sender, Option>>, ClientError>>, - }, - /// Remote call request. - Call { - /// Request. - request: light::RemoteCallRequest, - /// [`oneshot::Sender`] to return response. - sender: oneshot::Sender, ClientError>>, - }, - /// Remote changes request. - Changes { - /// Request. - request: light::RemoteChangesRequest, - /// [`oneshot::Sender`] to return response. - sender: oneshot::Sender, u32)>, ClientError>>, - }, -} - -impl Request { - fn is_block_request(&self) -> bool { - matches!(self, Request::Body { .. }) - } - - fn required_block(&self) -> NumberFor { - match self { - Request::Body { request, .. } => *request.header.number(), - Request::Header { request, .. } => request.block, - Request::Read { request, .. } => *request.header.number(), - Request::ReadChild { request, .. } => *request.header.number(), - Request::Call { request, .. } => *request.header.number(), - Request::Changes { request, .. } => request.max_block.0, - } - } - - fn retries(&self) -> usize { - let rc = match self { - Request::Body { request, .. } => request.retry_count, - Request::Header { request, .. } => request.retry_count, - Request::Read { request, .. } => request.retry_count, - Request::ReadChild { request, .. } => request.retry_count, - Request::Call { request, .. } => request.retry_count, - Request::Changes { request, .. } => request.retry_count, - }; - rc.unwrap_or(0) - } - - fn serialize_request(&self) -> Result, prost::EncodeError> { - let request = match self { - Request::Body { request, .. } => { - let rq = schema::v1::BlockRequest { - fields: BlockAttributes::BODY.to_be_u32(), - from_block: Some(schema::v1::block_request::FromBlock::Hash( - request.header.hash().encode(), - )), - to_block: Default::default(), - direction: schema::v1::Direction::Ascending as i32, - max_blocks: 1, - support_multiple_justifications: true, - }; - - let mut buf = Vec::with_capacity(rq.encoded_len()); - rq.encode(&mut buf)?; - return Ok(buf) - }, - Request::Header { request, .. } => { - let r = schema::v1::light::RemoteHeaderRequest { block: request.block.encode() }; - schema::v1::light::request::Request::RemoteHeaderRequest(r) - }, - Request::Read { request, .. } => { - let r = schema::v1::light::RemoteReadRequest { - block: request.block.encode(), - keys: request.keys.clone(), - }; - schema::v1::light::request::Request::RemoteReadRequest(r) - }, - Request::ReadChild { request, .. } => { - let r = schema::v1::light::RemoteReadChildRequest { - block: request.block.encode(), - storage_key: request.storage_key.clone().into_inner(), - keys: request.keys.clone(), - }; - schema::v1::light::request::Request::RemoteReadChildRequest(r) - }, - Request::Call { request, .. } => { - let r = schema::v1::light::RemoteCallRequest { - block: request.block.encode(), - method: request.method.clone(), - data: request.call_data.clone(), - }; - schema::v1::light::request::Request::RemoteCallRequest(r) - }, - Request::Changes { request, .. } => { - let r = schema::v1::light::RemoteChangesRequest { - first: request.first_block.1.encode(), - last: request.last_block.1.encode(), - min: request.tries_roots.1.encode(), - max: request.max_block.1.encode(), - storage_key: request - .storage_key - .clone() - .map(|s| s.into_inner()) - .unwrap_or_default(), - key: request.key.clone(), - }; - schema::v1::light::request::Request::RemoteChangesRequest(r) - }, - }; - - let rq = schema::v1::light::Request { request: Some(request) }; - let mut buf = Vec::with_capacity(rq.encoded_len()); - rq.encode(&mut buf)?; - Ok(buf) - } - - fn return_reply(self, result: Result, ClientError>) { - fn send(item: T, sender: oneshot::Sender) { - let _ = sender.send(item); // It is okay if the other end already hung up. - } - match self { - Request::Body { request, sender } => match result { - Err(e) => send(Err(e), sender), - Ok(Reply::Extrinsics(x)) => send(Ok(x), sender), - reply => log::error!("invalid reply for body request: {:?}, {:?}", reply, request), - }, - Request::Header { request, sender } => match result { - Err(e) => send(Err(e), sender), - Ok(Reply::Header(x)) => send(Ok(x), sender), - reply => { - log::error!("invalid reply for header request: {:?}, {:?}", reply, request) - }, - }, - Request::Read { request, sender } => match result { - Err(e) => send(Err(e), sender), - Ok(Reply::MapVecU8OptVecU8(x)) => send(Ok(x), sender), - reply => log::error!("invalid reply for read request: {:?}, {:?}", reply, request), - }, - Request::ReadChild { request, sender } => match result { - Err(e) => send(Err(e), sender), - Ok(Reply::MapVecU8OptVecU8(x)) => send(Ok(x), sender), - reply => { - log::error!("invalid reply for read child request: {:?}, {:?}", reply, request) - }, - }, - Request::Call { request, sender } => match result { - Err(e) => send(Err(e), sender), - Ok(Reply::VecU8(x)) => send(Ok(x), sender), - reply => log::error!("invalid reply for call request: {:?}, {:?}", reply, request), - }, - Request::Changes { request, sender } => match result { - Err(e) => send(Err(e), sender), - Ok(Reply::VecNumberU32(x)) => send(Ok(x), sender), - reply => { - log::error!("invalid reply for changes request: {:?}, {:?}", reply, request) - }, - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - light_client_requests::tests::{dummy_header, peerset, protocol_id, DummyFetchChecker}, - request_responses::OutboundFailure, - }; - - use assert_matches::assert_matches; - use futures::{channel::oneshot, executor::block_on, poll}; - use sc_client_api::StorageProof; - use sp_core::storage::ChildInfo; - use sp_runtime::{generic::Header, traits::BlakeTwo256}; - use std::{collections::HashSet, iter::FromIterator}; - - fn empty_proof() -> Vec { - StorageProof::empty().encode() - } - - #[test] - fn removes_peer_if_told() { - let peer = PeerId::random(); - let (_peer_set, peer_set_handle) = peerset(); - let mut sender = LightClientRequestSender::::new( - &protocol_id(), - Arc::new(DummyFetchChecker { ok: true, _mark: std::marker::PhantomData }), - peer_set_handle, - ); - - sender.inject_connected(peer); - assert_eq!(1, sender.peers.len()); - - sender.inject_disconnected(peer); - assert_eq!(0, sender.peers.len()); - } - - type Block = - sp_runtime::generic::Block, substrate_test_runtime::Extrinsic>; - - #[test] - fn body_request_fields_encoded_properly() { - let (sender, _receiver) = oneshot::channel(); - let request = Request::::Body { - request: RemoteBodyRequest { header: dummy_header(), retry_count: None }, - sender, - }; - let serialized_request = request.serialize_request().unwrap(); - let deserialized_request = - schema::v1::BlockRequest::decode(&serialized_request[..]).unwrap(); - assert!(BlockAttributes::from_be_u32(deserialized_request.fields) - .unwrap() - .contains(BlockAttributes::BODY)); - } - - #[test] - fn disconnects_from_peer_if_request_times_out() { - let peer0 = PeerId::random(); - let peer1 = PeerId::random(); - - let (_peer_set, peer_set_handle) = peerset(); - let mut sender = LightClientRequestSender::::new( - &protocol_id(), - Arc::new(crate::light_client_requests::tests::DummyFetchChecker { - ok: true, - _mark: std::marker::PhantomData, - }), - peer_set_handle, - ); - - sender.inject_connected(peer0); - sender.inject_connected(peer1); - - assert_eq!( - HashSet::from_iter(&[peer0.clone(), peer1.clone()]), - sender.peers.keys().collect::>(), - "Expect knowledge of two peers." - ); - - assert!(sender.pending_requests.is_empty(), "Expect no pending request."); - assert!(sender.sent_requests.is_empty(), "Expect no sent request."); - - // Issue a request! - let chan = oneshot::channel(); - let request = light::RemoteCallRequest { - block: Default::default(), - header: dummy_header(), - method: "test".into(), - call_data: vec![], - retry_count: Some(1), - }; - sender.request(Request::Call { request, sender: chan.0 }).unwrap(); - assert_eq!(1, sender.pending_requests.len(), "Expect one pending request."); - - let OutEvent::SendRequest { target, pending_response, .. } = - block_on(sender.next()).unwrap(); - assert!(target == peer0 || target == peer1, "Expect request to originate from known peer."); - - // And we should have one busy peer. - assert!({ - let (idle, busy): (Vec<_>, Vec<_>) = - sender.peers.iter().partition(|(_, info)| info.status == PeerStatus::Idle); - idle.len() == 1 && - busy.len() == 1 && (idle[0].0 == &peer0 || busy[0].0 == &peer0) && - (idle[0].0 == &peer1 || busy[0].0 == &peer1) - }); - - assert_eq!(0, sender.pending_requests.len(), "Expect no pending request."); - assert_eq!(1, sender.sent_requests.len(), "Expect one request to be sent."); - - // Report first attempt as timed out. - pending_response - .send(Err(RequestFailure::Network(OutboundFailure::Timeout))) - .unwrap(); - - // Expect a new request to be issued. - let OutEvent::SendRequest { pending_response, .. } = block_on(sender.next()).unwrap(); - - assert_eq!(1, sender.peers.len(), "Expect peer to be removed."); - assert_eq!(0, sender.pending_requests.len(), "Expect no request to be pending."); - assert_eq!(1, sender.sent_requests.len(), "Expect new request to be issued."); - - // Report second attempt as timed out. - pending_response - .send(Err(RequestFailure::Network(OutboundFailure::Timeout))) - .unwrap(); - assert_matches!( - block_on(async { poll!(sender.next()) }), - Poll::Pending, - "Expect sender to not issue another attempt.", - ); - assert_matches!( - block_on(chan.1).unwrap(), - Err(ClientError::RemoteFetchFailed), - "Expect request failure to be reported.", - ); - assert_eq!(0, sender.peers.len(), "Expect no peer to be left"); - assert_eq!(0, sender.pending_requests.len(), "Expect no request to be pending."); - assert_eq!(0, sender.sent_requests.len(), "Expect no other request to be in progress."); - } - - #[test] - fn disconnects_from_peer_on_incorrect_response() { - let peer = PeerId::random(); - - let (_peer_set, peer_set_handle) = peerset(); - let mut sender = LightClientRequestSender::::new( - &protocol_id(), - Arc::new(crate::light_client_requests::tests::DummyFetchChecker { - ok: false, - // ^--- Making sure the response data check fails. - _mark: std::marker::PhantomData, - }), - peer_set_handle, - ); - - sender.inject_connected(peer); - assert_eq!(1, sender.peers.len(), "Expect one peer."); - - let chan = oneshot::channel(); - let request = light::RemoteCallRequest { - block: Default::default(), - header: dummy_header(), - method: "test".into(), - call_data: vec![], - retry_count: Some(1), - }; - sender.request(Request::Call { request, sender: chan.0 }).unwrap(); - - assert_eq!(1, sender.pending_requests.len(), "Expect one pending request."); - assert_eq!(0, sender.sent_requests.len(), "Expect zero sent requests."); - - let OutEvent::SendRequest { pending_response, .. } = block_on(sender.next()).unwrap(); - assert_eq!(0, sender.pending_requests.len(), "Expect zero pending requests."); - assert_eq!(1, sender.sent_requests.len(), "Expect one sent request."); - - let response = { - let r = schema::v1::light::RemoteCallResponse { proof: empty_proof() }; - let response = schema::v1::light::Response { - response: Some(schema::v1::light::response::Response::RemoteCallResponse(r)), - }; - let mut data = Vec::new(); - response.encode(&mut data).unwrap(); - data - }; - - pending_response.send(Ok(response)).unwrap(); - - assert_matches!( - block_on(async { poll!(sender.next()) }), - Poll::Pending, - "Expect sender to not issue another attempt, given that there is no peer left.", - ); - - assert!(sender.peers.is_empty(), "Expect no peers to be left."); - assert_eq!(1, sender.pending_requests.len(), "Expect request to be pending again."); - assert_eq!(0, sender.sent_requests.len(), "Expect no request to be sent."); - } - - #[test] - fn disconnects_from_peer_on_wrong_response_type() { - let peer = PeerId::random(); - let (_peer_set, peer_set_handle) = peerset(); - let mut sender = LightClientRequestSender::::new( - &protocol_id(), - Arc::new(crate::light_client_requests::tests::DummyFetchChecker { - ok: true, - _mark: std::marker::PhantomData, - }), - peer_set_handle, - ); - - sender.inject_connected(peer); - assert_eq!(1, sender.peers.len(), "Expect one peer."); - - let chan = oneshot::channel(); - let request = light::RemoteCallRequest { - block: Default::default(), - header: dummy_header(), - method: "test".into(), - call_data: vec![], - retry_count: Some(1), - }; - sender.request(Request::Call { request, sender: chan.0 }).unwrap(); - - assert_eq!(1, sender.pending_requests.len()); - assert_eq!(0, sender.sent_requests.len()); - let OutEvent::SendRequest { pending_response, .. } = block_on(sender.next()).unwrap(); - assert_eq!(0, sender.pending_requests.len(), "Expect zero pending requests."); - assert_eq!(1, sender.sent_requests.len(), "Expect one sent request."); - - let response = { - let r = schema::v1::light::RemoteReadResponse { proof: empty_proof() }; // Not a RemoteCallResponse! - let response = schema::v1::light::Response { - response: Some(schema::v1::light::response::Response::RemoteReadResponse(r)), - }; - let mut data = Vec::new(); - response.encode(&mut data).unwrap(); - data - }; - - pending_response.send(Ok(response)).unwrap(); - assert_matches!( - block_on(async { poll!(sender.next()) }), - Poll::Pending, - "Expect sender to not issue another attempt, given that there is no peer left.", - ); - - assert!(sender.peers.is_empty(), "Expect no peers to be left."); - assert_eq!(1, sender.pending_requests.len(), "Expect request to be pending again."); - assert_eq!(0, sender.sent_requests.len(), "Expect no request to be sent."); - } - - #[test] - fn receives_remote_failure_after_retry_count_failures() { - let peers = (0..4).map(|_| PeerId::random()).collect::>(); - - let (_peer_set, peer_set_handle) = peerset(); - let mut sender = LightClientRequestSender::::new( - &protocol_id(), - Arc::new(crate::light_client_requests::tests::DummyFetchChecker { - ok: false, - // ^--- Making sure the response data check fails. - _mark: std::marker::PhantomData, - }), - peer_set_handle, - ); - - for peer in &peers { - sender.inject_connected(*peer); - } - assert_eq!(4, sender.peers.len(), "Expect four peers."); - - let mut chan = oneshot::channel(); - let request = light::RemoteCallRequest { - block: Default::default(), - header: dummy_header(), - method: "test".into(), - call_data: vec![], - retry_count: Some(3), // Attempt up to three retries. - }; - sender.request(Request::Call { request, sender: chan.0 }).unwrap(); - - assert_eq!(1, sender.pending_requests.len()); - assert_eq!(0, sender.sent_requests.len()); - let mut pending_response = match block_on(sender.next()).unwrap() { - OutEvent::SendRequest { pending_response, .. } => Some(pending_response), - }; - assert_eq!(0, sender.pending_requests.len(), "Expect zero pending requests."); - assert_eq!(1, sender.sent_requests.len(), "Expect one sent request."); - - for (i, _peer) in peers.iter().enumerate() { - // Construct an invalid response - let response = { - let r = schema::v1::light::RemoteCallResponse { proof: empty_proof() }; - let response = schema::v1::light::Response { - response: Some(schema::v1::light::response::Response::RemoteCallResponse(r)), - }; - let mut data = Vec::new(); - response.encode(&mut data).unwrap(); - data - }; - pending_response.take().unwrap().send(Ok(response)).unwrap(); - - if i < 3 { - pending_response = match block_on(sender.next()).unwrap() { - OutEvent::SendRequest { pending_response, .. } => Some(pending_response), - }; - assert_matches!(chan.1.try_recv(), Ok(None)) - } else { - // Last peer and last attempt. - assert_matches!( - block_on(async { poll!(sender.next()) }), - Poll::Pending, - "Expect sender to not issue another attempt, given that there is no peer left.", - ); - assert_matches!(chan.1.try_recv(), Ok(Some(Err(ClientError::RemoteFetchFailed)))) - } - } - } - - fn issue_request(request: Request) { - let peer = PeerId::random(); - - let (_peer_set, peer_set_handle) = peerset(); - let mut sender = LightClientRequestSender::::new( - &protocol_id(), - Arc::new(crate::light_client_requests::tests::DummyFetchChecker { - ok: true, - _mark: std::marker::PhantomData, - }), - peer_set_handle, - ); - - sender.inject_connected(peer); - assert_eq!(1, sender.peers.len(), "Expect one peer."); - - let response = match request { - Request::Body { .. } => unimplemented!(), - Request::Header { .. } => { - let r = schema::v1::light::RemoteHeaderResponse { - header: dummy_header().encode(), - proof: empty_proof(), - }; - schema::v1::light::Response { - response: Some(schema::v1::light::response::Response::RemoteHeaderResponse(r)), - } - }, - Request::Read { .. } => { - let r = schema::v1::light::RemoteReadResponse { proof: empty_proof() }; - schema::v1::light::Response { - response: Some(schema::v1::light::response::Response::RemoteReadResponse(r)), - } - }, - Request::ReadChild { .. } => { - let r = schema::v1::light::RemoteReadResponse { proof: empty_proof() }; - schema::v1::light::Response { - response: Some(schema::v1::light::response::Response::RemoteReadResponse(r)), - } - }, - Request::Call { .. } => { - let r = schema::v1::light::RemoteCallResponse { proof: empty_proof() }; - schema::v1::light::Response { - response: Some(schema::v1::light::response::Response::RemoteCallResponse(r)), - } - }, - Request::Changes { .. } => { - let r = schema::v1::light::RemoteChangesResponse { - max: std::iter::repeat(1).take(32).collect(), - proof: Vec::new(), - roots: Vec::new(), - roots_proof: empty_proof(), - }; - schema::v1::light::Response { - response: Some(schema::v1::light::response::Response::RemoteChangesResponse(r)), - } - }, - }; - - let response = { - let mut data = Vec::new(); - response.encode(&mut data).unwrap(); - data - }; - - sender.request(request).unwrap(); - - assert_eq!(1, sender.pending_requests.len()); - assert_eq!(0, sender.sent_requests.len()); - let OutEvent::SendRequest { pending_response, .. } = block_on(sender.next()).unwrap(); - assert_eq!(0, sender.pending_requests.len()); - assert_eq!(1, sender.sent_requests.len()); - - pending_response.send(Ok(response)).unwrap(); - assert_matches!( - block_on(async { poll!(sender.next()) }), - Poll::Pending, - "Expect sender to not issue another attempt, given that there is no peer left.", - ); - - assert_eq!(0, sender.pending_requests.len()); - assert_eq!(0, sender.sent_requests.len()) - } - - #[test] - fn receives_remote_call_response() { - let mut chan = oneshot::channel(); - let request = light::RemoteCallRequest { - block: Default::default(), - header: dummy_header(), - method: "test".into(), - call_data: vec![], - retry_count: None, - }; - issue_request(Request::Call { request, sender: chan.0 }); - assert_matches!(chan.1.try_recv(), Ok(Some(Ok(_)))) - } - - #[test] - fn receives_remote_read_response() { - let mut chan = oneshot::channel(); - let request = light::RemoteReadRequest { - header: dummy_header(), - block: Default::default(), - keys: vec![b":key".to_vec()], - retry_count: None, - }; - issue_request(Request::Read { request, sender: chan.0 }); - assert_matches!(chan.1.try_recv(), Ok(Some(Ok(_)))) - } - - #[test] - fn receives_remote_read_child_response() { - let mut chan = oneshot::channel(); - let child_info = ChildInfo::new_default(&b":child_storage:default:sub"[..]); - let request = light::RemoteReadChildRequest { - header: dummy_header(), - block: Default::default(), - storage_key: child_info.prefixed_storage_key(), - keys: vec![b":key".to_vec()], - retry_count: None, - }; - issue_request(Request::ReadChild { request, sender: chan.0 }); - assert_matches!(chan.1.try_recv(), Ok(Some(Ok(_)))) - } - - #[test] - fn receives_remote_header_response() { - let mut chan = oneshot::channel(); - let request = light::RemoteHeaderRequest { - cht_root: Default::default(), - block: 1, - retry_count: None, - }; - issue_request(Request::Header { request, sender: chan.0 }); - assert_matches!(chan.1.try_recv(), Ok(Some(Ok(_)))) - } - - #[test] - fn receives_remote_changes_response() { - let mut chan = oneshot::channel(); - let request = light::RemoteChangesRequest { - changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange { - zero: (0, Default::default()), - end: None, - config: Some(sp_core::ChangesTrieConfiguration::new(4, 2)), - }], - first_block: (1, Default::default()), - last_block: (100, Default::default()), - max_block: (100, Default::default()), - tries_roots: (1, Default::default(), Vec::new()), - key: Vec::new(), - storage_key: None, - retry_count: None, - }; - issue_request(Request::Changes { request, sender: chan.0 }); - assert_matches!(chan.1.try_recv(), Ok(Some(Ok(_)))) - } -} diff --git a/client/network/src/network_state.rs b/client/network/src/network_state.rs index 6f5f031bf35df..57073c57afa69 100644 --- a/client/network/src/network_state.rs +++ b/client/network/src/network_state.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,7 +20,10 @@ //! //! **Warning**: These APIs are not stable. -use libp2p::{core::ConnectedPoint, Multiaddr}; +use libp2p::{ + core::{ConnectedPoint, Endpoint as CoreEndpoint}, + Multiaddr, +}; use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, @@ -80,7 +83,7 @@ pub struct NotConnectedPeer { #[serde(rename_all = "camelCase")] pub enum PeerEndpoint { /// We are dialing the given address. - Dialing(Multiaddr), + Dialing(Multiaddr, Endpoint), /// We are listening. Listening { /// Local address of the connection. @@ -90,12 +93,32 @@ pub enum PeerEndpoint { }, } +/// Part of the `NetworkState` struct. Unstable. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Endpoint { + /// The socket comes from a dialer. + Dialer, + /// The socket comes from a listener. + Listener, +} + impl From for PeerEndpoint { fn from(endpoint: ConnectedPoint) -> Self { match endpoint { - ConnectedPoint::Dialer { address } => Self::Dialing(address), + ConnectedPoint::Dialer { address, role_override } => + Self::Dialing(address, role_override.into()), ConnectedPoint::Listener { local_addr, send_back_addr } => Self::Listening { local_addr, send_back_addr }, } } } + +impl From for Endpoint { + fn from(endpoint: CoreEndpoint) -> Self { + match endpoint { + CoreEndpoint::Dialer => Self::Dialer, + CoreEndpoint::Listener => Self::Listener, + } + } +} diff --git a/client/network/src/on_demand_layer.rs b/client/network/src/on_demand_layer.rs deleted file mode 100644 index eaeb0bee98f2c..0000000000000 --- a/client/network/src/on_demand_layer.rs +++ /dev/null @@ -1,241 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! On-demand requests service. - -use crate::light_client_requests; - -use futures::{channel::oneshot, prelude::*}; -use parking_lot::Mutex; -use sc_client_api::{ - ChangesProof, FetchChecker, Fetcher, RemoteBodyRequest, RemoteCallRequest, - RemoteChangesRequest, RemoteHeaderRequest, RemoteReadChildRequest, RemoteReadRequest, - StorageProof, -}; -use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; -use sp_blockchain::Error as ClientError; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; -use std::{ - collections::HashMap, - pin::Pin, - sync::Arc, - task::{Context, Poll}, -}; - -/// Implements the `Fetcher` trait of the client. Makes it possible for the light client to perform -/// network requests for some state. -/// -/// This implementation stores all the requests in a queue. The network, in parallel, is then -/// responsible for pulling elements out of that queue and fulfilling them. -pub struct OnDemand { - /// Objects that checks whether what has been retrieved is correct. - checker: Arc>, - - /// Queue of requests. Set to `Some` at initialization, then extracted by the network. - /// - /// Note that a better alternative would be to use a MPMC queue here, and add a `poll` method - /// from the `OnDemand`. However there exists no popular implementation of MPMC channels in - /// asynchronous Rust at the moment - requests_queue: - Mutex>>>, - - /// Sending side of `requests_queue`. - requests_send: TracingUnboundedSender>, -} - -#[derive(Debug, thiserror::Error)] -#[error("AlwaysBadChecker")] -struct ErrorAlwaysBadChecker; - -impl Into for ErrorAlwaysBadChecker { - fn into(self) -> ClientError { - ClientError::Application(Box::new(self)) - } -} - -/// Dummy implementation of `FetchChecker` that always assumes that responses are bad. -/// -/// Considering that it is the responsibility of the client to build the fetcher, it can use this -/// implementation if it knows that it will never perform any request. -#[derive(Default, Clone)] -pub struct AlwaysBadChecker; - -impl FetchChecker for AlwaysBadChecker { - fn check_header_proof( - &self, - _request: &RemoteHeaderRequest, - _remote_header: Option, - _remote_proof: StorageProof, - ) -> Result { - Err(ErrorAlwaysBadChecker.into()) - } - - fn check_read_proof( - &self, - _request: &RemoteReadRequest, - _remote_proof: StorageProof, - ) -> Result, Option>>, ClientError> { - Err(ErrorAlwaysBadChecker.into()) - } - - fn check_read_child_proof( - &self, - _request: &RemoteReadChildRequest, - _remote_proof: StorageProof, - ) -> Result, Option>>, ClientError> { - Err(ErrorAlwaysBadChecker.into()) - } - - fn check_execution_proof( - &self, - _request: &RemoteCallRequest, - _remote_proof: StorageProof, - ) -> Result, ClientError> { - Err(ErrorAlwaysBadChecker.into()) - } - - fn check_changes_proof( - &self, - _request: &RemoteChangesRequest, - _remote_proof: ChangesProof, - ) -> Result, u32)>, ClientError> { - Err(ErrorAlwaysBadChecker.into()) - } - - fn check_body_proof( - &self, - _request: &RemoteBodyRequest, - _body: Vec, - ) -> Result, ClientError> { - Err(ErrorAlwaysBadChecker.into()) - } -} - -impl OnDemand -where - B::Header: HeaderT, -{ - /// Creates new on-demand service. - pub fn new(checker: Arc>) -> Self { - let (requests_send, requests_queue) = tracing_unbounded("mpsc_ondemand"); - let requests_queue = Mutex::new(Some(requests_queue)); - - Self { checker, requests_queue, requests_send } - } - - /// Get checker reference. - pub fn checker(&self) -> &Arc> { - &self.checker - } - - /// Extracts the queue of requests. - /// - /// Whenever one of the methods of the `Fetcher` trait is called, an element is pushed on this - /// channel. - /// - /// If this function returns `None`, that means that the receiver has already been extracted in - /// the past, and therefore that something already handles the requests. - pub(crate) fn extract_receiver( - &self, - ) -> Option>> { - self.requests_queue.lock().take() - } -} - -impl Fetcher for OnDemand -where - B: BlockT, - B::Header: HeaderT, -{ - type RemoteHeaderResult = RemoteResponse; - type RemoteReadResult = RemoteResponse, Option>>>; - type RemoteCallResult = RemoteResponse>; - type RemoteChangesResult = RemoteResponse, u32)>>; - type RemoteBodyResult = RemoteResponse>; - - fn remote_header(&self, request: RemoteHeaderRequest) -> Self::RemoteHeaderResult { - let (sender, receiver) = oneshot::channel(); - let _ = self - .requests_send - .unbounded_send(light_client_requests::sender::Request::Header { request, sender }); - RemoteResponse { receiver } - } - - fn remote_read(&self, request: RemoteReadRequest) -> Self::RemoteReadResult { - let (sender, receiver) = oneshot::channel(); - let _ = self - .requests_send - .unbounded_send(light_client_requests::sender::Request::Read { request, sender }); - RemoteResponse { receiver } - } - - fn remote_read_child( - &self, - request: RemoteReadChildRequest, - ) -> Self::RemoteReadResult { - let (sender, receiver) = oneshot::channel(); - let _ = self - .requests_send - .unbounded_send(light_client_requests::sender::Request::ReadChild { request, sender }); - RemoteResponse { receiver } - } - - fn remote_call(&self, request: RemoteCallRequest) -> Self::RemoteCallResult { - let (sender, receiver) = oneshot::channel(); - let _ = self - .requests_send - .unbounded_send(light_client_requests::sender::Request::Call { request, sender }); - RemoteResponse { receiver } - } - - fn remote_changes( - &self, - request: RemoteChangesRequest, - ) -> Self::RemoteChangesResult { - let (sender, receiver) = oneshot::channel(); - let _ = self - .requests_send - .unbounded_send(light_client_requests::sender::Request::Changes { request, sender }); - RemoteResponse { receiver } - } - - fn remote_body(&self, request: RemoteBodyRequest) -> Self::RemoteBodyResult { - let (sender, receiver) = oneshot::channel(); - let _ = self - .requests_send - .unbounded_send(light_client_requests::sender::Request::Body { request, sender }); - RemoteResponse { receiver } - } -} - -/// Future for an on-demand remote call response. -pub struct RemoteResponse { - receiver: oneshot::Receiver>, -} - -impl Future for RemoteResponse { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - match self.receiver.poll_unpin(cx) { - Poll::Ready(Ok(res)) => Poll::Ready(res), - Poll::Ready(Err(_)) => Poll::Ready(Err(ClientError::RemoteFetchCancelled)), - Poll::Pending => Poll::Pending, - } - } -} diff --git a/client/network/src/peer_info.rs b/client/network/src/peer_info.rs index 141cc59247d1a..e04d006f50501 100644 --- a/client/network/src/peer_info.rs +++ b/client/network/src/peer_info.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,24 +16,26 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::utils::interval; use fnv::FnvHashMap; use futures::prelude::*; use libp2p::{ core::{ - connection::{ConnectionId, ListenerId}, - either::EitherOutput, - ConnectedPoint, PeerId, PublicKey, + connection::ConnectionId, either::EitherOutput, transport::ListenerId, ConnectedPoint, + PeerId, PublicKey, }, - identify::{Identify, IdentifyConfig, IdentifyEvent, IdentifyInfo}, - ping::{Ping, PingConfig, PingEvent, PingSuccess}, + identify::{ + Behaviour as Identify, Config as IdentifyConfig, Event as IdentifyEvent, + Info as IdentifyInfo, + }, + ping::{Behaviour as Ping, Config as PingConfig, Event as PingEvent, Success as PingSuccess}, swarm::{ - IntoProtocolsHandler, IntoProtocolsHandlerSelect, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, ProtocolsHandler, + ConnectionHandler, IntoConnectionHandler, IntoConnectionHandlerSelect, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, }, Multiaddr, }; use log::{debug, error, trace}; +use sc_network_common::utils::interval; use smallvec::SmallVec; use std::{ collections::hash_map::Entry, @@ -170,14 +172,14 @@ pub enum PeerInfoEvent { } impl NetworkBehaviour for PeerInfoBehaviour { - type ProtocolsHandler = IntoProtocolsHandlerSelect< - ::ProtocolsHandler, - ::ProtocolsHandler, + type ConnectionHandler = IntoConnectionHandlerSelect< + ::ConnectionHandler, + ::ConnectionHandler, >; type OutEvent = PeerInfoEvent; - fn new_handler(&mut self) -> Self::ProtocolsHandler { - IntoProtocolsHandler::select(self.ping.new_handler(), self.identify.new_handler()) + fn new_handler(&mut self) -> Self::ConnectionHandler { + IntoConnectionHandler::select(self.ping.new_handler(), self.identify.new_handler()) } fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { @@ -186,9 +188,27 @@ impl NetworkBehaviour for PeerInfoBehaviour { list } - fn inject_connected(&mut self, peer_id: &PeerId) { - self.ping.inject_connected(peer_id); - self.identify.inject_connected(peer_id); + fn inject_address_change( + &mut self, + peer_id: &PeerId, + conn: &ConnectionId, + old: &ConnectedPoint, + new: &ConnectedPoint, + ) { + self.ping.inject_address_change(peer_id, conn, old, new); + self.identify.inject_address_change(peer_id, conn, old, new); + + if let Some(entry) = self.nodes_info.get_mut(peer_id) { + if let Some(endpoint) = entry.endpoints.iter_mut().find(|e| e == &old) { + *endpoint = new.clone(); + } else { + error!(target: "sub-libp2p", + "Unknown address change for peer {:?} from {:?} to {:?}", peer_id, old, new); + } + } else { + error!(target: "sub-libp2p", + "Unknown peer {:?} to change address from {:?} to {:?}", peer_id, old, new); + } } fn inject_connection_established( @@ -196,9 +216,23 @@ impl NetworkBehaviour for PeerInfoBehaviour { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + failed_addresses: Option<&Vec>, + other_established: usize, ) { - self.ping.inject_connection_established(peer_id, conn, endpoint); - self.identify.inject_connection_established(peer_id, conn, endpoint); + self.ping.inject_connection_established( + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ); + self.identify.inject_connection_established( + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ); match self.nodes_info.entry(*peer_id) { Entry::Vacant(e) => { e.insert(NodeInfo::new(endpoint.clone())); @@ -220,11 +254,29 @@ impl NetworkBehaviour for PeerInfoBehaviour { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + handler: ::Handler, + remaining_established: usize, ) { - self.ping.inject_connection_closed(peer_id, conn, endpoint); - self.identify.inject_connection_closed(peer_id, conn, endpoint); + let (ping_handler, identity_handler) = handler.into_inner(); + self.identify.inject_connection_closed( + peer_id, + conn, + endpoint, + identity_handler, + remaining_established, + ); + self.ping.inject_connection_closed( + peer_id, + conn, + endpoint, + ping_handler, + remaining_established, + ); if let Some(entry) = self.nodes_info.get_mut(peer_id) { + if remaining_established == 0 { + entry.info_expire = Some(Instant::now() + CACHE_EXPIRE); + } entry.endpoints.retain(|ep| ep != endpoint) } else { error!(target: "sub-libp2p", @@ -232,23 +284,11 @@ impl NetworkBehaviour for PeerInfoBehaviour { } } - fn inject_disconnected(&mut self, peer_id: &PeerId) { - self.ping.inject_disconnected(peer_id); - self.identify.inject_disconnected(peer_id); - - if let Some(entry) = self.nodes_info.get_mut(peer_id) { - entry.info_expire = Some(Instant::now() + CACHE_EXPIRE); - } else { - error!(target: "sub-libp2p", - "Disconnected from node we were not connected to {:?}", peer_id); - } - } - fn inject_event( &mut self, peer_id: PeerId, connection: ConnectionId, - event: <::Handler as ProtocolsHandler>::OutEvent, + event: <::Handler as ConnectionHandler>::OutEvent, ) { match event { EitherOutput::First(event) => self.ping.inject_event(peer_id, connection, event), @@ -256,19 +296,15 @@ impl NetworkBehaviour for PeerInfoBehaviour { } } - fn inject_addr_reach_failure( + fn inject_dial_failure( &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - error: &dyn std::error::Error, + peer_id: Option, + handler: Self::ConnectionHandler, + error: &libp2p::swarm::DialError, ) { - self.ping.inject_addr_reach_failure(peer_id, addr, error); - self.identify.inject_addr_reach_failure(peer_id, addr, error); - } - - fn inject_dial_failure(&mut self, peer_id: &PeerId) { - self.ping.inject_dial_failure(peer_id); - self.identify.inject_dial_failure(peer_id); + let (ping_handler, identity_handler) = handler.into_inner(); + self.identify.inject_dial_failure(peer_id, identity_handler, error); + self.ping.inject_dial_failure(peer_id, ping_handler, error); } fn inject_new_listener(&mut self, id: ListenerId) { @@ -296,6 +332,18 @@ impl NetworkBehaviour for PeerInfoBehaviour { self.identify.inject_expired_external_addr(addr); } + fn inject_listen_failure( + &mut self, + local_addr: &Multiaddr, + send_back_addr: &Multiaddr, + handler: Self::ConnectionHandler, + ) { + let (ping_handler, identity_handler) = handler.into_inner(); + self.identify + .inject_listen_failure(local_addr, send_back_addr, identity_handler); + self.ping.inject_listen_failure(local_addr, send_back_addr, ping_handler); + } + fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn error::Error + 'static)) { self.ping.inject_listener_error(id, err); self.identify.inject_listener_error(id, err); @@ -309,13 +357,8 @@ impl NetworkBehaviour for PeerInfoBehaviour { fn poll( &mut self, cx: &mut Context, - params: &mut impl PollParameters - ) -> Poll< - NetworkBehaviourAction< - <::Handler as ProtocolsHandler>::InEvent, - Self::OutEvent - > - >{ + params: &mut impl PollParameters, + ) -> Poll> { loop { match self.ping.poll(cx, params) { Poll::Pending => break, @@ -324,10 +367,11 @@ impl NetworkBehaviour for PeerInfoBehaviour { self.handle_ping_report(&peer, rtt) } }, - Poll::Ready(NetworkBehaviourAction::DialAddress { address }) => - return Poll::Ready(NetworkBehaviourAction::DialAddress { address }), - Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition }) => - return Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition }), + Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) => { + let handler = + IntoConnectionHandler::select(handler, self.identify.new_handler()); + return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) + }, Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, handler, event }) => return Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, @@ -362,10 +406,10 @@ impl NetworkBehaviour for PeerInfoBehaviour { IdentifyEvent::Pushed { .. } => {}, IdentifyEvent::Sent { .. } => {}, }, - Poll::Ready(NetworkBehaviourAction::DialAddress { address }) => - return Poll::Ready(NetworkBehaviourAction::DialAddress { address }), - Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition }) => - return Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition }), + Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) => { + let handler = IntoConnectionHandler::select(self.ping.new_handler(), handler); + return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) + }, Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, handler, event }) => return Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index e22d96f32aeb8..10eb31b595253 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,51 +16,42 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{ - chain::Client, - config::{self, ProtocolId, WarpSyncProvider}, - error, - request_responses::RequestFailure, - schema::v1::StateResponse, - utils::{interval, LruHashSet}, - warp_request_handler::EncodedProof, -}; +use crate::config; use bytes::Bytes; use codec::{Decode, DecodeAll, Encode}; -use futures::{channel::oneshot, prelude::*}; +use futures::prelude::*; use libp2p::{ - core::{ - connection::{ConnectionId, ListenerId}, - ConnectedPoint, - }, - request_response::OutboundFailure, + core::{connection::ConnectionId, transport::ListenerId, ConnectedPoint}, swarm::{ - IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, - ProtocolsHandler, + ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, + PollParameters, }, Multiaddr, PeerId, }; -use log::{debug, error, info, log, trace, warn, Level}; -use message::{ - generic::{Message as GenericMessage, Roles}, - BlockAnnounce, Message, -}; +use log::{debug, error, log, trace, warn, Level}; +use lru::LruCache; +use message::{generic::Message as GenericMessage, Message}; use notifications::{Notifications, NotificationsOut}; use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; -use prost::Message as _; -use sc_consensus::import_queue::{BlockImportError, BlockImportStatus, IncomingBlock, Origin}; +use sc_client_api::HeaderBackend; +use sc_network_common::{ + config::NonReservedPeerMode, + error, + protocol::{role::Roles, ProtocolName}, + sync::{ + message::{BlockAnnounce, BlockAnnouncesHandshake, BlockData, BlockResponse, BlockState}, + BadPeer, ChainSync, PollBlockAnnounceValidation, SyncStatus, + }, + utils::{interval, LruHashSet}, +}; use sp_arithmetic::traits::SaturatedConversion; -use sp_consensus::{block_validation::BlockAnnounceValidator, BlockOrigin}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, CheckedSub, Header as HeaderT, NumberFor, Zero}, - Justifications, }; use std::{ - borrow::Cow, collections::{HashMap, HashSet, VecDeque}, - convert::TryFrom as _, io, iter, num::NonZeroUsize, pin::Pin, @@ -68,13 +59,10 @@ use std::{ task::Poll, time, }; -use sync::{ChainSync, Status as SyncStatus}; mod notifications; -pub mod event; pub mod message; -pub mod sync; pub use notifications::{NotificationsSink, NotifsHandlerError, Ready}; @@ -83,8 +71,6 @@ const TICK_TIMEOUT: time::Duration = time::Duration::from_millis(1100); /// Maximum number of known block hashes to keep for a peer. const MAX_KNOWN_BLOCKS: usize = 1024; // ~32kb per peer + LruHashSet overhead -/// Maximum allowed size for a block announce. -const MAX_BLOCK_ANNOUNCE_SIZE: u64 = 1024 * 1024; /// Maximum size used for notifications in the block announce and transaction protocols. // Must be equal to `max(MAX_BLOCK_ANNOUNCE_SIZE, MAX_TRANSACTIONS_SIZE)`. @@ -103,18 +89,12 @@ const LIGHT_MAXIMAL_BLOCKS_DIFFERENCE: u64 = 8192; mod rep { use sc_peerset::ReputationChange as Rep; - /// Reputation change when a peer doesn't respond in time to our messages. - pub const TIMEOUT: Rep = Rep::new(-(1 << 10), "Request timeout"); - /// Reputation change when a peer refuses a request. - pub const REFUSED: Rep = Rep::new(-(1 << 10), "Request refused"); /// Reputation change when we are a light client and a peer is behind us. pub const PEER_BEHIND_US_LIGHT: Rep = Rep::new(-(1 << 8), "Useless for a light peer"); /// We received a message that failed to decode. pub const BAD_MESSAGE: Rep = Rep::new(-(1 << 12), "Bad message"); /// Peer has different genesis. pub const GENESIS_MISMATCH: Rep = Rep::new_fatal("Genesis mismatch"); - /// Peer is on unsupported protocol version. - pub const BAD_PROTOCOL: Rep = Rep::new_fatal("Unsupported protocol"); /// Peer role does not match (e.g. light peer connecting to another light peer). pub const BAD_ROLE: Rep = Rep::new_fatal("Unsupported role"); /// Peer send us a block announcement that failed at validation. @@ -132,21 +112,22 @@ impl Metrics { fn register(r: &Registry) -> Result { Ok(Self { peers: { - let g = Gauge::new("sync_peers", "Number of peers we sync with")?; + let g = Gauge::new("substrate_sync_peers", "Number of peers we sync with")?; register(g, r)? }, queued_blocks: { - let g = Gauge::new("sync_queued_blocks", "Number of blocks in import queue")?; + let g = + Gauge::new("substrate_sync_queued_blocks", "Number of blocks in import queue")?; register(g, r)? }, fork_targets: { - let g = Gauge::new("sync_fork_targets", "Number of fork sync targets")?; + let g = Gauge::new("substrate_sync_fork_targets", "Number of fork sync targets")?; register(g, r)? }, justifications: { let g = GaugeVec::new( Opts::new( - "sync_extra_justifications", + "substrate_sync_extra_justifications", "Number of extra justifications requests", ), &["status"], @@ -158,26 +139,37 @@ impl Metrics { } // Lock must always be taken in order declared here. -pub struct Protocol { +pub struct Protocol { /// Interval at which we call `tick`. tick_timeout: Pin + Send>>, /// Pending list of messages to return from `poll` as a priority. pending_messages: VecDeque>, - config: ProtocolConfig, + /// Assigned roles. + roles: Roles, genesis_hash: B::Hash, - sync: ChainSync, - // All connected peers + /// State machine that handles the list of in-progress requests. Only full node peers are + /// registered. + chain_sync: Box>, + // All connected peers. Contains both full and light node peers. peers: HashMap>, - chain: Arc>, + chain: Arc, /// List of nodes for which we perform additional logging because they are important for the /// user. important_peers: HashSet, + /// List of nodes that should never occupy peer slots. + default_peers_set_no_slot_peers: HashSet, + /// Actual list of connected no-slot nodes. + default_peers_set_no_slot_connected_peers: HashSet, + /// Value that was passed as part of the configuration. Used to cap the number of full nodes. + default_peers_set_num_full: usize, + /// Number of slots to allocate to light nodes. + default_peers_set_num_light: usize, /// Used to report reputation changes. peerset_handle: sc_peerset::PeersetHandle, /// Handles opening the unique substream and sending and receiving raw messages. behaviour: Notifications, /// List of notifications protocols that have been registered. - notification_protocols: Vec>, + notification_protocols: Vec, /// If we receive a new "substream open" event that contains an invalid handshake, we ask the /// inner layer to force-close the substream. Force-closing the substream will generate a /// "substream closed" event. This is a problem: since we can't propagate the "substream open" @@ -190,22 +182,13 @@ pub struct Protocol { /// The `PeerId`'s of all boot nodes. boot_node_ids: HashSet, /// A cache for the data that was associated to a block announcement. - block_announce_data_cache: lru::LruCache>, -} - -#[derive(Debug)] -enum PeerRequest { - Block(message::BlockRequest), - State, - WarpProof, + block_announce_data_cache: LruCache>, } /// Peer information #[derive(Debug)] struct Peer { info: PeerInfo, - /// Current request, if any. Started by emitting [`CustomMessageOutcome::BlockRequest`]. - request: Option<(PeerRequest, oneshot::Receiver, RequestFailure>>)>, /// Holds a set of blocks known to this peer. known_blocks: LruHashSet, } @@ -221,83 +204,21 @@ pub struct PeerInfo { pub best_number: ::Number, } -/// Configuration for the Substrate-specific part of the networking layer. -#[derive(Clone)] -pub struct ProtocolConfig { - /// Assigned roles. - pub roles: Roles, - /// Maximum number of peers to ask the same blocks in parallel. - pub max_parallel_downloads: u32, - /// Enable state sync. - pub sync_mode: config::SyncMode, -} - -impl ProtocolConfig { - fn sync_mode(&self) -> sync::SyncMode { - if self.roles.is_light() { - sync::SyncMode::Light - } else { - match self.sync_mode { - config::SyncMode::Full => sync::SyncMode::Full, - config::SyncMode::Fast { skip_proofs, storage_chain_mode } => - sync::SyncMode::LightState { skip_proofs, storage_chain_mode }, - config::SyncMode::Warp => sync::SyncMode::Warp, - } - } - } -} - -impl Default for ProtocolConfig { - fn default() -> ProtocolConfig { - Self { roles: Roles::FULL, max_parallel_downloads: 5, sync_mode: config::SyncMode::Full } - } -} - -/// Handshake sent when we open a block announces substream. -#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] -struct BlockAnnouncesHandshake { - /// Roles of the node. - roles: Roles, - /// Best block number. - best_number: NumberFor, - /// Best block hash. - best_hash: B::Hash, - /// Genesis block hash. - genesis_hash: B::Hash, -} - -impl BlockAnnouncesHandshake { - fn build( - protocol_config: &ProtocolConfig, - best_number: NumberFor, - best_hash: B::Hash, - genesis_hash: B::Hash, - ) -> Self { - Self { genesis_hash, roles: protocol_config.roles, best_number, best_hash } - } -} - -impl Protocol { +impl Protocol +where + B: BlockT, + Client: HeaderBackend + 'static, +{ /// Create a new instance. pub fn new( - config: ProtocolConfig, - chain: Arc>, - protocol_id: ProtocolId, + roles: Roles, + chain: Arc, network_config: &config::NetworkConfiguration, - notifications_protocols_handshakes: Vec>, - block_announce_validator: Box + Send>, metrics_registry: Option<&Registry>, - warp_sync_provider: Option>>, - ) -> error::Result<(Protocol, sc_peerset::PeersetHandle, Vec<(PeerId, Multiaddr)>)> { + chain_sync: Box>, + block_announces_protocol: sc_network_common::config::NonDefaultSetConfig, + ) -> error::Result<(Self, sc_peerset::PeersetHandle, Vec<(PeerId, Multiaddr)>)> { let info = chain.info(); - let sync = ChainSync::new( - config.sync_mode(), - chain.clone(), - block_announce_validator, - config.max_parallel_downloads, - warp_sync_provider, - ) - .map_err(Box::new)?; let boot_node_ids = { let mut list = HashSet::new(); @@ -324,6 +245,17 @@ impl Protocol { imp_p }; + let default_peers_set_no_slot_peers = { + let mut no_slot_p: HashSet = network_config + .default_peers_set + .reserved_nodes + .iter() + .map(|reserved| reserved.peer_id) + .collect(); + no_slot_p.shrink_to_fit(); + no_slot_p + }; + let mut known_addresses = Vec::new(); let (peerset, peerset_handle) = { @@ -333,13 +265,15 @@ impl Protocol { let mut default_sets_reserved = HashSet::new(); for reserved in network_config.default_peers_set.reserved_nodes.iter() { default_sets_reserved.insert(reserved.peer_id); - known_addresses.push((reserved.peer_id, reserved.multiaddr.clone())); + + if !reserved.multiaddr.is_empty() { + known_addresses.push((reserved.peer_id, reserved.multiaddr.clone())); + } } let mut bootnodes = Vec::with_capacity(network_config.boot_nodes.len()); for bootnode in network_config.boot_nodes.iter() { bootnodes.push(bootnode.peer_id); - known_addresses.push((bootnode.peer_id, bootnode.multiaddr.clone())); } // Set number 0 is used for block announces. @@ -349,7 +283,7 @@ impl Protocol { bootnodes, reserved_nodes: default_sets_reserved.clone(), reserved_only: network_config.default_peers_set.non_reserved_mode == - config::NonReservedPeerMode::Deny, + NonReservedPeerMode::Deny, }); for set_cfg in &network_config.extra_sets { @@ -360,7 +294,7 @@ impl Protocol { } let reserved_only = - set_cfg.set_config.non_reserved_mode == config::NonReservedPeerMode::Deny; + set_cfg.set_config.non_reserved_mode == NonReservedPeerMode::Deny; sets.push(sc_peerset::SetConfig { in_peers: set_cfg.set_config.in_peers, @@ -374,65 +308,56 @@ impl Protocol { sc_peerset::Peerset::from_config(sc_peerset::PeersetConfig { sets }) }; - let block_announces_protocol: Cow<'static, str> = Cow::from({ - let mut proto = String::new(); - proto.push_str("/"); - proto.push_str(protocol_id.as_ref()); - proto.push_str("/block-announces/1"); - proto - }); - let behaviour = { - let best_number = info.best_number; - let best_hash = info.best_hash; - let genesis_hash = info.genesis_hash; - - let block_announces_handshake = - BlockAnnouncesHandshake::::build(&config, best_number, best_hash, genesis_hash) - .encode(); - - let sync_protocol_config = notifications::ProtocolConfig { - name: block_announces_protocol, - fallback_names: Vec::new(), - handshake: block_announces_handshake, - max_notification_size: MAX_BLOCK_ANNOUNCE_SIZE, - }; - Notifications::new( peerset, - iter::once(sync_protocol_config).chain( - network_config.extra_sets.iter().zip(notifications_protocols_handshakes).map( - |(s, hs)| notifications::ProtocolConfig { - name: s.notifications_protocol.clone(), - fallback_names: s.fallback_names.clone(), - handshake: hs, - max_notification_size: s.max_notification_size, - }, - ), - ), + // NOTE: Block announcement protocol is still very much hardcoded into `Protocol`. + // This protocol must be the first notification protocol given to + // `Notifications` + iter::once(notifications::ProtocolConfig { + name: block_announces_protocol.notifications_protocol.clone(), + fallback_names: block_announces_protocol.fallback_names.clone(), + handshake: block_announces_protocol.handshake.as_ref().unwrap().to_vec(), + max_notification_size: block_announces_protocol.max_notification_size, + }) + .chain(network_config.extra_sets.iter().map(|s| notifications::ProtocolConfig { + name: s.notifications_protocol.clone(), + fallback_names: s.fallback_names.clone(), + handshake: s.handshake.as_ref().map_or(roles.encode(), |h| (*h).to_vec()), + max_notification_size: s.max_notification_size, + })), ) }; - let block_announce_data_cache = lru::LruCache::new( - network_config.default_peers_set.in_peers as usize + - network_config.default_peers_set.out_peers as usize, - ); + let cache_capacity = NonZeroUsize::new( + (network_config.default_peers_set.in_peers as usize + + network_config.default_peers_set.out_peers as usize) + .max(1), + ) + .expect("cache capacity is not zero"); + let block_announce_data_cache = LruCache::new(cache_capacity); let protocol = Self { tick_timeout: Box::pin(interval(TICK_TIMEOUT)), pending_messages: VecDeque::new(), - config, + roles, peers: HashMap::new(), chain, genesis_hash: info.genesis_hash, - sync, + chain_sync, important_peers, + default_peers_set_no_slot_peers, + default_peers_set_no_slot_connected_peers: HashSet::new(), + default_peers_set_num_full: network_config.default_peers_set_num_full as usize, + default_peers_set_num_light: { + let total = network_config.default_peers_set.out_peers + + network_config.default_peers_set.in_peers; + total.saturating_sub(network_config.default_peers_set_num_full) as usize + }, peerset_handle: peerset_handle.clone(), behaviour, - notification_protocols: network_config - .extra_sets - .iter() - .map(|s| s.notifications_protocol.clone()) + notification_protocols: iter::once(block_announces_protocol.notifications_protocol) + .chain(network_config.extra_sets.iter().map(|s| s.notifications_protocol.clone())) .collect(), bad_handshake_substreams: Default::default(), metrics: if let Some(r) = metrics_registry { @@ -452,25 +377,16 @@ impl Protocol { self.behaviour.open_peers() } - /// Returns the list of all the peers that the peerset currently requests us to be connected - /// to on the default set. - pub fn requested_peers(&self) -> impl Iterator { - self.behaviour.requested_peers(HARDCODED_PEERSETS_SYNC) - } - /// Returns the number of discovered nodes that we keep in memory. pub fn num_discovered_peers(&self) -> usize { self.behaviour.num_discovered_peers() } /// Disconnects the given peer if we are connected to it. - pub fn disconnect_peer(&mut self, peer_id: &PeerId, protocol_name: &str) { + pub fn disconnect_peer(&mut self, peer_id: &PeerId, protocol_name: ProtocolName) { if let Some(position) = self.notification_protocols.iter().position(|p| *p == protocol_name) { - self.behaviour.disconnect_peer( - peer_id, - sc_peerset::SetId::from(position + NUM_HARDCODED_PEERSETS), - ); + self.behaviour.disconnect_peer(peer_id, sc_peerset::SetId::from(position)); } else { warn!(target: "sub-libp2p", "disconnect_peer() with invalid protocol name") } @@ -483,59 +399,59 @@ impl Protocol { /// Returns the number of peers we're connected to. pub fn num_connected_peers(&self) -> usize { - self.peers.values().count() + self.peers.len() } /// Returns the number of peers we're connected to and that are being queried. pub fn num_active_peers(&self) -> usize { - self.peers.values().filter(|p| p.request.is_some()).count() + self.chain_sync.num_active_peers() } /// Current global sync state. pub fn sync_state(&self) -> SyncStatus { - self.sync.status() + self.chain_sync.status() } /// Target sync block number. pub fn best_seen_block(&self) -> Option> { - self.sync.status().best_seen_block + self.chain_sync.status().best_seen_block } /// Number of peers participating in syncing. pub fn num_sync_peers(&self) -> u32 { - self.sync.status().num_peers + self.chain_sync.status().num_peers } /// Number of blocks in the import queue. pub fn num_queued_blocks(&self) -> u32 { - self.sync.status().queued_blocks + self.chain_sync.status().queued_blocks } /// Number of downloaded blocks. pub fn num_downloaded_blocks(&self) -> usize { - self.sync.num_downloaded_blocks() + self.chain_sync.num_downloaded_blocks() } /// Number of active sync requests. pub fn num_sync_requests(&self) -> usize { - self.sync.num_sync_requests() + self.chain_sync.num_sync_requests() } /// Inform sync about new best imported block. pub fn new_best_block_imported(&mut self, hash: B::Hash, number: NumberFor) { debug!(target: "sync", "New best block imported {:?}/#{}", hash, number); - self.sync.update_chain_info(&hash, number); + self.chain_sync.update_chain_info(&hash, number); self.behaviour.set_notif_protocol_handshake( HARDCODED_PEERSETS_SYNC, - BlockAnnouncesHandshake::::build(&self.config, number, hash, self.genesis_hash) + BlockAnnouncesHandshake::::build(self.roles, number, hash, self.genesis_hash) .encode(), ); } fn update_peer_info(&mut self, who: &PeerId) { - if let Some(info) = self.sync.peer_info(who) { + if let Some(info) = self.chain_sync.peer_info(who) { if let Some(ref mut peer) = self.peers.get_mut(who) { peer.info.best_hash = info.best_hash; peer.info.best_number = info.best_number; @@ -548,14 +464,6 @@ impl Protocol { self.peers.iter().map(|(id, peer)| (id, &peer.info)) } - fn prepare_block_request( - &mut self, - who: PeerId, - request: message::BlockRequest, - ) -> CustomMessageOutcome { - prepare_block_request::(&mut self.peers, who, request) - } - /// Called by peer when it is disconnecting. /// /// Returns a result if the handshake of this peer was indeed accepted. @@ -567,12 +475,8 @@ impl Protocol { } if let Some(_peer_data) = self.peers.remove(&peer) { - if let Some(sync::OnBlockData::Import(origin, blocks)) = - self.sync.peer_disconnected(&peer) - { - self.pending_messages - .push_back(CustomMessageOutcome::BlockImport(origin, blocks)); - } + self.chain_sync.peer_disconnected(&peer); + self.default_peers_set_no_slot_connected_peers.remove(&peer); Ok(()) } else { Err(()) @@ -584,162 +488,6 @@ impl Protocol { self.peerset_handle.report_peer(who, reputation) } - /// Must be called in response to a [`CustomMessageOutcome::BlockRequest`] being emitted. - /// Must contain the same `PeerId` and request that have been emitted. - pub fn on_block_response( - &mut self, - peer_id: PeerId, - request: message::BlockRequest, - response: crate::schema::v1::BlockResponse, - ) -> CustomMessageOutcome { - let blocks = response - .blocks - .into_iter() - .map(|block_data| { - Ok(message::BlockData:: { - hash: Decode::decode(&mut block_data.hash.as_ref())?, - header: if !block_data.header.is_empty() { - Some(Decode::decode(&mut block_data.header.as_ref())?) - } else { - None - }, - body: if request.fields.contains(message::BlockAttributes::BODY) { - Some( - block_data - .body - .iter() - .map(|body| Decode::decode(&mut body.as_ref())) - .collect::, _>>()?, - ) - } else { - None - }, - indexed_body: if request.fields.contains(message::BlockAttributes::INDEXED_BODY) - { - Some(block_data.indexed_body) - } else { - None - }, - receipt: if !block_data.receipt.is_empty() { - Some(block_data.receipt) - } else { - None - }, - message_queue: if !block_data.message_queue.is_empty() { - Some(block_data.message_queue) - } else { - None - }, - justification: if !block_data.justification.is_empty() { - Some(block_data.justification) - } else if block_data.is_empty_justification { - Some(Vec::new()) - } else { - None - }, - justifications: if !block_data.justifications.is_empty() { - Some(DecodeAll::decode_all(&mut block_data.justifications.as_ref())?) - } else { - None - }, - }) - }) - .collect::, codec::Error>>(); - - let blocks = match blocks { - Ok(blocks) => blocks, - Err(err) => { - debug!(target: "sync", "Failed to decode block response from {}: {}", peer_id, err); - self.peerset_handle.report_peer(peer_id, rep::BAD_MESSAGE); - return CustomMessageOutcome::None - }, - }; - - let block_response = message::BlockResponse:: { id: request.id, blocks }; - - let blocks_range = || match ( - block_response - .blocks - .first() - .and_then(|b| b.header.as_ref().map(|h| h.number())), - block_response.blocks.last().and_then(|b| b.header.as_ref().map(|h| h.number())), - ) { - (Some(first), Some(last)) if first != last => format!(" ({}..{})", first, last), - (Some(first), Some(_)) => format!(" ({})", first), - _ => Default::default(), - }; - trace!(target: "sync", "BlockResponse {} from {} with {} blocks {}", - block_response.id, - peer_id, - block_response.blocks.len(), - blocks_range(), - ); - - if request.fields == message::BlockAttributes::JUSTIFICATION { - match self.sync.on_block_justification(peer_id, block_response) { - Ok(sync::OnBlockJustification::Nothing) => CustomMessageOutcome::None, - Ok(sync::OnBlockJustification::Import { peer, hash, number, justifications }) => - CustomMessageOutcome::JustificationImport(peer, hash, number, justifications), - Err(sync::BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } - } else { - match self.sync.on_block_data(&peer_id, Some(request), block_response) { - Ok(sync::OnBlockData::Import(origin, blocks)) => - CustomMessageOutcome::BlockImport(origin, blocks), - Ok(sync::OnBlockData::Request(peer, req)) => self.prepare_block_request(peer, req), - Err(sync::BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } - } - } - - /// Must be called in response to a [`CustomMessageOutcome::StateRequest`] being emitted. - /// Must contain the same `PeerId` and request that have been emitted. - pub fn on_state_response( - &mut self, - peer_id: PeerId, - response: StateResponse, - ) -> CustomMessageOutcome { - match self.sync.on_state_data(&peer_id, response) { - Ok(sync::OnStateData::Import(origin, block)) => - CustomMessageOutcome::BlockImport(origin, vec![block]), - Ok(sync::OnStateData::Request(peer, req)) => - prepare_state_request::(&mut self.peers, peer, req), - Err(sync::BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } - } - - /// Must be called in response to a [`CustomMessageOutcome::WarpSyncRequest`] being emitted. - /// Must contain the same `PeerId` and request that have been emitted. - pub fn on_warp_sync_response( - &mut self, - peer_id: PeerId, - response: crate::warp_request_handler::EncodedProof, - ) -> CustomMessageOutcome { - match self.sync.on_warp_sync_data(&peer_id, response) { - Ok(sync::OnWarpSyncData::WarpProofRequest(peer, req)) => - prepare_warp_sync_request::(&mut self.peers, peer, req), - Ok(sync::OnWarpSyncData::StateRequest(peer, req)) => - prepare_state_request::(&mut self.peers, peer, req), - Err(sync::BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } - } - /// Perform time based maintenance. /// /// > **Note**: This method normally doesn't have to be called except for testing purposes. @@ -788,7 +536,7 @@ impl Protocol { return Err(()) } - if self.config.roles.is_light() { + if self.roles.is_light() { // we're not interested in light peers if status.roles.is_light() { debug!(target: "sync", "Peer {} is unable to serve light requests", who); @@ -811,22 +559,44 @@ impl Protocol { } } + let no_slot_peer = self.default_peers_set_no_slot_peers.contains(&who); + let this_peer_reserved_slot: usize = if no_slot_peer { 1 } else { 0 }; + + if status.roles.is_full() && + self.chain_sync.num_peers() >= + self.default_peers_set_num_full + + self.default_peers_set_no_slot_connected_peers.len() + + this_peer_reserved_slot + { + debug!(target: "sync", "Too many full nodes, rejecting {}", who); + self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); + return Err(()) + } + + if status.roles.is_light() && + (self.peers.len() - self.chain_sync.num_peers()) >= self.default_peers_set_num_light + { + // Make sure that not all slots are occupied by light clients. + debug!(target: "sync", "Too many light nodes, rejecting {}", who); + self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); + return Err(()) + } + let peer = Peer { info: PeerInfo { roles: status.roles, best_hash: status.best_hash, best_number: status.best_number, }, - request: None, known_blocks: LruHashSet::new( NonZeroUsize::new(MAX_KNOWN_BLOCKS).expect("Constant is nonzero"), ), }; let req = if peer.info.roles.is_full() { - match self.sync.new_peer(who, peer.info.best_hash, peer.info.best_number) { + match self.chain_sync.new_peer(who, peer.info.best_hash, peer.info.best_number) { Ok(req) => req, - Err(sync::BadPeer(id, repu)) => { + Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); self.peerset_handle.report_peer(id, repu); return Err(()) @@ -839,12 +609,14 @@ impl Protocol { debug!(target: "sync", "Connected {}", who); self.peers.insert(who, peer); + if no_slot_peer { + self.default_peers_set_no_slot_connected_peers.insert(who); + } self.pending_messages .push_back(CustomMessageOutcome::PeerNewBest(who, status.best_number)); if let Some(req) = req { - let event = self.prepare_block_request(who, req); - self.pending_messages.push_back(event); + self.chain_sync.send_block_request(who, req); } Ok(()) @@ -862,7 +634,7 @@ impl Protocol { return }, Err(e) => { - warn!("Error reading block header {}: {:?}", hash, e); + warn!("Error reading block header {}: {}", hash, e); return }, }; @@ -883,13 +655,9 @@ impl Protocol { let inserted = peer.known_blocks.insert(hash); if inserted { trace!(target: "sync", "Announcing block {:?} to {}", hash, who); - let message = message::BlockAnnounce { + let message = BlockAnnounce { header: header.clone(), - state: if is_best { - Some(message::BlockState::Best) - } else { - Some(message::BlockState::Normal) - }, + state: if is_best { Some(BlockState::Best) } else { Some(BlockState::Normal) }, data: Some(data.clone()), }; @@ -924,26 +692,26 @@ impl Protocol { }, }; - peer.known_blocks.insert(hash.clone()); + peer.known_blocks.insert(hash); - let is_best = match announce.state.unwrap_or(message::BlockState::Best) { - message::BlockState::Best => true, - message::BlockState::Normal => false, + let is_best = match announce.state.unwrap_or(BlockState::Best) { + BlockState::Best => true, + BlockState::Normal => false, }; if peer.info.roles.is_full() { - self.sync.push_block_announce_validation(who, hash, announce, is_best); + self.chain_sync.push_block_announce_validation(who, hash, announce, is_best); } } /// Process the result of the block announce validation. fn process_block_announce_validation_result( &mut self, - validation_result: sync::PollBlockAnnounceValidation, + validation_result: PollBlockAnnounceValidation, ) -> CustomMessageOutcome { let (header, is_best, who) = match validation_result { - sync::PollBlockAnnounceValidation::Skip => return CustomMessageOutcome::None, - sync::PollBlockAnnounceValidation::Nothing { is_best, who, announce } => { + PollBlockAnnounceValidation::Skip => return CustomMessageOutcome::None, + PollBlockAnnounceValidation::Nothing { is_best, who, announce } => { self.update_peer_info(&who); if let Some(data) = announce.data { @@ -964,7 +732,7 @@ impl Protocol { return CustomMessageOutcome::None } }, - sync::PollBlockAnnounceValidation::ImportHeader { announce, is_best, who } => { + PollBlockAnnounceValidation::ImportHeader { announce, is_best, who } => { self.update_peer_info(&who); if let Some(data) = announce.data { @@ -975,7 +743,7 @@ impl Protocol { (announce.header, is_best, who) }, - sync::PollBlockAnnounceValidation::Failure { who, disconnect } => { + PollBlockAnnounceValidation::Failure { who, disconnect } => { if disconnect { self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); } @@ -989,12 +757,12 @@ impl Protocol { // to import header from announced block let's construct response to request that normally // would have been sent over network (but it is not in our case) - let blocks_to_import = self.sync.on_block_data( + let blocks_to_import = self.chain_sync.on_block_data( &who, None, - message::generic::BlockResponse { + BlockResponse:: { id: 0, - blocks: vec![message::generic::BlockData { + blocks: vec![BlockData:: { hash: header.hash(), header: Some(header), body: None, @@ -1006,97 +774,19 @@ impl Protocol { }], }, ); + self.chain_sync.process_block_response_data(blocks_to_import); if is_best { self.pending_messages.push_back(CustomMessageOutcome::PeerNewBest(who, number)); } - match blocks_to_import { - Ok(sync::OnBlockData::Import(origin, blocks)) => - CustomMessageOutcome::BlockImport(origin, blocks), - Ok(sync::OnBlockData::Request(peer, req)) => self.prepare_block_request(peer, req), - Err(sync::BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } + CustomMessageOutcome::None } /// Call this when a block has been finalized. The sync layer may have some additional /// requesting to perform. pub fn on_block_finalized(&mut self, hash: B::Hash, header: &B::Header) { - self.sync.on_block_finalized(&hash, *header.number()) - } - - /// Request a justification for the given block. - /// - /// Uses `protocol` to queue a new justification request and tries to dispatch all pending - /// requests. - pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { - self.sync.request_justification(&hash, number) - } - - /// Clear all pending justification requests. - pub fn clear_justification_requests(&mut self) { - self.sync.clear_justification_requests(); - } - - /// Request syncing for the given block from given set of peers. - /// Uses `protocol` to queue a new block download request and tries to dispatch all pending - /// requests. - pub fn set_sync_fork_request( - &mut self, - peers: Vec, - hash: &B::Hash, - number: NumberFor, - ) { - self.sync.set_sync_fork_request(peers, hash, number) - } - - /// A batch of blocks have been processed, with or without errors. - /// Call this when a batch of blocks have been processed by the importqueue, with or without - /// errors. - pub fn on_blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) { - let results = self.sync.on_blocks_processed(imported, count, results); - for result in results { - match result { - Ok((id, req)) => { - self.pending_messages.push_back(prepare_block_request( - &mut self.peers, - id, - req, - )); - }, - Err(sync::BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu) - }, - } - } - } - - /// Call this when a justification has been processed by the import queue, with or without - /// errors. - pub fn justification_import_result( - &mut self, - who: PeerId, - hash: B::Hash, - number: NumberFor, - success: bool, - ) { - self.sync.on_justification_import(hash, number, success); - if !success { - info!("💔 Invalid justification provided by {} for #{}", who, hash); - self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); - self.peerset_handle - .report_peer(who, sc_peerset::ReputationChange::new_fatal("Invalid justification")); - } + self.chain_sync.on_block_finalized(&hash, *header.number()) } /// Set whether the syncing peers set is in reserved-only mode. @@ -1106,7 +796,7 @@ impl Protocol { /// Removes a `PeerId` from the list of reserved peers for syncing purposes. pub fn remove_reserved_peer(&self, peer: PeerId) { - self.peerset_handle.remove_reserved_peer(HARDCODED_PEERSETS_SYNC, peer.clone()); + self.peerset_handle.remove_reserved_peer(HARDCODED_PEERSETS_SYNC, peer); } /// Returns the list of reserved peers. @@ -1116,21 +806,31 @@ impl Protocol { /// Adds a `PeerId` to the list of reserved peers for syncing purposes. pub fn add_reserved_peer(&self, peer: PeerId) { - self.peerset_handle.add_reserved_peer(HARDCODED_PEERSETS_SYNC, peer.clone()); + self.peerset_handle.add_reserved_peer(HARDCODED_PEERSETS_SYNC, peer); } /// Sets the list of reserved peers for syncing purposes. pub fn set_reserved_peers(&self, peers: HashSet) { - self.peerset_handle.set_reserved_peers(HARDCODED_PEERSETS_SYNC, peers.clone()); + self.peerset_handle.set_reserved_peers(HARDCODED_PEERSETS_SYNC, peers); } - /// Removes a `PeerId` from the list of reserved peers. - pub fn remove_set_reserved_peer(&self, protocol: Cow<'static, str>, peer: PeerId) { + /// Sets the list of reserved peers for the given protocol/peerset. + pub fn set_reserved_peerset_peers(&self, protocol: ProtocolName, peers: HashSet) { if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { - self.peerset_handle.remove_reserved_peer( - sc_peerset::SetId::from(index + NUM_HARDCODED_PEERSETS), - peer, + self.peerset_handle.set_reserved_peers(sc_peerset::SetId::from(index), peers); + } else { + error!( + target: "sub-libp2p", + "set_reserved_peerset_peers with unknown protocol: {}", + protocol ); + } + } + + /// Removes a `PeerId` from the list of reserved peers. + pub fn remove_set_reserved_peer(&self, protocol: ProtocolName, peer: PeerId) { + if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { + self.peerset_handle.remove_reserved_peer(sc_peerset::SetId::from(index), peer); } else { error!( target: "sub-libp2p", @@ -1141,10 +841,9 @@ impl Protocol { } /// Adds a `PeerId` to the list of reserved peers. - pub fn add_set_reserved_peer(&self, protocol: Cow<'static, str>, peer: PeerId) { + pub fn add_set_reserved_peer(&self, protocol: ProtocolName, peer: PeerId) { if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { - self.peerset_handle - .add_reserved_peer(sc_peerset::SetId::from(index + NUM_HARDCODED_PEERSETS), peer); + self.peerset_handle.add_reserved_peer(sc_peerset::SetId::from(index), peer); } else { error!( target: "sub-libp2p", @@ -1164,10 +863,9 @@ impl Protocol { } /// Add a peer to a peers set. - pub fn add_to_peers_set(&self, protocol: Cow<'static, str>, peer: PeerId) { + pub fn add_to_peers_set(&self, protocol: ProtocolName, peer: PeerId) { if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { - self.peerset_handle - .add_to_peers_set(sc_peerset::SetId::from(index + NUM_HARDCODED_PEERSETS), peer); + self.peerset_handle.add_to_peers_set(sc_peerset::SetId::from(index), peer); } else { error!( target: "sub-libp2p", @@ -1178,12 +876,9 @@ impl Protocol { } /// Remove a peer from a peers set. - pub fn remove_from_peers_set(&self, protocol: Cow<'static, str>, peer: PeerId) { + pub fn remove_from_peers_set(&self, protocol: ProtocolName, peer: PeerId) { if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { - self.peerset_handle.remove_from_peers_set( - sc_peerset::SetId::from(index + NUM_HARDCODED_PEERSETS), - peer, - ); + self.peerset_handle.remove_from_peers_set(sc_peerset::SetId::from(index), peer); } else { error!( target: "sub-libp2p", @@ -1198,7 +893,7 @@ impl Protocol { let n = u64::try_from(self.peers.len()).unwrap_or(std::u64::MAX); metrics.peers.set(n); - let m = self.sync.metrics(); + let m = self.chain_sync.metrics(); metrics.fork_targets.set(m.fork_targets.into()); metrics.queued_blocks.set(m.queued_blocks.into()); @@ -1223,108 +918,34 @@ impl Protocol { } } -fn prepare_block_request( - peers: &mut HashMap>, - who: PeerId, - request: message::BlockRequest, -) -> CustomMessageOutcome { - let (tx, rx) = oneshot::channel(); - - if let Some(ref mut peer) = peers.get_mut(&who) { - peer.request = Some((PeerRequest::Block(request.clone()), rx)); - } - - let request = crate::schema::v1::BlockRequest { - fields: request.fields.to_be_u32(), - from_block: match request.from { - message::FromBlock::Hash(h) => - Some(crate::schema::v1::block_request::FromBlock::Hash(h.encode())), - message::FromBlock::Number(n) => - Some(crate::schema::v1::block_request::FromBlock::Number(n.encode())), - }, - to_block: request.to.map(|h| h.encode()).unwrap_or_default(), - direction: request.direction as i32, - max_blocks: request.max.unwrap_or(0), - support_multiple_justifications: true, - }; - - CustomMessageOutcome::BlockRequest { target: who, request, pending_response: tx } -} - -fn prepare_state_request( - peers: &mut HashMap>, - who: PeerId, - request: crate::schema::v1::StateRequest, -) -> CustomMessageOutcome { - let (tx, rx) = oneshot::channel(); - - if let Some(ref mut peer) = peers.get_mut(&who) { - peer.request = Some((PeerRequest::State, rx)); - } - CustomMessageOutcome::StateRequest { target: who, request, pending_response: tx } -} - -fn prepare_warp_sync_request( - peers: &mut HashMap>, - who: PeerId, - request: crate::warp_request_handler::Request, -) -> CustomMessageOutcome { - let (tx, rx) = oneshot::channel(); - - if let Some(ref mut peer) = peers.get_mut(&who) { - peer.request = Some((PeerRequest::WarpProof, rx)); - } - CustomMessageOutcome::WarpSyncRequest { target: who, request, pending_response: tx } -} - /// Outcome of an incoming custom message. #[derive(Debug)] #[must_use] pub enum CustomMessageOutcome { - BlockImport(BlockOrigin, Vec>), - JustificationImport(Origin, B::Hash, NumberFor, Justifications), /// Notification protocols have been opened with a remote. NotificationStreamOpened { remote: PeerId, - protocol: Cow<'static, str>, + protocol: ProtocolName, /// See [`crate::Event::NotificationStreamOpened::negotiated_fallback`]. - negotiated_fallback: Option>, + negotiated_fallback: Option, roles: Roles, notifications_sink: NotificationsSink, }, /// The [`NotificationsSink`] of some notification protocols need an update. NotificationStreamReplaced { remote: PeerId, - protocol: Cow<'static, str>, + protocol: ProtocolName, notifications_sink: NotificationsSink, }, /// Notification protocols have been closed with a remote. NotificationStreamClosed { remote: PeerId, - protocol: Cow<'static, str>, + protocol: ProtocolName, }, /// Messages have been received on one or more notifications protocols. NotificationsReceived { remote: PeerId, - messages: Vec<(Cow<'static, str>, Bytes)>, - }, - /// A new block request must be emitted. - BlockRequest { - target: PeerId, - request: crate::schema::v1::BlockRequest, - pending_response: oneshot::Sender, RequestFailure>>, - }, - /// A new storage request must be emitted. - StateRequest { - target: PeerId, - request: crate::schema::v1::StateRequest, - pending_response: oneshot::Sender, RequestFailure>>, - }, - /// A new warp sync request must be emitted. - WarpSyncRequest { - target: PeerId, - request: crate::warp_request_handler::Request, - pending_response: oneshot::Sender, RequestFailure>>, + messages: Vec<(ProtocolName, Bytes)>, }, /// Peer has a reported a new head of chain. PeerNewBest(PeerId, NumberFor), @@ -1335,11 +956,15 @@ pub enum CustomMessageOutcome { None, } -impl NetworkBehaviour for Protocol { - type ProtocolsHandler = ::ProtocolsHandler; +impl NetworkBehaviour for Protocol +where + B: BlockT, + Client: HeaderBackend + 'static, +{ + type ConnectionHandler = ::ConnectionHandler; type OutEvent = CustomMessageOutcome; - fn new_handler(&mut self) -> Self::ProtocolsHandler { + fn new_handler(&mut self) -> Self::ConnectionHandler { self.behaviour.new_handler() } @@ -1352,8 +977,16 @@ impl NetworkBehaviour for Protocol { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + failed_addresses: Option<&Vec>, + other_established: usize, ) { - self.behaviour.inject_connection_established(peer_id, conn, endpoint) + self.behaviour.inject_connection_established( + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ) } fn inject_connection_closed( @@ -1361,23 +994,23 @@ impl NetworkBehaviour for Protocol { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + handler: ::Handler, + remaining_established: usize, ) { - self.behaviour.inject_connection_closed(peer_id, conn, endpoint) - } - - fn inject_connected(&mut self, peer_id: &PeerId) { - self.behaviour.inject_connected(peer_id) - } - - fn inject_disconnected(&mut self, peer_id: &PeerId) { - self.behaviour.inject_disconnected(peer_id) + self.behaviour.inject_connection_closed( + peer_id, + conn, + endpoint, + handler, + remaining_established, + ) } fn inject_event( &mut self, peer_id: PeerId, connection: ConnectionId, - event: <::Handler as ProtocolsHandler>::OutEvent, + event: <::Handler as ConnectionHandler>::OutEvent, ) { self.behaviour.inject_event(peer_id, connection, event) } @@ -1386,165 +1019,26 @@ impl NetworkBehaviour for Protocol { &mut self, cx: &mut std::task::Context, params: &mut impl PollParameters, - ) -> Poll< - NetworkBehaviourAction< - <::Handler as ProtocolsHandler>::InEvent, - Self::OutEvent - > - >{ + ) -> Poll> { if let Some(message) = self.pending_messages.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(message)) } - // Check for finished outgoing requests. - let mut finished_block_requests = Vec::new(); - let mut finished_state_requests = Vec::new(); - let mut finished_warp_sync_requests = Vec::new(); - for (id, peer) in self.peers.iter_mut() { - if let Peer { request: Some((_, pending_response)), .. } = peer { - match pending_response.poll_unpin(cx) { - Poll::Ready(Ok(Ok(resp))) => { - let (req, _) = peer.request.take().unwrap(); - match req { - PeerRequest::Block(req) => { - let protobuf_response = - match crate::schema::v1::BlockResponse::decode(&resp[..]) { - Ok(proto) => proto, - Err(e) => { - debug!( - target: "sync", - "Failed to decode block response from peer {:?}: {:?}.", - id, - e - ); - self.peerset_handle.report_peer(*id, rep::BAD_MESSAGE); - self.behaviour - .disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - continue - }, - }; - - finished_block_requests.push((id.clone(), req, protobuf_response)); - }, - PeerRequest::State => { - let protobuf_response = - match crate::schema::v1::StateResponse::decode(&resp[..]) { - Ok(proto) => proto, - Err(e) => { - debug!( - target: "sync", - "Failed to decode state response from peer {:?}: {:?}.", - id, - e - ); - self.peerset_handle.report_peer(*id, rep::BAD_MESSAGE); - self.behaviour - .disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - continue - }, - }; - - finished_state_requests.push((*id, protobuf_response)); - }, - PeerRequest::WarpProof => { - finished_warp_sync_requests.push((*id, resp)); - }, - } - }, - Poll::Ready(Ok(Err(e))) => { - peer.request.take(); - debug!(target: "sync", "Request to peer {:?} failed: {:?}.", id, e); - - match e { - RequestFailure::Network(OutboundFailure::Timeout) => { - self.peerset_handle.report_peer(*id, rep::TIMEOUT); - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - RequestFailure::Network(OutboundFailure::UnsupportedProtocols) => { - self.peerset_handle.report_peer(*id, rep::BAD_PROTOCOL); - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - RequestFailure::Network(OutboundFailure::DialFailure) => { - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - RequestFailure::Refused => { - self.peerset_handle.report_peer(*id, rep::REFUSED); - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - RequestFailure::Network(OutboundFailure::ConnectionClosed) | - RequestFailure::NotConnected => { - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - RequestFailure::UnknownProtocol => { - debug_assert!( - false, - "Block request protocol should always be known." - ); - }, - RequestFailure::Obsolete => { - debug_assert!( - false, - "Can not receive `RequestFailure::Obsolete` after dropping the \ - response receiver.", - ); - }, - } - }, - Poll::Ready(Err(oneshot::Canceled)) => { - peer.request.take(); - trace!( - target: "sync", - "Request to peer {:?} failed due to oneshot being canceled.", - id, - ); - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - Poll::Pending => {}, - } + // Advance the state of `ChainSync` + // + // Process any received requests received from `NetworkService` and + // check if there is any block announcement validation finished. + while let Poll::Ready(result) = self.chain_sync.poll(cx) { + match self.process_block_announce_validation_result(result) { + CustomMessageOutcome::None => {}, + outcome => self.pending_messages.push_back(outcome), } } - for (id, req, protobuf_response) in finished_block_requests { - let ev = self.on_block_response(id, req, protobuf_response); - self.pending_messages.push_back(ev); - } - for (id, protobuf_response) in finished_state_requests { - let ev = self.on_state_response(id, protobuf_response); - self.pending_messages.push_back(ev); - } - for (id, response) in finished_warp_sync_requests { - let ev = self.on_warp_sync_response(id, EncodedProof(response)); - self.pending_messages.push_back(ev); - } while let Poll::Ready(Some(())) = self.tick_timeout.poll_next_unpin(cx) { self.tick(); } - for (id, request) in self.sync.block_requests() { - let event = prepare_block_request(&mut self.peers, id.clone(), request); - self.pending_messages.push_back(event); - } - if let Some((id, request)) = self.sync.state_request() { - let event = prepare_state_request(&mut self.peers, id, request); - self.pending_messages.push_back(event); - } - for (id, request) in self.sync.justification_requests() { - let event = prepare_block_request(&mut self.peers, id, request); - self.pending_messages.push_back(event); - } - if let Some((id, request)) = self.sync.warp_sync_request() { - let event = prepare_warp_sync_request(&mut self.peers, id, request); - self.pending_messages.push_back(event); - } - - // Check if there is any block announcement validation finished. - while let Poll::Ready(result) = self.sync.poll_block_announce_validation(cx) { - match self.process_block_announce_validation_result(result) { - CustomMessageOutcome::None => {}, - outcome => self.pending_messages.push_back(outcome), - } - } - if let Some(message) = self.pending_messages.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(message)) } @@ -1552,10 +1046,8 @@ impl NetworkBehaviour for Protocol { let event = match self.behaviour.poll(cx, params) { Poll::Pending => return Poll::Pending, Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) => ev, - Poll::Ready(NetworkBehaviourAction::DialAddress { address }) => - return Poll::Ready(NetworkBehaviourAction::DialAddress { address }), - Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition }) => - return Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition }), + Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) => + return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }), Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, handler, event }) => return Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, @@ -1578,8 +1070,6 @@ impl NetworkBehaviour for Protocol { } => { // Set number 0 is hardcoded the default set of peers we sync from. if set_id == HARDCODED_PEERSETS_SYNC { - debug_assert!(negotiated_fallback.is_none()); - // `received_handshake` can be either a `Status` message if received from the // legacy substream ,or a `BlockAnnouncesHandshake` if received from the block // announces substream. @@ -1636,14 +1126,12 @@ impl NetworkBehaviour for Protocol { } } else { match ( - message::Roles::decode_all(&received_handshake[..]), + Roles::decode_all(&mut &received_handshake[..]), self.peers.get(&peer_id), ) { (Ok(roles), _) => CustomMessageOutcome::NotificationStreamOpened { remote: peer_id, - protocol: self.notification_protocols - [usize::from(set_id) - NUM_HARDCODED_PEERSETS] - .clone(), + protocol: self.notification_protocols[usize::from(set_id)].clone(), negotiated_fallback, roles, notifications_sink, @@ -1655,9 +1143,7 @@ impl NetworkBehaviour for Protocol { // TODO: remove this after https://github.com/paritytech/substrate/issues/5685 CustomMessageOutcome::NotificationStreamOpened { remote: peer_id, - protocol: self.notification_protocols - [usize::from(set_id) - NUM_HARDCODED_PEERSETS] - .clone(), + protocol: self.notification_protocols[usize::from(set_id)].clone(), negotiated_fallback, roles: peer.info.roles, notifications_sink, @@ -1674,16 +1160,14 @@ impl NetworkBehaviour for Protocol { } }, NotificationsOut::CustomProtocolReplaced { peer_id, notifications_sink, set_id } => - if set_id == HARDCODED_PEERSETS_SYNC { - CustomMessageOutcome::None - } else if self.bad_handshake_substreams.contains(&(peer_id, set_id)) { + if set_id == HARDCODED_PEERSETS_SYNC || + self.bad_handshake_substreams.contains(&(peer_id, set_id)) + { CustomMessageOutcome::None } else { CustomMessageOutcome::NotificationStreamReplaced { remote: peer_id, - protocol: self.notification_protocols - [usize::from(set_id) - NUM_HARDCODED_PEERSETS] - .clone(), + protocol: self.notification_protocols[usize::from(set_id)].clone(), notifications_sink, } }, @@ -1708,20 +1192,19 @@ impl NetworkBehaviour for Protocol { } else { CustomMessageOutcome::NotificationStreamClosed { remote: peer_id, - protocol: self.notification_protocols - [usize::from(set_id) - NUM_HARDCODED_PEERSETS] - .clone(), + protocol: self.notification_protocols[usize::from(set_id)].clone(), } } }, NotificationsOut::Notification { peer_id, set_id, message } => match set_id { HARDCODED_PEERSETS_SYNC if self.peers.contains_key(&peer_id) => { - if let Ok(announce) = message::BlockAnnounce::decode(&mut message.as_ref()) { + if let Ok(announce) = BlockAnnounce::decode(&mut message.as_ref()) { self.push_block_announce_validation(peer_id, announce); // Make sure that the newly added block announce validation future was // polled once to be registered in the task. - if let Poll::Ready(res) = self.sync.poll_block_announce_validation(cx) { + if let Poll::Ready(res) = self.chain_sync.poll_block_announce_validation(cx) + { self.process_block_announce_validation_result(res) } else { CustomMessageOutcome::None @@ -1742,9 +1225,7 @@ impl NetworkBehaviour for Protocol { _ if self.bad_handshake_substreams.contains(&(peer_id, set_id)) => CustomMessageOutcome::None, _ => { - let protocol_name = self.notification_protocols - [usize::from(set_id) - NUM_HARDCODED_PEERSETS] - .clone(); + let protocol_name = self.notification_protocols[usize::from(set_id)].clone(); CustomMessageOutcome::NotificationsReceived { remote: peer_id, messages: vec![(protocol_name, message.freeze())], @@ -1768,17 +1249,13 @@ impl NetworkBehaviour for Protocol { Poll::Pending } - fn inject_addr_reach_failure( + fn inject_dial_failure( &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - error: &dyn std::error::Error, + peer_id: Option, + handler: Self::ConnectionHandler, + error: &libp2p::swarm::DialError, ) { - self.behaviour.inject_addr_reach_failure(peer_id, addr, error) - } - - fn inject_dial_failure(&mut self, peer_id: &PeerId) { - self.behaviour.inject_dial_failure(peer_id) + self.behaviour.inject_dial_failure(peer_id, handler, error); } fn inject_new_listener(&mut self, id: ListenerId) { diff --git a/client/network/src/protocol/message.rs b/client/network/src/protocol/message.rs index 8938c27aeddd8..ef652387d2c7d 100644 --- a/client/network/src/protocol/message.rs +++ b/client/network/src/protocol/message.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,19 +20,13 @@ //! payload. pub use self::generic::{ - BlockAnnounce, FromBlock, RemoteCallRequest, RemoteChangesRequest, RemoteChangesResponse, - RemoteHeaderRequest, RemoteHeaderResponse, RemoteReadChildRequest, RemoteReadRequest, Roles, + RemoteCallRequest, RemoteChangesRequest, RemoteChangesResponse, RemoteHeaderRequest, + RemoteHeaderResponse, RemoteReadChildRequest, RemoteReadRequest, }; -use bitflags::bitflags; -use codec::{Decode, Encode, Error, Input, Output}; +use codec::{Decode, Encode}; use sc_client_api::StorageProof; -use sp_runtime::{ - traits::{Block as BlockT, Header as HeaderT}, - ConsensusEngineId, -}; - -/// A unique ID of a request. -pub type RequestId = u64; +use sc_network_common::message::RequestId; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; /// Type alias for using the message type using block type parameters. pub type Message = generic::Message< @@ -42,86 +36,6 @@ pub type Message = generic::Message< ::Extrinsic, >; -/// Type alias for using the block request type using block type parameters. -pub type BlockRequest = - generic::BlockRequest<::Hash, <::Header as HeaderT>::Number>; - -/// Type alias for using the BlockData type using block type parameters. -pub type BlockData = - generic::BlockData<::Header, ::Hash, ::Extrinsic>; - -/// Type alias for using the BlockResponse type using block type parameters. -pub type BlockResponse = - generic::BlockResponse<::Header, ::Hash, ::Extrinsic>; - -/// A set of transactions. -pub type Transactions = Vec; - -// Bits of block data and associated artifacts to request. -bitflags! { - /// Node roles bitmask. - pub struct BlockAttributes: u8 { - /// Include block header. - const HEADER = 0b00000001; - /// Include block body. - const BODY = 0b00000010; - /// Include block receipt. - const RECEIPT = 0b00000100; - /// Include block message queue. - const MESSAGE_QUEUE = 0b00001000; - /// Include a justification for the block. - const JUSTIFICATION = 0b00010000; - /// Include indexed transactions for a block. - const INDEXED_BODY = 0b00100000; - } -} - -impl BlockAttributes { - /// Encodes attributes as big endian u32, compatible with SCALE-encoding (i.e the - /// significant byte has zero index). - pub fn to_be_u32(&self) -> u32 { - u32::from_be_bytes([self.bits(), 0, 0, 0]) - } - - /// Decodes attributes, encoded with the `encode_to_be_u32()` call. - pub fn from_be_u32(encoded: u32) -> Result { - Self::from_bits(encoded.to_be_bytes()[0]) - .ok_or_else(|| Error::from("Invalid BlockAttributes")) - } -} - -impl Encode for BlockAttributes { - fn encode_to(&self, dest: &mut T) { - dest.push_byte(self.bits()) - } -} - -impl codec::EncodeLike for BlockAttributes {} - -impl Decode for BlockAttributes { - fn decode(input: &mut I) -> Result { - Self::from_bits(input.read_byte()?).ok_or_else(|| Error::from("Invalid bytes")) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode)] -/// Block enumeration direction. -pub enum Direction { - /// Enumerate in ascending order (from child to parent). - Ascending = 0, - /// Enumerate in descending order (from parent to canonical child). - Descending = 1, -} - -/// Block state in the chain. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode)] -pub enum BlockState { - /// Block is not part of the best chain. - Normal, - /// Latest best block. - Best, -} - /// Remote call response. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] pub struct RemoteCallResponse { @@ -140,90 +54,20 @@ pub struct RemoteReadResponse { pub proof: StorageProof, } -/// Announcement summary used for debug logging. -#[derive(Debug)] -pub struct AnnouncementSummary { - block_hash: H::Hash, - number: H::Number, - parent_hash: H::Hash, - state: Option, -} - -impl generic::BlockAnnounce { - pub fn summary(&self) -> AnnouncementSummary { - AnnouncementSummary { - block_hash: self.header.hash(), - number: *self.header.number(), - parent_hash: self.header.parent_hash().clone(), - state: self.state, - } - } -} - /// Generic types. pub mod generic { - use super::{ - BlockAttributes, BlockState, ConsensusEngineId, Direction, RemoteCallResponse, - RemoteReadResponse, RequestId, StorageProof, Transactions, + use super::{RemoteCallResponse, RemoteReadResponse}; + use codec::{Decode, Encode, Input}; + use sc_client_api::StorageProof; + use sc_network_common::{ + message::RequestId, + protocol::role::Roles, + sync::message::{ + generic::{BlockRequest, BlockResponse}, + BlockAnnounce, + }, }; - use bitflags::bitflags; - use codec::{Decode, Encode, Input, Output}; - use sp_runtime::{EncodedJustification, Justifications}; - - bitflags! { - /// Bitmask of the roles that a node fulfills. - pub struct Roles: u8 { - /// No network. - const NONE = 0b00000000; - /// Full node, does not participate in consensus. - const FULL = 0b00000001; - /// Light client node. - const LIGHT = 0b00000010; - /// Act as an authority - const AUTHORITY = 0b00000100; - } - } - - impl Roles { - /// Does this role represents a client that holds full chain data locally? - pub fn is_full(&self) -> bool { - self.intersects(Self::FULL | Self::AUTHORITY) - } - - /// Does this role represents a client that does not participates in the consensus? - pub fn is_authority(&self) -> bool { - *self == Self::AUTHORITY - } - - /// Does this role represents a client that does not hold full chain data locally? - pub fn is_light(&self) -> bool { - !self.is_full() - } - } - - impl<'a> From<&'a crate::config::Role> for Roles { - fn from(roles: &'a crate::config::Role) -> Self { - match roles { - crate::config::Role::Full => Self::FULL, - crate::config::Role::Light => Self::LIGHT, - crate::config::Role::Authority { .. } => Self::AUTHORITY, - } - } - } - - impl codec::Encode for Roles { - fn encode_to(&self, dest: &mut T) { - dest.push_byte(self.bits()) - } - } - - impl codec::EncodeLike for Roles {} - - impl codec::Decode for Roles { - fn decode(input: &mut I) -> Result { - Self::from_bits(input.read_byte()?).ok_or_else(|| codec::Error::from("Invalid bytes")) - } - } + use sp_runtime::ConsensusEngineId; /// Consensus is mostly opaque to us #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] @@ -234,36 +78,6 @@ pub mod generic { pub data: Vec, } - /// Block data sent in the response. - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] - pub struct BlockData { - /// Block header hash. - pub hash: Hash, - /// Block header if requested. - pub header: Option
, - /// Block body if requested. - pub body: Option>, - /// Block body indexed transactions if requested. - pub indexed_body: Option>>, - /// Block receipt if requested. - pub receipt: Option>, - /// Block message queue if requested. - pub message_queue: Option>, - /// Justification if requested. - pub justification: Option, - /// Justifications if requested. - pub justifications: Option, - } - - /// Identifies starting point of a block sequence. - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] - pub enum FromBlock { - /// Start with given hash. - Hash(Hash), - /// Start with given block number. - Number(Number), - } - /// A network message. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] pub enum Message { @@ -275,9 +89,10 @@ pub mod generic { BlockResponse(BlockResponse), /// Block announce. BlockAnnounce(BlockAnnounce
), - /// Transactions. - Transactions(Transactions), /// Consensus protocol message. + // NOTE: index is incremented by 1 due to transaction-related + // message that was removed + #[codec(index = 6)] Consensus(ConsensusMessage), /// Remote method call request. RemoteCallRequest(RemoteCallRequest), @@ -380,68 +195,6 @@ pub mod generic { } } - /// Request block data from a peer. - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] - pub struct BlockRequest { - /// Unique request id. - pub id: RequestId, - /// Bits of block data to request. - pub fields: BlockAttributes, - /// Start from this block. - pub from: FromBlock, - /// End at this block. An implementation defined maximum is used when unspecified. - pub to: Option, - /// Sequence direction. - pub direction: Direction, - /// Maximum number of blocks to return. An implementation defined maximum is used when - /// unspecified. - pub max: Option, - } - - /// Response to `BlockRequest` - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] - pub struct BlockResponse { - /// Id of a request this response was made for. - pub id: RequestId, - /// Block data for the requested sequence. - pub blocks: Vec>, - } - - /// Announce a new complete relay chain block on the network. - #[derive(Debug, PartialEq, Eq, Clone)] - pub struct BlockAnnounce { - /// New block header. - pub header: H, - /// Block state. TODO: Remove `Option` and custom encoding when v4 becomes common. - pub state: Option, - /// Data associated with this block announcement, e.g. a candidate message. - pub data: Option>, - } - - // Custom Encode/Decode impl to maintain backwards compatibility with v3. - // This assumes that the packet contains nothing but the announcement message. - // TODO: Get rid of it once protocol v4 is common. - impl Encode for BlockAnnounce { - fn encode_to(&self, dest: &mut T) { - self.header.encode_to(dest); - if let Some(state) = &self.state { - state.encode_to(dest); - } - if let Some(data) = &self.data { - data.encode_to(dest) - } - } - } - - impl Decode for BlockAnnounce { - fn decode(input: &mut I) -> Result { - let header = H::decode(input)?; - let state = BlockState::decode(input).ok(); - let data = Vec::decode(input).ok(); - Ok(Self { header, state, data }) - } - } - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] /// Remote call request. pub struct RemoteCallRequest { diff --git a/client/network/src/protocol/notifications.rs b/client/network/src/protocol/notifications.rs index e489970e987c6..bf183abf160cb 100644 --- a/client/network/src/protocol/notifications.rs +++ b/client/network/src/protocol/notifications.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/network/src/protocol/notifications/behaviour.rs b/client/network/src/protocol/notifications/behaviour.rs index da2967d6f26eb..04f6fe445ac63 100644 --- a/client/network/src/protocol/notifications/behaviour.rs +++ b/client/network/src/protocol/notifications/behaviour.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -26,21 +26,21 @@ use futures::prelude::*; use libp2p::{ core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId}, swarm::{ - DialPeerCondition, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, + DialError, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, + PollParameters, }, }; use log::{error, trace, warn}; use parking_lot::RwLock; use rand::distributions::{Distribution as _, Uniform}; +use sc_network_common::protocol::ProtocolName; use sc_peerset::DropReason; use smallvec::SmallVec; use std::{ - borrow::Cow, cmp, collections::{hash_map::Entry, VecDeque}, - error, mem, + mem, pin::Pin, - str, sync::Arc, task::{Context, Poll}, time::{Duration, Instant}, @@ -132,16 +132,16 @@ pub struct Notifications { next_incoming_index: sc_peerset::IncomingIndex, /// Events to produce from `poll()`. - events: VecDeque>, + events: VecDeque>, } /// Configuration for a notifications protocol. #[derive(Debug, Clone)] pub struct ProtocolConfig { /// Name of the protocol. - pub name: Cow<'static, str>, + pub name: ProtocolName, /// Names of the protocol to use if the main one isn't available. - pub fallback_names: Vec>, + pub fallback_names: Vec, /// Handshake of the protocol. pub handshake: Vec, /// Maximum allowed size for a notification. @@ -250,16 +250,6 @@ impl PeerState { _ => None, } } - - /// True if that node has been requested by the PSM. - fn is_requested(&self) -> bool { - matches!( - self, - Self::PendingRequest { .. } | - Self::Requested | Self::DisabledPendingEnable { .. } | - Self::Enabled { .. } - ) - } } /// State of the handler of a single connection visible from this state machine. @@ -318,7 +308,7 @@ pub enum NotificationsOut { set_id: sc_peerset::SetId, /// If `Some`, a fallback protocol name has been used rather the main protocol name. /// Always matches one of the fallback names passed at initialization. - negotiated_fallback: Option>, + negotiated_fallback: Option, /// Handshake that was sent to us. /// This is normally a "Status" message, but this is out of the concern of this code. received_handshake: Vec, @@ -411,13 +401,13 @@ impl Notifications { } /// Returns the list of all the peers we have an open channel to. - pub fn open_peers<'a>(&'a self) -> impl Iterator + 'a { + pub fn open_peers(&self) -> impl Iterator { self.peers.iter().filter(|(_, state)| state.is_open()).map(|((id, _), _)| id) } /// Returns true if we have an open substream to the given peer. pub fn is_open(&self, peer_id: &PeerId, set_id: sc_peerset::SetId) -> bool { - self.peers.get(&(peer_id.clone(), set_id)).map(|p| p.is_open()).unwrap_or(false) + self.peers.get(&(*peer_id, set_id)).map(|p| p.is_open()).unwrap_or(false) } /// Disconnects the given peer if we are connected to it. @@ -559,22 +549,8 @@ impl Notifications { } } - /// Returns the list of all the peers that the peerset currently requests us to be connected to. - pub fn requested_peers<'a>( - &'a self, - set_id: sc_peerset::SetId, - ) -> impl Iterator + 'a { - self.peers - .iter() - .filter(move |((_, set), state)| *set == set_id && state.is_requested()) - .map(|((id, _), _)| id) - } - /// Returns the list of reserved peers. - pub fn reserved_peers<'a>( - &'a self, - set_id: sc_peerset::SetId, - ) -> impl Iterator + 'a { + pub fn reserved_peers(&self, set_id: sc_peerset::SetId) -> impl Iterator { self.peerset.reserved_peers(set_id) } @@ -628,6 +604,7 @@ impl Notifications { /// Function that is called when the peerset wants us to connect to a peer. fn peerset_report_connect(&mut self, peer_id: PeerId, set_id: sc_peerset::SetId) { // If `PeerId` is unknown to us, insert an entry, start dialing, and return early. + let handler = self.new_handler(); let mut occ_entry = match self.peers.entry((peer_id, set_id)) { Entry::Occupied(entry) => entry, Entry::Vacant(entry) => { @@ -639,10 +616,9 @@ impl Notifications { set_id, ); trace!(target: "sub-libp2p", "Libp2p <= Dial {}", entry.key().0); - // The `DialPeerCondition` ensures that dial attempts are de-duplicated - self.events.push_back(NetworkBehaviourAction::DialPeer { - peer_id: entry.key().0.clone(), - condition: DialPeerCondition::Disconnected, + self.events.push_back(NetworkBehaviourAction::Dial { + opts: entry.key().0.into(), + handler, }); entry.insert(PeerState::Requested); return @@ -654,7 +630,7 @@ impl Notifications { match mem::replace(occ_entry.get_mut(), PeerState::Poisoned) { // Backoff (not expired) => PendingRequest PeerState::Backoff { ref timer, ref timer_deadline } if *timer_deadline > now => { - let peer_id = occ_entry.key().0.clone(); + let peer_id = occ_entry.key().0; trace!( target: "sub-libp2p", "PSM => Connect({}, {:?}): Will start to connect at until {:?}", @@ -675,10 +651,9 @@ impl Notifications { set_id, ); trace!(target: "sub-libp2p", "Libp2p <= Dial {:?}", occ_entry.key()); - // The `DialPeerCondition` ensures that dial attempts are de-duplicated - self.events.push_back(NetworkBehaviourAction::DialPeer { - peer_id: occ_entry.key().0.clone(), - condition: DialPeerCondition::Disconnected, + self.events.push_back(NetworkBehaviourAction::Dial { + opts: occ_entry.key().0.into(), + handler, }); *occ_entry.into_mut() = PeerState::Requested; }, @@ -687,7 +662,7 @@ impl Notifications { PeerState::Disabled { connections, backoff_until: Some(ref backoff) } if *backoff > now => { - let peer_id = occ_entry.key().0.clone(); + let peer_id = occ_entry.key().0; trace!( target: "sub-libp2p", "PSM => Connect({}, {:?}): But peer is backed-off until {:?}", @@ -712,7 +687,7 @@ impl Notifications { timer: delay_id, timer_deadline: *backoff, }; - } + }, // Disabled => Enabled PeerState::Disabled { mut connections, backoff_until } => { @@ -802,7 +777,7 @@ impl Notifications { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", occ_entry.key(), *connec_id, set_id); self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: occ_entry.key().0.clone(), + peer_id: occ_entry.key().0, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, }); @@ -882,10 +857,8 @@ impl Notifications { if connections.iter().any(|(_, s)| matches!(s, ConnectionState::Open(_))) { trace!(target: "sub-libp2p", "External API <= Closed({}, {:?})", entry.key().0, set_id); - let event = NotificationsOut::CustomProtocolClosed { - peer_id: entry.key().0.clone(), - set_id, - }; + let event = + NotificationsOut::CustomProtocolClosed { peer_id: entry.key().0, set_id }; self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); } @@ -895,7 +868,7 @@ impl Notifications { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Close({:?})", entry.key(), *connec_id, set_id); self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: entry.key().0.clone(), + peer_id: entry.key().0, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, }); @@ -908,7 +881,7 @@ impl Notifications { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Close({:?})", entry.key(), *connec_id, set_id); self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: entry.key().0.clone(), + peer_id: entry.key().0, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, }); @@ -1076,10 +1049,10 @@ impl Notifications { } impl NetworkBehaviour for Notifications { - type ProtocolsHandler = NotifsHandlerProto; + type ConnectionHandler = NotifsHandlerProto; type OutEvent = NotificationsOut; - fn new_handler(&mut self) -> Self::ProtocolsHandler { + fn new_handler(&mut self) -> Self::ConnectionHandler { NotifsHandlerProto::new(self.notif_protocols.clone()) } @@ -1087,13 +1060,13 @@ impl NetworkBehaviour for Notifications { Vec::new() } - fn inject_connected(&mut self, _: &PeerId) {} - fn inject_connection_established( &mut self, peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + _failed_addresses: Option<&Vec>, + _other_established: usize, ) { for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { match self.peers.entry((*peer_id, set_id)).or_insert(PeerState::Poisoned) { @@ -1152,6 +1125,8 @@ impl NetworkBehaviour for Notifications { peer_id: &PeerId, conn: &ConnectionId, _endpoint: &ConnectedPoint, + _handler: ::Handler, + _remaining_established: usize, ) { for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { let mut entry = if let Entry::Occupied(entry) = self.peers.entry((*peer_id, set_id)) { @@ -1409,72 +1384,74 @@ impl NetworkBehaviour for Notifications { } } - fn inject_disconnected(&mut self, _peer_id: &PeerId) {} - - fn inject_addr_reach_failure( + fn inject_dial_failure( &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - error: &dyn error::Error, + peer_id: Option, + _: Self::ConnectionHandler, + error: &DialError, ) { - trace!(target: "sub-libp2p", "Libp2p => Reach failure for {:?} through {:?}: {:?}", peer_id, addr, error); - } + if let DialError::Transport(errors) = error { + for (addr, error) in errors.iter() { + trace!(target: "sub-libp2p", "Libp2p => Reach failure for {:?} through {:?}: {:?}", peer_id, addr, error); + } + } - fn inject_dial_failure(&mut self, peer_id: &PeerId) { - trace!(target: "sub-libp2p", "Libp2p => Dial failure for {:?}", peer_id); + if let Some(peer_id) = peer_id { + trace!(target: "sub-libp2p", "Libp2p => Dial failure for {:?}", peer_id); - for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { - if let Entry::Occupied(mut entry) = self.peers.entry((peer_id.clone(), set_id)) { - match mem::replace(entry.get_mut(), PeerState::Poisoned) { - // The peer is not in our list. - st @ PeerState::Backoff { .. } => { - *entry.into_mut() = st; - }, + for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { + if let Entry::Occupied(mut entry) = self.peers.entry((peer_id, set_id)) { + match mem::replace(entry.get_mut(), PeerState::Poisoned) { + // The peer is not in our list. + st @ PeerState::Backoff { .. } => { + *entry.into_mut() = st; + }, - // "Basic" situation: we failed to reach a peer that the peerset requested. - st @ PeerState::Requested | st @ PeerState::PendingRequest { .. } => { - trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); - self.peerset.dropped(set_id, *peer_id, DropReason::Unknown); - - let now = Instant::now(); - let ban_duration = match st { - PeerState::PendingRequest { timer_deadline, .. } - if timer_deadline > now => - cmp::max(timer_deadline - now, Duration::from_secs(5)), - _ => Duration::from_secs(5), - }; - - let delay_id = self.next_delay_id; - self.next_delay_id.0 += 1; - let delay = futures_timer::Delay::new(ban_duration); - let peer_id = *peer_id; - self.delays.push( - async move { - delay.await; - (delay_id, peer_id, set_id) - } - .boxed(), - ); + // "Basic" situation: we failed to reach a peer that the peerset requested. + st @ PeerState::Requested | st @ PeerState::PendingRequest { .. } => { + trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); + self.peerset.dropped(set_id, peer_id, DropReason::Unknown); - *entry.into_mut() = PeerState::Backoff { - timer: delay_id, - timer_deadline: now + ban_duration, - }; - }, + let now = Instant::now(); + let ban_duration = match st { + PeerState::PendingRequest { timer_deadline, .. } + if timer_deadline > now => + cmp::max(timer_deadline - now, Duration::from_secs(5)), + _ => Duration::from_secs(5), + }; - // We can still get dial failures even if we are already connected to the peer, - // as an extra diagnostic for an earlier attempt. - st @ PeerState::Disabled { .. } | - st @ PeerState::Enabled { .. } | - st @ PeerState::DisabledPendingEnable { .. } | - st @ PeerState::Incoming { .. } => { - *entry.into_mut() = st; - }, + let delay_id = self.next_delay_id; + self.next_delay_id.0 += 1; + let delay = futures_timer::Delay::new(ban_duration); + let peer_id = peer_id; + self.delays.push( + async move { + delay.await; + (delay_id, peer_id, set_id) + } + .boxed(), + ); - PeerState::Poisoned => { - error!(target: "sub-libp2p", "State of {:?} is poisoned", peer_id); - debug_assert!(false); - }, + *entry.into_mut() = PeerState::Backoff { + timer: delay_id, + timer_deadline: now + ban_duration, + }; + }, + + // We can still get dial failures even if we are already connected to the + // peer, as an extra diagnostic for an earlier attempt. + st @ PeerState::Disabled { .. } | + st @ PeerState::Enabled { .. } | + st @ PeerState::DisabledPendingEnable { .. } | + st @ PeerState::Incoming { .. } => { + *entry.into_mut() = st; + }, + + PeerState::Poisoned => { + error!(target: "sub-libp2p", "State of {:?} is poisoned", peer_id); + debug_assert!(false); + }, + } } } } @@ -1663,7 +1640,6 @@ impl NetworkBehaviour for Notifications { "OpenDesiredByRemote: Unexpected state in the custom protos handler: {:?}", state); debug_assert!(false); - return }, }; }, @@ -1759,13 +1735,11 @@ impl NetworkBehaviour for Notifications { state @ PeerState::Disabled { .. } | state @ PeerState::DisabledPendingEnable { .. } => { *entry.into_mut() = state; - return }, state => { error!(target: "sub-libp2p", "Unexpected state in the custom protos handler: {:?}", state); - return }, } }, @@ -1777,7 +1751,7 @@ impl NetworkBehaviour for Notifications { "Handler({}, {:?}) => CloseResult({:?})", source, connection, set_id); - match self.peers.get_mut(&(source.clone(), set_id)) { + match self.peers.get_mut(&(source, set_id)) { // Move the connection from `Closing` to `Closed`. Some(PeerState::Incoming { connections, .. }) | Some(PeerState::DisabledPendingEnable { connections, .. }) | @@ -1870,7 +1844,6 @@ impl NetworkBehaviour for Notifications { "OpenResultOk: Unexpected state in the custom protos handler: {:?}", state); debug_assert!(false); - return }, } }, @@ -2000,7 +1973,7 @@ impl NetworkBehaviour for Notifications { &mut self, cx: &mut Context, _params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll> { if let Some(event) = self.events.pop_front() { return Poll::Ready(event) } @@ -2032,6 +2005,8 @@ impl NetworkBehaviour for Notifications { while let Poll::Ready(Some((delay_id, peer_id, set_id))) = Pin::new(&mut self.delays).poll_next(cx) { + let handler = self.new_handler(); + let peer_state = match self.peers.get_mut(&(peer_id, set_id)) { Some(s) => s, // We intentionally never remove elements from `delays`, and it may @@ -2047,11 +2022,8 @@ impl NetworkBehaviour for Notifications { PeerState::PendingRequest { timer, .. } if *timer == delay_id => { trace!(target: "sub-libp2p", "Libp2p <= Dial {:?} now that ban has expired", peer_id); - // The `DialPeerCondition` ensures that dial attempts are de-duplicated - self.events.push_back(NetworkBehaviourAction::DialPeer { - peer_id, - condition: DialPeerCondition::Disconnected, - }); + self.events + .push_back(NetworkBehaviourAction::Dial { opts: peer_id.into(), handler }); *peer_state = PeerState::Requested; }, @@ -2070,9 +2042,7 @@ impl NetworkBehaviour for Notifications { event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, }); *connec_state = ConnectionState::Opening; - *peer_state = PeerState::Enabled { - connections: mem::replace(connections, Default::default()), - }; + *peer_state = PeerState::Enabled { connections: mem::take(connections) }; } else { *timer_deadline = Instant::now() + Duration::from_secs(5); let delay = futures_timer::Delay::new(Duration::from_secs(5)); @@ -2085,7 +2055,7 @@ impl NetworkBehaviour for Notifications { .boxed(), ); } - } + }, // We intentionally never remove elements from `delays`, and it may // thus contain obsolete entries. This is a normal situation. diff --git a/client/network/src/protocol/notifications/handler.rs b/client/network/src/protocol/notifications/handler.rs index a0c49fa592b21..ea09cb76edce1 100644 --- a/client/network/src/protocol/notifications/handler.rs +++ b/client/network/src/protocol/notifications/handler.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,10 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Implementations of the `IntoProtocolsHandler` and `ProtocolsHandler` traits for both incoming +//! Implementations of the `IntoConnectionHandler` and `ConnectionHandler` traits for both incoming //! and outgoing substreams for all gossiping protocols. //! -//! This is the main implementation of `ProtocolsHandler` in this crate, that handles all the +//! This is the main implementation of `ConnectionHandler` in this crate, that handles all the //! gossiping protocols that are Substrate-related and outside of the scope of libp2p. //! //! # Usage @@ -74,18 +74,17 @@ use libp2p::{ ConnectedPoint, PeerId, }, swarm::{ - IntoProtocolsHandler, KeepAlive, NegotiatedSubstream, ProtocolsHandler, - ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol, + ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, IntoConnectionHandler, + KeepAlive, NegotiatedSubstream, SubstreamProtocol, }, }; use log::error; use parking_lot::{Mutex, RwLock}; +use sc_network_common::protocol::ProtocolName; use std::{ - borrow::Cow, collections::VecDeque, mem, pin::Pin, - str, sync::Arc, task::{Context, Poll}, time::{Duration, Instant}, @@ -107,7 +106,7 @@ const OPEN_TIMEOUT: Duration = Duration::from_secs(10); /// open substreams. const INITIAL_KEEPALIVE_TIME: Duration = Duration::from_secs(5); -/// Implements the `IntoProtocolsHandler` trait of libp2p. +/// Implements the `IntoConnectionHandler` trait of libp2p. /// /// Every time a connection with a remote starts, an instance of this struct is created and /// sent to a background task dedicated to this connection. Once the connection is established, @@ -138,7 +137,7 @@ pub struct NotifsHandler { /// Events to return in priority from `poll`. events_queue: VecDeque< - ProtocolsHandlerEvent, + ConnectionHandlerEvent, >, } @@ -146,9 +145,9 @@ pub struct NotifsHandler { #[derive(Debug, Clone)] pub struct ProtocolConfig { /// Name of the protocol. - pub name: Cow<'static, str>, + pub name: ProtocolName, /// Names of the protocol to use if the main one isn't available. - pub fallback_names: Vec>, + pub fallback_names: Vec, /// Handshake of the protocol. The `RwLock` is locked every time a new substream is opened. pub handshake: Arc>>, /// Maximum allowed size for a notification. @@ -225,7 +224,7 @@ enum State { }, } -impl IntoProtocolsHandler for NotifsHandlerProto { +impl IntoConnectionHandler for NotifsHandlerProto { type Handler = NotifsHandler; fn inbound_protocol(&self) -> UpgradeCollec { @@ -297,7 +296,7 @@ pub enum NotifsHandlerOut { /// Index of the protocol in the list of protocols passed at initialization. protocol_index: usize, /// Name of the protocol that was actually negotiated, if the default one wasn't available. - negotiated_fallback: Option>, + negotiated_fallback: Option, /// The endpoint of the connection that is open for custom protocols. endpoint: ConnectedPoint, /// Handshake that was sent to us. @@ -365,10 +364,12 @@ struct NotificationsSinkInner { /// Sender to use in asynchronous contexts. Uses an asynchronous mutex. async_channel: FuturesMutex>, /// Sender to use in synchronous contexts. Uses a synchronous mutex. + /// Contains `None` if the channel was full at some point, in which case the channel will + /// be closed in the near future anyway. /// This channel has a large capacity and is meant to be used in contexts where /// back-pressure cannot be properly exerted. /// It will be removed in a future version. - sync_channel: Mutex>, + sync_channel: Mutex>>, } /// Message emitted through the [`NotificationsSink`] and processed by the background task @@ -398,16 +399,22 @@ impl NotificationsSink { /// error to send a notification using an unknown protocol. /// /// This method will be removed in a future version. - pub fn send_sync_notification<'a>(&'a self, message: impl Into>) { + pub fn send_sync_notification(&self, message: impl Into>) { let mut lock = self.inner.sync_channel.lock(); - let result = - lock.try_send(NotificationsSinkMessage::Notification { message: message.into() }); - - if result.is_err() { - // Cloning the `mpsc::Sender` guarantees the allocation of an extra spot in the - // buffer, and therefore `try_send` will succeed. - let _result2 = lock.clone().try_send(NotificationsSinkMessage::ForceClose); - debug_assert!(_result2.map(|()| true).unwrap_or_else(|err| err.is_disconnected())); + + if let Some(tx) = lock.as_mut() { + let result = + tx.try_send(NotificationsSinkMessage::Notification { message: message.into() }); + + if result.is_err() { + // Cloning the `mpsc::Sender` guarantees the allocation of an extra spot in the + // buffer, and therefore `try_send` will succeed. + let _result2 = tx.clone().try_send(NotificationsSinkMessage::ForceClose); + debug_assert!(_result2.map(|()| true).unwrap_or_else(|err| err.is_disconnected())); + + // Destroy the sender in order to not send more `ForceClose` messages. + *lock = None; + } } } @@ -417,7 +424,7 @@ impl NotificationsSink { /// /// The protocol name is expected to be checked ahead of calling this method. It is a logic /// error to send a notification using an unknown protocol. - pub async fn reserve_notification<'a>(&'a self) -> Result, ()> { + pub async fn reserve_notification(&self) -> Result, ()> { let mut lock = self.inner.async_channel.lock().await; let poll_ready = future::poll_fn(|cx| lock.poll_ready(cx)).await; @@ -449,9 +456,9 @@ impl<'a> Ready<'a> { } /// Error specific to the collection of protocols. -#[derive(Debug, derive_more::Display, derive_more::Error)] +#[derive(Debug, thiserror::Error)] pub enum NotifsHandlerError { - /// Channel of synchronous notifications is full. + #[error("Channel of synchronous notifications is full.")] SyncNotificationsClogged, } @@ -467,7 +474,7 @@ impl NotifsHandlerProto { } } -impl ProtocolsHandler for NotifsHandler { +impl ConnectionHandler for NotifsHandler { type InEvent = NotifsHandlerIn; type OutEvent = NotifsHandlerOut; type Error = NotifsHandlerError; @@ -497,7 +504,7 @@ impl ProtocolsHandler for NotifsHandler { let mut protocol_info = &mut self.protocols[protocol_index]; match protocol_info.state { State::Closed { pending_opening } => { - self.events_queue.push_back(ProtocolsHandlerEvent::Custom( + self.events_queue.push_back(ConnectionHandlerEvent::Custom( NotifsHandlerOut::OpenDesiredByRemote { protocol_index }, )); @@ -554,7 +561,7 @@ impl ProtocolsHandler for NotifsHandler { inner: Arc::new(NotificationsSinkInner { peer_id: self.peer_id, async_channel: FuturesMutex::new(async_tx), - sync_channel: Mutex::new(sync_tx), + sync_channel: Mutex::new(Some(sync_tx)), }), }; @@ -565,7 +572,7 @@ impl ProtocolsHandler for NotifsHandler { in_substream: in_substream.take(), }; - self.events_queue.push_back(ProtocolsHandlerEvent::Custom( + self.events_queue.push_back(ConnectionHandlerEvent::Custom( NotifsHandlerOut::OpenResultOk { protocol_index, negotiated_fallback: new_open.negotiated_fallback, @@ -593,7 +600,7 @@ impl ProtocolsHandler for NotifsHandler { ); self.events_queue.push_back( - ProtocolsHandlerEvent::OutboundSubstreamRequest { + ConnectionHandlerEvent::OutboundSubstreamRequest { protocol: SubstreamProtocol::new(proto, protocol_index) .with_timeout(OPEN_TIMEOUT), }, @@ -614,7 +621,7 @@ impl ProtocolsHandler for NotifsHandler { ); self.events_queue.push_back( - ProtocolsHandlerEvent::OutboundSubstreamRequest { + ConnectionHandlerEvent::OutboundSubstreamRequest { protocol: SubstreamProtocol::new(proto, protocol_index) .with_timeout(OPEN_TIMEOUT), }, @@ -652,7 +659,7 @@ impl ProtocolsHandler for NotifsHandler { self.protocols[protocol_index].state = State::Closed { pending_opening: true }; - self.events_queue.push_back(ProtocolsHandlerEvent::Custom( + self.events_queue.push_back(ConnectionHandlerEvent::Custom( NotifsHandlerOut::OpenResultErr { protocol_index }, )); }, @@ -662,7 +669,7 @@ impl ProtocolsHandler for NotifsHandler { State::Closed { .. } => {}, } - self.events_queue.push_back(ProtocolsHandlerEvent::Custom( + self.events_queue.push_back(ConnectionHandlerEvent::Custom( NotifsHandlerOut::CloseResult { protocol_index }, )); }, @@ -672,7 +679,7 @@ impl ProtocolsHandler for NotifsHandler { fn inject_dial_upgrade_error( &mut self, num: usize, - _: ProtocolsHandlerUpgrErr, + _: ConnectionHandlerUpgrErr, ) { match self.protocols[num].state { State::Closed { ref mut pending_opening } | @@ -684,7 +691,7 @@ impl ProtocolsHandler for NotifsHandler { State::Opening { .. } => { self.protocols[num].state = State::Closed { pending_opening: false }; - self.events_queue.push_back(ProtocolsHandlerEvent::Custom( + self.events_queue.push_back(ConnectionHandlerEvent::Custom( NotifsHandlerOut::OpenResultErr { protocol_index: num }, )); }, @@ -709,7 +716,7 @@ impl ProtocolsHandler for NotifsHandler { &mut self, cx: &mut Context, ) -> Poll< - ProtocolsHandlerEvent< + ConnectionHandlerEvent< Self::OutboundProtocol, Self::OutboundOpenInfo, Self::OutEvent, @@ -733,7 +740,7 @@ impl ProtocolsHandler for NotifsHandler { // a substream is ready to send if there isn't actually something to send. match Pin::new(&mut *notifications_sink_rx).as_mut().poll_peek(cx) { Poll::Ready(Some(&NotificationsSinkMessage::ForceClose)) => - return Poll::Ready(ProtocolsHandlerEvent::Close( + return Poll::Ready(ConnectionHandlerEvent::Close( NotifsHandlerError::SyncNotificationsClogged, )), Poll::Ready(Some(&NotificationsSinkMessage::Notification { .. })) => {}, @@ -781,7 +788,7 @@ impl ProtocolsHandler for NotifsHandler { Poll::Ready(Err(_)) => { *out_substream = None; let event = NotifsHandlerOut::CloseDesired { protocol_index }; - return Poll::Ready(ProtocolsHandlerEvent::Custom(event)) + return Poll::Ready(ConnectionHandlerEvent::Custom(event)) }, }; }, @@ -807,7 +814,7 @@ impl ProtocolsHandler for NotifsHandler { Poll::Pending => {}, Poll::Ready(Some(Ok(message))) => { let event = NotifsHandlerOut::Notification { protocol_index, message }; - return Poll::Ready(ProtocolsHandlerEvent::Custom(event)) + return Poll::Ready(ConnectionHandlerEvent::Custom(event)) }, Poll::Ready(None) | Poll::Ready(Some(Err(_))) => *in_substream = None, }, @@ -819,7 +826,7 @@ impl ProtocolsHandler for NotifsHandler { Poll::Ready(Err(_)) => { self.protocols[protocol_index].state = State::Closed { pending_opening: *pending_opening }; - return Poll::Ready(ProtocolsHandlerEvent::Custom( + return Poll::Ready(ConnectionHandlerEvent::Custom( NotifsHandlerOut::CloseDesired { protocol_index }, )) }, diff --git a/client/network/src/protocol/notifications/tests.rs b/client/network/src/protocol/notifications/tests.rs index 0b3ffc01a4b8d..fa79366d20283 100644 --- a/client/network/src/protocol/notifications/tests.rs +++ b/client/network/src/protocol/notifications/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,14 +23,14 @@ use crate::protocol::notifications::{Notifications, NotificationsOut, ProtocolCo use futures::prelude::*; use libp2p::{ core::{ - connection::{ConnectionId, ListenerId}, - transport::MemoryTransport, + connection::ConnectionId, + transport::{ListenerId, MemoryTransport}, upgrade, ConnectedPoint, }, identity, noise, swarm::{ - IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, - ProtocolsHandler, Swarm, SwarmEvent, + ConnectionHandler, DialError, IntoConnectionHandler, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, Swarm, SwarmEvent, }, yamux, Multiaddr, PeerId, Transport, }; @@ -56,7 +56,7 @@ fn build_nodes() -> (Swarm, Swarm) { let noise_keys = noise::Keypair::::new().into_authentic(&keypair).unwrap(); - let transport = MemoryTransport + let transport = MemoryTransport::new() .upgrade(upgrade::Version::V1) .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) .multiplex(yamux::YamuxConfig::default()) @@ -68,7 +68,7 @@ fn build_nodes() -> (Swarm, Swarm) { in_peers: 25, out_peers: 25, bootnodes: if index == 0 { - keypairs.iter().skip(1).map(|keypair| keypair.public().into_peer_id()).collect() + keypairs.iter().skip(1).map(|keypair| keypair.public().to_peer_id()).collect() } else { vec![] }, @@ -92,7 +92,7 @@ fn build_nodes() -> (Swarm, Swarm) { .enumerate() .filter_map(|(n, a)| { if n != index { - Some((keypairs[n].public().into_peer_id(), a.clone())) + Some((keypairs[n].public().to_peer_id(), a.clone())) } else { None } @@ -100,7 +100,7 @@ fn build_nodes() -> (Swarm, Swarm) { .collect(), }; - let mut swarm = Swarm::new(transport, behaviour, keypairs[index].public().into_peer_id()); + let mut swarm = Swarm::new(transport, behaviour, keypairs[index].public().to_peer_id()); swarm.listen_on(addrs[index].clone()).unwrap(); out.push(swarm); } @@ -133,10 +133,10 @@ impl std::ops::DerefMut for CustomProtoWithAddr { } impl NetworkBehaviour for CustomProtoWithAddr { - type ProtocolsHandler = ::ProtocolsHandler; + type ConnectionHandler = ::ConnectionHandler; type OutEvent = ::OutEvent; - fn new_handler(&mut self) -> Self::ProtocolsHandler { + fn new_handler(&mut self) -> Self::ConnectionHandler { self.inner.new_handler() } @@ -150,21 +150,21 @@ impl NetworkBehaviour for CustomProtoWithAddr { list } - fn inject_connected(&mut self, peer_id: &PeerId) { - self.inner.inject_connected(peer_id) - } - - fn inject_disconnected(&mut self, peer_id: &PeerId) { - self.inner.inject_disconnected(peer_id) - } - fn inject_connection_established( &mut self, peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + failed_addresses: Option<&Vec>, + other_established: usize, ) { - self.inner.inject_connection_established(peer_id, conn, endpoint) + self.inner.inject_connection_established( + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ) } fn inject_connection_closed( @@ -172,15 +172,18 @@ impl NetworkBehaviour for CustomProtoWithAddr { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + handler: ::Handler, + remaining_established: usize, ) { - self.inner.inject_connection_closed(peer_id, conn, endpoint) + self.inner + .inject_connection_closed(peer_id, conn, endpoint, handler, remaining_established) } fn inject_event( &mut self, peer_id: PeerId, connection: ConnectionId, - event: <::Handler as ProtocolsHandler>::OutEvent, + event: <::Handler as ConnectionHandler>::OutEvent, ) { self.inner.inject_event(peer_id, connection, event) } @@ -188,27 +191,18 @@ impl NetworkBehaviour for CustomProtoWithAddr { fn poll( &mut self, cx: &mut Context, - params: &mut impl PollParameters - ) -> Poll< - NetworkBehaviourAction< - <::Handler as ProtocolsHandler>::InEvent, - Self::OutEvent - > - >{ + params: &mut impl PollParameters, + ) -> Poll> { self.inner.poll(cx, params) } - fn inject_addr_reach_failure( + fn inject_dial_failure( &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - error: &dyn std::error::Error, + peer_id: Option, + handler: Self::ConnectionHandler, + error: &DialError, ) { - self.inner.inject_addr_reach_failure(peer_id, addr, error) - } - - fn inject_dial_failure(&mut self, peer_id: &PeerId) { - self.inner.inject_dial_failure(peer_id) + self.inner.inject_dial_failure(peer_id, handler, error) } fn inject_new_listener(&mut self, id: ListenerId) { diff --git a/client/network/src/protocol/notifications/upgrade.rs b/client/network/src/protocol/notifications/upgrade.rs index 196b4f44f81f7..c273361acabdd 100644 --- a/client/network/src/protocol/notifications/upgrade.rs +++ b/client/network/src/protocol/notifications/upgrade.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/network/src/protocol/notifications/upgrade/collec.rs b/client/network/src/protocol/notifications/upgrade/collec.rs index 2462d2becf4b1..db9850c8da74b 100644 --- a/client/network/src/protocol/notifications/upgrade/collec.rs +++ b/client/network/src/protocol/notifications/upgrade/collec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/network/src/protocol/notifications/upgrade/notifications.rs b/client/network/src/protocol/notifications/upgrade/notifications.rs index 997a1ccf1dec7..5d61e10727b66 100644 --- a/client/network/src/protocol/notifications/upgrade/notifications.rs +++ b/client/network/src/protocol/notifications/upgrade/notifications.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -39,9 +39,9 @@ use bytes::BytesMut; use futures::prelude::*; use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use log::{error, warn}; +use sc_network_common::protocol::ProtocolName; use std::{ - borrow::Cow, - convert::{Infallible, TryFrom as _}, + convert::Infallible, io, mem, pin::Pin, task::{Context, Poll}, @@ -58,7 +58,7 @@ const MAX_HANDSHAKE_SIZE: usize = 1024; pub struct NotificationsIn { /// Protocol name to use when negotiating the substream. /// The first one is the main name, while the other ones are fall backs. - protocol_names: Vec>, + protocol_names: Vec, /// Maximum allowed size for a single notification. max_notification_size: u64, } @@ -69,7 +69,7 @@ pub struct NotificationsIn { pub struct NotificationsOut { /// Protocol name to use when negotiating the substream. /// The first one is the main name, while the other ones are fall backs. - protocol_names: Vec>, + protocol_names: Vec, /// Message to send when we start the handshake. initial_message: Vec, /// Maximum allowed size for a single notification. @@ -114,8 +114,8 @@ pub struct NotificationsOutSubstream { impl NotificationsIn { /// Builds a new potential upgrade. pub fn new( - main_protocol_name: impl Into>, - fallback_names: Vec>, + main_protocol_name: impl Into, + fallback_names: Vec, max_notification_size: u64, ) -> Self { let mut protocol_names = fallback_names; @@ -126,16 +126,11 @@ impl NotificationsIn { } impl UpgradeInfo for NotificationsIn { - type Info = StringProtocolName; + type Info = ProtocolName; type InfoIter = vec::IntoIter; fn protocol_info(&self) -> Self::InfoIter { - self.protocol_names - .iter() - .cloned() - .map(StringProtocolName) - .collect::>() - .into_iter() + self.protocol_names.clone().into_iter() } } @@ -172,10 +167,10 @@ where Ok(NotificationsInOpen { handshake, - negotiated_fallback: if negotiated_name.0 == self.protocol_names[0] { + negotiated_fallback: if negotiated_name == self.protocol_names[0] { None } else { - Some(negotiated_name.0) + Some(negotiated_name) }, substream, }) @@ -189,7 +184,7 @@ pub struct NotificationsInOpen { pub handshake: Vec, /// If the negotiated name is not the "main" protocol name but a fallback, contains the /// name of the negotiated fallback. - pub negotiated_fallback: Option>, + pub negotiated_fallback: Option, /// Implementation of `Stream` that allows receives messages from the substream. pub substream: NotificationsInSubstream, } @@ -334,8 +329,8 @@ where impl NotificationsOut { /// Builds a new potential upgrade. pub fn new( - main_protocol_name: impl Into>, - fallback_names: Vec>, + main_protocol_name: impl Into, + fallback_names: Vec, initial_message: impl Into>, max_notification_size: u64, ) -> Self { @@ -351,27 +346,12 @@ impl NotificationsOut { } } -/// Implementation of the `ProtocolName` trait, where the protocol name is a string. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct StringProtocolName(Cow<'static, str>); - -impl upgrade::ProtocolName for StringProtocolName { - fn protocol_name(&self) -> &[u8] { - self.0.as_bytes() - } -} - impl UpgradeInfo for NotificationsOut { - type Info = StringProtocolName; + type Info = ProtocolName; type InfoIter = vec::IntoIter; fn protocol_info(&self) -> Self::InfoIter { - self.protocol_names - .iter() - .cloned() - .map(StringProtocolName) - .collect::>() - .into_iter() + self.protocol_names.clone().into_iter() } } @@ -406,10 +386,10 @@ where Ok(NotificationsOutOpen { handshake, - negotiated_fallback: if negotiated_name.0 == self.protocol_names[0] { + negotiated_fallback: if negotiated_name == self.protocol_names[0] { None } else { - Some(negotiated_name.0) + Some(negotiated_name) }, substream: NotificationsOutSubstream { socket: Framed::new(socket, codec) }, }) @@ -423,7 +403,7 @@ pub struct NotificationsOutOpen { pub handshake: Vec, /// If the negotiated name is not the "main" protocol name but a fallback, contains the /// name of the negotiated fallback. - pub negotiated_fallback: Option>, + pub negotiated_fallback: Option, /// Implementation of `Sink` that allows sending messages on the substream. pub substream: NotificationsOutSubstream, } @@ -457,13 +437,14 @@ where } /// Error generated by sending on a notifications out substream. -#[derive(Debug, derive_more::From, derive_more::Display)] +#[derive(Debug, thiserror::Error)] pub enum NotificationsHandshakeError { /// I/O error on the substream. - Io(io::Error), + #[error(transparent)] + Io(#[from] io::Error), /// Initial message or handshake was too large. - #[display(fmt = "Initial message or handshake was too large: {}", requested)] + #[error("Initial message or handshake was too large: {requested}")] TooLarge { /// Size requested by the remote. requested: usize, @@ -472,7 +453,8 @@ pub enum NotificationsHandshakeError { }, /// Error while decoding the variable-length integer. - VarintDecode(unsigned_varint::decode::Error), + #[error(transparent)] + VarintDecode(#[from] unsigned_varint::decode::Error), } impl From for NotificationsHandshakeError { @@ -489,30 +471,35 @@ impl From for NotificationsHandshakeError { } /// Error generated by sending on a notifications out substream. -#[derive(Debug, derive_more::From, derive_more::Display)] +#[derive(Debug, thiserror::Error)] pub enum NotificationsOutError { /// I/O error on the substream. - Io(io::Error), + #[error(transparent)] + Io(#[from] io::Error), } #[cfg(test)] mod tests { use super::{NotificationsIn, NotificationsInOpen, NotificationsOut, NotificationsOutOpen}; - - use async_std::net::{TcpListener, TcpStream}; use futures::{channel::oneshot, prelude::*}; use libp2p::core::upgrade; - use std::borrow::Cow; + use tokio::{ + net::{TcpListener, TcpStream}, + runtime::Runtime, + }; + use tokio_util::compat::TokioAsyncReadCompatExt; #[test] fn basic_works() { - const PROTO_NAME: Cow<'static, str> = Cow::Borrowed("/test/proto/1"); + const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let client = async_std::task::spawn(async move { + let runtime = Runtime::new().unwrap(); + + let client = runtime.spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let NotificationsOutOpen { handshake, mut substream, .. } = upgrade::apply_outbound( - socket, + socket.compat(), NotificationsOut::new(PROTO_NAME, Vec::new(), &b"initial message"[..], 1024 * 1024), upgrade::Version::V1, ) @@ -523,13 +510,13 @@ mod tests { substream.send(b"test message".to_vec()).await.unwrap(); }); - async_std::task::block_on(async move { + runtime.block_on(async move { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); let (socket, _) = listener.accept().await.unwrap(); let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( - socket, + socket.compat(), NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), ) .await @@ -542,20 +529,22 @@ mod tests { assert_eq!(msg.as_ref(), b"test message"); }); - async_std::task::block_on(client); + runtime.block_on(client).unwrap(); } #[test] fn empty_handshake() { // Check that everything still works when the handshake messages are empty. - const PROTO_NAME: Cow<'static, str> = Cow::Borrowed("/test/proto/1"); + const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let client = async_std::task::spawn(async move { + let runtime = Runtime::new().unwrap(); + + let client = runtime.spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let NotificationsOutOpen { handshake, mut substream, .. } = upgrade::apply_outbound( - socket, + socket.compat(), NotificationsOut::new(PROTO_NAME, Vec::new(), vec![], 1024 * 1024), upgrade::Version::V1, ) @@ -566,13 +555,13 @@ mod tests { substream.send(Default::default()).await.unwrap(); }); - async_std::task::block_on(async move { + runtime.block_on(async move { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); let (socket, _) = listener.accept().await.unwrap(); let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( - socket, + socket.compat(), NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), ) .await @@ -585,18 +574,20 @@ mod tests { assert!(msg.as_ref().is_empty()); }); - async_std::task::block_on(client); + runtime.block_on(client).unwrap(); } #[test] fn refused() { - const PROTO_NAME: Cow<'static, str> = Cow::Borrowed("/test/proto/1"); + const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let client = async_std::task::spawn(async move { + let runtime = Runtime::new().unwrap(); + + let client = runtime.spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let outcome = upgrade::apply_outbound( - socket, + socket.compat(), NotificationsOut::new(PROTO_NAME, Vec::new(), &b"hello"[..], 1024 * 1024), upgrade::Version::V1, ) @@ -608,13 +599,13 @@ mod tests { assert!(outcome.is_err()); }); - async_std::task::block_on(async move { + runtime.block_on(async move { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); let (socket, _) = listener.accept().await.unwrap(); let NotificationsInOpen { handshake, substream, .. } = upgrade::apply_inbound( - socket, + socket.compat(), NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), ) .await @@ -626,18 +617,20 @@ mod tests { drop(substream); }); - async_std::task::block_on(client); + runtime.block_on(client).unwrap(); } #[test] fn large_initial_message_refused() { - const PROTO_NAME: Cow<'static, str> = Cow::Borrowed("/test/proto/1"); + const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let client = async_std::task::spawn(async move { + let runtime = Runtime::new().unwrap(); + + let client = runtime.spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let ret = upgrade::apply_outbound( - socket, + socket.compat(), // We check that an initial message that is too large gets refused. NotificationsOut::new( PROTO_NAME, @@ -651,31 +644,33 @@ mod tests { assert!(ret.is_err()); }); - async_std::task::block_on(async move { + runtime.block_on(async move { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); let (socket, _) = listener.accept().await.unwrap(); let ret = upgrade::apply_inbound( - socket, + socket.compat(), NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), ) .await; assert!(ret.is_err()); }); - async_std::task::block_on(client); + runtime.block_on(client).unwrap(); } #[test] fn large_handshake_refused() { - const PROTO_NAME: Cow<'static, str> = Cow::Borrowed("/test/proto/1"); + const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let client = async_std::task::spawn(async move { + let runtime = Runtime::new().unwrap(); + + let client = runtime.spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let ret = upgrade::apply_outbound( - socket, + socket.compat(), NotificationsOut::new(PROTO_NAME, Vec::new(), &b"initial message"[..], 1024 * 1024), upgrade::Version::V1, ) @@ -683,13 +678,13 @@ mod tests { assert!(ret.is_err()); }); - async_std::task::block_on(async move { + runtime.block_on(async move { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); let (socket, _) = listener.accept().await.unwrap(); let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( - socket, + socket.compat(), NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), ) .await @@ -701,6 +696,6 @@ mod tests { let _ = substream.next().await; }); - async_std::task::block_on(client); + runtime.block_on(client).unwrap(); } } diff --git a/client/network/src/protocol/sync/state.rs b/client/network/src/protocol/sync/state.rs deleted file mode 100644 index d2e4463f98912..0000000000000 --- a/client/network/src/protocol/sync/state.rs +++ /dev/null @@ -1,170 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use super::StateDownloadProgress; -use crate::{ - chain::{Client, ImportedState}, - schema::v1::{StateEntry, StateRequest, StateResponse}, -}; -use codec::{Decode, Encode}; -use log::debug; -use sc_client_api::StorageProof; -use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; -use std::sync::Arc; - -/// State sync support. - -/// State sync state machine. Accumulates partial state data until it -/// is ready to be imported. -pub struct StateSync { - target_block: B::Hash, - target_header: B::Header, - target_root: B::Hash, - last_key: Vec, - state: Vec<(Vec, Vec)>, - complete: bool, - client: Arc>, - imported_bytes: u64, - skip_proof: bool, -} - -/// Import state chunk result. -pub enum ImportResult { - /// State is complete and ready for import. - Import(B::Hash, B::Header, ImportedState), - /// Continue dowloading. - Continue(StateRequest), - /// Bad state chunk. - BadResponse, -} - -impl StateSync { - /// Create a new instance. - pub fn new(client: Arc>, target: B::Header, skip_proof: bool) -> Self { - Self { - client, - target_block: target.hash(), - target_root: target.state_root().clone(), - target_header: target, - last_key: Vec::default(), - state: Vec::default(), - complete: false, - imported_bytes: 0, - skip_proof, - } - } - - /// Validate and import a state reponse. - pub fn import(&mut self, response: StateResponse) -> ImportResult { - if response.entries.is_empty() && response.proof.is_empty() && !response.complete { - debug!(target: "sync", "Bad state response"); - return ImportResult::BadResponse - } - if !self.skip_proof && response.proof.is_empty() { - debug!(target: "sync", "Missing proof"); - return ImportResult::BadResponse - } - let complete = if !self.skip_proof { - debug!(target: "sync", "Importing state from {} trie nodes", response.proof.len()); - let proof_size = response.proof.len() as u64; - let proof = match StorageProof::decode(&mut response.proof.as_ref()) { - Ok(proof) => proof, - Err(e) => { - debug!(target: "sync", "Error decoding proof: {:?}", e); - return ImportResult::BadResponse - }, - }; - let (values, complete) = - match self.client.verify_range_proof(self.target_root, proof, &self.last_key) { - Err(e) => { - debug!(target: "sync", "StateResponse failed proof verification: {:?}", e); - return ImportResult::BadResponse - }, - Ok(values) => values, - }; - debug!(target: "sync", "Imported with {} keys", values.len()); - - if let Some(last) = values.last().map(|(k, _)| k) { - self.last_key = last.clone(); - } - - for (key, value) in values { - self.imported_bytes += key.len() as u64; - self.state.push((key, value)) - } - self.imported_bytes += proof_size; - complete - } else { - debug!( - target: "sync", - "Importing state from {:?} to {:?}", - response.entries.last().map(|e| sp_core::hexdisplay::HexDisplay::from(&e.key)), - response.entries.first().map(|e| sp_core::hexdisplay::HexDisplay::from(&e.key)), - ); - - if let Some(e) = response.entries.last() { - self.last_key = e.key.clone(); - } - for StateEntry { key, value } in response.entries { - self.imported_bytes += (key.len() + value.len()) as u64; - self.state.push((key, value)) - } - response.complete - }; - if complete { - self.complete = true; - ImportResult::Import( - self.target_block, - self.target_header.clone(), - ImportedState { block: self.target_block, state: std::mem::take(&mut self.state) }, - ) - } else { - ImportResult::Continue(self.next_request()) - } - } - - /// Produce next state request. - pub fn next_request(&self) -> StateRequest { - StateRequest { - block: self.target_block.encode(), - start: self.last_key.clone(), - no_proof: self.skip_proof, - } - } - - /// Check if the state is complete. - pub fn is_complete(&self) -> bool { - self.complete - } - - /// Returns target block number. - pub fn target_block_num(&self) -> NumberFor { - *self.target_header.number() - } - - /// Returns target block hash. - pub fn target(&self) -> B::Hash { - self.target_block - } - - /// Returns state sync estimated progress. - pub fn progress(&self) -> StateDownloadProgress { - let percent_done = (*self.last_key.get(0).unwrap_or(&0u8) as u32) * 100 / 256; - StateDownloadProgress { percentage: percent_done, size: self.imported_bytes } - } -} diff --git a/client/network/src/request_responses.rs b/client/network/src/request_responses.rs index 6ebc7416c2a35..d49cbd8051341 100644 --- a/client/network/src/request_responses.rs +++ b/client/network/src/request_responses.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -40,23 +40,24 @@ use futures::{ prelude::*, }; use libp2p::{ - core::{ - connection::{ConnectionId, ListenerId}, - ConnectedPoint, Multiaddr, PeerId, - }, + core::{connection::ConnectionId, transport::ListenerId, ConnectedPoint, Multiaddr, PeerId}, request_response::{ - ProtocolSupport, RequestResponse, RequestResponseCodec, RequestResponseConfig, - RequestResponseEvent, RequestResponseMessage, ResponseChannel, + handler::RequestResponseHandler, ProtocolSupport, RequestResponse, RequestResponseCodec, + RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, }, swarm::{ - protocols_handler::multi::MultiHandler, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, ProtocolsHandler, + handler::multi::MultiHandler, ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, + }, +}; +use sc_network_common::{ + protocol::ProtocolName, + request_responses::{ + IfDisconnected, IncomingRequest, OutgoingResponse, ProtocolConfig, RequestFailure, }, }; use std::{ - borrow::Cow, collections::{hash_map::Entry, HashMap}, - convert::TryFrom as _, io, iter, pin::Pin, task::{Context, Poll}, @@ -64,96 +65,7 @@ use std::{ }; pub use libp2p::request_response::{InboundFailure, OutboundFailure, RequestId}; - -/// Configuration for a single request-response protocol. -#[derive(Debug, Clone)] -pub struct ProtocolConfig { - /// Name of the protocol on the wire. Should be something like `/foo/bar`. - pub name: Cow<'static, str>, - - /// Maximum allowed size, in bytes, of a request. - /// - /// Any request larger than this value will be declined as a way to avoid allocating too - /// much memory for it. - pub max_request_size: u64, - - /// Maximum allowed size, in bytes, of a response. - /// - /// Any response larger than this value will be declined as a way to avoid allocating too - /// much memory for it. - pub max_response_size: u64, - - /// Duration after which emitted requests are considered timed out. - /// - /// If you expect the response to come back quickly, you should set this to a smaller duration. - pub request_timeout: Duration, - - /// Channel on which the networking service will send incoming requests. - /// - /// Every time a peer sends a request to the local node using this protocol, the networking - /// service will push an element on this channel. The receiving side of this channel then has - /// to pull this element, process the request, and send back the response to send back to the - /// peer. - /// - /// The size of the channel has to be carefully chosen. If the channel is full, the networking - /// service will discard the incoming request send back an error to the peer. Consequently, - /// the channel being full is an indicator that the node is overloaded. - /// - /// You can typically set the size of the channel to `T / d`, where `T` is the - /// `request_timeout` and `d` is the expected average duration of CPU and I/O it takes to - /// build a response. - /// - /// Can be `None` if the local node does not support answering incoming requests. - /// If this is `None`, then the local node will not advertise support for this protocol towards - /// other peers. If this is `Some` but the channel is closed, then the local node will - /// advertise support for this protocol, but any incoming request will lead to an error being - /// sent back. - pub inbound_queue: Option>, -} - -/// A single request received by a peer on a request-response protocol. -#[derive(Debug)] -pub struct IncomingRequest { - /// Who sent the request. - pub peer: PeerId, - - /// Request sent by the remote. Will always be smaller than - /// [`ProtocolConfig::max_request_size`]. - pub payload: Vec, - - /// Channel to send back the response. - /// - /// There are two ways to indicate that handling the request failed: - /// - /// 1. Drop `pending_response` and thus not changing the reputation of the peer. - /// - /// 2. Sending an `Err(())` via `pending_response`, optionally including reputation changes for - /// the given peer. - pub pending_response: oneshot::Sender, -} - -/// Response for an incoming request to be send by a request protocol handler. -#[derive(Debug)] -pub struct OutgoingResponse { - /// The payload of the response. - /// - /// `Err(())` if none is available e.g. due an error while handling the request. - pub result: Result, ()>, - - /// Reputation changes accrued while handling the request. To be applied to the reputation of - /// the peer sending the request. - pub reputation_changes: Vec, - - /// If provided, the `oneshot::Sender` will be notified when the request has been sent to the - /// peer. - /// - /// > **Note**: Operating systems typically maintain a buffer of a few dozen kilobytes of - /// > outgoing data for each TCP socket, and it is not possible for a user - /// > application to inspect this buffer. This channel here is not actually notified - /// > when the response has been fully sent out, but rather when it has fully been - /// > written to the buffer managed by the operating system. - pub sent_feedback: Option>, -} +use sc_peerset::{PeersetHandle, BANNED_THRESHOLD}; /// Event generated by the [`RequestResponsesBehaviour`]. #[derive(Debug)] @@ -165,7 +77,7 @@ pub enum Event { /// Peer which has emitted the request. peer: PeerId, /// Name of the protocol in question. - protocol: Cow<'static, str>, + protocol: ProtocolName, /// Whether handling the request was successful or unsuccessful. /// /// When successful contains the time elapsed between when we received the request and when @@ -181,7 +93,7 @@ pub enum Event { /// Peer that we send a request to. peer: PeerId, /// Name of the protocol in question. - protocol: Cow<'static, str>, + protocol: ProtocolName, /// Duration the request took. duration: Duration, /// Result of the request. @@ -200,43 +112,23 @@ pub enum Event { /// [`ProtocolRequestId`]s. #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct ProtocolRequestId { - protocol: Cow<'static, str>, + protocol: ProtocolName, request_id: RequestId, } -impl From<(Cow<'static, str>, RequestId)> for ProtocolRequestId { - fn from((protocol, request_id): (Cow<'static, str>, RequestId)) -> Self { +impl From<(ProtocolName, RequestId)> for ProtocolRequestId { + fn from((protocol, request_id): (ProtocolName, RequestId)) -> Self { Self { protocol, request_id } } } -/// When sending a request, what to do on a disconnected recipient. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum IfDisconnected { - /// Try to connect to the peer. - TryConnect, - /// Just fail if the destination is not yet connected. - ImmediateError, -} - -/// Convenience functions for `IfDisconnected`. -impl IfDisconnected { - /// Shall we connect to a disconnected peer? - pub fn should_connect(self) -> bool { - match self { - Self::TryConnect => true, - Self::ImmediateError => false, - } - } -} - /// Implementation of `NetworkBehaviour` that provides support for request-response protocols. pub struct RequestResponsesBehaviour { /// The multiple sub-protocols, by name. /// Contains the underlying libp2p `RequestResponse` behaviour, plus an optional /// "response builder" used to build responses for incoming requests. protocols: HashMap< - Cow<'static, str>, + ProtocolName, (RequestResponse, Option>), >, @@ -256,13 +148,34 @@ pub struct RequestResponsesBehaviour { /// Whenever a response is received on `pending_responses`, insert a channel to be notified /// when the request has been sent out. send_feedback: HashMap>, + + /// Primarily used to get a reputation of a node. + peerset: PeersetHandle, + + /// Pending message request, holds `MessageRequest` as a Future state to poll it + /// until we get a response from `Peerset` + message_request: Option, +} + +// This is a state of processing incoming request Message. +// The main reason of this struct is to hold `get_peer_reputation` as a Future state. +struct MessageRequest { + peer: PeerId, + request_id: RequestId, + request: Vec, + channel: ResponseChannel, ()>>, + protocol: ProtocolName, + resp_builder: Option>, + // Once we get incoming request we save all params, create an async call to Peerset + // to get the reputation of the peer. + get_peer_reputation: Pin> + Send>>, } /// Generated by the response builder and waiting to be processed. struct RequestProcessingOutcome { peer: PeerId, request_id: RequestId, - protocol: Cow<'static, str>, + protocol: ProtocolName, inner_channel: ResponseChannel, ()>>, response: OutgoingResponse, } @@ -270,7 +183,10 @@ struct RequestProcessingOutcome { impl RequestResponsesBehaviour { /// Creates a new behaviour. Must be passed a list of supported protocols. Returns an error if /// the same protocol is passed twice. - pub fn new(list: impl Iterator) -> Result { + pub fn new( + list: impl Iterator, + peerset: PeersetHandle, + ) -> Result { let mut protocols = HashMap::new(); for protocol in list { let mut cfg = RequestResponseConfig::default(); @@ -288,7 +204,9 @@ impl RequestResponsesBehaviour { max_request_size: protocol.max_request_size, max_response_size: protocol.max_response_size, }, - iter::once((protocol.name.as_bytes().to_vec(), protocol_support)), + iter::once(protocol.name.as_bytes().to_vec()) + .chain(protocol.fallback_names.iter().map(|name| name.as_bytes().to_vec())) + .zip(iter::repeat(protocol_support)), cfg, ); @@ -304,6 +222,8 @@ impl RequestResponsesBehaviour { pending_responses: Default::default(), pending_responses_arrival_time: Default::default(), send_feedback: Default::default(), + peerset, + message_request: None, }) } @@ -329,35 +249,54 @@ impl RequestResponsesBehaviour { (Instant::now(), pending_response), ); debug_assert!(prev_req_id.is_none(), "Expect request id to be unique."); - } else { - if pending_response.send(Err(RequestFailure::NotConnected)).is_err() { - log::debug!( - target: "sub-libp2p", - "Not connected to peer {:?}. At the same time local \ - node is no longer interested in the result.", - target, - ); - }; - } - } else { - if pending_response.send(Err(RequestFailure::UnknownProtocol)).is_err() { + } else if pending_response.send(Err(RequestFailure::NotConnected)).is_err() { log::debug!( target: "sub-libp2p", - "Unknown protocol {:?}. At the same time local \ + "Not connected to peer {:?}. At the same time local \ node is no longer interested in the result.", - protocol_name, + target, ); - }; + } + } else if pending_response.send(Err(RequestFailure::UnknownProtocol)).is_err() { + log::debug!( + target: "sub-libp2p", + "Unknown protocol {:?}. At the same time local \ + node is no longer interested in the result.", + protocol_name, + ); } } + + fn new_handler_with_replacement( + &mut self, + protocol: String, + handler: RequestResponseHandler, + ) -> ::ConnectionHandler { + let mut handlers: HashMap<_, _> = self + .protocols + .iter_mut() + .map(|(p, (r, _))| (p.to_string(), NetworkBehaviour::new_handler(r))) + .collect(); + + if let Some(h) = handlers.get_mut(&protocol) { + *h = handler + } + + MultiHandler::try_from_iter(handlers).expect( + "Protocols are in a HashMap and there can be at most one handler per protocol name, \ + which is the only possible error; qed", + ) + } } impl NetworkBehaviour for RequestResponsesBehaviour { - type ProtocolsHandler = - MultiHandler as NetworkBehaviour>::ProtocolsHandler>; + type ConnectionHandler = MultiHandler< + String, + as NetworkBehaviour>::ConnectionHandler, + >; type OutEvent = Event; - fn new_handler(&mut self) -> Self::ProtocolsHandler { + fn new_handler(&mut self) -> Self::ConnectionHandler { let iter = self .protocols .iter_mut() @@ -378,15 +317,18 @@ impl NetworkBehaviour for RequestResponsesBehaviour { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + failed_addresses: Option<&Vec>, + other_established: usize, ) { for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_connection_established(p, peer_id, conn, endpoint) - } - } - - fn inject_connected(&mut self, peer_id: &PeerId) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_connected(p, peer_id) + NetworkBehaviour::inject_connection_established( + p, + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ) } } @@ -395,26 +337,25 @@ impl NetworkBehaviour for RequestResponsesBehaviour { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, + handler: ::Handler, + remaining_established: usize, ) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_connection_closed(p, peer_id, conn, endpoint) - } - } - - fn inject_disconnected(&mut self, peer_id: &PeerId) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_disconnected(p, peer_id) - } - } - - fn inject_addr_reach_failure( - &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - error: &dyn std::error::Error, - ) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_addr_reach_failure(p, peer_id, addr, error) + for (p_name, event) in handler.into_iter() { + if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { + proto.inject_connection_closed( + peer_id, + conn, + endpoint, + event, + remaining_established, + ) + } else { + log::error!( + target: "sub-libp2p", + "inject_connection_closed: no request-response instance registered for protocol {:?}", + p_name, + ) + } } } @@ -422,7 +363,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { &mut self, peer_id: PeerId, connection: ConnectionId, - (p_name, event): ::OutEvent, + (p_name, event): ::OutEvent, ) { if let Some((proto, _)) = self.protocols.get_mut(&*p_name) { return proto.inject_event(peer_id, connection, event) @@ -451,9 +392,15 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } } - fn inject_dial_failure(&mut self, peer_id: &PeerId) { + fn inject_dial_failure( + &mut self, + peer_id: Option, + _: Self::ConnectionHandler, + error: &libp2p::swarm::DialError, + ) { for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_dial_failure(p, peer_id) + let handler = p.new_handler(); + NetworkBehaviour::inject_dial_failure(p, peer_id, handler, error) } } @@ -485,13 +432,94 @@ impl NetworkBehaviour for RequestResponsesBehaviour { &mut self, cx: &mut Context, params: &mut impl PollParameters, - ) -> Poll< - NetworkBehaviourAction< - ::InEvent, - Self::OutEvent, - >, - > { + ) -> Poll> { 'poll_all: loop { + if let Some(message_request) = self.message_request.take() { + // Now we can can poll `MessageRequest` until we get the reputation + + let MessageRequest { + peer, + request_id, + request, + channel, + protocol, + resp_builder, + mut get_peer_reputation, + } = message_request; + + let reputation = Future::poll(Pin::new(&mut get_peer_reputation), cx); + match reputation { + Poll::Pending => { + // Save the state to poll it again next time. + + self.message_request = Some(MessageRequest { + peer, + request_id, + request, + channel, + protocol, + resp_builder, + get_peer_reputation, + }); + return Poll::Pending + }, + Poll::Ready(reputation) => { + // Once we get the reputation we can continue processing the request. + + let reputation = reputation.expect( + "The channel can only be closed if the peerset no longer exists; qed", + ); + + if reputation < BANNED_THRESHOLD { + log::debug!( + target: "sub-libp2p", + "Cannot handle requests from a node with a low reputation {}: {}", + peer, + reputation, + ); + continue 'poll_all + } + + let (tx, rx) = oneshot::channel(); + + // Submit the request to the "response builder" passed by the user at + // initialization. + if let Some(mut resp_builder) = resp_builder { + // If the response builder is too busy, silently drop `tx`. This + // will be reported by the corresponding `RequestResponse` through + // an `InboundFailure::Omission` event. + let _ = resp_builder.try_send(IncomingRequest { + peer, + payload: request, + pending_response: tx, + }); + } else { + debug_assert!(false, "Received message on outbound-only protocol."); + } + + self.pending_responses.push(Box::pin(async move { + // The `tx` created above can be dropped if we are not capable of + // processing this request, which is reflected as a + // `InboundFailure::Omission` event. + if let Ok(response) = rx.await { + Some(RequestProcessingOutcome { + peer, + request_id, + protocol, + inner_channel: channel, + response, + }) + } else { + None + } + })); + + // This `continue` makes sure that `pending_responses` gets polled + // after we have added the new element. + continue 'poll_all + }, + } + } // Poll to see if any response is ready to be sent back. while let Poll::Ready(Some(outcome)) = self.pending_responses.poll_next_unpin(cx) { let RequestProcessingOutcome { @@ -509,7 +537,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { if let Ok(payload) = result { if let Some((protocol, _)) = self.protocols.get_mut(&*protocol_name) { - if let Err(_) = protocol.send_response(inner_channel, Ok(payload)) { + if protocol.send_response(inner_channel, Ok(payload)).is_err() { // Note: Failure is handled further below when receiving // `InboundFailure` event from `RequestResponse` behaviour. log::debug!( @@ -519,11 +547,9 @@ impl NetworkBehaviour for RequestResponsesBehaviour { Dropping response", request_id, protocol_name, ); - } else { - if let Some(sent_feedback) = sent_feedback { - self.send_feedback - .insert((protocol_name, request_id).into(), sent_feedback); - } + } else if let Some(sent_feedback) = sent_feedback { + self.send_feedback + .insert((protocol_name, request_id).into(), sent_feedback); } } } @@ -544,17 +570,16 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // Other events generated by the underlying behaviour are transparently // passed through. - NetworkBehaviourAction::DialAddress { address } => { - log::error!( - "The request-response isn't supposed to start dialing peers" - ); - return Poll::Ready(NetworkBehaviourAction::DialAddress { address }) + NetworkBehaviourAction::Dial { opts, handler } => { + if opts.get_peer_id().is_none() { + log::error!( + "The request-response isn't supposed to start dialing addresses" + ); + } + let protocol = protocol.to_string(); + let handler = self.new_handler_with_replacement(protocol, handler); + return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) }, - NetworkBehaviourAction::DialPeer { peer_id, condition } => - return Poll::Ready(NetworkBehaviourAction::DialPeer { - peer_id, - condition, - }), NetworkBehaviourAction::NotifyHandler { peer_id, handler, event } => return Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, @@ -580,47 +605,26 @@ impl NetworkBehaviour for RequestResponsesBehaviour { message: RequestResponseMessage::Request { request_id, request, channel, .. }, } => { - self.pending_responses_arrival_time.insert( - (protocol.clone(), request_id.clone()).into(), - Instant::now(), - ); + self.pending_responses_arrival_time + .insert((protocol.clone(), request_id).into(), Instant::now()); - let (tx, rx) = oneshot::channel(); - - // Submit the request to the "response builder" passed by the user at - // initialization. - if let Some(resp_builder) = resp_builder { - // If the response builder is too busy, silently drop `tx`. This - // will be reported by the corresponding `RequestResponse` through - // an `InboundFailure::Omission` event. - let _ = resp_builder.try_send(IncomingRequest { - peer: peer.clone(), - payload: request, - pending_response: tx, - }); - } else { - debug_assert!(false, "Received message on outbound-only protocol."); - } + let get_peer_reputation = self.peerset.clone().peer_reputation(peer); + let get_peer_reputation = Box::pin(get_peer_reputation); - let protocol = protocol.clone(); - self.pending_responses.push(Box::pin(async move { - // The `tx` created above can be dropped if we are not capable of - // processing this request, which is reflected as a - // `InboundFailure::Omission` event. - if let Ok(response) = rx.await { - Some(RequestProcessingOutcome { - peer, - request_id, - protocol, - inner_channel: channel, - response, - }) - } else { - None - } - })); - - // This `continue` makes sure that `pending_responses` gets polled + // Save the Future-like state with params to poll `get_peer_reputation` + // and to continue processing the request once we get the reputation of + // the peer. + self.message_request = Some(MessageRequest { + peer, + request_id, + request, + channel, + protocol: protocol.clone(), + resp_builder: resp_builder.clone(), + get_peer_reputation, + }); + + // This `continue` makes sure that `message_request` gets polled // after we have added the new element. continue 'poll_all }, @@ -759,34 +763,18 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } /// Error when registering a protocol. -#[derive(Debug, derive_more::Display, derive_more::Error)] +#[derive(Debug, thiserror::Error)] pub enum RegisterError { /// A protocol has been specified multiple times. - DuplicateProtocol(#[error(ignore)] Cow<'static, str>), -} - -/// Error in a request. -#[derive(Debug, derive_more::Display, derive_more::Error)] -pub enum RequestFailure { - /// We are not currently connected to the requested peer. - NotConnected, - /// Given protocol hasn't been registered. - UnknownProtocol, - /// Remote has closed the substream before answering, thereby signaling that it considers the - /// request as valid, but refused to answer it. - Refused, - /// The remote replied, but the local node is no longer interested in the response. - Obsolete, - /// Problem on the network. - #[display(fmt = "Problem on the network: {}", _0)] - Network(OutboundFailure), + #[error("{0}")] + DuplicateProtocol(ProtocolName), } /// Error when processing a request sent by a remote. -#[derive(Debug, derive_more::Display, derive_more::Error)] +#[derive(Debug, thiserror::Error)] pub enum ResponseFailure { /// Problem on the network. - #[display(fmt = "Problem on the network: {}", _0)] + #[error("Problem on the network: {0}")] Network(InboundFailure), } @@ -934,29 +922,46 @@ mod tests { swarm::{Swarm, SwarmEvent}, Multiaddr, }; + use sc_peerset::{Peerset, PeersetConfig, SetConfig}; use std::{iter, time::Duration}; fn build_swarm( list: impl Iterator, - ) -> (Swarm, Multiaddr) { + ) -> (Swarm, Multiaddr, Peerset) { let keypair = Keypair::generate_ed25519(); let noise_keys = noise::Keypair::::new().into_authentic(&keypair).unwrap(); - let transport = MemoryTransport + let transport = MemoryTransport::new() .upgrade(upgrade::Version::V1) .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) .multiplex(libp2p::yamux::YamuxConfig::default()) .boxed(); - let behaviour = RequestResponsesBehaviour::new(list).unwrap(); + let config = PeersetConfig { + sets: vec![SetConfig { + in_peers: u32::max_value(), + out_peers: u32::max_value(), + bootnodes: vec![], + reserved_nodes: Default::default(), + reserved_only: false, + }], + }; + + let (peerset, handle) = Peerset::from_config(config); + + let behaviour = RequestResponsesBehaviour::new(list, handle).unwrap(); - let mut swarm = Swarm::new(transport, behaviour, keypair.public().into_peer_id()); + let mut swarm = Swarm::new(transport, behaviour, keypair.public().to_peer_id()); let listen_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); swarm.listen_on(listen_addr.clone()).unwrap(); - (swarm, listen_addr) + (swarm, listen_addr, peerset) + } + + async fn loop_peerset(peerset: Peerset) { + let _: Vec<_> = peerset.collect().await; } #[test] @@ -990,6 +995,7 @@ mod tests { let protocol_config = ProtocolConfig { name: From::from(protocol_name), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 1024 * 1024, request_timeout: Duration::from_secs(30), @@ -1004,13 +1010,15 @@ mod tests { // this test, so they wouldn't connect to each other. { let dial_addr = swarms[1].1.clone(); - Swarm::dial_addr(&mut swarms[0].0, dial_addr).unwrap(); + Swarm::dial(&mut swarms[0].0, dial_addr).unwrap(); } + let (mut swarm, _, peerset) = swarms.remove(0); + // Process every peerset event in the background. + pool.spawner().spawn_obj(loop_peerset(peerset).boxed().into()).unwrap(); // Running `swarm[0]` in the background. pool.spawner() .spawn_obj({ - let (mut swarm, _) = swarms.remove(0); async move { loop { match swarm.select_next_some().await { @@ -1027,7 +1035,9 @@ mod tests { .unwrap(); // Remove and run the remaining swarm. - let (mut swarm, _) = swarms.remove(0); + let (mut swarm, _, peerset) = swarms.remove(0); + // Process every peerset event in the background. + pool.spawner().spawn_obj(loop_peerset(peerset).boxed().into()).unwrap(); pool.run_until(async move { let mut response_receiver = None; @@ -1086,6 +1096,7 @@ mod tests { let protocol_config = ProtocolConfig { name: From::from(protocol_name), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 8, // <-- important for the test request_timeout: Duration::from_secs(30), @@ -1100,14 +1111,16 @@ mod tests { // this test, so they wouldn't connect to each other. { let dial_addr = swarms[1].1.clone(); - Swarm::dial_addr(&mut swarms[0].0, dial_addr).unwrap(); + Swarm::dial(&mut swarms[0].0, dial_addr).unwrap(); } // Running `swarm[0]` in the background until a `InboundRequest` event happens, // which is a hint about the test having ended. + let (mut swarm, _, peerset) = swarms.remove(0); + // Process every peerset event in the background. + pool.spawner().spawn_obj(loop_peerset(peerset).boxed().into()).unwrap(); pool.spawner() .spawn_obj({ - let (mut swarm, _) = swarms.remove(0); async move { loop { match swarm.select_next_some().await { @@ -1125,7 +1138,9 @@ mod tests { .unwrap(); // Remove and run the remaining swarm. - let (mut swarm, _) = swarms.remove(0); + let (mut swarm, _, peerset) = swarms.remove(0); + // Process every peerset event in the background. + pool.spawner().spawn_obj(loop_peerset(peerset).boxed().into()).unwrap(); pool.run_until(async move { let mut response_receiver = None; @@ -1178,6 +1193,7 @@ mod tests { let protocol_configs = vec![ ProtocolConfig { name: From::from(protocol_name_1), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 1024 * 1024, request_timeout: Duration::from_secs(30), @@ -1185,6 +1201,7 @@ mod tests { }, ProtocolConfig { name: From::from(protocol_name_2), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 1024 * 1024, request_timeout: Duration::from_secs(30), @@ -1195,13 +1212,14 @@ mod tests { build_swarm(protocol_configs.into_iter()).0 }; - let (mut swarm_2, mut swarm_2_handler_1, mut swarm_2_handler_2, listen_add_2) = { + let (mut swarm_2, mut swarm_2_handler_1, mut swarm_2_handler_2, listen_add_2, peerset) = { let (tx_1, rx_1) = mpsc::channel(64); let (tx_2, rx_2) = mpsc::channel(64); let protocol_configs = vec![ ProtocolConfig { name: From::from(protocol_name_1), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 1024 * 1024, request_timeout: Duration::from_secs(30), @@ -1209,6 +1227,7 @@ mod tests { }, ProtocolConfig { name: From::from(protocol_name_2), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 1024 * 1024, request_timeout: Duration::from_secs(30), @@ -1216,14 +1235,16 @@ mod tests { }, ]; - let (swarm, listen_addr) = build_swarm(protocol_configs.into_iter()); + let (swarm, listen_addr, peerset) = build_swarm(protocol_configs.into_iter()); - (swarm, rx_1, rx_2, listen_addr) + (swarm, rx_1, rx_2, listen_addr, peerset) }; + // Process every peerset event in the background. + pool.spawner().spawn_obj(loop_peerset(peerset).boxed().into()).unwrap(); // Ask swarm 1 to dial swarm 2. There isn't any discovery mechanism in place in this test, // so they wouldn't connect to each other. - swarm_1.dial_addr(listen_add_2).unwrap(); + swarm_1.dial(listen_add_2).unwrap(); // Run swarm 2 in the background, receiving two requests. pool.spawner() diff --git a/client/network/src/schema/light.v1.proto b/client/network/src/schema/light.v1.proto deleted file mode 100644 index 9b5d47719dc28..0000000000000 --- a/client/network/src/schema/light.v1.proto +++ /dev/null @@ -1,120 +0,0 @@ -// Schema definition for light client messages. - -syntax = "proto3"; - -package api.v1.light; - -// A pair of arbitrary bytes. -message Pair { - // The first element of the pair. - bytes fst = 1; - // The second element of the pair. - bytes snd = 2; -} - -// Enumerate all possible light client request messages. -message Request { - oneof request { - RemoteCallRequest remote_call_request = 1; - RemoteReadRequest remote_read_request = 2; - RemoteHeaderRequest remote_header_request = 3; - RemoteReadChildRequest remote_read_child_request = 4; - RemoteChangesRequest remote_changes_request = 5; - } -} - -// Enumerate all possible light client response messages. -message Response { - oneof response { - RemoteCallResponse remote_call_response = 1; - RemoteReadResponse remote_read_response = 2; - RemoteHeaderResponse remote_header_response = 3; - RemoteChangesResponse remote_changes_response = 4; - } -} - -// Remote call request. -message RemoteCallRequest { - // Block at which to perform call. - bytes block = 2; - // Method name. - string method = 3; - // Call data. - bytes data = 4; -} - -// Remote call response. -message RemoteCallResponse { - // Execution proof. - bytes proof = 2; -} - -// Remote storage read request. -message RemoteReadRequest { - // Block at which to perform call. - bytes block = 2; - // Storage keys. - repeated bytes keys = 3; -} - -// Remote read response. -message RemoteReadResponse { - // Read proof. - bytes proof = 2; -} - -// Remote storage read child request. -message RemoteReadChildRequest { - // Block at which to perform call. - bytes block = 2; - // Child Storage key, this is relative - // to the child type storage location. - bytes storage_key = 3; - // Storage keys. - repeated bytes keys = 6; -} - -// Remote header request. -message RemoteHeaderRequest { - // Block number to request header for. - bytes block = 2; -} - -// Remote header response. -message RemoteHeaderResponse { - // Header. None if proof generation has failed (e.g. header is unknown). - bytes header = 2; // optional - // Header proof. - bytes proof = 3; -} - -/// Remote changes request. -message RemoteChangesRequest { - // Hash of the first block of the range (including first) where changes are requested. - bytes first = 2; - // Hash of the last block of the range (including last) where changes are requested. - bytes last = 3; - // Hash of the first block for which the requester has the changes trie root. All other - // affected roots must be proved. - bytes min = 4; - // Hash of the last block that we can use when querying changes. - bytes max = 5; - // Storage child node key which changes are requested. - bytes storage_key = 6; // optional - // Storage key which changes are requested. - bytes key = 7; -} - -// Remote changes response. -message RemoteChangesResponse { - // Proof has been generated using block with this number as a max block. Should be - // less than or equal to the RemoteChangesRequest::max block number. - bytes max = 2; - // Changes proof. - repeated bytes proof = 3; - // Changes tries roots missing on the requester' node. - repeated Pair roots = 4; - // Missing changes tries roots proof. - bytes roots_proof = 5; -} - diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 525470145b78c..08e498299a1d3 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -29,54 +29,55 @@ use crate::{ behaviour::{self, Behaviour, BehaviourOut}, - bitswap::Bitswap, - config::{parse_str_addr, Params, TransportConfig}, + config::Params, discovery::DiscoveryConfig, - error::Error, - light_client_requests, network_state::{ NetworkState, NotConnectedPeer as NetworkStateNotConnectedPeer, Peer as NetworkStatePeer, }, - on_demand_layer::AlwaysBadChecker, - protocol::{ - self, - event::Event, - message::generic::Roles, - sync::{Status as SyncStatus, SyncState}, - NotificationsSink, NotifsHandlerError, PeerInfo, Protocol, Ready, - }, - transactions, transport, DhtEvent, ExHashT, NetworkStateInfo, NetworkStatus, ReputationChange, + protocol::{self, NotificationsSink, NotifsHandlerError, PeerInfo, Protocol, Ready}, + transport, ChainSyncInterface, ReputationChange, }; -use codec::Encode as _; use futures::{channel::oneshot, prelude::*}; use libp2p::{ - core::{ - connection::{ConnectionError, ConnectionLimits, PendingConnectionError}, - either::EitherError, - upgrade, ConnectedPoint, Executor, - }, - kad::record, + core::{either::EitherError, upgrade, ConnectedPoint, Executor}, + identify::Info as IdentifyInfo, + kad::record::Key as KademliaKey, multiaddr, - ping::handler::PingFailure, + ping::Failure as PingFailure, swarm::{ - protocols_handler::NodeHandlerWrapperError, AddressScore, NetworkBehaviour, SwarmBuilder, - SwarmEvent, + AddressScore, ConnectionError, ConnectionLimits, DialError, NetworkBehaviour, + PendingConnectionError, Swarm, SwarmBuilder, SwarmEvent, }, Multiaddr, PeerId, }; use log::{debug, error, info, trace, warn}; use metrics::{Histogram, HistogramVec, MetricSources, Metrics}; use parking_lot::Mutex; -use sc_consensus::{BlockImportError, BlockImportStatus, ImportQueue, Link}; +use sc_network_common::{ + config::{MultiaddrWithPeerId, TransportConfig}, + error::Error, + protocol::{ + event::{DhtEvent, Event}, + ProtocolName, + }, + request_responses::{IfDisconnected, RequestFailure}, + service::{ + NetworkDHTProvider, NetworkEventStream, NetworkNotification, NetworkPeers, NetworkSigner, + NetworkStateInfo, NetworkStatus, NetworkStatusProvider, NetworkSyncForkRequest, + NotificationSender as NotificationSenderT, NotificationSenderError, + NotificationSenderReady as NotificationSenderReadyT, Signature, SigningError, + }, + sync::SyncStatus, + ExHashT, +}; use sc_peerset::PeersetHandle; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; -use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_blockchain::HeaderBackend; +use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use std::{ - borrow::Cow, cmp, collections::{HashMap, HashSet}, - convert::TryFrom as _, fs, iter, marker::PhantomData, num::NonZeroUsize, @@ -89,15 +90,16 @@ use std::{ task::Poll, }; -pub use behaviour::{ - IfDisconnected, InboundFailure, OutboundFailure, RequestFailure, ResponseFailure, -}; +pub use behaviour::{InboundFailure, OutboundFailure, ResponseFailure}; mod metrics; mod out_events; #[cfg(test)] mod tests; +pub use libp2p::identity::{error::DecodingError, Keypair, PublicKey}; +use sc_network_common::service::{NetworkBlock, NetworkRequest}; + /// Substrate network service. Handles network IO and manages connectivity. pub struct NetworkService { /// Number of peers we're connected to. @@ -108,16 +110,20 @@ pub struct NetworkService { is_major_syncing: Arc, /// Local copy of the `PeerId` of the local node. local_peer_id: PeerId, + /// The `KeyPair` that defines the `PeerId` of the local node. + local_identity: Keypair, /// Bandwidth logging system. Can be queried to know the average bandwidth consumed. bandwidth: Arc, /// Peerset manager (PSM); manages the reputation of nodes and indicates the network which /// nodes it should be connected to or not. peerset: PeersetHandle, /// Channel that sends messages to the actual worker. - to_worker: TracingUnboundedSender>, + to_worker: TracingUnboundedSender>, + /// Interface that can be used to delegate calls to `ChainSync` + chain_sync_service: Box>, /// For each peer and protocol combination, an object that allows sending notifications to /// that peer. Updated by the [`NetworkWorker`]. - peers_notifications_sinks: Arc), NotificationsSink>>>, + peers_notifications_sinks: Arc>>, /// Field extracted from the [`Metrics`] struct and necessary to report the /// notifications-related metrics. notifications_sizes_metric: Option, @@ -126,13 +132,53 @@ pub struct NetworkService { _marker: PhantomData, } -impl NetworkWorker { +impl NetworkWorker +where + B: BlockT + 'static, + H: ExHashT, + Client: HeaderBackend + 'static, +{ /// Creates the network service. /// /// Returns a `NetworkWorker` that implements `Future` and must be regularly polled in order /// for the network processing to advance. From it, you can extract a `NetworkService` using /// `worker.service()`. The `NetworkService` can be shared through the codebase. - pub fn new(mut params: Params) -> Result { + pub fn new(mut params: Params) -> Result { + // Private and public keys configuration. + let local_identity = params.network_config.node_key.clone().into_keypair()?; + let local_public = local_identity.public(); + let local_peer_id = local_public.to_peer_id(); + + params + .network_config + .request_response_protocols + .extend(params.request_response_protocol_configs); + + params.network_config.boot_nodes = params + .network_config + .boot_nodes + .into_iter() + .filter(|boot_node| boot_node.peer_id != local_peer_id) + .collect(); + params.network_config.default_peers_set.reserved_nodes = params + .network_config + .default_peers_set + .reserved_nodes + .into_iter() + .filter(|reserved_node| { + if reserved_node.peer_id == local_peer_id { + warn!( + target: "sub-libp2p", + "Local peer ID used in reserved node, ignoring: {}", + reserved_node, + ); + false + } else { + true + } + }) + .collect(); + // Ensure the listen addresses are consistent with the transport. ensure_addresses_consistent_with_transport( params.network_config.listen_addresses.iter(), @@ -168,57 +214,26 @@ impl NetworkWorker { fs::create_dir_all(path)?; } - let transactions_handler_proto = - transactions::TransactionsHandlerPrototype::new(params.protocol_id.clone()); - params - .network_config - .extra_sets - .insert(0, transactions_handler_proto.set_config()); - - // Private and public keys configuration. - let local_identity = params.network_config.node_key.clone().into_keypair()?; - let local_public = local_identity.public(); - let local_peer_id = local_public.clone().into_peer_id(); info!( target: "sub-libp2p", - "🏷 Local node identity is: {}", + "🏷 Local node identity is: {}", local_peer_id.to_base58(), ); - let default_notif_handshake_message = Roles::from(¶ms.role).encode(); - - let (warp_sync_provider, warp_sync_protocol_config) = match params.warp_sync { - Some((p, c)) => (Some(p), Some(c)), - None => (None, None), - }; - let (protocol, peerset_handle, mut known_addresses) = Protocol::new( - protocol::ProtocolConfig { - roles: From::from(¶ms.role), - max_parallel_downloads: params.network_config.max_parallel_downloads, - sync_mode: params.network_config.sync_mode.clone(), - }, + From::from(¶ms.role), params.chain.clone(), - params.protocol_id.clone(), ¶ms.network_config, - iter::once(Vec::new()) - .chain( - (0..params.network_config.extra_sets.len() - 1) - .map(|_| default_notif_handshake_message.clone()), - ) - .collect(), - params.block_announce_validator, params.metrics_registry.as_ref(), - warp_sync_provider, + params.chain_sync, + params.block_announce_config, )?; // List of multiaddresses that we know in the network. - let mut bootnodes = Vec::new(); let mut boot_node_ids = HashSet::new(); // Process the bootnodes. for bootnode in params.network_config.boot_nodes.iter() { - bootnodes.push(bootnode.peer_id); boot_node_ids.insert(bootnode.peer_id); known_addresses.push((bootnode.peer_id, bootnode.multiaddr.clone())); } @@ -226,50 +241,47 @@ impl NetworkWorker { let boot_node_ids = Arc::new(boot_node_ids); // Check for duplicate bootnodes. - known_addresses.iter().try_for_each(|(peer_id, addr)| { - if let Some(other) = known_addresses.iter().find(|o| o.1 == *addr && o.0 != *peer_id) { + params.network_config.boot_nodes.iter().try_for_each(|bootnode| { + if let Some(other) = params + .network_config + .boot_nodes + .iter() + .filter(|o| o.multiaddr == bootnode.multiaddr) + .find(|o| o.peer_id != bootnode.peer_id) + { Err(Error::DuplicateBootnode { - address: addr.clone(), - first_id: *peer_id, - second_id: other.0.clone(), + address: bootnode.multiaddr.clone(), + first_id: bootnode.peer_id, + second_id: other.peer_id, }) } else { Ok(()) } })?; - let checker = params - .on_demand - .as_ref() - .map(|od| od.checker().clone()) - .unwrap_or_else(|| Arc::new(AlwaysBadChecker)); - let num_connected = Arc::new(AtomicUsize::new(0)); let is_major_syncing = Arc::new(AtomicBool::new(false)); // Build the swarm. - let client = params.chain.clone(); - let (mut swarm, bandwidth): (Swarm, _) = { + let (mut swarm, bandwidth): (Swarm>, _) = { let user_agent = format!( "{} ({})", params.network_config.client_version, params.network_config.node_name ); - let light_client_request_sender = { - light_client_requests::sender::LightClientRequestSender::new( - ¶ms.protocol_id, - checker, - peerset_handle.clone(), - ) - }; - let discovery_config = { let mut config = DiscoveryConfig::new(local_public.clone()); config.with_permanent_addresses(known_addresses); config.discovery_limit( u64::from(params.network_config.default_peers_set.out_peers) + 15, ); - config.add_protocol(params.protocol_id.clone()); + let genesis_hash = params + .chain + .hash(Zero::zero()) + .ok() + .flatten() + .expect("Genesis block exists; qed"); + config.with_kademlia(genesis_hash, params.fork_id.as_deref(), ¶ms.protocol_id); config.with_dht_random_walk(params.network_config.enable_dht_random_walk); config.allow_non_globals_in_dht(params.network_config.allow_non_globals_in_dht); config.use_kademlia_disjoint_query_paths( @@ -334,7 +346,7 @@ impl NetworkWorker { }; transport::build_transport( - local_identity, + local_identity.clone(), config_mem, params.network_config.yamux_window_size, yamux_maximum_buffer_size, @@ -342,19 +354,13 @@ impl NetworkWorker { }; let behaviour = { - let bitswap = params.network_config.ipfs_server.then(|| Bitswap::new(client)); let result = Behaviour::new( protocol, user_agent, local_public, - light_client_request_sender, discovery_config, - params.block_request_protocol_config, - params.state_request_protocol_config, - warp_sync_protocol_config, - bitswap, - params.light_client_request_protocol_config, params.network_config.request_response_protocols, + peerset_handle.clone(), ); match result { @@ -374,16 +380,17 @@ impl NetworkWorker { ) .substream_upgrade_protocol_override(upgrade::Version::V1Lazy) .notify_handler_buffer_size(NonZeroUsize::new(32).expect("32 != 0; qed")) - .connection_event_buffer_size(1024); - if let Some(spawner) = params.executor { - struct SpawnImpl(F); - impl + Send>>)> Executor for SpawnImpl { - fn exec(&self, f: Pin + Send>>) { - (self.0)(f) - } + .connection_event_buffer_size(1024) + .max_negotiating_inbound_streams(2048); + + struct SpawnImpl(F); + impl + Send>>)> Executor for SpawnImpl { + fn exec(&self, f: Pin + Send>>) { + (self.0)(f) } - builder = builder.executor(Box::new(SpawnImpl(spawner))); } + builder = builder.executor(Box::new(SpawnImpl(params.executor))); + (builder.build(), bandwidth) }; @@ -402,14 +409,18 @@ impl NetworkWorker { // Listen on multiaddresses. for addr in ¶ms.network_config.listen_addresses { - if let Err(err) = Swarm::::listen_on(&mut swarm, addr.clone()) { + if let Err(err) = Swarm::>::listen_on(&mut swarm, addr.clone()) { warn!(target: "sub-libp2p", "Can't listen on {} because: {:?}", addr, err) } } // Add external addresses. for addr in ¶ms.network_config.public_addresses { - Swarm::::add_external_address(&mut swarm, addr.clone(), AddressScore::Infinite); + Swarm::>::add_external_address( + &mut swarm, + addr.clone(), + AddressScore::Infinite, + ); } let external_addresses = Arc::new(Mutex::new(Vec::new())); @@ -422,7 +433,9 @@ impl NetworkWorker { is_major_syncing: is_major_syncing.clone(), peerset: peerset_handle, local_peer_id, + local_identity, to_worker, + chain_sync_service: params.chain_sync_service, peers_notifications_sinks: peers_notifications_sinks.clone(), notifications_sizes_metric: metrics .as_ref() @@ -430,28 +443,18 @@ impl NetworkWorker { _marker: PhantomData, }); - let (tx_handler, tx_handler_controller) = transactions_handler_proto.build( - service.clone(), - params.role, - params.transaction_pool, - params.metrics_registry.as_ref(), - )?; - (params.transactions_handler_executor)(tx_handler.run().boxed()); - Ok(NetworkWorker { external_addresses, num_connected, is_major_syncing, network_service: swarm, service, - import_queue: params.import_queue, from_service, - light_client_rqs: params.on_demand.and_then(|od| od.extract_receiver()), event_streams: out_events::OutChannels::new(params.metrics_registry.as_ref())?, peers_notifications_sinks, - tx_handler_controller, metrics, boot_node_ids, + _marker: Default::default(), }) } @@ -550,14 +553,14 @@ impl NetworkWorker { /// Returns the local `PeerId`. pub fn local_peer_id(&self) -> &PeerId { - Swarm::::local_peer_id(&self.network_service) + Swarm::>::local_peer_id(&self.network_service) } /// Returns the list of addresses we are listening on. /// /// Does **NOT** include a trailing `/p2p/` with our `PeerId`. pub fn listen_addresses(&self) -> impl Iterator { - Swarm::::listeners(&self.network_service) + Swarm::>::listeners(&self.network_service) } /// Get network state. @@ -578,7 +581,7 @@ impl NetworkWorker { .collect(); let endpoint = if let Some(e) = - swarm.behaviour_mut().node(peer_id).map(|i| i.endpoint()).flatten() + swarm.behaviour_mut().node(peer_id).and_then(|i| i.endpoint()) { e.clone().into() } else { @@ -637,7 +640,7 @@ impl NetworkWorker { .collect() }; - let peer_id = Swarm::::local_peer_id(&swarm).to_base58(); + let peer_id = Swarm::>::local_peer_id(swarm).to_base58(); let listened_addresses = swarm.listeners().cloned().collect(); let external_addresses = swarm.external_addresses().map(|r| &r.addr).cloned().collect(); @@ -657,7 +660,7 @@ impl NetworkWorker { .behaviour_mut() .user_protocol_mut() .peers_info() - .map(|(id, info)| (id.clone(), info.clone())) + .map(|(id, info)| (*id, info.clone())) .collect() } @@ -666,9 +669,8 @@ impl NetworkWorker { self.service.remove_reserved_peer(peer); } - /// Adds a `PeerId` and its address as reserved. The string should encode the address - /// and peer ID of the remote node. - pub fn add_reserved_peer(&self, peer: String) -> Result<(), String> { + /// Adds a `PeerId` and its `Multiaddr` as reserved. + pub fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String> { self.service.add_reserved_peer(peer) } @@ -679,296 +681,155 @@ impl NetworkWorker { } impl NetworkService { - /// Returns the local `PeerId`. - pub fn local_peer_id(&self) -> &PeerId { - &self.local_peer_id - } - - /// Set authorized peers. + /// Get network state. /// - /// Need a better solution to manage authorized peers, but now just use reserved peers for - /// prototyping. - pub fn set_authorized_peers(&self, peers: HashSet) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SetReserved(peers)); - } - - /// Set authorized_only flag. + /// **Note**: Use this only for debugging. This API is unstable. There are warnings literally + /// everywhere about this. Please don't use this function to retrieve actual information. /// - /// Need a better solution to decide authorized_only, but now just use reserved_only flag for - /// prototyping. - pub fn set_authorized_only(&self, reserved_only: bool) { - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::SetReservedOnly(reserved_only)); - } + /// Returns an error if the `NetworkWorker` is no longer running. + pub async fn network_state(&self) -> Result { + let (tx, rx) = oneshot::channel(); - /// Adds an address known to a node. - pub fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr) { let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); - } - - /// Appends a notification to the buffer of pending outgoing notifications with the given peer. - /// Has no effect if the notifications channel with this protocol name is not open. - /// - /// If the buffer of pending outgoing notifications with that peer is full, the notification - /// is silently dropped and the connection to the remote will start being shut down. This - /// happens if you call this method at a higher rate than the rate at which the peer processes - /// these notifications, or if the available network bandwidth is too low. - /// - /// For this reason, this method is considered soft-deprecated. You are encouraged to use - /// [`NetworkService::notification_sender`] instead. - /// - /// > **Note**: The reason why this is a no-op in the situation where we have no channel is - /// > that we don't guarantee message delivery anyway. Networking issues can cause - /// > connections to drop at any time, and higher-level logic shouldn't differentiate - /// > between the remote voluntarily closing a substream or a network error - /// > preventing the message from being delivered. - /// - /// The protocol must have been registered with - /// [`NetworkConfiguration::notifications_protocols`](crate::config::NetworkConfiguration:: - /// notifications_protocols). - pub fn write_notification( - &self, - target: PeerId, - protocol: Cow<'static, str>, - message: Vec, - ) { - // We clone the `NotificationsSink` in order to be able to unlock the network-wide - // `peers_notifications_sinks` mutex as soon as possible. - let sink = { - let peers_notifications_sinks = self.peers_notifications_sinks.lock(); - if let Some(sink) = peers_notifications_sinks.get(&(target.clone(), protocol.clone())) { - sink.clone() - } else { - // Notification silently discarded, as documented. - debug!( - target: "sub-libp2p", - "Attempted to send notification on missing or closed substream: {}, {:?}", - target, protocol, - ); - return - } - }; + .unbounded_send(ServiceToWorkerMsg::NetworkState { pending_response: tx }); - if let Some(notifications_sizes_metric) = self.notifications_sizes_metric.as_ref() { - notifications_sizes_metric - .with_label_values(&["out", &protocol]) - .observe(message.len() as f64); + match rx.await { + Ok(v) => v.map_err(|_| ()), + // The channel can only be closed if the network worker no longer exists. + Err(_) => Err(()), } - - // Sending is communicated to the `NotificationsSink`. - trace!( - target: "sub-libp2p", - "External API => Notification({:?}, {:?}, {} bytes)", - target, protocol, message.len() - ); - trace!(target: "sub-libp2p", "Handler({:?}) <= Sync notification", target); - sink.send_sync_notification(message); } - /// Obtains a [`NotificationSender`] for a connected peer, if it exists. - /// - /// A `NotificationSender` is scoped to a particular connection to the peer that holds - /// a receiver. With a `NotificationSender` at hand, sending a notification is done in two - /// steps: - /// - /// 1. [`NotificationSender::ready`] is used to wait for the sender to become ready - /// for another notification, yielding a [`NotificationSenderReady`] token. - /// 2. [`NotificationSenderReady::send`] enqueues the notification for sending. This operation - /// can only fail if the underlying notification substream or connection has suddenly closed. - /// - /// An error is returned by [`NotificationSenderReady::send`] if there exists no open - /// notifications substream with that combination of peer and protocol, or if the remote - /// has asked to close the notifications substream. If that happens, it is guaranteed that an - /// [`Event::NotificationStreamClosed`] has been generated on the stream returned by - /// [`NetworkService::event_stream`]. - /// - /// If the remote requests to close the notifications substream, all notifications successfully - /// enqueued using [`NotificationSenderReady::send`] will finish being sent out before the - /// substream actually gets closed, but attempting to enqueue more notifications will now - /// return an error. It is however possible for the entire connection to be abruptly closed, - /// in which case enqueued notifications will be lost. - /// - /// The protocol must have been registered with - /// [`NetworkConfiguration::notifications_protocols`](crate::config::NetworkConfiguration:: - /// notifications_protocols). - /// - /// # Usage - /// - /// This method returns a struct that allows waiting until there is space available in the - /// buffer of messages towards the given peer. If the peer processes notifications at a slower - /// rate than we send them, this buffer will quickly fill up. - /// - /// As such, you should never do something like this: - /// - /// ```ignore - /// // Do NOT do this - /// for peer in peers { - /// if let Ok(n) = network.notification_sender(peer, ...) { - /// if let Ok(s) = n.ready().await { - /// let _ = s.send(...); - /// } - /// } - /// } - /// ``` - /// - /// Doing so would slow down all peers to the rate of the slowest one. A malicious or - /// malfunctioning peer could intentionally process notifications at a very slow rate. - /// - /// Instead, you are encouraged to maintain your own buffer of notifications on top of the one - /// maintained by `sc-network`, and use `notification_sender` to progressively send out - /// elements from your buffer. If this additional buffer is full (which will happen at some - /// point if the peer is too slow to process notifications), appropriate measures can be taken, - /// such as removing non-critical notifications from the buffer or disconnecting the peer - /// using [`NetworkService::disconnect_peer`]. - /// - /// - /// Notifications Per-peer buffer - /// broadcast +-------> of notifications +--> `notification_sender` +--> Internet - /// ^ (not covered by - /// | sc-network) - /// + - /// Notifications should be dropped - /// if buffer is full - /// + /// Utility function to extract `PeerId` from each `Multiaddr` for peer set updates. /// - /// See also the [`gossip`](crate::gossip) module for a higher-level way to send - /// notifications. - pub fn notification_sender( + /// Returns an `Err` if one of the given addresses is invalid or contains an + /// invalid peer ID (which includes the local peer ID). + fn split_multiaddr_and_peer_id( &self, - target: PeerId, - protocol: Cow<'static, str>, - ) -> Result { - // We clone the `NotificationsSink` in order to be able to unlock the network-wide - // `peers_notifications_sinks` mutex as soon as possible. - let sink = { - let peers_notifications_sinks = self.peers_notifications_sinks.lock(); - if let Some(sink) = peers_notifications_sinks.get(&(target, protocol.clone())) { - sink.clone() - } else { - return Err(NotificationSenderError::Closed) - } - }; + peers: HashSet, + ) -> Result, String> { + peers + .into_iter() + .map(|mut addr| { + let peer = match addr.pop() { + Some(multiaddr::Protocol::P2p(key)) => PeerId::from_multihash(key) + .map_err(|_| "Invalid PeerId format".to_string())?, + _ => return Err("Missing PeerId from address".to_string()), + }; - let notification_size_metric = self - .notifications_sizes_metric - .as_ref() - .map(|histogram| histogram.with_label_values(&["out", &protocol])); + // Make sure the local peer ID is never added to the PSM + // or added as a "known address", even if given. + if peer == self.local_peer_id { + Err("Local peer ID in peer set.".to_string()) + } else { + Ok((peer, addr)) + } + }) + .collect::, String>>() + } +} - Ok(NotificationSender { sink, protocol_name: protocol, notification_size_metric }) +impl sp_consensus::SyncOracle for NetworkService { + fn is_major_syncing(&self) -> bool { + self.is_major_syncing.load(Ordering::Relaxed) } - /// Returns a stream containing the events that happen on the network. - /// - /// If this method is called multiple times, the events are duplicated. - /// - /// The stream never ends (unless the `NetworkWorker` gets shut down). - /// - /// The name passed is used to identify the channel in the Prometheus metrics. Note that the - /// parameter is a `&'static str`, and not a `String`, in order to avoid accidentally having - /// an unbounded set of Prometheus metrics, which would be quite bad in terms of memory - pub fn event_stream(&self, name: &'static str) -> impl Stream { - let (tx, rx) = out_events::channel(name); - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::EventStream(tx)); - rx + fn is_offline(&self) -> bool { + self.num_connected.load(Ordering::Relaxed) == 0 } +} - /// Sends a single targeted request to a specific peer. On success, returns the response of - /// the peer. - /// - /// Request-response protocols are a way to complement notifications protocols, but - /// notifications should remain the default ways of communicating information. For example, a - /// peer can announce something through a notification, after which the recipient can obtain - /// more information by performing a request. - /// As such, call this function with `IfDisconnected::ImmediateError` for `connect`. This way - /// you will get an error immediately for disconnected peers, instead of waiting for a - /// potentially very long connection attempt, which would suggest that something is wrong - /// anyway, as you are supposed to be connected because of the notification protocol. - /// - /// No limit or throttling of concurrent outbound requests per peer and protocol are enforced. - /// Such restrictions, if desired, need to be enforced at the call site(s). +impl sc_consensus::JustificationSyncLink for NetworkService { + /// Request a justification for the given block from the network. /// - /// The protocol must have been registered through - /// [`NetworkConfiguration::request_response_protocols`]( - /// crate::config::NetworkConfiguration::request_response_protocols). - pub async fn request( - &self, - target: PeerId, - protocol: impl Into>, - request: Vec, - connect: IfDisconnected, - ) -> Result, RequestFailure> { - let (tx, rx) = oneshot::channel(); + /// On success, the justification will be passed to the import queue that was part at + /// initialization as part of the configuration. + fn request_justification(&self, hash: &B::Hash, number: NumberFor) { + let _ = self.chain_sync_service.request_justification(hash, number); + } - self.start_request(target, protocol, request, tx, connect); + fn clear_justification_requests(&self) { + let _ = self.chain_sync_service.clear_justification_requests(); + } +} - match rx.await { - Ok(v) => v, - // The channel can only be closed if the network worker no longer exists. If the - // network worker no longer exists, then all connections to `target` are necessarily - // closed, and we legitimately report this situation as a "ConnectionClosed". - Err(_) => Err(RequestFailure::Network(OutboundFailure::ConnectionClosed)), - } +impl NetworkStateInfo for NetworkService +where + B: sp_runtime::traits::Block, + H: ExHashT, +{ + /// Returns the local external addresses. + fn external_addresses(&self) -> Vec { + self.external_addresses.lock().clone() } - /// Variation of `request` which starts a request whose response is delivered on a provided - /// channel. - /// - /// Instead of blocking and waiting for a reply, this function returns immediately, sending - /// responses via the passed in sender. This alternative API exists to make it easier to - /// integrate with message passing APIs. - /// - /// Keep in mind that the connected receiver might receive a `Canceled` event in case of a - /// closing connection. This is expected behaviour. With `request` you would get a - /// `RequestFailure::Network(OutboundFailure::ConnectionClosed)` in that case. - pub fn start_request( - &self, - target: PeerId, - protocol: impl Into>, - request: Vec, - tx: oneshot::Sender, RequestFailure>>, - connect: IfDisconnected, - ) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::Request { - target, - protocol: protocol.into(), - request, - pending_response: tx, - connect, - }); + /// Returns the local Peer ID. + fn local_peer_id(&self) -> PeerId { + self.local_peer_id } +} - /// High-level network status information. +impl NetworkSigner for NetworkService +where + B: sp_runtime::traits::Block, + H: ExHashT, +{ + fn sign_with_local_identity(&self, msg: impl AsRef<[u8]>) -> Result { + Signature::sign_message(msg.as_ref(), &self.local_identity) + } +} + +impl NetworkDHTProvider for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + /// Start getting a value from the DHT. /// - /// Returns an error if the `NetworkWorker` is no longer running. - pub async fn status(&self) -> Result, ()> { - let (tx, rx) = oneshot::channel(); + /// This will generate either a `ValueFound` or a `ValueNotFound` event and pass it as an + /// item on the [`NetworkWorker`] stream. + fn get_value(&self, key: &KademliaKey) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::GetValue(key.clone())); + } - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::NetworkStatus { pending_response: tx }); + /// Start putting a value in the DHT. + /// + /// This will generate either a `ValuePut` or a `ValuePutFailed` event and pass it as an + /// item on the [`NetworkWorker`] stream. + fn put_value(&self, key: KademliaKey, value: Vec) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::PutValue(key, value)); + } +} - match rx.await { - Ok(v) => v.map_err(|_| ()), - // The channel can only be closed if the network worker no longer exists. - Err(_) => Err(()), - } +impl NetworkSyncForkRequest> for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + /// Configure an explicit fork sync request. + /// Note that this function should not be used for recent blocks. + /// Sync should be able to download all the recent forks normally. + /// `set_sync_fork_request` should only be used if external code detects that there's + /// a stale fork missing. + /// Passing empty `peers` set effectively removes the sync request. + fn set_sync_fork_request(&self, peers: Vec, hash: B::Hash, number: NumberFor) { + self.chain_sync_service.set_sync_fork_request(peers, hash, number); } +} - /// Get network state. - /// - /// **Note**: Use this only for debugging. This API is unstable. There are warnings literally - /// everywhere about this. Please don't use this function to retrieve actual information. - /// - /// Returns an error if the `NetworkWorker` is no longer running. - pub async fn network_state(&self) -> Result { +#[async_trait::async_trait] +impl NetworkStatusProvider for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + async fn status(&self) -> Result, ()> { let (tx, rx) = oneshot::channel(); let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::NetworkState { pending_response: tx }); + .unbounded_send(ServiceToWorkerMsg::NetworkStatus { pending_response: tx }); match rx.await { Ok(v) => v.map_err(|_| ()), @@ -976,198 +837,130 @@ impl NetworkService { Err(_) => Err(()), } } +} - /// You may call this when new transactions are imported by the transaction pool. - /// - /// All transactions will be fetched from the `TransactionPool` that was passed at - /// initialization as part of the configuration and propagated to peers. - pub fn trigger_repropagate(&self) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::PropagateTransactions); - } - - /// You must call when new transaction is imported by the transaction pool. - /// - /// This transaction will be fetched from the `TransactionPool` that was passed at - /// initialization as part of the configuration and propagated to peers. - pub fn propagate_transaction(&self, hash: H) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::PropagateTransaction(hash)); - } - - /// Make sure an important block is propagated to peers. - /// - /// In chain-based consensus, we often need to make sure non-best forks are - /// at least temporarily synced. This function forces such an announcement. - pub fn announce_block(&self, hash: B::Hash, data: Option>) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::AnnounceBlock(hash, data)); - } - - /// Report a given peer as either beneficial (+) or costly (-) according to the - /// given scalar. - pub fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { - self.peerset.report_peer(who, cost_benefit); +impl NetworkPeers for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + fn set_authorized_peers(&self, peers: HashSet) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SetReserved(peers)); } - /// Disconnect from a node as soon as possible. - /// - /// This triggers the same effects as if the connection had closed itself spontaneously. - /// - /// See also [`NetworkService::remove_from_peers_set`], which has the same effect but also - /// prevents the local node from re-establishing an outgoing substream to this peer until it - /// is added again. - pub fn disconnect_peer(&self, who: PeerId, protocol: impl Into>) { + fn set_authorized_only(&self, reserved_only: bool) { let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::DisconnectPeer(who, protocol.into())); + .unbounded_send(ServiceToWorkerMsg::SetReservedOnly(reserved_only)); } - /// Request a justification for the given block from the network. - /// - /// On success, the justification will be passed to the import queue that was part at - /// initialization as part of the configuration. - pub fn request_justification(&self, hash: &B::Hash, number: NumberFor) { + fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr) { let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::RequestJustification(*hash, number)); - } - - /// Clear all pending justification requests. - pub fn clear_justification_requests(&self) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::ClearJustificationRequests); + .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); } - /// Are we in the process of downloading the chain? - pub fn is_major_syncing(&self) -> bool { - self.is_major_syncing.load(Ordering::Relaxed) + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { + self.peerset.report_peer(who, cost_benefit); } - /// Start getting a value from the DHT. - /// - /// This will generate either a `ValueFound` or a `ValueNotFound` event and pass it as an - /// item on the [`NetworkWorker`] stream. - pub fn get_value(&self, key: &record::Key) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::GetValue(key.clone())); + fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::DisconnectPeer(who, protocol)); } - /// Start putting a value in the DHT. - /// - /// This will generate either a `ValuePut` or a `ValuePutFailed` event and pass it as an - /// item on the [`NetworkWorker`] stream. - pub fn put_value(&self, key: record::Key, value: Vec) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::PutValue(key, value)); - } - - /// Connect to unreserved peers and allow unreserved peers to connect for syncing purposes. - pub fn accept_unreserved_peers(&self) { + fn accept_unreserved_peers(&self) { let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SetReservedOnly(false)); } - /// Disconnect from unreserved peers and deny new unreserved peers to connect for syncing - /// purposes. - pub fn deny_unreserved_peers(&self) { + fn deny_unreserved_peers(&self) { let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SetReservedOnly(true)); } - /// Adds a `PeerId` and its address as reserved. The string should encode the address - /// and peer ID of the remote node. - /// - /// Returns an `Err` if the given string is not a valid multiaddress - /// or contains an invalid peer ID (which includes the local peer ID). - pub fn add_reserved_peer(&self, peer: String) -> Result<(), String> { - let (peer_id, addr) = parse_str_addr(&peer).map_err(|e| format!("{:?}", e))?; + fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String> { // Make sure the local peer ID is never added to the PSM. - if peer_id == self.local_peer_id { + if peer.peer_id == self.local_peer_id { return Err("Local peer ID cannot be added as a reserved peer.".to_string()) } let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id.clone(), addr)); - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::AddReserved(peer_id)); + .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer.peer_id, peer.multiaddr)); + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::AddReserved(peer.peer_id)); Ok(()) } - /// Removes a `PeerId` from the list of reserved peers. - pub fn remove_reserved_peer(&self, peer_id: PeerId) { + fn remove_reserved_peer(&self, peer_id: PeerId) { let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::RemoveReserved(peer_id)); } - /// Add peers to a peer set. - /// - /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also - /// consist of only `/p2p/`. - /// - /// Returns an `Err` if one of the given addresses is invalid or contains an - /// invalid peer ID (which includes the local peer ID). - pub fn add_peers_to_reserved_set( + fn set_reserved_peers( &self, - protocol: Cow<'static, str>, + protocol: ProtocolName, peers: HashSet, ) -> Result<(), String> { - let peers = self.split_multiaddr_and_peer_id(peers)?; + let peers_addrs = self.split_multiaddr_and_peer_id(peers)?; - for (peer_id, addr) in peers.into_iter() { + let mut peers: HashSet = HashSet::with_capacity(peers_addrs.len()); + + for (peer_id, addr) in peers_addrs.into_iter() { // Make sure the local peer ID is never added to the PSM. if peer_id == self.local_peer_id { return Err("Local peer ID cannot be added as a reserved peer.".to_string()) } + peers.insert(peer_id); + if !addr.is_empty() { let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id.clone(), addr)); + .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); } - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::AddSetReserved(protocol.clone(), peer_id)); } + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::SetPeersetReserved(protocol, peers)); + Ok(()) } - /// Remove peers from a peer set. - /// - /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. - /// - /// Returns an `Err` if one of the given addresses is invalid or contains an - /// invalid peer ID (which includes the local peer ID). - // NOTE: technically, this function only needs `Vec`, but we use `Multiaddr` here for - // convenience. - pub fn remove_peers_from_reserved_set( + fn add_peers_to_reserved_set( &self, - protocol: Cow<'static, str>, + protocol: ProtocolName, peers: HashSet, ) -> Result<(), String> { let peers = self.split_multiaddr_and_peer_id(peers)?; - for (peer_id, _) in peers.into_iter() { + + for (peer_id, addr) in peers.into_iter() { + // Make sure the local peer ID is never added to the PSM. + if peer_id == self.local_peer_id { + return Err("Local peer ID cannot be added as a reserved peer.".to_string()) + } + + if !addr.is_empty() { + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); + } let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::RemoveSetReserved(protocol.clone(), peer_id)); + .unbounded_send(ServiceToWorkerMsg::AddSetReserved(protocol.clone(), peer_id)); } + Ok(()) } - /// Configure an explicit fork sync request. - /// Note that this function should not be used for recent blocks. - /// Sync should be able to download all the recent forks normally. - /// `set_sync_fork_request` should only be used if external code detects that there's - /// a stale fork missing. - /// Passing empty `peers` set effectively removes the sync request. - pub fn set_sync_fork_request(&self, peers: Vec, hash: B::Hash, number: NumberFor) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SyncFork(peers, hash, number)); + fn remove_peers_from_reserved_set(&self, protocol: ProtocolName, peers: Vec) { + for peer_id in peers.into_iter() { + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::RemoveSetReserved(protocol.clone(), peer_id)); + } } - /// Add a peer to a set of peers. - /// - /// If the set has slots available, it will try to open a substream with this peer. - /// - /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also - /// consist of only `/p2p/`. - /// - /// Returns an `Err` if one of the given addresses is invalid or contains an - /// invalid peer ID (which includes the local peer ID). - pub fn add_to_peers_set( + fn add_to_peers_set( &self, - protocol: Cow<'static, str>, + protocol: ProtocolName, peers: HashSet, ) -> Result<(), String> { let peers = self.split_multiaddr_and_peer_id(peers)?; @@ -1181,7 +974,7 @@ impl NetworkService { if !addr.is_empty() { let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id.clone(), addr)); + .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); } let _ = self .to_worker @@ -1191,114 +984,152 @@ impl NetworkService { Ok(()) } - /// Remove peers from a peer set. - /// - /// If we currently have an open substream with this peer, it will soon be closed. - /// - /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. - /// - /// Returns an `Err` if one of the given addresses is invalid or contains an - /// invalid peer ID (which includes the local peer ID). - // NOTE: technically, this function only needs `Vec`, but we use `Multiaddr` here for - // convenience. - pub fn remove_from_peers_set( - &self, - protocol: Cow<'static, str>, - peers: HashSet, - ) -> Result<(), String> { - let peers = self.split_multiaddr_and_peer_id(peers)?; - for (peer_id, _) in peers.into_iter() { + fn remove_from_peers_set(&self, protocol: ProtocolName, peers: Vec) { + for peer_id in peers.into_iter() { let _ = self .to_worker .unbounded_send(ServiceToWorkerMsg::RemoveFromPeersSet(protocol.clone(), peer_id)); } - Ok(()) } - /// Returns the number of peers we're connected to. - pub fn num_connected(&self) -> usize { + fn sync_num_connected(&self) -> usize { self.num_connected.load(Ordering::Relaxed) } +} - /// Inform the network service about new best imported block. - pub fn new_best_block_imported(&self, hash: B::Hash, number: NumberFor) { - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::NewBestBlockImported(hash, number)); +impl NetworkEventStream for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + fn event_stream(&self, name: &'static str) -> Pin + Send>> { + let (tx, rx) = out_events::channel(name); + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::EventStream(tx)); + Box::pin(rx) } +} - /// Utility function to extract `PeerId` from each `Multiaddr` for peer set updates. - /// - /// Returns an `Err` if one of the given addresses is invalid or contains an - /// invalid peer ID (which includes the local peer ID). - fn split_multiaddr_and_peer_id( - &self, - peers: HashSet, - ) -> Result, String> { - peers - .into_iter() - .map(|mut addr| { - let peer = match addr.pop() { - Some(multiaddr::Protocol::P2p(key)) => PeerId::from_multihash(key) - .map_err(|_| "Invalid PeerId format".to_string())?, - _ => return Err("Missing PeerId from address".to_string()), - }; +impl NetworkNotification for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + fn write_notification(&self, target: PeerId, protocol: ProtocolName, message: Vec) { + // We clone the `NotificationsSink` in order to be able to unlock the network-wide + // `peers_notifications_sinks` mutex as soon as possible. + let sink = { + let peers_notifications_sinks = self.peers_notifications_sinks.lock(); + if let Some(sink) = peers_notifications_sinks.get(&(target, protocol.clone())) { + sink.clone() + } else { + // Notification silently discarded, as documented. + debug!( + target: "sub-libp2p", + "Attempted to send notification on missing or closed substream: {}, {:?}", + target, protocol, + ); + return + } + }; - // Make sure the local peer ID is never added to the PSM - // or added as a "known address", even if given. - if peer == self.local_peer_id { - Err("Local peer ID in peer set.".to_string()) - } else { - Ok((peer, addr)) - } - }) - .collect::, String>>() - } -} + if let Some(notifications_sizes_metric) = self.notifications_sizes_metric.as_ref() { + notifications_sizes_metric + .with_label_values(&["out", &protocol]) + .observe(message.len() as f64); + } -impl sp_consensus::SyncOracle for NetworkService { - fn is_major_syncing(&mut self) -> bool { - Self::is_major_syncing(self) + // Sending is communicated to the `NotificationsSink`. + trace!( + target: "sub-libp2p", + "External API => Notification({:?}, {:?}, {} bytes)", + target, protocol, message.len() + ); + trace!(target: "sub-libp2p", "Handler({:?}) <= Sync notification", target); + sink.send_sync_notification(message); } - fn is_offline(&mut self) -> bool { - self.num_connected.load(Ordering::Relaxed) == 0 - } -} + fn notification_sender( + &self, + target: PeerId, + protocol: ProtocolName, + ) -> Result, NotificationSenderError> { + // We clone the `NotificationsSink` in order to be able to unlock the network-wide + // `peers_notifications_sinks` mutex as soon as possible. + let sink = { + let peers_notifications_sinks = self.peers_notifications_sinks.lock(); + if let Some(sink) = peers_notifications_sinks.get(&(target, protocol.clone())) { + sink.clone() + } else { + return Err(NotificationSenderError::Closed) + } + }; -impl<'a, B: BlockT + 'static, H: ExHashT> sp_consensus::SyncOracle for &'a NetworkService { - fn is_major_syncing(&mut self) -> bool { - NetworkService::is_major_syncing(self) - } + let notification_size_metric = self + .notifications_sizes_metric + .as_ref() + .map(|histogram| histogram.with_label_values(&["out", &protocol])); - fn is_offline(&mut self) -> bool { - self.num_connected.load(Ordering::Relaxed) == 0 + Ok(Box::new(NotificationSender { sink, protocol_name: protocol, notification_size_metric })) } } -impl sc_consensus::JustificationSyncLink for NetworkService { - fn request_justification(&self, hash: &B::Hash, number: NumberFor) { - Self::request_justification(self, hash, number); +#[async_trait::async_trait] +impl NetworkRequest for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + async fn request( + &self, + target: PeerId, + protocol: ProtocolName, + request: Vec, + connect: IfDisconnected, + ) -> Result, RequestFailure> { + let (tx, rx) = oneshot::channel(); + + self.start_request(target, protocol, request, tx, connect); + + match rx.await { + Ok(v) => v, + // The channel can only be closed if the network worker no longer exists. If the + // network worker no longer exists, then all connections to `target` are necessarily + // closed, and we legitimately report this situation as a "ConnectionClosed". + Err(_) => Err(RequestFailure::Network(OutboundFailure::ConnectionClosed)), + } } - fn clear_justification_requests(&self) { - Self::clear_justification_requests(self); + fn start_request( + &self, + target: PeerId, + protocol: ProtocolName, + request: Vec, + tx: oneshot::Sender, RequestFailure>>, + connect: IfDisconnected, + ) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::Request { + target, + protocol: protocol.into(), + request, + pending_response: tx, + connect, + }); } } -impl NetworkStateInfo for NetworkService +impl NetworkBlock> for NetworkService where - B: sp_runtime::traits::Block, + B: BlockT + 'static, H: ExHashT, { - /// Returns the local external addresses. - fn external_addresses(&self) -> Vec { - self.external_addresses.lock().clone() + fn announce_block(&self, hash: B::Hash, data: Option>) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::AnnounceBlock(hash, data)); } - /// Returns the local Peer ID. - fn local_peer_id(&self) -> PeerId { - self.local_peer_id + fn new_best_block_imported(&self, hash: B::Hash, number: NumberFor) { + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::NewBestBlockImported(hash, number)); } } @@ -1308,52 +1139,48 @@ pub struct NotificationSender { sink: NotificationsSink, /// Name of the protocol on the wire. - protocol_name: Cow<'static, str>, + protocol_name: ProtocolName, /// Field extracted from the [`Metrics`] struct and necessary to report the /// notifications-related metrics. notification_size_metric: Option, } -impl NotificationSender { - /// Returns a future that resolves when the `NotificationSender` is ready to send a - /// notification. - pub async fn ready<'a>( - &'a self, - ) -> Result, NotificationSenderError> { - Ok(NotificationSenderReady { +#[async_trait::async_trait] +impl NotificationSenderT for NotificationSender { + async fn ready( + &self, + ) -> Result, NotificationSenderError> { + Ok(Box::new(NotificationSenderReady { ready: match self.sink.reserve_notification().await { - Ok(r) => r, + Ok(r) => Some(r), Err(()) => return Err(NotificationSenderError::Closed), }, peer_id: self.sink.peer_id(), protocol_name: &self.protocol_name, notification_size_metric: self.notification_size_metric.clone(), - }) + })) } } /// Reserved slot in the notifications buffer, ready to accept data. #[must_use] pub struct NotificationSenderReady<'a> { - ready: Ready<'a>, + ready: Option>, /// Target of the notification. peer_id: &'a PeerId, /// Name of the protocol on the wire. - protocol_name: &'a Cow<'static, str>, + protocol_name: &'a ProtocolName, /// Field extracted from the [`Metrics`] struct and necessary to report the /// notifications-related metrics. notification_size_metric: Option, } -impl<'a> NotificationSenderReady<'a> { - /// Consumes this slots reservation and actually queues the notification. - pub fn send(self, notification: impl Into>) -> Result<(), NotificationSenderError> { - let notification = notification.into(); - +impl<'a> NotificationSenderReadyT for NotificationSenderReady<'a> { + fn send(&mut self, notification: Vec) -> Result<(), NotificationSenderError> { if let Some(notification_size_metric) = &self.notification_size_metric { notification_size_metric.observe(notification.len() as f64); } @@ -1365,49 +1192,35 @@ impl<'a> NotificationSenderReady<'a> { ); trace!(target: "sub-libp2p", "Handler({:?}) <= Async notification", self.peer_id); - self.ready.send(notification).map_err(|()| NotificationSenderError::Closed) + self.ready + .take() + .ok_or(NotificationSenderError::Closed)? + .send(notification) + .map_err(|()| NotificationSenderError::Closed) } } -/// Error returned by [`NetworkService::send_notification`]. -#[derive(Debug, derive_more::Display, derive_more::Error)] -pub enum NotificationSenderError { - /// The notification receiver has been closed, usually because the underlying connection - /// closed. - /// - /// Some of the notifications most recently sent may not have been received. However, - /// the peer may still be connected and a new `NotificationSender` for the same - /// protocol obtained from [`NetworkService::notification_sender`]. - Closed, - /// Protocol name hasn't been registered. - BadProtocol, -} - /// Messages sent from the `NetworkService` to the `NetworkWorker`. /// /// Each entry corresponds to a method of `NetworkService`. -enum ServiceToWorkerMsg { - PropagateTransaction(H), - PropagateTransactions, - RequestJustification(B::Hash, NumberFor), - ClearJustificationRequests, +enum ServiceToWorkerMsg { AnnounceBlock(B::Hash, Option>), - GetValue(record::Key), - PutValue(record::Key, Vec), + GetValue(KademliaKey), + PutValue(KademliaKey, Vec), AddKnownAddress(PeerId, Multiaddr), SetReservedOnly(bool), AddReserved(PeerId), RemoveReserved(PeerId), SetReserved(HashSet), - AddSetReserved(Cow<'static, str>, PeerId), - RemoveSetReserved(Cow<'static, str>, PeerId), - AddToPeersSet(Cow<'static, str>, PeerId), - RemoveFromPeersSet(Cow<'static, str>, PeerId), - SyncFork(Vec, B::Hash, NumberFor), + SetPeersetReserved(ProtocolName, HashSet), + AddSetReserved(ProtocolName, PeerId), + RemoveSetReserved(ProtocolName, PeerId), + AddToPeersSet(ProtocolName, PeerId), + RemoveFromPeersSet(ProtocolName, PeerId), EventStream(out_events::Sender), Request { target: PeerId, - protocol: Cow<'static, str>, + protocol: ProtocolName, request: Vec, pending_response: oneshot::Sender, RequestFailure>>, connect: IfDisconnected, @@ -1418,7 +1231,7 @@ enum ServiceToWorkerMsg { NetworkState { pending_response: oneshot::Sender>, }, - DisconnectPeer(PeerId, Cow<'static, str>), + DisconnectPeer(PeerId, ProtocolName), NewBestBlockImported(B::Hash, NumberFor), } @@ -1426,7 +1239,12 @@ enum ServiceToWorkerMsg { /// /// You are encouraged to poll this in a separate background thread or task. #[must_use = "The NetworkWorker must be polled in order for the network to advance"] -pub struct NetworkWorker { +pub struct NetworkWorker +where + B: BlockT + 'static, + H: ExHashT, + Client: HeaderBackend + 'static, +{ /// Updated by the `NetworkWorker` and loaded by the `NetworkService`. external_addresses: Arc>>, /// Updated by the `NetworkWorker` and loaded by the `NetworkService`. @@ -1436,13 +1254,9 @@ pub struct NetworkWorker { /// The network service that can be extracted and shared through the codebase. service: Arc>, /// The *actual* network. - network_service: Swarm, - /// The import queue that was passed at initialization. - import_queue: Box>, + network_service: Swarm>, /// Messages from the [`NetworkService`] that must be processed. - from_service: TracingUnboundedReceiver>, - /// Receiver for queries from the light client that must be processed. - light_client_rqs: Option>>, + from_service: TracingUnboundedReceiver>, /// Senders for events that happen on the network. event_streams: out_events::OutChannels, /// Prometheus network metrics. @@ -1451,38 +1265,23 @@ pub struct NetworkWorker { boot_node_ids: Arc>, /// For each peer and protocol combination, an object that allows sending notifications to /// that peer. Shared with the [`NetworkService`]. - peers_notifications_sinks: Arc), NotificationsSink>>>, - /// Controller for the handler of incoming and outgoing transactions. - tx_handler_controller: transactions::TransactionsHandlerController, + peers_notifications_sinks: Arc>>, + /// Marker to pin the `H` generic. Serves no purpose except to not break backwards + /// compatibility. + _marker: PhantomData, } -impl Future for NetworkWorker { +impl Future for NetworkWorker +where + B: BlockT + 'static, + H: ExHashT, + Client: HeaderBackend + 'static, +{ type Output = (); fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll { let this = &mut *self; - // Poll the import queue for actions to perform. - this.import_queue - .poll_actions(cx, &mut NetworkLink { protocol: &mut this.network_service }); - - // Check for new incoming light client requests. - if let Some(light_client_rqs) = this.light_client_rqs.as_mut() { - while let Poll::Ready(Some(rq)) = light_client_rqs.poll_next_unpin(cx) { - let result = this.network_service.behaviour_mut().light_client_request(rq); - match result { - Ok(()) => {}, - Err(light_client_requests::sender::SendRequestError::TooManyRequests) => { - warn!("Couldn't start light client request: too many pending requests"); - }, - } - - if let Some(metrics) = this.metrics.as_ref() { - metrics.issued_light_requests.inc(); - } - } - } - // At the time of writing of this comment, due to a high volume of messages, the network // worker sometimes takes a long time to process the loop below. When that happens, the // rest of the polling is frozen. In order to avoid negative side-effects caused by this @@ -1505,29 +1304,14 @@ impl Future for NetworkWorker { Poll::Ready(None) => return Poll::Ready(()), Poll::Pending => break, }; - match msg { ServiceToWorkerMsg::AnnounceBlock(hash, data) => this .network_service .behaviour_mut() .user_protocol_mut() .announce_block(hash, data), - ServiceToWorkerMsg::RequestJustification(hash, number) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .request_justification(&hash, number), - ServiceToWorkerMsg::ClearJustificationRequests => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .clear_justification_requests(), - ServiceToWorkerMsg::PropagateTransaction(hash) => - this.tx_handler_controller.propagate_transaction(hash), - ServiceToWorkerMsg::PropagateTransactions => - this.tx_handler_controller.propagate_transactions(), ServiceToWorkerMsg::GetValue(key) => - this.network_service.behaviour_mut().get_value(&key), + this.network_service.behaviour_mut().get_value(key), ServiceToWorkerMsg::PutValue(key, value) => this.network_service.behaviour_mut().put_value(key, value), ServiceToWorkerMsg::SetReservedOnly(reserved_only) => this @@ -1540,6 +1324,11 @@ impl Future for NetworkWorker { .behaviour_mut() .user_protocol_mut() .set_reserved_peers(peers), + ServiceToWorkerMsg::SetPeersetReserved(protocol, peers) => this + .network_service + .behaviour_mut() + .user_protocol_mut() + .set_reserved_peerset_peers(protocol, peers), ServiceToWorkerMsg::AddReserved(peer_id) => this .network_service .behaviour_mut() @@ -1572,11 +1361,6 @@ impl Future for NetworkWorker { .behaviour_mut() .user_protocol_mut() .remove_from_peers_set(protocol, peer_id), - ServiceToWorkerMsg::SyncFork(peer_ids, hash, number) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .set_sync_fork_request(peer_ids, &hash, number), ServiceToWorkerMsg::EventStream(sender) => this.event_streams.push(sender), ServiceToWorkerMsg::Request { target, @@ -1603,7 +1387,7 @@ impl Future for NetworkWorker { .network_service .behaviour_mut() .user_protocol_mut() - .disconnect_peer(&who, &protocol_name), + .disconnect_peer(&who, protocol_name), ServiceToWorkerMsg::NewBestBlockImported(hash, number) => this .network_service .behaviour_mut() @@ -1629,23 +1413,6 @@ impl Future for NetworkWorker { match poll_value { Poll::Pending => break, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::BlockImport(origin, blocks))) => { - if let Some(metrics) = this.metrics.as_ref() { - metrics.import_queue_blocks_submitted.inc(); - } - this.import_queue.import_blocks(origin, blocks); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::JustificationImport( - origin, - hash, - nb, - justifications, - ))) => { - if let Some(metrics) = this.metrics.as_ref() { - metrics.import_queue_justifications_submitted.inc(); - } - this.import_queue.import_justifications(origin, hash, nb, justifications); - }, Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::InboundRequest { protocol, result, @@ -1721,14 +1488,51 @@ impl Future for NetworkWorker { }, } }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::RandomKademliaStarted( - protocol, - ))) => + Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::ReputationChanges { + peer, + changes, + })) => + for change in changes { + this.network_service.behaviour().user_protocol().report_peer(peer, change); + }, + Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::PeerIdentify { + peer_id, + info: + IdentifyInfo { + protocol_version, + agent_version, + mut listen_addrs, + protocols, + .. + }, + })) => { + if listen_addrs.len() > 30 { + debug!( + target: "sub-libp2p", + "Node {:?} has reported more than 30 addresses; it is identified by {:?} and {:?}", + peer_id, protocol_version, agent_version + ); + listen_addrs.truncate(30); + } + for addr in listen_addrs { + this.network_service + .behaviour_mut() + .add_self_reported_address_to_dht(&peer_id, &protocols, addr); + } + this.network_service + .behaviour_mut() + .user_protocol_mut() + .add_default_set_discovered_nodes(iter::once(peer_id)); + }, + Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::Discovered(peer_id))) => { + this.network_service + .behaviour_mut() + .user_protocol_mut() + .add_default_set_discovered_nodes(iter::once(peer_id)); + }, + Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::RandomKademliaStarted)) => if let Some(metrics) = this.metrics.as_ref() { - metrics - .kademlia_random_queries_total - .with_label_values(&[&protocol.as_ref()]) - .inc(); + metrics.kademlia_random_queries_total.inc(); }, Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::NotificationStreamOpened { remote, @@ -1746,7 +1550,7 @@ impl Future for NetworkWorker { { let mut peers_notifications_sinks = this.peers_notifications_sinks.lock(); let _previous_value = peers_notifications_sinks - .insert((remote.clone(), protocol.clone()), notifications_sink); + .insert((remote, protocol.clone()), notifications_sink); debug_assert!(_previous_value.is_none()); } this.event_streams.send(Event::NotificationStreamOpened { @@ -1804,13 +1608,12 @@ impl Future for NetworkWorker { .inc(); } this.event_streams.send(Event::NotificationStreamClosed { - remote: remote.clone(), + remote, protocol: protocol.clone(), }); { let mut peers_notifications_sinks = this.peers_notifications_sinks.lock(); - let _previous_value = - peers_notifications_sinks.remove(&(remote.clone(), protocol)); + let _previous_value = peers_notifications_sinks.remove(&(remote, protocol)); debug_assert!(_previous_value.is_some()); } }, @@ -1850,12 +1653,20 @@ impl Future for NetworkWorker { this.event_streams.send(Event::Dht(event)); }, + Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::None)) => { + // Ignored event from lower layers. + }, Poll::Ready(SwarmEvent::ConnectionEstablished { peer_id, endpoint, num_established, + concurrent_dial_errors, }) => { - debug!(target: "sub-libp2p", "Libp2p => Connected({:?})", peer_id); + if let Some(errors) = concurrent_dial_errors { + debug!(target: "sub-libp2p", "Libp2p => Connected({:?}) with errors: {:?}", peer_id, errors); + } else { + debug!(target: "sub-libp2p", "Libp2p => Connected({:?})", peer_id); + } if let Some(metrics) = this.metrics.as_ref() { let direction = match endpoint { @@ -1883,21 +1694,14 @@ impl Future for NetworkWorker { }; let reason = match cause { Some(ConnectionError::IO(_)) => "transport-error", - Some(ConnectionError::Handler(NodeHandlerWrapperError::Handler( - EitherError::A(EitherError::A(EitherError::A(EitherError::B( - EitherError::A(PingFailure::Timeout), - )))), - ))) => "ping-timeout", - Some(ConnectionError::Handler(NodeHandlerWrapperError::Handler( - EitherError::A(EitherError::A(EitherError::A(EitherError::A( - NotifsHandlerError::SyncNotificationsClogged, - )))), - ))) => "sync-notifications-clogged", - Some(ConnectionError::Handler(NodeHandlerWrapperError::Handler(_))) => - "protocol-error", - Some(ConnectionError::Handler( - NodeHandlerWrapperError::KeepAliveTimeout, - )) => "keep-alive-timeout", + Some(ConnectionError::Handler(EitherError::A(EitherError::A( + EitherError::B(EitherError::A(PingFailure::Timeout)), + )))) => "ping-timeout", + Some(ConnectionError::Handler(EitherError::A(EitherError::A( + EitherError::A(NotifsHandlerError::SyncNotificationsClogged), + )))) => "sync-notifications-clogged", + Some(ConnectionError::Handler(_)) => "protocol-error", + Some(ConnectionError::KeepAliveTimeout) => "keep-alive-timeout", None => "actively-closed", }; metrics @@ -1923,37 +1727,48 @@ impl Future for NetworkWorker { metrics.listeners_local_addresses.dec(); } }, - Poll::Ready(SwarmEvent::UnreachableAddr { peer_id, address, error, .. }) => { - trace!( - target: "sub-libp2p", - "Libp2p => Failed to reach {:?} through {:?}: {}", - peer_id, address, error, - ); + Poll::Ready(SwarmEvent::OutgoingConnectionError { peer_id, error }) => { + if let Some(peer_id) = peer_id { + trace!( + target: "sub-libp2p", + "Libp2p => Failed to reach {:?}: {}", + peer_id, error, + ); - if this.boot_node_ids.contains(&peer_id) { - if let PendingConnectionError::InvalidPeerId = error { - error!( - "💔 The bootnode you want to connect to at `{}` provided a different peer ID than the one you expect: `{}`.", - address, peer_id, - ); + if this.boot_node_ids.contains(&peer_id) { + if let DialError::WrongPeerId { obtained, endpoint } = &error { + if let ConnectedPoint::Dialer { address, role_override: _ } = + endpoint + { + warn!( + "💔 The bootnode you want to connect to at `{}` provided a different peer ID `{}` than the one you expect `{}`.", + address, + obtained, + peer_id, + ); + } + } } } if let Some(metrics) = this.metrics.as_ref() { - match error { - PendingConnectionError::ConnectionLimit(_) => metrics - .pending_connections_errors_total - .with_label_values(&["limit-reached"]) - .inc(), - PendingConnectionError::InvalidPeerId => metrics - .pending_connections_errors_total - .with_label_values(&["invalid-peer-id"]) - .inc(), - PendingConnectionError::Transport(_) | - PendingConnectionError::IO(_) => metrics + let reason = match error { + DialError::ConnectionLimit(_) => Some("limit-reached"), + DialError::InvalidPeerId(_) => Some("invalid-peer-id"), + DialError::Transport(_) | DialError::ConnectionIo(_) => + Some("transport-error"), + DialError::Banned | + DialError::LocalPeerId | + DialError::NoAddresses | + DialError::DialPeerConditionFalse(_) | + DialError::WrongPeerId { .. } | + DialError::Aborted => None, // ignore them + }; + if let Some(reason) = reason { + metrics .pending_connections_errors_total - .with_label_values(&["transport-error"]) - .inc(), + .with_label_values(&[reason]) + .inc(); } } }, @@ -1979,16 +1794,19 @@ impl Future for NetworkWorker { ); if let Some(metrics) = this.metrics.as_ref() { let reason = match error { - PendingConnectionError::ConnectionLimit(_) => "limit-reached", - PendingConnectionError::InvalidPeerId => "invalid-peer-id", + PendingConnectionError::ConnectionLimit(_) => Some("limit-reached"), + PendingConnectionError::WrongPeerId { .. } => Some("invalid-peer-id"), PendingConnectionError::Transport(_) | - PendingConnectionError::IO(_) => "transport-error", + PendingConnectionError::IO(_) => Some("transport-error"), + PendingConnectionError::Aborted => None, // ignore it }; - metrics - .incoming_connections_errors_total - .with_label_values(&[reason]) - .inc(); + if let Some(reason) = reason { + metrics + .incoming_connections_errors_total + .with_label_values(&[reason]) + .inc(); + } } }, Poll::Ready(SwarmEvent::BannedPeer { peer_id, endpoint }) => { @@ -2004,8 +1822,6 @@ impl Future for NetworkWorker { .inc(); } }, - Poll::Ready(SwarmEvent::UnknownPeerUnreachableAddr { address, error }) => - trace!(target: "sub-libp2p", "Libp2p => UnknownPeerUnreachableAddr({}): {}", address, error), Poll::Ready(SwarmEvent::ListenerClosed { reason, addresses, .. }) => { if let Some(metrics) = this.metrics.as_ref() { metrics.listeners_local_addresses.sub(addresses.len() as u64); @@ -2040,58 +1856,45 @@ impl Future for NetworkWorker { // Update the variables shared with the `NetworkService`. this.num_connected.store(num_connected_peers, Ordering::Relaxed); { - let external_addresses = Swarm::::external_addresses(&this.network_service) - .map(|r| &r.addr) - .cloned() - .collect(); + let external_addresses = + Swarm::>::external_addresses(&this.network_service) + .map(|r| &r.addr) + .cloned() + .collect(); *this.external_addresses.lock() = external_addresses; } - let is_major_syncing = - match this.network_service.behaviour_mut().user_protocol_mut().sync_state().state { - SyncState::Idle => false, - SyncState::Downloading => true, - }; - - this.tx_handler_controller.set_gossip_enabled(!is_major_syncing); + let is_major_syncing = this + .network_service + .behaviour_mut() + .user_protocol_mut() + .sync_state() + .state + .is_major_syncing(); this.is_major_syncing.store(is_major_syncing, Ordering::Relaxed); if let Some(metrics) = this.metrics.as_ref() { - for (proto, buckets) in this.network_service.behaviour_mut().num_entries_per_kbucket() { + if let Some(buckets) = this.network_service.behaviour_mut().num_entries_per_kbucket() { for (lower_ilog2_bucket_bound, num_entries) in buckets { metrics .kbuckets_num_nodes - .with_label_values(&[ - &proto.as_ref(), - &lower_ilog2_bucket_bound.to_string(), - ]) + .with_label_values(&[&lower_ilog2_bucket_bound.to_string()]) .set(num_entries as u64); } } - for (proto, num_entries) in this.network_service.behaviour_mut().num_kademlia_records() - { - metrics - .kademlia_records_count - .with_label_values(&[&proto.as_ref()]) - .set(num_entries as u64); + if let Some(num_entries) = this.network_service.behaviour_mut().num_kademlia_records() { + metrics.kademlia_records_count.set(num_entries as u64); } - for (proto, num_entries) in + if let Some(num_entries) = this.network_service.behaviour_mut().kademlia_records_total_size() { - metrics - .kademlia_records_sizes_total - .with_label_values(&[&proto.as_ref()]) - .set(num_entries as u64); + metrics.kademlia_records_sizes_total.set(num_entries as u64); } metrics .peerset_num_discovered .set(this.network_service.behaviour_mut().user_protocol().num_discovered_peers() as u64); - metrics.peerset_num_requested.set( - this.network_service.behaviour_mut().user_protocol().requested_peers().count() - as u64, - ); metrics.pending_connections.set( Swarm::network_info(&this.network_service).connection_counters().num_pending() as u64, @@ -2102,48 +1905,12 @@ impl Future for NetworkWorker { } } -impl Unpin for NetworkWorker {} - -/// The libp2p swarm, customized for our needs. -type Swarm = libp2p::swarm::Swarm>; - -// Implementation of `import_queue::Link` trait using the available local variables. -struct NetworkLink<'a, B: BlockT> { - protocol: &'a mut Swarm, -} - -impl<'a, B: BlockT> Link for NetworkLink<'a, B> { - fn blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) { - self.protocol - .behaviour_mut() - .user_protocol_mut() - .on_blocks_processed(imported, count, results) - } - fn justification_imported( - &mut self, - who: PeerId, - hash: &B::Hash, - number: NumberFor, - success: bool, - ) { - self.protocol.behaviour_mut().user_protocol_mut().justification_import_result( - who, - hash.clone(), - number, - success, - ); - } - fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { - self.protocol - .behaviour_mut() - .user_protocol_mut() - .request_justification(hash, number) - } +impl Unpin for NetworkWorker +where + B: BlockT + 'static, + H: ExHashT, + Client: HeaderBackend + 'static, +{ } fn ensure_addresses_consistent_with_transport<'a>( diff --git a/client/network/src/service/metrics.rs b/client/network/src/service/metrics.rs index e33cd4b194d69..a099bba716eb9 100644 --- a/client/network/src/service/metrics.rs +++ b/client/network/src/service/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -53,15 +53,13 @@ pub struct Metrics { pub connections_opened_total: CounterVec, pub distinct_peers_connections_closed_total: Counter, pub distinct_peers_connections_opened_total: Counter, - pub import_queue_blocks_submitted: Counter, - pub import_queue_justifications_submitted: Counter, pub incoming_connections_errors_total: CounterVec, pub incoming_connections_total: Counter, pub issued_light_requests: Counter, pub kademlia_query_duration: HistogramVec, - pub kademlia_random_queries_total: CounterVec, - pub kademlia_records_count: GaugeVec, - pub kademlia_records_sizes_total: GaugeVec, + pub kademlia_random_queries_total: Counter, + pub kademlia_records_count: Gauge, + pub kademlia_records_sizes_total: Gauge, pub kbuckets_num_nodes: GaugeVec, pub listeners_local_addresses: Gauge, pub listeners_errors_total: Counter, @@ -69,7 +67,6 @@ pub struct Metrics { pub notifications_streams_closed_total: CounterVec, pub notifications_streams_opened_total: CounterVec, pub peerset_num_discovered: Gauge, - pub peerset_num_requested: Gauge, pub pending_connections: Gauge, pub pending_connections_errors_total: CounterVec, pub requests_in_failure_total: CounterVec, @@ -84,54 +81,46 @@ impl Metrics { // This list is ordered alphabetically connections_closed_total: prometheus::register(CounterVec::new( Opts::new( - "sub_libp2p_connections_closed_total", + "substrate_sub_libp2p_connections_closed_total", "Total number of connections closed, by direction and reason" ), &["direction", "reason"] )?, registry)?, connections_opened_total: prometheus::register(CounterVec::new( Opts::new( - "sub_libp2p_connections_opened_total", + "substrate_sub_libp2p_connections_opened_total", "Total number of connections opened by direction" ), &["direction"] )?, registry)?, distinct_peers_connections_closed_total: prometheus::register(Counter::new( - "sub_libp2p_distinct_peers_connections_closed_total", + "substrate_sub_libp2p_distinct_peers_connections_closed_total", "Total number of connections closed with distinct peers" )?, registry)?, distinct_peers_connections_opened_total: prometheus::register(Counter::new( - "sub_libp2p_distinct_peers_connections_opened_total", + "substrate_sub_libp2p_distinct_peers_connections_opened_total", "Total number of connections opened with distinct peers" )?, registry)?, - import_queue_blocks_submitted: prometheus::register(Counter::new( - "import_queue_blocks_submitted", - "Number of blocks submitted to the import queue.", - )?, registry)?, - import_queue_justifications_submitted: prometheus::register(Counter::new( - "import_queue_justifications_submitted", - "Number of justifications submitted to the import queue.", - )?, registry)?, incoming_connections_errors_total: prometheus::register(CounterVec::new( Opts::new( - "sub_libp2p_incoming_connections_handshake_errors_total", + "substrate_sub_libp2p_incoming_connections_handshake_errors_total", "Total number of incoming connections that have failed during the \ initial handshake" ), &["reason"] )?, registry)?, incoming_connections_total: prometheus::register(Counter::new( - "sub_libp2p_incoming_connections_total", + "substrate_sub_libp2p_incoming_connections_total", "Total number of incoming connections on the listening sockets" )?, registry)?, issued_light_requests: prometheus::register(Counter::new( - "issued_light_requests", + "substrate_issued_light_requests", "Number of light client requests that our node has issued.", )?, registry)?, kademlia_query_duration: prometheus::register(HistogramVec::new( HistogramOpts { common_opts: Opts::new( - "sub_libp2p_kademlia_query_duration", + "substrate_sub_libp2p_kademlia_query_duration", "Duration of Kademlia queries per query type" ), buckets: prometheus::exponential_buckets(0.5, 2.0, 10) @@ -139,45 +128,37 @@ impl Metrics { }, &["type"] )?, registry)?, - kademlia_random_queries_total: prometheus::register(CounterVec::new( - Opts::new( - "sub_libp2p_kademlia_random_queries_total", - "Number of random Kademlia queries started" - ), - &["protocol"] + kademlia_random_queries_total: prometheus::register(Counter::new( + "substrate_sub_libp2p_kademlia_random_queries_total", + "Number of random Kademlia queries started", )?, registry)?, - kademlia_records_count: prometheus::register(GaugeVec::new( - Opts::new( - "sub_libp2p_kademlia_records_count", - "Number of records in the Kademlia records store" - ), - &["protocol"] + kademlia_records_count: prometheus::register(Gauge::new( + "substrate_sub_libp2p_kademlia_records_count", + "Number of records in the Kademlia records store", )?, registry)?, - kademlia_records_sizes_total: prometheus::register(GaugeVec::new( - Opts::new( - "sub_libp2p_kademlia_records_sizes_total", - "Total size of all the records in the Kademlia records store" - ), - &["protocol"] + kademlia_records_sizes_total: prometheus::register(Gauge::new( + "substrate_sub_libp2p_kademlia_records_sizes_total", + "Total size of all the records in the Kademlia records store", )?, registry)?, kbuckets_num_nodes: prometheus::register(GaugeVec::new( Opts::new( - "sub_libp2p_kbuckets_num_nodes", + "substrate_sub_libp2p_kbuckets_num_nodes", "Number of nodes per kbucket per Kademlia instance" ), - &["protocol", "lower_ilog2_bucket_bound"] + &["lower_ilog2_bucket_bound"] )?, registry)?, listeners_local_addresses: prometheus::register(Gauge::new( - "sub_libp2p_listeners_local_addresses", "Number of local addresses we're listening on" + "substrate_sub_libp2p_listeners_local_addresses", + "Number of local addresses we're listening on" )?, registry)?, listeners_errors_total: prometheus::register(Counter::new( - "sub_libp2p_listeners_errors_total", + "substrate_sub_libp2p_listeners_errors_total", "Total number of non-fatal errors reported by a listener" )?, registry)?, notifications_sizes: prometheus::register(HistogramVec::new( HistogramOpts { common_opts: Opts::new( - "sub_libp2p_notifications_sizes", + "substrate_sub_libp2p_notifications_sizes", "Sizes of the notifications send to and received from all nodes" ), buckets: prometheus::exponential_buckets(64.0, 4.0, 8) @@ -187,38 +168,36 @@ impl Metrics { )?, registry)?, notifications_streams_closed_total: prometheus::register(CounterVec::new( Opts::new( - "sub_libp2p_notifications_streams_closed_total", + "substrate_sub_libp2p_notifications_streams_closed_total", "Total number of notification substreams that have been closed" ), &["protocol"] )?, registry)?, notifications_streams_opened_total: prometheus::register(CounterVec::new( Opts::new( - "sub_libp2p_notifications_streams_opened_total", + "substrate_sub_libp2p_notifications_streams_opened_total", "Total number of notification substreams that have been opened" ), &["protocol"] )?, registry)?, peerset_num_discovered: prometheus::register(Gauge::new( - "sub_libp2p_peerset_num_discovered", "Number of nodes stored in the peerset manager", - )?, registry)?, - peerset_num_requested: prometheus::register(Gauge::new( - "sub_libp2p_peerset_num_requested", "Number of nodes that the peerset manager wants us to be connected to", + "substrate_sub_libp2p_peerset_num_discovered", + "Number of nodes stored in the peerset manager", )?, registry)?, pending_connections: prometheus::register(Gauge::new( - "sub_libp2p_pending_connections", + "substrate_sub_libp2p_pending_connections", "Number of connections in the process of being established", )?, registry)?, pending_connections_errors_total: prometheus::register(CounterVec::new( Opts::new( - "sub_libp2p_pending_connections_errors_total", + "substrate_sub_libp2p_pending_connections_errors_total", "Total number of pending connection errors" ), &["reason"] )?, registry)?, requests_in_failure_total: prometheus::register(CounterVec::new( Opts::new( - "sub_libp2p_requests_in_failure_total", + "substrate_sub_libp2p_requests_in_failure_total", "Total number of incoming requests that the node has failed to answer" ), &["protocol", "reason"] @@ -226,7 +205,7 @@ impl Metrics { requests_in_success_total: prometheus::register(HistogramVec::new( HistogramOpts { common_opts: Opts::new( - "sub_libp2p_requests_in_success_total", + "substrate_sub_libp2p_requests_in_success_total", "For successful incoming requests, time between receiving the request and \ starting to send the response" ), @@ -237,7 +216,7 @@ impl Metrics { )?, registry)?, requests_out_failure_total: prometheus::register(CounterVec::new( Opts::new( - "sub_libp2p_requests_out_failure_total", + "substrate_sub_libp2p_requests_out_failure_total", "Total number of requests that have failed" ), &["protocol", "reason"] @@ -245,7 +224,7 @@ impl Metrics { requests_out_success_total: prometheus::register(HistogramVec::new( HistogramOpts { common_opts: Opts::new( - "sub_libp2p_requests_out_success_total", + "substrate_sub_libp2p_requests_out_success_total", "For successful outgoing requests, time between a request's start and finish" ), buckets: prometheus::exponential_buckets(0.001, 2.0, 16) @@ -267,7 +246,7 @@ impl BandwidthCounters { fn register(registry: &Registry, sinks: Arc) -> Result<(), PrometheusError> { prometheus::register( SourcedCounter::new( - &Opts::new("sub_libp2p_network_bytes_total", "Total bandwidth usage") + &Opts::new("substrate_sub_libp2p_network_bytes_total", "Total bandwidth usage") .variable_label("direction"), BandwidthCounters(sinks), )?, @@ -282,8 +261,8 @@ impl MetricSource for BandwidthCounters { type N = u64; fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) { - set(&[&"in"], self.0.total_inbound()); - set(&[&"out"], self.0.total_outbound()); + set(&["in"], self.0.total_inbound()); + set(&["out"], self.0.total_outbound()); } } @@ -298,7 +277,7 @@ impl MajorSyncingGauge { prometheus::register( SourcedGauge::new( &Opts::new( - "sub_libp2p_is_major_syncing", + "substrate_sub_libp2p_is_major_syncing", "Whether the node is performing a major sync or not.", ), MajorSyncingGauge(value), @@ -328,7 +307,7 @@ impl NumConnectedGauge { fn register(registry: &Registry, value: Arc) -> Result<(), PrometheusError> { prometheus::register( SourcedGauge::new( - &Opts::new("sub_libp2p_peers_count", "Number of connected peers"), + &Opts::new("substrate_sub_libp2p_peers_count", "Number of connected peers"), NumConnectedGauge(value), )?, registry, diff --git a/client/network/src/service/out_events.rs b/client/network/src/service/out_events.rs index 2d6241278005b..4144d7f19551e 100644 --- a/client/network/src/service/out_events.rs +++ b/client/network/src/service/out_events.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -31,13 +31,12 @@ //! - Send events by calling [`OutChannels::send`]. Events are cloned for each sender in the //! collection. -use crate::Event; - use futures::{channel::mpsc, prelude::*, ready, stream::FusedStream}; use parking_lot::Mutex; use prometheus_endpoint::{register, CounterVec, GaugeVec, Opts, PrometheusError, Registry, U64}; +use sc_network_common::protocol::event::Event; use std::{ - convert::TryFrom as _, + cell::RefCell, fmt, pin::Pin, sync::Arc, @@ -187,12 +186,29 @@ struct Metrics { num_channels: GaugeVec, } +thread_local! { + static LABEL_BUFFER: RefCell = RefCell::new(String::new()); +} + +fn format_label(prefix: &str, protocol: &str, callback: impl FnOnce(&str)) { + LABEL_BUFFER.with(|label_buffer| { + let mut label_buffer = label_buffer.borrow_mut(); + label_buffer.clear(); + label_buffer.reserve(prefix.len() + protocol.len() + 2); + label_buffer.push_str(prefix); + label_buffer.push('"'); + label_buffer.push_str(protocol); + label_buffer.push('"'); + callback(&label_buffer); + }); +} + impl Metrics { fn register(registry: &Registry) -> Result { Ok(Self { events_total: register(CounterVec::new( Opts::new( - "sub_libp2p_out_events_events_total", + "substrate_sub_libp2p_out_events_events_total", "Number of broadcast network events that have been sent or received across all \ channels" ), @@ -200,7 +216,7 @@ impl Metrics { )?, registry)?, notifications_sizes: register(CounterVec::new( Opts::new( - "sub_libp2p_out_events_notifications_sizes", + "substrate_sub_libp2p_out_events_notifications_sizes", "Size of notification events that have been sent or received across all \ channels" ), @@ -208,7 +224,7 @@ impl Metrics { )?, registry)?, num_channels: register(GaugeVec::new( Opts::new( - "sub_libp2p_out_events_num_channels", + "substrate_sub_libp2p_out_events_num_channels", "Number of internal active channels that broadcast network events", ), &["name"] @@ -232,20 +248,26 @@ impl Metrics { .inc_by(num); }, Event::NotificationStreamOpened { protocol, .. } => { - self.events_total - .with_label_values(&[&format!("notif-open-{:?}", protocol), "sent", name]) - .inc_by(num); + format_label("notif-open-", protocol, |protocol_label| { + self.events_total + .with_label_values(&[protocol_label, "sent", name]) + .inc_by(num); + }); }, Event::NotificationStreamClosed { protocol, .. } => { - self.events_total - .with_label_values(&[&format!("notif-closed-{:?}", protocol), "sent", name]) - .inc_by(num); + format_label("notif-closed-", protocol, |protocol_label| { + self.events_total + .with_label_values(&[protocol_label, "sent", name]) + .inc_by(num); + }); }, Event::NotificationsReceived { messages, .. } => for (protocol, message) in messages { - self.events_total - .with_label_values(&[&format!("notif-{:?}", protocol), "sent", name]) - .inc_by(num); + format_label("notif-", protocol, |protocol_label| { + self.events_total + .with_label_values(&[protocol_label, "sent", name]) + .inc_by(num); + }); self.notifications_sizes.with_label_values(&[protocol, "sent", name]).inc_by( num.saturating_mul(u64::try_from(message.len()).unwrap_or(u64::MAX)), ); @@ -267,22 +289,24 @@ impl Metrics { .inc(); }, Event::NotificationStreamOpened { protocol, .. } => { - self.events_total - .with_label_values(&[&format!("notif-open-{:?}", protocol), "received", name]) - .inc(); + format_label("notif-open-", protocol, |protocol_label| { + self.events_total.with_label_values(&[protocol_label, "received", name]).inc(); + }); }, Event::NotificationStreamClosed { protocol, .. } => { - self.events_total - .with_label_values(&[&format!("notif-closed-{:?}", protocol), "received", name]) - .inc(); + format_label("notif-closed-", protocol, |protocol_label| { + self.events_total.with_label_values(&[protocol_label, "received", name]).inc(); + }); }, Event::NotificationsReceived { messages, .. } => for (protocol, message) in messages { - self.events_total - .with_label_values(&[&format!("notif-{:?}", protocol), "received", name]) - .inc(); + format_label("notif-", protocol, |protocol_label| { + self.events_total + .with_label_values(&[protocol_label, "received", name]) + .inc(); + }); self.notifications_sizes - .with_label_values(&[&protocol, "received", name]) + .with_label_values(&[protocol, "received", name]) .inc_by(u64::try_from(message.len()).unwrap_or(u64::MAX)); }, } diff --git a/client/network/src/service/tests.rs b/client/network/src/service/tests.rs deleted file mode 100644 index 69b172d07edfe..0000000000000 --- a/client/network/src/service/tests.rs +++ /dev/null @@ -1,662 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use crate::{ - block_request_handler::BlockRequestHandler, config, - light_client_requests::handler::LightClientRequestHandler, - state_request_handler::StateRequestHandler, Event, NetworkService, NetworkWorker, -}; - -use futures::prelude::*; -use libp2p::PeerId; -use sp_runtime::traits::{Block as BlockT, Header as _}; -use std::{borrow::Cow, sync::Arc, time::Duration}; -use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; - -type TestNetworkService = NetworkService< - substrate_test_runtime_client::runtime::Block, - substrate_test_runtime_client::runtime::Hash, ->; - -/// Builds a full node to be used for testing. Returns the node service and its associated events -/// stream. -/// -/// > **Note**: We return the events stream in order to not possibly lose events between the -/// > construction of the service and the moment the events stream is grabbed. -fn build_test_full_node( - config: config::NetworkConfiguration, -) -> (Arc, impl Stream) { - let client = Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0); - - #[derive(Clone)] - struct PassThroughVerifier(bool); - - #[async_trait::async_trait] - impl sc_consensus::Verifier for PassThroughVerifier { - async fn verify( - &mut self, - mut block: sc_consensus::BlockImportParams, - ) -> Result< - ( - sc_consensus::BlockImportParams, - Option)>>, - ), - String, - > { - let maybe_keys = block - .header - .digest() - .log(|l| { - l.try_as_raw(sp_runtime::generic::OpaqueDigestItemId::Consensus(b"aura")) - .or_else(|| { - l.try_as_raw(sp_runtime::generic::OpaqueDigestItemId::Consensus( - b"babe", - )) - }) - }) - .map(|blob| { - vec![(sp_blockchain::well_known_cache_keys::AUTHORITIES, blob.to_vec())] - }); - - block.finalized = self.0; - block.fork_choice = Some(sc_consensus::ForkChoiceStrategy::LongestChain); - Ok((block, maybe_keys)) - } - } - - let import_queue = Box::new(sc_consensus::BasicQueue::new( - PassThroughVerifier(false), - Box::new(client.clone()), - None, - &sp_core::testing::TaskExecutor::new(), - None, - )); - - let protocol_id = config::ProtocolId::from("/test-protocol-name"); - - let block_request_protocol_config = { - let (handler, protocol_config) = BlockRequestHandler::new(&protocol_id, client.clone(), 50); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - let state_request_protocol_config = { - let (handler, protocol_config) = StateRequestHandler::new(&protocol_id, client.clone(), 50); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - let light_client_request_protocol_config = { - let (handler, protocol_config) = - LightClientRequestHandler::new(&protocol_id, client.clone()); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - let worker = NetworkWorker::new(config::Params { - role: config::Role::Full, - executor: None, - transactions_handler_executor: Box::new(|task| { - async_std::task::spawn(task); - }), - network_config: config, - chain: client.clone(), - on_demand: None, - transaction_pool: Arc::new(crate::config::EmptyTransactionPool), - protocol_id, - import_queue, - block_announce_validator: Box::new( - sp_consensus::block_validation::DefaultBlockAnnounceValidator, - ), - metrics_registry: None, - block_request_protocol_config, - state_request_protocol_config, - light_client_request_protocol_config, - warp_sync: None, - }) - .unwrap(); - - let service = worker.service().clone(); - let event_stream = service.event_stream("test"); - - async_std::task::spawn(async move { - futures::pin_mut!(worker); - let _ = worker.await; - }); - - (service, event_stream) -} - -const PROTOCOL_NAME: Cow<'static, str> = Cow::Borrowed("/foo"); - -/// Builds two nodes and their associated events stream. -/// The nodes are connected together and have the `PROTOCOL_NAME` protocol registered. -fn build_nodes_one_proto() -> ( - Arc, - impl Stream, - Arc, - impl Stream, -) { - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - - let (node1, events_stream1) = build_test_full_node(config::NetworkConfiguration { - extra_sets: vec![config::NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME, - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - set_config: Default::default(), - }], - listen_addresses: vec![listen_addr.clone()], - transport: config::TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); - - let (node2, events_stream2) = build_test_full_node(config::NetworkConfiguration { - extra_sets: vec![config::NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME, - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - set_config: config::SetConfig { - reserved_nodes: vec![config::MultiaddrWithPeerId { - multiaddr: listen_addr, - peer_id: node1.local_peer_id().clone(), - }], - ..Default::default() - }, - }], - listen_addresses: vec![], - transport: config::TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); - - (node1, events_stream1, node2, events_stream2) -} - -#[ignore] -#[test] -fn notifications_state_consistent() { - // Runs two nodes and ensures that events are propagated out of the API in a consistent - // correct order, which means no notification received on a closed substream. - - let (node1, mut events_stream1, node2, mut events_stream2) = build_nodes_one_proto(); - - // Write some initial notifications that shouldn't get through. - for _ in 0..(rand::random::() % 5) { - node1.write_notification( - node2.local_peer_id().clone(), - PROTOCOL_NAME, - b"hello world".to_vec(), - ); - } - for _ in 0..(rand::random::() % 5) { - node2.write_notification( - node1.local_peer_id().clone(), - PROTOCOL_NAME, - b"hello world".to_vec(), - ); - } - - async_std::task::block_on(async move { - // True if we have an active substream from node1 to node2. - let mut node1_to_node2_open = false; - // True if we have an active substream from node2 to node1. - let mut node2_to_node1_open = false; - // We stop the test after a certain number of iterations. - let mut iterations = 0; - // Safe guard because we don't want the test to pass if no substream has been open. - let mut something_happened = false; - - loop { - iterations += 1; - if iterations >= 1_000 { - assert!(something_happened); - break - } - - // Start by sending a notification from node1 to node2 and vice-versa. Part of the - // test consists in ensuring that notifications get ignored if the stream isn't open. - if rand::random::() % 5 >= 3 { - node1.write_notification( - node2.local_peer_id().clone(), - PROTOCOL_NAME, - b"hello world".to_vec(), - ); - } - if rand::random::() % 5 >= 3 { - node2.write_notification( - node1.local_peer_id().clone(), - PROTOCOL_NAME, - b"hello world".to_vec(), - ); - } - - // Also randomly disconnect the two nodes from time to time. - if rand::random::() % 20 == 0 { - node1.disconnect_peer(node2.local_peer_id().clone(), PROTOCOL_NAME); - } - if rand::random::() % 20 == 0 { - node2.disconnect_peer(node1.local_peer_id().clone(), PROTOCOL_NAME); - } - - // Grab next event from either `events_stream1` or `events_stream2`. - let next_event = { - let next1 = events_stream1.next(); - let next2 = events_stream2.next(); - // We also await on a small timer, otherwise it is possible for the test to wait - // forever while nothing at all happens on the network. - let continue_test = futures_timer::Delay::new(Duration::from_millis(20)); - match future::select(future::select(next1, next2), continue_test).await { - future::Either::Left((future::Either::Left((Some(ev), _)), _)) => - future::Either::Left(ev), - future::Either::Left((future::Either::Right((Some(ev), _)), _)) => - future::Either::Right(ev), - future::Either::Right(_) => continue, - _ => break, - } - }; - - match next_event { - future::Either::Left(Event::NotificationStreamOpened { - remote, protocol, .. - }) => { - something_happened = true; - assert!(!node1_to_node2_open); - node1_to_node2_open = true; - assert_eq!(remote, *node2.local_peer_id()); - assert_eq!(protocol, PROTOCOL_NAME); - }, - future::Either::Right(Event::NotificationStreamOpened { - remote, protocol, .. - }) => { - something_happened = true; - assert!(!node2_to_node1_open); - node2_to_node1_open = true; - assert_eq!(remote, *node1.local_peer_id()); - assert_eq!(protocol, PROTOCOL_NAME); - }, - future::Either::Left(Event::NotificationStreamClosed { - remote, protocol, .. - }) => { - assert!(node1_to_node2_open); - node1_to_node2_open = false; - assert_eq!(remote, *node2.local_peer_id()); - assert_eq!(protocol, PROTOCOL_NAME); - }, - future::Either::Right(Event::NotificationStreamClosed { - remote, protocol, .. - }) => { - assert!(node2_to_node1_open); - node2_to_node1_open = false; - assert_eq!(remote, *node1.local_peer_id()); - assert_eq!(protocol, PROTOCOL_NAME); - }, - future::Either::Left(Event::NotificationsReceived { remote, .. }) => { - assert!(node1_to_node2_open); - assert_eq!(remote, *node2.local_peer_id()); - if rand::random::() % 5 >= 4 { - node1.write_notification( - node2.local_peer_id().clone(), - PROTOCOL_NAME, - b"hello world".to_vec(), - ); - } - }, - future::Either::Right(Event::NotificationsReceived { remote, .. }) => { - assert!(node2_to_node1_open); - assert_eq!(remote, *node1.local_peer_id()); - if rand::random::() % 5 >= 4 { - node2.write_notification( - node1.local_peer_id().clone(), - PROTOCOL_NAME, - b"hello world".to_vec(), - ); - } - }, - - // Add new events here. - future::Either::Left(Event::SyncConnected { .. }) => {}, - future::Either::Right(Event::SyncConnected { .. }) => {}, - future::Either::Left(Event::SyncDisconnected { .. }) => {}, - future::Either::Right(Event::SyncDisconnected { .. }) => {}, - future::Either::Left(Event::Dht(_)) => {}, - future::Either::Right(Event::Dht(_)) => {}, - }; - } - }); -} - -#[test] -fn lots_of_incoming_peers_works() { - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - - let (main_node, _) = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - extra_sets: vec![config::NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME, - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - set_config: config::SetConfig { in_peers: u32::MAX, ..Default::default() }, - }], - transport: config::TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); - - let main_node_peer_id = *main_node.local_peer_id(); - - // We spawn background tasks and push them in this `Vec`. They will all be waited upon before - // this test ends. - let mut background_tasks_to_wait = Vec::new(); - - for _ in 0..32 { - let (_dialing_node, event_stream) = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![], - extra_sets: vec![config::NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME, - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - set_config: config::SetConfig { - reserved_nodes: vec![config::MultiaddrWithPeerId { - multiaddr: listen_addr.clone(), - peer_id: main_node_peer_id, - }], - ..Default::default() - }, - }], - transport: config::TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); - - background_tasks_to_wait.push(async_std::task::spawn(async move { - // Create a dummy timer that will "never" fire, and that will be overwritten when we - // actually need the timer. Using an Option would be technically cleaner, but it would - // make the code below way more complicated. - let mut timer = futures_timer::Delay::new(Duration::from_secs(3600 * 24 * 7)).fuse(); - - let mut event_stream = event_stream.fuse(); - loop { - futures::select! { - _ = timer => { - // Test succeeds when timer fires. - return; - } - ev = event_stream.next() => { - match ev.unwrap() { - Event::NotificationStreamOpened { remote, .. } => { - assert_eq!(remote, main_node_peer_id); - // Test succeeds after 5 seconds. This timer is here in order to - // detect a potential problem after opening. - timer = futures_timer::Delay::new(Duration::from_secs(5)).fuse(); - } - Event::NotificationStreamClosed { .. } => { - // Test failed. - panic!(); - } - _ => {} - } - } - } - } - })); - } - - futures::executor::block_on(async move { future::join_all(background_tasks_to_wait).await }); -} - -#[test] -fn notifications_back_pressure() { - // Node 1 floods node 2 with notifications. Random sleeps are done on node 2 to simulate the - // node being busy. We make sure that all notifications are received. - - const TOTAL_NOTIFS: usize = 10_000; - - let (node1, mut events_stream1, node2, mut events_stream2) = build_nodes_one_proto(); - let node2_id = node2.local_peer_id(); - - let receiver = async_std::task::spawn(async move { - let mut received_notifications = 0; - - while received_notifications < TOTAL_NOTIFS { - match events_stream2.next().await.unwrap() { - Event::NotificationStreamClosed { .. } => panic!(), - Event::NotificationsReceived { messages, .. } => - for message in messages { - assert_eq!(message.0, PROTOCOL_NAME); - assert_eq!(message.1, format!("hello #{}", received_notifications)); - received_notifications += 1; - }, - _ => {}, - }; - - if rand::random::() < 2 { - async_std::task::sleep(Duration::from_millis(rand::random::() % 750)).await; - } - } - }); - - async_std::task::block_on(async move { - // Wait for the `NotificationStreamOpened`. - loop { - match events_stream1.next().await.unwrap() { - Event::NotificationStreamOpened { .. } => break, - _ => {}, - }; - } - - // Sending! - for num in 0..TOTAL_NOTIFS { - let notif = node1.notification_sender(node2_id.clone(), PROTOCOL_NAME).unwrap(); - notif.ready().await.unwrap().send(format!("hello #{}", num)).unwrap(); - } - - receiver.await; - }); -} - -#[test] -fn fallback_name_working() { - // Node 1 supports the protocols "new" and "old". Node 2 only supports "old". Checks whether - // they can connect. - - const NEW_PROTOCOL_NAME: Cow<'static, str> = - Cow::Borrowed("/new-shiny-protocol-that-isnt-PROTOCOL_NAME"); - - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - - let (node1, mut events_stream1) = build_test_full_node(config::NetworkConfiguration { - extra_sets: vec![config::NonDefaultSetConfig { - notifications_protocol: NEW_PROTOCOL_NAME.clone(), - fallback_names: vec![PROTOCOL_NAME], - max_notification_size: 1024 * 1024, - set_config: Default::default(), - }], - listen_addresses: vec![listen_addr.clone()], - transport: config::TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); - - let (_, mut events_stream2) = build_test_full_node(config::NetworkConfiguration { - extra_sets: vec![config::NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME, - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - set_config: config::SetConfig { - reserved_nodes: vec![config::MultiaddrWithPeerId { - multiaddr: listen_addr, - peer_id: node1.local_peer_id().clone(), - }], - ..Default::default() - }, - }], - listen_addresses: vec![], - transport: config::TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); - - let receiver = async_std::task::spawn(async move { - // Wait for the `NotificationStreamOpened`. - loop { - match events_stream2.next().await.unwrap() { - Event::NotificationStreamOpened { protocol, negotiated_fallback, .. } => { - assert_eq!(protocol, PROTOCOL_NAME); - assert_eq!(negotiated_fallback, None); - break - }, - _ => {}, - }; - } - }); - - async_std::task::block_on(async move { - // Wait for the `NotificationStreamOpened`. - loop { - match events_stream1.next().await.unwrap() { - Event::NotificationStreamOpened { protocol, negotiated_fallback, .. } - if protocol == NEW_PROTOCOL_NAME => - { - assert_eq!(negotiated_fallback, Some(PROTOCOL_NAME)); - break - } - _ => {}, - }; - } - - receiver.await; - }); -} - -#[test] -#[should_panic(expected = "don't match the transport")] -fn ensure_listen_addresses_consistent_with_transport_memory() { - let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; - - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - transport: config::TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); -} - -#[test] -#[should_panic(expected = "don't match the transport")] -fn ensure_listen_addresses_consistent_with_transport_not_memory() { - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); -} - -#[test] -#[should_panic(expected = "don't match the transport")] -fn ensure_boot_node_addresses_consistent_with_transport_memory() { - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let boot_node = config::MultiaddrWithPeerId { - multiaddr: config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)], - peer_id: PeerId::random(), - }; - - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - transport: config::TransportConfig::MemoryOnly, - boot_nodes: vec![boot_node], - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); -} - -#[test] -#[should_panic(expected = "don't match the transport")] -fn ensure_boot_node_addresses_consistent_with_transport_not_memory() { - let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; - let boot_node = config::MultiaddrWithPeerId { - multiaddr: config::build_multiaddr![Memory(rand::random::())], - peer_id: PeerId::random(), - }; - - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - boot_nodes: vec![boot_node], - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); -} - -#[test] -#[should_panic(expected = "don't match the transport")] -fn ensure_reserved_node_addresses_consistent_with_transport_memory() { - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let reserved_node = config::MultiaddrWithPeerId { - multiaddr: config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)], - peer_id: PeerId::random(), - }; - - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - transport: config::TransportConfig::MemoryOnly, - default_peers_set: config::SetConfig { - reserved_nodes: vec![reserved_node], - ..Default::default() - }, - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); -} - -#[test] -#[should_panic(expected = "don't match the transport")] -fn ensure_reserved_node_addresses_consistent_with_transport_not_memory() { - let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; - let reserved_node = config::MultiaddrWithPeerId { - multiaddr: config::build_multiaddr![Memory(rand::random::())], - peer_id: PeerId::random(), - }; - - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - default_peers_set: config::SetConfig { - reserved_nodes: vec![reserved_node], - ..Default::default() - }, - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); -} - -#[test] -#[should_panic(expected = "don't match the transport")] -fn ensure_public_addresses_consistent_with_transport_memory() { - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let public_address = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; - - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - transport: config::TransportConfig::MemoryOnly, - public_addresses: vec![public_address], - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); -} - -#[test] -#[should_panic(expected = "don't match the transport")] -fn ensure_public_addresses_consistent_with_transport_not_memory() { - let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; - let public_address = config::build_multiaddr![Memory(rand::random::())]; - - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - public_addresses: vec![public_address], - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); -} diff --git a/client/network/src/service/tests/chain_sync.rs b/client/network/src/service/tests/chain_sync.rs new file mode 100644 index 0000000000000..0f47b64c352f2 --- /dev/null +++ b/client/network/src/service/tests/chain_sync.rs @@ -0,0 +1,440 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{ + config, + service::tests::{TestNetworkBuilder, BLOCK_ANNOUNCE_PROTO_NAME}, +}; + +use futures::prelude::*; +use libp2p::PeerId; +use sc_block_builder::BlockBuilderProvider; +use sc_client_api::HeaderBackend; +use sc_consensus::JustificationSyncLink; +use sc_network_common::{ + config::{MultiaddrWithPeerId, ProtocolId, SetConfig}, + protocol::{event::Event, role::Roles, ProtocolName}, + service::NetworkSyncForkRequest, + sync::{SyncState, SyncStatus}, +}; +use sc_network_sync::{mock::MockChainSync, service::mock::MockChainSyncInterface, ChainSync}; +use sp_core::H256; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Header as _}, +}; +use std::{ + sync::{Arc, RwLock}, + task::Poll, + time::Duration, +}; +use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; +use tokio::runtime::Handle; + +fn set_default_expecations_no_peers( + chain_sync: &mut MockChainSync, +) { + chain_sync.expect_poll().returning(|_| Poll::Pending); + chain_sync.expect_status().returning(|| SyncStatus { + state: SyncState::Idle, + best_seen_block: None, + num_peers: 0u32, + queued_blocks: 0u32, + state_sync: None, + warp_sync: None, + }); +} + +#[tokio::test] +async fn normal_network_poll_no_peers() { + // build `ChainSync` and set default expectations for it + let mut chain_sync = + Box::new(MockChainSync::::new()); + set_default_expecations_no_peers(&mut chain_sync); + + // build `ChainSyncInterface` provider and set no expecations for it (i.e., it cannot be + // called) + let chain_sync_service = + Box::new(MockChainSyncInterface::::new()); + + let mut network = TestNetworkBuilder::new(Handle::current()) + .with_chain_sync((chain_sync, chain_sync_service)) + .build(); + + // poll the network once + futures::future::poll_fn(|cx| { + let _ = network.network().poll_unpin(cx); + Poll::Ready(()) + }) + .await; +} + +#[tokio::test] +async fn request_justification() { + let hash = H256::random(); + let number = 1337u64; + + // build `ChainSyncInterface` provider and and expect + // `JustificationSyncLink::request_justification() to be called once + let mut chain_sync_service = + Box::new(MockChainSyncInterface::::new()); + + chain_sync_service + .expect_justification_sync_link_request_justification() + .withf(move |in_hash, in_number| &hash == in_hash && &number == in_number) + .once() + .returning(|_, _| ()); + + // build `ChainSync` and set default expecations for it + let mut chain_sync = MockChainSync::::new(); + + set_default_expecations_no_peers(&mut chain_sync); + let mut network = TestNetworkBuilder::new(Handle::current()) + .with_chain_sync((Box::new(chain_sync), chain_sync_service)) + .build(); + + // send "request justifiction" message and poll the network + network.service().request_justification(&hash, number); + + futures::future::poll_fn(|cx| { + let _ = network.network().poll_unpin(cx); + Poll::Ready(()) + }) + .await; +} + +#[tokio::test] +async fn clear_justification_requests() { + // build `ChainSyncInterface` provider and expect + // `JustificationSyncLink::clear_justification_requests()` to be called + let mut chain_sync_service = + Box::new(MockChainSyncInterface::::new()); + + chain_sync_service + .expect_justification_sync_link_clear_justification_requests() + .once() + .returning(|| ()); + + // build `ChainSync` and set default expecations for it + let mut chain_sync = + Box::new(MockChainSync::::new()); + + set_default_expecations_no_peers(&mut chain_sync); + let mut network = TestNetworkBuilder::new(Handle::current()) + .with_chain_sync((chain_sync, chain_sync_service)) + .build(); + + // send "request justifiction" message and poll the network + network.service().clear_justification_requests(); + + futures::future::poll_fn(|cx| { + let _ = network.network().poll_unpin(cx); + Poll::Ready(()) + }) + .await; +} + +#[tokio::test] +async fn set_sync_fork_request() { + // build `ChainSync` and set default expectations for it + let mut chain_sync = + Box::new(MockChainSync::::new()); + set_default_expecations_no_peers(&mut chain_sync); + + // build `ChainSyncInterface` provider and verify that the `set_sync_fork_request()` + // call is delegated to `ChainSyncInterface` (which eventually forwards it to `ChainSync`) + let mut chain_sync_service = + MockChainSyncInterface::::new(); + + let hash = H256::random(); + let number = 1337u64; + let peers = (0..3).map(|_| PeerId::random()).collect::>(); + let copy_peers = peers.clone(); + + chain_sync_service + .expect_set_sync_fork_request() + .withf(move |in_peers, in_hash, in_number| { + &peers == in_peers && &hash == in_hash && &number == in_number + }) + .once() + .returning(|_, _, _| ()); + + let mut network = TestNetworkBuilder::new(Handle::current()) + .with_chain_sync((chain_sync, Box::new(chain_sync_service))) + .build(); + + // send "set sync fork request" message and poll the network + network.service().set_sync_fork_request(copy_peers, hash, number); + + futures::future::poll_fn(|cx| { + let _ = network.network().poll_unpin(cx); + Poll::Ready(()) + }) + .await; +} + +#[tokio::test] +async fn on_block_finalized() { + let client = Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0); + // build `ChainSyncInterface` provider and set no expecations for it (i.e., it cannot be + // called) + let chain_sync_service = + Box::new(MockChainSyncInterface::::new()); + + // build `ChainSync` and verify that call to `on_block_finalized()` is made + let mut chain_sync = + Box::new(MockChainSync::::new()); + + let at = client.header(&BlockId::Hash(client.info().best_hash)).unwrap().unwrap().hash(); + let block = client + .new_block_at(&BlockId::Hash(at), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + let header = block.header.clone(); + let block_number = *header.number(); + let hash = block.hash(); + + chain_sync + .expect_on_block_finalized() + .withf(move |in_hash, in_number| &hash == in_hash && &block_number == in_number) + .once() + .returning(|_, _| ()); + + set_default_expecations_no_peers(&mut chain_sync); + let mut network = TestNetworkBuilder::new(Handle::current()) + .with_client(client) + .with_chain_sync((chain_sync, chain_sync_service)) + .build(); + + // send "set sync fork request" message and poll the network + network.network().on_block_finalized(hash, header); + + futures::future::poll_fn(|cx| { + let _ = network.network().poll_unpin(cx); + Poll::Ready(()) + }) + .await; +} + +// report from mock import queue that importing a justification was not successful +// and verify that connection to the peer is closed +#[tokio::test] +async fn invalid_justification_imported() { + struct DummyImportQueueHandle; + + impl + sc_consensus::import_queue::ImportQueueService< + substrate_test_runtime_client::runtime::Block, + > for DummyImportQueueHandle + { + fn import_blocks( + &mut self, + _origin: sp_consensus::BlockOrigin, + _blocks: Vec< + sc_consensus::IncomingBlock, + >, + ) { + } + + fn import_justifications( + &mut self, + _who: sc_consensus::import_queue::RuntimeOrigin, + _hash: substrate_test_runtime_client::runtime::Hash, + _number: sp_runtime::traits::NumberFor, + _justifications: sp_runtime::Justifications, + ) { + } + } + + struct DummyImportQueue( + Arc< + RwLock< + Option<( + PeerId, + substrate_test_runtime_client::runtime::Hash, + sp_runtime::traits::NumberFor, + )>, + >, + >, + DummyImportQueueHandle, + ); + + #[async_trait::async_trait] + impl sc_consensus::ImportQueue for DummyImportQueue { + fn poll_actions( + &mut self, + _cx: &mut futures::task::Context, + link: &mut dyn sc_consensus::Link, + ) { + if let Some((peer, hash, number)) = *self.0.read().unwrap() { + link.justification_imported(peer, &hash, number, false); + } + } + + fn service( + &self, + ) -> Box< + dyn sc_consensus::import_queue::ImportQueueService< + substrate_test_runtime_client::runtime::Block, + >, + > { + Box::new(DummyImportQueueHandle {}) + } + + fn service_ref( + &mut self, + ) -> &mut dyn sc_consensus::import_queue::ImportQueueService< + substrate_test_runtime_client::runtime::Block, + > { + &mut self.1 + } + + async fn run( + self, + _link: Box>, + ) { + } + } + + let justification_info = Arc::new(RwLock::new(None)); + let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + + let (service1, mut event_stream1) = TestNetworkBuilder::new(Handle::current()) + .with_import_queue(Box::new(DummyImportQueue( + justification_info.clone(), + DummyImportQueueHandle {}, + ))) + .with_listen_addresses(vec![listen_addr.clone()]) + .build() + .start_network(); + + let (service2, mut event_stream2) = TestNetworkBuilder::new(Handle::current()) + .with_set_config(SetConfig { + reserved_nodes: vec![MultiaddrWithPeerId { + multiaddr: listen_addr, + peer_id: service1.local_peer_id, + }], + ..Default::default() + }) + .build() + .start_network(); + + async fn wait_for_events(stream: &mut (impl Stream + std::marker::Unpin)) { + let mut notif_received = false; + let mut sync_received = false; + while !notif_received || !sync_received { + match stream.next().await.unwrap() { + Event::NotificationStreamOpened { .. } => notif_received = true, + Event::SyncConnected { .. } => sync_received = true, + _ => {}, + }; + } + } + + wait_for_events(&mut event_stream1).await; + wait_for_events(&mut event_stream2).await; + + { + let mut info = justification_info.write().unwrap(); + *info = Some((service2.local_peer_id, H256::random(), 1337u64)); + } + + let wait_disconnection = async { + while !std::matches!(event_stream1.next().await, Some(Event::SyncDisconnected { .. })) {} + }; + + if tokio::time::timeout(Duration::from_secs(5), wait_disconnection).await.is_err() { + panic!("did not receive disconnection event in time"); + } +} + +#[tokio::test] +async fn disconnect_peer_using_chain_sync_handle() { + let client = Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0); + let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (chain_sync_network_provider, chain_sync_network_handle) = + sc_network_sync::service::network::NetworkServiceProvider::new(); + let handle_clone = chain_sync_network_handle.clone(); + + let (chain_sync, chain_sync_service, _) = ChainSync::new( + sc_network_common::sync::SyncMode::Full, + client.clone(), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&config::Role::Full), + Box::new(sp_consensus::block_validation::DefaultBlockAnnounceValidator), + 1u32, + None, + None, + chain_sync_network_handle.clone(), + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, + ) + .unwrap(); + + let (node1, mut event_stream1) = TestNetworkBuilder::new(Handle::current()) + .with_listen_addresses(vec![listen_addr.clone()]) + .with_chain_sync((Box::new(chain_sync), Box::new(chain_sync_service))) + .with_chain_sync_network((chain_sync_network_provider, chain_sync_network_handle)) + .with_client(client.clone()) + .build() + .start_network(); + + let (node2, mut event_stream2) = TestNetworkBuilder::new(Handle::current()) + .with_set_config(SetConfig { + reserved_nodes: vec![MultiaddrWithPeerId { + multiaddr: listen_addr, + peer_id: node1.local_peer_id, + }], + ..Default::default() + }) + .with_client(client.clone()) + .build() + .start_network(); + + async fn wait_for_events(stream: &mut (impl Stream + std::marker::Unpin)) { + let mut notif_received = false; + let mut sync_received = false; + while !notif_received || !sync_received { + match stream.next().await.unwrap() { + Event::NotificationStreamOpened { .. } => notif_received = true, + Event::SyncConnected { .. } => sync_received = true, + _ => {}, + }; + } + } + + wait_for_events(&mut event_stream1).await; + wait_for_events(&mut event_stream2).await; + + handle_clone.disconnect_peer(node2.local_peer_id, BLOCK_ANNOUNCE_PROTO_NAME.into()); + + let wait_disconnection = async { + while !std::matches!(event_stream1.next().await, Some(Event::SyncDisconnected { .. })) {} + }; + + if tokio::time::timeout(Duration::from_secs(5), wait_disconnection).await.is_err() { + panic!("did not receive disconnection event in time"); + } +} diff --git a/client/network/src/service/tests/mod.rs b/client/network/src/service/tests/mod.rs new file mode 100644 index 0000000000000..fa1486a791213 --- /dev/null +++ b/client/network/src/service/tests/mod.rs @@ -0,0 +1,359 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{config, ChainSyncInterface, NetworkService, NetworkWorker}; + +use futures::prelude::*; +use libp2p::Multiaddr; +use sc_client_api::{BlockBackend, HeaderBackend}; +use sc_consensus::{ImportQueue, Link}; +use sc_network_common::{ + config::{ + NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, ProtocolId, SetConfig, + TransportConfig, + }, + protocol::{event::Event, role::Roles}, + service::NetworkEventStream, + sync::{message::BlockAnnouncesHandshake, ChainSync as ChainSyncT}, +}; +use sc_network_light::light_client_requests::handler::LightClientRequestHandler; +use sc_network_sync::{ + block_request_handler::BlockRequestHandler, + service::network::{NetworkServiceHandle, NetworkServiceProvider}, + state_request_handler::StateRequestHandler, + ChainSync, +}; +use sp_runtime::traits::{Block as BlockT, Header as _, Zero}; +use std::sync::Arc; +use substrate_test_runtime_client::{ + runtime::{Block as TestBlock, Hash as TestHash}, + TestClient, TestClientBuilder, TestClientBuilderExt as _, +}; +use tokio::runtime::Handle; + +#[cfg(test)] +mod chain_sync; +#[cfg(test)] +mod service; + +type TestNetworkWorker = NetworkWorker; +type TestNetworkService = NetworkService; + +const BLOCK_ANNOUNCE_PROTO_NAME: &str = "/block-announces"; +const PROTOCOL_NAME: &str = "/foo"; + +struct TestNetwork { + network: TestNetworkWorker, + rt_handle: Handle, +} + +impl TestNetwork { + pub fn new(network: TestNetworkWorker, rt_handle: Handle) -> Self { + Self { network, rt_handle } + } + + pub fn service(&self) -> &Arc { + &self.network.service() + } + + pub fn network(&mut self) -> &mut TestNetworkWorker { + &mut self.network + } + + pub fn start_network( + self, + ) -> (Arc, (impl Stream + std::marker::Unpin)) { + let worker = self.network; + let service = worker.service().clone(); + let event_stream = service.event_stream("test"); + + self.rt_handle.spawn(async move { + futures::pin_mut!(worker); + let _ = worker.await; + }); + + (service, event_stream) + } +} + +struct TestNetworkBuilder { + import_queue: Option>>, + link: Option>>, + client: Option>, + listen_addresses: Vec, + set_config: Option, + chain_sync: Option<(Box>, Box>)>, + chain_sync_network: Option<(NetworkServiceProvider, NetworkServiceHandle)>, + config: Option, + rt_handle: Handle, +} + +impl TestNetworkBuilder { + pub fn new(rt_handle: Handle) -> Self { + Self { + import_queue: None, + link: None, + client: None, + listen_addresses: Vec::new(), + set_config: None, + chain_sync: None, + chain_sync_network: None, + config: None, + rt_handle, + } + } + + pub fn with_client(mut self, client: Arc) -> Self { + self.client = Some(client); + self + } + + pub fn with_config(mut self, config: config::NetworkConfiguration) -> Self { + self.config = Some(config); + self + } + + pub fn with_listen_addresses(mut self, addresses: Vec) -> Self { + self.listen_addresses = addresses; + self + } + + pub fn with_set_config(mut self, set_config: SetConfig) -> Self { + self.set_config = Some(set_config); + self + } + + pub fn with_chain_sync( + mut self, + chain_sync: (Box>, Box>), + ) -> Self { + self.chain_sync = Some(chain_sync); + self + } + + pub fn with_chain_sync_network( + mut self, + chain_sync_network: (NetworkServiceProvider, NetworkServiceHandle), + ) -> Self { + self.chain_sync_network = Some(chain_sync_network); + self + } + + pub fn with_import_queue(mut self, import_queue: Box>) -> Self { + self.import_queue = Some(import_queue); + self + } + + pub fn build(mut self) -> TestNetwork { + let client = self.client.as_mut().map_or( + Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0), + |v| v.clone(), + ); + + let network_config = self.config.unwrap_or(config::NetworkConfiguration { + extra_sets: vec![NonDefaultSetConfig { + notifications_protocol: PROTOCOL_NAME.into(), + fallback_names: Vec::new(), + max_notification_size: 1024 * 1024, + handshake: None, + set_config: self.set_config.unwrap_or_default(), + }], + listen_addresses: self.listen_addresses, + transport: TransportConfig::MemoryOnly, + ..config::NetworkConfiguration::new_local() + }); + + #[derive(Clone)] + struct PassThroughVerifier(bool); + + #[async_trait::async_trait] + impl sc_consensus::Verifier for PassThroughVerifier { + async fn verify( + &mut self, + mut block: sc_consensus::BlockImportParams, + ) -> Result< + ( + sc_consensus::BlockImportParams, + Option)>>, + ), + String, + > { + let maybe_keys = block + .header + .digest() + .log(|l| { + l.try_as_raw(sp_runtime::generic::OpaqueDigestItemId::Consensus(b"aura")) + .or_else(|| { + l.try_as_raw(sp_runtime::generic::OpaqueDigestItemId::Consensus( + b"babe", + )) + }) + }) + .map(|blob| { + vec![(sp_blockchain::well_known_cache_keys::AUTHORITIES, blob.to_vec())] + }); + + block.finalized = self.0; + block.fork_choice = Some(sc_consensus::ForkChoiceStrategy::LongestChain); + Ok((block, maybe_keys)) + } + } + + let mut import_queue = + self.import_queue.unwrap_or(Box::new(sc_consensus::BasicQueue::new( + PassThroughVerifier(false), + Box::new(client.clone()), + None, + &sp_core::testing::TaskExecutor::new(), + None, + ))); + + let protocol_id = ProtocolId::from("test-protocol-name"); + let fork_id = Some(String::from("test-fork-id")); + + let block_request_protocol_config = { + let (handler, protocol_config) = + BlockRequestHandler::new(&protocol_id, None, client.clone(), 50); + self.rt_handle.spawn(handler.run().boxed()); + protocol_config + }; + + let state_request_protocol_config = { + let (handler, protocol_config) = + StateRequestHandler::new(&protocol_id, None, client.clone(), 50); + self.rt_handle.spawn(handler.run().boxed()); + protocol_config + }; + + let light_client_request_protocol_config = { + let (handler, protocol_config) = + LightClientRequestHandler::new(&protocol_id, None, client.clone()); + self.rt_handle.spawn(handler.run().boxed()); + protocol_config + }; + + let block_announce_config = NonDefaultSetConfig { + notifications_protocol: BLOCK_ANNOUNCE_PROTO_NAME.into(), + fallback_names: vec![], + max_notification_size: 1024 * 1024, + handshake: Some(NotificationHandshake::new(BlockAnnouncesHandshake::< + substrate_test_runtime_client::runtime::Block, + >::build( + Roles::from(&config::Role::Full), + client.info().best_number, + client.info().best_hash, + client + .block_hash(Zero::zero()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + ))), + set_config: SetConfig { + in_peers: 0, + out_peers: 0, + reserved_nodes: Vec::new(), + non_reserved_mode: NonReservedPeerMode::Deny, + }, + }; + + let (chain_sync_network_provider, chain_sync_network_handle) = + self.chain_sync_network.unwrap_or(NetworkServiceProvider::new()); + + let (chain_sync, chain_sync_service) = self.chain_sync.unwrap_or({ + let (chain_sync, chain_sync_service, _) = ChainSync::new( + match network_config.sync_mode { + config::SyncMode::Full => sc_network_common::sync::SyncMode::Full, + config::SyncMode::Fast { skip_proofs, storage_chain_mode } => + sc_network_common::sync::SyncMode::LightState { + skip_proofs, + storage_chain_mode, + }, + config::SyncMode::Warp => sc_network_common::sync::SyncMode::Warp, + }, + client.clone(), + protocol_id.clone(), + &fork_id, + Roles::from(&config::Role::Full), + Box::new(sp_consensus::block_validation::DefaultBlockAnnounceValidator), + network_config.max_parallel_downloads, + None, + None, + chain_sync_network_handle, + import_queue.service(), + block_request_protocol_config.name.clone(), + state_request_protocol_config.name.clone(), + None, + ) + .unwrap(); + + if let None = self.link { + self.link = Some(Box::new(chain_sync_service.clone())); + } + (Box::new(chain_sync), Box::new(chain_sync_service)) + }); + let mut link = self + .link + .unwrap_or(Box::new(sc_network_sync::service::mock::MockChainSyncInterface::new())); + + let handle = self.rt_handle.clone(); + let executor = move |f| { + handle.spawn(f); + }; + + let worker = NetworkWorker::< + substrate_test_runtime_client::runtime::Block, + substrate_test_runtime_client::runtime::Hash, + substrate_test_runtime_client::TestClient, + >::new(config::Params { + block_announce_config, + role: config::Role::Full, + executor: Box::new(executor), + network_config, + chain: client.clone(), + protocol_id, + fork_id, + chain_sync, + chain_sync_service, + metrics_registry: None, + request_response_protocol_configs: [ + block_request_protocol_config, + state_request_protocol_config, + light_client_request_protocol_config, + ] + .to_vec(), + }) + .unwrap(); + + let service = worker.service().clone(); + self.rt_handle.spawn(async move { + let _ = chain_sync_network_provider.run(service).await; + }); + self.rt_handle.spawn(async move { + loop { + futures::future::poll_fn(|cx| { + import_queue.poll_actions(cx, &mut *link); + std::task::Poll::Ready(()) + }) + .await; + tokio::time::sleep(std::time::Duration::from_millis(250)).await; + } + }); + + TestNetwork::new(worker, self.rt_handle) + } +} diff --git a/client/network/src/service/tests/service.rs b/client/network/src/service/tests/service.rs new file mode 100644 index 0000000000000..aa74e595fff7e --- /dev/null +++ b/client/network/src/service/tests/service.rs @@ -0,0 +1,636 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{config, service::tests::TestNetworkBuilder, NetworkService}; + +use futures::prelude::*; +use libp2p::PeerId; +use sc_network_common::{ + config::{MultiaddrWithPeerId, NonDefaultSetConfig, SetConfig, TransportConfig}, + protocol::event::Event, + service::{NetworkNotification, NetworkPeers, NetworkStateInfo}, +}; +use std::{sync::Arc, time::Duration}; +use tokio::runtime::Handle; + +type TestNetworkService = NetworkService< + substrate_test_runtime_client::runtime::Block, + substrate_test_runtime_client::runtime::Hash, +>; + +const BLOCK_ANNOUNCE_PROTO_NAME: &str = "/block-announces"; +const PROTOCOL_NAME: &str = "/foo"; + +/// Builds two nodes and their associated events stream. +/// The nodes are connected together and have the `PROTOCOL_NAME` protocol registered. +fn build_nodes_one_proto( + rt_handle: &Handle, +) -> ( + Arc, + impl Stream, + Arc, + impl Stream, +) { + let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + + let (node1, events_stream1) = TestNetworkBuilder::new(rt_handle.clone()) + .with_listen_addresses(vec![listen_addr.clone()]) + .build() + .start_network(); + + let (node2, events_stream2) = TestNetworkBuilder::new(rt_handle.clone()) + .with_set_config(SetConfig { + reserved_nodes: vec![MultiaddrWithPeerId { + multiaddr: listen_addr, + peer_id: node1.local_peer_id(), + }], + ..Default::default() + }) + .build() + .start_network(); + + (node1, events_stream1, node2, events_stream2) +} + +#[test] +fn notifications_state_consistent() { + // Runs two nodes and ensures that events are propagated out of the API in a consistent + // correct order, which means no notification received on a closed substream. + + let runtime = tokio::runtime::Runtime::new().unwrap(); + + let (node1, mut events_stream1, node2, mut events_stream2) = + build_nodes_one_proto(runtime.handle()); + + // Write some initial notifications that shouldn't get through. + for _ in 0..(rand::random::() % 5) { + node1.write_notification( + node2.local_peer_id(), + PROTOCOL_NAME.into(), + b"hello world".to_vec(), + ); + } + for _ in 0..(rand::random::() % 5) { + node2.write_notification( + node1.local_peer_id(), + PROTOCOL_NAME.into(), + b"hello world".to_vec(), + ); + } + + runtime.block_on(async move { + // True if we have an active substream from node1 to node2. + let mut node1_to_node2_open = false; + // True if we have an active substream from node2 to node1. + let mut node2_to_node1_open = false; + // We stop the test after a certain number of iterations. + let mut iterations = 0; + // Safe guard because we don't want the test to pass if no substream has been open. + let mut something_happened = false; + + loop { + iterations += 1; + if iterations >= 1_000 { + assert!(something_happened); + break + } + + // Start by sending a notification from node1 to node2 and vice-versa. Part of the + // test consists in ensuring that notifications get ignored if the stream isn't open. + if rand::random::() % 5 >= 3 { + node1.write_notification( + node2.local_peer_id(), + PROTOCOL_NAME.into(), + b"hello world".to_vec(), + ); + } + if rand::random::() % 5 >= 3 { + node2.write_notification( + node1.local_peer_id(), + PROTOCOL_NAME.into(), + b"hello world".to_vec(), + ); + } + + // Also randomly disconnect the two nodes from time to time. + if rand::random::() % 20 == 0 { + node1.disconnect_peer(node2.local_peer_id(), PROTOCOL_NAME.into()); + } + if rand::random::() % 20 == 0 { + node2.disconnect_peer(node1.local_peer_id(), PROTOCOL_NAME.into()); + } + + // Grab next event from either `events_stream1` or `events_stream2`. + let next_event = { + let next1 = events_stream1.next(); + let next2 = events_stream2.next(); + // We also await on a small timer, otherwise it is possible for the test to wait + // forever while nothing at all happens on the network. + let continue_test = futures_timer::Delay::new(Duration::from_millis(20)); + match future::select(future::select(next1, next2), continue_test).await { + future::Either::Left((future::Either::Left((Some(ev), _)), _)) => + future::Either::Left(ev), + future::Either::Left((future::Either::Right((Some(ev), _)), _)) => + future::Either::Right(ev), + future::Either::Right(_) => continue, + _ => break, + } + }; + + match next_event { + future::Either::Left(Event::NotificationStreamOpened { + remote, protocol, .. + }) => + if protocol == PROTOCOL_NAME.into() { + something_happened = true; + assert!(!node1_to_node2_open); + node1_to_node2_open = true; + assert_eq!(remote, node2.local_peer_id()); + }, + future::Either::Right(Event::NotificationStreamOpened { + remote, protocol, .. + }) => + if protocol == PROTOCOL_NAME.into() { + something_happened = true; + assert!(!node2_to_node1_open); + node2_to_node1_open = true; + assert_eq!(remote, node1.local_peer_id()); + }, + future::Either::Left(Event::NotificationStreamClosed { + remote, protocol, .. + }) => + if protocol == PROTOCOL_NAME.into() { + assert!(node1_to_node2_open); + node1_to_node2_open = false; + assert_eq!(remote, node2.local_peer_id()); + }, + future::Either::Right(Event::NotificationStreamClosed { + remote, protocol, .. + }) => + if protocol == PROTOCOL_NAME.into() { + assert!(node2_to_node1_open); + node2_to_node1_open = false; + assert_eq!(remote, node1.local_peer_id()); + }, + future::Either::Left(Event::NotificationsReceived { remote, .. }) => { + assert!(node1_to_node2_open); + assert_eq!(remote, node2.local_peer_id()); + if rand::random::() % 5 >= 4 { + node1.write_notification( + node2.local_peer_id(), + PROTOCOL_NAME.into(), + b"hello world".to_vec(), + ); + } + }, + future::Either::Right(Event::NotificationsReceived { remote, .. }) => { + assert!(node2_to_node1_open); + assert_eq!(remote, node1.local_peer_id()); + if rand::random::() % 5 >= 4 { + node2.write_notification( + node1.local_peer_id(), + PROTOCOL_NAME.into(), + b"hello world".to_vec(), + ); + } + }, + + // Add new events here. + future::Either::Left(Event::SyncConnected { .. }) => {}, + future::Either::Right(Event::SyncConnected { .. }) => {}, + future::Either::Left(Event::SyncDisconnected { .. }) => {}, + future::Either::Right(Event::SyncDisconnected { .. }) => {}, + future::Either::Left(Event::Dht(_)) => {}, + future::Either::Right(Event::Dht(_)) => {}, + }; + } + }); +} + +#[tokio::test] +async fn lots_of_incoming_peers_works() { + let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + + let (main_node, _) = TestNetworkBuilder::new(Handle::current()) + .with_listen_addresses(vec![listen_addr.clone()]) + .with_set_config(SetConfig { in_peers: u32::MAX, ..Default::default() }) + .build() + .start_network(); + + let main_node_peer_id = main_node.local_peer_id(); + + // We spawn background tasks and push them in this `Vec`. They will all be waited upon before + // this test ends. + let mut background_tasks_to_wait = Vec::new(); + + for _ in 0..32 { + let (_dialing_node, event_stream) = TestNetworkBuilder::new(Handle::current()) + .with_set_config(SetConfig { + reserved_nodes: vec![MultiaddrWithPeerId { + multiaddr: listen_addr.clone(), + peer_id: main_node_peer_id, + }], + ..Default::default() + }) + .build() + .start_network(); + + background_tasks_to_wait.push(tokio::spawn(async move { + // Create a dummy timer that will "never" fire, and that will be overwritten when we + // actually need the timer. Using an Option would be technically cleaner, but it would + // make the code below way more complicated. + let mut timer = futures_timer::Delay::new(Duration::from_secs(3600 * 24 * 7)).fuse(); + + let mut event_stream = event_stream.fuse(); + loop { + futures::select! { + _ = timer => { + // Test succeeds when timer fires. + return; + } + ev = event_stream.next() => { + match ev.unwrap() { + Event::NotificationStreamOpened { remote, .. } => { + assert_eq!(remote, main_node_peer_id); + // Test succeeds after 5 seconds. This timer is here in order to + // detect a potential problem after opening. + timer = futures_timer::Delay::new(Duration::from_secs(5)).fuse(); + } + Event::NotificationStreamClosed { .. } => { + // Test failed. + panic!(); + } + _ => {} + } + } + } + } + })); + } + + future::join_all(background_tasks_to_wait).await; +} + +#[test] +fn notifications_back_pressure() { + // Node 1 floods node 2 with notifications. Random sleeps are done on node 2 to simulate the + // node being busy. We make sure that all notifications are received. + + const TOTAL_NOTIFS: usize = 10_000; + + let runtime = tokio::runtime::Runtime::new().unwrap(); + + let (node1, mut events_stream1, node2, mut events_stream2) = + build_nodes_one_proto(runtime.handle()); + let node2_id = node2.local_peer_id(); + + let receiver = runtime.spawn(async move { + let mut received_notifications = 0; + + while received_notifications < TOTAL_NOTIFS { + match events_stream2.next().await.unwrap() { + Event::NotificationStreamClosed { .. } => panic!(), + Event::NotificationsReceived { messages, .. } => + for message in messages { + assert_eq!(message.0, PROTOCOL_NAME.into()); + assert_eq!(message.1, format!("hello #{}", received_notifications)); + received_notifications += 1; + }, + _ => {}, + }; + + if rand::random::() < 2 { + tokio::time::sleep(Duration::from_millis(rand::random::() % 750)).await; + } + } + }); + + runtime.block_on(async move { + // Wait for the `NotificationStreamOpened`. + loop { + match events_stream1.next().await.unwrap() { + Event::NotificationStreamOpened { .. } => break, + _ => {}, + }; + } + + // Sending! + for num in 0..TOTAL_NOTIFS { + let notif = node1.notification_sender(node2_id, PROTOCOL_NAME.into()).unwrap(); + notif + .ready() + .await + .unwrap() + .send(format!("hello #{}", num).into_bytes()) + .unwrap(); + } + + receiver.await.unwrap(); + }); +} + +#[test] +fn fallback_name_working() { + // Node 1 supports the protocols "new" and "old". Node 2 only supports "old". Checks whether + // they can connect. + const NEW_PROTOCOL_NAME: &str = "/new-shiny-protocol-that-isnt-PROTOCOL_NAME"; + + let runtime = tokio::runtime::Runtime::new().unwrap(); + + let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + let (node1, mut events_stream1) = TestNetworkBuilder::new(runtime.handle().clone()) + .with_config(config::NetworkConfiguration { + extra_sets: vec![NonDefaultSetConfig { + notifications_protocol: NEW_PROTOCOL_NAME.into(), + fallback_names: vec![PROTOCOL_NAME.into()], + max_notification_size: 1024 * 1024, + handshake: None, + set_config: Default::default(), + }], + listen_addresses: vec![listen_addr.clone()], + transport: TransportConfig::MemoryOnly, + ..config::NetworkConfiguration::new_local() + }) + .build() + .start_network(); + + let (_, mut events_stream2) = TestNetworkBuilder::new(runtime.handle().clone()) + .with_set_config(SetConfig { + reserved_nodes: vec![MultiaddrWithPeerId { + multiaddr: listen_addr, + peer_id: node1.local_peer_id(), + }], + ..Default::default() + }) + .build() + .start_network(); + + let receiver = runtime.spawn(async move { + // Wait for the `NotificationStreamOpened`. + loop { + match events_stream2.next().await.unwrap() { + Event::NotificationStreamOpened { protocol, negotiated_fallback, .. } => { + assert_eq!(protocol, PROTOCOL_NAME.into()); + assert_eq!(negotiated_fallback, None); + break + }, + _ => {}, + }; + } + }); + + runtime.block_on(async move { + // Wait for the `NotificationStreamOpened`. + loop { + match events_stream1.next().await.unwrap() { + Event::NotificationStreamOpened { protocol, negotiated_fallback, .. } + if protocol == NEW_PROTOCOL_NAME.into() => + { + assert_eq!(negotiated_fallback, Some(PROTOCOL_NAME.into())); + break + }, + _ => {}, + }; + } + + receiver.await.unwrap(); + }); +} + +// Disconnect peer by calling `Protocol::disconnect_peer()` with the supplied block announcement +// protocol name and verify that `SyncDisconnected` event is emitted +#[tokio::test] +async fn disconnect_sync_peer_using_block_announcement_protocol_name() { + let (node1, mut events_stream1, node2, mut events_stream2) = + build_nodes_one_proto(&Handle::current()); + + async fn wait_for_events(stream: &mut (impl Stream + std::marker::Unpin)) { + let mut notif_received = false; + let mut sync_received = false; + + while !notif_received || !sync_received { + match stream.next().await.unwrap() { + Event::NotificationStreamOpened { .. } => notif_received = true, + Event::SyncConnected { .. } => sync_received = true, + _ => {}, + }; + } + } + + wait_for_events(&mut events_stream1).await; + wait_for_events(&mut events_stream2).await; + + // disconnect peer using `PROTOCOL_NAME`, verify `NotificationStreamClosed` event is emitted + node2.disconnect_peer(node1.local_peer_id(), PROTOCOL_NAME.into()); + assert!(std::matches!( + events_stream2.next().await, + Some(Event::NotificationStreamClosed { .. }) + )); + let _ = events_stream2.next().await; // ignore the reopen event + + // now disconnect using `BLOCK_ANNOUNCE_PROTO_NAME`, verify that `SyncDisconnected` is + // emitted + node2.disconnect_peer(node1.local_peer_id(), BLOCK_ANNOUNCE_PROTO_NAME.into()); + assert!(std::matches!(events_stream2.next().await, Some(Event::SyncDisconnected { .. }))); +} + +#[tokio::test] +#[should_panic(expected = "don't match the transport")] +async fn ensure_listen_addresses_consistent_with_transport_memory() { + let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; + + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + transport: TransportConfig::MemoryOnly, + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); +} + +#[tokio::test] +#[should_panic(expected = "don't match the transport")] +async fn ensure_listen_addresses_consistent_with_transport_not_memory() { + let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); +} + +#[tokio::test] +#[should_panic(expected = "don't match the transport")] +async fn ensure_boot_node_addresses_consistent_with_transport_memory() { + let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + let boot_node = MultiaddrWithPeerId { + multiaddr: config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)], + peer_id: PeerId::random(), + }; + + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + transport: TransportConfig::MemoryOnly, + boot_nodes: vec![boot_node], + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); +} + +#[tokio::test] +#[should_panic(expected = "don't match the transport")] +async fn ensure_boot_node_addresses_consistent_with_transport_not_memory() { + let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; + let boot_node = MultiaddrWithPeerId { + multiaddr: config::build_multiaddr![Memory(rand::random::())], + peer_id: PeerId::random(), + }; + + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + boot_nodes: vec![boot_node], + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); +} + +#[tokio::test] +#[should_panic(expected = "don't match the transport")] +async fn ensure_reserved_node_addresses_consistent_with_transport_memory() { + let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + let reserved_node = MultiaddrWithPeerId { + multiaddr: config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)], + peer_id: PeerId::random(), + }; + + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + transport: TransportConfig::MemoryOnly, + default_peers_set: SetConfig { + reserved_nodes: vec![reserved_node], + ..Default::default() + }, + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); +} + +#[tokio::test] +#[should_panic(expected = "don't match the transport")] +async fn ensure_reserved_node_addresses_consistent_with_transport_not_memory() { + let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; + let reserved_node = MultiaddrWithPeerId { + multiaddr: config::build_multiaddr![Memory(rand::random::())], + peer_id: PeerId::random(), + }; + + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + default_peers_set: SetConfig { + reserved_nodes: vec![reserved_node], + ..Default::default() + }, + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); +} + +#[tokio::test] +#[should_panic(expected = "don't match the transport")] +async fn ensure_public_addresses_consistent_with_transport_memory() { + let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + let public_address = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; + + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + transport: TransportConfig::MemoryOnly, + public_addresses: vec![public_address], + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); +} + +#[tokio::test] +#[should_panic(expected = "don't match the transport")] +async fn ensure_public_addresses_consistent_with_transport_not_memory() { + let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; + let public_address = config::build_multiaddr![Memory(rand::random::())]; + + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + public_addresses: vec![public_address], + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); +} diff --git a/client/network/src/transport.rs b/client/network/src/transport.rs index 3f977a21b1165..b0d5f0235b6e4 100644 --- a/client/network/src/transport.rs +++ b/client/network/src/transport.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -54,13 +54,17 @@ pub fn build_transport( ) -> (Boxed<(PeerId, StreamMuxerBox)>, Arc) { // Build the base layer of the transport. let transport = if !memory_only { - let desktop_trans = tcp::TcpConfig::new().nodelay(true); - let desktop_trans = - websocket::WsConfig::new(desktop_trans.clone()).or_transport(desktop_trans); - let dns_init = futures::executor::block_on(dns::DnsConfig::system(desktop_trans.clone())); + let tcp_config = tcp::GenTcpConfig::new().nodelay(true); + let desktop_trans = tcp::TokioTcpTransport::new(tcp_config.clone()); + let desktop_trans = websocket::WsConfig::new(desktop_trans) + .or_transport(tcp::TokioTcpTransport::new(tcp_config.clone())); + let dns_init = dns::TokioDnsConfig::system(desktop_trans); EitherTransport::Left(if let Ok(dns) = dns_init { EitherTransport::Left(dns) } else { + let desktop_trans = tcp::TokioTcpTransport::new(tcp_config.clone()); + let desktop_trans = websocket::WsConfig::new(desktop_trans) + .or_transport(tcp::TokioTcpTransport::new(tcp_config)); EitherTransport::Right(desktop_trans.map_err(dns::DnsErr::Transport)) }) } else { @@ -82,11 +86,11 @@ pub fn build_transport( rare panic here is basically zero"); // Legacy noise configurations for backward compatibility. - let mut noise_legacy = noise::LegacyConfig::default(); - noise_legacy.recv_legacy_handshake = true; + let noise_legacy = + noise::LegacyConfig { recv_legacy_handshake: true, ..Default::default() }; let mut xx_config = noise::NoiseConfig::xx(noise_keypair); - xx_config.set_legacy_config(noise_legacy.clone()); + xx_config.set_legacy_config(noise_legacy); xx_config.into_authenticated() }; diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml new file mode 100644 index 0000000000000..e29d8047161ce --- /dev/null +++ b/client/network/sync/Cargo.toml @@ -0,0 +1,50 @@ +[package] +description = "Substrate sync network protocol" +name = "sc-network-sync" +version = "0.10.0-dev" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors = ["Parity Technologies "] +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +documentation = "https://docs.rs/sc-network-sync" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[build-dependencies] +prost-build = "0.11" + +[dependencies] +array-bytes = "4.1" +async-trait = "0.1.58" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +futures = "0.3.21" +libp2p = "0.49.0" +log = "0.4.17" +lru = "0.8.1" +mockall = "0.11.2" +prost = "0.11" +smallvec = "1.8.0" +thiserror = "1.0" +fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } +sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } +sc-utils = { version = "4.0.0-dev", path = "../../utils" } +sp-arithmetic = { version = "6.0.0", path = "../../../primitives/arithmetic" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } + +[dev-dependencies] +tokio = { version = "1.22.0", features = ["macros"] } +quickcheck = { version = "1.0.3", default-features = false } +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sp-test-primitives = { version = "2.0.0", path = "../../../primitives/test-primitives" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/network/sync/build.rs b/client/network/sync/build.rs new file mode 100644 index 0000000000000..55794919cdb42 --- /dev/null +++ b/client/network/sync/build.rs @@ -0,0 +1,5 @@ +const PROTOS: &[&str] = &["src/schema/api.v1.proto"]; + +fn main() { + prost_build::compile_protos(PROTOS, &["src/schema"]).unwrap(); +} diff --git a/client/network/src/block_request_handler.rs b/client/network/sync/src/block_request_handler.rs similarity index 70% rename from client/network/src/block_request_handler.rs rename to client/network/sync/src/block_request_handler.rs index 9411ca71fd009..b5f8b6b73bce9 100644 --- a/client/network/src/block_request_handler.rs +++ b/client/network/sync/src/block_request_handler.rs @@ -15,24 +15,25 @@ // along with Substrate. If not, see . //! Helper for handling (i.e. answering) block requests from a remote peer via the -//! [`crate::request_responses::RequestResponsesBehaviour`]. +//! `crate::request_responses::RequestResponsesBehaviour`. -use crate::{ - chain::Client, - config::ProtocolId, - protocol::message::BlockAttributes, - request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, - schema::v1::{block_request::FromBlock, BlockResponse, Direction}, - PeerId, ReputationChange, -}; +use crate::schema::v1::{block_request::FromBlock, BlockResponse, Direction}; use codec::{Decode, Encode}; use futures::{ channel::{mpsc, oneshot}, stream::StreamExt, }; +use libp2p::PeerId; use log::debug; use lru::LruCache; use prost::Message; +use sc_client_api::BlockBackend; +use sc_network_common::{ + config::ProtocolId, + request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, + sync::message::BlockAttributes, +}; +use sp_blockchain::HeaderBackend; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header, One, Zero}, @@ -40,6 +41,7 @@ use sp_runtime::{ use std::{ cmp::min, hash::{Hash, Hasher}, + num::NonZeroUsize, sync::Arc, time::Duration, }; @@ -50,16 +52,26 @@ const MAX_BODY_BYTES: usize = 8 * 1024 * 1024; const MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER: usize = 2; mod rep { - use super::ReputationChange as Rep; + use sc_peerset::ReputationChange as Rep; /// Reputation change when a peer sent us the same request multiple times. pub const SAME_REQUEST: Rep = Rep::new_fatal("Same block request multiple times"); + + /// Reputation change when a peer sent us the same "small" request multiple times. + pub const SAME_SMALL_REQUEST: Rep = + Rep::new(-(1 << 10), "same small block request multiple times"); } /// Generates a [`ProtocolConfig`] for the block request protocol, refusing incoming requests. -pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { +pub fn generate_protocol_config>( + protocol_id: &ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, +) -> ProtocolConfig { ProtocolConfig { - name: generate_protocol_name(protocol_id).into(), + name: generate_protocol_name(genesis_hash, fork_id).into(), + fallback_names: std::iter::once(generate_legacy_protocol_name(protocol_id).into()) + .collect(), max_request_size: 1024 * 1024, max_response_size: 16 * 1024 * 1024, request_timeout: Duration::from_secs(20), @@ -67,10 +79,18 @@ pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { } } -/// Generate the block protocol name from chain specific protocol identifier. -// Visibility `pub(crate)` to allow `crate::light_client_requests::sender` to generate block request -// protocol name and send block requests. -pub(crate) fn generate_protocol_name(protocol_id: &ProtocolId) -> String { +/// Generate the block protocol name from the genesis hash and fork id. +fn generate_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> String { + let genesis_hash = genesis_hash.as_ref(); + if let Some(fork_id) = fork_id { + format!("/{}/{}/sync/2", array_bytes::bytes2hex("", genesis_hash), fork_id) + } else { + format!("/{}/sync/2", array_bytes::bytes2hex("", genesis_hash)) + } +} + +/// Generate the legacy block protocol name from chain specific protocol identifier. +fn generate_legacy_protocol_name(protocol_id: &ProtocolId) -> String { format!("/{}/sync/2", protocol_id.as_ref()) } @@ -85,13 +105,14 @@ struct SeenRequestsKey { support_multiple_justifications: bool, } +#[allow(clippy::derive_hash_xor_eq)] impl Hash for SeenRequestsKey { fn hash(&self, state: &mut H) { self.peer.hash(state); self.max_blocks.hash(state); self.direction.hash(state); self.attributes.hash(state); - + self.support_multiple_justifications.hash(state); match self.from { BlockId::Hash(h) => h.hash(state), BlockId::Number(n) => n.hash(state), @@ -108,8 +129,8 @@ enum SeenRequestsValue { } /// Handler for incoming block requests from a remote peer. -pub struct BlockRequestHandler { - client: Arc>, +pub struct BlockRequestHandler { + client: Arc, request_receiver: mpsc::Receiver, /// Maps from request to number of times we have seen this request. /// @@ -117,21 +138,36 @@ pub struct BlockRequestHandler { seen_requests: LruCache, SeenRequestsValue>, } -impl BlockRequestHandler { +impl BlockRequestHandler +where + B: BlockT, + Client: HeaderBackend + BlockBackend + Send + Sync + 'static, +{ /// Create a new [`BlockRequestHandler`]. pub fn new( protocol_id: &ProtocolId, - client: Arc>, + fork_id: Option<&str>, + client: Arc, num_peer_hint: usize, ) -> (Self, ProtocolConfig) { // Reserve enough request slots for one request per peer when we are at the maximum // number of peers. let (tx, request_receiver) = mpsc::channel(num_peer_hint); - let mut protocol_config = generate_protocol_config(protocol_id); + let mut protocol_config = generate_protocol_config( + protocol_id, + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + fork_id, + ); protocol_config.inbound_queue = Some(tx); - let seen_requests = LruCache::new(num_peer_hint * 2); + let capacity = + NonZeroUsize::new(num_peer_hint.max(1) * 2).expect("cache capacity is not zero"); + let seen_requests = LruCache::new(capacity); (Self { client, request_receiver, seen_requests }, protocol_config) } @@ -187,20 +223,28 @@ impl BlockRequestHandler { peer: *peer, max_blocks, direction, - from: from_block_id.clone(), + from: from_block_id, attributes, support_multiple_justifications, }; let mut reputation_change = None; + let small_request = attributes + .difference(BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION) + .is_empty(); + match self.seen_requests.get_mut(&key) { Some(SeenRequestsValue::First) => {}, Some(SeenRequestsValue::Fulfilled(ref mut requests)) => { *requests = requests.saturating_add(1); if *requests > MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER { - reputation_change = Some(rep::SAME_REQUEST); + reputation_change = Some(if small_request { + rep::SAME_SMALL_REQUEST + } else { + rep::SAME_REQUEST + }); } }, None => { @@ -210,16 +254,13 @@ impl BlockRequestHandler { debug!( target: LOG_TARGET, - "Handling block request from {}: Starting at `{:?}` with maximum blocks \ - of `{}`, direction `{:?}` and attributes `{:?}`.", - peer, - from_block_id, - max_blocks, - direction, - attributes, + "Handling block request from {peer}: Starting at `{from_block_id:?}` with \ + maximum blocks of `{max_blocks}`, reputation_change: `{reputation_change:?}`, \ + small_request `{small_request:?}`, direction `{direction:?}` and \ + attributes `{attributes:?}`.", ); - let result = if reputation_change.is_none() { + let maybe_block_response = if reputation_change.is_none() || small_request { let block_response = self.get_block_response( attributes, from_block_id, @@ -243,9 +284,22 @@ impl BlockRequestHandler { } } + Some(block_response) + } else { + None + }; + + debug!( + target: LOG_TARGET, + "Sending result of block request from {peer} starting at `{from_block_id:?}`: \ + blocks: {:?}, data: {:?}", + maybe_block_response.as_ref().map(|res| res.blocks.len()), + maybe_block_response.as_ref().map(|res| res.encoded_len()), + ); + + let result = if let Some(block_response) = maybe_block_response { let mut data = Vec::with_capacity(block_response.encoded_len()); block_response.encode(&mut data)?; - Ok(data) } else { Err(()) @@ -280,11 +334,8 @@ impl BlockRequestHandler { let number = *header.number(); let hash = header.hash(); let parent_hash = *header.parent_hash(); - let justifications = if get_justification { - self.client.justifications(&BlockId::Hash(hash))? - } else { - None - }; + let justifications = + if get_justification { self.client.justifications(hash)? } else { None }; let (justifications, justification, is_empty_justification) = if support_multiple_justifications { @@ -313,7 +364,7 @@ impl BlockRequestHandler { }; let body = if get_body { - match self.client.block_body(&BlockId::Hash(hash))? { + match self.client.block_body(hash)? { Some(mut extrinsics) => extrinsics.iter_mut().map(|extrinsic| extrinsic.encode()).collect(), None => { @@ -326,7 +377,7 @@ impl BlockRequestHandler { }; let indexed_body = if get_indexed_body { - match self.client.block_indexed_body(&BlockId::Hash(hash))? { + match self.client.block_indexed_body(hash)? { Some(transactions) => transactions, None => { log::trace!( @@ -355,11 +406,20 @@ impl BlockRequestHandler { indexed_body, }; - total_size += block_data.body.iter().map(|ex| ex.len()).sum::(); - total_size += block_data.indexed_body.iter().map(|ex| ex.len()).sum::(); + let new_total_size = total_size + + block_data.body.iter().map(|ex| ex.len()).sum::() + + block_data.indexed_body.iter().map(|ex| ex.len()).sum::(); + + // Send at least one block, but make sure to not exceed the limit. + if !blocks.is_empty() && new_total_size > MAX_BODY_BYTES { + break + } + + total_size = new_total_size; + blocks.push(block_data); - if blocks.len() >= max_blocks as usize || total_size > MAX_BODY_BYTES { + if blocks.len() >= max_blocks as usize { break } @@ -378,19 +438,20 @@ impl BlockRequestHandler { } } -#[derive(derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] enum HandleRequestError { - #[display(fmt = "Failed to decode request: {}.", _0)] - DecodeProto(prost::DecodeError), - #[display(fmt = "Failed to encode response: {}.", _0)] - EncodeProto(prost::EncodeError), - #[display(fmt = "Failed to decode block hash: {}.", _0)] - DecodeScale(codec::Error), - #[display(fmt = "Missing `BlockRequest::from_block` field.")] + #[error("Failed to decode request: {0}.")] + DecodeProto(#[from] prost::DecodeError), + #[error("Failed to encode response: {0}.")] + EncodeProto(#[from] prost::EncodeError), + #[error("Failed to decode block hash: {0}.")] + DecodeScale(#[from] codec::Error), + #[error("Missing `BlockRequest::from_block` field.")] MissingFromField, - #[display(fmt = "Failed to parse BlockRequest::direction.")] + #[error("Failed to parse BlockRequest::direction.")] ParseDirection, - Client(sp_blockchain::Error), - #[display(fmt = "Failed to send response.")] + #[error(transparent)] + Client(#[from] sp_blockchain::Error), + #[error("Failed to send response.")] SendResponse, } diff --git a/client/network/src/protocol/sync/blocks.rs b/client/network/sync/src/blocks.rs similarity index 60% rename from client/network/src/protocol/sync/blocks.rs rename to client/network/sync/src/blocks.rs index 30ba7ffafeffc..b8acd61a2009f 100644 --- a/client/network/src/protocol/sync/blocks.rs +++ b/client/network/sync/src/blocks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,9 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::protocol::message; use libp2p::PeerId; use log::trace; +use sc_network_common::sync::message; use sp_runtime::traits::{Block as BlockT, NumberFor, One}; use std::{ cmp, @@ -39,6 +39,7 @@ pub struct BlockData { enum BlockRangeState { Downloading { len: NumberFor, downloading: u32 }, Complete(Vec>), + Queued { len: NumberFor }, } impl BlockRangeState { @@ -46,6 +47,7 @@ impl BlockRangeState { match *self { Self::Downloading { len, .. } => len, Self::Complete(ref blocks) => (blocks.len() as u32).into(), + Self::Queued { len } => len, } } } @@ -56,12 +58,19 @@ pub struct BlockCollection { /// Downloaded blocks. blocks: BTreeMap, BlockRangeState>, peer_requests: HashMap>, + /// Block ranges downloaded and queued for import. + /// Maps start_hash => (start_num, end_num). + queued_blocks: HashMap, NumberFor)>, } impl BlockCollection { /// Create a new instance. pub fn new() -> Self { - Self { blocks: BTreeMap::new(), peer_requests: HashMap::new() } + Self { + blocks: BTreeMap::new(), + peer_requests: HashMap::new(), + queued_blocks: HashMap::new(), + } } /// Clear everything. @@ -170,29 +179,48 @@ impl BlockCollection { } /// Get a valid chain of blocks ordered in descending order and ready for importing into - /// blockchain. - pub fn drain(&mut self, from: NumberFor) -> Vec> { - let mut drained = Vec::new(); - let mut ranges = Vec::new(); + /// the blockchain. + /// `from` is the maximum block number for the start of the range that we are interested in. + /// The function will return empty Vec if the first block ready is higher than `from`. + /// For each returned block hash `clear_queued` must be called at some later stage. + pub fn ready_blocks(&mut self, from: NumberFor) -> Vec> { + let mut ready = Vec::new(); let mut prev = from; - for (start, range_data) in &mut self.blocks { - match range_data { - BlockRangeState::Complete(blocks) if *start <= prev => { - prev = *start + (blocks.len() as u32).into(); - // Remove all elements from `blocks` and add them to `drained` - drained.append(blocks); - ranges.push(*start); + for (&start, range_data) in &mut self.blocks { + if start > prev { + break + } + let len = match range_data { + BlockRangeState::Complete(blocks) => { + let len = (blocks.len() as u32).into(); + prev = start + len; + if let Some(BlockData { block, .. }) = blocks.first() { + self.queued_blocks + .insert(block.hash, (start, start + (blocks.len() as u32).into())); + } + // Remove all elements from `blocks` and add them to `ready` + ready.append(blocks); + len }, + BlockRangeState::Queued { .. } => continue, _ => break, - } + }; + *range_data = BlockRangeState::Queued { len }; } + trace!(target: "sync", "{} blocks ready for import", ready.len()); + ready + } - for r in ranges { - self.blocks.remove(&r); + pub fn clear_queued(&mut self, hash: &B::Hash) { + if let Some((from, to)) = self.queued_blocks.remove(hash) { + let mut block_num = from; + while block_num < to { + self.blocks.remove(&block_num); + block_num += One::one(); + } + trace!(target: "sync", "Cleared blocks from {:?} to {:?}", from, to); } - trace!(target: "sync", "Drained {} blocks from {:?}", drained.len(), from); - drained } pub fn clear_peer_download(&mut self, who: &PeerId) { @@ -203,7 +231,7 @@ impl BlockCollection { { *downloading -= 1; false - } + }, Some(&mut BlockRangeState::Downloading { .. }) => true, _ => false, }; @@ -217,7 +245,8 @@ impl BlockCollection { #[cfg(test)] mod test { use super::{BlockCollection, BlockData, BlockRangeState}; - use crate::{protocol::message, PeerId}; + use libp2p::PeerId; + use sc_network_common::sync::message; use sp_core::H256; use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper}; @@ -261,66 +290,66 @@ mod test { let peer2 = PeerId::random(); let blocks = generate_blocks(150); - assert_eq!(bc.needed_blocks(peer0.clone(), 40, 150, 0, 1, 200), Some(1..41)); - assert_eq!(bc.needed_blocks(peer1.clone(), 40, 150, 0, 1, 200), Some(41..81)); - assert_eq!(bc.needed_blocks(peer2.clone(), 40, 150, 0, 1, 200), Some(81..121)); + assert_eq!(bc.needed_blocks(peer0, 40, 150, 0, 1, 200), Some(1..41)); + assert_eq!(bc.needed_blocks(peer1, 40, 150, 0, 1, 200), Some(41..81)); + assert_eq!(bc.needed_blocks(peer2, 40, 150, 0, 1, 200), Some(81..121)); bc.clear_peer_download(&peer1); - bc.insert(41, blocks[41..81].to_vec(), peer1.clone()); - assert_eq!(bc.drain(1), vec![]); - assert_eq!(bc.needed_blocks(peer1.clone(), 40, 150, 0, 1, 200), Some(121..151)); + bc.insert(41, blocks[41..81].to_vec(), peer1); + assert_eq!(bc.ready_blocks(1), vec![]); + assert_eq!(bc.needed_blocks(peer1, 40, 150, 0, 1, 200), Some(121..151)); bc.clear_peer_download(&peer0); - bc.insert(1, blocks[1..11].to_vec(), peer0.clone()); + bc.insert(1, blocks[1..11].to_vec(), peer0); - assert_eq!(bc.needed_blocks(peer0.clone(), 40, 150, 0, 1, 200), Some(11..41)); + assert_eq!(bc.needed_blocks(peer0, 40, 150, 0, 1, 200), Some(11..41)); assert_eq!( - bc.drain(1), + bc.ready_blocks(1), blocks[1..11] .iter() - .map(|b| BlockData { block: b.clone(), origin: Some(peer0.clone()) }) + .map(|b| BlockData { block: b.clone(), origin: Some(peer0) }) .collect::>() ); bc.clear_peer_download(&peer0); - bc.insert(11, blocks[11..41].to_vec(), peer0.clone()); + bc.insert(11, blocks[11..41].to_vec(), peer0); - let drained = bc.drain(12); + let ready = bc.ready_blocks(12); assert_eq!( - drained[..30], + ready[..30], blocks[11..41] .iter() - .map(|b| BlockData { block: b.clone(), origin: Some(peer0.clone()) }) + .map(|b| BlockData { block: b.clone(), origin: Some(peer0) }) .collect::>()[..] ); assert_eq!( - drained[30..], + ready[30..], blocks[41..81] .iter() - .map(|b| BlockData { block: b.clone(), origin: Some(peer1.clone()) }) + .map(|b| BlockData { block: b.clone(), origin: Some(peer1) }) .collect::>()[..] ); bc.clear_peer_download(&peer2); - assert_eq!(bc.needed_blocks(peer2.clone(), 40, 150, 80, 1, 200), Some(81..121)); + assert_eq!(bc.needed_blocks(peer2, 40, 150, 80, 1, 200), Some(81..121)); bc.clear_peer_download(&peer2); - bc.insert(81, blocks[81..121].to_vec(), peer2.clone()); + bc.insert(81, blocks[81..121].to_vec(), peer2); bc.clear_peer_download(&peer1); - bc.insert(121, blocks[121..150].to_vec(), peer1.clone()); + bc.insert(121, blocks[121..150].to_vec(), peer1); - assert_eq!(bc.drain(80), vec![]); - let drained = bc.drain(81); + assert_eq!(bc.ready_blocks(80), vec![]); + let ready = bc.ready_blocks(81); assert_eq!( - drained[..40], + ready[..40], blocks[81..121] .iter() - .map(|b| BlockData { block: b.clone(), origin: Some(peer2.clone()) }) + .map(|b| BlockData { block: b.clone(), origin: Some(peer2) }) .collect::>()[..] ); assert_eq!( - drained[40..], + ready[40..], blocks[121..150] .iter() - .map(|b| BlockData { block: b.clone(), origin: Some(peer1.clone()) }) + .map(|b| BlockData { block: b.clone(), origin: Some(peer1) }) .collect::>()[..] ); } @@ -336,11 +365,70 @@ mod test { bc.blocks.insert(114305, BlockRangeState::Complete(blocks)); let peer0 = PeerId::random(); - assert_eq!(bc.needed_blocks(peer0.clone(), 128, 10000, 000, 1, 200), Some(1..100)); - assert_eq!(bc.needed_blocks(peer0.clone(), 128, 10000, 600, 1, 200), None); // too far ahead + assert_eq!(bc.needed_blocks(peer0, 128, 10000, 000, 1, 200), Some(1..100)); + assert_eq!(bc.needed_blocks(peer0, 128, 10000, 600, 1, 200), None); // too far ahead assert_eq!( - bc.needed_blocks(peer0.clone(), 128, 10000, 600, 1, 200000), + bc.needed_blocks(peer0, 128, 10000, 600, 1, 200000), Some(100 + 128..100 + 128 + 128) ); } + + #[test] + fn no_duplicate_requests_on_fork() { + let mut bc = BlockCollection::new(); + assert!(is_empty(&bc)); + let peer = PeerId::random(); + + let blocks = generate_blocks(10); + + // count = 5, peer_best = 50, common = 39, max_parallel = 0, max_ahead = 200 + assert_eq!(bc.needed_blocks(peer, 5, 50, 39, 0, 200), Some(40..45)); + + // got a response on the request for `40..45` + bc.clear_peer_download(&peer); + bc.insert(40, blocks[..5].to_vec(), peer); + + // our "node" started on a fork, with its current best = 47, which is > common + let ready = bc.ready_blocks(48); + assert_eq!( + ready, + blocks[..5] + .iter() + .map(|b| BlockData { block: b.clone(), origin: Some(peer) }) + .collect::>() + ); + + assert_eq!(bc.needed_blocks(peer, 5, 50, 39, 0, 200), Some(45..50)); + } + + #[test] + fn clear_queued_subsequent_ranges() { + let mut bc = BlockCollection::new(); + assert!(is_empty(&bc)); + let peer = PeerId::random(); + + let blocks = generate_blocks(10); + + // Request 2 ranges + assert_eq!(bc.needed_blocks(peer, 5, 50, 39, 0, 200), Some(40..45)); + assert_eq!(bc.needed_blocks(peer, 5, 50, 39, 0, 200), Some(45..50)); + + // got a response on the request for `40..50` + bc.clear_peer_download(&peer); + bc.insert(40, blocks.to_vec(), peer); + + // request any blocks starting from 1000 or lower. + let ready = bc.ready_blocks(1000); + assert_eq!( + ready, + blocks + .iter() + .map(|b| BlockData { block: b.clone(), origin: Some(peer) }) + .collect::>() + ); + + bc.clear_queued(&blocks[0].hash); + assert!(bc.blocks.is_empty()); + assert!(bc.queued_blocks.is_empty()); + } } diff --git a/client/network/src/protocol/sync/extra_requests.rs b/client/network/sync/src/extra_requests.rs similarity index 94% rename from client/network/src/protocol/sync/extra_requests.rs rename to client/network/sync/src/extra_requests.rs index 226762b9658d2..0506bd542ff3b 100644 --- a/client/network/src/protocol/sync/extra_requests.rs +++ b/client/network/sync/src/extra_requests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,10 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::protocol::sync::{PeerSync, PeerSyncState}; +use crate::{PeerSync, PeerSyncState}; use fork_tree::ForkTree; use libp2p::PeerId; use log::{debug, trace, warn}; +use sc_network_common::sync::metrics::Metrics; use sp_blockchain::Error as ClientError; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use std::{ @@ -31,7 +32,7 @@ use std::{ const EXTRA_RETRY_WAIT: Duration = Duration::from_secs(10); /// Pending extra data request for the given block (hash and number). -pub(crate) type ExtraRequest = (::Hash, NumberFor); +type ExtraRequest = (::Hash, NumberFor); /// Manages pending block extra data (e.g. justification) requests. /// @@ -56,15 +57,6 @@ pub(crate) struct ExtraRequests { request_type_name: &'static str, } -#[derive(Debug)] -pub(crate) struct Metrics { - pub(crate) pending_requests: u32, - pub(crate) active_requests: u32, - pub(crate) importing_requests: u32, - pub(crate) failed_requests: u32, - _priv: (), -} - impl ExtraRequests { pub(crate) fn new(request_type_name: &'static str) -> Self { Self { @@ -108,7 +100,7 @@ impl ExtraRequests { // ignore the `Revert` error. }, Err(err) => { - debug!(target: "sync", "Failed to insert request {:?} into tree: {:?}", request, err); + debug!(target: "sync", "Failed to insert request {:?} into tree: {}", request, err); }, _ => (), } @@ -173,9 +165,7 @@ impl ExtraRequests { } if best_finalized_number > self.best_seen_finalized_number { - // normally we'll receive finality notifications for every block => finalize would be - // enough but if many blocks are finalized at once, some notifications may be omitted - // => let's use finalize_with_ancestors here + // we receive finality notification only for the finalized branch head. match self.tree.finalize_with_ancestors( best_finalized_hash, best_finalized_number, @@ -255,13 +245,11 @@ impl ExtraRequests { /// Get some key metrics. pub(crate) fn metrics(&self) -> Metrics { - use std::convert::TryInto; Metrics { pending_requests: self.pending_requests.len().try_into().unwrap_or(std::u32::MAX), active_requests: self.active_requests.len().try_into().unwrap_or(std::u32::MAX), failed_requests: self.failed_requests.len().try_into().unwrap_or(std::u32::MAX), importing_requests: self.importing_requests.len().try_into().unwrap_or(std::u32::MAX), - _priv: (), } } } @@ -330,7 +318,7 @@ impl<'a, B: BlockT> Matcher<'a, B> { { continue } - self.extras.active_requests.insert(peer.clone(), request); + self.extras.active_requests.insert(*peer, request); trace!(target: "sync", "Sending {} request to {:?} for {:?}", @@ -355,7 +343,7 @@ impl<'a, B: BlockT> Matcher<'a, B> { #[cfg(test)] mod tests { use super::*; - use crate::protocol::sync::PeerSync; + use crate::PeerSync; use quickcheck::{Arbitrary, Gen, QuickCheck}; use sp_blockchain::Error as ClientError; use sp_test_primitives::{Block, BlockNumber, Hash}; @@ -458,16 +446,12 @@ mod tests { PeerSyncState::DownloadingJustification(r.0); } - let active = requests - .active_requests - .iter() - .map(|(p, &r)| (p.clone(), r)) - .collect::>(); + let active = requests.active_requests.iter().map(|(&p, &r)| (p, r)).collect::>(); for (peer, req) in &active { assert!(requests.failed_requests.get(req).is_none()); assert!(!requests.pending_requests.contains(req)); - assert!(requests.on_response::<()>(peer.clone(), None).is_none()); + assert!(requests.on_response::<()>(*peer, None).is_none()); assert!(requests.pending_requests.contains(req)); assert_eq!( 1, diff --git a/client/network/src/protocol/sync.rs b/client/network/sync/src/lib.rs similarity index 57% rename from client/network/src/protocol/sync.rs rename to client/network/sync/src/lib.rs index 07f5f76fce7f2..75eda91219ec8 100644 --- a/client/network/src/protocol/sync.rs +++ b/client/network/sync/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -28,20 +28,59 @@ //! the network, or whenever a block has been successfully verified, call the appropriate method in //! order to update it. +pub mod block_request_handler; +pub mod blocks; +pub mod mock; +mod schema; +pub mod service; +pub mod state; +pub mod state_request_handler; +#[cfg(test)] +mod tests; +pub mod warp; +pub mod warp_request_handler; + use crate::{ - protocol::message::{self, BlockAnnounce, BlockAttributes, BlockRequest, BlockResponse}, + blocks::BlockCollection, schema::v1::{StateRequest, StateResponse}, + service::chain_sync::{ChainSyncInterfaceHandle, ToServiceCommand}, + state::StateSync, + warp::{WarpProofImportResult, WarpSync}, }; -use blocks::BlockCollection; -use codec::Encode; -use either::Either; +use codec::{Decode, DecodeAll, Encode}; use extra_requests::ExtraRequests; -use futures::{stream::FuturesUnordered, task::Poll, Future, FutureExt, StreamExt}; -use libp2p::PeerId; +use futures::{ + channel::oneshot, stream::FuturesUnordered, task::Poll, Future, FutureExt, StreamExt, +}; +use libp2p::{request_response::OutboundFailure, PeerId}; use log::{debug, error, info, trace, warn}; -use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; +use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; +use prost::Message; +use sc_client_api::{BlockBackend, ProofProvider}; +use sc_consensus::{ + import_queue::ImportQueueService, BlockImportError, BlockImportStatus, IncomingBlock, +}; +use sc_network_common::{ + config::{ + NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, ProtocolId, SetConfig, + }, + protocol::{role::Roles, ProtocolName}, + request_responses::{IfDisconnected, RequestFailure}, + sync::{ + message::{ + BlockAnnounce, BlockAnnouncesHandshake, BlockAttributes, BlockData, BlockRequest, + BlockResponse, Direction, FromBlock, + }, + warp::{EncodedProof, WarpProofRequest, WarpSyncPhase, WarpSyncProgress, WarpSyncProvider}, + BadPeer, ChainSync as ChainSyncT, ImportResult, Metrics, OnBlockData, OnBlockJustification, + OnStateData, OpaqueBlockRequest, OpaqueBlockResponse, OpaqueStateRequest, + OpaqueStateResponse, PeerInfo, PeerRequest, PollBlockAnnounceValidation, SyncMode, + SyncState, SyncStatus, + }, +}; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver}; use sp_arithmetic::traits::Saturating; -use sp_blockchain::{Error as ClientError, HeaderMetadata}; +use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; use sp_consensus::{ block_validation::{BlockAnnounceValidator, Validation}, BlockOrigin, BlockStatus, @@ -54,20 +93,16 @@ use sp_runtime::{ }, EncodedJustification, Justifications, }; -use state::StateSync; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, - fmt, + iter, ops::Range, pin::Pin, sync::Arc, }; -use warp::{WarpProofRequest, WarpSync, WarpSyncProvider}; +use warp::TargetBlockImportResult; -mod blocks; mod extra_requests; -mod state; -mod warp; /// Maximum blocks to request in a single packet. const MAX_BLOCKS_TO_REQUEST: usize = 64; @@ -106,6 +141,9 @@ const MAJOR_SYNC_BLOCKS: u8 = 5; /// Number of peers that need to be connected before warp sync is started. const MIN_PEERS_TO_START_WARP_SYNC: usize = 3; +/// Maximum allowed size for a block announce. +const MAX_BLOCK_ANNOUNCE_SIZE: u64 = 1024 * 1024; + mod rep { use sc_peerset::ReputationChange as Rep; /// Reputation change when a peer sent us a message that led to a @@ -139,14 +177,26 @@ mod rep { /// Peer response data does not have requested bits. pub const BAD_RESPONSE: Rep = Rep::new(-(1 << 12), "Incomplete response"); + + /// Reputation change when a peer doesn't respond in time to our messages. + pub const TIMEOUT: Rep = Rep::new(-(1 << 10), "Request timeout"); + + /// Peer is on unsupported protocol version. + pub const BAD_PROTOCOL: Rep = Rep::new_fatal("Unsupported protocol"); + + /// Reputation change when a peer refuses a request. + pub const REFUSED: Rep = Rep::new(-(1 << 10), "Request refused"); + + /// We received a message that failed to decode. + pub const BAD_MESSAGE: Rep = Rep::new(-(1 << 12), "Bad message"); } -enum PendingRequests { +enum AllowedRequests { Some(HashSet), All, } -impl PendingRequests { +impl AllowedRequests { fn add(&mut self, id: &PeerId) { if let Self::Some(ref mut set) = self { set.insert(*id); @@ -174,19 +224,67 @@ impl PendingRequests { Self::All => false, } } + + fn clear(&mut self) { + std::mem::take(self); + } } -impl Default for PendingRequests { +impl Default for AllowedRequests { fn default() -> Self { Self::Some(HashSet::default()) } } +struct SyncingMetrics { + pub import_queue_blocks_submitted: Counter, + pub import_queue_justifications_submitted: Counter, +} + +impl SyncingMetrics { + fn register(registry: &Registry) -> Result { + Ok(Self { + import_queue_blocks_submitted: register( + Counter::new( + "substrate_sync_import_queue_blocks_submitted", + "Number of blocks submitted to the import queue.", + )?, + registry, + )?, + import_queue_justifications_submitted: register( + Counter::new( + "substrate_sync_import_queue_justifications_submitted", + "Number of justifications submitted to the import queue.", + )?, + registry, + )?, + }) + } +} + +struct GapSync { + blocks: BlockCollection, + best_queued_number: NumberFor, + target: NumberFor, +} + +type PendingResponse = Pin< + Box< + dyn Future< + Output = ( + PeerId, + PeerRequest, + Result, RequestFailure>, oneshot::Canceled>, + ), + > + Send, + >, +>; + /// The main data structure which contains all the state for a chains /// active syncing strategy. -pub struct ChainSync { +pub struct ChainSync { /// Chain client. - client: Arc>, + client: Arc, /// The active peers that we are using to sync and their PeerSync status peers: HashMap>, /// A `BlockCollection` of blocks that are being downloaded from peers @@ -205,7 +303,7 @@ pub struct ChainSync { /// Fork sync targets. fork_targets: HashMap>, /// A set of peers for which there might be potential block requests - pending_requests: PendingRequests, + allowed_requests: AllowedRequests, /// A type to check incoming block announcements. block_announce_validator: Box + Send>, /// Maximum number of peers to ask the same blocks in parallel. @@ -218,14 +316,34 @@ pub struct ChainSync { /// Stats per peer about the number of concurrent block announce validations. block_announce_validation_per_peer_stats: HashMap, /// State sync in progress, if any. - state_sync: Option>, + state_sync: Option>, /// Warp sync in progress, if any. - warp_sync: Option>, + warp_sync: Option>, /// Warp sync provider. warp_sync_provider: Option>>, /// Enable importing existing blocks. This is used used after the state download to /// catch up to the latest state while re-importing blocks. import_existing: bool, + /// Gap download process. + gap_sync: Option>, + /// Channel for receiving service commands + service_rx: TracingUnboundedReceiver>, + /// Handle for communicating with `NetworkService` + network_service: service::network::NetworkServiceHandle, + /// Protocol name used for block announcements + block_announce_protocol_name: ProtocolName, + /// Protocol name used to send out block requests + block_request_protocol_name: ProtocolName, + /// Protocol name used to send out state requests + state_request_protocol_name: ProtocolName, + /// Protocol name used to send out warp sync requests + warp_sync_protocol_name: Option, + /// Pending responses + pending_responses: FuturesUnordered>, + /// Handle to import queue. + import_queue: Box>, + /// Metrics. + metrics: Option, } /// All the data we have about a Peer that we are trying to sync with @@ -261,15 +379,6 @@ impl PeerSync { } } -/// The sync status of a peer we are trying to sync with -#[derive(Debug)] -pub struct PeerInfo { - /// Their best block hash. - pub best_hash: B::Hash, - /// Their best block number. - pub best_number: NumberFor, -} - struct ForkTarget { number: NumberFor, parent_hash: Option, @@ -298,6 +407,10 @@ pub enum PeerSyncState { DownloadingState, /// Downloading warp proof. DownloadingWarpProof, + /// Downloading warp sync target block. + DownloadingWarpTargetBlock, + /// Actively downloading block history after warp sync. + DownloadingGap(NumberFor), } impl PeerSyncState { @@ -306,159 +419,6 @@ impl PeerSyncState { } } -/// Reported sync state. -#[derive(Clone, Eq, PartialEq, Debug)] -pub enum SyncState { - /// Initial sync is complete, keep-up sync is active. - Idle, - /// Actively catching up with the chain. - Downloading, -} - -/// Reported state download progress. -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct StateDownloadProgress { - /// Estimated download percentage. - pub percentage: u32, - /// Total state size in bytes downloaded so far. - pub size: u64, -} - -/// Reported warp sync phase. -#[derive(Clone, Eq, PartialEq, Debug)] -pub enum WarpSyncPhase { - /// Waiting for peers to connect. - AwaitingPeers, - /// Downloading and verifying grandpa warp proofs. - DownloadingWarpProofs, - /// Downloading state data. - DownloadingState, - /// Importing state. - ImportingState, -} - -impl fmt::Display for WarpSyncPhase { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::AwaitingPeers => write!(f, "Waiting for peers"), - Self::DownloadingWarpProofs => write!(f, "Downloading finality proofs"), - Self::DownloadingState => write!(f, "Downloading state"), - Self::ImportingState => write!(f, "Importing state"), - } - } -} - -/// Reported warp sync progress. -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct WarpSyncProgress { - /// Estimated download percentage. - pub phase: WarpSyncPhase, - /// Total bytes downloaded so far. - pub total_bytes: u64, -} - -/// Syncing status and statistics. -#[derive(Clone)] -pub struct Status { - /// Current global sync state. - pub state: SyncState, - /// Target sync block number. - pub best_seen_block: Option>, - /// Number of peers participating in syncing. - pub num_peers: u32, - /// Number of blocks queued for import - pub queued_blocks: u32, - /// State sync status in progress, if any. - pub state_sync: Option, - /// Warp sync in progress, if any. - pub warp_sync: Option, -} - -/// A peer did not behave as expected and should be reported. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BadPeer(pub PeerId, pub sc_peerset::ReputationChange); - -impl fmt::Display for BadPeer { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Bad peer {}; Reputation change: {:?}", self.0, self.1) - } -} - -impl std::error::Error for BadPeer {} - -/// Result of [`ChainSync::on_block_data`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum OnBlockData { - /// The block should be imported. - Import(BlockOrigin, Vec>), - /// A new block request needs to be made to the given peer. - Request(PeerId, BlockRequest), -} - -impl OnBlockData { - /// Returns `self` as request. - #[cfg(test)] - fn into_request(self) -> Option<(PeerId, BlockRequest)> { - if let Self::Request(peer, req) = self { - Some((peer, req)) - } else { - None - } - } -} - -/// Result of [`ChainSync::on_state_data`]. -#[derive(Debug)] -pub enum OnStateData { - /// The block and state that should be imported. - Import(BlockOrigin, IncomingBlock), - /// A new state request needs to be made to the given peer. - Request(PeerId, StateRequest), -} - -/// Result of [`ChainSync::on_warp_sync_data`]. -#[derive(Debug)] -pub enum OnWarpSyncData { - /// Warp proof request is issued. - WarpProofRequest(PeerId, warp::WarpProofRequest), - /// A new state request needs to be made to the given peer. - StateRequest(PeerId, StateRequest), -} - -/// Result of [`ChainSync::poll_block_announce_validation`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PollBlockAnnounceValidation { - /// The announcement failed at validation. - /// - /// The peer reputation should be decreased. - Failure { - /// Who sent the processed block announcement? - who: PeerId, - /// Should the peer be disconnected? - disconnect: bool, - }, - /// The announcement does not require further handling. - Nothing { - /// Who sent the processed block announcement? - who: PeerId, - /// Was this their new best block? - is_best: bool, - /// The announcement. - announce: BlockAnnounce, - }, - /// The announcement header should be imported. - ImportHeader { - /// Who sent the processed block announcement? - who: PeerId, - /// Was this their new best block? - is_best: bool, - /// The announcement. - announce: BlockAnnounce, - }, - /// The block announcement should be skipped. - Skip, -} - /// Result of [`ChainSync::block_announce_validation`]. #[derive(Debug, Clone, PartialEq, Eq)] enum PreValidateBlockAnnounce { @@ -494,28 +454,6 @@ enum PreValidateBlockAnnounce { Skip, } -/// Result of [`ChainSync::on_block_justification`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum OnBlockJustification { - /// The justification needs no further handling. - Nothing, - /// The justification should be imported. - Import { peer: PeerId, hash: B::Hash, number: NumberFor, justifications: Justifications }, -} - -/// Operation mode. -#[derive(Debug, PartialEq, Eq)] -pub enum SyncMode { - // Sync headers only - Light, - // Sync headers and block bodies - Full, - // Sync headers and the last finalied state - LightState { storage_chain_mode: bool, skip_proofs: bool }, - // Warp sync mode. - Warp, -} - /// Result of [`ChainSync::has_slot_for_block_announce_validation`]. enum HasSlotForBlockAnnounceValidation { /// Yes, there is a slot for the block announce validation. @@ -526,81 +464,40 @@ enum HasSlotForBlockAnnounceValidation { MaximumPeerSlotsReached, } -impl ChainSync { - /// Create a new instance. - pub fn new( - mode: SyncMode, - client: Arc>, - block_announce_validator: Box + Send>, - max_parallel_downloads: u32, - warp_sync_provider: Option>>, - ) -> Result { - let mut sync = Self { - client, - peers: HashMap::new(), - blocks: BlockCollection::new(), - best_queued_hash: Default::default(), - best_queued_number: Zero::zero(), - extra_justifications: ExtraRequests::new("justification"), - mode, - queue_blocks: Default::default(), - fork_targets: Default::default(), - pending_requests: Default::default(), - block_announce_validator, - max_parallel_downloads, - downloaded_blocks: 0, - block_announce_validation: Default::default(), - block_announce_validation_per_peer_stats: Default::default(), - state_sync: None, - warp_sync: None, - warp_sync_provider, - import_existing: false, - }; - sync.reset_sync_start_point()?; - Ok(sync) - } - - fn required_block_attributes(&self) -> BlockAttributes { - match self.mode { - SyncMode::Full => - BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION | BlockAttributes::BODY, - SyncMode::Light => BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION, - SyncMode::LightState { storage_chain_mode: false, .. } | SyncMode::Warp => - BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION | BlockAttributes::BODY, - SyncMode::LightState { storage_chain_mode: true, .. } => - BlockAttributes::HEADER | - BlockAttributes::JUSTIFICATION | - BlockAttributes::INDEXED_BODY, - } - } - - fn skip_execution(&self) -> bool { - match self.mode { - SyncMode::Full => false, - SyncMode::Light => true, - SyncMode::LightState { .. } => true, - SyncMode::Warp => true, - } - } - - /// Returns the state of the sync of the given peer. - /// - /// Returns `None` if the peer is unknown. - pub fn peer_info(&self, who: &PeerId) -> Option> { +impl ChainSyncT for ChainSync +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ + fn peer_info(&self, who: &PeerId) -> Option> { self.peers .get(who) .map(|p| PeerInfo { best_hash: p.best_hash, best_number: p.best_number }) } /// Returns the current sync status. - pub fn status(&self) -> Status { - let best_seen = self.peers.values().map(|p| p.best_number).max(); - let sync_state = if let Some(n) = best_seen { + fn status(&self) -> SyncStatus { + let median_seen = self.median_seen(); + let best_seen_block = + median_seen.and_then(|median| (median > self.best_queued_number).then_some(median)); + let sync_state = if let Some(target) = median_seen { // A chain is classified as downloading if the provided best block is - // more than `MAJOR_SYNC_BLOCKS` behind the best queued block. - if n > self.best_queued_number && n - self.best_queued_number > MAJOR_SYNC_BLOCKS.into() - { - SyncState::Downloading + // more than `MAJOR_SYNC_BLOCKS` behind the best block or as importing + // if the same can be said about queued blocks. + let best_block = self.client.info().best_number; + if target > best_block && target - best_block > MAJOR_SYNC_BLOCKS.into() { + // If target is not queued, we're downloading, otherwise importing. + if target > self.best_queued_number { + SyncState::Downloading { target } + } else { + SyncState::Importing { target } + } } else { SyncState::Idle } @@ -608,16 +505,20 @@ impl ChainSync { SyncState::Idle }; - let warp_sync_progress = match (&self.warp_sync, &self.mode) { - (None, SyncMode::Warp) => + let warp_sync_progress = match (&self.warp_sync, &self.mode, &self.gap_sync) { + (_, _, Some(gap_sync)) => Some(WarpSyncProgress { + phase: WarpSyncPhase::DownloadingBlocks(gap_sync.best_queued_number), + total_bytes: 0, + }), + (None, SyncMode::Warp, _) => Some(WarpSyncProgress { phase: WarpSyncPhase::AwaitingPeers, total_bytes: 0 }), - (Some(sync), _) => Some(sync.progress()), + (Some(sync), _, _) => Some(sync.progress()), _ => None, }; - Status { + SyncStatus { state: sync_state, - best_seen_block: best_seen, + best_seen_block, num_peers: self.peers.len() as u32, queued_blocks: self.queue_blocks.len() as u32, state_sync: self.state_sync.as_ref().map(|s| s.progress()), @@ -625,24 +526,26 @@ impl ChainSync { } } - /// Number of active forks requests. This includes - /// requests that are pending or could be issued right away. - pub fn num_sync_requests(&self) -> usize { + fn num_sync_requests(&self) -> usize { self.fork_targets .values() .filter(|f| f.number <= self.best_queued_number) .count() } - /// Number of downloaded blocks. - pub fn num_downloaded_blocks(&self) -> usize { + fn num_downloaded_blocks(&self) -> usize { self.downloaded_blocks } - /// Handle a new connected peer. - /// - /// Call this method whenever we connect to a new peer. - pub fn new_peer( + fn num_peers(&self) -> usize { + self.peers.len() + } + + fn num_active_peers(&self) -> usize { + self.pending_responses.len() + } + + fn new_peer( &mut self, who: PeerId, best_hash: B::Hash, @@ -651,7 +554,7 @@ impl ChainSync { // There is nothing sync can get from the node that has no blockchain data. match self.block_status(&best_hash) { Err(e) => { - debug!(target:"sync", "Error reading blockchain: {:?}", e); + debug!(target:"sync", "Error reading blockchain: {}", e); Err(BadPeer(who, rep::BLOCKCHAIN_READ_ERROR)) }, Ok(BlockStatus::KnownBad) => { @@ -674,7 +577,7 @@ impl ChainSync { self.best_queued_number ); self.peers.insert( - who.clone(), + who, PeerSync { peer_id: who, common_number: self.best_queued_number, @@ -686,17 +589,6 @@ impl ChainSync { return Ok(None) } - if let SyncMode::Warp = &self.mode { - if self.peers.len() >= MIN_PEERS_TO_START_WARP_SYNC && self.warp_sync.is_none() - { - log::debug!(target: "sync", "Starting warp state sync."); - if let Some(provider) = &self.warp_sync_provider { - self.warp_sync = - Some(WarpSync::new(self.client.clone(), provider.clone())); - } - } - } - // If we are at genesis, just start downloading. let (state, req) = if self.best_queued_number.is_zero() { debug!( @@ -727,7 +619,7 @@ impl ChainSync { ) }; - self.pending_requests.add(&who); + self.allowed_requests.add(&who); self.peers.insert( who, PeerSync { @@ -739,6 +631,17 @@ impl ChainSync { }, ); + if let SyncMode::Warp = &self.mode { + if self.peers.len() >= MIN_PEERS_TO_START_WARP_SYNC && self.warp_sync.is_none() + { + log::debug!(target: "sync", "Starting warp state sync."); + if let Some(provider) = &self.warp_sync_provider { + self.warp_sync = + Some(WarpSync::new(self.client.clone(), provider.clone())); + } + } + } + Ok(req) }, Ok(BlockStatus::Queued) | @@ -760,33 +663,28 @@ impl ChainSync { state: PeerSyncState::Available, }, ); - self.pending_requests.add(&who); + self.allowed_requests.add(&who); Ok(None) }, } } - /// Signal that a new best block has been imported. - /// `ChainSync` state with that information. - pub fn update_chain_info(&mut self, best_hash: &B::Hash, best_number: NumberFor) { + fn update_chain_info(&mut self, best_hash: &B::Hash, best_number: NumberFor) { self.on_block_queued(best_hash, best_number); } - /// Schedule a justification request for the given block. - pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { + fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { let client = &self.client; self.extra_justifications .schedule((*hash, number), |base, block| is_descendent_of(&**client, base, block)) } - /// Clear all pending justification requests. - pub fn clear_justification_requests(&mut self) { + fn clear_justification_requests(&mut self) { self.extra_justifications.reset(); } - /// Request syncing for the given block from given set of peers. // The implementation is similar to on_block_announce with unknown parent hash. - pub fn set_sync_fork_request( + fn set_sync_fork_request( &mut self, mut peers: Vec, hash: &B::Hash, @@ -798,7 +696,7 @@ impl ChainSync { .iter() // Only request blocks from peers who are ahead or on a par. .filter(|(_, peer)| peer.best_number >= number) - .map(|(id, _)| id.clone()) + .map(|(id, _)| *id) .collect(); debug!( @@ -811,7 +709,7 @@ impl ChainSync { debug!(target: "sync", "Explicit sync request for block {:?} with {:?}", hash, peers); } - if self.is_known(&hash) { + if self.is_known(hash) { debug!(target: "sync", "Refusing to sync known hash {:?}", hash); return } @@ -827,7 +725,7 @@ impl ChainSync { peer.best_number = number; peer.best_hash = *hash; } - self.pending_requests.add(peer_id); + self.allowed_requests.add(peer_id); } } @@ -838,217 +736,21 @@ impl ChainSync { .extend(peers); } - /// Get an iterator over all scheduled justification requests. - pub fn justification_requests( + fn on_block_data( &mut self, - ) -> impl Iterator)> + '_ { - let peers = &mut self.peers; - let mut matcher = self.extra_justifications.matcher(); - std::iter::from_fn(move || { - if let Some((peer, request)) = matcher.next(&peers) { - peers - .get_mut(&peer) - .expect( - "`Matcher::next` guarantees the `PeerId` comes from the given peers; qed", - ) - .state = PeerSyncState::DownloadingJustification(request.0); - let req = message::generic::BlockRequest { - id: 0, - fields: BlockAttributes::JUSTIFICATION, - from: message::FromBlock::Hash(request.0), - to: None, - direction: message::Direction::Ascending, - max: Some(1), - }; - Some((peer, req)) - } else { - None - } - }) - } - - /// Get an iterator over all block requests of all peers. - pub fn block_requests(&mut self) -> impl Iterator)> + '_ { - if self.pending_requests.is_empty() || self.state_sync.is_some() || self.warp_sync.is_some() - { - return Either::Left(std::iter::empty()) - } - if self.queue_blocks.len() > MAX_IMPORTING_BLOCKS { - trace!(target: "sync", "Too many blocks in the queue."); - return Either::Left(std::iter::empty()) - } - let major_sync = self.status().state == SyncState::Downloading; - let attrs = self.required_block_attributes(); - let blocks = &mut self.blocks; - let fork_targets = &mut self.fork_targets; - let last_finalized = - std::cmp::min(self.best_queued_number, self.client.info().finalized_number); - let best_queued = self.best_queued_number; - let client = &self.client; - let queue = &self.queue_blocks; - let pending_requests = self.pending_requests.take(); - let max_parallel = if major_sync { 1 } else { self.max_parallel_downloads }; - let iter = self.peers.iter_mut().filter_map(move |(id, peer)| { - if !peer.state.is_available() || !pending_requests.contains(id) { - return None - } - - // If our best queued is more than `MAX_BLOCKS_TO_LOOK_BACKWARDS` blocks away from the - // common number, the peer best number is higher than our best queued and the common - // number is smaller than the last finalized block number, we should do an ancestor - // search to find a better common block. If the queue is full we wait till all blocks - // are imported though. - if best_queued.saturating_sub(peer.common_number) > MAX_BLOCKS_TO_LOOK_BACKWARDS.into() && - best_queued < peer.best_number && - peer.common_number < last_finalized && - queue.len() <= MAJOR_SYNC_BLOCKS.into() - { - trace!( - target: "sync", - "Peer {:?} common block {} too far behind of our best {}. Starting ancestry search.", - id, - peer.common_number, - best_queued, - ); - let current = std::cmp::min(peer.best_number, best_queued); - peer.state = PeerSyncState::AncestorSearch { - current, - start: best_queued, - state: AncestorSearchState::ExponentialBackoff(One::one()), - }; - Some((id, ancestry_request::(current))) - } else if let Some((range, req)) = peer_block_request( - id, - peer, - blocks, - attrs, - max_parallel, - last_finalized, - best_queued, - ) { - peer.state = PeerSyncState::DownloadingNew(range.start); - trace!( - target: "sync", - "New block request for {}, (best:{}, common:{}) {:?}", - id, - peer.best_number, - peer.common_number, - req, - ); - Some((id, req)) - } else if let Some((hash, req)) = - fork_sync_request(id, fork_targets, best_queued, last_finalized, attrs, |hash| { - if queue.contains(hash) { - BlockStatus::Queued - } else { - client.block_status(&BlockId::Hash(*hash)).unwrap_or(BlockStatus::Unknown) - } - }) { - trace!(target: "sync", "Downloading fork {:?} from {}", hash, id); - peer.state = PeerSyncState::DownloadingStale(hash); - Some((id, req)) - } else { - None - } - }); - Either::Right(iter) - } - - /// Get a state request, if any. - pub fn state_request(&mut self) -> Option<(PeerId, StateRequest)> { - if self.peers.iter().any(|(_, peer)| peer.state == PeerSyncState::DownloadingState) { - // Only one pending state request is allowed. - return None - } - if let Some(sync) = &self.state_sync { - if sync.is_complete() { - return None + who: &PeerId, + request: Option>, + response: BlockResponse, + ) -> Result, BadPeer> { + self.downloaded_blocks += response.blocks.len(); + let mut gap = false; + let new_blocks: Vec> = if let Some(peer) = self.peers.get_mut(who) { + let mut blocks = response.blocks; + if request.as_ref().map_or(false, |r| r.direction == Direction::Descending) { + trace!(target: "sync", "Reversing incoming block list"); + blocks.reverse() } - for (id, peer) in self.peers.iter_mut() { - if peer.state.is_available() && peer.common_number >= sync.target_block_num() { - trace!(target: "sync", "New StateRequest for {}", id); - peer.state = PeerSyncState::DownloadingState; - let request = sync.next_request(); - return Some((*id, request)) - } - } - } - if let Some(sync) = &self.warp_sync { - if sync.is_complete() { - return None - } - if let (Some(request), Some(target)) = - (sync.next_state_request(), sync.target_block_number()) - { - for (id, peer) in self.peers.iter_mut() { - if peer.state.is_available() && peer.best_number >= target { - trace!(target: "sync", "New StateRequest for {}", id); - peer.state = PeerSyncState::DownloadingState; - return Some((*id, request)) - } - } - } - } - None - } - - /// Get a warp sync request, if any. - pub fn warp_sync_request(&mut self) -> Option<(PeerId, WarpProofRequest)> { - if self - .peers - .iter() - .any(|(_, peer)| peer.state == PeerSyncState::DownloadingWarpProof) - { - // Only one pending state request is allowed. - return None - } - if let Some(sync) = &self.warp_sync { - if sync.is_complete() { - return None - } - if let Some(request) = sync.next_warp_poof_request() { - let mut targets: Vec<_> = self.peers.values().map(|p| p.best_number).collect(); - if !targets.is_empty() { - targets.sort(); - let median = targets[targets.len() / 2]; - // Find a random peer that is synced as much as peer majority. - for (id, peer) in self.peers.iter_mut() { - if peer.state.is_available() && peer.best_number >= median { - trace!(target: "sync", "New WarpProofRequest for {}", id); - peer.state = PeerSyncState::DownloadingWarpProof; - return Some((*id, request)) - } - } - } - } - } - None - } - - /// Handle a response from the remote to a block request that we made. - /// - /// `request` must be the original request that triggered `response`. - /// or `None` if data comes from the block announcement. - /// - /// If this corresponds to a valid block, this outputs the block that - /// must be imported in the import queue. - pub fn on_block_data( - &mut self, - who: &PeerId, - request: Option>, - response: BlockResponse, - ) -> Result, BadPeer> { - self.downloaded_blocks += response.blocks.len(); - let new_blocks: Vec> = if let Some(peer) = self.peers.get_mut(who) { - let mut blocks = response.blocks; - if request - .as_ref() - .map_or(false, |r| r.direction == message::Direction::Descending) - { - trace!(target: "sync", "Reversing incoming block list"); - blocks.reverse() - } - self.pending_requests.add(who); + self.allowed_requests.add(who); if let Some(request) = request { match &mut peer.state { PeerSyncState::DownloadingNew(_) => { @@ -1057,9 +759,51 @@ impl ChainSync { if let Some(start_block) = validate_blocks::(&blocks, who, Some(request))? { - self.blocks.insert(start_block, blocks, who.clone()); + self.blocks.insert(start_block, blocks, *who); + } + self.ready_blocks() + }, + PeerSyncState::DownloadingGap(_) => { + peer.state = PeerSyncState::Available; + if let Some(gap_sync) = &mut self.gap_sync { + gap_sync.blocks.clear_peer_download(who); + if let Some(start_block) = + validate_blocks::(&blocks, who, Some(request))? + { + gap_sync.blocks.insert(start_block, blocks, *who); + } + gap = true; + let blocks: Vec<_> = gap_sync + .blocks + .ready_blocks(gap_sync.best_queued_number + One::one()) + .into_iter() + .map(|block_data| { + let justifications = + block_data.block.justifications.or_else(|| { + legacy_justification_mapping( + block_data.block.justification, + ) + }); + IncomingBlock { + hash: block_data.block.hash, + header: block_data.block.header, + body: block_data.block.body, + indexed_body: block_data.block.indexed_body, + justifications, + origin: block_data.origin, + allow_missing_state: true, + import_existing: self.import_existing, + skip_execution: true, + state: None, + } + }) + .collect(); + debug!(target: "sync", "Drained {} gap blocks from {}", blocks.len(), gap_sync.best_queued_number); + blocks + } else { + debug!(target: "sync", "Unexpected gap block response from {}", who); + return Err(BadPeer(*who, rep::NO_BLOCK)) } - self.drain_blocks() }, PeerSyncState::DownloadingStale(_) => { peer.state = PeerSyncState::Available; @@ -1073,7 +817,7 @@ impl ChainSync { .map(|b| { let justifications = b .justifications - .or(legacy_justification_mapping(b.justification)); + .or_else(|| legacy_justification_mapping(b.justification)); IncomingBlock { hash: b.hash, header: b.header, @@ -1112,7 +856,7 @@ impl ChainSync { (_, Err(e)) => { info!( target: "sync", - "❌ Error answering legitimate blockchain query: {:?}", + "❌ Error answering legitimate blockchain query: {}", e, ); return Err(BadPeer(*who, rep::BLOCKCHAIN_READ_ERROR)) @@ -1179,6 +923,40 @@ impl ChainSync { Vec::new() } }, + PeerSyncState::DownloadingWarpTargetBlock => { + peer.state = PeerSyncState::Available; + if let Some(warp_sync) = &mut self.warp_sync { + if blocks.len() == 1 { + validate_blocks::(&blocks, who, Some(request))?; + match warp_sync.import_target_block( + blocks.pop().expect("`blocks` len checked above."), + ) { + TargetBlockImportResult::Success => + return Ok(OnBlockData::Continue), + TargetBlockImportResult::BadResponse => + return Err(BadPeer(*who, rep::VERIFICATION_FAIL)), + } + } else if blocks.is_empty() { + debug!(target: "sync", "Empty block response from {}", who); + return Err(BadPeer(*who, rep::NO_BLOCK)) + } else { + debug!( + target: "sync", + "Too many blocks ({}) in warp target block response from {}", + blocks.len(), + who, + ); + return Err(BadPeer(*who, rep::NOT_REQUESTED)) + } + } else { + debug!( + target: "sync", + "Logic error: we think we are downloading warp target block from {}, but no warp sync is happening.", + who, + ); + return Ok(OnBlockData::Continue) + } + }, PeerSyncState::Available | PeerSyncState::DownloadingJustification(..) | PeerSyncState::DownloadingState | @@ -1190,8 +968,9 @@ impl ChainSync { blocks .into_iter() .map(|b| { - let justifications = - b.justifications.or(legacy_justification_mapping(b.justification)); + let justifications = b + .justifications + .or_else(|| legacy_justification_mapping(b.justification)); IncomingBlock { hash: b.hash, header: b.header, @@ -1212,144 +991,23 @@ impl ChainSync { return Err(BadPeer(*who, rep::NOT_REQUESTED)) }; - Ok(self.validate_and_queue_blocks(new_blocks)) - } - - /// Handle a response from the remote to a state request that we made. - /// - /// Returns next request if any. - pub fn on_state_data( - &mut self, - who: &PeerId, - response: StateResponse, - ) -> Result, BadPeer> { - let import_result = if let Some(sync) = &mut self.state_sync { - debug!( - target: "sync", - "Importing state data from {} with {} keys, {} proof nodes.", - who, - response.entries.len(), - response.proof.len(), - ); - sync.import(response) - } else if let Some(sync) = &mut self.warp_sync { - debug!( - target: "sync", - "Importing state data from {} with {} keys, {} proof nodes.", - who, - response.entries.len(), - response.proof.len(), - ); - sync.import_state(response) - } else { - debug!(target: "sync", "Ignored obsolete state response from {}", who); - return Err(BadPeer(*who, rep::NOT_REQUESTED)) - }; - - match import_result { - state::ImportResult::Import(hash, header, state) => { - let origin = BlockOrigin::NetworkInitialSync; - let block = IncomingBlock { - hash, - header: Some(header), - body: None, - indexed_body: None, - justifications: None, - origin: None, - allow_missing_state: true, - import_existing: true, - skip_execution: self.skip_execution(), - state: Some(state), - }; - debug!(target: "sync", "State sync is complete. Import is queued"); - Ok(OnStateData::Import(origin, block)) - }, - state::ImportResult::Continue(request) => - Ok(OnStateData::Request(who.clone(), request)), - state::ImportResult::BadResponse => { - debug!(target: "sync", "Bad state data received from {}", who); - Err(BadPeer(*who, rep::BAD_BLOCK)) - }, - } + Ok(self.validate_and_queue_blocks(new_blocks, gap)) } - /// Handle a response from the remote to a warp proof request that we made. - /// - /// Returns next request. - pub fn on_warp_sync_data( - &mut self, - who: &PeerId, - response: warp::EncodedProof, - ) -> Result, BadPeer> { - let import_result = if let Some(sync) = &mut self.warp_sync { - debug!( - target: "sync", - "Importing warp proof data from {}, {} bytes.", - who, - response.0.len(), - ); - sync.import_warp_proof(response) - } else { - debug!(target: "sync", "Ignored obsolete warp sync response from {}", who); - return Err(BadPeer(*who, rep::NOT_REQUESTED)) - }; - - match import_result { - warp::WarpProofImportResult::StateRequest(request) => - Ok(OnWarpSyncData::StateRequest(*who, request)), - warp::WarpProofImportResult::WarpProofRequest(request) => - Ok(OnWarpSyncData::WarpProofRequest(*who, request)), - warp::WarpProofImportResult::BadResponse => { - debug!(target: "sync", "Bad proof data received from {}", who); - Err(BadPeer(*who, rep::BAD_BLOCK)) + fn process_block_response_data(&mut self, blocks_to_import: Result, BadPeer>) { + match blocks_to_import { + Ok(OnBlockData::Import(origin, blocks)) => self.import_blocks(origin, blocks), + Ok(OnBlockData::Request(peer, req)) => self.send_block_request(peer, req), + Ok(OnBlockData::Continue) => {}, + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu); }, } } - fn validate_and_queue_blocks( - &mut self, - mut new_blocks: Vec>, - ) -> OnBlockData { - let orig_len = new_blocks.len(); - new_blocks.retain(|b| !self.queue_blocks.contains(&b.hash)); - if new_blocks.len() != orig_len { - debug!( - target: "sync", - "Ignoring {} blocks that are already queued", - orig_len - new_blocks.len(), - ); - } - - let origin = if self.status().state != SyncState::Downloading { - BlockOrigin::NetworkBroadcast - } else { - BlockOrigin::NetworkInitialSync - }; - - if let Some((h, n)) = new_blocks - .last() - .and_then(|b| b.header.as_ref().map(|h| (&b.hash, *h.number()))) - { - trace!( - target:"sync", - "Accepted {} blocks ({:?}) with origin {:?}", - new_blocks.len(), - h, - origin, - ); - self.on_block_queued(h, n) - } - self.queue_blocks.extend(new_blocks.iter().map(|b| b.hash)); - OnBlockData::Import(origin, new_blocks) - } - - /// Handle a response from the remote to a justification request that we made. - /// - /// `request` must be the original request that triggered `response`. - /// - /// Returns `Some` if this produces a justification that must be imported - /// into the import queue. - pub fn on_block_justification( + fn on_block_justification( &mut self, who: PeerId, response: BlockResponse, @@ -1361,7 +1019,7 @@ impl ChainSync { return Ok(OnBlockJustification::Nothing) }; - self.pending_requests.add(&who); + self.allowed_requests.add(&who); if let PeerSyncState::DownloadingJustification(hash) = peer.state { peer.state = PeerSyncState::Available; @@ -1378,7 +1036,9 @@ impl ChainSync { return Err(BadPeer(who, rep::BAD_JUSTIFICATION)) } - block.justifications.or(legacy_justification_mapping(block.justification)) + block + .justifications + .or_else(|| legacy_justification_mapping(block.justification)) } else { // we might have asked the peer for a justification on a block that we assumed it // had but didn't (regardless of whether it had a justification for it or not). @@ -1402,271 +1062,56 @@ impl ChainSync { Ok(OnBlockJustification::Nothing) } - /// A batch of blocks have been processed, with or without errors. - /// - /// Call this when a batch of blocks have been processed by the import - /// queue, with or without errors. - /// - /// `peer_info` is passed in case of a restart. - pub fn on_blocks_processed<'a>( - &'a mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) -> impl Iterator), BadPeer>> + 'a { - trace!(target: "sync", "Imported {} of {}", imported, count); - - let mut output = Vec::new(); - - let mut has_error = false; - for (_, hash) in &results { - self.queue_blocks.remove(&hash); - } - for (result, hash) in results { - if has_error { - continue - } + fn on_justification_import(&mut self, hash: B::Hash, number: NumberFor, success: bool) { + let finalization_result = if success { Ok((hash, number)) } else { Err(()) }; + self.extra_justifications + .try_finalize_root((hash, number), finalization_result, true); + self.allowed_requests.set_all(); + } - if result.is_err() { - has_error = true; - } + fn on_block_finalized(&mut self, hash: &B::Hash, number: NumberFor) { + let client = &self.client; + let r = self.extra_justifications.on_block_finalized(hash, number, |base, block| { + is_descendent_of(&**client, base, block) + }); - match result { - Ok(BlockImportStatus::ImportedKnown(number, who)) => { - if let Some(peer) = who.and_then(|p| self.peers.get_mut(&p)) { - peer.update_common_number(number); - } - }, - Ok(BlockImportStatus::ImportedUnknown(number, aux, who)) => { - if aux.clear_justification_requests { - trace!( + if let SyncMode::LightState { skip_proofs, .. } = &self.mode { + if self.state_sync.is_none() && !self.peers.is_empty() && self.queue_blocks.is_empty() { + // Finalized a recent block. + let mut heads: Vec<_> = self.peers.values().map(|peer| peer.best_number).collect(); + heads.sort(); + let median = heads[heads.len() / 2]; + if number + STATE_SYNC_FINALITY_THRESHOLD.saturated_into() >= median { + if let Ok(Some(header)) = self.client.header(BlockId::hash(*hash)) { + log::debug!( target: "sync", - "Block imported clears all pending justification requests {}: {:?}", + "Starting state sync for #{} ({})", number, hash, ); - self.clear_justification_requests(); + self.state_sync = Some(StateSync::new( + self.client.clone(), + header, + None, + None, + *skip_proofs, + )); + self.allowed_requests.set_all(); } + } + } + } - if aux.needs_justification { - trace!( - target: "sync", - "Block imported but requires justification {}: {:?}", - number, - hash, - ); - self.request_justification(&hash, number); - } + if let Err(err) = r { + warn!( + target: "sync", + "💔 Error cleaning up pending extra justification data requests: {}", + err, + ); + } + } - if aux.bad_justification { - if let Some(ref peer) = who { - warn!("💔 Sent block with bad justification to import"); - output.push(Err(BadPeer(*peer, rep::BAD_JUSTIFICATION))); - } - } - - if let Some(peer) = who.and_then(|p| self.peers.get_mut(&p)) { - peer.update_common_number(number); - } - let state_sync_complete = - self.state_sync.as_ref().map_or(false, |s| s.target() == hash); - if state_sync_complete { - info!( - target: "sync", - "State sync is complete ({} MiB), restarting block sync.", - self.state_sync.as_ref().map_or(0, |s| s.progress().size / (1024 * 1024)), - ); - self.state_sync = None; - self.mode = SyncMode::Full; - output.extend(self.restart()); - } - let warp_sync_complete = self - .warp_sync - .as_ref() - .map_or(false, |s| s.target_block_hash() == Some(hash)); - if warp_sync_complete { - info!( - target: "sync", - "Warp sync is complete ({} MiB), restarting block sync.", - self.warp_sync.as_ref().map_or(0, |s| s.progress().total_bytes / (1024 * 1024)), - ); - self.warp_sync = None; - self.mode = SyncMode::Full; - output.extend(self.restart()); - } - }, - Err(BlockImportError::IncompleteHeader(who)) => - if let Some(peer) = who { - warn!( - target: "sync", - "💔 Peer sent block with incomplete header to import", - ); - output.push(Err(BadPeer(peer, rep::INCOMPLETE_HEADER))); - output.extend(self.restart()); - }, - Err(BlockImportError::VerificationFailed(who, e)) => - if let Some(peer) = who { - warn!( - target: "sync", - "💔 Verification failed for block {:?} received from peer: {}, {:?}", - hash, - peer, - e, - ); - output.push(Err(BadPeer(peer, rep::VERIFICATION_FAIL))); - output.extend(self.restart()); - }, - Err(BlockImportError::BadBlock(who)) => - if let Some(peer) = who { - warn!( - target: "sync", - "💔 Block {:?} received from peer {} has been blacklisted", - hash, - peer, - ); - output.push(Err(BadPeer(peer, rep::BAD_BLOCK))); - }, - Err(BlockImportError::MissingState) => { - // This may happen if the chain we were requesting upon has been discarded - // in the meantime because other chain has been finalized. - // Don't mark it as bad as it still may be synced if explicitly requested. - trace!(target: "sync", "Obsolete block {:?}", hash); - }, - e @ Err(BlockImportError::UnknownParent) | e @ Err(BlockImportError::Other(_)) => { - warn!(target: "sync", "💔 Error importing block {:?}: {:?}", hash, e); - self.state_sync = None; - self.warp_sync = None; - output.extend(self.restart()); - }, - Err(BlockImportError::Cancelled) => {}, - }; - } - - self.pending_requests.set_all(); - output.into_iter() - } - - /// Call this when a justification has been processed by the import queue, - /// with or without errors. - pub fn on_justification_import(&mut self, hash: B::Hash, number: NumberFor, success: bool) { - let finalization_result = if success { Ok((hash, number)) } else { Err(()) }; - self.extra_justifications - .try_finalize_root((hash, number), finalization_result, true); - self.pending_requests.set_all(); - } - - /// Notify about finalization of the given block. - pub fn on_block_finalized(&mut self, hash: &B::Hash, number: NumberFor) { - let client = &self.client; - let r = self.extra_justifications.on_block_finalized(hash, number, |base, block| { - is_descendent_of(&**client, base, block) - }); - - if let SyncMode::LightState { skip_proofs, .. } = &self.mode { - if self.state_sync.is_none() && !self.peers.is_empty() && self.queue_blocks.is_empty() { - // Finalized a recent block. - let mut heads: Vec<_> = - self.peers.iter().map(|(_, peer)| peer.best_number).collect(); - heads.sort(); - let median = heads[heads.len() / 2]; - if number + STATE_SYNC_FINALITY_THRESHOLD.saturated_into() >= median { - if let Ok(Some(header)) = self.client.header(BlockId::hash(hash.clone())) { - log::debug!( - target: "sync", - "Starting state sync for #{} ({})", - number, - hash, - ); - self.state_sync = - Some(StateSync::new(self.client.clone(), header, *skip_proofs)); - } - } - } - } - - if let Err(err) = r { - warn!( - target: "sync", - "💔 Error cleaning up pending extra justification data requests: {:?}", - err, - ); - } - } - - /// Called when a block has been queued for import. - /// - /// Updates our internal state for best queued block and then goes - /// through all peers to update our view of their state as well. - fn on_block_queued(&mut self, hash: &B::Hash, number: NumberFor) { - if self.fork_targets.remove(&hash).is_some() { - trace!(target: "sync", "Completed fork sync {:?}", hash); - } - if number > self.best_queued_number { - self.best_queued_number = number; - self.best_queued_hash = *hash; - // Update common blocks - for (n, peer) in self.peers.iter_mut() { - if let PeerSyncState::AncestorSearch { .. } = peer.state { - // Wait for ancestry search to complete first. - continue - } - let new_common_number = - if peer.best_number >= number { number } else { peer.best_number }; - trace!( - target: "sync", - "Updating peer {} info, ours={}, common={}->{}, their best={}", - n, - number, - peer.common_number, - new_common_number, - peer.best_number, - ); - peer.common_number = new_common_number; - } - } - self.pending_requests.set_all(); - } - - /// Checks if there is a slot for a block announce validation. - /// - /// The total number and the number per peer of concurrent block announce validations - /// is capped. - /// - /// Returns [`HasSlotForBlockAnnounceValidation`] to inform about the result. - /// - /// # Note - /// - /// It is *required* to call [`Self::peer_block_announce_validation_finished`] when the - /// validation is finished to clear the slot. - fn has_slot_for_block_announce_validation( - &mut self, - peer: &PeerId, - ) -> HasSlotForBlockAnnounceValidation { - if self.block_announce_validation.len() >= MAX_CONCURRENT_BLOCK_ANNOUNCE_VALIDATIONS { - return HasSlotForBlockAnnounceValidation::TotalMaximumSlotsReached - } - - match self.block_announce_validation_per_peer_stats.entry(peer.clone()) { - Entry::Vacant(entry) => { - entry.insert(1); - HasSlotForBlockAnnounceValidation::Yes - }, - Entry::Occupied(mut entry) => { - if *entry.get() < MAX_CONCURRENT_BLOCK_ANNOUNCE_VALIDATIONS_PER_PEER { - *entry.get_mut() += 1; - HasSlotForBlockAnnounceValidation::Yes - } else { - HasSlotForBlockAnnounceValidation::MaximumPeerSlotsReached - } - }, - } - } - - /// Push a block announce validation. - /// - /// It is required that [`ChainSync::poll_block_announce_validation`] is called - /// to check for finished block announce validations. - pub fn push_block_announce_validation( + fn push_block_announce_validation( &mut self, who: PeerId, hash: B::Hash, @@ -1735,8 +1180,7 @@ impl ChainSync { // Let external validator check the block announcement. let assoc_data = announce.data.as_ref().map_or(&[][..], |v| v.as_slice()); - let future = self.block_announce_validator.validate(&header, assoc_data); - let hash = hash.clone(); + let future = self.block_announce_validator.validate(header, assoc_data); self.block_announce_validation.push( async move { @@ -1770,16 +1214,7 @@ impl ChainSync { ); } - /// Poll block announce validation. - /// - /// Block announce validations can be pushed by using - /// [`ChainSync::push_block_announce_validation`]. - /// - /// This should be polled until it returns [`Poll::Pending`]. - /// - /// If [`PollBlockAnnounceValidation::ImportHeader`] is returned, then the caller MUST try to - /// import passed header (call `on_block_data`). The network request isn't sent in this case. - pub fn poll_block_announce_validation( + fn poll_block_announce_validation( &mut self, cx: &mut std::task::Context, ) -> Poll> { @@ -1792,316 +1227,1619 @@ impl ChainSync { } } - /// Should be called when a block announce validation is finished, to update the slots - /// of the peer that send the block announce. - fn peer_block_announce_validation_finished( - &mut self, - res: &PreValidateBlockAnnounce, - ) { - let peer = match res { - PreValidateBlockAnnounce::Failure { who, .. } | - PreValidateBlockAnnounce::Process { who, .. } | - PreValidateBlockAnnounce::Error { who } => who, - PreValidateBlockAnnounce::Skip => return, - }; + fn peer_disconnected(&mut self, who: &PeerId) { + self.blocks.clear_peer_download(who); + if let Some(gap_sync) = &mut self.gap_sync { + gap_sync.blocks.clear_peer_download(who) + } + self.peers.remove(who); + self.extra_justifications.peer_disconnected(who); + self.allowed_requests.set_all(); + self.fork_targets.retain(|_, target| { + target.peers.remove(who); + !target.peers.is_empty() + }); - match self.block_announce_validation_per_peer_stats.entry(peer.clone()) { - Entry::Vacant(_) => { - error!( - target: "sync", - "💔 Block announcement validation from peer {} finished for that no slot was allocated!", - peer, - ); - }, - Entry::Occupied(mut entry) => { - *entry.get_mut() = entry.get().saturating_sub(1); - if *entry.get() == 0 { - entry.remove(); - } - }, + let blocks = self.ready_blocks(); + if let Some(OnBlockData::Import(origin, blocks)) = + (!blocks.is_empty()).then(|| self.validate_and_queue_blocks(blocks, false)) + { + self.import_blocks(origin, blocks); } } - /// This will finish processing of the block announcement. - fn finish_block_announce_validation( - &mut self, - pre_validation_result: PreValidateBlockAnnounce, - ) -> PollBlockAnnounceValidation { - let (announce, is_best, who) = match pre_validation_result { - PreValidateBlockAnnounce::Failure { who, disconnect } => { - debug!( - target: "sync", - "Failed announce validation: {:?}, disconnect: {}", - who, - disconnect, - ); - return PollBlockAnnounceValidation::Failure { who, disconnect } - }, - PreValidateBlockAnnounce::Process { announce, is_new_best, who } => - (announce, is_new_best, who), - PreValidateBlockAnnounce::Error { .. } | PreValidateBlockAnnounce::Skip => { - debug!( - target: "sync", - "Ignored announce validation", - ); - return PollBlockAnnounceValidation::Skip - }, - }; + fn metrics(&self) -> Metrics { + Metrics { + queued_blocks: self.queue_blocks.len().try_into().unwrap_or(std::u32::MAX), + fork_targets: self.fork_targets.len().try_into().unwrap_or(std::u32::MAX), + justifications: self.extra_justifications.metrics(), + } + } - trace!( - target: "sync", - "Finished block announce validation: from {:?}: {:?}. local_best={}", - who, - announce.summary(), - is_best, - ); - - let number = *announce.header.number(); - let hash = announce.header.hash(); - let parent_status = - self.block_status(announce.header.parent_hash()).unwrap_or(BlockStatus::Unknown); - let known_parent = parent_status != BlockStatus::Unknown; - let ancient_parent = parent_status == BlockStatus::InChainPruned; - - let known = self.is_known(&hash); - let peer = if let Some(peer) = self.peers.get_mut(&who) { - peer - } else { - error!(target: "sync", "💔 Called on_block_announce with a bad peer ID"); - return PollBlockAnnounceValidation::Nothing { is_best, who, announce } - }; - - if is_best { - // update their best block - peer.best_number = number; - peer.best_hash = hash; - } - - if let PeerSyncState::AncestorSearch { .. } = peer.state { - trace!(target: "sync", "Peer state is ancestor search."); - return PollBlockAnnounceValidation::Nothing { is_best, who, announce } - } + fn block_response_into_blocks( + &self, + request: &BlockRequest, + response: OpaqueBlockResponse, + ) -> Result>, String> { + let response: Box = response.0.downcast().map_err(|_error| { + "Failed to downcast opaque block response during encoding, this is an \ + implementation bug." + .to_string() + })?; + + response + .blocks + .into_iter() + .map(|block_data| { + Ok(BlockData:: { + hash: Decode::decode(&mut block_data.hash.as_ref())?, + header: if !block_data.header.is_empty() { + Some(Decode::decode(&mut block_data.header.as_ref())?) + } else { + None + }, + body: if request.fields.contains(BlockAttributes::BODY) { + Some( + block_data + .body + .iter() + .map(|body| Decode::decode(&mut body.as_ref())) + .collect::, _>>()?, + ) + } else { + None + }, + indexed_body: if request.fields.contains(BlockAttributes::INDEXED_BODY) { + Some(block_data.indexed_body) + } else { + None + }, + receipt: if !block_data.receipt.is_empty() { + Some(block_data.receipt) + } else { + None + }, + message_queue: if !block_data.message_queue.is_empty() { + Some(block_data.message_queue) + } else { + None + }, + justification: if !block_data.justification.is_empty() { + Some(block_data.justification) + } else if block_data.is_empty_justification { + Some(Vec::new()) + } else { + None + }, + justifications: if !block_data.justifications.is_empty() { + Some(DecodeAll::decode_all(&mut block_data.justifications.as_ref())?) + } else { + None + }, + }) + }) + .collect::>() + .map_err(|error: codec::Error| error.to_string()) + } - // If the announced block is the best they have and is not ahead of us, our common number - // is either one further ahead or it's the one they just announced, if we know about it. - if is_best { - if known && self.best_queued_number >= number { - peer.update_common_number(number); - } else if announce.header.parent_hash() == &self.best_queued_hash || - known_parent && self.best_queued_number >= number - { - peer.update_common_number(number - One::one()); + fn poll( + &mut self, + cx: &mut std::task::Context, + ) -> Poll> { + while let Poll::Ready(Some(event)) = self.service_rx.poll_next_unpin(cx) { + match event { + ToServiceCommand::SetSyncForkRequest(peers, hash, number) => { + self.set_sync_fork_request(peers, &hash, number); + }, + ToServiceCommand::RequestJustification(hash, number) => + self.request_justification(&hash, number), + ToServiceCommand::ClearJustificationRequests => self.clear_justification_requests(), + ToServiceCommand::BlocksProcessed(imported, count, results) => { + for result in self.on_blocks_processed(imported, count, results) { + match result { + Ok((id, req)) => self.send_block_request(id, req), + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu) + }, + } + } + }, + ToServiceCommand::JustificationImported(peer, hash, number, success) => { + self.on_justification_import(hash, number, success); + if !success { + info!(target: "sync", "💔 Invalid justification provided by {} for #{}", peer, hash); + self.network_service + .disconnect_peer(peer, self.block_announce_protocol_name.clone()); + self.network_service.report_peer( + peer, + sc_peerset::ReputationChange::new_fatal("Invalid justification"), + ); + } + }, } } - self.pending_requests.add(&who); + self.process_outbound_requests(); - // known block case - if known || self.is_already_downloading(&hash) { - trace!(target: "sync", "Known block announce from {}: {}", who, hash); - if let Some(target) = self.fork_targets.get_mut(&hash) { - target.peers.insert(who.clone()); + while let Poll::Ready(result) = self.poll_pending_responses(cx) { + match result { + ImportResult::BlockImport(origin, blocks) => self.import_blocks(origin, blocks), + ImportResult::JustificationImport(who, hash, number, justifications) => + self.import_justifications(who, hash, number, justifications), } - return PollBlockAnnounceValidation::Nothing { is_best, who, announce } } - if ancient_parent { - trace!( - target: "sync", - "Ignored ancient block announced from {}: {} {:?}", - who, - hash, - announce.header, - ); - return PollBlockAnnounceValidation::Nothing { is_best, who, announce } + if let Poll::Ready(announce) = self.poll_block_announce_validation(cx) { + return Poll::Ready(announce) } - let requires_additional_data = self.mode != SyncMode::Light || !known_parent; - if !requires_additional_data { - trace!( - target: "sync", - "Importing new header announced from {}: {} {:?}", - who, - hash, - announce.header, - ); - return PollBlockAnnounceValidation::ImportHeader { is_best, announce, who } + Poll::Pending + } + + fn send_block_request(&mut self, who: PeerId, request: BlockRequest) { + let (tx, rx) = oneshot::channel(); + let opaque_req = self.create_opaque_block_request(&request); + + if self.peers.contains_key(&who) { + self.pending_responses + .push(Box::pin(async move { (who, PeerRequest::Block(request), rx.await) })); } - if self.status().state == SyncState::Idle { - trace!( - target: "sync", - "Added sync target for block announced from {}: {} {:?}", - who, - hash, - announce.summary(), - ); - self.fork_targets - .entry(hash) - .or_insert_with(|| ForkTarget { - number, - parent_hash: Some(*announce.header.parent_hash()), - peers: Default::default(), - }) - .peers - .insert(who); + match self.encode_block_request(&opaque_req) { + Ok(data) => { + self.network_service.start_request( + who, + self.block_request_protocol_name.clone(), + data, + tx, + IfDisconnected::ImmediateError, + ); + }, + Err(err) => { + log::warn!( + target: "sync", + "Failed to encode block request {:?}: {:?}", + opaque_req, err + ); + }, } + } +} - PollBlockAnnounceValidation::Nothing { is_best, who, announce } +impl ChainSync +where + Self: ChainSyncT, + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ + /// Create a new instance. + pub fn new( + mode: SyncMode, + client: Arc, + protocol_id: ProtocolId, + fork_id: &Option, + roles: Roles, + block_announce_validator: Box + Send>, + max_parallel_downloads: u32, + warp_sync_provider: Option>>, + metrics_registry: Option<&Registry>, + network_service: service::network::NetworkServiceHandle, + import_queue: Box>, + block_request_protocol_name: ProtocolName, + state_request_protocol_name: ProtocolName, + warp_sync_protocol_name: Option, + ) -> Result<(Self, ChainSyncInterfaceHandle, NonDefaultSetConfig), ClientError> { + let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync"); + let block_announce_config = Self::get_block_announce_proto_config( + protocol_id, + fork_id, + roles, + client.info().best_number, + client.info().best_hash, + client + .block_hash(Zero::zero()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + ); + + let mut sync = Self { + client, + peers: HashMap::new(), + blocks: BlockCollection::new(), + best_queued_hash: Default::default(), + best_queued_number: Zero::zero(), + extra_justifications: ExtraRequests::new("justification"), + mode, + queue_blocks: Default::default(), + fork_targets: Default::default(), + allowed_requests: Default::default(), + block_announce_validator, + max_parallel_downloads, + downloaded_blocks: 0, + block_announce_validation: Default::default(), + block_announce_validation_per_peer_stats: Default::default(), + state_sync: None, + warp_sync: None, + warp_sync_provider, + import_existing: false, + gap_sync: None, + service_rx, + network_service, + block_request_protocol_name, + state_request_protocol_name, + warp_sync_protocol_name, + block_announce_protocol_name: block_announce_config + .notifications_protocol + .clone() + .into(), + pending_responses: Default::default(), + import_queue, + metrics: if let Some(r) = &metrics_registry { + match SyncingMetrics::register(r) { + Ok(metrics) => Some(metrics), + Err(err) => { + error!(target: "sync", "Failed to register metrics for ChainSync: {err:?}"); + None + }, + } + } else { + None + }, + }; + + sync.reset_sync_start_point()?; + Ok((sync, ChainSyncInterfaceHandle::new(tx), block_announce_config)) } - /// Call when a peer has disconnected. - /// Canceled obsolete block request may result in some blocks being ready for - /// import, so this functions checks for such blocks and returns them. - pub fn peer_disconnected(&mut self, who: &PeerId) -> Option> { - self.blocks.clear_peer_download(who); - self.peers.remove(who); - self.extra_justifications.peer_disconnected(who); - self.pending_requests.set_all(); - self.fork_targets.retain(|_, target| { - target.peers.remove(who); - !target.peers.is_empty() - }); - let blocks = self.drain_blocks(); - if !blocks.is_empty() { - Some(self.validate_and_queue_blocks(blocks)) - } else { + /// Returns the median seen block number. + fn median_seen(&self) -> Option> { + let mut best_seens = self.peers.values().map(|p| p.best_number).collect::>(); + + if best_seens.is_empty() { None + } else { + let middle = best_seens.len() / 2; + + // Not the "perfect median" when we have an even number of peers. + Some(*best_seens.select_nth_unstable(middle).1) } } - /// Restart the sync process. This will reset all pending block requests and return an iterator - /// of new block requests to make to peers. Peers that were downloading finality data (i.e. - /// their state was `DownloadingJustification`) are unaffected and will stay in the same state. - fn restart<'a>( - &'a mut self, - ) -> impl Iterator), BadPeer>> + 'a { - self.blocks.clear(); - if let Err(e) = self.reset_sync_start_point() { - warn!(target: "sync", "💔 Unable to restart sync. :{:?}", e); + fn required_block_attributes(&self) -> BlockAttributes { + match self.mode { + SyncMode::Full => + BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION | BlockAttributes::BODY, + SyncMode::Light => BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION, + SyncMode::LightState { storage_chain_mode: false, .. } | SyncMode::Warp => + BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION | BlockAttributes::BODY, + SyncMode::LightState { storage_chain_mode: true, .. } => + BlockAttributes::HEADER | + BlockAttributes::JUSTIFICATION | + BlockAttributes::INDEXED_BODY, } - self.pending_requests.set_all(); - debug!(target:"sync", "Restarted with {} ({})", self.best_queued_number, self.best_queued_hash); - let old_peers = std::mem::take(&mut self.peers); - - old_peers.into_iter().filter_map(move |(id, mut p)| { - // peers that were downloading justifications - // should be kept in that state. - match p.state { - PeerSyncState::DownloadingJustification(_) => { - // We make sure our commmon number is at least something we have. - p.common_number = self.best_queued_number; - self.peers.insert(id, p); - return None - }, - _ => {}, - } + } - // handle peers that were in other states. - match self.new_peer(id.clone(), p.best_hash, p.best_number) { - Ok(None) => None, - Ok(Some(x)) => Some(Ok((id, x))), - Err(e) => Some(Err(e)), - } - }) + fn skip_execution(&self) -> bool { + match self.mode { + SyncMode::Full => false, + SyncMode::Light => true, + SyncMode::LightState { .. } => true, + SyncMode::Warp => true, + } } - /// Find a block to start sync from. If we sync with state, that's the latest block we have - /// state for. - fn reset_sync_start_point(&mut self) -> Result<(), ClientError> { - let info = self.client.info(); - if matches!(self.mode, SyncMode::LightState { .. }) && info.finalized_state.is_some() { - warn!( + fn validate_and_queue_blocks( + &mut self, + mut new_blocks: Vec>, + gap: bool, + ) -> OnBlockData { + let orig_len = new_blocks.len(); + new_blocks.retain(|b| !self.queue_blocks.contains(&b.hash)); + if new_blocks.len() != orig_len { + debug!( target: "sync", - "Can't use fast sync mode with a partially synced database. Reverting to full sync mode." + "Ignoring {} blocks that are already queued", + orig_len - new_blocks.len(), ); - self.mode = SyncMode::Full; } - if matches!(self.mode, SyncMode::Warp) && info.finalized_state.is_some() { - warn!( - target: "sync", - "Can't use warp sync mode with a partially synced database. Reverting to full sync mode." + + let origin = if !gap && !self.status().state.is_major_syncing() { + BlockOrigin::NetworkBroadcast + } else { + BlockOrigin::NetworkInitialSync + }; + + if let Some((h, n)) = new_blocks + .last() + .and_then(|b| b.header.as_ref().map(|h| (&b.hash, *h.number()))) + { + trace!( + target:"sync", + "Accepted {} blocks ({:?}) with origin {:?}", + new_blocks.len(), + h, + origin, ); - self.mode = SyncMode::Full; + self.on_block_queued(h, n) } - self.import_existing = false; - self.best_queued_hash = info.best_hash; - self.best_queued_number = info.best_number; - if self.mode == SyncMode::Full { - if self.client.block_status(&BlockId::hash(info.best_hash))? != - BlockStatus::InChainWithState - { - self.import_existing = true; - // Latest state is missing, start with the last finalized state or genesis instead. - if let Some((hash, number)) = info.finalized_state { - debug!(target: "sync", "Starting from finalized state #{}", number); - self.best_queued_hash = hash; - self.best_queued_number = number; - } else { - debug!(target: "sync", "Restarting from genesis"); - self.best_queued_hash = Default::default(); - self.best_queued_number = Zero::zero(); - } + self.queue_blocks.extend(new_blocks.iter().map(|b| b.hash)); + OnBlockData::Import(origin, new_blocks) + } + + fn update_peer_common_number(&mut self, peer_id: &PeerId, new_common: NumberFor) { + if let Some(peer) = self.peers.get_mut(peer_id) { + peer.update_common_number(new_common); + } + } + + /// Called when a block has been queued for import. + /// + /// Updates our internal state for best queued block and then goes + /// through all peers to update our view of their state as well. + fn on_block_queued(&mut self, hash: &B::Hash, number: NumberFor) { + if self.fork_targets.remove(hash).is_some() { + trace!(target: "sync", "Completed fork sync {:?}", hash); + } + if let Some(gap_sync) = &mut self.gap_sync { + if number > gap_sync.best_queued_number && number <= gap_sync.target { + gap_sync.best_queued_number = number; + } + } + if number > self.best_queued_number { + self.best_queued_number = number; + self.best_queued_hash = *hash; + // Update common blocks + for (n, peer) in self.peers.iter_mut() { + if let PeerSyncState::AncestorSearch { .. } = peer.state { + // Wait for ancestry search to complete first. + continue + } + let new_common_number = + if peer.best_number >= number { number } else { peer.best_number }; + trace!( + target: "sync", + "Updating peer {} info, ours={}, common={}->{}, their best={}", + n, + number, + peer.common_number, + new_common_number, + peer.best_number, + ); + peer.common_number = new_common_number; + } + } + self.allowed_requests.set_all(); + } + + /// Checks if there is a slot for a block announce validation. + /// + /// The total number and the number per peer of concurrent block announce validations + /// is capped. + /// + /// Returns [`HasSlotForBlockAnnounceValidation`] to inform about the result. + /// + /// # Note + /// + /// It is *required* to call [`Self::peer_block_announce_validation_finished`] when the + /// validation is finished to clear the slot. + fn has_slot_for_block_announce_validation( + &mut self, + peer: &PeerId, + ) -> HasSlotForBlockAnnounceValidation { + if self.block_announce_validation.len() >= MAX_CONCURRENT_BLOCK_ANNOUNCE_VALIDATIONS { + return HasSlotForBlockAnnounceValidation::TotalMaximumSlotsReached + } + + match self.block_announce_validation_per_peer_stats.entry(*peer) { + Entry::Vacant(entry) => { + entry.insert(1); + HasSlotForBlockAnnounceValidation::Yes + }, + Entry::Occupied(mut entry) => { + if *entry.get() < MAX_CONCURRENT_BLOCK_ANNOUNCE_VALIDATIONS_PER_PEER { + *entry.get_mut() += 1; + HasSlotForBlockAnnounceValidation::Yes + } else { + HasSlotForBlockAnnounceValidation::MaximumPeerSlotsReached + } + }, + } + } + + /// Should be called when a block announce validation is finished, to update the slots + /// of the peer that send the block announce. + fn peer_block_announce_validation_finished( + &mut self, + res: &PreValidateBlockAnnounce, + ) { + let peer = match res { + PreValidateBlockAnnounce::Failure { who, .. } | + PreValidateBlockAnnounce::Process { who, .. } | + PreValidateBlockAnnounce::Error { who } => who, + PreValidateBlockAnnounce::Skip => return, + }; + + match self.block_announce_validation_per_peer_stats.entry(*peer) { + Entry::Vacant(_) => { + error!( + target: "sync", + "💔 Block announcement validation from peer {} finished for that no slot was allocated!", + peer, + ); + }, + Entry::Occupied(mut entry) => { + *entry.get_mut() = entry.get().saturating_sub(1); + if *entry.get() == 0 { + entry.remove(); + } + }, + } + } + + /// This will finish processing of the block announcement. + fn finish_block_announce_validation( + &mut self, + pre_validation_result: PreValidateBlockAnnounce, + ) -> PollBlockAnnounceValidation { + let (announce, is_best, who) = match pre_validation_result { + PreValidateBlockAnnounce::Failure { who, disconnect } => { + debug!( + target: "sync", + "Failed announce validation: {:?}, disconnect: {}", + who, + disconnect, + ); + return PollBlockAnnounceValidation::Failure { who, disconnect } + }, + PreValidateBlockAnnounce::Process { announce, is_new_best, who } => + (announce, is_new_best, who), + PreValidateBlockAnnounce::Error { .. } | PreValidateBlockAnnounce::Skip => { + debug!( + target: "sync", + "Ignored announce validation", + ); + return PollBlockAnnounceValidation::Skip + }, + }; + + trace!( + target: "sync", + "Finished block announce validation: from {:?}: {:?}. local_best={}", + who, + announce.summary(), + is_best, + ); + + let number = *announce.header.number(); + let hash = announce.header.hash(); + let parent_status = + self.block_status(announce.header.parent_hash()).unwrap_or(BlockStatus::Unknown); + let known_parent = parent_status != BlockStatus::Unknown; + let ancient_parent = parent_status == BlockStatus::InChainPruned; + + let known = self.is_known(&hash); + let peer = if let Some(peer) = self.peers.get_mut(&who) { + peer + } else { + error!(target: "sync", "💔 Called on_block_announce with a bad peer ID"); + return PollBlockAnnounceValidation::Nothing { is_best, who, announce } + }; + + if let PeerSyncState::AncestorSearch { .. } = peer.state { + trace!(target: "sync", "Peer state is ancestor search."); + return PollBlockAnnounceValidation::Nothing { is_best, who, announce } + } + + if is_best { + // update their best block + peer.best_number = number; + peer.best_hash = hash; + } + + // If the announced block is the best they have and is not ahead of us, our common number + // is either one further ahead or it's the one they just announced, if we know about it. + if is_best { + if known && self.best_queued_number >= number { + self.update_peer_common_number(&who, number); + } else if announce.header.parent_hash() == &self.best_queued_hash || + known_parent && self.best_queued_number >= number + { + self.update_peer_common_number(&who, number - One::one()); + } + } + self.allowed_requests.add(&who); + + // known block case + if known || self.is_already_downloading(&hash) { + trace!(target: "sync", "Known block announce from {}: {}", who, hash); + if let Some(target) = self.fork_targets.get_mut(&hash) { + target.peers.insert(who); + } + return PollBlockAnnounceValidation::Nothing { is_best, who, announce } + } + + if ancient_parent { + trace!( + target: "sync", + "Ignored ancient block announced from {}: {} {:?}", + who, + hash, + announce.header, + ); + return PollBlockAnnounceValidation::Nothing { is_best, who, announce } + } + + let requires_additional_data = self.mode != SyncMode::Light || !known_parent; + if !requires_additional_data { + trace!( + target: "sync", + "Importing new header announced from {}: {} {:?}", + who, + hash, + announce.header, + ); + return PollBlockAnnounceValidation::ImportHeader { is_best, announce, who } + } + + if self.status().state == SyncState::Idle { + trace!( + target: "sync", + "Added sync target for block announced from {}: {} {:?}", + who, + hash, + announce.summary(), + ); + self.fork_targets + .entry(hash) + .or_insert_with(|| ForkTarget { + number, + parent_hash: Some(*announce.header.parent_hash()), + peers: Default::default(), + }) + .peers + .insert(who); + } + + PollBlockAnnounceValidation::Nothing { is_best, who, announce } + } + + /// Restart the sync process. This will reset all pending block requests and return an iterator + /// of new block requests to make to peers. Peers that were downloading finality data (i.e. + /// their state was `DownloadingJustification`) are unaffected and will stay in the same state. + fn restart(&mut self) -> impl Iterator), BadPeer>> + '_ { + self.blocks.clear(); + if let Err(e) = self.reset_sync_start_point() { + warn!(target: "sync", "💔 Unable to restart sync: {}", e); + } + self.allowed_requests.set_all(); + debug!(target:"sync", "Restarted with {} ({})", self.best_queued_number, self.best_queued_hash); + let old_peers = std::mem::take(&mut self.peers); + + old_peers.into_iter().filter_map(move |(id, mut p)| { + // peers that were downloading justifications + // should be kept in that state. + if let PeerSyncState::DownloadingJustification(_) = p.state { + // We make sure our commmon number is at least something we have. + p.common_number = self.best_queued_number; + self.peers.insert(id, p); + return None + } + + // handle peers that were in other states. + match self.new_peer(id, p.best_hash, p.best_number) { + Ok(None) => None, + Ok(Some(x)) => Some(Ok((id, x))), + Err(e) => Some(Err(e)), + } + }) + } + + /// Find a block to start sync from. If we sync with state, that's the latest block we have + /// state for. + fn reset_sync_start_point(&mut self) -> Result<(), ClientError> { + let info = self.client.info(); + if matches!(self.mode, SyncMode::LightState { .. }) && info.finalized_state.is_some() { + warn!( + target: "sync", + "Can't use fast sync mode with a partially synced database. Reverting to full sync mode." + ); + self.mode = SyncMode::Full; + } + if matches!(self.mode, SyncMode::Warp) && info.finalized_state.is_some() { + warn!( + target: "sync", + "Can't use warp sync mode with a partially synced database. Reverting to full sync mode." + ); + self.mode = SyncMode::Full; + } + self.import_existing = false; + self.best_queued_hash = info.best_hash; + self.best_queued_number = info.best_number; + + if self.mode == SyncMode::Full && + self.client.block_status(&BlockId::hash(info.best_hash))? != + BlockStatus::InChainWithState + { + self.import_existing = true; + // Latest state is missing, start with the last finalized state or genesis instead. + if let Some((hash, number)) = info.finalized_state { + debug!(target: "sync", "Starting from finalized state #{}", number); + self.best_queued_hash = hash; + self.best_queued_number = number; + } else { + debug!(target: "sync", "Restarting from genesis"); + self.best_queued_hash = Default::default(); + self.best_queued_number = Zero::zero(); + } + } + + if let Some((start, end)) = info.block_gap { + debug!(target: "sync", "Starting gap sync #{} - #{}", start, end); + self.gap_sync = Some(GapSync { + best_queued_number: start - One::one(), + target: end, + blocks: BlockCollection::new(), + }); + } + trace!(target: "sync", "Restarted sync at #{} ({:?})", self.best_queued_number, self.best_queued_hash); + Ok(()) + } + + /// What is the status of the block corresponding to the given hash? + fn block_status(&self, hash: &B::Hash) -> Result { + if self.queue_blocks.contains(hash) { + return Ok(BlockStatus::Queued) + } + self.client.block_status(&BlockId::Hash(*hash)) + } + + /// Is the block corresponding to the given hash known? + fn is_known(&self, hash: &B::Hash) -> bool { + self.block_status(hash).ok().map_or(false, |s| s != BlockStatus::Unknown) + } + + /// Is any peer downloading the given hash? + fn is_already_downloading(&self, hash: &B::Hash) -> bool { + self.peers + .iter() + .any(|(_, p)| p.state == PeerSyncState::DownloadingStale(*hash)) + } + + /// Get the set of downloaded blocks that are ready to be queued for import. + fn ready_blocks(&mut self) -> Vec> { + self.blocks + .ready_blocks(self.best_queued_number + One::one()) + .into_iter() + .map(|block_data| { + let justifications = block_data + .block + .justifications + .or_else(|| legacy_justification_mapping(block_data.block.justification)); + IncomingBlock { + hash: block_data.block.hash, + header: block_data.block.header, + body: block_data.block.body, + indexed_body: block_data.block.indexed_body, + justifications, + origin: block_data.origin, + allow_missing_state: true, + import_existing: self.import_existing, + skip_execution: self.skip_execution(), + state: None, + } + }) + .collect() + } + + /// Generate block request for downloading of the target block body during warp sync. + fn warp_target_block_request(&mut self) -> Option<(PeerId, BlockRequest)> { + let sync = &self.warp_sync.as_ref()?; + + if self.allowed_requests.is_empty() || + sync.is_complete() || + self.peers + .iter() + .any(|(_, peer)| peer.state == PeerSyncState::DownloadingWarpTargetBlock) + { + // Only one pending warp target block request is allowed. + return None + } + + if let Some((target_number, request)) = sync.next_target_block_request() { + // Find a random peer that has a block with the target number. + for (id, peer) in self.peers.iter_mut() { + if peer.state.is_available() && peer.best_number >= target_number { + trace!(target: "sync", "New warp target block request for {}", id); + peer.state = PeerSyncState::DownloadingWarpTargetBlock; + self.allowed_requests.clear(); + return Some((*id, request)) + } + } + } + + None + } + + /// Get config for the block announcement protocol + pub fn get_block_announce_proto_config( + protocol_id: ProtocolId, + fork_id: &Option, + roles: Roles, + best_number: NumberFor, + best_hash: B::Hash, + genesis_hash: B::Hash, + ) -> NonDefaultSetConfig { + let block_announces_protocol = { + let genesis_hash = genesis_hash.as_ref(); + if let Some(ref fork_id) = fork_id { + format!( + "/{}/{}/block-announces/1", + array_bytes::bytes2hex("", genesis_hash), + fork_id + ) + } else { + format!("/{}/block-announces/1", array_bytes::bytes2hex("", genesis_hash)) + } + }; + + NonDefaultSetConfig { + notifications_protocol: block_announces_protocol.into(), + fallback_names: iter::once( + format!("/{}/block-announces/1", protocol_id.as_ref()).into(), + ) + .collect(), + max_notification_size: MAX_BLOCK_ANNOUNCE_SIZE, + handshake: Some(NotificationHandshake::new(BlockAnnouncesHandshake::::build( + roles, + best_number, + best_hash, + genesis_hash, + ))), + // NOTE: `set_config` will be ignored by `protocol.rs` as the block announcement + // protocol is still hardcoded into the peerset. + set_config: SetConfig { + in_peers: 0, + out_peers: 0, + reserved_nodes: Vec::new(), + non_reserved_mode: NonReservedPeerMode::Deny, + }, + } + } + + fn decode_block_response(response: &[u8]) -> Result { + let response = schema::v1::BlockResponse::decode(response) + .map_err(|error| format!("Failed to decode block response: {error}"))?; + + Ok(OpaqueBlockResponse(Box::new(response))) + } + + fn decode_state_response(response: &[u8]) -> Result { + let response = StateResponse::decode(response) + .map_err(|error| format!("Failed to decode state response: {error}"))?; + + Ok(OpaqueStateResponse(Box::new(response))) + } + + fn send_state_request(&mut self, who: PeerId, request: OpaqueStateRequest) { + let (tx, rx) = oneshot::channel(); + + if self.peers.contains_key(&who) { + self.pending_responses + .push(Box::pin(async move { (who, PeerRequest::State, rx.await) })); + } + + match self.encode_state_request(&request) { + Ok(data) => { + self.network_service.start_request( + who, + self.state_request_protocol_name.clone(), + data, + tx, + IfDisconnected::ImmediateError, + ); + }, + Err(err) => { + log::warn!( + target: "sync", + "Failed to encode state request {:?}: {:?}", + request, err + ); + }, + } + } + + fn send_warp_sync_request(&mut self, who: PeerId, request: WarpProofRequest) { + let (tx, rx) = oneshot::channel(); + + if self.peers.contains_key(&who) { + self.pending_responses + .push(Box::pin(async move { (who, PeerRequest::WarpProof, rx.await) })); + } + + match &self.warp_sync_protocol_name { + Some(name) => self.network_service.start_request( + who, + name.clone(), + request.encode(), + tx, + IfDisconnected::ImmediateError, + ), + None => { + log::warn!( + target: "sync", + "Trying to send warp sync request when no protocol is configured {:?}", + request, + ); + }, + } + } + + fn on_block_response( + &mut self, + peer_id: PeerId, + request: BlockRequest, + response: OpaqueBlockResponse, + ) -> Option> { + let blocks = match self.block_response_into_blocks(&request, response) { + Ok(blocks) => blocks, + Err(err) => { + debug!(target: "sync", "Failed to decode block response from {}: {}", peer_id, err); + self.network_service.report_peer(peer_id, rep::BAD_MESSAGE); + return None + }, + }; + + let block_response = BlockResponse:: { id: request.id, blocks }; + + let blocks_range = || match ( + block_response + .blocks + .first() + .and_then(|b| b.header.as_ref().map(|h| h.number())), + block_response.blocks.last().and_then(|b| b.header.as_ref().map(|h| h.number())), + ) { + (Some(first), Some(last)) if first != last => format!(" ({}..{})", first, last), + (Some(first), Some(_)) => format!(" ({})", first), + _ => Default::default(), + }; + trace!( + target: "sync", + "BlockResponse {} from {} with {} blocks {}", + block_response.id, + peer_id, + block_response.blocks.len(), + blocks_range(), + ); + + if request.fields == BlockAttributes::JUSTIFICATION { + match self.on_block_justification(peer_id, block_response) { + Ok(OnBlockJustification::Nothing) => None, + Ok(OnBlockJustification::Import { peer, hash, number, justifications }) => { + self.import_justifications(peer, hash, number, justifications); + None + }, + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu); + None + }, + } + } else { + match self.on_block_data(&peer_id, Some(request), block_response) { + Ok(OnBlockData::Import(origin, blocks)) => { + self.import_blocks(origin, blocks); + None + }, + Ok(OnBlockData::Request(peer, req)) => { + self.send_block_request(peer, req); + None + }, + Ok(OnBlockData::Continue) => None, + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu); + None + }, + } + } + } + + pub fn on_state_response( + &mut self, + peer_id: PeerId, + response: OpaqueStateResponse, + ) -> Option> { + match self.on_state_data(&peer_id, response) { + Ok(OnStateData::Import(origin, block)) => + Some(ImportResult::BlockImport(origin, vec![block])), + Ok(OnStateData::Continue) => None, + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu); + None + }, + } + } + + pub fn on_warp_sync_response(&mut self, peer_id: PeerId, response: EncodedProof) { + if let Err(BadPeer(id, repu)) = self.on_warp_sync_data(&peer_id, response) { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu); + } + } + + fn process_outbound_requests(&mut self) { + for (id, request) in self.block_requests() { + self.send_block_request(id, request); + } + + if let Some((id, request)) = self.state_request() { + self.send_state_request(id, request); + } + + for (id, request) in self.justification_requests().collect::>() { + self.send_block_request(id, request); + } + + if let Some((id, request)) = self.warp_sync_request() { + self.send_warp_sync_request(id, request); + } + } + + fn poll_pending_responses(&mut self, cx: &mut std::task::Context) -> Poll> { + while let Poll::Ready(Some((id, request, response))) = + self.pending_responses.poll_next_unpin(cx) + { + match response { + Ok(Ok(resp)) => match request { + PeerRequest::Block(req) => { + let response = match Self::decode_block_response(&resp[..]) { + Ok(proto) => proto, + Err(e) => { + debug!( + target: "sync", + "Failed to decode block response from peer {:?}: {:?}.", + id, + e + ); + self.network_service.report_peer(id, rep::BAD_MESSAGE); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + continue + }, + }; + + if let Some(import) = self.on_block_response(id, req, response) { + return Poll::Ready(import) + } + }, + PeerRequest::State => { + let response = match Self::decode_state_response(&resp[..]) { + Ok(proto) => proto, + Err(e) => { + debug!( + target: "sync", + "Failed to decode state response from peer {:?}: {:?}.", + id, + e + ); + self.network_service.report_peer(id, rep::BAD_MESSAGE); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + continue + }, + }; + + if let Some(import) = self.on_state_response(id, response) { + return Poll::Ready(import) + } + }, + PeerRequest::WarpProof => { + self.on_warp_sync_response(id, EncodedProof(resp)); + }, + }, + Ok(Err(e)) => { + debug!(target: "sync", "Request to peer {:?} failed: {:?}.", id, e); + + match e { + RequestFailure::Network(OutboundFailure::Timeout) => { + self.network_service.report_peer(id, rep::TIMEOUT); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + RequestFailure::Network(OutboundFailure::UnsupportedProtocols) => { + self.network_service.report_peer(id, rep::BAD_PROTOCOL); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + RequestFailure::Network(OutboundFailure::DialFailure) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + RequestFailure::Refused => { + self.network_service.report_peer(id, rep::REFUSED); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + RequestFailure::Network(OutboundFailure::ConnectionClosed) | + RequestFailure::NotConnected => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + RequestFailure::UnknownProtocol => { + debug_assert!(false, "Block request protocol should always be known."); + }, + RequestFailure::Obsolete => { + debug_assert!( + false, + "Can not receive `RequestFailure::Obsolete` after dropping the \ + response receiver.", + ); + }, + } + }, + Err(oneshot::Canceled) => { + trace!( + target: "sync", + "Request to peer {:?} failed due to oneshot being canceled.", + id, + ); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + } + } + + Poll::Pending + } + + /// Create implementation-specific block request. + fn create_opaque_block_request(&self, request: &BlockRequest) -> OpaqueBlockRequest { + OpaqueBlockRequest(Box::new(schema::v1::BlockRequest { + fields: request.fields.to_be_u32(), + from_block: match request.from { + FromBlock::Hash(h) => Some(schema::v1::block_request::FromBlock::Hash(h.encode())), + FromBlock::Number(n) => + Some(schema::v1::block_request::FromBlock::Number(n.encode())), + }, + direction: request.direction as i32, + max_blocks: request.max.unwrap_or(0), + support_multiple_justifications: true, + })) + } + + fn encode_block_request(&self, request: &OpaqueBlockRequest) -> Result, String> { + let request: &schema::v1::BlockRequest = request.0.downcast_ref().ok_or_else(|| { + "Failed to downcast opaque block response during encoding, this is an \ + implementation bug." + .to_string() + })?; + + Ok(request.encode_to_vec()) + } + + fn encode_state_request(&self, request: &OpaqueStateRequest) -> Result, String> { + let request: &StateRequest = request.0.downcast_ref().ok_or_else(|| { + "Failed to downcast opaque state response during encoding, this is an \ + implementation bug." + .to_string() + })?; + + Ok(request.encode_to_vec()) + } + + fn justification_requests<'a>( + &'a mut self, + ) -> Box)> + 'a> { + let peers = &mut self.peers; + let mut matcher = self.extra_justifications.matcher(); + Box::new(std::iter::from_fn(move || { + if let Some((peer, request)) = matcher.next(peers) { + peers + .get_mut(&peer) + .expect( + "`Matcher::next` guarantees the `PeerId` comes from the given peers; qed", + ) + .state = PeerSyncState::DownloadingJustification(request.0); + let req = BlockRequest:: { + id: 0, + fields: BlockAttributes::JUSTIFICATION, + from: FromBlock::Hash(request.0), + direction: Direction::Ascending, + max: Some(1), + }; + Some((peer, req)) + } else { + None + } + })) + } + + fn block_requests(&mut self) -> Vec<(PeerId, BlockRequest)> { + if self.mode == SyncMode::Warp { + return self + .warp_target_block_request() + .map_or_else(|| Vec::new(), |req| Vec::from([req])) + } + + if self.allowed_requests.is_empty() || self.state_sync.is_some() { + return Vec::new() + } + + if self.queue_blocks.len() > MAX_IMPORTING_BLOCKS { + trace!(target: "sync", "Too many blocks in the queue."); + return Vec::new() + } + let is_major_syncing = self.status().state.is_major_syncing(); + let attrs = self.required_block_attributes(); + let blocks = &mut self.blocks; + let fork_targets = &mut self.fork_targets; + let last_finalized = + std::cmp::min(self.best_queued_number, self.client.info().finalized_number); + let best_queued = self.best_queued_number; + let client = &self.client; + let queue = &self.queue_blocks; + let allowed_requests = self.allowed_requests.take(); + let max_parallel = if is_major_syncing { 1 } else { self.max_parallel_downloads }; + let gap_sync = &mut self.gap_sync; + self.peers + .iter_mut() + .filter_map(move |(&id, peer)| { + if !peer.state.is_available() || !allowed_requests.contains(&id) { + return None + } + + // If our best queued is more than `MAX_BLOCKS_TO_LOOK_BACKWARDS` blocks away from + // the common number, the peer best number is higher than our best queued and the + // common number is smaller than the last finalized block number, we should do an + // ancestor search to find a better common block. If the queue is full we wait till + // all blocks are imported though. + if best_queued.saturating_sub(peer.common_number) > + MAX_BLOCKS_TO_LOOK_BACKWARDS.into() && + best_queued < peer.best_number && + peer.common_number < last_finalized && + queue.len() <= MAJOR_SYNC_BLOCKS.into() + { + trace!( + target: "sync", + "Peer {:?} common block {} too far behind of our best {}. Starting ancestry search.", + id, + peer.common_number, + best_queued, + ); + let current = std::cmp::min(peer.best_number, best_queued); + peer.state = PeerSyncState::AncestorSearch { + current, + start: best_queued, + state: AncestorSearchState::ExponentialBackoff(One::one()), + }; + Some((id, ancestry_request::(current))) + } else if let Some((range, req)) = peer_block_request( + &id, + peer, + blocks, + attrs, + max_parallel, + last_finalized, + best_queued, + ) { + peer.state = PeerSyncState::DownloadingNew(range.start); + trace!( + target: "sync", + "New block request for {}, (best:{}, common:{}) {:?}", + id, + peer.best_number, + peer.common_number, + req, + ); + Some((id, req)) + } else if let Some((hash, req)) = fork_sync_request( + &id, + fork_targets, + best_queued, + last_finalized, + attrs, + |hash| { + if queue.contains(hash) { + BlockStatus::Queued + } else { + client + .block_status(&BlockId::Hash(*hash)) + .unwrap_or(BlockStatus::Unknown) + } + }, + ) { + trace!(target: "sync", "Downloading fork {:?} from {}", hash, id); + peer.state = PeerSyncState::DownloadingStale(hash); + Some((id, req)) + } else if let Some((range, req)) = gap_sync.as_mut().and_then(|sync| { + peer_gap_block_request( + &id, + peer, + &mut sync.blocks, + attrs, + sync.target, + sync.best_queued_number, + ) + }) { + peer.state = PeerSyncState::DownloadingGap(range.start); + trace!( + target: "sync", + "New gap block request for {}, (best:{}, common:{}) {:?}", + id, + peer.best_number, + peer.common_number, + req, + ); + Some((id, req)) + } else { + None + } + }) + .collect() + // Box::new(iter) + } + + fn state_request(&mut self) -> Option<(PeerId, OpaqueStateRequest)> { + if self.allowed_requests.is_empty() { + return None + } + if (self.state_sync.is_some() || self.warp_sync.is_some()) && + self.peers.iter().any(|(_, peer)| peer.state == PeerSyncState::DownloadingState) + { + // Only one pending state request is allowed. + return None + } + if let Some(sync) = &self.state_sync { + if sync.is_complete() { + return None + } + + for (id, peer) in self.peers.iter_mut() { + if peer.state.is_available() && peer.common_number >= sync.target_block_num() { + peer.state = PeerSyncState::DownloadingState; + let request = sync.next_request(); + trace!(target: "sync", "New StateRequest for {}: {:?}", id, request); + self.allowed_requests.clear(); + return Some((*id, OpaqueStateRequest(Box::new(request)))) + } } } - trace!(target: "sync", "Restarted sync at #{} ({:?})", self.best_queued_number, self.best_queued_hash); - Ok(()) + if let Some(sync) = &self.warp_sync { + if sync.is_complete() { + return None + } + if let (Some(request), Some(target)) = + (sync.next_state_request(), sync.target_block_number()) + { + for (id, peer) in self.peers.iter_mut() { + if peer.state.is_available() && peer.best_number >= target { + trace!(target: "sync", "New StateRequest for {}: {:?}", id, request); + peer.state = PeerSyncState::DownloadingState; + self.allowed_requests.clear(); + return Some((*id, OpaqueStateRequest(Box::new(request)))) + } + } + } + } + None } - /// What is the status of the block corresponding to the given hash? - fn block_status(&self, hash: &B::Hash) -> Result { - if self.queue_blocks.contains(hash) { - return Ok(BlockStatus::Queued) + fn warp_sync_request(&mut self) -> Option<(PeerId, WarpProofRequest)> { + if let Some(sync) = &self.warp_sync { + if self.allowed_requests.is_empty() || + sync.is_complete() || + self.peers + .iter() + .any(|(_, peer)| peer.state == PeerSyncState::DownloadingWarpProof) + { + // Only one pending state request is allowed. + return None + } + if let Some(request) = sync.next_warp_proof_request() { + let mut targets: Vec<_> = self.peers.values().map(|p| p.best_number).collect(); + if !targets.is_empty() { + targets.sort(); + let median = targets[targets.len() / 2]; + // Find a random peer that is synced as much as peer majority. + for (id, peer) in self.peers.iter_mut() { + if peer.state.is_available() && peer.best_number >= median { + trace!(target: "sync", "New WarpProofRequest for {}", id); + peer.state = PeerSyncState::DownloadingWarpProof; + self.allowed_requests.clear(); + return Some((*id, request)) + } + } + } + } } - self.client.block_status(&BlockId::Hash(*hash)) + None } - /// Is the block corresponding to the given hash known? - fn is_known(&self, hash: &B::Hash) -> bool { - self.block_status(hash).ok().map_or(false, |s| s != BlockStatus::Unknown) + fn on_state_data( + &mut self, + who: &PeerId, + response: OpaqueStateResponse, + ) -> Result, BadPeer> { + let response: Box = response.0.downcast().map_err(|_error| { + error!( + target: "sync", + "Failed to downcast opaque state response, this is an implementation bug." + ); + + BadPeer(*who, rep::BAD_RESPONSE) + })?; + + if let Some(peer) = self.peers.get_mut(who) { + if let PeerSyncState::DownloadingState = peer.state { + peer.state = PeerSyncState::Available; + self.allowed_requests.set_all(); + } + } + let import_result = if let Some(sync) = &mut self.state_sync { + debug!( + target: "sync", + "Importing state data from {} with {} keys, {} proof nodes.", + who, + response.entries.len(), + response.proof.len(), + ); + sync.import(*response) + } else if let Some(sync) = &mut self.warp_sync { + debug!( + target: "sync", + "Importing state data from {} with {} keys, {} proof nodes.", + who, + response.entries.len(), + response.proof.len(), + ); + sync.import_state(*response) + } else { + debug!(target: "sync", "Ignored obsolete state response from {}", who); + return Err(BadPeer(*who, rep::NOT_REQUESTED)) + }; + + match import_result { + state::ImportResult::Import(hash, header, state, body, justifications) => { + let origin = BlockOrigin::NetworkInitialSync; + let block = IncomingBlock { + hash, + header: Some(header), + body, + indexed_body: None, + justifications, + origin: None, + allow_missing_state: true, + import_existing: true, + skip_execution: self.skip_execution(), + state: Some(state), + }; + debug!(target: "sync", "State download is complete. Import is queued"); + Ok(OnStateData::Import(origin, block)) + }, + state::ImportResult::Continue => Ok(OnStateData::Continue), + state::ImportResult::BadResponse => { + debug!(target: "sync", "Bad state data received from {}", who); + Err(BadPeer(*who, rep::BAD_BLOCK)) + }, + } } - /// Is any peer downloading the given hash? - fn is_already_downloading(&self, hash: &B::Hash) -> bool { - self.peers - .iter() - .any(|(_, p)| p.state == PeerSyncState::DownloadingStale(*hash)) + fn on_warp_sync_data(&mut self, who: &PeerId, response: EncodedProof) -> Result<(), BadPeer> { + if let Some(peer) = self.peers.get_mut(who) { + if let PeerSyncState::DownloadingWarpProof = peer.state { + peer.state = PeerSyncState::Available; + self.allowed_requests.set_all(); + } + } + let import_result = if let Some(sync) = &mut self.warp_sync { + debug!( + target: "sync", + "Importing warp proof data from {}, {} bytes.", + who, + response.0.len(), + ); + sync.import_warp_proof(response) + } else { + debug!(target: "sync", "Ignored obsolete warp sync response from {}", who); + return Err(BadPeer(*who, rep::NOT_REQUESTED)) + }; + + match import_result { + WarpProofImportResult::Success => Ok(()), + WarpProofImportResult::BadResponse => { + debug!(target: "sync", "Bad proof data received from {}", who); + Err(BadPeer(*who, rep::BAD_BLOCK)) + }, + } } - /// Return some key metrics. - pub(crate) fn metrics(&self) -> Metrics { - use std::convert::TryInto; - Metrics { - queued_blocks: self.queue_blocks.len().try_into().unwrap_or(std::u32::MAX), - fork_targets: self.fork_targets.len().try_into().unwrap_or(std::u32::MAX), - justifications: self.extra_justifications.metrics(), - _priv: (), + fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>) { + if let Some(metrics) = &self.metrics { + metrics.import_queue_blocks_submitted.inc(); + } + + self.import_queue.import_blocks(origin, blocks); + } + + fn import_justifications( + &mut self, + peer: PeerId, + hash: B::Hash, + number: NumberFor, + justifications: Justifications, + ) { + if let Some(metrics) = &self.metrics { + metrics.import_queue_justifications_submitted.inc(); + } + + self.import_queue.import_justifications(peer, hash, number, justifications); + } + + /// A batch of blocks have been processed, with or without errors. + /// + /// Call this when a batch of blocks have been processed by the import + /// queue, with or without errors. + fn on_blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<(Result>, BlockImportError>, B::Hash)>, + ) -> Box), BadPeer>>> { + trace!(target: "sync", "Imported {} of {}", imported, count); + + let mut output = Vec::new(); + + let mut has_error = false; + for (_, hash) in &results { + self.queue_blocks.remove(hash); + self.blocks.clear_queued(hash); + if let Some(gap_sync) = &mut self.gap_sync { + gap_sync.blocks.clear_queued(hash); + } + } + for (result, hash) in results { + if has_error { + break + } + + if result.is_err() { + has_error = true; + } + + match result { + Ok(BlockImportStatus::ImportedKnown(number, who)) => + if let Some(peer) = who { + self.update_peer_common_number(&peer, number); + }, + Ok(BlockImportStatus::ImportedUnknown(number, aux, who)) => { + if aux.clear_justification_requests { + trace!( + target: "sync", + "Block imported clears all pending justification requests {}: {:?}", + number, + hash, + ); + self.clear_justification_requests(); + } + + if aux.needs_justification { + trace!( + target: "sync", + "Block imported but requires justification {}: {:?}", + number, + hash, + ); + self.request_justification(&hash, number); + } + + if aux.bad_justification { + if let Some(ref peer) = who { + warn!("💔 Sent block with bad justification to import"); + output.push(Err(BadPeer(*peer, rep::BAD_JUSTIFICATION))); + } + } + + if let Some(peer) = who { + self.update_peer_common_number(&peer, number); + } + let state_sync_complete = + self.state_sync.as_ref().map_or(false, |s| s.target() == hash); + if state_sync_complete { + info!( + target: "sync", + "State sync is complete ({} MiB), restarting block sync.", + self.state_sync.as_ref().map_or(0, |s| s.progress().size / (1024 * 1024)), + ); + self.state_sync = None; + self.mode = SyncMode::Full; + output.extend(self.restart()); + } + let warp_sync_complete = self + .warp_sync + .as_ref() + .map_or(false, |s| s.target_block_hash() == Some(hash)); + if warp_sync_complete { + info!( + target: "sync", + "Warp sync is complete ({} MiB), restarting block sync.", + self.warp_sync.as_ref().map_or(0, |s| s.progress().total_bytes / (1024 * 1024)), + ); + self.warp_sync = None; + self.mode = SyncMode::Full; + output.extend(self.restart()); + } + let gap_sync_complete = + self.gap_sync.as_ref().map_or(false, |s| s.target == number); + if gap_sync_complete { + info!( + target: "sync", + "Block history download is complete." + ); + self.gap_sync = None; + } + }, + Err(BlockImportError::IncompleteHeader(who)) => + if let Some(peer) = who { + warn!( + target: "sync", + "💔 Peer sent block with incomplete header to import", + ); + output.push(Err(BadPeer(peer, rep::INCOMPLETE_HEADER))); + output.extend(self.restart()); + }, + Err(BlockImportError::VerificationFailed(who, e)) => + if let Some(peer) = who { + warn!( + target: "sync", + "💔 Verification failed for block {:?} received from peer: {}, {:?}", + hash, + peer, + e, + ); + output.push(Err(BadPeer(peer, rep::VERIFICATION_FAIL))); + output.extend(self.restart()); + }, + Err(BlockImportError::BadBlock(who)) => + if let Some(peer) = who { + warn!( + target: "sync", + "💔 Block {:?} received from peer {} has been blacklisted", + hash, + peer, + ); + output.push(Err(BadPeer(peer, rep::BAD_BLOCK))); + }, + Err(BlockImportError::MissingState) => { + // This may happen if the chain we were requesting upon has been discarded + // in the meantime because other chain has been finalized. + // Don't mark it as bad as it still may be synced if explicitly requested. + trace!(target: "sync", "Obsolete block {:?}", hash); + }, + e @ Err(BlockImportError::UnknownParent) | e @ Err(BlockImportError::Other(_)) => { + warn!(target: "sync", "💔 Error importing block {:?}: {}", hash, e.unwrap_err()); + self.state_sync = None; + self.warp_sync = None; + output.extend(self.restart()); + }, + Err(BlockImportError::Cancelled) => {}, + }; } - } - /// Drain the downloaded block set up to the first gap. - fn drain_blocks(&mut self) -> Vec> { - self.blocks - .drain(self.best_queued_number + One::one()) - .into_iter() - .map(|block_data| { - let justifications = block_data - .block - .justifications - .or(legacy_justification_mapping(block_data.block.justification)); - IncomingBlock { - hash: block_data.block.hash, - header: block_data.block.header, - body: block_data.block.body, - indexed_body: block_data.block.indexed_body, - justifications, - origin: block_data.origin, - allow_missing_state: true, - import_existing: self.import_existing, - skip_execution: self.skip_execution(), - state: None, - } - }) - .collect() + self.allowed_requests.set_all(); + Box::new(output.into_iter()) } } @@ -2115,23 +2853,14 @@ fn legacy_justification_mapping( justification.map(|just| (*b"FRNK", just).into()) } -#[derive(Debug)] -pub(crate) struct Metrics { - pub(crate) queued_blocks: u32, - pub(crate) fork_targets: u32, - pub(crate) justifications: extra_requests::Metrics, - _priv: (), -} - /// Request the ancestry for a block. Sends a request for header and justification for the given /// block number. Used during ancestry search. fn ancestry_request(block: NumberFor) -> BlockRequest { - message::generic::BlockRequest { + BlockRequest:: { id: 0, fields: BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION, - from: message::FromBlock::Number(block), - to: None, - direction: message::Direction::Ascending, + from: FromBlock::Number(block), + direction: Direction::Ascending, max: Some(1), } } @@ -2195,7 +2924,11 @@ fn handle_ancestor_search_state( } assert!(right >= left); let middle = left + (right - left) / two; - Some((AncestorSearchState::BinarySearch(left, right), middle)) + if middle == curr_block_num { + None + } else { + Some((AncestorSearchState::BinarySearch(left, right), middle)) + } }, } } @@ -2205,7 +2938,7 @@ fn peer_block_request( id: &PeerId, peer: &PeerSync, blocks: &mut BlockCollection, - attrs: message::BlockAttributes, + attrs: BlockAttributes, max_parallel_downloads: u32, finalized: NumberFor, best_num: NumberFor, @@ -2233,30 +2966,61 @@ fn peer_block_request( let last = range.end.saturating_sub(One::one()); let from = if peer.best_number == last { - message::FromBlock::Hash(peer.best_hash) + FromBlock::Hash(peer.best_hash) } else { - message::FromBlock::Number(last) + FromBlock::Number(last) }; - let request = message::generic::BlockRequest { + let request = BlockRequest:: { id: 0, - fields: attrs.clone(), + fields: attrs, from, - to: None, - direction: message::Direction::Descending, + direction: Direction::Descending, max: Some((range.end - range.start).saturated_into::()), }; Some((range, request)) } +/// Get a new block request for the peer if any. +fn peer_gap_block_request( + id: &PeerId, + peer: &PeerSync, + blocks: &mut BlockCollection, + attrs: BlockAttributes, + target: NumberFor, + common_number: NumberFor, +) -> Option<(Range>, BlockRequest)> { + let range = blocks.needed_blocks( + *id, + MAX_BLOCKS_TO_REQUEST, + std::cmp::min(peer.best_number, target), + common_number, + 1, + MAX_DOWNLOAD_AHEAD, + )?; + + // The end is not part of the range. + let last = range.end.saturating_sub(One::one()); + let from = FromBlock::Number(last); + + let request = BlockRequest:: { + id: 0, + fields: attrs, + from, + direction: Direction::Descending, + max: Some((range.end - range.start).saturated_into::()), + }; + Some((range, request)) +} + /// Get pending fork sync targets for a peer. fn fork_sync_request( id: &PeerId, targets: &mut HashMap>, best_num: NumberFor, finalized: NumberFor, - attributes: message::BlockAttributes, + attributes: BlockAttributes, check_block: impl Fn(&B::Hash) -> BlockStatus, ) -> Option<(B::Hash, BlockRequest)> { targets.retain(|hash, r| { @@ -2271,10 +3035,14 @@ fn fork_sync_request( true }); for (hash, r) in targets { - if !r.peers.contains(id) { + if !r.peers.contains(&id) { continue } - if r.number <= best_num { + // Download the fork only if it is behind or not too far ahead our tip of the chain + // Otherwise it should be downloaded in full sync mode. + if r.number <= best_num || + (r.number - best_num).saturated_into::() < MAX_BLOCKS_TO_REQUEST as u32 + { let parent_status = r.parent_hash.as_ref().map_or(BlockStatus::Unknown, check_block); let count = if parent_status == BlockStatus::Unknown { (r.number - finalized).saturated_into::() // up to the last finalized block @@ -2284,16 +3052,17 @@ fn fork_sync_request( }; trace!(target: "sync", "Downloading requested fork {:?} from {}, {} blocks", hash, id, count); return Some(( - hash.clone(), - message::generic::BlockRequest { + *hash, + BlockRequest:: { id: 0, - fields: attributes.clone(), - from: message::FromBlock::Hash(hash.clone()), - to: None, - direction: message::Direction::Descending, + fields: attributes, + from: FromBlock::Hash(*hash), + direction: Direction::Descending, max: Some(count), }, )) + } else { + trace!(target: "sync", "Fork too far in the future: {:?} (#{})", hash, r.number); } } None @@ -2323,7 +3092,7 @@ where /// /// It is expected that `blocks` are in ascending order. fn validate_blocks( - blocks: &Vec>, + blocks: &Vec>, who: &PeerId, request: Option>, ) -> Result>, BadPeer> { @@ -2340,16 +3109,13 @@ fn validate_blocks( return Err(BadPeer(*who, rep::NOT_REQUESTED)) } - let block_header = if request.direction == message::Direction::Descending { - blocks.last() - } else { - blocks.first() - } - .and_then(|b| b.header.as_ref()); + let block_header = + if request.direction == Direction::Descending { blocks.last() } else { blocks.first() } + .and_then(|b| b.header.as_ref()); let expected_block = block_header.as_ref().map_or(false, |h| match request.from { - message::FromBlock::Hash(hash) => h.hash() == hash, - message::FromBlock::Number(n) => h.number() == &n, + FromBlock::Hash(hash) => h.hash() == hash, + FromBlock::Number(n) => h.number() == &n, }); if !expected_block { @@ -2363,7 +3129,7 @@ fn validate_blocks( return Err(BadPeer(*who, rep::NOT_REQUESTED)) } - if request.fields.contains(message::BlockAttributes::HEADER) && + if request.fields.contains(BlockAttributes::HEADER) && blocks.iter().any(|b| b.header.is_none()) { trace!( @@ -2375,8 +3141,7 @@ fn validate_blocks( return Err(BadPeer(*who, rep::BAD_RESPONSE)) } - if request.fields.contains(message::BlockAttributes::BODY) && - blocks.iter().any(|b| b.body.is_none()) + if request.fields.contains(BlockAttributes::BODY) && blocks.iter().any(|b| b.body.is_none()) { trace!( target: "sync", @@ -2404,8 +3169,10 @@ fn validate_blocks( } if let (Some(header), Some(body)) = (&b.header, &b.body) { let expected = *header.extrinsics_root(); - let got = - HashFor::::ordered_trie_root(body.iter().map(Encode::encode).collect()); + let got = HashFor::::ordered_trie_root( + body.iter().map(Encode::encode).collect(), + sp_runtime::StateVersion::V0, + ); if expected != got { debug!( target:"sync", @@ -2425,12 +3192,14 @@ fn validate_blocks( #[cfg(test)] mod test { - use super::{ - message::{BlockData, BlockState, FromBlock}, - *, - }; + use super::*; + use crate::service::network::NetworkServiceProvider; use futures::{executor::block_on, future::poll_fn}; use sc_block_builder::BlockBuilderProvider; + use sc_network_common::{ + protocol::role::Role, + sync::message::{BlockData, BlockState, FromBlock}, + }; use sp_blockchain::HeaderBackend; use sp_consensus::block_validation::DefaultBlockAnnounceValidator; use substrate_test_runtime_client::{ @@ -2449,9 +3218,26 @@ mod test { let block_announce_validator = Box::new(DefaultBlockAnnounceValidator); let peer_id = PeerId::random(); - let mut sync = - ChainSync::new(SyncMode::Full, client.clone(), block_announce_validator, 1, None) - .unwrap(); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = + NetworkServiceProvider::new(); + let (mut sync, _, _) = ChainSync::new( + SyncMode::Full, + client.clone(), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), + block_announce_validator, + 1, + None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, + ) + .unwrap(); let (a1_hash, a1_number) = { let a1 = client.new_block(Default::default()).unwrap().build().unwrap().block; @@ -2497,12 +3283,25 @@ mod test { #[test] fn restart_doesnt_affect_peers_downloading_finality_data() { let mut client = Arc::new(TestClientBuilder::new().build()); - let mut sync = ChainSync::new( + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = + NetworkServiceProvider::new(); + + let (mut sync, _, _) = ChainSync::new( SyncMode::Full, client.clone(), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 1, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); @@ -2523,15 +3322,18 @@ mod test { let (b1_hash, b1_number) = new_blocks(50); // add 2 peers at blocks that we don't have locally - sync.new_peer(peer_id1.clone(), Hash::random(), 42).unwrap(); - sync.new_peer(peer_id2.clone(), Hash::random(), 10).unwrap(); + sync.new_peer(peer_id1, Hash::random(), 42).unwrap(); + sync.new_peer(peer_id2, Hash::random(), 10).unwrap(); // we wil send block requests to these peers // for these blocks we don't know about - assert!(sync.block_requests().all(|(p, _)| { *p == peer_id1 || *p == peer_id2 })); + assert!(sync + .block_requests() + .into_iter() + .all(|(p, _)| { p == peer_id1 || p == peer_id2 })); // add a new peer at a known block - sync.new_peer(peer_id3.clone(), b1_hash, b1_number).unwrap(); + sync.new_peer(peer_id3, b1_hash, b1_number).unwrap(); // we request a justification for a block we have locally sync.request_justification(&b1_hash, b1_number); @@ -2541,8 +3343,7 @@ mod test { assert!(sync.justification_requests().any(|(p, r)| { p == peer_id3 && r.fields == BlockAttributes::JUSTIFICATION && - r.from == message::FromBlock::Hash(b1_hash) && - r.to == None + r.from == FromBlock::Hash(b1_hash) })); assert_eq!( @@ -2571,14 +3372,18 @@ mod test { } /// Send a block annoucnement for the given `header`. - fn send_block_announce(header: Header, peer_id: &PeerId, sync: &mut ChainSync) { + fn send_block_announce( + header: Header, + peer_id: &PeerId, + sync: &mut ChainSync, + ) { let block_annnounce = BlockAnnounce { header: header.clone(), state: Some(BlockState::Best), data: Some(Vec::new()), }; - sync.push_block_announce_validation(peer_id.clone(), header.hash(), block_annnounce, true); + sync.push_block_announce_validation(*peer_id, header.hash(), block_annnounce, true); // Poll until we have procssed the block announcement block_on(poll_fn(|cx| loop { @@ -2610,17 +3415,17 @@ mod test { /// Get a block request from `sync` and check that is matches the expected request. fn get_block_request( - sync: &mut ChainSync, + sync: &mut ChainSync, from: FromBlock, max: u32, peer: &PeerId, ) -> BlockRequest { - let requests = sync.block_requests().collect::>(); + let requests = sync.block_requests(); log::trace!(target: "sync", "Requests: {:?}", requests); assert_eq!(1, requests.len()); - assert_eq!(peer, requests[0].0); + assert_eq!(*peer, requests[0].0); let request = requests[0].1.clone(); @@ -2660,13 +3465,25 @@ mod test { sp_tracing::try_init_simple(); let mut client = Arc::new(TestClientBuilder::new().build()); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = + NetworkServiceProvider::new(); - let mut sync = ChainSync::new( + let (mut sync, _, _) = ChainSync::new( SyncMode::Full, client.clone(), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); @@ -2695,14 +3512,14 @@ mod test { let block3_fork = build_block_at(block2.hash(), false); // Add two peers which are on block 1. - sync.new_peer(peer_id1.clone(), block1.hash(), 1).unwrap(); - sync.new_peer(peer_id2.clone(), block1.hash(), 1).unwrap(); + sync.new_peer(peer_id1, block1.hash(), 1).unwrap(); + sync.new_peer(peer_id2, block1.hash(), 1).unwrap(); // Tell sync that our best block is 3. sync.update_chain_info(&block3.hash(), 3); // There should be no requests. - assert!(sync.block_requests().collect::>().is_empty()); + assert!(sync.block_requests().is_empty()); // Let peer2 announce a fork of block 3 send_block_announce(block3_fork.header().clone(), &peer_id2, &mut sync); @@ -2774,14 +3591,26 @@ mod test { }; let mut client = Arc::new(TestClientBuilder::new().build()); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = + NetworkServiceProvider::new(); let info = client.info(); - let mut sync = ChainSync::new( + let (mut sync, _, _) = ChainSync::new( SyncMode::Full, client.clone(), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); @@ -2790,9 +3619,9 @@ mod test { let best_block = blocks.last().unwrap().clone(); // Connect the node we will sync from - sync.new_peer(peer_id1.clone(), best_block.hash(), *best_block.header().number()) + sync.new_peer(peer_id1, best_block.hash(), *best_block.header().number()) .unwrap(); - sync.new_peer(peer_id2.clone(), info.best_hash, 0).unwrap(); + sync.new_peer(peer_id2, info.best_hash, 0).unwrap(); let mut best_block_num = 0; while best_block_num < MAX_DOWNLOAD_AHEAD { @@ -2818,6 +3647,25 @@ mod test { best_block_num += MAX_BLOCKS_TO_REQUEST as u32; + let _ = sync.on_blocks_processed( + MAX_BLOCKS_TO_REQUEST as usize, + MAX_BLOCKS_TO_REQUEST as usize, + resp_blocks + .iter() + .rev() + .map(|b| { + ( + Ok(BlockImportStatus::ImportedUnknown( + *b.header().number(), + Default::default(), + Some(peer_id1), + )), + b.hash(), + ) + }) + .collect(), + ); + resp_blocks .into_iter() .rev() @@ -2830,15 +3678,16 @@ mod test { // Let peer2 announce that it finished syncing send_block_announce(best_block.header().clone(), &peer_id2, &mut sync); - let (peer1_req, peer2_req) = sync.block_requests().fold((None, None), |res, req| { - if req.0 == &peer_id1 { - (Some(req.1), res.1) - } else if req.0 == &peer_id2 { - (res.0, Some(req.1)) - } else { - panic!("Unexpected req: {:?}", req) - } - }); + let (peer1_req, peer2_req) = + sync.block_requests().into_iter().fold((None, None), |res, req| { + if req.0 == peer_id1 { + (Some(req.1), res.1) + } else if req.0 == peer_id2 { + (res.0, Some(req.1)) + } else { + panic!("Unexpected req: {:?}", req) + } + }); // We should now do an ancestor search to find the correct common block. let peer2_req = peer2_req.unwrap(); @@ -2876,6 +3725,9 @@ mod test { fn can_sync_huge_fork() { sp_tracing::try_init_simple(); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = + NetworkServiceProvider::new(); let mut client = Arc::new(TestClientBuilder::new().build()); let blocks = (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 4) .map(|_| build_block(&mut client, None, false)) @@ -2900,27 +3752,34 @@ mod test { let info = client.info(); - let mut sync = ChainSync::new( + let (mut sync, _, _) = ChainSync::new( SyncMode::Full, client.clone(), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); let just = (*b"TEST", Vec::new()); - client - .finalize_block(BlockId::Hash(finalized_block.hash()), Some(just)) - .unwrap(); + client.finalize_block(finalized_block.hash(), Some(just)).unwrap(); sync.update_chain_info(&info.best_hash, info.best_number); let peer_id1 = PeerId::random(); let common_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize / 2].clone(); // Connect the node we will sync from - sync.new_peer(peer_id1.clone(), common_block.hash(), *common_block.header().number()) + sync.new_peer(peer_id1, common_block.hash(), *common_block.header().number()) .unwrap(); send_block_announce(fork_blocks.last().unwrap().header().clone(), &peer_id1, &mut sync); @@ -2934,17 +3793,18 @@ mod test { let response = create_block_response(vec![block.clone()]); let on_block_data = sync.on_block_data(&peer_id1, Some(request), response).unwrap(); - request = match on_block_data.into_request() { - Some(req) => req.1, + request = if let OnBlockData::Request(_peer, request) = on_block_data { + request + } else { // We found the ancenstor - None => break, + break }; log::trace!(target: "sync", "Request: {:?}", request); } // Now request and import the fork. - let mut best_block_num = finalized_block.header().number().clone() as u32; + let mut best_block_num = *finalized_block.header().number() as u32; while best_block_num < *fork_blocks.last().unwrap().header().number() as u32 - 1 { let request = get_block_request( &mut sync, @@ -2977,9 +3837,9 @@ mod test { .map(|b| { ( Ok(BlockImportStatus::ImportedUnknown( - b.header().number().clone(), + *b.header().number(), Default::default(), - Some(peer_id1.clone()), + Some(peer_id1), )), b.hash(), ) @@ -3002,25 +3862,200 @@ mod test { ); } + #[test] + fn syncs_fork_without_duplicate_requests() { + sp_tracing::try_init_simple(); + + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = + NetworkServiceProvider::new(); + let mut client = Arc::new(TestClientBuilder::new().build()); + let blocks = (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 4) + .map(|_| build_block(&mut client, None, false)) + .collect::>(); + + let fork_blocks = { + let mut client = Arc::new(TestClientBuilder::new().build()); + let fork_blocks = blocks[..MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2] + .into_iter() + .inspect(|b| block_on(client.import(BlockOrigin::Own, (*b).clone())).unwrap()) + .cloned() + .collect::>(); + + fork_blocks + .into_iter() + .chain( + (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 2 + 1) + .map(|_| build_block(&mut client, None, true)), + ) + .collect::>() + }; + + let info = client.info(); + + let (mut sync, _, _) = ChainSync::new( + SyncMode::Full, + client.clone(), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), + Box::new(DefaultBlockAnnounceValidator), + 5, + None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, + ) + .unwrap(); + + let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); + let just = (*b"TEST", Vec::new()); + client.finalize_block(finalized_block.hash(), Some(just)).unwrap(); + sync.update_chain_info(&info.best_hash, info.best_number); + + let peer_id1 = PeerId::random(); + + let common_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize / 2].clone(); + // Connect the node we will sync from + sync.new_peer(peer_id1, common_block.hash(), *common_block.header().number()) + .unwrap(); + + send_block_announce(fork_blocks.last().unwrap().header().clone(), &peer_id1, &mut sync); + + let mut request = + get_block_request(&mut sync, FromBlock::Number(info.best_number), 1, &peer_id1); + + // Do the ancestor search + loop { + let block = &fork_blocks[unwrap_from_block_number(request.from.clone()) as usize - 1]; + let response = create_block_response(vec![block.clone()]); + + let on_block_data = sync.on_block_data(&peer_id1, Some(request), response).unwrap(); + request = if let OnBlockData::Request(_peer, request) = on_block_data { + request + } else { + // We found the ancenstor + break + }; + + log::trace!(target: "sync", "Request: {:?}", request); + } + + // Now request and import the fork. + let mut best_block_num = *finalized_block.header().number() as u32; + let mut request = get_block_request( + &mut sync, + FromBlock::Number(MAX_BLOCKS_TO_REQUEST as u64 + best_block_num as u64), + MAX_BLOCKS_TO_REQUEST as u32, + &peer_id1, + ); + let last_block_num = *fork_blocks.last().unwrap().header().number() as u32 - 1; + while best_block_num < last_block_num { + let from = unwrap_from_block_number(request.from.clone()); + + let mut resp_blocks = fork_blocks[best_block_num as usize..from as usize].to_vec(); + resp_blocks.reverse(); + + let response = create_block_response(resp_blocks.clone()); + + let res = sync.on_block_data(&peer_id1, Some(request.clone()), response).unwrap(); + assert!(matches!( + res, + OnBlockData::Import(_, blocks) if blocks.len() == MAX_BLOCKS_TO_REQUEST + ),); + + best_block_num += MAX_BLOCKS_TO_REQUEST as u32; + + if best_block_num < last_block_num { + // make sure we're not getting a duplicate request in the time before the blocks are + // processed + request = get_block_request( + &mut sync, + FromBlock::Number(MAX_BLOCKS_TO_REQUEST as u64 + best_block_num as u64), + MAX_BLOCKS_TO_REQUEST as u32, + &peer_id1, + ); + } + + let mut notify_imported: Vec<_> = resp_blocks + .iter() + .rev() + .map(|b| { + ( + Ok(BlockImportStatus::ImportedUnknown( + *b.header().number(), + Default::default(), + Some(peer_id1), + )), + b.hash(), + ) + }) + .collect(); + + // The import queue may send notifications in batches of varying size. So we simulate + // this here by splitting the batch into 2 notifications. + let second_batch = notify_imported.split_off(notify_imported.len() / 2); + let _ = sync.on_blocks_processed( + MAX_BLOCKS_TO_REQUEST as usize, + MAX_BLOCKS_TO_REQUEST as usize, + notify_imported, + ); + + let _ = sync.on_blocks_processed( + MAX_BLOCKS_TO_REQUEST as usize, + MAX_BLOCKS_TO_REQUEST as usize, + second_batch, + ); + + resp_blocks + .into_iter() + .rev() + .for_each(|b| block_on(client.import(BlockOrigin::Own, b)).unwrap()); + } + + // Request the tip + get_block_request( + &mut sync, + FromBlock::Hash(fork_blocks.last().unwrap().hash()), + 1, + &peer_id1, + ); + } + #[test] fn removes_target_fork_on_disconnect() { sp_tracing::try_init_simple(); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = + NetworkServiceProvider::new(); let mut client = Arc::new(TestClientBuilder::new().build()); let blocks = (0..3).map(|_| build_block(&mut client, None, false)).collect::>(); - let mut sync = ChainSync::new( + let (mut sync, _, _) = ChainSync::new( SyncMode::Full, client.clone(), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 1, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); let peer_id1 = PeerId::random(); let common_block = blocks[1].clone(); // Connect the node we will sync from - sync.new_peer(peer_id1.clone(), common_block.hash(), *common_block.header().number()) + sync.new_peer(peer_id1, common_block.hash(), *common_block.header().number()) .unwrap(); // Create a "new" header and announce it @@ -3032,4 +4067,61 @@ mod test { sync.peer_disconnected(&peer_id1); assert!(sync.fork_targets.len() == 0); } + + #[test] + fn can_import_response_with_missing_blocks() { + sp_tracing::try_init_simple(); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = + NetworkServiceProvider::new(); + let mut client2 = Arc::new(TestClientBuilder::new().build()); + let blocks = (0..4).map(|_| build_block(&mut client2, None, false)).collect::>(); + + let empty_client = Arc::new(TestClientBuilder::new().build()); + + let (mut sync, _, _) = ChainSync::new( + SyncMode::Full, + empty_client.clone(), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), + Box::new(DefaultBlockAnnounceValidator), + 1, + None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, + ) + .unwrap(); + + let peer_id1 = PeerId::random(); + let best_block = blocks[3].clone(); + sync.new_peer(peer_id1, best_block.hash(), *best_block.header().number()) + .unwrap(); + + sync.peers.get_mut(&peer_id1).unwrap().state = PeerSyncState::Available; + sync.peers.get_mut(&peer_id1).unwrap().common_number = 0; + + // Request all missing blocks and respond only with some. + let request = + get_block_request(&mut sync, FromBlock::Hash(best_block.hash()), 4, &peer_id1); + let response = + create_block_response(vec![blocks[3].clone(), blocks[2].clone(), blocks[1].clone()]); + sync.on_block_data(&peer_id1, Some(request.clone()), response).unwrap(); + assert_eq!(sync.best_queued_number, 0); + + // Request should only contain the missing block. + let request = get_block_request(&mut sync, FromBlock::Number(1), 1, &peer_id1); + let response = create_block_response(vec![blocks[0].clone()]); + sync.on_block_data(&peer_id1, Some(request), response).unwrap(); + assert_eq!(sync.best_queued_number, 4); + } + #[test] + fn ancestor_search_repeat() { + let state = AncestorSearchState::::BinarySearch(1, 3); + assert!(handle_ancestor_search_state(&state, 2, true).is_none()); + } } diff --git a/client/network/sync/src/mock.rs b/client/network/sync/src/mock.rs new file mode 100644 index 0000000000000..b59ea7e4fea70 --- /dev/null +++ b/client/network/sync/src/mock.rs @@ -0,0 +1,103 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Contains a mock implementation of `ChainSync` that can be used +//! for testing calls made to `ChainSync`. + +use futures::task::Poll; +use libp2p::PeerId; +use sc_network_common::sync::{ + message::{BlockAnnounce, BlockData, BlockRequest, BlockResponse}, + BadPeer, ChainSync as ChainSyncT, Metrics, OnBlockData, OnBlockJustification, + OpaqueBlockResponse, PeerInfo, PollBlockAnnounceValidation, SyncStatus, +}; +use sp_runtime::traits::{Block as BlockT, NumberFor}; + +mockall::mock! { + pub ChainSync {} + + impl ChainSyncT for ChainSync { + fn peer_info(&self, who: &PeerId) -> Option>; + fn status(&self) -> SyncStatus; + fn num_sync_requests(&self) -> usize; + fn num_downloaded_blocks(&self) -> usize; + fn num_peers(&self) -> usize; + fn num_active_peers(&self) -> usize; + fn new_peer( + &mut self, + who: PeerId, + best_hash: Block::Hash, + best_number: NumberFor, + ) -> Result>, BadPeer>; + fn update_chain_info(&mut self, best_hash: &Block::Hash, best_number: NumberFor); + fn request_justification(&mut self, hash: &Block::Hash, number: NumberFor); + fn clear_justification_requests(&mut self); + fn set_sync_fork_request( + &mut self, + peers: Vec, + hash: &Block::Hash, + number: NumberFor, + ); + fn on_block_data( + &mut self, + who: &PeerId, + request: Option>, + response: BlockResponse, + ) -> Result, BadPeer>; + fn process_block_response_data(&mut self, blocks_to_import: Result, BadPeer>); + fn on_block_justification( + &mut self, + who: PeerId, + response: BlockResponse, + ) -> Result, BadPeer>; + fn on_justification_import( + &mut self, + hash: Block::Hash, + number: NumberFor, + success: bool, + ); + fn on_block_finalized(&mut self, hash: &Block::Hash, number: NumberFor); + fn push_block_announce_validation( + &mut self, + who: PeerId, + hash: Block::Hash, + announce: BlockAnnounce, + is_best: bool, + ); + fn poll_block_announce_validation<'a>( + &mut self, + cx: &mut std::task::Context<'a>, + ) -> Poll>; + fn peer_disconnected(&mut self, who: &PeerId); + fn metrics(&self) -> Metrics; + fn block_response_into_blocks( + &self, + request: &BlockRequest, + response: OpaqueBlockResponse, + ) -> Result>, String>; + fn poll<'a>( + &mut self, + cx: &mut std::task::Context<'a>, + ) -> Poll>; + fn send_block_request( + &mut self, + who: PeerId, + request: BlockRequest, + ); + } +} diff --git a/client/network/sync/src/schema.rs b/client/network/sync/src/schema.rs new file mode 100644 index 0000000000000..b31005360d023 --- /dev/null +++ b/client/network/sync/src/schema.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Include sources generated from protobuf definitions. + +pub(crate) mod v1 { + include!(concat!(env!("OUT_DIR"), "/api.v1.rs")); +} diff --git a/client/network/src/schema/api.v1.proto b/client/network/sync/src/schema/api.v1.proto similarity index 84% rename from client/network/src/schema/api.v1.proto rename to client/network/sync/src/schema/api.v1.proto index c5333c7dcdbf1..1490f61a41ddd 100644 --- a/client/network/src/schema/api.v1.proto +++ b/client/network/sync/src/schema/api.v1.proto @@ -23,9 +23,8 @@ message BlockRequest { // Start with given block number. bytes number = 3; } - // End at this block. An implementation defined maximum is used when unspecified. - bytes to_block = 4; // optional // Sequence direction. + // If missing, should be interpreted as "Ascending". Direction direction = 5; // Maximum number of blocks to return. An implementation defined maximum is used when unspecified. uint32 max_blocks = 6; // optional @@ -74,22 +73,32 @@ message BlockData { message StateRequest { // Block header hash. bytes block = 1; - // Start from this key. Equivalent to if omitted. - bytes start = 2; // optional + // Start from this key. + // Multiple keys used for nested state start. + repeated bytes start = 2; // optional // if 'true' indicates that response should contain raw key-values, rather than proof. bool no_proof = 3; } message StateResponse { - // A collection of keys-values. Only populated if `no_proof` is `true` - repeated StateEntry entries = 1; + // A collection of keys-values states. Only populated if `no_proof` is `true` + repeated KeyValueStateEntry entries = 1; // If `no_proof` is false in request, this contains proof nodes. bytes proof = 2; +} + +// A key value state. +message KeyValueStateEntry { + // Root of for this level, empty length bytes + // if top level. + bytes state_root = 1; + // A collection of keys-values. + repeated StateEntry entries = 2; // Set to true when there are no more keys to return. bool complete = 3; } -// A key-value pair +// A key-value pair. message StateEntry { bytes key = 1; bytes value = 2; diff --git a/client/network/sync/src/service/chain_sync.rs b/client/network/sync/src/service/chain_sync.rs new file mode 100644 index 0000000000000..50ded5b643dea --- /dev/null +++ b/client/network/sync/src/service/chain_sync.rs @@ -0,0 +1,111 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use libp2p::PeerId; +use sc_consensus::{BlockImportError, BlockImportStatus, JustificationSyncLink, Link}; +use sc_network_common::service::NetworkSyncForkRequest; +use sc_utils::mpsc::TracingUnboundedSender; +use sp_runtime::traits::{Block as BlockT, NumberFor}; + +/// Commands send to `ChainSync` +#[derive(Debug)] +pub enum ToServiceCommand { + SetSyncForkRequest(Vec, B::Hash, NumberFor), + RequestJustification(B::Hash, NumberFor), + ClearJustificationRequests, + BlocksProcessed( + usize, + usize, + Vec<(Result>, BlockImportError>, B::Hash)>, + ), + JustificationImported(PeerId, B::Hash, NumberFor, bool), +} + +/// Handle for communicating with `ChainSync` asynchronously +#[derive(Clone)] +pub struct ChainSyncInterfaceHandle { + tx: TracingUnboundedSender>, +} + +impl ChainSyncInterfaceHandle { + /// Create new handle + pub fn new(tx: TracingUnboundedSender>) -> Self { + Self { tx } + } +} + +impl NetworkSyncForkRequest> + for ChainSyncInterfaceHandle +{ + /// Configure an explicit fork sync request. + /// + /// Note that this function should not be used for recent blocks. + /// Sync should be able to download all the recent forks normally. + /// `set_sync_fork_request` should only be used if external code detects that there's + /// a stale fork missing. + /// + /// Passing empty `peers` set effectively removes the sync request. + fn set_sync_fork_request(&self, peers: Vec, hash: B::Hash, number: NumberFor) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::SetSyncForkRequest(peers, hash, number)); + } +} + +impl JustificationSyncLink for ChainSyncInterfaceHandle { + /// Request a justification for the given block from the network. + /// + /// On success, the justification will be passed to the import queue that was part at + /// initialization as part of the configuration. + fn request_justification(&self, hash: &B::Hash, number: NumberFor) { + let _ = self.tx.unbounded_send(ToServiceCommand::RequestJustification(*hash, number)); + } + + fn clear_justification_requests(&self) { + let _ = self.tx.unbounded_send(ToServiceCommand::ClearJustificationRequests); + } +} + +impl Link for ChainSyncInterfaceHandle { + fn blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<(Result>, BlockImportError>, B::Hash)>, + ) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::BlocksProcessed(imported, count, results)); + } + + fn justification_imported( + &mut self, + who: PeerId, + hash: &B::Hash, + number: NumberFor, + success: bool, + ) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::JustificationImported(who, *hash, number, success)); + } + + fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { + let _ = self.tx.unbounded_send(ToServiceCommand::RequestJustification(*hash, number)); + } +} diff --git a/client/network/sync/src/service/mock.rs b/client/network/sync/src/service/mock.rs new file mode 100644 index 0000000000000..d8aad2fa7bac1 --- /dev/null +++ b/client/network/sync/src/service/mock.rs @@ -0,0 +1,128 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use futures::channel::oneshot; +use libp2p::{Multiaddr, PeerId}; +use sc_consensus::{BlockImportError, BlockImportStatus}; +use sc_network_common::{ + config::MultiaddrWithPeerId, + protocol::ProtocolName, + request_responses::{IfDisconnected, RequestFailure}, + service::{NetworkPeers, NetworkRequest, NetworkSyncForkRequest}, +}; +use sc_peerset::ReputationChange; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::collections::HashSet; + +mockall::mock! { + pub ChainSyncInterface { + pub fn justification_sync_link_request_justification(&self, hash: &B::Hash, number: NumberFor); + pub fn justification_sync_link_clear_justification_requests(&self); + } + + impl NetworkSyncForkRequest> + for ChainSyncInterface + { + fn set_sync_fork_request(&self, peers: Vec, hash: B::Hash, number: NumberFor); + } + + impl sc_consensus::Link for ChainSyncInterface { + fn blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<(Result>, BlockImportError>, B::Hash)>, + ); + fn justification_imported( + &mut self, + who: PeerId, + hash: &B::Hash, + number: NumberFor, + success: bool, + ); + fn request_justification(&mut self, hash: &B::Hash, number: NumberFor); + } +} + +impl sc_consensus::JustificationSyncLink for MockChainSyncInterface { + fn request_justification(&self, hash: &B::Hash, number: NumberFor) { + self.justification_sync_link_request_justification(hash, number); + } + + fn clear_justification_requests(&self) { + self.justification_sync_link_clear_justification_requests(); + } +} + +mockall::mock! { + pub NetworkServiceHandle {} +} + +// Mocked `Network` for `ChainSync`-related tests +mockall::mock! { + pub Network {} + + impl NetworkPeers for Network { + fn set_authorized_peers(&self, peers: HashSet); + fn set_authorized_only(&self, reserved_only: bool); + fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr); + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange); + fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName); + fn accept_unreserved_peers(&self); + fn deny_unreserved_peers(&self); + fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String>; + fn remove_reserved_peer(&self, peer_id: PeerId); + fn set_reserved_peers( + &self, + protocol: ProtocolName, + peers: HashSet, + ) -> Result<(), String>; + fn add_peers_to_reserved_set( + &self, + protocol: ProtocolName, + peers: HashSet, + ) -> Result<(), String>; + fn remove_peers_from_reserved_set(&self, protocol: ProtocolName, peers: Vec); + fn add_to_peers_set( + &self, + protocol: ProtocolName, + peers: HashSet, + ) -> Result<(), String>; + fn remove_from_peers_set(&self, protocol: ProtocolName, peers: Vec); + fn sync_num_connected(&self) -> usize; + } + + #[async_trait::async_trait] + impl NetworkRequest for Network { + async fn request( + &self, + target: PeerId, + protocol: ProtocolName, + request: Vec, + connect: IfDisconnected, + ) -> Result, RequestFailure>; + fn start_request( + &self, + target: PeerId, + protocol: ProtocolName, + request: Vec, + tx: oneshot::Sender, RequestFailure>>, + connect: IfDisconnected, + ); + } +} diff --git a/client/network/sync/src/service/mod.rs b/client/network/sync/src/service/mod.rs new file mode 100644 index 0000000000000..692aa26985458 --- /dev/null +++ b/client/network/sync/src/service/mod.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! `ChainSync`-related service code + +pub mod chain_sync; +pub mod mock; +pub mod network; diff --git a/client/network/sync/src/service/network.rs b/client/network/sync/src/service/network.rs new file mode 100644 index 0000000000000..c44398b0f1a9e --- /dev/null +++ b/client/network/sync/src/service/network.rs @@ -0,0 +1,157 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use futures::{channel::oneshot, StreamExt}; +use libp2p::PeerId; +use sc_network_common::{ + protocol::ProtocolName, + request_responses::{IfDisconnected, RequestFailure}, + service::{NetworkPeers, NetworkRequest}, +}; +use sc_peerset::ReputationChange; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use std::sync::Arc; + +/// Network-related services required by `sc-network-sync` +pub trait Network: NetworkPeers + NetworkRequest {} + +impl Network for T where T: NetworkPeers + NetworkRequest {} + +/// Network service provider for `ChainSync` +/// +/// It runs as an asynchronous task and listens to commands coming from `ChainSync` and +/// calls the `NetworkService` on its behalf. +pub struct NetworkServiceProvider { + rx: TracingUnboundedReceiver, +} + +/// Commands that `ChainSync` wishes to send to `NetworkService` +pub enum ToServiceCommand { + /// Call `NetworkPeers::disconnect_peer()` + DisconnectPeer(PeerId, ProtocolName), + + /// Call `NetworkPeers::report_peer()` + ReportPeer(PeerId, ReputationChange), + + /// Call `NetworkRequest::start_request()` + StartRequest( + PeerId, + ProtocolName, + Vec, + oneshot::Sender, RequestFailure>>, + IfDisconnected, + ), +} + +/// Handle that is (temporarily) passed to `ChainSync` so it can +/// communicate with `NetworkService` through `SyncingEngine` +#[derive(Clone)] +pub struct NetworkServiceHandle { + tx: TracingUnboundedSender, +} + +impl NetworkServiceHandle { + /// Create new service handle + pub fn new(tx: TracingUnboundedSender) -> NetworkServiceHandle { + Self { tx } + } + + /// Report peer + pub fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { + let _ = self.tx.unbounded_send(ToServiceCommand::ReportPeer(who, cost_benefit)); + } + + /// Disconnect peer + pub fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) { + let _ = self.tx.unbounded_send(ToServiceCommand::DisconnectPeer(who, protocol)); + } + + /// Send request to peer + pub fn start_request( + &self, + who: PeerId, + protocol: ProtocolName, + request: Vec, + tx: oneshot::Sender, RequestFailure>>, + connect: IfDisconnected, + ) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::StartRequest(who, protocol, request, tx, connect)); + } +} + +impl NetworkServiceProvider { + /// Create new `NetworkServiceProvider` + pub fn new() -> (Self, NetworkServiceHandle) { + let (tx, rx) = tracing_unbounded("mpsc_network_service_provider"); + + (Self { rx }, NetworkServiceHandle::new(tx)) + } + + /// Run the `NetworkServiceProvider` + pub async fn run(mut self, service: Arc) { + while let Some(inner) = self.rx.next().await { + match inner { + ToServiceCommand::DisconnectPeer(peer, protocol_name) => + service.disconnect_peer(peer, protocol_name), + ToServiceCommand::ReportPeer(peer, reputation_change) => + service.report_peer(peer, reputation_change), + ToServiceCommand::StartRequest(peer, protocol, request, tx, connect) => + service.start_request(peer, protocol, request, tx, connect), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::service::mock::MockNetwork; + + // typical pattern in `Protocol` code where peer is disconnected + // and then reported + #[tokio::test] + async fn disconnect_and_report_peer() { + let (provider, handle) = NetworkServiceProvider::new(); + + let peer = PeerId::random(); + let proto = ProtocolName::from("test-protocol"); + let proto_clone = proto.clone(); + let change = sc_peerset::ReputationChange::new_fatal("test-change"); + + let mut mock_network = MockNetwork::new(); + mock_network + .expect_disconnect_peer() + .withf(move |in_peer, in_proto| &peer == in_peer && &proto == in_proto) + .once() + .returning(|_, _| ()); + mock_network + .expect_report_peer() + .withf(move |in_peer, in_change| &peer == in_peer && &change == in_change) + .once() + .returning(|_, _| ()); + + tokio::spawn(async move { + provider.run(Arc::new(mock_network)).await; + }); + + handle.disconnect_peer(peer, proto_clone); + handle.report_peer(peer, change); + } +} diff --git a/client/network/sync/src/state.rs b/client/network/sync/src/state.rs new file mode 100644 index 0000000000000..9f64b52334c8a --- /dev/null +++ b/client/network/sync/src/state.rs @@ -0,0 +1,267 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! State sync support. + +use crate::schema::v1::{StateEntry, StateRequest, StateResponse}; +use codec::{Decode, Encode}; +use log::debug; +use sc_client_api::{CompactProof, ProofProvider}; +use sc_consensus::ImportedState; +use sc_network_common::sync::StateDownloadProgress; +use smallvec::SmallVec; +use sp_core::storage::well_known_keys; +use sp_runtime::{ + traits::{Block as BlockT, Header, NumberFor}, + Justifications, +}; +use std::{collections::HashMap, sync::Arc}; + +/// State sync state machine. Accumulates partial state data until it +/// is ready to be imported. +pub struct StateSync { + target_block: B::Hash, + target_header: B::Header, + target_root: B::Hash, + target_body: Option>, + target_justifications: Option, + last_key: SmallVec<[Vec; 2]>, + state: HashMap, (Vec<(Vec, Vec)>, Vec>)>, + complete: bool, + client: Arc, + imported_bytes: u64, + skip_proof: bool, +} + +/// Import state chunk result. +pub enum ImportResult { + /// State is complete and ready for import. + Import(B::Hash, B::Header, ImportedState, Option>, Option), + /// Continue downloading. + Continue, + /// Bad state chunk. + BadResponse, +} + +impl StateSync +where + B: BlockT, + Client: ProofProvider + Send + Sync + 'static, +{ + /// Create a new instance. + pub fn new( + client: Arc, + target_header: B::Header, + target_body: Option>, + target_justifications: Option, + skip_proof: bool, + ) -> Self { + Self { + client, + target_block: target_header.hash(), + target_root: *target_header.state_root(), + target_header, + target_body, + target_justifications, + last_key: SmallVec::default(), + state: HashMap::default(), + complete: false, + imported_bytes: 0, + skip_proof, + } + } + + /// Validate and import a state response. + pub fn import(&mut self, response: StateResponse) -> ImportResult { + if response.entries.is_empty() && response.proof.is_empty() { + debug!(target: "sync", "Bad state response"); + return ImportResult::BadResponse + } + if !self.skip_proof && response.proof.is_empty() { + debug!(target: "sync", "Missing proof"); + return ImportResult::BadResponse + } + let complete = if !self.skip_proof { + debug!(target: "sync", "Importing state from {} trie nodes", response.proof.len()); + let proof_size = response.proof.len() as u64; + let proof = match CompactProof::decode(&mut response.proof.as_ref()) { + Ok(proof) => proof, + Err(e) => { + debug!(target: "sync", "Error decoding proof: {:?}", e); + return ImportResult::BadResponse + }, + }; + let (values, completed) = match self.client.verify_range_proof( + self.target_root, + proof, + self.last_key.as_slice(), + ) { + Err(e) => { + debug!( + target: "sync", + "StateResponse failed proof verification: {}", + e, + ); + return ImportResult::BadResponse + }, + Ok(values) => values, + }; + debug!(target: "sync", "Imported with {} keys", values.len()); + + let complete = completed == 0; + if !complete && !values.update_last_key(completed, &mut self.last_key) { + debug!(target: "sync", "Error updating key cursor, depth: {}", completed); + }; + + for values in values.0 { + let key_values = if values.state_root.is_empty() { + // Read child trie roots. + values + .key_values + .into_iter() + .filter(|key_value| { + if well_known_keys::is_child_storage_key(key_value.0.as_slice()) { + self.state + .entry(key_value.1.clone()) + .or_default() + .1 + .push(key_value.0.clone()); + false + } else { + true + } + }) + .collect() + } else { + values.key_values + }; + let mut entry = self.state.entry(values.state_root).or_default(); + if entry.0.len() > 0 && entry.1.len() > 1 { + // Already imported child_trie with same root. + // Warning this will not work with parallel download. + } else if entry.0.is_empty() { + for (key, _value) in key_values.iter() { + self.imported_bytes += key.len() as u64; + } + + entry.0 = key_values; + } else { + for (key, value) in key_values { + self.imported_bytes += key.len() as u64; + entry.0.push((key, value)) + } + } + } + self.imported_bytes += proof_size; + complete + } else { + let mut complete = true; + // if the trie is a child trie and one of its parent trie is empty, + // the parent cursor stays valid. + // Empty parent trie content only happens when all the response content + // is part of a single child trie. + if self.last_key.len() == 2 && response.entries[0].entries.is_empty() { + // Do not remove the parent trie position. + self.last_key.pop(); + } else { + self.last_key.clear(); + } + for state in response.entries { + debug!( + target: "sync", + "Importing state from {:?} to {:?}", + state.entries.last().map(|e| sp_core::hexdisplay::HexDisplay::from(&e.key)), + state.entries.first().map(|e| sp_core::hexdisplay::HexDisplay::from(&e.key)), + ); + + if !state.complete { + if let Some(e) = state.entries.last() { + self.last_key.push(e.key.clone()); + } + complete = false; + } + let is_top = state.state_root.is_empty(); + let entry = self.state.entry(state.state_root).or_default(); + if entry.0.len() > 0 && entry.1.len() > 1 { + // Already imported child trie with same root. + } else { + let mut child_roots = Vec::new(); + for StateEntry { key, value } in state.entries { + // Skip all child key root (will be recalculated on import). + if is_top && well_known_keys::is_child_storage_key(key.as_slice()) { + child_roots.push((value, key)); + } else { + self.imported_bytes += key.len() as u64; + entry.0.push((key, value)) + } + } + for (root, storage_key) in child_roots { + self.state.entry(root).or_default().1.push(storage_key); + } + } + } + complete + }; + if complete { + self.complete = true; + ImportResult::Import( + self.target_block, + self.target_header.clone(), + ImportedState { + block: self.target_block, + state: std::mem::take(&mut self.state).into(), + }, + self.target_body.clone(), + self.target_justifications.clone(), + ) + } else { + ImportResult::Continue + } + } + + /// Produce next state request. + pub fn next_request(&self) -> StateRequest { + StateRequest { + block: self.target_block.encode(), + start: self.last_key.clone().into_vec(), + no_proof: self.skip_proof, + } + } + + /// Check if the state is complete. + pub fn is_complete(&self) -> bool { + self.complete + } + + /// Returns target block number. + pub fn target_block_num(&self) -> NumberFor { + *self.target_header.number() + } + + /// Returns target block hash. + pub fn target(&self) -> B::Hash { + self.target_block + } + + /// Returns state sync estimated progress. + pub fn progress(&self) -> StateDownloadProgress { + let cursor = *self.last_key.get(0).and_then(|last| last.get(0)).unwrap_or(&0u8); + let percent_done = cursor as u32 * 100 / 256; + StateDownloadProgress { percentage: percent_done, size: self.imported_bytes } + } +} diff --git a/client/network/src/state_request_handler.rs b/client/network/sync/src/state_request_handler.rs similarity index 60% rename from client/network/src/state_request_handler.rs rename to client/network/sync/src/state_request_handler.rs index b4e5320ebfda8..98f1ed34d0581 100644 --- a/client/network/src/state_request_handler.rs +++ b/client/network/sync/src/state_request_handler.rs @@ -15,26 +15,27 @@ // along with Substrate. If not, see . //! Helper for handling (i.e. answering) state requests from a remote peer via the -//! [`crate::request_responses::RequestResponsesBehaviour`]. +//! `crate::request_responses::RequestResponsesBehaviour`. -use crate::{ - chain::Client, - config::ProtocolId, - request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, - schema::v1::{StateEntry, StateRequest, StateResponse}, - PeerId, ReputationChange, -}; +use crate::schema::v1::{KeyValueStateEntry, StateEntry, StateRequest, StateResponse}; use codec::{Decode, Encode}; use futures::{ channel::{mpsc, oneshot}, stream::StreamExt, }; +use libp2p::PeerId; use log::{debug, trace}; use lru::LruCache; use prost::Message; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sc_client_api::{BlockBackend, ProofProvider}; +use sc_network_common::{ + config::ProtocolId, + request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, +}; +use sp_runtime::traits::Block as BlockT; use std::{ hash::{Hash, Hasher}, + num::NonZeroUsize, sync::Arc, time::Duration, }; @@ -44,16 +45,22 @@ const MAX_RESPONSE_BYTES: usize = 2 * 1024 * 1024; // Actual reponse may be bigg const MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER: usize = 2; mod rep { - use super::ReputationChange as Rep; + use sc_peerset::ReputationChange as Rep; /// Reputation change when a peer sent us the same request multiple times. pub const SAME_REQUEST: Rep = Rep::new(i32::MIN, "Same state request multiple times"); } -/// Generates a [`ProtocolConfig`] for the block request protocol, refusing incoming requests. -pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { +/// Generates a [`ProtocolConfig`] for the state request protocol, refusing incoming requests. +pub fn generate_protocol_config>( + protocol_id: &ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, +) -> ProtocolConfig { ProtocolConfig { - name: generate_protocol_name(protocol_id).into(), + name: generate_protocol_name(genesis_hash, fork_id).into(), + fallback_names: std::iter::once(generate_legacy_protocol_name(protocol_id).into()) + .collect(), max_request_size: 1024 * 1024, max_response_size: 16 * 1024 * 1024, request_timeout: Duration::from_secs(40), @@ -61,13 +68,19 @@ pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { } } -/// Generate the state protocol name from chain specific protocol identifier. -fn generate_protocol_name(protocol_id: &ProtocolId) -> String { - let mut s = String::new(); - s.push_str("/"); - s.push_str(protocol_id.as_ref()); - s.push_str("/state/1"); - s +/// Generate the state protocol name from the genesis hash and fork id. +fn generate_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> String { + let genesis_hash = genesis_hash.as_ref(); + if let Some(fork_id) = fork_id { + format!("/{}/{}/state/2", array_bytes::bytes2hex("", genesis_hash), fork_id) + } else { + format!("/{}/state/2", array_bytes::bytes2hex("", genesis_hash)) + } +} + +/// Generate the legacy state protocol name from chain specific protocol identifier. +fn generate_legacy_protocol_name(protocol_id: &ProtocolId) -> String { + format!("/{}/state/2", protocol_id.as_ref()) } /// The key of [`BlockRequestHandler::seen_requests`]. @@ -75,9 +88,10 @@ fn generate_protocol_name(protocol_id: &ProtocolId) -> String { struct SeenRequestsKey { peer: PeerId, block: B::Hash, - start: Vec, + start: Vec>, } +#[allow(clippy::derive_hash_xor_eq)] impl Hash for SeenRequestsKey { fn hash(&self, state: &mut H) { self.peer.hash(state); @@ -95,8 +109,8 @@ enum SeenRequestsValue { } /// Handler for incoming block requests from a remote peer. -pub struct StateRequestHandler { - client: Arc>, +pub struct StateRequestHandler { + client: Arc, request_receiver: mpsc::Receiver, /// Maps from request to number of times we have seen this request. /// @@ -104,21 +118,36 @@ pub struct StateRequestHandler { seen_requests: LruCache, SeenRequestsValue>, } -impl StateRequestHandler { +impl StateRequestHandler +where + B: BlockT, + Client: BlockBackend + ProofProvider + Send + Sync + 'static, +{ /// Create a new [`StateRequestHandler`]. pub fn new( protocol_id: &ProtocolId, - client: Arc>, + fork_id: Option<&str>, + client: Arc, num_peer_hint: usize, ) -> (Self, ProtocolConfig) { // Reserve enough request slots for one request per peer when we are at the maximum // number of peers. let (tx, request_receiver) = mpsc::channel(num_peer_hint); - let mut protocol_config = generate_protocol_config(protocol_id); + let mut protocol_config = generate_protocol_config( + protocol_id, + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + fork_id, + ); protocol_config.inbound_queue = Some(tx); - let seen_requests = LruCache::new(num_peer_hint * 2); + let capacity = + NonZeroUsize::new(num_peer_hint.max(1) * 2).expect("cache capacity is not zero"); + let seen_requests = LruCache::new(capacity); (Self { client, request_receiver, seen_requests }, protocol_config) } @@ -147,8 +176,7 @@ impl StateRequestHandler { let request = StateRequest::decode(&payload[..])?; let block: B::Hash = Decode::decode(&mut request.block.as_ref())?; - let key = - SeenRequestsKey { peer: *peer, block: block.clone(), start: request.start.clone() }; + let key = SeenRequestsKey { peer: *peer, block, start: request.start.clone() }; let mut reputation_changes = Vec::new(); @@ -168,10 +196,10 @@ impl StateRequestHandler { trace!( target: LOG_TARGET, - "Handling state request from {}: Block {:?}, Starting at {:?}, no_proof={}", + "Handling state request from {}: Block {:?}, Starting at {:x?}, no_proof={}", peer, request.block, - sp_core::hexdisplay::HexDisplay::from(&request.start), + &request.start, request.no_proof, ); @@ -179,36 +207,45 @@ impl StateRequestHandler { let mut response = StateResponse::default(); if !request.no_proof { - let (proof, count) = self.client.read_proof_collection( - &BlockId::hash(block), - &request.start, + let (proof, _count) = self.client.read_proof_collection( + block, + request.start.as_slice(), MAX_RESPONSE_BYTES, )?; response.proof = proof.encode(); - if count == 0 { - response.complete = true; - } } else { let entries = self.client.storage_collection( - &BlockId::hash(block), - &request.start, + block, + request.start.as_slice(), MAX_RESPONSE_BYTES, )?; - response.entries = - entries.into_iter().map(|(key, value)| StateEntry { key, value }).collect(); - if response.entries.is_empty() { - response.complete = true; - } + response.entries = entries + .into_iter() + .map(|(state, complete)| KeyValueStateEntry { + state_root: state.state_root, + entries: state + .key_values + .into_iter() + .map(|(key, value)| StateEntry { key, value }) + .collect(), + complete, + }) + .collect(); } trace!( target: LOG_TARGET, - "StateResponse contains {} keys, {}, proof nodes, complete={}, from {:?} to {:?}", + "StateResponse contains {} keys, {}, proof nodes, from {:?} to {:?}", response.entries.len(), response.proof.len(), - response.complete, - response.entries.first().map(|e| sp_core::hexdisplay::HexDisplay::from(&e.key)), - response.entries.last().map(|e| sp_core::hexdisplay::HexDisplay::from(&e.key)), + response.entries.get(0).and_then(|top| top + .entries + .first() + .map(|e| sp_core::hexdisplay::HexDisplay::from(&e.key))), + response.entries.get(0).and_then(|top| top + .entries + .last() + .map(|e| sp_core::hexdisplay::HexDisplay::from(&e.key))), ); if let Some(value) = self.seen_requests.get_mut(&key) { // If this is the first time we have processed this request, we need to change @@ -231,15 +268,20 @@ impl StateRequestHandler { } } -#[derive(derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] enum HandleRequestError { - #[display(fmt = "Failed to decode request: {}.", _0)] - DecodeProto(prost::DecodeError), - #[display(fmt = "Failed to encode response: {}.", _0)] - EncodeProto(prost::EncodeError), - #[display(fmt = "Failed to decode block hash: {}.", _0)] - InvalidHash(codec::Error), - Client(sp_blockchain::Error), - #[display(fmt = "Failed to send response.")] + #[error("Failed to decode request: {0}.")] + DecodeProto(#[from] prost::DecodeError), + + #[error("Failed to encode response: {0}.")] + EncodeProto(#[from] prost::EncodeError), + + #[error("Failed to decode block hash: {0}.")] + InvalidHash(#[from] codec::Error), + + #[error(transparent)] + Client(#[from] sp_blockchain::Error), + + #[error("Failed to send response.")] SendResponse, } diff --git a/client/network/sync/src/tests.rs b/client/network/sync/src/tests.rs new file mode 100644 index 0000000000000..61de08443a6c2 --- /dev/null +++ b/client/network/sync/src/tests.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{service::network::NetworkServiceProvider, ChainSync, ForkTarget}; + +use libp2p::PeerId; +use sc_network_common::{ + config::ProtocolId, + protocol::{ + role::{Role, Roles}, + ProtocolName, + }, + service::NetworkSyncForkRequest, + sync::ChainSync as ChainSyncT, +}; +use sp_consensus::block_validation::DefaultBlockAnnounceValidator; +use sp_core::H256; +use std::{sync::Arc, task::Poll}; +use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; + +// verify that the fork target map is empty, then submit a new sync fork request, +// poll `ChainSync` and verify that a new sync fork request has been registered +#[tokio::test] +async fn delegate_to_chainsync() { + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); + let (mut chain_sync, chain_sync_service, _) = ChainSync::new( + sc_network_common::sync::SyncMode::Full, + Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), + Box::new(DefaultBlockAnnounceValidator), + 1u32, + None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, + ) + .unwrap(); + + let hash = H256::random(); + let in_number = 1337u64; + let peers = (0..3).map(|_| PeerId::random()).collect::>(); + + assert!(chain_sync.fork_targets.is_empty()); + chain_sync_service.set_sync_fork_request(peers, hash, in_number); + + futures::future::poll_fn(|cx| { + let _ = chain_sync.poll(cx); + Poll::Ready(()) + }) + .await; + + if let Some(ForkTarget { number, .. }) = chain_sync.fork_targets.get(&hash) { + assert_eq!(number, &in_number); + } else { + panic!("expected to contain `ForkTarget`"); + } +} diff --git a/client/network/src/protocol/sync/warp.rs b/client/network/sync/src/warp.rs similarity index 50% rename from client/network/src/protocol/sync/warp.rs rename to client/network/sync/src/warp.rs index 32bd5cb9ed798..ab8a7c66b9856 100644 --- a/client/network/src/protocol/sync/warp.rs +++ b/client/network/sync/src/warp.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,50 +16,62 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -///! Warp sync support. -pub use super::state::ImportResult; -use super::state::StateSync; -pub use crate::warp_request_handler::{ - EncodedProof, Request as WarpProofRequest, VerificationResult, WarpSyncProvider, -}; +//! Warp sync support. + use crate::{ - chain::Client, schema::v1::{StateRequest, StateResponse}, - WarpSyncPhase, WarpSyncProgress, + state::{ImportResult, StateSync}, }; +use sc_client_api::ProofProvider; +use sc_network_common::sync::{ + message::{BlockAttributes, BlockData, BlockRequest, Direction, FromBlock}, + warp::{ + EncodedProof, VerificationResult, WarpProofRequest, WarpSyncPhase, WarpSyncProgress, + WarpSyncProvider, + }, +}; +use sp_blockchain::HeaderBackend; use sp_finality_grandpa::{AuthorityList, SetId}; -use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor, Zero}; use std::sync::Arc; -enum Phase { +enum Phase { WarpProof { set_id: SetId, authorities: AuthorityList, last_hash: B::Hash }, - State(StateSync), + TargetBlock(B::Header), + State(StateSync), } /// Import warp proof result. -pub enum WarpProofImportResult { - /// Start downloading state data. - StateRequest(StateRequest), - /// Continue dowloading warp sync proofs. - WarpProofRequest(WarpProofRequest), +pub enum WarpProofImportResult { + /// Import was successful. + Success, /// Bad proof. BadResponse, } +/// Import target block result. +pub enum TargetBlockImportResult { + /// Import was successful. + Success, + /// Invalid block. + BadResponse, +} + /// Warp sync state machine. Accumulates warp proofs and state. -pub struct WarpSync { - phase: Phase, - client: Arc>, +pub struct WarpSync { + phase: Phase, + client: Arc, warp_sync_provider: Arc>, total_proof_bytes: u64, } -impl WarpSync { +impl WarpSync +where + B: BlockT, + Client: HeaderBackend + ProofProvider + 'static, +{ /// Create a new instance. - pub fn new( - client: Arc>, - warp_sync_provider: Arc>, - ) -> Self { + pub fn new(client: Arc, warp_sync_provider: Arc>) -> Self { let last_hash = client.hash(Zero::zero()).unwrap().expect("Genesis header always exists"); let phase = Phase::WarpProof { set_id: 0, @@ -69,86 +81,142 @@ impl WarpSync { Self { client, warp_sync_provider, phase, total_proof_bytes: 0 } } - /// Validate and import a state reponse. + /// Validate and import a state response. pub fn import_state(&mut self, response: StateResponse) -> ImportResult { match &mut self.phase { - Phase::WarpProof { .. } => { + Phase::WarpProof { .. } | Phase::TargetBlock(_) => { log::debug!(target: "sync", "Unexpected state response"); - return ImportResult::BadResponse + ImportResult::BadResponse }, Phase::State(sync) => sync.import(response), } } - /// Validate and import a warp proof reponse. - pub fn import_warp_proof(&mut self, response: EncodedProof) -> WarpProofImportResult { + /// Validate and import a warp proof response. + pub fn import_warp_proof(&mut self, response: EncodedProof) -> WarpProofImportResult { match &mut self.phase { - Phase::State(_) => { + Phase::State(_) | Phase::TargetBlock(_) => { log::debug!(target: "sync", "Unexpected warp proof response"); WarpProofImportResult::BadResponse }, Phase::WarpProof { set_id, authorities, last_hash } => { - match self.warp_sync_provider.verify( - &response, - *set_id, - std::mem::take(authorities), - ) { + match self.warp_sync_provider.verify(&response, *set_id, authorities.clone()) { Err(e) => { - log::debug!(target: "sync", "Bad warp proof response: {:?}", e); - return WarpProofImportResult::BadResponse + log::debug!(target: "sync", "Bad warp proof response: {}", e); + WarpProofImportResult::BadResponse }, Ok(VerificationResult::Partial(new_set_id, new_authorities, new_last_hash)) => { log::debug!(target: "sync", "Verified partial proof, set_id={:?}", new_set_id); *set_id = new_set_id; *authorities = new_authorities; - *last_hash = new_last_hash.clone(); + *last_hash = new_last_hash; self.total_proof_bytes += response.0.len() as u64; - WarpProofImportResult::WarpProofRequest(WarpProofRequest { - begin: new_last_hash, - }) + WarpProofImportResult::Success }, Ok(VerificationResult::Complete(new_set_id, _, header)) => { log::debug!(target: "sync", "Verified complete proof, set_id={:?}", new_set_id); self.total_proof_bytes += response.0.len() as u64; - let state_sync = StateSync::new(self.client.clone(), header, false); - let request = state_sync.next_request(); - self.phase = Phase::State(state_sync); - WarpProofImportResult::StateRequest(request) + self.phase = Phase::TargetBlock(header); + WarpProofImportResult::Success }, } }, } } + /// Import the target block body. + pub fn import_target_block(&mut self, block: BlockData) -> TargetBlockImportResult { + match &mut self.phase { + Phase::WarpProof { .. } | Phase::State(_) => { + log::debug!(target: "sync", "Unexpected target block response"); + TargetBlockImportResult::BadResponse + }, + Phase::TargetBlock(header) => + if let Some(block_header) = &block.header { + if block_header == header { + if block.body.is_some() { + let state_sync = StateSync::new( + self.client.clone(), + header.clone(), + block.body, + block.justifications, + false, + ); + self.phase = Phase::State(state_sync); + TargetBlockImportResult::Success + } else { + log::debug!( + target: "sync", + "Importing target block failed: missing body.", + ); + TargetBlockImportResult::BadResponse + } + } else { + log::debug!( + target: "sync", + "Importing target block failed: different header.", + ); + TargetBlockImportResult::BadResponse + } + } else { + log::debug!(target: "sync", "Importing target block failed: missing header."); + TargetBlockImportResult::BadResponse + }, + } + } + /// Produce next state request. pub fn next_state_request(&self) -> Option { match &self.phase { Phase::WarpProof { .. } => None, + Phase::TargetBlock(_) => None, Phase::State(sync) => Some(sync.next_request()), } } /// Produce next warp proof request. - pub fn next_warp_poof_request(&self) -> Option> { + pub fn next_warp_proof_request(&self) -> Option> { match &self.phase { - Phase::State(_) => None, Phase::WarpProof { last_hash, .. } => Some(WarpProofRequest { begin: *last_hash }), + Phase::TargetBlock(_) => None, + Phase::State(_) => None, + } + } + + /// Produce next target block request. + pub fn next_target_block_request(&self) -> Option<(NumberFor, BlockRequest)> { + match &self.phase { + Phase::WarpProof { .. } => None, + Phase::TargetBlock(header) => { + let request = BlockRequest:: { + id: 0, + fields: BlockAttributes::HEADER | + BlockAttributes::BODY | BlockAttributes::JUSTIFICATION, + from: FromBlock::Hash(header.hash()), + direction: Direction::Ascending, + max: Some(1), + }; + Some((*header.number(), request)) + }, + Phase::State(_) => None, } } /// Return target block hash if it is known. pub fn target_block_hash(&self) -> Option { match &self.phase { - Phase::State(s) => Some(s.target()), Phase::WarpProof { .. } => None, + Phase::TargetBlock(_) => None, + Phase::State(s) => Some(s.target()), } } /// Return target block number if it is known. pub fn target_block_number(&self) -> Option> { match &self.phase { - Phase::State(s) => Some(s.target_block_num()), Phase::WarpProof { .. } => None, + Phase::TargetBlock(header) => Some(*header.number()), + Phase::State(s) => Some(s.target_block_num()), } } @@ -156,17 +224,22 @@ impl WarpSync { pub fn is_complete(&self) -> bool { match &self.phase { Phase::WarpProof { .. } => false, + Phase::TargetBlock(_) => false, Phase::State(sync) => sync.is_complete(), } } /// Returns state sync estimated progress (percentage, bytes) - pub fn progress(&self) -> WarpSyncProgress { + pub fn progress(&self) -> WarpSyncProgress { match &self.phase { Phase::WarpProof { .. } => WarpSyncProgress { phase: WarpSyncPhase::DownloadingWarpProofs, total_bytes: self.total_proof_bytes, }, + Phase::TargetBlock(_) => WarpSyncProgress { + phase: WarpSyncPhase::DownloadingTargetBlock, + total_bytes: self.total_proof_bytes, + }, Phase::State(sync) => WarpSyncProgress { phase: if self.is_complete() { WarpSyncPhase::ImportingState diff --git a/client/network/src/warp_request_handler.rs b/client/network/sync/src/warp_request_handler.rs similarity index 52% rename from client/network/src/warp_request_handler.rs rename to client/network/sync/src/warp_request_handler.rs index 2ab95bb3853ba..e675bf45cad91 100644 --- a/client/network/src/warp_request_handler.rs +++ b/client/network/sync/src/warp_request_handler.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright 2022 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -16,62 +16,35 @@ //! Helper for handling (i.e. answering) grandpa warp sync requests from a remote peer. -use crate::config::{IncomingRequest, OutgoingResponse, ProtocolId, RequestResponseConfig}; -use codec::{Decode, Encode}; +use codec::Decode; use futures::{ channel::{mpsc, oneshot}, stream::StreamExt, }; use log::debug; -use sp_finality_grandpa::{AuthorityList, SetId}; +use sc_network_common::{ + config::ProtocolId, + request_responses::{ + IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, + }, + sync::warp::{EncodedProof, WarpProofRequest, WarpSyncProvider}, +}; use sp_runtime::traits::Block as BlockT; use std::{sync::Arc, time::Duration}; -/// Scale-encoded warp sync proof response. -pub struct EncodedProof(pub Vec); - -/// Warp sync request -#[derive(Encode, Decode, Debug)] -pub struct Request { - /// Start collecting proofs from this block. - pub begin: B::Hash, -} - const MAX_RESPONSE_SIZE: u64 = 16 * 1024 * 1024; -/// Proof verification result. -pub enum VerificationResult { - /// Proof is valid, but the target was not reached. - Partial(SetId, AuthorityList, Block::Hash), - /// Target finality is proved. - Complete(SetId, AuthorityList, Block::Header), -} - -/// Warp sync backend. Handles retrieveing and verifying warp sync proofs. -pub trait WarpSyncProvider: Send + Sync { - /// Generate proof starting at given block hash. The proof is accumulated until maximum proof - /// size is reached. - fn generate( - &self, - start: B::Hash, - ) -> Result>; - /// Verify warp proof agains current set of authorities. - fn verify( - &self, - proof: &EncodedProof, - set_id: SetId, - authorities: AuthorityList, - ) -> Result, Box>; - /// Get current list of authorities. This is supposed to be genesis authorities when starting - /// sync. - fn current_authorities(&self) -> AuthorityList; -} - /// Generates a [`RequestResponseConfig`] for the grandpa warp sync request protocol, refusing /// incoming requests. -pub fn generate_request_response_config(protocol_id: ProtocolId) -> RequestResponseConfig { +pub fn generate_request_response_config>( + protocol_id: ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, +) -> RequestResponseConfig { RequestResponseConfig { - name: generate_protocol_name(protocol_id).into(), + name: generate_protocol_name(genesis_hash, fork_id).into(), + fallback_names: std::iter::once(generate_legacy_protocol_name(protocol_id).into()) + .collect(), max_request_size: 32, max_response_size: MAX_RESPONSE_SIZE, request_timeout: Duration::from_secs(10), @@ -79,13 +52,19 @@ pub fn generate_request_response_config(protocol_id: ProtocolId) -> RequestRespo } } -/// Generate the grandpa warp sync protocol name from chain specific protocol identifier. -fn generate_protocol_name(protocol_id: ProtocolId) -> String { - let mut s = String::new(); - s.push_str("/"); - s.push_str(protocol_id.as_ref()); - s.push_str("/sync/warp"); - s +/// Generate the grandpa warp sync protocol name from the genesi hash and fork id. +fn generate_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> String { + let genesis_hash = genesis_hash.as_ref(); + if let Some(fork_id) = fork_id { + format!("/{}/{}/sync/warp", array_bytes::bytes2hex("", genesis_hash), fork_id) + } else { + format!("/{}/sync/warp", array_bytes::bytes2hex("", genesis_hash)) + } +} + +/// Generate the legacy grandpa warp sync protocol name from chain specific protocol identifier. +fn generate_legacy_protocol_name(protocol_id: ProtocolId) -> String { + format!("/{}/sync/warp", protocol_id.as_ref()) } /// Handler for incoming grandpa warp sync requests from a remote peer. @@ -96,13 +75,16 @@ pub struct RequestHandler { impl RequestHandler { /// Create a new [`RequestHandler`]. - pub fn new( + pub fn new>( protocol_id: ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, backend: Arc>, ) -> (Self, RequestResponseConfig) { let (tx, request_receiver) = mpsc::channel(20); - let mut request_response_config = generate_request_response_config(protocol_id); + let mut request_response_config = + generate_request_response_config(protocol_id, genesis_hash, fork_id); request_response_config.inbound_queue = Some(tx); (Self { backend, request_receiver }, request_response_config) @@ -113,7 +95,7 @@ impl RequestHandler { payload: Vec, pending_response: oneshot::Sender, ) -> Result<(), HandleRequestError> { - let request = Request::::decode(&mut &payload[..])?; + let request = WarpProofRequest::::decode(&mut &payload[..])?; let EncodedProof(proof) = self .backend @@ -148,18 +130,23 @@ impl RequestHandler { } } -#[derive(Debug, derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] enum HandleRequestError { - #[display(fmt = "Failed to decode request: {}.", _0)] - DecodeProto(prost::DecodeError), - #[display(fmt = "Failed to encode response: {}.", _0)] - EncodeProto(prost::EncodeError), - #[display(fmt = "Failed to decode block hash: {}.", _0)] - DecodeScale(codec::Error), - Client(sp_blockchain::Error), - #[from(ignore)] - #[display(fmt = "Invalid request {}.", _0)] - InvalidRequest(Box), - #[display(fmt = "Failed to send response.")] + #[error("Failed to decode request: {0}.")] + DecodeProto(#[from] prost::DecodeError), + + #[error("Failed to encode response: {0}.")] + EncodeProto(#[from] prost::EncodeError), + + #[error("Failed to decode block hash: {0}.")] + DecodeScale(#[from] codec::Error), + + #[error(transparent)] + Client(#[from] sp_blockchain::Error), + + #[error("Invalid request {0}.")] + InvalidRequest(#[from] Box), + + #[error("Failed to send response.")] SendResponse, } diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 88399ca54a436..86b5be37d256a 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -4,33 +4,36 @@ name = "sc-network-test" version = "0.8.0" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" publish = false -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-std = "1.6.5" -sc-network = { version = "0.10.0-dev", path = "../" } -log = "0.4.8" -parking_lot = "0.11.1" -futures = "0.3.9" +tokio = "1.22.0" +async-trait = "0.1.57" +futures = "0.3.21" futures-timer = "3.0.1" +libp2p = { version = "0.49.0", default-features = false } +log = "0.4.17" +parking_lot = "0.12.1" rand = "0.7.2" -libp2p = { version = "0.39.1", default-features = false } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } +sc-network = { version = "0.10.0-dev", path = "../" } +sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-network-light = { version = "0.10.0-dev", path = "../light" } +sc-network-sync = { version = "0.10.0-dev", path = "../sync" } +sc-service = { version = "0.10.0-dev", default-features = false, features = ["test-helpers"], path = "../../service" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } -sp-tracing = { version = "4.0.0-dev", path = "../../../primitives/tracing" } -sc-service = { version = "0.10.0-dev", default-features = false, features = ["test-helpers"], path = "../../service" } -async-trait = "0.1.50" +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/network/test/src/block_import.rs b/client/network/test/src/block_import.rs index 7a4c4f6c83081..b86f6787f30b5 100644 --- a/client/network/test/src/block_import.rs +++ b/client/network/test/src/block_import.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -40,7 +40,7 @@ fn prepare_good_block() -> (TestClient, Hash, u64, PeerId, IncomingBlock) let (hash, number) = (client.block_hash(1).unwrap().unwrap(), 1); let header = client.header(&BlockId::Number(1)).unwrap(); - let justifications = client.justifications(&BlockId::Number(1)).unwrap(); + let justifications = client.justifications(hash).unwrap(); let peer_id = PeerId::random(); ( client, diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index bb49cef8c642c..173ca81653b1a 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,11 +23,12 @@ mod block_import; mod sync; use std::{ - borrow::Cow, collections::HashMap, + marker::PhantomData, pin::Pin, sync::Arc, task::{Context as FutureContext, Poll}, + time::Duration, }; use futures::{future::BoxFuture, prelude::*}; @@ -42,30 +43,38 @@ use sc_client_api::{ }; use sc_consensus::{ BasicQueue, BlockCheckParams, BlockImport, BlockImportParams, BoxJustificationImport, - ForkChoiceStrategy, ImportResult, JustificationImport, LongestChain, Verifier, + ForkChoiceStrategy, ImportQueue, ImportResult, JustificationImport, JustificationSyncLink, + LongestChain, Verifier, }; -pub use sc_network::config::EmptyTransactionPool; use sc_network::{ - block_request_handler::{self, BlockRequestHandler}, + config::{NetworkConfiguration, RequestResponseConfig, Role, SyncMode}, + Multiaddr, NetworkService, NetworkWorker, +}; +use sc_network_common::{ config::{ - MultiaddrWithPeerId, NetworkConfiguration, NonDefaultSetConfig, NonReservedPeerMode, - ProtocolConfig, ProtocolId, Role, SyncMode, TransportConfig, + MultiaddrWithPeerId, NonDefaultSetConfig, NonReservedPeerMode, ProtocolId, TransportConfig, }, - light_client_requests::{self, handler::LightClientRequestHandler}, - state_request_handler::{self, StateRequestHandler}, - Multiaddr, NetworkService, NetworkWorker, + protocol::{role::Roles, ProtocolName}, + service::{NetworkBlock, NetworkStateInfo, NetworkSyncForkRequest}, + sync::warp::{AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncProvider}, +}; +use sc_network_light::light_client_requests::handler::LightClientRequestHandler; +use sc_network_sync::{ + block_request_handler::BlockRequestHandler, service::network::NetworkServiceProvider, + state_request_handler::StateRequestHandler, warp_request_handler, ChainSync, }; use sc_service::client::Client; use sp_blockchain::{ well_known_cache_keys::{self, Id as CacheKeyId}, - HeaderBackend, Info as BlockchainInfo, Result as ClientResult, + Backend as BlockchainBackend, HeaderBackend, Info as BlockchainInfo, Result as ClientResult, }; use sp_consensus::{ block_validation::{BlockAnnounceValidator, DefaultBlockAnnounceValidator}, - BlockOrigin, Error as ConsensusError, + BlockOrigin, Error as ConsensusError, SyncOracle, }; use sp_core::H256; use sp_runtime::{ + codec::{Decode, Encode}, generic::{BlockId, OpaqueDigestItemId}, traits::{Block as BlockT, Header as HeaderT, NumberFor}, Justification, Justifications, @@ -75,6 +84,7 @@ pub use substrate_test_runtime_client::{ runtime::{Block, Extrinsic, Hash, Transfer}, TestClient, TestClientBuilder, TestClientBuilderExt, }; +use tokio::time::timeout; type AuthorityId = sp_consensus_babe::AuthorityId; @@ -83,7 +93,6 @@ type AuthorityId = sp_consensus_babe::AuthorityId; #[derive(Clone)] pub struct PassThroughVerifier { finalized: bool, - fork_choice: ForkChoiceStrategy, } impl PassThroughVerifier { @@ -91,15 +100,7 @@ impl PassThroughVerifier { /// /// Every verified block will use `finalized` for the `BlockImportParams`. pub fn new(finalized: bool) -> Self { - Self { finalized, fork_choice: ForkChoiceStrategy::LongestChain } - } - - /// Create a new instance. - /// - /// Every verified block will use `finalized` for the `BlockImportParams` and - /// the given [`ForkChoiceStrategy`]. - pub fn new_with_fork_choice(finalized: bool, fork_choice: ForkChoiceStrategy) -> Self { - Self { finalized, fork_choice } + Self { finalized } } } @@ -118,8 +119,10 @@ impl Verifier for PassThroughVerifier { .or_else(|| l.try_as_raw(OpaqueDigestItemId::Consensus(b"babe"))) }) .map(|blob| vec![(well_known_cache_keys::AUTHORITIES, blob.to_vec())]); + if block.fork_choice.is_none() { + block.fork_choice = Some(ForkChoiceStrategy::LongestChain); + }; block.finalized = self.finalized; - block.fork_choice = Some(self.fork_choice.clone()); Ok((block, maybe_keys)) } } @@ -130,25 +133,20 @@ pub type PeersFullClient = Client< Block, substrate_test_runtime_client::runtime::RuntimeApi, >; -pub type PeersLightClient = Client< - substrate_test_runtime_client::LightBackend, - substrate_test_runtime_client::LightExecutor, - Block, - substrate_test_runtime_client::runtime::RuntimeApi, ->; #[derive(Clone)] -pub enum PeersClient { - Full(Arc, Arc), - Light(Arc, Arc), +pub struct PeersClient { + client: Arc, + backend: Arc, } impl PeersClient { - pub fn as_full(&self) -> Option> { - match *self { - PeersClient::Full(ref client, _) => Some(client.clone()), - _ => None, - } + pub fn as_client(&self) -> Arc { + self.client.clone() + } + + pub fn as_backend(&self) -> Arc { + self.backend.clone() } pub fn as_block_import(&self) -> BlockImportAdapter { @@ -156,27 +154,18 @@ impl PeersClient { } pub fn get_aux(&self, key: &[u8]) -> ClientResult>> { - match *self { - PeersClient::Full(ref client, _) => client.get_aux(key), - PeersClient::Light(ref client, _) => client.get_aux(key), - } + self.client.get_aux(key) } pub fn info(&self) -> BlockchainInfo { - match *self { - PeersClient::Full(ref client, _) => client.chain_info(), - PeersClient::Light(ref client, _) => client.chain_info(), - } + self.client.info() } pub fn header( &self, block: &BlockId, ) -> ClientResult::Header>> { - match *self { - PeersClient::Full(ref client, _) => client.header(block), - PeersClient::Light(ref client, _) => client.header(block), - } + self.client.header(block) } pub fn has_state_at(&self, block: &BlockId) -> bool { @@ -184,47 +173,31 @@ impl PeersClient { Some(header) => header, None => return false, }; - match self { - PeersClient::Full(_client, backend) => - backend.have_state_at(&header.hash(), *header.number()), - PeersClient::Light(_client, backend) => - backend.have_state_at(&header.hash(), *header.number()), - } + self.backend.have_state_at(header.hash(), *header.number()) } - pub fn justifications(&self, block: &BlockId) -> ClientResult> { - match *self { - PeersClient::Full(ref client, _) => client.justifications(block), - PeersClient::Light(ref client, _) => client.justifications(block), - } + pub fn justifications( + &self, + hash: ::Hash, + ) -> ClientResult> { + self.client.justifications(hash) } pub fn finality_notification_stream(&self) -> FinalityNotifications { - match *self { - PeersClient::Full(ref client, _) => client.finality_notification_stream(), - PeersClient::Light(ref client, _) => client.finality_notification_stream(), - } + self.client.finality_notification_stream() } pub fn import_notification_stream(&self) -> ImportNotifications { - match *self { - PeersClient::Full(ref client, _) => client.import_notification_stream(), - PeersClient::Light(ref client, _) => client.import_notification_stream(), - } + self.client.import_notification_stream() } pub fn finalize_block( &self, - id: BlockId, + hash: ::Hash, justification: Option, notify: bool, ) -> ClientResult<()> { - match *self { - PeersClient::Full(ref client, ref _backend) => - client.finalize_block(id, justification, notify), - PeersClient::Light(ref client, ref _backend) => - client.finalize_block(id, justification, notify), - } + self.client.finalize_block(hash, justification, notify) } } @@ -237,10 +210,7 @@ impl BlockImport for PeersClient { &mut self, block: BlockCheckParams, ) -> Result { - match self { - PeersClient::Full(client, _) => client.check_block(block).await, - PeersClient::Light(client, _) => client.check_block(block).await, - } + self.client.check_block(block).await } async fn import_block( @@ -248,12 +218,7 @@ impl BlockImport for PeersClient { block: BlockImportParams, cache: HashMap>, ) -> Result { - match self { - PeersClient::Full(client, _) => - client.import_block(block.clear_storage_changes_and_mutate(), cache).await, - PeersClient::Light(client, _) => - client.import_block(block.clear_storage_changes_and_mutate(), cache).await, - } + self.client.import_block(block.clear_storage_changes_and_mutate(), cache).await } } @@ -268,7 +233,7 @@ pub struct Peer { block_import: BlockImportAdapter, select_chain: Option>, backend: Option>, - network: NetworkWorker::Hash>, + network: NetworkWorker::Hash, PeersFullClient>, imported_blocks_stream: Pin> + Send>>, finality_notification_stream: Pin> + Send>>, listen_addr: Multiaddr, @@ -281,7 +246,7 @@ where { /// Get this peer ID. pub fn id(&self) -> PeerId { - self.network.service().local_peer_id().clone() + self.network.service().local_peer_id() } /// Returns true if we're major syncing. @@ -347,12 +312,39 @@ where false, true, true, + ForkChoiceStrategy::LongestChain, + ) + } + + /// Add blocks to the peer -- edit the block before adding and use custom fork choice rule. + pub fn generate_blocks_with_fork_choice( + &mut self, + count: usize, + origin: BlockOrigin, + edit_block: F, + fork_choice: ForkChoiceStrategy, + ) -> H256 + where + F: FnMut( + BlockBuilder, + ) -> Block, + { + let best_hash = self.client.info().best_hash; + self.generate_blocks_at( + BlockId::Hash(best_hash), + count, + origin, + edit_block, + false, + true, + true, + fork_choice, ) } /// Add blocks to the peer -- edit the block before adding. The chain will /// start at the given block iD. - fn generate_blocks_at( + pub fn generate_blocks_at( &mut self, at: BlockId, count: usize, @@ -361,14 +353,14 @@ where headers_only: bool, inform_sync_about_new_best_block: bool, announce_block: bool, + fork_choice: ForkChoiceStrategy, ) -> H256 where F: FnMut( BlockBuilder, ) -> Block, { - let full_client = - self.client.as_full().expect("blocks could only be generated by full clients"); + let full_client = self.client.as_client(); let mut at = full_client.header(&at).unwrap().unwrap().hash(); for _ in 0..count { let builder = @@ -385,6 +377,7 @@ where let header = block.header.clone(); let mut import_block = BlockImportParams::new(origin, header.clone()); import_block.body = if headers_only { None } else { Some(block.extrinsics) }; + import_block.fork_choice = Some(fork_choice); let (import_block, cache) = futures::executor::block_on(self.verifier.verify(import_block)).unwrap(); let cache = if let Some(cache) = cache { @@ -404,7 +397,7 @@ where if inform_sync_about_new_best_block { self.network.new_best_block_imported( at, - full_client.header(&BlockId::Hash(at)).ok().flatten().unwrap().number().clone(), + *full_client.header(&BlockId::Hash(at)).ok().flatten().unwrap().number(), ); } at @@ -435,8 +428,9 @@ where at: BlockId, count: usize, with_tx: bool, + announce_block: bool, ) -> H256 { - self.generate_tx_blocks_at(at, count, with_tx, false, false, true) + self.generate_tx_blocks_at(at, count, with_tx, false, false, announce_block) } /// Push blocks to the peer (simplified: with or without a TX) starting from @@ -475,12 +469,13 @@ where nonce, }; builder.push(transfer.into_signed_tx()).unwrap(); - nonce = nonce + 1; + nonce += 1; builder.build().unwrap().block }, headers_only, inform_sync_about_new_best_block, announce_block, + ForkChoiceStrategy::LongestChain, ) } else { self.generate_blocks_at( @@ -491,6 +486,7 @@ where headers_only, inform_sync_about_new_best_block, announce_block, + ForkChoiceStrategy::LongestChain, ) } } @@ -509,11 +505,11 @@ where /// Get a reference to the network service. pub fn network_service(&self) -> &Arc::Hash>> { - &self.network.service() + self.network.service() } /// Get a reference to the network worker. - pub fn network(&self) -> &NetworkWorker::Hash> { + pub fn network(&self) -> &NetworkWorker::Hash, PeersFullClient> { &self.network } @@ -540,10 +536,17 @@ where self.verifier.failed_verifications.lock().clone() } - pub fn has_block(&self, hash: &H256) -> bool { + pub fn has_block(&self, hash: H256) -> bool { self.backend .as_ref() - .map(|backend| backend.blockchain().header(BlockId::hash(*hash)).unwrap().is_some()) + .map(|backend| backend.blockchain().header(BlockId::hash(hash)).unwrap().is_some()) + .unwrap_or(false) + } + + pub fn has_body(&self, hash: H256) -> bool { + self.backend + .as_ref() + .map(|backend| backend.blockchain().body(hash).unwrap().is_some()) .unwrap_or(false) } } @@ -576,25 +579,27 @@ impl BlockImportAdapterFull for T where /// This is required as the `TestNetFactory` trait does not distinguish between /// full and light nodes. #[derive(Clone)] -pub struct BlockImportAdapter { +pub struct BlockImportAdapter { inner: I, + _phantom: PhantomData, } -impl BlockImportAdapter { +impl BlockImportAdapter { /// Create a new instance of `Self::Full`. pub fn new(inner: I) -> Self { - Self { inner } + Self { inner, _phantom: PhantomData } } } #[async_trait::async_trait] -impl BlockImport for BlockImportAdapter +impl BlockImport for BlockImportAdapter where I: BlockImport + Send + Sync, I::Transaction: Send, + Transaction: Send + 'static, { type Error = ConsensusError; - type Transaction = (); + type Transaction = Transaction; async fn check_block( &mut self, @@ -605,7 +610,7 @@ where async fn import_block( &mut self, - block: BlockImportParams, + block: BlockImportParams, cache: HashMap>, ) -> Result { self.inner.import_block(block.clear_storage_changes_and_mutate(), cache).await @@ -650,15 +655,45 @@ impl VerifierAdapter { } } +struct TestWarpSyncProvider(Arc>); + +impl WarpSyncProvider for TestWarpSyncProvider { + fn generate( + &self, + _start: B::Hash, + ) -> Result> { + let info = self.0.info(); + let best_header = self.0.header(BlockId::hash(info.best_hash)).unwrap().unwrap(); + Ok(EncodedProof(best_header.encode())) + } + fn verify( + &self, + proof: &EncodedProof, + _set_id: SetId, + _authorities: AuthorityList, + ) -> Result, Box> { + let EncodedProof(encoded) = proof; + let header = B::Header::decode(&mut encoded.as_slice()).unwrap(); + Ok(VerificationResult::Complete(0, Default::default(), header)) + } + fn current_authorities(&self) -> AuthorityList { + Default::default() + } +} + /// Configuration for a full peer. #[derive(Default)] pub struct FullPeerConfig { /// Pruning window size. - pub keep_blocks: Option, + /// + /// NOTE: only finalized blocks are subject for removal! + pub blocks_pruning: Option, /// Block announce validator. pub block_announce_validator: Option + Send + Sync>>, /// List of notification protocols that the network must support. - pub notifications_protocols: Vec>, + pub notifications_protocols: Vec, + /// List of request-response protocols that the network must support. + pub request_response_protocols: Vec, /// The indices of the peers the peer should be connected to. /// /// If `None`, it will be connected to all other peers. @@ -667,11 +702,22 @@ pub struct FullPeerConfig { pub is_authority: bool, /// Syncing mode pub sync_mode: SyncMode, + /// Extra genesis storage. + pub extra_storage: Option, /// Enable transaction indexing. pub storage_chain: bool, } -pub trait TestNetFactory: Sized +/// Trait for text fixtures with tokio runtime. +pub trait WithRuntime { + /// Construct with runtime handle. + fn with_runtime(rt_handle: tokio::runtime::Handle) -> Self; + /// Get runtime handle. + fn rt_handle(&self) -> &tokio::runtime::Handle; +} + +#[async_trait::async_trait] +pub trait TestNetFactory: WithRuntime + Sized where >::Transaction: Send, { @@ -679,14 +725,8 @@ where type BlockImport: BlockImport + Clone + Send + Sync + 'static; type PeerData: Default; - /// These two need to be implemented! - fn from_config(config: &ProtocolConfig) -> Self; - fn make_verifier( - &self, - client: PeersClient, - config: &ProtocolConfig, - peer_data: &Self::PeerData, - ) -> Self::Verifier; + /// This one needs to be implemented! + fn make_verifier(&self, client: PeersClient, peer_data: &Self::PeerData) -> Self::Verifier; /// Get reference to peer. fn peer(&mut self, i: usize) -> &mut Peer; @@ -706,15 +746,10 @@ where Self::PeerData, ); - fn default_config() -> ProtocolConfig { - ProtocolConfig::default() - } - /// Create new test network with this many peers. - fn new(n: usize) -> Self { + fn new(rt_handle: tokio::runtime::Handle, n: usize) -> Self { trace!(target: "test_network", "Creating test network"); - let config = Self::default_config(); - let mut net = Self::from_config(&config); + let mut net = Self::with_runtime(rt_handle); for i in 0..n { trace!(target: "test_network", "Adding peer {}", i); @@ -729,27 +764,29 @@ where /// Add a full peer. fn add_full_peer_with_config(&mut self, config: FullPeerConfig) { - let mut test_client_builder = match (config.keep_blocks, config.storage_chain) { - (Some(keep_blocks), true) => TestClientBuilder::with_tx_storage(keep_blocks), + let mut test_client_builder = match (config.blocks_pruning, config.storage_chain) { + (Some(blocks_pruning), true) => TestClientBuilder::with_tx_storage(blocks_pruning), (None, true) => TestClientBuilder::with_tx_storage(u32::MAX), - (Some(keep_blocks), false) => TestClientBuilder::with_pruning_window(keep_blocks), + (Some(blocks_pruning), false) => TestClientBuilder::with_pruning_window(blocks_pruning), (None, false) => TestClientBuilder::with_default_backend(), }; - if matches!(config.sync_mode, SyncMode::Fast { .. }) { + if let Some(storage) = config.extra_storage { + let genesis_extra_storage = test_client_builder.genesis_init_mut().extra_storage(); + *genesis_extra_storage = storage; + } + + if matches!(config.sync_mode, SyncMode::Fast { .. } | SyncMode::Warp) { test_client_builder = test_client_builder.set_no_genesis(); } let backend = test_client_builder.backend(); let (c, longest_chain) = test_client_builder.build_with_longest_chain(); let client = Arc::new(c); - let (block_import, justification_import, data) = - self.make_block_import(PeersClient::Full(client.clone(), backend.clone())); + let (block_import, justification_import, data) = self + .make_block_import(PeersClient { client: client.clone(), backend: backend.clone() }); - let verifier = self.make_verifier( - PeersClient::Full(client.clone(), backend.clone()), - &Default::default(), - &data, - ); + let verifier = self + .make_verifier(PeersClient { client: client.clone(), backend: backend.clone() }, &data); let verifier = VerifierAdapter::new(verifier); let import_queue = Box::new(BasicQueue::new( @@ -768,6 +805,9 @@ where network_config.transport = TransportConfig::MemoryOnly; network_config.listen_addresses = vec![listen_addr.clone()]; network_config.allow_non_globals_in_dht = true; + network_config + .request_response_protocols + .extend(config.request_response_protocols); network_config.extra_sets = config .notifications_protocols .into_iter() @@ -775,6 +815,7 @@ where notifications_protocol: p, fallback_names: Vec::new(), max_notification_size: 1024 * 1024, + handshake: None, set_config: Default::default(), }) .collect(); @@ -782,7 +823,7 @@ where let addrs = connect_to .iter() .map(|v| { - let peer_id = self.peer(*v).network_service().local_peer_id().clone(); + let peer_id = self.peer(*v).network_service().local_peer_id(); let multiaddr = self.peer(*v).listen_addr.clone(); MultiaddrWithPeerId { peer_id, multiaddr } }) @@ -793,58 +834,117 @@ where let protocol_id = ProtocolId::from("test-protocol-name"); + let fork_id = Some(String::from("test-fork-id")); + let block_request_protocol_config = { let (handler, protocol_config) = - BlockRequestHandler::new(&protocol_id, client.clone(), 50); + BlockRequestHandler::new(&protocol_id, None, client.clone(), 50); self.spawn_task(handler.run().boxed()); protocol_config }; let state_request_protocol_config = { let (handler, protocol_config) = - StateRequestHandler::new(&protocol_id, client.clone(), 50); + StateRequestHandler::new(&protocol_id, None, client.clone(), 50); self.spawn_task(handler.run().boxed()); protocol_config }; let light_client_request_protocol_config = { let (handler, protocol_config) = - LightClientRequestHandler::new(&protocol_id, client.clone()); + LightClientRequestHandler::new(&protocol_id, None, client.clone()); + self.spawn_task(handler.run().boxed()); + protocol_config + }; + + let warp_sync = Arc::new(TestWarpSyncProvider(client.clone())); + + let warp_protocol_config = { + let (handler, protocol_config) = warp_request_handler::RequestHandler::new( + protocol_id.clone(), + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + None, + warp_sync.clone(), + ); self.spawn_task(handler.run().boxed()); protocol_config }; + let block_announce_validator = config + .block_announce_validator + .unwrap_or_else(|| Box::new(DefaultBlockAnnounceValidator)); + let (chain_sync_network_provider, chain_sync_network_handle) = + NetworkServiceProvider::new(); + let (chain_sync, chain_sync_service, block_announce_config) = ChainSync::new( + match network_config.sync_mode { + SyncMode::Full => sc_network_common::sync::SyncMode::Full, + SyncMode::Fast { skip_proofs, storage_chain_mode } => + sc_network_common::sync::SyncMode::LightState { + skip_proofs, + storage_chain_mode, + }, + SyncMode::Warp => sc_network_common::sync::SyncMode::Warp, + }, + client.clone(), + protocol_id.clone(), + &fork_id, + Roles::from(if config.is_authority { &Role::Authority } else { &Role::Full }), + block_announce_validator, + network_config.max_parallel_downloads, + Some(warp_sync), + None, + chain_sync_network_handle, + import_queue.service(), + block_request_protocol_config.name.clone(), + state_request_protocol_config.name.clone(), + Some(warp_protocol_config.name.clone()), + ) + .unwrap(); + + let handle = self.rt_handle().clone(); + let executor = move |f| { + handle.spawn(f); + }; + let network = NetworkWorker::new(sc_network::config::Params { role: if config.is_authority { Role::Authority } else { Role::Full }, - executor: None, - transactions_handler_executor: Box::new(|task| { - async_std::task::spawn(task); - }), + executor: Box::new(executor), network_config, chain: client.clone(), - on_demand: None, - transaction_pool: Arc::new(EmptyTransactionPool), protocol_id, - import_queue, - block_announce_validator: config - .block_announce_validator - .unwrap_or_else(|| Box::new(DefaultBlockAnnounceValidator)), + fork_id, + chain_sync: Box::new(chain_sync), + chain_sync_service: Box::new(chain_sync_service.clone()), metrics_registry: None, - block_request_protocol_config, - state_request_protocol_config, - light_client_request_protocol_config, - warp_sync: None, + block_announce_config, + request_response_protocol_configs: [ + block_request_protocol_config, + state_request_protocol_config, + light_client_request_protocol_config, + warp_protocol_config, + ] + .to_vec(), }) .unwrap(); trace!(target: "test_network", "Peer identifier: {}", network.service().local_peer_id()); + let service = network.service().clone(); + self.rt_handle().spawn(async move { + chain_sync_network_provider.run(service).await; + }); + self.rt_handle().spawn(async move { + import_queue.run(Box::new(chain_sync_service)).await; + }); + self.mut_peers(move |peers| { for peer in peers.iter_mut() { - peer.network.add_known_address( - network.service().local_peer_id().clone(), - listen_addr.clone(), - ); + peer.network + .add_known_address(network.service().local_peer_id(), listen_addr.clone()); } let imported_blocks_stream = Box::pin(client.import_notification_stream().fuse()); @@ -853,7 +953,7 @@ where peers.push(Peer { data, - client: PeersClient::Full(client.clone(), backend.clone()), + client: PeersClient { client: client.clone(), backend: backend.clone() }, select_chain: Some(longest_chain), backend: Some(backend), imported_blocks_stream, @@ -866,97 +966,9 @@ where }); } - /// Add a light peer. - fn add_light_peer(&mut self) { - let (c, backend) = substrate_test_runtime_client::new_light(); - let client = Arc::new(c); - let (block_import, justification_import, data) = - self.make_block_import(PeersClient::Light(client.clone(), backend.clone())); - - let verifier = self.make_verifier( - PeersClient::Light(client.clone(), backend.clone()), - &Default::default(), - &data, - ); - let verifier = VerifierAdapter::new(verifier); - - let import_queue = Box::new(BasicQueue::new( - verifier.clone(), - Box::new(block_import.clone()), - justification_import, - &sp_core::testing::TaskExecutor::new(), - None, - )); - - let listen_addr = build_multiaddr![Memory(rand::random::())]; - - let mut network_config = - NetworkConfiguration::new("test-node", "test-client", Default::default(), None); - network_config.transport = TransportConfig::MemoryOnly; - network_config.listen_addresses = vec![listen_addr.clone()]; - network_config.allow_non_globals_in_dht = true; - - let protocol_id = ProtocolId::from("test-protocol-name"); - - let block_request_protocol_config = - block_request_handler::generate_protocol_config(&protocol_id); - let state_request_protocol_config = - state_request_handler::generate_protocol_config(&protocol_id); - - let light_client_request_protocol_config = - light_client_requests::generate_protocol_config(&protocol_id); - - let network = NetworkWorker::new(sc_network::config::Params { - role: Role::Light, - executor: None, - transactions_handler_executor: Box::new(|task| { - async_std::task::spawn(task); - }), - network_config, - chain: client.clone(), - on_demand: None, - transaction_pool: Arc::new(EmptyTransactionPool), - protocol_id, - import_queue, - block_announce_validator: Box::new(DefaultBlockAnnounceValidator), - metrics_registry: None, - block_request_protocol_config, - state_request_protocol_config, - light_client_request_protocol_config, - warp_sync: None, - }) - .unwrap(); - - self.mut_peers(|peers| { - for peer in peers.iter_mut() { - peer.network.add_known_address( - network.service().local_peer_id().clone(), - listen_addr.clone(), - ); - } - - let imported_blocks_stream = Box::pin(client.import_notification_stream().fuse()); - let finality_notification_stream = - Box::pin(client.finality_notification_stream().fuse()); - - peers.push(Peer { - data, - verifier, - select_chain: None, - backend: None, - block_import, - client: PeersClient::Light(client, backend), - imported_blocks_stream, - finality_notification_stream, - network, - listen_addr, - }); - }); - } - /// Used to spawn background tasks, e.g. the block request protocol handler. fn spawn_task(&self, f: BoxFuture<'static, ()>) { - async_std::task::spawn(f); + self.rt_handle().spawn(f); } /// Polls the testnet until all nodes are in sync. @@ -997,6 +1009,7 @@ where return Poll::Pending } } + Poll::Ready(()) } @@ -1014,37 +1027,37 @@ where Poll::Pending } - /// Blocks the current thread until we are sync'ed. + /// Wait until we are sync'ed. /// /// Calls `poll_until_sync` repeatedly. - fn block_until_sync(&mut self) { - futures::executor::block_on(futures::future::poll_fn::<(), _>(|cx| { - self.poll_until_sync(cx) - })); + /// (If we've not synced within 10 mins then panic rather than hang.) + async fn wait_until_sync(&mut self) { + timeout( + Duration::from_secs(10 * 60), + futures::future::poll_fn::<(), _>(|cx| self.poll_until_sync(cx)), + ) + .await + .expect("sync didn't happen within 10 mins"); } - /// Blocks the current thread until there are no pending packets. + /// Wait until there are no pending packets. /// /// Calls `poll_until_idle` repeatedly with the runtime passed as parameter. - fn block_until_idle(&mut self) { - futures::executor::block_on(futures::future::poll_fn::<(), _>(|cx| { - self.poll_until_idle(cx) - })); + async fn wait_until_idle(&mut self) { + futures::future::poll_fn::<(), _>(|cx| self.poll_until_idle(cx)).await; } - /// Blocks the current thread until all peers are connected to each other. + /// Wait until all peers are connected to each other. /// /// Calls `poll_until_connected` repeatedly with the runtime passed as parameter. - fn block_until_connected(&mut self) { - futures::executor::block_on(futures::future::poll_fn::<(), _>(|cx| { - self.poll_until_connected(cx) - })); + async fn wait_until_connected(&mut self) { + futures::future::poll_fn::<(), _>(|cx| self.poll_until_connected(cx)).await; } /// Polls the testnet. Processes all the pending actions. fn poll(&mut self, cx: &mut FutureContext) { self.mut_peers(|peers| { - for (i, peer) in peers.into_iter().enumerate() { + for (i, peer) in peers.iter_mut().enumerate() { trace!(target: "sync", "-- Polling {}: {}", i, peer.id()); if let Poll::Ready(()) = peer.network.poll_unpin(cx) { panic!("NetworkWorker has terminated unexpectedly.") @@ -1058,14 +1071,10 @@ where peer.network.service().announce_block(notification.hash, None); } - // We poll `finality_notification_stream`, but we only take the last event. - let mut last = None; - while let Poll::Ready(Some(item)) = + // We poll `finality_notification_stream`. + while let Poll::Ready(Some(notification)) = peer.finality_notification_stream.as_mut().poll_next(cx) { - last = Some(item); - } - if let Some(notification) = last { peer.network.on_block_finalized(notification.hash, notification.header); } } @@ -1074,14 +1083,16 @@ where } pub struct TestNet { + rt_handle: tokio::runtime::Handle, peers: Vec>, - fork_choice: ForkChoiceStrategy, } -impl TestNet { - /// Create a `TestNet` that used the given fork choice rule. - pub fn with_fork_choice(fork_choice: ForkChoiceStrategy) -> Self { - Self { peers: Vec::new(), fork_choice } +impl WithRuntime for TestNet { + fn with_runtime(rt_handle: tokio::runtime::Handle) -> Self { + TestNet { rt_handle, peers: Vec::new() } + } + fn rt_handle(&self) -> &tokio::runtime::Handle { + &self.rt_handle } } @@ -1090,18 +1101,8 @@ impl TestNetFactory for TestNet { type PeerData = (); type BlockImport = PeersClient; - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - TestNet { peers: Vec::new(), fork_choice: ForkChoiceStrategy::LongestChain } - } - - fn make_verifier( - &self, - _client: PeersClient, - _config: &ProtocolConfig, - _peer_data: &(), - ) -> Self::Verifier { - PassThroughVerifier::new_with_fork_choice(false, self.fork_choice.clone()) + fn make_verifier(&self, _client: PeersClient, _peer_data: &()) -> Self::Verifier { + PassThroughVerifier::new(false) } fn make_block_import( @@ -1145,29 +1146,28 @@ impl JustificationImport for ForceFinalized { justification: Justification, ) -> Result<(), Self::Error> { self.0 - .finalize_block(BlockId::Hash(hash), Some(justification), true) - .map_err(|_| ConsensusError::InvalidJustification.into()) + .finalize_block(hash, Some(justification), true) + .map_err(|_| ConsensusError::InvalidJustification) } } - pub struct JustificationTestNet(TestNet); +impl WithRuntime for JustificationTestNet { + fn with_runtime(rt_handle: tokio::runtime::Handle) -> Self { + JustificationTestNet(TestNet::with_runtime(rt_handle)) + } + fn rt_handle(&self) -> &tokio::runtime::Handle { + &self.0.rt_handle() + } +} + impl TestNetFactory for JustificationTestNet { type Verifier = PassThroughVerifier; type PeerData = (); type BlockImport = PeersClient; - fn from_config(config: &ProtocolConfig) -> Self { - JustificationTestNet(TestNet::from_config(config)) - } - - fn make_verifier( - &self, - client: PeersClient, - config: &ProtocolConfig, - peer_data: &(), - ) -> Self::Verifier { - self.0.make_verifier(client, config, peer_data) + fn make_verifier(&self, client: PeersClient, peer_data: &()) -> Self::Verifier { + self.0.make_verifier(client, peer_data) } fn peer(&mut self, i: usize) -> &mut Peer { diff --git a/client/network/test/src/sync.rs b/client/network/test/src/sync.rs index c86ccfeac3ed1..efe0e0577c11e 100644 --- a/client/network/test/src/sync.rs +++ b/client/network/test/src/sync.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,15 +17,16 @@ // along with this program. If not, see . use super::*; -use futures::{executor::block_on, Future}; +use futures::Future; use sp_consensus::{block_validation::Validation, BlockOrigin}; use sp_runtime::Justifications; -use std::time::Duration; use substrate_test_runtime::Header; +use tokio::runtime::Runtime; fn test_ancestor_search_when_common_is(n: usize) { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(n, false); net.peer(1).push_blocks(n, false); @@ -35,7 +36,7 @@ fn test_ancestor_search_when_common_is(n: usize) { net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } @@ -43,9 +44,10 @@ fn test_ancestor_search_when_common_is(n: usize) { #[test] fn sync_peers_works() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); for peer in 0..3 { if net.peer(peer).num_peers() != 2 { @@ -59,7 +61,8 @@ fn sync_peers_works() { #[test] fn sync_cycle_from_offline_to_syncing_to_offline() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); for peer in 0..3 { // Offline, and not major syncing. assert!(net.peer(peer).is_offline()); @@ -70,7 +73,7 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { net.peer(2).push_blocks(100, false); // Block until all nodes are online and nodes 0 and 1 and major syncing. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); for peer in 0..3 { // Online @@ -88,7 +91,7 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { })); // Block until all nodes are done syncing. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); for peer in 0..3 { if net.peer(peer).is_major_syncing() { @@ -101,7 +104,7 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { // Now drop nodes 1 and 2, and check that node 0 is offline. net.peers.remove(2); net.peers.remove(1); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if !net.peer(0).is_offline() { Poll::Pending @@ -114,7 +117,8 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { #[test] fn syncing_node_not_major_syncing_when_disconnected() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); // Generate blocks. net.peer(2).push_blocks(100, false); @@ -123,7 +127,7 @@ fn syncing_node_not_major_syncing_when_disconnected() { assert!(!net.peer(1).is_major_syncing()); // Check that we switch to major syncing. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if !net.peer(1).is_major_syncing() { Poll::Pending @@ -135,7 +139,7 @@ fn syncing_node_not_major_syncing_when_disconnected() { // Destroy two nodes, and check that we switch to non-major syncing. net.peers.remove(2); net.peers.remove(0); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).is_major_syncing() { Poll::Pending @@ -148,10 +152,11 @@ fn syncing_node_not_major_syncing_when_disconnected() { #[test] fn sync_from_two_peers_works() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); assert!(!net.peer(0).is_major_syncing()); @@ -160,11 +165,12 @@ fn sync_from_two_peers_works() { #[test] fn sync_from_two_peers_with_ancestry_search_works() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(10, true); net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } @@ -172,13 +178,14 @@ fn sync_from_two_peers_with_ancestry_search_works() { #[test] fn ancestry_search_works_when_backoff_is_one() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(1, false); net.peer(1).push_blocks(2, false); net.peer(2).push_blocks(2, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } @@ -186,13 +193,14 @@ fn ancestry_search_works_when_backoff_is_one() { #[test] fn ancestry_search_works_when_ancestor_is_genesis() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(13, true); net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } @@ -215,9 +223,10 @@ fn ancestry_search_works_when_common_is_hundred() { #[test] fn sync_long_chain_works() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(1).push_blocks(500, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } @@ -225,10 +234,11 @@ fn sync_long_chain_works() { #[test] fn sync_no_common_longer_chain_fails() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(20, true); net.peer(1).push_blocks(20, false); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).is_major_syncing() { Poll::Pending @@ -243,48 +253,45 @@ fn sync_no_common_longer_chain_fails() { #[test] fn sync_justifications() { sp_tracing::try_init_simple(); - let mut net = JustificationTestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = JustificationTestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(20, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); + + let backend = net.peer(0).client().as_backend(); + let hashof10 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(10)).unwrap(); + let hashof15 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(15)).unwrap(); + let hashof20 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(20)).unwrap(); // there's currently no justification for block #10 - assert_eq!(net.peer(0).client().justifications(&BlockId::Number(10)).unwrap(), None); - assert_eq!(net.peer(1).client().justifications(&BlockId::Number(10)).unwrap(), None); + assert_eq!(net.peer(0).client().justifications(hashof10).unwrap(), None); + assert_eq!(net.peer(1).client().justifications(hashof10).unwrap(), None); // we finalize block #10, #15 and #20 for peer 0 with a justification let just = (*b"FRNK", Vec::new()); - net.peer(0) - .client() - .finalize_block(BlockId::Number(10), Some(just.clone()), true) - .unwrap(); - net.peer(0) - .client() - .finalize_block(BlockId::Number(15), Some(just.clone()), true) - .unwrap(); - net.peer(0) - .client() - .finalize_block(BlockId::Number(20), Some(just.clone()), true) - .unwrap(); + net.peer(0).client().finalize_block(hashof10, Some(just.clone()), true).unwrap(); + net.peer(0).client().finalize_block(hashof15, Some(just.clone()), true).unwrap(); + net.peer(0).client().finalize_block(hashof20, Some(just.clone()), true).unwrap(); - let h1 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap(); - let h2 = net.peer(1).client().header(&BlockId::Number(15)).unwrap().unwrap(); - let h3 = net.peer(1).client().header(&BlockId::Number(20)).unwrap().unwrap(); + let hashof10 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap().hash(); + let hashof15 = net.peer(1).client().header(&BlockId::Number(15)).unwrap().unwrap().hash(); + let hashof20 = net.peer(1).client().header(&BlockId::Number(20)).unwrap().unwrap().hash(); // peer 1 should get the justifications from the network - net.peer(1).request_justification(&h1.hash().into(), 10); - net.peer(1).request_justification(&h2.hash().into(), 15); - net.peer(1).request_justification(&h3.hash().into(), 20); + net.peer(1).request_justification(&hashof10, 10); + net.peer(1).request_justification(&hashof15, 15); + net.peer(1).request_justification(&hashof20, 20); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - for height in (10..21).step_by(5) { - if net.peer(0).client().justifications(&BlockId::Number(height)).unwrap() != + for hash in [hashof10, hashof15, hashof20] { + if net.peer(0).client().justifications(hash).unwrap() != Some(Justifications::from((*b"FRNK", Vec::new()))) { return Poll::Pending } - if net.peer(1).client().justifications(&BlockId::Number(height)).unwrap() != + if net.peer(1).client().justifications(hash).unwrap() != Some(Justifications::from((*b"FRNK", Vec::new()))) { return Poll::Pending @@ -298,7 +305,8 @@ fn sync_justifications() { #[test] fn sync_justifications_across_forks() { sp_tracing::try_init_simple(); - let mut net = JustificationTestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = JustificationTestNet::new(runtime.handle().clone(), 3); // we push 5 blocks net.peer(0).push_blocks(5, false); // and then two forks 5 and 6 blocks long @@ -307,23 +315,20 @@ fn sync_justifications_across_forks() { // peer 1 will only see the longer fork. but we'll request justifications // for both and finalize the small fork instead. - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let just = (*b"FRNK", Vec::new()); - net.peer(0) - .client() - .finalize_block(BlockId::Hash(f1_best), Some(just), true) - .unwrap(); + net.peer(0).client().finalize_block(f1_best, Some(just), true).unwrap(); net.peer(1).request_justification(&f1_best, 10); net.peer(1).request_justification(&f2_best, 11); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(0).client().justifications(&BlockId::Number(10)).unwrap() == + if net.peer(0).client().justifications(f1_best).unwrap() == Some(Justifications::from((*b"FRNK", Vec::new()))) && - net.peer(1).client().justifications(&BlockId::Number(10)).unwrap() == + net.peer(1).client().justifications(f1_best).unwrap() == Some(Justifications::from((*b"FRNK", Vec::new()))) { Poll::Ready(()) @@ -336,7 +341,8 @@ fn sync_justifications_across_forks() { #[test] fn sync_after_fork_works() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(30, false); net.peer(1).push_blocks(30, false); net.peer(2).push_blocks(30, false); @@ -349,7 +355,7 @@ fn sync_after_fork_works() { net.peer(2).push_blocks(1, false); // peer 1 has the best chain - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); (net.peers()[1].blockchain_canon_equals(peer1)); @@ -359,30 +365,32 @@ fn sync_after_fork_works() { #[test] fn syncs_all_forks() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(4); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 4); net.peer(0).push_blocks(2, false); net.peer(1).push_blocks(2, false); let b1 = net.peer(0).push_blocks(2, true); let b2 = net.peer(1).push_blocks(4, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); // Check that all peers have all of the branches. - assert!(net.peer(0).has_block(&b1)); - assert!(net.peer(0).has_block(&b2)); - assert!(net.peer(1).has_block(&b1)); - assert!(net.peer(1).has_block(&b2)); + assert!(net.peer(0).has_block(b1)); + assert!(net.peer(0).has_block(b2)); + assert!(net.peer(1).has_block(b1)); + assert!(net.peer(1).has_block(b2)); } #[test] fn own_blocks_are_announced() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); - net.block_until_sync(); // connect'em + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); + runtime.block_on(net.wait_until_sync()); // connect'em net.peer(0) .generate_blocks(1, BlockOrigin::Own, |builder| builder.build().unwrap().block); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert_eq!(net.peer(0).client.info().best_number, 1); assert_eq!(net.peer(1).client.info().best_number, 1); @@ -391,39 +399,11 @@ fn own_blocks_are_announced() { (net.peers()[2].blockchain_canon_equals(peer0)); } -#[test] -fn blocks_are_not_announced_by_light_nodes() { - sp_tracing::try_init_simple(); - let mut net = TestNet::new(0); - - // full peer0 is connected to light peer - // light peer1 is connected to full peer2 - net.add_full_peer(); - net.add_light_peer(); - - // Sync between 0 and 1. - net.peer(0).push_blocks(1, false); - assert_eq!(net.peer(0).client.info().best_number, 1); - net.block_until_sync(); - assert_eq!(net.peer(1).client.info().best_number, 1); - - // Add another node and remove node 0. - net.add_full_peer(); - net.peers.remove(0); - - // Poll for a few seconds and make sure 1 and 2 (now 0 and 1) don't sync together. - let mut delay = futures_timer::Delay::new(Duration::from_secs(5)); - block_on(futures::future::poll_fn::<(), _>(|cx| { - net.poll(cx); - Pin::new(&mut delay).poll(cx) - })); - assert_eq!(net.peer(1).client.info().best_number, 0); -} - #[test] fn can_sync_small_non_best_forks() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(30, false); net.peer(1).push_blocks(30, false); @@ -441,7 +421,7 @@ fn can_sync_small_non_best_forks() { assert!(net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none()); // poll until the two nodes connect, otherwise announcing the block will not work - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).num_peers() == 0 { Poll::Pending @@ -461,7 +441,7 @@ fn can_sync_small_non_best_forks() { // after announcing, peer 1 downloads the block. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); @@ -470,11 +450,11 @@ fn can_sync_small_non_best_forks() { } Poll::Ready(()) })); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let another_fork = net.peer(0).push_blocks_at(BlockId::Number(35), 2, true); net.peer(0).announce_block(another_fork, None); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().header(&BlockId::Hash(another_fork)).unwrap().is_none() { return Poll::Pending @@ -484,75 +464,43 @@ fn can_sync_small_non_best_forks() { } #[test] -fn can_not_sync_from_light_peer() { +fn can_sync_forks_ahead_of_the_best_chain() { sp_tracing::try_init_simple(); - - // given the network with 1 full nodes (#0) and 1 light node (#1) - let mut net = TestNet::new(1); - net.add_light_peer(); - - // generate some blocks on #0 + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(1, false); + net.peer(1).push_blocks(1, false); - // and let the light client sync from this node - net.block_until_sync(); - - // ensure #0 && #1 have the same best block - let full0_info = net.peer(0).client.info(); - let light_info = net.peer(1).client.info(); - assert_eq!(full0_info.best_number, 1); - assert_eq!(light_info.best_number, 1); - assert_eq!(light_info.best_hash, full0_info.best_hash); - - // add new full client (#2) && remove #0 - net.add_full_peer(); - net.peers.remove(0); + runtime.block_on(net.wait_until_connected()); + // Peer 0 is on 2-block fork which is announced with is_best=false + let fork_hash = net.peer(0).generate_blocks_with_fork_choice( + 2, + BlockOrigin::Own, + |builder| builder.build().unwrap().block, + ForkChoiceStrategy::Custom(false), + ); + // Peer 1 is on 1-block fork + net.peer(1).push_blocks(1, false); + assert!(net.peer(0).client().header(&BlockId::Hash(fork_hash)).unwrap().is_some()); + assert_eq!(net.peer(0).client().info().best_number, 1); + assert_eq!(net.peer(1).client().info().best_number, 2); - // ensure that the #2 (now #1) fails to sync block #1 even after 5 seconds - let mut test_finished = futures_timer::Delay::new(Duration::from_secs(5)); - block_on(futures::future::poll_fn::<(), _>(|cx| { + // after announcing, peer 1 downloads the block. + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - Pin::new(&mut test_finished).poll(cx) - })); -} - -#[test] -fn light_peer_imports_header_from_announce() { - sp_tracing::try_init_simple(); - - fn import_with_announce(net: &mut TestNet, hash: H256) { - net.peer(0).announce_block(hash, None); - block_on(futures::future::poll_fn::<(), _>(|cx| { - net.poll(cx); - if net.peer(1).client().header(&BlockId::Hash(hash)).unwrap().is_some() { - Poll::Ready(()) - } else { - Poll::Pending - } - })); - } - - // given the network with 1 full nodes (#0) and 1 light node (#1) - let mut net = TestNet::new(1); - net.add_light_peer(); - - // let them connect to each other - net.block_until_sync(); - - // check that NEW block is imported from announce message - let new_hash = net.peer(0).push_blocks(1, false); - import_with_announce(&mut net, new_hash); - - // check that KNOWN STALE block is imported from announce message - let known_stale_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 1, true); - import_with_announce(&mut net, known_stale_hash); + if net.peer(1).client().header(&BlockId::Hash(fork_hash)).unwrap().is_none() { + return Poll::Pending + } + Poll::Ready(()) + })); } #[test] fn can_sync_explicit_forks() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(30, false); net.peer(1).push_blocks(30, false); @@ -571,7 +519,7 @@ fn can_sync_explicit_forks() { assert!(net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none()); // poll until the two nodes connect, otherwise announcing the block will not work - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).num_peers() == 0 || net.peer(1).num_peers() == 0 { Poll::Pending @@ -592,7 +540,7 @@ fn can_sync_explicit_forks() { net.peer(1).set_sync_fork_request(vec![first_peer_id], small_hash, small_number); // peer 1 downloads the block. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); @@ -606,9 +554,10 @@ fn can_sync_explicit_forks() { #[test] fn syncs_header_only_forks() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(0); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); net.add_full_peer_with_config(Default::default()); - net.add_full_peer_with_config(FullPeerConfig { keep_blocks: Some(3), ..Default::default() }); + net.add_full_peer_with_config(FullPeerConfig { blocks_pruning: Some(3), ..Default::default() }); net.peer(0).push_blocks(2, false); net.peer(1).push_blocks(2, false); @@ -616,15 +565,21 @@ fn syncs_header_only_forks() { let small_hash = net.peer(0).client().info().best_hash; net.peer(1).push_blocks(4, false); - net.block_until_sync(); // Peer 1 will sync the small fork even though common block state is missing - assert!(net.peer(1).has_block(&small_hash)); + while !net.peer(1).has_block(small_hash) { + runtime.block_on(net.wait_until_idle()); + } + + runtime.block_on(net.wait_until_sync()); + assert_eq!(net.peer(0).client().info().best_hash, net.peer(1).client().info().best_hash); + assert_ne!(small_hash, net.peer(0).client().info().best_hash); } #[test] fn does_not_sync_announced_old_best_block() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); let old_hash = net.peer(0).push_blocks(1, false); let old_hash_with_parent = net.peer(0).push_blocks(1, false); @@ -632,7 +587,7 @@ fn does_not_sync_announced_old_best_block() { net.peer(1).push_blocks(20, true); net.peer(0).announce_block(old_hash, None); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { // poll once to import announcement net.poll(cx); Poll::Ready(()) @@ -640,7 +595,7 @@ fn does_not_sync_announced_old_best_block() { assert!(!net.peer(1).is_major_syncing()); net.peer(0).announce_block(old_hash_with_parent, None); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { // poll once to import announcement net.poll(cx); Poll::Ready(()) @@ -652,11 +607,12 @@ fn does_not_sync_announced_old_best_block() { fn full_sync_requires_block_body() { // Check that we don't sync headers-only in full mode. sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_headers(1); // Wait for nodes to connect - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).num_peers() == 0 || net.peer(1).num_peers() == 0 { Poll::Pending @@ -664,20 +620,21 @@ fn full_sync_requires_block_body() { Poll::Ready(()) } })); - net.block_until_idle(); + runtime.block_on(net.wait_until_idle()); assert_eq!(net.peer(1).client.info().best_number, 0); } #[test] fn imports_stale_once() { sp_tracing::try_init_simple(); + let runtime = Runtime::new().unwrap(); - fn import_with_announce(net: &mut TestNet, hash: H256) { + fn import_with_announce(runtime: &Runtime, net: &mut TestNet, hash: H256) { // Announce twice net.peer(0).announce_block(hash, None); net.peer(0).announce_block(hash, None); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().header(&BlockId::Hash(hash)).unwrap().is_some() { Poll::Ready(()) @@ -688,49 +645,44 @@ fn imports_stale_once() { } // given the network with 2 full nodes - let mut net = TestNet::new(2); + let mut net = TestNet::new(runtime.handle().clone(), 2); // let them connect to each other - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); // check that NEW block is imported from announce message let new_hash = net.peer(0).push_blocks(1, false); - import_with_announce(&mut net, new_hash); + import_with_announce(&runtime, &mut net, new_hash); assert_eq!(net.peer(1).num_downloaded_blocks(), 1); // check that KNOWN STALE block is imported from announce message let known_stale_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 1, true); - import_with_announce(&mut net, known_stale_hash); + import_with_announce(&runtime, &mut net, known_stale_hash); assert_eq!(net.peer(1).num_downloaded_blocks(), 2); } #[test] fn can_sync_to_peers_with_wrong_common_block() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(2, true); net.peer(1).push_blocks(2, true); let fork_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 2, false); net.peer(1).push_blocks_at(BlockId::Number(0), 2, false); // wait for connection - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); // both peers re-org to the same fork without notifying each other let just = Some((*b"FRNK", Vec::new())); - net.peer(0) - .client() - .finalize_block(BlockId::Hash(fork_hash), just.clone(), true) - .unwrap(); - net.peer(1) - .client() - .finalize_block(BlockId::Hash(fork_hash), just, true) - .unwrap(); + net.peer(0).client().finalize_block(fork_hash, just.clone(), true).unwrap(); + net.peer(1).client().finalize_block(fork_hash, just, true).unwrap(); let final_hash = net.peer(0).push_blocks(1, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); - assert!(net.peer(1).has_block(&final_hash)); + assert!(net.peer(1).has_block(final_hash)); } /// Returns `is_new_best = true` for each validated announcement. @@ -773,7 +725,8 @@ impl BlockAnnounceValidator for FailingBlockAnnounceValidator { #[test] fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { sp_tracing::try_init_simple(); - let mut net = TestNet::with_fork_choice(ForkChoiceStrategy::Custom(false)); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(FullPeerConfig { @@ -781,18 +734,19 @@ fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { ..Default::default() }); - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); - let block_hash = net.peer(0).push_blocks(1, false); + // Add blocks but don't set them as best + let block_hash = net.peer(0).generate_blocks_with_fork_choice( + 1, + BlockOrigin::Own, + |builder| builder.build().unwrap().block, + ForkChoiceStrategy::Custom(false), + ); - while !net.peer(2).has_block(&block_hash) { - net.block_until_idle(); + while !net.peer(2).has_block(block_hash) { + runtime.block_on(net.wait_until_idle()); } - - // Peer1 should not have the block, because peer 0 did not reported the block - // as new best. However, peer2 has a special block announcement validator - // that flags all blocks as `is_new_best` and thus, it should have synced the blocks. - assert!(!net.peer(1).has_block(&block_hash)); } /// Waits for some time until the validation is successfull. @@ -816,19 +770,26 @@ impl BlockAnnounceValidator for DeferredBlockAnnounceValidator { #[test] fn wait_until_deferred_block_announce_validation_is_ready() { sp_tracing::try_init_simple(); - let mut net = TestNet::with_fork_choice(ForkChoiceStrategy::Custom(false)); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(FullPeerConfig { block_announce_validator: Some(Box::new(NewBestBlockAnnounceValidator)), ..Default::default() }); - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); - let block_hash = net.peer(0).push_blocks(1, true); + // Add blocks but don't set them as best + let block_hash = net.peer(0).generate_blocks_with_fork_choice( + 1, + BlockOrigin::Own, + |builder| builder.build().unwrap().block, + ForkChoiceStrategy::Custom(false), + ); - while !net.peer(1).has_block(&block_hash) { - net.block_until_idle(); + while !net.peer(1).has_block(block_hash) { + runtime.block_on(net.wait_until_idle()); } } @@ -837,33 +798,45 @@ fn wait_until_deferred_block_announce_validation_is_ready() { #[test] fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 1); // Produce some blocks - let block_hash = net.peer(0).push_blocks_at_without_informing_sync(BlockId::Number(0), 3, true); + let block_hash = + net.peer(0) + .push_blocks_at_without_informing_sync(BlockId::Number(0), 3, true, true); // Add a node and wait until they are connected - net.add_full_peer_with_config(Default::default()); - net.block_until_connected(); - net.block_until_idle(); + runtime.block_on(async { + net.add_full_peer_with_config(Default::default()); + net.wait_until_connected().await; + net.wait_until_idle().await; + }); // The peer should not have synced the block. - assert!(!net.peer(1).has_block(&block_hash)); + assert!(!net.peer(1).has_block(block_hash)); // Make sync protocol aware of the best block net.peer(0).network_service().new_best_block_imported(block_hash, 3); - net.block_until_idle(); + runtime.block_on(net.wait_until_idle()); // Connect another node that should now sync to the tip - net.add_full_peer_with_config(Default::default()); - net.block_until_connected(); + net.add_full_peer_with_config(FullPeerConfig { + connect_to_peers: Some(vec![0]), + ..Default::default() + }); - while !net.peer(2).has_block(&block_hash) { - net.block_until_idle(); - } + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + net.poll(cx); + if net.peer(2).has_block(block_hash) { + Poll::Ready(()) + } else { + Poll::Pending + } + })); // However peer 1 should still not have the block. - assert!(!net.peer(1).has_block(&block_hash)); + assert!(!net.peer(1).has_block(block_hash)); } /// Ensures that if we as a syncing node sync to the tip while we are connected to another peer @@ -871,24 +844,29 @@ fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { #[test] fn sync_to_tip_when_we_sync_together_with_multiple_peers() { sp_tracing::try_init_simple(); + let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(3); + let mut net = TestNet::new(runtime.handle().clone(), 3); let block_hash = net.peer(0) - .push_blocks_at_without_informing_sync(BlockId::Number(0), 10_000, false); + .push_blocks_at_without_informing_sync(BlockId::Number(0), 10_000, false, false); net.peer(1) - .push_blocks_at_without_informing_sync(BlockId::Number(0), 5_000, false); + .push_blocks_at_without_informing_sync(BlockId::Number(0), 5_000, false, false); - net.block_until_connected(); - net.block_until_idle(); + runtime.block_on(async { + net.wait_until_connected().await; + net.wait_until_idle().await; + }); - assert!(!net.peer(2).has_block(&block_hash)); + assert!(!net.peer(2).has_block(block_hash)); net.peer(0).network_service().new_best_block_imported(block_hash, 10_000); - while !net.peer(2).has_block(&block_hash) && !net.peer(1).has_block(&block_hash) { - net.block_until_idle(); + net.peer(0).network_service().announce_block(block_hash, None); + + while !net.peer(2).has_block(block_hash) && !net.peer(1).has_block(block_hash) { + runtime.block_on(net.wait_until_idle()); } } @@ -919,7 +897,8 @@ fn block_announce_data_is_propagated() { } sp_tracing::try_init_simple(); - let mut net = TestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 1); net.add_full_peer_with_config(FullPeerConfig { block_announce_validator: Some(Box::new(TestBlockAnnounceValidator)), @@ -933,9 +912,12 @@ fn block_announce_data_is_propagated() { }); // Wait until peer 1 is connected to both nodes. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(1).num_peers() == 2 { + if net.peer(1).num_peers() == 2 && + net.peer(0).num_peers() == 1 && + net.peer(2).num_peers() == 1 + { Poll::Ready(()) } else { Poll::Pending @@ -945,8 +927,8 @@ fn block_announce_data_is_propagated() { let block_hash = net.peer(0).push_blocks_at_without_announcing(BlockId::Number(0), 1, true); net.peer(0).announce_block(block_hash, Some(vec![137])); - while !net.peer(1).has_block(&block_hash) || !net.peer(2).has_block(&block_hash) { - net.block_until_idle(); + while !net.peer(1).has_block(block_hash) || !net.peer(2).has_block(block_hash) { + runtime.block_on(net.wait_until_idle()); } } @@ -976,20 +958,23 @@ fn continue_to_sync_after_some_block_announcement_verifications_failed() { } sp_tracing::try_init_simple(); - let mut net = TestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 1); net.add_full_peer_with_config(FullPeerConfig { block_announce_validator: Some(Box::new(TestBlockAnnounceValidator)), ..Default::default() }); - net.block_until_connected(); - net.block_until_idle(); + runtime.block_on(async { + net.wait_until_connected().await; + net.wait_until_idle().await; + }); let block_hash = net.peer(0).push_blocks(500, true); - net.block_until_sync(); - assert!(net.peer(1).has_block(&block_hash)); + runtime.block_on(net.wait_until_sync()); + assert!(net.peer(1).has_block(block_hash)); } /// When being spammed by the same request of a peer, we ban this peer. However, we should only ban @@ -999,18 +984,19 @@ fn continue_to_sync_after_some_block_announcement_verifications_failed() { #[test] fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { sp_tracing::try_init_simple(); - let mut net = JustificationTestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = JustificationTestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(10, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); - // there's currently no justification for block #10 - assert_eq!(net.peer(0).client().justifications(&BlockId::Number(10)).unwrap(), None); - assert_eq!(net.peer(1).client().justifications(&BlockId::Number(10)).unwrap(), None); + let hashof10 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap().hash(); - let h1 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap(); + // there's currently no justification for block #10 + assert_eq!(net.peer(0).client().justifications(hashof10).unwrap(), None); + assert_eq!(net.peer(1).client().justifications(hashof10).unwrap(), None); // Let's assume block 10 was finalized, but we still need the justification from the network. - net.peer(1).request_justification(&h1.hash().into(), 10); + net.peer(1).request_justification(&hashof10, 10); // Let's build some more blocks and wait always for the network to have synced them for _ in 0..5 { @@ -1018,20 +1004,27 @@ fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { // justification request. std::thread::sleep(std::time::Duration::from_secs(10)); net.peer(0).push_blocks(1, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert_eq!(1, net.peer(0).num_peers()); } + let hashof10 = net + .peer(0) + .client() + .as_backend() + .blockchain() + .expect_block_hash_from_id(&BlockId::Number(10)) + .unwrap(); // Finalize the block and make the justification available. net.peer(0) .client() - .finalize_block(BlockId::Number(10), Some((*b"FRNK", Vec::new())), true) + .finalize_block(hashof10, Some((*b"FRNK", Vec::new())), true) .unwrap(); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(1).client().justifications(&BlockId::Number(10)).unwrap() != + if net.peer(1).client().justifications(hashof10).unwrap() != Some(Justifications::from((*b"FRNK", Vec::new()))) { return Poll::Pending @@ -1044,18 +1037,19 @@ fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { #[test] fn syncs_all_forks_from_single_peer() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(10, false); net.peer(1).push_blocks(10, false); // poll until the two nodes connect, otherwise announcing the block will not work - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); // Peer 0 produces new blocks and announces. let branch1 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, true); // Wait till peer 1 starts downloading - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).network().best_seen_block() != Some(12) { return Poll::Pending @@ -1066,7 +1060,7 @@ fn syncs_all_forks_from_single_peer() { // Peer 0 produces and announces another fork let branch2 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); // Peer 1 should have both branches, assert!(net.peer(1).client().header(&BlockId::Hash(branch1)).unwrap().is_some()); @@ -1076,7 +1070,8 @@ fn syncs_all_forks_from_single_peer() { #[test] fn syncs_after_missing_announcement() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(0); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); net.add_full_peer_with_config(Default::default()); // Set peer 1 to ignore announcement net.add_full_peer_with_config(FullPeerConfig { @@ -1086,39 +1081,77 @@ fn syncs_after_missing_announcement() { net.peer(0).push_blocks(10, false); net.peer(1).push_blocks(10, false); - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); // Peer 0 produces a new block and announces. Peer 1 ignores announcement. net.peer(0).push_blocks_at(BlockId::Number(10), 1, false); // Peer 0 produces another block and announces. let final_block = net.peer(0).push_blocks_at(BlockId::Number(11), 1, false); net.peer(1).push_blocks_at(BlockId::Number(10), 1, true); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert!(net.peer(1).client().header(&BlockId::Hash(final_block)).unwrap().is_some()); } #[test] fn syncs_state() { sp_tracing::try_init_simple(); + let runtime = Runtime::new().unwrap(); for skip_proofs in &[false, true] { - let mut net = TestNet::new(0); - net.add_full_peer_with_config(Default::default()); - net.add_full_peer_with_config(FullPeerConfig { - sync_mode: SyncMode::Fast { skip_proofs: *skip_proofs, storage_chain_mode: false }, - ..Default::default() - }); + let mut net = TestNet::new(runtime.handle().clone(), 0); + let mut genesis_storage: sp_core::storage::Storage = Default::default(); + genesis_storage.top.insert(b"additional_key".to_vec(), vec![1]); + let mut child_data: std::collections::BTreeMap, Vec> = Default::default(); + for i in 0u8..16 { + child_data.insert(vec![i; 5], vec![i; 33]); + } + let child1 = sp_core::storage::StorageChild { + data: child_data.clone(), + child_info: sp_core::storage::ChildInfo::new_default(b"child1"), + }; + let child3 = sp_core::storage::StorageChild { + data: child_data.clone(), + child_info: sp_core::storage::ChildInfo::new_default(b"child3"), + }; + for i in 22u8..33 { + child_data.insert(vec![i; 5], vec![i; 33]); + } + let child2 = sp_core::storage::StorageChild { + data: child_data.clone(), + child_info: sp_core::storage::ChildInfo::new_default(b"child2"), + }; + genesis_storage + .children_default + .insert(child1.child_info.storage_key().to_vec(), child1); + genesis_storage + .children_default + .insert(child2.child_info.storage_key().to_vec(), child2); + genesis_storage + .children_default + .insert(child3.child_info.storage_key().to_vec(), child3); + let mut config_one = FullPeerConfig::default(); + config_one.extra_storage = Some(genesis_storage.clone()); + net.add_full_peer_with_config(config_one); + let mut config_two = FullPeerConfig::default(); + config_two.extra_storage = Some(genesis_storage); + config_two.sync_mode = + SyncMode::Fast { skip_proofs: *skip_proofs, storage_chain_mode: false }; + net.add_full_peer_with_config(config_two); net.peer(0).push_blocks(64, false); // Wait for peer 1 to sync header chain. - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert!(!net.peer(1).client().has_state_at(&BlockId::Number(64))); let just = (*b"FRNK", Vec::new()); - net.peer(1) + let hashof60 = net + .peer(0) .client() - .finalize_block(BlockId::Number(60), Some(just), true) + .as_backend() + .blockchain() + .expect_block_hash_from_id(&BlockId::Number(60)) .unwrap(); + net.peer(1).client().finalize_block(hashof60, Some(just), true).unwrap(); // Wait for state sync. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client.info().finalized_state.is_some() { Poll::Ready(()) @@ -1128,7 +1161,7 @@ fn syncs_state() { })); assert!(!net.peer(1).client().has_state_at(&BlockId::Number(64))); // Wait for the rest of the states to be imported. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().has_state_at(&BlockId::Number(64)) { Poll::Ready(()) @@ -1143,7 +1176,8 @@ fn syncs_state() { fn syncs_indexed_blocks() { use sp_runtime::traits::Hash; sp_tracing::try_init_simple(); - let mut net = TestNet::new(0); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); let mut n: u64 = 0; net.add_full_peer_with_config(FullPeerConfig { storage_chain: true, ..Default::default() }); net.add_full_peer_with_config(FullPeerConfig { @@ -1164,36 +1198,67 @@ fn syncs_indexed_blocks() { false, true, true, + ForkChoiceStrategy::LongestChain, ); let indexed_key = sp_runtime::traits::BlakeTwo256::hash(&42u64.to_le_bytes()); assert!(net .peer(0) .client() - .as_full() - .unwrap() - .indexed_transaction(&indexed_key) + .as_client() + .indexed_transaction(indexed_key) .unwrap() .is_some()); assert!(net .peer(1) .client() - .as_full() - .unwrap() - .indexed_transaction(&indexed_key) + .as_client() + .indexed_transaction(indexed_key) .unwrap() .is_none()); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert!(net .peer(1) .client() - .as_full() - .unwrap() - .indexed_transaction(&indexed_key) + .as_client() + .indexed_transaction(indexed_key) .unwrap() .is_some()); } +#[test] +fn warp_sync() { + sp_tracing::try_init_simple(); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); + // Create 3 synced peers and 1 peer trying to warp sync. + net.add_full_peer_with_config(Default::default()); + net.add_full_peer_with_config(Default::default()); + net.add_full_peer_with_config(Default::default()); + net.add_full_peer_with_config(FullPeerConfig { + sync_mode: SyncMode::Warp, + ..Default::default() + }); + let gap_end = net.peer(0).push_blocks(63, false); + let target = net.peer(0).push_blocks(1, false); + net.peer(1).push_blocks(64, false); + net.peer(2).push_blocks(64, false); + // Wait for peer 1 to sync state. + runtime.block_on(net.wait_until_sync()); + assert!(!net.peer(3).client().has_state_at(&BlockId::Number(1))); + assert!(net.peer(3).client().has_state_at(&BlockId::Number(64))); + + // Wait for peer 1 download block history + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + net.poll(cx); + if net.peer(3).has_body(gap_end) && net.peer(3).has_body(target) { + Poll::Ready(()) + } else { + Poll::Pending + } + })); +} + #[test] fn syncs_huge_blocks() { use sp_core::storage::well_known_keys::HEAP_PAGES; @@ -1201,7 +1266,8 @@ fn syncs_huge_blocks() { use substrate_test_runtime_client::BlockBuilderExt; sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); // Increase heap space for bigger blocks. net.peer(0).generate_blocks(1, BlockOrigin::Own, |mut builder| { @@ -1218,7 +1284,7 @@ fn syncs_huge_blocks() { builder.build().unwrap().block }); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert_eq!(net.peer(0).client.info().best_number, 33); assert_eq!(net.peer(1).client.info().best_number, 33); } diff --git a/client/network/transactions/Cargo.toml b/client/network/transactions/Cargo.toml new file mode 100644 index 0000000000000..cb45abca02f6a --- /dev/null +++ b/client/network/transactions/Cargo.toml @@ -0,0 +1,27 @@ +[package] +description = "Substrate transaction protocol" +name = "sc-network-transactions" +version = "0.10.0-dev" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors = ["Parity Technologies "] +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +documentation = "https://docs.rs/sc-network-transactions" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +array-bytes = "4.1" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +futures = "0.3.21" +hex = "0.4.0" +libp2p = "0.49.0" +log = "0.4.17" +pin-project = "1.0.12" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } diff --git a/client/network/transactions/src/config.rs b/client/network/transactions/src/config.rs new file mode 100644 index 0000000000000..abb8cccd301ac --- /dev/null +++ b/client/network/transactions/src/config.rs @@ -0,0 +1,98 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Configuration of the transaction protocol + +use futures::prelude::*; +use sc_network_common::ExHashT; +use sp_runtime::traits::Block as BlockT; +use std::{collections::HashMap, future::Future, pin::Pin, time}; + +/// Interval at which we propagate transactions; +pub(crate) const PROPAGATE_TIMEOUT: time::Duration = time::Duration::from_millis(2900); + +/// Maximum number of known transaction hashes to keep for a peer. +/// +/// This should be approx. 2 blocks full of transactions for the network to function properly. +pub(crate) const MAX_KNOWN_TRANSACTIONS: usize = 10240; // ~300kb per peer + overhead. + +/// Maximum allowed size for a transactions notification. +pub(crate) const MAX_TRANSACTIONS_SIZE: u64 = 16 * 1024 * 1024; + +/// Maximum number of transaction validation request we keep at any moment. +pub(crate) const MAX_PENDING_TRANSACTIONS: usize = 8192; + +/// Result of the transaction import. +#[derive(Clone, Copy, Debug)] +pub enum TransactionImport { + /// Transaction is good but already known by the transaction pool. + KnownGood, + /// Transaction is good and not yet known. + NewGood, + /// Transaction is invalid. + Bad, + /// Transaction import was not performed. + None, +} + +/// Future resolving to transaction import result. +pub type TransactionImportFuture = Pin + Send>>; + +/// Transaction pool interface +pub trait TransactionPool: Send + Sync { + /// Get transactions from the pool that are ready to be propagated. + fn transactions(&self) -> Vec<(H, B::Extrinsic)>; + /// Get hash of transaction. + fn hash_of(&self, transaction: &B::Extrinsic) -> H; + /// Import a transaction into the pool. + /// + /// This will return future. + fn import(&self, transaction: B::Extrinsic) -> TransactionImportFuture; + /// Notify the pool about transactions broadcast. + fn on_broadcasted(&self, propagations: HashMap>); + /// Get transaction by hash. + fn transaction(&self, hash: &H) -> Option; +} + +/// Dummy implementation of the [`TransactionPool`] trait for a transaction pool that is always +/// empty and discards all incoming transactions. +/// +/// Requires the "hash" type to implement the `Default` trait. +/// +/// Useful for testing purposes. +pub struct EmptyTransactionPool; + +impl TransactionPool for EmptyTransactionPool { + fn transactions(&self) -> Vec<(H, B::Extrinsic)> { + Vec::new() + } + + fn hash_of(&self, _transaction: &B::Extrinsic) -> H { + Default::default() + } + + fn import(&self, _transaction: B::Extrinsic) -> TransactionImportFuture { + Box::pin(future::ready(TransactionImport::KnownGood)) + } + + fn on_broadcasted(&self, _: HashMap>) {} + + fn transaction(&self, _h: &H) -> Option { + None + } +} diff --git a/client/network/src/transactions.rs b/client/network/transactions/src/lib.rs similarity index 73% rename from client/network/src/transactions.rs rename to client/network/transactions/src/lib.rs index 82e7e8fe1714c..4cc76507c6f16 100644 --- a/client/network/src/transactions.rs +++ b/client/network/transactions/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -26,48 +26,34 @@ //! - Use [`TransactionsHandlerPrototype::build`] then [`TransactionsHandler::run`] to obtain a //! `Future` that processes transactions. -use crate::{ - config::{self, ProtocolId, TransactionImport, TransactionImportFuture, TransactionPool}, - error, - protocol::message, - service::NetworkService, - utils::{interval, LruHashSet}, - Event, ExHashT, ObservedRole, -}; - +use crate::config::*; use codec::{Decode, Encode}; use futures::{channel::mpsc, prelude::*, stream::FuturesUnordered}; use libp2p::{multiaddr, PeerId}; use log::{debug, trace, warn}; use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; +use sc_network_common::{ + config::{NonDefaultSetConfig, NonReservedPeerMode, ProtocolId, SetConfig}, + error, + protocol::{event::Event, role::ObservedRole, ProtocolName}, + service::{NetworkEventStream, NetworkNotification, NetworkPeers}, + utils::{interval, LruHashSet}, + ExHashT, +}; use sp_runtime::traits::Block as BlockT; use std::{ - borrow::Cow, collections::{hash_map::Entry, HashMap}, iter, num::NonZeroUsize, pin::Pin, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + sync::Arc, task::Poll, - time, }; -/// Interval at which we propagate transactions; -const PROPAGATE_TIMEOUT: time::Duration = time::Duration::from_millis(2900); - -/// Maximum number of known transaction hashes to keep for a peer. -/// -/// This should be approx. 2 blocks full of transactions for the network to function properly. -const MAX_KNOWN_TRANSACTIONS: usize = 10240; // ~300kb per peer + overhead. +pub mod config; -/// Maximum allowed size for a transactions notification. -const MAX_TRANSACTIONS_SIZE: u64 = 16 * 1024 * 1024; - -/// Maximum number of transaction validation request we keep at any moment. -const MAX_PENDING_TRANSACTIONS: usize = 8192; +/// A set of transactions. +pub type Transactions = Vec; mod rep { use sc_peerset::ReputationChange as Rep; @@ -82,8 +68,6 @@ mod rep { pub const GOOD_TRANSACTION: Rep = Rep::new(1 << 7, "Good transaction"); /// Reputation change when a peer sends us a bad transaction. pub const BAD_TRANSACTION: Rep = Rep::new(-(1 << 12), "Bad transaction"); - /// We received an unexpected transaction packet. - pub const UNEXPECTED_TRANSACTIONS: Rep = Rep::new_fatal("Unexpected transactions packet"); } struct Metrics { @@ -92,10 +76,10 @@ struct Metrics { impl Metrics { fn register(r: &Registry) -> Result { - Ok(Metrics { + Ok(Self { propagated_transactions: register( Counter::new( - "sync_propagated_transactions", + "substrate_sync_propagated_transactions", "Number of transactions propagated to at least one peer", )?, r, @@ -127,34 +111,43 @@ impl Future for PendingTransaction { /// Prototype for a [`TransactionsHandler`]. pub struct TransactionsHandlerPrototype { - protocol_name: Cow<'static, str>, + protocol_name: ProtocolName, + fallback_protocol_names: Vec, } impl TransactionsHandlerPrototype { /// Create a new instance. - pub fn new(protocol_id: ProtocolId) -> Self { - TransactionsHandlerPrototype { - protocol_name: Cow::from({ - let mut proto = String::new(); - proto.push_str("/"); - proto.push_str(protocol_id.as_ref()); - proto.push_str("/transactions/1"); - proto - }), + pub fn new>( + protocol_id: ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, + ) -> Self { + let genesis_hash = genesis_hash.as_ref(); + let protocol_name = if let Some(fork_id) = fork_id { + format!("/{}/{}/transactions/1", array_bytes::bytes2hex("", genesis_hash), fork_id) + } else { + format!("/{}/transactions/1", array_bytes::bytes2hex("", genesis_hash)) + }; + let legacy_protocol_name = format!("/{}/transactions/1", protocol_id.as_ref()); + + Self { + protocol_name: protocol_name.into(), + fallback_protocol_names: iter::once(legacy_protocol_name.into()).collect(), } } /// Returns the configuration of the set to put in the network configuration. - pub fn set_config(&self) -> config::NonDefaultSetConfig { - config::NonDefaultSetConfig { + pub fn set_config(&self) -> NonDefaultSetConfig { + NonDefaultSetConfig { notifications_protocol: self.protocol_name.clone(), - fallback_names: Vec::new(), + fallback_names: self.fallback_protocol_names.clone(), max_notification_size: MAX_TRANSACTIONS_SIZE, - set_config: config::SetConfig { + handshake: None, + set_config: SetConfig { in_peers: 0, out_peers: 0, reserved_nodes: Vec::new(), - non_reserved_mode: config::NonReservedPeerMode::Deny, + non_reserved_mode: NonReservedPeerMode::Deny, }, } } @@ -163,29 +156,31 @@ impl TransactionsHandlerPrototype { /// the behaviour of the handler while it's running. /// /// Important: the transactions handler is initially disabled and doesn't gossip transactions. - /// You must call [`TransactionsHandlerController::set_gossip_enabled`] to enable it. - pub fn build( + /// Gossiping is enabled when major syncing is done. + pub fn build< + B: BlockT + 'static, + H: ExHashT, + S: NetworkPeers + NetworkEventStream + NetworkNotification + sp_consensus::SyncOracle, + >( self, - service: Arc>, - local_role: config::Role, + service: S, transaction_pool: Arc>, metrics_registry: Option<&Registry>, - ) -> error::Result<(TransactionsHandler, TransactionsHandlerController)> { - let event_stream = service.event_stream("transactions-handler").boxed(); + ) -> error::Result<(TransactionsHandler, TransactionsHandlerController)> { + let event_stream = service.event_stream("transactions-handler"); let (to_handler, from_controller) = mpsc::unbounded(); - let gossip_enabled = Arc::new(AtomicBool::new(false)); let handler = TransactionsHandler { protocol_name: self.protocol_name, - propagate_timeout: Box::pin(interval(PROPAGATE_TIMEOUT)), + propagate_timeout: (Box::pin(interval(PROPAGATE_TIMEOUT)) + as Pin + Send>>) + .fuse(), pending_transactions: FuturesUnordered::new(), pending_transactions_peers: HashMap::new(), - gossip_enabled: gossip_enabled.clone(), service, - event_stream, + event_stream: event_stream.fuse(), peers: HashMap::new(), transaction_pool, - local_role, from_controller, metrics: if let Some(r) = metrics_registry { Some(Metrics::register(r)?) @@ -194,7 +189,7 @@ impl TransactionsHandlerPrototype { }, }; - let controller = TransactionsHandlerController { to_handler, gossip_enabled }; + let controller = TransactionsHandlerController { to_handler }; Ok((handler, controller)) } @@ -203,15 +198,9 @@ impl TransactionsHandlerPrototype { /// Controls the behaviour of a [`TransactionsHandler`] it is connected to. pub struct TransactionsHandlerController { to_handler: mpsc::UnboundedSender>, - gossip_enabled: Arc, } impl TransactionsHandlerController { - /// Controls whether transactions are being gossiped on the network. - pub fn set_gossip_enabled(&mut self, enabled: bool) { - self.gossip_enabled.store(enabled, Ordering::Relaxed); - } - /// You may call this when new transactions are imported by the transaction pool. /// /// All transactions will be fetched from the `TransactionPool` that was passed at @@ -235,10 +224,14 @@ enum ToHandler { } /// Handler for transactions. Call [`TransactionsHandler::run`] to start the processing. -pub struct TransactionsHandler { - protocol_name: Cow<'static, str>, +pub struct TransactionsHandler< + B: BlockT + 'static, + H: ExHashT, + S: NetworkPeers + NetworkEventStream + NetworkNotification + sp_consensus::SyncOracle, +> { + protocol_name: ProtocolName, /// Interval at which we call `propagate_transactions`. - propagate_timeout: Pin + Send>>, + propagate_timeout: stream::Fuse + Send>>>, /// Pending transactions verification tasks. pending_transactions: FuturesUnordered>, /// As multiple peers can send us the same transaction, we group @@ -247,14 +240,12 @@ pub struct TransactionsHandler { /// multiple times concurrently. pending_transactions_peers: HashMap>, /// Network service to use to send messages and manage peers. - service: Arc>, + service: S, /// Stream of networking events. - event_stream: Pin + Send>>, + event_stream: stream::Fuse + Send>>>, // All connected peers peers: HashMap>, transaction_pool: Arc>, - gossip_enabled: Arc, - local_role: config::Role, from_controller: mpsc::UnboundedReceiver>, /// Prometheus metrics. metrics: Option, @@ -268,13 +259,18 @@ struct Peer { role: ObservedRole, } -impl TransactionsHandler { +impl TransactionsHandler +where + B: BlockT + 'static, + H: ExHashT, + S: NetworkPeers + NetworkEventStream + NetworkNotification + sp_consensus::SyncOracle, +{ /// Turns the [`TransactionsHandler`] into a future that should run forever and not be /// interrupted. pub async fn run(mut self) { loop { futures::select! { - _ = self.propagate_timeout.next().fuse() => { + _ = self.propagate_timeout.next() => { self.propagate_transactions(); }, (tx_hash, result) = self.pending_transactions.select_next_some() => { @@ -284,7 +280,7 @@ impl TransactionsHandler { warn!(target: "sub-libp2p", "Inconsistent state, no peers for pending transaction!"); } }, - network_event = self.event_stream.next().fuse() => { + network_event = self.event_stream.next() => { if let Some(network_event) = network_event { self.handle_network_event(network_event).await; } else { @@ -292,7 +288,7 @@ impl TransactionsHandler { return; } }, - message = self.from_controller.select_next_some().fuse() => { + message = self.from_controller.select_next_some() => { match message { ToHandler::PropagateTransaction(hash) => self.propagate_transaction(&hash), ToHandler::PropagateTransactions => self.propagate_transactions(), @@ -317,15 +313,10 @@ impl TransactionsHandler { } }, Event::SyncDisconnected { remote } => { - let addr = iter::once(multiaddr::Protocol::P2p(remote.into())) - .collect::(); - let result = self.service.remove_peers_from_reserved_set( + self.service.remove_peers_from_reserved_set( self.protocol_name.clone(), - iter::once(addr).collect(), + iter::once(remote).collect(), ); - if let Err(err) = result { - log::error!(target: "sync", "Removing reserved peer failed: {}", err); - } }, Event::NotificationStreamOpened { remote, protocol, role, .. } @@ -341,13 +332,13 @@ impl TransactionsHandler { }, ); debug_assert!(_was_in.is_none()); - } + }, Event::NotificationStreamClosed { remote, protocol } if protocol == self.protocol_name => { let _peer = self.peers.remove(&remote); debug_assert!(_peer.is_some()); - } + }, Event::NotificationsReceived { remote, messages } => { for (protocol, message) in messages { @@ -355,9 +346,9 @@ impl TransactionsHandler { continue } - if let Ok(m) = as Decode>::decode( - &mut message.as_ref(), - ) { + if let Ok(m) = + as Decode>::decode(&mut message.as_ref()) + { self.on_transactions(remote, m); } else { warn!(target: "sub-libp2p", "Failed to decode transactions list"); @@ -371,18 +362,10 @@ impl TransactionsHandler { } /// Called when peer sends us new transactions - fn on_transactions(&mut self, who: PeerId, transactions: message::Transactions) { - // sending transaction to light node is considered a bad behavior - if matches!(self.local_role, config::Role::Light) { - debug!(target: "sync", "Peer {} is trying to send transactions to the light node", who); - self.service.disconnect_peer(who, self.protocol_name.clone()); - self.service.report_peer(who, rep::UNEXPECTED_TRANSACTIONS); - return - } - - // Accept transactions only when enabled - if !self.gossip_enabled.load(Ordering::Relaxed) { - trace!(target: "sync", "{} Ignoring transactions while disabled", who); + fn on_transactions(&mut self, who: PeerId, transactions: Transactions) { + // Accept transactions only when node is not major syncing + if self.service.is_major_syncing() { + trace!(target: "sync", "{} Ignoring transactions while major syncing", who); return } @@ -401,7 +384,7 @@ impl TransactionsHandler { let hash = self.transaction_pool.hash_of(&t); peer.known_transactions.insert(hash.clone()); - self.service.report_peer(who.clone(), rep::ANY_TRANSACTION); + self.service.report_peer(who, rep::ANY_TRANSACTION); match self.pending_transactions_peers.entry(hash.clone()) { Entry::Vacant(entry) => { @@ -409,10 +392,10 @@ impl TransactionsHandler { validation: self.transaction_pool.import(t), tx_hash: hash, }); - entry.insert(vec![who.clone()]); + entry.insert(vec![who]); }, Entry::Occupied(mut entry) => { - entry.get_mut().push(who.clone()); + entry.get_mut().push(who); }, } } @@ -431,11 +414,12 @@ impl TransactionsHandler { /// Propagate one transaction. pub fn propagate_transaction(&mut self, hash: &H) { - debug!(target: "sync", "Propagating transaction [{:?}]", hash); - // Accept transactions only when enabled - if !self.gossip_enabled.load(Ordering::Relaxed) { + // Accept transactions only when node is not major syncing + if self.service.is_major_syncing() { return } + + debug!(target: "sync", "Propagating transaction [{:?}]", hash); if let Some(transaction) = self.transaction_pool.transaction(hash) { let propagated_to = self.do_propagate_transactions(&[(hash.clone(), transaction)]); self.transaction_pool.on_broadcasted(propagated_to); @@ -468,11 +452,8 @@ impl TransactionsHandler { propagated_to.entry(hash).or_default().push(who.to_base58()); } trace!(target: "sync", "Sending {} transactions to {}", to_send.len(), who); - self.service.write_notification( - who.clone(), - self.protocol_name.clone(), - to_send.encode(), - ); + self.service + .write_notification(*who, self.protocol_name.clone(), to_send.encode()); } } @@ -485,10 +466,11 @@ impl TransactionsHandler { /// Call when we must propagate ready transactions to peers. fn propagate_transactions(&mut self) { - // Accept transactions only when enabled - if !self.gossip_enabled.load(Ordering::Relaxed) { + // Accept transactions only when node is not major syncing + if self.service.is_major_syncing() { return } + debug!(target: "sync", "Propagating transactions"); let transactions = self.transaction_pool.transactions(); let propagated_to = self.do_propagate_transactions(&transactions); diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index 641a1e55063d0..6406d7dc475a8 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -4,8 +4,8 @@ name = "sc-offchain" version = "4.0.0-dev" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" authors = ["Parity Technologies "] -edition = "2018" -homepage = "https://substrate.dev" +edition = "2021" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,37 +13,40 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -bytes = "1.0" -codec = { package = "parity-scale-codec", version = "2.0.0", features = ["derive"] } -hex = "0.4" +array-bytes = "4.1" +bytes = "1.1" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } fnv = "1.0.6" -futures = "0.3.9" -futures-timer = "3.0.1" -log = "0.4.8" -num_cpus = "1.10" -parking_lot = "0.11.1" +futures = "0.3.21" +futures-timer = "3.0.2" +hyper = { version = "0.14.16", features = ["stream", "http2"] } +hyper-rustls = { version = "0.23.0", features = ["http2"] } +libp2p = { version = "0.49.0", default-features = false } +num_cpus = "1.13" +once_cell = "1.8" +parking_lot = "0.12.1" rand = "0.7.2" +threadpool = "1.7" +tracing = "0.1.29" sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-network = { version = "0.10.0-dev", path = "../network" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } +sc-peerset = { version = "4.0.0-dev", path = "../peerset" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-offchain = { version = "4.0.0-dev", path = "../../primitives/offchain" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -threadpool = "1.7" -hyper = "0.14.11" -hyper-rustls = "0.22.1" +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] -sc-client-db = { version = "0.10.0-dev", default-features = true, path = "../db" } +lazy_static = "1.4.0" +tokio = "1.22.0" sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sc-client-db = { version = "0.10.0-dev", default-features = true, path = "../db" } sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -tokio = "1.10" -lazy_static = "1.4.0" [features] default = [] diff --git a/client/offchain/src/api.rs b/client/offchain/src/api.rs index b2276a852372f..1301ce9fd9627 100644 --- a/client/offchain/src/api.rs +++ b/client/offchain/src/api.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,13 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{collections::HashSet, convert::TryFrom, str::FromStr, sync::Arc, thread::sleep}; +use std::{collections::HashSet, str::FromStr, sync::Arc, thread::sleep}; use crate::NetworkProvider; use codec::{Decode, Encode}; use futures::Future; pub use http::SharedClient; -use sc_network::{Multiaddr, PeerId}; +use libp2p::{Multiaddr, PeerId}; use sp_core::{ offchain::{ self, HttpError, HttpRequestId, HttpRequestStatus, OffchainStorage, OpaqueMultiaddr, @@ -37,10 +37,11 @@ mod http; mod timestamp; fn unavailable_yet(name: &str) -> R { - log::error!( - target: "sc_offchain", + tracing::error!( + target: super::LOG_TARGET, "The {:?} API is not available for offchain workers yet. Follow \ - https://github.com/paritytech/substrate/issues/1458 for details", name + https://github.com/paritytech/substrate/issues/1458 for details", + name ); Default::default() } @@ -75,9 +76,12 @@ impl Db { impl offchain::DbExternalities for Db { fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) { - log::debug!( - target: "sc_offchain", - "{:?}: Write: {:?} <= {:?}", kind, hex::encode(key), hex::encode(value) + tracing::debug!( + target: "offchain-worker::storage", + ?kind, + key = ?array_bytes::bytes2hex("", key), + value = ?array_bytes::bytes2hex("", value), + "Write", ); match kind { StorageKind::PERSISTENT => self.persistent.set(STORAGE_PREFIX, key, value), @@ -86,9 +90,11 @@ impl offchain::DbExternalities for Db { } fn local_storage_clear(&mut self, kind: StorageKind, key: &[u8]) { - log::debug!( - target: "sc_offchain", - "{:?}: Clear: {:?}", kind, hex::encode(key) + tracing::debug!( + target: "offchain-worker::storage", + ?kind, + key = ?array_bytes::bytes2hex("", key), + "Clear", ); match kind { StorageKind::PERSISTENT => self.persistent.remove(STORAGE_PREFIX, key), @@ -103,13 +109,13 @@ impl offchain::DbExternalities for Db { old_value: Option<&[u8]>, new_value: &[u8], ) -> bool { - log::debug!( - target: "sc_offchain", - "{:?}: CAS: {:?} <= {:?} vs {:?}", - kind, - hex::encode(key), - hex::encode(new_value), - old_value.as_ref().map(hex::encode), + tracing::debug!( + target: "offchain-worker::storage", + ?kind, + key = ?array_bytes::bytes2hex("", key), + new_value = ?array_bytes::bytes2hex("", new_value), + old_value = ?old_value.as_ref().map(|s| array_bytes::bytes2hex("", s)), + "CAS", ); match kind { StorageKind::PERSISTENT => @@ -123,12 +129,12 @@ impl offchain::DbExternalities for Db { StorageKind::PERSISTENT => self.persistent.get(STORAGE_PREFIX, key), StorageKind::LOCAL => unavailable_yet(LOCAL_DB), }; - log::debug!( - target: "sc_offchain", - "{:?}: Read: {:?} => {:?}", - kind, - hex::encode(key), - result.as_ref().map(hex::encode) + tracing::debug!( + target: "offchain-worker::storage", + ?kind, + key = ?array_bytes::bytes2hex("", key), + result = ?result.as_ref().map(|s| array_bytes::bytes2hex("", s)), + "Read", ); result } @@ -294,13 +300,13 @@ pub(crate) struct AsyncApi { } impl AsyncApi { - /// Creates new Offchain extensions API implementation an the asynchronous processing part. + /// Creates new Offchain extensions API implementation and the asynchronous processing part. pub fn new( network_provider: Arc, is_validator: bool, - shared_client: SharedClient, + shared_http_client: SharedClient, ) -> (Api, Self) { - let (http_api, http_worker) = http::http(shared_client); + let (http_api, http_worker) = http::http(shared_http_client); let api = Api { network_provider, is_validator, http: http_api }; @@ -310,33 +316,98 @@ impl AsyncApi { } /// Run a processing task for the API - pub fn process(mut self) -> impl Future { - let http = self.http.take().expect("Take invoked only once."); - - http + pub fn process(self) -> impl Future { + self.http.expect("`process` is only called once; qed") } } #[cfg(test)] mod tests { use super::*; + use libp2p::PeerId; use sc_client_db::offchain::LocalStorage; - use sc_network::{NetworkStateInfo, PeerId}; - use sp_core::offchain::{DbExternalities, Externalities}; - use std::{ - convert::{TryFrom, TryInto}, - time::SystemTime, + use sc_network_common::{ + config::MultiaddrWithPeerId, + protocol::ProtocolName, + service::{NetworkPeers, NetworkStateInfo}, }; + use sc_peerset::ReputationChange; + use sp_core::offchain::{DbExternalities, Externalities}; + use std::time::SystemTime; - struct TestNetwork(); + pub(super) struct TestNetwork(); - impl NetworkProvider for TestNetwork { + impl NetworkPeers for TestNetwork { fn set_authorized_peers(&self, _peers: HashSet) { - unimplemented!() + unimplemented!(); } fn set_authorized_only(&self, _reserved_only: bool) { - unimplemented!() + unimplemented!(); + } + + fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) { + unimplemented!(); + } + + fn report_peer(&self, _who: PeerId, _cost_benefit: ReputationChange) { + unimplemented!(); + } + + fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) { + unimplemented!(); + } + + fn accept_unreserved_peers(&self) { + unimplemented!(); + } + + fn deny_unreserved_peers(&self) { + unimplemented!(); + } + + fn add_reserved_peer(&self, _peer: MultiaddrWithPeerId) -> Result<(), String> { + unimplemented!(); + } + + fn remove_reserved_peer(&self, _peer_id: PeerId) { + unimplemented!(); + } + + fn set_reserved_peers( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn add_peers_to_reserved_set( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_peers_from_reserved_set(&self, _protocol: ProtocolName, _peers: Vec) { + unimplemented!(); + } + + fn add_to_peers_set( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_from_peers_set(&self, _protocol: ProtocolName, _peers: Vec) { + unimplemented!(); + } + + fn sync_num_connected(&self) -> usize { + unimplemented!(); } } diff --git a/client/offchain/src/api/http.rs b/client/offchain/src/api/http.rs index ce9fb298d1b0c..4c97e5a47058d 100644 --- a/client/offchain/src/api/http.rs +++ b/client/offchain/src/api/http.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -32,12 +32,11 @@ use bytes::buf::{Buf, Reader}; use fnv::FnvHashMap; use futures::{channel::mpsc, future, prelude::*}; use hyper::{client, Body, Client as HyperClient}; -use hyper_rustls::HttpsConnector; -use log::error; +use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; +use once_cell::sync::Lazy; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_core::offchain::{HttpError, HttpRequestId, HttpRequestStatus, Timestamp}; use std::{ - convert::TryFrom, fmt, io::Read as _, pin::Pin, @@ -45,13 +44,23 @@ use std::{ task::{Context, Poll}, }; +const LOG_TARGET: &str = "offchain-worker::http"; + /// Wrapper struct used for keeping the hyper_rustls client running. #[derive(Clone)] -pub struct SharedClient(Arc, Body>>); +pub struct SharedClient(Arc, Body>>>); impl SharedClient { pub fn new() -> Self { - Self(Arc::new(HyperClient::builder().build(HttpsConnector::with_native_roots()))) + Self(Arc::new(Lazy::new(|| { + let connector = HttpsConnectorBuilder::new() + .with_native_roots() + .https_or_http() + .enable_http1() + .enable_http2() + .build(); + HyperClient::builder().build(connector) + }))) } } @@ -143,13 +152,24 @@ impl HttpApi { match self.next_id.0.checked_add(1) { Some(new_id) => self.next_id.0 = new_id, None => { - error!("Overflow in offchain worker HTTP request ID assignment"); + tracing::error!( + target: LOG_TARGET, + "Overflow in offchain worker HTTP request ID assignment" + ); return Err(()) }, }; self.requests .insert(new_id, HttpApiRequest::NotDispatched(request, body_sender)); + tracing::error!( + target: LOG_TARGET, + id = %new_id.0, + %method, + %uri, + "Requested started", + ); + Ok(new_id) } @@ -165,11 +185,14 @@ impl HttpApi { _ => return Err(()), }; - let name = hyper::header::HeaderName::try_from(name).map_err(drop)?; - let value = hyper::header::HeaderValue::try_from(value).map_err(drop)?; + let header_name = hyper::header::HeaderName::try_from(name).map_err(drop)?; + let header_value = hyper::header::HeaderValue::try_from(value).map_err(drop)?; // Note that we're always appending headers and never replacing old values. // We assume here that the user knows what they're doing. - request.headers_mut().append(name, value); + request.headers_mut().append(header_name, header_value); + + tracing::debug!(target: LOG_TARGET, id = %request_id.0, %name, %value, "Added header to request"); + Ok(()) } @@ -182,7 +205,7 @@ impl HttpApi { ) -> Result<(), HttpError> { // Extract the request from the list. // Don't forget to add it back if necessary when returning. - let mut request = self.requests.remove(&request_id).ok_or_else(|| HttpError::Invalid)?; + let mut request = self.requests.remove(&request_id).ok_or(HttpError::Invalid)?; let mut deadline = timestamp::deadline_to_future(deadline); // Closure that writes data to a sender, taking the deadline into account. Can return `Ok` @@ -204,7 +227,7 @@ impl HttpApi { sender.send_data(hyper::body::Bytes::from(chunk.to_owned())), ) .map_err(|_| { - error!("HTTP sender refused data despite being ready"); + tracing::error!(target: "offchain-worker::http", "HTTP sender refused data despite being ready"); HttpError::IoError }) }; @@ -212,6 +235,7 @@ impl HttpApi { loop { request = match request { HttpApiRequest::NotDispatched(request, sender) => { + tracing::debug!(target: LOG_TARGET, id = %request_id.0, "Added new body chunk"); // If the request is not dispatched yet, dispatch it and loop again. let _ = self .to_worker @@ -222,14 +246,20 @@ impl HttpApi { HttpApiRequest::Dispatched(Some(mut sender)) => { if !chunk.is_empty() { match poll_sender(&mut sender) { - Err(HttpError::IoError) => return Err(HttpError::IoError), + Err(HttpError::IoError) => { + tracing::debug!(target: LOG_TARGET, id = %request_id.0, "Encountered io error while trying to add new chunk to body"); + return Err(HttpError::IoError) + }, other => { + tracing::debug!(target: LOG_TARGET, id = %request_id.0, res = ?other, "Added chunk to body"); self.requests .insert(request_id, HttpApiRequest::Dispatched(Some(sender))); return other }, } } else { + tracing::debug!(target: LOG_TARGET, id = %request_id.0, "Finished writing body"); + // Writing an empty body is a hint that we should stop writing. Dropping // the sender. self.requests.insert(request_id, HttpApiRequest::Dispatched(None)); @@ -247,14 +277,20 @@ impl HttpApi { .as_mut() .expect("Can only enter this match branch if Some; qed"), ) { - Err(HttpError::IoError) => return Err(HttpError::IoError), + Err(HttpError::IoError) => { + tracing::debug!(target: LOG_TARGET, id = %request_id.0, "Encountered io error while trying to add new chunk to body"); + return Err(HttpError::IoError) + }, other => { + tracing::debug!(target: LOG_TARGET, id = %request_id.0, res = ?other, "Added chunk to body"); self.requests .insert(request_id, HttpApiRequest::Response(response)); return other }, } } else { + tracing::debug!(target: LOG_TARGET, id = %request_id.0, "Finished writing body"); + // Writing an empty body is a hint that we should stop writing. Dropping // the sender. self.requests.insert( @@ -268,13 +304,18 @@ impl HttpApi { } }, - HttpApiRequest::Fail(_) => - // If the request has already failed, return without putting back the request - // in the list. - return Err(HttpError::IoError), + HttpApiRequest::Fail(error) => { + tracing::debug!(target: LOG_TARGET, id = %request_id.0, ?error, "Request failed"); + + // If the request has already failed, return without putting back the request + // in the list. + return Err(HttpError::IoError) + }, v @ HttpApiRequest::Dispatched(None) | v @ HttpApiRequest::Response(HttpApiRequestRp { sending_body: None, .. }) => { + tracing::debug!(target: LOG_TARGET, id = %request_id.0, "Body sending already finished"); + // We have already finished sending this body. self.requests.insert(request_id, v); return Err(HttpError::Invalid) @@ -347,8 +388,19 @@ impl HttpApi { // Requests in "fail" mode are purged before returning. debug_assert_eq!(output.len(), ids.len()); for n in (0..ids.len()).rev() { - if let HttpRequestStatus::IoError = output[n] { - self.requests.remove(&ids[n]); + match output[n] { + HttpRequestStatus::IoError => { + self.requests.remove(&ids[n]); + }, + HttpRequestStatus::Invalid => { + tracing::debug!(target: LOG_TARGET, id = %ids[n].0, "Unknown request"); + }, + HttpRequestStatus::DeadlineReached => { + tracing::debug!(target: LOG_TARGET, id = %ids[n].0, "Deadline reached"); + }, + HttpRequestStatus::Finished(_) => { + tracing::debug!(target: LOG_TARGET, id = %ids[n].0, "Request finished"); + }, } } return output @@ -385,20 +437,25 @@ impl HttpApi { ); }, None => {}, // can happen if we detected an IO error when sending the body - _ => error!("State mismatch between the API and worker"), + _ => { + tracing::error!(target: "offchain-worker::http", "State mismatch between the API and worker") + }, } }, Some(WorkerToApi::Fail { id, error }) => match self.requests.remove(&id) { Some(HttpApiRequest::Dispatched(_)) => { + tracing::debug!(target: LOG_TARGET, id = %id.0, ?error, "Request failed"); self.requests.insert(id, HttpApiRequest::Fail(error)); }, None => {}, // can happen if we detected an IO error when sending the body - _ => error!("State mismatch between the API and worker"), + _ => { + tracing::error!(target: "offchain-worker::http", "State mismatch between the API and worker") + }, }, None => { - error!("Worker has crashed"); + tracing::error!(target: "offchain-worker::http", "Worker has crashed"); return ids.iter().map(|_| HttpRequestStatus::IoError).collect() }, } @@ -471,7 +528,7 @@ impl HttpApi { }, Err(err) => { // This code should never be reached unless there's a logic error somewhere. - error!("Failed to read from current read chunk: {:?}", err); + tracing::error!(target: "offchain-worker::http", "Failed to read from current read chunk: {:?}", err); return Err(HttpError::IoError) }, } @@ -567,7 +624,7 @@ pub struct HttpWorker { /// Used to receive messages from the `HttpApi`. from_api: TracingUnboundedReceiver, /// The engine that runs HTTP requests. - http_client: Arc, Body>>, + http_client: Arc, Body>>>, /// HTTP requests that are being worked on by the engine. requests: Vec<(HttpRequestId, HttpWorkerRequest)>, } @@ -697,12 +754,15 @@ impl fmt::Debug for HttpWorkerRequest { #[cfg(test)] mod tests { - use super::{http, SharedClient}; + use super::{ + super::{tests::TestNetwork, AsyncApi}, + *, + }; use crate::api::timestamp; use core::convert::Infallible; - use futures::future; + use futures::{future, StreamExt}; use lazy_static::lazy_static; - use sp_core::offchain::{Duration, HttpError, HttpRequestId, HttpRequestStatus}; + use sp_core::offchain::{Duration, Externalities, HttpError, HttpRequestId, HttpRequestStatus}; // Using lazy_static to avoid spawning lots of different SharedClients, // as spawning a SharedClient is CPU-intensive and opens lots of fds. @@ -713,7 +773,10 @@ mod tests { // Returns an `HttpApi` whose worker is ran in the background, and a `SocketAddr` to an HTTP // server that runs in the background as well. macro_rules! build_api_server { - () => {{ + () => { + build_api_server!(hyper::Response::new(hyper::Body::from("Hello World!"))) + }; + ( $response:expr ) => {{ let hyper_client = SHARED_CLIENT.clone(); let (api, worker) = http(hyper_client.clone()); @@ -725,10 +788,12 @@ mod tests { let server = hyper::Server::bind(&"127.0.0.1:0".parse().unwrap()).serve( hyper::service::make_service_fn(|_| async move { Ok::<_, Infallible>(hyper::service::service_fn( - move |_req| async move { - Ok::<_, Infallible>(hyper::Response::new(hyper::Body::from( - "Hello World!", - ))) + move |req: hyper::Request| async move { + // Wait until the complete request was received and processed, + // otherwise the tests are flaky. + let _ = req.into_body().collect::>().await; + + Ok::<_, Infallible>($response) }, )) }), @@ -766,6 +831,33 @@ mod tests { assert_eq!(&buf[..n], b"Hello World!"); } + #[test] + fn basic_http2_localhost() { + let deadline = timestamp::now().add(Duration::from_millis(10_000)); + + // Performs an HTTP query to a background HTTP server. + + let (mut api, addr) = build_api_server!(hyper::Response::builder() + .version(hyper::Version::HTTP_2) + .body(hyper::Body::from("Hello World!")) + .unwrap()); + + let id = api.request_start("POST", &format!("http://{}", addr)).unwrap(); + api.request_write_body(id, &[], Some(deadline)).unwrap(); + + match api.response_wait(&[id], Some(deadline))[0] { + HttpRequestStatus::Finished(200) => {}, + v => panic!("Connecting to localhost failed: {:?}", v), + } + + let headers = api.response_headers(id); + assert!(headers.iter().any(|(h, _)| h.eq_ignore_ascii_case(b"Date"))); + + let mut buf = vec![0; 2048]; + let n = api.response_read_body(id, &mut buf, Some(deadline)).unwrap(); + assert_eq!(&buf[..n], b"Hello World!"); + } + #[test] fn request_start_invalid_call() { let (mut api, addr) = build_api_server!(); @@ -1002,4 +1094,37 @@ mod tests { } } } + + #[test] + fn shared_http_client_is_only_initialized_on_access() { + let shared_client = SharedClient::new(); + + { + let mock = Arc::new(TestNetwork()); + let (mut api, async_api) = AsyncApi::new(mock, false, shared_client.clone()); + api.timestamp(); + + futures::executor::block_on(async move { + assert!(futures::poll!(async_api.process()).is_pending()); + }); + } + + // Check that the http client wasn't initialized, because it wasn't used. + assert!(Lazy::into_value(Arc::try_unwrap(shared_client.0).unwrap()).is_err()); + + let shared_client = SharedClient::new(); + + { + let mock = Arc::new(TestNetwork()); + let (mut api, async_api) = AsyncApi::new(mock, false, shared_client.clone()); + let id = api.http_request_start("lol", "nope", &[]).unwrap(); + api.http_request_write_body(id, &[], None).unwrap(); + futures::executor::block_on(async move { + assert!(futures::poll!(async_api.process()).is_pending()); + }); + } + + // Check that the http client initialized, because it was used. + assert!(Lazy::into_value(Arc::try_unwrap(shared_client.0).unwrap()).is_ok()); + } } diff --git a/client/offchain/src/api/timestamp.rs b/client/offchain/src/api/timestamp.rs index f1c8c004a0198..4b3f5efddf275 100644 --- a/client/offchain/src/api/timestamp.rs +++ b/client/offchain/src/api/timestamp.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,10 +19,7 @@ //! Helper methods dedicated to timestamps. use sp_core::offchain::Timestamp; -use std::{ - convert::TryInto, - time::{Duration, SystemTime}, -}; +use std::time::{Duration, SystemTime}; /// Returns the current time as a `Timestamp`. pub fn now() -> Timestamp { diff --git a/client/offchain/src/lib.rs b/client/offchain/src/lib.rs index be6e4238ca5f1..87e79833b9706 100644 --- a/client/offchain/src/lib.rs +++ b/client/offchain/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -35,15 +35,14 @@ #![warn(missing_docs)] -use std::{collections::HashSet, fmt, marker::PhantomData, sync::Arc}; +use std::{fmt, marker::PhantomData, sync::Arc}; use futures::{ future::{ready, Future}, prelude::*, }; -use log::{debug, warn}; use parking_lot::Mutex; -use sc_network::{ExHashT, NetworkService, NetworkStateInfo, PeerId}; +use sc_network_common::service::{NetworkPeers, NetworkStateInfo}; use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_core::{offchain, traits::SpawnNamed, ExecutionContext}; use sp_runtime::{ @@ -57,28 +56,20 @@ mod api; pub use api::Db as OffchainDb; pub use sp_offchain::{OffchainWorkerApi, STORAGE_PREFIX}; +const LOG_TARGET: &str = "offchain-worker"; + /// NetworkProvider provides [`OffchainWorkers`] with all necessary hooks into the /// underlying Substrate networking. -pub trait NetworkProvider: NetworkStateInfo { - /// Set the authorized peers. - fn set_authorized_peers(&self, peers: HashSet); - - /// Set the authorized only flag. - fn set_authorized_only(&self, reserved_only: bool); -} +pub trait NetworkProvider: NetworkStateInfo + NetworkPeers {} -impl NetworkProvider for NetworkService -where - B: traits::Block + 'static, - H: ExHashT, -{ - fn set_authorized_peers(&self, peers: HashSet) { - self.set_authorized_peers(peers) - } +impl NetworkProvider for T where T: NetworkStateInfo + NetworkPeers {} - fn set_authorized_only(&self, reserved_only: bool) { - self.set_authorized_only(reserved_only) - } +/// Options for [`OffchainWorkers`] +pub struct OffchainWorkerOptions { + /// Enable http requests from offchain workers? + /// + /// If not enabled, any http request will panic. + pub enable_http_requests: bool, } /// An offchain workers manager. @@ -86,13 +77,18 @@ pub struct OffchainWorkers { client: Arc, _block: PhantomData, thread_pool: Mutex, - shared_client: api::SharedClient, + shared_http_client: api::SharedClient, + enable_http: bool, } impl OffchainWorkers { - /// Creates new `OffchainWorkers`. + /// Creates new [`OffchainWorkers`]. pub fn new(client: Arc) -> Self { - let shared_client = api::SharedClient::new(); + Self::new_with_options(client, OffchainWorkerOptions { enable_http_requests: true }) + } + + /// Creates new [`OffchainWorkers`] using the given `options`. + pub fn new_with_options(client: Arc, options: OffchainWorkerOptions) -> Self { Self { client, _block: PhantomData, @@ -100,7 +96,8 @@ impl OffchainWorkers { "offchain-worker".into(), num_cpus::get(), )), - shared_client, + shared_http_client: api::SharedClient::new(), + enable_http: options.enable_http_requests, } } } @@ -135,23 +132,37 @@ where err => { let help = "Consider turning off offchain workers if they are not part of your runtime."; - log::error!("Unsupported Offchain Worker API version: {:?}. {}.", err, help); + tracing::error!( + target: LOG_TARGET, + "Unsupported Offchain Worker API version: {:?}. {}.", + err, + help + ); 0 }, }; - debug!("Checking offchain workers at {:?}: version:{}", at, version); - if version > 0 { + tracing::debug!( + target: LOG_TARGET, + "Checking offchain workers at {:?}: version:{}", + at, + version + ); + let process = (version > 0).then(|| { let (api, runner) = - api::AsyncApi::new(network_provider, is_validator, self.shared_client.clone()); - debug!("Spawning offchain workers at {:?}", at); + api::AsyncApi::new(network_provider, is_validator, self.shared_http_client.clone()); + tracing::debug!(target: LOG_TARGET, "Spawning offchain workers at {:?}", at); let header = header.clone(); let client = self.client.clone(); + + let mut capabilities = offchain::Capabilities::all(); + + capabilities.set(offchain::Capabilities::HTTP, self.enable_http); self.spawn_worker(move || { let runtime = client.runtime_api(); let api = Box::new(api); - debug!("Running offchain workers at {:?}", at); - let context = - ExecutionContext::OffchainCall(Some((api, offchain::Capabilities::all()))); + tracing::debug!(target: LOG_TARGET, "Running offchain workers at {:?}", at); + + let context = ExecutionContext::OffchainCall(Some((api, capabilities))); let run = if version == 2 { runtime.offchain_worker_with_context(&at, context, &header) } else { @@ -163,12 +174,20 @@ where ) }; if let Err(e) = run { - log::error!("Error running offchain workers at {:?}: {:?}", at, e); + tracing::error!( + target: LOG_TARGET, + "Error running offchain workers at {:?}: {}", + at, + e + ); } }); - futures::future::Either::Left(runner.process()) - } else { - futures::future::Either::Right(futures::future::ready(())) + + runner.process() + }); + + async move { + futures::future::OptionFuture::from(process).await; } } @@ -205,13 +224,14 @@ pub async fn notification_future( if n.is_new_best { spawner.spawn( "offchain-on-block", + Some("offchain-worker"), offchain .on_block_imported(&n.header, network_provider.clone(), is_validator) .boxed(), ); } else { - log::debug!( - target: "sc_offchain", + tracing::debug!( + target: LOG_TARGET, "Skipping offchain workers for non-canon block: {:?}", n.header, ) @@ -226,13 +246,15 @@ pub async fn notification_future( mod tests { use super::*; use futures::executor::block_on; + use libp2p::{Multiaddr, PeerId}; use sc_block_builder::BlockBuilderProvider as _; use sc_client_api::Backend as _; - use sc_network::{Multiaddr, PeerId}; + use sc_network_common::{config::MultiaddrWithPeerId, protocol::ProtocolName}; + use sc_peerset::ReputationChange; use sc_transaction_pool::{BasicPool, FullChainApi}; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; use sp_consensus::BlockOrigin; - use std::sync::Arc; + use std::{collections::HashSet, sync::Arc}; use substrate_test_runtime_client::{ runtime::Block, ClientBlockImportExt, DefaultTestClientBuilderExt, TestClient, TestClientBuilderExt, @@ -250,13 +272,77 @@ mod tests { } } - impl NetworkProvider for TestNetwork { + impl NetworkPeers for TestNetwork { fn set_authorized_peers(&self, _peers: HashSet) { - unimplemented!() + unimplemented!(); } fn set_authorized_only(&self, _reserved_only: bool) { - unimplemented!() + unimplemented!(); + } + + fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) { + unimplemented!(); + } + + fn report_peer(&self, _who: PeerId, _cost_benefit: ReputationChange) { + unimplemented!(); + } + + fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) { + unimplemented!(); + } + + fn accept_unreserved_peers(&self) { + unimplemented!(); + } + + fn deny_unreserved_peers(&self) { + unimplemented!(); + } + + fn add_reserved_peer(&self, _peer: MultiaddrWithPeerId) -> Result<(), String> { + unimplemented!(); + } + + fn remove_reserved_peer(&self, _peer_id: PeerId) { + unimplemented!(); + } + + fn set_reserved_peers( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn add_peers_to_reserved_set( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_peers_from_reserved_set(&self, _protocol: ProtocolName, _peers: Vec) { + unimplemented!(); + } + + fn add_to_peers_set( + &self, + _protocol: ProtocolName, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_from_peers_set(&self, _protocol: ProtocolName, _peers: Vec) { + unimplemented!(); + } + + fn sync_num_connected(&self) -> usize { + unimplemented!(); } } diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index 5962620d6e06e..ade2bc3d78d03 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -1,11 +1,11 @@ [package] description = "Connectivity manager based on reputation" -homepage = "http://parity.io" +homepage = "https://substrate.io" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" name = "sc-peerset" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-peerset" readme = "README.md" @@ -13,14 +13,13 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -futures = "0.3.9" -libp2p = { version = "0.39.1", default-features = false } -sc-utils = { version = "4.0.0-dev", path = "../utils"} -log = "0.4.8" -serde_json = "1.0.68" +futures = "0.3.21" +libp2p = { version = "0.49.0", default-features = false } +log = "0.4.17" +serde_json = "1.0.85" wasm-timer = "0.2" +sc-utils = { version = "4.0.0-dev", path = "../utils" } [dev-dependencies] rand = "0.7.2" diff --git a/client/peerset/src/lib.rs b/client/peerset/src/lib.rs index 9c6c5617c34b1..ec09835c4898e 100644 --- a/client/peerset/src/lib.rs +++ b/client/peerset/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -34,7 +34,7 @@ mod peersstate; -use futures::prelude::*; +use futures::{channel::oneshot, prelude::*}; use log::{debug, error, trace}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use serde_json::json; @@ -49,7 +49,7 @@ use wasm_timer::Delay; pub use libp2p::PeerId; /// We don't accept nodes whose reputation is under this value. -const BANNED_THRESHOLD: i32 = 82 * (i32::MIN / 100); +pub const BANNED_THRESHOLD: i32 = 82 * (i32::MIN / 100); /// Reputation change for a node when we get disconnected from it. const DISCONNECT_REPUTATION_CHANGE: i32 = -256; /// Amount of time between the moment we disconnect from a node and the moment we remove it from @@ -65,6 +65,7 @@ enum Action { ReportPeer(PeerId, ReputationChange), AddToPeersSet(SetId, PeerId), RemoveFromPeersSet(SetId, PeerId), + PeerReputation(PeerId, oneshot::Sender), } /// Identifier of a set in the peerset. @@ -78,13 +79,13 @@ pub struct SetId(usize); impl SetId { pub const fn from(id: usize) -> Self { - SetId(id) + Self(id) } } impl From for SetId { fn from(id: usize) -> Self { - SetId(id) + Self(id) } } @@ -106,12 +107,12 @@ pub struct ReputationChange { impl ReputationChange { /// New reputation change with given delta and reason. pub const fn new(value: i32, reason: &'static str) -> ReputationChange { - ReputationChange { value, reason } + Self { value, reason } } /// New reputation change that forces minimum possible reputation. pub const fn new_fatal(reason: &'static str) -> ReputationChange { - ReputationChange { value: i32::MIN, reason } + Self { value: i32::MIN, reason } } } @@ -128,7 +129,7 @@ impl PeersetHandle { /// Has no effect if the node was already a reserved peer. /// /// > **Note**: Keep in mind that the networking has to know an address for this node, - /// > otherwise it will not be able to connect to it. + /// > otherwise it will not be able to connect to it. pub fn add_reserved_peer(&self, set_id: SetId, peer_id: PeerId) { let _ = self.tx.unbounded_send(Action::AddReservedPeer(set_id, peer_id)); } @@ -165,6 +166,16 @@ impl PeersetHandle { pub fn remove_from_peers_set(&self, set_id: SetId, peer_id: PeerId) { let _ = self.tx.unbounded_send(Action::RemoveFromPeersSet(set_id, peer_id)); } + + /// Returns the reputation value of the peer. + pub async fn peer_reputation(self, peer_id: PeerId) -> Result { + let (tx, rx) = oneshot::channel(); + + let _ = self.tx.unbounded_send(Action::PeerReputation(peer_id, tx)); + + // The channel can only be closed if the peerset no longer exists. + rx.await.map_err(|_| ()) + } } /// Message that can be sent by the peer set manager (PSM). @@ -197,8 +208,8 @@ pub enum Message { pub struct IncomingIndex(pub u64); impl From for IncomingIndex { - fn from(val: u64) -> IncomingIndex { - IncomingIndex(val) + fn from(val: u64) -> Self { + Self(val) } } @@ -221,7 +232,7 @@ pub struct SetConfig { /// List of bootstrap nodes to initialize the set with. /// /// > **Note**: Keep in mind that the networking has to know an address for these nodes, - /// > otherwise it will not be able to connect to them. + /// > otherwise it will not be able to connect to them. pub bootnodes: Vec, /// Lists of nodes we should always be connected to. @@ -263,7 +274,7 @@ pub struct Peerset { impl Peerset { /// Builds a new peerset from the given configuration. - pub fn from_config(config: PeersetConfig) -> (Peerset, PeersetHandle) { + pub fn from_config(config: PeersetConfig) -> (Self, PeersetHandle) { let (tx, rx) = tracing_unbounded("mpsc_peerset_messages"); let handle = PeersetHandle { tx: tx.clone() }; @@ -271,7 +282,7 @@ impl Peerset { let mut peerset = { let now = Instant::now(); - Peerset { + Self { data: peersstate::PeersState::new(config.sets.iter().map(|set| { peersstate::SetConfig { in_peers: set.in_peers, out_peers: set.out_peers } })), @@ -311,7 +322,7 @@ impl Peerset { } fn on_add_reserved_peer(&mut self, set_id: SetId, peer_id: PeerId) { - let newly_inserted = self.reserved_nodes[set_id.0].0.insert(peer_id.clone()); + let newly_inserted = self.reserved_nodes[set_id.0].0.insert(peer_id); if !newly_inserted { return } @@ -411,8 +422,7 @@ impl Peerset { match self.data.peer(set_id.0, &peer_id) { peersstate::Peer::Connected(peer) => { - self.message_queue - .push_back(Message::Drop { set_id, peer_id: peer.peer_id().clone() }); + self.message_queue.push_back(Message::Drop { set_id, peer_id: *peer.peer_id() }); peer.disconnect().forget_peer(); }, peersstate::Peer::NotConnected(peer) => { @@ -454,6 +464,11 @@ impl Peerset { } } + fn on_peer_reputation(&mut self, peer_id: PeerId, pending_response: oneshot::Sender) { + let reputation = self.data.peer_reputation(peer_id); + let _ = pending_response.send(reputation.reputation()); + } + /// Updates the value of `self.latest_time_update` and performs all the updates that happen /// over time, such as reputation increases for staying connected. fn update_time(&mut self) { @@ -604,11 +619,9 @@ impl Peerset { self.update_time(); - if self.reserved_nodes[set_id.0].1 { - if !self.reserved_nodes[set_id.0].0.contains(&peer_id) { - self.message_queue.push_back(Message::Reject(index)); - return - } + if self.reserved_nodes[set_id.0].1 && !self.reserved_nodes[set_id.0].0.contains(&peer_id) { + self.message_queue.push_back(Message::Reject(index)); + return } let not_connected = match self.data.peer(set_id.0, &peer_id) { @@ -715,8 +728,7 @@ impl Stream for Peerset { return Poll::Ready(Some(message)) } - if let Poll::Ready(_) = Future::poll(Pin::new(&mut self.next_periodic_alloc_slots), cx) - { + if Future::poll(Pin::new(&mut self.next_periodic_alloc_slots), cx).is_ready() { self.next_periodic_alloc_slots = Delay::new(Duration::new(1, 0)); for set_index in 0..self.data.num_sets() { @@ -744,6 +756,8 @@ impl Stream for Peerset { self.add_to_peers_set(sets_name, peer_id), Action::RemoveFromPeersSet(sets_name, peer_id) => self.on_remove_from_peers_set(sets_name, peer_id), + Action::PeerReputation(peer_id, pending_response) => + self.on_peer_reputation(peer_id, pending_response), } } } @@ -781,7 +795,7 @@ mod tests { fn next_message(mut peerset: Peerset) -> Result<(Message, Peerset), ()> { let next = futures::executor::block_on_stream(&mut peerset).next(); - let message = next.ok_or_else(|| ())?; + let message = next.ok_or(())?; Ok((message, peerset)) } @@ -801,8 +815,8 @@ mod tests { }; let (peerset, handle) = Peerset::from_config(config); - handle.add_reserved_peer(SetId::from(0), reserved_peer.clone()); - handle.add_reserved_peer(SetId::from(0), reserved_peer2.clone()); + handle.add_reserved_peer(SetId::from(0), reserved_peer); + handle.add_reserved_peer(SetId::from(0), reserved_peer2); assert_messages( peerset, @@ -827,22 +841,22 @@ mod tests { sets: vec![SetConfig { in_peers: 2, out_peers: 1, - bootnodes: vec![bootnode.clone()], + bootnodes: vec![bootnode], reserved_nodes: Default::default(), reserved_only: false, }], }; let (mut peerset, _handle) = Peerset::from_config(config); - peerset.incoming(SetId::from(0), incoming.clone(), ii); - peerset.incoming(SetId::from(0), incoming.clone(), ii4); - peerset.incoming(SetId::from(0), incoming2.clone(), ii2); - peerset.incoming(SetId::from(0), incoming3.clone(), ii3); + peerset.incoming(SetId::from(0), incoming, ii); + peerset.incoming(SetId::from(0), incoming, ii4); + peerset.incoming(SetId::from(0), incoming2, ii2); + peerset.incoming(SetId::from(0), incoming3, ii3); assert_messages( peerset, vec![ - Message::Connect { set_id: SetId::from(0), peer_id: bootnode.clone() }, + Message::Connect { set_id: SetId::from(0), peer_id: bootnode }, Message::Accept(ii), Message::Accept(ii2), Message::Reject(ii3), @@ -865,7 +879,7 @@ mod tests { }; let (mut peerset, _) = Peerset::from_config(config); - peerset.incoming(SetId::from(0), incoming.clone(), ii); + peerset.incoming(SetId::from(0), incoming, ii); assert_messages(peerset, vec![Message::Reject(ii)]); } @@ -879,15 +893,15 @@ mod tests { sets: vec![SetConfig { in_peers: 0, out_peers: 2, - bootnodes: vec![bootnode.clone()], + bootnodes: vec![bootnode], reserved_nodes: Default::default(), reserved_only: false, }], }; let (mut peerset, _handle) = Peerset::from_config(config); - peerset.add_to_peers_set(SetId::from(0), discovered.clone()); - peerset.add_to_peers_set(SetId::from(0), discovered.clone()); + peerset.add_to_peers_set(SetId::from(0), discovered); + peerset.add_to_peers_set(SetId::from(0), discovered); peerset.add_to_peers_set(SetId::from(0), discovered2); assert_messages( @@ -913,7 +927,7 @@ mod tests { // We ban a node by setting its reputation under the threshold. let peer_id = PeerId::random(); - handle.report_peer(peer_id.clone(), ReputationChange::new(BANNED_THRESHOLD - 1, "")); + handle.report_peer(peer_id, ReputationChange::new(BANNED_THRESHOLD - 1, "")); let fut = futures::future::poll_fn(move |cx| { // We need one polling for the message to be processed. @@ -956,7 +970,7 @@ mod tests { // We ban a node by setting its reputation under the threshold. let peer_id = PeerId::random(); - handle.report_peer(peer_id.clone(), ReputationChange::new(BANNED_THRESHOLD - 1, "")); + handle.report_peer(peer_id, ReputationChange::new(BANNED_THRESHOLD - 1, "")); let fut = futures::future::poll_fn(move |cx| { // We need one polling for the message to be processed. diff --git a/client/peerset/src/peersstate.rs b/client/peerset/src/peersstate.rs index 7717620eae3a7..c9af5b8e2ccd0 100644 --- a/client/peerset/src/peersstate.rs +++ b/client/peerset/src/peersstate.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -25,8 +25,8 @@ //! slots. //! //! > Note: This module is purely dedicated to managing slots and reputations. Features such as -//! > for example connecting to some nodes in priority should be added outside of this -//! > module, rather than inside. +//! > for example connecting to some nodes in priority should be added outside of this +//! > module, rather than inside. use libp2p::PeerId; use log::error; @@ -50,8 +50,8 @@ pub struct PeersState { /// List of nodes that we know about. /// /// > **Note**: This list should really be ordered by decreasing reputation, so that we can - /// easily select the best node to connect to. As a first draft, however, we don't - /// sort, to make the logic easier. + /// > easily select the best node to connect to. As a first draft, however, we don't sort, to + /// > make the logic easier. nodes: HashMap, /// Configuration of each set. The size of this `Vec` is never modified. @@ -105,8 +105,8 @@ struct Node { } impl Node { - fn new(num_sets: usize) -> Node { - Node { sets: (0..num_sets).map(|_| MembershipState::NotMember).collect(), reputation: 0 } + fn new(num_sets: usize) -> Self { + Self { sets: (0..num_sets).map(|_| MembershipState::NotMember).collect(), reputation: 0 } } } @@ -128,21 +128,24 @@ enum MembershipState { } impl MembershipState { - /// Returns `true` for `In` and `Out`. + /// Returns `true` for [`MembershipState::In`] and [`MembershipState::Out`]. fn is_connected(self) -> bool { match self { - MembershipState::NotMember => false, - MembershipState::In => true, - MembershipState::Out => true, - MembershipState::NotConnected { .. } => false, + Self::In | Self::Out => true, + Self::NotMember | Self::NotConnected { .. } => false, } } + + /// Returns `true` for [`MembershipState::NotConnected`]. + fn is_not_connected(self) -> bool { + matches!(self, Self::NotConnected { .. }) + } } impl PeersState { - /// Builds a new empty `PeersState`. + /// Builds a new empty [`PeersState`]. pub fn new(sets: impl IntoIterator) -> Self { - PeersState { + Self { nodes: HashMap::new(), sets: sets .into_iter() @@ -166,9 +169,7 @@ impl PeersState { /// Returns an object that grants access to the reputation value of a peer. pub fn peer_reputation(&mut self, peer_id: PeerId) -> Reputation { - if !self.nodes.contains_key(&peer_id) { - self.nodes.insert(peer_id, Node::new(self.sets.len())); - } + self.nodes.entry(peer_id).or_insert_with(|| Node::new(self.sets.len())); let entry = match self.nodes.entry(peer_id) { Entry::Vacant(_) => unreachable!("guaranteed to be inserted above; qed"), @@ -242,12 +243,7 @@ impl PeersState { let outcome = self .nodes .iter_mut() - .filter(|(_, Node { sets, .. })| match sets[set] { - MembershipState::NotMember => false, - MembershipState::In => false, - MembershipState::Out => false, - MembershipState::NotConnected { .. } => true, - }) + .filter(|(_, Node { sets, .. })| sets[set].is_not_connected()) .fold(None::<(&PeerId, &mut Node)>, |mut cur_node, to_try| { if let Some(cur_node) = cur_node.take() { if cur_node.1.reputation >= to_try.1.reputation { @@ -318,35 +314,32 @@ pub enum Peer<'a> { } impl<'a> Peer<'a> { - /// If we are the `Connected` variant, returns the inner `ConnectedPeer`. Returns `None` + /// If we are the `Connected` variant, returns the inner [`ConnectedPeer`]. Returns `None` /// otherwise. pub fn into_connected(self) -> Option> { match self { - Peer::Connected(peer) => Some(peer), - Peer::NotConnected(_) => None, - Peer::Unknown(_) => None, + Self::Connected(peer) => Some(peer), + Self::NotConnected(..) | Self::Unknown(..) => None, } } - /// If we are the `Unknown` variant, returns the inner `ConnectedPeer`. Returns `None` + /// If we are the `NotConnected` variant, returns the inner [`NotConnectedPeer`]. Returns `None` /// otherwise. #[cfg(test)] // Feel free to remove this if this function is needed outside of tests pub fn into_not_connected(self) -> Option> { match self { - Peer::Connected(_) => None, - Peer::NotConnected(peer) => Some(peer), - Peer::Unknown(_) => None, + Self::NotConnected(peer) => Some(peer), + Self::Connected(..) | Self::Unknown(..) => None, } } - /// If we are the `Unknown` variant, returns the inner `ConnectedPeer`. Returns `None` + /// If we are the `Unknown` variant, returns the inner [`UnknownPeer`]. Returns `None` /// otherwise. #[cfg(test)] // Feel free to remove this if this function is needed outside of tests pub fn into_unknown(self) -> Option> { match self { - Peer::Connected(_) => None, - Peer::NotConnected(_) => None, - Peer::Unknown(peer) => Some(peer), + Self::Unknown(peer) => Some(peer), + Self::Connected(..) | Self::NotConnected(..) => None, } } } @@ -473,7 +466,7 @@ impl<'a> NotConnectedPeer<'a> { /// the slots are full, the node stays "not connected" and we return `Err`. /// /// Non-slot-occupying nodes don't count towards the number of slots. - pub fn try_outgoing(self) -> Result, NotConnectedPeer<'a>> { + pub fn try_outgoing(self) -> Result, Self> { let is_no_slot_occupy = self.state.sets[self.set].no_slot_nodes.contains(&*self.peer_id); // Note that it is possible for num_out to be strictly superior to the max, in case we were @@ -500,7 +493,7 @@ impl<'a> NotConnectedPeer<'a> { /// the slots are full, the node stays "not connected" and we return `Err`. /// /// Non-slot-occupying nodes don't count towards the number of slots. - pub fn try_accept_incoming(self) -> Result, NotConnectedPeer<'a>> { + pub fn try_accept_incoming(self) -> Result, Self> { let is_no_slot_occupy = self.state.sets[self.set].no_slot_nodes.contains(&*self.peer_id); // Note that it is possible for num_in to be strictly superior to the max, in case we were @@ -657,7 +650,7 @@ mod tests { let id1 = PeerId::random(); let id2 = PeerId::random(); - peers_state.add_no_slot_node(0, id1.clone()); + peers_state.add_no_slot_node(0, id1); if let Peer::Unknown(p) = peers_state.peer(0, &id1) { assert!(p.discover().try_accept_incoming().is_ok()); } else { @@ -710,43 +703,28 @@ mod tests { assert!(peers_state.highest_not_connected_peer(0).is_none()); peers_state.peer(0, &id1).into_unknown().unwrap().discover().set_reputation(50); peers_state.peer(0, &id2).into_unknown().unwrap().discover().set_reputation(25); - assert_eq!( - peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), - Some(id1.clone()) - ); + assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id1)); peers_state.peer(0, &id2).into_not_connected().unwrap().set_reputation(75); - assert_eq!( - peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), - Some(id2.clone()) - ); + assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id2)); peers_state .peer(0, &id2) .into_not_connected() .unwrap() .try_accept_incoming() .unwrap(); - assert_eq!( - peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), - Some(id1.clone()) - ); + assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id1)); peers_state.peer(0, &id1).into_not_connected().unwrap().set_reputation(100); peers_state.peer(0, &id2).into_connected().unwrap().disconnect(); - assert_eq!( - peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), - Some(id1.clone()) - ); + assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id1)); peers_state.peer(0, &id1).into_not_connected().unwrap().set_reputation(-100); - assert_eq!( - peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), - Some(id2.clone()) - ); + assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id2)); } #[test] fn disconnect_no_slot_doesnt_panic() { let mut peers_state = PeersState::new(iter::once(SetConfig { in_peers: 1, out_peers: 1 })); let id = PeerId::random(); - peers_state.add_no_slot_node(0, id.clone()); + peers_state.add_no_slot_node(0, id); let peer = peers_state .peer(0, &id) .into_unknown() diff --git a/client/peerset/tests/fuzz.rs b/client/peerset/tests/fuzz.rs index 3a9ba686ee95c..48c5cb341c35a 100644 --- a/client/peerset/tests/fuzz.rs +++ b/client/peerset/tests/fuzz.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -52,7 +52,7 @@ fn test_once() { bootnodes: (0..Uniform::new_inclusive(0, 4).sample(&mut rng)) .map(|_| { let id = PeerId::random(); - known_nodes.insert(id.clone()); + known_nodes.insert(id); id }) .collect(), @@ -60,8 +60,8 @@ fn test_once() { (0..Uniform::new_inclusive(0, 2).sample(&mut rng)) .map(|_| { let id = PeerId::random(); - known_nodes.insert(id.clone()); - reserved_nodes.insert(id.clone()); + known_nodes.insert(id); + reserved_nodes.insert(id); id }) .collect() @@ -114,7 +114,7 @@ fn test_once() { // If we generate 1, discover a new node. 1 => { let new_id = PeerId::random(); - known_nodes.insert(new_id.clone()); + known_nodes.insert(new_id); peerset.add_to_peers_set(SetId::from(0), new_id); }, @@ -122,7 +122,7 @@ fn test_once() { 2 => if let Some(id) = known_nodes.iter().choose(&mut rng) { let val = Uniform::new_inclusive(i32::MIN, i32::MAX).sample(&mut rng); - peerset_handle.report_peer(id.clone(), ReputationChange::new(val, "")); + peerset_handle.report_peer(*id, ReputationChange::new(val, "")); }, // If we generate 3, disconnect from a random node. @@ -142,8 +142,8 @@ fn test_once() { }) .choose(&mut rng) { - peerset.incoming(SetId::from(0), id.clone(), next_incoming_id.clone()); - incoming_nodes.insert(next_incoming_id.clone(), id.clone()); + peerset.incoming(SetId::from(0), *id, next_incoming_id); + incoming_nodes.insert(next_incoming_id, *id); next_incoming_id.0 += 1; } }, @@ -157,8 +157,8 @@ fn test_once() { if let Some(id) = known_nodes.iter().filter(|n| !reserved_nodes.contains(*n)).choose(&mut rng) { - peerset_handle.add_reserved_peer(SetId::from(0), id.clone()); - reserved_nodes.insert(id.clone()); + peerset_handle.add_reserved_peer(SetId::from(0), *id); + reserved_nodes.insert(*id); } }, 8 => diff --git a/client/proposer-metrics/Cargo.toml b/client/proposer-metrics/Cargo.toml index ffe5045461f77..97a7c076bd9e7 100644 --- a/client/proposer-metrics/Cargo.toml +++ b/client/proposer-metrics/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sc-proposer-metrics" -version = "0.9.0" +version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Basic metrics for block production." readme = "README.md" @@ -13,5 +13,5 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.8" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.9.0"} +log = "0.4.17" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/proposer-metrics/src/lib.rs b/client/proposer-metrics/src/lib.rs index da29fb2951995..c27d16ea04d7d 100644 --- a/client/proposer-metrics/src/lib.rs +++ b/client/proposer-metrics/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,7 +19,8 @@ //! Prometheus basic proposer metrics. use prometheus_endpoint::{ - register, Gauge, Histogram, HistogramOpts, PrometheusError, Registry, U64, + prometheus::CounterVec, register, Gauge, Histogram, HistogramOpts, Opts, PrometheusError, + Registry, U64, }; /// Optional shareable link to basic authorship metrics. @@ -38,15 +39,26 @@ impl MetricsLink { } pub fn report(&self, do_this: impl FnOnce(&Metrics) -> O) -> Option { - Some(do_this(self.0.as_ref()?)) + self.0.as_ref().map(do_this) } } +/// The reason why proposing a block ended. +pub enum EndProposingReason { + NoMoreTransactions, + HitDeadline, + HitBlockSizeLimit, + HitBlockWeightLimit, +} + /// Authorship metrics. #[derive(Clone)] pub struct Metrics { pub block_constructed: Histogram, pub number_of_transactions: Gauge, + pub end_proposing_reason: CounterVec, + pub create_inherents_time: Histogram, + pub create_block_proposal_time: Histogram, } impl Metrics { @@ -54,18 +66,54 @@ impl Metrics { Ok(Self { block_constructed: register( Histogram::with_opts(HistogramOpts::new( - "proposer_block_constructed", + "substrate_proposer_block_constructed", "Histogram of time taken to construct new block", ))?, registry, )?, number_of_transactions: register( Gauge::new( - "proposer_number_of_transactions", + "substrate_proposer_number_of_transactions", "Number of transactions included in block", )?, registry, )?, + create_inherents_time: register( + Histogram::with_opts(HistogramOpts::new( + "substrate_proposer_create_inherents_time", + "Histogram of time taken to execute create inherents", + ))?, + registry, + )?, + create_block_proposal_time: register( + Histogram::with_opts(HistogramOpts::new( + "substrate_proposer_block_proposal_time", + "Histogram of time taken to construct a block and prepare it for proposal", + ))?, + registry, + )?, + end_proposing_reason: register( + CounterVec::new( + Opts::new( + "substrate_proposer_end_proposal_reason", + "The reason why the block proposing was ended. This doesn't include errors.", + ), + &["reason"], + )?, + registry, + )?, }) } + + /// Report the reason why the proposing ended. + pub fn report_end_proposing_reason(&self, reason: EndProposingReason) { + let reason = match reason { + EndProposingReason::HitDeadline => "hit_deadline", + EndProposingReason::NoMoreTransactions => "no_more_transactions", + EndProposingReason::HitBlockSizeLimit => "hit_block_size_limit", + EndProposingReason::HitBlockWeightLimit => "hit_block_weight_limit", + }; + + self.end_proposing_reason.with_label_values(&[reason]).inc(); + } } diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 6342abb1a3c41..c46488db2d8e1 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-rpc-api" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate RPC interfaces." readme = "README.md" @@ -13,22 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } -futures = "0.3.16" -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -jsonrpc-pubsub = "18.0.0" -log = "0.4.8" -parking_lot = "0.11.1" +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" +log = "0.4.17" +parking_lot = "0.12.1" +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.85" thiserror = "1.0" - -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-version = { version = "4.0.0-dev", path = "../../primitives/version" } -sp-runtime = { path = "../../primitives/runtime", version = "4.0.0-dev" } -sc-chain-spec = { path = "../chain-spec", version = "4.0.0-dev" } -serde = { version = "1.0.126", features = ["derive"] } -serde_json = "1.0.68" +sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } -sp-rpc = { version = "4.0.0-dev", path = "../../primitives/rpc" } -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } +jsonrpsee = { version = "0.16.2", features = ["server", "client-core", "macros"] } diff --git a/client/rpc-api/src/author/error.rs b/client/rpc-api/src/author/error.rs index c7e3ccffabbb7..57a27d48de3ad 100644 --- a/client/rpc-api/src/author/error.rs +++ b/client/rpc-api/src/author/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,40 +18,33 @@ //! Authoring RPC module errors. -use crate::errors; -use jsonrpc_core as rpc; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; use sp_runtime::transaction_validity::InvalidTransaction; /// Author RPC Result type. pub type Result = std::result::Result; -/// Author RPC future Result type. -pub type FutureResult = jsonrpc_core::BoxFuture>; - /// Author RPC errors. #[derive(Debug, thiserror::Error)] pub enum Error { /// Client error. #[error("Client error: {}", .0)] - Client(Box), + Client(Box), /// Transaction pool error, #[error("Transaction pool error: {}", .0)] Pool(#[from] sc_transaction_pool_api::error::Error), /// Verification error #[error("Extrinsic verification error: {}", .0)] - Verification(Box), + Verification(Box), /// Incorrect extrinsic format. #[error("Invalid extrinsic format: {}", .0)] BadFormat(#[from] codec::Error), - /// Incorrect seed phrase. - #[error("Invalid seed phrase/SURI")] - BadSeedPhrase, /// Key type ID has an unknown format. #[error("Invalid key type ID format (should be of length four)")] BadKeyType, - /// Key type ID has some unsupported crypto. - #[error("The crypto of key type ID is unknown")] - UnsupportedKeyType, /// Some random issue with the key store. Shouldn't happen. #[error("The key store is unavailable")] KeyStoreUnavailable, @@ -64,108 +57,127 @@ pub enum Error { } /// Base code for all authorship errors. -const BASE_ERROR: i64 = 1000; +const BASE_ERROR: i32 = 1000; /// Extrinsic has an invalid format. -const BAD_FORMAT: i64 = BASE_ERROR + 1; +const BAD_FORMAT: i32 = BASE_ERROR + 1; /// Error during transaction verification in runtime. -const VERIFICATION_ERROR: i64 = BASE_ERROR + 2; +const VERIFICATION_ERROR: i32 = BASE_ERROR + 2; /// Pool rejected the transaction as invalid -const POOL_INVALID_TX: i64 = BASE_ERROR + 10; +const POOL_INVALID_TX: i32 = BASE_ERROR + 10; /// Cannot determine transaction validity. -const POOL_UNKNOWN_VALIDITY: i64 = POOL_INVALID_TX + 1; +const POOL_UNKNOWN_VALIDITY: i32 = POOL_INVALID_TX + 1; /// The transaction is temporarily banned. -const POOL_TEMPORARILY_BANNED: i64 = POOL_INVALID_TX + 2; +const POOL_TEMPORARILY_BANNED: i32 = POOL_INVALID_TX + 2; /// The transaction is already in the pool -const POOL_ALREADY_IMPORTED: i64 = POOL_INVALID_TX + 3; +const POOL_ALREADY_IMPORTED: i32 = POOL_INVALID_TX + 3; /// Transaction has too low priority to replace existing one in the pool. -const POOL_TOO_LOW_PRIORITY: i64 = POOL_INVALID_TX + 4; +const POOL_TOO_LOW_PRIORITY: i32 = POOL_INVALID_TX + 4; /// Including this transaction would cause a dependency cycle. -const POOL_CYCLE_DETECTED: i64 = POOL_INVALID_TX + 5; +const POOL_CYCLE_DETECTED: i32 = POOL_INVALID_TX + 5; /// The transaction was not included to the pool because of the limits. -const POOL_IMMEDIATELY_DROPPED: i64 = POOL_INVALID_TX + 6; -/// The key type crypto is not known. -const UNSUPPORTED_KEY_TYPE: i64 = POOL_INVALID_TX + 7; +const POOL_IMMEDIATELY_DROPPED: i32 = POOL_INVALID_TX + 6; /// The transaction was not included to the pool since it is unactionable, /// it is not propagable and the local node does not author blocks. -const POOL_UNACTIONABLE: i64 = POOL_INVALID_TX + 8; +const POOL_UNACTIONABLE: i32 = POOL_INVALID_TX + 8; +/// Transaction does not provide any tags, so the pool can't identify it. +const POOL_NO_TAGS: i32 = POOL_INVALID_TX + 9; +/// Invalid block ID. +const POOL_INVALID_BLOCK_ID: i32 = POOL_INVALID_TX + 10; +/// The pool is not accepting future transactions. +const POOL_FUTURE_TX: i32 = POOL_INVALID_TX + 11; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { use sc_transaction_pool_api::error::Error as PoolError; match e { - Error::BadFormat(e) => rpc::Error { - code: rpc::ErrorCode::ServerError(BAD_FORMAT), - message: format!("Extrinsic has invalid format: {}", e).into(), - data: None, - }, - Error::Verification(e) => rpc::Error { - code: rpc::ErrorCode::ServerError(VERIFICATION_ERROR), - message: format!("Verification Error: {}", e).into(), - data: Some(format!("{:?}", e).into()), - }, - Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Custom(e))) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_INVALID_TX), - message: "Invalid Transaction".into(), - data: Some(format!("Custom error: {}", e).into()), + Error::BadFormat(e) => CallError::Custom(ErrorObject::owned( + BAD_FORMAT, + format!("Extrinsic has invalid format: {}", e), + None::<()>, + )), + Error::Verification(e) => CallError::Custom(ErrorObject::owned( + VERIFICATION_ERROR, + format!("Verification Error: {}", e), + Some(format!("{:?}", e)), + )), + Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Custom(e))) => { + CallError::Custom(ErrorObject::owned( + POOL_INVALID_TX, + "Invalid Transaction", + Some(format!("Custom error: {}", e)), + )) }, Error::Pool(PoolError::InvalidTransaction(e)) => { let msg: &str = e.into(); - rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_INVALID_TX), - message: "Invalid Transaction".into(), - data: Some(msg.into()), - } - }, - Error::Pool(PoolError::UnknownTransaction(e)) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_UNKNOWN_VALIDITY), - message: "Unknown Transaction Validity".into(), - data: serde_json::to_value(e).ok(), - }, - Error::Pool(PoolError::TemporarilyBanned) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_TEMPORARILY_BANNED), - message: "Transaction is temporarily banned".into(), - data: None, - }, - Error::Pool(PoolError::AlreadyImported(hash)) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_ALREADY_IMPORTED), - message: "Transaction Already Imported".into(), - data: Some(format!("{:?}", hash).into()), - }, - Error::Pool(PoolError::TooLowPriority { old, new }) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_TOO_LOW_PRIORITY), - message: format!("Priority is too low: ({} vs {})", old, new), - data: Some("The transaction has too low priority to replace another transaction already in the pool.".into()), - }, - Error::Pool(PoolError::CycleDetected) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_CYCLE_DETECTED), - message: "Cycle Detected".into(), - data: None, - }, - Error::Pool(PoolError::ImmediatelyDropped) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_IMMEDIATELY_DROPPED), - message: "Immediately Dropped".into(), - data: Some("The transaction couldn't enter the pool because of the limit".into()), + CallError::Custom(ErrorObject::owned( + POOL_INVALID_TX, + "Invalid Transaction", + Some(msg), + )) }, - Error::Pool(PoolError::Unactionable) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_UNACTIONABLE), - message: "Unactionable".into(), - data: Some( - "The transaction is unactionable since it is not propagable and \ - the local node does not author blocks".into(), - ), + Error::Pool(PoolError::UnknownTransaction(e)) => { + CallError::Custom(ErrorObject::owned( + POOL_UNKNOWN_VALIDITY, + "Unknown Transaction Validity", + Some(format!("{:?}", e)), + )) }, - Error::UnsupportedKeyType => rpc::Error { - code: rpc::ErrorCode::ServerError(UNSUPPORTED_KEY_TYPE), - message: "Unknown key type crypto" .into(), - data: Some( - "The crypto for the given key type is unknown, please add the public key to the \ - request to insert the key successfully.".into() - ), + Error::Pool(PoolError::TemporarilyBanned) => + CallError::Custom(ErrorObject::owned( + POOL_TEMPORARILY_BANNED, + "Transaction is temporarily banned", + None::<()>, + )), + Error::Pool(PoolError::AlreadyImported(hash)) => + CallError::Custom(ErrorObject::owned( + POOL_ALREADY_IMPORTED, + "Transaction Already Imported", + Some(format!("{:?}", hash)), + )), + Error::Pool(PoolError::TooLowPriority { old, new }) => CallError::Custom(ErrorObject::owned( + POOL_TOO_LOW_PRIORITY, + format!("Priority is too low: ({} vs {})", old, new), + Some("The transaction has too low priority to replace another transaction already in the pool.") + )), + Error::Pool(PoolError::CycleDetected) => + CallError::Custom(ErrorObject::owned( + POOL_CYCLE_DETECTED, + "Cycle Detected", + None::<()> + )), + Error::Pool(PoolError::ImmediatelyDropped) => CallError::Custom(ErrorObject::owned( + POOL_IMMEDIATELY_DROPPED, + "Immediately Dropped", + Some("The transaction couldn't enter the pool because of the limit"), + )), + Error::Pool(PoolError::Unactionable) => CallError::Custom(ErrorObject::owned( + POOL_UNACTIONABLE, + "Unactionable", + Some("The transaction is unactionable since it is not propagable and \ + the local node does not author blocks") + )), + Error::Pool(PoolError::NoTagsProvided) => CallError::Custom(ErrorObject::owned( + POOL_NO_TAGS, + "No tags provided", + Some("Transaction does not provide any tags, so the pool can't identify it") + )), + Error::Pool(PoolError::InvalidBlockId(_)) => + CallError::Custom(ErrorObject::owned( + POOL_INVALID_BLOCK_ID, + "The provided block ID is not valid", + None::<()> + )), + Error::Pool(PoolError::RejectedFutureTransaction) => { + CallError::Custom(ErrorObject::owned( + POOL_FUTURE_TX, + "The pool is not accepting future transactions", + None::<()>, + )) }, Error::UnsafeRpcCalled(e) => e.into(), - e => errors::internal(e), - } + e => CallError::Failed(e.into()), + }.into() } } diff --git a/client/rpc-api/src/author/hash.rs b/client/rpc-api/src/author/hash.rs index c4acfb819ddbb..453947dd21f26 100644 --- a/client/rpc-api/src/author/hash.rs +++ b/client/rpc-api/src/author/hash.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/rpc-api/src/author/mod.rs b/client/rpc-api/src/author/mod.rs index 720598e0b32a8..feba7640e3b9f 100644 --- a/client/rpc-api/src/author/mod.rs +++ b/client/rpc-api/src/author/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,85 +18,61 @@ //! Substrate block-author/full-node API. -pub mod error; -pub mod hash; - -use self::error::{FutureResult, Result}; -use jsonrpc_derive::rpc; -use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sc_transaction_pool_api::TransactionStatus; use sp_core::Bytes; -pub use self::gen_client::Client as AuthorClient; +pub mod error; +pub mod hash; /// Substrate authoring RPC API -#[rpc] +#[rpc(client, server)] pub trait AuthorApi { - /// RPC metadata - type Metadata; - /// Submit hex-encoded extrinsic for inclusion in block. - #[rpc(name = "author_submitExtrinsic")] - fn submit_extrinsic(&self, extrinsic: Bytes) -> FutureResult; + #[method(name = "author_submitExtrinsic")] + async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult; /// Insert a key into the keystore. - #[rpc(name = "author_insertKey")] - fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<()>; + #[method(name = "author_insertKey")] + fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> RpcResult<()>; /// Generate new session keys and returns the corresponding public keys. - #[rpc(name = "author_rotateKeys")] - fn rotate_keys(&self) -> Result; + #[method(name = "author_rotateKeys")] + fn rotate_keys(&self) -> RpcResult; /// Checks if the keystore has private keys for the given session public keys. /// /// `session_keys` is the SCALE encoded session keys object from the runtime. /// /// Returns `true` iff all private keys could be found. - #[rpc(name = "author_hasSessionKeys")] - fn has_session_keys(&self, session_keys: Bytes) -> Result; + #[method(name = "author_hasSessionKeys")] + fn has_session_keys(&self, session_keys: Bytes) -> RpcResult; /// Checks if the keystore has private keys for the given public key and key type. /// /// Returns `true` if a private key could be found. - #[rpc(name = "author_hasKey")] - fn has_key(&self, public_key: Bytes, key_type: String) -> Result; + #[method(name = "author_hasKey")] + fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult; /// Returns all pending extrinsics, potentially grouped by sender. - #[rpc(name = "author_pendingExtrinsics")] - fn pending_extrinsics(&self) -> Result>; + #[method(name = "author_pendingExtrinsics")] + fn pending_extrinsics(&self) -> RpcResult>; /// Remove given extrinsic from the pool and temporarily ban it to prevent reimporting. - #[rpc(name = "author_removeExtrinsic")] + #[method(name = "author_removeExtrinsic")] fn remove_extrinsic( &self, bytes_or_hash: Vec>, - ) -> Result>; + ) -> RpcResult>; /// Submit an extrinsic to watch. /// /// See [`TransactionStatus`](sc_transaction_pool_api::TransactionStatus) for details on /// transaction life cycle. - #[pubsub( - subscription = "author_extrinsicUpdate", - subscribe, - name = "author_submitAndWatchExtrinsic" + #[subscription( + name = "author_submitAndWatchExtrinsic" => "author_extrinsicUpdate", + unsubscribe = "author_unwatchExtrinsic", + item = TransactionStatus, )] - fn watch_extrinsic( - &self, - metadata: Self::Metadata, - subscriber: Subscriber>, - bytes: Bytes, - ); - - /// Unsubscribe from extrinsic watching. - #[pubsub( - subscription = "author_extrinsicUpdate", - unsubscribe, - name = "author_unwatchExtrinsic" - )] - fn unwatch_extrinsic( - &self, - metadata: Option, - id: SubscriptionId, - ) -> Result; + fn watch_extrinsic(&self, bytes: Bytes); } diff --git a/client/rpc-api/src/chain/error.rs b/client/rpc-api/src/chain/error.rs index c7f14b2dfc168..670e221cf1cde 100644 --- a/client/rpc-api/src/chain/error.rs +++ b/client/rpc-api/src/chain/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,38 +18,33 @@ //! Error helpers for Chain RPC module. -use crate::errors; -use jsonrpc_core as rpc; - +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; /// Chain RPC Result type. pub type Result = std::result::Result; -/// Chain RPC future Result type. -pub type FutureResult = jsonrpc_core::BoxFuture>; - /// Chain RPC errors. #[derive(Debug, thiserror::Error)] pub enum Error { /// Client error. #[error("Client error: {}", .0)] - Client(#[from] Box), + Client(#[from] Box), /// Other error type. #[error("{0}")] Other(String), } /// Base error code for all chain errors. -const BASE_ERROR: i64 = 3000; +const BASE_ERROR: i32 = 3000; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { match e { - Error::Other(message) => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), - message, - data: None, - }, - e => errors::internal(e), + Error::Other(message) => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, message, None::<()>)).into(), + e => e.into(), } } } diff --git a/client/rpc-api/src/chain/mod.rs b/client/rpc-api/src/chain/mod.rs index 79ae80d0c4d1d..c7cc97463983d 100644 --- a/client/rpc-api/src/chain/mod.rs +++ b/client/rpc-api/src/chain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,96 +18,59 @@ //! Substrate blockchain API. -pub mod error; - -use self::error::{FutureResult, Result}; -use jsonrpc_core::Result as RpcResult; -use jsonrpc_derive::rpc; -use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sp_rpc::{list::ListOrValue, number::NumberOrHex}; -pub use self::gen_client::Client as ChainClient; +pub mod error; -/// Substrate blockchain API -#[rpc] +#[rpc(client, server)] pub trait ChainApi { - /// RPC metadata - type Metadata; - - /// Get header of a relay chain block. - #[rpc(name = "chain_getHeader")] - fn header(&self, hash: Option) -> FutureResult>; + /// Get header. + #[method(name = "chain_getHeader", blocking)] + fn header(&self, hash: Option) -> RpcResult>; /// Get header and body of a relay chain block. - #[rpc(name = "chain_getBlock")] - fn block(&self, hash: Option) -> FutureResult>; + #[method(name = "chain_getBlock", blocking)] + fn block(&self, hash: Option) -> RpcResult>; /// Get hash of the n-th block in the canon chain. /// /// By default returns latest block hash. - #[rpc(name = "chain_getBlockHash", alias("chain_getHead"))] + #[method(name = "chain_getBlockHash", aliases = ["chain_getHead"], blocking)] fn block_hash( &self, hash: Option>, - ) -> Result>>; + ) -> RpcResult>>; /// Get hash of the last finalized block in the canon chain. - #[rpc(name = "chain_getFinalizedHead", alias("chain_getFinalisedHead"))] - fn finalized_head(&self) -> Result; - - /// All head subscription - #[pubsub(subscription = "chain_allHead", subscribe, name = "chain_subscribeAllHeads")] - fn subscribe_all_heads(&self, metadata: Self::Metadata, subscriber: Subscriber
); - - /// Unsubscribe from all head subscription. - #[pubsub(subscription = "chain_allHead", unsubscribe, name = "chain_unsubscribeAllHeads")] - fn unsubscribe_all_heads( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult; - - /// New head subscription - #[pubsub( - subscription = "chain_newHead", - subscribe, - name = "chain_subscribeNewHeads", - alias("subscribe_newHead", "chain_subscribeNewHead") + #[method(name = "chain_getFinalizedHead", aliases = ["chain_getFinalisedHead"], blocking)] + fn finalized_head(&self) -> RpcResult; + + /// All head subscription. + #[subscription( + name = "chain_subscribeAllHeads" => "chain_allHead", + unsubscribe = "chain_unsubscribeAllHeads", + item = Header )] - fn subscribe_new_heads(&self, metadata: Self::Metadata, subscriber: Subscriber
); - - /// Unsubscribe from new head subscription. - #[pubsub( - subscription = "chain_newHead", - unsubscribe, - name = "chain_unsubscribeNewHeads", - alias("unsubscribe_newHead", "chain_unsubscribeNewHead") + fn subscribe_all_heads(&self); + + /// New head subscription. + #[subscription( + name = "chain_subscribeNewHeads" => "chain_newHead", + aliases = ["subscribe_newHead", "chain_subscribeNewHead"], + unsubscribe = "chain_unsubscribeNewHeads", + unsubscribe_aliases = ["unsubscribe_newHead", "chain_unsubscribeNewHead"], + item = Header )] - fn unsubscribe_new_heads( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult; - - /// Finalized head subscription - #[pubsub( - subscription = "chain_finalizedHead", - subscribe, - name = "chain_subscribeFinalizedHeads", - alias("chain_subscribeFinalisedHeads") + fn subscribe_new_heads(&self); + + /// Finalized head subscription. + #[subscription( + name = "chain_subscribeFinalizedHeads" => "chain_finalizedHead", + aliases = ["chain_subscribeFinalisedHeads"], + unsubscribe = "chain_unsubscribeFinalizedHeads", + unsubscribe_aliases = ["chain_unsubscribeFinalisedHeads"], + item = Header )] - fn subscribe_finalized_heads(&self, metadata: Self::Metadata, subscriber: Subscriber
); - - /// Unsubscribe from finalized head subscription. - #[pubsub( - subscription = "chain_finalizedHead", - unsubscribe, - name = "chain_unsubscribeFinalizedHeads", - alias("chain_unsubscribeFinalisedHeads") - )] - fn unsubscribe_finalized_heads( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult; + fn subscribe_finalized_heads(&self); } diff --git a/client/rpc-api/src/child_state/mod.rs b/client/rpc-api/src/child_state/mod.rs index de94790d09907..be279a97463b0 100644 --- a/client/rpc-api/src/child_state/mod.rs +++ b/client/rpc-api/src/child_state/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,38 +16,31 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Substrate state API. - -use crate::state::error::FutureResult; -use jsonrpc_derive::rpc; -use sp_core::storage::{PrefixedStorageKey, StorageData, StorageKey}; - -pub use self::gen_client::Client as ChildStateClient; +//! Substrate child state API use crate::state::ReadProof; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use sp_core::storage::{PrefixedStorageKey, StorageData, StorageKey}; /// Substrate child state API /// /// Note that all `PrefixedStorageKey` are deserialized /// from json and not guaranteed valid. -#[rpc] +#[rpc(client, server)] pub trait ChildStateApi { - /// RPC Metadata - type Metadata; - - /// DEPRECATED: Please use `childstate_getKeysPaged` with proper paging support. /// Returns the keys with prefix from a child storage, leave empty to get all the keys - #[rpc(name = "childstate_getKeys")] + #[method(name = "childstate_getKeys", blocking)] + #[deprecated(since = "2.0.0", note = "Please use `getKeysPaged` with proper paging support")] fn storage_keys( &self, child_storage_key: PrefixedStorageKey, prefix: StorageKey, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns the keys with prefix from a child storage with pagination support. /// Up to `count` keys will be returned. /// If `start_key` is passed, return next keys in storage in lexicographic order. - #[rpc(name = "childstate_getKeysPaged", alias("childstate_getKeysPagedAt"))] + #[method(name = "childstate_getKeysPaged", aliases = ["childstate_getKeysPagedAt"], blocking)] fn storage_keys_paged( &self, child_storage_key: PrefixedStorageKey, @@ -55,50 +48,50 @@ pub trait ChildStateApi { count: u32, start_key: Option, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns a child storage entry at a specific block's state. - #[rpc(name = "childstate_getStorage")] + #[method(name = "childstate_getStorage", blocking)] fn storage( &self, child_storage_key: PrefixedStorageKey, key: StorageKey, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns child storage entries for multiple keys at a specific block's state. - #[rpc(name = "childstate_getStorageEntries")] + #[method(name = "childstate_getStorageEntries", blocking)] fn storage_entries( &self, child_storage_key: PrefixedStorageKey, keys: Vec, hash: Option, - ) -> FutureResult>>; + ) -> RpcResult>>; /// Returns the hash of a child storage entry at a block's state. - #[rpc(name = "childstate_getStorageHash")] + #[method(name = "childstate_getStorageHash", blocking)] fn storage_hash( &self, child_storage_key: PrefixedStorageKey, key: StorageKey, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns the size of a child storage entry at a block's state. - #[rpc(name = "childstate_getStorageSize")] + #[method(name = "childstate_getStorageSize", blocking)] fn storage_size( &self, child_storage_key: PrefixedStorageKey, key: StorageKey, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns proof of storage for child key entries at a specific block's state. - #[rpc(name = "state_getChildReadProof")] + #[method(name = "state_getChildReadProof", blocking)] fn read_child_proof( &self, child_storage_key: PrefixedStorageKey, keys: Vec, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; } diff --git a/client/rpc-api/src/dev/error.rs b/client/rpc-api/src/dev/error.rs new file mode 100644 index 0000000000000..43fd3325fa598 --- /dev/null +++ b/client/rpc-api/src/dev/error.rs @@ -0,0 +1,66 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Error helpers for Dev RPC module. + +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; + +/// Dev RPC errors. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Failed to query specified block or its parent: Probably an invalid hash. + #[error("Error while querying block: {0}")] + BlockQueryError(Box), + /// The re-execution of the specified block failed. + #[error("Failed to re-execute the specified block")] + BlockExecutionFailed, + /// Failed to extract the proof. + #[error("Failed to extract the proof")] + ProofExtractionFailed, + /// The witness compaction failed. + #[error("Failed to create to compact the witness")] + WitnessCompactionFailed, + /// The method is marked as unsafe but unsafe flag wasn't supplied on the CLI. + #[error(transparent)] + UnsafeRpcCalled(#[from] crate::policy::UnsafeRpcError), +} + +/// Base error code for all dev errors. +const BASE_ERROR: i32 = 6000; + +impl From for JsonRpseeError { + fn from(e: Error) -> Self { + let msg = e.to_string(); + + match e { + Error::BlockQueryError(_) => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, msg, None::<()>)), + Error::BlockExecutionFailed => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 3, msg, None::<()>)), + Error::WitnessCompactionFailed => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 4, msg, None::<()>)), + Error::ProofExtractionFailed => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 5, msg, None::<()>)), + Error::UnsafeRpcCalled(e) => e.into(), + } + .into() + } +} diff --git a/client/rpc-api/src/dev/mod.rs b/client/rpc-api/src/dev/mod.rs new file mode 100644 index 0000000000000..afd83272a0127 --- /dev/null +++ b/client/rpc-api/src/dev/mod.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Substrate dev API containing RPCs that are mainly meant for debugging and stats collection for +//! developers. The endpoints in this RPC module are not meant to be available to non-local users +//! and are all marked `unsafe`. + +pub mod error; + +use codec::{Decode, Encode}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; + +/// Statistics of a block returned by the `dev_getBlockStats` RPC. +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlockStats { + /// The length in bytes of the storage proof produced by executing the block. + pub witness_len: u64, + /// The length in bytes of the storage proof after compaction. + pub witness_compact_len: u64, + /// Length of the block in bytes. + /// + /// This information can also be acquired by downloading the whole block. This merely + /// saves some complexity on the client side. + pub block_len: u64, + /// Number of extrinsics in the block. + /// + /// This information can also be acquired by downloading the whole block. This merely + /// saves some complexity on the client side. + pub num_extrinsics: u64, +} + +/// Substrate dev API. +/// +/// This API contains unstable and unsafe methods only meant for development nodes. They +/// are all flagged as unsafe for this reason. +#[rpc(client, server)] +pub trait DevApi { + /// Reexecute the specified `block_hash` and gather statistics while doing so. + /// + /// This function requires the specified block and its parent to be available + /// at the queried node. If either the specified block or the parent is pruned, + /// this function will return `None`. + #[method(name = "dev_getBlockStats")] + fn block_stats(&self, block_hash: Hash) -> RpcResult>; +} diff --git a/client/rpc-api/src/helpers.rs b/client/rpc-api/src/helpers.rs deleted file mode 100644 index a26adbf2e9032..0000000000000 --- a/client/rpc-api/src/helpers.rs +++ /dev/null @@ -1,41 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use futures::{channel::oneshot, Future}; -use std::pin::Pin; - -/// Wraps around `oneshot::Receiver` and adjusts the error type to produce an internal error if the -/// sender gets dropped. -pub struct Receiver(pub oneshot::Receiver); - -impl Future for Receiver { - type Output = Result; - - fn poll( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - Future::poll(Pin::new(&mut self.0), cx).map_err(|_| jsonrpc_core::Error::internal_error()) - } -} - -impl jsonrpc_core::WrapFuture for Receiver { - fn into_future(self) -> jsonrpc_core::BoxFuture> { - Box::pin(async { self.await }) - } -} diff --git a/client/rpc-api/src/lib.rs b/client/rpc-api/src/lib.rs index 92de1e7fcb344..a0cbbcee80e3e 100644 --- a/client/rpc-api/src/lib.rs +++ b/client/rpc-api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,19 +22,14 @@ #![warn(missing_docs)] -mod errors; -mod helpers; -mod metadata; mod policy; -pub use helpers::Receiver; -pub use jsonrpc_core::IoHandlerExtension as RpcExtension; -pub use metadata::Metadata; -pub use policy::{DenyUnsafe, UnsafeRpcError}; +pub use policy::DenyUnsafe; pub mod author; pub mod chain; pub mod child_state; +pub mod dev; pub mod offchain; pub mod state; pub mod system; diff --git a/client/rpc-api/src/metadata.rs b/client/rpc-api/src/metadata.rs deleted file mode 100644 index d493b92c11ac5..0000000000000 --- a/client/rpc-api/src/metadata.rs +++ /dev/null @@ -1,60 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! RPC Metadata -use std::sync::Arc; - -use futures::channel::mpsc; -use jsonrpc_pubsub::{PubSubMetadata, Session}; - -/// RPC Metadata. -/// -/// Manages persistent session for transports that support it -/// and may contain some additional info extracted from specific transports -/// (like remote client IP address, request headers, etc) -#[derive(Default, Clone)] -pub struct Metadata { - session: Option>, -} - -impl jsonrpc_core::Metadata for Metadata {} -impl PubSubMetadata for Metadata { - fn session(&self) -> Option> { - self.session.clone() - } -} - -impl Metadata { - /// Create new `Metadata` with session (Pub/Sub) support. - pub fn new(transport: mpsc::UnboundedSender) -> Self { - Metadata { session: Some(Arc::new(Session::new(transport))) } - } - - /// Create new `Metadata` for tests. - #[cfg(test)] - pub fn new_test() -> (mpsc::UnboundedReceiver, Self) { - let (tx, rx) = mpsc::unbounded(); - (rx, Self::new(tx)) - } -} - -impl From> for Metadata { - fn from(sender: mpsc::UnboundedSender) -> Self { - Self::new(sender) - } -} diff --git a/client/rpc-api/src/offchain/error.rs b/client/rpc-api/src/offchain/error.rs index 6b8e2bfe189b1..be72e05fc4460 100644 --- a/client/rpc-api/src/offchain/error.rs +++ b/client/rpc-api/src/offchain/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,7 +18,10 @@ //! Offchain RPC errors. -use jsonrpc_core as rpc; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; /// Offchain RPC Result type. pub type Result = std::result::Result; @@ -35,16 +38,17 @@ pub enum Error { } /// Base error code for all offchain errors. -const BASE_ERROR: i64 = 5000; +const BASE_ERROR: i32 = 5000; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { match e { - Error::UnavailableStorageKind => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), - message: "This storage kind is not available yet".into(), - data: None, - }, + Error::UnavailableStorageKind => CallError::Custom(ErrorObject::owned( + BASE_ERROR + 1, + "This storage kind is not available yet", + None::<()>, + )) + .into(), Error::UnsafeRpcCalled(e) => e.into(), } } diff --git a/client/rpc-api/src/offchain/mod.rs b/client/rpc-api/src/offchain/mod.rs index 333892fc19c4c..d9435d9a875fe 100644 --- a/client/rpc-api/src/offchain/mod.rs +++ b/client/rpc-api/src/offchain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,22 +18,19 @@ //! Substrate offchain API. -pub mod error; - -use self::error::Result; -use jsonrpc_derive::rpc; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sp_core::{offchain::StorageKind, Bytes}; -pub use self::gen_client::Client as OffchainClient; +pub mod error; /// Substrate offchain RPC API -#[rpc] +#[rpc(client, server)] pub trait OffchainApi { /// Set offchain local storage under given key and prefix. - #[rpc(name = "offchain_localStorageSet")] - fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<()>; + #[method(name = "offchain_localStorageSet")] + fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> RpcResult<()>; /// Get offchain local storage under given key and prefix. - #[rpc(name = "offchain_localStorageGet")] - fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result>; + #[method(name = "offchain_localStorageGet")] + fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> RpcResult>; } diff --git a/client/rpc-api/src/policy.rs b/client/rpc-api/src/policy.rs index 5d56c62bfece3..69ca8958520a6 100644 --- a/client/rpc-api/src/policy.rs +++ b/client/rpc-api/src/policy.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,7 +21,13 @@ //! Contains a `DenyUnsafe` type that can be used to deny potentially unsafe //! RPC when accessed externally. -use jsonrpc_core as rpc; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::{ + error::{CallError, ErrorCode}, + ErrorObject, + }, +}; /// Signifies whether a potentially unsafe RPC should be denied. #[derive(Clone, Copy, Debug)] @@ -55,8 +61,18 @@ impl std::fmt::Display for UnsafeRpcError { impl std::error::Error for UnsafeRpcError {} -impl From for rpc::Error { - fn from(_: UnsafeRpcError) -> rpc::Error { - rpc::Error::method_not_found() +impl From for CallError { + fn from(e: UnsafeRpcError) -> CallError { + CallError::Custom(ErrorObject::owned( + ErrorCode::MethodNotFound.code(), + e.to_string(), + None::<()>, + )) + } +} + +impl From for JsonRpseeError { + fn from(e: UnsafeRpcError) -> JsonRpseeError { + JsonRpseeError::Call(e.into()) } } diff --git a/client/rpc-api/src/state/error.rs b/client/rpc-api/src/state/error.rs index d700863476329..b1df64b4789ab 100644 --- a/client/rpc-api/src/state/error.rs +++ b/client/rpc-api/src/state/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,21 +18,19 @@ //! State RPC errors. -use crate::errors; -use jsonrpc_core as rpc; - +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; /// State RPC Result type. pub type Result = std::result::Result; -/// State RPC future Result type. -pub type FutureResult = jsonrpc_core::BoxFuture>; - /// State RPC errors. #[derive(Debug, thiserror::Error)] pub enum Error { /// Client error. #[error("Client error: {}", .0)] - Client(#[from] Box), + Client(#[from] Box), /// Provided block range couldn't be resolved to a list of blocks. #[error("Cannot resolve a block range ['{:?}' ... '{:?}]. {}", .from, .to, .details)] InvalidBlockRange { @@ -57,22 +55,18 @@ pub enum Error { } /// Base code for all state errors. -const BASE_ERROR: i64 = 4000; +const BASE_ERROR: i32 = 4000; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { match e { - Error::InvalidBlockRange { .. } => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), - message: format!("{}", e), - data: None, - }, - Error::InvalidCount { .. } => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 2), - message: format!("{}", e), - data: None, - }, - e => errors::internal(e), + Error::InvalidBlockRange { .. } => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, e.to_string(), None::<()>)) + .into(), + Error::InvalidCount { .. } => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 2, e.to_string(), None::<()>)) + .into(), + e => Self::to_call_error(e), } } } diff --git a/client/rpc-api/src/state/helpers.rs b/client/rpc-api/src/state/helpers.rs index 718ad69ac232c..25f07be1c97c8 100644 --- a/client/rpc-api/src/state/helpers.rs +++ b/client/rpc-api/src/state/helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/rpc-api/src/state/mod.rs b/client/rpc-api/src/state/mod.rs index 620a000c500f4..40e208c2eba8d 100644 --- a/client/rpc-api/src/state/mod.rs +++ b/client/rpc-api/src/state/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,150 +18,114 @@ //! Substrate state API. -pub mod error; -pub mod helpers; - -use self::error::FutureResult; -use jsonrpc_core::Result as RpcResult; -use jsonrpc_derive::rpc; -use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sp_core::{ storage::{StorageChangeSet, StorageData, StorageKey}, Bytes, }; use sp_version::RuntimeVersion; -pub use self::{gen_client::Client as StateClient, helpers::ReadProof}; +pub mod error; +pub mod helpers; + +pub use self::helpers::ReadProof; /// Substrate state API -#[rpc] +#[rpc(client, server)] pub trait StateApi { - /// RPC Metadata - type Metadata; - /// Call a contract at a block's state. - #[rpc(name = "state_call", alias("state_callAt"))] - fn call(&self, name: String, bytes: Bytes, hash: Option) -> FutureResult; + #[method(name = "state_call", aliases = ["state_callAt"], blocking)] + fn call(&self, name: String, bytes: Bytes, hash: Option) -> RpcResult; - /// DEPRECATED: Please use `state_getKeysPaged` with proper paging support. /// Returns the keys with prefix, leave empty to get all the keys. - #[rpc(name = "state_getKeys")] - fn storage_keys(&self, prefix: StorageKey, hash: Option) - -> FutureResult>; + #[method(name = "state_getKeys", blocking)] + #[deprecated(since = "2.0.0", note = "Please use `getKeysPaged` with proper paging support")] + fn storage_keys(&self, prefix: StorageKey, hash: Option) -> RpcResult>; /// Returns the keys with prefix, leave empty to get all the keys - #[rpc(name = "state_getPairs")] + #[method(name = "state_getPairs", blocking)] fn storage_pairs( &self, prefix: StorageKey, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns the keys with prefix with pagination support. /// Up to `count` keys will be returned. /// If `start_key` is passed, return next keys in storage in lexicographic order. - #[rpc(name = "state_getKeysPaged", alias("state_getKeysPagedAt"))] + #[method(name = "state_getKeysPaged", aliases = ["state_getKeysPagedAt"], blocking)] fn storage_keys_paged( &self, prefix: Option, count: u32, start_key: Option, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns a storage entry at a specific block's state. - #[rpc(name = "state_getStorage", alias("state_getStorageAt"))] - fn storage(&self, key: StorageKey, hash: Option) -> FutureResult>; + #[method(name = "state_getStorage", aliases = ["state_getStorageAt"], blocking)] + fn storage(&self, key: StorageKey, hash: Option) -> RpcResult>; /// Returns the hash of a storage entry at a block's state. - #[rpc(name = "state_getStorageHash", alias("state_getStorageHashAt"))] - fn storage_hash(&self, key: StorageKey, hash: Option) -> FutureResult>; + #[method(name = "state_getStorageHash", aliases = ["state_getStorageHashAt"], blocking)] + fn storage_hash(&self, key: StorageKey, hash: Option) -> RpcResult>; /// Returns the size of a storage entry at a block's state. - #[rpc(name = "state_getStorageSize", alias("state_getStorageSizeAt"))] - fn storage_size(&self, key: StorageKey, hash: Option) -> FutureResult>; + #[method(name = "state_getStorageSize", aliases = ["state_getStorageSizeAt"], blocking)] + fn storage_size(&self, key: StorageKey, hash: Option) -> RpcResult>; /// Returns the runtime metadata as an opaque blob. - #[rpc(name = "state_getMetadata")] - fn metadata(&self, hash: Option) -> FutureResult; + #[method(name = "state_getMetadata", blocking)] + fn metadata(&self, hash: Option) -> RpcResult; /// Get the runtime version. - #[rpc(name = "state_getRuntimeVersion", alias("chain_getRuntimeVersion"))] - fn runtime_version(&self, hash: Option) -> FutureResult; + #[method(name = "state_getRuntimeVersion", aliases = ["chain_getRuntimeVersion"], blocking)] + fn runtime_version(&self, hash: Option) -> RpcResult; /// Query historical storage entries (by key) starting from a block given as the second /// parameter. /// /// NOTE This first returned result contains the initial state of storage for all keys. /// Subsequent values in the vector represent changes to the previous state (diffs). - #[rpc(name = "state_queryStorage")] + #[method(name = "state_queryStorage", blocking)] fn query_storage( &self, keys: Vec, block: Hash, hash: Option, - ) -> FutureResult>>; + ) -> RpcResult>>; /// Query storage entries (by key) starting at block hash given as the second parameter. - #[rpc(name = "state_queryStorageAt")] + #[method(name = "state_queryStorageAt", blocking)] fn query_storage_at( &self, keys: Vec, at: Option, - ) -> FutureResult>>; + ) -> RpcResult>>; /// Returns proof of storage entries at a specific block's state. - #[rpc(name = "state_getReadProof")] - fn read_proof( - &self, - keys: Vec, - hash: Option, - ) -> FutureResult>; + #[method(name = "state_getReadProof", blocking)] + fn read_proof(&self, keys: Vec, hash: Option) -> RpcResult>; /// New runtime version subscription - #[pubsub( - subscription = "state_runtimeVersion", - subscribe, - name = "state_subscribeRuntimeVersion", - alias("chain_subscribeRuntimeVersion") + #[subscription( + name = "state_subscribeRuntimeVersion" => "state_runtimeVersion", + unsubscribe = "state_unsubscribeRuntimeVersion", + aliases = ["chain_subscribeRuntimeVersion"], + unsubscribe_aliases = ["chain_unsubscribeRuntimeVersion"], + item = RuntimeVersion, )] - fn subscribe_runtime_version( - &self, - metadata: Self::Metadata, - subscriber: Subscriber, - ); - - /// Unsubscribe from runtime version subscription - #[pubsub( - subscription = "state_runtimeVersion", - unsubscribe, - name = "state_unsubscribeRuntimeVersion", - alias("chain_unsubscribeRuntimeVersion") - )] - fn unsubscribe_runtime_version( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult; + fn subscribe_runtime_version(&self); /// New storage subscription - #[pubsub(subscription = "state_storage", subscribe, name = "state_subscribeStorage")] - fn subscribe_storage( - &self, - metadata: Self::Metadata, - subscriber: Subscriber>, - keys: Option>, - ); - - /// Unsubscribe from storage subscription - #[pubsub(subscription = "state_storage", unsubscribe, name = "state_unsubscribeStorage")] - fn unsubscribe_storage( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult; + #[subscription( + name = "state_subscribeStorage" => "state_storage", + unsubscribe = "state_unsubscribeStorage", + item = StorageChangeSet, + )] + fn subscribe_storage(&self, keys: Option>); - /// The `state_traceBlock` RPC provides a way to trace the re-execution of a single + /// The `traceBlock` RPC provides a way to trace the re-execution of a single /// block, collecting Spans and Events from both the client and the relevant WASM runtime. /// The Spans and Events are conceptually equivalent to those from the [Tracing][1] crate. /// @@ -301,7 +265,7 @@ pub trait StateApi { /// [substrate storage][1], [transparent keys in substrate][2], /// [querying substrate storage via rpc][3]. /// - /// [1]: https://substrate.dev/docs/en/knowledgebase/advanced/storage#storage-map-key + /// [1]: https://docs.substrate.io/main-docs/fundamentals/state-transitions-and-storage/ /// [2]: https://www.shawntabrizi.com/substrate/transparent-keys-in-substrate/ /// [3]: https://www.shawntabrizi.com/substrate/querying-substrate-storage-via-rpc/ /// @@ -312,13 +276,13 @@ pub trait StateApi { /// narrow down the traces using a smaller set of targets and/or storage keys. /// /// If you are having issues with maximum payload size you can use the flag - /// `-lstate_tracing=trace` to get some logging during tracing. - #[rpc(name = "state_traceBlock")] + /// `-ltracing=trace` to get some logging during tracing. + #[method(name = "state_traceBlock", blocking)] fn trace_block( &self, block: Hash, targets: Option, storage_keys: Option, methods: Option, - ) -> FutureResult; + ) -> RpcResult; } diff --git a/client/rpc-api/src/system/error.rs b/client/rpc-api/src/system/error.rs index 4ba5125d82bc1..777f8c6c6df0b 100644 --- a/client/rpc-api/src/system/error.rs +++ b/client/rpc-api/src/system/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,7 +19,10 @@ //! System RPC module errors. use crate::system::helpers::Health; -use jsonrpc_core as rpc; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; /// System RPC Result type. pub type Result = std::result::Result; @@ -35,22 +38,24 @@ pub enum Error { MalformattedPeerArg(String), } -/// Base code for all system errors. -const BASE_ERROR: i64 = 2000; +// Base code for all system errors. +const BASE_ERROR: i32 = 2000; +// Provided block range couldn't be resolved to a list of blocks. +const NOT_HEALTHY_ERROR: i32 = BASE_ERROR + 1; +// Peer argument is malformatted. +const MALFORMATTED_PEER_ARG_ERROR: i32 = BASE_ERROR + 2; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { match e { - Error::NotHealthy(ref h) => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), - message: format!("{}", e), - data: serde_json::to_value(h).ok(), - }, - Error::MalformattedPeerArg(ref e) => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 2), - message: e.clone(), - data: None, - }, + Error::NotHealthy(ref h) => + CallError::Custom(ErrorObject::owned(NOT_HEALTHY_ERROR, e.to_string(), Some(h))), + Error::MalformattedPeerArg(e) => CallError::Custom(ErrorObject::owned( + MALFORMATTED_PEER_ARG_ERROR + 2, + e, + None::<()>, + )), } + .into() } } diff --git a/client/rpc-api/src/system/helpers.rs b/client/rpc-api/src/system/helpers.rs index 96e8aeb1ae3d7..7ddb3f813c249 100644 --- a/client/rpc-api/src/system/helpers.rs +++ b/client/rpc-api/src/system/helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -76,8 +76,6 @@ pub struct PeerInfo { pub enum NodeRole { /// The node is a full node Full, - /// The node is a light client - LightClient, /// The node is an authority Authority, } @@ -90,10 +88,10 @@ pub struct SyncState { pub starting_block: Number, /// Height of the current best block of the node. pub current_block: Number, - /// Height of the highest block learned from the network. Missing if no block is known yet. - #[serde(default = "Default::default", skip_serializing_if = "Option::is_none")] - pub highest_block: Option, + /// Height of the highest block in the network. + pub highest_block: Number, } + #[cfg(test)] mod tests { use super::*; @@ -131,7 +129,7 @@ mod tests { ::serde_json::to_string(&SyncState { starting_block: 12u32, current_block: 50u32, - highest_block: Some(128u32), + highest_block: 128u32, }) .unwrap(), r#"{"startingBlock":12,"currentBlock":50,"highestBlock":128}"#, @@ -141,10 +139,10 @@ mod tests { ::serde_json::to_string(&SyncState { starting_block: 12u32, current_block: 50u32, - highest_block: None, + highest_block: 50u32, }) .unwrap(), - r#"{"startingBlock":12,"currentBlock":50}"#, + r#"{"startingBlock":12,"currentBlock":50,"highestBlock":50}"#, ); } } diff --git a/client/rpc-api/src/system/mod.rs b/client/rpc-api/src/system/mod.rs index 3ffc5f4346508..1e12d5be87ee8 100644 --- a/client/rpc-api/src/system/mod.rs +++ b/client/rpc-api/src/system/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,65 +18,61 @@ //! Substrate system API. -pub mod error; -pub mod helpers; - -use crate::helpers::Receiver; -use jsonrpc_core::BoxFuture; -use jsonrpc_derive::rpc; +use jsonrpsee::{ + core::{JsonValue, RpcResult}, + proc_macros::rpc, +}; -use self::error::Result as SystemResult; +pub use self::helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo}; -pub use self::{ - gen_client::Client as SystemClient, - helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo}, -}; +pub mod error; +pub mod helpers; /// Substrate system RPC API -#[rpc] +#[rpc(client, server)] pub trait SystemApi { /// Get the node's implementation name. Plain old string. - #[rpc(name = "system_name")] - fn system_name(&self) -> SystemResult; + #[method(name = "system_name")] + fn system_name(&self) -> RpcResult; /// Get the node implementation's version. Should be a semver string. - #[rpc(name = "system_version")] - fn system_version(&self) -> SystemResult; + #[method(name = "system_version")] + fn system_version(&self) -> RpcResult; /// Get the chain's name. Given as a string identifier. - #[rpc(name = "system_chain")] - fn system_chain(&self) -> SystemResult; + #[method(name = "system_chain")] + fn system_chain(&self) -> RpcResult; /// Get the chain's type. - #[rpc(name = "system_chainType")] - fn system_type(&self) -> SystemResult; + #[method(name = "system_chainType")] + fn system_type(&self) -> RpcResult; /// Get a custom set of properties as a JSON object, defined in the chain spec. - #[rpc(name = "system_properties")] - fn system_properties(&self) -> SystemResult; + #[method(name = "system_properties")] + fn system_properties(&self) -> RpcResult; /// Return health status of the node. /// /// Node is considered healthy if it is: /// - connected to some peers (unless running in dev mode) /// - not performing a major sync - #[rpc(name = "system_health", returns = "Health")] - fn system_health(&self) -> Receiver; + #[method(name = "system_health")] + async fn system_health(&self) -> RpcResult; /// Returns the base58-encoded PeerId of the node. - #[rpc(name = "system_localPeerId", returns = "String")] - fn system_local_peer_id(&self) -> Receiver; + #[method(name = "system_localPeerId")] + async fn system_local_peer_id(&self) -> RpcResult; - /// Returns the multiaddresses that the local node is listening on + /// Returns the multi-addresses that the local node is listening on /// /// The addresses include a trailing `/p2p/` with the local PeerId, and are thus suitable to - /// be passed to `system_addReservedPeer` or as a bootnode address for example. - #[rpc(name = "system_localListenAddresses", returns = "Vec")] - fn system_local_listen_addresses(&self) -> Receiver>; + /// be passed to `addReservedPeer` or as a bootnode address for example. + #[method(name = "system_localListenAddresses")] + async fn system_local_listen_addresses(&self) -> RpcResult>; /// Returns currently connected peers - #[rpc(name = "system_peers", returns = "Vec>")] - fn system_peers(&self) -> BoxFuture>>>; + #[method(name = "system_peers")] + async fn system_peers(&self) -> RpcResult>>; /// Returns current state of the network. /// @@ -84,47 +80,44 @@ pub trait SystemApi { /// as its format might change at any time. // TODO: the future of this call is uncertain: https://github.com/paritytech/substrate/issues/1890 // https://github.com/paritytech/substrate/issues/5541 - #[rpc(name = "system_unstable_networkState", returns = "jsonrpc_core::Value")] - fn system_network_state(&self) -> BoxFuture>; + #[method(name = "system_unstable_networkState")] + async fn system_network_state(&self) -> RpcResult; /// Adds a reserved peer. Returns the empty string or an error. The string /// parameter should encode a `p2p` multiaddr. /// /// `/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV` /// is an example of a valid, passing multiaddr with PeerId attached. - #[rpc(name = "system_addReservedPeer", returns = "()")] - fn system_add_reserved_peer(&self, peer: String) -> BoxFuture>; + #[method(name = "system_addReservedPeer")] + async fn system_add_reserved_peer(&self, peer: String) -> RpcResult<()>; /// Remove a reserved peer. Returns the empty string or an error. The string /// should encode only the PeerId e.g. `QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV`. - #[rpc(name = "system_removeReservedPeer", returns = "()")] - fn system_remove_reserved_peer( - &self, - peer_id: String, - ) -> BoxFuture>; + #[method(name = "system_removeReservedPeer")] + async fn system_remove_reserved_peer(&self, peer_id: String) -> RpcResult<()>; /// Returns the list of reserved peers - #[rpc(name = "system_reservedPeers", returns = "Vec")] - fn system_reserved_peers(&self) -> Receiver>; + #[method(name = "system_reservedPeers")] + async fn system_reserved_peers(&self) -> RpcResult>; /// Returns the roles the node is running as. - #[rpc(name = "system_nodeRoles", returns = "Vec")] - fn system_node_roles(&self) -> Receiver>; + #[method(name = "system_nodeRoles")] + async fn system_node_roles(&self) -> RpcResult>; /// Returns the state of the syncing of the node: starting block, current best block, highest /// known block. - #[rpc(name = "system_syncState", returns = "SyncState")] - fn system_sync_state(&self) -> Receiver>; + #[method(name = "system_syncState")] + async fn system_sync_state(&self) -> RpcResult>; /// Adds the supplied directives to the current log filter /// /// The syntax is identical to the CLI `=`: /// /// `sync=debug,state=trace` - #[rpc(name = "system_addLogFilter", returns = "()")] - fn system_add_log_filter(&self, directives: String) -> Result<(), jsonrpc_core::Error>; + #[method(name = "system_addLogFilter")] + fn system_add_log_filter(&self, directives: String) -> RpcResult<()>; /// Resets the log filter to Substrate defaults - #[rpc(name = "system_resetLogFilter", returns = "()")] - fn system_reset_log_filter(&self) -> Result<(), jsonrpc_core::Error>; + #[method(name = "system_resetLogFilter")] + fn system_reset_log_filter(&self) -> RpcResult<()>; } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index 26a05a8263dc4..b494749ffd26a 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-rpc-server" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate RPC servers." readme = "README.md" @@ -13,13 +13,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures = "0.3.16" -jsonrpc-core = "18.0.0" -pubsub = { package = "jsonrpc-pubsub", version = "18.0.0" } -log = "0.4.8" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.9.0"} -serde_json = "1.0.68" -tokio = "1.10" -http = { package = "jsonrpc-http-server", version = "18.0.0" } -ipc = { package = "jsonrpc-ipc-server", version = "18.0.0" } -ws = { package = "jsonrpc-ws-server", version = "18.0.0" } +futures = "0.3.21" +jsonrpsee = { version = "0.16.2", features = ["server"] } +log = "0.4.17" +serde_json = "1.0.85" +tokio = { version = "1.22.0", features = ["parking_lot"] } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +tower-http = { version = "0.3.4", features = ["cors"] } +tower = "0.4.13" +http = "0.2.8" diff --git a/client/rpc-servers/src/lib.rs b/client/rpc-servers/src/lib.rs index 65ed6a914b19a..1fa2ba81d8672 100644 --- a/client/rpc-servers/src/lib.rs +++ b/client/rpc-servers/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,13 +20,22 @@ #![warn(missing_docs)] -mod middleware; - -use jsonrpc_core::{IoHandlerExtension, MetaIoHandler}; -use log::error; -use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; -use pubsub::PubSubMetadata; -use std::io; +use jsonrpsee::{ + server::{ + middleware::proxy_get_request::ProxyGetRequestLayer, AllowHosts, ServerBuilder, + ServerHandle, + }, + RpcModule, +}; +use std::{error::Error as StdError, net::SocketAddr}; + +pub use crate::middleware::RpcMetrics; +use http::header::HeaderValue; +pub use jsonrpsee::core::{ + id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, + traits::IdProvider, +}; +use tower_http::cors::{AllowOrigin, CorsLayer}; const MEGABYTE: usize = 1024 * 1024; @@ -36,180 +45,196 @@ pub const RPC_MAX_PAYLOAD_DEFAULT: usize = 15 * MEGABYTE; /// Default maximum number of connections for WS RPC servers. const WS_MAX_CONNECTIONS: usize = 100; -/// The RPC IoHandler containing all requested APIs. -pub type RpcHandler = pubsub::PubSubHandler; - -pub use middleware::{method_names, RpcMetrics, RpcMiddleware}; - -/// Construct rpc `IoHandler` -pub fn rpc_handler( - extension: impl IoHandlerExtension, - rpc_middleware: RpcMiddleware, -) -> RpcHandler { - let io_handler = MetaIoHandler::with_middleware(rpc_middleware); - let mut io = pubsub::PubSubHandler::new(io_handler); - extension.augment(&mut io); - - // add an endpoint to list all available methods. - let mut methods = io.iter().map(|x| x.0.clone()).collect::>(); - io.add_method("rpc_methods", { - methods.sort(); - let methods = serde_json::to_value(&methods) - .expect("Serialization of Vec is infallible; qed"); - - move |_| { - let methods = methods.clone(); - async move { - Ok(serde_json::json!({ - "version": 1, - "methods": methods, - })) - } - } - }); - io +/// Default maximum number subscriptions per connection for WS RPC servers. +const WS_MAX_SUBS_PER_CONN: usize = 1024; + +pub mod middleware; + +/// Type alias JSON-RPC server +pub type Server = ServerHandle; + +/// Server config. +#[derive(Debug, Clone)] +pub struct WsConfig { + /// Maximum connections. + pub max_connections: Option, + /// Maximum subscriptions per connection. + pub max_subs_per_conn: Option, + /// Maximum rpc request payload size. + pub max_payload_in_mb: Option, + /// Maximum rpc response payload size. + pub max_payload_out_mb: Option, } -/// RPC server-specific prometheus metrics. -#[derive(Debug, Clone, Default)] -pub struct ServerMetrics { - /// Number of sessions opened. - session_opened: Option>, - /// Number of sessions closed. - session_closed: Option>, +impl WsConfig { + // Deconstructs the config to get the finalized inner values. + // + // `Payload size` or `max subs per connection` bigger than u32::MAX will be truncated. + fn deconstruct(self) -> (u32, u32, u32, u32) { + let max_conns = self.max_connections.unwrap_or(WS_MAX_CONNECTIONS) as u32; + let max_payload_in_mb = payload_size_or_default(self.max_payload_in_mb) as u32; + let max_payload_out_mb = payload_size_or_default(self.max_payload_out_mb) as u32; + let max_subs_per_conn = self.max_subs_per_conn.unwrap_or(WS_MAX_SUBS_PER_CONN) as u32; + + (max_payload_in_mb, max_payload_out_mb, max_conns, max_subs_per_conn) + } } -impl ServerMetrics { - /// Create new WebSocket RPC server metrics. - pub fn new(registry: Option<&Registry>) -> Result { - registry - .map(|r| { - Ok(Self { - session_opened: register( - Counter::new( - "rpc_sessions_opened", - "Number of persistent RPC sessions opened", - )?, - r, - )? - .into(), - session_closed: register( - Counter::new( - "rpc_sessions_closed", - "Number of persistent RPC sessions closed", - )?, - r, - )? - .into(), - }) - }) - .unwrap_or_else(|| Ok(Default::default())) - } +/// Start HTTP server listening on given address. +pub async fn start_http( + addrs: [SocketAddr; 2], + cors: Option<&Vec>, + max_payload_in_mb: Option, + max_payload_out_mb: Option, + metrics: Option, + rpc_api: RpcModule, + rt: tokio::runtime::Handle, +) -> Result> { + let max_payload_in = payload_size_or_default(max_payload_in_mb) as u32; + let max_payload_out = payload_size_or_default(max_payload_out_mb) as u32; + let host_filter = hosts_filter(cors.is_some(), &addrs); + + let middleware = tower::ServiceBuilder::new() + // Proxy `GET /health` requests to internal `system_health` method. + .layer(ProxyGetRequestLayer::new("/health", "system_health")?) + .layer(try_into_cors(cors)?); + + let builder = ServerBuilder::new() + .max_request_body_size(max_payload_in) + .max_response_body_size(max_payload_out) + .set_host_filtering(host_filter) + .set_middleware(middleware) + .custom_tokio_runtime(rt) + .http_only(); + + let rpc_api = build_rpc_api(rpc_api); + let (handle, addr) = if let Some(metrics) = metrics { + let server = builder.set_logger(metrics).build(&addrs[..]).await?; + let addr = server.local_addr(); + (server.start(rpc_api)?, addr) + } else { + let server = builder.build(&addrs[..]).await?; + let addr = server.local_addr(); + (server.start(rpc_api)?, addr) + }; + + log::info!( + "Running JSON-RPC HTTP server: addr={}, allowed origins={}", + addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()), + format_cors(cors) + ); + + Ok(handle) } -/// Type alias for ipc server -pub type IpcServer = ipc::Server; -/// Type alias for http server -pub type HttpServer = http::Server; -/// Type alias for ws server -pub type WsServer = ws::Server; +/// Start a JSON-RPC server listening on given address that supports both HTTP and WS. +pub async fn start( + addrs: [SocketAddr; 2], + cors: Option<&Vec>, + ws_config: WsConfig, + metrics: Option, + rpc_api: RpcModule, + rt: tokio::runtime::Handle, + id_provider: Option>, +) -> Result> { + let (max_payload_in, max_payload_out, max_connections, max_subs_per_conn) = + ws_config.deconstruct(); + + let host_filter = hosts_filter(cors.is_some(), &addrs); + + let middleware = tower::ServiceBuilder::new() + // Proxy `GET /health` requests to internal `system_health` method. + .layer(ProxyGetRequestLayer::new("/health", "system_health")?) + .layer(try_into_cors(cors)?); + + let mut builder = ServerBuilder::new() + .max_request_body_size(max_payload_in) + .max_response_body_size(max_payload_out) + .max_connections(max_connections) + .max_subscriptions_per_connection(max_subs_per_conn) + .ping_interval(std::time::Duration::from_secs(30)) + .set_host_filtering(host_filter) + .set_middleware(middleware) + .custom_tokio_runtime(rt); + + if let Some(provider) = id_provider { + builder = builder.set_id_provider(provider); + } else { + builder = builder.set_id_provider(RandomStringIdProvider::new(16)); + }; + + let rpc_api = build_rpc_api(rpc_api); + let (handle, addr) = if let Some(metrics) = metrics { + let server = builder.set_logger(metrics).build(&addrs[..]).await?; + let addr = server.local_addr(); + (server.start(rpc_api)?, addr) + } else { + let server = builder.build(&addrs[..]).await?; + let addr = server.local_addr(); + (server.start(rpc_api)?, addr) + }; + + log::info!( + "Running JSON-RPC WS server: addr={}, allowed origins={}", + addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()), + format_cors(cors) + ); + + Ok(handle) +} -impl ws::SessionStats for ServerMetrics { - fn open_session(&self, _id: ws::SessionId) { - self.session_opened.as_ref().map(|m| m.inc()); - } +fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModule { + let mut available_methods = rpc_api.method_names().collect::>(); + available_methods.sort(); - fn close_session(&self, _id: ws::SessionId) { - self.session_closed.as_ref().map(|m| m.inc()); - } -} + rpc_api + .register_method("rpc_methods", move |_, _| { + Ok(serde_json::json!({ + "methods": available_methods, + })) + }) + .expect("infallible all other methods have their own address space; qed"); -/// Start HTTP server listening on given address. -pub fn start_http( - addr: &std::net::SocketAddr, - cors: Option<&Vec>, - io: RpcHandler, - maybe_max_payload_mb: Option, - tokio_handle: tokio::runtime::Handle, -) -> io::Result { - let max_request_body_size = maybe_max_payload_mb - .map(|mb| mb.saturating_mul(MEGABYTE)) - .unwrap_or(RPC_MAX_PAYLOAD_DEFAULT); - - http::ServerBuilder::new(io) - .threads(1) - .event_loop_executor(tokio_handle) - .health_api(("/health", "system_health")) - .allowed_hosts(hosts_filtering(cors.is_some())) - .rest_api(if cors.is_some() { http::RestApi::Secure } else { http::RestApi::Unsecure }) - .cors(map_cors::(cors)) - .max_request_body_size(max_request_body_size) - .start_http(addr) + rpc_api } -/// Start IPC server listening on given path. -pub fn start_ipc( - addr: &str, - io: RpcHandler, - server_metrics: ServerMetrics, -) -> io::Result { - let builder = ipc::ServerBuilder::new(io); - #[cfg(target_os = "unix")] - builder.set_security_attributes({ - let security_attributes = ipc::SecurityAttributes::empty(); - security_attributes.set_mode(0o600)?; - security_attributes - }); - builder.session_stats(server_metrics).start(addr) +fn payload_size_or_default(size_mb: Option) -> usize { + size_mb.map_or(RPC_MAX_PAYLOAD_DEFAULT, |mb| mb.saturating_mul(MEGABYTE)) } -/// Start WS server listening on given address. -pub fn start_ws< - M: pubsub::PubSubMetadata + From>, ->( - addr: &std::net::SocketAddr, - max_connections: Option, - cors: Option<&Vec>, - io: RpcHandler, - maybe_max_payload_mb: Option, - server_metrics: ServerMetrics, - tokio_handle: tokio::runtime::Handle, -) -> io::Result { - let rpc_max_payload = maybe_max_payload_mb - .map(|mb| mb.saturating_mul(MEGABYTE)) - .unwrap_or(RPC_MAX_PAYLOAD_DEFAULT); - ws::ServerBuilder::with_meta_extractor(io, |context: &ws::RequestContext| { - context.sender().into() - }) - .event_loop_executor(tokio_handle) - .max_payload(rpc_max_payload) - .max_connections(max_connections.unwrap_or(WS_MAX_CONNECTIONS)) - .allowed_origins(map_cors(cors)) - .allowed_hosts(hosts_filtering(cors.is_some())) - .session_stats(server_metrics) - .start(addr) - .map_err(|err| match err { - ws::Error::Io(io) => io, - ws::Error::ConnectionClosed => io::ErrorKind::BrokenPipe.into(), - e => { - error!("{}", e); - io::ErrorKind::Other.into() - }, - }) +fn hosts_filter(enabled: bool, addrs: &[SocketAddr]) -> AllowHosts { + if enabled { + // NOTE The listening addresses are whitelisted by default. + let mut hosts = Vec::with_capacity(addrs.len() * 2); + for addr in addrs { + hosts.push(format!("localhost:{}", addr.port()).into()); + hosts.push(format!("127.0.0.1:{}", addr.port()).into()); + } + AllowHosts::Only(hosts) + } else { + AllowHosts::Any + } } -fn map_cors From<&'a str>>(cors: Option<&Vec>) -> http::DomainsValidation { - cors.map(|x| x.iter().map(AsRef::as_ref).map(Into::into).collect::>()) - .into() +fn try_into_cors( + maybe_cors: Option<&Vec>, +) -> Result> { + if let Some(cors) = maybe_cors { + let mut list = Vec::new(); + for origin in cors { + list.push(HeaderValue::from_str(origin)?); + } + Ok(CorsLayer::new().allow_origin(AllowOrigin::list(list))) + } else { + // allow all cors + Ok(CorsLayer::permissive()) + } } -fn hosts_filtering(enable: bool) -> http::DomainsValidation { - if enable { - // NOTE The listening address is whitelisted by default. - // Setting an empty vector here enables the validation - // and allows only the listening address. - http::DomainsValidation::AllowOnly(vec![]) +fn format_cors(maybe_cors: Option<&Vec>) -> String { + if let Some(cors) = maybe_cors { + format!("{:?}", cors) } else { - http::DomainsValidation::Disabled + format!("{:?}", ["*"]) } } diff --git a/client/rpc-servers/src/middleware.rs b/client/rpc-servers/src/middleware.rs index 00532b0e8d661..1c25ac1dfd1b3 100644 --- a/client/rpc-servers/src/middleware.rs +++ b/client/rpc-servers/src/middleware.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,85 +16,122 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Middleware for RPC requests. +//! RPC middleware to collect prometheus metrics on RPC calls. -use std::collections::HashSet; - -use jsonrpc_core::{FutureOutput, FutureResponse, Metadata, Middleware as RequestMiddleware}; +use jsonrpsee::server::logger::{HttpRequest, Logger, MethodKind, Params, TransportProtocol}; use prometheus_endpoint::{ - register, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, U64, + register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, + U64, }; - -use futures::{future::Either, Future, FutureExt}; -use pubsub::PubSubMetadata; - -use crate::RpcHandler; - -/// Metrics for RPC middleware +use std::net::SocketAddr; + +/// Histogram time buckets in microseconds. +const HISTOGRAM_BUCKETS: [f64; 11] = [ + 5.0, + 25.0, + 100.0, + 500.0, + 1_000.0, + 2_500.0, + 10_000.0, + 25_000.0, + 100_000.0, + 1_000_000.0, + 10_000_000.0, +]; + +/// Metrics for RPC middleware storing information about the number of requests started/completed, +/// calls started/completed and their timings. #[derive(Debug, Clone)] pub struct RpcMetrics { + /// Number of RPC requests received since the server started. requests_started: CounterVec, + /// Number of RPC requests completed since the server started. requests_finished: CounterVec, + /// Histogram over RPC execution times. calls_time: HistogramVec, + /// Number of calls started. calls_started: CounterVec, + /// Number of calls completed. calls_finished: CounterVec, + /// Number of Websocket sessions opened. + ws_sessions_opened: Option>, + /// Number of Websocket sessions closed. + ws_sessions_closed: Option>, } impl RpcMetrics { /// Create an instance of metrics pub fn new(metrics_registry: Option<&Registry>) -> Result, PrometheusError> { - if let Some(r) = metrics_registry { + if let Some(metrics_registry) = metrics_registry { Ok(Some(Self { requests_started: register( CounterVec::new( Opts::new( - "rpc_requests_started", + "substrate_rpc_requests_started", "Number of RPC requests (not calls) received by the server.", ), &["protocol"], )?, - r, + metrics_registry, )?, requests_finished: register( CounterVec::new( Opts::new( - "rpc_requests_finished", + "substrate_rpc_requests_finished", "Number of RPC requests (not calls) processed by the server.", ), &["protocol"], )?, - r, + metrics_registry, )?, calls_time: register( HistogramVec::new( HistogramOpts::new( - "rpc_calls_time", + "substrate_rpc_calls_time", "Total time [μs] of processed RPC calls", - ), + ) + .buckets(HISTOGRAM_BUCKETS.to_vec()), &["protocol", "method"], )?, - r, + metrics_registry, )?, calls_started: register( CounterVec::new( Opts::new( - "rpc_calls_started", + "substrate_rpc_calls_started", "Number of received RPC calls (unique un-batched requests)", ), &["protocol", "method"], )?, - r, + metrics_registry, )?, calls_finished: register( CounterVec::new( Opts::new( - "rpc_calls_finished", + "substrate_rpc_calls_finished", "Number of processed RPC calls (unique un-batched requests)", ), &["protocol", "method", "is_error"], )?, - r, + metrics_registry, )?, + ws_sessions_opened: register( + Counter::new( + "substrate_rpc_sessions_opened", + "Number of persistent RPC sessions opened", + )?, + metrics_registry, + )? + .into(), + ws_sessions_closed: register( + Counter::new( + "substrate_rpc_sessions_closed", + "Number of persistent RPC sessions closed", + )?, + metrics_registry, + )? + .into(), })) } else { Ok(None) @@ -102,143 +139,86 @@ impl RpcMetrics { } } -/// Instantiates a dummy `IoHandler` given a builder function to extract supported method names. -pub fn method_names(gen_handler: F) -> Result, E> -where - F: FnOnce(RpcMiddleware) -> Result, E>, - M: PubSubMetadata, -{ - let io = gen_handler(RpcMiddleware::new(None, HashSet::new(), "dummy"))?; - Ok(io.iter().map(|x| x.0.clone()).collect()) -} +impl Logger for RpcMetrics { + type Instant = std::time::Instant; -/// Middleware for RPC calls -pub struct RpcMiddleware { - metrics: Option, - known_rpc_method_names: HashSet, - transport_label: String, -} + fn on_connect( + &self, + _remote_addr: SocketAddr, + _request: &HttpRequest, + transport: TransportProtocol, + ) { + if let TransportProtocol::WebSocket = transport { + self.ws_sessions_opened.as_ref().map(|counter| counter.inc()); + } + } -impl RpcMiddleware { - /// Create an instance of middleware. - /// - /// - `metrics`: Will be used to report statistics. - /// - `transport_label`: The label that is used when reporting the statistics. - pub fn new( - metrics: Option, - known_rpc_method_names: HashSet, - transport_label: &str, - ) -> Self { - RpcMiddleware { metrics, known_rpc_method_names, transport_label: transport_label.into() } + fn on_request(&self, transport: TransportProtocol) -> Self::Instant { + let transport_label = transport_label_str(transport); + let now = std::time::Instant::now(); + self.requests_started.with_label_values(&[transport_label]).inc(); + now } -} -impl RequestMiddleware for RpcMiddleware { - type Future = FutureResponse; - type CallFuture = FutureOutput; + fn on_call(&self, name: &str, params: Params, kind: MethodKind, transport: TransportProtocol) { + let transport_label = transport_label_str(transport); + log::trace!( + target: "rpc_metrics", + "[{}] on_call name={} params={:?} kind={}", + transport_label, + name, + params, + kind, + ); + self.calls_started.with_label_values(&[transport_label, name]).inc(); + } - fn on_request( + fn on_result( &self, - request: jsonrpc_core::Request, - meta: M, - next: F, - ) -> Either - where - F: Fn(jsonrpc_core::Request, M) -> X + Send + Sync, - X: Future> + Send + 'static, - { - let metrics = self.metrics.clone(); - let transport_label = self.transport_label.clone(); - if let Some(ref metrics) = metrics { - metrics.requests_started.with_label_values(&[transport_label.as_str()]).inc(); - } - let r = next(request, meta); - Either::Left( - async move { - let r = r.await; - if let Some(ref metrics) = metrics { - metrics.requests_finished.with_label_values(&[transport_label.as_str()]).inc(); - } - r - } - .boxed(), - ) + name: &str, + success: bool, + started_at: Self::Instant, + transport: TransportProtocol, + ) { + let transport_label = transport_label_str(transport); + let micros = started_at.elapsed().as_micros(); + log::debug!( + target: "rpc_metrics", + "[{}] {} call took {} μs", + transport_label, + name, + micros, + ); + self.calls_time.with_label_values(&[transport_label, name]).observe(micros as _); + + self.calls_finished + .with_label_values(&[ + transport_label, + name, + // the label "is_error", so `success` should be regarded as false + // and vice-versa to be registrered correctly. + if success { "false" } else { "true" }, + ]) + .inc(); } - fn on_call( - &self, - call: jsonrpc_core::Call, - meta: M, - next: F, - ) -> Either - where - F: Fn(jsonrpc_core::Call, M) -> X + Send + Sync, - X: Future> + Send + 'static, - { - let start = std::time::Instant::now(); - let name = call_name(&call, &self.known_rpc_method_names).to_owned(); - let metrics = self.metrics.clone(); - let transport_label = self.transport_label.clone(); - log::trace!(target: "rpc_metrics", "[{}] {} call: {:?}", transport_label, name, &call); - if let Some(ref metrics) = metrics { - metrics - .calls_started - .with_label_values(&[transport_label.as_str(), name.as_str()]) - .inc(); - } - let r = next(call, meta); - Either::Left( - async move { - let r = r.await; - let micros = start.elapsed().as_micros(); - if let Some(ref metrics) = metrics { - metrics - .calls_time - .with_label_values(&[transport_label.as_str(), name.as_str()]) - .observe(micros as _); - metrics - .calls_finished - .with_label_values(&[ - transport_label.as_str(), - name.as_str(), - if is_success(&r) { "true" } else { "false" }, - ]) - .inc(); - } - log::debug!( - target: "rpc_metrics", - "[{}] {} call took {} μs", - transport_label, - name, - micros, - ); - r - } - .boxed(), - ) + fn on_response(&self, result: &str, started_at: Self::Instant, transport: TransportProtocol) { + let transport_label = transport_label_str(transport); + log::trace!(target: "rpc_metrics", "[{}] on_response started_at={:?}", transport_label, started_at); + log::trace!(target: "rpc_metrics::extra", "[{}] result={:?}", transport_label, result); + self.requests_finished.with_label_values(&[transport_label]).inc(); } -} -fn call_name<'a>(call: &'a jsonrpc_core::Call, known_methods: &HashSet) -> &'a str { - // To prevent bloating metric with all invalid method names we filter them out here. - let only_known = |method: &'a String| { - if known_methods.contains(method) { - method.as_str() - } else { - "invalid method" + fn on_disconnect(&self, _remote_addr: SocketAddr, transport: TransportProtocol) { + if let TransportProtocol::WebSocket = transport { + self.ws_sessions_closed.as_ref().map(|counter| counter.inc()); } - }; - - match call { - jsonrpc_core::Call::Invalid { .. } => "invalid call", - jsonrpc_core::Call::MethodCall(ref call) => only_known(&call.method), - jsonrpc_core::Call::Notification(ref notification) => only_known(¬ification.method), } } -fn is_success(output: &Option) -> bool { - match output { - Some(jsonrpc_core::Output::Success(..)) => true, - _ => false, +fn transport_label_str(t: TransportProtocol) -> &'static str { + match t { + TransportProtocol::Http => "http", + TransportProtocol::WebSocket => "ws", } } diff --git a/client/rpc-spec-v2/Cargo.toml b/client/rpc-spec-v2/Cargo.toml new file mode 100644 index 0000000000000..930aeb4bd8956 --- /dev/null +++ b/client/rpc-spec-v2/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "sc-rpc-spec-v2" +version = "0.10.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Substrate RPC interface v2." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +# Internal chain structures for "chain_spec". +sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } +# Pool for submitting extrinsics required by "transaction" +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +codec = { package = "parity-scale-codec", version = "3.0.0" } +thiserror = "1.0" +serde = "1.0" +hex = "0.4" +futures = "0.3.21" + +[dev-dependencies] +serde_json = "1.0" +tokio = { version = "1.22.0", features = ["macros"] } diff --git a/client/rpc-spec-v2/README.md b/client/rpc-spec-v2/README.md new file mode 100644 index 0000000000000..e860e0c2334da --- /dev/null +++ b/client/rpc-spec-v2/README.md @@ -0,0 +1,5 @@ +Substrate RPC interfaces. + +A collection of RPC methods and subscriptions supported by all substrate clients. + +License: GPL-3.0-or-later WITH Classpath-exception-2.0 \ No newline at end of file diff --git a/client/rpc-spec-v2/src/chain_spec/api.rs b/client/rpc-spec-v2/src/chain_spec/api.rs new file mode 100644 index 0000000000000..dfe2d76de6501 --- /dev/null +++ b/client/rpc-spec-v2/src/chain_spec/api.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! API trait of the chain spec. + +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use sc_chain_spec::Properties; + +#[rpc(client, server)] +pub trait ChainSpecApi { + /// Get the chain name, as present in the chain specification. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[method(name = "chainSpec_unstable_chainName")] + fn chain_spec_unstable_chain_name(&self) -> RpcResult; + + /// Get the chain's genesis hash. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[method(name = "chainSpec_unstable_genesisHash")] + fn chain_spec_unstable_genesis_hash(&self) -> RpcResult; + + /// Get the properties of the chain, as present in the chain specification. + /// + /// # Note + /// + /// The json whitespaces are not guaranteed to persist. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[method(name = "chainSpec_unstable_properties")] + fn chain_spec_unstable_properties(&self) -> RpcResult; +} diff --git a/client/rpc-spec-v2/src/chain_spec/chain_spec.rs b/client/rpc-spec-v2/src/chain_spec/chain_spec.rs new file mode 100644 index 0000000000000..90d05f1d9d41d --- /dev/null +++ b/client/rpc-spec-v2/src/chain_spec/chain_spec.rs @@ -0,0 +1,60 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! API implementation for the specification of a chain. + +use crate::chain_spec::api::ChainSpecApiServer; +use jsonrpsee::core::RpcResult; +use sc_chain_spec::Properties; + +/// An API for chain spec RPC calls. +pub struct ChainSpec { + /// The name of the chain. + name: String, + /// The hexadecimal encoded hash of the genesis block. + genesis_hash: String, + /// Chain properties. + properties: Properties, +} + +impl ChainSpec { + /// Creates a new [`ChainSpec`]. + pub fn new>( + name: String, + genesis_hash: Hash, + properties: Properties, + ) -> Self { + let genesis_hash = format!("0x{}", hex::encode(genesis_hash)); + + Self { name, properties, genesis_hash } + } +} + +impl ChainSpecApiServer for ChainSpec { + fn chain_spec_unstable_chain_name(&self) -> RpcResult { + Ok(self.name.clone()) + } + + fn chain_spec_unstable_genesis_hash(&self) -> RpcResult { + Ok(self.genesis_hash.clone()) + } + + fn chain_spec_unstable_properties(&self) -> RpcResult { + Ok(self.properties.clone()) + } +} diff --git a/client/rpc-spec-v2/src/chain_spec/mod.rs b/client/rpc-spec-v2/src/chain_spec/mod.rs new file mode 100644 index 0000000000000..cd4fcf246f603 --- /dev/null +++ b/client/rpc-spec-v2/src/chain_spec/mod.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Substrate chain specification API. +//! +//! The *chain spec* (short for *chain specification*) allows inspecting the content of +//! the specification of the chain that a JSON-RPC server is targeting. +//! +//! The values returned by the API are guaranteed to never change during the lifetime of the +//! JSON-RPC server. +//! +//! # Note +//! +//! Methods are prefixed by `chainSpec`. + +#[cfg(test)] +mod tests; + +pub mod api; +pub mod chain_spec; + +pub use api::ChainSpecApiServer; +pub use chain_spec::ChainSpec; diff --git a/client/rpc-spec-v2/src/chain_spec/tests.rs b/client/rpc-spec-v2/src/chain_spec/tests.rs new file mode 100644 index 0000000000000..6f662ba422bc4 --- /dev/null +++ b/client/rpc-spec-v2/src/chain_spec/tests.rs @@ -0,0 +1,61 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; +use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule}; +use sc_chain_spec::Properties; + +const CHAIN_NAME: &'static str = "TEST_CHAIN_NAME"; +const CHAIN_GENESIS: [u8; 32] = [0; 32]; +const CHAIN_PROPERTIES: &'static str = r#"{"three": "123", "one": 1, "two": 12}"#; + +fn api() -> RpcModule { + ChainSpec::new( + CHAIN_NAME.to_string(), + CHAIN_GENESIS, + serde_json::from_str(CHAIN_PROPERTIES).unwrap(), + ) + .into_rpc() +} + +#[tokio::test] +async fn chain_spec_chain_name_works() { + let name = api() + .call::<_, String>("chainSpec_unstable_chainName", EmptyParams::new()) + .await + .unwrap(); + assert_eq!(name, CHAIN_NAME); +} + +#[tokio::test] +async fn chain_spec_genesis_hash_works() { + let genesis = api() + .call::<_, String>("chainSpec_unstable_genesisHash", EmptyParams::new()) + .await + .unwrap(); + assert_eq!(genesis, format!("0x{}", hex::encode(CHAIN_GENESIS))); +} + +#[tokio::test] +async fn chain_spec_properties_works() { + let properties = api() + .call::<_, Properties>("chainSpec_unstable_properties", EmptyParams::new()) + .await + .unwrap(); + assert_eq!(properties, serde_json::from_str(CHAIN_PROPERTIES).unwrap()); +} diff --git a/client/rpc-spec-v2/src/lib.rs b/client/rpc-spec-v2/src/lib.rs new file mode 100644 index 0000000000000..f4b9d2f95bf97 --- /dev/null +++ b/client/rpc-spec-v2/src/lib.rs @@ -0,0 +1,30 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Substrate JSON-RPC interface v2. +//! +//! Specification [document](https://paritytech.github.io/json-rpc-interface-spec/). + +#![warn(missing_docs)] +#![deny(unused_crate_dependencies)] + +pub mod chain_spec; +pub mod transaction; + +/// Task executor that is being used by RPC subscriptions. +pub type SubscriptionTaskExecutor = std::sync::Arc; diff --git a/client/rpc-spec-v2/src/transaction/api.rs b/client/rpc-spec-v2/src/transaction/api.rs new file mode 100644 index 0000000000000..2f0c799f1cc19 --- /dev/null +++ b/client/rpc-spec-v2/src/transaction/api.rs @@ -0,0 +1,37 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! API trait for transactions. + +use crate::transaction::event::TransactionEvent; +use jsonrpsee::proc_macros::rpc; +use sp_core::Bytes; + +#[rpc(client, server)] +pub trait TransactionApi { + /// Submit an extrinsic to watch. + /// + /// See [`TransactionEvent`](crate::transaction::event::TransactionEvent) for details on + /// transaction life cycle. + #[subscription( + name = "transaction_unstable_submitAndWatch" => "transaction_unstable_submitExtrinsic", + unsubscribe = "transaction_unstable_unwatch", + item = TransactionEvent, + )] + fn submit_and_watch(&self, bytes: Bytes); +} diff --git a/client/rpc-spec-v2/src/transaction/error.rs b/client/rpc-spec-v2/src/transaction/error.rs new file mode 100644 index 0000000000000..72a5959992f9e --- /dev/null +++ b/client/rpc-spec-v2/src/transaction/error.rs @@ -0,0 +1,100 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Transaction RPC errors. +//! +//! Errors are interpreted as transaction events for subscriptions. + +use crate::transaction::event::{TransactionError, TransactionEvent}; +use sc_transaction_pool_api::error::Error as PoolError; +use sp_runtime::transaction_validity::InvalidTransaction; + +/// Transaction RPC errors. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Transaction pool error. + #[error("Transaction pool error: {}", .0)] + Pool(#[from] PoolError), + /// Verification error. + #[error("Extrinsic verification error: {}", .0)] + Verification(Box), +} + +impl From for TransactionEvent { + fn from(e: Error) -> Self { + match e { + Error::Verification(e) => TransactionEvent::Invalid(TransactionError { + error: format!("Verification error: {}", e), + }), + Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Custom(e))) => + TransactionEvent::Invalid(TransactionError { + error: format!("Invalid transaction with custom error: {}", e), + }), + Error::Pool(PoolError::InvalidTransaction(e)) => { + let msg: &str = e.into(); + TransactionEvent::Invalid(TransactionError { + error: format!("Invalid transaction: {}", msg), + }) + }, + Error::Pool(PoolError::UnknownTransaction(e)) => { + let msg: &str = e.into(); + TransactionEvent::Invalid(TransactionError { + error: format!("Unknown transaction validity: {}", msg), + }) + }, + Error::Pool(PoolError::TemporarilyBanned) => + TransactionEvent::Invalid(TransactionError { + error: "Transaction is temporarily banned".into(), + }), + Error::Pool(PoolError::AlreadyImported(_)) => + TransactionEvent::Invalid(TransactionError { + error: "Transaction is already imported".into(), + }), + Error::Pool(PoolError::TooLowPriority { old, new }) => + TransactionEvent::Invalid(TransactionError { + error: format!( + "The priority of the transactin is too low (pool {} > current {})", + old, new + ), + }), + Error::Pool(PoolError::CycleDetected) => TransactionEvent::Invalid(TransactionError { + error: "The transaction contains a cyclic dependency".into(), + }), + Error::Pool(PoolError::ImmediatelyDropped) => + TransactionEvent::Invalid(TransactionError { + error: "The transaction could not enter the pool because of the limit".into(), + }), + Error::Pool(PoolError::Unactionable) => TransactionEvent::Invalid(TransactionError { + error: "Transaction cannot be propagated and the local node does not author blocks" + .into(), + }), + Error::Pool(PoolError::NoTagsProvided) => TransactionEvent::Invalid(TransactionError { + error: "Transaction does not provide any tags, so the pool cannot identify it" + .into(), + }), + Error::Pool(PoolError::InvalidBlockId(_)) => + TransactionEvent::Invalid(TransactionError { + error: "The provided block ID is not valid".into(), + }), + Error::Pool(PoolError::RejectedFutureTransaction) => + TransactionEvent::Invalid(TransactionError { + error: "The pool is not accepting future transactions".into(), + }), + } + } +} diff --git a/client/rpc-spec-v2/src/transaction/event.rs b/client/rpc-spec-v2/src/transaction/event.rs new file mode 100644 index 0000000000000..3c75eaff10fd4 --- /dev/null +++ b/client/rpc-spec-v2/src/transaction/event.rs @@ -0,0 +1,353 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! The transaction's event returned as json compatible object. + +use serde::{Deserialize, Serialize}; + +/// The transaction was broadcasted to a number of peers. +/// +/// # Note +/// +/// The RPC does not guarantee that the peers have received the +/// transaction. +/// +/// When the number of peers is zero, the event guarantees that +/// shutting down the local node will lead to the transaction +/// not being included in the chain. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionBroadcasted { + /// The number of peers the transaction was broadcasted to. + #[serde(with = "as_string")] + pub num_peers: usize, +} + +/// The transaction was included in a block of the chain. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionBlock { + /// The hash of the block the transaction was included into. + pub hash: Hash, + /// The index (zero-based) of the transaction within the body of the block. + #[serde(with = "as_string")] + pub index: usize, +} + +/// The transaction could not be processed due to an error. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionError { + /// Reason of the error. + pub error: String, +} + +/// The transaction was dropped because of exceeding limits. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionDropped { + /// True if the transaction was broadcasted to other peers and + /// may still be included in the block. + pub broadcasted: bool, + /// Reason of the event. + pub error: String, +} + +/// Possible transaction status events. +/// +/// The status events can be grouped based on their kinds as: +/// +/// 1. Runtime validated the transaction: +/// - `Validated` +/// +/// 2. Inside the `Ready` queue: +/// - `Broadcast` +/// +/// 3. Leaving the pool: +/// - `BestChainBlockIncluded` +/// - `Invalid` +/// +/// 4. Block finalized: +/// - `Finalized` +/// +/// 5. At any time: +/// - `Dropped` +/// - `Error` +/// +/// The subscription's stream is considered finished whenever the following events are +/// received: `Finalized`, `Error`, `Invalid` or `Dropped`. However, the user is allowed +/// to unsubscribe at any moment. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// We need to manually specify the trait bounds for the `Hash` trait to ensure `into` and +// `from` still work. +#[serde(bound( + serialize = "Hash: Serialize + Clone", + deserialize = "Hash: Deserialize<'de> + Clone" +))] +#[serde(into = "TransactionEventIR", from = "TransactionEventIR")] +pub enum TransactionEvent { + /// The transaction was validated by the runtime. + Validated, + /// The transaction was broadcasted to a number of peers. + Broadcasted(TransactionBroadcasted), + /// The transaction was included in a best block of the chain. + /// + /// # Note + /// + /// This may contain `None` if the block is no longer a best + /// block of the chain. + BestChainBlockIncluded(Option>), + /// The transaction was included in a finalized block. + Finalized(TransactionBlock), + /// The transaction could not be processed due to an error. + Error(TransactionError), + /// The transaction is marked as invalid. + Invalid(TransactionError), + /// The client was not capable of keeping track of this transaction. + Dropped(TransactionDropped), +} + +/// Intermediate representation (IR) for the transaction events +/// that handles block events only. +/// +/// The block events require a JSON compatible interpretation similar to: +/// +/// ```json +/// { event: "EVENT", block: { hash: "0xFF", index: 0 } } +/// ``` +/// +/// This IR is introduced to circumvent that the block events need to +/// be serialized/deserialized with "tag" and "content", while other +/// events only require "tag". +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "event", content = "block")] +enum TransactionEventBlockIR { + /// The transaction was included in the best block of the chain. + BestChainBlockIncluded(Option>), + /// The transaction was included in a finalized block of the chain. + Finalized(TransactionBlock), +} + +/// Intermediate representation (IR) for the transaction events +/// that handles non-block events only. +/// +/// The non-block events require a JSON compatible interpretation similar to: +/// +/// ```json +/// { event: "EVENT", num_peers: 0 } +/// ``` +/// +/// This IR is introduced to circumvent that the block events need to +/// be serialized/deserialized with "tag" and "content", while other +/// events only require "tag". +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "event")] +enum TransactionEventNonBlockIR { + Validated, + Broadcasted(TransactionBroadcasted), + Error(TransactionError), + Invalid(TransactionError), + Dropped(TransactionDropped), +} + +/// Intermediate representation (IR) used for serialization/deserialization of the +/// [`TransactionEvent`] in a JSON compatible format. +/// +/// Serde cannot mix `#[serde(tag = "event")]` with `#[serde(tag = "event", content = "block")]` +/// for specific enum variants. Therefore, this IR is introduced to circumvent this +/// restriction, while exposing a simplified [`TransactionEvent`] for users of the +/// rust ecosystem. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(bound(serialize = "Hash: Serialize", deserialize = "Hash: Deserialize<'de>"))] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +enum TransactionEventIR { + Block(TransactionEventBlockIR), + NonBlock(TransactionEventNonBlockIR), +} + +impl From> for TransactionEventIR { + fn from(value: TransactionEvent) -> Self { + match value { + TransactionEvent::Validated => + TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Validated), + TransactionEvent::Broadcasted(event) => + TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Broadcasted(event)), + TransactionEvent::BestChainBlockIncluded(event) => + TransactionEventIR::Block(TransactionEventBlockIR::BestChainBlockIncluded(event)), + TransactionEvent::Finalized(event) => + TransactionEventIR::Block(TransactionEventBlockIR::Finalized(event)), + TransactionEvent::Error(event) => + TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Error(event)), + TransactionEvent::Invalid(event) => + TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Invalid(event)), + TransactionEvent::Dropped(event) => + TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Dropped(event)), + } + } +} + +impl From> for TransactionEvent { + fn from(value: TransactionEventIR) -> Self { + match value { + TransactionEventIR::NonBlock(status) => match status { + TransactionEventNonBlockIR::Validated => TransactionEvent::Validated, + TransactionEventNonBlockIR::Broadcasted(event) => + TransactionEvent::Broadcasted(event), + TransactionEventNonBlockIR::Error(event) => TransactionEvent::Error(event), + TransactionEventNonBlockIR::Invalid(event) => TransactionEvent::Invalid(event), + TransactionEventNonBlockIR::Dropped(event) => TransactionEvent::Dropped(event), + }, + TransactionEventIR::Block(block) => match block { + TransactionEventBlockIR::Finalized(event) => TransactionEvent::Finalized(event), + TransactionEventBlockIR::BestChainBlockIncluded(event) => + TransactionEvent::BestChainBlockIncluded(event), + }, + } + } +} + +/// Serialize and deserialize helper as string. +mod as_string { + use super::*; + use serde::{Deserializer, Serializer}; + + pub fn serialize(data: &usize, serializer: S) -> Result { + data.to_string().serialize(serializer) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + String::deserialize(deserializer)? + .parse() + .map_err(|e| serde::de::Error::custom(format!("Parsing failed: {}", e))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::H256; + + #[test] + fn validated_event() { + let event: TransactionEvent<()> = TransactionEvent::Validated; + let ser = serde_json::to_string(&event).unwrap(); + + let exp = r#"{"event":"validated"}"#; + assert_eq!(ser, exp); + + let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn broadcasted_event() { + let event: TransactionEvent<()> = + TransactionEvent::Broadcasted(TransactionBroadcasted { num_peers: 2 }); + let ser = serde_json::to_string(&event).unwrap(); + + let exp = r#"{"event":"broadcasted","numPeers":"2"}"#; + assert_eq!(ser, exp); + + let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn best_chain_event() { + let event: TransactionEvent<()> = TransactionEvent::BestChainBlockIncluded(None); + let ser = serde_json::to_string(&event).unwrap(); + + let exp = r#"{"event":"bestChainBlockIncluded","block":null}"#; + assert_eq!(ser, exp); + + let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + + let event: TransactionEvent = + TransactionEvent::BestChainBlockIncluded(Some(TransactionBlock { + hash: H256::from_low_u64_be(1), + index: 2, + })); + let ser = serde_json::to_string(&event).unwrap(); + + let exp = r#"{"event":"bestChainBlockIncluded","block":{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","index":"2"}}"#; + assert_eq!(ser, exp); + + let event_dec: TransactionEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn finalized_event() { + let event: TransactionEvent = TransactionEvent::Finalized(TransactionBlock { + hash: H256::from_low_u64_be(1), + index: 10, + }); + let ser = serde_json::to_string(&event).unwrap(); + + let exp = r#"{"event":"finalized","block":{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","index":"10"}}"#; + assert_eq!(ser, exp); + + let event_dec: TransactionEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn error_event() { + let event: TransactionEvent<()> = + TransactionEvent::Error(TransactionError { error: "abc".to_string() }); + let ser = serde_json::to_string(&event).unwrap(); + + let exp = r#"{"event":"error","error":"abc"}"#; + assert_eq!(ser, exp); + + let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn invalid_event() { + let event: TransactionEvent<()> = + TransactionEvent::Invalid(TransactionError { error: "abc".to_string() }); + let ser = serde_json::to_string(&event).unwrap(); + + let exp = r#"{"event":"invalid","error":"abc"}"#; + assert_eq!(ser, exp); + + let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn dropped_event() { + let event: TransactionEvent<()> = TransactionEvent::Dropped(TransactionDropped { + broadcasted: true, + error: "abc".to_string(), + }); + let ser = serde_json::to_string(&event).unwrap(); + + let exp = r#"{"event":"dropped","broadcasted":true,"error":"abc"}"#; + assert_eq!(ser, exp); + + let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } +} diff --git a/client/rpc-spec-v2/src/transaction/mod.rs b/client/rpc-spec-v2/src/transaction/mod.rs new file mode 100644 index 0000000000000..bb983894a428c --- /dev/null +++ b/client/rpc-spec-v2/src/transaction/mod.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Substrate transaction API. +//! +//! The transaction methods allow submitting a transaction and subscribing to +//! its status updates generated by the chain. +//! +//! # Note +//! +//! Methods are prefixed by `transaction`. + +pub mod api; +pub mod error; +pub mod event; +pub mod transaction; + +pub use api::TransactionApiServer; +pub use event::{ + TransactionBlock, TransactionBroadcasted, TransactionDropped, TransactionError, + TransactionEvent, +}; +pub use transaction::Transaction; diff --git a/client/rpc-spec-v2/src/transaction/transaction.rs b/client/rpc-spec-v2/src/transaction/transaction.rs new file mode 100644 index 0000000000000..e2cf736dff17a --- /dev/null +++ b/client/rpc-spec-v2/src/transaction/transaction.rs @@ -0,0 +1,208 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! API implementation for submitting transactions. + +use crate::{ + transaction::{ + api::TransactionApiServer, + error::Error, + event::{ + TransactionBlock, TransactionBroadcasted, TransactionDropped, TransactionError, + TransactionEvent, + }, + }, + SubscriptionTaskExecutor, +}; +use jsonrpsee::{ + core::async_trait, + types::{ + error::{CallError, ErrorObject}, + SubscriptionResult, + }, + SubscriptionSink, +}; +use sc_transaction_pool_api::{ + error::IntoPoolError, BlockHash, TransactionFor, TransactionPool, TransactionSource, + TransactionStatus, +}; +use std::sync::Arc; + +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_core::Bytes; +use sp_runtime::{generic, traits::Block as BlockT}; + +use codec::Decode; +use futures::{FutureExt, StreamExt, TryFutureExt}; + +/// An API for transaction RPC calls. +pub struct Transaction { + /// Substrate client. + client: Arc, + /// Transactions pool. + pool: Arc, + /// Executor to spawn subscriptions. + executor: SubscriptionTaskExecutor, +} + +impl Transaction { + /// Creates a new [`Transaction`]. + pub fn new(client: Arc, pool: Arc, executor: SubscriptionTaskExecutor) -> Self { + Transaction { client, pool, executor } + } +} + +/// Currently we treat all RPC transactions as externals. +/// +/// Possibly in the future we could allow opt-in for special treatment +/// of such transactions, so that the block authors can inject +/// some unique transactions via RPC and have them included in the pool. +const TX_SOURCE: TransactionSource = TransactionSource::External; + +/// Extrinsic has an invalid format. +/// +/// # Note +/// +/// This is similar to the old `author` API error code. +const BAD_FORMAT: i32 = 1001; + +#[async_trait] +impl TransactionApiServer> for Transaction +where + Pool: TransactionPool + Sync + Send + 'static, + Pool::Hash: Unpin, + ::Hash: Unpin, + Client: HeaderBackend + ProvideRuntimeApi + Send + Sync + 'static, +{ + fn submit_and_watch(&self, mut sink: SubscriptionSink, xt: Bytes) -> SubscriptionResult { + // This is the only place where the RPC server can return an error for this + // subscription. Other defects must be signaled as events to the sink. + let decoded_extrinsic = match TransactionFor::::decode(&mut &xt[..]) { + Ok(decoded_extrinsic) => decoded_extrinsic, + Err(e) => { + let err = CallError::Custom(ErrorObject::owned( + BAD_FORMAT, + format!("Extrinsic has invalid format: {}", e), + None::<()>, + )); + let _ = sink.reject(err); + return Ok(()) + }, + }; + + let best_block_hash = self.client.info().best_hash; + + let submit = self + .pool + .submit_and_watch( + &generic::BlockId::hash(best_block_hash), + TX_SOURCE, + decoded_extrinsic, + ) + .map_err(|e| { + e.into_pool_error() + .map(Error::from) + .unwrap_or_else(|e| Error::Verification(Box::new(e))) + }); + + let fut = async move { + match submit.await { + Ok(stream) => { + let mut state = TransactionState::new(); + let stream = + stream.filter_map(|event| async move { state.handle_event(event) }); + sink.pipe_from_stream(stream.boxed()).await; + }, + Err(err) => { + // We have not created an `Watcher` for the tx. Make sure the + // error is still propagated as an event. + let event: TransactionEvent<::Hash> = err.into(); + sink.pipe_from_stream(futures::stream::once(async { event }).boxed()).await; + }, + }; + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) + } +} + +/// The transaction's state that needs to be preserved between +/// multiple events generated by the transaction-pool. +/// +/// # Note +/// +/// In the future, the RPC server can submit only the last event when multiple +/// identical events happen in a row. +#[derive(Clone, Copy)] +struct TransactionState { + /// True if the transaction was previously broadcasted. + broadcasted: bool, +} + +impl TransactionState { + /// Construct a new [`TransactionState`]. + pub fn new() -> Self { + TransactionState { broadcasted: false } + } + + /// Handle events generated by the transaction-pool and convert them + /// to the new API expected state. + #[inline] + pub fn handle_event( + &mut self, + event: TransactionStatus, + ) -> Option> { + match event { + TransactionStatus::Ready | TransactionStatus::Future => + Some(TransactionEvent::::Validated), + TransactionStatus::Broadcast(peers) => { + // Set the broadcasted flag once if we submitted the transaction to + // at least one peer. + self.broadcasted = self.broadcasted || !peers.is_empty(); + + Some(TransactionEvent::Broadcasted(TransactionBroadcasted { + num_peers: peers.len(), + })) + }, + TransactionStatus::InBlock((hash, index)) => + Some(TransactionEvent::BestChainBlockIncluded(Some(TransactionBlock { + hash, + index, + }))), + TransactionStatus::Retracted(_) => Some(TransactionEvent::BestChainBlockIncluded(None)), + TransactionStatus::FinalityTimeout(_) => + Some(TransactionEvent::Dropped(TransactionDropped { + broadcasted: self.broadcasted, + error: "Maximum number of finality watchers has been reached".into(), + })), + TransactionStatus::Finalized((hash, index)) => + Some(TransactionEvent::Finalized(TransactionBlock { hash, index })), + TransactionStatus::Usurped(_) => Some(TransactionEvent::Invalid(TransactionError { + error: "Extrinsic was rendered invalid by another extrinsic".into(), + })), + TransactionStatus::Dropped => Some(TransactionEvent::Invalid(TransactionError { + error: "Extrinsic dropped from the pool due to exceeding limits".into(), + })), + TransactionStatus::Invalid => Some(TransactionEvent::Invalid(TransactionError { + error: "Extrinsic marked as invalid".into(), + })), + } + } +} diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index 427800f74ddf2..a241807cc242b 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-rpc" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate Client RPC" readme = "README.md" @@ -13,40 +13,45 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sc-rpc-api = { version = "0.10.0-dev", path = "../rpc-api" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -codec = { package = "parity-scale-codec", version = "2.0.0" } -futures = "0.3.16" -jsonrpc-pubsub = "18.0.0" -log = "0.4.8" -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -rpc = { package = "jsonrpc-core", version = "18.0.0" } -sp-version = { version = "4.0.0-dev", path = "../../primitives/version" } -serde_json = "1.0.68" -sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } -sp-offchain = { version = "4.0.0-dev", path = "../../primitives/offchain" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-rpc = { version = "4.0.0-dev", path = "../../primitives/rpc" } -sp-keystore = { version = "0.10.0-dev", path = "../../primitives/keystore" } -sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sc-tracing = { version = "4.0.0-dev", path = "../tracing" } +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } -parking_lot = "0.11.1" +jsonrpsee = { version = "0.16.2", features = ["server"] } lazy_static = { version = "1.4.0", optional = true } +log = "0.4.17" +parking_lot = "0.12.1" +serde_json = "1.0.85" +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-rpc-api = { version = "0.10.0-dev", path = "../rpc-api" } +sc-tracing = { version = "4.0.0-dev", path = "../tracing" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +sp-offchain = { version = "4.0.0-dev", path = "../../primitives/offchain" } +sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } + +tokio = { version = "1.22.0", optional = true } [dev-dependencies] +env_logger = "0.9" assert_matches = "1.3.0" lazy_static = "1.4.0" +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-network = { version = "0.10.0-dev", path = "../network" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +tokio = "1.22.0" +sp-io = { version = "7.0.0", path = "../../primitives/io" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] -test-helpers = ["lazy_static"] +test-helpers = ["lazy_static", "tokio"] diff --git a/client/rpc/src/author/mod.rs b/client/rpc/src/author/mod.rs index 40b477a662a60..7d0ffdc62e080 100644 --- a/client/rpc/src/author/mod.rs +++ b/client/rpc/src/author/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,29 +21,30 @@ #[cfg(test)] mod tests; -use log::warn; -use std::{convert::TryInto, sync::Arc}; +use std::sync::Arc; -use sp_blockchain::HeaderBackend; +use crate::SubscriptionTaskExecutor; use codec::{Decode, Encode}; -use futures::{ - future::{FutureExt, TryFutureExt}, - SinkExt, StreamExt as _, +use futures::{FutureExt, TryFutureExt}; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + types::SubscriptionResult, + SubscriptionSink, }; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{ error::IntoPoolError, BlockHash, InPoolTransaction, TransactionFor, TransactionPool, - TransactionSource, TransactionStatus, TxHash, + TransactionSource, TxHash, }; use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; use sp_core::Bytes; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{generic, traits::Block as BlockT}; use sp_session::SessionKeys; -use self::error::{Error, FutureResult, Result}; +use self::error::{Error, Result}; /// Re-export the API for backward compatibility. pub use sc_rpc_api::author::*; @@ -53,12 +54,12 @@ pub struct Author { client: Arc, /// Transactions pool pool: Arc

, - /// Subscriptions manager - subscriptions: SubscriptionManager, /// The key store. keystore: SyncCryptoStorePtr, /// Whether to deny unsafe calls deny_unsafe: DenyUnsafe, + /// Executor to spawn subscriptions. + executor: SubscriptionTaskExecutor, } impl Author { @@ -66,11 +67,11 @@ impl Author { pub fn new( client: Arc, pool: Arc

, - subscriptions: SubscriptionManager, keystore: SyncCryptoStorePtr, deny_unsafe: DenyUnsafe, + executor: SubscriptionTaskExecutor, ) -> Self { - Author { client, pool, subscriptions, keystore, deny_unsafe } + Author { client, pool, keystore, deny_unsafe, executor } } } @@ -81,7 +82,8 @@ impl Author { /// some unique transactions via RPC and have them included in the pool. const TX_SOURCE: TransactionSource = TransactionSource::External; -impl AuthorApi, BlockHash

> for Author +#[async_trait] +impl AuthorApiServer, BlockHash

> for Author where P: TransactionPool + Sync + Send + 'static, Client: HeaderBackend + ProvideRuntimeApi + Send + Sync + 'static, @@ -89,9 +91,24 @@ where P::Hash: Unpin, ::Hash: Unpin, { - type Metadata = crate::Metadata; + async fn submit_extrinsic(&self, ext: Bytes) -> RpcResult> { + let xt = match Decode::decode(&mut &ext[..]) { + Ok(xt) => xt, + Err(err) => return Err(Error::Client(Box::new(err)).into()), + }; + let best_block_hash = self.client.info().best_hash; + self.pool + .submit_one(&generic::BlockId::hash(best_block_hash), TX_SOURCE, xt) + .await + .map_err(|e| { + e.into_pool_error() + .map(|e| Error::Pool(e)) + .unwrap_or_else(|e| Error::Verification(Box::new(e))) + .into() + }) + } - fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<()> { + fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> RpcResult<()> { self.deny_unsafe.check_if_safe()?; let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?; @@ -100,7 +117,7 @@ where Ok(()) } - fn rotate_keys(&self) -> Result { + fn rotate_keys(&self) -> RpcResult { self.deny_unsafe.check_if_safe()?; let best_block_hash = self.client.info().best_hash; @@ -108,10 +125,10 @@ where .runtime_api() .generate_session_keys(&generic::BlockId::Hash(best_block_hash), None) .map(Into::into) - .map_err(|e| Error::Client(Box::new(e))) + .map_err(|api_err| Error::Client(Box::new(api_err)).into()) } - fn has_session_keys(&self, session_keys: Bytes) -> Result { + fn has_session_keys(&self, session_keys: Bytes) -> RpcResult { self.deny_unsafe.check_if_safe()?; let best_block_hash = self.client.info().best_hash; @@ -120,45 +137,27 @@ where .runtime_api() .decode_session_keys(&generic::BlockId::Hash(best_block_hash), session_keys.to_vec()) .map_err(|e| Error::Client(Box::new(e)))? - .ok_or_else(|| Error::InvalidSessionKeys)?; + .ok_or(Error::InvalidSessionKeys)?; Ok(SyncCryptoStore::has_keys(&*self.keystore, &keys)) } - fn has_key(&self, public_key: Bytes, key_type: String) -> Result { + fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult { self.deny_unsafe.check_if_safe()?; let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?; Ok(SyncCryptoStore::has_keys(&*self.keystore, &[(public_key.to_vec(), key_type)])) } - fn submit_extrinsic(&self, ext: Bytes) -> FutureResult> { - let xt = match Decode::decode(&mut &ext[..]) { - Ok(xt) => xt, - Err(err) => return async move { Err(err.into()) }.boxed(), - }; - let best_block_hash = self.client.info().best_hash; - - self.pool - .submit_one(&generic::BlockId::hash(best_block_hash), TX_SOURCE, xt) - .map_err(|e| { - e.into_pool_error() - .map(Into::into) - .unwrap_or_else(|e| error::Error::Verification(Box::new(e)).into()) - }) - .boxed() - } - - fn pending_extrinsics(&self) -> Result> { + fn pending_extrinsics(&self) -> RpcResult> { Ok(self.pool.ready().map(|tx| tx.data().encode().into()).collect()) } fn remove_extrinsic( &self, bytes_or_hash: Vec>>, - ) -> Result>> { + ) -> RpcResult>> { self.deny_unsafe.check_if_safe()?; - let hashes = bytes_or_hash .into_iter() .map(|x| match x { @@ -178,21 +177,13 @@ where .collect()) } - fn watch_extrinsic( - &self, - _metadata: Self::Metadata, - subscriber: Subscriber, BlockHash

>>, - xt: Bytes, - ) { + fn watch_extrinsic(&self, mut sink: SubscriptionSink, xt: Bytes) -> SubscriptionResult { let best_block_hash = self.client.info().best_hash; - let dxt = match TransactionFor::

::decode(&mut &xt[..]).map_err(error::Error::from) { - Ok(tx) => tx, - Err(err) => { - warn!("Failed to submit extrinsic: {}", err); - // reject the subscriber (ignore errors - we don't care if subscriber is no longer - // there). - let _ = subscriber.reject(err.into()); - return + let dxt = match TransactionFor::

::decode(&mut &xt[..]).map_err(|e| Error::from(e)) { + Ok(dxt) => dxt, + Err(e) => { + let _ = sink.reject(JsonRpseeError::from(e)); + return Ok(()) }, }; @@ -202,42 +193,22 @@ where .map_err(|e| { e.into_pool_error() .map(error::Error::from) - .unwrap_or_else(|e| error::Error::Verification(Box::new(e)).into()) + .unwrap_or_else(|e| error::Error::Verification(Box::new(e))) }); - let subscriptions = self.subscriptions.clone(); - - let future = async move { - let tx_stream = match submit.await { - Ok(s) => s, + let fut = async move { + let stream = match submit.await { + Ok(stream) => stream, Err(err) => { - warn!("Failed to submit extrinsic: {}", err); - // reject the subscriber (ignore errors - we don't care if subscriber is no - // longer there). - let _ = subscriber.reject(err.into()); + let _ = sink.reject(JsonRpseeError::from(err)); return }, }; - subscriptions.add(subscriber, move |sink| { - tx_stream - .map(|v| Ok(Ok(v))) - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - .map(drop) - }); + sink.pipe_from_stream(stream).await; }; - let res = self.subscriptions.executor().spawn_obj(future.boxed().into()); - if res.is_err() { - warn!("Error spawning subscription RPC task."); - } - } - - fn unwatch_extrinsic( - &self, - _metadata: Option, - id: SubscriptionId, - ) -> Result { - Ok(self.subscriptions.cancel(id)) + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) } } diff --git a/client/rpc/src/author/tests.rs b/client/rpc/src/author/tests.rs index 2349e08fee506..573d01630de32 100644 --- a/client/rpc/src/author/tests.rs +++ b/client/rpc/src/author/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,21 +18,26 @@ use super::*; +use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; use codec::Encode; -use futures::executor; +use jsonrpsee::{ + core::Error as RpcError, + types::{error::CallError, EmptyServerParams as EmptyParams}, + RpcModule, +}; use sc_transaction_pool::{BasicPool, FullChainApi}; +use sc_transaction_pool_api::TransactionStatus; use sp_core::{ blake2_256, - crypto::{CryptoTypePublicPair, Pair, Public}, - ed25519, - hexdisplay::HexDisplay, - sr25519, + bytes::to_hex, + crypto::{ByteArray, CryptoTypePublicPair, Pair}, + ed25519, sr25519, testing::{ED25519, SR25519}, H256, }; use sp_keystore::testing::KeyStore; -use std::{mem, sync::Arc}; +use std::sync::Arc; use substrate_test_runtime_client::{ self, runtime::{Block, Extrinsic, SessionKeys, Transfer}, @@ -40,8 +45,12 @@ use substrate_test_runtime_client::{ }; fn uxt(sender: AccountKeyring, nonce: u64) -> Extrinsic { - let tx = - Transfer { amount: Default::default(), nonce, from: sender.into(), to: Default::default() }; + let tx = Transfer { + amount: Default::default(), + nonce, + from: sender.into(), + to: AccountKeyring::Bob.into(), + }; tx.into_signed_tx() } @@ -71,240 +80,253 @@ impl TestSetup { Author { client: self.client.clone(), pool: self.pool.clone(), - subscriptions: SubscriptionManager::new(Arc::new(crate::testing::TaskExecutor)), keystore: self.keystore.clone(), deny_unsafe: DenyUnsafe::No, + executor: test_executor(), } } -} - -#[test] -fn submit_transaction_should_not_cause_error() { - let p = TestSetup::default().author(); - let xt = uxt(AccountKeyring::Alice, 1).encode(); - let h: H256 = blake2_256(&xt).into(); - assert_matches!( - executor::block_on(AuthorApi::submit_extrinsic(&p, xt.clone().into())), - Ok(h2) if h == h2 - ); - assert!(executor::block_on(AuthorApi::submit_extrinsic(&p, xt.into())).is_err()); + fn into_rpc() -> RpcModule>> { + Self::default().author().into_rpc() + } } -#[test] -fn submit_rich_transaction_should_not_cause_error() { - let p = TestSetup::default().author(); - let xt = uxt(AccountKeyring::Alice, 0).encode(); - let h: H256 = blake2_256(&xt).into(); +#[tokio::test] +async fn author_submit_transaction_should_not_cause_error() { + let _ = env_logger::try_init(); + let author = TestSetup::default().author(); + let api = author.into_rpc(); + let xt: Bytes = uxt(AccountKeyring::Alice, 1).encode().into(); + let extrinsic_hash: H256 = blake2_256(&xt).into(); + let response: H256 = api.call("author_submitExtrinsic", [xt.clone()]).await.unwrap(); + + assert_eq!(response, extrinsic_hash); assert_matches!( - executor::block_on(AuthorApi::submit_extrinsic(&p, xt.clone().into())), - Ok(h2) if h == h2 + api.call::<_, H256>("author_submitExtrinsic", [xt]).await, + Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Already Imported") && err.code() == 1013 ); - assert!(executor::block_on(AuthorApi::submit_extrinsic(&p, xt.into())).is_err()); } -#[test] -fn should_watch_extrinsic() { - // given - let setup = TestSetup::default(); - let p = setup.author(); - - let (subscriber, id_rx, data) = jsonrpc_pubsub::typed::Subscriber::new_test("test"); - - // when - p.watch_extrinsic( - Default::default(), - subscriber, - uxt(AccountKeyring::Alice, 0).encode().into(), - ); +#[tokio::test] +async fn author_should_watch_extrinsic() { + let api = TestSetup::into_rpc(); + let xt = to_hex(&uxt(AccountKeyring::Alice, 0).encode(), true); - let id = executor::block_on(id_rx).unwrap().unwrap(); - assert_matches!(id, SubscriptionId::String(_)); + let mut sub = api.subscribe("author_submitAndWatchExtrinsic", [xt]).await.unwrap(); + let (tx, sub_id) = timeout_secs(10, sub.next::>()) + .await + .unwrap() + .unwrap() + .unwrap(); - let id = match id { - SubscriptionId::String(id) => id, - _ => unreachable!(), - }; + assert_matches!(tx, TransactionStatus::Ready); + assert_eq!(&sub_id, sub.subscription_id()); - // check notifications - let replacement = { + // Replace the extrinsic and observe the subscription is notified. + let (xt_replacement, xt_hash) = { let tx = Transfer { amount: 5, nonce: 0, from: AccountKeyring::Alice.into(), - to: Default::default(), + to: AccountKeyring::Bob.into(), }; - tx.into_signed_tx() + let tx = tx.into_signed_tx().encode(); + let hash = blake2_256(&tx); + + (to_hex(&tx, true), hash) }; - executor::block_on(AuthorApi::submit_extrinsic(&p, replacement.encode().into())).unwrap(); - let (res, data) = executor::block_on(data.into_future()); - - let expected = Some(format!( - r#"{{"jsonrpc":"2.0","method":"test","params":{{"result":"ready","subscription":"{}"}}}}"#, - id, - )); - assert_eq!(res, expected); - - let h = blake2_256(&replacement.encode()); - let expected = Some(format!( - r#"{{"jsonrpc":"2.0","method":"test","params":{{"result":{{"usurped":"0x{}"}},"subscription":"{}"}}}}"#, - HexDisplay::from(&h), - id, - )); - - let res = executor::block_on(data.into_future()).0; - assert_eq!(res, expected); + + let _ = api.call::<_, H256>("author_submitExtrinsic", [xt_replacement]).await.unwrap(); + + let (tx, sub_id) = timeout_secs(10, sub.next::>()) + .await + .unwrap() + .unwrap() + .unwrap(); + assert_eq!(tx, TransactionStatus::Usurped(xt_hash.into())); + assert_eq!(&sub_id, sub.subscription_id()); } -#[test] -fn should_return_watch_validation_error() { - // given - let setup = TestSetup::default(); - let p = setup.author(); +#[tokio::test] +async fn author_should_return_watch_validation_error() { + const METHOD: &'static str = "author_submitAndWatchExtrinsic"; - let (subscriber, id_rx, _data) = jsonrpc_pubsub::typed::Subscriber::new_test("test"); + let api = TestSetup::into_rpc(); + let failed_sub = api + .subscribe(METHOD, [to_hex(&uxt(AccountKeyring::Alice, 179).encode(), true)]) + .await; - // when - p.watch_extrinsic( - Default::default(), - subscriber, - uxt(AccountKeyring::Alice, 179).encode().into(), + assert_matches!( + failed_sub, + Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Invalid Transaction") && err.code() == 1010 ); - - // then - let res = executor::block_on(id_rx).unwrap(); - assert!(res.is_err(), "Expected the transaction to be rejected as invalid."); } -#[test] -fn should_return_pending_extrinsics() { - let p = TestSetup::default().author(); +#[tokio::test] +async fn author_should_return_pending_extrinsics() { + let api = TestSetup::into_rpc(); - let ex = uxt(AccountKeyring::Alice, 0); - executor::block_on(AuthorApi::submit_extrinsic(&p, ex.encode().into())).unwrap(); - assert_matches!( - p.pending_extrinsics(), - Ok(ref expected) if *expected == vec![Bytes(ex.encode())] - ); + let xt_bytes: Bytes = uxt(AccountKeyring::Alice, 0).encode().into(); + api.call::<_, H256>("author_submitExtrinsic", [to_hex(&xt_bytes, true)]) + .await + .unwrap(); + + let pending: Vec = + api.call("author_pendingExtrinsics", EmptyParams::new()).await.unwrap(); + assert_eq!(pending, vec![xt_bytes]); } -#[test] -fn should_remove_extrinsics() { +#[tokio::test] +async fn author_should_remove_extrinsics() { + const METHOD: &'static str = "author_removeExtrinsic"; let setup = TestSetup::default(); - let p = setup.author(); - - let ex1 = uxt(AccountKeyring::Alice, 0); - executor::block_on(p.submit_extrinsic(ex1.encode().into())).unwrap(); - let ex2 = uxt(AccountKeyring::Alice, 1); - executor::block_on(p.submit_extrinsic(ex2.encode().into())).unwrap(); - let ex3 = uxt(AccountKeyring::Bob, 0); - let hash3 = executor::block_on(p.submit_extrinsic(ex3.encode().into())).unwrap(); + let api = setup.author().into_rpc(); + + // Submit three extrinsics, then remove two of them (will cause the third to be removed as well, + // having a higher nonce) + let xt1_bytes = uxt(AccountKeyring::Alice, 0).encode(); + let xt1 = to_hex(&xt1_bytes, true); + let xt1_hash: H256 = api.call("author_submitExtrinsic", [xt1]).await.unwrap(); + + let xt2 = to_hex(&uxt(AccountKeyring::Alice, 1).encode(), true); + let xt2_hash: H256 = api.call("author_submitExtrinsic", [xt2]).await.unwrap(); + + let xt3 = to_hex(&uxt(AccountKeyring::Bob, 0).encode(), true); + let xt3_hash: H256 = api.call("author_submitExtrinsic", [xt3]).await.unwrap(); assert_eq!(setup.pool.status().ready, 3); - // now remove all 3 - let removed = p - .remove_extrinsic(vec![ - hash::ExtrinsicOrHash::Hash(hash3), - // Removing this one will also remove ex2 - hash::ExtrinsicOrHash::Extrinsic(ex1.encode().into()), - ]) + // Now remove all three. + // Notice how we need an extra `Vec` wrapping the `Vec` we want to submit as params. + let removed: Vec = api + .call( + METHOD, + vec![vec![ + hash::ExtrinsicOrHash::Hash(xt3_hash), + // Removing this one will also remove xt2 + hash::ExtrinsicOrHash::Extrinsic(xt1_bytes.into()), + ]], + ) + .await .unwrap(); - assert_eq!(removed.len(), 3); + assert_eq!(removed, vec![xt1_hash, xt2_hash, xt3_hash]); } -#[test] -fn should_insert_key() { +#[tokio::test] +async fn author_should_insert_key() { let setup = TestSetup::default(); - let p = setup.author(); - + let api = setup.author().into_rpc(); let suri = "//Alice"; - let key_pair = ed25519::Pair::from_string(suri, None).expect("Generates keypair"); - p.insert_key( + let keypair = ed25519::Pair::from_string(suri, None).expect("generates keypair"); + let params: (String, String, Bytes) = ( String::from_utf8(ED25519.0.to_vec()).expect("Keytype is a valid string"), suri.to_string(), - key_pair.public().0.to_vec().into(), - ) - .expect("Insert key"); - - let public_keys = SyncCryptoStore::keys(&*setup.keystore, ED25519).unwrap(); + keypair.public().0.to_vec().into(), + ); + api.call::<_, ()>("author_insertKey", params).await.unwrap(); + let pubkeys = SyncCryptoStore::keys(&*setup.keystore, ED25519).unwrap(); - assert!(public_keys - .contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, key_pair.public().to_raw_vec()))); + assert!( + pubkeys.contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, keypair.public().to_raw_vec())) + ); } -#[test] -fn should_rotate_keys() { +#[tokio::test] +async fn author_should_rotate_keys() { let setup = TestSetup::default(); - let p = setup.author(); - - let new_public_keys = p.rotate_keys().expect("Rotates the keys"); + let api = setup.author().into_rpc(); + let new_pubkeys: Bytes = api.call("author_rotateKeys", EmptyParams::new()).await.unwrap(); let session_keys = - SessionKeys::decode(&mut &new_public_keys[..]).expect("SessionKeys decode successfully"); - - let ed25519_public_keys = SyncCryptoStore::keys(&*setup.keystore, ED25519).unwrap(); - let sr25519_public_keys = SyncCryptoStore::keys(&*setup.keystore, SR25519).unwrap(); - - assert!(ed25519_public_keys + SessionKeys::decode(&mut &new_pubkeys[..]).expect("SessionKeys decode successfully"); + let ed25519_pubkeys = SyncCryptoStore::keys(&*setup.keystore, ED25519).unwrap(); + let sr25519_pubkeys = SyncCryptoStore::keys(&*setup.keystore, SR25519).unwrap(); + assert!(ed25519_pubkeys .contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, session_keys.ed25519.to_raw_vec()))); - assert!(sr25519_public_keys + assert!(sr25519_pubkeys .contains(&CryptoTypePublicPair(sr25519::CRYPTO_ID, session_keys.sr25519.to_raw_vec()))); } -#[test] -fn test_has_session_keys() { - let setup = TestSetup::default(); - let p = setup.author(); - - let non_existent_public_keys = - TestSetup::default().author().rotate_keys().expect("Rotates the keys"); - - let public_keys = p.rotate_keys().expect("Rotates the keys"); - let test_vectors = vec![ - (public_keys, Ok(true)), - (vec![1, 2, 3].into(), Err(Error::InvalidSessionKeys)), - (non_existent_public_keys, Ok(false)), - ]; - - for (keys, result) in test_vectors { - assert_eq!( - result.map_err(|e| mem::discriminant(&e)), - p.has_session_keys(keys).map_err(|e| mem::discriminant(&e)), - ); - } +#[tokio::test] +async fn author_has_session_keys() { + // Setup + let api = TestSetup::into_rpc(); + + // Add a valid session key + let pubkeys: Bytes = api + .call("author_rotateKeys", EmptyParams::new()) + .await + .expect("Rotates the keys"); + + // Add a session key in a different keystore + let non_existent_pubkeys: Bytes = { + let api2 = TestSetup::default().author().into_rpc(); + api2.call("author_rotateKeys", EmptyParams::new()) + .await + .expect("Rotates the keys") + }; + + // Then… + let existing = api.call::<_, bool>("author_hasSessionKeys", vec![pubkeys]).await.unwrap(); + assert!(existing, "Existing key is in the session keys"); + + let inexistent = api + .call::<_, bool>("author_hasSessionKeys", vec![non_existent_pubkeys]) + .await + .unwrap(); + assert_eq!(inexistent, false, "Inexistent key is not in the session keys"); + + assert_matches!( + api.call::<_, bool>("author_hasSessionKeys", vec![Bytes::from(vec![1, 2, 3])]).await, + Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Session keys are not encoded correctly") + ); } -#[test] -fn test_has_key() { - let setup = TestSetup::default(); - let p = setup.author(); +#[tokio::test] +async fn author_has_key() { + let _ = env_logger::try_init(); + let api = TestSetup::into_rpc(); let suri = "//Alice"; - let alice_key_pair = ed25519::Pair::from_string(suri, None).expect("Generates keypair"); - p.insert_key( + let alice_keypair = ed25519::Pair::from_string(suri, None).expect("Generates keypair"); + let params = ( String::from_utf8(ED25519.0.to_vec()).expect("Keytype is a valid string"), suri.to_string(), - alice_key_pair.public().0.to_vec().into(), - ) - .expect("Insert key"); - let bob_key_pair = ed25519::Pair::from_string("//Bob", None).expect("Generates keypair"); - - let test_vectors = vec![ - (alice_key_pair.public().to_raw_vec().into(), ED25519, Ok(true)), - (alice_key_pair.public().to_raw_vec().into(), SR25519, Ok(false)), - (bob_key_pair.public().to_raw_vec().into(), ED25519, Ok(false)), - ]; - - for (key, key_type, result) in test_vectors { - assert_eq!( - result.map_err(|e| mem::discriminant(&e)), - p.has_key( - key, - String::from_utf8(key_type.0.to_vec()).expect("Keytype is a valid string"), - ) - .map_err(|e| mem::discriminant(&e)), + Bytes::from(alice_keypair.public().0.to_vec()), + ); + + api.call::<_, ()>("author_insertKey", params).await.expect("insertKey works"); + + let bob_keypair = ed25519::Pair::from_string("//Bob", None).expect("Generates keypair"); + + // Alice's ED25519 key is there + let has_alice_ed: bool = { + let params = ( + Bytes::from(alice_keypair.public().to_raw_vec()), + String::from_utf8(ED25519.0.to_vec()).expect("Keytype is a valid string"), ); - } + api.call("author_hasKey", params).await.unwrap() + }; + assert!(has_alice_ed); + + // Alice's SR25519 key is not there + let has_alice_sr: bool = { + let params = ( + Bytes::from(alice_keypair.public().to_raw_vec()), + String::from_utf8(SR25519.0.to_vec()).expect("Keytype is a valid string"), + ); + api.call("author_hasKey", params).await.unwrap() + }; + assert!(!has_alice_sr); + + // Bob's ED25519 key is not there + let has_bob_ed: bool = { + let params = ( + Bytes::from(bob_keypair.public().to_raw_vec()), + String::from_utf8(ED25519.0.to_vec()).expect("Keytype is a valid string"), + ); + api.call("author_hasKey", params).await.unwrap() + }; + assert!(!has_bob_ed); } diff --git a/client/rpc/src/chain/chain_full.rs b/client/rpc/src/chain/chain_full.rs index 96d5b86f42498..375e724a33d69 100644 --- a/client/rpc/src/chain/chain_full.rs +++ b/client/rpc/src/chain/chain_full.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,31 +18,36 @@ //! Blockchain API backend for full nodes. -use super::{client_err, error::FutureResult, ChainBackend}; -use futures::FutureExt; -use jsonrpc_pubsub::manager::SubscriptionManager; +use super::{client_err, ChainBackend, Error}; +use crate::SubscriptionTaskExecutor; +use std::{marker::PhantomData, sync::Arc}; + +use futures::{ + future::{self, FutureExt}, + stream::{self, Stream, StreamExt}, +}; +use jsonrpsee::SubscriptionSink; use sc_client_api::{BlockBackend, BlockchainEvents}; use sp_blockchain::HeaderBackend; use sp_runtime::{ generic::{BlockId, SignedBlock}, traits::Block as BlockT, }; -use std::{marker::PhantomData, sync::Arc}; /// Blockchain API backend for full nodes. Reads all the data from local database. pub struct FullChain { /// Substrate client. client: Arc, - /// Current subscriptions. - subscriptions: SubscriptionManager, /// phantom member to pin the block type _phantom: PhantomData, + /// Subscription executor. + executor: SubscriptionTaskExecutor, } impl FullChain { /// Create new Chain API RPC handler. - pub fn new(client: Arc, subscriptions: SubscriptionManager) -> Self { - Self { client, subscriptions, _phantom: PhantomData } + pub fn new(client: Arc, executor: SubscriptionTaskExecutor) -> Self { + Self { client, executor, _phantom: PhantomData } } } @@ -56,17 +61,90 @@ where &self.client } - fn subscriptions(&self) -> &SubscriptionManager { - &self.subscriptions + fn header(&self, hash: Option) -> Result, Error> { + self.client.header(BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err) + } + + fn block(&self, hash: Option) -> Result>, Error> { + self.client.block(&BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err) + } + + fn subscribe_all_heads(&self, sink: SubscriptionSink) { + subscribe_headers( + &self.client, + &self.executor, + sink, + || self.client().info().best_hash, + || { + self.client() + .import_notification_stream() + .map(|notification| notification.header) + }, + ) } - fn header(&self, hash: Option) -> FutureResult> { - let res = self.client.header(BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err); - async move { res }.boxed() + fn subscribe_new_heads(&self, sink: SubscriptionSink) { + subscribe_headers( + &self.client, + &self.executor, + sink, + || self.client().info().best_hash, + || { + self.client() + .import_notification_stream() + .filter(|notification| future::ready(notification.is_new_best)) + .map(|notification| notification.header) + }, + ) } - fn block(&self, hash: Option) -> FutureResult>> { - let res = self.client.block(&BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err); - async move { res }.boxed() + fn subscribe_finalized_heads(&self, sink: SubscriptionSink) { + subscribe_headers( + &self.client, + &self.executor, + sink, + || self.client().info().finalized_hash, + || { + self.client() + .finality_notification_stream() + .map(|notification| notification.header) + }, + ) } } + +/// Subscribe to new headers. +fn subscribe_headers( + client: &Arc, + executor: &SubscriptionTaskExecutor, + mut sink: SubscriptionSink, + best_block_hash: G, + stream: F, +) where + Block: BlockT + 'static, + Block::Header: Unpin, + Client: HeaderBackend + 'static, + F: FnOnce() -> S, + G: FnOnce() -> Block::Hash, + S: Stream + Send + Unpin + 'static, +{ + // send current head right at the start. + let maybe_header = client + .header(BlockId::Hash(best_block_hash())) + .map_err(client_err) + .and_then(|header| header.ok_or_else(|| Error::Other("Best header missing.".into()))) + .map_err(|e| log::warn!("Best header error {:?}", e)) + .ok(); + + // NOTE: by the time we set up the stream there might be a new best block and so there is a risk + // that the stream has a hole in it. The alternative would be to look up the best block *after* + // we set up the stream and chain it to the stream. Consuming code would need to handle + // duplicates at the beginning of the stream though. + let stream = stream::iter(maybe_header).chain(stream()); + + let fut = async move { + sink.pipe_from_stream(stream).await; + }; + + executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); +} diff --git a/client/rpc/src/chain/chain_light.rs b/client/rpc/src/chain/chain_light.rs deleted file mode 100644 index 2d15c819e1dab..0000000000000 --- a/client/rpc/src/chain/chain_light.rs +++ /dev/null @@ -1,114 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Blockchain API backend for light nodes. - -use futures::{future::ready, FutureExt, TryFutureExt}; -use jsonrpc_pubsub::manager::SubscriptionManager; -use std::sync::Arc; - -use sc_client_api::light::{Fetcher, RemoteBlockchain, RemoteBodyRequest}; -use sp_runtime::{ - generic::{BlockId, SignedBlock}, - traits::Block as BlockT, -}; - -use super::{client_err, error::FutureResult, ChainBackend}; -use sc_client_api::BlockchainEvents; -use sp_blockchain::HeaderBackend; - -/// Blockchain API backend for light nodes. Reads all the data from local -/// database, if available, or fetches it from remote node otherwise. -pub struct LightChain { - /// Substrate client. - client: Arc, - /// Current subscriptions. - subscriptions: SubscriptionManager, - /// Remote blockchain reference - remote_blockchain: Arc>, - /// Remote fetcher reference. - fetcher: Arc, -} - -impl> LightChain { - /// Create new Chain API RPC handler. - pub fn new( - client: Arc, - subscriptions: SubscriptionManager, - remote_blockchain: Arc>, - fetcher: Arc, - ) -> Self { - Self { client, subscriptions, remote_blockchain, fetcher } - } -} - -impl ChainBackend for LightChain -where - Block: BlockT + 'static, - Block::Header: Unpin, - Client: BlockchainEvents + HeaderBackend + Send + Sync + 'static, - F: Fetcher + Send + Sync + 'static, -{ - fn client(&self) -> &Arc { - &self.client - } - - fn subscriptions(&self) -> &SubscriptionManager { - &self.subscriptions - } - - fn header(&self, hash: Option) -> FutureResult> { - let hash = self.unwrap_or_best(hash); - - let fetcher = self.fetcher.clone(); - let maybe_header = sc_client_api::light::future_header( - &*self.remote_blockchain, - &*fetcher, - BlockId::Hash(hash), - ); - - maybe_header.then(move |result| ready(result.map_err(client_err))).boxed() - } - - fn block(&self, hash: Option) -> FutureResult>> { - let fetcher = self.fetcher.clone(); - self.header(hash) - .and_then(move |header| async move { - match header { - Some(header) => { - let body = fetcher - .remote_body(RemoteBodyRequest { - header: header.clone(), - retry_count: Default::default(), - }) - .await; - - body.map(|body| { - Some(SignedBlock { - block: Block::new(header, body), - justifications: None, - }) - }) - .map_err(client_err) - }, - None => Ok(None), - } - }) - .boxed() - } -} diff --git a/client/rpc/src/chain/mod.rs b/client/rpc/src/chain/mod.rs index a06c3a094b40f..be06b91ca747f 100644 --- a/client/rpc/src/chain/mod.rs +++ b/client/rpc/src/chain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,31 +19,23 @@ //! Substrate blockchain API. mod chain_full; -mod chain_light; #[cfg(test)] mod tests; -use futures::{future, StreamExt, TryStreamExt}; -use log::warn; -use rpc::{ - futures::{stream, FutureExt, SinkExt, Stream}, - Result as RpcResult, -}; use std::sync::Arc; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; -use sc_client_api::{ - light::{Fetcher, RemoteBlockchain}, - BlockchainEvents, -}; +use crate::SubscriptionTaskExecutor; + +use jsonrpsee::{core::RpcResult, types::SubscriptionResult, SubscriptionSink}; +use sc_client_api::BlockchainEvents; use sp_rpc::{list::ListOrValue, number::NumberOrHex}; use sp_runtime::{ generic::{BlockId, SignedBlock}, traits::{Block as BlockT, Header, NumberFor}, }; -use self::error::{Error, FutureResult, Result}; +use self::error::Error; use sc_client_api::BlockBackend; pub use sc_rpc_api::chain::*; @@ -59,32 +51,27 @@ where /// Get client reference. fn client(&self) -> &Arc; - /// Get subscriptions reference. - fn subscriptions(&self) -> &SubscriptionManager; - /// Tries to unwrap passed block hash, or uses best block hash otherwise. fn unwrap_or_best(&self, hash: Option) -> Block::Hash { - match hash.into() { + match hash { None => self.client().info().best_hash, Some(hash) => hash, } } /// Get header of a relay chain block. - fn header(&self, hash: Option) -> FutureResult>; + fn header(&self, hash: Option) -> Result, Error>; /// Get header and body of a relay chain block. - fn block(&self, hash: Option) -> FutureResult>>; + fn block(&self, hash: Option) -> Result>, Error>; /// Get hash of the n-th block in the canon chain. /// /// By default returns latest block hash. - fn block_hash(&self, number: Option) -> Result> { + fn block_hash(&self, number: Option) -> Result, Error> { match number { None => Ok(Some(self.client().info().best_hash)), Some(num_or_hex) => { - use std::convert::TryInto; - // FIXME <2329>: Database seems to limit the block number to u32 for no reason let block_num: u32 = num_or_hex.try_into().map_err(|_| { Error::Other(format!( @@ -103,130 +90,31 @@ where } /// Get hash of the last finalized block in the canon chain. - fn finalized_head(&self) -> Result { + fn finalized_head(&self) -> Result { Ok(self.client().info().finalized_hash) } /// All new head subscription - fn subscribe_all_heads( - &self, - _metadata: crate::Metadata, - subscriber: Subscriber, - ) { - subscribe_headers( - self.client(), - self.subscriptions(), - subscriber, - || self.client().info().best_hash, - || { - self.client() - .import_notification_stream() - .map(|notification| Ok::<_, rpc::Error>(notification.header)) - }, - ) - } - - /// Unsubscribe from all head subscription. - fn unsubscribe_all_heads( - &self, - _metadata: Option, - id: SubscriptionId, - ) -> RpcResult { - Ok(self.subscriptions().cancel(id)) - } + fn subscribe_all_heads(&self, sink: SubscriptionSink); /// New best head subscription - fn subscribe_new_heads( - &self, - _metadata: crate::Metadata, - subscriber: Subscriber, - ) { - subscribe_headers( - self.client(), - self.subscriptions(), - subscriber, - || self.client().info().best_hash, - || { - self.client() - .import_notification_stream() - .filter(|notification| future::ready(notification.is_new_best)) - .map(|notification| Ok::<_, rpc::Error>(notification.header)) - }, - ) - } - - /// Unsubscribe from new best head subscription. - fn unsubscribe_new_heads( - &self, - _metadata: Option, - id: SubscriptionId, - ) -> RpcResult { - Ok(self.subscriptions().cancel(id)) - } + fn subscribe_new_heads(&self, sink: SubscriptionSink); /// Finalized head subscription - fn subscribe_finalized_heads( - &self, - _metadata: crate::Metadata, - subscriber: Subscriber, - ) { - subscribe_headers( - self.client(), - self.subscriptions(), - subscriber, - || self.client().info().finalized_hash, - || { - self.client() - .finality_notification_stream() - .map(|notification| Ok::<_, rpc::Error>(notification.header)) - }, - ) - } - - /// Unsubscribe from finalized head subscription. - fn unsubscribe_finalized_heads( - &self, - _metadata: Option, - id: SubscriptionId, - ) -> RpcResult { - Ok(self.subscriptions().cancel(id)) - } + fn subscribe_finalized_heads(&self, sink: SubscriptionSink); } /// Create new state API that works on full node. pub fn new_full( client: Arc, - subscriptions: SubscriptionManager, + executor: SubscriptionTaskExecutor, ) -> Chain where Block: BlockT + 'static, Block::Header: Unpin, Client: BlockBackend + HeaderBackend + BlockchainEvents + 'static, { - Chain { backend: Box::new(self::chain_full::FullChain::new(client, subscriptions)) } -} - -/// Create new state API that works on light node. -pub fn new_light>( - client: Arc, - subscriptions: SubscriptionManager, - remote_blockchain: Arc>, - fetcher: Arc, -) -> Chain -where - Block: BlockT + 'static, - Block::Header: Unpin, - Client: BlockBackend + HeaderBackend + BlockchainEvents + 'static, - F: Send + Sync + 'static, -{ - Chain { - backend: Box::new(self::chain_light::LightChain::new( - client, - subscriptions, - remote_blockchain, - fetcher, - )), - } + Chain { backend: Box::new(self::chain_full::FullChain::new(client, executor)) } } /// Chain API with subscriptions support. @@ -234,120 +122,58 @@ pub struct Chain { backend: Box>, } -impl ChainApi, Block::Hash, Block::Header, SignedBlock> +impl ChainApiServer, Block::Hash, Block::Header, SignedBlock> for Chain where Block: BlockT + 'static, Block::Header: Unpin, Client: HeaderBackend + BlockchainEvents + 'static, { - type Metadata = crate::Metadata; - - fn header(&self, hash: Option) -> FutureResult> { - self.backend.header(hash) + fn header(&self, hash: Option) -> RpcResult> { + self.backend.header(hash).map_err(Into::into) } - fn block(&self, hash: Option) -> FutureResult>> { - self.backend.block(hash) + fn block(&self, hash: Option) -> RpcResult>> { + self.backend.block(hash).map_err(Into::into) } fn block_hash( &self, number: Option>, - ) -> Result>> { + ) -> RpcResult>> { match number { - None => self.backend.block_hash(None).map(ListOrValue::Value), - Some(ListOrValue::Value(number)) => - self.backend.block_hash(Some(number)).map(ListOrValue::Value), + None => self.backend.block_hash(None).map(ListOrValue::Value).map_err(Into::into), + Some(ListOrValue::Value(number)) => self + .backend + .block_hash(Some(number)) + .map(ListOrValue::Value) + .map_err(Into::into), Some(ListOrValue::List(list)) => Ok(ListOrValue::List( list.into_iter() .map(|number| self.backend.block_hash(Some(number))) - .collect::>()?, + .collect::>()?, )), } } - fn finalized_head(&self) -> Result { - self.backend.finalized_head() - } - - fn subscribe_all_heads(&self, metadata: Self::Metadata, subscriber: Subscriber) { - self.backend.subscribe_all_heads(metadata, subscriber) + fn finalized_head(&self) -> RpcResult { + self.backend.finalized_head().map_err(Into::into) } - fn unsubscribe_all_heads( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult { - self.backend.unsubscribe_all_heads(metadata, id) + fn subscribe_all_heads(&self, sink: SubscriptionSink) -> SubscriptionResult { + self.backend.subscribe_all_heads(sink); + Ok(()) } - fn subscribe_new_heads(&self, metadata: Self::Metadata, subscriber: Subscriber) { - self.backend.subscribe_new_heads(metadata, subscriber) + fn subscribe_new_heads(&self, sink: SubscriptionSink) -> SubscriptionResult { + self.backend.subscribe_new_heads(sink); + Ok(()) } - fn unsubscribe_new_heads( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult { - self.backend.unsubscribe_new_heads(metadata, id) + fn subscribe_finalized_heads(&self, sink: SubscriptionSink) -> SubscriptionResult { + self.backend.subscribe_finalized_heads(sink); + Ok(()) } - - fn subscribe_finalized_heads( - &self, - metadata: Self::Metadata, - subscriber: Subscriber, - ) { - self.backend.subscribe_finalized_heads(metadata, subscriber) - } - - fn unsubscribe_finalized_heads( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult { - self.backend.unsubscribe_finalized_heads(metadata, id) - } -} - -/// Subscribe to new headers. -fn subscribe_headers( - client: &Arc, - subscriptions: &SubscriptionManager, - subscriber: Subscriber, - best_block_hash: G, - stream: F, -) where - Block: BlockT + 'static, - Block::Header: Unpin, - Client: HeaderBackend + 'static, - F: FnOnce() -> S, - G: FnOnce() -> Block::Hash, - S: Stream> + Send + 'static, -{ - subscriptions.add(subscriber, |sink| { - // send current head right at the start. - let header = client - .header(BlockId::Hash(best_block_hash())) - .map_err(client_err) - .and_then(|header| { - header.ok_or_else(|| Error::Other("Best header missing.".to_string())) - }) - .map_err(Into::into); - - // send further subscriptions - let stream = stream() - .inspect_err(|e| warn!("Block notification stream error: {:?}", e)) - .map(|res| Ok(res)); - - stream::iter(vec![Ok(header)]) - .chain(stream) - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - // we ignore the resulting Stream (if the first stream is over we are unsubscribed) - .map(|_| ()) - }); } fn client_err(err: sp_blockchain::Error) -> Error { diff --git a/client/rpc/src/chain/tests.rs b/client/rpc/src/chain/tests.rs index caa9f33138b86..224d021f9409e 100644 --- a/client/rpc/src/chain/tests.rs +++ b/client/rpc/src/chain/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,9 +17,9 @@ // along with this program. If not, see . use super::*; -use crate::testing::TaskExecutor; +use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; -use futures::executor; +use jsonrpsee::types::EmptyServerParams as EmptyParams; use sc_block_builder::BlockBuilderProvider; use sp_consensus::BlockOrigin; use sp_rpc::list::ListOrValue; @@ -28,221 +28,220 @@ use substrate_test_runtime_client::{ runtime::{Block, Header, H256}, }; -#[test] -fn should_return_header() { +#[tokio::test] +async fn should_return_header() { let client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); + let api = new_full(client.clone(), test_executor()).into_rpc(); - assert_matches!( - executor::block_on(api.header(Some(client.genesis_hash()).into())), - Ok(Some(ref x)) if x == &Header { + let res: Header = + api.call("chain_getHeader", [H256::from(client.genesis_hash())]).await.unwrap(); + assert_eq!( + res, + Header { parent_hash: H256::from_low_u64_be(0), number: 0, - state_root: x.state_root.clone(), - extrinsics_root: - "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314".parse().unwrap(), + state_root: res.state_root, + extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314" + .parse() + .unwrap(), digest: Default::default(), } ); - assert_matches!( - executor::block_on(api.header(None.into())), - Ok(Some(ref x)) if x == &Header { + let res: Header = api.call("chain_getHeader", EmptyParams::new()).await.unwrap(); + assert_eq!( + res, + Header { parent_hash: H256::from_low_u64_be(0), number: 0, - state_root: x.state_root.clone(), - extrinsics_root: - "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314".parse().unwrap(), + state_root: res.state_root, + extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314" + .parse() + .unwrap(), digest: Default::default(), } ); assert_matches!( - executor::block_on(api.header(Some(H256::from_low_u64_be(5)).into())), - Ok(None) + api.call::<_, Option

>("chain_getHeader", [H256::from_low_u64_be(5)]) + .await + .unwrap(), + None ); } -#[test] -fn should_return_a_block() { +#[tokio::test] +async fn should_return_a_block() { let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); + let api = new_full(client.clone(), test_executor()).into_rpc(); let block = client.new_block(Default::default()).unwrap().build().unwrap().block; let block_hash = block.hash(); - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); + client.import(BlockOrigin::Own, block).await.unwrap(); + + let res: SignedBlock = + api.call("chain_getBlock", [H256::from(client.genesis_hash())]).await.unwrap(); // Genesis block is not justified - assert_matches!( - executor::block_on(api.block(Some(client.genesis_hash()).into())), - Ok(Some(SignedBlock { justifications: None, .. })) - ); + assert!(res.justifications.is_none()); - assert_matches!( - executor::block_on(api.block(Some(block_hash).into())), - Ok(Some(ref x)) if x.block == Block { + let res: SignedBlock = + api.call("chain_getBlock", [H256::from(block_hash)]).await.unwrap(); + assert_eq!( + res.block, + Block { header: Header { parent_hash: client.genesis_hash(), number: 1, - state_root: x.block.header.state_root.clone(), - extrinsics_root: - "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314".parse().unwrap(), + state_root: res.block.header.state_root, + extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314" + .parse() + .unwrap(), digest: Default::default(), }, extrinsics: vec![], } ); - assert_matches!( - executor::block_on(api.block(None.into())), - Ok(Some(ref x)) if x.block == Block { + let res: SignedBlock = api.call("chain_getBlock", Vec::::new()).await.unwrap(); + assert_eq!( + res.block, + Block { header: Header { parent_hash: client.genesis_hash(), number: 1, - state_root: x.block.header.state_root.clone(), - extrinsics_root: - "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314".parse().unwrap(), + state_root: res.block.header.state_root, + extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314" + .parse() + .unwrap(), digest: Default::default(), }, extrinsics: vec![], } ); - assert_matches!(executor::block_on(api.block(Some(H256::from_low_u64_be(5)).into())), Ok(None)); + assert_matches!( + api.call::<_, Option
>("chain_getBlock", [H256::from_low_u64_be(5)]) + .await + .unwrap(), + None + ); } -#[test] -fn should_return_block_hash() { +#[tokio::test] +async fn should_return_block_hash() { let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); + let api = new_full(client.clone(), test_executor()).into_rpc(); - assert_matches!( - api.block_hash(None.into()), - Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash() - ); + let res: ListOrValue> = + api.call("chain_getBlockHash", EmptyParams::new()).await.unwrap(); assert_matches!( - api.block_hash(Some(ListOrValue::Value(0u64.into())).into()), - Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash() + res, + ListOrValue::Value(Some(ref x)) if x == &client.genesis_hash() ); + let res: ListOrValue> = + api.call("chain_getBlockHash", [ListOrValue::from(0_u64)]).await.unwrap(); assert_matches!( - api.block_hash(Some(ListOrValue::Value(1u64.into())).into()), - Ok(ListOrValue::Value(None)) + res, + ListOrValue::Value(Some(ref x)) if x == &client.genesis_hash() ); + let res: Option>> = + api.call("chain_getBlockHash", [ListOrValue::from(1_u64)]).await.unwrap(); + assert_matches!(res, None); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + let res: ListOrValue> = + api.call("chain_getBlockHash", [ListOrValue::from(0_u64)]).await.unwrap(); assert_matches!( - api.block_hash(Some(ListOrValue::Value(0u64.into())).into()), - Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash() + res, + ListOrValue::Value(Some(ref x)) if x == &client.genesis_hash() ); + + let res: ListOrValue> = + api.call("chain_getBlockHash", [ListOrValue::from(1_u64)]).await.unwrap(); assert_matches!( - api.block_hash(Some(ListOrValue::Value(1u64.into())).into()), - Ok(ListOrValue::Value(Some(ref x))) if x == &block.hash() + res, + ListOrValue::Value(Some(ref x)) if x == &block.hash() ); + + let res: ListOrValue> = api + .call("chain_getBlockHash", [ListOrValue::Value(sp_core::U256::from(1_u64))]) + .await + .unwrap(); assert_matches!( - api.block_hash(Some(ListOrValue::Value(sp_core::U256::from(1u64).into())).into()), - Ok(ListOrValue::Value(Some(ref x))) if x == &block.hash() + res, + ListOrValue::Value(Some(ref x)) if x == &block.hash() ); + let res: ListOrValue> = api + .call("chain_getBlockHash", [ListOrValue::List(vec![0_u64, 1_u64, 2_u64])]) + .await + .unwrap(); assert_matches!( - api.block_hash(Some(vec![0u64.into(), 1u64.into(), 2u64.into()].into())), - Ok(ListOrValue::List(list)) if list == &[client.genesis_hash().into(), block.hash().into(), None] + res, + ListOrValue::List(list) if list == &[client.genesis_hash().into(), block.hash().into(), None] ); } -#[test] -fn should_return_finalized_hash() { +#[tokio::test] +async fn should_return_finalized_hash() { let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); + let api = new_full(client.clone(), test_executor()).into_rpc(); - assert_matches!( - api.finalized_head(), - Ok(ref x) if x == &client.genesis_hash() - ); + let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap(); + assert_eq!(res, client.genesis_hash()); // import new block let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); + let block_hash = block.hash(); + client.import(BlockOrigin::Own, block).await.unwrap(); + // no finalization yet - assert_matches!( - api.finalized_head(), - Ok(ref x) if x == &client.genesis_hash() - ); + let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap(); + assert_eq!(res, client.genesis_hash()); // finalize - client.finalize_block(BlockId::number(1), None).unwrap(); - assert_matches!( - api.finalized_head(), - Ok(ref x) if x == &client.block_hash(1).unwrap().unwrap() - ); + client.finalize_block(block_hash, None).unwrap(); + let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap(); + assert_eq!(res, block_hash); } -#[test] -fn should_notify_about_latest_block() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); - - { - let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); - - api.subscribe_all_heads(Default::default(), subscriber); - - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); - - let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); - } - - // Check for the correct number of notifications - executor::block_on((&mut transport).take(2).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); +#[tokio::test] +async fn should_notify_about_latest_block() { + test_head_subscription("chain_subscribeAllHeads").await; } -#[test] -fn should_notify_about_best_block() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); - - { - let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); - - api.subscribe_new_heads(Default::default(), subscriber); - - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); - - let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); - } - - // Assert that the correct number of notifications have been sent. - executor::block_on((&mut transport).take(2).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); +#[tokio::test] +async fn should_notify_about_best_block() { + test_head_subscription("chain_subscribeNewHeads").await; } -#[test] -fn should_notify_about_finalized_block() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); - - { - let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); - - api.subscribe_finalized_heads(Default::default(), subscriber); +#[tokio::test] +async fn should_notify_about_finalized_block() { + test_head_subscription("chain_subscribeFinalizedHeads").await; +} - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); +async fn test_head_subscription(method: &str) { + let mut client = Arc::new(substrate_test_runtime_client::new()); + let mut sub = { + let api = new_full(client.clone(), test_executor()).into_rpc(); + let sub = api.subscribe(method, EmptyParams::new()).await.unwrap(); let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); - client.finalize_block(BlockId::number(1), None).unwrap(); - } + let block_hash = block.hash(); + client.import(BlockOrigin::Own, block).await.unwrap(); + client.finalize_block(block_hash, None).unwrap(); + sub + }; + + assert_matches!(timeout_secs(10, sub.next::
()).await, Ok(Some(_))); + assert_matches!(timeout_secs(10, sub.next::
()).await, Ok(Some(_))); - // Assert that the correct number of notifications have been sent. - executor::block_on((&mut transport).take(2).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); + sub.close(); + assert_matches!(timeout_secs(10, sub.next::
()).await, Ok(None)); } diff --git a/client/rpc/src/dev/mod.rs b/client/rpc/src/dev/mod.rs new file mode 100644 index 0000000000000..7f4b68f56f6f6 --- /dev/null +++ b/client/rpc/src/dev/mod.rs @@ -0,0 +1,116 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Implementation of the [`DevApiServer`] trait providing debug utilities for Substrate based +//! blockchains. + +#[cfg(test)] +mod tests; + +use jsonrpsee::core::RpcResult; +use sc_client_api::{BlockBackend, HeaderBackend}; +use sc_rpc_api::{dev::error::Error, DenyUnsafe}; +use sp_api::{ApiExt, Core, ProvideRuntimeApi}; +use sp_core::Encode; +use sp_runtime::{ + generic::{BlockId, DigestItem}, + traits::{Block as BlockT, Header}, +}; +use std::{ + marker::{PhantomData, Send, Sync}, + sync::Arc, +}; + +pub use sc_rpc_api::dev::{BlockStats, DevApiServer}; + +type HasherOf = <::Header as Header>::Hashing; + +/// The Dev API. All methods are unsafe. +pub struct Dev { + client: Arc, + deny_unsafe: DenyUnsafe, + _phantom: PhantomData, +} + +impl Dev { + /// Create a new Dev API. + pub fn new(client: Arc, deny_unsafe: DenyUnsafe) -> Self { + Self { client, deny_unsafe, _phantom: PhantomData::default() } + } +} + +impl DevApiServer for Dev +where + Block: BlockT + 'static, + Client: BlockBackend + + HeaderBackend + + ProvideRuntimeApi + + Send + + Sync + + 'static, + Client::Api: Core, +{ + fn block_stats(&self, hash: Block::Hash) -> RpcResult> { + self.deny_unsafe.check_if_safe()?; + + let block = { + let block = self + .client + .block(&BlockId::Hash(hash)) + .map_err(|e| Error::BlockQueryError(Box::new(e)))?; + if let Some(block) = block { + let (mut header, body) = block.block.deconstruct(); + // Remove the `Seal` to ensure we have the number of digests as expected by the + // runtime. + header.digest_mut().logs.retain(|item| !matches!(item, DigestItem::Seal(_, _))); + Block::new(header, body) + } else { + return Ok(None) + } + }; + let parent_header = { + let parent_hash = *block.header().parent_hash(); + let parent_header = self + .client + .header(BlockId::Hash(parent_hash)) + .map_err(|e| Error::BlockQueryError(Box::new(e)))?; + if let Some(header) = parent_header { + header + } else { + return Ok(None) + } + }; + let block_len = block.encoded_size() as u64; + let num_extrinsics = block.extrinsics().len() as u64; + let pre_root = *parent_header.state_root(); + let mut runtime_api = self.client.runtime_api(); + runtime_api.record_proof(); + runtime_api + .execute_block(&BlockId::Hash(parent_header.hash()), block) + .map_err(|_| Error::BlockExecutionFailed)?; + let witness = runtime_api + .extract_proof() + .expect("We enabled proof recording. A proof must be available; qed"); + let witness_len = witness.encoded_size() as u64; + let witness_compact_len = witness + .into_compact_proof::>(pre_root) + .map_err(|_| Error::WitnessCompactionFailed)? + .encoded_size() as u64; + Ok(Some(BlockStats { witness_len, witness_compact_len, block_len, num_extrinsics })) + } +} diff --git a/client/rpc/src/dev/tests.rs b/client/rpc/src/dev/tests.rs new file mode 100644 index 0000000000000..816f3cdfe6025 --- /dev/null +++ b/client/rpc/src/dev/tests.rs @@ -0,0 +1,76 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; +use sc_block_builder::BlockBuilderProvider; +use sp_blockchain::HeaderBackend; +use sp_consensus::BlockOrigin; +use substrate_test_runtime_client::{prelude::*, runtime::Block}; + +#[tokio::test] +async fn block_stats_work() { + let mut client = Arc::new(substrate_test_runtime_client::new()); + let api = >::new(client.clone(), DenyUnsafe::No).into_rpc(); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block).await.unwrap(); + + // Can't gather stats for a block without a parent. + assert_eq!( + api.call::<_, Option>("dev_getBlockStats", [client.genesis_hash()]) + .await + .unwrap(), + None + ); + + assert_eq!( + api.call::<_, Option>("dev_getBlockStats", [client.info().best_hash]) + .await + .unwrap(), + Some(BlockStats { + witness_len: 630, + witness_compact_len: 534, + block_len: 99, + num_extrinsics: 0, + }), + ); +} + +#[tokio::test] +async fn deny_unsafe_works() { + let mut client = Arc::new(substrate_test_runtime_client::new()); + let api = >::new(client.clone(), DenyUnsafe::Yes).into_rpc(); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block).await.unwrap(); + + let best_hash = client.info().best_hash; + let best_hash_param = + serde_json::to_string(&best_hash).expect("To string must always succeed for block hashes"); + + let request = format!( + "{{\"jsonrpc\":\"2.0\",\"method\":\"dev_getBlockStats\",\"params\":[{}],\"id\":1}}", + best_hash_param + ); + let (resp, _) = api.raw_json_request(&request).await.expect("Raw calls should succeed"); + + assert_eq!( + resp.result, + r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"# + ); +} diff --git a/client/rpc/src/lib.rs b/client/rpc/src/lib.rs index 832585db4854c..a0e810eafbb62 100644 --- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,18 +22,18 @@ #![warn(missing_docs)] -use futures::{ - task::{FutureObj, Spawn, SpawnError}, - FutureExt, +pub use jsonrpsee::core::{ + id_providers::{ + RandomIntegerIdProvider as RandomIntegerSubscriptionId, + RandomStringIdProvider as RandomStringSubscriptionId, + }, + traits::IdProvider as RpcSubscriptionIdProvider, }; -use sp_core::traits::SpawnNamed; -use std::sync::Arc; - -pub use rpc::IoHandlerExtension as RpcExtension; -pub use sc_rpc_api::{DenyUnsafe, Metadata}; +pub use sc_rpc_api::DenyUnsafe; pub mod author; pub mod chain; +pub mod dev; pub mod offchain; pub mod state; pub mod system; @@ -42,23 +42,4 @@ pub mod system; pub mod testing; /// Task executor that is being used by RPC subscriptions. -#[derive(Clone)] -pub struct SubscriptionTaskExecutor(Arc); - -impl SubscriptionTaskExecutor { - /// Create a new `Self` with the given spawner. - pub fn new(spawn: impl SpawnNamed + 'static) -> Self { - Self(Arc::new(spawn)) - } -} - -impl Spawn for SubscriptionTaskExecutor { - fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { - self.0.spawn("substrate-rpc-subscription", future.map(drop).boxed()); - Ok(()) - } - - fn status(&self) -> Result<(), SpawnError> { - Ok(()) - } -} +pub type SubscriptionTaskExecutor = std::sync::Arc; diff --git a/client/rpc/src/offchain/mod.rs b/client/rpc/src/offchain/mod.rs index 9d1cc702b51e0..6896b82619166 100644 --- a/client/rpc/src/offchain/mod.rs +++ b/client/rpc/src/offchain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,7 +21,8 @@ #[cfg(test)] mod tests; -use self::error::{Error, Result}; +use self::error::Error; +use jsonrpsee::core::{async_trait, Error as JsonRpseeError, RpcResult}; use parking_lot::RwLock; /// Re-export the API for backward compatibility. pub use sc_rpc_api::offchain::*; @@ -47,27 +48,27 @@ impl Offchain { } } -impl OffchainApi for Offchain { - /// Set offchain local storage under given key and prefix. - fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<()> { +#[async_trait] +impl OffchainApiServer for Offchain { + fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> RpcResult<()> { self.deny_unsafe.check_if_safe()?; let prefix = match kind { StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, - StorageKind::LOCAL => return Err(Error::UnavailableStorageKind), + StorageKind::LOCAL => return Err(JsonRpseeError::from(Error::UnavailableStorageKind)), }; - self.storage.write().set(prefix, &*key, &*value); + self.storage.write().set(prefix, &key, &value); Ok(()) } - /// Get offchain local storage under given key and prefix. - fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result> { + fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> RpcResult> { self.deny_unsafe.check_if_safe()?; let prefix = match kind { StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, - StorageKind::LOCAL => return Err(Error::UnavailableStorageKind), + StorageKind::LOCAL => return Err(JsonRpseeError::from(Error::UnavailableStorageKind)), }; - Ok(self.storage.read().get(prefix, &*key).map(Into::into)) + + Ok(self.storage.read().get(prefix, &key).map(Into::into)) } } diff --git a/client/rpc/src/offchain/tests.rs b/client/rpc/src/offchain/tests.rs index f9629e70198a3..28a7b6115b657 100644 --- a/client/rpc/src/offchain/tests.rs +++ b/client/rpc/src/offchain/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -39,6 +39,7 @@ fn local_storage_should_work() { #[test] fn offchain_calls_considered_unsafe() { + use jsonrpsee::types::error::CallError; let storage = InMemOffchainStorage::default(); let offchain = Offchain::new(storage, DenyUnsafe::Yes); let key = Bytes(b"offchain_storage".to_vec()); @@ -46,10 +47,14 @@ fn offchain_calls_considered_unsafe() { assert_matches!( offchain.set_local_storage(StorageKind::PERSISTENT, key.clone(), value.clone()), - Err(Error::UnsafeRpcCalled(_)) + Err(JsonRpseeError::Call(CallError::Custom(err))) => { + assert_eq!(err.message(), "RPC call is unsafe to be called externally") + } ); assert_matches!( offchain.get_local_storage(StorageKind::PERSISTENT, key), - Err(Error::UnsafeRpcCalled(_)) + Err(JsonRpseeError::Call(CallError::Custom(err))) => { + assert_eq!(err.message(), "RPC call is unsafe to be called externally") + } ); } diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index 80eccc2c97deb..fd802e5a80391 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,17 +19,19 @@ //! Substrate state API. mod state_full; -mod state_light; #[cfg(test)] mod tests; -use futures::FutureExt; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; -use rpc::Result as RpcResult; use std::sync::Arc; -use sc_client_api::light::{Fetcher, RemoteBlockchain}; +use crate::SubscriptionTaskExecutor; + +use jsonrpsee::{ + core::{server::rpc_module::SubscriptionSink, Error as JsonRpseeError, RpcResult}, + types::SubscriptionResult, +}; + use sc_rpc_api::{state::ReadProof, DenyUnsafe}; use sp_core::{ storage::{PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey}, @@ -40,7 +42,7 @@ use sp_version::RuntimeVersion; use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi}; -use self::error::{Error, FutureResult}; +use self::error::Error; use sc_client_api::{ Backend, BlockBackend, BlockchainEvents, ExecutorProvider, ProofProvider, StorageProvider, @@ -62,21 +64,21 @@ where block: Option, method: String, call_data: Bytes, - ) -> FutureResult; + ) -> Result; /// Returns the keys with prefix, leave empty to get all the keys. fn storage_keys( &self, block: Option, prefix: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the keys with prefix along with their values, leave empty to get all the pairs. fn storage_pairs( &self, block: Option, prefix: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the keys with prefix with pagination support. fn storage_keys_paged( @@ -85,21 +87,21 @@ where prefix: Option, count: u32, start_key: Option, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns a storage entry at a specific block's state. fn storage( &self, block: Option, key: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the hash of a storage entry at a block's state. fn storage_hash( &self, block: Option, key: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the size of a storage entry at a block's state. /// @@ -109,13 +111,13 @@ where &self, block: Option, key: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the runtime metadata as an opaque blob. - fn metadata(&self, block: Option) -> FutureResult; + fn metadata(&self, block: Option) -> Result; /// Get the runtime version. - fn runtime_version(&self, block: Option) -> FutureResult; + fn runtime_version(&self, block: Option) -> Result; /// Query historical storage entries (by key) starting from a block given as the second /// parameter. @@ -127,50 +129,21 @@ where from: Block::Hash, to: Option, keys: Vec, - ) -> FutureResult>>; + ) -> Result>, Error>; /// Query storage entries (by key) starting at block hash given as the second parameter. fn query_storage_at( &self, keys: Vec, at: Option, - ) -> FutureResult>>; + ) -> Result>, Error>; /// Returns proof of storage entries at a specific block's state. fn read_proof( &self, block: Option, keys: Vec, - ) -> FutureResult>; - - /// New runtime version subscription - fn subscribe_runtime_version( - &self, - _meta: crate::Metadata, - subscriber: Subscriber, - ); - - /// Unsubscribe from runtime version subscription - fn unsubscribe_runtime_version( - &self, - _meta: Option, - id: SubscriptionId, - ) -> RpcResult; - - /// New storage subscription - fn subscribe_storage( - &self, - _meta: crate::Metadata, - subscriber: Subscriber>, - keys: Option>, - ); - - /// Unsubscribe from storage subscription - fn unsubscribe_storage( - &self, - _meta: Option, - id: SubscriptionId, - ) -> RpcResult; + ) -> Result, Error>; /// Trace storage changes for block fn trace_block( @@ -179,13 +152,19 @@ where targets: Option, storage_keys: Option, methods: Option, - ) -> FutureResult; + ) -> Result; + + /// New runtime version subscription + fn subscribe_runtime_version(&self, sink: SubscriptionSink); + + /// New storage subscription + fn subscribe_storage(&self, sink: SubscriptionSink, keys: Option>); } /// Create new state API that works on full node. pub fn new_full( client: Arc, - subscriptions: SubscriptionManager, + executor: SubscriptionTaskExecutor, deny_unsafe: DenyUnsafe, rpc_max_payload: Option, ) -> (State, ChildState) @@ -209,50 +188,10 @@ where { let child_backend = Box::new(self::state_full::FullState::new( client.clone(), - subscriptions.clone(), + executor.clone(), rpc_max_payload, )); - let backend = - Box::new(self::state_full::FullState::new(client, subscriptions, rpc_max_payload)); - (State { backend, deny_unsafe }, ChildState { backend: child_backend }) -} - -/// Create new state API that works on light node. -pub fn new_light>( - client: Arc, - subscriptions: SubscriptionManager, - remote_blockchain: Arc>, - fetcher: Arc, - deny_unsafe: DenyUnsafe, -) -> (State, ChildState) -where - Block: BlockT + 'static, - Block::Hash: Unpin, - BE: Backend + 'static, - Client: ExecutorProvider - + StorageProvider - + HeaderMetadata - + ProvideRuntimeApi - + HeaderBackend - + BlockchainEvents - + Send - + Sync - + 'static, - F: Send + Sync + 'static, -{ - let child_backend = Box::new(self::state_light::LightState::new( - client.clone(), - subscriptions.clone(), - remote_blockchain.clone(), - fetcher.clone(), - )); - - let backend = Box::new(self::state_light::LightState::new( - client, - subscriptions, - remote_blockchain, - fetcher, - )); + let backend = Box::new(self::state_full::FullState::new(client, executor, rpc_max_payload)); (State { backend, deny_unsafe }, ChildState { backend: child_backend }) } @@ -263,35 +202,30 @@ pub struct State { deny_unsafe: DenyUnsafe, } -impl StateApi for State +impl StateApiServer for State where Block: BlockT + 'static, Client: Send + Sync + 'static, { - type Metadata = crate::Metadata; - - fn call(&self, method: String, data: Bytes, block: Option) -> FutureResult { - self.backend.call(block, method, data) + fn call(&self, method: String, data: Bytes, block: Option) -> RpcResult { + self.backend.call(block, method, data).map_err(Into::into) } fn storage_keys( &self, key_prefix: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage_keys(block, key_prefix) + ) -> RpcResult> { + self.backend.storage_keys(block, key_prefix).map_err(Into::into) } fn storage_pairs( &self, key_prefix: StorageKey, block: Option, - ) -> FutureResult> { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return async move { Err(err.into()) }.boxed() - } - - self.backend.storage_pairs(block, key_prefix) + ) -> RpcResult> { + self.deny_unsafe.check_if_safe()?; + self.backend.storage_pairs(block, key_prefix).map_err(Into::into) } fn storage_keys_paged( @@ -300,42 +234,44 @@ where count: u32, start_key: Option, block: Option, - ) -> FutureResult> { + ) -> RpcResult> { if count > STORAGE_KEYS_PAGED_MAX_COUNT { - return async move { - Err(Error::InvalidCount { value: count, max: STORAGE_KEYS_PAGED_MAX_COUNT }) - } - .boxed() + return Err(JsonRpseeError::from(Error::InvalidCount { + value: count, + max: STORAGE_KEYS_PAGED_MAX_COUNT, + })) } - self.backend.storage_keys_paged(block, prefix, count, start_key) + self.backend + .storage_keys_paged(block, prefix, count, start_key) + .map_err(Into::into) } fn storage( &self, key: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage(block, key) + ) -> RpcResult> { + self.backend.storage(block, key).map_err(Into::into) } fn storage_hash( &self, key: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage_hash(block, key) + ) -> RpcResult> { + self.backend.storage_hash(block, key).map_err(Into::into) } - fn storage_size( - &self, - key: StorageKey, - block: Option, - ) -> FutureResult> { - self.backend.storage_size(block, key) + fn storage_size(&self, key: StorageKey, block: Option) -> RpcResult> { + self.backend.storage_size(block, key).map_err(Into::into) + } + + fn metadata(&self, block: Option) -> RpcResult { + self.backend.metadata(block).map_err(Into::into) } - fn metadata(&self, block: Option) -> FutureResult { - self.backend.metadata(block) + fn runtime_version(&self, at: Option) -> RpcResult { + self.backend.runtime_version(at).map_err(Into::into) } fn query_storage( @@ -343,65 +279,25 @@ where keys: Vec, from: Block::Hash, to: Option, - ) -> FutureResult>> { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return async move { Err(err.into()) }.boxed() - } - - self.backend.query_storage(from, to, keys) + ) -> RpcResult>> { + self.deny_unsafe.check_if_safe()?; + self.backend.query_storage(from, to, keys).map_err(Into::into) } fn query_storage_at( &self, keys: Vec, at: Option, - ) -> FutureResult>> { - self.backend.query_storage_at(keys, at) + ) -> RpcResult>> { + self.backend.query_storage_at(keys, at).map_err(Into::into) } fn read_proof( &self, keys: Vec, block: Option, - ) -> FutureResult> { - self.backend.read_proof(block, keys) - } - - fn subscribe_storage( - &self, - meta: Self::Metadata, - subscriber: Subscriber>, - keys: Option>, - ) { - self.backend.subscribe_storage(meta, subscriber, keys); - } - - fn unsubscribe_storage( - &self, - meta: Option, - id: SubscriptionId, - ) -> RpcResult { - self.backend.unsubscribe_storage(meta, id) - } - - fn runtime_version(&self, at: Option) -> FutureResult { - self.backend.runtime_version(at) - } - - fn subscribe_runtime_version( - &self, - meta: Self::Metadata, - subscriber: Subscriber, - ) { - self.backend.subscribe_runtime_version(meta, subscriber); - } - - fn unsubscribe_runtime_version( - &self, - meta: Option, - id: SubscriptionId, - ) -> RpcResult { - self.backend.unsubscribe_runtime_version(meta, id) + ) -> RpcResult> { + self.backend.read_proof(block, keys).map_err(Into::into) } /// Re-execute the given block with the tracing targets given in `targets` @@ -415,12 +311,32 @@ where targets: Option, storage_keys: Option, methods: Option, - ) -> FutureResult { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return async move { Err(err.into()) }.boxed() + ) -> RpcResult { + self.deny_unsafe.check_if_safe()?; + self.backend + .trace_block(block, targets, storage_keys, methods) + .map_err(Into::into) + } + + fn subscribe_runtime_version(&self, sink: SubscriptionSink) -> SubscriptionResult { + self.backend.subscribe_runtime_version(sink); + Ok(()) + } + + fn subscribe_storage( + &self, + mut sink: SubscriptionSink, + keys: Option>, + ) -> SubscriptionResult { + if keys.is_none() { + if let Err(err) = self.deny_unsafe.check_if_safe() { + let _ = sink.reject(JsonRpseeError::from(err)); + return Ok(()) + } } - self.backend.trace_block(block, targets, storage_keys, methods) + self.backend.subscribe_storage(sink, keys); + Ok(()) } } @@ -436,7 +352,7 @@ where block: Option, storage_key: PrefixedStorageKey, keys: Vec, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the keys with prefix from a child storage, /// leave prefix empty to get all the keys. @@ -445,7 +361,7 @@ where block: Option, storage_key: PrefixedStorageKey, prefix: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the keys with prefix from a child storage with pagination support. fn storage_keys_paged( @@ -455,7 +371,7 @@ where prefix: Option, count: u32, start_key: Option, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns a child storage entry at a specific block's state. fn storage( @@ -463,7 +379,7 @@ where block: Option, storage_key: PrefixedStorageKey, key: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns child storage entries at a specific block's state. fn storage_entries( @@ -471,7 +387,7 @@ where block: Option, storage_key: PrefixedStorageKey, keys: Vec, - ) -> FutureResult>>; + ) -> Result>, Error>; /// Returns the hash of a child storage entry at a block's state. fn storage_hash( @@ -479,7 +395,7 @@ where block: Option, storage_key: PrefixedStorageKey, key: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the size of a child storage entry at a block's state. fn storage_size( @@ -487,10 +403,8 @@ where block: Option, storage_key: PrefixedStorageKey, key: StorageKey, - ) -> FutureResult> { - self.storage(block, storage_key, key) - .map(|x| x.map(|r| r.map(|v| v.0.len() as u64))) - .boxed() + ) -> Result, Error> { + self.storage(block, storage_key, key).map(|x| x.map(|x| x.0.len() as u64)) } } @@ -499,58 +413,49 @@ pub struct ChildState { backend: Box>, } -impl ChildStateApi for ChildState +impl ChildStateApiServer for ChildState where Block: BlockT + 'static, Client: Send + Sync + 'static, { - type Metadata = crate::Metadata; - - fn read_child_proof( - &self, - child_storage_key: PrefixedStorageKey, - keys: Vec, - block: Option, - ) -> FutureResult> { - self.backend.read_child_proof(block, child_storage_key, keys) - } - - fn storage( + fn storage_keys( &self, storage_key: PrefixedStorageKey, - key: StorageKey, + key_prefix: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage(block, storage_key, key) + ) -> RpcResult> { + self.backend.storage_keys(block, storage_key, key_prefix).map_err(Into::into) } - fn storage_entries( + fn storage_keys_paged( &self, storage_key: PrefixedStorageKey, - keys: Vec, + prefix: Option, + count: u32, + start_key: Option, block: Option, - ) -> FutureResult>> { - self.backend.storage_entries(block, storage_key, keys) + ) -> RpcResult> { + self.backend + .storage_keys_paged(block, storage_key, prefix, count, start_key) + .map_err(Into::into) } - fn storage_keys( + fn storage( &self, storage_key: PrefixedStorageKey, - key_prefix: StorageKey, + key: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage_keys(block, storage_key, key_prefix) + ) -> RpcResult> { + self.backend.storage(block, storage_key, key).map_err(Into::into) } - fn storage_keys_paged( + fn storage_entries( &self, storage_key: PrefixedStorageKey, - prefix: Option, - count: u32, - start_key: Option, + keys: Vec, block: Option, - ) -> FutureResult> { - self.backend.storage_keys_paged(block, storage_key, prefix, count, start_key) + ) -> RpcResult>> { + self.backend.storage_entries(block, storage_key, keys).map_err(Into::into) } fn storage_hash( @@ -558,8 +463,8 @@ where storage_key: PrefixedStorageKey, key: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage_hash(block, storage_key, key) + ) -> RpcResult> { + self.backend.storage_hash(block, storage_key, key).map_err(Into::into) } fn storage_size( @@ -567,8 +472,19 @@ where storage_key: PrefixedStorageKey, key: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage_size(block, storage_key, key) + ) -> RpcResult> { + self.backend.storage_size(block, storage_key, key).map_err(Into::into) + } + + fn read_child_proof( + &self, + child_storage_key: PrefixedStorageKey, + keys: Vec, + block: Option, + ) -> RpcResult> { + self.backend + .read_child_proof(block, child_storage_key, keys) + .map_err(Into::into) } } diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index 97f77a4077962..58aeac66e5c79 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,21 +18,23 @@ //! State API backend for full nodes. -use futures::{ - future, - future::{err, try_join_all}, - stream, FutureExt, SinkExt, StreamExt, -}; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; -use log::warn; -use rpc::Result as RpcResult; -use std::{ - collections::{BTreeMap, HashMap}, - ops::Range, - sync::Arc, +use std::{collections::HashMap, marker::PhantomData, sync::Arc}; + +use super::{ + client_err, + error::{Error, Result}, + ChildStateBackend, StateBackend, }; +use crate::SubscriptionTaskExecutor; +use futures::{future, stream, FutureExt, StreamExt}; +use jsonrpsee::{core::Error as JsonRpseeError, SubscriptionSink}; +use sc_client_api::{ + Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, ProofProvider, + StorageProvider, +}; use sc_rpc_api::state::ReadProof; +use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi}; use sp_blockchain::{ CachedHeaderMetadata, Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult, @@ -43,43 +45,19 @@ use sp_core::{ }, Bytes, }; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, CheckedSub, NumberFor, SaturatedConversion}, -}; +use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use sp_version::RuntimeVersion; -use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi}; - -use super::{ - client_err, - error::{Error, FutureResult, Result}, - ChildStateBackend, StateBackend, -}; -use sc_client_api::{ - Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, ProofProvider, - StorageProvider, -}; -use std::marker::PhantomData; - /// Ranges to query in state_queryStorage. struct QueryStorageRange { /// Hashes of all the blocks in the range. pub hashes: Vec, - /// Number of the first block in the range. - pub first_number: NumberFor, - /// Blocks subrange ([begin; end) indices within `hashes`) where we should read keys at - /// each state to get changes. - pub unfiltered_range: Range, - /// Blocks subrange ([begin; end) indices within `hashes`) where we could pre-filter - /// blocks-with-changes by using changes tries. - pub filtered_range: Option>, } /// State API backend for full nodes. pub struct FullState { client: Arc, - subscriptions: SubscriptionManager, + executor: SubscriptionTaskExecutor, _phantom: PhantomData<(BE, Block)>, rpc_max_payload: Option, } @@ -96,10 +74,10 @@ where /// Create new state API backend for full nodes. pub fn new( client: Arc, - subscriptions: SubscriptionManager, + executor: SubscriptionTaskExecutor, rpc_max_payload: Option, ) -> Self { - Self { client, subscriptions, _phantom: PhantomData, rpc_max_payload } + Self { client, executor, _phantom: PhantomData, rpc_max_payload } } /// Returns given block hash or best block hash if None is passed. @@ -107,10 +85,8 @@ where Ok(hash.unwrap_or_else(|| self.client.info().best_hash)) } - /// Splits the `query_storage` block range into 'filtered' and 'unfiltered' subranges. - /// Blocks that contain changes within filtered subrange could be filtered using changes tries. - /// Blocks that contain changes within unfiltered subrange must be filtered manually. - fn split_query_storage_range( + /// Validates block range. + fn query_storage_range( &self, from: Block::Hash, to: Option, @@ -156,23 +132,7 @@ where hashes }; - // check if we can filter blocks-with-changes from some (sub)range using changes tries - let changes_trie_range = self - .client - .max_key_changes_range(from_number, BlockId::Hash(to_meta.hash)) - .map_err(client_err)?; - let filtered_range_begin = changes_trie_range.and_then(|(begin, _)| { - // avoids a corner case where begin < from_number (happens when querying genesis) - begin.checked_sub(&from_number).map(|x| x.saturated_into::()) - }); - let (unfiltered_range, filtered_range) = split_range(hashes.len(), filtered_range_begin); - - Ok(QueryStorageRange { - hashes, - first_number: from_number, - unfiltered_range, - filtered_range, - }) + Ok(QueryStorageRange { hashes }) } /// Iterates through range.unfiltered_range and check each block for changes of keys' values. @@ -183,14 +143,11 @@ where last_values: &mut HashMap>, changes: &mut Vec>, ) -> Result<()> { - for block in range.unfiltered_range.start..range.unfiltered_range.end { - let block_hash = range.hashes[block].clone(); - let mut block_changes = - StorageChangeSet { block: block_hash.clone(), changes: Vec::new() }; - let id = BlockId::hash(block_hash); + for block_hash in &range.hashes { + let mut block_changes = StorageChangeSet { block: *block_hash, changes: Vec::new() }; for key in keys { let (has_changed, data) = { - let curr_data = self.client.storage(&id, key).map_err(client_err)?; + let curr_data = self.client.storage(*block_hash, key).map_err(client_err)?; match last_values.get(key) { Some(prev_data) => (curr_data != *prev_data, curr_data), None => (true, curr_data), @@ -207,57 +164,6 @@ where } Ok(()) } - - /// Iterates through all blocks that are changing keys within range.filtered_range and collects - /// these changes. - fn query_storage_filtered( - &self, - range: &QueryStorageRange, - keys: &[StorageKey], - last_values: &HashMap>, - changes: &mut Vec>, - ) -> Result<()> { - let (begin, end) = match range.filtered_range { - Some(ref filtered_range) => ( - range.first_number + filtered_range.start.saturated_into(), - BlockId::Hash(range.hashes[filtered_range.end - 1].clone()), - ), - None => return Ok(()), - }; - let mut changes_map: BTreeMap, StorageChangeSet> = - BTreeMap::new(); - for key in keys { - let mut last_block = None; - let mut last_value = last_values.get(key).cloned().unwrap_or_default(); - let key_changes = self.client.key_changes(begin, end, None, key).map_err(client_err)?; - for (block, _) in key_changes.into_iter().rev() { - if last_block == Some(block) { - continue - } - - let block_hash = - range.hashes[(block - range.first_number).saturated_into::()].clone(); - let id = BlockId::Hash(block_hash); - let value_at_block = self.client.storage(&id, key).map_err(client_err)?; - if last_value == value_at_block { - continue - } - - changes_map - .entry(block) - .or_insert_with(|| StorageChangeSet { block: block_hash, changes: Vec::new() }) - .changes - .push((key.clone(), value_at_block.clone())); - last_block = Some(block); - last_value = value_at_block; - } - } - if let Some(additional_capacity) = changes_map.len().checked_sub(changes.len()) { - changes.reserve(additional_capacity); - } - changes.extend(changes_map.into_iter().map(|(_, cs)| cs)); - Ok(()) - } } impl StateBackend for FullState @@ -284,47 +190,40 @@ where block: Option, method: String, call_data: Bytes, - ) -> FutureResult { - let r = self - .block_or_best(block) + ) -> std::result::Result { + self.block_or_best(block) .and_then(|block| { self.client .executor() .call( &BlockId::Hash(block), &method, - &*call_data, + &call_data, self.client.execution_extensions().strategies().other, - None, ) .map(Into::into) }) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } fn storage_keys( &self, block: Option, prefix: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) - .and_then(|block| self.client.storage_keys(&BlockId::Hash(block), &prefix)) - .map_err(client_err); - async move { r }.boxed() + ) -> std::result::Result, Error> { + self.block_or_best(block) + .and_then(|block| self.client.storage_keys(block, &prefix)) + .map_err(client_err) } fn storage_pairs( &self, block: Option, prefix: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) - .and_then(|block| self.client.storage_pairs(&BlockId::Hash(block), &prefix)) - .map_err(client_err); - async move { r }.boxed() + ) -> std::result::Result, Error> { + self.block_or_best(block) + .and_then(|block| self.client.storage_pairs(block, &prefix)) + .map_err(client_err) } fn storage_keys_paged( @@ -333,52 +232,43 @@ where prefix: Option, count: u32, start_key: Option, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { - self.client.storage_keys_iter( - &BlockId::Hash(block), - prefix.as_ref(), - start_key.as_ref(), - ) + self.client.storage_keys_iter(block, prefix.as_ref(), start_key.as_ref()) }) .map(|iter| iter.take(count as usize).collect()) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } fn storage( &self, block: Option, key: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) - .and_then(|block| self.client.storage(&BlockId::Hash(block), &key)) - .map_err(client_err); - async move { r }.boxed() + ) -> std::result::Result, Error> { + self.block_or_best(block) + .and_then(|block| self.client.storage(block, &key)) + .map_err(client_err) } fn storage_size( &self, block: Option, key: StorageKey, - ) -> FutureResult> { + ) -> std::result::Result, Error> { let block = match self.block_or_best(block) { Ok(b) => b, - Err(e) => return async move { Err(client_err(e)) }.boxed(), + Err(e) => return Err(client_err(e)), }; - match self.client.storage(&BlockId::Hash(block), &key) { - Ok(Some(d)) => return async move { Ok(Some(d.0.len() as u64)) }.boxed(), - Err(e) => return async move { Err(client_err(e)) }.boxed(), + match self.client.storage(block, &key) { + Ok(Some(d)) => return Ok(Some(d.0.len() as u64)), + Err(e) => return Err(client_err(e)), Ok(None) => {}, } - let r = self - .client - .storage_pairs(&BlockId::Hash(block), &key) + self.client + .storage_pairs(block, &key) .map(|kv| { let item_sum = kv.iter().map(|(_, v)| v.0.len() as u64).sum::(); if item_sum > 0 { @@ -387,40 +277,38 @@ where None } }) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } fn storage_hash( &self, block: Option, key: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) - .and_then(|block| self.client.storage_hash(&BlockId::Hash(block), &key)) - .map_err(client_err); - async move { r }.boxed() + ) -> std::result::Result, Error> { + self.block_or_best(block) + .and_then(|block| self.client.storage_hash(block, &key)) + .map_err(client_err) } - fn metadata(&self, block: Option) -> FutureResult { - let r = self.block_or_best(block).map_err(client_err).and_then(|block| { + fn metadata(&self, block: Option) -> std::result::Result { + self.block_or_best(block).map_err(client_err).and_then(|block| { self.client .runtime_api() .metadata(&BlockId::Hash(block)) .map(Into::into) .map_err(|e| Error::Client(Box::new(e))) - }); - async move { r }.boxed() + }) } - fn runtime_version(&self, block: Option) -> FutureResult { - let r = self.block_or_best(block).map_err(client_err).and_then(|block| { + fn runtime_version( + &self, + block: Option, + ) -> std::result::Result { + self.block_or_best(block).map_err(client_err).and_then(|block| { self.client .runtime_version_at(&BlockId::Hash(block)) .map_err(|e| Error::Client(Box::new(e))) - }); - async move { r }.boxed() + }) } fn query_storage( @@ -428,25 +316,22 @@ where from: Block::Hash, to: Option, keys: Vec, - ) -> FutureResult>> { + ) -> std::result::Result>, Error> { let call_fn = move || { - let range = self.split_query_storage_range(from, to)?; + let range = self.query_storage_range(from, to)?; let mut changes = Vec::new(); let mut last_values = HashMap::new(); self.query_storage_unfiltered(&range, &keys, &mut last_values, &mut changes)?; - self.query_storage_filtered(&range, &keys, &last_values, &mut changes)?; Ok(changes) }; - - let r = call_fn(); - async move { r }.boxed() + call_fn() } fn query_storage_at( &self, keys: Vec, at: Option, - ) -> FutureResult>> { + ) -> std::result::Result>, Error> { let at = at.unwrap_or_else(|| self.client.info().best_hash); self.query_storage(at, Some(at), keys) } @@ -455,129 +340,104 @@ where &self, block: Option, keys: Vec, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { self.client - .read_proof(&BlockId::Hash(block), &mut keys.iter().map(|key| key.0.as_ref())) - .map(|proof| proof.iter_nodes().map(|node| node.into()).collect()) + .read_proof(block, &mut keys.iter().map(|key| key.0.as_ref())) + .map(|proof| proof.into_iter_nodes().map(|node| node.into()).collect()) .map(|proof| ReadProof { at: block, proof }) }) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } - fn subscribe_runtime_version( - &self, - _meta: crate::Metadata, - subscriber: Subscriber, - ) { - self.subscriptions.add(subscriber, |sink| { - let version = self - .block_or_best(None) - .and_then(|block| { - self.client.runtime_version_at(&BlockId::Hash(block)).map_err(Into::into) - }) - .map_err(client_err) - .map_err(Into::into); + fn subscribe_runtime_version(&self, mut sink: SubscriptionSink) { + let client = self.client.clone(); - let client = self.client.clone(); - let mut previous_version = version.clone(); + let initial = match self + .block_or_best(None) + .and_then(|block| { + self.client.runtime_version_at(&BlockId::Hash(block)).map_err(Into::into) + }) + .map_err(|e| Error::Client(Box::new(e))) + { + Ok(initial) => initial, + Err(e) => { + let _ = sink.reject(JsonRpseeError::from(e)); + return + }, + }; - // A stream of all best blocks. - let stream = - client.import_notification_stream().filter(|n| future::ready(n.is_new_best)); + let mut previous_version = initial.clone(); - let stream = stream.filter_map(move |n| { + // A stream of new versions + let version_stream = client + .import_notification_stream() + .filter(|n| future::ready(n.is_new_best)) + .filter_map(move |n| { let version = client .runtime_version_at(&BlockId::hash(n.hash)) - .map_err(|e| Error::Client(Box::new(e))) - .map_err(Into::into); - - if previous_version != version { - previous_version = version.clone(); - future::ready(Some(Ok::<_, ()>(version))) - } else { - future::ready(None) + .map_err(|e| Error::Client(Box::new(e))); + + match version { + Ok(version) if version != previous_version => { + previous_version = version.clone(); + future::ready(Some(version)) + }, + _ => future::ready(None), } }); - stream::iter(vec![Ok(version)]) - .chain(stream) - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - // we ignore the resulting Stream (if the first stream is over we are unsubscribed) - .map(|_| ()) - }); - } + let stream = futures::stream::once(future::ready(initial)).chain(version_stream); - fn unsubscribe_runtime_version( - &self, - _meta: Option, - id: SubscriptionId, - ) -> RpcResult { - Ok(self.subscriptions.cancel(id)) + let fut = async move { + sink.pipe_from_stream(stream).await; + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } - fn subscribe_storage( - &self, - _meta: crate::Metadata, - subscriber: Subscriber>, - keys: Option>, - ) { - let keys = Into::>>::into(keys); - let stream = match self - .client - .storage_changes_notification_stream(keys.as_ref().map(|x| &**x), None) - { + fn subscribe_storage(&self, mut sink: SubscriptionSink, keys: Option>) { + let stream = match self.client.storage_changes_notification_stream(keys.as_deref(), None) { Ok(stream) => stream, - Err(err) => { - let _ = subscriber.reject(client_err(err).into()); + Err(blockchain_err) => { + let _ = sink.reject(JsonRpseeError::from(Error::Client(Box::new(blockchain_err)))); return }, }; // initial values - let initial = stream::iter( - keys.map(|keys| { - let block = self.client.info().best_hash; - let changes = keys - .into_iter() - .map(|key| { - let v = self.client.storage(&BlockId::Hash(block), &key).ok().flatten(); - (key, v) - }) - .collect(); - vec![Ok(Ok(StorageChangeSet { block, changes }))] - }) - .unwrap_or_default(), - ); + let initial = stream::iter(keys.map(|keys| { + let block = self.client.info().best_hash; + let changes = keys + .into_iter() + .map(|key| { + let v = self.client.storage(block, &key).ok().flatten(); + (key, v) + }) + .collect(); + StorageChangeSet { block, changes } + })); + + // let storage_stream = stream.map(|(block, changes)| StorageChangeSet { + let storage_stream = stream.map(|storage_notif| StorageChangeSet { + block: storage_notif.block, + changes: storage_notif + .changes + .iter() + .filter_map(|(o_sk, k, v)| o_sk.is_none().then(|| (k.clone(), v.cloned()))) + .collect(), + }); - self.subscriptions.add(subscriber, |sink| { - let stream = stream.map(|(block, changes)| { - Ok(Ok::<_, rpc::Error>(StorageChangeSet { - block, - changes: changes - .iter() - .filter_map(|(o_sk, k, v)| o_sk.is_none().then(|| (k.clone(), v.cloned()))) - .collect(), - })) - }); + let stream = initial + .chain(storage_stream) + .filter(|storage| future::ready(!storage.changes.is_empty())); - initial - .chain(stream) - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - // we ignore the resulting Stream (if the first stream is over we are unsubscribed) - .map(|_| ()) - }); - } + let fut = async move { + sink.pipe_from_stream(stream).await; + }; - fn unsubscribe_storage( - &self, - _meta: Option, - id: SubscriptionId, - ) -> RpcResult { - Ok(self.subscriptions.cancel(id)) + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } fn trace_block( @@ -586,19 +446,17 @@ where targets: Option, storage_keys: Option, methods: Option, - ) -> FutureResult { - let block_executor = sc_tracing::block::BlockExecutor::new( + ) -> std::result::Result { + sc_tracing::block::BlockExecutor::new( self.client.clone(), block, targets, storage_keys, methods, self.rpc_max_payload, - ); - let r = block_executor - .trace_block() - .map_err(|e| invalid_block::(block, None, e.to_string())); - async move { r }.boxed() + ) + .trace_block() + .map_err(|e| invalid_block::(block, None, e.to_string())) } } @@ -625,9 +483,8 @@ where block: Option, storage_key: PrefixedStorageKey, keys: Vec, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { Some((ChildType::ParentKeyId, storage_key)) => @@ -636,16 +493,14 @@ where }; self.client .read_child_proof( - &BlockId::Hash(block), + block, &child_info, &mut keys.iter().map(|key| key.0.as_ref()), ) - .map(|proof| proof.iter_nodes().map(|node| node.into()).collect()) + .map(|proof| proof.into_iter_nodes().map(|node| node.into()).collect()) .map(|proof| ReadProof { at: block, proof }) }) - .map_err(client_err); - - async move { r }.boxed() + .map_err(client_err) } fn storage_keys( @@ -653,20 +508,17 @@ where block: Option, storage_key: PrefixedStorageKey, prefix: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { Some((ChildType::ParentKeyId, storage_key)) => ChildInfo::new_default(storage_key), None => return Err(sp_blockchain::Error::InvalidChildStorageKey), }; - self.client.child_storage_keys(&BlockId::Hash(block), &child_info, &prefix) + self.client.child_storage_keys(block, &child_info, &prefix) }) - .map_err(client_err); - - async move { r }.boxed() + .map_err(client_err) } fn storage_keys_paged( @@ -676,9 +528,8 @@ where prefix: Option, count: u32, start_key: Option, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { Some((ChildType::ParentKeyId, storage_key)) => @@ -686,16 +537,14 @@ where None => return Err(sp_blockchain::Error::InvalidChildStorageKey), }; self.client.child_storage_keys_iter( - &BlockId::Hash(block), + block, child_info, prefix.as_ref(), start_key.as_ref(), ) }) .map(|iter| iter.take(count as usize).collect()) - .map_err(client_err); - - async move { r }.boxed() + .map_err(client_err) } fn storage( @@ -703,20 +552,17 @@ where block: Option, storage_key: PrefixedStorageKey, key: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { Some((ChildType::ParentKeyId, storage_key)) => ChildInfo::new_default(storage_key), None => return Err(sp_blockchain::Error::InvalidChildStorageKey), }; - self.client.child_storage(&BlockId::Hash(block), &child_info, &key) + self.client.child_storage(block, &child_info, &key) }) - .map_err(client_err); - - async move { r }.boxed() + .map_err(client_err) } fn storage_entries( @@ -724,26 +570,22 @@ where block: Option, storage_key: PrefixedStorageKey, keys: Vec, - ) -> FutureResult>> { - let child_info = match ChildType::from_prefixed_key(&storage_key) { - Some((ChildType::ParentKeyId, storage_key)) => - Arc::new(ChildInfo::new_default(storage_key)), - None => return err(client_err(sp_blockchain::Error::InvalidChildStorageKey)).boxed(), - }; - let block = match self.block_or_best(block) { - Ok(b) => b, - Err(e) => return err(client_err(e)).boxed(), + ) -> std::result::Result>, Error> { + let child_info = if let Some((ChildType::ParentKeyId, storage_key)) = + ChildType::from_prefixed_key(&storage_key) + { + Arc::new(ChildInfo::new_default(storage_key)) + } else { + return Err(client_err(sp_blockchain::Error::InvalidChildStorageKey)) }; + let block = self.block_or_best(block).map_err(client_err)?; let client = self.client.clone(); - try_join_all(keys.into_iter().map(move |key| { - let res = client - .clone() - .child_storage(&BlockId::Hash(block), &child_info, &key) - .map_err(client_err); - - async move { res } - })) - .boxed() + + keys.into_iter() + .map(move |key| { + client.clone().child_storage(block, &child_info, &key).map_err(client_err) + }) + .collect() } fn storage_hash( @@ -751,47 +593,20 @@ where block: Option, storage_key: PrefixedStorageKey, key: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { Some((ChildType::ParentKeyId, storage_key)) => ChildInfo::new_default(storage_key), None => return Err(sp_blockchain::Error::InvalidChildStorageKey), }; - self.client.child_storage_hash(&BlockId::Hash(block), &child_info, &key) + self.client.child_storage_hash(block, &child_info, &key) }) - .map_err(client_err); - - async move { r }.boxed() + .map_err(client_err) } } -/// Splits passed range into two subranges where: -/// - first range has at least one element in it; -/// - second range (optionally) starts at given `middle` element. -pub(crate) fn split_range( - size: usize, - middle: Option, -) -> (Range, Option>) { - // check if we can filter blocks-with-changes from some (sub)range using changes tries - let range2_begin = match middle { - // some of required changes tries are pruned => use available tries - Some(middle) if middle != 0 => Some(middle), - // all required changes tries are available, but we still want values at first block - // => do 'unfiltered' read for the first block and 'filtered' for the rest - Some(_) if size > 1 => Some(1), - // range contains single element => do not use changes tries - Some(_) => None, - // changes tries are not available => do 'unfiltered' read for the whole range - None => None, - }; - let range1 = 0..range2_begin.unwrap_or(size); - let range2 = range2_begin.map(|begin| begin..size); - (range1, range2) -} - fn invalid_block_range( from: &CachedHeaderMetadata, to: &CachedHeaderMetadata, diff --git a/client/rpc/src/state/state_light.rs b/client/rpc/src/state/state_light.rs deleted file mode 100644 index 749e57c365cc0..0000000000000 --- a/client/rpc/src/state/state_light.rs +++ /dev/null @@ -1,873 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! State API backend for light nodes. - -use codec::Decode; -use futures::{ - channel::oneshot::{channel, Sender}, - future::{ready, Either}, - Future, FutureExt, SinkExt, Stream, StreamExt as _, TryFutureExt, TryStreamExt as _, -}; -use hash_db::Hasher; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; -use log::warn; -use parking_lot::Mutex; -use rpc::Result as RpcResult; -use std::{ - collections::{hash_map::Entry, HashMap, HashSet}, - sync::Arc, -}; - -use sc_client_api::{ - light::{ - future_header, Fetcher, RemoteBlockchain, RemoteCallRequest, RemoteReadChildRequest, - RemoteReadRequest, - }, - BlockchainEvents, -}; -use sc_rpc_api::state::ReadProof; -use sp_blockchain::{Error as ClientError, HeaderBackend}; -use sp_core::{ - storage::{PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey}, - Bytes, OpaqueMetadata, -}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, HashFor}, -}; -use sp_version::RuntimeVersion; - -use super::{ - client_err, - error::{Error, FutureResult}, - ChildStateBackend, StateBackend, -}; - -/// Storage data map of storage keys => (optional) storage value. -type StorageMap = HashMap>; - -/// State API backend for light nodes. -#[derive(Clone)] -pub struct LightState, Client> { - client: Arc, - subscriptions: SubscriptionManager, - version_subscriptions: SimpleSubscriptions, - storage_subscriptions: Arc>>, - remote_blockchain: Arc>, - fetcher: Arc, -} - -/// Shared requests container. -trait SharedRequests: Clone + Send + Sync { - /// Tries to listen for already issued request, or issues request. - /// - /// Returns true if requests has been issued. - fn listen_request(&self, block: Hash, sender: Sender>) -> bool; - - /// Returns (and forgets) all listeners for given request. - fn on_response_received(&self, block: Hash) -> Vec>>; -} - -/// Storage subscriptions data. -struct StorageSubscriptions { - /// Active storage requests. - active_requests: HashMap>>>, - /// Map of subscription => keys that this subscription watch for. - keys_by_subscription: HashMap>, - /// Map of key => set of subscriptions that watch this key. - subscriptions_by_key: HashMap>, -} - -impl SharedRequests - for Arc>> -{ - fn listen_request(&self, block: Block::Hash, sender: Sender>) -> bool { - let mut subscriptions = self.lock(); - let active_requests_at = subscriptions.active_requests.entry(block).or_default(); - active_requests_at.push(sender); - active_requests_at.len() == 1 - } - - fn on_response_received(&self, block: Block::Hash) -> Vec>> { - self.lock().active_requests.remove(&block).unwrap_or_default() - } -} - -/// Simple, maybe shared, subscription data that shares per block requests. -type SimpleSubscriptions = Arc>>>>>; - -impl SharedRequests for SimpleSubscriptions -where - Hash: Send + Eq + std::hash::Hash, - V: Send, -{ - fn listen_request(&self, block: Hash, sender: Sender>) -> bool { - let mut subscriptions = self.lock(); - let active_requests_at = subscriptions.entry(block).or_default(); - active_requests_at.push(sender); - active_requests_at.len() == 1 - } - - fn on_response_received(&self, block: Hash) -> Vec>> { - self.lock().remove(&block).unwrap_or_default() - } -} - -impl + 'static, Client> LightState -where - Block: BlockT, - Client: HeaderBackend + Send + Sync + 'static, -{ - /// Create new state API backend for light nodes. - pub fn new( - client: Arc, - subscriptions: SubscriptionManager, - remote_blockchain: Arc>, - fetcher: Arc, - ) -> Self { - Self { - client, - subscriptions, - version_subscriptions: Arc::new(Mutex::new(HashMap::new())), - storage_subscriptions: Arc::new(Mutex::new(StorageSubscriptions { - active_requests: HashMap::new(), - keys_by_subscription: HashMap::new(), - subscriptions_by_key: HashMap::new(), - })), - remote_blockchain, - fetcher, - } - } - - /// Returns given block hash or best block hash if None is passed. - fn block_or_best(&self, hash: Option) -> Block::Hash { - hash.unwrap_or_else(|| self.client.info().best_hash) - } -} - -impl StateBackend for LightState -where - Block: BlockT, - Block::Hash: Unpin, - Client: BlockchainEvents + HeaderBackend + Send + Sync + 'static, - F: Fetcher + 'static, -{ - fn call( - &self, - block: Option, - method: String, - call_data: Bytes, - ) -> FutureResult { - call( - &*self.remote_blockchain, - self.fetcher.clone(), - self.block_or_best(block), - method, - call_data, - ) - .boxed() - } - - fn storage_keys( - &self, - _block: Option, - _prefix: StorageKey, - ) -> FutureResult> { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } - - fn storage_pairs( - &self, - _block: Option, - _prefix: StorageKey, - ) -> FutureResult> { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } - - fn storage_keys_paged( - &self, - _block: Option, - _prefix: Option, - _count: u32, - _start_key: Option, - ) -> FutureResult> { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } - - fn storage_size(&self, _: Option, _: StorageKey) -> FutureResult> { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } - - fn storage( - &self, - block: Option, - key: StorageKey, - ) -> FutureResult> { - storage( - &*self.remote_blockchain, - self.fetcher.clone(), - self.block_or_best(block), - vec![key.0.clone()], - ) - .map_ok(move |mut values| { - values - .remove(&key) - .expect("successful request has entries for all requested keys; qed") - }) - .boxed() - } - - fn storage_hash( - &self, - block: Option, - key: StorageKey, - ) -> FutureResult> { - let res = StateBackend::storage(self, block, key); - async move { res.await.map(|r| r.map(|s| HashFor::::hash(&s.0))) }.boxed() - } - - fn metadata(&self, block: Option) -> FutureResult { - self.call(block, "Metadata_metadata".into(), Bytes(Vec::new())) - .and_then(|metadata| async move { - OpaqueMetadata::decode(&mut &metadata.0[..]) - .map(Into::into) - .map_err(|decode_err| { - client_err(ClientError::CallResultDecode( - "Unable to decode metadata", - decode_err, - )) - }) - }) - .boxed() - } - - fn runtime_version(&self, block: Option) -> FutureResult { - runtime_version(&*self.remote_blockchain, self.fetcher.clone(), self.block_or_best(block)) - .boxed() - } - - fn query_storage( - &self, - _from: Block::Hash, - _to: Option, - _keys: Vec, - ) -> FutureResult>> { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } - - fn query_storage_at( - &self, - _keys: Vec, - _at: Option, - ) -> FutureResult>> { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } - - fn read_proof( - &self, - _block: Option, - _keys: Vec, - ) -> FutureResult> { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } - - fn subscribe_storage( - &self, - _meta: crate::Metadata, - subscriber: Subscriber>, - keys: Option>, - ) { - let keys = match keys { - Some(keys) if !keys.is_empty() => keys, - _ => { - warn!("Cannot subscribe to all keys on light client. Subscription rejected."); - return - }, - }; - - let keys = keys.iter().cloned().collect::>(); - let keys_to_check = keys.iter().map(|k| k.0.clone()).collect::>(); - let subscription_id = self.subscriptions.add(subscriber, move |sink| { - let fetcher = self.fetcher.clone(); - let remote_blockchain = self.remote_blockchain.clone(); - let storage_subscriptions = self.storage_subscriptions.clone(); - let initial_block = self.block_or_best(None); - let initial_keys = keys_to_check.iter().cloned().collect::>(); - - let changes_stream = subscription_stream::( - storage_subscriptions.clone(), - self.client.import_notification_stream().map(|notification| notification.hash), - display_error( - storage(&*remote_blockchain, fetcher.clone(), initial_block, initial_keys) - .map(move |r| r.map(|r| (initial_block, r))), - ), - move |block| { - // there'll be single request per block for all active subscriptions - // with all subscribed keys - let keys = storage_subscriptions - .lock() - .subscriptions_by_key - .keys() - .map(|k| k.0.clone()) - .collect(); - - storage(&*remote_blockchain, fetcher.clone(), block, keys) - }, - move |block, old_value, new_value| { - // let's only select keys which are valid for this subscription - let new_value = new_value - .iter() - .filter(|(k, _)| keys_to_check.contains(&k.0)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect::>(); - let value_differs = old_value - .as_ref() - .map(|old_value| **old_value != new_value) - .unwrap_or(true); - - value_differs.then(|| StorageChangeSet { - block, - changes: new_value.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), - }) - }, - ); - - changes_stream - .map_ok(Ok) - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - // we ignore the resulting Stream (if the first stream is over we are unsubscribed) - .map(|_| ()) - }); - - // remember keys associated with this subscription - let mut storage_subscriptions = self.storage_subscriptions.lock(); - storage_subscriptions - .keys_by_subscription - .insert(subscription_id.clone(), keys.clone()); - for key in keys { - storage_subscriptions - .subscriptions_by_key - .entry(key) - .or_default() - .insert(subscription_id.clone()); - } - } - - fn unsubscribe_storage( - &self, - _meta: Option, - id: SubscriptionId, - ) -> RpcResult { - if !self.subscriptions.cancel(id.clone()) { - return Ok(false) - } - - // forget subscription keys - let mut storage_subscriptions = self.storage_subscriptions.lock(); - let keys = storage_subscriptions.keys_by_subscription.remove(&id); - for key in keys.into_iter().flat_map(|keys| keys.into_iter()) { - match storage_subscriptions.subscriptions_by_key.entry(key) { - Entry::Vacant(_) => unreachable!( - "every key from keys_by_subscription has\ - corresponding entry in subscriptions_by_key; qed" - ), - Entry::Occupied(mut entry) => { - entry.get_mut().remove(&id); - if entry.get().is_empty() { - entry.remove(); - } - }, - } - } - - Ok(true) - } - - fn subscribe_runtime_version( - &self, - _meta: crate::Metadata, - subscriber: Subscriber, - ) { - self.subscriptions.add(subscriber, move |sink| { - let fetcher = self.fetcher.clone(); - let remote_blockchain = self.remote_blockchain.clone(); - let version_subscriptions = self.version_subscriptions.clone(); - let initial_block = self.block_or_best(None); - - let versions_stream = subscription_stream::( - version_subscriptions, - self.client.import_notification_stream().map(|notification| notification.hash), - display_error( - runtime_version(&*remote_blockchain, fetcher.clone(), initial_block) - .map(move |r| r.map(|r| (initial_block, r))), - ), - move |block| runtime_version(&*remote_blockchain, fetcher.clone(), block), - |_, old_version, new_version| { - let version_differs = old_version - .as_ref() - .map(|old_version| *old_version != new_version) - .unwrap_or(true); - - version_differs.then(|| new_version.clone()) - }, - ); - - versions_stream - .map_ok(Ok) - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - // we ignore the resulting Stream (if the first stream is over we are unsubscribed) - .map(|_| ()) - }); - } - - fn unsubscribe_runtime_version( - &self, - _meta: Option, - id: SubscriptionId, - ) -> RpcResult { - Ok(self.subscriptions.cancel(id)) - } - - fn trace_block( - &self, - _block: Block::Hash, - _targets: Option, - _storage_keys: Option, - _methods: Option, - ) -> FutureResult { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } -} - -impl ChildStateBackend for LightState -where - Block: BlockT, - Client: BlockchainEvents + HeaderBackend + Send + Sync + 'static, - F: Fetcher + 'static, -{ - fn read_child_proof( - &self, - _block: Option, - _storage_key: PrefixedStorageKey, - _keys: Vec, - ) -> FutureResult> { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } - - fn storage_keys( - &self, - _block: Option, - _storage_key: PrefixedStorageKey, - _prefix: StorageKey, - ) -> FutureResult> { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } - - fn storage_keys_paged( - &self, - _block: Option, - _storage_key: PrefixedStorageKey, - _prefix: Option, - _count: u32, - _start_key: Option, - ) -> FutureResult> { - async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed() - } - - fn storage( - &self, - block: Option, - storage_key: PrefixedStorageKey, - key: StorageKey, - ) -> FutureResult> { - let block = self.block_or_best(block); - let fetcher = self.fetcher.clone(); - let child_storage = - resolve_header(&*self.remote_blockchain, &*self.fetcher, block).then(move |result| { - match result { - Ok(header) => Either::Left( - fetcher - .remote_read_child(RemoteReadChildRequest { - block, - header, - storage_key, - keys: vec![key.0.clone()], - retry_count: Default::default(), - }) - .then(move |result| { - ready( - result - .map(|mut data| { - data.remove(&key.0) - .expect( - "successful result has entry for all keys; qed", - ) - .map(StorageData) - }) - .map_err(client_err), - ) - }), - ), - Err(error) => Either::Right(ready(Err(error))), - } - }); - - child_storage.boxed() - } - - fn storage_entries( - &self, - block: Option, - storage_key: PrefixedStorageKey, - keys: Vec, - ) -> FutureResult>> { - let block = self.block_or_best(block); - let fetcher = self.fetcher.clone(); - let keys = keys.iter().map(|k| k.0.clone()).collect::>(); - let child_storage = - resolve_header(&*self.remote_blockchain, &*self.fetcher, block).then(move |result| { - match result { - Ok(header) => Either::Left( - fetcher - .remote_read_child(RemoteReadChildRequest { - block, - header, - storage_key, - keys: keys.clone(), - retry_count: Default::default(), - }) - .then(move |result| { - ready( - result - .map(|data| { - data.iter() - .filter_map(|(k, d)| { - keys.contains(k).then(|| { - d.as_ref().map(|v| StorageData(v.to_vec())) - }) - }) - .collect::>() - }) - .map_err(client_err), - ) - }), - ), - Err(error) => Either::Right(ready(Err(error))), - } - }); - - child_storage.boxed() - } - - fn storage_hash( - &self, - block: Option, - storage_key: PrefixedStorageKey, - key: StorageKey, - ) -> FutureResult> { - let child_storage = ChildStateBackend::storage(self, block, storage_key, key); - - async move { child_storage.await.map(|r| r.map(|s| HashFor::::hash(&s.0))) }.boxed() - } -} - -/// Resolve header by hash. -fn resolve_header>( - remote_blockchain: &dyn RemoteBlockchain, - fetcher: &F, - block: Block::Hash, -) -> impl std::future::Future> { - let maybe_header = future_header(remote_blockchain, fetcher, BlockId::Hash(block)); - - maybe_header.then(move |result| { - ready( - result - .and_then(|maybe_header| { - maybe_header.ok_or_else(|| ClientError::UnknownBlock(format!("{}", block))) - }) - .map_err(client_err), - ) - }) -} - -/// Call runtime method at given block -fn call>( - remote_blockchain: &dyn RemoteBlockchain, - fetcher: Arc, - block: Block::Hash, - method: String, - call_data: Bytes, -) -> impl std::future::Future> { - resolve_header(remote_blockchain, &*fetcher, block).then(move |result| match result { - Ok(header) => Either::Left( - fetcher - .remote_call(RemoteCallRequest { - block, - header, - method, - call_data: call_data.0, - retry_count: Default::default(), - }) - .then(|result| ready(result.map(Bytes).map_err(client_err))), - ), - Err(error) => Either::Right(ready(Err(error))), - }) -} - -/// Get runtime version at given block. -fn runtime_version>( - remote_blockchain: &dyn RemoteBlockchain, - fetcher: Arc, - block: Block::Hash, -) -> impl std::future::Future> { - call(remote_blockchain, fetcher, block, "Core_version".into(), Bytes(Vec::new())).then( - |version| { - ready(version.and_then(|version| { - Decode::decode(&mut &version.0[..]) - .map_err(|e| client_err(ClientError::VersionInvalid(e.to_string()))) - })) - }, - ) -} - -/// Get storage value at given key at given block. -fn storage>( - remote_blockchain: &dyn RemoteBlockchain, - fetcher: Arc, - block: Block::Hash, - keys: Vec>, -) -> impl std::future::Future>, Error>> { - resolve_header(remote_blockchain, &*fetcher, block).then(move |result| match result { - Ok(header) => Either::Left( - fetcher - .remote_read(RemoteReadRequest { - block, - header, - keys, - retry_count: Default::default(), - }) - .then(|result| { - ready( - result - .map(|result| { - result - .into_iter() - .map(|(key, value)| (StorageKey(key), value.map(StorageData))) - .collect() - }) - .map_err(client_err), - ) - }), - ), - Err(error) => Either::Right(ready(Err(error))), - }) -} - -/// Returns subscription stream that issues request on every imported block and -/// if value has changed from previous block, emits (stream) item. -fn subscription_stream< - Block, - Requests, - FutureBlocksStream, - V, - N, - InitialRequestFuture, - IssueRequest, - IssueRequestFuture, - CompareValues, ->( - shared_requests: Requests, - future_blocks_stream: FutureBlocksStream, - initial_request: InitialRequestFuture, - issue_request: IssueRequest, - compare_values: CompareValues, -) -> impl Stream> -where - Block: BlockT, - Requests: 'static + SharedRequests, - FutureBlocksStream: Stream, - V: Send + 'static + Clone, - InitialRequestFuture: Future> + Send + 'static, - IssueRequest: 'static + Fn(Block::Hash) -> IssueRequestFuture, - IssueRequestFuture: Future> + Send + 'static, - CompareValues: Fn(Block::Hash, Option<&V>, &V) -> Option, -{ - // we need to send initial value first, then we'll only be sending if value has changed - let previous_value = Arc::new(Mutex::new(None)); - - // prepare 'stream' of initial values - let initial_value_stream = initial_request.into_stream(); - - // prepare stream of future values - // - // we do not want to stop stream if single request fails - // (the warning should have been already issued by the request issuer) - let future_values_stream = future_blocks_stream - .then(move |block| { - maybe_share_remote_request::( - shared_requests.clone(), - block, - &issue_request, - ) - .map(move |r| r.map(|v| (block, v))) - }) - .filter(|r| ready(r.is_ok())); - - // now let's return changed values for selected blocks - initial_value_stream - .chain(future_values_stream) - .try_filter_map(move |(block, new_value)| { - let mut previous_value = previous_value.lock(); - let res = compare_values(block, previous_value.as_ref(), &new_value).map( - |notification_value| { - *previous_value = Some(new_value); - notification_value - }, - ); - async move { Ok(res) } - }) - .map_err(|_| ()) -} - -/// Request some data from remote node, probably reusing response from already -/// (in-progress) existing request. -fn maybe_share_remote_request( - shared_requests: Requests, - block: Block::Hash, - issue_request: &IssueRequest, -) -> impl std::future::Future> -where - V: Clone, - Requests: SharedRequests, - IssueRequest: Fn(Block::Hash) -> IssueRequestFuture, - IssueRequestFuture: std::future::Future>, -{ - let (sender, receiver) = channel(); - let need_issue_request = shared_requests.listen_request(block, sender); - - // if that isn't the first request - just listen for existing request' response - if !need_issue_request { - return Either::Right(receiver.then(|r| ready(r.unwrap_or(Err(()))))) - } - - // that is the first request - issue remote request + notify all listeners on - // completion - Either::Left(display_error(issue_request(block)).then(move |remote_result| { - let listeners = shared_requests.on_response_received(block); - // skip first element, because this future is the first element - for receiver in listeners.into_iter().skip(1) { - if let Err(_) = receiver.send(remote_result.clone()) { - // we don't care if receiver has been dropped already - } - } - - ready(remote_result) - })) -} - -/// Convert successful future result into Ok(result) and error into Err(()), -/// displaying warning. -fn display_error(future: F) -> impl std::future::Future> -where - F: std::future::Future>, -{ - future.then(|result| { - ready(result.or_else(|err| { - warn!("Remote request for subscription data has failed with: {:?}", err); - Err(()) - })) - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use futures::{executor, stream}; - use sp_core::H256; - use substrate_test_runtime_client::runtime::Block; - - #[test] - fn subscription_stream_works() { - let stream = subscription_stream::( - SimpleSubscriptions::default(), - stream::iter(vec![H256::from([2; 32]), H256::from([3; 32])]), - ready(Ok((H256::from([1; 32]), 100))), - |block| match block[0] { - 2 => ready(Ok(100)), - 3 => ready(Ok(200)), - _ => unreachable!("should not issue additional requests"), - }, - |_, old_value, new_value| match old_value == Some(new_value) { - true => None, - false => Some(new_value.clone()), - }, - ); - - assert_eq!(executor::block_on(stream.collect::>()), vec![Ok(100), Ok(200)]); - } - - #[test] - fn subscription_stream_ignores_failed_requests() { - let stream = subscription_stream::( - SimpleSubscriptions::default(), - stream::iter(vec![H256::from([2; 32]), H256::from([3; 32])]), - ready(Ok((H256::from([1; 32]), 100))), - |block| match block[0] { - 2 => ready(Err(client_err(ClientError::NotAvailableOnLightClient))), - 3 => ready(Ok(200)), - _ => unreachable!("should not issue additional requests"), - }, - |_, old_value, new_value| match old_value == Some(new_value) { - true => None, - false => Some(new_value.clone()), - }, - ); - - assert_eq!(executor::block_on(stream.collect::>()), vec![Ok(100), Ok(200)]); - } - - #[test] - fn maybe_share_remote_request_shares_request() { - type UnreachableFuture = futures::future::Ready>; - - let shared_requests = SimpleSubscriptions::default(); - - // let's 'issue' requests for B1 - shared_requests.lock().insert(H256::from([1; 32]), vec![channel().0]); - - // make sure that no additional requests are issued when we're asking for B1 - let _ = maybe_share_remote_request::( - shared_requests.clone(), - H256::from([1; 32]), - &|_| unreachable!("no duplicate requests issued"), - ); - - // make sure that additional requests is issued when we're asking for B2 - let request_issued = Arc::new(Mutex::new(false)); - let _ = maybe_share_remote_request::( - shared_requests.clone(), - H256::from([2; 32]), - &|_| { - *request_issued.lock() = true; - ready(Ok(Default::default())) - }, - ); - assert!(*request_issued.lock()); - } -} diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index 712fe00c54386..3ef59e5ca9a7c 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,28 +17,31 @@ // along with this program. If not, see . use self::error::Error; -use super::{state_full::split_range, *}; -use crate::testing::TaskExecutor; +use super::*; +use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; -use futures::{executor, StreamExt}; +use futures::executor; +use jsonrpsee::{ + core::Error as RpcError, + types::{error::CallError as RpcCallError, EmptyServerParams as EmptyParams, ErrorObject}, +}; use sc_block_builder::BlockBuilderProvider; use sc_rpc_api::DenyUnsafe; use sp_consensus::BlockOrigin; -use sp_core::{hash::H256, storage::ChildInfo, ChangesTrieConfiguration}; +use sp_core::{hash::H256, storage::ChildInfo}; use sp_io::hashing::blake2_256; -use sp_runtime::generic::BlockId; use std::sync::Arc; use substrate_test_runtime_client::{prelude::*, runtime}; const STORAGE_KEY: &[u8] = b"child"; fn prefixed_storage_key() -> PrefixedStorageKey { - let child_info = ChildInfo::new_default(&STORAGE_KEY[..]); + let child_info = ChildInfo::new_default(STORAGE_KEY); child_info.prefixed_storage_key() } -#[test] -fn should_return_storage() { +#[tokio::test] +async fn should_return_storage() { const KEY: &[u8] = b":mock"; const VALUE: &[u8] = b"hello world"; const CHILD_VALUE: &[u8] = b"hello world !"; @@ -52,49 +55,38 @@ fn should_return_storage() { .add_extra_storage(b":map:acc2".to_vec(), vec![1, 2, 3]) .build(); let genesis_hash = client.genesis_hash(); - let (client, child) = new_full( - Arc::new(client), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No, None); let key = StorageKey(KEY.to_vec()); assert_eq!( - executor::block_on(client.storage(key.clone(), Some(genesis_hash).into())) + client + .storage(key.clone(), Some(genesis_hash).into()) .map(|x| x.map(|x| x.0.len())) .unwrap() .unwrap() as usize, VALUE.len(), ); assert_matches!( - executor::block_on(client.storage_hash(key.clone(), Some(genesis_hash).into())) - .map(|x| x.is_some()), + client.storage_hash(key.clone(), Some(genesis_hash).into()).map(|x| x.is_some()), Ok(true) ); + assert_eq!(client.storage_size(key.clone(), None).unwrap().unwrap() as usize, VALUE.len(),); assert_eq!( - executor::block_on(client.storage_size(key.clone(), None)).unwrap().unwrap() as usize, - VALUE.len(), + client.storage_size(StorageKey(b":map".to_vec()), None).unwrap().unwrap() as usize, + 2 + 3, ); assert_eq!( - executor::block_on(client.storage_size(StorageKey(b":map".to_vec()), None)) + child + .storage(prefixed_storage_key(), key, Some(genesis_hash).into()) + .map(|x| x.map(|x| x.0.len())) .unwrap() .unwrap() as usize, - 2 + 3, - ); - assert_eq!( - executor::block_on( - child - .storage(prefixed_storage_key(), key, Some(genesis_hash).into()) - .map(|x| x.map(|x| x.unwrap().0.len())) - ) - .unwrap() as usize, CHILD_VALUE.len(), ); } -#[test] -fn should_return_storage_entries() { +#[tokio::test] +async fn should_return_storage_entries() { const KEY1: &[u8] = b":mock"; const KEY2: &[u8] = b":turtle"; const VALUE: &[u8] = b"hello world"; @@ -108,22 +100,14 @@ fn should_return_storage_entries() { .add_extra_child_storage(&child_info, KEY2.to_vec(), CHILD_VALUE2.to_vec()) .build(); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full( - Arc::new(client), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (_client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No, None); let keys = &[StorageKey(KEY1.to_vec()), StorageKey(KEY2.to_vec())]; assert_eq!( - executor::block_on(child.storage_entries( - prefixed_storage_key(), - keys.to_vec(), - Some(genesis_hash).into() - )) - .map(|x| x.into_iter().map(|x| x.map(|x| x.0.len()).unwrap()).sum::()) - .unwrap(), + child + .storage_entries(prefixed_storage_key(), keys.to_vec(), Some(genesis_hash).into()) + .map(|x| x.into_iter().map(|x| x.map(|x| x.0.len()).unwrap()).sum::()) + .unwrap(), CHILD_VALUE1.len() + CHILD_VALUE2.len() ); @@ -131,18 +115,15 @@ fn should_return_storage_entries() { let mut failing_keys = vec![StorageKey(b":soup".to_vec())]; failing_keys.extend_from_slice(keys); assert_matches!( - executor::block_on(child.storage_entries( - prefixed_storage_key(), - failing_keys, - Some(genesis_hash).into() - )) - .map(|x| x.iter().all(|x| x.is_some())), + child + .storage_entries(prefixed_storage_key(), failing_keys, Some(genesis_hash).into()) + .map(|x| x.iter().all(|x| x.is_some())), Ok(false) ); } -#[test] -fn should_return_child_storage() { +#[tokio::test] +async fn should_return_child_storage() { let child_info = ChildInfo::new_default(STORAGE_KEY); let client = Arc::new( substrate_test_runtime_client::TestClientBuilder::new() @@ -150,49 +131,29 @@ fn should_return_child_storage() { .build(), ); let genesis_hash = client.genesis_hash(); - let (_client, child) = - new_full(client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, None); + let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No, None); let child_key = prefixed_storage_key(); let key = StorageKey(b"key".to_vec()); assert_matches!( - executor::block_on(child.storage( + child.storage( child_key.clone(), key.clone(), Some(genesis_hash).into(), - )), + ), Ok(Some(StorageData(ref d))) if d[0] == 42 && d.len() == 1 ); - - // should fail if key does not exist. - let failing_key = StorageKey(b":soup".to_vec()); - assert_matches!( - executor::block_on(child.storage( - prefixed_storage_key(), - failing_key, - Some(genesis_hash).into() - )) - .map(|x| x.is_some()), - Ok(false) - ); - assert_matches!( - executor::block_on(child.storage_hash( - child_key.clone(), - key.clone(), - Some(genesis_hash).into(), - )) - .map(|x| x.is_some()), + child + .storage_hash(child_key.clone(), key.clone(), Some(genesis_hash).into(),) + .map(|x| x.is_some()), Ok(true) ); - assert_matches!( - executor::block_on(child.storage_size(child_key.clone(), key.clone(), None)), - Ok(Some(1)) - ); + assert_matches!(child.storage_size(child_key.clone(), key.clone(), None), Ok(Some(1))); } -#[test] -fn should_return_child_storage_entries() { +#[tokio::test] +async fn should_return_child_storage_entries() { let child_info = ChildInfo::new_default(STORAGE_KEY); let client = Arc::new( substrate_test_runtime_client::TestClientBuilder::new() @@ -201,17 +162,13 @@ fn should_return_child_storage_entries() { .build(), ); let genesis_hash = client.genesis_hash(); - let (_client, child) = - new_full(client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, None); + let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No, None); let child_key = prefixed_storage_key(); let keys = vec![StorageKey(b"key1".to_vec()), StorageKey(b"key2".to_vec())]; - let res = executor::block_on(child.storage_entries( - child_key.clone(), - keys.clone(), - Some(genesis_hash).into(), - )) - .unwrap(); + let res = child + .storage_entries(child_key.clone(), keys.clone(), Some(genesis_hash).into()) + .unwrap(); assert_matches!( res[0], @@ -224,55 +181,38 @@ fn should_return_child_storage_entries() { if d[0] == 43 && d[1] == 44 && d.len() == 2 ); assert_matches!( - executor::block_on(child.storage_hash( - child_key.clone(), - keys[0].clone(), - Some(genesis_hash).into() - )) - .map(|x| x.is_some()), + child + .storage_hash(child_key.clone(), keys[0].clone(), Some(genesis_hash).into()) + .map(|x| x.is_some()), Ok(true) ); - assert_matches!( - executor::block_on(child.storage_size(child_key.clone(), keys[0].clone(), None)), - Ok(Some(1)) - ); + assert_matches!(child.storage_size(child_key.clone(), keys[0].clone(), None), Ok(Some(1))); } -#[test] -fn should_call_contract() { +#[tokio::test] +async fn should_call_contract() { let client = Arc::new(substrate_test_runtime_client::new()); let genesis_hash = client.genesis_hash(); - let (client, _child) = - new_full(client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, None); + let (client, _child) = new_full(client, test_executor(), DenyUnsafe::No, None); + + use jsonrpsee::{core::Error, types::error::CallError}; assert_matches!( - executor::block_on(client.call( - "balanceOf".into(), - Bytes(vec![1, 2, 3]), - Some(genesis_hash).into() - )), - Err(Error::Client(_)) + client.call("balanceOf".into(), Bytes(vec![1, 2, 3]), Some(genesis_hash).into()), + Err(Error::Call(CallError::Failed(_))) ) } -#[test] -fn should_notify_about_storage_changes() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); - - { +#[tokio::test] +async fn should_notify_about_storage_changes() { + let mut sub = { let mut client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); - api.subscribe_storage(Default::default(), subscriber, None.into()); - - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); + let api_rpc = api.into_rpc(); + let sub = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await.unwrap(); + // Cause a change: let mut builder = client.new_block(Default::default()).unwrap(); builder .push_transfer(runtime::Transfer { @@ -283,38 +223,32 @@ fn should_notify_about_storage_changes() { }) .unwrap(); let block = builder.build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); - } + client.import(BlockOrigin::Own, block).await.unwrap(); - // Check notification sent to transport - executor::block_on((&mut transport).take(2).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); -} + sub + }; -#[test] -fn should_send_initial_storage_changes_and_notifications() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); + // We should get a message back on our subscription about the storage change: + // NOTE: previous versions of the subscription code used to return an empty value for the + // "initial" storage change here + assert_matches!(timeout_secs(1, sub.next::>()).await, Ok(Some(_))); + assert_matches!(timeout_secs(1, sub.next::>()).await, Ok(None)); +} - { +#[tokio::test] +async fn should_send_initial_storage_changes_and_notifications() { + let mut sub = { let mut client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); let alice_balance_key = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Alice.into())); - api.subscribe_storage( - Default::default(), - subscriber, - Some(vec![StorageKey(alice_balance_key.to_vec())]).into(), - ); - - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); + let api_rpc = api.into_rpc(); + let sub = api_rpc + .subscribe("state_subscribeStorage", [[StorageKey(alice_balance_key.to_vec())]]) + .await + .unwrap(); let mut builder = client.new_block(Default::default()).unwrap(); builder @@ -326,23 +260,22 @@ fn should_send_initial_storage_changes_and_notifications() { }) .unwrap(); let block = builder.build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); - } + client.import(BlockOrigin::Own, block).await.unwrap(); + + sub + }; + + assert_matches!(timeout_secs(1, sub.next::>()).await, Ok(Some(_))); + assert_matches!(timeout_secs(1, sub.next::>()).await, Ok(Some(_))); - // Check for the correct number of notifications - executor::block_on((&mut transport).take(2).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); + // No more messages to follow + assert_matches!(timeout_secs(1, sub.next::>()).await, Ok(None)); } -#[test] -fn should_query_storage() { - fn run_tests(mut client: Arc, has_changes_trie_config: bool) { - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); +#[tokio::test] +async fn should_query_storage() { + async fn run_tests(mut client: Arc) { + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); let mut add_block = |nonce| { let mut builder = client.new_block(Default::default()).unwrap(); @@ -369,13 +302,6 @@ fn should_query_storage() { let block2_hash = add_block(1); let genesis_hash = client.genesis_hash(); - if has_changes_trie_config { - assert_eq!( - client.max_key_changes_range(1, BlockId::Hash(block1_hash)).unwrap(), - Some((0, BlockId::Hash(block1_hash))), - ); - } - let mut expected = vec![ StorageChangeSet { block: genesis_hash, @@ -401,7 +327,7 @@ fn should_query_storage() { let keys = (1..6).map(|k| StorageKey(vec![k])).collect::>(); let result = api.query_storage(keys.clone(), genesis_hash, Some(block1_hash).into()); - assert_eq!(executor::block_on(result).unwrap(), expected); + assert_eq!(result.unwrap(), expected); // Query all changes let result = api.query_storage(keys.clone(), genesis_hash, None.into()); @@ -414,23 +340,28 @@ fn should_query_storage() { (StorageKey(vec![5]), Some(StorageData(vec![1]))), ], }); - assert_eq!(executor::block_on(result).unwrap(), expected); + assert_eq!(result.unwrap(), expected); // Query changes up to block2. let result = api.query_storage(keys.clone(), genesis_hash, Some(block2_hash)); - assert_eq!(executor::block_on(result).unwrap(), expected); + assert_eq!(result.unwrap(), expected); // Inverted range. let result = api.query_storage(keys.clone(), block1_hash, Some(genesis_hash)); assert_eq!( - executor::block_on(result).map_err(|e| e.to_string()), - Err(Error::InvalidBlockRange { - from: format!("1 ({:?})", block1_hash), - to: format!("0 ({:?})", genesis_hash), - details: "from number > to number".to_owned(), - }) + result.map_err(|e| e.to_string()), + Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( + 4001, + Error::InvalidBlockRange { + from: format!("1 ({:?})", block1_hash), + to: format!("0 ({:?})", genesis_hash), + details: "from number > to number".to_owned(), + } + .to_string(), + None::<()>, + )))) .map_err(|e| e.to_string()) ); @@ -441,15 +372,20 @@ fn should_query_storage() { let result = api.query_storage(keys.clone(), genesis_hash, Some(random_hash1)); assert_eq!( - executor::block_on(result).map_err(|e| e.to_string()), - Err(Error::InvalidBlockRange { - from: format!("{:?}", genesis_hash), - to: format!("{:?}", Some(random_hash1)), - details: format!( - "UnknownBlock: Header was not found in the database: {:?}", - random_hash1 - ), - }) + result.map_err(|e| e.to_string()), + Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( + 4001, + Error::InvalidBlockRange { + from: format!("{:?}", genesis_hash), + to: format!("{:?}", Some(random_hash1)), + details: format!( + "UnknownBlock: Header was not found in the database: {:?}", + random_hash1 + ), + } + .to_string(), + None::<()>, + )))) .map_err(|e| e.to_string()) ); @@ -457,15 +393,20 @@ fn should_query_storage() { let result = api.query_storage(keys.clone(), random_hash1, Some(genesis_hash)); assert_eq!( - executor::block_on(result).map_err(|e| e.to_string()), - Err(Error::InvalidBlockRange { - from: format!("{:?}", random_hash1), - to: format!("{:?}", Some(genesis_hash)), - details: format!( - "UnknownBlock: Header was not found in the database: {:?}", - random_hash1 - ), - }) + result.map_err(|e| e.to_string()), + Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( + 4001, + Error::InvalidBlockRange { + from: format!("{:?}", random_hash1), + to: format!("{:?}", Some(genesis_hash)), + details: format!( + "UnknownBlock: Header was not found in the database: {:?}", + random_hash1 + ), + } + .to_string(), + None::<()>, + )))) .map_err(|e| e.to_string()), ); @@ -473,15 +414,20 @@ fn should_query_storage() { let result = api.query_storage(keys.clone(), random_hash1, None); assert_eq!( - executor::block_on(result).map_err(|e| e.to_string()), - Err(Error::InvalidBlockRange { - from: format!("{:?}", random_hash1), - to: format!("{:?}", Some(block2_hash)), // Best block hash. - details: format!( - "UnknownBlock: Header was not found in the database: {:?}", - random_hash1 - ), - }) + result.map_err(|e| e.to_string()), + Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( + 4001, + Error::InvalidBlockRange { + from: format!("{:?}", random_hash1), + to: format!("{:?}", Some(block2_hash)), // Best block hash. + details: format!( + "UnknownBlock: Header was not found in the database: {:?}", + random_hash1 + ), + } + .to_string(), + None::<()>, + )))) .map_err(|e| e.to_string()), ); @@ -489,15 +435,20 @@ fn should_query_storage() { let result = api.query_storage(keys.clone(), random_hash1, Some(random_hash2)); assert_eq!( - executor::block_on(result).map_err(|e| e.to_string()), - Err(Error::InvalidBlockRange { - from: format!("{:?}", random_hash1), // First hash not found. - to: format!("{:?}", Some(random_hash2)), - details: format!( - "UnknownBlock: Header was not found in the database: {:?}", - random_hash1 - ), - }) + result.map_err(|e| e.to_string()), + Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( + 4001, + Error::InvalidBlockRange { + from: format!("{:?}", random_hash1), // First hash not found. + to: format!("{:?}", Some(random_hash2)), + details: format!( + "UnknownBlock: Header was not found in the database: {:?}", + random_hash1 + ), + } + .to_string(), + None::<()> + )))) .map_err(|e| e.to_string()), ); @@ -505,7 +456,7 @@ fn should_query_storage() { let result = api.query_storage_at(keys.clone(), Some(block1_hash)); assert_eq!( - executor::block_on(result).unwrap(), + result.unwrap(), vec![StorageChangeSet { block: block1_hash, changes: vec![ @@ -519,44 +470,23 @@ fn should_query_storage() { ); } - run_tests(Arc::new(substrate_test_runtime_client::new()), false); - run_tests( - Arc::new( - TestClientBuilder::new() - .changes_trie_config(Some(ChangesTrieConfiguration::new(4, 2))) - .build(), - ), - true, - ); -} - -#[test] -fn should_split_ranges() { - assert_eq!(split_range(1, None), (0..1, None)); - assert_eq!(split_range(100, None), (0..100, None)); - assert_eq!(split_range(1, Some(0)), (0..1, None)); - assert_eq!(split_range(100, Some(50)), (0..50, Some(50..100))); - assert_eq!(split_range(100, Some(99)), (0..99, Some(99..100))); + run_tests(Arc::new(substrate_test_runtime_client::new())).await; + run_tests(Arc::new(TestClientBuilder::new().build())).await; } -#[test] -fn should_return_runtime_version() { +#[tokio::test] +async fn should_return_runtime_version() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ - \"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",3],\ - [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",5],\ + \"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\ + [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\ [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",2],\ [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]],\ - \"transactionVersion\":1}"; + \"transactionVersion\":1,\"stateVersion\":1}"; - let runtime_version = executor::block_on(api.runtime_version(None.into())).unwrap(); + let runtime_version = api.runtime_version(None.into()).unwrap(); let serialized = serde_json::to_string(&runtime_version).unwrap(); assert_eq!(serialized, result); @@ -564,28 +494,26 @@ fn should_return_runtime_version() { assert_eq!(deserialized, runtime_version); } -#[test] -fn should_notify_on_runtime_version_initially() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); - - { +#[tokio::test] +async fn should_notify_on_runtime_version_initially() { + let mut sub = { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (api, _child) = new_full(client, test_executor(), DenyUnsafe::No, None); - api.subscribe_runtime_version(Default::default(), subscriber); + let api_rpc = api.into_rpc(); + let sub = api_rpc + .subscribe("state_subscribeRuntimeVersion", EmptyParams::new()) + .await + .unwrap(); - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); - } + sub + }; // assert initial version sent. - executor::block_on((&mut transport).take(1).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); + assert_matches!(timeout_secs(10, sub.next::()).await, Ok(Some(_))); + + sub.close(); + assert_matches!(timeout_secs(10, sub.next::()).await, Ok(None)); } #[test] @@ -595,3 +523,25 @@ fn should_deserialize_storage_key() { assert_eq!(k.0.len(), 32); } + +#[tokio::test] +async fn wildcard_storage_subscriptions_are_rpc_unsafe() { + let client = Arc::new(substrate_test_runtime_client::new()); + let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes, None); + + let api_rpc = api.into_rpc(); + let err = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await; + assert_matches!(err, Err(RpcError::Call(RpcCallError::Custom(e))) if e.message() == "RPC call is unsafe to be called externally"); +} + +#[tokio::test] +async fn concrete_storage_subscriptions_are_rpc_safe() { + let client = Arc::new(substrate_test_runtime_client::new()); + let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes, None); + let api_rpc = api.into_rpc(); + + let key = StorageKey(STORAGE_KEY.to_vec()); + let sub = api_rpc.subscribe("state_subscribeStorage", [[key]]).await; + + assert!(sub.is_ok()); +} diff --git a/client/rpc/src/system/mod.rs b/client/rpc/src/system/mod.rs index f99994e41a1be..ea24524cd2ea9 100644 --- a/client/rpc/src/system/mod.rs +++ b/client/rpc/src/system/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,30 +18,23 @@ //! Substrate system API. -use self::error::Result; -use futures::{channel::oneshot, FutureExt}; -use sc_rpc_api::{DenyUnsafe, Receiver}; +#[cfg(test)] +mod tests; + +use futures::channel::oneshot; +use jsonrpsee::{ + core::{async_trait, error::Error as JsonRpseeError, JsonValue, RpcResult}, + types::error::{CallError, ErrorCode, ErrorObject}, +}; +use sc_rpc_api::DenyUnsafe; use sc_tracing::logging; use sc_utils::mpsc::TracingUnboundedSender; use sp_runtime::traits::{self, Header as HeaderT}; -pub use self::{ - gen_client::Client as SystemClient, - helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo}, -}; -pub use sc_rpc_api::system::*; - -#[cfg(test)] -mod tests; +use self::error::Result; -/// Early exit for RPCs that require `--rpc-methods=Unsafe` to be enabled -macro_rules! bail_if_unsafe { - ($value: expr) => { - if let Err(err) = $value.check_if_safe() { - return async move { Err(err.into()) }.boxed() - } - }; -} +pub use self::helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo}; +pub use sc_rpc_api::system::*; /// System API implementation pub struct System { @@ -62,7 +55,7 @@ pub enum Request { /// Must return information about the peers we are connected to. Peers(oneshot::Sender::Number>>>), /// Must return the state of the network. - NetworkState(oneshot::Sender), + NetworkState(oneshot::Sender), /// Must return any potential parse error. NetworkAddReservedPeer(String, oneshot::Sender>), /// Must return any potential parse error. @@ -89,121 +82,123 @@ impl System { } } -impl SystemApi::Number> for System { - fn system_name(&self) -> Result { +#[async_trait] +impl SystemApiServer::Number> for System { + fn system_name(&self) -> RpcResult { Ok(self.info.impl_name.clone()) } - fn system_version(&self) -> Result { + fn system_version(&self) -> RpcResult { Ok(self.info.impl_version.clone()) } - fn system_chain(&self) -> Result { + fn system_chain(&self) -> RpcResult { Ok(self.info.chain_name.clone()) } - fn system_type(&self) -> Result { + fn system_type(&self) -> RpcResult { Ok(self.info.chain_type.clone()) } - fn system_properties(&self) -> Result { + fn system_properties(&self) -> RpcResult { Ok(self.info.properties.clone()) } - fn system_health(&self) -> Receiver { + async fn system_health(&self) -> RpcResult { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::Health(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_local_peer_id(&self) -> Receiver { + async fn system_local_peer_id(&self) -> RpcResult { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::LocalPeerId(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_local_listen_addresses(&self) -> Receiver> { + async fn system_local_listen_addresses(&self) -> RpcResult> { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::LocalListenAddresses(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_peers( + async fn system_peers( &self, - ) -> rpc::BoxFuture::Number>>>> { - bail_if_unsafe!(self.deny_unsafe); - + ) -> RpcResult::Number>>> { + self.deny_unsafe.check_if_safe()?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::Peers(tx)); - - async move { rx.await.map_err(|_| rpc::Error::internal_error()) }.boxed() + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_network_state(&self) -> rpc::BoxFuture> { - bail_if_unsafe!(self.deny_unsafe); - + async fn system_network_state(&self) -> RpcResult { + self.deny_unsafe.check_if_safe()?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkState(tx)); - - async move { rx.await.map_err(|_| rpc::Error::internal_error()) }.boxed() + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_add_reserved_peer(&self, peer: String) -> rpc::BoxFuture> { - bail_if_unsafe!(self.deny_unsafe); - + async fn system_add_reserved_peer(&self, peer: String) -> RpcResult<()> { + self.deny_unsafe.check_if_safe()?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkAddReservedPeer(peer, tx)); - async move { - match rx.await { - Ok(Ok(())) => Ok(()), - Ok(Err(e)) => Err(rpc::Error::from(e)), - Err(_) => Err(rpc::Error::internal_error()), - } + match rx.await { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => Err(JsonRpseeError::from(e)), + Err(e) => Err(JsonRpseeError::to_call_error(e)), } - .boxed() } - fn system_remove_reserved_peer(&self, peer: String) -> rpc::BoxFuture> { - bail_if_unsafe!(self.deny_unsafe); - + async fn system_remove_reserved_peer(&self, peer: String) -> RpcResult<()> { + self.deny_unsafe.check_if_safe()?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkRemoveReservedPeer(peer, tx)); - async move { - match rx.await { - Ok(Ok(())) => Ok(()), - Ok(Err(e)) => Err(rpc::Error::from(e)), - Err(_) => Err(rpc::Error::internal_error()), - } + match rx.await { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => Err(JsonRpseeError::from(e)), + Err(e) => Err(JsonRpseeError::to_call_error(e)), } - .boxed() } - fn system_reserved_peers(&self) -> Receiver> { + async fn system_reserved_peers(&self) -> RpcResult> { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkReservedPeers(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_node_roles(&self) -> Receiver> { + async fn system_node_roles(&self) -> RpcResult> { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NodeRoles(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_sync_state(&self) -> Receiver::Number>> { + async fn system_sync_state(&self) -> RpcResult::Number>> { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::SyncState(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_add_log_filter(&self, directives: String) -> rpc::Result<()> { + fn system_add_log_filter(&self, directives: String) -> RpcResult<()> { self.deny_unsafe.check_if_safe()?; + logging::add_directives(&directives); - logging::reload_filter().map_err(|_e| rpc::Error::internal_error()) + logging::reload_filter().map_err(|e| { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InternalError.code(), + e, + None::<()>, + ))) + }) } - fn system_reset_log_filter(&self) -> rpc::Result<()> { + fn system_reset_log_filter(&self) -> RpcResult<()> { self.deny_unsafe.check_if_safe()?; - logging::reset_log_filter().map_err(|_e| rpc::Error::internal_error()) + logging::reset_log_filter().map_err(|e| { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InternalError.code(), + e, + None::<()>, + ))) + }) } } diff --git a/client/rpc/src/system/tests.rs b/client/rpc/src/system/tests.rs index 14997545031df..00ab9c46861e2 100644 --- a/client/rpc/src/system/tests.rs +++ b/client/rpc/src/system/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,12 +16,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use super::*; - +use super::{helpers::SyncState, *}; use assert_matches::assert_matches; -use futures::{executor, prelude::*}; +use futures::prelude::*; +use jsonrpsee::{ + core::Error as RpcError, + types::{error::CallError, EmptyServerParams as EmptyParams}, + RpcModule, +}; use sc_network::{self, config::Role, PeerId}; +use sc_rpc_api::system::helpers::PeerInfo; use sc_utils::mpsc::tracing_unbounded; +use sp_core::H256; use std::{ env, io::{BufRead, BufReader, Write}, @@ -43,7 +49,7 @@ impl Default for Status { } } -fn api>>(sync: T) -> System { +fn api>>(sync: T) -> RpcModule> { let status = sync.into().unwrap_or_default(); let should_have_peers = !status.is_dev; let (tx, rx) = tracing_unbounded("rpc_system_tests"); @@ -93,7 +99,7 @@ fn api>>(sync: T) -> System { ); }, Request::NetworkAddReservedPeer(peer, sender) => { - let _ = match sc_network::config::parse_str_addr(&peer) { + let _ = match sc_network_common::config::parse_str_addr(&peer) { Ok(_) => sender.send(Ok(())), Err(s) => sender.send(Err(error::Error::MalformattedPeerArg(s.to_string()))), @@ -117,7 +123,7 @@ fn api>>(sync: T) -> System { let _ = sender.send(SyncState { starting_block: 1, current_block: 2, - highest_block: Some(3), + highest_block: 3, }); }, }; @@ -136,98 +142,122 @@ fn api>>(sync: T) -> System { tx, sc_rpc_api::DenyUnsafe::No, ) + .into_rpc() } -fn wait_receiver(rx: Receiver) -> T { - futures::executor::block_on(rx).unwrap() +#[tokio::test] +async fn system_name_works() { + assert_eq!( + api(None).call::<_, String>("system_name", EmptyParams::new()).await.unwrap(), + "testclient".to_string(), + ); } -#[test] -fn system_name_works() { - assert_eq!(api(None).system_name().unwrap(), "testclient".to_owned()); +#[tokio::test] +async fn system_version_works() { + assert_eq!( + api(None).call::<_, String>("system_version", EmptyParams::new()).await.unwrap(), + "0.2.0".to_string(), + ); } -#[test] -fn system_version_works() { - assert_eq!(api(None).system_version().unwrap(), "0.2.0".to_owned()); +#[tokio::test] +async fn system_chain_works() { + assert_eq!( + api(None).call::<_, String>("system_chain", EmptyParams::new()).await.unwrap(), + "testchain".to_string(), + ); } -#[test] -fn system_chain_works() { - assert_eq!(api(None).system_chain().unwrap(), "testchain".to_owned()); -} +#[tokio::test] +async fn system_properties_works() { + type Map = serde_json::map::Map; -#[test] -fn system_properties_works() { - assert_eq!(api(None).system_properties().unwrap(), serde_json::map::Map::new()); + assert_eq!( + api(None).call::<_, Map>("system_properties", EmptyParams::new()).await.unwrap(), + Map::new() + ); } -#[test] -fn system_type_works() { - assert_eq!(api(None).system_type().unwrap(), Default::default()); +#[tokio::test] +async fn system_type_works() { + assert_eq!( + api(None) + .call::<_, String>("system_chainType", EmptyParams::new()) + .await + .unwrap(), + "Live".to_owned(), + ); } -#[test] -fn system_health() { - assert_matches!( - wait_receiver(api(None).system_health()), - Health { peers: 0, is_syncing: false, should_have_peers: true } +#[tokio::test] +async fn system_health() { + assert_eq!( + api(None).call::<_, Health>("system_health", EmptyParams::new()).await.unwrap(), + Health { peers: 0, is_syncing: false, should_have_peers: true }, ); - assert_matches!( - wait_receiver( - api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: true, is_dev: true }) - .system_health() - ), - Health { peers: 5, is_syncing: true, should_have_peers: false } + assert_eq!( + api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: true, is_dev: true }) + .call::<_, Health>("system_health", EmptyParams::new()) + .await + .unwrap(), + Health { peers: 5, is_syncing: true, should_have_peers: false }, ); assert_eq!( - wait_receiver( - api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: false, is_dev: false }) - .system_health() - ), - Health { peers: 5, is_syncing: false, should_have_peers: true } + api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: false, is_dev: false }) + .call::<_, Health>("system_health", EmptyParams::new()) + .await + .unwrap(), + Health { peers: 5, is_syncing: false, should_have_peers: true }, ); assert_eq!( - wait_receiver( - api(Status { peer_id: PeerId::random(), peers: 0, is_syncing: false, is_dev: true }) - .system_health() - ), - Health { peers: 0, is_syncing: false, should_have_peers: false } + api(Status { peer_id: PeerId::random(), peers: 0, is_syncing: false, is_dev: true }) + .call::<_, Health>("system_health", EmptyParams::new()) + .await + .unwrap(), + Health { peers: 0, is_syncing: false, should_have_peers: false }, ); } -#[test] -fn system_local_peer_id_works() { +#[tokio::test] +async fn system_local_peer_id_works() { assert_eq!( - wait_receiver(api(None).system_local_peer_id()), - "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_owned(), + api(None) + .call::<_, String>("system_localPeerId", EmptyParams::new()) + .await + .unwrap(), + "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_owned() ); } -#[test] -fn system_local_listen_addresses_works() { +#[tokio::test] +async fn system_local_listen_addresses_works() { assert_eq!( - wait_receiver(api(None).system_local_listen_addresses()), + api(None) + .call::<_, Vec>("system_localListenAddresses", EmptyParams::new()) + .await + .unwrap(), vec![ - "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV" - .to_string(), + "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV", "/ip4/127.0.0.1/tcp/30334/ws/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV" - .to_string(), ] ); } -#[test] -fn system_peers() { +#[tokio::test] +async fn system_peers() { let peer_id = PeerId::random(); - let req = api(Status { peer_id, peers: 1, is_syncing: false, is_dev: true }).system_peers(); - let res = executor::block_on(req).unwrap(); + let peer_info: Vec> = + api(Status { peer_id, peers: 1, is_syncing: false, is_dev: true }) + .call("system_peers", EmptyParams::new()) + .await + .unwrap(); assert_eq!( - res, + peer_info, vec![PeerInfo { peer_id: peer_id.to_base58(), roles: "FULL".into(), @@ -237,14 +267,16 @@ fn system_peers() { ); } -#[test] -fn system_network_state() { - let req = api(None).system_network_state(); - let res = executor::block_on(req).unwrap(); - +#[tokio::test] +async fn system_network_state() { + use sc_network::network_state::NetworkState; + let network_state: NetworkState = api(None) + .call("system_unstable_networkState", EmptyParams::new()) + .await + .unwrap(); assert_eq!( - serde_json::from_value::(res).unwrap(), - sc_network::network_state::NetworkState { + network_state, + NetworkState { peer_id: String::new(), listened_addresses: Default::default(), external_addresses: Default::default(), @@ -255,50 +287,56 @@ fn system_network_state() { ); } -#[test] -fn system_node_roles() { - assert_eq!(wait_receiver(api(None).system_node_roles()), vec![NodeRole::Authority]); +#[tokio::test] +async fn system_node_roles() { + let node_roles: Vec = + api(None).call("system_nodeRoles", EmptyParams::new()).await.unwrap(); + assert_eq!(node_roles, vec![NodeRole::Authority]); } - -#[test] -fn system_sync_state() { - assert_eq!( - wait_receiver(api(None).system_sync_state()), - SyncState { starting_block: 1, current_block: 2, highest_block: Some(3) } - ); +#[tokio::test] +async fn system_sync_state() { + let sync_state: SyncState = + api(None).call("system_syncState", EmptyParams::new()).await.unwrap(); + assert_eq!(sync_state, SyncState { starting_block: 1, current_block: 2, highest_block: 3 }); } -#[test] -fn system_network_add_reserved() { +#[tokio::test] +async fn system_network_add_reserved() { let good_peer_id = - "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"; - let bad_peer_id = "/ip4/198.51.100.19/tcp/30333"; + ["/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"]; + let _good: () = api(None) + .call("system_addReservedPeer", good_peer_id) + .await + .expect("good peer id works"); - let good_fut = api(None).system_add_reserved_peer(good_peer_id.into()); - let bad_fut = api(None).system_add_reserved_peer(bad_peer_id.into()); - assert_eq!(executor::block_on(good_fut), Ok(())); - assert!(executor::block_on(bad_fut).is_err()); + let bad_peer_id = ["/ip4/198.51.100.19/tcp/30333"]; + assert_matches!( + api(None).call::<_, ()>("system_addReservedPeer", bad_peer_id).await, + Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Peer id is missing from the address") + ); } -#[test] -fn system_network_remove_reserved() { - let good_peer_id = "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"; - let bad_peer_id = - "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"; +#[tokio::test] +async fn system_network_remove_reserved() { + let _good_peer: () = api(None) + .call("system_removeReservedPeer", ["QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"]) + .await + .expect("call with good peer id works"); - let good_fut = api(None).system_remove_reserved_peer(good_peer_id.into()); - let bad_fut = api(None).system_remove_reserved_peer(bad_peer_id.into()); - assert_eq!(executor::block_on(good_fut), Ok(())); - assert!(executor::block_on(bad_fut).is_err()); -} + let bad_peer_id = + ["/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"]; -#[test] -fn system_network_reserved_peers() { - assert_eq!( - wait_receiver(api(None).system_reserved_peers()), - vec!["QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_string()] + assert_matches!( + api(None).call::<_, String>("system_removeReservedPeer", bad_peer_id).await, + Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("base-58 decode error: provided string contained invalid character '/' at byte 0") ); } +#[tokio::test] +async fn system_network_reserved_peers() { + let reserved_peers: Vec = + api(None).call("system_reservedPeers", EmptyParams::new()).await.unwrap(); + assert_eq!(reserved_peers, vec!["QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_string()],); +} #[test] fn test_add_reset_log_filter() { @@ -308,19 +346,27 @@ fn test_add_reset_log_filter() { // Enter log generation / filter reload if std::env::var("TEST_LOG_FILTER").is_ok() { - sc_tracing::logging::LoggerBuilder::new("test_before_add=debug").init().unwrap(); + let mut builder = sc_tracing::logging::LoggerBuilder::new("test_before_add=debug"); + builder.with_log_reloading(true); + builder.init().unwrap(); + for line in std::io::stdin().lock().lines() { let line = line.expect("Failed to read bytes"); if line.contains("add_reload") { - api(None) - .system_add_log_filter("test_after_add".into()) - .expect("`system_add_log_filter` failed"); + let filter = "test_after_add"; + let fut = + async move { api(None).call::<_, ()>("system_addLogFilter", [filter]).await }; + futures::executor::block_on(fut).expect("`system_addLogFilter` failed"); } else if line.contains("add_trace") { - api(None) - .system_add_log_filter("test_before_add=trace".into()) - .expect("`system_add_log_filter` failed"); + let filter = "test_before_add=trace"; + let fut = + async move { api(None).call::<_, ()>("system_addLogFilter", [filter]).await }; + futures::executor::block_on(fut).expect("`system_addLogFilter (trace)` failed"); } else if line.contains("reset") { - api(None).system_reset_log_filter().expect("`system_reset_log_filter` failed"); + let fut = async move { + api(None).call::<_, ()>("system_resetLogFilter", EmptyParams::new()).await + }; + futures::executor::block_on(fut).expect("`system_resetLogFilter` failed"); } else if line.contains("exit") { return } @@ -351,26 +397,26 @@ fn test_add_reset_log_filter() { }; // Initiate logs loop in child process - child_in.write(b"\n").unwrap(); + child_in.write_all(b"\n").unwrap(); assert!(read_line().contains(EXPECTED_BEFORE_ADD)); // Initiate add directive & reload in child process - child_in.write(b"add_reload\n").unwrap(); + child_in.write_all(b"add_reload\n").unwrap(); assert!(read_line().contains(EXPECTED_BEFORE_ADD)); assert!(read_line().contains(EXPECTED_AFTER_ADD)); // Check that increasing the max log level works - child_in.write(b"add_trace\n").unwrap(); + child_in.write_all(b"add_trace\n").unwrap(); assert!(read_line().contains(EXPECTED_WITH_TRACE)); assert!(read_line().contains(EXPECTED_BEFORE_ADD)); assert!(read_line().contains(EXPECTED_AFTER_ADD)); // Initiate logs filter reset in child process - child_in.write(b"reset\n").unwrap(); + child_in.write_all(b"reset\n").unwrap(); assert!(read_line().contains(EXPECTED_BEFORE_ADD)); // Return from child process - child_in.write(b"exit\n").unwrap(); + child_in.write_all(b"exit\n").unwrap(); assert!(child_process.wait().expect("Error waiting for child process").success()); // Check for EOF diff --git a/client/rpc/src/testing.rs b/client/rpc/src/testing.rs index 23071ba10e0d6..584e4a9901eab 100644 --- a/client/rpc/src/testing.rs +++ b/client/rpc/src/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,29 +18,16 @@ //! Testing utils used by the RPC tests. -use futures::{ - executor, - task::{FutureObj, Spawn, SpawnError}, -}; - -// Executor shared by all tests. -// -// This shared executor is used to prevent `Too many open files` errors -// on systems with a lot of cores. -lazy_static::lazy_static! { - static ref EXECUTOR: executor::ThreadPool = executor::ThreadPool::new() - .expect("Failed to create thread pool executor for tests"); +use std::{future::Future, sync::Arc}; + +use sp_core::testing::TaskExecutor; + +/// Executor for testing. +pub fn test_executor() -> Arc { + Arc::new(TaskExecutor::default()) } -/// Executor for use in testing -pub struct TaskExecutor; -impl Spawn for TaskExecutor { - fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { - EXECUTOR.spawn_ok(future); - Ok(()) - } - - fn status(&self) -> Result<(), SpawnError> { - Ok(()) - } +/// Wrap a future in a timeout a little more concisely +pub fn timeout_secs>(s: u64, f: F) -> tokio::time::Timeout { + tokio::time::timeout(std::time::Duration::from_secs(s), f) } diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 5120cc8f4dfaa..4d1d267d45c97 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-service" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate service. Starts a thread that spins up the network, client, and extrinsic pool. Manages communication between them." readme = "README.md" @@ -13,51 +13,54 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [features] -default = ["db"] +default = ["rocksdb"] # The RocksDB feature activates the RocksDB database backend. If it is not activated, and you pass # a path to a database, an error will be produced at runtime. -db = ["sc-client-db/with-kvdb-rocksdb", "sc-client-db/with-parity-db"] -wasmtime = ["sc-executor/wasmtime"] +rocksdb = ["sc-client-db/rocksdb"] # exposes the client type test-helpers = [] +runtime-benchmarks = ["sc-client-db/runtime-benchmarks"] [dependencies] -thiserror = "1.0.21" -futures = "0.3.16" -jsonrpc-pubsub = "18.0" -jsonrpc-core = "18.0" +jsonrpsee = { version = "0.16.2", features = ["server"] } +thiserror = "1.0.30" +futures = "0.3.21" rand = "0.7.3" -parking_lot = "0.11.1" -log = "0.4.11" +parking_lot = "0.12.1" +log = "0.4.17" futures-timer = "3.0.1" exit-future = "0.2.0" -pin-project = "1.0.4" +pin-project = "1.0.12" hash-db = "0.15.2" -serde = "1.0.126" -serde_json = "1.0.68" +serde = "1.0.136" +serde_json = "1.0.85" sc-keystore = { version = "4.0.0-dev", path = "../keystore" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sp-trie = { version = "4.0.0-dev", path = "../../primitives/trie" } -sp-externalities = { version = "0.10.0-dev", path = "../../primitives/externalities" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-trie = { version = "7.0.0", path = "../../primitives/trie" } +sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" } sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-version = { version = "4.0.0-dev", path = "../../primitives/version" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", path = "../../primitives/keystore" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } -sp-state-machine = { version = "0.10.0-dev", path = "../../primitives/state-machine" } -sp-application-crypto = { version = "4.0.0-dev", path = "../../primitives/application-crypto" } +sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } +sp-application-crypto = { version = "7.0.0", path = "../../primitives/application-crypto" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../client/consensus/common" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sp-storage = { version = "4.0.0-dev", path = "../../primitives/storage" } +sp-storage = { version = "7.0.0", path = "../../primitives/storage" } sc-network = { version = "0.10.0-dev", path = "../network" } +sc-network-bitswap = { version = "0.10.0-dev", path = "../network/bitswap" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } +sc-network-light = { version = "0.10.0-dev", path = "../network/light" } +sc-network-sync = { version = "0.10.0-dev", path = "../network/sync" } +sc-network-transactions = { version = "0.10.0-dev", path = "../network/transactions" } sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } -sc-light = { version = "4.0.0-dev", path = "../light" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../db" } -codec = { package = "parity-scale-codec", version = "2.0.0" } +codec = { package = "parity-scale-codec", version = "3.0.0" } sc-executor = { version = "0.10.0-dev", path = "../executor" } sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } sp-transaction-pool = { version = "4.0.0-dev", path = "../../primitives/transaction-pool" } @@ -65,25 +68,24 @@ sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/a sp-transaction-storage-proof = { version = "4.0.0-dev", path = "../../primitives/transaction-storage-proof" } sc-rpc-server = { version = "4.0.0-dev", path = "../rpc-servers" } sc-rpc = { version = "4.0.0-dev", path = "../rpc" } +sc-rpc-spec-v2 = { version = "0.10.0-dev", path = "../rpc-spec-v2" } sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder" } sc-informant = { version = "0.10.0-dev", path = "../informant" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } sc-offchain = { version = "4.0.0-dev", path = "../offchain" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.9.0" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev" } sc-tracing = { version = "4.0.0-dev", path = "../tracing" } -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } -tracing = "0.1.25" +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sc-sysinfo = { version = "6.0.0-dev", path = "../sysinfo" } +tracing = "0.1.29" tracing-futures = { version = "0.2.4" } -parity-util-mem = { version = "0.10.0", default-features = false, features = [ - "primitive-types", -] } -async-trait = "0.1.50" -tokio = { version = "1.10", features = ["time", "rt-multi-thread"] } +async-trait = "0.1.57" +tokio = { version = "1.22.0", features = ["time", "rt-multi-thread", "parking_lot"] } tempfile = "3.1.0" -directories = "3.0.2" +directories = "4.0.1" +static_init = "1.0.3" [dev-dependencies] substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime/" } -async-std = { version = "1.6.5", default-features = false } diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index e01a85878817c..7153672030d6a 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,34 +18,47 @@ use crate::{ build_network_future, - client::{light, Client, ClientConfig}, - config::{Configuration, KeystoreConfig, PrometheusConfig, TransactionStorageMode}, + client::{Client, ClientConfig}, + config::{Configuration, KeystoreConfig, PrometheusConfig}, error::Error, metrics::MetricsService, start_rpc_servers, RpcHandlers, SpawnTaskHandle, TaskManager, TransactionPoolAdapter, }; use futures::{channel::oneshot, future::ready, FutureExt, StreamExt}; -use jsonrpc_pubsub::manager::SubscriptionManager; +use jsonrpsee::RpcModule; use log::info; use prometheus_endpoint::Registry; use sc_chain_spec::get_extension; use sc_client_api::{ - execution_extensions::ExecutionExtensions, light::RemoteBlockchain, - proof_provider::ProofProvider, BadBlocks, BlockBackend, BlockchainEvents, ExecutorProvider, - ForkBlocks, StorageProvider, UsageProvider, + execution_extensions::ExecutionExtensions, proof_provider::ProofProvider, BadBlocks, + BlockBackend, BlockchainEvents, ExecutorProvider, ForkBlocks, StorageProvider, UsageProvider, }; use sc_client_db::{Backend, DatabaseSettings}; use sc_consensus::import_queue::ImportQueue; use sc_executor::RuntimeVersionOf; use sc_keystore::LocalKeystore; -use sc_network::{ - block_request_handler::{self, BlockRequestHandler}, - config::{OnDemand, Role, SyncMode}, - light_client_requests::{self, handler::LightClientRequestHandler}, - state_request_handler::{self, StateRequestHandler}, - warp_request_handler::{self, RequestHandler as WarpSyncRequestHandler, WarpSyncProvider}, - NetworkService, +use sc_network::{config::SyncMode, NetworkService}; +use sc_network_bitswap::BitswapRequestHandler; +use sc_network_common::{ + protocol::role::Roles, + service::{NetworkStateInfo, NetworkStatusProvider}, + sync::warp::WarpSyncProvider, }; +use sc_network_light::light_client_requests::handler::LightClientRequestHandler; +use sc_network_sync::{ + block_request_handler::BlockRequestHandler, service::network::NetworkServiceProvider, + state_request_handler::StateRequestHandler, + warp_request_handler::RequestHandler as WarpSyncRequestHandler, ChainSync, +}; +use sc_rpc::{ + author::AuthorApiServer, + chain::ChainApiServer, + offchain::OffchainApiServer, + state::{ChildStateApiServer, StateApiServer}, + system::SystemApiServer, + DenyUnsafe, SubscriptionTaskExecutor, +}; +use sc_rpc_spec_v2::transaction::TransactionApiServer; use sc_telemetry::{telemetry, ConnectionMessage, Telemetry, TelemetryHandle, SUBSTRATE_INFO}; use sc_transaction_pool_api::MaintainedTransactionPool; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; @@ -58,74 +71,11 @@ use sp_core::traits::{CodeExecutor, SpawnNamed}; use sp_keystore::{CryptoStore, SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ generic::BlockId, - traits::{Block as BlockT, BlockIdTo, HashFor, Zero}, + traits::{Block as BlockT, BlockIdTo, NumberFor, Zero}, BuildStorage, }; use std::{str::FromStr, sync::Arc, time::SystemTime}; -/// A utility trait for building an RPC extension given a `DenyUnsafe` instance. -/// This is useful since at service definition time we don't know whether the -/// specific interface where the RPC extension will be exposed is safe or not. -/// This trait allows us to lazily build the RPC extension whenever we bind the -/// service to an interface. -pub trait RpcExtensionBuilder { - /// The type of the RPC extension that will be built. - type Output: sc_rpc::RpcExtension; - - /// Returns an instance of the RPC extension for a particular `DenyUnsafe` - /// value, e.g. the RPC extension might not expose some unsafe methods. - fn build( - &self, - deny: sc_rpc::DenyUnsafe, - subscription_executor: sc_rpc::SubscriptionTaskExecutor, - ) -> Result; -} - -impl RpcExtensionBuilder for F -where - F: Fn(sc_rpc::DenyUnsafe, sc_rpc::SubscriptionTaskExecutor) -> Result, - R: sc_rpc::RpcExtension, -{ - type Output = R; - - fn build( - &self, - deny: sc_rpc::DenyUnsafe, - subscription_executor: sc_rpc::SubscriptionTaskExecutor, - ) -> Result { - (*self)(deny, subscription_executor) - } -} - -/// A utility struct for implementing an `RpcExtensionBuilder` given a cloneable -/// `RpcExtension`, the resulting builder will simply ignore the provided -/// `DenyUnsafe` instance and return a static `RpcExtension` instance. -pub struct NoopRpcExtensionBuilder(pub R); - -impl RpcExtensionBuilder for NoopRpcExtensionBuilder -where - R: Clone + sc_rpc::RpcExtension, -{ - type Output = R; - - fn build( - &self, - _deny: sc_rpc::DenyUnsafe, - _subscription_executor: sc_rpc::SubscriptionTaskExecutor, - ) -> Result { - Ok(self.0.clone()) - } -} - -impl From for NoopRpcExtensionBuilder -where - R: sc_rpc::RpcExtension, -{ - fn from(e: R) -> NoopRpcExtensionBuilder { - NoopRpcExtensionBuilder(e) - } -} - /// Full client type. pub type TFullClient = Client, TFullCallExecutor, TBl, TRtApi>; @@ -137,47 +87,9 @@ pub type TFullBackend = sc_client_db::Backend; pub type TFullCallExecutor = crate::client::LocalCallExecutor, TExec>; -/// Light client type. -pub type TLightClient = - TLightClientWithBackend>; - -/// Light client backend type. -pub type TLightBackend = - sc_light::Backend, HashFor>; - -/// Light call executor type. -pub type TLightCallExecutor = sc_light::GenesisCallExecutor< - sc_light::Backend, HashFor>, - crate::client::LocalCallExecutor< - TBl, - sc_light::Backend, HashFor>, - TExec, - >, ->; - type TFullParts = (TFullClient, Arc>, KeystoreContainer, TaskManager); -type TLightParts = ( - Arc>, - Arc>, - KeystoreContainer, - TaskManager, - Arc>, -); - -/// Light client backend type with a specific hash type. -pub type TLightBackendWithHash = - sc_light::Backend, THash>; - -/// Light client type with a specific backend. -pub type TLightClientWithBackend = Client< - TBackend, - sc_light::GenesisCallExecutor>, - TBl, - TRtApi, ->; - trait AsCryptoStoreRef { fn keystore_ref(&self) -> Arc; fn sync_keystore_ref(&self) -> Arc; @@ -266,7 +178,6 @@ pub fn new_full_client( where TBl: BlockT, TExec: CodeExecutor + RuntimeVersionOf + Clone, - TBl::Hash: FromStr, { new_full_parts(config, telemetry, executor).map(|parts| parts.0) } @@ -280,7 +191,6 @@ pub fn new_full_parts( where TBl: BlockT, TExec: CodeExecutor + RuntimeVersionOf + Clone, - TBl::Hash: FromStr, { let keystore_container = KeystoreContainer::new(&config.keystore)?; @@ -300,12 +210,10 @@ where let (client, backend) = { let db_config = sc_client_db::DatabaseSettings { - state_cache_size: config.state_cache_size, - state_cache_child_ratio: config.state_cache_child_ratio.map(|v| (v, 100)), + trie_cache_maximum_size: config.trie_cache_maximum_size, state_pruning: config.state_pruning.clone(), source: config.database.clone(), - keep_blocks: config.keep_blocks.clone(), - transaction_storage: config.transaction_storage.clone(), + blocks_pruning: config.blocks_pruning, }; let backend = new_db_backend(db_config)?; @@ -320,14 +228,16 @@ where .chain_spec .code_substitutes() .into_iter() - .map(|(h, c)| { - let hash = TBl::Hash::from_str(&h).map_err(|_| { + .map(|(n, c)| { + let number = NumberFor::::from_str(&n).map_err(|_| { Error::Application(Box::from(format!( - "Failed to parse `{}` as block hash for code substitutes.", - h + "Failed to parse `{}` as block number for code substitutes. \ + In an old version the key for code substitute was a block hash. \ + Please update the chain spec to a version that is compatible with your node.", + n ))) })?; - Ok((hash, c)) + Ok((number, c)) }) .collect::, Error>>()?; @@ -347,7 +257,7 @@ where wasm_runtime_overrides: config.wasm_runtime_overrides.clone(), no_genesis: matches!( config.network.sync_mode, - sc_network::config::SyncMode::Fast { .. } | sc_network::config::SyncMode::Warp + SyncMode::Fast { .. } | SyncMode::Warp { .. } ), wasm_runtime_substitutes, }, @@ -359,53 +269,6 @@ where Ok((client, backend, keystore_container, task_manager)) } -/// Create the initial parts of a light node. -pub fn new_light_parts( - config: &Configuration, - telemetry: Option, - executor: TExec, -) -> Result, Error> -where - TBl: BlockT, - TExec: CodeExecutor + RuntimeVersionOf + Clone, -{ - let keystore_container = KeystoreContainer::new(&config.keystore)?; - let task_manager = { - let registry = config.prometheus_config.as_ref().map(|cfg| &cfg.registry); - TaskManager::new(config.tokio_handle.clone(), registry)? - }; - - let db_storage = { - let db_settings = sc_client_db::DatabaseSettings { - state_cache_size: config.state_cache_size, - state_cache_child_ratio: config.state_cache_child_ratio.map(|v| (v, 100)), - state_pruning: config.state_pruning.clone(), - source: config.database.clone(), - keep_blocks: config.keep_blocks.clone(), - transaction_storage: config.transaction_storage.clone(), - }; - sc_client_db::light::LightStorage::new(db_settings)? - }; - let light_blockchain = sc_light::new_light_blockchain(db_storage); - let fetch_checker = Arc::new(sc_light::new_fetch_checker::<_, TBl, _>( - light_blockchain.clone(), - executor.clone(), - Box::new(task_manager.spawn_handle()), - )); - let on_demand = Arc::new(sc_network::config::OnDemand::new(fetch_checker)); - let backend = sc_light::new_light_backend(light_blockchain); - let client = Arc::new(light::new_light( - backend.clone(), - config.chain_spec.as_storage_builder(), - executor, - Box::new(task_manager.spawn_handle()), - config.prometheus_config.as_ref().map(|config| config.registry.clone()), - telemetry, - )?); - - Ok((client, backend, keystore_container, task_manager, on_demand)) -} - /// Create an instance of default DB-backend backend. pub fn new_db_backend( settings: DatabaseSettings, @@ -448,45 +311,67 @@ where executor, spawn_handle, config.clone(), + execution_extensions, )?; - Ok(crate::client::Client::new( + crate::client::Client::new( backend, executor, genesis_storage, fork_blocks, bad_blocks, - execution_extensions, prometheus_registry, telemetry, config, - )?) + ) +} + +/// Shared network instance implementing a set of mandatory traits. +pub trait SpawnTaskNetwork: + sc_offchain::NetworkProvider + + NetworkStateInfo + + NetworkStatusProvider + + Send + + Sync + + 'static +{ +} + +impl SpawnTaskNetwork for T +where + Block: BlockT, + T: sc_offchain::NetworkProvider + + NetworkStateInfo + + NetworkStatusProvider + + Send + + Sync + + 'static, +{ } /// Parameters to pass into `build`. pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> { /// The service configuration. pub config: Configuration, - /// A shared client returned by `new_full_parts`/`new_light_parts`. + /// A shared client returned by `new_full_parts`. pub client: Arc, - /// A shared backend returned by `new_full_parts`/`new_light_parts`. + /// A shared backend returned by `new_full_parts`. pub backend: Arc, - /// A task manager returned by `new_full_parts`/`new_light_parts`. + /// A task manager returned by `new_full_parts`. pub task_manager: &'a mut TaskManager, - /// A shared keystore returned by `new_full_parts`/`new_light_parts`. + /// A shared keystore returned by `new_full_parts`. pub keystore: SyncCryptoStorePtr, - /// An optional, shared data fetcher for light clients. - pub on_demand: Option>>, /// A shared transaction pool. pub transaction_pool: Arc, - /// A RPC extension builder. Use `NoopRpcExtensionBuilder` if you just want to pass in the - /// extensions directly. - pub rpc_extensions_builder: Box + Send>, - /// An optional, shared remote blockchain instance. Used for light clients. - pub remote_blockchain: Option>>, + /// Builds additional [`RpcModule`]s that should be added to the server + pub rpc_builder: + Box Result, Error>>, /// A shared network instance. - pub network: Arc::Hash>>, + pub network: Arc>, /// A Sender for RPC requests. pub system_rpc_tx: TracingUnboundedSender>, + /// Controller for transactions handlers + pub tx_handler_controller: + sc_network_transactions::TransactionsHandlerController<::Hash>, /// Telemetry instance for this node. pub telemetry: Option<&'a mut Telemetry>, } @@ -496,7 +381,7 @@ pub fn build_offchain_workers( config: &Configuration, spawn_handle: SpawnTaskHandle, client: Arc, - network: Arc::Hash>>, + network: Arc, ) -> Option>> where TBl: BlockT, @@ -509,12 +394,13 @@ where if let Some(offchain) = offchain_workers.clone() { spawn_handle.spawn( "offchain-notifications", + Some("offchain-worker"), sc_offchain::notification_future( config.role.is_authority(), - client.clone(), + client, offchain, Clone::clone(&spawn_handle), - network.clone(), + network, ), ); } @@ -550,23 +436,19 @@ where TBl::Hash: Unpin, TBl::Header: Unpin, TBackend: 'static + sc_client_api::backend::Backend + Send, - TExPool: MaintainedTransactionPool::Hash> - + parity_util_mem::MallocSizeOf - + 'static, - TRpc: sc_rpc::RpcExtension, + TExPool: MaintainedTransactionPool::Hash> + 'static, { let SpawnTasksParams { mut config, task_manager, client, - on_demand, backend, keystore, transaction_pool, - rpc_extensions_builder, - remote_blockchain, + rpc_builder, network, system_rpc_tx, + tx_handler_controller, telemetry, } = params; @@ -579,8 +461,13 @@ where ) .map_err(|e| Error::Application(Box::new(e)))?; + let sysinfo = sc_sysinfo::gather_sysinfo(); + sc_sysinfo::print_sysinfo(&sysinfo); + let telemetry = telemetry - .map(|telemetry| init_telemetry(&mut config, network.clone(), client.clone(), telemetry)) + .map(|telemetry| { + init_telemetry(&mut config, network.clone(), client.clone(), telemetry, Some(sysinfo)) + }) .transpose()?; info!("📦 Highest known block at #{}", chain_info.best_number); @@ -590,96 +477,90 @@ where // Inform the tx pool about imported and finalized blocks. spawn_handle.spawn( "txpool-notifications", + Some("transaction-pool"), sc_transaction_pool::notification_future(client.clone(), transaction_pool.clone()), ); spawn_handle.spawn( "on-transaction-imported", - transaction_notifications(transaction_pool.clone(), network.clone(), telemetry.clone()), + Some("transaction-pool"), + transaction_notifications( + transaction_pool.clone(), + tx_handler_controller, + telemetry.clone(), + ), ); // Prometheus metrics. let metrics_service = if let Some(PrometheusConfig { port, registry }) = config.prometheus_config.clone() { // Set static metrics. - let metrics = MetricsService::with_prometheus(telemetry.clone(), ®istry, &config)?; + let metrics = MetricsService::with_prometheus(telemetry, ®istry, &config)?; spawn_handle.spawn( "prometheus-endpoint", + None, prometheus_endpoint::init_prometheus(port, registry).map(drop), ); metrics } else { - MetricsService::new(telemetry.clone()) + MetricsService::new(telemetry) }; // Periodically updated metrics and telemetry updates. spawn_handle.spawn( "telemetry-periodic-send", + None, metrics_service.run(client.clone(), transaction_pool.clone(), network.clone()), ); - // RPC - let gen_handler = |deny_unsafe: sc_rpc::DenyUnsafe, - rpc_middleware: sc_rpc_server::RpcMiddleware| { - gen_handler( + let rpc_id_provider = config.rpc_id_provider.take(); + + // jsonrpsee RPC + let gen_rpc_module = |deny_unsafe: DenyUnsafe| { + gen_rpc_module( deny_unsafe, - rpc_middleware, - &config, task_manager.spawn_handle(), client.clone(), transaction_pool.clone(), keystore.clone(), - on_demand.clone(), - remote_blockchain.clone(), - &*rpc_extensions_builder, - backend.offchain_storage(), system_rpc_tx.clone(), + &config, + backend.offchain_storage(), + &*rpc_builder, ) }; - let rpc_metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?; - let server_metrics = sc_rpc_server::ServerMetrics::new(config.prometheus_registry())?; - let rpc = start_rpc_servers(&config, gen_handler, rpc_metrics.clone(), server_metrics)?; - // This is used internally, so don't restrict access to unsafe RPC - let known_rpc_method_names = - sc_rpc_server::method_names(|m| gen_handler(sc_rpc::DenyUnsafe::No, m))?; - let rpc_handlers = RpcHandlers(Arc::new( - gen_handler( - sc_rpc::DenyUnsafe::No, - sc_rpc_server::RpcMiddleware::new(rpc_metrics, known_rpc_method_names, "inbrowser"), - )? - .into(), - )); + + let rpc = start_rpc_servers(&config, gen_rpc_module, rpc_id_provider)?; + let rpc_handlers = RpcHandlers(Arc::new(gen_rpc_module(sc_rpc::DenyUnsafe::No)?.into())); // Spawn informant task spawn_handle.spawn( "informant", - sc_informant::build( - client.clone(), - network.clone(), - transaction_pool.clone(), - config.informant_output_format, - ), + None, + sc_informant::build(client.clone(), network, config.informant_output_format), ); - task_manager.keep_alive((config.base_path, rpc, rpc_handlers.clone())); + task_manager.keep_alive((config.base_path, rpc)); Ok(rpc_handlers) } -async fn transaction_notifications( - transaction_pool: Arc, - network: Arc::Hash>>, +async fn transaction_notifications( + transaction_pool: Arc, + tx_handler_controller: sc_network_transactions::TransactionsHandlerController< + ::Hash, + >, telemetry: Option, ) where - TBl: BlockT, - TExPool: MaintainedTransactionPool::Hash>, + Block: BlockT, + ExPool: MaintainedTransactionPool::Hash>, { // transaction notifications transaction_pool .import_notification_stream() .for_each(move |hash| { - network.propagate_transaction(hash); + tx_handler_controller.propagate_transaction(hash); let status = transaction_pool.status(); telemetry!( telemetry; @@ -693,17 +574,26 @@ async fn transaction_notifications( .await; } -fn init_telemetry>( +fn init_telemetry( config: &mut Configuration, - network: Arc::Hash>>, - client: Arc, + network: Network, + client: Arc, telemetry: &mut Telemetry, -) -> sc_telemetry::Result { + sysinfo: Option, +) -> sc_telemetry::Result +where + Block: BlockT, + Client: BlockBackend, + Network: NetworkStateInfo, +{ let genesis_hash = client.block_hash(Zero::zero()).ok().flatten().unwrap_or_default(); let connection_message = ConnectionMessage { name: config.network.node_name.to_owned(), implementation: config.impl_name.to_owned(), version: config.impl_version.to_owned(), + target_os: sc_sysinfo::TARGET_OS.into(), + target_arch: sc_sysinfo::TARGET_ARCH.into(), + target_env: sc_sysinfo::TARGET_ENV.into(), config: String::new(), chain: config.chain_spec.name().to_owned(), genesis_hash: format!("{:?}", genesis_hash), @@ -714,6 +604,7 @@ fn init_telemetry>( .unwrap_or(0) .to_string(), network_id: network.local_peer_id().to_base58(), + sysinfo, }; telemetry.start_telemetry(connection_message)?; @@ -721,20 +612,17 @@ fn init_telemetry>( Ok(telemetry.handle()) } -fn gen_handler( - deny_unsafe: sc_rpc::DenyUnsafe, - rpc_middleware: sc_rpc_server::RpcMiddleware, - config: &Configuration, +fn gen_rpc_module( + deny_unsafe: DenyUnsafe, spawn_handle: SpawnTaskHandle, client: Arc, transaction_pool: Arc, keystore: SyncCryptoStorePtr, - on_demand: Option>>, - remote_blockchain: Option>>, - rpc_extensions_builder: &(dyn RpcExtensionBuilder + Send), - offchain_storage: Option<>::OffchainStorage>, system_rpc_tx: TracingUnboundedSender>, -) -> Result, Error> + config: &Configuration, + offchain_storage: Option<>::OffchainStorage>, + rpc_builder: &(dyn Fn(DenyUnsafe, SubscriptionTaskExecutor) -> Result, Error>), +) -> Result, Error> where TBl: BlockT, TCl: ProvideRuntimeApi @@ -749,15 +637,12 @@ where + Send + Sync + 'static, - TExPool: MaintainedTransactionPool::Hash> + 'static, TBackend: sc_client_api::backend::Backend + 'static, - TRpc: sc_rpc::RpcExtension, >::Api: sp_session::SessionKeys + sp_api::Metadata, + TExPool: MaintainedTransactionPool::Hash> + 'static, TBl::Hash: Unpin, TBl::Header: Unpin, { - use sc_rpc::{author, chain, offchain, state, system}; - let system_info = sc_rpc::system::SystemInfo { chain_name: config.chain_spec.name().into(), impl_name: config.impl_name.clone(), @@ -766,66 +651,68 @@ where chain_type: config.chain_spec.chain_type(), }; - let task_executor = sc_rpc::SubscriptionTaskExecutor::new(spawn_handle); - let subscriptions = SubscriptionManager::new(Arc::new(task_executor.clone())); - - let (chain, state, child_state) = - if let (Some(remote_blockchain), Some(on_demand)) = (remote_blockchain, on_demand) { - // Light clients - let chain = sc_rpc::chain::new_light( - client.clone(), - subscriptions.clone(), - remote_blockchain.clone(), - on_demand.clone(), - ); - let (state, child_state) = sc_rpc::state::new_light( - client.clone(), - subscriptions.clone(), - remote_blockchain.clone(), - on_demand, - deny_unsafe, - ); - (chain, state, child_state) - } else { - // Full nodes - let chain = sc_rpc::chain::new_full(client.clone(), subscriptions.clone()); - let (state, child_state) = sc_rpc::state::new_full( - client.clone(), - subscriptions.clone(), - deny_unsafe, - config.rpc_max_payload, - ); - (chain, state, child_state) - }; + let mut rpc_api = RpcModule::new(()); + let task_executor = Arc::new(spawn_handle); - let author = - sc_rpc::author::Author::new(client, transaction_pool, subscriptions, keystore, deny_unsafe); - let system = system::System::new(system_info, system_rpc_tx, deny_unsafe); + let (chain, state, child_state) = { + let chain = sc_rpc::chain::new_full(client.clone(), task_executor.clone()).into_rpc(); + let (state, child_state) = sc_rpc::state::new_full( + client.clone(), + task_executor.clone(), + deny_unsafe, + config.rpc_max_payload, + ); + let state = state.into_rpc(); + let child_state = child_state.into_rpc(); - let maybe_offchain_rpc = offchain_storage.map(|storage| { - let offchain = sc_rpc::offchain::Offchain::new(storage, deny_unsafe); - offchain::OffchainApi::to_delegate(offchain) - }); + (chain, state, child_state) + }; - Ok(sc_rpc_server::rpc_handler( - ( - state::StateApi::to_delegate(state), - state::ChildStateApi::to_delegate(child_state), - chain::ChainApi::to_delegate(chain), - maybe_offchain_rpc, - author::AuthorApi::to_delegate(author), - system::SystemApi::to_delegate(system), - rpc_extensions_builder.build(deny_unsafe, task_executor)?, - ), - rpc_middleware, - )) + let transaction_v2 = sc_rpc_spec_v2::transaction::Transaction::new( + client.clone(), + transaction_pool.clone(), + task_executor.clone(), + ) + .into_rpc(); + + let author = sc_rpc::author::Author::new( + client.clone(), + transaction_pool, + keystore, + deny_unsafe, + task_executor.clone(), + ) + .into_rpc(); + + let system = sc_rpc::system::System::new(system_info, system_rpc_tx, deny_unsafe).into_rpc(); + + if let Some(storage) = offchain_storage { + let offchain = sc_rpc::offchain::Offchain::new(storage, deny_unsafe).into_rpc(); + + rpc_api.merge(offchain).map_err(|e| Error::Application(e.into()))?; + } + + // Part of the RPC v2 spec. + rpc_api.merge(transaction_v2).map_err(|e| Error::Application(e.into()))?; + + // Part of the old RPC spec. + rpc_api.merge(chain).map_err(|e| Error::Application(e.into()))?; + rpc_api.merge(author).map_err(|e| Error::Application(e.into()))?; + rpc_api.merge(system).map_err(|e| Error::Application(e.into()))?; + rpc_api.merge(state).map_err(|e| Error::Application(e.into()))?; + rpc_api.merge(child_state).map_err(|e| Error::Application(e.into()))?; + // Additional [`RpcModule`]s defined in the node to fit the specific blockchain + let extra_rpcs = rpc_builder(deny_unsafe, task_executor.clone())?; + rpc_api.merge(extra_rpcs).map_err(|e| Error::Application(e.into()))?; + + Ok(rpc_api) } /// Parameters to pass into `build_network`. pub struct BuildNetworkParams<'a, TBl: BlockT, TExPool, TImpQu, TCl> { /// The service configuration. pub config: &'a Configuration, - /// A shared client returned by `new_full_parts`/`new_light_parts`. + /// A shared client returned by `new_full_parts`. pub client: Arc, /// A shared transaction pool. pub transaction_pool: Arc, @@ -833,15 +720,12 @@ pub struct BuildNetworkParams<'a, TBl: BlockT, TExPool, TImpQu, TCl> { pub spawn_handle: SpawnTaskHandle, /// An import queue. pub import_queue: TImpQu, - /// An optional, shared data fetcher for light clients. - pub on_demand: Option>>, /// A block announce validator builder. pub block_announce_validator_builder: Option) -> Box + Send> + Send>>, /// An optional warp sync provider. pub warp_sync: Option>>, } - /// Build the network service, the network status sinks and an RPC sender. pub fn build_network( params: BuildNetworkParams, @@ -849,6 +733,7 @@ pub fn build_network( ( Arc::Hash>>, TracingUnboundedSender>, + sc_network_transactions::TransactionsHandlerController<::Hash>, NetworkStarter, ), Error, @@ -873,16 +758,23 @@ where transaction_pool, spawn_handle, import_queue, - on_demand, block_announce_validator_builder, warp_sync, } = params; - let transaction_pool_adapter = Arc::new(TransactionPoolAdapter { - imports_external_transactions: !matches!(config.role, Role::Light), - pool: transaction_pool, - client: client.clone(), - }); + let mut request_response_protocol_configs = Vec::new(); + + if warp_sync.is_none() && config.network.sync_mode.is_warp() { + return Err("Warp sync enabled, but no warp sync provider configured.".into()) + } + + if client.requires_full_sync() { + match config.network.sync_mode { + SyncMode::Fast { .. } => return Err("Fast sync doesn't work for archive nodes".into()), + SyncMode::Warp => return Err("Warp sync doesn't work for archive nodes".into()), + SyncMode::Full => {}, + } + } let protocol_id = config.protocol_id(); @@ -893,105 +785,149 @@ where }; let block_request_protocol_config = { - if matches!(config.role, Role::Light) { - // Allow outgoing requests but deny incoming requests. - block_request_handler::generate_protocol_config(&protocol_id) - } else { - // Allow both outgoing and incoming requests. - let (handler, protocol_config) = BlockRequestHandler::new( - &protocol_id, - client.clone(), - config.network.default_peers_set.in_peers as usize + - config.network.default_peers_set.out_peers as usize, - ); - spawn_handle.spawn("block_request_handler", handler.run()); - protocol_config - } + // Allow both outgoing and incoming requests. + let (handler, protocol_config) = BlockRequestHandler::new( + &protocol_id, + config.chain_spec.fork_id(), + client.clone(), + config.network.default_peers_set.in_peers as usize + + config.network.default_peers_set.out_peers as usize, + ); + spawn_handle.spawn("block-request-handler", Some("networking"), handler.run()); + protocol_config }; let state_request_protocol_config = { - if matches!(config.role, Role::Light) { - // Allow outgoing requests but deny incoming requests. - state_request_handler::generate_protocol_config(&protocol_id) - } else { - // Allow both outgoing and incoming requests. - let (handler, protocol_config) = StateRequestHandler::new( - &protocol_id, - client.clone(), - config.network.default_peers_set.in_peers as usize + - config.network.default_peers_set.out_peers as usize, - ); - spawn_handle.spawn("state_request_handler", handler.run()); - protocol_config - } + // Allow both outgoing and incoming requests. + let (handler, protocol_config) = StateRequestHandler::new( + &protocol_id, + config.chain_spec.fork_id(), + client.clone(), + config.network.default_peers_set_num_full as usize, + ); + spawn_handle.spawn("state-request-handler", Some("networking"), handler.run()); + protocol_config }; - let warp_sync_params = warp_sync.map(|provider| { - let protocol_config = if matches!(config.role, Role::Light) { - // Allow outgoing requests but deny incoming requests. - warp_request_handler::generate_request_response_config(protocol_id.clone()) - } else { + let (warp_sync_provider, warp_sync_protocol_config) = warp_sync + .map(|provider| { // Allow both outgoing and incoming requests. - let (handler, protocol_config) = - WarpSyncRequestHandler::new(protocol_id.clone(), provider.clone()); - spawn_handle.spawn("warp_sync_request_handler", handler.run()); - protocol_config - }; - (provider, protocol_config) - }); + let (handler, protocol_config) = WarpSyncRequestHandler::new( + protocol_id.clone(), + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + config.chain_spec.fork_id(), + provider.clone(), + ); + spawn_handle.spawn("warp-sync-request-handler", Some("networking"), handler.run()); + (Some(provider), Some(protocol_config)) + }) + .unwrap_or_default(); let light_client_request_protocol_config = { - if matches!(config.role, Role::Light) { - // Allow outgoing requests but deny incoming requests. - light_client_requests::generate_protocol_config(&protocol_id) - } else { - // Allow both outgoing and incoming requests. - let (handler, protocol_config) = - LightClientRequestHandler::new(&protocol_id, client.clone()); - spawn_handle.spawn("light_client_request_handler", handler.run()); - protocol_config - } + // Allow both outgoing and incoming requests. + let (handler, protocol_config) = LightClientRequestHandler::new( + &protocol_id, + config.chain_spec.fork_id(), + client.clone(), + ); + spawn_handle.spawn("light-client-request-handler", Some("networking"), handler.run()); + protocol_config }; + let (chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); + let (chain_sync, chain_sync_service, block_announce_config) = ChainSync::new( + match config.network.sync_mode { + SyncMode::Full => sc_network_common::sync::SyncMode::Full, + SyncMode::Fast { skip_proofs, storage_chain_mode } => + sc_network_common::sync::SyncMode::LightState { skip_proofs, storage_chain_mode }, + SyncMode::Warp => sc_network_common::sync::SyncMode::Warp, + }, + client.clone(), + protocol_id.clone(), + &config.chain_spec.fork_id().map(ToOwned::to_owned), + Roles::from(&config.role), + block_announce_validator, + config.network.max_parallel_downloads, + warp_sync_provider, + config.prometheus_config.as_ref().map(|config| config.registry.clone()).as_ref(), + chain_sync_network_handle, + import_queue.service(), + block_request_protocol_config.name.clone(), + state_request_protocol_config.name.clone(), + warp_sync_protocol_config.as_ref().map(|config| config.name.clone()), + )?; + + request_response_protocol_configs.push(config.network.ipfs_server.then(|| { + let (handler, protocol_config) = BitswapRequestHandler::new(client.clone()); + spawn_handle.spawn("bitswap-request-handler", Some("networking"), handler.run()); + protocol_config + })); + let mut network_params = sc_network::config::Params { role: config.role.clone(), executor: { - let spawn_handle = Clone::clone(&spawn_handle); - Some(Box::new(move |fut| { - spawn_handle.spawn("libp2p-node", fut); - })) - }, - transactions_handler_executor: { let spawn_handle = Clone::clone(&spawn_handle); Box::new(move |fut| { - spawn_handle.spawn("network-transactions-handler", fut); + spawn_handle.spawn("libp2p-node", Some("networking"), fut); }) }, network_config: config.network.clone(), chain: client.clone(), - on_demand, - transaction_pool: transaction_pool_adapter as _, - import_queue: Box::new(import_queue), - protocol_id, - block_announce_validator, + protocol_id: protocol_id.clone(), + fork_id: config.chain_spec.fork_id().map(ToOwned::to_owned), + chain_sync: Box::new(chain_sync), + chain_sync_service: Box::new(chain_sync_service.clone()), metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()), - block_request_protocol_config, - state_request_protocol_config, - warp_sync: warp_sync_params, - light_client_request_protocol_config, + block_announce_config, + request_response_protocol_configs: request_response_protocol_configs + .into_iter() + .chain([ + Some(block_request_protocol_config), + Some(state_request_protocol_config), + Some(light_client_request_protocol_config), + warp_sync_protocol_config, + ]) + .flatten() + .collect::>(), }; - // Storage chains don't keep full block history and can't be synced in full mode. - // Force fast sync when storage chain mode is enabled. - if matches!(config.transaction_storage, TransactionStorageMode::StorageChain) { - network_params.network_config.sync_mode = - SyncMode::Fast { storage_chain_mode: true, skip_proofs: false }; - } + // crate transactions protocol and add it to the list of supported protocols of `network_params` + let transactions_handler_proto = sc_network_transactions::TransactionsHandlerPrototype::new( + protocol_id.clone(), + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + config.chain_spec.fork_id(), + ); + network_params + .network_config + .extra_sets + .insert(0, transactions_handler_proto.set_config()); let has_bootnodes = !network_params.network_config.boot_nodes.is_empty(); let network_mut = sc_network::NetworkWorker::new(network_params)?; let network = network_mut.service().clone(); + let (tx_handler, tx_handler_controller) = transactions_handler_proto.build( + network.clone(), + Arc::new(TransactionPoolAdapter { pool: transaction_pool, client: client.clone() }), + config.prometheus_config.as_ref().map(|config| &config.registry), + )?; + + spawn_handle.spawn("network-transactions-handler", Some("networking"), tx_handler.run()); + spawn_handle.spawn( + "chain-sync-network-service-provider", + Some("networking"), + chain_sync_network_provider.run(network.clone()), + ); + spawn_handle.spawn("import-queue", None, import_queue.run(Box::new(chain_sync_service))); + let (system_rpc_tx, system_rpc_rx) = tracing_unbounded("mpsc_system_rpc"); let future = build_network_future( @@ -1026,7 +962,7 @@ where // issue, and ideally we would like to fix the network future to take as little time as // possible, but we also take the extra harm-prevention measure to execute the networking // future using `spawn_blocking`. - spawn_handle.spawn_blocking("network-worker", async move { + spawn_handle.spawn_blocking("network-worker", Some("networking"), async move { if network_start_rx.await.is_err() { log::warn!( "The NetworkStart returned as part of `build_network` has been silently dropped" @@ -1039,7 +975,7 @@ where future.await }); - Ok((network, system_rpc_tx, NetworkStarter(network_start_tx))) + Ok((network, system_rpc_tx, tx_handler_controller, NetworkStarter(network_start_tx))) } /// Object used to start the network. @@ -1047,6 +983,11 @@ where pub struct NetworkStarter(oneshot::Sender<()>); impl NetworkStarter { + /// Create a new NetworkStarter + pub fn new(sender: oneshot::Sender<()>) -> Self { + NetworkStarter(sender) + } + /// Start the network. Call this after all sub-components have been initialized. /// /// > **Note**: If you don't call this function, the networking will not work. diff --git a/client/service/src/chain_ops/check_block.rs b/client/service/src/chain_ops/check_block.rs index 4728e014540ee..41a6c73c5f473 100644 --- a/client/service/src/chain_ops/check_block.rs +++ b/client/service/src/chain_ops/check_block.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,7 +19,7 @@ use crate::error::Error; use codec::Encode; use futures::{future, prelude::*}; -use sc_client_api::{BlockBackend, UsageProvider}; +use sc_client_api::{BlockBackend, HeaderBackend}; use sc_consensus::import_queue::ImportQueue; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; @@ -33,7 +33,7 @@ pub fn check_block( block_id: BlockId, ) -> Pin> + Send>> where - C: BlockBackend + UsageProvider + Send + Sync + 'static, + C: BlockBackend + HeaderBackend + Send + Sync + 'static, B: BlockT + for<'de> serde::Deserialize<'de>, IQ: ImportQueue + 'static, { @@ -46,6 +46,6 @@ where import_blocks(client, import_queue, reader, true, true) }, Ok(None) => Box::pin(future::err("Unknown block".into())), - Err(e) => Box::pin(future::err(format!("Error reading block: {:?}", e).into())), + Err(e) => Box::pin(future::err(format!("Error reading block: {}", e).into())), } } diff --git a/client/service/src/chain_ops/export_blocks.rs b/client/service/src/chain_ops/export_blocks.rs index 8887180103182..d442a11f2c39b 100644 --- a/client/service/src/chain_ops/export_blocks.rs +++ b/client/service/src/chain_ops/export_blocks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/service/src/chain_ops/export_raw_state.rs b/client/service/src/chain_ops/export_raw_state.rs index 975149c61cfab..ca7a070086f45 100644 --- a/client/service/src/chain_ops/export_raw_state.rs +++ b/client/service/src/chain_ops/export_raw_state.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,25 +19,20 @@ use crate::error::Error; use sc_client_api::{StorageProvider, UsageProvider}; use sp_core::storage::{well_known_keys, ChildInfo, Storage, StorageChild, StorageKey, StorageMap}; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_runtime::traits::Block as BlockT; use std::{collections::HashMap, sync::Arc}; /// Export the raw state at the given `block`. If `block` is `None`, the /// best block will be used. -pub fn export_raw_state( - client: Arc, - block: Option>, -) -> Result +pub fn export_raw_state(client: Arc, hash: B::Hash) -> Result where C: UsageProvider + StorageProvider, B: BlockT, BA: sc_client_api::backend::Backend, { - let block = block.unwrap_or_else(|| BlockId::Hash(client.usage_info().chain.best_hash)); - let empty_key = StorageKey(Vec::new()); - let mut top_storage = client.storage_pairs(&block, &empty_key)?; + let mut top_storage = client.storage_pairs(hash, &empty_key)?; let mut children_default = HashMap::new(); // Remove all default child storage roots from the top storage and collect the child storage @@ -52,10 +47,10 @@ where StorageKey(key.0[well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX.len()..].to_vec()); let child_info = ChildInfo::new_default(&key.0); - let keys = client.child_storage_keys(&block, &child_info, &empty_key)?; + let keys = client.child_storage_keys(hash, &child_info, &empty_key)?; let mut pairs = StorageMap::new(); keys.into_iter().try_for_each(|k| { - if let Some(value) = client.child_storage(&block, &child_info, &k)? { + if let Some(value) = client.child_storage(hash, &child_info, &k)? { pairs.insert(k.0, value.0); } diff --git a/client/service/src/chain_ops/import_blocks.rs b/client/service/src/chain_ops/import_blocks.rs index 1ba9e0bd61444..ca09c1658d72f 100644 --- a/client/service/src/chain_ops/import_blocks.rs +++ b/client/service/src/chain_ops/import_blocks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,7 +22,7 @@ use futures::{future, prelude::*}; use futures_timer::Delay; use log::{info, warn}; use sc_chain_spec::ChainSpec; -use sc_client_api::UsageProvider; +use sc_client_api::HeaderBackend; use sc_consensus::import_queue::{ BlockImportError, BlockImportStatus, ImportQueue, IncomingBlock, Link, }; @@ -35,18 +35,17 @@ use sp_runtime::{ }, }; use std::{ - convert::{TryFrom, TryInto}, - io::{Read, Seek}, + io::Read, pin::Pin, task::Poll, time::{Duration, Instant}, }; /// Number of blocks we will add to the queue before waiting for the queue to catch up. -const MAX_PENDING_BLOCKS: u64 = 1_024; +const MAX_PENDING_BLOCKS: u64 = 10_000; /// Number of milliseconds to wait until next poll. -const DELAY_TIME: u64 = 2_000; +const DELAY_TIME: u64 = 200; /// Number of milliseconds that must have passed between two updates. const TIME_BETWEEN_UPDATES: u64 = 3_000; @@ -63,7 +62,7 @@ pub fn build_spec(spec: &dyn ChainSpec, raw: bool) -> error::Result { /// SignedBlock and return it. enum BlockIter where - R: std::io::Read + std::io::Seek, + R: std::io::Read, { Binary { // Total number of blocks we are expecting to decode. @@ -83,7 +82,7 @@ where impl BlockIter where - R: Read + Seek + 'static, + R: Read + 'static, B: BlockT + MaybeSerializeDeserialize, { fn new(input: R, binary: bool) -> Result { @@ -119,7 +118,7 @@ where impl Iterator for BlockIter where - R: Read + Seek + 'static, + R: Read + 'static, B: BlockT + MaybeSerializeDeserialize, { type Item = Result, String>; @@ -158,7 +157,7 @@ fn import_block_to_queue( let (header, extrinsics) = signed_block.block.deconstruct(); let hash = header.hash(); // import queue handles verification and importing it into the client. - queue.import_blocks( + queue.service_ref().import_blocks( BlockOrigin::File, vec![IncomingBlock:: { hash, @@ -267,10 +266,11 @@ impl Speedometer { /// Different State that the `import_blocks` future could be in. enum ImportState where - R: Read + Seek + 'static, + R: Read + 'static, B: BlockT + MaybeSerializeDeserialize, { - /// We are reading from the BlockIter structure, adding those blocks to the queue if possible. + /// We are reading from the [`BlockIter`] structure, adding those blocks to the queue if + /// possible. Reading { block_iter: BlockIter }, /// The queue is full (contains at least MAX_PENDING_BLOCKS blocks) and we are waiting for it /// to catch up. @@ -291,12 +291,12 @@ where pub fn import_blocks( client: Arc, mut import_queue: IQ, - input: impl Read + Seek + Send + 'static, + input: impl Read + Send + 'static, force: bool, binary: bool, ) -> Pin> + Send>> where - C: UsageProvider + Send + Sync + 'static, + C: HeaderBackend + Send + Sync + 'static, B: BlockT + for<'de> serde::Deserialize<'de>, IQ: ImportQueue + 'static, { @@ -322,7 +322,7 @@ where for result in results { if let (Err(err), hash) = result { - warn!("There was an error importing block with hash {:?}: {:?}", hash, err); + warn!("There was an error importing block with hash {:?}: {}", hash, err); self.has_error = true; break } @@ -438,7 +438,7 @@ where info!( "🎉 Imported {} blocks. Best: #{}", read_block_count, - client.usage_info().chain.best_number + client.info().best_number ); return Poll::Ready(Ok(())) } else { @@ -469,7 +469,7 @@ where queue.poll_actions(cx, &mut link); - let best_number = client.usage_info().chain.best_number; + let best_number = client.info().best_number; speedometer.notify_user(best_number); if link.has_error { diff --git a/client/service/src/chain_ops/mod.rs b/client/service/src/chain_ops/mod.rs index c213e745a5d6b..98245eeb98d08 100644 --- a/client/service/src/chain_ops/mod.rs +++ b/client/service/src/chain_ops/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/service/src/chain_ops/revert_chain.rs b/client/service/src/chain_ops/revert_chain.rs index 63f1cbd15dd63..3ee4399d063b3 100644 --- a/client/service/src/chain_ops/revert_chain.rs +++ b/client/service/src/chain_ops/revert_chain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -40,6 +40,13 @@ where info!("There aren't any non-finalized blocks to revert."); } else { info!("Reverted {} blocks. Best: #{} ({})", reverted.0, info.best_number, info.best_hash); + + if reverted.0 > blocks { + info!( + "Number of reverted blocks is higher than requested \ + because of reverted leaves higher than the best block." + ) + } } Ok(()) } diff --git a/client/service/src/client/block_rules.rs b/client/service/src/client/block_rules.rs index 4bdf338362960..2ed27b8fe1b63 100644 --- a/client/service/src/client/block_rules.rs +++ b/client/service/src/client/block_rules.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -47,8 +47,8 @@ impl BlockRules { /// New block rules with provided black and white lists. pub fn new(fork_blocks: ForkBlocks, bad_blocks: BadBlocks) -> Self { Self { - bad: bad_blocks.unwrap_or_else(|| HashSet::new()), - forks: fork_blocks.unwrap_or_else(|| vec![]).into_iter().collect(), + bad: bad_blocks.unwrap_or_default(), + forks: fork_blocks.unwrap_or_default().into_iter().collect(), } } @@ -61,7 +61,7 @@ impl BlockRules { pub fn lookup(&self, number: NumberFor, hash: &B::Hash) -> LookupResult { if let Some(hash_for_height) = self.forks.get(&number) { if hash_for_height != hash { - return LookupResult::Expected(hash_for_height.clone()) + return LookupResult::Expected(*hash_for_height) } } diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index 9b8774ce6d497..fcece49b5f228 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,34 +17,28 @@ // along with this program. If not, see . use super::{client::ClientConfig, wasm_override::WasmOverride, wasm_substitutes::WasmSubstitutes}; -use codec::{Decode, Encode}; -use sc_client_api::{backend, call_executor::CallExecutor, HeaderBackend}; -use sc_executor::{RuntimeVersion, RuntimeVersionOf}; -use sp_api::{ProofRecorder, StorageTransactionCache}; -use sp_core::{ - traits::{CodeExecutor, RuntimeCode, SpawnNamed}, - NativeOrEncoded, NeverNativeValue, -}; -use sp_externalities::Extensions; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, NumberFor}, +use sc_client_api::{ + backend, call_executor::CallExecutor, execution_extensions::ExecutionExtensions, HeaderBackend, }; +use sc_executor::{RuntimeVersion, RuntimeVersionOf}; +use sp_api::{ExecutionContext, ProofRecorder, StorageTransactionCache}; +use sp_core::traits::{CodeExecutor, RuntimeCode, SpawnNamed}; +use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use sp_state_machine::{ - self, backend::Backend as _, ExecutionManager, ExecutionStrategy, Ext, OverlayedChanges, - StateMachine, StorageProof, + backend::AsTrieBackend, ExecutionStrategy, Ext, OverlayedChanges, StateMachine, StorageProof, }; -use std::{cell::RefCell, panic::UnwindSafe, result, sync::Arc}; +use std::{cell::RefCell, sync::Arc}; /// Call executor that executes methods locally, querying all required /// data from local backend. pub struct LocalCallExecutor { backend: Arc, executor: E, - wasm_override: Option>, + wasm_override: Arc>, wasm_substitutes: WasmSubstitutes, spawn_handle: Box, client_config: ClientConfig, + execution_extensions: Arc>, } impl LocalCallExecutor @@ -58,11 +52,12 @@ where executor: E, spawn_handle: Box, client_config: ClientConfig, + execution_extensions: ExecutionExtensions, ) -> sp_blockchain::Result { let wasm_override = client_config .wasm_runtime_overrides .as_ref() - .map(|p| WasmOverride::new(p.clone(), executor.clone())) + .map(|p| WasmOverride::new(p.clone(), &executor)) .transpose()?; let wasm_substitutes = WasmSubstitutes::new( @@ -74,10 +69,11 @@ where Ok(LocalCallExecutor { backend, executor, - wasm_override, + wasm_override: Arc::new(wasm_override), spawn_handle, client_config, wasm_substitutes, + execution_extensions: Arc::new(execution_extensions), }) } @@ -93,26 +89,27 @@ where Block: BlockT, B: backend::Backend, { - let spec = self.runtime_version(id)?.spec_version; - let code = if let Some(d) = self - .wasm_override - .as_ref() - .map(|o| o.get(&spec, onchain_code.heap_pages)) - .flatten() - { - log::debug!(target: "wasm_overrides", "using WASM override for block {}", id); - d - } else if let Some(s) = self.wasm_substitutes.get(spec, onchain_code.heap_pages, id) { - log::debug!(target: "wasm_substitutes", "Using WASM substitute for block {:?}", id); - s - } else { - log::debug!( - target: "wasm_overrides", - "No WASM override available for block {}, using onchain code", - id - ); - onchain_code - }; + let spec = CallExecutor::runtime_version(self, id)?; + let code = + if let Some(d) = + self.wasm_override.as_ref().as_ref().and_then(|o| { + o.get(&spec.spec_version, onchain_code.heap_pages, &spec.spec_name) + }) { + log::debug!(target: "wasm_overrides", "using WASM override for block {}", id); + d + } else if let Some(s) = + self.wasm_substitutes.get(spec.spec_version, onchain_code.heap_pages, id) + { + log::debug!(target: "wasm_substitutes", "Using WASM substitute for block {:?}", id); + s + } else { + log::debug!( + target: "wasm_overrides", + "No WASM override available for block {}, using onchain code", + id + ); + onchain_code + }; Ok(code) } @@ -130,6 +127,7 @@ where spawn_handle: self.spawn_handle.clone(), client_config: self.client_config.clone(), wasm_substitutes: self.wasm_substitutes.clone(), + execution_extensions: self.execution_extensions.clone(), } } } @@ -144,81 +142,70 @@ where type Backend = B; + fn execution_extensions(&self) -> &ExecutionExtensions { + &self.execution_extensions + } + fn call( &self, at: &BlockId, method: &str, call_data: &[u8], strategy: ExecutionStrategy, - extensions: Option, ) -> sp_blockchain::Result> { let mut changes = OverlayedChanges::default(); - let changes_trie = - backend::changes_tries_state_at_block(at, self.backend.changes_trie_storage())?; - let state = self.backend.state_at(*at)?; + let at_hash = self.backend.blockchain().expect_block_hash_from_id(at)?; + let at_number = self.backend.blockchain().expect_block_number_from_id(at)?; + let state = self.backend.state_at(at_hash)?; + let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&state); let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; let runtime_code = self.check_override(runtime_code, at)?; - let at_hash = self.backend.blockchain().block_hash_from_id(at)?.ok_or_else(|| { - sp_blockchain::Error::UnknownBlock(format!("Could not find block hash for {:?}", at)) - })?; + let extensions = self.execution_extensions.extensions( + at_hash, + at_number, + ExecutionContext::OffchainCall(None), + ); - let return_data = StateMachine::new( + let mut sm = StateMachine::new( &state, - changes_trie, &mut changes, &self.executor, method, call_data, - extensions.unwrap_or_default(), + extensions, &runtime_code, self.spawn_handle.clone(), ) - .set_parent_hash(at_hash) - .execute_using_consensus_failure_handler::<_, NeverNativeValue, fn() -> _>( - strategy.get_manager(), - None, - )?; + .set_parent_hash(at_hash); - Ok(return_data.into_encoded()) + sm.execute_using_consensus_failure_handler(strategy.get_manager()) + .map_err(Into::into) } - fn contextual_call< - EM: Fn( - Result, Self::Error>, - Result, Self::Error>, - ) -> Result, Self::Error>, - R: Encode + Decode + PartialEq, - NC: FnOnce() -> result::Result + UnwindSafe, - >( + fn contextual_call( &self, at: &BlockId, method: &str, call_data: &[u8], changes: &RefCell, storage_transaction_cache: Option<&RefCell>>, - execution_manager: ExecutionManager, - native_call: Option, recorder: &Option>, - extensions: Option, - ) -> Result, sp_blockchain::Error> - where - ExecutionManager: Clone, - { - let changes_trie_state = - backend::changes_tries_state_at_block(at, self.backend.changes_trie_storage())?; + context: ExecutionContext, + ) -> Result, sp_blockchain::Error> { let mut storage_transaction_cache = storage_transaction_cache.map(|c| c.borrow_mut()); - let state = self.backend.state_at(*at)?; + let at_hash = self.backend.blockchain().expect_block_hash_from_id(at)?; + let at_number = self.backend.blockchain().expect_block_number_from_id(at)?; + let state = self.backend.state_at(at_hash)?; - let changes = &mut *changes.borrow_mut(); + let (execution_manager, extensions) = + self.execution_extensions.manager_and_extensions(at_hash, at_number, context); - let at_hash = self.backend.blockchain().block_hash_from_id(at)?.ok_or_else(|| { - sp_blockchain::Error::UnknownBlock(format!("Could not find block hash for {:?}", at)) - })?; + let changes = &mut *changes.borrow_mut(); // It is important to extract the runtime code here before we create the proof // recorder to not record it. We also need to fetch the runtime code from `state` to @@ -231,54 +218,40 @@ where match recorder { Some(recorder) => { - let trie_state = state.as_trie_backend().ok_or_else(|| { - Box::new(sp_state_machine::ExecutionError::UnableToGenerateProof) - as Box - })?; - - let backend = sp_state_machine::ProvingBackend::new_with_recorder( - trie_state, - recorder.clone(), - ); + let trie_state = state.as_trie_backend(); + + let backend = sp_state_machine::TrieBackendBuilder::wrap(&trie_state) + .with_recorder(recorder.clone()) + .build(); let mut state_machine = StateMachine::new( &backend, - changes_trie_state, changes, &self.executor, method, call_data, - extensions.unwrap_or_default(), + extensions, &runtime_code, self.spawn_handle.clone(), ) + .with_storage_transaction_cache(storage_transaction_cache.as_deref_mut()) .set_parent_hash(at_hash); - // TODO: https://github.com/paritytech/substrate/issues/4455 - state_machine.execute_using_consensus_failure_handler( - execution_manager, - native_call.map(|n| || (n)().map_err(|e| Box::new(e) as Box<_>)), - ) + state_machine.execute_using_consensus_failure_handler(execution_manager) }, None => { let mut state_machine = StateMachine::new( &state, - changes_trie_state, changes, &self.executor, method, call_data, - extensions.unwrap_or_default(), + extensions, &runtime_code, self.spawn_handle.clone(), ) - .with_storage_transaction_cache( - storage_transaction_cache.as_mut().map(|c| &mut **c), - ) + .with_storage_transaction_cache(storage_transaction_cache.as_deref_mut()) .set_parent_hash(at_hash); - state_machine.execute_using_consensus_failure_handler( - execution_manager, - native_call.map(|n| || (n)().map_err(|e| Box::new(e) as Box<_>)), - ) + state_machine.execute_using_consensus_failure_handler(execution_manager) }, } .map_err(Into::into) @@ -286,17 +259,17 @@ where fn runtime_version(&self, id: &BlockId) -> sp_blockchain::Result { let mut overlay = OverlayedChanges::default(); - let changes_trie_state = - backend::changes_tries_state_at_block(id, self.backend.changes_trie_storage())?; - let state = self.backend.state_at(*id)?; + + let at_hash = self.backend.blockchain().expect_block_hash_from_id(id)?; + let state = self.backend.state_at(at_hash)?; let mut cache = StorageTransactionCache::::default(); - let mut ext = Ext::new(&mut overlay, &mut cache, &state, changes_trie_state, None); + let mut ext = Ext::new(&mut overlay, &mut cache, &state, None); let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&state); let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; self.executor .runtime_version(&mut ext, &runtime_code) - .map_err(|e| sp_blockchain::Error::VersionInvalid(format!("{:?}", e)).into()) + .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string())) } fn prove_execution( @@ -305,31 +278,49 @@ where method: &str, call_data: &[u8], ) -> sp_blockchain::Result<(Vec, StorageProof)> { - let state = self.backend.state_at(*at)?; + let at_hash = self.backend.blockchain().expect_block_hash_from_id(at)?; + let at_number = self.backend.blockchain().expect_block_number_from_id(at)?; + let state = self.backend.state_at(at_hash)?; - let trie_backend = state.as_trie_backend().ok_or_else(|| { - Box::new(sp_state_machine::ExecutionError::UnableToGenerateProof) - as Box - })?; + let trie_backend = state.as_trie_backend(); let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend); let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; let runtime_code = self.check_override(runtime_code, at)?; - sp_state_machine::prove_execution_on_trie_backend::<_, _, NumberFor, _, _>( - &trie_backend, + sp_state_machine::prove_execution_on_trie_backend( + trie_backend, &mut Default::default(), &self.executor, self.spawn_handle.clone(), method, call_data, &runtime_code, + self.execution_extensions.extensions( + at_hash, + at_number, + ExecutionContext::OffchainCall(None), + ), ) .map_err(Into::into) } } +impl RuntimeVersionOf for LocalCallExecutor +where + E: RuntimeVersionOf, + Block: BlockT, +{ + fn runtime_version( + &self, + ext: &mut dyn sp_externalities::Externalities, + runtime_code: &sp_core::traits::RuntimeCode, + ) -> Result { + RuntimeVersionOf::runtime_version(&self.executor, ext, runtime_code) + } +} + impl sp_version::GetRuntimeVersionAt for LocalCallExecutor where B: backend::Backend, @@ -337,7 +328,7 @@ where Block: BlockT, { fn runtime_version(&self, at: &BlockId) -> Result { - CallExecutor::runtime_version(self, at).map_err(|e| format!("{:?}", e)) + CallExecutor::runtime_version(self, at).map_err(|e| e.to_string()) } } @@ -369,9 +360,10 @@ mod tests { WasmExecutionMethod::Interpreted, Some(128), 1, + 2, ); - let overrides = crate::client::wasm_override::dummy_overrides(&executor); + let overrides = crate::client::wasm_override::dummy_overrides(); let onchain_code = WrappedRuntimeCode(substrate_test_runtime::wasm_binary_unwrap().into()); let onchain_code = RuntimeCode { code_fetcher: &onchain_code, @@ -407,7 +399,7 @@ mod tests { let call_executor = LocalCallExecutor { backend: backend.clone(), executor: executor.clone(), - wasm_override: Some(overrides), + wasm_override: Arc::new(Some(overrides)), spawn_handle: Box::new(TaskExecutor::new()), client_config, wasm_substitutes: WasmSubstitutes::new( @@ -416,6 +408,11 @@ mod tests { backend.clone(), ) .unwrap(), + execution_extensions: Arc::new(ExecutionExtensions::new( + Default::default(), + None, + None, + )), }; let check = call_executor diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index f7d93d036a3fa..8ded5ec95c166 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,8 +22,6 @@ use super::{ block_rules::{BlockRules, LookupResult as BlockLookupResult}, genesis, }; -use codec::{Decode, Encode}; -use hash_db::Prefix; use log::{info, trace, warn}; use parking_lot::{Mutex, RwLock}; use prometheus_endpoint::Registry; @@ -31,64 +29,60 @@ use rand::Rng; use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider, RecordProof}; use sc_client_api::{ backend::{ - self, apply_aux, changes_tries_state_at_block, BlockImportOperation, ClientImportOperation, - Finalizer, ImportSummary, LockImportRun, NewBlockState, PrunableStateChangesTrieStorage, - StorageProvider, + self, apply_aux, BlockImportOperation, ClientImportOperation, FinalizeSummary, Finalizer, + ImportSummary, LockImportRun, NewBlockState, StorageProvider, }, - cht, client::{ BadBlocks, BlockBackend, BlockImportNotification, BlockOf, BlockchainEvents, ClientInfo, FinalityNotification, FinalityNotifications, ForkBlocks, ImportNotifications, - ProvideUncles, + PreCommitActions, ProvideUncles, }, execution_extensions::ExecutionExtensions, notifications::{StorageEventStream, StorageNotifications}, - CallExecutor, ExecutorProvider, KeyIterator, ProofProvider, UsageProvider, + CallExecutor, ExecutorProvider, KeyIterator, OnFinalityAction, OnImportAction, ProofProvider, + UsageProvider, }; use sc_consensus::{ BlockCheckParams, BlockImportParams, ForkChoiceStrategy, ImportResult, StateAction, }; -use sc_executor::RuntimeVersion; -use sc_light::fetcher::ChangesProof; +use sc_executor::{RuntimeVersion, RuntimeVersionOf}; use sc_telemetry::{telemetry, TelemetryHandle, SUBSTRATE_INFO}; use sp_api::{ ApiExt, ApiRef, CallApiAt, CallApiAtParams, ConstructRuntimeApi, Core as CoreApi, ProvideRuntimeApi, }; use sp_blockchain::{ - self as blockchain, well_known_cache_keys::Id as CacheKeyId, Backend as ChainBackend, Cache, - CachedHeaderMetadata, Error, HeaderBackend as ChainHeaderBackend, HeaderMetadata, ProvideCache, + self as blockchain, well_known_cache_keys::Id as CacheKeyId, Backend as ChainBackend, + CachedHeaderMetadata, Error, HeaderBackend as ChainHeaderBackend, HeaderMetadata, }; use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; -use sp_core::{ - convert_hash, - storage::{well_known_keys, ChildInfo, PrefixedStorageKey, StorageData, StorageKey}, - ChangesTrieConfiguration, NativeOrEncoded, +use sp_core::storage::{ + well_known_keys, ChildInfo, ChildType, PrefixedStorageKey, Storage, StorageChild, StorageData, + StorageKey, }; #[cfg(feature = "test-helpers")] use sp_keystore::SyncCryptoStorePtr; use sp_runtime::{ - generic::{BlockId, DigestItem, SignedBlock}, + generic::{BlockId, SignedBlock}, traits::{ - Block as BlockT, DigestFor, HashFor, Header as HeaderT, NumberFor, One, + Block as BlockT, BlockIdTo, HashFor, Header as HeaderT, NumberFor, One, SaturatedConversion, Zero, }, - BuildStorage, Justification, Justifications, + BuildStorage, Digest, Justification, Justifications, StateVersion, }; use sp_state_machine::{ - key_changes, key_changes_proof, prove_child_read, prove_range_read_with_size, prove_read, - read_range_proof_check, Backend as StateBackend, ChangesTrieAnchorBlockId, - ChangesTrieConfigurationRange, ChangesTrieRootsStorage, ChangesTrieStorage, DBValue, + prove_child_read, prove_range_read_with_child_with_size, prove_read, + read_range_proof_check_with_child_on_proving_backend, Backend as StateBackend, + ChildStorageCollection, KeyValueStates, KeyValueStorageLevel, StorageCollection, + MAX_NESTED_TRIE_DEPTH, }; -use sp_trie::StorageProof; +use sp_trie::{CompactProof, StorageProof}; use std::{ - collections::{BTreeMap, HashMap, HashSet}, + collections::{hash_map::DefaultHasher, HashMap, HashSet}, marker::PhantomData, - panic::UnwindSafe, path::PathBuf, - result, sync::Arc, }; @@ -96,7 +90,6 @@ use std::{ use { super::call_executor::LocalCallExecutor, sc_client_api::in_mem, - sc_executor::RuntimeVersionOf, sp_core::traits::{CodeExecutor, SpawnNamed}, }; @@ -109,13 +102,18 @@ where { backend: Arc, executor: E, - storage_notifications: Mutex>, + storage_notifications: StorageNotifications, import_notification_sinks: NotificationSinks>, finality_notification_sinks: NotificationSinks>, - // holds the block hash currently being imported. TODO: replace this with block queue + // Collects auxiliary operations to be performed atomically together with + // block import operations. + import_actions: Mutex>>, + // Collects auxiliary operations to be performed atomically together with + // block finalization operations. + finality_actions: Mutex>>, + // Holds the block hash currently being imported. TODO: replace this with block queue. importing_block: RwLock>, block_rules: BlockRules, - execution_extensions: ExecutionExtensions, config: ClientConfig, telemetry: Option, _phantom: PhantomData, @@ -198,7 +196,7 @@ pub struct ClientConfig { pub no_genesis: bool, /// Map of WASM runtime substitute starting at the child of the given block until the runtime /// version doesn't match anymore. - pub wasm_runtime_substitutes: HashMap>, + pub wasm_runtime_substitutes: HashMap, Vec>, } impl Default for ClientConfig { @@ -232,20 +230,26 @@ where Block: BlockT, B: backend::LocalBackend + 'static, { - let call_executor = - LocalCallExecutor::new(backend.clone(), executor, spawn_handle, config.clone())?; let extensions = ExecutionExtensions::new( Default::default(), keystore, sc_offchain::OffchainDb::factory_from_backend(&*backend), ); + + let call_executor = LocalCallExecutor::new( + backend.clone(), + executor, + spawn_handle, + config.clone(), + extensions, + )?; + Client::new( backend, call_executor, build_genesis_storage, Default::default(), Default::default(), - extensions, prometheus_registry, telemetry, config, @@ -278,16 +282,37 @@ where let mut op = ClientImportOperation { op: self.backend.begin_operation()?, notify_imported: None, - notify_finalized: Vec::new(), + notify_finalized: None, }; let r = f(&mut op)?; - let ClientImportOperation { op, notify_imported, notify_finalized } = op; + let ClientImportOperation { mut op, notify_imported, notify_finalized } = op; + + let finality_notification = notify_finalized.map(|summary| summary.into()); + let (import_notification, storage_changes) = match notify_imported { + Some(mut summary) => { + let storage_changes = summary.storage_changes.take(); + (Some(summary.into()), storage_changes) + }, + None => (None, None), + }; + + if let Some(ref notification) = finality_notification { + for action in self.finality_actions.lock().iter_mut() { + op.insert_aux(action(notification))?; + } + } + if let Some(ref notification) = import_notification { + for action in self.import_actions.lock().iter_mut() { + op.insert_aux(action(notification))?; + } + } + self.backend.commit_operation(op)?; - self.notify_finalized(notify_finalized)?; - self.notify_imported(notify_imported)?; + self.notify_finalized(finality_notification)?; + self.notify_imported(import_notification, storage_changes)?; Ok(r) }; @@ -328,7 +353,6 @@ where build_genesis_storage: &dyn BuildStorage, fork_blocks: ForkBlocks, bad_blocks: BadBlocks, - execution_extensions: ExecutionExtensions, prometheus_registry: Option, telemetry: Option, config: ClientConfig, @@ -337,9 +361,12 @@ where if info.finalized_state.is_none() { let genesis_storage = build_genesis_storage.build_storage().map_err(sp_blockchain::Error::Storage)?; + let genesis_state_version = + Self::resolve_state_version_from_wasm(&genesis_storage, &executor)?; let mut op = backend.begin_operation()?; - let state_root = op.set_genesis_state(genesis_storage, !config.no_genesis)?; - let genesis_block = genesis::construct_genesis_block::(state_root.into()); + let state_root = + op.set_genesis_state(genesis_storage, !config.no_genesis, genesis_state_version)?; + let genesis_block = genesis::construct_genesis_block::(state_root); info!( "🔨 Initializing Genesis block/state (state: {}, header-hash: {})", genesis_block.header().state_root(), @@ -365,12 +392,13 @@ where Ok(Client { backend, executor, - storage_notifications: Mutex::new(StorageNotifications::new(prometheus_registry)), + storage_notifications: StorageNotifications::new(prometheus_registry), import_notification_sinks: Default::default(), finality_notification_sinks: Default::default(), + import_actions: Default::default(), + finality_actions: Default::default(), importing_block: Default::default(), block_rules: BlockRules::new(fork_blocks, bad_blocks), - execution_extensions, config, telemetry, _phantom: Default::default(), @@ -390,13 +418,14 @@ where } /// Get a reference to the state at a given block. - pub fn state_at(&self, block: &BlockId) -> sp_blockchain::Result { - self.backend.state_at(*block) + pub fn state_at(&self, hash: Block::Hash) -> sp_blockchain::Result { + self.backend.state_at(hash) } /// Get the code at a given block. pub fn code_at(&self, id: &BlockId) -> sp_blockchain::Result> { - Ok(StorageProvider::storage(self, id, &StorageKey(well_known_keys::CODE.to_vec()))? + let hash = self.backend.blockchain().expect_block_hash_from_id(id)?; + Ok(StorageProvider::storage(self, hash, &StorageKey(well_known_keys::CODE.to_vec()))? .expect( "None is returned if there's no value stored for the given key;\ ':code' key is always defined; qed", @@ -406,251 +435,7 @@ where /// Get the RuntimeVersion at a given block. pub fn runtime_version_at(&self, id: &BlockId) -> sp_blockchain::Result { - self.executor.runtime_version(id) - } - - /// Reads given header and generates CHT-based header proof for CHT of given size. - pub fn header_proof_with_cht_size( - &self, - id: &BlockId, - cht_size: NumberFor, - ) -> sp_blockchain::Result<(Block::Header, StorageProof)> { - let proof_error = || { - sp_blockchain::Error::Backend(format!("Failed to generate header proof for {:?}", id)) - }; - let header = self.backend.blockchain().expect_header(*id)?; - let block_num = *header.number(); - let cht_num = cht::block_to_cht_number(cht_size, block_num).ok_or_else(proof_error)?; - let cht_start = cht::start_number(cht_size, cht_num); - let mut current_num = cht_start; - let cht_range = ::std::iter::from_fn(|| { - let old_current_num = current_num; - current_num = current_num + One::one(); - Some(old_current_num) - }); - let headers = cht_range.map(|num| self.block_hash(num)); - let proof = cht::build_proof::, _, _>( - cht_size, - cht_num, - std::iter::once(block_num), - headers, - )?; - Ok((header, proof)) - } - - /// Does the same work as `key_changes_proof`, but assumes that CHTs are of passed size. - pub fn key_changes_proof_with_cht_size( - &self, - first: Block::Hash, - last: Block::Hash, - min: Block::Hash, - max: Block::Hash, - storage_key: Option<&PrefixedStorageKey>, - key: &StorageKey, - cht_size: NumberFor, - ) -> sp_blockchain::Result> { - struct AccessedRootsRecorder<'a, Block: BlockT> { - storage: &'a dyn ChangesTrieStorage, NumberFor>, - min: NumberFor, - required_roots_proofs: Mutex, Block::Hash>>, - } - - impl<'a, Block: BlockT> ChangesTrieRootsStorage, NumberFor> - for AccessedRootsRecorder<'a, Block> - { - fn build_anchor( - &self, - hash: Block::Hash, - ) -> Result>, String> { - self.storage.build_anchor(hash) - } - - fn root( - &self, - anchor: &ChangesTrieAnchorBlockId>, - block: NumberFor, - ) -> Result, String> { - let root = self.storage.root(anchor, block)?; - if block < self.min { - if let Some(ref root) = root { - self.required_roots_proofs.lock().insert(block, root.clone()); - } - } - Ok(root) - } - } - - impl<'a, Block: BlockT> ChangesTrieStorage, NumberFor> - for AccessedRootsRecorder<'a, Block> - { - fn as_roots_storage( - &self, - ) -> &dyn sp_state_machine::ChangesTrieRootsStorage, NumberFor> { - self - } - - fn with_cached_changed_keys( - &self, - root: &Block::Hash, - functor: &mut dyn FnMut(&HashMap, HashSet>>), - ) -> bool { - self.storage.with_cached_changed_keys(root, functor) - } - - fn get(&self, key: &Block::Hash, prefix: Prefix) -> Result, String> { - self.storage.get(key, prefix) - } - } - - let first_number = - self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(first))?; - let (storage, configs) = self.require_changes_trie(first_number, last, true)?; - let min_number = - self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(min))?; - - let recording_storage = AccessedRootsRecorder:: { - storage: storage.storage(), - min: min_number, - required_roots_proofs: Mutex::new(BTreeMap::new()), - }; - - let max_number = std::cmp::min( - self.backend.blockchain().info().best_number, - self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(max))?, - ); - - // fetch key changes proof - let mut proof = Vec::new(); - for (config_zero, config_end, config) in configs { - let last_number = - self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(last))?; - let config_range = ChangesTrieConfigurationRange { - config: &config, - zero: config_zero, - end: config_end.map(|(config_end_number, _)| config_end_number), - }; - let proof_range = key_changes_proof::, _>( - config_range, - &recording_storage, - first_number, - &ChangesTrieAnchorBlockId { hash: convert_hash(&last), number: last_number }, - max_number, - storage_key, - &key.0, - ) - .map_err(|err| sp_blockchain::Error::ChangesTrieAccessFailed(err))?; - proof.extend(proof_range); - } - - // now gather proofs for all changes tries roots that were touched during key_changes_proof - // execution AND are unknown (i.e. replaced with CHT) to the requester - let roots = recording_storage.required_roots_proofs.into_inner(); - let roots_proof = self.changes_trie_roots_proof(cht_size, roots.keys().cloned())?; - - Ok(ChangesProof { - max_block: max_number, - proof, - roots: roots.into_iter().map(|(n, h)| (n, convert_hash(&h))).collect(), - roots_proof, - }) - } - - /// Generate CHT-based proof for roots of changes tries at given blocks. - fn changes_trie_roots_proof>>( - &self, - cht_size: NumberFor, - blocks: I, - ) -> sp_blockchain::Result { - // most probably we have touched several changes tries that are parts of the single CHT - // => GroupBy changes tries by CHT number and then gather proof for the whole group at once - let mut proofs = Vec::new(); - - cht::for_each_cht_group::( - cht_size, - blocks, - |_, cht_num, cht_blocks| { - let cht_proof = - self.changes_trie_roots_proof_at_cht(cht_size, cht_num, cht_blocks)?; - proofs.push(cht_proof); - Ok(()) - }, - (), - )?; - - Ok(StorageProof::merge(proofs)) - } - - /// Generates CHT-based proof for roots of changes tries at given blocks - /// (that are part of single CHT). - fn changes_trie_roots_proof_at_cht( - &self, - cht_size: NumberFor, - cht_num: NumberFor, - blocks: Vec>, - ) -> sp_blockchain::Result { - let cht_start = cht::start_number(cht_size, cht_num); - let mut current_num = cht_start; - let cht_range = ::std::iter::from_fn(|| { - let old_current_num = current_num; - current_num = current_num + One::one(); - Some(old_current_num) - }); - let roots = cht_range.map(|num| { - self.header(&BlockId::Number(num)).map(|block| { - block - .and_then(|block| block.digest().log(DigestItem::as_changes_trie_root).cloned()) - }) - }); - let proof = cht::build_proof::, _, _>( - cht_size, cht_num, blocks, roots, - )?; - Ok(proof) - } - - /// Returns changes trie storage and all configurations that have been active - /// in the range [first; last]. - /// - /// Configurations are returned in descending order (and obviously never overlap). - /// If fail_if_disabled is false, returns maximal consequent configurations ranges, - /// starting from last and stopping on either first, or when CT have been disabled. - /// If fail_if_disabled is true, fails when there's a subrange where CT have been disabled - /// inside first..last blocks range. - fn require_changes_trie( - &self, - first: NumberFor, - last: Block::Hash, - fail_if_disabled: bool, - ) -> sp_blockchain::Result<( - &dyn PrunableStateChangesTrieStorage, - Vec<(NumberFor, Option<(NumberFor, Block::Hash)>, ChangesTrieConfiguration)>, - )> { - let storage = self - .backend - .changes_trie_storage() - .ok_or_else(|| sp_blockchain::Error::ChangesTriesNotSupported)?; - - let mut configs = Vec::with_capacity(1); - let mut current = last; - loop { - let config_range = storage.configuration_at(&BlockId::Hash(current))?; - match config_range.config { - Some(config) => configs.push((config_range.zero.0, config_range.end, config)), - None if !fail_if_disabled => return Ok((storage, configs)), - None => return Err(sp_blockchain::Error::ChangesTriesNotSupported), - } - - if config_range.zero.0 < first { - break - } - - current = *self - .backend - .blockchain() - .expect_header(BlockId::Hash(config_range.zero.1))? - .parent_hash(); - } - - Ok((storage, configs)) + CallExecutor::runtime_version(&self.executor, id) } /// Apply a checked and validated block to an operation. If a justification is provided @@ -684,8 +469,6 @@ where .. } = import_block; - assert!(justifications.is_some() && finalized || justifications.is_none()); - if !intermediates.is_empty() { return Err(Error::IncompletePipeline) } @@ -767,7 +550,7 @@ where >::Api: CoreApi + ApiExt, { - let parent_hash = import_headers.post().parent_hash().clone(); + let parent_hash = *import_headers.post().parent_hash(); let status = self.backend.blockchain().status(BlockId::Hash(hash))?; let parent_exists = self.backend.blockchain().status(BlockId::Hash(parent_hash))? == blockchain::BlockStatus::InChain; @@ -779,11 +562,17 @@ where } let info = self.backend.blockchain().info(); + let gap_block = info + .block_gap + .map_or(false, |(start, _)| *import_headers.post().number() == start); + + assert!(justifications.is_some() && finalized || justifications.is_none() || gap_block); // the block is lower than our last finalized block so it must revert // finality, refusing import. if status == blockchain::BlockStatus::Unknown && - *import_headers.post().number() <= info.finalized_number + *import_headers.post().number() <= info.finalized_number && + !gap_block { return Err(sp_blockchain::Error::NotInFinalizedChain) } @@ -801,9 +590,8 @@ where Some(storage_changes) => { let storage_changes = match storage_changes { sc_consensus::StorageChanges::Changes(storage_changes) => { - self.backend - .begin_state_operation(&mut operation.op, BlockId::Hash(parent_hash))?; - let (main_sc, child_sc, offchain_sc, tx, _, changes_trie_tx, tx_index) = + self.backend.begin_state_operation(&mut operation.op, parent_hash)?; + let (main_sc, child_sc, offchain_sc, tx, _, tx_index) = storage_changes.into_inner(); if self.config.offchain_indexing_api { @@ -814,39 +602,55 @@ where operation.op.update_storage(main_sc.clone(), child_sc.clone())?; operation.op.update_transaction_index(tx_index)?; - if let Some(changes_trie_transaction) = changes_trie_tx { - operation.op.update_changes_trie(changes_trie_transaction)?; - } Some((main_sc, child_sc)) }, sc_consensus::StorageChanges::Import(changes) => { - let storage = sp_storage::Storage { - top: changes.state.into_iter().collect(), - children_default: Default::default(), - }; + let mut storage = sp_storage::Storage::default(); + for state in changes.state.0.into_iter() { + if state.parent_storage_keys.is_empty() && state.state_root.is_empty() { + for (key, value) in state.key_values.into_iter() { + storage.top.insert(key, value); + } + } else { + for parent_storage in state.parent_storage_keys { + let storage_key = PrefixedStorageKey::new_ref(&parent_storage); + let storage_key = + match ChildType::from_prefixed_key(storage_key) { + Some((ChildType::ParentKeyId, storage_key)) => + storage_key, + None => + return Err(Error::Backend( + "Invalid child storage key.".to_string(), + )), + }; + let entry = storage + .children_default + .entry(storage_key.to_vec()) + .or_insert_with(|| StorageChild { + data: Default::default(), + child_info: ChildInfo::new_default(storage_key), + }); + for (key, value) in state.key_values.iter() { + entry.data.insert(key.clone(), value.clone()); + } + } + } + } - let state_root = operation.op.reset_storage(storage)?; + // This is use by fast sync for runtime version to be resolvable from + // changes. + let state_version = + Self::resolve_state_version_from_wasm(&storage, &self.executor)?; + let state_root = operation.op.reset_storage(storage, state_version)?; if state_root != *import_headers.post().state_root() { // State root mismatch when importing state. This should not happen in // safe fast sync mode, but may happen in unsafe mode. - warn!("Error imporing state: State root mismatch."); + warn!("Error importing state: State root mismatch."); return Err(Error::InvalidStateRoot) } None }, }; - // Ensure parent chain is finalized to maintain invariant that - // finality is called sequentially. This will also send finality - // notifications for top 250 newly finalized blocks. - if finalized && parent_exists { - self.apply_finality_with_block_hash( - operation, - parent_hash, - None, - info.best_hash, - make_notifications, - )?; - } operation.op.update_cache(new_cache); storage_changes @@ -854,12 +658,25 @@ where None => None, }; - let is_new_best = finalized || - match fork_choice { - ForkChoiceStrategy::LongestChain => - import_headers.post().number() > &info.best_number, - ForkChoiceStrategy::Custom(v) => v, - }; + // Ensure parent chain is finalized to maintain invariant that finality is called + // sequentially. + if finalized && parent_exists && info.finalized_hash != parent_hash { + self.apply_finality_with_block_hash( + operation, + parent_hash, + None, + info.best_hash, + make_notifications, + )?; + } + + let is_new_best = !gap_block && + (finalized || + match fork_choice { + ForkChoiceStrategy::LongestChain => + import_headers.post().number() > &info.best_number, + ForkChoiceStrategy::Custom(v) => v, + }); let leaf_state = if finalized { NewBlockState::Final @@ -895,17 +712,51 @@ where operation.op.insert_aux(aux)?; - // we only notify when we are already synced to the tip of the chain + // We only notify when we are already synced to the tip of the chain // or if this import triggers a re-org if make_notifications || tree_route.is_some() { + let header = import_headers.into_post(); if finalized { - operation.notify_finalized.push(hash); + let mut summary = match operation.notify_finalized.take() { + Some(mut summary) => { + summary.header = header.clone(); + summary.finalized.push(hash); + summary + }, + None => FinalizeSummary { + header: header.clone(), + finalized: vec![hash], + stale_heads: Vec::new(), + }, + }; + + if parent_exists { + // Add to the stale list all heads that are branching from parent besides our + // current `head`. + for head in self + .backend + .blockchain() + .leaves()? + .into_iter() + .filter(|h| *h != parent_hash) + { + let route_from_parent = sp_blockchain::tree_route( + self.backend.blockchain(), + parent_hash, + head, + )?; + if route_from_parent.retracted().is_empty() { + summary.stale_heads.push(head); + } + } + } + operation.notify_finalized = Some(summary); } operation.notify_imported = Some(ImportSummary { hash, origin, - header: import_headers.into_post(), + header, is_new_best, storage_changes, tree_route, @@ -966,12 +817,9 @@ where Block::new(import_block.header.clone(), body.clone()), )?; - let state = self.backend.state_at(at)?; - let changes_trie_state = - changes_tries_state_at_block(&at, self.backend.changes_trie_storage())?; - + let state = self.backend.state_at(*parent_hash)?; let gen_storage_changes = runtime_api - .into_storage_changes(&state, changes_trie_state.as_ref(), *parent_hash) + .into_storage_changes(&state, *parent_hash) .map_err(sp_blockchain::Error::Storage)?; if import_block.header.state_root() != &gen_storage_changes.transaction_storage_root @@ -1033,111 +881,105 @@ where // plugable we cannot make a better choice here. usages that need // an accurate "best" block need to go through `SelectChain` // instead. - operation.op.mark_head(BlockId::Hash(block))?; + operation.op.mark_head(block)?; } let enacted = route_from_finalized.enacted(); assert!(enacted.len() > 0); for finalize_new in &enacted[..enacted.len() - 1] { - operation.op.mark_finalized(BlockId::Hash(finalize_new.hash), None)?; + operation.op.mark_finalized(finalize_new.hash, None)?; } assert_eq!(enacted.last().map(|e| e.hash), Some(block)); - operation.op.mark_finalized(BlockId::Hash(block), justification)?; + operation.op.mark_finalized(block, justification)?; if notify { - // sometimes when syncing, tons of blocks can be finalized at once. - // we'll send notifications spuriously in that case. - const MAX_TO_NOTIFY: usize = 256; - let enacted = route_from_finalized.enacted(); - let start = enacted.len() - std::cmp::min(enacted.len(), MAX_TO_NOTIFY); - for finalized in &enacted[start..] { - operation.notify_finalized.push(finalized.hash); - } + let finalized = + route_from_finalized.enacted().iter().map(|elem| elem.hash).collect::>(); + + let block_number = route_from_finalized + .last() + .expect( + "The block to finalize is always the latest \ + block in the route to the finalized block; qed", + ) + .number; + + // The stale heads are the leaves that will be displaced after the + // block is finalized. + let stale_heads = + self.backend.blockchain().displaced_leaves_after_finalizing(block_number)?; + + let header = self + .backend + .blockchain() + .header(BlockId::Hash(block))? + .expect("Block to finalize expected to be onchain; qed"); + + operation.notify_finalized = Some(FinalizeSummary { header, finalized, stale_heads }); } Ok(()) } - fn notify_finalized(&self, notify_finalized: Vec) -> sp_blockchain::Result<()> { + fn notify_finalized( + &self, + notification: Option>, + ) -> sp_blockchain::Result<()> { let mut sinks = self.finality_notification_sinks.lock(); - if notify_finalized.is_empty() { - // cleanup any closed finality notification sinks - // since we won't be running the loop below which - // would also remove any closed sinks. - sinks.retain(|sink| !sink.is_closed()); - - return Ok(()) - } - - // We assume the list is sorted and only want to inform the - // telemetry once about the finalized block. - if let Some(last) = notify_finalized.last() { - let header = self.header(&BlockId::Hash(*last))?.expect( - "Header already known to exist in DB because it is indicated in the tree route; \ - qed", - ); - - telemetry!( - self.telemetry; - SUBSTRATE_INFO; - "notify.finalized"; - "height" => format!("{}", header.number()), - "best" => ?last, - ); - } - - for finalized_hash in notify_finalized { - let header = self.header(&BlockId::Hash(finalized_hash))?.expect( - "Header already known to exist in DB because it is indicated in the tree route; \ - qed", - ); + let notification = match notification { + Some(notify_finalized) => notify_finalized, + None => { + // Cleanup any closed finality notification sinks + // since we won't be running the loop below which + // would also remove any closed sinks. + sinks.retain(|sink| !sink.is_closed()); + return Ok(()) + }, + }; - let notification = FinalityNotification { header, hash: finalized_hash }; + telemetry!( + self.telemetry; + SUBSTRATE_INFO; + "notify.finalized"; + "height" => format!("{}", notification.header.number()), + "best" => ?notification.hash, + ); - sinks.retain(|sink| sink.unbounded_send(notification.clone()).is_ok()); - } + sinks.retain(|sink| sink.unbounded_send(notification.clone()).is_ok()); Ok(()) } fn notify_imported( &self, - notify_import: Option>, + notification: Option>, + storage_changes: Option<(StorageCollection, ChildStorageCollection)>, ) -> sp_blockchain::Result<()> { - let notify_import = match notify_import { + let notification = match notification { Some(notify_import) => notify_import, None => { - // cleanup any closed import notification sinks since we won't + // Cleanup any closed import notification sinks since we won't // be sending any notifications below which would remove any // closed sinks. this is necessary since during initial sync we // won't send any import notifications which could lead to a // temporary leak of closed/discarded notification sinks (e.g. // from consensus code). self.import_notification_sinks.lock().retain(|sink| !sink.is_closed()); - return Ok(()) }, }; - if let Some(storage_changes) = notify_import.storage_changes { + if let Some(storage_changes) = storage_changes { // TODO [ToDr] How to handle re-orgs? Should we re-emit all storage changes? - self.storage_notifications.lock().trigger( - ¬ify_import.hash, + self.storage_notifications.trigger( + ¬ification.hash, storage_changes.0.into_iter(), storage_changes.1.into_iter().map(|(sk, v)| (sk, v.into_iter())), ); } - let notification = BlockImportNotification:: { - hash: notify_import.hash, - origin: notify_import.origin, - header: notify_import.header, - is_new_best: notify_import.is_new_best, - tree_route: notify_import.tree_route.map(Arc::new), - }; - self.import_notification_sinks .lock() .retain(|sink| sink.unbounded_send(notification.clone()).is_ok()); @@ -1189,13 +1031,13 @@ where return Ok(BlockStatus::Queued) } } - let hash_and_number = match id.clone() { + let hash_and_number = match *id { BlockId::Hash(hash) => self.backend.blockchain().number(hash)?.map(|n| (hash, n)), BlockId::Number(n) => self.backend.blockchain().hash(n)?.map(|hash| (hash, n)), }; match hash_and_number { Some((hash, number)) => - if self.backend.have_state_at(&hash, number) { + if self.backend.have_state_at(hash, number) { Ok(BlockStatus::InChainWithState) } else { Ok(BlockStatus::InChainPruned) @@ -1215,9 +1057,9 @@ where /// Get block body by id. pub fn body( &self, - id: &BlockId, + hash: Block::Hash, ) -> sp_blockchain::Result::Extrinsic>>> { - self.backend.blockchain().body(*id) + self.backend.blockchain().body(hash) } /// Gets the uncles of the block with `target_hash` going back `max_generation` ancestors. @@ -1262,6 +1104,35 @@ where trace!("Collected {} uncles", uncles.len()); Ok(uncles) } + + fn resolve_state_version_from_wasm( + storage: &Storage, + executor: &E, + ) -> sp_blockchain::Result { + if let Some(wasm) = storage.top.get(well_known_keys::CODE) { + let mut ext = sp_state_machine::BasicExternalities::new_empty(); // just to read runtime version. + + let code_fetcher = sp_core::traits::WrappedRuntimeCode(wasm.as_slice().into()); + let runtime_code = sp_core::traits::RuntimeCode { + code_fetcher: &code_fetcher, + heap_pages: None, + hash: { + use std::hash::{Hash, Hasher}; + let mut state = DefaultHasher::new(); + wasm.hash(&mut state); + state.finish().to_le_bytes().to_vec() + }, + }; + let runtime_version = + RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code) + .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()))?; + Ok(runtime_version.state_version()) + } else { + Err(sp_blockchain::Error::VersionInvalid( + "Runtime missing from initial storage, could not read state version.".to_string(), + )) + } + } } impl UsageProvider for Client @@ -1284,120 +1155,184 @@ where { fn read_proof( &self, - id: &BlockId, + hash: Block::Hash, keys: &mut dyn Iterator, ) -> sp_blockchain::Result { - self.state_at(id).and_then(|state| prove_read(state, keys).map_err(Into::into)) + self.state_at(hash) + .and_then(|state| prove_read(state, keys).map_err(Into::into)) } fn read_child_proof( &self, - id: &BlockId, + hash: Block::Hash, child_info: &ChildInfo, keys: &mut dyn Iterator, ) -> sp_blockchain::Result { - self.state_at(id) + self.state_at(hash) .and_then(|state| prove_child_read(state, child_info, keys).map_err(Into::into)) } fn execution_proof( &self, - id: &BlockId, + hash: Block::Hash, method: &str, call_data: &[u8], ) -> sp_blockchain::Result<(Vec, StorageProof)> { - // Make sure we include the `:code` and `:heap_pages` in the execution proof to be - // backwards compatible. - // - // TODO: Remove when solved: https://github.com/paritytech/substrate/issues/5047 - let code_proof = self.read_proof( - id, - &mut [well_known_keys::CODE, well_known_keys::HEAP_PAGES].iter().map(|v| *v), - )?; - - self.executor - .prove_execution(id, method, call_data) - .map(|(r, p)| (r, StorageProof::merge(vec![p, code_proof]))) - } - - fn header_proof( - &self, - id: &BlockId, - ) -> sp_blockchain::Result<(Block::Header, StorageProof)> { - self.header_proof_with_cht_size(id, cht::size()) - } - - fn key_changes_proof( - &self, - first: Block::Hash, - last: Block::Hash, - min: Block::Hash, - max: Block::Hash, - storage_key: Option<&PrefixedStorageKey>, - key: &StorageKey, - ) -> sp_blockchain::Result> { - self.key_changes_proof_with_cht_size(first, last, min, max, storage_key, key, cht::size()) + self.executor.prove_execution(&BlockId::Hash(hash), method, call_data) } fn read_proof_collection( &self, - id: &BlockId, - start_key: &[u8], + hash: Block::Hash, + start_key: &[Vec], size_limit: usize, - ) -> sp_blockchain::Result<(StorageProof, u32)> { - let state = self.state_at(id)?; - Ok(prove_range_read_with_size::<_, HashFor>( - state, - None, - None, - size_limit, - Some(start_key), - )?) + ) -> sp_blockchain::Result<(CompactProof, u32)> { + let state = self.state_at(hash)?; + // this is a read proof, using version V0 or V1 is equivalent. + let root = state.storage_root(std::iter::empty(), StateVersion::V0).0; + + let (proof, count) = prove_range_read_with_child_with_size::<_, HashFor>( + state, size_limit, start_key, + )?; + let proof = proof + .into_compact_proof::>(root) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?; + Ok((proof, count)) } fn storage_collection( &self, - id: &BlockId, - start_key: &[u8], + hash: Block::Hash, + start_key: &[Vec], size_limit: usize, - ) -> sp_blockchain::Result, Vec)>> { - let state = self.state_at(id)?; - let mut current_key = start_key.to_vec(); - let mut total_size = 0; - let mut entries = Vec::new(); - while let Some(next_key) = state - .next_storage_key(¤t_key) - .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))? - { - let value = state - .storage(next_key.as_ref()) + ) -> sp_blockchain::Result> { + if start_key.len() > MAX_NESTED_TRIE_DEPTH { + return Err(Error::Backend("Invalid start key.".to_string())) + } + let state = self.state_at(hash)?; + let child_info = |storage_key: &Vec| -> sp_blockchain::Result { + let storage_key = PrefixedStorageKey::new_ref(storage_key); + match ChildType::from_prefixed_key(storage_key) { + Some((ChildType::ParentKeyId, storage_key)) => + Ok(ChildInfo::new_default(storage_key)), + None => Err(Error::Backend("Invalid child storage key.".to_string())), + } + }; + let mut current_child = if start_key.len() == 2 { + let start_key = start_key.get(0).expect("checked len"); + if let Some(child_root) = state + .storage(start_key) .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))? - .unwrap_or_default(); - let size = value.len() + next_key.len(); - if total_size + size > size_limit && !entries.is_empty() { + { + Some((child_info(start_key)?, child_root)) + } else { + return Err(Error::Backend("Invalid root start key.".to_string())) + } + } else { + None + }; + let mut current_key = start_key.last().map(Clone::clone).unwrap_or_default(); + let mut total_size = 0; + let mut result = vec![( + KeyValueStorageLevel { + state_root: Vec::new(), + key_values: Vec::new(), + parent_storage_keys: Vec::new(), + }, + false, + )]; + + let mut child_roots = HashSet::new(); + loop { + let mut entries = Vec::new(); + let mut complete = true; + let mut switch_child_key = None; + while let Some(next_key) = if let Some(child) = current_child.as_ref() { + state + .next_child_storage_key(&child.0, ¤t_key) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))? + } else { + state + .next_storage_key(¤t_key) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))? + } { + let value = if let Some(child) = current_child.as_ref() { + state + .child_storage(&child.0, next_key.as_ref()) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))? + .unwrap_or_default() + } else { + state + .storage(next_key.as_ref()) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))? + .unwrap_or_default() + }; + let size = value.len() + next_key.len(); + if total_size + size > size_limit && !entries.is_empty() { + complete = false; + break + } + total_size += size; + + if current_child.is_none() && + sp_core::storage::well_known_keys::is_child_storage_key(next_key.as_slice()) && + !child_roots.contains(value.as_slice()) + { + child_roots.insert(value.clone()); + switch_child_key = Some((next_key.clone(), value.clone())); + entries.push((next_key.clone(), value)); + break + } + entries.push((next_key.clone(), value)); + current_key = next_key; + } + if let Some((child, child_root)) = switch_child_key.take() { + result[0].0.key_values.extend(entries.into_iter()); + current_child = Some((child_info(&child)?, child_root)); + current_key = Vec::new(); + } else if let Some((child, child_root)) = current_child.take() { + current_key = child.into_prefixed_storage_key().into_inner(); + result.push(( + KeyValueStorageLevel { + state_root: child_root, + key_values: entries, + parent_storage_keys: Vec::new(), + }, + complete, + )); + if !complete { + break + } + } else { + result[0].0.key_values.extend(entries.into_iter()); + result[0].1 = complete; break } - total_size += size; - entries.push((next_key.clone(), value)); - current_key = next_key; } - Ok(entries) + Ok(result) } fn verify_range_proof( &self, root: Block::Hash, - proof: StorageProof, - start_key: &[u8], - ) -> sp_blockchain::Result<(Vec<(Vec, Vec)>, bool)> { - Ok(read_range_proof_check::>( - root, - proof, - None, - None, - None, - Some(start_key), - )?) + proof: CompactProof, + start_key: &[Vec], + ) -> sp_blockchain::Result<(KeyValueStates, usize)> { + let mut db = sp_state_machine::MemoryDB::>::new(&[]); + // Compact encoding + let _ = sp_trie::decode_compact::>, _, _>( + &mut db, + proof.iter_compact_encoded_nodes(), + Some(&root), + ) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?; + let proving_backend = sp_state_machine::TrieBackendBuilder::new(db, root).build(); + let state = read_range_proof_check_with_child_on_proving_backend::>( + &proving_backend, + start_key, + )?; + + Ok(state) } } @@ -1413,7 +1348,7 @@ where fn new_block_at>( &self, parent: &BlockId, - inherent_digests: DigestFor, + inherent_digests: Digest, record_proof: R, ) -> sp_blockchain::Result> { sc_block_builder::BlockBuilder::new( @@ -1428,7 +1363,7 @@ where fn new_block( &self, - inherent_digests: DigestFor, + inherent_digests: Digest, ) -> sp_blockchain::Result> { let info = self.chain_info(); sc_block_builder::BlockBuilder::new( @@ -1455,7 +1390,7 @@ where } fn execution_extensions(&self) -> &ExecutionExtensions { - &self.execution_extensions + self.executor.execution_extensions() } } @@ -1467,19 +1402,19 @@ where { fn storage_keys( &self, - id: &BlockId, + hash: Block::Hash, key_prefix: &StorageKey, ) -> sp_blockchain::Result> { - let keys = self.state_at(id)?.keys(&key_prefix.0).into_iter().map(StorageKey).collect(); + let keys = self.state_at(hash)?.keys(&key_prefix.0).into_iter().map(StorageKey).collect(); Ok(keys) } fn storage_pairs( &self, - id: &BlockId, + hash: ::Hash, key_prefix: &StorageKey, ) -> sp_blockchain::Result> { - let state = self.state_at(id)?; + let state = self.state_at(hash)?; let keys = state .keys(&key_prefix.0) .into_iter() @@ -1493,34 +1428,34 @@ where fn storage_keys_iter<'a>( &self, - id: &BlockId, + hash: ::Hash, prefix: Option<&'a StorageKey>, start_key: Option<&StorageKey>, ) -> sp_blockchain::Result> { - let state = self.state_at(id)?; + let state = self.state_at(hash)?; let start_key = start_key.or(prefix).map(|key| key.0.clone()).unwrap_or_else(Vec::new); Ok(KeyIterator::new(state, prefix, start_key)) } fn child_storage_keys_iter<'a>( &self, - id: &BlockId, + hash: ::Hash, child_info: ChildInfo, prefix: Option<&'a StorageKey>, start_key: Option<&StorageKey>, ) -> sp_blockchain::Result> { - let state = self.state_at(id)?; + let state = self.state_at(hash)?; let start_key = start_key.or(prefix).map(|key| key.0.clone()).unwrap_or_else(Vec::new); Ok(KeyIterator::new_child(state, child_info, prefix, start_key)) } fn storage( &self, - id: &BlockId, + hash: Block::Hash, key: &StorageKey, ) -> sp_blockchain::Result> { Ok(self - .state_at(id)? + .state_at(hash)? .storage(&key.0) .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))? .map(StorageData)) @@ -1528,23 +1463,22 @@ where fn storage_hash( &self, - id: &BlockId, + hash: ::Hash, key: &StorageKey, ) -> sp_blockchain::Result> { - Ok(self - .state_at(id)? + self.state_at(hash)? .storage_hash(&key.0) - .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e))) } fn child_storage_keys( &self, - id: &BlockId, + hash: ::Hash, child_info: &ChildInfo, key_prefix: &StorageKey, ) -> sp_blockchain::Result> { let keys = self - .state_at(id)? + .state_at(hash)? .child_keys(child_info, &key_prefix.0) .into_iter() .map(StorageKey) @@ -1554,12 +1488,12 @@ where fn child_storage( &self, - id: &BlockId, + hash: ::Hash, child_info: &ChildInfo, key: &StorageKey, ) -> sp_blockchain::Result> { Ok(self - .state_at(id)? + .state_at(hash)? .child_storage(child_info, &key.0) .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))? .map(StorageData)) @@ -1567,97 +1501,13 @@ where fn child_storage_hash( &self, - id: &BlockId, + hash: ::Hash, child_info: &ChildInfo, key: &StorageKey, ) -> sp_blockchain::Result> { - Ok(self - .state_at(id)? + self.state_at(hash)? .child_storage_hash(child_info, &key.0) - .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?) - } - - fn max_key_changes_range( - &self, - first: NumberFor, - last: BlockId, - ) -> sp_blockchain::Result, BlockId)>> { - let last_number = self.backend.blockchain().expect_block_number_from_id(&last)?; - let last_hash = self.backend.blockchain().expect_block_hash_from_id(&last)?; - if first > last_number { - return Err(sp_blockchain::Error::ChangesTrieAccessFailed( - "Invalid changes trie range".into(), - )) - } - - let (storage, configs) = match self.require_changes_trie(first, last_hash, false).ok() { - Some((storage, configs)) => (storage, configs), - None => return Ok(None), - }; - - let first_available_changes_trie = configs.last().map(|config| config.0); - match first_available_changes_trie { - Some(first_available_changes_trie) => { - let oldest_unpruned = storage.oldest_pruned_digest_range_end(); - let first = std::cmp::max(first_available_changes_trie, oldest_unpruned); - Ok(Some((first, last))) - }, - None => Ok(None), - } - } - - fn key_changes( - &self, - first: NumberFor, - last: BlockId, - storage_key: Option<&PrefixedStorageKey>, - key: &StorageKey, - ) -> sp_blockchain::Result, u32)>> { - let last_number = self.backend.blockchain().expect_block_number_from_id(&last)?; - let last_hash = self.backend.blockchain().expect_block_hash_from_id(&last)?; - let (storage, configs) = self.require_changes_trie(first, last_hash, true)?; - - let mut result = Vec::new(); - let best_number = self.backend.blockchain().info().best_number; - for (config_zero, config_end, config) in configs { - let range_first = ::std::cmp::max(first, config_zero + One::one()); - let range_anchor = match config_end { - Some((config_end_number, config_end_hash)) => - if last_number > config_end_number { - ChangesTrieAnchorBlockId { - hash: config_end_hash, - number: config_end_number, - } - } else { - ChangesTrieAnchorBlockId { - hash: convert_hash(&last_hash), - number: last_number, - } - }, - None => - ChangesTrieAnchorBlockId { hash: convert_hash(&last_hash), number: last_number }, - }; - - let config_range = ChangesTrieConfigurationRange { - config: &config, - zero: config_zero.clone(), - end: config_end.map(|(config_end_number, _)| config_end_number), - }; - let result_range: Vec<(NumberFor, u32)> = key_changes::, _>( - config_range, - storage.storage(), - range_first, - &range_anchor, - best_number, - storage_key, - &key.0, - ) - .and_then(|r| r.map(|r| r.map(|(block, tx)| (block, tx))).collect::>()) - .map_err(|err| sp_blockchain::Error::ChangesTrieAccessFailed(err))?; - result.extend(result_range); - } - - Ok(result) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e))) } } @@ -1734,7 +1584,7 @@ where } } -impl sp_runtime::traits::BlockIdTo for Client +impl BlockIdTo for Client where B: backend::Backend, E: CallExecutor + Send + Sync, @@ -1763,11 +1613,11 @@ where RA: Send + Sync, { fn header(&self, id: BlockId) -> sp_blockchain::Result> { - (**self).backend.blockchain().header(id) + self.backend.blockchain().header(id) } fn info(&self) -> blockchain::Info { - (**self).backend.blockchain().info() + self.backend.blockchain().info() } fn status(&self, id: BlockId) -> sp_blockchain::Result { @@ -1786,26 +1636,16 @@ where } } -impl ProvideCache for Client -where - B: backend::Backend, - Block: BlockT, -{ - fn cache(&self) -> Option>> { - self.backend.blockchain().cache() - } -} - impl ProvideRuntimeApi for Client where B: backend::Backend, E: CallExecutor + Send + Sync, Block: BlockT, - RA: ConstructRuntimeApi, + RA: ConstructRuntimeApi + Send + Sync, { type Api = >::RuntimeApi; - fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { + fn runtime_api(&self) -> ApiRef { RA::construct_runtime_api(self) } } @@ -1815,39 +1655,34 @@ where B: backend::Backend, E: CallExecutor + Send + Sync, Block: BlockT, + RA: Send + Sync, { type StateBackend = B::State; - fn call_api_at< - 'a, - R: Encode + Decode + PartialEq, - NC: FnOnce() -> result::Result + UnwindSafe, - >( + fn call_api_at( &self, - params: CallApiAtParams<'a, Block, NC, B::State>, - ) -> Result, sp_api::ApiError> { - let at = params.at; - - let (manager, extensions) = - self.execution_extensions.manager_and_extensions(at, params.context); - + params: CallApiAtParams, + ) -> Result, sp_api::ApiError> { self.executor - .contextual_call:: _, _, _>( - at, + .contextual_call( + params.at, params.function, ¶ms.arguments, params.overlayed_changes, Some(params.storage_transaction_cache), - manager, - params.native_call, params.recorder, - Some(extensions), + params.context, ) .map_err(Into::into) } fn runtime_version_at(&self, at: &BlockId) -> Result { - self.runtime_version_at(at).map_err(Into::into) + CallExecutor::runtime_version(&self.executor, at).map_err(Into::into) + } + + fn state_at(&self, at: &BlockId) -> Result { + let hash = self.backend.blockchain().expect_block_hash_from_id(at)?; + self.state_at(hash).map_err(Into::into) } } @@ -1888,7 +1723,7 @@ where let storage_changes = match self.prepare_block_storage_changes(&mut import_block).map_err(|e| { - warn!("Block prepare storage changes error:\n{:?}", e); + warn!("Block prepare storage changes error: {}", e); ConsensusError::ClientImport(e.to_string()) })? { PrepareStorageChangesResult::Discard(res) => return Ok(res), @@ -1899,8 +1734,8 @@ where self.apply_block(operation, import_block, new_cache, storage_changes) }) .map_err(|e| { - warn!("Block import error:\n{:?}", e); - ConsensusError::ClientImport(e.to_string()).into() + warn!("Block import error: {}", e); + ConsensusError::ClientImport(e.to_string()) }) } @@ -1943,9 +1778,8 @@ where .block_status(&BlockId::Hash(hash)) .map_err(|e| ConsensusError::ClientImport(e.to_string()))? { - BlockStatus::InChainWithState | BlockStatus::Queued if !import_existing => + BlockStatus::InChainWithState | BlockStatus::Queued => return Ok(ImportResult::AlreadyInChain), - BlockStatus::InChainWithState | BlockStatus::Queued => {}, BlockStatus::InChainPruned if !import_existing => return Ok(ImportResult::AlreadyInChain), BlockStatus::InChainPruned => {}, @@ -2009,29 +1843,22 @@ where fn apply_finality( &self, operation: &mut ClientImportOperation, - id: BlockId, + hash: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()> { let last_best = self.backend.blockchain().info().best_hash; - let to_finalize_hash = self.backend.blockchain().expect_block_hash_from_id(&id)?; - self.apply_finality_with_block_hash( - operation, - to_finalize_hash, - justification, - last_best, - notify, - ) + self.apply_finality_with_block_hash(operation, hash, justification, last_best, notify) } fn finalize_block( &self, - id: BlockId, + hash: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()> { self.lock_import_and_run(|operation| { - self.apply_finality(operation, id, justification, notify) + self.apply_finality(operation, hash, justification, notify) }) } } @@ -2045,20 +1872,33 @@ where fn apply_finality( &self, operation: &mut ClientImportOperation, - id: BlockId, + hash: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()> { - (**self).apply_finality(operation, id, justification, notify) + (**self).apply_finality(operation, hash, justification, notify) } fn finalize_block( &self, - id: BlockId, + hash: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()> { - (**self).finalize_block(id, justification, notify) + (**self).finalize_block(hash, justification, notify) + } +} + +impl PreCommitActions for Client +where + Block: BlockT, +{ + fn register_import_action(&self, action: OnImportAction) { + self.import_actions.lock().push(action); + } + + fn register_finality_action(&self, action: OnFinalityAction) { + self.finality_actions.lock().push(action); } } @@ -2086,7 +1926,7 @@ where filter_keys: Option<&[StorageKey]>, child_filter_keys: Option<&[(StorageKey, Option>)]>, ) -> sp_blockchain::Result> { - Ok(self.storage_notifications.lock().listen(filter_keys, child_filter_keys)) + Ok(self.storage_notifications.listen(filter_keys, child_filter_keys)) } } @@ -2098,16 +1938,22 @@ where { fn block_body( &self, - id: &BlockId, + hash: Block::Hash, ) -> sp_blockchain::Result::Extrinsic>>> { - self.body(id) + self.body(hash) } fn block(&self, id: &BlockId) -> sp_blockchain::Result>> { - Ok(match (self.header(id)?, self.body(id)?, self.justifications(id)?) { - (Some(header), Some(extrinsics), justifications) => - Some(SignedBlock { block: Block::new(header, extrinsics), justifications }), - _ => None, + Ok(match self.header(id)? { + Some(header) => { + let hash = header.hash(); + match (self.body(hash)?, self.justifications(hash)?) { + (Some(extrinsics), justifications) => + Some(SignedBlock { block: Block::new(header, extrinsics), justifications }), + _ => None, + } + }, + None => None, }) } @@ -2115,27 +1961,28 @@ where Client::block_status(self, id) } - fn justifications(&self, id: &BlockId) -> sp_blockchain::Result> { - self.backend.blockchain().justifications(*id) + fn justifications(&self, hash: Block::Hash) -> sp_blockchain::Result> { + self.backend.blockchain().justifications(hash) } fn block_hash(&self, number: NumberFor) -> sp_blockchain::Result> { self.backend.blockchain().hash(number) } - fn indexed_transaction(&self, hash: &Block::Hash) -> sp_blockchain::Result>> { + fn indexed_transaction(&self, hash: Block::Hash) -> sp_blockchain::Result>> { self.backend.blockchain().indexed_transaction(hash) } - fn has_indexed_transaction(&self, hash: &Block::Hash) -> sp_blockchain::Result { + fn has_indexed_transaction(&self, hash: Block::Hash) -> sp_blockchain::Result { self.backend.blockchain().has_indexed_transaction(hash) } - fn block_indexed_body( - &self, - id: &BlockId, - ) -> sp_blockchain::Result>>> { - self.backend.blockchain().block_indexed_body(*id) + fn block_indexed_body(&self, hash: Block::Hash) -> sp_blockchain::Result>>> { + self.backend.blockchain().block_indexed_body(hash) + } + + fn requires_full_sync(&self) -> bool { + self.backend.requires_full_sync() } } @@ -2222,9 +2069,19 @@ where &self, number: NumberFor, ) -> Result>>, sp_transaction_storage_proof::Error> { + let hash = match self + .backend + .blockchain() + .block_hash_from_id(&BlockId::Number(number)) + .map_err(|e| sp_transaction_storage_proof::Error::Application(Box::new(e)))? + { + Some(hash) => hash, + None => return Ok(None), + }; + self.backend .blockchain() - .block_indexed_body(BlockId::number(number)) + .block_indexed_body(hash) .map_err(|e| sp_transaction_storage_proof::Error::Application(Box::new(e))) } diff --git a/client/service/src/client/genesis.rs b/client/service/src/client/genesis.rs index e764e8e24f105..35fb11f04972a 100644 --- a/client/service/src/client/genesis.rs +++ b/client/service/src/client/genesis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,8 +22,10 @@ use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero /// Create a genesis block, given the initial storage. pub fn construct_genesis_block(state_root: Block::Hash) -> Block { - let extrinsics_root = - <<::Header as HeaderT>::Hashing as HashT>::trie_root(Vec::new()); + let extrinsics_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( + Vec::new(), + sp_runtime::StateVersion::V0, + ); Block::new( <::Header as HeaderT>::new( diff --git a/client/service/src/client/light.rs b/client/service/src/client/light.rs deleted file mode 100644 index 7c13b98843e05..0000000000000 --- a/client/service/src/client/light.rs +++ /dev/null @@ -1,82 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Light client utilities. - -use std::sync::Arc; - -use prometheus_endpoint::Registry; -use sc_executor::RuntimeVersionOf; -use sc_telemetry::TelemetryHandle; -use sp_blockchain::Result as ClientResult; -use sp_core::traits::{CodeExecutor, SpawnNamed}; -use sp_runtime::{ - traits::{Block as BlockT, HashFor}, - BuildStorage, -}; - -use super::{ - call_executor::LocalCallExecutor, - client::{Client, ClientConfig}, -}; -use sc_client_api::light::Storage as BlockchainStorage; -use sc_light::{Backend, GenesisCallExecutor}; - -/// Create an instance of light client. -pub fn new_light( - backend: Arc>>, - genesis_storage: &dyn BuildStorage, - code_executor: E, - spawn_handle: Box, - prometheus_registry: Option, - telemetry: Option, -) -> ClientResult< - Client< - Backend>, - GenesisCallExecutor< - Backend>, - LocalCallExecutor>, E>, - >, - B, - RA, - >, -> -where - B: BlockT, - S: BlockchainStorage + 'static, - E: CodeExecutor + RuntimeVersionOf + Clone + 'static, -{ - let local_executor = LocalCallExecutor::new( - backend.clone(), - code_executor, - spawn_handle.clone(), - ClientConfig::default(), - )?; - let executor = GenesisCallExecutor::new(backend.clone(), local_executor); - Client::new( - backend, - executor, - genesis_storage, - Default::default(), - Default::default(), - Default::default(), - prometheus_registry, - telemetry, - ClientConfig::default(), - ) -} diff --git a/client/service/src/client/mod.rs b/client/service/src/client/mod.rs index 754309e864ebd..eac744923d501 100644 --- a/client/service/src/client/mod.rs +++ b/client/service/src/client/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -37,9 +37,8 @@ //! The latter typically requires passing one of: //! //! - A [`LocalCallExecutor`] running the runtime locally. -//! - A [`RemoteCallExecutor`](sc_client_api::light::RemoteCallRequest) that will ask a -//! third-party to perform the executions. -//! - A [`RemoteOrLocalCallExecutor`](sc_client_api::light::LocalOrRemote), combination of the two. +//! - A `RemoteCallExecutor` that will ask a third-party to perform the executions. +//! - A `RemoteOrLocalCallExecutor` combination of the two. //! //! Additionally, the fourth generic parameter of the `Client` is a marker type representing //! the ways in which the runtime can interface with the outside. Any code that builds a `Client` @@ -49,7 +48,6 @@ mod block_rules; mod call_executor; mod client; pub mod genesis; -pub mod light; mod wasm_override; mod wasm_substitutes; diff --git a/client/service/src/client/wasm_override.rs b/client/service/src/client/wasm_override.rs index 6d5a071269d4d..5fc748f3e88b9 100644 --- a/client/service/src/client/wasm_override.rs +++ b/client/service/src/client/wasm_override.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -35,9 +35,10 @@ //! A custom WASM blob will override on-chain WASM if the spec version matches. If it is //! required to overrides multiple runtimes, multiple WASM blobs matching each of the spec versions //! needed must be provided in the given directory. + use sc_executor::RuntimeVersionOf; use sp_blockchain::Result; -use sp_core::traits::{FetchRuntimeCode, RuntimeCode}; +use sp_core::traits::{FetchRuntimeCode, RuntimeCode, WrappedRuntimeCode}; use sp_state_machine::BasicExternalities; use sp_version::RuntimeVersion; use std::{ @@ -45,19 +46,32 @@ use std::{ fs, hash::Hasher as _, path::{Path, PathBuf}, + time::{Duration, Instant}, }; -#[derive(Clone, Debug, PartialEq)] +/// The interval in that we will print a warning when a wasm blob `spec_name` +/// doesn't match with the on-chain `spec_name`. +const WARN_INTERVAL: Duration = Duration::from_secs(30); + /// Auxiliary structure that holds a wasm blob and its hash. +#[derive(Debug)] struct WasmBlob { + /// The actual wasm blob, aka the code. code: Vec, + /// The hash of [`Self::code`]. hash: Vec, + /// The path where this blob was found. + path: PathBuf, + /// The `spec_name` found in the runtime version of this blob. + spec_name: String, + /// When was the last time we have warned about the wasm blob having + /// a wrong `spec_name`? + last_warn: parking_lot::Mutex>, } impl WasmBlob { - fn new(code: Vec) -> Self { - let hash = make_hash(&code); - Self { code, hash } + fn new(code: Vec, hash: Vec, path: PathBuf, spec_name: String) -> Self { + Self { code, hash, path, spec_name, last_warn: Default::default() } } fn runtime_code(&self, heap_pages: Option) -> RuntimeCode { @@ -73,7 +87,7 @@ fn make_hash(val: &K) -> Vec { } impl FetchRuntimeCode for WasmBlob { - fn fetch_runtime_code<'a>(&'a self) -> Option> { + fn fetch_runtime_code(&self) -> Option> { Some(self.code.as_slice().into()) } } @@ -103,35 +117,62 @@ impl From for sp_blockchain::Error { /// Scrapes WASM from a folder and returns WASM from that folder /// if the runtime spec version matches. -#[derive(Clone, Debug)] -pub struct WasmOverride { +#[derive(Debug)] +pub struct WasmOverride { // Map of runtime spec version -> Wasm Blob overrides: HashMap, - executor: E, } -impl WasmOverride -where - E: RuntimeVersionOf + Clone + 'static, -{ - pub fn new

(path: P, executor: E) -> Result +impl WasmOverride { + pub fn new(path: P, executor: &E) -> Result where P: AsRef, + E: RuntimeVersionOf, { - let overrides = Self::scrape_overrides(path.as_ref(), &executor)?; - Ok(Self { overrides, executor }) + let overrides = Self::scrape_overrides(path.as_ref(), executor)?; + Ok(Self { overrides }) } /// Gets an override by it's runtime spec version. /// /// Returns `None` if an override for a spec version does not exist. - pub fn get<'a, 'b: 'a>(&'b self, spec: &u32, pages: Option) -> Option> { - self.overrides.get(spec).map(|w| w.runtime_code(pages)) + pub fn get<'a, 'b: 'a>( + &'b self, + spec: &u32, + pages: Option, + spec_name: &str, + ) -> Option> { + self.overrides.get(spec).and_then(|w| { + if spec_name == w.spec_name { + Some(w.runtime_code(pages)) + } else { + let mut last_warn = w.last_warn.lock(); + let now = Instant::now(); + + if last_warn.map_or(true, |l| l + WARN_INTERVAL <= now) { + *last_warn = Some(now); + + tracing::warn!( + target = "wasm_overrides", + on_chain_spec_name = %spec_name, + override_spec_name = %w.spec_name, + spec_version = %spec, + wasm_file = %w.path.display(), + "On chain and override `spec_name` do not match! Ignoring override.", + ); + } + + None + } + }) } /// Scrapes a folder for WASM runtimes. /// Returns a hashmap of the runtime version and wasm runtime code. - fn scrape_overrides(dir: &Path, executor: &E) -> Result> { + fn scrape_overrides(dir: &Path, executor: &E) -> Result> + where + E: RuntimeVersionOf, + { let handle_err = |e: std::io::Error| -> sp_blockchain::Error { WasmOverrideError::Io(dir.to_owned(), e).into() }; @@ -145,27 +186,30 @@ where for entry in fs::read_dir(dir).map_err(handle_err)? { let entry = entry.map_err(handle_err)?; let path = entry.path(); - match path.extension().map(|e| e.to_str()).flatten() { - Some("wasm") => { - let wasm = WasmBlob::new(fs::read(&path).map_err(handle_err)?); - let version = Self::runtime_version(executor, &wasm, Some(128))?; - log::info!( + if let Some("wasm") = path.extension().and_then(|e| e.to_str()) { + let code = fs::read(&path).map_err(handle_err)?; + let code_hash = make_hash(&code); + let version = Self::runtime_version(executor, &code, &code_hash, Some(128))?; + tracing::info!( + target: "wasm_overrides", + version = %version, + file = %path.display(), + "Found wasm override.", + ); + + let wasm = + WasmBlob::new(code, code_hash, path.clone(), version.spec_name.to_string()); + + if let Some(other) = overrides.insert(version.spec_version, wasm) { + tracing::info!( target: "wasm_overrides", - "Found wasm override in file: `{:?}`, version: {}", - path.to_str(), - version, + first = %other.path.display(), + second = %path.display(), + %version, + "Found duplicate spec version for runtime.", ); - if let Some(_duplicate) = overrides.insert(version.spec_version, wasm) { - log::info!( - target: "wasm_overrides", - "Found duplicate spec version for runtime in file: `{:?}`, version: {}", - path.to_str(), - version, - ); - duplicates.push(format!("{}", path.display())); - } - }, - _ => (), + duplicates.push(path.display().to_string()); + } } } @@ -176,29 +220,46 @@ where Ok(overrides) } - fn runtime_version( + fn runtime_version( executor: &E, - code: &WasmBlob, + code: &[u8], + code_hash: &[u8], heap_pages: Option, - ) -> Result { + ) -> Result + where + E: RuntimeVersionOf, + { let mut ext = BasicExternalities::default(); executor - .runtime_version(&mut ext, &code.runtime_code(heap_pages)) - .map_err(|e| WasmOverrideError::VersionInvalid(format!("{:?}", e)).into()) + .runtime_version( + &mut ext, + &RuntimeCode { + code_fetcher: &WrappedRuntimeCode(code.into()), + heap_pages, + hash: code_hash.into(), + }, + ) + .map_err(|e| WasmOverrideError::VersionInvalid(e.to_string()).into()) } } /// Returns a WasmOverride struct filled with dummy data for testing. #[cfg(test)] -pub fn dummy_overrides(executor: &E) -> WasmOverride -where - E: RuntimeVersionOf + Clone + 'static, -{ +pub fn dummy_overrides() -> WasmOverride { let mut overrides = HashMap::new(); - overrides.insert(0, WasmBlob::new(vec![0, 0, 0, 0, 0, 0, 0, 0])); - overrides.insert(1, WasmBlob::new(vec![1, 1, 1, 1, 1, 1, 1, 1])); - overrides.insert(2, WasmBlob::new(vec![2, 2, 2, 2, 2, 2, 2, 2])); - WasmOverride { overrides, executor: executor.clone() } + overrides.insert( + 0, + WasmBlob::new(vec![0, 0, 0, 0, 0, 0, 0, 0], vec![0], PathBuf::new(), "test".into()), + ); + overrides.insert( + 1, + WasmBlob::new(vec![1, 1, 1, 1, 1, 1, 1, 1], vec![1], PathBuf::new(), "test".into()), + ); + overrides.insert( + 2, + WasmBlob::new(vec![2, 2, 2, 2, 2, 2, 2, 2], vec![2], PathBuf::new(), "test".into()), + ); + WasmOverride { overrides } } #[cfg(test)] @@ -217,6 +278,7 @@ mod tests { WasmExecutionMethod::Interpreted, Some(128), 1, + 2, ); let bytes = substrate_test_runtime::wasm_binary_unwrap(); let dir = tempfile::tempdir().expect("Create a temporary directory"); @@ -226,15 +288,20 @@ mod tests { #[test] fn should_get_runtime_version() { - let wasm = WasmBlob::new(substrate_test_runtime::wasm_binary_unwrap().to_vec()); let executor = NativeElseWasmExecutor::::new( WasmExecutionMethod::Interpreted, Some(128), 1, + 2, ); - let version = WasmOverride::runtime_version(&executor, &wasm, Some(128)) - .expect("should get the `RuntimeVersion` of the test-runtime wasm blob"); + let version = WasmOverride::runtime_version( + &executor, + substrate_test_runtime::wasm_binary_unwrap(), + &[1], + Some(128), + ) + .expect("should get the `RuntimeVersion` of the test-runtime wasm blob"); assert_eq!(version.spec_version, 2); } diff --git a/client/service/src/client/wasm_substitutes.rs b/client/service/src/client/wasm_substitutes.rs index 28975790e9b57..f826fa3613c84 100644 --- a/client/service/src/client/wasm_substitutes.rs +++ b/client/service/src/client/wasm_substitutes.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,7 +18,6 @@ //! # WASM substitutes -use parking_lot::RwLock; use sc_client_api::backend; use sc_executor::RuntimeVersionOf; use sp_blockchain::{HeaderBackend, Result}; @@ -40,21 +39,14 @@ use std::{ struct WasmSubstitute { code: Vec, hash: Vec, - /// The hash of the block from that on we should use the substitute. - block_hash: Block::Hash, - /// The block number of `block_hash`. If `None`, the block is still unknown. - block_number: RwLock>>, + /// The block number on which we should start using the substitute. + block_number: NumberFor, } impl WasmSubstitute { - fn new( - code: Vec, - block_hash: Block::Hash, - backend: &impl backend::Backend, - ) -> Result { - let block_number = RwLock::new(backend.blockchain().number(block_hash)?); + fn new(code: Vec, block_number: NumberFor) -> Self { let hash = make_hash(&code); - Ok(Self { code, hash, block_hash, block_number }) + Self { code, hash, block_number } } fn runtime_code(&self, heap_pages: Option) -> RuntimeCode { @@ -63,32 +55,10 @@ impl WasmSubstitute { /// Returns `true` when the substitute matches for the given `block_id`. fn matches(&self, block_id: &BlockId, backend: &impl backend::Backend) -> bool { - let block_number = *self.block_number.read(); - let block_number = if let Some(block_number) = block_number { - block_number - } else { - let block_number = match backend.blockchain().number(self.block_hash) { - Ok(Some(n)) => n, - // still unknown - Ok(None) => return false, - Err(e) => { - log::debug!( - target: "wasm_substitutes", - "Failed to get block number for block hash {:?}: {:?}", - self.block_hash, - e, - ); - return false - }, - }; - *self.block_number.write() = Some(block_number); - block_number - }; - let requested_block_number = - backend.blockchain().block_number_from_id(&block_id).ok().flatten(); + backend.blockchain().block_number_from_id(block_id).ok().flatten(); - Some(block_number) <= requested_block_number + Some(self.block_number) <= requested_block_number } } @@ -100,7 +70,7 @@ fn make_hash(val: &K) -> Vec { } impl FetchRuntimeCode for WasmSubstitute { - fn fetch_runtime_code<'a>(&'a self) -> Option> { + fn fetch_runtime_code(&self) -> Option> { Some(self.code.as_slice().into()) } } @@ -145,14 +115,14 @@ where { /// Create a new instance. pub fn new( - substitutes: HashMap>, + substitutes: HashMap, Vec>, executor: Executor, backend: Arc, ) -> Result { let substitutes = substitutes .into_iter() - .map(|(parent_block_hash, code)| { - let substitute = WasmSubstitute::new(code, parent_block_hash, &*backend)?; + .map(|(block_number, code)| { + let substitute = WasmSubstitute::new(code, block_number); let version = Self::runtime_version(&executor, &substitute)?; Ok((version.spec_version, substitute)) }) @@ -181,6 +151,6 @@ where let mut ext = BasicExternalities::default(); executor .runtime_version(&mut ext, &code.runtime_code(None)) - .map_err(|e| WasmSubstituteError::VersionInvalid(format!("{:?}", e)).into()) + .map_err(|e| WasmSubstituteError::VersionInvalid(e.to_string()).into()) } } diff --git a/client/service/src/config.rs b/client/service/src/config.rs index a98a34b473cee..e79ff48d6f0ff 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,16 +19,18 @@ //! Service configuration. pub use sc_client_api::execution_extensions::{ExecutionStrategies, ExecutionStrategy}; -pub use sc_client_db::{Database, DatabaseSource, KeepBlocks, PruningMode, TransactionStorageMode}; -pub use sc_executor::WasmExecutionMethod; +pub use sc_client_db::{BlocksPruning, Database, DatabaseSource, PruningMode}; +pub use sc_executor::{WasmExecutionMethod, WasmtimeInstantiationStrategy}; pub use sc_network::{ - config::{ - IncomingRequest, MultiaddrWithPeerId, NetworkConfiguration, NodeKeyConfig, - NonDefaultSetConfig, OutgoingResponse, RequestResponseConfig, Role, SetConfig, - TransportConfig, - }, + config::{NetworkConfiguration, NodeKeyConfig, Role}, Multiaddr, }; +pub use sc_network_common::{ + config::{MultiaddrWithPeerId, NonDefaultSetConfig, ProtocolId, SetConfig, TransportConfig}, + request_responses::{ + IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, + }, +}; use prometheus_endpoint::Registry; use sc_chain_spec::ChainSpec; @@ -36,7 +38,7 @@ pub use sc_telemetry::TelemetryEndpoints; pub use sc_transaction_pool::Options as TransactionPoolOptions; use sp_core::crypto::SecretString; use std::{ - io, + io, iter, net::SocketAddr, path::{Path, PathBuf}, }; @@ -63,16 +65,16 @@ pub struct Configuration { pub keystore_remote: Option, /// Configuration for the database. pub database: DatabaseSource, - /// Size of internal state cache in Bytes - pub state_cache_size: usize, - /// Size in percent of cache size dedicated to child tries - pub state_cache_child_ratio: Option, + /// Maximum size of internal trie cache in bytes. + /// + /// If `None` is given the cache is disabled. + pub trie_cache_maximum_size: Option, /// State pruning settings. - pub state_pruning: PruningMode, + pub state_pruning: Option, /// Number of blocks to keep in the db. - pub keep_blocks: KeepBlocks, - /// Transaction storage scheme. - pub transaction_storage: TransactionStorageMode, + /// + /// NOTE: only finalized blocks are subject for removal! + pub blocks_pruning: BlocksPruning, /// Chain configuration. pub chain_spec: Box, /// Wasm execution method. @@ -97,6 +99,20 @@ pub struct Configuration { pub rpc_methods: RpcMethods, /// Maximum payload of rpc request/responses. pub rpc_max_payload: Option, + /// Maximum payload of a rpc request + pub rpc_max_request_size: Option, + /// Maximum payload of a rpc request + pub rpc_max_response_size: Option, + /// Custom JSON-RPC subscription ID provider. + /// + /// Default: [`crate::RandomStringSubscriptionId`]. + pub rpc_id_provider: Option>, + /// Maximum allowed subscriptions per rpc connection + /// + /// Default: 1024. + pub rpc_max_subs_per_conn: Option, + /// Maximum size of the output buffer capacity for websocket connections. + pub ws_max_out_buffer_capacity: Option, /// Prometheus endpoint configuration. `None` if disabled. pub prometheus_config: Option, /// Telemetry service URL. `None` if disabled. @@ -118,8 +134,6 @@ pub struct Configuration { pub dev_key_seed: Option, /// Tracing targets pub tracing_targets: Option, - /// Is log filter reloading disabled - pub disable_log_reloading: bool, /// Tracing receiver pub tracing_receiver: sc_tracing::TracingReceiver, /// The size of the instances cache. @@ -132,6 +146,8 @@ pub struct Configuration { pub base_path: Option, /// Configuration of the output format that the informant uses. pub informant_output_format: sc_informant::OutputFormat, + /// Maximum number of different runtime versions that can be cached. + pub runtime_cache_size: u8, } /// Type for tasks spawned by the executor. @@ -186,12 +202,11 @@ pub struct PrometheusConfig { impl PrometheusConfig { /// Create a new config using the default registry. - /// - /// The default registry prefixes metrics with `substrate`. - pub fn new_with_default_registry(port: SocketAddr) -> Self { + pub fn new_with_default_registry(port: SocketAddr, chain_id: String) -> Self { + let param = iter::once((String::from("chain"), chain_id)).collect(); Self { port, - registry: Registry::new_custom(Some("substrate".into()), None) + registry: Registry::new_custom(None, Some(param)) .expect("this can only fail if the prefix is empty"), } } @@ -209,7 +224,7 @@ impl Configuration { } /// Returns the network protocol id from the chain spec, or the default. - pub fn protocol_id(&self) -> sc_network::config::ProtocolId { + pub fn protocol_id(&self) -> ProtocolId { let protocol_id_full = match self.chain_spec.protocol_id() { Some(pid) => pid, None => { @@ -221,7 +236,7 @@ impl Configuration { crate::DEFAULT_PROTOCOL_ID }, }; - sc_network::config::ProtocolId::from(protocol_id_full) + ProtocolId::from(protocol_id_full) } } @@ -243,31 +258,43 @@ impl Default for RpcMethods { } } -/// The base path that is used for everything that needs to be write on disk to run a node. +#[static_init::dynamic(drop, lazy)] +static mut BASE_PATH_TEMP: Option = None; + +/// The base path that is used for everything that needs to be written on disk to run a node. #[derive(Debug)] -pub enum BasePath { - /// A temporary directory is used as base path and will be deleted when dropped. - Temporary(TempDir), - /// A path on the disk. - Permanenent(PathBuf), +pub struct BasePath { + path: PathBuf, } impl BasePath { /// Create a `BasePath` instance using a temporary directory prefixed with "substrate" and use /// it as base path. /// - /// Note: the temporary directory will be created automatically and deleted when the `BasePath` - /// instance is dropped. + /// Note: The temporary directory will be created automatically and deleted when the program + /// exits. Every call to this function will return the same path for the lifetime of the + /// program. pub fn new_temp_dir() -> io::Result { - Ok(BasePath::Temporary(tempfile::Builder::new().prefix("substrate").tempdir()?)) + let mut temp = BASE_PATH_TEMP.write(); + + match &*temp { + Some(p) => Ok(Self::new(p.path())), + None => { + let temp_dir = tempfile::Builder::new().prefix("substrate").tempdir()?; + let path = PathBuf::from(temp_dir.path()); + + *temp = Some(temp_dir); + Ok(Self::new(path)) + }, + } } /// Create a `BasePath` instance based on an existing path on disk. /// /// Note: this function will not ensure that the directory exist nor create the directory. It /// will also not delete the directory when the instance is dropped. - pub fn new>(path: P) -> BasePath { - BasePath::Permanenent(path.as_ref().to_path_buf()) + pub fn new>(path: P) -> BasePath { + Self { path: path.into() } } /// Create a base path from values describing the project. @@ -281,10 +308,7 @@ impl BasePath { /// Retrieve the base path. pub fn path(&self) -> &Path { - match self { - BasePath::Temporary(temp_dir) => temp_dir.path(), - BasePath::Permanenent(path) => path.as_path(), - } + &self.path } /// Returns the configuration directory inside this base path. @@ -295,7 +319,7 @@ impl BasePath { } } -impl std::convert::From for BasePath { +impl From for BasePath { fn from(path: PathBuf) -> Self { BasePath::new(path) } diff --git a/client/service/src/error.rs b/client/service/src/error.rs index 1acd33ead6777..001a83922d776 100644 --- a/client/service/src/error.rs +++ b/client/service/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,7 +19,6 @@ //! Errors that can occur during the service operation. use sc_keystore; -use sc_network; use sp_blockchain; use sp_consensus; @@ -41,7 +40,7 @@ pub enum Error { Consensus(#[from] sp_consensus::Error), #[error(transparent)] - Network(#[from] sc_network::error::Error), + Network(#[from] sc_network_common::error::Error), #[error(transparent)] Keystore(#[from] sc_keystore::Error), @@ -71,7 +70,7 @@ impl<'a> From<&'a str> for Error { } } -impl<'a> From for Error { +impl From for Error { fn from(s: String) -> Self { Error::Other(s) } diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 7284747424aa9..f0e3f72510c28 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -34,13 +34,18 @@ mod client; mod metrics; mod task_manager; -use std::{collections::HashMap, io, net::SocketAddr, pin::Pin, task::Poll}; +use std::{collections::HashMap, net::SocketAddr}; use codec::{Decode, Encode}; -use futures::{stream, Future, FutureExt, Stream, StreamExt}; +use futures::{channel::mpsc, FutureExt, StreamExt}; +use jsonrpsee::{core::Error as JsonRpseeError, RpcModule}; use log::{debug, error, warn}; +use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider}; use sc_network::PeerId; +use sc_network_common::{config::MultiaddrWithPeerId, service::NetworkBlock}; use sc_utils::mpsc::TracingUnboundedReceiver; +use sp_blockchain::HeaderMetadata; +use sp_consensus::SyncOracle; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header as HeaderT}, @@ -49,65 +54,62 @@ use sp_runtime::{ pub use self::{ builder::{ build_network, build_offchain_workers, new_client, new_db_backend, new_full_client, - new_full_parts, new_light_parts, spawn_tasks, BuildNetworkParams, KeystoreContainer, - NetworkStarter, NoopRpcExtensionBuilder, RpcExtensionBuilder, SpawnTasksParams, - TFullBackend, TFullCallExecutor, TFullClient, TLightBackend, TLightBackendWithHash, - TLightCallExecutor, TLightClient, TLightClientWithBackend, + new_full_parts, spawn_tasks, BuildNetworkParams, KeystoreContainer, NetworkStarter, + SpawnTasksParams, TFullBackend, TFullCallExecutor, TFullClient, }, client::{ClientConfig, LocalCallExecutor}, error::Error, }; pub use config::{ - BasePath, Configuration, DatabaseSource, KeepBlocks, PruningMode, Role, RpcMethods, TaskType, - TransactionStorageMode, + BasePath, BlocksPruning, Configuration, DatabaseSource, PruningMode, Role, RpcMethods, TaskType, }; pub use sc_chain_spec::{ ChainSpec, ChainType, Extension as ChainSpecExtension, GenericChainSpec, NoExtension, Properties, RuntimeGenesis, }; -use sc_client_api::{blockchain::HeaderBackend, BlockchainEvents}; + pub use sc_consensus::ImportQueue; pub use sc_executor::NativeExecutionDispatch; #[doc(hidden)] -pub use sc_network::config::{OnDemand, TransactionImport, TransactionImportFuture}; -pub use sc_rpc::Metadata as RpcMetadata; +pub use sc_network_transactions::config::{TransactionImport, TransactionImportFuture}; +pub use sc_rpc::{ + RandomIntegerSubscriptionId, RandomStringSubscriptionId, RpcSubscriptionIdProvider, +}; pub use sc_tracing::TracingReceiver; pub use sc_transaction_pool::Options as TransactionPoolOptions; pub use sc_transaction_pool_api::{error::IntoPoolError, InPoolTransaction, TransactionPool}; #[doc(hidden)] pub use std::{ops::Deref, result::Result, sync::Arc}; -pub use task_manager::{SpawnTaskHandle, TaskManager}; +pub use task_manager::{SpawnTaskHandle, TaskManager, DEFAULT_GROUP_NAME}; const DEFAULT_PROTOCOL_ID: &str = "sup"; /// RPC handlers that can perform RPC queries. #[derive(Clone)] -pub struct RpcHandlers( - Arc>, -); +pub struct RpcHandlers(Arc>); impl RpcHandlers { /// Starts an RPC query. /// - /// The query is passed as a string and must be a JSON text similar to what an HTTP client - /// would for example send. + /// The query is passed as a string and must be valid JSON-RPC request object. /// - /// Returns a `Future` that contains the optional response. + /// Returns a response and a stream if the call successful, fails if the + /// query could not be decoded as a JSON-RPC request object. /// - /// If the request subscribes you to events, the `Sender` in the `RpcSession` object is used to - /// send back spontaneous events. - pub fn rpc_query( + /// If the request subscribes you to events, the `stream` can be used to + /// retrieve the events. + pub async fn rpc_query( &self, - mem: &RpcSession, - request: &str, - ) -> Pin> + Send>> { - self.0.handle_request(request, mem.metadata.clone()).boxed() + json_query: &str, + ) -> Result<(String, mpsc::UnboundedReceiver), JsonRpseeError> { + self.0 + .raw_json_request(json_query) + .await + .map(|(method_res, recv)| (method_res.result, recv)) } - /// Provides access to the underlying `MetaIoHandler` - pub fn io_handler( - &self, - ) -> Arc> { + /// Provides access to the underlying `RpcModule` + pub fn handle(&self) -> Arc> { self.0.clone() } } @@ -137,11 +139,18 @@ pub struct PartialComponents + HeaderBackend, - H: sc_network::ExHashT, + C: BlockchainEvents + + HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, + H: sc_network_common::ExHashT, >( role: Role, - mut network: sc_network::NetworkWorker, + mut network: sc_network::NetworkWorker, client: Arc, mut rpc_rx: TracingUnboundedReceiver>, should_have_peers: bool, @@ -153,26 +162,7 @@ async fn build_network_future< let starting_block = client.info().best_number; // Stream of finalized blocks reported by the client. - let mut finality_notification_stream = { - let mut finality_notification_stream = client.finality_notification_stream().fuse(); - - // We tweak the `Stream` in order to merge together multiple items if they happen to be - // ready. This way, we only get the latest finalized block. - stream::poll_fn(move |cx| { - let mut last = None; - while let Poll::Ready(Some(item)) = - Pin::new(&mut finality_notification_stream).poll_next(cx) - { - last = Some(item); - } - if let Some(last) = last { - Poll::Ready(Some(last)) - } else { - Poll::Pending - } - }) - .fuse() - }; + let mut finality_notification_stream = client.finality_notification_stream().fuse(); loop { futures::select! { @@ -192,7 +182,7 @@ async fn build_network_future< if notification.is_new_best { network.service().new_best_block_imported( notification.hash, - notification.header.number().clone(), + *notification.header.number(), ); } } @@ -216,7 +206,7 @@ async fn build_network_future< let _ = sender.send(network.local_peer_id().to_base58()); }, sc_rpc::system::Request::LocalListenAddresses(sender) => { - let peer_id = network.local_peer_id().clone().into(); + let peer_id = (*network.local_peer_id()).into(); let p2p_proto_suffix = sc_network::multiaddr::Protocol::P2p(peer_id); let addresses = network.listen_addresses() .map(|addr| addr.clone().with(p2p_proto_suffix.clone()).to_string()) @@ -234,13 +224,20 @@ async fn build_network_future< ).collect()); } sc_rpc::system::Request::NetworkState(sender) => { - if let Some(network_state) = serde_json::to_value(&network.network_state()).ok() { + if let Ok(network_state) = serde_json::to_value(&network.network_state()) { let _ = sender.send(network_state); } } sc_rpc::system::Request::NetworkAddReservedPeer(peer_addr, sender) => { - let x = network.add_reserved_peer(peer_addr) - .map_err(sc_rpc::system::error::Error::MalformattedPeerArg); + let result = match MultiaddrWithPeerId::try_from(peer_addr) { + Ok(peer) => { + network.add_reserved_peer(peer) + }, + Err(err) => { + Err(err.to_string()) + }, + }; + let x = result.map_err(sc_rpc::system::error::Error::MalformattedPeerArg); let _ = sender.send(x); } sc_rpc::system::Request::NetworkRemoveReservedPeer(peer_id, sender) => { @@ -267,7 +264,6 @@ async fn build_network_future< let node_role = match role { Role::Authority { .. } => NodeRole::Authority, - Role::Light => NodeRole::LightClient, Role::Full => NodeRole::Full, }; @@ -276,10 +272,12 @@ async fn build_network_future< sc_rpc::system::Request::SyncState(sender) => { use sc_rpc::system::SyncState; + let best_number = client.info().best_number; + let _ = sender.send(SyncState { - starting_block: starting_block, - current_block: client.info().best_number, - highest_block: network.best_seen_block(), + starting_block, + current_block: best_number, + highest_block: network.best_seen_block().unwrap_or(best_number), }); } } @@ -295,75 +293,28 @@ async fn build_network_future< // Wrapper for HTTP and WS servers that makes sure they are properly shut down. mod waiting { - pub struct HttpServer(pub Option); - impl Drop for HttpServer { - fn drop(&mut self) { - if let Some(server) = self.0.take() { - server.close_handle().close(); - server.wait(); - } - } - } - - pub struct IpcServer(pub Option); - impl Drop for IpcServer { - fn drop(&mut self) { - if let Some(server) = self.0.take() { - server.close_handle().close(); - let _ = server.wait(); - } - } - } + pub struct Server(pub Option); - pub struct WsServer(pub Option); - impl Drop for WsServer { + impl Drop for Server { fn drop(&mut self) { if let Some(server) = self.0.take() { - server.close_handle().close(); - let _ = server.wait(); + // This doesn't not wait for the server to be stopped but fires the signal. + let _ = server.stop(); } } } } -/// Starts RPC servers that run in their own thread, and returns an opaque object that keeps them -/// alive. -fn start_rpc_servers< - H: FnMut( - sc_rpc::DenyUnsafe, - sc_rpc_server::RpcMiddleware, - ) -> Result, Error>, ->( +/// Starts RPC servers. +fn start_rpc_servers( config: &Configuration, - mut gen_handler: H, - rpc_metrics: Option, - server_metrics: sc_rpc_server::ServerMetrics, -) -> Result, Error> { - fn maybe_start_server( - address: Option, - mut start: F, - ) -> Result, Error> - where - F: FnMut(&SocketAddr) -> Result, - { - address - .map(|mut address| { - start(&address).or_else(|e| match e { - Error::Io(e) => match e.kind() { - io::ErrorKind::AddrInUse | io::ErrorKind::PermissionDenied => { - warn!("Unable to bind RPC server to {}. Trying random port.", address); - address.set_port(0); - start(&address) - }, - _ => Err(e.into()), - }, - e => Err(e), - }) - }) - .transpose() - } - - fn deny_unsafe(addr: &SocketAddr, methods: &RpcMethods) -> sc_rpc::DenyUnsafe { + gen_rpc_module: R, + rpc_id_provider: Option>, +) -> Result, error::Error> +where + R: Fn(sc_rpc::DenyUnsafe) -> Result, Error>, +{ + fn deny_unsafe(addr: SocketAddr, methods: &RpcMethods) -> sc_rpc::DenyUnsafe { let is_exposed_addr = !addr.ip().is_loopback(); match (is_exposed_addr, methods) { | (_, RpcMethods::Unsafe) | (false, RpcMethods::Auto) => sc_rpc::DenyUnsafe::No, @@ -371,90 +322,63 @@ fn start_rpc_servers< } } - let rpc_method_names = sc_rpc_server::method_names(|m| gen_handler(sc_rpc::DenyUnsafe::No, m))?; - Ok(Box::new(( - config - .rpc_ipc - .as_ref() - .map(|path| { - sc_rpc_server::start_ipc( - &*path, - gen_handler( - sc_rpc::DenyUnsafe::No, - sc_rpc_server::RpcMiddleware::new( - rpc_metrics.clone(), - rpc_method_names.clone(), - "ipc", - ), - )?, - server_metrics.clone(), - ) - .map_err(Error::from) - }) - .transpose()?, - maybe_start_server(config.rpc_http, |address| { - sc_rpc_server::start_http( - address, - config.rpc_cors.as_ref(), - gen_handler( - deny_unsafe(&address, &config.rpc_methods), - sc_rpc_server::RpcMiddleware::new( - rpc_metrics.clone(), - rpc_method_names.clone(), - "http", - ), - )?, - config.rpc_max_payload, - config.tokio_handle.clone(), - ) - .map_err(Error::from) - })? - .map(|s| waiting::HttpServer(Some(s))), - maybe_start_server(config.rpc_ws, |address| { - sc_rpc_server::start_ws( - address, - config.rpc_ws_max_connections, - config.rpc_cors.as_ref(), - gen_handler( - deny_unsafe(&address, &config.rpc_methods), - sc_rpc_server::RpcMiddleware::new( - rpc_metrics.clone(), - rpc_method_names.clone(), - "ws", - ), - )?, - config.rpc_max_payload, - server_metrics.clone(), - config.tokio_handle.clone(), - ) - .map_err(Error::from) - })? - .map(|s| waiting::WsServer(Some(s))), - ))) -} + let (max_request_size, ws_max_response_size, http_max_response_size) = + legacy_cli_parsing(config); -/// An RPC session. Used to perform in-memory RPC queries (ie. RPC queries that don't go through -/// the HTTP or WebSockets server). -#[derive(Clone)] -pub struct RpcSession { - metadata: sc_rpc::Metadata, -} + let random_port = |mut addr: SocketAddr| { + addr.set_port(0); + addr + }; -impl RpcSession { - /// Creates an RPC session. - /// - /// The `sender` is stored inside the `RpcSession` and is used to communicate spontaneous JSON - /// messages. - /// - /// The `RpcSession` must be kept alive in order to receive messages on the sender. - pub fn new(sender: futures::channel::mpsc::UnboundedSender) -> RpcSession { - RpcSession { metadata: sender.into() } + let ws_addr = config + .rpc_ws + .unwrap_or_else(|| "127.0.0.1:9944".parse().expect("valid sockaddr; qed")); + let ws_addr2 = random_port(ws_addr); + + let http_addr = config + .rpc_http + .unwrap_or_else(|| "127.0.0.1:9933".parse().expect("valid sockaddr; qed")); + let http_addr2 = random_port(http_addr); + + let metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?; + + let server_config = sc_rpc_server::WsConfig { + max_connections: config.rpc_ws_max_connections, + max_payload_in_mb: max_request_size, + max_payload_out_mb: ws_max_response_size, + max_subs_per_conn: config.rpc_max_subs_per_conn, + }; + + let http_fut = sc_rpc_server::start_http( + [http_addr, http_addr2], + config.rpc_cors.as_ref(), + max_request_size, + http_max_response_size, + metrics.clone(), + gen_rpc_module(deny_unsafe(http_addr, &config.rpc_methods))?, + config.tokio_handle.clone(), + ); + + let ws_fut = sc_rpc_server::start( + [ws_addr, ws_addr2], + config.rpc_cors.as_ref(), + server_config.clone(), + metrics.clone(), + gen_rpc_module(deny_unsafe(ws_addr, &config.rpc_methods))?, + config.tokio_handle.clone(), + rpc_id_provider, + ); + + match tokio::task::block_in_place(|| { + config.tokio_handle.block_on(futures::future::try_join(http_fut, ws_fut)) + }) { + Ok((http, ws)) => Ok(Box::new((waiting::Server(Some(http)), waiting::Server(Some(ws))))), + Err(e) => Err(Error::Application(e)), } } /// Transaction pool adapter. pub struct TransactionPoolAdapter { - imports_external_transactions: bool, pool: Arc

, client: Arc, } @@ -479,9 +403,16 @@ where .collect() } -impl sc_network::config::TransactionPool for TransactionPoolAdapter +impl sc_network_transactions::config::TransactionPool + for TransactionPoolAdapter where - C: sc_network::config::Client + Send + Sync, + C: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, Pool: 'static + TransactionPool, B: BlockT, H: std::hash::Hash + Eq + sp_runtime::traits::Member + sp_runtime::traits::MaybeSerialize, @@ -496,11 +427,6 @@ where } fn import(&self, transaction: B::Extrinsic) -> TransactionImportFuture { - if !self.imports_external_transactions { - debug!("Transaction rejected"); - Box::pin(futures::future::ready(TransactionImport::None)); - } - let encoded = transaction.encode(); let uxt = match Decode::decode(&mut &encoded[..]) { Ok(uxt) => uxt, @@ -528,7 +454,7 @@ where TransactionImport::Bad }, Err(e) => { - debug!("Error converting pool error: {:?}", e); + debug!("Error converting pool error: {}", e); // it is not bad at least, just some internal node logic error, so peer is // innocent. TransactionImport::KnownGood @@ -550,6 +476,51 @@ where } } +fn legacy_cli_parsing(config: &Configuration) -> (Option, Option, Option) { + let ws_max_response_size = match ( + config.ws_max_out_buffer_capacity, + config.rpc_max_response_size, + ) { + (Some(legacy_max), max) => { + eprintln!("DEPRECATED: `--ws_max_out_buffer_capacity` has been removed; use `rpc-max-response-size or rpc-max-request-size` instead"); + eprintln!("Setting WS `rpc-max-response-size` to `max(ws_max_out_buffer_capacity, rpc_max_response_size)`"); + Some(std::cmp::max(legacy_max, max.unwrap_or(0))) + }, + (None, Some(m)) => Some(m), + (None, None) => None, + }; + + let max_request_size = match (config.rpc_max_payload, config.rpc_max_request_size) { + (Some(legacy_max), max) => { + eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead"); + eprintln!( + "Setting `rpc-max-response-size` to `max(rpc_max_payload, rpc_max_request_size)`" + ); + Some(std::cmp::max(legacy_max, max.unwrap_or(0))) + }, + (None, Some(max)) => Some(max), + (None, None) => None, + }; + + let http_max_response_size = match (config.rpc_max_payload, config.rpc_max_response_size) { + (Some(legacy_max), max) => { + eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead"); + eprintln!( + "Setting HTTP `rpc-max-response-size` to `max(rpc_max_payload, rpc_max_response_size)`" + ); + Some(std::cmp::max(legacy_max, max.unwrap_or(0))) + }, + (None, Some(max)) => Some(max), + (None, None) => None, + }; + + if config.rpc_ipc.is_some() { + eprintln!("DEPRECATED: `--ipc-path` has no effect anymore IPC support has been removed"); + } + + (max_request_size, ws_max_response_size, http_max_response_size) +} + #[cfg(test)] mod tests { use super::*; @@ -576,7 +547,7 @@ mod tests { amount: 5, nonce: 0, from: AccountKeyring::Alice.into(), - to: Default::default(), + to: AccountKeyring::Bob.into(), } .into_signed_tx(); block_on(pool.submit_one(&BlockId::hash(best.hash()), source, transaction.clone())) diff --git a/client/service/src/metrics.rs b/client/service/src/metrics.rs index 4d3c6df92fee7..c83b3988f9fa3 100644 --- a/client/service/src/metrics.rs +++ b/client/service/src/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,13 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{convert::TryFrom, time::SystemTime}; +use std::time::SystemTime; use crate::config::Configuration; use futures_timer::Delay; use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; use sc_client_api::{ClientInfo, UsageProvider}; -use sc_network::{config::Role, NetworkService, NetworkStatus}; +use sc_network::config::Role; +use sc_network_common::service::{NetworkStatus, NetworkStatusProvider}; use sc_telemetry::{telemetry, TelemetryHandle, SUBSTRATE_INFO}; use sc_transaction_pool_api::{MaintainedTransactionPool, PoolStatus}; use sc_utils::metrics::register_globals; @@ -42,7 +43,6 @@ struct PrometheusMetrics { // I/O database_cache: Gauge, state_cache: Gauge, - state_db: GaugeVec, } impl PrometheusMetrics { @@ -55,18 +55,21 @@ impl PrometheusMetrics { register( Gauge::::with_opts( Opts::new( - "build_info", + "substrate_build_info", "A metric with a constant '1' value labeled by name, version", ) .const_label("name", name) .const_label("version", version), )?, - ®istry, + registry, )? .set(1); - register(Gauge::::new("node_roles", "The roles the node is running as")?, ®istry)? - .set(roles); + register( + Gauge::::new("substrate_node_roles", "The roles the node is running as")?, + registry, + )? + .set(roles); register_globals(registry)?; @@ -74,7 +77,7 @@ impl PrometheusMetrics { SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default(); register( Gauge::::new( - "process_start_time_seconds", + "substrate_process_start_time_seconds", "Number of seconds between the UNIX epoch and the moment the process started", )?, registry, @@ -85,20 +88,20 @@ impl PrometheusMetrics { // generic internals block_height: register( GaugeVec::new( - Opts::new("block_height", "Block height info of the chain"), + Opts::new("substrate_block_height", "Block height info of the chain"), &["status"], )?, registry, )?, number_leaves: register( - Gauge::new("number_leaves", "Number of known chain leaves (aka forks)")?, + Gauge::new("substrate_number_leaves", "Number of known chain leaves (aka forks)")?, registry, )?, ready_transactions_number: register( Gauge::new( - "ready_transactions_number", + "substrate_ready_transactions_number", "Number of transactions in the ready queue", )?, registry, @@ -106,18 +109,11 @@ impl PrometheusMetrics { // I/ O database_cache: register( - Gauge::new("database_cache_bytes", "RocksDB cache size in bytes")?, + Gauge::new("substrate_database_cache_bytes", "RocksDB cache size in bytes")?, registry, )?, state_cache: register( - Gauge::new("state_cache_bytes", "State cache size in bytes")?, - registry, - )?, - state_db: register( - GaugeVec::new( - Opts::new("state_db_cache_bytes", "State DB cache in bytes"), - &["subtype"], - )?, + Gauge::new("substrate_state_cache_bytes", "State cache size in bytes")?, registry, )?, }) @@ -157,7 +153,7 @@ impl MetricsService { ) -> Result { let role_bits = match config.role { Role::Full => 1u64, - Role::Light => 2u64, + // 2u64 used to represent light client role Role::Authority { .. } => 4u64, }; @@ -179,15 +175,16 @@ impl MetricsService { /// Returns a never-ending `Future` that performs the /// metric and telemetry updates with information from /// the given sources. - pub async fn run( + pub async fn run( mut self, client: Arc, transactions: Arc, - network: Arc::Hash>>, + network: TNet, ) where TBl: Block, TCl: ProvideRuntimeApi + UsageProvider, TExPool: MaintainedTransactionPool::Hash>, + TNet: NetworkStatusProvider, { let mut timer = Delay::new(Duration::from_secs(0)); let timer_interval = Duration::from_secs(5); @@ -249,18 +246,6 @@ impl MetricsService { if let Some(info) = info.usage.as_ref() { metrics.database_cache.set(info.memory.database_cache.as_bytes() as u64); metrics.state_cache.set(info.memory.state_cache.as_bytes() as u64); - - metrics - .state_db - .with_label_values(&["non_canonical"]) - .set(info.memory.state_db.non_canonical.as_bytes() as u64); - if let Some(pruning) = info.memory.state_db.pruning { - metrics.state_db.with_label_values(&["pruning"]).set(pruning.as_bytes() as u64); - } - metrics - .state_db - .with_label_values(&["pinned"]) - .set(info.memory.state_db.pinned.as_bytes() as u64); } } @@ -295,9 +280,10 @@ impl MetricsService { UniqueSaturatedInto::::unique_saturated_into(num) }); - if let Some(best_seen_block) = best_seen_block { - metrics.block_height.with_label_values(&["sync_target"]).set(best_seen_block); - } + metrics + .block_height + .with_label_values(&["sync_target"]) + .set(best_seen_block.unwrap_or(best_number)); } } } diff --git a/client/service/src/task_manager/mod.rs b/client/service/src/task_manager/mod.rs index c827aa71dac2c..49189dc21ce8d 100644 --- a/client/service/src/task_manager/mod.rs +++ b/client/service/src/task_manager/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,83 +21,119 @@ use crate::{config::TaskType, Error}; use exit_future::Signal; use futures::{ - future::{join_all, pending, select, try_join_all, BoxFuture, Either}, + future::{pending, select, try_join_all, BoxFuture, Either}, Future, FutureExt, StreamExt, }; -use log::debug; use prometheus_endpoint::{ exponential_buckets, register, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, U64, }; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use std::{panic, pin::Pin, result::Result}; -use tokio::{runtime::Handle, task::JoinHandle}; +use tokio::runtime::Handle; use tracing_futures::Instrument; mod prometheus_future; #[cfg(test)] mod tests; +/// Default task group name. +pub const DEFAULT_GROUP_NAME: &str = "default"; + +/// The name of a group a task belongs to. +/// +/// This name is passed belong-side the task name to the prometheus metrics and can be used +/// to group tasks. +pub enum GroupName { + /// Sets the group name to `default`. + Default, + /// Use the specifically given name as group name. + Specific(&'static str), +} + +impl From> for GroupName { + fn from(name: Option<&'static str>) -> Self { + match name { + Some(name) => Self::Specific(name), + None => Self::Default, + } + } +} + +impl From<&'static str> for GroupName { + fn from(name: &'static str) -> Self { + Self::Specific(name) + } +} + /// An handle for spawning tasks in the service. #[derive(Clone)] pub struct SpawnTaskHandle { on_exit: exit_future::Exit, tokio_handle: Handle, metrics: Option, - task_notifier: TracingUnboundedSender>, } impl SpawnTaskHandle { - /// Spawns the given task with the given name. + /// Spawns the given task with the given name and a group name. + /// If group is not specified `DEFAULT_GROUP_NAME` will be used. /// - /// Note that the `name` is a `&'static str`. The reason for this choice is that statistics - /// about this task are getting reported to the Prometheus endpoint (if enabled), and that - /// therefore the set of possible task names must be bounded. + /// Note that the `name` is a `&'static str`. The reason for this choice is that + /// statistics about this task are getting reported to the Prometheus endpoint (if enabled), and + /// that therefore the set of possible task names must be bounded. /// /// In other words, it would be a bad idea for someone to do for example /// `spawn(format!("{:?}", some_public_key))`. - pub fn spawn(&self, name: &'static str, task: impl Future + Send + 'static) { - self.spawn_inner(name, task, TaskType::Async) + pub fn spawn( + &self, + name: &'static str, + group: impl Into, + task: impl Future + Send + 'static, + ) { + self.spawn_inner(name, group, task, TaskType::Async) } /// Spawns the blocking task with the given name. See also `spawn`. pub fn spawn_blocking( &self, name: &'static str, + group: impl Into, task: impl Future + Send + 'static, ) { - self.spawn_inner(name, task, TaskType::Blocking) + self.spawn_inner(name, group, task, TaskType::Blocking) } /// Helper function that implements the spawning logic. See `spawn` and `spawn_blocking`. fn spawn_inner( &self, name: &'static str, + group: impl Into, task: impl Future + Send + 'static, task_type: TaskType, ) { - if self.task_notifier.is_closed() { - debug!("Attempt to spawn a new task has been prevented: {}", name); - return - } - let on_exit = self.on_exit.clone(); let metrics = self.metrics.clone(); + let group = match group.into() { + GroupName::Specific(var) => var, + // If no group is specified use default. + GroupName::Default => DEFAULT_GROUP_NAME, + }; + // Note that we increase the started counter here and not within the future. This way, // we could properly visualize on Prometheus situations where the spawning doesn't work. if let Some(metrics) = &self.metrics { - metrics.tasks_spawned.with_label_values(&[name]).inc(); + metrics.tasks_spawned.with_label_values(&[name, group]).inc(); // We do a dummy increase in order for the task to show up in metrics. - metrics.tasks_ended.with_label_values(&[name, "finished"]).inc_by(0); + metrics.tasks_ended.with_label_values(&[name, "finished", group]).inc_by(0); } let future = async move { if let Some(metrics) = metrics { // Add some wrappers around `task`. let task = { - let poll_duration = metrics.poll_duration.with_label_values(&[name]); - let poll_start = metrics.poll_start.with_label_values(&[name]); + let poll_duration = metrics.poll_duration.with_label_values(&[name, group]); + let poll_start = metrics.poll_start.with_label_values(&[name, group]); let inner = prometheus_future::with_poll_durations(poll_duration, poll_start, task); // The logic of `AssertUnwindSafe` here is ok considering that we throw @@ -108,15 +144,15 @@ impl SpawnTaskHandle { match select(on_exit, task).await { Either::Right((Err(payload), _)) => { - metrics.tasks_ended.with_label_values(&[name, "panic"]).inc(); + metrics.tasks_ended.with_label_values(&[name, "panic", group]).inc(); panic::resume_unwind(payload) }, Either::Right((Ok(()), _)) => { - metrics.tasks_ended.with_label_values(&[name, "finished"]).inc(); + metrics.tasks_ended.with_label_values(&[name, "finished", group]).inc(); }, Either::Left(((), _)) => { // The `on_exit` has triggered. - metrics.tasks_ended.with_label_values(&[name, "interrupted"]).inc(); + metrics.tasks_ended.with_label_values(&[name, "interrupted", group]).inc(); }, } } else { @@ -126,27 +162,37 @@ impl SpawnTaskHandle { } .in_current_span(); - let join_handle = match task_type { - TaskType::Async => self.tokio_handle.spawn(future), + match task_type { + TaskType::Async => { + self.tokio_handle.spawn(future); + }, TaskType::Blocking => { let handle = self.tokio_handle.clone(); self.tokio_handle.spawn_blocking(move || { handle.block_on(future); - }) + }); }, - }; - - let _ = self.task_notifier.unbounded_send(join_handle); + } } } impl sp_core::traits::SpawnNamed for SpawnTaskHandle { - fn spawn_blocking(&self, name: &'static str, future: BoxFuture<'static, ()>) { - self.spawn_blocking(name, future); + fn spawn_blocking( + &self, + name: &'static str, + group: Option<&'static str>, + future: BoxFuture<'static, ()>, + ) { + self.spawn_inner(name, group, future, TaskType::Blocking) } - fn spawn(&self, name: &'static str, future: BoxFuture<'static, ()>) { - self.spawn(name, future); + fn spawn( + &self, + name: &'static str, + group: Option<&'static str>, + future: BoxFuture<'static, ()>, + ) { + self.spawn_inner(name, group, future, TaskType::Async) } } @@ -172,8 +218,13 @@ impl SpawnEssentialTaskHandle { /// Spawns the given task with the given name. /// /// See also [`SpawnTaskHandle::spawn`]. - pub fn spawn(&self, name: &'static str, task: impl Future + Send + 'static) { - self.spawn_inner(name, task, TaskType::Async) + pub fn spawn( + &self, + name: &'static str, + group: impl Into, + task: impl Future + Send + 'static, + ) { + self.spawn_inner(name, group, task, TaskType::Async) } /// Spawns the blocking task with the given name. @@ -182,14 +233,16 @@ impl SpawnEssentialTaskHandle { pub fn spawn_blocking( &self, name: &'static str, + group: impl Into, task: impl Future + Send + 'static, ) { - self.spawn_inner(name, task, TaskType::Blocking) + self.spawn_inner(name, group, task, TaskType::Blocking) } fn spawn_inner( &self, name: &'static str, + group: impl Into, task: impl Future + Send + 'static, task_type: TaskType, ) { @@ -199,17 +252,27 @@ impl SpawnEssentialTaskHandle { let _ = essential_failed.close_channel(); }); - let _ = self.inner.spawn_inner(name, essential_task, task_type); + let _ = self.inner.spawn_inner(name, group, essential_task, task_type); } } impl sp_core::traits::SpawnEssentialNamed for SpawnEssentialTaskHandle { - fn spawn_essential_blocking(&self, name: &'static str, future: BoxFuture<'static, ()>) { - self.spawn_blocking(name, future); + fn spawn_essential_blocking( + &self, + name: &'static str, + group: Option<&'static str>, + future: BoxFuture<'static, ()>, + ) { + self.spawn_blocking(name, group, future); } - fn spawn_essential(&self, name: &'static str, future: BoxFuture<'static, ()>) { - self.spawn(name, future); + fn spawn_essential( + &self, + name: &'static str, + group: Option<&'static str>, + future: BoxFuture<'static, ()>, + ) { + self.spawn(name, group, future); } } @@ -218,8 +281,8 @@ pub struct TaskManager { /// A future that resolves when the service has exited, this is useful to /// make sure any internally spawned futures stop when the service does. on_exit: exit_future::Exit, - /// A signal that makes the exit future above resolve, fired on service drop. - signal: Option, + /// A signal that makes the exit future above resolve, fired on drop. + _signal: Signal, /// Tokio runtime handle that is used to spawn futures. tokio_handle: Handle, /// Prometheus metric where to report the polling times. @@ -231,10 +294,6 @@ pub struct TaskManager { essential_failed_rx: TracingUnboundedReceiver<()>, /// Things to keep alive until the task manager is dropped. keep_alive: Box, - /// A sender to a stream of background tasks. This is used for the completion future. - task_notifier: TracingUnboundedSender>, - /// This future will complete when all the tasks are joined and the stream is closed. - completion_future: JoinHandle<()>, /// A list of other `TaskManager`'s to terminate and gracefully shutdown when the parent /// terminates and gracefully shutdown. Also ends the parent `future()` if a child's essential /// task fails. @@ -255,25 +314,14 @@ impl TaskManager { let metrics = prometheus_registry.map(Metrics::register).transpose()?; - let (task_notifier, background_tasks) = tracing_unbounded("mpsc_background_tasks"); - // NOTE: for_each_concurrent will await on all the JoinHandle futures at the same time. It - // is possible to limit this but it's actually better for the memory foot print to await - // them all to not accumulate anything on that stream. - let completion_future = - tokio_handle.spawn(background_tasks.for_each_concurrent(None, |x| async move { - let _ = x.await; - })); - Ok(Self { on_exit, - signal: Some(signal), + _signal: signal, tokio_handle, metrics, essential_failed_tx, essential_failed_rx, keep_alive: Box::new(()), - task_notifier, - completion_future, children: Vec::new(), }) } @@ -284,7 +332,6 @@ impl TaskManager { on_exit: self.on_exit.clone(), tokio_handle: self.tokio_handle.clone(), metrics: self.metrics.clone(), - task_notifier: self.task_notifier.clone(), } } @@ -293,36 +340,12 @@ impl TaskManager { SpawnEssentialTaskHandle::new(self.essential_failed_tx.clone(), self.spawn_handle()) } - /// Send the signal for termination, prevent new tasks to be created, await for all the existing - /// tasks to be finished and drop the object. You can consider this as an async drop. - /// - /// It's always better to call and await this function before exiting the process as background - /// tasks may be running in the background. If the process exit and the background tasks are not - /// cancelled, this will lead to objects not getting dropped properly. - /// - /// This is an issue in some cases as some of our dependencies do require that we drop all the - /// objects properly otherwise it triggers a SIGABRT on exit. - pub fn clean_shutdown(mut self) -> Pin + Send>> { - self.terminate(); - let children_shutdowns = self.children.into_iter().map(|x| x.clean_shutdown()); - let keep_alive = self.keep_alive; - let completion_future = self.completion_future; - - Box::pin(async move { - join_all(children_shutdowns).await; - let _ = completion_future.await; - - let _ = keep_alive; - }) - } - /// Return a future that will end with success if the signal to terminate was sent /// (`self.terminate()`) or with an error if an essential task fails. /// /// # Warning /// - /// This function will not wait until the end of the remaining task. You must call and await - /// `clean_shutdown()` after this. + /// This function will not wait until the end of the remaining task. pub fn future<'a>( &'a mut self, ) -> Pin> + Send + 'a>> { @@ -347,18 +370,6 @@ impl TaskManager { }) } - /// Signal to terminate all the running tasks. - pub fn terminate(&mut self) { - if let Some(signal) = self.signal.take() { - let _ = signal.fire(); - // NOTE: this will prevent new tasks to be spawned - self.task_notifier.close_channel(); - for child in self.children.iter_mut() { - child.terminate(); - } - } - } - /// Set what the task manager should keep alive, can be called multiple times. pub fn keep_alive(&mut self, to_keep_alive: T) { // allows this fn to safely called multiple times. @@ -390,34 +401,34 @@ impl Metrics { poll_duration: register(HistogramVec::new( HistogramOpts { common_opts: Opts::new( - "tasks_polling_duration", + "substrate_tasks_polling_duration", "Duration in seconds of each invocation of Future::poll" ), buckets: exponential_buckets(0.001, 4.0, 9) .expect("function parameters are constant and always valid; qed"), }, - &["task_name"] + &["task_name", "task_group"] )?, registry)?, poll_start: register(CounterVec::new( Opts::new( - "tasks_polling_started_total", + "substrate_tasks_polling_started_total", "Total number of times we started invoking Future::poll" ), - &["task_name"] + &["task_name", "task_group"] )?, registry)?, tasks_spawned: register(CounterVec::new( Opts::new( - "tasks_spawned_total", + "substrate_tasks_spawned_total", "Total number of tasks that have been spawned on the Service" ), - &["task_name"] + &["task_name", "task_group"] )?, registry)?, tasks_ended: register(CounterVec::new( Opts::new( - "tasks_ended_total", + "substrate_tasks_ended_total", "Total number of tasks for which Future::poll has returned Ready(()) or panicked" ), - &["task_name", "reason"] + &["task_name", "reason", "task_group"] )?, registry)?, }) } diff --git a/client/service/src/task_manager/prometheus_future.rs b/client/service/src/task_manager/prometheus_future.rs index 43a76a0f596c2..88296a67336fe 100644 --- a/client/service/src/task_manager/prometheus_future.rs +++ b/client/service/src/task_manager/prometheus_future.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/service/src/task_manager/tests.rs b/client/service/src/task_manager/tests.rs index 291d71ebaf03b..323110a4e1b0c 100644 --- a/client/service/src/task_manager/tests.rs +++ b/client/service/src/task_manager/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -90,223 +90,163 @@ fn new_task_manager(tokio_handle: tokio::runtime::Handle) -> TaskManager { #[test] fn ensure_tasks_are_awaited_on_shutdown() { - let runtime = tokio::runtime::Runtime::new().unwrap(); - let handle = runtime.handle().clone(); - - let task_manager = new_task_manager(handle); - let spawn_handle = task_manager.spawn_handle(); let drop_tester = DropTester::new(); - spawn_handle.spawn("task1", run_background_task(drop_tester.new_ref())); - spawn_handle.spawn("task2", run_background_task(drop_tester.new_ref())); - assert_eq!(drop_tester, 2); - // allow the tasks to even start - runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); - assert_eq!(drop_tester, 2); - runtime.block_on(task_manager.clean_shutdown()); + { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let handle = runtime.handle().clone(); + + let task_manager = new_task_manager(handle); + let spawn_handle = task_manager.spawn_handle(); + spawn_handle.spawn("task1", None, run_background_task(drop_tester.new_ref())); + spawn_handle.spawn("task2", None, run_background_task(drop_tester.new_ref())); + assert_eq!(drop_tester, 2); + // allow the tasks to even start + runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); + assert_eq!(drop_tester, 2); + } drop_tester.wait_on_drop(); } #[test] fn ensure_keep_alive_during_shutdown() { - let runtime = tokio::runtime::Runtime::new().unwrap(); - let handle = runtime.handle().clone(); - - let mut task_manager = new_task_manager(handle); - let spawn_handle = task_manager.spawn_handle(); let drop_tester = DropTester::new(); - task_manager.keep_alive(drop_tester.new_ref()); - spawn_handle.spawn("task1", run_background_task(())); - assert_eq!(drop_tester, 1); - // allow the tasks to even start - runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); - assert_eq!(drop_tester, 1); - runtime.block_on(task_manager.clean_shutdown()); + { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let handle = runtime.handle().clone(); + + let mut task_manager = new_task_manager(handle); + let spawn_handle = task_manager.spawn_handle(); + task_manager.keep_alive(drop_tester.new_ref()); + spawn_handle.spawn("task1", None, run_background_task(())); + assert_eq!(drop_tester, 1); + // allow the tasks to even start + runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); + assert_eq!(drop_tester, 1); + } drop_tester.wait_on_drop(); } #[test] fn ensure_blocking_futures_are_awaited_on_shutdown() { - let runtime = tokio::runtime::Runtime::new().unwrap(); - let handle = runtime.handle().clone(); - - let task_manager = new_task_manager(handle); - let spawn_handle = task_manager.spawn_handle(); - let drop_tester = DropTester::new(); - spawn_handle.spawn( - "task1", - run_background_task_blocking(Duration::from_secs(3), drop_tester.new_ref()), - ); - spawn_handle.spawn( - "task2", - run_background_task_blocking(Duration::from_secs(3), drop_tester.new_ref()), - ); - assert_eq!(drop_tester, 2); - // allow the tasks to even start - runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); - assert_eq!(drop_tester, 2); - runtime.block_on(task_manager.clean_shutdown()); - assert_eq!(drop_tester, 0); -} - -#[test] -fn ensure_no_task_can_be_spawn_after_terminate() { - let runtime = tokio::runtime::Runtime::new().unwrap(); - let handle = runtime.handle().clone(); - - let mut task_manager = new_task_manager(handle); - let spawn_handle = task_manager.spawn_handle(); - let drop_tester = DropTester::new(); - spawn_handle.spawn("task1", run_background_task(drop_tester.new_ref())); - spawn_handle.spawn("task2", run_background_task(drop_tester.new_ref())); - assert_eq!(drop_tester, 2); - // allow the tasks to even start - runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); - assert_eq!(drop_tester, 2); - task_manager.terminate(); - spawn_handle.spawn("task3", run_background_task(drop_tester.new_ref())); - runtime.block_on(task_manager.clean_shutdown()); - drop_tester.wait_on_drop(); -} - -#[test] -fn ensure_task_manager_future_ends_when_task_manager_terminated() { - let runtime = tokio::runtime::Runtime::new().unwrap(); - let handle = runtime.handle().clone(); - - let mut task_manager = new_task_manager(handle); - let spawn_handle = task_manager.spawn_handle(); let drop_tester = DropTester::new(); - spawn_handle.spawn("task1", run_background_task(drop_tester.new_ref())); - spawn_handle.spawn("task2", run_background_task(drop_tester.new_ref())); - assert_eq!(drop_tester, 2); - // allow the tasks to even start - runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); - assert_eq!(drop_tester, 2); - task_manager.terminate(); - runtime.block_on(task_manager.future()).expect("future has ended without error"); - runtime.block_on(task_manager.clean_shutdown()); + { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let handle = runtime.handle().clone(); + + let task_manager = new_task_manager(handle); + let spawn_handle = task_manager.spawn_handle(); + spawn_handle.spawn( + "task1", + None, + run_background_task_blocking(Duration::from_secs(3), drop_tester.new_ref()), + ); + spawn_handle.spawn( + "task2", + None, + run_background_task_blocking(Duration::from_secs(3), drop_tester.new_ref()), + ); + assert_eq!(drop_tester, 2); + // allow the tasks to even start + runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); + assert_eq!(drop_tester, 2); + } assert_eq!(drop_tester, 0); } #[test] fn ensure_task_manager_future_ends_with_error_when_essential_task_fails() { - let runtime = tokio::runtime::Runtime::new().unwrap(); - let handle = runtime.handle().clone(); - - let mut task_manager = new_task_manager(handle); - let spawn_handle = task_manager.spawn_handle(); - let spawn_essential_handle = task_manager.spawn_essential_handle(); let drop_tester = DropTester::new(); - spawn_handle.spawn("task1", run_background_task(drop_tester.new_ref())); - spawn_handle.spawn("task2", run_background_task(drop_tester.new_ref())); - assert_eq!(drop_tester, 2); - // allow the tasks to even start - runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); - assert_eq!(drop_tester, 2); - spawn_essential_handle.spawn("task3", async { panic!("task failed") }); - runtime - .block_on(task_manager.future()) - .expect_err("future()'s Result must be Err"); - assert_eq!(drop_tester, 2); - runtime.block_on(task_manager.clean_shutdown()); - drop_tester.wait_on_drop(); -} - -#[test] -fn ensure_children_tasks_ends_when_task_manager_terminated() { - let runtime = tokio::runtime::Runtime::new().unwrap(); - let handle = runtime.handle().clone(); - - let mut task_manager = new_task_manager(handle.clone()); - let child_1 = new_task_manager(handle.clone()); - let spawn_handle_child_1 = child_1.spawn_handle(); - let child_2 = new_task_manager(handle.clone()); - let spawn_handle_child_2 = child_2.spawn_handle(); - task_manager.add_child(child_1); - task_manager.add_child(child_2); - let spawn_handle = task_manager.spawn_handle(); - let drop_tester = DropTester::new(); - spawn_handle.spawn("task1", run_background_task(drop_tester.new_ref())); - spawn_handle.spawn("task2", run_background_task(drop_tester.new_ref())); - spawn_handle_child_1.spawn("task3", run_background_task(drop_tester.new_ref())); - spawn_handle_child_2.spawn("task4", run_background_task(drop_tester.new_ref())); - assert_eq!(drop_tester, 4); - // allow the tasks to even start - runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); - assert_eq!(drop_tester, 4); - task_manager.terminate(); - runtime.block_on(task_manager.future()).expect("future has ended without error"); - runtime.block_on(task_manager.clean_shutdown()); + { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let handle = runtime.handle().clone(); + + let mut task_manager = new_task_manager(handle); + let spawn_handle = task_manager.spawn_handle(); + let spawn_essential_handle = task_manager.spawn_essential_handle(); + spawn_handle.spawn("task1", None, run_background_task(drop_tester.new_ref())); + spawn_handle.spawn("task2", None, run_background_task(drop_tester.new_ref())); + assert_eq!(drop_tester, 2); + // allow the tasks to even start + runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); + assert_eq!(drop_tester, 2); + spawn_essential_handle.spawn("task3", None, async { panic!("task failed") }); + runtime + .block_on(task_manager.future()) + .expect_err("future()'s Result must be Err"); + assert_eq!(drop_tester, 2); + } drop_tester.wait_on_drop(); } #[test] fn ensure_task_manager_future_ends_with_error_when_childs_essential_task_fails() { - let runtime = tokio::runtime::Runtime::new().unwrap(); - let handle = runtime.handle().clone(); - - let mut task_manager = new_task_manager(handle.clone()); - let child_1 = new_task_manager(handle.clone()); - let spawn_handle_child_1 = child_1.spawn_handle(); - let spawn_essential_handle_child_1 = child_1.spawn_essential_handle(); - let child_2 = new_task_manager(handle.clone()); - let spawn_handle_child_2 = child_2.spawn_handle(); - task_manager.add_child(child_1); - task_manager.add_child(child_2); - let spawn_handle = task_manager.spawn_handle(); let drop_tester = DropTester::new(); - spawn_handle.spawn("task1", run_background_task(drop_tester.new_ref())); - spawn_handle.spawn("task2", run_background_task(drop_tester.new_ref())); - spawn_handle_child_1.spawn("task3", run_background_task(drop_tester.new_ref())); - spawn_handle_child_2.spawn("task4", run_background_task(drop_tester.new_ref())); - assert_eq!(drop_tester, 4); - // allow the tasks to even start - runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); - assert_eq!(drop_tester, 4); - spawn_essential_handle_child_1.spawn("task5", async { panic!("task failed") }); - runtime - .block_on(task_manager.future()) - .expect_err("future()'s Result must be Err"); - assert_eq!(drop_tester, 4); - runtime.block_on(task_manager.clean_shutdown()); + { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let handle = runtime.handle().clone(); + + let mut task_manager = new_task_manager(handle.clone()); + let child_1 = new_task_manager(handle.clone()); + let spawn_handle_child_1 = child_1.spawn_handle(); + let spawn_essential_handle_child_1 = child_1.spawn_essential_handle(); + let child_2 = new_task_manager(handle.clone()); + let spawn_handle_child_2 = child_2.spawn_handle(); + task_manager.add_child(child_1); + task_manager.add_child(child_2); + let spawn_handle = task_manager.spawn_handle(); + spawn_handle.spawn("task1", None, run_background_task(drop_tester.new_ref())); + spawn_handle.spawn("task2", None, run_background_task(drop_tester.new_ref())); + spawn_handle_child_1.spawn("task3", None, run_background_task(drop_tester.new_ref())); + spawn_handle_child_2.spawn("task4", None, run_background_task(drop_tester.new_ref())); + assert_eq!(drop_tester, 4); + // allow the tasks to even start + runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); + assert_eq!(drop_tester, 4); + spawn_essential_handle_child_1.spawn("task5", None, async { panic!("task failed") }); + runtime + .block_on(task_manager.future()) + .expect_err("future()'s Result must be Err"); + assert_eq!(drop_tester, 4); + } drop_tester.wait_on_drop(); } #[test] fn ensure_task_manager_future_continues_when_childs_not_essential_task_fails() { - let runtime = tokio::runtime::Runtime::new().unwrap(); - let handle = runtime.handle().clone(); - - let mut task_manager = new_task_manager(handle.clone()); - let child_1 = new_task_manager(handle.clone()); - let spawn_handle_child_1 = child_1.spawn_handle(); - let child_2 = new_task_manager(handle.clone()); - let spawn_handle_child_2 = child_2.spawn_handle(); - task_manager.add_child(child_1); - task_manager.add_child(child_2); - let spawn_handle = task_manager.spawn_handle(); let drop_tester = DropTester::new(); - spawn_handle.spawn("task1", run_background_task(drop_tester.new_ref())); - spawn_handle.spawn("task2", run_background_task(drop_tester.new_ref())); - spawn_handle_child_1.spawn("task3", run_background_task(drop_tester.new_ref())); - spawn_handle_child_2.spawn("task4", run_background_task(drop_tester.new_ref())); - assert_eq!(drop_tester, 4); - // allow the tasks to even start - runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); - assert_eq!(drop_tester, 4); - spawn_handle_child_1.spawn("task5", async { panic!("task failed") }); - runtime.block_on(async { - let t1 = task_manager.future().fuse(); - let t2 = tokio::time::sleep(Duration::from_secs(3)).fuse(); - - pin_mut!(t1, t2); - - select! { - res = t1 => panic!("task should not have stopped: {:?}", res), - _ = t2 => {}, - } - }); - assert_eq!(drop_tester, 4); - runtime.block_on(task_manager.clean_shutdown()); + { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let handle = runtime.handle().clone(); + + let mut task_manager = new_task_manager(handle.clone()); + let child_1 = new_task_manager(handle.clone()); + let spawn_handle_child_1 = child_1.spawn_handle(); + let child_2 = new_task_manager(handle.clone()); + let spawn_handle_child_2 = child_2.spawn_handle(); + task_manager.add_child(child_1); + task_manager.add_child(child_2); + let spawn_handle = task_manager.spawn_handle(); + spawn_handle.spawn("task1", None, run_background_task(drop_tester.new_ref())); + spawn_handle.spawn("task2", None, run_background_task(drop_tester.new_ref())); + spawn_handle_child_1.spawn("task3", None, run_background_task(drop_tester.new_ref())); + spawn_handle_child_2.spawn("task4", None, run_background_task(drop_tester.new_ref())); + assert_eq!(drop_tester, 4); + // allow the tasks to even start + runtime.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }); + assert_eq!(drop_tester, 4); + spawn_handle_child_1.spawn("task5", None, async { panic!("task failed") }); + runtime.block_on(async { + let t1 = task_manager.future().fuse(); + let t2 = tokio::time::sleep(Duration::from_secs(3)).fuse(); + + pin_mut!(t1, t2); + + select! { + res = t1 => panic!("task should not have stopped: {:?}", res), + _ = t2 => {}, + } + }); + assert_eq!(drop_tester, 4); + } drop_tester.wait_on_drop(); } diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index 85a6dcc9e8b29..46ba1b33931ac 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -2,43 +2,44 @@ name = "sc-service-test" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" publish = false -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -hex-literal = "0.3.1" -tempfile = "3.1.0" -tokio = { version = "1.10.0", features = ["time"] } -log = "0.4.8" +array-bytes = "4.1" fdlimit = "0.2.1" -parking_lot = "0.11.1" -sc-light = { version = "4.0.0-dev", path = "../../light" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-state-machine = { version = "0.10.0-dev", path = "../../../primitives/state-machine" } -sp-externalities = { version = "0.10.0-dev", path = "../../../primitives/externalities" } -sp-trie = { version = "4.0.0-dev", path = "../../../primitives/trie" } -sp-storage = { version = "4.0.0-dev", path = "../../../primitives/storage" } +futures = "0.3.21" +log = "0.4.17" +parity-scale-codec = "3.0.0" +parking_lot = "0.12.1" +tempfile = "3.1.0" +tokio = { version = "1.22.0", features = ["time"] } +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../../db" } -futures = "0.3.16" -sc-service = { version = "0.10.0-dev", features = ["test-helpers"], path = "../../service" } -sc-network = { version = "0.10.0-dev", path = "../../network" } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } +sc-executor = { version = "0.10.0-dev", path = "../../executor" } +sc-network = { version = "0.10.0-dev", path = "../../network" } +sc-network-common = { version = "0.10.0-dev", path = "../../network/common" } +sc-service = { version = "0.10.0-dev", features = ["test-helpers"], path = "../../service" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-externalities = { version = "0.13.0", path = "../../../primitives/externalities" } +sp-panic-handler = { version = "5.0.0", path = "../../../primitives/panic-handler" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } +sp-storage = { version = "7.0.0", path = "../../../primitives/storage" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +sp-trie = { version = "7.0.0", path = "../../../primitives/trie" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } -sc-executor = { version = "0.10.0-dev", path = "../../executor" } -sp-panic-handler = { version = "3.0.0", path = "../../../primitives/panic-handler" } -parity-scale-codec = "2.0.0" -sp-tracing = { version = "4.0.0-dev", path = "../../../primitives/tracing" } diff --git a/client/service/test/src/client/db.rs b/client/service/test/src/client/db.rs index 5278c9a13a4d7..5c1315fc870cd 100644 --- a/client/service/test/src/client/db.rs +++ b/client/service/test/src/client/db.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/service/test/src/client/light.rs b/client/service/test/src/client/light.rs deleted file mode 100644 index fb9566d208f76..0000000000000 --- a/client/service/test/src/client/light.rs +++ /dev/null @@ -1,981 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use super::prepare_client_with_key_changes; -use parity_scale_codec::{Decode, Encode}; -use parking_lot::Mutex; -use sc_block_builder::BlockBuilderProvider; -use sc_client_api::{ - backend::NewBlockState, - blockchain::Info, - cht, - in_mem::{Backend as InMemBackend, Blockchain as InMemoryBlockchain}, - AuxStore, Backend as ClientBackend, BlockBackend, BlockImportOperation, CallExecutor, - ChangesProof, ExecutionStrategy, FetchChecker, ProofProvider, ProvideChtRoots, - RemoteBodyRequest, RemoteCallRequest, RemoteChangesRequest, RemoteHeaderRequest, - RemoteReadChildRequest, RemoteReadRequest, Storage, StorageProof, StorageProvider, -}; -use sc_executor::{NativeElseWasmExecutor, RuntimeVersion, WasmExecutionMethod}; -use sc_light::{ - backend::{Backend, GenesisOrUnavailableState}, - blockchain::{Blockchain, BlockchainCache}, - call_executor::{check_execution_proof, GenesisCallExecutor}, - fetcher::LightDataChecker, -}; -use sp_api::{ProofRecorder, StorageTransactionCache}; -use sp_blockchain::{ - well_known_cache_keys, BlockStatus, CachedHeaderMetadata, Error as ClientError, HeaderBackend, - Result as ClientResult, -}; -use sp_consensus::BlockOrigin; -use sp_core::{testing::TaskExecutor, NativeOrEncoded, H256}; -use sp_externalities::Extensions; -use sp_runtime::{ - generic::BlockId, - traits::{BlakeTwo256, Block as _, Header as HeaderT, NumberFor}, - Digest, Justifications, -}; -use sp_state_machine::{ExecutionManager, OverlayedChanges}; -use std::{cell::RefCell, collections::HashMap, panic::UnwindSafe, sync::Arc}; -use substrate_test_runtime_client::{ - runtime::{self, Block, Extrinsic, Hash, Header}, - AccountKeyring, ClientBlockImportExt, TestClient, -}; - -use sp_core::{ - blake2_256, - storage::{well_known_keys, ChildInfo, StorageKey}, - ChangesTrieConfiguration, -}; -use sp_state_machine::Backend as _; - -pub type DummyBlockchain = Blockchain; - -pub struct DummyStorage { - pub changes_tries_cht_roots: HashMap, - pub aux_store: Mutex, Vec>>, -} - -impl DummyStorage { - pub fn new() -> Self { - DummyStorage { - changes_tries_cht_roots: HashMap::new(), - aux_store: Mutex::new(HashMap::new()), - } - } -} - -impl sp_blockchain::HeaderBackend for DummyStorage { - fn header(&self, _id: BlockId) -> ClientResult> { - Err(ClientError::Backend("Test error".into())) - } - - fn info(&self) -> Info { - panic!("Test error") - } - - fn status(&self, _id: BlockId) -> ClientResult { - Err(ClientError::Backend("Test error".into())) - } - - fn number(&self, hash: Hash) -> ClientResult>> { - if hash == Default::default() { - Ok(Some(Default::default())) - } else { - Err(ClientError::Backend("Test error".into())) - } - } - - fn hash(&self, number: u64) -> ClientResult> { - if number == 0 { - Ok(Some(Default::default())) - } else { - Err(ClientError::Backend("Test error".into())) - } - } -} - -impl sp_blockchain::HeaderMetadata for DummyStorage { - type Error = ClientError; - - fn header_metadata(&self, hash: Hash) -> Result, Self::Error> { - self.header(BlockId::hash(hash))? - .map(|header| CachedHeaderMetadata::from(&header)) - .ok_or(ClientError::UnknownBlock("header not found".to_owned())) - } - fn insert_header_metadata(&self, _hash: Hash, _metadata: CachedHeaderMetadata) {} - fn remove_header_metadata(&self, _hash: Hash) {} -} - -impl AuxStore for DummyStorage { - fn insert_aux< - 'a, - 'b: 'a, - 'c: 'a, - I: IntoIterator, - D: IntoIterator, - >( - &self, - insert: I, - _delete: D, - ) -> ClientResult<()> { - for (k, v) in insert.into_iter() { - self.aux_store.lock().insert(k.to_vec(), v.to_vec()); - } - Ok(()) - } - - fn get_aux(&self, key: &[u8]) -> ClientResult>> { - Ok(self.aux_store.lock().get(key).cloned()) - } -} - -impl Storage for DummyStorage { - fn import_header( - &self, - _header: Header, - _cache: HashMap>, - _state: NewBlockState, - _aux_ops: Vec<(Vec, Option>)>, - ) -> ClientResult<()> { - Ok(()) - } - - fn set_head(&self, _block: BlockId) -> ClientResult<()> { - Err(ClientError::Backend("Test error".into())) - } - - fn finalize_header(&self, _block: BlockId) -> ClientResult<()> { - Err(ClientError::Backend("Test error".into())) - } - - fn last_finalized(&self) -> ClientResult { - Err(ClientError::Backend("Test error".into())) - } - - fn cache(&self) -> Option>> { - None - } - - fn usage_info(&self) -> Option { - None - } -} - -impl ProvideChtRoots for DummyStorage { - fn header_cht_root(&self, _cht_size: u64, _block: u64) -> ClientResult> { - Err(ClientError::Backend("Test error".into())) - } - - fn changes_trie_cht_root(&self, cht_size: u64, block: u64) -> ClientResult> { - cht::block_to_cht_number(cht_size, block) - .and_then(|cht_num| self.changes_tries_cht_roots.get(&cht_num)) - .cloned() - .ok_or_else(|| { - ClientError::Backend(format!("Test error: CHT for block #{} not found", block)) - .into() - }) - .map(Some) - } -} - -struct DummyCallExecutor; - -impl CallExecutor for DummyCallExecutor { - type Error = ClientError; - - type Backend = substrate_test_runtime_client::Backend; - - fn call( - &self, - _id: &BlockId, - _method: &str, - _call_data: &[u8], - _strategy: ExecutionStrategy, - _extensions: Option, - ) -> Result, ClientError> { - Ok(vec![42]) - } - - fn contextual_call< - EM: Fn( - Result, Self::Error>, - Result, Self::Error>, - ) -> Result, Self::Error>, - R: Encode + Decode + PartialEq, - NC: FnOnce() -> Result + UnwindSafe, - >( - &self, - _at: &BlockId, - _method: &str, - _call_data: &[u8], - _changes: &RefCell, - _storage_transaction_cache: Option< - &RefCell< - StorageTransactionCache< - Block, - >::State, - >, - >, - >, - _execution_manager: ExecutionManager, - _native_call: Option, - _proof_recorder: &Option>, - _extensions: Option, - ) -> ClientResult> - where - ExecutionManager: Clone, - { - unreachable!() - } - - fn runtime_version(&self, _id: &BlockId) -> Result { - unreachable!() - } - - fn prove_execution( - &self, - _: &BlockId, - _: &str, - _: &[u8], - ) -> Result<(Vec, StorageProof), ClientError> { - unreachable!() - } -} - -fn local_executor() -> NativeElseWasmExecutor -{ - NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8) -} - -#[test] -fn local_state_is_created_when_genesis_state_is_available() { - let def = Default::default(); - let header0 = - substrate_test_runtime_client::runtime::Header::new(0, def, def, def, Default::default()); - - let backend: Backend<_, BlakeTwo256> = - Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new()))); - let mut op = backend.begin_operation().unwrap(); - op.set_block_data(header0, None, None, None, NewBlockState::Final).unwrap(); - op.set_genesis_state(Default::default(), true).unwrap(); - backend.commit_operation(op).unwrap(); - - match backend.state_at(BlockId::Number(0)).unwrap() { - GenesisOrUnavailableState::Genesis(_) => (), - _ => panic!("unexpected state"), - } -} - -#[test] -fn unavailable_state_is_created_when_genesis_state_is_unavailable() { - let backend: Backend<_, BlakeTwo256> = - Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new()))); - - match backend.state_at(BlockId::Number(0)).unwrap() { - GenesisOrUnavailableState::Unavailable => (), - _ => panic!("unexpected state"), - } -} - -#[test] -fn light_aux_store_is_updated_via_non_importing_op() { - let backend = Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new()))); - let mut op = ClientBackend::::begin_operation(&backend).unwrap(); - BlockImportOperation::::insert_aux(&mut op, vec![(vec![1], Some(vec![2]))]).unwrap(); - ClientBackend::::commit_operation(&backend, op).unwrap(); - - assert_eq!(AuxStore::get_aux(&backend, &[1]).unwrap(), Some(vec![2])); -} - -#[test] -fn execution_proof_is_generated_and_checked() { - fn execute(remote_client: &TestClient, at: u64, method: &'static str) -> (Vec, Vec) { - let remote_block_id = BlockId::Number(at); - let remote_header = remote_client.header(&remote_block_id).unwrap().unwrap(); - - // 'fetch' execution proof from remote node - let (remote_result, remote_execution_proof) = - remote_client.execution_proof(&remote_block_id, method, &[]).unwrap(); - - // check remote execution proof locally - let local_result = check_execution_proof::<_, _, BlakeTwo256>( - &local_executor(), - Box::new(TaskExecutor::new()), - &RemoteCallRequest { - block: substrate_test_runtime_client::runtime::Hash::default(), - header: remote_header, - method: method.into(), - call_data: vec![], - retry_count: None, - }, - remote_execution_proof, - ) - .unwrap(); - - (remote_result, local_result) - } - - fn execute_with_proof_failure(remote_client: &TestClient, at: u64) { - let remote_block_id = BlockId::Number(at); - let remote_header = remote_client.header(&remote_block_id).unwrap().unwrap(); - - // 'fetch' execution proof from remote node - let (_, remote_execution_proof) = remote_client - .execution_proof( - &remote_block_id, - "Core_initialize_block", - &Header::new( - at, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ) - .encode(), - ) - .unwrap(); - - // check remote execution proof locally - let execution_result = check_execution_proof::<_, _, BlakeTwo256>( - &local_executor(), - Box::new(TaskExecutor::new()), - &RemoteCallRequest { - block: substrate_test_runtime_client::runtime::Hash::default(), - header: remote_header.clone(), - method: "Core_initialize_block".into(), - call_data: Header::new( - at + 1, - Default::default(), - Default::default(), - remote_header.hash(), - remote_header.digest().clone(), // this makes next header wrong - ) - .encode(), - retry_count: None, - }, - remote_execution_proof, - ); - match execution_result { - Err(sp_blockchain::Error::Execution(_)) => (), - _ => panic!("Unexpected execution result: {:?}", execution_result), - } - } - - // prepare remote client - let mut remote_client = substrate_test_runtime_client::new(); - for i in 1u32..3u32 { - let mut digest = Digest::default(); - digest.push(sp_runtime::generic::DigestItem::Other::(i.to_le_bytes().to_vec())); - futures::executor::block_on(remote_client.import_justified( - BlockOrigin::Own, - remote_client.new_block(digest).unwrap().build().unwrap().block, - Justifications::from((*b"TEST", Default::default())), - )) - .unwrap(); - } - - // check method that doesn't requires environment - let (remote, local) = execute(&remote_client, 0, "Core_version"); - assert_eq!(remote, local); - - let (remote, local) = execute(&remote_client, 2, "Core_version"); - assert_eq!(remote, local); - - // check that proof check doesn't panic even if proof is incorrect AND no panic handler is set - execute_with_proof_failure(&remote_client, 2); - - // check that proof check doesn't panic even if proof is incorrect AND panic handler is set - sp_panic_handler::set("TEST", "1.2.3"); - execute_with_proof_failure(&remote_client, 2); -} - -#[test] -fn code_is_executed_at_genesis_only() { - let backend = Arc::new(InMemBackend::::new()); - let def = H256::default(); - let header0 = - substrate_test_runtime_client::runtime::Header::new(0, def, def, def, Default::default()); - let hash0 = header0.hash(); - let header1 = - substrate_test_runtime_client::runtime::Header::new(1, def, def, hash0, Default::default()); - let hash1 = header1.hash(); - backend - .blockchain() - .insert(hash0, header0, None, None, NewBlockState::Final) - .unwrap(); - backend - .blockchain() - .insert(hash1, header1, None, None, NewBlockState::Final) - .unwrap(); - - let genesis_executor = GenesisCallExecutor::new(backend, DummyCallExecutor); - assert_eq!( - genesis_executor - .call(&BlockId::Number(0), "test_method", &[], ExecutionStrategy::NativeElseWasm, None,) - .unwrap(), - vec![42], - ); - - let call_on_unavailable = genesis_executor.call( - &BlockId::Number(1), - "test_method", - &[], - ExecutionStrategy::NativeElseWasm, - None, - ); - - match call_on_unavailable { - Err(ClientError::NotAvailableOnLightClient) => (), - _ => unreachable!("unexpected result: {:?}", call_on_unavailable), - } -} - -type TestChecker = LightDataChecker< - NativeElseWasmExecutor, - Block, - DummyStorage, ->; - -fn prepare_for_read_proof_check() -> (TestChecker, Header, StorageProof, u32) { - // prepare remote client - let remote_client = substrate_test_runtime_client::new(); - let remote_block_id = BlockId::Number(0); - let remote_block_hash = remote_client.block_hash(0).unwrap().unwrap(); - let mut remote_block_header = remote_client.header(&remote_block_id).unwrap().unwrap(); - remote_block_header.state_root = remote_client - .state_at(&remote_block_id) - .unwrap() - .storage_root(::std::iter::empty()) - .0 - .into(); - - // 'fetch' read proof from remote node - let heap_pages = remote_client - .storage(&remote_block_id, &StorageKey(well_known_keys::HEAP_PAGES.to_vec())) - .unwrap() - .and_then(|v| Decode::decode(&mut &v.0[..]).ok()) - .unwrap(); - let remote_read_proof = remote_client - .read_proof(&remote_block_id, &mut std::iter::once(well_known_keys::HEAP_PAGES)) - .unwrap(); - - // check remote read proof locally - let local_storage = InMemoryBlockchain::::new(); - local_storage - .insert(remote_block_hash, remote_block_header.clone(), None, None, NewBlockState::Final) - .unwrap(); - let local_checker = LightDataChecker::new( - Arc::new(DummyBlockchain::new(DummyStorage::new())), - local_executor(), - Box::new(TaskExecutor::new()), - ); - (local_checker, remote_block_header, remote_read_proof, heap_pages) -} - -fn prepare_for_read_child_proof_check() -> (TestChecker, Header, StorageProof, Vec) { - use substrate_test_runtime_client::{DefaultTestClientBuilderExt, TestClientBuilderExt}; - let child_info = ChildInfo::new_default(b"child1"); - let child_info = &child_info; - // prepare remote client - let remote_client = substrate_test_runtime_client::TestClientBuilder::new() - .add_extra_child_storage(child_info, b"key1".to_vec(), b"value1".to_vec()) - .build(); - let remote_block_id = BlockId::Number(0); - let remote_block_hash = remote_client.block_hash(0).unwrap().unwrap(); - let mut remote_block_header = remote_client.header(&remote_block_id).unwrap().unwrap(); - remote_block_header.state_root = remote_client - .state_at(&remote_block_id) - .unwrap() - .storage_root(::std::iter::empty()) - .0 - .into(); - - // 'fetch' child read proof from remote node - let child_value = remote_client - .child_storage(&remote_block_id, child_info, &StorageKey(b"key1".to_vec())) - .unwrap() - .unwrap() - .0; - assert_eq!(b"value1"[..], child_value[..]); - let remote_read_proof = remote_client - .read_child_proof(&remote_block_id, child_info, &mut std::iter::once("key1".as_bytes())) - .unwrap(); - - // check locally - let local_storage = InMemoryBlockchain::::new(); - local_storage - .insert(remote_block_hash, remote_block_header.clone(), None, None, NewBlockState::Final) - .unwrap(); - let local_checker = LightDataChecker::new( - Arc::new(DummyBlockchain::new(DummyStorage::new())), - local_executor(), - Box::new(TaskExecutor::new()), - ); - (local_checker, remote_block_header, remote_read_proof, child_value) -} - -fn prepare_for_header_proof_check(insert_cht: bool) -> (TestChecker, Hash, Header, StorageProof) { - // prepare remote client - let mut remote_client = substrate_test_runtime_client::new(); - let mut local_headers_hashes = Vec::new(); - for i in 0..4 { - let block = remote_client.new_block(Default::default()).unwrap().build().unwrap().block; - futures::executor::block_on(remote_client.import(BlockOrigin::Own, block)).unwrap(); - local_headers_hashes.push( - remote_client - .block_hash(i + 1) - .map_err(|_| ClientError::Backend("TestError".into())), - ); - } - - // 'fetch' header proof from remote node - let remote_block_id = BlockId::Number(1); - let (remote_block_header, remote_header_proof) = - remote_client.header_proof_with_cht_size(&remote_block_id, 4).unwrap(); - - // check remote read proof locally - let local_storage = InMemoryBlockchain::::new(); - let local_cht_root = - cht::compute_root::(4, 0, local_headers_hashes).unwrap(); - if insert_cht { - local_storage.insert_cht_root(1, local_cht_root); - } - let local_checker = LightDataChecker::new( - Arc::new(DummyBlockchain::new(DummyStorage::new())), - local_executor(), - Box::new(TaskExecutor::new()), - ); - (local_checker, local_cht_root, remote_block_header, remote_header_proof) -} - -fn header_with_computed_extrinsics_root(extrinsics: Vec) -> Header { - use sp_trie::{trie_types::Layout, TrieConfiguration}; - let iter = extrinsics.iter().map(Encode::encode); - let extrinsics_root = Layout::::ordered_trie_root(iter); - - // only care about `extrinsics_root` - Header::new(0, extrinsics_root, H256::zero(), H256::zero(), Default::default()) -} - -#[test] -fn storage_read_proof_is_generated_and_checked() { - let (local_checker, remote_block_header, remote_read_proof, heap_pages) = - prepare_for_read_proof_check(); - assert_eq!( - (&local_checker as &dyn FetchChecker) - .check_read_proof( - &RemoteReadRequest::

{ - block: remote_block_header.hash(), - header: remote_block_header, - keys: vec![well_known_keys::HEAP_PAGES.to_vec()], - retry_count: None, - }, - remote_read_proof - ) - .unwrap() - .remove(well_known_keys::HEAP_PAGES) - .unwrap() - .unwrap()[0], - heap_pages as u8 - ); -} - -#[test] -fn storage_child_read_proof_is_generated_and_checked() { - let child_info = ChildInfo::new_default(&b"child1"[..]); - let (local_checker, remote_block_header, remote_read_proof, result) = - prepare_for_read_child_proof_check(); - assert_eq!( - (&local_checker as &dyn FetchChecker) - .check_read_child_proof( - &RemoteReadChildRequest::
{ - block: remote_block_header.hash(), - header: remote_block_header, - storage_key: child_info.prefixed_storage_key(), - keys: vec![b"key1".to_vec()], - retry_count: None, - }, - remote_read_proof - ) - .unwrap() - .remove(b"key1".as_ref()) - .unwrap() - .unwrap(), - result - ); -} - -#[test] -fn header_proof_is_generated_and_checked() { - let (local_checker, local_cht_root, remote_block_header, remote_header_proof) = - prepare_for_header_proof_check(true); - assert_eq!( - (&local_checker as &dyn FetchChecker) - .check_header_proof( - &RemoteHeaderRequest::
{ - cht_root: local_cht_root, - block: 1, - retry_count: None, - }, - Some(remote_block_header.clone()), - remote_header_proof - ) - .unwrap(), - remote_block_header - ); -} - -#[test] -fn check_header_proof_fails_if_cht_root_is_invalid() { - let (local_checker, _, mut remote_block_header, remote_header_proof) = - prepare_for_header_proof_check(true); - remote_block_header.number = 100; - assert!((&local_checker as &dyn FetchChecker) - .check_header_proof( - &RemoteHeaderRequest::
{ - cht_root: Default::default(), - block: 1, - retry_count: None, - }, - Some(remote_block_header.clone()), - remote_header_proof - ) - .is_err()); -} - -#[test] -fn check_header_proof_fails_if_invalid_header_provided() { - let (local_checker, local_cht_root, mut remote_block_header, remote_header_proof) = - prepare_for_header_proof_check(true); - remote_block_header.number = 100; - assert!((&local_checker as &dyn FetchChecker) - .check_header_proof( - &RemoteHeaderRequest::
{ - cht_root: local_cht_root, - block: 1, - retry_count: None, - }, - Some(remote_block_header.clone()), - remote_header_proof - ) - .is_err()); -} - -#[test] -fn changes_proof_is_generated_and_checked_when_headers_are_not_pruned() { - let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes(); - let local_checker = TestChecker::new( - Arc::new(DummyBlockchain::new(DummyStorage::new())), - local_executor(), - Box::new(TaskExecutor::new()), - ); - let local_checker = &local_checker as &dyn FetchChecker; - let max = remote_client.chain_info().best_number; - let max_hash = remote_client.chain_info().best_hash; - - for (index, (begin, end, key, expected_result)) in test_cases.into_iter().enumerate() { - let begin_hash = remote_client.block_hash(begin).unwrap().unwrap(); - let end_hash = remote_client.block_hash(end).unwrap().unwrap(); - - // 'fetch' changes proof from remote node - let key = StorageKey(key); - let remote_proof = remote_client - .key_changes_proof(begin_hash, end_hash, begin_hash, max_hash, None, &key) - .unwrap(); - - // check proof on local client - let local_roots_range = local_roots.clone()[(begin - 1) as usize..].to_vec(); - let config = ChangesTrieConfiguration::new(4, 2); - let request = RemoteChangesRequest::
{ - changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange { - zero: (0, Default::default()), - end: None, - config: Some(config), - }], - first_block: (begin, begin_hash), - last_block: (end, end_hash), - max_block: (max, max_hash), - tries_roots: (begin, begin_hash, local_roots_range), - key: key.0, - storage_key: None, - retry_count: None, - }; - let local_result = local_checker - .check_changes_proof( - &request, - ChangesProof { - max_block: remote_proof.max_block, - proof: remote_proof.proof, - roots: remote_proof.roots, - roots_proof: remote_proof.roots_proof, - }, - ) - .unwrap(); - - // ..and ensure that result is the same as on remote node - if local_result != expected_result { - panic!( - "Failed test {}: local = {:?}, expected = {:?}", - index, local_result, expected_result, - ); - } - } -} - -#[test] -fn changes_proof_is_generated_and_checked_when_headers_are_pruned() { - // we're testing this test case here: - // (1, 4, dave.clone(), vec![(4, 0), (1, 1), (1, 0)]), - let (remote_client, remote_roots, _) = prepare_client_with_key_changes(); - let dave = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Dave.into())).to_vec(); - let dave = StorageKey(dave); - - // 'fetch' changes proof from remote node: - // we're fetching changes for range b1..b4 - // we do not know changes trie roots before b3 (i.e. we only know b3+b4) - // but we have changes trie CHT root for b1...b4 - let b1 = remote_client.block_hash_from_id(&BlockId::Number(1)).unwrap().unwrap(); - let b3 = remote_client.block_hash_from_id(&BlockId::Number(3)).unwrap().unwrap(); - let b4 = remote_client.block_hash_from_id(&BlockId::Number(4)).unwrap().unwrap(); - let remote_proof = remote_client - .key_changes_proof_with_cht_size(b1, b4, b3, b4, None, &dave, 4) - .unwrap(); - - // prepare local checker, having a root of changes trie CHT#0 - let local_cht_root = cht::compute_root::( - 4, - 0, - remote_roots.iter().cloned().map(|ct| Ok(Some(ct))), - ) - .unwrap(); - let mut local_storage = DummyStorage::new(); - local_storage.changes_tries_cht_roots.insert(0, local_cht_root); - let local_checker = TestChecker::new( - Arc::new(DummyBlockchain::new(local_storage)), - local_executor(), - Box::new(TaskExecutor::new()), - ); - - // check proof on local client - let config = ChangesTrieConfiguration::new(4, 2); - let request = RemoteChangesRequest::
{ - changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange { - zero: (0, Default::default()), - end: None, - config: Some(config), - }], - first_block: (1, b1), - last_block: (4, b4), - max_block: (4, b4), - tries_roots: (3, b3, vec![remote_roots[2].clone(), remote_roots[3].clone()]), - storage_key: None, - key: dave.0, - retry_count: None, - }; - let local_result = local_checker - .check_changes_proof_with_cht_size( - &request, - ChangesProof { - max_block: remote_proof.max_block, - proof: remote_proof.proof, - roots: remote_proof.roots, - roots_proof: remote_proof.roots_proof, - }, - 4, - ) - .unwrap(); - - assert_eq!(local_result, vec![(4, 0), (1, 1), (1, 0)]); -} - -#[test] -fn check_changes_proof_fails_if_proof_is_wrong() { - let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes(); - let local_checker = TestChecker::new( - Arc::new(DummyBlockchain::new(DummyStorage::new())), - local_executor(), - Box::new(TaskExecutor::new()), - ); - let local_checker = &local_checker as &dyn FetchChecker; - let max = remote_client.chain_info().best_number; - let max_hash = remote_client.chain_info().best_hash; - - let (begin, end, key, _) = test_cases[0].clone(); - let begin_hash = remote_client.block_hash(begin).unwrap().unwrap(); - let end_hash = remote_client.block_hash(end).unwrap().unwrap(); - - // 'fetch' changes proof from remote node - let key = StorageKey(key); - let remote_proof = remote_client - .key_changes_proof(begin_hash, end_hash, begin_hash, max_hash, None, &key) - .unwrap(); - - let local_roots_range = local_roots.clone()[(begin - 1) as usize..].to_vec(); - let config = ChangesTrieConfiguration::new(4, 2); - let request = RemoteChangesRequest::
{ - changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange { - zero: (0, Default::default()), - end: None, - config: Some(config), - }], - first_block: (begin, begin_hash), - last_block: (end, end_hash), - max_block: (max, max_hash), - tries_roots: (begin, begin_hash, local_roots_range.clone()), - storage_key: None, - key: key.0, - retry_count: None, - }; - - // check proof on local client using max from the future - assert!(local_checker - .check_changes_proof( - &request, - ChangesProof { - max_block: remote_proof.max_block + 1, - proof: remote_proof.proof.clone(), - roots: remote_proof.roots.clone(), - roots_proof: remote_proof.roots_proof.clone(), - } - ) - .is_err()); - - // check proof on local client using broken proof - assert!(local_checker - .check_changes_proof( - &request, - ChangesProof { - max_block: remote_proof.max_block, - proof: local_roots_range.clone().into_iter().map(|v| v.as_ref().to_vec()).collect(), - roots: remote_proof.roots, - roots_proof: remote_proof.roots_proof, - } - ) - .is_err()); - - // extra roots proofs are provided - assert!(local_checker - .check_changes_proof( - &request, - ChangesProof { - max_block: remote_proof.max_block, - proof: remote_proof.proof.clone(), - roots: vec![(begin - 1, Default::default())].into_iter().collect(), - roots_proof: StorageProof::empty(), - } - ) - .is_err()); - assert!(local_checker - .check_changes_proof( - &request, - ChangesProof { - max_block: remote_proof.max_block, - proof: remote_proof.proof.clone(), - roots: vec![(end + 1, Default::default())].into_iter().collect(), - roots_proof: StorageProof::empty(), - } - ) - .is_err()); -} - -#[test] -fn check_changes_tries_proof_fails_if_proof_is_wrong() { - // we're testing this test case here: - // (1, 4, dave.clone(), vec![(4, 0), (1, 1), (1, 0)]), - let (remote_client, remote_roots, _) = prepare_client_with_key_changes(); - let local_cht_root = cht::compute_root::( - 4, - 0, - remote_roots.iter().cloned().map(|ct| Ok(Some(ct))), - ) - .unwrap(); - let dave = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Dave.into())).to_vec(); - let dave = StorageKey(dave); - - // 'fetch' changes proof from remote node: - // we're fetching changes for range b1..b4 - // we do not know changes trie roots before b3 (i.e. we only know b3+b4) - // but we have changes trie CHT root for b1...b4 - let b1 = remote_client.block_hash_from_id(&BlockId::Number(1)).unwrap().unwrap(); - let b3 = remote_client.block_hash_from_id(&BlockId::Number(3)).unwrap().unwrap(); - let b4 = remote_client.block_hash_from_id(&BlockId::Number(4)).unwrap().unwrap(); - let remote_proof = remote_client - .key_changes_proof_with_cht_size(b1, b4, b3, b4, None, &dave, 4) - .unwrap(); - - // fails when changes trie CHT is missing from the local db - let local_checker = TestChecker::new( - Arc::new(DummyBlockchain::new(DummyStorage::new())), - local_executor(), - Box::new(TaskExecutor::new()), - ); - assert!(local_checker - .check_changes_tries_proof(4, &remote_proof.roots, remote_proof.roots_proof.clone()) - .is_err()); - - // fails when proof is broken - let mut local_storage = DummyStorage::new(); - local_storage.changes_tries_cht_roots.insert(0, local_cht_root); - let local_checker = TestChecker::new( - Arc::new(DummyBlockchain::new(local_storage)), - local_executor(), - Box::new(TaskExecutor::new()), - ); - let result = - local_checker.check_changes_tries_proof(4, &remote_proof.roots, StorageProof::empty()); - assert!(result.is_err()); -} - -#[test] -fn check_body_proof_faulty() { - let header = - header_with_computed_extrinsics_root(vec![Extrinsic::IncludeData(vec![1, 2, 3, 4])]); - let block = Block::new(header.clone(), Vec::new()); - - let local_checker = TestChecker::new( - Arc::new(DummyBlockchain::new(DummyStorage::new())), - local_executor(), - Box::new(TaskExecutor::new()), - ); - - let body_request = RemoteBodyRequest { header: header.clone(), retry_count: None }; - - assert!( - local_checker.check_body_proof(&body_request, block.extrinsics).is_err(), - "vec![1, 2, 3, 4] != vec![]" - ); -} - -#[test] -fn check_body_proof_of_same_data_should_succeed() { - let extrinsics = vec![Extrinsic::IncludeData(vec![1, 2, 3, 4, 5, 6, 7, 8, 255])]; - - let header = header_with_computed_extrinsics_root(extrinsics.clone()); - let block = Block::new(header.clone(), extrinsics); - - let local_checker = TestChecker::new( - Arc::new(DummyBlockchain::new(DummyStorage::new())), - local_executor(), - Box::new(TaskExecutor::new()), - ); - - let body_request = RemoteBodyRequest { header: header.clone(), retry_count: None }; - - assert!(local_checker.check_body_proof(&body_request, block.extrinsics).is_ok()); -} diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index 295e941f7ceb1..be9253d8c78e8 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,39 +17,35 @@ // along with this program. If not, see . use futures::executor::block_on; -use hex_literal::hex; use parity_scale_codec::{Decode, Encode, Joiner}; use sc_block_builder::BlockBuilderProvider; -use sc_client_api::{in_mem, BlockBackend, BlockchainEvents, StorageProvider}; -use sc_client_db::{ - Backend, DatabaseSettings, DatabaseSource, KeepBlocks, PruningMode, TransactionStorageMode, +use sc_client_api::{ + in_mem, BlockBackend, BlockchainEvents, ExecutorProvider, FinalityNotifications, HeaderBackend, + StorageProvider, }; +use sc_client_db::{Backend, BlocksPruning, DatabaseSettings, DatabaseSource, PruningMode}; use sc_consensus::{ BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, }; -use sc_service::client::{self, new_in_mem, Client, LocalCallExecutor}; +use sc_service::client::{new_in_mem, Client, LocalCallExecutor}; use sp_api::ProvideRuntimeApi; use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError, SelectChain}; -use sp_core::{blake2_256, testing::TaskExecutor, ChangesTrieConfiguration, H256}; +use sp_core::{testing::TaskExecutor, H256}; use sp_runtime::{ generic::BlockId, traits::{BlakeTwo256, Block as BlockT, Header as HeaderT}, - ConsensusEngineId, DigestItem, Justifications, + ConsensusEngineId, Justifications, StateVersion, }; use sp_state_machine::{ backend::Backend as _, ExecutionStrategy, InMemoryBackend, OverlayedChanges, StateMachine, }; use sp_storage::{ChildInfo, StorageKey}; -use sp_trie::{trie_types::Layout, TrieConfiguration}; -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; +use sp_trie::{LayoutV0, TrieConfiguration}; +use std::{collections::HashSet, sync::Arc}; use substrate_test_runtime::TestAPI; use substrate_test_runtime_client::{ prelude::*, runtime::{ - self, genesismap::{insert_genesis_block, GenesisConfig}, Block, BlockNumber, Digest, Hash, Header, RuntimeApi, Transfer, }, @@ -58,7 +54,6 @@ use substrate_test_runtime_client::{ }; mod db; -mod light; const TEST_ENGINE_ID: ConsensusEngineId = *b"TEST"; @@ -77,87 +72,12 @@ impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { } fn executor() -> sc_executor::NativeElseWasmExecutor { - sc_executor::NativeElseWasmExecutor::new(sc_executor::WasmExecutionMethod::Interpreted, None, 8) -} - -pub fn prepare_client_with_key_changes() -> ( - client::Client< - substrate_test_runtime_client::Backend, - substrate_test_runtime_client::ExecutorDispatch, - Block, - RuntimeApi, - >, - Vec, - Vec<(u64, u64, Vec, Vec<(u64, u32)>)>, -) { - // prepare block structure - let blocks_transfers = vec![ - vec![ - (AccountKeyring::Alice, AccountKeyring::Dave), - (AccountKeyring::Bob, AccountKeyring::Dave), - ], - vec![(AccountKeyring::Charlie, AccountKeyring::Eve)], - vec![], - vec![(AccountKeyring::Alice, AccountKeyring::Dave)], - ]; - - // prepare client ang import blocks - let mut local_roots = Vec::new(); - let config = Some(ChangesTrieConfiguration::new(4, 2)); - let mut remote_client = TestClientBuilder::new().changes_trie_config(config).build(); - let mut nonces: HashMap<_, u64> = Default::default(); - for (i, block_transfers) in blocks_transfers.into_iter().enumerate() { - let mut builder = remote_client.new_block(Default::default()).unwrap(); - for (from, to) in block_transfers { - builder - .push_transfer(Transfer { - from: from.into(), - to: to.into(), - amount: 1, - nonce: *nonces.entry(from).and_modify(|n| *n = *n + 1).or_default(), - }) - .unwrap(); - } - let block = builder.build().unwrap().block; - block_on(remote_client.import(BlockOrigin::Own, block)).unwrap(); - - let header = remote_client.header(&BlockId::Number(i as u64 + 1)).unwrap().unwrap(); - let trie_root = header - .digest() - .log(DigestItem::as_changes_trie_root) - .map(|root| H256::from_slice(root.as_ref())) - .unwrap(); - local_roots.push(trie_root); - } - - // prepare test cases - let alice = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Alice.into())).to_vec(); - let bob = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Bob.into())).to_vec(); - let charlie = - blake2_256(&runtime::system::balance_of_key(AccountKeyring::Charlie.into())).to_vec(); - let dave = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Dave.into())).to_vec(); - let eve = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Eve.into())).to_vec(); - let ferdie = - blake2_256(&runtime::system::balance_of_key(AccountKeyring::Ferdie.into())).to_vec(); - let test_cases = vec![ - (1, 4, alice.clone(), vec![(4, 0), (1, 0)]), - (1, 3, alice.clone(), vec![(1, 0)]), - (2, 4, alice.clone(), vec![(4, 0)]), - (2, 3, alice.clone(), vec![]), - (1, 4, bob.clone(), vec![(1, 1)]), - (1, 1, bob.clone(), vec![(1, 1)]), - (2, 4, bob.clone(), vec![]), - (1, 4, charlie.clone(), vec![(2, 0)]), - (1, 4, dave.clone(), vec![(4, 0), (1, 1), (1, 0)]), - (1, 1, dave.clone(), vec![(1, 1), (1, 0)]), - (3, 4, dave.clone(), vec![(4, 0)]), - (1, 4, eve.clone(), vec![(2, 0)]), - (1, 1, eve.clone(), vec![]), - (3, 4, eve.clone(), vec![]), - (1, 4, ferdie.clone(), vec![]), - ]; - - (remote_client, local_roots, test_cases) + sc_executor::NativeElseWasmExecutor::new( + sc_executor::WasmExecutionMethod::Interpreted, + None, + 8, + 2, + ) } fn construct_block( @@ -170,7 +90,7 @@ fn construct_block( let transactions = txs.into_iter().map(|tx| tx.into_signed_tx()).collect::>(); let iter = transactions.iter().map(Encode::encode); - let extrinsics_root = Layout::::ordered_trie_root(iter).into(); + let extrinsics_root = LayoutV0::::ordered_trie_root(iter).into(); let mut header = Header { parent_hash, @@ -187,7 +107,6 @@ fn construct_block( StateMachine::new( backend, - sp_state_machine::disabled_changes_trie_state::<_, u64>(), &mut overlay, &executor(), "Core_initialize_block", @@ -202,7 +121,6 @@ fn construct_block( for tx in transactions.iter() { StateMachine::new( backend, - sp_state_machine::disabled_changes_trie_state::<_, u64>(), &mut overlay, &executor(), "BlockBuilder_apply_extrinsic", @@ -217,7 +135,6 @@ fn construct_block( let ret_data = StateMachine::new( backend, - sp_state_machine::disabled_changes_trie_state::<_, u64>(), &mut overlay, &executor(), "BlockBuilder_finalize_block", @@ -238,7 +155,9 @@ fn block1(genesis_hash: Hash, backend: &InMemoryBackend) -> (Vec) -> (Vec, + finalized: &[Hash], + stale_heads: &[Hash], +) { + match notifications.try_next() { + Ok(Some(notif)) => { + let stale_heads_expected: HashSet<_> = stale_heads.iter().collect(); + let stale_heads: HashSet<_> = notif.stale_heads.iter().collect(); + assert_eq!(notif.tree_route.as_ref(), &finalized[..finalized.len() - 1]); + assert_eq!(notif.hash, *finalized.last().unwrap()); + assert_eq!(stale_heads, stale_heads_expected); + }, + Ok(None) => panic!("unexpected notification result, client send channel was closed"), + Err(_) => assert!(finalized.is_empty()), + } +} + #[test] fn construct_genesis_should_work_with_native() { let mut storage = GenesisConfig::new( - None, vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], 1000, @@ -261,7 +197,7 @@ fn construct_genesis_should_work_with_native() { .genesis_map(); let genesis_hash = insert_genesis_block(&mut storage); - let backend = InMemoryBackend::from(storage); + let backend = InMemoryBackend::from((storage, StateVersion::default())); let (b1data, _b1hash) = block1(genesis_hash, &backend); let backend_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&backend); let runtime_code = backend_runtime_code.runtime_code().expect("Code is part of the backend"); @@ -270,7 +206,6 @@ fn construct_genesis_should_work_with_native() { let _ = StateMachine::new( &backend, - sp_state_machine::disabled_changes_trie_state::<_, u64>(), &mut overlay, &executor(), "Core_execute_block", @@ -286,7 +221,6 @@ fn construct_genesis_should_work_with_native() { #[test] fn construct_genesis_should_work_with_wasm() { let mut storage = GenesisConfig::new( - None, vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], 1000, @@ -296,7 +230,7 @@ fn construct_genesis_should_work_with_wasm() { .genesis_map(); let genesis_hash = insert_genesis_block(&mut storage); - let backend = InMemoryBackend::from(storage); + let backend = InMemoryBackend::from((storage, StateVersion::default())); let (b1data, _b1hash) = block1(genesis_hash, &backend); let backend_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&backend); let runtime_code = backend_runtime_code.runtime_code().expect("Code is part of the backend"); @@ -305,7 +239,6 @@ fn construct_genesis_should_work_with_wasm() { let _ = StateMachine::new( &backend, - sp_state_machine::disabled_changes_trie_state::<_, u64>(), &mut overlay, &executor(), "Core_execute_block", @@ -321,7 +254,6 @@ fn construct_genesis_should_work_with_wasm() { #[test] fn construct_genesis_with_bad_transaction_should_panic() { let mut storage = GenesisConfig::new( - None, vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], 68, @@ -331,7 +263,7 @@ fn construct_genesis_with_bad_transaction_should_panic() { .genesis_map(); let genesis_hash = insert_genesis_block(&mut storage); - let backend = InMemoryBackend::from(storage); + let backend = InMemoryBackend::from((storage, StateVersion::default())); let (b1data, _b1hash) = block1(genesis_hash, &backend); let backend_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&backend); let runtime_code = backend_runtime_code.runtime_code().expect("Code is part of the backend"); @@ -340,7 +272,6 @@ fn construct_genesis_with_bad_transaction_should_panic() { let r = StateMachine::new( &backend, - sp_state_machine::disabled_changes_trie_state::<_, u64>(), &mut overlay, &executor(), "Core_execute_block", @@ -408,11 +339,15 @@ fn block_builder_works_with_transactions() { let block = builder.build().unwrap().block; block_on(client.import(BlockOrigin::Own, block)).unwrap(); + let hash0 = client + .expect_block_hash_from_id(&BlockId::Number(0)) + .expect("block 0 was just imported. qed"); + let hash1 = client + .expect_block_hash_from_id(&BlockId::Number(1)) + .expect("block 1 was just imported. qed"); + assert_eq!(client.chain_info().best_number, 1); - assert_ne!( - client.state_at(&BlockId::Number(1)).unwrap().pairs(), - client.state_at(&BlockId::Number(0)).unwrap().pairs() - ); + assert_ne!(client.state_at(hash1).unwrap().pairs(), client.state_at(hash0).unwrap().pairs()); assert_eq!( client .runtime_api() @@ -462,12 +397,19 @@ fn block_builder_does_not_include_invalid() { let block = builder.build().unwrap().block; block_on(client.import(BlockOrigin::Own, block)).unwrap(); + let hashof0 = client + .expect_block_hash_from_id(&BlockId::Number(0)) + .expect("block 0 was just imported. qed"); + let hashof1 = client + .expect_block_hash_from_id(&BlockId::Number(1)) + .expect("block 1 was just imported. qed"); + assert_eq!(client.chain_info().best_number, 1); assert_ne!( - client.state_at(&BlockId::Number(1)).unwrap().pairs(), - client.state_at(&BlockId::Number(0)).unwrap().pairs() + client.state_at(hashof1).unwrap().pairs(), + client.state_at(hashof0).unwrap().pairs() ); - assert_eq!(client.body(&BlockId::Number(1)).unwrap().unwrap().len(), 1) + assert_eq!(client.body(hashof1).unwrap().unwrap().len(), 1) } #[test] @@ -481,25 +423,7 @@ fn best_containing_with_genesis_block() { assert_eq!( genesis_hash.clone(), - block_on(longest_chain_select.finality_target(genesis_hash.clone(), None)) - .unwrap() - .unwrap(), - ); -} - -#[test] -fn best_containing_with_hash_not_found() { - // block tree: - // G - - let (client, longest_chain_select) = TestClientBuilder::new().build_with_longest_chain(); - - let uninserted_block = client.new_block(Default::default()).unwrap().build().unwrap().block; - - assert_eq!( - None, - block_on(longest_chain_select.finality_target(uninserted_block.hash().clone(), None)) - .unwrap(), + block_on(longest_chain_select.finality_target(genesis_hash, None)).unwrap(), ); } @@ -525,8 +449,8 @@ fn uncles_with_multiple_forks() { // block tree: // G -> A1 -> A2 -> A3 -> A4 -> A5 // A1 -> B2 -> B3 -> B4 - // B2 -> C3 - // A1 -> D2 + // B2 -> C3 + // A1 -> D2 let mut client = substrate_test_runtime_client::new(); // G -> A1 @@ -675,22 +599,10 @@ fn best_containing_on_longest_chain_with_single_chain_3_blocks() { assert_eq!( a2.hash(), - block_on(longest_chain_select.finality_target(genesis_hash, None)) - .unwrap() - .unwrap() - ); - assert_eq!( - a2.hash(), - block_on(longest_chain_select.finality_target(a1.hash(), None)) - .unwrap() - .unwrap() - ); - assert_eq!( - a2.hash(), - block_on(longest_chain_select.finality_target(a2.hash(), None)) - .unwrap() - .unwrap() + block_on(longest_chain_select.finality_target(genesis_hash, None)).unwrap() ); + assert_eq!(a2.hash(), block_on(longest_chain_select.finality_target(a1.hash(), None)).unwrap()); + assert_eq!(a2.hash(), block_on(longest_chain_select.finality_target(a2.hash(), None)).unwrap()); } #[test] @@ -819,343 +731,101 @@ fn best_containing_on_longest_chain_with_multiple_forks() { assert!(leaves.contains(&d2.hash())); assert_eq!(leaves.len(), 4); + let finality_target = |target_hash, number| { + block_on(longest_chain_select.finality_target(target_hash, number)).unwrap() + }; + // search without restriction - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(genesis_hash, None)) - .unwrap() - .unwrap() - ); - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(a1.hash(), None)) - .unwrap() - .unwrap() - ); - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(a2.hash(), None)) - .unwrap() - .unwrap() - ); - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(a3.hash(), None)) - .unwrap() - .unwrap() - ); - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(a4.hash(), None)) - .unwrap() - .unwrap() - ); - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(a5.hash(), None)) - .unwrap() - .unwrap() - ); - assert_eq!( - b4.hash(), - block_on(longest_chain_select.finality_target(b2.hash(), None)) - .unwrap() - .unwrap() - ); - assert_eq!( - b4.hash(), - block_on(longest_chain_select.finality_target(b3.hash(), None)) - .unwrap() - .unwrap() - ); - assert_eq!( - b4.hash(), - block_on(longest_chain_select.finality_target(b4.hash(), None)) - .unwrap() - .unwrap() - ); - assert_eq!( - c3.hash(), - block_on(longest_chain_select.finality_target(c3.hash(), None)) - .unwrap() - .unwrap() - ); - assert_eq!( - d2.hash(), - block_on(longest_chain_select.finality_target(d2.hash(), None)) - .unwrap() - .unwrap() - ); + assert_eq!(a5.hash(), finality_target(genesis_hash, None)); + assert_eq!(a5.hash(), finality_target(a1.hash(), None)); + assert_eq!(a5.hash(), finality_target(a2.hash(), None)); + assert_eq!(a5.hash(), finality_target(a3.hash(), None)); + assert_eq!(a5.hash(), finality_target(a4.hash(), None)); + assert_eq!(a5.hash(), finality_target(a5.hash(), None)); + assert_eq!(b4.hash(), finality_target(b2.hash(), None)); + assert_eq!(b4.hash(), finality_target(b3.hash(), None)); + assert_eq!(b4.hash(), finality_target(b4.hash(), None)); + assert_eq!(c3.hash(), finality_target(c3.hash(), None)); + assert_eq!(d2.hash(), finality_target(d2.hash(), None)); // search only blocks with number <= 5. equivalent to without restriction for this scenario - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(genesis_hash, Some(5))) - .unwrap() - .unwrap() - ); - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(a1.hash(), Some(5))) - .unwrap() - .unwrap() - ); - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(a2.hash(), Some(5))) - .unwrap() - .unwrap() - ); - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(a3.hash(), Some(5))) - .unwrap() - .unwrap() - ); - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(a4.hash(), Some(5))) - .unwrap() - .unwrap() - ); - assert_eq!( - a5.hash(), - block_on(longest_chain_select.finality_target(a5.hash(), Some(5))) - .unwrap() - .unwrap() - ); - assert_eq!( - b4.hash(), - block_on(longest_chain_select.finality_target(b2.hash(), Some(5))) - .unwrap() - .unwrap() - ); - assert_eq!( - b4.hash(), - block_on(longest_chain_select.finality_target(b3.hash(), Some(5))) - .unwrap() - .unwrap() - ); - assert_eq!( - b4.hash(), - block_on(longest_chain_select.finality_target(b4.hash(), Some(5))) - .unwrap() - .unwrap() - ); - assert_eq!( - c3.hash(), - block_on(longest_chain_select.finality_target(c3.hash(), Some(5))) - .unwrap() - .unwrap() - ); - assert_eq!( - d2.hash(), - block_on(longest_chain_select.finality_target(d2.hash(), Some(5))) - .unwrap() - .unwrap() - ); + assert_eq!(a5.hash(), finality_target(genesis_hash, Some(5))); + assert_eq!(a5.hash(), finality_target(a1.hash(), Some(5))); + assert_eq!(a5.hash(), finality_target(a2.hash(), Some(5))); + assert_eq!(a5.hash(), finality_target(a3.hash(), Some(5))); + assert_eq!(a5.hash(), finality_target(a4.hash(), Some(5))); + assert_eq!(a5.hash(), finality_target(a5.hash(), Some(5))); + assert_eq!(b4.hash(), finality_target(b2.hash(), Some(5))); + assert_eq!(b4.hash(), finality_target(b3.hash(), Some(5))); + assert_eq!(b4.hash(), finality_target(b4.hash(), Some(5))); + assert_eq!(c3.hash(), finality_target(c3.hash(), Some(5))); + assert_eq!(d2.hash(), finality_target(d2.hash(), Some(5))); // search only blocks with number <= 4 - assert_eq!( - a4.hash(), - block_on(longest_chain_select.finality_target(genesis_hash, Some(4))) - .unwrap() - .unwrap() - ); - assert_eq!( - a4.hash(), - block_on(longest_chain_select.finality_target(a1.hash(), Some(4))) - .unwrap() - .unwrap() - ); - assert_eq!( - a4.hash(), - block_on(longest_chain_select.finality_target(a2.hash(), Some(4))) - .unwrap() - .unwrap() - ); - assert_eq!( - a4.hash(), - block_on(longest_chain_select.finality_target(a3.hash(), Some(4))) - .unwrap() - .unwrap() - ); - assert_eq!( - a4.hash(), - block_on(longest_chain_select.finality_target(a4.hash(), Some(4))) - .unwrap() - .unwrap() - ); - assert_eq!(None, block_on(longest_chain_select.finality_target(a5.hash(), Some(4))).unwrap()); - assert_eq!( - b4.hash(), - block_on(longest_chain_select.finality_target(b2.hash(), Some(4))) - .unwrap() - .unwrap() - ); - assert_eq!( - b4.hash(), - block_on(longest_chain_select.finality_target(b3.hash(), Some(4))) - .unwrap() - .unwrap() - ); - assert_eq!( - b4.hash(), - block_on(longest_chain_select.finality_target(b4.hash(), Some(4))) - .unwrap() - .unwrap() - ); - assert_eq!( - c3.hash(), - block_on(longest_chain_select.finality_target(c3.hash(), Some(4))) - .unwrap() - .unwrap() - ); - assert_eq!( - d2.hash(), - block_on(longest_chain_select.finality_target(d2.hash(), Some(4))) - .unwrap() - .unwrap() - ); + assert_eq!(a4.hash(), finality_target(genesis_hash, Some(4))); + assert_eq!(a4.hash(), finality_target(a1.hash(), Some(4))); + assert_eq!(a4.hash(), finality_target(a2.hash(), Some(4))); + assert_eq!(a4.hash(), finality_target(a3.hash(), Some(4))); + assert_eq!(a4.hash(), finality_target(a4.hash(), Some(4))); + assert_eq!(a5.hash(), finality_target(a5.hash(), Some(4))); + assert_eq!(b4.hash(), finality_target(b2.hash(), Some(4))); + assert_eq!(b4.hash(), finality_target(b3.hash(), Some(4))); + assert_eq!(b4.hash(), finality_target(b4.hash(), Some(4))); + assert_eq!(c3.hash(), finality_target(c3.hash(), Some(4))); + assert_eq!(d2.hash(), finality_target(d2.hash(), Some(4))); // search only blocks with number <= 3 - assert_eq!( - a3.hash(), - block_on(longest_chain_select.finality_target(genesis_hash, Some(3))) - .unwrap() - .unwrap() - ); - assert_eq!( - a3.hash(), - block_on(longest_chain_select.finality_target(a1.hash(), Some(3))) - .unwrap() - .unwrap() - ); - assert_eq!( - a3.hash(), - block_on(longest_chain_select.finality_target(a2.hash(), Some(3))) - .unwrap() - .unwrap() - ); - assert_eq!( - a3.hash(), - block_on(longest_chain_select.finality_target(a3.hash(), Some(3))) - .unwrap() - .unwrap() - ); - assert_eq!(None, block_on(longest_chain_select.finality_target(a4.hash(), Some(3))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(a5.hash(), Some(3))).unwrap()); - assert_eq!( - b3.hash(), - block_on(longest_chain_select.finality_target(b2.hash(), Some(3))) - .unwrap() - .unwrap() - ); - assert_eq!( - b3.hash(), - block_on(longest_chain_select.finality_target(b3.hash(), Some(3))) - .unwrap() - .unwrap() - ); - assert_eq!(None, block_on(longest_chain_select.finality_target(b4.hash(), Some(3))).unwrap()); - assert_eq!( - c3.hash(), - block_on(longest_chain_select.finality_target(c3.hash(), Some(3))) - .unwrap() - .unwrap() - ); - assert_eq!( - d2.hash(), - block_on(longest_chain_select.finality_target(d2.hash(), Some(3))) - .unwrap() - .unwrap() - ); + assert_eq!(a3.hash(), finality_target(genesis_hash, Some(3))); + assert_eq!(a3.hash(), finality_target(a1.hash(), Some(3))); + assert_eq!(a3.hash(), finality_target(a2.hash(), Some(3))); + assert_eq!(a3.hash(), finality_target(a3.hash(), Some(3))); + assert_eq!(a4.hash(), finality_target(a4.hash(), Some(3))); + assert_eq!(a5.hash(), finality_target(a5.hash(), Some(3))); + assert_eq!(b3.hash(), finality_target(b2.hash(), Some(3))); + assert_eq!(b3.hash(), finality_target(b3.hash(), Some(3))); + assert_eq!(b4.hash(), finality_target(b4.hash(), Some(3))); + assert_eq!(c3.hash(), finality_target(c3.hash(), Some(3))); + assert_eq!(d2.hash(), finality_target(d2.hash(), Some(3))); // search only blocks with number <= 2 - assert_eq!( - a2.hash(), - block_on(longest_chain_select.finality_target(genesis_hash, Some(2))) - .unwrap() - .unwrap() - ); - assert_eq!( - a2.hash(), - block_on(longest_chain_select.finality_target(a1.hash(), Some(2))) - .unwrap() - .unwrap() - ); - assert_eq!( - a2.hash(), - block_on(longest_chain_select.finality_target(a2.hash(), Some(2))) - .unwrap() - .unwrap() - ); - assert_eq!(None, block_on(longest_chain_select.finality_target(a3.hash(), Some(2))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(a4.hash(), Some(2))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(a5.hash(), Some(2))).unwrap()); - assert_eq!( - b2.hash(), - block_on(longest_chain_select.finality_target(b2.hash(), Some(2))) - .unwrap() - .unwrap() - ); - assert_eq!(None, block_on(longest_chain_select.finality_target(b3.hash(), Some(2))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(b4.hash(), Some(2))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(c3.hash(), Some(2))).unwrap()); - assert_eq!( - d2.hash(), - block_on(longest_chain_select.finality_target(d2.hash(), Some(2))) - .unwrap() - .unwrap() - ); + assert_eq!(a2.hash(), finality_target(genesis_hash, Some(2))); + assert_eq!(a2.hash(), finality_target(a1.hash(), Some(2))); + assert_eq!(a2.hash(), finality_target(a2.hash(), Some(2))); + assert_eq!(a3.hash(), finality_target(a3.hash(), Some(2))); + assert_eq!(a4.hash(), finality_target(a4.hash(), Some(2))); + assert_eq!(a5.hash(), finality_target(a5.hash(), Some(2))); + assert_eq!(b2.hash(), finality_target(b2.hash(), Some(2))); + assert_eq!(b3.hash(), finality_target(b3.hash(), Some(2))); + assert_eq!(b4.hash(), finality_target(b4.hash(), Some(2))); + assert_eq!(c3.hash(), finality_target(c3.hash(), Some(2))); + assert_eq!(d2.hash(), finality_target(d2.hash(), Some(2))); // search only blocks with number <= 1 - assert_eq!( - a1.hash(), - block_on(longest_chain_select.finality_target(genesis_hash, Some(1))) - .unwrap() - .unwrap() - ); - assert_eq!( - a1.hash(), - block_on(longest_chain_select.finality_target(a1.hash(), Some(1))) - .unwrap() - .unwrap() - ); - assert_eq!(None, block_on(longest_chain_select.finality_target(a2.hash(), Some(1))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(a3.hash(), Some(1))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(a4.hash(), Some(1))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(a5.hash(), Some(1))).unwrap()); - - assert_eq!(None, block_on(longest_chain_select.finality_target(b2.hash(), Some(1))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(b3.hash(), Some(1))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(b4.hash(), Some(1))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(c3.hash(), Some(1))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(d2.hash(), Some(1))).unwrap()); + assert_eq!(a1.hash(), finality_target(genesis_hash, Some(1))); + assert_eq!(a1.hash(), finality_target(a1.hash(), Some(1))); + assert_eq!(a2.hash(), finality_target(a2.hash(), Some(1))); + assert_eq!(a3.hash(), finality_target(a3.hash(), Some(1))); + assert_eq!(a4.hash(), finality_target(a4.hash(), Some(1))); + assert_eq!(a5.hash(), finality_target(a5.hash(), Some(1))); + + assert_eq!(b2.hash(), finality_target(b2.hash(), Some(1))); + assert_eq!(b3.hash(), finality_target(b3.hash(), Some(1))); + assert_eq!(b4.hash(), finality_target(b4.hash(), Some(1))); + assert_eq!(c3.hash(), finality_target(c3.hash(), Some(1))); + assert_eq!(d2.hash(), finality_target(d2.hash(), Some(1))); // search only blocks with number <= 0 - assert_eq!( - genesis_hash, - block_on(longest_chain_select.finality_target(genesis_hash, Some(0))) - .unwrap() - .unwrap() - ); - assert_eq!(None, block_on(longest_chain_select.finality_target(a1.hash(), Some(0))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(a2.hash(), Some(0))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(a3.hash(), Some(0))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(a4.hash(), Some(0))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(a5.hash(), Some(0))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(b2.hash(), Some(0))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(b3.hash(), Some(0))).unwrap()); - assert_eq!(None, block_on(longest_chain_select.finality_target(b4.hash(), Some(0))).unwrap()); - assert_eq!( - None, - block_on(longest_chain_select.finality_target(c3.hash().clone(), Some(0))).unwrap(), - ); - assert_eq!( - None, - block_on(longest_chain_select.finality_target(d2.hash().clone(), Some(0))).unwrap(), - ); + assert_eq!(genesis_hash, finality_target(genesis_hash, Some(0))); + assert_eq!(a1.hash(), finality_target(a1.hash(), Some(0))); + assert_eq!(a2.hash(), finality_target(a2.hash(), Some(0))); + assert_eq!(a3.hash(), finality_target(a3.hash(), Some(0))); + assert_eq!(a4.hash(), finality_target(a4.hash(), Some(0))); + assert_eq!(a5.hash(), finality_target(a5.hash(), Some(0))); + assert_eq!(b2.hash(), finality_target(b2.hash(), Some(0))); + assert_eq!(b3.hash(), finality_target(b3.hash(), Some(0))); + assert_eq!(b4.hash(), finality_target(b4.hash(), Some(0))); + assert_eq!(c3.hash(), finality_target(c3.hash(), Some(0))); + assert_eq!(d2.hash(), finality_target(d2.hash(), Some(0))); } #[test] @@ -1177,33 +847,18 @@ fn best_containing_on_longest_chain_with_max_depth_higher_than_best() { assert_eq!( a2.hash(), - block_on(longest_chain_select.finality_target(genesis_hash, Some(10))) - .unwrap() - .unwrap(), + block_on(longest_chain_select.finality_target(genesis_hash, Some(10))).unwrap(), ); } -#[test] -fn key_changes_works() { - let (client, _, test_cases) = prepare_client_with_key_changes(); - - for (index, (begin, end, key, expected_result)) in test_cases.into_iter().enumerate() { - let end = client.block_hash(end).unwrap().unwrap(); - let actual_result = - client.key_changes(begin, BlockId::Hash(end), None, &StorageKey(key)).unwrap(); - if actual_result != expected_result { - panic!( - "Failed test {}: actual = {:?}, expected = {:?}", - index, actual_result, expected_result, - ); - } - } -} - #[test] fn import_with_justification() { + // block tree: + // G -> A1 -> A2 -> A3 let mut client = substrate_test_runtime_client::new(); + let mut finality_notifications = client.finality_notification_stream(); + // G -> A1 let a1 = client.new_block(Default::default()).unwrap().build().unwrap().block; block_on(client.import(BlockOrigin::Own, a1.clone())).unwrap(); @@ -1216,7 +871,7 @@ fn import_with_justification() { .unwrap() .block; block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); - client.finalize_block(BlockId::hash(a2.hash()), None).unwrap(); + client.finalize_block(a2.hash(), None).unwrap(); // A2 -> A3 let justification = Justifications::from((TEST_ENGINE_ID, vec![1, 2, 3])); @@ -1230,11 +885,15 @@ fn import_with_justification() { assert_eq!(client.chain_info().finalized_hash, a3.hash()); - assert_eq!(client.justifications(&BlockId::Hash(a3.hash())).unwrap(), Some(justification)); + assert_eq!(client.justifications(a3.hash()).unwrap(), Some(justification)); - assert_eq!(client.justifications(&BlockId::Hash(a1.hash())).unwrap(), None); + assert_eq!(client.justifications(a1.hash()).unwrap(), None); - assert_eq!(client.justifications(&BlockId::Hash(a2.hash())).unwrap(), None); + assert_eq!(client.justifications(a2.hash()).unwrap(), None); + + finality_notification_check(&mut finality_notifications, &[a1.hash(), a2.hash()], &[]); + finality_notification_check(&mut finality_notifications, &[a3.hash()], &[]); + assert!(finality_notifications.try_next().is_err()); } #[test] @@ -1244,6 +903,9 @@ fn importing_diverged_finalized_block_should_trigger_reorg() { // G -> A1 -> A2 // \ // -> B1 + + let mut finality_notifications = client.finality_notification_stream(); + let a1 = client .new_block_at(&BlockId::Number(0), Default::default(), false) .unwrap() @@ -1282,6 +944,9 @@ fn importing_diverged_finalized_block_should_trigger_reorg() { assert_eq!(client.chain_info().best_hash, b1.hash()); assert_eq!(client.chain_info().finalized_hash, b1.hash()); + + finality_notification_check(&mut finality_notifications, &[b1.hash()], &[a2.hash()]); + assert!(finality_notifications.try_next().is_err()); } #[test] @@ -1291,6 +956,9 @@ fn finalizing_diverged_block_should_trigger_reorg() { // G -> A1 -> A2 // \ // -> B1 -> B2 + + let mut finality_notifications = client.finality_notification_stream(); + let a1 = client .new_block_at(&BlockId::Number(0), Default::default(), false) .unwrap() @@ -1332,7 +1000,7 @@ fn finalizing_diverged_block_should_trigger_reorg() { // we finalize block B1 which is on a different branch from current best // which should trigger a re-org. - ClientExt::finalize_block(&client, BlockId::Hash(b1.hash()), None).unwrap(); + ClientExt::finalize_block(&client, b1.hash(), None).unwrap(); // B1 should now be the latest finalized assert_eq!(client.chain_info().finalized_hash, b1.hash()); @@ -1355,6 +1023,121 @@ fn finalizing_diverged_block_should_trigger_reorg() { block_on(client.import(BlockOrigin::Own, b3.clone())).unwrap(); assert_eq!(client.chain_info().best_hash, b3.hash()); + + ClientExt::finalize_block(&client, b3.hash(), None).unwrap(); + + finality_notification_check(&mut finality_notifications, &[b1.hash()], &[]); + finality_notification_check(&mut finality_notifications, &[b2.hash(), b3.hash()], &[a2.hash()]); + assert!(finality_notifications.try_next().is_err()); +} + +#[test] +fn finality_notifications_content() { + sp_tracing::try_init_simple(); + let (mut client, _select_chain) = TestClientBuilder::new().build_with_longest_chain(); + + // -> D3 -> D4 + // G -> A1 -> A2 -> A3 + // -> B1 -> B2 + // -> C1 + + let mut finality_notifications = client.finality_notification_stream(); + + let a1 = client + .new_block_at(&BlockId::Number(0), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import(BlockOrigin::Own, a1.clone())).unwrap(); + + let a2 = client + .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); + + let a3 = client + .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import(BlockOrigin::Own, a3.clone())).unwrap(); + + let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + // needed to make sure B1 gets a different hash from A1 + b1.push_transfer(Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 1, + nonce: 0, + }) + .unwrap(); + let b1 = b1.build().unwrap().block; + block_on(client.import(BlockOrigin::Own, b1.clone())).unwrap(); + + let b2 = client + .new_block_at(&BlockId::Hash(b1.hash()), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import(BlockOrigin::Own, b2.clone())).unwrap(); + + let mut c1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + // needed to make sure B1 gets a different hash from A1 + c1.push_transfer(Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 2, + nonce: 0, + }) + .unwrap(); + let c1 = c1.build().unwrap().block; + block_on(client.import(BlockOrigin::Own, c1.clone())).unwrap(); + + let mut d3 = client + .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .unwrap(); + // needed to make sure D3 gets a different hash from A3 + d3.push_transfer(Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 2, + nonce: 0, + }) + .unwrap(); + let d3 = d3.build().unwrap().block; + block_on(client.import(BlockOrigin::Own, d3.clone())).unwrap(); + + let d4 = client + .new_block_at(&BlockId::Hash(d3.hash()), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + + // Postpone import to test behavior of import of finalized block. + + ClientExt::finalize_block(&client, a2.hash(), None).unwrap(); + + // Import and finalize D4 + block_on(client.import_as_final(BlockOrigin::Own, d4.clone())).unwrap(); + + finality_notification_check(&mut finality_notifications, &[a1.hash(), a2.hash()], &[c1.hash()]); + finality_notification_check(&mut finality_notifications, &[d3.hash(), d4.hash()], &[b2.hash()]); + assert!(finality_notifications.try_next().is_err()); +} + +#[test] +fn get_block_by_bad_block_hash_returns_none() { + let client = substrate_test_runtime_client::new(); + + let hash = H256::from_low_u64_be(5); + assert!(client.block(&BlockId::Hash(hash)).unwrap().is_none()); } #[test] @@ -1435,11 +1218,9 @@ fn doesnt_import_blocks_that_revert_finality() { let backend = Arc::new( Backend::new( DatabaseSettings { - state_cache_size: 1 << 20, - state_cache_child_ratio: None, - state_pruning: PruningMode::ArchiveAll, - keep_blocks: KeepBlocks::All, - transaction_storage: TransactionStorageMode::BlockBody, + trie_cache_maximum_size: Some(1 << 20), + state_pruning: Some(PruningMode::ArchiveAll), + blocks_pruning: BlocksPruning::KeepAll, source: DatabaseSource::RocksDb { path: tmp.path().into(), cache_size: 1024 }, }, u64::MAX, @@ -1449,9 +1230,11 @@ fn doesnt_import_blocks_that_revert_finality() { let mut client = TestClientBuilder::with_backend(backend).build(); + let mut finality_notifications = client.finality_notification_stream(); + // -> C1 // / - // G -> A1 -> A2 + // G -> A1 -> A2 -> A3 // \ // -> B1 -> B2 -> B3 @@ -1503,15 +1286,11 @@ fn doesnt_import_blocks_that_revert_finality() { // we will finalize A2 which should make it impossible to import a new // B3 at the same height but that doesn't include it - ClientExt::finalize_block(&client, BlockId::Hash(a2.hash()), None).unwrap(); + ClientExt::finalize_block(&client, a2.hash(), None).unwrap(); let import_err = block_on(client.import(BlockOrigin::Own, b3)).err().unwrap(); - let expected_err = ConsensusError::ClientImport( - sp_blockchain::Error::RuntimeApiError(sp_api::ApiError::Application(Box::new( - sp_blockchain::Error::NotInFinalizedChain, - ))) - .to_string(), - ); + let expected_err = + ConsensusError::ClientImport(sp_blockchain::Error::NotInFinalizedChain.to_string()); assert_eq!(import_err.to_string(), expected_err.to_string()); @@ -1534,6 +1313,21 @@ fn doesnt_import_blocks_that_revert_finality() { ConsensusError::ClientImport(sp_blockchain::Error::NotInFinalizedChain.to_string()); assert_eq!(import_err.to_string(), expected_err.to_string()); + + let a3 = client + .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import(BlockOrigin::Own, a3.clone())).unwrap(); + ClientExt::finalize_block(&client, a3.hash(), None).unwrap(); + + finality_notification_check(&mut finality_notifications, &[a1.hash(), a2.hash()], &[]); + + finality_notification_check(&mut finality_notifications, &[a3.hash()], &[b2.hash()]); + + assert!(finality_notifications.try_next().is_err()); } #[test] @@ -1559,9 +1353,9 @@ fn respects_block_rules() { .block; let params = BlockCheckParams { - hash: block_ok.hash().clone(), + hash: block_ok.hash(), number: 0, - parent_hash: block_ok.header().parent_hash().clone(), + parent_hash: *block_ok.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, import_existing: false, @@ -1575,9 +1369,9 @@ fn respects_block_rules() { let block_not_ok = block_not_ok.build().unwrap().block; let params = BlockCheckParams { - hash: block_not_ok.hash().clone(), + hash: block_not_ok.hash(), number: 0, - parent_hash: block_not_ok.header().parent_hash().clone(), + parent_hash: *block_not_ok.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, import_existing: false, @@ -1598,15 +1392,15 @@ fn respects_block_rules() { let block_ok = block_ok.build().unwrap().block; let params = BlockCheckParams { - hash: block_ok.hash().clone(), + hash: block_ok.hash(), number: 1, - parent_hash: block_ok.header().parent_hash().clone(), + parent_hash: *block_ok.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, import_existing: false, }; if record_only { - fork_rules.push((1, block_ok.hash().clone())); + fork_rules.push((1, block_ok.hash())); } assert_eq!(block_on(client.check_block(params)).unwrap(), ImportResult::imported(false)); @@ -1617,9 +1411,9 @@ fn respects_block_rules() { let block_not_ok = block_not_ok.build().unwrap().block; let params = BlockCheckParams { - hash: block_not_ok.hash().clone(), + hash: block_not_ok.hash(), number: 1, - parent_hash: block_not_ok.header().parent_hash().clone(), + parent_hash: *block_not_ok.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, import_existing: false, @@ -1650,11 +1444,9 @@ fn returns_status_for_pruned_blocks() { let backend = Arc::new( Backend::new( DatabaseSettings { - state_cache_size: 1 << 20, - state_cache_child_ratio: None, - state_pruning: PruningMode::keep_blocks(1), - keep_blocks: KeepBlocks::All, - transaction_storage: TransactionStorageMode::BlockBody, + trie_cache_maximum_size: Some(1 << 20), + state_pruning: Some(PruningMode::blocks_pruning(1)), + blocks_pruning: BlocksPruning::KeepFinalized, source: DatabaseSource::RocksDb { path: tmp.path().into(), cache_size: 1024 }, }, u64::MAX, @@ -1684,9 +1476,9 @@ fn returns_status_for_pruned_blocks() { let b1 = b1.build().unwrap().block; let check_block_a1 = BlockCheckParams { - hash: a1.hash().clone(), + hash: a1.hash(), number: 0, - parent_hash: a1.header().parent_hash().clone(), + parent_hash: *a1.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, import_existing: false, @@ -1721,9 +1513,9 @@ fn returns_status_for_pruned_blocks() { block_on(client.import_as_final(BlockOrigin::Own, a2.clone())).unwrap(); let check_block_a2 = BlockCheckParams { - hash: a2.hash().clone(), + hash: a2.hash(), number: 1, - parent_hash: a1.header().parent_hash().clone(), + parent_hash: *a1.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, import_existing: false, @@ -1755,9 +1547,9 @@ fn returns_status_for_pruned_blocks() { block_on(client.import_as_final(BlockOrigin::Own, a3.clone())).unwrap(); let check_block_a3 = BlockCheckParams { - hash: a3.hash().clone(), + hash: a3.hash(), number: 2, - parent_hash: a2.header().parent_hash().clone(), + parent_hash: *a2.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, import_existing: false, @@ -1790,9 +1582,9 @@ fn returns_status_for_pruned_blocks() { ); let mut check_block_b1 = BlockCheckParams { - hash: b1.hash().clone(), + hash: b1.hash(), number: 0, - parent_hash: b1.header().parent_hash().clone(), + parent_hash: *b1.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, import_existing: false, @@ -1813,152 +1605,6 @@ fn returns_status_for_pruned_blocks() { ); } -#[test] -fn imports_blocks_with_changes_tries_config_change() { - // create client with initial 4^2 configuration - let mut client = TestClientBuilder::with_default_backend() - .changes_trie_config(Some(ChangesTrieConfiguration { - digest_interval: 4, - digest_levels: 2, - })) - .build(); - - // =================================================================== - // blocks 1,2,3,4,5,6,7,8,9,10 are empty - // block 11 changes the key - // block 12 is the L1 digest that covers this change - // blocks 13,14,15,16,17,18,19,20,21,22 are empty - // block 23 changes the configuration to 5^1 AND is skewed digest - // =================================================================== - // blocks 24,25 are changing the key - // block 26 is empty - // block 27 changes the key - // block 28 is the L1 digest (NOT SKEWED!!!) that covers changes AND changes configuration to - // `3^1` - // =================================================================== - // block 29 is empty - // block 30 changes the key - // block 31 is L1 digest that covers this change - // =================================================================== - (1..11).for_each(|number| { - let block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap() - .build() - .unwrap() - .block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - (11..12).for_each(|number| { - let mut block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap(); - block - .push_storage_change(vec![42], Some(number.to_le_bytes().to_vec())) - .unwrap(); - let block = block.build().unwrap().block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - (12..23).for_each(|number| { - let block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap() - .build() - .unwrap() - .block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - (23..24).for_each(|number| { - let mut block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap(); - block - .push_changes_trie_configuration_update(Some(ChangesTrieConfiguration { - digest_interval: 5, - digest_levels: 1, - })) - .unwrap(); - let block = block.build().unwrap().block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - (24..26).for_each(|number| { - let mut block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap(); - block - .push_storage_change(vec![42], Some(number.to_le_bytes().to_vec())) - .unwrap(); - let block = block.build().unwrap().block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - (26..27).for_each(|number| { - let block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap() - .build() - .unwrap() - .block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - (27..28).for_each(|number| { - let mut block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap(); - block - .push_storage_change(vec![42], Some(number.to_le_bytes().to_vec())) - .unwrap(); - let block = block.build().unwrap().block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - (28..29).for_each(|number| { - let mut block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap(); - block - .push_changes_trie_configuration_update(Some(ChangesTrieConfiguration { - digest_interval: 3, - digest_levels: 1, - })) - .unwrap(); - let block = block.build().unwrap().block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - (29..30).for_each(|number| { - let block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap() - .build() - .unwrap() - .block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - (30..31).for_each(|number| { - let mut block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap(); - block - .push_storage_change(vec![42], Some(number.to_le_bytes().to_vec())) - .unwrap(); - let block = block.build().unwrap().block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - (31..32).for_each(|number| { - let block = client - .new_block_at(&BlockId::Number(number - 1), Default::default(), false) - .unwrap() - .build() - .unwrap() - .block; - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - }); - - // now check that configuration cache works - assert_eq!( - client.key_changes(1, BlockId::Number(31), None, &StorageKey(vec![42])).unwrap(), - vec![(30, 0), (27, 0), (25, 0), (24, 0), (11, 0)] - ); -} - #[test] fn storage_keys_iter_prefix_and_start_key_works() { let child_info = ChildInfo::new_default(b"child"); @@ -1968,36 +1614,42 @@ fn storage_keys_iter_prefix_and_start_key_works() { .add_extra_child_storage(&child_info, b"third".to_vec(), vec![0u8; 32]) .build(); + let block_hash = client.info().best_hash; + let child_root = b":child_storage:default:child".to_vec(); - let prefix = StorageKey(hex!("3a").to_vec()); + let prefix = StorageKey(array_bytes::hex2bytes_unchecked("3a")); let child_prefix = StorageKey(b"sec".to_vec()); let res: Vec<_> = client - .storage_keys_iter(&BlockId::Number(0), Some(&prefix), None) + .storage_keys_iter(block_hash, Some(&prefix), None) .unwrap() .map(|x| x.0) .collect(); assert_eq!( res, - [child_root.clone(), hex!("3a636f6465").to_vec(), hex!("3a686561707061676573").to_vec(),] + [ + child_root.clone(), + array_bytes::hex2bytes_unchecked("3a636f6465"), + array_bytes::hex2bytes_unchecked("3a686561707061676573"), + ] ); let res: Vec<_> = client .storage_keys_iter( - &BlockId::Number(0), + block_hash, Some(&prefix), - Some(&StorageKey(hex!("3a636f6465").to_vec())), + Some(&StorageKey(array_bytes::hex2bytes_unchecked("3a636f6465"))), ) .unwrap() .map(|x| x.0) .collect(); - assert_eq!(res, [hex!("3a686561707061676573").to_vec()]); + assert_eq!(res, [array_bytes::hex2bytes_unchecked("3a686561707061676573")]); let res: Vec<_> = client .storage_keys_iter( - &BlockId::Number(0), + block_hash, Some(&prefix), - Some(&StorageKey(hex!("3a686561707061676573").to_vec())), + Some(&StorageKey(array_bytes::hex2bytes_unchecked("3a686561707061676573"))), ) .unwrap() .map(|x| x.0) @@ -2005,7 +1657,7 @@ fn storage_keys_iter_prefix_and_start_key_works() { assert_eq!(res, Vec::>::new()); let res: Vec<_> = client - .child_storage_keys_iter(&BlockId::Number(0), child_info.clone(), Some(&child_prefix), None) + .child_storage_keys_iter(block_hash, child_info.clone(), Some(&child_prefix), None) .unwrap() .map(|x| x.0) .collect(); @@ -2013,7 +1665,7 @@ fn storage_keys_iter_prefix_and_start_key_works() { let res: Vec<_> = client .child_storage_keys_iter( - &BlockId::Number(0), + block_hash, child_info, None, Some(&StorageKey(b"second".to_vec())), @@ -2028,53 +1680,75 @@ fn storage_keys_iter_prefix_and_start_key_works() { fn storage_keys_iter_works() { let client = substrate_test_runtime_client::new(); - let prefix = StorageKey(hex!("").to_vec()); + let block_hash = client.info().best_hash; + + let prefix = StorageKey(array_bytes::hex2bytes_unchecked("")); let res: Vec<_> = client - .storage_keys_iter(&BlockId::Number(0), Some(&prefix), None) + .storage_keys_iter(block_hash, Some(&prefix), None) .unwrap() - .take(2) - .map(|x| x.0) + .take(9) + .map(|x| array_bytes::bytes2hex("", &x.0)) .collect(); assert_eq!( res, - [hex!("0befda6e1ca4ef40219d588a727f1271").to_vec(), hex!("3a636f6465").to_vec()] + [ + "00c232cf4e70a5e343317016dc805bf80a6a8cd8ad39958d56f99891b07851e0", + "085b2407916e53a86efeb8b72dbe338c4b341dab135252f96b6ed8022209b6cb", + "0befda6e1ca4ef40219d588a727f1271", + "1a560ecfd2a62c2b8521ef149d0804eb621050e3988ed97dca55f0d7c3e6aa34", + "1d66850d32002979d67dd29dc583af5b2ae2a1f71c1f35ad90fff122be7a3824", + "237498b98d8803334286e9f0483ef513098dd3c1c22ca21c4dc155b4ef6cc204", + "26aa394eea5630e07c48ae0c9558cef75e0621c4869aa60c02be9adcc98a0d1d", + "29b9db10ec5bf7907d8f74b5e60aa8140c4fbdd8127a1ee5600cb98e5ec01729", + "3a636f6465", + ] ); let res: Vec<_> = client .storage_keys_iter( - &BlockId::Number(0), + block_hash, Some(&prefix), - Some(&StorageKey(hex!("3a636f6465").to_vec())), + Some(&StorageKey(array_bytes::hex2bytes_unchecked("3a636f6465"))), ) .unwrap() - .take(3) - .map(|x| x.0) + .take(7) + .map(|x| array_bytes::bytes2hex("", &x.0)) .collect(); assert_eq!( res, [ - hex!("3a686561707061676573").to_vec(), - hex!("6644b9b8bc315888ac8e41a7968dc2b4141a5403c58acdf70b7e8f7e07bf5081").to_vec(), - hex!("79c07e2b1d2e2abfd4855b936617eeff5e0621c4869aa60c02be9adcc98a0d1d").to_vec(), + "3a686561707061676573", + "52008686cc27f6e5ed83a216929942f8bcd32a396f09664a5698f81371934b56", + "5348d72ac6cc66e5d8cbecc27b0e0677503b845fe2382d819f83001781788fd5", + "5c2d5fda66373dabf970e4fb13d277ce91c5233473321129d32b5a8085fa8133", + "6644b9b8bc315888ac8e41a7968dc2b4141a5403c58acdf70b7e8f7e07bf5081", + "66484000ed3f75c95fc7b03f39c20ca1e1011e5999278247d3b2f5e3c3273808", + "7d5007603a7f5dd729d51d93cf695d6465789443bb967c0d1fe270e388c96eaa", ] ); let res: Vec<_> = client .storage_keys_iter( - &BlockId::Number(0), + block_hash, Some(&prefix), - Some(&StorageKey( - hex!("79c07e2b1d2e2abfd4855b936617eeff5e0621c4869aa60c02be9adcc98a0d1d").to_vec(), - )), + Some(&StorageKey(array_bytes::hex2bytes_unchecked( + "7d5007603a7f5dd729d51d93cf695d6465789443bb967c0d1fe270e388c96eaa", + ))), ) .unwrap() - .take(1) - .map(|x| x.0) + .take(5) + .map(|x| array_bytes::bytes2hex("", &x.0)) .collect(); assert_eq!( res, - [hex!("cf722c0832b5231d35e29f319ff27389f5032bfc7bfc3ba5ed7839f2042fb99f").to_vec()] + [ + "811ecfaadcf5f2ee1d67393247e2f71a1662d433e8ce7ff89fb0d4aa9561820b", + "a93d74caa7ec34ea1b04ce1e5c090245f867d333f0f88278a451e45299654dc5", + "a9ee1403384afbfc13f13be91ff70bfac057436212e53b9733914382ac942892", + "cf722c0832b5231d35e29f319ff27389f5032bfc7bfc3ba5ed7839f2042fb99f", + "e3b47b6c84c0493481f97c5197d2554f", + ] ); } @@ -2085,12 +1759,7 @@ fn cleans_up_closed_notification_sinks_on_block_import() { // NOTE: we need to build the client here instead of using the client // provided by test_runtime_client otherwise we can't access the private // `import_notification_sinks` and `finality_notification_sinks` fields. - let mut client = new_in_mem::< - _, - substrate_test_runtime_client::runtime::Block, - _, - substrate_test_runtime_client::runtime::RuntimeApi, - >( + let mut client = new_in_mem::<_, Block, _, RuntimeApi>( substrate_test_runtime_client::new_native_executor(), &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), None, @@ -2108,8 +1777,8 @@ fn cleans_up_closed_notification_sinks_on_block_import() { in_mem::Backend, sc_executor::NativeElseWasmExecutor, >, - substrate_test_runtime_client::runtime::Block, - substrate_test_runtime_client::runtime::RuntimeApi, + Block, + RuntimeApi, >; let import_notif1 = client.import_notification_stream(); @@ -2207,3 +1876,40 @@ fn reorg_triggers_a_notification_even_for_sources_that_should_not_trigger_notifi let tree_route = notification.tree_route.unwrap(); assert_eq!(tree_route.enacted()[0].hash, b1.hash()); } + +#[test] +fn use_dalek_ext_works() { + fn zero_ed_pub() -> sp_core::ed25519::Public { + sp_core::ed25519::Public([0u8; 32]) + } + + fn zero_ed_sig() -> sp_core::ed25519::Signature { + sp_core::ed25519::Signature::from_raw([0u8; 64]) + } + + let mut client = TestClientBuilder::new().build(); + + client.execution_extensions().set_extensions_factory( + sc_client_api::execution_extensions::ExtensionBeforeBlock::::new( + 1, + ), + ); + + let a1 = client + .new_block_at(&BlockId::Number(0), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import(BlockOrigin::NetworkInitialSync, a1.clone())).unwrap(); + + // On block zero it will use dalek and then on block 1 it will use zebra + assert!(!client + .runtime_api() + .verify_ed25519(&BlockId::Number(0), zero_ed_sig(), zero_ed_pub(), vec![]) + .unwrap()); + assert!(client + .runtime_api() + .verify_ed25519(&BlockId::Number(1), zero_ed_sig(), zero_ed_pub(), vec![]) + .unwrap()); +} diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index 8000c536cdf93..5f75e3521e235 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -22,19 +22,21 @@ use futures::{task::Poll, Future, TryFutureExt as _}; use log::{debug, info}; use parking_lot::Mutex; use sc_client_api::{Backend, CallExecutor}; -use sc_network::{ - config::{NetworkConfiguration, TransportConfig}, - multiaddr, Multiaddr, +use sc_network::{config::NetworkConfiguration, multiaddr}; +use sc_network_common::{ + config::{MultiaddrWithPeerId, TransportConfig}, + service::{NetworkBlock, NetworkPeers, NetworkStateInfo}, }; use sc_service::{ client::Client, config::{BasePath, DatabaseSource, KeystoreConfig}, - ChainSpecExtension, Configuration, Error, GenericChainSpec, KeepBlocks, Role, RuntimeGenesis, - SpawnTaskHandle, TaskManager, TransactionStorageMode, + BlocksPruning, ChainSpecExtension, Configuration, Error, GenericChainSpec, Role, + RuntimeGenesis, SpawnTaskHandle, TaskManager, }; use sc_transaction_pool_api::TransactionPool; +use sp_api::BlockId; use sp_blockchain::HeaderBackend; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_runtime::traits::Block as BlockT; use std::{iter, net::Ipv4Addr, pin::Pin, sync::Arc, task::Context, time::Duration}; use tempfile::TempDir; use tokio::{runtime::Runtime, time}; @@ -45,22 +47,20 @@ mod client; /// Maximum duration of single wait call. const MAX_WAIT_TIME: Duration = Duration::from_secs(60 * 3); -struct TestNet { +struct TestNet { runtime: Runtime, - authority_nodes: Vec<(usize, F, U, Multiaddr)>, - full_nodes: Vec<(usize, F, U, Multiaddr)>, - light_nodes: Vec<(usize, L, Multiaddr)>, + authority_nodes: Vec<(usize, F, U, MultiaddrWithPeerId)>, + full_nodes: Vec<(usize, F, U, MultiaddrWithPeerId)>, chain_spec: GenericChainSpec, base_port: u16, nodes: usize, } -impl Drop for TestNet { +impl Drop for TestNet { fn drop(&mut self) { // Drop the nodes before dropping the runtime, as the runtime otherwise waits for all // futures to be ended and we run into a dead lock. self.full_nodes.drain(..); - self.light_nodes.drain(..); self.authority_nodes.drain(..); } } @@ -156,39 +156,26 @@ where } } -impl TestNet +impl TestNet where F: Clone + Send + 'static, - L: Clone + Send + 'static, U: Clone + Send + 'static, { - pub fn run_until_all_full(&mut self, full_predicate: FP, light_predicate: LP) + pub fn run_until_all_full(&mut self, full_predicate: FP) where FP: Send + Fn(usize, &F) -> bool + 'static, - LP: Send + Fn(usize, &L) -> bool + 'static, { let full_nodes = self.full_nodes.clone(); - let light_nodes = self.light_nodes.clone(); let future = async move { let mut interval = time::interval(Duration::from_millis(100)); - loop { interval.tick().await; - let full_ready = full_nodes - .iter() - .all(|&(ref id, ref service, _, _)| full_predicate(*id, service)); - - if !full_ready { - continue - } - - let light_ready = light_nodes + if full_nodes .iter() - .all(|&(ref id, ref service, _)| light_predicate(*id, service)); - - if light_ready { - return + .all(|&(ref id, ref service, _, _)| full_predicate(*id, service)) + { + break } } }; @@ -245,11 +232,9 @@ fn node_config< keystore_remote: Default::default(), keystore: KeystoreConfig::Path { path: root.join("key"), password: None }, database: DatabaseSource::RocksDb { path: root.join("db"), cache_size: 128 }, - state_cache_size: 16777216, - state_cache_child_ratio: None, + trie_cache_maximum_size: Some(16 * 1024 * 1024), state_pruning: Default::default(), - keep_blocks: KeepBlocks::All, - transaction_storage: TransactionStorageMode::BlockBody, + blocks_pruning: BlocksPruning::KeepFinalized, chain_spec: Box::new((*spec).clone()), wasm_method: sc_service::config::WasmExecutionMethod::Interpreted, wasm_runtime_overrides: Default::default(), @@ -261,6 +246,11 @@ fn node_config< rpc_cors: None, rpc_methods: Default::default(), rpc_max_payload: None, + rpc_max_request_size: None, + rpc_max_response_size: None, + rpc_id_provider: None, + rpc_max_subs_per_conn: None, + ws_max_out_buffer_capacity: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, @@ -274,14 +264,13 @@ fn node_config< announce_block: true, base_path: Some(BasePath::new(root)), informant_output_format: Default::default(), - disable_log_reloading: false, + runtime_cache_size: 2, } } -impl TestNet +impl TestNet where F: TestNetNode, - L: TestNetNode, E: ChainSpecExtension + Clone + 'static + Send + Sync, G: RuntimeGenesis + 'static, { @@ -289,10 +278,9 @@ where temp: &TempDir, spec: GenericChainSpec, full: impl Iterator Result<(F, U), Error>>, - light: impl Iterator Result>, authorities: impl Iterator Result<(F, U), Error>)>, base_port: u16, - ) -> TestNet { + ) -> TestNet { sp_tracing::try_init_simple(); fdlimit::raise_fd_limit(); let runtime = Runtime::new().expect("Error creating tokio runtime"); @@ -300,12 +288,11 @@ where runtime, authority_nodes: Default::default(), full_nodes: Default::default(), - light_nodes: Default::default(), chain_spec: spec, base_port, nodes: 0, }; - net.insert_nodes(temp, full, light, authorities); + net.insert_nodes(temp, full, authorities); net } @@ -313,71 +300,57 @@ where &mut self, temp: &TempDir, full: impl Iterator Result<(F, U), Error>>, - light: impl Iterator Result>, authorities: impl Iterator Result<(F, U), Error>)>, ) { - let handle = self.runtime.handle().clone(); - - for (key, authority) in authorities { - let node_config = node_config( - self.nodes, - &self.chain_spec, - Role::Authority, - handle.clone(), - Some(key), - self.base_port, - &temp, - ); - let addr = node_config.network.listen_addresses.iter().next().unwrap().clone(); - let (service, user_data) = - authority(node_config).expect("Error creating test node service"); - - handle.spawn(service.clone().map_err(|_| ())); - let addr = addr - .with(multiaddr::Protocol::P2p(service.network().local_peer_id().clone().into())); - self.authority_nodes.push((self.nodes, service, user_data, addr)); - self.nodes += 1; - } - - for full in full { - let node_config = node_config( - self.nodes, - &self.chain_spec, - Role::Full, - handle.clone(), - None, - self.base_port, - &temp, - ); - let addr = node_config.network.listen_addresses.iter().next().unwrap().clone(); - let (service, user_data) = full(node_config).expect("Error creating test node service"); - - handle.spawn(service.clone().map_err(|_| ())); - let addr = addr - .with(multiaddr::Protocol::P2p(service.network().local_peer_id().clone().into())); - self.full_nodes.push((self.nodes, service, user_data, addr)); - self.nodes += 1; - } + self.runtime.block_on(async { + let handle = self.runtime.handle().clone(); + + for (key, authority) in authorities { + let node_config = node_config( + self.nodes, + &self.chain_spec, + Role::Authority, + handle.clone(), + Some(key), + self.base_port, + temp, + ); + let addr = node_config.network.listen_addresses.first().unwrap().clone(); + let (service, user_data) = + authority(node_config).expect("Error creating test node service"); + + handle.spawn(service.clone().map_err(|_| ())); + let addr = MultiaddrWithPeerId { + multiaddr: addr, + peer_id: service.network().local_peer_id(), + }; + self.authority_nodes.push((self.nodes, service, user_data, addr)); + self.nodes += 1; + } - for light in light { - let node_config = node_config( - self.nodes, - &self.chain_spec, - Role::Light, - handle.clone(), - None, - self.base_port, - &temp, - ); - let addr = node_config.network.listen_addresses.iter().next().unwrap().clone(); - let service = light(node_config).expect("Error creating test node service"); - - handle.spawn(service.clone().map_err(|_| ())); - let addr = addr - .with(multiaddr::Protocol::P2p(service.network().local_peer_id().clone().into())); - self.light_nodes.push((self.nodes, service, addr)); - self.nodes += 1; - } + for full in full { + let node_config = node_config( + self.nodes, + &self.chain_spec, + Role::Full, + handle.clone(), + None, + self.base_port, + temp, + ); + let addr = node_config.network.listen_addresses.first().unwrap().clone(); + let (service, user_data) = + full(node_config).expect("Error creating test node service"); + + handle.spawn(service.clone().map_err(|_| ())); + let addr = MultiaddrWithPeerId { + multiaddr: addr, + peer_id: service.network().local_peer_id(), + }; + self.full_nodes.push((self.nodes, service, user_data, addr)); + self.nodes += 1; + } + }); } } @@ -388,23 +361,16 @@ fn tempdir_with_prefix(prefix: &str) -> TempDir { .expect("Error creating test dir") } -pub fn connectivity( - spec: GenericChainSpec, - full_builder: Fb, - light_builder: Lb, -) where +pub fn connectivity(spec: GenericChainSpec, full_builder: Fb) +where E: ChainSpecExtension + Clone + 'static + Send + Sync, G: RuntimeGenesis + 'static, Fb: Fn(Configuration) -> Result, F: TestNetNode, - Lb: Fn(Configuration) -> Result, - L: TestNetNode, { const NUM_FULL_NODES: usize = 5; - const NUM_LIGHT_NODES: usize = 5; - let expected_full_connections = NUM_FULL_NODES - 1 + NUM_LIGHT_NODES; - let expected_light_connections = NUM_FULL_NODES; + let expected_full_connections = NUM_FULL_NODES - 1; { let temp = tempdir_with_prefix("substrate-connectivity-test"); @@ -413,7 +379,6 @@ pub fn connectivity( &temp, spec.clone(), (0..NUM_FULL_NODES).map(|_| |cfg| full_builder(cfg).map(|s| (s, ()))), - (0..NUM_LIGHT_NODES).map(|_| |cfg| light_builder(cfg)), // Note: this iterator is empty but we can't just use `iter::empty()`, otherwise // the type of the closure cannot be inferred. (0..0).map(|_| (String::new(), { |cfg| full_builder(cfg).map(|s| (s, ())) })), @@ -424,28 +389,15 @@ pub fn connectivity( for (_, service, _, _) in network.full_nodes.iter().skip(1) { service .network() - .add_reserved_peer(first_address.to_string()) - .expect("Error adding reserved peer"); - } - for (_, service, _) in network.light_nodes.iter() { - service - .network() - .add_reserved_peer(first_address.to_string()) + .add_reserved_peer(first_address.clone()) .expect("Error adding reserved peer"); } - network.run_until_all_full( - move |_index, service| { - let connected = service.network().num_connected(); - debug!("Got {}/{} full connections...", connected, expected_full_connections); - connected == expected_full_connections - }, - move |_index, service| { - let connected = service.network().num_connected(); - debug!("Got {}/{} light connections...", connected, expected_light_connections); - connected == expected_light_connections - }, - ); + network.run_until_all_full(move |_index, service| { + let connected = service.network().sync_num_connected(); + debug!("Got {}/{} full connections...", connected, expected_full_connections); + connected == expected_full_connections + }); }; temp.close().expect("Error removing temp dir"); @@ -457,7 +409,6 @@ pub fn connectivity( &temp, spec, (0..NUM_FULL_NODES).map(|_| |cfg| full_builder(cfg).map(|s| (s, ()))), - (0..NUM_LIGHT_NODES).map(|_| |cfg| light_builder(cfg)), // Note: this iterator is empty but we can't just use `iter::empty()`, otherwise // the type of the closure cannot be inferred. (0..0).map(|_| (String::new(), { |cfg| full_builder(cfg).map(|s| (s, ())) })), @@ -465,55 +416,36 @@ pub fn connectivity( ); info!("Checking linked topology"); let mut address = network.full_nodes[0].3.clone(); - let max_nodes = std::cmp::max(NUM_FULL_NODES, NUM_LIGHT_NODES); - for i in 0..max_nodes { + for i in 0..NUM_FULL_NODES { if i != 0 { if let Some((_, service, _, node_id)) = network.full_nodes.get(i) { service .network() - .add_reserved_peer(address.to_string()) + .add_reserved_peer(address) .expect("Error adding reserved peer"); address = node_id.clone(); } } - - if let Some((_, service, node_id)) = network.light_nodes.get(i) { - service - .network() - .add_reserved_peer(address.to_string()) - .expect("Error adding reserved peer"); - address = node_id.clone(); - } } - network.run_until_all_full( - move |_index, service| { - let connected = service.network().num_connected(); - debug!("Got {}/{} full connections...", connected, expected_full_connections); - connected == expected_full_connections - }, - move |_index, service| { - let connected = service.network().num_connected(); - debug!("Got {}/{} light connections...", connected, expected_light_connections); - connected == expected_light_connections - }, - ); + network.run_until_all_full(move |_index, service| { + let connected = service.network().sync_num_connected(); + debug!("Got {}/{} full connections...", connected, expected_full_connections); + connected == expected_full_connections + }); } temp.close().expect("Error removing temp dir"); } } -pub fn sync( +pub fn sync( spec: GenericChainSpec, full_builder: Fb, - light_builder: Lb, mut make_block_and_import: B, mut extrinsic_factory: ExF, ) where Fb: Fn(Configuration) -> Result<(F, U), Error>, F: TestNetNode, - Lb: Fn(Configuration) -> Result, - L: TestNetNode, B: FnMut(&F, &mut U), ExF: FnMut(&F, &U) -> ::Extrinsic, U: Clone + Send + 'static, @@ -521,15 +453,12 @@ pub fn sync( G: RuntimeGenesis + 'static, { const NUM_FULL_NODES: usize = 10; - // FIXME: BABE light client support is currently not working. - const NUM_LIGHT_NODES: usize = 10; const NUM_BLOCKS: usize = 512; let temp = tempdir_with_prefix("substrate-sync-test"); let mut network = TestNet::new( &temp, spec, (0..NUM_FULL_NODES).map(|_| |cfg| full_builder(cfg)), - (0..NUM_LIGHT_NODES).map(|_| |cfg| light_builder(cfg)), // Note: this iterator is empty but we can't just use `iter::empty()`, otherwise // the type of the closure cannot be inferred. (0..0).map(|_| (String::new(), { |cfg| full_builder(cfg) })), @@ -543,7 +472,7 @@ pub fn sync( info!("Generating #{}", i + 1); } - make_block_and_import(&first_service, first_user_data); + make_block_and_import(first_service, first_user_data); } let info = network.full_nodes[0].1.client().info(); network.full_nodes[0] @@ -557,19 +486,13 @@ pub fn sync( for (_, service, _, _) in network.full_nodes.iter().skip(1) { service .network() - .add_reserved_peer(first_address.to_string()) + .add_reserved_peer(first_address.clone()) .expect("Error adding reserved peer"); } - for (_, service, _) in network.light_nodes.iter() { - service - .network() - .add_reserved_peer(first_address.to_string()) - .expect("Error adding reserved peer"); - } - network.run_until_all_full( - |_index, service| service.client().info().best_number == (NUM_BLOCKS as u32).into(), - |_index, service| service.client().info().best_number == (NUM_BLOCKS as u32).into(), - ); + + network.run_until_all_full(|_index, service| { + service.client().info().best_number == (NUM_BLOCKS as u32).into() + }); info!("Checking extrinsic propagation"); let first_service = network.full_nodes[0].1.clone(); @@ -585,34 +508,26 @@ pub fn sync( )) .expect("failed to submit extrinsic"); - network.run_until_all_full( - |_index, service| service.transaction_pool().ready().count() == 1, - |_index, _service| true, - ); + network.run_until_all_full(|_index, service| service.transaction_pool().ready().count() == 1); } -pub fn consensus( +pub fn consensus( spec: GenericChainSpec, full_builder: Fb, - light_builder: Lb, authorities: impl IntoIterator, ) where Fb: Fn(Configuration) -> Result, F: TestNetNode, - Lb: Fn(Configuration) -> Result, - L: TestNetNode, E: ChainSpecExtension + Clone + 'static + Send + Sync, G: RuntimeGenesis + 'static, { const NUM_FULL_NODES: usize = 10; - const NUM_LIGHT_NODES: usize = 10; const NUM_BLOCKS: usize = 10; // 10 * 2 sec block production time = ~20 seconds let temp = tempdir_with_prefix("substrate-consensus-test"); let mut network = TestNet::new( &temp, spec, (0..NUM_FULL_NODES / 2).map(|_| |cfg| full_builder(cfg).map(|s| (s, ()))), - (0..NUM_LIGHT_NODES / 2).map(|_| |cfg| light_builder(cfg)), authorities .into_iter() .map(|key| (key, { |cfg| full_builder(cfg).map(|s| (s, ())) })), @@ -624,33 +539,23 @@ pub fn consensus( for (_, service, _, _) in network.full_nodes.iter() { service .network() - .add_reserved_peer(first_address.to_string()) - .expect("Error adding reserved peer"); - } - for (_, service, _) in network.light_nodes.iter() { - service - .network() - .add_reserved_peer(first_address.to_string()) + .add_reserved_peer(first_address.clone()) .expect("Error adding reserved peer"); } for (_, service, _, _) in network.authority_nodes.iter().skip(1) { service .network() - .add_reserved_peer(first_address.to_string()) + .add_reserved_peer(first_address.clone()) .expect("Error adding reserved peer"); } - network.run_until_all_full( - |_index, service| { - service.client().info().finalized_number >= (NUM_BLOCKS as u32 / 2).into() - }, - |_index, service| service.client().info().best_number >= (NUM_BLOCKS as u32 / 2).into(), - ); + network.run_until_all_full(|_index, service| { + service.client().info().finalized_number >= (NUM_BLOCKS as u32 / 2).into() + }); info!("Adding more peers"); network.insert_nodes( &temp, (0..NUM_FULL_NODES / 2).map(|_| |cfg| full_builder(cfg).map(|s| (s, ()))), - (0..NUM_LIGHT_NODES / 2).map(|_| |cfg| light_builder(cfg)), // Note: this iterator is empty but we can't just use `iter::empty()`, otherwise // the type of the closure cannot be inferred. (0..0).map(|_| (String::new(), { |cfg| full_builder(cfg).map(|s| (s, ())) })), @@ -658,17 +563,11 @@ pub fn consensus( for (_, service, _, _) in network.full_nodes.iter() { service .network() - .add_reserved_peer(first_address.to_string()) + .add_reserved_peer(first_address.clone()) .expect("Error adding reserved peer"); } - for (_, service, _) in network.light_nodes.iter() { - service - .network() - .add_reserved_peer(first_address.to_string()) - .expect("Error adding reserved peer"); - } - network.run_until_all_full( - |_index, service| service.client().info().finalized_number >= (NUM_BLOCKS as u32).into(), - |_index, service| service.client().info().best_number >= (NUM_BLOCKS as u32).into(), - ); + + network.run_until_all_full(|_index, service| { + service.client().info().finalized_number >= (NUM_BLOCKS as u32).into() + }); } diff --git a/client/state-db/Cargo.toml b/client/state-db/Cargo.toml index 93d5e1464b39b..b6d40a8106322 100644 --- a/client/state-db/Cargo.toml +++ b/client/state-db/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-state-db" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "State database maintenance. Handles canonicalization and pruning in the database." readme = "README.md" @@ -13,10 +13,8 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -parking_lot = "0.11.1" -log = "0.4.11" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +log = "0.4.17" +parking_lot = "0.12.1" sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -codec = { package = "parity-scale-codec", version = "2.0.0", features = ["derive"] } -parity-util-mem = { version = "0.10.0", default-features = false, features = ["primitive-types"] } -parity-util-mem-derive = "0.1.0" +sp-core = { version = "7.0.0", path = "../../primitives/core" } diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index 44629975d7813..94d41787701b3 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -49,10 +49,8 @@ mod test; use codec::Codec; use log::trace; use noncanonical::NonCanonicalOverlay; -use parity_util_mem::{malloc_size, MallocSizeOf}; use parking_lot::RwLock; -use pruning::RefWindow; -use sc_client_api::{MemorySize, StateDbMemoryInfo}; +use pruning::{HaveBlock, RefWindow}; use std::{ collections::{hash_map::Entry, HashMap}, fmt, @@ -62,6 +60,7 @@ const PRUNING_MODE: &[u8] = b"mode"; const PRUNING_MODE_ARCHIVE: &[u8] = b"archive"; const PRUNING_MODE_ARCHIVE_CANON: &[u8] = b"archive_canonical"; const PRUNING_MODE_CONSTRAINED: &[u8] = b"constrained"; +pub(crate) const DEFAULT_MAX_BLOCK_CONSTRAINT: u32 = 256; /// Database value type. pub type DBValue = Vec; @@ -115,9 +114,15 @@ pub trait NodeDb { } /// Error type. -pub enum Error { +#[derive(Eq, PartialEq)] +pub enum Error { /// Database backend error. Db(E), + StateDb(StateDbError), +} + +#[derive(Eq, PartialEq)] +pub enum StateDbError { /// `Codec` decoding error. Decoding(codec::Error), /// Trying to canonicalize invalid block. @@ -127,9 +132,23 @@ pub enum Error { /// Trying to insert block with unknown parent. InvalidParent, /// Invalid pruning mode specified. Contains expected mode. - InvalidPruningMode(String), + IncompatiblePruningModes { stored: PruningMode, requested: PruningMode }, /// Too many unfinalized sibling blocks inserted. TooManySiblingBlocks, + /// Trying to insert existing block. + BlockAlreadyExists, + /// Invalid metadata + Metadata(String), + /// Trying to get a block record from db while it is not commit to db yet + BlockUnavailable, + /// Block record is missing from the pruning window + BlockMissing, +} + +impl From for Error { + fn from(inner: StateDbError) -> Self { + Self::StateDb(inner) + } } /// Pinning error type. @@ -140,20 +159,37 @@ pub enum PinError { impl From for Error { fn from(x: codec::Error) -> Self { - Error::Decoding(x) + StateDbError::Decoding(x).into() } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Error::Db(e) => e.fmt(f), - Error::Decoding(e) => write!(f, "Error decoding sliceable value: {}", e), - Error::InvalidBlock => write!(f, "Trying to canonicalize invalid block"), - Error::InvalidBlockNumber => write!(f, "Trying to insert block with invalid number"), - Error::InvalidParent => write!(f, "Trying to insert block with unknown parent"), - Error::InvalidPruningMode(e) => write!(f, "Expected pruning mode: {}", e), - Error::TooManySiblingBlocks => write!(f, "Too many sibling blocks inserted"), + Self::Db(e) => e.fmt(f), + Self::StateDb(e) => e.fmt(f), + } + } +} + +impl fmt::Debug for StateDbError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Decoding(e) => write!(f, "Error decoding sliceable value: {}", e), + Self::InvalidBlock => write!(f, "Trying to canonicalize invalid block"), + Self::InvalidBlockNumber => write!(f, "Trying to insert block with invalid number"), + Self::InvalidParent => write!(f, "Trying to insert block with unknown parent"), + Self::IncompatiblePruningModes { stored, requested } => write!( + f, + "Incompatible pruning modes [stored: {:?}; requested: {:?}]", + stored, requested + ), + Self::TooManySiblingBlocks => write!(f, "Too many sibling blocks inserted"), + Self::BlockAlreadyExists => write!(f, "Block already exists"), + Self::Metadata(message) => write!(f, "Invalid metadata: {}", message), + Self::BlockUnavailable => + write!(f, "Trying to get a block record from db while it is not commit to db yet"), + Self::BlockMissing => write!(f, "Block record is missing from the pruning window"), } } } @@ -177,13 +213,11 @@ pub struct CommitSet { } /// Pruning constraints. If none are specified pruning is -#[derive(Default, Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Constraints { /// Maximum blocks. Defaults to 0 when unspecified, effectively keeping only non-canonical /// states. pub max_blocks: Option, - /// Maximum memory in the pruning overlay. - pub max_mem: Option, } /// Pruning mode. @@ -199,8 +233,8 @@ pub enum PruningMode { impl PruningMode { /// Create a mode that keeps given number of blocks. - pub fn keep_blocks(n: u32) -> PruningMode { - PruningMode::Constrained(Constraints { max_blocks: Some(n), max_mem: None }) + pub fn blocks_pruning(n: u32) -> PruningMode { + PruningMode::Constrained(Constraints { max_blocks: Some(n) }) } /// Is this an archive (either ArchiveAll or ArchiveCanonical) pruning mode? @@ -211,7 +245,7 @@ impl PruningMode { } } - /// Is this an archive (either ArchiveAll or ArchiveCanonical) pruning mode? + /// Returns the pruning mode pub fn id(&self) -> &[u8] { match self { PruningMode::ArchiveAll => PRUNING_MODE_ARCHIVE, @@ -219,11 +253,26 @@ impl PruningMode { PruningMode::Constrained(_) => PRUNING_MODE_CONSTRAINED, } } + + pub fn from_id(id: &[u8]) -> Option { + match id { + PRUNING_MODE_ARCHIVE => Some(Self::ArchiveAll), + PRUNING_MODE_ARCHIVE_CANON => Some(Self::ArchiveCanonical), + PRUNING_MODE_CONSTRAINED => Some(Self::Constrained(Default::default())), + _ => None, + } + } } impl Default for PruningMode { fn default() -> Self { - PruningMode::keep_blocks(256) + PruningMode::Constrained(Default::default()) + } +} + +impl Default for Constraints { + fn default() -> Self { + Self { max_blocks: Some(DEFAULT_MAX_BLOCK_CONSTRAINT) } } } @@ -233,118 +282,101 @@ fn to_meta_key(suffix: &[u8], data: &S) -> Vec { buffer } -struct StateDbSync { +pub struct StateDbSync { mode: PruningMode, non_canonical: NonCanonicalOverlay, - pruning: Option>, + pruning: Option>, pinned: HashMap, + ref_counting: bool, } -impl StateDbSync { - fn new( +impl StateDbSync { + fn new( mode: PruningMode, ref_counting: bool, - db: &D, - ) -> Result, Error> { + db: D, + ) -> Result, Error> { trace!(target: "state-db", "StateDb settings: {:?}. Ref-counting: {}", mode, ref_counting); - // Check that settings match - Self::check_meta(&mode, db)?; - - let non_canonical: NonCanonicalOverlay = NonCanonicalOverlay::new(db)?; - let pruning: Option> = match mode { - PruningMode::Constrained(Constraints { max_mem: Some(_), .. }) => unimplemented!(), - PruningMode::Constrained(_) => Some(RefWindow::new(db, ref_counting)?), + let non_canonical: NonCanonicalOverlay = NonCanonicalOverlay::new(&db)?; + let pruning: Option> = match mode { + PruningMode::Constrained(Constraints { max_blocks }) => + Some(RefWindow::new(db, max_blocks.unwrap_or(0), ref_counting)?), PruningMode::ArchiveAll | PruningMode::ArchiveCanonical => None, }; - Ok(StateDbSync { mode, non_canonical, pruning, pinned: Default::default() }) - } - - fn check_meta(mode: &PruningMode, db: &D) -> Result<(), Error> { - let db_mode = db.get_meta(&to_meta_key(PRUNING_MODE, &())).map_err(Error::Db)?; - trace!(target: "state-db", - "DB pruning mode: {:?}", - db_mode.as_ref().map(|v| std::str::from_utf8(&v)) - ); - match &db_mode { - Some(v) if v.as_slice() == mode.id() => Ok(()), - Some(v) => Err(Error::InvalidPruningMode(String::from_utf8_lossy(v).into())), - None => Ok(()), - } + Ok(StateDbSync { mode, non_canonical, pruning, pinned: Default::default(), ref_counting }) } - fn insert_block( + fn insert_block( &mut self, hash: &BlockHash, number: u64, parent_hash: &BlockHash, mut changeset: ChangeSet, - ) -> Result, Error> { - let mut meta = ChangeSet::default(); - if number == 0 { - // Save pruning mode when writing first block. - meta.inserted.push((to_meta_key(PRUNING_MODE, &()), self.mode.id().into())); - } - + ) -> Result, Error> { match self.mode { PruningMode::ArchiveAll => { changeset.deleted.clear(); // write changes immediately - Ok(CommitSet { data: changeset, meta }) - }, - PruningMode::Constrained(_) | PruningMode::ArchiveCanonical => { - let commit = self.non_canonical.insert(hash, number, parent_hash, changeset); - commit.map(|mut c| { - c.meta.inserted.extend(meta.inserted); - c - }) + Ok(CommitSet { data: changeset, meta: Default::default() }) }, + PruningMode::Constrained(_) | PruningMode::ArchiveCanonical => self + .non_canonical + .insert(hash, number, parent_hash, changeset) + .map_err(Into::into), } } - fn canonicalize_block( - &mut self, - hash: &BlockHash, - ) -> Result, Error> { + fn canonicalize_block(&mut self, hash: &BlockHash) -> Result, Error> { + // NOTE: it is important that the change to `LAST_CANONICAL` (emit from + // `non_canonical.canonicalize`) and the insert of the new pruning journal (emit from + // `pruning.note_canonical`) are collected into the same `CommitSet` and are committed to + // the database atomically to keep their consistency when restarting the node let mut commit = CommitSet::default(); if self.mode == PruningMode::ArchiveAll { return Ok(commit) } - match self.non_canonical.canonicalize(&hash, &mut commit) { - Ok(()) => - if self.mode == PruningMode::ArchiveCanonical { - commit.data.deleted.clear(); - }, - Err(e) => return Err(e), - }; + let number = self.non_canonical.canonicalize(hash, &mut commit)?; + if self.mode == PruningMode::ArchiveCanonical { + commit.data.deleted.clear(); + } if let Some(ref mut pruning) = self.pruning { - pruning.note_canonical(&hash, &mut commit); + pruning.note_canonical(hash, number, &mut commit)?; } - self.prune(&mut commit); + self.prune(&mut commit)?; Ok(commit) } fn best_canonical(&self) -> Option { - return self.non_canonical.last_canonicalized_block_number() + self.non_canonical.last_canonicalized_block_number() } - fn is_pruned(&self, hash: &BlockHash, number: u64) -> bool { + fn is_pruned(&self, hash: &BlockHash, number: u64) -> IsPruned { match self.mode { - PruningMode::ArchiveAll => false, + PruningMode::ArchiveAll => IsPruned::NotPruned, PruningMode::ArchiveCanonical | PruningMode::Constrained(_) => { if self.best_canonical().map(|c| number > c).unwrap_or(true) { - !self.non_canonical.have_block(hash) + if self.non_canonical.have_block(hash) { + IsPruned::NotPruned + } else { + IsPruned::Pruned + } } else { - self.pruning.as_ref().map_or(false, |pruning| { - number < pruning.pending() || !pruning.have_block(hash) - }) + match self.pruning.as_ref() { + None => IsPruned::NotPruned, + Some(pruning) => match pruning.have_block(hash, number) { + HaveBlock::No => IsPruned::Pruned, + HaveBlock::Yes => IsPruned::NotPruned, + HaveBlock::Maybe => IsPruned::MaybePruned, + }, + } } }, } } - fn prune(&mut self, commit: &mut CommitSet) { + fn prune(&mut self, commit: &mut CommitSet) -> Result<(), Error> { if let (&mut Some(ref mut pruning), &PruningMode::Constrained(ref constraints)) = (&mut self.pruning, &self.mode) { @@ -353,17 +385,24 @@ impl StateDbSync m) { - break - } - let pinned = &self.pinned; - if pruning.next_hash().map_or(false, |h| pinned.contains_key(&h)) { - break + match pruning.next_hash() { + // the block record is temporary unavailable, break and try next time + Err(Error::StateDb(StateDbError::BlockUnavailable)) => break, + res => + if res?.map_or(false, |h| pinned.contains_key(&h)) { + break + }, + } + match pruning.prune_one(commit) { + // this branch should not reach as previous `next_hash` don't return error + // keeping it for robustness + Err(Error::StateDb(StateDbError::BlockUnavailable)) => break, + res => res?, } - pruning.prune_one(commit); } } + Ok(()) } /// Revert all non-canonical blocks with the best block number. @@ -385,13 +424,22 @@ impl StateDbSync Result<(), PinError> { + fn pin(&mut self, hash: &BlockHash, number: u64, hint: F) -> Result<(), PinError> + where + F: Fn() -> bool, + { match self.mode { PruningMode::ArchiveAll => Ok(()), PruningMode::ArchiveCanonical | PruningMode::Constrained(_) => { - if self.non_canonical.have_block(hash) || - self.pruning.as_ref().map_or(false, |pruning| pruning.have_block(hash)) - { + let have_block = self.non_canonical.have_block(hash) || + self.pruning.as_ref().map_or(false, |pruning| { + match pruning.have_block(hash, number) { + HaveBlock::No => false, + HaveBlock::Yes => true, + HaveBlock::Maybe => hint(), + } + }); + if have_block { let refs = self.pinned.entry(hash.clone()).or_default(); if *refs == 0 { trace!(target: "state-db-pin", "Pinned block: {:?}", hash); @@ -422,92 +470,102 @@ impl StateDbSync( + pub fn get( &self, key: &Q, - db: &D, - ) -> Result, Error> + db: &DB, + ) -> Result, Error> where - Q: AsRef, + Q: AsRef, Key: std::borrow::Borrow, Q: std::hash::Hash + Eq, { if let Some(value) = self.non_canonical.get(key) { return Ok(Some(value)) } - db.get(key.as_ref()).map_err(|e| Error::Db(e)) - } - - fn apply_pending(&mut self) { - self.non_canonical.apply_pending(); - if let Some(pruning) = &mut self.pruning { - pruning.apply_pending(); - } - trace!( - target: "forks", - "First available: {:?} ({}), Last canon: {:?} ({}), Best forks: {:?}", - self.pruning.as_ref().and_then(|p| p.next_hash()), - self.pruning.as_ref().map(|p| p.pending()).unwrap_or(0), - self.non_canonical.last_canonicalized_hash(), - self.non_canonical.last_canonicalized_block_number().unwrap_or(0), - self.non_canonical.top_level(), - ); - } - - fn revert_pending(&mut self) { - if let Some(pruning) = &mut self.pruning { - pruning.revert_pending(); - } - self.non_canonical.revert_pending(); - } - - fn memory_info(&self) -> StateDbMemoryInfo { - StateDbMemoryInfo { - non_canonical: MemorySize::from_bytes(malloc_size(&self.non_canonical)), - pruning: self.pruning.as_ref().map(|p| MemorySize::from_bytes(malloc_size(p))), - pinned: MemorySize::from_bytes(malloc_size(&self.pinned)), - } + db.get(key.as_ref()).map_err(Error::Db) } } /// State DB maintenance. See module description. /// Can be shared across threads. -pub struct StateDb { - db: RwLock>, +pub struct StateDb { + db: RwLock>, } -impl StateDb { - /// Creates a new instance. Does not expect any metadata in the database. - pub fn new( - mode: PruningMode, +impl StateDb { + /// Create an instance of [`StateDb`]. + pub fn open( + db: D, + requested_mode: Option, ref_counting: bool, - db: &D, - ) -> Result, Error> { - Ok(StateDb { db: RwLock::new(StateDbSync::new(mode, ref_counting, db)?) }) + should_init: bool, + ) -> Result<(CommitSet, StateDb), Error> { + let stored_mode = fetch_stored_pruning_mode(&db)?; + + let selected_mode = match (should_init, stored_mode, requested_mode) { + (true, stored_mode, requested_mode) => { + assert!(stored_mode.is_none(), "The storage has just been initialized. No meta-data is expected to be found in it."); + requested_mode.unwrap_or_default() + }, + + (false, None, _) => + return Err(StateDbError::Metadata( + "An existing StateDb does not have PRUNING_MODE stored in its meta-data".into(), + ) + .into()), + + (false, Some(stored), None) => stored, + + (false, Some(stored), Some(requested)) => choose_pruning_mode(stored, requested)?, + }; + + let db_init_commit_set = if should_init { + let mut cs: CommitSet = Default::default(); + + let key = to_meta_key(PRUNING_MODE, &()); + let value = selected_mode.id().to_owned(); + + cs.meta.inserted.push((key, value)); + + cs + } else { + Default::default() + }; + + let state_db = + StateDb { db: RwLock::new(StateDbSync::new(selected_mode, ref_counting, db)?) }; + + Ok((db_init_commit_set, state_db)) + } + + pub fn pruning_mode(&self) -> PruningMode { + self.db.read().mode.clone() } /// Add a new non-canonical block. - pub fn insert_block( + pub fn insert_block( &self, hash: &BlockHash, number: u64, parent_hash: &BlockHash, changeset: ChangeSet, - ) -> Result, Error> { + ) -> Result, Error> { self.db.write().insert_block(hash, number, parent_hash, changeset) } /// Finalize a previously inserted block. - pub fn canonicalize_block( - &self, - hash: &BlockHash, - ) -> Result, Error> { + pub fn canonicalize_block(&self, hash: &BlockHash) -> Result, Error> { self.db.write().canonicalize_block(hash) } /// Prevents pruning of specified block and its descendants. - pub fn pin(&self, hash: &BlockHash) -> Result<(), PinError> { - self.db.write().pin(hash) + /// `hint` used for futher checking if the given block exists + pub fn pin(&self, hash: &BlockHash, number: u64, hint: F) -> Result<(), PinError> + where + F: Fn() -> bool, + { + self.db.write().pin(hash, number, hint) } /// Allows pruning of specified block. @@ -516,13 +574,13 @@ impl StateDb( + pub fn get( &self, key: &Q, - db: &D, - ) -> Result, Error> + db: &DB, + ) -> Result, Error> where - Q: AsRef, + Q: AsRef, Key: std::borrow::Borrow, Q: std::hash::Hash + Eq, { @@ -548,23 +606,57 @@ impl StateDb bool { + pub fn is_pruned(&self, hash: &BlockHash, number: u64) -> IsPruned { return self.db.read().is_pruned(hash, number) } - /// Apply all pending changes - pub fn apply_pending(&self) { - self.db.write().apply_pending(); + /// Reset in-memory changes to the last disk-backed state. + pub fn reset(&self, db: D) -> Result<(), Error> { + let mut state_db = self.db.write(); + *state_db = StateDbSync::new(state_db.mode.clone(), state_db.ref_counting, db)?; + Ok(()) } +} + +/// The result return by `StateDb::is_pruned` +#[derive(Debug, PartialEq, Eq)] +pub enum IsPruned { + /// Definitely pruned + Pruned, + /// Definitely not pruned + NotPruned, + /// May or may not pruned, need futher checking + MaybePruned, +} - /// Revert all pending changes - pub fn revert_pending(&self) { - self.db.write().revert_pending(); +fn fetch_stored_pruning_mode(db: &D) -> Result, Error> { + let meta_key_mode = to_meta_key(PRUNING_MODE, &()); + if let Some(stored_mode) = db.get_meta(&meta_key_mode).map_err(Error::Db)? { + if let Some(mode) = PruningMode::from_id(&stored_mode) { + Ok(Some(mode)) + } else { + Err(StateDbError::Metadata(format!( + "Invalid value stored for PRUNING_MODE: {:02x?}", + stored_mode + )) + .into()) + } + } else { + Ok(None) } +} - /// Returns the current memory statistics of this instance. - pub fn memory_info(&self) -> StateDbMemoryInfo { - self.db.read().memory_info() +fn choose_pruning_mode( + stored: PruningMode, + requested: PruningMode, +) -> Result { + match (stored, requested) { + (PruningMode::ArchiveAll, PruningMode::ArchiveAll) => Ok(PruningMode::ArchiveAll), + (PruningMode::ArchiveCanonical, PruningMode::ArchiveCanonical) => + Ok(PruningMode::ArchiveCanonical), + (PruningMode::Constrained(_), PruningMode::Constrained(requested)) => + Ok(PruningMode::Constrained(requested)), + (stored, requested) => Err(StateDbError::IncompatiblePruningModes { requested, stored }), } } @@ -572,18 +664,19 @@ impl StateDb (TestDb, StateDb) { + fn make_test_db(settings: PruningMode) -> (TestDb, StateDb) { let mut db = make_db(&[91, 921, 922, 93, 94]); - let state_db = StateDb::new(settings, false, &db).unwrap(); + let (state_db_init, state_db) = + StateDb::open(db.clone(), Some(settings), false, true).unwrap(); + db.commit(&state_db_init); db.commit( &state_db - .insert_block::( + .insert_block( &H256::from_low_u64_be(1), 1, &H256::from_low_u64_be(0), @@ -593,7 +686,7 @@ mod tests { ); db.commit( &state_db - .insert_block::( + .insert_block( &H256::from_low_u64_be(21), 2, &H256::from_low_u64_be(1), @@ -603,7 +696,7 @@ mod tests { ); db.commit( &state_db - .insert_block::( + .insert_block( &H256::from_low_u64_be(22), 2, &H256::from_low_u64_be(1), @@ -613,7 +706,7 @@ mod tests { ); db.commit( &state_db - .insert_block::( + .insert_block( &H256::from_low_u64_be(3), 3, &H256::from_low_u64_be(21), @@ -621,12 +714,10 @@ mod tests { ) .unwrap(), ); - state_db.apply_pending(); - db.commit(&state_db.canonicalize_block::(&H256::from_low_u64_be(1)).unwrap()); - state_db.apply_pending(); + db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(1)).unwrap()); db.commit( &state_db - .insert_block::( + .insert_block( &H256::from_low_u64_be(4), 4, &H256::from_low_u64_be(3), @@ -634,11 +725,8 @@ mod tests { ) .unwrap(), ); - state_db.apply_pending(); - db.commit(&state_db.canonicalize_block::(&H256::from_low_u64_be(21)).unwrap()); - state_db.apply_pending(); - db.commit(&state_db.canonicalize_block::(&H256::from_low_u64_be(3)).unwrap()); - state_db.apply_pending(); + db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(21)).unwrap()); + db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(3)).unwrap()); (db, state_db) } @@ -647,7 +735,7 @@ mod tests { fn full_archive_keeps_everything() { let (db, sdb) = make_test_db(PruningMode::ArchiveAll); assert!(db.data_eq(&make_db(&[1, 21, 22, 3, 4, 91, 921, 922, 93, 94]))); - assert!(!sdb.is_pruned(&H256::from_low_u64_be(0), 0)); + assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::NotPruned); } #[test] @@ -656,48 +744,76 @@ mod tests { assert!(db.data_eq(&make_db(&[1, 21, 3, 91, 921, 922, 93, 94]))); } + #[test] + fn block_record_unavailable() { + let (mut db, state_db) = + make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(1) })); + // import 2 blocks + for i in &[5, 6] { + db.commit( + &state_db + .insert_block( + &H256::from_low_u64_be(*i), + *i, + &H256::from_low_u64_be(*i - 1), + make_changeset(&[], &[]), + ) + .unwrap(), + ); + } + // canonicalize block 4 but not commit it to db + let c1 = state_db.canonicalize_block(&H256::from_low_u64_be(4)).unwrap(); + assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(3), 3), IsPruned::Pruned); + + // canonicalize block 5 but not commit it to db, block 4 is not pruned due to it is not + // commit to db yet (unavailable), return `MaybePruned` here because `apply_pending` is not + // called and block 3 is still in cache + let c2 = state_db.canonicalize_block(&H256::from_low_u64_be(5)).unwrap(); + assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(4), 4), IsPruned::MaybePruned); + + // commit block 4 and 5 to db, and import a new block will prune both block 4 and 5 + db.commit(&c1); + db.commit(&c2); + db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(6)).unwrap()); + assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(4), 4), IsPruned::Pruned); + assert_eq!(state_db.is_pruned(&H256::from_low_u64_be(5), 5), IsPruned::Pruned); + } + #[test] fn prune_window_0() { - let (db, _) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(0), - max_mem: None, - })); + let (db, _) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(0) })); assert!(db.data_eq(&make_db(&[21, 3, 922, 94]))); } #[test] fn prune_window_1() { - let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(1), - max_mem: None, - })); - assert!(sdb.is_pruned(&H256::from_low_u64_be(0), 0)); - assert!(sdb.is_pruned(&H256::from_low_u64_be(1), 1)); - assert!(sdb.is_pruned(&H256::from_low_u64_be(21), 2)); - assert!(sdb.is_pruned(&H256::from_low_u64_be(22), 2)); + let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(1) })); + assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::Pruned); + assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(1), 1), IsPruned::Pruned); + assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(21), 2), IsPruned::Pruned); + assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(22), 2), IsPruned::Pruned); assert!(db.data_eq(&make_db(&[21, 3, 922, 93, 94]))); } #[test] fn prune_window_2() { - let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(2), - max_mem: None, - })); - assert!(sdb.is_pruned(&H256::from_low_u64_be(0), 0)); - assert!(sdb.is_pruned(&H256::from_low_u64_be(1), 1)); - assert!(!sdb.is_pruned(&H256::from_low_u64_be(21), 2)); - assert!(sdb.is_pruned(&H256::from_low_u64_be(22), 2)); + let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(2) })); + assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::Pruned); + assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(1), 1), IsPruned::Pruned); + assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(21), 2), IsPruned::NotPruned); + assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(22), 2), IsPruned::Pruned); assert!(db.data_eq(&make_db(&[1, 21, 3, 921, 922, 93, 94]))); } #[test] fn detects_incompatible_mode() { let mut db = make_db(&[]); - let state_db = StateDb::new(PruningMode::ArchiveAll, false, &db).unwrap(); + let (state_db_init, state_db) = + StateDb::open(db.clone(), Some(PruningMode::ArchiveAll), false, true).unwrap(); + db.commit(&state_db_init); db.commit( &state_db - .insert_block::( + .insert_block( &H256::from_low_u64_be(0), 0, &H256::from_low_u64_be(0), @@ -705,8 +821,87 @@ mod tests { ) .unwrap(), ); - let new_mode = PruningMode::Constrained(Constraints { max_blocks: Some(2), max_mem: None }); - let state_db: Result, _> = StateDb::new(new_mode, false, &db); - assert!(state_db.is_err()); + let new_mode = PruningMode::Constrained(Constraints { max_blocks: Some(2) }); + let state_db_open_result: Result<(_, StateDb), _> = + StateDb::open(db.clone(), Some(new_mode), false, false); + assert!(state_db_open_result.is_err()); + } + + fn check_stored_and_requested_mode_compatibility( + mode_when_created: Option, + mode_when_reopened: Option, + expected_effective_mode_when_reopenned: Result, + ) { + let mut db = make_db(&[]); + let (state_db_init, state_db) = + StateDb::::open(db.clone(), mode_when_created, false, true) + .unwrap(); + db.commit(&state_db_init); + std::mem::drop(state_db); + + let state_db_reopen_result = + StateDb::::open(db.clone(), mode_when_reopened, false, false); + if let Ok(expected_mode) = expected_effective_mode_when_reopenned { + let (state_db_init, state_db_reopened) = state_db_reopen_result.unwrap(); + db.commit(&state_db_init); + assert_eq!(state_db_reopened.pruning_mode(), expected_mode,) + } else { + assert!(matches!( + state_db_reopen_result, + Err(Error::StateDb(StateDbError::IncompatiblePruningModes { .. })) + )); + } + } + + #[test] + fn pruning_mode_compatibility() { + for (created, reopened, expected) in [ + (None, None, Ok(PruningMode::blocks_pruning(256))), + (None, Some(PruningMode::blocks_pruning(256)), Ok(PruningMode::blocks_pruning(256))), + (None, Some(PruningMode::blocks_pruning(128)), Ok(PruningMode::blocks_pruning(128))), + (None, Some(PruningMode::blocks_pruning(512)), Ok(PruningMode::blocks_pruning(512))), + (None, Some(PruningMode::ArchiveAll), Err(())), + (None, Some(PruningMode::ArchiveCanonical), Err(())), + (Some(PruningMode::blocks_pruning(256)), None, Ok(PruningMode::blocks_pruning(256))), + ( + Some(PruningMode::blocks_pruning(256)), + Some(PruningMode::blocks_pruning(256)), + Ok(PruningMode::blocks_pruning(256)), + ), + ( + Some(PruningMode::blocks_pruning(256)), + Some(PruningMode::blocks_pruning(128)), + Ok(PruningMode::blocks_pruning(128)), + ), + ( + Some(PruningMode::blocks_pruning(256)), + Some(PruningMode::blocks_pruning(512)), + Ok(PruningMode::blocks_pruning(512)), + ), + (Some(PruningMode::blocks_pruning(256)), Some(PruningMode::ArchiveAll), Err(())), + (Some(PruningMode::blocks_pruning(256)), Some(PruningMode::ArchiveCanonical), Err(())), + (Some(PruningMode::ArchiveAll), None, Ok(PruningMode::ArchiveAll)), + (Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(256)), Err(())), + (Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(128)), Err(())), + (Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(512)), Err(())), + ( + Some(PruningMode::ArchiveAll), + Some(PruningMode::ArchiveAll), + Ok(PruningMode::ArchiveAll), + ), + (Some(PruningMode::ArchiveAll), Some(PruningMode::ArchiveCanonical), Err(())), + (Some(PruningMode::ArchiveCanonical), None, Ok(PruningMode::ArchiveCanonical)), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(256)), Err(())), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(128)), Err(())), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(512)), Err(())), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::ArchiveAll), Err(())), + ( + Some(PruningMode::ArchiveCanonical), + Some(PruningMode::ArchiveCanonical), + Ok(PruningMode::ArchiveCanonical), + ), + ] { + check_stored_and_requested_mode_compatibility(created, reopened, expected); + } } } diff --git a/client/state-db/src/noncanonical.rs b/client/state-db/src/noncanonical.rs index c726ceae4b058..7cb3017966b0f 100644 --- a/client/state-db/src/noncanonical.rs +++ b/client/state-db/src/noncanonical.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -19,36 +19,28 @@ //! Canonicalization window. //! Maintains trees of block overlays and allows discarding trees/roots //! The overlays are added in `insert` and removed in `canonicalize`. -//! All pending changes are kept in memory until next call to `apply_pending` or -//! `revert_pending` -use super::{to_meta_key, ChangeSet, CommitSet, DBValue, Error, Hash, MetaDb}; +use super::{to_meta_key, ChangeSet, CommitSet, DBValue, Error, Hash, MetaDb, StateDbError}; use codec::{Decode, Encode}; use log::trace; -use std::{ - collections::{hash_map::Entry, HashMap, VecDeque}, - fmt, -}; +use std::collections::{hash_map::Entry, HashMap, VecDeque}; const NON_CANONICAL_JOURNAL: &[u8] = b"noncanonical_journal"; -const LAST_CANONICAL: &[u8] = b"last_canonical"; +pub(crate) const LAST_CANONICAL: &[u8] = b"last_canonical"; const MAX_BLOCKS_PER_LEVEL: u64 = 32; /// See module documentation. -#[derive(parity_util_mem_derive::MallocSizeOf)] pub struct NonCanonicalOverlay { last_canonicalized: Option<(BlockHash, u64)>, levels: VecDeque>, parents: HashMap, - pending_canonicalizations: Vec, - pending_insertions: Vec, values: HashMap, // ref counted // would be deleted but kept around because block is pinned, ref counted. pinned: HashMap, pinned_insertions: HashMap, u32)>, + last_canon_pinned: Option, } -#[derive(parity_util_mem_derive::MallocSizeOf)] #[cfg_attr(test, derive(PartialEq, Debug))] struct OverlayLevel { blocks: Vec>, @@ -88,7 +80,6 @@ fn to_journal_key(block: u64, index: u64) -> Vec { } #[cfg_attr(test, derive(PartialEq, Debug))] -#[derive(parity_util_mem_derive::MallocSizeOf)] struct BlockOverlay { hash: BlockHash, journal_index: u64, @@ -127,20 +118,18 @@ fn discard_values(values: &mut HashMap, inserted fn discard_descendants( levels: &mut (&mut [OverlayLevel], &mut [OverlayLevel]), - mut values: &mut HashMap, + values: &mut HashMap, parents: &mut HashMap, pinned: &HashMap, pinned_insertions: &mut HashMap, u32)>, hash: &BlockHash, ) -> u32 { let (first, mut remainder) = if let Some((first, rest)) = levels.0.split_first_mut() { - (Some(first), (rest, &mut levels.1[..])) + (Some(first), (rest, &mut *levels.1)) + } else if let Some((first, rest)) = levels.1.split_first_mut() { + (Some(first), (&mut *levels.0, rest)) } else { - if let Some((first, rest)) = levels.1.split_first_mut() { - (Some(first), (&mut levels.0[..], rest)) - } else { - (None, (&mut levels.0[..], &mut levels.1[..])) - } + (None, (&mut *levels.0, &mut *levels.1)) }; let mut pinned_children = 0; if let Some(level) = first { @@ -169,7 +158,7 @@ fn discard_descendants( } else { // discard immediately. parents.remove(&overlay.hash); - discard_values(&mut values, overlay.inserted); + discard_values(values, overlay.inserted); } } } @@ -180,7 +169,7 @@ impl NonCanonicalOverlay { /// Creates a new instance. Does not expect any metadata to be present in the DB. pub fn new(db: &D) -> Result, Error> { let last_canonicalized = - db.get_meta(&to_meta_key(LAST_CANONICAL, &())).map_err(|e| Error::Db(e))?; + db.get_meta(&to_meta_key(LAST_CANONICAL, &())).map_err(Error::Db)?; let last_canonicalized = last_canonicalized .map(|buffer| <(BlockHash, u64)>::decode(&mut buffer.as_slice())) .transpose()?; @@ -196,7 +185,7 @@ impl NonCanonicalOverlay { let mut level = OverlayLevel::new(); for index in 0..MAX_BLOCKS_PER_LEVEL { let journal_key = to_journal_key(block, index); - if let Some(record) = db.get_meta(&journal_key).map_err(|e| Error::Db(e))? { + if let Some(record) = db.get_meta(&journal_key).map_err(Error::Db)? { let record: JournalRecord = Decode::decode(&mut record.as_slice())?; let inserted = record.inserted.iter().map(|(k, _)| k.clone()).collect(); @@ -210,9 +199,10 @@ impl NonCanonicalOverlay { insert_values(&mut values, record.inserted); trace!( target: "state-db", - "Uncanonicalized journal entry {}.{} ({} inserted, {} deleted)", + "Uncanonicalized journal entry {}.{} ({:?}) ({} inserted, {} deleted)", block, index, + record.hash, overlay.inserted.len(), overlay.deleted.len() ); @@ -233,23 +223,22 @@ impl NonCanonicalOverlay { last_canonicalized, levels, parents, - pending_canonicalizations: Default::default(), - pending_insertions: Default::default(), pinned: Default::default(), pinned_insertions: Default::default(), values, + last_canon_pinned: None, }) } /// Insert a new block into the overlay. If inserted on the second level or lover expects parent /// to be present in the window. - pub fn insert( + pub fn insert( &mut self, hash: &BlockHash, number: u64, parent_hash: &BlockHash, changeset: ChangeSet, - ) -> Result, Error> { + ) -> Result, StateDbError> { let mut commit = CommitSet::default(); let front_block_number = self.front_block_number(); if self.levels.is_empty() && self.last_canonicalized.is_none() && number > 0 { @@ -261,15 +250,14 @@ impl NonCanonicalOverlay { .push((to_meta_key(LAST_CANONICAL, &()), last_canonicalized.encode())); self.last_canonicalized = Some(last_canonicalized); } else if self.last_canonicalized.is_some() { - if number < front_block_number || - number >= front_block_number + self.levels.len() as u64 + 1 + if number < front_block_number || number > front_block_number + self.levels.len() as u64 { trace!(target: "state-db", "Failed to insert block {}, current is {} .. {})", number, front_block_number, front_block_number + self.levels.len() as u64, ); - return Err(Error::InvalidBlockNumber) + return Err(StateDbError::InvalidBlockNumber) } // check for valid parent if inserting on second level or higher if number == front_block_number { @@ -278,10 +266,10 @@ impl NonCanonicalOverlay { .as_ref() .map_or(false, |&(ref h, n)| h == parent_hash && n == number - 1) { - return Err(Error::InvalidParent) + return Err(StateDbError::InvalidParent) } - } else if !self.parents.contains_key(&parent_hash) { - return Err(Error::InvalidParent) + } else if !self.parents.contains_key(parent_hash) { + return Err(StateDbError::InvalidParent) } } let level = if self.levels.is_empty() || @@ -295,7 +283,10 @@ impl NonCanonicalOverlay { }; if level.blocks.len() >= MAX_BLOCKS_PER_LEVEL as usize { - return Err(Error::TooManySiblingBlocks) + return Err(StateDbError::TooManySiblingBlocks) + } + if level.blocks.iter().any(|b| b.hash == *hash) { + return Err(StateDbError::BlockAlreadyExists) } let index = level.available_index(); @@ -318,9 +309,8 @@ impl NonCanonicalOverlay { deleted: changeset.deleted, }; commit.meta.inserted.push((journal_key, journal_record.encode())); - trace!(target: "state-db", "Inserted uncanonicalized changeset {}.{} ({} inserted, {} deleted)", number, index, journal_record.inserted.len(), journal_record.deleted.len()); + trace!(target: "state-db", "Inserted uncanonicalized changeset {}.{} {:?} ({} inserted, {} deleted)", number, index, hash, journal_record.inserted.len(), journal_record.deleted.len()); insert_values(&mut self.values, journal_record.inserted); - self.pending_insertions.push(hash.clone()); Ok(commit) } @@ -357,131 +347,97 @@ impl NonCanonicalOverlay { } pub fn last_canonicalized_block_number(&self) -> Option { - match self.last_canonicalized.as_ref().map(|&(_, n)| n) { - Some(n) => Some(n + self.pending_canonicalizations.len() as u64), - None if !self.pending_canonicalizations.is_empty() => - Some(self.pending_canonicalizations.len() as u64), - _ => None, - } - } - - pub fn last_canonicalized_hash(&self) -> Option { - self.last_canonicalized.as_ref().map(|&(ref h, _)| h.clone()) - } - - pub fn top_level(&self) -> Vec<(BlockHash, u64)> { - let start = self.last_canonicalized_block_number().unwrap_or(0); - self.levels - .get(self.pending_canonicalizations.len()) - .map(|level| level.blocks.iter().map(|r| (r.hash.clone(), start)).collect()) - .unwrap_or_default() + self.last_canonicalized.as_ref().map(|&(_, n)| n) } /// Select a top-level root and canonicalized it. Discards all sibling subtrees and the root. - /// Returns a set of changes that need to be added to the DB. - pub fn canonicalize( + /// Add a set of changes of the canonicalized block to `CommitSet` + /// Return the block number of the canonicalized block + pub fn canonicalize( &mut self, hash: &BlockHash, commit: &mut CommitSet, - ) -> Result<(), Error> { + ) -> Result { trace!(target: "state-db", "Canonicalizing {:?}", hash); - let level = self - .levels - .get(self.pending_canonicalizations.len()) - .ok_or_else(|| Error::InvalidBlock)?; + let level = match self.levels.pop_front() { + Some(level) => level, + None => return Err(StateDbError::InvalidBlock), + }; let index = level .blocks .iter() .position(|overlay| overlay.hash == *hash) - .ok_or_else(|| Error::InvalidBlock)?; + .ok_or(StateDbError::InvalidBlock)?; + + // No failures are possible beyond this point. + + // Unpin previously canonicalized block + if let Some(prev_hash) = self.last_canon_pinned.take() { + self.unpin(&prev_hash); + } + // Force pin canonicalized block so that it is no discarded immediately + self.pin(hash); + self.last_canon_pinned = Some(hash.clone()); let mut discarded_journals = Vec::new(); let mut discarded_blocks = Vec::new(); - for (i, overlay) in level.blocks.iter().enumerate() { - if i != index { + for (i, overlay) in level.blocks.into_iter().enumerate() { + let mut pinned_children = 0; + // That's the one we need to canonicalize + if i == index { + commit.data.inserted.extend(overlay.inserted.iter().map(|k| { + ( + k.clone(), + self.values + .get(k) + .expect("For each key in overlays there's a value in values") + .1 + .clone(), + ) + })); + commit.data.deleted.extend(overlay.deleted.clone()); + } else { + // Discard this overlay self.discard_journals( - self.pending_canonicalizations.len() + 1, + 0, &mut discarded_journals, &mut discarded_blocks, &overlay.hash, ); + pinned_children = discard_descendants( + &mut self.levels.as_mut_slices(), + &mut self.values, + &mut self.parents, + &self.pinned, + &mut self.pinned_insertions, + &overlay.hash, + ); + } + if self.pinned.contains_key(&overlay.hash) { + pinned_children += 1; + } + if pinned_children != 0 { + self.pinned_insertions + .insert(overlay.hash.clone(), (overlay.inserted, pinned_children)); + } else { + self.parents.remove(&overlay.hash); + discard_values(&mut self.values, overlay.inserted); } discarded_journals.push(overlay.journal_key.clone()); discarded_blocks.push(overlay.hash.clone()); } - - // get the one we need to canonicalize - let overlay = &level.blocks[index]; - commit.data.inserted.extend(overlay.inserted.iter().map(|k| { - ( - k.clone(), - self.values - .get(k) - .expect("For each key in overlays there's a value in values") - .1 - .clone(), - ) - })); - commit.data.deleted.extend(overlay.deleted.clone()); - commit.meta.deleted.append(&mut discarded_journals); - let canonicalized = - (hash.clone(), self.front_block_number() + self.pending_canonicalizations.len() as u64); + + let canonicalized = (hash.clone(), self.front_block_number()); commit .meta .inserted .push((to_meta_key(LAST_CANONICAL, &()), canonicalized.encode())); trace!(target: "state-db", "Discarding {} records", commit.meta.deleted.len()); - self.pending_canonicalizations.push(hash.clone()); - Ok(()) - } - fn apply_canonicalizations(&mut self) { - let last = self.pending_canonicalizations.last().cloned(); - let count = self.pending_canonicalizations.len() as u64; - for hash in self.pending_canonicalizations.drain(..) { - trace!(target: "state-db", "Post canonicalizing {:?}", hash); - let level = - self.levels.pop_front().expect("Hash validity is checked in `canonicalize`"); - let index = level - .blocks - .iter() - .position(|overlay| overlay.hash == hash) - .expect("Hash validity is checked in `canonicalize`"); - - // discard unfinalized overlays and values - for (i, overlay) in level.blocks.into_iter().enumerate() { - let mut pinned_children = if i != index { - discard_descendants( - &mut self.levels.as_mut_slices(), - &mut self.values, - &mut self.parents, - &self.pinned, - &mut self.pinned_insertions, - &overlay.hash, - ) - } else { - 0 - }; - if self.pinned.contains_key(&overlay.hash) { - pinned_children += 1; - } - if pinned_children != 0 { - self.pinned_insertions - .insert(overlay.hash.clone(), (overlay.inserted, pinned_children)); - } else { - self.parents.remove(&overlay.hash); - discard_values(&mut self.values, overlay.inserted); - } - } - } - if let Some(hash) = last { - let last_canonicalized = ( - hash, - self.last_canonicalized.as_ref().map(|(_, n)| n + count).unwrap_or(count - 1), - ); - self.last_canonicalized = Some(last_canonicalized); - } + let num = canonicalized.1; + self.last_canonicalized = Some(canonicalized); + Ok(num) } /// Get a value from the node overlay. This searches in every existing changeset. @@ -490,16 +446,12 @@ impl NonCanonicalOverlay { Key: std::borrow::Borrow, Q: std::hash::Hash + Eq, { - if let Some((_, value)) = self.values.get(&key) { - return Some(value.clone()) - } - None + self.values.get(key).map(|v| v.1.clone()) } /// Check if the block is in the canonicalization queue. pub fn have_block(&self, hash: &BlockHash) -> bool { - (self.parents.contains_key(hash) || self.pending_insertions.contains(hash)) && - !self.pending_canonicalizations.contains(hash) + self.parents.contains_key(hash) } /// Revert a single level. Returns commit set that deletes the journal or `None` if not @@ -547,50 +499,8 @@ impl NonCanonicalOverlay { } } - fn revert_insertions(&mut self) { - self.pending_insertions.reverse(); - for hash in self.pending_insertions.drain(..) { - self.parents.remove(&hash); - // find a level. When iterating insertions backwards the hash is always last in the - // level. - let level_index = self - .levels - .iter() - .position(|level| { - level.blocks.last().expect("Hash is added in `insert` in reverse order").hash == - hash - }) - .expect("Hash is added in insert"); - - let overlay_index = self.levels[level_index].blocks.len() - 1; - let overlay = self.levels[level_index].remove(overlay_index); - discard_values(&mut self.values, overlay.inserted); - if self.levels[level_index].blocks.is_empty() { - debug_assert_eq!(level_index, self.levels.len() - 1); - self.levels.pop_back(); - } - } - } - - /// Apply all pending changes - pub fn apply_pending(&mut self) { - self.apply_canonicalizations(); - self.pending_insertions.clear(); - } - - /// Revert all pending changes - pub fn revert_pending(&mut self) { - self.pending_canonicalizations.clear(); - self.revert_insertions(); - } - /// Pin state values in memory pub fn pin(&mut self, hash: &BlockHash) { - if self.pending_insertions.contains(hash) { - // Pinning pending state is not implemented. Pending states - // won't be pruned for quite some time anyway, so it's not a big deal. - return - } let refs = self.pinned.entry(hash.clone()).or_default(); if *refs == 0 { trace!(target: "state-db-pin", "Pinned non-canon block: {:?}", hash); @@ -642,10 +552,9 @@ mod tests { use super::{to_journal_key, NonCanonicalOverlay}; use crate::{ test::{make_changeset, make_db}, - ChangeSet, CommitSet, MetaDb, + ChangeSet, CommitSet, MetaDb, StateDbError, }; use sp_core::H256; - use std::io; fn contains(overlay: &NonCanonicalOverlay, key: u64) -> bool { overlay.get(&H256::from_low_u64_be(key)) == @@ -667,7 +576,7 @@ mod tests { let db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); let mut commit = CommitSet::default(); - overlay.canonicalize::(&H256::default(), &mut commit).unwrap(); + overlay.canonicalize(&H256::default(), &mut commit).unwrap(); } #[test] @@ -677,10 +586,8 @@ mod tests { let h1 = H256::random(); let h2 = H256::random(); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - overlay - .insert::(&h1, 2, &H256::default(), ChangeSet::default()) - .unwrap(); - overlay.insert::(&h2, 1, &h1, ChangeSet::default()).unwrap(); + overlay.insert(&h1, 2, &H256::default(), ChangeSet::default()).unwrap(); + overlay.insert(&h2, 1, &h1, ChangeSet::default()).unwrap(); } #[test] @@ -690,10 +597,8 @@ mod tests { let h2 = H256::random(); let db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - overlay - .insert::(&h1, 1, &H256::default(), ChangeSet::default()) - .unwrap(); - overlay.insert::(&h2, 3, &h1, ChangeSet::default()).unwrap(); + overlay.insert(&h1, 1, &H256::default(), ChangeSet::default()).unwrap(); + overlay.insert(&h2, 3, &h1, ChangeSet::default()).unwrap(); } #[test] @@ -703,12 +608,20 @@ mod tests { let h1 = H256::random(); let h2 = H256::random(); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - overlay - .insert::(&h1, 1, &H256::default(), ChangeSet::default()) - .unwrap(); - overlay - .insert::(&h2, 2, &H256::default(), ChangeSet::default()) - .unwrap(); + overlay.insert(&h1, 1, &H256::default(), ChangeSet::default()).unwrap(); + overlay.insert(&h2, 2, &H256::default(), ChangeSet::default()).unwrap(); + } + + #[test] + fn insert_existing_fails() { + let db = make_db(&[]); + let h1 = H256::random(); + let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); + overlay.insert(&h1, 2, &H256::default(), ChangeSet::default()).unwrap(); + assert!(matches!( + overlay.insert(&h1, 2, &H256::default(), ChangeSet::default()), + Err(StateDbError::BlockAlreadyExists) + )); } #[test] @@ -718,11 +631,9 @@ mod tests { let h2 = H256::random(); let db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - overlay - .insert::(&h1, 1, &H256::default(), ChangeSet::default()) - .unwrap(); + overlay.insert(&h1, 1, &H256::default(), ChangeSet::default()).unwrap(); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h2, &mut commit).unwrap(); + overlay.canonicalize(&h2, &mut commit).unwrap(); } #[test] @@ -731,16 +642,14 @@ mod tests { let mut db = make_db(&[1, 2]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); let changeset = make_changeset(&[3, 4], &[2]); - let insertion = overlay - .insert::(&h1, 1, &H256::default(), changeset.clone()) - .unwrap(); + let insertion = overlay.insert(&h1, 1, &H256::default(), changeset.clone()).unwrap(); assert_eq!(insertion.data.inserted.len(), 0); assert_eq!(insertion.data.deleted.len(), 0); assert_eq!(insertion.meta.inserted.len(), 2); assert_eq!(insertion.meta.deleted.len(), 0); db.commit(&insertion); let mut finalization = CommitSet::default(); - overlay.canonicalize::(&h1, &mut finalization).unwrap(); + overlay.canonicalize(&h1, &mut finalization).unwrap(); assert_eq!(finalization.data.inserted.len(), changeset.inserted.len()); assert_eq!(finalization.data.deleted.len(), changeset.deleted.len()); assert_eq!(finalization.meta.inserted.len(), 1); @@ -757,11 +666,11 @@ mod tests { let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); db.commit( &overlay - .insert::(&h1, 10, &H256::default(), make_changeset(&[3, 4], &[2])) + .insert(&h1, 10, &H256::default(), make_changeset(&[3, 4], &[2])) .unwrap(), ); - db.commit(&overlay.insert::(&h2, 11, &h1, make_changeset(&[5], &[3])).unwrap()); - assert_eq!(db.meta.len(), 3); + db.commit(&overlay.insert(&h2, 11, &h1, make_changeset(&[5], &[3])).unwrap()); + assert_eq!(db.meta_len(), 3); let overlay2 = NonCanonicalOverlay::::new(&db).unwrap(); assert_eq!(overlay.levels, overlay2.levels); @@ -777,14 +686,14 @@ mod tests { let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); db.commit( &overlay - .insert::(&h1, 10, &H256::default(), make_changeset(&[3, 4], &[2])) + .insert(&h1, 10, &H256::default(), make_changeset(&[3, 4], &[2])) .unwrap(), ); - db.commit(&overlay.insert::(&h2, 11, &h1, make_changeset(&[5], &[3])).unwrap()); + db.commit(&overlay.insert(&h2, 11, &h1, make_changeset(&[5], &[3])).unwrap()); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h1, &mut commit).unwrap(); + overlay.canonicalize(&h1, &mut commit).unwrap(); + overlay.unpin(&h1); db.commit(&commit); - overlay.apply_pending(); assert_eq!(overlay.levels.len(), 1); let overlay2 = NonCanonicalOverlay::::new(&db).unwrap(); @@ -801,30 +710,26 @@ mod tests { let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); let changeset1 = make_changeset(&[5, 6], &[2]); let changeset2 = make_changeset(&[7, 8], &[5, 3]); - db.commit(&overlay.insert::(&h1, 1, &H256::default(), changeset1).unwrap()); + db.commit(&overlay.insert(&h1, 1, &H256::default(), changeset1).unwrap()); assert!(contains(&overlay, 5)); - db.commit(&overlay.insert::(&h2, 2, &h1, changeset2).unwrap()); + db.commit(&overlay.insert(&h2, 2, &h1, changeset2).unwrap()); assert!(contains(&overlay, 7)); assert!(contains(&overlay, 5)); assert_eq!(overlay.levels.len(), 2); assert_eq!(overlay.parents.len(), 2); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h1, &mut commit).unwrap(); + overlay.canonicalize(&h1, &mut commit).unwrap(); db.commit(&commit); assert!(contains(&overlay, 5)); - assert_eq!(overlay.levels.len(), 2); - assert_eq!(overlay.parents.len(), 2); - overlay.apply_pending(); - assert_eq!(overlay.levels.len(), 1); - assert_eq!(overlay.parents.len(), 1); - assert!(!contains(&overlay, 5)); assert!(contains(&overlay, 7)); + assert_eq!(overlay.levels.len(), 1); + assert_eq!(overlay.parents.len(), 2); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h2, &mut commit).unwrap(); + overlay.canonicalize(&h2, &mut commit).unwrap(); + assert!(!contains(&overlay, 5)); db.commit(&commit); - overlay.apply_pending(); assert_eq!(overlay.levels.len(), 0); - assert_eq!(overlay.parents.len(), 0); + assert_eq!(overlay.parents.len(), 1); assert!(db.data_eq(&make_db(&[1, 4, 6, 7, 8]))); } @@ -835,38 +740,32 @@ mod tests { let (h_2, c_2) = (H256::random(), make_changeset(&[1], &[])); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit(&overlay.insert::(&h_1, 1, &H256::default(), c_1).unwrap()); - db.commit(&overlay.insert::(&h_2, 1, &H256::default(), c_2).unwrap()); + db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); + db.commit(&overlay.insert(&h_2, 1, &H256::default(), c_2).unwrap()); assert!(contains(&overlay, 1)); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_1, &mut commit).unwrap(); + overlay.canonicalize(&h_1, &mut commit).unwrap(); db.commit(&commit); - assert!(contains(&overlay, 1)); - overlay.apply_pending(); + // explicitly unpin last block + overlay.unpin(&h_1); assert!(!contains(&overlay, 1)); } #[test] - fn insert_with_pending_canonicalization() { + fn insert_and_canonicalize() { let h1 = H256::random(); let h2 = H256::random(); let h3 = H256::random(); let mut db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); let changeset = make_changeset(&[], &[]); - db.commit( - &overlay - .insert::(&h1, 1, &H256::default(), changeset.clone()) - .unwrap(), - ); - db.commit(&overlay.insert::(&h2, 2, &h1, changeset.clone()).unwrap()); - overlay.apply_pending(); + db.commit(&overlay.insert(&h1, 1, &H256::default(), changeset.clone()).unwrap()); + db.commit(&overlay.insert(&h2, 2, &h1, changeset.clone()).unwrap()); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h1, &mut commit).unwrap(); - overlay.canonicalize::(&h2, &mut commit).unwrap(); + overlay.canonicalize(&h1, &mut commit).unwrap(); + overlay.canonicalize(&h2, &mut commit).unwrap(); db.commit(&commit); - db.commit(&overlay.insert::(&h3, 3, &h2, changeset.clone()).unwrap()); - overlay.apply_pending(); + db.commit(&overlay.insert(&h3, 3, &h2, changeset.clone()).unwrap()); assert_eq!(overlay.levels.len(), 1); } @@ -900,21 +799,21 @@ mod tests { let (h_2_1_1, c_2_1_1) = (H256::random(), make_changeset(&[211], &[])); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit(&overlay.insert::(&h_1, 1, &H256::default(), c_1).unwrap()); + db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); - db.commit(&overlay.insert::(&h_1_1, 2, &h_1, c_1_1).unwrap()); - db.commit(&overlay.insert::(&h_1_2, 2, &h_1, c_1_2).unwrap()); + db.commit(&overlay.insert(&h_1_1, 2, &h_1, c_1_1).unwrap()); + db.commit(&overlay.insert(&h_1_2, 2, &h_1, c_1_2).unwrap()); - db.commit(&overlay.insert::(&h_2, 1, &H256::default(), c_2).unwrap()); + db.commit(&overlay.insert(&h_2, 1, &H256::default(), c_2).unwrap()); - db.commit(&overlay.insert::(&h_2_1, 2, &h_2, c_2_1).unwrap()); - db.commit(&overlay.insert::(&h_2_2, 2, &h_2, c_2_2).unwrap()); + db.commit(&overlay.insert(&h_2_1, 2, &h_2, c_2_1).unwrap()); + db.commit(&overlay.insert(&h_2_2, 2, &h_2, c_2_2).unwrap()); - db.commit(&overlay.insert::(&h_1_1_1, 3, &h_1_1, c_1_1_1).unwrap()); - db.commit(&overlay.insert::(&h_1_2_1, 3, &h_1_2, c_1_2_1).unwrap()); - db.commit(&overlay.insert::(&h_1_2_2, 3, &h_1_2, c_1_2_2).unwrap()); - db.commit(&overlay.insert::(&h_1_2_3, 3, &h_1_2, c_1_2_3).unwrap()); - db.commit(&overlay.insert::(&h_2_1_1, 3, &h_2_1, c_2_1_1).unwrap()); + db.commit(&overlay.insert(&h_1_1_1, 3, &h_1_1, c_1_1_1).unwrap()); + db.commit(&overlay.insert(&h_1_2_1, 3, &h_1_2, c_1_2_1).unwrap()); + db.commit(&overlay.insert(&h_1_2_2, 3, &h_1_2, c_1_2_2).unwrap()); + db.commit(&overlay.insert(&h_1_2_3, 3, &h_1_2, c_1_2_3).unwrap()); + db.commit(&overlay.insert(&h_2_1_1, 3, &h_2_1, c_2_1_1).unwrap()); assert!(contains(&overlay, 2)); assert!(contains(&overlay, 11)); @@ -934,9 +833,10 @@ mod tests { // canonicalize 1. 2 and all its children should be discarded let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_1, &mut commit).unwrap(); + overlay.canonicalize(&h_1, &mut commit).unwrap(); + // explicitly unpin last block + overlay.unpin(&h_1); db.commit(&commit); - overlay.apply_pending(); assert_eq!(overlay.levels.len(), 2); assert_eq!(overlay.parents.len(), 6); assert!(!contains(&overlay, 1)); @@ -955,9 +855,9 @@ mod tests { // canonicalize 1_2. 1_1 and all its children should be discarded let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_1_2, &mut commit).unwrap(); + overlay.canonicalize(&h_1_2, &mut commit).unwrap(); + overlay.unpin(&h_1_2); db.commit(&commit); - overlay.apply_pending(); assert_eq!(overlay.levels.len(), 1); assert_eq!(overlay.parents.len(), 3); assert!(!contains(&overlay, 11)); @@ -972,9 +872,9 @@ mod tests { // canonicalize 1_2_2 let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_1_2_2, &mut commit).unwrap(); + overlay.canonicalize(&h_1_2_2, &mut commit).unwrap(); + overlay.unpin(&h_1_2_2); db.commit(&commit); - overlay.apply_pending(); assert_eq!(overlay.levels.len(), 0); assert_eq!(overlay.parents.len(), 0); assert!(db.data_eq(&make_db(&[1, 12, 122]))); @@ -990,8 +890,8 @@ mod tests { assert!(overlay.revert_one().is_none()); let changeset1 = make_changeset(&[5, 6], &[2]); let changeset2 = make_changeset(&[7, 8], &[5, 3]); - db.commit(&overlay.insert::(&h1, 1, &H256::default(), changeset1).unwrap()); - db.commit(&overlay.insert::(&h2, 2, &h1, changeset2).unwrap()); + db.commit(&overlay.insert(&h1, 1, &H256::default(), changeset1).unwrap()); + db.commit(&overlay.insert(&h2, 2, &h1, changeset2).unwrap()); assert!(contains(&overlay, 7)); db.commit(&overlay.revert_one().unwrap()); assert_eq!(overlay.parents.len(), 1); @@ -1003,31 +903,6 @@ mod tests { assert!(overlay.revert_one().is_none()); } - #[test] - fn revert_pending_insertion() { - let h1 = H256::random(); - let h2_1 = H256::random(); - let h2_2 = H256::random(); - let db = make_db(&[]); - let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - let changeset1 = make_changeset(&[5, 6], &[2]); - let changeset2 = make_changeset(&[7, 8], &[5, 3]); - let changeset3 = make_changeset(&[9], &[]); - overlay.insert::(&h1, 1, &H256::default(), changeset1).unwrap(); - assert!(contains(&overlay, 5)); - overlay.insert::(&h2_1, 2, &h1, changeset2).unwrap(); - overlay.insert::(&h2_2, 2, &h1, changeset3).unwrap(); - assert!(contains(&overlay, 7)); - assert!(contains(&overlay, 5)); - assert!(contains(&overlay, 9)); - assert_eq!(overlay.levels.len(), 2); - assert_eq!(overlay.parents.len(), 3); - overlay.revert_pending(); - assert!(!contains(&overlay, 5)); - assert_eq!(overlay.levels.len(), 0); - assert_eq!(overlay.parents.len(), 0); - } - #[test] fn keeps_pinned() { let mut db = make_db(&[]); @@ -1040,16 +915,14 @@ mod tests { let (h_2, c_2) = (H256::random(), make_changeset(&[2], &[])); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit(&overlay.insert::(&h_1, 1, &H256::default(), c_1).unwrap()); - db.commit(&overlay.insert::(&h_2, 1, &H256::default(), c_2).unwrap()); - overlay.apply_pending(); + db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); + db.commit(&overlay.insert(&h_2, 1, &H256::default(), c_2).unwrap()); overlay.pin(&h_1); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_2, &mut commit).unwrap(); + overlay.canonicalize(&h_2, &mut commit).unwrap(); db.commit(&commit); - overlay.apply_pending(); assert!(contains(&overlay, 1)); overlay.unpin(&h_1); assert!(!contains(&overlay, 1)); @@ -1070,17 +943,15 @@ mod tests { let (h_3, c_3) = (H256::random(), make_changeset(&[], &[])); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit(&overlay.insert::(&h_1, 1, &H256::default(), c_1).unwrap()); - db.commit(&overlay.insert::(&h_2, 1, &H256::default(), c_2).unwrap()); - db.commit(&overlay.insert::(&h_3, 1, &H256::default(), c_3).unwrap()); - overlay.apply_pending(); + db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); + db.commit(&overlay.insert(&h_2, 1, &H256::default(), c_2).unwrap()); + db.commit(&overlay.insert(&h_3, 1, &H256::default(), c_3).unwrap()); overlay.pin(&h_1); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_3, &mut commit).unwrap(); + overlay.canonicalize(&h_3, &mut commit).unwrap(); db.commit(&commit); - overlay.apply_pending(); // 1_2 should be discarded, 1_1 is pinned assert!(contains(&overlay, 1)); overlay.unpin(&h_1); @@ -1100,21 +971,20 @@ mod tests { let (h_21, c_21) = (H256::random(), make_changeset(&[], &[])); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit(&overlay.insert::(&h_11, 1, &H256::default(), c_11).unwrap()); - db.commit(&overlay.insert::(&h_12, 1, &H256::default(), c_12).unwrap()); - db.commit(&overlay.insert::(&h_21, 2, &h_11, c_21).unwrap()); - overlay.apply_pending(); + db.commit(&overlay.insert(&h_11, 1, &H256::default(), c_11).unwrap()); + db.commit(&overlay.insert(&h_12, 1, &H256::default(), c_12).unwrap()); + db.commit(&overlay.insert(&h_21, 2, &h_11, c_21).unwrap()); overlay.pin(&h_21); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_12, &mut commit).unwrap(); + overlay.canonicalize(&h_12, &mut commit).unwrap(); db.commit(&commit); - overlay.apply_pending(); // 1_1 and 2_1 should be both pinned assert!(contains(&overlay, 1)); overlay.unpin(&h_21); assert!(!contains(&overlay, 1)); + overlay.unpin(&h_12); assert!(overlay.pinned.is_empty()); } @@ -1129,34 +999,28 @@ mod tests { let h21 = H256::random(); let mut db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit( - &overlay - .insert::(&root, 10, &H256::default(), make_changeset(&[], &[])) - .unwrap(), - ); - db.commit(&overlay.insert::(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); - db.commit(&overlay.insert::(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); - db.commit(&overlay.insert::(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); - db.commit(&overlay.insert::(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); + db.commit(&overlay.insert(&root, 10, &H256::default(), make_changeset(&[], &[])).unwrap()); + db.commit(&overlay.insert(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); + db.commit(&overlay.insert(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); + db.commit(&overlay.insert(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); + db.commit(&overlay.insert(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); let mut commit = CommitSet::default(); - overlay.canonicalize::(&root, &mut commit).unwrap(); - overlay.canonicalize::(&h2, &mut commit).unwrap(); // h11 should stay in the DB + overlay.canonicalize(&root, &mut commit).unwrap(); + overlay.canonicalize(&h2, &mut commit).unwrap(); // h11 should stay in the DB db.commit(&commit); - overlay.apply_pending(); assert_eq!(overlay.levels.len(), 1); assert!(contains(&overlay, 21)); assert!(!contains(&overlay, 11)); assert!(db.get_meta(&to_journal_key(12, 1)).unwrap().is_some()); - assert!(db.get_meta(&to_journal_key(12, 0)).unwrap().is_none()); // Restore into a new overlay and check that journaled value exists. let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); assert!(contains(&overlay, 21)); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h21, &mut commit).unwrap(); // h11 should stay in the DB + overlay.canonicalize(&h21, &mut commit).unwrap(); // h11 should stay in the DB + overlay.unpin(&h21); db.commit(&commit); - overlay.apply_pending(); assert!(!contains(&overlay, 21)); } @@ -1171,25 +1035,20 @@ mod tests { let h21 = H256::random(); let mut db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit( - &overlay - .insert::(&root, 10, &H256::default(), make_changeset(&[], &[])) - .unwrap(), - ); - db.commit(&overlay.insert::(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); - db.commit(&overlay.insert::(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); - db.commit(&overlay.insert::(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); - db.commit(&overlay.insert::(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); + db.commit(&overlay.insert(&root, 10, &H256::default(), make_changeset(&[], &[])).unwrap()); + db.commit(&overlay.insert(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); + db.commit(&overlay.insert(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); + db.commit(&overlay.insert(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); + db.commit(&overlay.insert(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); let mut commit = CommitSet::default(); - overlay.canonicalize::(&root, &mut commit).unwrap(); - overlay.canonicalize::(&h2, &mut commit).unwrap(); // h11 should stay in the DB + overlay.canonicalize(&root, &mut commit).unwrap(); + overlay.canonicalize(&h2, &mut commit).unwrap(); // h11 should stay in the DB db.commit(&commit); - overlay.apply_pending(); // add another block at top level. It should reuse journal index 0 of previously discarded // block let h22 = H256::random(); - db.commit(&overlay.insert::(&h22, 12, &h2, make_changeset(&[22], &[])).unwrap()); + db.commit(&overlay.insert(&h22, 12, &h2, make_changeset(&[22], &[])).unwrap()); assert_eq!(overlay.levels[0].blocks[0].journal_index, 1); assert_eq!(overlay.levels[0].blocks[1].journal_index, 0); @@ -1209,15 +1068,11 @@ mod tests { let h21 = H256::random(); let mut db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit( - &overlay - .insert::(&root, 10, &H256::default(), make_changeset(&[], &[])) - .unwrap(), - ); - db.commit(&overlay.insert::(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); - db.commit(&overlay.insert::(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); - db.commit(&overlay.insert::(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); - db.commit(&overlay.insert::(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); + db.commit(&overlay.insert(&root, 10, &H256::default(), make_changeset(&[], &[])).unwrap()); + db.commit(&overlay.insert(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); + db.commit(&overlay.insert(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); + db.commit(&overlay.insert(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); + db.commit(&overlay.insert(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); assert!(overlay.remove(&h1).is_none()); assert!(overlay.remove(&h2).is_none()); assert_eq!(overlay.levels.len(), 3); diff --git a/client/state-db/src/pruning.rs b/client/state-db/src/pruning.rs index 465c1ecda6cc1..d942fb2428b6a 100644 --- a/client/state-db/src/pruning.rs +++ b/client/state-db/src/pruning.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -24,418 +24,823 @@ //! the death list. //! The changes are journaled in the DB. -use crate::{to_meta_key, CommitSet, Error, Hash, MetaDb}; +use crate::{ + noncanonical::LAST_CANONICAL, to_meta_key, CommitSet, Error, Hash, MetaDb, StateDbError, + DEFAULT_MAX_BLOCK_CONSTRAINT, +}; use codec::{Decode, Encode}; -use log::{trace, warn}; +use log::trace; use std::collections::{HashMap, HashSet, VecDeque}; -const LAST_PRUNED: &[u8] = b"last_pruned"; +pub(crate) const LAST_PRUNED: &[u8] = b"last_pruned"; const PRUNING_JOURNAL: &[u8] = b"pruning_journal"; /// See module documentation. -#[derive(parity_util_mem_derive::MallocSizeOf)] -pub struct RefWindow { - /// A queue of keys that should be deleted for each block in the pruning window. - death_rows: VecDeque>, - /// An index that maps each key from `death_rows` to block number. - death_index: HashMap, - /// Block number that corresponds to the front of `death_rows`. - pending_number: u64, - /// Number of call of `note_canonical` after - /// last call `apply_pending` or `revert_pending` - pending_canonicalizations: usize, - /// Number of calls of `prune_one` after - /// last call `apply_pending` or `revert_pending` - pending_prunings: usize, - /// Keep track of re-inserted keys and do not delete them when pruning. - /// Setting this to false requires backend that supports reference - /// counting. - count_insertions: bool, +pub struct RefWindow { + /// A queue of blocks keep tracking keys that should be deleted for each block in the + /// pruning window. + queue: DeathRowQueue, + /// Block number that is next to be pruned. + base: u64, } -#[derive(Debug, PartialEq, Eq, parity_util_mem_derive::MallocSizeOf)] -struct DeathRow { - hash: BlockHash, - journal_key: Vec, - deleted: HashSet, -} - -#[derive(Encode, Decode)] -struct JournalRecord { - hash: BlockHash, - inserted: Vec, - deleted: Vec, +/// `DeathRowQueue` used to keep track of blocks in the pruning window, there are two flavors: +/// - `Mem`, used when the backend database do not supports reference counting, keep all +/// blocks in memory, and keep track of re-inserted keys to not delete them when pruning +/// - `DbBacked`, used when the backend database supports reference counting, only keep +/// a few number of blocks in memory and load more blocks on demand +enum DeathRowQueue { + Mem { + /// A queue of keys that should be deleted for each block in the pruning window. + death_rows: VecDeque>, + /// An index that maps each key from `death_rows` to block number. + death_index: HashMap, + }, + DbBacked { + // The backend database + db: D, + /// A queue of keys that should be deleted for each block in the pruning window. + /// Only caching the first few blocks of the pruning window, blocks inside are + /// successive and ordered by block number + cache: VecDeque>, + /// A soft limit of the cache's size + cache_capacity: usize, + /// Last block number added to the window + last: Option, + }, } -fn to_journal_key(block: u64) -> Vec { - to_meta_key(PRUNING_JOURNAL, &block) -} - -impl RefWindow { - pub fn new( - db: &D, - count_insertions: bool, - ) -> Result, Error> { - let last_pruned = db.get_meta(&to_meta_key(LAST_PRUNED, &())).map_err(|e| Error::Db(e))?; - let pending_number: u64 = match last_pruned { - Some(buffer) => u64::decode(&mut buffer.as_slice())? + 1, - None => 0, - }; - let mut block = pending_number; - let mut pruning = RefWindow { - death_rows: Default::default(), - death_index: Default::default(), - pending_number, - pending_canonicalizations: 0, - pending_prunings: 0, - count_insertions, +impl DeathRowQueue { + /// Return a `DeathRowQueue` that all blocks are keep in memory + fn new_mem(db: &D, base: u64) -> Result, Error> { + let mut block = base; + let mut queue = DeathRowQueue::::Mem { + death_rows: VecDeque::new(), + death_index: HashMap::new(), }; // read the journal - trace!(target: "state-db", "Reading pruning journal. Pending #{}", pending_number); + trace!(target: "state-db", "Reading pruning journal for the memory queue. Pending #{}", base); loop { let journal_key = to_journal_key(block); - match db.get_meta(&journal_key).map_err(|e| Error::Db(e))? { + match db.get_meta(&journal_key).map_err(Error::Db)? { Some(record) => { let record: JournalRecord = Decode::decode(&mut record.as_slice())?; trace!(target: "state-db", "Pruning journal entry {} ({} inserted, {} deleted)", block, record.inserted.len(), record.deleted.len()); - pruning.import( - &record.hash, - journal_key, - record.inserted.into_iter(), - record.deleted, - ); + queue.import(base, block, record); }, None => break, } block += 1; } - Ok(pruning) + Ok(queue) } - fn import>( + /// Return a `DeathRowQueue` that backed by an database, and only keep a few number + /// of blocks in memory + fn new_db_backed( + db: D, + base: u64, + last: Option, + window_size: u32, + ) -> Result, Error> { + // limit the cache capacity from 1 to `DEFAULT_MAX_BLOCK_CONSTRAINT` + let cache_capacity = window_size.clamp(1, DEFAULT_MAX_BLOCK_CONSTRAINT) as usize; + let mut cache = VecDeque::with_capacity(cache_capacity); + trace!(target: "state-db", "Reading pruning journal for the database-backed queue. Pending #{}", base); + DeathRowQueue::load_batch_from_db(&db, &mut cache, base, cache_capacity)?; + Ok(DeathRowQueue::DbBacked { db, cache, cache_capacity, last }) + } + + /// import a new block to the back of the queue + fn import(&mut self, base: u64, num: u64, journal_record: JournalRecord) { + let JournalRecord { hash, inserted, deleted } = journal_record; + trace!(target: "state-db", "Importing {}, base={}", num, base); + match self { + DeathRowQueue::DbBacked { cache, cache_capacity, last, .. } => { + // If the new block continues cached range and there is space, load it directly into + // cache. + if num == base + cache.len() as u64 && cache.len() < *cache_capacity { + trace!(target: "state-db", "Adding to DB backed cache {:?} (#{})", hash, num); + cache.push_back(DeathRow { hash, deleted: deleted.into_iter().collect() }); + } + *last = Some(num); + }, + DeathRowQueue::Mem { death_rows, death_index } => { + // remove all re-inserted keys from death rows + for k in inserted { + if let Some(block) = death_index.remove(&k) { + death_rows[(block - base) as usize].deleted.remove(&k); + } + } + // add new keys + let imported_block = base + death_rows.len() as u64; + for k in deleted.iter() { + death_index.insert(k.clone(), imported_block); + } + death_rows.push_back(DeathRow { hash, deleted: deleted.into_iter().collect() }); + }, + } + } + + /// Pop out one block from the front of the queue, `base` is the block number + /// of the first block of the queue + fn pop_front( &mut self, - hash: &BlockHash, - journal_key: Vec, - inserted: I, - deleted: Vec, - ) { - if self.count_insertions { - // remove all re-inserted keys from death rows - for k in inserted { - if let Some(block) = self.death_index.remove(&k) { - self.death_rows[(block - self.pending_number) as usize].deleted.remove(&k); + base: u64, + ) -> Result>, Error> { + match self { + DeathRowQueue::DbBacked { db, cache, cache_capacity, .. } => { + if cache.is_empty() { + DeathRowQueue::load_batch_from_db(db, cache, base, *cache_capacity)?; } - } + Ok(cache.pop_front()) + }, + DeathRowQueue::Mem { death_rows, death_index } => match death_rows.pop_front() { + Some(row) => { + for k in row.deleted.iter() { + death_index.remove(k); + } + Ok(Some(row)) + }, + None => Ok(None), + }, + } + } - // add new keys - let imported_block = self.pending_number + self.death_rows.len() as u64; - for k in deleted.iter() { - self.death_index.insert(k.clone(), imported_block); + /// Load a batch of blocks from the backend database into `cache`, starting from `base` and up + /// to `base + cache_capacity` + fn load_batch_from_db( + db: &D, + cache: &mut VecDeque>, + base: u64, + cache_capacity: usize, + ) -> Result<(), Error> { + let start = base + cache.len() as u64; + let batch_size = cache_capacity; + for i in 0..batch_size as u64 { + match load_death_row_from_db::(db, start + i)? { + Some(row) => { + cache.push_back(row); + }, + None => break, } } - self.death_rows.push_back(DeathRow { - hash: hash.clone(), - deleted: deleted.into_iter().collect(), - journal_key, - }); + Ok(()) } - pub fn window_size(&self) -> u64 { - (self.death_rows.len() - self.pending_prunings) as u64 + /// Check if the block at the given `index` of the queue exist + /// it is the caller's responsibility to ensure `index` won't be out of bounds + fn have_block(&self, hash: &BlockHash, index: usize) -> HaveBlock { + match self { + DeathRowQueue::DbBacked { cache, .. } => { + if cache.len() > index { + (cache[index].hash == *hash).into() + } else { + // The block is not in the cache but it still may exist on disk. + HaveBlock::Maybe + } + }, + DeathRowQueue::Mem { death_rows, .. } => (death_rows[index].hash == *hash).into(), + } + } + + /// Return the number of block in the pruning window + fn len(&self, base: u64) -> u64 { + match self { + DeathRowQueue::DbBacked { last, .. } => last.map_or(0, |l| l + 1 - base), + DeathRowQueue::Mem { death_rows, .. } => death_rows.len() as u64, + } + } + + #[cfg(test)] + fn get_mem_queue_state( + &self, + ) -> Option<(&VecDeque>, &HashMap)> { + match self { + DeathRowQueue::DbBacked { .. } => None, + DeathRowQueue::Mem { death_rows, death_index } => Some((death_rows, death_index)), + } + } + + #[cfg(test)] + fn get_db_backed_queue_state( + &self, + ) -> Option<(&VecDeque>, Option)> { + match self { + DeathRowQueue::DbBacked { cache, last, .. } => Some((cache, *last)), + DeathRowQueue::Mem { .. } => None, + } + } +} + +fn load_death_row_from_db( + db: &D, + block: u64, +) -> Result>, Error> { + let journal_key = to_journal_key(block); + match db.get_meta(&journal_key).map_err(Error::Db)? { + Some(record) => { + let JournalRecord { hash, deleted, .. } = Decode::decode(&mut record.as_slice())?; + Ok(Some(DeathRow { hash, deleted: deleted.into_iter().collect() })) + }, + None => Ok(None), + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct DeathRow { + hash: BlockHash, + deleted: HashSet, +} + +#[derive(Encode, Decode, Default)] +struct JournalRecord { + hash: BlockHash, + inserted: Vec, + deleted: Vec, +} + +fn to_journal_key(block: u64) -> Vec { + to_meta_key(PRUNING_JOURNAL, &block) +} + +/// The result return by `RefWindow::have_block` +#[derive(Debug, PartialEq, Eq)] +pub enum HaveBlock { + /// Definitely don't have this block. + No, + /// May or may not have this block, need further checking + Maybe, + /// Definitely has this block + Yes, +} + +impl From for HaveBlock { + fn from(have: bool) -> Self { + if have { + HaveBlock::Yes + } else { + HaveBlock::No + } + } +} + +impl RefWindow { + pub fn new( + db: D, + window_size: u32, + count_insertions: bool, + ) -> Result, Error> { + // the block number of the first block in the queue or the next block number if the queue is + // empty + let base = match db.get_meta(&to_meta_key(LAST_PRUNED, &())).map_err(Error::Db)? { + Some(buffer) => u64::decode(&mut buffer.as_slice())? + 1, + None => 0, + }; + // the block number of the last block in the queue + let last_canonicalized_number = + match db.get_meta(&to_meta_key(LAST_CANONICAL, &())).map_err(Error::Db)? { + Some(buffer) => Some(<(BlockHash, u64)>::decode(&mut buffer.as_slice())?.1), + None => None, + }; + + let queue = if count_insertions { + DeathRowQueue::new_mem(&db, base)? + } else { + let last = match last_canonicalized_number { + Some(last_canonicalized_number) => { + debug_assert!(last_canonicalized_number + 1 >= base); + Some(last_canonicalized_number) + }, + // None means `LAST_CANONICAL` is never been wrote, since the pruning journals are + // in the same `CommitSet` as `LAST_CANONICAL`, it means no pruning journal have + // ever been committed to the db, thus set `unload` to zero + None => None, + }; + DeathRowQueue::new_db_backed(db, base, last, window_size)? + }; + + Ok(RefWindow { queue, base }) } - pub fn next_hash(&self) -> Option { - self.death_rows.get(self.pending_prunings).map(|r| r.hash.clone()) + pub fn window_size(&self) -> u64 { + self.queue.len(self.base) as u64 } - pub fn mem_used(&self) -> usize { - 0 + /// Get the hash of the next pruning block + pub fn next_hash(&mut self) -> Result, Error> { + let res = match &mut self.queue { + DeathRowQueue::DbBacked { db, cache, cache_capacity, .. } => { + if cache.is_empty() { + DeathRowQueue::load_batch_from_db(db, cache, self.base, *cache_capacity)?; + } + cache.front().map(|r| r.hash.clone()) + }, + DeathRowQueue::Mem { death_rows, .. } => death_rows.front().map(|r| r.hash.clone()), + }; + Ok(res) } - pub fn pending(&self) -> u64 { - self.pending_number + self.pending_prunings as u64 + fn is_empty(&self) -> bool { + self.window_size() == 0 } - pub fn have_block(&self, hash: &BlockHash) -> bool { - self.death_rows.iter().skip(self.pending_prunings).any(|r| r.hash == *hash) + // Check if a block is in the pruning window and not be pruned yet + pub fn have_block(&self, hash: &BlockHash, number: u64) -> HaveBlock { + // if the queue is empty or the block number exceed the pruning window, we definitely + // do not have this block + if self.is_empty() || number < self.base || number >= self.base + self.window_size() { + return HaveBlock::No + } + self.queue.have_block(hash, (number - self.base) as usize) } /// Prune next block. Expects at least one block in the window. Adds changes to `commit`. - pub fn prune_one(&mut self, commit: &mut CommitSet) { - if let Some(pruned) = self.death_rows.get(self.pending_prunings) { + pub fn prune_one(&mut self, commit: &mut CommitSet) -> Result<(), Error> { + if let Some(pruned) = self.queue.pop_front(self.base)? { trace!(target: "state-db", "Pruning {:?} ({} deleted)", pruned.hash, pruned.deleted.len()); - let index = self.pending_number + self.pending_prunings as u64; - commit.data.deleted.extend(pruned.deleted.iter().cloned()); + let index = self.base; + commit.data.deleted.extend(pruned.deleted.into_iter()); commit.meta.inserted.push((to_meta_key(LAST_PRUNED, &()), index.encode())); - commit.meta.deleted.push(pruned.journal_key.clone()); - self.pending_prunings += 1; + commit.meta.deleted.push(to_journal_key(self.base)); + self.base += 1; + Ok(()) } else { - warn!(target: "state-db", "Trying to prune when there's nothing to prune"); + trace!(target: "state-db", "Trying to prune when there's nothing to prune"); + Err(Error::StateDb(StateDbError::BlockUnavailable)) } } /// Add a change set to the window. Creates a journal record and pushes it to `commit` - pub fn note_canonical(&mut self, hash: &BlockHash, commit: &mut CommitSet) { + pub fn note_canonical( + &mut self, + hash: &BlockHash, + number: u64, + commit: &mut CommitSet, + ) -> Result<(), Error> { + if self.base == 0 && self.is_empty() && number > 0 { + // assume that parent was canonicalized + self.base = number; + } else if (self.base + self.window_size()) != number { + return Err(Error::StateDb(StateDbError::InvalidBlockNumber)) + } trace!(target: "state-db", "Adding to pruning window: {:?} ({} inserted, {} deleted)", hash, commit.data.inserted.len(), commit.data.deleted.len()); - let inserted = if self.count_insertions { + let inserted = if matches!(self.queue, DeathRowQueue::Mem { .. }) { commit.data.inserted.iter().map(|(k, _)| k.clone()).collect() } else { Default::default() }; - let deleted = ::std::mem::take(&mut commit.data.deleted); + let deleted = std::mem::take(&mut commit.data.deleted); let journal_record = JournalRecord { hash: hash.clone(), inserted, deleted }; - let block = self.pending_number + self.death_rows.len() as u64; - let journal_key = to_journal_key(block); - commit.meta.inserted.push((journal_key.clone(), journal_record.encode())); - self.import( - &journal_record.hash, - journal_key, - journal_record.inserted.into_iter(), - journal_record.deleted, - ); - self.pending_canonicalizations += 1; - } - - /// Apply all pending changes - pub fn apply_pending(&mut self) { - self.pending_canonicalizations = 0; - for _ in 0..self.pending_prunings { - let pruned = self - .death_rows - .pop_front() - .expect("pending_prunings is always < death_rows.len()"); - trace!(target: "state-db", "Applying pruning {:?} ({} deleted)", pruned.hash, pruned.deleted.len()); - if self.count_insertions { - for k in pruned.deleted.iter() { - self.death_index.remove(&k); - } - } - self.pending_number += 1; - } - self.pending_prunings = 0; - } - - /// Revert all pending changes - pub fn revert_pending(&mut self) { - // Revert pending deletions. - // Note that pending insertions might cause some existing deletions to be removed from - // `death_index` We don't bother to track and revert that for now. This means that a few - // nodes might end up no being deleted in case transaction fails and `revert_pending` is - // called. - self.death_rows.truncate(self.death_rows.len() - self.pending_canonicalizations); - if self.count_insertions { - let new_max_block = self.death_rows.len() as u64 + self.pending_number; - self.death_index.retain(|_, block| *block < new_max_block); - } - self.pending_canonicalizations = 0; - self.pending_prunings = 0; + commit.meta.inserted.push((to_journal_key(number), journal_record.encode())); + self.queue.import(self.base, number, journal_record); + Ok(()) } } #[cfg(test)] mod tests { - use super::RefWindow; + use super::{to_journal_key, DeathRowQueue, HaveBlock, JournalRecord, RefWindow, LAST_PRUNED}; use crate::{ + noncanonical::LAST_CANONICAL, test::{make_commit, make_db, TestDb}, - CommitSet, + to_meta_key, CommitSet, Error, Hash, StateDbError, DEFAULT_MAX_BLOCK_CONSTRAINT, }; + use codec::Encode; use sp_core::H256; - fn check_journal(pruning: &RefWindow, db: &TestDb) { - let restored: RefWindow = RefWindow::new(db, pruning.count_insertions).unwrap(); - assert_eq!(pruning.pending_number, restored.pending_number); - assert_eq!(pruning.death_rows, restored.death_rows); - assert_eq!(pruning.death_index, restored.death_index); + fn check_journal(pruning: &RefWindow, db: &TestDb) { + let count_insertions = matches!(pruning.queue, DeathRowQueue::Mem { .. }); + let restored: RefWindow = + RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, count_insertions).unwrap(); + assert_eq!(pruning.base, restored.base); + assert_eq!(pruning.queue.get_mem_queue_state(), restored.queue.get_mem_queue_state()); } #[test] fn created_from_empty_db() { let db = make_db(&[]); - let pruning: RefWindow = RefWindow::new(&db, true).unwrap(); - assert_eq!(pruning.pending_number, 0); - assert!(pruning.death_rows.is_empty()); - assert!(pruning.death_index.is_empty()); + let pruning: RefWindow = + RefWindow::new(db, DEFAULT_MAX_BLOCK_CONSTRAINT, true).unwrap(); + assert_eq!(pruning.base, 0); + let (death_rows, death_index) = pruning.queue.get_mem_queue_state().unwrap(); + assert!(death_rows.is_empty()); + assert!(death_index.is_empty()); } #[test] fn prune_empty() { let db = make_db(&[]); - let mut pruning: RefWindow = RefWindow::new(&db, true).unwrap(); + let mut pruning: RefWindow = + RefWindow::new(db, DEFAULT_MAX_BLOCK_CONSTRAINT, true).unwrap(); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); - assert_eq!(pruning.pending_number, 0); - assert!(pruning.death_rows.is_empty()); - assert!(pruning.death_index.is_empty()); - assert!(pruning.pending_prunings == 0); - assert!(pruning.pending_canonicalizations == 0); + assert_eq!( + Err(Error::StateDb(StateDbError::BlockUnavailable)), + pruning.prune_one(&mut commit) + ); + assert_eq!(pruning.base, 0); + let (death_rows, death_index) = pruning.queue.get_mem_queue_state().unwrap(); + assert!(death_rows.is_empty()); + assert!(death_index.is_empty()); } #[test] fn prune_one() { let mut db = make_db(&[1, 2, 3]); - let mut pruning: RefWindow = RefWindow::new(&db, true).unwrap(); + let mut pruning: RefWindow = + RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, true).unwrap(); let mut commit = make_commit(&[4, 5], &[1, 3]); - let h = H256::random(); - pruning.note_canonical(&h, &mut commit); + let hash = H256::random(); + pruning.note_canonical(&hash, 0, &mut commit).unwrap(); db.commit(&commit); - assert!(pruning.have_block(&h)); - pruning.apply_pending(); - assert!(pruning.have_block(&h)); + assert_eq!(pruning.have_block(&hash, 0), HaveBlock::Yes); + assert_eq!(pruning.have_block(&hash, 0), HaveBlock::Yes); assert!(commit.data.deleted.is_empty()); - assert_eq!(pruning.death_rows.len(), 1); - assert_eq!(pruning.death_index.len(), 2); + let (death_rows, death_index) = pruning.queue.get_mem_queue_state().unwrap(); + assert_eq!(death_rows.len(), 1); + assert_eq!(death_index.len(), 2); assert!(db.data_eq(&make_db(&[1, 2, 3, 4, 5]))); check_journal(&pruning, &db); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); - assert!(!pruning.have_block(&h)); + pruning.prune_one(&mut commit).unwrap(); + assert_eq!(pruning.have_block(&hash, 0), HaveBlock::No); db.commit(&commit); - pruning.apply_pending(); - assert!(!pruning.have_block(&h)); + assert_eq!(pruning.have_block(&hash, 0), HaveBlock::No); assert!(db.data_eq(&make_db(&[2, 4, 5]))); - assert!(pruning.death_rows.is_empty()); - assert!(pruning.death_index.is_empty()); - assert_eq!(pruning.pending_number, 1); + let (death_rows, death_index) = pruning.queue.get_mem_queue_state().unwrap(); + assert!(death_rows.is_empty()); + assert!(death_index.is_empty()); + assert_eq!(pruning.base, 1); } #[test] fn prune_two() { let mut db = make_db(&[1, 2, 3]); - let mut pruning: RefWindow = RefWindow::new(&db, true).unwrap(); + let mut pruning: RefWindow = + RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, true).unwrap(); let mut commit = make_commit(&[4], &[1]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 0, &mut commit).unwrap(); db.commit(&commit); let mut commit = make_commit(&[5], &[2]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 1, &mut commit).unwrap(); db.commit(&commit); - pruning.apply_pending(); assert!(db.data_eq(&make_db(&[1, 2, 3, 4, 5]))); check_journal(&pruning, &db); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); - pruning.apply_pending(); assert!(db.data_eq(&make_db(&[2, 3, 4, 5]))); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); - pruning.apply_pending(); assert!(db.data_eq(&make_db(&[3, 4, 5]))); - assert_eq!(pruning.pending_number, 2); + assert_eq!(pruning.base, 2); } #[test] fn prune_two_pending() { let mut db = make_db(&[1, 2, 3]); - let mut pruning: RefWindow = RefWindow::new(&db, true).unwrap(); + let mut pruning: RefWindow = + RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, true).unwrap(); let mut commit = make_commit(&[4], &[1]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 0, &mut commit).unwrap(); db.commit(&commit); let mut commit = make_commit(&[5], &[2]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 1, &mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 2, 3, 4, 5]))); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[2, 3, 4, 5]))); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); - pruning.apply_pending(); assert!(db.data_eq(&make_db(&[3, 4, 5]))); - assert_eq!(pruning.pending_number, 2); + assert_eq!(pruning.base, 2); } #[test] fn reinserted_survives() { let mut db = make_db(&[1, 2, 3]); - let mut pruning: RefWindow = RefWindow::new(&db, true).unwrap(); + let mut pruning: RefWindow = + RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, true).unwrap(); let mut commit = make_commit(&[], &[2]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 0, &mut commit).unwrap(); db.commit(&commit); let mut commit = make_commit(&[2], &[]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 1, &mut commit).unwrap(); db.commit(&commit); let mut commit = make_commit(&[], &[2]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 2, &mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 2, 3]))); - pruning.apply_pending(); check_journal(&pruning, &db); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 2, 3]))); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 2, 3]))); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 3]))); - pruning.apply_pending(); - assert_eq!(pruning.pending_number, 3); + assert_eq!(pruning.base, 3); } #[test] fn reinserted_survive_pending() { let mut db = make_db(&[1, 2, 3]); - let mut pruning: RefWindow = RefWindow::new(&db, true).unwrap(); + let mut pruning: RefWindow = + RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, true).unwrap(); let mut commit = make_commit(&[], &[2]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 0, &mut commit).unwrap(); db.commit(&commit); let mut commit = make_commit(&[2], &[]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 1, &mut commit).unwrap(); db.commit(&commit); let mut commit = make_commit(&[], &[2]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 2, &mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 2, 3]))); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 2, 3]))); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 2, 3]))); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 3]))); - pruning.apply_pending(); - assert_eq!(pruning.pending_number, 3); + assert_eq!(pruning.base, 3); } #[test] fn reinserted_ignores() { let mut db = make_db(&[1, 2, 3]); - let mut pruning: RefWindow = RefWindow::new(&db, false).unwrap(); + let mut pruning: RefWindow = + RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); let mut commit = make_commit(&[], &[2]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 0, &mut commit).unwrap(); db.commit(&commit); let mut commit = make_commit(&[2], &[]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 1, &mut commit).unwrap(); db.commit(&commit); let mut commit = make_commit(&[], &[2]); - pruning.note_canonical(&H256::random(), &mut commit); + pruning.note_canonical(&H256::random(), 2, &mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 2, 3]))); - pruning.apply_pending(); check_journal(&pruning, &db); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit); + pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 3]))); - assert!(pruning.death_index.is_empty()); + } + + fn push_last_canonicalized(block: u64, commit: &mut CommitSet) { + commit + .meta + .inserted + .push((to_meta_key(LAST_CANONICAL, &()), (block, block).encode())); + } + + fn push_last_pruned(block: u64, commit: &mut CommitSet) { + commit.meta.inserted.push((to_meta_key(LAST_PRUNED, &()), block.encode())); + } + + #[test] + fn init_db_backed_queue() { + let mut db = make_db(&[]); + let mut commit = CommitSet::default(); + + fn load_pruning_from_db(db: TestDb) -> (usize, u64) { + let pruning: RefWindow = + RefWindow::new(db, DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); + (cache.len(), pruning.base) + } + + fn push_record(block: u64, commit: &mut CommitSet) { + commit + .meta + .inserted + .push((to_journal_key(block), JournalRecord::::default().encode())); + } + + // empty database + let (loaded_blocks, base) = load_pruning_from_db(db.clone()); + assert_eq!(loaded_blocks, 0); + assert_eq!(base, 0); + + // canonicalized the genesis block but no pruning + push_last_canonicalized(0, &mut commit); + push_record(0, &mut commit); + db.commit(&commit); + let (loaded_blocks, base) = load_pruning_from_db(db.clone()); + assert_eq!(loaded_blocks, 1); + assert_eq!(base, 0); + + // pruned the genesis block + push_last_pruned(0, &mut commit); + db.commit(&commit); + let (loaded_blocks, base) = load_pruning_from_db(db.clone()); + assert_eq!(loaded_blocks, 0); + assert_eq!(base, 1); + + // canonicalize more blocks + push_last_canonicalized(10, &mut commit); + for i in 1..=10 { + push_record(i, &mut commit); + } + db.commit(&commit); + let (loaded_blocks, base) = load_pruning_from_db(db.clone()); + assert_eq!(loaded_blocks, 10); + assert_eq!(base, 1); + + // pruned all blocks + push_last_pruned(10, &mut commit); + db.commit(&commit); + let (loaded_blocks, base) = load_pruning_from_db(db.clone()); + assert_eq!(loaded_blocks, 0); + assert_eq!(base, 11); + } + + #[test] + fn db_backed_queue() { + let mut db = make_db(&[]); + let mut pruning: RefWindow = + RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); + let cache_capacity = DEFAULT_MAX_BLOCK_CONSTRAINT as usize; + + // start as an empty queue + let (cache, last) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(cache.len(), 0); + assert_eq!(last, None); + + // import blocks + // queue size and content should match + for i in 0..(cache_capacity + 10) { + let mut commit = make_commit(&[], &[]); + pruning.note_canonical(&(i as u64), i as u64, &mut commit).unwrap(); + push_last_canonicalized(i as u64, &mut commit); + db.commit(&commit); + // blocks will fill the cache first + let (cache, last) = pruning.queue.get_db_backed_queue_state().unwrap(); + if i < cache_capacity { + assert_eq!(cache.len(), i + 1); + } else { + assert_eq!(cache.len(), cache_capacity); + } + assert_eq!(last, Some(i as u64)); + } + assert_eq!(pruning.window_size(), cache_capacity as u64 + 10); + let (cache, last) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(cache.len(), cache_capacity); + assert_eq!(last, Some(cache_capacity as u64 + 10 - 1)); + for i in 0..cache_capacity { + assert_eq!(cache[i].hash, i as u64); + } + + // import a new block to the end of the queue + // won't keep the new block in memory + let mut commit = CommitSet::default(); + pruning + .note_canonical(&(cache_capacity as u64 + 10), cache_capacity as u64 + 10, &mut commit) + .unwrap(); + assert_eq!(pruning.window_size(), cache_capacity as u64 + 11); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(cache.len(), cache_capacity); + + // revert the last add that no apply yet + // NOTE: do not commit the previous `CommitSet` to db + pruning = RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); + let cache_capacity = DEFAULT_MAX_BLOCK_CONSTRAINT as usize; + assert_eq!(pruning.window_size(), cache_capacity as u64 + 10); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(cache.len(), cache_capacity); + + // remove one block from the start of the queue + // block is removed from the head of cache + let mut commit = CommitSet::default(); + pruning.prune_one(&mut commit).unwrap(); + db.commit(&commit); + assert_eq!(pruning.window_size(), cache_capacity as u64 + 9); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(cache.len(), cache_capacity - 1); + for i in 0..(cache_capacity - 1) { + assert_eq!(cache[i].hash, (i + 1) as u64); + } + + // load a new queue from db + // `cache` is full again but the content of the queue should be the same + let pruning: RefWindow = + RefWindow::new(db, DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); + assert_eq!(pruning.window_size(), cache_capacity as u64 + 9); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(cache.len(), cache_capacity); + for i in 0..cache_capacity { + assert_eq!(cache[i].hash, (i + 1) as u64); + } + } + + #[test] + fn load_block_from_db() { + let mut db = make_db(&[]); + let mut pruning: RefWindow = + RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); + let cache_capacity = DEFAULT_MAX_BLOCK_CONSTRAINT as usize; + + // import blocks + for i in 0..(cache_capacity as u64 * 2 + 10) { + let mut commit = make_commit(&[], &[]); + pruning.note_canonical(&i, i, &mut commit).unwrap(); + push_last_canonicalized(i as u64, &mut commit); + db.commit(&commit); + } + + // the following operations won't trigger loading block from db: + // - getting block in cache + // - getting block not in the queue + assert_eq!(pruning.next_hash().unwrap().unwrap(), 0); + let (cache, last) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(cache.len(), cache_capacity); + assert_eq!(last, Some(cache_capacity as u64 * 2 + 10 - 1)); + + // clear all block loaded in cache + for _ in 0..cache_capacity * 2 { + let mut commit = CommitSet::default(); + pruning.prune_one(&mut commit).unwrap(); + db.commit(&commit); + } + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert!(cache.is_empty()); + + // getting the hash of block that not in cache will also trigger loading + // the remaining blocks from db + assert_eq!(pruning.next_hash().unwrap().unwrap(), (cache_capacity * 2) as u64); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(cache.len(), 10); + + // load a new queue from db + // `cache` should be the same + let pruning: RefWindow = + RefWindow::new(db, DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); + assert_eq!(pruning.window_size(), 10); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(cache.len(), 10); + for i in 0..10 { + assert_eq!(cache[i].hash, (cache_capacity * 2 + i) as u64); + } + } + + #[test] + fn get_block_from_queue() { + let mut db = make_db(&[]); + let mut pruning: RefWindow = + RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); + let cache_capacity = DEFAULT_MAX_BLOCK_CONSTRAINT as u64; + + // import blocks and commit to db + let mut commit = make_commit(&[], &[]); + for i in 0..(cache_capacity + 10) { + pruning.note_canonical(&i, i, &mut commit).unwrap(); + } + db.commit(&commit); + + // import a block but not commit to db yet + let mut pending_commit = make_commit(&[], &[]); + let index = cache_capacity + 10; + pruning.note_canonical(&index, index, &mut pending_commit).unwrap(); + + let mut commit = make_commit(&[], &[]); + // prune blocks that had committed to db + for i in 0..(cache_capacity + 10) { + assert_eq!(pruning.next_hash().unwrap(), Some(i)); + pruning.prune_one(&mut commit).unwrap(); + } + // return `None` for block that did not commit to db + assert_eq!(pruning.next_hash().unwrap(), None); + assert_eq!( + pruning.prune_one(&mut commit).unwrap_err(), + Error::StateDb(StateDbError::BlockUnavailable) + ); + // commit block to db and no error return + db.commit(&pending_commit); + assert_eq!(pruning.next_hash().unwrap(), Some(index)); + pruning.prune_one(&mut commit).unwrap(); + db.commit(&commit); } } diff --git a/client/state-db/src/test.rs b/client/state-db/src/test.rs index ad5ce8e874cc7..314ec2902452a 100644 --- a/client/state-db/src/test.rs +++ b/client/state-db/src/test.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,10 +20,16 @@ use crate::{ChangeSet, CommitSet, DBValue, MetaDb, NodeDb}; use sp_core::H256; -use std::collections::HashMap; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; + +#[derive(Default, Debug, Clone)] +pub struct TestDb(Arc>); #[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct TestDb { +struct TestDbInner { pub data: HashMap, pub meta: HashMap, DBValue>, } @@ -32,7 +38,7 @@ impl MetaDb for TestDb { type Error = (); fn get_meta(&self, key: &[u8]) -> Result, ()> { - Ok(self.meta.get(key).cloned()) + Ok(self.0.read().unwrap().meta.get(key).cloned()) } } @@ -41,25 +47,29 @@ impl NodeDb for TestDb { type Key = H256; fn get(&self, key: &H256) -> Result, ()> { - Ok(self.data.get(key).cloned()) + Ok(self.0.read().unwrap().data.get(key).cloned()) } } impl TestDb { pub fn commit(&mut self, commit: &CommitSet) { - self.data.extend(commit.data.inserted.iter().cloned()); - self.meta.extend(commit.meta.inserted.iter().cloned()); + self.0.write().unwrap().data.extend(commit.data.inserted.iter().cloned()); + self.0.write().unwrap().meta.extend(commit.meta.inserted.iter().cloned()); for k in commit.data.deleted.iter() { - self.data.remove(k); + self.0.write().unwrap().data.remove(k); } - self.meta.extend(commit.meta.inserted.iter().cloned()); + self.0.write().unwrap().meta.extend(commit.meta.inserted.iter().cloned()); for k in commit.meta.deleted.iter() { - self.meta.remove(k); + self.0.write().unwrap().meta.remove(k); } } pub fn data_eq(&self, other: &TestDb) -> bool { - self.data == other.data + self.0.read().unwrap().data == other.0.read().unwrap().data + } + + pub fn meta_len(&self) -> usize { + self.0.read().unwrap().meta.len() } } @@ -78,11 +88,11 @@ pub fn make_commit(inserted: &[u64], deleted: &[u64]) -> CommitSet { } pub fn make_db(inserted: &[u64]) -> TestDb { - TestDb { + TestDb(Arc::new(RwLock::new(TestDbInner { data: inserted .iter() .map(|v| (H256::from_low_u64_be(*v), H256::from_low_u64_be(*v).as_bytes().to_vec())) .collect(), meta: Default::default(), - } + }))) } diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index b81fd1fd5c611..a72b4106ba873 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -3,28 +3,24 @@ name = "sc-sync-state-rpc" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "A RPC handler to create sync states for light clients." -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -thiserror = "1.0.21" -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" +codec = { package = "parity-scale-codec", version = "3.0.0" } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.85" +thiserror = "1.0.30" sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus-babe = { version = "0.10.0-dev", path = "../consensus/babe" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../consensus/epochs" } sc-finality-grandpa = { version = "0.10.0-dev", path = "../finality-grandpa" } -sc-rpc-api = { version = "0.10.0-dev", path = "../rpc-api" } -serde_json = "1.0.68" -serde = { version = "1.0.126", features = ["derive"] } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -codec = { package = "parity-scale-codec", version = "2.0.0" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } diff --git a/client/sync-state-rpc/src/lib.rs b/client/sync-state-rpc/src/lib.rs index a1621e3986d76..9540d94c57918 100644 --- a/client/sync-state-rpc/src/lib.rs +++ b/client/sync-state-rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -37,10 +37,15 @@ //! ``` //! //! If the [`LightSyncStateExtension`] is not added as an extension to the chain spec, -//! the [`SyncStateRpcHandler`] will fail at instantiation. +//! the [`SyncState`] will fail at instantiation. #![deny(unused_crate_dependencies)] +use jsonrpsee::{ + core::{Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::{error::CallError, ErrorObject}, +}; use sc_client_api::StorageData; use sp_blockchain::HeaderBackend; use sp_runtime::{ @@ -49,8 +54,6 @@ use sp_runtime::{ }; use std::sync::Arc; -use jsonrpc_derive::rpc; - type SharedAuthoritySet = sc_finality_grandpa::SharedAuthoritySet<::Hash, NumberFor>; type SharedEpochChanges = @@ -76,13 +79,13 @@ pub enum Error { LightSyncStateExtensionNotFound, } -impl From> for jsonrpc_core::Error { +impl From> for JsonRpseeError { fn from(error: Error) -> Self { let message = match error { Error::JsonRpc(s) => s, _ => error.to_string(), }; - jsonrpc_core::Error { message, code: jsonrpc_core::ErrorCode::ServerError(1), data: None } + CallError::Custom(ErrorObject::owned(1, message, None::<()>)).into() } } @@ -101,7 +104,7 @@ fn serialize_encoded( /// chain-spec as an extension. pub type LightSyncStateExtension = Option; -/// Hardcoded infomation that allows light clients to sync quickly. +/// Hardcoded information that allows light clients to sync quickly. #[derive(serde::Serialize, Clone)] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] @@ -121,39 +124,37 @@ pub struct LightSyncState { } /// An api for sync state RPC calls. -#[rpc] -pub trait SyncStateRpcApi { - /// Returns the json-serialized chainspec running the node, with a sync state. - #[rpc(name = "sync_state_genSyncSpec", returns = "jsonrpc_core::Value")] - fn system_gen_sync_spec(&self, raw: bool) -> jsonrpc_core::Result; +#[rpc(client, server)] +pub trait SyncStateApi { + /// Returns the JSON serialized chainspec running the node, with a sync state. + #[method(name = "sync_state_genSyncSpec")] + fn system_gen_sync_spec(&self, raw: bool) -> RpcResult; } -/// The handler for sync state RPC calls. -pub struct SyncStateRpcHandler { +/// An api for sync state RPC calls. +pub struct SyncState { chain_spec: Box, - client: Arc, + client: Arc, shared_authority_set: SharedAuthoritySet, shared_epoch_changes: SharedEpochChanges, - deny_unsafe: sc_rpc_api::DenyUnsafe, } -impl SyncStateRpcHandler +impl SyncState where Block: BlockT, - Backend: HeaderBackend + sc_client_api::AuxStore + 'static, + Client: HeaderBackend + sc_client_api::AuxStore + 'static, { - /// Create a new handler. + /// Create a new sync state RPC helper. pub fn new( chain_spec: Box, - client: Arc, + client: Arc, shared_authority_set: SharedAuthoritySet, shared_epoch_changes: SharedEpochChanges, - deny_unsafe: sc_rpc_api::DenyUnsafe, ) -> Result> { if sc_chain_spec::get_extension::(chain_spec.extensions()) .is_some() { - Ok(Self { chain_spec, client, shared_authority_set, shared_epoch_changes, deny_unsafe }) + Ok(Self { chain_spec, client, shared_authority_set, shared_epoch_changes }) } else { Err(Error::::LightSyncStateExtensionNotFound) } @@ -168,7 +169,7 @@ where let finalized_block_weight = sc_consensus_babe::aux_schema::load_block_weight(&*self.client, finalized_hash)? - .ok_or_else(|| Error::LoadingBlockWeightFailed(finalized_hash))?; + .ok_or(Error::LoadingBlockWeightFailed(finalized_hash))?; Ok(LightSyncState { finalized_block_header: finalized_header, @@ -179,36 +180,24 @@ where } } -impl SyncStateRpcApi for SyncStateRpcHandler +impl SyncStateApiServer for SyncState where Block: BlockT, Backend: HeaderBackend + sc_client_api::AuxStore + 'static, { - fn system_gen_sync_spec(&self, raw: bool) -> jsonrpc_core::Result { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return Err(err.into()) - } - + fn system_gen_sync_spec(&self, raw: bool) -> RpcResult { + let current_sync_state = self.build_sync_state()?; let mut chain_spec = self.chain_spec.cloned_box(); - let sync_state = self.build_sync_state().map_err(map_error::>)?; - let extension = sc_chain_spec::get_extension_mut::( chain_spec.extensions_mut(), ) - .ok_or_else(|| { - Error::::JsonRpc("Could not find `LightSyncState` chain-spec extension!".into()) - })?; - - *extension = - Some(serde_json::to_value(&sync_state).map_err(|err| map_error::(err))?); + .ok_or(Error::::LightSyncStateExtensionNotFound)?; - let json_string = chain_spec.as_json(raw).map_err(map_error::)?; + let val = serde_json::to_value(¤t_sync_state)?; + *extension = Some(val); - serde_json::from_str(&json_string).map_err(|err| map_error::(err)) + let json_str = chain_spec.as_json(raw).map_err(|e| Error::::JsonRpc(e))?; + serde_json::from_str(&json_str).map_err(Into::into) } } - -fn map_error(error: S) -> jsonrpc_core::Error { - Error::::JsonRpc(error.to_string()).into() -} diff --git a/client/sysinfo/Cargo.toml b/client/sysinfo/Cargo.toml new file mode 100644 index 0000000000000..c59611ed1b432 --- /dev/null +++ b/client/sysinfo/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "sc-sysinfo" +version = "6.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "A crate that provides basic hardware and software telemetry information." +documentation = "https://docs.rs/sc-sysinfo" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +futures = "0.3.19" +libc = "0.2" +log = "0.4.17" +rand = "0.7.3" +rand_pcg = "0.2.1" +regex = "1" +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.85" +sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-std = { version = "5.0.0", path = "../../primitives/std" } + +[dev-dependencies] +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } diff --git a/client/sysinfo/README.md b/client/sysinfo/README.md new file mode 100644 index 0000000000000..4a2189c5ed8db --- /dev/null +++ b/client/sysinfo/README.md @@ -0,0 +1,4 @@ +This crate contains the code necessary to gather basic hardware +and software telemetry information about the node on which we're running. + +License: GPL-3.0-or-later WITH Classpath-exception-2.0 diff --git a/client/sysinfo/build.rs b/client/sysinfo/build.rs new file mode 100644 index 0000000000000..6d288107445aa --- /dev/null +++ b/client/sysinfo/build.rs @@ -0,0 +1,31 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +fn main() { + let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR is always set in build scripts; qed"); + let out_dir = std::path::PathBuf::from(out_dir); + let target_os = std::env::var("CARGO_CFG_TARGET_OS") + .expect("CARGO_CFG_TARGET_OS is always set in build scripts; qed"); + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH") + .expect("CARGO_CFG_TARGET_ARCH is always set in build scripts; qed"); + let target_env = std::env::var("CARGO_CFG_TARGET_ENV") + .expect("CARGO_CFG_TARGET_ENV is always set in build scripts; qed"); + std::fs::write(out_dir.join("target_os.txt"), target_os).unwrap(); + std::fs::write(out_dir.join("target_arch.txt"), target_arch).unwrap(); + std::fs::write(out_dir.join("target_env.txt"), target_env).unwrap(); +} diff --git a/client/sysinfo/src/lib.rs b/client/sysinfo/src/lib.rs new file mode 100644 index 0000000000000..cef5a4d210df1 --- /dev/null +++ b/client/sysinfo/src/lib.rs @@ -0,0 +1,163 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! This crate contains the code necessary to gather basic hardware +//! and software telemetry information about the node on which we're running. + +use futures::prelude::*; +use std::time::Duration; + +mod sysinfo; +#[cfg(target_os = "linux")] +mod sysinfo_linux; + +pub use sysinfo::{ + benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes, + benchmark_memory, benchmark_sr25519_verify, gather_hwbench, gather_sysinfo, + serialize_throughput, serialize_throughput_option, Throughput, +}; + +/// The operating system part of the current target triplet. +pub const TARGET_OS: &str = include_str!(concat!(env!("OUT_DIR"), "/target_os.txt")); + +/// The CPU ISA architecture part of the current target triplet. +pub const TARGET_ARCH: &str = include_str!(concat!(env!("OUT_DIR"), "/target_arch.txt")); + +/// The environment part of the current target triplet. +pub const TARGET_ENV: &str = include_str!(concat!(env!("OUT_DIR"), "/target_env.txt")); + +/// Hardware benchmark results for the node. +#[derive(Clone, Debug, serde::Serialize)] +pub struct HwBench { + /// The CPU speed, as measured in how many MB/s it can hash using the BLAKE2b-256 hash. + #[serde(serialize_with = "serialize_throughput")] + pub cpu_hashrate_score: Throughput, + /// Memory bandwidth in MB/s, calculated by measuring the throughput of `memcpy`. + #[serde(serialize_with = "serialize_throughput")] + pub memory_memcpy_score: Throughput, + /// Sequential disk write speed in MB/s. + #[serde( + serialize_with = "serialize_throughput_option", + skip_serializing_if = "Option::is_none" + )] + pub disk_sequential_write_score: Option, + /// Random disk write speed in MB/s. + #[serde( + serialize_with = "serialize_throughput_option", + skip_serializing_if = "Option::is_none" + )] + pub disk_random_write_score: Option, +} + +/// Limit the execution time of a benchmark. +pub enum ExecutionLimit { + /// Limit by the maximal duration. + MaxDuration(Duration), + + /// Limit by the maximal number of iterations. + MaxIterations(usize), + + /// Limit by the maximal duration and maximal number of iterations. + Both { max_iterations: usize, max_duration: Duration }, +} + +impl ExecutionLimit { + /// Creates a new execution limit with the passed seconds as duration limit. + pub fn from_secs_f32(secs: f32) -> Self { + Self::MaxDuration(Duration::from_secs_f32(secs)) + } + + /// Returns the duration limit or `MAX` if none is present. + pub fn max_duration(&self) -> Duration { + match self { + Self::MaxDuration(d) => *d, + Self::Both { max_duration, .. } => *max_duration, + _ => Duration::from_secs(u64::MAX), + } + } + + /// Returns the iterations limit or `MAX` if none is present. + pub fn max_iterations(&self) -> usize { + match self { + Self::MaxIterations(d) => *d, + Self::Both { max_iterations, .. } => *max_iterations, + _ => usize::MAX, + } + } +} + +/// Prints out the system software/hardware information in the logs. +pub fn print_sysinfo(sysinfo: &sc_telemetry::SysInfo) { + log::info!("💻 Operating system: {}", TARGET_OS); + log::info!("💻 CPU architecture: {}", TARGET_ARCH); + if !TARGET_ENV.is_empty() { + log::info!("💻 Target environment: {}", TARGET_ENV); + } + + if let Some(ref cpu) = sysinfo.cpu { + log::info!("💻 CPU: {}", cpu); + } + if let Some(core_count) = sysinfo.core_count { + log::info!("💻 CPU cores: {}", core_count); + } + if let Some(memory) = sysinfo.memory { + log::info!("💻 Memory: {}MB", memory / (1024 * 1024)); + } + if let Some(ref linux_kernel) = sysinfo.linux_kernel { + log::info!("💻 Kernel: {}", linux_kernel); + } + if let Some(ref linux_distro) = sysinfo.linux_distro { + log::info!("💻 Linux distribution: {}", linux_distro); + } + if let Some(is_virtual_machine) = sysinfo.is_virtual_machine { + log::info!("💻 Virtual machine: {}", if is_virtual_machine { "yes" } else { "no" }); + } +} + +/// Prints out the results of the hardware benchmarks in the logs. +pub fn print_hwbench(hwbench: &HwBench) { + log::info!("🏁 CPU score: {}", hwbench.cpu_hashrate_score); + log::info!("🏁 Memory score: {}", hwbench.memory_memcpy_score); + + if let Some(score) = hwbench.disk_sequential_write_score { + log::info!("🏁 Disk score (seq. writes): {}", score); + } + if let Some(score) = hwbench.disk_random_write_score { + log::info!("🏁 Disk score (rand. writes): {}", score); + } +} + +/// Initializes the hardware benchmarks telemetry. +pub fn initialize_hwbench_telemetry( + telemetry_handle: sc_telemetry::TelemetryHandle, + hwbench: HwBench, +) -> impl std::future::Future { + let mut connect_stream = telemetry_handle.on_connect_stream(); + async move { + let payload = serde_json::to_value(&hwbench) + .expect("the `HwBench` can always be serialized into a JSON object; qed"); + let mut payload = match payload { + serde_json::Value::Object(map) => map, + _ => unreachable!("the `HwBench` always serializes into a JSON object; qed"), + }; + payload.insert("msg".into(), "sysinfo.hwbench".into()); + while connect_stream.next().await.is_some() { + telemetry_handle.send_telemetry(sc_telemetry::SUBSTRATE_INFO, payload.clone()); + } + } +} diff --git a/client/sysinfo/src/sysinfo.rs b/client/sysinfo/src/sysinfo.rs new file mode 100644 index 0000000000000..c66a6f6a62aed --- /dev/null +++ b/client/sysinfo/src/sysinfo.rs @@ -0,0 +1,620 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{ExecutionLimit, HwBench}; + +use sc_telemetry::SysInfo; +use sp_core::{sr25519, Pair}; +use sp_io::crypto::sr25519_verify; +use sp_std::{fmt, prelude::*}; + +use rand::{seq::SliceRandom, Rng, RngCore}; +use serde::Serializer; +use std::{ + fs::File, + io::{Seek, SeekFrom, Write}, + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, + time::{Duration, Instant}, +}; + +/// The unit in which the [`Throughput`] (bytes per second) is denoted. +pub enum Unit { + GiBs, + MiBs, + KiBs, +} + +impl fmt::Display for Unit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Unit::GiBs => "GiBs", + Unit::MiBs => "MiBs", + Unit::KiBs => "KiBs", + }) + } +} + +/// Throughput as measured in bytes per second. +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub struct Throughput(f64); + +const KIBIBYTE: f64 = (1 << 10) as f64; +const MEBIBYTE: f64 = (1 << 20) as f64; +const GIBIBYTE: f64 = (1 << 30) as f64; + +impl Throughput { + /// Construct [`Self`] from kibibyte/s. + pub fn from_kibs(kibs: f64) -> Throughput { + Throughput(kibs * KIBIBYTE) + } + + /// Construct [`Self`] from mebibyte/s. + pub fn from_mibs(mibs: f64) -> Throughput { + Throughput(mibs * MEBIBYTE) + } + + /// Construct [`Self`] from gibibyte/s. + pub fn from_gibs(gibs: f64) -> Throughput { + Throughput(gibs * GIBIBYTE) + } + + /// [`Self`] as number of byte/s. + pub fn as_bytes(&self) -> f64 { + self.0 + } + + /// [`Self`] as number of kibibyte/s. + pub fn as_kibs(&self) -> f64 { + self.0 / KIBIBYTE + } + + /// [`Self`] as number of mebibyte/s. + pub fn as_mibs(&self) -> f64 { + self.0 / MEBIBYTE + } + + /// [`Self`] as number of gibibyte/s. + pub fn as_gibs(&self) -> f64 { + self.0 / GIBIBYTE + } + + /// Normalizes [`Self`] to use the largest unit possible. + pub fn normalize(&self) -> (f64, Unit) { + let bs = self.0; + + if bs >= GIBIBYTE { + (self.as_gibs(), Unit::GiBs) + } else if bs >= MEBIBYTE { + (self.as_mibs(), Unit::MiBs) + } else { + (self.as_kibs(), Unit::KiBs) + } + } +} + +impl fmt::Display for Throughput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (value, unit) = self.normalize(); + write!(f, "{:.2?} {}", value, unit) + } +} + +/// Serializes `Throughput` and uses MiBs as the unit. +pub fn serialize_throughput(throughput: &Throughput, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_u64(throughput.as_mibs() as u64) +} + +/// Serializes `Option` and uses MiBs as the unit. +pub fn serialize_throughput_option( + maybe_throughput: &Option, + serializer: S, +) -> Result +where + S: Serializer, +{ + if let Some(throughput) = maybe_throughput { + return serializer.serialize_some(&(throughput.as_mibs() as u64)) + } + serializer.serialize_none() +} + +#[inline(always)] +pub(crate) fn benchmark( + name: &str, + size: usize, + max_iterations: usize, + max_duration: Duration, + mut run: impl FnMut() -> Result<(), E>, +) -> Result { + // Run the benchmark once as a warmup to get the code into the L1 cache. + run()?; + + // Then run it multiple times and average the result. + let timestamp = Instant::now(); + let mut elapsed = Duration::default(); + let mut count = 0; + for _ in 0..max_iterations { + run()?; + + count += 1; + elapsed = timestamp.elapsed(); + + if elapsed >= max_duration { + break + } + } + + let score = Throughput::from_kibs((size * count) as f64 / (elapsed.as_secs_f64() * 1024.0)); + log::trace!( + "Calculated {} of {} in {} iterations in {}ms", + name, + score, + count, + elapsed.as_millis() + ); + Ok(score) +} + +/// Gathers information about node's hardware and software. +pub fn gather_sysinfo() -> SysInfo { + #[allow(unused_mut)] + let mut sysinfo = SysInfo { + cpu: None, + memory: None, + core_count: None, + linux_kernel: None, + linux_distro: None, + is_virtual_machine: None, + }; + + #[cfg(target_os = "linux")] + crate::sysinfo_linux::gather_linux_sysinfo(&mut sysinfo); + + sysinfo +} + +#[inline(never)] +fn clobber_slice(slice: &mut [T]) { + assert!(!slice.is_empty()); + + // Discourage the compiler from optimizing out our benchmarks. + // + // Volatile reads and writes are guaranteed to not be elided nor reordered, + // so we can use them to effectively clobber a piece of memory and prevent + // the compiler from optimizing out our technically unnecessary code. + // + // This is not totally bulletproof in theory, but should work in practice. + // + // SAFETY: We've checked that the slice is not empty, so reading and writing + // its first element is always safe. + unsafe { + let value = std::ptr::read_volatile(slice.as_ptr()); + std::ptr::write_volatile(slice.as_mut_ptr(), value); + } +} + +#[inline(never)] +fn clobber_value(input: &mut T) { + // Look into `clobber_slice` for a comment. + unsafe { + let value = std::ptr::read_volatile(input); + std::ptr::write_volatile(input, value); + } +} + +/// A default [`ExecutionLimit`] that can be used to call [`benchmark_cpu`]. +pub const DEFAULT_CPU_EXECUTION_LIMIT: ExecutionLimit = + ExecutionLimit::Both { max_iterations: 4 * 1024, max_duration: Duration::from_millis(100) }; + +// This benchmarks the CPU speed as measured by calculating BLAKE2b-256 hashes, in bytes per second. +pub fn benchmark_cpu(limit: ExecutionLimit) -> Throughput { + // In general the results of this benchmark are somewhat sensitive to how much + // data we hash at the time. The smaller this is the *less* B/s we can hash, + // the bigger this is the *more* B/s we can hash, up until a certain point + // where we can achieve roughly ~100% of what the hasher can do. If we'd plot + // this on a graph with the number of bytes we want to hash on the X axis + // and the speed in B/s on the Y axis then we'd essentially see it grow + // logarithmically. + // + // In practice however we might not always have enough data to hit the maximum + // possible speed that the hasher can achieve, so the size set here should be + // picked in such a way as to still measure how fast the hasher is at hashing, + // but without hitting its theoretical maximum speed. + const SIZE: usize = 32 * 1024; + + let mut buffer = Vec::new(); + buffer.resize(SIZE, 0x66); + let mut hash = Default::default(); + + let run = || -> Result<(), ()> { + clobber_slice(&mut buffer); + hash = sp_core::hashing::blake2_256(&buffer); + clobber_slice(&mut hash); + + Ok(()) + }; + + benchmark("CPU score", SIZE, limit.max_iterations(), limit.max_duration(), run) + .expect("benchmark cannot fail; qed") +} + +/// A default [`ExecutionLimit`] that can be used to call [`benchmark_memory`]. +pub const DEFAULT_MEMORY_EXECUTION_LIMIT: ExecutionLimit = + ExecutionLimit::Both { max_iterations: 32, max_duration: Duration::from_millis(100) }; + +// This benchmarks the effective `memcpy` memory bandwidth available in bytes per second. +// +// It doesn't technically measure the absolute maximum memory bandwidth available, +// but that's fine, because real code most of the time isn't optimized to take +// advantage of the full memory bandwidth either. +pub fn benchmark_memory(limit: ExecutionLimit) -> Throughput { + // Ideally this should be at least as big as the CPU's L3 cache, + // and it should be big enough so that the `memcpy` takes enough + // time to be actually measurable. + // + // As long as it's big enough increasing it further won't change + // the benchmark's results. + const SIZE: usize = 64 * 1024 * 1024; + + let mut src = Vec::new(); + let mut dst = Vec::new(); + + // Prefault the pages; we want to measure the memory bandwidth, + // not how fast the kernel can supply us with fresh memory pages. + src.resize(SIZE, 0x66); + dst.resize(SIZE, 0x77); + + let run = || -> Result<(), ()> { + clobber_slice(&mut src); + clobber_slice(&mut dst); + + // SAFETY: Both vectors are of the same type and of the same size, + // so copying data between them is safe. + unsafe { + // We use `memcpy` directly here since `copy_from_slice` isn't actually + // guaranteed to be turned into a `memcpy`. + libc::memcpy(dst.as_mut_ptr().cast(), src.as_ptr().cast(), SIZE); + } + + clobber_slice(&mut dst); + clobber_slice(&mut src); + + Ok(()) + }; + + benchmark("memory score", SIZE, limit.max_iterations(), limit.max_duration(), run) + .expect("benchmark cannot fail; qed") +} + +struct TemporaryFile { + fp: Option, + path: PathBuf, +} + +impl Drop for TemporaryFile { + fn drop(&mut self) { + let _ = self.fp.take(); + + // Remove the file. + // + // This has to be done *after* the benchmark, + // otherwise it changes the results as the data + // doesn't actually get properly flushed to the disk, + // since the file's not there anymore. + if let Err(error) = std::fs::remove_file(&self.path) { + log::warn!("Failed to remove the file used for the disk benchmark: {}", error); + } + } +} + +impl Deref for TemporaryFile { + type Target = File; + fn deref(&self) -> &Self::Target { + self.fp.as_ref().expect("`fp` is None only during `drop`") + } +} + +impl DerefMut for TemporaryFile { + fn deref_mut(&mut self) -> &mut Self::Target { + self.fp.as_mut().expect("`fp` is None only during `drop`") + } +} + +fn rng() -> rand_pcg::Pcg64 { + rand_pcg::Pcg64::new(0xcafef00dd15ea5e5, 0xa02bdbf7bb3c0a7ac28fa16a64abf96) +} + +fn random_data(size: usize) -> Vec { + let mut buffer = Vec::new(); + buffer.resize(size, 0); + rng().fill(&mut buffer[..]); + buffer +} + +/// A default [`ExecutionLimit`] that can be used to call [`benchmark_disk_sequential_writes`] +/// and [`benchmark_disk_random_writes`]. +pub const DEFAULT_DISK_EXECUTION_LIMIT: ExecutionLimit = + ExecutionLimit::Both { max_iterations: 32, max_duration: Duration::from_millis(300) }; + +pub fn benchmark_disk_sequential_writes( + limit: ExecutionLimit, + directory: &Path, +) -> Result { + const SIZE: usize = 64 * 1024 * 1024; + + let buffer = random_data(SIZE); + let path = directory.join(".disk_bench_seq_wr.tmp"); + + let fp = + File::create(&path).map_err(|error| format!("failed to create a test file: {}", error))?; + + let mut fp = TemporaryFile { fp: Some(fp), path }; + + fp.sync_all() + .map_err(|error| format!("failed to fsync the test file: {}", error))?; + + let run = || { + // Just dump everything to the disk in one go. + fp.write_all(&buffer) + .map_err(|error| format!("failed to write to the test file: {}", error))?; + + // And then make sure it was actually written to disk. + fp.sync_all() + .map_err(|error| format!("failed to fsync the test file: {}", error))?; + + // Rewind to the beginning for the next iteration of the benchmark. + fp.seek(SeekFrom::Start(0)) + .map_err(|error| format!("failed to seek to the start of the test file: {}", error))?; + + Ok(()) + }; + + benchmark( + "disk sequential write score", + SIZE, + limit.max_iterations(), + limit.max_duration(), + run, + ) +} + +pub fn benchmark_disk_random_writes( + limit: ExecutionLimit, + directory: &Path, +) -> Result { + const SIZE: usize = 64 * 1024 * 1024; + + let buffer = random_data(SIZE); + let path = directory.join(".disk_bench_rand_wr.tmp"); + + let fp = + File::create(&path).map_err(|error| format!("failed to create a test file: {}", error))?; + + let mut fp = TemporaryFile { fp: Some(fp), path }; + + // Since we want to test random writes we need an existing file + // through which we can seek, so here we just populate it with some data. + fp.write_all(&buffer) + .map_err(|error| format!("failed to write to the test file: {}", error))?; + + fp.sync_all() + .map_err(|error| format!("failed to fsync the test file: {}", error))?; + + // Generate a list of random positions at which we'll issue writes. + let mut positions = Vec::with_capacity(SIZE / 4096); + { + let mut position = 0; + while position < SIZE { + positions.push(position); + position += 4096; + } + } + + positions.shuffle(&mut rng()); + + let run = || { + for &position in &positions { + fp.seek(SeekFrom::Start(position as u64)) + .map_err(|error| format!("failed to seek in the test file: {}", error))?; + + // Here we deliberately only write half of the chunk since we don't + // want the OS' disk scheduler to coalesce our writes into one single + // sequential write. + // + // Also the chunk's size is deliberately exactly half of a modern disk's + // sector size to trigger an RMW cycle. + let chunk = &buffer[position..position + 2048]; + fp.write_all(&chunk) + .map_err(|error| format!("failed to write to the test file: {}", error))?; + } + + fp.sync_all() + .map_err(|error| format!("failed to fsync the test file: {}", error))?; + + Ok(()) + }; + + // We only wrote half of the bytes hence `SIZE / 2`. + benchmark( + "disk random write score", + SIZE / 2, + limit.max_iterations(), + limit.max_duration(), + run, + ) +} + +/// Benchmarks the verification speed of sr25519 signatures. +/// +/// Returns the throughput in B/s by convention. +/// The values are rather small (0.4-0.8) so it is advised to convert them into KB/s. +pub fn benchmark_sr25519_verify(limit: ExecutionLimit) -> Throughput { + const INPUT_SIZE: usize = 32; + const ITERATION_SIZE: usize = 2048; + let pair = sr25519::Pair::from_string("//Alice", None).unwrap(); + + let mut rng = rng(); + let mut msgs = Vec::new(); + let mut sigs = Vec::new(); + + for _ in 0..ITERATION_SIZE { + let mut msg = vec![0u8; INPUT_SIZE]; + rng.fill_bytes(&mut msg[..]); + + sigs.push(pair.sign(&msg)); + msgs.push(msg); + } + + let run = || -> Result<(), String> { + for (sig, msg) in sigs.iter().zip(msgs.iter()) { + let mut ok = sr25519_verify(&sig, &msg[..], &pair.public()); + clobber_value(&mut ok); + } + Ok(()) + }; + benchmark( + "sr25519 verification score", + INPUT_SIZE * ITERATION_SIZE, + limit.max_iterations(), + limit.max_duration(), + run, + ) + .expect("sr25519 verification cannot fail; qed") +} + +/// Benchmarks the hardware and returns the results of those benchmarks. +/// +/// Optionally accepts a path to a `scratch_directory` to use to benchmark the disk. +pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { + #[allow(unused_mut)] + let mut hwbench = HwBench { + cpu_hashrate_score: benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT), + memory_memcpy_score: benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT), + disk_sequential_write_score: None, + disk_random_write_score: None, + }; + + if let Some(scratch_directory) = scratch_directory { + hwbench.disk_sequential_write_score = + match benchmark_disk_sequential_writes(DEFAULT_DISK_EXECUTION_LIMIT, scratch_directory) + { + Ok(score) => Some(score), + Err(error) => { + log::warn!("Failed to run the sequential write disk benchmark: {}", error); + None + }, + }; + + hwbench.disk_random_write_score = + match benchmark_disk_random_writes(DEFAULT_DISK_EXECUTION_LIMIT, scratch_directory) { + Ok(score) => Some(score), + Err(error) => { + log::warn!("Failed to run the random write disk benchmark: {}", error); + None + }, + }; + } + + hwbench +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_runtime::assert_eq_error_rate_float; + + #[cfg(target_os = "linux")] + #[test] + fn test_gather_sysinfo_linux() { + let sysinfo = gather_sysinfo(); + assert!(sysinfo.cpu.unwrap().len() > 0); + assert!(sysinfo.core_count.unwrap() > 0); + assert!(sysinfo.memory.unwrap() > 0); + assert_ne!(sysinfo.is_virtual_machine, None); + assert_ne!(sysinfo.linux_kernel, None); + assert_ne!(sysinfo.linux_distro, None); + } + + #[test] + fn test_benchmark_cpu() { + assert!(benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT) > Throughput::from_mibs(0.0)); + } + + #[test] + fn test_benchmark_memory() { + assert!(benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT) > Throughput::from_mibs(0.0)); + } + + #[test] + fn test_benchmark_disk_sequential_writes() { + assert!( + benchmark_disk_sequential_writes(DEFAULT_DISK_EXECUTION_LIMIT, "./".as_ref()).unwrap() > + Throughput::from_mibs(0.0) + ); + } + + #[test] + fn test_benchmark_disk_random_writes() { + assert!( + benchmark_disk_random_writes(DEFAULT_DISK_EXECUTION_LIMIT, "./".as_ref()).unwrap() > + Throughput::from_mibs(0.0) + ); + } + + #[test] + fn test_benchmark_sr25519_verify() { + assert!( + benchmark_sr25519_verify(ExecutionLimit::MaxIterations(1)) > Throughput::from_mibs(0.0) + ); + } + + /// Test the [`Throughput`]. + #[test] + fn throughput_works() { + /// Float precision. + const EPS: f64 = 0.1; + let gib = Throughput::from_gibs(14.324); + + assert_eq_error_rate_float!(14.324, gib.as_gibs(), EPS); + assert_eq_error_rate_float!(14667.776, gib.as_mibs(), EPS); + assert_eq_error_rate_float!(14667.776 * 1024.0, gib.as_kibs(), EPS); + assert_eq!("14.32 GiBs", gib.to_string()); + + let mib = Throughput::from_mibs(1029.0); + assert_eq!("1.00 GiBs", mib.to_string()); + } + + /// Test the [`HwBench`] serialization. + #[test] + fn hwbench_serialize_works() { + let hwbench = HwBench { + cpu_hashrate_score: Throughput::from_gibs(1.32), + memory_memcpy_score: Throughput::from_kibs(9342.432), + disk_sequential_write_score: Some(Throughput::from_kibs(4332.12)), + disk_random_write_score: None, + }; + + let serialized = serde_json::to_string(&hwbench).unwrap(); + // Throughput from all of the benchmarks should be converted to MiBs. + assert_eq!(serialized, "{\"cpu_hashrate_score\":1351,\"memory_memcpy_score\":9,\"disk_sequential_write_score\":4}"); + } +} diff --git a/client/sysinfo/src/sysinfo_linux.rs b/client/sysinfo/src/sysinfo_linux.rs new file mode 100644 index 0000000000000..41ab6014cbef0 --- /dev/null +++ b/client/sysinfo/src/sysinfo_linux.rs @@ -0,0 +1,101 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use regex::Regex; +use sc_telemetry::SysInfo; +use std::collections::HashSet; + +fn read_file(path: &str) -> Option { + match std::fs::read_to_string(path) { + Ok(data) => Some(data), + Err(error) => { + log::warn!("Failed to read '{}': {}", path, error); + None + }, + } +} + +fn extract(data: &str, regex: &str) -> Option +where + T: std::str::FromStr, +{ + Regex::new(regex) + .expect("regex is correct; qed") + .captures(&data)? + .get(1)? + .as_str() + .parse() + .ok() +} + +const LINUX_REGEX_CPU: &str = r#"(?m)^model name\s*:\s*([^\n]+)"#; +const LINUX_REGEX_PHYSICAL_ID: &str = r#"(?m)^physical id\s*:\s*(\d+)"#; +const LINUX_REGEX_CORE_ID: &str = r#"(?m)^core id\s*:\s*(\d+)"#; +const LINUX_REGEX_HYPERVISOR: &str = r#"(?m)^flags\s*:.+?\bhypervisor\b"#; +const LINUX_REGEX_MEMORY: &str = r#"(?m)^MemTotal:\s*(\d+) kB"#; +const LINUX_REGEX_DISTRO: &str = r#"(?m)^PRETTY_NAME\s*=\s*"?(.+?)"?$"#; + +pub fn gather_linux_sysinfo(sysinfo: &mut SysInfo) { + if let Some(data) = read_file("/proc/cpuinfo") { + sysinfo.cpu = extract(&data, LINUX_REGEX_CPU); + sysinfo.is_virtual_machine = + Some(Regex::new(LINUX_REGEX_HYPERVISOR).unwrap().is_match(&data)); + + // The /proc/cpuinfo returns a list of all of the hardware threads. + // + // Here we extract all of the unique {CPU ID, core ID} pairs to get + // the total number of cores. + let mut set: HashSet<(u32, u32)> = HashSet::new(); + for chunk in data.split("\n\n") { + let pid = extract(chunk, LINUX_REGEX_PHYSICAL_ID); + let cid = extract(chunk, LINUX_REGEX_CORE_ID); + if let (Some(pid), Some(cid)) = (pid, cid) { + set.insert((pid, cid)); + } + } + + if !set.is_empty() { + sysinfo.core_count = Some(set.len() as u32); + } + } + + if let Some(data) = read_file("/proc/meminfo") { + sysinfo.memory = extract(&data, LINUX_REGEX_MEMORY).map(|memory: u64| memory * 1024); + } + + if let Some(data) = read_file("/etc/os-release") { + sysinfo.linux_distro = extract(&data, LINUX_REGEX_DISTRO); + } + + // NOTE: We don't use the `nix` crate to call this since it doesn't + // currently check for errors. + unsafe { + // SAFETY: The `utsname` is full of byte arrays, so this is safe. + let mut uname: libc::utsname = std::mem::zeroed(); + if libc::uname(&mut uname) < 0 { + log::warn!("uname failed: {}", std::io::Error::last_os_error()); + } else { + let length = + uname.release.iter().position(|&byte| byte == 0).unwrap_or(uname.release.len()); + let release = std::slice::from_raw_parts(uname.release.as_ptr().cast(), length); + if let Ok(release) = std::str::from_utf8(release) { + sysinfo.linux_kernel = Some(release.into()); + } + } + } +} diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index f115017f09701..0a0b9284efa24 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -3,9 +3,9 @@ name = "sc-telemetry" version = "4.0.0-dev" authors = ["Parity Technologies "] description = "Telemetry utils" -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-telemetry" readme = "README.md" @@ -13,16 +13,15 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -parking_lot = "0.11.1" -futures = "0.3.9" -wasm-timer = "0.2.5" -libp2p = { version = "0.39.1", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } -log = "0.4.8" -pin-project = "1.0.4" -rand = "0.7.2" -serde = { version = "1.0.126", features = ["derive"] } -serde_json = "1.0.68" chrono = "0.4.19" -thiserror = "1.0.21" +futures = "0.3.21" +libp2p = { version = "0.49.0", default-features = false, features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"] } +log = "0.4.17" +parking_lot = "0.12.1" +pin-project = "1.0.12" +rand = "0.7.2" +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.85" +thiserror = "1.0.30" +wasm-timer = "0.2.5" diff --git a/client/telemetry/src/endpoints.rs b/client/telemetry/src/endpoints.rs index 62e6180311980..fba3822a90676 100644 --- a/client/telemetry/src/endpoints.rs +++ b/client/telemetry/src/endpoints.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/telemetry/src/error.rs b/client/telemetry/src/error.rs index 90a8018f4e1d3..4d9cdc05c51b9 100644 --- a/client/telemetry/src/error.rs +++ b/client/telemetry/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/telemetry/src/lib.rs b/client/telemetry/src/lib.rs index 9fb86f57d8392..503a326f76c2b 100644 --- a/client/telemetry/src/lib.rs +++ b/client/telemetry/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -42,7 +42,10 @@ use log::{error, warn}; use parking_lot::Mutex; use serde::Serialize; use std::{ - collections::HashMap, + collections::{ + hash_map::Entry::{Occupied, Vacant}, + HashMap, + }, sync::{atomic, Arc}, }; @@ -101,6 +104,38 @@ pub struct ConnectionMessage { pub startup_time: String, /// Node's network ID. pub network_id: String, + + /// Node's OS. + pub target_os: String, + + /// Node's ISA. + pub target_arch: String, + + /// Node's target platform ABI or libc. + pub target_env: String, + + /// Node's software and hardware information. + pub sysinfo: Option, +} + +/// Hardware and software information for the node. +/// +/// Gathering most of this information is highly OS-specific, +/// so most of the fields here are optional. +#[derive(Debug, Serialize)] +pub struct SysInfo { + /// The exact CPU model. + pub cpu: Option, + /// The total amount of memory, in bytes. + pub memory: Option, + /// The number of physical CPU cores. + pub core_count: Option, + /// The Linux kernel version. + pub linux_kernel: Option, + /// The exact Linux distribution used. + pub linux_distro: Option, + /// Whether the node's running under a virtual machine. + pub is_virtual_machine: Option, } /// Telemetry worker. @@ -115,7 +150,6 @@ pub struct TelemetryWorker { register_receiver: mpsc::UnboundedReceiver, register_sender: mpsc::UnboundedSender, id_counter: Arc, - transport: WsTrans, } impl TelemetryWorker { @@ -123,7 +157,11 @@ impl TelemetryWorker { /// /// Only one is needed per process. pub fn new(buffer_size: usize) -> Result { - let transport = initialize_transport()?; + // Let's try to initialize a transport to get an early return. + // Later transport will be initialized multiple times in + // `::process_register`, so it's a convenient way to get an + // error as early as possible. + let _transport = initialize_transport()?; let (message_sender, message_receiver) = mpsc::channel(buffer_size); let (register_sender, register_receiver) = mpsc::unbounded(); @@ -133,7 +171,6 @@ impl TelemetryWorker { register_receiver, register_sender, id_counter: Arc::new(atomic::AtomicU64::new(1)), - transport, }) } @@ -168,7 +205,6 @@ impl TelemetryWorker { &mut node_pool, &mut node_map, &mut pending_connection_notifications, - self.transport.clone(), ).await, } } @@ -179,7 +215,6 @@ impl TelemetryWorker { node_pool: &mut HashMap>, node_map: &mut HashMap>, pending_connection_notifications: &mut Vec<(Multiaddr, ConnectionNotifierSender)>, - transport: WsTrans, ) { let input = input.expect("the stream is never closed; qed"); @@ -214,11 +249,26 @@ impl TelemetryWorker { "Initializing telemetry for: {:?}", addr, ); - node_map.entry(id.clone()).or_default().push((verbosity, addr.clone())); - - let node = node_pool.entry(addr.clone()).or_insert_with(|| { - Node::new(transport.clone(), addr.clone(), Vec::new(), Vec::new()) - }); + node_map.entry(id).or_default().push((verbosity, addr.clone())); + + let node = match node_pool.entry(addr.clone()) { + Occupied(entry) => entry.into_mut(), + Vacant(entry) => { + let transport = initialize_transport(); + let transport = match transport { + Ok(t) => t, + Err(err) => { + log::error!( + target: "telemetry", + "Could not initialise transport: {}", + err, + ); + continue + }, + }; + entry.insert(Node::new(transport, addr.clone(), Vec::new(), Vec::new())) + }, + }; node.connection_messages.extend(connection_message.clone()); @@ -256,7 +306,7 @@ impl TelemetryWorker { ) { let (id, verbosity, payload) = input.expect("the stream is never closed; qed"); - let ts = chrono::Local::now().to_rfc3339().to_string(); + let ts = chrono::Local::now().to_rfc3339(); let mut message = serde_json::Map::new(); message.insert("id".into(), id.into()); message.insert("ts".into(), ts.into()); @@ -286,7 +336,7 @@ impl TelemetryWorker { continue } - if let Some(node) = node_pool.get_mut(&addr) { + if let Some(node) = node_pool.get_mut(addr) { let _ = node.send(message.clone()).await; } else { log::debug!( @@ -354,7 +404,7 @@ impl Telemetry { /// The `connection_message` argument is a JSON object that is sent every time the connection /// (re-)establishes. pub fn start_telemetry(&mut self, connection_message: ConnectionMessage) -> Result<()> { - let endpoints = self.endpoints.take().ok_or_else(|| Error::TelemetryAlreadyInitialized)?; + let endpoints = self.endpoints.take().ok_or(Error::TelemetryAlreadyInitialized)?; self.register_sender .unbounded_send(Register::Telemetry { id: self.id, endpoints, connection_message }) @@ -440,7 +490,7 @@ enum Register { /// Report a telemetry. /// -/// Translates to [`tracing::info`], but contains an additional verbosity parameter which the log +/// Translates to `tracing::info`, but contains an additional verbosity parameter which the log /// record is tagged with. Additionally the verbosity parameter is added to the record as a /// key-value pair. /// diff --git a/client/telemetry/src/node.rs b/client/telemetry/src/node.rs index 4d845c328fe89..0d71a363a1b26 100644 --- a/client/telemetry/src/node.rs +++ b/client/telemetry/src/node.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -110,7 +110,6 @@ impl Node { impl Node where - TTrans: Clone + Unpin, TTrans::Dial: Unpin, TTrans::Output: Sink, Error = TSinkErr> + Stream, TSinkErr>> + Unpin, @@ -137,7 +136,7 @@ pub(crate) enum Infallible {} impl Sink for Node where - TTrans: Clone + Unpin, + TTrans: Unpin, TTrans::Dial: Unpin, TTrans::Output: Sink, Error = TSinkErr> + Stream, TSinkErr>> + Unpin, @@ -179,8 +178,20 @@ where Poll::Ready(Ok(sink)) => { log::debug!(target: "telemetry", "✅ Connected to {}", self.addr); - for sender in self.telemetry_connection_notifier.iter_mut() { - let _ = sender.send(()); + { + let mut index = 0; + while index < self.telemetry_connection_notifier.len() { + let sender = &mut self.telemetry_connection_notifier[index]; + if let Err(error) = sender.try_send(()) { + if !error.is_disconnected() { + log::debug!(target: "telemetry", "Failed to send a telemetry connection notification: {}", error); + } else { + self.telemetry_connection_notifier.swap_remove(index); + continue + } + } + index += 1; + } } let buf = self @@ -216,18 +227,21 @@ where socket = NodeSocket::wait_reconnect(); }, }, - NodeSocket::ReconnectNow => match self.transport.clone().dial(self.addr.clone()) { - Ok(d) => { - log::trace!(target: "telemetry", "Re-dialing {}", self.addr); - socket = NodeSocket::Dialing(d); - }, - Err(err) => { - log::warn!(target: "telemetry", "❌ Error while re-dialing {}: {:?}", self.addr, err); - socket = NodeSocket::wait_reconnect(); - }, + NodeSocket::ReconnectNow => { + let addr = self.addr.clone(); + match self.transport.dial(addr) { + Ok(d) => { + log::trace!(target: "telemetry", "Re-dialing {}", self.addr); + socket = NodeSocket::Dialing(d); + }, + Err(err) => { + log::warn!(target: "telemetry", "❌ Error while re-dialing {}: {:?}", self.addr, err); + socket = NodeSocket::wait_reconnect(); + }, + } }, NodeSocket::WaitingReconnect(mut s) => { - if let Poll::Ready(_) = Future::poll(Pin::new(&mut s), cx) { + if Future::poll(Pin::new(&mut s), cx).is_ready() { socket = NodeSocket::ReconnectNow; } else { break NodeSocket::WaitingReconnect(s) diff --git a/client/telemetry/src/transport.rs b/client/telemetry/src/transport.rs index 04ec79ebf5645..d8bd138c5af25 100644 --- a/client/telemetry/src/transport.rs +++ b/client/telemetry/src/transport.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,7 +17,6 @@ // along with this program. If not, see . use futures::{ - executor::block_on, prelude::*, ready, task::{Context, Poll}, @@ -31,15 +30,21 @@ const CONNECT_TIMEOUT: Duration = Duration::from_secs(20); pub(crate) fn initialize_transport() -> Result { let transport = { - let inner = block_on(libp2p::dns::DnsConfig::system(libp2p::tcp::TcpConfig::new()))?; + let tcp_transport = libp2p::tcp::TokioTcpTransport::new(libp2p::tcp::GenTcpConfig::new()); + let inner = libp2p::dns::TokioDnsConfig::system(tcp_transport)?; libp2p::websocket::framed::WsConfig::new(inner).and_then(|connec, _| { let connec = connec .with(|item| { let item = libp2p::websocket::framed::OutgoingData::Binary(item); future::ready(Ok::<_, io::Error>(item)) }) - .try_filter(|item| future::ready(item.is_data())) - .map_ok(|data| data.into_bytes()); + .try_filter_map(|item| async move { + if let libp2p::websocket::framed::Incoming::Data(data) = item { + Ok(Some(data.into_bytes())) + } else { + Ok(None) + } + }); future::ready(Ok::<_, io::Error>(connec)) }) }; diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index 3e314a82aa583..be6237a344f52 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -3,8 +3,8 @@ name = "sc-tracing" version = "4.0.0-dev" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" authors = ["Parity Technologies "] -edition = "2018" -homepage = "https://substrate.dev" +edition = "2021" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Instrumentation implementation for substrate." readme = "README.md" @@ -15,23 +15,32 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] ansi_term = "0.12.1" atty = "0.2.13" +chrono = "0.4.19" lazy_static = "1.4.0" -log = { version = "0.4.8" } -once_cell = "1.4.1" -parking_lot = "0.11.1" -regex = "1.4.2" +libc = "0.2.121" +log = { version = "0.4.17" } +once_cell = "1.8.0" +parking_lot = "0.12.1" +regex = "1.6.0" rustc-hash = "1.1.0" -serde = "1.0.126" -thiserror = "1.0.21" -tracing = "0.1.25" -tracing-log = "0.1.2" -tracing-subscriber = "0.2.19" -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } -sp-rpc = { version = "4.0.0-dev", path = "../../primitives/rpc" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +serde = "1.0.136" +thiserror = "1.0.30" +tracing = "0.1.29" +tracing-log = "0.1.3" +tracing-subscriber = { version = "0.2.25", features = ["parking_lot"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-tracing-proc-macro = { version = "4.0.0-dev", path = "./proc-macro" } sc-rpc-server = { version = "4.0.0-dev", path = "../rpc-servers" } +sc-tracing-proc-macro = { version = "4.0.0-dev", path = "./proc-macro" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } + +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "bench" +harness = false diff --git a/client/tracing/benches/bench.rs b/client/tracing/benches/bench.rs new file mode 100644 index 0000000000000..a939a26797396 --- /dev/null +++ b/client/tracing/benches/bench.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 criterion::{criterion_group, criterion_main, Criterion}; +use tracing_subscriber::fmt::time::{ChronoLocal, FormatTime}; + +fn bench_fast_local_time(c: &mut Criterion) { + c.bench_function("fast_local_time", |b| { + let mut buffer = String::new(); + let t = sc_tracing::logging::FastLocalTime { with_fractional: true }; + b.iter(|| { + buffer.clear(); + t.format_time(&mut buffer).unwrap(); + }) + }); +} + +// This is here just as a point of comparison. +fn bench_chrono_local(c: &mut Criterion) { + c.bench_function("chrono_local", |b| { + let mut buffer = String::new(); + let t = ChronoLocal::with_format("%Y-%m-%d %H:%M:%S%.3f".to_string()); + b.iter(|| { + buffer.clear(); + t.format_time(&mut buffer).unwrap(); + }) + }); +} + +criterion_group! { + name = benches; + config = Criterion::default(); + targets = bench_fast_local_time, bench_chrono_local +} +criterion_main!(benches); diff --git a/client/tracing/proc-macro/Cargo.toml b/client/tracing/proc-macro/Cargo.toml index 002370b515f28..031f1fe49d970 100644 --- a/client/tracing/proc-macro/Cargo.toml +++ b/client/tracing/proc-macro/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-tracing-proc-macro" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Helper macros for Substrate's client CLI" @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro-crate = "1.0.0" -proc-macro2 = "1.0.29" -quote = { version = "1.0.3", features = ["proc-macro"] } -syn = { version = "1.0.58", features = ["proc-macro", "full", "extra-traits", "parsing"] } +proc-macro-crate = "1.1.3" +proc-macro2 = "1.0.37" +quote = { version = "1.0.10", features = ["proc-macro"] } +syn = { version = "1.0.98", features = ["proc-macro", "full", "extra-traits", "parsing"] } diff --git a/client/tracing/proc-macro/src/lib.rs b/client/tracing/proc-macro/src/lib.rs index e9a4f58705b41..ba757619fb5a0 100644 --- a/client/tracing/proc-macro/src/lib.rs +++ b/client/tracing/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -56,7 +56,7 @@ use syn::{Error, Expr, Ident, ItemFn}; /// 2020-10-16 08:03:14 ✌️ version 2.0.0-47f7d3f2e-x86_64-linux-gnu /// 2020-10-16 08:03:14 ❤️ by Anonymous, 2017-2020 /// 2020-10-16 08:03:14 📋 Chain specification: Local Testnet -/// 2020-10-16 08:03:14 🏷 Node name: nice-glove-1401 +/// 2020-10-16 08:03:14 🏷 Node name: nice-glove-1401 /// 2020-10-16 08:03:14 👤 Role: LIGHT /// 2020-10-16 08:03:14 💾 Database: RocksDb at /tmp/substrate95w2Dk/chains/local_testnet/db /// 2020-10-16 08:03:14 ⛓ Native runtime: node-template-1 (node-template-1.tx1.au1) @@ -64,7 +64,7 @@ use syn::{Error, Expr, Ident, ItemFn}; /// 2020-10-16 08:03:14 [light] Loading GRANDPA authorities from genesis on what appears to be first startup. /// 2020-10-16 08:03:15 [light] ⏱ Loaded block-time = 6000 milliseconds from genesis on first-launch /// 2020-10-16 08:03:15 [light] Using default protocol ID "sup" because none is configured in the chain specs -/// 2020-10-16 08:03:15 [light] 🏷 Local node identity is: 12D3KooWHX4rkWT6a6N55Km7ZnvenGdShSKPkzJ3yj9DU5nqDtWR +/// 2020-10-16 08:03:15 [light] 🏷 Local node identity is: 12D3KooWHX4rkWT6a6N55Km7ZnvenGdShSKPkzJ3yj9DU5nqDtWR /// 2020-10-16 08:03:15 [light] 📦 Highest known block at #0 /// 2020-10-16 08:03:15 [light] 〽️ Prometheus server started at 127.0.0.1:9615 /// 2020-10-16 08:03:15 [light] Listening for new connections on 127.0.0.1:9944. @@ -90,7 +90,7 @@ use syn::{Error, Expr, Ident, ItemFn}; /// 2020-10-16 08:12:57 ✌️ version 2.0.0-efb9b822a-x86_64-linux-gnu /// 2020-10-16 08:12:57 ❤️ by Anonymous, 2017-2020 /// 2020-10-16 08:12:57 📋 Chain specification: Local Testnet -/// 2020-10-16 08:12:57 🏷 Node name: open-harbor-1619 +/// 2020-10-16 08:12:57 🏷 Node name: open-harbor-1619 /// 2020-10-16 08:12:57 👤 Role: LIGHT /// 2020-10-16 08:12:57 💾 Database: RocksDb at /tmp/substrate9T9Mtb/chains/local_testnet/db /// 2020-10-16 08:12:57 ⛓ Native runtime: node-template-1 (node-template-1.tx1.au1) @@ -98,7 +98,7 @@ use syn::{Error, Expr, Ident, ItemFn}; /// 2020-10-16 08:12:58 [open-harbor-1619] Loading GRANDPA authorities from genesis on what appears to be first startup. /// 2020-10-16 08:12:58 [open-harbor-1619] ⏱ Loaded block-time = 6000 milliseconds from genesis on first-launch /// 2020-10-16 08:12:58 [open-harbor-1619] Using default protocol ID "sup" because none is configured in the chain specs -/// 2020-10-16 08:12:58 [open-harbor-1619] 🏷 Local node identity is: 12D3KooWRzmYC8QTK1Pm8Cfvid3skTS4Hn54jc4AUtje8Rqbfgtp +/// 2020-10-16 08:12:58 [open-harbor-1619] 🏷 Local node identity is: 12D3KooWRzmYC8QTK1Pm8Cfvid3skTS4Hn54jc4AUtje8Rqbfgtp /// 2020-10-16 08:12:58 [open-harbor-1619] 📦 Highest known block at #0 /// 2020-10-16 08:12:58 [open-harbor-1619] 〽️ Prometheus server started at 127.0.0.1:9615 /// 2020-10-16 08:12:58 [open-harbor-1619] Listening for new connections on 127.0.0.1:9944. @@ -119,7 +119,7 @@ pub fn prefix_logs_with(arg: TokenStream, item: TokenStream) -> TokenStream { let name = syn::parse_macro_input!(arg as Expr); let crate_name = match crate_name("sc-tracing") { - Ok(FoundCrate::Itself) => Ident::from(Ident::new("sc_tracing", Span::call_site())), + Ok(FoundCrate::Itself) => Ident::new("sc_tracing", Span::call_site()), Ok(FoundCrate::Name(crate_name)) => Ident::new(&crate_name, Span::call_site()), Err(e) => return Error::new(Span::call_site(), e).to_compile_error().into(), }; diff --git a/client/tracing/src/block/mod.rs b/client/tracing/src/block/mod.rs index 8280d4613a189..63fd1de374cba 100644 --- a/client/tracing/src/block/mod.rs +++ b/client/tracing/src/block/mod.rs @@ -53,7 +53,7 @@ const AVG_SPAN: usize = 100 * 8; // are used for the RPC Id this may need to be adjusted. Note: The base payload // does not include the RPC result. // -// The estimate is based on the JSONRPC response message which has the following format: +// The estimate is based on the JSON-RPC response message which has the following format: // `{"jsonrpc":"2.0","result":[],"id":18446744073709551615}`. // // We care about the total size of the payload because jsonrpc-server will simply ignore @@ -108,7 +108,7 @@ impl BlockSubscriber { impl Subscriber for BlockSubscriber { fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool { - if !metadata.is_span() && !metadata.fields().field(REQUIRED_EVENT_FIELD).is_some() { + if !metadata.is_span() && metadata.fields().field(REQUIRED_EVENT_FIELD).is_none() { return false } for (target, level) in &self.targets { @@ -221,12 +221,12 @@ where let mut header = self .client .header(id) - .map_err(|e| Error::InvalidBlockId(e))? + .map_err(Error::InvalidBlockId)? .ok_or_else(|| Error::MissingBlockComponent("Header not found".to_string()))?; let extrinsics = self .client - .block_body(&id) - .map_err(|e| Error::InvalidBlockId(e))? + .block_body(self.block) + .map_err(Error::InvalidBlockId)? .ok_or_else(|| Error::MissingBlockComponent("Extrinsics not found".to_string()))?; tracing::debug!(target: "state_tracing", "Found {} extrinsics", extrinsics.len()); let parent_hash = *header.parent_hash(); @@ -252,22 +252,24 @@ where let _enter = span.enter(); self.client.runtime_api().execute_block(&parent_id, block) }) { - return Err(Error::Dispatch( - format!("Failed to collect traces and execute block: {:?}", e).to_string(), - )) + return Err(Error::Dispatch(format!( + "Failed to collect traces and execute block: {}", + e + ))) } } - let block_subscriber = - dispatch.downcast_ref::().ok_or(Error::Dispatch( + let block_subscriber = dispatch.downcast_ref::().ok_or_else(|| { + Error::Dispatch( "Cannot downcast Dispatch to BlockSubscriber after tracing block".to_string(), - ))?; + ) + })?; let spans: Vec<_> = block_subscriber .spans .lock() .drain() // Patch wasm identifiers - .filter_map(|(_, s)| patch_and_filter(SpanDatum::from(s), targets)) + .filter_map(|(_, s)| patch_and_filter(s, targets)) .collect(); let events: Vec<_> = block_subscriber .events @@ -315,7 +317,7 @@ fn event_values_filter(event: &TraceEvent, filter_kind: &str, values: &str) -> b .values .string_values .get(filter_kind) - .and_then(|value| Some(check_target(values, value, &event.level))) + .map(|value| check_target(values, value, &event.level)) .unwrap_or(false) } diff --git a/client/tracing/src/lib.rs b/client/tracing/src/lib.rs index bf6e3d780c6ed..acbde8b75da25 100644 --- a/client/tracing/src/lib.rs +++ b/client/tracing/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -58,7 +58,7 @@ const ZERO_DURATION: Duration = Duration::from_nanos(0); /// Responsible for assigning ids to new spans, which are not re-used. pub struct ProfilingLayer { targets: Vec<(String, Level)>, - trace_handler: Box, + trace_handlers: Vec>, } /// Used to configure how to receive the metrics @@ -76,14 +76,14 @@ impl Default for TracingReceiver { /// A handler for tracing `SpanDatum` pub trait TraceHandler: Send + Sync { - /// Process a `SpanDatum` - fn handle_span(&self, span: SpanDatum); - /// Process a `TraceEvent` - fn handle_event(&self, event: TraceEvent); + /// Process a `SpanDatum`. + fn handle_span(&self, span: &SpanDatum); + /// Process a `TraceEvent`. + fn handle_event(&self, event: &TraceEvent); } /// Represents a tracing event, complete with values -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TraceEvent { /// Name of the event. pub name: String, @@ -98,7 +98,7 @@ pub struct TraceEvent { } /// Represents a single instance of a tracing span -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SpanDatum { /// id for this span pub id: Id, @@ -166,8 +166,7 @@ impl Visit for Values { } fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { - self.string_values - .insert(field.name().to_string(), format!("{:?}", value).to_owned()); + self.string_values.insert(field.name().to_string(), format!("{:?}", value)); } } @@ -213,6 +212,15 @@ impl fmt::Display for Values { } } +/// Trace handler event types. +#[derive(Debug)] +pub enum TraceHandlerEvents { + /// An event. + Event(TraceEvent), + /// A span. + Span(SpanDatum), +} + impl ProfilingLayer { /// Takes a `TracingReceiver` and a comma separated list of targets, /// either with a level: "pallet=trace,frame=debug" @@ -230,8 +238,13 @@ impl ProfilingLayer { /// or without: "pallet" in which case the level defaults to `trace`. /// wasm_tracing indicates whether to enable wasm traces pub fn new_with_handler(trace_handler: Box, targets: &str) -> Self { - let targets: Vec<_> = targets.split(',').map(|s| parse_target(s)).collect(); - Self { targets, trace_handler } + let targets: Vec<_> = targets.split(',').map(parse_target).collect(); + Self { targets, trace_handlers: vec![trace_handler] } + } + + /// Attach additional handlers to allow handling of custom events/spans. + pub fn add_handler(&mut self, trace_handler: Box) { + self.trace_handlers.push(trace_handler); } fn check_target(&self, target: &str, level: &Level) -> bool { @@ -242,6 +255,18 @@ impl ProfilingLayer { } false } + + /// Sequentially dispatch a trace event to all handlers. + fn dispatch_event(&self, event: TraceHandlerEvents) { + match &event { + TraceHandlerEvents::Span(span_datum) => { + self.trace_handlers.iter().for_each(|handler| handler.handle_span(span_datum)); + }, + TraceHandlerEvents::Event(event) => { + self.trace_handlers.iter().for_each(|handler| handler.handle_event(event)); + }, + } + } } // Default to TRACE if no level given or unable to parse Level @@ -320,7 +345,7 @@ where values, parent_id, }; - self.trace_handler.handle_event(trace_event); + self.dispatch_event(TraceHandlerEvents::Event(trace_event)); } fn on_enter(&self, span: &Id, ctx: Context) { @@ -348,10 +373,10 @@ where span_datum.target = t; } if self.check_target(&span_datum.target, &span_datum.level) { - self.trace_handler.handle_span(span_datum); + self.dispatch_event(TraceHandlerEvents::Span(span_datum)); } } else { - self.trace_handler.handle_span(span_datum); + self.dispatch_event(TraceHandlerEvents::Span(span_datum)); } } } @@ -374,7 +399,7 @@ fn log_level(level: Level) -> log::Level { } impl TraceHandler for LogTraceHandler { - fn handle_span(&self, span_datum: SpanDatum) { + fn handle_span(&self, span_datum: &SpanDatum) { if span_datum.values.is_empty() { log::log!( log_level(span_datum.level), @@ -383,7 +408,7 @@ impl TraceHandler for LogTraceHandler { span_datum.name, span_datum.overall_time.as_nanos(), span_datum.id.into_u64(), - span_datum.parent_id.map(|s| s.into_u64()), + span_datum.parent_id.as_ref().map(|s| s.into_u64()), ); } else { log::log!( @@ -393,18 +418,18 @@ impl TraceHandler for LogTraceHandler { span_datum.name, span_datum.overall_time.as_nanos(), span_datum.id.into_u64(), - span_datum.parent_id.map(|s| s.into_u64()), + span_datum.parent_id.as_ref().map(|s| s.into_u64()), span_datum.values, ); } } - fn handle_event(&self, event: TraceEvent) { + fn handle_event(&self, event: &TraceEvent) { log::log!( log_level(event.level), "{}, parent_id: {:?}, {}", event.target, - event.parent_id.map(|s| s.into_u64()), + event.parent_id.as_ref().map(|s| s.into_u64()), event.values, ); } @@ -438,7 +463,10 @@ impl From for sp_rpc::tracing::Span { mod tests { use super::*; use parking_lot::Mutex; - use std::sync::Arc; + use std::sync::{ + mpsc::{Receiver, Sender}, + Arc, + }; use tracing_subscriber::layer::SubscriberExt; struct TestTraceHandler { @@ -447,12 +475,12 @@ mod tests { } impl TraceHandler for TestTraceHandler { - fn handle_span(&self, sd: SpanDatum) { - self.spans.lock().push(sd); + fn handle_span(&self, sd: &SpanDatum) { + self.spans.lock().push(sd.clone()); } - fn handle_event(&self, event: TraceEvent) { - self.events.lock().push(event); + fn handle_event(&self, event: &TraceEvent) { + self.events.lock().push(event.clone()); } } @@ -591,17 +619,13 @@ mod tests { let span1 = tracing::info_span!(target: "test_target", "test_span1"); let _guard1 = span1.enter(); - let (tx, rx) = mpsc::channel(); + let (tx, rx): (Sender, Receiver) = mpsc::channel(); let handle = thread::spawn(move || { let span2 = tracing::info_span!(target: "test_target", "test_span2"); let _guard2 = span2.enter(); // emit event tracing::event!(target: "test_target", tracing::Level::INFO, "test_event1"); - for msg in rx.recv() { - if msg == false { - break - } - } + let _ = rx.recv(); // guard2 and span2 dropped / exited }); diff --git a/client/tracing/src/logging/directives.rs b/client/tracing/src/logging/directives.rs index 5aaeb4d17e7d3..b5fb373674ace 100644 --- a/client/tracing/src/logging/directives.rs +++ b/client/tracing/src/logging/directives.rs @@ -17,8 +17,7 @@ use once_cell::sync::OnceCell; use parking_lot::Mutex; use tracing_subscriber::{ - filter::Directive, fmt as tracing_fmt, fmt::time::ChronoLocal, layer, reload::Handle, - EnvFilter, Registry, + filter::Directive, fmt as tracing_fmt, layer, reload::Handle, EnvFilter, Registry, }; // Handle to reload the tracing log filter @@ -85,7 +84,7 @@ pub fn reload_filter() -> Result<(), String> { log::debug!(target: "tracing", "Reloading log filter with: {}", env_filter); FILTER_RELOAD_HANDLE .get() - .ok_or("No reload handle present".to_string())? + .ok_or("No reload handle present")? .reload(env_filter) .map_err(|e| format!("{}", e)) } @@ -109,6 +108,6 @@ pub(crate) fn set_reload_handle(handle: Handle) { // Used in the reload `Handle`. type SCSubscriber< N = tracing_fmt::format::DefaultFields, - E = crate::logging::EventFormat, - W = fn() -> std::io::Stderr, + E = crate::logging::EventFormat, + W = crate::logging::DefaultLogger, > = layer::Layered, Registry>; diff --git a/client/tracing/src/logging/event_format.rs b/client/tracing/src/logging/event_format.rs index 61d7fe77aec68..aec6b76843daf 100644 --- a/client/tracing/src/logging/event_format.rs +++ b/client/tracing/src/logging/event_format.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use crate::logging::fast_local_time::FastLocalTime; use ansi_term::Colour; use regex::Regex; use std::fmt::{self, Write}; @@ -23,16 +24,13 @@ use tracing::{Event, Level, Subscriber}; use tracing_log::NormalizeEvent; use tracing_subscriber::{ field::RecordFields, - fmt::{ - time::{FormatTime, SystemTime}, - FmtContext, FormatEvent, FormatFields, - }, + fmt::{time::FormatTime, FmtContext, FormatEvent, FormatFields}, layer::Context, registry::{LookupSpan, SpanRef}, }; /// A pre-configured event formatter. -pub struct EventFormat { +pub struct EventFormat { /// Use the given timer for log message timestamps. pub timer: T, /// Sets whether or not an event's target is displayed. @@ -64,7 +62,7 @@ where S: Subscriber + for<'a> LookupSpan<'a>, N: for<'a> FormatFields<'a> + 'static, { - let writer = &mut MaybeColorWriter::new(self.enable_color, writer); + let writer = &mut ControlCodeSanitizer::new(!self.enable_color, writer); let normalized_meta = event.normalized_metadata(); let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata()); time::write(&self.timer, writer, self.enable_color)?; @@ -102,10 +100,18 @@ where } } + // The writer only sanitizes its output once it's flushed, so if we don't actually need + // to sanitize everything we need to flush out what was already buffered as-is and only + // force-sanitize what follows. + if !writer.sanitize { + writer.flush()?; + writer.sanitize = true; + } + ctx.format_fields(writer, event)?; writeln!(writer)?; - writer.write() + writer.flush() } } @@ -296,43 +302,60 @@ where } } -/// A writer that may write to `inner_writer` with colors. +/// A writer which (optionally) strips out terminal control codes from the logs. /// -/// This is used by [`EventFormat`] to kill colors when `enable_color` is `false`. +/// This is used by [`EventFormat`] to sanitize the log messages. /// -/// It is required to call [`MaybeColorWriter::write`] after all writes are done, +/// It is required to call [`ControlCodeSanitizer::flush`] after all writes are done, /// because the content of these writes is buffered and will only be written to the /// `inner_writer` at that point. -struct MaybeColorWriter<'a> { - enable_color: bool, +struct ControlCodeSanitizer<'a> { + sanitize: bool, buffer: String, inner_writer: &'a mut dyn fmt::Write, } -impl<'a> fmt::Write for MaybeColorWriter<'a> { +impl<'a> fmt::Write for ControlCodeSanitizer<'a> { fn write_str(&mut self, buf: &str) -> fmt::Result { self.buffer.push_str(buf); Ok(()) } } -impl<'a> MaybeColorWriter<'a> { +// NOTE: When making any changes here make sure to also change this function in `sp-panic-handler`. +fn strip_control_codes(input: &str) -> std::borrow::Cow { + lazy_static::lazy_static! { + static ref RE: Regex = Regex::new(r#"(?x) + \x1b\[[^m]+m| # VT100 escape codes + [ + \x00-\x09\x0B-\x1F # ASCII control codes / Unicode C0 control codes, except \n + \x7F # ASCII delete + \u{80}-\u{9F} # Unicode C1 control codes + \u{202A}-\u{202E} # Unicode left-to-right / right-to-left control characters + \u{2066}-\u{2069} # Same as above + ] + "#).expect("regex parsing doesn't fail; qed"); + } + + RE.replace_all(input, "") +} + +impl<'a> ControlCodeSanitizer<'a> { /// Creates a new instance. - fn new(enable_color: bool, inner_writer: &'a mut dyn fmt::Write) -> Self { - Self { enable_color, inner_writer, buffer: String::new() } + fn new(sanitize: bool, inner_writer: &'a mut dyn fmt::Write) -> Self { + Self { sanitize, inner_writer, buffer: String::new() } } /// Write the buffered content to the `inner_writer`. - fn write(&mut self) -> fmt::Result { - lazy_static::lazy_static! { - static ref RE: Regex = Regex::new("\x1b\\[[^m]+m").expect("Error initializing color regex"); - } - - if !self.enable_color { - let replaced = RE.replace_all(&self.buffer, ""); - self.inner_writer.write_str(&replaced) + fn flush(&mut self) -> fmt::Result { + if self.sanitize { + let replaced = strip_control_codes(&self.buffer); + self.inner_writer.write_str(&replaced)? } else { - self.inner_writer.write_str(&self.buffer) + self.inner_writer.write_str(&self.buffer)? } + + self.buffer.clear(); + Ok(()) } } diff --git a/client/tracing/src/logging/fast_local_time.rs b/client/tracing/src/logging/fast_local_time.rs new file mode 100644 index 0000000000000..47fc23340482a --- /dev/null +++ b/client/tracing/src/logging/fast_local_time.rs @@ -0,0 +1,160 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use chrono::{Datelike, Timelike}; +use std::{cell::RefCell, fmt::Write, time::SystemTime}; +use tracing_subscriber::fmt::time::FormatTime; + +/// A structure which, when `Display`d, will print out the current local time. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] +pub struct FastLocalTime { + /// Decides whenever the fractional timestamp with be included in the output. + /// + /// If `false` the output will match the following `chrono` format string: + /// `%Y-%m-%d %H:%M:%S` + /// + /// If `true` the output will match the following `chrono` format string: + /// `%Y-%m-%d %H:%M:%S%.3f` + pub with_fractional: bool, +} + +// This is deliberately slightly larger than we actually need, just in case. +const TIMESTAMP_MAXIMUM_LENGTH: usize = 32; + +#[derive(Default)] +struct InlineString { + buffer: [u8; TIMESTAMP_MAXIMUM_LENGTH], + length: usize, +} + +impl Write for InlineString { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + let new_length = self.length + s.len(); + assert!( + new_length <= TIMESTAMP_MAXIMUM_LENGTH, + "buffer overflow when formatting the current timestamp" + ); + + self.buffer[self.length..new_length].copy_from_slice(s.as_bytes()); + self.length = new_length; + Ok(()) + } +} + +impl InlineString { + fn as_str(&self) -> &str { + // SAFETY: this is safe since the only place we append to the buffer + // is in `write_str` from an `&str` + unsafe { std::str::from_utf8_unchecked(&self.buffer[..self.length]) } + } +} + +#[derive(Default)] +struct CachedTimestamp { + buffer: InlineString, + last_regenerated_at: u64, + last_fractional: u32, +} + +thread_local! { + static TIMESTAMP: RefCell = Default::default(); +} + +impl FormatTime for FastLocalTime { + fn format_time(&self, w: &mut dyn Write) -> std::fmt::Result { + const TIMESTAMP_PARTIAL_LENGTH: usize = "0000-00-00 00:00:00".len(); + + let elapsed = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("system time is never before UNIX epoch; qed"); + let unix_time = elapsed.as_secs(); + + TIMESTAMP.with(|cache| { + let mut cache = cache.borrow_mut(); + + // Regenerate the timestamp only at most once each second. + if cache.last_regenerated_at != unix_time { + let ts = chrono::Local::now(); + let fractional = (ts.nanosecond() % 1_000_000_000) / 1_000_000; + cache.last_regenerated_at = unix_time; + cache.last_fractional = fractional; + cache.buffer.length = 0; + + write!( + &mut cache.buffer, + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}", + ts.year(), + ts.month(), + ts.day(), + ts.hour(), + ts.minute(), + ts.second(), + fractional + )?; + } else if self.with_fractional { + let fractional = elapsed.subsec_millis(); + + // Regenerate the fractional part at most once each millisecond. + if cache.last_fractional != fractional { + cache.last_fractional = fractional; + cache.buffer.length = TIMESTAMP_PARTIAL_LENGTH + 1; + write!(&mut cache.buffer, "{:03}", fractional)?; + } + } + + let mut slice = cache.buffer.as_str(); + if !self.with_fractional { + slice = &slice[..TIMESTAMP_PARTIAL_LENGTH]; + } + + w.write_str(slice) + }) + } +} + +impl std::fmt::Display for FastLocalTime { + fn fmt(&self, w: &mut std::fmt::Formatter) -> std::fmt::Result { + self.format_time(w) + } +} + +#[test] +fn test_format_fast_local_time() { + assert_eq!( + chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string().len(), + FastLocalTime { with_fractional: false }.to_string().len() + ); + assert_eq!( + chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string().len(), + FastLocalTime { with_fractional: true }.to_string().len() + ); + + // A simple trick to make sure this test won't randomly fail if we so happen + // to land on the exact moment when we tick over to the next second. + let now_1 = FastLocalTime { with_fractional: false }.to_string(); + let expected = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); + let now_2 = FastLocalTime { with_fractional: false }.to_string(); + + assert!( + now_1 == expected || now_2 == expected, + "'{}' or '{}' should have been equal to '{}'", + now_1, + now_2, + expected + ); +} diff --git a/client/tracing/src/logging/layers/mod.rs b/client/tracing/src/logging/layers/mod.rs index 7dd0c4d120ad7..382b79bc85d73 100644 --- a/client/tracing/src/logging/layers/mod.rs +++ b/client/tracing/src/logging/layers/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/tracing/src/logging/layers/prefix_layer.rs b/client/tracing/src/logging/layers/prefix_layer.rs index 2ad786a092233..836ffd2adda8e 100644 --- a/client/tracing/src/logging/layers/prefix_layer.rs +++ b/client/tracing/src/logging/layers/prefix_layer.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/tracing/src/logging/mod.rs b/client/tracing/src/logging/mod.rs index dd4830fe89752..978e24df68d78 100644 --- a/client/tracing/src/logging/mod.rs +++ b/client/tracing/src/logging/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -24,7 +24,11 @@ mod directives; mod event_format; +mod fast_local_time; mod layers; +mod stderr_writer; + +pub(crate) type DefaultLogger = stderr_writer::MakeStderrWriter; pub use directives::*; pub use sc_tracing_proc_macro::*; @@ -34,8 +38,8 @@ use tracing::Subscriber; use tracing_subscriber::{ filter::LevelFilter, fmt::{ - format, time::ChronoLocal, FormatEvent, FormatFields, Formatter, Layer as FmtLayer, - MakeWriter, SubscriberBuilder, + format, FormatEvent, FormatFields, Formatter, Layer as FmtLayer, MakeWriter, + SubscriberBuilder, }, layer::{self, SubscriberExt}, registry::LookupSpan, @@ -43,8 +47,11 @@ use tracing_subscriber::{ }; pub use event_format::*; +pub use fast_local_time::FastLocalTime; pub use layers::*; +use stderr_writer::MakeStderrWriter; + /// Logging Result typedef. pub type Result = std::result::Result; @@ -88,13 +95,9 @@ fn prepare_subscriber( directives: &str, profiling_targets: Option<&str>, force_colors: Option, + detailed_output: bool, builder_hook: impl Fn( - SubscriberBuilder< - format::DefaultFields, - EventFormat, - EnvFilter, - fn() -> std::io::Stderr, - >, + SubscriberBuilder, ) -> SubscriberBuilder, ) -> Result LookupSpan<'a>> where @@ -107,7 +110,7 @@ where // Accept all valid directives and print invalid ones fn parse_user_directives(mut env_filter: EnvFilter, dirs: &str) -> Result { for dir in dirs.split(',') { - env_filter = env_filter.add_directive(parse_default_directive(&dir)?); + env_filter = env_filter.add_directive(parse_default_directive(dir)?); } Ok(env_filter) } @@ -130,7 +133,14 @@ where .add_directive( parse_default_directive("cranelift_wasm=warn").expect("provided directive is valid"), ) - .add_directive(parse_default_directive("hyper=warn").expect("provided directive is valid")); + .add_directive(parse_default_directive("hyper=warn").expect("provided directive is valid")) + .add_directive( + parse_default_directive("trust_dns_proto=off").expect("provided directive is valid"), + ) + .add_directive( + parse_default_directive("libp2p_mdns::behaviour::iface=off") + .expect("provided directive is valid"), + ); if let Ok(lvl) = std::env::var("RUST_LOG") { if lvl != "" { @@ -155,23 +165,19 @@ where tracing_log::LogTracer::builder().with_max_level(max_level).init()?; // If we're only logging `INFO` entries then we'll use a simplified logging format. - let simple = match max_level_hint { - Some(level) if level <= tracing_subscriber::filter::LevelFilter::INFO => true, - _ => false, - }; + let detailed_output = match max_level_hint { + Some(level) if level <= tracing_subscriber::filter::LevelFilter::INFO => false, + _ => true, + } || detailed_output; let enable_color = force_colors.unwrap_or_else(|| atty::is(atty::Stream::Stderr)); - let timer = ChronoLocal::with_format(if simple { - "%Y-%m-%d %H:%M:%S".to_string() - } else { - "%Y-%m-%d %H:%M:%S%.3f".to_string() - }); + let timer = fast_local_time::FastLocalTime { with_fractional: detailed_output }; let event_format = EventFormat { timer, - display_target: !simple, - display_level: !simple, - display_thread_name: !simple, + display_target: detailed_output, + display_level: detailed_output, + display_thread_name: detailed_output, enable_color, dup_to_stdout: !atty::is(atty::Stream::Stderr) && atty::is(atty::Stream::Stdout), }; @@ -179,7 +185,7 @@ where let builder = builder.with_span_events(format::FmtSpan::NONE); - let builder = builder.with_writer(std::io::stderr as _); + let builder = builder.with_writer(MakeStderrWriter::default()); let builder = builder.event_format(event_format); @@ -194,8 +200,10 @@ where pub struct LoggerBuilder { directives: String, profiling: Option<(crate::TracingReceiver, String)>, + custom_profiler: Option>, log_reloading: bool, force_colors: Option, + detailed_output: bool, } impl LoggerBuilder { @@ -204,8 +212,10 @@ impl LoggerBuilder { Self { directives: directives.into(), profiling: None, - log_reloading: true, + custom_profiler: None, + log_reloading: false, force_colors: None, + detailed_output: false, } } @@ -219,12 +229,32 @@ impl LoggerBuilder { self } + /// Add a custom profiler. + pub fn with_custom_profiling( + &mut self, + custom_profiler: Box, + ) -> &mut Self { + self.custom_profiler = Some(custom_profiler); + self + } + /// Wether or not to disable log reloading. pub fn with_log_reloading(&mut self, enabled: bool) -> &mut Self { self.log_reloading = enabled; self } + /// Whether detailed log output should be enabled. + /// + /// This includes showing the log target, log level and thread name. + /// + /// This will be automatically enabled when there is a log level enabled that is higher than + /// `info`. + pub fn with_detailed_output(&mut self, detailed: bool) -> &mut Self { + self.detailed_output = detailed; + self + } + /// Force enable/disable colors. pub fn with_colors(&mut self, enable: bool) -> &mut Self { self.force_colors = Some(enable); @@ -241,9 +271,15 @@ impl LoggerBuilder { &self.directives, Some(&profiling_targets), self.force_colors, + self.detailed_output, |builder| enable_log_reloading!(builder), )?; - let profiling = crate::ProfilingLayer::new(tracing_receiver, &profiling_targets); + let mut profiling = + crate::ProfilingLayer::new(tracing_receiver, &profiling_targets); + + self.custom_profiler + .into_iter() + .for_each(|profiler| profiling.add_handler(profiler)); tracing::subscriber::set_global_default(subscriber.with(profiling))?; @@ -253,34 +289,44 @@ impl LoggerBuilder { &self.directives, Some(&profiling_targets), self.force_colors, + self.detailed_output, |builder| builder, )?; - let profiling = crate::ProfilingLayer::new(tracing_receiver, &profiling_targets); + let mut profiling = + crate::ProfilingLayer::new(tracing_receiver, &profiling_targets); + + self.custom_profiler + .into_iter() + .for_each(|profiler| profiling.add_handler(profiler)); tracing::subscriber::set_global_default(subscriber.with(profiling))?; Ok(()) } + } else if self.log_reloading { + let subscriber = prepare_subscriber( + &self.directives, + None, + self.force_colors, + self.detailed_output, + |builder| enable_log_reloading!(builder), + )?; + + tracing::subscriber::set_global_default(subscriber)?; + + Ok(()) } else { - if self.log_reloading { - let subscriber = - prepare_subscriber(&self.directives, None, self.force_colors, |builder| { - enable_log_reloading!(builder) - })?; - - tracing::subscriber::set_global_default(subscriber)?; + let subscriber = prepare_subscriber( + &self.directives, + None, + self.force_colors, + self.detailed_output, + |builder| builder, + )?; - Ok(()) - } else { - let subscriber = - prepare_subscriber(&self.directives, None, self.force_colors, |builder| { - builder - })?; + tracing::subscriber::set_global_default(subscriber)?; - tracing::subscriber::set_global_default(subscriber)?; - - Ok(()) - } + Ok(()) } } } @@ -289,7 +335,16 @@ impl LoggerBuilder { mod tests { use super::*; use crate as sc_tracing; - use std::{env, process::Command}; + use log::info; + use std::{ + collections::BTreeMap, + env, + process::Command, + sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + Arc, + }, + }; use tracing::{metadata::Kind, subscriber::Interest, Callsite, Level, Metadata}; const EXPECTED_LOG_MESSAGE: &'static str = "yeah logging works as expected"; @@ -299,9 +354,28 @@ mod tests { let _ = LoggerBuilder::new(directives).init().unwrap(); } + fn run_test_in_another_process( + test_name: &str, + test_body: impl FnOnce(), + ) -> Option { + if env::var("RUN_FORKED_TEST").is_ok() { + test_body(); + None + } else { + let output = Command::new(env::current_exe().unwrap()) + .arg(test_name) + .env("RUN_FORKED_TEST", "1") + .output() + .unwrap(); + + assert!(output.status.success()); + Some(output) + } + } + #[test] fn test_logger_filters() { - if env::var("RUN_TEST_LOGGER_FILTERS").is_ok() { + run_test_in_another_process("test_logger_filters", || { let test_directives = "afg=debug,sync=trace,client=warn,telemetry,something-with-dash=error"; init_logger(&test_directives); @@ -338,15 +412,7 @@ mod tests { assert!(test_filter("telemetry", Level::TRACE)); assert!(test_filter("something-with-dash", Level::ERROR)); }); - } else { - let status = Command::new(env::current_exe().unwrap()) - .arg("test_logger_filters") - .env("RUN_TEST_LOGGER_FILTERS", "1") - .output() - .unwrap() - .status; - assert!(status.success()); - } + }); } /// This test ensures that using dash (`-`) in the target name in logs and directives actually @@ -481,4 +547,132 @@ mod tests { assert_eq!("MAX_LOG_LEVEL=Trace", run_test(None, Some("test=info".into()))); } } + + // This creates a bunch of threads and makes sure they start executing + // a given callback almost exactly at the same time. + fn run_on_many_threads(thread_count: usize, callback: impl Fn(usize) + 'static + Send + Clone) { + let started_count = Arc::new(AtomicUsize::new(0)); + let barrier = Arc::new(AtomicBool::new(false)); + let threads: Vec<_> = (0..thread_count) + .map(|nth_thread| { + let started_count = started_count.clone(); + let barrier = barrier.clone(); + let callback = callback.clone(); + + std::thread::spawn(move || { + started_count.fetch_add(1, Ordering::SeqCst); + while !barrier.load(Ordering::SeqCst) { + std::thread::yield_now(); + } + + callback(nth_thread); + }) + }) + .collect(); + + while started_count.load(Ordering::SeqCst) != thread_count { + std::thread::yield_now(); + } + barrier.store(true, Ordering::SeqCst); + + for thread in threads { + if let Err(error) = thread.join() { + println!("error: failed to join thread: {:?}", error); + unsafe { libc::abort() } + } + } + } + + #[test] + fn parallel_logs_from_multiple_threads_are_properly_gathered() { + const THREAD_COUNT: usize = 128; + const LOGS_PER_THREAD: usize = 1024; + + let output = run_test_in_another_process( + "parallel_logs_from_multiple_threads_are_properly_gathered", + || { + let builder = LoggerBuilder::new(""); + builder.init().unwrap(); + + run_on_many_threads(THREAD_COUNT, |nth_thread| { + for _ in 0..LOGS_PER_THREAD { + info!("Thread <<{}>>", nth_thread); + } + }); + }, + ); + + if let Some(output) = output { + let stderr = String::from_utf8(output.stderr).unwrap(); + let mut count_per_thread = BTreeMap::new(); + for line in stderr.split("\n") { + if let Some(index_s) = line.find("Thread <<") { + let index_s = index_s + "Thread <<".len(); + let index_e = line.find(">>").unwrap(); + let nth_thread: usize = line[index_s..index_e].parse().unwrap(); + *count_per_thread.entry(nth_thread).or_insert(0) += 1; + } + } + + assert_eq!(count_per_thread.len(), THREAD_COUNT); + for (_, count) in count_per_thread { + assert_eq!(count, LOGS_PER_THREAD); + } + } + } + + #[test] + fn huge_single_line_log_is_properly_printed_out() { + let mut line = String::new(); + line.push_str("$$START$$"); + for n in 0..16 * 1024 * 1024 { + let ch = b'a' + (n as u8 % (b'z' - b'a')); + line.push(char::from(ch)); + } + line.push_str("$$END$$"); + + let output = + run_test_in_another_process("huge_single_line_log_is_properly_printed_out", || { + let builder = LoggerBuilder::new(""); + builder.init().unwrap(); + info!("{}", line); + }); + + if let Some(output) = output { + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stderr.contains(&line)); + } + } + + #[test] + fn control_characters_are_always_stripped_out_from_the_log_messages() { + const RAW_LINE: &str = "$$START$$\x1B[1;32mIn\u{202a}\u{202e}\u{2066}\u{2069}ner\n\r\x7ftext!\u{80}\u{9f}\x1B[0m$$END$$"; + const SANITIZED_LINE: &str = "$$START$$Inner\ntext!$$END$$"; + + let output = run_test_in_another_process( + "control_characters_are_always_stripped_out_from_the_log_messages", + || { + std::env::set_var("RUST_LOG", "trace"); + let mut builder = LoggerBuilder::new(""); + builder.with_colors(true); + builder.init().unwrap(); + log::error!("{}", RAW_LINE); + }, + ); + + if let Some(output) = output { + let stderr = String::from_utf8(output.stderr).unwrap(); + // The log messages should always be sanitized. + assert!(!stderr.contains(RAW_LINE)); + assert!(stderr.contains(SANITIZED_LINE)); + + // The part where the timestamp, the logging level, etc. is printed out doesn't + // always have to be sanitized unless it's necessary, and here it shouldn't be. + assert!(stderr.contains("\x1B[31mERROR\x1B[0m")); + + // Make sure the logs aren't being duplicated. + assert_eq!(stderr.find("ERROR"), stderr.rfind("ERROR")); + assert_eq!(stderr.find(SANITIZED_LINE), stderr.rfind(SANITIZED_LINE)); + } + } } diff --git a/client/tracing/src/logging/stderr_writer.rs b/client/tracing/src/logging/stderr_writer.rs new file mode 100644 index 0000000000000..de78a61af41a2 --- /dev/null +++ b/client/tracing/src/logging/stderr_writer.rs @@ -0,0 +1,228 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! This module contains a buffered semi-asynchronous stderr writer. +//! +//! Depending on how we were started writing to stderr can take a surprisingly long time. +//! +//! If the other side takes their sweet sweet time reading whatever we send them then writing +//! to stderr might block for a long time, since it is effectively a synchronous operation. +//! And every time we write to stderr we need to grab a global lock, which affects every thread +//! which also tries to log something at the same time. +//! +//! Of course we *will* be ultimately limited by how fast the recipient can ingest our logs, +//! but it's not like logging is the only thing we're doing. And we still can't entirely +//! avoid the problem of multiple threads contending for the same lock. (Well, technically +//! we could employ something like a lock-free circular buffer, but that might be like +//! killing a fly with a sledgehammer considering the complexity involved; this is only +//! a logger after all.) +//! +//! But we can try to make things a little better. We can offload actually writing to stderr +//! to another thread and flush the logs in bulk instead of doing it per-line, which should +//! reduce the amount of CPU time we waste on making syscalls and on spinning waiting for locks. +//! +//! How much this helps depends on a multitude of factors, including the hardware we're running on, +//! how much we're logging, from how many threads, which exact set of threads are logging, to what +//! stderr is actually connected to (is it a terminal emulator? a file? an UDP socket?), etc. +//! +//! In general this can reduce the real time execution time as much as 75% in certain cases, or it +//! can make absolutely no difference in others. + +use parking_lot::{Condvar, Mutex, Once}; +use std::{ + io::Write, + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; +use tracing::{Level, Metadata}; + +/// How many bytes of buffered logs will trigger an async flush on another thread? +const ASYNC_FLUSH_THRESHOLD: usize = 16 * 1024; + +/// How many bytes of buffered logs will trigger a sync flush on the current thread? +const SYNC_FLUSH_THRESHOLD: usize = 768 * 1024; + +/// How many bytes can be buffered at maximum? +const EMERGENCY_FLUSH_THRESHOLD: usize = 2 * 1024 * 1024; + +/// If there isn't enough printed out this is how often the logs will be automatically flushed. +const AUTOFLUSH_EVERY: Duration = Duration::from_millis(50); + +/// The least serious level at which a synchronous flush will be triggered. +const SYNC_FLUSH_LEVEL_THRESHOLD: Level = Level::ERROR; + +/// The amount of time we'll block until the buffer is fully flushed on exit. +/// +/// This should be completely unnecessary in normal circumstances. +const ON_EXIT_FLUSH_TIMEOUT: Duration = Duration::from_secs(5); + +/// A global buffer to which we'll append all of our logs before flushing them out to stderr. +static BUFFER: Mutex> = parking_lot::const_mutex(Vec::new()); + +/// A spare buffer which we'll swap with the main buffer on each flush to minimize lock contention. +static SPARE_BUFFER: Mutex> = parking_lot::const_mutex(Vec::new()); + +/// A conditional variable used to forcefully trigger asynchronous flushes. +static ASYNC_FLUSH_CONDVAR: Condvar = Condvar::new(); + +static ENABLE_ASYNC_LOGGING: AtomicBool = AtomicBool::new(true); + +fn flush_logs(mut buffer: parking_lot::lock_api::MutexGuard>) { + let mut spare_buffer = SPARE_BUFFER.lock(); + std::mem::swap(&mut *spare_buffer, &mut *buffer); + std::mem::drop(buffer); + + let stderr = std::io::stderr(); + let mut stderr_lock = stderr.lock(); + let _ = stderr_lock.write_all(&spare_buffer); + std::mem::drop(stderr_lock); + + spare_buffer.clear(); +} + +fn log_autoflush_thread() { + let mut buffer = BUFFER.lock(); + loop { + ASYNC_FLUSH_CONDVAR.wait_for(&mut buffer, AUTOFLUSH_EVERY); + loop { + flush_logs(buffer); + + buffer = BUFFER.lock(); + if buffer.len() >= ASYNC_FLUSH_THRESHOLD { + // While we were busy flushing we picked up enough logs to do another flush. + continue + } else { + break + } + } + } +} + +#[cold] +fn initialize() { + std::thread::Builder::new() + .name("log-autoflush".to_owned()) + .spawn(log_autoflush_thread) + .expect("thread spawning doesn't normally fail; qed"); + + // SAFETY: This is safe since we pass a valid pointer to `atexit`. + let errcode = unsafe { libc::atexit(on_exit) }; + assert_eq!(errcode, 0, "atexit failed while setting up the logger: {}", errcode); +} + +extern "C" fn on_exit() { + ENABLE_ASYNC_LOGGING.store(false, Ordering::SeqCst); + + if let Some(buffer) = BUFFER.try_lock_for(ON_EXIT_FLUSH_TIMEOUT) { + flush_logs(buffer); + } +} + +/// A drop-in replacement for [`std::io::stderr`] for use anywhere +/// a [`tracing_subscriber::fmt::MakeWriter`] is accepted. +pub struct MakeStderrWriter { + // A dummy field so that the structure is not publicly constructible. + _dummy: (), +} + +impl Default for MakeStderrWriter { + fn default() -> Self { + static ONCE: Once = Once::new(); + ONCE.call_once(initialize); + MakeStderrWriter { _dummy: () } + } +} + +impl tracing_subscriber::fmt::MakeWriter for MakeStderrWriter { + type Writer = StderrWriter; + + fn make_writer(&self) -> Self::Writer { + StderrWriter::new(false) + } + + // The `tracing-subscriber` crate calls this for every line logged. + fn make_writer_for(&self, meta: &Metadata<'_>) -> Self::Writer { + StderrWriter::new(*meta.level() <= SYNC_FLUSH_LEVEL_THRESHOLD) + } +} + +pub struct StderrWriter { + buffer: Option>>, + sync_flush_on_drop: bool, + original_len: usize, +} + +impl StderrWriter { + fn new(mut sync_flush_on_drop: bool) -> Self { + if !ENABLE_ASYNC_LOGGING.load(Ordering::Relaxed) { + sync_flush_on_drop = true; + } + + // This lock isn't as expensive as it might look, since this is only called once the full + // line to be logged is already serialized into a thread-local buffer inside of the + // `tracing-subscriber` crate, and basically the only thing we'll do when holding this lock + // is to copy that over to our global shared buffer in one go in `Write::write_all` and be + // immediately dropped. + let buffer = BUFFER.lock(); + StderrWriter { original_len: buffer.len(), buffer: Some(buffer), sync_flush_on_drop } + } +} + +#[cold] +fn emergency_flush(buffer: &mut Vec, input: &[u8]) { + let stderr = std::io::stderr(); + let mut stderr_lock = stderr.lock(); + let _ = stderr_lock.write_all(buffer); + buffer.clear(); + + let _ = stderr_lock.write_all(input); +} + +impl Write for StderrWriter { + fn write(&mut self, input: &[u8]) -> Result { + let buffer = self.buffer.as_mut().expect("buffer is only None after `drop`; qed"); + if buffer.len() + input.len() >= EMERGENCY_FLUSH_THRESHOLD { + // Make sure we don't blow our memory budget. Normally this should never happen, + // but there are cases where we directly print out untrusted user input which + // can potentially be megabytes in size. + emergency_flush(buffer, input); + } else { + buffer.extend_from_slice(input); + } + Ok(input.len()) + } + + fn write_all(&mut self, input: &[u8]) -> Result<(), std::io::Error> { + self.write(input).map(|_| ()) + } + + fn flush(&mut self) -> Result<(), std::io::Error> { + Ok(()) + } +} + +impl Drop for StderrWriter { + fn drop(&mut self) { + let buf = self.buffer.take().expect("buffer is only None after `drop`; qed"); + if self.sync_flush_on_drop || buf.len() >= SYNC_FLUSH_THRESHOLD { + flush_logs(buf); + } else if self.original_len < ASYNC_FLUSH_THRESHOLD && buf.len() >= ASYNC_FLUSH_THRESHOLD { + ASYNC_FLUSH_CONDVAR.notify_one(); + } + } +} diff --git a/client/transaction-pool/Cargo.toml b/client/transaction-pool/Cargo.toml index 2184af819adf7..7a3ab042d5a13 100644 --- a/client/transaction-pool/Cargo.toml +++ b/client/transaction-pool/Cargo.toml @@ -2,9 +2,9 @@ name = "sc-transaction-pool" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate transaction pool implementation." readme = "README.md" @@ -13,41 +13,36 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } -thiserror = "1.0.21" -futures = "0.3.16" -intervalier = "0.4.0" -log = "0.4.8" -parity-util-mem = { version = "0.10.0", default-features = false, features = ["primitive-types"] } -parking_lot = "0.11.1" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.9.0"} +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" +futures-timer = "3.0.2" +linked-hash-map = "0.5.4" +log = "0.4.17" +parking_lot = "0.12.1" +serde = { version = "1.0.136", features = ["derive"] } +thiserror = "1.0.30" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } -sp-transaction-pool = { version = "4.0.0-dev", path = "../../primitives/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "./api" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sc-utils = { version = "4.0.0-dev", path = "../utils" } -serde = { version = "1.0.126", features = ["derive"] } -linked-hash-map = "0.5.4" -retain_mut = "0.1.3" +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-transaction-pool = { version = "4.0.0-dev", path = "../../primitives/transaction-pool" } [dev-dependencies] +array-bytes = "4.1" assert_matches = "1.3.0" -hex = "0.4" -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -substrate-test-runtime-transaction-pool = { version = "2.0.0", path = "../../test-utils/runtime/transaction-pool" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +criterion = "0.3" sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -codec = { package = "parity-scale-codec", version = "2.0.0" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } -criterion = "0.3" +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +substrate-test-runtime-transaction-pool = { version = "2.0.0", path = "../../test-utils/runtime/transaction-pool" } [[bench]] name = "basics" harness = false - -[features] -test-helpers = [] diff --git a/client/transaction-pool/README.md b/client/transaction-pool/README.md index e4f8ccb3d8105..4a2bbb8838f9c 100644 --- a/client/transaction-pool/README.md +++ b/client/transaction-pool/README.md @@ -39,7 +39,7 @@ runtime (queried at current best imported block). Since the blockchain is not always linear, forks need to be correctly handled by the transaction pool as well. In case of a fork, some blocks are *retracted* from the canonical chain, and some other blocks get *enacted* on top of some -common ancestor. The transactions from retrated blocks could simply be discarded, +common ancestor. The transactions from retracted blocks could simply be discarded, but it's desirable to make sure they are still considered for inclusion in case they are deemed valid by the runtime state at best, recently enacted block (fork the chain re-organized to). @@ -49,7 +49,7 @@ pool, it's broadcasting status, block inclusion, finality, etc. ## Transaction Validity details -Information retrieved from the the runtime are encapsulated in `TransactionValidity` +Information retrieved from the the runtime are encapsulated in the `TransactionValidity` type. ```rust @@ -147,7 +147,7 @@ choosing the ones with highest priority to include to the next block first. - `priority` of transaction may change over time - on-chain conditions may affect `priority` -- Given two transactions with overlapping `provides` tags, the one with higher +- given two transactions with overlapping `provides` tags, the one with higher `priority` should be preferred. However we can also look at the total priority of a subtree rooted at that transaction and compare that instead (i.e. even though the transaction itself has lower `priority` it "unlocks" other high priority transactions). @@ -163,7 +163,7 @@ the transaction is valid all that time though. - `longevity` of transaction may change over time - on-chain conditions may affect `longevity` -- After `longevity` lapses the transaction may still be valid +- after `longevity` lapses, the transaction may still be valid ### `propagate` @@ -231,15 +231,16 @@ to instead of gossiping everyting have other peers request transactions they are interested in. Since the pool is expected to store more transactions than what can fit -to a single block. Validating the entire pool on every block might not be -feasible, so the actual implementation might need to take some shortcuts. +in a single block, validating the entire pool on every block might not be +feasible. This means that the actual implementation might need to take some +shortcuts. ## Suggestions & caveats -1. The validity of transaction should not change significantly from block to +1. The validity of a transaction should not change significantly from block to block. I.e. changes in validity should happen predictably, e.g. `longevity` decrements by 1, `priority` stays the same, `requires` changes if transaction - that provided a tag was included in block. `provides` does not change, etc. + that provided a tag was included in block, `provides` does not change, etc. 1. That means we don't have to revalidate every transaction after every block import, but we need to take care of removing potentially stale transactions. @@ -253,9 +254,9 @@ feasible, so the actual implementation might need to take some shortcuts. 1. In the past there were many issues found when running small networks with a lot of re-orgs. Make sure that transactions are never lost. -1. UTXO model is quite challenging. The transaction becomes valid right after - it's included in block, however it is waiting for exactly the same inputs to - be spent, so it will never really be included again. +1. The UTXO model is quite challenging. A transaction becomes valid right after + it's included in a block, however it is waiting for exactly the same inputs + to be spent, so it will never really be included again. 1. Note that in a non-ideal implementation the state of the pool will most likely always be a bit off, i.e. some transactions might be still in the pool, @@ -277,8 +278,8 @@ feasible, so the actual implementation might need to take some shortcuts. 1. We periodically validate all transactions in the pool in batches. -1. To minimize runtime calls, we introduce batch-verify call. Note it should reset - the state (overlay) after every verification. +1. To minimize runtime calls, we introduce the batch-verify call. Note it should + reset the state (overlay) after every verification. 1. Consider leveraging finality. Maybe we could verify against latest finalised block instead. With this the pool in different nodes can be more similar @@ -286,16 +287,16 @@ feasible, so the actual implementation might need to take some shortcuts. is not a strict requirement for a Substrate chain to have though. 1. Perhaps we could avoid maintaining ready/future queues as currently, but - rather if transaction doesn't have all requirements satisfied by existing + rather if a transaction doesn't have all requirements satisfied by existing transactions we attempt to re-import it in the future. 1. Instead of maintaining a full pool with total ordering we attempt to maintain a set of next (couple of) blocks. We could introduce batch-validate runtime - api method that pretty much attempts to simulate actual block inclusion of + api method that pretty much attempts to simulate actual block inclusion of a set of such transactions (without necessarily fully running/dispatching them). Importing a transaction would consist of figuring out which next block - this transaction have a chance to be included in and then attempting to - either push it back or replace some of existing transactions. + this transaction has a chance to be included in and then attempting to + either push it back or replace some existing transactions. 1. Perhaps we could use some immutable graph structure to easily add/remove transactions. We need some traversal method that takes priority and @@ -320,7 +321,7 @@ The pool consists of basically two independent parts: The pool is split into `ready` pool and `future` pool. The latter contains transactions that don't have their requirements satisfied, and the former holds transactions that can be used to build a graph of dependencies. Note that the -graph is build ad-hoc during the traversal process (getting the `ready` +graph is built ad-hoc during the traversal process (using the `ready` iterator). This makes the importing process cheaper (we don't need to find the exact position in the queue or graph), but traversal process slower (logarithmic). However most of the time we will only need the beginning of the @@ -342,26 +343,26 @@ to limit number of runtime verification calls. Each time a transaction is imported, we first verify it's validity and later find if the tags it `requires` can be satisfied by transactions already in `ready` pool. In case the transaction is imported to the `ready` pool we -additionally *promote* transactions from `future` pool if the transaction +additionally *promote* transactions from the `future` pool if the transaction happened to fulfill their requirements. -Note we need to cater for cases where transaction might replace a already +Note we need to cater for cases where a transaction might replace an already existing transaction in the pool. In such case we check the entire sub-tree of transactions that we are about to replace, compare their cumulative priority to determine which subtree to keep. -After a block is imported we kick-off pruning procedure. We first attempt to -figure out what tags were satisfied by transaction in that block. For each block -transaction we either call into runtime to get it's `ValidTransaction` object, +After a block is imported we kick-off the pruning procedure. We first attempt to +figure out what tags were satisfied by a transaction in that block. For each block +transaction we either call into the runtime to get it's `ValidTransaction` object, or we check the pool if that transaction is already known to spare the runtime -call. From this we gather full set of `provides` tags and perform pruning of -`ready` pool based on that. Also we promote all transactions from `future` that -have their tags satisfied. +call. From this we gather the full set of `provides` tags and perform pruning of +the `ready` pool based on that. Also, we promote all transactions from `future` +that have their tags satisfied. In case we remove transactions that we are unsure if they were already included -in current block or some block in the past, it is being added to revalidation -queue and attempted to be re-imported by the background task in the future. +in the current block or some block in the past, it gets added to the revalidation +queue and attempts to be re-imported by the background task in the future. Runtime calls to verify transactions are performed from a separate (limited) -thread pool to avoid interferring too much with other subsystems of the node. We +thread pool to avoid interfering too much with other subsystems of the node. We definitely don't want to have all cores validating network transactions, because all of these transactions need to be considered untrusted (potentially DoS). diff --git a/client/transaction-pool/api/Cargo.toml b/client/transaction-pool/api/Cargo.toml index efef36071f083..e14a3ff4f3839 100644 --- a/client/transaction-pool/api/Cargo.toml +++ b/client/transaction-pool/api/Cargo.toml @@ -2,18 +2,20 @@ name = "sc-transaction-pool-api" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Transaction pool client facing API." [dependencies] -futures = { version = "0.3.1" } -log = { version = "0.4.8" } -serde = { version = "1.0.126", features = ["derive"] } -thiserror = { version = "1.0.21" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } - -derive_more = { version = "0.99.11" } +async-trait = "0.1.57" +futures = "0.3.21" +log = "0.4.17" +serde = { version = "1.0.136", features = ["derive"] } +thiserror = "1.0.30" sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } + +[dev-dependencies] +serde_json = "1.0" diff --git a/client/transaction-pool/api/src/error.rs b/client/transaction-pool/api/src/error.rs index feee3b0a949c2..aada44734d053 100644 --- a/client/transaction-pool/api/src/error.rs +++ b/client/transaction-pool/api/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -26,7 +26,7 @@ use sp_runtime::transaction_validity::{ pub type Result = std::result::Result; /// Transaction pool error type. -#[derive(Debug, thiserror::Error, derive_more::From)] +#[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error("Unknown transaction validity: {0:?}")] @@ -46,7 +46,7 @@ pub enum Error { TemporarilyBanned, #[error("[{0:?}] Already imported")] - AlreadyImported(Box), + AlreadyImported(Box), #[error("Too low priority ({} > {})", old, new)] TooLowPriority { @@ -64,7 +64,6 @@ pub enum Error { #[error("Transaction cannot be propagated and the local node does not author blocks")] Unactionable, - #[from(ignore)] #[error("{0}")] InvalidBlockId(String), @@ -73,7 +72,7 @@ pub enum Error { } /// Transaction pool error conversion. -pub trait IntoPoolError: std::error::Error + Send + Sized { +pub trait IntoPoolError: std::error::Error + Send + Sized + Sync { /// Try to extract original `Error` /// /// This implementation is optional and used only to diff --git a/client/transaction-pool/api/src/lib.rs b/client/transaction-pool/api/src/lib.rs index a6252f1373c5d..c1e49ad07d7b1 100644 --- a/client/transaction-pool/api/src/lib.rs +++ b/client/transaction-pool/api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -21,17 +21,19 @@ pub mod error; +use async_trait::async_trait; use futures::{Future, Stream}; -use serde::{Deserialize, Serialize}; -pub use sp_runtime::transaction_validity::{ - TransactionLongevity, TransactionPriority, TransactionSource, TransactionTag, -}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Member, NumberFor}, }; use std::{collections::HashMap, hash::Hash, pin::Pin, sync::Arc}; +pub use sp_runtime::transaction_validity::{ + TransactionLongevity, TransactionPriority, TransactionSource, TransactionTag, +}; + /// Transaction pool status. #[derive(Debug)] pub struct PoolStatus { @@ -107,15 +109,18 @@ pub enum TransactionStatus { Ready, /// The transaction has been broadcast to the given peers. Broadcast(Vec), - /// Transaction has been included in block with given hash. - InBlock(BlockHash), + /// Transaction has been included in block with given hash + /// at the given position. + #[serde(with = "v1_compatible")] + InBlock((BlockHash, TxIndex)), /// The block this transaction was included in has been retracted. Retracted(BlockHash), /// Maximum number of finality watchers has been reached, /// old watchers are being removed. FinalityTimeout(BlockHash), - /// Transaction has been finalized by a finality-gadget, e.g GRANDPA - Finalized(BlockHash), + /// Transaction has been finalized by a finality-gadget, e.g GRANDPA. + #[serde(with = "v1_compatible")] + Finalized((BlockHash, TxIndex)), /// Transaction has been replaced in the pool, by another transaction /// that provides the same tags. (e.g. same (sender, nonce)). Usurped(Hash), @@ -142,6 +147,8 @@ pub type TransactionFor

= <

::Block as BlockT>::Extrinsi pub type TransactionStatusStreamFor

= TransactionStatusStream, BlockHash

>; /// Transaction type for a local pool. pub type LocalTransactionFor

= <

::Block as BlockT>::Extrinsic; +/// Transaction's index within the block in which it was included. +pub type TxIndex = usize; /// Typical future type used in transaction pool api. pub type PoolFuture = std::pin::Pin> + Send>>; @@ -177,7 +184,7 @@ pub trait TransactionPool: Send + Sync { /// Block type. type Block: BlockT; /// Transaction hash type. - type Hash: Hash + Eq + Member + Serialize; + type Hash: Hash + Eq + Member + Serialize + DeserializeOwned; /// In-pool transaction type. type InPoolTransaction: InPoolTransaction< Transaction = TransactionFor, @@ -223,13 +230,14 @@ pub trait TransactionPool: Send + Sync { at: NumberFor, ) -> Pin< Box< - dyn Future> + Send>> - + Send, + dyn Future< + Output = Box> + Send>, + > + Send, >, >; /// Get an iterator for ready transactions ordered by priority. - fn ready(&self) -> Box> + Send>; + fn ready(&self) -> Box> + Send>; // *** Block production /// Remove transactions identified by given hashes (and dependent transactions) from the pool. @@ -254,9 +262,30 @@ pub trait TransactionPool: Send + Sync { fn ready_transaction(&self, hash: &TxHash) -> Option>; } +/// An iterator of ready transactions. +/// +/// The trait extends regular [`std::iter::Iterator`] trait and allows reporting +/// last-returned element as invalid. +/// +/// The implementation is then allowed, for performance reasons, to change the elements +/// returned next, by e.g. skipping elements that are known to depend on the reported +/// transaction, which yields them invalid as well. +pub trait ReadyTransactions: Iterator { + /// Report given transaction as invalid. + /// + /// This might affect subsequent elements returned by the iterator, so dependent transactions + /// are skipped for performance reasons. + fn report_invalid(&mut self, _tx: &Self::Item); +} + +/// A no-op implementation for an empty iterator. +impl ReadyTransactions for std::iter::Empty { + fn report_invalid(&mut self, _tx: &T) {} +} + /// Events that the transaction pool listens for. pub enum ChainEvent { - /// New best block have been added to the chain + /// New best block have been added to the chain. NewBestBlock { /// Hash of the block. hash: B::Hash, @@ -267,15 +296,18 @@ pub enum ChainEvent { }, /// An existing block has been finalized. Finalized { - /// Hash of just finalized block + /// Hash of just finalized block. hash: B::Hash, + /// Path from old finalized to new finalized parent. + tree_route: Arc<[B::Hash]>, }, } /// Trait for transaction pool maintenance. +#[async_trait] pub trait MaintainedTransactionPool: TransactionPool { /// Perform maintenance - fn maintain(&self, event: ChainEvent) -> Pin + Send>>; + async fn maintain(&self, event: ChainEvent); } /// Transaction pool interface for submitting local transactions that exposes a @@ -326,14 +358,63 @@ impl OffchainSubmitTransaction for TP extrinsic ); - let result = self.submit_local(&at, extrinsic); + let result = self.submit_local(at, extrinsic); result.map(|_| ()).map_err(|e| { log::warn!( target: "txpool", - "(offchain call) Error submitting a transaction to the pool: {:?}", + "(offchain call) Error submitting a transaction to the pool: {}", e ) }) } } + +/// Wrapper functions to keep the API backwards compatible over the wire for the old RPC spec. +mod v1_compatible { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(data: &(H, usize), serializer: S) -> Result + where + S: Serializer, + H: Serialize, + { + let (hash, _) = data; + serde::Serialize::serialize(&hash, serializer) + } + + pub fn deserialize<'de, D, H>(deserializer: D) -> Result<(H, usize), D::Error> + where + D: Deserializer<'de>, + H: Deserialize<'de>, + { + let hash: H = serde::Deserialize::deserialize(deserializer)?; + Ok((hash, 0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tx_status_compatibility() { + let event: TransactionStatus = TransactionStatus::InBlock((1, 2)); + let ser = serde_json::to_string(&event).unwrap(); + + let exp = r#"{"inBlock":1}"#; + assert_eq!(ser, exp); + + let event_dec: TransactionStatus = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, TransactionStatus::InBlock((1, 0))); + + let event: TransactionStatus = TransactionStatus::Finalized((1, 2)); + let ser = serde_json::to_string(&event).unwrap(); + + let exp = r#"{"finalized":1}"#; + assert_eq!(ser, exp); + + let event_dec: TransactionStatus = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, TransactionStatus::Finalized((1, 0))); + } +} diff --git a/client/transaction-pool/benches/basics.rs b/client/transaction-pool/benches/basics.rs index cf30a0200ad76..602e84b47775c 100644 --- a/client/transaction-pool/benches/basics.rs +++ b/client/transaction-pool/benches/basics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,16 +18,16 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use codec::Encode; +use codec::{Decode, Encode}; use futures::{ executor::block_on, future::{ready, Ready}, }; -use sc_transaction_pool::{test_helpers::*, *}; +use sc_transaction_pool::*; use sp_core::blake2_256; use sp_runtime::{ generic::BlockId, - traits::Block as BlockT, + traits::{Block as BlockT, NumberFor}, transaction_validity::{ InvalidTransaction, TransactionSource, TransactionTag as Tag, TransactionValidity, ValidTransaction, @@ -63,10 +63,10 @@ impl ChainApi for TestApi { &self, at: &BlockId, _source: TransactionSource, - uxt: test_helpers::ExtrinsicFor, + uxt: ::Extrinsic, ) -> Self::ValidationFuture { let nonce = uxt.transfer().nonce; - let from = uxt.transfer().from.clone(); + let from = uxt.transfer().from; match self.block_id_to_number(at) { Ok(Some(num)) if num > 5 => return ready(Ok(Err(InvalidTransaction::Stale.into()))), @@ -76,7 +76,7 @@ impl ChainApi for TestApi { ready(Ok(Ok(ValidTransaction { priority: 4, requires: if nonce > 1 && self.nonce_dependant { - vec![to_tag(nonce - 1, from.clone())] + vec![to_tag(nonce - 1, from)] } else { vec![] }, @@ -89,7 +89,7 @@ impl ChainApi for TestApi { fn block_id_to_number( &self, at: &BlockId, - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { Ok(match at { BlockId::Number(num) => Some(*num), BlockId::Hash(_) => None, @@ -99,19 +99,19 @@ impl ChainApi for TestApi { fn block_id_to_hash( &self, at: &BlockId, - ) -> Result>, Self::Error> { + ) -> Result::Hash>, Self::Error> { Ok(match at { BlockId::Number(num) => Some(H256::from_low_u64_be(*num)).into(), BlockId::Hash(_) => None, }) } - fn hash_and_length(&self, uxt: &test_helpers::ExtrinsicFor) -> (H256, usize) { + fn hash_and_length(&self, uxt: &::Extrinsic) -> (H256, usize) { let encoded = uxt.encode(); (blake2_256(&encoded).into(), encoded.len()) } - fn block_body(&self, _id: &BlockId) -> Self::BodyFuture { + fn block_body(&self, _id: ::Hash) -> Self::BodyFuture { ready(Ok(None)) } @@ -121,12 +121,21 @@ impl ChainApi for TestApi { ) -> Result::Header>, Self::Error> { Ok(None) } + + fn tree_route( + &self, + _from: ::Hash, + _to: ::Hash, + ) -> Result, Self::Error> { + unimplemented!() + } } fn uxt(transfer: Transfer) -> Extrinsic { Extrinsic::Transfer { transfer, - signature: Default::default(), + signature: Decode::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) + .expect("infinite input; no dead input space; qed"), exhaust_resources_when_not_first: false, } } diff --git a/client/transaction-pool/graph/Cargo.toml b/client/transaction-pool/graph/Cargo.toml deleted file mode 100644 index b49cadc51c33c..0000000000000 --- a/client/transaction-pool/graph/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "sc-transaction-graph" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "Generic Transaction Pool" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -derive_more = "0.99.2" -thiserror = "1.0.21" -futures = "0.3.9" -log = "0.4.8" -parking_lot = "0.11.1" -serde = { version = "1.0.101", features = ["derive"] } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sc-utils = { version = "4.0.0-dev", path = "../../utils" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sp-transaction-pool = { version = "4.0.0-dev", path = "../../../primitives/transaction-pool" } -parity-util-mem = { version = "0.10.0", default-features = false, features = ["primitive-types"] } -linked-hash-map = "0.5.4" -retain_mut = "0.1.3" - -[dev-dependencies] -assert_matches = "1.3.0" -codec = { package = "parity-scale-codec", version = "2.0.0" } -substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } -criterion = "0.3" - -[[bench]] -name = "basics" -harness = false diff --git a/client/transaction-pool/src/api.rs b/client/transaction-pool/src/api.rs index a735c67d846ce..c3f9b50f9482d 100644 --- a/client/transaction-pool/src/api.rs +++ b/client/transaction-pool/src/api.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -18,7 +18,7 @@ //! Chain api required for the transaction pool. -use codec::{Decode, Encode}; +use codec::Encode; use futures::{ channel::{mpsc, oneshot}, future::{ready, Future, FutureExt, Ready}, @@ -28,16 +28,13 @@ use futures::{ use std::{marker::PhantomData, pin::Pin, sync::Arc}; use prometheus_endpoint::Registry as PrometheusRegistry; -use sc_client_api::{ - blockchain::HeaderBackend, - light::{Fetcher, RemoteBodyRequest, RemoteCallRequest}, - BlockBackend, -}; +use sc_client_api::{blockchain::HeaderBackend, BlockBackend}; use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_blockchain::{HeaderMetadata, TreeRoute}; use sp_core::traits::SpawnEssentialNamed; use sp_runtime::{ generic::BlockId, - traits::{self, Block as BlockT, BlockIdTo, Hash as HashT, Header as HeaderT}, + traits::{self, Block as BlockT, BlockIdTo}, transaction_validity::{TransactionSource, TransactionValidity}, }; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; @@ -64,6 +61,7 @@ fn spawn_validation_pool_task( ) { spawner.spawn_essential_blocking( name, + Some("transaction-pool"), async move { loop { let task = receiver.lock().await.next().await; @@ -114,8 +112,11 @@ impl FullChainApi { impl graph::ChainApi for FullChainApi where Block: BlockT, - Client: - ProvideRuntimeApi + BlockBackend + BlockIdTo + HeaderBackend, + Client: ProvideRuntimeApi + + BlockBackend + + BlockIdTo + + HeaderBackend + + HeaderMetadata, Client: Send + Sync + 'static, Client::Api: TaggedTransactionQueue, { @@ -125,8 +126,8 @@ where Pin> + Send>>; type BodyFuture = Ready::Extrinsic>>>>; - fn block_body(&self, id: &BlockId) -> Self::BodyFuture { - ready(self.client.block_body(&id).map_err(|e| error::Error::from(e))) + fn block_body(&self, hash: Block::Hash) -> Self::BodyFuture { + ready(self.client.block_body(hash).map_err(error::Error::from)) } fn validate_transaction( @@ -137,7 +138,7 @@ where ) -> Self::ValidationFuture { let (tx, rx) = oneshot::channel(); let client = self.client.clone(); - let at = at.clone(); + let at = *at; let validation_pool = self.validation_pool.clone(); let metrics = self.metrics.clone(); @@ -170,18 +171,14 @@ where &self, at: &BlockId, ) -> error::Result>> { - self.client - .to_number(at) - .map_err(|e| Error::BlockIdConversion(format!("{:?}", e))) + self.client.to_number(at).map_err(|e| Error::BlockIdConversion(e.to_string())) } fn block_id_to_hash( &self, at: &BlockId, ) -> error::Result>> { - self.client - .to_hash(at) - .map_err(|e| Error::BlockIdConversion(format!("{:?}", e))) + self.client.to_hash(at).map_err(|e| Error::BlockIdConversion(e.to_string())) } fn hash_and_length( @@ -197,6 +194,14 @@ where ) -> Result::Header>, Self::Error> { self.client.header(*at).map_err(Into::into) } + + fn tree_route( + &self, + from: ::Hash, + to: ::Hash, + ) -> Result, Self::Error> { + sp_blockchain::tree_route::(&*self.client, from, to).map_err(Into::into) + } } /// Helper function to validate a transaction using a full chain API. @@ -209,8 +214,11 @@ fn validate_transaction_blocking( ) -> error::Result where Block: BlockT, - Client: - ProvideRuntimeApi + BlockBackend + BlockIdTo + HeaderBackend, + Client: ProvideRuntimeApi + + BlockBackend + + BlockIdTo + + HeaderBackend + + HeaderMetadata, Client: Send + Sync + 'static, Client::Api: TaggedTransactionQueue, { @@ -219,7 +227,7 @@ where let runtime_api = client.runtime_api(); let api_version = sp_tracing::within_span! { sp_tracing::Level::TRACE, "check_version"; runtime_api - .api_version::>(&at) + .api_version::>(at) .map_err(|e| Error::RuntimeApi(e.to_string()))? .ok_or_else(|| Error::RuntimeApi( format!("Could not find `TaggedTransactionQueue` api for block `{:?}`.", at) @@ -227,7 +235,7 @@ where }?; let block_hash = client.to_hash(at) - .map_err(|e| Error::RuntimeApi(format!("{:?}", e)))? + .map_err(|e| Error::RuntimeApi(e.to_string()))? .ok_or_else(|| Error::RuntimeApi(format!("Could not get hash for block `{:?}`.", at)))?; use sp_api::Core; @@ -236,11 +244,11 @@ where sp_tracing::Level::TRACE, "runtime::validate_transaction"; { if api_version >= 3 { - runtime_api.validate_transaction(&at, source, uxt, block_hash) + runtime_api.validate_transaction(at, source, uxt, block_hash) .map_err(|e| Error::RuntimeApi(e.to_string())) } else { let block_number = client.to_number(at) - .map_err(|e| Error::RuntimeApi(format!("{:?}", e)))? + .map_err(|e| Error::RuntimeApi(e.to_string()))? .ok_or_else(|| Error::RuntimeApi(format!("Could not get number for block `{:?}`.", at)) )?; @@ -256,11 +264,11 @@ where if api_version == 2 { #[allow(deprecated)] // old validate_transaction - runtime_api.validate_transaction_before_version_3(&at, source, uxt) + runtime_api.validate_transaction_before_version_3(at, source, uxt) .map_err(|e| Error::RuntimeApi(e.to_string())) } else { #[allow(deprecated)] // old validate_transaction - runtime_api.validate_transaction_before_version_2(&at, uxt) + runtime_api.validate_transaction_before_version_2(at, uxt) .map_err(|e| Error::RuntimeApi(e.to_string())) } } @@ -271,8 +279,11 @@ where impl FullChainApi where Block: BlockT, - Client: - ProvideRuntimeApi + BlockBackend + BlockIdTo + HeaderBackend, + Client: ProvideRuntimeApi + + BlockBackend + + BlockIdTo + + HeaderBackend + + HeaderMetadata, Client: Send + Sync + 'static, Client::Api: TaggedTransactionQueue, { @@ -289,127 +300,3 @@ where validate_transaction_blocking(&*self.client, at, source, uxt) } } - -/// The transaction pool logic for light client. -pub struct LightChainApi { - client: Arc, - fetcher: Arc, - _phantom: PhantomData, -} - -impl LightChainApi { - /// Create new transaction pool logic. - pub fn new(client: Arc, fetcher: Arc) -> Self { - LightChainApi { client, fetcher, _phantom: Default::default() } - } -} - -impl graph::ChainApi for LightChainApi -where - Block: BlockT, - Client: HeaderBackend + 'static, - F: Fetcher + 'static, -{ - type Block = Block; - type Error = error::Error; - type ValidationFuture = - Box> + Send + Unpin>; - type BodyFuture = Pin< - Box< - dyn Future::Extrinsic>>>> - + Send, - >, - >; - - fn validate_transaction( - &self, - at: &BlockId, - source: TransactionSource, - uxt: graph::ExtrinsicFor, - ) -> Self::ValidationFuture { - let header_hash = self.client.expect_block_hash_from_id(at); - let header_and_hash = header_hash.and_then(|header_hash| { - self.client - .expect_header(BlockId::Hash(header_hash)) - .map(|header| (header_hash, header)) - }); - let (block, header) = match header_and_hash { - Ok((header_hash, header)) => (header_hash, header), - Err(err) => return Box::new(ready(Err(err.into()))), - }; - let remote_validation_request = self.fetcher.remote_call(RemoteCallRequest { - block, - header, - method: "TaggedTransactionQueue_validate_transaction".into(), - call_data: (source, uxt, block).encode(), - retry_count: None, - }); - let remote_validation_request = remote_validation_request.then(move |result| { - let result: error::Result = - result.map_err(Into::into).and_then(|result| { - Decode::decode(&mut &result[..]).map_err(|e| { - Error::RuntimeApi(format!("Error decoding tx validation result: {:?}", e)) - }) - }); - ready(result) - }); - - Box::new(remote_validation_request) - } - - fn block_id_to_number( - &self, - at: &BlockId, - ) -> error::Result>> { - Ok(self.client.block_number_from_id(at)?) - } - - fn block_id_to_hash( - &self, - at: &BlockId, - ) -> error::Result>> { - Ok(self.client.block_hash_from_id(at)?) - } - - fn hash_and_length( - &self, - ex: &graph::ExtrinsicFor, - ) -> (graph::ExtrinsicHash, usize) { - ex.using_encoded(|x| (<::Hashing as HashT>::hash(x), x.len())) - } - - fn block_body(&self, id: &BlockId) -> Self::BodyFuture { - let header = self - .client - .header(*id) - .and_then(|h| h.ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", id)))); - let header = match header { - Ok(header) => header, - Err(err) => { - log::warn!(target: "txpool", "Failed to query header: {:?}", err); - return Box::pin(ready(Ok(None))) - }, - }; - - let fetcher = self.fetcher.clone(); - async move { - let transactions = fetcher - .remote_body(RemoteBodyRequest { header, retry_count: None }) - .await - .unwrap_or_else(|e| { - log::warn!(target: "txpool", "Failed to fetch block body: {:?}", e); - Vec::new() - }); - - Ok(Some(transactions)) - } - .boxed() - } - - fn block_header( - &self, - at: &BlockId, - ) -> Result::Header>, Self::Error> { - self.client.header(*at).map_err(Into::into) - } -} diff --git a/client/transaction-pool/src/enactment_state.rs b/client/transaction-pool/src/enactment_state.rs new file mode 100644 index 0000000000000..6aac98641cf85 --- /dev/null +++ b/client/transaction-pool/src/enactment_state.rs @@ -0,0 +1,613 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Substrate transaction pool implementation. + +use sc_transaction_pool_api::ChainEvent; +use sp_blockchain::TreeRoute; +use sp_runtime::traits::Block as BlockT; + +/// Helper struct for keeping track of the current state of processed new best +/// block and finalized events. The main purpose of keeping track of this state +/// is to figure out if a transaction pool enactment is needed or not. +/// +/// Given the following chain: +/// +/// B1-C1-D1-E1 +/// / +/// A +/// \ +/// B2-C2-D2-E2 +/// +/// Some scenarios and expected behavior for sequence of `NewBestBlock` (`nbb`) and `Finalized` +/// (`f`) events: +/// +/// - `nbb(C1)`, `f(C1)` -> false (enactment was already performed in `nbb(C1))` +/// - `f(C1)`, `nbb(C1)` -> false (enactment was already performed in `f(C1))` +/// - `f(C1)`, `nbb(D2)` -> false (enactment was already performed in `f(C1)`, +/// we should not retract finalized block) +/// - `f(C1)`, `f(C2)`, `nbb(C1)` -> false +/// - `nbb(C1)`, `nbb(C2)` -> true (switching fork is OK) +/// - `nbb(B1)`, `nbb(B2)` -> true +/// - `nbb(B1)`, `nbb(C1)`, `f(C1)` -> false (enactment was already performed in `nbb(B1)`) +/// - `nbb(C1)`, `f(B1)` -> false (enactment was already performed in `nbb(B2)`) +pub struct EnactmentState +where + Block: BlockT, +{ + recent_best_block: Block::Hash, + recent_finalized_block: Block::Hash, +} + +impl EnactmentState +where + Block: BlockT, +{ + /// Returns a new `EnactmentState` initialized with the given parameters. + pub fn new(recent_best_block: Block::Hash, recent_finalized_block: Block::Hash) -> Self { + EnactmentState { recent_best_block, recent_finalized_block } + } + + /// Returns the recently finalized block. + pub fn recent_finalized_block(&self) -> Block::Hash { + self.recent_finalized_block + } + + /// Updates the state according to the given `ChainEvent`, returning + /// `Some(tree_route)` with a tree route including the blocks that need to + /// be enacted/retracted. If no enactment is needed then `None` is returned. + pub fn update( + &mut self, + event: &ChainEvent, + tree_route: &F, + ) -> Result>, String> + where + F: Fn(Block::Hash, Block::Hash) -> Result, String>, + { + let (new_hash, finalized) = match event { + ChainEvent::NewBestBlock { hash, .. } => (*hash, false), + ChainEvent::Finalized { hash, .. } => (*hash, true), + }; + + // block was already finalized + if self.recent_finalized_block == new_hash { + log::debug!(target: "txpool", "handle_enactment: block already finalized"); + return Ok(None) + } + + // compute actual tree route from best_block to notified block, and use + // it instead of tree_route provided with event + let tree_route = tree_route(self.recent_best_block, new_hash)?; + + log::debug!( + target: "txpool", + "resolve hash:{:?} finalized:{:?} tree_route:{:?} best_block:{:?} finalized_block:{:?}", + new_hash, finalized, tree_route, self.recent_best_block, self.recent_finalized_block + ); + + // check if recently finalized block is on retracted path. this could be + // happening if we first received a finalization event and then a new + // best event for some old stale best head. + if tree_route.retracted().iter().any(|x| x.hash == self.recent_finalized_block) { + log::debug!( + target: "txpool", + "Recently finalized block {} would be retracted by ChainEvent {}, skipping", + self.recent_finalized_block, new_hash + ); + return Ok(None) + } + + if finalized { + self.recent_finalized_block = new_hash; + + // if there are no enacted blocks in best_block -> hash tree_route, + // it means that block being finalized was already enacted (this + // case also covers best_block == new_hash), recent_best_block + // remains valid. + if tree_route.enacted().is_empty() { + log::trace!( + target: "txpool", + "handle_enactment: no newly enacted blocks since recent best block" + ); + return Ok(None) + } + + // otherwise enacted finalized block becomes best block... + } + + self.recent_best_block = new_hash; + + Ok(Some(tree_route)) + } + + /// Forces update of the state according to the given `ChainEvent`. Intended to be used as a + /// fallback when tree_route cannot be computed. + pub fn force_update(&mut self, event: &ChainEvent) { + match event { + ChainEvent::NewBestBlock { hash, .. } => self.recent_best_block = *hash, + ChainEvent::Finalized { hash, .. } => self.recent_finalized_block = *hash, + }; + log::debug!(target: "txpool", "forced update: {:?}, {:?}", self.recent_best_block, self.recent_finalized_block); + } +} + +#[cfg(test)] +mod enactment_state_tests { + use super::EnactmentState; + use sc_transaction_pool_api::ChainEvent; + use sp_blockchain::{HashAndNumber, TreeRoute}; + use std::sync::Arc; + use substrate_test_runtime_client::runtime::{Block, Hash}; + + // some helpers for convenient blocks' hash naming + fn a() -> HashAndNumber { + HashAndNumber { number: 1, hash: Hash::from([0xAA; 32]) } + } + fn b1() -> HashAndNumber { + HashAndNumber { number: 2, hash: Hash::from([0xB1; 32]) } + } + fn c1() -> HashAndNumber { + HashAndNumber { number: 3, hash: Hash::from([0xC1; 32]) } + } + fn d1() -> HashAndNumber { + HashAndNumber { number: 4, hash: Hash::from([0xD1; 32]) } + } + fn e1() -> HashAndNumber { + HashAndNumber { number: 5, hash: Hash::from([0xE1; 32]) } + } + fn b2() -> HashAndNumber { + HashAndNumber { number: 2, hash: Hash::from([0xB2; 32]) } + } + fn c2() -> HashAndNumber { + HashAndNumber { number: 3, hash: Hash::from([0xC2; 32]) } + } + fn d2() -> HashAndNumber { + HashAndNumber { number: 4, hash: Hash::from([0xD2; 32]) } + } + fn e2() -> HashAndNumber { + HashAndNumber { number: 5, hash: Hash::from([0xE2; 32]) } + } + + /// mock tree_route computing function for simple two-forks chain + fn tree_route(from: Hash, to: Hash) -> Result, String> { + let chain = vec![e1(), d1(), c1(), b1(), a(), b2(), c2(), d2(), e2()]; + let pivot = 4_usize; + + let from = chain + .iter() + .position(|bn| bn.hash == from) + .ok_or("existing block should be given")?; + let to = chain + .iter() + .position(|bn| bn.hash == to) + .ok_or("existing block should be given")?; + + // B1-C1-D1-E1 + // / + // A + // \ + // B2-C2-D2-E2 + // + // [E1 D1 C1 B1 A B2 C2 D2 E2] + + let vec: Vec> = if from < to { + chain.into_iter().skip(from).take(to - from + 1).collect() + } else { + chain.into_iter().skip(to).take(from - to + 1).rev().collect() + }; + + let pivot = if from <= pivot && to <= pivot { + if from < to { + to - from + } else { + 0 + } + } else if from >= pivot && to >= pivot { + if from < to { + 0 + } else { + from - to + } + } else { + if from < to { + pivot - from + } else { + from - pivot + } + }; + + TreeRoute::new(vec, pivot) + } + + mod mock_tree_route_tests { + use super::*; + + /// asserts that tree routes are equal + fn assert_treeroute_eq( + expected: Result, String>, + result: Result, String>, + ) { + let expected = expected.unwrap(); + let result = result.unwrap(); + assert_eq!(result.common_block().hash, expected.common_block().hash); + assert_eq!(result.enacted().len(), expected.enacted().len()); + assert_eq!(result.retracted().len(), expected.retracted().len()); + assert!(result + .enacted() + .iter() + .zip(expected.enacted().iter()) + .all(|(a, b)| a.hash == b.hash)); + assert!(result + .retracted() + .iter() + .zip(expected.retracted().iter()) + .all(|(a, b)| a.hash == b.hash)); + } + + // some tests for mock tree_route function + + #[test] + fn tree_route_mock_test_01() { + let result = tree_route(b1().hash, a().hash); + let expected = TreeRoute::new(vec![b1(), a()], 1); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_02() { + let result = tree_route(a().hash, b1().hash); + let expected = TreeRoute::new(vec![a(), b1()], 0); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_03() { + let result = tree_route(a().hash, c2().hash); + let expected = TreeRoute::new(vec![a(), b2(), c2()], 0); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_04() { + let result = tree_route(e2().hash, a().hash); + let expected = TreeRoute::new(vec![e2(), d2(), c2(), b2(), a()], 4); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_05() { + let result = tree_route(d1().hash, b1().hash); + let expected = TreeRoute::new(vec![d1(), c1(), b1()], 2); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_06() { + let result = tree_route(d2().hash, b2().hash); + let expected = TreeRoute::new(vec![d2(), c2(), b2()], 2); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_07() { + let result = tree_route(b1().hash, d1().hash); + let expected = TreeRoute::new(vec![b1(), c1(), d1()], 0); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_08() { + let result = tree_route(b2().hash, d2().hash); + let expected = TreeRoute::new(vec![b2(), c2(), d2()], 0); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_09() { + let result = tree_route(e2().hash, e1().hash); + let expected = + TreeRoute::new(vec![e2(), d2(), c2(), b2(), a(), b1(), c1(), d1(), e1()], 4); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_10() { + let result = tree_route(e1().hash, e2().hash); + let expected = + TreeRoute::new(vec![e1(), d1(), c1(), b1(), a(), b2(), c2(), d2(), e2()], 4); + assert_treeroute_eq(result, expected); + } + #[test] + fn tree_route_mock_test_11() { + let result = tree_route(b1().hash, c2().hash); + let expected = TreeRoute::new(vec![b1(), a(), b2(), c2()], 1); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_12() { + let result = tree_route(d2().hash, b1().hash); + let expected = TreeRoute::new(vec![d2(), c2(), b2(), a(), b1()], 3); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_13() { + let result = tree_route(c2().hash, e1().hash); + let expected = TreeRoute::new(vec![c2(), b2(), a(), b1(), c1(), d1(), e1()], 2); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_14() { + let result = tree_route(b1().hash, b1().hash); + let expected = TreeRoute::new(vec![b1()], 0); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_15() { + let result = tree_route(b2().hash, b2().hash); + let expected = TreeRoute::new(vec![b2()], 0); + assert_treeroute_eq(result, expected); + } + + #[test] + fn tree_route_mock_test_16() { + let result = tree_route(a().hash, a().hash); + let expected = TreeRoute::new(vec![a()], 0); + assert_treeroute_eq(result, expected); + } + } + + fn trigger_new_best_block( + state: &mut EnactmentState, + from: HashAndNumber, + acted_on: HashAndNumber, + ) -> bool { + let (from, acted_on) = (from.hash, acted_on.hash); + + let event_tree_route = tree_route(from, acted_on).expect("Tree route exists"); + + state + .update( + &ChainEvent::NewBestBlock { + hash: acted_on, + tree_route: Some(Arc::new(event_tree_route)), + }, + &tree_route, + ) + .unwrap() + .is_some() + } + + fn trigger_finalized( + state: &mut EnactmentState, + from: HashAndNumber, + acted_on: HashAndNumber, + ) -> bool { + let (from, acted_on) = (from.hash, acted_on.hash); + + let v = tree_route(from, acted_on) + .expect("Tree route exists") + .enacted() + .iter() + .map(|h| h.hash) + .collect::>(); + + state + .update(&ChainEvent::Finalized { hash: acted_on, tree_route: v.into() }, &tree_route) + .unwrap() + .is_some() + } + + fn assert_es_eq( + es: &EnactmentState, + expected_best_block: HashAndNumber, + expected_finalized_block: HashAndNumber, + ) { + assert_eq!(es.recent_best_block, expected_best_block.hash); + assert_eq!(es.recent_finalized_block, expected_finalized_block.hash); + } + + #[test] + fn test_enactment_helper() { + sp_tracing::try_init_simple(); + let mut es = EnactmentState::new(a().hash, a().hash); + + // B1-C1-D1-E1 + // / + // A + // \ + // B2-C2-D2-E2 + + let result = trigger_new_best_block(&mut es, a(), d1()); + assert!(result); + assert_es_eq(&es, d1(), a()); + + let result = trigger_new_best_block(&mut es, d1(), e1()); + assert!(result); + assert_es_eq(&es, e1(), a()); + + let result = trigger_finalized(&mut es, a(), d2()); + assert!(result); + assert_es_eq(&es, d2(), d2()); + + let result = trigger_new_best_block(&mut es, d2(), e1()); + assert_eq!(result, false); + assert_es_eq(&es, d2(), d2()); + + let result = trigger_finalized(&mut es, a(), b2()); + assert_eq!(result, false); + assert_es_eq(&es, d2(), d2()); + + let result = trigger_finalized(&mut es, a(), b1()); + assert_eq!(result, false); + assert_es_eq(&es, d2(), d2()); + + let result = trigger_new_best_block(&mut es, a(), d2()); + assert_eq!(result, false); + assert_es_eq(&es, d2(), d2()); + + let result = trigger_finalized(&mut es, a(), d2()); + assert_eq!(result, false); + assert_es_eq(&es, d2(), d2()); + + let result = trigger_new_best_block(&mut es, a(), c2()); + assert_eq!(result, false); + assert_es_eq(&es, d2(), d2()); + + let result = trigger_new_best_block(&mut es, a(), c1()); + assert_eq!(result, false); + assert_es_eq(&es, d2(), d2()); + + let result = trigger_new_best_block(&mut es, d2(), e2()); + assert!(result); + assert_es_eq(&es, e2(), d2()); + + let result = trigger_finalized(&mut es, d2(), e2()); + assert_eq!(result, false); + assert_es_eq(&es, e2(), e2()); + } + + #[test] + fn test_enactment_helper_2() { + sp_tracing::try_init_simple(); + let mut es = EnactmentState::new(a().hash, a().hash); + + // A-B1-C1-D1-E1 + + let result = trigger_new_best_block(&mut es, a(), b1()); + assert!(result); + assert_es_eq(&es, b1(), a()); + + let result = trigger_new_best_block(&mut es, b1(), c1()); + assert!(result); + assert_es_eq(&es, c1(), a()); + + let result = trigger_new_best_block(&mut es, c1(), d1()); + assert!(result); + assert_es_eq(&es, d1(), a()); + + let result = trigger_new_best_block(&mut es, d1(), e1()); + assert!(result); + assert_es_eq(&es, e1(), a()); + + let result = trigger_finalized(&mut es, a(), c1()); + assert_eq!(result, false); + assert_es_eq(&es, e1(), c1()); + + let result = trigger_finalized(&mut es, c1(), e1()); + assert_eq!(result, false); + assert_es_eq(&es, e1(), e1()); + } + + #[test] + fn test_enactment_helper_3() { + sp_tracing::try_init_simple(); + let mut es = EnactmentState::new(a().hash, a().hash); + + // A-B1-C1-D1-E1 + + let result = trigger_new_best_block(&mut es, a(), e1()); + assert!(result); + assert_es_eq(&es, e1(), a()); + + let result = trigger_finalized(&mut es, a(), b1()); + assert_eq!(result, false); + assert_es_eq(&es, e1(), b1()); + } + + #[test] + fn test_enactment_helper_4() { + sp_tracing::try_init_simple(); + let mut es = EnactmentState::new(a().hash, a().hash); + + // A-B1-C1-D1-E1 + + let result = trigger_finalized(&mut es, a(), e1()); + assert!(result); + assert_es_eq(&es, e1(), e1()); + + let result = trigger_finalized(&mut es, e1(), b1()); + assert_eq!(result, false); + assert_es_eq(&es, e1(), e1()); + } + + #[test] + fn test_enactment_helper_5() { + sp_tracing::try_init_simple(); + let mut es = EnactmentState::new(a().hash, a().hash); + + // B1-C1-D1-E1 + // / + // A + // \ + // B2-C2-D2-E2 + + let result = trigger_finalized(&mut es, a(), e1()); + assert!(result); + assert_es_eq(&es, e1(), e1()); + + let result = trigger_finalized(&mut es, e1(), e2()); + assert_eq!(result, false); + assert_es_eq(&es, e1(), e1()); + } + + #[test] + fn test_enactment_helper_6() { + sp_tracing::try_init_simple(); + let mut es = EnactmentState::new(a().hash, a().hash); + + // A-B1-C1-D1-E1 + + let result = trigger_new_best_block(&mut es, a(), b1()); + assert!(result); + assert_es_eq(&es, b1(), a()); + + let result = trigger_finalized(&mut es, a(), d1()); + assert!(result); + assert_es_eq(&es, d1(), d1()); + + let result = trigger_new_best_block(&mut es, a(), e1()); + assert!(result); + assert_es_eq(&es, e1(), d1()); + + let result = trigger_new_best_block(&mut es, a(), c1()); + assert_eq!(result, false); + assert_es_eq(&es, e1(), d1()); + } + + #[test] + fn test_enactment_forced_update_best_block() { + sp_tracing::try_init_simple(); + let mut es = EnactmentState::new(a().hash, a().hash); + + es.force_update(&ChainEvent::NewBestBlock { hash: b1().hash, tree_route: None }); + assert_es_eq(&es, b1(), a()); + } + + #[test] + fn test_enactment_forced_update_finalize() { + sp_tracing::try_init_simple(); + let mut es = EnactmentState::new(a().hash, a().hash); + + es.force_update(&ChainEvent::Finalized { hash: b1().hash, tree_route: Arc::from([]) }); + assert_es_eq(&es, a(), b1()); + } +} diff --git a/client/transaction-pool/src/error.rs b/client/transaction-pool/src/error.rs index b14e0569f0830..a4cf756481dc3 100644 --- a/client/transaction-pool/src/error.rs +++ b/client/transaction-pool/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -27,10 +27,10 @@ pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { - #[error("Transaction pool error")] + #[error("Transaction pool error: {0}")] Pool(#[from] TxPoolError), - #[error("Blockchain error")] + #[error("Blockchain error: {0}")] Blockchain(#[from] sp_blockchain::Error), #[error("Block conversion error: {0}")] diff --git a/client/transaction-pool/src/graph/base_pool.rs b/client/transaction-pool/src/graph/base_pool.rs index 890a87e82929d..67580d698b8c1 100644 --- a/client/transaction-pool/src/graph/base_pool.rs +++ b/client/transaction-pool/src/graph/base_pool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ //! //! For a more full-featured pool, have a look at the `pool` module. -use std::{collections::HashSet, fmt, hash, sync::Arc}; +use std::{cmp::Ordering, collections::HashSet, fmt, hash, sync::Arc}; use log::{debug, trace, warn}; use sc_transaction_pool_api::{error, InPoolTransaction, PoolStatus}; @@ -36,7 +36,7 @@ use sp_runtime::{ use super::{ future::{FutureTransactions, WaitingTransaction}, - ready::ReadyTransactions, + ready::{BestIterator, ReadyTransactions, TransactionRef}, }; /// Successful import result. @@ -84,7 +84,7 @@ pub struct PruneStatus { /// Immutable transaction #[cfg_attr(test, derive(Clone))] -#[derive(PartialEq, Eq, parity_util_mem::MallocSizeOf)] +#[derive(PartialEq, Eq)] pub struct Transaction { /// Raw extrinsic representing that transaction. pub data: Extrinsic, @@ -207,7 +207,7 @@ const RECENTLY_PRUNED_TAGS: usize = 2; /// as-is for the second time will fail or produce unwanted results. /// Most likely it is required to revalidate them and recompute set of /// required tags. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct BasePool { reject_future_transactions: bool, future: FutureTransactions, @@ -355,7 +355,7 @@ impl BasePool impl Iterator>> { + pub fn ready(&self) -> BestIterator { self.ready.get() } @@ -384,8 +384,8 @@ impl BasePool BasePool, _>(|worst, current| { let transaction = ¤t.transaction; - match minimal { - None => Some(transaction.clone()), - Some(ref tx) if tx.insertion_id > transaction.insertion_id => - Some(transaction.clone()), - other => other, - } + worst + .map(|worst| { + // Here we don't use `TransactionRef`'s ordering implementation because + // while it prefers priority like need here, it also prefers older + // transactions for inclusion purposes and limit enforcement needs to prefer + // newer transactions instead and drop the older ones. + match worst.transaction.priority.cmp(&transaction.transaction.priority) { + Ordering::Less => worst, + Ordering::Equal => + if worst.insertion_id > transaction.insertion_id { + transaction.clone() + } else { + worst + }, + Ordering::Greater => transaction.clone(), + } + }) + .or_else(|| Some(transaction.clone())) }); - if let Some(minimal) = minimal { - removed.append(&mut self.remove_subtree(&[minimal.transaction.hash.clone()])) + if let Some(worst) = worst { + removed.append(&mut self.remove_subtree(&[worst.transaction.hash.clone()])) } else { break } @@ -414,14 +426,14 @@ impl BasePool Some(current.clone()), Some(ref tx) if tx.imported_at > current.imported_at => Some(current.clone()), other => other, }); - if let Some(minimal) = minimal { - removed.append(&mut self.remove_subtree(&[minimal.transaction.hash.clone()])) + if let Some(worst) = worst { + removed.append(&mut self.remove_subtree(&[worst.transaction.hash.clone()])) } else { break } @@ -784,27 +796,6 @@ mod tests { } } - #[test] - fn can_track_heap_size() { - let mut pool = pool(); - pool.import(Transaction { - data: vec![5u8; 1024], - hash: 5, - provides: vec![vec![0], vec![4]], - ..DEFAULT_TX.clone() - }) - .expect("import 1 should be ok"); - pool.import(Transaction { - data: vec![3u8; 1024], - hash: 7, - provides: vec![vec![2], vec![7]], - ..DEFAULT_TX.clone() - }) - .expect("import 2 should be ok"); - - assert!(parity_util_mem::malloc_size(&pool) > 5000); - } - #[test] fn should_remove_invalid_transactions() { // given diff --git a/client/transaction-pool/src/graph/future.rs b/client/transaction-pool/src/graph/future.rs index 6ed1f1014304f..d23b5f2b6e1f1 100644 --- a/client/transaction-pool/src/graph/future.rs +++ b/client/transaction-pool/src/graph/future.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -28,7 +28,6 @@ use std::time::Instant; use super::base_pool::Transaction; -#[derive(parity_util_mem::MallocSizeOf)] /// Transaction with partially satisfied dependencies. pub struct WaitingTransaction { /// Transaction details. @@ -108,7 +107,7 @@ impl WaitingTransaction { /// /// Contains transactions that are still awaiting for some other transactions that /// could provide a tag that they require. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct FutureTransactions { /// tags that are not yet provided by any transaction and we await for them wanted_tags: HashMap>, @@ -251,33 +250,3 @@ impl FutureTransactions { self.waiting.values().fold(0, |acc, tx| acc + tx.transaction.bytes) } } - -#[cfg(test)] -mod tests { - use super::*; - use sp_runtime::transaction_validity::TransactionSource; - - #[test] - fn can_track_heap_size() { - let mut future = FutureTransactions::default(); - future.import(WaitingTransaction { - transaction: Transaction { - data: vec![0u8; 1024], - bytes: 1, - hash: 1, - priority: 1, - valid_till: 2, - requires: vec![vec![1], vec![2]], - provides: vec![vec![3], vec![4]], - propagate: true, - source: TransactionSource::External, - } - .into(), - missing_tags: vec![vec![1u8], vec![2u8]].into_iter().collect(), - imported_at: std::time::Instant::now(), - }); - - // data is at least 1024! - assert!(parity_util_mem::malloc_size(&future) > 1024); - } -} diff --git a/client/transaction-pool/src/graph/listener.rs b/client/transaction-pool/src/graph/listener.rs index b8149018f7836..776749abf2d5d 100644 --- a/client/transaction-pool/src/graph/listener.rs +++ b/client/transaction-pool/src/graph/listener.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -104,13 +104,18 @@ impl Listener { /// Transaction was pruned from the pool. pub fn pruned(&mut self, block_hash: BlockHash, tx: &H) { debug!(target: "txpool", "[{:?}] Pruned at {:?}", tx, block_hash); - self.fire(tx, |s| s.in_block(block_hash)); - self.finality_watchers.entry(block_hash).or_insert(vec![]).push(tx.clone()); + // Get the transactions included in the given block hash. + let txs = self.finality_watchers.entry(block_hash).or_insert(vec![]); + txs.push(tx.clone()); + // Current transaction is the last one included. + let tx_index = txs.len() - 1; + + self.fire(tx, |watcher| watcher.in_block(block_hash, tx_index)); while self.finality_watchers.len() > MAX_FINALITY_WATCHERS { if let Some((hash, txs)) = self.finality_watchers.pop_front() { for tx in txs { - self.fire(&tx, |s| s.finality_timeout(hash)); + self.fire(&tx, |watcher| watcher.finality_timeout(hash)); } } } @@ -120,7 +125,7 @@ impl Listener { pub fn retracted(&mut self, block_hash: BlockHash) { if let Some(hashes) = self.finality_watchers.remove(&block_hash) { for hash in hashes { - self.fire(&hash, |s| s.retracted(block_hash)) + self.fire(&hash, |watcher| watcher.retracted(block_hash)) } } } @@ -128,9 +133,9 @@ impl Listener { /// Notify all watchers that transactions have been finalized pub fn finalized(&mut self, block_hash: BlockHash) { if let Some(hashes) = self.finality_watchers.remove(&block_hash) { - for hash in hashes { + for (tx_index, hash) in hashes.into_iter().enumerate() { log::debug!(target: "txpool", "[{:?}] Sent finalization event (block {:?})", hash, block_hash); - self.fire(&hash, |s| s.finalized(block_hash)) + self.fire(&hash, |watcher| watcher.finalized(block_hash, tx_index)) } } } diff --git a/client/transaction-pool/src/graph/mod.rs b/client/transaction-pool/src/graph/mod.rs index 3ecfb8fe68c60..42812897a954b 100644 --- a/client/transaction-pool/src/graph/mod.rs +++ b/client/transaction-pool/src/graph/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/transaction-pool/src/graph/pool.rs b/client/transaction-pool/src/graph/pool.rs index 2af5a8a19a5a9..9c747ade1229a 100644 --- a/client/transaction-pool/src/graph/pool.rs +++ b/client/transaction-pool/src/graph/pool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,10 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, sync::Arc, time::Duration}; use futures::{channel::mpsc::Receiver, Future}; use sc_transaction_pool_api::error; +use sp_blockchain::TreeRoute; use sp_runtime::{ generic::BlockId, traits::{self, Block as BlockT, SaturatedConversion}, @@ -89,14 +90,21 @@ pub trait ChainApi: Send + Sync { /// Returns hash and encoding length of the extrinsic. fn hash_and_length(&self, uxt: &ExtrinsicFor) -> (ExtrinsicHash, usize); - /// Returns a block body given the block id. - fn block_body(&self, at: &BlockId) -> Self::BodyFuture; + /// Returns a block body given the block. + fn block_body(&self, at: ::Hash) -> Self::BodyFuture; /// Returns a block header given the block id. fn block_header( &self, at: &BlockId, ) -> Result::Header>, Self::Error>; + + /// Compute a tree-route between two blocks. See [`TreeRoute`] for more details. + fn tree_route( + &self, + from: ::Hash, + to: ::Hash, + ) -> Result, Self::Error>; } /// Pool configuration options. @@ -108,6 +116,8 @@ pub struct Options { pub future: base::Limit, /// Reject future transactions. pub reject_future_transactions: bool, + /// How long the extrinsic is banned for. + pub ban_time: Duration, } impl Default for Options { @@ -116,6 +126,7 @@ impl Default for Options { ready: base::Limit { count: 8192, total_bytes: 20 * 1024 * 1024 }, future: base::Limit { count: 512, total_bytes: 1 * 1024 * 1024 }, reject_future_transactions: false, + ban_time: Duration::from_secs(60 * 30), } } } @@ -133,15 +144,6 @@ pub struct Pool { validated_pool: Arc>, } -impl parity_util_mem::MallocSizeOf for Pool -where - ExtrinsicFor: parity_util_mem::MallocSizeOf, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - self.validated_pool.size_of(ops) - } -} - impl Pool { /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { @@ -157,7 +159,7 @@ impl Pool { ) -> Result, B::Error>>, B::Error> { let xts = xts.into_iter().map(|xt| (source, xt)); let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::Yes).await?; - Ok(self.validated_pool.submit(validated_transactions.into_iter().map(|(_, tx)| tx))) + Ok(self.validated_pool.submit(validated_transactions.into_values())) } /// Resubmit the given extrinsics to the pool. @@ -171,7 +173,7 @@ impl Pool { ) -> Result, B::Error>>, B::Error> { let xts = xts.into_iter().map(|xt| (source, xt)); let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::No).await?; - Ok(self.validated_pool.submit(validated_transactions.into_iter().map(|(_, tx)| tx))) + Ok(self.validated_pool.submit(validated_transactions.into_values())) } /// Imports one unverified extrinsic to the pool @@ -224,12 +226,8 @@ impl Pool { hashes: &[ExtrinsicHash], ) -> Result<(), B::Error> { // Get details of all extrinsics that are already in the pool - let in_pool_tags = self - .validated_pool - .extrinsics_tags(hashes) - .into_iter() - .filter_map(|x| x) - .flatten(); + let in_pool_tags = + self.validated_pool.extrinsics_tags(hashes).into_iter().flatten().flatten(); // Prune all transactions that provide given tags let prune_status = self.validated_pool.prune_tags(in_pool_tags)?; @@ -339,10 +337,10 @@ impl Pool { // And finally - submit reverified transactions back to the pool self.validated_pool.resubmit_pruned( - &at, + at, known_imported_hashes, pruned_hashes, - reverified_transactions.into_iter().map(|(_, xt)| xt).collect(), + reverified_transactions.into_values().collect(), ) } @@ -444,159 +442,16 @@ impl Clone for Pool { #[cfg(test)] mod tests { use super::{super::base_pool::Limit, *}; + use crate::tests::{pool, uxt, TestApi, INVALID_NONCE}; use assert_matches::assert_matches; - use codec::Encode; use futures::executor::block_on; use parking_lot::Mutex; use sc_transaction_pool_api::TransactionStatus; - use sp_runtime::{ - traits::Hash, - transaction_validity::{InvalidTransaction, TransactionSource, ValidTransaction}, - }; - use std::{ - collections::{HashMap, HashSet}, - time::Instant, - }; - use substrate_test_runtime::{AccountId, Block, Extrinsic, Hashing, Transfer, H256}; - - const INVALID_NONCE: u64 = 254; - const SOURCE: TransactionSource = TransactionSource::External; - - #[derive(Clone, Debug, Default)] - struct TestApi { - delay: Arc>>>, - invalidate: Arc>>, - clear_requirements: Arc>>, - add_requirements: Arc>>, - } + use sp_runtime::transaction_validity::TransactionSource; + use std::{collections::HashMap, time::Instant}; + use substrate_test_runtime::{AccountId, Extrinsic, Transfer, H256}; - impl ChainApi for TestApi { - type Block = Block; - type Error = error::Error; - type ValidationFuture = futures::future::Ready>; - type BodyFuture = futures::future::Ready>>>; - - /// Verify extrinsic at given block. - fn validate_transaction( - &self, - at: &BlockId, - _source: TransactionSource, - uxt: ExtrinsicFor, - ) -> Self::ValidationFuture { - let hash = self.hash_and_length(&uxt).0; - let block_number = self.block_id_to_number(at).unwrap().unwrap(); - - let res = match uxt { - Extrinsic::Transfer { transfer, .. } => { - let nonce = transfer.nonce; - - // This is used to control the test flow. - if nonce > 0 { - let opt = self.delay.lock().take(); - if let Some(delay) = opt { - if delay.recv().is_err() { - println!("Error waiting for delay!"); - } - } - } - - if self.invalidate.lock().contains(&hash) { - InvalidTransaction::Custom(0).into() - } else if nonce < block_number { - InvalidTransaction::Stale.into() - } else { - let mut transaction = ValidTransaction { - priority: 4, - requires: if nonce > block_number { - vec![vec![nonce as u8 - 1]] - } else { - vec![] - }, - provides: if nonce == INVALID_NONCE { - vec![] - } else { - vec![vec![nonce as u8]] - }, - longevity: 3, - propagate: true, - }; - - if self.clear_requirements.lock().contains(&hash) { - transaction.requires.clear(); - } - - if self.add_requirements.lock().contains(&hash) { - transaction.requires.push(vec![128]); - } - - Ok(transaction) - } - }, - Extrinsic::IncludeData(_) => Ok(ValidTransaction { - priority: 9001, - requires: vec![], - provides: vec![vec![42]], - longevity: 9001, - propagate: false, - }), - _ => unimplemented!(), - }; - - futures::future::ready(Ok(res)) - } - - /// Returns a block number given the block id. - fn block_id_to_number( - &self, - at: &BlockId, - ) -> Result>, Self::Error> { - Ok(match at { - BlockId::Number(num) => Some(*num), - BlockId::Hash(_) => None, - }) - } - - /// Returns a block hash given the block id. - fn block_id_to_hash( - &self, - at: &BlockId, - ) -> Result::Hash>, Self::Error> { - Ok(match at { - BlockId::Number(num) => Some(H256::from_low_u64_be(*num)).into(), - BlockId::Hash(_) => None, - }) - } - - /// Hash the extrinsic. - fn hash_and_length(&self, uxt: &ExtrinsicFor) -> (BlockHash, usize) { - let encoded = uxt.encode(); - let len = encoded.len(); - (Hashing::hash(&encoded), len) - } - - fn block_body(&self, _id: &BlockId) -> Self::BodyFuture { - futures::future::ready(Ok(None)) - } - - fn block_header( - &self, - _: &BlockId, - ) -> Result::Header>, Self::Error> { - Ok(None) - } - } - - fn uxt(transfer: Transfer) -> Extrinsic { - Extrinsic::Transfer { - transfer, - signature: Default::default(), - exhaust_resources_when_not_first: false, - } - } - - fn pool() -> Pool { - Pool::new(Default::default(), true.into(), TestApi::default().into()) - } + const SOURCE: TransactionSource = TransactionSource::External; #[test] fn should_validate_and_import_transaction() { @@ -632,7 +487,7 @@ mod tests { }); // when - pool.validated_pool.rotator().ban(&Instant::now(), vec![pool.hash_of(&uxt)]); + pool.validated_pool.ban(&Instant::now(), vec![pool.hash_of(&uxt)]); let res = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt)); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 0); @@ -763,9 +618,9 @@ mod tests { assert_eq!(pool.validated_pool().status().future, 0); assert_eq!(pool.validated_pool().status().ready, 0); // make sure they are temporarily banned as well - assert!(pool.validated_pool.rotator().is_banned(&hash1)); - assert!(pool.validated_pool.rotator().is_banned(&hash2)); - assert!(pool.validated_pool.rotator().is_banned(&hash3)); + assert!(pool.validated_pool.is_banned(&hash1)); + assert!(pool.validated_pool.is_banned(&hash2)); + assert!(pool.validated_pool.is_banned(&hash3)); } #[test] @@ -785,10 +640,10 @@ mod tests { .unwrap(); // when - block_on(pool.prune_tags(&BlockId::Number(1), vec![vec![0]], vec![hash1.clone()])).unwrap(); + block_on(pool.prune_tags(&BlockId::Number(1), vec![vec![0]], vec![hash1])).unwrap(); // then - assert!(pool.validated_pool.rotator().is_banned(&hash1)); + assert!(pool.validated_pool.is_banned(&hash1)); } #[test] @@ -828,8 +683,8 @@ mod tests { // then assert_eq!(pool.validated_pool().status().future, 1); - assert!(pool.validated_pool.rotator().is_banned(&hash1)); - assert!(!pool.validated_pool.rotator().is_banned(&hash2)); + assert!(pool.validated_pool.is_banned(&hash1)); + assert!(!pool.validated_pool.is_banned(&hash2)); } #[test] @@ -914,7 +769,7 @@ mod tests { assert_eq!(stream.next(), Some(TransactionStatus::Ready)); assert_eq!( stream.next(), - Some(TransactionStatus::InBlock(H256::from_low_u64_be(2).into())), + Some(TransactionStatus::InBlock((H256::from_low_u64_be(2).into(), 0))), ); } @@ -937,12 +792,8 @@ mod tests { assert_eq!(pool.validated_pool().status().future, 0); // when - block_on(pool.prune_tags( - &BlockId::Number(2), - vec![vec![0u8]], - vec![watcher.hash().clone()], - )) - .unwrap(); + block_on(pool.prune_tags(&BlockId::Number(2), vec![vec![0u8]], vec![*watcher.hash()])) + .unwrap(); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 0); @@ -951,7 +802,7 @@ mod tests { assert_eq!(stream.next(), Some(TransactionStatus::Ready)); assert_eq!( stream.next(), - Some(TransactionStatus::InBlock(H256::from_low_u64_be(2).into())), + Some(TransactionStatus::InBlock((H256::from_low_u64_be(2).into(), 0))), ); } @@ -1044,7 +895,7 @@ mod tests { } #[test] - fn should_trigger_dropped() { + fn should_trigger_dropped_older() { // given let limit = Limit { count: 1, total_bytes: 1000 }; let options = @@ -1077,6 +928,67 @@ mod tests { assert_eq!(stream.next(), Some(TransactionStatus::Dropped)); } + #[test] + fn should_trigger_dropped_lower_priority() { + { + // given + let limit = Limit { count: 1, total_bytes: 1000 }; + let options = + Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; + + let pool = Pool::new(options, true.into(), TestApi::default().into()); + + let xt = Extrinsic::IncludeData(Vec::new()); + block_on(pool.submit_one(&BlockId::Number(0), SOURCE, xt)).unwrap(); + assert_eq!(pool.validated_pool().status().ready, 1); + + // then + let xt = uxt(Transfer { + from: AccountId::from_h256(H256::from_low_u64_be(2)), + to: AccountId::from_h256(H256::from_low_u64_be(1)), + amount: 4, + nonce: 1, + }); + let result = block_on(pool.submit_one(&BlockId::Number(1), SOURCE, xt)); + assert!(matches!( + result, + Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped) + )); + } + { + // given + let limit = Limit { count: 2, total_bytes: 1000 }; + let options = + Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; + + let pool = Pool::new(options, true.into(), TestApi::default().into()); + + let xt = Extrinsic::IncludeData(Vec::new()); + block_on(pool.submit_and_watch(&BlockId::Number(0), SOURCE, xt)).unwrap(); + assert_eq!(pool.validated_pool().status().ready, 1); + + let xt = uxt(Transfer { + from: AccountId::from_h256(H256::from_low_u64_be(1)), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 0, + }); + let watcher = + block_on(pool.submit_and_watch(&BlockId::Number(0), SOURCE, xt)).unwrap(); + assert_eq!(pool.validated_pool().status().ready, 2); + + // when + let xt = Extrinsic::Store(Vec::new()); + block_on(pool.submit_one(&BlockId::Number(1), SOURCE, xt)).unwrap(); + assert_eq!(pool.validated_pool().status().ready, 2); + + // then + let mut stream = futures::executor::block_on_stream(watcher.into_stream()); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::Dropped)); + } + } + #[test] fn should_handle_pruning_in_the_middle_of_import() { // given diff --git a/client/transaction-pool/src/graph/ready.rs b/client/transaction-pool/src/graph/ready.rs index 03689aeb32e6d..b52372a3c4d10 100644 --- a/client/transaction-pool/src/graph/ready.rs +++ b/client/transaction-pool/src/graph/ready.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,7 +23,7 @@ use std::{ sync::Arc, }; -use log::trace; +use log::{debug, trace}; use sc_transaction_pool_api::error; use serde::Serialize; use sp_runtime::{traits::Member, transaction_validity::TransactionTag as Tag}; @@ -31,13 +31,13 @@ use sp_runtime::{traits::Member, transaction_validity::TransactionTag as Tag}; use super::{ base_pool::Transaction, future::WaitingTransaction, - tracked_map::{self, ReadOnlyTrackedMap, TrackedMap}, + tracked_map::{self, TrackedMap}, }; /// An in-pool transaction reference. /// /// Should be cheap to clone. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct TransactionRef { /// The actual transaction data. pub transaction: Arc>, @@ -74,7 +74,7 @@ impl PartialEq for TransactionRef { } impl Eq for TransactionRef {} -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct ReadyTx { /// A reference to a transaction pub transaction: TransactionRef, @@ -105,7 +105,7 @@ qed "#; /// Validated transactions that are block ready with all their dependencies met. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct ReadyTransactions { /// Next free insertion id (used to indicate when a transaction was inserted into the pool). insertion_id: u64, @@ -156,11 +156,16 @@ impl ReadyTransactions { /// - transactions that are valid for a shorter time go first /// 4. Lastly we sort by the time in the queue /// - transactions that are longer in the queue go first - pub fn get(&self) -> impl Iterator>> { + /// + /// The iterator is providing a way to report transactions that the receiver considers invalid. + /// In such case the entire subgraph of transactions that depend on the reported one will be + /// skipped. + pub fn get(&self) -> BestIterator { BestIterator { - all: self.ready.clone(), + all: self.ready.clone_map(), best: self.best.clone(), awaiting: Default::default(), + invalid: Default::default(), } } @@ -295,7 +300,7 @@ impl ReadyTransactions { for tag in &tx.transaction.transaction.requires { if let Some(hash) = self.provided_tags.get(tag) { if let Some(tx) = ready.get_mut(hash) { - remove_item(&mut tx.unlocks, &hash); + remove_item(&mut tx.unlocks, hash); } } } @@ -346,7 +351,7 @@ impl ReadyTransactions { let mut ready = self.ready.write(); let mut find_previous = |tag| -> Option> { let prev_hash = self.provided_tags.get(tag)?; - let tx2 = ready.get_mut(&prev_hash)?; + let tx2 = ready.get_mut(prev_hash)?; remove_item(&mut tx2.unlocks, hash); // We eagerly prune previous transactions as well. // But it might not always be good. @@ -479,9 +484,10 @@ impl ReadyTransactions { /// Iterator of ready transactions ordered by priority. pub struct BestIterator { - all: ReadOnlyTrackedMap>, + all: HashMap>, awaiting: HashMap)>, best: BTreeSet>, + invalid: HashSet, } impl BestIterator { @@ -498,6 +504,34 @@ impl BestIterator { } } +impl sc_transaction_pool_api::ReadyTransactions + for BestIterator +{ + fn report_invalid(&mut self, tx: &Self::Item) { + BestIterator::report_invalid(self, tx) + } +} + +impl BestIterator { + /// Report given transaction as invalid. + /// + /// As a consequence, all values that depend on the invalid one will be skipped. + /// When given transaction is not in the pool it has no effect. + /// When invoked on a fully drained iterator it has no effect either. + pub fn report_invalid(&mut self, tx: &Arc>) { + if let Some(to_report) = self.all.get(&tx.hash) { + debug!( + target: "txpool", + "[{:?}] Reported as invalid. Will skip sub-chains while iterating.", + to_report.transaction.transaction.hash + ); + for hash in &to_report.unlocks { + self.invalid.insert(hash.clone()); + } + } + } +} + impl Iterator for BestIterator { type Item = Arc>; @@ -505,9 +539,19 @@ impl Iterator for BestIterator { loop { let best = self.best.iter().next_back()?.clone(); let best = self.best.take(&best)?; + let hash = &best.transaction.hash; + + // Check if the transaction was marked invalid. + if self.invalid.contains(hash) { + debug!( + target: "txpool", + "[{:?}] Skipping invalid child transaction while iterating.", + hash + ); + continue + } - let next = self.all.read().get(&best.transaction.hash).cloned(); - let ready = match next { + let ready = match self.all.get(hash).cloned() { Some(ready) => ready, // The transaction is not in all, maybe it was removed in the meantime? None => continue, @@ -522,7 +566,6 @@ impl Iterator for BestIterator { // then get from the pool } else { self.all - .read() .get(hash) .map(|next| (next.requires_offset + 1, next.transaction.clone())) }; @@ -635,10 +678,13 @@ mod tests { assert_eq!(ready.get().count(), 3); } - #[test] - fn should_return_best_transactions_in_correct_order() { - // given - let mut ready = ReadyTransactions::default(); + /// Populate the pool, with a graph that looks like so: + /// + /// tx1 -> tx2 \ + /// -> -> tx3 + /// -> tx4 -> tx5 -> tx6 + /// -> tx7 + fn populate_pool(ready: &mut ReadyTransactions>) { let mut tx1 = tx(1); tx1.requires.clear(); let mut tx2 = tx(2); @@ -649,11 +695,17 @@ mod tests { tx3.provides = vec![]; let mut tx4 = tx(4); tx4.requires = vec![tx1.provides[0].clone()]; - tx4.provides = vec![]; - let tx5 = Transaction { - data: vec![5], + tx4.provides = vec![vec![107]]; + let mut tx5 = tx(5); + tx5.requires = vec![tx4.provides[0].clone()]; + tx5.provides = vec![vec![108]]; + let mut tx6 = tx(6); + tx6.requires = vec![tx5.provides[0].clone()]; + tx6.provides = vec![]; + let tx7 = Transaction { + data: vec![7], bytes: 1, - hash: 5, + hash: 7, priority: 1, valid_till: u64::MAX, // use the max here for testing. requires: vec![tx1.provides[0].clone()], @@ -663,42 +715,33 @@ mod tests { }; // when - for tx in vec![tx1, tx2, tx3, tx4, tx5] { - import(&mut ready, tx).unwrap(); + for tx in vec![tx1, tx2, tx3, tx7, tx4, tx5, tx6] { + import(ready, tx).unwrap(); } - // then assert_eq!(ready.best.len(), 1); + } + + #[test] + fn should_return_best_transactions_in_correct_order() { + // given + let mut ready = ReadyTransactions::default(); + populate_pool(&mut ready); + // when let mut it = ready.get().map(|tx| tx.data[0]); + // then assert_eq!(it.next(), Some(1)); assert_eq!(it.next(), Some(2)); assert_eq!(it.next(), Some(3)); assert_eq!(it.next(), Some(4)); assert_eq!(it.next(), Some(5)); + assert_eq!(it.next(), Some(6)); + assert_eq!(it.next(), Some(7)); assert_eq!(it.next(), None); } - #[test] - fn can_report_heap_size() { - let mut ready = ReadyTransactions::default(); - let tx = Transaction { - data: vec![5], - bytes: 1, - hash: 5, - priority: 1, - valid_till: u64::MAX, // use the max here for testing. - requires: vec![], - provides: vec![], - propagate: true, - source: Source::External, - }; - import(&mut ready, tx).unwrap(); - - assert!(parity_util_mem::malloc_size(&ready) > 200); - } - #[test] fn should_order_refs() { let mut id = 1; @@ -725,4 +768,26 @@ mod tests { TransactionRef { transaction: Arc::new(with_priority(3, 3)), insertion_id: 2 } ); } + + #[test] + fn should_skip_invalid_transactions_while_iterating() { + // given + let mut ready = ReadyTransactions::default(); + populate_pool(&mut ready); + + // when + let mut it = ready.get(); + let data = |tx: &Arc>>| tx.data[0]; + + // then + assert_eq!(it.next().as_ref().map(data), Some(1)); + assert_eq!(it.next().as_ref().map(data), Some(2)); + assert_eq!(it.next().as_ref().map(data), Some(3)); + let tx4 = it.next(); + assert_eq!(tx4.as_ref().map(data), Some(4)); + // report 4 as invalid, which should skip 5 & 6. + it.report_invalid(&tx4.unwrap()); + assert_eq!(it.next().as_ref().map(data), Some(7)); + assert_eq!(it.next().as_ref().map(data), None); + } } diff --git a/client/transaction-pool/src/graph/rotator.rs b/client/transaction-pool/src/graph/rotator.rs index 910f86b5ed5b8..47e00a1292155 100644 --- a/client/transaction-pool/src/graph/rotator.rs +++ b/client/transaction-pool/src/graph/rotator.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -51,6 +51,11 @@ impl Default for PoolRotator { } impl PoolRotator { + /// New rotator instance with specified ban time. + pub fn new(ban_time: Duration) -> Self { + Self { ban_time, banned_until: Default::default() } + } + /// Returns `true` if extrinsic hash is currently banned. pub fn is_banned(&self, hash: &Hash) -> bool { self.banned_until.read().contains_key(hash) @@ -115,7 +120,7 @@ mod tests { let tx = Transaction { data: (), bytes: 1, - hash: hash.clone(), + hash, priority: 5, valid_till: 1, requires: vec![], diff --git a/client/transaction-pool/src/graph/tracked_map.rs b/client/transaction-pool/src/graph/tracked_map.rs index c1fdda227c6ae..7292293258f57 100644 --- a/client/transaction-pool/src/graph/tracked_map.rs +++ b/client/transaction-pool/src/graph/tracked_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -33,7 +33,7 @@ pub trait Size { /// Map with size tracking. /// /// Size reported might be slightly off and only approximately true. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct TrackedMap { index: Arc>>, bytes: AtomicIsize, @@ -57,11 +57,6 @@ impl TrackedMap { std::cmp::max(self.bytes.load(AtomicOrdering::Relaxed), 0) as usize } - /// Read-only clone of the interior. - pub fn clone(&self) -> ReadOnlyTrackedMap { - ReadOnlyTrackedMap(self.index.clone()) - } - /// Lock map for read. pub fn read(&self) -> TrackedMapReadAccess { TrackedMapReadAccess { inner_guard: self.index.read() } @@ -77,18 +72,10 @@ impl TrackedMap { } } -/// Read-only access to map. -/// -/// The only thing can be done is .read(). -pub struct ReadOnlyTrackedMap(Arc>>); - -impl ReadOnlyTrackedMap -where - K: Eq + std::hash::Hash, -{ - /// Lock map for read. - pub fn read(&self) -> TrackedMapReadAccess { - TrackedMapReadAccess { inner_guard: self.0.read() } +impl TrackedMap { + /// Clone the inner map. + pub fn clone_map(&self) -> HashMap { + self.index.read().clone() } } diff --git a/client/transaction-pool/src/graph/validated_pool.rs b/client/transaction-pool/src/graph/validated_pool.rs index e4aad7f342b5b..ab99a090e5654 100644 --- a/client/transaction-pool/src/graph/validated_pool.rs +++ b/client/transaction-pool/src/graph/validated_pool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -24,8 +24,7 @@ use std::{ use futures::channel::mpsc::{channel, Sender}; use parking_lot::{Mutex, RwLock}; -use retain_mut::RetainMut; -use sc_transaction_pool_api::{error, PoolStatus}; +use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions}; use serde::Serialize; use sp_runtime::{ generic::BlockId, @@ -111,20 +110,11 @@ pub struct ValidatedPool { rotator: PoolRotator>, } -impl parity_util_mem::MallocSizeOf for ValidatedPool -where - ExtrinsicFor: parity_util_mem::MallocSizeOf, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - // other entries insignificant or non-primary references - self.pool.size_of(ops) - } -} - impl ValidatedPool { /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { let base_pool = base::BasePool::new(options.reject_future_transactions); + let ban_time = options.ban_time; Self { is_validator, options, @@ -132,7 +122,7 @@ impl ValidatedPool { api, pool: RwLock::new(base_pool), import_notification_sinks: Default::default(), - rotator: Default::default(), + rotator: PoolRotator::new(ban_time), } } @@ -203,21 +193,20 @@ impl ValidatedPool { let imported = self.pool.write().import(tx)?; if let base::Imported::Ready { ref hash, .. } = imported { - self.import_notification_sinks.lock().retain_mut(|sink| { - match sink.try_send(*hash) { - Ok(()) => true, - Err(e) => - if e.is_full() { - log::warn!( - target: "txpool", - "[{:?}] Trying to notify an import but the channel is full", - hash, - ); - true - } else { - false - }, - } + let sinks = &mut self.import_notification_sinks.lock(); + sinks.retain_mut(|sink| match sink.try_send(*hash) { + Ok(()) => true, + Err(e) => + if e.is_full() { + log::warn!( + target: "txpool", + "[{:?}] Trying to notify an import but the channel is full", + hash, + ); + true + } else { + false + }, }); } @@ -442,7 +431,7 @@ impl ValidatedPool { pub fn extrinsics_tags(&self, hashes: &[ExtrinsicHash]) -> Vec>> { self.pool .read() - .by_hashes(&hashes) + .by_hashes(hashes) .into_iter() .map(|existing_in_pool| { existing_in_pool.map(|transaction| transaction.provides.to_vec()) @@ -546,7 +535,7 @@ impl ValidatedPool { let now = Instant::now(); let to_remove = { self.ready() - .filter(|tx| self.rotator.ban_if_stale(&now, block_number, &tx)) + .filter(|tx| self.rotator.ban_if_stale(&now, block_number, tx)) .map(|tx| tx.hash) .collect::>() }; @@ -554,7 +543,7 @@ impl ValidatedPool { let p = self.pool.read(); let mut hashes = Vec::new(); for tx in p.futures() { - if self.rotator.ban_if_stale(&now, block_number, &tx) { + if self.rotator.ban_if_stale(&now, block_number, tx) { hashes.push(tx.hash); } } @@ -569,12 +558,6 @@ impl ValidatedPool { Ok(()) } - /// Get rotator reference. - #[cfg(feature = "test-helpers")] - pub fn rotator(&self) -> &PoolRotator> { - &self.rotator - } - /// Get api reference. pub fn api(&self) -> &B { &self.api @@ -630,17 +613,13 @@ impl ValidatedPool { } /// Get an iterator for ready transactions ordered by priority - pub fn ready(&self) -> impl Iterator> + Send { + pub fn ready(&self) -> impl ReadyTransactions> + Send { self.pool.read().ready() } /// Returns a Vec of hashes and extrinsics in the future pool. pub fn futures(&self) -> Vec<(ExtrinsicHash, ExtrinsicFor)> { - self.pool - .read() - .futures() - .map(|tx| (tx.hash.clone(), tx.data.clone())) - .collect() + self.pool.read().futures().map(|tx| (tx.hash, tx.data.clone())).collect() } /// Returns pool status. @@ -669,9 +648,9 @@ where match *imported { base::Imported::Ready { ref promoted, ref failed, ref removed, ref hash } => { listener.ready(hash, None); - failed.into_iter().for_each(|f| listener.invalid(f)); - removed.into_iter().for_each(|r| listener.dropped(&r.hash, Some(hash))); - promoted.into_iter().for_each(|p| listener.ready(p, None)); + failed.iter().for_each(|f| listener.invalid(f)); + removed.iter().for_each(|r| listener.dropped(&r.hash, Some(hash))); + promoted.iter().for_each(|p| listener.ready(p, None)); }, base::Imported::Future { ref hash } => listener.future(hash), } diff --git a/client/transaction-pool/src/graph/watcher.rs b/client/transaction-pool/src/graph/watcher.rs index 975ee6608886b..0613300c8684b 100644 --- a/client/transaction-pool/src/graph/watcher.rs +++ b/client/transaction-pool/src/graph/watcher.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -84,13 +84,13 @@ impl Sender { } /// Extrinsic has been included in block with given hash. - pub fn in_block(&mut self, hash: BH) { - self.send(TransactionStatus::InBlock(hash)); + pub fn in_block(&mut self, hash: BH, index: usize) { + self.send(TransactionStatus::InBlock((hash, index))); } /// Extrinsic has been finalized by a finality gadget. - pub fn finalized(&mut self, hash: BH) { - self.send(TransactionStatus::Finalized(hash)); + pub fn finalized(&mut self, hash: BH, index: usize) { + self.send(TransactionStatus::Finalized((hash, index))); self.is_finalized = true; } diff --git a/client/transaction-pool/src/lib.rs b/client/transaction-pool/src/lib.rs index 6eb5bd2f332ec..0124038b75949 100644 --- a/client/transaction-pool/src/lib.rs +++ b/client/transaction-pool/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -23,40 +23,37 @@ #![warn(unused_extern_crates)] mod api; +mod enactment_state; +pub mod error; mod graph; mod metrics; mod revalidation; +#[cfg(test)] +mod tests; -pub mod error; - -/// Common types for testing the transaction pool -#[cfg(feature = "test-helpers")] -pub mod test_helpers { - pub use super::{ - graph::{BlockHash, ChainApi, ExtrinsicFor, NumberFor, Pool}, - revalidation::RevalidationQueue, - }; -} - -pub use crate::api::{FullChainApi, LightChainApi}; +pub use crate::api::FullChainApi; +use async_trait::async_trait; +use enactment_state::EnactmentState; use futures::{ channel::oneshot, future::{self, ready}, prelude::*, }; -pub use graph::{ChainApi, Options, Pool, Transaction}; +pub use graph::{ + base_pool::Limit as PoolLimit, ChainApi, Options, Pool, Transaction, ValidatedTransaction, +}; use parking_lot::Mutex; use std::{ collections::{HashMap, HashSet}, - convert::TryInto, pin::Pin, sync::Arc, }; use graph::{ExtrinsicHash, IsValidator}; use sc_transaction_pool_api::{ - ChainEvent, ImportNotificationStream, MaintainedTransactionPool, PoolFuture, PoolStatus, - TransactionFor, TransactionPool, TransactionSource, TransactionStatusStreamFor, TxHash, + error::Error as TxPoolError, ChainEvent, ImportNotificationStream, MaintainedTransactionPool, + PoolFuture, PoolStatus, ReadyTransactions, TransactionFor, TransactionPool, TransactionSource, + TransactionStatusStreamFor, TxHash, }; use sp_core::traits::SpawnEssentialNamed; use sp_runtime::{ @@ -68,8 +65,10 @@ use std::time::Instant; use crate::metrics::MetricsLink as PrometheusMetrics; use prometheus_endpoint::Registry as PrometheusRegistry; +use sp_blockchain::{HashAndNumber, TreeRoute}; + type BoxedReadyIterator = - Box>> + Send>; + Box>> + Send>; type ReadyIteratorFor = BoxedReadyIterator, graph::ExtrinsicFor>; @@ -78,9 +77,6 @@ type PolledIterator = Pin = BasicPool, Block>; -/// A transaction pool for a light node. -pub type LightPool = - BasicPool, Block>; /// Basic implementation of transaction pool that can be customized by providing PoolApi. pub struct BasicPool @@ -94,6 +90,7 @@ where revalidation_queue: Arc>, ready_poll: Arc, Block>>>, metrics: PrometheusMetrics, + enactment_state: Arc>>, } struct ReadyPoll { @@ -138,17 +135,6 @@ impl ReadyPoll { } } -impl parity_util_mem::MallocSizeOf for BasicPool -where - PoolApi: graph::ChainApi, - Block: BlockT, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - // other entries insignificant or non-primary references - self.pool.size_of(ops) - } -} - /// Type of revalidation. pub enum RevalidationType { /// Light revalidation type. @@ -172,13 +158,14 @@ where PoolApi: graph::ChainApi + 'static, { /// Create new basic transaction pool with provided api, for tests. - #[cfg(feature = "test-helpers")] pub fn new_test( pool_api: Arc, - ) -> (Self, Pin + Send>>, intervalier::BackSignalControl) { + best_block_hash: Block::Hash, + finalized_hash: Block::Hash, + ) -> (Self, Pin + Send>>) { let pool = Arc::new(graph::Pool::new(Default::default(), true.into(), pool_api.clone())); - let (revalidation_queue, background_task, notifier) = - revalidation::RevalidationQueue::new_test(pool_api.clone(), pool.clone()); + let (revalidation_queue, background_task) = + revalidation::RevalidationQueue::new_background(pool_api.clone(), pool.clone()); ( Self { api: pool_api, @@ -187,9 +174,12 @@ where revalidation_strategy: Arc::new(Mutex::new(RevalidationStrategy::Always)), ready_poll: Default::default(), metrics: Default::default(), + enactment_state: Arc::new(Mutex::new(EnactmentState::new( + best_block_hash, + finalized_hash, + ))), }, background_task, - notifier, ) } @@ -203,6 +193,8 @@ where revalidation_type: RevalidationType, spawner: impl SpawnEssentialNamed, best_block_number: NumberFor, + best_block_hash: Block::Hash, + finalized_hash: Block::Hash, ) -> Self { let pool = Arc::new(graph::Pool::new(options, is_validator, pool_api.clone())); let (revalidation_queue, background_task) = match revalidation_type { @@ -216,7 +208,7 @@ where }; if let Some(background_task) = background_task { - spawner.spawn_essential("txpool-background", background_task); + spawner.spawn_essential("txpool-background", Some("transaction-pool"), background_task); } Self { @@ -230,6 +222,10 @@ where })), ready_poll: Arc::new(Mutex::new(ReadyPoll::new(best_block_number))), metrics: PrometheusMetrics::new(prometheus), + enactment_state: Arc::new(Mutex::new(EnactmentState::new( + best_block_hash, + finalized_hash, + ))), } } @@ -239,7 +235,6 @@ where } /// Get access to the underlying api - #[cfg(feature = "test-helpers")] pub fn api(&self) -> &PoolApi { &self.api } @@ -363,33 +358,6 @@ where } } -impl LightPool -where - Block: BlockT, - Client: sp_blockchain::HeaderBackend + sc_client_api::UsageProvider + 'static, - Fetcher: sc_client_api::Fetcher + 'static, -{ - /// Create new basic transaction pool for a light node with the provided api. - pub fn new_light( - options: graph::Options, - prometheus: Option<&PrometheusRegistry>, - spawner: impl SpawnEssentialNamed, - client: Arc, - fetcher: Arc, - ) -> Self { - let pool_api = Arc::new(LightChainApi::new(client.clone(), fetcher)); - Self::with_revalidation_type( - options, - false.into(), - pool_api, - prometheus, - RevalidationType::Light, - spawner, - client.usage_info().chain.best_number, - ) - } -} - impl FullPool where Block: BlockT, @@ -399,6 +367,7 @@ where + sp_runtime::traits::BlockIdTo + sc_client_api::ExecutorProvider + sc_client_api::UsageProvider + + sp_blockchain::HeaderMetadata + Send + Sync + 'static, @@ -421,6 +390,8 @@ where RevalidationType::Full, spawner, client.usage_info().chain.best_number, + client.usage_info().chain.best_hash, + client.usage_info().chain.finalized_hash, )); // make transaction pool available for off-chain runtime calls. @@ -437,7 +408,8 @@ where Client: sp_api::ProvideRuntimeApi + sc_client_api::BlockBackend + sc_client_api::blockchain::HeaderBackend - + sp_runtime::traits::BlockIdTo, + + sp_runtime::traits::BlockIdTo + + sp_blockchain::HeaderMetadata, Client: Send + Sync + 'static, Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, { @@ -450,7 +422,6 @@ where at: &BlockId, xt: sc_transaction_pool_api::LocalTransactionFor, ) -> Result { - use graph::ValidatedTransaction; use sp_runtime::{ traits::SaturatedConversion, transaction_validity::TransactionValidityError, }; @@ -460,8 +431,8 @@ where .validate_transaction_blocking(at, TransactionSource::Local, xt.clone())? .map_err(|e| { Self::Error::Pool(match e { - TransactionValidityError::Invalid(i) => i.into(), - TransactionValidityError::Unknown(u) => u.into(), + TransactionValidityError::Invalid(i) => TxPoolError::InvalidTransaction(i), + TransactionValidityError::Unknown(u) => TxPoolError::UnknownTransaction(u), }) })?; @@ -473,7 +444,7 @@ where let validated = ValidatedTransaction::valid_at( block_number.saturated_into::(), - hash.clone(), + hash, TransactionSource::Local, xt, bytes, @@ -568,202 +539,229 @@ impl RevalidationStatus { /// Prune the known txs for the given block. async fn prune_known_txs_for_block>( - block_id: BlockId, + block_hash: Block::Hash, api: &Api, pool: &graph::Pool, ) -> Vec> { let extrinsics = api - .block_body(&block_id) + .block_body(block_hash) .await .unwrap_or_else(|e| { - log::warn!("Prune known transactions: error request {:?}!", e); + log::warn!("Prune known transactions: error request: {}", e); None }) .unwrap_or_default(); - let hashes = extrinsics.iter().map(|tx| pool.hash_of(&tx)).collect::>(); + let hashes = extrinsics.iter().map(|tx| pool.hash_of(tx)).collect::>(); log::trace!(target: "txpool", "Pruning transactions: {:?}", hashes); - let header = match api.block_header(&block_id) { + let header = match api.block_header(&BlockId::Hash(block_hash)) { Ok(Some(h)) => h, Ok(None) => { - log::debug!(target: "txpool", "Could not find header for {:?}.", block_id); + log::debug!(target: "txpool", "Could not find header for {:?}.", block_hash); return hashes }, Err(e) => { - log::debug!(target: "txpool", "Error retrieving header for {:?}: {:?}", block_id, e); + log::debug!(target: "txpool", "Error retrieving header for {:?}: {}", block_hash, e); return hashes }, }; - if let Err(e) = pool.prune(&block_id, &BlockId::hash(*header.parent_hash()), &extrinsics).await + if let Err(e) = pool + .prune(&BlockId::Hash(block_hash), &BlockId::hash(*header.parent_hash()), &extrinsics) + .await { - log::error!("Cannot prune known in the pool {:?}!", e); + log::error!("Cannot prune known in the pool: {}", e); } hashes } -impl MaintainedTransactionPool for BasicPool +impl BasicPool where Block: BlockT, PoolApi: 'static + graph::ChainApi, { - fn maintain(&self, event: ChainEvent) -> Pin + Send>> { - match event { - ChainEvent::NewBestBlock { hash, tree_route } => { - let pool = self.pool.clone(); - let api = self.api.clone(); - - let id = BlockId::hash(hash); - let block_number = match api.block_id_to_number(&id) { - Ok(Some(number)) => number, - _ => { - log::trace!( + /// Handles enactment and retraction of blocks, prunes stale transactions + /// (that have already been enacted) and resubmits transactions that were + /// retracted. + async fn handle_enactment(&self, tree_route: TreeRoute) { + log::trace!(target: "txpool", "handle_enactment tree_route: {tree_route:?}"); + let pool = self.pool.clone(); + let api = self.api.clone(); + + let (hash, block_number) = match tree_route.last() { + Some(HashAndNumber { hash, number }) => (hash, number), + None => { + log::warn!( + target: "txpool", + "Skipping ChainEvent - no last block in tree route {:?}", + tree_route, + ); + return + }, + }; + + let next_action = self.revalidation_strategy.lock().next( + *block_number, + Some(std::time::Duration::from_secs(60)), + Some(20u32.into()), + ); + + // We keep track of everything we prune so that later we won't add + // transactions with those hashes from the retracted blocks. + let mut pruned_log = HashSet::>::new(); + + // If there is a tree route, we use this to prune known tx based on the enacted + // blocks. Before pruning enacted transactions, we inform the listeners about + // retracted blocks and their transactions. This order is important, because + // if we enact and retract the same transaction at the same time, we want to + // send first the retract and than the prune event. + for retracted in tree_route.retracted() { + // notify txs awaiting finality that it has been retracted + pool.validated_pool().on_block_retracted(retracted.hash); + } + + future::join_all( + tree_route + .enacted() + .iter() + .map(|h| prune_known_txs_for_block(h.hash, &*api, &*pool)), + ) + .await + .into_iter() + .for_each(|enacted_log| { + pruned_log.extend(enacted_log); + }); + + self.metrics + .report(|metrics| metrics.block_transactions_pruned.inc_by(pruned_log.len() as u64)); + + if next_action.resubmit { + let mut resubmit_transactions = Vec::new(); + + for retracted in tree_route.retracted() { + let hash = retracted.hash; + + let block_transactions = api + .block_body(hash) + .await + .unwrap_or_else(|e| { + log::warn!("Failed to fetch block body: {}", e); + None + }) + .unwrap_or_default() + .into_iter() + .filter(|tx| tx.is_signed().unwrap_or(true)); + + let mut resubmitted_to_report = 0; + + resubmit_transactions.extend(block_transactions.into_iter().filter(|tx| { + let tx_hash = pool.hash_of(tx); + let contains = pruned_log.contains(&tx_hash); + + // need to count all transactions, not just filtered, here + resubmitted_to_report += 1; + + if !contains { + log::debug!( target: "txpool", - "Skipping chain event - no number for that block {:?}", - id, + "[{:?}]: Resubmitting from retracted block {:?}", + tx_hash, + hash, ); - return Box::pin(ready(())) - }, - }; - - let next_action = self.revalidation_strategy.lock().next( - block_number, - Some(std::time::Duration::from_secs(60)), - Some(20u32.into()), - ); - let revalidation_strategy = self.revalidation_strategy.clone(); - let revalidation_queue = self.revalidation_queue.clone(); - let ready_poll = self.ready_poll.clone(); - let metrics = self.metrics.clone(); - - async move { - // We keep track of everything we prune so that later we won't add - // transactions with those hashes from the retracted blocks. - let mut pruned_log = HashSet::>::new(); - - // If there is a tree route, we use this to prune known tx based on the enacted - // blocks. Before pruning enacted transactions, we inform the listeners about - // retracted blocks and their transactions. This order is important, because - // if we enact and retract the same transaction at the same time, we want to - // send first the retract and than the prune event. - if let Some(ref tree_route) = tree_route { - for retracted in tree_route.retracted() { - // notify txs awaiting finality that it has been retracted - pool.validated_pool().on_block_retracted(retracted.hash.clone()); - } - - future::join_all(tree_route.enacted().iter().map(|h| { - prune_known_txs_for_block(BlockId::Hash(h.hash.clone()), &*api, &*pool) - })) - .await - .into_iter() - .for_each(|enacted_log| { - pruned_log.extend(enacted_log); - }) } + !contains + })); - pruned_log.extend(prune_known_txs_for_block(id.clone(), &*api, &*pool).await); - - metrics.report(|metrics| { - metrics.block_transactions_pruned.inc_by(pruned_log.len() as u64) - }); - - if let (true, Some(tree_route)) = (next_action.resubmit, tree_route) { - let mut resubmit_transactions = Vec::new(); - - for retracted in tree_route.retracted() { - let hash = retracted.hash.clone(); - - let block_transactions = api - .block_body(&BlockId::hash(hash)) - .await - .unwrap_or_else(|e| { - log::warn!("Failed to fetch block body {:?}!", e); - None - }) - .unwrap_or_default() - .into_iter() - .filter(|tx| tx.is_signed().unwrap_or(true)); - - let mut resubmitted_to_report = 0; - - resubmit_transactions.extend(block_transactions.into_iter().filter( - |tx| { - let tx_hash = pool.hash_of(&tx); - let contains = pruned_log.contains(&tx_hash); - - // need to count all transactions, not just filtered, here - resubmitted_to_report += 1; - - if !contains { - log::debug!( - target: "txpool", - "[{:?}]: Resubmitting from retracted block {:?}", - tx_hash, - hash, - ); - } - !contains - }, - )); - - metrics.report(|metrics| { - metrics.block_transactions_resubmitted.inc_by(resubmitted_to_report) - }); - } - - if let Err(e) = pool - .resubmit_at( - &id, - // These transactions are coming from retracted blocks, we should - // simply consider them external. - TransactionSource::External, - resubmit_transactions, - ) - .await - { - log::debug!( - target: "txpool", - "[{:?}] Error re-submitting transactions: {:?}", - id, - e, - ) - } - } + self.metrics.report(|metrics| { + metrics.block_transactions_resubmitted.inc_by(resubmitted_to_report) + }); + } - let extra_pool = pool.clone(); - // After #5200 lands, this arguably might be moved to the - // handler of "all blocks notification". - ready_poll.lock().trigger(block_number, move || { - Box::new(extra_pool.validated_pool().ready()) - }); + if let Err(e) = pool + .resubmit_at( + &BlockId::Hash(*hash), + // These transactions are coming from retracted blocks, we should + // simply consider them external. + TransactionSource::External, + resubmit_transactions, + ) + .await + { + log::debug!( + target: "txpool", + "[{:?}] Error re-submitting transactions: {}", + hash, + e, + ) + } + } - if next_action.revalidate { - let hashes = - pool.validated_pool().ready().map(|tx| tx.hash.clone()).collect(); - revalidation_queue.revalidate_later(block_number, hashes).await; + let extra_pool = pool.clone(); + // After #5200 lands, this arguably might be moved to the + // handler of "all blocks notification". + self.ready_poll + .lock() + .trigger(*block_number, move || Box::new(extra_pool.validated_pool().ready())); - revalidation_strategy.lock().clear(); - } - } - .boxed() + if next_action.revalidate { + let hashes = pool.validated_pool().ready().map(|tx| tx.hash).collect(); + self.revalidation_queue.revalidate_later(*block_number, hashes).await; + + self.revalidation_strategy.lock().clear(); + } + } +} + +#[async_trait] +impl MaintainedTransactionPool for BasicPool +where + Block: BlockT, + PoolApi: 'static + graph::ChainApi, +{ + async fn maintain(&self, event: ChainEvent) { + let prev_finalized_block = self.enactment_state.lock().recent_finalized_block(); + let compute_tree_route = |from, to| -> Result, String> { + match self.api.tree_route(from, to) { + Ok(tree_route) => Ok(tree_route), + Err(e) => + return Err(format!( + "Error occurred while computing tree_route from {from:?} to {to:?}: {e}" + )), + } + }; + + let result = self.enactment_state.lock().update(&event, &compute_tree_route); + + match result { + Err(msg) => { + log::debug!(target: "txpool", "{msg}"); + self.enactment_state.lock().force_update(&event); }, - ChainEvent::Finalized { hash } => { - let pool = self.pool.clone(); - async move { - if let Err(e) = pool.validated_pool().on_block_finalized(hash).await { - log::warn!( - target: "txpool", - "Error [{}] occurred while attempting to notify watchers of finalization {}", - e, hash - ) - } - } - .boxed() + Ok(None) => {}, + Ok(Some(tree_route)) => { + self.handle_enactment(tree_route).await; }, + }; + + if let ChainEvent::Finalized { hash, tree_route } = event { + log::trace!( + target: "txpool", + "on-finalized enacted: {tree_route:?}, previously finalized: \ + {prev_finalized_block:?}", + ); + + for hash in tree_route.iter().chain(std::iter::once(&hash)) { + if let Err(e) = self.pool.validated_pool().on_block_finalized(*hash).await { + log::warn!( + target: "txpool", + "Error occurred while attempting to notify watchers about finalization {}: {}", + hash, e + ) + } + } } } } diff --git a/client/transaction-pool/src/metrics.rs b/client/transaction-pool/src/metrics.rs index d62d64f13a0a4..8bcefe246bff5 100644 --- a/client/transaction-pool/src/metrics.rs +++ b/client/transaction-pool/src/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -56,28 +56,28 @@ impl Metrics { Ok(Self { submitted_transactions: register( Counter::new( - "sub_txpool_submitted_transactions", + "substrate_sub_txpool_submitted_transactions", "Total number of transactions submitted", )?, registry, )?, validations_invalid: register( Counter::new( - "sub_txpool_validations_invalid", + "substrate_sub_txpool_validations_invalid", "Total number of transactions that were removed from the pool as invalid", )?, registry, )?, block_transactions_pruned: register( Counter::new( - "sub_txpool_block_transactions_pruned", + "substrate_sub_txpool_block_transactions_pruned", "Total number of transactions that was requested to be pruned by block events", )?, registry, )?, block_transactions_resubmitted: register( Counter::new( - "sub_txpool_block_transactions_resubmitted", + "substrate_sub_txpool_block_transactions_resubmitted", "Total number of transactions that was requested to be resubmitted by block events", )?, registry, @@ -98,14 +98,14 @@ impl ApiMetrics { Ok(Self { validations_scheduled: register( Counter::new( - "sub_txpool_validations_scheduled", + "substrate_sub_txpool_validations_scheduled", "Total number of transactions scheduled for validation", )?, registry, )?, validations_finished: register( Counter::new( - "sub_txpool_validations_finished", + "substrate_sub_txpool_validations_finished", "Total number of transactions that finished validation", )?, registry, diff --git a/client/transaction-pool/src/revalidation.rs b/client/transaction-pool/src/revalidation.rs index a8b2c1d32036a..b4b4299240a32 100644 --- a/client/transaction-pool/src/revalidation.rs +++ b/client/transaction-pool/src/revalidation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -35,10 +35,7 @@ use sp_runtime::{ use futures::prelude::*; use std::time::Duration; -#[cfg(not(feature = "test-helpers"))] const BACKGROUND_REVALIDATION_INTERVAL: Duration = Duration::from_millis(200); -#[cfg(feature = "test-helpers")] -pub const BACKGROUND_REVALIDATION_INTERVAL: Duration = Duration::from_millis(1); const MIN_BACKGROUND_REVALIDATION_BATCH_SIZE: usize = 20; @@ -95,7 +92,7 @@ async fn batch_revalidate( }, Ok(Ok(validity)) => { revalidated.insert( - ext_hash.clone(), + ext_hash, ValidatedTransaction::valid_at( at.saturated_into::(), ext_hash, @@ -109,7 +106,7 @@ async fn batch_revalidate( Err(validation_err) => { log::debug!( target: "txpool", - "[{:?}]: Error during revalidation: {:?}. Removing.", + "[{:?}]: Removing due to error during revalidation: {}", ext_hash, validation_err ); @@ -197,14 +194,14 @@ impl RevalidationWorker { self.block_ordered .entry(block_number) .and_modify(|value| { - value.insert(ext_hash.clone()); + value.insert(ext_hash); }) .or_insert_with(|| { let mut bt = HashSet::new(); - bt.insert(ext_hash.clone()); + bt.insert(ext_hash); bt }); - self.members.insert(ext_hash.clone(), block_number); + self.members.insert(ext_hash, block_number); } } @@ -213,36 +210,25 @@ impl RevalidationWorker { /// It does two things: periodically tries to process some transactions /// from the queue and also accepts messages to enqueue some more /// transactions from the pool. - pub async fn run( + pub async fn run( mut self, from_queue: TracingUnboundedReceiver>, - interval: R, - ) where - R: Send, - R::Guard: Send, - { - let interval = interval.into_stream().fuse(); + interval: Duration, + ) { + let interval_fut = futures_timer::Delay::new(interval); let from_queue = from_queue.fuse(); - futures::pin_mut!(interval, from_queue); + futures::pin_mut!(interval_fut, from_queue); let this = &mut self; loop { futures::select! { - _guard = interval.next() => { + // Using `fuse()` in here is okay, because we reset the interval when it has fired. + _ = (&mut interval_fut).fuse() => { let next_batch = this.prepare_batch(); let batch_len = next_batch.len(); batch_revalidate(this.pool.clone(), this.api.clone(), this.best_block, next_batch).await; - #[cfg(feature = "test-helpers")] - { - use intervalier::Guard; - // only trigger test events if something was processed - if batch_len == 0 { - _guard.expect("Always some() in tests").skip(); - } - } - if batch_len > 0 || this.len() > 0 { log::debug!( target: "txpool", @@ -251,6 +237,8 @@ impl RevalidationWorker { this.len(), ); } + + interval_fut.reset(interval); }, workload = from_queue.next() => { match workload { @@ -298,15 +286,11 @@ where } /// New revalidation queue with background worker. - pub fn new_with_interval( + pub fn new_with_interval( api: Arc, pool: Arc>, - interval: R, - ) -> (Self, Pin + Send>>) - where - R: Send + 'static, - R::Guard: Send, - { + interval: Duration, + ) -> (Self, Pin + Send>>) { let (to_worker, from_queue) = tracing_unbounded("mpsc_revalidation_queue"); let worker = RevalidationWorker::new(api.clone(), pool.clone()); @@ -321,24 +305,7 @@ where api: Arc, pool: Arc>, ) -> (Self, Pin + Send>>) { - Self::new_with_interval( - api, - pool, - intervalier::Interval::new(BACKGROUND_REVALIDATION_INTERVAL), - ) - } - - /// New revalidation queue with background worker and test signal. - #[cfg(feature = "test-helpers")] - pub fn new_test( - api: Arc, - pool: Arc>, - ) -> (Self, Pin + Send>>, intervalier::BackSignalControl) { - let (interval, notifier) = - intervalier::BackSignalInterval::new(BACKGROUND_REVALIDATION_INTERVAL); - let (queue, background) = Self::new_with_interval(api, pool, interval); - - (queue, background, notifier) + Self::new_with_interval(api, pool, BACKGROUND_REVALIDATION_INTERVAL) } /// Queue some transaction for later revalidation. @@ -371,4 +338,41 @@ where } #[cfg(test)] -mod tests {} +mod tests { + use super::*; + use crate::{ + graph::Pool, + tests::{uxt, TestApi}, + }; + use futures::executor::block_on; + use sc_transaction_pool_api::TransactionSource; + use sp_runtime::generic::BlockId; + use substrate_test_runtime::{AccountId, Transfer, H256}; + + #[test] + fn revalidation_queue_works() { + let api = Arc::new(TestApi::default()); + let pool = Arc::new(Pool::new(Default::default(), true.into(), api.clone())); + let queue = Arc::new(RevalidationQueue::new(api.clone(), pool.clone())); + + let uxt = uxt(Transfer { + from: AccountId::from_h256(H256::from_low_u64_be(1)), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 0, + }); + let uxt_hash = block_on(pool.submit_one( + &BlockId::number(0), + TransactionSource::External, + uxt.clone(), + )) + .expect("Should be valid"); + + block_on(queue.revalidate_later(0, vec![uxt_hash])); + + // revalidated in sync offload 2nd time + assert_eq!(api.validation_requests().len(), 2); + // number of ready + assert_eq!(pool.validated_pool().status().ready, 1); + } +} diff --git a/client/transaction-pool/src/tests.rs b/client/transaction-pool/src/tests.rs new file mode 100644 index 0000000000000..d8732077eba52 --- /dev/null +++ b/client/transaction-pool/src/tests.rs @@ -0,0 +1,194 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Testing related primitives for internal usage in this crate. + +use crate::graph::{BlockHash, ChainApi, ExtrinsicFor, NumberFor, Pool}; +use codec::Encode; +use parking_lot::Mutex; +use sc_transaction_pool_api::error; +use sp_blockchain::TreeRoute; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Hash}, + transaction_validity::{ + InvalidTransaction, TransactionSource, TransactionValidity, ValidTransaction, + }, +}; +use std::{collections::HashSet, sync::Arc}; +use substrate_test_runtime::{Block, Extrinsic, Hashing, Transfer, H256}; + +pub(crate) const INVALID_NONCE: u64 = 254; + +/// Test api that implements [`ChainApi`]. +#[derive(Clone, Debug, Default)] +pub(crate) struct TestApi { + pub delay: Arc>>>, + pub invalidate: Arc>>, + pub clear_requirements: Arc>>, + pub add_requirements: Arc>>, + pub validation_requests: Arc>>, +} + +impl TestApi { + /// Query validation requests received. + pub fn validation_requests(&self) -> Vec { + self.validation_requests.lock().clone() + } +} + +impl ChainApi for TestApi { + type Block = Block; + type Error = error::Error; + type ValidationFuture = futures::future::Ready>; + type BodyFuture = futures::future::Ready>>>; + + /// Verify extrinsic at given block. + fn validate_transaction( + &self, + at: &BlockId, + _source: TransactionSource, + uxt: ExtrinsicFor, + ) -> Self::ValidationFuture { + self.validation_requests.lock().push(uxt.clone()); + let hash = self.hash_and_length(&uxt).0; + let block_number = self.block_id_to_number(at).unwrap().unwrap(); + + let res = match uxt { + Extrinsic::Transfer { transfer, .. } => { + let nonce = transfer.nonce; + + // This is used to control the test flow. + if nonce > 0 { + let opt = self.delay.lock().take(); + if let Some(delay) = opt { + if delay.recv().is_err() { + println!("Error waiting for delay!"); + } + } + } + + if self.invalidate.lock().contains(&hash) { + InvalidTransaction::Custom(0).into() + } else if nonce < block_number { + InvalidTransaction::Stale.into() + } else { + let mut transaction = ValidTransaction { + priority: 4, + requires: if nonce > block_number { + vec![vec![nonce as u8 - 1]] + } else { + vec![] + }, + provides: if nonce == INVALID_NONCE { + vec![] + } else { + vec![vec![nonce as u8]] + }, + longevity: 3, + propagate: true, + }; + + if self.clear_requirements.lock().contains(&hash) { + transaction.requires.clear(); + } + + if self.add_requirements.lock().contains(&hash) { + transaction.requires.push(vec![128]); + } + + Ok(transaction) + } + }, + Extrinsic::IncludeData(_) => Ok(ValidTransaction { + priority: 9001, + requires: vec![], + provides: vec![vec![42]], + longevity: 9001, + propagate: false, + }), + Extrinsic::Store(_) => Ok(ValidTransaction { + priority: 9001, + requires: vec![], + provides: vec![vec![43]], + longevity: 9001, + propagate: false, + }), + _ => unimplemented!(), + }; + + futures::future::ready(Ok(res)) + } + + /// Returns a block number given the block id. + fn block_id_to_number( + &self, + at: &BlockId, + ) -> Result>, Self::Error> { + Ok(match at { + BlockId::Number(num) => Some(*num), + BlockId::Hash(_) => None, + }) + } + + /// Returns a block hash given the block id. + fn block_id_to_hash( + &self, + at: &BlockId, + ) -> Result::Hash>, Self::Error> { + Ok(match at { + BlockId::Number(num) => Some(H256::from_low_u64_be(*num)).into(), + BlockId::Hash(_) => None, + }) + } + + /// Hash the extrinsic. + fn hash_and_length(&self, uxt: &ExtrinsicFor) -> (BlockHash, usize) { + let encoded = uxt.encode(); + let len = encoded.len(); + (Hashing::hash(&encoded), len) + } + + fn block_body(&self, _id: ::Hash) -> Self::BodyFuture { + futures::future::ready(Ok(None)) + } + + fn block_header( + &self, + _: &BlockId, + ) -> Result::Header>, Self::Error> { + Ok(None) + } + + fn tree_route( + &self, + _from: ::Hash, + _to: ::Hash, + ) -> Result, Self::Error> { + unimplemented!() + } +} + +pub(crate) fn uxt(transfer: Transfer) -> Extrinsic { + let signature = TryFrom::try_from(&[0; 64][..]).unwrap(); + Extrinsic::Transfer { transfer, signature, exhaust_resources_when_not_first: false } +} + +pub(crate) fn pool() -> Pool { + Pool::new(Default::default(), true.into(), TestApi::default().into()) +} diff --git a/client/transaction-pool/tests/pool.rs b/client/transaction-pool/tests/pool.rs index 6c34d05cd5dcb..7ba61e58b4cb5 100644 --- a/client/transaction-pool/tests/pool.rs +++ b/client/transaction-pool/tests/pool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -17,6 +17,7 @@ // along with this program. If not, see . //! Tests for top-level transaction pool api + use codec::Encode; use futures::{ executor::{block_on, block_on_stream}, @@ -25,17 +26,18 @@ use futures::{ }; use sc_block_builder::BlockBuilderProvider; use sc_client_api::client::BlockchainEvents; -use sc_transaction_pool::{test_helpers::*, *}; +use sc_transaction_pool::*; use sc_transaction_pool_api::{ ChainEvent, MaintainedTransactionPool, TransactionPool, TransactionStatus, }; +use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; use sp_runtime::{ generic::BlockId, traits::Block as _, transaction_validity::{InvalidTransaction, TransactionSource, ValidTransaction}, }; -use std::{collections::BTreeSet, convert::TryInto, sync::Arc}; +use std::{collections::BTreeSet, pin::Pin, sync::Arc}; use substrate_test_runtime_client::{ runtime::{Block, Extrinsic, Hash, Header, Index, Transfer}, AccountKeyring::*, @@ -47,14 +49,32 @@ fn pool() -> Pool { Pool::new(Default::default(), true.into(), TestApi::with_alice_nonce(209).into()) } -fn maintained_pool( -) -> (BasicPool, futures::executor::ThreadPool, intervalier::BackSignalControl) { - let (pool, background_task, notifier) = - BasicPool::new_test(Arc::new(TestApi::with_alice_nonce(209))); +fn maintained_pool() -> (BasicPool, Arc, futures::executor::ThreadPool) { + let api = Arc::new(TestApi::with_alice_nonce(209)); + let (pool, background_task) = create_basic_pool_with_genesis(api.clone()); let thread_pool = futures::executor::ThreadPool::new().unwrap(); thread_pool.spawn_ok(background_task); - (pool, thread_pool, notifier) + (pool, api, thread_pool) +} + +fn create_basic_pool_with_genesis( + test_api: Arc, +) -> (BasicPool, Pin + Send>>) { + let genesis_hash = { + test_api + .chain() + .read() + .block_by_number + .get(&0) + .map(|blocks| blocks[0].0.header.hash()) + .expect("there is block 0. qed") + }; + BasicPool::new_test(test_api, genesis_hash, genesis_hash) +} + +fn create_basic_pool(test_api: TestApi) -> BasicPool { + create_basic_pool_with_genesis(Arc::from(test_api)).0 } const SOURCE: TransactionSource = TransactionSource::External; @@ -135,7 +155,7 @@ fn should_ban_invalid_transactions() { #[test] fn only_prune_on_new_best() { - let pool = maintained_pool().0; + let (pool, api, _) = maintained_pool(); let uxt = uxt(Alice, 209); let _ = block_on(pool.submit_and_watch(&BlockId::number(0), SOURCE, uxt.clone())) @@ -143,7 +163,7 @@ fn only_prune_on_new_best() { pool.api().push_block(1, vec![uxt.clone()], true); assert_eq!(pool.status().ready, 1); - let header = pool.api().push_block(2, vec![uxt], true); + let header = api.push_block(2, vec![uxt], true); let event = ChainEvent::NewBestBlock { hash: header.hash(), tree_route: None }; block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 0); @@ -205,12 +225,12 @@ fn block_event_with_retracted( fn should_prune_old_during_maintenance() { let xt = uxt(Alice, 209); - let (pool, _guard, _notifier) = maintained_pool(); + let (pool, api, _guard) = maintained_pool(); block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.clone())).expect("1. Imported"); assert_eq!(pool.status().ready, 1); - let header = pool.api().push_block(1, vec![xt.clone()], true); + let header = api.push_block(1, vec![xt.clone()], true); block_on(pool.maintain(block_event(header))); assert_eq!(pool.status().ready, 0); @@ -221,35 +241,40 @@ fn should_revalidate_during_maintenance() { let xt1 = uxt(Alice, 209); let xt2 = uxt(Alice, 210); - let (pool, _guard, mut notifier) = maintained_pool(); + let (pool, api, _guard) = maintained_pool(); block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt1.clone())).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt2.clone())).expect("2. Imported"); + let watcher = block_on(pool.submit_and_watch(&BlockId::number(0), SOURCE, xt2.clone())) + .expect("2. Imported"); assert_eq!(pool.status().ready, 2); - assert_eq!(pool.api().validation_requests().len(), 2); + assert_eq!(api.validation_requests().len(), 2); - let header = pool.api().push_block(1, vec![xt1.clone()], true); + let header = api.push_block(1, vec![xt1.clone()], true); + + api.add_invalid(&xt2); block_on(pool.maintain(block_event(header))); assert_eq!(pool.status().ready, 1); - block_on(notifier.next()); // test that pool revalidated transaction that left ready and not included in the block - assert_eq!(pool.api().validation_requests().len(), 3); + assert_eq!( + futures::executor::block_on_stream(watcher).collect::>(), + vec![TransactionStatus::Ready, TransactionStatus::Invalid], + ); } #[test] fn should_resubmit_from_retracted_during_maintenance() { let xt = uxt(Alice, 209); - let (pool, _guard, _notifier) = maintained_pool(); + let (pool, api, _guard) = maintained_pool(); block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.clone())).expect("1. Imported"); assert_eq!(pool.status().ready, 1); - let header = pool.api().push_block(1, vec![], true); - let fork_header = pool.api().push_block(1, vec![], false); + let header = api.push_block(1, vec![], true); + let fork_header = api.push_block(1, vec![], false); - let event = block_event_with_retracted(header, fork_header.hash(), &*pool.api()); + let event = block_event_with_retracted(header, fork_header.hash(), pool.api()); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 1); @@ -259,15 +284,15 @@ fn should_resubmit_from_retracted_during_maintenance() { fn should_not_resubmit_from_retracted_during_maintenance_if_tx_is_also_in_enacted() { let xt = uxt(Alice, 209); - let (pool, _guard, _notifier) = maintained_pool(); + let (pool, api, _guard) = maintained_pool(); block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.clone())).expect("1. Imported"); assert_eq!(pool.status().ready, 1); - let header = pool.api().push_block(1, vec![xt.clone()], true); - let fork_header = pool.api().push_block(1, vec![xt], false); + let header = api.push_block(1, vec![xt.clone()], true); + let fork_header = api.push_block(1, vec![xt], false); - let event = block_event_with_retracted(header, fork_header.hash(), &*pool.api()); + let event = block_event_with_retracted(header, fork_header.hash(), pool.api()); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 0); @@ -277,19 +302,23 @@ fn should_not_resubmit_from_retracted_during_maintenance_if_tx_is_also_in_enacte fn should_not_retain_invalid_hashes_from_retracted() { let xt = uxt(Alice, 209); - let (pool, _guard, mut notifier) = maintained_pool(); + let (pool, api, _guard) = maintained_pool(); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.clone())).expect("1. Imported"); + let watcher = block_on(pool.submit_and_watch(&BlockId::number(0), SOURCE, xt.clone())) + .expect("1. Imported"); assert_eq!(pool.status().ready, 1); - let header = pool.api().push_block(1, vec![], true); - let fork_header = pool.api().push_block(1, vec![xt.clone()], false); - pool.api().add_invalid(&xt); - - let event = block_event_with_retracted(header, fork_header.hash(), &*pool.api()); + let header = api.push_block(1, vec![], true); + let fork_header = api.push_block(1, vec![xt.clone()], false); + api.add_invalid(&xt); + let event = block_event_with_retracted(header, fork_header.hash(), pool.api()); block_on(pool.maintain(event)); - block_on(notifier.next()); + + assert_eq!( + futures::executor::block_on_stream(watcher).collect::>(), + vec![TransactionStatus::Ready, TransactionStatus::Invalid], + ); assert_eq!(pool.status().ready, 0); } @@ -300,26 +329,30 @@ fn should_revalidate_across_many_blocks() { let xt2 = uxt(Alice, 210); let xt3 = uxt(Alice, 211); - let (pool, _guard, mut notifier) = maintained_pool(); + let (pool, api, _guard) = maintained_pool(); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt1.clone())).expect("1. Imported"); + let watcher1 = block_on(pool.submit_and_watch(&BlockId::number(0), SOURCE, xt1.clone())) + .expect("1. Imported"); block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt2.clone())).expect("1. Imported"); assert_eq!(pool.status().ready, 2); - let header = pool.api().push_block(1, vec![], true); + let header = api.push_block(1, vec![], true); block_on(pool.maintain(block_event(header))); - block_on(notifier.next()); block_on(pool.submit_one(&BlockId::number(1), SOURCE, xt3.clone())).expect("1. Imported"); assert_eq!(pool.status().ready, 3); - let header = pool.api().push_block(2, vec![xt1.clone()], true); - block_on(pool.maintain(block_event(header))); - block_on(notifier.next()); + let header = api.push_block(2, vec![xt1.clone()], true); + let block_hash = header.hash(); + block_on(pool.maintain(block_event(header.clone()))); + + block_on( + watcher1 + .take_while(|s| future::ready(*s != TransactionStatus::InBlock((block_hash, 0)))) + .collect::>(), + ); assert_eq!(pool.status().ready, 2); - // xt1 and xt2 validated twice, then xt3 once, then xt2 and xt3 again - assert_eq!(pool.api().validation_requests().len(), 7); } #[test] @@ -329,7 +362,7 @@ fn should_push_watchers_during_maintenance() { } // given - let (pool, _guard, mut notifier) = maintained_pool(); + let (pool, api, _guard) = maintained_pool(); let tx0 = alice_uxt(0); let watcher0 = @@ -349,18 +382,16 @@ fn should_push_watchers_during_maintenance() { assert_eq!(pool.status().ready, 5); // when - pool.api().add_invalid(&tx3); - pool.api().add_invalid(&tx4); + api.add_invalid(&tx3); + api.add_invalid(&tx4); // clear timer events if any - let header = pool.api().push_block(1, vec![], true); + let header = api.push_block(1, vec![], true); block_on(pool.maintain(block_event(header))); - block_on(notifier.next()); // then // hash3 is now invalid // hash4 is now invalid - assert_eq!(pool.status().ready, 3); assert_eq!( futures::executor::block_on_stream(watcher3).collect::>(), vec![TransactionStatus::Ready, TransactionStatus::Invalid], @@ -369,13 +400,14 @@ fn should_push_watchers_during_maintenance() { futures::executor::block_on_stream(watcher4).collect::>(), vec![TransactionStatus::Ready, TransactionStatus::Invalid], ); + assert_eq!(pool.status().ready, 3); // when - let header = pool.api().push_block(2, vec![tx0, tx1, tx2], true); + let header = api.push_block(2, vec![tx0, tx1, tx2], true); let header_hash = header.hash(); block_on(pool.maintain(block_event(header))); - let event = ChainEvent::Finalized { hash: header_hash.clone() }; + let event = ChainEvent::Finalized { hash: header_hash, tree_route: Arc::from(vec![]) }; block_on(pool.maintain(event)); // then @@ -386,45 +418,34 @@ fn should_push_watchers_during_maintenance() { futures::executor::block_on_stream(watcher0).collect::>(), vec![ TransactionStatus::Ready, - TransactionStatus::InBlock(header_hash.clone()), - TransactionStatus::Finalized(header_hash.clone()) + TransactionStatus::InBlock((header_hash, 0)), + TransactionStatus::Finalized((header_hash, 0)) ], ); assert_eq!( futures::executor::block_on_stream(watcher1).collect::>(), vec![ TransactionStatus::Ready, - TransactionStatus::InBlock(header_hash.clone()), - TransactionStatus::Finalized(header_hash.clone()) + TransactionStatus::InBlock((header_hash, 1)), + TransactionStatus::Finalized((header_hash, 1)) ], ); assert_eq!( futures::executor::block_on_stream(watcher2).collect::>(), vec![ TransactionStatus::Ready, - TransactionStatus::InBlock(header_hash.clone()), - TransactionStatus::Finalized(header_hash.clone()) + TransactionStatus::InBlock((header_hash, 2)), + TransactionStatus::Finalized((header_hash, 2)) ], ); } -#[test] -fn can_track_heap_size() { - let (pool, _guard, _notifier) = maintained_pool(); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 210))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 211))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 212))).expect("1. Imported"); - - assert!(parity_util_mem::malloc_size(&pool) > 3000); -} - #[test] fn finalization() { let xt = uxt(Alice, 209); let api = TestApi::with_alice_nonce(209); api.push_block(1, vec![], true); - let (pool, _background, _) = BasicPool::new_test(api.into()); + let pool = create_basic_pool(api); let watcher = block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, xt.clone())) .expect("1. Imported"); pool.api().push_block(2, vec![xt.clone()], true); @@ -433,13 +454,13 @@ fn finalization() { let event = ChainEvent::NewBestBlock { hash: header.hash(), tree_route: None }; block_on(pool.maintain(event)); - let event = ChainEvent::Finalized { hash: header.hash() }; + let event = ChainEvent::Finalized { hash: header.hash(), tree_route: Arc::from(vec![]) }; block_on(pool.maintain(event)); let mut stream = futures::executor::block_on_stream(watcher); assert_eq!(stream.next(), Some(TransactionStatus::Ready)); - assert_eq!(stream.next(), Some(TransactionStatus::InBlock(header.hash()))); - assert_eq!(stream.next(), Some(TransactionStatus::Finalized(header.hash()))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((header.hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((header.hash(), 0)))); assert_eq!(stream.next(), None); } @@ -447,9 +468,9 @@ fn finalization() { fn fork_aware_finalization() { let api = TestApi::empty(); // starting block A1 (last finalized.) - api.push_block(1, vec![], true); + let a_header = api.push_block(1, vec![], true); - let (pool, _background, _) = BasicPool::new_test(api.into()); + let pool = create_basic_pool(api); let mut canon_watchers = vec![]; let from_alice = uxt(Alice, 1); @@ -464,10 +485,13 @@ fn fork_aware_finalization() { let from_dave_watcher; let from_bob_watcher; let b1; + let c1; let d1; let c2; let d2; + block_on(pool.maintain(block_event(a_header))); + // block B1 { let watcher = @@ -477,11 +501,12 @@ fn fork_aware_finalization() { canon_watchers.push((watcher, header.hash())); assert_eq!(pool.status().ready, 1); + log::trace!(target:"txpool", ">> B1: {:?} {:?}", header.hash(), header); let event = ChainEvent::NewBestBlock { hash: header.hash(), tree_route: None }; b1 = header.hash(); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 0); - let event = ChainEvent::Finalized { hash: b1 }; + let event = ChainEvent::Finalized { hash: b1, tree_route: Arc::from(vec![]) }; block_on(pool.maintain(event)); } @@ -492,6 +517,7 @@ fn fork_aware_finalization() { block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_dave.clone())) .expect("1. Imported"); assert_eq!(pool.status().ready, 1); + log::trace!(target:"txpool", ">> C2: {:?} {:?}", header.hash(), header); let event = ChainEvent::NewBestBlock { hash: header.hash(), tree_route: None }; c2 = header.hash(); block_on(pool.maintain(event)); @@ -506,6 +532,7 @@ fn fork_aware_finalization() { assert_eq!(pool.status().ready, 1); let header = pool.api().push_block_with_parent(c2, vec![from_bob.clone()], true); + log::trace!(target:"txpool", ">> D2: {:?} {:?}", header.hash(), header); let event = ChainEvent::NewBestBlock { hash: header.hash(), tree_route: None }; d2 = header.hash(); block_on(pool.maintain(event)); @@ -518,14 +545,15 @@ fn fork_aware_finalization() { block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_charlie.clone())) .expect("1.Imported"); assert_eq!(pool.status().ready, 1); - let header = pool.api().push_block(3, vec![from_charlie.clone()], true); - + let header = pool.api().push_block_with_parent(b1, vec![from_charlie.clone()], true); + log::trace!(target:"txpool", ">> C1: {:?} {:?}", header.hash(), header); + c1 = header.hash(); canon_watchers.push((watcher, header.hash())); - let event = block_event_with_retracted(header.clone(), d2, &*pool.api()); + let event = block_event_with_retracted(header.clone(), d2, pool.api()); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 2); - let event = ChainEvent::Finalized { hash: header.hash() }; + let event = ChainEvent::Finalized { hash: header.hash(), tree_route: Arc::from(vec![]) }; block_on(pool.maintain(event)); } @@ -535,56 +563,59 @@ fn fork_aware_finalization() { let w = block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, xt.clone())) .expect("1. Imported"); assert_eq!(pool.status().ready, 3); - let header = pool.api().push_block(4, vec![xt.clone()], true); + let header = pool.api().push_block_with_parent(c1, vec![xt.clone()], true); + log::trace!(target:"txpool", ">> D1: {:?} {:?}", header.hash(), header); + d1 = header.hash(); canon_watchers.push((w, header.hash())); let event = ChainEvent::NewBestBlock { hash: header.hash(), tree_route: None }; - d1 = header.hash(); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 2); - let event = ChainEvent::Finalized { hash: d1 }; + let event = ChainEvent::Finalized { hash: d1, tree_route: Arc::from(vec![]) }; block_on(pool.maintain(event)); } let e1; - // block e1 + // block E1 { - let header = pool.api().push_block(5, vec![from_dave, from_bob], true); + let header = pool.api().push_block_with_parent(d1, vec![from_dave, from_bob], true); + log::trace!(target:"txpool", ">> E1: {:?} {:?}", header.hash(), header); e1 = header.hash(); let event = ChainEvent::NewBestBlock { hash: header.hash(), tree_route: None }; block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 0); - block_on(pool.maintain(ChainEvent::Finalized { hash: e1 })); + block_on(pool.maintain(ChainEvent::Finalized { hash: e1, tree_route: Arc::from(vec![]) })); } for (canon_watcher, h) in canon_watchers { let mut stream = futures::executor::block_on_stream(canon_watcher); assert_eq!(stream.next(), Some(TransactionStatus::Ready)); - assert_eq!(stream.next(), Some(TransactionStatus::InBlock(h.clone()))); - assert_eq!(stream.next(), Some(TransactionStatus::Finalized(h))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((h, 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((h, 0)))); assert_eq!(stream.next(), None); } { let mut stream = futures::executor::block_on_stream(from_dave_watcher); assert_eq!(stream.next(), Some(TransactionStatus::Ready)); - assert_eq!(stream.next(), Some(TransactionStatus::InBlock(c2.clone()))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((c2, 0)))); assert_eq!(stream.next(), Some(TransactionStatus::Retracted(c2))); assert_eq!(stream.next(), Some(TransactionStatus::Ready)); - assert_eq!(stream.next(), Some(TransactionStatus::InBlock(e1))); - assert_eq!(stream.next(), Some(TransactionStatus::Finalized(e1.clone()))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((e1, 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((e1, 0)))); assert_eq!(stream.next(), None); } { let mut stream = futures::executor::block_on_stream(from_bob_watcher); assert_eq!(stream.next(), Some(TransactionStatus::Ready)); - assert_eq!(stream.next(), Some(TransactionStatus::InBlock(d2.clone()))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((d2, 0)))); assert_eq!(stream.next(), Some(TransactionStatus::Retracted(d2))); assert_eq!(stream.next(), Some(TransactionStatus::Ready)); - assert_eq!(stream.next(), Some(TransactionStatus::InBlock(e1))); - assert_eq!(stream.next(), Some(TransactionStatus::Finalized(e1.clone()))); + // In block e1 we submitted: [dave, bob] xts in this order. + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((e1, 1)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((e1, 1)))); assert_eq!(stream.next(), None); } } @@ -597,7 +628,7 @@ fn prune_and_retract_tx_at_same_time() { // starting block A1 (last finalized.) api.push_block(1, vec![], true); - let (pool, _background, _) = BasicPool::new_test(api.into()); + let pool = create_basic_pool(api); let from_alice = uxt(Alice, 1); pool.api().increment_nonce(Alice.into()); @@ -621,11 +652,11 @@ fn prune_and_retract_tx_at_same_time() { let header = pool.api().push_block(2, vec![from_alice.clone()], false); assert_eq!(pool.status().ready, 0); - let event = block_event_with_retracted(header.clone(), b1, &*pool.api()); + let event = block_event_with_retracted(header.clone(), b1, pool.api()); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 0); - let event = ChainEvent::Finalized { hash: header.hash() }; + let event = ChainEvent::Finalized { hash: header.hash(), tree_route: Arc::from(vec![]) }; block_on(pool.maintain(event)); header.hash() @@ -634,10 +665,10 @@ fn prune_and_retract_tx_at_same_time() { { let mut stream = futures::executor::block_on_stream(watcher); assert_eq!(stream.next(), Some(TransactionStatus::Ready)); - assert_eq!(stream.next(), Some(TransactionStatus::InBlock(b1.clone()))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b1, 0)))); assert_eq!(stream.next(), Some(TransactionStatus::Retracted(b1))); - assert_eq!(stream.next(), Some(TransactionStatus::InBlock(b2.clone()))); - assert_eq!(stream.next(), Some(TransactionStatus::Finalized(b2))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b2, 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((b2, 0)))); assert_eq!(stream.next(), None); } } @@ -663,7 +694,7 @@ fn resubmit_tx_of_fork_that_is_not_part_of_retracted() { // starting block A1 (last finalized.) api.push_block(1, vec![], true); - let (pool, _background, _) = BasicPool::new_test(api.into()); + let pool = create_basic_pool(api); let tx0 = uxt(Alice, 1); let tx1 = uxt(Dave, 2); @@ -696,7 +727,7 @@ fn resubmit_tx_of_fork_that_is_not_part_of_retracted() { // Block D2 { let header = pool.api().push_block(2, vec![], false); - let event = block_event_with_retracted(header, d0, &*pool.api()); + let event = block_event_with_retracted(header, d0, pool.api()); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 2); } @@ -708,7 +739,7 @@ fn resubmit_from_retracted_fork() { // starting block A1 (last finalized.) api.push_block(1, vec![], true); - let (pool, _background, _) = BasicPool::new_test(api.into()); + let pool = create_basic_pool(api); let tx0 = uxt(Alice, 1); let tx1 = uxt(Dave, 2); @@ -769,7 +800,7 @@ fn resubmit_from_retracted_fork() { let e1 = { let _ = block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, tx4.clone())) .expect("1. Imported"); - let header = pool.api().push_block_with_parent(d1.clone(), vec![tx4.clone()], true); + let header = pool.api().push_block_with_parent(d1, vec![tx4.clone()], true); assert_eq!(pool.status().ready, 2); header.hash() }; @@ -778,7 +809,7 @@ fn resubmit_from_retracted_fork() { let f1_header = { let _ = block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, tx5.clone())) .expect("1. Imported"); - let header = pool.api().push_block_with_parent(e1.clone(), vec![tx5.clone()], true); + let header = pool.api().push_block_with_parent(e1, vec![tx5.clone()], true); // Don't announce the block event to the pool directly, because we will // re-org to this block. assert_eq!(pool.status().ready, 3); @@ -789,7 +820,7 @@ fn resubmit_from_retracted_fork() { let expected_ready = vec![tx3, tx4, tx5].iter().map(Encode::encode).collect::>(); assert_eq!(expected_ready, ready); - let event = block_event_with_retracted(f1_header, f0, &*pool.api()); + let event = block_event_with_retracted(f1_header, f0, pool.api()); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 3); @@ -800,7 +831,7 @@ fn resubmit_from_retracted_fork() { #[test] fn ready_set_should_not_resolve_before_block_update() { - let (pool, _guard, _notifier) = maintained_pool(); + let (pool, _api, _guard) = maintained_pool(); let xt1 = uxt(Alice, 209); block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt1.clone())).expect("1. Imported"); @@ -809,8 +840,8 @@ fn ready_set_should_not_resolve_before_block_update() { #[test] fn ready_set_should_resolve_after_block_update() { - let (pool, _guard, _notifier) = maintained_pool(); - let header = pool.api().push_block(1, vec![], true); + let (pool, api, _guard) = maintained_pool(); + let header = api.push_block(1, vec![], true); let xt1 = uxt(Alice, 209); @@ -822,8 +853,8 @@ fn ready_set_should_resolve_after_block_update() { #[test] fn ready_set_should_eventually_resolve_when_block_update_arrives() { - let (pool, _guard, _notifier) = maintained_pool(); - let header = pool.api().push_block(1, vec![], true); + let (pool, api, _guard) = maintained_pool(); + let header = api.push_block(1, vec![], true); let xt1 = uxt(Alice, 209); @@ -833,7 +864,7 @@ fn ready_set_should_eventually_resolve_when_block_update_arrives() { let mut context = futures::task::Context::from_waker(&noop_waker); let mut ready_set_future = pool.ready_at(1); - if let Poll::Ready(_) = ready_set_future.poll_unpin(&mut context) { + if ready_set_future.poll_unpin(&mut context).is_ready() { panic!("Ready set should not be ready before block update!"); } @@ -852,16 +883,15 @@ fn ready_set_should_eventually_resolve_when_block_update_arrives() { #[test] fn should_not_accept_old_signatures() { - use std::convert::TryFrom; - let client = Arc::new(substrate_test_runtime_client::new()); - + let best_hash = client.info().best_hash; + let finalized_hash = client.info().finalized_hash; let pool = Arc::new( - BasicPool::new_test(Arc::new(FullChainApi::new( - client, - None, - &sp_core::testing::TaskExecutor::new(), - ))) + BasicPool::new_test( + Arc::new(FullChainApi::new(client, None, &sp_core::testing::TaskExecutor::new())), + best_hash, + finalized_hash, + ) .0, ); @@ -870,7 +900,7 @@ fn should_not_accept_old_signatures() { // generated with schnorrkel 0.1.1 from `_bytes` let old_singature = sp_core::sr25519::Signature::try_from( - &hex::decode( + &array_bytes::hex2bytes( "c427eb672e8c441c86d31f1a81b22b43102058e9ce237cabe9897ea5099ffd426\ cd1c6a1f4f2869c3df57901d36bedcb295657adb3a4355add86ed234eb83108", ) @@ -897,12 +927,19 @@ fn should_not_accept_old_signatures() { fn import_notification_to_pool_maintain_works() { let mut client = Arc::new(substrate_test_runtime_client::new()); + let best_hash = client.info().best_hash; + let finalized_hash = client.info().finalized_hash; + let pool = Arc::new( - BasicPool::new_test(Arc::new(FullChainApi::new( - client.clone(), - None, - &sp_core::testing::TaskExecutor::new(), - ))) + BasicPool::new_test( + Arc::new(FullChainApi::new( + client.clone(), + None, + &sp_core::testing::TaskExecutor::new(), + )), + best_hash, + finalized_hash, + ) .0, ); @@ -929,13 +966,13 @@ fn import_notification_to_pool_maintain_works() { // When we prune transactions, we need to make sure that we remove #[test] fn pruning_a_transaction_should_remove_it_from_best_transaction() { - let (pool, _guard, _notifier) = maintained_pool(); + let (pool, api, _guard) = maintained_pool(); let xt1 = Extrinsic::IncludeData(Vec::new()); block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt1.clone())).expect("1. Imported"); assert_eq!(pool.status().ready, 1); - let header = pool.api().push_block(1, vec![xt1.clone()], true); + let header = api.push_block(1, vec![xt1.clone()], true); // This will prune `xt1`. block_on(pool.maintain(block_event(header))); @@ -943,26 +980,6 @@ fn pruning_a_transaction_should_remove_it_from_best_transaction() { assert_eq!(pool.status().ready, 0); } -#[test] -fn only_revalidate_on_best_block() { - let xt = uxt(Alice, 209); - - let (pool, _guard, mut notifier) = maintained_pool(); - - block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.clone())).expect("1. Imported"); - assert_eq!(pool.status().ready, 1); - - let header = pool.api().push_block(1, vec![], true); - - pool.api().push_block(2, vec![], false); - pool.api().push_block(2, vec![], false); - - block_on(pool.maintain(block_event(header))); - block_on(notifier.next()); - - assert_eq!(pool.status().ready, 1); -} - #[test] fn stale_transactions_are_pruned() { sp_tracing::try_init_simple(); @@ -974,7 +991,7 @@ fn stale_transactions_are_pruned() { Transfer { from: Alice.into(), to: Bob.into(), nonce: 3, amount: 1 }, ]; - let (pool, _guard, _notifier) = maintained_pool(); + let (pool, api, _guard) = maintained_pool(); xts.into_iter().for_each(|xt| { block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.into_signed_tx())) @@ -992,7 +1009,7 @@ fn stale_transactions_are_pruned() { ]; // Import block - let header = pool.api().push_block(1, xts, true); + let header = api.push_block(1, xts, true); block_on(pool.maintain(block_event(header))); // The imported transactions have a different hash and should not evict our initial // transactions. @@ -1000,10 +1017,547 @@ fn stale_transactions_are_pruned() { // Import enough blocks to make our transactions stale for n in 1..66 { - let header = pool.api().push_block(n, vec![], true); + let header = api.push_block(n, vec![], true); block_on(pool.maintain(block_event(header))); } assert_eq!(pool.status().future, 0); assert_eq!(pool.status().ready, 0); } + +#[test] +fn finalized_only_handled_correctly() { + sp_tracing::try_init_simple(); + let xt = uxt(Alice, 209); + + let (pool, api, _guard) = maintained_pool(); + + let watcher = block_on(pool.submit_and_watch(&BlockId::number(0), SOURCE, xt.clone())) + .expect("1. Imported"); + assert_eq!(pool.status().ready, 1); + + let header = api.push_block(1, vec![xt], false); + + let event = + ChainEvent::Finalized { hash: header.clone().hash(), tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + + assert_eq!(pool.status().ready, 0); + + { + let mut stream = futures::executor::block_on_stream(watcher); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((header.clone().hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((header.hash(), 0)))); + assert_eq!(stream.next(), None); + } +} + +#[test] +fn best_block_after_finalized_handled_correctly() { + sp_tracing::try_init_simple(); + let xt = uxt(Alice, 209); + + let (pool, api, _guard) = maintained_pool(); + + let watcher = block_on(pool.submit_and_watch(&BlockId::number(0), SOURCE, xt.clone())) + .expect("1. Imported"); + assert_eq!(pool.status().ready, 1); + + let header = api.push_block(1, vec![xt], true); + + let event = + ChainEvent::Finalized { hash: header.clone().hash(), tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + block_on(pool.maintain(block_event(header.clone()))); + + assert_eq!(pool.status().ready, 0); + + { + let mut stream = futures::executor::block_on_stream(watcher); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((header.clone().hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((header.hash(), 0)))); + assert_eq!(stream.next(), None); + } +} + +#[test] +fn switching_fork_with_finalized_works() { + sp_tracing::try_init_simple(); + let api = TestApi::empty(); + // starting block A1 (last finalized.) + let a_header = api.push_block(1, vec![], true); + + let pool = create_basic_pool(api); + + let from_alice = uxt(Alice, 1); + let from_bob = uxt(Bob, 2); + pool.api().increment_nonce(Alice.into()); + pool.api().increment_nonce(Bob.into()); + + let from_alice_watcher; + let from_bob_watcher; + let b1_header; + let b2_header; + + // block B1 + { + from_alice_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_alice.clone())) + .expect("1. Imported"); + let header = + pool.api() + .push_block_with_parent(a_header.hash(), vec![from_alice.clone()], true); + assert_eq!(pool.status().ready, 1); + log::trace!(target:"txpool", ">> B1: {:?} {:?}", header.hash(), header); + b1_header = header; + } + + // block B2 + { + from_bob_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_bob.clone())) + .expect("1. Imported"); + let header = pool.api().push_block_with_parent( + a_header.hash(), + vec![from_alice.clone(), from_bob.clone()], + true, + ); + assert_eq!(pool.status().ready, 2); + + log::trace!(target:"txpool", ">> B2: {:?} {:?}", header.hash(), header); + b2_header = header; + } + + { + let event = ChainEvent::NewBestBlock { hash: b1_header.hash(), tree_route: None }; + block_on(pool.maintain(event)); + assert_eq!(pool.status().ready, 1); + } + + { + let event = ChainEvent::Finalized { hash: b2_header.hash(), tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + } + + { + let mut stream = futures::executor::block_on_stream(from_alice_watcher); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b1_header.hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Retracted(b1_header.hash()))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b2_header.hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((b2_header.hash(), 0)))); + assert_eq!(stream.next(), None); + } + + { + let mut stream = futures::executor::block_on_stream(from_bob_watcher); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b2_header.hash(), 1)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((b2_header.hash(), 1)))); + assert_eq!(stream.next(), None); + } +} + +#[test] +fn switching_fork_multiple_times_works() { + sp_tracing::try_init_simple(); + let api = TestApi::empty(); + // starting block A1 (last finalized.) + let a_header = api.push_block(1, vec![], true); + + let pool = create_basic_pool(api); + + let from_alice = uxt(Alice, 1); + let from_bob = uxt(Bob, 2); + pool.api().increment_nonce(Alice.into()); + pool.api().increment_nonce(Bob.into()); + + let from_alice_watcher; + let from_bob_watcher; + let b1_header; + let b2_header; + + // block B1 + { + from_alice_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_alice.clone())) + .expect("1. Imported"); + let header = + pool.api() + .push_block_with_parent(a_header.hash(), vec![from_alice.clone()], true); + assert_eq!(pool.status().ready, 1); + log::trace!(target:"txpool", ">> B1: {:?} {:?}", header.hash(), header); + b1_header = header; + } + + // block B2 + { + from_bob_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_bob.clone())) + .expect("1. Imported"); + let header = pool.api().push_block_with_parent( + a_header.hash(), + vec![from_alice.clone(), from_bob.clone()], + true, + ); + assert_eq!(pool.status().ready, 2); + + log::trace!(target:"txpool", ">> B2: {:?} {:?}", header.hash(), header); + b2_header = header; + } + + { + // phase-0 + let event = ChainEvent::NewBestBlock { hash: b1_header.hash(), tree_route: None }; + block_on(pool.maintain(event)); + assert_eq!(pool.status().ready, 1); + } + + { + // phase-1 + let event = block_event_with_retracted(b2_header.clone(), b1_header.hash(), pool.api()); + block_on(pool.maintain(event)); + assert_eq!(pool.status().ready, 0); + } + + { + // phase-2 + let event = block_event_with_retracted(b1_header.clone(), b2_header.hash(), pool.api()); + block_on(pool.maintain(event)); + assert_eq!(pool.status().ready, 1); + } + + { + // phase-3 + let event = ChainEvent::Finalized { hash: b2_header.hash(), tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + } + + { + let mut stream = futures::executor::block_on_stream(from_alice_watcher); + //phase-0 + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b1_header.hash(), 0)))); + //phase-1 + assert_eq!(stream.next(), Some(TransactionStatus::Retracted(b1_header.hash()))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b2_header.hash(), 0)))); + //phase-2 + assert_eq!(stream.next(), Some(TransactionStatus::Retracted(b2_header.hash()))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b1_header.hash(), 0)))); + //phase-3 + assert_eq!(stream.next(), Some(TransactionStatus::Retracted(b1_header.hash()))); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b2_header.hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((b2_header.hash(), 0)))); + assert_eq!(stream.next(), None); + } + + { + let mut stream = futures::executor::block_on_stream(from_bob_watcher); + //phase-1 + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b2_header.hash(), 1)))); + //phase-2 + assert_eq!(stream.next(), Some(TransactionStatus::Retracted(b2_header.hash()))); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + //phase-3 + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b2_header.hash(), 1)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((b2_header.hash(), 1)))); + assert_eq!(stream.next(), None); + } +} + +#[test] +fn two_blocks_delayed_finalization_works() { + sp_tracing::try_init_simple(); + let api = TestApi::empty(); + // starting block A1 (last finalized.) + let a_header = api.push_block(1, vec![], true); + + let pool = create_basic_pool(api); + + let from_alice = uxt(Alice, 1); + let from_bob = uxt(Bob, 2); + let from_charlie = uxt(Charlie, 3); + pool.api().increment_nonce(Alice.into()); + pool.api().increment_nonce(Bob.into()); + pool.api().increment_nonce(Charlie.into()); + + let from_alice_watcher; + let from_bob_watcher; + let from_charlie_watcher; + let b1_header; + let c1_header; + let d1_header; + + // block B1 + { + from_alice_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_alice.clone())) + .expect("1. Imported"); + let header = + pool.api() + .push_block_with_parent(a_header.hash(), vec![from_alice.clone()], true); + assert_eq!(pool.status().ready, 1); + + log::trace!(target:"txpool", ">> B1: {:?} {:?}", header.hash(), header); + b1_header = header; + } + + // block C1 + { + from_bob_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_bob.clone())) + .expect("1. Imported"); + let header = + pool.api() + .push_block_with_parent(b1_header.hash(), vec![from_bob.clone()], true); + assert_eq!(pool.status().ready, 2); + + log::trace!(target:"txpool", ">> C1: {:?} {:?}", header.hash(), header); + c1_header = header; + } + + // block D1 + { + from_charlie_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_charlie.clone())) + .expect("1. Imported"); + let header = + pool.api() + .push_block_with_parent(c1_header.hash(), vec![from_charlie.clone()], true); + assert_eq!(pool.status().ready, 3); + + log::trace!(target:"txpool", ">> D1: {:?} {:?}", header.hash(), header); + d1_header = header; + } + + { + let event = ChainEvent::Finalized { hash: a_header.hash(), tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + assert_eq!(pool.status().ready, 3); + } + + { + let event = ChainEvent::NewBestBlock { hash: d1_header.hash(), tree_route: None }; + block_on(pool.maintain(event)); + assert_eq!(pool.status().ready, 0); + } + + { + let event = ChainEvent::Finalized { + hash: c1_header.hash(), + tree_route: Arc::from(vec![b1_header.hash()]), + }; + block_on(pool.maintain(event)); + } + + // this is to collect events from_charlie_watcher and make sure nothing was retracted + { + let event = ChainEvent::Finalized { hash: d1_header.hash(), tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + } + + { + let mut stream = futures::executor::block_on_stream(from_alice_watcher); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b1_header.hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((b1_header.hash(), 0)))); + assert_eq!(stream.next(), None); + } + + { + let mut stream = futures::executor::block_on_stream(from_bob_watcher); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((c1_header.hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((c1_header.hash(), 0)))); + assert_eq!(stream.next(), None); + } + + { + let mut stream = futures::executor::block_on_stream(from_charlie_watcher); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((d1_header.hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((d1_header.hash(), 0)))); + assert_eq!(stream.next(), None); + } +} + +#[test] +fn delayed_finalization_does_not_retract() { + sp_tracing::try_init_simple(); + let api = TestApi::empty(); + // starting block A1 (last finalized.) + let a_header = api.push_block(1, vec![], true); + + let pool = create_basic_pool(api); + + let from_alice = uxt(Alice, 1); + let from_bob = uxt(Bob, 2); + pool.api().increment_nonce(Alice.into()); + pool.api().increment_nonce(Bob.into()); + + let from_alice_watcher; + let from_bob_watcher; + let b1_header; + let c1_header; + + // block B1 + { + from_alice_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_alice.clone())) + .expect("1. Imported"); + let header = + pool.api() + .push_block_with_parent(a_header.hash(), vec![from_alice.clone()], true); + assert_eq!(pool.status().ready, 1); + + log::trace!(target:"txpool", ">> B1: {:?} {:?}", header.hash(), header); + b1_header = header; + } + + // block C1 + { + from_bob_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_bob.clone())) + .expect("1. Imported"); + let header = + pool.api() + .push_block_with_parent(b1_header.hash(), vec![from_bob.clone()], true); + assert_eq!(pool.status().ready, 2); + + log::trace!(target:"txpool", ">> C1: {:?} {:?}", header.hash(), header); + c1_header = header; + } + + { + // phase-0 + let event = ChainEvent::NewBestBlock { hash: b1_header.hash(), tree_route: None }; + block_on(pool.maintain(event)); + assert_eq!(pool.status().ready, 1); + } + + { + // phase-1 + let event = ChainEvent::NewBestBlock { hash: c1_header.hash(), tree_route: None }; + block_on(pool.maintain(event)); + assert_eq!(pool.status().ready, 0); + } + + { + // phase-2 + let event = ChainEvent::Finalized { hash: b1_header.hash(), tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + } + + { + // phase-3 + let event = ChainEvent::Finalized { hash: c1_header.hash(), tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + } + + { + let mut stream = futures::executor::block_on_stream(from_alice_watcher); + //phase-0 + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b1_header.hash(), 0)))); + //phase-2 + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((b1_header.hash(), 0)))); + assert_eq!(stream.next(), None); + } + + { + let mut stream = futures::executor::block_on_stream(from_bob_watcher); + //phase-0 + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + //phase-1 + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((c1_header.hash(), 0)))); + //phase-3 + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((c1_header.hash(), 0)))); + assert_eq!(stream.next(), None); + } +} + +#[test] +fn best_block_after_finalization_does_not_retract() { + sp_tracing::try_init_simple(); + let api = TestApi::empty(); + // starting block A1 (last finalized.) + let a_header = api.push_block(1, vec![], true); + + let pool = create_basic_pool(api); + + let from_alice = uxt(Alice, 1); + let from_bob = uxt(Bob, 2); + pool.api().increment_nonce(Alice.into()); + pool.api().increment_nonce(Bob.into()); + + let from_alice_watcher; + let from_bob_watcher; + let b1_header; + let c1_header; + + // block B1 + { + from_alice_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_alice.clone())) + .expect("1. Imported"); + let header = + pool.api() + .push_block_with_parent(a_header.hash(), vec![from_alice.clone()], true); + assert_eq!(pool.status().ready, 1); + + log::trace!(target:"txpool", ">> B1: {:?} {:?}", header.hash(), header); + b1_header = header; + } + + // block C1 + { + from_bob_watcher = + block_on(pool.submit_and_watch(&BlockId::number(1), SOURCE, from_bob.clone())) + .expect("1. Imported"); + let header = + pool.api() + .push_block_with_parent(b1_header.hash(), vec![from_bob.clone()], true); + assert_eq!(pool.status().ready, 2); + + log::trace!(target:"txpool", ">> C1: {:?} {:?}", header.hash(), header); + c1_header = header; + } + + { + let event = ChainEvent::Finalized { hash: a_header.hash(), tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + } + + { + let event = ChainEvent::Finalized { + hash: c1_header.hash(), + tree_route: Arc::from(vec![a_header.hash(), b1_header.hash()]), + }; + block_on(pool.maintain(event)); + assert_eq!(pool.status().ready, 0); + } + + { + let event = ChainEvent::NewBestBlock { hash: b1_header.hash(), tree_route: None }; + block_on(pool.maintain(event)); + } + + { + let mut stream = futures::executor::block_on_stream(from_alice_watcher); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((b1_header.hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((b1_header.hash(), 0)))); + assert_eq!(stream.next(), None); + } + + { + let mut stream = futures::executor::block_on_stream(from_bob_watcher); + assert_eq!(stream.next(), Some(TransactionStatus::Ready)); + assert_eq!(stream.next(), Some(TransactionStatus::InBlock((c1_header.hash(), 0)))); + assert_eq!(stream.next(), Some(TransactionStatus::Finalized((c1_header.hash(), 0)))); + assert_eq!(stream.next(), None); + } +} diff --git a/client/transaction-pool/tests/revalidation.rs b/client/transaction-pool/tests/revalidation.rs deleted file mode 100644 index b2c8225b78f58..0000000000000 --- a/client/transaction-pool/tests/revalidation.rs +++ /dev/null @@ -1,32 +0,0 @@ -use futures::executor::block_on; -use sc_transaction_pool::test_helpers::{Pool, RevalidationQueue}; -use sc_transaction_pool_api::TransactionSource; -use sp_runtime::generic::BlockId; -use std::sync::Arc; -use substrate_test_runtime_client::AccountKeyring::*; -use substrate_test_runtime_transaction_pool::{uxt, TestApi}; - -fn setup() -> (Arc, Pool) { - let test_api = Arc::new(TestApi::empty()); - let pool = Pool::new(Default::default(), true.into(), test_api.clone()); - (test_api, pool) -} - -#[test] -fn smoky() { - let (api, pool) = setup(); - let pool = Arc::new(pool); - let queue = Arc::new(RevalidationQueue::new(api.clone(), pool.clone())); - - let uxt = uxt(Alice, 0); - let uxt_hash = - block_on(pool.submit_one(&BlockId::number(0), TransactionSource::External, uxt.clone())) - .expect("Should be valid"); - - block_on(queue.revalidate_later(0, vec![uxt_hash])); - - // revalidated in sync offload 2nd time - assert_eq!(api.validation_requests().len(), 2); - // number of ready - assert_eq!(pool.validated_pool().status().ready, 1); -} diff --git a/client/utils/Cargo.toml b/client/utils/Cargo.toml index 99765dd501dd5..082ac3b55e80d 100644 --- a/client/utils/Cargo.toml +++ b/client/utils/Cargo.toml @@ -2,19 +2,24 @@ name = "sc-utils" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "I/O for Substrate runtimes" readme = "README.md" [dependencies] -futures = "0.3.9" -lazy_static = "1.4.0" -prometheus = { version = "0.11.0", default-features = false } +futures = "0.3.21" futures-timer = "3.0.2" +lazy_static = "1.4.0" +log = "0.4" +parking_lot = "0.12.1" +prometheus = { version = "0.13.0", default-features = false } [features] default = ["metered"] metered = [] + +[dev-dependencies] +tokio-test = "0.4.2" diff --git a/client/utils/src/id_sequence.rs b/client/utils/src/id_sequence.rs new file mode 100644 index 0000000000000..2ccb4fc3f32ee --- /dev/null +++ b/client/utils/src/id_sequence.rs @@ -0,0 +1,54 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Produce opaque sequential IDs. + +/// A Sequence of IDs. +#[derive(Debug, Default)] +// The `Clone` trait is intentionally not defined on this type. +pub struct IDSequence { + next_id: u64, +} + +/// A Sequential ID. +/// +/// Its integer value is intentionally not public: it is supposed to be instantiated from within +/// this module only. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct SeqID(u64); + +impl std::fmt::Display for SeqID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl IDSequence { + /// Create a new ID-sequence. + pub fn new() -> Self { + Default::default() + } + + /// Obtain another ID from this sequence. + pub fn next_id(&mut self) -> SeqID { + let id = SeqID(self.next_id); + self.next_id += 1; + + id + } +} diff --git a/client/utils/src/lib.rs b/client/utils/src/lib.rs index b49cd60d67b13..0e47330335c27 100644 --- a/client/utils/src/lib.rs +++ b/client/utils/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -36,6 +36,9 @@ //! | entity | Name of channel passed to `tracing_unbounded` | //! | action | One of `send`/`received`/`dropped` | +pub mod id_sequence; pub mod metrics; pub mod mpsc; +pub mod notification; +pub mod pubsub; pub mod status_sinks; diff --git a/client/utils/src/metrics.rs b/client/utils/src/metrics.rs index 85ccce626bc25..457ef23e41ab1 100644 --- a/client/utils/src/metrics.rs +++ b/client/utils/src/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -29,17 +29,17 @@ use prometheus::{core::GenericCounterVec, Opts}; lazy_static! { pub static ref TOKIO_THREADS_TOTAL: GenericCounter = - GenericCounter::new("tokio_threads_total", "Total number of threads created") + GenericCounter::new("substrate_tokio_threads_total", "Total number of threads created") .expect("Creating of statics doesn't fail. qed"); pub static ref TOKIO_THREADS_ALIVE: GenericGauge = - GenericGauge::new("tokio_threads_alive", "Number of threads alive right now") + GenericGauge::new("substrate_tokio_threads_alive", "Number of threads alive right now") .expect("Creating of statics doesn't fail. qed"); } #[cfg(feature = "metered")] lazy_static! { pub static ref UNBOUNDED_CHANNELS_COUNTER : GenericCounterVec = GenericCounterVec::new( - Opts::new("unbounded_channel_len", "Items in each mpsc::unbounded instance"), + Opts::new("substrate_unbounded_channel_len", "Items in each mpsc::unbounded instance"), &["entity", "action"] // 'name of channel, send|received|dropped ).expect("Creating of statics doesn't fail. qed"); diff --git a/client/utils/src/mpsc.rs b/client/utils/src/mpsc.rs index 1739af5e9015c..ee3fba4a5ee67 100644 --- a/client/utils/src/mpsc.rs +++ b/client/utils/src/mpsc.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/client/utils/src/notification.rs b/client/utils/src/notification.rs new file mode 100644 index 0000000000000..ff527c343f9f2 --- /dev/null +++ b/client/utils/src/notification.rs @@ -0,0 +1,122 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Provides mpsc notification channel that can be instantiated +//! _after_ it's been shared to the consumer and producers entities. +//! +//! Useful when building RPC extensions where, at service definition time, we +//! don't know whether the specific interface where the RPC extension will be +//! exposed is safe or not and we want to lazily build the RPC extension +//! whenever we bind the service to an interface. +//! +//! See [`sc-service::builder::RpcExtensionBuilder`] for more details. + +use futures::stream::{FusedStream, Stream}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use crate::pubsub::{Hub, Receiver}; + +mod registry; +use registry::Registry; + +#[cfg(test)] +mod tests; + +/// Trait used to define the "tracing key" string used to tag +/// and identify the mpsc channels. +pub trait TracingKeyStr { + /// Const `str` representing the "tracing key" used to tag and identify + /// the mpsc channels owned by the object implemeting this trait. + const TRACING_KEY: &'static str; +} + +/// The receiving half of the notifications channel. +/// +/// The [`NotificationStream`] entity stores the [`Hub`] so it can be +/// used to add more subscriptions. +#[derive(Clone)] +pub struct NotificationStream { + hub: Hub, + _pd: std::marker::PhantomData, +} + +/// The receiving half of the notifications channel(s). +#[derive(Debug)] +pub struct NotificationReceiver { + receiver: Receiver, +} + +/// The sending half of the notifications channel(s). +pub struct NotificationSender { + hub: Hub, +} + +impl NotificationStream { + /// Creates a new pair of receiver and sender of `Payload` notifications. + pub fn channel() -> (NotificationSender, Self) { + let hub = Hub::new(TK::TRACING_KEY); + let sender = NotificationSender { hub: hub.clone() }; + let receiver = NotificationStream { hub, _pd: Default::default() }; + (sender, receiver) + } + + /// Subscribe to a channel through which the generic payload can be received. + pub fn subscribe(&self) -> NotificationReceiver { + let receiver = self.hub.subscribe(()); + NotificationReceiver { receiver } + } +} + +impl NotificationSender { + /// Send out a notification to all subscribers that a new payload is available for a + /// block. + pub fn notify( + &self, + payload: impl FnOnce() -> Result, + ) -> Result<(), Error> + where + Payload: Clone, + { + self.hub.send(payload) + } +} + +impl Clone for NotificationSender { + fn clone(&self) -> Self { + Self { hub: self.hub.clone() } + } +} + +impl Unpin for NotificationReceiver {} + +impl Stream for NotificationReceiver { + type Item = Payload; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.get_mut().receiver).poll_next(cx) + } +} + +impl FusedStream for NotificationReceiver { + fn is_terminated(&self) -> bool { + self.receiver.is_terminated() + } +} diff --git a/client/utils/src/notification/registry.rs b/client/utils/src/notification/registry.rs new file mode 100644 index 0000000000000..ebe41452a567e --- /dev/null +++ b/client/utils/src/notification/registry.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use std::collections::HashSet; + +use crate::{ + id_sequence::SeqID, + pubsub::{Dispatch, Subscribe, Unsubscribe}, +}; + +/// The shared structure to keep track on subscribers. +#[derive(Debug, Default)] +pub(super) struct Registry { + pub(super) subscribers: HashSet, +} + +impl Subscribe<()> for Registry { + fn subscribe(&mut self, _subs_key: (), subs_id: SeqID) { + self.subscribers.insert(subs_id); + } +} +impl Unsubscribe for Registry { + fn unsubscribe(&mut self, subs_id: SeqID) { + self.subscribers.remove(&subs_id); + } +} + +impl Dispatch for Registry +where + MakePayload: FnOnce() -> Result, + Payload: Clone, +{ + type Item = Payload; + type Ret = Result<(), Error>; + + fn dispatch(&mut self, make_payload: MakePayload, mut dispatch: F) -> Self::Ret + where + F: FnMut(&SeqID, Self::Item), + { + if !self.subscribers.is_empty() { + let payload = make_payload()?; + for subs_id in &self.subscribers { + dispatch(subs_id, payload.clone()); + } + } + Ok(()) + } +} diff --git a/client/utils/src/notification/tests.rs b/client/utils/src/notification/tests.rs new file mode 100644 index 0000000000000..a001fa7e89e95 --- /dev/null +++ b/client/utils/src/notification/tests.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; +use futures::StreamExt; + +#[derive(Clone)] +pub struct DummyTracingKey; +impl TracingKeyStr for DummyTracingKey { + const TRACING_KEY: &'static str = "test_notification_stream"; +} + +type StringStream = NotificationStream; + +#[test] +fn notification_channel_simple() { + let (sender, stream) = StringStream::channel(); + + let test_payload = String::from("test payload"); + let closure_payload = test_payload.clone(); + + // Create a future to receive a single notification + // from the stream and verify its payload. + let future = stream.subscribe().take(1).for_each(move |payload| { + let test_payload = closure_payload.clone(); + async move { + assert_eq!(payload, test_payload); + } + }); + + // Send notification. + let r: std::result::Result<(), ()> = sender.notify(|| Ok(test_payload)); + r.unwrap(); + + // Run receiver future. + tokio_test::block_on(future); +} diff --git a/client/utils/src/pubsub.rs b/client/utils/src/pubsub.rs new file mode 100644 index 0000000000000..ba6e9ddc6ca2a --- /dev/null +++ b/client/utils/src/pubsub.rs @@ -0,0 +1,261 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Provides means to implement a typical Pub/Sub mechanism. +//! +//! This module provides a type [`Hub`] which can be used both to subscribe, +//! and to send the broadcast messages. +//! +//! The [`Hub`] type is parametrized by two other types: +//! - `Message` — the type of a message that shall be delivered to the subscribers; +//! - `Registry` — implementation of the subscription/dispatch logic. +//! +//! A Registry is implemented by defining the following traits: +//! - [`Subscribe`]; +//! - [`Dispatch`]; +//! - [`Unsubscribe`]. +//! +//! As a result of subscription `Hub::subscribe` method returns an instance of +//! [`Receiver`]. That can be used as a [`Stream`] to receive the messages. +//! Upon drop the [`Receiver`] shall unregister itself from the `Hub`. + +use std::{ + collections::HashMap, + pin::Pin, + sync::{Arc, Weak}, + task::{Context, Poll}, +}; + +use futures::stream::{FusedStream, Stream}; +// use parking_lot::Mutex; +use parking_lot::ReentrantMutex; +use std::cell::RefCell; + +use crate::{ + id_sequence::SeqID, + mpsc::{TracingUnboundedReceiver, TracingUnboundedSender}, +}; + +#[cfg(test)] +mod tests; + +/// Unsubscribe: unregisters a previously created subscription. +pub trait Unsubscribe { + /// Remove all registrations of the subscriber with ID `subs_id`. + fn unsubscribe(&mut self, subs_id: SeqID); +} + +/// Subscribe using a key of type `K` +pub trait Subscribe { + /// Register subscriber with the ID `subs_id` as having interest to the key `K`. + fn subscribe(&mut self, subs_key: K, subs_id: SeqID); +} + +/// Dispatch a message of type `M`. +pub trait Dispatch { + /// The type of the that shall be sent through the channel as a result of such dispatch. + type Item; + /// The type returned by the `dispatch`-method. + type Ret; + + /// Dispatch the message of type `M`. + /// + /// The implementation is given an instance of `M` and is supposed to invoke `dispatch` for + /// each matching subscriber, with an argument of type `Self::Item` matching that subscriber. + /// + /// Note that this does not have to be of the same type with the item that will be sent through + /// to the subscribers. The subscribers will receive a message of type `Self::Item`. + fn dispatch(&mut self, message: M, dispatch: F) -> Self::Ret + where + F: FnMut(&SeqID, Self::Item); +} + +/// A subscription hub. +/// +/// Does the subscription and dispatch. +/// The exact subscription and routing behaviour is to be implemented by the Registry (of type `R`). +/// The Hub under the hood uses the channel defined in `crate::mpsc` module. +#[derive(Debug)] +pub struct Hub { + tracing_key: &'static str, + shared: Arc>>>, +} + +/// The receiving side of the subscription. +/// +/// The messages are delivered as items of a [`Stream`]. +/// Upon drop this receiver unsubscribes itself from the [`Hub`]. +#[derive(Debug)] +pub struct Receiver +where + R: Unsubscribe, +{ + rx: TracingUnboundedReceiver, + + shared: Weak>>>, + subs_id: SeqID, +} + +#[derive(Debug)] +struct Shared { + id_sequence: crate::id_sequence::IDSequence, + registry: R, + sinks: HashMap>, +} + +impl Hub +where + R: Unsubscribe, +{ + /// Provide access to the registry (for test purposes). + pub fn map_registry_for_tests(&self, map: MapF) -> Ret + where + MapF: FnOnce(&R) -> Ret, + { + let shared_locked = self.shared.lock(); + let shared_borrowed = shared_locked.borrow(); + map(&shared_borrowed.registry) + } +} + +impl Drop for Receiver +where + R: Unsubscribe, +{ + fn drop(&mut self) { + if let Some(shared) = self.shared.upgrade() { + shared.lock().borrow_mut().unsubscribe(self.subs_id); + } + } +} + +impl Hub { + /// Create a new instance of Hub (with default value for the Registry). + pub fn new(tracing_key: &'static str) -> Self + where + R: Default, + { + Self::new_with_registry(tracing_key, Default::default()) + } + + /// Create a new instance of Hub over the initialized Registry. + pub fn new_with_registry(tracing_key: &'static str, registry: R) -> Self { + let shared = + Shared { registry, sinks: Default::default(), id_sequence: Default::default() }; + let shared = Arc::new(ReentrantMutex::new(RefCell::new(shared))); + Self { tracing_key, shared } + } + + /// Subscribe to this Hub using the `subs_key: K`. + /// + /// A subscription with a key `K` is possible if the Registry implements `Subscribe`. + pub fn subscribe(&self, subs_key: K) -> Receiver + where + R: Subscribe + Unsubscribe, + { + let shared_locked = self.shared.lock(); + let mut shared_borrowed = shared_locked.borrow_mut(); + + let subs_id = shared_borrowed.id_sequence.next_id(); + + // The order (registry.subscribe then sinks.insert) is important here: + // assuming that `Subscribe::subscribe` can panic, it is better to at least + // have the sink disposed. + shared_borrowed.registry.subscribe(subs_key, subs_id); + + let (tx, rx) = crate::mpsc::tracing_unbounded(self.tracing_key); + assert!(shared_borrowed.sinks.insert(subs_id, tx).is_none(), "Used IDSequence to create another ID. Should be unique until u64 is overflowed. Should be unique."); + + Receiver { shared: Arc::downgrade(&self.shared), subs_id, rx } + } + + /// Send the message produced with `Trigger`. + /// + /// This is possible if the registry implements `Dispatch`. + pub fn send(&self, trigger: Trigger) -> >::Ret + where + R: Dispatch, + { + let shared_locked = self.shared.lock(); + let mut shared_borrowed = shared_locked.borrow_mut(); + let (registry, sinks) = shared_borrowed.get_mut(); + + registry.dispatch(trigger, |subs_id, item| { + if let Some(tx) = sinks.get_mut(subs_id) { + if let Err(send_err) = tx.unbounded_send(item) { + log::warn!("Sink with SubsID = {} failed to perform unbounded_send: {} ({} as Dispatch<{}, Item = {}>::dispatch(...))", subs_id, send_err, std::any::type_name::(), + std::any::type_name::(), + std::any::type_name::()); + } + } else { + log::warn!( + "No Sink for SubsID = {} ({} as Dispatch<{}, Item = {}>::dispatch(...))", + subs_id, + std::any::type_name::(), + std::any::type_name::(), + std::any::type_name::(), + ); + } + }) + } +} + +impl Shared { + fn get_mut(&mut self) -> (&mut R, &mut HashMap>) { + (&mut self.registry, &mut self.sinks) + } + + fn unsubscribe(&mut self, subs_id: SeqID) + where + R: Unsubscribe, + { + // The order (sinks.remove then registry.unsubscribe) is important here: + // assuming that `Unsubscribe::unsubscribe` can panic, it is better to at least + // have the sink disposed. + self.sinks.remove(&subs_id); + self.registry.unsubscribe(subs_id); + } +} + +impl Clone for Hub { + fn clone(&self) -> Self { + Self { tracing_key: self.tracing_key, shared: self.shared.clone() } + } +} + +impl Unpin for Receiver where R: Unsubscribe {} + +impl Stream for Receiver +where + R: Unsubscribe, +{ + type Item = M; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.get_mut().rx).poll_next(cx) + } +} + +impl FusedStream for Receiver +where + R: Unsubscribe, +{ + fn is_terminated(&self) -> bool { + self.rx.is_terminated() + } +} diff --git a/client/utils/src/pubsub/tests.rs b/client/utils/src/pubsub/tests.rs new file mode 100644 index 0000000000000..12945c0d886b8 --- /dev/null +++ b/client/utils/src/pubsub/tests.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use futures::StreamExt; +use tokio_test::block_on; + +use super::*; + +mod normal_operation; +mod panicking_registry; + +const TK: &str = "a_tracing_key"; + +type Message = u64; +type TestHub = Hub; +type TestReceiver = Receiver; + +#[derive(Default)] +struct Registry { + subscribers: HashMap, +} + +struct SubsKey { + _receiver: Option, + panic: SubsKeyPanic, +} + +impl SubsKey { + fn new() -> Self { + Self { _receiver: None, panic: SubsKeyPanic::None } + } + fn with_receiver(self, receiver: TestReceiver) -> Self { + Self { _receiver: Some(receiver), ..self } + } + fn with_panic(self, panic: SubsKeyPanic) -> Self { + Self { panic, ..self } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum SubsKeyPanic { + None, + + OnSubscribePanicBefore, + OnSubscribePanicAfter, + + OnUnsubscribePanicBefore, + OnUnsubscribePanicAfter, + + OnDispatchPanicBefore, + OnDispatchPanicAfter, +} + +impl Hub { + fn subs_count(&self) -> usize { + self.map_registry_for_tests(|r| r.subscribers.len()) + } + fn sink_count(&self) -> usize { + self.shared.lock().borrow().sinks.len() + } +} + +impl Subscribe for Registry { + fn subscribe(&mut self, subs_key: SubsKey, subs_id: SeqID) { + let sk_panic = subs_key.panic; + + if sk_panic == SubsKeyPanic::OnSubscribePanicBefore { + panic!("on-subscribe-panic-before") + } + self.subscribers.insert(subs_id, subs_key); + if sk_panic == SubsKeyPanic::OnSubscribePanicAfter { + panic!("on-subscribe-panic-after") + } + } +} +impl Unsubscribe for Registry { + fn unsubscribe(&mut self, subs_id: SeqID) { + let sk_panic = + self.subscribers.get(&subs_id).map(|sk| sk.panic).unwrap_or(SubsKeyPanic::None); + + if sk_panic == SubsKeyPanic::OnUnsubscribePanicBefore { + panic!("on-unsubscribe-panic-before") + } + self.subscribers.remove(&subs_id); + if sk_panic == SubsKeyPanic::OnUnsubscribePanicAfter { + panic!("on-unsubscribe-panic-after") + } + } +} +impl Dispatch for Registry { + type Item = Message; + type Ret = (); + + fn dispatch(&mut self, message: Message, mut dispatch: F) -> Self::Ret + where + F: FnMut(&SeqID, Self::Item), + { + self.subscribers.iter().for_each(|(id, subs_key)| { + if subs_key.panic == SubsKeyPanic::OnDispatchPanicBefore { + panic!("on-dispatch-panic-before") + } + dispatch(id, message); + if subs_key.panic == SubsKeyPanic::OnDispatchPanicAfter { + panic!("on-dispatch-panic-after") + } + }); + } +} diff --git a/client/utils/src/pubsub/tests/normal_operation.rs b/client/utils/src/pubsub/tests/normal_operation.rs new file mode 100644 index 0000000000000..a13c718d74a8f --- /dev/null +++ b/client/utils/src/pubsub/tests/normal_operation.rs @@ -0,0 +1,88 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; + +#[test] +fn positive_rx_receives_relevant_messages_and_terminates_upon_hub_drop() { + block_on(async { + let hub = TestHub::new(TK); + assert_eq!(hub.subs_count(), 0); + + // No subscribers yet. That message is not supposed to get to anyone. + hub.send(0); + + let mut rx_01 = hub.subscribe(SubsKey::new()); + assert_eq!(hub.subs_count(), 1); + + // That message is sent after subscription. Should be delivered into rx_01. + hub.send(1); + assert_eq!(Some(1), rx_01.next().await); + + // Hub is disposed. The rx_01 should be over after that. + std::mem::drop(hub); + + assert!(!rx_01.is_terminated()); + assert_eq!(None, rx_01.next().await); + assert!(rx_01.is_terminated()); + }); +} + +#[test] +fn positive_subs_count_is_correct_upon_drop_of_rxs() { + block_on(async { + let hub = TestHub::new(TK); + assert_eq!(hub.subs_count(), 0); + + let rx_01 = hub.subscribe(SubsKey::new()); + assert_eq!(hub.subs_count(), 1); + let rx_02 = hub.subscribe(SubsKey::new()); + assert_eq!(hub.subs_count(), 2); + + std::mem::drop(rx_01); + assert_eq!(hub.subs_count(), 1); + std::mem::drop(rx_02); + assert_eq!(hub.subs_count(), 0); + }); +} + +#[test] +fn positive_subs_count_is_correct_upon_drop_of_rxs_on_cloned_hubs() { + block_on(async { + let hub_01 = TestHub::new(TK); + let hub_02 = hub_01.clone(); + assert_eq!(hub_01.subs_count(), 0); + assert_eq!(hub_02.subs_count(), 0); + + let rx_01 = hub_02.subscribe(SubsKey::new()); + assert_eq!(hub_01.subs_count(), 1); + assert_eq!(hub_02.subs_count(), 1); + + let rx_02 = hub_02.subscribe(SubsKey::new()); + assert_eq!(hub_01.subs_count(), 2); + assert_eq!(hub_02.subs_count(), 2); + + std::mem::drop(rx_01); + assert_eq!(hub_01.subs_count(), 1); + assert_eq!(hub_02.subs_count(), 1); + + std::mem::drop(rx_02); + assert_eq!(hub_01.subs_count(), 0); + assert_eq!(hub_02.subs_count(), 0); + }); +} diff --git a/client/utils/src/pubsub/tests/panicking_registry.rs b/client/utils/src/pubsub/tests/panicking_registry.rs new file mode 100644 index 0000000000000..26ce63bd51b01 --- /dev/null +++ b/client/utils/src/pubsub/tests/panicking_registry.rs @@ -0,0 +1,248 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; + +use std::panic::{catch_unwind, AssertUnwindSafe}; + +fn assert_hub_props(hub: &TestHub, sinks_count: usize, subs_count: usize) { + assert_eq!(hub.sink_count(), sinks_count); + assert_eq!(hub.subs_count(), subs_count); +} + +#[test] +fn t01() { + let hub = TestHub::new(TK); + assert_hub_props(&hub, 0, 0); + + let rx_01 = hub.subscribe(SubsKey::new()); + assert_hub_props(&hub, 1, 1); + + std::mem::drop(rx_01); + assert_hub_props(&hub, 0, 0); +} + +#[test] +fn t02() { + block_on(async { + // Create a Hub + let hub = TestHub::new(TK); + assert_hub_props(&hub, 0, 0); + + // Subscribe rx-01 + let rx_01 = hub.subscribe(SubsKey::new()); + assert_hub_props(&hub, 1, 1); + + // Subscribe rx-02 so that its unsubscription will lead to an attempt to drop rx-01 in the + // middle of unsubscription of rx-02 + let rx_02 = hub.subscribe(SubsKey::new().with_receiver(rx_01)); + assert_hub_props(&hub, 2, 2); + + // Subscribe rx-03 in order to see that it will receive messages after the unclean + // unsubscription + let mut rx_03 = hub.subscribe(SubsKey::new()); + assert_hub_props(&hub, 3, 3); + + // drop rx-02 leads to an attempt to unsubscribe rx-01 + assert!(catch_unwind(AssertUnwindSafe(move || { + std::mem::drop(rx_02); + })) + .is_err()); + + // One of the rxes could not unsubscribe + assert_hub_props(&hub, 2, 2); + + // Subscribe rx-04 in order to see that it will receive messages after the unclean + // unsubscription + let mut rx_04 = hub.subscribe(SubsKey::new()); + assert_hub_props(&hub, 3, 3); + + hub.send(2); + + // The messages are still received + assert_eq!(rx_03.next().await, Some(2)); + assert_eq!(rx_04.next().await, Some(2)); + + // Perform a clean unsubscription + std::mem::drop(rx_04); + + hub.send(3); + + // The messages are still received + assert_eq!(rx_03.next().await, Some(3)); + + std::mem::drop(rx_03); + + hub.send(4); + + // The stuck subscription is still there + assert_hub_props(&hub, 1, 1); + }); +} + +async fn add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(hub: &TestHub) { + let rx_01 = hub.subscribe(SubsKey::new()); + let rx_02 = hub.subscribe(SubsKey::new()); + + hub.send(1); + hub.send(2); + hub.send(3); + + assert_eq!(rx_01.take(3).collect::>().await, vec![1, 2, 3]); + + hub.send(4); + hub.send(5); + hub.send(6); + + assert_eq!(rx_02.take(6).collect::>().await, vec![1, 2, 3, 4, 5, 6]); +} + +#[test] +fn t03() { + block_on(async { + // Create a Hub + let hub = TestHub::new(TK); + assert_hub_props(&hub, 0, 0); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 0); + + assert!(catch_unwind(AssertUnwindSafe( + || hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnSubscribePanicBefore)) + )) + .is_err()); + + assert_hub_props(&hub, 0, 0); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 0); + }); +} + +#[test] +fn t04() { + block_on(async { + let hub = TestHub::new(TK); + + assert_hub_props(&hub, 0, 0); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 0); + + assert!(catch_unwind(AssertUnwindSafe( + || hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnSubscribePanicAfter)) + )) + .is_err()); + + // the registry has panicked after it has added a subs-id into its internal storage — the + // sinks do not leak, although the subscriptions storage contains some garbage + assert_hub_props(&hub, 0, 1); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 1); + }) +} + +#[test] +fn t05() { + block_on(async { + let hub = TestHub::new(TK); + + assert_hub_props(&hub, 0, 0); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 0); + + let rx_01 = + hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnUnsubscribePanicBefore)); + + assert_hub_props(&hub, 1, 1); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 1, 1); + + assert!(catch_unwind(AssertUnwindSafe(move || std::mem::drop(rx_01))).is_err()); + + // the registry has panicked on-unsubscribe before it removed the subs-id from its internal + // storage — the sinks do not leak, although the subscriptions storage contains some garbage + assert_hub_props(&hub, 0, 1); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 1); + }) +} + +#[test] +fn t06() { + block_on(async { + let hub = TestHub::new(TK); + + assert_hub_props(&hub, 0, 0); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 0); + + let rx_01 = hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnUnsubscribePanicAfter)); + + assert_hub_props(&hub, 1, 1); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 1, 1); + + assert!(catch_unwind(AssertUnwindSafe(move || std::mem::drop(rx_01))).is_err()); + + // the registry has panicked on-unsubscribe after it removed the subs-id from its internal + // storage — the sinks do not leak, the subscriptions storage does not contain any garbage + assert_hub_props(&hub, 0, 0); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 0); + }) +} + +#[test] +fn t07() { + block_on(async { + let hub = TestHub::new(TK); + + assert_hub_props(&hub, 0, 0); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 0); + + let rx_01 = hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnDispatchPanicBefore)); + assert_hub_props(&hub, 1, 1); + assert!(catch_unwind(AssertUnwindSafe(|| hub.send(1))).is_err()); + assert_hub_props(&hub, 1, 1); + + std::mem::drop(rx_01); + assert_hub_props(&hub, 0, 0); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 0); + }) +} + +#[test] +fn t08() { + block_on(async { + let hub = TestHub::new(TK); + + assert_hub_props(&hub, 0, 0); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 0); + + let rx_01 = hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnDispatchPanicAfter)); + assert_hub_props(&hub, 1, 1); + assert!(catch_unwind(AssertUnwindSafe(|| hub.send(1))).is_err()); + assert_hub_props(&hub, 1, 1); + + std::mem::drop(rx_01); + assert_hub_props(&hub, 0, 0); + add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; + assert_hub_props(&hub, 0, 0); + }) +} diff --git a/client/utils/src/status_sinks.rs b/client/utils/src/status_sinks.rs index a87f0e0ad6e8f..a1d965d08085e 100644 --- a/client/utils/src/status_sinks.rs +++ b/client/utils/src/status_sinks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000000..ca3c1bde4e321 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,22 @@ +# Substrate Builder Docker Image + +The Docker image in this folder is a `builder` image. It is self contained and allow users to build the binaries themselves. +There is no requirement on having Rust or any other toolchain installed but a working Docker environment. + +Unlike the `parity/polkadot` image which contains a single binary (`polkadot`!) used by default, the image in this folder builds and contains several binaries and you need to provide the name of the binary to be called. + +You should refer to the .Dockerfile for the actual list. At the time of editing, the list of included binaries is: + +- substrate +- subkey +- node-template +- chain-spec-builder + +The image can be used by passing the selected binary followed by the appropriate tags for this binary. + +Your best guess to get started is to pass the `--help flag`. Here are a few examples: + +- `docker run --rm -it parity/substrate substrate --version` +- `docker run --rm -it parity/substrate subkey --help` +- `docker run --rm -it parity/substrate node-template --version` +- `docker run --rm -it parity/substrate chain-spec-builder --help` diff --git a/docker/build.sh b/docker/build.sh new file mode 100755 index 0000000000000..f0a4560ff8fea --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -e + +pushd . + +# The following line ensure we run from the project root +PROJECT_ROOT=`git rev-parse --show-toplevel` +cd $PROJECT_ROOT + +# Find the current version from Cargo.toml +VERSION=`grep "^version" ./bin/node/cli/Cargo.toml | egrep -o "([0-9\.]+)"` +GITUSER=parity +GITREPO=substrate + +# Build the image +echo "Building ${GITUSER}/${GITREPO}:latest docker image, hang on!" +time docker build -f ./docker/substrate_builder.Dockerfile -t ${GITUSER}/${GITREPO}:latest . +docker tag ${GITUSER}/${GITREPO}:latest ${GITUSER}/${GITREPO}:v${VERSION} + +# Show the list of available images for this repo +echo "Image is ready" +docker images | grep ${GITREPO} + +popd diff --git a/docker/substrate_builder.Dockerfile b/docker/substrate_builder.Dockerfile new file mode 100644 index 0000000000000..d0812c1a80c40 --- /dev/null +++ b/docker/substrate_builder.Dockerfile @@ -0,0 +1,35 @@ +# This is the build stage for Substrate. Here we create the binary. +FROM docker.io/paritytech/ci-linux:production as builder + +WORKDIR /substrate +COPY . /substrate +RUN cargo build --locked --release + +# This is the 2nd stage: a very small image where we copy the Substrate binary." +FROM docker.io/library/ubuntu:20.04 +LABEL description="Multistage Docker image for Substrate: a platform for web3" \ + io.parity.image.type="builder" \ + io.parity.image.authors="chevdor@gmail.com, devops-team@parity.io" \ + io.parity.image.vendor="Parity Technologies" \ + io.parity.image.description="Substrate is a next-generation framework for blockchain innovation 🚀" \ + io.parity.image.source="https://github.com/paritytech/polkadot/blob/${VCS_REF}/docker/substrate_builder.Dockerfile" \ + io.parity.image.documentation="https://github.com/paritytech/polkadot/" + +COPY --from=builder /substrate/target/release/substrate /usr/local/bin +COPY --from=builder /substrate/target/release/subkey /usr/local/bin +COPY --from=builder /substrate/target/release/node-template /usr/local/bin +COPY --from=builder /substrate/target/release/chain-spec-builder /usr/local/bin + +RUN useradd -m -u 1000 -U -s /bin/sh -d /substrate substrate && \ + mkdir -p /data /substrate/.local/share/substrate && \ + chown -R substrate:substrate /data && \ + ln -s /data /substrate/.local/share/substrate && \ +# unclutter and minimize the attack surface + rm -rf /usr/bin /usr/sbin && \ +# Sanity checks + ldd /usr/local/bin/substrate && \ + /usr/local/bin/substrate --version + +USER substrate +EXPOSE 30333 9933 9944 9615 +VOLUME ["/data"] diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c867a245739ff..25f8e582c78fc 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -360,7 +360,7 @@ Runtime ------- * Introduce stacked filtering (#6273) -* Allow "anonymous" proxied accounts (#6236) +* Allow "pure" proxied accounts (#6236) * Allow over-weight collective proposals to be closed (#6163) * Fix Election when ForceNone V1 (#6166) diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 42d25a0a228f7..133ba7b094d43 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -19,17 +19,24 @@ # - The latest matching rule, if multiple, takes precedence. # CI -/.maintain/ @paritytech/ci +/scripts/ci/ @paritytech/ci /.github/ @paritytech/ci /.gitlab-ci.yml @paritytech/ci # Sandboxing capability of Substrate Runtime -/primitives/sr-sandbox/ @pepyakin -/primitives/core/src/sandbox.rs @pepyakin +/primitives/sandbox/ @pepyakin @koute -# Offchain -/client/offchain/ @tomusdrw -/primitives/offchain/ @tomusdrw +# WASM executor, low-level client <-> WASM interface and other WASM-related code +/client/executor/ @koute +/client/allocator/ @koute +/primitives/wasm-interface/ @koute +/primitives/runtime-interface/ @koute +/primitives/panic-handler/ @koute +/utils/wasm-builder/ @koute + +# Systems-related bits and bobs on the client side +/client/sysinfo/ @koute +/client/tracing/ @koute # GRANDPA, BABE, consensus stuff /frame/babe/ @andresilva @@ -40,6 +47,14 @@ /client/consensus/pow/ @sorpaas /primitives/consensus/pow/ @sorpaas +# BEEFY, MMR +/client/beefy/ @acatangiu +/frame/beefy/ @acatangiu +/frame/beefy-mmr/ @acatangiu +/frame/merkle-mountain-range/ @acatangiu +/primitives/beefy/ @acatangiu +/primitives/merkle-mountain-range/ @acatangiu + # Contracts /frame/contracts/ @athei diff --git a/docs/CONTRIBUTING.adoc b/docs/CONTRIBUTING.adoc index b0eaec04455e4..d39b8a6d729ff 100644 --- a/docs/CONTRIBUTING.adoc +++ b/docs/CONTRIBUTING.adoc @@ -42,7 +42,7 @@ A Pull Request (PR) needs to be reviewed and approved by project maintainers unl . PRs must be tagged with their release importance via the `C1-C9` labels. . PRs must be tagged with their audit requirements via the `D1-D9` labels. . PRs that must be backported to a stable branch must be tagged with https://github.com/paritytech/substrate/labels/E1-runtimemigration[`E0-patchthis`]. -. PRs that introduce runtime migrations must be tagged with https://github.com/paritytech/substrate/labels/E1-runtimemigration[`E1-runtimemigration`]. +. PRs that introduce runtime migrations must be tagged with https://github.com/paritytech/substrate/labels/E1-runtimemigration[`E1-runtimemigration`]. See the https://github.com/paritytech/substrate/blob/master/utils/frame/try-runtime/cli/src/lib.rs#L18[Migration Best Practices here] for more info about how to test runtime migrations. . PRs that introduce irreversible database migrations must be tagged with https://github.com/paritytech/substrate/labels/E2-databasemigration[`E2-databasemigration`]. . PRs that add host functions must be tagged with with https://github.com/paritytech/substrate/labels/E4-newhostfunctions[`E4-newhostfunctions`]. . PRs that break the external API must be tagged with https://github.com/paritytech/substrate/labels/E5-breaksapi[`E5-breaksapi`]. @@ -79,17 +79,17 @@ To create a Polkadot companion PR: . Override substrate deps to point to your local path or branch using https://github.com/bkchr/diener. (E.g. from the polkadot clone dir run `diener patch --crates-to-patch ../substrate --substrate` assuming substrate clone is in a sibling dir. If you do use diener, ensure that you _do not_ commit the changes diener makes to the Cargo.tomls.) . Make the changes required and build polkadot locally. . Submit all this as a PR against the Polkadot Repo. -. Link to your Polkadot PR in the _description_ of your _Substrate_ PR as "polkadot companion: [URL]" +. In the _description_ of your _Substrate_ PR add "polkadot companion: [Polkadot_PR_URL]" . Now you should see that the `check_polkadot` CI job will build your Substrate PR agains the mentioned Polkadot branch in your PR description. . Someone will need to approve the Polkadot PR before the Substrate CI will go green. (The Polkadot CI failing can be ignored as long as the polkadot job in the _substrate_ PR is green). . Wait for reviews on both the Substrate and the Polkadot PRs. . Once the Substrate PR runs green, a member of the `parity` github group can comment on the Substrate PR with `bot merge` which will: - Merge the Substrate PR. - - The bot will push a commit to the Polkadot PR updating its Substrate reference. + - The bot will push a commit to the Polkadot PR updating its Substrate reference. (effecively doing `cargo update -p sp-io`) - If the polkadot PR origins from a fork then a project member may need to press `approve run` on the polkadot PR. - The bot will merge the Polkadot PR once all its CI `{"build_allow_failure":false}` checks are green. - - Note: The merge-bot currently doesn't work with forks on org accounts, only individual accounts. + Note: The merge-bot currently doesn't work with forks on org accounts, only individual accounts. + (Hint: it's recommended to use `bot merge` to merge all substrate PRs, not just ones with a polkadot companion.) If your PR is reviewed well, but a Polkadot PR is missing, signal it with https://github.com/paritytech/substrate/labels/A7-needspolkadotpr[`A7-needspolkadotpr`] to prevent it from getting automatically merged. @@ -111,6 +111,14 @@ Please label issues with the following labels: Declaring formal releases remains the prerogative of the project maintainer(s). +== UI tests + +UI tests are used for macros to ensure that the output of a macro doesn't change and is in the expected format. These UI tests are sensible to any changes +in the macro generated code or to switching the rust stable version. The tests are only run when the `RUN_UI_TESTS` environment variable is set. So, when +the CI is for example complaining about failing UI tests and it is expected that they fail these tests need to be executed locally. To simplify the updating +of the UI test ouput there is the `.maintain/update-rust-stable.sh` script. This can be run with `.maintain/update-rust-stable.sh CURRENT_STABLE_VERSION` +and then it will run all UI tests to update the expected output. + == Changes to this arrangement This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md index 77f5f79f60d40..12f39371892e7 100644 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -1,26 +1,32 @@ -Thank you for your Pull Request! -Before you submitting, please check that: -- [ ] You added a brief description of the PR, e.g.: +✄ ----------------------------------------------------------------------------- + +Thank you for your Pull Request! 🙏 + +Before you submit, please check that: + +- [ ] **Description:** You added a brief description of the PR, e.g.: - What does it do? - - What important points reviewers should know? + - What important points should reviewers know? - Is there something left for follow-up PRs? -- [ ] You labeled the PR appropriately if you have permissions to do so: +- [ ] **Labels:** You labeled the PR appropriately if you have permissions to do so: - [ ] `A*` for PR status (**one required**) - [ ] `B*` for changelog (**one required**) - [ ] `C*` for release notes (**exactly one required**) - [ ] `D*` for various implications/requirements - - [ ] Github's project assignment -- [ ] You mentioned a related issue if this PR related to it, e.g. `Fixes #228` or `Related #1337`. -- [ ] You asked any particular reviewers to review. If you aren't sure, start with GH suggestions. -- [ ] Your PR adheres to [the style guide](https://github.com/paritytech/substrate/blob/master/docs/STYLE_GUIDE.md) + - [ ] Github project assignment +- [ ] **Related Issues:** You mentioned a related issue if this PR is related to it, e.g. `Fixes #228` or `Related #1337`. +- [ ] **2 Reviewers:** You asked at least two reviewers to review. If you aren't sure, start with GH suggestions. +- [ ] **Style Guide:** Your PR adheres to [the style guide](https://github.com/paritytech/substrate/blob/master/docs/STYLE_GUIDE.md) - In particular, mind the maximal line length of 100 (120 in exceptional circumstances). - There is no commented code checked in unless necessary. - - Any panickers have a proof or removed. -- [ ] You bumped the runtime version if there are breaking changes in the **runtime**. -- [ ] You updated any rustdocs which may have changed -- [ ] Has the PR altered the external API or interfaces used by Polkadot? Do you have the corresponding Polkadot PR ready? + - Any panickers in the runtime have a proof or were removed. +- [ ] **Runtime Version:** You bumped the runtime version if there are breaking changes in the **runtime**. +- [ ] **Docs:** You updated any rustdocs which may need to change. +- [ ] **Polkadot Companion:** Has the PR altered the external API or interfaces used by Polkadot? + - [ ] If so, do you have the corresponding Polkadot PR ready? + - [ ] Optionally: Do you have a corresponding Cumulus PR? Refer to [the contributing guide](https://github.com/paritytech/substrate/blob/master/docs/CONTRIBUTING.adoc) for details. diff --git a/docs/README.adoc b/docs/README.adoc index 71052420b1aa9..5d7b0b52c5250 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -190,7 +190,7 @@ You will also need to install the following packages: - Linux: [source, shell] -sudo apt install cmake pkg-config libssl-dev git clang libclang-dev +sudo apt install cmake pkg-config libssl-dev git clang libclang-dev llvm - Linux on ARM: `rust-lld` is required for linking wasm, but is missing on non Tier 1 platforms. @@ -250,6 +250,20 @@ If you are trying to set up Substrate on Windows, you should do the following: 7. Finally, you need to install `cmake`: https://cmake.org/download/ +==== Docker + +You can use https://github.com/paritytech/scripts/tree/master/dockerfiles/ci-linux[Parity CI docker image] with all necessary dependencies to build Substrate: + +[source, shell] +---- +#run it in the folder with the Substrate source code +docker run --rm -it -w /shellhere/substrate \ + -v $(pwd):/shellhere/substrate \ + paritytech/ci-linux:production +---- + +You can find necessary cargo commands in <> + ==== Shared Steps Then, grab the Substrate source code: @@ -431,7 +445,7 @@ pallet-assets, pallet-balances, pallet-consensus, pallet-contracts, pallet-counc frame-executive, pallet-session, pallet-staking, pallet-timestamp, pallet-treasury * Node [source, shell] -node-cli, node-consensus, node-executor, node-network, node-primitives, node-runtime +node-cli, node-consensus, node-executor, node-network, node-primitives, kitchensink-runtime * Subkey [source, shell] subkey diff --git a/docs/STYLE_GUIDE.md b/docs/STYLE_GUIDE.md index ea070cdbc59f3..8854f885a4b22 100644 --- a/docs/STYLE_GUIDE.md +++ b/docs/STYLE_GUIDE.md @@ -140,8 +140,8 @@ let mut target_path = ``` - Unsafe code requires explicit proofs just as panickers do. When introducing unsafe code, - consider tradeoffs between efficiency on one hand and reliability, maintenance costs, and - security on the other. Here is a list of questions that may help evaluating the tradeoff while + consider trade-offs between efficiency on one hand and reliability, maintenance costs, and + security on the other. Here is a list of questions that may help evaluating the trade-off while preparing or reviewing a PR: - how much more performant or compact the resulting code will be using unsafe code, - how likely is it that invariants could be violated, diff --git a/docs/Upgrading-2.0-to-3.0.md b/docs/Upgrading-2.0-to-3.0.md index 45da3811220f4..906018db9a707 100644 --- a/docs/Upgrading-2.0-to-3.0.md +++ b/docs/Upgrading-2.0-to-3.0.md @@ -100,12 +100,12 @@ And update the overall definition for weights on frame and a few related types a +/// by Operational extrinsics. +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +/// We allow for 2 seconds of compute with a 6 second average block time. -+const MAXIMUM_BLOCK_WEIGHT: Weight = 2 * WEIGHT_PER_SECOND; ++const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX); + parameter_types! { pub const BlockHashCount: BlockNumber = 2400; - /// We allow for 2 seconds of compute with a 6 second average block time. -- pub const MaximumBlockWeight: Weight = 2 * WEIGHT_PER_SECOND; +- pub const MaximumBlockWeight: Weight = Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX); - pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); - /// Assume 10% of weight for average on_initialize calls. - pub MaximumExtrinsicWeight: Weight = @@ -147,8 +147,8 @@ And update the overall definition for weights on frame and a few related types a + type BlockWeights = RuntimeBlockWeights; + type BlockLength = RuntimeBlockLength; + type DbWeight = RocksDbWeight; - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = Index; @@ -171,25 +198,19 @@ impl frame_system::Trait for Runtime { type Header = generic::Header; @@ -199,6 +199,7 @@ As mentioned above, Bounties, Tips and Lottery have been extracted out of treasu type OnSlash = (); type ProposalBond = ProposalBond; type ProposalBondMinimum = ProposalBondMinimum; + type ProposalBondMaximum = (); type SpendPeriod = SpendPeriod; type Burn = Burn; + type BurnDestination = (); @@ -289,7 +290,7 @@ Democracy brings three new settings with this release, all to allow for better i type CancellationOrigin = pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId, CouncilCollective>; + // To cancel a proposal before it has been passed, the technical committee must be unanimous or + // Root must agree. -+ type CancelProposalOrigin = EnsureOneOf< ++ type CancelProposalOrigin = EitherOfDiverse< + AccountId, + EnsureRoot, + pallet_collective::EnsureProportionAtLeast<_1, _1, AccountId, TechnicalCollective>, diff --git a/docs/node-template-release.md b/docs/node-template-release.md index 25834ae99f438..4f4977a9df03c 100644 --- a/docs/node-template-release.md +++ b/docs/node-template-release.md @@ -7,7 +7,7 @@ the existence of your current git commit ID in the remote repository. Assume you are in root directory of Substrate. Run: ```bash - cd .maintain/ + cd scripts/ci/ ./node-template-release.sh ``` @@ -50,7 +50,7 @@ commit in Substrate remote repository, such as: ``` P.S: This step can be automated if we update `node-template-release` package in - `.maintain/node-template-release`. + `scripts/ci/node-template-release`. 4. Once the three `Cargo.toml`s are updated, compile and confirm that the Node Template builds. Then commit the changes to a new branch in [Substrate Node Template](https://github.com/substrate-developer-hub/substrate-node-template), and make a PR. diff --git a/docs/rustdocs-release.md b/docs/rustdocs-release.md new file mode 100644 index 0000000000000..5c7e2db40a2cd --- /dev/null +++ b/docs/rustdocs-release.md @@ -0,0 +1,20 @@ +# Rustdocs Release Process + +There is [a script in place](../.maintain/rustdocs-release.sh) to manage the deployment of Substrate rustdocs at +https://paritytech.github.io/substrate, which is pushing the rustdocs file in `gh-pages` branch of +https://github.com/paritytech/substrate. + +The documentation at the top of the `rustdocs-release.sh` explains most of the mechanics of the script. + +Manage the rustdocs deployment with one of the following commands. + +```bash +# Deploy rustdocs of `monthly-2021-10` tag +.maintain/rustdocs-release.sh deploy monthly-2021-10 + +# In addition to the above, the `latest` symlink will point to this version of rustdocs +.maintain/rustdocs-release.sh deploy -l monthly-2021-10 + +# Remove the rustdocs of `monthly-2021-10` from `gh-pages`. +.maintain/rustdocs-release.sh remove monthly-2021-10 +``` diff --git a/frame/alliance/Cargo.toml b/frame/alliance/Cargo.toml new file mode 100644 index 0000000000000..da0a7d665747d --- /dev/null +++ b/frame/alliance/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "pallet-alliance" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://docs.substrate.io/" +repository = "https://github.com/paritytech/substrate/" +description = "The Alliance pallet provides a collective for standard-setting industry collaboration." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +array-bytes = { version = "4.1", optional = true } +sha2 = { version = "0.10.1", default-features = false, optional = true } +log = { version = "0.4.14", default-features = false } + +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } + +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } + +pallet-identity = { version = "4.0.0-dev", path = "../identity", default-features = false } +pallet-collective = { version = "4.0.0-dev", path = "../collective", default-features = false, optional = true } + +[dev-dependencies] +array-bytes = "4.1" +sha2 = "0.10.1" +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-collective = { version = "4.0.0-dev", path = "../collective" } + +[features] +default = ["std"] +std = [ + "pallet-collective?/std", + "frame-benchmarking?/std", + "log/std", + "codec/std", + "scale-info/std", + "sp-std/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "pallet-identity/std", +] +runtime-benchmarks = [ + "array-bytes", + "sha2", + "frame-benchmarking/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-collective/runtime-benchmarks", + "pallet-identity/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/frame/alliance/README.md b/frame/alliance/README.md new file mode 100644 index 0000000000000..9930008e2d635 --- /dev/null +++ b/frame/alliance/README.md @@ -0,0 +1,66 @@ +# Alliance Pallet + +The Alliance Pallet provides a collective that curates a list of accounts and URLs, deemed by +the voting members to be unscrupulous actors. The Alliance + +- provides a set of ethics against bad behavior, and +- provides recognition and influence for those teams that contribute something back to the + ecosystem. + +## Overview + +The network initializes the Alliance via a Root call. After that, anyone with an approved +identity and website can join as an Ally. The `MembershipManager` origin can elevate Allies to +Fellows, giving them voting rights within the Alliance. + +Voting members of the Alliance maintain a list of accounts and websites. Members can also vote +to update the Alliance's rule and make announcements. + +### Terminology + +- Rule: The IPFS CID (hash) of the Alliance rules for the community to read and the Alliance + members to enforce. Similar to a Charter or Code of Conduct. +- Announcement: An IPFS CID of some content that the Alliance want to announce. +- Member: An account that is already in the group of the Alliance, including three types: + Fellow, or Ally. A member can also be kicked by the `MembershipManager` origin + or retire by itself. +- Fellow: An account who is elevated from Ally by other Fellows. +- Ally: An account who would like to join the Alliance. To become a voting member (Fellow), it + will need approval from the `MembershipManager` origin. Any account can join as an Ally either + by placing a deposit or by nomination from a voting member. +- Unscrupulous List: A list of bad websites and addresses; items can be added or removed by + voting members. + +## Interface + +### Dispatchable Functions + +#### For General Users + +- `join_alliance` - Join the Alliance as an Ally. This requires a slashable deposit. + +#### For Members (All) + +- `give_retirement_notice` - Give a retirement notice and start a retirement period required to + pass in order to retire. +- `retire` - Retire from the Alliance and release the caller's deposit. + +#### For Voting Members + +- `propose` - Propose a motion. +- `vote` - Vote on a motion. +- `close` - Close a motion with enough votes or that has expired. +- `set_rule` - Initialize or update the Alliance's rule by IPFS CID. +- `announce` - Make announcement by IPFS CID. +- `nominate_ally` - Nominate a non-member to become an Ally, without deposit. +- `elevate_ally` - Approve an ally to become a Fellow. +- `kick_member` - Kick a member and slash its deposit. +- `add_unscrupulous_items` - Add some items, either accounts or websites, to the list of + unscrupulous items. +- `remove_unscrupulous_items` - Remove some items from the list of unscrupulous items. +- `abdicate_fellow_status` - Abdicate one's voting rights, demoting themself to Ally. + +#### Root Calls + +- `init_members` - Initialize the Alliance, onboard fellows and allies. +- `disband` - Disband the Alliance, remove all active members and unreserve deposits. diff --git a/frame/alliance/src/benchmarking.rs b/frame/alliance/src/benchmarking.rs new file mode 100644 index 0000000000000..a34b76bd96ece --- /dev/null +++ b/frame/alliance/src/benchmarking.rs @@ -0,0 +1,807 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Alliance pallet benchmarking. + +use sp_runtime::traits::{Bounded, Hash, StaticLookup}; +use sp_std::{ + cmp, + convert::{TryFrom, TryInto}, + mem::size_of, + prelude::*, +}; + +use frame_benchmarking::{account, benchmarks_instance_pallet}; +use frame_support::traits::{EnsureOrigin, Get, UnfilteredDispatchable}; +use frame_system::{Pallet as System, RawOrigin as SystemOrigin}; + +use super::{Call as AllianceCall, Pallet as Alliance, *}; + +const SEED: u32 = 0; + +const MAX_BYTES: u32 = 1_024; + +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn cid(input: impl AsRef<[u8]>) -> Cid { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(input); + let result = hasher.finalize(); + Cid::new_v0(&*result) +} + +fn rule(input: impl AsRef<[u8]>) -> Cid { + cid(input) +} + +fn announcement(input: impl AsRef<[u8]>) -> Cid { + cid(input) +} + +fn funded_account, I: 'static>(name: &'static str, index: u32) -> T::AccountId { + let account: T::AccountId = account(name, index, SEED); + T::Currency::make_free_balance_be(&account, BalanceOf::::max_value() / 100u8.into()); + account +} + +fn fellow, I: 'static>(index: u32) -> T::AccountId { + funded_account::("fellow", index) +} + +fn ally, I: 'static>(index: u32) -> T::AccountId { + funded_account::("ally", index) +} + +fn outsider, I: 'static>(index: u32) -> T::AccountId { + funded_account::("outsider", index) +} + +fn generate_unscrupulous_account, I: 'static>(index: u32) -> T::AccountId { + funded_account::("unscrupulous", index) +} + +fn set_members, I: 'static>() { + let fellows: BoundedVec<_, T::MaxMembersCount> = + BoundedVec::try_from(vec![fellow::(1), fellow::(2)]).unwrap(); + fellows.iter().for_each(|who| { + T::Currency::reserve(&who, T::AllyDeposit::get()).unwrap(); + >::insert(&who, T::AllyDeposit::get()); + }); + Members::::insert(MemberRole::Fellow, fellows.clone()); + + let allies: BoundedVec<_, T::MaxMembersCount> = + BoundedVec::try_from(vec![ally::(1)]).unwrap(); + allies.iter().for_each(|who| { + T::Currency::reserve(&who, T::AllyDeposit::get()).unwrap(); + >::insert(&who, T::AllyDeposit::get()); + }); + Members::::insert(MemberRole::Ally, allies); + + T::InitializeMembers::initialize_members(&[fellows.as_slice()].concat()); +} + +benchmarks_instance_pallet! { + // This tests when proposal is created and queued as "proposed" + propose_proposed { + let b in 1 .. MAX_BYTES; + let m in 2 .. T::MaxFellows::get(); + let p in 1 .. T::MaxProposals::get(); + + let bytes_in_storage = b + size_of::() as u32 + 32; + + // Construct `members`. + let fellows = (0 .. m).map(fellow::).collect::>(); + let proposer = fellows[0].clone(); + + Alliance::::init_members( + SystemOrigin::Root.into(), + fellows, + vec![], + )?; + + let threshold = m; + // Add previous proposals. + for i in 0 .. p - 1 { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; b as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal), + bytes_in_storage, + )?; + } + + let proposal: T::Proposal = AllianceCall::::set_rule { rule: rule(vec![p as u8; b as usize]) }.into(); + + }: propose(SystemOrigin::Signed(proposer.clone()), threshold, Box::new(proposal.clone()), bytes_in_storage) + verify { + // New proposal is recorded + let proposal_hash = T::Hashing::hash_of(&proposal); + assert_eq!(T::ProposalProvider::proposal_of(proposal_hash), Some(proposal)); + } + + vote { + // We choose 5 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let m in 5 .. T::MaxFellows::get(); + + let p = T::MaxProposals::get(); + let b = MAX_BYTES; + let bytes_in_storage = b + size_of::() as u32 + 32; + + // Construct `members`. + let fellows = (0 .. m).map(fellow::).collect::>(); + let proposer = fellows[0].clone(); + + let members = fellows.clone(); + + Alliance::::init_members( + SystemOrigin::Root.into(), + fellows, + vec![], + )?; + + // Threshold is 1 less than the number of members so that one person can vote nay + let threshold = m - 1; + + // Add previous proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; b as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal.clone()), + b, + )?; + last_hash = T::Hashing::hash_of(&proposal); + } + + let index = p - 1; + // Have almost everyone vote aye on last proposal, while keeping it from passing. + for j in 0 .. m - 3 { + let voter = &members[j as usize]; + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + } + + let voter = members[m as usize - 3].clone(); + // Voter votes aye without resolving the vote. + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + + // Voter switches vote to nay, but does not kill the vote, just updates + inserts + let approve = false; + + // Whitelist voter account from further DB operations. + let voter_key = frame_system::Account::::hashed_key_for(&voter); + frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into()); + }: _(SystemOrigin::Signed(voter), last_hash.clone(), index, approve) + verify { + } + + close_early_disapproved { + // We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let m in 4 .. T::MaxFellows::get(); + let p in 1 .. T::MaxProposals::get(); + + let bytes = 100; + let bytes_in_storage = bytes + size_of::() as u32 + 32; + + // Construct `members`. + let fellows = (0 .. m).map(fellow::).collect::>(); + + let members = fellows.clone(); + + Alliance::::init_members( + SystemOrigin::Root.into(), + fellows, + vec![], + )?; + + let proposer = members[0].clone(); + let voter = members[1].clone(); + + // Threshold is total members so that one nay will disapprove the vote + let threshold = m; + + // Add previous proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; bytes as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal.clone()), + bytes_in_storage, + )?; + last_hash = T::Hashing::hash_of(&proposal); + assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal)); + } + + let index = p - 1; + // Have most everyone vote aye on last proposal, while keeping it from passing. + for j in 2 .. m - 1 { + let voter = &members[j as usize]; + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + } + + // Voter votes aye without resolving the vote. + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + + // Voter switches vote to nay, which kills the vote + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + false, + )?; + + // Whitelist voter account from further DB operations. + let voter_key = frame_system::Account::::hashed_key_for(&voter); + frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into()); + }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::MAX, bytes_in_storage) + verify { + // The last proposal is removed. + assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); + } + + close_early_approved { + let b in 1 .. MAX_BYTES; + // We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let m in 4 .. T::MaxFellows::get(); + let p in 1 .. T::MaxProposals::get(); + + let bytes_in_storage = b + size_of::() as u32 + 32; + + // Construct `members`. + let fellows = (0 .. m).map(fellow::).collect::>(); + + let members = fellows.clone(); + + Alliance::::init_members( + SystemOrigin::Root.into(), + fellows, + vec![], + )?; + + let proposer = members[0].clone(); + let voter = members[1].clone(); + + // Threshold is 2 so any two ayes will approve the vote + let threshold = 2; + + // Add previous proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; b as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal.clone()), + bytes_in_storage, + )?; + last_hash = T::Hashing::hash_of(&proposal); + assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal)); + } + + let index = p - 1; + // Caller switches vote to nay on their own proposal, allowing them to be the deciding approval vote + Alliance::::vote( + SystemOrigin::Signed(proposer.clone()).into(), + last_hash.clone(), + index, + false, + )?; + + // Have almost everyone vote nay on last proposal, while keeping it from failing. + for j in 2 .. m - 1 { + let voter = &members[j as usize]; + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + false, + )?; + } + + // Member zero is the first aye + Alliance::::vote( + SystemOrigin::Signed(members[0].clone()).into(), + last_hash.clone(), + index, + true, + )?; + + let voter = members[1].clone(); + // Caller switches vote to aye, which passes the vote + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::MAX, bytes_in_storage) + verify { + // The last proposal is removed. + assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); + } + + close_disapproved { + // We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let m in 2 .. T::MaxFellows::get(); + let p in 1 .. T::MaxProposals::get(); + + let bytes = 100; + let bytes_in_storage = bytes + size_of::() as u32 + 32; + + // Construct `members`. + let fellows = (0 .. m).map(fellow::).collect::>(); + + let members = fellows.clone(); + + Alliance::::init_members( + SystemOrigin::Root.into(), + fellows, + vec![], + )?; + + let proposer = members[0].clone(); + let voter = members[1].clone(); + + // Threshold is one less than total members so that two nays will disapprove the vote + let threshold = m - 1; + + // Add proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; bytes as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal.clone()), + bytes_in_storage, + )?; + last_hash = T::Hashing::hash_of(&proposal); + assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal)); + } + + let index = p - 1; + // Have almost everyone vote aye on last proposal, while keeping it from passing. + // A few abstainers will be the nay votes needed to fail the vote. + for j in 2 .. m - 1 { + let voter = &members[j as usize]; + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + } + + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + false, + )?; + + System::::set_block_number(T::BlockNumber::max_value()); + + }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::MAX, bytes_in_storage) + verify { + // The last proposal is removed. + assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); + } + + close_approved { + let b in 1 .. MAX_BYTES; + // We choose 4 fellows as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let m in 5 .. T::MaxFellows::get(); + let p in 1 .. T::MaxProposals::get(); + + let bytes_in_storage = b + size_of::() as u32 + 32; + + // Construct `members`. + let fellows = (0 .. m).map(fellow::).collect::>(); + + let members = fellows.clone(); + + Alliance::::init_members( + SystemOrigin::Root.into(), + fellows, + vec![], + )?; + + let proposer = members[0].clone(); + let voter = members[1].clone(); + + // Threshold is two, so any two ayes will pass the vote + let threshold = 2; + + // Add proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; b as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal.clone()), + bytes_in_storage, + )?; + last_hash = T::Hashing::hash_of(&proposal); + assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal)); + } + + // The prime member votes aye, so abstentions default to aye. + Alliance::::vote( + SystemOrigin::Signed(proposer.clone()).into(), + last_hash.clone(), + p - 1, + true // Vote aye. + )?; + + let index = p - 1; + // Have almost everyone vote nay on last proposal, while keeping it from failing. + // A few abstainers will be the aye votes needed to pass the vote. + for j in 2 .. m - 1 { + let voter = &members[j as usize]; + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + false + )?; + } + + // caller is prime, prime already votes aye by creating the proposal + System::::set_block_number(T::BlockNumber::max_value()); + + }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::MAX, bytes_in_storage) + verify { + // The last proposal is removed. + assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); + } + + init_members { + // at least 1 fellow + let m in 1 .. T::MaxFellows::get(); + let z in 0 .. T::MaxAllies::get(); + + let mut fellows = (0 .. m).map(fellow::).collect::>(); + let mut allies = (0 .. z).map(ally::).collect::>(); + + }: _(SystemOrigin::Root, fellows.clone(), allies.clone()) + verify { + fellows.sort(); + allies.sort(); + assert_last_event::(Event::MembersInitialized { + fellows: fellows.clone(), + allies: allies.clone(), + }.into()); + assert_eq!(Alliance::::members(MemberRole::Fellow), fellows); + assert_eq!(Alliance::::members(MemberRole::Ally), allies); + } + + disband { + // at least 1 founders + let x in 1 .. T::MaxFellows::get(); + let y in 0 .. T::MaxAllies::get(); + let z in 0 .. T::MaxMembersCount::get() / 2; + + let fellows = (0 .. x).map(fellow::).collect::>(); + let allies = (0 .. y).map(ally::).collect::>(); + let witness = DisbandWitness{ + fellow_members: x, + ally_members: y, + }; + + // setting the Alliance to disband on the benchmark call + Alliance::::init_members( + SystemOrigin::Root.into(), + fellows.clone(), + allies.clone(), + )?; + + // reserve deposits + let deposit = T::AllyDeposit::get(); + for member in fellows.iter().chain(allies.iter()).take(z as usize) { + T::Currency::reserve(&member, deposit)?; + >::insert(&member, deposit); + } + + assert_eq!(Alliance::::voting_members_count(), x); + assert_eq!(Alliance::::ally_members_count(), y); + }: _(SystemOrigin::Root, witness) + verify { + assert_last_event::(Event::AllianceDisbanded { + fellow_members: x, + ally_members: y, + unreserved: cmp::min(z, x + y), + }.into()); + + assert!(!Alliance::::is_initialized()); + } + + set_rule { + set_members::(); + + let rule = rule(b"hello world"); + + let call = Call::::set_rule { rule: rule.clone() }; + let origin = T::AdminOrigin::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_eq!(Alliance::::rule(), Some(rule.clone())); + assert_last_event::(Event::NewRuleSet { rule }.into()); + } + + announce { + set_members::(); + + let announcement = announcement(b"hello world"); + + let call = Call::::announce { announcement: announcement.clone() }; + let origin = T::AnnouncementOrigin::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(Alliance::::announcements().contains(&announcement)); + assert_last_event::(Event::Announced { announcement }.into()); + } + + remove_announcement { + set_members::(); + + let announcement = announcement(b"hello world"); + let announcements: BoundedVec<_, T::MaxAnnouncementsCount> = BoundedVec::try_from(vec![announcement.clone()]).unwrap(); + Announcements::::put(announcements); + + let call = Call::::remove_announcement { announcement: announcement.clone() }; + let origin = T::AnnouncementOrigin::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(Alliance::::announcements().is_empty()); + assert_last_event::(Event::AnnouncementRemoved { announcement }.into()); + } + + join_alliance { + set_members::(); + + let outsider = outsider::(1); + assert!(!Alliance::::is_member(&outsider)); + assert_eq!(DepositOf::::get(&outsider), None); + }: _(SystemOrigin::Signed(outsider.clone())) + verify { + assert!(Alliance::::is_member_of(&outsider, MemberRole::Ally)); // outsider is now an ally + assert_eq!(DepositOf::::get(&outsider), Some(T::AllyDeposit::get())); // with a deposit + assert!(!Alliance::::has_voting_rights(&outsider)); // allies don't have voting rights + assert_last_event::(Event::NewAllyJoined { + ally: outsider, + nominator: None, + reserved: Some(T::AllyDeposit::get()) + }.into()); + } + + nominate_ally { + set_members::(); + + let fellow1 = fellow::(1); + assert!(Alliance::::is_member_of(&fellow1, MemberRole::Fellow)); + + let outsider = outsider::(1); + assert!(!Alliance::::is_member(&outsider)); + assert_eq!(DepositOf::::get(&outsider), None); + + let outsider_lookup = T::Lookup::unlookup(outsider.clone()); + }: _(SystemOrigin::Signed(fellow1.clone()), outsider_lookup) + verify { + assert!(Alliance::::is_member_of(&outsider, MemberRole::Ally)); // outsider is now an ally + assert_eq!(DepositOf::::get(&outsider), None); // without a deposit + assert!(!Alliance::::has_voting_rights(&outsider)); // allies don't have voting rights + assert_last_event::(Event::NewAllyJoined { + ally: outsider, + nominator: Some(fellow1), + reserved: None + }.into()); + } + + elevate_ally { + set_members::(); + + let ally1 = ally::(1); + assert!(Alliance::::is_ally(&ally1)); + + let ally1_lookup = T::Lookup::unlookup(ally1.clone()); + let call = Call::::elevate_ally { ally: ally1_lookup }; + let origin = T::MembershipManager::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(!Alliance::::is_ally(&ally1)); + assert!(Alliance::::has_voting_rights(&ally1)); + assert_last_event::(Event::AllyElevated { ally: ally1 }.into()); + } + + give_retirement_notice { + set_members::(); + let fellow2 = fellow::(2); + + assert!(Alliance::::has_voting_rights(&fellow2)); + }: _(SystemOrigin::Signed(fellow2.clone())) + verify { + assert!(Alliance::::is_member_of(&fellow2, MemberRole::Retiring)); + + assert_eq!( + RetiringMembers::::get(&fellow2), + Some(System::::block_number() + T::RetirementPeriod::get()) + ); + assert_last_event::( + Event::MemberRetirementPeriodStarted {member: fellow2}.into() + ); + } + + retire { + set_members::(); + + let fellow2 = fellow::(2); + assert!(Alliance::::has_voting_rights(&fellow2)); + + assert_eq!( + Alliance::::give_retirement_notice( + SystemOrigin::Signed(fellow2.clone()).into() + ), + Ok(()) + ); + System::::set_block_number(System::::block_number() + T::RetirementPeriod::get()); + + assert_eq!(DepositOf::::get(&fellow2), Some(T::AllyDeposit::get())); + }: _(SystemOrigin::Signed(fellow2.clone())) + verify { + assert!(!Alliance::::is_member(&fellow2)); + assert_eq!(DepositOf::::get(&fellow2), None); + assert_last_event::(Event::MemberRetired { + member: fellow2, + unreserved: Some(T::AllyDeposit::get()) + }.into()); + } + + kick_member { + set_members::(); + + let fellow2 = fellow::(2); + assert!(Alliance::::is_member_of(&fellow2, MemberRole::Fellow)); + assert_eq!(DepositOf::::get(&fellow2), Some(T::AllyDeposit::get())); + + let fellow2_lookup = T::Lookup::unlookup(fellow2.clone()); + let call = Call::::kick_member { who: fellow2_lookup }; + let origin = T::MembershipManager::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(!Alliance::::is_member(&fellow2)); + assert_eq!(DepositOf::::get(&fellow2), None); + assert_last_event::(Event::MemberKicked { + member: fellow2, + slashed: Some(T::AllyDeposit::get()) + }.into()); + } + + add_unscrupulous_items { + let n in 0 .. T::MaxUnscrupulousItems::get(); + let l in 0 .. T::MaxWebsiteUrlLength::get(); + + set_members::(); + + let accounts = (0 .. n) + .map(|i| generate_unscrupulous_account::(i)) + .collect::>(); + let websites = (0 .. n).map(|i| -> BoundedVec { + BoundedVec::try_from(vec![i as u8; l as usize]).unwrap() + }).collect::>(); + + let mut unscrupulous_list = Vec::with_capacity(accounts.len() + websites.len()); + unscrupulous_list.extend(accounts.into_iter().map(UnscrupulousItem::AccountId)); + unscrupulous_list.extend(websites.into_iter().map(UnscrupulousItem::Website)); + + let call = Call::::add_unscrupulous_items { items: unscrupulous_list.clone() }; + let origin = T::AnnouncementOrigin::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::UnscrupulousItemAdded { items: unscrupulous_list }.into()); + } + + remove_unscrupulous_items { + let n in 0 .. T::MaxUnscrupulousItems::get(); + let l in 0 .. T::MaxWebsiteUrlLength::get(); + + set_members::(); + + let mut accounts = (0 .. n) + .map(|i| generate_unscrupulous_account::(i)) + .collect::>(); + accounts.sort(); + let accounts: BoundedVec<_, T::MaxUnscrupulousItems> = accounts.try_into().unwrap(); + UnscrupulousAccounts::::put(accounts.clone()); + + let mut websites = (0 .. n).map(|i| -> BoundedVec<_, T::MaxWebsiteUrlLength> + { BoundedVec::try_from(vec![i as u8; l as usize]).unwrap() }).collect::>(); + websites.sort(); + let websites: BoundedVec<_, T::MaxUnscrupulousItems> = websites.try_into().unwrap(); + UnscrupulousWebsites::::put(websites.clone()); + + let mut unscrupulous_list = Vec::with_capacity(accounts.len() + websites.len()); + unscrupulous_list.extend(accounts.into_iter().map(UnscrupulousItem::AccountId)); + unscrupulous_list.extend(websites.into_iter().map(UnscrupulousItem::Website)); + + let call = Call::::remove_unscrupulous_items { items: unscrupulous_list.clone() }; + let origin = T::AnnouncementOrigin::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::UnscrupulousItemRemoved { items: unscrupulous_list }.into()); + } + + abdicate_fellow_status { + set_members::(); + let fellow2 = fellow::(2); + assert!(Alliance::::has_voting_rights(&fellow2)); + }: _(SystemOrigin::Signed(fellow2.clone())) + verify { + assert!(Alliance::::is_member_of(&fellow2, MemberRole::Ally)); + + assert_last_event::( + Event::FellowAbdicated {fellow: fellow2}.into() + ); + } + + impl_benchmark_test_suite!(Alliance, crate::mock::new_bench_ext(), crate::mock::Test); +} diff --git a/frame/alliance/src/lib.rs b/frame/alliance/src/lib.rs new file mode 100644 index 0000000000000..790c3c384e701 --- /dev/null +++ b/frame/alliance/src/lib.rs @@ -0,0 +1,1171 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! # Alliance Pallet +//! +//! The Alliance Pallet provides a collective that curates a list of accounts and URLs, deemed by +//! the voting members to be unscrupulous actors. The Alliance +//! +//! - provides a set of ethics against bad behavior, and +//! - provides recognition and influence for those teams that contribute something back to the +//! ecosystem. +//! +//! ## Overview +//! +//! The network initializes the Alliance via a Root call. After that, anyone with an approved +//! identity and website can join as an Ally. The `MembershipManager` origin can elevate Allies to +//! Fellows, giving them voting rights within the Alliance. +//! +//! Voting members of the Alliance maintain a list of accounts and websites. Members can also vote +//! to update the Alliance's rule and make announcements. +//! +//! ### Terminology +//! +//! - Rule: The IPFS CID (hash) of the Alliance rules for the community to read and the Alliance +//! members to enforce. Similar to a Charter or Code of Conduct. +//! - Announcement: An IPFS CID of some content that the Alliance want to announce. +//! - Member: An account that is already in the group of the Alliance, including two types: Fellow, +//! or Ally. A member can also be kicked by the `MembershipManager` origin or retire by itself. +//! - Fellow: An account who is elevated from Ally by other Fellows. +//! - Ally: An account who would like to join the Alliance. To become a voting member (Fellow), it +//! will need approval from the `MembershipManager` origin. Any account can join as an Ally either +//! by placing a deposit or by nomination from a voting member. +//! - Unscrupulous List: A list of bad websites and addresses; items can be added or removed by +//! voting members. +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! +//! #### For General Users +//! +//! - `join_alliance` - Join the Alliance as an Ally. This requires a slashable deposit. +//! +//! #### For Members (All) +//! +//! - `give_retirement_notice` - Give a retirement notice and start a retirement period required to +//! pass in order to retire. +//! - `retire` - Retire from the Alliance and release the caller's deposit. +//! +//! #### For Voting Members +//! +//! - `propose` - Propose a motion. +//! - `vote` - Vote on a motion. +//! - `close` - Close a motion with enough votes or that has expired. +//! - `set_rule` - Initialize or update the Alliance's rule by IPFS CID. +//! - `announce` - Make announcement by IPFS CID. +//! - `nominate_ally` - Nominate a non-member to become an Ally, without deposit. +//! - `elevate_ally` - Approve an ally to become a Fellow. +//! - `kick_member` - Kick a member and slash its deposit. +//! - `add_unscrupulous_items` - Add some items, either accounts or websites, to the list of +//! unscrupulous items. +//! - `remove_unscrupulous_items` - Remove some items from the list of unscrupulous items. +//! - `abdicate_fellow_status` - Abdicate one's voting rights, demoting themself to Ally. +//! +//! #### Root Calls +//! +//! - `init_members` - Initialize the Alliance, onboard fellows and allies. +//! - `disband` - Disband the Alliance, remove all active members and unreserve deposits. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod migration; +mod types; +pub mod weights; + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; +use sp_runtime::{ + traits::{Saturating, StaticLookup, Zero}, + RuntimeDebug, +}; +use sp_std::{convert::TryInto, prelude::*}; + +use frame_support::{ + codec::{Decode, Encode, MaxEncodedLen}, + dispatch::{ + DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo, + PostDispatchInfo, + }, + ensure, + scale_info::TypeInfo, + traits::{ + ChangeMembers, Currency, Get, InitializeMembers, IsSubType, OnUnbalanced, + ReservableCurrency, + }, + weights::{OldWeight, Weight}, +}; +use pallet_identity::IdentityField; + +pub use pallet::*; +pub use types::*; +pub use weights::*; + +/// The log target of this pallet. +pub const LOG_TARGET: &str = "runtime::alliance"; + +/// Simple index type for proposal counting. +pub type ProposalIndex = u32; + +type UrlOf = BoundedVec>::MaxWebsiteUrlLength>; + +type BalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +type NegativeImbalanceOf = <>::Currency as Currency< + ::AccountId, +>>::NegativeImbalance; + +/// Interface required for identity verification. +pub trait IdentityVerifier { + /// Function that returns whether an account has an identity registered with the identity + /// provider. + fn has_identity(who: &AccountId, fields: u64) -> bool; + + /// Whether an account has been deemed "good" by the provider. + fn has_good_judgement(who: &AccountId) -> bool; + + /// If the identity provider allows sub-accounts, provide the super of an account. Should + /// return `None` if the provider does not allow sub-accounts or if the account is not a sub. + fn super_account_id(who: &AccountId) -> Option; +} + +/// The non-provider. Imposes no restrictions on account identity. +impl IdentityVerifier for () { + fn has_identity(_who: &AccountId, _fields: u64) -> bool { + true + } + + fn has_good_judgement(_who: &AccountId) -> bool { + true + } + + fn super_account_id(_who: &AccountId) -> Option { + None + } +} + +/// The provider of a collective action interface, for example an instance of `pallet-collective`. +pub trait ProposalProvider { + /// Add a new proposal. + /// Returns a proposal length and active proposals count if successful. + fn propose_proposal( + who: AccountId, + threshold: u32, + proposal: Box, + length_bound: u32, + ) -> Result<(u32, u32), DispatchError>; + + /// Add an aye or nay vote for the sender to the given proposal. + /// Returns true if the sender votes first time if successful. + fn vote_proposal( + who: AccountId, + proposal: Hash, + index: ProposalIndex, + approve: bool, + ) -> Result; + + /// Close a proposal that is either approved, disapproved, or whose voting period has ended. + fn close_proposal( + proposal_hash: Hash, + index: ProposalIndex, + proposal_weight_bound: Weight, + length_bound: u32, + ) -> DispatchResultWithPostInfo; + + /// Return a proposal of the given hash. + fn proposal_of(proposal_hash: Hash) -> Option; +} + +/// The various roles that a member can hold. +#[derive(Copy, Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum MemberRole { + Fellow, + Ally, + Retiring, +} + +/// The type of item that may be deemed unscrupulous. +#[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum UnscrupulousItem { + AccountId(AccountId), + Website(Url), +} + +type UnscrupulousItemOf = + UnscrupulousItem<::AccountId, UrlOf>; + +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub (super) trait Store)] + #[pallet::storage_version(migration::STORAGE_VERSION)] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// The runtime call dispatch type. + type Proposal: Parameter + + Dispatchable + + From> + + From> + + GetDispatchInfo + + IsSubType> + + IsType<::RuntimeCall>; + + /// Origin for admin-level operations, like setting the Alliance's rules. + type AdminOrigin: EnsureOrigin; + + /// Origin that manages entry and forcible discharge from the Alliance. + type MembershipManager: EnsureOrigin; + + /// Origin for making announcements and adding/removing unscrupulous items. + type AnnouncementOrigin: EnsureOrigin; + + /// The currency used for deposits. + type Currency: ReservableCurrency; + + /// What to do with slashed funds. + type Slashed: OnUnbalanced>; + + /// What to do with initial voting members of the Alliance. + type InitializeMembers: InitializeMembers; + + /// What to do when a member has been added or removed. + type MembershipChanged: ChangeMembers; + + /// The identity verifier of an Alliance member. + type IdentityVerifier: IdentityVerifier; + + /// The provider of the proposal operation. + type ProposalProvider: ProposalProvider; + + /// Maximum number of proposals allowed to be active in parallel. + type MaxProposals: Get; + + /// The maximum number of Fellows supported by the pallet. Used for weight estimation. + /// + /// NOTE: + /// + Benchmarks will need to be re-run and weights adjusted if this changes. + /// + This pallet assumes that dependencies keep to the limit without enforcing it. + type MaxFellows: Get; + + /// The maximum number of Allies supported by the pallet. Used for weight estimation. + /// + /// NOTE: + /// + Benchmarks will need to be re-run and weights adjusted if this changes. + /// + This pallet assumes that dependencies keep to the limit without enforcing it. + type MaxAllies: Get; + + /// The maximum number of the unscrupulous items supported by the pallet. + #[pallet::constant] + type MaxUnscrupulousItems: Get; + + /// The maximum length of a website URL. + #[pallet::constant] + type MaxWebsiteUrlLength: Get; + + /// The deposit required for submitting candidacy. + #[pallet::constant] + type AllyDeposit: Get>; + + /// The maximum number of announcements. + #[pallet::constant] + type MaxAnnouncementsCount: Get; + + /// The maximum number of members per member role. + #[pallet::constant] + type MaxMembersCount: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The number of blocks a member must wait between giving a retirement notice and retiring. + /// Supposed to be greater than time required to `kick_member`. + type RetirementPeriod: Get; + } + + #[pallet::error] + pub enum Error { + /// The Alliance has not been initialized yet, therefore accounts cannot join it. + AllianceNotYetInitialized, + /// The Alliance has been initialized, therefore cannot be initialized again. + AllianceAlreadyInitialized, + /// Account is already a member. + AlreadyMember, + /// Account is not a member. + NotMember, + /// Account is not an ally. + NotAlly, + /// Account does not have voting rights. + NoVotingRights, + /// Account is already an elevated (fellow) member. + AlreadyElevated, + /// Item is already listed as unscrupulous. + AlreadyUnscrupulous, + /// Account has been deemed unscrupulous by the Alliance and is not welcome to join or be + /// nominated. + AccountNonGrata, + /// Item has not been deemed unscrupulous. + NotListedAsUnscrupulous, + /// The number of unscrupulous items exceeds `MaxUnscrupulousItems`. + TooManyUnscrupulousItems, + /// Length of website URL exceeds `MaxWebsiteUrlLength`. + TooLongWebsiteUrl, + /// Balance is insufficient for the required deposit. + InsufficientFunds, + /// The account's identity does not have display field and website field. + WithoutIdentityDisplayAndWebsite, + /// The account's identity has no good judgement. + WithoutGoodIdentityJudgement, + /// The proposal hash is not found. + MissingProposalHash, + /// The announcement is not found. + MissingAnnouncement, + /// Number of members exceeds `MaxMembersCount`. + TooManyMembers, + /// Number of announcements exceeds `MaxAnnouncementsCount`. + TooManyAnnouncements, + /// Invalid witness data given. + BadWitness, + /// Account already gave retirement notice + AlreadyRetiring, + /// Account did not give a retirement notice required to retire. + RetirementNoticeNotGiven, + /// Retirement period has not passed. + RetirementPeriodNotPassed, + /// Fellows must be provided to initialize the Alliance. + FellowsMissing, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// A new rule has been set. + NewRuleSet { rule: Cid }, + /// A new announcement has been proposed. + Announced { announcement: Cid }, + /// An on-chain announcement has been removed. + AnnouncementRemoved { announcement: Cid }, + /// Some accounts have been initialized as members (fellows/allies). + MembersInitialized { fellows: Vec, allies: Vec }, + /// An account has been added as an Ally and reserved its deposit. + NewAllyJoined { + ally: T::AccountId, + nominator: Option, + reserved: Option>, + }, + /// An ally has been elevated to Fellow. + AllyElevated { ally: T::AccountId }, + /// A member gave retirement notice and their retirement period started. + MemberRetirementPeriodStarted { member: T::AccountId }, + /// A member has retired with its deposit unreserved. + MemberRetired { member: T::AccountId, unreserved: Option> }, + /// A member has been kicked out with its deposit slashed. + MemberKicked { member: T::AccountId, slashed: Option> }, + /// Accounts or websites have been added into the list of unscrupulous items. + UnscrupulousItemAdded { items: Vec> }, + /// Accounts or websites have been removed from the list of unscrupulous items. + UnscrupulousItemRemoved { items: Vec> }, + /// Alliance disbanded. Includes number deleted members and unreserved deposits. + AllianceDisbanded { fellow_members: u32, ally_members: u32, unreserved: u32 }, + /// A Fellow abdicated their voting rights. They are now an Ally. + FellowAbdicated { fellow: T::AccountId }, + } + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + pub fellows: Vec, + pub allies: Vec, + pub phantom: PhantomData<(T, I)>, + } + + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { fellows: Vec::new(), allies: Vec::new(), phantom: Default::default() } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + for m in self.fellows.iter().chain(self.allies.iter()) { + assert!(Pallet::::has_identity(m).is_ok(), "Member does not set identity!"); + } + + if !self.fellows.is_empty() { + assert!( + !Pallet::::has_member(MemberRole::Fellow), + "Fellows are already initialized!" + ); + let members: BoundedVec = + self.fellows.clone().try_into().expect("Too many genesis fellows"); + Members::::insert(MemberRole::Fellow, members); + } + if !self.allies.is_empty() { + assert!( + !Pallet::::has_member(MemberRole::Ally), + "Allies are already initialized!" + ); + assert!( + !self.fellows.is_empty(), + "Fellows must be provided to initialize the Alliance" + ); + let members: BoundedVec = + self.allies.clone().try_into().expect("Too many genesis allies"); + Members::::insert(MemberRole::Ally, members); + } + + T::InitializeMembers::initialize_members(self.fellows.as_slice()) + } + } + + /// The IPFS CID of the alliance rule. + /// Fellows can propose a new rule with a super-majority. + #[pallet::storage] + #[pallet::getter(fn rule)] + pub type Rule, I: 'static = ()> = StorageValue<_, Cid, OptionQuery>; + + /// The current IPFS CIDs of any announcements. + #[pallet::storage] + #[pallet::getter(fn announcements)] + pub type Announcements, I: 'static = ()> = + StorageValue<_, BoundedVec, ValueQuery>; + + /// Maps members to their candidacy deposit. + #[pallet::storage] + #[pallet::getter(fn deposit_of)] + pub type DepositOf, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf, OptionQuery>; + + /// Maps member type to members of each type. + #[pallet::storage] + #[pallet::getter(fn members)] + pub type Members, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + MemberRole, + BoundedVec, + ValueQuery, + >; + + /// A set of members who gave a retirement notice. They can retire after the end of retirement + /// period stored as a future block number. + #[pallet::storage] + #[pallet::getter(fn retiring_members)] + pub type RetiringMembers, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, T::BlockNumber, OptionQuery>; + + /// The current list of accounts deemed unscrupulous. These accounts non grata cannot submit + /// candidacy. + #[pallet::storage] + #[pallet::getter(fn unscrupulous_accounts)] + pub type UnscrupulousAccounts, I: 'static = ()> = + StorageValue<_, BoundedVec, ValueQuery>; + + /// The current list of websites deemed unscrupulous. + #[pallet::storage] + #[pallet::getter(fn unscrupulous_websites)] + pub type UnscrupulousWebsites, I: 'static = ()> = + StorageValue<_, BoundedVec, T::MaxUnscrupulousItems>, ValueQuery>; + + #[pallet::call] + impl, I: 'static> Pallet { + /// Add a new proposal to be voted on. + /// + /// Must be called by a Fellow. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::propose_proposed( + *length_bound, // B + T::MaxFellows::get(), // M + T::MaxProposals::get(), // P2 + ))] + pub fn propose( + origin: OriginFor, + #[pallet::compact] threshold: u32, + proposal: Box<>::Proposal>, + #[pallet::compact] length_bound: u32, + ) -> DispatchResult { + let proposor = ensure_signed(origin)?; + ensure!(Self::has_voting_rights(&proposor), Error::::NoVotingRights); + + T::ProposalProvider::propose_proposal(proposor, threshold, proposal, length_bound)?; + Ok(()) + } + + /// Add an aye or nay vote for the sender to the given proposal. + /// + /// Must be called by a Fellow. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::vote(T::MaxFellows::get()))] + pub fn vote( + origin: OriginFor, + proposal: T::Hash, + #[pallet::compact] index: ProposalIndex, + approve: bool, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + ensure!(Self::has_voting_rights(&who), Error::::NoVotingRights); + + T::ProposalProvider::vote_proposal(who, proposal, index, approve)?; + Ok(()) + } + + /// Close a vote that is either approved, disapproved, or whose voting period has ended. + /// + /// Must be called by a Fellow. + #[pallet::call_index(2)] + #[pallet::weight({ + let b = *length_bound; + let m = T::MaxFellows::get(); + let p1 = *proposal_weight_bound; + let p2 = T::MaxProposals::get(); + T::WeightInfo::close_early_approved(b, m, p2) + .max(T::WeightInfo::close_early_disapproved(m, p2)) + .max(T::WeightInfo::close_approved(b, m, p2)) + .max(T::WeightInfo::close_disapproved(m, p2)) + .saturating_add(p1.into()) + })] + #[allow(deprecated)] + #[deprecated(note = "1D weight is used in this extrinsic, please migrate to use `close`")] + pub fn close_old_weight( + origin: OriginFor, + proposal_hash: T::Hash, + #[pallet::compact] index: ProposalIndex, + #[pallet::compact] proposal_weight_bound: OldWeight, + #[pallet::compact] length_bound: u32, + ) -> DispatchResultWithPostInfo { + let proposal_weight_bound: Weight = proposal_weight_bound.into(); + let who = ensure_signed(origin)?; + ensure!(Self::has_voting_rights(&who), Error::::NoVotingRights); + + Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound) + } + + /// Initialize the Alliance, onboard fellows and allies. + /// + /// The Alliance must be empty, and the call must provide some founding members. + /// + /// Must be called by the Root origin. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::init_members( + fellows.len() as u32, + allies.len() as u32, + ))] + pub fn init_members( + origin: OriginFor, + fellows: Vec, + allies: Vec, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!(!fellows.is_empty(), Error::::FellowsMissing); + ensure!(!Self::is_initialized(), Error::::AllianceAlreadyInitialized); + + let mut fellows: BoundedVec = + fellows.try_into().map_err(|_| Error::::TooManyMembers)?; + let mut allies: BoundedVec = + allies.try_into().map_err(|_| Error::::TooManyMembers)?; + + for member in fellows.iter().chain(allies.iter()) { + Self::has_identity(member)?; + } + + fellows.sort(); + Members::::insert(&MemberRole::Fellow, fellows.clone()); + allies.sort(); + Members::::insert(&MemberRole::Ally, allies.clone()); + + let mut voteable_members = fellows.clone(); + voteable_members.sort(); + + T::InitializeMembers::initialize_members(&voteable_members); + + log::debug!( + target: LOG_TARGET, + "Initialize alliance fellows: {:?}, allies: {:?}", + fellows, + allies + ); + + Self::deposit_event(Event::MembersInitialized { + fellows: fellows.into(), + allies: allies.into(), + }); + Ok(()) + } + + /// Disband the Alliance, remove all active members and unreserve deposits. + /// + /// Witness data must be set. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::disband( + witness.fellow_members, + witness.ally_members, + witness.fellow_members.saturating_add(witness.ally_members), + ))] + pub fn disband( + origin: OriginFor, + witness: DisbandWitness, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + ensure!(!witness.is_zero(), Error::::BadWitness); + ensure!( + Self::voting_members_count() <= witness.fellow_members, + Error::::BadWitness + ); + ensure!(Self::ally_members_count() <= witness.ally_members, Error::::BadWitness); + ensure!(Self::is_initialized(), Error::::AllianceNotYetInitialized); + + let voting_members = Self::voting_members(); + T::MembershipChanged::change_members_sorted(&[], &voting_members, &[]); + + let ally_members = Self::members_of(MemberRole::Ally); + let mut unreserve_count: u32 = 0; + for member in voting_members.iter().chain(ally_members.iter()) { + if let Some(deposit) = DepositOf::::take(&member) { + let err_amount = T::Currency::unreserve(&member, deposit); + debug_assert!(err_amount.is_zero()); + unreserve_count += 1; + } + } + + Members::::remove(&MemberRole::Fellow); + Members::::remove(&MemberRole::Ally); + + Self::deposit_event(Event::AllianceDisbanded { + fellow_members: voting_members.len() as u32, + ally_members: ally_members.len() as u32, + unreserved: unreserve_count, + }); + + Ok(Some(T::WeightInfo::disband( + voting_members.len() as u32, + ally_members.len() as u32, + unreserve_count, + )) + .into()) + } + + /// Set a new IPFS CID to the alliance rule. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::set_rule())] + pub fn set_rule(origin: OriginFor, rule: Cid) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + Rule::::put(&rule); + + Self::deposit_event(Event::NewRuleSet { rule }); + Ok(()) + } + + /// Make an announcement of a new IPFS CID about alliance issues. + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::announce())] + pub fn announce(origin: OriginFor, announcement: Cid) -> DispatchResult { + T::AnnouncementOrigin::ensure_origin(origin)?; + + let mut announcements = >::get(); + announcements + .try_push(announcement.clone()) + .map_err(|_| Error::::TooManyAnnouncements)?; + >::put(announcements); + + Self::deposit_event(Event::Announced { announcement }); + Ok(()) + } + + /// Remove an announcement. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::remove_announcement())] + pub fn remove_announcement(origin: OriginFor, announcement: Cid) -> DispatchResult { + T::AnnouncementOrigin::ensure_origin(origin)?; + + let mut announcements = >::get(); + let pos = announcements + .binary_search(&announcement) + .ok() + .ok_or(Error::::MissingAnnouncement)?; + announcements.remove(pos); + >::put(announcements); + + Self::deposit_event(Event::AnnouncementRemoved { announcement }); + Ok(()) + } + + /// Submit oneself for candidacy. A fixed deposit is reserved. + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::join_alliance())] + pub fn join_alliance(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + + // We don't want anyone to join as an Ally before the Alliance has been initialized via + // Root call. The reasons are two-fold: + // + // 1. There is no `Rule` or admission criteria, so the joiner would be an ally to + // nought, and + // 2. It adds complexity to the initialization, namely deciding to overwrite accounts + // that already joined as an Ally. + ensure!(Self::is_initialized(), Error::::AllianceNotYetInitialized); + + // Unscrupulous accounts are non grata. + ensure!(!Self::is_unscrupulous_account(&who), Error::::AccountNonGrata); + ensure!(!Self::is_member(&who), Error::::AlreadyMember); + // check user self or parent should has verified identity to reuse display name and + // website. + Self::has_identity(&who)?; + + let deposit = T::AllyDeposit::get(); + T::Currency::reserve(&who, deposit).map_err(|_| Error::::InsufficientFunds)?; + >::insert(&who, deposit); + + Self::add_member(&who, MemberRole::Ally)?; + + Self::deposit_event(Event::NewAllyJoined { + ally: who, + nominator: None, + reserved: Some(deposit), + }); + Ok(()) + } + + /// A Fellow can nominate someone to join the alliance as an Ally. There is no deposit + /// required from the nominator or nominee. + #[pallet::call_index(9)] + #[pallet::weight(T::WeightInfo::nominate_ally())] + pub fn nominate_ally(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { + let nominator = ensure_signed(origin)?; + ensure!(Self::has_voting_rights(&nominator), Error::::NoVotingRights); + let who = T::Lookup::lookup(who)?; + + // Individual voting members cannot nominate accounts non grata. + ensure!(!Self::is_unscrupulous_account(&who), Error::::AccountNonGrata); + ensure!(!Self::is_member(&who), Error::::AlreadyMember); + // check user self or parent should has verified identity to reuse display name and + // website. + Self::has_identity(&who)?; + + Self::add_member(&who, MemberRole::Ally)?; + + Self::deposit_event(Event::NewAllyJoined { + ally: who, + nominator: Some(nominator), + reserved: None, + }); + Ok(()) + } + + /// Elevate an Ally to Fellow. + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::elevate_ally())] + pub fn elevate_ally(origin: OriginFor, ally: AccountIdLookupOf) -> DispatchResult { + T::MembershipManager::ensure_origin(origin)?; + let ally = T::Lookup::lookup(ally)?; + ensure!(Self::is_ally(&ally), Error::::NotAlly); + ensure!(!Self::has_voting_rights(&ally), Error::::AlreadyElevated); + + Self::remove_member(&ally, MemberRole::Ally)?; + Self::add_member(&ally, MemberRole::Fellow)?; + + Self::deposit_event(Event::AllyElevated { ally }); + Ok(()) + } + + /// As a member, give a retirement notice and start a retirement period required to pass in + /// order to retire. + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::give_retirement_notice())] + pub fn give_retirement_notice(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let role = Self::member_role_of(&who).ok_or(Error::::NotMember)?; + ensure!(role.ne(&MemberRole::Retiring), Error::::AlreadyRetiring); + + Self::remove_member(&who, role)?; + Self::add_member(&who, MemberRole::Retiring)?; + >::insert( + &who, + frame_system::Pallet::::block_number() + .saturating_add(T::RetirementPeriod::get()), + ); + + Self::deposit_event(Event::MemberRetirementPeriodStarted { member: who }); + Ok(()) + } + + /// As a member, retire from the Alliance and unreserve the deposit. + /// + /// This can only be done once you have called `give_retirement_notice` and the + /// `RetirementPeriod` has passed. + #[pallet::call_index(12)] + #[pallet::weight(T::WeightInfo::retire())] + pub fn retire(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let retirement_period_end = RetiringMembers::::get(&who) + .ok_or(Error::::RetirementNoticeNotGiven)?; + ensure!( + frame_system::Pallet::::block_number() >= retirement_period_end, + Error::::RetirementPeriodNotPassed + ); + + Self::remove_member(&who, MemberRole::Retiring)?; + >::remove(&who); + let deposit = DepositOf::::take(&who); + if let Some(deposit) = deposit { + let err_amount = T::Currency::unreserve(&who, deposit); + debug_assert!(err_amount.is_zero()); + } + Self::deposit_event(Event::MemberRetired { member: who, unreserved: deposit }); + Ok(()) + } + + /// Kick a member from the Alliance and slash its deposit. + #[pallet::call_index(13)] + #[pallet::weight(T::WeightInfo::kick_member())] + pub fn kick_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { + T::MembershipManager::ensure_origin(origin)?; + let member = T::Lookup::lookup(who)?; + + let role = Self::member_role_of(&member).ok_or(Error::::NotMember)?; + Self::remove_member(&member, role)?; + let deposit = DepositOf::::take(member.clone()); + if let Some(deposit) = deposit { + T::Slashed::on_unbalanced(T::Currency::slash_reserved(&member, deposit).0); + } + + Self::deposit_event(Event::MemberKicked { member, slashed: deposit }); + Ok(()) + } + + /// Add accounts or websites to the list of unscrupulous items. + #[pallet::call_index(14)] + #[pallet::weight(T::WeightInfo::add_unscrupulous_items(items.len() as u32, T::MaxWebsiteUrlLength::get()))] + pub fn add_unscrupulous_items( + origin: OriginFor, + items: Vec>, + ) -> DispatchResult { + T::AnnouncementOrigin::ensure_origin(origin)?; + + let mut accounts = vec![]; + let mut webs = vec![]; + for info in items.iter() { + ensure!(!Self::is_unscrupulous(info), Error::::AlreadyUnscrupulous); + match info { + UnscrupulousItem::AccountId(who) => accounts.push(who.clone()), + UnscrupulousItem::Website(url) => { + ensure!( + url.len() as u32 <= T::MaxWebsiteUrlLength::get(), + Error::::TooLongWebsiteUrl + ); + webs.push(url.clone()); + }, + } + } + + Self::do_add_unscrupulous_items(&mut accounts, &mut webs)?; + Self::deposit_event(Event::UnscrupulousItemAdded { items }); + Ok(()) + } + + /// Deem some items no longer unscrupulous. + #[pallet::call_index(15)] + #[pallet::weight(>::WeightInfo::remove_unscrupulous_items( + items.len() as u32, T::MaxWebsiteUrlLength::get() + ))] + pub fn remove_unscrupulous_items( + origin: OriginFor, + items: Vec>, + ) -> DispatchResult { + T::AnnouncementOrigin::ensure_origin(origin)?; + let mut accounts = vec![]; + let mut webs = vec![]; + for info in items.iter() { + ensure!(Self::is_unscrupulous(info), Error::::NotListedAsUnscrupulous); + match info { + UnscrupulousItem::AccountId(who) => accounts.push(who.clone()), + UnscrupulousItem::Website(url) => webs.push(url.clone()), + } + } + Self::do_remove_unscrupulous_items(&mut accounts, &mut webs)?; + Self::deposit_event(Event::UnscrupulousItemRemoved { items }); + Ok(()) + } + + /// Close a vote that is either approved, disapproved, or whose voting period has ended. + /// + /// Must be called by a Fellow. + #[pallet::call_index(16)] + #[pallet::weight({ + let b = *length_bound; + let m = T::MaxFellows::get(); + let p1 = *proposal_weight_bound; + let p2 = T::MaxProposals::get(); + T::WeightInfo::close_early_approved(b, m, p2) + .max(T::WeightInfo::close_early_disapproved(m, p2)) + .max(T::WeightInfo::close_approved(b, m, p2)) + .max(T::WeightInfo::close_disapproved(m, p2)) + .saturating_add(p1) + })] + pub fn close( + origin: OriginFor, + proposal_hash: T::Hash, + #[pallet::compact] index: ProposalIndex, + proposal_weight_bound: Weight, + #[pallet::compact] length_bound: u32, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!(Self::has_voting_rights(&who), Error::::NoVotingRights); + + Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound) + } + + /// Abdicate one's position as a voting member and just be an Ally. May be used by Fellows + /// who do not want to leave the Alliance but do not have the capacity to participate + /// operationally for some time. + #[pallet::call_index(17)] + #[pallet::weight(T::WeightInfo::abdicate_fellow_status())] + pub fn abdicate_fellow_status(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let role = Self::member_role_of(&who).ok_or(Error::::NotMember)?; + // Not applicable to members who are retiring or who are already Allies. + ensure!(Self::has_voting_rights(&who), Error::::NoVotingRights); + + Self::remove_member(&who, role)?; + Self::add_member(&who, MemberRole::Ally)?; + + Self::deposit_event(Event::FellowAbdicated { fellow: who }); + Ok(()) + } + } +} + +impl, I: 'static> Pallet { + /// Check if the Alliance has been initialized. + fn is_initialized() -> bool { + Self::has_member(MemberRole::Fellow) || Self::has_member(MemberRole::Ally) + } + + /// Check if a given role has any members. + fn has_member(role: MemberRole) -> bool { + Members::::decode_len(role).unwrap_or_default() > 0 + } + + /// Look up the role, if any, of an account. + fn member_role_of(who: &T::AccountId) -> Option { + Members::::iter() + .find_map(|(r, members)| if members.contains(who) { Some(r) } else { None }) + } + + /// Check if a user is a alliance member. + pub fn is_member(who: &T::AccountId) -> bool { + Self::member_role_of(who).is_some() + } + + /// Check if an account has a given role. + pub fn is_member_of(who: &T::AccountId, role: MemberRole) -> bool { + Members::::get(role).contains(&who) + } + + /// Check if an account is an Ally. + fn is_ally(who: &T::AccountId) -> bool { + Self::is_member_of(who, MemberRole::Ally) + } + + /// Check if a member has voting rights. + fn has_voting_rights(who: &T::AccountId) -> bool { + Self::is_member_of(who, MemberRole::Fellow) + } + + /// Count of ally members. + fn ally_members_count() -> u32 { + Members::::decode_len(MemberRole::Ally).unwrap_or(0) as u32 + } + + /// Count of all members who have voting rights. + fn voting_members_count() -> u32 { + Members::::decode_len(MemberRole::Fellow).unwrap_or(0) as u32 + } + + /// Get all members of a given role. + fn members_of(role: MemberRole) -> Vec { + Members::::get(role).into_inner() + } + + /// Collect all members who have voting rights into one list. + fn voting_members() -> Vec { + Self::members_of(MemberRole::Fellow) + } + + /// Add a user to the sorted alliance member set. + fn add_member(who: &T::AccountId, role: MemberRole) -> DispatchResult { + >::try_mutate(role, |members| -> DispatchResult { + let pos = members.binary_search(who).err().ok_or(Error::::AlreadyMember)?; + members + .try_insert(pos, who.clone()) + .map_err(|_| Error::::TooManyMembers)?; + Ok(()) + })?; + + if role == MemberRole::Fellow { + let members = Self::voting_members(); + T::MembershipChanged::change_members_sorted(&[who.clone()], &[], &members[..]); + } + Ok(()) + } + + /// Remove a user from the alliance member set. + fn remove_member(who: &T::AccountId, role: MemberRole) -> DispatchResult { + >::try_mutate(role, |members| -> DispatchResult { + let pos = members.binary_search(who).ok().ok_or(Error::::NotMember)?; + members.remove(pos); + Ok(()) + })?; + + if role == MemberRole::Fellow { + let members = Self::voting_members(); + T::MembershipChanged::change_members_sorted(&[], &[who.clone()], &members[..]); + } + Ok(()) + } + + /// Check if an item is listed as unscrupulous. + fn is_unscrupulous(info: &UnscrupulousItemOf) -> bool { + match info { + UnscrupulousItem::Website(url) => >::get().contains(url), + UnscrupulousItem::AccountId(who) => >::get().contains(who), + } + } + + /// Check if an account is listed as unscrupulous. + fn is_unscrupulous_account(who: &T::AccountId) -> bool { + >::get().contains(who) + } + + /// Add item to the unscrupulous list. + fn do_add_unscrupulous_items( + new_accounts: &mut Vec, + new_webs: &mut Vec>, + ) -> DispatchResult { + if !new_accounts.is_empty() { + >::try_mutate(|accounts| -> DispatchResult { + accounts + .try_append(new_accounts) + .map_err(|_| Error::::TooManyUnscrupulousItems)?; + accounts.sort(); + + Ok(()) + })?; + } + if !new_webs.is_empty() { + >::try_mutate(|webs| -> DispatchResult { + webs.try_append(new_webs).map_err(|_| Error::::TooManyUnscrupulousItems)?; + webs.sort(); + + Ok(()) + })?; + } + + Ok(()) + } + + /// Remove item from the unscrupulous list. + fn do_remove_unscrupulous_items( + out_accounts: &mut Vec, + out_webs: &mut Vec>, + ) -> DispatchResult { + if !out_accounts.is_empty() { + >::try_mutate(|accounts| -> DispatchResult { + for who in out_accounts.iter() { + let pos = accounts + .binary_search(who) + .ok() + .ok_or(Error::::NotListedAsUnscrupulous)?; + accounts.remove(pos); + } + Ok(()) + })?; + } + if !out_webs.is_empty() { + >::try_mutate(|webs| -> DispatchResult { + for web in out_webs.iter() { + let pos = webs + .binary_search(web) + .ok() + .ok_or(Error::::NotListedAsUnscrupulous)?; + webs.remove(pos); + } + Ok(()) + })?; + } + Ok(()) + } + + fn has_identity(who: &T::AccountId) -> DispatchResult { + const IDENTITY_FIELD_DISPLAY: u64 = IdentityField::Display as u64; + const IDENTITY_FIELD_WEB: u64 = IdentityField::Web as u64; + + let judgement = |who: &T::AccountId| -> DispatchResult { + ensure!( + T::IdentityVerifier::has_identity(who, IDENTITY_FIELD_DISPLAY | IDENTITY_FIELD_WEB), + Error::::WithoutIdentityDisplayAndWebsite + ); + ensure!( + T::IdentityVerifier::has_good_judgement(who), + Error::::WithoutGoodIdentityJudgement + ); + Ok(()) + }; + + let res = judgement(who); + if res.is_err() { + if let Some(parent) = T::IdentityVerifier::super_account_id(who) { + return judgement(&parent) + } + } + res + } + + fn do_close( + proposal_hash: T::Hash, + index: ProposalIndex, + proposal_weight_bound: Weight, + length_bound: u32, + ) -> DispatchResultWithPostInfo { + let info = T::ProposalProvider::close_proposal( + proposal_hash, + index, + proposal_weight_bound, + length_bound, + )?; + Ok(info.into()) + } +} diff --git a/frame/alliance/src/migration.rs b/frame/alliance/src/migration.rs new file mode 100644 index 0000000000000..ea07f8c1279b1 --- /dev/null +++ b/frame/alliance/src/migration.rs @@ -0,0 +1,179 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::{Config, Pallet, Weight, LOG_TARGET}; +use frame_support::{pallet_prelude::*, storage::migration, traits::OnRuntimeUpgrade}; +use log; + +/// The current storage version. +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + +/// Wrapper for all migrations of this pallet. +pub fn migrate, I: 'static>() -> Weight { + let onchain_version = Pallet::::on_chain_storage_version(); + let mut weight: Weight = Weight::zero(); + + if onchain_version < 1 { + weight = weight.saturating_add(v0_to_v1::migrate::()); + } + + if onchain_version < 2 { + weight = weight.saturating_add(v1_to_v2::migrate::()); + } + + STORAGE_VERSION.put::>(); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + weight +} + +/// Implements `OnRuntimeUpgrade` trait. +pub struct Migration(PhantomData<(T, I)>); + +impl, I: 'static> OnRuntimeUpgrade for Migration { + fn on_runtime_upgrade() -> Weight { + migrate::() + } +} + +/// v0_to_v1: `UpForKicking` is replaced by a retirement period. +mod v0_to_v1 { + use super::*; + + pub fn migrate, I: 'static>() -> Weight { + log::info!(target: LOG_TARGET, "Running migration v0_to_v1."); + + let res = migration::clear_storage_prefix( + >::name().as_bytes(), + b"UpForKicking", + b"", + None, + None, + ); + + log::info!( + target: LOG_TARGET, + "Cleared '{}' entries from 'UpForKicking' storage prefix", + res.unique + ); + + if res.maybe_cursor.is_some() { + log::error!( + target: LOG_TARGET, + "Storage prefix 'UpForKicking' is not completely cleared." + ); + } + + T::DbWeight::get().writes(res.unique.into()) + } +} + +/// v1_to_v2: `Members` storage map collapses `Founder` and `Fellow` keys into one `Fellow`. +/// Total number of `Founder`s and `Fellow`s must not be higher than `T::MaxMembersCount`. +pub(crate) mod v1_to_v2 { + use super::*; + use crate::{MemberRole, Members}; + + /// V1 Role set. + #[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen)] + pub enum MemberRoleV1 { + Founder, + Fellow, + Ally, + Retiring, + } + + pub fn migrate, I: 'static>() -> Weight { + log::info!(target: LOG_TARGET, "Running migration v1_to_v2: `Members` storage map collapses `Founder` and `Fellow` keys into one `Fellow`."); + // fetch into the scope all members. + let founders_vec = take_members::(MemberRoleV1::Founder).into_inner(); + let mut fellows_vec = take_members::(MemberRoleV1::Fellow).into_inner(); + let allies = take_members::(MemberRoleV1::Ally); + let retiring = take_members::(MemberRoleV1::Retiring); + if founders_vec + .len() + .saturating_add(fellows_vec.len()) + .saturating_add(allies.len()) + .saturating_add(retiring.len()) == + 0 + { + return T::DbWeight::get().reads(4) + } + log::info!( + target: LOG_TARGET, + "Members storage v1 contains, '{}' founders, '{}' fellows, '{}' allies, '{}' retiring members.", + founders_vec.len(), + fellows_vec.len(), + allies.len(), + retiring.len(), + ); + // merge founders with fellows and sort. + fellows_vec.extend(founders_vec); + fellows_vec.sort(); + if fellows_vec.len() as u32 > T::MaxMembersCount::get() { + log::error!( + target: LOG_TARGET, + "Merged list of founders and fellows do not fit into `T::MaxMembersCount` bound. Truncating the merged set into max members count." + ); + fellows_vec.truncate(T::MaxMembersCount::get() as usize); + } + let fellows: BoundedVec = + fellows_vec.try_into().unwrap_or_default(); + // insert members with new storage map key. + Members::::insert(&MemberRole::Fellow, fellows.clone()); + Members::::insert(&MemberRole::Ally, allies.clone()); + Members::::insert(&MemberRole::Retiring, retiring.clone()); + log::info!( + target: LOG_TARGET, + "Members storage updated with, '{}' fellows, '{}' allies, '{}' retiring members.", + fellows.len(), + allies.len(), + retiring.len(), + ); + T::DbWeight::get().reads_writes(4, 4) + } + + fn take_members, I: 'static>( + role: MemberRoleV1, + ) -> BoundedVec { + migration::take_storage_item::< + MemberRoleV1, + BoundedVec, + Twox64Concat, + >(>::name().as_bytes(), b"Members", role) + .unwrap_or_default() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{mock::*, MemberRole}; + + #[test] + fn migration_v1_to_v2_works() { + new_test_ext().execute_with(|| { + assert_ok!(Alliance::join_alliance(RuntimeOrigin::signed(4))); + assert_eq!(Alliance::members(MemberRole::Ally), vec![4]); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); + v1_to_v2::migrate::(); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3, 4]); + assert_eq!(Alliance::members(MemberRole::Ally), vec![]); + assert_eq!(Alliance::members(MemberRole::Retiring), vec![]); + }); + } +} diff --git a/frame/alliance/src/mock.rs b/frame/alliance/src/mock.rs new file mode 100644 index 0000000000000..e708d29d529fe --- /dev/null +++ b/frame/alliance/src/mock.rs @@ -0,0 +1,393 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 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 utilities + +pub use sp_core::H256; +use sp_runtime::traits::Hash; +pub use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +use sp_std::convert::{TryFrom, TryInto}; + +pub use frame_support::{ + assert_noop, assert_ok, ord_parameter_types, parameter_types, + traits::{EitherOfDiverse, GenesisBuild, SortedMembers}, + BoundedVec, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use pallet_identity::{Data, IdentityInfo, Judgement}; + +pub use crate as pallet_alliance; + +use super::*; + +type BlockNumber = u64; +type AccountId = u64; + +parameter_types! { + pub const BlockHashCount: BlockNumber = 250; +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + 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>; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; + pub const MaxLocks: u32 = 10; +} +impl pallet_balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + +const MOTION_DURATION_IN_BLOCKS: BlockNumber = 3; + +parameter_types! { + pub const MotionDuration: BlockNumber = MOTION_DURATION_IN_BLOCKS; + pub const MaxProposals: u32 = 100; + pub const MaxMembers: u32 = 100; +} +type AllianceCollective = pallet_collective::Instance1; +impl pallet_collective::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = MotionDuration; + type MaxProposals = MaxProposals; + type MaxMembers = MaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type WeightInfo = (); +} + +parameter_types! { + pub const BasicDeposit: u64 = 10; + pub const FieldDeposit: u64 = 10; + pub const SubAccountDeposit: u64 = 10; + pub const MaxSubAccounts: u32 = 2; + pub const MaxAdditionalFields: u32 = 2; + pub const MaxRegistrars: u32 = 20; +} +ord_parameter_types! { + pub const One: u64 = 1; + pub const Two: u64 = 2; + pub const Three: u64 = 3; + pub const Four: u64 = 4; + pub const Five: u64 = 5; +} +type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; +type EnsureTwoOrRoot = EitherOfDiverse, EnsureSignedBy>; + +impl pallet_identity::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BasicDeposit = BasicDeposit; + type FieldDeposit = FieldDeposit; + type SubAccountDeposit = SubAccountDeposit; + type MaxSubAccounts = MaxSubAccounts; + type MaxAdditionalFields = MaxAdditionalFields; + type MaxRegistrars = MaxRegistrars; + type Slashed = (); + type RegistrarOrigin = EnsureOneOrRoot; + type ForceOrigin = EnsureTwoOrRoot; + type WeightInfo = (); +} + +pub struct AllianceIdentityVerifier; +impl IdentityVerifier for AllianceIdentityVerifier { + fn has_identity(who: &AccountId, fields: u64) -> bool { + Identity::has_identity(who, fields) + } + + fn has_good_judgement(who: &AccountId) -> bool { + if let Some(judgements) = + Identity::identity(who).map(|registration| registration.judgements) + { + judgements + .iter() + .any(|(_, j)| matches!(j, Judgement::KnownGood | Judgement::Reasonable)) + } else { + false + } + } + + fn super_account_id(who: &AccountId) -> Option { + Identity::super_of(who).map(|parent| parent.0) + } +} + +pub struct AllianceProposalProvider; +impl ProposalProvider for AllianceProposalProvider { + fn propose_proposal( + who: AccountId, + threshold: u32, + proposal: Box, + length_bound: u32, + ) -> Result<(u32, u32), DispatchError> { + AllianceMotion::do_propose_proposed(who, threshold, proposal, length_bound) + } + + fn vote_proposal( + who: AccountId, + proposal: H256, + index: ProposalIndex, + approve: bool, + ) -> Result { + AllianceMotion::do_vote(who, proposal, index, approve) + } + + fn close_proposal( + proposal_hash: H256, + proposal_index: ProposalIndex, + proposal_weight_bound: Weight, + length_bound: u32, + ) -> DispatchResultWithPostInfo { + AllianceMotion::do_close(proposal_hash, proposal_index, proposal_weight_bound, length_bound) + } + + fn proposal_of(proposal_hash: H256) -> Option { + AllianceMotion::proposal_of(proposal_hash) + } +} + +parameter_types! { + pub const MaxFellows: u32 = MaxMembers::get(); + pub const MaxAllies: u32 = 100; + pub const AllyDeposit: u64 = 25; + pub const RetirementPeriod: BlockNumber = MOTION_DURATION_IN_BLOCKS + 1; +} +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type Proposal = RuntimeCall; + type AdminOrigin = EnsureSignedBy; + type MembershipManager = EnsureSignedBy; + type AnnouncementOrigin = EnsureSignedBy; + type Currency = Balances; + type Slashed = (); + type InitializeMembers = AllianceMotion; + type MembershipChanged = AllianceMotion; + #[cfg(not(feature = "runtime-benchmarks"))] + type IdentityVerifier = AllianceIdentityVerifier; + #[cfg(feature = "runtime-benchmarks")] + type IdentityVerifier = (); + type ProposalProvider = AllianceProposalProvider; + type MaxProposals = MaxProposals; + type MaxFellows = MaxFellows; + type MaxAllies = MaxAllies; + type MaxUnscrupulousItems = ConstU32<100>; + type MaxWebsiteUrlLength = ConstU32<255>; + type MaxAnnouncementsCount = ConstU32<100>; + type MaxMembersCount = MaxMembers; + type AllyDeposit = AllyDeposit; + type WeightInfo = (); + type RetirementPeriod = RetirementPeriod; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Identity: pallet_identity, + AllianceMotion: pallet_collective::, + Alliance: pallet_alliance, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (1, 50), + (2, 50), + (3, 50), + (4, 50), + (5, 30), + (6, 50), + (7, 50), + (8, 50), + (9, 50), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + GenesisBuild::::assimilate_storage( + &pallet_alliance::GenesisConfig { + fellows: vec![], + allies: vec![], + phantom: Default::default(), + }, + &mut t, + ) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 1)); + + let info = IdentityInfo { + additional: BoundedVec::default(), + display: Data::Raw(b"name".to_vec().try_into().unwrap()), + legal: Data::default(), + web: Data::Raw(b"website".to_vec().try_into().unwrap()), + riot: Data::default(), + email: Data::default(), + pgp_fingerprint: None, + image: Data::default(), + twitter: Data::default(), + }; + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(1), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(1), + 0, + 1, + Judgement::KnownGood, + BlakeTwo256::hash_of(&info) + )); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(2), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(1), + 0, + 2, + Judgement::KnownGood, + BlakeTwo256::hash_of(&info) + )); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(3), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(1), + 0, + 3, + Judgement::KnownGood, + BlakeTwo256::hash_of(&info) + )); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(4), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(1), + 0, + 4, + Judgement::KnownGood, + BlakeTwo256::hash_of(&info) + )); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(5), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(1), + 0, + 5, + Judgement::KnownGood, + BlakeTwo256::hash_of(&info) + )); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(6), Box::new(info.clone()))); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(8), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(1), + 0, + 8, + Judgement::KnownGood, + BlakeTwo256::hash_of(&info) + )); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(9), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(1), + 0, + 9, + Judgement::KnownGood, + BlakeTwo256::hash_of(&info) + )); + + // Joining before init should fail. + assert_noop!( + Alliance::join_alliance(RuntimeOrigin::signed(1)), + Error::::AllianceNotYetInitialized + ); + + assert_ok!(Alliance::init_members(RuntimeOrigin::root(), vec![1, 2, 3], vec![])); + + System::set_block_number(1); + }); + ext +} + +#[cfg(feature = "runtime-benchmarks")] +pub fn new_bench_ext() -> sp_io::TestExternalities { + GenesisConfig::default().build_storage().unwrap().into() +} + +pub fn test_cid() -> Cid { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(b"hello world"); + let result = hasher.finalize(); + Cid::new_v0(&*result) +} + +pub fn make_remark_proposal(value: u64) -> (RuntimeCall, u32, H256) { + make_proposal(RuntimeCall::System(frame_system::Call::remark { remark: value.encode() })) +} + +pub fn make_kick_member_proposal(who: AccountId) -> (RuntimeCall, u32, H256) { + make_proposal(RuntimeCall::Alliance(pallet_alliance::Call::kick_member { who })) +} + +pub fn make_proposal(proposal: RuntimeCall) -> (RuntimeCall, u32, H256) { + let len: u32 = proposal.using_encoded(|p| p.len() as u32); + let hash = BlakeTwo256::hash_of(&proposal); + (proposal, len, hash) +} + +pub fn is_fellow(who: &AccountId) -> bool { + Alliance::is_member_of(who, MemberRole::Fellow) +} diff --git a/frame/alliance/src/tests.rs b/frame/alliance/src/tests.rs new file mode 100644 index 0000000000000..363b19429b80f --- /dev/null +++ b/frame/alliance/src/tests.rs @@ -0,0 +1,631 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 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. + +//! Tests for the alliance pallet. + +use frame_support::{assert_noop, assert_ok, error::BadOrigin}; +use frame_system::{EventRecord, Phase}; + +use super::*; +use crate::mock::*; + +type AllianceMotionEvent = pallet_collective::Event; + +fn assert_powerless(user: RuntimeOrigin, user_is_member: bool) { + //vote / veto with a valid propsal + let cid = test_cid(); + let (proposal, _, _) = make_kick_member_proposal(42); + + assert_noop!(Alliance::init_members(user.clone(), vec![], vec![],), BadOrigin); + + assert_noop!( + Alliance::disband(user.clone(), DisbandWitness { fellow_members: 3, ..Default::default() }), + BadOrigin + ); + + assert_noop!(Alliance::set_rule(user.clone(), cid.clone()), BadOrigin); + + assert_noop!(Alliance::retire(user.clone()), Error::::RetirementNoticeNotGiven); + + // Allies should be able to give retirement notice. + if !user_is_member { + assert_noop!(Alliance::give_retirement_notice(user.clone()), Error::::NotMember); + } + + assert_noop!(Alliance::elevate_ally(user.clone(), 4), BadOrigin); + + assert_noop!(Alliance::kick_member(user.clone(), 1), BadOrigin); + + assert_noop!(Alliance::nominate_ally(user.clone(), 4), Error::::NoVotingRights); + + assert_noop!( + Alliance::propose(user.clone(), 5, Box::new(proposal), 1000), + Error::::NoVotingRights + ); +} + +#[test] +fn init_members_works() { + new_test_ext().execute_with(|| { + // alliance must be reset first, no witness data + assert_noop!( + Alliance::init_members(RuntimeOrigin::root(), vec![8], vec![],), + Error::::AllianceAlreadyInitialized, + ); + + // give a retirement notice to check later a retiring member not removed + assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(2))); + assert!(Alliance::is_member_of(&2, MemberRole::Retiring)); + + // disband the Alliance to init new + assert_ok!(Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(2, 0))); + + // fails without root + assert_noop!(Alliance::init_members(RuntimeOrigin::signed(1), vec![], vec![]), BadOrigin); + + // fellows missing, other members given + assert_noop!( + Alliance::init_members(RuntimeOrigin::root(), vec![], vec![2],), + Error::::FellowsMissing, + ); + + // success call + assert_ok!(Alliance::init_members(RuntimeOrigin::root(), vec![8, 5], vec![2],)); + + // assert new set of voting members + assert_eq!(Alliance::voting_members(), vec![5, 8]); + // assert new members member + assert!(is_fellow(&8)); + assert!(is_fellow(&5)); + assert!(Alliance::is_ally(&2)); + // assert a retiring member from previous Alliance not removed + assert!(Alliance::is_member_of(&2, MemberRole::Retiring)); + + System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::MembersInitialized { + fellows: vec![5, 8], + allies: vec![2], + })); + }) +} + +#[test] +fn disband_works() { + new_test_ext().execute_with(|| { + // ensure alliance is set + assert_eq!(Alliance::voting_members(), vec![1, 2, 3]); + + // give a retirement notice to check later a retiring member not removed + assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(2))); + assert!(Alliance::is_member_of(&2, MemberRole::Retiring)); + + // join alliance and reserve funds + assert_eq!(Balances::free_balance(9), 40); + assert_ok!(Alliance::join_alliance(RuntimeOrigin::signed(9))); + assert_eq!(Alliance::deposit_of(9), Some(25)); + assert_eq!(Balances::free_balance(9), 15); + assert!(Alliance::is_member_of(&9, MemberRole::Ally)); + + // fails without root + assert_noop!(Alliance::disband(RuntimeOrigin::signed(1), Default::default()), BadOrigin); + + // bad witness data checks + assert_noop!( + Alliance::disband(RuntimeOrigin::root(), Default::default(),), + Error::::BadWitness + ); + + assert_noop!( + Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(1, 1)), + Error::::BadWitness, + ); + assert_noop!( + Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(2, 0)), + Error::::BadWitness, + ); + + // success call + assert_ok!(Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(2, 1))); + + // assert members disband + assert!(!Alliance::is_member(&1)); + assert!(!Alliance::is_initialized()); + // assert a retiring member from the previous Alliance not removed + assert!(Alliance::is_member_of(&2, MemberRole::Retiring)); + // deposit unreserved + assert_eq!(Balances::free_balance(9), 40); + + System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::AllianceDisbanded { + fellow_members: 2, + ally_members: 1, + unreserved: 1, + })); + + // the Alliance must be set first + assert_noop!( + Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(100, 100)), + Error::::AllianceNotYetInitialized, + ); + }) +} + +#[test] +fn propose_works() { + new_test_ext().execute_with(|| { + let (proposal, proposal_len, hash) = make_remark_proposal(42); + + // only voting member can propose proposal, 4 is ally not have vote rights + assert_noop!( + Alliance::propose( + RuntimeOrigin::signed(4), + 3, + Box::new(proposal.clone()), + proposal_len + ), + Error::::NoVotingRights + ); + + assert_ok!(Alliance::propose( + RuntimeOrigin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + assert_eq!(*AllianceMotion::proposals(), vec![hash]); + assert_eq!(AllianceMotion::proposal_of(&hash), Some(proposal)); + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 3, + }), + topics: vec![], + }] + ); + }); +} + +#[test] +fn vote_works() { + new_test_ext().execute_with(|| { + let (proposal, proposal_len, hash) = make_remark_proposal(42); + assert_ok!(Alliance::propose( + RuntimeOrigin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + assert_ok!(Alliance::vote(RuntimeOrigin::signed(2), hash, 0, true)); + + let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; + assert_eq!( + System::events(), + vec![ + record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 3 + })), + record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Voted { + account: 2, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0, + })), + ] + ); + }); +} + +#[test] +fn close_works() { + new_test_ext().execute_with(|| { + let (proposal, proposal_len, hash) = make_remark_proposal(42); + let proposal_weight = proposal.get_dispatch_info().weight; + assert_ok!(Alliance::propose( + RuntimeOrigin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + assert_ok!(Alliance::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Alliance::vote(RuntimeOrigin::signed(2), hash, 0, true)); + assert_ok!(Alliance::vote(RuntimeOrigin::signed(3), hash, 0, true)); + assert_ok!(Alliance::close( + RuntimeOrigin::signed(1), + hash, + 0, + proposal_weight, + proposal_len + )); + + let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; + assert_eq!( + System::events(), + vec![ + record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 3 + })), + record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0, + })), + record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Voted { + account: 2, + proposal_hash: hash, + voted: true, + yes: 2, + no: 0, + })), + record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Voted { + account: 3, + proposal_hash: hash, + voted: true, + yes: 3, + no: 0, + })), + record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Closed { + proposal_hash: hash, + yes: 3, + no: 0, + })), + record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Approved { + proposal_hash: hash + })), + record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Executed { + proposal_hash: hash, + result: Err(DispatchError::BadOrigin), + })) + ] + ); + }); +} + +#[test] +fn set_rule_works() { + new_test_ext().execute_with(|| { + let cid = test_cid(); + assert_ok!(Alliance::set_rule(RuntimeOrigin::signed(1), cid.clone())); + assert_eq!(Alliance::rule(), Some(cid.clone())); + + System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::NewRuleSet { + rule: cid, + })); + }); +} + +#[test] +fn announce_works() { + new_test_ext().execute_with(|| { + let cid = test_cid(); + + assert_noop!(Alliance::announce(RuntimeOrigin::signed(2), cid.clone()), BadOrigin); + + assert_ok!(Alliance::announce(RuntimeOrigin::signed(3), cid.clone())); + assert_eq!(Alliance::announcements(), vec![cid.clone()]); + + System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::Announced { + announcement: cid, + })); + }); +} + +#[test] +fn remove_announcement_works() { + new_test_ext().execute_with(|| { + let cid = test_cid(); + assert_ok!(Alliance::announce(RuntimeOrigin::signed(3), cid.clone())); + assert_eq!(Alliance::announcements(), vec![cid.clone()]); + System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::Announced { + announcement: cid.clone(), + })); + + System::set_block_number(2); + + assert_ok!(Alliance::remove_announcement(RuntimeOrigin::signed(3), cid.clone())); + assert_eq!(Alliance::announcements(), vec![]); + System::assert_last_event(mock::RuntimeEvent::Alliance( + crate::Event::AnnouncementRemoved { announcement: cid }, + )); + }); +} + +#[test] +fn join_alliance_works() { + new_test_ext().execute_with(|| { + // check already member + assert_noop!( + Alliance::join_alliance(RuntimeOrigin::signed(1)), + Error::::AlreadyMember + ); + + // check already listed as unscrupulous + assert_ok!(Alliance::add_unscrupulous_items( + RuntimeOrigin::signed(3), + vec![UnscrupulousItem::AccountId(4)] + )); + assert_noop!( + Alliance::join_alliance(RuntimeOrigin::signed(4)), + Error::::AccountNonGrata + ); + assert_ok!(Alliance::remove_unscrupulous_items( + RuntimeOrigin::signed(3), + vec![UnscrupulousItem::AccountId(4)] + )); + + // check deposit funds + assert_noop!( + Alliance::join_alliance(RuntimeOrigin::signed(5)), + Error::::InsufficientFunds + ); + + // success to submit + assert_ok!(Alliance::join_alliance(RuntimeOrigin::signed(4))); + assert_eq!(Alliance::deposit_of(4), Some(25)); + assert_eq!(Alliance::members(MemberRole::Ally), vec![4]); + + // check already member + assert_noop!( + Alliance::join_alliance(RuntimeOrigin::signed(4)), + Error::::AlreadyMember + ); + + // check missing identity judgement + #[cfg(not(feature = "runtime-benchmarks"))] + assert_noop!( + Alliance::join_alliance(RuntimeOrigin::signed(6)), + Error::::WithoutGoodIdentityJudgement + ); + // check missing identity info + #[cfg(not(feature = "runtime-benchmarks"))] + assert_noop!( + Alliance::join_alliance(RuntimeOrigin::signed(7)), + Error::::WithoutIdentityDisplayAndWebsite + ); + }); +} + +#[test] +fn nominate_ally_works() { + new_test_ext().execute_with(|| { + // check already member + assert_noop!( + Alliance::nominate_ally(RuntimeOrigin::signed(1), 2), + Error::::AlreadyMember + ); + + // only voting members (Fellows) have nominate right + assert_noop!( + Alliance::nominate_ally(RuntimeOrigin::signed(5), 4), + Error::::NoVotingRights + ); + + // check already listed as unscrupulous + assert_ok!(Alliance::add_unscrupulous_items( + RuntimeOrigin::signed(3), + vec![UnscrupulousItem::AccountId(4)] + )); + assert_noop!( + Alliance::nominate_ally(RuntimeOrigin::signed(1), 4), + Error::::AccountNonGrata + ); + assert_ok!(Alliance::remove_unscrupulous_items( + RuntimeOrigin::signed(3), + vec![UnscrupulousItem::AccountId(4)] + )); + + // success to nominate + assert_ok!(Alliance::nominate_ally(RuntimeOrigin::signed(1), 4)); + assert_eq!(Alliance::deposit_of(4), None); + assert_eq!(Alliance::members(MemberRole::Ally), vec![4]); + + // check already member + assert_noop!( + Alliance::nominate_ally(RuntimeOrigin::signed(1), 4), + Error::::AlreadyMember + ); + + // check missing identity judgement + #[cfg(not(feature = "runtime-benchmarks"))] + assert_noop!( + Alliance::join_alliance(RuntimeOrigin::signed(6)), + Error::::WithoutGoodIdentityJudgement + ); + // check missing identity info + #[cfg(not(feature = "runtime-benchmarks"))] + assert_noop!( + Alliance::join_alliance(RuntimeOrigin::signed(7)), + Error::::WithoutIdentityDisplayAndWebsite + ); + }); +} + +#[test] +fn elevate_ally_works() { + new_test_ext().execute_with(|| { + assert_noop!( + Alliance::elevate_ally(RuntimeOrigin::signed(2), 4), + Error::::NotAlly + ); + + assert_ok!(Alliance::join_alliance(RuntimeOrigin::signed(4))); + assert_eq!(Alliance::members(MemberRole::Ally), vec![4]); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); + + assert_ok!(Alliance::elevate_ally(RuntimeOrigin::signed(2), 4)); + assert_eq!(Alliance::members(MemberRole::Ally), Vec::::new()); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3, 4]); + }); +} + +#[test] +fn give_retirement_notice_work() { + new_test_ext().execute_with(|| { + assert_noop!( + Alliance::give_retirement_notice(RuntimeOrigin::signed(4)), + Error::::NotMember + ); + + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); + assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(3))); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2]); + assert_eq!(Alliance::members(MemberRole::Retiring), vec![3]); + System::assert_last_event(mock::RuntimeEvent::Alliance( + crate::Event::MemberRetirementPeriodStarted { member: (3) }, + )); + + assert_noop!( + Alliance::give_retirement_notice(RuntimeOrigin::signed(3)), + Error::::AlreadyRetiring + ); + }); +} + +#[test] +fn retire_works() { + new_test_ext().execute_with(|| { + assert_noop!( + Alliance::retire(RuntimeOrigin::signed(2)), + Error::::RetirementNoticeNotGiven + ); + + assert_noop!( + Alliance::retire(RuntimeOrigin::signed(4)), + Error::::RetirementNoticeNotGiven + ); + + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); + assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(3))); + assert_noop!( + Alliance::retire(RuntimeOrigin::signed(3)), + Error::::RetirementPeriodNotPassed + ); + System::set_block_number(System::block_number() + RetirementPeriod::get()); + assert_ok!(Alliance::retire(RuntimeOrigin::signed(3))); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2]); + System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::MemberRetired { + member: (3), + unreserved: None, + })); + + // Move time on: + System::set_block_number(System::block_number() + RetirementPeriod::get()); + + assert_powerless(RuntimeOrigin::signed(3), false); + }); +} + +#[test] +fn abdicate_works() { + new_test_ext().execute_with(|| { + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); + assert_ok!(Alliance::abdicate_fellow_status(RuntimeOrigin::signed(3))); + + System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::FellowAbdicated { + fellow: (3), + })); + + assert_powerless(RuntimeOrigin::signed(3), true); + }); +} + +#[test] +fn kick_member_works() { + new_test_ext().execute_with(|| { + assert_noop!(Alliance::kick_member(RuntimeOrigin::signed(4), 4), BadOrigin); + + assert_noop!( + Alliance::kick_member(RuntimeOrigin::signed(2), 4), + Error::::NotMember + ); + + >::insert(2, 25); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); + assert_ok!(Alliance::kick_member(RuntimeOrigin::signed(2), 2)); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 3]); + assert_eq!(>::get(2), None); + System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::MemberKicked { + member: (2), + slashed: Some(25), + })); + }); +} + +#[test] +fn add_unscrupulous_items_works() { + new_test_ext().execute_with(|| { + assert_noop!(Alliance::add_unscrupulous_items(RuntimeOrigin::signed(2), vec![]), BadOrigin); + + assert_ok!(Alliance::add_unscrupulous_items( + RuntimeOrigin::signed(3), + vec![ + UnscrupulousItem::AccountId(3), + UnscrupulousItem::Website("abc".as_bytes().to_vec().try_into().unwrap()) + ] + )); + assert_eq!(Alliance::unscrupulous_accounts().into_inner(), vec![3]); + assert_eq!(Alliance::unscrupulous_websites().into_inner(), vec!["abc".as_bytes().to_vec()]); + + assert_noop!( + Alliance::add_unscrupulous_items( + RuntimeOrigin::signed(3), + vec![UnscrupulousItem::AccountId(3)] + ), + Error::::AlreadyUnscrupulous + ); + }); +} + +#[test] +fn remove_unscrupulous_items_works() { + new_test_ext().execute_with(|| { + assert_noop!( + Alliance::remove_unscrupulous_items(RuntimeOrigin::signed(2), vec![]), + BadOrigin + ); + + assert_noop!( + Alliance::remove_unscrupulous_items( + RuntimeOrigin::signed(3), + vec![UnscrupulousItem::AccountId(3)] + ), + Error::::NotListedAsUnscrupulous + ); + + assert_ok!(Alliance::add_unscrupulous_items( + RuntimeOrigin::signed(3), + vec![UnscrupulousItem::AccountId(3)] + )); + assert_eq!(Alliance::unscrupulous_accounts(), vec![3]); + assert_ok!(Alliance::remove_unscrupulous_items( + RuntimeOrigin::signed(3), + vec![UnscrupulousItem::AccountId(3)] + )); + assert_eq!(Alliance::unscrupulous_accounts(), Vec::::new()); + }); +} diff --git a/frame/alliance/src/types.rs b/frame/alliance/src/types.rs new file mode 100644 index 0000000000000..c155b9fa90f95 --- /dev/null +++ b/frame/alliance/src/types.rs @@ -0,0 +1,122 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{traits::ConstU32, BoundedVec}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; +use sp_std::{convert::TryInto, prelude::*}; + +/// A Multihash instance that only supports the basic functionality and no hashing. +#[derive( + Clone, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen, +)] +pub struct Multihash { + /// The code of the Multihash. + pub code: u64, + /// The digest. + pub digest: BoundedVec>, // 4 byte dig size + 64 bytes hash digest +} + +impl Multihash { + /// Returns the size of the digest. + pub fn size(&self) -> usize { + self.digest.len() + } +} + +/// The version of the CID. +#[derive( + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + RuntimeDebug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, +)] +pub enum Version { + /// CID version 0. + V0, + /// CID version 1. + V1, +} + +/// Representation of a CID. +/// +/// The generic is about the allocated size of the multihash. +#[derive( + Clone, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen, +)] +pub struct Cid { + /// The version of CID. + pub version: Version, + /// The codec of CID. + pub codec: u64, + /// The multihash of CID. + pub hash: Multihash, +} + +impl Cid { + /// Creates a new CIDv0. + pub fn new_v0(sha2_256_digest: impl Into>) -> Self { + /// DAG-PB multicodec code + const DAG_PB: u64 = 0x70; + /// The SHA_256 multicodec code + const SHA2_256: u64 = 0x12; + + let digest = sha2_256_digest.into(); + assert_eq!(digest.len(), 32); + + Self { + version: Version::V0, + codec: DAG_PB, + hash: Multihash { code: SHA2_256, digest: digest.try_into().expect("msg") }, + } + } +} + +/// Witness data for the `disband` call. +#[derive( + Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo, Default, +)] +pub struct DisbandWitness { + /// Total number of fellow members in the current Alliance. + #[codec(compact)] + pub(super) fellow_members: u32, + /// Total number of ally members in the current Alliance. + #[codec(compact)] + pub(super) ally_members: u32, +} + +#[cfg(test)] +impl DisbandWitness { + // Creates new DisbandWitness. + pub(super) fn new(fellow_members: u32, ally_members: u32) -> Self { + Self { fellow_members, ally_members } + } +} + +impl DisbandWitness { + pub(super) fn is_zero(self) -> bool { + self == Self::default() + } +} diff --git a/frame/alliance/src/weights.rs b/frame/alliance/src/weights.rs new file mode 100644 index 0000000000000..58d28b28f2cf3 --- /dev/null +++ b/frame/alliance/src/weights.rs @@ -0,0 +1,609 @@ +//! Autogenerated weights for pallet_alliance +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `cob`, CPU: `` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet-alliance +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/alliance/src/._weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_alliance. +pub trait WeightInfo { + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight; + fn vote(m: u32, ) -> Weight; + fn veto(p: u32, ) -> Weight; + fn close_early_disapproved(m: u32, p: u32, ) -> Weight; + fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight; + fn close_disapproved(m: u32, p: u32, ) -> Weight; + fn close_approved(b: u32, m: u32, p: u32, ) -> Weight; + fn init_members(m: u32, z: u32, ) -> Weight; + fn disband(x: u32, y: u32, z: u32, ) -> Weight; + fn set_rule() -> Weight; + fn announce() -> Weight; + fn remove_announcement() -> Weight; + fn join_alliance() -> Weight; + fn nominate_ally() -> Weight; + fn elevate_ally() -> Weight; + fn give_retirement_notice() -> Weight; + fn retire() -> Weight; + fn kick_member() -> Weight; + fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight; + fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight; + fn abdicate_fellow_status() -> Weight; +} + +/// Weights for pallet_alliance using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion ProposalCount (r:1 w:1) + // Storage: AllianceMotion Voting (r:0 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[2, 100]`. + /// The range of component `p` is `[1, 100]`. + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(24_357_172 as u64) + // Standard Error: 428 + .saturating_add(Weight::from_ref_time(233 as u64).saturating_mul(b as u64)) + // Standard Error: 4_474 + .saturating_add(Weight::from_ref_time(48_024 as u64).saturating_mul(m as u64)) + // Standard Error: 4_418 + .saturating_add(Weight::from_ref_time(97_604 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + /// The range of component `m` is `[5, 100]`. + fn vote(m: u32, ) -> Weight { + // Minimum execution time: 24_000 nanoseconds. + Weight::from_ref_time(26_617_201 as u64) + // Standard Error: 4_280 + .saturating_add(Weight::from_ref_time(43_152 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion Voting (r:0 w:1) + /// The range of component `p` is `[1, 100]`. + fn veto(p: u32, ) -> Weight { + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(26_045_752 as u64) + // Standard Error: 2_154 + .saturating_add(Weight::from_ref_time(61_220 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion ProposalOf (r:0 w:1) + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. + fn close_early_disapproved(m: u32, p: u32, ) -> Weight { + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(25_697_866 as u64) + // Standard Error: 3_827 + .saturating_add(Weight::from_ref_time(48_360 as u64).saturating_mul(m as u64)) + // Standard Error: 3_731 + .saturating_add(Weight::from_ref_time(116_922 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Proposals (r:1 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. + fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 34_000 nanoseconds. + Weight::from_ref_time(30_725_464 as u64) + // Standard Error: 370 + .saturating_add(Weight::from_ref_time(2_367 as u64).saturating_mul(b as u64)) + // Standard Error: 3_920 + .saturating_add(Weight::from_ref_time(40_710 as u64).saturating_mul(m as u64)) + // Standard Error: 3_822 + .saturating_add(Weight::from_ref_time(111_866 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Prime (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion ProposalOf (r:0 w:1) + /// The range of component `m` is `[2, 100]`. + /// The range of component `p` is `[1, 100]`. + fn close_disapproved(m: u32, p: u32, ) -> Weight { + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(29_444_599 as u64) + // Standard Error: 4_043 + .saturating_add(Weight::from_ref_time(48_928 as u64).saturating_mul(m as u64)) + // Standard Error: 3_994 + .saturating_add(Weight::from_ref_time(76_527 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Prime (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion ProposalOf (r:0 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[5, 100]`. + /// The range of component `p` is `[1, 100]`. + fn close_approved(_b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(32_315_075 as u64) + // Standard Error: 4_502 + .saturating_add(Weight::from_ref_time(43_205 as u64).saturating_mul(m as u64)) + // Standard Error: 4_340 + .saturating_add(Weight::from_ref_time(101_872 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:2 w:2) + // Storage: AllianceMotion Members (r:1 w:1) + /// The range of component `m` is `[1, 100]`. + /// The range of component `z` is `[0, 100]`. + fn init_members(m: u32, z: u32, ) -> Weight { + // Minimum execution time: 22_000 nanoseconds. + Weight::from_ref_time(13_523_882 as u64) + // Standard Error: 1_823 + .saturating_add(Weight::from_ref_time(91_625 as u64).saturating_mul(m as u64)) + // Standard Error: 1_802 + .saturating_add(Weight::from_ref_time(98_184 as u64).saturating_mul(z as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:2 w:2) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: Alliance DepositOf (r:101 w:50) + // Storage: System Account (r:50 w:50) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + /// The range of component `x` is `[1, 100]`. + /// The range of component `y` is `[0, 100]`. + /// The range of component `z` is `[0, 50]`. + fn disband(x: u32, y: u32, z: u32, ) -> Weight { + // Minimum execution time: 145_000 nanoseconds. + Weight::from_ref_time(147_000_000 as u64) + // Standard Error: 13_290 + .saturating_add(Weight::from_ref_time(304_371 as u64).saturating_mul(x as u64)) + // Standard Error: 13_226 + .saturating_add(Weight::from_ref_time(330_798 as u64).saturating_mul(y as u64)) + // Standard Error: 26_428 + .saturating_add(Weight::from_ref_time(7_207_748 as u64).saturating_mul(z as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(y as u64))) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(z as u64))) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(z as u64))) + } + // Storage: Alliance Rule (r:0 w:1) + fn set_rule() -> Weight { + // Minimum execution time: 11_000 nanoseconds. + Weight::from_ref_time(12_000_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Alliance Announcements (r:1 w:1) + fn announce() -> Weight { + // Minimum execution time: 13_000 nanoseconds. + Weight::from_ref_time(14_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Alliance Announcements (r:1 w:1) + fn remove_announcement() -> Weight { + // Minimum execution time: 14_000 nanoseconds. + Weight::from_ref_time(14_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Alliance Members (r:3 w:1) + // Storage: Alliance UnscrupulousAccounts (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Alliance DepositOf (r:0 w:1) + fn join_alliance() -> Weight { + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(41_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:3 w:1) + // Storage: Alliance UnscrupulousAccounts (r:1 w:0) + fn nominate_ally() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Alliance Members (r:2 w:2) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn elevate_ally() -> Weight { + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(24_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: Alliance Members (r:4 w:2) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + // Storage: Alliance RetiringMembers (r:0 w:1) + fn give_retirement_notice() -> Weight { + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(32_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: Alliance RetiringMembers (r:1 w:1) + // Storage: Alliance Members (r:1 w:1) + // Storage: Alliance DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn retire() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: Alliance Members (r:3 w:1) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: Alliance DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn kick_member() -> Weight { + // Minimum execution time: 45_000 nanoseconds. + Weight::from_ref_time(47_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: Alliance UnscrupulousAccounts (r:1 w:1) + // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + /// The range of component `n` is `[0, 100]`. + /// The range of component `l` is `[0, 255]`. + fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight { + // Minimum execution time: 9_000 nanoseconds. + Weight::from_ref_time(9_512_816 as u64) + // Standard Error: 2_976 + .saturating_add(Weight::from_ref_time(560_175 as u64).saturating_mul(n as u64)) + // Standard Error: 1_169 + .saturating_add(Weight::from_ref_time(24_530 as u64).saturating_mul(l as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Alliance UnscrupulousAccounts (r:1 w:1) + // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + /// The range of component `n` is `[0, 100]`. + /// The range of component `l` is `[0, 255]`. + fn remove_unscrupulous_items(n: u32, _l: u32, ) -> Weight { + // Minimum execution time: 10_000 nanoseconds. + Weight::from_ref_time(10_000_000 as u64) + // Standard Error: 94_049 + .saturating_add(Weight::from_ref_time(10_887_604 as u64).saturating_mul(n as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Alliance Members (r:3 w:2) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn abdicate_fellow_status() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion ProposalCount (r:1 w:1) + // Storage: AllianceMotion Voting (r:0 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[2, 100]`. + /// The range of component `p` is `[1, 100]`. + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(24_357_172 as u64) + // Standard Error: 428 + .saturating_add(Weight::from_ref_time(233 as u64).saturating_mul(b as u64)) + // Standard Error: 4_474 + .saturating_add(Weight::from_ref_time(48_024 as u64).saturating_mul(m as u64)) + // Standard Error: 4_418 + .saturating_add(Weight::from_ref_time(97_604 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + /// The range of component `m` is `[5, 100]`. + fn vote(m: u32, ) -> Weight { + // Minimum execution time: 24_000 nanoseconds. + Weight::from_ref_time(26_617_201 as u64) + // Standard Error: 4_280 + .saturating_add(Weight::from_ref_time(43_152 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion Voting (r:0 w:1) + /// The range of component `p` is `[1, 100]`. + fn veto(p: u32, ) -> Weight { + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(26_045_752 as u64) + // Standard Error: 2_154 + .saturating_add(Weight::from_ref_time(61_220 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion ProposalOf (r:0 w:1) + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. + fn close_early_disapproved(m: u32, p: u32, ) -> Weight { + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(25_697_866 as u64) + // Standard Error: 3_827 + .saturating_add(Weight::from_ref_time(48_360 as u64).saturating_mul(m as u64)) + // Standard Error: 3_731 + .saturating_add(Weight::from_ref_time(116_922 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Proposals (r:1 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. + fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 34_000 nanoseconds. + Weight::from_ref_time(30_725_464 as u64) + // Standard Error: 370 + .saturating_add(Weight::from_ref_time(2_367 as u64).saturating_mul(b as u64)) + // Standard Error: 3_920 + .saturating_add(Weight::from_ref_time(40_710 as u64).saturating_mul(m as u64)) + // Standard Error: 3_822 + .saturating_add(Weight::from_ref_time(111_866 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Prime (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion ProposalOf (r:0 w:1) + /// The range of component `m` is `[2, 100]`. + /// The range of component `p` is `[1, 100]`. + fn close_disapproved(m: u32, p: u32, ) -> Weight { + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(29_444_599 as u64) + // Standard Error: 4_043 + .saturating_add(Weight::from_ref_time(48_928 as u64).saturating_mul(m as u64)) + // Standard Error: 3_994 + .saturating_add(Weight::from_ref_time(76_527 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Prime (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion ProposalOf (r:0 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[5, 100]`. + /// The range of component `p` is `[1, 100]`. + fn close_approved(_b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(32_315_075 as u64) + // Standard Error: 4_502 + .saturating_add(Weight::from_ref_time(43_205 as u64).saturating_mul(m as u64)) + // Standard Error: 4_340 + .saturating_add(Weight::from_ref_time(101_872 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:2 w:2) + // Storage: AllianceMotion Members (r:1 w:1) + /// The range of component `m` is `[1, 100]`. + /// The range of component `z` is `[0, 100]`. + fn init_members(m: u32, z: u32, ) -> Weight { + // Minimum execution time: 22_000 nanoseconds. + Weight::from_ref_time(13_523_882 as u64) + // Standard Error: 1_823 + .saturating_add(Weight::from_ref_time(91_625 as u64).saturating_mul(m as u64)) + // Standard Error: 1_802 + .saturating_add(Weight::from_ref_time(98_184 as u64).saturating_mul(z as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:2 w:2) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: Alliance DepositOf (r:101 w:50) + // Storage: System Account (r:50 w:50) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + /// The range of component `x` is `[1, 100]`. + /// The range of component `y` is `[0, 100]`. + /// The range of component `z` is `[0, 50]`. + fn disband(x: u32, y: u32, z: u32, ) -> Weight { + // Minimum execution time: 145_000 nanoseconds. + Weight::from_ref_time(147_000_000 as u64) + // Standard Error: 13_290 + .saturating_add(Weight::from_ref_time(304_371 as u64).saturating_mul(x as u64)) + // Standard Error: 13_226 + .saturating_add(Weight::from_ref_time(330_798 as u64).saturating_mul(y as u64)) + // Standard Error: 26_428 + .saturating_add(Weight::from_ref_time(7_207_748 as u64).saturating_mul(z as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(y as u64))) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(z as u64))) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(z as u64))) + } + // Storage: Alliance Rule (r:0 w:1) + fn set_rule() -> Weight { + // Minimum execution time: 11_000 nanoseconds. + Weight::from_ref_time(12_000_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Alliance Announcements (r:1 w:1) + fn announce() -> Weight { + // Minimum execution time: 13_000 nanoseconds. + Weight::from_ref_time(14_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Alliance Announcements (r:1 w:1) + fn remove_announcement() -> Weight { + // Minimum execution time: 14_000 nanoseconds. + Weight::from_ref_time(14_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Alliance Members (r:3 w:1) + // Storage: Alliance UnscrupulousAccounts (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Alliance DepositOf (r:0 w:1) + fn join_alliance() -> Weight { + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(41_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Alliance Members (r:3 w:1) + // Storage: Alliance UnscrupulousAccounts (r:1 w:0) + fn nominate_ally() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Alliance Members (r:2 w:2) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn elevate_ally() -> Weight { + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(24_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: Alliance Members (r:4 w:2) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + // Storage: Alliance RetiringMembers (r:0 w:1) + fn give_retirement_notice() -> Weight { + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(32_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: Alliance RetiringMembers (r:1 w:1) + // Storage: Alliance Members (r:1 w:1) + // Storage: Alliance DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn retire() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: Alliance Members (r:3 w:1) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: Alliance DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn kick_member() -> Weight { + // Minimum execution time: 45_000 nanoseconds. + Weight::from_ref_time(47_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: Alliance UnscrupulousAccounts (r:1 w:1) + // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + /// The range of component `n` is `[0, 100]`. + /// The range of component `l` is `[0, 255]`. + fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight { + // Minimum execution time: 9_000 nanoseconds. + Weight::from_ref_time(9_512_816 as u64) + // Standard Error: 2_976 + .saturating_add(Weight::from_ref_time(560_175 as u64).saturating_mul(n as u64)) + // Standard Error: 1_169 + .saturating_add(Weight::from_ref_time(24_530 as u64).saturating_mul(l as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Alliance UnscrupulousAccounts (r:1 w:1) + // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + /// The range of component `n` is `[0, 100]`. + /// The range of component `l` is `[0, 255]`. + fn remove_unscrupulous_items(n: u32, _l: u32, ) -> Weight { + // Minimum execution time: 10_000 nanoseconds. + Weight::from_ref_time(10_000_000 as u64) + // Standard Error: 94_049 + .saturating_add(Weight::from_ref_time(10_887_604 as u64).saturating_mul(n as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Alliance Members (r:3 w:2) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn abdicate_fellow_status() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } +} diff --git a/frame/assets/Cargo.toml b/frame/assets/Cargo.toml index 05e7912dd07c6..715149b20c042 100644 --- a/frame/assets/Cargo.toml +++ b/frame/assets/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-assets" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME asset management pallet" readme = "README.md" @@ -13,11 +13,11 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } # Needed for various traits. In our case, `OnFinalize`. -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } # Needed for type-safe access to storage DB. frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } # `system` module provides us with all sorts of useful stuff and macros depend on it being around. @@ -25,9 +25,9 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-std = { version = "4.0.0-dev", path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-std = { version = "5.0.0", path = "../../primitives/std" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] @@ -39,10 +39,10 @@ std = [ "sp-runtime/std", "frame-support/std", "frame-system/std", - "frame-benchmarking/std", + "frame-benchmarking?/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/assets/README.md b/frame/assets/README.md index a99b60fa33d56..aae5244953e50 100644 --- a/frame/assets/README.md +++ b/frame/assets/README.md @@ -69,35 +69,43 @@ Import the Assets module and types and derive your runtime's configuration trait ```rust use pallet_assets as assets; -use frame_support::{decl_module, dispatch, ensure}; -use frame_system::ensure_signed; use sp_runtime::ArithmeticError; -pub trait Config: assets::Config { } +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - pub fn issue_token_airdrop(origin) -> dispatch::DispatchResult { - let sender = ensure_signed(origin).map_err(|e| e.as_str())?; + #[pallet::pallet] + pub struct Pallet(_); - const ACCOUNT_ALICE: u64 = 1; - const ACCOUNT_BOB: u64 = 2; - const COUNT_AIRDROP_RECIPIENTS: u64 = 2; - const TOKENS_FIXED_SUPPLY: u64 = 100; + #[pallet::config] + pub trait Config: frame_system::Config + assets::Config {} - ensure!(!COUNT_AIRDROP_RECIPIENTS.is_zero(), ArithmeticError::DivisionByZero); + #[pallet::call] + impl Pallet { + pub fn issue_token_airdrop(origin: OriginFor) -> DispatchResult { + let sender = ensure_signed(origin)?; - let asset_id = Self::next_asset_id(); + const ACCOUNT_ALICE: u64 = 1; + const ACCOUNT_BOB: u64 = 2; + const COUNT_AIRDROP_RECIPIENTS: u64 = 2; + const TOKENS_FIXED_SUPPLY: u64 = 100; - >::mutate(|asset_id| *asset_id += 1); - >::insert((asset_id, &ACCOUNT_ALICE), TOKENS_FIXED_SUPPLY / COUNT_AIRDROP_RECIPIENTS); - >::insert((asset_id, &ACCOUNT_BOB), TOKENS_FIXED_SUPPLY / COUNT_AIRDROP_RECIPIENTS); - >::insert(asset_id, TOKENS_FIXED_SUPPLY); + ensure!(!COUNT_AIRDROP_RECIPIENTS.is_zero(), ArithmeticError::DivisionByZero); - Self::deposit_event(RawEvent::Issued(asset_id, sender, TOKENS_FIXED_SUPPLY)); - Ok(()) - } - } + let asset_id = Self::next_asset_id(); + + >::mutate(|asset_id| *asset_id += 1); + >::insert((asset_id, &ACCOUNT_ALICE), TOKENS_FIXED_SUPPLY / COUNT_AIRDROP_RECIPIENTS); + >::insert((asset_id, &ACCOUNT_BOB), TOKENS_FIXED_SUPPLY / COUNT_AIRDROP_RECIPIENTS); + >::insert(asset_id, TOKENS_FIXED_SUPPLY); + + Self::deposit_event(Event::Issued(asset_id, sender, TOKENS_FIXED_SUPPLY)); + Ok(()) + } + } } ``` diff --git a/frame/assets/src/benchmarking.rs b/frame/assets/src/benchmarking.rs index 43eadffbe8497..ede5b4e77fac6 100644 --- a/frame/assets/src/benchmarking.rs +++ b/frame/assets/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,7 @@ use super::*; use frame_benchmarking::{ - account, benchmarks_instance_pallet, impl_benchmark_test_suite, whitelist_account, - whitelisted_caller, + account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, }; use frame_support::{ dispatch::UnfilteredDispatchable, @@ -36,69 +35,57 @@ use crate::Pallet as Assets; const SEED: u32 = 0; +fn default_asset_id, I: 'static>() -> T::AssetIdParameter { + T::BenchmarkHelper::create_asset_id_parameter(0) +} + fn create_default_asset, I: 'static>( is_sufficient: bool, -) -> (T::AccountId, ::Source) { +) -> (T::AssetIdParameter, T::AccountId, AccountIdLookupOf) { + let asset_id = default_asset_id::(); let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); let root = SystemOrigin::Root.into(); assert!(Assets::::force_create( root, - Default::default(), + asset_id, caller_lookup.clone(), is_sufficient, 1u32.into(), ) .is_ok()); - (caller, caller_lookup) + (asset_id, caller, caller_lookup) } fn create_default_minted_asset, I: 'static>( is_sufficient: bool, amount: T::Balance, -) -> (T::AccountId, ::Source) { - let (caller, caller_lookup) = create_default_asset::(is_sufficient); +) -> (T::AssetIdParameter, T::AccountId, AccountIdLookupOf) { + let (asset_id, caller, caller_lookup) = create_default_asset::(is_sufficient); if !is_sufficient { T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); } assert!(Assets::::mint( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + asset_id, caller_lookup.clone(), amount, ) .is_ok()); - (caller, caller_lookup) + (asset_id, caller, caller_lookup) } fn swap_is_sufficient, I: 'static>(s: &mut bool) { - Asset::::mutate(&T::AssetId::default(), |maybe_a| { + let asset_id = default_asset_id::(); + Asset::::mutate(&asset_id.into(), |maybe_a| { if let Some(ref mut a) = maybe_a { sp_std::mem::swap(s, &mut a.is_sufficient) } }); } -fn add_consumers, I: 'static>(minter: T::AccountId, n: u32) { - let origin = SystemOrigin::Signed(minter); - let mut s = false; - swap_is_sufficient::(&mut s); - for i in 0..n { - let target = account("consumer", i, SEED); - T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); - let target_lookup = T::Lookup::unlookup(target); - assert!(Assets::::mint( - origin.clone().into(), - Default::default(), - target_lookup, - 100u32.into() - ) - .is_ok()); - } - swap_is_sufficient::(&mut s); -} - fn add_sufficients, I: 'static>(minter: T::AccountId, n: u32) { + let asset_id = default_asset_id::(); let origin = SystemOrigin::Signed(minter); let mut s = true; swap_is_sufficient::(&mut s); @@ -107,7 +94,7 @@ fn add_sufficients, I: 'static>(minter: T::AccountId, n: u32) { let target_lookup = T::Lookup::unlookup(target); assert!(Assets::::mint( origin.clone().into(), - Default::default(), + asset_id, target_lookup, 100u32.into() ) @@ -117,23 +104,19 @@ fn add_sufficients, I: 'static>(minter: T::AccountId, n: u32) { } fn add_approvals, I: 'static>(minter: T::AccountId, n: u32) { + let asset_id = default_asset_id::(); T::Currency::deposit_creating(&minter, T::ApprovalDeposit::get() * n.into()); let minter_lookup = T::Lookup::unlookup(minter.clone()); let origin = SystemOrigin::Signed(minter); - Assets::::mint( - origin.clone().into(), - Default::default(), - minter_lookup, - (100 * (n + 1)).into(), - ) - .unwrap(); + Assets::::mint(origin.clone().into(), asset_id, minter_lookup, (100 * (n + 1)).into()) + .unwrap(); for i in 0..n { let target = account("approval", i, SEED); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let target_lookup = T::Lookup::unlookup(target); Assets::::approve_transfer( origin.clone().into(), - Default::default(), + asset_id, target_lookup, 100u32.into(), ) @@ -141,155 +124,206 @@ fn add_approvals, I: 'static>(minter: T::AccountId, n: u32) { } } -fn assert_last_event, I: 'static>(generic_event: >::Event) { +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } -fn assert_event, I: 'static>(generic_event: >::Event) { +fn assert_event, I: 'static>(generic_event: >::RuntimeEvent) { frame_system::Pallet::::assert_has_event(generic_event.into()); } benchmarks_instance_pallet! { create { - let caller: T::AccountId = whitelisted_caller(); + let asset_id = default_asset_id::(); + let origin = T::CreateOrigin::successful_origin(&asset_id.into()); + let caller = T::CreateOrigin::ensure_origin(origin, &asset_id.into()).unwrap(); let caller_lookup = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, 1u32.into()) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, 1u32.into()) verify { - assert_last_event::(Event::Created(Default::default(), caller.clone(), caller).into()); + assert_last_event::(Event::Created { asset_id: asset_id.into(), creator: caller.clone(), owner: caller }.into()); } force_create { + let asset_id = default_asset_id::(); let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, Default::default(), caller_lookup, true, 1u32.into()) + }: _(SystemOrigin::Root, asset_id, caller_lookup, true, 1u32.into()) verify { - assert_last_event::(Event::ForceCreated(Default::default(), caller).into()); + assert_last_event::(Event::ForceCreated { asset_id: asset_id.into(), owner: caller }.into()); } - destroy { - let c in 0 .. 5_000; - let s in 0 .. 5_000; - let a in 0 .. 5_00; - let (caller, _) = create_default_asset::(true); - add_consumers::(caller.clone(), c); - add_sufficients::(caller.clone(), s); + start_destroy { + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + Assets::::freeze_asset( + SystemOrigin::Signed(caller.clone()).into(), + asset_id, + )?; + }:_(SystemOrigin::Signed(caller), asset_id) + verify { + assert_last_event::(Event::DestructionStarted { asset_id: asset_id.into() }.into()); + } + + destroy_accounts { + let c in 0 .. T::RemoveItemsLimit::get(); + let (asset_id, caller, _) = create_default_asset::(true); + add_sufficients::(caller.clone(), c); + Assets::::freeze_asset( + SystemOrigin::Signed(caller.clone()).into(), + asset_id, + )?; + Assets::::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id)?; + }:_(SystemOrigin::Signed(caller), asset_id) + verify { + assert_last_event::(Event::AccountsDestroyed { + asset_id: asset_id.into(), + accounts_destroyed: c, + accounts_remaining: 0, + }.into()); + } + + destroy_approvals { + let a in 0 .. T::RemoveItemsLimit::get(); + let (asset_id, caller, _) = create_default_minted_asset::(true, 100u32.into()); add_approvals::(caller.clone(), a); - let witness = Asset::::get(T::AssetId::default()).unwrap().destroy_witness(); - }: _(SystemOrigin::Signed(caller), Default::default(), witness) + Assets::::freeze_asset( + SystemOrigin::Signed(caller.clone()).into(), + asset_id, + )?; + Assets::::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id)?; + }:_(SystemOrigin::Signed(caller), asset_id) verify { - assert_last_event::(Event::Destroyed(Default::default()).into()); + assert_last_event::(Event::ApprovalsDestroyed { + asset_id: asset_id.into(), + approvals_destroyed: a, + approvals_remaining: 0, + }.into()); + } + + finish_destroy { + let (asset_id, caller, caller_lookup) = create_default_asset::(true); + Assets::::freeze_asset( + SystemOrigin::Signed(caller.clone()).into(), + asset_id, + )?; + Assets::::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id)?; + }:_(SystemOrigin::Signed(caller), asset_id) + verify { + assert_last_event::(Event::Destroyed { + asset_id: asset_id.into(), + }.into() + ); } mint { - let (caller, caller_lookup) = create_default_asset::(true); + let (asset_id, caller, caller_lookup) = create_default_asset::(true); let amount = T::Balance::from(100u32); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, amount) verify { - assert_last_event::(Event::Issued(Default::default(), caller, amount).into()); + assert_last_event::(Event::Issued { asset_id: asset_id.into(), owner: caller, total_supply: amount }.into()); } burn { let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, amount); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, amount); + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, amount) verify { - assert_last_event::(Event::Burned(Default::default(), caller, amount).into()); + assert_last_event::(Event::Burned { asset_id: asset_id.into(), owner: caller, balance: amount }.into()); } transfer { let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, amount); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, amount); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), target_lookup, amount) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, target_lookup, amount) verify { - assert_last_event::(Event::Transferred(Default::default(), caller, target, amount).into()); + assert_last_event::(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into()); } transfer_keep_alive { let mint_amount = T::Balance::from(200u32); let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, mint_amount); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, mint_amount); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), target_lookup, amount) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, target_lookup, amount) verify { assert!(frame_system::Pallet::::account_exists(&caller)); - assert_last_event::(Event::Transferred(Default::default(), caller, target, amount).into()); + assert_last_event::(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into()); } force_transfer { let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, amount); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, amount); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, target_lookup, amount) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, target_lookup, amount) verify { assert_last_event::( - Event::Transferred(Default::default(), caller, target, amount).into() + Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into() ); } freeze { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup) verify { - assert_last_event::(Event::Frozen(Default::default(), caller).into()); + assert_last_event::(Event::Frozen { asset_id: asset_id.into(), who: caller }.into()); } thaw { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); Assets::::freeze( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + asset_id, caller_lookup.clone(), )?; - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup) verify { - assert_last_event::(Event::Thawed(Default::default(), caller).into()); + assert_last_event::(Event::Thawed { asset_id: asset_id.into(), who: caller }.into()); } freeze_asset { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default()) + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + }: _(SystemOrigin::Signed(caller.clone()), asset_id) verify { - assert_last_event::(Event::AssetFrozen(Default::default()).into()); + assert_last_event::(Event::AssetFrozen { asset_id: asset_id.into() }.into()); } thaw_asset { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); Assets::::freeze_asset( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + asset_id, )?; - }: _(SystemOrigin::Signed(caller.clone()), Default::default()) + }: _(SystemOrigin::Signed(caller.clone()), asset_id) verify { - assert_last_event::(Event::AssetThawed(Default::default()).into()); + assert_last_event::(Event::AssetThawed { asset_id: asset_id.into() }.into()); } transfer_ownership { - let (caller, _) = create_default_asset::(true); + let (asset_id, caller, _) = create_default_asset::(true); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller), Default::default(), target_lookup) + }: _(SystemOrigin::Signed(caller), asset_id, target_lookup) verify { - assert_last_event::(Event::OwnerChanged(Default::default(), target).into()); + assert_last_event::(Event::OwnerChanged { asset_id: asset_id.into(), owner: target }.into()); } set_team { - let (caller, _) = create_default_asset::(true); + let (asset_id, caller, _) = create_default_asset::(true); let target0 = T::Lookup::unlookup(account("target", 0, SEED)); let target1 = T::Lookup::unlookup(account("target", 1, SEED)); let target2 = T::Lookup::unlookup(account("target", 2, SEED)); - }: _(SystemOrigin::Signed(caller), Default::default(), target0.clone(), target1.clone(), target2.clone()) + }: _(SystemOrigin::Signed(caller), asset_id, target0, target1, target2) verify { - assert_last_event::(Event::TeamChanged( - Default::default(), - account("target", 0, SEED), - account("target", 1, SEED), - account("target", 2, SEED), - ).into()); + assert_last_event::(Event::TeamChanged { + asset_id: asset_id.into(), + issuer: account("target", 0, SEED), + admin: account("target", 1, SEED), + freezer: account("target", 2, SEED), + }.into()); } set_metadata { @@ -300,23 +334,22 @@ benchmarks_instance_pallet! { let symbol = vec![0u8; s as usize]; let decimals = 12; - let (caller, _) = create_default_asset::(true); + let (asset_id, caller, _) = create_default_asset::(true); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(caller), Default::default(), name.clone(), symbol.clone(), decimals) + }: _(SystemOrigin::Signed(caller), asset_id, name.clone(), symbol.clone(), decimals) verify { - let id = Default::default(); - assert_last_event::(Event::MetadataSet(id, name, symbol, decimals, false).into()); + assert_last_event::(Event::MetadataSet { asset_id: asset_id.into(), name, symbol, decimals, is_frozen: false }.into()); } clear_metadata { - let (caller, _) = create_default_asset::(true); + let (asset_id, caller, _) = create_default_asset::(true); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); let dummy = vec![0u8; T::StringLimit::get() as usize]; let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::set_metadata(origin, Default::default(), dummy.clone(), dummy, 12)?; - }: _(SystemOrigin::Signed(caller), Default::default()) + Assets::::set_metadata(origin, asset_id, dummy.clone(), dummy, 12)?; + }: _(SystemOrigin::Signed(caller), asset_id) verify { - assert_last_event::(Event::MetadataCleared(Default::default()).into()); + assert_last_event::(Event::MetadataCleared { asset_id: asset_id.into() }.into()); } force_set_metadata { @@ -327,11 +360,11 @@ benchmarks_instance_pallet! { let symbol = vec![0u8; s as usize]; let decimals = 12; - create_default_asset::(true); + let (asset_id, _, _) = create_default_asset::(true); let origin = T::ForceOrigin::successful_origin(); let call = Call::::force_set_metadata { - id: Default::default(), + id: asset_id, name: name.clone(), symbol: symbol.clone(), decimals, @@ -339,105 +372,100 @@ benchmarks_instance_pallet! { }; }: { call.dispatch_bypass_filter(origin)? } verify { - let id = Default::default(); - assert_last_event::(Event::MetadataSet(id, name, symbol, decimals, false).into()); + assert_last_event::(Event::MetadataSet { asset_id: asset_id.into(), name, symbol, decimals, is_frozen: false }.into()); } force_clear_metadata { - let (caller, _) = create_default_asset::(true); + let (asset_id, caller, _) = create_default_asset::(true); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); let dummy = vec![0u8; T::StringLimit::get() as usize]; - let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::set_metadata(origin, Default::default(), dummy.clone(), dummy, 12)?; + let origin = SystemOrigin::Signed(caller).into(); + Assets::::set_metadata(origin, asset_id, dummy.clone(), dummy, 12)?; let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_clear_metadata { id: Default::default() }; + let call = Call::::force_clear_metadata { id: asset_id }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::MetadataCleared(Default::default()).into()); + assert_last_event::(Event::MetadataCleared { asset_id: asset_id.into() }.into()); } force_asset_status { - let (caller, caller_lookup) = create_default_asset::(true); + let (asset_id, caller, caller_lookup) = create_default_asset::(true); let origin = T::ForceOrigin::successful_origin(); let call = Call::::force_asset_status { - id: Default::default(), + id: asset_id, owner: caller_lookup.clone(), issuer: caller_lookup.clone(), admin: caller_lookup.clone(), - freezer: caller_lookup.clone(), + freezer: caller_lookup, min_balance: 100u32.into(), is_sufficient: true, is_frozen: false, }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::AssetStatusChanged(Default::default()).into()); + assert_last_event::(Event::AssetStatusChanged { asset_id: asset_id.into() }.into()); } approve_transfer { - let (caller, _) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, caller, _) = create_default_minted_asset::(true, 100u32.into()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let id = Default::default(); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let amount = 100u32.into(); - }: _(SystemOrigin::Signed(caller.clone()), id, delegate_lookup, amount) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, delegate_lookup, amount) verify { - assert_last_event::(Event::ApprovedTransfer(id, caller, delegate, amount).into()); + assert_last_event::(Event::ApprovedTransfer { asset_id: asset_id.into(), source: caller, delegate, amount }.into()); } transfer_approved { - let (owner, owner_lookup) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, owner, owner_lookup) = create_default_minted_asset::(true, 100u32.into()); T::Currency::make_free_balance_be(&owner, DepositBalanceOf::::max_value()); - let id = Default::default(); let delegate: T::AccountId = account("delegate", 0, SEED); whitelist_account!(delegate); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let amount = 100u32.into(); let origin = SystemOrigin::Signed(owner.clone()).into(); - Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; + Assets::::approve_transfer(origin, asset_id, delegate_lookup, amount)?; let dest: T::AccountId = account("dest", 0, SEED); let dest_lookup = T::Lookup::unlookup(dest.clone()); - }: _(SystemOrigin::Signed(delegate.clone()), id, owner_lookup, dest_lookup, amount) + }: _(SystemOrigin::Signed(delegate.clone()), asset_id, owner_lookup, dest_lookup, amount) verify { assert!(T::Currency::reserved_balance(&owner).is_zero()); - assert_event::(Event::Transferred(id, owner, dest, amount).into()); + assert_event::(Event::Transferred { asset_id: asset_id.into(), from: owner, to: dest, amount }.into()); } cancel_approval { - let (caller, _) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, caller, _) = create_default_minted_asset::(true, 100u32.into()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let id = Default::default(); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let amount = 100u32.into(); let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; - }: _(SystemOrigin::Signed(caller.clone()), id, delegate_lookup) + Assets::::approve_transfer(origin, asset_id, delegate_lookup.clone(), amount)?; + }: _(SystemOrigin::Signed(caller.clone()), asset_id, delegate_lookup) verify { - assert_last_event::(Event::ApprovalCancelled(id, caller, delegate).into()); + assert_last_event::(Event::ApprovalCancelled { asset_id: asset_id.into(), owner: caller, delegate }.into()); } force_cancel_approval { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let id = Default::default(); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let amount = 100u32.into(); let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; - }: _(SystemOrigin::Signed(caller.clone()), id, caller_lookup, delegate_lookup) + Assets::::approve_transfer(origin, asset_id, delegate_lookup.clone(), amount)?; + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, delegate_lookup) verify { - assert_last_event::(Event::ApprovalCancelled(id, caller, delegate).into()); + assert_last_event::(Event::ApprovalCancelled { asset_id: asset_id.into(), owner: caller, delegate }.into()); } -} -impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test) +} diff --git a/frame/assets/src/extra_mutator.rs b/frame/assets/src/extra_mutator.rs index 8c601b746346c..b72bfa86df5b8 100644 --- a/frame/assets/src/extra_mutator.rs +++ b/frame/assets/src/extra_mutator.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,11 +62,11 @@ impl, I: 'static> ExtraMutator { id: T::AssetId, who: impl sp_std::borrow::Borrow, ) -> Option> { - if Account::::contains_key(id, who.borrow()) { + if let Some(a) = Account::::get(id, who.borrow()) { Some(ExtraMutator:: { id, who: who.borrow().clone(), - original: Account::::get(id, who.borrow()).extra, + original: a.extra, pending: None, }) } else { @@ -77,13 +77,8 @@ impl, I: 'static> ExtraMutator { /// Commit any changes to storage. pub fn commit(&mut self) -> Result<(), ()> { if let Some(extra) = self.pending.take() { - Account::::try_mutate_exists(self.id, self.who.borrow(), |maybe_account| { - if let Some(ref mut account) = maybe_account { - account.extra = extra; - Ok(()) - } else { - Err(()) - } + Account::::try_mutate(self.id, self.who.borrow(), |maybe_account| { + maybe_account.as_mut().ok_or(()).map(|account| account.extra = extra) }) } else { Ok(()) @@ -93,13 +88,11 @@ impl, I: 'static> ExtraMutator { /// Revert any changes, even those already committed by `self` and drop self. pub fn revert(mut self) -> Result<(), ()> { self.pending = None; - Account::::try_mutate_exists(self.id, self.who.borrow(), |maybe_account| { - if let Some(ref mut account) = maybe_account { - account.extra = self.original.clone(); - Ok(()) - } else { - Err(()) - } + Account::::try_mutate(self.id, self.who.borrow(), |maybe_account| { + maybe_account + .as_mut() + .ok_or(()) + .map(|account| account.extra = self.original.clone()) }) } } diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index 81b490eaf877c..f7f11cafecbe2 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,15 @@ //! Functions for the Assets pallet. use super::*; +use frame_support::{traits::Get, BoundedVec}; + +#[must_use] +pub(super) enum DeadConsequence { + Remove, + Keep, +} + +use DeadConsequence::*; // The main implementation block for the module. impl, I: 'static> Pallet { @@ -31,70 +40,98 @@ impl, I: 'static> Pallet { ExtraMutator::maybe_new(id, who) } - /// Get the asset `id` balance of `who`. + /// Get the asset `id` balance of `who`, or zero if the asset-account doesn't exist. pub fn balance(id: T::AssetId, who: impl sp_std::borrow::Borrow) -> T::Balance { - Account::::get(id, who.borrow()).balance + Self::maybe_balance(id, who).unwrap_or_default() + } + + /// Get the asset `id` balance of `who` if the asset-account exists. + pub fn maybe_balance( + id: T::AssetId, + who: impl sp_std::borrow::Borrow, + ) -> Option { + Account::::get(id, who.borrow()).map(|a| a.balance) } /// Get the total supply of an asset `id`. pub fn total_supply(id: T::AssetId) -> T::Balance { - Asset::::get(id).map(|x| x.supply).unwrap_or_else(Zero::zero) + Self::maybe_total_supply(id).unwrap_or_default() + } + + /// Get the total supply of an asset `id` if the asset exists. + pub fn maybe_total_supply(id: T::AssetId) -> Option { + Asset::::get(id).map(|x| x.supply) } pub(super) fn new_account( who: &T::AccountId, d: &mut AssetDetails>, - ) -> Result { + maybe_deposit: Option>, + ) -> Result>, DispatchError> { let accounts = d.accounts.checked_add(1).ok_or(ArithmeticError::Overflow)?; - let is_sufficient = if d.is_sufficient { + let reason = if let Some(deposit) = maybe_deposit { + ExistenceReason::DepositHeld(deposit) + } else if d.is_sufficient { frame_system::Pallet::::inc_sufficients(who); d.sufficients += 1; - true + ExistenceReason::Sufficient } else { frame_system::Pallet::::inc_consumers(who).map_err(|_| Error::::NoProvider)?; - false + ExistenceReason::Consumer }; d.accounts = accounts; - Ok(is_sufficient) + Ok(reason) } pub(super) fn dead_account( - what: T::AssetId, who: &T::AccountId, d: &mut AssetDetails>, - sufficient: bool, - ) { - if sufficient { - d.sufficients = d.sufficients.saturating_sub(1); - frame_system::Pallet::::dec_sufficients(who); - } else { - frame_system::Pallet::::dec_consumers(who); + reason: &ExistenceReason>, + force: bool, + ) -> DeadConsequence { + match *reason { + ExistenceReason::Consumer => frame_system::Pallet::::dec_consumers(who), + ExistenceReason::Sufficient => { + d.sufficients = d.sufficients.saturating_sub(1); + frame_system::Pallet::::dec_sufficients(who); + }, + ExistenceReason::DepositRefunded => {}, + ExistenceReason::DepositHeld(_) if !force => return Keep, + ExistenceReason::DepositHeld(_) => {}, } d.accounts = d.accounts.saturating_sub(1); - T::Freezer::died(what, who) + Remove } + /// Returns `true` when the balance of `account` can be increased by `amount`. + /// + /// - `id`: The id of the asset that should be increased. + /// - `who`: The account of which the balance should be increased. + /// - `amount`: The amount by which the balance should be increased. + /// - `increase_supply`: Will the supply of the asset be increased by `amount` at the same time + /// as crediting the `account`. pub(super) fn can_increase( id: T::AssetId, who: &T::AccountId, amount: T::Balance, + increase_supply: bool, ) -> DepositConsequence { let details = match Asset::::get(id) { Some(details) => details, None => return DepositConsequence::UnknownAsset, }; - if details.supply.checked_add(&amount).is_none() { + if increase_supply && details.supply.checked_add(&amount).is_none() { return DepositConsequence::Overflow } - let account = Account::::get(id, who); - if account.balance.checked_add(&amount).is_none() { - return DepositConsequence::Overflow - } - if account.balance.is_zero() { + if let Some(balance) = Self::maybe_balance(id, who) { + if balance.checked_add(&amount).is_none() { + return DepositConsequence::Overflow + } + } else { if amount < details.min_balance { return DepositConsequence::BelowMinimum } - if !details.is_sufficient && frame_system::Pallet::::providers(who) == 0 { + if !details.is_sufficient && !frame_system::Pallet::::can_inc_consumer(who) { return DepositConsequence::CannotCreate } if details.is_sufficient && details.sufficients.checked_add(1).is_none() { @@ -120,10 +157,16 @@ impl, I: 'static> Pallet { if details.supply.checked_sub(&amount).is_none() { return Underflow } - if details.is_frozen { + if details.status == AssetStatus::Frozen { return Frozen } - let account = Account::::get(id, who); + if amount.is_zero() { + return Success + } + let account = match Account::::get(id, who) { + Some(a) => a, + None => return NoFunds, + }; if account.is_frozen { return Frozen } @@ -161,10 +204,10 @@ impl, I: 'static> Pallet { who: &T::AccountId, keep_alive: bool, ) -> Result { - let details = Asset::::get(id).ok_or_else(|| Error::::Unknown)?; - ensure!(!details.is_frozen, Error::::Frozen); + let details = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); - let account = Account::::get(id, who); + let account = Account::::get(id, who).ok_or(Error::::NoAccount)?; ensure!(!account.is_frozen, Error::::Frozen); let amount = if let Some(frozen) = T::Freezer::frozen_balance(id, who) { @@ -215,7 +258,7 @@ impl, I: 'static> Pallet { Ok(dust) => actual.saturating_add(dust), //< guaranteed by reducible_balance Err(e) => { debug_assert!(false, "passed from reducible_balance; qed"); - return Err(e.into()) + return Err(e) }, }; @@ -248,10 +291,54 @@ impl, I: 'static> Pallet { (true, Some(dust)) => (amount, Some(dust)), _ => (debit, None), }; - Self::can_increase(id, &dest, credit).into_result()?; + Self::can_increase(id, dest, credit, false).into_result()?; Ok((credit, maybe_burn)) } + /// Creates a account for `who` to hold asset `id` with a zero balance and takes a deposit. + pub(super) fn do_touch(id: T::AssetId, who: T::AccountId) -> DispatchResult { + ensure!(!Account::::contains_key(id, &who), Error::::AlreadyExists); + let deposit = T::AssetAccountDeposit::get(); + let mut details = Asset::::get(&id).ok_or(Error::::Unknown)?; + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); + let reason = Self::new_account(&who, &mut details, Some(deposit))?; + T::Currency::reserve(&who, deposit)?; + Asset::::insert(&id, details); + Account::::insert( + id, + &who, + AssetAccountOf:: { + balance: Zero::zero(), + is_frozen: false, + reason, + extra: T::Extra::default(), + }, + ); + Ok(()) + } + + /// Returns a deposit, destroying an asset-account. + pub(super) fn do_refund(id: T::AssetId, who: T::AccountId, allow_burn: bool) -> DispatchResult { + let mut account = Account::::get(id, &who).ok_or(Error::::NoDeposit)?; + let deposit = account.reason.take_deposit().ok_or(Error::::NoDeposit)?; + let mut details = Asset::::get(&id).ok_or(Error::::Unknown)?; + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); + ensure!(account.balance.is_zero() || allow_burn, Error::::WouldBurn); + ensure!(!account.is_frozen, Error::::Frozen); + + T::Currency::unreserve(&who, deposit); + + if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) { + Account::::remove(id, &who); + } else { + debug_assert!(false, "refund did not result in dead account?!"); + } + Asset::::insert(&id, details); + // Executing a hook here is safe, since it is not in a `mutate`. + T::Freezer::died(id, &who); + Ok(()) + } + /// Increases the asset `id` balance of `beneficiary` by `amount`. /// /// This alters the registered supply of the asset and emits an event. @@ -265,7 +352,7 @@ impl, I: 'static> Pallet { ) -> DispatchResult { Self::increase_balance(id, beneficiary, amount, |details| -> DispatchResult { if let Some(check_issuer) = maybe_check_issuer { - ensure!(&check_issuer == &details.issuer, Error::::NoPermission); + ensure!(check_issuer == details.issuer, Error::::NoPermission); } debug_assert!( T::Balance::max_value() - details.supply >= amount, @@ -274,7 +361,11 @@ impl, I: 'static> Pallet { details.supply = details.supply.saturating_add(amount); Ok(()) })?; - Self::deposit_event(Event::Issued(id, beneficiary.clone(), amount)); + Self::deposit_event(Event::Issued { + asset_id: id, + owner: beneficiary.clone(), + total_supply: amount, + }); Ok(()) } @@ -296,19 +387,28 @@ impl, I: 'static> Pallet { return Ok(()) } - Self::can_increase(id, beneficiary, amount).into_result()?; + Self::can_increase(id, beneficiary, amount, true).into_result()?; Asset::::try_mutate(id, |maybe_details| -> DispatchResult { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); check(details)?; - Account::::try_mutate(id, beneficiary, |t| -> DispatchResult { - let new_balance = t.balance.saturating_add(amount); - ensure!(new_balance >= details.min_balance, TokenError::BelowMinimum); - if t.balance.is_zero() { - t.sufficient = Self::new_account(beneficiary, details)?; + Account::::try_mutate(id, beneficiary, |maybe_account| -> DispatchResult { + match maybe_account { + Some(ref mut account) => { + account.balance.saturating_accrue(amount); + }, + maybe_account @ None => { + // Note this should never fail as it's already checked by `can_increase`. + ensure!(amount >= details.min_balance, TokenError::BelowMinimum); + *maybe_account = Some(AssetAccountOf:: { + balance: amount, + reason: Self::new_account(beneficiary, details, None)?, + is_frozen: false, + extra: T::Extra::default(), + }); + }, } - t.balance = new_balance; Ok(()) })?; Ok(()) @@ -330,10 +430,16 @@ impl, I: 'static> Pallet { maybe_check_admin: Option, f: DebitFlags, ) -> Result { + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!( + d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, + Error::::AssetNotLive + ); + let actual = Self::decrease_balance(id, target, amount, f, |actual, details| { // Check admin rights. if let Some(check_admin) = maybe_check_admin { - ensure!(&check_admin == &details.admin, Error::::NoPermission); + ensure!(check_admin == details.admin, Error::::NoPermission); } debug_assert!(details.supply >= actual, "checked in prep; qed"); @@ -341,7 +447,7 @@ impl, I: 'static> Pallet { Ok(()) })?; - Self::deposit_event(Event::Burned(id, target.clone(), actual)); + Self::deposit_event(Event::Burned { asset_id: id, owner: target.clone(), balance: actual }); Ok(actual) } @@ -367,32 +473,40 @@ impl, I: 'static> Pallet { return Ok(amount) } + let details = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); + let actual = Self::prep_debit(id, target, amount, f)?; + let mut target_died: Option = None; Asset::::try_mutate(id, |maybe_details| -> DispatchResult { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - check(actual, details)?; - Account::::try_mutate_exists(id, target, |maybe_account| -> DispatchResult { - let mut account = maybe_account.take().unwrap_or_default(); + Account::::try_mutate(id, target, |maybe_account| -> DispatchResult { + let mut account = maybe_account.take().ok_or(Error::::NoAccount)?; debug_assert!(account.balance >= actual, "checked in prep; qed"); // Make the debit. account.balance = account.balance.saturating_sub(actual); - *maybe_account = if account.balance < details.min_balance { + if account.balance < details.min_balance { debug_assert!(account.balance.is_zero(), "checked in prep; qed"); - Self::dead_account(id, target, details, account.sufficient); - None - } else { - Some(account) + target_died = Some(Self::dead_account(target, details, &account.reason, false)); + if let Some(Remove) = target_died { + return Ok(()) + } }; + *maybe_account = Some(account); Ok(()) })?; Ok(()) })?; + // Execute hook outside of `mutate`. + if let Some(Remove) = target_died { + T::Freezer::died(id, target); + } Ok(actual) } @@ -412,24 +526,45 @@ impl, I: 'static> Pallet { maybe_need_admin: Option, f: TransferFlags, ) -> Result { - // Early exist if no-op. + let (balance, died) = + Self::transfer_and_die(id, source, dest, amount, maybe_need_admin, f)?; + if let Some(Remove) = died { + T::Freezer::died(id, source); + } + Ok(balance) + } + + /// Same as `do_transfer` but it does not execute the `FrozenBalance::died` hook and + /// instead returns whether and how the `source` account died in this operation. + fn transfer_and_die( + id: T::AssetId, + source: &T::AccountId, + dest: &T::AccountId, + amount: T::Balance, + maybe_need_admin: Option, + f: TransferFlags, + ) -> Result<(T::Balance, Option), DispatchError> { + // Early exit if no-op. if amount.is_zero() { - Self::deposit_event(Event::Transferred(id, source.clone(), dest.clone(), amount)); - return Ok(amount) + return Ok((amount, None)) } + let details = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); // Figure out the debit and credit, together with side-effects. - let debit = Self::prep_debit(id, &source, amount, f.into())?; - let (credit, maybe_burn) = Self::prep_credit(id, &dest, amount, debit, f.burn_dust)?; + let debit = Self::prep_debit(id, source, amount, f.into())?; + let (credit, maybe_burn) = Self::prep_credit(id, dest, amount, debit, f.burn_dust)?; - let mut source_account = Account::::get(id, &source); + let mut source_account = + Account::::get(id, &source).ok_or(Error::::NoAccount)?; + let mut source_died: Option = None; Asset::::try_mutate(id, |maybe_details| -> DispatchResult { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; // Check admin rights. if let Some(need_admin) = maybe_need_admin { - ensure!(&need_admin == &details.admin, Error::::NoPermission); + ensure!(need_admin == details.admin, Error::::NoPermission); } // Skip if source == dest @@ -449,33 +584,345 @@ impl, I: 'static> Pallet { debug_assert!(source_account.balance >= debit, "checked in prep; qed"); source_account.balance = source_account.balance.saturating_sub(debit); - Account::::try_mutate(id, &dest, |a| -> DispatchResult { - // Calculate new balance; this will not saturate since it's already checked in prep. - debug_assert!(a.balance.checked_add(&credit).is_some(), "checked in prep; qed"); - let new_balance = a.balance.saturating_add(credit); - - // Create a new account if there wasn't one already. - if a.balance.is_zero() { - a.sufficient = Self::new_account(&dest, details)?; + Account::::try_mutate(id, &dest, |maybe_account| -> DispatchResult { + match maybe_account { + Some(ref mut account) => { + // Calculate new balance; this will not saturate since it's already checked + // in prep. + debug_assert!( + account.balance.checked_add(&credit).is_some(), + "checked in prep; qed" + ); + account.balance.saturating_accrue(credit); + }, + maybe_account @ None => { + *maybe_account = Some(AssetAccountOf:: { + balance: credit, + is_frozen: false, + reason: Self::new_account(dest, details, None)?, + extra: T::Extra::default(), + }); + }, } - - a.balance = new_balance; Ok(()) })?; // Remove source account if it's now dead. if source_account.balance < details.min_balance { debug_assert!(source_account.balance.is_zero(), "checked in prep; qed"); - Self::dead_account(id, &source, details, source_account.sufficient); - Account::::remove(id, &source); - } else { - Account::::insert(id, &source, &source_account) + source_died = + Some(Self::dead_account(source, details, &source_account.reason, false)); + if let Some(Remove) = source_died { + Account::::remove(id, &source); + return Ok(()) + } } - + Account::::insert(id, &source, &source_account); Ok(()) })?; - Self::deposit_event(Event::Transferred(id, source.clone(), dest.clone(), credit)); - Ok(credit) + Self::deposit_event(Event::Transferred { + asset_id: id, + from: source.clone(), + to: dest.clone(), + amount: credit, + }); + Ok((credit, source_died)) + } + + /// Create a new asset without taking a deposit. + /// + /// * `id`: The `AssetId` you want the new asset to have. Must not already be in use. + /// * `owner`: The owner, issuer, admin, and freezer of this asset upon creation. + /// * `is_sufficient`: Whether this asset needs users to have an existential deposit to hold + /// this asset. + /// * `min_balance`: The minimum balance a user is allowed to have of this asset before they are + /// considered dust and cleaned up. + pub(super) fn do_force_create( + id: T::AssetId, + owner: T::AccountId, + is_sufficient: bool, + min_balance: T::Balance, + ) -> DispatchResult { + ensure!(!Asset::::contains_key(id), Error::::InUse); + ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); + + Asset::::insert( + id, + AssetDetails { + owner: owner.clone(), + issuer: owner.clone(), + admin: owner.clone(), + freezer: owner.clone(), + supply: Zero::zero(), + deposit: Zero::zero(), + min_balance, + is_sufficient, + accounts: 0, + sufficients: 0, + approvals: 0, + status: AssetStatus::Live, + }, + ); + Self::deposit_event(Event::ForceCreated { asset_id: id, owner }); + Ok(()) + } + + /// Start the process of destroying an asset, by setting the asset status to `Destroying`, and + /// emitting the `DestructionStarted` event. + pub(super) fn do_start_destroy( + id: T::AssetId, + maybe_check_owner: Option, + ) -> DispatchResult { + Asset::::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> { + let mut details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + if let Some(check_owner) = maybe_check_owner { + ensure!(details.owner == check_owner, Error::::NoPermission); + } + details.status = AssetStatus::Destroying; + + Self::deposit_event(Event::DestructionStarted { asset_id: id }); + Ok(()) + }) + } + + /// Destroy accounts associated with a given asset up to the max (T::RemoveItemsLimit). + /// + /// Each call emits the `Event::DestroyedAccounts` event. + /// Returns the number of destroyed accounts. + pub(super) fn do_destroy_accounts( + id: T::AssetId, + max_items: u32, + ) -> Result { + let mut dead_accounts: Vec = vec![]; + let mut remaining_accounts = 0; + let _ = + Asset::::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> { + let mut details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + // Should only destroy accounts while the asset is in a destroying state + ensure!(details.status == AssetStatus::Destroying, Error::::IncorrectStatus); + + for (who, v) in Account::::drain_prefix(id) { + let _ = Self::dead_account(&who, &mut details, &v.reason, true); + dead_accounts.push(who); + if dead_accounts.len() >= (max_items as usize) { + break + } + } + remaining_accounts = details.accounts; + Ok(()) + })?; + + for who in &dead_accounts { + T::Freezer::died(id, &who); + } + + Self::deposit_event(Event::AccountsDestroyed { + asset_id: id, + accounts_destroyed: dead_accounts.len() as u32, + accounts_remaining: remaining_accounts as u32, + }); + Ok(dead_accounts.len() as u32) + } + + /// Destroy approvals associated with a given asset up to the max (T::RemoveItemsLimit). + /// + /// Each call emits the `Event::DestroyedApprovals` event + /// Returns the number of destroyed approvals. + pub(super) fn do_destroy_approvals( + id: T::AssetId, + max_items: u32, + ) -> Result { + let mut removed_approvals = 0; + let _ = + Asset::::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> { + let mut details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + + // Should only destroy accounts while the asset is in a destroying state. + ensure!(details.status == AssetStatus::Destroying, Error::::IncorrectStatus); + + for ((owner, _), approval) in Approvals::::drain_prefix((id,)) { + T::Currency::unreserve(&owner, approval.deposit); + removed_approvals = removed_approvals.saturating_add(1); + details.approvals = details.approvals.saturating_sub(1); + if removed_approvals >= max_items { + break + } + } + Self::deposit_event(Event::ApprovalsDestroyed { + asset_id: id, + approvals_destroyed: removed_approvals as u32, + approvals_remaining: details.approvals as u32, + }); + Ok(()) + })?; + Ok(removed_approvals) + } + + /// Complete destroying an asset and unreserve the deposit. + /// + /// On success, the `Event::Destroyed` event is emitted. + pub(super) fn do_finish_destroy(id: T::AssetId) -> DispatchResult { + Asset::::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> { + let details = maybe_details.take().ok_or(Error::::Unknown)?; + ensure!(details.status == AssetStatus::Destroying, Error::::IncorrectStatus); + ensure!(details.accounts == 0, Error::::InUse); + ensure!(details.approvals == 0, Error::::InUse); + + let metadata = Metadata::::take(&id); + T::Currency::unreserve( + &details.owner, + details.deposit.saturating_add(metadata.deposit), + ); + Self::deposit_event(Event::Destroyed { asset_id: id }); + + Ok(()) + }) + } + + /// Creates an approval from `owner` to spend `amount` of asset `id` tokens by 'delegate' + /// while reserving `T::ApprovalDeposit` from owner + /// + /// If an approval already exists, the new amount is added to such existing approval + pub(super) fn do_approve_transfer( + id: T::AssetId, + owner: &T::AccountId, + delegate: &T::AccountId, + amount: T::Balance, + ) -> DispatchResult { + let mut d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); + Approvals::::try_mutate( + (id, &owner, &delegate), + |maybe_approved| -> DispatchResult { + let mut approved = match maybe_approved.take() { + // an approval already exists and is being updated + Some(a) => a, + // a new approval is created + None => { + d.approvals.saturating_inc(); + Default::default() + }, + }; + let deposit_required = T::ApprovalDeposit::get(); + if approved.deposit < deposit_required { + T::Currency::reserve(owner, deposit_required - approved.deposit)?; + approved.deposit = deposit_required; + } + approved.amount = approved.amount.saturating_add(amount); + *maybe_approved = Some(approved); + Ok(()) + }, + )?; + Asset::::insert(id, d); + Self::deposit_event(Event::ApprovedTransfer { + asset_id: id, + source: owner.clone(), + delegate: delegate.clone(), + amount, + }); + + Ok(()) + } + + /// Reduces the asset `id` balance of `owner` by some `amount` and increases the balance of + /// `dest` by (similar) amount, checking that 'delegate' has an existing approval from `owner` + /// to spend`amount`. + /// + /// Will fail if `amount` is greater than the approval from `owner` to 'delegate' + /// Will unreserve the deposit from `owner` if the entire approved `amount` is spent by + /// 'delegate' + pub(super) fn do_transfer_approved( + id: T::AssetId, + owner: &T::AccountId, + delegate: &T::AccountId, + destination: &T::AccountId, + amount: T::Balance, + ) -> DispatchResult { + let mut owner_died: Option = None; + + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); + + Approvals::::try_mutate_exists( + (id, &owner, delegate), + |maybe_approved| -> DispatchResult { + let mut approved = maybe_approved.take().ok_or(Error::::Unapproved)?; + let remaining = + approved.amount.checked_sub(&amount).ok_or(Error::::Unapproved)?; + + let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false }; + owner_died = Self::transfer_and_die(id, owner, destination, amount, None, f)?.1; + + if remaining.is_zero() { + T::Currency::unreserve(owner, approved.deposit); + Asset::::mutate(id, |maybe_details| { + if let Some(details) = maybe_details { + details.approvals.saturating_dec(); + } + }); + } else { + approved.amount = remaining; + *maybe_approved = Some(approved); + } + Ok(()) + }, + )?; + + // Execute hook outside of `mutate`. + if let Some(Remove) = owner_died { + T::Freezer::died(id, owner); + } + Ok(()) + } + + /// Do set metadata + pub(super) fn do_set_metadata( + id: T::AssetId, + from: &T::AccountId, + name: Vec, + symbol: Vec, + decimals: u8, + ) -> DispatchResult { + let bounded_name: BoundedVec = + name.clone().try_into().map_err(|_| Error::::BadMetadata)?; + let bounded_symbol: BoundedVec = + symbol.clone().try_into().map_err(|_| Error::::BadMetadata)?; + + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); + ensure!(from == &d.owner, Error::::NoPermission); + + Metadata::::try_mutate_exists(id, |metadata| { + ensure!(metadata.as_ref().map_or(true, |m| !m.is_frozen), Error::::NoPermission); + + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + let new_deposit = T::MetadataDepositPerByte::get() + .saturating_mul(((name.len() + symbol.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + + if new_deposit > old_deposit { + T::Currency::reserve(from, new_deposit - old_deposit)?; + } else { + T::Currency::unreserve(from, old_deposit - new_deposit); + } + + *metadata = Some(AssetMetadata { + deposit: new_deposit, + name: bounded_name, + symbol: bounded_symbol, + decimals, + is_frozen: false, + }); + + Self::deposit_event(Event::MetadataSet { + asset_id: id, + name, + symbol, + decimals, + is_frozen: false, + }); + Ok(()) + }) } } diff --git a/frame/assets/src/impl_fungibles.rs b/frame/assets/src/impl_fungibles.rs index 4e85b20a1fbb1..b10b8c6b10755 100644 --- a/frame/assets/src/impl_fungibles.rs +++ b/frame/assets/src/impl_fungibles.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,8 +47,9 @@ impl, I: 'static> fungibles::Inspect<::AccountId asset: Self::AssetId, who: &::AccountId, amount: Self::Balance, + mint: bool, ) -> DepositConsequence { - Pallet::::can_increase(asset, who, amount) + Pallet::::can_increase(asset, who, amount, mint) } fn can_withdraw( @@ -58,6 +59,29 @@ impl, I: 'static> fungibles::Inspect<::AccountId ) -> WithdrawConsequence { Pallet::::can_decrease(asset, who, amount, false) } + + fn asset_exists(asset: Self::AssetId) -> bool { + Asset::::contains_key(asset) + } +} + +impl, I: 'static> fungibles::InspectMetadata<::AccountId> + for Pallet +{ + /// Return the name of an asset. + fn name(asset: &Self::AssetId) -> Vec { + Metadata::::get(asset).name.to_vec() + } + + /// Return the symbol of an asset. + fn symbol(asset: &Self::AssetId) -> Vec { + Metadata::::get(asset).symbol.to_vec() + } + + /// Return the decimals of an asset. + fn decimals(asset: &Self::AssetId) -> u8 { + Metadata::::get(asset).decimals + } } impl, I: 'static> fungibles::Mutate<::AccountId> for Pallet { @@ -147,3 +171,132 @@ impl, I: 'static> fungibles::Unbalanced for Pallet, I: 'static> fungibles::Create for Pallet { + fn create( + id: T::AssetId, + admin: T::AccountId, + is_sufficient: bool, + min_balance: Self::Balance, + ) -> DispatchResult { + Self::do_force_create(id, admin, is_sufficient, min_balance) + } +} + +impl, I: 'static> fungibles::Destroy for Pallet { + fn start_destroy(id: T::AssetId, maybe_check_owner: Option) -> DispatchResult { + Self::do_start_destroy(id, maybe_check_owner) + } + + fn destroy_accounts(id: T::AssetId, max_items: u32) -> Result { + Self::do_destroy_accounts(id, max_items) + } + + fn destroy_approvals(id: T::AssetId, max_items: u32) -> Result { + Self::do_destroy_approvals(id, max_items) + } + + fn finish_destroy(id: T::AssetId) -> DispatchResult { + Self::do_finish_destroy(id) + } +} + +impl, I: 'static> fungibles::metadata::Inspect<::AccountId> + for Pallet +{ + fn name(asset: T::AssetId) -> Vec { + Metadata::::get(asset).name.to_vec() + } + + fn symbol(asset: T::AssetId) -> Vec { + Metadata::::get(asset).symbol.to_vec() + } + + fn decimals(asset: T::AssetId) -> u8 { + Metadata::::get(asset).decimals + } +} + +impl, I: 'static> fungibles::metadata::Mutate<::AccountId> + for Pallet +{ + fn set( + asset: T::AssetId, + from: &::AccountId, + name: Vec, + symbol: Vec, + decimals: u8, + ) -> DispatchResult { + Self::do_set_metadata(asset, from, name, symbol, decimals) + } +} + +impl, I: 'static> fungibles::approvals::Inspect<::AccountId> + for Pallet +{ + // Check the amount approved to be spent by an owner to a delegate + fn allowance( + asset: T::AssetId, + owner: &::AccountId, + delegate: &::AccountId, + ) -> T::Balance { + Approvals::::get((asset, &owner, &delegate)) + .map(|x| x.amount) + .unwrap_or_else(Zero::zero) + } +} + +impl, I: 'static> fungibles::approvals::Mutate<::AccountId> + for Pallet +{ + fn approve( + asset: T::AssetId, + owner: &::AccountId, + delegate: &::AccountId, + amount: T::Balance, + ) -> DispatchResult { + Self::do_approve_transfer(asset, owner, delegate, amount) + } + + // Aprove spending tokens from a given account + fn transfer_from( + asset: T::AssetId, + owner: &::AccountId, + delegate: &::AccountId, + dest: &::AccountId, + amount: T::Balance, + ) -> DispatchResult { + Self::do_transfer_approved(asset, owner, delegate, dest, amount) + } +} + +impl, I: 'static> fungibles::roles::Inspect<::AccountId> + for Pallet +{ + fn owner(asset: T::AssetId) -> Option<::AccountId> { + Asset::::get(asset).map(|x| x.owner) + } + + fn issuer(asset: T::AssetId) -> Option<::AccountId> { + Asset::::get(asset).map(|x| x.issuer) + } + + fn admin(asset: T::AssetId) -> Option<::AccountId> { + Asset::::get(asset).map(|x| x.admin) + } + + fn freezer(asset: T::AssetId) -> Option<::AccountId> { + Asset::::get(asset).map(|x| x.freezer) + } +} + +impl, I: 'static> fungibles::InspectEnumerable for Pallet { + type AssetsIterator = KeyPrefixIterator<>::AssetId>; + + /// Returns an iterator of the assets in existence. + /// + /// NOTE: iterating this list invokes a storage read per item. + fn asset_ids() -> Self::AssetsIterator { + Asset::::iter_keys() + } +} diff --git a/frame/assets/src/impl_stored_map.rs b/frame/assets/src/impl_stored_map.rs index 4c1ff1a0c6027..a4669c776ed41 100644 --- a/frame/assets/src/impl_stored_map.rs +++ b/frame/assets/src/impl_stored_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,11 +22,7 @@ use super::*; impl, I: 'static> StoredMap<(T::AssetId, T::AccountId), T::Extra> for Pallet { fn get(id_who: &(T::AssetId, T::AccountId)) -> T::Extra { let &(id, ref who) = id_who; - if Account::::contains_key(id, who) { - Account::::get(id, who).extra - } else { - Default::default() - } + Account::::get(id, who).map(|a| a.extra).unwrap_or_default() } fn try_mutate_exists>( @@ -34,19 +30,19 @@ impl, I: 'static> StoredMap<(T::AssetId, T::AccountId), T::Extra> f f: impl FnOnce(&mut Option) -> Result, ) -> Result { let &(id, ref who) = id_who; - let mut maybe_extra = Some(Account::::get(id, who).extra); + let mut maybe_extra = Account::::get(id, who).map(|a| a.extra); let r = f(&mut maybe_extra)?; // They want to write some value or delete it. // If the account existed and they want to write a value, then we write. // If the account didn't exist and they want to delete it, then we let it pass. // Otherwise, we fail. - Account::::try_mutate_exists(id, who, |maybe_account| { + Account::::try_mutate(id, who, |maybe_account| { if let Some(extra) = maybe_extra { // They want to write a value. Let this happen only if the account actually exists. if let Some(ref mut account) = maybe_account { account.extra = extra; } else { - Err(DispatchError::NoProviders)?; + return Err(DispatchError::NoProviders.into()) } } else { // They want to delete it. Let this pass if the item never existed anyway. diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 797a3ae7ee9fb..629a0243cfc80 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,15 +36,15 @@ //! //! ### Terminology //! -//! * **Admin**: An account ID uniquely privileged to be able to unfreeze (thaw) an account and it's +//! * **Admin**: An account ID uniquely privileged to be able to unfreeze (thaw) an account and its //! assets, as well as forcibly transfer a particular class of assets between arbitrary accounts //! and reduce the balance of a particular class of assets of arbitrary accounts. //! * **Asset issuance/minting**: The creation of a new asset, whose total supply will belong to the -//! account that issues the asset. This is a privileged operation. +//! account designated as the beneficiary of the asset. This is a privileged operation. //! * **Asset transfer**: The reduction of the balance of an asset of one account with the //! corresponding increase in the balance of another. -//! * **Asset destruction**: The process of reduce the balance of an asset of one account. This is a -//! privileged operation. +//! * **Asset destruction**: The process of reducing the balance of an asset of one account. This is +//! a privileged operation. //! * **Fungible asset**: An asset whose units are interchangeable. //! * **Issuer**: An account ID uniquely privileged to be able to mint a particular class of assets. //! * **Freezer**: An account ID uniquely privileged to be able to freeze an account from @@ -63,12 +63,12 @@ //! //! The assets system in Substrate is designed to make the following possible: //! -//! * Issue a new assets in a permissioned or permissionless way, if permissionless, then with a +//! * Issue new assets in a permissioned or permissionless way, if permissionless, then with a //! deposit required. //! * Allow accounts to be delegated the ability to transfer assets without otherwise existing //! on-chain (*approvals*). //! * Move assets between accounts. -//! * Update the asset's total supply. +//! * Update an asset class's total supply. //! * Allow administrative activities by specially privileged accounts including freezing account //! balances and minting/burning assets. //! @@ -79,8 +79,6 @@ //! * `create`: Creates a new asset class, taking the required deposit. //! * `transfer`: Transfer sender's assets to another account. //! * `transfer_keep_alive`: Transfer sender's assets to another account, keeping the sender alive. -//! * `set_metadata`: Set the metadata of an asset class. -//! * `clear_metadata`: Remove the metadata of an asset class. //! * `approve_transfer`: Create or increase an delegated transfer. //! * `cancel_approval`: Rescind a previous approval. //! * `transfer_approved`: Transfer third-party's assets to another account. @@ -94,6 +92,7 @@ //! * `force_cancel_approval`: Rescind a previous approval. //! //! ### Privileged Functions +//! //! * `destroy`: Destroys an entire asset class; called by the asset class's Owner. //! * `mint`: Increases the asset balance of an account; called by the asset class's Issuer. //! * `burn`: Decreases the asset balance of an account; called by the asset class's Admin. @@ -103,6 +102,8 @@ //! * `transfer_ownership`: Changes an asset class's Owner; called by the asset class's Owner. //! * `set_team`: Changes an asset class's Admin, Freezer and Issuer; called by the asset class's //! Owner. +//! * `set_metadata`: Set the metadata of an asset class; called by the asset class's Owner. +//! * `clear_metadata`: Remove the metadata of an asset class; called by the asset class's Owner. //! //! Please refer to the [`Call`] enum and its associated variants for documentation on each //! function. @@ -120,11 +121,15 @@ //! * [`System`](../frame_system/index.html) //! * [`Support`](../frame_support/index.html) +// This recursion limit is needed because we have too many benchmarks and benchmarking will fail if +// we add more without this limit. +#![recursion_limit = "1024"] // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +pub mod migration; #[cfg(test)] pub mod mock; #[cfg(test)] @@ -139,62 +144,121 @@ mod impl_stored_map; mod types; pub use types::*; -use codec::HasCompact; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{ + AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero, + }, + ArithmeticError, TokenError, +}; +use sp_std::{borrow::Borrow, prelude::*}; + use frame_support::{ dispatch::{DispatchError, DispatchResult}, ensure, + pallet_prelude::DispatchResultWithPostInfo, + storage::KeyPrefixIterator, traits::{ tokens::{fungibles, DepositConsequence, WithdrawConsequence}, BalanceStatus::Reserved, - Currency, ReservableCurrency, StoredMap, + Currency, EnsureOriginWithArg, ReservableCurrency, StoredMap, }, }; use frame_system::Config as SystemConfig; -use sp_runtime::{ - traits::{ - AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero, - }, - ArithmeticError, TokenError, -}; -use sp_std::{borrow::Borrow, convert::TryInto, prelude::*}; pub use pallet::*; pub use weights::WeightInfo; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::generate_storage_info] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn create_asset_id_parameter(id: u32) -> AssetIdParameter; + } + #[cfg(feature = "runtime-benchmarks")] + impl> BenchmarkHelper for () { + fn create_asset_id_parameter(id: u32) -> AssetIdParameter { + id.into() + } + } + #[pallet::config] /// The module configuration trait. pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; /// The units in which we record balances. - type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy + MaxEncodedLen; + type Balance: Member + + Parameter + + AtLeast32BitUnsigned + + Default + + Copy + + MaybeSerializeDeserialize + + MaxEncodedLen + + TypeInfo; + + /// Max number of items to destroy per `destroy_accounts` and `destroy_approvals` call. + /// + /// Must be configured to result in a weight that makes each call fit in a block. + #[pallet::constant] + type RemoveItemsLimit: Get; /// Identifier for the class of asset. - type AssetId: Member + Parameter + Default + Copy + HasCompact + MaxEncodedLen; + type AssetId: Member + Parameter + Copy + MaybeSerializeDeserialize + MaxEncodedLen; + + /// Wrapper around `Self::AssetId` to use in dispatchable call signatures. Allows the use + /// of compact encoding in instances of the pallet, which will prevent breaking changes + /// resulting from the removal of `HasCompact` from `Self::AssetId`. + /// + /// This type includes the `From` bound, since tightly coupled pallets may + /// want to convert an `AssetId` into a parameter for calling dispatchable functions + /// directly. + type AssetIdParameter: Parameter + + Copy + + From + + Into + + MaxEncodedLen; /// The currency mechanism. type Currency: ReservableCurrency; + /// Standard asset class creation is only allowed if the origin attempting it and the + /// asset class are in this set. + type CreateOrigin: EnsureOriginWithArg< + Self::RuntimeOrigin, + Self::AssetId, + Success = Self::AccountId, + >; + /// The origin which may forcibly create or destroy an asset or otherwise alter privileged /// attributes. - type ForceOrigin: EnsureOrigin; + type ForceOrigin: EnsureOrigin; /// The basic amount of funds that must be reserved for an asset. #[pallet::constant] type AssetDeposit: Get>; + /// The amount of funds that must be reserved for a non-provider asset account to be + /// maintained. + #[pallet::constant] + type AssetAccountDeposit: Get>; + /// The basic amount of funds that must be reserved when adding metadata to your asset. #[pallet::constant] type MetadataDepositBase: Get>; @@ -221,6 +285,10 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// Helper trait for benchmarks. + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: BenchmarkHelper; } #[pallet::storage] @@ -233,17 +301,14 @@ pub mod pallet { >; #[pallet::storage] - /// The number of units of assets held by any given account. + /// The holdings of a specific account for a specific asset. pub(super) type Account, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::AssetId, Blake2_128Concat, T::AccountId, - AssetBalance, - ValueQuery, - GetDefault, - ConstU32<300_000>, + AssetAccountOf, >; #[pallet::storage] @@ -258,9 +323,6 @@ pub mod pallet { NMapKey, // delegate ), Approval>, - OptionQuery, - GetDefault, - ConstU32<300_000>, >; #[pallet::storage] @@ -271,62 +333,176 @@ pub mod pallet { T::AssetId, AssetMetadata, BoundedVec>, ValueQuery, - GetDefault, - ConstU32<300_000>, >; + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + /// Genesis assets: id, owner, is_sufficient, min_balance + pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>, + /// Genesis metadata: id, name, symbol, decimals + pub metadata: Vec<(T::AssetId, Vec, Vec, u8)>, + /// Genesis accounts: id, account_id, balance + pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>, + } + + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + assets: Default::default(), + metadata: Default::default(), + accounts: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + for (id, owner, is_sufficient, min_balance) in &self.assets { + assert!(!Asset::::contains_key(id), "Asset id already in use"); + assert!(!min_balance.is_zero(), "Min balance should not be zero"); + Asset::::insert( + id, + AssetDetails { + owner: owner.clone(), + issuer: owner.clone(), + admin: owner.clone(), + freezer: owner.clone(), + supply: Zero::zero(), + deposit: Zero::zero(), + min_balance: *min_balance, + is_sufficient: *is_sufficient, + accounts: 0, + sufficients: 0, + approvals: 0, + status: AssetStatus::Live, + }, + ); + } + + for (id, name, symbol, decimals) in &self.metadata { + assert!(Asset::::contains_key(id), "Asset does not exist"); + + let bounded_name: BoundedVec = + name.clone().try_into().expect("asset name is too long"); + let bounded_symbol: BoundedVec = + symbol.clone().try_into().expect("asset symbol is too long"); + + let metadata = AssetMetadata { + deposit: Zero::zero(), + name: bounded_name, + symbol: bounded_symbol, + decimals: *decimals, + is_frozen: false, + }; + Metadata::::insert(id, metadata); + } + + for (id, account_id, amount) in &self.accounts { + let result = >::increase_balance( + *id, + account_id, + *amount, + |details| -> DispatchResult { + debug_assert!( + T::Balance::max_value() - details.supply >= *amount, + "checked in prep; qed" + ); + details.supply = details.supply.saturating_add(*amount); + Ok(()) + }, + ); + assert!(result.is_ok()); + } + } + } + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { - /// Some asset class was created. \[asset_id, creator, owner\] - Created(T::AssetId, T::AccountId, T::AccountId), - /// Some assets were issued. \[asset_id, owner, total_supply\] - Issued(T::AssetId, T::AccountId, T::Balance), - /// Some assets were transferred. \[asset_id, from, to, amount\] - Transferred(T::AssetId, T::AccountId, T::AccountId, T::Balance), - /// Some assets were destroyed. \[asset_id, owner, balance\] - Burned(T::AssetId, T::AccountId, T::Balance), - /// The management team changed \[asset_id, issuer, admin, freezer\] - TeamChanged(T::AssetId, T::AccountId, T::AccountId, T::AccountId), - /// The owner changed \[asset_id, owner\] - OwnerChanged(T::AssetId, T::AccountId), - /// Some account `who` was frozen. \[asset_id, who\] - Frozen(T::AssetId, T::AccountId), - /// Some account `who` was thawed. \[asset_id, who\] - Thawed(T::AssetId, T::AccountId), - /// Some asset `asset_id` was frozen. \[asset_id\] - AssetFrozen(T::AssetId), - /// Some asset `asset_id` was thawed. \[asset_id\] - AssetThawed(T::AssetId), + /// Some asset class was created. + Created { asset_id: T::AssetId, creator: T::AccountId, owner: T::AccountId }, + /// Some assets were issued. + Issued { asset_id: T::AssetId, owner: T::AccountId, total_supply: T::Balance }, + /// Some assets were transferred. + Transferred { + asset_id: T::AssetId, + from: T::AccountId, + to: T::AccountId, + amount: T::Balance, + }, + /// Some assets were destroyed. + Burned { asset_id: T::AssetId, owner: T::AccountId, balance: T::Balance }, + /// The management team changed. + TeamChanged { + asset_id: T::AssetId, + issuer: T::AccountId, + admin: T::AccountId, + freezer: T::AccountId, + }, + /// The owner changed. + OwnerChanged { asset_id: T::AssetId, owner: T::AccountId }, + /// Some account `who` was frozen. + Frozen { asset_id: T::AssetId, who: T::AccountId }, + /// Some account `who` was thawed. + Thawed { asset_id: T::AssetId, who: T::AccountId }, + /// Some asset `asset_id` was frozen. + AssetFrozen { asset_id: T::AssetId }, + /// Some asset `asset_id` was thawed. + AssetThawed { asset_id: T::AssetId }, + /// Accounts were destroyed for given asset. + AccountsDestroyed { asset_id: T::AssetId, accounts_destroyed: u32, accounts_remaining: u32 }, + /// Approvals were destroyed for given asset. + ApprovalsDestroyed { + asset_id: T::AssetId, + approvals_destroyed: u32, + approvals_remaining: u32, + }, + /// An asset class is in the process of being destroyed. + DestructionStarted { asset_id: T::AssetId }, /// An asset class was destroyed. - Destroyed(T::AssetId), - /// Some asset class was force-created. \[asset_id, owner\] - ForceCreated(T::AssetId, T::AccountId), - /// New metadata has been set for an asset. \[asset_id, name, symbol, decimals, is_frozen\] - MetadataSet(T::AssetId, Vec, Vec, u8, bool), - /// Metadata has been cleared for an asset. \[asset_id\] - MetadataCleared(T::AssetId), + Destroyed { asset_id: T::AssetId }, + /// Some asset class was force-created. + ForceCreated { asset_id: T::AssetId, owner: T::AccountId }, + /// New metadata has been set for an asset. + MetadataSet { + asset_id: T::AssetId, + name: Vec, + symbol: Vec, + decimals: u8, + is_frozen: bool, + }, + /// Metadata has been cleared for an asset. + MetadataCleared { asset_id: T::AssetId }, /// (Additional) funds have been approved for transfer to a destination account. - /// \[asset_id, source, delegate, amount\] - ApprovedTransfer(T::AssetId, T::AccountId, T::AccountId, T::Balance), + ApprovedTransfer { + asset_id: T::AssetId, + source: T::AccountId, + delegate: T::AccountId, + amount: T::Balance, + }, /// An approval for account `delegate` was cancelled by `owner`. - /// \[id, owner, delegate\] - ApprovalCancelled(T::AssetId, T::AccountId, T::AccountId), + ApprovalCancelled { asset_id: T::AssetId, owner: T::AccountId, delegate: T::AccountId }, /// An `amount` was transferred in its entirety from `owner` to `destination` by /// the approved `delegate`. - /// \[id, owner, delegate, destination\] - TransferredApproved(T::AssetId, T::AccountId, T::AccountId, T::AccountId, T::Balance), + TransferredApproved { + asset_id: T::AssetId, + owner: T::AccountId, + delegate: T::AccountId, + destination: T::AccountId, + amount: T::Balance, + }, /// An asset has had its attributes changed by the `Force` origin. - /// \[id\] - AssetStatusChanged(T::AssetId), + AssetStatusChanged { asset_id: T::AssetId }, } #[pallet::error] pub enum Error { /// Account balance must be greater than or equal to the transfer amount. BalanceLow, - /// Balance should be non-zero. - BalanceZero, + /// The account to alter does not exist. + NoAccount, /// The signing account has no permission to do the operation. NoPermission, /// The given asset ID is unknown. @@ -339,8 +515,9 @@ pub mod pallet { BadWitness, /// Minimum balance should be non-zero. MinBalanceZero, - /// No provider reference exists to allow a non-zero balance of a non-self-sufficient - /// asset. + /// Unable to increment the consumer reference counters on the account. Either no provider + /// reference exists to allow a non-zero balance of a non-self-sufficient asset, or the + /// maximum number of consumers has been reached. NoProvider, /// Invalid metadata given. BadMetadata, @@ -348,6 +525,21 @@ pub mod pallet { Unapproved, /// The source account would not survive the transfer and it needs to stay alive. WouldDie, + /// The asset-account already exists. + AlreadyExists, + /// The asset-account doesn't have an associated deposit. + NoDeposit, + /// The operation would result in funds being burned. + WouldBurn, + /// The asset is a live asset and is actively being used. Usually emit for operations such + /// as `start_destroy` which require the asset to be in a destroying state. + LiveAsset, + /// The asset is not live, and likely being destroyed. + AssetNotLive, + /// The asset status is not the expected status. + IncorrectStatus, + /// The asset should be frozen before the given operation. + NotFrozen, } #[pallet::call] @@ -356,7 +548,7 @@ pub mod pallet { /// /// This new asset class has no assets initially and its owner is the origin. /// - /// The origin must be Signed and the sender must have sufficient funds free. + /// The origin must conform to the configured `CreateOrigin` and have sufficient funds free. /// /// Funds of sender are reserved by `AssetDeposit`. /// @@ -371,14 +563,16 @@ pub mod pallet { /// Emits `Created` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - admin: ::Source, + id: T::AssetIdParameter, + admin: AccountIdLookupOf, min_balance: T::Balance, ) -> DispatchResult { - let owner = ensure_signed(origin)?; + let id: T::AssetId = id.into(); + let owner = T::CreateOrigin::ensure_origin(origin, &id)?; let admin = T::Lookup::lookup(admin)?; ensure!(!Asset::::contains_key(id), Error::::InUse); @@ -401,10 +595,10 @@ pub mod pallet { accounts: 0, sufficients: 0, approvals: 0, - is_frozen: false, + status: AssetStatus::Live, }, ); - Self::deposit_event(Event::Created(id, owner, admin)); + Self::deposit_event(Event::Created { asset_id: id, creator: owner, owner: admin }); Ok(()) } @@ -427,106 +621,107 @@ pub mod pallet { /// Emits `ForceCreated` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - owner: ::Source, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, is_sufficient: bool, #[pallet::compact] min_balance: T::Balance, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; - - ensure!(!Asset::::contains_key(id), Error::::InUse); - ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); - - Asset::::insert( - id, - AssetDetails { - owner: owner.clone(), - issuer: owner.clone(), - admin: owner.clone(), - freezer: owner.clone(), - supply: Zero::zero(), - deposit: Zero::zero(), - min_balance, - is_sufficient, - accounts: 0, - sufficients: 0, - approvals: 0, - is_frozen: false, - }, - ); - Self::deposit_event(Event::ForceCreated(id, owner)); - Ok(()) + let id: T::AssetId = id.into(); + Self::do_force_create(id, owner, is_sufficient, min_balance) } - /// Destroy a class of fungible assets. + /// Start the process of destroying a fungible asset class. /// - /// The origin must conform to `ForceOrigin` or must be Signed and the sender must be the - /// owner of the asset `id`. + /// `start_destroy` is the first in a series of extrinsics that should be called, to allow + /// destruction of an asset class. + /// + /// The origin must conform to `ForceOrigin` or must be `Signed` by the asset's `owner`. /// /// - `id`: The identifier of the asset to be destroyed. This must identify an existing - /// asset. - /// - /// Emits `Destroyed` event when successful. - /// - /// NOTE: It can be helpful to first freeze an asset before destroying it so that you - /// can provide accurate witness information and prevent users from manipulating state - /// in a way that can make it harder to destroy. - /// - /// Weight: `O(c + p + a)` where: - /// - `c = (witness.accounts - witness.sufficients)` - /// - `s = witness.sufficients` - /// - `a = witness.approvals` - #[pallet::weight(T::WeightInfo::destroy( - witness.accounts.saturating_sub(witness.sufficients), - witness.sufficients, - witness.approvals, - ))] - pub fn destroy( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - witness: DestroyWitness, - ) -> DispatchResultWithPostInfo { + /// asset. + /// + /// The asset class must be frozen before calling `start_destroy`. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::start_destroy())] + pub fn start_destroy(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { Ok(_) => None, Err(origin) => Some(ensure_signed(origin)?), }; - Asset::::try_mutate_exists(id, |maybe_details| { - let mut details = maybe_details.take().ok_or(Error::::Unknown)?; - if let Some(check_owner) = maybe_check_owner { - ensure!(details.owner == check_owner, Error::::NoPermission); - } - ensure!(details.accounts <= witness.accounts, Error::::BadWitness); - ensure!(details.sufficients <= witness.sufficients, Error::::BadWitness); - ensure!(details.approvals <= witness.approvals, Error::::BadWitness); + let id: T::AssetId = id.into(); + Self::do_start_destroy(id, maybe_check_owner) + } - for (who, v) in Account::::drain_prefix(id) { - Self::dead_account(id, &who, &mut details, v.sufficient); - } - debug_assert_eq!(details.accounts, 0); - debug_assert_eq!(details.sufficients, 0); + /// Destroy all accounts associated with a given asset. + /// + /// `destroy_accounts` should only be called after `start_destroy` has been called, and the + /// asset is in a `Destroying` state. + /// + /// Due to weight restrictions, this function may need to be called multiple times to fully + /// destroy all accounts. It will destroy `RemoveItemsLimit` accounts at a time. + /// + /// - `id`: The identifier of the asset to be destroyed. This must identify an existing + /// asset. + /// + /// Each call emits the `Event::DestroyedAccounts` event. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::destroy_accounts(T::RemoveItemsLimit::get()))] + pub fn destroy_accounts( + origin: OriginFor, + id: T::AssetIdParameter, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let id: T::AssetId = id.into(); + let removed_accounts = Self::do_destroy_accounts(id, T::RemoveItemsLimit::get())?; + Ok(Some(T::WeightInfo::destroy_accounts(removed_accounts)).into()) + } - let metadata = Metadata::::take(&id); - T::Currency::unreserve( - &details.owner, - details.deposit.saturating_add(metadata.deposit), - ); + /// Destroy all approvals associated with a given asset up to the max (T::RemoveItemsLimit). + /// + /// `destroy_approvals` should only be called after `start_destroy` has been called, and the + /// asset is in a `Destroying` state. + /// + /// Due to weight restrictions, this function may need to be called multiple times to fully + /// destroy all approvals. It will destroy `RemoveItemsLimit` approvals at a time. + /// + /// - `id`: The identifier of the asset to be destroyed. This must identify an existing + /// asset. + /// + /// Each call emits the `Event::DestroyedApprovals` event. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::destroy_approvals(T::RemoveItemsLimit::get()))] + pub fn destroy_approvals( + origin: OriginFor, + id: T::AssetIdParameter, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let id: T::AssetId = id.into(); + let removed_approvals = Self::do_destroy_approvals(id, T::RemoveItemsLimit::get())?; + Ok(Some(T::WeightInfo::destroy_approvals(removed_approvals)).into()) + } - for ((owner, _), approval) in Approvals::::drain_prefix((&id,)) { - T::Currency::unreserve(&owner, approval.deposit); - } - Self::deposit_event(Event::Destroyed(id)); - - Ok(Some(T::WeightInfo::destroy( - details.accounts.saturating_sub(details.sufficients), - details.sufficients, - details.approvals, - )) - .into()) - }) + /// Complete destroying asset and unreserve currency. + /// + /// `finish_destroy` should only be called after `start_destroy` has been called, and the + /// asset is in a `Destroying` state. All accounts or approvals should be destroyed before + /// hand. + /// + /// - `id`: The identifier of the asset to be destroyed. This must identify an existing + /// asset. + /// + /// Each successful call emits the `Event::Destroyed` event. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::finish_destroy())] + pub fn finish_destroy(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { + let _ = ensure_signed(origin)?; + let id: T::AssetId = id.into(); + Self::do_finish_destroy(id) } /// Mint assets of a particular class. @@ -541,15 +736,17 @@ pub mod pallet { /// /// Weight: `O(1)` /// Modes: Pre-existing balance of `beneficiary`; Account pre-existence of `beneficiary`. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::mint())] pub fn mint( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - beneficiary: ::Source, + id: T::AssetIdParameter, + beneficiary: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let origin = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; + let id: T::AssetId = id.into(); Self::do_mint(id, &beneficiary, amount, Some(origin))?; Ok(()) } @@ -558,7 +755,7 @@ pub mod pallet { /// /// Origin must be Signed and the sender should be the Manager of the asset `id`. /// - /// Bails with `BalanceZero` if the `who` is already dead. + /// Bails with `NoAccount` if the `who` is already dead. /// /// - `id`: The identifier of the asset to have some amount burned. /// - `who`: The account to be debited from. @@ -569,15 +766,17 @@ pub mod pallet { /// /// Weight: `O(1)` /// Modes: Post-existence of `who`; Pre & post Zombie-status of `who`. + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - who: ::Source, + id: T::AssetIdParameter, + who: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let origin = ensure_signed(origin)?; let who = T::Lookup::lookup(who)?; + let id: T::AssetId = id.into(); let f = DebitFlags { keep_alive: false, best_effort: true }; let _ = Self::do_burn(id, &who, amount, Some(origin), f)?; @@ -602,15 +801,17 @@ pub mod pallet { /// Weight: `O(1)` /// Modes: Pre-existence of `target`; Post-existence of sender; Account pre-existence of /// `target`. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - target: ::Source, + id: T::AssetIdParameter, + target: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(target)?; + let id: T::AssetId = id.into(); let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false }; Self::do_transfer(id, &origin, &dest, amount, None, f).map(|_| ()) @@ -634,15 +835,17 @@ pub mod pallet { /// Weight: `O(1)` /// Modes: Pre-existence of `target`; Post-existence of sender; Account pre-existence of /// `target`. + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::transfer_keep_alive())] pub fn transfer_keep_alive( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - target: ::Source, + id: T::AssetIdParameter, + target: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let source = ensure_signed(origin)?; let dest = T::Lookup::lookup(target)?; + let id: T::AssetId = id.into(); let f = TransferFlags { keep_alive: true, best_effort: false, burn_dust: false }; Self::do_transfer(id, &source, &dest, amount, None, f).map(|_| ()) @@ -667,17 +870,19 @@ pub mod pallet { /// Weight: `O(1)` /// Modes: Pre-existence of `dest`; Post-existence of `source`; Account pre-existence of /// `dest`. + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::force_transfer())] pub fn force_transfer( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - source: ::Source, - dest: ::Source, + id: T::AssetIdParameter, + source: AccountIdLookupOf, + dest: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let origin = ensure_signed(origin)?; let source = T::Lookup::lookup(source)?; let dest = T::Lookup::lookup(dest)?; + let id: T::AssetId = id.into(); let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false }; Self::do_transfer(id, &source, &dest, amount, Some(origin), f).map(|_| ()) @@ -693,22 +898,30 @@ pub mod pallet { /// Emits `Frozen`. /// /// Weight: `O(1)` + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - who: ::Source, + id: T::AssetIdParameter, + who: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; + let id: T::AssetId = id.into(); let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.freezer, Error::::NoPermission); + ensure!( + d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, + Error::::AssetNotLive + ); + ensure!(origin == d.freezer, Error::::NoPermission); let who = T::Lookup::lookup(who)?; - ensure!(Account::::contains_key(id, &who), Error::::BalanceZero); - Account::::mutate(id, &who, |a| a.is_frozen = true); + Account::::try_mutate(id, &who, |maybe_account| -> DispatchResult { + maybe_account.as_mut().ok_or(Error::::NoAccount)?.is_frozen = true; + Ok(()) + })?; - Self::deposit_event(Event::::Frozen(id, who)); + Self::deposit_event(Event::::Frozen { asset_id: id, who }); Ok(()) } @@ -722,22 +935,30 @@ pub mod pallet { /// Emits `Thawed`. /// /// Weight: `O(1)` + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::thaw())] pub fn thaw( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - who: ::Source, + id: T::AssetIdParameter, + who: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; + let id: T::AssetId = id.into(); let details = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &details.admin, Error::::NoPermission); + ensure!( + details.status == AssetStatus::Live || details.status == AssetStatus::Frozen, + Error::::AssetNotLive + ); + ensure!(origin == details.admin, Error::::NoPermission); let who = T::Lookup::lookup(who)?; - ensure!(Account::::contains_key(id, &who), Error::::BalanceZero); - Account::::mutate(id, &who, |a| a.is_frozen = false); + Account::::try_mutate(id, &who, |maybe_account| -> DispatchResult { + maybe_account.as_mut().ok_or(Error::::NoAccount)?.is_frozen = false; + Ok(()) + })?; - Self::deposit_event(Event::::Thawed(id, who)); + Self::deposit_event(Event::::Thawed { asset_id: id, who }); Ok(()) } @@ -750,20 +971,20 @@ pub mod pallet { /// Emits `Frozen`. /// /// Weight: `O(1)` + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::freeze_asset())] - pub fn freeze_asset( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - ) -> DispatchResult { + pub fn freeze_asset(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let origin = ensure_signed(origin)?; + let id: T::AssetId = id.into(); Asset::::try_mutate(id, |maybe_details| { let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &d.freezer, Error::::NoPermission); + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); + ensure!(origin == d.freezer, Error::::NoPermission); - d.is_frozen = true; + d.status = AssetStatus::Frozen; - Self::deposit_event(Event::::AssetFrozen(id)); + Self::deposit_event(Event::::AssetFrozen { asset_id: id }); Ok(()) }) } @@ -777,20 +998,20 @@ pub mod pallet { /// Emits `Thawed`. /// /// Weight: `O(1)` + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::thaw_asset())] - pub fn thaw_asset( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - ) -> DispatchResult { + pub fn thaw_asset(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let origin = ensure_signed(origin)?; + let id: T::AssetId = id.into(); Asset::::try_mutate(id, |maybe_details| { let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &d.admin, Error::::NoPermission); + ensure!(origin == d.admin, Error::::NoPermission); + ensure!(d.status == AssetStatus::Frozen, Error::::NotFrozen); - d.is_frozen = false; + d.status = AssetStatus::Live; - Self::deposit_event(Event::::AssetThawed(id)); + Self::deposit_event(Event::::AssetThawed { asset_id: id }); Ok(()) }) } @@ -805,18 +1026,21 @@ pub mod pallet { /// Emits `OwnerChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - owner: ::Source, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; + let id: T::AssetId = id.into(); Asset::::try_mutate(id, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); + ensure!(details.status == AssetStatus::Live, Error::::LiveAsset); + ensure!(origin == details.owner, Error::::NoPermission); if details.owner == owner { return Ok(()) } @@ -829,7 +1053,7 @@ pub mod pallet { details.owner = owner.clone(); - Self::deposit_event(Event::OwnerChanged(id, owner)); + Self::deposit_event(Event::OwnerChanged { asset_id: id, owner }); Ok(()) }) } @@ -846,28 +1070,31 @@ pub mod pallet { /// Emits `TeamChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - issuer: ::Source, - admin: ::Source, - freezer: ::Source, + id: T::AssetIdParameter, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; let issuer = T::Lookup::lookup(issuer)?; let admin = T::Lookup::lookup(admin)?; let freezer = T::Lookup::lookup(freezer)?; + let id: T::AssetId = id.into(); Asset::::try_mutate(id, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); + ensure!(origin == details.owner, Error::::NoPermission); details.issuer = issuer.clone(); details.admin = admin.clone(); details.freezer = freezer.clone(); - Self::deposit_event(Event::TeamChanged(id, issuer, admin, freezer)); + Self::deposit_event(Event::TeamChanged { asset_id: id, issuer, admin, freezer }); Ok(()) }) } @@ -888,52 +1115,18 @@ pub mod pallet { /// Emits `MetadataSet`. /// /// Weight: `O(1)` + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::set_metadata(name.len() as u32, symbol.len() as u32))] pub fn set_metadata( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, name: Vec, symbol: Vec, decimals: u8, ) -> DispatchResult { let origin = ensure_signed(origin)?; - - let bounded_name: BoundedVec = - name.clone().try_into().map_err(|_| Error::::BadMetadata)?; - let bounded_symbol: BoundedVec = - symbol.clone().try_into().map_err(|_| Error::::BadMetadata)?; - - let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.owner, Error::::NoPermission); - - Metadata::::try_mutate_exists(id, |metadata| { - ensure!( - metadata.as_ref().map_or(true, |m| !m.is_frozen), - Error::::NoPermission - ); - - let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - let new_deposit = T::MetadataDepositPerByte::get() - .saturating_mul(((name.len() + symbol.len()) as u32).into()) - .saturating_add(T::MetadataDepositBase::get()); - - if new_deposit > old_deposit { - T::Currency::reserve(&origin, new_deposit - old_deposit)?; - } else { - T::Currency::unreserve(&origin, old_deposit - new_deposit); - } - - *metadata = Some(AssetMetadata { - deposit: new_deposit, - name: bounded_name, - symbol: bounded_symbol, - decimals, - is_frozen: false, - }); - - Self::deposit_event(Event::MetadataSet(id, name, symbol, decimals, false)); - Ok(()) - }) + let id: T::AssetId = id.into(); + Self::do_set_metadata(id, &origin, name, symbol, decimals) } /// Clear the metadata for an asset. @@ -947,20 +1140,20 @@ pub mod pallet { /// Emits `MetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::clear_metadata())] - pub fn clear_metadata( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - ) -> DispatchResult { + pub fn clear_metadata(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let origin = ensure_signed(origin)?; + let id: T::AssetId = id.into(); let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.owner, Error::::NoPermission); + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); + ensure!(origin == d.owner, Error::::NoPermission); Metadata::::try_mutate_exists(id, |metadata| { let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; T::Currency::unreserve(&d.owner, deposit); - Self::deposit_event(Event::MetadataCleared(id)); + Self::deposit_event(Event::MetadataCleared { asset_id: id }); Ok(()) }) } @@ -979,16 +1172,18 @@ pub mod pallet { /// Emits `MetadataSet`. /// /// Weight: `O(N + S)` where N and S are the length of the name and symbol respectively. + #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::force_set_metadata(name.len() as u32, symbol.len() as u32))] pub fn force_set_metadata( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, name: Vec, symbol: Vec, decimals: u8, is_frozen: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; + let id: T::AssetId = id.into(); let bounded_name: BoundedVec = name.clone().try_into().map_err(|_| Error::::BadMetadata)?; @@ -1007,7 +1202,13 @@ pub mod pallet { is_frozen, }); - Self::deposit_event(Event::MetadataSet(id, name, symbol, decimals, is_frozen)); + Self::deposit_event(Event::MetadataSet { + asset_id: id, + name, + symbol, + decimals, + is_frozen, + }); Ok(()) }) } @@ -1023,18 +1224,20 @@ pub mod pallet { /// Emits `MetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::force_clear_metadata())] pub fn force_clear_metadata( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; + let id: T::AssetId = id.into(); let d = Asset::::get(id).ok_or(Error::::Unknown)?; Metadata::::try_mutate_exists(id, |metadata| { let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; T::Currency::unreserve(&d.owner, deposit); - Self::deposit_event(Event::MetadataCleared(id)); + Self::deposit_event(Event::MetadataCleared { asset_id: id }); Ok(()) }) } @@ -1061,32 +1264,39 @@ pub mod pallet { /// Emits `AssetStatusChanged` with the identity of the asset. /// /// Weight: `O(1)` + #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::force_asset_status())] pub fn force_asset_status( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - owner: ::Source, - issuer: ::Source, - admin: ::Source, - freezer: ::Source, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, #[pallet::compact] min_balance: T::Balance, is_sufficient: bool, is_frozen: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; + let id: T::AssetId = id.into(); Asset::::try_mutate(id, |maybe_asset| { let mut asset = maybe_asset.take().ok_or(Error::::Unknown)?; + ensure!(asset.status != AssetStatus::Destroying, Error::::AssetNotLive); asset.owner = T::Lookup::lookup(owner)?; asset.issuer = T::Lookup::lookup(issuer)?; asset.admin = T::Lookup::lookup(admin)?; asset.freezer = T::Lookup::lookup(freezer)?; asset.min_balance = min_balance; asset.is_sufficient = is_sufficient; - asset.is_frozen = is_frozen; + if is_frozen { + asset.status = AssetStatus::Frozen; + } else { + asset.status = AssetStatus::Live; + } *maybe_asset = Some(asset); - Self::deposit_event(Event::AssetStatusChanged(id)); + Self::deposit_event(Event::AssetStatusChanged { asset_id: id }); Ok(()) }) } @@ -1111,44 +1321,18 @@ pub mod pallet { /// Emits `ApprovedTransfer` on success. /// /// Weight: `O(1)` + #[pallet::call_index(22)] #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - delegate: ::Source, + id: T::AssetIdParameter, + delegate: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let owner = ensure_signed(origin)?; let delegate = T::Lookup::lookup(delegate)?; - - let mut d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(!d.is_frozen, Error::::Frozen); - Approvals::::try_mutate( - (id, &owner, &delegate), - |maybe_approved| -> DispatchResult { - let mut approved = match maybe_approved.take() { - // an approval already exists and is being updated - Some(a) => a, - // a new approval is created - None => { - d.approvals.saturating_inc(); - Default::default() - }, - }; - let deposit_required = T::ApprovalDeposit::get(); - if approved.deposit < deposit_required { - T::Currency::reserve(&owner, deposit_required - approved.deposit)?; - approved.deposit = deposit_required; - } - approved.amount = approved.amount.saturating_add(amount); - *maybe_approved = Some(approved); - Ok(()) - }, - )?; - Asset::::insert(id, d); - Self::deposit_event(Event::ApprovedTransfer(id, owner, delegate, amount)); - - Ok(()) + let id: T::AssetId = id.into(); + Self::do_approve_transfer(id, &owner, &delegate, amount) } /// Cancel all of some asset approved for delegated transfer by a third-party account. @@ -1164,15 +1348,19 @@ pub mod pallet { /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - delegate: ::Source, + id: T::AssetIdParameter, + delegate: AccountIdLookupOf, ) -> DispatchResult { let owner = ensure_signed(origin)?; let delegate = T::Lookup::lookup(delegate)?; + let id: T::AssetId = id.into(); let mut d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); + let approval = Approvals::::take((id, &owner, &delegate)).ok_or(Error::::Unknown)?; T::Currency::unreserve(&owner, approval.deposit); @@ -1180,7 +1368,7 @@ pub mod pallet { d.approvals.saturating_dec(); Asset::::insert(id, d); - Self::deposit_event(Event::ApprovalCancelled(id, owner, delegate)); + Self::deposit_event(Event::ApprovalCancelled { asset_id: id, owner, delegate }); Ok(()) } @@ -1197,19 +1385,22 @@ pub mod pallet { /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::force_cancel_approval())] pub fn force_cancel_approval( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - owner: ::Source, - delegate: ::Source, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + delegate: AccountIdLookupOf, ) -> DispatchResult { + let id: T::AssetId = id.into(); let mut d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); T::ForceOrigin::try_origin(origin) .map(|_| ()) .or_else(|origin| -> DispatchResult { let origin = ensure_signed(origin)?; - ensure!(&origin == &d.admin, Error::::NoPermission); + ensure!(origin == d.admin, Error::::NoPermission); Ok(()) })?; @@ -1222,7 +1413,7 @@ pub mod pallet { d.approvals.saturating_dec(); Asset::::insert(id, d); - Self::deposit_event(Event::ApprovalCancelled(id, owner, delegate)); + Self::deposit_event(Event::ApprovalCancelled { asset_id: id, owner, delegate }); Ok(()) } @@ -1244,44 +1435,55 @@ pub mod pallet { /// Emits `TransferredApproved` on success. /// /// Weight: `O(1)` + #[pallet::call_index(25)] #[pallet::weight(T::WeightInfo::transfer_approved())] pub fn transfer_approved( origin: OriginFor, - #[pallet::compact] id: T::AssetId, - owner: ::Source, - destination: ::Source, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + destination: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let delegate = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; let destination = T::Lookup::lookup(destination)?; + let id: T::AssetId = id.into(); + Self::do_transfer_approved(id, &owner, &delegate, &destination, amount) + } - Approvals::::try_mutate_exists( - (id, &owner, delegate), - |maybe_approved| -> DispatchResult { - let mut approved = maybe_approved.take().ok_or(Error::::Unapproved)?; - let remaining = - approved.amount.checked_sub(&amount).ok_or(Error::::Unapproved)?; - - let f = - TransferFlags { keep_alive: false, best_effort: false, burn_dust: false }; - Self::do_transfer(id, &owner, &destination, amount, None, f)?; - - if remaining.is_zero() { - T::Currency::unreserve(&owner, approved.deposit); - Asset::::mutate(id, |maybe_details| { - if let Some(details) = maybe_details { - details.approvals.saturating_dec(); - } - }); - } else { - approved.amount = remaining; - *maybe_approved = Some(approved); - } - Ok(()) - }, - )?; - Ok(()) + /// Create an asset account for non-provider assets. + /// + /// A deposit will be taken from the signer account. + /// + /// - `origin`: Must be Signed; the signer account must have sufficient funds for a deposit + /// to be taken. + /// - `id`: The identifier of the asset for the account to be created. + /// + /// Emits `Touched` event when successful. + #[pallet::call_index(26)] + #[pallet::weight(T::WeightInfo::mint())] + pub fn touch(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { + let id: T::AssetId = id.into(); + Self::do_touch(id, ensure_signed(origin)?) + } + + /// Return the deposit (if any) of an asset account. + /// + /// The origin must be Signed. + /// + /// - `id`: The identifier of the asset for the account to be created. + /// - `allow_burn`: If `true` then assets may be destroyed in order to complete the refund. + /// + /// Emits `Refunded` event when successful. + #[pallet::call_index(27)] + #[pallet::weight(T::WeightInfo::mint())] + pub fn refund( + origin: OriginFor, + id: T::AssetIdParameter, + allow_burn: bool, + ) -> DispatchResult { + let id: T::AssetId = id.into(); + Self::do_refund(id, ensure_signed(origin)?, allow_burn) } } } diff --git a/frame/assets/src/migration.rs b/frame/assets/src/migration.rs new file mode 100644 index 0000000000000..89f8d39a9049c --- /dev/null +++ b/frame/assets/src/migration.rs @@ -0,0 +1,122 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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 frame_support::{log, traits::OnRuntimeUpgrade}; + +pub mod v1 { + use frame_support::{pallet_prelude::*, weights::Weight}; + + use super::*; + + #[derive(Decode)] + pub struct OldAssetDetails { + pub owner: AccountId, + pub issuer: AccountId, + pub admin: AccountId, + pub freezer: AccountId, + pub supply: Balance, + pub deposit: DepositBalance, + pub min_balance: Balance, + pub is_sufficient: bool, + pub accounts: u32, + pub sufficients: u32, + pub approvals: u32, + pub is_frozen: bool, + } + + impl OldAssetDetails { + fn migrate_to_v1(self) -> AssetDetails { + let status = if self.is_frozen { AssetStatus::Frozen } else { AssetStatus::Live }; + + AssetDetails { + owner: self.owner, + issuer: self.issuer, + admin: self.admin, + freezer: self.freezer, + supply: self.supply, + deposit: self.deposit, + min_balance: self.min_balance, + is_sufficient: self.is_sufficient, + accounts: self.accounts, + sufficients: self.sufficients, + approvals: self.approvals, + status, + } + } + } + + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + fn on_runtime_upgrade() -> Weight { + let current_version = Pallet::::current_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); + if onchain_version == 0 && current_version == 1 { + let mut translated = 0u64; + Asset::::translate::< + OldAssetDetails>, + _, + >(|_key, old_value| { + translated.saturating_inc(); + Some(old_value.migrate_to_v1()) + }); + current_version.put::>(); + log::info!(target: "runtime::assets", "Upgraded {} pools, storage to version {:?}", translated, current_version); + T::DbWeight::get().reads_writes(translated + 1, translated + 1) + } else { + log::info!(target: "runtime::assets", "Migration did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + frame_support::ensure!( + Pallet::::on_chain_storage_version() == 0, + "must upgrade linearly" + ); + let prev_count = Asset::::iter().count(); + Ok((prev_count as u32).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev_count: Vec) -> Result<(), &'static str> { + let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( + "the state parameter should be something that was generated by pre_upgrade", + ); + let post_count = Asset::::iter().count() as u32; + assert_eq!( + prev_count, post_count, + "the asset count before and after the migration should be the same" + ); + + let current_version = Pallet::::current_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); + + frame_support::ensure!(current_version == 1, "must_upgrade"); + assert_eq!( + current_version, onchain_version, + "after migration, the current_version and onchain_version should be the same" + ); + + Asset::::iter().for_each(|(_id, asset)| { + assert!(asset.status == AssetStatus::Live || asset.status == AssetStatus::Frozen, "assets should only be live or frozen. None should be in destroying status, or undefined state") + }); + Ok(()) + } + } +} diff --git a/frame/assets/src/mock.rs b/frame/assets/src/mock.rs index 1b2602792d844..06b6ccf06c57e 100644 --- a/frame/assets/src/mock.rs +++ b/frame/assets/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,10 @@ use super::*; use crate as pallet_assets; -use frame_support::{construct_runtime, parameter_types}; +use frame_support::{ + construct_runtime, parameter_types, + traits::{AsEnsureOriginWithArg, ConstU32, ConstU64, GenesisBuild}, +}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -42,15 +45,12 @@ construct_runtime!( } ); -parameter_types! { - pub const BlockHashCount: u64 = 250; -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -58,8 +58,8 @@ impl frame_system::Config for Test { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; @@ -69,17 +69,14 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 1; + type MaxConsumers = ConstU32<2>; } impl pallet_balances::Config for Test { type Balance = u64; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); type MaxLocks = (); @@ -87,66 +84,96 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; } -parameter_types! { - pub const AssetDeposit: u64 = 1; - pub const ApprovalDeposit: u64 = 1; - pub const StringLimit: u32 = 50; - pub const MetadataDepositBase: u64 = 1; - pub const MetadataDepositPerByte: u64 = 1; -} - impl Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Balance = u64; type AssetId = u32; + type AssetIdParameter = u32; type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; - type AssetDeposit = AssetDeposit; - type MetadataDepositBase = MetadataDepositBase; - type MetadataDepositPerByte = MetadataDepositPerByte; - type ApprovalDeposit = ApprovalDeposit; - type StringLimit = StringLimit; + type AssetDeposit = ConstU64<1>; + type AssetAccountDeposit = ConstU64<10>; + type MetadataDepositBase = ConstU64<1>; + type MetadataDepositPerByte = ConstU64<1>; + type ApprovalDeposit = ConstU64<1>; + type StringLimit = ConstU32<50>; type Freezer = TestFreezer; type WeightInfo = (); type Extra = (); + type RemoveItemsLimit = ConstU32<5>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); } -use std::{cell::RefCell, collections::HashMap}; +use std::collections::HashMap; #[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) enum Hook { +pub enum Hook { Died(u32, u64), } -thread_local! { - static FROZEN: RefCell> = RefCell::new(Default::default()); - static HOOKS: RefCell> = RefCell::new(Default::default()); +parameter_types! { + static Frozen: HashMap<(u32, u64), u64> = Default::default(); + static Hooks: Vec = Default::default(); } pub struct TestFreezer; impl FrozenBalance for TestFreezer { fn frozen_balance(asset: u32, who: &u64) -> Option { - FROZEN.with(|f| f.borrow().get(&(asset, who.clone())).cloned()) + Frozen::get().get(&(asset, *who)).cloned() } fn died(asset: u32, who: &u64) { - HOOKS.with(|h| h.borrow_mut().push(Hook::Died(asset, who.clone()))); + Hooks::mutate(|v| v.push(Hook::Died(asset, *who))); + + // Sanity check: dead accounts have no balance. + assert!(Assets::balance(asset, *who).is_zero()); } } pub(crate) fn set_frozen_balance(asset: u32, who: u64, amount: u64) { - FROZEN.with(|f| f.borrow_mut().insert((asset, who), amount)); + Frozen::mutate(|v| { + v.insert((asset, who), amount); + }); } + pub(crate) fn clear_frozen_balance(asset: u32, who: u64) { - FROZEN.with(|f| f.borrow_mut().remove(&(asset, who))); + Frozen::mutate(|v| { + v.remove(&(asset, who)); + }); } + pub(crate) fn hooks() -> Vec { - HOOKS.with(|h| h.borrow().clone()) + Hooks::get().clone() } -pub(crate) fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); +pub(crate) fn take_hooks() -> Vec { + Hooks::take() +} - let mut ext = sp_io::TestExternalities::new(t); +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let config: pallet_assets::GenesisConfig = pallet_assets::GenesisConfig { + assets: vec![ + // id, owner, is_sufficient, min_balance + (999, 0, true, 1), + ], + metadata: vec![ + // id, name, symbol, decimals + (999, "Token Name".into(), "TOKEN".into(), 10), + ], + accounts: vec![ + // id, account_id, balance + (999, 1, 100), + ], + }; + + config.assimilate_storage(&mut storage).unwrap(); + + let mut ext: sp_io::TestExternalities = storage.into(); + // Clear thread local vars for https://github.com/paritytech/substrate/issues/10479. + ext.execute_with(|| take_hooks()); ext.execute_with(|| System::set_block_number(1)); ext } diff --git a/frame/assets/src/tests.rs b/frame/assets/src/tests.rs index aab534a6e4efc..d5fcece0e91d8 100644 --- a/frame/assets/src/tests.rs +++ b/frame/assets/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,18 +19,136 @@ use super::*; use crate::{mock::*, Error}; -use frame_support::{assert_noop, assert_ok, traits::Currency}; +use frame_support::{ + assert_noop, assert_ok, + traits::{fungibles::InspectEnumerable, Currency}, +}; use pallet_balances::Error as BalancesError; use sp_runtime::{traits::ConvertInto, TokenError}; +fn asset_ids() -> Vec { + let mut s: Vec<_> = Assets::asset_ids().collect(); + s.sort(); + s +} + #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); assert_eq!(Assets::balance(0, 2), 100); + assert_eq!(asset_ids(), vec![0, 999]); + }); +} + +#[test] +fn minting_too_many_insufficient_assets_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 2, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100), TokenError::CannotCreate); + + Balances::make_free_balance_be(&2, 1); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100)); + assert_eq!(asset_ids(), vec![0, 1, 2, 999]); + }); +} + +#[test] +fn minting_insufficient_asset_with_deposit_should_work_when_consumers_exhausted() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 2, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100), TokenError::CannotCreate); + + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 2)); + assert_eq!(Balances::reserved_balance(&1), 10); + + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100)); + }); +} + +#[test] +fn minting_insufficient_assets_with_deposit_without_consumer_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Balances::reserved_balance(&1), 10); + assert_eq!(System::consumers(&1), 0); + }); +} + +#[test] +fn refunding_asset_deposit_with_burn_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, true)); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Assets::balance(1, 0), 0); + }); +} + +#[test] +fn refunding_asset_deposit_with_burn_disallowed_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_noop!(Assets::refund(RuntimeOrigin::signed(1), 0, false), Error::::WouldBurn); + }); +} + +#[test] +fn refunding_asset_deposit_without_burn_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100)); + assert_eq!(Assets::balance(0, 2), 100); + assert_eq!(Assets::balance(0, 1), 0); + assert_eq!(Balances::reserved_balance(&1), 10); + assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, false)); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Assets::balance(1, 0), 0); + }); +} + +/// Refunding reaps an account and calls the `FrozenBalance::died` hook. +#[test] +fn refunding_calls_died_hook() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, true)); + + assert_eq!(Asset::::get(0).unwrap().accounts, 0); + assert_eq!(hooks(), vec![Hook::Died(0, 1)]); + assert_eq!(asset_ids(), vec![0, 999]); }); } @@ -38,21 +156,25 @@ fn basic_minting_should_work() { fn approval_lifecycle_works() { new_test_ext().execute_with(|| { // can't approve non-existent token - assert_noop!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50), Error::::Unknown); + assert_noop!( + Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), + Error::::Unknown + ); // so we create it :) - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Asset::::get(0).unwrap().approvals, 1); assert_eq!(Balances::reserved_balance(&1), 1); - assert_ok!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 40)); + assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 40)); assert_eq!(Asset::::get(0).unwrap().approvals, 1); - assert_ok!(Assets::cancel_approval(Origin::signed(1), 0, 2)); + assert_ok!(Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 2)); assert_eq!(Asset::::get(0).unwrap().approvals, 0); assert_eq!(Assets::balance(0, 1), 60); assert_eq!(Assets::balance(0, 3), 40); assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(asset_ids(), vec![0, 999]); }); } @@ -60,17 +182,20 @@ fn approval_lifecycle_works() { fn transfer_approved_all_funds() { new_test_ext().execute_with(|| { // can't approve non-existent token - assert_noop!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50), Error::::Unknown); + assert_noop!( + Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), + Error::::Unknown + ); // so we create it :) - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Asset::::get(0).unwrap().approvals, 1); assert_eq!(Balances::reserved_balance(&1), 1); // transfer the full amount, which should trigger auto-cleanup - assert_ok!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 50)); + assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 50)); assert_eq!(Asset::::get(0).unwrap().approvals, 0); assert_eq!(Assets::balance(0, 1), 50); assert_eq!(Assets::balance(0, 3), 50); @@ -81,20 +206,20 @@ fn transfer_approved_all_funds() { #[test] fn approval_deposits_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); let e = BalancesError::::InsufficientBalance; - assert_noop!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50), e); + assert_noop!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), e); Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Balances::reserved_balance(&1), 1); - assert_ok!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 50)); + assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 50)); assert_eq!(Balances::reserved_balance(&1), 0); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); - assert_ok!(Assets::cancel_approval(Origin::signed(1), 0, 2)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 2)); assert_eq!(Balances::reserved_balance(&1), 0); }); } @@ -102,72 +227,84 @@ fn approval_deposits_work() { #[test] fn cannot_transfer_more_than_approved() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); let e = Error::::Unapproved; - assert_noop!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 51), e); + assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 51), e); }); } #[test] fn cannot_transfer_more_than_exists() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 101)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 101)); let e = Error::::BalanceLow; - assert_noop!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 101), e); + assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 101), e); }); } #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Asset::::get(0).unwrap().approvals, 1); - assert_noop!(Assets::cancel_approval(Origin::signed(1), 1, 2), Error::::Unknown); - assert_noop!(Assets::cancel_approval(Origin::signed(2), 0, 2), Error::::Unknown); - assert_noop!(Assets::cancel_approval(Origin::signed(1), 0, 3), Error::::Unknown); + assert_noop!( + Assets::cancel_approval(RuntimeOrigin::signed(1), 1, 2), + Error::::Unknown + ); + assert_noop!( + Assets::cancel_approval(RuntimeOrigin::signed(2), 0, 2), + Error::::Unknown + ); + assert_noop!( + Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 3), + Error::::Unknown + ); assert_eq!(Asset::::get(0).unwrap().approvals, 1); - assert_ok!(Assets::cancel_approval(Origin::signed(1), 0, 2)); + assert_ok!(Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 2)); assert_eq!(Asset::::get(0).unwrap().approvals, 0); - assert_noop!(Assets::cancel_approval(Origin::signed(1), 0, 2), Error::::Unknown); + assert_noop!( + Assets::cancel_approval(RuntimeOrigin::signed(1), 0, 2), + Error::::Unknown + ); }); } #[test] fn force_cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Asset::::get(0).unwrap().approvals, 1); let e = Error::::NoPermission; - assert_noop!(Assets::force_cancel_approval(Origin::signed(2), 0, 1, 2), e); + assert_noop!(Assets::force_cancel_approval(RuntimeOrigin::signed(2), 0, 1, 2), e); assert_noop!( - Assets::force_cancel_approval(Origin::signed(1), 1, 1, 2), + Assets::force_cancel_approval(RuntimeOrigin::signed(1), 1, 1, 2), Error::::Unknown ); assert_noop!( - Assets::force_cancel_approval(Origin::signed(1), 0, 2, 2), + Assets::force_cancel_approval(RuntimeOrigin::signed(1), 0, 2, 2), Error::::Unknown ); assert_noop!( - Assets::force_cancel_approval(Origin::signed(1), 0, 1, 3), + Assets::force_cancel_approval(RuntimeOrigin::signed(1), 0, 1, 3), Error::::Unknown ); assert_eq!(Asset::::get(0).unwrap().approvals, 1); - assert_ok!(Assets::force_cancel_approval(Origin::signed(1), 0, 1, 2)); + assert_ok!(Assets::force_cancel_approval(RuntimeOrigin::signed(1), 0, 1, 2)); assert_eq!(Asset::::get(0).unwrap().approvals, 0); assert_noop!( - Assets::force_cancel_approval(Origin::signed(1), 0, 1, 2), + Assets::force_cancel_approval(RuntimeOrigin::signed(1), 0, 1, 2), Error::::Unknown ); }); @@ -177,42 +314,50 @@ fn force_cancel_approval_works() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::create(Origin::signed(1), 0, 1, 1)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); assert_eq!(Balances::reserved_balance(&1), 1); assert!(Asset::::contains_key(0)); - assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0], vec![0], 12)); + assert_ok!(Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0], vec![0], 12)); assert_eq!(Balances::reserved_balance(&1), 4); assert!(Metadata::::contains_key(0)); Balances::make_free_balance_be(&10, 100); - assert_ok!(Assets::mint(Origin::signed(1), 0, 10, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100)); Balances::make_free_balance_be(&20, 100); - assert_ok!(Assets::mint(Origin::signed(1), 0, 20, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 20, 100)); assert_eq!(Account::::iter_prefix(0).count(), 2); - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::destroy(Origin::signed(1), 0, w)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert_eq!(Balances::reserved_balance(&1), 0); assert!(!Asset::::contains_key(0)); assert!(!Metadata::::contains_key(0)); assert_eq!(Account::::iter_prefix(0).count(), 0); - assert_ok!(Assets::create(Origin::signed(1), 0, 1, 1)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); assert_eq!(Balances::reserved_balance(&1), 1); assert!(Asset::::contains_key(0)); - assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0], vec![0], 12)); + assert_ok!(Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0], vec![0], 12)); assert_eq!(Balances::reserved_balance(&1), 4); assert!(Metadata::::contains_key(0)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 10, 100)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 20, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 20, 100)); assert_eq!(Account::::iter_prefix(0).count(), 2); - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::destroy(Origin::root(), 0, w)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert_eq!(Balances::reserved_balance(&1), 0); assert!(!Asset::::contains_key(0)); @@ -221,113 +366,173 @@ fn lifecycle_should_work() { }); } -#[test] -fn destroy_with_bad_witness_should_not_work() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - let mut w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::mint(Origin::signed(1), 0, 10, 100)); - // witness too low - assert_noop!(Assets::destroy(Origin::signed(1), 0, w), Error::::BadWitness); - // witness too high is okay though - w.accounts += 2; - w.sufficients += 2; - assert_ok!(Assets::destroy(Origin::signed(1), 0, w)); - }); -} - #[test] fn destroy_should_refund_approvals() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 10, 100)); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 3, 50)); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 4, 50)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 3, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 4, 50)); assert_eq!(Balances::reserved_balance(&1), 3); + assert_eq!(asset_ids(), vec![0, 999]); + + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::destroy(Origin::signed(1), 0, w)); assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(asset_ids(), vec![999]); // all approvals are removed assert!(Approvals::::iter().count().is_zero()) }); } +#[test] +fn partial_destroy_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 3, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 4, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 5, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 6, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 7, 10)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + // Asset is in use, as all the accounts have not yet been destroyed. + // We need to call destroy_accounts or destroy_approvals again until asset is completely + // cleaned up. + assert_noop!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0), Error::::InUse); + + System::assert_has_event(RuntimeEvent::Assets(crate::Event::AccountsDestroyed { + asset_id: 0, + accounts_destroyed: 5, + accounts_remaining: 2, + })); + System::assert_has_event(RuntimeEvent::Assets(crate::Event::ApprovalsDestroyed { + asset_id: 0, + approvals_destroyed: 0, + approvals_remaining: 0, + })); + // Partially destroyed Asset should continue to exist + assert!(Asset::::contains_key(0)); + + // Second call to destroy on PartiallyDestroyed asset + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + System::assert_has_event(RuntimeEvent::Assets(crate::Event::AccountsDestroyed { + asset_id: 0, + accounts_destroyed: 2, + accounts_remaining: 0, + })); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + + System::assert_has_event(RuntimeEvent::Assets(crate::Event::Destroyed { asset_id: 0 })); + + // Destroyed Asset should not exist + assert!(!Asset::::contains_key(0)); + }) +} + #[test] fn non_providing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); Balances::make_free_balance_be(&0, 100); - assert_ok!(Assets::mint(Origin::signed(1), 0, 0, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 0, 100)); // Cannot mint into account 2 since it doesn't (yet) exist... - assert_noop!(Assets::mint(Origin::signed(1), 0, 1, 100), TokenError::CannotCreate); + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); // ...or transfer... - assert_noop!(Assets::transfer(Origin::signed(0), 0, 1, 50), TokenError::CannotCreate); + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(0), 0, 1, 50), + TokenError::CannotCreate + ); // ...or force-transfer assert_noop!( - Assets::force_transfer(Origin::signed(1), 0, 0, 1, 50), + Assets::force_transfer(RuntimeOrigin::signed(1), 0, 0, 1, 50), TokenError::CannotCreate ); Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::transfer(Origin::signed(0), 0, 1, 25)); - assert_ok!(Assets::force_transfer(Origin::signed(1), 0, 0, 2, 25)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(0), 0, 1, 25)); + assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 0, 2, 25)); + assert_eq!(asset_ids(), vec![0, 999]); }); } #[test] fn min_balance_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Asset::::get(0).unwrap().accounts, 1); // Cannot create a new account with a balance that is below minimum... - assert_noop!(Assets::mint(Origin::signed(1), 0, 2, 9), TokenError::BelowMinimum); - assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 9), TokenError::BelowMinimum); + assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 9), TokenError::BelowMinimum); + assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 9), TokenError::BelowMinimum); assert_noop!( - Assets::force_transfer(Origin::signed(1), 0, 1, 2, 9), + Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 9), TokenError::BelowMinimum ); // When deducting from an account to below minimum, it should be reaped. - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 91)); - assert!(Assets::balance(0, 1).is_zero()); + // Death by `transfer`. + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 91)); + assert!(Assets::maybe_balance(0, 1).is_none()); assert_eq!(Assets::balance(0, 2), 100); assert_eq!(Asset::::get(0).unwrap().accounts, 1); + assert_eq!(take_hooks(), vec![Hook::Died(0, 1)]); - assert_ok!(Assets::force_transfer(Origin::signed(1), 0, 2, 1, 91)); - assert!(Assets::balance(0, 2).is_zero()); + // Death by `force_transfer`. + assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 2, 1, 91)); + assert!(Assets::maybe_balance(0, 2).is_none()); assert_eq!(Assets::balance(0, 1), 100); assert_eq!(Asset::::get(0).unwrap().accounts, 1); + assert_eq!(take_hooks(), vec![Hook::Died(0, 2)]); - assert_ok!(Assets::burn(Origin::signed(1), 0, 1, 91)); - assert!(Assets::balance(0, 1).is_zero()); + // Death by `burn`. + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, 91)); + assert!(Assets::maybe_balance(0, 1).is_none()); assert_eq!(Asset::::get(0).unwrap().accounts, 0); + assert_eq!(take_hooks(), vec![Hook::Died(0, 1)]); + + // Death by `transfer_approved`. + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 1); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 100)); + assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 91)); + assert_eq!(take_hooks(), vec![Hook::Died(0, 1)]); }); } #[test] fn querying_total_supply_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Assets::balance(0, 1), 50); assert_eq!(Assets::balance(0, 2), 50); - assert_ok!(Assets::transfer(Origin::signed(2), 0, 3, 31)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 3, 31)); assert_eq!(Assets::balance(0, 1), 50); assert_eq!(Assets::balance(0, 2), 19); assert_eq!(Assets::balance(0, 3), 31); - assert_ok!(Assets::burn(Origin::signed(1), 0, 3, u64::MAX)); + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 3, u64::MAX)); assert_eq!(Assets::total_supply(0), 69); }); } @@ -335,10 +540,10 @@ fn querying_total_supply_should_work() { #[test] fn transferring_amount_below_available_balance_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Assets::balance(0, 1), 50); assert_eq!(Assets::balance(0, 2), 50); }); @@ -347,42 +552,47 @@ fn transferring_amount_below_available_balance_should_work() { #[test] fn transferring_enough_to_kill_source_when_keep_alive_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_noop!( - Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 91), + Assets::transfer_keep_alive(RuntimeOrigin::signed(1), 0, 2, 91), Error::::BalanceLow ); - assert_ok!(Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 90)); + assert_ok!(Assets::transfer_keep_alive(RuntimeOrigin::signed(1), 0, 2, 90)); assert_eq!(Assets::balance(0, 1), 10); assert_eq!(Assets::balance(0, 2), 90); + assert!(hooks().is_empty()); + assert_eq!(asset_ids(), vec![0, 999]); }); } #[test] fn transferring_frozen_user_should_not_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::freeze(Origin::signed(1), 0, 1)); - assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 50), Error::::Frozen); - assert_ok!(Assets::thaw(Origin::signed(1), 0, 1)); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 1)); + assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::Frozen); + assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); }); } #[test] fn transferring_frozen_asset_should_not_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::freeze_asset(Origin::signed(1), 0)); - assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 50), Error::::Frozen); - assert_ok!(Assets::thaw_asset(Origin::signed(1), 0)); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), + Error::::AssetNotLive + ); + assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); }); } @@ -390,36 +600,50 @@ fn transferring_frozen_asset_should_not_work() { fn approve_transfer_frozen_asset_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::freeze_asset(Origin::signed(1), 0)); - assert_noop!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50), Error::::Frozen); - assert_ok!(Assets::thaw_asset(Origin::signed(1), 0)); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_noop!( + Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), + Error::::AssetNotLive + ); + assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); }); } #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_noop!( - Assets::transfer_ownership(Origin::signed(2), 0, 2), + Assets::transfer_ownership(RuntimeOrigin::signed(2), 0, 2), Error::::NoPermission ); - assert_noop!(Assets::set_team(Origin::signed(2), 0, 2, 2, 2), Error::::NoPermission); - assert_noop!(Assets::freeze(Origin::signed(2), 0, 1), Error::::NoPermission); - assert_noop!(Assets::thaw(Origin::signed(2), 0, 2), Error::::NoPermission); - assert_noop!(Assets::mint(Origin::signed(2), 0, 2, 100), Error::::NoPermission); - assert_noop!(Assets::burn(Origin::signed(2), 0, 1, 100), Error::::NoPermission); assert_noop!( - Assets::force_transfer(Origin::signed(2), 0, 1, 2, 100), + Assets::set_team(RuntimeOrigin::signed(2), 0, 2, 2, 2), + Error::::NoPermission + ); + assert_noop!(Assets::freeze(RuntimeOrigin::signed(2), 0, 1), Error::::NoPermission); + assert_noop!(Assets::thaw(RuntimeOrigin::signed(2), 0, 2), Error::::NoPermission); + assert_noop!( + Assets::mint(RuntimeOrigin::signed(2), 0, 2, 100), + Error::::NoPermission + ); + assert_noop!( + Assets::burn(RuntimeOrigin::signed(2), 0, 1, 100), + Error::::NoPermission + ); + assert_noop!( + Assets::force_transfer(RuntimeOrigin::signed(2), 0, 1, 2, 100), + Error::::NoPermission + ); + assert_noop!( + Assets::start_destroy(RuntimeOrigin::signed(2), 0), Error::::NoPermission ); - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_noop!(Assets::destroy(Origin::signed(2), 0, w), Error::::NoPermission); }); } @@ -428,22 +652,29 @@ fn transfer_owner_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::create(Origin::signed(1), 0, 1, 1)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_eq!(asset_ids(), vec![0, 999]); assert_eq!(Balances::reserved_balance(&1), 1); - assert_ok!(Assets::transfer_ownership(Origin::signed(1), 0, 2)); + assert_ok!(Assets::transfer_ownership(RuntimeOrigin::signed(1), 0, 2)); assert_eq!(Balances::reserved_balance(&2), 1); assert_eq!(Balances::reserved_balance(&1), 0); assert_noop!( - Assets::transfer_ownership(Origin::signed(1), 0, 1), + Assets::transfer_ownership(RuntimeOrigin::signed(1), 0, 1), Error::::NoPermission ); // Set metadata now and make sure that deposit gets transferred back. - assert_ok!(Assets::set_metadata(Origin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12)); - assert_ok!(Assets::transfer_ownership(Origin::signed(2), 0, 1)); + assert_ok!(Assets::set_metadata( + RuntimeOrigin::signed(2), + 0, + vec![0u8; 10], + vec![0u8; 10], + 12 + )); + assert_ok!(Assets::transfer_ownership(RuntimeOrigin::signed(2), 0, 1)); assert_eq!(Balances::reserved_balance(&1), 22); assert_eq!(Balances::reserved_balance(&2), 0); }); @@ -452,27 +683,27 @@ fn transfer_owner_should_work() { #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::set_team(Origin::signed(1), 0, 2, 3, 4)); - - assert_ok!(Assets::mint(Origin::signed(2), 0, 2, 100)); - assert_ok!(Assets::freeze(Origin::signed(4), 0, 2)); - assert_ok!(Assets::thaw(Origin::signed(3), 0, 2)); - assert_ok!(Assets::force_transfer(Origin::signed(3), 0, 2, 3, 100)); - assert_ok!(Assets::burn(Origin::signed(3), 0, 3, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); + + assert_ok!(Assets::mint(RuntimeOrigin::signed(2), 0, 2, 100)); + assert_ok!(Assets::freeze(RuntimeOrigin::signed(4), 0, 2)); + assert_ok!(Assets::thaw(RuntimeOrigin::signed(3), 0, 2)); + assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(3), 0, 2, 3, 100)); + assert_ok!(Assets::burn(RuntimeOrigin::signed(3), 0, 3, 100)); }); } #[test] fn transferring_to_frozen_account_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_eq!(Assets::balance(0, 2), 100); - assert_ok!(Assets::freeze(Origin::signed(1), 0, 2)); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Assets::balance(0, 2), 150); }); } @@ -480,47 +711,57 @@ fn transferring_to_frozen_account_should_work() { #[test] fn transferring_amount_more_than_available_balance_should_not_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Assets::balance(0, 1), 50); assert_eq!(Assets::balance(0, 2), 50); - assert_ok!(Assets::burn(Origin::signed(1), 0, 1, u64::MAX)); + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, u64::MAX)); assert_eq!(Assets::balance(0, 1), 0); - assert_noop!(Assets::transfer(Origin::signed(1), 0, 1, 50), Error::::BalanceLow); - assert_noop!(Assets::transfer(Origin::signed(2), 0, 1, 51), Error::::BalanceLow); + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(1), 0, 1, 50), + Error::::NoAccount + ); + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 51), + Error::::BalanceLow + ); }); } #[test] fn transferring_less_than_one_unit_is_fine() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 0)); - System::assert_last_event(mock::Event::Assets(crate::Event::Transferred(0, 1, 2, 0))); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 0)); + // `ForceCreated` and `Issued` but no `Transferred` event. + assert_eq!(System::events().len(), 2); }); } #[test] fn transferring_more_units_than_total_supply_should_not_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 101), Error::::BalanceLow); + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 101), + Error::::BalanceLow + ); }); } #[test] fn burning_asset_balance_with_positive_balance_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); - assert_ok!(Assets::burn(Origin::signed(1), 0, 1, u64::MAX)); + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, u64::MAX)); assert_eq!(Assets::balance(0, 1), 0); }); } @@ -528,10 +769,13 @@ fn burning_asset_balance_with_positive_balance_should_work() { #[test] fn burning_asset_balance_with_zero_balance_does_nothing() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 2), 0); - assert_ok!(Assets::burn(Origin::signed(1), 0, 2, u64::MAX)); + assert_noop!( + Assets::burn(RuntimeOrigin::signed(1), 0, 2, u64::MAX), + Error::::NoAccount + ); assert_eq!(Assets::balance(0, 2), 0); assert_eq!(Assets::total_supply(0), 100); }); @@ -542,85 +786,142 @@ fn set_metadata_should_work() { new_test_ext().execute_with(|| { // Cannot add metadata to unknown asset assert_noop!( - Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12), + Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12), Error::::Unknown, ); - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); // Cannot add metadata to unowned asset assert_noop!( - Assets::set_metadata(Origin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12), + Assets::set_metadata(RuntimeOrigin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12), Error::::NoPermission, ); // Cannot add oversized metadata assert_noop!( - Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 100], vec![0u8; 10], 12), + Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0u8; 100], vec![0u8; 10], 12), Error::::BadMetadata, ); assert_noop!( - Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 100], 12), + Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0u8; 10], vec![0u8; 100], 12), Error::::BadMetadata, ); // Successfully add metadata and take deposit Balances::make_free_balance_be(&1, 30); - assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12)); + assert_ok!(Assets::set_metadata( + RuntimeOrigin::signed(1), + 0, + vec![0u8; 10], + vec![0u8; 10], + 12 + )); assert_eq!(Balances::free_balance(&1), 9); // Update deposit - assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 5], 12)); + assert_ok!(Assets::set_metadata( + RuntimeOrigin::signed(1), + 0, + vec![0u8; 10], + vec![0u8; 5], + 12 + )); assert_eq!(Balances::free_balance(&1), 14); - assert_ok!(Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 15], 12)); + assert_ok!(Assets::set_metadata( + RuntimeOrigin::signed(1), + 0, + vec![0u8; 10], + vec![0u8; 15], + 12 + )); assert_eq!(Balances::free_balance(&1), 4); // Cannot over-reserve assert_noop!( - Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 20], vec![0u8; 20], 12), + Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0u8; 20], vec![0u8; 20], 12), BalancesError::::InsufficientBalance, ); // Clear Metadata assert!(Metadata::::contains_key(0)); - assert_noop!(Assets::clear_metadata(Origin::signed(2), 0), Error::::NoPermission); - assert_noop!(Assets::clear_metadata(Origin::signed(1), 1), Error::::Unknown); - assert_ok!(Assets::clear_metadata(Origin::signed(1), 0)); + assert_noop!( + Assets::clear_metadata(RuntimeOrigin::signed(2), 0), + Error::::NoPermission + ); + assert_noop!(Assets::clear_metadata(RuntimeOrigin::signed(1), 1), Error::::Unknown); + assert_ok!(Assets::clear_metadata(RuntimeOrigin::signed(1), 0)); assert!(!Metadata::::contains_key(0)); }); } +/// Destroying an asset calls the `FrozenBalance::died` hooks of all accounts. +#[test] +fn destroy_accounts_calls_died_hooks() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); + // Create account 1 and 2. + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); + // Destroy the accounts. + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + + // Accounts 1 and 2 died. + assert_eq!(hooks(), vec![Hook::Died(0, 1), Hook::Died(0, 2)]); + }) +} + +/// Destroying an asset calls the `FrozenBalance::died` hooks of all accounts. +#[test] +fn finish_destroy_asset_destroys_asset() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); + // Destroy the accounts. + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + + // Asset is gone + assert!(Asset::::get(0).is_none()); + }) +} + #[test] fn freezer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); // freeze 50 of it. set_frozen_balance(0, 1, 50); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 20)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 20)); // cannot transfer another 21 away as this would take the non-frozen balance (30) to below // the minimum balance (10). - assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 21), Error::::BalanceLow); + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 21), + Error::::BalanceLow + ); // create an approved transfer... Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::approve_transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); let e = Error::::BalanceLow; // ...but that wont work either: - assert_noop!(Assets::transfer_approved(Origin::signed(2), 0, 1, 2, 21), e); + assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 2, 21), e); // a force transfer won't work also. let e = Error::::BalanceLow; - assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 21), e); + assert_noop!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 21), e); // reduce it to only 49 frozen... set_frozen_balance(0, 1, 49); // ...and it's all good: - assert_ok!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 21)); + assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, 21)); // and if we clear it, we can remove the account completely. clear_frozen_balance(0, 1); - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(hooks(), vec![Hook::Died(0, 1)]); }); } @@ -630,7 +931,7 @@ fn imbalances_should_work() { use frame_support::traits::tokens::fungibles::Balanced; new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); let imb = Assets::issue(0, 100); assert_eq!(Assets::total_supply(0), 100); @@ -653,9 +954,9 @@ fn imbalances_should_work() { fn force_metadata_should_work() { new_test_ext().execute_with(|| { // force set metadata works - assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); assert_ok!(Assets::force_set_metadata( - Origin::root(), + RuntimeOrigin::root(), 0, vec![0u8; 10], vec![0u8; 10], @@ -667,7 +968,7 @@ fn force_metadata_should_work() { // overwrites existing metadata let asset_original_metadata = Metadata::::get(0); assert_ok!(Assets::force_set_metadata( - Origin::root(), + RuntimeOrigin::root(), 0, vec![1u8; 10], vec![1u8; 10], @@ -678,15 +979,22 @@ fn force_metadata_should_work() { // attempt to set metadata for non-existent asset class assert_noop!( - Assets::force_set_metadata(Origin::root(), 1, vec![0u8; 10], vec![0u8; 10], 8, false), + Assets::force_set_metadata( + RuntimeOrigin::root(), + 1, + vec![0u8; 10], + vec![0u8; 10], + 8, + false + ), Error::::Unknown ); // string length limit check - let limit = StringLimit::get() as usize; + let limit = 50usize; assert_noop!( Assets::force_set_metadata( - Origin::root(), + RuntimeOrigin::root(), 0, vec![0u8; limit + 1], vec![0u8; 10], @@ -697,7 +1005,7 @@ fn force_metadata_should_work() { ); assert_noop!( Assets::force_set_metadata( - Origin::root(), + RuntimeOrigin::root(), 0, vec![0u8; 10], vec![0u8; limit + 1], @@ -709,11 +1017,14 @@ fn force_metadata_should_work() { // force clear metadata works assert!(Metadata::::contains_key(0)); - assert_ok!(Assets::force_clear_metadata(Origin::root(), 0)); + assert_ok!(Assets::force_clear_metadata(RuntimeOrigin::root(), 0)); assert!(!Metadata::::contains_key(0)); // Error handles clearing non-existent asset class - assert_noop!(Assets::force_clear_metadata(Origin::root(), 1), Error::::Unknown); + assert_noop!( + Assets::force_clear_metadata(RuntimeOrigin::root(), 1), + Error::::Unknown + ); }); } @@ -722,34 +1033,57 @@ fn force_asset_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 10); Balances::make_free_balance_be(&2, 10); - assert_ok!(Assets::create(Origin::signed(1), 0, 1, 30)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 50)); - assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 150)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 30)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 50)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 150)); // force asset status to change min_balance > balance - assert_ok!(Assets::force_asset_status(Origin::root(), 0, 1, 1, 1, 1, 100, true, false)); + assert_ok!(Assets::force_asset_status( + RuntimeOrigin::root(), + 0, + 1, + 1, + 1, + 1, + 100, + true, + false + )); assert_eq!(Assets::balance(0, 1), 50); // account can recieve assets for balance < min_balance - assert_ok!(Assets::transfer(Origin::signed(2), 0, 1, 1)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 1)); assert_eq!(Assets::balance(0, 1), 51); // account on outbound transfer will cleanup for balance < min_balance - assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 1)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 1)); assert_eq!(Assets::balance(0, 1), 0); // won't create new account with balance below min_balance - assert_noop!(Assets::transfer(Origin::signed(2), 0, 3, 50), TokenError::BelowMinimum); + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(2), 0, 3, 50), + TokenError::BelowMinimum + ); // force asset status will not execute for non-existent class assert_noop!( - Assets::force_asset_status(Origin::root(), 1, 1, 1, 1, 1, 90, true, false), + Assets::force_asset_status(RuntimeOrigin::root(), 1, 1, 1, 1, 1, 90, true, false), Error::::Unknown ); // account drains to completion when funds dip below min_balance - assert_ok!(Assets::force_asset_status(Origin::root(), 0, 1, 1, 1, 1, 110, true, false)); - assert_ok!(Assets::transfer(Origin::signed(2), 0, 1, 110)); + assert_ok!(Assets::force_asset_status( + RuntimeOrigin::root(), + 0, + 1, + 1, + 1, + 1, + 110, + true, + false + )); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 110)); assert_eq!(Assets::balance(0, 1), 200); assert_eq!(Assets::balance(0, 2), 0); assert_eq!(Assets::total_supply(0), 200); @@ -762,10 +1096,10 @@ fn balance_conversion_should_work() { use frame_support::traits::tokens::BalanceConversion; let id = 42; - assert_ok!(Assets::force_create(Origin::root(), id, 1, true, 10)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), id, 1, true, 10)); let not_sufficient = 23; - assert_ok!(Assets::force_create(Origin::root(), not_sufficient, 1, false, 10)); - + assert_ok!(Assets::force_create(RuntimeOrigin::root(), not_sufficient, 1, false, 10)); + assert_eq!(asset_ids(), vec![23, 42, 999]); assert_eq!( BalanceToAssetBalance::::to_asset_balance(100, 1234), Err(ConversionError::AssetMissing) @@ -784,3 +1118,79 @@ fn balance_conversion_should_work() { ); }); } + +#[test] +fn assets_from_genesis_should_exist() { + new_test_ext().execute_with(|| { + assert_eq!(asset_ids(), vec![999]); + assert!(Metadata::::contains_key(999)); + assert_eq!(Assets::balance(999, 1), 100); + assert_eq!(Assets::total_supply(999), 100); + }); +} + +#[test] +fn querying_name_symbol_and_decimals_should_work() { + new_test_ext().execute_with(|| { + use frame_support::traits::tokens::fungibles::metadata::Inspect; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_set_metadata( + RuntimeOrigin::root(), + 0, + vec![0u8; 10], + vec![1u8; 10], + 12, + false + )); + assert_eq!(Assets::name(0), vec![0u8; 10]); + assert_eq!(Assets::symbol(0), vec![1u8; 10]); + assert_eq!(Assets::decimals(0), 12); + }); +} + +#[test] +fn querying_allowance_should_work() { + new_test_ext().execute_with(|| { + use frame_support::traits::tokens::fungibles::approvals::{Inspect, Mutate}; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + Balances::make_free_balance_be(&1, 1); + assert_ok!(Assets::approve(0, &1, &2, 50)); + assert_eq!(Assets::allowance(0, &1, &2), 50); + // Transfer asset 0, from owner 1 and delegate 2 to destination 3 + assert_ok!(Assets::transfer_from(0, &1, &2, &3, 50)); + assert_eq!(Assets::allowance(0, &1, &2), 0); + }); +} + +#[test] +fn transfer_large_asset() { + new_test_ext().execute_with(|| { + let amount = u64::pow(2, 63) + 2; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, amount)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, amount - 1)); + }) +} + +#[test] +fn querying_roles_should_work() { + new_test_ext().execute_with(|| { + use frame_support::traits::tokens::fungibles::roles::Inspect; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::set_team( + RuntimeOrigin::signed(1), + 0, + // Issuer + 2, + // Admin + 3, + // Freezer + 4, + )); + assert_eq!(Assets::owner(0), Some(1)); + assert_eq!(Assets::issuer(0), Some(2)); + assert_eq!(Assets::admin(0), Some(3)); + assert_eq!(Assets::freezer(0), Some(4)); + }); +} diff --git a/frame/assets/src/types.rs b/frame/assets/src/types.rs index bc2edce848a64..557af6bd3f488 100644 --- a/frame/assets/src/types.rs +++ b/frame/assets/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,14 +18,29 @@ //! Various basic types for use in the assets pallet. use super::*; -use frame_support::pallet_prelude::*; -use scale_info::TypeInfo; - -use frame_support::traits::{fungible, tokens::BalanceConversion}; +use frame_support::{ + pallet_prelude::*, + traits::{fungible, tokens::BalanceConversion}, +}; use sp_runtime::{traits::Convert, FixedPointNumber, FixedPointOperand, FixedU128}; pub(super) type DepositBalanceOf = <>::Currency as Currency<::AccountId>>::Balance; +pub(super) type AssetAccountOf = + AssetAccount<>::Balance, DepositBalanceOf, >::Extra>; + +/// AssetStatus holds the current state of the asset. It could either be Live and available for use, +/// or in a Destroying state. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub(super) enum AssetStatus { + /// The asset is active and able to be used. + Live, + /// Whether the asset is frozen for non-admin transfers. + Frozen, + /// The asset is currently being destroyed, and all actions are no longer permitted on the + /// asset. Once set to `Destroying`, the asset can never transition back to a `Live` state. + Destroying, +} #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct AssetDetails { @@ -52,18 +67,8 @@ pub struct AssetDetails { pub(super) sufficients: u32, /// The total number of approvals. pub(super) approvals: u32, - /// Whether the asset is frozen for non-admin transfers. - pub(super) is_frozen: bool, -} - -impl AssetDetails { - pub fn destroy_witness(&self) -> DestroyWitness { - DestroyWitness { - accounts: self.accounts, - sufficients: self.sufficients, - approvals: self.approvals, - } - } + /// The status of the asset + pub(super) status: AssetStatus, } /// Data concerning an approval. @@ -76,14 +81,47 @@ pub struct Approval { pub(super) deposit: DepositBalance, } -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)] -pub struct AssetBalance { +#[test] +fn ensure_bool_decodes_to_consumer_or_sufficient() { + assert_eq!(false.encode(), ExistenceReason::<()>::Consumer.encode()); + assert_eq!(true.encode(), ExistenceReason::<()>::Sufficient.encode()); +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub enum ExistenceReason { + #[codec(index = 0)] + Consumer, + #[codec(index = 1)] + Sufficient, + #[codec(index = 2)] + DepositHeld(Balance), + #[codec(index = 3)] + DepositRefunded, +} + +impl ExistenceReason { + pub(crate) fn take_deposit(&mut self) -> Option { + if !matches!(self, ExistenceReason::DepositHeld(_)) { + return None + } + if let ExistenceReason::DepositHeld(deposit) = + sp_std::mem::replace(self, ExistenceReason::DepositRefunded) + { + Some(deposit) + } else { + None + } + } +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct AssetAccount { /// The balance. pub(super) balance: Balance, /// Whether the account is frozen. pub(super) is_frozen: bool, - /// `true` if this balance gave the account a self-sufficient reference. - pub(super) sufficient: bool, + /// The reason for the existence of the account. + pub(super) reason: ExistenceReason, /// Additional "sidecar" data, in case some other pallet wants to use this storage item. pub(super) extra: Extra, } @@ -104,38 +142,28 @@ pub struct AssetMetadata { pub(super) is_frozen: bool, } -/// Witness data for the destroy transactions. -#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct DestroyWitness { - /// The number of accounts holding the asset. - #[codec(compact)] - pub(super) accounts: u32, - /// The number of accounts holding the asset with a self-sufficient reference. - #[codec(compact)] - pub(super) sufficients: u32, - /// The number of transfer-approvals of the asset. - #[codec(compact)] - pub(super) approvals: u32, -} - /// Trait for allowing a minimum balance on the account to be specified, beyond the /// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be /// met *and then* anything here in addition. pub trait FrozenBalance { - /// Return the frozen balance. Under normal behaviour, this amount should always be - /// withdrawable. + /// Return the frozen balance. /// - /// In reality, the balance of every account must be at least the sum of this (if `Some`) and - /// the asset's minimum_balance, since there may be complications to destroying an asset's - /// account completely. + /// Generally, the balance of every account must be at least the sum of this (if `Some`) and + /// the asset's `minimum_balance` (the latter since there may be complications to destroying an + /// asset's account completely). /// - /// If `None` is returned, then nothing special is enforced. + /// Under normal behaviour, the account balance should not go below the sum of this (if `Some`) + /// and the asset's minimum balance. However, the account balance may reasonably begin below + /// this sum (e.g. if less than the sum had ever been transferred into the account). + /// + /// In special cases (privileged intervention) the account balance may also go below the sum. /// - /// If any operation ever breaks this requirement (which will only happen through some sort of - /// privileged intervention), then `melted` is called to do any cleanup. + /// If `None` is returned, then nothing special is enforced. fn frozen_balance(asset: AssetId, who: &AccountId) -> Option; - /// Called when an account has been removed. + /// Called after an account has been removed. + /// + /// NOTE: It is possible that the asset does no longer exist when this hook is called. fn died(asset: AssetId, who: &AccountId); } diff --git a/frame/assets/src/weights.rs b/frame/assets/src/weights.rs index 912ebcf7e8510..747198ae3e5ad 100644 --- a/frame/assets/src/weights.rs +++ b/frame/assets/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_assets //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/assets/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -47,7 +49,10 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn create() -> Weight; fn force_create() -> Weight; - fn destroy(c: u32, s: u32, a: u32, ) -> Weight; + fn start_destroy() -> Weight; + fn destroy_accounts(c: u32) -> Weight; + fn destroy_approvals(m: u32) -> Weight; + fn finish_destroy() -> Weight; fn mint() -> Weight; fn burn() -> Weight; fn transfer() -> Weight; @@ -75,184 +80,231 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Assets Asset (r:1 w:1) fn create() -> Weight { - (41_651_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 33_241 nanoseconds. + Weight::from_ref_time(33_873_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) fn force_create() -> Weight { - (21_378_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 19_883 nanoseconds. + Weight::from_ref_time(20_651_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + + // Storage: Assets Asset (r:1 w:1) + fn start_destroy() -> Weight { + Weight::from_ref_time(31_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + + // Storage: Assets Asset (r:1 w:1) + // Storage: Assets Account (r:1 w:0) + // Storage: System Account (r:20 w:20) + /// The range of component `c` is `[0, 1000]`. + fn destroy_accounts(c: u32, ) -> Weight { + Weight::from_ref_time(37_000_000 as u64) + // Standard Error: 19_301 + .saturating_add(Weight::from_ref_time(25_467_908 as u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(c as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(c as u64))) + } + + // Storage: Assets Asset (r:1 w:1) + // Storage: Assets Approvals (r:1 w:0) + /// The range of component `a` is `[0, 1000]`. + fn destroy_approvals(a: u32, ) -> Weight { + Weight::from_ref_time(39_000_000 as u64) + // Standard Error: 14_298 + .saturating_add(Weight::from_ref_time(27_632_144 as u64).saturating_mul(a as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(a as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(a as u64))) } + // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:5002 w:5001) - // Storage: System Account (r:5000 w:5000) // Storage: Assets Metadata (r:1 w:0) - // Storage: Assets Approvals (r:501 w:500) - fn destroy(c: u32, s: u32, a: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 32_000 - .saturating_add((21_163_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 32_000 - .saturating_add((26_932_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 329_000 - .saturating_add((29_714_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(a as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(s as Weight))) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) + fn finish_destroy() -> Weight { + Weight::from_ref_time(33_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } + // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn mint() -> Weight { - (47_913_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 36_782 nanoseconds. + Weight::from_ref_time(37_340_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn burn() -> Weight { - (55_759_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 44_425 nanoseconds. + Weight::from_ref_time(45_485_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (83_205_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 58_294 nanoseconds. + Weight::from_ref_time(59_447_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (70_665_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 46_704 nanoseconds. + Weight::from_ref_time(47_521_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (81_458_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 57_647 nanoseconds. + Weight::from_ref_time(58_417_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn freeze() -> Weight { - (32_845_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 26_827 nanoseconds. + Weight::from_ref_time(27_373_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn thaw() -> Weight { - (33_303_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 26_291 nanoseconds. + Weight::from_ref_time(26_854_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) fn freeze_asset() -> Weight { - (23_434_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 22_694 nanoseconds. + Weight::from_ref_time(23_613_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) fn thaw_asset() -> Weight { - (24_173_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 22_572 nanoseconds. + Weight::from_ref_time(24_121_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Metadata (r:1 w:0) fn transfer_ownership() -> Weight { - (27_466_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 23_949 nanoseconds. + Weight::from_ref_time(24_347_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) fn set_team() -> Weight { - (24_608_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 23_102 nanoseconds. + Weight::from_ref_time(23_518_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn set_metadata(n: u32, s: u32, ) -> Weight { - (49_515_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 1_000 - .saturating_add((6_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn set_metadata(_n: u32, s: u32, ) -> Weight { + // Minimum execution time: 41_032 nanoseconds. + Weight::from_ref_time(42_845_624 as u64) + // Standard Error: 1_274 + .saturating_add(Weight::from_ref_time(1_875 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn clear_metadata() -> Weight { - (48_163_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 42_570 nanoseconds. + Weight::from_ref_time(42_957_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn force_set_metadata(_n: u32, s: u32, ) -> Weight { - (26_722_000 as Weight) - // Standard Error: 0 - .saturating_add((5_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn force_set_metadata(n: u32, s: u32, ) -> Weight { + // Minimum execution time: 22_768 nanoseconds. + Weight::from_ref_time(23_868_816 as u64) + // Standard Error: 612 + .saturating_add(Weight::from_ref_time(1_602 as u64).saturating_mul(n as u64)) + // Standard Error: 612 + .saturating_add(Weight::from_ref_time(2_097 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn force_clear_metadata() -> Weight { - (47_923_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 41_863 nanoseconds. + Weight::from_ref_time(42_643_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) fn force_asset_status() -> Weight { - (23_081_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 21_747 nanoseconds. + Weight::from_ref_time(22_595_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn approve_transfer() -> Weight { - (56_998_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 45_602 nanoseconds. + Weight::from_ref_time(46_004_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Assets Approvals (r:1 w:1) // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_approved() -> Weight { - (107_171_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + // Minimum execution time: 70_944 nanoseconds. + Weight::from_ref_time(71_722_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn cancel_approval() -> Weight { - (57_358_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 46_316 nanoseconds. + Weight::from_ref_time(46_910_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn force_cancel_approval() -> Weight { - (58_330_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 47_145 nanoseconds. + Weight::from_ref_time(47_611_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } } @@ -260,183 +312,230 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Assets Asset (r:1 w:1) fn create() -> Weight { - (41_651_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 33_241 nanoseconds. + Weight::from_ref_time(33_873_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) fn force_create() -> Weight { - (21_378_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 19_883 nanoseconds. + Weight::from_ref_time(20_651_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } + + // Storage: Assets Asset (r:1 w:1) + fn start_destroy() -> Weight { + Weight::from_ref_time(31_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + + // Storage: Assets Asset (r:1 w:1) + // Storage: Assets Account (r:1 w:0) + // Storage: System Account (r:20 w:20) + /// The range of component `c` is `[0, 1000]`. + fn destroy_accounts(c: u32, ) -> Weight { + Weight::from_ref_time(37_000_000 as u64) + // Standard Error: 19_301 + .saturating_add(Weight::from_ref_time(25_467_908 as u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(c as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(c as u64))) + } + + // Storage: Assets Asset (r:1 w:1) + // Storage: Assets Approvals (r:1 w:0) + /// The range of component `a` is `[0, 1000]`. + fn destroy_approvals(a: u32, ) -> Weight { + Weight::from_ref_time(39_000_000 as u64) + // Standard Error: 14_298 + .saturating_add(Weight::from_ref_time(27_632_144 as u64).saturating_mul(a as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(a as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(a as u64))) + } + // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:5002 w:5001) - // Storage: System Account (r:5000 w:5000) // Storage: Assets Metadata (r:1 w:0) - // Storage: Assets Approvals (r:501 w:500) - fn destroy(c: u32, s: u32, a: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 32_000 - .saturating_add((21_163_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 32_000 - .saturating_add((26_932_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 329_000 - .saturating_add((29_714_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(a as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(s as Weight))) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) + fn finish_destroy() -> Weight { + Weight::from_ref_time(33_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } + // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn mint() -> Weight { - (47_913_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 36_782 nanoseconds. + Weight::from_ref_time(37_340_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn burn() -> Weight { - (55_759_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 44_425 nanoseconds. + Weight::from_ref_time(45_485_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (83_205_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 58_294 nanoseconds. + Weight::from_ref_time(59_447_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (70_665_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 46_704 nanoseconds. + Weight::from_ref_time(47_521_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (81_458_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 57_647 nanoseconds. + Weight::from_ref_time(58_417_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn freeze() -> Weight { - (32_845_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 26_827 nanoseconds. + Weight::from_ref_time(27_373_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn thaw() -> Weight { - (33_303_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 26_291 nanoseconds. + Weight::from_ref_time(26_854_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) fn freeze_asset() -> Weight { - (23_434_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 22_694 nanoseconds. + Weight::from_ref_time(23_613_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) fn thaw_asset() -> Weight { - (24_173_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 22_572 nanoseconds. + Weight::from_ref_time(24_121_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Metadata (r:1 w:0) fn transfer_ownership() -> Weight { - (27_466_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 23_949 nanoseconds. + Weight::from_ref_time(24_347_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) fn set_team() -> Weight { - (24_608_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 23_102 nanoseconds. + Weight::from_ref_time(23_518_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn set_metadata(n: u32, s: u32, ) -> Weight { - (49_515_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 1_000 - .saturating_add((6_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn set_metadata(_n: u32, s: u32, ) -> Weight { + // Minimum execution time: 41_032 nanoseconds. + Weight::from_ref_time(42_845_624 as u64) + // Standard Error: 1_274 + .saturating_add(Weight::from_ref_time(1_875 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn clear_metadata() -> Weight { - (48_163_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 42_570 nanoseconds. + Weight::from_ref_time(42_957_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn force_set_metadata(_n: u32, s: u32, ) -> Weight { - (26_722_000 as Weight) - // Standard Error: 0 - .saturating_add((5_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn force_set_metadata(n: u32, s: u32, ) -> Weight { + // Minimum execution time: 22_768 nanoseconds. + Weight::from_ref_time(23_868_816 as u64) + // Standard Error: 612 + .saturating_add(Weight::from_ref_time(1_602 as u64).saturating_mul(n as u64)) + // Standard Error: 612 + .saturating_add(Weight::from_ref_time(2_097 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn force_clear_metadata() -> Weight { - (47_923_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 41_863 nanoseconds. + Weight::from_ref_time(42_643_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) fn force_asset_status() -> Weight { - (23_081_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 21_747 nanoseconds. + Weight::from_ref_time(22_595_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn approve_transfer() -> Weight { - (56_998_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 45_602 nanoseconds. + Weight::from_ref_time(46_004_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Assets Approvals (r:1 w:1) // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_approved() -> Weight { - (107_171_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + // Minimum execution time: 70_944 nanoseconds. + Weight::from_ref_time(71_722_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn cancel_approval() -> Weight { - (57_358_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 46_316 nanoseconds. + Weight::from_ref_time(46_910_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn force_cancel_approval() -> Weight { - (58_330_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 47_145 nanoseconds. + Weight::from_ref_time(47_611_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } } diff --git a/frame/atomic-swap/Cargo.toml b/frame/atomic-swap/Cargo.toml index 53a8c3a81165b..5220edb9d17c7 100644 --- a/frame/atomic-swap/Cargo.toml +++ b/frame/atomic-swap/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-atomic-swap" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME atomic swap pallet" readme = "README.md" @@ -13,14 +13,14 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -29,12 +29,12 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-support/std", "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", "sp-runtime/std", "sp-std/std", - "sp-io/std", - "sp-core/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/atomic-swap/src/lib.rs b/frame/atomic-swap/src/lib.rs index 9cf92c3bd2337..66628e8e6f242 100644 --- a/frame/atomic-swap/src/lib.rs +++ b/frame/atomic-swap/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,6 +45,7 @@ mod tests; use codec::{Decode, Encode}; use frame_support::{ dispatch::DispatchResult, + pallet_prelude::MaxEncodedLen, traits::{BalanceStatus, Currency, Get, ReservableCurrency}, weights::Weight, RuntimeDebugNoBound, @@ -59,8 +60,9 @@ use sp_std::{ }; /// Pending atomic swap operation. -#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo)] +#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] +#[codec(mel_bound())] pub struct PendingSwap { /// Source of the swap. pub source: T::AccountId, @@ -93,8 +95,9 @@ pub trait SwapAction { } /// A swap action that only allows transferring balances. -#[derive(Clone, RuntimeDebug, Eq, PartialEq, Encode, Decode, TypeInfo)] +#[derive(Clone, RuntimeDebug, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(C))] +#[codec(mel_bound())] pub struct BalanceSwapAction> { value: >::Balance, _marker: PhantomData, @@ -135,7 +138,7 @@ where C: ReservableCurrency, { fn reserve(&self, source: &AccountId) -> DispatchResult { - C::reserve(&source, self.value) + C::reserve(source, self.value) } fn claim(&self, source: &AccountId, target: &AccountId) -> bool { @@ -163,9 +166,9 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Swap action. - type SwapAction: SwapAction + Parameter; + type SwapAction: SwapAction + Parameter + MaxEncodedLen; /// Limit of proof size. /// /// Atomic swap is only atomic if once the proof is revealed, both parties can submit the @@ -218,19 +221,14 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Swap created. \[account, proof, swap\] - NewSwap(T::AccountId, HashedProof, PendingSwap), + /// Swap created. + NewSwap { account: T::AccountId, proof: HashedProof, swap: PendingSwap }, /// Swap claimed. The last parameter indicates whether the execution succeeds. - /// \[account, proof, success\] - SwapClaimed(T::AccountId, HashedProof, bool), - /// Swap cancelled. \[account, proof\] - SwapCancelled(T::AccountId, HashedProof), + SwapClaimed { account: T::AccountId, proof: HashedProof, success: bool }, + /// Swap cancelled. + SwapCancelled { account: T::AccountId, proof: HashedProof }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::call] impl Pallet { /// Register a new atomic swap, declaring an intention to send funds from origin to target @@ -245,7 +243,8 @@ pub mod pallet { /// - `duration`: Locked duration of the atomic swap. For safety reasons, it is recommended /// that the revealer uses a shorter duration than the counterparty, to prevent the /// situation where the revealer reveals the proof too late around the end block. - #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).saturating_add(40_000_000))] + #[pallet::call_index(0)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))] pub fn create_swap( origin: OriginFor, target: T::AccountId, @@ -266,9 +265,9 @@ pub mod pallet { action, end_block: frame_system::Pallet::::block_number() + duration, }; - PendingSwaps::::insert(target.clone(), hashed_proof.clone(), swap.clone()); + PendingSwaps::::insert(target.clone(), hashed_proof, swap.clone()); - Self::deposit_event(Event::NewSwap(target, hashed_proof, swap)); + Self::deposit_event(Event::NewSwap { account: target, proof: hashed_proof, swap }); Ok(()) } @@ -280,11 +279,13 @@ pub mod pallet { /// - `proof`: Revealed proof of the claim. /// - `action`: Action defined in the swap, it must match the entry in blockchain. Otherwise /// the operation fails. This is used for weight calculation. + #[pallet::call_index(1)] #[pallet::weight( T::DbWeight::get().reads_writes(1, 1) - .saturating_add(40_000_000) - .saturating_add((proof.len() as Weight).saturating_mul(100)) .saturating_add(action.weight()) + .ref_time() + .saturating_add(40_000_000) + .saturating_add((proof.len() as u64).saturating_mul(100)) )] pub fn claim_swap( origin: OriginFor, @@ -302,9 +303,13 @@ pub mod pallet { let succeeded = swap.action.claim(&swap.source, &target); - PendingSwaps::::remove(target.clone(), hashed_proof.clone()); + PendingSwaps::::remove(target.clone(), hashed_proof); - Self::deposit_event(Event::SwapClaimed(target, hashed_proof, succeeded)); + Self::deposit_event(Event::SwapClaimed { + account: target, + proof: hashed_proof, + success: succeeded, + }); Ok(()) } @@ -315,7 +320,8 @@ pub mod pallet { /// /// - `target`: Target of the original atomic swap. /// - `hashed_proof`: Hashed proof of the original atomic swap. - #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).saturating_add(40_000_000))] + #[pallet::call_index(2)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))] pub fn cancel_swap( origin: OriginFor, target: T::AccountId, @@ -331,9 +337,9 @@ pub mod pallet { ); swap.action.cancel(&swap.source); - PendingSwaps::::remove(&target, hashed_proof.clone()); + PendingSwaps::::remove(&target, hashed_proof); - Self::deposit_event(Event::SwapCancelled(target, hashed_proof)); + Self::deposit_event(Event::SwapCancelled { account: target, proof: hashed_proof }); Ok(()) } diff --git a/frame/atomic-swap/src/tests.rs b/frame/atomic-swap/src/tests.rs index a76d0f20ffa3b..5ad63dfce55b4 100644 --- a/frame/atomic-swap/src/tests.rs +++ b/frame/atomic-swap/src/tests.rs @@ -3,7 +3,10 @@ use super::*; use crate as pallet_atomic_swap; -use frame_support::parameter_types; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -26,26 +29,25 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -54,29 +56,25 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } -parameter_types! { - pub const ProofLimit: u32 = 1024; - pub const ExpireDuration: u64 = 100; -} + impl Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type SwapAction = BalanceSwapAction; - type ProofLimit = ProofLimit; + type ProofLimit = ConstU32<1024>; } const A: u64 = 1; @@ -102,9 +100,9 @@ fn two_party_successful_swap() { // A creates the swap on chain1. chain1.execute_with(|| { AtomicSwap::create_swap( - Origin::signed(A), + RuntimeOrigin::signed(A), B, - hashed_proof.clone(), + hashed_proof, BalanceSwapAction::new(50), 1000, ) @@ -117,9 +115,9 @@ fn two_party_successful_swap() { // B creates the swap on chain2. chain2.execute_with(|| { AtomicSwap::create_swap( - Origin::signed(B), + RuntimeOrigin::signed(B), A, - hashed_proof.clone(), + hashed_proof, BalanceSwapAction::new(75), 1000, ) @@ -131,8 +129,12 @@ fn two_party_successful_swap() { // A reveals the proof and claims the swap on chain2. chain2.execute_with(|| { - AtomicSwap::claim_swap(Origin::signed(A), proof.to_vec(), BalanceSwapAction::new(75)) - .unwrap(); + AtomicSwap::claim_swap( + RuntimeOrigin::signed(A), + proof.to_vec(), + BalanceSwapAction::new(75), + ) + .unwrap(); assert_eq!(Balances::free_balance(A), 100 + 75); assert_eq!(Balances::free_balance(B), 200 - 75); @@ -140,8 +142,12 @@ fn two_party_successful_swap() { // B use the revealed proof to claim the swap on chain1. chain1.execute_with(|| { - AtomicSwap::claim_swap(Origin::signed(B), proof.to_vec(), BalanceSwapAction::new(50)) - .unwrap(); + AtomicSwap::claim_swap( + RuntimeOrigin::signed(B), + proof.to_vec(), + BalanceSwapAction::new(50), + ) + .unwrap(); assert_eq!(Balances::free_balance(A), 100 - 50); assert_eq!(Balances::free_balance(B), 200 + 50); diff --git a/frame/aura/Cargo.toml b/frame/aura/Cargo.toml index 8f5c42bc3c465..552f13301d311 100644 --- a/frame/aura/Cargo.toml +++ b/frame/aura/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-aura" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME AURA consensus pallet" readme = "README.md" @@ -13,31 +13,31 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../../primitives/application-crypto" } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -sp-consensus-aura = { version = "0.10.0-dev", path = "../../primitives/consensus/aura", default-features = false } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/aura" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "sp-application-crypto/std", "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", "frame-support/std", - "sp-consensus-aura/std", "frame-system/std", "pallet-timestamp/std", + "scale-info/std", + "sp-application-crypto/std", + "sp-consensus-aura/std", + "sp-runtime/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/aura/src/lib.rs b/frame/aura/src/lib.rs index e8b68f928e087..ff2c5df04a453 100644 --- a/frame/aura/src/lib.rs +++ b/frame/aura/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,8 +40,9 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ + log, traits::{DisabledValidators, FindAuthor, Get, OnTimestampSet, OneSessionHandler}, - BoundedSlice, ConsensusEngineId, Parameter, WeakBoundedVec, + BoundedSlice, BoundedVec, ConsensusEngineId, Parameter, }; use sp_consensus_aura::{AuthorityIndex, ConsensusLog, Slot, AURA_ENGINE_ID}; use sp_runtime::{ @@ -49,7 +50,7 @@ use sp_runtime::{ traits::{IsMember, Member, SaturatedConversion, Saturating, Zero}, RuntimeAppPublic, }; -use sp_std::{convert::TryFrom, vec::Vec}; +use sp_std::prelude::*; pub mod migrations; mod mock; @@ -69,7 +70,6 @@ pub mod pallet { type AuthorityId: Member + Parameter + RuntimeAppPublic - + Default + MaybeSerializeDeserialize + MaxEncodedLen; /// The maximum number of authorities that the pallet can hold. @@ -82,7 +82,6 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_storage_info] pub struct Pallet(sp_std::marker::PhantomData); #[pallet::hooks] @@ -118,7 +117,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn authorities)] pub(super) type Authorities = - StorageValue<_, WeakBoundedVec, ValueQuery>; + StorageValue<_, BoundedVec, ValueQuery>; /// The current slot of this block. /// @@ -148,17 +147,26 @@ pub mod pallet { } impl Pallet { - fn change_authorities(new: WeakBoundedVec) { + /// Change authorities. + /// + /// The storage will be applied immediately. + /// And aura consensus log will be appended to block's log. + pub fn change_authorities(new: BoundedVec) { >::put(&new); - let log: DigestItem = DigestItem::Consensus( + let log = DigestItem::Consensus( AURA_ENGINE_ID, ConsensusLog::AuthoritiesChange(new.into_inner()).encode(), ); - >::deposit_log(log.into()); + >::deposit_log(log); } - fn initialize_authorities(authorities: &[T::AuthorityId]) { + /// Initial authorities. + /// + /// The storage will be applied immediately. + /// + /// The authorities length must be equal or less than T::MaxAuthorities. + pub fn initialize_authorities(authorities: &[T::AuthorityId]) { if !authorities.is_empty() { assert!(>::get().is_empty(), "Authorities are already initialized!"); let bounded = >::try_from(authorities) @@ -212,22 +220,26 @@ impl OneSessionHandler for Pallet { let next_authorities = validators.map(|(_, k)| k).collect::>(); let last_authorities = Self::authorities(); if last_authorities != next_authorities { - let bounded = >::force_from( - next_authorities, - Some("AuRa new session"), - ); + if next_authorities.len() as u32 > T::MaxAuthorities::get() { + log::warn!( + target: "runtime::aura", + "next authorities list larger than {}, truncating", + T::MaxAuthorities::get(), + ); + } + let bounded = >::truncate_from(next_authorities); Self::change_authorities(bounded); } } } - fn on_disabled(i: usize) { - let log: DigestItem = DigestItem::Consensus( + fn on_disabled(i: u32) { + let log = DigestItem::Consensus( AURA_ENGINE_ID, ConsensusLog::::OnDisabled(i as AuthorityIndex).encode(), ); - >::deposit_log(log.into()); + >::deposit_log(log); } } @@ -263,7 +275,7 @@ impl> FindAuthor let i = Inner::find_author(digests)?; let validators = >::authorities(); - validators.get(i as usize).map(|k| k.clone()) + validators.get(i as usize).cloned() } } diff --git a/frame/aura/src/migrations.rs b/frame/aura/src/migrations.rs index e194c17406b63..d9b7cb8872a83 100644 --- a/frame/aura/src/migrations.rs +++ b/frame/aura/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/aura/src/mock.rs b/frame/aura/src/mock.rs index 4418d9e85ae24..07fa9aa680e80 100644 --- a/frame/aura/src/mock.rs +++ b/frame/aura/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use crate as pallet_aura; use frame_support::{ parameter_types, - traits::{DisabledValidators, GenesisBuild}, + traits::{ConstU32, ConstU64, DisabledValidators, GenesisBuild}, }; use sp_consensus_aura::{ed25519::AuthorityId, AuthorityIndex}; use sp_core::H256; @@ -30,7 +30,6 @@ use sp_runtime::{ testing::{Header, UintAuthorityId}, traits::IdentityLookup, }; -use sp_std::cell::RefCell; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -48,10 +47,8 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); - pub const MinimumPeriod: u64 = 1; + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { @@ -59,17 +56,17 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -78,31 +75,27 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = Aura; - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<1>; type WeightInfo = (); } parameter_types! { - pub const MaxAuthorities: u32 = 10; -} - -thread_local! { - static DISABLED_VALIDATORS: RefCell> = RefCell::new(Default::default()); + static DisabledValidatorTestValue: Vec = Default::default(); } pub struct MockDisabledValidators; impl MockDisabledValidators { pub fn disable_validator(index: AuthorityIndex) { - DISABLED_VALIDATORS.with(|v| { - let mut disabled = v.borrow_mut(); - if let Err(i) = disabled.binary_search(&index) { - disabled.insert(i, index); + DisabledValidatorTestValue::mutate(|v| { + if let Err(i) = v.binary_search(&index) { + v.insert(i, index); } }) } @@ -110,14 +103,14 @@ impl MockDisabledValidators { impl DisabledValidators for MockDisabledValidators { fn is_disabled(index: AuthorityIndex) -> bool { - DISABLED_VALIDATORS.with(|v| v.borrow().binary_search(&index).is_ok()) + DisabledValidatorTestValue::get().binary_search(&index).is_ok() } } impl pallet_aura::Config for Test { type AuthorityId = AuthorityId; type DisabledValidators = MockDisabledValidators; - type MaxAuthorities = MaxAuthorities; + type MaxAuthorities = ConstU32<10>; } pub fn new_test_ext(authorities: Vec) -> sp_io::TestExternalities { diff --git a/frame/aura/src/tests.rs b/frame/aura/src/tests.rs index 596858aac7c92..ce09f85678c00 100644 --- a/frame/aura/src/tests.rs +++ b/frame/aura/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,6 @@ use crate::mock::{new_test_ext, Aura, MockDisabledValidators, System}; use codec::Encode; use frame_support::traits::OnInitialize; -use frame_system::InitKind; use sp_consensus_aura::{Slot, AURA_ENGINE_ID}; use sp_runtime::{Digest, DigestItem}; @@ -45,7 +44,8 @@ fn disabled_validators_cannot_author_blocks() { let pre_digest = Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())] }; - System::initialize(&42, &System::parent_hash(), &pre_digest, InitKind::Full); + System::reset_events(); + System::initialize(&42, &System::parent_hash(), &pre_digest); // let's disable the validator MockDisabledValidators::disable_validator(1); diff --git a/frame/authority-discovery/Cargo.toml b/frame/authority-discovery/Cargo.toml index 80a320c31e77f..47bd1a126f4da 100644 --- a/frame/authority-discovery/Cargo.toml +++ b/frame/authority-discovery/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-authority-discovery" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for authority discovery" readme = "README.md" @@ -13,35 +13,35 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authority-discovery" } -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../../primitives/application-crypto" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -pallet-session = { version = "4.0.0-dev", features = [ - "historical", -], path = "../session", default-features = false } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-session = { version = "4.0.0-dev", default-features = false, features = [ + "historical", +], path = "../session" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authority-discovery" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "sp-application-crypto/std", - "sp-authority-discovery/std", "codec/std", - "scale-info/std", - "sp-std/std", - "pallet-session/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", + "pallet-session/std", + "scale-info/std", + "sp-application-crypto/std", + "sp-authority-discovery/std", + "sp-runtime/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/authority-discovery/src/lib.rs b/frame/authority-discovery/src/lib.rs index d093b1533c693..b642a9ac283f2 100644 --- a/frame/authority-discovery/src/lib.rs +++ b/frame/authority-discovery/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,8 +30,6 @@ use frame_support::{ use sp_authority_discovery::AuthorityId; use sp_std::prelude::*; -use core::convert::TryFrom; - pub use pallet::*; #[frame_support::pallet] @@ -41,7 +39,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::generate_storage_info] pub struct Pallet(_); #[pallet::config] @@ -63,17 +60,12 @@ pub mod pallet { pub(super) type NextKeys = StorageValue<_, WeakBoundedVec, ValueQuery>; + #[cfg_attr(feature = "std", derive(Default))] #[pallet::genesis_config] pub struct GenesisConfig { pub keys: Vec, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { keys: Default::default() } - } - } #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { @@ -166,7 +158,7 @@ impl OneSessionHandler for Pallet { } } - fn on_disabled(_i: usize) { + fn on_disabled(_i: u32) { // ignore } } @@ -175,7 +167,10 @@ impl OneSessionHandler for Pallet { mod tests { use super::*; use crate as pallet_authority_discovery; - use frame_support::{parameter_types, traits::GenesisBuild}; + use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64, GenesisBuild}, + }; use sp_application_crypto::Pair; use sp_authority_discovery::AuthorityPair; use sp_core::{crypto::key_types, H256}; @@ -203,11 +198,10 @@ mod tests { parameter_types! { pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33); - pub const MaxAuthorities: u32 = 100; } impl Config for Test { - type MaxAuthorities = MaxAuthorities; + type MaxAuthorities = ConstU32<100>; } impl pallet_session::Config for Test { @@ -215,10 +209,9 @@ mod tests { type Keys = UintAuthorityId; type ShouldEndSession = pallet_session::PeriodicSessions; type SessionHandler = TestSessionHandler; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ValidatorId = AuthorityId; type ValidatorIdOf = ConvertInto; - type DisabledValidatorsThreshold = DisabledValidatorsThreshold; type NextSessionRotation = pallet_session::PeriodicSessions; type WeightInfo = (); } @@ -233,10 +226,8 @@ mod tests { parameter_types! { pub const Period: BlockNumber = 1; pub const Offset: BlockNumber = 0; - pub const UncleGenerations: u64 = 0; - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { @@ -244,17 +235,17 @@ mod tests { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = BlockNumber; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AuthorityId; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -263,6 +254,7 @@ mod tests { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } pub struct TestSessionHandler; @@ -276,7 +268,7 @@ mod tests { ) { } - fn on_disabled(_validator_index: usize) {} + fn on_disabled(_validator_index: u32) {} fn on_genesis_session(_validators: &[(AuthorityId, Ks)]) {} } diff --git a/frame/authorship/Cargo.toml b/frame/authorship/Cargo.toml index 120b72f8e6511..7c0289909f806 100644 --- a/frame/authorship/Cargo.toml +++ b/frame/authorship/Cargo.toml @@ -3,9 +3,9 @@ name = "pallet-authorship" version = "4.0.0-dev" description = "Block and Uncle Author tracking for the FRAME" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,30 +13,30 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-authorship = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authorship" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +impl-trait-for-tuples = "0.2.2" +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -impl-trait-for-tuples = "0.2.1" +sp-authorship = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authorship" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-runtime/std", - "sp-std/std", "frame-support/std", "frame-system/std", + "scale-info/std", "sp-authorship/std", + "sp-runtime/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/authorship/src/lib.rs b/frame/authorship/src/lib.rs index 5d36adabe888f..a40f32d36c265 100644 --- a/frame/authorship/src/lib.rs +++ b/frame/authorship/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,17 +21,33 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch, - traits::{FindAuthor, Get, VerifySeal}, + traits::{Defensive, FindAuthor, Get, VerifySeal}, + BoundedSlice, BoundedVec, }; use sp_authorship::{InherentError, UnclesInherentData, INHERENT_IDENTIFIER}; -use sp_runtime::traits::{Header as HeaderT, One, Saturating}; +use sp_runtime::traits::{Header as HeaderT, One, Saturating, UniqueSaturatedInto}; use sp_std::{collections::btree_set::BTreeSet, prelude::*, result}; const MAX_UNCLES: usize = 10; +struct MaxUncleEntryItems(core::marker::PhantomData); +impl Get for MaxUncleEntryItems { + fn get() -> u32 { + // There can be at most `MAX_UNCLES` of `UncleEntryItem::Uncle` and + // one `UncleEntryItem::InclusionHeight` per one `UncleGenerations`, + // so this gives us `MAX_UNCLES + 1` entries per one generation. + // + // There can be one extra generation worth of uncles (e.g. even + // if `UncleGenerations` is zero the pallet will still hold + // one generation worth of uncles). + let max_generations: u32 = T::UncleGenerations::get().unique_saturated_into(); + (MAX_UNCLES as u32 + 1) * (max_generations + 1) + } +} + pub use pallet::*; /// An event handler for the authorship pallet. There is a dummy implementation @@ -106,7 +122,7 @@ where let number = header.number(); if let Some(ref author) = author { - if !acc.insert((number.clone(), author.clone())) { + if !acc.insert((*number, author.clone())) { return Err("more than one uncle per number per author included") } } @@ -115,7 +131,7 @@ where } } -#[derive(Encode, Decode, sp_runtime::RuntimeDebug, scale_info::TypeInfo)] +#[derive(Encode, Decode, sp_runtime::RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen)] #[cfg_attr(any(feature = "std", test), derive(PartialEq))] enum UncleEntryItem { InclusionHeight(BlockNumber), @@ -170,9 +186,11 @@ pub mod pallet { >::put(false); - T::EventHandler::note_author(Self::author()); + if let Some(author) = Self::author() { + T::EventHandler::note_author(author); + } - 0 + Weight::zero() } fn on_finalize(_: T::BlockNumber) { @@ -184,8 +202,11 @@ pub mod pallet { #[pallet::storage] /// Uncles - pub(super) type Uncles = - StorageValue<_, Vec>, ValueQuery>; + pub(super) type Uncles = StorageValue< + _, + BoundedVec, MaxUncleEntryItems>, + ValueQuery, + >; #[pallet::storage] /// Author of current block. @@ -216,13 +237,14 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Provide a set of uncles. + #[pallet::call_index(0)] #[pallet::weight((0, DispatchClass::Mandatory))] pub fn set_uncles(origin: OriginFor, new_uncles: Vec) -> DispatchResult { ensure_none(origin)?; ensure!(new_uncles.len() <= MAX_UNCLES, Error::::TooManyUncles); if >::get() { - Err(Error::::UnclesAlreadySet)? + return Err(Error::::UnclesAlreadySet.into()) } >::put(true); @@ -300,27 +322,28 @@ impl Pallet { /// /// This is safe to invoke in `on_initialize` implementations, as well /// as afterwards. - pub fn author() -> T::AccountId { + pub fn author() -> Option { // Check the memoized storage value. if let Some(author) = >::get() { - return author + return Some(author) } let digest = >::digest(); let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); - if let Some(author) = T::FindAuthor::find_author(pre_runtime_digests) { - >::put(&author); - author - } else { - Default::default() - } + T::FindAuthor::find_author(pre_runtime_digests).map(|a| { + >::put(&a); + a + }) } fn verify_and_import_uncles(new_uncles: Vec) -> dispatch::DispatchResult { let now = >::block_number(); let mut uncles = >::get(); - uncles.push(UncleEntryItem::InclusionHeight(now)); + uncles + .try_push(UncleEntryItem::InclusionHeight(now)) + .defensive_proof("the list of uncles accepted per generation is bounded, and the number of generations is bounded, so pushing a new element will always succeed") + .map_err(|_| Error::::TooManyUncles)?; let mut acc: >::Accumulator = Default::default(); @@ -329,14 +352,15 @@ impl Pallet { UncleEntryItem::InclusionHeight(_) => None, UncleEntryItem::Uncle(h, _) => Some(h), }); - let author = Self::verify_uncle(&uncle, prev_uncles, &mut acc)?; + let maybe_author = Self::verify_uncle(&uncle, prev_uncles, &mut acc)?; let hash = uncle.hash(); - T::EventHandler::note_uncle( - author.clone().unwrap_or_default(), - now - uncle.number().clone(), - ); - uncles.push(UncleEntryItem::Uncle(hash, author)); + if let Some(author) = maybe_author.clone() { + T::EventHandler::note_uncle(author, now - *uncle.number()); + } + uncles.try_push(UncleEntryItem::Uncle(hash, maybe_author)) + .defensive_proof("the list of uncles accepted per generation is bounded, and the number of generations is bounded, so pushing a new element will always succeed") + .map_err(|_| Error::::TooManyUncles)?; } >::put(&uncles); @@ -368,7 +392,7 @@ impl Pallet { } { - let parent_number = uncle.number().clone() - One::one(); + let parent_number = *uncle.number() - One::one(); let parent_hash = >::block_hash(&parent_number); if &parent_hash != uncle.parent_hash() { return Err(Error::::InvalidUncleParent.into()) @@ -387,7 +411,7 @@ impl Pallet { } // check uncle validity. - T::FilterUncle::filter_uncle(&uncle, accumulator).map_err(|e| Into::into(e)) + T::FilterUncle::filter_uncle(uncle, accumulator).map_err(Into::into) } fn prune_old_uncles(minimum_height: T::BlockNumber) { @@ -397,8 +421,11 @@ impl Pallet { UncleEntryItem::InclusionHeight(height) => height < &minimum_height, }); let prune_index = prune_entries.count(); + let pruned_uncles = + >>::try_from(&uncles[prune_index..]) + .expect("after pruning we can't end up with more uncles than we started with"); - >::put(&uncles[prune_index..]); + >::put(pruned_uncles); } } @@ -406,7 +433,11 @@ impl Pallet { mod tests { use super::*; use crate as pallet_authorship; - use frame_support::{parameter_types, ConsensusEngineId}; + use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64, OnFinalize, OnInitialize}, + ConsensusEngineId, + }; use sp_core::H256; use sp_runtime::{ generic::DigestItem, @@ -429,9 +460,8 @@ mod tests { ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { @@ -439,17 +469,17 @@ mod tests { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -458,15 +488,12 @@ mod tests { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); - } - - parameter_types! { - pub const UncleGenerations: u64 = 5; + type MaxConsumers = ConstU32<16>; } impl pallet::Config for Test { type FindAuthor = AuthorGiven; - type UncleGenerations = UncleGenerations; + type UncleGenerations = ConstU64<5>; type FilterUncle = SealVerify; type EventHandler = (); } @@ -480,9 +507,9 @@ mod tests { where I: 'a + IntoIterator, { - for (id, data) in digests { + for (id, mut data) in digests { if id == TEST_ID { - return u64::decode(&mut &data[..]).ok() + return u64::decode(&mut data).ok() } } @@ -500,9 +527,9 @@ mod tests { let author = AuthorGiven::find_author(pre_runtime_digests).ok_or_else(|| "no author")?; - for (id, seal) in seals { + for (id, mut seal) in seals { if id == TEST_ID { - match u64::decode(&mut &seal[..]) { + match u64::decode(&mut seal) { Err(_) => return Err("wrong seal"), Ok(a) => { if a != author { @@ -553,6 +580,7 @@ mod tests { InclusionHeight(3u64), Uncle(hash, None), ]; + let uncles = BoundedVec::try_from(uncles).unwrap(); ::Uncles::put(uncles); Authorship::prune_old_uncles(3); @@ -597,7 +625,8 @@ mod tests { }; let initialize_block = |number, hash: H256| { - System::initialize(&number, &hash, &Default::default(), Default::default()) + System::reset_events(); + System::initialize(&number, &hash, &Default::default()) }; for number in 1..8 { @@ -682,6 +711,49 @@ mod tests { }); } + #[test] + fn maximum_bound() { + new_test_ext().execute_with(|| { + let mut max_item_count = 0; + + let mut author_counter = 0; + let mut current_depth = 1; + let mut parent_hash: H256 = [1; 32].into(); + let mut uncles = vec![]; + + // We deliberately run this for more generations than the limit + // so that we can get the `Uncles` to hit its cap. + for _ in 0..<::UncleGenerations as Get>::get() + 3 { + let new_uncles: Vec<_> = (0..MAX_UNCLES) + .map(|_| { + System::reset_events(); + System::initialize(¤t_depth, &parent_hash, &Default::default()); + // Increment the author on every block to make sure the hash's always + // different. + author_counter += 1; + seal_header(System::finalize(), author_counter) + }) + .collect(); + + author_counter += 1; + System::reset_events(); + System::initialize(¤t_depth, &parent_hash, &Default::default()); + Authorship::on_initialize(current_depth); + Authorship::set_uncles(RuntimeOrigin::none(), uncles).unwrap(); + Authorship::on_finalize(current_depth); + max_item_count = + std::cmp::max(max_item_count, ::Uncles::get().len()); + + let new_parent = seal_header(System::finalize(), author_counter); + parent_hash = new_parent.hash(); + uncles = new_uncles; + current_depth += 1; + } + + assert_eq!(max_item_count, MaxUncleEntryItems::::get() as usize); + }); + } + #[test] fn sets_author_lazily() { new_test_ext().execute_with(|| { @@ -690,9 +762,10 @@ mod tests { seal_header(create_header(1, Default::default(), [1; 32].into()), author); header.digest_mut().pop(); // pop the seal off. - System::initialize(&1, &Default::default(), header.digest(), Default::default()); + System::reset_events(); + System::initialize(&1, &Default::default(), header.digest()); - assert_eq!(Authorship::author(), author); + assert_eq!(Authorship::author(), Some(author)); }); } diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index d95f1419fd035..a3232f6f981d0 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-babe" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Consensus extension module for BABE consensus. Collects on-chain randomness from VRF outputs and manages epoch transitions." readme = "README.md" @@ -13,43 +13,44 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../../primitives/application-crypto" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe" } sp-consensus-vrf = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/vrf" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -log = { version = "0.4.14", default-features = false } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] +frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-offences = { version = "4.0.0-dev", path = "../offences" } pallet-staking = { version = "4.0.0-dev", path = "../staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "frame-benchmarking/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "log/std", "pallet-authorship/std", "pallet-session/std", "pallet-timestamp/std", + "scale-info/std", "sp-application-crypto/std", "sp-consensus-babe/std", "sp-consensus-vrf/std", @@ -58,7 +59,6 @@ std = [ "sp-session/std", "sp-staking/std", "sp-std/std", - "log/std", ] -runtime-benchmarks = ["frame-benchmarking"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/babe/src/benchmarking.rs b/frame/babe/src/benchmarking.rs index 372dfa532a894..20002c97f8e56 100644 --- a/frame/babe/src/benchmarking.rs +++ b/frame/babe/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,8 @@ //! Benchmarks for the BABE Pallet. +#![cfg(feature = "runtime-benchmarks")] + use super::*; use frame_benchmarking::benchmarks; @@ -63,37 +65,10 @@ benchmarks! { } verify { assert!(sp_consensus_babe::check_equivocation_proof::

(equivocation_proof2)); } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mock::*; - frame_benchmarking::impl_benchmark_test_suite!( + impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(3), crate::mock::Test, - ); - - #[test] - fn test_generate_equivocation_report_blob() { - let (pairs, mut ext) = new_test_ext_with_pairs(3); - - let offending_authority_index = 0; - let offending_authority_pair = &pairs[0]; - - ext.execute_with(|| { - start_era(1); - - let equivocation_proof = generate_equivocation_proof( - offending_authority_index, - offending_authority_pair, - CurrentSlot::::get() + 1, - ); - - println!("equivocation_proof: {:?}", equivocation_proof); - println!("equivocation_proof.encode(): {:?}", equivocation_proof.encode()); - }); - } + ) } diff --git a/frame/babe/src/default_weights.rs b/frame/babe/src/default_weights.rs index 20ac9b961fc8d..f864fd18d86a6 100644 --- a/frame/babe/src/default_weights.rs +++ b/frame/babe/src/default_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ //! This file was not auto-generated. use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}, + constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_MICROS, WEIGHT_REF_TIME_PER_NANOS}, Weight, }; @@ -38,14 +38,19 @@ impl crate::WeightInfo for () { const MAX_NOMINATORS: u64 = 200; // checking membership proof - (35 * WEIGHT_PER_MICROS) - .saturating_add((175 * WEIGHT_PER_NANOS).saturating_mul(validator_count)) + Weight::from_ref_time(35u64 * WEIGHT_REF_TIME_PER_MICROS) + .saturating_add( + Weight::from_ref_time(175u64 * WEIGHT_REF_TIME_PER_NANOS) + .saturating_mul(validator_count), + ) .saturating_add(DbWeight::get().reads(5)) // check equivocation proof - .saturating_add(110 * WEIGHT_PER_MICROS) + .saturating_add(Weight::from_ref_time(110u64 * WEIGHT_REF_TIME_PER_MICROS)) // report offence - .saturating_add(110 * WEIGHT_PER_MICROS) - .saturating_add(25 * WEIGHT_PER_MICROS * MAX_NOMINATORS) + .saturating_add(Weight::from_ref_time(110u64 * WEIGHT_REF_TIME_PER_MICROS)) + .saturating_add(Weight::from_ref_time( + 25u64 * WEIGHT_REF_TIME_PER_MICROS * MAX_NOMINATORS, + )) .saturating_add(DbWeight::get().reads(14 + 3 * MAX_NOMINATORS)) .saturating_add(DbWeight::get().writes(10 + 3 * MAX_NOMINATORS)) } diff --git a/frame/babe/src/equivocation.rs b/frame/babe/src/equivocation.rs index 2397918d1ef13..f55bda751887d 100644 --- a/frame/babe/src/equivocation.rs +++ b/frame/babe/src/equivocation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -176,7 +176,7 @@ where } fn block_author() -> Option { - Some(>::author()) + >::author() } } @@ -284,9 +284,9 @@ impl Offence self.slot } - fn slash_fraction(offenders_count: u32, validator_set_count: u32) -> Perbill { + fn slash_fraction(&self, offenders_count: u32) -> Perbill { // the formula is min((3k / n)^2, 1) - let x = Perbill::from_rational(3 * offenders_count, validator_set_count); + let x = Perbill::from_rational(3 * offenders_count, self.validator_set_count); // _ ^ 2 x.square() } diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index b39074bb3f057..1a9b3200087ae 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,13 +23,16 @@ use codec::{Decode, Encode}; use frame_support::{ - dispatch::DispatchResultWithPostInfo, + dispatch::{DispatchResultWithPostInfo, Pays}, + ensure, traits::{ - DisabledValidators, FindAuthor, Get, KeyOwnerProofSystem, OnTimestampSet, OneSessionHandler, + ConstU32, DisabledValidators, FindAuthor, Get, KeyOwnerProofSystem, OnTimestampSet, + OneSessionHandler, }, - weights::{Pays, Weight}, + weights::Weight, + BoundedVec, WeakBoundedVec, }; -use sp_application_crypto::Public; +use sp_application_crypto::ByteArray; use sp_runtime::{ generic::DigestItem, traits::{IsMember, One, SaturatedConversion, Saturating, Zero}, @@ -40,8 +43,8 @@ use sp_std::prelude::*; use sp_consensus_babe::{ digests::{NextConfigDescriptor, NextEpochDescriptor, PreDigest}, - BabeAuthorityWeight, BabeEpochConfiguration, ConsensusLog, Epoch, EquivocationProof, Slot, - BABE_ENGINE_ID, + AllowedSlots, BabeAuthorityWeight, BabeEpochConfiguration, ConsensusLog, Epoch, + EquivocationProof, Slot, BABE_ENGINE_ID, }; use sp_consensus_vrf::schnorrkel; @@ -59,8 +62,10 @@ mod mock; mod tests; pub use equivocation::{BabeEquivocationOffence, EquivocationHandler, HandleEquivocation}; +#[allow(deprecated)] +pub use randomness::CurrentBlockRandomness; pub use randomness::{ - CurrentBlockRandomness, RandomnessFromOneEpochAgo, RandomnessFromTwoEpochsAgo, + ParentBlockRandomness, RandomnessFromOneEpochAgo, RandomnessFromTwoEpochsAgo, }; pub use pallet::*; @@ -100,9 +105,7 @@ impl EpochChangeTrigger for SameAuthoritiesForever { } } -const UNDER_CONSTRUCTION_SEGMENT_LENGTH: usize = 256; - -type MaybeRandomness = Option; +const UNDER_CONSTRUCTION_SEGMENT_LENGTH: u32 = 256; #[frame_support::pallet] pub mod pallet { @@ -169,6 +172,10 @@ pub mod pallet { type HandleEquivocation: HandleEquivocation; type WeightInfo: WeightInfo; + + /// Max number of authorities allowed + #[pallet::constant] + type MaxAuthorities: Get; } #[pallet::error] @@ -179,6 +186,8 @@ pub mod pallet { InvalidKeyOwnershipProof, /// A given equivocation report is valid but already previously reported. DuplicateOffenceReport, + /// Submitted configuration is invalid. + InvalidConfiguration, } /// Current epoch index. @@ -189,7 +198,11 @@ pub mod pallet { /// Current epoch authorities. #[pallet::storage] #[pallet::getter(fn authorities)] - pub type Authorities = StorageValue<_, Vec<(AuthorityId, BabeAuthorityWeight)>, ValueQuery>; + pub type Authorities = StorageValue< + _, + WeakBoundedVec<(AuthorityId, BabeAuthorityWeight), T::MaxAuthorities>, + ValueQuery, + >; /// The slot at which the first epoch actually started. This is 0 /// until the first block of the chain. @@ -229,12 +242,15 @@ pub mod pallet { /// Next epoch authorities. #[pallet::storage] - pub(super) type NextAuthorities = - StorageValue<_, Vec<(AuthorityId, BabeAuthorityWeight)>, ValueQuery>; + pub(super) type NextAuthorities = StorageValue< + _, + WeakBoundedVec<(AuthorityId, BabeAuthorityWeight), T::MaxAuthorities>, + ValueQuery, + >; /// Randomness under construction. /// - /// We make a tradeoff between storage accesses and list length. + /// We make a trade-off between storage accesses and list length. /// We store the under-construction randomness in segments of up to /// `UNDER_CONSTRUCTION_SEGMENT_LENGTH`. /// @@ -246,22 +262,28 @@ pub mod pallet { /// TWOX-NOTE: `SegmentIndex` is an increasing integer, so this is okay. #[pallet::storage] - pub(super) type UnderConstruction = - StorageMap<_, Twox64Concat, u32, Vec, ValueQuery>; + pub(super) type UnderConstruction = StorageMap< + _, + Twox64Concat, + u32, + BoundedVec>, + ValueQuery, + >; /// Temporary value (cleared at block finalization) which is `Some` /// if per-block initialization has already been called for current block. #[pallet::storage] #[pallet::getter(fn initialized)] - pub(super) type Initialized = StorageValue<_, MaybeRandomness>; + pub(super) type Initialized = StorageValue<_, Option>; /// This field should always be populated during block processing unless /// secondary plain slots are enabled (which don't contain a VRF output). /// - /// It is set in `on_initialize`, before it will contain the value from the last block. + /// It is set in `on_finalize`, before it will contain the value from the last block. #[pallet::storage] #[pallet::getter(fn author_vrf_randomness)] - pub(super) type AuthorVrfRandomness = StorageValue<_, MaybeRandomness, ValueQuery>; + pub(super) type AuthorVrfRandomness = + StorageValue<_, Option, ValueQuery>; /// The block numbers when the last and current epoch have started, respectively `N-1` and /// `N`. @@ -284,6 +306,7 @@ pub mod pallet { /// The configuration for the current epoch. Should never be `None` as it is initialized in /// genesis. #[pallet::storage] + #[pallet::getter(fn epoch_config)] pub(super) type EpochConfig = StorageValue<_, BabeEpochConfiguration>; /// The configuration for the next epoch, `None` if the config will not change @@ -291,24 +314,18 @@ pub mod pallet { #[pallet::storage] pub(super) type NextEpochConfig = StorageValue<_, BabeEpochConfiguration>; + #[cfg_attr(feature = "std", derive(Default))] #[pallet::genesis_config] pub struct GenesisConfig { pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, pub epoch_config: Option, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { authorities: Default::default(), epoch_config: Default::default() } - } - } - #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { SegmentIndex::::put(0); - Pallet::::initialize_authorities(&self.authorities); + Pallet::::initialize_genesis_authorities(&self.authorities); EpochConfig::::put( self.epoch_config.clone().expect("epoch_config must not be None"), ); @@ -319,19 +336,60 @@ pub mod pallet { impl Hooks> for Pallet { /// Initialization fn on_initialize(now: BlockNumberFor) -> Weight { - Self::do_initialize(now); - 0 + Self::initialize(now); + Weight::zero() } /// Block finalization - fn on_finalize(_n: BlockNumberFor) { + fn on_finalize(_now: BlockNumberFor) { // at the end of the block, we can safely include the new VRF output // from this block into the under-construction randomness. If we've determined // that this block was the first in a new epoch, the changeover logic has // already occurred at this point, so the under-construction randomness // will only contain outputs from the right epoch. - if let Some(Some(randomness)) = Initialized::::take() { - Self::deposit_randomness(&randomness); + if let Some(pre_digest) = Initialized::::take().flatten() { + let authority_index = pre_digest.authority_index(); + + if T::DisabledValidators::is_disabled(authority_index) { + panic!( + "Validator with index {:?} is disabled and should not be attempting to author blocks.", + authority_index, + ); + } + + if let Some((vrf_output, vrf_proof)) = pre_digest.vrf() { + let randomness: Option = Authorities::::get() + .get(authority_index as usize) + .and_then(|(authority, _)| { + schnorrkel::PublicKey::from_bytes(authority.as_slice()).ok() + }) + .and_then(|pubkey| { + let current_slot = CurrentSlot::::get(); + + let transcript = sp_consensus_babe::make_transcript( + &Self::randomness(), + current_slot, + EpochIndex::::get(), + ); + + // NOTE: this is verified by the client when importing the block, before + // execution. we don't run the verification again here to avoid slowing + // down the runtime. + debug_assert!(pubkey + .vrf_verify(transcript.clone(), vrf_output, vrf_proof) + .is_ok()); + + vrf_output.0.attach_input_hash(&pubkey, transcript).ok() + }) + .map(|inout| inout.make_bytes(sp_consensus_babe::BABE_VRF_INOUT_CONTEXT)); + + if let Some(randomness) = pre_digest.is_primary().then(|| randomness).flatten() + { + Self::deposit_randomness(&randomness); + } + + AuthorVrfRandomness::::put(randomness); + } } // remove temporary "environment" entry from storage @@ -345,6 +403,7 @@ pub mod pallet { /// the equivocation proof and validate the given key ownership proof /// against the extracted offender. If both are valid, the offence will /// be reported. + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::report_equivocation( key_owner_proof.validator_count(), ))] @@ -366,6 +425,7 @@ pub mod pallet { /// block authors will call it (validated in `ValidateUnsigned`), as such /// if the block author is defined it will be defined as the equivocation /// reporter. + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::report_equivocation( key_owner_proof.validator_count(), ))] @@ -387,12 +447,21 @@ pub mod pallet { /// the next call to `enact_epoch_change`. The config will be activated one epoch after. /// Multiple calls to this method will replace any existing planned config change that had /// not been enacted yet. + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::plan_config_change())] pub fn plan_config_change( origin: OriginFor, config: NextConfigDescriptor, ) -> DispatchResult { ensure_root(origin)?; + match config { + NextConfigDescriptor::V1 { c, allowed_slots } => { + ensure!( + (c.0 != 0 || allowed_slots != AllowedSlots::PrimarySlots) && c.1 != 0, + Error::::InvalidConfiguration + ); + }, + } PendingEpochConfigChange::::put(config); Ok(()) } @@ -426,7 +495,7 @@ impl FindAuthor for Pallet { } } - return None + None } } @@ -439,11 +508,10 @@ impl IsMember for Pallet { impl pallet_session::ShouldEndSession for Pallet { fn should_end_session(now: T::BlockNumber) -> bool { // it might be (and it is in current implementation) that session module is calling - // should_end_session() from it's own on_initialize() handler - // => because pallet_session on_initialize() is called earlier than ours, let's ensure - // that we have synced with digest before checking if session should be ended. - Self::do_initialize(now); - + // `should_end_session` from it's own `on_initialize` handler, in which case it's + // possible that babe's own `on_initialize` has not run yet, so let's ensure that we + // have initialized the pallet and updated the current slot. + Self::initialize(now); Self::should_epoch_change(now) } } @@ -503,8 +571,8 @@ impl Pallet { /// Typically, this is not handled directly by the user, but by higher-level validator-set /// manager logic like `pallet-session`. pub fn enact_epoch_change( - authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, - next_authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, + authorities: WeakBoundedVec<(AuthorityId, BabeAuthorityWeight), T::MaxAuthorities>, + next_authorities: WeakBoundedVec<(AuthorityId, BabeAuthorityWeight), T::MaxAuthorities>, ) { // PRECONDITION: caller has done initialization and is guaranteed // by the session module to be called before this. @@ -541,8 +609,10 @@ impl Pallet { // so that nodes can track changes. let next_randomness = NextRandomness::::get(); - let next_epoch = - NextEpochDescriptor { authorities: next_authorities, randomness: next_randomness }; + let next_epoch = NextEpochDescriptor { + authorities: next_authorities.to_vec(), + randomness: next_randomness, + }; Self::deposit_consensus(ConsensusLog::NextEpochData(next_epoch)); if let Some(next_config) = NextEpochConfig::::get() { @@ -559,7 +629,7 @@ impl Pallet { } /// Finds the start slot of the current epoch. only guaranteed to - /// give correct results after `do_initialize` of the first block + /// give correct results after `initialize` of the first block /// in the chain (as its result is based off of `GenesisSlot`). pub fn current_epoch_start() -> Slot { Self::epoch_start(EpochIndex::::get()) @@ -571,7 +641,7 @@ impl Pallet { epoch_index: EpochIndex::::get(), start_slot: Self::current_epoch_start(), duration: T::EpochDuration::get(), - authorities: Self::authorities(), + authorities: Self::authorities().to_vec(), randomness: Self::randomness(), config: EpochConfig::::get() .expect("EpochConfig is initialized in genesis; we never `take` or `kill` it; qed"), @@ -590,7 +660,7 @@ impl Pallet { epoch_index: next_epoch_index, start_slot: Self::epoch_start(next_epoch_index), duration: T::EpochDuration::get(), - authorities: NextAuthorities::::get(), + authorities: NextAuthorities::::get().to_vec(), randomness: NextRandomness::::get(), config: NextEpochConfig::::get().unwrap_or_else(|| { EpochConfig::::get().expect( @@ -612,34 +682,64 @@ impl Pallet { } fn deposit_consensus(new: U) { - let log: DigestItem = DigestItem::Consensus(BABE_ENGINE_ID, new.encode()); - >::deposit_log(log.into()) + let log = DigestItem::Consensus(BABE_ENGINE_ID, new.encode()); + >::deposit_log(log) } fn deposit_randomness(randomness: &schnorrkel::Randomness) { let segment_idx = SegmentIndex::::get(); let mut segment = UnderConstruction::::get(&segment_idx); - if segment.len() < UNDER_CONSTRUCTION_SEGMENT_LENGTH { + if segment.try_push(*randomness).is_ok() { // push onto current segment: not full. - segment.push(*randomness); UnderConstruction::::insert(&segment_idx, &segment); } else { // move onto the next segment and update the index. let segment_idx = segment_idx + 1; - UnderConstruction::::insert(&segment_idx, &vec![randomness.clone()]); + let bounded_randomness = + BoundedVec::<_, ConstU32>::try_from(vec![ + *randomness, + ]) + .expect("UNDER_CONSTRUCTION_SEGMENT_LENGTH >= 1"); + UnderConstruction::::insert(&segment_idx, bounded_randomness); SegmentIndex::::put(&segment_idx); } } - fn do_initialize(now: T::BlockNumber) { - // since do_initialize can be called twice (if session module is present) - // => let's ensure that we only modify the storage once per block + fn initialize_genesis_authorities(authorities: &[(AuthorityId, BabeAuthorityWeight)]) { + if !authorities.is_empty() { + assert!(Authorities::::get().is_empty(), "Authorities are already initialized!"); + let bounded_authorities = + WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.to_vec()) + .expect("Initial number of authorities should be lower than T::MaxAuthorities"); + Authorities::::put(&bounded_authorities); + NextAuthorities::::put(&bounded_authorities); + } + } + + fn initialize_genesis_epoch(genesis_slot: Slot) { + GenesisSlot::::put(genesis_slot); + debug_assert_ne!(*GenesisSlot::::get(), 0); + + // deposit a log because this is the first block in epoch #0 + // we use the same values as genesis because we haven't collected any + // randomness yet. + let next = NextEpochDescriptor { + authorities: Self::authorities().to_vec(), + randomness: Self::randomness(), + }; + + Self::deposit_consensus(ConsensusLog::NextEpochData(next)); + } + + fn initialize(now: T::BlockNumber) { + // since `initialize` can be called twice (e.g. if session module is present) + // let's ensure that we only do the initialization once per block let initialized = Self::initialized().is_some(); if initialized { return } - let maybe_pre_digest: Option = + let pre_digest = >::digest() .logs .iter() @@ -653,76 +753,29 @@ impl Pallet { }) .next(); - let is_primary = matches!(maybe_pre_digest, Some(PreDigest::Primary(..))); + if let Some(ref pre_digest) = pre_digest { + // the slot number of the current block being initialized + let current_slot = pre_digest.slot(); - let maybe_randomness: MaybeRandomness = maybe_pre_digest.and_then(|digest| { // on the first non-zero block (i.e. block #1) // this is where the first epoch (epoch #0) actually starts. // we need to adjust internal storage accordingly. if *GenesisSlot::::get() == 0 { - GenesisSlot::::put(digest.slot()); - debug_assert_ne!(*GenesisSlot::::get(), 0); - - // deposit a log because this is the first block in epoch #0 - // we use the same values as genesis because we haven't collected any - // randomness yet. - let next = NextEpochDescriptor { - authorities: Self::authorities(), - randomness: Self::randomness(), - }; - - Self::deposit_consensus(ConsensusLog::NextEpochData(next)) + Self::initialize_genesis_epoch(current_slot) } - // the slot number of the current block being initialized - let current_slot = digest.slot(); - // how many slots were skipped between current and last block let lateness = current_slot.saturating_sub(CurrentSlot::::get() + 1); let lateness = T::BlockNumber::from(*lateness as u32); Lateness::::put(lateness); CurrentSlot::::put(current_slot); + } - let authority_index = digest.authority_index(); - - if T::DisabledValidators::is_disabled(authority_index) { - panic!( - "Validator with index {:?} is disabled and should not be attempting to author blocks.", - authority_index, - ); - } - - // Extract out the VRF output if we have it - digest.vrf_output().and_then(|vrf_output| { - // Reconstruct the bytes of VRFInOut using the authority id. - Authorities::::get() - .get(authority_index as usize) - .and_then(|author| schnorrkel::PublicKey::from_bytes(author.0.as_slice()).ok()) - .and_then(|pubkey| { - let transcript = sp_consensus_babe::make_transcript( - &Self::randomness(), - current_slot, - EpochIndex::::get(), - ); - - vrf_output.0.attach_input_hash(&pubkey, transcript).ok() - }) - .map(|inout| inout.make_bytes(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT)) - }) - }); - - // For primary VRF output we place it in the `Initialized` storage - // item and it'll be put onto the under-construction randomness later, - // once we've decided which epoch this block is in. - Initialized::::put(if is_primary { maybe_randomness } else { None }); - - // Place either the primary or secondary VRF output into the - // `AuthorVrfRandomness` storage item. - AuthorVrfRandomness::::put(maybe_randomness); + Initialized::::put(pre_digest); // enact epoch change, if necessary. - T::EpochChangeTrigger::trigger::(now) + T::EpochChangeTrigger::trigger::(now); } /// Call this function exactly once when an epoch changes, to update the @@ -732,7 +785,7 @@ impl Pallet { let segment_idx: u32 = SegmentIndex::::mutate(|s| sp_std::mem::replace(s, 0)); // overestimate to the segment being full. - let rho_size = segment_idx.saturating_add(1) as usize * UNDER_CONSTRUCTION_SEGMENT_LENGTH; + let rho_size = (segment_idx.saturating_add(1) * UNDER_CONSTRUCTION_SEGMENT_LENGTH) as usize; let next_randomness = compute_randomness( this_randomness, @@ -744,14 +797,6 @@ impl Pallet { this_randomness } - fn initialize_authorities(authorities: &[(AuthorityId, BabeAuthorityWeight)]) { - if !authorities.is_empty() { - assert!(Authorities::::get().is_empty(), "Authorities are already initialized!"); - Authorities::::put(authorities); - NextAuthorities::::put(authorities); - } - } - fn do_report_equivocation( reporter: Option, equivocation_proof: EquivocationProof, @@ -870,7 +915,7 @@ impl OneSessionHandler for Pallet { I: Iterator, { let authorities = validators.map(|(_, k)| (k, 1)).collect::>(); - Self::initialize_authorities(&authorities); + Self::initialize_genesis_authorities(&authorities); } fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I) @@ -878,14 +923,28 @@ impl OneSessionHandler for Pallet { I: Iterator, { let authorities = validators.map(|(_account, k)| (k, 1)).collect::>(); + let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from( + authorities, + Some( + "Warning: The session has more validators than expected. \ + A runtime configuration adjustment may be needed.", + ), + ); let next_authorities = queued_validators.map(|(_account, k)| (k, 1)).collect::>(); + let next_bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from( + next_authorities, + Some( + "Warning: The session has more queued validators than expected. \ + A runtime configuration adjustment may be needed.", + ), + ); - Self::enact_epoch_change(authorities, next_authorities) + Self::enact_epoch_change(bounded_authorities, next_bounded_authorities) } - fn on_disabled(i: usize) { - Self::deposit_consensus(ConsensusLog::OnDisabled(i as u32)) + fn on_disabled(i: u32) { + Self::deposit_consensus(ConsensusLog::OnDisabled(i)) } } @@ -953,6 +1012,6 @@ pub mod migrations { writes += 3; - T::DbWeight::get().writes(writes) + T::DbWeight::get().reads(reads) + T::DbWeight::get().reads_writes(reads, writes) } } diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index bc0be32624cba..204de8aae172e 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,14 +19,12 @@ use crate::{self as pallet_babe, Config, CurrentSlot}; use codec::Encode; -use frame_election_provider_support::onchain; +use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, - traits::{GenesisBuild, KeyOwnerProofSystem, OnInitialize}, + traits::{ConstU128, ConstU32, ConstU64, GenesisBuild, KeyOwnerProofSystem, OnInitialize}, }; -use frame_system::InitKind; use pallet_session::historical as pallet_session_historical; -use pallet_staking::EraIndex; use sp_consensus_babe::{AuthorityId, AuthorityPair, Slot}; use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; use sp_core::{ @@ -41,7 +39,7 @@ use sp_runtime::{ traits::{Header as _, IdentityLookup, OpaqueKeys}, Perbill, }; -use sp_staking::SessionIndex; +use sp_staking::{EraIndex, SessionIndex}; type DummyValidatorId = u64; @@ -67,10 +65,8 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(16); pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { @@ -78,18 +74,18 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Version = (); type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = DummyValidatorId; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; type OnNewAccount = (); @@ -97,14 +93,15 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } impl frame_system::offchain::SendTransactionTypes for Test where - Call: From, + RuntimeCall: From, { - type OverarchingCall = Call; - type Extrinsic = TestXt; + type OverarchingCall = RuntimeCall; + type Extrinsic = TestXt; } impl_opaque_keys! { @@ -114,7 +111,7 @@ impl_opaque_keys! { } impl pallet_session::Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ValidatorId = ::AccountId; type ValidatorIdOf = pallet_staking::StashOf; type ShouldEndSession = Babe; @@ -122,7 +119,6 @@ impl pallet_session::Config for Test { type SessionManager = pallet_session::historical::NoteHistoricalRoot; type SessionHandler = ::KeyTypeIdProviders; type Keys = MockSessionKeys; - type DisabledValidatorsThreshold = DisabledValidatorsThreshold; type WeightInfo = (); } @@ -131,40 +127,28 @@ impl pallet_session::historical::Config for Test { type FullIdentificationOf = pallet_staking::ExposureOf; } -parameter_types! { - pub const UncleGenerations: u64 = 0; -} - impl pallet_authorship::Config for Test { type FindAuthor = pallet_session::FindAccountFromAuthorIndex; - type UncleGenerations = UncleGenerations; + type UncleGenerations = ConstU64<0>; type FilterUncle = (); type EventHandler = (); } -parameter_types! { - pub const MinimumPeriod: u64 = 1; -} - impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = Babe; - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<1>; type WeightInfo = (); } -parameter_types! { - pub const ExistentialDeposit: u128 = 1; -} - impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u128; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; type AccountStore = System; type WeightInfo = (); } @@ -184,24 +168,28 @@ parameter_types! { pub const SessionsPerEra: SessionIndex = 3; pub const BondingDuration: EraIndex = 3; pub const SlashDeferDuration: EraIndex = 0; - pub const AttestationPeriod: u64 = 100; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const MaxNominatorRewardedPerValidator: u32 = 64; - pub const ElectionLookahead: u64 = 0; - pub const StakingUnsignedPriority: u64 = u64::MAX / 2; + pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(16); } -impl onchain::Config for Test { - type Accuracy = Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = ConstU32<100>; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; } impl pallet_staking::Config for Test { - const MAX_NOMINATIONS: u32 = 16; + type MaxNominations = ConstU32<16>; type RewardRemainder = (); type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; + type CurrencyBalance = ::Balance; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; @@ -211,29 +199,35 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type OnStakerSlash = (); + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } impl pallet_offences::Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type IdentificationTuple = pallet_session::historical::IdentificationTuple; type OnOffenceHandler = Staking; } parameter_types! { pub const EpochDuration: u64 = 3; - pub const ExpectedBlockTime: u64 = 1; pub const ReportLongevity: u64 = BondingDuration::get() as u64 * SessionsPerEra::get() as u64 * EpochDuration::get(); } impl Config for Test { type EpochDuration = EpochDuration; - type ExpectedBlockTime = ExpectedBlockTime; + type ExpectedBlockTime = ConstU64<1>; type EpochChangeTrigger = crate::ExternalTrigger; type DisabledValidators = Session; @@ -251,6 +245,7 @@ impl Config for Test { super::EquivocationHandler; type WeightInfo = (); + type MaxAuthorities = ConstU32<10>; } pub fn go_to_block(n: u64, s: u64) { @@ -269,7 +264,8 @@ pub fn go_to_block(n: u64, s: u64) { let pre_digest = make_secondary_plain_pre_digest(0, s.into()); - System::initialize(&n, &parent_hash, &pre_digest, InitKind::Full); + System::reset_events(); + System::initialize(&n, &parent_hash, &pre_digest); Babe::on_initialize(n); Session::on_initialize(n); @@ -435,9 +431,10 @@ pub fn generate_equivocation_proof( let make_header = || { let parent_hash = System::parent_hash(); let pre_digest = make_secondary_plain_pre_digest(offender_authority_index, slot); - System::initialize(¤t_block, &parent_hash, &pre_digest, InitKind::Full); + System::reset_events(); + System::initialize(¤t_block, &parent_hash, &pre_digest); System::set_block_number(current_block); - Timestamp::set_timestamp(current_block); + Timestamp::set_timestamp(*current_slot * Babe::slot_duration()); System::finalize() }; diff --git a/frame/babe/src/randomness.rs b/frame/babe/src/randomness.rs index 7d18629050213..28b2ea7cd665e 100644 --- a/frame/babe/src/randomness.rs +++ b/frame/babe/src/randomness.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use super::{ AuthorVrfRandomness, Config, EpochStart, NextRandomness, Randomness, VRF_OUTPUT_LENGTH, }; use frame_support::traits::Randomness as RandomnessT; -use sp_runtime::traits::Hash; +use sp_runtime::traits::{Hash, One, Saturating}; /// Randomness usable by consensus protocols that **depend** upon finality and take action /// based upon on-chain commitments made during the epoch before the previous epoch. @@ -38,7 +38,7 @@ use sp_runtime::traits::Hash; /// /// All input commitments used with `RandomnessFromTwoEpochsAgo` should come from at least /// three epochs ago. We require BABE session keys be registered at least three epochs -/// before being used to derive `CurrentBlockRandomness` for example. +/// before being used to derive `ParentBlockRandomness` for example. /// /// All users learn `RandomnessFromTwoEpochsAgo` when epoch `current_epoch - 1` starts, /// although some learn it a few block earlier inside epoch `current_epoch - 2`. @@ -76,7 +76,7 @@ pub struct RandomnessFromTwoEpochsAgo(sp_std::marker::PhantomData); /// end of every epoch, but they possess some influence over when they possess more slots. /// /// As an example usage, we determine parachain auctions ending times in Polkadot using -/// `RandomnessFromOneEpochAgo` because it reduces bias from `CurrentBlockRandomness` and +/// `RandomnessFromOneEpochAgo` because it reduces bias from `ParentBlockRandomness` and /// does not require the extra finality delay of `RandomnessFromTwoEpochsAgo`. pub struct RandomnessFromOneEpochAgo(sp_std::marker::PhantomData); @@ -89,28 +89,44 @@ pub struct RandomnessFromOneEpochAgo(sp_std::marker::PhantomData); /// wins whatever game they play. /// /// As with `RandomnessFromTwoEpochsAgo`, all input commitments combined with -/// `CurrentBlockRandomness` should come from at least two epoch ago, except preferably +/// `ParentBlockRandomness` should come from at least two epoch ago, except preferably /// not near epoch ending, and thus ideally three epochs ago. /// -/// Almost all users learn this randomness for a block when the block producer announces -/// the block, which makes this randomness appear quite fresh. Yet, the block producer +/// Almost all users learn this randomness for a given block by the time they receive it's +/// parent block, which makes this randomness appear fresh enough. Yet, the block producer /// themselves learned this randomness at the beginning of epoch `current_epoch - 2`, at /// the same time as they learn `RandomnessFromTwoEpochsAgo`. /// /// Aside from just biasing `RandomnessFromTwoEpochsAgo`, adversaries could also bias -/// `CurrentBlockRandomness` by never announcing their block if doing so yields an -/// unfavorable randomness. As such, `CurrentBlockRandomness` should be considered weaker -/// than both other randomness sources provided by BABE, but `CurrentBlockRandomness` +/// `ParentBlockRandomness` by never announcing their block if doing so yields an +/// unfavorable randomness. As such, `ParentBlockRandomness` should be considered weaker +/// than both other randomness sources provided by BABE, but `ParentBlockRandomness` /// remains constrained by declared staking, while a randomness source like block hash is /// only constrained by adversaries' unknowable computational power. /// /// As an example use, parachains could assign block production slots based upon the -/// `CurrentBlockRandomness` of their relay parent or relay parent's parent, provided the +/// `ParentBlockRandomness` of their relay parent or relay parent's parent, provided the /// parachain registers collators but avoids censorship sensitive functionality like /// slashing. Any parachain with slashing could operate BABE itself or perhaps better yet -/// a BABE-like approach that derives its `CurrentBlockRandomness`, and authorizes block -/// production, based upon the relay parent's `CurrentBlockRandomness` or more likely the +/// a BABE-like approach that derives its `ParentBlockRandomness`, and authorizes block +/// production, based upon the relay parent's `ParentBlockRandomness` or more likely the /// relay parent's `RandomnessFromTwoEpochsAgo`. +/// +/// NOTE: there is some nuance here regarding what is current and parent randomness. If +/// you are using this trait from within the runtime (i.e. as part of block execution) +/// then the randomness provided here will always be generated from the parent block. If +/// instead you are using this randomness externally, i.e. after block execution, then +/// this randomness will be provided by the "current" block (this stems from the fact that +/// we process VRF outputs on block execution finalization, i.e. `on_finalize`). +pub struct ParentBlockRandomness(sp_std::marker::PhantomData); + +/// Randomness produced semi-freshly with each block, but inherits limitations of +/// `RandomnessFromTwoEpochsAgo` from which it derives. +/// +/// See [`ParentBlockRandomness`]. +#[deprecated(note = "Should not be relied upon for correctness, \ + will not provide fresh randomness for the current block. \ + Please use `ParentBlockRandomness` instead.")] pub struct CurrentBlockRandomness(sp_std::marker::PhantomData); impl RandomnessT for RandomnessFromTwoEpochsAgo { @@ -133,7 +149,7 @@ impl RandomnessT for RandomnessFromOneEpochA } } -impl RandomnessT, T::BlockNumber> for CurrentBlockRandomness { +impl RandomnessT, T::BlockNumber> for ParentBlockRandomness { fn random(subject: &[u8]) -> (Option, T::BlockNumber) { let random = AuthorVrfRandomness::::get().map(|random| { let mut subject = subject.to_vec(); @@ -143,6 +159,14 @@ impl RandomnessT, T::BlockNumber> for CurrentBlockRan T::Hashing::hash(&subject[..]) }); + (random, >::block_number().saturating_sub(One::one())) + } +} + +#[allow(deprecated)] +impl RandomnessT, T::BlockNumber> for CurrentBlockRandomness { + fn random(subject: &[u8]) -> (Option, T::BlockNumber) { + let (random, _) = ParentBlockRandomness::::random(subject); (random, >::block_number()) } } diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index dc2f74c719519..d4132e6378540 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,12 +20,13 @@ use super::{Call, *}; use frame_support::{ assert_err, assert_noop, assert_ok, + dispatch::{GetDispatchInfo, Pays}, traits::{Currency, EstimateNextSessionRotation, OnFinalize}, - weights::{GetDispatchInfo, Pays}, }; use mock::*; use pallet_session::ShouldEndSession; use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration, Slot}; +use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; use sp_core::crypto::Pair; const EMPTY_RANDOMNESS: [u8; 32] = [ @@ -67,7 +68,8 @@ fn first_block_epoch_zero_start() { let pre_digest = make_primary_pre_digest(0, genesis_slot, first_vrf.clone(), vrf_proof); assert_eq!(Babe::genesis_slot(), Slot::from(0)); - System::initialize(&1, &Default::default(), &pre_digest, Default::default()); + System::reset_events(); + System::initialize(&1, &Default::default(), &pre_digest); // see implementation of the function for details why: we issue an // epoch-change digest but don't do it via the normal session mechanism. @@ -75,11 +77,11 @@ fn first_block_epoch_zero_start() { assert_eq!(Babe::genesis_slot(), genesis_slot); assert_eq!(Babe::current_slot(), genesis_slot); assert_eq!(Babe::epoch_index(), 0); - assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); Babe::on_finalize(1); let header = System::finalize(); + assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); assert_eq!(SegmentIndex::::get(), 0); assert_eq!(UnderConstruction::::get(0), vec![vrf_randomness]); assert_eq!(Babe::randomness(), [0; 32]); @@ -92,7 +94,7 @@ fn first_block_epoch_zero_start() { let consensus_log = sp_consensus_babe::ConsensusLog::NextEpochData( sp_consensus_babe::digests::NextEpochDescriptor { - authorities: Babe::authorities(), + authorities: Babe::authorities().to_vec(), randomness: Babe::randomness(), }, ); @@ -104,61 +106,82 @@ fn first_block_epoch_zero_start() { } #[test] -fn author_vrf_output_for_primary() { +fn current_slot_is_processed_on_initialization() { let (pairs, mut ext) = new_test_ext_with_pairs(1); ext.execute_with(|| { let genesis_slot = Slot::from(10); let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); - let primary_pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_output, vrf_proof); + let pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_output, vrf_proof); - System::initialize(&1, &Default::default(), &primary_pre_digest, Default::default()); + System::reset_events(); + System::initialize(&1, &Default::default(), &pre_digest); + assert_eq!(Babe::current_slot(), Slot::from(0)); + assert!(Babe::initialized().is_none()); - Babe::do_initialize(1); - assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); + // current slot is updated on initialization + Babe::initialize(1); + assert_eq!(Babe::current_slot(), genesis_slot); + assert!(Babe::initialized().is_some()); + // but author vrf randomness isn't + assert_eq!(Babe::author_vrf_randomness(), None); + // instead it is updated on block finalization Babe::on_finalize(1); - System::finalize(); assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); }) } -#[test] -fn author_vrf_output_for_secondary_vrf() { +fn test_author_vrf_output(make_pre_digest: F) +where + F: Fn(sp_consensus_babe::AuthorityIndex, Slot, VRFOutput, VRFProof) -> sp_runtime::Digest, +{ let (pairs, mut ext) = new_test_ext_with_pairs(1); ext.execute_with(|| { let genesis_slot = Slot::from(10); let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); - let secondary_vrf_pre_digest = - make_secondary_vrf_pre_digest(0, genesis_slot, vrf_output, vrf_proof); + let pre_digest = make_pre_digest(0, genesis_slot, vrf_output, vrf_proof); - System::initialize(&1, &Default::default(), &secondary_vrf_pre_digest, Default::default()); + System::reset_events(); + System::initialize(&1, &Default::default(), &pre_digest); - Babe::do_initialize(1); - assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); + // author vrf randomness is not updated on initialization + Babe::initialize(1); + assert_eq!(Babe::author_vrf_randomness(), None); + // instead it is updated on block finalization to account for any + // epoch changes that might happen during the block Babe::on_finalize(1); + assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); + + // and it is kept after finalizing the block System::finalize(); assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); }) } +#[test] +fn author_vrf_output_for_primary() { + test_author_vrf_output(make_primary_pre_digest); +} + +#[test] +fn author_vrf_output_for_secondary_vrf() { + test_author_vrf_output(make_secondary_vrf_pre_digest); +} + #[test] fn no_author_vrf_output_for_secondary_plain() { new_test_ext(1).execute_with(|| { let genesis_slot = Slot::from(10); let secondary_plain_pre_digest = make_secondary_plain_pre_digest(0, genesis_slot); - System::initialize( - &1, - &Default::default(), - &secondary_plain_pre_digest, - Default::default(), - ); + System::reset_events(); + System::initialize(&1, &Default::default(), &secondary_plain_pre_digest); assert_eq!(Babe::author_vrf_randomness(), None); - Babe::do_initialize(1); + Babe::initialize(1); assert_eq!(Babe::author_vrf_randomness(), None); Babe::on_finalize(1); @@ -266,7 +289,7 @@ fn can_enact_next_config() { assert_eq!(NextEpochConfig::::get(), Some(next_config.clone())); Babe::plan_config_change( - Origin::root(), + RuntimeOrigin::root(), NextConfigDescriptor::V1 { c: next_next_config.c, allowed_slots: next_next_config.allowed_slots, @@ -300,15 +323,15 @@ fn only_root_can_enact_config_change() { let next_config = NextConfigDescriptor::V1 { c: (1, 4), allowed_slots: AllowedSlots::PrimarySlots }; - let res = Babe::plan_config_change(Origin::none(), next_config.clone()); + let res = Babe::plan_config_change(RuntimeOrigin::none(), next_config.clone()); assert_noop!(res, DispatchError::BadOrigin); - let res = Babe::plan_config_change(Origin::signed(1), next_config.clone()); + let res = Babe::plan_config_change(RuntimeOrigin::signed(1), next_config.clone()); assert_noop!(res, DispatchError::BadOrigin); - let res = Babe::plan_config_change(Origin::root(), next_config); + let res = Babe::plan_config_change(RuntimeOrigin::root(), next_config); assert!(res.is_ok()); }); @@ -441,7 +464,7 @@ fn report_equivocation_current_session_works() { // report the equivocation Babe::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ) @@ -513,7 +536,7 @@ fn report_equivocation_old_session_works() { // report the equivocation Babe::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ) @@ -565,7 +588,7 @@ fn report_equivocation_invalid_key_owner_proof() { key_owner_proof.session = 0; assert_err!( Babe::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof.clone()), key_owner_proof ), @@ -585,7 +608,7 @@ fn report_equivocation_invalid_key_owner_proof() { assert_err!( Babe::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ), @@ -619,7 +642,7 @@ fn report_equivocation_invalid_equivocation_proof() { let assert_invalid_equivocation = |equivocation_proof| { assert_err!( Babe::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof.clone(), ), @@ -636,7 +659,7 @@ fn report_equivocation_invalid_equivocation_proof() { equivocation_proof.second_header = equivocation_proof.first_header.clone(); assert_invalid_equivocation(equivocation_proof); - // missing preruntime digest from one header + // missing pre-runtime digest from one header let mut equivocation_proof = generate_equivocation_proof( offending_validator_index as u32, &offending_authority_pair, @@ -761,7 +784,7 @@ fn report_equivocation_validate_unsigned_prevents_duplicates() { // we submit the report Babe::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ) @@ -800,7 +823,7 @@ fn report_equivocation_has_valid_weight() { .map(::WeightInfo::report_equivocation) .collect::>() .windows(2) - .all(|w| w[0] < w[1])); + .all(|w| w[0].ref_time() < w[1].ref_time())); } #[test] @@ -829,12 +852,13 @@ fn valid_equivocation_reports_dont_pay_fees() { .get_dispatch_info(); // it should have non-zero weight and the fee has to be paid. - assert!(info.weight > 0); + // TODO: account for proof size weight + assert!(info.weight.ref_time() > 0); assert_eq!(info.pays_fee, Pays::Yes); // report the equivocation. let post_info = Babe::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof.clone()), key_owner_proof.clone(), ) @@ -848,7 +872,7 @@ fn valid_equivocation_reports_dont_pay_fees() { // report the equivocation again which is invalid now since it is // duplicate. let post_info = Babe::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ) @@ -903,3 +927,24 @@ fn add_epoch_configurations_migration_works() { assert_eq!(PendingEpochConfigChange::::get(), Some(next_config_descriptor)); }); } + +#[test] +fn generate_equivocation_report_blob() { + let (pairs, mut ext) = new_test_ext_with_pairs(3); + + let offending_authority_index = 0; + let offending_authority_pair = &pairs[0]; + + ext.execute_with(|| { + start_era(1); + + let equivocation_proof = generate_equivocation_proof( + offending_authority_index, + offending_authority_pair, + CurrentSlot::::get() + 1, + ); + + println!("equivocation_proof: {:?}", equivocation_proof); + println!("equivocation_proof.encode(): {:?}", equivocation_proof.encode()); + }); +} diff --git a/frame/bags-list/Cargo.toml b/frame/bags-list/Cargo.toml new file mode 100644 index 0000000000000..52dd14b7d01c8 --- /dev/null +++ b/frame/bags-list/Cargo.toml @@ -0,0 +1,78 @@ +[package] +name = "pallet-bags-list" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet bags list" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# parity +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# primitives +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +# FRAME +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } + +# third party +log = { version = "0.4.17", default-features = false } + +# Optional imports for benchmarking +frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking", optional = true, default-features = false } +pallet-balances = { version = "4.0.0-dev", path = "../balances", optional = true, default-features = false } +sp-core = { version = "7.0.0", path = "../../primitives/core", optional = true, default-features = false } +sp-io = { version = "7.0.0", path = "../../primitives/io", optional = true, default-features = false } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing", optional = true, default-features = false } + +[dev-dependencies] +sp-core = { version = "7.0.0", path = "../../primitives/core"} +sp-io = { version = "7.0.0", path = "../../primitives/io"} +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } +frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } + +[features] +default = ["std"] +std = [ + "sp-tracing?/std", + "sp-io?/std", + "sp-core?/std", + "pallet-balances?/std", + "frame-benchmarking?/std", + "scale-info/std", + "codec/std", + "sp-runtime/std", + "sp-std/std", + "frame-support/std", + "frame-system/std", + "frame-election-provider-support/std", + "log/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "sp-core", + "sp-io", + "pallet-balances/runtime-benchmarks", + "sp-tracing", + "frame-election-provider-support/runtime-benchmarks", +] +fuzz = [ + "sp-core", + "sp-io", + "pallet-balances", + "sp-tracing", + "frame-election-provider-support/fuzz", +] +try-runtime = [ "frame-support/try-runtime" ] diff --git a/frame/bags-list/fuzzer/.gitignore b/frame/bags-list/fuzzer/.gitignore new file mode 100644 index 0000000000000..3ebcb104d4a50 --- /dev/null +++ b/frame/bags-list/fuzzer/.gitignore @@ -0,0 +1,2 @@ +hfuzz_target +hfuzz_workspace diff --git a/frame/bags-list/fuzzer/Cargo.toml b/frame/bags-list/fuzzer/Cargo.toml new file mode 100644 index 0000000000000..fc2334bea5ca7 --- /dev/null +++ b/frame/bags-list/fuzzer/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pallet-bags-list-fuzzer" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Fuzzer for FRAME pallet bags list" +publish = false + +[dependencies] +honggfuzz = "0.5" +rand = { version = "0.8", features = ["std", "small_rng"] } +frame-election-provider-support = { version = "4.0.0-dev", features = ["fuzz"], path = "../../election-provider-support" } +pallet-bags-list = { version = "4.0.0-dev", features = ["fuzz"], path = ".." } + +[[bin]] +name = "bags-list" +path = "src/main.rs" diff --git a/frame/bags-list/fuzzer/src/main.rs b/frame/bags-list/fuzzer/src/main.rs new file mode 100644 index 0000000000000..9f7ca464cc2b8 --- /dev/null +++ b/frame/bags-list/fuzzer/src/main.rs @@ -0,0 +1,94 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! # Running +//! Running this fuzzer can be done with `cargo hfuzz run bags-list`. `honggfuzz` CLI options can +//! be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads. +//! +//! # Debugging a panic +//! Once a panic is found, it can be debugged with +//! `cargo hfuzz run-debug fixed_point hfuzz_workspace/bags_list/*.fuzz`. +//! +//! # More information +//! More information about `honggfuzz` can be found +//! [here](https://docs.rs/honggfuzz/). + +use frame_election_provider_support::{SortedListProvider, VoteWeight}; +use honggfuzz::fuzz; +use pallet_bags_list::mock::{AccountId, BagsList, ExtBuilder}; + +const ID_RANGE: AccountId = 25_000; + +/// Actions of a `SortedListProvider` that we fuzz. +enum Action { + Insert, + Update, + Remove, +} + +impl From for Action { + fn from(v: u32) -> Self { + let num_variants = Self::Remove as u32 + 1; + match v % num_variants { + _x if _x == Action::Insert as u32 => Action::Insert, + _x if _x == Action::Update as u32 => Action::Update, + _x if _x == Action::Remove as u32 => Action::Remove, + _ => unreachable!(), + } + } +} + +fn main() { + ExtBuilder::default().build_and_execute(|| loop { + fuzz!(|data: (AccountId, VoteWeight, u32)| { + let (account_id_seed, vote_weight, action_seed) = data; + + let id = account_id_seed % ID_RANGE; + let action = Action::from(action_seed); + + match action { + Action::Insert => { + if BagsList::on_insert(id, vote_weight).is_err() { + // this was a duplicate id, which is ok. We can just update it. + BagsList::on_update(&id, vote_weight).unwrap(); + } + assert!(BagsList::contains(&id)); + }, + Action::Update => { + let already_contains = BagsList::contains(&id); + if already_contains { + BagsList::on_update(&id, vote_weight).unwrap(); + assert!(BagsList::contains(&id)); + } else { + BagsList::on_update(&id, vote_weight).unwrap_err(); + } + }, + Action::Remove => { + let already_contains = BagsList::contains(&id); + if already_contains { + BagsList::on_remove(&id).unwrap(); + } else { + BagsList::on_remove(&id).unwrap_err(); + } + assert!(!BagsList::contains(&id)); + }, + } + + assert!(BagsList::try_state().is_ok()); + }) + }); +} diff --git a/frame/bags-list/remote-tests/Cargo.toml b/frame/bags-list/remote-tests/Cargo.toml new file mode 100644 index 0000000000000..9fb6d0154b302 --- /dev/null +++ b/frame/bags-list/remote-tests/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "pallet-bags-list-remote-tests" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet bags list remote test" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# frame +pallet-staking = { path = "../../staking", version = "4.0.0-dev" } +pallet-bags-list = { path = "../../bags-list", version = "4.0.0-dev" } +frame-election-provider-support = { path = "../../election-provider-support", version = "4.0.0-dev" } +frame-system = { path = "../../system", version = "4.0.0-dev" } +frame-support = { path = "../../support", version = "4.0.0-dev" } + +# core +sp-storage = { path = "../../../primitives/storage", version = "7.0.0" } +sp-core = { path = "../../../primitives/core", version = "7.0.0" } +sp-tracing = { path = "../../../primitives/tracing", version = "6.0.0" } +sp-runtime = { path = "../../../primitives/runtime", version = "7.0.0" } +sp-std = { path = "../../../primitives/std", version = "5.0.0" } + +# utils +remote-externalities = { path = "../../../utils/frame/remote-externalities", version = "0.10.0-dev", package = "frame-remote-externalities" } + +# others +log = "0.4.17" diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs new file mode 100644 index 0000000000000..fc25e3b65ddb1 --- /dev/null +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -0,0 +1,160 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Utilities for remote-testing pallet-bags-list. + +use frame_election_provider_support::ScoreProvider; +use pallet_bags_list::Instance1; +use sp_std::prelude::*; + +/// A common log target to use. +pub const LOG_TARGET: &str = "runtime::bags-list::remote-tests"; + +pub mod migration; +pub mod snapshot; +pub mod try_state; + +/// A wrapper for a runtime that the functions of this crate expect. +/// +/// For example, this can be the `Runtime` type of the Polkadot runtime. +pub trait RuntimeT: + pallet_staking::Config + pallet_bags_list::Config + frame_system::Config +{ +} +impl< + I: 'static, + T: pallet_staking::Config + pallet_bags_list::Config + frame_system::Config, + > RuntimeT for T +{ +} + +fn percent(portion: u32, total: u32) -> f64 { + (portion as f64 / total as f64) * 100f64 +} + +/// Display the number of nodes in each bag, while identifying those that need a rebag. +pub fn display_and_check_bags>( + currency_unit: u64, + currency_name: &'static str, +) { + use frame_election_provider_support::SortedListProvider; + use frame_support::traits::Get; + + let min_nominator_bond = >::get(); + log::info!(target: LOG_TARGET, "min nominator bond is {:?}", min_nominator_bond); + + let voter_list_count = ::VoterList::count(); + + // go through every bag to track the total number of voters within bags and log some info about + // how voters are distributed within the bags. + let mut seen_in_bags = 0; + let mut rebaggable = 0; + let mut active_bags = 0; + for vote_weight_thresh in >::BagThresholds::get() + { + let vote_weight_thresh_u64: u64 = (*vote_weight_thresh) + .try_into() + .map_err(|_| "runtime must configure score to at most u64 to use this test") + .unwrap(); + // threshold in terms of UNITS (e.g. KSM, DOT etc) + let vote_weight_thresh_as_unit = vote_weight_thresh_u64 as f64 / currency_unit as f64; + let pretty_thresh = format!("Threshold: {}. {}", vote_weight_thresh_as_unit, currency_name); + + let bag = match pallet_bags_list::Pallet::::list_bags_get( + *vote_weight_thresh, + ) { + Some(bag) => bag, + None => { + log::info!(target: LOG_TARGET, "{} NO VOTERS.", pretty_thresh); + continue + }, + }; + + active_bags += 1; + + for id in bag.std_iter().map(|node| node.std_id().clone()) { + let vote_weight = + >::ScoreProvider::score(&id); + let vote_weight_thresh_u64: u64 = (*vote_weight_thresh) + .try_into() + .map_err(|_| "runtime must configure score to at most u64 to use this test") + .unwrap(); + let vote_weight_as_balance: pallet_staking::BalanceOf = + vote_weight_thresh_u64.try_into().map_err(|_| "can't convert").unwrap(); + + if vote_weight_as_balance < min_nominator_bond { + log::trace!( + target: LOG_TARGET, + "⚠️ {} Account found below min bond: {:?}.", + pretty_thresh, + id + ); + } + + let node = pallet_bags_list::Node::::get(&id) + .expect("node in bag must exist."); + if node.is_misplaced(vote_weight) { + rebaggable += 1; + let notional_bag = pallet_bags_list::notional_bag_for::(vote_weight); + let notional_bag_as_u64: u64 = notional_bag + .try_into() + .map_err(|_| "runtime must configure score to at most u64 to use this test") + .unwrap(); + log::trace!( + target: LOG_TARGET, + "Account {:?} can be rebagged from {:?} to {:?}", + id, + vote_weight_thresh_as_unit, + notional_bag_as_u64 as f64 / currency_unit as f64 + ); + } + } + + // update our overall counter + let voters_in_bag = bag.std_iter().count() as u32; + seen_in_bags += voters_in_bag; + + // percentage of all nominators + let percent_of_voters = percent(voters_in_bag, voter_list_count); + + log::info!( + target: LOG_TARGET, + "{} Nominators: {} [%{:.3}]", + pretty_thresh, + voters_in_bag, + percent_of_voters, + ); + } + + if seen_in_bags != voter_list_count { + log::error!( + target: LOG_TARGET, + "bags list population ({}) not on par whoever is voter_list ({})", + seen_in_bags, + voter_list_count, + ) + } + + log::info!( + target: LOG_TARGET, + "a total of {} nodes are in {} active bags [{} total bags], {} of which can be rebagged.", + voter_list_count, + active_bags, + >::BagThresholds::get().len(), + rebaggable, + ); +} diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs new file mode 100644 index 0000000000000..b013472b4c90e --- /dev/null +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -0,0 +1,67 @@ +// Copyright 2021 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 . + +//! Test to check the migration of the voter bag. + +use crate::{RuntimeT, LOG_TARGET}; +use frame_support::traits::PalletInfoAccess; +use pallet_staking::Nominators; +use remote_externalities::{Builder, Mode, OnlineConfig}; +use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; + +/// Test voter bags migration. `currency_unit` is the number of planks per the the runtimes `UNITS` +/// (i.e. number of decimal places per DOT, KSM etc) +pub async fn execute( + currency_unit: u64, + currency_name: &'static str, + ws_url: String, +) where + Runtime: RuntimeT, + Block: BlockT, + Block::Header: DeserializeOwned, +{ + let mut ext = Builder::::new() + .mode(Mode::Online(OnlineConfig { + transport: ws_url.to_string().into(), + pallets: vec![pallet_staking::Pallet::::name().to_string()], + ..Default::default() + })) + .build() + .await + .unwrap(); + + ext.execute_with(|| { + // get the nominator & validator count prior to migrating; these should be invariant. + let pre_migrate_nominator_count = >::iter().count() as u32; + log::info!(target: LOG_TARGET, "Nominator count: {}", pre_migrate_nominator_count); + + use frame_election_provider_support::SortedListProvider; + // run the actual migration + let moved = ::VoterList::unsafe_regenerate( + pallet_staking::Nominators::::iter().map(|(n, _)| n), + pallet_staking::Pallet::::weight_of_fn(), + ); + log::info!(target: LOG_TARGET, "Moved {} nominators", moved); + + let voter_list_len = ::VoterList::iter().count() as u32; + let voter_list_count = ::VoterList::count(); + // and confirm it is equal to the length of the `VoterList`. + assert_eq!(pre_migrate_nominator_count, voter_list_len); + assert_eq!(pre_migrate_nominator_count, voter_list_count); + + crate::display_and_check_bags::(currency_unit, currency_name); + }); +} diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs new file mode 100644 index 0000000000000..cfe065924bd92 --- /dev/null +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -0,0 +1,88 @@ +// Copyright 2021 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 . + +//! Test to execute the snapshot using the voter bag. + +use frame_election_provider_support::SortedListProvider; +use frame_support::traits::PalletInfoAccess; +use remote_externalities::{Builder, Mode, OnlineConfig}; +use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; + +/// Execute create a snapshot from pallet-staking. +pub async fn execute(voter_limit: Option, currency_unit: u64, ws_url: String) +where + Runtime: crate::RuntimeT, + Block: BlockT, + Block::Header: DeserializeOwned, +{ + use frame_support::storage::generator::StorageMap; + + let mut ext = Builder::::new() + .mode(Mode::Online(OnlineConfig { + transport: ws_url.to_string().into(), + // NOTE: we don't scrape pallet-staking, this kinda ensures that the source of the data + // is bags-list. + pallets: vec![pallet_bags_list::Pallet::::name() + .to_string()], + at: None, + ..Default::default() + })) + .inject_hashed_prefix(&>::prefix_hash()) + .inject_hashed_prefix(&>::prefix_hash()) + .inject_hashed_prefix(&>::map_storage_final_prefix()) + .inject_hashed_prefix(&>::map_storage_final_prefix()) + .inject_hashed_key(&>::counter_storage_final_key()) + .inject_hashed_key(&>::counter_storage_final_key()) + .build() + .await + .unwrap(); + + ext.execute_with(|| { + use frame_election_provider_support::ElectionDataProvider; + log::info!( + target: crate::LOG_TARGET, + "{} nodes in bags list.", + ::VoterList::count(), + ); + + let voters = + as ElectionDataProvider>::electing_voters(voter_limit) + .unwrap(); + + let mut voters_nominator_only = voters + .iter() + .filter(|(v, _, _)| pallet_staking::Nominators::::contains_key(v)) + .cloned() + .collect::>(); + voters_nominator_only.sort_by_key(|(_, w, _)| *w); + + let currency_unit = currency_unit as f64; + let min_voter = voters_nominator_only + .first() + .map(|(x, y, _)| (x.clone(), *y as f64 / currency_unit)); + let max_voter = voters_nominator_only + .last() + .map(|(x, y, _)| (x.clone(), *y as f64 / currency_unit)); + log::info!( + target: crate::LOG_TARGET, + "a snapshot with limit {:?} has been created, {} voters are taken. min nominator: {:?}, max: {:?}", + voter_limit, + voters.len(), + min_voter, + max_voter + ); + }); +} diff --git a/frame/bags-list/remote-tests/src/try_state.rs b/frame/bags-list/remote-tests/src/try_state.rs new file mode 100644 index 0000000000000..d3fb63f045a64 --- /dev/null +++ b/frame/bags-list/remote-tests/src/try_state.rs @@ -0,0 +1,57 @@ +// Copyright 2021 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 . + +//! Test to execute the sanity-check of the voter bag. + +use frame_election_provider_support::SortedListProvider; +use frame_support::{ + storage::generator::StorageMap, + traits::{Get, PalletInfoAccess}, +}; +use remote_externalities::{Builder, Mode, OnlineConfig}; +use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; + +/// Execute the sanity check of the bags-list. +pub async fn execute( + currency_unit: u64, + currency_name: &'static str, + ws_url: String, +) where + Runtime: crate::RuntimeT, + Block: BlockT, + Block::Header: DeserializeOwned, +{ + let mut ext = Builder::::new() + .mode(Mode::Online(OnlineConfig { + transport: ws_url.to_string().into(), + pallets: vec![pallet_bags_list::Pallet::::name() + .to_string()], + ..Default::default() + })) + .inject_hashed_prefix(&>::prefix_hash()) + .inject_hashed_prefix(&>::prefix_hash()) + .build() + .await + .unwrap(); + + ext.execute_with(|| { + sp_core::crypto::set_default_ss58_version(Runtime::SS58Prefix::get().try_into().unwrap()); + pallet_bags_list::Pallet::::try_state().unwrap(); + log::info!(target: crate::LOG_TARGET, "executed bags-list sanity check with no errors."); + + crate::display_and_check_bags::(currency_unit, currency_name); + }); +} diff --git a/frame/bags-list/src/benchmarks.rs b/frame/bags-list/src/benchmarks.rs new file mode 100644 index 0000000000000..1f66697cb6765 --- /dev/null +++ b/frame/bags-list/src/benchmarks.rs @@ -0,0 +1,194 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 bags list pallet. + +use super::*; +use crate::list::List; +use frame_benchmarking::{account, whitelist_account, whitelisted_caller}; +use frame_election_provider_support::ScoreProvider; +use frame_support::{assert_ok, traits::Get}; +use frame_system::RawOrigin as SystemOrigin; +use sp_runtime::traits::One; + +frame_benchmarking::benchmarks_instance_pallet! { + rebag_non_terminal { + // An expensive case for rebag-ing (rebag a non-terminal node): + // + // - The node to be rebagged, _R_, should exist as a non-terminal node in a bag with at + // least 2 other nodes. Thus _R_ will have both its `prev` and `next` nodes updated when + // it is removed. (3 W/R) + // - The destination bag is not empty, thus we need to update the `next` pointer of the last + // node in the destination in addition to the work we do otherwise. (2 W/R) + + // clear any pre-existing storage. + // NOTE: safe to call outside block production + List::::unsafe_clear(); + + // define our origin and destination thresholds. + let origin_bag_thresh = T::BagThresholds::get()[0]; + let dest_bag_thresh = T::BagThresholds::get()[1]; + + // seed items in the origin bag. + let origin_head: T::AccountId = account("origin_head", 0, 0); + assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); + + let origin_middle: T::AccountId = account("origin_middle", 0, 0); // the node we rebag (_R_) + assert_ok!(List::::insert(origin_middle.clone(), origin_bag_thresh)); + + let origin_tail: T::AccountId = account("origin_tail", 0, 0); + assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); + + // seed items in the destination bag. + let dest_head: T::AccountId = account("dest_head", 0, 0); + assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); + + let origin_middle_lookup = T::Lookup::unlookup(origin_middle.clone()); + + // the bags are in the expected state after initial setup. + assert_eq!( + List::::get_bags(), + vec![ + (origin_bag_thresh, vec![origin_head.clone(), origin_middle.clone(), origin_tail.clone()]), + (dest_bag_thresh, vec![dest_head.clone()]) + ] + ); + + let caller = whitelisted_caller(); + // update the weight of `origin_middle` to guarantee it will be rebagged into the destination. + T::ScoreProvider::set_score_of(&origin_middle, dest_bag_thresh); + }: rebag(SystemOrigin::Signed(caller), origin_middle_lookup.clone()) + verify { + // check the bags have updated as expected. + assert_eq!( + List::::get_bags(), + vec![ + ( + origin_bag_thresh, + vec![origin_head, origin_tail], + ), + ( + dest_bag_thresh, + vec![dest_head, origin_middle], + ) + ] + ); + } + + rebag_terminal { + // An expensive case for rebag-ing (rebag a terminal node): + // + // - The node to be rebagged, _R_, is a terminal node; so _R_, the node pointing to _R_ and + // the origin bag itself will need to be updated. (3 W/R) + // - The destination bag is not empty, thus we need to update the `next` pointer of the last + // node in the destination in addition to the work we do otherwise. (2 W/R) + + // clear any pre-existing storage. + // NOTE: safe to call outside block production + List::::unsafe_clear(); + + // define our origin and destination thresholds. + let origin_bag_thresh = T::BagThresholds::get()[0]; + let dest_bag_thresh = T::BagThresholds::get()[1]; + + // seed items in the origin bag. + let origin_head: T::AccountId = account("origin_head", 0, 0); + assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); + + let origin_tail: T::AccountId = account("origin_tail", 0, 0); // the node we rebag (_R_) + assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); + + // seed items in the destination bag. + let dest_head: T::AccountId = account("dest_head", 0, 0); + assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); + + let origin_tail_lookup = T::Lookup::unlookup(origin_tail.clone()); + + // the bags are in the expected state after initial setup. + assert_eq!( + List::::get_bags(), + vec![ + (origin_bag_thresh, vec![origin_head.clone(), origin_tail.clone()]), + (dest_bag_thresh, vec![dest_head.clone()]) + ] + ); + + let caller = whitelisted_caller(); + // update the weight of `origin_tail` to guarantee it will be rebagged into the destination. + T::ScoreProvider::set_score_of(&origin_tail, dest_bag_thresh); + }: rebag(SystemOrigin::Signed(caller), origin_tail_lookup.clone()) + verify { + // check the bags have updated as expected. + assert_eq!( + List::::get_bags(), + vec![ + (origin_bag_thresh, vec![origin_head.clone()]), + (dest_bag_thresh, vec![dest_head.clone(), origin_tail]) + ] + ); + } + + put_in_front_of { + // The most expensive case for `put_in_front_of`: + // + // - both heavier's `prev` and `next` are nodes that will need to be read and written. + // - `lighter` is the bag's `head`, so the bag will need to be read and written. + + // clear any pre-existing storage. + // NOTE: safe to call outside block production + List::::unsafe_clear(); + + let bag_thresh = T::BagThresholds::get()[0]; + + // insert the nodes in order + let lighter: T::AccountId = account("lighter", 0, 0); + assert_ok!(List::::insert(lighter.clone(), bag_thresh)); + + let heavier_prev: T::AccountId = account("heavier_prev", 0, 0); + assert_ok!(List::::insert(heavier_prev.clone(), bag_thresh)); + + let heavier: T::AccountId = account("heavier", 0, 0); + assert_ok!(List::::insert(heavier.clone(), bag_thresh)); + + let heavier_next: T::AccountId = account("heavier_next", 0, 0); + assert_ok!(List::::insert(heavier_next.clone(), bag_thresh)); + + T::ScoreProvider::set_score_of(&lighter, bag_thresh - One::one()); + T::ScoreProvider::set_score_of(&heavier, bag_thresh); + + let lighter_lookup = T::Lookup::unlookup(lighter.clone()); + + assert_eq!( + List::::iter().map(|n| n.id().clone()).collect::>(), + vec![lighter.clone(), heavier_prev.clone(), heavier.clone(), heavier_next.clone()] + ); + + whitelist_account!(heavier); + }: _(SystemOrigin::Signed(heavier.clone()), lighter_lookup.clone()) + verify { + assert_eq!( + List::::iter().map(|n| n.id().clone()).collect::>(), + vec![heavier, lighter, heavier_prev, heavier_next] + ) + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::ExtBuilder::default().skip_genesis_ids().build(), + crate::mock::Runtime + ); +} diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs new file mode 100644 index 0000000000000..1ffdf29345513 --- /dev/null +++ b/frame/bags-list/src/lib.rs @@ -0,0 +1,402 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! # Bags-List Pallet +//! +//! A semi-sorted list, where items hold an `AccountId` based on some `Score`. The +//! `AccountId` (`id` for short) might be synonym to a `voter` or `nominator` in some context, and +//! `Score` signifies the chance of each id being included in the final +//! [`SortedListProvider::iter`]. +//! +//! It implements [`frame_election_provider_support::SortedListProvider`] to provide a semi-sorted +//! list of accounts to another pallet. It needs some other pallet to give it some information about +//! the weights of accounts via [`frame_election_provider_support::ScoreProvider`]. +//! +//! This pallet is not configurable at genesis. Whoever uses it should call appropriate functions of +//! the `SortedListProvider` (e.g. `on_insert`, or `unsafe_regenerate`) at their genesis. +//! +//! # Goals +//! +//! The data structure exposed by this pallet aims to be optimized for: +//! +//! - insertions and removals. +//! - iteration over the top* N items by score, where the precise ordering of items doesn't +//! particularly matter. +//! +//! # Details +//! +//! - items are kept in bags, which are delineated by their range of score (See +//! [`Config::BagThresholds`]). +//! - for iteration, bags are chained together from highest to lowest and elements within the bag +//! are iterated from head to tail. +//! - items within a bag are iterated in order of insertion. Thus removing an item and re-inserting +//! it will worsen its position in list iteration; this reduces incentives for some types of spam +//! that involve consistently removing and inserting for better position. Further, ordering +//! granularity is thus dictated by range between each bag threshold. +//! - if an item's score changes to a value no longer within the range of its current bag the item's +//! position will need to be updated by an external actor with rebag (update), or removal and +//! insertion. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::FullCodec; +use frame_election_provider_support::{ScoreProvider, SortedListProvider}; +use frame_system::ensure_signed; +use sp_runtime::traits::{AtLeast32BitUnsigned, Bounded, StaticLookup}; +use sp_std::prelude::*; + +#[cfg(any(feature = "runtime-benchmarks", test))] +mod benchmarks; + +mod list; +pub mod migrations; +#[cfg(any(test, feature = "fuzz"))] +pub mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +pub use list::{notional_bag_for, Bag, List, ListError, Node}; +pub use pallet::*; +pub use weights::WeightInfo; + +pub(crate) const LOG_TARGET: &str = "runtime::bags_list"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 👜 [{}]", $patter), + >::block_number(), + as frame_support::traits::PalletInfoAccess>::name() + $(, $values)* + ) + }; +} + +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(crate) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: weights::WeightInfo; + + /// Something that provides the scores of ids. + type ScoreProvider: ScoreProvider; + + /// The list of thresholds separating the various bags. + /// + /// Ids are separated into unsorted bags according to their score. This specifies the + /// thresholds separating the bags. An id's bag is the largest bag for which the id's score + /// is less than or equal to its upper threshold. + /// + /// When ids are iterated, higher bags are iterated completely before lower bags. This means + /// that iteration is _semi-sorted_: ids of higher score tend to come before ids of lower + /// score, but peer ids within a particular bag are sorted in insertion order. + /// + /// # Expressing the constant + /// + /// This constant must be sorted in strictly increasing order. Duplicate items are not + /// permitted. + /// + /// There is an implied upper limit of `Score::MAX`; that value does not need to be + /// specified within the bag. For any two threshold lists, if one ends with + /// `Score::MAX`, the other one does not, and they are otherwise equal, the two + /// lists will behave identically. + /// + /// # Calculation + /// + /// It is recommended to generate the set of thresholds in a geometric series, such that + /// there exists some constant ratio such that `threshold[k + 1] == (threshold[k] * + /// constant_ratio).max(threshold[k] + 1)` for all `k`. + /// + /// The helpers in the `/utils/frame/generate-bags` module can simplify this calculation. + /// + /// # Examples + /// + /// - If `BagThresholds::get().is_empty()`, then all ids are put into the same bag, and + /// iteration is strictly in insertion order. + /// - If `BagThresholds::get().len() == 64`, and the thresholds are determined according to + /// the procedure given above, then the constant ratio is equal to 2. + /// - If `BagThresholds::get().len() == 200`, and the thresholds are determined according to + /// the procedure given above, then the constant ratio is approximately equal to 1.248. + /// - If the threshold list begins `[1, 2, 3, ...]`, then an id with score 0 or 1 will fall + /// into bag 0, an id with score 2 will fall into bag 1, etc. + /// + /// # Migration + /// + /// In the event that this list ever changes, a copy of the old bags list must be retained. + /// With that `List::migrate` can be called, which will perform the appropriate migration. + #[pallet::constant] + type BagThresholds: Get<&'static [Self::Score]>; + + /// The type used to dictate a node position relative to other nodes. + type Score: Clone + + Default + + PartialEq + + Eq + + Ord + + PartialOrd + + sp_std::fmt::Debug + + Copy + + AtLeast32BitUnsigned + + Bounded + + TypeInfo + + FullCodec + + MaxEncodedLen; + } + + /// A single node, within some bag. + /// + /// Nodes store links forward and back within their respective bags. + #[pallet::storage] + pub(crate) type ListNodes, I: 'static = ()> = + CountedStorageMap<_, Twox64Concat, T::AccountId, list::Node>; + + /// A bag stored in storage. + /// + /// Stores a `Bag` struct, which stores head and tail pointers to itself. + #[pallet::storage] + pub(crate) type ListBags, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::Score, list::Bag>; + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// Moved an account from one bag to another. + Rebagged { who: T::AccountId, from: T::Score, to: T::Score }, + /// Updated the score of some account to the given amount. + ScoreUpdated { who: T::AccountId, new_score: T::Score }, + } + + #[pallet::error] + #[cfg_attr(test, derive(PartialEq))] + pub enum Error { + /// A error in the list interface implementation. + List(ListError), + } + + impl From for Error { + fn from(t: ListError) -> Self { + Error::::List(t) + } + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Declare that some `dislocated` account has, through rewards or penalties, sufficiently + /// changed its score that it should properly fall into a different bag than its current + /// one. + /// + /// Anyone can call this function about any potentially dislocated account. + /// + /// Will always update the stored score of `dislocated` to the correct score, based on + /// `ScoreProvider`. + /// + /// If `dislocated` does not exists, it returns an error. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::rebag_non_terminal().max(T::WeightInfo::rebag_terminal()))] + pub fn rebag(origin: OriginFor, dislocated: AccountIdLookupOf) -> DispatchResult { + ensure_signed(origin)?; + let dislocated = T::Lookup::lookup(dislocated)?; + let current_score = T::ScoreProvider::score(&dislocated); + let _ = Pallet::::do_rebag(&dislocated, current_score) + .map_err::, _>(Into::into)?; + Ok(()) + } + + /// Move the caller's Id directly in front of `lighter`. + /// + /// The dispatch origin for this call must be _Signed_ and can only be called by the Id of + /// the account going in front of `lighter`. + /// + /// Only works if + /// - both nodes are within the same bag, + /// - and `origin` has a greater `Score` than `lighter`. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::put_in_front_of())] + pub fn put_in_front_of( + origin: OriginFor, + lighter: AccountIdLookupOf, + ) -> DispatchResult { + let heavier = ensure_signed(origin)?; + let lighter = T::Lookup::lookup(lighter)?; + List::::put_in_front_of(&lighter, &heavier) + .map_err::, _>(Into::into) + .map_err::(Into::into) + } + } + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + fn integrity_test() { + // ensure they are strictly increasing, this also implies that duplicates are detected. + assert!( + T::BagThresholds::get().windows(2).all(|window| window[1] > window[0]), + "thresholds must strictly increase, and have no duplicates", + ); + } + + #[cfg(feature = "try-runtime")] + fn try_state(_: BlockNumberFor) -> Result<(), &'static str> { + >::try_state() + } + } +} + +impl, I: 'static> Pallet { + /// Move an account from one bag to another, depositing an event on success. + /// + /// If the account changed bags, returns `Ok(Some((from, to)))`. + pub fn do_rebag( + account: &T::AccountId, + new_score: T::Score, + ) -> Result, ListError> { + // If no voter at that node, don't do anything. the caller just wasted the fee to call this. + let node = list::Node::::get(&account).ok_or(ListError::NodeNotFound)?; + let maybe_movement = List::update_position_for(node, new_score); + if let Some((from, to)) = maybe_movement { + Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); + }; + Self::deposit_event(Event::::ScoreUpdated { who: account.clone(), new_score }); + Ok(maybe_movement) + } + + /// Equivalent to `ListBags::get`, but public. Useful for tests in outside of this crate. + #[cfg(feature = "std")] + pub fn list_bags_get(score: T::Score) -> Option> { + ListBags::get(score) + } +} + +impl, I: 'static> SortedListProvider for Pallet { + type Error = ListError; + type Score = T::Score; + + fn iter() -> Box> { + Box::new(List::::iter().map(|n| n.id().clone())) + } + + fn iter_from( + start: &T::AccountId, + ) -> Result>, Self::Error> { + let iter = List::::iter_from(start)?; + Ok(Box::new(iter.map(|n| n.id().clone()))) + } + + fn count() -> u32 { + ListNodes::::count() + } + + fn contains(id: &T::AccountId) -> bool { + List::::contains(id) + } + + fn on_insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> { + List::::insert(id, score) + } + + fn get_score(id: &T::AccountId) -> Result { + List::::get_score(id) + } + + fn on_update(id: &T::AccountId, new_score: T::Score) -> Result<(), ListError> { + Pallet::::do_rebag(id, new_score).map(|_| ()) + } + + fn on_remove(id: &T::AccountId) -> Result<(), ListError> { + List::::remove(id) + } + + fn unsafe_regenerate( + all: impl IntoIterator, + score_of: Box T::Score>, + ) -> u32 { + // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_regenerate. + // I.e. because it can lead to many storage accesses. + // So it is ok to call it as caller must ensure the conditions. + List::::unsafe_regenerate(all, score_of) + } + + fn try_state() -> Result<(), &'static str> { + List::::try_state() + } + + fn unsafe_clear() { + // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_clear. + // I.e. because it can lead to many storage accesses. + // So it is ok to call it as caller must ensure the conditions. + List::::unsafe_clear() + } + + #[cfg(feature = "runtime-benchmarks")] + fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { + use frame_support::traits::Get as _; + let thresholds = T::BagThresholds::get(); + let node = list::Node::::get(who).unwrap(); + let current_bag_idx = thresholds + .iter() + .chain(sp_std::iter::once(&T::Score::max_value())) + .position(|w| w == &node.bag_upper()) + .unwrap(); + + if is_increase { + let next_threshold_idx = current_bag_idx + 1; + assert!(thresholds.len() > next_threshold_idx); + thresholds[next_threshold_idx] + } else { + assert!(current_bag_idx != 0); + let prev_threshold_idx = current_bag_idx - 1; + thresholds[prev_threshold_idx] + } + } +} + +impl, I: 'static> ScoreProvider for Pallet { + type Score = as SortedListProvider>::Score; + + fn score(id: &T::AccountId) -> T::Score { + Node::::get(id).map(|node| node.score()).unwrap_or_default() + } + + #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", test))] + fn set_score_of(id: &T::AccountId, new_score: T::Score) { + ListNodes::::mutate(id, |maybe_node| { + if let Some(node) = maybe_node.as_mut() { + node.set_score(new_score) + } else { + panic!("trying to mutate {:?} which does not exists", id); + } + }) + } +} diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs new file mode 100644 index 0000000000000..272526ad1a636 --- /dev/null +++ b/frame/bags-list/src/list/mod.rs @@ -0,0 +1,919 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Implementation of a "bags list": a semi-sorted list where ordering granularity is dictated by +//! configurable thresholds that delineate the boundaries of bags. It uses a pattern of composite +//! data structures, where multiple storage items are masked by one outer API. See +//! [`crate::ListNodes`], [`crate::ListBags`] for more information. +//! +//! The outer API of this module is the [`List`] struct. It wraps all acceptable operations on top +//! of the aggregate linked list. All operations with the bags list should happen through this +//! interface. + +use crate::Config; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_election_provider_support::ScoreProvider; +use frame_support::{ + defensive, ensure, + traits::{Defensive, DefensiveOption, Get}, + DefaultNoBound, PalletError, +}; +use scale_info::TypeInfo; +use sp_runtime::traits::{Bounded, Zero}; +use sp_std::{ + boxed::Box, + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + iter, + marker::PhantomData, + prelude::*, +}; + +#[derive(Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, PalletError)] +pub enum ListError { + /// A duplicate id has been detected. + Duplicate, + /// An Id does not have a greater score than another Id. + NotHeavier, + /// Attempted to place node in front of a node in another bag. + NotInSameBag, + /// Given node id was not found. + NodeNotFound, +} + +#[cfg(test)] +mod tests; + +/// Given a certain score, to which bag does it belong to? +/// +/// Bags are identified by their upper threshold; the value returned by this function is guaranteed +/// to be a member of `T::BagThresholds`. +/// +/// Note that even if the thresholds list does not have `T::Score::max_value()` as its final member, +/// this function behaves as if it does. +pub fn notional_bag_for, I: 'static>(score: T::Score) -> T::Score { + let thresholds = T::BagThresholds::get(); + let idx = thresholds.partition_point(|&threshold| score > threshold); + thresholds.get(idx).copied().unwrap_or_else(T::Score::max_value) +} + +/// The **ONLY** entry point of this module. All operations to the bags-list should happen through +/// this interface. It is forbidden to access other module members directly. +// +// Data structure providing efficient mostly-accurate selection of the top N id by `Score`. +// +// It's implemented as a set of linked lists. Each linked list comprises a bag of ids of +// arbitrary and unbounded length, all having a score within a particular constant range. +// This structure means that ids can be added and removed in `O(1)` time. +// +// Iteration is accomplished by chaining the iteration of each bag, from greatest to least. While +// the users within any particular bag are sorted in an entirely arbitrary order, the overall score +// decreases as successive bags are reached. This means that it is valid to truncate +// iteration at any desired point; only those ids in the lowest bag can be excluded. This +// satisfies both the desire for fairness and the requirement for efficiency. +pub struct List, I: 'static = ()>(PhantomData<(T, I)>); + +impl, I: 'static> List { + /// Remove all data associated with the list from storage. + /// + /// ## WARNING + /// + /// this function should generally not be used in production as it could lead to a very large + /// number of storage accesses. + pub(crate) fn unsafe_clear() { + #[allow(deprecated)] + crate::ListBags::::remove_all(None); + #[allow(deprecated)] + crate::ListNodes::::remove_all(); + } + + /// Regenerate all of the data from the given ids. + /// + /// WARNING: this is expensive and should only ever be performed when the list needs to be + /// generated from scratch. Care needs to be taken to ensure + /// + /// This may or may not need to be called at genesis as well, based on the configuration of the + /// pallet using this `List`. + /// + /// Returns the number of ids migrated. + pub fn unsafe_regenerate( + all: impl IntoIterator, + score_of: Box T::Score>, + ) -> u32 { + // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_regenerate. + // I.e. because it can lead to many storage accesses. + // So it is ok to call it as caller must ensure the conditions. + Self::unsafe_clear(); + Self::insert_many(all, score_of) + } + + /// Migrate the list from one set of thresholds to another. + /// + /// This should only be called as part of an intentional migration; it's fairly expensive. + /// + /// Returns the number of accounts affected. + /// + /// Preconditions: + /// + /// - `old_thresholds` is the previous list of thresholds. + /// - All `bag_upper` currently in storage are members of `old_thresholds`. + /// - `T::BagThresholds` has already been updated and is the new set of thresholds. + /// + /// Postconditions: + /// + /// - All `bag_upper` currently in storage are members of `T::BagThresholds`. + /// - No id is changed unless required to by the difference between the old threshold list and + /// the new. + /// - ids whose bags change at all are implicitly rebagged into the appropriate bag in the new + /// threshold set. + #[allow(dead_code)] + pub fn migrate(old_thresholds: &[T::Score]) -> u32 { + let new_thresholds = T::BagThresholds::get(); + if new_thresholds == old_thresholds { + return 0 + } + + // we can't check all preconditions, but we can check one + debug_assert!( + crate::ListBags::::iter() + .all(|(threshold, _)| old_thresholds.contains(&threshold)), + "not all `bag_upper` currently in storage are members of `old_thresholds`", + ); + debug_assert!( + crate::ListNodes::::iter() + .all(|(_, node)| old_thresholds.contains(&node.bag_upper)), + "not all `node.bag_upper` currently in storage are members of `old_thresholds`", + ); + + let old_set: BTreeSet<_> = old_thresholds.iter().copied().collect(); + let new_set: BTreeSet<_> = new_thresholds.iter().copied().collect(); + + // accounts that need to be rebagged + let mut affected_accounts = BTreeSet::new(); + // track affected old bags to make sure we only iterate them once + let mut affected_old_bags = BTreeSet::new(); + + let new_bags = new_set.difference(&old_set).copied(); + // a new bag means that all accounts previously using the old bag's threshold must now + // be rebagged + for inserted_bag in new_bags { + let affected_bag = { + // this recreates `notional_bag_for` logic, but with the old thresholds. + let idx = old_thresholds.partition_point(|&threshold| inserted_bag > threshold); + old_thresholds.get(idx).copied().unwrap_or_else(T::Score::max_value) + }; + if !affected_old_bags.insert(affected_bag) { + // If the previous threshold list was [10, 20], and we insert [3, 5], then there's + // no point iterating through bag 10 twice. + continue + } + + if let Some(bag) = Bag::::get(affected_bag) { + affected_accounts.extend(bag.iter().map(|node| node.id)); + } + } + + let removed_bags = old_set.difference(&new_set).copied(); + // a removed bag means that all members of that bag must be rebagged + for removed_bag in removed_bags.clone() { + if !affected_old_bags.insert(removed_bag) { + continue + } + + if let Some(bag) = Bag::::get(removed_bag) { + affected_accounts.extend(bag.iter().map(|node| node.id)); + } + } + + // migrate the voters whose bag has changed + let num_affected = affected_accounts.len() as u32; + let score_of = T::ScoreProvider::score; + let _removed = Self::remove_many(&affected_accounts); + debug_assert_eq!(_removed, num_affected); + let _inserted = Self::insert_many(affected_accounts.into_iter(), score_of); + debug_assert_eq!(_inserted, num_affected); + + // we couldn't previously remove the old bags because both insertion and removal assume that + // it's always safe to add a bag if it's not present. Now that that's sorted, we can get rid + // of them. + // + // it's pretty cheap to iterate this again, because both sets are in-memory and require no + // lookups. + for removed_bag in removed_bags { + debug_assert!( + !crate::ListNodes::::iter().any(|(_, node)| node.bag_upper == removed_bag), + "no id should be present in a removed bag", + ); + crate::ListBags::::remove(removed_bag); + } + + #[cfg(feature = "std")] + debug_assert_eq!(Self::try_state(), Ok(())); + + num_affected + } + + /// Returns `true` if the list contains `id`, otherwise returns `false`. + pub(crate) fn contains(id: &T::AccountId) -> bool { + crate::ListNodes::::contains_key(id) + } + + /// Get the score of the given node, + pub fn get_score(id: &T::AccountId) -> Result { + Node::::get(id).map(|node| node.score()).ok_or(ListError::NodeNotFound) + } + + /// Iterate over all nodes in all bags in the list. + /// + /// Full iteration can be expensive; it's recommended to limit the number of items with + /// `.take(n)`. + pub(crate) fn iter() -> impl Iterator> { + // We need a touch of special handling here: because we permit `T::BagThresholds` to + // omit the final bound, we need to ensure that we explicitly include that threshold in the + // list. + // + // It's important to retain the ability to omit the final bound because it makes tests much + // easier; they can just configure `type BagThresholds = ()`. + let thresholds = T::BagThresholds::get(); + let iter = thresholds.iter().copied(); + let iter: Box> = if thresholds.last() == + Some(&T::Score::max_value()) + { + // in the event that they included it, we can just pass the iterator through unchanged. + Box::new(iter.rev()) + } else { + // otherwise, insert it here. + Box::new(iter.chain(iter::once(T::Score::max_value())).rev()) + }; + + iter.filter_map(Bag::get).flat_map(|bag| bag.iter()) + } + + /// Same as `iter`, but we start from a specific node. + /// + /// All items after this node are returned, excluding `start` itself. + pub(crate) fn iter_from( + start: &T::AccountId, + ) -> Result>, ListError> { + // We chain two iterators: + // 1. from the given `start` till the end of the bag + // 2. all the bags that come after `start`'s bag. + + let start_node = Node::::get(start).ok_or(ListError::NodeNotFound)?; + let start_node_upper = start_node.bag_upper; + let start_bag = sp_std::iter::successors(start_node.next(), |prev| prev.next()); + + let thresholds = T::BagThresholds::get(); + let idx = thresholds.partition_point(|&threshold| start_node_upper > threshold); + let leftover_bags = thresholds + .into_iter() + .take(idx) + .copied() + .rev() + .filter_map(Bag::get) + .flat_map(|bag| bag.iter()); + + Ok(start_bag.chain(leftover_bags)) + } + + /// Insert several ids into the appropriate bags in the list. Continues with insertions + /// if duplicates are detected. + /// + /// Returns the final count of number of ids inserted. + fn insert_many( + ids: impl IntoIterator, + score_of: impl Fn(&T::AccountId) -> T::Score, + ) -> u32 { + let mut count = 0; + ids.into_iter().for_each(|v| { + let score = score_of(&v); + if Self::insert(v, score).is_ok() { + count += 1; + } + }); + + count + } + + /// Insert a new id into the appropriate bag in the list. + /// + /// Returns an error if the list already contains `id`. + pub(crate) fn insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> { + if Self::contains(&id) { + return Err(ListError::Duplicate) + } + + let bag_score = notional_bag_for::(score); + let mut bag = Bag::::get_or_make(bag_score); + // unchecked insertion is okay; we just got the correct `notional_bag_for`. + bag.insert_unchecked(id.clone(), score); + + // new inserts are always the tail, so we must write the bag. + bag.put(); + + crate::log!( + debug, + "inserted {:?} with score {:?} into bag {:?}, new count is {}", + id, + score, + bag_score, + crate::ListNodes::::count(), + ); + + Ok(()) + } + + /// Remove an id from the list, returning an error if `id` does not exists. + pub(crate) fn remove(id: &T::AccountId) -> Result<(), ListError> { + if !Self::contains(id) { + return Err(ListError::NodeNotFound) + } + let _ = Self::remove_many(sp_std::iter::once(id)); + Ok(()) + } + + /// Remove many ids from the list. + /// + /// This is more efficient than repeated calls to `Self::remove`. + /// + /// Returns the final count of number of ids removed. + fn remove_many<'a>(ids: impl IntoIterator) -> u32 { + let mut bags = BTreeMap::new(); + let mut count = 0; + + for id in ids.into_iter() { + let node = match Node::::get(id) { + Some(node) => node, + None => continue, + }; + count += 1; + + if !node.is_terminal() { + // this node is not a head or a tail and thus the bag does not need to be updated + node.excise() + } else { + // this node is a head or tail, so the bag needs to be updated + let bag = bags + .entry(node.bag_upper) + .or_insert_with(|| Bag::::get_or_make(node.bag_upper)); + // node.bag_upper must be correct, therefore this bag will contain this node. + bag.remove_node_unchecked(&node); + } + + // now get rid of the node itself + node.remove_from_storage_unchecked() + } + + for (_, bag) in bags { + bag.put(); + } + + count + } + + /// Update a node's position in the list. + /// + /// If the node was in the correct bag, no effect. If the node was in the incorrect bag, they + /// are moved into the correct bag. + /// + /// Returns `Some((old_idx, new_idx))` if the node moved, otherwise `None`. In both cases, the + /// node's score is written to the `score` field. Thus, this is not a noop, even if `None`. + /// + /// This operation is somewhat more efficient than simply calling [`self.remove`] followed by + /// [`self.insert`]. However, given large quantities of nodes to move, it may be more efficient + /// to call [`self.remove_many`] followed by [`self.insert_many`]. + pub(crate) fn update_position_for( + mut node: Node, + new_score: T::Score, + ) -> Option<(T::Score, T::Score)> { + node.score = new_score; + if node.is_misplaced(new_score) { + let old_bag_upper = node.bag_upper; + + if !node.is_terminal() { + // this node is not a head or a tail, so we can just cut it out of the list. update + // and put the prev and next of this node, we do `node.put` inside `insert_note`. + node.excise(); + } else if let Some(mut bag) = Bag::::get(node.bag_upper) { + // this is a head or tail, so the bag must be updated. + bag.remove_node_unchecked(&node); + bag.put(); + } else { + frame_support::defensive!( + "Node did not have a bag; BagsList is in an inconsistent state" + ); + } + + // put the node into the appropriate new bag. + let new_bag_upper = notional_bag_for::(new_score); + let mut bag = Bag::::get_or_make(new_bag_upper); + // prev, next, and bag_upper of the node are updated inside `insert_node`, also + // `node.put` is in there. + bag.insert_node_unchecked(node); + bag.put(); + + Some((old_bag_upper, new_bag_upper)) + } else { + // just write the new score. + node.put(); + None + } + } + + /// Put `heavier_id` to the position directly in front of `lighter_id`. Both ids must be in the + /// same bag and the `score_of` `lighter_id` must be less than that of `heavier_id`. + pub(crate) fn put_in_front_of( + lighter_id: &T::AccountId, + heavier_id: &T::AccountId, + ) -> Result<(), ListError> { + let lighter_node = Node::::get(&lighter_id).ok_or(ListError::NodeNotFound)?; + let heavier_node = Node::::get(&heavier_id).ok_or(ListError::NodeNotFound)?; + + ensure!(lighter_node.bag_upper == heavier_node.bag_upper, ListError::NotInSameBag); + + // this is the most expensive check, so we do it last. + ensure!( + T::ScoreProvider::score(&heavier_id) > T::ScoreProvider::score(&lighter_id), + ListError::NotHeavier + ); + + // remove the heavier node from this list. Note that this removes the node from storage and + // decrements the node counter. + let _ = + Self::remove(&heavier_id).defensive_proof("both nodes have been checked to exist; qed"); + + // re-fetch `lighter_node` from storage since it may have been updated when `heavier_node` + // was removed. + let lighter_node = + Node::::get(lighter_id).defensive_ok_or_else(|| ListError::NodeNotFound)?; + + // insert `heavier_node` directly in front of `lighter_node`. This will update both nodes + // in storage and update the node counter. + Self::insert_at_unchecked(lighter_node, heavier_node); + + Ok(()) + } + + /// Insert `node` directly in front of `at`. + /// + /// WARNINGS: + /// - this is a naive function in that it does not check if `node` belongs to the same bag as + /// `at`. It is expected that the call site will check preconditions. + /// - this will panic if `at.bag_upper` is not a bag that already exists in storage. + fn insert_at_unchecked(mut at: Node, mut node: Node) { + // connect `node` to its new `prev`. + node.prev = at.prev.clone(); + if let Some(mut prev) = at.prev() { + prev.next = Some(node.id().clone()); + prev.put() + } + + // connect `node` and `at`. + node.next = Some(at.id().clone()); + at.prev = Some(node.id().clone()); + + if node.is_terminal() { + // `node` is the new head, so we make sure the bag is updated. Note, + // since `node` is always in front of `at` we know that 1) there is always at least 2 + // nodes in the bag, and 2) only `node` could be the head and only `at` could be the + // tail. + let mut bag = Bag::::get(at.bag_upper) + .expect("given nodes must always have a valid bag. qed."); + + if node.prev == None { + bag.head = Some(node.id().clone()) + } + + bag.put() + }; + + // write the updated nodes to storage. + at.put(); + node.put(); + } + + /// Check the internal state of the list. + /// + /// This should be called from the call-site, whenever one of the mutating apis (e.g. `insert`) + /// is being used, after all other staking data (such as counter) has been updated. It checks: + /// + /// * there are no duplicate ids, + /// * length of this list is in sync with `ListNodes::count()`, + /// * and sanity-checks all bags and nodes. This will cascade down all the checks and makes sure + /// all bags and nodes are checked per *any* update to `List`. + pub(crate) fn try_state() -> Result<(), &'static str> { + let mut seen_in_list = BTreeSet::new(); + ensure!( + Self::iter().map(|node| node.id).all(|id| seen_in_list.insert(id)), + "duplicate identified", + ); + + let iter_count = Self::iter().count() as u32; + let stored_count = crate::ListNodes::::count(); + let nodes_count = crate::ListNodes::::iter().count() as u32; + ensure!(iter_count == stored_count, "iter_count != stored_count"); + ensure!(stored_count == nodes_count, "stored_count != nodes_count"); + + crate::log!(trace, "count of nodes: {}", stored_count); + + let active_bags = { + let thresholds = T::BagThresholds::get().iter().copied(); + let thresholds: Vec = + if thresholds.clone().last() == Some(T::Score::max_value()) { + // in the event that they included it, we don't need to make any changes + thresholds.collect() + } else { + // otherwise, insert it here. + thresholds.chain(iter::once(T::Score::max_value())).collect() + }; + thresholds.into_iter().filter_map(|t| Bag::::get(t)) + }; + + let _ = active_bags.clone().try_for_each(|b| b.try_state())?; + + let nodes_in_bags_count = + active_bags.clone().fold(0u32, |acc, cur| acc + cur.iter().count() as u32); + ensure!(nodes_count == nodes_in_bags_count, "stored_count != nodes_in_bags_count"); + + crate::log!(trace, "count of active bags {}", active_bags.count()); + + // check that all nodes are sane. We check the `ListNodes` storage item directly in case we + // have some "stale" nodes that are not in a bag. + for (_id, node) in crate::ListNodes::::iter() { + node.try_state()? + } + + Ok(()) + } + + /// Returns the nodes of all non-empty bags. For testing and benchmarks. + #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] + #[allow(dead_code)] + pub(crate) fn get_bags() -> Vec<(T::Score, Vec)> { + use frame_support::traits::Get as _; + + let thresholds = T::BagThresholds::get(); + let iter = thresholds.iter().copied(); + let iter: Box> = if thresholds.last() == + Some(&T::Score::max_value()) + { + // in the event that they included it, we can just pass the iterator through unchanged. + Box::new(iter) + } else { + // otherwise, insert it here. + Box::new(iter.chain(sp_std::iter::once(T::Score::max_value()))) + }; + + iter.filter_map(|t| { + Bag::::get(t) + .map(|bag| (t, bag.iter().map(|n| n.id().clone()).collect::>())) + }) + .collect::>() + } +} + +/// A Bag is a doubly-linked list of ids, where each id is mapped to a [`Node`]. +/// +/// Note that we maintain both head and tail pointers. While it would be possible to get away with +/// maintaining only a head pointer and cons-ing elements onto the front of the list, it's more +/// desirable to ensure that there is some element of first-come, first-serve to the list's +/// iteration so that there's no incentive to churn ids positioning to improve the chances of +/// appearing within the ids set. +#[derive(DefaultNoBound, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T, I))] +#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] +pub struct Bag, I: 'static = ()> { + head: Option, + tail: Option, + + #[codec(skip)] + bag_upper: T::Score, + #[codec(skip)] + _phantom: PhantomData, +} + +impl, I: 'static> Bag { + #[cfg(test)] + pub(crate) fn new( + head: Option, + tail: Option, + bag_upper: T::Score, + ) -> Self { + Self { head, tail, bag_upper, _phantom: PhantomData } + } + + /// Get a bag by its upper score. + pub(crate) fn get(bag_upper: T::Score) -> Option> { + crate::ListBags::::try_get(bag_upper).ok().map(|mut bag| { + bag.bag_upper = bag_upper; + bag + }) + } + + /// Get a bag by its upper score or make it, appropriately initialized. Does not check if + /// if `bag_upper` is a valid threshold. + fn get_or_make(bag_upper: T::Score) -> Bag { + Self::get(bag_upper).unwrap_or(Bag { bag_upper, ..Default::default() }) + } + + /// `True` if self is empty. + fn is_empty(&self) -> bool { + self.head.is_none() && self.tail.is_none() + } + + /// Put the bag back into storage. + fn put(self) { + if self.is_empty() { + crate::ListBags::::remove(self.bag_upper); + } else { + crate::ListBags::::insert(self.bag_upper, self); + } + } + + /// Get the head node in this bag. + fn head(&self) -> Option> { + self.head.as_ref().and_then(|id| Node::get(id)) + } + + /// Get the tail node in this bag. + fn tail(&self) -> Option> { + self.tail.as_ref().and_then(|id| Node::get(id)) + } + + /// Iterate over the nodes in this bag. + pub(crate) fn iter(&self) -> impl Iterator> { + sp_std::iter::successors(self.head(), |prev| prev.next()) + } + + /// Insert a new id into this bag. + /// + /// This is private on purpose because it's naive: it doesn't check whether this is the + /// appropriate bag for this id at all. Generally, use [`List::insert`] instead. + /// + /// Storage note: this modifies storage, but only for the nodes. You still need to call + /// `self.put()` after use. + fn insert_unchecked(&mut self, id: T::AccountId, score: T::Score) { + // insert_node will overwrite `prev`, `next` and `bag_upper` to the proper values. As long + // as this bag is the correct one, we're good. All calls to this must come after getting the + // correct [`notional_bag_for`]. + self.insert_node_unchecked(Node:: { + id, + prev: None, + next: None, + bag_upper: Zero::zero(), + score, + _phantom: PhantomData, + }); + } + + /// Insert a node into this bag. + /// + /// This is private on purpose because it's naive; it doesn't check whether this is the + /// appropriate bag for this node at all. Generally, use [`List::insert`] instead. + /// + /// Storage note: this modifies storage, but only for the node. You still need to call + /// `self.put()` after use. + fn insert_node_unchecked(&mut self, mut node: Node) { + if let Some(tail) = &self.tail { + if *tail == node.id { + // this should never happen, but this check prevents one path to a worst case + // infinite loop. + defensive!("system logic error: inserting a node who has the id of tail"); + return + }; + } + + // re-set the `bag_upper`. Regardless of whatever the node had previously, now it is going + // to be `self.bag_upper`. + node.bag_upper = self.bag_upper; + + let id = node.id.clone(); + // update this node now, treating it as the new tail. + node.prev = self.tail.clone(); + node.next = None; + node.put(); + + // update the previous tail. + if let Some(mut old_tail) = self.tail() { + old_tail.next = Some(id.clone()); + old_tail.put(); + } + self.tail = Some(id.clone()); + + // ensure head exist. This is only set when the length of the bag is just 1, i.e. if this is + // the first insertion into the bag. In this case, both head and tail should point to the + // same node. + if self.head.is_none() { + self.head = Some(id); + debug_assert!(self.iter().count() == 1); + } + } + + /// Remove a node from this bag. + /// + /// This is private on purpose because it doesn't check whether this bag contains the node in + /// the first place. Generally, use [`List::remove`] instead, similar to `insert_unchecked`. + /// + /// Storage note: this modifies storage, but only for adjacent nodes. You still need to call + /// `self.put()` and `ListNodes::remove(id)` to update storage for the bag and `node`. + fn remove_node_unchecked(&mut self, node: &Node) { + // reassign neighboring nodes. + node.excise(); + + // clear the bag head/tail pointers as necessary. + if self.tail.as_ref() == Some(&node.id) { + self.tail = node.prev.clone(); + } + if self.head.as_ref() == Some(&node.id) { + self.head = node.next.clone(); + } + } + + /// Check the internal state of the bag. + /// + /// Should be called by the call-site, after any mutating operation on a bag. The call site of + /// this struct is always `List`. + /// + /// * Ensures head has no prev. + /// * Ensures tail has no next. + /// * Ensures there are no loops, traversal from head to tail is correct. + fn try_state(&self) -> Result<(), &'static str> { + frame_support::ensure!( + self.head() + .map(|head| head.prev().is_none()) + // if there is no head, then there must not be a tail, meaning that the bag is + // empty. + .unwrap_or_else(|| self.tail.is_none()), + "head has a prev" + ); + + frame_support::ensure!( + self.tail() + .map(|tail| tail.next().is_none()) + // if there is no tail, then there must not be a head, meaning that the bag is + // empty. + .unwrap_or_else(|| self.head.is_none()), + "tail has a next" + ); + + let mut seen_in_bag = BTreeSet::new(); + frame_support::ensure!( + self.iter() + .map(|node| node.id) + // each voter is only seen once, thus there is no cycle within a bag + .all(|voter| seen_in_bag.insert(voter)), + "duplicate found in bag" + ); + + Ok(()) + } + + /// Iterate over the nodes in this bag (public for tests). + #[cfg(feature = "std")] + #[allow(dead_code)] + pub fn std_iter(&self) -> impl Iterator> { + sp_std::iter::successors(self.head(), |prev| prev.next()) + } + + /// Check if the bag contains a node with `id`. + fn contains(&self, id: &T::AccountId) -> bool { + self.iter().any(|n| n.id() == id) + } +} + +/// A Node is the fundamental element comprising the doubly-linked list described by `Bag`. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T, I))] +#[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] +pub struct Node, I: 'static = ()> { + pub(crate) id: T::AccountId, + pub(crate) prev: Option, + pub(crate) next: Option, + pub(crate) bag_upper: T::Score, + pub(crate) score: T::Score, + #[codec(skip)] + pub(crate) _phantom: PhantomData, +} + +impl, I: 'static> Node { + /// Get a node by id. + pub fn get(id: &T::AccountId) -> Option> { + crate::ListNodes::::try_get(id).ok() + } + + /// Put the node back into storage. + fn put(self) { + crate::ListNodes::::insert(self.id.clone(), self); + } + + /// Update neighboring nodes to point to reach other. + /// + /// Only updates storage for adjacent nodes, but not `self`; so the user may need to call + /// `self.put`. + fn excise(&self) { + // Update previous node. + if let Some(mut prev) = self.prev() { + prev.next = self.next.clone(); + prev.put(); + } + // Update next self. + if let Some(mut next) = self.next() { + next.prev = self.prev.clone(); + next.put(); + } + } + + /// This is a naive function that removes a node from the `ListNodes` storage item. + /// + /// It is naive because it does not check if the node has first been removed from its bag. + fn remove_from_storage_unchecked(&self) { + crate::ListNodes::::remove(&self.id) + } + + /// Get the previous node in the bag. + fn prev(&self) -> Option> { + self.prev.as_ref().and_then(|id| Node::get(id)) + } + + /// Get the next node in the bag. + fn next(&self) -> Option> { + self.next.as_ref().and_then(|id| Node::get(id)) + } + + /// `true` when this voter is in the wrong bag. + pub fn is_misplaced(&self, current_score: T::Score) -> bool { + notional_bag_for::(current_score) != self.bag_upper + } + + /// `true` when this voter is a bag head or tail. + fn is_terminal(&self) -> bool { + self.prev.is_none() || self.next.is_none() + } + + /// Get the underlying voter. + pub(crate) fn id(&self) -> &T::AccountId { + &self.id + } + + /// Get the current vote weight of the node. + pub(crate) fn score(&self) -> T::Score { + self.score + } + + /// Get the underlying voter (public fo tests). + #[cfg(feature = "std")] + #[allow(dead_code)] + pub fn std_id(&self) -> &T::AccountId { + &self.id + } + + #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", test))] + pub fn set_score(&mut self, s: T::Score) { + self.score = s + } + + /// The bag this nodes belongs to (public for benchmarks). + #[cfg(feature = "runtime-benchmarks")] + #[allow(dead_code)] + pub fn bag_upper(&self) -> T::Score { + self.bag_upper + } + + fn try_state(&self) -> Result<(), &'static str> { + let expected_bag = Bag::::get(self.bag_upper).ok_or("bag not found for node")?; + + let id = self.id(); + + frame_support::ensure!( + expected_bag.contains(id), + "node does not exist in the expected bag" + ); + + let non_terminal_check = !self.is_terminal() && + expected_bag.head.as_ref() != Some(id) && + expected_bag.tail.as_ref() != Some(id); + let terminal_check = + expected_bag.head.as_ref() == Some(id) || expected_bag.tail.as_ref() == Some(id); + frame_support::ensure!( + non_terminal_check || terminal_check, + "a terminal node is neither its bag head or tail" + ); + + Ok(()) + } +} diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs new file mode 100644 index 0000000000000..966ea1a74c71c --- /dev/null +++ b/frame/bags-list/src/list/tests.rs @@ -0,0 +1,958 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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::{ + mock::{test_utils::*, *}, + ListBags, ListNodes, +}; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; +use frame_support::{assert_ok, assert_storage_noop}; + +fn node( + id: AccountId, + prev: Option, + next: Option, + bag_upper: VoteWeight, +) -> Node { + Node:: { id, prev, next, bag_upper, score: bag_upper, _phantom: PhantomData } +} + +#[test] +fn basic_setup_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(ListNodes::::count(), 4); + assert_eq!(ListNodes::::iter().count(), 4); + assert_eq!(ListBags::::iter().count(), 2); + + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + // the state of the bags is as expected + assert_eq!( + ListBags::::get(10).unwrap(), + Bag:: { head: Some(1), tail: Some(1), bag_upper: 0, _phantom: PhantomData } + ); + assert_eq!( + ListBags::::get(1_000).unwrap(), + Bag:: { head: Some(2), tail: Some(4), bag_upper: 0, _phantom: PhantomData } + ); + + assert_eq!(ListNodes::::get(2).unwrap(), node(2, None, Some(3), 1_000)); + assert_eq!(ListNodes::::get(3).unwrap(), node(3, Some(2), Some(4), 1_000)); + assert_eq!(ListNodes::::get(4).unwrap(), node(4, Some(3), None, 1_000)); + assert_eq!(ListNodes::::get(1).unwrap(), node(1, None, None, 10)); + + // non-existent id does not have a storage footprint + assert_eq!(ListNodes::::get(42), None); + + // iteration of the bags would yield: + assert_eq!( + List::::iter().map(|n| *n.id()).collect::>(), + vec![2, 3, 4, 1], + // ^^ note the order of insertion in genesis! + ); + }); +} + +#[test] +fn notional_bag_for_works() { + // under a threshold gives the next threshold. + assert_eq!(notional_bag_for::(0), 10); + assert_eq!(notional_bag_for::(9), 10); + + // at a threshold gives that threshold. + assert_eq!(notional_bag_for::(10), 10); + + // above the threshold, gives the next threshold. + assert_eq!(notional_bag_for::(11), 20); + + let max_explicit_threshold = *::BagThresholds::get().last().unwrap(); + assert_eq!(max_explicit_threshold, 10_000); + + // if the max explicit threshold is less than T::Score::max_value(), + assert!(VoteWeight::MAX > max_explicit_threshold); + + // then anything above it will belong to the T::Score::max_value() bag. + assert_eq!(notional_bag_for::(max_explicit_threshold), max_explicit_threshold); + assert_eq!(notional_bag_for::(max_explicit_threshold + 1), VoteWeight::MAX); +} + +#[test] +fn remove_last_node_in_bags_cleans_bag() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + // bump 1 to a bigger bag + List::::remove(&1).unwrap(); + assert_ok!(List::::insert(1, 10_000)); + + // then the bag with bound 10 is wiped from storage. + assert_eq!(List::::get_bags(), vec![(1_000, vec![2, 3, 4]), (10_000, vec![1])]); + + // and can be recreated again as needed. + assert_ok!(List::::insert(77, 10)); + assert_eq!( + List::::get_bags(), + vec![(10, vec![77]), (1_000, vec![2, 3, 4]), (10_000, vec![1])] + ); + }); +} + +#[test] +fn migrate_works() { + ExtBuilder::default() + .add_ids(vec![(710, 15), (711, 16), (712, 2_000)]) + .build_and_execute(|| { + // given + assert_eq!( + List::::get_bags(), + vec![ + (10, vec![1]), + (20, vec![710, 711]), + (1_000, vec![2, 3, 4]), + (2_000, vec![712]) + ] + ); + let old_thresholds = ::BagThresholds::get(); + assert_eq!(old_thresholds, vec![10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]); + + // when the new thresholds adds `15` and removes `2_000` + const NEW_THRESHOLDS: &'static [VoteWeight] = + &[10, 15, 20, 30, 40, 50, 60, 1_000, 10_000]; + BagThresholds::set(NEW_THRESHOLDS); + // and we call + List::::migrate(old_thresholds); + + // then + assert_eq!( + List::::get_bags(), + vec![ + (10, vec![1]), + (15, vec![710]), // nodes in range 11 ..= 15 move from bag 20 to bag 15 + (20, vec![711]), + (1_000, vec![2, 3, 4]), + // nodes in range 1_001 ..= 2_000 move from bag 2_000 to bag 10_000 + (10_000, vec![712]), + ] + ); + }); +} + +mod list { + use frame_support::assert_noop; + + use super::*; + + #[test] + fn iteration_is_semi_sorted() { + ExtBuilder::default() + .add_ids(vec![(5, 2_000), (6, 2_000)]) + .build_and_execute(|| { + // given + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![5, 6])] + ); + assert_eq!( + get_list_as_ids(), + vec![ + 5, 6, // best bag + 2, 3, 4, // middle bag + 1, // last bag. + ] + ); + + // when adding an id that has a higher score than pre-existing ids in the bag + assert_ok!(List::::insert(7, 10)); + + // then + assert_eq!( + get_list_as_ids(), + vec![ + 5, 6, // best bag + 2, 3, 4, // middle bag + 1, 7, // last bag; new id is last. + ] + ); + }) + } + + /// we can `take` x ids, even if that quantity ends midway through a list. + #[test] + fn take_works() { + ExtBuilder::default() + .add_ids(vec![(5, 2_000), (6, 2_000)]) + .build_and_execute(|| { + // given + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![5, 6])] + ); + + // when + let iteration = + List::::iter().map(|node| *node.id()).take(4).collect::>(); + + // then + assert_eq!( + iteration, + vec![ + 5, 6, // best bag, fully iterated + 2, 3, // middle bag, partially iterated + ] + ); + }) + } + + #[test] + fn insert_works() { + ExtBuilder::default().build_and_execute(|| { + // when inserting into an existing bag + assert_ok!(List::::insert(5, 1_000)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]); + assert_eq!(get_list_as_ids(), vec![2, 3, 4, 5, 1]); + + // when inserting into a non-existent bag + assert_ok!(List::::insert(6, 1_001)); + + // then + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5]), (2_000, vec![6])] + ); + assert_eq!(get_list_as_ids(), vec![6, 2, 3, 4, 5, 1]); + }); + } + + #[test] + fn insert_errors_with_duplicate_id() { + ExtBuilder::default().build_and_execute(|| { + // given + assert!(get_list_as_ids().contains(&3)); + + // then + assert_noop!(List::::insert(3, 20), ListError::Duplicate); + }); + } + + #[test] + fn remove_works() { + use crate::{ListBags, ListNodes}; + let ensure_left = |id, counter| { + assert!(!ListNodes::::contains_key(id)); + assert_eq!(ListNodes::::count(), counter); + assert_eq!(ListNodes::::iter().count() as u32, counter); + }; + + ExtBuilder::default().build_and_execute(|| { + // removing a non-existent id is a noop + assert!(!ListNodes::::contains_key(42)); + assert_noop!(List::::remove(&42), ListError::NodeNotFound); + + // when removing a node from a bag with multiple nodes: + List::::remove(&2).unwrap(); + + // then + assert_eq!(get_list_as_ids(), vec![3, 4, 1]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 4])]); + ensure_left(2, 3); + + // when removing a node from a bag with only one node: + List::::remove(&1).unwrap(); + + // then + assert_eq!(get_list_as_ids(), vec![3, 4]); + assert_eq!(List::::get_bags(), vec![(1_000, vec![3, 4])]); + ensure_left(1, 2); + // bag 10 is removed + assert!(!ListBags::::contains_key(10)); + + // remove remaining ids to make sure storage cleans up as expected + List::::remove(&3).unwrap(); + ensure_left(3, 1); + assert_eq!(get_list_as_ids(), vec![4]); + + List::::remove(&4).unwrap(); + ensure_left(4, 0); + assert_eq!(get_list_as_ids(), Vec::::new()); + + // bags are deleted via removals + assert_eq!(ListBags::::iter().count(), 0); + }); + } + + #[test] + fn remove_many_is_noop_with_non_existent_ids() { + ExtBuilder::default().build_and_execute(|| { + let non_existent_ids = vec![&42, &666, &13]; + + // when account ids don' exist in the list + assert!(non_existent_ids.iter().all(|id| !BagsList::contains(id))); + + // then removing them is a noop + assert_storage_noop!(List::::remove_many(non_existent_ids)); + }); + } + + #[test] + fn update_position_for_works() { + ExtBuilder::default().build_and_execute(|| { + // given a correctly placed account 1 at bag 10. + let node = Node::::get(&1).unwrap(); + assert_eq!(node.score, 10); + assert!(!node.is_misplaced(10)); + + // .. it is invalid with score 20 + assert!(node.is_misplaced(20)); + + // move it to bag 20. + assert_eq!(List::::update_position_for(node.clone(), 20), Some((10, 20))); + assert_eq!(Node::::get(&1).unwrap().score, 20); + + assert_eq!(List::::get_bags(), vec![(20, vec![1]), (1_000, vec![2, 3, 4])]); + + // get the new updated node; try and update the position with no change in score. + let node = Node::::get(&1).unwrap(); + assert_storage_noop!(assert_eq!( + List::::update_position_for(node.clone(), 20), + None + )); + + // then move it to bag 1_000 by giving it score 500. + assert_eq!(List::::update_position_for(node.clone(), 500), Some((20, 1_000))); + assert_eq!(Node::::get(&1).unwrap().score, 500); + assert_eq!(List::::get_bags(), vec![(1_000, vec![2, 3, 4, 1])]); + + // moving within that bag again is a noop + let node = Node::::get(&1).unwrap(); + assert_eq!(List::::update_position_for(node.clone(), 750), None); + assert_eq!(Node::::get(&1).unwrap().score, 750); + assert_eq!(List::::update_position_for(node.clone(), 1_000), None,); + assert_eq!(Node::::get(&1).unwrap().score, 1_000); + }); + } + + #[test] + fn try_state_works() { + ExtBuilder::default().build_and_execute_no_post_check(|| { + assert_ok!(List::::try_state()); + }); + + // make sure there are no duplicates. + ExtBuilder::default().build_and_execute_no_post_check(|| { + Bag::::get(10).unwrap().insert_unchecked(2, 10); + assert_eq!(List::::try_state(), Err("duplicate identified")); + }); + + // ensure count is in sync with `ListNodes::count()`. + ExtBuilder::default().build_and_execute_no_post_check(|| { + assert_eq!(crate::ListNodes::::count(), 4); + // we do some wacky stuff here to get access to the counter, since it is (reasonably) + // not exposed as mutable in any sense. + #[frame_support::storage_alias] + type CounterForListNodes = + StorageValue, u32, frame_support::pallet_prelude::ValueQuery>; + CounterForListNodes::::mutate(|counter| *counter += 1); + assert_eq!(crate::ListNodes::::count(), 5); + + assert_eq!(List::::try_state(), Err("iter_count != stored_count")); + }); + } + + #[test] + fn contains_works() { + ExtBuilder::default().build_and_execute(|| { + assert!(GENESIS_IDS.iter().all(|(id, _)| List::::contains(id))); + + let non_existent_ids = vec![&42, &666, &13]; + assert!(non_existent_ids.iter().all(|id| !List::::contains(id))); + }) + } + + #[test] + #[should_panic = "given nodes must always have a valid bag. qed."] + fn put_in_front_of_panics_if_bag_not_found() { + ExtBuilder::default().skip_genesis_ids().build_and_execute_no_post_check(|| { + let node_10_no_bag = Node:: { + id: 10, + prev: None, + next: None, + bag_upper: 15, + score: 15, + _phantom: PhantomData, + }; + let node_11_no_bag = Node:: { + id: 11, + prev: None, + next: None, + bag_upper: 15, + score: 15, + _phantom: PhantomData, + }; + + // given + ListNodes::::insert(10, node_10_no_bag); + ListNodes::::insert(11, node_11_no_bag); + StakingMock::set_score_of(&10, 14); + StakingMock::set_score_of(&11, 15); + assert!(!ListBags::::contains_key(15)); + assert_eq!(List::::get_bags(), vec![]); + + // then .. this panics + let _ = List::::put_in_front_of(&10, &11); + }); + } + + #[test] + fn insert_at_unchecked_at_is_only_node() { + // Note that this `insert_at_unchecked` test should fail post checks because node 42 does + // not get re-assigned the correct bagu pper. This is because `insert_at_unchecked` assumes + // both nodes are already in the same bag with the correct bag upper. + ExtBuilder::default().build_and_execute_no_post_check(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. + let node_42 = Node:: { + id: 42, + prev: Some(1), + next: Some(2), + bag_upper: 1_000, + score: 1_000, + _phantom: PhantomData, + }; + assert!(!crate::ListNodes::::contains_key(42)); + + let node_1 = crate::ListNodes::::get(&1).unwrap(); + + // when + List::::insert_at_unchecked(node_1, node_42); + + // then + assert_eq!( + List::::get_bags(), + vec![(10, vec![42, 1]), (1_000, vec![2, 3, 4])] + ); + }) + } + + #[test] + fn insert_at_unchecked_at_is_head() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. + let node_42 = Node:: { + id: 42, + prev: Some(4), + next: None, + bag_upper: 1_000, + score: 1_000, + _phantom: PhantomData, + }; + assert!(!crate::ListNodes::::contains_key(42)); + + let node_2 = crate::ListNodes::::get(&2).unwrap(); + + // when + List::::insert_at_unchecked(node_2, node_42); + + // then + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![42, 2, 3, 4])] + ); + }) + } + + #[test] + fn insert_at_unchecked_at_is_non_terminal() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. + let node_42 = Node:: { + id: 42, + prev: None, + next: Some(2), + bag_upper: 1_000, + score: 1_000, + _phantom: PhantomData, + }; + assert!(!crate::ListNodes::::contains_key(42)); + + let node_3 = crate::ListNodes::::get(&3).unwrap(); + + // when + List::::insert_at_unchecked(node_3, node_42); + + // then + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 42, 3, 4])] + ); + }) + } + + #[test] + fn insert_at_unchecked_at_is_tail() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. + let node_42 = Node:: { + id: 42, + prev: Some(42), + next: Some(42), + bag_upper: 1_000, + score: 1_000, + _phantom: PhantomData, + }; + assert!(!crate::ListNodes::::contains_key(42)); + + let node_4 = crate::ListNodes::::get(&4).unwrap(); + + // when + List::::insert_at_unchecked(node_4, node_42); + + // then + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 42, 4])] + ); + }) + } +} + +mod bags { + use super::*; + + #[test] + fn get_works() { + ExtBuilder::default().build_and_execute(|| { + let check_bag = |bag_upper, head, tail, ids| { + let bag = Bag::::get(bag_upper).unwrap(); + let bag_ids = bag.iter().map(|n| *n.id()).collect::>(); + + assert_eq!(bag, Bag:: { head, tail, bag_upper, _phantom: PhantomData }); + assert_eq!(bag_ids, ids); + }; + + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + // we can fetch them + check_bag(10, Some(1), Some(1), vec![1]); + check_bag(1_000, Some(2), Some(4), vec![2, 3, 4]); + + // and all other bag thresholds don't get bags. + ::BagThresholds::get() + .iter() + .chain(iter::once(&VoteWeight::MAX)) + .filter(|bag_upper| !vec![10, 1_000].contains(bag_upper)) + .for_each(|bag_upper| { + assert_storage_noop!(assert_eq!(Bag::::get(*bag_upper), None)); + assert!(!ListBags::::contains_key(*bag_upper)); + }); + + // when we make a pre-existing bag empty + List::::remove(&1).unwrap(); + + // then + assert_eq!(Bag::::get(10), None) + }); + } + + #[test] + fn insert_node_sets_proper_bag() { + ExtBuilder::default().build_and_execute_no_post_check(|| { + let node = |id, bag_upper| Node:: { + id, + prev: None, + next: None, + bag_upper, + score: bag_upper, + _phantom: PhantomData, + }; + + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + let mut bag_10 = Bag::::get(10).unwrap(); + bag_10.insert_node_unchecked(node(42, 5)); + + assert_eq!( + ListNodes::::get(&42).unwrap(), + Node { + bag_upper: 10, + score: 5, + prev: Some(1), + next: None, + id: 42, + _phantom: PhantomData + } + ); + }); + } + + #[test] + fn insert_node_happy_paths_works() { + ExtBuilder::default().build_and_execute_no_post_check(|| { + let node = |id, bag_upper| Node:: { + id, + prev: None, + next: None, + bag_upper, + score: bag_upper, + _phantom: PhantomData, + }; + + // when inserting into a bag with 1 node + let mut bag_10 = Bag::::get(10).unwrap(); + bag_10.insert_node_unchecked(node(42, bag_10.bag_upper)); + // then + assert_eq!(bag_as_ids(&bag_10), vec![1, 42]); + + // when inserting into a bag with 3 nodes + let mut bag_1000 = Bag::::get(1_000).unwrap(); + bag_1000.insert_node_unchecked(node(52, bag_1000.bag_upper)); + // then + assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4, 52]); + + // when inserting into a new bag + let mut bag_20 = Bag::::get_or_make(20); + bag_20.insert_node_unchecked(node(62, bag_20.bag_upper)); + // then + assert_eq!(bag_as_ids(&bag_20), vec![62]); + + // when inserting a node pointing to the accounts not in the bag + let node_61 = Node:: { + id: 61, + prev: Some(21), + next: Some(101), + bag_upper: 20, + score: 20, + _phantom: PhantomData, + }; + bag_20.insert_node_unchecked(node_61); + // then ids are in order + assert_eq!(bag_as_ids(&bag_20), vec![62, 61]); + // and when the node is re-fetched all the info is correct + assert_eq!( + Node::::get(&61).unwrap(), + Node:: { + id: 61, + prev: Some(62), + next: None, + bag_upper: 20, + score: 20, + _phantom: PhantomData, + } + ); + + // state of all bags is as expected + bag_20.put(); // need to put this newly created bag so its in the storage map + assert_eq!( + List::::get_bags(), + vec![(10, vec![1, 42]), (20, vec![62, 61]), (1_000, vec![2, 3, 4, 52])] + ); + }); + } + + // Document improper ways `insert_node` may be getting used. + #[test] + fn insert_node_bad_paths_documented() { + ExtBuilder::default().build_and_execute_no_post_check(|| { + // when inserting a node with both prev & next pointing at an account in an incorrect + // bag. + let mut bag_1000 = Bag::::get(1_000).unwrap(); + bag_1000.insert_node_unchecked(node(42, Some(1), Some(1), 500)); + + // then the proper prev and next is set. + assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4, 42]); + + // and when the node is re-fetched all the info is correct + assert_eq!( + Node::::get(&42).unwrap(), + Node:: { + id: 42, + prev: Some(4), + next: None, + bag_upper: bag_1000.bag_upper, + score: 500, + _phantom: PhantomData + } + ); + }); + + ExtBuilder::default().build_and_execute_no_post_check(|| { + // given 3 is in bag_1000 (and not a tail node) + let mut bag_1000 = Bag::::get(1_000).unwrap(); + assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4]); + + // when inserting a node with duplicate id 3 + bag_1000.insert_node_unchecked(node(3, None, None, bag_1000.bag_upper)); + + // then all the nodes after the duplicate are lost (because it is set as the tail) + assert_eq!(bag_as_ids(&bag_1000), vec![2, 3]); + // also in the full iteration, 2 and 3 are from bag_1000 and 1 is from bag_10. + assert_eq!(get_list_as_ids(), vec![2, 3, 1]); + + // and the last accessible node has an **incorrect** prev pointer. + assert_eq!( + Node::::get(&3).unwrap(), + node(3, Some(4), None, bag_1000.bag_upper) + ); + }); + + ExtBuilder::default().build_and_execute_no_post_check(|| { + // when inserting a duplicate id of the head + let mut bag_1000 = Bag::::get(1_000).unwrap(); + assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4]); + bag_1000.insert_node_unchecked(node(2, None, None, 0)); + + // then all nodes after the head are lost + assert_eq!(bag_as_ids(&bag_1000), vec![2]); + + // and the re-fetched node has bad pointers + assert_eq!( + Node::::get(&2).unwrap(), + Node:: { + id: 2, + prev: Some(4), + next: None, + bag_upper: bag_1000.bag_upper, + score: 0, + _phantom: PhantomData + }, + ); + // ^^^ despite being the bags head, it has a prev + + assert_eq!( + bag_1000, + Bag { head: Some(2), tail: Some(2), bag_upper: 1_000, _phantom: PhantomData } + ) + }); + } + + // Panics in case of duplicate tail insert (which would result in an infinite loop). + #[test] + #[cfg_attr( + debug_assertions, + should_panic = "system logic error: inserting a node who has the id of tail" + )] + fn insert_node_duplicate_tail_panics_with_debug_assert() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])],); + let mut bag_1000 = Bag::::get(1_000).unwrap(); + + // when inserting a duplicate id that is already the tail + assert_eq!(bag_1000.tail, Some(4)); + assert_eq!(bag_1000.iter().count(), 3); + bag_1000.insert_node_unchecked(node(4, None, None, bag_1000.bag_upper)); // panics in debug + assert_eq!(bag_1000.iter().count(), 3); // in release we expect it to silently ignore the request. + }); + } + + #[test] + fn remove_node_happy_paths_works() { + ExtBuilder::default() + .add_ids(vec![ + (11, 10), + (12, 10), + (13, 1_000), + (14, 1_000), + (15, 2_000), + (16, 2_000), + (17, 2_000), + (18, 2_000), + (19, 2_000), + ]) + .build_and_execute_no_post_check(|| { + let mut bag_10 = Bag::::get(10).unwrap(); + let mut bag_1000 = Bag::::get(1_000).unwrap(); + let mut bag_2000 = Bag::::get(2_000).unwrap(); + + // given + assert_eq!(bag_as_ids(&bag_10), vec![1, 11, 12]); + assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4, 13, 14]); + assert_eq!(bag_as_ids(&bag_2000), vec![15, 16, 17, 18, 19]); + + // when removing a node that is not pointing at the head or tail + let node_4 = Node::::get(&4).unwrap(); + let node_4_pre_remove = node_4.clone(); + bag_1000.remove_node_unchecked(&node_4); + + // then + assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 13, 14]); + assert_ok!(bag_1000.try_state()); + // and the node isn't mutated when its removed + assert_eq!(node_4, node_4_pre_remove); + + // when removing a head that is not pointing at the tail + let node_2 = Node::::get(&2).unwrap(); + bag_1000.remove_node_unchecked(&node_2); + + // then + assert_eq!(bag_as_ids(&bag_1000), vec![3, 13, 14]); + assert_ok!(bag_1000.try_state()); + + // when removing a tail that is not pointing at the head + let node_14 = Node::::get(&14).unwrap(); + bag_1000.remove_node_unchecked(&node_14); + + // then + assert_eq!(bag_as_ids(&bag_1000), vec![3, 13]); + assert_ok!(bag_1000.try_state()); + + // when removing a tail that is pointing at the head + let node_13 = Node::::get(&13).unwrap(); + bag_1000.remove_node_unchecked(&node_13); + + // then + assert_eq!(bag_as_ids(&bag_1000), vec![3]); + assert_ok!(bag_1000.try_state()); + + // when removing a node that is both the head & tail + let node_3 = Node::::get(&3).unwrap(); + bag_1000.remove_node_unchecked(&node_3); + bag_1000.put(); // put into storage so `get` returns the updated bag + + // then + assert_eq!(Bag::::get(1_000), None); + + // when removing a node that is pointing at both the head & tail + let node_11 = Node::::get(&11).unwrap(); + bag_10.remove_node_unchecked(&node_11); + + // then + assert_eq!(bag_as_ids(&bag_10), vec![1, 12]); + assert_ok!(bag_10.try_state()); + + // when removing a head that is pointing at the tail + let node_1 = Node::::get(&1).unwrap(); + bag_10.remove_node_unchecked(&node_1); + + // then + assert_eq!(bag_as_ids(&bag_10), vec![12]); + assert_ok!(bag_10.try_state()); + // and since we updated the bag's head/tail, we need to write this storage so we + // can correctly `get` it again in later checks + bag_10.put(); + + // when removing a node that is pointing at the head but not the tail + let node_16 = Node::::get(&16).unwrap(); + bag_2000.remove_node_unchecked(&node_16); + + // then + assert_eq!(bag_as_ids(&bag_2000), vec![15, 17, 18, 19]); + assert_ok!(bag_2000.try_state()); + + // when removing a node that is pointing at tail, but not head + let node_18 = Node::::get(&18).unwrap(); + bag_2000.remove_node_unchecked(&node_18); + + // then + assert_eq!(bag_as_ids(&bag_2000), vec![15, 17, 19]); + assert_ok!(bag_2000.try_state()); + + // finally, when reading from storage, the state of all bags is as expected + assert_eq!( + List::::get_bags(), + vec![(10, vec![12]), (2_000, vec![15, 17, 19])] + ); + }); + } + + #[test] + fn remove_node_bad_paths_documented() { + ExtBuilder::default().build_and_execute_no_post_check(|| { + let bad_upper_node_2 = Node:: { + id: 2, + prev: None, + next: Some(3), + bag_upper: 10, // should be 1_000 + score: 10, + _phantom: PhantomData, + }; + let mut bag_1000 = Bag::::get(1_000).unwrap(); + + // when removing a node that is in the bag but has the wrong upper + bag_1000.remove_node_unchecked(&bad_upper_node_2); + bag_1000.put(); + + // then the node is no longer in any bags + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 4])]); + // .. and the bag it was removed from + let bag_1000 = Bag::::get(1_000).unwrap(); + // is sane + assert_ok!(bag_1000.try_state()); + // and has the correct head and tail. + assert_eq!(bag_1000.head, Some(3)); + assert_eq!(bag_1000.tail, Some(4)); + }); + + // Removing a node that is in another bag, will mess up that other bag. + ExtBuilder::default().build_and_execute_no_post_check(|| { + // given a tail node is in bag 1_000 + let node_4 = Node::::get(&4).unwrap(); + + // when we remove it from bag 10 + let mut bag_10 = Bag::::get(10).unwrap(); + bag_10.remove_node_unchecked(&node_4); + bag_10.put(); + + // then bag remove was called on is ok, + let bag_10 = Bag::::get(10).unwrap(); + assert_eq!(bag_10.tail, Some(1)); + assert_eq!(bag_10.head, Some(1)); + + // but the bag that the node belonged to is in an invalid state + let bag_1000 = Bag::::get(1_000).unwrap(); + // because it still has the removed node as its tail. + assert_eq!(bag_1000.tail, Some(4)); + assert_eq!(bag_1000.head, Some(2)); + }); + } +} + +mod node { + use super::*; + + #[test] + fn is_misplaced_works() { + ExtBuilder::default().build_and_execute(|| { + let node = Node::::get(&1).unwrap(); + + // given + assert_eq!(node.bag_upper, 10); + + // then within bag 10 its not misplaced, + assert!(!node.is_misplaced(0)); + assert!(!node.is_misplaced(9)); + assert!(!node.is_misplaced(10)); + + // and out of bag 10 it is misplaced + assert!(node.is_misplaced(11)); + }); + } +} diff --git a/frame/bags-list/src/migrations.rs b/frame/bags-list/src/migrations.rs new file mode 100644 index 0000000000000..e1dc9f777e537 --- /dev/null +++ b/frame/bags-list/src/migrations.rs @@ -0,0 +1,138 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! The migrations of this pallet. + +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use frame_election_provider_support::ScoreProvider; +use frame_support::traits::OnRuntimeUpgrade; + +#[cfg(feature = "try-runtime")] +use frame_support::ensure; +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +/// A struct that does not migration, but only checks that the counter prefix exists and is correct. +pub struct CheckCounterPrefix, I: 'static>(sp_std::marker::PhantomData<(T, I)>); +impl, I: 'static> OnRuntimeUpgrade for CheckCounterPrefix { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + frame_support::weights::Weight::zero() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + // The old explicit storage item. + #[frame_support::storage_alias] + type CounterForListNodes, I: 'static> = + StorageValue, u32>; + + // ensure that a value exists in the counter struct. + ensure!( + crate::ListNodes::::count() == CounterForListNodes::::get().unwrap(), + "wrong list node counter" + ); + + crate::log!( + info, + "checked bags-list prefix to be correct and have {} nodes", + crate::ListNodes::::count() + ); + + Ok(Vec::new()) + } +} + +mod old { + use super::*; + use frame_support::pallet_prelude::*; + + #[derive(Encode, Decode)] + pub struct PreScoreNode, I: 'static = ()> { + pub id: T::AccountId, + pub prev: Option, + pub next: Option, + pub bag_upper: T::Score, + #[codec(skip)] + pub _phantom: PhantomData, + } + + #[frame_support::storage_alias] + pub type ListNodes, I: 'static> = StorageMap< + crate::Pallet, + Twox64Concat, + ::AccountId, + PreScoreNode, + >; + + #[frame_support::storage_alias] + pub type CounterForListNodes, I: 'static> = + StorageValue, u32, ValueQuery>; +} + +/// A struct that migrates all bags lists to contain a score value. +pub struct AddScore, I: 'static = ()>(sp_std::marker::PhantomData<(T, I)>); +impl, I: 'static> OnRuntimeUpgrade for AddScore { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + // The list node data should be corrupt at this point, so this is zero. + ensure!(crate::ListNodes::::iter().count() == 0, "list node data is not corrupt"); + // We can use the helper `old::ListNode` to get the existing data. + let iter_node_count: u32 = old::ListNodes::::iter().count() as u32; + let tracked_node_count: u32 = old::CounterForListNodes::::get(); + crate::log!(info, "number of nodes before: {:?} {:?}", iter_node_count, tracked_node_count); + ensure!(iter_node_count == tracked_node_count, "Node count is wrong."); + Ok(iter_node_count.encode()) + } + + fn on_runtime_upgrade() -> frame_support::weights::Weight { + for (_key, node) in old::ListNodes::::iter() { + let score = T::ScoreProvider::score(&node.id); + + let new_node = crate::Node { + id: node.id.clone(), + prev: node.prev, + next: node.next, + bag_upper: node.bag_upper, + score, + _phantom: node._phantom, + }; + + crate::ListNodes::::insert(node.id, new_node); + } + + return frame_support::weights::Weight::MAX + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(node_count_before: Vec) -> Result<(), &'static str> { + let node_count_before: u32 = Decode::decode(&mut node_count_before.as_slice()) + .expect("the state parameter should be something that was generated by pre_upgrade"); + // Now the list node data is not corrupt anymore. + let iter_node_count_after: u32 = crate::ListNodes::::iter().count() as u32; + let tracked_node_count_after: u32 = crate::ListNodes::::count(); + crate::log!( + info, + "number of nodes after: {:?} {:?}", + iter_node_count_after, + tracked_node_count_after, + ); + ensure!(iter_node_count_after == node_count_before, "Not all nodes were migrated."); + ensure!(tracked_node_count_after == iter_node_count_after, "Node count is wrong."); + Ok(()) + } +} diff --git a/frame/bags-list/src/mock.rs b/frame/bags-list/src/mock.rs new file mode 100644 index 0000000000000..8cc96a988e72a --- /dev/null +++ b/frame/bags-list/src/mock.rs @@ -0,0 +1,175 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Mock runtime for pallet-bags-lists tests. + +use super::*; +use crate::{self as bags_list}; +use frame_election_provider_support::VoteWeight; +use frame_support::parameter_types; +use std::collections::HashMap; + +pub type AccountId = u32; +pub type Balance = u32; + +parameter_types! { + // Set the vote weight for any id who's weight has _not_ been set with `set_score_of`. + pub static NextVoteWeight: VoteWeight = 0; + pub static NextVoteWeightMap: HashMap = Default::default(); +} + +pub struct StakingMock; +impl frame_election_provider_support::ScoreProvider for StakingMock { + type Score = VoteWeight; + + fn score(id: &AccountId) -> Self::Score { + *NextVoteWeightMap::get().get(id).unwrap_or(&NextVoteWeight::get()) + } + + #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", test))] + fn set_score_of(id: &AccountId, weight: Self::Score) { + NEXT_VOTE_WEIGHT_MAP.with(|m| m.borrow_mut().insert(*id, weight)); + } +} + +impl frame_system::Config for Runtime { + type SS58Prefix = (); + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = sp_runtime::testing::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = (); + type DbWeight = (); + type BlockLength = (); + type BlockWeights = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; +} + +impl bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type BagThresholds = BagThresholds; + type ScoreProvider = StakingMock; + type Score = VoteWeight; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Event, Config}, + BagsList: bags_list::{Pallet, Call, Storage, Event}, + } +); + +/// Default AccountIds and their weights. +pub(crate) const GENESIS_IDS: [(AccountId, VoteWeight); 4] = + [(1, 10), (2, 1_000), (3, 1_000), (4, 1_000)]; + +#[derive(Default)] +pub struct ExtBuilder { + ids: Vec<(AccountId, VoteWeight)>, + skip_genesis_ids: bool, +} + +#[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", test))] +impl ExtBuilder { + /// Skip adding the default genesis ids to the list. + #[cfg(test)] + pub(crate) fn skip_genesis_ids(mut self) -> Self { + self.skip_genesis_ids = true; + self + } + + /// Add some AccountIds to insert into `List`. + #[cfg(test)] + pub(crate) fn add_ids(mut self, ids: Vec<(AccountId, VoteWeight)>) -> Self { + self.ids = ids; + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let ids_with_weight: Vec<_> = if self.skip_genesis_ids { + self.ids.iter().collect() + } else { + GENESIS_IDS.iter().chain(self.ids.iter()).collect() + }; + + let mut ext = sp_io::TestExternalities::from(storage); + ext.execute_with(|| { + for (id, weight) in ids_with_weight { + frame_support::assert_ok!(List::::insert(*id, *weight)); + StakingMock::set_score_of(id, *weight); + } + }); + + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + List::::try_state().expect("Try-state post condition failed") + }) + } + + #[cfg(test)] + pub(crate) fn build_and_execute_no_post_check(self, test: impl FnOnce() -> ()) { + self.build().execute_with(test) + } +} + +#[cfg(test)] +pub(crate) mod test_utils { + use super::*; + use list::Bag; + + /// Returns the ordered ids within the given bag. + pub(crate) fn bag_as_ids(bag: &Bag) -> Vec { + bag.iter().map(|n| *n.id()).collect::>() + } + + /// Returns the ordered ids from the list. + pub(crate) fn get_list_as_ids() -> Vec { + List::::iter().map(|n| *n.id()).collect::>() + } +} diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs new file mode 100644 index 0000000000000..63a395ed0d65a --- /dev/null +++ b/frame/bags-list/src/tests.rs @@ -0,0 +1,663 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 frame_support::{assert_noop, assert_ok, assert_storage_noop, traits::IntegrityTest}; + +use super::*; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; +use list::Bag; +use mock::{test_utils::*, *}; + +mod pallet { + use super::*; + + #[test] + fn rebag_works() { + ExtBuilder::default().add_ids(vec![(42, 20)]).build_and_execute(|| { + // given + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])] + ); + + // when increasing score to the level of non-existent bag + assert_eq!(List::::get_score(&42).unwrap(), 20); + StakingMock::set_score_of(&42, 2_000); + assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 42)); + assert_eq!(List::::get_score(&42).unwrap(), 2_000); + + // then a new bag is created and the id moves into it + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])] + ); + + // when decreasing score within the range of the current bag + StakingMock::set_score_of(&42, 1_001); + assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 42)); + + // then the id does not move + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])] + ); + // but the score is updated + assert_eq!(List::::get_score(&42).unwrap(), 1_001); + + // when reducing score to the level of a non-existent bag + StakingMock::set_score_of(&42, 30); + assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 42)); + + // then a new bag is created and the id moves into it + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (30, vec![42]), (1_000, vec![2, 3, 4])] + ); + assert_eq!(List::::get_score(&42).unwrap(), 30); + + // when increasing score to the level of a pre-existing bag + StakingMock::set_score_of(&42, 500); + assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 42)); + + // then the id moves into that bag + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4, 42])] + ); + assert_eq!(List::::get_score(&42).unwrap(), 500); + }); + } + + // Rebagging the tail of a bag results in the old bag having a new tail and an overall correct + // state. + #[test] + fn rebag_tail_works() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + // when + StakingMock::set_score_of(&4, 10); + assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 4)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1, 4]), (1_000, vec![2, 3])]); + assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(2), Some(3), 1_000)); + + // when + StakingMock::set_score_of(&3, 10); + assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 3)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1, 4, 3]), (1_000, vec![2])]); + + assert_eq!(Bag::::get(10).unwrap(), Bag::new(Some(1), Some(3), 10)); + assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(2), Some(2), 1_000)); + assert_eq!(get_list_as_ids(), vec![2u32, 1, 4, 3]); + + // when + StakingMock::set_score_of(&2, 10); + assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 2)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1, 4, 3, 2])]); + assert_eq!(Bag::::get(1_000), None); + }); + } + + // Rebagging the head of a bag results in the old bag having a new head and an overall correct + // state. + #[test] + fn rebag_head_works() { + ExtBuilder::default().build_and_execute(|| { + // when + StakingMock::set_score_of(&2, 10); + assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 2)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1, 2]), (1_000, vec![3, 4])]); + assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(3), Some(4), 1_000)); + + // when + StakingMock::set_score_of(&3, 10); + assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 3)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1, 2, 3]), (1_000, vec![4])]); + assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(4), Some(4), 1_000)); + + // when + StakingMock::set_score_of(&4, 10); + assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 4)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1, 2, 3, 4])]); + assert_eq!(Bag::::get(1_000), None); + }); + } + + #[test] + fn wrong_rebag_errs() { + ExtBuilder::default().build_and_execute(|| { + let node_3 = list::Node::::get(&3).unwrap(); + // when account 3 is _not_ misplaced with score 500 + NextVoteWeight::set(500); + assert!(!node_3.is_misplaced(500)); + + // then calling rebag on account 3 with score 500 is a noop + assert_storage_noop!(assert_eq!(BagsList::rebag(RuntimeOrigin::signed(0), 3), Ok(()))); + + // when account 42 is not in the list + assert!(!BagsList::contains(&42)); + // then rebag-ing account 42 is an error + assert_storage_noop!(assert!(matches!( + BagsList::rebag(RuntimeOrigin::signed(0), 42), + Err(_) + ))); + }); + } + + #[test] + #[should_panic = "thresholds must strictly increase, and have no duplicates"] + fn duplicate_in_bags_threshold_panics() { + const DUPE_THRESH: &[VoteWeight; 4] = &[10, 20, 30, 30]; + BagThresholds::set(DUPE_THRESH); + BagsList::integrity_test(); + } + + #[test] + #[should_panic = "thresholds must strictly increase, and have no duplicates"] + fn decreasing_in_bags_threshold_panics() { + const DECREASING_THRESH: &[VoteWeight; 4] = &[10, 30, 20, 40]; + BagThresholds::set(DECREASING_THRESH); + BagsList::integrity_test(); + } + + #[test] + fn empty_threshold_works() { + BagThresholds::set(Default::default()); // which is the same as passing `()` to `Get<_>`. + ExtBuilder::default().build_and_execute(|| { + // everyone in the same bag. + assert_eq!(List::::get_bags(), vec![(VoteWeight::MAX, vec![1, 2, 3, 4])]); + + // any insertion goes there as well. + assert_ok!(List::::insert(5, 999)); + assert_ok!(List::::insert(6, 0)); + assert_eq!( + List::::get_bags(), + vec![(VoteWeight::MAX, vec![1, 2, 3, 4, 5, 6])] + ); + + // any rebag is noop. + assert_storage_noop!(assert_eq!(BagsList::rebag(RuntimeOrigin::signed(0), 1), Ok(()))); + }) + } + + #[test] + fn put_in_front_of_two_node_bag_heavier_is_tail() { + ExtBuilder::default() + .skip_genesis_ids() + .add_ids(vec![(10, 15), (11, 16)]) + .build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(20, vec![10, 11])]); + + // when + assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(11), 10)); + + // then + assert_eq!(List::::get_bags(), vec![(20, vec![11, 10])]); + }); + } + + #[test] + fn put_in_front_of_two_node_bag_heavier_is_head() { + ExtBuilder::default() + .skip_genesis_ids() + .add_ids(vec![(11, 16), (10, 15)]) + .build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(20, vec![11, 10])]); + + // when + assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(11), 10)); + + // then + assert_eq!(List::::get_bags(), vec![(20, vec![11, 10])]); + }); + } + + #[test] + fn put_in_front_of_non_terminal_nodes_heavier_behind() { + ExtBuilder::default().add_ids(vec![(5, 1_000)]).build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]); + + StakingMock::set_score_of(&3, 999); + + // when + assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(4), 3)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 4, 3, 5])]); + }); + } + + #[test] + fn put_in_front_of_non_terminal_nodes_heavier_in_front() { + ExtBuilder::default() + .add_ids(vec![(5, 1_000), (6, 1_000)]) + .build_and_execute(|| { + // given + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5, 6])] + ); + + StakingMock::set_score_of(&5, 999); + + // when + assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(3), 5)); + + // then + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 4, 3, 5, 6])] + ); + }); + } + + #[test] + fn put_in_front_of_lighter_is_head_heavier_is_non_terminal() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + StakingMock::set_score_of(&2, 999); + + // when + assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(3), 2)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 2, 4])]); + }); + } + + #[test] + fn put_in_front_of_heavier_is_tail_lighter_is_non_terminal() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + StakingMock::set_score_of(&3, 999); + + // when + assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(4), 3)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 4, 3])]); + }); + } + + #[test] + fn put_in_front_of_heavier_is_tail_lighter_is_head() { + ExtBuilder::default().add_ids(vec![(5, 1_000)]).build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]); + + StakingMock::set_score_of(&2, 999); + + // when + assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(5), 2)); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![5, 2, 3, 4])]); + }); + } + + #[test] + fn put_in_front_of_heavier_is_head_lighter_is_not_terminal() { + ExtBuilder::default().add_ids(vec![(5, 1_000)]).build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]); + + StakingMock::set_score_of(&4, 999); + + // when + BagsList::put_in_front_of(RuntimeOrigin::signed(2), 4).unwrap(); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 2, 4, 5])]); + }); + } + + #[test] + fn put_in_front_of_lighter_is_tail_heavier_is_not_terminal() { + ExtBuilder::default().add_ids(vec![(5, 900)]).build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]); + + // when + BagsList::put_in_front_of(RuntimeOrigin::signed(3), 5).unwrap(); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 4, 3, 5])]); + }); + } + + #[test] + fn put_in_front_of_lighter_is_tail_heavier_is_head() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + StakingMock::set_score_of(&4, 999); + + // when + BagsList::put_in_front_of(RuntimeOrigin::signed(2), 4).unwrap(); + + // then + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 2, 4])]); + }); + } + + #[test] + fn put_in_front_of_errors_if_heavier_is_less_than_lighter() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + StakingMock::set_score_of(&3, 999); + + // then + assert_noop!( + BagsList::put_in_front_of(RuntimeOrigin::signed(3), 2), + crate::pallet::Error::::List(ListError::NotHeavier) + ); + }); + } + + #[test] + fn put_in_front_of_errors_if_heavier_is_equal_weight_to_lighter() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + // then + assert_noop!( + BagsList::put_in_front_of(RuntimeOrigin::signed(3), 4), + crate::pallet::Error::::List(ListError::NotHeavier) + ); + }); + } + + #[test] + fn put_in_front_of_errors_if_nodes_not_found() { + // `heavier` not found + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + assert!(!ListNodes::::contains_key(5)); + + // then + assert_noop!( + BagsList::put_in_front_of(RuntimeOrigin::signed(5), 4), + crate::pallet::Error::::List(ListError::NodeNotFound) + ); + }); + + // `lighter` not found + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + assert!(!ListNodes::::contains_key(5)); + + // then + assert_noop!( + BagsList::put_in_front_of(RuntimeOrigin::signed(4), 5), + crate::pallet::Error::::List(ListError::NodeNotFound) + ); + }); + } + + #[test] + fn put_in_front_of_errors_if_nodes_not_in_same_bag() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + + // then + assert_noop!( + BagsList::put_in_front_of(RuntimeOrigin::signed(4), 1), + crate::pallet::Error::::List(ListError::NotInSameBag) + ); + }); + } +} + +mod sorted_list_provider { + use super::*; + + #[test] + fn iter_works() { + ExtBuilder::default().build_and_execute(|| { + let expected = vec![2, 3, 4, 1]; + for (i, id) in BagsList::iter().enumerate() { + assert_eq!(id, expected[i]) + } + }); + } + + #[test] + fn iter_from_works() { + ExtBuilder::default().add_ids(vec![(5, 5), (6, 15)]).build_and_execute(|| { + // given + assert_eq!( + List::::get_bags(), + vec![(10, vec![1, 5]), (20, vec![6]), (1000, vec![2, 3, 4])] + ); + + assert_eq!(BagsList::iter_from(&2).unwrap().collect::>(), vec![3, 4, 6, 1, 5]); + assert_eq!(BagsList::iter_from(&3).unwrap().collect::>(), vec![4, 6, 1, 5]); + assert_eq!(BagsList::iter_from(&4).unwrap().collect::>(), vec![6, 1, 5]); + assert_eq!(BagsList::iter_from(&6).unwrap().collect::>(), vec![1, 5]); + assert_eq!(BagsList::iter_from(&1).unwrap().collect::>(), vec![5]); + assert!(BagsList::iter_from(&5).unwrap().collect::>().is_empty()); + assert!(BagsList::iter_from(&7).is_err()); + + assert_storage_noop!(assert!(BagsList::iter_from(&8).is_err())); + }); + } + + #[test] + fn count_works() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(BagsList::count(), 4); + + // when inserting + assert_ok!(BagsList::on_insert(201, 0)); + // then the count goes up + assert_eq!(BagsList::count(), 5); + + // when removing + BagsList::on_remove(&201).unwrap(); + // then the count goes down + assert_eq!(BagsList::count(), 4); + + // when updating + assert_noop!(BagsList::on_update(&201, VoteWeight::MAX), ListError::NodeNotFound); + // then the count stays the same + assert_eq!(BagsList::count(), 4); + }); + } + + #[test] + fn on_insert_works() { + ExtBuilder::default().build_and_execute(|| { + // when + assert_ok!(BagsList::on_insert(6, 1_000)); + + // then the bags + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 6])]); + // and list correctly include the new id, + assert_eq!(BagsList::iter().collect::>(), vec![2, 3, 4, 6, 1]); + // and the count is incremented. + assert_eq!(BagsList::count(), 5); + + // when + assert_ok!(BagsList::on_insert(7, 1_001)); + + // then the bags + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4, 6]), (2_000, vec![7])] + ); + // and list correctly include the new id, + assert_eq!(BagsList::iter().collect::>(), vec![7, 2, 3, 4, 6, 1]); + // and the count is incremented. + assert_eq!(BagsList::count(), 6); + }) + } + + #[test] + fn on_insert_errors_with_duplicate_id() { + ExtBuilder::default().build_and_execute(|| { + // given + assert!(get_list_as_ids().contains(&3)); + + // then + assert_storage_noop!(assert_eq!( + BagsList::on_insert(3, 20).unwrap_err(), + ListError::Duplicate + )); + }); + } + + #[test] + fn on_update_works() { + ExtBuilder::default().add_ids(vec![(42, 20)]).build_and_execute(|| { + // given + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])] + ); + assert_eq!(BagsList::count(), 5); + + // when increasing score to the level of non-existent bag + BagsList::on_update(&42, 2_000).unwrap(); + + // then the bag is created with the id in it, + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2000, vec![42])] + ); + // and the id position is updated in the list. + assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); + + // when decreasing score within the range of the current bag + BagsList::on_update(&42, 1_001).unwrap(); + + // then the id does not change bags, + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2000, vec![42])] + ); + // or change position in the list. + assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); + + // when increasing score to the level of a non-existent bag with the max threshold + BagsList::on_update(&42, VoteWeight::MAX).unwrap(); + + // the the new bag is created with the id in it, + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (VoteWeight::MAX, vec![42])] + ); + // and the id position is updated in the list. + assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); + + // when decreasing the score to a pre-existing bag + BagsList::on_update(&42, 1_000).unwrap(); + + // then id is moved to the correct bag (as the last member), + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 4, 42])] + ); + // and the id position is updated in the list. + assert_eq!(BagsList::iter().collect::>(), vec![2, 3, 4, 42, 1]); + + // since we have only called on_update, the `count` has not changed. + assert_eq!(BagsList::count(), 5); + }); + } + + #[test] + fn on_remove_works() { + let ensure_left = |id, counter| { + assert!(!ListNodes::::contains_key(id)); + assert_eq!(BagsList::count(), counter); + assert_eq!(ListNodes::::count(), counter); + assert_eq!(ListNodes::::iter().count() as u32, counter); + }; + + ExtBuilder::default().build_and_execute(|| { + // it is a noop removing a non-existent id + assert!(!ListNodes::::contains_key(42)); + assert_noop!(BagsList::on_remove(&42), ListError::NodeNotFound); + + // when removing a node from a bag with multiple nodes + BagsList::on_remove(&2).unwrap(); + + // then + assert_eq!(get_list_as_ids(), vec![3, 4, 1]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 4])]); + ensure_left(2, 3); + + // when removing a node from a bag with only one node + BagsList::on_remove(&1).unwrap(); + + // then + assert_eq!(get_list_as_ids(), vec![3, 4]); + assert_eq!(List::::get_bags(), vec![(1_000, vec![3, 4])]); + ensure_left(1, 2); + + // when removing all remaining ids + BagsList::on_remove(&4).unwrap(); + assert_eq!(get_list_as_ids(), vec![3]); + ensure_left(4, 1); + BagsList::on_remove(&3).unwrap(); + + // then the storage is completely cleaned up + assert_eq!(get_list_as_ids(), Vec::::new()); + ensure_left(3, 0); + }); + } + + #[test] + fn contains_works() { + ExtBuilder::default().build_and_execute(|| { + assert!(GENESIS_IDS.iter().all(|(id, _)| BagsList::contains(id))); + + let non_existent_ids = vec![&42, &666, &13]; + assert!(non_existent_ids.iter().all(|id| !BagsList::contains(id))); + }) + } +} diff --git a/frame/bags-list/src/weights.rs b/frame/bags-list/src/weights.rs new file mode 100644 index 0000000000000..a298dc3172f79 --- /dev/null +++ b/frame/bags-list/src/weights.rs @@ -0,0 +1,124 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_bags_list +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_bags_list +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/bags-list/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_bags_list. +pub trait WeightInfo { + fn rebag_non_terminal() -> Weight; + fn rebag_terminal() -> Weight; + fn put_in_front_of() -> Weight; +} + +/// Weights for pallet_bags_list using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: VoterList ListNodes (r:4 w:4) + // Storage: VoterList ListBags (r:1 w:1) + fn rebag_non_terminal() -> Weight { + // Minimum execution time: 73_553 nanoseconds. + Weight::from_ref_time(74_366_000 as u64) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) + fn rebag_terminal() -> Weight { + // Minimum execution time: 72_700 nanoseconds. + Weight::from_ref_time(73_322_000 as u64) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: VoterList ListNodes (r:4 w:4) + // Storage: Staking Bonded (r:2 w:0) + // Storage: Staking Ledger (r:2 w:0) + // Storage: VoterList CounterForListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) + fn put_in_front_of() -> Weight { + // Minimum execution time: 71_691 nanoseconds. + Weight::from_ref_time(72_798_000 as u64) + .saturating_add(T::DbWeight::get().reads(10 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: VoterList ListNodes (r:4 w:4) + // Storage: VoterList ListBags (r:1 w:1) + fn rebag_non_terminal() -> Weight { + // Minimum execution time: 73_553 nanoseconds. + Weight::from_ref_time(74_366_000 as u64) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) + fn rebag_terminal() -> Weight { + // Minimum execution time: 72_700 nanoseconds. + Weight::from_ref_time(73_322_000 as u64) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: VoterList ListNodes (r:4 w:4) + // Storage: Staking Bonded (r:2 w:0) + // Storage: Staking Ledger (r:2 w:0) + // Storage: VoterList CounterForListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) + fn put_in_front_of() -> Weight { + // Minimum execution time: 71_691 nanoseconds. + Weight::from_ref_time(72_798_000 as u64) + .saturating_add(RocksDbWeight::get().reads(10 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } +} diff --git a/frame/balances/Cargo.toml b/frame/balances/Cargo.toml index 2263387d6d8ef..934138a900214 100644 --- a/frame/balances/Cargo.toml +++ b/frame/balances/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-balances" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet to manage balances" readme = "README.md" @@ -13,31 +13,31 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.14", default-features = false } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../transaction-payment" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", - "frame-benchmarking/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "log/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", ] -runtime-benchmarks = ["frame-benchmarking"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/balances/src/benchmarking.rs b/frame/balances/src/benchmarking.rs index 06d202ea37002..206adba0f044b 100644 --- a/frame/balances/src/benchmarking.rs +++ b/frame/balances/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,7 @@ use super::*; -use frame_benchmarking::{ - account, benchmarks_instance_pallet, impl_benchmark_test_suite, whitelisted_caller, -}; +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; @@ -48,7 +46,7 @@ benchmarks_instance_pallet! { // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, // and reap this user. let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup: ::Source = T::Lookup::unlookup(recipient.clone()); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); let transfer_amount = existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); }: transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount) verify { @@ -62,7 +60,7 @@ benchmarks_instance_pallet! { transfer_best_case { let caller = whitelisted_caller(); let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup: ::Source = T::Lookup::unlookup(recipient.clone()); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); // Give the sender account max funds for transfer (their account will never reasonably be killed). let _ = as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); @@ -82,7 +80,7 @@ benchmarks_instance_pallet! { transfer_keep_alive { let caller = whitelisted_caller(); let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup: ::Source = T::Lookup::unlookup(recipient.clone()); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); // Give the sender account max funds, thus a transfer will not kill account. let _ = as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); @@ -97,7 +95,7 @@ benchmarks_instance_pallet! { // Benchmark `set_balance` coming from ROOT account. This always creates an account. set_balance_creating { let user: T::AccountId = account("user", 0, SEED); - let user_lookup: ::Source = T::Lookup::unlookup(user.clone()); + let user_lookup = T::Lookup::unlookup(user.clone()); // Give the user some initial balance. let existential_deposit = T::ExistentialDeposit::get(); @@ -112,7 +110,7 @@ benchmarks_instance_pallet! { // Benchmark `set_balance` coming from ROOT account. This always kills an account. set_balance_killing { let user: T::AccountId = account("user", 0, SEED); - let user_lookup: ::Source = T::Lookup::unlookup(user.clone()); + let user_lookup = T::Lookup::unlookup(user.clone()); // Give the user some initial balance. let existential_deposit = T::ExistentialDeposit::get(); @@ -129,7 +127,7 @@ benchmarks_instance_pallet! { force_transfer { let existential_deposit = T::ExistentialDeposit::get(); let source: T::AccountId = account("source", 0, SEED); - let source_lookup: ::Source = T::Lookup::unlookup(source.clone()); + let source_lookup = T::Lookup::unlookup(source.clone()); // Give some multiple of the existential deposit let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); @@ -137,7 +135,7 @@ benchmarks_instance_pallet! { // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, and reap this user. let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup: ::Source = T::Lookup::unlookup(recipient.clone()); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); let transfer_amount = existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); }: force_transfer(RawOrigin::Root, source_lookup, recipient_lookup, transfer_amount) verify { @@ -162,7 +160,7 @@ benchmarks_instance_pallet! { // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, // and reap this user. let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup: ::Source = T::Lookup::unlookup(recipient.clone()); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); let transfer_amount = existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); // Create a bunch of users in storage. @@ -184,7 +182,7 @@ benchmarks_instance_pallet! { transfer_all { let caller = whitelisted_caller(); let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup: ::Source = T::Lookup::unlookup(recipient.clone()); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); // Give some multiple of the existential deposit let existential_deposit = T::ExistentialDeposit::get(); @@ -198,7 +196,7 @@ benchmarks_instance_pallet! { force_unreserve { let user: T::AccountId = account("user", 0, SEED); - let user_lookup: ::Source = T::Lookup::unlookup(user.clone()); + let user_lookup = T::Lookup::unlookup(user.clone()); // Give some multiple of the existential deposit let existential_deposit = T::ExistentialDeposit::get(); @@ -215,10 +213,10 @@ benchmarks_instance_pallet! { assert!(Balances::::reserved_balance(&user).is_zero()); assert_eq!(Balances::::free_balance(&user), balance); } -} -impl_benchmark_test_suite!( - Balances, - crate::tests_composite::ExtBuilder::default().build(), - crate::tests_composite::Test, -); + impl_benchmark_test_suite!( + Balances, + crate::tests_composite::ExtBuilder::default().build(), + crate::tests_composite::Test, + ) +} diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index afd2331c8e3cf..57f76b1ff679d 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -156,8 +156,10 @@ #[macro_use] mod tests; mod benchmarking; +pub mod migration; mod tests_composite; mod tests_local; +#[cfg(test)] mod tests_reentrancy; pub mod weights; @@ -170,7 +172,7 @@ use frame_support::{ pallet_prelude::DispatchResult, traits::{ tokens::{fungible, BalanceStatus as Status, DepositConsequence, WithdrawConsequence}, - Currency, ExistenceRequirement, + Currency, DefensiveSaturating, ExistenceRequirement, ExistenceRequirement::{AllowDeath, KeepAlive}, Get, Imbalance, LockIdentifier, LockableCurrency, NamedReservableCurrency, OnUnbalanced, ReservableCurrency, SignedImbalance, StoredMap, TryDrop, WithdrawReasons, @@ -184,13 +186,15 @@ use sp_runtime::{ AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Saturating, StaticLookup, Zero, }, - ArithmeticError, DispatchError, RuntimeDebug, + ArithmeticError, DispatchError, FixedPointOperand, RuntimeDebug, }; use sp_std::{cmp, fmt::Debug, mem, ops::BitOr, prelude::*, result}; pub use weights::WeightInfo; pub use pallet::*; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -209,13 +213,15 @@ pub mod pallet { + MaybeSerializeDeserialize + Debug + MaxEncodedLen - + TypeInfo; + + TypeInfo + + FixedPointOperand; /// Handler for the unbalanced reduction when removing a dust account. type DustRemoval: OnUnbalanced>; /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; /// The minimum amount required to keep an account open. #[pallet::constant] @@ -240,9 +246,13 @@ pub mod pallet { type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy; } + /// The current storage version. + const STORAGE_VERSION: frame_support::traits::StorageVersion = + frame_support::traits::StorageVersion::new(1); + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::generate_storage_info] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData<(T, I)>); #[pallet::call] @@ -250,7 +260,6 @@ pub mod pallet { /// Transfer some liquid free balance to another account. /// /// `transfer` will set the `FreeBalance` of the sender and receiver. - /// It will decrease the total issuance of the system by the `TransferFee`. /// If the sender's account is below the existential deposit as a result /// of the transfer, the account will be reaped. /// @@ -271,14 +280,13 @@ pub mod pallet { /// - `transfer_keep_alive` works the same way as `transfer`, but has an additional check /// that the transfer will not kill the origin account. /// --------------------------------- - /// - Base Weight: 73.64 µs, worst case scenario (account created, account removed) - /// - DB Weight: 1 Read and 1 Write to destination account /// - Origin account is already in memory, so no DB operations for them. /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, - dest: ::Source, + dest: AccountIdLookupOf, #[pallet::compact] value: T::Balance, ) -> DispatchResultWithPostInfo { let transactor = ensure_signed(origin)?; @@ -295,28 +303,19 @@ pub mod pallet { /// Set the balances of a given account. /// /// This will alter `FreeBalance` and `ReservedBalance` in storage. it will - /// also decrease the total issuance of the system (`TotalIssuance`). + /// also alter the total issuance of the system (`TotalIssuance`) appropriately. /// If the new free or reserved balance is below the existential deposit, /// it will reset the account nonce (`frame_system::AccountNonce`). /// /// The dispatch origin for this call is `root`. - /// - /// # - /// - Independent of the arguments. - /// - Contains a limited number of reads and writes. - /// --------------------- - /// - Base Weight: - /// - Creating: 27.56 µs - /// - Killing: 35.11 µs - /// - DB Weight: 1 Read, 1 Write to `who` - /// # + #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::set_balance_creating() // Creates a new account. .max(T::WeightInfo::set_balance_killing()) // Kills an existing account. )] pub fn set_balance( origin: OriginFor, - who: ::Source, + who: AccountIdLookupOf, #[pallet::compact] new_free: T::Balance, #[pallet::compact] new_reserved: T::Balance, ) -> DispatchResultWithPostInfo { @@ -328,25 +327,32 @@ pub mod pallet { let new_free = if wipeout { Zero::zero() } else { new_free }; let new_reserved = if wipeout { Zero::zero() } else { new_reserved }; - let (free, reserved) = Self::mutate_account(&who, |account| { - if new_free > account.free { - mem::drop(PositiveImbalance::::new(new_free - account.free)); - } else if new_free < account.free { - mem::drop(NegativeImbalance::::new(account.free - new_free)); - } - - if new_reserved > account.reserved { - mem::drop(PositiveImbalance::::new(new_reserved - account.reserved)); - } else if new_reserved < account.reserved { - mem::drop(NegativeImbalance::::new(account.reserved - new_reserved)); - } + // First we try to modify the account's balance to the forced balance. + let (old_free, old_reserved) = Self::mutate_account(&who, |account| { + let old_free = account.free; + let old_reserved = account.reserved; account.free = new_free; account.reserved = new_reserved; - (account.free, account.reserved) + (old_free, old_reserved) })?; - Self::deposit_event(Event::BalanceSet(who, free, reserved)); + + // This will adjust the total issuance, which was not done by the `mutate_account` + // above. + if new_free > old_free { + mem::drop(PositiveImbalance::::new(new_free - old_free)); + } else if new_free < old_free { + mem::drop(NegativeImbalance::::new(old_free - new_free)); + } + + if new_reserved > old_reserved { + mem::drop(PositiveImbalance::::new(new_reserved - old_reserved)); + } else if new_reserved < old_reserved { + mem::drop(NegativeImbalance::::new(old_reserved - new_reserved)); + } + + Self::deposit_event(Event::BalanceSet { who, free: new_free, reserved: new_reserved }); Ok(().into()) } @@ -356,11 +362,12 @@ pub mod pallet { /// - Same as transfer, but additional read and write because the source account is not /// assumed to be in the overlay. /// # + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::force_transfer())] pub fn force_transfer( origin: OriginFor, - source: ::Source, - dest: ::Source, + source: AccountIdLookupOf, + dest: AccountIdLookupOf, #[pallet::compact] value: T::Balance, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; @@ -381,15 +388,11 @@ pub mod pallet { /// 99% of the time you want [`transfer`] instead. /// /// [`transfer`]: struct.Pallet.html#method.transfer - /// # - /// - Cheaper than transfer because account cannot be killed. - /// - Base Weight: 51.4 µs - /// - DB Weight: 1 Read and 1 Write to dest (sender is in overlay already) - /// # + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::transfer_keep_alive())] pub fn transfer_keep_alive( origin: OriginFor, - dest: ::Source, + dest: AccountIdLookupOf, #[pallet::compact] value: T::Balance, ) -> DispatchResultWithPostInfo { let transactor = ensure_signed(origin)?; @@ -415,10 +418,11 @@ pub mod pallet { /// keep the sender account alive (true). # /// - O(1). Just like transfer, but reading the user's transferable balance first. /// # + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::transfer_all())] pub fn transfer_all( origin: OriginFor, - dest: ::Source, + dest: AccountIdLookupOf, keep_alive: bool, ) -> DispatchResult { use fungible::Inspect; @@ -426,22 +430,18 @@ pub mod pallet { let reducible_balance = Self::reducible_balance(&transactor, keep_alive); let dest = T::Lookup::lookup(dest)?; let keep_alive = if keep_alive { KeepAlive } else { AllowDeath }; - >::transfer( - &transactor, - &dest, - reducible_balance, - keep_alive.into(), - )?; + >::transfer(&transactor, &dest, reducible_balance, keep_alive)?; Ok(()) } /// Unreserve some balance from a user by force. /// /// Can only be called by ROOT. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::force_unreserve())] pub fn force_unreserve( origin: OriginFor, - who: ::Source, + who: AccountIdLookupOf, amount: T::Balance, ) -> DispatchResult { ensure_root(origin)?; @@ -454,38 +454,42 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { - /// An account was created with some free balance. \[account, free_balance\] - Endowed(T::AccountId, T::Balance), + /// An account was created with some free balance. + Endowed { account: T::AccountId, free_balance: T::Balance }, /// An account was removed whose balance was non-zero but below ExistentialDeposit, - /// resulting in an outright loss. \[account, balance\] - DustLost(T::AccountId, T::Balance), - /// Transfer succeeded. \[from, to, value\] - Transfer(T::AccountId, T::AccountId, T::Balance), - /// A balance was set by root. \[who, free, reserved\] - BalanceSet(T::AccountId, T::Balance, T::Balance), - /// Some amount was deposited (e.g. for transaction fees). \[who, deposit\] - Deposit(T::AccountId, T::Balance), - /// Some balance was reserved (moved from free to reserved). \[who, value\] - Reserved(T::AccountId, T::Balance), - /// Some balance was unreserved (moved from reserved to free). \[who, value\] - Unreserved(T::AccountId, T::Balance), + /// resulting in an outright loss. + DustLost { account: T::AccountId, amount: T::Balance }, + /// Transfer succeeded. + Transfer { from: T::AccountId, to: T::AccountId, amount: T::Balance }, + /// A balance was set by root. + BalanceSet { who: T::AccountId, free: T::Balance, reserved: T::Balance }, + /// Some balance was reserved (moved from free to reserved). + Reserved { who: T::AccountId, amount: T::Balance }, + /// Some balance was unreserved (moved from reserved to free). + Unreserved { who: T::AccountId, amount: T::Balance }, /// Some balance was moved from the reserve of the first account to the second account. /// Final argument indicates the destination balance type. - /// \[from, to, balance, destination_status\] - ReserveRepatriated(T::AccountId, T::AccountId, T::Balance, Status), + ReserveRepatriated { + from: T::AccountId, + to: T::AccountId, + amount: T::Balance, + destination_status: Status, + }, + /// Some amount was deposited (e.g. for transaction fees). + Deposit { who: T::AccountId, amount: T::Balance }, + /// Some amount was withdrawn from the account (e.g. for transaction fees). + Withdraw { who: T::AccountId, amount: T::Balance }, + /// Some amount was removed from the account (e.g. for misbehavior). + Slashed { who: T::AccountId, amount: T::Balance }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// Vesting balance too high to send value VestingBalance, /// Account liquidity restrictions prevent withdrawal LiquidityRestrictions, - /// Balance too low to send value + /// Balance too low to send value. InsufficientBalance, /// Value too low to create account due to existential deposit ExistentialDeposit, @@ -502,21 +506,43 @@ pub mod pallet { /// The total units issued in the system. #[pallet::storage] #[pallet::getter(fn total_issuance)] + #[pallet::whitelist_storage] pub type TotalIssuance, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>; - /// The balance of an account. + /// The total units of outstanding deactivated balance in the system. + #[pallet::storage] + #[pallet::getter(fn inactive_issuance)] + #[pallet::whitelist_storage] + pub type InactiveIssuance, I: 'static = ()> = + StorageValue<_, T::Balance, ValueQuery>; + + /// The Balances pallet example of storing the balance of an account. + /// + /// # Example + /// + /// ```nocompile + /// impl pallet_balances::Config for Runtime { + /// type AccountStore = StorageMapShim, frame_system::Provider, AccountId, Self::AccountData> + /// } + /// ``` + /// + /// You can also store the balance of an account in the `System` pallet. + /// + /// # Example + /// + /// ```nocompile + /// impl pallet_balances::Config for Runtime { + /// type AccountStore = System + /// } + /// ``` /// + /// But this comes with tradeoffs, storing account balances in the system pallet stores + /// `frame_system` data alongside the account data contrary to storing account balances in the + /// `Balances` pallet, which uses a `StorageMap` to store balances data only. /// NOTE: This is only used in the case that this pallet is used to store balances. #[pallet::storage] - pub type Account, I: 'static = ()> = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - AccountData, - ValueQuery, - GetDefault, - ConstU32<300_000>, - >; + pub type Account, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, AccountData, ValueQuery>; /// Any liquidity locks on some account balances. /// NOTE: Should only be accessed when setting, changing and freeing a lock. @@ -528,8 +554,6 @@ pub mod pallet { T::AccountId, WeakBoundedVec, T::MaxLocks>, ValueQuery, - GetDefault, - ConstU32<300_000>, >; /// Named reserves on some account balances. @@ -543,13 +567,6 @@ pub mod pallet { ValueQuery, >; - /// Storage version of the pallet. - /// - /// This is set to v2.0.0 for new networks. - #[pallet::storage] - pub(super) type StorageVersion, I: 'static = ()> = - StorageValue<_, Releases, ValueQuery>; - #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { pub balances: Vec<(T::AccountId, T::Balance)>, @@ -568,8 +585,6 @@ pub mod pallet { let total = self.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n); >::put(total); - >::put(Releases::V2_0_0); - for (_, balance) in &self.balances { assert!( *balance >= >::ExistentialDeposit::get(), @@ -628,7 +643,7 @@ pub enum Reasons { impl From for Reasons { fn from(r: WithdrawReasons) -> Reasons { - if r == WithdrawReasons::from(WithdrawReasons::TRANSACTION_PAYMENT) { + if r == WithdrawReasons::TRANSACTION_PAYMENT { Reasons::Fee } else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) { Reasons::All @@ -714,21 +729,6 @@ impl AccountData { } } -// A value placed in storage that represents the current version of the Balances storage. -// This value is used by the `on_runtime_upgrade` logic to determine whether we run -// storage migration logic. This should match directly with the semantic versions of the Rust crate. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -enum Releases { - V1_0_0, - V2_0_0, -} - -impl Default for Releases { - fn default() -> Self { - Releases::V1_0_0 - } -} - pub struct DustCleaner, I: 'static = ()>( Option<(T::AccountId, NegativeImbalance)>, ); @@ -736,7 +736,7 @@ pub struct DustCleaner, I: 'static = ()>( impl, I: 'static> Drop for DustCleaner { fn drop(&mut self) { if let Some((who, dust)) = self.0.take() { - Pallet::::deposit_event(Event::DustLost(who, dust.peek())); + Pallet::::deposit_event(Event::DustLost { account: who, amount: dust.peek() }); T::DustRemoval::on_unbalanced(dust); } } @@ -767,7 +767,7 @@ impl, I: 'static> Pallet { /// Get both the free and reserved balances of an account. fn account(who: &T::AccountId) -> AccountData { - T::AccountStore::get(&who) + T::AccountStore::get(who) } /// Handles any steps needed after mutating an account. @@ -798,12 +798,13 @@ impl, I: 'static> Pallet { _who: &T::AccountId, amount: T::Balance, account: &AccountData, + mint: bool, ) -> DepositConsequence { if amount.is_zero() { return DepositConsequence::Success } - if TotalIssuance::::get().checked_add(&amount).is_none() { + if mint && TotalIssuance::::get().checked_add(&amount).is_none() { return DepositConsequence::Overflow } @@ -933,7 +934,7 @@ impl, I: 'static> Pallet { }); result.map(|(maybe_endowed, maybe_dust, result)| { if let Some(endowed) = maybe_endowed { - Self::deposit_event(Event::Endowed(who.clone(), endowed)); + Self::deposit_event(Event::Endowed { account: who.clone(), free_balance: endowed }); } let dust_cleaner = DustCleaner(maybe_dust.map(|dust| (who.clone(), dust))); (result, dust_cleaner) @@ -979,17 +980,15 @@ impl, I: 'static> Pallet { } } else { Locks::::insert(who, bounded_locks); - if !existed { - if system::Pallet::::inc_consumers(who).is_err() { - // No providers for the locks. This is impossible under normal circumstances - // since the funds that are under the lock will themselves be stored in the - // account and therefore will need a reference. - log::warn!( - target: "runtime::balances", - "Warning: Attempt to introduce lock consumer reference, yet no providers. \ - This is unexpected but should be safe." - ); - } + if !existed && system::Pallet::::inc_consumers_without_limit(who).is_err() { + // No providers for the locks. This is impossible under normal circumstances + // since the funds that are under the lock will themselves be stored in the + // account and therefore will need a reference. + log::warn!( + target: "runtime::balances", + "Warning: Attempt to introduce lock consumer reference, yet no providers. \ + This is unexpected but should be safe." + ); } } } @@ -999,6 +998,8 @@ impl, I: 'static> Pallet { /// Is a no-op if: /// - the value to be moved is zero; or /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. + /// + /// NOTE: returns actual amount of transferred value in `Ok` case. fn do_transfer_reserved( slashed: &T::AccountId, beneficiary: &T::AccountId, @@ -1012,7 +1013,7 @@ impl, I: 'static> Pallet { if slashed == beneficiary { return match status { - Status::Free => Ok(Self::unreserve(slashed, value)), + Status::Free => Ok(value.saturating_sub(Self::unreserve(slashed, value))), Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance(slashed))), } } @@ -1045,12 +1046,12 @@ impl, I: 'static> Pallet { }, )?; - Self::deposit_event(Event::ReserveRepatriated( - slashed.clone(), - beneficiary.clone(), - actual, - status, - )); + Self::deposit_event(Event::ReserveRepatriated { + from: slashed.clone(), + to: beneficiary.clone(), + amount: actual, + destination_status: status, + }); Ok(actual) } } @@ -1061,6 +1062,9 @@ impl, I: 'static> fungible::Inspect for Pallet fn total_issuance() -> Self::Balance { TotalIssuance::::get() } + fn active_issuance() -> Self::Balance { + TotalIssuance::::get().saturating_sub(InactiveIssuance::::get()) + } fn minimum_balance() -> Self::Balance { T::ExistentialDeposit::get() } @@ -1081,8 +1085,8 @@ impl, I: 'static> fungible::Inspect for Pallet liquid.saturating_sub(must_remain_to_exist) } } - fn can_deposit(who: &T::AccountId, amount: Self::Balance) -> DepositConsequence { - Self::deposit_consequence(who, amount, &Self::account(who)) + fn can_deposit(who: &T::AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence { + Self::deposit_consequence(who, amount, &Self::account(who), mint) } fn can_withdraw( who: &T::AccountId, @@ -1098,11 +1102,12 @@ impl, I: 'static> fungible::Mutate for Pallet { return Ok(()) } Self::try_mutate_account(who, |account, _is_new| -> DispatchResult { - Self::deposit_consequence(who, amount, &account).into_result()?; + Self::deposit_consequence(who, amount, account, true).into_result()?; account.free += amount; Ok(()) })?; TotalIssuance::::mutate(|t| *t += amount); + Self::deposit_event(Event::Deposit { who: who.clone(), amount }); Ok(()) } @@ -1116,13 +1121,14 @@ impl, I: 'static> fungible::Mutate for Pallet { let actual = Self::try_mutate_account( who, |account, _is_new| -> Result { - let extra = Self::withdraw_consequence(who, amount, &account).into_result()?; + let extra = Self::withdraw_consequence(who, amount, account).into_result()?; let actual = amount + extra; account.free -= actual; Ok(actual) }, )?; TotalIssuance::::mutate(|t| *t -= actual); + Self::deposit_event(Event::Withdraw { who: who.clone(), amount }); Ok(actual) } } @@ -1137,12 +1143,31 @@ impl, I: 'static> fungible::Transfer for Pallet let er = if keep_alive { KeepAlive } else { AllowDeath }; >::transfer(source, dest, amount, er).map(|_| amount) } + + fn deactivate(amount: Self::Balance) { + InactiveIssuance::::mutate(|b| b.saturating_accrue(amount)); + } + + fn reactivate(amount: Self::Balance) { + InactiveIssuance::::mutate(|b| b.saturating_reduce(amount)); + } } impl, I: 'static> fungible::Unbalanced for Pallet { fn set_balance(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - Self::mutate_account(who, |account| account.free = amount)?; - Ok(()) + Self::mutate_account(who, |account| -> DispatchResult { + // fungibles::Unbalanced::decrease_balance didn't check account.reserved + // free = new_balance - reserved + account.free = + amount.checked_sub(&account.reserved).ok_or(ArithmeticError::Underflow)?; + Self::deposit_event(Event::BalanceSet { + who: who.clone(), + free: account.free, + reserved: account.reserved, + }); + + Ok(()) + })? } fn set_total_issuance(amount: Self::Balance) { @@ -1196,7 +1221,7 @@ impl, I: 'static> fungible::MutateHold for Pallet::InsufficientBalance); // ^^^ Guaranteed to be <= amount and <= a.reserved a.free = new_free; - a.reserved = a.reserved.saturating_sub(actual.clone()); + a.reserved = a.reserved.saturating_sub(actual); Ok(actual) }) } @@ -1300,7 +1325,7 @@ mod imbalances { } } fn peek(&self) -> T::Balance { - self.0.clone() + self.0 } } @@ -1359,7 +1384,7 @@ mod imbalances { } } fn peek(&self) -> T::Balance { - self.0.clone() + self.0 } } @@ -1399,7 +1424,19 @@ where } fn total_issuance() -> Self::Balance { - >::get() + TotalIssuance::::get() + } + + fn active_issuance() -> Self::Balance { + >::active_issuance() + } + + fn deactivate(amount: Self::Balance) { + >::deactivate(amount); + } + + fn reactivate(amount: Self::Balance) { + >::reactivate(amount); } fn minimum_balance() -> Self::Balance { @@ -1503,7 +1540,7 @@ where .map_err(|_| Error::::LiquidityRestrictions)?; // TODO: This is over-conservative. There may now be other providers, and - // this pallet may not even be a provider. + // this pallet may not even be a provider. let allow_death = existence_requirement == ExistenceRequirement::AllowDeath; let allow_death = allow_death && system::Pallet::::can_dec_provider(transactor); @@ -1520,7 +1557,11 @@ where )?; // Emit transfer event. - Self::deposit_event(Event::Transfer(transactor.clone(), dest.clone(), value)); + Self::deposit_event(Event::Transfer { + from: transactor.clone(), + to: dest.clone(), + amount: value, + }); Ok(()) } @@ -1538,7 +1579,7 @@ where if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) } - if Self::total_balance(&who).is_zero() { + if Self::total_balance(who).is_zero() { return (NegativeImbalance::zero(), value) } @@ -1583,7 +1624,13 @@ where } }, ) { - Ok(r) => return r, + Ok((imbalance, not_slashed)) => { + Self::deposit_event(Event::Slashed { + who: who.clone(), + amount: value.saturating_sub(not_slashed), + }); + return (imbalance, not_slashed) + }, Err(_) => (), } } @@ -1608,6 +1655,7 @@ where |account, is_new| -> Result { ensure!(!is_new, Error::::DeadAccount); account.free = account.free.checked_add(&value).ok_or(ArithmeticError::Overflow)?; + Self::deposit_event(Event::Deposit { who: who.clone(), amount: value }); Ok(PositiveImbalance::new(value)) }, ) @@ -1627,7 +1675,7 @@ where return Self::PositiveImbalance::zero() } - let r = Self::try_mutate_account( + Self::try_mutate_account( who, |account, is_new| -> Result { let ed = T::ExistentialDeposit::get(); @@ -1640,12 +1688,11 @@ where None => return Ok(Self::PositiveImbalance::zero()), }; + Self::deposit_event(Event::Deposit { who: who.clone(), amount: value }); Ok(PositiveImbalance::new(value)) }, ) - .unwrap_or_else(|_| Self::PositiveImbalance::zero()); - - r + .unwrap_or_else(|_| Self::PositiveImbalance::zero()) } /// Withdraw some free balance from an account, respecting existence requirements. @@ -1677,6 +1724,7 @@ where account.free = new_free_account; + Self::deposit_event(Event::Withdraw { who: who.clone(), amount: value }); Ok(NegativeImbalance::new(value)) }, ) @@ -1709,6 +1757,11 @@ where SignedImbalance::Negative(NegativeImbalance::new(account.free - value)) }; account.free = value; + Self::deposit_event(Event::BalanceSet { + who: who.clone(), + free: account.free, + reserved: account.reserved, + }); Ok(imbalance) }, ) @@ -1749,21 +1802,23 @@ where account.free.checked_sub(&value).ok_or(Error::::InsufficientBalance)?; account.reserved = account.reserved.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - Self::ensure_can_withdraw(&who, value.clone(), WithdrawReasons::RESERVE, account.free) + Self::ensure_can_withdraw(&who, value, WithdrawReasons::RESERVE, account.free) })?; - Self::deposit_event(Event::Reserved(who.clone(), value)); + Self::deposit_event(Event::Reserved { who: who.clone(), amount: value }); Ok(()) } /// Unreserve some funds, returning any amount that was unable to be unreserved. /// /// Is a no-op if the value to be unreserved is zero or the account does not exist. + /// + /// NOTE: returns amount value which wasn't successfully unreserved. fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { if value.is_zero() { return Zero::zero() } - if Self::total_balance(&who).is_zero() { + if Self::total_balance(who).is_zero() { return value } @@ -1772,7 +1827,7 @@ where account.reserved -= actual; // defensive only: this can never fail since total issuance which is at least // free+reserved fits into the same data type. - account.free = account.free.saturating_add(actual); + account.free = account.free.defensive_saturating_add(actual); actual }) { Ok(x) => x, @@ -1784,7 +1839,7 @@ where }, }; - Self::deposit_event(Event::Unreserved(who.clone(), actual.clone())); + Self::deposit_event(Event::Unreserved { who: who.clone(), amount: actual }); value - actual } @@ -1799,7 +1854,7 @@ where if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) } - if Self::total_balance(&who).is_zero() { + if Self::total_balance(who).is_zero() { return (NegativeImbalance::zero(), value) } @@ -1824,7 +1879,13 @@ where // underflow should never happen, but it if does, there's nothing to be done here. (NegativeImbalance::new(actual), value - actual) }) { - Ok(r) => return r, + Ok((imbalance, not_slashed)) => { + Self::deposit_event(Event::Slashed { + who: who.clone(), + amount: value.saturating_sub(not_slashed), + }); + return (imbalance, not_slashed) + }, Err(_) => (), } } @@ -1879,11 +1940,11 @@ where match reserves.binary_search_by_key(id, |data| data.id) { Ok(index) => { // this add can't overflow but just to be defensive. - reserves[index].amount = reserves[index].amount.saturating_add(value); + reserves[index].amount = reserves[index].amount.defensive_saturating_add(value); }, Err(index) => { reserves - .try_insert(index, ReserveData { id: id.clone(), amount: value }) + .try_insert(index, ReserveData { id: *id, amount: value }) .map_err(|_| Error::::TooManyReserves)?; }, }; @@ -1912,8 +1973,8 @@ where let remain = >::unreserve(who, to_change); - // remain should always be zero but just to be defensive here - let actual = to_change.saturating_sub(remain); + // remain should always be zero but just to be defensive here. + let actual = to_change.defensive_saturating_sub(remain); // `actual <= to_change` and `to_change <= amount`; qed; reserves[index].amount -= actual; @@ -1959,12 +2020,13 @@ where let (imb, remain) = >::slash_reserved(who, to_change); - // remain should always be zero but just to be defensive here - let actual = to_change.saturating_sub(remain); + // remain should always be zero but just to be defensive here. + let actual = to_change.defensive_saturating_sub(remain); // `actual <= to_change` and `to_change <= amount`; qed; reserves[index].amount -= actual; + Self::deposit_event(Event::Slashed { who: who.clone(), amount: actual }); (imb, value - actual) }, Err(_) => (NegativeImbalance::zero(), value), @@ -2018,12 +2080,12 @@ where )?; // remain should always be zero but just to be defensive - // here - let actual = to_change.saturating_sub(remain); + // here. + let actual = to_change.defensive_saturating_sub(remain); // this add can't overflow but just to be defensive. reserves[index].amount = - reserves[index].amount.saturating_add(actual); + reserves[index].amount.defensive_saturating_add(actual); Ok(actual) }, @@ -2038,12 +2100,12 @@ where // remain should always be zero but just to be defensive // here - let actual = to_change.saturating_sub(remain); + let actual = to_change.defensive_saturating_sub(remain); reserves .try_insert( index, - ReserveData { id: id.clone(), amount: actual }, + ReserveData { id: *id, amount: actual }, ) .map_err(|_| Error::::TooManyReserves)?; @@ -2061,7 +2123,7 @@ where )?; // remain should always be zero but just to be defensive here - to_change.saturating_sub(remain) + to_change.defensive_saturating_sub(remain) }; // `actual <= to_change` and `to_change <= amount`; qed; diff --git a/frame/balances/src/migration.rs b/frame/balances/src/migration.rs new file mode 100644 index 0000000000000..08e1d8c7a2c74 --- /dev/null +++ b/frame/balances/src/migration.rs @@ -0,0 +1,71 @@ +// Copyright 2017-2020 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 frame_support::{ + pallet_prelude::*, + traits::{OnRuntimeUpgrade, PalletInfoAccess}, + weights::Weight, +}; + +fn migrate_v0_to_v1, I: 'static>(accounts: &[T::AccountId]) -> Weight { + let onchain_version = Pallet::::on_chain_storage_version(); + + if onchain_version == 0 { + let total = accounts + .iter() + .map(|a| Pallet::::total_balance(a)) + .fold(T::Balance::zero(), |a, e| a.saturating_add(e)); + Pallet::::deactivate(total); + + // Remove the old `StorageVersion` type. + frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix( + Pallet::::name().as_bytes(), + "StorageVersion".as_bytes(), + )); + + // Set storage version to `1`. + StorageVersion::new(1).put::>(); + + log::info!(target: "runtime::balances", "Storage to version 1"); + T::DbWeight::get().reads_writes(2 + accounts.len() as u64, 3) + } else { + log::info!(target: "runtime::balances", "Migration did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } +} + +// NOTE: This must be used alongside the account whose balance is expected to be inactive. +// Generally this will be used for the XCM teleport checking account. +pub struct MigrateToTrackInactive(PhantomData<(T, A, I)>); +impl, A: Get, I: 'static> OnRuntimeUpgrade + for MigrateToTrackInactive +{ + fn on_runtime_upgrade() -> Weight { + migrate_v0_to_v1::(&[A::get()]) + } +} + +// NOTE: This must be used alongside the accounts whose balance is expected to be inactive. +// Generally this will be used for the XCM teleport checking accounts. +pub struct MigrateManyToTrackInactive(PhantomData<(T, A, I)>); +impl, A: Get>, I: 'static> OnRuntimeUpgrade + for MigrateManyToTrackInactive +{ + fn on_runtime_upgrade() -> Weight { + migrate_v0_to_v1::(&A::get()) + } +} diff --git a/frame/balances/src/tests.rs b/frame/balances/src/tests.rs index a08643821eba8..83944caf9f7ff 100644 --- a/frame/balances/src/tests.rs +++ b/frame/balances/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ macro_rules! decl_tests { ($test:ty, $ext_builder:ty, $existential_deposit:expr) => { use crate::*; - use sp_runtime::{ArithmeticError, FixedPointNumber, traits::{SignedExtension, BadOrigin}}; + use sp_runtime::{ArithmeticError, TokenError, FixedPointNumber, traits::{SignedExtension, BadOrigin}}; use frame_support::{ assert_noop, assert_storage_noop, assert_ok, assert_err, traits::{ @@ -38,15 +38,15 @@ macro_rules! decl_tests { const ID_1: LockIdentifier = *b"1 "; const ID_2: LockIdentifier = *b"2 "; - pub const CALL: &<$test as frame_system::Config>::Call = - &Call::Balances(pallet_balances::Call::transfer { dest: 0, value: 0 }); + pub const CALL: &<$test as frame_system::Config>::RuntimeCall = + &RuntimeCall::Balances(pallet_balances::Call::transfer { dest: 0, value: 0 }); /// create a transaction info struct from weight. Handy to avoid building the whole struct. pub fn info_from_weight(w: Weight) -> DispatchInfo { DispatchInfo { weight: w, ..Default::default() } } - fn events() -> Vec { + fn events() -> Vec { let evt = System::events().into_iter().map(|evt| evt.event).collect::>(); System::reset_events(); @@ -188,14 +188,14 @@ macro_rules! decl_tests { ChargeTransactionPayment::from(1), &1, CALL, - &info_from_weight(1), + &info_from_weight(Weight::from_ref_time(1)), 1, ).is_err()); assert_ok!( as SignedExtension>::pre_dispatch( ChargeTransactionPayment::from(0), &1, CALL, - &info_from_weight(1), + &info_from_weight(Weight::from_ref_time(1)), 1, )); @@ -206,14 +206,14 @@ macro_rules! decl_tests { ChargeTransactionPayment::from(1), &1, CALL, - &info_from_weight(1), + &info_from_weight(Weight::from_ref_time(1)), 1, ).is_err()); assert!( as SignedExtension>::pre_dispatch( ChargeTransactionPayment::from(0), &1, CALL, - &info_from_weight(1), + &info_from_weight(Weight::from_ref_time(1)), 1, ).is_err()); }); @@ -314,6 +314,7 @@ macro_rules! decl_tests { <$ext_builder>::default().monied(true).build().execute_with(|| { assert_eq!(Balances::total_balance(&1), 10); assert_ok!(Balances::deposit_into_existing(&1, 10).map(drop)); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Deposit { who: 1, amount: 10 })); assert_eq!(Balances::total_balance(&1), 20); assert_eq!(>::get(), 120); }); @@ -341,6 +342,7 @@ macro_rules! decl_tests { fn balance_works() { <$ext_builder>::default().build().execute_with(|| { let _ = Balances::deposit_creating(&1, 42); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { who: 1, amount: 42 })); assert_eq!(Balances::free_balance(1), 42); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Balances::total_balance(&1), 42); @@ -435,6 +437,19 @@ macro_rules! decl_tests { }); } + #[test] + fn withdrawing_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&2, 111); + let _ = Balances::withdraw( + &2, 11, WithdrawReasons::TRANSFER, ExistenceRequirement::KeepAlive + ); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Withdraw { who: 2, amount: 11 })); + assert_eq!(Balances::free_balance(2), 100); + assert_eq!(>::get(), 100); + }); + } + #[test] fn slashing_incomplete_balance_should_work() { <$ext_builder>::default().build().execute_with(|| { @@ -490,7 +505,7 @@ macro_rules! decl_tests { assert_ok!(Balances::reserve(&1, 110)); assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Status::Free), 0); System::assert_last_event( - Event::Balances(crate::Event::ReserveRepatriated(1, 2, 41, Status::Free)) + RuntimeEvent::Balances(crate::Event::ReserveRepatriated { from: 1, to: 2, amount: 41, destination_status: Status::Free }) ); assert_eq!(Balances::reserved_balance(1), 69); assert_eq!(Balances::free_balance(1), 0); @@ -513,6 +528,22 @@ macro_rules! decl_tests { }); } + #[test] + fn transferring_reserved_balance_to_yourself_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); + assert_ok!(Balances::reserve(&1, 50)); + assert_ok!(Balances::repatriate_reserved(&1, &1, 50, Status::Free), 0); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance(1), 0); + + assert_ok!(Balances::reserve(&1, 50)); + assert_ok!(Balances::repatriate_reserved(&1, &1, 60, Status::Free), 10); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance(1), 0); + }); + } + #[test] fn transferring_reserved_balance_to_nonexistent_should_fail() { <$ext_builder>::default().build().execute_with(|| { @@ -709,18 +740,18 @@ macro_rules! decl_tests { System::set_block_number(2); assert_ok!(Balances::reserve(&1, 10)); - System::assert_last_event(Event::Balances(crate::Event::Reserved(1, 10))); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Reserved { who: 1, amount: 10 })); System::set_block_number(3); assert!(Balances::unreserve(&1, 5).is_zero()); - System::assert_last_event(Event::Balances(crate::Event::Unreserved(1, 5))); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { who: 1, amount: 5 })); System::set_block_number(4); assert_eq!(Balances::unreserve(&1, 6), 1); // should only unreserve 5 - System::assert_last_event(Event::Balances(crate::Event::Unreserved(1, 5))); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { who: 1, amount: 5 })); }); } @@ -735,9 +766,9 @@ macro_rules! decl_tests { assert_eq!( events(), [ - Event::System(system::Event::NewAccount(1)), - Event::Balances(crate::Event::Endowed(1, 100)), - Event::Balances(crate::Event::BalanceSet(1, 100, 0)), + RuntimeEvent::System(system::Event::NewAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), + RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100, reserved: 0 }), ] ); @@ -747,8 +778,9 @@ macro_rules! decl_tests { assert_eq!( events(), [ - Event::System(system::Event::KilledAccount(1)), - Event::Balances(crate::Event::DustLost(1, 99)), + RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 99 }), + RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1 }), ] ); }); @@ -765,9 +797,9 @@ macro_rules! decl_tests { assert_eq!( events(), [ - Event::System(system::Event::NewAccount(1)), - Event::Balances(crate::Event::Endowed(1, 100)), - Event::Balances(crate::Event::BalanceSet(1, 100, 0)), + RuntimeEvent::System(system::Event::NewAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), + RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100, reserved: 0 }), ] ); @@ -777,7 +809,8 @@ macro_rules! decl_tests { assert_eq!( events(), [ - Event::System(system::Event::KilledAccount(1)) + RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 100 }), ] ); }); @@ -792,42 +825,43 @@ macro_rules! decl_tests { /* User has no reference counter, so they can die in these scenarios */ // SCENARIO: Slash would not kill account. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); // Slashed completed in full assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); // Account is still alive assert!(System::account_exists(&1)); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 900 })); // SCENARIO: Slash will kill account because not enough balance left. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); // Slashed completed in full assert_eq!(Balances::slash(&1, 950), (NegativeImbalance::new(950), 0)); // Account is killed assert!(!System::account_exists(&1)); // SCENARIO: Over-slash will kill account, and report missing slash amount. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); // Slashed full free_balance, and reports 300 not slashed assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1000), 300)); // Account is dead assert!(!System::account_exists(&1)); // SCENARIO: Over-slash can take from reserved, but keep alive. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 400)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 400)); // Slashed full free_balance and 300 of reserved balance assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1300), 0)); // Account is still alive assert!(System::account_exists(&1)); // SCENARIO: Over-slash can take from reserved, and kill. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 350)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 350)); // Slashed full free_balance and 300 of reserved balance assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1300), 0)); // Account is dead because 50 reserved balance is not enough to keep alive assert!(!System::account_exists(&1)); // SCENARIO: Over-slash can take as much as possible from reserved, kill, and report missing amount. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 250)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 250)); // Slashed full free_balance and 300 of reserved balance assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1250), 50)); // Account is super dead @@ -836,7 +870,7 @@ macro_rules! decl_tests { /* User will now have a reference counter on them, keeping them alive in these scenarios */ // SCENARIO: Slash would not kill account. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests // Slashed completed in full assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); @@ -844,35 +878,35 @@ macro_rules! decl_tests { assert!(System::account_exists(&1)); // SCENARIO: Slash will take as much as possible without killing account. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); // Slashed completed in full assert_eq!(Balances::slash(&1, 950), (NegativeImbalance::new(900), 50)); // Account is still alive assert!(System::account_exists(&1)); // SCENARIO: Over-slash will not kill account, and report missing slash amount. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); // Slashed full free_balance minus ED, and reports 400 not slashed assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(900), 400)); // Account is still alive assert!(System::account_exists(&1)); // SCENARIO: Over-slash can take from reserved, but keep alive. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 400)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 400)); // Slashed full free_balance and 300 of reserved balance assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1300), 0)); // Account is still alive assert!(System::account_exists(&1)); // SCENARIO: Over-slash can take from reserved, but keep alive. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 350)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 350)); // Slashed full free_balance and 250 of reserved balance to leave ED assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1250), 50)); // Account is still alive assert!(System::account_exists(&1)); // SCENARIO: Over-slash can take as much as possible from reserved and report missing amount. - assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 250)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 250)); // Slashed full free_balance and 300 of reserved balance assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1150), 150)); // Account is still alive @@ -892,28 +926,28 @@ macro_rules! decl_tests { /* User has no reference counter, so they can die in these scenarios */ // SCENARIO: Slash would not kill account. - assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); // Slashed completed in full assert_eq!(Balances::slash_reserved(&1, 900), (NegativeImbalance::new(900), 0)); // Account is still alive assert!(System::account_exists(&1)); // SCENARIO: Slash would kill account. - assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); // Slashed completed in full assert_eq!(Balances::slash_reserved(&1, 1_000), (NegativeImbalance::new(1_000), 0)); // Account is dead assert!(!System::account_exists(&1)); // SCENARIO: Over-slash would kill account, and reports left over slash. - assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); // Slashed completed in full assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(1_000), 300)); // Account is dead assert!(!System::account_exists(&1)); // SCENARIO: Over-slash does not take from free balance. - assert_ok!(Balances::set_balance(Origin::root(), 1, 300, 1_000)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 300, 1_000)); // Slashed completed in full assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(1_000), 300)); // Account is alive because of free balance @@ -922,7 +956,7 @@ macro_rules! decl_tests { /* User has a reference counter, so they cannot die */ // SCENARIO: Slash would not kill account. - assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests // Slashed completed in full assert_eq!(Balances::slash_reserved(&1, 900), (NegativeImbalance::new(900), 0)); @@ -930,21 +964,21 @@ macro_rules! decl_tests { assert!(System::account_exists(&1)); // SCENARIO: Slash as much as possible without killing. - assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); // Slashed as much as possible assert_eq!(Balances::slash_reserved(&1, 1_000), (NegativeImbalance::new(950), 50)); // Account is still alive assert!(System::account_exists(&1)); // SCENARIO: Over-slash reports correctly, where reserved is needed to keep alive. - assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); // Slashed as much as possible assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(950), 350)); // Account is still alive assert!(System::account_exists(&1)); // SCENARIO: Over-slash reports correctly, where full reserved is removed. - assert_ok!(Balances::set_balance(Origin::root(), 1, 200, 1_000)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 200, 1_000)); // Slashed as much as possible assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(1_000), 300)); // Account is still alive @@ -984,7 +1018,7 @@ macro_rules! decl_tests { .existential_deposit(100) .build() .execute_with(|| { - assert_ok!(Balances::set_balance(Origin::root(), 1, 100, 100)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 100, 100)); assert_ok!(Balances::transfer_keep_alive(Some(1).into(), 2, 100)); assert_eq!(Balances::total_balance(&1), 100); assert_eq!(Balances::total_balance(&2), 100); @@ -998,32 +1032,32 @@ macro_rules! decl_tests { .build() .execute_with(|| { // setup - assert_ok!(Balances::set_balance(Origin::root(), 1, 200, 0)); - assert_ok!(Balances::set_balance(Origin::root(), 2, 0, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 200, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 0, 0)); // transfer all and allow death assert_ok!(Balances::transfer_all(Some(1).into(), 2, false)); assert_eq!(Balances::total_balance(&1), 0); assert_eq!(Balances::total_balance(&2), 200); // setup - assert_ok!(Balances::set_balance(Origin::root(), 1, 200, 0)); - assert_ok!(Balances::set_balance(Origin::root(), 2, 0, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 200, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 0, 0)); // transfer all and keep alive assert_ok!(Balances::transfer_all(Some(1).into(), 2, true)); assert_eq!(Balances::total_balance(&1), 100); assert_eq!(Balances::total_balance(&2), 100); // setup - assert_ok!(Balances::set_balance(Origin::root(), 1, 200, 10)); - assert_ok!(Balances::set_balance(Origin::root(), 2, 0, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 200, 10)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 0, 0)); // transfer all and allow death w/ reserved assert_ok!(Balances::transfer_all(Some(1).into(), 2, false)); assert_eq!(Balances::total_balance(&1), 0); assert_eq!(Balances::total_balance(&2), 200); // setup - assert_ok!(Balances::set_balance(Origin::root(), 1, 200, 10)); - assert_ok!(Balances::set_balance(Origin::root(), 2, 0, 0)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 200, 10)); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 0, 0)); // transfer all and keep alive w/ reserved assert_ok!(Balances::transfer_all(Some(1).into(), 2, true)); assert_eq!(Balances::total_balance(&1), 100); @@ -1149,6 +1183,25 @@ macro_rules! decl_tests { }); } + #[test] + fn reserved_named_to_yourself_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); + + let id = [1u8; 8]; + + assert_ok!(Balances::reserve_named(&id, &1, 50)); + assert_ok!(Balances::repatriate_reserved_named(&id, &1, &1, 50, Status::Free), 0); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + + assert_ok!(Balances::reserve_named(&id, &1, 50)); + assert_ok!(Balances::repatriate_reserved_named(&id, &1, &1, 60, Status::Free), 10); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + }); + } + #[test] fn ensure_reserved_named_should_work() { <$ext_builder>::default().build().execute_with(|| { @@ -1221,5 +1274,187 @@ macro_rules! decl_tests { assert_eq!(Balances::free_balance(&3), 25); }); } + + #[test] + fn set_balance_handles_killing_account() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(frame_system::Pallet::::inc_consumers(&1)); + assert_noop!( + Balances::set_balance(RuntimeOrigin::root(), 1, 0, 0), + DispatchError::ConsumerRemaining, + ); + }); + } + + #[test] + fn set_balance_handles_total_issuance() { + <$ext_builder>::default().build().execute_with(|| { + let old_total_issuance = Balances::total_issuance(); + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1337, 69, 42)); + assert_eq!(Balances::total_issuance(), old_total_issuance + 69 + 42); + assert_eq!(Balances::total_balance(&1337), 69 + 42); + assert_eq!(Balances::free_balance(&1337), 69); + assert_eq!(Balances::reserved_balance(&1337), 42); + }); + } + + #[test] + fn fungible_unbalanced_trait_set_balance_works() { + <$ext_builder>::default().build().execute_with(|| { + assert_eq!(>::balance(&1337), 0); + assert_ok!(>::set_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + + assert_ok!(Balances::reserve(&1337, 60)); + assert_eq!(Balances::free_balance(1337) , 40); + assert_eq!(Balances::reserved_balance(1337), 60); + + assert_noop!(>::set_balance(&1337, 0), ArithmeticError::Underflow); + + assert_ok!(>::set_balance(&1337, 60)); + assert_eq!(Balances::free_balance(1337) , 0); + assert_eq!(Balances::reserved_balance(1337), 60); + }); + } + + #[test] + fn fungible_unbalanced_trait_set_total_issuance_works() { + <$ext_builder>::default().build().execute_with(|| { + assert_eq!(>::total_issuance(), 0); + >::set_total_issuance(100); + assert_eq!(>::total_issuance(), 100); + }); + } + + #[test] + fn fungible_unbalanced_trait_decrease_balance_simple_works() { + <$ext_builder>::default().build().execute_with(|| { + // An Account that starts at 100 + assert_ok!(>::set_balance(&1337, 100)); + // and reserves 50 + assert_ok!(Balances::reserve(&1337, 50)); + // and is decreased by 20 + assert_ok!(>::decrease_balance(&1337, 20)); + // should end up at 80. + assert_eq!(>::balance(&1337), 80); + }); + } + + #[test] + fn fungible_unbalanced_trait_decrease_balance_works() { + <$ext_builder>::default().build().execute_with(|| { + assert_ok!(>::set_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + + assert_noop!( + >::decrease_balance(&1337, 101), + TokenError::NoFunds + ); + assert_eq!( + >::decrease_balance(&1337, 100), + Ok(100) + ); + assert_eq!(>::balance(&1337), 0); + + // free: 40, reserved: 60 + assert_ok!(>::set_balance(&1337, 100)); + assert_ok!(Balances::reserve(&1337, 60)); + assert_eq!(Balances::free_balance(1337) , 40); + assert_eq!(Balances::reserved_balance(1337), 60); + assert_noop!( + >::decrease_balance(&1337, 41), + TokenError::NoFunds + ); + assert_eq!( + >::decrease_balance(&1337, 40), + Ok(40) + ); + assert_eq!(>::balance(&1337), 60); + assert_eq!(Balances::free_balance(1337), 0); + assert_eq!(Balances::reserved_balance(1337), 60); + }); + } + + #[test] + fn fungible_unbalanced_trait_decrease_balance_at_most_works() { + <$ext_builder>::default().build().execute_with(|| { + assert_ok!(>::set_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + + assert_eq!( + >::decrease_balance_at_most(&1337, 101), + 100 + ); + assert_eq!(>::balance(&1337), 0); + + assert_ok!(>::set_balance(&1337, 100)); + assert_eq!( + >::decrease_balance_at_most(&1337, 100), + 100 + ); + assert_eq!(>::balance(&1337), 0); + + // free: 40, reserved: 60 + assert_ok!(>::set_balance(&1337, 100)); + assert_ok!(Balances::reserve(&1337, 60)); + assert_eq!(Balances::free_balance(1337) , 40); + assert_eq!(Balances::reserved_balance(1337), 60); + assert_eq!( + >::decrease_balance_at_most(&1337, 0), + 0 + ); + assert_eq!(Balances::free_balance(1337) , 40); + assert_eq!(Balances::reserved_balance(1337), 60); + assert_eq!( + >::decrease_balance_at_most(&1337, 10), + 10 + ); + assert_eq!(Balances::free_balance(1337), 30); + assert_eq!( + >::decrease_balance_at_most(&1337, 200), + 30 + ); + assert_eq!(>::balance(&1337), 60); + assert_eq!(Balances::free_balance(1337), 0); + assert_eq!(Balances::reserved_balance(1337), 60); + }); + } + + #[test] + fn fungible_unbalanced_trait_increase_balance_works() { + <$ext_builder>::default().build().execute_with(|| { + assert_noop!( + >::increase_balance(&1337, 0), + TokenError::BelowMinimum + ); + assert_eq!( + >::increase_balance(&1337, 1), + Ok(1) + ); + assert_noop!( + >::increase_balance(&1337, u64::MAX), + ArithmeticError::Overflow + ); + }); + } + + #[test] + fn fungible_unbalanced_trait_increase_balance_at_most_works() { + <$ext_builder>::default().build().execute_with(|| { + assert_eq!( + >::increase_balance_at_most(&1337, 0), + 0 + ); + assert_eq!( + >::increase_balance_at_most(&1337, 1), + 1 + ); + assert_eq!( + >::increase_balance_at_most(&1337, u64::MAX), + u64::MAX - 1 + ); + }); + } } } diff --git a/frame/balances/src/tests_composite.rs b/frame/balances/src/tests_composite.rs index f6faebed39316..f8a8fdd1851d4 100644 --- a/frame/balances/src/tests_composite.rs +++ b/frame/balances/src/tests_composite.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,10 @@ use crate::{self as pallet_balances, decl_tests, Config, Pallet}; use frame_support::{ + dispatch::DispatchInfo, parameter_types, - weights::{DispatchInfo, IdentityFee, Weight}, + traits::{ConstU32, ConstU64, ConstU8}, + weights::{IdentityFee, Weight}, }; use pallet_transaction_payment::CurrencyAdapter; use sp_core::H256; @@ -39,14 +41,15 @@ frame_support::construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, } ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max( + frame_support::weights::Weight::from_ref_time(1024).set_proof_size(u64::MAX), + ); pub static ExistentialDeposit: u64 = 0; } impl frame_system::Config for Test { @@ -54,17 +57,17 @@ impl frame_system::Config for Test { type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = super::AccountData; @@ -73,29 +76,26 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } -parameter_types! { - pub const TransactionByteFee: u64 = 1; -} + impl pallet_transaction_payment::Config for Test { + type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = CurrencyAdapter, ()>; - type TransactionByteFee = TransactionByteFee; + type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; type FeeMultiplierUpdate = (); } -parameter_types! { - pub const MaxReserves: u32 = 2; -} - impl Config for Test { type Balance = u64; type DustRemoval = (); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; type AccountStore = frame_system::Pallet; type MaxLocks = (); - type MaxReserves = MaxReserves; + type MaxReserves = ConstU32<2>; type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } diff --git a/frame/balances/src/tests_local.rs b/frame/balances/src/tests_local.rs index d8c07aa9c42e5..152a5da37410f 100644 --- a/frame/balances/src/tests_local.rs +++ b/frame/balances/src/tests_local.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,10 @@ use crate::{self as pallet_balances, decl_tests, Config, Pallet}; use frame_support::{ + dispatch::DispatchInfo, parameter_types, - traits::StorageMapShim, - weights::{DispatchInfo, IdentityFee, Weight}, + traits::{ConstU32, ConstU64, ConstU8, StorageMapShim}, + weights::{IdentityFee, Weight}, }; use pallet_transaction_payment::CurrencyAdapter; use sp_core::H256; @@ -34,21 +35,22 @@ type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where + pub struct Test where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, } ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max( + frame_support::weights::Weight::from_ref_time(1024).set_proof_size(u64::MAX), + ); pub static ExistentialDeposit: u64 = 0; } impl frame_system::Config for Test { @@ -56,17 +58,17 @@ impl frame_system::Config for Test { type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -75,29 +77,27 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } -parameter_types! { - pub const TransactionByteFee: u64 = 1; -} + impl pallet_transaction_payment::Config for Test { + type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = CurrencyAdapter, ()>; - type TransactionByteFee = TransactionByteFee; + type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; type FeeMultiplierUpdate = (); } -parameter_types! { - pub const MaxLocks: u32 = 50; - pub const MaxReserves: u32 = 2; -} + impl Config for Test { type Balance = u64; type DustRemoval = (); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; type AccountStore = StorageMapShim, system::Provider, u64, super::AccountData>; - type MaxLocks = MaxLocks; - type MaxReserves = MaxReserves; + type MaxLocks = ConstU32<50>; + type MaxReserves = ConstU32<2>; type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } @@ -161,9 +161,9 @@ fn emit_events_with_no_existential_deposit_suicide_with_dust() { assert_eq!( events(), [ - Event::System(system::Event::NewAccount(1)), - Event::Balances(crate::Event::Endowed(1, 100)), - Event::Balances(crate::Event::BalanceSet(1, 100, 0)), + RuntimeEvent::System(system::Event::NewAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), + RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100, reserved: 0 }), ] ); @@ -171,7 +171,10 @@ fn emit_events_with_no_existential_deposit_suicide_with_dust() { assert_eq!(res, (NegativeImbalance::new(98), 0)); // no events - assert_eq!(events(), []); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 98 })] + ); let res = Balances::slash(&1, 1); assert_eq!(res, (NegativeImbalance::new(1), 0)); @@ -179,8 +182,9 @@ fn emit_events_with_no_existential_deposit_suicide_with_dust() { assert_eq!( events(), [ - Event::System(system::Event::KilledAccount(1)), - Event::Balances(crate::Event::DustLost(1, 1)), + RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 1 }), + RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1 }) ] ); }); diff --git a/frame/balances/src/tests_reentrancy.rs b/frame/balances/src/tests_reentrancy.rs index 9c7ba3e1ec824..90363140000e8 100644 --- a/frame/balances/src/tests_reentrancy.rs +++ b/frame/balances/src/tests_reentrancy.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +19,11 @@ #![cfg(test)] -use crate::{self as pallet_balances, Config, Pallet}; -use frame_support::{parameter_types, traits::StorageMapShim, weights::IdentityFee}; -use pallet_transaction_payment::CurrencyAdapter; +use crate::{self as pallet_balances, Config}; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64, StorageMapShim}, +}; use sp_core::H256; use sp_io; use sp_runtime::{testing::Header, traits::IdentityLookup}; @@ -48,9 +50,10 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max( + frame_support::weights::Weight::from_ref_time(1024).set_proof_size(u64::MAX), + ); pub static ExistentialDeposit: u64 = 0; } impl frame_system::Config for Test { @@ -58,17 +61,17 @@ impl frame_system::Config for Test { type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -77,15 +80,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); -} -parameter_types! { - pub const TransactionByteFee: u64 = 1; -} -impl pallet_transaction_payment::Config for Test { - type OnChargeTransaction = CurrencyAdapter, ()>; - type TransactionByteFee = TransactionByteFee; - type WeightToFee = IdentityFee; - type FeeMultiplierUpdate = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } pub struct OnDustRemoval; @@ -94,19 +89,16 @@ impl OnUnbalanced> for OnDustRemoval { assert_ok!(Balances::resolve_into_existing(&1, amount)); } } -parameter_types! { - pub const MaxLocks: u32 = 50; - pub const MaxReserves: u32 = 2; -} + impl Config for Test { type Balance = u64; type DustRemoval = OnDustRemoval; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; type AccountStore = StorageMapShim, system::Provider, u64, super::AccountData>; - type MaxLocks = MaxLocks; - type MaxReserves = MaxReserves; + type MaxLocks = ConstU32<50>; + type MaxReserves = ConstU32<2>; type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } @@ -165,11 +157,21 @@ fn transfer_dust_removal_tst1_should_work() { assert_eq!(Balances::free_balance(&1), 1050); // Verify the events - // Number of events expected is 8 - assert_eq!(System::events().len(), 11); - - System::assert_has_event(Event::Balances(crate::Event::Transfer(2, 3, 450))); - System::assert_has_event(Event::Balances(crate::Event::DustLost(2, 50))); + assert_eq!(System::events().len(), 12); + + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { + from: 2, + to: 3, + amount: 450, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { + account: 2, + amount: 50, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 50, + })); }); } @@ -193,11 +195,21 @@ fn transfer_dust_removal_tst2_should_work() { assert_eq!(Balances::free_balance(&1), 1500); // Verify the events - // Number of events expected is 8 - assert_eq!(System::events().len(), 9); + assert_eq!(System::events().len(), 10); - System::assert_has_event(Event::Balances(crate::Event::Transfer(2, 1, 450))); - System::assert_has_event(Event::Balances(crate::Event::DustLost(2, 50))); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { + from: 2, + to: 1, + amount: 450, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { + account: 2, + amount: 50, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 50, + })); }); } @@ -230,16 +242,23 @@ fn repatriating_reserved_balance_dust_removal_should_work() { assert_eq!(Balances::free_balance(1), 1500); // Verify the events - // Number of events expected is 10 - assert_eq!(System::events().len(), 10); - - System::assert_has_event(Event::Balances(crate::Event::ReserveRepatriated( - 2, - 1, - 450, - Status::Free, - ))); + assert_eq!(System::events().len(), 11); - System::assert_last_event(Event::Balances(crate::Event::DustLost(2, 50))); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::ReserveRepatriated { + from: 2, + to: 1, + amount: 450, + destination_status: Status::Free, + })); + + System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { + account: 2, + amount: 50, + })); + + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 50, + })); }); } diff --git a/frame/balances/src/weights.rs b/frame/balances/src/weights.rs index 6f333bfc0500f..6324745fd4310 100644 --- a/frame/balances/src/weights.rs +++ b/frame/balances/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_balances //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-09-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/balances/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -59,45 +61,52 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (70_952_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 48_134 nanoseconds. + Weight::from_ref_time(48_811_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (54_410_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 36_586 nanoseconds. + Weight::from_ref_time(36_966_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: System Account (r:1 w:1) fn set_balance_creating() -> Weight { - (29_176_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 28_486 nanoseconds. + Weight::from_ref_time(28_940_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: System Account (r:1 w:1) fn set_balance_killing() -> Weight { - (35_214_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 31_225 nanoseconds. + Weight::from_ref_time(31_946_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: System Account (r:2 w:2) fn force_transfer() -> Weight { - (71_780_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 47_347 nanoseconds. + Weight::from_ref_time(48_005_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: System Account (r:1 w:1) fn transfer_all() -> Weight { - (66_475_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 41_668 nanoseconds. + Weight::from_ref_time(42_232_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: System Account (r:1 w:1) fn force_unreserve() -> Weight { - (27_766_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 23_741 nanoseconds. + Weight::from_ref_time(24_073_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } } @@ -105,44 +114,51 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (70_952_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 48_134 nanoseconds. + Weight::from_ref_time(48_811_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (54_410_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 36_586 nanoseconds. + Weight::from_ref_time(36_966_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: System Account (r:1 w:1) fn set_balance_creating() -> Weight { - (29_176_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 28_486 nanoseconds. + Weight::from_ref_time(28_940_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: System Account (r:1 w:1) fn set_balance_killing() -> Weight { - (35_214_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 31_225 nanoseconds. + Weight::from_ref_time(31_946_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: System Account (r:2 w:2) fn force_transfer() -> Weight { - (71_780_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 47_347 nanoseconds. + Weight::from_ref_time(48_005_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: System Account (r:1 w:1) fn transfer_all() -> Weight { - (66_475_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 41_668 nanoseconds. + Weight::from_ref_time(42_232_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: System Account (r:1 w:1) fn force_unreserve() -> Weight { - (27_766_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 23_741 nanoseconds. + Weight::from_ref_time(24_073_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } } diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml new file mode 100644 index 0000000000000..f65270acdc3f8 --- /dev/null +++ b/frame/beefy-mmr/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "pallet-beefy-mmr" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +description = "BEEFY + MMR runtime utilities" +repository = "https://github.com/paritytech/substrate" +homepage = "https://substrate.io" + +[dependencies] +array-bytes = { version = "4.1", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } +beefy-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "./primitives" } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-beefy = { version = "4.0.0-dev", default-features = false, path = "../beefy" } +pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../merkle-mountain-range" } +pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +array-bytes = "4.1" +sp-staking = { version = "4.0.0-dev", path = "../../primitives/staking" } + +[features] +default = ["std"] +std = [ + "array-bytes", + "beefy-merkle-tree/std", + "beefy-primitives/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-beefy/std", + "pallet-mmr/std", + "pallet-session/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/beefy-mmr/primitives/Cargo.toml b/frame/beefy-mmr/primitives/Cargo.toml new file mode 100644 index 0000000000000..c087bc2f37cd3 --- /dev/null +++ b/frame/beefy-mmr/primitives/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "beefy-merkle-tree" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/paritytech/substrate" +description = "A no-std/Substrate compatible library to construct binary merkle tree." +homepage = "https://substrate.io" + +[dependencies] +array-bytes = { version = "4.1", optional = true } +log = { version = "0.4", default-features = false, optional = true } + +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/beefy", package = "sp-beefy" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } + +[dev-dependencies] +array-bytes = "4.1" +env_logger = "0.9" + +[features] +debug = ["array-bytes", "log"] +default = ["debug", "std"] +std = [ + "beefy-primitives/std", + "sp-api/std", + "sp-runtime/std" +] diff --git a/frame/beefy-mmr/primitives/src/lib.rs b/frame/beefy-mmr/primitives/src/lib.rs new file mode 100644 index 0000000000000..f88fb89acaaab --- /dev/null +++ b/frame/beefy-mmr/primitives/src/lib.rs @@ -0,0 +1,826 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] + +//! This crate implements a simple binary Merkle Tree utilities required for inter-op with Ethereum +//! bridge & Solidity contract. +//! +//! The implementation is optimised for usage within Substrate Runtime and supports no-std +//! compilation targets. +//! +//! Merkle Tree is constructed from arbitrary-length leaves, that are initially hashed using the +//! same hasher as the inner nodes. +//! Inner nodes are created by concatenating child hashes and hashing again. The implementation +//! does not perform any sorting of the input data (leaves) nor when inner nodes are created. +//! +//! If the number of leaves is not even, last leaf (hash of) is promoted to the upper layer. + +pub use sp_runtime::traits::Keccak256; +use sp_runtime::{app_crypto::sp_core, sp_std, traits::Hash as HashT}; +use sp_std::{vec, vec::Vec}; + +use beefy_primitives::mmr::{BeefyAuthoritySet, BeefyNextAuthoritySet}; + +/// Construct a root hash of a Binary Merkle Tree created from given leaves. +/// +/// See crate-level docs for details about Merkle Tree construction. +/// +/// In case an empty list of leaves is passed the function returns a 0-filled hash. +pub fn merkle_root(leaves: I) -> H::Output +where + H: HashT, + H::Output: Default + AsRef<[u8]>, + I: IntoIterator, + I::Item: AsRef<[u8]>, +{ + let iter = leaves.into_iter().map(|l| ::hash(l.as_ref())); + merkelize::(iter, &mut ()).into() +} + +fn merkelize(leaves: I, visitor: &mut V) -> H::Output +where + H: HashT, + H::Output: Default + AsRef<[u8]>, + V: Visitor, + I: Iterator, +{ + let upper = Vec::with_capacity((leaves.size_hint().1.unwrap_or(0).saturating_add(1)) / 2); + let mut next = match merkelize_row::(leaves, upper, visitor) { + Ok(root) => return root, + Err(next) if next.is_empty() => return H::Output::default(), + Err(next) => next, + }; + + let mut upper = Vec::with_capacity((next.len().saturating_add(1)) / 2); + loop { + visitor.move_up(); + + match merkelize_row::(next.drain(..), upper, visitor) { + Ok(root) => return root, + Err(t) => { + // swap collections to avoid allocations + upper = next; + next = t; + }, + }; + } +} + +/// A generated merkle proof. +/// +/// The structure contains all necessary data to later on verify the proof and the leaf itself. +#[derive(Debug, PartialEq, Eq)] +pub struct MerkleProof { + /// Root hash of generated merkle tree. + pub root: H, + /// Proof items (does not contain the leaf hash, nor the root obviously). + /// + /// This vec contains all inner node hashes necessary to reconstruct the root hash given the + /// leaf hash. + pub proof: Vec, + /// Number of leaves in the original tree. + /// + /// This is needed to detect a case where we have an odd number of leaves that "get promoted" + /// to upper layers. + pub number_of_leaves: usize, + /// Index of the leaf the proof is for (0-based). + pub leaf_index: usize, + /// Leaf content. + pub leaf: L, +} + +/// A trait of object inspecting merkle root creation. +/// +/// It can be passed to [`merkelize_row`] or [`merkelize`] functions and will be notified +/// about tree traversal. +trait Visitor { + /// We are moving one level up in the tree. + fn move_up(&mut self); + + /// We are creating an inner node from given `left` and `right` nodes. + /// + /// Note that in case of last odd node in the row `right` might be empty. + /// The method will also visit the `root` hash (level 0). + /// + /// The `index` is an index of `left` item. + fn visit(&mut self, index: usize, left: &Option, right: &Option); +} + +/// No-op implementation of the visitor. +impl Visitor for () { + fn move_up(&mut self) {} + fn visit(&mut self, _index: usize, _left: &Option, _right: &Option) {} +} + +/// Construct a Merkle Proof for leaves given by indices. +/// +/// The function constructs a (partial) Merkle Tree first and stores all elements required +/// to prove requested item (leaf) given the root hash. +/// +/// Both the Proof and the Root Hash is returned. +/// +/// # Panic +/// +/// The function will panic if given `leaf_index` is greater than the number of leaves. +pub fn merkle_proof(leaves: I, leaf_index: usize) -> MerkleProof +where + H: HashT, + H::Output: Default + Copy + AsRef<[u8]>, + I: IntoIterator, + I::IntoIter: ExactSizeIterator, + T: AsRef<[u8]>, +{ + let mut leaf = None; + let iter = leaves.into_iter().enumerate().map(|(idx, l)| { + let hash = ::hash(l.as_ref()); + if idx == leaf_index { + leaf = Some(l); + } + hash + }); + + /// The struct collects a proof for single leaf. + struct ProofCollection { + proof: Vec, + position: usize, + } + + impl ProofCollection { + fn new(position: usize) -> Self { + ProofCollection { proof: Default::default(), position } + } + } + + impl Visitor for ProofCollection { + fn move_up(&mut self) { + self.position /= 2; + } + + fn visit(&mut self, index: usize, left: &Option, right: &Option) { + // we are at left branch - right goes to the proof. + if self.position == index { + if let Some(right) = right { + self.proof.push(*right); + } + } + // we are at right branch - left goes to the proof. + if self.position == index + 1 { + if let Some(left) = left { + self.proof.push(*left); + } + } + } + } + + let number_of_leaves = iter.len(); + let mut collect_proof = ProofCollection::new(leaf_index); + + let root = merkelize::(iter, &mut collect_proof); + let leaf = leaf.expect("Requested `leaf_index` is greater than number of leaves."); + + #[cfg(feature = "debug")] + log::debug!( + "[merkle_proof] Proof: {:?}", + collect_proof + .proof + .iter() + .map(|s| array_bytes::bytes2hex("", s.as_ref())) + .collect::>() + ); + + MerkleProof { root, proof: collect_proof.proof, number_of_leaves, leaf_index, leaf } +} + +/// Leaf node for proof verification. +/// +/// Can be either a value that needs to be hashed first, +/// or the hash itself. +#[derive(Debug, PartialEq, Eq)] +pub enum Leaf<'a, H> { + /// Leaf content. + Value(&'a [u8]), + /// Hash of the leaf content. + Hash(H), +} + +impl<'a, H, T: AsRef<[u8]>> From<&'a T> for Leaf<'a, H> { + fn from(v: &'a T) -> Self { + Leaf::Value(v.as_ref()) + } +} + +/// Verify Merkle Proof correctness versus given root hash. +/// +/// The proof is NOT expected to contain leaf hash as the first +/// element, but only all adjacent nodes required to eventually by process of +/// concatenating and hashing end up with given root hash. +/// +/// The proof must not contain the root hash. +pub fn verify_proof<'a, H, P, L>( + root: &'a H::Output, + proof: P, + number_of_leaves: usize, + leaf_index: usize, + leaf: L, +) -> bool +where + H: HashT, + H::Output: PartialEq + AsRef<[u8]>, + P: IntoIterator, + L: Into>, +{ + if leaf_index >= number_of_leaves { + return false + } + + let leaf_hash = match leaf.into() { + Leaf::Value(content) => ::hash(content), + Leaf::Hash(hash) => hash, + }; + + let hash_len = ::LENGTH; + let mut combined = vec![0_u8; hash_len * 2]; + let mut position = leaf_index; + let mut width = number_of_leaves; + let computed = proof.into_iter().fold(leaf_hash, |a, b| { + if position % 2 == 1 || position + 1 == width { + combined[..hash_len].copy_from_slice(&b.as_ref()); + combined[hash_len..].copy_from_slice(&a.as_ref()); + } else { + combined[..hash_len].copy_from_slice(&a.as_ref()); + combined[hash_len..].copy_from_slice(&b.as_ref()); + } + let hash = ::hash(&combined); + #[cfg(feature = "debug")] + log::debug!( + "[verify_proof]: (a, b) {:?}, {:?} => {:?} ({:?}) hash", + array_bytes::bytes2hex("", &a.as_ref()), + array_bytes::bytes2hex("", &b.as_ref()), + array_bytes::bytes2hex("", &hash.as_ref()), + array_bytes::bytes2hex("", &combined.as_ref()) + ); + position /= 2; + width = ((width - 1) / 2) + 1; + hash + }); + + root == &computed +} + +/// Processes a single row (layer) of a tree by taking pairs of elements, +/// concatenating them, hashing and placing into resulting vector. +/// +/// In case only one element is provided it is returned via `Ok` result, in any other case (also an +/// empty iterator) an `Err` with the inner nodes of upper layer is returned. +fn merkelize_row( + mut iter: I, + mut next: Vec, + visitor: &mut V, +) -> Result> +where + H: HashT, + H::Output: AsRef<[u8]>, + V: Visitor, + I: Iterator, +{ + #[cfg(feature = "debug")] + log::debug!("[merkelize_row]"); + next.clear(); + + let hash_len = ::LENGTH; + let mut index = 0; + let mut combined = vec![0_u8; hash_len * 2]; + loop { + let a = iter.next(); + let b = iter.next(); + visitor.visit(index, &a, &b); + + #[cfg(feature = "debug")] + log::debug!( + " {:?}\n {:?}", + a.as_ref().map(|s| array_bytes::bytes2hex("", s.as_ref())), + b.as_ref().map(|s| array_bytes::bytes2hex("", s.as_ref())) + ); + + index += 2; + match (a, b) { + (Some(a), Some(b)) => { + combined[..hash_len].copy_from_slice(a.as_ref()); + combined[hash_len..].copy_from_slice(b.as_ref()); + + next.push(::hash(&combined)); + }, + // Odd number of items. Promote the item to the upper layer. + (Some(a), None) if !next.is_empty() => { + next.push(a); + }, + // Last item = root. + (Some(a), None) => return Ok(a), + // Finish up, no more items. + _ => { + #[cfg(feature = "debug")] + log::debug!( + "[merkelize_row] Next: {:?}", + next.iter().map(|s| array_bytes::bytes2hex("", s.as_ref())).collect::>() + ); + return Err(next) + }, + } + } +} + +sp_api::decl_runtime_apis! { + /// API useful for BEEFY light clients. + pub trait BeefyMmrApi + where + BeefyAuthoritySet: sp_api::Decode, + { + /// Return the currently active BEEFY authority set proof. + fn authority_set_proof() -> BeefyAuthoritySet; + + /// Return the next/queued BEEFY authority set proof. + fn next_authority_set_proof() -> BeefyNextAuthoritySet; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sp_core::H256; + + #[test] + fn should_generate_empty_root() { + // given + let _ = env_logger::try_init(); + let data: Vec<[u8; 1]> = Default::default(); + + // when + let out = merkle_root::(data); + + // then + assert_eq!( + array_bytes::bytes2hex("", out.as_ref()), + "0000000000000000000000000000000000000000000000000000000000000000" + ); + } + + #[test] + fn should_generate_single_root() { + // given + let _ = env_logger::try_init(); + let data = vec![array_bytes::hex2array_unchecked::<20>( + "E04CC55ebEE1cBCE552f250e85c57B70B2E2625b", + )]; + + // when + let out = merkle_root::(data); + + // then + assert_eq!( + array_bytes::bytes2hex("", out.as_ref()), + "aeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7" + ); + } + + #[test] + fn should_generate_root_pow_2() { + // given + let _ = env_logger::try_init(); + let data = vec![ + array_bytes::hex2array_unchecked::<20>("E04CC55ebEE1cBCE552f250e85c57B70B2E2625b"), + array_bytes::hex2array_unchecked::<20>("25451A4de12dcCc2D166922fA938E900fCc4ED24"), + ]; + + // when + let out = merkle_root::(data); + + // then + assert_eq!( + array_bytes::bytes2hex("", out.as_ref()), + "697ea2a8fe5b03468548a7a413424a6292ab44a82a6f5cc594c3fa7dda7ce402" + ); + } + + #[test] + fn should_generate_root_complex() { + let _ = env_logger::try_init(); + let test = |root, data| { + assert_eq!( + array_bytes::bytes2hex("", &merkle_root::(data).as_ref()), + root + ); + }; + + test( + "aff1208e69c9e8be9b584b07ebac4e48a1ee9d15ce3afe20b77a4d29e4175aa3", + vec!["a", "b", "c"], + ); + + test( + "b8912f7269068901f231a965adfefbc10f0eedcfa61852b103efd54dac7db3d7", + vec!["a", "b", "a"], + ); + + test( + "dc8e73fe6903148ff5079baecc043983625c23b39f31537e322cd0deee09fa9c", + vec!["a", "b", "a", "b"], + ); + + test( + "fb3b3be94be9e983ba5e094c9c51a7d96a4fa2e5d8e891df00ca89ba05bb1239", + vec!["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"], + ); + } + + #[test] + fn should_generate_and_verify_proof_simple() { + // given + let _ = env_logger::try_init(); + let data = vec!["a", "b", "c"]; + + // when + let proof0 = merkle_proof::(data.clone(), 0); + assert!(verify_proof::( + &proof0.root, + proof0.proof.clone(), + data.len(), + proof0.leaf_index, + &proof0.leaf, + )); + + let proof1 = merkle_proof::(data.clone(), 1); + assert!(verify_proof::( + &proof1.root, + proof1.proof, + data.len(), + proof1.leaf_index, + &proof1.leaf, + )); + + let proof2 = merkle_proof::(data.clone(), 2); + assert!(verify_proof::( + &proof2.root, + proof2.proof, + data.len(), + proof2.leaf_index, + &proof2.leaf + )); + + // then + assert_eq!( + array_bytes::bytes2hex("", &proof0.root.as_ref()), + array_bytes::bytes2hex("", &proof1.root.as_ref()) + ); + assert_eq!( + array_bytes::bytes2hex("", &proof2.root.as_ref()), + array_bytes::bytes2hex("", &proof1.root.as_ref()) + ); + + assert!(!verify_proof::( + &array_bytes::hex2array_unchecked( + "fb3b3be94be9e983ba5e094c9c51a7d96a4fa2e5d8e891df00ca89ba05bb1239" + ) + .into(), + proof0.proof, + data.len(), + proof0.leaf_index, + &proof0.leaf + )); + + assert!(!verify_proof::( + &proof0.root.into(), + vec![], + data.len(), + proof0.leaf_index, + &proof0.leaf + )); + } + + #[test] + fn should_generate_and_verify_proof_complex() { + // given + let _ = env_logger::try_init(); + let data = vec!["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]; + + for l in 0..data.len() { + // when + let proof = merkle_proof::(data.clone(), l); + // then + assert!(verify_proof::( + &proof.root, + proof.proof, + data.len(), + proof.leaf_index, + &proof.leaf + )); + } + } + + #[test] + fn should_generate_and_verify_proof_large() { + // given + let _ = env_logger::try_init(); + let mut data = vec![]; + for i in 1..16 { + for c in 'a'..'z' { + if c as usize % i != 0 { + data.push(c.to_string()); + } + } + + for l in 0..data.len() { + // when + let proof = merkle_proof::(data.clone(), l); + // then + assert!(verify_proof::( + &proof.root, + proof.proof, + data.len(), + proof.leaf_index, + &proof.leaf + )); + } + } + } + + #[test] + fn should_generate_and_verify_proof_large_tree() { + // given + let _ = env_logger::try_init(); + let mut data = vec![]; + for i in 0..6000 { + data.push(format!("{}", i)); + } + + for l in (0..data.len()).step_by(13) { + // when + let proof = merkle_proof::(data.clone(), l); + // then + assert!(verify_proof::( + &proof.root, + proof.proof, + data.len(), + proof.leaf_index, + &proof.leaf + )); + } + } + + #[test] + #[should_panic] + fn should_panic_on_invalid_leaf_index() { + let _ = env_logger::try_init(); + merkle_proof::(vec!["a"], 5); + } + + #[test] + fn should_generate_and_verify_proof_on_test_data() { + let addresses = vec![ + "0x9aF1Ca5941148eB6A3e9b9C741b69738292C533f", + "0xDD6ca953fddA25c496165D9040F7F77f75B75002", + "0x60e9C47B64Bc1C7C906E891255EaEC19123E7F42", + "0xfa4859480Aa6D899858DE54334d2911E01C070df", + "0x19B9b128470584F7209eEf65B69F3624549Abe6d", + "0xC436aC1f261802C4494504A11fc2926C726cB83b", + "0xc304C8C2c12522F78aD1E28dD86b9947D7744bd0", + "0xDa0C2Cba6e832E55dE89cF4033affc90CC147352", + "0xf850Fd22c96e3501Aad4CDCBf38E4AEC95622411", + "0x684918D4387CEb5E7eda969042f036E226E50642", + "0x963F0A1bFbb6813C0AC88FcDe6ceB96EA634A595", + "0x39B38ad74b8bCc5CE564f7a27Ac19037A95B6099", + "0xC2Dec7Fdd1fef3ee95aD88EC8F3Cd5bd4065f3C7", + "0x9E311f05c2b6A43C2CCF16fB2209491BaBc2ec01", + "0x927607C30eCE4Ef274e250d0bf414d4a210b16f0", + "0x98882bcf85E1E2DFF780D0eB360678C1cf443266", + "0xFBb50191cd0662049E7C4EE32830a4Cc9B353047", + "0x963854fc2C358c48C3F9F0A598B9572c581B8DEF", + "0xF9D7Bc222cF6e3e07bF66711e6f409E51aB75292", + "0xF2E3fd32D063F8bBAcB9e6Ea8101C2edd899AFe6", + "0x407a5b9047B76E8668570120A96d580589fd1325", + "0xEAD9726FAFB900A07dAd24a43AE941d2eFDD6E97", + "0x42f5C8D9384034A9030313B51125C32a526b6ee8", + "0x158fD2529Bc4116570Eb7C80CC76FEf33ad5eD95", + "0x0A436EE2E4dEF3383Cf4546d4278326Ccc82514E", + "0x34229A215db8FeaC93Caf8B5B255e3c6eA51d855", + "0xEb3B7CF8B1840242CB98A732BA464a17D00b5dDF", + "0x2079692bf9ab2d6dc7D79BBDdEE71611E9aA3B72", + "0x46e2A67e5d450e2Cf7317779f8274a2a630f3C9B", + "0xA7Ece4A5390DAB18D08201aE18800375caD78aab", + "0x15E1c0D24D62057Bf082Cb2253dA11Ef0d469570", + "0xADDEF4C9b5687Eb1F7E55F2251916200A3598878", + "0xe0B16Fb96F936035db2b5A68EB37D470fED2f013", + "0x0c9A84993feaa779ae21E39F9793d09e6b69B62D", + "0x3bc4D5148906F70F0A7D1e2756572655fd8b7B34", + "0xFf4675C26903D5319795cbd3a44b109E7DDD9fDe", + "0xCec4450569A8945C6D2Aba0045e4339030128a92", + "0x85f0584B10950E421A32F471635b424063FD8405", + "0xb38bEe7Bdc0bC43c096e206EFdFEad63869929E3", + "0xc9609466274Fef19D0e58E1Ee3b321D5C141067E", + "0xa08EA868cF75268E7401021E9f945BAe73872ecc", + "0x67C9Cb1A29E964Fe87Ff669735cf7eb87f6868fE", + "0x1B6BEF636aFcdd6085cD4455BbcC93796A12F6E2", + "0x46B37b243E09540b55cF91C333188e7D5FD786dD", + "0x8E719E272f62Fa97da93CF9C941F5e53AA09e44a", + "0xa511B7E7DB9cb24AD5c89fBb6032C7a9c2EfA0a5", + "0x4D11FDcAeD335d839132AD450B02af974A3A66f8", + "0xB8cf790a5090E709B4619E1F335317114294E17E", + "0x7f0f57eA064A83210Cafd3a536866ffD2C5eDCB3", + "0xC03C848A4521356EF800e399D889e9c2A25D1f9E", + "0xC6b03DF05cb686D933DD31fCa5A993bF823dc4FE", + "0x58611696b6a8102cf95A32c25612E4cEF32b910F", + "0x2ed4bC7197AEF13560F6771D930Bf907772DE3CE", + "0x3C5E58f334306be029B0e47e119b8977B2639eb4", + "0x288646a1a4FeeC560B349d210263c609aDF649a6", + "0xb4F4981E0d027Dc2B3c86afA0D0fC03d317e83C0", + "0xaAE4A87F8058feDA3971f9DEd639Ec9189aA2500", + "0x355069DA35E598913d8736E5B8340527099960b8", + "0x3cf5A0F274cd243C0A186d9fCBdADad089821B93", + "0xca55155dCc4591538A8A0ca322a56EB0E4aD03C4", + "0xE824D0268366ec5C4F23652b8eD70D552B1F2b8B", + "0x84C3e9B25AE8a9b39FF5E331F9A597F2DCf27Ca9", + "0xcA0018e278751De10d26539915d9c7E7503432FE", + "0xf13077dE6191D6c1509ac7E088b8BE7Fe656c28b", + "0x7a6bcA1ec9Db506e47ac6FD86D001c2aBc59C531", + "0xeA7f9A2A9dd6Ba9bc93ca615C3Ddf26973146911", + "0x8D0d8577e16F8731d4F8712BAbFa97aF4c453458", + "0xB7a7855629dF104246997e9ACa0E6510df75d0ea", + "0x5C1009BDC70b0C8Ab2e5a53931672ab448C17c89", + "0x40B47D1AfefEF5eF41e0789F0285DE7b1C31631C", + "0x5086933d549cEcEB20652CE00973703CF10Da373", + "0xeb364f6FE356882F92ae9314fa96116Cf65F47d8", + "0xdC4D31516A416cEf533C01a92D9a04bbdb85EE67", + "0x9b36E086E5A274332AFd3D8509e12ca5F6af918d", + "0xBC26394fF36e1673aE0608ce91A53B9768aD0D76", + "0x81B5AB400be9e563fA476c100BE898C09966426c", + "0x9d93C8ae5793054D28278A5DE6d4653EC79e90FE", + "0x3B8E75804F71e121008991E3177fc942b6c28F50", + "0xC6Eb5886eB43dD473f5BB4e21e56E08dA464D9B4", + "0xfdf1277b71A73c813cD0e1a94B800f4B1Db66DBE", + "0xc2ff2cCc98971556670e287Ff0CC39DA795231ad", + "0x76b7E1473f0D0A87E9B4a14E2B179266802740f5", + "0xA7Bc965660a6EF4687CCa4F69A97563163A3C2Ef", + "0xB9C2b47888B9F8f7D03dC1de83F3F55E738CebD3", + "0xEd400162E6Dd6bD2271728FFb04176bF770De94a", + "0xE3E8331156700339142189B6E555DCb2c0962750", + "0xbf62e342Bc7706a448EdD52AE871d9C4497A53b1", + "0xb9d7A1A111eed75714a0AcD2dd467E872eE6B03D", + "0x03942919DFD0383b8c574AB8A701d89fd4bfA69D", + "0x0Ef4C92355D3c8c7050DFeb319790EFCcBE6fe9e", + "0xA6895a3cf0C60212a73B3891948ACEcF1753f25E", + "0x0Ed509239DB59ef3503ded3d31013C983d52803A", + "0xc4CE8abD123BfAFc4deFf37c7D11DeCd5c350EE4", + "0x4A4Bf59f7038eDcd8597004f35d7Ee24a7Bdd2d3", + "0x5769E8e8A2656b5ed6b6e6fa2a2bFAeaf970BB87", + "0xf9E15cCE181332F4F57386687c1776b66C377060", + "0xc98f8d4843D56a46C21171900d3eE538Cc74dbb5", + "0x3605965B47544Ce4302b988788B8195601AE4dEd", + "0xe993BDfdcAac2e65018efeE0F69A12678031c71d", + "0x274fDf8801385D3FAc954BCc1446Af45f5a8304c", + "0xBFb3f476fcD6429F4a475bA23cEFdDdd85c6b964", + "0x806cD16588Fe812ae740e931f95A289aFb4a4B50", + "0xa89488CE3bD9C25C3aF797D1bbE6CA689De79d81", + "0xd412f1AfAcf0Ebf3Cd324593A231Fc74CC488B12", + "0xd1f715b2D7951d54bc31210BbD41852D9BF98Ed1", + "0xf65aD707c344171F467b2ADba3d14f312219cE23", + "0x2971a4b242e9566dEF7bcdB7347f5E484E11919B", + "0x12b113D6827E07E7D426649fBd605f427da52314", + "0x1c6CA45171CDb9856A6C9Dba9c5F1216913C1e97", + "0x11cC6ee1d74963Db23294FCE1E3e0A0555779CeA", + "0x8Aa1C721255CDC8F895E4E4c782D86726b068667", + "0xA2cDC1f37510814485129aC6310b22dF04e9Bbf0", + "0xCf531b71d388EB3f5889F1f78E0d77f6fb109767", + "0xBe703e3545B2510979A0cb0C440C0Fba55c6dCB5", + "0x30a35886F989db39c797D8C93880180Fdd71b0c8", + "0x1071370D981F60c47A9Cd27ac0A61873a372cBB2", + "0x3515d74A11e0Cb65F0F46cB70ecf91dD1712daaa", + "0x50500a3c2b7b1229c6884505D00ac6Be29Aecd0C", + "0x9A223c2a11D4FD3585103B21B161a2B771aDA3d1", + "0xd7218df03AD0907e6c08E707B15d9BD14285e657", + "0x76CfD72eF5f93D1a44aD1F80856797fBE060c70a", + "0x44d093cB745944991EFF5cBa151AA6602d6f5420", + "0x626516DfF43bf09A71eb6fd1510E124F96ED0Cde", + "0x6530824632dfe099304E2DC5701cA99E6d031E08", + "0x57e6c423d6a7607160d6379A0c335025A14DaFC0", + "0x3966D4AD461Ef150E0B10163C81E79b9029E69c3", + "0xF608aCfd0C286E23721a3c347b2b65039f6690F1", + "0xbfB8FAac31A25646681936977837f7740fCd0072", + "0xd80aa634a623a7ED1F069a1a3A28a173061705c7", + "0x9122a77B36363e24e12E1E2D73F87b32926D3dF5", + "0x62562f0d1cD31315bCCf176049B6279B2bfc39C2", + "0x48aBF7A2a7119e5675059E27a7082ba7F38498b2", + "0xb4596983AB9A9166b29517acD634415807569e5F", + "0x52519D16E20BC8f5E96Da6d736963e85b2adA118", + "0x7663893C3dC0850EfC5391f5E5887eD723e51B83", + "0x5FF323a29bCC3B5b4B107e177EccEF4272959e61", + "0xee6e499AdDf4364D75c05D50d9344e9daA5A9AdF", + "0x1631b0BD31fF904aD67dD58994C6C2051CDe4E75", + "0xbc208e9723D44B9811C428f6A55722a26204eEF2", + "0xe76103a222Ee2C7Cf05B580858CEe625C4dc00E1", + "0xC71Bb2DBC51760f4fc2D46D84464410760971B8a", + "0xB4C18811e6BFe564D69E12c224FFc57351f7a7ff", + "0xD11DB0F5b41061A887cB7eE9c8711438844C298A", + "0xB931269934A3D4432c084bAAc3d0de8143199F4f", + "0x070037cc85C761946ec43ea2b8A2d5729908A2a1", + "0x2E34aa8C95Ffdbb37f14dCfBcA69291c55Ba48DE", + "0x052D93e8d9220787c31d6D83f87eC7dB088E998f", + "0x498dAC6C69b8b9ad645217050054840f1D91D029", + "0xE4F7D60f9d84301e1fFFd01385a585F3A11F8E89", + "0xEa637992f30eA06460732EDCBaCDa89355c2a107", + "0x4960d8Da07c27CB6Be48a79B96dD70657c57a6bF", + "0x7e471A003C8C9fdc8789Ded9C3dbe371d8aa0329", + "0xd24265Cc10eecb9e8d355CCc0dE4b11C556E74D7", + "0xDE59C8f7557Af779674f41CA2cA855d571018690", + "0x2fA8A6b3b6226d8efC9d8f6EBDc73Ca33DDcA4d8", + "0xe44102664c6c2024673Ff07DFe66E187Db77c65f", + "0x94E3f4f90a5f7CBF2cc2623e66B8583248F01022", + "0x0383EdBbc21D73DEd039E9C1Ff6bf56017b4CC40", + "0x64C3E49898B88d1E0f0d02DA23E0c00A2Cd0cA99", + "0xF4ccfB67b938d82B70bAb20975acFAe402E812E1", + "0x4f9ee5829e9852E32E7BC154D02c91D8E203e074", + "0xb006312eF9713463bB33D22De60444Ba95609f6B", + "0x7Cbe76ef69B52110DDb2e3b441C04dDb11D63248", + "0x70ADEEa65488F439392B869b1Df7241EF317e221", + "0x64C0bf8AA36Ba590477585Bc0D2BDa7970769463", + "0xA4cDc98593CE52d01Fe5Ca47CB3dA5320e0D7592", + "0xc26B34D375533fFc4c5276282Fa5D660F3d8cbcB", + ]; + let root: H256 = array_bytes::hex2array_unchecked( + "72b0acd7c302a84f1f6b6cefe0ba7194b7398afb440e1b44a9dbbe270394ca53", + ) + .into(); + + let data = addresses + .into_iter() + .map(|address| array_bytes::hex2bytes_unchecked(&address)) + .collect::>(); + + for l in 0..data.len() { + // when + let proof = merkle_proof::(data.clone(), l); + assert_eq!( + array_bytes::bytes2hex("", &proof.root.as_ref()), + array_bytes::bytes2hex("", &root.as_ref()) + ); + assert_eq!(proof.leaf_index, l); + assert_eq!(&proof.leaf, &data[l]); + + // then + assert!(verify_proof::( + &proof.root, + proof.proof, + data.len(), + proof.leaf_index, + &proof.leaf + )); + } + + let proof = merkle_proof::(data.clone(), data.len() - 1); + + assert_eq!( + proof, + MerkleProof { + root, + proof: vec![ + array_bytes::hex2array_unchecked( + "340bcb1d49b2d82802ddbcf5b85043edb3427b65d09d7f758fbc76932ad2da2f" + ) + .into(), + array_bytes::hex2array_unchecked( + "ba0580e5bd530bc93d61276df7969fb5b4ae8f1864b4a28c280249575198ff1f" + ) + .into(), + array_bytes::hex2array_unchecked( + "d02609d2bbdb28aa25f58b85afec937d5a4c85d37925bce6d0cf802f9d76ba79" + ) + .into(), + array_bytes::hex2array_unchecked( + "ae3f8991955ed884613b0a5f40295902eea0e0abe5858fc520b72959bc016d4e" + ) + .into(), + ], + number_of_leaves: data.len(), + leaf_index: data.len() - 1, + leaf: array_bytes::hex2array_unchecked::<20>( + "c26B34D375533fFc4c5276282Fa5D660F3d8cbcB" + ) + .to_vec(), + } + ); + } +} diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs new file mode 100644 index 0000000000000..0b7fc22cd279b --- /dev/null +++ b/frame/beefy-mmr/src/lib.rs @@ -0,0 +1,209 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] + +//! A BEEFY+MMR pallet combo. +//! +//! While both BEEFY and Merkle Mountain Range (MMR) can be used separately, +//! these tools were designed to work together in unison. +//! +//! The pallet provides a standardized MMR Leaf format that is can be used +//! to bridge BEEFY+MMR-based networks (both standalone and polkadot-like). +//! +//! The MMR leaf contains: +//! 1. Block number and parent block hash. +//! 2. Merkle Tree Root Hash of next BEEFY validator set. +//! 3. Arbitrary extra leaf data to be used by downstream pallets to include custom data. +//! +//! and thanks to versioning can be easily updated in the future. + +use sp_runtime::traits::{Convert, Member}; +use sp_std::prelude::*; + +use beefy_primitives::{ + mmr::{BeefyAuthoritySet, BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion}, + ValidatorSet as BeefyValidatorSet, +}; +use pallet_mmr::{LeafDataProvider, ParentNumberAndHash}; + +use frame_support::{crypto::ecdsa::ECDSAExt, traits::Get}; + +pub use pallet::*; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +/// A BEEFY consensus digest item with MMR root hash. +pub struct DepositBeefyDigest(sp_std::marker::PhantomData); + +impl pallet_mmr::primitives::OnNewRoot for DepositBeefyDigest +where + T: pallet_mmr::Config, + T: pallet_beefy::Config, +{ + fn on_new_root(root: &::Hash) { + let digest = sp_runtime::generic::DigestItem::Consensus( + beefy_primitives::BEEFY_ENGINE_ID, + codec::Encode::encode(&beefy_primitives::ConsensusLog::< + ::BeefyId, + >::MmrRoot(*root)), + ); + >::deposit_log(digest); + } +} + +/// Convert BEEFY secp256k1 public keys into Ethereum addresses +pub struct BeefyEcdsaToEthereum; +impl Convert> for BeefyEcdsaToEthereum { + fn convert(beefy_id: beefy_primitives::crypto::AuthorityId) -> Vec { + sp_core::ecdsa::Public::from(beefy_id) + .to_eth_address() + .map(|v| v.to_vec()) + .map_err(|_| { + log::error!(target: "runtime::beefy", "Failed to convert BEEFY PublicKey to ETH address!"); + }) + .unwrap_or_default() + } +} + +type MerkleRootOf = ::Hash; + +#[frame_support::pallet] +pub mod pallet { + #![allow(missing_docs)] + + use super::*; + use frame_support::pallet_prelude::*; + + /// BEEFY-MMR pallet. + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// The module's configuration trait. + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: pallet_mmr::Config + pallet_beefy::Config { + /// Current leaf version. + /// + /// Specifies the version number added to every leaf that get's appended to the MMR. + /// Read more in [`MmrLeafVersion`] docs about versioning leaves. + type LeafVersion: Get; + + /// Convert BEEFY AuthorityId to a form that would end up in the Merkle Tree. + /// + /// For instance for ECDSA (secp256k1) we want to store uncompressed public keys (65 bytes) + /// and later to Ethereum Addresses (160 bits) to simplify using them on Ethereum chain, + /// but the rest of the Substrate codebase is storing them compressed (33 bytes) for + /// efficiency reasons. + type BeefyAuthorityToMerkleLeaf: Convert<::BeefyId, Vec>; + + /// The type expected for the leaf extra data + type LeafExtra: Member + codec::FullCodec; + + /// Retrieve arbitrary data that should be added to the mmr leaf + type BeefyDataProvider: BeefyDataProvider; + } + + /// Details of current BEEFY authority set. + #[pallet::storage] + #[pallet::getter(fn beefy_authorities)] + pub type BeefyAuthorities = + StorageValue<_, BeefyAuthoritySet>, ValueQuery>; + + /// Details of next BEEFY authority set. + /// + /// This storage entry is used as cache for calls to `update_beefy_next_authority_set`. + #[pallet::storage] + #[pallet::getter(fn beefy_next_authorities)] + pub type BeefyNextAuthorities = + StorageValue<_, BeefyNextAuthoritySet>, ValueQuery>; +} + +impl LeafDataProvider for Pallet { + type LeafData = MmrLeaf< + ::BlockNumber, + ::Hash, + MerkleRootOf, + T::LeafExtra, + >; + + fn leaf_data() -> Self::LeafData { + MmrLeaf { + version: T::LeafVersion::get(), + parent_number_and_hash: ParentNumberAndHash::::leaf_data(), + leaf_extra: T::BeefyDataProvider::extra_data(), + beefy_next_authority_set: Pallet::::beefy_next_authorities(), + } + } +} + +impl beefy_primitives::OnNewValidatorSet<::BeefyId> for Pallet +where + T: pallet::Config, +{ + /// Compute and cache BEEFY authority sets based on updated BEEFY validator sets. + fn on_new_validator_set( + current_set: &BeefyValidatorSet<::BeefyId>, + next_set: &BeefyValidatorSet<::BeefyId>, + ) { + let current = Pallet::::compute_authority_set(current_set); + let next = Pallet::::compute_authority_set(next_set); + // cache the result + BeefyAuthorities::::put(¤t); + BeefyNextAuthorities::::put(&next); + } +} + +impl Pallet { + /// Return the currently active BEEFY authority set proof. + pub fn authority_set_proof() -> BeefyAuthoritySet> { + Pallet::::beefy_authorities() + } + + /// Return the next/queued BEEFY authority set proof. + pub fn next_authority_set_proof() -> BeefyNextAuthoritySet> { + Pallet::::beefy_next_authorities() + } + + /// Returns details of a BEEFY authority set. + /// + /// Details contain authority set id, authority set length and a merkle root, + /// constructed from uncompressed secp256k1 public keys converted to Ethereum addresses + /// of the next BEEFY authority set. + fn compute_authority_set( + validator_set: &BeefyValidatorSet<::BeefyId>, + ) -> BeefyAuthoritySet> { + let id = validator_set.id(); + let beefy_addresses = validator_set + .validators() + .into_iter() + .cloned() + .map(T::BeefyAuthorityToMerkleLeaf::convert) + .collect::>(); + let len = beefy_addresses.len() as u32; + let root = beefy_merkle_tree::merkle_root::<::Hashing, _>( + beefy_addresses, + ) + .into(); + BeefyAuthoritySet { id, len, root } + } +} diff --git a/frame/beefy-mmr/src/mock.rs b/frame/beefy-mmr/src/mock.rs new file mode 100644 index 0000000000000..0a64ad3fc9976 --- /dev/null +++ b/frame/beefy-mmr/src/mock.rs @@ -0,0 +1,213 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 std::vec; + +use beefy_primitives::mmr::MmrLeafVersion; +use codec::Encode; +use frame_support::{ + construct_runtime, parameter_types, + sp_io::TestExternalities, + traits::{ConstU16, ConstU32, ConstU64, GenesisBuild}, + BasicExternalities, +}; +use sp_core::{Hasher, H256}; +use sp_runtime::{ + app_crypto::ecdsa::Public, + impl_opaque_keys, + testing::Header, + traits::{BlakeTwo256, ConvertInto, IdentityLookup, Keccak256, OpaqueKeys}, +}; + +use crate as pallet_beefy_mmr; + +pub use beefy_primitives::{ + crypto::AuthorityId as BeefyId, mmr::BeefyDataProvider, ConsensusLog, BEEFY_ENGINE_ID, +}; + +impl_opaque_keys! { + pub struct MockSessionKeys { + pub dummy: pallet_beefy::Pallet, + } +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Session: pallet_session::{Pallet, Call, Storage, Event, Config}, + Mmr: pallet_mmr::{Pallet, Storage}, + Beefy: pallet_beefy::{Pallet, Config, Storage}, + BeefyMmr: pallet_beefy_mmr::{Pallet, Storage}, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_session::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = u64; + type ValidatorIdOf = ConvertInto; + type ShouldEndSession = pallet_session::PeriodicSessions, ConstU64<0>>; + type NextSessionRotation = pallet_session::PeriodicSessions, ConstU64<0>>; + type SessionManager = MockSessionManager; + type SessionHandler = ::KeyTypeIdProviders; + type Keys = MockSessionKeys; + type WeightInfo = (); +} + +pub type MmrLeaf = beefy_primitives::mmr::MmrLeaf< + ::BlockNumber, + ::Hash, + ::Hash, + Vec, +>; + +impl pallet_mmr::Config for Test { + const INDEXING_PREFIX: &'static [u8] = b"mmr"; + + type Hashing = Keccak256; + + type Hash = ::Out; + + type LeafData = BeefyMmr; + + type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest; + + type WeightInfo = (); +} + +impl pallet_beefy::Config for Test { + type BeefyId = BeefyId; + type MaxAuthorities = ConstU32<100>; + type OnNewValidatorSet = BeefyMmr; +} + +parameter_types! { + pub LeafVersion: MmrLeafVersion = MmrLeafVersion::new(1, 5); +} + +impl pallet_beefy_mmr::Config for Test { + type LeafVersion = LeafVersion; + + type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum; + + type LeafExtra = Vec; + + type BeefyDataProvider = DummyDataProvider; +} + +pub struct DummyDataProvider; +impl BeefyDataProvider> for DummyDataProvider { + fn extra_data() -> Vec { + let mut col = vec![(15, vec![1, 2, 3]), (5, vec![4, 5, 6])]; + col.sort(); + beefy_merkle_tree::merkle_root::<::Hashing, _>( + col.into_iter().map(|pair| pair.encode()), + ) + .as_ref() + .to_vec() + } +} + +pub struct MockSessionManager; +impl pallet_session::SessionManager for MockSessionManager { + fn end_session(_: sp_staking::SessionIndex) {} + fn start_session(_: sp_staking::SessionIndex) {} + fn new_session(idx: sp_staking::SessionIndex) -> Option> { + if idx == 0 || idx == 1 { + Some(vec![1, 2]) + } else if idx == 2 { + Some(vec![3, 4]) + } else { + None + } + } +} + +// Note, that we can't use `UintAuthorityId` here. Reason is that the implementation +// of `to_public_key()` assumes, that a public key is 32 bytes long. This is true for +// ed25519 and sr25519 but *not* for ecdsa. A compressed ecdsa public key is 33 bytes, +// with the first one containing information to reconstruct the uncompressed key. +pub fn mock_beefy_id(id: u8) -> BeefyId { + let mut buf: [u8; 33] = [id; 33]; + // Set to something valid. + buf[0] = 0x02; + let pk = Public::from_raw(buf); + BeefyId::from(pk) +} + +pub fn mock_authorities(vec: Vec) -> Vec<(u64, BeefyId)> { + vec.into_iter().map(|id| ((id as u64), mock_beefy_id(id))).collect() +} + +pub fn new_test_ext(ids: Vec) -> TestExternalities { + new_test_ext_raw_authorities(mock_authorities(ids)) +} + +pub fn new_test_ext_raw_authorities(authorities: Vec<(u64, BeefyId)>) -> TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let session_keys: Vec<_> = authorities + .iter() + .enumerate() + .map(|(_, id)| (id.0 as u64, id.0 as u64, MockSessionKeys { dummy: id.1.clone() })) + .collect(); + + BasicExternalities::execute_with_storage(&mut t, || { + for (ref id, ..) in &session_keys { + frame_system::Pallet::::inc_providers(id); + } + }); + + pallet_session::GenesisConfig:: { keys: session_keys } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() +} diff --git a/frame/beefy-mmr/src/tests.rs b/frame/beefy-mmr/src/tests.rs new file mode 100644 index 0000000000000..1826331f59e53 --- /dev/null +++ b/frame/beefy-mmr/src/tests.rs @@ -0,0 +1,209 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 std::vec; + +use beefy_primitives::{ + mmr::{BeefyNextAuthoritySet, MmrLeafVersion}, + ValidatorSet, +}; +use codec::{Decode, Encode}; + +use sp_core::H256; +use sp_io::TestExternalities; +use sp_runtime::{traits::Keccak256, DigestItem}; + +use frame_support::traits::OnInitialize; + +use crate::mock::*; + +fn init_block(block: u64) { + System::set_block_number(block); + Session::on_initialize(block); + Mmr::on_initialize(block); + Beefy::on_initialize(block); + BeefyMmr::on_initialize(block); +} + +pub fn beefy_log(log: ConsensusLog) -> DigestItem { + DigestItem::Consensus(BEEFY_ENGINE_ID, log.encode()) +} + +fn read_mmr_leaf(ext: &mut TestExternalities, key: Vec) -> MmrLeaf { + type Node = pallet_mmr::primitives::DataOrHash; + ext.persist_offchain_overlay(); + let offchain_db = ext.offchain_db(); + offchain_db + .get(&key) + .map(|d| Node::decode(&mut &*d).unwrap()) + .map(|n| match n { + Node::Data(d) => d, + _ => panic!("Unexpected MMR node."), + }) + .unwrap() +} + +#[test] +fn should_contain_mmr_digest() { + let mut ext = new_test_ext(vec![1, 2, 3, 4]); + ext.execute_with(|| { + init_block(1); + + assert_eq!( + System::digest().logs, + vec![ + beefy_log(ConsensusLog::AuthoritiesChange( + ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap() + )), + beefy_log(ConsensusLog::MmrRoot(array_bytes::hex_n_into_unchecked( + "95803defe6ea9f41e7ec6afa497064f21bfded027d8812efacbdf984e630cbdc" + ))) + ] + ); + + // unique every time + init_block(2); + + assert_eq!( + System::digest().logs, + vec![ + beefy_log(ConsensusLog::AuthoritiesChange( + ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap() + )), + beefy_log(ConsensusLog::MmrRoot(array_bytes::hex_n_into_unchecked( + "95803defe6ea9f41e7ec6afa497064f21bfded027d8812efacbdf984e630cbdc" + ))), + beefy_log(ConsensusLog::AuthoritiesChange( + ValidatorSet::new(vec![mock_beefy_id(3), mock_beefy_id(4)], 2).unwrap() + )), + beefy_log(ConsensusLog::MmrRoot(array_bytes::hex_n_into_unchecked( + "a73271a0974f1e67d6e9b8dd58e506177a2e556519a330796721e98279a753e2" + ))), + ] + ); + }); +} + +#[test] +fn should_contain_valid_leaf_data() { + fn node_offchain_key(pos: usize, parent_hash: H256) -> Vec { + (::INDEXING_PREFIX, pos as u64, parent_hash).encode() + } + + let mut ext = new_test_ext(vec![1, 2, 3, 4]); + let parent_hash = ext.execute_with(|| { + init_block(1); + >::parent_hash() + }); + + let mmr_leaf = read_mmr_leaf(&mut ext, node_offchain_key(0, parent_hash)); + assert_eq!( + mmr_leaf, + MmrLeaf { + version: MmrLeafVersion::new(1, 5), + parent_number_and_hash: (0_u64, H256::repeat_byte(0x45)), + beefy_next_authority_set: BeefyNextAuthoritySet { + id: 2, + len: 2, + root: array_bytes::hex_n_into_unchecked( + "9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5" + ) + }, + leaf_extra: array_bytes::hex2bytes_unchecked( + "55b8e9e1cc9f0db7776fac0ca66318ef8acfb8ec26db11e373120583e07ee648" + ) + } + ); + + // build second block on top + let parent_hash = ext.execute_with(|| { + init_block(2); + >::parent_hash() + }); + + let mmr_leaf = read_mmr_leaf(&mut ext, node_offchain_key(1, parent_hash)); + assert_eq!( + mmr_leaf, + MmrLeaf { + version: MmrLeafVersion::new(1, 5), + parent_number_and_hash: (1_u64, H256::repeat_byte(0x45)), + beefy_next_authority_set: BeefyNextAuthoritySet { + id: 3, + len: 2, + root: array_bytes::hex_n_into_unchecked( + "9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5" + ) + }, + leaf_extra: array_bytes::hex2bytes_unchecked( + "55b8e9e1cc9f0db7776fac0ca66318ef8acfb8ec26db11e373120583e07ee648" + ) + } + ); +} + +#[test] +fn should_update_authorities() { + new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { + let auth_set = BeefyMmr::authority_set_proof(); + let next_auth_set = BeefyMmr::next_authority_set_proof(); + + // check current authority set + assert_eq!(0, auth_set.id); + assert_eq!(2, auth_set.len); + let want = array_bytes::hex_n_into_unchecked::( + "176e73f1bf656478b728e28dd1a7733c98621b8acf830bff585949763dca7a96", + ); + assert_eq!(want, auth_set.root); + + // next authority set should have same validators but different id + assert_eq!(1, next_auth_set.id); + assert_eq!(auth_set.len, next_auth_set.len); + assert_eq!(auth_set.root, next_auth_set.root); + + let announced_set = next_auth_set; + init_block(1); + let auth_set = BeefyMmr::authority_set_proof(); + let next_auth_set = BeefyMmr::next_authority_set_proof(); + + // check new auth are expected ones + assert_eq!(announced_set, auth_set); + assert_eq!(1, auth_set.id); + // check next auth set + assert_eq!(2, next_auth_set.id); + let want = array_bytes::hex_n_into_unchecked::( + "9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5", + ); + assert_eq!(2, next_auth_set.len); + assert_eq!(want, next_auth_set.root); + + let announced_set = next_auth_set; + init_block(2); + let auth_set = BeefyMmr::authority_set_proof(); + let next_auth_set = BeefyMmr::next_authority_set_proof(); + + // check new auth are expected ones + assert_eq!(announced_set, auth_set); + assert_eq!(2, auth_set.id); + // check next auth set + assert_eq!(3, next_auth_set.id); + let want = array_bytes::hex_n_into_unchecked::( + "9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5", + ); + assert_eq!(2, next_auth_set.len); + assert_eq!(want, next_auth_set.root); + }); +} diff --git a/frame/beefy/Cargo.toml b/frame/beefy/Cargo.toml new file mode 100644 index 0000000000000..707e8e25712ab --- /dev/null +++ b/frame/beefy/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "pallet-beefy" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/paritytech/substrate" +description = "BEEFY FRAME pallet" +homepage = "https://substrate.io" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-staking = { version = "4.0.0-dev", path = "../../primitives/staking" } + +[features] +default = ["std"] +std = [ + "beefy-primitives/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "pallet-session/std", + "scale-info/std", + "serde", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/beefy/src/lib.rs b/frame/beefy/src/lib.rs new file mode 100644 index 0000000000000..4cb23107e7843 --- /dev/null +++ b/frame/beefy/src/lib.rs @@ -0,0 +1,249 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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_attr(not(feature = "std"), no_std)] + +use codec::{Encode, MaxEncodedLen}; + +use frame_support::{ + log, + traits::{Get, OneSessionHandler}, + BoundedSlice, BoundedVec, Parameter, +}; + +use sp_runtime::{ + generic::DigestItem, + traits::{IsMember, Member}, + RuntimeAppPublic, +}; +use sp_std::prelude::*; + +use beefy_primitives::{ + AuthorityIndex, ConsensusLog, OnNewValidatorSet, ValidatorSet, BEEFY_ENGINE_ID, + GENESIS_AUTHORITY_SET_ID, +}; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Authority identifier type + type BeefyId: Member + + Parameter + + RuntimeAppPublic + + MaybeSerializeDeserialize + + MaxEncodedLen; + + /// The maximum number of authorities that can be added. + type MaxAuthorities: Get; + + /// A hook to act on the new BEEFY validator set. + /// + /// For some applications it might be beneficial to make the BEEFY validator set available + /// externally apart from having it in the storage. For instance you might cache a light + /// weight MMR root over validators and make it available for Light Clients. + type OnNewValidatorSet: OnNewValidatorSet<::BeefyId>; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + /// The current authorities set + #[pallet::storage] + #[pallet::getter(fn authorities)] + pub(super) type Authorities = + StorageValue<_, BoundedVec, ValueQuery>; + + /// The current validator set id + #[pallet::storage] + #[pallet::getter(fn validator_set_id)] + pub(super) type ValidatorSetId = + StorageValue<_, beefy_primitives::ValidatorSetId, ValueQuery>; + + /// Authorities set scheduled to be used with the next session + #[pallet::storage] + #[pallet::getter(fn next_authorities)] + pub(super) type NextAuthorities = + StorageValue<_, BoundedVec, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub authorities: Vec, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { authorities: Vec::new() } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + Pallet::::initialize_authorities(&self.authorities) + // we panic here as runtime maintainers can simply reconfigure genesis and restart + // the chain easily + .expect("Authorities vec too big"); + } + } +} + +impl Pallet { + /// Return the current active BEEFY validator set. + pub fn validator_set() -> Option> { + let validators: BoundedVec = Self::authorities(); + let id: beefy_primitives::ValidatorSetId = Self::validator_set_id(); + ValidatorSet::::new(validators, id) + } + + fn change_authorities( + new: BoundedVec, + queued: BoundedVec, + ) { + >::put(&new); + + let new_id = Self::validator_set_id() + 1u64; + >::put(new_id); + + >::put(&queued); + + if let Some(validator_set) = ValidatorSet::::new(new, new_id) { + let log = DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::AuthoritiesChange(validator_set.clone()).encode(), + ); + >::deposit_log(log); + + let next_id = new_id + 1; + if let Some(next_validator_set) = ValidatorSet::::new(queued, next_id) { + >::on_new_validator_set( + &validator_set, + &next_validator_set, + ); + } + } + } + + fn initialize_authorities(authorities: &Vec) -> Result<(), ()> { + if authorities.is_empty() { + return Ok(()) + } + + if !>::get().is_empty() { + return Err(()) + } + + let bounded_authorities = + BoundedSlice::::try_from(authorities.as_slice()) + .map_err(|_| ())?; + + let id = GENESIS_AUTHORITY_SET_ID; + >::put(bounded_authorities); + >::put(id); + // Like `pallet_session`, initialize the next validator set as well. + >::put(bounded_authorities); + + if let Some(validator_set) = ValidatorSet::::new(authorities.clone(), id) { + let next_id = id + 1; + if let Some(next_validator_set) = + ValidatorSet::::new(authorities.clone(), next_id) + { + >::on_new_validator_set( + &validator_set, + &next_validator_set, + ); + } + } + Ok(()) + } +} + +impl sp_runtime::BoundToRuntimeAppPublic for Pallet { + type Public = T::BeefyId; +} + +impl OneSessionHandler for Pallet { + type Key = T::BeefyId; + + fn on_genesis_session<'a, I: 'a>(validators: I) + where + I: Iterator, + { + let authorities = validators.map(|(_, k)| k).collect::>(); + // we panic here as runtime maintainers can simply reconfigure genesis and restart the + // chain easily + Self::initialize_authorities(&authorities).expect("Authorities vec too big"); + } + + fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I) + where + I: Iterator, + { + let next_authorities = validators.map(|(_, k)| k).collect::>(); + if next_authorities.len() as u32 > T::MaxAuthorities::get() { + log::error!( + target: "runtime::beefy", + "authorities list {:?} truncated to length {}", + next_authorities, T::MaxAuthorities::get(), + ); + } + let bounded_next_authorities = + BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_authorities); + + let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::>(); + if next_queued_authorities.len() as u32 > T::MaxAuthorities::get() { + log::error!( + target: "runtime::beefy", + "queued authorities list {:?} truncated to length {}", + next_queued_authorities, T::MaxAuthorities::get(), + ); + } + let bounded_next_queued_authorities = + BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_queued_authorities); + + // Always issue a change on each `session`, even if validator set hasn't changed. + // We want to have at least one BEEFY mandatory block per session. + Self::change_authorities(bounded_next_authorities, bounded_next_queued_authorities); + } + + fn on_disabled(i: u32) { + let log = DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::OnDisabled(i as AuthorityIndex).encode(), + ); + + >::deposit_log(log); + } +} + +impl IsMember for Pallet { + fn is_member(authority_id: &T::BeefyId) -> bool { + Self::authorities().iter().any(|id| id == authority_id) + } +} diff --git a/frame/beefy/src/mock.rs b/frame/beefy/src/mock.rs new file mode 100644 index 0000000000000..ad3a672333dd5 --- /dev/null +++ b/frame/beefy/src/mock.rs @@ -0,0 +1,164 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 std::vec; + +use frame_support::{ + construct_runtime, parameter_types, + sp_io::TestExternalities, + traits::{ConstU16, ConstU32, ConstU64, GenesisBuild}, + BasicExternalities, +}; +use sp_core::H256; +use sp_runtime::{ + app_crypto::ecdsa::Public, + impl_opaque_keys, + testing::Header, + traits::{BlakeTwo256, ConvertInto, IdentityLookup, OpaqueKeys}, + Perbill, +}; + +use crate as pallet_beefy; + +pub use beefy_primitives::{crypto::AuthorityId as BeefyId, ConsensusLog, BEEFY_ENGINE_ID}; + +impl_opaque_keys! { + pub struct MockSessionKeys { + pub dummy: pallet_beefy::Pallet, + } +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Beefy: pallet_beefy::{Pallet, Config, Storage}, + Session: pallet_session::{Pallet, Call, Storage, Event, Config}, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_beefy::Config for Test { + type BeefyId = BeefyId; + type MaxAuthorities = ConstU32<100>; + type OnNewValidatorSet = (); +} + +parameter_types! { + pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33); +} + +impl pallet_session::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = u64; + type ValidatorIdOf = ConvertInto; + type ShouldEndSession = pallet_session::PeriodicSessions, ConstU64<0>>; + type NextSessionRotation = pallet_session::PeriodicSessions, ConstU64<0>>; + type SessionManager = MockSessionManager; + type SessionHandler = ::KeyTypeIdProviders; + type Keys = MockSessionKeys; + type WeightInfo = (); +} + +pub struct MockSessionManager; + +impl pallet_session::SessionManager for MockSessionManager { + fn end_session(_: sp_staking::SessionIndex) {} + fn start_session(_: sp_staking::SessionIndex) {} + fn new_session(idx: sp_staking::SessionIndex) -> Option> { + if idx == 0 || idx == 1 { + Some(vec![1, 2]) + } else if idx == 2 { + Some(vec![3, 4]) + } else { + None + } + } +} + +// Note, that we can't use `UintAuthorityId` here. Reason is that the implementation +// of `to_public_key()` assumes, that a public key is 32 bytes long. This is true for +// ed25519 and sr25519 but *not* for ecdsa. A compressed ecdsa public key is 33 bytes, +// with the first one containing information to reconstruct the uncompressed key. +pub fn mock_beefy_id(id: u8) -> BeefyId { + let mut buf: [u8; 33] = [id; 33]; + // Set to something valid. + buf[0] = 0x02; + let pk = Public::from_raw(buf); + BeefyId::from(pk) +} + +pub fn mock_authorities(vec: Vec) -> Vec<(u64, BeefyId)> { + vec.into_iter().map(|id| ((id as u64), mock_beefy_id(id))).collect() +} + +pub fn new_test_ext(ids: Vec) -> TestExternalities { + new_test_ext_raw_authorities(mock_authorities(ids)) +} + +pub fn new_test_ext_raw_authorities(authorities: Vec<(u64, BeefyId)>) -> TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let session_keys: Vec<_> = authorities + .iter() + .map(|id| (id.0 as u64, id.0 as u64, MockSessionKeys { dummy: id.1.clone() })) + .collect(); + + BasicExternalities::execute_with_storage(&mut t, || { + for (ref id, ..) in &session_keys { + frame_system::Pallet::::inc_providers(id); + } + }); + + pallet_session::GenesisConfig:: { keys: session_keys } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() +} diff --git a/frame/beefy/src/tests.rs b/frame/beefy/src/tests.rs new file mode 100644 index 0000000000000..4136b0c1f1ecf --- /dev/null +++ b/frame/beefy/src/tests.rs @@ -0,0 +1,146 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 std::vec; + +use beefy_primitives::ValidatorSet; +use codec::Encode; + +use sp_runtime::DigestItem; + +use frame_support::traits::OnInitialize; + +use crate::mock::*; + +fn init_block(block: u64) { + System::set_block_number(block); + Session::on_initialize(block); +} + +pub fn beefy_log(log: ConsensusLog) -> DigestItem { + DigestItem::Consensus(BEEFY_ENGINE_ID, log.encode()) +} + +#[test] +fn genesis_session_initializes_authorities() { + let want = vec![mock_beefy_id(1), mock_beefy_id(2), mock_beefy_id(3), mock_beefy_id(4)]; + + new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { + let authorities = Beefy::authorities(); + + assert!(authorities.len() == 2); + assert_eq!(want[0], authorities[0]); + assert_eq!(want[1], authorities[1]); + + assert!(Beefy::validator_set_id() == 0); + + let next_authorities = Beefy::next_authorities(); + + assert!(next_authorities.len() == 2); + assert_eq!(want[0], next_authorities[0]); + assert_eq!(want[1], next_authorities[1]); + }); +} + +#[test] +fn session_change_updates_authorities() { + new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { + assert!(0 == Beefy::validator_set_id()); + + init_block(1); + + assert!(1 == Beefy::validator_set_id()); + + let want = beefy_log(ConsensusLog::AuthoritiesChange( + ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap(), + )); + + let log = System::digest().logs[0].clone(); + assert_eq!(want, log); + + init_block(2); + + assert!(2 == Beefy::validator_set_id()); + + let want = beefy_log(ConsensusLog::AuthoritiesChange( + ValidatorSet::new(vec![mock_beefy_id(3), mock_beefy_id(4)], 2).unwrap(), + )); + + let log = System::digest().logs[1].clone(); + assert_eq!(want, log); + }); +} + +#[test] +fn session_change_updates_next_authorities() { + let want = vec![mock_beefy_id(1), mock_beefy_id(2), mock_beefy_id(3), mock_beefy_id(4)]; + + new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { + let next_authorities = Beefy::next_authorities(); + + assert!(next_authorities.len() == 2); + assert_eq!(want[0], next_authorities[0]); + assert_eq!(want[1], next_authorities[1]); + + init_block(1); + + let next_authorities = Beefy::next_authorities(); + + assert!(next_authorities.len() == 2); + assert_eq!(want[2], next_authorities[0]); + assert_eq!(want[3], next_authorities[1]); + }); +} + +#[test] +fn validator_set_at_genesis() { + let want = vec![mock_beefy_id(1), mock_beefy_id(2)]; + + new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { + let vs = Beefy::validator_set().unwrap(); + + assert_eq!(vs.id(), 0u64); + assert_eq!(vs.validators()[0], want[0]); + assert_eq!(vs.validators()[1], want[1]); + }); +} + +#[test] +fn validator_set_updates_work() { + let want = vec![mock_beefy_id(1), mock_beefy_id(2), mock_beefy_id(3), mock_beefy_id(4)]; + + new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { + let vs = Beefy::validator_set().unwrap(); + assert_eq!(vs.id(), 0u64); + + init_block(1); + + let vs = Beefy::validator_set().unwrap(); + + assert_eq!(vs.id(), 1u64); + assert_eq!(want[0], vs.validators()[0]); + assert_eq!(want[1], vs.validators()[1]); + + init_block(2); + + let vs = Beefy::validator_set().unwrap(); + + assert_eq!(vs.id(), 2u64); + assert_eq!(want[2], vs.validators()[0]); + assert_eq!(want[3], vs.validators()[1]); + }); +} diff --git a/frame/benchmarking/Cargo.toml b/frame/benchmarking/Cargo.toml index ea690d966c979..7c18b69401884 100644 --- a/frame/benchmarking/Cargo.toml +++ b/frame/benchmarking/Cargo.toml @@ -2,9 +2,9 @@ name = "frame-benchmarking" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Macro for benchmarking a FRAME runtime." readme = "README.md" @@ -13,34 +13,47 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -linregress = { version = "0.4.3", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +linregress = { version = "0.4.4", optional = true } +log = { version = "0.4.17", default-features = false } paste = "1.0" -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api", default-features = false } -sp-runtime-interface = { version = "4.0.0-dev", path = "../../primitives/runtime-interface", default-features = false } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime", default-features = false } -sp-std = { version = "4.0.0-dev", path = "../../primitives/std", default-features = false } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io", default-features = false } -sp-storage = { version = "4.0.0-dev", path = "../../primitives/storage", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.14", default-features = false } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../../primitives/runtime-interface" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-storage = { version = "7.0.0", default-features = false, path = "../../primitives/storage" } [dev-dependencies] -hex-literal = "0.3.1" +array-bytes = "4.1" +rusty-fork = { version = "0.3.0", default-features = false } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-runtime-interface/std", - "sp-runtime/std", - "sp-api/std", - "sp-std/std", "frame-support/std", "frame-system/std", "linregress", "log/std", + "scale-info/std", + "serde", + "sp-api/std", + "sp-application-crypto/std", + "sp-core/std", + "sp-io/std", + "sp-runtime-interface/std", + "sp-runtime/std", + "sp-std/std", + "sp-storage/std", +] +runtime-benchmarks = [ + "frame-system/runtime-benchmarks", ] diff --git a/frame/benchmarking/README.md b/frame/benchmarking/README.md index 38c683cb8db5b..76673c5f69b33 100644 --- a/frame/benchmarking/README.md +++ b/frame/benchmarking/README.md @@ -43,7 +43,7 @@ The benchmarking framework comes with the following tools: * [A set of macros](./src/lib.rs) (`benchmarks!`, `add_benchmark!`, etc...) to make it easy to write, test, and add runtime benchmarks. * [A set of linear regression analysis functions](./src/analysis.rs) for processing benchmark data. -* [A CLI extension](../../utils/frame/benchmarking-cli/) to make it easy to execute benchmarks on your +* [A CLI extension](../../utils/frame/benchmarking-cli/README.md) to make it easy to execute benchmarks on your node. The end-to-end benchmarking pipeline is disabled by default when compiling a node. If you want to @@ -125,6 +125,8 @@ cargo test -p pallet-balances --features runtime-benchmarks > ``` > To solve this, navigate to the folder of the node (`cd bin/node/cli`) or pallet (`cd frame/pallet`) and run the command there. +This will instance each linear component with different values. The number of values per component is set to six and can be changed with the `VALUES_PER_COMPONENT` environment variable. + ## Adding Benchmarks The benchmarks included with each pallet are not automatically added to your node. To actually @@ -150,9 +152,13 @@ feature flag: ```bash cd bin/node/cli -cargo build --release --features runtime-benchmarks +cargo build --profile=production --features runtime-benchmarks ``` +The production profile applies various compiler optimizations. +These optimizations slow down the compilation process *a lot*. +If you are just testing things out and don't need final numbers, don't include `--profile=production`. + ## Running Benchmarks Finally, once you have a node binary with benchmarks enabled, you need to execute your various @@ -161,13 +167,13 @@ benchmarks. You can get a list of the available benchmarks by running: ```bash -./target/release/substrate benchmark --chain dev --pallet "*" --extrinsic "*" --repeat 0 +./target/production/substrate benchmark pallet --chain dev --pallet "*" --extrinsic "*" --repeat 0 ``` Then you can run a benchmark like so: ```bash -./target/release/substrate benchmark \ +./target/production/substrate benchmark pallet \ --chain dev \ # Configurable Chain Spec --execution=wasm \ # Always test with Wasm --wasm-execution=compiled \ # Always used `wasm-time` @@ -179,7 +185,7 @@ Then you can run a benchmark like so: ``` This will output a file `pallet_name.rs` which implements the `WeightInfo` trait you should include -in your pallet. Each blockchain should generate their own benchmark file with their custom +in your pallet. Double colons `::` will be replaced with a `_` in the output name if you specify a directory. Each blockchain should generate their own benchmark file with their custom implementation of the `WeightInfo` trait. This means that you will be able to use these modular Substrate pallets while still keeping your network safe for your specific configuration and requirements. @@ -200,7 +206,7 @@ used for joining all the arguments passed to the CLI. To get a full list of available options when running benchmarks, run: ```bash -./target/release/substrate benchmark --help +./target/production/substrate benchmark --help ``` License: Apache-2.0 diff --git a/frame/benchmarking/src/analysis.rs b/frame/benchmarking/src/analysis.rs index 2bb20ebe2e7f8..0b77a92347d03 100644 --- a/frame/benchmarking/src/analysis.rs +++ b/frame/benchmarking/src/analysis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,18 +18,16 @@ //! Tools for analyzing the benchmark results. use crate::BenchmarkResult; -use core::convert::TryFrom; -use linregress::{FormulaRegressionBuilder, RegressionDataBuilder}; use std::collections::BTreeMap; -pub use linregress::RegressionModel; - pub struct Analysis { pub base: u128, pub slopes: Vec, pub names: Vec, pub value_dists: Option, u128, u128)>>, - pub model: Option, + pub errors: Option>, + pub minimum: u128, + selector: BenchmarkSelector, } #[derive(Clone, Copy)] @@ -41,6 +39,65 @@ pub enum BenchmarkSelector { ProofSize, } +/// Multiplies the value by 1000 and converts it into an u128. +fn mul_1000_into_u128(value: f64) -> u128 { + // This is slighly more precise than the alternative of `(value * 1000.0) as u128`. + (value as u128) + .saturating_mul(1000) + .saturating_add((value.fract() * 1000.0) as u128) +} + +impl BenchmarkSelector { + fn scale_and_cast_weight(self, value: f64, round_up: bool) -> u128 { + if let BenchmarkSelector::ExtrinsicTime = self { + // We add a very slight bias here to counteract the numerical imprecision of the linear + // regression where due to rounding issues it can emit a number like `2999999.999999998` + // which we most certainly always want to round up instead of truncating. + mul_1000_into_u128(value + 0.000_000_005) + } else { + if round_up { + (value + 0.5) as u128 + } else { + value as u128 + } + } + } + + fn scale_weight(self, value: u128) -> u128 { + if let BenchmarkSelector::ExtrinsicTime = self { + value.saturating_mul(1000) + } else { + value + } + } + + fn nanos_from_weight(self, value: u128) -> u128 { + if let BenchmarkSelector::ExtrinsicTime = self { + value / 1000 + } else { + value + } + } + + fn get_value(self, result: &BenchmarkResult) -> u128 { + match self { + BenchmarkSelector::ExtrinsicTime => result.extrinsic_time, + BenchmarkSelector::StorageRootTime => result.storage_root_time, + BenchmarkSelector::Reads => result.reads.into(), + BenchmarkSelector::Writes => result.writes.into(), + BenchmarkSelector::ProofSize => result.proof_size.into(), + } + } + + fn get_minimum(self, results: &[BenchmarkResult]) -> u128 { + results + .iter() + .map(|result| self.get_value(result)) + .min() + .expect("results cannot be empty") + } +} + #[derive(Debug)] pub enum AnalysisChoice { /// Use minimum squares regression for analyzing the benchmarking results. @@ -73,6 +130,70 @@ impl TryFrom> for AnalysisChoice { } } +fn raw_linear_regression( + xs: &[f64], + ys: &[f64], + x_vars: usize, + with_intercept: bool, +) -> Option<(f64, Vec, Vec)> { + let mut data: Vec = Vec::new(); + + // Here we build a raw matrix of linear equations for the `linregress` crate to solve for us + // and build a linear regression model around it. + // + // Each row of the matrix contains as the first column the actual value which we want + // the model to predict for us (the `y`), and the rest of the columns contain the input + // parameters on which the model will base its predictions on (the `xs`). + // + // In machine learning terms this is essentially the training data for the model. + // + // As a special case the very first input parameter represents the constant factor + // of the linear equation: the so called "intercept value". Since it's supposed to + // be constant we can just put a dummy input parameter of either a `1` (in case we want it) + // or a `0` (in case we do not). + for (&y, xs) in ys.iter().zip(xs.chunks_exact(x_vars)) { + data.push(y); + if with_intercept { + data.push(1.0); + } else { + data.push(0.0); + } + data.extend(xs); + } + let model = linregress::fit_low_level_regression_model(&data, ys.len(), x_vars + 2).ok()?; + Some((model.parameters[0], model.parameters[1..].to_vec(), model.se)) +} + +fn linear_regression( + xs: Vec, + mut ys: Vec, + x_vars: usize, +) -> Option<(f64, Vec, Vec)> { + let (intercept, params, errors) = raw_linear_regression(&xs, &ys, x_vars, true)?; + if intercept >= -0.0001 { + // The intercept is positive, or is effectively zero. + return Some((intercept, params, errors[1..].to_vec())) + } + + // The intercept is negative. + // The weights must be always positive, so we can't have that. + + let mut min = ys[0]; + for &value in &ys { + if value < min { + min = value; + } + } + + for value in &mut ys { + *value -= min; + } + + let (intercept, params, errors) = raw_linear_regression(&xs, &ys, x_vars, false)?; + assert!(intercept.abs() <= 0.0001); + Some((min, params, errors[1..].to_vec())) +} + impl Analysis { // Useful for when there are no components, and we just need an median value of the benchmark // results. Note: We choose the median value because it is more robust to outliers. @@ -96,11 +217,13 @@ impl Analysis { let mid = values.len() / 2; Some(Self { - base: values[mid], + base: selector.scale_weight(values[mid]), slopes: Vec::new(), names: Vec::new(), value_dists: None, - model: None, + errors: None, + minimum: selector.get_minimum(&r), + selector, }) } @@ -187,20 +310,25 @@ impl Analysis { }) .collect::>(); - let base = models[0].0.max(0f64) as u128; - let slopes = models.iter().map(|x| x.1.max(0f64) as u128).collect::>(); + let base = selector.scale_and_cast_weight(models[0].0.max(0f64), false); + let slopes = models + .iter() + .map(|x| selector.scale_and_cast_weight(x.1.max(0f64), false)) + .collect::>(); Some(Self { base, slopes, names: results.into_iter().map(|x| x.0).collect::>(), value_dists: None, - model: None, + errors: None, + minimum: selector.get_minimum(&r), + selector, }) } pub fn min_squares_iqr(r: &Vec, selector: BenchmarkSelector) -> Option { - if r[0].components.is_empty() { + if r[0].components.is_empty() || r.len() <= 2 { return Self::median_value(r, selector) } @@ -222,41 +350,12 @@ impl Analysis { *rs = rs[ql..rs.len() - ql].to_vec(); } - let mut data = - vec![("Y", results.iter().flat_map(|x| x.1.iter().map(|v| *v as f64)).collect())]; - let names = r[0].components.iter().map(|x| format!("{:?}", x.0)).collect::>(); - data.extend(names.iter().enumerate().map(|(i, p)| { - ( - p.as_str(), - results - .iter() - .flat_map(|x| Some(x.0[i] as f64).into_iter().cycle().take(x.1.len())) - .collect::>(), - ) - })); - - let data = RegressionDataBuilder::new().build_from(data).ok()?; - - let model = FormulaRegressionBuilder::new() - .data(&data) - .formula(format!("Y ~ {}", names.join(" + "))) - .fit() - .ok()?; - - let slopes = model - .parameters - .regressor_values - .iter() - .enumerate() - .map(|(_, x)| (*x + 0.5) as u128) - .collect(); - let value_dists = results .iter() .map(|(p, vs)| { // Avoid divide by zero - if vs.len() == 0 { + if vs.is_empty() { return (p.clone(), 0, 0) } let total = vs.iter().fold(0u128, |acc, v| acc + *v); @@ -270,12 +369,34 @@ impl Analysis { }) .collect::>(); + let mut ys: Vec = Vec::new(); + let mut xs: Vec = Vec::new(); + for result in results { + let x: Vec = result.0.iter().map(|value| *value as f64).collect(); + for y in result.1 { + xs.extend(x.iter().copied()); + ys.push(y as f64); + } + } + + let (intercept, slopes, errors) = linear_regression(xs, ys, r[0].components.len())?; + Some(Self { - base: (model.parameters.intercept_value + 0.5) as u128, - slopes, + base: selector.scale_and_cast_weight(intercept, true), + slopes: slopes + .into_iter() + .map(|value| selector.scale_and_cast_weight(value, true)) + .collect(), names, value_dists: Some(value_dists), - model: Some(model), + errors: Some( + errors + .into_iter() + .map(|value| selector.scale_and_cast_weight(value, false)) + .collect(), + ), + minimum: selector.get_minimum(&r), + selector, }) } @@ -305,9 +426,10 @@ impl Analysis { .for_each(|(a, b)| assert!(a == b, "benchmark results not in the same order")); let names = median_slopes.names; let value_dists = min_squares.value_dists; - let model = min_squares.model; + let errors = min_squares.errors; + let minimum = selector.get_minimum(&r); - Some(Self { base, slopes, names, value_dists, model }) + Some(Self { base, slopes, names, value_dists, errors, selector, minimum }) } } @@ -364,18 +486,19 @@ impl std::fmt::Display for Analysis { } } } - if let Some(ref model) = self.model { + + if let Some(ref errors) = self.errors { writeln!(f, "\nQuality and confidence:")?; writeln!(f, "param error")?; - for (p, se) in self.names.iter().zip(model.se.regressor_values.iter()) { - writeln!(f, "{} {:>8}", p, ms(*se as u128))?; + for (p, se) in self.names.iter().zip(errors.iter()) { + writeln!(f, "{} {:>8}", p, ms(self.selector.nanos_from_weight(*se)))?; } } writeln!(f, "\nModel:")?; - writeln!(f, "Time ~= {:>8}", ms(self.base))?; + writeln!(f, "Time ~= {:>8}", ms(self.selector.nanos_from_weight(self.base)))?; for (&t, n) in self.slopes.iter().zip(self.names.iter()) { - writeln!(f, " + {} {:>8}", n, ms(t))?; + writeln!(f, " + {} {:>8}", n, ms(self.selector.nanos_from_weight(t)))?; } writeln!(f, " µs") } @@ -416,6 +539,52 @@ mod tests { } } + #[test] + fn test_linear_regression() { + let ys = vec![ + 3797981.0, + 37857779.0, + 70569402.0, + 104004114.0, + 137233924.0, + 169826237.0, + 203521133.0, + 237552333.0, + 271082065.0, + 305554637.0, + 335218347.0, + 371759065.0, + 405086197.0, + 438353555.0, + 472891417.0, + 505339532.0, + 527784778.0, + 562590596.0, + 635291991.0, + 673027090.0, + 708119408.0, + ]; + let xs = vec![ + 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, + 16.0, 17.0, 18.0, 19.0, 20.0, + ]; + + let (intercept, params, errors) = raw_linear_regression(&xs, &ys, 1, true).unwrap(); + assert_eq!(intercept as i64, -2712997); + assert_eq!(params.len(), 1); + assert_eq!(params[0] as i64, 34444926); + assert_eq!(errors.len(), 2); + assert_eq!(errors[0] as i64, 4805766); + assert_eq!(errors[1] as i64, 411084); + + let (intercept, params, errors) = linear_regression(xs, ys, 1).unwrap(); + assert_eq!(intercept as i64, 3797981); + assert_eq!(params.len(), 1); + assert_eq!(params[0] as i64, 33968513); + assert_eq!(errors.len(), 1); + assert_eq!(errors[0] as i64, 217331); + } + #[test] fn analysis_median_slopes_should_work() { let data = vec![ @@ -479,8 +648,8 @@ mod tests { let extrinsic_time = Analysis::median_slopes(&data, BenchmarkSelector::ExtrinsicTime).unwrap(); - assert_eq!(extrinsic_time.base, 10_000_000); - assert_eq!(extrinsic_time.slopes, vec![1_000_000, 100_000]); + assert_eq!(extrinsic_time.base, 10_000_000_000); + assert_eq!(extrinsic_time.slopes, vec![1_000_000_000, 100_000_000]); let reads = Analysis::median_slopes(&data, BenchmarkSelector::Reads).unwrap(); assert_eq!(reads.base, 2); @@ -554,8 +723,8 @@ mod tests { let extrinsic_time = Analysis::min_squares_iqr(&data, BenchmarkSelector::ExtrinsicTime).unwrap(); - assert_eq!(extrinsic_time.base, 10_000_000); - assert_eq!(extrinsic_time.slopes, vec![1_000_000, 100_000]); + assert_eq!(extrinsic_time.base, 10_000_000_000); + assert_eq!(extrinsic_time.slopes, vec![1000000000, 100000000]); let reads = Analysis::min_squares_iqr(&data, BenchmarkSelector::Reads).unwrap(); assert_eq!(reads.base, 2); @@ -565,4 +734,36 @@ mod tests { assert_eq!(writes.base, 0); assert_eq!(writes.slopes, vec![0, 2]); } + + #[test] + fn analysis_min_squares_iqr_uses_multiple_samples_for_same_parameters() { + let data = vec![ + benchmark_result(vec![(BenchmarkParameter::n, 0)], 2_000_000, 0, 0, 0), + benchmark_result(vec![(BenchmarkParameter::n, 0)], 4_000_000, 0, 0, 0), + benchmark_result(vec![(BenchmarkParameter::n, 1)], 4_000_000, 0, 0, 0), + benchmark_result(vec![(BenchmarkParameter::n, 1)], 8_000_000, 0, 0, 0), + ]; + + let extrinsic_time = + Analysis::min_squares_iqr(&data, BenchmarkSelector::ExtrinsicTime).unwrap(); + assert_eq!(extrinsic_time.base, 3_000_000_000); + assert_eq!(extrinsic_time.slopes, vec![3_000_000_000]); + } + + #[test] + fn intercept_of_a_little_under_zero_is_rounded_up_to_zero() { + // Analytically this should result in an intercept of 0, but + // due to numerical imprecision this will generate an intercept + // equal to roughly -0.0000000000000004440892098500626 + let data = vec![ + benchmark_result(vec![(BenchmarkParameter::n, 1)], 2, 0, 0, 0), + benchmark_result(vec![(BenchmarkParameter::n, 2)], 4, 0, 0, 0), + benchmark_result(vec![(BenchmarkParameter::n, 3)], 6, 0, 0, 0), + ]; + + let extrinsic_time = + Analysis::min_squares_iqr(&data, BenchmarkSelector::ExtrinsicTime).unwrap(); + assert_eq!(extrinsic_time.base, 0); + assert_eq!(extrinsic_time.slopes, vec![2000]); + } } diff --git a/frame/benchmarking/src/baseline.rs b/frame/benchmarking/src/baseline.rs new file mode 100644 index 0000000000000..5fd845551daca --- /dev/null +++ b/frame/benchmarking/src/baseline.rs @@ -0,0 +1,213 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! A set of benchmarks which can establish a global baseline for all other +//! benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::benchmarks; +use codec::Encode; +use frame_system::Pallet as System; +use sp_application_crypto::KeyTypeId; +use sp_runtime::{ + traits::{AppVerify, Hash}, + RuntimeAppPublic, +}; +use sp_std::prelude::*; + +pub const TEST_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"test"); + +mod app_sr25519 { + use super::TEST_KEY_TYPE_ID; + use sp_application_crypto::{app_crypto, sr25519}; + app_crypto!(sr25519, TEST_KEY_TYPE_ID); +} + +type SignerId = app_sr25519::Public; + +pub struct Pallet(System); +pub trait Config: frame_system::Config {} + +benchmarks! { + addition { + let i in 0 .. 1_000_000; + let mut start = 0; + }: { + (0..i).for_each(|_| start += 1); + } verify { + assert_eq!(start, i); + } + + subtraction { + let i in 0 .. 1_000_000; + let mut start = u32::MAX; + }: { + (0..i).for_each(|_| start -= 1); + } verify { + assert_eq!(start, u32::MAX - i); + } + + multiplication { + let i in 0 .. 1_000_000; + let mut out = 0; + }: { + (1..=i).for_each(|j| out = 2 * j); + } verify { + assert_eq!(out, 2 * i); + } + + division { + let i in 0 .. 1_000_000; + let mut out = 0; + }: { + (0..=i).for_each(|j| out = j / 2); + } verify { + assert_eq!(out, i / 2); + } + + hashing { + let i in 0 .. 100; + let mut hash = T::Hash::default(); + }: { + (0..=100_000u32).for_each(|j| hash = T::Hashing::hash(&j.to_be_bytes())); + } verify { + assert!(hash != T::Hash::default()); + } + + sr25519_verification { + let i in 0 .. 100; + + let public = SignerId::generate_pair(None); + + let sigs_count: u8 = i.try_into().unwrap(); + let msg_and_sigs: Vec<_> = (0..sigs_count).map(|j| { + let msg = vec![j, j]; + (msg.clone(), public.sign(&msg).unwrap()) + }) + .collect(); + }: { + msg_and_sigs.iter().for_each(|(msg, sig)| { + assert!(sig.verify(&msg[..], &public)); + }); + } + + #[skip_meta] + storage_read { + let i in 0 .. 1_000; + let mut people = Vec::new(); + (0..i).for_each(|j| { + let hash = T::Hashing::hash(&j.to_be_bytes()).encode(); + frame_support::storage::unhashed::put(&hash, &hash); + people.push(hash); + }); + }: { + people.iter().for_each(|hash| { + // This does a storage read + let value = frame_support::storage::unhashed::get(hash); + assert_eq!(value, Some(hash.to_vec())); + }); + } + + #[skip_meta] + storage_write { + let i in 0 .. 1_000; + let mut hashes = Vec::new(); + (0..i).for_each(|j| { + let hash = T::Hashing::hash(&j.to_be_bytes()); + hashes.push(hash.encode()); + }); + }: { + hashes.iter().for_each(|hash| { + // This does a storage write + frame_support::storage::unhashed::put(hash, hash); + }); + } verify { + hashes.iter().for_each(|hash| { + let value = frame_support::storage::unhashed::get(hash); + assert_eq!(value, Some(hash.to_vec())); + }); + } + + impl_benchmark_test_suite!( + Pallet, + crate::baseline::mock::new_test_ext(), + crate::baseline::mock::Test, + ); +} + +#[cfg(test)] +pub mod mock { + use sp_runtime::{testing::H256, traits::IdentityLookup}; + + type AccountId = u64; + type AccountIndex = u32; + type BlockNumber = u64; + + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + type Block = frame_system::mocking::MockBlock; + + frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + } + ); + + impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = AccountIndex; + type BlockNumber = BlockNumber; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = sp_runtime::testing::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + } + + impl super::Config for Test {} + + pub fn new_test_ext() -> sp_io::TestExternalities { + use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStorePtr}; + use sp_std::sync::Arc; + + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()) as SyncCryptoStorePtr)); + + ext + } +} diff --git a/frame/benchmarking/src/lib.rs b/frame/benchmarking/src/lib.rs index 6c124a8a75761..29fa0b6a6af70 100644 --- a/frame/benchmarking/src/lib.rs +++ b/frame/benchmarking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,8 +27,10 @@ mod tests; mod tests_instance; mod utils; +pub mod baseline; + #[cfg(feature = "std")] -pub use analysis::{Analysis, AnalysisChoice, BenchmarkSelector, RegressionModel}; +pub use analysis::{Analysis, AnalysisChoice, BenchmarkSelector}; #[doc(hidden)] pub use frame_support; #[doc(hidden)] @@ -36,12 +38,15 @@ pub use log; #[doc(hidden)] pub use paste; #[doc(hidden)] +pub use sp_core::defer; +#[doc(hidden)] pub use sp_io::storage::root as storage_root; #[doc(hidden)] pub use sp_runtime::traits::Zero; #[doc(hidden)] -pub use sp_std::{self, boxed::Box, prelude::Vec, str, vec}; +pub use sp_runtime::StateVersion; #[doc(hidden)] +pub use sp_std::{self, boxed::Box, prelude::Vec, str, vec}; pub use sp_storage::TrackedStorageKey; pub use utils::*; @@ -81,12 +86,12 @@ macro_rules! whitelist { /// ``` /// /// Note that due to parsing restrictions, if the `from` expression is not a single token (i.e. a -/// literal or constant), then it must be parenthesised. +/// literal or constant), then it must be parenthesized. /// /// The macro allows for a number of "arms", each representing an individual benchmark. Using the /// simple syntax, the associated dispatchable function maps 1:1 with the benchmark and the name of /// the benchmark is the same as that of the associated function. However, extended syntax allows -/// for arbitrary expresions to be evaluated in a benchmark (including for example, +/// for arbitrary expressions to be evaluated in a benchmark (including for example, /// `on_initialize`). /// /// Note that the ranges are *inclusive* on both sides. This is in contrast to ranges in Rust which @@ -107,14 +112,14 @@ macro_rules! whitelist { /// foo { /// let caller = account::(b"caller", 0, benchmarks_seed); /// let l in 1 .. MAX_LENGTH => initialize_l(l); -/// }: _(Origin::Signed(caller), vec![0u8; l]) +/// }: _(RuntimeOrigin::Signed(caller), vec![0u8; l]) /// /// // second dispatchable: bar; this is a root dispatchable and accepts a `u8` vector of size /// // `l`. /// // In this case, we explicitly name the call using `bar` instead of `_`. /// bar { /// let l in 1 .. MAX_LENGTH => initialize_l(l); -/// }: bar(Origin::Root, vec![0u8; l]) +/// }: bar(RuntimeOrigin::Root, vec![0u8; l]) /// /// // third dispatchable: baz; this is a user dispatchable. It isn't dependent on length like the /// // other two but has its own complexity `c` that needs setting up. It uses `caller` (in the @@ -123,13 +128,20 @@ macro_rules! whitelist { /// baz1 { /// let caller = account::(b"caller", 0, benchmarks_seed); /// let c = 0 .. 10 => setup_c(&caller, c); -/// }: baz(Origin::Signed(caller)) +/// }: baz(RuntimeOrigin::Signed(caller)) /// /// // this is a second benchmark of the baz dispatchable with a different setup. /// baz2 { /// let caller = account::(b"caller", 0, benchmarks_seed); /// let c = 0 .. 10 => setup_c_in_some_other_way(&caller, c); -/// }: baz(Origin::Signed(caller)) +/// }: baz(RuntimeOrigin::Signed(caller)) +/// +/// // You may optionally specify the origin type if it can't be determined automatically like +/// // this. +/// baz3 { +/// let caller = account::(b"caller", 0, benchmarks_seed); +/// let l in 1 .. MAX_LENGTH => initialize_l(l); +/// }: baz(RuntimeOrigin::Signed(caller), vec![0u8; l]) /// /// // this is benchmarking some code that is not a dispatchable. /// populate_a_set { @@ -148,6 +160,12 @@ macro_rules! whitelist { /// benchmark just like a regular benchmark, but only testing at the lowest and highest values for /// each component. The function will return `Ok(())` if the benchmarks return no errors. /// +/// It is also possible to generate one #[test] function per benchmark by calling the +/// `impl_benchmark_test_suite` macro inside the `benchmarks` block. The functions will be named +/// `bench_` and can be run via `cargo test`. +/// You will see one line of output per benchmark. This approach will give you more understandable +/// error messages and allows for parallel benchmark execution. +/// /// You can optionally add a `verify` code block at the end of a benchmark to test any final state /// of your benchmark in a unit test. For example: /// @@ -167,7 +185,8 @@ macro_rules! whitelist { /// /// These `verify` blocks will not affect your benchmark results! /// -/// You can construct benchmark tests like so: +/// You can construct benchmark by using the `impl_benchmark_test_suite` macro or +/// by manually implementing them like so: /// /// ```ignore /// #[test] @@ -186,6 +205,7 @@ macro_rules! benchmarks { $( $rest:tt )* ) => { $crate::benchmarks_iter!( + { } { } { } ( ) @@ -205,6 +225,7 @@ macro_rules! benchmarks_instance { $( $rest:tt )* ) => { $crate::benchmarks_iter!( + { } { I: Instance } { } ( ) @@ -224,6 +245,7 @@ macro_rules! benchmarks_instance_pallet { $( $rest:tt )* ) => { $crate::benchmarks_iter!( + { } { I: 'static } { } ( ) @@ -237,8 +259,60 @@ macro_rules! benchmarks_instance_pallet { #[macro_export] #[doc(hidden)] macro_rules! benchmarks_iter { + // detect and extract `impl_benchmark_test_suite` call: + // - with a semi-colon + ( + { } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + impl_benchmark_test_suite!( + $bench_module:ident, + $new_test_ext:expr, + $test:path + $(, $( $args:tt )* )?); + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $bench_module, $new_test_ext, $test $(, $( $args )* )? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + $( $rest )* + } + }; + // - without a semicolon + ( + { } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + impl_benchmark_test_suite!( + $bench_module:ident, + $new_test_ext:expr, + $test:path + $(, $( $args:tt )* )?) + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $bench_module, $new_test_ext, $test $(, $( $args )* )? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + $( $rest )* + } + }; // detect and extract where clause: ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) @@ -248,6 +322,7 @@ macro_rules! benchmarks_iter { $( $rest:tt )* ) => { $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound)? } { $( $where_bound )* } ( $( $names )* ) @@ -258,6 +333,7 @@ macro_rules! benchmarks_iter { }; // detect and extract `#[skip_meta]` tag: ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) @@ -268,6 +344,7 @@ macro_rules! benchmarks_iter { $( $rest:tt )* ) => { $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) @@ -277,8 +354,9 @@ macro_rules! benchmarks_iter { $( $rest )* } }; - // detect and extract `#[extra] tag: + // detect and extract `#[extra]` tag: ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) @@ -289,6 +367,7 @@ macro_rules! benchmarks_iter { $( $rest:tt )* ) => { $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) @@ -300,39 +379,43 @@ macro_rules! benchmarks_iter { }; // mutation arm: ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) // This contains $( $( { $instance } )? $name:ident )* ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: _ ( $origin:expr $( , $arg:expr )* ) + $name:ident { $( $code:tt )* }: _ $(< $origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) verify $postcode:block $( $rest:tt )* ) => { $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) - $name { $( $code )* }: $name ( $origin $( , $arg )* ) + $name { $( $code )* }: $name $(< $origin_type >)? ( $origin $( , $arg )* ) verify $postcode $( $rest )* } }; // mutation arm: ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: $dispatch:ident ( $origin:expr $( , $arg:expr )* ) + $name:ident { $( $code:tt )* }: $dispatch:ident $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) verify $postcode:block $( $rest:tt )* ) => { $crate::paste::paste! { $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) @@ -350,15 +433,14 @@ macro_rules! benchmarks_iter { &__call ); }: { - let call_decoded = < + let __call_decoded = < Call as $crate::frame_support::codec::Decode >::decode(&mut &__benchmarked_call_encoded[..]) .expect("call is encoded above, encoding must be correct"); - - < - Call as $crate::frame_support::traits::UnfilteredDispatchable - >::dispatch_bypass_filter(call_decoded, $origin.into())?; + let __origin = $crate::to_origin!($origin $(, $origin_type)?); + as $crate::frame_support::traits::UnfilteredDispatchable + >::dispatch_bypass_filter(__call_decoded, __origin)?; } verify $postcode $( $rest )* @@ -367,6 +449,7 @@ macro_rules! benchmarks_iter { }; // iteration arm: ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) @@ -394,6 +477,7 @@ macro_rules! benchmarks_iter { ); $crate::benchmarks_iter!( + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* { $( $instance )? } $name ) @@ -402,8 +486,40 @@ macro_rules! benchmarks_iter { $( $rest )* ); }; - // iteration-exit arm + // iteration-exit arm which generates a #[test] function for each case. + ( + { $bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ) => { + $crate::selected_benchmark!( + { $( $where_clause)* } + { $( $instance: $instance_bound )? } + $( $names )* + ); + $crate::impl_benchmark!( + { $( $where_clause )* } + { $( $instance: $instance_bound )? } + ( $( $names )* ) + ( $( $names_extra ),* ) + ( $( $names_skip_meta ),* ) + ); + $crate::impl_test_function!( + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + $bench_module, + $new_test_ext, + $test + $(, $( $args )* )? + ); + }; + // iteration-exit arm which doesn't generate a #[test] function for all cases. ( + { } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) @@ -425,69 +541,86 @@ macro_rules! benchmarks_iter { }; // add verify block to _() format ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: _ ( $origin:expr $( , $arg:expr )* ) + $name:ident { $( $code:tt )* }: _ $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) $( $rest:tt )* ) => { $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) - $name { $( $code )* }: _ ( $origin $( , $arg )* ) + $name { $( $code )* }: _ $(<$origin_type>)? ( $origin $( , $arg )* ) verify { } $( $rest )* } }; // add verify block to name() format ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: $dispatch:ident ( $origin:expr $( , $arg:expr )* ) + $name:ident { $( $code:tt )* }: $dispatch:ident $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) $( $rest:tt )* ) => { $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) - $name { $( $code )* }: $dispatch ( $origin $( , $arg )* ) + $name { $( $code )* }: $dispatch $(<$origin_type>)? ( $origin $( , $arg )* ) verify { } $( $rest )* } }; // add verify block to {} format ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: $eval:block + $name:ident { $( $code:tt )* }: $(<$origin_type:ty>)? $eval:block $( $rest:tt )* ) => { $crate::benchmarks_iter!( + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) - $name { $( $code )* }: $eval + $name { $( $code )* }: $(<$origin_type>)? $eval verify { } $( $rest )* ); }; } +#[macro_export] +#[doc(hidden)] +macro_rules! to_origin { + ($origin:expr) => { + $origin.into() + }; + ($origin:expr, $origin_type:ty) => { + <::RuntimeOrigin as From<$origin_type>>::from($origin) + }; +} + #[macro_export] #[doc(hidden)] macro_rules! benchmark_backend { @@ -678,6 +811,107 @@ macro_rules! benchmark_backend { }; } +// Creates #[test] functions for the given bench cases. +#[macro_export] +#[doc(hidden)] +macro_rules! impl_bench_case_tests { + ( + { $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr } + { $( $names_extra:tt )* } + $( { $( $bench_inst:ident )? } $bench:ident )* + ) + => { + $crate::impl_bench_name_tests!( + $module, $new_test_exec, $exec_name, $test, $extra, + { $( $names_extra )* }, + $( { $bench } )+ + ); + } +} + +// Creates a #[test] function for the given bench name. +#[macro_export] +#[doc(hidden)] +macro_rules! impl_bench_name_tests { + // recursion anchor + ( + $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr, + { $( $names_extra:tt )* }, + { $name:ident } + ) => { + $crate::paste::paste! { + #[test] + fn [] () { + $new_test_exec.$exec_name(|| { + // Skip all #[extra] benchmarks if $extra is false. + if !($extra) { + let disabled = $crate::vec![ $( stringify!($names_extra).as_ref() ),* ]; + if disabled.contains(&stringify!($name)) { + $crate::log::error!( + "INFO: extra benchmark skipped - {}", + stringify!($name), + ); + return (); + } + } + + // Same per-case logic as when all cases are run in the + // same function. + match std::panic::catch_unwind(|| { + $module::<$test>::[< test_benchmark_ $name >] () + }) { + Err(err) => { + panic!("{}: {:?}", stringify!($name), err); + }, + Ok(Err(err)) => { + match err { + $crate::BenchmarkError::Stop(err) => { + panic!("{}: {:?}", stringify!($name), err); + }, + $crate::BenchmarkError::Override(_) => { + // This is still considered a success condition. + $crate::log::error!( + "WARNING: benchmark error overrided - {}", + stringify!($name), + ); + }, + $crate::BenchmarkError::Skip => { + // This is considered a success condition. + $crate::log::error!( + "WARNING: benchmark error skipped - {}", + stringify!($name), + ); + }, + $crate::BenchmarkError::Weightless => { + // This is considered a success condition. + $crate::log::error!( + "WARNING: benchmark weightless skipped - {}", + stringify!($name), + ); + } + } + }, + Ok(Ok(())) => (), + } + }); + } + } + }; + // recursion tail + ( + $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr, + { $( $names_extra:tt )* }, + { $name:ident } $( { $rest:ident } )+ + ) => { + // car + $crate::impl_bench_name_tests!($module, $new_test_exec, $exec_name, $test, $extra, + { $( $names_extra )* }, { $name }); + // cdr + $crate::impl_bench_name_tests!($module, $new_test_exec, $exec_name, $test, $extra, + { $( $names_extra )* }, $( { $rest } )+); + }; +} + // Creates a `SelectedBenchmark` enum implementing `BenchmarkingSetup`. // // Every variant must implement [`BenchmarkingSetup`]. @@ -748,6 +982,8 @@ macro_rules! impl_benchmark { ( $( $name_extra:ident ),* ) ( $( $name_skip_meta:ident ),* ) ) => { + // We only need to implement benchmarks for the runtime-benchmarks feature or testing. + #[cfg(any(feature = "runtime-benchmarks", test))] impl, $instance: $instance_bound )? > $crate::Benchmarking for Pallet where T: frame_system::Config, $( $where_clause )* @@ -796,12 +1032,21 @@ macro_rules! impl_benchmark { $crate::whitelisted_caller::() ); whitelist.push(whitelisted_caller_key.into()); + // Whitelist the transactional layer. + let transactional_layer_key = $crate::TrackedStorageKey::new( + $crate::frame_support::storage::transactional::TRANSACTION_LEVEL_KEY.into() + ); + whitelist.push(transactional_layer_key); + $crate::benchmarking::set_whitelist(whitelist); let mut results: $crate::Vec<$crate::BenchmarkResult> = $crate::Vec::new(); // Always do at least one internal repeat... for _ in 0 .. internal_repeats.max(1) { + // Always reset the state after the benchmark. + $crate::defer!($crate::benchmarking::wipe_db()); + // Set up the externalities environment for the setup we want to // benchmark. let closure_to_benchmark = < @@ -823,7 +1068,9 @@ macro_rules! impl_benchmark { // Time the extrinsic logic. $crate::log::trace!( target: "benchmark", - "Start Benchmark: {:?}", c + "Start Benchmark: {} ({:?})", + extrinsic, + c ); let start_pov = $crate::benchmarking::proof_size(); @@ -855,7 +1102,7 @@ macro_rules! impl_benchmark { // Time the storage root recalculation. let start_storage_root = $crate::benchmarking::current_time(); - $crate::storage_root(); + $crate::storage_root($crate::StateVersion::V1); let finish_storage_root = $crate::benchmarking::current_time(); let elapsed_storage_root = finish_storage_root - start_storage_root; @@ -877,9 +1124,6 @@ macro_rules! impl_benchmark { proof_size: diff_pov, keys: read_and_written_keys, }); - - // Wipe the DB back to the genesis state. - $crate::benchmarking::wipe_db(); } return Ok(results); @@ -918,6 +1162,8 @@ macro_rules! impl_benchmark { // This creates a unit test for one benchmark of the main benchmark macro. // It runs the benchmark using the `high` and `low` value for each component // and ensure that everything completes successfully. +// Instances each component with six values which can be controlled with the +// env variable `VALUES_PER_COMPONENT`. #[macro_export] #[doc(hidden)] macro_rules! impl_benchmark_test { @@ -942,6 +1188,9 @@ macro_rules! impl_benchmark_test { let execute_benchmark = | c: $crate::Vec<($crate::BenchmarkParameter, u32)> | -> Result<(), $crate::BenchmarkError> { + // Always reset the state after the benchmark. + $crate::defer!($crate::benchmarking::wipe_db()); + // Set up the benchmark, return execution + verification function. let closure_to_verify = < SelectedBenchmark as $crate::BenchmarkingSetup @@ -953,27 +1202,48 @@ macro_rules! impl_benchmark_test { } // Run execution + verification - closure_to_verify()?; - - // Reset the state - $crate::benchmarking::wipe_db(); - - Ok(()) + closure_to_verify() }; if components.is_empty() { execute_benchmark(Default::default())?; } else { - for (name, low, high) in components.iter() { - // Test only the low and high value, assuming values in the middle - // won't break - for component_value in $crate::vec![low, high] { + let num_values: u32 = if let Ok(ev) = std::env::var("VALUES_PER_COMPONENT") { + ev.parse().map_err(|_| { + $crate::BenchmarkError::Stop( + "Could not parse env var `VALUES_PER_COMPONENT` as u32." + ) + })? + } else { + 6 + }; + + if num_values < 2 { + return Err("`VALUES_PER_COMPONENT` must be at least 2".into()); + } + + for (name, low, high) in components.clone().into_iter() { + // Test the lowest, highest (if its different from the lowest) + // and up to num_values-2 more equidistant values in between. + // For 0..10 and num_values=6 this would mean: [0, 2, 4, 6, 8, 10] + + let mut values = $crate::vec![low]; + let diff = (high - low).min(num_values - 1); + let slope = (high - low) as f32 / diff as f32; + + for i in 1..=diff { + let value = ((low as f32 + slope * i as f32) as u32) + .clamp(low, high); + values.push(value); + } + + for component_value in values { // Select the max value for all the other components. let c: $crate::Vec<($crate::BenchmarkParameter, u32)> = components .iter() .map(|(n, _, h)| - if n == name { - (*n, *component_value) + if *n == name { + (*n, component_value) } else { (*n, *h) } @@ -993,7 +1263,7 @@ macro_rules! impl_benchmark_test { /// This creates a test suite which runs the module's benchmarks. /// -/// When called in `pallet_example` as +/// When called in `pallet_example_basic` as /// /// ```rust,ignore /// impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); @@ -1013,13 +1283,54 @@ macro_rules! impl_benchmark_test { /// new_test_ext().execute_with(|| { /// assert_ok!(test_benchmark_accumulate_dummy::()); /// assert_ok!(test_benchmark_set_dummy::()); -/// assert_ok!(test_benchmark_another_set_dummy::()); /// assert_ok!(test_benchmark_sort_vector::()); /// }); /// } /// } /// ``` /// +/// When called inside the `benchmarks` macro of the `pallet_example_basic` as +/// +/// ```rust,ignore +/// benchmarks! { +/// // Benchmarks omitted for brevity +/// +/// impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); +/// } +/// ``` +/// +/// It expands to the equivalent of: +/// +/// ```rust,ignore +/// #[cfg(test)] +/// mod benchmarking { +/// use super::*; +/// use crate::tests::{new_test_ext, Test}; +/// use frame_support::assert_ok; +/// +/// #[test] +/// fn bench_accumulate_dummy() { +/// new_test_ext().execute_with(|| { +/// assert_ok!(test_benchmark_accumulate_dummy::()); +/// }) +/// } +/// +/// #[test] +/// fn bench_set_dummy() { +/// new_test_ext().execute_with(|| { +/// assert_ok!(test_benchmark_set_dummy::()); +/// }) +/// } +/// +/// #[test] +/// fn bench_sort_vector() { +/// new_test_ext().execute_with(|| { +/// assert_ok!(test_benchmark_sort_vector::()); +/// }) +/// } +/// } +/// ``` +/// /// ## Arguments /// /// The first argument, `module`, must be the path to this crate's module. @@ -1092,16 +1403,50 @@ macro_rules! impl_benchmark_test { // just iterate over the `Benchmarking::benchmarks` list to run the actual implementations. #[macro_export] macro_rules! impl_benchmark_test_suite { + ( + $bench_module:ident, + $new_test_ext:expr, + $test:path + $(, $( $rest:tt )* )? + ) => { + $crate::impl_test_function!( + () + () + () + $bench_module, + $new_test_ext, + $test + $(, $( $rest )* )? + ); + } +} + +// Takes all arguments from `impl_benchmark_test_suite` and three additional arguments. +// +// Can be configured to generate one #[test] fn per bench case or +// one #[test] fn for all bench cases. +// This depends on whether or not the first argument contains a non-empty list of bench names. +#[macro_export] +#[doc(hidden)] +macro_rules! impl_test_function { // user might or might not have set some keyword arguments; set the defaults // // The weird syntax indicates that `rest` comes only after a comma, which is otherwise optional ( + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + $bench_module:ident, $new_test_ext:expr, $test:path $(, $( $rest:tt )* )? ) => { - $crate::impl_benchmark_test_suite!( + $crate::impl_test_function!( + @cases: + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) @selected: $bench_module, $new_test_ext, @@ -1115,6 +1460,10 @@ macro_rules! impl_benchmark_test_suite { }; // pick off the benchmarks_path keyword argument ( + @cases: + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) @selected: $bench_module:ident, $new_test_ext:expr, @@ -1126,7 +1475,11 @@ macro_rules! impl_benchmark_test_suite { benchmarks_path = $benchmarks_path:ident $(, $( $rest:tt )* )? ) => { - $crate::impl_benchmark_test_suite!( + $crate::impl_test_function!( + @cases: + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) @selected: $bench_module, $new_test_ext, @@ -1140,6 +1493,10 @@ macro_rules! impl_benchmark_test_suite { }; // pick off the extra keyword argument ( + @cases: + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) @selected: $bench_module:ident, $new_test_ext:expr, @@ -1151,7 +1508,11 @@ macro_rules! impl_benchmark_test_suite { extra = $extra:expr $(, $( $rest:tt )* )? ) => { - $crate::impl_benchmark_test_suite!( + $crate::impl_test_function!( + @cases: + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) @selected: $bench_module, $new_test_ext, @@ -1165,6 +1526,10 @@ macro_rules! impl_benchmark_test_suite { }; // pick off the exec_name keyword argument ( + @cases: + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) @selected: $bench_module:ident, $new_test_ext:expr, @@ -1176,7 +1541,11 @@ macro_rules! impl_benchmark_test_suite { exec_name = $exec_name:ident $(, $( $rest:tt )* )? ) => { - $crate::impl_benchmark_test_suite!( + $crate::impl_test_function!( + @cases: + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) @selected: $bench_module, $new_test_ext, @@ -1188,8 +1557,34 @@ macro_rules! impl_benchmark_test_suite { $( $( $rest )* )? ); }; - // all options set; nothing else in user-provided keyword arguments + // iteration-exit arm which generates a #[test] function for each case. ( + @cases: + ( $( $names:tt )+ ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + @selected: + $bench_module:ident, + $new_test_ext:expr, + $test:path, + benchmarks_path = $path_to_benchmarks_invocation:ident, + extra = $extra:expr, + exec_name = $exec_name:ident, + @user: + $(,)? + ) => { + $crate::impl_bench_case_tests!( + { $bench_module, $new_test_ext, $exec_name, $test, $extra } + { $( $names_extra:tt )* } + $($names)+ + ); + }; + // iteration-exit arm which generates one #[test] function for all cases. + ( + @cases: + () + () + () @selected: $bench_module:ident, $new_test_ext:expr, @@ -1252,6 +1647,14 @@ macro_rules! impl_benchmark_test_suite { .expect("benchmark name is always a valid string!"), ); } + $crate::BenchmarkError::Weightless => { + // This is considered a success condition. + $crate::log::error!( + "WARNING: benchmark weightless skipped - {}", + $crate::str::from_utf8(benchmark_name) + .expect("benchmark name is always a valid string!"), + ); + } } }, Ok(Ok(())) => (), @@ -1307,13 +1710,13 @@ pub fn show_benchmark_debug_info( /// use frame_benchmarking::TrackedStorageKey; /// let whitelist: Vec = vec![ /// // Block Number -/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), +/// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac"), /// // Total Issuance -/// hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), +/// array_bytes::hex_into_unchecked("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80"), /// // Execution Phase -/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), +/// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a"), /// // Event Count -/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), +/// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850"), /// ]; /// ``` /// @@ -1351,7 +1754,6 @@ pub fn show_benchmark_debug_info( /// type Council2 = TechnicalCommittee; /// add_benchmark!(params, batches, pallet_collective, Council2); // pallet_collective_council_2.rs /// ``` - #[macro_export] macro_rules! add_benchmark { ( $params:ident, $batches:ident, $name:path, $( $location:tt )* ) => ( @@ -1405,6 +1807,17 @@ macro_rules! add_benchmark { .expect("benchmark name is always a valid string!") ); None + }, + Err($crate::BenchmarkError::Weightless) => { + $crate::log::error!( + "WARNING: benchmark weightless skipped - {}", + $crate::str::from_utf8(benchmark) + .expect("benchmark name is always a valid string!") + ); + Some(vec![$crate::BenchmarkResult { + components: selected_components.clone(), + .. Default::default() + }]) } }; @@ -1420,6 +1833,20 @@ macro_rules! add_benchmark { ) } +/// Callback for `define_benchmarks` to call `add_benchmark`. +#[macro_export] +macro_rules! cb_add_benchmarks { + // anchor + ( $params:ident, $batches:ident, [ $name:path, $( $location:tt )* ] ) => { + add_benchmark!( $params, $batches, $name, $( $location )* ); + }; + // recursion tail + ( $params:ident, $batches:ident, [ $name:path, $( $location:tt )* ] $([ $names:path, $( $locations:tt )* ])+ ) => { + cb_add_benchmarks!( $params, $batches, [ $name, $( $location )* ] ); + cb_add_benchmarks!( $params, $batches, $([ $names, $( $locations )* ])+ ); + } +} + /// This macro allows users to easily generate a list of benchmarks for the pallets configured /// in the runtime. /// @@ -1439,7 +1866,6 @@ macro_rules! add_benchmark { /// ``` /// /// This should match what exists with the `add_benchmark!` macro. - #[macro_export] macro_rules! list_benchmark { ( $list:ident, $extra:ident, $name:path, $( $location:tt )* ) => ( @@ -1454,3 +1880,54 @@ macro_rules! list_benchmark { $list.push(pallet_benchmarks) ) } + +/// Callback for `define_benchmarks` to call `list_benchmark`. +#[macro_export] +macro_rules! cb_list_benchmarks { + // anchor + ( $list:ident, $extra:ident, [ $name:path, $( $location:tt )* ] ) => { + list_benchmark!( $list, $extra, $name, $( $location )* ); + }; + // recursion tail + ( $list:ident, $extra:ident, [ $name:path, $( $location:tt )* ] $([ $names:path, $( $locations:tt )* ])+ ) => { + cb_list_benchmarks!( $list, $extra, [ $name, $( $location )* ] ); + cb_list_benchmarks!( $list, $extra, $([ $names, $( $locations )* ])+ ); + } +} + +/// Defines pallet configs that `add_benchmarks` and `list_benchmarks` use. +/// Should be preferred instead of having a repetitive list of configs +/// in `add_benchmark` and `list_benchmark`. + +#[macro_export] +macro_rules! define_benchmarks { + ( $([ $names:path, $( $locations:tt )* ])* ) => { + /// Calls `list_benchmark` with all configs from `define_benchmarks` + /// and passes the first two parameters on. + /// + /// Use as: + /// ```ignore + /// list_benchmarks!(list, extra); + /// ``` + #[macro_export] + macro_rules! list_benchmarks { + ( $list:ident, $extra:ident ) => { + cb_list_benchmarks!( $list, $extra, $([ $names, $( $locations )* ])+ ); + } + } + + /// Calls `add_benchmark` with all configs from `define_benchmarks` + /// and passes the first two parameters on. + /// + /// Use as: + /// ```ignore + /// add_benchmarks!(params, batches); + /// ``` + #[macro_export] + macro_rules! add_benchmarks { + ( $params:ident, $batches:ident ) => { + cb_add_benchmarks!( $params, $batches, $([ $names, $( $locations )* ])+ ); + } + } + } +} diff --git a/frame/benchmarking/src/tests.rs b/frame/benchmarking/src/tests.rs index a2cf381e6ecf8..1499f9c182fce 100644 --- a/frame/benchmarking/src/tests.rs +++ b/frame/benchmarking/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,13 +20,14 @@ #![cfg(test)] use super::*; -use frame_support::parameter_types; +use frame_support::{parameter_types, traits::ConstU32}; use sp_runtime::{ testing::{Header, H256}, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; use sp_std::prelude::*; +use std::cell::RefCell; #[frame_support::pallet] mod pallet_test { @@ -35,7 +36,7 @@ mod pallet_test { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); + pub struct Pallet(PhantomData); #[pallet::config] pub trait Config: frame_system::Config { @@ -45,24 +46,27 @@ mod pallet_test { } #[pallet::storage] - #[pallet::getter(fn heartbeat_after)] + #[pallet::getter(fn value)] pub(crate) type Value = StorageValue<_, u32, OptionQuery>; #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(0)] pub fn set_value(origin: OriginFor, n: u32) -> DispatchResult { - let _sender = frame_system::ensure_signed(origin)?; + let _sender = ensure_signed(origin)?; Value::::put(n); Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(0)] pub fn dummy(origin: OriginFor, _n: u32) -> DispatchResult { - let _sender = frame_system::ensure_none(origin)?; + let _sender = ensure_none(origin)?; Ok(()) } + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn always_error(_origin: OriginFor) -> DispatchResult { return Err("I always fail".into()) @@ -89,16 +93,16 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); type PalletInfo = PalletInfo; @@ -108,17 +112,16 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { - pub const LowerBound: u32 = 1; - pub const UpperBound: u32 = 100; pub const MaybeItem: Option = None; } impl pallet_test::Config for Test { - type LowerBound = LowerBound; - type UpperBound = UpperBound; + type LowerBound = ConstU32<1>; + type UpperBound = ConstU32<100>; type MaybeItem = MaybeItem; } @@ -126,11 +129,20 @@ fn new_test_ext() -> sp_io::TestExternalities { GenesisConfig::default().build_storage().unwrap().into() } +thread_local! { + /// Tracks the used components per value. Needs to be a thread local since the + /// benchmarking clears the storage after each run. + static VALUES_PER_COMPONENT: RefCell> = RefCell::new(vec![]); +} + +// NOTE: This attribute is only needed for the `modify_in_` functions. +#[allow(unreachable_code)] mod benchmarks { - use super::{new_test_ext, pallet_test::Value, Test}; + use super::{new_test_ext, pallet_test::Value, Test, VALUES_PER_COMPONENT}; use crate::{account, BenchmarkError, BenchmarkParameter, BenchmarkResult, BenchmarkingSetup}; use frame_support::{assert_err, assert_ok, ensure, traits::Get}; use frame_system::RawOrigin; + use rusty_fork::rusty_fork_test; use sp_std::prelude::*; // Additional used internally by the benchmark macro. @@ -139,7 +151,7 @@ mod benchmarks { crate::benchmarks! { where_clause { where - crate::tests::Origin: From::AccountId>>, + crate::tests::RuntimeOrigin: From::AccountId>>, } set_value { @@ -228,6 +240,31 @@ mod benchmarks { // This should never be reached. assert!(value > 100); } + + modify_in_setup_then_error { + Value::::set(Some(123)); + return Err(BenchmarkError::Stop("Should error")); + }: { } + + modify_in_call_then_error { + }: { + Value::::set(Some(123)); + return Err(BenchmarkError::Stop("Should error")); + } + + modify_in_verify_then_error { + }: { + } verify { + Value::::set(Some(123)); + return Err(BenchmarkError::Stop("Should error")); + } + + // Stores all component values in the thread-local storage. + values_per_component { + let n in 0 .. 10; + }: { + VALUES_PER_COMPONENT.with(|v| v.borrow_mut().push(n)); + } } #[test] @@ -351,4 +388,70 @@ mod benchmarks { assert_eq!(Pallet::::test_benchmark_skip_benchmark(), Err(BenchmarkError::Skip),); }); } + + /// An error return of a benchmark test function still causes the db to be wiped. + #[test] + fn benchmark_error_wipes_storage() { + new_test_ext().execute_with(|| { + // It resets when the error happens in the setup: + assert_err!( + Pallet::::test_benchmark_modify_in_setup_then_error(), + "Should error" + ); + assert_eq!(Value::::get(), None); + + // It resets when the error happens in the call: + assert_err!(Pallet::::test_benchmark_modify_in_call_then_error(), "Should error"); + assert_eq!(Value::::get(), None); + + // It resets when the error happens in the verify: + assert_err!( + Pallet::::test_benchmark_modify_in_verify_then_error(), + "Should error" + ); + assert_eq!(Value::::get(), None); + }); + } + + rusty_fork_test! { + /// Test that the benchmarking uses the correct values for each component and + /// that the number of components can be controlled with `VALUES_PER_COMPONENT`. + /// + /// NOTE: This test needs to run in its own process, since it + /// otherwise messes up the env variable for the other tests. + #[test] + fn test_values_per_component() { + let tests = vec![ + (Some("1"), Err("`VALUES_PER_COMPONENT` must be at least 2".into())), + (Some("asdf"), Err("Could not parse env var `VALUES_PER_COMPONENT` as u32.".into())), + (None, Ok(vec![0, 2, 4, 6, 8, 10])), + (Some("2"), Ok(vec![0, 10])), + (Some("4"), Ok(vec![0, 3, 6, 10])), + (Some("6"), Ok(vec![0, 2, 4, 6, 8, 10])), + (Some("10"), Ok(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 10])), + (Some("11"), Ok(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])), + (Some("99"), Ok(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])), + ]; + + for (num, expected) in tests { + run_test_values_per_component(num, expected); + } + } + } + + /// Helper for [`test_values_per_component`]. + fn run_test_values_per_component(num: Option<&str>, output: Result, BenchmarkError>) { + VALUES_PER_COMPONENT.with(|v| v.borrow_mut().clear()); + match num { + Some(n) => std::env::set_var("VALUES_PER_COMPONENT", n), + None => std::env::remove_var("VALUES_PER_COMPONENT"), + } + + new_test_ext().execute_with(|| { + let got = Pallet::::test_benchmark_values_per_component() + .map(|_| VALUES_PER_COMPONENT.with(|v| v.borrow().clone())); + + assert_eq!(got, output); + }); + } } diff --git a/frame/benchmarking/src/tests_instance.rs b/frame/benchmarking/src/tests_instance.rs index caccebd39c70b..ecc0a78a199b9 100644 --- a/frame/benchmarking/src/tests_instance.rs +++ b/frame/benchmarking/src/tests_instance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(test)] use super::*; -use frame_support::parameter_types; +use frame_support::traits::ConstU32; use sp_runtime::{ testing::{Header, H256}, traits::{BlakeTwo256, IdentityLookup}, @@ -28,48 +28,54 @@ use sp_runtime::{ }; use sp_std::prelude::*; +#[frame_support::pallet] mod pallet_test { - use frame_support::pallet_prelude::Get; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; - frame_support::decl_storage! { - trait Store for Module, I: Instance = DefaultInstance> as Test where - ::OtherEvent: Into<>::Event> - { - pub Value get(fn value): Option; - } - } - - frame_support::decl_module! { - pub struct Module, I: Instance = DefaultInstance> for enum Call where - origin: T::Origin, ::OtherEvent: Into<>::Event> - { - #[weight = 0] - fn set_value(origin, n: u32) -> frame_support::dispatch::DispatchResult { - let _sender = frame_system::ensure_signed(origin)?; - Value::::put(n); - Ok(()) - } - - #[weight = 0] - fn dummy(origin, _n: u32) -> frame_support::dispatch::DispatchResult { - let _sender = frame_system::ensure_none(origin)?; - Ok(()) - } - } - } + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); pub trait OtherConfig { type OtherEvent; } - pub trait Config: frame_system::Config + OtherConfig - where - Self::OtherEvent: Into<>::Event>, - { - type Event; + #[pallet::config] + pub trait Config: frame_system::Config + OtherConfig { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; type LowerBound: Get; type UpperBound: Get; } + + #[pallet::storage] + #[pallet::getter(fn value)] + pub(crate) type Value, I: 'static = ()> = StorageValue<_, u32, OptionQuery>; + + #[pallet::event] + pub enum Event, I: 'static = ()> {} + + #[pallet::call] + impl, I: 'static> Pallet + where + ::OtherEvent: Into<>::RuntimeEvent>, + { + #[pallet::call_index(0)] + #[pallet::weight(0)] + pub fn set_value(origin: OriginFor, n: u32) -> DispatchResult { + let _sender = ensure_signed(origin)?; + Value::::put(n); + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(0)] + pub fn dummy(origin: OriginFor, _n: u32) -> DispatchResult { + let _sender = ensure_none(origin)?; + Ok(()) + } + } } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -82,7 +88,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - TestPallet: pallet_test::{Pallet, Call, Storage}, + TestPallet: pallet_test::{Pallet, Call, Storage, Event}, } ); @@ -90,18 +96,18 @@ impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); - type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); + type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -110,21 +116,17 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); -} - -parameter_types! { - pub const LowerBound: u32 = 1; - pub const UpperBound: u32 = 100; + type MaxConsumers = ConstU32<16>; } impl pallet_test::Config for Test { - type Event = Event; - type LowerBound = LowerBound; - type UpperBound = UpperBound; + type RuntimeEvent = RuntimeEvent; + type LowerBound = ConstU32<1>; + type UpperBound = ConstU32<100>; } impl pallet_test::OtherConfig for Test { - type OtherEvent = Event; + type OtherEvent = RuntimeEvent; } fn new_test_ext() -> sp_io::TestExternalities { @@ -134,20 +136,19 @@ fn new_test_ext() -> sp_io::TestExternalities { mod benchmarks { use super::pallet_test::{self, Value}; use crate::account; - use frame_support::{ensure, StorageValue}; + use frame_support::ensure; use frame_system::RawOrigin; use sp_std::prelude::*; // Additional used internally by the benchmark macro. use super::pallet_test::{Call, Config, Pallet}; - use frame_support::traits::Instance; - crate::benchmarks_instance! { + crate::benchmarks_instance_pallet! { where_clause { where ::OtherEvent: Clone - + Into<>::Event>, - >::Event: Clone, + + Into<>::RuntimeEvent>, + >::RuntimeEvent: Clone, } set_value { @@ -155,7 +156,7 @@ mod benchmarks { let caller = account::("caller", 0, 0); }: _ (RawOrigin::Signed(caller), b.into()) verify { - assert_eq!(Value::::get(), Some(b)); + assert_eq!(Value::::get(), Some(b)); } other_name { @@ -173,11 +174,11 @@ mod benchmarks { } verify { ensure!(m[0] == 0, "You forgot to sort!") } - } - crate::impl_benchmark_test_suite!( - Pallet, - crate::tests_instance::new_test_ext(), - crate::tests_instance::Test - ); + impl_benchmark_test_suite!( + Pallet, + crate::tests_instance::new_test_ext(), + crate::tests_instance::Test + ) + } } diff --git a/frame/benchmarking/src/utils.rs b/frame/benchmarking/src/utils.rs index 158f5c5b57573..654b1c34c0658 100644 --- a/frame/benchmarking/src/utils.rs +++ b/frame/benchmarking/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,18 +16,21 @@ // limitations under the License. //! Interfaces, types and utils for benchmarking a FRAME runtime. - use codec::{Decode, Encode}; use frame_support::{ dispatch::{DispatchError, DispatchErrorWithPostInfo}, pallet_prelude::*, traits::StorageInfo, }; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; use sp_io::hashing::blake2_256; +use sp_runtime::traits::TrailingZeroInput; use sp_std::{prelude::Box, vec::Vec}; use sp_storage::TrackedStorageKey; /// An alphabet of possible parameters to use for benchmarking. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Encode, Decode, Clone, Copy, PartialEq, Debug)] #[allow(missing_docs)] #[allow(non_camel_case_types)] @@ -68,13 +71,17 @@ impl std::fmt::Display for BenchmarkParameter { } /// The results of a single of benchmark. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Encode, Decode, Clone, PartialEq, Debug)] pub struct BenchmarkBatch { /// The pallet containing this benchmark. + #[cfg_attr(feature = "std", serde(with = "serde_as_str"))] pub pallet: Vec, /// The instance of this pallet being benchmarked. + #[cfg_attr(feature = "std", serde(with = "serde_as_str"))] pub instance: Vec, /// The extrinsic (or benchmark name) of this benchmark. + #[cfg_attr(feature = "std", serde(with = "serde_as_str"))] pub benchmark: Vec, /// The results from this benchmark. pub results: Vec, @@ -82,13 +89,17 @@ pub struct BenchmarkBatch { // TODO: could probably make API cleaner here. /// The results of a single of benchmark, where time and db results are separated. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Encode, Decode, Clone, PartialEq, Debug)] pub struct BenchmarkBatchSplitResults { /// The pallet containing this benchmark. + #[cfg_attr(feature = "std", serde(with = "serde_as_str"))] pub pallet: Vec, /// The instance of this pallet being benchmarked. + #[cfg_attr(feature = "std", serde(with = "serde_as_str"))] pub instance: Vec, /// The extrinsic (or benchmark name) of this benchmark. + #[cfg_attr(feature = "std", serde(with = "serde_as_str"))] pub benchmark: Vec, /// The extrinsic timing results from this benchmark. pub time_results: Vec, @@ -99,6 +110,7 @@ pub struct BenchmarkBatchSplitResults { /// Result from running benchmarks on a FRAME pallet. /// Contains duration of the function call in nanoseconds along with the benchmark parameters /// used for that benchmark result. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Encode, Decode, Default, Clone, PartialEq, Debug)] pub struct BenchmarkResult { pub components: Vec<(BenchmarkParameter, u32)>, @@ -109,12 +121,33 @@ pub struct BenchmarkResult { pub writes: u32, pub repeat_writes: u32, pub proof_size: u32, + #[cfg_attr(feature = "std", serde(skip))] pub keys: Vec<(Vec, u32, u32, bool)>, } impl BenchmarkResult { pub fn from_weight(w: Weight) -> Self { - Self { extrinsic_time: (w as u128) / 1_000, ..Default::default() } + Self { extrinsic_time: (w.ref_time() / 1_000) as u128, ..Default::default() } + } +} + +/// Helper module to make serde serialize `Vec` as strings. +#[cfg(feature = "std")] +mod serde_as_str { + pub fn serialize(value: &Vec, serializer: S) -> Result + where + S: serde::Serializer, + { + let s = std::str::from_utf8(value).map_err(serde::ser::Error::custom)?; + serializer.collect_str(s) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::de::Deserializer<'de>, + { + let s: &str = serde::de::Deserialize::deserialize(deserializer)?; + Ok(s.into()) } } @@ -129,6 +162,11 @@ pub enum BenchmarkError { /// The benchmarking pipeline is allowed to fail here, and we should simply /// skip processing these results. Skip, + /// No weight can be determined; set the weight of this call to zero. + /// + /// You can also use `Override` instead, but this is easier to use since `Override` expects the + /// correct components to be present. + Weightless, } impl From for &'static str { @@ -137,6 +175,7 @@ impl From for &'static str { BenchmarkError::Stop(s) => s, BenchmarkError::Override(_) => "benchmark override", BenchmarkError::Skip => "benchmark skip", + BenchmarkError::Weightless => "benchmark weightless", } } } @@ -322,17 +361,14 @@ pub trait BenchmarkingSetup { } /// Grab an account, seeded by a name and index. -pub fn account( - name: &'static str, - index: u32, - seed: u32, -) -> AccountId { +pub fn account(name: &'static str, index: u32, seed: u32) -> AccountId { let entropy = (name, index, seed).using_encoded(blake2_256); - AccountId::decode(&mut &entropy[..]).unwrap_or_default() + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") } /// This caller account is automatically whitelisted for DB reads/writes by the benchmarking macro. -pub fn whitelisted_caller() -> AccountId { +pub fn whitelisted_caller() -> AccountId { account::("whitelisted_caller", 0, 0) } diff --git a/frame/benchmarking/src/weights.rs b/frame/benchmarking/src/weights.rs new file mode 100644 index 0000000000000..5e5a2e7ee343c --- /dev/null +++ b/frame/benchmarking/src/weights.rs @@ -0,0 +1,167 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 frame_benchmarking +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=frame_benchmarking +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/benchmarking/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for frame_benchmarking. +pub trait WeightInfo { + fn addition(i: u32, ) -> Weight; + fn subtraction(i: u32, ) -> Weight; + fn multiplication(i: u32, ) -> Weight; + fn division(i: u32, ) -> Weight; + fn hashing(i: u32, ) -> Weight; + fn sr25519_verification(i: u32, ) -> Weight; + fn storage_read(i: u32, ) -> Weight; + fn storage_write(i: u32, ) -> Weight; +} + +/// Weights for frame_benchmarking using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// The range of component `i` is `[0, 1000000]`. + fn addition(_i: u32, ) -> Weight { + // Minimum execution time: 108 nanoseconds. + Weight::from_ref_time(137_610 as u64) + } + /// The range of component `i` is `[0, 1000000]`. + fn subtraction(_i: u32, ) -> Weight { + // Minimum execution time: 104 nanoseconds. + Weight::from_ref_time(133_508 as u64) + } + /// The range of component `i` is `[0, 1000000]`. + fn multiplication(_i: u32, ) -> Weight { + // Minimum execution time: 110 nanoseconds. + Weight::from_ref_time(140_230 as u64) + } + /// The range of component `i` is `[0, 1000000]`. + fn division(_i: u32, ) -> Weight { + // Minimum execution time: 96 nanoseconds. + Weight::from_ref_time(136_059 as u64) + } + /// The range of component `i` is `[0, 100]`. + fn hashing(_i: u32, ) -> Weight { + // Minimum execution time: 21_804_747 nanoseconds. + Weight::from_ref_time(22_013_681_386 as u64) + } + /// The range of component `i` is `[0, 100]`. + fn sr25519_verification(i: u32, ) -> Weight { + // Minimum execution time: 136 nanoseconds. + Weight::from_ref_time(156_000 as u64) + // Standard Error: 4_531 + .saturating_add(Weight::from_ref_time(46_817_640 as u64).saturating_mul(i as u64)) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[0, 1000]`. + fn storage_read(i: u32, ) -> Weight { + // Minimum execution time: 125 nanoseconds. + Weight::from_ref_time(135_000 as u64) + // Standard Error: 3_651 + .saturating_add(Weight::from_ref_time(2_021_172 as u64).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[0, 1000]`. + fn storage_write(i: u32, ) -> Weight { + // Minimum execution time: 120 nanoseconds. + Weight::from_ref_time(131_000 as u64) + // Standard Error: 348 + .saturating_add(Weight::from_ref_time(377_243 as u64).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// The range of component `i` is `[0, 1000000]`. + fn addition(_i: u32, ) -> Weight { + // Minimum execution time: 108 nanoseconds. + Weight::from_ref_time(137_610 as u64) + } + /// The range of component `i` is `[0, 1000000]`. + fn subtraction(_i: u32, ) -> Weight { + // Minimum execution time: 104 nanoseconds. + Weight::from_ref_time(133_508 as u64) + } + /// The range of component `i` is `[0, 1000000]`. + fn multiplication(_i: u32, ) -> Weight { + // Minimum execution time: 110 nanoseconds. + Weight::from_ref_time(140_230 as u64) + } + /// The range of component `i` is `[0, 1000000]`. + fn division(_i: u32, ) -> Weight { + // Minimum execution time: 96 nanoseconds. + Weight::from_ref_time(136_059 as u64) + } + /// The range of component `i` is `[0, 100]`. + fn hashing(_i: u32, ) -> Weight { + // Minimum execution time: 21_804_747 nanoseconds. + Weight::from_ref_time(22_013_681_386 as u64) + } + /// The range of component `i` is `[0, 100]`. + fn sr25519_verification(i: u32, ) -> Weight { + // Minimum execution time: 136 nanoseconds. + Weight::from_ref_time(156_000 as u64) + // Standard Error: 4_531 + .saturating_add(Weight::from_ref_time(46_817_640 as u64).saturating_mul(i as u64)) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[0, 1000]`. + fn storage_read(i: u32, ) -> Weight { + // Minimum execution time: 125 nanoseconds. + Weight::from_ref_time(135_000 as u64) + // Standard Error: 3_651 + .saturating_add(Weight::from_ref_time(2_021_172 as u64).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[0, 1000]`. + fn storage_write(i: u32, ) -> Weight { + // Minimum execution time: 120 nanoseconds. + Weight::from_ref_time(131_000 as u64) + // Standard Error: 348 + .saturating_add(Weight::from_ref_time(377_243 as u64).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + } +} diff --git a/frame/bounties/Cargo.toml b/frame/bounties/Cargo.toml index 3bb184d5b3393..a5411952a385a 100644 --- a/frame/bounties/Cargo.toml +++ b/frame/bounties/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-bounties" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet to manage bounties" readme = "README.md" @@ -13,36 +13,40 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", + "log/std", "pallet-treasury/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/bounties/README.md b/frame/bounties/README.md index bf63fca5f34b2..232334cb1edd6 100644 --- a/frame/bounties/README.md +++ b/frame/bounties/README.md @@ -2,28 +2,38 @@ ## Bounty -**Note :: This pallet is tightly coupled with pallet-treasury** +> NOTE: This pallet is tightly coupled with pallet-treasury. -A Bounty Spending is a reward for a specified body of work - or specified set of objectives - that -needs to be executed for a predefined Treasury amount to be paid out. A curator is assigned after -the bounty is approved and funded by Council, to be delegated with the responsibility of assigning a -payout address once the specified set of objectives is completed. +A Bounty Spending is a reward for a specified body of work - or specified set of objectives - +that needs to be executed for a predefined Treasury amount to be paid out. A curator is assigned +after the bounty is approved and funded by Council, to be delegated with the responsibility of +assigning a payout address once the specified set of objectives is completed. -After the Council has activated a bounty, it delegates the work that requires expertise to a curator -in exchange of a deposit. Once the curator accepts the bounty, they get to close the active bounty. -Closing the active bounty enacts a delayed payout to the payout address, the curator fee and the -return of the curator deposit. The delay allows for intervention through regular democracy. The -Council gets to unassign the curator, resulting in a new curator election. The Council also gets to -cancel the bounty if deemed necessary before assigning a curator or once the bounty is active or -payout is pending, resulting in the slash of the curator's deposit. +After the Council has activated a bounty, it delegates the work that requires expertise to a +curator in exchange of a deposit. Once the curator accepts the bounty, they get to close the +active bounty. Closing the active bounty enacts a delayed payout to the payout address, the +curator fee and the return of the curator deposit. The delay allows for intervention through +regular democracy. The Council gets to unassign the curator, resulting in a new curator +election. The Council also gets to cancel the bounty if deemed necessary before assigning a +curator or once the bounty is active or payout is pending, resulting in the slash of the +curator's deposit. + +This pallet may opt into using a [`ChildBountyManager`] that enables bounties to be split into +sub-bounties, as children of anh established bounty (called the parent in the context of it's +children). + +> NOTE: The parent bounty cannot be closed if it has a non-zero number of it has active child +> bounties associated with it. ### Terminology -- **Bounty spending proposal:** A proposal to reward a predefined body of work upon completion by - the Treasury. +Bounty: + +- **Bounty spending proposal:** A proposal to reward a predefined body of work upon completion + by the Treasury. - **Proposer:** An account proposing a bounty spending. -- **Curator:** An account managing the bounty and assigning a payout address receiving the reward - for the completion of work. +- **Curator:** An account managing the bounty and assigning a payout address receiving the + reward for the completion of work. - **Deposit:** The amount held on deposit for placing a bounty proposal plus the amount held on deposit per byte within the bounty description. - **Curator deposit:** The payment from a candidate willing to curate an approved bounty. The @@ -31,7 +41,8 @@ payout is pending, resulting in the slash of the curator's deposit. - **Bounty value:** The total amount that should be paid to the Payout Address if the bounty is rewarded. - **Payout address:** The account to which the total or part of the bounty is assigned to. -- **Payout Delay:** The delay period for which a bounty beneficiary needs to wait before claiming. +- **Payout Delay:** The delay period for which a bounty beneficiary needs to wait before + claiming. - **Curator fee:** The reserved upfront payment for a curator for work related to the bounty. ## Interface @@ -39,6 +50,7 @@ payout is pending, resulting in the slash of the curator's deposit. ### Dispatchable Functions Bounty protocol: + - `propose_bounty` - Propose a specific treasury amount to be earmarked for a predefined set of tasks and stake the required deposit. - `approve_bounty` - Accept a specific treasury amount to be earmarked for a predefined body of diff --git a/frame/bounties/src/benchmarking.rs b/frame/bounties/src/benchmarking.rs index 798d929d241f7..adadb3411ac65 100644 --- a/frame/bounties/src/benchmarking.rs +++ b/frame/bounties/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,37 +21,40 @@ use super::*; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -use frame_support::traits::OnInitialize; +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller, BenchmarkError}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; -use crate::Module as Bounties; +use crate::Pallet as Bounties; use pallet_treasury::Pallet as Treasury; const SEED: u32 = 0; // Create bounties that are approved for use in `on_initialize`. -fn create_approved_bounties(n: u32) -> Result<(), &'static str> { +fn create_approved_bounties, I: 'static>(n: u32) -> Result<(), BenchmarkError> { for i in 0..n { - let (caller, _curator, _fee, value, reason) = setup_bounty::(i, MAX_BYTES); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::get() - 1; - Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; + let (caller, _curator, _fee, value, reason) = + setup_bounty::(i, T::MaximumReasonLength::get()); + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; + let approve_origin = + T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + Bounties::::approve_bounty(approve_origin, bounty_id)?; } - ensure!(BountyApprovals::get().len() == n as usize, "Not all bounty approved"); + ensure!(BountyApprovals::::get().len() == n as usize, "Not all bounty approved"); Ok(()) } // Create the pre-requisite information needed to create a treasury `propose_bounty`. -fn setup_bounty( +fn setup_bounty, I: 'static>( u: u32, d: u32, -) -> (T::AccountId, T::AccountId, BalanceOf, BalanceOf, Vec) { +) -> (T::AccountId, T::AccountId, BalanceOf, BalanceOf, Vec) { let caller = account("caller", u, SEED); - let value: BalanceOf = T::BountyValueMinimum::get().saturating_mul(100u32.into()); + let value: BalanceOf = T::BountyValueMinimum::get().saturating_mul(100u32.into()); let fee = value / 2u32.into(); - let deposit = T::BountyDepositBase::get() + T::DataDepositPerByte::get() * MAX_BYTES.into(); + let deposit = T::BountyDepositBase::get() + + T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into(); let _ = T::Currency::make_free_balance_be(&caller, deposit); let curator = account("curator", u, SEED); let _ = T::Currency::make_free_balance_be(&curator, fee / 2u32.into()); @@ -59,100 +62,103 @@ fn setup_bounty( (caller, curator, fee, value, reason) } -fn create_bounty( -) -> Result<(::Source, BountyIndex), &'static str> { - let (caller, curator, fee, value, reason) = setup_bounty::(0, MAX_BYTES); +fn create_bounty, I: 'static>( +) -> Result<(AccountIdLookupOf, BountyIndex), BenchmarkError> { + let (caller, curator, fee, value, reason) = + setup_bounty::(0, T::MaximumReasonLength::get()); let curator_lookup = T::Lookup::unlookup(curator.clone()); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::get() - 1; - Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; - Treasury::::on_initialize(T::BlockNumber::zero()); - Bounties::::propose_curator(RawOrigin::Root.into(), bounty_id, curator_lookup.clone(), fee)?; - Bounties::::accept_curator(RawOrigin::Signed(curator).into(), bounty_id)?; + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; + let approve_origin = + T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; + Treasury::::on_initialize(T::BlockNumber::zero()); + Bounties::::propose_curator(approve_origin, bounty_id, curator_lookup.clone(), fee)?; + Bounties::::accept_curator(RawOrigin::Signed(curator).into(), bounty_id)?; Ok((curator_lookup, bounty_id)) } -fn setup_pot_account() { - let pot_account = Bounties::::account_id(); +fn setup_pot_account, I: 'static>() { + let pot_account = Bounties::::account_id(); let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000u32.into()); let _ = T::Currency::make_free_balance_be(&pot_account, value); } -fn assert_last_event(generic_event: ::Event) { +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } -const MAX_BYTES: u32 = 16384; - -benchmarks! { +benchmarks_instance_pallet! { propose_bounty { - let d in 0 .. MAX_BYTES; + let d in 0 .. T::MaximumReasonLength::get(); - let (caller, curator, fee, value, description) = setup_bounty::(0, d); + let (caller, curator, fee, value, description) = setup_bounty::(0, d); }: _(RawOrigin::Signed(caller), value, description) approve_bounty { - let (caller, curator, fee, value, reason) = setup_bounty::(0, MAX_BYTES); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::get() - 1; - }: _(RawOrigin::Root, bounty_id) + let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; + let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + }: _(approve_origin, bounty_id) propose_curator { - setup_pot_account::(); - let (caller, curator, fee, value, reason) = setup_bounty::(0, MAX_BYTES); - let curator_lookup = T::Lookup::unlookup(curator.clone()); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::get() - 1; - Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; - Bounties::::on_initialize(T::BlockNumber::zero()); - }: _(RawOrigin::Root, bounty_id, curator_lookup, fee) + setup_pot_account::(); + let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); + let curator_lookup = T::Lookup::unlookup(curator); + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; + let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; + Treasury::::on_initialize(T::BlockNumber::zero()); + }: _(approve_origin, bounty_id, curator_lookup, fee) // Worst case when curator is inactive and any sender unassigns the curator. unassign_curator { - setup_pot_account::(); - let (curator_lookup, bounty_id) = create_bounty::()?; - Bounties::::on_initialize(T::BlockNumber::zero()); - let bounty_id = BountyCount::get() - 1; - frame_system::Pallet::::set_block_number(T::BountyUpdatePeriod::get() + 1u32.into()); + setup_pot_account::(); + let (curator_lookup, bounty_id) = create_bounty::()?; + Treasury::::on_initialize(T::BlockNumber::zero()); + let bounty_id = BountyCount::::get() - 1; + frame_system::Pallet::::set_block_number(T::BountyUpdatePeriod::get() + 2u32.into()); let caller = whitelisted_caller(); }: _(RawOrigin::Signed(caller), bounty_id) accept_curator { - setup_pot_account::(); - let (caller, curator, fee, value, reason) = setup_bounty::(0, MAX_BYTES); + setup_pot_account::(); + let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); let curator_lookup = T::Lookup::unlookup(curator.clone()); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::get() - 1; - Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; - Bounties::::on_initialize(T::BlockNumber::zero()); - Bounties::::propose_curator(RawOrigin::Root.into(), bounty_id, curator_lookup, fee)?; + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; + let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; + Treasury::::on_initialize(T::BlockNumber::zero()); + Bounties::::propose_curator(approve_origin, bounty_id, curator_lookup, fee)?; }: _(RawOrigin::Signed(curator), bounty_id) award_bounty { - setup_pot_account::(); - let (curator_lookup, bounty_id) = create_bounty::()?; - Bounties::::on_initialize(T::BlockNumber::zero()); + setup_pot_account::(); + let (curator_lookup, bounty_id) = create_bounty::()?; + Treasury::::on_initialize(T::BlockNumber::zero()); - let bounty_id = BountyCount::get() - 1; + let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; let beneficiary = T::Lookup::unlookup(account("beneficiary", 0, SEED)); }: _(RawOrigin::Signed(curator), bounty_id, beneficiary) claim_bounty { - setup_pot_account::(); - let (curator_lookup, bounty_id) = create_bounty::()?; - Bounties::::on_initialize(T::BlockNumber::zero()); + setup_pot_account::(); + let (curator_lookup, bounty_id) = create_bounty::()?; + Treasury::::on_initialize(T::BlockNumber::zero()); - let bounty_id = BountyCount::get() - 1; + let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; - let beneficiary_account: T::AccountId = account("beneficiary", 0, SEED); let beneficiary = T::Lookup::unlookup(beneficiary_account.clone()); - Bounties::::award_bounty(RawOrigin::Signed(curator.clone()).into(), bounty_id, beneficiary)?; + Bounties::::award_bounty(RawOrigin::Signed(curator.clone()).into(), bounty_id, beneficiary)?; - frame_system::Pallet::::set_block_number(T::BountyDepositPayoutDelay::get()); + frame_system::Pallet::::set_block_number(T::BountyDepositPayoutDelay::get() + 1u32.into()); ensure!(T::Currency::free_balance(&beneficiary_account).is_zero(), "Beneficiary already has balance"); }: _(RawOrigin::Signed(curator), bounty_id) @@ -161,45 +167,47 @@ benchmarks! { } close_bounty_proposed { - setup_pot_account::(); - let (caller, curator, fee, value, reason) = setup_bounty::(0, 0); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::get() - 1; - }: close_bounty(RawOrigin::Root, bounty_id) + setup_pot_account::(); + let (caller, curator, fee, value, reason) = setup_bounty::(0, 0); + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; + let approve_origin = T::ApproveOrigin::successful_origin(); + }: close_bounty(approve_origin, bounty_id) close_bounty_active { - setup_pot_account::(); - let (curator_lookup, bounty_id) = create_bounty::()?; - Bounties::::on_initialize(T::BlockNumber::zero()); - let bounty_id = BountyCount::get() - 1; - }: close_bounty(RawOrigin::Root, bounty_id) + setup_pot_account::(); + let (curator_lookup, bounty_id) = create_bounty::()?; + Treasury::::on_initialize(T::BlockNumber::zero()); + let bounty_id = BountyCount::::get() - 1; + let approve_origin = T::ApproveOrigin::successful_origin(); + }: close_bounty(approve_origin, bounty_id) verify { - assert_last_event::(RawEvent::BountyCanceled(bounty_id).into()) + assert_last_event::(Event::BountyCanceled { index: bounty_id }.into()) } extend_bounty_expiry { - setup_pot_account::(); - let (curator_lookup, bounty_id) = create_bounty::()?; - Bounties::::on_initialize(T::BlockNumber::zero()); + setup_pot_account::(); + let (curator_lookup, bounty_id) = create_bounty::()?; + Treasury::::on_initialize(T::BlockNumber::zero()); - let bounty_id = BountyCount::get() - 1; + let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; }: _(RawOrigin::Signed(curator), bounty_id, Vec::new()) verify { - assert_last_event::(RawEvent::BountyExtended(bounty_id).into()) + assert_last_event::(Event::BountyExtended { index: bounty_id }.into()) } spend_funds { - let b in 1 .. 100; - setup_pot_account::(); - create_approved_bounties::(b)?; + let b in 0 .. 100; + setup_pot_account::(); + create_approved_bounties::(b)?; - let mut budget_remaining = BalanceOf::::max_value(); - let mut imbalance = PositiveImbalanceOf::::zero(); + let mut budget_remaining = BalanceOf::::max_value(); + let mut imbalance = PositiveImbalanceOf::::zero(); let mut total_weight = Weight::zero(); let mut missed_any = false; }: { - as pallet_treasury::SpendFunds>::spend_funds( + as pallet_treasury::SpendFunds>::spend_funds( &mut budget_remaining, &mut imbalance, &mut total_weight, @@ -207,10 +215,14 @@ benchmarks! { ); } verify { - ensure!(budget_remaining < BalanceOf::::max_value(), "Budget not used"); ensure!(missed_any == false, "Missed some"); - assert_last_event::(RawEvent::BountyBecameActive(b - 1).into()) + if b > 0 { + ensure!(budget_remaining < BalanceOf::::max_value(), "Budget not used"); + assert_last_event::(Event::BountyBecameActive { index: b - 1 }.into()) + } else { + ensure!(budget_remaining == BalanceOf::::max_value(), "Budget used"); + } } -} -impl_benchmark_test_suite!(Bounties, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(Bounties, crate::tests::new_test_ext(), crate::tests::Test) +} diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index 77a8e47174019..c3c2c08d24b2a 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,10 +35,17 @@ //! curator or once the bounty is active or payout is pending, resulting in the slash of the //! curator's deposit. //! +//! This pallet may opt into using a [`ChildBountyManager`] that enables bounties to be split into +//! sub-bounties, as children of anh established bounty (called the parent in the context of it's +//! children). +//! +//! > NOTE: The parent bounty cannot be closed if it has a non-zero number of it has active child +//! > bounties associated with it. //! //! ### Terminology //! //! Bounty: +//! //! - **Bounty spending proposal:** A proposal to reward a predefined body of work upon completion //! by the Treasury. //! - **Proposer:** An account proposing a bounty spending. @@ -60,6 +67,7 @@ //! ### Dispatchable Functions //! //! Bounty protocol: +//! //! - `propose_bounty` - Propose a specific treasury amount to be earmarked for a predefined set of //! tasks and stake the required deposit. //! - `approve_bounty` - Accept a specific treasury amount to be earmarked for a predefined body of @@ -75,13 +83,12 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +pub mod migrations; mod tests; pub mod weights; use sp_std::prelude::*; -use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure}; - use frame_support::traits::{ Currency, ExistenceRequirement::AllowDeath, Get, Imbalance, OnUnbalanced, ReservableCurrency, }; @@ -93,51 +100,24 @@ use sp_runtime::{ use frame_support::{dispatch::DispatchResultWithPostInfo, traits::EnsureOrigin}; -use frame_support::weights::Weight; - -use codec::{Decode, Encode}; -use frame_system::{self as system, ensure_signed}; +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; use scale_info::TypeInfo; pub use weights::WeightInfo; -type BalanceOf = pallet_treasury::BalanceOf; - -type PositiveImbalanceOf = pallet_treasury::PositiveImbalanceOf; - -pub trait Config: frame_system::Config + pallet_treasury::Config { - /// The amount held on deposit for placing a bounty proposal. - type BountyDepositBase: Get>; - - /// The delay period for which a bounty beneficiary need to wait before claim the payout. - type BountyDepositPayoutDelay: Get; - - /// Bounty duration in blocks. - type BountyUpdatePeriod: Get; - - /// Percentage of the curator fee that will be reserved upfront as deposit for bounty curator. - type BountyCuratorDeposit: Get; - - /// Minimum value for a bounty. - type BountyValueMinimum: Get>; - - /// The amount held on deposit per byte within the tip report reason or bounty description. - type DataDepositPerByte: Get>; +pub use pallet::*; - /// The overarching event type. - type Event: From> + Into<::Event>; +type BalanceOf = pallet_treasury::BalanceOf; - /// Maximum acceptable reason length. - type MaximumReasonLength: Get; - - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; -} +type PositiveImbalanceOf = pallet_treasury::PositiveImbalanceOf; /// An index of a bounty. Just a `u32`. pub type BountyIndex = u32; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + /// A bounty proposal. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct Bounty { /// The account proposing it. proposer: AccountId, @@ -153,8 +133,17 @@ pub struct Bounty { status: BountyStatus, } +impl + Bounty +{ + /// Getter for bounty status, to be used for child bounties. + pub fn get_status(&self) -> BountyStatus { + self.status.clone() + } +} + /// The status of a bounty proposal. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum BountyStatus { /// The bounty is proposed and waiting for approval. Proposed, @@ -162,8 +151,7 @@ pub enum BountyStatus { Approved, /// The bounty is funded and waiting for curator assignment. Funded, - /// A curator has been proposed by the `ApproveOrigin`. Waiting for acceptance from the - /// curator. + /// A curator has been proposed. Waiting for acceptance from the curator. CuratorProposed { /// The assigned curator of this bounty. curator: AccountId, @@ -186,55 +174,79 @@ pub enum BountyStatus { }, } -// Note :: For backward compatibility reasons, -// pallet-bounties uses Treasury for storage. -// This is temporary solution, soon will get replaced with -// Own storage identifier. -decl_storage! { - trait Store for Module as Treasury { +/// The child bounty manager. +pub trait ChildBountyManager { + /// Get the active child bounties for a parent bounty. + fn child_bounties_count(bounty_id: BountyIndex) -> BountyIndex; - /// Number of bounty proposals that have been made. - pub BountyCount get(fn bounty_count): BountyIndex; + /// Get total curator fees of children-bounty curators. + fn children_curator_fees(bounty_id: BountyIndex) -> Balance; +} - /// Bounties that have been made. - pub Bounties get(fn bounties): - map hasher(twox_64_concat) BountyIndex - => Option, T::BlockNumber>>; +#[frame_support::pallet] +pub mod pallet { + use super::*; - /// The description of each bounty. - pub BountyDescriptions get(fn bounty_descriptions): map hasher(twox_64_concat) BountyIndex => Option>; + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); - /// Bounty indices that have been approved but not yet funded. - pub BountyApprovals get(fn bounty_approvals): Vec; - } -} + #[pallet::config] + pub trait Config: frame_system::Config + pallet_treasury::Config { + /// The amount held on deposit for placing a bounty proposal. + #[pallet::constant] + type BountyDepositBase: Get>; -decl_event!( - pub enum Event - where - Balance = BalanceOf, - ::AccountId, - { - /// New bounty proposal. \[index\] - BountyProposed(BountyIndex), - /// A bounty proposal was rejected; funds were slashed. \[index, bond\] - BountyRejected(BountyIndex, Balance), - /// A bounty proposal is funded and became active. \[index\] - BountyBecameActive(BountyIndex), - /// A bounty is awarded to a beneficiary. \[index, beneficiary\] - BountyAwarded(BountyIndex, AccountId), - /// A bounty is claimed by beneficiary. \[index, payout, beneficiary\] - BountyClaimed(BountyIndex, Balance, AccountId), - /// A bounty is cancelled. \[index\] - BountyCanceled(BountyIndex), - /// A bounty expiry is extended. \[index\] - BountyExtended(BountyIndex), + /// The delay period for which a bounty beneficiary need to wait before claim the payout. + #[pallet::constant] + type BountyDepositPayoutDelay: Get; + + /// Bounty duration in blocks. + #[pallet::constant] + type BountyUpdatePeriod: Get; + + /// The curator deposit is calculated as a percentage of the curator fee. + /// + /// This deposit has optional upper and lower bounds with `CuratorDepositMax` and + /// `CuratorDepositMin`. + #[pallet::constant] + type CuratorDepositMultiplier: Get; + + /// Maximum amount of funds that should be placed in a deposit for making a proposal. + #[pallet::constant] + type CuratorDepositMax: Get>>; + + /// Minimum amount of funds that should be placed in a deposit for making a proposal. + #[pallet::constant] + type CuratorDepositMin: Get>>; + + /// Minimum value for a bounty. + #[pallet::constant] + type BountyValueMinimum: Get>; + + /// The amount held on deposit per byte within the tip report reason or bounty description. + #[pallet::constant] + type DataDepositPerByte: Get>; + + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// Maximum acceptable reason length. + /// + /// Benchmarks depend on this value, be sure to update weights file when changing this value + #[pallet::constant] + type MaximumReasonLength: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The child bounty manager. + type ChildBountyManager: ChildBountyManager>; } -); -decl_error! { - /// Error for the treasury module. - pub enum Error for Module { + #[pallet::error] + pub enum Error { /// Proposer's balance is too low. InsufficientProposersBalance, /// No proposal or bounty at that index. @@ -254,39 +266,60 @@ decl_error! { PendingPayout, /// The bounties cannot be claimed/closed because it's still in the countdown period. Premature, + /// The bounty cannot be closed because it has active child bounties. + HasActiveChildBounty, + /// Too many approvals are already queued. + TooManyQueued, } -} - -decl_module! { - pub struct Module - for enum Call - where origin: T::Origin - { - /// The amount held on deposit per byte within bounty description. - const DataDepositPerByte: BalanceOf = T::DataDepositPerByte::get(); - /// The amount held on deposit for placing a bounty proposal. - const BountyDepositBase: BalanceOf = T::BountyDepositBase::get(); - - /// The delay period for which a bounty beneficiary need to wait before claim the payout. - const BountyDepositPayoutDelay: T::BlockNumber = T::BountyDepositPayoutDelay::get(); - - /// Bounty duration in blocks. - const BountyUpdatePeriod: T::BlockNumber = T::BountyUpdatePeriod::get(); - - /// Percentage of the curator fee that will be reserved upfront as deposit for bounty curator. - const BountyCuratorDeposit: Permill = T::BountyCuratorDeposit::get(); - - /// Minimum value for a bounty. - const BountyValueMinimum: BalanceOf = T::BountyValueMinimum::get(); - - /// Maximum acceptable reason length. - const MaximumReasonLength: u32 = T::MaximumReasonLength::get(); - - type Error = Error; - - fn deposit_event() = default; + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// New bounty proposal. + BountyProposed { index: BountyIndex }, + /// A bounty proposal was rejected; funds were slashed. + BountyRejected { index: BountyIndex, bond: BalanceOf }, + /// A bounty proposal is funded and became active. + BountyBecameActive { index: BountyIndex }, + /// A bounty is awarded to a beneficiary. + BountyAwarded { index: BountyIndex, beneficiary: T::AccountId }, + /// A bounty is claimed by beneficiary. + BountyClaimed { index: BountyIndex, payout: BalanceOf, beneficiary: T::AccountId }, + /// A bounty is cancelled. + BountyCanceled { index: BountyIndex }, + /// A bounty expiry is extended. + BountyExtended { index: BountyIndex }, + } + /// Number of bounty proposals that have been made. + #[pallet::storage] + #[pallet::getter(fn bounty_count)] + pub type BountyCount, I: 'static = ()> = StorageValue<_, BountyIndex, ValueQuery>; + + /// Bounties that have been made. + #[pallet::storage] + #[pallet::getter(fn bounties)] + pub type Bounties, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + BountyIndex, + Bounty, T::BlockNumber>, + >; + + /// The description of each bounty. + #[pallet::storage] + #[pallet::getter(fn bounty_descriptions)] + pub type BountyDescriptions, I: 'static = ()> = + StorageMap<_, Twox64Concat, BountyIndex, BoundedVec>; + + /// Bounty indices that have been approved but not yet funded. + #[pallet::storage] + #[pallet::getter(fn bounty_approvals)] + pub type BountyApprovals, I: 'static = ()> = + StorageValue<_, BoundedVec, ValueQuery>; + + #[pallet::call] + impl, I: 'static> Pallet { /// Propose a new bounty. /// /// The dispatch origin for this call must be _Signed_. @@ -299,80 +332,96 @@ decl_module! { /// - `fee`: The curator fee. /// - `value`: The total payment amount of this bounty, curator fee included. /// - `description`: The description of this bounty. - #[weight = ::WeightInfo::propose_bounty(description.len() as u32)] - fn propose_bounty( - origin, - #[compact] value: BalanceOf, + #[pallet::call_index(0)] + #[pallet::weight(>::WeightInfo::propose_bounty(description.len() as u32))] + pub fn propose_bounty( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, description: Vec, - ) { + ) -> DispatchResult { let proposer = ensure_signed(origin)?; Self::create_bounty(proposer, description, value)?; + Ok(()) } /// Approve a bounty proposal. At a later time, the bounty will be funded and become active /// and the original deposit will be returned. /// - /// May only be called from `T::ApproveOrigin`. + /// May only be called from `T::SpendOrigin`. /// /// # /// - O(1). /// # - #[weight = ::WeightInfo::approve_bounty()] - fn approve_bounty(origin, #[compact] bounty_id: BountyIndex) { - T::ApproveOrigin::ensure_origin(origin)?; - - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; - ensure!(bounty.status == BountyStatus::Proposed, Error::::UnexpectedStatus); + #[pallet::call_index(1)] + #[pallet::weight(>::WeightInfo::approve_bounty())] + pub fn approve_bounty( + origin: OriginFor, + #[pallet::compact] bounty_id: BountyIndex, + ) -> DispatchResult { + let max_amount = T::SpendOrigin::ensure_origin(origin)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + ensure!( + bounty.value <= max_amount, + pallet_treasury::Error::::InsufficientPermission + ); + ensure!(bounty.status == BountyStatus::Proposed, Error::::UnexpectedStatus); bounty.status = BountyStatus::Approved; - BountyApprovals::append(bounty_id); + BountyApprovals::::try_append(bounty_id) + .map_err(|()| Error::::TooManyQueued)?; Ok(()) })?; + Ok(()) } /// Assign a curator to a funded bounty. /// - /// May only be called from `T::ApproveOrigin`. + /// May only be called from `T::SpendOrigin`. /// /// # /// - O(1). /// # - #[weight = ::WeightInfo::propose_curator()] - fn propose_curator( - origin, - #[compact] bounty_id: BountyIndex, - curator: ::Source, - #[compact] fee: BalanceOf, - ) { - T::ApproveOrigin::ensure_origin(origin)?; + #[pallet::call_index(2)] + #[pallet::weight(>::WeightInfo::propose_curator())] + pub fn propose_curator( + origin: OriginFor, + #[pallet::compact] bounty_id: BountyIndex, + curator: AccountIdLookupOf, + #[pallet::compact] fee: BalanceOf, + ) -> DispatchResult { + let max_amount = T::SpendOrigin::ensure_origin(origin)?; let curator = T::Lookup::lookup(curator)?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + ensure!( + bounty.value <= max_amount, + pallet_treasury::Error::::InsufficientPermission + ); match bounty.status { - BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => {}, - _ => return Err(Error::::UnexpectedStatus.into()), + BountyStatus::Funded => {}, + _ => return Err(Error::::UnexpectedStatus.into()), }; - ensure!(fee < bounty.value, Error::::InvalidFee); + ensure!(fee < bounty.value, Error::::InvalidFee); bounty.status = BountyStatus::CuratorProposed { curator }; bounty.fee = fee; Ok(()) })?; + Ok(()) } /// Unassign curator from a bounty. /// /// This function can only be called by the `RejectOrigin` a signed origin. /// - /// If this function is called by the `RejectOrigin`, we assume that the curator is malicious - /// or inactive. As a result, we will slash the curator when possible. + /// If this function is called by the `RejectOrigin`, we assume that the curator is + /// malicious or inactive. As a result, we will slash the curator when possible. /// /// If the origin is the curator, we take this as a sign they are unable to do their job and /// they willingly give up. We could slash them, but for now we allow them to recover their @@ -385,19 +434,21 @@ decl_module! { /// # /// - O(1). /// # - #[weight = ::WeightInfo::unassign_curator()] - fn unassign_curator( - origin, - #[compact] bounty_id: BountyIndex, - ) { + #[pallet::call_index(3)] + #[pallet::weight(>::WeightInfo::unassign_curator())] + pub fn unassign_curator( + origin: OriginFor, + #[pallet::compact] bounty_id: BountyIndex, + ) -> DispatchResult { let maybe_sender = ensure_signed(origin.clone()) .map(Some) .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; - let slash_curator = |curator: &T::AccountId, curator_deposit: &mut BalanceOf| { + let slash_curator = |curator: &T::AccountId, + curator_deposit: &mut BalanceOf| { let imbalance = T::Currency::slash_reserved(curator, *curator_deposit).0; T::OnSlash::on_unbalanced(imbalance); *curator_deposit = Zero::zero(); @@ -406,8 +457,8 @@ decl_module! { match bounty.status { BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => { // No curator to unassign at this point. - return Err(Error::::UnexpectedStatus.into()) - } + return Err(Error::::UnexpectedStatus.into()) + }, BountyStatus::CuratorProposed { ref curator } => { // A curator has been proposed, but not accepted yet. // Either `RejectOrigin` or the proposed curator can unassign the curator. @@ -425,19 +476,21 @@ decl_module! { // If the sender is not the curator, and the curator is inactive, // slash the curator. if sender != *curator { - let block_number = system::Pallet::::block_number(); + let block_number = frame_system::Pallet::::block_number(); if *update_due < block_number { slash_curator(curator, &mut bounty.curator_deposit); - // Continue to change bounty status below... + // Continue to change bounty status below... } else { // Curator has more time to give an update. - return Err(Error::::Premature.into()) + return Err(Error::::Premature.into()) } } else { // Else this is the curator, willingly giving up their role. // Give back their deposit. - let err_amount = T::Currency::unreserve(&curator, bounty.curator_deposit); + let err_amount = + T::Currency::unreserve(curator, bounty.curator_deposit); debug_assert!(err_amount.is_zero()); + bounty.curator_deposit = Zero::zero(); // Continue to change bounty status below... } }, @@ -450,12 +503,13 @@ decl_module! { ensure!(maybe_sender.is_none(), BadOrigin); slash_curator(curator, &mut bounty.curator_deposit); // Continue to change bounty status below... - } + }, }; bounty.status = BountyStatus::Funded; Ok(()) })?; + Ok(()) } /// Accept the curator role for a bounty. @@ -466,32 +520,40 @@ decl_module! { /// # /// - O(1). /// # - #[weight = ::WeightInfo::accept_curator()] - fn accept_curator(origin, #[compact] bounty_id: BountyIndex) { + #[pallet::call_index(4)] + #[pallet::weight(>::WeightInfo::accept_curator())] + pub fn accept_curator( + origin: OriginFor, + #[pallet::compact] bounty_id: BountyIndex, + ) -> DispatchResult { let signer = ensure_signed(origin)?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; match bounty.status { BountyStatus::CuratorProposed { ref curator } => { - ensure!(signer == *curator, Error::::RequireCurator); + ensure!(signer == *curator, Error::::RequireCurator); - let deposit = T::BountyCuratorDeposit::get() * bounty.fee; + let deposit = Self::calculate_curator_deposit(&bounty.fee); T::Currency::reserve(curator, deposit)?; bounty.curator_deposit = deposit; - let update_due = system::Pallet::::block_number() + T::BountyUpdatePeriod::get(); - bounty.status = BountyStatus::Active { curator: curator.clone(), update_due }; + let update_due = frame_system::Pallet::::block_number() + + T::BountyUpdatePeriod::get(); + bounty.status = + BountyStatus::Active { curator: curator.clone(), update_due }; Ok(()) }, - _ => Err(Error::::UnexpectedStatus.into()), + _ => Err(Error::::UnexpectedStatus.into()), } })?; + Ok(()) } - /// Award bounty to a beneficiary account. The beneficiary will be able to claim the funds after a delay. + /// Award bounty to a beneficiary account. The beneficiary will be able to claim the funds + /// after a delay. /// /// The dispatch origin for this call must be the curator of this bounty. /// @@ -501,32 +563,43 @@ decl_module! { /// # /// - O(1). /// # - #[weight = ::WeightInfo::award_bounty()] - fn award_bounty(origin, #[compact] bounty_id: BountyIndex, beneficiary: ::Source) { + #[pallet::call_index(5)] + #[pallet::weight(>::WeightInfo::award_bounty())] + pub fn award_bounty( + origin: OriginFor, + #[pallet::compact] bounty_id: BountyIndex, + beneficiary: AccountIdLookupOf, + ) -> DispatchResult { let signer = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + + // Ensure no active child bounties before processing the call. + ensure!( + T::ChildBountyManager::child_bounties_count(bounty_id) == 0, + Error::::HasActiveChildBounty + ); + match &bounty.status { - BountyStatus::Active { - curator, - .. - } => { - ensure!(signer == *curator, Error::::RequireCurator); + BountyStatus::Active { curator, .. } => { + ensure!(signer == *curator, Error::::RequireCurator); }, - _ => return Err(Error::::UnexpectedStatus.into()), + _ => return Err(Error::::UnexpectedStatus.into()), } bounty.status = BountyStatus::PendingPayout { curator: signer, beneficiary: beneficiary.clone(), - unlock_at: system::Pallet::::block_number() + T::BountyDepositPayoutDelay::get(), + unlock_at: frame_system::Pallet::::block_number() + + T::BountyDepositPayoutDelay::get(), }; Ok(()) })?; - Self::deposit_event(Event::::BountyAwarded(bounty_id, beneficiary)); + Self::deposit_event(Event::::BountyAwarded { index: bounty_id, beneficiary }); + Ok(()) } /// Claim the payout from an awarded bounty after payout delay. @@ -538,35 +611,58 @@ decl_module! { /// # /// - O(1). /// # - #[weight = ::WeightInfo::claim_bounty()] - fn claim_bounty(origin, #[compact] bounty_id: BountyIndex) { + #[pallet::call_index(6)] + #[pallet::weight(>::WeightInfo::claim_bounty())] + pub fn claim_bounty( + origin: OriginFor, + #[pallet::compact] bounty_id: BountyIndex, + ) -> DispatchResult { let _ = ensure_signed(origin)?; // anyone can trigger claim - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let bounty = maybe_bounty.take().ok_or(Error::::InvalidIndex)?; - if let BountyStatus::PendingPayout { curator, beneficiary, unlock_at } = bounty.status { - ensure!(system::Pallet::::block_number() >= unlock_at, Error::::Premature); + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let bounty = maybe_bounty.take().ok_or(Error::::InvalidIndex)?; + if let BountyStatus::PendingPayout { curator, beneficiary, unlock_at } = + bounty.status + { + ensure!( + frame_system::Pallet::::block_number() >= unlock_at, + Error::::Premature + ); let bounty_account = Self::bounty_account_id(bounty_id); let balance = T::Currency::free_balance(&bounty_account); let fee = bounty.fee.min(balance); // just to be safe let payout = balance.saturating_sub(fee); let err_amount = T::Currency::unreserve(&curator, bounty.curator_deposit); debug_assert!(err_amount.is_zero()); - let res = T::Currency::transfer(&bounty_account, &curator, fee, AllowDeath); // should not fail + + // Get total child bounties curator fees, and subtract it from the parent + // curator fee (the fee in present referenced bounty, `self`). + let children_fee = T::ChildBountyManager::children_curator_fees(bounty_id); + debug_assert!(children_fee <= fee); + + let final_fee = fee.saturating_sub(children_fee); + let res = + T::Currency::transfer(&bounty_account, &curator, final_fee, AllowDeath); // should not fail debug_assert!(res.is_ok()); - let res = T::Currency::transfer(&bounty_account, &beneficiary, payout, AllowDeath); // should not fail + let res = + T::Currency::transfer(&bounty_account, &beneficiary, payout, AllowDeath); // should not fail debug_assert!(res.is_ok()); *maybe_bounty = None; - BountyDescriptions::remove(bounty_id); + BountyDescriptions::::remove(bounty_id); - Self::deposit_event(Event::::BountyClaimed(bounty_id, payout, beneficiary)); + Self::deposit_event(Event::::BountyClaimed { + index: bounty_id, + payout, + beneficiary, + }); Ok(()) } else { - Err(Error::::UnexpectedStatus.into()) + Err(Error::::UnexpectedStatus.into()) } })?; + Ok(()) } /// Cancel a proposed or active bounty. All the funds will be sent to treasury and @@ -579,62 +675,86 @@ decl_module! { /// # /// - O(1). /// # - #[weight = ::WeightInfo::close_bounty_proposed().max(::WeightInfo::close_bounty_active())] - fn close_bounty(origin, #[compact] bounty_id: BountyIndex) -> DispatchResultWithPostInfo { + #[pallet::call_index(7)] + #[pallet::weight(>::WeightInfo::close_bounty_proposed() + .max(>::WeightInfo::close_bounty_active()))] + pub fn close_bounty( + origin: OriginFor, + #[pallet::compact] bounty_id: BountyIndex, + ) -> DispatchResultWithPostInfo { T::RejectOrigin::ensure_origin(origin)?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResultWithPostInfo { - let bounty = maybe_bounty.as_ref().ok_or(Error::::InvalidIndex)?; - - match &bounty.status { - BountyStatus::Proposed => { - // The reject origin would like to cancel a proposed bounty. - BountyDescriptions::remove(bounty_id); - let value = bounty.bond; - let imbalance = T::Currency::slash_reserved(&bounty.proposer, value).0; - T::OnSlash::on_unbalanced(imbalance); - *maybe_bounty = None; - - Self::deposit_event(Event::::BountyRejected(bounty_id, value)); - // Return early, nothing else to do. - return Ok(Some(::WeightInfo::close_bounty_proposed()).into()) - }, - BountyStatus::Approved => { - // For weight reasons, we don't allow a council to cancel in this phase. - // We ask for them to wait until it is funded before they can cancel. - return Err(Error::::UnexpectedStatus.into()) - }, - BountyStatus::Funded | - BountyStatus::CuratorProposed { .. } => { - // Nothing extra to do besides the removal of the bounty below. - }, - BountyStatus::Active { curator, .. } => { - // Cancelled by council, refund deposit of the working curator. - let err_amount = T::Currency::unreserve(&curator, bounty.curator_deposit); - debug_assert!(err_amount.is_zero()); - // Then execute removal of the bounty below. - }, - BountyStatus::PendingPayout { .. } => { - // Bounty is already pending payout. If council wants to cancel - // this bounty, it should mean the curator was acting maliciously. - // So the council should first unassign the curator, slashing their - // deposit. - return Err(Error::::PendingPayout.into()) + Bounties::::try_mutate_exists( + bounty_id, + |maybe_bounty| -> DispatchResultWithPostInfo { + let bounty = maybe_bounty.as_ref().ok_or(Error::::InvalidIndex)?; + + // Ensure no active child bounties before processing the call. + ensure!( + T::ChildBountyManager::child_bounties_count(bounty_id) == 0, + Error::::HasActiveChildBounty + ); + + match &bounty.status { + BountyStatus::Proposed => { + // The reject origin would like to cancel a proposed bounty. + BountyDescriptions::::remove(bounty_id); + let value = bounty.bond; + let imbalance = T::Currency::slash_reserved(&bounty.proposer, value).0; + T::OnSlash::on_unbalanced(imbalance); + *maybe_bounty = None; + + Self::deposit_event(Event::::BountyRejected { + index: bounty_id, + bond: value, + }); + // Return early, nothing else to do. + return Ok( + Some(>::WeightInfo::close_bounty_proposed()).into() + ) + }, + BountyStatus::Approved => { + // For weight reasons, we don't allow a council to cancel in this phase. + // We ask for them to wait until it is funded before they can cancel. + return Err(Error::::UnexpectedStatus.into()) + }, + BountyStatus::Funded | BountyStatus::CuratorProposed { .. } => { + // Nothing extra to do besides the removal of the bounty below. + }, + BountyStatus::Active { curator, .. } => { + // Cancelled by council, refund deposit of the working curator. + let err_amount = + T::Currency::unreserve(curator, bounty.curator_deposit); + debug_assert!(err_amount.is_zero()); + // Then execute removal of the bounty below. + }, + BountyStatus::PendingPayout { .. } => { + // Bounty is already pending payout. If council wants to cancel + // this bounty, it should mean the curator was acting maliciously. + // So the council should first unassign the curator, slashing their + // deposit. + return Err(Error::::PendingPayout.into()) + }, } - } - let bounty_account = Self::bounty_account_id(bounty_id); + let bounty_account = Self::bounty_account_id(bounty_id); - BountyDescriptions::remove(bounty_id); + BountyDescriptions::::remove(bounty_id); - let balance = T::Currency::free_balance(&bounty_account); - let res = T::Currency::transfer(&bounty_account, &Self::account_id(), balance, AllowDeath); // should not fail - debug_assert!(res.is_ok()); - *maybe_bounty = None; + let balance = T::Currency::free_balance(&bounty_account); + let res = T::Currency::transfer( + &bounty_account, + &Self::account_id(), + balance, + AllowDeath, + ); // should not fail + debug_assert!(res.is_ok()); + *maybe_bounty = None; - Self::deposit_event(Event::::BountyCanceled(bounty_id)); - Ok(Some(::WeightInfo::close_bounty_active()).into()) - }) + Self::deposit_event(Event::::BountyCanceled { index: bounty_id }); + Ok(Some(>::WeightInfo::close_bounty_active()).into()) + }, + ) } /// Extend the expiry time of an active bounty. @@ -647,67 +767,85 @@ decl_module! { /// # /// - O(1). /// # - #[weight = ::WeightInfo::extend_bounty_expiry()] - fn extend_bounty_expiry(origin, #[compact] bounty_id: BountyIndex, _remark: Vec) { + #[pallet::call_index(8)] + #[pallet::weight(>::WeightInfo::extend_bounty_expiry())] + pub fn extend_bounty_expiry( + origin: OriginFor, + #[pallet::compact] bounty_id: BountyIndex, + _remark: Vec, + ) -> DispatchResult { let signer = ensure_signed(origin)?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; match bounty.status { BountyStatus::Active { ref curator, ref mut update_due } => { - ensure!(*curator == signer, Error::::RequireCurator); - *update_due = (system::Pallet::::block_number() + T::BountyUpdatePeriod::get()).max(*update_due); + ensure!(*curator == signer, Error::::RequireCurator); + *update_due = (frame_system::Pallet::::block_number() + + T::BountyUpdatePeriod::get()) + .max(*update_due); }, - _ => return Err(Error::::UnexpectedStatus.into()), + _ => return Err(Error::::UnexpectedStatus.into()), } Ok(()) })?; - Self::deposit_event(Event::::BountyExtended(bounty_id)); + Self::deposit_event(Event::::BountyExtended { index: bounty_id }); + Ok(()) } } } -impl Module { - // Add public immutables and private mutables. +impl, I: 'static> Pallet { + pub fn calculate_curator_deposit(fee: &BalanceOf) -> BalanceOf { + let mut deposit = T::CuratorDepositMultiplier::get() * *fee; + + if let Some(max_deposit) = T::CuratorDepositMax::get() { + deposit = deposit.min(max_deposit) + } + + if let Some(min_deposit) = T::CuratorDepositMin::get() { + deposit = deposit.max(min_deposit) + } + + deposit + } /// The account ID of the treasury pot. /// /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() + T::PalletId::get().into_account_truncating() } /// The account ID of a bounty account pub fn bounty_account_id(id: BountyIndex) -> T::AccountId { // only use two byte prefix to support 16 byte account id (used by test) // "modl" ++ "py/trsry" ++ "bt" is 14 bytes, and two bytes remaining for bounty index - T::PalletId::get().into_sub_account(("bt", id)) + T::PalletId::get().into_sub_account_truncating(("bt", id)) } fn create_bounty( proposer: T::AccountId, description: Vec, - value: BalanceOf, + value: BalanceOf, ) -> DispatchResult { - ensure!( - description.len() <= T::MaximumReasonLength::get() as usize, - Error::::ReasonTooBig - ); - ensure!(value >= T::BountyValueMinimum::get(), Error::::InvalidValue); + let bounded_description: BoundedVec<_, _> = + description.try_into().map_err(|_| Error::::ReasonTooBig)?; + ensure!(value >= T::BountyValueMinimum::get(), Error::::InvalidValue); let index = Self::bounty_count(); // reserve deposit for new bounty let bond = T::BountyDepositBase::get() + - T::DataDepositPerByte::get() * (description.len() as u32).into(); + T::DataDepositPerByte::get() * (bounded_description.len() as u32).into(); T::Currency::reserve(&proposer, bond) - .map_err(|_| Error::::InsufficientProposersBalance)?; + .map_err(|_| Error::::InsufficientProposersBalance)?; - BountyCount::put(index + 1); + BountyCount::::put(index + 1); let bounty = Bounty { proposer, @@ -718,26 +856,26 @@ impl Module { status: BountyStatus::Proposed, }; - Bounties::::insert(index, &bounty); - BountyDescriptions::insert(index, description); + Bounties::::insert(index, &bounty); + BountyDescriptions::::insert(index, bounded_description); - Self::deposit_event(RawEvent::BountyProposed(index)); + Self::deposit_event(Event::::BountyProposed { index }); Ok(()) } } -impl pallet_treasury::SpendFunds for Module { +impl, I: 'static> pallet_treasury::SpendFunds for Pallet { fn spend_funds( - budget_remaining: &mut BalanceOf, - imbalance: &mut PositiveImbalanceOf, + budget_remaining: &mut BalanceOf, + imbalance: &mut PositiveImbalanceOf, total_weight: &mut Weight, missed_any: &mut bool, ) { - let bounties_len = BountyApprovals::mutate(|v| { + let bounties_len = BountyApprovals::::mutate(|v| { let bounties_approval_len = v.len() as u32; v.retain(|&index| { - Bounties::::mutate(index, |bounty| { + Bounties::::mutate(index, |bounty| { // Should always be true, but shouldn't panic if false or we're screwed. if let Some(bounty) = bounty { if bounty.value <= *budget_remaining { @@ -755,7 +893,7 @@ impl pallet_treasury::SpendFunds for Module { bounty.value, )); - Self::deposit_event(RawEvent::BountyBecameActive(index)); + Self::deposit_event(Event::::BountyBecameActive { index }); false } else { *missed_any = true; @@ -769,6 +907,17 @@ impl pallet_treasury::SpendFunds for Module { bounties_approval_len }); - *total_weight += ::WeightInfo::spend_funds(bounties_len); + *total_weight += >::WeightInfo::spend_funds(bounties_len); + } +} + +// Default impl for when ChildBounties is not being used in the runtime. +impl ChildBountyManager for () { + fn child_bounties_count(_bounty_id: BountyIndex) -> BountyIndex { + Default::default() + } + + fn children_curator_fees(_bounty_id: BountyIndex) -> Balance { + Zero::zero() } } diff --git a/frame/bounties/src/migrations/mod.rs b/frame/bounties/src/migrations/mod.rs new file mode 100644 index 0000000000000..235d0f1c7cf15 --- /dev/null +++ b/frame/bounties/src/migrations/mod.rs @@ -0,0 +1,19 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +/// Version 4. +pub mod v4; diff --git a/frame/bounties/src/migrations/v4.rs b/frame/bounties/src/migrations/v4.rs new file mode 100644 index 0000000000000..2f81c97127bcd --- /dev/null +++ b/frame/bounties/src/migrations/v4.rs @@ -0,0 +1,230 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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 frame_support::{ + storage::{generator::StorageValue, StoragePrefixedMap}, + traits::{ + Get, GetStorageVersion, PalletInfoAccess, StorageVersion, + STORAGE_VERSION_STORAGE_KEY_POSTFIX, + }, + weights::Weight, +}; +use sp_core::hexdisplay::HexDisplay; +use sp_io::{hashing::twox_128, storage}; +use sp_std::str; + +use crate as pallet_bounties; + +/// Migrate the storage of the bounties pallet to a new prefix, leaving all other storage untouched +/// +/// This new prefix must be the same as the one set in construct_runtime. For safety, use +/// `PalletInfo` to get it, as: +/// `::PalletInfo::name::`. +/// +/// The migration will look into the storage version in order not to trigger a migration on an up +/// to date storage. Thus the on chain storage version must be less than 4 in order to trigger the +/// migration. +pub fn migrate< + T: pallet_bounties::Config, + P: GetStorageVersion + PalletInfoAccess, + N: AsRef, +>( + old_pallet_name: N, + new_pallet_name: N, +) -> Weight { + let old_pallet_name = old_pallet_name.as_ref(); + let new_pallet_name = new_pallet_name.as_ref(); + + if new_pallet_name == old_pallet_name { + log::info!( + target: "runtime::bounties", + "New pallet name is equal to the old prefix. No migration needs to be done.", + ); + return Weight::zero() + } + + let on_chain_storage_version =

::on_chain_storage_version(); + log::info!( + target: "runtime::bounties", + "Running migration to v4 for bounties with storage version {:?}", + on_chain_storage_version, + ); + + if on_chain_storage_version < 4 { + let storage_prefix = pallet_bounties::BountyCount::::storage_prefix(); + frame_support::storage::migration::move_storage_from_pallet( + storage_prefix, + old_pallet_name.as_bytes(), + new_pallet_name.as_bytes(), + ); + log_migration("migration", storage_prefix, old_pallet_name, new_pallet_name); + + let storage_prefix = pallet_bounties::Bounties::::storage_prefix(); + frame_support::storage::migration::move_storage_from_pallet( + storage_prefix, + old_pallet_name.as_bytes(), + new_pallet_name.as_bytes(), + ); + log_migration("migration", storage_prefix, old_pallet_name, new_pallet_name); + + let storage_prefix = pallet_bounties::BountyDescriptions::::storage_prefix(); + frame_support::storage::migration::move_storage_from_pallet( + storage_prefix, + old_pallet_name.as_bytes(), + new_pallet_name.as_bytes(), + ); + log_migration("migration", storage_prefix, old_pallet_name, new_pallet_name); + + let storage_prefix = pallet_bounties::BountyApprovals::::storage_prefix(); + frame_support::storage::migration::move_storage_from_pallet( + storage_prefix, + old_pallet_name.as_bytes(), + new_pallet_name.as_bytes(), + ); + log_migration("migration", storage_prefix, old_pallet_name, new_pallet_name); + + StorageVersion::new(4).put::

(); + ::BlockWeights::get().max_block + } else { + log::warn!( + target: "runtime::bounties", + "Attempted to apply migration to v4 but failed because storage version is {:?}", + on_chain_storage_version, + ); + Weight::zero() + } +} + +/// Some checks prior to migration. This can be linked to +/// [`frame_support::traits::OnRuntimeUpgrade::pre_upgrade`] for further testing. +/// +/// Panics if anything goes wrong. +pub fn pre_migration>( + old_pallet_name: N, + new_pallet_name: N, +) { + let old_pallet_name = old_pallet_name.as_ref(); + let new_pallet_name = new_pallet_name.as_ref(); + let storage_prefix_bounties_count = pallet_bounties::BountyCount::::storage_prefix(); + let storage_prefix_bounties = pallet_bounties::Bounties::::storage_prefix(); + let storage_prefix_bounties_description = + pallet_bounties::BountyDescriptions::::storage_prefix(); + let storage_prefix_bounties_approvals = pallet_bounties::BountyApprovals::::storage_prefix(); + log_migration("pre-migration", storage_prefix_bounties_count, old_pallet_name, new_pallet_name); + log_migration("pre-migration", storage_prefix_bounties, old_pallet_name, new_pallet_name); + log_migration( + "pre-migration", + storage_prefix_bounties_description, + old_pallet_name, + new_pallet_name, + ); + log_migration( + "pre-migration", + storage_prefix_bounties_approvals, + old_pallet_name, + new_pallet_name, + ); + + let new_pallet_prefix = twox_128(new_pallet_name.as_bytes()); + let storage_version_key = + [&new_pallet_prefix, &twox_128(STORAGE_VERSION_STORAGE_KEY_POSTFIX)[..]].concat(); + + // ensure nothing is stored in the new prefix. + assert!( + storage::next_key(&new_pallet_prefix).map_or( + // either nothing is there + true, + // or we ensure that the next key has no common prefix with twox_128(new), + // or is the pallet version that is already stored using the pallet name + |next_key| { + storage::next_key(&next_key).map_or(true, |next_key| { + !next_key.starts_with(&new_pallet_prefix) || next_key == storage_version_key + }) + }, + ), + "unexpected next_key({}) = {:?}", + new_pallet_name, + HexDisplay::from(&sp_io::storage::next_key(&new_pallet_prefix).unwrap()), + ); + assert!(

::on_chain_storage_version() < 4); +} + +/// Some checks for after migration. This can be linked to +/// [`frame_support::traits::OnRuntimeUpgrade::post_upgrade`] for further testing. +/// +/// Panics if anything goes wrong. +pub fn post_migration>( + old_pallet_name: N, + new_pallet_name: N, +) { + let old_pallet_name = old_pallet_name.as_ref(); + let new_pallet_name = new_pallet_name.as_ref(); + let storage_prefix_bounties_count = pallet_bounties::BountyCount::::storage_prefix(); + let storage_prefix_bounties = pallet_bounties::Bounties::::storage_prefix(); + let storage_prefix_bounties_description = + pallet_bounties::BountyDescriptions::::storage_prefix(); + let storage_prefix_bounties_approvals = pallet_bounties::BountyApprovals::::storage_prefix(); + log_migration( + "post-migration", + storage_prefix_bounties_count, + old_pallet_name, + new_pallet_name, + ); + log_migration("post-migration", storage_prefix_bounties, old_pallet_name, new_pallet_name); + log_migration( + "post-migration", + storage_prefix_bounties_description, + old_pallet_name, + new_pallet_name, + ); + log_migration( + "post-migration", + storage_prefix_bounties_approvals, + old_pallet_name, + new_pallet_name, + ); + + let old_pallet_prefix = twox_128(old_pallet_name.as_bytes()); + let old_bounties_count_key = + [&old_pallet_prefix, &twox_128(storage_prefix_bounties_count)[..]].concat(); + let old_bounties_key = [&old_pallet_prefix, &twox_128(storage_prefix_bounties)[..]].concat(); + let old_bounties_description_key = + [&old_pallet_prefix, &twox_128(storage_prefix_bounties_description)[..]].concat(); + let old_bounties_approvals_key = + [&old_pallet_prefix, &twox_128(storage_prefix_bounties_approvals)[..]].concat(); + assert!(storage::next_key(&old_bounties_count_key) + .map_or(true, |next_key| !next_key.starts_with(&old_bounties_count_key))); + assert!(storage::next_key(&old_bounties_key) + .map_or(true, |next_key| !next_key.starts_with(&old_bounties_key))); + assert!(storage::next_key(&old_bounties_description_key) + .map_or(true, |next_key| !next_key.starts_with(&old_bounties_description_key))); + assert!(storage::next_key(&old_bounties_approvals_key) + .map_or(true, |next_key| !next_key.starts_with(&old_bounties_approvals_key))); + + assert_eq!(

::on_chain_storage_version(), 4); +} + +fn log_migration(stage: &str, storage_prefix: &[u8], old_pallet_name: &str, new_pallet_name: &str) { + log::info!( + target: "runtime::bounties", + "{} prefix of storage '{}': '{}' ==> '{}'", + stage, + str::from_utf8(storage_prefix).unwrap_or(""), + old_pallet_name, + new_pallet_name, + ); +} diff --git a/frame/bounties/src/tests.rs b/frame/bounties/src/tests.rs index ff058a3601e07..bc59e3a70fd82 100644 --- a/frame/bounties/src/tests.rs +++ b/frame/bounties/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,20 +21,24 @@ use super::*; use crate as pallet_bounties; -use std::cell::RefCell; use frame_support::{ - assert_noop, assert_ok, pallet_prelude::GenesisBuild, parameter_types, traits::OnInitialize, - weights::Weight, PalletId, + assert_noop, assert_ok, + pallet_prelude::GenesisBuild, + parameter_types, + traits::{ConstU32, ConstU64, OnInitialize}, + PalletId, }; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - Perbill, + BuildStorage, Perbill, Storage, }; +use super::Event as BountiesEvent; + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -47,33 +51,34 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Bounties: pallet_bounties::{Pallet, Call, Storage, Event}, + Bounties1: pallet_bounties::::{Pallet, Call, Storage, Event}, Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, + Treasury1: pallet_treasury::::{Pallet, Call, Storage, Config, Event}, } ); parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaximumBlockWeight: Weight = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::one(); } +type Balance = u64; + impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -82,89 +87,127 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; - type Balance = u64; - type Event = Event; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } -thread_local! { - static TEN_TO_FOURTEEN: RefCell> = RefCell::new(vec![10,11,12,13,14]); -} parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); - pub const ProposalBondMinimum: u64 = 1; - pub const SpendPeriod: u64 = 2; - pub const Burn: Permill = Permill::from_percent(50); - pub const DataDepositPerByte: u64 = 1; + pub static Burn: Permill = Permill::from_percent(50); pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); - pub const MaxApprovals: u32 = 100; + pub const TreasuryPalletId2: PalletId = PalletId(*b"py/trsr2"); + pub static SpendLimit: Balance = u64::MAX; + pub static SpendLimit1: Balance = u64::MAX; } -// impl pallet_treasury::Config for Test { + impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId; type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type OnSlash = (); type ProposalBond = ProposalBond; - type ProposalBondMinimum = ProposalBondMinimum; - type SpendPeriod = SpendPeriod; + type ProposalBondMinimum = ConstU64<1>; + type ProposalBondMaximum = (); + type SpendPeriod = ConstU64<2>; type Burn = Burn; type BurnDestination = (); // Just gets burned. type WeightInfo = (); type SpendFunds = Bounties; - type MaxApprovals = MaxApprovals; + type MaxApprovals = ConstU32<100>; + type SpendOrigin = frame_system::EnsureRootWithSuccess; } + +impl pallet_treasury::Config for Test { + type PalletId = TreasuryPalletId2; + type Currency = pallet_balances::Pallet; + type ApproveOrigin = frame_system::EnsureRoot; + type RejectOrigin = frame_system::EnsureRoot; + type RuntimeEvent = RuntimeEvent; + type OnSlash = (); + type ProposalBond = ProposalBond; + type ProposalBondMinimum = ConstU64<1>; + type ProposalBondMaximum = (); + type SpendPeriod = ConstU64<2>; + type Burn = Burn; + type BurnDestination = (); // Just gets burned. + type WeightInfo = (); + type SpendFunds = Bounties1; + type MaxApprovals = ConstU32<100>; + type SpendOrigin = frame_system::EnsureRootWithSuccess; +} + parameter_types! { - pub const BountyDepositBase: u64 = 80; - pub const BountyDepositPayoutDelay: u64 = 3; - pub const BountyUpdatePeriod: u32 = 20; - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); - pub const BountyValueMinimum: u64 = 1; - pub const MaximumReasonLength: u32 = 16384; + // This will be 50% of the bounty fee. + pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50); + pub const CuratorDepositMax: Balance = 1_000; + pub const CuratorDepositMin: Balance = 3; + } + impl Config for Test { - type Event = Event; - type BountyDepositBase = BountyDepositBase; - type BountyDepositPayoutDelay = BountyDepositPayoutDelay; - type BountyUpdatePeriod = BountyUpdatePeriod; - type BountyCuratorDeposit = BountyCuratorDeposit; - type BountyValueMinimum = BountyValueMinimum; - type DataDepositPerByte = DataDepositPerByte; - type MaximumReasonLength = MaximumReasonLength; + type RuntimeEvent = RuntimeEvent; + type BountyDepositBase = ConstU64<80>; + type BountyDepositPayoutDelay = ConstU64<3>; + type BountyUpdatePeriod = ConstU64<20>; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMax = CuratorDepositMax; + type CuratorDepositMin = CuratorDepositMin; + type BountyValueMinimum = ConstU64<1>; + type DataDepositPerByte = ConstU64<1>; + type MaximumReasonLength = ConstU32<16384>; + type WeightInfo = (); + type ChildBountyManager = (); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type BountyDepositBase = ConstU64<80>; + type BountyDepositPayoutDelay = ConstU64<3>; + type BountyUpdatePeriod = ConstU64<20>; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMax = CuratorDepositMax; + type CuratorDepositMin = CuratorDepositMin; + type BountyValueMinimum = ConstU64<1>; + type DataDepositPerByte = ConstU64<1>; + type MaximumReasonLength = ConstU32<16384>; type WeightInfo = (); + type ChildBountyManager = (); } type TreasuryError = pallet_treasury::Error; +type TreasuryError1 = pallet_treasury::Error; pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { - // Total issuance will be 200 with treasury account initialized at ED. - balances: vec![(0, 100), (1, 98), (2, 1)], + let mut ext: sp_io::TestExternalities = GenesisConfig { + system: frame_system::GenesisConfig::default(), + balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, + treasury: Default::default(), + treasury_1: Default::default(), } - .assimilate_storage(&mut t) - .unwrap(); - GenesisBuild::::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap(); - t.into() + .build_storage() + .unwrap() + .into(); + ext.execute_with(|| System::set_block_number(1)); + ext } -fn last_event() -> RawEvent { +fn last_event() -> BountiesEvent { System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let Event::Bounties(inner) = e { Some(inner) } else { None }) + .filter_map(|e| if let RuntimeEvent::Bounties(inner) = e { Some(inner) } else { None }) .last() .unwrap() } @@ -189,7 +232,7 @@ fn minting_works() { #[test] fn spend_proposal_takes_min_deposit() { new_test_ext().execute_with(|| { - assert_ok!(Treasury::propose_spend(Origin::signed(0), 1, 3)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 1, 3)); assert_eq!(Balances::free_balance(0), 99); assert_eq!(Balances::reserved_balance(0), 1); }); @@ -198,7 +241,7 @@ fn spend_proposal_takes_min_deposit() { #[test] fn spend_proposal_takes_proportional_deposit() { new_test_ext().execute_with(|| { - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); assert_eq!(Balances::free_balance(0), 95); assert_eq!(Balances::reserved_balance(0), 5); }); @@ -208,7 +251,7 @@ fn spend_proposal_takes_proportional_deposit() { fn spend_proposal_fails_when_proposer_poor() { new_test_ext().execute_with(|| { assert_noop!( - Treasury::propose_spend(Origin::signed(2), 100, 3), + Treasury::propose_spend(RuntimeOrigin::signed(2), 100, 3), TreasuryError::InsufficientProposersBalance, ); }); @@ -219,8 +262,8 @@ fn accepted_spend_proposal_ignored_outside_spend_period() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); >::on_initialize(1); assert_eq!(Balances::free_balance(3), 0); @@ -246,8 +289,8 @@ fn rejected_spend_proposal_ignored_on_spend_period() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::reject_proposal(Origin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::reject_proposal(RuntimeOrigin::root(), 0)); >::on_initialize(2); assert_eq!(Balances::free_balance(3), 0); @@ -260,9 +303,12 @@ fn reject_already_rejected_spend_proposal_fails() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::reject_proposal(Origin::root(), 0)); - assert_noop!(Treasury::reject_proposal(Origin::root(), 0), TreasuryError::InvalidIndex); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::reject_proposal(RuntimeOrigin::root(), 0)); + assert_noop!( + Treasury::reject_proposal(RuntimeOrigin::root(), 0), + TreasuryError::InvalidIndex + ); }); } @@ -270,8 +316,8 @@ fn reject_already_rejected_spend_proposal_fails() { fn reject_non_existent_spend_proposal_fails() { new_test_ext().execute_with(|| { assert_noop!( - Treasury::reject_proposal(Origin::root(), 0), - pallet_treasury::Error::::InvalidIndex + Treasury::reject_proposal(RuntimeOrigin::root(), 0), + pallet_treasury::Error::::InvalidIndex ); }); } @@ -279,7 +325,10 @@ fn reject_non_existent_spend_proposal_fails() { #[test] fn accept_non_existent_spend_proposal_fails() { new_test_ext().execute_with(|| { - assert_noop!(Treasury::approve_proposal(Origin::root(), 0), TreasuryError::InvalidIndex); + assert_noop!( + Treasury::approve_proposal(RuntimeOrigin::root(), 0), + TreasuryError::InvalidIndex + ); }); } @@ -288,9 +337,12 @@ fn accept_already_rejected_spend_proposal_fails() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::reject_proposal(Origin::root(), 0)); - assert_noop!(Treasury::approve_proposal(Origin::root(), 0), TreasuryError::InvalidIndex); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::reject_proposal(RuntimeOrigin::root(), 0)); + assert_noop!( + Treasury::approve_proposal(RuntimeOrigin::root(), 0), + TreasuryError::InvalidIndex + ); }); } @@ -300,8 +352,8 @@ fn accepted_spend_proposal_enacted_on_spend_period() { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); >::on_initialize(2); assert_eq!(Balances::free_balance(3), 100); @@ -315,8 +367,8 @@ fn pot_underflow_should_not_diminish() { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 150, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 150, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); >::on_initialize(2); assert_eq!(Treasury::pot(), 100); // Pot hasn't changed @@ -337,14 +389,14 @@ fn treasury_account_doesnt_get_deleted() { assert_eq!(Treasury::pot(), 100); let treasury_balance = Balances::free_balance(&Treasury::account_id()); - assert_ok!(Treasury::propose_spend(Origin::signed(0), treasury_balance, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), treasury_balance, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); >::on_initialize(2); assert_eq!(Treasury::pot(), 100); // Pot hasn't changed - assert_ok!(Treasury::propose_spend(Origin::signed(0), Treasury::pot(), 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 1)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), Treasury::pot(), 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 1)); >::on_initialize(4); assert_eq!(Treasury::pot(), 0); // Pot is emptied @@ -367,10 +419,10 @@ fn inexistent_account_works() { assert_eq!(Balances::free_balance(Treasury::account_id()), 0); // Account does not exist assert_eq!(Treasury::pot(), 0); // Pot is empty - assert_ok!(Treasury::propose_spend(Origin::signed(0), 99, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 1, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 1)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 99, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 1, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 1)); >::on_initialize(2); assert_eq!(Treasury::pot(), 0); // Pot hasn't changed assert_eq!(Balances::free_balance(3), 0); // Balance of `3` hasn't changed @@ -394,9 +446,9 @@ fn propose_bounty_works() { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 10, b"1234567890".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 10, b"1234567890".to_vec())); - assert_eq!(last_event(), RawEvent::BountyProposed(0)); + assert_eq!(last_event(), BountiesEvent::BountyProposed { index: 0 }); let deposit: u64 = 85 + 5; assert_eq!(Balances::reserved_balance(0), deposit); @@ -429,17 +481,21 @@ fn propose_bounty_validation_works() { assert_eq!(Treasury::pot(), 100); assert_noop!( - Bounties::propose_bounty(Origin::signed(1), 0, [0; 17_000].to_vec()), + Bounties::propose_bounty(RuntimeOrigin::signed(1), 0, [0; 17_000].to_vec()), Error::::ReasonTooBig ); assert_noop!( - Bounties::propose_bounty(Origin::signed(1), 10, b"12345678901234567890".to_vec()), + Bounties::propose_bounty( + RuntimeOrigin::signed(1), + 10, + b"12345678901234567890".to_vec() + ), Error::::InsufficientProposersBalance ); assert_noop!( - Bounties::propose_bounty(Origin::signed(1), 0, b"12345678901234567890".to_vec()), + Bounties::propose_bounty(RuntimeOrigin::signed(1), 0, b"12345678901234567890".to_vec()), Error::::InvalidValue ); }); @@ -450,21 +506,21 @@ fn close_bounty_works() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_noop!(Bounties::close_bounty(Origin::root(), 0), Error::::InvalidIndex); + assert_noop!(Bounties::close_bounty(RuntimeOrigin::root(), 0), Error::::InvalidIndex); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 10, b"12345".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 10, b"12345".to_vec())); - assert_ok!(Bounties::close_bounty(Origin::root(), 0)); + assert_ok!(Bounties::close_bounty(RuntimeOrigin::root(), 0)); let deposit: u64 = 80 + 5; - assert_eq!(last_event(), RawEvent::BountyRejected(0, deposit)); + assert_eq!(last_event(), BountiesEvent::BountyRejected { index: 0, bond: deposit }); assert_eq!(Balances::reserved_balance(0), 0); assert_eq!(Balances::free_balance(0), 100 - deposit); assert_eq!(Bounties::bounties(0), None); - assert!(!pallet_treasury::Proposals::::contains_key(0)); + assert!(!pallet_treasury::Proposals::::contains_key(0)); assert_eq!(Bounties::bounty_descriptions(0), None); }); @@ -475,11 +531,14 @@ fn approve_bounty_works() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_noop!(Bounties::approve_bounty(Origin::root(), 0), Error::::InvalidIndex); + assert_noop!( + Bounties::approve_bounty(RuntimeOrigin::root(), 0), + Error::::InvalidIndex + ); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); - assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); let deposit: u64 = 80 + 5; @@ -496,7 +555,10 @@ fn approve_bounty_works() { ); assert_eq!(Bounties::bounty_approvals(), vec![0]); - assert_noop!(Bounties::close_bounty(Origin::root(), 0), Error::::UnexpectedStatus); + assert_noop!( + Bounties::close_bounty(RuntimeOrigin::root(), 0), + Error::::UnexpectedStatus + ); // deposit not returned yet assert_eq!(Balances::reserved_balance(0), deposit); @@ -532,29 +594,30 @@ fn assign_curator_works() { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_noop!( - Bounties::propose_curator(Origin::root(), 0, 4, 4), + Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 4), Error::::InvalidIndex ); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); - assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); System::set_block_number(2); >::on_initialize(2); assert_noop!( - Bounties::propose_curator(Origin::root(), 0, 4, 50), + Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 50), Error::::InvalidFee ); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); + let fee = 4; + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, + fee, curator_deposit: 0, value: 50, bond: 85, @@ -562,30 +625,35 @@ fn assign_curator_works() { } ); - assert_noop!(Bounties::accept_curator(Origin::signed(1), 0), Error::::RequireCurator); assert_noop!( - Bounties::accept_curator(Origin::signed(4), 0), + Bounties::accept_curator(RuntimeOrigin::signed(1), 0), + Error::::RequireCurator + ); + assert_noop!( + Bounties::accept_curator(RuntimeOrigin::signed(4), 0), pallet_balances::Error::::InsufficientBalance ); Balances::make_free_balance_be(&4, 10); - assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + let expected_deposit = Bounties::calculate_curator_deposit(&fee); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, - curator_deposit: 2, + fee, + curator_deposit: expected_deposit, value: 50, bond: 85, status: BountyStatus::Active { curator: 4, update_due: 22 }, } ); - assert_eq!(Balances::free_balance(&4), 8); - assert_eq!(Balances::reserved_balance(&4), 2); + assert_eq!(Balances::free_balance(&4), 10 - expected_deposit); + assert_eq!(Balances::reserved_balance(&4), expected_deposit); }); } @@ -594,24 +662,24 @@ fn unassign_curator_works() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); - assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); - - assert_noop!(Bounties::unassign_curator(Origin::signed(1), 0), BadOrigin); + let fee = 4; - assert_ok!(Bounties::unassign_curator(Origin::signed(4), 0)); + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee)); + assert_noop!(Bounties::unassign_curator(RuntimeOrigin::signed(1), 0), BadOrigin); + assert_ok!(Bounties::unassign_curator(RuntimeOrigin::signed(4), 0)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, + fee, curator_deposit: 0, value: 50, bond: 85, @@ -619,19 +687,17 @@ fn unassign_curator_works() { } ); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); - + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee)); Balances::make_free_balance_be(&4, 10); - - assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); - - assert_ok!(Bounties::unassign_curator(Origin::root(), 0)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + let expected_deposit = Bounties::calculate_curator_deposit(&fee); + assert_ok!(Bounties::unassign_curator(RuntimeOrigin::root(), 0)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, + fee, curator_deposit: 0, value: 50, bond: 85, @@ -639,8 +705,8 @@ fn unassign_curator_works() { } ); - assert_eq!(Balances::free_balance(&4), 8); - assert_eq!(Balances::reserved_balance(&4), 0); // slashed 2 + assert_eq!(Balances::free_balance(&4), 10 - expected_deposit); + assert_eq!(Balances::reserved_balance(&4), 0); // slashed curator deposit }); } @@ -650,47 +716,56 @@ fn award_and_claim_bounty_works() { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&4, 10); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); - assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); - assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); + let fee = 4; + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); - assert_eq!(Balances::free_balance(4), 8); // inital 10 - 2 deposit + let expected_deposit = Bounties::calculate_curator_deposit(&fee); + assert_eq!(Balances::free_balance(4), 10 - expected_deposit); assert_noop!( - Bounties::award_bounty(Origin::signed(1), 0, 3), + Bounties::award_bounty(RuntimeOrigin::signed(1), 0, 3), Error::::RequireCurator ); - assert_ok!(Bounties::award_bounty(Origin::signed(4), 0, 3)); + assert_ok!(Bounties::award_bounty(RuntimeOrigin::signed(4), 0, 3)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, - curator_deposit: 2, + fee, + curator_deposit: expected_deposit, value: 50, bond: 85, status: BountyStatus::PendingPayout { curator: 4, beneficiary: 3, unlock_at: 5 }, } ); - assert_noop!(Bounties::claim_bounty(Origin::signed(1), 0), Error::::Premature); + assert_noop!(Bounties::claim_bounty(RuntimeOrigin::signed(1), 0), Error::::Premature); System::set_block_number(5); >::on_initialize(5); - assert_ok!(Balances::transfer(Origin::signed(0), Bounties::bounty_account_id(0), 10)); + assert_ok!(Balances::transfer( + RuntimeOrigin::signed(0), + Bounties::bounty_account_id(0), + 10 + )); - assert_ok!(Bounties::claim_bounty(Origin::signed(1), 0)); + assert_ok!(Bounties::claim_bounty(RuntimeOrigin::signed(1), 0)); - assert_eq!(last_event(), RawEvent::BountyClaimed(0, 56, 3)); + assert_eq!( + last_event(), + BountiesEvent::BountyClaimed { index: 0, payout: 56, beneficiary: 3 } + ); assert_eq!(Balances::free_balance(4), 14); // initial 10 + fee 4 @@ -708,17 +783,17 @@ fn claim_handles_high_fee() { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&4, 30); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); - assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 49)); - assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 49)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); - assert_ok!(Bounties::award_bounty(Origin::signed(4), 0, 3)); + assert_ok!(Bounties::award_bounty(RuntimeOrigin::signed(4), 0, 3)); System::set_block_number(5); >::on_initialize(5); @@ -727,9 +802,12 @@ fn claim_handles_high_fee() { let res = Balances::slash(&Bounties::bounty_account_id(0), 10); assert_eq!(res.0.peek(), 10); - assert_ok!(Bounties::claim_bounty(Origin::signed(1), 0)); + assert_ok!(Bounties::claim_bounty(RuntimeOrigin::signed(1), 0)); - assert_eq!(last_event(), RawEvent::BountyClaimed(0, 0, 3)); + assert_eq!( + last_event(), + BountiesEvent::BountyClaimed { index: 0, payout: 0, beneficiary: 3 } + ); assert_eq!(Balances::free_balance(4), 70); // 30 + 50 - 10 assert_eq!(Balances::free_balance(3), 0); @@ -747,14 +825,18 @@ fn cancel_and_refund() { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); - assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); System::set_block_number(2); >::on_initialize(2); - assert_ok!(Balances::transfer(Origin::signed(0), Bounties::bounty_account_id(0), 10)); + assert_ok!(Balances::transfer( + RuntimeOrigin::signed(0), + Bounties::bounty_account_id(0), + 10 + )); assert_eq!( Bounties::bounties(0).unwrap(), @@ -770,9 +852,9 @@ fn cancel_and_refund() { assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 60); - assert_noop!(Bounties::close_bounty(Origin::signed(0), 0), BadOrigin); + assert_noop!(Bounties::close_bounty(RuntimeOrigin::signed(0), 0), BadOrigin); - assert_ok!(Bounties::close_bounty(Origin::root(), 0)); + assert_ok!(Bounties::close_bounty(RuntimeOrigin::root(), 0)); // `- 25 + 10` assert_eq!(Treasury::pot(), 85); @@ -784,29 +866,32 @@ fn award_and_cancel() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); - assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 0, 10)); - assert_ok!(Bounties::accept_curator(Origin::signed(0), 0)); + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 0, 10)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(0), 0)); assert_eq!(Balances::free_balance(0), 95); assert_eq!(Balances::reserved_balance(0), 5); - assert_ok!(Bounties::award_bounty(Origin::signed(0), 0, 3)); + assert_ok!(Bounties::award_bounty(RuntimeOrigin::signed(0), 0, 3)); // Cannot close bounty directly when payout is happening... - assert_noop!(Bounties::close_bounty(Origin::root(), 0), Error::::PendingPayout); + assert_noop!( + Bounties::close_bounty(RuntimeOrigin::root(), 0), + Error::::PendingPayout + ); // Instead unassign the curator to slash them and then close. - assert_ok!(Bounties::unassign_curator(Origin::root(), 0)); - assert_ok!(Bounties::close_bounty(Origin::root(), 0)); + assert_ok!(Bounties::unassign_curator(RuntimeOrigin::root(), 0)); + assert_ok!(Bounties::close_bounty(RuntimeOrigin::root(), 0)); - assert_eq!(last_event(), RawEvent::BountyCanceled(0)); + assert_eq!(last_event(), BountiesEvent::BountyCanceled { index: 0 }); assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 0); @@ -824,15 +909,15 @@ fn expire_and_unassign() { new_test_ext().execute_with(|| { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); - assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 1, 10)); - assert_ok!(Bounties::accept_curator(Origin::signed(1), 0)); + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 1, 10)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(1), 0)); assert_eq!(Balances::free_balance(1), 93); assert_eq!(Balances::reserved_balance(1), 5); @@ -840,12 +925,15 @@ fn expire_and_unassign() { System::set_block_number(22); >::on_initialize(22); - assert_noop!(Bounties::unassign_curator(Origin::signed(0), 0), Error::::Premature); + assert_noop!( + Bounties::unassign_curator(RuntimeOrigin::signed(0), 0), + Error::::Premature + ); System::set_block_number(23); >::on_initialize(23); - assert_ok!(Bounties::unassign_curator(Origin::signed(0), 0)); + assert_ok!(Bounties::unassign_curator(RuntimeOrigin::signed(0), 0)); assert_eq!( Bounties::bounties(0).unwrap(), @@ -870,20 +958,20 @@ fn extend_expiry() { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&4, 10); - assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); - assert_ok!(Bounties::approve_bounty(Origin::root(), 0)); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); assert_noop!( - Bounties::extend_bounty_expiry(Origin::signed(1), 0, Vec::new()), + Bounties::extend_bounty_expiry(RuntimeOrigin::signed(1), 0, Vec::new()), Error::::UnexpectedStatus ); System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 10)); - assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 10)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); assert_eq!(Balances::free_balance(4), 5); assert_eq!(Balances::reserved_balance(4), 5); @@ -892,10 +980,10 @@ fn extend_expiry() { >::on_initialize(10); assert_noop!( - Bounties::extend_bounty_expiry(Origin::signed(0), 0, Vec::new()), + Bounties::extend_bounty_expiry(RuntimeOrigin::signed(0), 0, Vec::new()), Error::::RequireCurator ); - assert_ok!(Bounties::extend_bounty_expiry(Origin::signed(4), 0, Vec::new())); + assert_ok!(Bounties::extend_bounty_expiry(RuntimeOrigin::signed(4), 0, Vec::new())); assert_eq!( Bounties::bounties(0).unwrap(), @@ -909,7 +997,7 @@ fn extend_expiry() { } ); - assert_ok!(Bounties::extend_bounty_expiry(Origin::signed(4), 0, Vec::new())); + assert_ok!(Bounties::extend_bounty_expiry(RuntimeOrigin::signed(4), 0, Vec::new())); assert_eq!( Bounties::bounties(0).unwrap(), @@ -926,14 +1014,59 @@ fn extend_expiry() { System::set_block_number(25); >::on_initialize(25); - assert_noop!(Bounties::unassign_curator(Origin::signed(0), 0), Error::::Premature); - assert_ok!(Bounties::unassign_curator(Origin::signed(4), 0)); + assert_noop!( + Bounties::unassign_curator(RuntimeOrigin::signed(0), 0), + Error::::Premature + ); + assert_ok!(Bounties::unassign_curator(RuntimeOrigin::signed(4), 0)); assert_eq!(Balances::free_balance(4), 10); // not slashed assert_eq!(Balances::reserved_balance(4), 0); }); } +#[test] +fn test_migration_v4() { + let mut s = Storage::default(); + + let index: u32 = 10; + + let bounty = Bounty:: { + proposer: 0, + value: 20, + fee: 20, + curator_deposit: 20, + bond: 50, + status: BountyStatus::::Proposed, + }; + + let data = vec![ + (pallet_bounties::BountyCount::::hashed_key().to_vec(), 10.encode().to_vec()), + (pallet_bounties::Bounties::::hashed_key_for(index), bounty.encode().to_vec()), + (pallet_bounties::BountyDescriptions::::hashed_key_for(index), vec![0, 0]), + ( + pallet_bounties::BountyApprovals::::hashed_key().to_vec(), + vec![10 as u32].encode().to_vec(), + ), + ]; + + s.top = data.into_iter().collect(); + + sp_io::TestExternalities::new(s).execute_with(|| { + use frame_support::traits::PalletInfo; + let old_pallet_name = ::PalletInfo::name::() + .expect("Bounties is part of runtime, so it has a name; qed"); + let new_pallet_name = "NewBounties"; + + crate::migrations::v4::pre_migration::(old_pallet_name, new_pallet_name); + crate::migrations::v4::migrate::(old_pallet_name, new_pallet_name); + crate::migrations::v4::post_migration::( + old_pallet_name, + new_pallet_name, + ); + }); +} + #[test] fn genesis_funding_works() { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); @@ -952,3 +1085,229 @@ fn genesis_funding_works() { assert_eq!(Treasury::pot(), initial_funding - Balances::minimum_balance()); }); } + +#[test] +fn unassign_curator_self() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 1, 10)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(1), 0)); + + assert_eq!(Balances::free_balance(1), 93); + assert_eq!(Balances::reserved_balance(1), 5); + + System::set_block_number(8); + >::on_initialize(8); + + assert_ok!(Bounties::unassign_curator(RuntimeOrigin::signed(1), 0)); + + assert_eq!( + Bounties::bounties(0).unwrap(), + Bounty { + proposer: 0, + fee: 10, + curator_deposit: 0, + value: 50, + bond: 85, + status: BountyStatus::Funded, + } + ); + + assert_eq!(Balances::free_balance(1), 98); + assert_eq!(Balances::reserved_balance(1), 0); // not slashed + }); +} + +#[test] +fn accept_curator_handles_different_deposit_calculations() { + // This test will verify that a bounty with and without a fee results + // in a different curator deposit: one using the value, and one using the fee. + new_test_ext().execute_with(|| { + // Case 1: With a fee + let user = 1; + let bounty_index = 0; + let value = 88; + let fee = 42; + + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&user, 100); + // Allow for a larger spend limit: + SpendLimit::set(value); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), value, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), bounty_index)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), bounty_index, user, fee)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(user), bounty_index)); + + let expected_deposit = CuratorDepositMultiplier::get() * fee; + assert_eq!(Balances::free_balance(&user), 100 - expected_deposit); + assert_eq!(Balances::reserved_balance(&user), expected_deposit); + + // Case 2: Lower bound + let user = 2; + let bounty_index = 1; + let value = 35; + let fee = 0; + + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&user, 100); + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), value, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), bounty_index)); + + System::set_block_number(4); + >::on_initialize(4); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), bounty_index, user, fee)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(user), bounty_index)); + + let expected_deposit = CuratorDepositMin::get(); + assert_eq!(Balances::free_balance(&user), 100 - expected_deposit); + assert_eq!(Balances::reserved_balance(&user), expected_deposit); + + // Case 3: Upper bound + let user = 3; + let bounty_index = 2; + let value = 1_000_000; + let fee = 50_000; + let starting_balance = fee * 2; + + Balances::make_free_balance_be(&Treasury::account_id(), value * 2); + Balances::make_free_balance_be(&user, starting_balance); + Balances::make_free_balance_be(&0, starting_balance); + + // Allow for a larger spend limit: + SpendLimit::set(value); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), value, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), bounty_index)); + + System::set_block_number(6); + >::on_initialize(6); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), bounty_index, user, fee)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(user), bounty_index)); + + let expected_deposit = CuratorDepositMax::get(); + assert_eq!(Balances::free_balance(&user), starting_balance - expected_deposit); + assert_eq!(Balances::reserved_balance(&user), expected_deposit); + }); +} + +#[test] +fn approve_bounty_works_second_instance() { + new_test_ext().execute_with(|| { + // Set burn to 0 to make tracking funds easier. + Burn::set(Permill::from_percent(0)); + + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&Treasury1::account_id(), 201); + assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); + assert_eq!(Balances::free_balance(&Treasury1::account_id()), 201); + + assert_ok!(Bounties1::propose_bounty(RuntimeOrigin::signed(0), 10, b"12345".to_vec())); + assert_ok!(Bounties1::approve_bounty(RuntimeOrigin::root(), 0)); + >::on_initialize(2); + >::on_initialize(2); + + // Bounties 1 is funded... but from where? + assert_eq!(Balances::free_balance(Bounties1::bounty_account_id(0)), 10); + // Treasury 1 unchanged + assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); + // Treasury 2 has funds removed + assert_eq!(Balances::free_balance(&Treasury1::account_id()), 201 - 10); + }); +} + +#[test] +fn approve_bounty_insufficient_spend_limit_errors() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Treasury::pot(), 100); + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 51, b"123".to_vec())); + // 51 will not work since the limit is 50. + SpendLimit::set(50); + assert_noop!( + Bounties::approve_bounty(RuntimeOrigin::root(), 0), + TreasuryError::InsufficientPermission + ); + }); +} + +#[test] +fn approve_bounty_instance1_insufficient_spend_limit_errors() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + Balances::make_free_balance_be(&Treasury1::account_id(), 101); + assert_eq!(Treasury1::pot(), 100); + + assert_ok!(Bounties1::propose_bounty(RuntimeOrigin::signed(0), 51, b"123".to_vec())); + // 51 will not work since the limit is 50. + SpendLimit1::set(50); + assert_noop!( + Bounties1::approve_bounty(RuntimeOrigin::root(), 0), + TreasuryError1::InsufficientPermission + ); + }); +} + +#[test] +fn propose_curator_insufficient_spend_limit_errors() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + + // Temporarily set a larger spend limit; + SpendLimit::set(51); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 51, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + SpendLimit::set(50); + // 51 will not work since the limit is 50. + assert_noop!( + Bounties::propose_curator(RuntimeOrigin::root(), 0, 0, 0), + TreasuryError::InsufficientPermission + ); + }); +} + +#[test] +fn propose_curator_instance1_insufficient_spend_limit_errors() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + + // Temporarily set a larger spend limit; + SpendLimit1::set(11); + assert_ok!(Bounties1::propose_bounty(RuntimeOrigin::signed(0), 11, b"12345".to_vec())); + assert_ok!(Bounties1::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + SpendLimit1::set(10); + // 11 will not work since the limit is 10. + assert_noop!( + Bounties1::propose_curator(RuntimeOrigin::root(), 0, 0, 0), + TreasuryError1::InsufficientPermission + ); + }); +} diff --git a/frame/bounties/src/weights.rs b/frame/bounties/src/weights.rs index be93636424399..34e78bfa556e7 100644 --- a/frame/bounties/src/weights.rs +++ b/frame/bounties/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,24 +18,27 @@ //! Autogenerated weights for pallet_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark -// --chain=dev +// pallet // --steps=50 // --repeat=20 -// --pallet=pallet_bounties // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_bounties +// --chain=dev +// --header=./HEADER-APACHE2 // --output=./frame/bounties/src/weights.rs // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -61,180 +64,214 @@ pub trait WeightInfo { /// Weights for pallet_bounties using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Treasury BountyCount (r:1 w:1) + // Storage: Bounties BountyCount (r:1 w:1) // Storage: System Account (r:1 w:1) - // Storage: Treasury BountyDescriptions (r:0 w:1) - // Storage: Treasury Bounties (r:0 w:1) + // Storage: Bounties BountyDescriptions (r:0 w:1) + // Storage: Bounties Bounties (r:0 w:1) + /// The range of component `d` is `[0, 300]`. fn propose_bounty(d: u32, ) -> Weight { - (44_482_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) - } - // Storage: Treasury Bounties (r:1 w:1) - // Storage: Treasury BountyApprovals (r:1 w:1) + // Minimum execution time: 33_366 nanoseconds. + Weight::from_ref_time(34_444_773) + // Standard Error: 1_161 + .saturating_add(Weight::from_ref_time(4_723).saturating_mul(d.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Bounties Bounties (r:1 w:1) + // Storage: Bounties BountyApprovals (r:1 w:1) fn approve_bounty() -> Weight { - (11_955_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 14_478 nanoseconds. + Weight::from_ref_time(14_763_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) fn propose_curator() -> Weight { - (9_771_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 13_376 nanoseconds. + Weight::from_ref_time(13_705_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - (40_683_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 38_072 nanoseconds. + Weight::from_ref_time(38_676_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - (36_390_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 33_207 nanoseconds. + Weight::from_ref_time(34_415_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) + // Storage: ChildBounties ParentChildBounties (r:1 w:0) fn award_bounty() -> Weight { - (25_187_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 28_033 nanoseconds. + Weight::from_ref_time(28_343_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:3 w:3) - // Storage: Treasury BountyDescriptions (r:0 w:1) + // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + // Storage: Bounties BountyDescriptions (r:0 w:1) fn claim_bounty() -> Weight { - (124_785_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + // Minimum execution time: 75_855 nanoseconds. + Weight::from_ref_time(76_318_000) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(6)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) + // Storage: ChildBounties ParentChildBounties (r:1 w:0) // Storage: System Account (r:1 w:1) - // Storage: Treasury BountyDescriptions (r:0 w:1) + // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_proposed() -> Weight { - (39_483_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 41_955 nanoseconds. + Weight::from_ref_time(42_733_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) + // Storage: ChildBounties ParentChildBounties (r:1 w:0) // Storage: System Account (r:2 w:2) - // Storage: Treasury BountyDescriptions (r:0 w:1) + // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_active() -> Weight { - (83_453_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 58_267 nanoseconds. + Weight::from_ref_time(59_604_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) fn extend_bounty_expiry() -> Weight { - (24_151_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 24_893 nanoseconds. + Weight::from_ref_time(25_299_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: Treasury BountyApprovals (r:1 w:1) - // Storage: Treasury Bounties (r:1 w:1) - // Storage: System Account (r:2 w:2) + // Storage: Bounties BountyApprovals (r:1 w:1) + // Storage: Bounties Bounties (r:2 w:2) + // Storage: System Account (r:4 w:4) + /// The range of component `b` is `[0, 100]`. fn spend_funds(b: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 16_000 - .saturating_add((58_004_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(b as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(b as Weight))) + // Minimum execution time: 8_846 nanoseconds. + Weight::from_ref_time(20_166_004) + // Standard Error: 28_485 + .saturating_add(Weight::from_ref_time(26_712_253).saturating_mul(b.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(b.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(b.into()))) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Treasury BountyCount (r:1 w:1) + // Storage: Bounties BountyCount (r:1 w:1) // Storage: System Account (r:1 w:1) - // Storage: Treasury BountyDescriptions (r:0 w:1) - // Storage: Treasury Bounties (r:0 w:1) + // Storage: Bounties BountyDescriptions (r:0 w:1) + // Storage: Bounties Bounties (r:0 w:1) + /// The range of component `d` is `[0, 300]`. fn propose_bounty(d: u32, ) -> Weight { - (44_482_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) - } - // Storage: Treasury Bounties (r:1 w:1) - // Storage: Treasury BountyApprovals (r:1 w:1) + // Minimum execution time: 33_366 nanoseconds. + Weight::from_ref_time(34_444_773) + // Standard Error: 1_161 + .saturating_add(Weight::from_ref_time(4_723).saturating_mul(d.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Bounties Bounties (r:1 w:1) + // Storage: Bounties BountyApprovals (r:1 w:1) fn approve_bounty() -> Weight { - (11_955_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 14_478 nanoseconds. + Weight::from_ref_time(14_763_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) fn propose_curator() -> Weight { - (9_771_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 13_376 nanoseconds. + Weight::from_ref_time(13_705_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - (40_683_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 38_072 nanoseconds. + Weight::from_ref_time(38_676_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - (36_390_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 33_207 nanoseconds. + Weight::from_ref_time(34_415_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) + // Storage: ChildBounties ParentChildBounties (r:1 w:0) fn award_bounty() -> Weight { - (25_187_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 28_033 nanoseconds. + Weight::from_ref_time(28_343_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:3 w:3) - // Storage: Treasury BountyDescriptions (r:0 w:1) + // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + // Storage: Bounties BountyDescriptions (r:0 w:1) fn claim_bounty() -> Weight { - (124_785_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + // Minimum execution time: 75_855 nanoseconds. + Weight::from_ref_time(76_318_000) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(6)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) + // Storage: ChildBounties ParentChildBounties (r:1 w:0) // Storage: System Account (r:1 w:1) - // Storage: Treasury BountyDescriptions (r:0 w:1) + // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_proposed() -> Weight { - (39_483_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 41_955 nanoseconds. + Weight::from_ref_time(42_733_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) + // Storage: ChildBounties ParentChildBounties (r:1 w:0) // Storage: System Account (r:2 w:2) - // Storage: Treasury BountyDescriptions (r:0 w:1) + // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_active() -> Weight { - (83_453_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 58_267 nanoseconds. + Weight::from_ref_time(59_604_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } - // Storage: Treasury Bounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:1) fn extend_bounty_expiry() -> Weight { - (24_151_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 24_893 nanoseconds. + Weight::from_ref_time(25_299_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: Treasury BountyApprovals (r:1 w:1) - // Storage: Treasury Bounties (r:1 w:1) - // Storage: System Account (r:2 w:2) + // Storage: Bounties BountyApprovals (r:1 w:1) + // Storage: Bounties Bounties (r:2 w:2) + // Storage: System Account (r:4 w:4) + /// The range of component `b` is `[0, 100]`. fn spend_funds(b: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 16_000 - .saturating_add((58_004_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(b as Weight))) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(b as Weight))) + // Minimum execution time: 8_846 nanoseconds. + Weight::from_ref_time(20_166_004) + // Standard Error: 28_485 + .saturating_add(Weight::from_ref_time(26_712_253).saturating_mul(b.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(b.into()))) + .saturating_add(RocksDbWeight::get().writes(1)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(b.into()))) } } diff --git a/frame/child-bounties/Cargo.toml b/frame/child-bounties/Cargo.toml new file mode 100644 index 0000000000000..6b0a672d04225 --- /dev/null +++ b/frame/child-bounties/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "pallet-child-bounties" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet to manage child bounties" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ + "derive", +] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-bounties = { version = "4.0.0-dev", default-features = false, path = "../bounties" } +pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } + +[features] +default = ["std"] +std = [ + "frame-benchmarking?/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-bounties/std", + "pallet-treasury/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/child-bounties/README.md b/frame/child-bounties/README.md new file mode 100644 index 0000000000000..695b6616b1751 --- /dev/null +++ b/frame/child-bounties/README.md @@ -0,0 +1,29 @@ +# Child Bounties Pallet ( `pallet-child-bounties` ) + +## Child Bounty + +> NOTE: This pallet is tightly coupled with `pallet-treasury` and `pallet-bounties`. + +With child bounties, a large bounty proposal can be divided into smaller chunks, +for parallel execution, and for efficient governance and tracking of spent funds. +A child bounty is a smaller piece of work, extracted from a parent bounty. +A curator is assigned after the child bounty is created by the parent bounty curator, +to be delegated with the responsibility of assigning a payout address once +the specified set of tasks is completed. + +## Interface + +### Dispatchable Functions + +Child Bounty protocol: + +- `add_child_bounty` - Add a child bounty for a parent bounty to for dividing the work in + smaller tasks. +- `propose_curator` - Assign an account to a child bounty as candidate curator. +- `accept_curator` - Accept a child bounty assignment from the parent bounty curator, + setting a curator deposit. +- `award_child_bounty` - Close and pay out the specified amount for the completed work. +- `claim_child_bounty` - Claim a specific child bounty amount from the payout address. +- `unassign_curator` - Unassign an accepted curator from a specific child bounty. +- `close_child_bounty` - Cancel the child bounty for a specific treasury amount + and close the bounty. diff --git a/frame/child-bounties/src/benchmarking.rs b/frame/child-bounties/src/benchmarking.rs new file mode 100644 index 0000000000000..04a8583f286f1 --- /dev/null +++ b/frame/child-bounties/src/benchmarking.rs @@ -0,0 +1,310 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Child-bounties pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{account, benchmarks, whitelisted_caller, BenchmarkError}; +use frame_system::RawOrigin; + +use crate::Pallet as ChildBounties; +use pallet_bounties::Pallet as Bounties; +use pallet_treasury::Pallet as Treasury; + +const SEED: u32 = 0; + +#[derive(Clone)] +struct BenchmarkChildBounty { + /// Bounty ID. + bounty_id: BountyIndex, + /// ChildBounty ID. + child_bounty_id: BountyIndex, + /// The account proposing it. + caller: T::AccountId, + /// The master curator account. + curator: T::AccountId, + /// The child-bounty curator account. + child_curator: T::AccountId, + /// The (total) amount that should be paid if the bounty is rewarded. + value: BalanceOf, + /// The curator fee. included in value. + fee: BalanceOf, + /// The (total) amount that should be paid if the child-bounty is rewarded. + child_bounty_value: BalanceOf, + /// The child-bounty curator fee. included in value. + child_bounty_fee: BalanceOf, + /// Bounty description. + reason: Vec, +} + +fn setup_bounty( + user: u32, + description: u32, +) -> (T::AccountId, T::AccountId, BalanceOf, BalanceOf, Vec) { + let caller = account("caller", user, SEED); + let value: BalanceOf = T::BountyValueMinimum::get().saturating_mul(100u32.into()); + let fee = value / 2u32.into(); + let deposit = T::BountyDepositBase::get() + + T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into(); + let _ = T::Currency::make_free_balance_be(&caller, deposit); + let curator = account("curator", user, SEED); + let _ = T::Currency::make_free_balance_be(&curator, fee / 2u32.into()); + let reason = vec![0; description as usize]; + (caller, curator, fee, value, reason) +} + +fn setup_child_bounty(user: u32, description: u32) -> BenchmarkChildBounty { + let (caller, curator, fee, value, reason) = setup_bounty::(user, description); + let child_curator = account("child-curator", user, SEED); + let _ = T::Currency::make_free_balance_be(&child_curator, fee / 2u32.into()); + let child_bounty_value = (value - fee) / 4u32.into(); + let child_bounty_fee = child_bounty_value / 2u32.into(); + + BenchmarkChildBounty:: { + bounty_id: 0, + child_bounty_id: 0, + caller, + curator, + child_curator, + value, + fee, + child_bounty_value, + child_bounty_fee, + reason, + } +} + +fn activate_bounty( + user: u32, + description: u32, +) -> Result, BenchmarkError> { + let mut child_bounty_setup = setup_child_bounty::(user, description); + let curator_lookup = T::Lookup::unlookup(child_bounty_setup.curator.clone()); + Bounties::::propose_bounty( + RawOrigin::Signed(child_bounty_setup.caller.clone()).into(), + child_bounty_setup.value, + child_bounty_setup.reason.clone(), + )?; + + child_bounty_setup.bounty_id = Bounties::::bounty_count() - 1; + + let approve_origin = + T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + Bounties::::approve_bounty(approve_origin, child_bounty_setup.bounty_id)?; + Treasury::::on_initialize(T::BlockNumber::zero()); + Bounties::::propose_curator( + RawOrigin::Root.into(), + child_bounty_setup.bounty_id, + curator_lookup, + child_bounty_setup.fee, + )?; + Bounties::::accept_curator( + RawOrigin::Signed(child_bounty_setup.curator.clone()).into(), + child_bounty_setup.bounty_id, + )?; + + Ok(child_bounty_setup) +} + +fn activate_child_bounty( + user: u32, + description: u32, +) -> Result, BenchmarkError> { + let mut bounty_setup = activate_bounty::(user, description)?; + let child_curator_lookup = T::Lookup::unlookup(bounty_setup.child_curator.clone()); + + ChildBounties::::add_child_bounty( + RawOrigin::Signed(bounty_setup.curator.clone()).into(), + bounty_setup.bounty_id, + bounty_setup.child_bounty_value, + bounty_setup.reason.clone(), + )?; + + bounty_setup.child_bounty_id = ChildBountyCount::::get() - 1; + + ChildBounties::::propose_curator( + RawOrigin::Signed(bounty_setup.curator.clone()).into(), + bounty_setup.bounty_id, + bounty_setup.child_bounty_id, + child_curator_lookup, + bounty_setup.child_bounty_fee, + )?; + + ChildBounties::::accept_curator( + RawOrigin::Signed(bounty_setup.child_curator.clone()).into(), + bounty_setup.bounty_id, + bounty_setup.child_bounty_id, + )?; + + Ok(bounty_setup) +} + +fn setup_pot_account() { + let pot_account = Bounties::::account_id(); + let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000u32.into()); + let _ = T::Currency::make_free_balance_be(&pot_account, value); +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +benchmarks! { + add_child_bounty { + let d in 0 .. T::MaximumReasonLength::get(); + setup_pot_account::(); + let bounty_setup = activate_bounty::(0, d)?; + }: _(RawOrigin::Signed(bounty_setup.curator), bounty_setup.bounty_id, + bounty_setup.child_bounty_value, bounty_setup.reason.clone()) + verify { + assert_last_event::(Event::Added { + index: bounty_setup.bounty_id, + child_index: bounty_setup.child_bounty_id, + }.into()) + } + + propose_curator { + setup_pot_account::(); + let bounty_setup = activate_bounty::(0, T::MaximumReasonLength::get())?; + let child_curator_lookup = T::Lookup::unlookup(bounty_setup.child_curator.clone()); + + ChildBounties::::add_child_bounty( + RawOrigin::Signed(bounty_setup.curator.clone()).into(), + bounty_setup.bounty_id, + bounty_setup.child_bounty_value, + bounty_setup.reason.clone(), + )?; + let child_bounty_id = ChildBountyCount::::get() - 1; + + }: _(RawOrigin::Signed(bounty_setup.curator), bounty_setup.bounty_id, + child_bounty_id, child_curator_lookup, bounty_setup.child_bounty_fee) + + accept_curator { + setup_pot_account::(); + let mut bounty_setup = activate_bounty::(0, T::MaximumReasonLength::get())?; + let child_curator_lookup = T::Lookup::unlookup(bounty_setup.child_curator.clone()); + + ChildBounties::::add_child_bounty( + RawOrigin::Signed(bounty_setup.curator.clone()).into(), + bounty_setup.bounty_id, + bounty_setup.child_bounty_value, + bounty_setup.reason.clone(), + )?; + bounty_setup.child_bounty_id = ChildBountyCount::::get() - 1; + + ChildBounties::::propose_curator( + RawOrigin::Signed(bounty_setup.curator.clone()).into(), + bounty_setup.bounty_id, + bounty_setup.child_bounty_id, + child_curator_lookup, + bounty_setup.child_bounty_fee, + )?; + }: _(RawOrigin::Signed(bounty_setup.child_curator), bounty_setup.bounty_id, + bounty_setup.child_bounty_id) + + // Worst case when curator is inactive and any sender un-assigns the curator. + unassign_curator { + setup_pot_account::(); + let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; + Treasury::::on_initialize(T::BlockNumber::zero()); + frame_system::Pallet::::set_block_number(T::BountyUpdatePeriod::get() + 1u32.into()); + let caller = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), bounty_setup.bounty_id, + bounty_setup.child_bounty_id) + + award_child_bounty { + setup_pot_account::(); + let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; + let beneficiary_account: T::AccountId = account("beneficiary", 0, SEED); + let beneficiary = T::Lookup::unlookup(beneficiary_account.clone()); + }: _(RawOrigin::Signed(bounty_setup.child_curator), bounty_setup.bounty_id, + bounty_setup.child_bounty_id, beneficiary) + verify { + assert_last_event::(Event::Awarded { + index: bounty_setup.bounty_id, + child_index: bounty_setup.child_bounty_id, + beneficiary: beneficiary_account + }.into()) + } + + claim_child_bounty { + setup_pot_account::(); + let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; + let beneficiary_account: T::AccountId = account("beneficiary", 0, SEED); + let beneficiary = T::Lookup::unlookup(beneficiary_account); + + ChildBounties::::award_child_bounty( + RawOrigin::Signed(bounty_setup.child_curator.clone()).into(), + bounty_setup.bounty_id, + bounty_setup.child_bounty_id, + beneficiary + )?; + + let beneficiary_account: T::AccountId = account("beneficiary", 0, SEED); + let beneficiary = T::Lookup::unlookup(beneficiary_account.clone()); + + frame_system::Pallet::::set_block_number(T::BountyDepositPayoutDelay::get()); + ensure!(T::Currency::free_balance(&beneficiary_account).is_zero(), + "Beneficiary already has balance."); + + }: _(RawOrigin::Signed(bounty_setup.curator), bounty_setup.bounty_id, + bounty_setup.child_bounty_id) + verify { + ensure!(!T::Currency::free_balance(&beneficiary_account).is_zero(), + "Beneficiary didn't get paid."); + } + + // Best case scenario. + close_child_bounty_added { + setup_pot_account::(); + let mut bounty_setup = activate_bounty::(0, T::MaximumReasonLength::get())?; + + ChildBounties::::add_child_bounty( + RawOrigin::Signed(bounty_setup.curator.clone()).into(), + bounty_setup.bounty_id, + bounty_setup.child_bounty_value, + bounty_setup.reason.clone(), + )?; + bounty_setup.child_bounty_id = ChildBountyCount::::get() - 1; + + }: close_child_bounty(RawOrigin::Root, bounty_setup.bounty_id, + bounty_setup.child_bounty_id) + verify { + assert_last_event::(Event::Canceled { + index: bounty_setup.bounty_id, + child_index: bounty_setup.child_bounty_id + }.into()) + } + + // Worst case scenario. + close_child_bounty_active { + setup_pot_account::(); + let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; + Treasury::::on_initialize(T::BlockNumber::zero()); + }: close_child_bounty(RawOrigin::Root, bounty_setup.bounty_id, bounty_setup.child_bounty_id) + verify { + assert_last_event::(Event::Canceled { + index: bounty_setup.bounty_id, + child_index: bounty_setup.child_bounty_id, + }.into()) + } + + impl_benchmark_test_suite!(ChildBounties, crate::tests::new_test_ext(), crate::tests::Test) +} diff --git a/frame/child-bounties/src/lib.rs b/frame/child-bounties/src/lib.rs new file mode 100644 index 0000000000000..9eb784eaccd23 --- /dev/null +++ b/frame/child-bounties/src/lib.rs @@ -0,0 +1,915 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! # Child Bounties Pallet ( `pallet-child-bounties` ) +//! +//! ## Child Bounty +//! +//! > NOTE: This pallet is tightly coupled with `pallet-treasury` and `pallet-bounties`. +//! +//! With child bounties, a large bounty proposal can be divided into smaller chunks, +//! for parallel execution, and for efficient governance and tracking of spent funds. +//! A child bounty is a smaller piece of work, extracted from a parent bounty. +//! A curator is assigned after the child bounty is created by the parent bounty curator, +//! to be delegated with the responsibility of assigning a payout address once the specified +//! set of tasks is completed. +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! +//! Child Bounty protocol: +//! - `add_child_bounty` - Add a child bounty for a parent bounty to for dividing the work in +//! smaller tasks. +//! - `propose_curator` - Assign an account to a child bounty as candidate curator. +//! - `accept_curator` - Accept a child bounty assignment from the parent bounty curator, setting a +//! curator deposit. +//! - `award_child_bounty` - Close and pay out the specified amount for the completed work. +//! - `claim_child_bounty` - Claim a specific child bounty amount from the payout address. +//! - `unassign_curator` - Unassign an accepted curator from a specific child bounty. +//! - `close_child_bounty` - Cancel the child bounty for a specific treasury amount and close the +//! bounty. + +// Most of the business logic in this pallet has been +// originally contributed by "https://github.com/shamb0", +// as part of the PR - https://github.com/paritytech/substrate/pull/7965. +// The code has been moved here and then refactored in order to +// extract child bounties as a separate pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +mod tests; +pub mod weights; + +use sp_std::prelude::*; + +use frame_support::traits::{ + Currency, + ExistenceRequirement::{AllowDeath, KeepAlive}, + Get, OnUnbalanced, ReservableCurrency, WithdrawReasons, +}; + +use sp_runtime::{ + traits::{AccountIdConversion, BadOrigin, CheckedSub, Saturating, StaticLookup, Zero}, + DispatchResult, RuntimeDebug, +}; + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; +use pallet_bounties::BountyStatus; +use scale_info::TypeInfo; +pub use weights::WeightInfo; + +pub use pallet::*; + +type BalanceOf = pallet_treasury::BalanceOf; +type BountiesError = pallet_bounties::Error; +type BountyIndex = pallet_bounties::BountyIndex; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +/// A child bounty proposal. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ChildBounty { + /// The parent of this child-bounty. + parent_bounty: BountyIndex, + /// The (total) amount that should be paid if this child-bounty is rewarded. + value: Balance, + /// The child bounty curator fee. + fee: Balance, + /// The deposit of child-bounty curator. + curator_deposit: Balance, + /// The status of this child-bounty. + status: ChildBountyStatus, +} + +/// The status of a child-bounty. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum ChildBountyStatus { + /// The child-bounty is added and waiting for curator assignment. + Added, + /// A curator has been proposed by the parent bounty curator. Waiting for + /// acceptance from the child-bounty curator. + CuratorProposed { + /// The assigned child-bounty curator of this bounty. + curator: AccountId, + }, + /// The child-bounty is active and waiting to be awarded. + Active { + /// The curator of this child-bounty. + curator: AccountId, + }, + /// The child-bounty is awarded and waiting to released after a delay. + PendingPayout { + /// The curator of this child-bounty. + curator: AccountId, + /// The beneficiary of the child-bounty. + beneficiary: AccountId, + /// When the child-bounty can be claimed. + unlock_at: BlockNumber, + }, +} + +#[frame_support::pallet] +pub mod pallet { + + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: + frame_system::Config + pallet_treasury::Config + pallet_bounties::Config + { + /// Maximum number of child bounties that can be added to a parent bounty. + #[pallet::constant] + type MaxActiveChildBountyCount: Get; + + /// Minimum value for a child-bounty. + #[pallet::constant] + type ChildBountyValueMinimum: Get>; + + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The parent bounty is not in active state. + ParentBountyNotActive, + /// The bounty balance is not enough to add new child-bounty. + InsufficientBountyBalance, + /// Number of child bounties exceeds limit `MaxActiveChildBountyCount`. + TooManyChildBounties, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A child-bounty is added. + Added { index: BountyIndex, child_index: BountyIndex }, + /// A child-bounty is awarded to a beneficiary. + Awarded { index: BountyIndex, child_index: BountyIndex, beneficiary: T::AccountId }, + /// A child-bounty is claimed by beneficiary. + Claimed { + index: BountyIndex, + child_index: BountyIndex, + payout: BalanceOf, + beneficiary: T::AccountId, + }, + /// A child-bounty is cancelled. + Canceled { index: BountyIndex, child_index: BountyIndex }, + } + + /// Number of total child bounties. + #[pallet::storage] + #[pallet::getter(fn child_bounty_count)] + pub type ChildBountyCount = StorageValue<_, BountyIndex, ValueQuery>; + + /// Number of child bounties per parent bounty. + /// Map of parent bounty index to number of child bounties. + #[pallet::storage] + #[pallet::getter(fn parent_child_bounties)] + pub type ParentChildBounties = + StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>; + + /// Child bounties that have been added. + #[pallet::storage] + #[pallet::getter(fn child_bounties)] + pub type ChildBounties = StorageDoubleMap< + _, + Twox64Concat, + BountyIndex, + Twox64Concat, + BountyIndex, + ChildBounty, T::BlockNumber>, + >; + + /// The description of each child-bounty. + #[pallet::storage] + #[pallet::getter(fn child_bounty_descriptions)] + pub type ChildBountyDescriptions = + StorageMap<_, Twox64Concat, BountyIndex, BoundedVec>; + + /// The cumulative child-bounty curator fee for each parent bounty. + #[pallet::storage] + #[pallet::getter(fn children_curator_fees)] + pub type ChildrenCuratorFees = + StorageMap<_, Twox64Concat, BountyIndex, BalanceOf, ValueQuery>; + + #[pallet::call] + impl Pallet { + /// Add a new child-bounty. + /// + /// The dispatch origin for this call must be the curator of parent + /// bounty and the parent bounty must be in "active" state. + /// + /// Child-bounty gets added successfully & fund gets transferred from + /// parent bounty to child-bounty account, if parent bounty has enough + /// funds, else the call fails. + /// + /// Upper bound to maximum number of active child bounties that can be + /// added are managed via runtime trait config + /// [`Config::MaxActiveChildBountyCount`]. + /// + /// If the call is success, the status of child-bounty is updated to + /// "Added". + /// + /// - `parent_bounty_id`: Index of parent bounty for which child-bounty is being added. + /// - `value`: Value for executing the proposal. + /// - `description`: Text description for the child-bounty. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::add_child_bounty(description.len() as u32))] + pub fn add_child_bounty( + origin: OriginFor, + #[pallet::compact] parent_bounty_id: BountyIndex, + #[pallet::compact] value: BalanceOf, + description: Vec, + ) -> DispatchResult { + let signer = ensure_signed(origin)?; + + // Verify the arguments. + let bounded_description = + description.try_into().map_err(|_| BountiesError::::ReasonTooBig)?; + ensure!(value >= T::ChildBountyValueMinimum::get(), BountiesError::::InvalidValue); + ensure!( + Self::parent_child_bounties(parent_bounty_id) <= + T::MaxActiveChildBountyCount::get() as u32, + Error::::TooManyChildBounties, + ); + + let (curator, _) = Self::ensure_bounty_active(parent_bounty_id)?; + ensure!(signer == curator, BountiesError::::RequireCurator); + + // Read parent bounty account info. + let parent_bounty_account = + pallet_bounties::Pallet::::bounty_account_id(parent_bounty_id); + + // Ensure parent bounty has enough balance after adding child-bounty. + let bounty_balance = T::Currency::free_balance(&parent_bounty_account); + let new_bounty_balance = bounty_balance + .checked_sub(&value) + .ok_or(Error::::InsufficientBountyBalance)?; + T::Currency::ensure_can_withdraw( + &parent_bounty_account, + value, + WithdrawReasons::TRANSFER, + new_bounty_balance, + )?; + + // Get child-bounty ID. + let child_bounty_id = Self::child_bounty_count(); + let child_bounty_account = Self::child_bounty_account_id(child_bounty_id); + + // Transfer funds from parent bounty to child-bounty. + T::Currency::transfer(&parent_bounty_account, &child_bounty_account, value, KeepAlive)?; + + // Increment the active child-bounty count. + >::mutate(parent_bounty_id, |count| count.saturating_inc()); + >::put(child_bounty_id.saturating_add(1)); + + // Create child-bounty instance. + Self::create_child_bounty( + parent_bounty_id, + child_bounty_id, + value, + bounded_description, + ); + Ok(()) + } + + /// Propose curator for funded child-bounty. + /// + /// The dispatch origin for this call must be curator of parent bounty. + /// + /// Parent bounty must be in active state, for this child-bounty call to + /// work. + /// + /// Child-bounty must be in "Added" state, for processing the call. And + /// state of child-bounty is moved to "CuratorProposed" on successful + /// call completion. + /// + /// - `parent_bounty_id`: Index of parent bounty. + /// - `child_bounty_id`: Index of child bounty. + /// - `curator`: Address of child-bounty curator. + /// - `fee`: payment fee to child-bounty curator for execution. + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::propose_curator())] + pub fn propose_curator( + origin: OriginFor, + #[pallet::compact] parent_bounty_id: BountyIndex, + #[pallet::compact] child_bounty_id: BountyIndex, + curator: AccountIdLookupOf, + #[pallet::compact] fee: BalanceOf, + ) -> DispatchResult { + let signer = ensure_signed(origin)?; + let child_bounty_curator = T::Lookup::lookup(curator)?; + + let (curator, _) = Self::ensure_bounty_active(parent_bounty_id)?; + ensure!(signer == curator, BountiesError::::RequireCurator); + + // Mutate the child-bounty instance. + ChildBounties::::try_mutate_exists( + parent_bounty_id, + child_bounty_id, + |maybe_child_bounty| -> DispatchResult { + let mut child_bounty = + maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; + + // Ensure child-bounty is in expected state. + ensure!( + child_bounty.status == ChildBountyStatus::Added, + BountiesError::::UnexpectedStatus, + ); + + // Ensure child-bounty curator fee is less than child-bounty value. + ensure!(fee < child_bounty.value, BountiesError::::InvalidFee); + + // Add child-bounty curator fee to the cumulative sum. To be + // subtracted from the parent bounty curator when claiming + // bounty. + ChildrenCuratorFees::::mutate(parent_bounty_id, |value| { + *value = value.saturating_add(fee) + }); + + // Update the child-bounty curator fee. + child_bounty.fee = fee; + + // Update the child-bounty state. + child_bounty.status = + ChildBountyStatus::CuratorProposed { curator: child_bounty_curator }; + + Ok(()) + }, + ) + } + + /// Accept the curator role for the child-bounty. + /// + /// The dispatch origin for this call must be the curator of this + /// child-bounty. + /// + /// A deposit will be reserved from the curator and refund upon + /// successful payout or cancellation. + /// + /// Fee for curator is deducted from curator fee of parent bounty. + /// + /// Parent bounty must be in active state, for this child-bounty call to + /// work. + /// + /// Child-bounty must be in "CuratorProposed" state, for processing the + /// call. And state of child-bounty is moved to "Active" on successful + /// call completion. + /// + /// - `parent_bounty_id`: Index of parent bounty. + /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::accept_curator())] + pub fn accept_curator( + origin: OriginFor, + #[pallet::compact] parent_bounty_id: BountyIndex, + #[pallet::compact] child_bounty_id: BountyIndex, + ) -> DispatchResult { + let signer = ensure_signed(origin)?; + + let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?; + // Mutate child-bounty. + ChildBounties::::try_mutate_exists( + parent_bounty_id, + child_bounty_id, + |maybe_child_bounty| -> DispatchResult { + let mut child_bounty = + maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; + + // Ensure child-bounty is in expected state. + if let ChildBountyStatus::CuratorProposed { ref curator } = child_bounty.status + { + ensure!(signer == *curator, BountiesError::::RequireCurator); + + // Reserve child-bounty curator deposit. + let deposit = Self::calculate_curator_deposit( + &parent_curator, + curator, + &child_bounty.fee, + ); + + T::Currency::reserve(curator, deposit)?; + child_bounty.curator_deposit = deposit; + + child_bounty.status = + ChildBountyStatus::Active { curator: curator.clone() }; + Ok(()) + } else { + Err(BountiesError::::UnexpectedStatus.into()) + } + }, + ) + } + + /// Unassign curator from a child-bounty. + /// + /// The dispatch origin for this call can be either `RejectOrigin`, or + /// the curator of the parent bounty, or any signed origin. + /// + /// For the origin other than T::RejectOrigin and the child-bounty + /// curator, parent bounty must be in active state, for this call to + /// work. We allow child-bounty curator and T::RejectOrigin to execute + /// this call irrespective of the parent bounty state. + /// + /// If this function is called by the `RejectOrigin` or the + /// parent bounty curator, we assume that the child-bounty curator is + /// malicious or inactive. As a result, child-bounty curator deposit is + /// slashed. + /// + /// If the origin is the child-bounty curator, we take this as a sign + /// that they are unable to do their job, and are willingly giving up. + /// We could slash the deposit, but for now we allow them to unreserve + /// their deposit and exit without issue. (We may want to change this if + /// it is abused.) + /// + /// Finally, the origin can be anyone iff the child-bounty curator is + /// "inactive". Expiry update due of parent bounty is used to estimate + /// inactive state of child-bounty curator. + /// + /// This allows anyone in the community to call out that a child-bounty + /// curator is not doing their due diligence, and we should pick a new + /// one. In this case the child-bounty curator deposit is slashed. + /// + /// State of child-bounty is moved to Added state on successful call + /// completion. + /// + /// - `parent_bounty_id`: Index of parent bounty. + /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::unassign_curator())] + pub fn unassign_curator( + origin: OriginFor, + #[pallet::compact] parent_bounty_id: BountyIndex, + #[pallet::compact] child_bounty_id: BountyIndex, + ) -> DispatchResult { + let maybe_sender = ensure_signed(origin.clone()) + .map(Some) + .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?; + + ChildBounties::::try_mutate_exists( + parent_bounty_id, + child_bounty_id, + |maybe_child_bounty| -> DispatchResult { + let mut child_bounty = + maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; + + let slash_curator = |curator: &T::AccountId, + curator_deposit: &mut BalanceOf| { + let imbalance = T::Currency::slash_reserved(curator, *curator_deposit).0; + T::OnSlash::on_unbalanced(imbalance); + *curator_deposit = Zero::zero(); + }; + + match child_bounty.status { + ChildBountyStatus::Added => { + // No curator to unassign at this point. + return Err(BountiesError::::UnexpectedStatus.into()) + }, + ChildBountyStatus::CuratorProposed { ref curator } => { + // A child-bounty curator has been proposed, but not accepted yet. + // Either `RejectOrigin`, parent bounty curator or the proposed + // child-bounty curator can unassign the child-bounty curator. + ensure!( + maybe_sender.map_or(true, |sender| { + sender == *curator || + Self::ensure_bounty_active(parent_bounty_id) + .map_or(false, |(parent_curator, _)| { + sender == parent_curator + }) + }), + BadOrigin + ); + // Continue to change bounty status below. + }, + ChildBountyStatus::Active { ref curator } => { + // The child-bounty is active. + match maybe_sender { + // If the `RejectOrigin` is calling this function, slash the curator + // deposit. + None => { + slash_curator(curator, &mut child_bounty.curator_deposit); + // Continue to change child-bounty status below. + }, + Some(sender) if sender == *curator => { + // This is the child-bounty curator, willingly giving up their + // role. Give back their deposit. + T::Currency::unreserve(curator, child_bounty.curator_deposit); + // Reset curator deposit. + child_bounty.curator_deposit = Zero::zero(); + // Continue to change bounty status below. + }, + Some(sender) => { + let (parent_curator, update_due) = + Self::ensure_bounty_active(parent_bounty_id)?; + if sender == parent_curator || + update_due < frame_system::Pallet::::block_number() + { + // Slash the child-bounty curator if + // + the call is made by the parent bounty curator. + // + or the curator is inactive. + slash_curator(curator, &mut child_bounty.curator_deposit); + // Continue to change bounty status below. + } else { + // Curator has more time to give an update. + return Err(BountiesError::::Premature.into()) + } + }, + } + }, + ChildBountyStatus::PendingPayout { ref curator, .. } => { + let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?; + ensure!( + maybe_sender.map_or(true, |sender| parent_curator == sender), + BadOrigin, + ); + slash_curator(curator, &mut child_bounty.curator_deposit); + // Continue to change child-bounty status below. + }, + }; + // Move the child-bounty state to Added. + child_bounty.status = ChildBountyStatus::Added; + Ok(()) + }, + ) + } + + /// Award child-bounty to a beneficiary. + /// + /// The beneficiary will be able to claim the funds after a delay. + /// + /// The dispatch origin for this call must be the parent curator or + /// curator of this child-bounty. + /// + /// Parent bounty must be in active state, for this child-bounty call to + /// work. + /// + /// Child-bounty must be in active state, for processing the call. And + /// state of child-bounty is moved to "PendingPayout" on successful call + /// completion. + /// + /// - `parent_bounty_id`: Index of parent bounty. + /// - `child_bounty_id`: Index of child bounty. + /// - `beneficiary`: Beneficiary account. + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::award_child_bounty())] + pub fn award_child_bounty( + origin: OriginFor, + #[pallet::compact] parent_bounty_id: BountyIndex, + #[pallet::compact] child_bounty_id: BountyIndex, + beneficiary: AccountIdLookupOf, + ) -> DispatchResult { + let signer = ensure_signed(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; + + // Ensure parent bounty exists, and is active. + let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?; + + ChildBounties::::try_mutate_exists( + parent_bounty_id, + child_bounty_id, + |maybe_child_bounty| -> DispatchResult { + let mut child_bounty = + maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; + + // Ensure child-bounty is in active state. + if let ChildBountyStatus::Active { ref curator } = child_bounty.status { + ensure!( + signer == *curator || signer == parent_curator, + BountiesError::::RequireCurator, + ); + // Move the child-bounty state to pending payout. + child_bounty.status = ChildBountyStatus::PendingPayout { + curator: signer, + beneficiary: beneficiary.clone(), + unlock_at: frame_system::Pallet::::block_number() + + T::BountyDepositPayoutDelay::get(), + }; + Ok(()) + } else { + Err(BountiesError::::UnexpectedStatus.into()) + } + }, + )?; + + // Trigger the event Awarded. + Self::deposit_event(Event::::Awarded { + index: parent_bounty_id, + child_index: child_bounty_id, + beneficiary, + }); + + Ok(()) + } + + /// Claim the payout from an awarded child-bounty after payout delay. + /// + /// The dispatch origin for this call may be any signed origin. + /// + /// Call works independent of parent bounty state, No need for parent + /// bounty to be in active state. + /// + /// The Beneficiary is paid out with agreed bounty value. Curator fee is + /// paid & curator deposit is unreserved. + /// + /// Child-bounty must be in "PendingPayout" state, for processing the + /// call. And instance of child-bounty is removed from the state on + /// successful call completion. + /// + /// - `parent_bounty_id`: Index of parent bounty. + /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(5)] + #[pallet::weight(::WeightInfo::claim_child_bounty())] + pub fn claim_child_bounty( + origin: OriginFor, + #[pallet::compact] parent_bounty_id: BountyIndex, + #[pallet::compact] child_bounty_id: BountyIndex, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + + // Ensure child-bounty is in expected state. + ChildBounties::::try_mutate_exists( + parent_bounty_id, + child_bounty_id, + |maybe_child_bounty| -> DispatchResult { + let child_bounty = + maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; + + if let ChildBountyStatus::PendingPayout { + ref curator, + ref beneficiary, + ref unlock_at, + } = child_bounty.status + { + // Ensure block number is elapsed for processing the + // claim. + ensure!( + frame_system::Pallet::::block_number() >= *unlock_at, + BountiesError::::Premature, + ); + + // Make curator fee payment. + let child_bounty_account = Self::child_bounty_account_id(child_bounty_id); + let balance = T::Currency::free_balance(&child_bounty_account); + let curator_fee = child_bounty.fee.min(balance); + let payout = balance.saturating_sub(curator_fee); + + // Unreserve the curator deposit. Should not fail + // because the deposit is always reserved when curator is + // assigned. + let _ = T::Currency::unreserve(curator, child_bounty.curator_deposit); + + // Make payout to child-bounty curator. + // Should not fail because curator fee is always less than bounty value. + let fee_transfer_result = T::Currency::transfer( + &child_bounty_account, + curator, + curator_fee, + AllowDeath, + ); + debug_assert!(fee_transfer_result.is_ok()); + + // Make payout to beneficiary. + // Should not fail. + let payout_transfer_result = T::Currency::transfer( + &child_bounty_account, + beneficiary, + payout, + AllowDeath, + ); + debug_assert!(payout_transfer_result.is_ok()); + + // Trigger the Claimed event. + Self::deposit_event(Event::::Claimed { + index: parent_bounty_id, + child_index: child_bounty_id, + payout, + beneficiary: beneficiary.clone(), + }); + + // Update the active child-bounty tracking count. + >::mutate(parent_bounty_id, |count| { + count.saturating_dec() + }); + + // Remove the child-bounty description. + >::remove(child_bounty_id); + + // Remove the child-bounty instance from the state. + *maybe_child_bounty = None; + + Ok(()) + } else { + Err(BountiesError::::UnexpectedStatus.into()) + } + }, + ) + } + + /// Cancel a proposed or active child-bounty. Child-bounty account funds + /// are transferred to parent bounty account. The child-bounty curator + /// deposit may be unreserved if possible. + /// + /// The dispatch origin for this call must be either parent curator or + /// `T::RejectOrigin`. + /// + /// If the state of child-bounty is `Active`, curator deposit is + /// unreserved. + /// + /// If the state of child-bounty is `PendingPayout`, call fails & + /// returns `PendingPayout` error. + /// + /// For the origin other than T::RejectOrigin, parent bounty must be in + /// active state, for this child-bounty call to work. For origin + /// T::RejectOrigin execution is forced. + /// + /// Instance of child-bounty is removed from the state on successful + /// call completion. + /// + /// - `parent_bounty_id`: Index of parent bounty. + /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::close_child_bounty_added() + .max(::WeightInfo::close_child_bounty_active()))] + pub fn close_child_bounty( + origin: OriginFor, + #[pallet::compact] parent_bounty_id: BountyIndex, + #[pallet::compact] child_bounty_id: BountyIndex, + ) -> DispatchResult { + let maybe_sender = ensure_signed(origin.clone()) + .map(Some) + .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?; + + // Ensure parent bounty exist, get parent curator. + let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?; + + ensure!(maybe_sender.map_or(true, |sender| parent_curator == sender), BadOrigin); + + Self::impl_close_child_bounty(parent_bounty_id, child_bounty_id)?; + Ok(()) + } + } +} + +impl Pallet { + // This function will calculate the deposit of a curator. + fn calculate_curator_deposit( + parent_curator: &T::AccountId, + child_curator: &T::AccountId, + bounty_fee: &BalanceOf, + ) -> BalanceOf { + if parent_curator == child_curator { + return Zero::zero() + } + + // We just use the same logic from the parent bounties pallet. + pallet_bounties::Pallet::::calculate_curator_deposit(bounty_fee) + } + + /// The account ID of a child-bounty account. + pub fn child_bounty_account_id(id: BountyIndex) -> T::AccountId { + // This function is taken from the parent (bounties) pallet, but the + // prefix is changed to have different AccountId when the index of + // parent and child is same. + T::PalletId::get().into_sub_account_truncating(("cb", id)) + } + + fn create_child_bounty( + parent_bounty_id: BountyIndex, + child_bounty_id: BountyIndex, + child_bounty_value: BalanceOf, + description: BoundedVec, + ) { + let child_bounty = ChildBounty { + parent_bounty: parent_bounty_id, + value: child_bounty_value, + fee: 0u32.into(), + curator_deposit: 0u32.into(), + status: ChildBountyStatus::Added, + }; + ChildBounties::::insert(parent_bounty_id, child_bounty_id, &child_bounty); + ChildBountyDescriptions::::insert(child_bounty_id, description); + Self::deposit_event(Event::Added { index: parent_bounty_id, child_index: child_bounty_id }); + } + + fn ensure_bounty_active( + bounty_id: BountyIndex, + ) -> Result<(T::AccountId, T::BlockNumber), DispatchError> { + let parent_bounty = pallet_bounties::Pallet::::bounties(bounty_id) + .ok_or(BountiesError::::InvalidIndex)?; + if let BountyStatus::Active { curator, update_due } = parent_bounty.get_status() { + Ok((curator, update_due)) + } else { + Err(Error::::ParentBountyNotActive.into()) + } + } + + fn impl_close_child_bounty( + parent_bounty_id: BountyIndex, + child_bounty_id: BountyIndex, + ) -> DispatchResult { + ChildBounties::::try_mutate_exists( + parent_bounty_id, + child_bounty_id, + |maybe_child_bounty| -> DispatchResult { + let child_bounty = + maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; + + match &child_bounty.status { + ChildBountyStatus::Added | ChildBountyStatus::CuratorProposed { .. } => { + // Nothing extra to do besides the removal of the child-bounty below. + }, + ChildBountyStatus::Active { curator } => { + // Cancelled by parent curator or RejectOrigin, + // refund deposit of the working child-bounty curator. + let _ = T::Currency::unreserve(curator, child_bounty.curator_deposit); + // Then execute removal of the child-bounty below. + }, + ChildBountyStatus::PendingPayout { .. } => { + // Child-bounty is already in pending payout. If parent + // curator or RejectOrigin wants to close this + // child-bounty, it should mean the child-bounty curator + // was acting maliciously. So first unassign the + // child-bounty curator, slashing their deposit. + return Err(BountiesError::::PendingPayout.into()) + }, + } + + // Revert the curator fee back to parent bounty curator & + // reduce the active child-bounty count. + ChildrenCuratorFees::::mutate(parent_bounty_id, |value| { + *value = value.saturating_sub(child_bounty.fee) + }); + >::mutate(parent_bounty_id, |count| { + *count = count.saturating_sub(1) + }); + + // Transfer fund from child-bounty to parent bounty. + let parent_bounty_account = + pallet_bounties::Pallet::::bounty_account_id(parent_bounty_id); + let child_bounty_account = Self::child_bounty_account_id(child_bounty_id); + let balance = T::Currency::free_balance(&child_bounty_account); + let transfer_result = T::Currency::transfer( + &child_bounty_account, + &parent_bounty_account, + balance, + AllowDeath, + ); // Should not fail; child bounty account gets this balance during creation. + debug_assert!(transfer_result.is_ok()); + + // Remove the child-bounty description. + >::remove(child_bounty_id); + + *maybe_child_bounty = None; + + Self::deposit_event(Event::::Canceled { + index: parent_bounty_id, + child_index: child_bounty_id, + }); + Ok(()) + }, + ) + } +} + +// Implement ChildBountyManager to connect with the bounties pallet. This is +// where we pass the active child bounties and child curator fees to the parent +// bounty. +impl pallet_bounties::ChildBountyManager> for Pallet { + fn child_bounties_count( + bounty_id: pallet_bounties::BountyIndex, + ) -> pallet_bounties::BountyIndex { + Self::parent_child_bounties(bounty_id) + } + + fn children_curator_fees(bounty_id: pallet_bounties::BountyIndex) -> BalanceOf { + // This is asked for when the parent bounty is being claimed. No use of + // keeping it in state after that. Hence removing. + let children_fee_total = Self::children_curator_fees(bounty_id); + >::remove(bounty_id); + children_fee_total + } +} diff --git a/frame/child-bounties/src/tests.rs b/frame/child-bounties/src/tests.rs new file mode 100644 index 0000000000000..f3415c69df611 --- /dev/null +++ b/frame/child-bounties/src/tests.rs @@ -0,0 +1,1467 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Child-bounties pallet tests. + +#![cfg(test)] + +use super::*; +use crate as pallet_child_bounties; + +use frame_support::{ + assert_noop, assert_ok, + pallet_prelude::GenesisBuild, + parameter_types, + traits::{ConstU32, ConstU64, OnInitialize}, + weights::Weight, + PalletId, +}; + +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + Perbill, Permill, +}; + +use super::Event as ChildBountiesEvent; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +type BountiesError = pallet_bounties::Error; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Bounties: pallet_bounties::{Pallet, Call, Storage, Event}, + Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, + ChildBounties: pallet_child_bounties::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024); + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} + +type Balance = u64; + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u128; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); +} +parameter_types! { + pub const ProposalBond: Permill = Permill::from_percent(5); + pub const Burn: Permill = Permill::from_percent(50); + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const SpendLimit: Balance = u64::MAX; +} + +impl pallet_treasury::Config for Test { + type PalletId = TreasuryPalletId; + type Currency = pallet_balances::Pallet; + type ApproveOrigin = frame_system::EnsureRoot; + type RejectOrigin = frame_system::EnsureRoot; + type RuntimeEvent = RuntimeEvent; + type OnSlash = (); + type ProposalBond = ProposalBond; + type ProposalBondMinimum = ConstU64<1>; + type ProposalBondMaximum = (); + type SpendPeriod = ConstU64<2>; + type Burn = Burn; + type BurnDestination = (); + type WeightInfo = (); + type SpendFunds = Bounties; + type MaxApprovals = ConstU32<100>; + type SpendOrigin = frame_system::EnsureRootWithSuccess; +} +parameter_types! { + // This will be 50% of the bounty fee. + pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50); + pub const CuratorDepositMax: Balance = 1_000; + pub const CuratorDepositMin: Balance = 3; + +} +impl pallet_bounties::Config for Test { + type RuntimeEvent = RuntimeEvent; + type BountyDepositBase = ConstU64<80>; + type BountyDepositPayoutDelay = ConstU64<3>; + type BountyUpdatePeriod = ConstU64<10>; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMax = CuratorDepositMax; + type CuratorDepositMin = CuratorDepositMin; + type BountyValueMinimum = ConstU64<5>; + type DataDepositPerByte = ConstU64<1>; + type MaximumReasonLength = ConstU32<300>; + type WeightInfo = (); + type ChildBountyManager = ChildBounties; +} +impl pallet_child_bounties::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaxActiveChildBountyCount = ConstU32<2>; + type ChildBountyValueMinimum = ConstU64<1>; + type WeightInfo = (); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + // Total issuance will be 200 with treasury account initialized at ED. + balances: vec![(0, 100), (1, 98), (2, 1)], + } + .assimilate_storage(&mut t) + .unwrap(); + GenesisBuild::::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap(); + t.into() +} + +fn last_event() -> ChildBountiesEvent { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::ChildBounties(inner) = e { Some(inner) } else { None }) + .last() + .unwrap() +} + +#[test] +fn genesis_config_works() { + new_test_ext().execute_with(|| { + assert_eq!(Treasury::pot(), 0); + assert_eq!(Treasury::proposal_count(), 0); + }); +} + +#[test] +fn minting_works() { + new_test_ext().execute_with(|| { + // Check that accumulate works when we have Some value in Dummy already. + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Treasury::pot(), 100); + }); +} + +#[test] +fn add_child_bounty() { + new_test_ext().execute_with(|| { + // TestProcedure. + // 1, Create bounty & move to active state with enough bounty fund & parent curator. + // 2, Parent curator adds child-bounty child-bounty-1, test for error like RequireCurator + // ,InsufficientProposersBalance, InsufficientBountyBalance with invalid arguments. + // 3, Parent curator adds child-bounty child-bounty-1, moves to "Approved" state & + // test for the event Added. + // 4, Test for DB state of `Bounties` & `ChildBounties`. + // 5, Observe fund transaction moment between Bounty, Child-bounty, + // Curator, child-bounty curator & beneficiary. + + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + let fee = 8; + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee)); + + Balances::make_free_balance_be(&4, 10); + + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // This verifies that the accept curator logic took a deposit. + let expected_deposit = CuratorDepositMultiplier::get() * fee; + assert_eq!(Balances::reserved_balance(&4), expected_deposit); + assert_eq!(Balances::free_balance(&4), 10 - expected_deposit); + + // Add child-bounty. + // Acc-4 is the parent curator. + // Call from invalid origin & check for error "RequireCurator". + assert_noop!( + ChildBounties::add_child_bounty(RuntimeOrigin::signed(0), 0, 10, b"12345-p1".to_vec()), + BountiesError::RequireCurator, + ); + + // Update the parent curator balance. + Balances::make_free_balance_be(&4, 101); + + // parent curator fee is reserved on parent bounty account. + assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50); + assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0); + + assert_noop!( + ChildBounties::add_child_bounty(RuntimeOrigin::signed(4), 0, 50, b"12345-p1".to_vec()), + pallet_balances::Error::::KeepAlive, + ); + + assert_noop!( + ChildBounties::add_child_bounty(RuntimeOrigin::signed(4), 0, 100, b"12345-p1".to_vec()), + Error::::InsufficientBountyBalance, + ); + + // Add child-bounty with valid value, which can be funded by parent bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + + // Check for the event child-bounty added. + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + assert_eq!(Balances::free_balance(4), 101); + assert_eq!(Balances::reserved_balance(4), expected_deposit); + + // DB check. + // Check the child-bounty status. + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee: 0, + curator_deposit: 0, + status: ChildBountyStatus::Added, + } + ); + + // Check the child-bounty count. + assert_eq!(ChildBounties::parent_child_bounties(0), 1); + + // Check the child-bounty description status. + assert_eq!(ChildBounties::child_bounty_descriptions(0).unwrap(), b"12345-p1".to_vec(),); + }); +} + +#[test] +fn child_bounty_assign_curator() { + new_test_ext().execute_with(|| { + // TestProcedure + // 1, Create bounty & move to active state with enough bounty fund & parent curator. + // 2, Parent curator adds child-bounty child-bounty-1, moves to "Active" state. + // 3, Test for DB state of `ChildBounties`. + + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&4, 101); + Balances::make_free_balance_be(&8, 101); + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + let fee = 4; + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, fee)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Bounty account status before adding child-bounty. + assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50); + assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0); + + // Check the balance of parent curator. + // Curator deposit is reserved for parent curator on parent bounty. + let expected_deposit = Bounties::calculate_curator_deposit(&fee); + assert_eq!(Balances::free_balance(4), 101 - expected_deposit); + assert_eq!(Balances::reserved_balance(4), expected_deposit); + + // Add child-bounty. + // Acc-4 is the parent curator & make sure enough deposit. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + // Bounty account status after adding child-bounty. + assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 40); + assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0); + + // Child-bounty account status. + assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 10); + assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0); + + let fee = 6u64; + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, fee)); + + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: 0, + status: ChildBountyStatus::CuratorProposed { curator: 8 }, + } + ); + + // Check the balance of parent curator. + assert_eq!(Balances::free_balance(4), 101 - expected_deposit); + assert_eq!(Balances::reserved_balance(4), expected_deposit); + + assert_noop!( + ChildBounties::accept_curator(RuntimeOrigin::signed(3), 0, 0), + BountiesError::RequireCurator, + ); + + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0)); + + let expected_child_deposit = CuratorDepositMultiplier::get() * fee; + + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: expected_child_deposit, + status: ChildBountyStatus::Active { curator: 8 }, + } + ); + + // Deposit for child-bounty curator deposit is reserved. + assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit); + assert_eq!(Balances::reserved_balance(8), expected_child_deposit); + + // Bounty account status at exit. + assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 40); + assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0); + + // Child-bounty account status at exit. + assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 10); + assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0); + + // Treasury account status at exit. + assert_eq!(Balances::free_balance(Treasury::account_id()), 26); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + }); +} + +#[test] +fn award_claim_child_bounty() { + new_test_ext().execute_with(|| { + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::free_balance(Treasury::account_id()), 101); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + + // Bounty curator initial balance. + Balances::make_free_balance_be(&4, 101); // Parent-bounty curator. + Balances::make_free_balance_be(&8, 101); // Child-bounty curator. + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Child-bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + // Propose and accept curator for child-bounty. + let fee = 8; + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, fee)); + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0)); + + // Award child-bounty. + // Test for non child-bounty curator. + assert_noop!( + ChildBounties::award_child_bounty(RuntimeOrigin::signed(3), 0, 0, 7), + BountiesError::RequireCurator, + ); + + assert_ok!(ChildBounties::award_child_bounty(RuntimeOrigin::signed(8), 0, 0, 7)); + + let expected_deposit = CuratorDepositMultiplier::get() * fee; + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: expected_deposit, + status: ChildBountyStatus::PendingPayout { + curator: 8, + beneficiary: 7, + unlock_at: 5 + }, + } + ); + + // Claim child-bounty. + // Test for Premature condition. + assert_noop!( + ChildBounties::claim_child_bounty(RuntimeOrigin::signed(7), 0, 0), + BountiesError::Premature + ); + + System::set_block_number(9); + + assert_ok!(ChildBounties::claim_child_bounty(RuntimeOrigin::signed(7), 0, 0)); + + // Ensure child-bounty curator is paid with curator fee & deposit refund. + assert_eq!(Balances::free_balance(8), 101 + fee); + assert_eq!(Balances::reserved_balance(8), 0); + + // Ensure executor is paid with beneficiary amount. + assert_eq!(Balances::free_balance(7), 10 - fee); + assert_eq!(Balances::reserved_balance(7), 0); + + // Child-bounty account status. + assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 0); + assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0); + + // Check the child-bounty count. + assert_eq!(ChildBounties::parent_child_bounties(0), 0); + }); +} + +#[test] +fn close_child_bounty_added() { + new_test_ext().execute_with(|| { + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::free_balance(Treasury::account_id()), 101); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + + // Bounty curator initial balance. + Balances::make_free_balance_be(&4, 101); // Parent-bounty curator. + Balances::make_free_balance_be(&8, 101); // Child-bounty curator. + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); + + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Child-bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + System::set_block_number(4); + + // Close child-bounty. + // Wrong origin. + assert_noop!(ChildBounties::close_child_bounty(RuntimeOrigin::signed(7), 0, 0), BadOrigin); + assert_noop!(ChildBounties::close_child_bounty(RuntimeOrigin::signed(8), 0, 0), BadOrigin); + + // Correct origin - parent curator. + assert_ok!(ChildBounties::close_child_bounty(RuntimeOrigin::signed(4), 0, 0)); + + // Check the child-bounty count. + assert_eq!(ChildBounties::parent_child_bounties(0), 0); + + // Parent-bounty account status. + assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50); + assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0); + + // Child-bounty account status. + assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 0); + assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0); + }); +} + +#[test] +fn close_child_bounty_active() { + new_test_ext().execute_with(|| { + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::free_balance(Treasury::account_id()), 101); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + + // Bounty curator initial balance. + Balances::make_free_balance_be(&4, 101); // Parent-bounty curator. + Balances::make_free_balance_be(&8, 101); // Child-bounty curator. + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); + + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Child-bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + // Propose and accept curator for child-bounty. + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, 2)); + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0)); + + // Close child-bounty in active state. + assert_ok!(ChildBounties::close_child_bounty(RuntimeOrigin::signed(4), 0, 0)); + + // Check the child-bounty count. + assert_eq!(ChildBounties::parent_child_bounties(0), 0); + + // Ensure child-bounty curator balance is unreserved. + assert_eq!(Balances::free_balance(8), 101); + assert_eq!(Balances::reserved_balance(8), 0); + + // Parent-bounty account status. + assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50); + assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0); + + // Child-bounty account status. + assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 0); + assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0); + }); +} + +#[test] +fn close_child_bounty_pending() { + new_test_ext().execute_with(|| { + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::free_balance(Treasury::account_id()), 101); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + + // Bounty curator initial balance. + Balances::make_free_balance_be(&4, 101); // Parent-bounty curator. + Balances::make_free_balance_be(&8, 101); // Child-bounty curator. + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + let parent_fee = 6; + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, parent_fee)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Child-bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + // Propose and accept curator for child-bounty. + let child_fee = 4; + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, child_fee)); + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0)); + let expected_child_deposit = CuratorDepositMin::get(); + + assert_ok!(ChildBounties::award_child_bounty(RuntimeOrigin::signed(8), 0, 0, 7)); + + // Close child-bounty in pending_payout state. + assert_noop!( + ChildBounties::close_child_bounty(RuntimeOrigin::signed(4), 0, 0), + BountiesError::PendingPayout + ); + + // Check the child-bounty count. + assert_eq!(ChildBounties::parent_child_bounties(0), 1); + + // Ensure no changes in child-bounty curator balance. + assert_eq!(Balances::reserved_balance(8), expected_child_deposit); + assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit); + + // Child-bounty account status. + assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 10); + assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0); + }); +} + +#[test] +fn child_bounty_added_unassign_curator() { + new_test_ext().execute_with(|| { + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::free_balance(Treasury::account_id()), 101); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + + // Bounty curator initial balance. + Balances::make_free_balance_be(&4, 101); // Parent-bounty curator. + Balances::make_free_balance_be(&8, 101); // Child-bounty curator. + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); + + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Child-bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + // Unassign curator in added state. + assert_noop!( + ChildBounties::unassign_curator(RuntimeOrigin::signed(4), 0, 0), + BountiesError::UnexpectedStatus + ); + }); +} + +#[test] +fn child_bounty_curator_proposed_unassign_curator() { + new_test_ext().execute_with(|| { + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::free_balance(Treasury::account_id()), 101); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + + // Bounty curator initial balance. + Balances::make_free_balance_be(&4, 101); // Parent-bounty curator. + Balances::make_free_balance_be(&8, 101); // Child-bounty curator. + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); + + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Child-bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + // Propose curator for child-bounty. + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, 2)); + + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee: 2, + curator_deposit: 0, + status: ChildBountyStatus::CuratorProposed { curator: 8 }, + } + ); + + // Random account cannot unassign the curator when in proposed state. + assert_noop!(ChildBounties::unassign_curator(RuntimeOrigin::signed(99), 0, 0), BadOrigin); + + // Unassign curator. + assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(4), 0, 0)); + + // Verify updated child-bounty status. + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee: 2, + curator_deposit: 0, + status: ChildBountyStatus::Added, + } + ); + }); +} + +#[test] +fn child_bounty_active_unassign_curator() { + // Covers all scenarios with all origin types. + // Step 1: Setup bounty, child bounty. + // Step 2: Assign, accept curator for child bounty. Unassign from reject origin. Should slash. + // Step 3: Assign, accept another curator for child bounty. Unassign from parent-bounty curator. + // Should slash. Step 4: Assign, accept another curator for child bounty. Unassign from + // child-bounty curator. Should NOT slash. Step 5: Assign, accept another curator for child + // bounty. Unassign from random account. Should slash. + new_test_ext().execute_with(|| { + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::free_balance(Treasury::account_id()), 101); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + + // Bounty curator initial balance. + Balances::make_free_balance_be(&4, 101); // Parent-bounty curator. + Balances::make_free_balance_be(&6, 101); // Child-bounty curator 1. + Balances::make_free_balance_be(&7, 101); // Child-bounty curator 2. + Balances::make_free_balance_be(&8, 101); // Child-bounty curator 3. + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Create Child-bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + System::set_block_number(3); + >::on_initialize(3); + + // Propose and accept curator for child-bounty. + let fee = 6; + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, fee)); + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0)); + let expected_child_deposit = CuratorDepositMultiplier::get() * fee; + + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: expected_child_deposit, + status: ChildBountyStatus::Active { curator: 8 }, + } + ); + + System::set_block_number(4); + >::on_initialize(4); + + // Unassign curator - from reject origin. + assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::root(), 0, 0)); + + // Verify updated child-bounty status. + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: 0, + status: ChildBountyStatus::Added, + } + ); + + // Ensure child-bounty curator was slashed. + assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit); + assert_eq!(Balances::reserved_balance(8), 0); // slashed + + // Propose and accept curator for child-bounty again. + let fee = 2; + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 7, fee)); + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(7), 0, 0)); + let expected_child_deposit = CuratorDepositMin::get(); + + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: expected_child_deposit, + status: ChildBountyStatus::Active { curator: 7 }, + } + ); + + System::set_block_number(5); + >::on_initialize(5); + + // Unassign curator again - from parent curator. + assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(4), 0, 0)); + + // Verify updated child-bounty status. + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee: 2, + curator_deposit: 0, + status: ChildBountyStatus::Added, + } + ); + + // Ensure child-bounty curator was slashed. + assert_eq!(Balances::free_balance(7), 101 - expected_child_deposit); + assert_eq!(Balances::reserved_balance(7), 0); // slashed + + // Propose and accept curator for child-bounty again. + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 6, 2)); + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(6), 0, 0)); + + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: expected_child_deposit, + status: ChildBountyStatus::Active { curator: 6 }, + } + ); + + System::set_block_number(6); + >::on_initialize(6); + + // Unassign curator again - from child-bounty curator. + assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(6), 0, 0)); + + // Verify updated child-bounty status. + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee: 2, + curator_deposit: 0, + status: ChildBountyStatus::Added, + } + ); + + // Ensure child-bounty curator was **not** slashed. + assert_eq!(Balances::free_balance(6), 101); // not slashed + assert_eq!(Balances::reserved_balance(6), 0); + + // Propose and accept curator for child-bounty one last time. + let fee = 2; + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 6, fee)); + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(6), 0, 0)); + let expected_child_deposit = CuratorDepositMin::get(); + + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: expected_child_deposit, + status: ChildBountyStatus::Active { curator: 6 }, + } + ); + + System::set_block_number(7); + >::on_initialize(7); + + // Unassign curator again - from non curator; non reject origin; some random guy. + // Bounty update period is not yet complete. + assert_noop!( + ChildBounties::unassign_curator(RuntimeOrigin::signed(3), 0, 0), + BountiesError::Premature + ); + + System::set_block_number(20); + >::on_initialize(20); + + // Unassign child curator from random account after inactivity. + assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(3), 0, 0)); + + // Verify updated child-bounty status. + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee: 2, + curator_deposit: 0, + status: ChildBountyStatus::Added, + } + ); + + // Ensure child-bounty curator was slashed. + assert_eq!(Balances::free_balance(6), 101 - expected_child_deposit); // slashed + assert_eq!(Balances::reserved_balance(6), 0); + }); +} + +#[test] +fn parent_bounty_inactive_unassign_curator_child_bounty() { + // Unassign curator when parent bounty in not in active state. + // This can happen when the curator of parent bounty has been unassigned. + new_test_ext().execute_with(|| { + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::free_balance(Treasury::account_id()), 101); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + + // Bounty curator initial balance. + Balances::make_free_balance_be(&4, 101); // Parent-bounty curator 1. + Balances::make_free_balance_be(&5, 101); // Parent-bounty curator 2. + Balances::make_free_balance_be(&6, 101); // Child-bounty curator 1. + Balances::make_free_balance_be(&7, 101); // Child-bounty curator 2. + Balances::make_free_balance_be(&8, 101); // Child-bounty curator 3. + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Create Child-bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + System::set_block_number(3); + >::on_initialize(3); + + // Propose and accept curator for child-bounty. + let fee = 8; + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, fee)); + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0)); + let expected_child_deposit = CuratorDepositMultiplier::get() * fee; + + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: expected_child_deposit, + status: ChildBountyStatus::Active { curator: 8 }, + } + ); + + System::set_block_number(4); + >::on_initialize(4); + + // Unassign parent bounty curator. + assert_ok!(Bounties::unassign_curator(RuntimeOrigin::root(), 0)); + + System::set_block_number(5); + >::on_initialize(5); + + // Try unassign child-bounty curator - from non curator; non reject + // origin; some random guy. Bounty update period is not yet complete. + assert_noop!( + ChildBounties::unassign_curator(RuntimeOrigin::signed(3), 0, 0), + Error::::ParentBountyNotActive + ); + + // Unassign curator - from reject origin. + assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::root(), 0, 0)); + + // Verify updated child-bounty status. + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: 0, + status: ChildBountyStatus::Added, + } + ); + + // Ensure child-bounty curator was slashed. + assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit); + assert_eq!(Balances::reserved_balance(8), 0); // slashed + + System::set_block_number(6); + >::on_initialize(6); + + // Propose and accept curator for parent-bounty again. + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 5, 6)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(5), 0)); + + System::set_block_number(7); + >::on_initialize(7); + + // Propose and accept curator for child-bounty again. + let fee = 2; + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(5), 0, 0, 7, fee)); + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(7), 0, 0)); + let expected_deposit = CuratorDepositMin::get(); + + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: expected_deposit, + status: ChildBountyStatus::Active { curator: 7 }, + } + ); + + System::set_block_number(8); + >::on_initialize(8); + + assert_noop!( + ChildBounties::unassign_curator(RuntimeOrigin::signed(3), 0, 0), + BountiesError::Premature + ); + + // Unassign parent bounty curator again. + assert_ok!(Bounties::unassign_curator(RuntimeOrigin::signed(5), 0)); + + System::set_block_number(9); + >::on_initialize(9); + + // Unassign curator again - from parent curator. + assert_ok!(ChildBounties::unassign_curator(RuntimeOrigin::signed(7), 0, 0)); + + // Verify updated child-bounty status. + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee: 2, + curator_deposit: 0, + status: ChildBountyStatus::Added, + } + ); + + // Ensure child-bounty curator was not slashed. + assert_eq!(Balances::free_balance(7), 101); + assert_eq!(Balances::reserved_balance(7), 0); // slashed + }); +} + +#[test] +fn close_parent_with_child_bounty() { + new_test_ext().execute_with(|| { + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::free_balance(Treasury::account_id()), 101); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + + // Bounty curator initial balance. + Balances::make_free_balance_be(&4, 101); // Parent-bounty curator. + Balances::make_free_balance_be(&8, 101); // Child-bounty curator. + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + // Try add child-bounty. + // Should fail, parent bounty not active yet. + assert_noop!( + ChildBounties::add_child_bounty(RuntimeOrigin::signed(4), 0, 10, b"12345-p1".to_vec()), + Error::::ParentBountyNotActive + ); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Child-bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + System::set_block_number(4); + >::on_initialize(4); + + // Try close parent-bounty. + // Child bounty active, can't close parent. + assert_noop!( + Bounties::close_bounty(RuntimeOrigin::root(), 0), + BountiesError::HasActiveChildBounty + ); + + System::set_block_number(2); + + // Close child-bounty. + assert_ok!(ChildBounties::close_child_bounty(RuntimeOrigin::root(), 0, 0)); + + // Check the child-bounty count. + assert_eq!(ChildBounties::parent_child_bounties(0), 0); + + // Try close parent-bounty again. + // Should pass this time. + assert_ok!(Bounties::close_bounty(RuntimeOrigin::root(), 0)); + }); +} + +#[test] +fn children_curator_fee_calculation_test() { + // Tests the calculation of subtracting child-bounty curator fee + // from parent bounty fee when claiming bounties. + new_test_ext().execute_with(|| { + // Make the parent bounty. + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::free_balance(Treasury::account_id()), 101); + assert_eq!(Balances::reserved_balance(Treasury::account_id()), 0); + + // Bounty curator initial balance. + Balances::make_free_balance_be(&4, 101); // Parent-bounty curator. + Balances::make_free_balance_be(&8, 101); // Child-bounty curator. + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), 0, 4, 6)); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(4), 0)); + + // Child-bounty. + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(4), + 0, + 10, + b"12345-p1".to_vec() + )); + assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); + + System::set_block_number(4); + >::on_initialize(4); + + let fee = 6; + + // Propose curator for child-bounty. + assert_ok!(ChildBounties::propose_curator(RuntimeOrigin::signed(4), 0, 0, 8, fee)); + // Check curator fee added to the sum. + assert_eq!(ChildBounties::children_curator_fees(0), fee); + // Accept curator for child-bounty. + assert_ok!(ChildBounties::accept_curator(RuntimeOrigin::signed(8), 0, 0)); + // Award child-bounty. + assert_ok!(ChildBounties::award_child_bounty(RuntimeOrigin::signed(8), 0, 0, 7)); + + let expected_child_deposit = CuratorDepositMultiplier::get() * fee; + + assert_eq!( + ChildBounties::child_bounties(0, 0).unwrap(), + ChildBounty { + parent_bounty: 0, + value: 10, + fee, + curator_deposit: expected_child_deposit, + status: ChildBountyStatus::PendingPayout { + curator: 8, + beneficiary: 7, + unlock_at: 7 + }, + } + ); + + System::set_block_number(9); + + // Claim child-bounty. + assert_ok!(ChildBounties::claim_child_bounty(RuntimeOrigin::signed(7), 0, 0)); + + // Check the child-bounty count. + assert_eq!(ChildBounties::parent_child_bounties(0), 0); + + // Award the parent bounty. + assert_ok!(Bounties::award_bounty(RuntimeOrigin::signed(4), 0, 9)); + + System::set_block_number(15); + + // Claim the parent bounty. + assert_ok!(Bounties::claim_bounty(RuntimeOrigin::signed(9), 0)); + + // Ensure parent-bounty curator received correctly reduced fee. + assert_eq!(Balances::free_balance(4), 101 + 6 - fee); // 101 + 6 - 2 + assert_eq!(Balances::reserved_balance(4), 0); + + // Verify parent-bounty beneficiary balance. + assert_eq!(Balances::free_balance(9), 34); + assert_eq!(Balances::reserved_balance(9), 0); + }); +} + +#[test] +fn accept_curator_handles_different_deposit_calculations() { + // This test will verify that a bounty with and without a fee results + // in a different curator deposit, and if the child curator matches the parent curator. + new_test_ext().execute_with(|| { + // Setup a parent bounty. + let parent_curator = 0; + let parent_index = 0; + let parent_value = 1_000_000; + let parent_fee = 10_000; + + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), parent_value * 3); + Balances::make_free_balance_be(&parent_curator, parent_fee * 100); + assert_ok!(Bounties::propose_bounty( + RuntimeOrigin::signed(parent_curator), + parent_value, + b"12345".to_vec() + )); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), parent_index)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator( + RuntimeOrigin::root(), + parent_index, + parent_curator, + parent_fee + )); + assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(parent_curator), parent_index)); + + // Now we can start creating some child bounties. + // Case 1: Parent and child curator are not the same. + + let child_index = 0; + let child_curator = 1; + let child_value = 1_000; + let child_fee = 100; + let starting_balance = 100 * child_fee + child_value; + + Balances::make_free_balance_be(&child_curator, starting_balance); + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(3); + >::on_initialize(3); + assert_ok!(ChildBounties::propose_curator( + RuntimeOrigin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + RuntimeOrigin::signed(child_curator), + parent_index, + child_index + )); + + let expected_deposit = CuratorDepositMultiplier::get() * child_fee; + assert_eq!(Balances::free_balance(child_curator), starting_balance - expected_deposit); + assert_eq!(Balances::reserved_balance(child_curator), expected_deposit); + + // Case 2: Parent and child curator are the same. + + let child_index = 1; + let child_curator = parent_curator; // The same as parent bounty curator + let child_value = 1_000; + let child_fee = 10; + + let free_before = Balances::free_balance(&parent_curator); + let reserved_before = Balances::reserved_balance(&parent_curator); + + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(4); + >::on_initialize(4); + assert_ok!(ChildBounties::propose_curator( + RuntimeOrigin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + RuntimeOrigin::signed(child_curator), + parent_index, + child_index + )); + + // No expected deposit + assert_eq!(Balances::free_balance(child_curator), free_before); + assert_eq!(Balances::reserved_balance(child_curator), reserved_before); + + // Case 3: Upper Limit + + let child_index = 2; + let child_curator = 2; + let child_value = 10_000; + let child_fee = 5_000; + + Balances::make_free_balance_be(&child_curator, starting_balance); + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(5); + >::on_initialize(5); + assert_ok!(ChildBounties::propose_curator( + RuntimeOrigin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + RuntimeOrigin::signed(child_curator), + parent_index, + child_index + )); + + let expected_deposit = CuratorDepositMax::get(); + assert_eq!(Balances::free_balance(child_curator), starting_balance - expected_deposit); + assert_eq!(Balances::reserved_balance(child_curator), expected_deposit); + + // There is a max number of child bounties at a time. + assert_ok!(ChildBounties::impl_close_child_bounty(parent_index, child_index)); + + // Case 4: Lower Limit + + let child_index = 3; + let child_curator = 3; + let child_value = 10_000; + let child_fee = 0; + + Balances::make_free_balance_be(&child_curator, starting_balance); + assert_ok!(ChildBounties::add_child_bounty( + RuntimeOrigin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(5); + >::on_initialize(5); + assert_ok!(ChildBounties::propose_curator( + RuntimeOrigin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + RuntimeOrigin::signed(child_curator), + parent_index, + child_index + )); + + let expected_deposit = CuratorDepositMin::get(); + assert_eq!(Balances::free_balance(child_curator), starting_balance - expected_deposit); + assert_eq!(Balances::reserved_balance(child_curator), expected_deposit); + }); +} diff --git a/frame/child-bounties/src/weights.rs b/frame/child-bounties/src/weights.rs new file mode 100644 index 0000000000000..235c84320effa --- /dev/null +++ b/frame/child-bounties/src/weights.rs @@ -0,0 +1,235 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_child_bounties +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_child_bounties +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/child-bounties/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_child_bounties. +pub trait WeightInfo { + fn add_child_bounty(d: u32, ) -> Weight; + fn propose_curator() -> Weight; + fn accept_curator() -> Weight; + fn unassign_curator() -> Weight; + fn award_child_bounty() -> Weight; + fn claim_child_bounty() -> Weight; + fn close_child_bounty_added() -> Weight; + fn close_child_bounty_active() -> Weight; +} + +/// Weights for pallet_child_bounties using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: ChildBounties ParentChildBounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: ChildBounties ChildBountyCount (r:1 w:1) + // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + // Storage: ChildBounties ChildBounties (r:0 w:1) + /// The range of component `d` is `[0, 300]`. + fn add_child_bounty(d: u32, ) -> Weight { + // Minimum execution time: 59_121 nanoseconds. + Weight::from_ref_time(60_212_235 as u64) + // Standard Error: 149 + .saturating_add(Weight::from_ref_time(412 as u64).saturating_mul(d as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: Bounties Bounties (r:1 w:0) + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + fn propose_curator() -> Weight { + // Minimum execution time: 20_785 nanoseconds. + Weight::from_ref_time(21_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Bounties Bounties (r:1 w:0) + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn accept_curator() -> Weight { + // Minimum execution time: 37_874 nanoseconds. + Weight::from_ref_time(38_322_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:0) + // Storage: System Account (r:1 w:1) + fn unassign_curator() -> Weight { + // Minimum execution time: 43_385 nanoseconds. + Weight::from_ref_time(43_774_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Bounties Bounties (r:1 w:0) + // Storage: ChildBounties ChildBounties (r:1 w:1) + fn award_child_bounty() -> Weight { + // Minimum execution time: 31_390 nanoseconds. + Weight::from_ref_time(31_802_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: System Account (r:3 w:3) + // Storage: ChildBounties ParentChildBounties (r:1 w:1) + // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + fn claim_child_bounty() -> Weight { + // Minimum execution time: 74_956 nanoseconds. + Weight::from_ref_time(75_850_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: Bounties Bounties (r:1 w:0) + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + // Storage: ChildBounties ParentChildBounties (r:1 w:1) + // Storage: System Account (r:2 w:2) + // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + fn close_child_bounty_added() -> Weight { + // Minimum execution time: 57_215 nanoseconds. + Weight::from_ref_time(58_285_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: Bounties Bounties (r:1 w:0) + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: System Account (r:3 w:3) + // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + // Storage: ChildBounties ParentChildBounties (r:1 w:1) + // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + fn close_child_bounty_active() -> Weight { + // Minimum execution time: 67_641 nanoseconds. + Weight::from_ref_time(69_184_000 as u64) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: ChildBounties ParentChildBounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: ChildBounties ChildBountyCount (r:1 w:1) + // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + // Storage: ChildBounties ChildBounties (r:0 w:1) + /// The range of component `d` is `[0, 300]`. + fn add_child_bounty(d: u32, ) -> Weight { + // Minimum execution time: 59_121 nanoseconds. + Weight::from_ref_time(60_212_235 as u64) + // Standard Error: 149 + .saturating_add(Weight::from_ref_time(412 as u64).saturating_mul(d as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: Bounties Bounties (r:1 w:0) + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + fn propose_curator() -> Weight { + // Minimum execution time: 20_785 nanoseconds. + Weight::from_ref_time(21_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Bounties Bounties (r:1 w:0) + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn accept_curator() -> Weight { + // Minimum execution time: 37_874 nanoseconds. + Weight::from_ref_time(38_322_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: Bounties Bounties (r:1 w:0) + // Storage: System Account (r:1 w:1) + fn unassign_curator() -> Weight { + // Minimum execution time: 43_385 nanoseconds. + Weight::from_ref_time(43_774_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Bounties Bounties (r:1 w:0) + // Storage: ChildBounties ChildBounties (r:1 w:1) + fn award_child_bounty() -> Weight { + // Minimum execution time: 31_390 nanoseconds. + Weight::from_ref_time(31_802_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: System Account (r:3 w:3) + // Storage: ChildBounties ParentChildBounties (r:1 w:1) + // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + fn claim_child_bounty() -> Weight { + // Minimum execution time: 74_956 nanoseconds. + Weight::from_ref_time(75_850_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: Bounties Bounties (r:1 w:0) + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + // Storage: ChildBounties ParentChildBounties (r:1 w:1) + // Storage: System Account (r:2 w:2) + // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + fn close_child_bounty_added() -> Weight { + // Minimum execution time: 57_215 nanoseconds. + Weight::from_ref_time(58_285_000 as u64) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: Bounties Bounties (r:1 w:0) + // Storage: ChildBounties ChildBounties (r:1 w:1) + // Storage: System Account (r:3 w:3) + // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + // Storage: ChildBounties ParentChildBounties (r:1 w:1) + // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + fn close_child_bounty_active() -> Weight { + // Minimum execution time: 67_641 nanoseconds. + Weight::from_ref_time(69_184_000 as u64) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } +} diff --git a/frame/collective/Cargo.toml b/frame/collective/Cargo.toml index e88f28d417730..0e8c5421f5ff1 100644 --- a/frame/collective/Cargo.toml +++ b/frame/collective/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-collective" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins." readme = "README.md" @@ -13,37 +13,35 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.14", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } - -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] std = [ "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", "log/std", "scale-info/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", ] runtime-benchmarks = [ - "frame-benchmarking", - "sp-runtime/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/collective/src/benchmarking.rs b/frame/collective/src/benchmarking.rs index c7e695babf27d..75a724623002e 100644 --- a/frame/collective/src/benchmarking.rs +++ b/frame/collective/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,90 +23,91 @@ use crate::Pallet as Collective; use sp_runtime::traits::Bounded; use sp_std::mem::size_of; -use frame_benchmarking::{ - account, benchmarks_instance_pallet, impl_benchmark_test_suite, whitelisted_caller, -}; +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; use frame_system::{Call as SystemCall, Pallet as System, RawOrigin as SystemOrigin}; const SEED: u32 = 0; const MAX_BYTES: u32 = 1_024; -fn assert_last_event, I: 'static>(generic_event: >::Event) { +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } +fn id_to_remark_data(id: u32, length: usize) -> Vec { + id.to_le_bytes().into_iter().cycle().take(length).collect() +} + benchmarks_instance_pallet! { set_members { - let m in 1 .. T::MaxMembers::get(); - let n in 1 .. T::MaxMembers::get(); - let p in 1 .. T::MaxProposals::get(); + let m in 0 .. T::MaxMembers::get(); + let n in 0 .. T::MaxMembers::get(); + let p in 0 .. T::MaxProposals::get(); // Set old members. // We compute the difference of old and new members, so it should influence timing. let mut old_members = vec![]; - let mut last_old_member = T::AccountId::default(); for i in 0 .. m { - last_old_member = account("old member", i, SEED); - old_members.push(last_old_member.clone()); + let old_member = account::("old member", i, SEED); + old_members.push(old_member); } let old_members_count = old_members.len() as u32; Collective::::set_members( SystemOrigin::Root.into(), old_members.clone(), - Some(last_old_member.clone()), + old_members.last().cloned(), T::MaxMembers::get(), )?; - // Set a high threshold for proposals passing so that they stay around. - let threshold = m.max(2); - // Length of the proposals should be irrelevant to `set_members`. - let length = 100; - for i in 0 .. p { - // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { - remark: vec![i as u8; length] - }.into(); - Collective::::propose( - SystemOrigin::Signed(last_old_member.clone()).into(), - threshold, - Box::new(proposal.clone()), - MAX_BYTES, - )?; - let hash = T::Hashing::hash_of(&proposal); - // Vote on the proposal to increase state relevant for `set_members`. - // Not voting for `last_old_member` because they proposed and not voting for the first member - // to keep the proposal from passing. - for j in 2 .. m - 1 { - let voter = &old_members[j as usize]; - let approve = true; - Collective::::vote( - SystemOrigin::Signed(voter.clone()).into(), - hash, - i, - approve, + // If there were any old members generate a bunch of proposals. + if m > 0 { + // Set a high threshold for proposals passing so that they stay around. + let threshold = m.max(2); + // Length of the proposals should be irrelevant to `set_members`. + let length = 100; + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, length) }.into(); + Collective::::propose( + SystemOrigin::Signed(old_members.last().unwrap().clone()).into(), + threshold, + Box::new(proposal.clone()), + MAX_BYTES, )?; + let hash = T::Hashing::hash_of(&proposal); + // Vote on the proposal to increase state relevant for `set_members`. + // Not voting for last old member because they proposed and not voting for the first member + // to keep the proposal from passing. + for j in 2 .. m - 1 { + let voter = &old_members[j as usize]; + let approve = true; + Collective::::vote( + SystemOrigin::Signed(voter.clone()).into(), + hash, + i, + approve, + )?; + } } } // Construct `new_members`. // It should influence timing since it will sort this vector. let mut new_members = vec![]; - let mut last_member = T::AccountId::default(); for i in 0 .. n { - last_member = account("member", i, SEED); - new_members.push(last_member.clone()); + let member = account::("member", i, SEED); + new_members.push(member); } - }: _(SystemOrigin::Root, new_members.clone(), Some(last_member), T::MaxMembers::get()) + }: _(SystemOrigin::Root, new_members.clone(), new_members.last().cloned(), T::MaxMembers::get()) verify { new_members.sort(); assert_eq!(Collective::::members(), new_members); } execute { - let b in 1 .. MAX_BYTES; + let b in 2 .. MAX_BYTES; let m in 1 .. T::MaxMembers::get(); let bytes_in_storage = b + size_of::() as u32; @@ -114,7 +115,7 @@ benchmarks_instance_pallet! { // Construct `members`. let mut members = vec![]; for i in 0 .. m - 1 { - let member = account("member", i, SEED); + let member = account::("member", i, SEED); members.push(member); } @@ -123,20 +124,20 @@ benchmarks_instance_pallet! { Collective::::set_members(SystemOrigin::Root.into(), members, None, T::MaxMembers::get())?; - let proposal: T::Proposal = SystemCall::::remark { remark: vec![1; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(1, b as usize) }.into(); }: _(SystemOrigin::Signed(caller), Box::new(proposal.clone()), bytes_in_storage) verify { let proposal_hash = T::Hashing::hash_of(&proposal); // Note that execution fails due to mis-matched origin assert_last_event::( - Event::MemberExecuted(proposal_hash, Err(DispatchError::BadOrigin)).into() + Event::MemberExecuted { proposal_hash, result: Err(DispatchError::BadOrigin) }.into() ); } // This tests when execution would happen immediately after proposal propose_execute { - let b in 1 .. MAX_BYTES; + let b in 2 .. MAX_BYTES; let m in 1 .. T::MaxMembers::get(); let bytes_in_storage = b + size_of::() as u32; @@ -144,7 +145,7 @@ benchmarks_instance_pallet! { // Construct `members`. let mut members = vec![]; for i in 0 .. m - 1 { - let member = account("member", i, SEED); + let member = account::("member", i, SEED); members.push(member); } @@ -153,7 +154,7 @@ benchmarks_instance_pallet! { Collective::::set_members(SystemOrigin::Root.into(), members, None, T::MaxMembers::get())?; - let proposal: T::Proposal = SystemCall::::remark { remark: vec![1; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(1, b as usize) }.into(); let threshold = 1; }: propose(SystemOrigin::Signed(caller), threshold, Box::new(proposal.clone()), bytes_in_storage) @@ -161,13 +162,13 @@ benchmarks_instance_pallet! { let proposal_hash = T::Hashing::hash_of(&proposal); // Note that execution fails due to mis-matched origin assert_last_event::( - Event::Executed(proposal_hash, Err(DispatchError::BadOrigin)).into() + Event::Executed { proposal_hash, result: Err(DispatchError::BadOrigin) }.into() ); } // This tests when proposal is created and queued as "proposed" propose_proposed { - let b in 1 .. MAX_BYTES; + let b in 2 .. MAX_BYTES; let m in 2 .. T::MaxMembers::get(); let p in 1 .. T::MaxProposals::get(); @@ -176,7 +177,7 @@ benchmarks_instance_pallet! { // Construct `members`. let mut members = vec![]; for i in 0 .. m - 1 { - let member = account("member", i, SEED); + let member = account::("member", i, SEED); members.push(member); } let caller: T::AccountId = whitelisted_caller(); @@ -187,7 +188,7 @@ benchmarks_instance_pallet! { // Add previous proposals. for i in 0 .. p - 1 { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { remark: vec![i as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, b as usize) }.into(); Collective::::propose( SystemOrigin::Signed(caller.clone()).into(), threshold, @@ -198,14 +199,14 @@ benchmarks_instance_pallet! { assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - let proposal: T::Proposal = SystemCall::::remark { remark: vec![p as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(p, b as usize) }.into(); }: propose(SystemOrigin::Signed(caller.clone()), threshold, Box::new(proposal.clone()), bytes_in_storage) verify { // New proposal is recorded assert_eq!(Collective::::proposals().len(), p as usize); let proposal_hash = T::Hashing::hash_of(&proposal); - assert_last_event::(Event::Proposed(caller, p - 1, proposal_hash, threshold).into()); + assert_last_event::(Event::Proposed { account: caller, proposal_index: p - 1, proposal_hash, threshold }.into()); } vote { @@ -218,13 +219,13 @@ benchmarks_instance_pallet! { // Construct `members`. let mut members = vec![]; - let proposer: T::AccountId = account("proposer", 0, SEED); + let proposer: T::AccountId = account::("proposer", 0, SEED); members.push(proposer.clone()); for i in 1 .. m - 1 { - let member = account("member", i, SEED); + let member = account::("member", i, SEED); members.push(member); } - let voter: T::AccountId = account("voter", 0, SEED); + let voter: T::AccountId = account::("voter", 0, SEED); members.push(voter.clone()); Collective::::set_members(SystemOrigin::Root.into(), members.clone(), None, T::MaxMembers::get())?; @@ -235,7 +236,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { remark: vec![i as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, b as usize) }.into(); Collective::::propose( SystemOrigin::Signed(proposer.clone()).into(), threshold, @@ -252,7 +253,7 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -261,7 +262,7 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -274,7 +275,7 @@ benchmarks_instance_pallet! { // Whitelist voter account from further DB operations. let voter_key = frame_system::Account::::hashed_key_for(&voter); frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into()); - }: _(SystemOrigin::Signed(voter), last_hash.clone(), index, approve) + }: _(SystemOrigin::Signed(voter), last_hash, index, approve) verify { // All proposals exist and the last proposal has just been updated. assert_eq!(Collective::::proposals().len(), p as usize); @@ -293,13 +294,13 @@ benchmarks_instance_pallet! { // Construct `members`. let mut members = vec![]; - let proposer: T::AccountId = account("proposer", 0, SEED); + let proposer = account::("proposer", 0, SEED); members.push(proposer.clone()); for i in 1 .. m - 1 { - let member = account("member", i, SEED); + let member = account::("member", i, SEED); members.push(member); } - let voter: T::AccountId = account("voter", 0, SEED); + let voter = account::("voter", 0, SEED); members.push(voter.clone()); Collective::::set_members(SystemOrigin::Root.into(), members.clone(), None, T::MaxMembers::get())?; @@ -310,9 +311,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { - remark: vec![i as u8; bytes as usize] - }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, bytes as usize) }.into(); Collective::::propose( SystemOrigin::Signed(proposer.clone()).into(), threshold, @@ -329,7 +328,7 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -338,7 +337,7 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -349,7 +348,7 @@ benchmarks_instance_pallet! { let approve = false; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -357,15 +356,15 @@ benchmarks_instance_pallet! { // Whitelist voter account from further DB operations. let voter_key = frame_system::Account::::hashed_key_for(&voter); frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into()); - }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::max_value(), bytes_in_storage) + }: close(SystemOrigin::Signed(voter), last_hash, index, Weight::MAX, bytes_in_storage) verify { // The last proposal is removed. assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(Event::Disapproved(last_hash).into()); + assert_last_event::(Event::Disapproved { proposal_hash: last_hash }.into()); } close_early_approved { - let b in 1 .. MAX_BYTES; + let b in 2 .. MAX_BYTES; // We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) let m in 4 .. T::MaxMembers::get(); let p in 1 .. T::MaxProposals::get(); @@ -375,7 +374,7 @@ benchmarks_instance_pallet! { // Construct `members`. let mut members = vec![]; for i in 0 .. m - 1 { - let member = account("member", i, SEED); + let member = account::("member", i, SEED); members.push(member); } let caller: T::AccountId = whitelisted_caller(); @@ -389,7 +388,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { remark: vec![i as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, b as usize) }.into(); Collective::::propose( SystemOrigin::Signed(caller.clone()).into(), threshold, @@ -402,7 +401,7 @@ benchmarks_instance_pallet! { // Caller switches vote to nay on their own proposal, allowing them to be the deciding approval vote Collective::::vote( SystemOrigin::Signed(caller.clone()).into(), - last_hash.clone(), + last_hash, p - 1, false, )?; @@ -413,7 +412,7 @@ benchmarks_instance_pallet! { let approve = false; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, p - 1, approve, )?; @@ -422,7 +421,7 @@ benchmarks_instance_pallet! { // Member zero is the first aye Collective::::vote( SystemOrigin::Signed(members[0].clone()).into(), - last_hash.clone(), + last_hash, p - 1, true, )?; @@ -434,15 +433,15 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(caller.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; - }: close(SystemOrigin::Signed(caller), last_hash.clone(), index, Weight::max_value(), bytes_in_storage) + }: close(SystemOrigin::Signed(caller), last_hash, index, Weight::MAX, bytes_in_storage) verify { // The last proposal is removed. assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(Event::Executed(last_hash, Err(DispatchError::BadOrigin)).into()); + assert_last_event::(Event::Executed { proposal_hash: last_hash, result: Err(DispatchError::BadOrigin) }.into()); } close_disapproved { @@ -456,7 +455,7 @@ benchmarks_instance_pallet! { // Construct `members`. let mut members = vec![]; for i in 0 .. m - 1 { - let member = account("member", i, SEED); + let member = account::("member", i, SEED); members.push(member); } let caller: T::AccountId = whitelisted_caller(); @@ -475,9 +474,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { - remark: vec![i as u8; bytes as usize] - }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, bytes as usize) }.into(); Collective::::propose( SystemOrigin::Signed(caller.clone()).into(), threshold, @@ -490,12 +487,22 @@ benchmarks_instance_pallet! { let index = p - 1; // Have almost everyone vote aye on last proposal, while keeping it from passing. // A few abstainers will be the nay votes needed to fail the vote. + let mut yes_votes: MemberCount = 0; for j in 2 .. m - 1 { let voter = &members[j as usize]; let approve = true; + yes_votes += 1; + // vote aye till a prime nay vote keeps the proposal disapproved. + if <>::DefaultVote as DefaultVote>::default_vote( + Some(false), + yes_votes, + 0, + m,) { + break; + } Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -504,7 +511,7 @@ benchmarks_instance_pallet! { // caller is prime, prime votes nay Collective::::vote( SystemOrigin::Signed(caller.clone()).into(), - last_hash.clone(), + last_hash, index, false, )?; @@ -513,14 +520,14 @@ benchmarks_instance_pallet! { assert_eq!(Collective::::proposals().len(), p as usize); // Prime nay will close it as disapproved - }: close(SystemOrigin::Signed(caller), last_hash, index, Weight::max_value(), bytes_in_storage) + }: close(SystemOrigin::Signed(caller), last_hash, index, Weight::MAX, bytes_in_storage) verify { assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(Event::Disapproved(last_hash).into()); + assert_last_event::(Event::Disapproved { proposal_hash: last_hash }.into()); } close_approved { - let b in 1 .. MAX_BYTES; + let b in 2 .. MAX_BYTES; // We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) let m in 4 .. T::MaxMembers::get(); let p in 1 .. T::MaxProposals::get(); @@ -530,7 +537,7 @@ benchmarks_instance_pallet! { // Construct `members`. let mut members = vec![]; for i in 0 .. m - 1 { - let member = account("member", i, SEED); + let member = account::("member", i, SEED); members.push(member); } let caller: T::AccountId = whitelisted_caller(); @@ -549,7 +556,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { remark: vec![i as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, b as usize) }.into(); Collective::::propose( SystemOrigin::Signed(caller.clone()).into(), threshold, @@ -562,7 +569,7 @@ benchmarks_instance_pallet! { // The prime member votes aye, so abstentions default to aye. Collective::::vote( SystemOrigin::Signed(caller.clone()).into(), - last_hash.clone(), + last_hash, p - 1, true // Vote aye. )?; @@ -574,7 +581,7 @@ benchmarks_instance_pallet! { let approve = false; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, p - 1, approve )?; @@ -585,10 +592,10 @@ benchmarks_instance_pallet! { assert_eq!(Collective::::proposals().len(), p as usize); // Prime aye will close it as approved - }: close(SystemOrigin::Signed(caller), last_hash, p - 1, Weight::max_value(), bytes_in_storage) + }: close(SystemOrigin::Signed(caller), last_hash, p - 1, Weight::MAX, bytes_in_storage) verify { assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(Event::Executed(last_hash, Err(DispatchError::BadOrigin)).into()); + assert_last_event::(Event::Executed { proposal_hash: last_hash, result: Err(DispatchError::BadOrigin) }.into()); } disapprove_proposal { @@ -601,10 +608,10 @@ benchmarks_instance_pallet! { // Construct `members`. let mut members = vec![]; for i in 0 .. m - 1 { - let member = account("member", i, SEED); + let member = account::("member", i, SEED); members.push(member); } - let caller: T::AccountId = account("caller", 0, SEED); + let caller = account::("caller", 0, SEED); members.push(caller.clone()); Collective::::set_members( SystemOrigin::Root.into(), @@ -620,7 +627,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { remark: vec![i as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, b as usize) }.into(); Collective::::propose( SystemOrigin::Signed(caller.clone()).into(), threshold, @@ -636,8 +643,8 @@ benchmarks_instance_pallet! { }: _(SystemOrigin::Root, last_hash) verify { assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(Event::Disapproved(last_hash).into()); + assert_last_event::(Event::Disapproved { proposal_hash: last_hash }.into()); } -} -impl_benchmark_test_suite!(Collective, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(Collective, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 89d4c8a150c36..c522b71891b3c 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,19 +43,21 @@ #![recursion_limit = "128"] use scale_info::TypeInfo; -use sp_core::u32_trait::Value as U32; use sp_io::storage; use sp_runtime::{traits::Hash, RuntimeDebug}; use sp_std::{marker::PhantomData, prelude::*, result}; use frame_support::{ - codec::{Decode, Encode}, - dispatch::{DispatchError, DispatchResultWithPostInfo, Dispatchable, PostDispatchInfo}, + codec::{Decode, Encode, MaxEncodedLen}, + dispatch::{ + DispatchError, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo, Pays, + PostDispatchInfo, + }, ensure, traits::{ Backing, ChangeMembers, EnsureOrigin, Get, GetBacking, InitializeMembers, StorageVersion, }, - weights::{GetDispatchInfo, Weight}, + weights::{OldWeight, Weight}, }; #[cfg(test)] @@ -125,8 +127,9 @@ impl DefaultVote for MoreThanMajorityThenPrimeDefaultVote { } /// Origin for the collective module. -#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo)] +#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(I))] +#[codec(mel_bound(AccountId: MaxEncodedLen))] pub enum RawOrigin { /// It has been condoned by a given number of members of the collective from a given total. Members(MemberCount, MemberCount), @@ -172,21 +175,25 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] pub struct Pallet(PhantomData<(T, I)>); #[pallet::config] pub trait Config: frame_system::Config { - /// The outer origin type. - type Origin: From>; + /// The runtime origin type. + type RuntimeOrigin: From>; - /// The outer call dispatch type. + /// The runtime call dispatch type. type Proposal: Parameter - + Dispatchable>::Origin, PostInfo = PostDispatchInfo> - + From> + + Dispatchable< + RuntimeOrigin = >::RuntimeOrigin, + PostInfo = PostDispatchInfo, + > + From> + GetDispatchInfo; - /// The outer event type. - type Event: From> + IsType<::Event>; + /// The runtime event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; /// The time-out for council motions. type MotionDuration: Get; @@ -279,33 +286,33 @@ pub mod pallet { pub enum Event, I: 'static = ()> { /// A motion (given hash) has been proposed (by given account) with a threshold (given /// `MemberCount`). - /// \[account, proposal_index, proposal_hash, threshold\] - Proposed(T::AccountId, ProposalIndex, T::Hash, MemberCount), + Proposed { + account: T::AccountId, + proposal_index: ProposalIndex, + proposal_hash: T::Hash, + threshold: MemberCount, + }, /// A motion (given hash) has been voted on by given account, leaving /// a tally (yes votes and no votes given respectively as `MemberCount`). - /// \[account, proposal_hash, voted, yes, no\] - Voted(T::AccountId, T::Hash, bool, MemberCount, MemberCount), + Voted { + account: T::AccountId, + proposal_hash: T::Hash, + voted: bool, + yes: MemberCount, + no: MemberCount, + }, /// A motion was approved by the required threshold. - /// \[proposal_hash\] - Approved(T::Hash), + Approved { proposal_hash: T::Hash }, /// A motion was not approved by the required threshold. - /// \[proposal_hash\] - Disapproved(T::Hash), + Disapproved { proposal_hash: T::Hash }, /// A motion was executed; result will be `Ok` if it returned without error. - /// \[proposal_hash, result\] - Executed(T::Hash, DispatchResult), + Executed { proposal_hash: T::Hash, result: DispatchResult }, /// A single member did some action; result will be `Ok` if it returned without error. - /// \[proposal_hash, result\] - MemberExecuted(T::Hash, DispatchResult), + MemberExecuted { proposal_hash: T::Hash, result: DispatchResult }, /// A proposal was closed because its threshold was reached or after its duration was up. - /// \[proposal_hash, yes, no\] - Closed(T::Hash, MemberCount, MemberCount), + Closed { proposal_hash: T::Hash, yes: MemberCount, no: MemberCount }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// Account is not a member @@ -345,6 +352,13 @@ pub mod pallet { /// NOTE: Does not enforce the expected `MaxMembers` limit on the amount of members, but /// the weight estimations rely on it to estimate dispatchable weight. /// + /// # WARNING: + /// + /// The `pallet-collective` can also be managed by logic outside of the pallet through the + /// implementation of the trait [`ChangeMembers`]. + /// Any call to `set_members` must be careful that the member set doesn't get out of sync + /// with other logic managing the member set. + /// /// # /// ## Weight /// - `O(MP + N)` where: @@ -358,6 +372,7 @@ pub mod pallet { /// - `P` storage mutations (codec `O(M)`) for updating the votes for each proposal /// - 1 storage write (codec `O(1)`) for deleting the old `prime` and setting the new one /// # + #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::set_members( *old_count, // M @@ -415,6 +430,7 @@ pub mod pallet { /// - DB: 1 read (codec `O(M)`) + DB access of `proposal` /// - 1 event /// # + #[pallet::call_index(1)] #[pallet::weight(( T::WeightInfo::execute( *length_bound, // B @@ -430,15 +446,15 @@ pub mod pallet { let who = ensure_signed(origin)?; let members = Self::members(); ensure!(members.contains(&who), Error::::NotMember); - let proposal_len = proposal.using_encoded(|x| x.len()); + let proposal_len = proposal.encoded_size(); ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); let proposal_hash = T::Hashing::hash_of(&proposal); let result = proposal.dispatch(RawOrigin::Member(who).into()); - Self::deposit_event(Event::MemberExecuted( + Self::deposit_event(Event::MemberExecuted { proposal_hash, - result.map(|_| ()).map_err(|e| e.error), - )); + result: result.map(|_| ()).map_err(|e| e.error), + }); Ok(get_result_weight(result) .map(|w| { @@ -478,6 +494,7 @@ pub mod pallet { /// - 1 storage write `Voting` (codec `O(M)`) /// - 1 event /// # + #[pallet::call_index(2)] #[pallet::weight(( if *threshold < 2 { T::WeightInfo::propose_execute( @@ -503,21 +520,8 @@ pub mod pallet { let members = Self::members(); ensure!(members.contains(&who), Error::::NotMember); - let proposal_len = proposal.using_encoded(|x| x.len()); - ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); - let proposal_hash = T::Hashing::hash_of(&proposal); - ensure!( - !>::contains_key(proposal_hash), - Error::::DuplicateProposal - ); - if threshold < 2 { - let seats = Self::members().len() as MemberCount; - let result = proposal.dispatch(RawOrigin::Members(1, seats).into()); - Self::deposit_event(Event::Executed( - proposal_hash, - result.map(|_| ()).map_err(|e| e.error), - )); + let (proposal_len, result) = Self::do_propose_execute(proposal, length_bound)?; Ok(get_result_weight(result) .map(|w| { @@ -529,28 +533,13 @@ pub mod pallet { }) .into()) } else { - let active_proposals = - >::try_mutate(|proposals| -> Result { - proposals - .try_push(proposal_hash) - .map_err(|_| Error::::TooManyProposals)?; - Ok(proposals.len()) - })?; - let index = Self::proposal_count(); - >::mutate(|i| *i += 1); - >::insert(proposal_hash, *proposal); - let votes = { - let end = frame_system::Pallet::::block_number() + T::MotionDuration::get(); - Votes { index, threshold, ayes: vec![], nays: vec![], end } - }; - >::insert(proposal_hash, votes); - - Self::deposit_event(Event::Proposed(who, index, proposal_hash, threshold)); + let (proposal_len, active_proposals) = + Self::do_propose_proposed(who, threshold, proposal, length_bound)?; Ok(Some(T::WeightInfo::propose_proposed( - proposal_len as u32, // B - members.len() as u32, // M - active_proposals as u32, // P2 + proposal_len as u32, // B + members.len() as u32, // M + active_proposals, // P2 )) .into()) } @@ -571,6 +560,7 @@ pub mod pallet { /// - 1 storage mutation `Voting` (codec `O(M)`) /// - 1 event /// # + #[pallet::call_index(3)] #[pallet::weight((T::WeightInfo::vote(T::MaxMembers::get()), DispatchClass::Operational))] pub fn vote( origin: OriginFor, @@ -582,40 +572,8 @@ pub mod pallet { let members = Self::members(); ensure!(members.contains(&who), Error::::NotMember); - let mut voting = Self::voting(&proposal).ok_or(Error::::ProposalMissing)?; - ensure!(voting.index == index, Error::::WrongIndex); - - let position_yes = voting.ayes.iter().position(|a| a == &who); - let position_no = voting.nays.iter().position(|a| a == &who); - // Detects first vote of the member in the motion - let is_account_voting_first_time = position_yes.is_none() && position_no.is_none(); - - if approve { - if position_yes.is_none() { - voting.ayes.push(who.clone()); - } else { - return Err(Error::::DuplicateVote.into()) - } - if let Some(pos) = position_no { - voting.nays.swap_remove(pos); - } - } else { - if position_no.is_none() { - voting.nays.push(who.clone()); - } else { - return Err(Error::::DuplicateVote.into()) - } - if let Some(pos) = position_yes { - voting.ayes.swap_remove(pos); - } - } - - let yes_votes = voting.ayes.len() as MemberCount; - let no_votes = voting.nays.len() as MemberCount; - Self::deposit_event(Event::Voted(who, proposal, approve, yes_votes, no_votes)); - - Voting::::insert(&proposal, voting); + let is_account_voting_first_time = Self::do_vote(who, proposal, index, approve)?; if is_account_voting_first_time { Ok((Some(T::WeightInfo::vote(members.len() as u32)), Pays::No).into()) @@ -656,6 +614,7 @@ pub mod pallet { /// - any mutations done while executing `proposal` (`P1`) /// - up to 3 events /// # + #[pallet::call_index(4)] #[pallet::weight(( { let b = *length_bound; @@ -666,95 +625,23 @@ pub mod pallet { .max(T::WeightInfo::close_early_disapproved(m, p2)) .max(T::WeightInfo::close_approved(b, m, p2)) .max(T::WeightInfo::close_disapproved(m, p2)) - .saturating_add(p1) + .saturating_add(p1.into()) }, DispatchClass::Operational ))] - pub fn close( + #[allow(deprecated)] + #[deprecated(note = "1D weight is used in this extrinsic, please migrate to `close`")] + pub fn close_old_weight( origin: OriginFor, proposal_hash: T::Hash, #[pallet::compact] index: ProposalIndex, - #[pallet::compact] proposal_weight_bound: Weight, + #[pallet::compact] proposal_weight_bound: OldWeight, #[pallet::compact] length_bound: u32, ) -> DispatchResultWithPostInfo { + let proposal_weight_bound: Weight = proposal_weight_bound.into(); let _ = ensure_signed(origin)?; - let voting = Self::voting(&proposal_hash).ok_or(Error::::ProposalMissing)?; - ensure!(voting.index == index, Error::::WrongIndex); - - let mut no_votes = voting.nays.len() as MemberCount; - let mut yes_votes = voting.ayes.len() as MemberCount; - let seats = Self::members().len() as MemberCount; - let approved = yes_votes >= voting.threshold; - let disapproved = seats.saturating_sub(no_votes) < voting.threshold; - // Allow (dis-)approving the proposal as soon as there are enough votes. - if approved { - let (proposal, len) = Self::validate_and_get_proposal( - &proposal_hash, - length_bound, - proposal_weight_bound, - )?; - Self::deposit_event(Event::Closed(proposal_hash, yes_votes, no_votes)); - let (proposal_weight, proposal_count) = - Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal); - return Ok(( - Some( - T::WeightInfo::close_early_approved(len as u32, seats, proposal_count) - .saturating_add(proposal_weight), - ), - Pays::Yes, - ) - .into()) - } else if disapproved { - Self::deposit_event(Event::Closed(proposal_hash, yes_votes, no_votes)); - let proposal_count = Self::do_disapprove_proposal(proposal_hash); - return Ok(( - Some(T::WeightInfo::close_early_disapproved(seats, proposal_count)), - Pays::No, - ) - .into()) - } - - // Only allow actual closing of the proposal after the voting period has ended. - ensure!( - frame_system::Pallet::::block_number() >= voting.end, - Error::::TooEarly - ); - - let prime_vote = Self::prime().map(|who| voting.ayes.iter().any(|a| a == &who)); - - // default voting strategy. - let default = T::DefaultVote::default_vote(prime_vote, yes_votes, no_votes, seats); - - let abstentions = seats - (yes_votes + no_votes); - match default { - true => yes_votes += abstentions, - false => no_votes += abstentions, - } - let approved = yes_votes >= voting.threshold; - - if approved { - let (proposal, len) = Self::validate_and_get_proposal( - &proposal_hash, - length_bound, - proposal_weight_bound, - )?; - Self::deposit_event(Event::Closed(proposal_hash, yes_votes, no_votes)); - let (proposal_weight, proposal_count) = - Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal); - Ok(( - Some( - T::WeightInfo::close_approved(len as u32, seats, proposal_count) - .saturating_add(proposal_weight), - ), - Pays::Yes, - ) - .into()) - } else { - Self::deposit_event(Event::Closed(proposal_hash, yes_votes, no_votes)); - let proposal_count = Self::do_disapprove_proposal(proposal_hash); - Ok((Some(T::WeightInfo::close_disapproved(seats, proposal_count)), Pays::No).into()) - } + Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound) } /// Disapprove a proposal, close, and remove it from the system, regardless of its current @@ -771,6 +658,7 @@ pub mod pallet { /// * Reads: Proposals /// * Writes: Voting, Proposals, ProposalOf /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::disapprove_proposal(T::MaxProposals::get()))] pub fn disapprove_proposal( origin: OriginFor, @@ -780,6 +668,65 @@ pub mod pallet { let proposal_count = Self::do_disapprove_proposal(proposal_hash); Ok(Some(T::WeightInfo::disapprove_proposal(proposal_count)).into()) } + + /// Close a vote that is either approved, disapproved or whose voting period has ended. + /// + /// May be called by any signed account in order to finish voting and close the proposal. + /// + /// If called before the end of the voting period it will only close the vote if it is + /// has enough votes to be approved or disapproved. + /// + /// If called after the end of the voting period abstentions are counted as rejections + /// unless there is a prime member set and the prime member cast an approval. + /// + /// If the close operation completes successfully with disapproval, the transaction fee will + /// be waived. Otherwise execution of the approved operation will be charged to the caller. + /// + /// + `proposal_weight_bound`: The maximum amount of weight consumed by executing the closed + /// proposal. + /// + `length_bound`: The upper bound for the length of the proposal in storage. Checked via + /// `storage::read` so it is `size_of::() == 4` larger than the pure length. + /// + /// # + /// ## Weight + /// - `O(B + M + P1 + P2)` where: + /// - `B` is `proposal` size in bytes (length-fee-bounded) + /// - `M` is members-count (code- and governance-bounded) + /// - `P1` is the complexity of `proposal` preimage. + /// - `P2` is proposal-count (code-bounded) + /// - DB: + /// - 2 storage reads (`Members`: codec `O(M)`, `Prime`: codec `O(1)`) + /// - 3 mutations (`Voting`: codec `O(M)`, `ProposalOf`: codec `O(B)`, `Proposals`: codec + /// `O(P2)`) + /// - any mutations done while executing `proposal` (`P1`) + /// - up to 3 events + /// # + #[pallet::call_index(6)] + #[pallet::weight(( + { + let b = *length_bound; + let m = T::MaxMembers::get(); + let p1 = *proposal_weight_bound; + let p2 = T::MaxProposals::get(); + T::WeightInfo::close_early_approved(b, m, p2) + .max(T::WeightInfo::close_early_disapproved(m, p2)) + .max(T::WeightInfo::close_approved(b, m, p2)) + .max(T::WeightInfo::close_disapproved(m, p2)) + .saturating_add(p1) + }, + DispatchClass::Operational + ))] + pub fn close( + origin: OriginFor, + proposal_hash: T::Hash, + #[pallet::compact] index: ProposalIndex, + proposal_weight_bound: Weight, + #[pallet::compact] length_bound: u32, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + + Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound) + } } } @@ -801,6 +748,197 @@ impl, I: 'static> Pallet { Self::members().contains(who) } + /// Execute immediately when adding a new proposal. + pub fn do_propose_execute( + proposal: Box<>::Proposal>, + length_bound: MemberCount, + ) -> Result<(u32, DispatchResultWithPostInfo), DispatchError> { + let proposal_len = proposal.encoded_size(); + ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); + + let proposal_hash = T::Hashing::hash_of(&proposal); + ensure!(!>::contains_key(proposal_hash), Error::::DuplicateProposal); + + let seats = Self::members().len() as MemberCount; + let result = proposal.dispatch(RawOrigin::Members(1, seats).into()); + Self::deposit_event(Event::Executed { + proposal_hash, + result: result.map(|_| ()).map_err(|e| e.error), + }); + Ok((proposal_len as u32, result)) + } + + /// Add a new proposal to be voted. + pub fn do_propose_proposed( + who: T::AccountId, + threshold: MemberCount, + proposal: Box<>::Proposal>, + length_bound: MemberCount, + ) -> Result<(u32, u32), DispatchError> { + let proposal_len = proposal.encoded_size(); + ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); + + let proposal_hash = T::Hashing::hash_of(&proposal); + ensure!(!>::contains_key(proposal_hash), Error::::DuplicateProposal); + + let active_proposals = + >::try_mutate(|proposals| -> Result { + proposals.try_push(proposal_hash).map_err(|_| Error::::TooManyProposals)?; + Ok(proposals.len()) + })?; + + let index = Self::proposal_count(); + >::mutate(|i| *i += 1); + >::insert(proposal_hash, proposal); + let votes = { + let end = frame_system::Pallet::::block_number() + T::MotionDuration::get(); + Votes { index, threshold, ayes: vec![], nays: vec![], end } + }; + >::insert(proposal_hash, votes); + + Self::deposit_event(Event::Proposed { + account: who, + proposal_index: index, + proposal_hash, + threshold, + }); + Ok((proposal_len as u32, active_proposals as u32)) + } + + /// Add an aye or nay vote for the member to the given proposal, returns true if it's the first + /// vote of the member in the motion + pub fn do_vote( + who: T::AccountId, + proposal: T::Hash, + index: ProposalIndex, + approve: bool, + ) -> Result { + let mut voting = Self::voting(&proposal).ok_or(Error::::ProposalMissing)?; + ensure!(voting.index == index, Error::::WrongIndex); + + let position_yes = voting.ayes.iter().position(|a| a == &who); + let position_no = voting.nays.iter().position(|a| a == &who); + + // Detects first vote of the member in the motion + let is_account_voting_first_time = position_yes.is_none() && position_no.is_none(); + + if approve { + if position_yes.is_none() { + voting.ayes.push(who.clone()); + } else { + return Err(Error::::DuplicateVote.into()) + } + if let Some(pos) = position_no { + voting.nays.swap_remove(pos); + } + } else { + if position_no.is_none() { + voting.nays.push(who.clone()); + } else { + return Err(Error::::DuplicateVote.into()) + } + if let Some(pos) = position_yes { + voting.ayes.swap_remove(pos); + } + } + + let yes_votes = voting.ayes.len() as MemberCount; + let no_votes = voting.nays.len() as MemberCount; + Self::deposit_event(Event::Voted { + account: who, + proposal_hash: proposal, + voted: approve, + yes: yes_votes, + no: no_votes, + }); + + Voting::::insert(&proposal, voting); + + Ok(is_account_voting_first_time) + } + + /// Close a vote that is either approved, disapproved or whose voting period has ended. + pub fn do_close( + proposal_hash: T::Hash, + index: ProposalIndex, + proposal_weight_bound: Weight, + length_bound: u32, + ) -> DispatchResultWithPostInfo { + let voting = Self::voting(&proposal_hash).ok_or(Error::::ProposalMissing)?; + ensure!(voting.index == index, Error::::WrongIndex); + + let mut no_votes = voting.nays.len() as MemberCount; + let mut yes_votes = voting.ayes.len() as MemberCount; + let seats = Self::members().len() as MemberCount; + let approved = yes_votes >= voting.threshold; + let disapproved = seats.saturating_sub(no_votes) < voting.threshold; + // Allow (dis-)approving the proposal as soon as there are enough votes. + if approved { + let (proposal, len) = Self::validate_and_get_proposal( + &proposal_hash, + length_bound, + proposal_weight_bound, + )?; + Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); + let (proposal_weight, proposal_count) = + Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal); + return Ok(( + Some( + T::WeightInfo::close_early_approved(len as u32, seats, proposal_count) + .saturating_add(proposal_weight), + ), + Pays::Yes, + ) + .into()) + } else if disapproved { + Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); + let proposal_count = Self::do_disapprove_proposal(proposal_hash); + return Ok(( + Some(T::WeightInfo::close_early_disapproved(seats, proposal_count)), + Pays::No, + ) + .into()) + } + + // Only allow actual closing of the proposal after the voting period has ended. + ensure!(frame_system::Pallet::::block_number() >= voting.end, Error::::TooEarly); + + let prime_vote = Self::prime().map(|who| voting.ayes.iter().any(|a| a == &who)); + + // default voting strategy. + let default = T::DefaultVote::default_vote(prime_vote, yes_votes, no_votes, seats); + + let abstentions = seats - (yes_votes + no_votes); + match default { + true => yes_votes += abstentions, + false => no_votes += abstentions, + } + let approved = yes_votes >= voting.threshold; + + if approved { + let (proposal, len) = Self::validate_and_get_proposal( + &proposal_hash, + length_bound, + proposal_weight_bound, + )?; + Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); + let (proposal_weight, proposal_count) = + Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal); + Ok(( + Some( + T::WeightInfo::close_approved(len as u32, seats, proposal_count) + .saturating_add(proposal_weight), + ), + Pays::Yes, + ) + .into()) + } else { + Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); + let proposal_count = Self::do_disapprove_proposal(proposal_hash); + Ok((Some(T::WeightInfo::close_disapproved(seats, proposal_count)), Pays::No).into()) + } + } + /// Ensure that the right proposal bounds were passed and get the proposal from storage. /// /// Checks the length in storage via `storage::read` which adds an extra `size_of::() == 4` @@ -817,7 +955,7 @@ impl, I: 'static> Pallet { ensure!(proposal_len <= length_bound, Error::::WrongProposalLength); let proposal = ProposalOf::::get(hash).ok_or(Error::::ProposalMissing)?; let proposal_weight = proposal.get_dispatch_info().weight; - ensure!(proposal_weight <= weight_bound, Error::::WrongProposalWeight); + ensure!(proposal_weight.all_lte(weight_bound), Error::::WrongProposalWeight); Ok((proposal, proposal_len as usize)) } @@ -841,15 +979,15 @@ impl, I: 'static> Pallet { proposal_hash: T::Hash, proposal: >::Proposal, ) -> (Weight, u32) { - Self::deposit_event(Event::Approved(proposal_hash)); + Self::deposit_event(Event::Approved { proposal_hash }); let dispatch_weight = proposal.get_dispatch_info().weight; let origin = RawOrigin::Members(yes_votes, seats).into(); let result = proposal.dispatch(origin); - Self::deposit_event(Event::Executed( + Self::deposit_event(Event::Executed { proposal_hash, - result.map(|_| ()).map_err(|e| e.error), - )); + result: result.map(|_| ()).map_err(|e| e.error), + }); // default to the dispatch info weight for safety let proposal_weight = get_result_weight(result).unwrap_or(dispatch_weight); // P1 @@ -857,9 +995,10 @@ impl, I: 'static> Pallet { (proposal_weight, proposal_count) } - fn do_disapprove_proposal(proposal_hash: T::Hash) -> u32 { + /// Removes a proposal from the pallet, and deposit the `Disapproved` event. + pub fn do_disapprove_proposal(proposal_hash: T::Hash) -> u32 { // disapproved - Self::deposit_event(Event::Disapproved(proposal_hash)); + Self::deposit_event(Event::Disapproved { proposal_hash }); Self::remove_proposal(proposal_hash) } @@ -967,8 +1106,8 @@ where pub struct EnsureMember(PhantomData<(AccountId, I)>); impl< O: Into, O>> + From>, - AccountId: Default, I, + AccountId: Decode, > EnsureOrigin for EnsureMember { type Success = AccountId; @@ -980,79 +1119,82 @@ impl< } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Member(Default::default())) + fn try_successful_origin() -> Result { + let zero_account_id = + AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) + .expect("infinite length input; no invalid inputs for type; qed"); + Ok(O::from(RawOrigin::Member(zero_account_id))) } } -pub struct EnsureMembers(PhantomData<(N, AccountId, I)>); +pub struct EnsureMembers(PhantomData<(AccountId, I)>); impl< O: Into, O>> + From>, - N: U32, AccountId, I, - > EnsureOrigin for EnsureMembers + const N: u32, + > EnsureOrigin for EnsureMembers { type Success = (MemberCount, MemberCount); fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { - RawOrigin::Members(n, m) if n >= N::VALUE => Ok((n, m)), + RawOrigin::Members(n, m) if n >= N => Ok((n, m)), r => Err(O::from(r)), }) } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Members(N::VALUE, N::VALUE)) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::Members(N, N))) } } -pub struct EnsureProportionMoreThan( - PhantomData<(N, D, AccountId, I)>, +pub struct EnsureProportionMoreThan( + PhantomData<(AccountId, I)>, ); impl< O: Into, O>> + From>, - N: U32, - D: U32, AccountId, I, - > EnsureOrigin for EnsureProportionMoreThan + const N: u32, + const D: u32, + > EnsureOrigin for EnsureProportionMoreThan { type Success = (); fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { - RawOrigin::Members(n, m) if n * D::VALUE > N::VALUE * m => Ok(()), + RawOrigin::Members(n, m) if n * D > N * m => Ok(()), r => Err(O::from(r)), }) } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Members(1u32, 0u32)) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::Members(1u32, 0u32))) } } -pub struct EnsureProportionAtLeast( - PhantomData<(N, D, AccountId, I)>, +pub struct EnsureProportionAtLeast( + PhantomData<(AccountId, I)>, ); impl< O: Into, O>> + From>, - N: U32, - D: U32, AccountId, I, - > EnsureOrigin for EnsureProportionAtLeast + const N: u32, + const D: u32, + > EnsureOrigin for EnsureProportionAtLeast { type Success = (); fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { - RawOrigin::Members(n, m) if n * D::VALUE >= N::VALUE * m => Ok(()), + RawOrigin::Members(n, m) if n * D >= N * m => Ok(()), r => Err(O::from(r)), }) } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Members(0u32, 0u32)) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::Members(0u32, 0u32))) } } diff --git a/frame/collective/src/migrations/mod.rs b/frame/collective/src/migrations/mod.rs index 26d07a0cd5ac8..235d0f1c7cf15 100644 --- a/frame/collective/src/migrations/mod.rs +++ b/frame/collective/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/collective/src/migrations/v4.rs b/frame/collective/src/migrations/v4.rs index 68284ba4df91d..483c3f9fa9e69 100644 --- a/frame/collective/src/migrations/v4.rs +++ b/frame/collective/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,7 +45,7 @@ pub fn migrate::on_chain_storage_version(); @@ -70,7 +70,7 @@ pub fn migrate; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( pub enum Test where @@ -52,9 +53,8 @@ mod mock_democracy { pub use pallet::*; #[frame_support::pallet] pub mod pallet { - use frame_support::{pallet_prelude::*, traits::EnsureOrigin}; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use sp_runtime::DispatchResult; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -62,12 +62,14 @@ mod mock_democracy { #[pallet::config] pub trait Config: frame_system::Config + Sized { - type Event: From> + IsType<::Event>; - type ExternalMajorityOrigin: EnsureOrigin; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + type ExternalMajorityOrigin: EnsureOrigin; } #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(0)] pub fn external_propose_majority(origin: OriginFor) -> DispatchResult { T::ExternalMajorityOrigin::ensure_origin(origin)?; @@ -84,30 +86,30 @@ mod mock_democracy { } } +pub type MaxMembers = ConstU32<100>; + parameter_types! { - pub const BlockHashCount: u64 = 250; pub const MotionDuration: u64 = 3; - pub const MaxProposals: u32 = 100; - pub const MaxMembers: u32 = 100; + pub const MaxProposals: u32 = 257; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -116,36 +118,37 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl Config for Test { - type Origin = Origin; - type Proposal = Call; - type Event = Event; - type MotionDuration = MotionDuration; + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = ConstU64<3>; type MaxProposals = MaxProposals; type MaxMembers = MaxMembers; type DefaultVote = PrimeDefaultVote; type WeightInfo = (); } impl Config for Test { - type Origin = Origin; - type Proposal = Call; - type Event = Event; - type MotionDuration = MotionDuration; + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = ConstU64<3>; type MaxProposals = MaxProposals; type MaxMembers = MaxMembers; type DefaultVote = MoreThanMajorityThenPrimeDefaultVote; type WeightInfo = (); } impl mock_democracy::Config for Test { - type Event = Event; - type ExternalMajorityOrigin = EnsureProportionAtLeast<_3, _4, u64, Instance1>; + type RuntimeEvent = RuntimeEvent; + type ExternalMajorityOrigin = EnsureProportionAtLeast; } impl Config for Test { - type Origin = Origin; - type Proposal = Call; - type Event = Event; - type MotionDuration = MotionDuration; + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = ConstU64<3>; type MaxProposals = MaxProposals; type MaxMembers = MaxMembers; type DefaultVote = PrimeDefaultVote; @@ -171,11 +174,13 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext } -fn make_proposal(value: u64) -> Call { - Call::System(frame_system::Call::remark { remark: value.encode() }) +fn make_proposal(value: u64) -> RuntimeCall { + RuntimeCall::System(frame_system::Call::remark_with_event { + remark: value.to_be_bytes().to_vec(), + }) } -fn record(event: Event) -> EventRecord { +fn record(event: RuntimeEvent) -> EventRecord { EventRecord { phase: Phase::Initialization, event, topics: vec![] } } @@ -196,31 +201,60 @@ fn close_works() { let hash = BlakeTwo256::hash_of(&proposal); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, true)); System::set_block_number(3); assert_noop!( - Collective::close(Origin::signed(4), hash, 0, proposal_weight, proposal_len), + Collective::close(RuntimeOrigin::signed(4), hash, 0, proposal_weight, proposal_len), Error::::TooEarly ); System::set_block_number(4); - assert_ok!(Collective::close(Origin::signed(4), hash, 0, proposal_weight, proposal_len)); + assert_ok!(Collective::close( + RuntimeOrigin::signed(4), + hash, + 0, + proposal_weight, + proposal_len + )); assert_eq!( System::events(), vec![ - record(Event::Collective(CollectiveEvent::Proposed(1, 0, hash, 3))), - record(Event::Collective(CollectiveEvent::Voted(1, hash, true, 1, 0))), - record(Event::Collective(CollectiveEvent::Voted(2, hash, true, 2, 0))), - record(Event::Collective(CollectiveEvent::Closed(hash, 2, 1))), - record(Event::Collective(CollectiveEvent::Disapproved(hash))) + record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 3 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 2, + proposal_hash: hash, + voted: true, + yes: 2, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Closed { + proposal_hash: hash, + yes: 2, + no: 1 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Disapproved { + proposal_hash: hash + })) ] ); }); @@ -229,7 +263,7 @@ fn close_works() { #[test] fn proposal_weight_limit_works_on_approve() { new_test_ext().execute_with(|| { - let proposal = Call::Collective(crate::Call::set_members { + let proposal = RuntimeCall::Collective(crate::Call::set_members { new_members: vec![1, 2, 3], prime: None, old_count: MaxMembers::get(), @@ -240,26 +274,38 @@ fn proposal_weight_limit_works_on_approve() { // Set 1 as prime voter Prime::::set(Some(1)); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); // With 1's prime vote, this should pass System::set_block_number(4); assert_noop!( - Collective::close(Origin::signed(4), hash, 0, proposal_weight - 100, proposal_len), + Collective::close( + RuntimeOrigin::signed(4), + hash, + 0, + proposal_weight - Weight::from_ref_time(100), + proposal_len + ), Error::::WrongProposalWeight ); - assert_ok!(Collective::close(Origin::signed(4), hash, 0, proposal_weight, proposal_len)); + assert_ok!(Collective::close( + RuntimeOrigin::signed(4), + hash, + 0, + proposal_weight, + proposal_len + )); }) } #[test] fn proposal_weight_limit_ignored_on_disapprove() { new_test_ext().execute_with(|| { - let proposal = Call::Collective(crate::Call::set_members { + let proposal = RuntimeCall::Collective(crate::Call::set_members { new_members: vec![1, 2, 3], prime: None, old_count: MaxMembers::get(), @@ -269,7 +315,7 @@ fn proposal_weight_limit_ignored_on_disapprove() { let hash = BlakeTwo256::hash_of(&proposal); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len @@ -277,10 +323,10 @@ fn proposal_weight_limit_ignored_on_disapprove() { // No votes, this proposal wont pass System::set_block_number(4); assert_ok!(Collective::close( - Origin::signed(4), + RuntimeOrigin::signed(4), hash, 0, - proposal_weight - 100, + proposal_weight - Weight::from_ref_time(100), proposal_len )); }) @@ -294,32 +340,61 @@ fn close_with_prime_works() { let proposal_weight = proposal.get_dispatch_info().weight; let hash = BlakeTwo256::hash_of(&proposal); assert_ok!(Collective::set_members( - Origin::root(), + RuntimeOrigin::root(), vec![1, 2, 3], Some(3), MaxMembers::get() )); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, true)); System::set_block_number(4); - assert_ok!(Collective::close(Origin::signed(4), hash, 0, proposal_weight, proposal_len)); + assert_ok!(Collective::close( + RuntimeOrigin::signed(4), + hash, + 0, + proposal_weight, + proposal_len + )); assert_eq!( System::events(), vec![ - record(Event::Collective(CollectiveEvent::Proposed(1, 0, hash, 3))), - record(Event::Collective(CollectiveEvent::Voted(1, hash, true, 1, 0))), - record(Event::Collective(CollectiveEvent::Voted(2, hash, true, 2, 0))), - record(Event::Collective(CollectiveEvent::Closed(hash, 2, 1))), - record(Event::Collective(CollectiveEvent::Disapproved(hash))) + record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 3 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 2, + proposal_hash: hash, + voted: true, + yes: 2, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Closed { + proposal_hash: hash, + yes: 2, + no: 1 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Disapproved { + proposal_hash: hash + })) ] ); }); @@ -333,36 +408,63 @@ fn close_with_voting_prime_works() { let proposal_weight = proposal.get_dispatch_info().weight; let hash = BlakeTwo256::hash_of(&proposal); assert_ok!(Collective::set_members( - Origin::root(), + RuntimeOrigin::root(), vec![1, 2, 3], Some(1), MaxMembers::get() )); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, true)); System::set_block_number(4); - assert_ok!(Collective::close(Origin::signed(4), hash, 0, proposal_weight, proposal_len)); + assert_ok!(Collective::close( + RuntimeOrigin::signed(4), + hash, + 0, + proposal_weight, + proposal_len + )); assert_eq!( System::events(), vec![ - record(Event::Collective(CollectiveEvent::Proposed(1, 0, hash, 3))), - record(Event::Collective(CollectiveEvent::Voted(1, hash, true, 1, 0))), - record(Event::Collective(CollectiveEvent::Voted(2, hash, true, 2, 0))), - record(Event::Collective(CollectiveEvent::Closed(hash, 3, 0))), - record(Event::Collective(CollectiveEvent::Approved(hash))), - record(Event::Collective(CollectiveEvent::Executed( - hash, - Err(DispatchError::BadOrigin) - ))) + record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 3 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 2, + proposal_hash: hash, + voted: true, + yes: 2, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Closed { + proposal_hash: hash, + yes: 3, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Approved { proposal_hash: hash })), + record(RuntimeEvent::Collective(CollectiveEvent::Executed { + proposal_hash: hash, + result: Err(DispatchError::BadOrigin) + })) ] ); }); @@ -376,25 +478,25 @@ fn close_with_no_prime_but_majority_works() { let proposal_weight = proposal.get_dispatch_info().weight; let hash = BlakeTwo256::hash_of(&proposal); assert_ok!(CollectiveMajority::set_members( - Origin::root(), + RuntimeOrigin::root(), vec![1, 2, 3, 4, 5], Some(5), MaxMembers::get() )); assert_ok!(CollectiveMajority::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 5, Box::new(proposal.clone()), proposal_len )); - assert_ok!(CollectiveMajority::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(CollectiveMajority::vote(Origin::signed(2), hash, 0, true)); - assert_ok!(CollectiveMajority::vote(Origin::signed(3), hash, 0, true)); + assert_ok!(CollectiveMajority::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(CollectiveMajority::vote(RuntimeOrigin::signed(2), hash, 0, true)); + assert_ok!(CollectiveMajority::vote(RuntimeOrigin::signed(3), hash, 0, true)); System::set_block_number(4); assert_ok!(CollectiveMajority::close( - Origin::signed(4), + RuntimeOrigin::signed(4), hash, 0, proposal_weight, @@ -404,16 +506,45 @@ fn close_with_no_prime_but_majority_works() { assert_eq!( System::events(), vec![ - record(Event::CollectiveMajority(CollectiveEvent::Proposed(1, 0, hash, 5))), - record(Event::CollectiveMajority(CollectiveEvent::Voted(1, hash, true, 1, 0))), - record(Event::CollectiveMajority(CollectiveEvent::Voted(2, hash, true, 2, 0))), - record(Event::CollectiveMajority(CollectiveEvent::Voted(3, hash, true, 3, 0))), - record(Event::CollectiveMajority(CollectiveEvent::Closed(hash, 5, 0))), - record(Event::CollectiveMajority(CollectiveEvent::Approved(hash))), - record(Event::CollectiveMajority(CollectiveEvent::Executed( - hash, - Err(DispatchError::BadOrigin) - ))) + record(RuntimeEvent::CollectiveMajority(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 5 + })), + record(RuntimeEvent::CollectiveMajority(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0 + })), + record(RuntimeEvent::CollectiveMajority(CollectiveEvent::Voted { + account: 2, + proposal_hash: hash, + voted: true, + yes: 2, + no: 0 + })), + record(RuntimeEvent::CollectiveMajority(CollectiveEvent::Voted { + account: 3, + proposal_hash: hash, + voted: true, + yes: 3, + no: 0 + })), + record(RuntimeEvent::CollectiveMajority(CollectiveEvent::Closed { + proposal_hash: hash, + yes: 5, + no: 0 + })), + record(RuntimeEvent::CollectiveMajority(CollectiveEvent::Approved { + proposal_hash: hash + })), + record(RuntimeEvent::CollectiveMajority(CollectiveEvent::Executed { + proposal_hash: hash, + result: Err(DispatchError::BadOrigin) + })) ] ); }); @@ -427,13 +558,13 @@ fn removal_of_old_voters_votes_works() { let hash = BlakeTwo256::hash_of(&proposal); let end = 4; assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, true)); assert_eq!( Collective::voting(&hash), Some(Votes { index: 0, threshold: 3, ayes: vec![1, 2], nays: vec![], end }) @@ -448,13 +579,13 @@ fn removal_of_old_voters_votes_works() { let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash = BlakeTwo256::hash_of(&proposal); assert_ok!(Collective::propose( - Origin::signed(2), + RuntimeOrigin::signed(2), 2, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(2), hash, 1, true)); - assert_ok!(Collective::vote(Origin::signed(3), hash, 1, false)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 1, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(3), hash, 1, false)); assert_eq!( Collective::voting(&hash), Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![3], end }) @@ -475,18 +606,23 @@ fn removal_of_old_voters_votes_works_with_set_members() { let hash = BlakeTwo256::hash_of(&proposal); let end = 4; assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, true)); assert_eq!( Collective::voting(&hash), Some(Votes { index: 0, threshold: 3, ayes: vec![1, 2], nays: vec![], end }) ); - assert_ok!(Collective::set_members(Origin::root(), vec![2, 3, 4], None, MaxMembers::get())); + assert_ok!(Collective::set_members( + RuntimeOrigin::root(), + vec![2, 3, 4], + None, + MaxMembers::get() + )); assert_eq!( Collective::voting(&hash), Some(Votes { index: 0, threshold: 3, ayes: vec![2], nays: vec![], end }) @@ -496,18 +632,23 @@ fn removal_of_old_voters_votes_works_with_set_members() { let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash = BlakeTwo256::hash_of(&proposal); assert_ok!(Collective::propose( - Origin::signed(2), + RuntimeOrigin::signed(2), 2, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(2), hash, 1, true)); - assert_ok!(Collective::vote(Origin::signed(3), hash, 1, false)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 1, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(3), hash, 1, false)); assert_eq!( Collective::voting(&hash), Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![3], end }) ); - assert_ok!(Collective::set_members(Origin::root(), vec![2, 4], None, MaxMembers::get())); + assert_ok!(Collective::set_members( + RuntimeOrigin::root(), + vec![2, 4], + None, + MaxMembers::get() + )); assert_eq!( Collective::voting(&hash), Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![], end }) @@ -523,7 +664,7 @@ fn propose_works() { let hash = proposal.blake2_256().into(); let end = 4; assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len @@ -537,7 +678,12 @@ fn propose_works() { assert_eq!( System::events(), - vec![record(Event::Collective(CollectiveEvent::Proposed(1, 0, hash, 3)))] + vec![record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 3 + }))] ); }); } @@ -549,7 +695,7 @@ fn limit_active_proposals() { let proposal = make_proposal(i as u64); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len @@ -558,7 +704,12 @@ fn limit_active_proposals() { let proposal = make_proposal(MaxProposals::get() as u64 + 1); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); assert_noop!( - Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone()), proposal_len), + Collective::propose( + RuntimeOrigin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + ), Error::::TooManyProposals ); }) @@ -567,13 +718,18 @@ fn limit_active_proposals() { #[test] fn correct_validate_and_get_proposal() { new_test_ext().execute_with(|| { - let proposal = Call::Collective(crate::Call::set_members { + let proposal = RuntimeCall::Collective(crate::Call::set_members { new_members: vec![1, 2, 3], prime: None, old_count: MaxMembers::get(), }); let length = proposal.encode().len() as u32; - assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone()), length)); + assert_ok!(Collective::propose( + RuntimeOrigin::signed(1), + 3, + Box::new(proposal.clone()), + length + )); let hash = BlakeTwo256::hash_of(&proposal); let weight = proposal.get_dispatch_info().weight; @@ -590,7 +746,11 @@ fn correct_validate_and_get_proposal() { Error::::WrongProposalLength ); assert_noop!( - Collective::validate_and_get_proposal(&hash, length, weight - 10), + Collective::validate_and_get_proposal( + &hash, + length, + weight - Weight::from_ref_time(10) + ), Error::::WrongProposalWeight ); let res = Collective::validate_and_get_proposal(&hash, length, weight); @@ -607,7 +767,12 @@ fn motions_ignoring_non_collective_proposals_works() { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); assert_noop!( - Collective::propose(Origin::signed(42), 3, Box::new(proposal.clone()), proposal_len), + Collective::propose( + RuntimeOrigin::signed(42), + 3, + Box::new(proposal.clone()), + proposal_len + ), Error::::NotMember ); }); @@ -620,13 +785,13 @@ fn motions_ignoring_non_collective_votes_works() { let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); assert_noop!( - Collective::vote(Origin::signed(42), hash, 0, true), + Collective::vote(RuntimeOrigin::signed(42), hash, 0, true), Error::::NotMember, ); }); @@ -640,13 +805,13 @@ fn motions_ignoring_bad_index_collective_vote_works() { let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); assert_noop!( - Collective::vote(Origin::signed(2), hash, 1, true), + Collective::vote(RuntimeOrigin::signed(2), hash, 1, true), Error::::WrongIndex, ); }); @@ -660,7 +825,7 @@ fn motions_vote_after_works() { let hash: H256 = proposal.blake2_256().into(); let end = 4; assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, Box::new(proposal.clone()), proposal_len @@ -671,34 +836,51 @@ fn motions_vote_after_works() { Some(Votes { index: 0, threshold: 2, ayes: vec![], nays: vec![], end }) ); // Cast first aye vote. - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); assert_eq!( Collective::voting(&hash), Some(Votes { index: 0, threshold: 2, ayes: vec![1], nays: vec![], end }) ); // Try to cast a duplicate aye vote. assert_noop!( - Collective::vote(Origin::signed(1), hash, 0, true), + Collective::vote(RuntimeOrigin::signed(1), hash, 0, true), Error::::DuplicateVote, ); // Cast a nay vote. - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, false)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, false)); assert_eq!( Collective::voting(&hash), Some(Votes { index: 0, threshold: 2, ayes: vec![], nays: vec![1], end }) ); // Try to cast a duplicate nay vote. assert_noop!( - Collective::vote(Origin::signed(1), hash, 0, false), + Collective::vote(RuntimeOrigin::signed(1), hash, 0, false), Error::::DuplicateVote, ); assert_eq!( System::events(), vec![ - record(Event::Collective(CollectiveEvent::Proposed(1, 0, hash, 2))), - record(Event::Collective(CollectiveEvent::Voted(1, hash, true, 1, 0))), - record(Event::Collective(CollectiveEvent::Voted(1, hash, false, 0, 1))), + record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 2 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: false, + yes: 0, + no: 1 + })), ] ); }); @@ -712,7 +894,7 @@ fn motions_all_first_vote_free_works() { let hash: H256 = proposal.blake2_256().into(); let end = 4; assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, Box::new(proposal.clone()), proposal_len, @@ -724,40 +906,40 @@ fn motions_all_first_vote_free_works() { // For the motion, acc 2's first vote, expecting Ok with Pays::No. let vote_rval: DispatchResultWithPostInfo = - Collective::vote(Origin::signed(2), hash, 0, true); + Collective::vote(RuntimeOrigin::signed(2), hash, 0, true); assert_eq!(vote_rval.unwrap().pays_fee, Pays::No); // Duplicate vote, expecting error with Pays::Yes. let vote_rval: DispatchResultWithPostInfo = - Collective::vote(Origin::signed(2), hash, 0, true); + Collective::vote(RuntimeOrigin::signed(2), hash, 0, true); assert_eq!(vote_rval.unwrap_err().post_info.pays_fee, Pays::Yes); // Modifying vote, expecting ok with Pays::Yes. let vote_rval: DispatchResultWithPostInfo = - Collective::vote(Origin::signed(2), hash, 0, false); + Collective::vote(RuntimeOrigin::signed(2), hash, 0, false); assert_eq!(vote_rval.unwrap().pays_fee, Pays::Yes); // For the motion, acc 3's first vote, expecting Ok with Pays::No. let vote_rval: DispatchResultWithPostInfo = - Collective::vote(Origin::signed(3), hash, 0, true); + Collective::vote(RuntimeOrigin::signed(3), hash, 0, true); assert_eq!(vote_rval.unwrap().pays_fee, Pays::No); // acc 3 modify the vote, expecting Ok with Pays::Yes. let vote_rval: DispatchResultWithPostInfo = - Collective::vote(Origin::signed(3), hash, 0, false); + Collective::vote(RuntimeOrigin::signed(3), hash, 0, false); assert_eq!(vote_rval.unwrap().pays_fee, Pays::Yes); // Test close() Extrincis | Check DispatchResultWithPostInfo with Pay Info let proposal_weight = proposal.get_dispatch_info().weight; let close_rval: DispatchResultWithPostInfo = - Collective::close(Origin::signed(2), hash, 0, proposal_weight, proposal_len); + Collective::close(RuntimeOrigin::signed(2), hash, 0, proposal_weight, proposal_len); assert_eq!(close_rval.unwrap().pays_fee, Pays::No); // trying to close the proposal, which is already closed. // Expecting error "ProposalMissing" with Pays::Yes let close_rval: DispatchResultWithPostInfo = - Collective::close(Origin::signed(2), hash, 0, proposal_weight, proposal_len); + Collective::close(RuntimeOrigin::signed(2), hash, 0, proposal_weight, proposal_len); assert_eq!(close_rval.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -770,16 +952,22 @@ fn motions_reproposing_disapproved_works() { let proposal_weight = proposal.get_dispatch_info().weight; let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, false)); - assert_ok!(Collective::close(Origin::signed(2), hash, 0, proposal_weight, proposal_len)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, false)); + assert_ok!(Collective::close( + RuntimeOrigin::signed(2), + hash, + 0, + proposal_weight, + proposal_len + )); assert_eq!(*Collective::proposals(), vec![]); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, Box::new(proposal.clone()), proposal_len @@ -791,7 +979,7 @@ fn motions_reproposing_disapproved_works() { #[test] fn motions_approval_with_enough_votes_and_lower_voting_threshold_works() { new_test_ext().execute_with(|| { - let proposal = Call::Democracy(mock_democracy::Call::external_propose_majority {}); + let proposal = RuntimeCall::Democracy(mock_democracy::Call::external_propose_majority {}); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let proposal_weight = proposal.get_dispatch_info().weight; let hash: H256 = proposal.blake2_256().into(); @@ -801,26 +989,53 @@ fn motions_approval_with_enough_votes_and_lower_voting_threshold_works() { // // Failed to execute with only 2 yes votes. assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, true)); - assert_ok!(Collective::close(Origin::signed(2), hash, 0, proposal_weight, proposal_len)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, true)); + assert_ok!(Collective::close( + RuntimeOrigin::signed(2), + hash, + 0, + proposal_weight, + proposal_len + )); assert_eq!( System::events(), vec![ - record(Event::Collective(CollectiveEvent::Proposed(1, 0, hash, 2))), - record(Event::Collective(CollectiveEvent::Voted(1, hash, true, 1, 0))), - record(Event::Collective(CollectiveEvent::Voted(2, hash, true, 2, 0))), - record(Event::Collective(CollectiveEvent::Closed(hash, 2, 0))), - record(Event::Collective(CollectiveEvent::Approved(hash))), - record(Event::Collective(CollectiveEvent::Executed( - hash, - Err(DispatchError::BadOrigin) - ))), + record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 2 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 2, + proposal_hash: hash, + voted: true, + yes: 2, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Closed { + proposal_hash: hash, + yes: 2, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Approved { proposal_hash: hash })), + record(RuntimeEvent::Collective(CollectiveEvent::Executed { + proposal_hash: hash, + result: Err(DispatchError::BadOrigin) + })), ] ); @@ -828,26 +1043,64 @@ fn motions_approval_with_enough_votes_and_lower_voting_threshold_works() { // Executed with 3 yes votes. assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(1), hash, 1, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 1, true)); - assert_ok!(Collective::vote(Origin::signed(3), hash, 1, true)); - assert_ok!(Collective::close(Origin::signed(2), hash, 1, proposal_weight, proposal_len)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 1, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 1, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(3), hash, 1, true)); + assert_ok!(Collective::close( + RuntimeOrigin::signed(2), + hash, + 1, + proposal_weight, + proposal_len + )); assert_eq!( System::events(), vec![ - record(Event::Collective(CollectiveEvent::Proposed(1, 1, hash, 2))), - record(Event::Collective(CollectiveEvent::Voted(1, hash, true, 1, 0))), - record(Event::Collective(CollectiveEvent::Voted(2, hash, true, 2, 0))), - record(Event::Collective(CollectiveEvent::Voted(3, hash, true, 3, 0))), - record(Event::Collective(CollectiveEvent::Closed(hash, 3, 0))), - record(Event::Collective(CollectiveEvent::Approved(hash))), - record(Event::Democracy(mock_democracy::pallet::Event::::ExternalProposed)), - record(Event::Collective(CollectiveEvent::Executed(hash, Ok(())))), + record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 1, + proposal_hash: hash, + threshold: 2 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 2, + proposal_hash: hash, + voted: true, + yes: 2, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 3, + proposal_hash: hash, + voted: true, + yes: 3, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Closed { + proposal_hash: hash, + yes: 3, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Approved { proposal_hash: hash })), + record(RuntimeEvent::Democracy( + mock_democracy::pallet::Event::::ExternalProposed + )), + record(RuntimeEvent::Collective(CollectiveEvent::Executed { + proposal_hash: hash, + result: Ok(()) + })), ] ); }); @@ -861,23 +1114,52 @@ fn motions_disapproval_works() { let proposal_weight = proposal.get_dispatch_info().weight; let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, false)); - assert_ok!(Collective::close(Origin::signed(2), hash, 0, proposal_weight, proposal_len)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, false)); + assert_ok!(Collective::close( + RuntimeOrigin::signed(2), + hash, + 0, + proposal_weight, + proposal_len + )); assert_eq!( System::events(), vec![ - record(Event::Collective(CollectiveEvent::Proposed(1, 0, hash, 3))), - record(Event::Collective(CollectiveEvent::Voted(1, hash, true, 1, 0))), - record(Event::Collective(CollectiveEvent::Voted(2, hash, false, 1, 1))), - record(Event::Collective(CollectiveEvent::Closed(hash, 1, 1))), - record(Event::Collective(CollectiveEvent::Disapproved(hash))), + record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 3 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 2, + proposal_hash: hash, + voted: false, + yes: 1, + no: 1 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Closed { + proposal_hash: hash, + yes: 1, + no: 1 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Disapproved { + proposal_hash: hash + })), ] ); }); @@ -891,27 +1173,54 @@ fn motions_approval_works() { let proposal_weight = proposal.get_dispatch_info().weight; let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, Box::new(proposal.clone()), proposal_len )); - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, true)); - assert_ok!(Collective::close(Origin::signed(2), hash, 0, proposal_weight, proposal_len)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, true)); + assert_ok!(Collective::close( + RuntimeOrigin::signed(2), + hash, + 0, + proposal_weight, + proposal_len + )); assert_eq!( System::events(), vec![ - record(Event::Collective(CollectiveEvent::Proposed(1, 0, hash, 2))), - record(Event::Collective(CollectiveEvent::Voted(1, hash, true, 1, 0))), - record(Event::Collective(CollectiveEvent::Voted(2, hash, true, 2, 0))), - record(Event::Collective(CollectiveEvent::Closed(hash, 2, 0))), - record(Event::Collective(CollectiveEvent::Approved(hash))), - record(Event::Collective(CollectiveEvent::Executed( - hash, - Err(DispatchError::BadOrigin) - ))), + record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 2 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 2, + proposal_hash: hash, + voted: true, + yes: 2, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Closed { + proposal_hash: hash, + yes: 2, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Approved { proposal_hash: hash })), + record(RuntimeEvent::Collective(CollectiveEvent::Executed { + proposal_hash: hash, + result: Err(DispatchError::BadOrigin) + })), ] ); }); @@ -925,20 +1234,25 @@ fn motion_with_no_votes_closes_with_disapproval() { let proposal_weight = proposal.get_dispatch_info().weight; let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, Box::new(proposal.clone()), proposal_len )); assert_eq!( System::events()[0], - record(Event::Collective(CollectiveEvent::Proposed(1, 0, hash, 3))) + record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 3 + })) ); // Closing the motion too early is not possible because it has neither // an approving or disapproving simple majority due to the lack of votes. assert_noop!( - Collective::close(Origin::signed(2), hash, 0, proposal_weight, proposal_len), + Collective::close(RuntimeOrigin::signed(2), hash, 0, proposal_weight, proposal_len), Error::::TooEarly ); @@ -946,16 +1260,26 @@ fn motion_with_no_votes_closes_with_disapproval() { let closing_block = System::block_number() + MotionDuration::get(); System::set_block_number(closing_block); // we can successfully close the motion. - assert_ok!(Collective::close(Origin::signed(2), hash, 0, proposal_weight, proposal_len)); + assert_ok!(Collective::close( + RuntimeOrigin::signed(2), + hash, + 0, + proposal_weight, + proposal_len + )); // Events show that the close ended in a disapproval. assert_eq!( System::events()[1], - record(Event::Collective(CollectiveEvent::Closed(hash, 0, 3))) + record(RuntimeEvent::Collective(CollectiveEvent::Closed { + proposal_hash: hash, + yes: 0, + no: 3 + })) ); assert_eq!( System::events()[2], - record(Event::Collective(CollectiveEvent::Disapproved(hash))) + record(RuntimeEvent::Collective(CollectiveEvent::Disapproved { proposal_hash: hash })) ); }) } @@ -970,28 +1294,28 @@ fn close_disapprove_does_not_care_about_weight_or_len() { let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, Box::new(proposal.clone()), proposal_len )); // First we make the proposal succeed - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, true)); // It will not close with bad weight/len information assert_noop!( - Collective::close(Origin::signed(2), hash, 0, 0, 0), + Collective::close(RuntimeOrigin::signed(2), hash, 0, Weight::zero(), 0), Error::::WrongProposalLength, ); assert_noop!( - Collective::close(Origin::signed(2), hash, 0, 0, proposal_len), + Collective::close(RuntimeOrigin::signed(2), hash, 0, Weight::zero(), proposal_len), Error::::WrongProposalWeight, ); // Now we make the proposal fail - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, false)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, false)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, false)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, false)); // It can close even if the weight/len information is bad - assert_ok!(Collective::close(Origin::signed(2), hash, 0, 0, 0)); + assert_ok!(Collective::close(RuntimeOrigin::signed(2), hash, 0, Weight::zero(), 0)); }) } @@ -1002,23 +1326,42 @@ fn disapprove_proposal_works() { let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, Box::new(proposal.clone()), proposal_len )); // Proposal would normally succeed - assert_ok!(Collective::vote(Origin::signed(1), hash, 0, true)); - assert_ok!(Collective::vote(Origin::signed(2), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Collective::vote(RuntimeOrigin::signed(2), hash, 0, true)); // But Root can disapprove and remove it anyway - assert_ok!(Collective::disapprove_proposal(Origin::root(), hash)); + assert_ok!(Collective::disapprove_proposal(RuntimeOrigin::root(), hash)); assert_eq!( System::events(), vec![ - record(Event::Collective(CollectiveEvent::Proposed(1, 0, hash, 2))), - record(Event::Collective(CollectiveEvent::Voted(1, hash, true, 1, 0))), - record(Event::Collective(CollectiveEvent::Voted(2, hash, true, 2, 0))), - record(Event::Collective(CollectiveEvent::Disapproved(hash))), + record(RuntimeEvent::Collective(CollectiveEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 2 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 1, + proposal_hash: hash, + voted: true, + yes: 1, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Voted { + account: 2, + proposal_hash: hash, + voted: true, + yes: 2, + no: 0 + })), + record(RuntimeEvent::Collective(CollectiveEvent::Disapproved { + proposal_hash: hash + })), ] ); }) diff --git a/frame/collective/src/weights.rs b/frame/collective/src/weights.rs index 40ac9eabdd6e4..052550de7bd7e 100644 --- a/frame/collective/src/weights.rs +++ b/frame/collective/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_collective //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/collective/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -60,266 +62,326 @@ pub trait WeightInfo { /// Weights for pallet_collective using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Instance1Collective Members (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:0) - // Storage: Instance1Collective Voting (r:100 w:100) - // Storage: Instance1Collective Prime (r:0 w:1) - fn set_members(m: u32, n: u32, p: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 4_000 - .saturating_add((14_084_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 4_000 - .saturating_add((161_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 4_000 - .saturating_add((19_201_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(p as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) + // Storage: Council Members (r:1 w:1) + // Storage: Council Proposals (r:1 w:0) + // Storage: Council Prime (r:0 w:1) + // Storage: Council Voting (r:100 w:100) + /// The range of component `m` is `[0, 100]`. + /// The range of component `n` is `[0, 100]`. + /// The range of component `p` is `[0, 100]`. + fn set_members(m: u32, _n: u32, p: u32, ) -> Weight { + // Minimum execution time: 18_895 nanoseconds. + Weight::from_ref_time(19_254_000 as u64) + // Standard Error: 63_540 + .saturating_add(Weight::from_ref_time(5_061_801 as u64).saturating_mul(m as u64)) + // Standard Error: 63_540 + .saturating_add(Weight::from_ref_time(7_588_981 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(p as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(p as u64))) } - // Storage: Instance1Collective Members (r:1 w:0) + // Storage: Council Members (r:1 w:0) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[1, 100]`. fn execute(b: u32, m: u32, ) -> Weight { - (22_748_000 as Weight) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 0 - .saturating_add((92_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) + // Minimum execution time: 24_469 nanoseconds. + Weight::from_ref_time(23_961_134 as u64) + // Standard Error: 43 + .saturating_add(Weight::from_ref_time(1_677 as u64).saturating_mul(b as u64)) + // Standard Error: 450 + .saturating_add(Weight::from_ref_time(18_645 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) } - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective ProposalOf (r:1 w:0) + // Storage: Council Members (r:1 w:0) + // Storage: Council ProposalOf (r:1 w:0) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[1, 100]`. fn propose_execute(b: u32, m: u32, ) -> Weight { - (27_465_000 as Weight) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 0 - .saturating_add((178_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) + // Minimum execution time: 26_476 nanoseconds. + Weight::from_ref_time(25_829_298 as u64) + // Standard Error: 49 + .saturating_add(Weight::from_ref_time(1_741 as u64).saturating_mul(b as u64)) + // Standard Error: 515 + .saturating_add(Weight::from_ref_time(29_436 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) } - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective ProposalOf (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:1) - // Storage: Instance1Collective ProposalCount (r:1 w:1) - // Storage: Instance1Collective Voting (r:0 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council ProposalOf (r:1 w:1) + // Storage: Council Proposals (r:1 w:1) + // Storage: Council ProposalCount (r:1 w:1) + // Storage: Council Voting (r:0 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[2, 100]`. + /// The range of component `p` is `[1, 100]`. fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { - (39_869_000 as Weight) - // Standard Error: 0 - .saturating_add((8_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 1_000 - .saturating_add((107_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 1_000 - .saturating_add((406_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 33_585 nanoseconds. + Weight::from_ref_time(33_092_289 as u64) + // Standard Error: 173 + .saturating_add(Weight::from_ref_time(4_266 as u64).saturating_mul(b as u64)) + // Standard Error: 1_812 + .saturating_add(Weight::from_ref_time(29_262 as u64).saturating_mul(m as u64)) + // Standard Error: 1_789 + .saturating_add(Weight::from_ref_time(181_285 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective Voting (r:1 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council Voting (r:1 w:1) + /// The range of component `m` is `[5, 100]`. fn vote(m: u32, ) -> Weight { - (37_387_000 as Weight) - // Standard Error: 2_000 - .saturating_add((223_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 36_374 nanoseconds. + Weight::from_ref_time(38_950_243 as u64) + // Standard Error: 2_583 + .saturating_add(Weight::from_ref_time(65_345 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Instance1Collective Voting (r:1 w:1) - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective Proposals (r:1 w:1) - // Storage: Instance1Collective ProposalOf (r:0 w:1) + // Storage: Council Voting (r:1 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council Proposals (r:1 w:1) + // Storage: Council ProposalOf (r:0 w:1) + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - (45_670_000 as Weight) - // Standard Error: 1_000 - .saturating_add((170_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 1_000 - .saturating_add((358_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 36_066 nanoseconds. + Weight::from_ref_time(38_439_655 as u64) + // Standard Error: 1_281 + .saturating_add(Weight::from_ref_time(17_045 as u64).saturating_mul(m as u64)) + // Standard Error: 1_249 + .saturating_add(Weight::from_ref_time(164_998 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Instance1Collective Voting (r:1 w:1) - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective ProposalOf (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:1) + // Storage: Council Voting (r:1 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council ProposalOf (r:1 w:1) + // Storage: Council Proposals (r:1 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - (52_529_000 as Weight) - // Standard Error: 0 - .saturating_add((7_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 1_000 - .saturating_add((206_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 1_000 - .saturating_add((412_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 47_753 nanoseconds. + Weight::from_ref_time(46_507_829 as u64) + // Standard Error: 149 + .saturating_add(Weight::from_ref_time(2_159 as u64).saturating_mul(b as u64)) + // Standard Error: 1_581 + .saturating_add(Weight::from_ref_time(37_842 as u64).saturating_mul(m as u64)) + // Standard Error: 1_541 + .saturating_add(Weight::from_ref_time(173_395 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Instance1Collective Voting (r:1 w:1) - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective Prime (r:1 w:0) - // Storage: Instance1Collective Proposals (r:1 w:1) - // Storage: Instance1Collective ProposalOf (r:0 w:1) + // Storage: Council Voting (r:1 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council Prime (r:1 w:0) + // Storage: Council Proposals (r:1 w:1) + // Storage: Council ProposalOf (r:0 w:1) + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. fn close_disapproved(m: u32, p: u32, ) -> Weight { - (50_427_000 as Weight) - // Standard Error: 1_000 - .saturating_add((170_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 1_000 - .saturating_add((354_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 39_416 nanoseconds. + Weight::from_ref_time(39_610_161 as u64) + // Standard Error: 1_231 + .saturating_add(Weight::from_ref_time(32_991 as u64).saturating_mul(m as u64)) + // Standard Error: 1_200 + .saturating_add(Weight::from_ref_time(170_773 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Instance1Collective Voting (r:1 w:1) - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective Prime (r:1 w:0) - // Storage: Instance1Collective ProposalOf (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:1) + // Storage: Council Voting (r:1 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council Prime (r:1 w:0) + // Storage: Council ProposalOf (r:1 w:1) + // Storage: Council Proposals (r:1 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { - (57_031_000 as Weight) - // Standard Error: 0 - .saturating_add((7_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 1_000 - .saturating_add((208_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 1_000 - .saturating_add((408_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 49_840 nanoseconds. + Weight::from_ref_time(48_542_914 as u64) + // Standard Error: 136 + .saturating_add(Weight::from_ref_time(2_650 as u64).saturating_mul(b as u64)) + // Standard Error: 1_442 + .saturating_add(Weight::from_ref_time(37_898 as u64).saturating_mul(m as u64)) + // Standard Error: 1_406 + .saturating_add(Weight::from_ref_time(182_176 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Instance1Collective Proposals (r:1 w:1) - // Storage: Instance1Collective Voting (r:0 w:1) - // Storage: Instance1Collective ProposalOf (r:0 w:1) + // Storage: Council Proposals (r:1 w:1) + // Storage: Council Voting (r:0 w:1) + // Storage: Council ProposalOf (r:0 w:1) + /// The range of component `p` is `[1, 100]`. fn disapprove_proposal(p: u32, ) -> Weight { - (27_458_000 as Weight) - // Standard Error: 1_000 - .saturating_add((402_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 24_199 nanoseconds. + Weight::from_ref_time(26_869_176 as u64) + // Standard Error: 1_609 + .saturating_add(Weight::from_ref_time(163_341 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Instance1Collective Members (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:0) - // Storage: Instance1Collective Voting (r:100 w:100) - // Storage: Instance1Collective Prime (r:0 w:1) - fn set_members(m: u32, n: u32, p: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 4_000 - .saturating_add((14_084_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 4_000 - .saturating_add((161_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 4_000 - .saturating_add((19_201_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(p as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) + // Storage: Council Members (r:1 w:1) + // Storage: Council Proposals (r:1 w:0) + // Storage: Council Prime (r:0 w:1) + // Storage: Council Voting (r:100 w:100) + /// The range of component `m` is `[0, 100]`. + /// The range of component `n` is `[0, 100]`. + /// The range of component `p` is `[0, 100]`. + fn set_members(m: u32, _n: u32, p: u32, ) -> Weight { + // Minimum execution time: 18_895 nanoseconds. + Weight::from_ref_time(19_254_000 as u64) + // Standard Error: 63_540 + .saturating_add(Weight::from_ref_time(5_061_801 as u64).saturating_mul(m as u64)) + // Standard Error: 63_540 + .saturating_add(Weight::from_ref_time(7_588_981 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(p as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(p as u64))) } - // Storage: Instance1Collective Members (r:1 w:0) + // Storage: Council Members (r:1 w:0) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[1, 100]`. fn execute(b: u32, m: u32, ) -> Weight { - (22_748_000 as Weight) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 0 - .saturating_add((92_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + // Minimum execution time: 24_469 nanoseconds. + Weight::from_ref_time(23_961_134 as u64) + // Standard Error: 43 + .saturating_add(Weight::from_ref_time(1_677 as u64).saturating_mul(b as u64)) + // Standard Error: 450 + .saturating_add(Weight::from_ref_time(18_645 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) } - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective ProposalOf (r:1 w:0) + // Storage: Council Members (r:1 w:0) + // Storage: Council ProposalOf (r:1 w:0) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[1, 100]`. fn propose_execute(b: u32, m: u32, ) -> Weight { - (27_465_000 as Weight) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 0 - .saturating_add((178_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + // Minimum execution time: 26_476 nanoseconds. + Weight::from_ref_time(25_829_298 as u64) + // Standard Error: 49 + .saturating_add(Weight::from_ref_time(1_741 as u64).saturating_mul(b as u64)) + // Standard Error: 515 + .saturating_add(Weight::from_ref_time(29_436 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) } - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective ProposalOf (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:1) - // Storage: Instance1Collective ProposalCount (r:1 w:1) - // Storage: Instance1Collective Voting (r:0 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council ProposalOf (r:1 w:1) + // Storage: Council Proposals (r:1 w:1) + // Storage: Council ProposalCount (r:1 w:1) + // Storage: Council Voting (r:0 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[2, 100]`. + /// The range of component `p` is `[1, 100]`. fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { - (39_869_000 as Weight) - // Standard Error: 0 - .saturating_add((8_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 1_000 - .saturating_add((107_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 1_000 - .saturating_add((406_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 33_585 nanoseconds. + Weight::from_ref_time(33_092_289 as u64) + // Standard Error: 173 + .saturating_add(Weight::from_ref_time(4_266 as u64).saturating_mul(b as u64)) + // Standard Error: 1_812 + .saturating_add(Weight::from_ref_time(29_262 as u64).saturating_mul(m as u64)) + // Standard Error: 1_789 + .saturating_add(Weight::from_ref_time(181_285 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective Voting (r:1 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council Voting (r:1 w:1) + /// The range of component `m` is `[5, 100]`. fn vote(m: u32, ) -> Weight { - (37_387_000 as Weight) - // Standard Error: 2_000 - .saturating_add((223_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 36_374 nanoseconds. + Weight::from_ref_time(38_950_243 as u64) + // Standard Error: 2_583 + .saturating_add(Weight::from_ref_time(65_345 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Instance1Collective Voting (r:1 w:1) - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective Proposals (r:1 w:1) - // Storage: Instance1Collective ProposalOf (r:0 w:1) + // Storage: Council Voting (r:1 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council Proposals (r:1 w:1) + // Storage: Council ProposalOf (r:0 w:1) + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - (45_670_000 as Weight) - // Standard Error: 1_000 - .saturating_add((170_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 1_000 - .saturating_add((358_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 36_066 nanoseconds. + Weight::from_ref_time(38_439_655 as u64) + // Standard Error: 1_281 + .saturating_add(Weight::from_ref_time(17_045 as u64).saturating_mul(m as u64)) + // Standard Error: 1_249 + .saturating_add(Weight::from_ref_time(164_998 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Instance1Collective Voting (r:1 w:1) - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective ProposalOf (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:1) + // Storage: Council Voting (r:1 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council ProposalOf (r:1 w:1) + // Storage: Council Proposals (r:1 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - (52_529_000 as Weight) - // Standard Error: 0 - .saturating_add((7_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 1_000 - .saturating_add((206_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 1_000 - .saturating_add((412_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 47_753 nanoseconds. + Weight::from_ref_time(46_507_829 as u64) + // Standard Error: 149 + .saturating_add(Weight::from_ref_time(2_159 as u64).saturating_mul(b as u64)) + // Standard Error: 1_581 + .saturating_add(Weight::from_ref_time(37_842 as u64).saturating_mul(m as u64)) + // Standard Error: 1_541 + .saturating_add(Weight::from_ref_time(173_395 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Instance1Collective Voting (r:1 w:1) - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective Prime (r:1 w:0) - // Storage: Instance1Collective Proposals (r:1 w:1) - // Storage: Instance1Collective ProposalOf (r:0 w:1) + // Storage: Council Voting (r:1 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council Prime (r:1 w:0) + // Storage: Council Proposals (r:1 w:1) + // Storage: Council ProposalOf (r:0 w:1) + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. fn close_disapproved(m: u32, p: u32, ) -> Weight { - (50_427_000 as Weight) - // Standard Error: 1_000 - .saturating_add((170_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 1_000 - .saturating_add((354_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 39_416 nanoseconds. + Weight::from_ref_time(39_610_161 as u64) + // Standard Error: 1_231 + .saturating_add(Weight::from_ref_time(32_991 as u64).saturating_mul(m as u64)) + // Standard Error: 1_200 + .saturating_add(Weight::from_ref_time(170_773 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Instance1Collective Voting (r:1 w:1) - // Storage: Instance1Collective Members (r:1 w:0) - // Storage: Instance1Collective Prime (r:1 w:0) - // Storage: Instance1Collective ProposalOf (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:1) + // Storage: Council Voting (r:1 w:1) + // Storage: Council Members (r:1 w:0) + // Storage: Council Prime (r:1 w:0) + // Storage: Council ProposalOf (r:1 w:1) + // Storage: Council Proposals (r:1 w:1) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { - (57_031_000 as Weight) - // Standard Error: 0 - .saturating_add((7_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 1_000 - .saturating_add((208_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 1_000 - .saturating_add((408_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 49_840 nanoseconds. + Weight::from_ref_time(48_542_914 as u64) + // Standard Error: 136 + .saturating_add(Weight::from_ref_time(2_650 as u64).saturating_mul(b as u64)) + // Standard Error: 1_442 + .saturating_add(Weight::from_ref_time(37_898 as u64).saturating_mul(m as u64)) + // Standard Error: 1_406 + .saturating_add(Weight::from_ref_time(182_176 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Instance1Collective Proposals (r:1 w:1) - // Storage: Instance1Collective Voting (r:0 w:1) - // Storage: Instance1Collective ProposalOf (r:0 w:1) + // Storage: Council Proposals (r:1 w:1) + // Storage: Council Voting (r:0 w:1) + // Storage: Council ProposalOf (r:0 w:1) + /// The range of component `p` is `[1, 100]`. fn disapprove_proposal(p: u32, ) -> Weight { - (27_458_000 as Weight) - // Standard Error: 1_000 - .saturating_add((402_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 24_199 nanoseconds. + Weight::from_ref_time(26_869_176 as u64) + // Standard Error: 1_609 + .saturating_add(Weight::from_ref_time(163_341 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } } diff --git a/frame/contracts/CHANGELOG.md b/frame/contracts/CHANGELOG.md index eaedd28bf3e47..ab3998e6dc4f7 100644 --- a/frame/contracts/CHANGELOG.md +++ b/frame/contracts/CHANGELOG.md @@ -23,13 +23,9 @@ In other words: Upgrading this pallet will not break pre-existing contracts. - Allow contracts to dispatch calls into the runtime (**unstable**) [#9276](https://github.com/paritytech/substrate/pull/9276) -- New **unstable** version of `seal_call` that offers more features. +- New version of `seal_call` that offers more features. [#8909](https://github.com/paritytech/substrate/pull/8909) -- New **unstable** `seal_rent_params` and `seal_rent_status` contract callable function. -[#8231](https://github.com/paritytech/substrate/pull/8231) -[#8780](https://github.com/paritytech/substrate/pull/8780) - - New `instantiate` RPC that allows clients to dry-run contract instantiation. [#8451](https://github.com/paritytech/substrate/pull/8451) @@ -38,6 +34,10 @@ In other words: Upgrading this pallet will not break pre-existing contracts. ### Changed +- Replaced storage rent with automatic storage deposits +[#9669](https://github.com/paritytech/substrate/pull/9669) +[#10082](https://github.com/paritytech/substrate/pull/10082) + - Replaced `seal_println` with the `seal_debug_message` API which allows outputting debug messages to the console and RPC clients. [#8773](https://github.com/paritytech/substrate/pull/8773) diff --git a/frame/contracts/COMPLEXITY.md b/frame/contracts/COMPLEXITY.md deleted file mode 100644 index 1fc1932fe1b5f..0000000000000 --- a/frame/contracts/COMPLEXITY.md +++ /dev/null @@ -1,487 +0,0 @@ -# Complexity - -This analysis is on the computing and memory complexity of specific procedures. It provides a rough estimate of operations performed in general and especially focusing on DB reads and writes. It is also an attempt to estimate the memory consumption at its peak. - -The primary goal is to come up with decent pricing for functions that can be invoked by a user (via extrinsics) or by untrusted code that prevents DoS attacks. - -## Sandboxing - -It makes sense to describe the sandboxing module first because the smart-contract module is built upon it. - -### Memory - -#### set - -Copies data from the supervisor's memory to the guest's memory. - -**complexity**: It doesn't allocate, and the computational complexity is proportional to the number of bytes to copy. - -#### get - -Copies data from the guest's memory to the supervisor's memory. - -**complexity**: It doesn't allocate, and the computational complexity is proportional to the number of bytes to copy. - -## Instance - -### Instantiation - -Instantiation of a sandbox module consists of the following steps: - -1. Loading the wasm module in the in-memory representation, -2. Performing validation of the wasm code, -3. Setting up the environment which will be used to instantiate the module, -4. Performing the standard wasm instantiation process, which includes (but is not limited to): - 1. Allocating of memory requested by the instance, - 2. Copying static data from the module to newly allocated memory, - 3. Executing the `start` function. - -**Note** that the `start` function can be viewed as a normal function and can do anything that a normal function can do, including allocation of more memory or calling the host environment. The complexity of running the `start` function should be considered separately. - -In order to start the process of instantiation, the supervisor should provide the wasm module code being instantiated and the environment definition (a set of functions, memories (and maybe globals and tables in the future) available for import by the guest module) for that module. While the environment definition typically is of the constant size (unless mechanisms like dynamic linking are used), the size of wasm is not. - -Validation and instantiation in WebAssembly are designed to be able to be performed in linear time. The allocation and computational complexity of loading a wasm module depend on the underlying wasm VM being used. For example, for JIT compilers it can and probably will be non-linear because of compilation. However, for wasmi, it should be linear. We can try to use other VMs that are able to compile code with memory and time consumption proportional to the size of the code. - -Since the module itself requests memory, the amount of allocation depends on the module code itself. If untrusted code is being instantiated, it's up to the supervisor to limit the amount of memory available to allocate. - -**complexity**: The computational complexity is proportional to the size of wasm code. Memory complexity is proportional to the size of wasm code and the amount of memory requested by the module. - -### Preparation to invoke - -Invocation of an exported function in the sandboxed module consists of the following steps: - -1. Marshalling, copying and unmarshalling the arguments when passing them between the supervisor and executor, -2. Calling into the underlying VM, -3. Marshalling, copying and unmarshalling the result when passing it between the executor and supervisor, - -**Note** that the complexity of running the function code itself should be considered separately. - -The actual complexity of invocation depends on the underlying VM. Wasmi will reserve a relatively large chunk of memory for the stack before execution of the code, although it's of constant size. - -The size of the arguments and the return value depends on the exact function in question, but can be considered as constant. - -**complexity**: Memory and computational complexity can be considered as a constant. - -### Call from the guest to the supervisor - -The executor handles each call from the guest. The execution of it consists of the following steps: - -1. Marshalling, copying and unmarshalling the arguments when passing them between the guest and executor, -2. Calling into the supervisor, -3. Marshaling, copying and unmarshalling the result when passing it between the executor and guest. - -**Note** that the complexity of running the supervisor handler should be considered separately. - -Because calling into the supervisor requires invoking a wasm VM, the actual complexity of invocation depends on the actual VM used for the runtime/supervisor. Wasmi will reserve a relatively large chunk of memory for the stack before execution of the code, although it's of constant size. - -The size of the arguments and the return value depends on the exact function in question, but can be considered as a constant. - -**complexity**: Memory and computational complexity can be considered as a constant. - -## Transactional Storage - -The contracts module makes use of the nested storage transactions feature offered by -the underlying storage which allows efficient roll back of changes made by contracts. - -> ℹ️ The underlying storage has a overlay layer implemented as a `Map`. If the runtime reads a storage location and the -> respective key doesn't exist in the overlay, then the underlying storage performs a DB access, but the value won't be -> placed into the overlay. The overlay is only filled with writes. -> -> This means that the overlay can be abused in the following ways: -> -> - The overlay can be inflated by issuing a lot of writes to unique locations, -> - Deliberate cache misses can be induced by reading non-modified storage locations, - -It also worth noting that the performance degrades with more state stored in the trie. Due to this -there is not negligible chance that gas schedule will be updated for all operations that involve -storage access. - -## get_storage, get_code_hash, get_rent_allowance, get_balance, contract_exists - -Those query the underlying storage for the requested value. If the value was modified in the -current block they are served from the cache. Otherwise a database read is performed. - -**complexity**: The memory complexity is proportional to the size of the value. The computational complexity is proportional the size of the value; the cost is dominated by the DB read. - -## set_storage, set_balance, set_rent_allowance - -These function write to the underlying storage which caches those values and does not write -them to the database immediately. - -While these functions only modify the local cache, they trigger a database write later when -all changes that were not rolled back are written to storage. Moreover, if the balance of the -account is changed to be below `existential_deposit` then that account along with all its storage -will be removed, which requires time proportional to the number of storage entries that account has. -It should be ensured that pricing accounts for these facts. - -**complexity**: Each lookup has a logarithmical computing time to the number of already inserted entries. -No additional memory is required. - -## instantiate_contract - -Calls `contract_exists` and if it doesn't exist, do not modify the local `Map` similarly to `set_rent_allowance`. - -**complexity**: The computational complexity is proportional to the depth of the overlay cascade and the size of the value; the cost is dominated by the DB read though. No additional memory is required. - -## commit - -In this function, all values modified in the current transactions are committed to the parent -transaction. - -This will trigger `N` inserts into parent transaction (`O(log M)` complexity) or into the storage, where `N` is the size of the current transaction and `M` is the size of the parent transaction. Consider adjusting the price of modifying the -current transaction to account for this (since pricing for the count of entries in `commit` will make the price of commit way less predictable). No additional memory is required. - -Note that in case of storage modification we need to construct a key in the underlying storage. In order to do that we need: - -- perform `twox_128` hashing over a concatenation of some prefix literal and the `AccountId` of the storage owner. -- then perform `blake2_256` hashing of the storage key. -- concatenation of these hashes will constitute the key in the underlying storage. - -There is also a special case to think of: if the balance of some account goes below `existential_deposit`, then all storage entries of that account will be erased, which requires time proportional to the number of storage entries that account has. - -**complexity**: `N` inserts into a transaction or eventually into the storage (if committed). Every deleted account will induce removal of all its storage which is proportional to the number of storage entries that account has. - -## revert - -Consists of dropping (in the Rust sense) of the current transaction. - -**complexity**: Computing complexity is proportional to a number of changed entries in a overlay. No additional memory is required. - -## Executive - -### Transfer - -This function performs the following steps: - -1. Querying source and destination balances from the current transaction (see `get_balance`), -2. Querying `existential_deposit`. -3. Executing `ensure_account_liquid` hook. -4. Updating source and destination balance in the overlay (see `set_balance`). - -**Note** that the complexity of executing `ensure_account_liquid` hook should be considered separately. - -In the course of the execution this function can perform up to 2 DB reads to `get_balance` of source and destination accounts. It can also induce up to 2 DB writes via `set_balance` if flushed to the storage. - -Moreover, if the source balance goes below `existential_deposit` then the transfer is denied and -returns with an error. - -Assuming marshaled size of a balance value is of the constant size we can neglect its effect on the performance. - -**complexity**: up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has. Memorywise it can be assumed to be constant. - -### Initialization - -Before a call or instantiate can be performed the execution context must be initialized. - -For the first call or instantiation in the handling of an extrinsic, this involves two calls: - -1. `>::now()` -2. `>::block_number()` - -The complexity of initialization depends on the complexity of these functions. In the current -implementation they just involve a DB read. - -For subsequent calls and instantiations during contract execution, the initialization requires no -expensive operations. - -### Terminate - -This function performs the following steps: - -1. Check the calling contract is not already on the callstack by calling `is_live`. -2. `transfer` funds from caller to the beneficiary. -3. Flag the caller contract as deleted in the overlay. - -`is_live` does not do any database access nor does it allocate memory. It walks up the call -stack and therefore executes in linear time depending on size of the call stack. Because -the call stack is of a fixed maximum size we consider this operation as constant time. - -**complexity**: Database accesses as described in Transfer + Removal of the contract. Currently, -we are using child trie removal which is linear in the amount of stored keys. Upcoming changes -will make the account removal constant time. - -### Call - -This function receives input data for the contract execution. The execution consists of the following steps: - -1. Initialization of the execution context. -2. Checking rent payment. -3. Loading code from the DB. -4. Starting a new storage transaction. -5. `transfer`-ing funds between the caller and the destination account. -6. Executing the code of the destination account. -7. Committing or rolling back the storage transaction. - -**Note** that the complexity of executing the contract code should be considered separately. - -Checking for rent involves 2 unconditional DB reads: `ContractInfoOf` and `block_number` -and on top of that at most once per block: - -- DB read to `free_balance` and -- `rent_deposit_offset` and -- `rent_byte_price` and -- `Currency::minimum_balance` and -- `tombstone_deposit`. -- Calls to `ensure_can_withdraw`, `withdraw`, `make_free_balance_be` can perform arbitrary logic and should be considered separately, -- `child_storage_root` -- `kill_child_storage` -- mutation of `ContractInfoOf` - -Loading code most likely will trigger a DB read, since the code is immutable and therefore will not get into the cache (unless a suicide removes it, or it has been instantiated in the same call chain). - -Also, `transfer` can make up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has. - -Finally, the current storage transaction is closed. The complexity of this depends on the number of changes performed by the code. Thus, the pricing of storage modification should account for that. - -**complexity**: - -- Only for the first invocation of the contract: up to 5 DB reads and one DB write as well as logic executed by `ensure_can_withdraw`, `withdraw`, `make_free_balance_be`. -- On top of that for every invocation: Up to 5 DB reads. DB read of the code is of dynamic size. There can also be up to 2 DB writes (if flushed to the storage). Additionally, if the source account removal takes place a DB write will be performed per one storage entry that the account has. - -### Instantiate - -This function takes the code of the constructor and input data. Instantiation of a contract consists of the following steps: - -1. Initialization of the execution context. -2. Calling `DetermineContractAddress` hook to determine an address for the contract, -3. Starting a new storage transaction. -4. `transfer`-ing funds between self and the newly instantiated contract. -5. Executing the constructor code. This will yield the final code of the code. -6. Storing the code for the newly instantiated contract in the overlay. -7. Committing or rolling back the storage transaction. - -**Note** that the complexity of executing the constructor code should be considered separately. - -**Note** that the complexity of `DetermineContractAddress` hook should be considered separately as well. Most likely it will use some kind of hashing over the code of the constructor and input data. The default `SimpleAddressDeterminer` does precisely that. - -**Note** that the constructor returns code in the owned form and it's obtained via return facilities, which should have take fee for the return value. - -Also, `transfer` can make up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has. - -Storing the code in the overlay may induce another DB write (if flushed to the storage) with the size proportional to the size of the constructor code. - -Finally, the current storage transaction is closed.. The complexity of this depends on the number of changes performed by the constructor code. Thus, the pricing of storage modification should account for that. - -**complexity**: Up to 2 DB reads and induces up to 3 DB writes (if flushed to the storage), one of which is dependent on the size of the code. Additionally, if the source account removal takes place a DB write will be performed per one storage entry that the account has. - -## Contracts API - -Each API function invoked from a contract can involve some overhead. - -## Getter functions - -Those are simple getter functions which copy a requested value to contract memory. They -all have the following two arguments: - -- `output_ptr`: Pointer into contract memory where to copy the value. -- `output_len_ptr`: Pointer into contract memory where the size of the buffer is stored. The size of the copied value is also stored there. - -**complexity**: The size of the returned value is constant for a given runtime. Therefore we -consider its complexity constant even though some of them might involve at most one DB read. Some of those -functions call into other pallets of the runtime. The assumption here is that those functions are also -linear in regard to the size of the data that is returned and therefore considered constant for a -given runtime. - -This is the list of getters: - -- seal_caller -- seal_address -- seal_weight_to_fee -- seal_gas_left -- seal_balance -- seal_value_transferred -- seal_now -- seal_minimum_balance -- seal_tombstone_deposit -- seal_rent_allowance -- seal_block_number - -### seal_set_storage - -This function receives a `key` and `value` as arguments. It consists of the following steps: - -1. Reading the sandbox memory for `key` and `value` (see sandboxing memory get). -2. Setting the storage at the given `key` to the given `value` (see `set_storage`). - -**complexity**: Complexity is proportional to the size of the `value`. This function induces a DB write of size proportional to the `value` size (if flushed to the storage), so should be priced accordingly. - -### seal_clear_storage - -This function receives a `key` as argument. It consists of the following steps: - -1. Reading the sandbox memory for `key` (see sandboxing memory get). -2. Clearing the storage at the given `key` (see `set_storage`). - -**complexity**: Complexity is constant. This function induces a DB write to clear the storage entry -(upon being flushed to the storage) and should be priced accordingly. - -### seal_get_storage - -This function receives a `key` as an argument. It consists of the following steps: - -1. Reading the sandbox memory for `key` (see sandboxing memory get). -2. Reading the storage with the given key (see `get_storage`). It receives back the owned result buffer. -3. Writing the storage value to contract memory. - -Key is of a constant size. Therefore, the sandbox memory load can be considered to be of constant complexity. - -Unless the value is cached, a DB read will be performed. The size of the value is not known until the read is -performed. Moreover, the DB read has to be synchronous and no progress can be made until the value is fetched. - -**complexity**: The memory and computing complexity is proportional to the size of the fetched value. This function performs a DB read. - -### seal_transfer - -This function receives the following arguments: - -- `account` buffer of a marshaled `AccountId`, -- `value` buffer of a marshaled `Balance`, - -It consists of the following steps: - -1. Loading `account` buffer from the sandbox memory (see sandboxing memory get) and then decoding it. -2. Loading `value` buffer from the sandbox memory and then decoding it. -3. Invoking the executive function `transfer`. - -Loading of `account` and `value` buffers should be charged. This is because the sizes of buffers are specified by the calling code, even though marshaled representations are, essentially, of constant size. This can be fixed by assigning an upper bound for sizes of `AccountId` and `Balance`. - -### seal_call - -This function receives the following arguments: - -- `callee` buffer of a marshaled `AccountId`, -- `gas` limit which is plain u64, -- `value` buffer of a marshaled `Balance`, -- `input_data` an arbitrarily sized byte vector. -- `output_ptr` pointer to contract memory. - -It consists of the following steps: - -1. Loading `callee` buffer from the sandbox memory (see sandboxing memory get) and then decoding it. -2. Loading `value` buffer from the sandbox memory and then decoding it. -3. Loading `input_data` buffer from the sandbox memory. -4. Invoking the executive function `call`. -5. Writing output buffer to contract memory. - -Loading of `callee` and `value` buffers should be charged. This is because the sizes of buffers are specified by the calling code, even though marshaled representations are, essentially, of constant size. This can be fixed by assigning an upper bound for sizes of `AccountId` and `Balance`. - -Loading `input_data` should be charged in any case. - -**complexity**: All complexity comes from loading and writing buffers and executing `call` executive function. The former component is proportional to the sizes of `callee`, `value`, `input_data` and `output_ptr` buffers. The latter component completely depends on the complexity of `call` executive function, and also dominated by it. - -### seal_instantiate - -This function receives the following arguments: - -- `init_code`, a buffer which contains the code of the constructor. -- `gas` limit which is plain u64 -- `value` buffer of a marshaled `Balance` -- `input_data`. an arbitrarily sized byte vector. - -It consists of the following steps: - -1. Loading `init_code` buffer from the sandbox memory (see sandboxing memory get) and then decoding it. -2. Loading `value` buffer from the sandbox memory and then decoding it. -3. Loading `input_data` buffer from the sandbox memory. -4. Invoking `instantiate` executive function. - -Loading of `value` buffer should be charged. This is because the size of the buffer is specified by the calling code, even though marshaled representation is, essentially, of constant size. This can be fixed by assigning an upper bound for size for `Balance`. - -Loading `init_code` and `input_data` should be charged in any case. - -**complexity**: All complexity comes from loading buffers and executing `instantiate` executive function. The former component is proportional to the sizes of `init_code`, `value` and `input_data` buffers. The latter component completely depends on the complexity of `instantiate` executive function and also dominated by it. - -### seal_terminate - -This function receives the following arguments: - -- `beneficiary`, buffer of a marshaled `AccountId` - -It consists of the following steps: - -1. Loading `beneficiary` buffer from the sandbox memory (see sandboxing memory get) and then decoding it. - -Loading of the `beneficiary` buffer should be charged. This is because the sizes of buffers are specified by the calling code, even though marshaled representations are, essentially, of constant size. This can be fixed by assigning an upper bound for sizes of `AccountId`. - -**complexity**: All complexity comes from loading buffers and executing `terminate` executive function. The former component is proportional to the size of the `beneficiary` buffer. The latter component completely depends on the complexity of `terminate` executive function and also dominated by it. - -### seal_input - -This function receives a pointer to contract memory. It copies the input to the contract call to this location. - -**complexity**: The complextity is proportional to the size of the input buffer. - -### seal_return - -This function receives a `data` buffer and `flags` arguments. Execution of the function consists of the following steps: - -1. Loading `data` buffer from the sandbox memory (see sandboxing memory get). -2. Storing the `u32` flags value. -3. Trapping - -**complexity**: The complexity of this function is proportional to the size of the `data` buffer. - -### seal_deposit_event - -This function receives a `data` buffer as an argument. Execution of the function consists of the following steps: - -1. Loading `data` buffer from the sandbox memory (see sandboxing memory get), -2. Insert to nested context execution -3. Copies from nested to underlying contexts -4. Call system deposit event - -**complexity**: The complexity of this function is proportional to the size of the `data` buffer. - -### seal_set_rent_allowance - -This function receives the following argument: - -- `value` buffer of a marshaled `Balance`, - -It consists of the following steps: - -1. Loading `value` buffer from the sandbox memory and then decoding it. -2. Invoking `set_rent_allowance` AccountDB function. - -**complexity**: Complexity is proportional to the size of the `value`. This function induces a DB write of size proportional to the `value` size (if flushed to the storage), so should be priced accordingly. - -## Built-in hashing functions - -This paragraph concerns the following supported built-in hash functions: - -- `SHA2` with 256-bit width -- `KECCAK` with 256-bit width -- `BLAKE2` with 128-bit and 256-bit widths - -These functions compute a cryptographic hash on the given inputs and copy the -resulting hash directly back into the sandboxed Wasm contract output buffer. - -Execution of the function consists of the following steps: - -1. Load data stored in the input buffer into an intermediate buffer. -2. Compute the cryptographic hash `H` on the intermediate buffer. -3. Copy back the bytes of `H` into the contract side output buffer. - -**complexity**: Complexity is proportional to the size of the input buffer in bytes -as well as to the size of the output buffer in bytes. Also different cryptographic -algorithms have different inherent complexity so users must expect the above -mentioned crypto hashes to have varying gas costs. -The complexity of each cryptographic hash function highly depends on the underlying -implementation. - -### seal_ecdsa_recover - -This function receives the following arguments: - -- `signature` is 65 bytes buffer, -- `message_hash` is 32 bytes buffer, -- `output` is 33 bytes buffer to return compressed public key, - -It consists of the following steps: - -1. Loading `signature` buffer from the sandbox memory (see sandboxing memory get). -2. Loading `message_hash` buffer from the sandbox memory. -3. Invoking the executive function `secp256k1_ecdsa_recover_compressed`. -4. Copy the bytes of compressed public key into the contract side output buffer. - -**complexity**: Complexity is partially constant(it doesn't depend on input) but still depends on points of ECDSA and calculation. \ No newline at end of file diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index 80dc0b05e7511..8e6368490f6d7 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-contracts" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for WASM contracts" readme = "README.md" @@ -14,40 +14,42 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] bitflags = "1.3" -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", "max-encoded-len", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } log = { version = "0.4", default-features = false } -pwasm-utils = { version = "0.18.2", default-features = false } +wasm-instrument = { version = "0.4", default-features = false } serde = { version = "1", optional = true, features = ["derive"] } smallvec = { version = "1", default-features = false, features = [ "const_generics", ] } -wasmi-validation = { version = "0.4", default-features = false } +wasmi = { version = "0.20", default-features = false } +wasmparser = { package = "wasmparser-nostd", version = "0.91", default-features = false } +impl-trait-for-tuples = "0.2" # Only used in benchmarking to generate random contract code -libsecp256k1 = { version = "0.3.5", optional = true, default-features = false, features = ["hmac"] } -rand = { version = "0.7.3", optional = true, default-features = false } -rand_pcg = { version = "0.2", optional = true } +rand = { version = "0.8", optional = true, default-features = false } +rand_pcg = { version = "0.3", optional = true } # Substrate Dependencies frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -pallet-contracts-primitives = { version = "4.0.0-dev", default-features = false, path = "common" } +pallet-contracts-primitives = { version = "7.0.0", default-features = false, path = "primitives" } pallet-contracts-proc-macro = { version = "4.0.0-dev", path = "proc-macro" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../primitives/sandbox" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] +array-bytes = "4.1" assert_matches = "1" -hex-literal = "0.3" -pretty_assertions = "0.7" +env_logger = "0.9" +pretty_assertions = "1" wat = "1" # Substrate Dependencies @@ -55,6 +57,7 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } pallet-randomness-collective-flip = { version = "4.0.0-dev", path = "../randomness-collective-flip" } pallet-utility = { version = "4.0.0-dev", path = "../utility" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } [features] default = ["std"] @@ -66,26 +69,20 @@ std = [ "sp-runtime/std", "sp-io/std", "sp-std/std", - "sp-sandbox/std", - "frame-benchmarking/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "pwasm-utils/std", - "wasmi-validation/std", + "wasm-instrument/std", + "wasmi/std", "pallet-contracts-primitives/std", "pallet-contracts-proc-macro/full", "log/std", "rand/std", - "libsecp256k1/std", + "wasmparser/std", ] runtime-benchmarks = [ - "frame-benchmarking", - "libsecp256k1", + "frame-benchmarking/runtime-benchmarks", "rand", "rand_pcg", - "unstable-interface", ] try-runtime = ["frame-support/try-runtime"] -# Make contract callable functions marked as __unstable__ available. Do not enable -# on live chains as those are subject to change. -unstable-interface = [] diff --git a/frame/contracts/README.md b/frame/contracts/README.md index f3a8d13f6e77d..4df7d9449682d 100644 --- a/frame/contracts/README.md +++ b/frame/contracts/README.md @@ -2,10 +2,10 @@ The Contract module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts. -- [`Call`](https://docs.rs/pallet-contracts/latest/pallet_contracts/enum.Call.html) -- [`Config`](https://docs.rs/pallet-contracts/latest/pallet_contracts/trait.Config.html) -- [`Error`](https://docs.rs/pallet-contracts/latest/pallet_contracts/enum.Error.html) -- [`Event`](https://docs.rs/pallet-contracts/latest/pallet_contracts/enum.Event.html) +- [`Call`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Call.html) +- [`Config`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/trait.Config.html) +- [`Error`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Error.html) +- [`Event`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Error.html) ## Overview @@ -34,20 +34,73 @@ reverted at the current call's contract level. For example, if contract A calls then all of B's calls are reverted. Assuming correct error handling by contract A, A's other calls and state changes still persist. -One gas is equivalent to one [weight](https://substrate.dev/docs/en/knowledgebase/learn-substrate/weight) +One gas is equivalent to one [weight](https://docs.substrate.io/v3/runtime/weights-and-fees) which is defined as one picosecond of execution time on the runtime's reference machine. -### Notable Scenarios +### Revert Behaviour -Contract call failures are not always cascading. When failures occur in a sub-call, they do not "bubble up", +Contract call failures are not cascading. When failures occur in a sub-call, they do not "bubble up", and the call will only revert at the specific contract level. For example, if contract A calls contract B, and B fails, A can decide how to handle that failure, either proceeding or reverting A's changes. +### Offchain Execution + +In general, a contract execution needs to be deterministic so that all nodes come to the same +conclusion when executing it. To that end we disallow any instructions that could cause +indeterminism. Most notable are any floating point arithmetic. That said, sometimes contracts +are executed off-chain and hence are not subject to consensus. If code is only executed by a +single node and implicitly trusted by other actors is such a case. Trusted execution environments +come to mind. To that end we allow the execution of indeterminstic code for offchain usages +with the following constraints: + +1. No contract can ever be instantiated from an indeterministic code. The only way to execute +the code is to use a delegate call from a deterministic contract. +2. The code that wants to use this feature needs to depend on `pallet-contracts` and use `bare_call` +directly. This makes sure that by default `pallet-contracts` does not expose any indeterminism. + +## How to use + +When setting up the `Schedule` for your runtime make sure to set `InstructionWeights::fallback` +to a non zero value. The default is `0` and prevents the upload of any non deterministic code. + +An indeterministic code can be deployed on-chain by passing `Determinism::AllowIndeterministic` +to `upload_code`. A determinstic contract can then delegate call into it if and only if it +is ran by using `bare_call` and passing `Determinism::AllowIndeterministic` to it. **Never use +this argument when the contract is called from an on-chain transaction.** + ## Interface ### Dispatchable functions -Those are documented in the [reference documentation](https://docs.rs/pallet-contracts/latest/pallet_contracts/#dispatchable-functions). +Those are documented in the [reference documentation](https://paritytech.github.io/substrate/master/pallet_contracts/index.html#dispatchable-functions). + +### Interface exposed to contracts + +Each contract is one WebAssembly module that looks like this: + +```wat +(module + ;; Invoked by pallet-contracts when a contract is instantiated. + ;; No arguments and empty return type. + (func (export "deploy")) + + ;; Invoked by pallet-contracts when a contract is called. + ;; No arguments and empty return type. + (func (export "call")) + + ;; If a contract uses memory it must be imported. Memory is optional. + ;; The maximum allowed memory size depends on the pallet-contracts configuration. + (import "env" "memory" (memory 1 1)) + + ;; This is one of many functions that can be imported and is implemented by pallet-contracts. + ;; This function is used to copy the result buffer and flags back to the caller. + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) +) +``` + +The documentation of all importable functions can be found +[here](https://github.com/paritytech/substrate/blob/master/frame/contracts/src/wasm/runtime.rs). +Look for the `define_env!` macro invocation. ## Usage @@ -61,7 +114,7 @@ writing WebAssembly based smart contracts in the Rust programming language. Contracts can emit messages to the client when called as RPC through the `seal_debug_message` API. This is exposed in ink! via -[`ink_env::debug_println()`](https://docs.rs/ink_env/latest/ink_env/fn.debug_println.html). +[`ink_env::debug_message()`](https://paritytech.github.io/ink/ink_env/fn.debug_message.html). Those messages are gathered into an internal buffer and send to the RPC client. It is up the the individual client if and how those messages are presented to the user. @@ -73,7 +126,7 @@ by block production. A good starting point for observing them on the console is command line in the root directory of the substrate repository: ```bash -cargo run --release -- --dev --tmp -lerror,runtime::contracts=debug +cargo run --release -- --dev -lerror,runtime::contracts=debug ``` This raises the log level of `runtime::contracts` to `debug` and all other targets @@ -89,18 +142,11 @@ this pallet contains the concept of an unstable interface. Akin to the rust nigh it allows us to add new interfaces but mark them as unstable so that contract languages can experiment with them and give feedback before we stabilize those. -In order to access interfaces marked as `__unstable__` in `runtime.rs` one need to compile -this crate with the `unstable-interface` feature enabled. It should be obvious that any -live runtime should never be compiled with this feature: In addition to be subject to -change or removal those interfaces do not have proper weights associated with them and -are therefore considered unsafe. - -The substrate runtime exposes this feature as `contracts-unstable-interface`. Example -commandline for running the substrate node with unstable contracts interfaces: - -```bash -cargo run --release --features contracts-unstable-interface -- --dev -``` +In order to access interfaces marked as `#[unstable]` in `runtime.rs` one need to set +`pallet_contracts::Config::UnsafeUnstableInterface` to `ConstU32`. It should be obvious +that any production runtime should never be compiled with this feature: In addition to be +subject to change or removal those interfaces might not have proper weights associated with +them and are therefore considered unsafe. New interfaces are generally added as unstable and might go through several iterations before they are promoted to a stable interface. diff --git a/frame/contracts/common/Cargo.toml b/frame/contracts/common/Cargo.toml deleted file mode 100644 index b441d88453ae2..0000000000000 --- a/frame/contracts/common/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "pallet-contracts-primitives" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "A crate that hosts a common definitions that are relevant for the pallet-contracts." -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -bitflags = "1.0" -codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -serde = { version = "1", features = ["derive"], optional = true } - -# Substrate Dependencies (This crate should not rely on frame) -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core", default-features = false } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } - -[features] -default = ["std"] -std = [ - "codec/std", - "scale-info/std", - "sp-core/std", - "sp-runtime/std", - "sp-std/std", - "serde", -] diff --git a/frame/contracts/common/src/lib.rs b/frame/contracts/common/src/lib.rs deleted file mode 100644 index c57f728c26b68..0000000000000 --- a/frame/contracts/common/src/lib.rs +++ /dev/null @@ -1,151 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 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. - -//! A crate that hosts a common definitions that are relevant for the pallet-contracts. - -#![cfg_attr(not(feature = "std"), no_std)] - -use bitflags::bitflags; -use codec::{Decode, Encode}; -use sp_core::Bytes; -use sp_runtime::{DispatchError, RuntimeDebug}; -use sp_std::prelude::*; - -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; - -/// Result type of a `bare_call` or `bare_instantiate` call. -/// -/// It contains the execution result together with some auxiliary information. -#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -pub struct ContractResult { - /// How much gas was consumed during execution. - pub gas_consumed: u64, - /// How much gas is required as gas limit in order to execute this call. - /// - /// This value should be used to determine the gas limit for on-chain execution. - /// - /// # Note - /// - /// This can only different from [`Self::gas_consumed`] when weight pre charging - /// is used. Currently, only `seal_call_runtime` makes use of pre charging. - pub gas_required: u64, - /// An optional debug message. This message is only filled when explicitly requested - /// by the code that calls into the contract. Otherwise it is empty. - /// - /// The contained bytes are valid UTF-8. This is not declared as `String` because - /// this type is not allowed within the runtime. - /// - /// Clients should not make any assumptions about the format of the buffer. - /// They should just display it as-is. It is **not** only a collection of log lines - /// provided by a contract but a formatted buffer with different sections. - /// - /// # Note - /// - /// The debug message is never generated during on-chain execution. It is reserved for - /// RPC calls. - #[cfg_attr(feature = "std", serde(with = "as_string"))] - pub debug_message: Vec, - /// The execution result of the wasm code. - pub result: T, -} - -/// Result type of a `bare_call` call. -pub type ContractExecResult = ContractResult>; - -/// Result type of a `bare_instantiate` call. -pub type ContractInstantiateResult = - ContractResult, DispatchError>>; - -/// Result type of a `get_storage` call. -pub type GetStorageResult = Result>, ContractAccessError>; - -/// The possible errors that can happen querying the storage of a contract. -#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] -pub enum ContractAccessError { - /// The given address doesn't point to a contract. - DoesntExist, -} - -bitflags! { - /// Flags used by a contract to customize exit behaviour. - #[derive(Encode, Decode)] - #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "std", serde(rename_all = "camelCase", transparent))] - pub struct ReturnFlags: u32 { - /// If this bit is set all changes made by the contract execution are rolled back. - const REVERT = 0x0000_0001; - } -} - -/// Output of a contract call or instantiation which ran to completion. -#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -pub struct ExecReturnValue { - /// Flags passed along by `seal_return`. Empty when `seal_return` was never called. - pub flags: ReturnFlags, - /// Buffer passed along by `seal_return`. Empty when `seal_return` was never called. - pub data: Bytes, -} - -impl ExecReturnValue { - /// We understand the absense of a revert flag as success. - pub fn is_success(&self) -> bool { - !self.flags.contains(ReturnFlags::REVERT) - } -} - -/// The result of a successful contract instantiation. -#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -pub struct InstantiateReturnValue { - /// The output of the called constructor. - pub result: ExecReturnValue, - /// The account id of the new contract. - pub account_id: AccountId, -} - -/// Reference to an existing code hash or a new wasm module. -#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -pub enum Code { - /// A wasm module as raw bytes. - Upload(Bytes), - /// The code hash of an on-chain wasm blob. - Existing(Hash), -} - -#[cfg(feature = "std")] -mod as_string { - use super::*; - use serde::{ser::Error, Deserializer, Serializer}; - - pub fn serialize(bytes: &Vec, serializer: S) -> Result { - std::str::from_utf8(bytes) - .map_err(|e| S::Error::custom(format!("Debug buffer contains invalid UTF8: {}", e)))? - .serialize(serializer) - } - - pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { - Ok(String::deserialize(deserializer)?.into_bytes()) - } -} diff --git a/frame/contracts/fixtures/account_reentrance_count_call.wat b/frame/contracts/fixtures/account_reentrance_count_call.wat new file mode 100644 index 0000000000000..ab67890664870 --- /dev/null +++ b/frame/contracts/fixtures/account_reentrance_count_call.wat @@ -0,0 +1,37 @@ +;; This fixture tests if account_reentrance_count works as expected +;; testing it with 2 different addresses +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_caller" (func $seal_caller (param i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "seal0" "account_reentrance_count" (func $account_reentrance_count (param i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 32) buffer where input is copied + ;; [32, 36) size of the input buffer + (data (i32.const 32) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; Reading "callee" input address + (call $seal_input (i32.const 0) (i32.const 32)) + + (i32.store + (i32.const 36) + (call $account_reentrance_count (i32.const 0)) + ) + + (call $seal_return (i32.const 0) (i32.const 36) (i32.const 4)) + ) + + (func (export "deploy")) + +) \ No newline at end of file diff --git a/frame/contracts/fixtures/call_runtime.wat b/frame/contracts/fixtures/call_runtime.wat index d5467f6e95e3e..d3d08ee24541a 100644 --- a/frame/contracts/fixtures/call_runtime.wat +++ b/frame/contracts/fixtures/call_runtime.wat @@ -1,6 +1,6 @@ ;; This passes its input to `seal_call_runtime` and returns the return value to its caller. (module - (import "__unstable__" "seal_call_runtime" (func $seal_call_runtime (param i32 i32) (result i32))) + (import "seal0" "call_runtime" (func $call_runtime (param i32 i32) (result i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) @@ -17,7 +17,7 @@ ) ;; Just use the call passed as input and store result to memory (i32.store (i32.const 0) - (call $seal_call_runtime + (call $call_runtime (i32.const 4) ;; Pointer where the call is stored (i32.load (i32.const 0)) ;; Size of the call ) diff --git a/frame/contracts/fixtures/chain_extension.wat b/frame/contracts/fixtures/chain_extension.wat index db7e83fd96b42..7cc7335052e90 100644 --- a/frame/contracts/fixtures/chain_extension.wat +++ b/frame/contracts/fixtures/chain_extension.wat @@ -15,12 +15,12 @@ ) ;; [0, 4) len of input output - (data (i32.const 0) "\02") + (data (i32.const 0) "\08") ;; [4, 12) buffer for input - ;; [12, 16) len of output buffer - (data (i32.const 12) "\02") + ;; [12, 48) len of output buffer + (data (i32.const 12) "\20") ;; [16, inf) buffer for output @@ -31,15 +31,15 @@ ;; the chain extension passes through the input and returns it as output (call $seal_call_chain_extension - (i32.load8_u (i32.const 4)) ;; func_id + (i32.load (i32.const 4)) ;; id (i32.const 4) ;; input_ptr (i32.load (i32.const 0)) ;; input_len (i32.const 16) ;; output_ptr (i32.const 12) ;; output_len_ptr ) - ;; the chain extension passes through the func_id - (call $assert (i32.eq (i32.load8_u (i32.const 4)))) + ;; the chain extension passes through the id + (call $assert (i32.eq (i32.load (i32.const 4)))) (call $seal_return (i32.const 0) (i32.const 16) (i32.load (i32.const 12))) ) diff --git a/frame/contracts/fixtures/chain_extension_temp_storage.wat b/frame/contracts/fixtures/chain_extension_temp_storage.wat new file mode 100644 index 0000000000000..b481abb5bc7c9 --- /dev/null +++ b/frame/contracts/fixtures/chain_extension_temp_storage.wat @@ -0,0 +1,85 @@ +;; Call chain extension two times with the specified func_ids +;; It then calls itself once +(module + (import "seal0" "seal_call_chain_extension" + (func $seal_call_chain_extension (param i32 i32 i32 i32 i32) (result i32)) + ) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_address" (func $seal_address (param i32 i32))) + (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 16 16)) + + (func $assert (param i32) + (block $ok + (br_if $ok (get_local 0)) + (unreachable) + ) + ) + + ;; [0, 4) len of input buffer: 8 byte (func_ids) + 1byte (stop_recurse) + (data (i32.const 0) "\09") + + ;; [4, 16) buffer for input + + ;; [16, 48] buffer for self address + + ;; [48, 52] len of self address buffer + (data (i32.const 48) "\20") + + (func (export "deploy")) + + (func (export "call") + ;; input: (func_id1: i32, func_id2: i32, stop_recurse: i8) + (call $seal_input (i32.const 4) (i32.const 0)) + + (call $seal_call_chain_extension + (i32.load (i32.const 4)) ;; id + (i32.const 0) ;; input_ptr + (i32.const 0) ;; input_len + (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; output_len_ptr + ) + drop + + (call $seal_call_chain_extension + (i32.load (i32.const 8)) ;; _id + (i32.const 0) ;; input_ptr + (i32.const 0) ;; input_len + (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; output_len_ptr + ) + drop + + (if (i32.eqz (i32.load8_u (i32.const 12))) + (then + ;; stop recursion + (i32.store8 (i32.const 12) (i32.const 1)) + + ;; load own address into buffer + (call $seal_address (i32.const 16) (i32.const 48)) + + ;; call function 2 + 3 of chainext 3 next time + ;; (3 << 16) | 2 + ;; (3 << 16) | 3 + (i32.store (i32.const 4) (i32.const 196610)) + (i32.store (i32.const 8) (i32.const 196611)) + + ;; call self + (call $seal_call + (i32.const 8) ;; Set ALLOW_REENTRY + (i32.const 16) ;; Pointer to "callee" address. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 512) ;; Pointer to the buffer with value to transfer + (i32.const 4) ;; Pointer to input data buffer address + (i32.load (i32.const 0)) ;; Length of input data buffer + (i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + + ;; check that call succeeded of call + (call $assert (i32.eqz)) + ) + (else) + ) + ) +) diff --git a/frame/contracts/fixtures/create_storage_and_call.wat b/frame/contracts/fixtures/create_storage_and_call.wat new file mode 100644 index 0000000000000..2a1e53f7ce08a --- /dev/null +++ b/frame/contracts/fixtures/create_storage_and_call.wat @@ -0,0 +1,55 @@ +;; This calls another contract as passed as its account id. It also creates some storage. +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy")) + + (func (export "call") + ;; store length of input buffer + (i32.store (i32.const 0) (i32.const 512)) + + ;; copy input at address 4 + (call $seal_input (i32.const 4) (i32.const 0)) + + ;; create 4 byte of storage before calling + (call $seal_set_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 0) ;; Pointer to value + (i32.const 4) ;; Size of value + ) + + ;; call passed contract + (call $assert (i32.eqz + (call $seal_call + (i32.const 0) ;; No flags + (i32.const 8) ;; Pointer to "callee" address. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 512) ;; Pointer to the buffer with value to transfer + (i32.const 4) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + (i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + )) + + ;; create 8 byte of storage after calling + ;; item of 12 bytes because we override 4 bytes + (call $seal_set_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 0) ;; Pointer to value + (i32.const 12) ;; Size of value + ) + ) +) diff --git a/frame/contracts/fixtures/delegate_call.wat b/frame/contracts/fixtures/delegate_call.wat new file mode 100644 index 0000000000000..7fe422af45511 --- /dev/null +++ b/frame/contracts/fixtures/delegate_call.wat @@ -0,0 +1,111 @@ +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32))) + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 3 3)) + + ;; [0, 32) storage key + (data (i32.const 0) "\01") + + ;; [32, 64) storage key + (data (i32.const 32) "\02") + + ;; [64, 96) buffer where input is copied + + ;; [96, 100) size of the input buffer + (data (i32.const 96) "\20") + + ;; [100, 104) size of buffer for seal_get_storage + (data (i32.const 100) "\20") + + ;; [104, 136) seal_get_storage buffer + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + (local $exit_code i32) + + ;; Reading "callee" code_hash + (call $seal_input (i32.const 64) (i32.const 96)) + + ;; assert input size == 32 + (call $assert + (i32.eq + (i32.load (i32.const 96)) + (i32.const 32) + ) + ) + + ;; place a value in storage, the size of which is specified by the call input. + (call $seal_set_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 32) ;; Pointer to initial value + (i32.load (i32.const 100)) ;; Size of value + ) + + (call $assert + (i32.eq + (call $seal_get_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 104) ;; buffer where to copy result + (i32.const 100) ;; pointer to size of buffer + ) + (i32.const 0) ;; ReturnCode::Success + ) + ) + + (call $assert + (i32.eq + (i32.load (i32.const 104)) ;; value received from storage + (i32.load (i32.const 32)) ;; initial value + ) + ) + + ;; Call deployed library contract code. + (set_local $exit_code + (call $seal_delegate_call + (i32.const 0) ;; Set no call flags + (i32.const 64) ;; Pointer to "callee" code_hash. + (i32.const 0) ;; Input is ignored + (i32.const 0) ;; Length of the input + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + ) + + ;; Check for success exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0)) ;; ReturnCode::Success + ) + + (call $assert + (i32.eq + (call $seal_get_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 104) ;; buffer where to copy result + (i32.const 100) ;; pointer to size of buffer + ) + (i32.const 0) ;; ReturnCode::Success + ) + ) + + ;; Make sure that 'callee' code changed the value + (call $assert + (i32.eq + (i32.load (i32.const 104)) + (i32.const 1) + ) + ) + ) + + (func (export "deploy")) + +) diff --git a/frame/contracts/fixtures/delegate_call_lib.wat b/frame/contracts/fixtures/delegate_call_lib.wat new file mode 100644 index 0000000000000..340b9699f8755 --- /dev/null +++ b/frame/contracts/fixtures/delegate_call_lib.wat @@ -0,0 +1,79 @@ +(module + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal0" "seal_caller" (func $seal_caller (param i32 i32))) + (import "seal0" "seal_value_transferred" (func $seal_value_transferred (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 32) storage key + (data (i32.const 0) "\01") + + ;; [32, 64) buffer for transferred value + + ;; [64, 96) size of the buffer for transferred value + (data (i32.const 64) "\20") + + ;; [96, 128) buffer for the caller + + ;; [128, 160) size of the buffer for caller + (data (i32.const 128) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; place a value in storage + (call $seal_set_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 0) ;; Pointer to value + (i32.const 32) ;; Size of value + ) + + ;; This stores the value transferred in the buffer + (call $seal_value_transferred (i32.const 32) (i32.const 64)) + + ;; assert len == 8 + (call $assert + (i32.eq + (i32.load (i32.const 64)) + (i32.const 8) + ) + ) + + ;; assert that contents of the buffer is equal to the value + ;; passed to the `caller` contract: 1337 + (call $assert + (i64.eq + (i64.load (i32.const 32)) + (i64.const 1337) + ) + ) + + ;; fill the buffer with the caller. + (call $seal_caller (i32.const 96) (i32.const 128)) + + ;; assert len == 32 + (call $assert + (i32.eq + (i32.load (i32.const 128)) + (i32.const 32) + ) + ) + + ;; assert that the first 64 byte are the beginning of "ALICE", + ;; who is the caller of the `caller` contract + (call $assert + (i64.eq + (i64.load (i32.const 96)) + (i64.const 0x0101010101010101) + ) + ) + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/delegate_call_simple.wat b/frame/contracts/fixtures/delegate_call_simple.wat new file mode 100644 index 0000000000000..24ae5a13e33e5 --- /dev/null +++ b/frame/contracts/fixtures/delegate_call_simple.wat @@ -0,0 +1,50 @@ +;; Just delegate call into the passed code hash and assert success. +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 3 3)) + + ;; [0, 32) buffer where input is copied + + ;; [32, 36) size of the input buffer + (data (i32.const 32) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; Reading "callee" code_hash + (call $seal_input (i32.const 0) (i32.const 32)) + + ;; assert input size == 32 + (call $assert + (i32.eq + (i32.load (i32.const 32)) + (i32.const 32) + ) + ) + + ;; Delegate call into passed code hash + (call $assert + (i32.eq + (call $seal_delegate_call + (i32.const 0) ;; Set no call flags + (i32.const 0) ;; Pointer to "callee" code_hash. + (i32.const 0) ;; Input is ignored + (i32.const 0) ;; Length of the input + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + (i32.const 0) + ) + ) + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/destroy_and_transfer.wat b/frame/contracts/fixtures/destroy_and_transfer.wat index aa13cd8b81072..2555479555272 100644 --- a/frame/contracts/fixtures/destroy_and_transfer.wat +++ b/frame/contracts/fixtures/destroy_and_transfer.wat @@ -9,7 +9,7 @@ )) (import "env" "memory" (memory 1 1)) - ;; [0, 8) Endowment to send when creating contract. + ;; [0, 8) value to send when creating contract. (data (i32.const 0) "\00\00\01") ;; [8, 16) Value to send when calling contract. diff --git a/frame/contracts/fixtures/drain.wat b/frame/contracts/fixtures/drain.wat index 546026ac95986..94c6518422667 100644 --- a/frame/contracts/fixtures/drain.wat +++ b/frame/contracts/fixtures/drain.wat @@ -33,7 +33,9 @@ ) ) - ;; Self-destruct by sending full balance to the 0 address. + ;; Try to self-destruct by sending full balance to the 0 address. + ;; All the *free* balance will be send away, which is a valid thing to do + ;; because the storage deposits will keep the account alive. (call $assert (i32.eq (call $seal_transfer @@ -42,7 +44,7 @@ (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Length of the buffer with value to transfer ) - (i32.const 4) ;; ReturnCode::BelowSubsistenceThreshold + (i32.const 0) ;; ReturnCode::Success ) ) ) diff --git a/frame/contracts/fixtures/ecdsa_recover.wat b/frame/contracts/fixtures/ecdsa_recover.wat index c196e88094d2c..d694b3215e86b 100644 --- a/frame/contracts/fixtures/ecdsa_recover.wat +++ b/frame/contracts/fixtures/ecdsa_recover.wat @@ -4,7 +4,7 @@ ;; 3) Validates that result is Success ;; 4) Returns recovered compressed public key (module - (import "__unstable__" "seal_ecdsa_recover" (func $seal_ecdsa_recover (param i32 i32 i32) (result i32))) + (import "seal0" "seal_ecdsa_recover" (func $seal_ecdsa_recover (param i32 i32 i32) (result i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "env" "memory" (memory 1 1)) diff --git a/frame/contracts/fixtures/return_from_start_fn.wat b/frame/contracts/fixtures/event_and_return_on_deploy.wat similarity index 91% rename from frame/contracts/fixtures/return_from_start_fn.wat rename to frame/contracts/fixtures/event_and_return_on_deploy.wat index 854b552a828c2..809cfe13545a6 100644 --- a/frame/contracts/fixtures/return_from_start_fn.wat +++ b/frame/contracts/fixtures/event_and_return_on_deploy.wat @@ -3,8 +3,7 @@ (import "seal0" "seal_deposit_event" (func $seal_deposit_event (param i32 i32 i32 i32))) (import "env" "memory" (memory 1 1)) - (start $start) - (func $start + (func (export "deploy") (call $seal_deposit_event (i32.const 0) ;; The topics buffer (i32.const 0) ;; The topics buffer's length @@ -22,7 +21,6 @@ (func (export "call") (unreachable) ) - (func (export "deploy")) (data (i32.const 8) "\01\02\03\04") ) diff --git a/frame/contracts/fixtures/float_instruction.wat b/frame/contracts/fixtures/float_instruction.wat new file mode 100644 index 0000000000000..c19b5c12cdcec --- /dev/null +++ b/frame/contracts/fixtures/float_instruction.wat @@ -0,0 +1,11 @@ +;; Module that contains a float instruction which is illegal in deterministic mode +(module + (func (export "call") + f32.const 1 + drop + ) + (func (export "deploy") + f32.const 2 + drop + ) +) diff --git a/frame/contracts/fixtures/invalid_contract.wat b/frame/contracts/fixtures/invalid_contract.wat new file mode 100644 index 0000000000000..085569000c559 --- /dev/null +++ b/frame/contracts/fixtures/invalid_contract.wat @@ -0,0 +1,4 @@ +;; Valid module but missing the call function +(module + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/invalid_module.wat b/frame/contracts/fixtures/invalid_module.wat new file mode 100644 index 0000000000000..e4a72f74273f9 --- /dev/null +++ b/frame/contracts/fixtures/invalid_module.wat @@ -0,0 +1,8 @@ +;; An invalid module +(module + (func (export "deploy")) + (func (export "call") + ;; imbalanced stack + (i32.const 7) + ) +) diff --git a/frame/contracts/fixtures/multi_store.wat b/frame/contracts/fixtures/multi_store.wat new file mode 100644 index 0000000000000..2592baf618355 --- /dev/null +++ b/frame/contracts/fixtures/multi_store.wat @@ -0,0 +1,54 @@ +;; Does two stores to two seperate storage items +;; Expects (len0, len1) as input. +(module + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "env" "memory" (memory 16 16)) + + ;; [0, 32) storage key 0 + (data (i32.const 0) "\01") + + ;; [32, 64) storage key 1 + (data (i32.const 32) "\02") + + ;; [64, 72) buffer where input is copied (expected sizes of storage items) + + ;; [72, 76) size of the input buffer + (data (i32.const 72) "\08") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + (call $seal_input (i32.const 64) (i32.const 72)) + + ;; assert input size == 8 + (call $assert + (i32.eq + (i32.load (i32.const 72)) + (i32.const 8) + ) + ) + + ;; place a values in storage sizes are specified in the input buffer + ;; we don't care about the contents of the storage item + (call $seal_set_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 0) ;; Pointer to value + (i32.load (i32.const 64)) ;; Size of value + ) + (call $seal_set_storage + (i32.const 32) ;; Pointer to storage key + (i32.const 0) ;; Pointer to value + (i32.load (i32.const 68)) ;; Size of value + ) + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/new_set_code_hash_contract.wat b/frame/contracts/fixtures/new_set_code_hash_contract.wat new file mode 100644 index 0000000000000..86ab2737be484 --- /dev/null +++ b/frame/contracts/fixtures/new_set_code_hash_contract.wat @@ -0,0 +1,13 @@ +(module + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 32) return value + (data (i32.const 0) "\02") + + (func (export "deploy")) + + (func (export "call") + (call $seal_return (i32.const 0) (i32.const 0) (i32.const 4)) + ) +) diff --git a/frame/contracts/fixtures/reentrance_count_call.wat b/frame/contracts/fixtures/reentrance_count_call.wat new file mode 100644 index 0000000000000..c6b529e2aff8b --- /dev/null +++ b/frame/contracts/fixtures/reentrance_count_call.wat @@ -0,0 +1,76 @@ +;; This fixture recursively tests if reentrance_count returns correct reentrant count value when +;; using seal_call to make caller contract call to itself +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_address" (func $seal_address (param i32 i32))) + (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal0" "reentrance_count" (func $reentrance_count (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 32) reserved for $seal_address output + + ;; [32, 36) buffer for the call stack height + + ;; [36, 40) size of the input buffer + (data (i32.const 36) "\04") + + ;; [40, 44) length of the buffer for $seal_address + (data (i32.const 40) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (local $expected_reentrance_count i32) + (local $seal_call_exit_code i32) + + ;; reading current contract address + (call $seal_address (i32.const 0) (i32.const 40)) + + ;; reading passed input + (call $seal_input (i32.const 32) (i32.const 36)) + + ;; reading manually passed reentrant count + (set_local $expected_reentrance_count (i32.load (i32.const 32))) + + ;; reentrance count is calculated correctly + (call $assert + (i32.eq (call $reentrance_count) (get_local $expected_reentrance_count)) + ) + + ;; re-enter 5 times in a row and assert that the reentrant counter works as expected + (i32.eq (call $reentrance_count) (i32.const 5)) + (if + (then) ;; recursion exit case + (else + ;; incrementing $expected_reentrance_count passed to the contract + (i32.store (i32.const 32) (i32.add (i32.load (i32.const 32)) (i32.const 1))) + + ;; Call to itself + (set_local $seal_call_exit_code + (call $seal_call + (i32.const 8) ;; Allow reentrancy flag set + (i32.const 0) ;; Pointer to "callee" address + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 32) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Ptr to output buffer len + ) + ) + + (call $assert + (i32.eq (get_local $seal_call_exit_code) (i32.const 0)) + ) + ) + ) + ) + + (func (export "deploy")) +) \ No newline at end of file diff --git a/frame/contracts/fixtures/reentrance_count_delegated_call.wat b/frame/contracts/fixtures/reentrance_count_delegated_call.wat new file mode 100644 index 0000000000000..b8219a8462ee2 --- /dev/null +++ b/frame/contracts/fixtures/reentrance_count_delegated_call.wat @@ -0,0 +1,71 @@ +;; This fixture recursively tests if reentrance_count returns correct reentrant count value when +;; using seal_delegate_call to make caller contract delegate call to itself +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "seal0" "reentrance_count" (func $reentrance_count (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 32) buffer where code hash is copied + + ;; [32, 36) buffer for the call stack height + + ;; [36, 40) size of the input buffer + (data (i32.const 36) "\24") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (local $callstack_height i32) + (local $delegate_call_exit_code i32) + + ;; Reading input + (call $seal_input (i32.const 0) (i32.const 36)) + + ;; reading passed callstack height + (set_local $callstack_height (i32.load (i32.const 32))) + + ;; incrementing callstack height + (i32.store (i32.const 32) (i32.add (i32.load (i32.const 32)) (i32.const 1))) + + ;; reentrance count stays 0 + (call $assert + (i32.eq (call $reentrance_count) (i32.const 0)) + ) + + (i32.eq (get_local $callstack_height) (i32.const 5)) + (if + (then) ;; exit recursion case + (else + ;; Call to itself + (set_local $delegate_call_exit_code + (call $seal_delegate_call + (i32.const 0) ;; Set no call flags + (i32.const 0) ;; Pointer to "callee" code_hash. + (i32.const 0) ;; Pointer to the input data + (i32.const 36) ;; Length of the input + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + ) + + (call $assert + (i32.eq (get_local $delegate_call_exit_code) (i32.const 0)) + ) + ) + ) + + (call $assert + (i32.le_s (get_local $callstack_height) (i32.const 5)) + ) + ) + + (func (export "deploy")) +) \ No newline at end of file diff --git a/frame/contracts/fixtures/set_code_hash.wat b/frame/contracts/fixtures/set_code_hash.wat new file mode 100644 index 0000000000000..b4df1b133186b --- /dev/null +++ b/frame/contracts/fixtures/set_code_hash.wat @@ -0,0 +1,43 @@ +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "seal0" "seal_set_code_hash" (func $seal_set_code_hash (param i32) (result i32))) + + (import "env" "memory" (memory 1 1)) + + ;; [0, 32) here we store input + + ;; [32, 36) input size + (data (i32.const 32) "\20") + + ;; [36, 40) return value + (data (i32.const 36) "\01") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + (local $exit_code i32) + + (call $seal_input (i32.const 0) (i32.const 32)) + + (set_local $exit_code + (call $seal_set_code_hash (i32.const 0)) ;; Pointer to the input data. + ) + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0)) ;; ReturnCode::Success + ) + + ;; we return 1 after setting new code_hash + ;; next `call` will NOT return this value, because contract code has been changed + (call $seal_return (i32.const 0) (i32.const 36) (i32.const 4)) + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/store.wat b/frame/contracts/fixtures/store.wat new file mode 100644 index 0000000000000..9e090d31801f8 --- /dev/null +++ b/frame/contracts/fixtures/store.wat @@ -0,0 +1,45 @@ +;; Stores a value of the passed size. +(module + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "env" "memory" (memory 16 16)) + + ;; [0, 32) storage key + (data (i32.const 0) "\01") + + ;; [32, 36) buffer where input is copied (expected size of storage item) + + ;; [36, 40) size of the input buffer + (data (i32.const 36) "\04") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + (call $seal_input (i32.const 32) (i32.const 36)) + + ;; assert input size == 4 + (call $assert + (i32.eq + (i32.load (i32.const 36)) + (i32.const 4) + ) + ) + + ;; place a value in storage, the size of which is specified by the call input. + ;; we don't care about the contents of the storage item + (call $seal_set_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 0) ;; Pointer to value + (i32.load (i32.const 32)) ;; Size of value + ) + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/primitives/Cargo.toml b/frame/contracts/primitives/Cargo.toml new file mode 100644 index 0000000000000..d0c3a34ddfccb --- /dev/null +++ b/frame/contracts/primitives/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "pallet-contracts-primitives" +version = "7.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "A crate that hosts a common definitions that are relevant for the pallet-contracts." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +bitflags = "1.0" +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } + +# Substrate Dependencies (This crate should not rely on frame) +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../../primitives/weights" } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/frame/contracts/common/README.md b/frame/contracts/primitives/README.md similarity index 100% rename from frame/contracts/common/README.md rename to frame/contracts/primitives/README.md diff --git a/frame/contracts/primitives/src/lib.rs b/frame/contracts/primitives/src/lib.rs new file mode 100644 index 0000000000000..4faea9eb3ee75 --- /dev/null +++ b/frame/contracts/primitives/src/lib.rs @@ -0,0 +1,252 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! A crate that hosts a common definitions that are relevant for the pallet-contracts. + +#![cfg_attr(not(feature = "std"), no_std)] + +use bitflags::bitflags; +use codec::{Decode, Encode}; +use sp_runtime::{ + traits::{Saturating, Zero}, + DispatchError, RuntimeDebug, +}; +use sp_std::prelude::*; +use sp_weights::Weight; + +/// Result type of a `bare_call` or `bare_instantiate` call. +/// +/// It contains the execution result together with some auxiliary information. +#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] +pub struct ContractResult { + /// How much weight was consumed during execution. + pub gas_consumed: Weight, + /// How much weight is required as gas limit in order to execute this call. + /// + /// This value should be used to determine the weight limit for on-chain execution. + /// + /// # Note + /// + /// This can only different from [`Self::gas_consumed`] when weight pre charging + /// is used. Currently, only `seal_call_runtime` makes use of pre charging. + /// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging + /// when a non-zero `gas_limit` argument is supplied. + pub gas_required: Weight, + /// How much balance was deposited and reserved during execution in order to pay for storage. + /// + /// The storage deposit is never actually charged from the caller in case of [`Self::result`] + /// is `Err`. This is because on error all storage changes are rolled back. + pub storage_deposit: StorageDeposit, + /// An optional debug message. This message is only filled when explicitly requested + /// by the code that calls into the contract. Otherwise it is empty. + /// + /// The contained bytes are valid UTF-8. This is not declared as `String` because + /// this type is not allowed within the runtime. + /// + /// Clients should not make any assumptions about the format of the buffer. + /// They should just display it as-is. It is **not** only a collection of log lines + /// provided by a contract but a formatted buffer with different sections. + /// + /// # Note + /// + /// The debug message is never generated during on-chain execution. It is reserved for + /// RPC calls. + pub debug_message: Vec, + /// The execution result of the wasm code. + pub result: R, +} + +/// Result type of a `bare_call` call. +pub type ContractExecResult = + ContractResult, Balance>; + +/// Result type of a `bare_instantiate` call. +pub type ContractInstantiateResult = + ContractResult, DispatchError>, Balance>; + +/// Result type of a `bare_code_upload` call. +pub type CodeUploadResult = + Result, DispatchError>; + +/// Result type of a `get_storage` call. +pub type GetStorageResult = Result>, ContractAccessError>; + +/// The possible errors that can happen querying the storage of a contract. +#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] +pub enum ContractAccessError { + /// The given address doesn't point to a contract. + DoesntExist, + /// Storage key cannot be decoded from the provided input data. + KeyDecodingFailed, +} + +bitflags! { + /// Flags used by a contract to customize exit behaviour. + #[derive(Encode, Decode)] + pub struct ReturnFlags: u32 { + /// If this bit is set all changes made by the contract execution are rolled back. + const REVERT = 0x0000_0001; + } +} + +/// Output of a contract call or instantiation which ran to completion. +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] +pub struct ExecReturnValue { + /// Flags passed along by `seal_return`. Empty when `seal_return` was never called. + pub flags: ReturnFlags, + /// Buffer passed along by `seal_return`. Empty when `seal_return` was never called. + pub data: Vec, +} + +impl ExecReturnValue { + /// The contract did revert all storage changes. + pub fn did_revert(&self) -> bool { + self.flags.contains(ReturnFlags::REVERT) + } +} + +/// The result of a successful contract instantiation. +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] +pub struct InstantiateReturnValue { + /// The output of the called constructor. + pub result: ExecReturnValue, + /// The account id of the new contract. + pub account_id: AccountId, +} + +/// The result of succesfully uploading a contract. +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] +pub struct CodeUploadReturnValue { + /// The key under which the new code is stored. + pub code_hash: CodeHash, + /// The deposit that was reserved at the caller. Is zero when the code already existed. + pub deposit: Balance, +} + +/// Reference to an existing code hash or a new wasm module. +#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] +pub enum Code { + /// A wasm module as raw bytes. + Upload(Vec), + /// The code hash of an on-chain wasm blob. + Existing(Hash), +} + +impl>, Hash> From for Code { + fn from(from: T) -> Self { + Code::Upload(from.into()) + } +} + +/// The amount of balance that was either charged or refunded in order to pay for storage. +#[derive(Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, Clone)] +pub enum StorageDeposit { + /// The transaction reduced storage consumption. + /// + /// This means that the specified amount of balance was transferred from the involved + /// contracts to the call origin. + Refund(Balance), + /// The transaction increased overall storage usage. + /// + /// This means that the specified amount of balance was transferred from the call origin + /// to the contracts involved. + Charge(Balance), +} + +impl Default for StorageDeposit { + fn default() -> Self { + Self::Charge(Zero::zero()) + } +} + +impl StorageDeposit { + /// Returns how much balance is charged or `0` in case of a refund. + pub fn charge_or_zero(&self) -> Balance { + match self { + Self::Charge(amount) => *amount, + Self::Refund(_) => Zero::zero(), + } + } + + pub fn is_zero(&self) -> bool { + match self { + Self::Charge(amount) => amount.is_zero(), + Self::Refund(amount) => amount.is_zero(), + } + } +} + +impl StorageDeposit +where + Balance: Saturating + Ord + Copy, +{ + /// This is essentially a saturating signed add. + pub fn saturating_add(&self, rhs: &Self) -> Self { + use StorageDeposit::*; + match (self, rhs) { + (Charge(lhs), Charge(rhs)) => Charge(lhs.saturating_add(*rhs)), + (Refund(lhs), Refund(rhs)) => Refund(lhs.saturating_add(*rhs)), + (Charge(lhs), Refund(rhs)) => + if lhs >= rhs { + Charge(lhs.saturating_sub(*rhs)) + } else { + Refund(rhs.saturating_sub(*lhs)) + }, + (Refund(lhs), Charge(rhs)) => + if lhs > rhs { + Refund(lhs.saturating_sub(*rhs)) + } else { + Charge(rhs.saturating_sub(*lhs)) + }, + } + } + + /// This is essentially a saturating signed sub. + pub fn saturating_sub(&self, rhs: &Self) -> Self { + use StorageDeposit::*; + match (self, rhs) { + (Charge(lhs), Refund(rhs)) => Charge(lhs.saturating_add(*rhs)), + (Refund(lhs), Charge(rhs)) => Refund(lhs.saturating_add(*rhs)), + (Charge(lhs), Charge(rhs)) => + if lhs >= rhs { + Charge(lhs.saturating_sub(*rhs)) + } else { + Refund(rhs.saturating_sub(*lhs)) + }, + (Refund(lhs), Refund(rhs)) => + if lhs > rhs { + Refund(lhs.saturating_sub(*rhs)) + } else { + Charge(rhs.saturating_sub(*lhs)) + }, + } + } + + /// If the amount of deposit (this type) is constrained by a `limit` this calcuates how + /// much balance (if any) is still available from this limit. + /// + /// # Note + /// + /// In case of a refund the return value can be larger than `limit`. + pub fn available(&self, limit: &Balance) -> Balance { + use StorageDeposit::*; + match self { + Charge(amount) => limit.saturating_sub(*amount), + Refund(amount) => limit.saturating_add(*amount), + } + } +} diff --git a/frame/contracts/proc-macro/Cargo.toml b/frame/contracts/proc-macro/Cargo.toml index 605c69fe73e25..08dafcc6a8fca 100644 --- a/frame/contracts/proc-macro/Cargo.toml +++ b/frame/contracts/proc-macro/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-contracts-proc-macro" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Procedural macros used in pallet_contracts" @@ -17,7 +17,7 @@ proc-macro = true [dependencies] proc-macro2 = "1" quote = "1" -syn = "1" +syn = { version = "1.0.98", features = ["full"] } [dev-dependencies] diff --git a/frame/contracts/proc-macro/src/lib.rs b/frame/contracts/proc-macro/src/lib.rs index 302a0d01a93d9..a8f95bd10cff8 100644 --- a/frame/contracts/proc-macro/src/lib.rs +++ b/frame/contracts/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,36 +15,41 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Proc macros used in the contracts module. +//! Procedural macroses used in the contracts module. +//! +//! Most likely you should use the [`#[define_env]`][`macro@define_env`] attribute macro which hides +//! boilerplate of defining external environment for a wasm module. #![no_std] extern crate alloc; -use alloc::string::ToString; -use proc_macro2::TokenStream; -use quote::{quote, quote_spanned}; -use syn::{parse_macro_input, spanned::Spanned, Data, DataStruct, DeriveInput, Fields, Ident}; +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, quote_spanned, ToTokens}; +use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, FnArg, Ident}; /// This derives `Debug` for a struct where each field must be of some numeric type. /// It interprets each field as its represents some weight and formats it as times so that /// it is readable by humans. #[proc_macro_derive(WeightDebug)] -pub fn derive_weight_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn derive_weight_debug(input: TokenStream) -> TokenStream { derive_debug(input, format_weight) } /// This is basically identical to the std libs Debug derive but without adding any /// bounds to existing generics. #[proc_macro_derive(ScheduleDebug)] -pub fn derive_schedule_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn derive_schedule_debug(input: TokenStream) -> TokenStream { derive_debug(input, format_default) } -fn derive_debug( - input: proc_macro::TokenStream, - fmt: impl Fn(&Ident) -> TokenStream, -) -> proc_macro::TokenStream { +fn derive_debug(input: TokenStream, fmt: impl Fn(&Ident) -> TokenStream2) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); @@ -65,7 +70,7 @@ fn derive_debug( let fields = { drop(fmt); drop(data); - TokenStream::new() + TokenStream2::new() }; let tokens = quote! { @@ -84,7 +89,9 @@ fn derive_debug( /// This is only used then the `full` feature is activated. #[cfg(feature = "full")] -fn iterate_fields(data: &DataStruct, fmt: impl Fn(&Ident) -> TokenStream) -> TokenStream { +fn iterate_fields(data: &syn::DataStruct, fmt: impl Fn(&Ident) -> TokenStream2) -> TokenStream2 { + use syn::Fields; + match &data.fields { Fields::Named(fields) => { let recurse = fields.named.iter().filter_map(|f| { @@ -110,7 +117,7 @@ fn iterate_fields(data: &DataStruct, fmt: impl Fn(&Ident) -> TokenStream) -> Tok } } -fn format_weight(field: &Ident) -> TokenStream { +fn format_weight(field: &Ident) -> TokenStream2 { quote_spanned! { field.span() => &if self.#field > 1_000_000_000 { format!( @@ -133,8 +140,461 @@ fn format_weight(field: &Ident) -> TokenStream { } } -fn format_default(field: &Ident) -> TokenStream { +fn format_default(field: &Ident) -> TokenStream2 { quote_spanned! { field.span() => &self.#field } } + +/// Parsed environment definition. +struct EnvDef { + host_funcs: Vec, +} + +/// Parsed host function definition. +struct HostFn { + item: syn::ItemFn, + module: String, + name: String, + returns: HostFnReturn, + is_stable: bool, +} + +enum HostFnReturn { + Unit, + U32, + U64, + ReturnCode, +} + +impl HostFnReturn { + fn to_wasm_sig(&self) -> TokenStream2 { + let ok = match self { + Self::Unit => quote! { () }, + Self::U32 | Self::ReturnCode => quote! { ::core::primitive::u32 }, + Self::U64 => quote! { ::core::primitive::u64 }, + }; + quote! { + ::core::result::Result<#ok, ::wasmi::core::Trap> + } + } +} + +impl ToTokens for HostFn { + fn to_tokens(&self, tokens: &mut TokenStream2) { + self.item.to_tokens(tokens); + } +} + +impl HostFn { + pub fn try_from(item: syn::ItemFn) -> syn::Result { + let err = |span, msg| { + let msg = format!("Invalid host function definition. {}", msg); + syn::Error::new(span, msg) + }; + + // process attributes + let msg = + "only #[version()], #[unstable] and #[prefixed_alias] attributes are allowed."; + let span = item.span(); + let mut attrs = item.attrs.clone(); + attrs.retain(|a| !(a.path.is_ident("doc") || a.path.is_ident("prefixed_alias"))); + let name = item.sig.ident.to_string(); + let mut maybe_module = None; + let mut is_stable = true; + while let Some(attr) = attrs.pop() { + let ident = attr.path.get_ident().ok_or(err(span, msg))?.to_string(); + match ident.as_str() { + "version" => { + if maybe_module.is_some() { + return Err(err(span, "#[version] can only be specified once")) + } + let ver: u8 = + attr.parse_args::().and_then(|lit| lit.base10_parse())?; + maybe_module = Some(format!("seal{}", ver)); + }, + "unstable" => { + if !is_stable { + return Err(err(span, "#[unstable] can only be specified once")) + } + is_stable = false; + }, + _ => return Err(err(span, msg)), + } + } + + // process arguments: The first and second arg are treated differently (ctx, memory) + // they must exist and be `ctx: _` and `memory: _`. + let msg = "Every function must start with two inferred parameters: ctx: _ and memory: _"; + let special_args = item + .sig + .inputs + .iter() + .take(2) + .enumerate() + .map(|(i, arg)| is_valid_special_arg(i, arg)) + .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc }); + + if special_args != 2 { + return Err(err(span, msg)) + } + + // process return type + let msg = r#"Should return one of the following: + - Result<(), TrapReason>, + - Result, + - Result, + - Result"#; + let ret_ty = match item.clone().sig.output { + syn::ReturnType::Type(_, ty) => Ok(ty.clone()), + _ => Err(err(span, &msg)), + }?; + match *ret_ty { + syn::Type::Path(tp) => { + let result = &tp.path.segments.last().ok_or(err(span, &msg))?; + let (id, span) = (result.ident.to_string(), result.ident.span()); + id.eq(&"Result".to_string()).then_some(()).ok_or(err(span, &msg))?; + + match &result.arguments { + syn::PathArguments::AngleBracketed(group) => { + if group.args.len() != 2 { + return Err(err(span, &msg)) + }; + + let arg2 = group.args.last().ok_or(err(span, &msg))?; + + let err_ty = match arg2 { + syn::GenericArgument::Type(ty) => Ok(ty.clone()), + _ => Err(err(arg2.span(), &msg)), + }?; + + match err_ty { + syn::Type::Path(tp) => Ok(tp + .path + .segments + .first() + .ok_or(err(arg2.span(), &msg))? + .ident + .to_string()), + _ => Err(err(tp.span(), &msg)), + }? + .eq("TrapReason") + .then_some(()) + .ok_or(err(span, &msg))?; + + let arg1 = group.args.first().ok_or(err(span, &msg))?; + let ok_ty = match arg1 { + syn::GenericArgument::Type(ty) => Ok(ty.clone()), + _ => Err(err(arg1.span(), &msg)), + }?; + let ok_ty_str = match ok_ty { + syn::Type::Path(tp) => Ok(tp + .path + .segments + .first() + .ok_or(err(arg1.span(), &msg))? + .ident + .to_string()), + syn::Type::Tuple(tt) => { + if !tt.elems.is_empty() { + return Err(err(arg1.span(), &msg)) + }; + Ok("()".to_string()) + }, + _ => Err(err(ok_ty.span(), &msg)), + }?; + let returns = match ok_ty_str.as_str() { + "()" => Ok(HostFnReturn::Unit), + "u32" => Ok(HostFnReturn::U32), + "u64" => Ok(HostFnReturn::U64), + "ReturnCode" => Ok(HostFnReturn::ReturnCode), + _ => Err(err(arg1.span(), &msg)), + }?; + + Ok(Self { + item, + module: maybe_module.unwrap_or_else(|| "seal0".to_string()), + name, + returns, + is_stable, + }) + }, + _ => Err(err(span, &msg)), + } + }, + _ => Err(err(span, &msg)), + } + } +} + +impl EnvDef { + pub fn try_from(item: syn::ItemMod) -> syn::Result { + let span = item.span(); + let err = |msg| syn::Error::new(span, msg); + let items = &item + .content + .as_ref() + .ok_or(err("Invalid environment definition, expected `mod` to be inlined."))? + .1; + + let extract_fn = |i: &syn::Item| match i { + syn::Item::Fn(i_fn) => Some(i_fn.clone()), + _ => None, + }; + + let selector = |a: &syn::Attribute| a.path.is_ident("prefixed_alias"); + + let aliases = items + .iter() + .filter_map(extract_fn) + .filter(|i| i.attrs.iter().any(selector)) + .map(|mut i| { + i.attrs.retain(|i| !selector(i)); + i.sig.ident = syn::Ident::new( + &format!("seal_{}", &i.sig.ident.to_string()), + i.sig.ident.span(), + ); + i + }) + .map(|i| HostFn::try_from(i)); + + let host_funcs = items + .iter() + .filter_map(extract_fn) + .map(|i| HostFn::try_from(i)) + .chain(aliases) + .collect::, _>>()?; + + Ok(Self { host_funcs }) + } +} + +fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool { + let pat = if let FnArg::Typed(pat) = arg { pat } else { return false }; + let ident = if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false }; + let name_ok = match idx { + 0 => ident == "ctx" || ident == "_ctx", + 1 => ident == "memory" || ident == "_memory", + _ => false, + }; + if !name_ok { + return false + } + matches!(*pat.ty, syn::Type::Infer(_)) +} + +/// Expands environment definiton. +/// Should generate source code for: +/// - implementations of the host functions to be added to the wasm runtime environment (see +/// `expand_impls()`). +fn expand_env(def: &mut EnvDef) -> TokenStream2 { + let impls = expand_impls(def); + + quote! { + pub struct Env; + #impls + } +} + +/// Generates for every host function: +/// - real implementation, to register it in the contract execution environment; +/// - dummy implementation, to be used as mocks for contract validation step. +fn expand_impls(def: &mut EnvDef) -> TokenStream2 { + let impls = expand_functions(def, true, quote! { crate::wasm::Runtime }); + let dummy_impls = expand_functions(def, false, quote! { () }); + + quote! { + impl<'a, E> crate::wasm::Environment> for Env + where + E: Ext, + ::AccountId: + ::sp_core::crypto::UncheckedFrom<::Hash> + ::core::convert::AsRef<[::core::primitive::u8]>, + { + fn define(store: &mut ::wasmi::Store>, linker: &mut ::wasmi::Linker>, allow_unstable: bool) -> Result<(), ::wasmi::errors::LinkerError> { + #impls + Ok(()) + } + } + + impl crate::wasm::Environment<()> for Env + { + fn define(store: &mut ::wasmi::Store<()>, linker: &mut ::wasmi::Linker<()>, allow_unstable: bool) -> Result<(), ::wasmi::errors::LinkerError> { + #dummy_impls + Ok(()) + } + } + } +} + +fn expand_functions( + def: &mut EnvDef, + expand_blocks: bool, + host_state: TokenStream2, +) -> TokenStream2 { + let impls = def.host_funcs.iter().map(|f| { + // skip the context and memory argument + let params = f.item.sig.inputs.iter().skip(2); + let (module, name, body, wasm_output, output) = ( + &f.module, + &f.name, + &f.item.block, + f.returns.to_wasm_sig(), + &f.item.sig.output + ); + let is_stable = f.is_stable; + + // If we don't expand blocks (implementing for `()`) we change a few things: + // - We replace any code by unreachable! + // - Allow unused variables as the code that uses is not expanded + // - We don't need to map the error as we simply panic if they code would ever be executed + let inner = if expand_blocks { + quote! { || #output { + let (memory, ctx) = __caller__ + .host_data() + .memory() + .expect("Memory must be set when setting up host data; qed") + .data_and_store_mut(&mut __caller__); + #body + } } + } else { + quote! { || -> #wasm_output { + // This is part of the implementation for `Environment<()>` which is not + // meant to be actually executed. It is only for validation which will + // never call host functions. + ::core::unreachable!() + } } + }; + let map_err = if expand_blocks { + quote! { + |reason| { + ::wasmi::core::Trap::host(reason) + } + } + } else { + quote! { + |reason| { reason } + } + }; + let allow_unused = if expand_blocks { + quote! { } + } else { + quote! { #[allow(unused_variables)] } + }; + + quote! { + // We need to allow unstable functions when runtime benchmarks are performed because + // we generate the weights even when those interfaces are not enabled. + if ::core::cfg!(feature = "runtime-benchmarks") || #is_stable || allow_unstable { + #allow_unused + linker.define(#module, #name, ::wasmi::Func::wrap(&mut*store, |mut __caller__: ::wasmi::Caller<#host_state>, #( #params, )*| -> #wasm_output { + let mut func = #inner; + func() + .map_err(#map_err) + .map(::core::convert::Into::into) + }))?; + } + } + }); + quote! { + #( #impls )* + } +} + +/// Defines a host functions set that can be imported by contract wasm code. +/// +/// **NB**: Be advised that all functions defined by this macro +/// will panic if called with unexpected arguments. +/// +/// It's up to you as the user of this macro to check signatures of wasm code to be executed +/// and reject the code if any imported function has a mismatched signature. +/// +/// ## Example +/// +/// ```nocompile +/// #[define_env] +/// pub mod some_env { +/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// } +/// ``` +/// This example will expand to the `foo()` defined in the wasm module named `seal0`. This is +/// because the module `seal0` is the default when no module is specified. +/// +/// To define a host function in `seal2` and `seal3` modules, it should be annotated with the +/// appropriate attribute as follows: +/// +/// ## Example +/// +/// ```nocompile +/// #[define_env] +/// pub mod some_env { +/// #[version(2)] +/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// +/// #[version(3)] +/// #[unstable] +/// fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// } +/// ``` +/// The function `bar` is additionally annotated with `unstable` which removes it from the stable +/// interface. Check out the README to learn about unstable functions. +/// +/// In legacy versions of pallet_contracts, it was a naming convention that all host functions had +/// to be named with the `seal_` prefix. For the sake of backwards compatibility, each host function +/// now can get a such prefix-named alias function generated by marking it by the +/// `#[prefixed_alias]` attribute: +/// +/// ## Example +/// +/// ```nocompile +/// #[define_env] +/// pub mod some_env { +/// #[version(1)] +/// #[prefixed_alias] +/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// +/// #[version(42)] +/// fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// } +/// ``` +/// +/// In this example, the following host functions will be generated by the macro: +/// - `foo()` in module `seal1`, +/// - `seal_foo()` in module `seal1`, +/// - `bar()` in module `seal42`. +/// +/// Only following return types are allowed for the host functions defined with the macro: +/// - `Result<(), TrapReason>`, +/// - `Result`, +/// - `Result`. +/// +/// The macro expands to `pub struct Env` declaration, with the following traits implementations: +/// - `pallet_contracts::wasm::Environment> where E: Ext` +/// - `pallet_contracts::wasm::Environment<()>` +/// +/// The implementation on `()` can be used in places where no `Ext` exists, yet. This is useful +/// when only checking whether a code can be instantiated without actually executing any code. +#[proc_macro_attribute] +pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { + if !attr.is_empty() { + let msg = "Invalid `define_env` attribute macro: expected no attributes: `#[define_env]`."; + let span = TokenStream2::from(attr).span(); + return syn::Error::new(span, msg).to_compile_error().into() + } + + let item = syn::parse_macro_input!(item as syn::ItemMod); + + match EnvDef::try_from(item) { + Ok(mut def) => expand_env(&mut def).into(), + Err(e) => e.to_compile_error().into(), + } +} diff --git a/frame/contracts/rpc/Cargo.toml b/frame/contracts/rpc/Cargo.toml deleted file mode 100644 index b73039ba7191e..0000000000000 --- a/frame/contracts/rpc/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "pallet-contracts-rpc" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "Node-specific RPC methods for interaction with contracts." -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "2" } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -serde = { version = "1", features = ["derive"] } - -# Substrate Dependencies -pallet-contracts-primitives = { version = "4.0.0-dev", path = "../common" } -pallet-contracts-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } -sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-rpc = { version = "4.0.0-dev", path = "../../../primitives/rpc" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } - -[dev-dependencies] -serde_json = "1" diff --git a/frame/contracts/rpc/README.md b/frame/contracts/rpc/README.md deleted file mode 100644 index be6df237bf60d..0000000000000 --- a/frame/contracts/rpc/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Node-specific RPC methods for interaction with contracts. - -License: Apache-2.0 \ No newline at end of file diff --git a/frame/contracts/rpc/runtime-api/Cargo.toml b/frame/contracts/rpc/runtime-api/Cargo.toml deleted file mode 100644 index e5f6d1ec7eb8e..0000000000000 --- a/frame/contracts/rpc/runtime-api/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "pallet-contracts-rpc-runtime-api" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "Runtime API definition required by Contracts RPC extensions." -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } - -# Substrate Dependencies -pallet-contracts-primitives = { version = "4.0.0-dev", default-features = false, path = "../../common" } -sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/std" } - -[features] -default = ["std"] -std = [ - "sp-api/std", - "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", - "pallet-contracts-primitives/std", -] diff --git a/frame/contracts/rpc/runtime-api/README.md b/frame/contracts/rpc/runtime-api/README.md deleted file mode 100644 index d57f29a93bd1d..0000000000000 --- a/frame/contracts/rpc/runtime-api/README.md +++ /dev/null @@ -1,7 +0,0 @@ -Runtime API definition required by Contracts RPC extensions. - -This API should be imported and implemented by the runtime, -of a node that wants to use the custom RPC extension -adding Contracts access methods. - -License: Apache-2.0 \ No newline at end of file diff --git a/frame/contracts/rpc/runtime-api/src/lib.rs b/frame/contracts/rpc/runtime-api/src/lib.rs deleted file mode 100644 index 20dfbe210e5ce..0000000000000 --- a/frame/contracts/rpc/runtime-api/src/lib.rs +++ /dev/null @@ -1,73 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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. - -//! Runtime API definition required by Contracts RPC extensions. -//! -//! This API should be imported and implemented by the runtime, -//! of a node that wants to use the custom RPC extension -//! adding Contracts access methods. - -#![cfg_attr(not(feature = "std"), no_std)] - -use codec::Codec; -use pallet_contracts_primitives::{ - Code, ContractExecResult, ContractInstantiateResult, GetStorageResult, -}; -use sp_std::vec::Vec; - -sp_api::decl_runtime_apis! { - /// The API to interact with contracts without using executive. - pub trait ContractsApi where - AccountId: Codec, - Balance: Codec, - BlockNumber: Codec, - Hash: Codec, - { - /// Perform a call from a specified account to a given contract. - /// - /// See `pallet_contracts::Pallet::call`. - fn call( - origin: AccountId, - dest: AccountId, - value: Balance, - gas_limit: u64, - input_data: Vec, - ) -> ContractExecResult; - - /// Instantiate a new contract. - /// - /// See `pallet_contracts::Pallet::instantiate`. - fn instantiate( - origin: AccountId, - endowment: Balance, - gas_limit: u64, - code: Code, - data: Vec, - salt: Vec, - ) -> ContractInstantiateResult; - - /// Query a given storage key in a given contract. - /// - /// Returns `Ok(Some(Vec))` if the storage value exists under the given key in the - /// specified account and `Ok(None)` if it doesn't. If the account specified by the address - /// doesn't exist, or doesn't have a contract then `Err` is returned. - fn get_storage( - address: AccountId, - key: [u8; 32], - ) -> GetStorageResult; - } -} diff --git a/frame/contracts/rpc/src/lib.rs b/frame/contracts/rpc/src/lib.rs deleted file mode 100644 index e0796af056540..0000000000000 --- a/frame/contracts/rpc/src/lib.rs +++ /dev/null @@ -1,395 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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. - -//! Node-specific RPC methods for interaction with contracts. - -use std::sync::Arc; - -use codec::Codec; -use jsonrpc_core::{Error, ErrorCode, Result}; -use jsonrpc_derive::rpc; -use pallet_contracts_primitives::{Code, ContractExecResult, ContractInstantiateResult}; -use serde::{Deserialize, Serialize}; -use sp_api::ProvideRuntimeApi; -use sp_blockchain::HeaderBackend; -use sp_core::{Bytes, H256}; -use sp_rpc::number::NumberOrHex; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as HeaderT}, -}; -use std::convert::{TryFrom, TryInto}; - -pub use pallet_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi; - -const RUNTIME_ERROR: i64 = 1; -const CONTRACT_DOESNT_EXIST: i64 = 2; - -pub type Weight = u64; - -/// A rough estimate of how much gas a decent hardware consumes per second, -/// using native execution. -/// This value is used to set the upper bound for maximal contract calls to -/// prevent blocking the RPC for too long. -/// -/// As 1 gas is equal to 1 weight we base this on the conducted benchmarks which -/// determined runtime weights: -/// -const GAS_PER_SECOND: Weight = 1_000_000_000_000; - -/// The maximum amount of weight that the call and instantiate rpcs are allowed to consume. -/// This puts a ceiling on the weight limit that is supplied to the rpc as an argument. -const GAS_LIMIT: Weight = 5 * GAS_PER_SECOND; - -/// A private newtype for converting `ContractAccessError` into an RPC error. -struct ContractAccessError(pallet_contracts_primitives::ContractAccessError); -impl From for Error { - fn from(e: ContractAccessError) -> Error { - use pallet_contracts_primitives::ContractAccessError::*; - match e.0 { - DoesntExist => Error { - code: ErrorCode::ServerError(CONTRACT_DOESNT_EXIST), - message: "The specified contract doesn't exist.".into(), - data: None, - }, - } - } -} - -/// A struct that encodes RPC parameters required for a call to a smart-contract. -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct CallRequest { - origin: AccountId, - dest: AccountId, - value: NumberOrHex, - gas_limit: NumberOrHex, - input_data: Bytes, -} - -/// A struct that encodes RPC parameters required to instantiate a new smart-contract. -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct InstantiateRequest { - origin: AccountId, - endowment: NumberOrHex, - gas_limit: NumberOrHex, - code: Code, - data: Bytes, - salt: Bytes, -} - -/// Contracts RPC methods. -#[rpc] -pub trait ContractsApi { - /// Executes a call to a contract. - /// - /// This call is performed locally without submitting any transactions. Thus executing this - /// won't change any state. Nonetheless, the calling state-changing contracts is still possible. - /// - /// This method is useful for calling getter-like methods on contracts. - #[rpc(name = "contracts_call")] - fn call( - &self, - call_request: CallRequest, - at: Option, - ) -> Result; - - /// Instantiate a new contract. - /// - /// This call is performed locally without submitting any transactions. Thus the contract - /// is not actually created. - /// - /// This method is useful for UIs to dry-run contract instantiations. - #[rpc(name = "contracts_instantiate")] - fn instantiate( - &self, - instantiate_request: InstantiateRequest, - at: Option, - ) -> Result>; - - /// Returns the value under a specified storage `key` in a contract given by `address` param, - /// or `None` if it is not set. - #[rpc(name = "contracts_getStorage")] - fn get_storage( - &self, - address: AccountId, - key: H256, - at: Option, - ) -> Result>; -} - -/// An implementation of contract specific RPC methods. -pub struct Contracts { - client: Arc, - _marker: std::marker::PhantomData, -} - -impl Contracts { - /// Create new `Contracts` with the given reference to the client. - pub fn new(client: Arc) -> Self { - Contracts { client, _marker: Default::default() } - } -} -impl - ContractsApi< - ::Hash, - <::Header as HeaderT>::Number, - AccountId, - Balance, - Hash, - > for Contracts -where - Block: BlockT, - C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, - C::Api: ContractsRuntimeApi< - Block, - AccountId, - Balance, - <::Header as HeaderT>::Number, - Hash, - >, - AccountId: Codec, - Balance: Codec + TryFrom, - Hash: Codec, -{ - fn call( - &self, - call_request: CallRequest, - at: Option<::Hash>, - ) -> Result { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); - - let CallRequest { origin, dest, value, gas_limit, input_data } = call_request; - - let value: Balance = decode_hex(value, "balance")?; - let gas_limit: Weight = decode_hex(gas_limit, "weight")?; - limit_gas(gas_limit)?; - - let exec_result = api - .call(&at, origin, dest, value, gas_limit, input_data.to_vec()) - .map_err(runtime_error_into_rpc_err)?; - - Ok(exec_result) - } - - fn instantiate( - &self, - instantiate_request: InstantiateRequest, - at: Option<::Hash>, - ) -> Result> { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); - - let InstantiateRequest { origin, endowment, gas_limit, code, data, salt } = - instantiate_request; - - let endowment: Balance = decode_hex(endowment, "balance")?; - let gas_limit: Weight = decode_hex(gas_limit, "weight")?; - limit_gas(gas_limit)?; - - let exec_result = api - .instantiate(&at, origin, endowment, gas_limit, code, data.to_vec(), salt.to_vec()) - .map_err(runtime_error_into_rpc_err)?; - - Ok(exec_result) - } - - fn get_storage( - &self, - address: AccountId, - key: H256, - at: Option<::Hash>, - ) -> Result> { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); - - let result = api - .get_storage(&at, address, key.into()) - .map_err(runtime_error_into_rpc_err)? - .map_err(ContractAccessError)? - .map(Bytes); - - Ok(result) - } -} - -/// Converts a runtime trap into an RPC error. -fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> Error { - Error { - code: ErrorCode::ServerError(RUNTIME_ERROR), - message: "Runtime error".into(), - data: Some(format!("{:?}", err).into()), - } -} - -fn decode_hex>(from: H, name: &str) -> Result { - from.try_into().map_err(|_| Error { - code: ErrorCode::InvalidParams, - message: format!("{:?} does not fit into the {} type", from, name), - data: None, - }) -} - -fn limit_gas(gas_limit: Weight) -> Result<()> { - if gas_limit > GAS_LIMIT { - Err(Error { - code: ErrorCode::InvalidParams, - message: format!( - "Requested gas limit is greater than maximum allowed: {} > {}", - gas_limit, GAS_LIMIT - ), - data: None, - }) - } else { - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_core::U256; - - fn trim(json: &str) -> String { - json.chars().filter(|c| !c.is_whitespace()).collect() - } - - #[test] - fn call_request_should_serialize_deserialize_properly() { - type Req = CallRequest; - let req: Req = serde_json::from_str( - r#" - { - "origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", - "dest": "5DRakbLVnjVrW6niwLfHGW24EeCEvDAFGEXrtaYS5M4ynoom", - "value": "0x112210f4B16c1cb1", - "gasLimit": 1000000000000, - "inputData": "0x8c97db39" - } - "#, - ) - .unwrap(); - assert_eq!(req.gas_limit.into_u256(), U256::from(0xe8d4a51000u64)); - assert_eq!(req.value.into_u256(), U256::from(1234567890987654321u128)); - } - - #[test] - fn instantiate_request_should_serialize_deserialize_properly() { - type Req = InstantiateRequest; - let req: Req = serde_json::from_str( - r#" - { - "origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", - "endowment": "0x88", - "gasLimit": 42, - "code": { "existing": "0x1122" }, - "data": "0x4299", - "salt": "0x9988" - } - "#, - ) - .unwrap(); - - assert_eq!(req.origin, "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL"); - assert_eq!(req.endowment.into_u256(), 0x88.into()); - assert_eq!(req.gas_limit.into_u256(), 42.into()); - assert_eq!(&*req.data, [0x42, 0x99].as_ref()); - assert_eq!(&*req.salt, [0x99, 0x88].as_ref()); - let code = match req.code { - Code::Existing(hash) => hash, - _ => panic!("json encoded an existing hash"), - }; - assert_eq!(&code, "0x1122"); - } - - #[test] - fn call_result_should_serialize_deserialize_properly() { - fn test(expected: &str) { - let res: ContractExecResult = serde_json::from_str(expected).unwrap(); - let actual = serde_json::to_string(&res).unwrap(); - assert_eq!(actual, trim(expected).as_str()); - } - test( - r#"{ - "gasConsumed": 5000, - "gasRequired": 8000, - "debugMessage": "HelloWorld", - "result": { - "Ok": { - "flags": 5, - "data": "0x1234" - } - } - }"#, - ); - test( - r#"{ - "gasConsumed": 3400, - "gasRequired": 5200, - "debugMessage": "HelloWorld", - "result": { - "Err": "BadOrigin" - } - }"#, - ); - } - - #[test] - fn instantiate_result_should_serialize_deserialize_properly() { - fn test(expected: &str) { - let res: ContractInstantiateResult = serde_json::from_str(expected).unwrap(); - let actual = serde_json::to_string(&res).unwrap(); - assert_eq!(actual, trim(expected).as_str()); - } - test( - r#"{ - "gasConsumed": 5000, - "gasRequired": 8000, - "debugMessage": "HelloWorld", - "result": { - "Ok": { - "result": { - "flags": 5, - "data": "0x1234" - }, - "accountId": "5CiPP" - } - } - }"#, - ); - test( - r#"{ - "gasConsumed": 3400, - "gasRequired": 5200, - "debugMessage": "HelloWorld", - "result": { - "Err": "BadOrigin" - } - }"#, - ); - } -} diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs index b24005ec58699..2074e1867d506 100644 --- a/frame/contracts/src/benchmarking/code.rs +++ b/frame/contracts/src/benchmarking/code.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,19 +24,29 @@ //! we define this simple definition of a contract that can be passed to `create_code` that //! compiles it down into a `WasmModule` that can be used as a contract's code. -use crate::Config; +use crate::{Config, Determinism}; use frame_support::traits::Get; -use pwasm_utils::parity_wasm::{ - builder, - elements::{ - self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module, - Section, ValueType, - }, -}; use sp_core::crypto::UncheckedFrom; use sp_runtime::traits::Hash; -use sp_sandbox::{EnvironmentDefinitionBuilder, Memory}; -use sp_std::{borrow::ToOwned, convert::TryFrom, prelude::*}; +use sp_std::{borrow::ToOwned, prelude::*}; +use wasm_instrument::{ + gas_metering, + parity_wasm::{ + builder, + elements::{ + self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module, + Section, ValueType, + }, + }, +}; + +/// The location where to put the genrated code. +pub enum Location { + /// Generate all code into the `call` exported function. + Call, + /// Generate all code into the `deploy` exported function. + Deploy, +} /// Pass to `create_code` in order to create a compiled `WasmModule`. /// @@ -117,7 +127,7 @@ pub struct ImportedFunction { pub struct WasmModule { pub code: Vec, pub hash: ::Output, - memory: Option, + pub memory: Option, } impl From for WasmModule @@ -184,7 +194,7 @@ where for func in def.imported_functions { let sig = builder::signature() .with_params(func.params) - .with_results(func.return_type.into_iter().collect()) + .with_results(func.return_type) .build_sig(); let sig = contract.push_signature(sig); contract = contract @@ -243,9 +253,9 @@ where code = inject_stack_metering::(code); } - let code = code.to_bytes().unwrap(); + let code = code.into_bytes().unwrap(); let hash = T::Hashing::hash(&code); - Self { code, hash, memory: def.memory } + Self { code: code.into(), hash, memory: def.memory } } } @@ -266,20 +276,19 @@ where } module }; - let limits = module + let limits = *module .import_section() .unwrap() .entries() .iter() .find_map(|e| if let External::Memory(mem) = e.external() { Some(mem) } else { None }) .unwrap() - .limits() - .clone(); - let code = module.to_bytes().unwrap(); + .limits(); + let code = module.into_bytes().unwrap(); let hash = T::Hashing::hash(&code); let memory = ImportedMemory { min_pages: limits.initial(), max_pages: limits.maximum().unwrap() }; - Self { code, hash, memory: Some(memory) } + Self { code: code.into(), hash, memory: Some(memory) } } /// Creates a wasm module with an empty `call` and `deploy` function and nothing else. @@ -305,7 +314,8 @@ where /// Creates a wasm module of `target_bytes` size. Used to benchmark the performance of /// `instantiate_with_code` for different sizes of wasm modules. The generated module maximizes /// instrumentation runtime by nesting blocks as deeply as possible given the byte budget. - pub fn sized(target_bytes: u32) -> Self { + /// `code_location`: Whether to place the code into `deploy` or `call`. + pub fn sized(target_bytes: u32, code_location: Location) -> Self { use self::elements::Instruction::{End, I32Const, If, Return}; // Base size of a contract is 63 bytes and each expansion adds 6 bytes. // We do one expansion less to account for the code section and function body @@ -314,23 +324,25 @@ where // because of the maximum code size that is enforced by `instantiate_with_code`. let expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1); const EXPANSION: [Instruction; 4] = [I32Const(0), If(BlockType::NoResult), Return, End]; - ModuleDefinition { - call_body: Some(body::repeated(expansions, &EXPANSION)), - memory: Some(ImportedMemory::max::()), - ..Default::default() + let mut module = + ModuleDefinition { memory: Some(ImportedMemory::max::()), ..Default::default() }; + let body = Some(body::repeated(expansions, &EXPANSION)); + match code_location { + Location::Call => module.call_body = body, + Location::Deploy => module.deploy_body = body, } - .into() + module.into() } /// Creates a wasm module that calls the imported function named `getter_name` `repeat` /// times. The imported function is expected to have the "getter signature" of /// (out_ptr: u32, len_ptr: u32) -> (). - pub fn getter(getter_name: &'static str, repeat: u32) -> Self { + pub fn getter(module_name: &'static str, getter_name: &'static str, repeat: u32) -> Self { let pages = max_pages::(); ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", + module: module_name, name: getter_name, params: vec![ValueType::I32, ValueType::I32], return_type: None, @@ -382,16 +394,6 @@ where .into() } - /// Creates a memory instance for use in a sandbox with dimensions declared in this module - /// and adds it to `env`. A reference to that memory is returned so that it can be used to - /// access the memory contents from the supervisor. - pub fn add_memory(&self, env: &mut EnvironmentDefinitionBuilder) -> Option { - let memory = if let Some(memory) = &self.memory { memory } else { return None }; - let memory = Memory::new(memory.min_pages, Some(memory.max_pages)).unwrap(); - env.add_memory("env", "memory", memory.clone()); - Some(memory) - } - pub fn unary_instr(instr: Instruction, repeat: u32) -> Self { use body::DynInstr::{RandomI64Repeated, Regular}; ModuleDefinition { @@ -492,36 +494,30 @@ pub mod body { vec![Instruction::I32Const(current as i32)] }, DynInstr::RandomUnaligned(low, high) => { - let unaligned = rng.gen_range(*low, *high) | 1; + let unaligned = rng.gen_range(*low..*high) | 1; vec![Instruction::I32Const(unaligned as i32)] }, DynInstr::RandomI32(low, high) => { - vec![Instruction::I32Const(rng.gen_range(*low, *high))] + vec![Instruction::I32Const(rng.gen_range(*low..*high))] }, - DynInstr::RandomI32Repeated(num) => (&mut rng) - .sample_iter(Standard) - .take(*num) - .map(|val| Instruction::I32Const(val)) - .collect(), - DynInstr::RandomI64Repeated(num) => (&mut rng) - .sample_iter(Standard) - .take(*num) - .map(|val| Instruction::I64Const(val)) - .collect(), + DynInstr::RandomI32Repeated(num) => + (&mut rng).sample_iter(Standard).take(*num).map(Instruction::I32Const).collect(), + DynInstr::RandomI64Repeated(num) => + (&mut rng).sample_iter(Standard).take(*num).map(Instruction::I64Const).collect(), DynInstr::RandomGetLocal(low, high) => { - vec![Instruction::GetLocal(rng.gen_range(*low, *high))] + vec![Instruction::GetLocal(rng.gen_range(*low..*high))] }, DynInstr::RandomSetLocal(low, high) => { - vec![Instruction::SetLocal(rng.gen_range(*low, *high))] + vec![Instruction::SetLocal(rng.gen_range(*low..*high))] }, DynInstr::RandomTeeLocal(low, high) => { - vec![Instruction::TeeLocal(rng.gen_range(*low, *high))] + vec![Instruction::TeeLocal(rng.gen_range(*low..*high))] }, DynInstr::RandomGetGlobal(low, high) => { - vec![Instruction::GetGlobal(rng.gen_range(*low, *high))] + vec![Instruction::GetGlobal(rng.gen_range(*low..*high))] }, DynInstr::RandomSetGlobal(low, high) => { - vec![Instruction::SetGlobal(rng.gen_range(*low, *high))] + vec![Instruction::SetGlobal(rng.gen_range(*low..*high))] }, }) .chain(sp_std::iter::once(Instruction::End)) @@ -547,11 +543,15 @@ where fn inject_gas_metering(module: Module) -> Module { let schedule = T::Schedule::get(); - let gas_rules = schedule.rules(&module); - pwasm_utils::inject_gas_counter(module, &gas_rules, "seal0").unwrap() + let gas_rules = schedule.rules(&module, Determinism::Deterministic); + let backend = gas_metering::host_function::Injector::new("seal0", "gas"); + gas_metering::inject(module, backend, &gas_rules).unwrap() } fn inject_stack_metering(module: Module) -> Module { - let height = T::Schedule::get().limits.stack_height; - pwasm_utils::stack_height::inject_limiter(module, height).unwrap() + if let Some(height) = T::Schedule::get().limits.stack_height { + wasm_instrument::inject_stack_limiter(module, height).unwrap() + } else { + module + } } diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index db657e618322e..6b8701ac84d96 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,26 +25,27 @@ mod sandbox; use self::{ code::{ body::{self, DynInstr::*}, - DataSegment, ImportedFunction, ImportedMemory, ModuleDefinition, WasmModule, + DataSegment, ImportedFunction, ImportedMemory, Location, ModuleDefinition, WasmModule, }, sandbox::Sandbox, }; use crate::{ - exec::{AccountIdOf, StorageKey}, + exec::{AccountIdOf, FixSizedKey, VarSizedKey}, schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE}, storage::Storage, + wasm::CallFlags, Pallet as Contracts, *, }; -use codec::Encode; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +use codec::{Encode, MaxEncodedLen}; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; use frame_support::weights::Weight; use frame_system::RawOrigin; -use pwasm_utils::parity_wasm::elements::{BlockType, BrTableData, Instruction, ValueType}; use sp_runtime::{ traits::{Bounded, Hash}, Perbill, }; -use sp_std::{convert::TryInto, default::Default, vec, vec::Vec}; +use sp_std::prelude::*; +use wasm_instrument::parity_wasm::elements::{BlockType, BrTableData, Instruction, ValueType}; /// How many batches we do per API benchmark. const API_BENCHMARK_BATCHES: u32 = 20; @@ -56,14 +57,15 @@ const INSTR_BENCHMARK_BATCHES: u32 = 50; struct Contract { caller: T::AccountId, account_id: T::AccountId, - addr: ::Source, - endowment: BalanceOf, + addr: AccountIdLookupOf, + value: BalanceOf, } impl Contract where T: Config, T::AccountId: UncheckedFrom + AsRef<[u8]>, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, { /// Create new contract and use a default account id as instantiator. fn new(module: WasmModule, data: Vec) -> Result, &'static str> { @@ -85,27 +87,24 @@ where module: WasmModule, data: Vec, ) -> Result, &'static str> { - let endowment = contract_funding::(); + let value = Pallet::::min_balance(); T::Currency::make_free_balance_be(&caller, caller_funding::()); let salt = vec![0xff]; let addr = Contracts::::contract_address(&caller, &module.hash, &salt); - Contracts::::store_code_raw(module.code)?; + Contracts::::store_code_raw(module.code, caller.clone())?; Contracts::::instantiate( RawOrigin::Signed(caller.clone()).into(), - endowment, - Weight::max_value(), + value, + Weight::MAX, + None, module.hash, data, salt, )?; - let result = Contract { - caller, - account_id: addr.clone(), - addr: T::Lookup::unlookup(addr), - endowment, - }; + let result = + Contract { caller, account_id: addr.clone(), addr: T::Lookup::unlookup(addr), value }; ContractInfoOf::::insert(&result.account_id, result.info()?); @@ -133,13 +132,19 @@ where } /// Store the supplied storage items into this contracts storage. - fn store(&self, items: &Vec<(StorageKey, Vec)>) -> Result<(), &'static str> { - let mut info = self.info()?; + fn store(&self, items: &Vec<(FixSizedKey, Vec)>) -> Result<(), &'static str> { + let info = self.info()?; for item in items { - Storage::::write(&mut info, &item.0, Some(item.1.clone())) - .map_err(|_| "Failed to write storage to restoration dest")?; + Storage::::write( + &info.trie_id, + &item.0 as &FixSizedKey, + Some(item.1.clone()), + None, + false, + ) + .map_err(|_| "Failed to write storage to restoration dest")?; } - >::insert(&self.account_id, info.clone()); + >::insert(&self.account_id, info); Ok(()) } @@ -152,6 +157,25 @@ where fn info(&self) -> Result, &'static str> { Self::address_info(&self.account_id) } + + /// Set the balance of the contract to the supplied amount. + fn set_balance(&self, balance: BalanceOf) { + T::Currency::make_free_balance_be(&self.account_id, balance); + } + + /// Returns `true` iff all storage entries related to code storage exist. + fn code_exists(hash: &CodeHash) -> bool { + >::contains_key(hash) && + >::contains_key(&hash) && + >::contains_key(&hash) + } + + /// Returns `true` iff no storage entry related to code storage exist. + fn code_removed(hash: &CodeHash) -> bool { + !>::contains_key(hash) && + !>::contains_key(&hash) && + !>::contains_key(&hash) + } } /// The funding that each account that either calls or instantiates contracts is funded with. @@ -159,11 +183,6 @@ fn caller_funding() -> BalanceOf { BalanceOf::::max_value() / 2u32.into() } -/// The funding used for contracts. It is less than `caller_funding` in purpose. -fn contract_funding() -> BalanceOf { - caller_funding::().saturating_sub(T::Currency::minimum_balance() * 100u32.into()) -} - /// Load the specified contract file from disk by including it into the runtime. /// /// We need to load a different version of ink! contracts when the benchmark is run as @@ -186,11 +205,12 @@ benchmarks! { where_clause { where T::AccountId: UncheckedFrom, T::AccountId: AsRef<[u8]>, + as codec::HasCompact>::Type: Clone + Eq + PartialEq + sp_std::fmt::Debug + scale_info::TypeInfo + codec::Encode, } - // The base weight without any actual work performed apart from the setup costs. - on_initialize {}: { - Storage::::process_deletion_queue_batch(Weight::max_value()) + // The base weight consumed on processing contracts deletion queue. + on_process_deletion_queue_batch {}: { + Storage::::process_deletion_queue_batch(Weight::MAX) } #[skip_meta] @@ -199,7 +219,7 @@ benchmarks! { let instance = Contract::::with_storage(WasmModule::dummy(), k, T::Schedule::get().limits.payload_len)?; Storage::::queue_trie_for_deletion(&instance.info()?)?; }: { - Storage::::process_deletion_queue_batch(Weight::max_value()) + Storage::::process_deletion_queue_batch(Weight::MAX) } on_initialize_per_queue_item { @@ -210,45 +230,45 @@ benchmarks! { ContractInfoOf::::remove(instance.account_id); } }: { - Storage::::process_deletion_queue_batch(Weight::max_value()) + Storage::::process_deletion_queue_batch(Weight::MAX) } // This benchmarks the additional weight that is charged when a contract is executed the // first time after a new schedule was deployed: For every new schedule a contract needs // to re-run the instrumentation once. - instrument { - let c in 0 .. T::Schedule::get().limits.code_len / 1024; - let WasmModule { code, hash, .. } = WasmModule::::sized(c * 1024); - Contracts::::store_code_raw(code)?; - let mut module = PrefabWasmModule::from_storage_noinstr(hash)?; + reinstrument { + let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); + let WasmModule { code, hash, .. } = WasmModule::::sized(c, Location::Call); + Contracts::::store_code_raw(code, whitelisted_caller())?; let schedule = T::Schedule::get(); + let mut gas_meter = GasMeter::new(Weight::MAX); + let mut module = PrefabWasmModule::from_storage(hash, &schedule, &mut gas_meter)?; }: { Contracts::::reinstrument_module(&mut module, &schedule)?; } - // The weight of loading and decoding of a contract's code per kilobyte. - code_load { - let c in 0 .. T::Schedule::get().limits.code_len / 1024; - let WasmModule { code, hash, .. } = WasmModule::::dummy_with_bytes(c * 1024); - Contracts::::store_code_raw(code)?; - }: { - >::from_storage_noinstr(hash)?; - } - - // The weight of changing the refcount of a contract's code per kilobyte. - code_refcount { - let c in 0 .. T::Schedule::get().limits.code_len / 1024; - let WasmModule { code, hash, .. } = WasmModule::::dummy_with_bytes(c * 1024); - Contracts::::store_code_raw(code)?; - let mut gas_meter = GasMeter::new(Weight::max_value()); - }: { - >::add_user(hash, &mut gas_meter)?; - } + // This benchmarks the overhead of loading a code of size `c` byte from storage and into + // the sandbox. This does **not** include the actual execution for which the gas meter + // is responsible. This is achieved by generating all code to the `deploy` function + // which is in the wasm module but not executed on `call`. + // The results are supposed to be used as `call_with_code_kb(c) - call_with_code_kb(0)`. + call_with_code_per_byte { + let c in 0 .. T::MaxCodeLen::get(); + let instance = Contract::::with_caller( + whitelisted_caller(), WasmModule::sized(c, Location::Deploy), vec![], + )?; + let value = Pallet::::min_balance(); + let origin = RawOrigin::Signed(instance.caller.clone()); + let callee = instance.addr; + }: call(origin, callee, value, Weight::MAX, None, vec![]) // This constructs a contract that is maximal expensive to instrument. // It creates a maximum number of metering blocks per byte. // The size of the salt influences the runtime because is is hashed in order to - // determine the contract address. + // determine the contract address. All code is generated to the `call` function so that + // we don't benchmark the actual execution of this code but merely what it takes to load + // a code of that size into the sandbox. + // // `c`: Size of the code in kilobytes. // `s`: Size of the salt in kilobytes. // @@ -257,21 +277,27 @@ benchmarks! { // We cannot let `c` grow to the maximum code size because the code is not allowed // to be larger than the maximum size **after instrumentation**. instantiate_with_code { - let c in 0 .. Perbill::from_percent(50).mul_ceil(T::Schedule::get().limits.code_len / 1024); - let s in 0 .. code::max_pages::() * 64; - let salt = vec![42u8; (s * 1024) as usize]; - let endowment = contract_funding::() / 3u32.into(); + let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); + let s in 0 .. code::max_pages::() * 64 * 1024; + let salt = vec![42u8; s as usize]; + let value = Pallet::::min_balance(); let caller = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, caller_funding::()); - let WasmModule { code, hash, .. } = WasmModule::::sized(c * 1024); + let WasmModule { code, hash, .. } = WasmModule::::sized(c, Location::Call); let origin = RawOrigin::Signed(caller.clone()); let addr = Contracts::::contract_address(&caller, &hash, &salt); - }: _(origin, endowment, Weight::max_value(), code, vec![], salt) + }: _(origin, value, Weight::MAX, None, code, vec![], salt) verify { - // endowment was removed from the caller - assert_eq!(T::Currency::free_balance(&caller), caller_funding::() - endowment); - // contract has the full endowment - assert_eq!(T::Currency::free_balance(&addr), endowment); + // the contract itself does not trigger any reserves + let deposit = T::Currency::reserved_balance(&addr); + // uploading the code reserves some balance in the callers account + let code_deposit = T::Currency::reserved_balance(&caller); + assert_eq!( + T::Currency::free_balance(&caller), + caller_funding::() - value - deposit - code_deposit, + ); + // contract has the full value + assert_eq!(T::Currency::free_balance(&addr), value); // instantiate should leave a contract Contract::::address_info(&addr)?; } @@ -279,21 +305,23 @@ benchmarks! { // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. // `s`: Size of the salt in kilobytes. instantiate { - let s in 0 .. code::max_pages::() * 64; - let salt = vec![42u8; (s * 1024) as usize]; - let endowment = contract_funding::() / 3u32.into(); + let s in 0 .. code::max_pages::() * 64 * 1024; + let salt = vec![42u8; s as usize]; + let value = Pallet::::min_balance(); let caller = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, caller_funding::()); let WasmModule { code, hash, .. } = WasmModule::::dummy(); let origin = RawOrigin::Signed(caller.clone()); let addr = Contracts::::contract_address(&caller, &hash, &salt); - Contracts::::store_code_raw(code)?; - }: _(origin, endowment, Weight::max_value(), hash, vec![], salt) + Contracts::::store_code_raw(code, caller.clone())?; + }: _(origin, value, Weight::MAX, None, hash, vec![], salt) verify { - // endowment was removed from the caller - assert_eq!(T::Currency::free_balance(&caller), caller_funding::() - endowment); - // contract has the full endowment - assert_eq!(T::Currency::free_balance(&addr), endowment); + // the contract itself does not trigger any reserves + let deposit = T::Currency::reserved_balance(&addr); + // value was removed from the caller + assert_eq!(T::Currency::free_balance(&caller), caller_funding::() - value - deposit); + // contract has the full value + assert_eq!(T::Currency::free_balance(&addr), value); // instantiate should leave a contract Contract::::address_info(&addr)?; } @@ -302,22 +330,26 @@ benchmarks! { // The size of the data has no influence on the costs of this extrinsic as long as the contract // won't call `seal_input` in its constructor to copy the data to contract memory. // The dummy contract used here does not do this. The costs for the data copy is billed as - // part of `seal_input`. + // part of `seal_input`. The costs for invoking a contract of a specific size are not part + // of this benchmark because we cannot know the size of the contract when issuing a call + // transaction. See `invoke_per_code_kb` for this. call { let data = vec![42u8; 1024]; let instance = Contract::::with_caller( whitelisted_caller(), WasmModule::dummy(), vec![], )?; - let value = T::Currency::minimum_balance(); + let value = Pallet::::min_balance(); let origin = RawOrigin::Signed(instance.caller.clone()); let callee = instance.addr.clone(); let before = T::Currency::free_balance(&instance.account_id); - }: _(origin, callee, value, Weight::max_value(), data) + }: _(origin, callee, value, Weight::MAX, None, data) verify { - // endowment and value transfered via call should be removed from the caller + // the contract itself does not trigger any reserves + let deposit = T::Currency::reserved_balance(&instance.account_id); + // value and value transfered via call should be removed from the caller assert_eq!( T::Currency::free_balance(&instance.caller), - caller_funding::() - instance.endowment - value, + caller_funding::() - instance.value - value - deposit, ); // contract should have received the value assert_eq!(T::Currency::free_balance(&instance.account_id), before + value); @@ -325,77 +357,233 @@ benchmarks! { instance.info()?; } + // This constructs a contract that is maximal expensive to instrument. + // It creates a maximum number of metering blocks per byte. + // `c`: Size of the code in kilobytes. + // + // # Note + // + // We cannot let `c` grow to the maximum code size because the code is not allowed + // to be larger than the maximum size **after instrumentation**. + upload_code { + let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); + let caller = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, caller_funding::()); + let WasmModule { code, hash, .. } = WasmModule::::sized(c, Location::Call); + let origin = RawOrigin::Signed(caller.clone()); + }: _(origin, code, None, Determinism::Deterministic) + verify { + // uploading the code reserves some balance in the callers account + assert!(T::Currency::reserved_balance(&caller) > 0u32.into()); + assert!(>::code_exists(&hash)); + } + + // Removing code does not depend on the size of the contract because all the information + // needed to verify the removal claim (refcount, owner) is stored in a separate storage + // item (`OwnerInfoOf`). + remove_code { + let caller = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, caller_funding::()); + let WasmModule { code, hash, .. } = WasmModule::::dummy(); + let origin = RawOrigin::Signed(caller.clone()); + let uploaded = >::bare_upload_code(caller.clone(), code, None, Determinism::Deterministic)?; + assert_eq!(uploaded.code_hash, hash); + assert_eq!(uploaded.deposit, T::Currency::reserved_balance(&caller)); + assert!(>::code_exists(&hash)); + }: _(origin, hash) + verify { + // removing the code should have unreserved the deposit + assert_eq!(T::Currency::reserved_balance(&caller), 0u32.into()); + assert!(>::code_removed(&hash)); + } + + set_code { + let instance = >::with_caller( + whitelisted_caller(), WasmModule::dummy(), vec![], + )?; + // we just add some bytes so that the code hash is different + let WasmModule { code, hash, .. } = >::dummy_with_bytes(128); + >::store_code_raw(code, instance.caller.clone())?; + let callee = instance.addr.clone(); + assert_ne!(instance.info()?.code_hash, hash); + }: _(RawOrigin::Root, callee, hash) + verify { + assert_eq!(instance.info()?.code_hash, hash); + } + seal_caller { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_caller", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_caller", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_address { + seal_is_contract { + let r in 0 .. API_BENCHMARK_BATCHES; + let accounts = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .map(|n| account::("account", n, 0)) + .collect::>(); + let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); + let accounts_bytes = accounts.iter().flat_map(|a| a.encode()).collect::>(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "seal_is_contract", + params: vec![ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: accounts_bytes + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, account_len as u32), // address_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + // every account would be a contract (worst case) + for acc in accounts.iter() { + >::insert(acc, info.clone()); + } + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + seal_code_hash { + let r in 0 .. API_BENCHMARK_BATCHES; + let accounts = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .map(|n| account::("account", n, 0)) + .collect::>(); + let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); + let accounts_bytes = accounts.iter().flat_map(|a| a.encode()).collect::>(); + let accounts_len = accounts_bytes.len(); + let pages = code::max_pages::(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "seal_code_hash", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: 32u32.to_le_bytes().to_vec(), // output length + }, + DataSegment { + offset: 36, + value: accounts_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(36, account_len as u32), // address_ptr + Regular(Instruction::I32Const(4)), // ptr to output data + Regular(Instruction::I32Const(0)), // ptr to output length + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + // every account would be a contract (worst case) + for acc in accounts.iter() { + >::insert(acc, info.clone()); + } + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + seal_own_code_hash { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_address", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_own_code_hash", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_gas_left { + seal_caller_is_origin { + let r in 0 .. API_BENCHMARK_BATCHES; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "seal_caller_is_origin", + params: vec![], + return_type: Some(ValueType::I32), + }], + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::Call(0), + Instruction::Drop, + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + seal_address { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_gas_left", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_address", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_balance { + seal_gas_left { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_balance", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_gas_left", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_value_transferred { + seal_balance { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_value_transferred", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_balance", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_minimum_balance { + seal_value_transferred { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_minimum_balance", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_value_transferred", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_tombstone_deposit { + seal_minimum_balance { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_tombstone_deposit", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_minimum_balance", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) seal_block_number { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_block_number", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_block_number", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) seal_now { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_now", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_now", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) seal_weight_to_fee { let r in 0 .. API_BENCHMARK_BATCHES; @@ -422,7 +610,7 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) seal_gas { let r in 0 .. API_BENCHMARK_BATCHES; @@ -430,11 +618,11 @@ benchmarks! { imported_functions: vec![ImportedFunction { module: "seal0", name: "gas", - params: vec![ValueType::I32], + params: vec![ValueType::I64], return_type: None, }], call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ - Instruction::I32Const(42), + Instruction::I64Const(42), Instruction::Call(0), ])), .. Default::default() @@ -442,7 +630,7 @@ benchmarks! { let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) seal_input { let r in 0 .. API_BENCHMARK_BATCHES; @@ -469,7 +657,7 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) seal_input_per_kb { let n in 0 .. code::max_pages::() * 64; @@ -499,7 +687,7 @@ benchmarks! { let instance = Contract::::new(code, vec![])?; let data = vec![42u8; (n * 1024).min(buffer_size) as usize]; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), data) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, data) // We cannot call `seal_return` multiple times. Therefore our weight determination is not // as precise as with other APIs. Because this function can only be called once per @@ -524,7 +712,7 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) seal_return_per_kb { let n in 0 .. code::max_pages::() * 64; @@ -547,7 +735,7 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // The same argument as for `seal_return` is true here. seal_terminate { @@ -579,12 +767,13 @@ benchmarks! { let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into()); - assert_eq!(T::Currency::total_balance(&instance.account_id), contract_funding::()); - }: call(origin, instance.addr.clone(), 0u32.into(), Weight::max_value(), vec![]) + assert_eq!(T::Currency::free_balance(&instance.account_id), Pallet::::min_balance()); + assert_ne!(T::Currency::reserved_balance(&instance.account_id), 0u32.into()); + }: call(origin, instance.addr.clone(), 0u32.into(), Weight::MAX, None, vec![]) verify { if r > 0 { assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into()); - assert_eq!(T::Currency::total_balance(&beneficiary), contract_funding::()); + assert_eq!(T::Currency::total_balance(&beneficiary), Pallet::::min_balance()); } } @@ -621,7 +810,7 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Overhead of calling the function without any topic. // We benchmark for the worst case (largest event). @@ -646,7 +835,7 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Benchmark the overhead that topics generate. // `t`: Number of topics @@ -684,7 +873,7 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // The size of the supplied message does not influence the weight because as it is never // processed during on-chain execution: It is only ever read during debugging which happens @@ -710,93 +899,429 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. - // The contract is a bit more complex because I needs to use different keys in order + // The contract is a bit more complex because it needs to use different keys in order // to generate unique storage accesses. However, it is still dominated by the storage - // accesses. + // accesses. We store all the keys that we are about to write at beforehand + // because re-writing at an existing key is always more expensive than writing + // it at a virgin key. #[skip_meta] seal_set_storage { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_BATCHES/2; + let max_key_len = T::MaxStorageKeyLen::get(); let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) - .flat_map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = sp_std::mem::size_of::<::Output>(); + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); + let keys_bytes = keys.iter().flatten().cloned().collect::>(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", - name: "seal_set_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], - return_type: None, + module: "seal2", + name: "set_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), }], data_segments: vec![ DataSegment { offset: 0, - value: keys, + value: keys_bytes, }, ], call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const(0)), // value_ptr Regular(Instruction::I32Const(0)), // value_len Regular(Instruction::Call(0)), + Regular(Instruction::Drop), ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + for key in keys { + Storage::::write( + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_set_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 1024; - let key = T::Hashing::hash_of(&1u32).as_ref().to_vec(); - let key_len = key.len(); + #[skip_meta] + seal_set_storage_per_new_kb { + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", - name: "seal_set_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], - return_type: None, + module: "seal2", + name: "set_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), }], data_segments: vec![ DataSegment { offset: 0, - value: key, + value: key_bytes, }, ], - call_body: Some(body::repeated(API_BENCHMARK_BATCH_SIZE, &[ - Instruction::I32Const(0), // key_ptr - Instruction::I32Const(0), // value_ptr - Instruction::I32Const((n * 1024) as i32), // value_len - Instruction::Call(0), + call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len + Regular(Instruction::I32Const(0)), // value_ptr + Regular(Instruction::I32Const((n * 2048) as i32)), // value_len increments by 2kb up to max payload_len + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + for key in keys { + Storage::::write( + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + #[skip_meta] + seal_set_storage_per_old_kb { + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal2", + name: "set_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key_bytes, + }, + ], + call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len + Regular(Instruction::I32Const(0)), // value_ptr + Regular(Instruction::I32Const(0)), // value_len is 0 as testing vs pre-existing value len + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + for key in keys { + Storage::::write( + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + // Similar to seal_set_storage. We store all the keys that we are about to + // delete beforehand in order to prevent any optimizations that could occur when + // deleting a non existing key. We generate keys of a maximum length, and have to + // reduce batch size in order to make resulting contract code size less than MaxCodeLen. + #[skip_meta] + seal_clear_storage { + let r in 0 .. API_BENCHMARK_BATCHES/2; + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal1", + name: "clear_storage", + params: vec![ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + for key in keys { + Storage::::write( + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + >::insert(&instance.account_id, info); + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + #[skip_meta] + seal_clear_storage_per_kb { + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal1", + name: "clear_storage", + params: vec![ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key_bytes, + }, + ], + call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + for key in keys { + Storage::::write( + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + // We make sure that all storage accesses are to unique keys. + #[skip_meta] + seal_get_storage { + let r in 0 .. API_BENCHMARK_BATCHES/2; + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key_bytes_len = key_bytes.len(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal1", + name: "get_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key_bytes, + }, + DataSegment { + offset: key_bytes_len as u32, + value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len + Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr + Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + for key in keys { + Storage::::write( + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + >::insert(&instance.account_id, info); + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + #[skip_meta] + seal_get_storage_per_kb { + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key_bytes_len = key_bytes.len(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal1", + name: "get_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key_bytes, + }, + DataSegment { + offset: key_bytes_len as u32, + value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), + }, + ], + call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len + Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr + Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + for key in keys { + Storage::::write( + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + >::insert(&instance.account_id, info); + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + // We make sure that all storage accesses are to unique keys. + #[skip_meta] + seal_contains_storage { + let r in 0 .. API_BENCHMARK_BATCHES/2; + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key_bytes_len = key_bytes.len(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal1", + name: "contains_storage", + params: vec![ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + for key in keys { + Storage::::write( + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // Similar to seal_set_storage. However, we store all the keys that we are about to - // delete beforehand in order to prevent any optimizations that could occur when - // deleting a non existing key. #[skip_meta] - seal_clear_storage { - let r in 0 .. API_BENCHMARK_BATCHES; - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); + seal_contains_storage_per_kb { + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); - let key_len = sp_std::mem::size_of::<::Output>(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", - name: "seal_clear_storage", - params: vec![ValueType::I32], - return_type: None, + module: "seal1", + name: "contains_storage", + params: vec![ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), }], data_segments: vec![ DataSegment { @@ -804,42 +1329,46 @@ benchmarks! { value: key_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), + call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::Call(0)), + Regular(Instruction::Drop), ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; - let mut info = instance.info()?; + let info = instance.info()?; for key in keys { Storage::::write( - &mut info, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, - Some(vec![42; T::Schedule::get().limits.payload_len as usize]) + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len + None, + false, ) .map_err(|_| "Failed to write to storage during setup.")?; } - >::insert(&instance.account_id, info.clone()); + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // We make sure that all storage accesses are to unique keys. #[skip_meta] - seal_get_storage { - let r in 0 .. API_BENCHMARK_BATCHES; + seal_take_storage { + let r in 0 .. API_BENCHMARK_BATCHES/2; + let max_key_len = T::MaxStorageKeyLen::get(); let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = sp_std::mem::size_of::<::Output>(); + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let key_bytes_len = key_bytes.len(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal0", - name: "seal_get_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + name: "take_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -847,9 +1376,14 @@ benchmarks! { offset: 0, value: key_bytes, }, + DataSegment { + offset: key_bytes_len as u32, + value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), + }, ], call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr Regular(Instruction::Call(0)), @@ -858,62 +1392,74 @@ benchmarks! { .. Default::default() }); let instance = Contract::::new(code, vec![])?; - let mut info = instance.info()?; + let info = instance.info()?; for key in keys { Storage::::write( - &mut info, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, - Some(vec![]) + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![]), + None, + false, ) .map_err(|_| "Failed to write to storage during setup.")?; } - >::insert(&instance.account_id, info.clone()); + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_get_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 1024; - let key = T::Hashing::hash_of(&1u32).as_ref().to_vec(); - let key_len = key.len(); + #[skip_meta] + seal_take_storage_per_kb { + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key_bytes_len = key_bytes.len(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal0", - name: "seal_get_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + name: "take_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ DataSegment { offset: 0, - value: key.clone(), + value: key_bytes, }, DataSegment { - offset: key_len as u32, + offset: key_bytes_len as u32, value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), }, ], - call_body: Some(body::repeated(API_BENCHMARK_BATCH_SIZE, &[ - // call at key_ptr - Instruction::I32Const(0), // key_ptr - Instruction::I32Const((key_len + 4) as i32), // out_ptr - Instruction::I32Const(key_len as i32), // out_len_ptr - Instruction::Call(0), - Instruction::Drop, + call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len + Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr + Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; - let mut info = instance.info()?; - Storage::::write( - &mut info, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 1024) as usize]) - ) - .map_err(|_| "Failed to write to storage during setup.")?; - >::insert(&instance.account_id, info.clone()); + let info = instance.info()?; + for key in keys { + Storage::::write( + &info.trie_id, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // We transfer to unique accounts. seal_transfer { @@ -923,7 +1469,7 @@ benchmarks! { .collect::>(); let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); let account_bytes = accounts.iter().flat_map(|x| x.encode()).collect(); - let value = Contracts::::subsistence_threshold(); + let value = Pallet::::min_balance(); assert!(value > 0u32.into()); let value_bytes = value.encode(); let value_len = value_bytes.len(); @@ -956,11 +1502,12 @@ benchmarks! { .. Default::default() }); let instance = Contract::::new(code, vec![])?; + instance.set_balance(value * (r * API_BENCHMARK_BATCH_SIZE + 1).into()); let origin = RawOrigin::Signed(instance.caller.clone()); for account in &accounts { assert_eq!(T::Currency::total_balance(account), 0u32.into()); } - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) verify { for account in &accounts { assert_eq!(T::Currency::total_balance(account), value); @@ -1015,7 +1562,7 @@ benchmarks! { Regular(Instruction::I32Const(value_len as i32)), // value_len Regular(Instruction::I32Const(0)), // input_data_ptr Regular(Instruction::I32Const(0)), // input_data_len - Regular(Instruction::I32Const(u32::max_value() as i32)), // output_ptr + Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr Regular(Instruction::I32Const(0)), // output_len_ptr Regular(Instruction::Call(0)), Regular(Instruction::Drop), @@ -1024,46 +1571,75 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_call_per_transfer_input_output_kb { - let t in 0 .. 1; - let i in 0 .. code::max_pages::() * 64; - let o in 0 .. (code::max_pages::() - 1) * 64; - let callee_code = WasmModule::::from(ModuleDefinition { + seal_delegate_call { + let r in 0 .. API_BENCHMARK_BATCHES; + let hashes = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|i| { + let code = WasmModule::::dummy_with_bytes(i); + Contracts::::store_code_raw(code.code, whitelisted_caller())?; + Ok(code.hash) + }) + .collect::, &'static str>>()?; + let hash_len = hashes.get(0).map(|x| x.encode().len()).unwrap_or(0); + let hashes_bytes = hashes.iter().flat_map(|x| x.encode()).collect::>(); + let hashes_len = hashes_bytes.len(); + let hashes_offset = 0; + + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal0", - name: "seal_return", + name: "seal_delegate_call", params: vec![ ValueType::I32, ValueType::I32, ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, ], - return_type: None, + return_type: Some(ValueType::I32), }], - call_body: Some(body::plain(vec![ - Instruction::I32Const(0), // flags - Instruction::I32Const(0), // data_ptr - Instruction::I32Const((o * 1024) as i32), // data_len - Instruction::Call(0), - Instruction::End, + data_segments: vec![ + DataSegment { + offset: hashes_offset as u32, + value: hashes_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Regular(Instruction::I32Const(0)), // flags + Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr + Regular(Instruction::I32Const(0)), // input_data_ptr + Regular(Instruction::I32Const(0)), // input_data_len + Regular(Instruction::I32Const(u32::max_value() as i32)), // output_ptr + Regular(Instruction::I32Const(0)), // output_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), ])), .. Default::default() }); + let instance = Contract::::new(code, vec![])?; + let callee = instance.addr.clone(); + let origin = RawOrigin::Signed(instance.caller); + }: call(origin, callee, 0u32.into(), Weight::MAX, None, vec![]) + + seal_call_per_transfer_clone_kb { + let t in 0 .. 1; + let c in 0 .. code::max_pages::() * 64; let callees = (0..API_BENCHMARK_BATCH_SIZE) - .map(|i| Contract::with_index(i + 1, callee_code.clone(), vec![])) + .map(|i| Contract::with_index(i + 1, >::dummy(), vec![])) .collect::, _>>()?; let callee_len = callees.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect::>(); - let callees_len = callee_bytes.len(); let value: BalanceOf = t.into(); let value_bytes = value.encode(); let value_len = value_bytes.len(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", + module: "seal1", name: "seal_call", params: vec![ ValueType::I32, @@ -1074,7 +1650,6 @@ benchmarks! { ValueType::I32, ValueType::I32, ValueType::I32, - ValueType::I32, ], return_type: Some(ValueType::I32), }], @@ -1087,21 +1662,16 @@ benchmarks! { offset: value_len as u32, value: callee_bytes, }, - DataSegment { - offset: (value_len + callees_len) as u32, - value: (o * 1024).to_le_bytes().into(), - }, ], call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ + Regular(Instruction::I32Const(CallFlags::CLONE_INPUT.bits() as i32)), // flags Counter(value_len as u32, callee_len as u32), // callee_ptr - Regular(Instruction::I32Const(callee_len as i32)), // callee_len Regular(Instruction::I64Const(0)), // gas Regular(Instruction::I32Const(0)), // value_ptr - Regular(Instruction::I32Const(value_len as i32)), // value_len Regular(Instruction::I32Const(0)), // input_data_ptr - Regular(Instruction::I32Const((i * 1024) as i32)), // input_data_len - Regular(Instruction::I32Const((value_len + callees_len + 4) as i32)), // output_ptr - Regular(Instruction::I32Const((value_len + callees_len) as i32)), // output_len_ptr + Regular(Instruction::I32Const(0)), // input_data_len + Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr + Regular(Instruction::I32Const(0)), // output_len_ptr Regular(Instruction::Call(0)), Regular(Instruction::Drop), ])), @@ -1109,9 +1679,10 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + let bytes = vec![42; (c * 1024) as usize]; + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, bytes) - // We assume that every instantiate sends at least the subsistence amount. + // We assume that every instantiate sends at least the minimum balance. seal_instantiate { let r in 0 .. API_BENCHMARK_BATCHES; let hashes = (0..r * API_BENCHMARK_BATCH_SIZE) @@ -1127,18 +1698,18 @@ benchmarks! { ])), .. Default::default() }); - Contracts::::store_code_raw(code.code)?; + Contracts::::store_code_raw(code.code, whitelisted_caller())?; Ok(code.hash) }) .collect::, &'static str>>()?; let hash_len = hashes.get(0).map(|x| x.encode().len()).unwrap_or(0); let hashes_bytes = hashes.iter().flat_map(|x| x.encode()).collect::>(); let hashes_len = hashes_bytes.len(); - let value = contract_funding::() / (r * API_BENCHMARK_BATCH_SIZE + 2).into(); + let value = Pallet::::min_balance(); assert!(value > 0u32.into()); let value_bytes = value.encode(); let value_len = value_bytes.len(); - let addr_len = sp_std::mem::size_of::(); + let addr_len = T::AccountId::max_encoded_len(); // offsets where to place static data in contract memory let value_offset = 0; @@ -1192,7 +1763,7 @@ benchmarks! { Regular(Instruction::I32Const(0)), // input_data_len Regular(Instruction::I32Const(addr_offset as i32)), // address_ptr Regular(Instruction::I32Const(addr_len_offset as i32)), // address_len_ptr - Regular(Instruction::I32Const(u32::max_value() as i32)), // output_ptr + Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr Regular(Instruction::I32Const(0)), // output_len_ptr Regular(Instruction::I32Const(0)), // salt_ptr Regular(Instruction::I32Const(0)), // salt_ptr_len @@ -1202,6 +1773,7 @@ benchmarks! { .. Default::default() }); let instance = Contract::::new(code, vec![])?; + instance.set_balance(value * (r * API_BENCHMARK_BATCH_SIZE + 1).into()); let origin = RawOrigin::Signed(instance.caller.clone()); let callee = instance.addr.clone(); let addresses = hashes @@ -1212,64 +1784,40 @@ benchmarks! { .collect::>(); for addr in &addresses { - if let Some(_) = ContractInfoOf::::get(&addr) { + if ContractInfoOf::::get(&addr).is_some() { return Err("Expected that contract does not exist at this point.".into()); } } - }: call(origin, callee, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, callee, 0u32.into(), Weight::MAX, None, vec![]) verify { for addr in &addresses { ContractInfoOf::::get(&addr) - .ok_or_else(|| "Contract should have been instantiated")?; + .ok_or("Contract should have been instantiated")?; } } - seal_instantiate_per_input_output_salt_kb { - let i in 0 .. (code::max_pages::() - 1) * 64; - let o in 0 .. (code::max_pages::() - 1) * 64; + seal_instantiate_per_transfer_salt_kb { + let t in 0 .. 1; let s in 0 .. (code::max_pages::() - 1) * 64; - let callee_code = WasmModule::::from(ModuleDefinition { - memory: Some(ImportedMemory::max::()), - imported_functions: vec![ImportedFunction { - module: "seal0", - name: "seal_return", - params: vec![ - ValueType::I32, - ValueType::I32, - ValueType::I32, - ], - return_type: None, - }], - deploy_body: Some(body::plain(vec![ - Instruction::I32Const(0), // flags - Instruction::I32Const(0), // data_ptr - Instruction::I32Const((o * 1024) as i32), // data_len - Instruction::Call(0), - Instruction::End, - ])), - .. Default::default() - }); - let hash = callee_code.hash.clone(); + let callee_code = WasmModule::::dummy(); + let hash = callee_code.hash; let hash_bytes = callee_code.hash.encode(); let hash_len = hash_bytes.len(); - Contracts::::store_code_raw(callee_code.code)?; - let inputs = (0..API_BENCHMARK_BATCH_SIZE).map(|x| x.encode()).collect::>(); - let input_len = inputs.get(0).map(|x| x.len()).unwrap_or(0); - let input_bytes = inputs.iter().cloned().flatten().collect::>(); - let inputs_len = input_bytes.len(); - let value = contract_funding::() / (API_BENCHMARK_BATCH_SIZE + 2).into(); - assert!(value > 0u32.into()); + Contracts::::store_code_raw(callee_code.code, whitelisted_caller())?; + let salts = (0..API_BENCHMARK_BATCH_SIZE).map(|x| x.encode()).collect::>(); + let salt_len = salts.get(0).map(|x| x.len()).unwrap_or(0); + let salt_bytes = salts.iter().cloned().flatten().collect::>(); + let salts_len = salt_bytes.len(); + let value: BalanceOf = t.into(); let value_bytes = value.encode(); let value_len = value_bytes.len(); - let addr_len = sp_std::mem::size_of::(); + let addr_len = T::AccountId::max_encoded_len(); // offsets where to place static data in contract memory - let input_offset = 0; - let value_offset = inputs_len; + let salt_offset = 0; + let value_offset = salts_len; let hash_offset = value_offset + value_len; let addr_len_offset = hash_offset + hash_len; - let output_len_offset = addr_len_offset + 4; - let output_offset = output_len_offset + 4; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), @@ -1295,8 +1843,8 @@ benchmarks! { }], data_segments: vec![ DataSegment { - offset: input_offset as u32, - value: input_bytes, + offset: salt_offset as u32, + value: salt_bytes, }, DataSegment { offset: value_offset as u32, @@ -1310,10 +1858,6 @@ benchmarks! { offset: addr_len_offset as u32, value: (addr_len as u32).to_le_bytes().into(), }, - DataSegment { - offset: output_len_offset as u32, - value: (o * 1024).to_le_bytes().into(), - }, ], call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ Regular(Instruction::I32Const(hash_offset as i32)), // code_hash_ptr @@ -1321,14 +1865,14 @@ benchmarks! { Regular(Instruction::I64Const(0)), // gas Regular(Instruction::I32Const(value_offset as i32)), // value_ptr Regular(Instruction::I32Const(value_len as i32)), // value_len - Counter(input_offset as u32, input_len as u32), // input_data_ptr - Regular(Instruction::I32Const((i * 1024).max(input_len as u32) as i32)), // input_data_len + Regular(Instruction::I32Const(0)), // input_data_ptr + Regular(Instruction::I32Const(0)), // input_data_len Regular(Instruction::I32Const((addr_len_offset + addr_len) as i32)), // address_ptr Regular(Instruction::I32Const(addr_len_offset as i32)), // address_len_ptr - Regular(Instruction::I32Const(output_offset as i32)), // output_ptr - Regular(Instruction::I32Const(output_len_offset as i32)), // output_len_ptr - Counter(input_offset as u32, input_len as u32), // salt_ptr - Regular(Instruction::I32Const((s * 1024).max(input_len as u32) as i32)), // salt_len + Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr + Regular(Instruction::I32Const(0)), // output_len_ptr + Counter(salt_offset as u32, salt_len as u32), // salt_ptr + Regular(Instruction::I32Const((s * 1024).max(salt_len as u32) as i32)), // salt_len Regular(Instruction::Call(0)), Regular(Instruction::I32Eqz), Regular(Instruction::If(BlockType::NoResult)), @@ -1340,17 +1884,18 @@ benchmarks! { .. Default::default() }); let instance = Contract::::new(code, vec![])?; + instance.set_balance(value * (API_BENCHMARK_BATCH_SIZE + 1).into()); let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. seal_hash_sha2_256 { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. 1; let instance = Contract::::new(WasmModule::hasher( "seal_hash_sha2_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // `n`: Input to hash in kilobytes seal_hash_sha2_256_per_kb { @@ -1359,16 +1904,16 @@ benchmarks! { "seal_hash_sha2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. seal_hash_keccak_256 { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. 1; let instance = Contract::::new(WasmModule::hasher( "seal_hash_keccak_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // `n`: Input to hash in kilobytes seal_hash_keccak_256_per_kb { @@ -1377,16 +1922,16 @@ benchmarks! { "seal_hash_keccak_256", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. seal_hash_blake2_256 { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. 1; let instance = Contract::::new(WasmModule::hasher( "seal_hash_blake2_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // `n`: Input to hash in kilobytes seal_hash_blake2_256_per_kb { @@ -1395,16 +1940,16 @@ benchmarks! { "seal_hash_blake2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. seal_hash_blake2_128 { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. 1; let instance = Contract::::new(WasmModule::hasher( "seal_hash_blake2_128", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // `n`: Input to hash in kilobytes seal_hash_blake2_128_per_kb { @@ -1413,26 +1958,20 @@ benchmarks! { "seal_hash_blake2_128", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only calling the function itself with valid arguments. // It generates different private keys and signatures for the message "Hello world". seal_ecdsa_recover { - let r in 0 .. API_BENCHMARK_BATCHES; - use rand::SeedableRng; - let mut rng = rand_pcg::Pcg32::seed_from_u64(123456); + let r in 0 .. 1; let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); + let key_type = sp_core::crypto::KeyTypeId(*b"code"); let signatures = (0..r * API_BENCHMARK_BATCH_SIZE) .map(|i| { - use secp256k1::{SecretKey, Message, sign}; - - let private_key = SecretKey::random(&mut rng); - let (signature, recovery_id) = sign(&Message::parse(&message_hash), &private_key); - let mut full_signature = [0; 65]; - full_signature[..64].copy_from_slice(&signature.serialize()); - full_signature[64] = recovery_id.serialize(); - full_signature + let pub_key = sp_io::crypto::ecdsa_generate(key_type, None); + let sig = sp_io::crypto::ecdsa_sign_prehashed(key_type, &pub_key, &message_hash).expect("Generates signature"); + AsRef::<[u8; 65]>::as_ref(&sig).to_vec() }) .collect::>(); let signatures = signatures.iter().flatten().cloned().collect::>(); @@ -1441,7 +1980,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal0", name: "seal_ecdsa_recover", params: vec![ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1467,7 +2006,157 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + // Only calling the function itself for the list of + // generated different ECDSA keys. + seal_ecdsa_to_eth_address { + let r in 0 .. 1; + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let pub_keys_bytes = (0..r * API_BENCHMARK_BATCH_SIZE) + .flat_map(|_| { + sp_io::crypto::ecdsa_generate(key_type, None).0 + }) + .collect::>(); + let pub_keys_bytes_len = pub_keys_bytes.len() as i32; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "seal_ecdsa_to_eth_address", + params: vec![ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: pub_keys_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, 33), // pub_key_ptr + Regular(Instruction::I32Const(pub_keys_bytes_len)), // out_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + seal_set_code_hash { + let r in 0 .. API_BENCHMARK_BATCHES; + let code_hashes = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|i| { + let new_code = WasmModule::::dummy_with_bytes(i); + Contracts::::store_code_raw(new_code.code, whitelisted_caller())?; + Ok(new_code.hash) + }) + .collect::, &'static str>>()?; + let code_hash_len = code_hashes.get(0).map(|x| x.encode().len()).unwrap_or(0); + let code_hashes_bytes = code_hashes.iter().flat_map(|x| x.encode()).collect::>(); + let code_hashes_len = code_hashes_bytes.len(); + + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "seal_set_code_hash", + params: vec![ + ValueType::I32, + ], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: code_hashes_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, code_hash_len as u32), // code_hash_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + seal_reentrance_count { + let r in 0 .. API_BENCHMARK_BATCHES; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "reentrance_count", + params: vec![], + return_type: Some(ValueType::I32), + }], + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::Call(0), + Instruction::Drop, + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + seal_account_reentrance_count { + let r in 0 .. API_BENCHMARK_BATCHES; + let dummy_code = WasmModule::::dummy_with_bytes(0); + let accounts = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|i| Contract::with_index(i + 1, dummy_code.clone(), vec![])) + .collect::, _>>()?; + let account_id_len = accounts.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); + let account_id_bytes = accounts.iter().flat_map(|x| x.account_id.encode()).collect(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "account_reentrance_count", + params: vec![ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: account_id_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, account_id_len as u32), // account_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + seal_instantiation_nonce { + let r in 0 .. API_BENCHMARK_BATCHES; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "instantiation_nonce", + params: vec![], + return_type: Some(ValueType::I64), + }], + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::Call(0), + Instruction::Drop, + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // We make the assumption that pushing a constant and dropping a value takes roughly // the same amount of time. We follow that `t.load` and `drop` both have the weight @@ -1760,10 +2449,28 @@ benchmarks! { sbox.invoke(); } + // w_per_local = w_bench + instr_call_per_local { + let l in 0 .. T::Schedule::get().limits.locals; + let mut aux_body = body::plain(vec![ + Instruction::End, + ]); + body::inject_locals(&mut aux_body, l); + let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { + aux_body: Some(aux_body), + call_body: Some(body::repeated(INSTR_BENCHMARK_BATCH_SIZE, &[ + Instruction::Call(2), // call aux + ])), + .. Default::default() + })); + }: { + sbox.invoke(); + } + // w_local_get = w_bench - 1 * w_param instr_local_get { let r in 0 .. INSTR_BENCHMARK_BATCHES; - let max_locals = T::Schedule::get().limits.stack_height; + let max_locals = T::Schedule::get().limits.locals; let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ RandomGetLocal(0, max_locals), Regular(Instruction::Drop), @@ -1780,7 +2487,7 @@ benchmarks! { // w_local_set = w_bench - 1 * w_param instr_local_set { let r in 0 .. INSTR_BENCHMARK_BATCHES; - let max_locals = T::Schedule::get().limits.stack_height; + let max_locals = T::Schedule::get().limits.locals; let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ RandomI64Repeated(1), RandomSetLocal(0, max_locals), @@ -1797,7 +2504,7 @@ benchmarks! { // w_local_tee = w_bench - 2 * w_param instr_local_tee { let r in 0 .. INSTR_BENCHMARK_BATCHES; - let max_locals = T::Schedule::get().limits.stack_height; + let max_locals = T::Schedule::get().limits.locals; let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ RandomI64Repeated(1), RandomTeeLocal(0, max_locals), @@ -1860,7 +2567,7 @@ benchmarks! { } // w_memory_grow = w_bench - 2 * w_param - // We can only allow allocate as much memory as it is allowed in a a contract. + // We can only allow allocate as much memory as it is allowed in a contract. // Therefore the repeat count is limited by the maximum memory any contract can have. // Using a contract with more memory will skew the benchmark because the runtime of grow // depends on how much memory is already allocated. @@ -2221,7 +2928,7 @@ benchmarks! { // configured `Schedule` during benchmark development. // It can be outputed using the following command: // cargo run --manifest-path=bin/node/cli/Cargo.toml --release \ - // --features runtime-benchmarks -- benchmark --dev --execution=native \ + // --features runtime-benchmarks -- benchmark --extra --dev --execution=native \ // -p pallet_contracts -e print_schedule --no-median-slopes --no-min-squares #[extra] print_schedule { @@ -2236,12 +2943,12 @@ benchmarks! { println!("{:#?}", Schedule::::default()); println!("###############################################"); println!("Lazy deletion throughput per block (empty queue, full queue): {}, {}", - weight_limit / weight_per_key, - (weight_limit - weight_per_queue_item * queue_depth) / weight_per_key, + weight_limit / weight_per_key.ref_time(), + (weight_limit - weight_per_queue_item * queue_depth) / weight_per_key.ref_time(), ); } #[cfg(not(feature = "std"))] - return Err("Run this bench with a native runtime in order to see the schedule.".into()); + Err("Run this bench with a native runtime in order to see the schedule.")?; }: {} // Execute one erc20 transfer using the ink! erc20 example contract. @@ -2251,7 +2958,7 @@ benchmarks! { #[extra] ink_erc20_transfer { let g in 0 .. 1; - let gas_metering = if g == 0 { false } else { true }; + let gas_metering = g != 0; let code = load_benchmark!("ink_erc20"); let data = { let new: ([u8; 4], BalanceOf) = ([0x9b, 0xae, 0x9d, 0x5e], 1000u32.into()); @@ -2274,8 +2981,10 @@ benchmarks! { instance.account_id, 0u32.into(), Weight::MAX, + None, data, false, + Determinism::Deterministic, ) .result?; } @@ -2287,7 +2996,7 @@ benchmarks! { #[extra] solang_erc20_transfer { let g in 0 .. 1; - let gas_metering = if g == 0 { false } else { true }; + let gas_metering = g != 0; let code = include_bytes!("../../benchmarks/solang_erc20.wasm"); let caller = account::("instantiator", 0, 0); let mut balance = [0u8; 32]; @@ -2320,15 +3029,17 @@ benchmarks! { instance.account_id, 0u32.into(), Weight::MAX, + None, data, false, + Determinism::Deterministic, ) .result?; } -} -impl_benchmark_test_suite!( - Contracts, - crate::tests::ExtBuilder::default().build(), - crate::tests::Test, -); + impl_benchmark_test_suite!( + Contracts, + crate::tests::ExtBuilder::default().build(), + crate::tests::Test, + ) +} diff --git a/frame/contracts/src/benchmarking/sandbox.rs b/frame/contracts/src/benchmarking/sandbox.rs index 320ac90cce64e..a35aad3500109 100644 --- a/frame/contracts/src/benchmarking/sandbox.rs +++ b/frame/contracts/src/benchmarking/sandbox.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,19 +19,20 @@ /// ! sandbox to execute the wasm code. This is because we do not need the full /// ! environment that provides the seal interface as imported functions. use super::{code::WasmModule, Config}; +use crate::wasm::{Environment, PrefabWasmModule}; use sp_core::crypto::UncheckedFrom; -use sp_sandbox::{EnvironmentDefinitionBuilder, Instance, Memory}; +use wasmi::{errors::LinkerError, Func, Linker, StackLimits, Store}; -/// Minimal execution environment without any exported functions. +/// Minimal execution environment without any imported functions. pub struct Sandbox { - instance: Instance<()>, - _memory: Option, + entry_point: Func, + store: Store<()>, } impl Sandbox { /// Invoke the `call` function of a contract code and panic on any execution error. pub fn invoke(&mut self) { - self.instance.invoke("call", &[], &mut ()).unwrap(); + self.entry_point.call(&mut self.store, &[], &mut []).unwrap(); } } @@ -43,10 +44,27 @@ where /// Creates an instance from the supplied module and supplies as much memory /// to the instance as the module declares as imported. fn from(module: &WasmModule) -> Self { - let mut env_builder = EnvironmentDefinitionBuilder::new(); - let memory = module.add_memory(&mut env_builder); - let instance = Instance::new(&module.code, &env_builder, &mut ()) - .expect("Failed to create benchmarking Sandbox instance"); - Self { instance, _memory: memory } + let memory = module + .memory + .as_ref() + .map(|mem| (mem.min_pages, mem.max_pages)) + .unwrap_or((0, 0)); + let (store, _memory, instance) = PrefabWasmModule::::instantiate::( + &module.code, + (), + memory, + StackLimits::default(), + ) + .expect("Failed to create benchmarking Sandbox instance"); + let entry_point = instance.get_export(&store, "call").unwrap().into_func().unwrap(); + Self { entry_point, store } + } +} + +struct EmptyEnv; + +impl Environment<()> for EmptyEnv { + fn define(_: &mut Store<()>, _: &mut Linker<()>, _: bool) -> Result<(), LinkerError> { + Ok(()) } } diff --git a/frame/contracts/src/chain_extension.rs b/frame/contracts/src/chain_extension.rs index 14080102933cb..3c3e9b1ef0f59 100644 --- a/frame/contracts/src/chain_extension.rs +++ b/frame/contracts/src/chain_extension.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,6 +29,22 @@ //! required for this endeavour are defined or re-exported in this module. There is an //! implementation on `()` which can be used to signal that no chain extension is available. //! +//! # Using multiple chain extensions +//! +//! Often there is a need for having multiple chain extensions. This is often the case when +//! some generally useful off-the-shelf extensions should be included. To have multiple chain +//! extensions they can be put into a tuple which is then passed to [`Config::ChainExtension`] like +//! this `type Extensions = (ExtensionA, ExtensionB)`. +//! +//! However, only extensions implementing [`RegisteredChainExtension`] can be put into a tuple. +//! This is because the [`RegisteredChainExtension::ID`] is used to decide which of those extensions +//! should be used when the contract calls a chain extensions. Extensions which are generally +//! useful should claim their `ID` with [the registry](https://github.com/paritytech/chainextension-registry) +//! so that no collisions with other vendors will occur. +//! +//! **Chain specific extensions must use the reserved `ID = 0` so that they can't be registered with +//! the registry.** +//! //! # Security //! //! The chain author alone is responsible for the security of the chain extension. @@ -68,7 +84,6 @@ pub use crate::{exec::Ext, Config}; pub use frame_system::Config as SysConfig; pub use pallet_contracts_primitives::ReturnFlags; pub use sp_core::crypto::UncheckedFrom; -pub use state::Init as InitState; /// Result that returns a [`DispatchError`] on error. pub type Result = sp_std::result::Result; @@ -78,6 +93,12 @@ pub type Result = sp_std::result::Result; /// In order to create a custom chain extension this trait must be implemented and supplied /// to the pallet contracts configuration trait as the associated type of the same name. /// Consult the [module documentation](self) for a general explanation of chain extensions. +/// +/// # Lifetime +/// +/// The extension will be [`Default`] initialized at the beginning of each call +/// (**not** per call stack) and dropped afterwards. Hence any value held inside the extension +/// can be used as a per-call scratch buffer. pub trait ChainExtension { /// Call the chain extension logic. /// @@ -86,8 +107,6 @@ pub trait ChainExtension { /// imported wasm function. /// /// # Parameters - /// - `func_id`: The first argument to `seal_call_chain_extension`. Usually used to determine - /// which function to realize. /// - `env`: Access to the remaining arguments and the execution environment. /// /// # Return @@ -95,7 +114,7 @@ pub trait ChainExtension { /// In case of `Err` the contract execution is immediately suspended and the passed error /// is returned to the caller. Otherwise the value of [`RetVal`] determines the exit /// behaviour. - fn call(func_id: u32, env: Environment) -> Result + fn call(&mut self, env: Environment) -> Result where E: Ext, ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>; @@ -112,20 +131,51 @@ pub trait ChainExtension { } } -/// Implementation that indicates that no chain extension is available. -impl ChainExtension for () { - fn call(_func_id: u32, mut _env: Environment) -> Result +/// A [`ChainExtension`] that can be composed with other extensions using a tuple. +/// +/// An extension that implements this trait can be put in a tuple in order to have multiple +/// extensions available. The tuple implementation routes requests based on the first two +/// most significant bytes of the `id` passed to `call`. +/// +/// If this extensions is to be used by multiple runtimes consider +/// [registering it](https://github.com/paritytech/chainextension-registry) to ensure that there +/// are no collisions with other vendors. +/// +/// # Note +/// +/// Currently, we support tuples of up to ten registred chain extensions. If more chain extensions +/// are needed consider opening an issue. +pub trait RegisteredChainExtension: ChainExtension { + /// The extensions globally unique identifier. + const ID: u16; +} + +#[impl_trait_for_tuples::impl_for_tuples(10)] +#[tuple_types_custom_trait_bound(RegisteredChainExtension)] +impl ChainExtension for Tuple { + fn call(&mut self, mut env: Environment) -> Result where E: Ext, ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { - // Never called since [`Self::enabled()`] is set to `false`. Because we want to - // avoid panics at all costs we supply a sensible error value here instead - // of an `unimplemented!`. + for_tuples!( + #( + if (Tuple::ID == env.ext_id()) && Tuple::enabled() { + return Tuple.call(env); + } + )* + ); Err(Error::::NoChainExtension.into()) } fn enabled() -> bool { + for_tuples!( + #( + if Tuple::enabled() { + return true; + } + )* + ); false } } @@ -147,7 +197,7 @@ pub enum RetVal { /// /// It uses [typestate programming](https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html) /// to enforce the correct usage of the parameters passed to the chain extension. -pub struct Environment<'a, 'b, E: Ext, S: state::State> { +pub struct Environment<'a, 'b, E: Ext, S: State> { /// The actual data of this type. inner: Inner<'a, 'b, E>, /// `S` is only used in the type system but never as value. @@ -155,10 +205,26 @@ pub struct Environment<'a, 'b, E: Ext, S: state::State> { } /// Functions that are available in every state of this type. -impl<'a, 'b, E: Ext, S: state::State> Environment<'a, 'b, E, S> +impl<'a, 'b, E: Ext, S: State> Environment<'a, 'b, E, S> where ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { + /// The function id within the `id` passed by a contract. + /// + /// It returns the two least significant bytes of the `id` passed by a contract as the other + /// two bytes represent the chain extension itself (the code which is calling this function). + pub fn func_id(&self) -> u16 { + (self.inner.id & 0x0000FFFF) as u16 + } + + /// The chain extension id within the `id` passed by a contract. + /// + /// It returns the two most significant bytes of the `id` passed by a contract which represent + /// the chain extension itself (the code which is calling this function). + pub fn ext_id(&self) -> u16 { + (self.inner.id >> 16) as u16 + } + /// Charge the passed `amount` of weight from the overall limit. /// /// It returns `Ok` when there the remaining weight budget is larger than the passed @@ -172,7 +238,7 @@ where /// /// Weight is synonymous with gas in substrate. pub fn charge_weight(&mut self, amount: Weight) -> Result { - self.inner.runtime.charge_gas(RuntimeCosts::ChainExtension(amount)) + self.inner.runtime.charge_gas(RuntimeCosts::ChainExtension(amount.ref_time())) } /// Adjust a previously charged amount down to its actual amount. @@ -182,7 +248,7 @@ where pub fn adjust_weight(&mut self, charged: ChargedAmount, actual_weight: Weight) { self.inner .runtime - .adjust_gas(charged, RuntimeCosts::ChainExtension(actual_weight)) + .adjust_gas(charged, RuntimeCosts::ChainExtension(actual_weight.ref_time())) } /// Grants access to the execution environment of the current contract call. @@ -197,42 +263,44 @@ where /// /// Those are the functions that determine how the arguments to the chain extensions /// should be consumed. -impl<'a, 'b, E: Ext> Environment<'a, 'b, E, state::Init> { +impl<'a, 'b, E: Ext> Environment<'a, 'b, E, InitState> { /// Creates a new environment for consumption by a chain extension. /// /// It is only available to this crate because only the wasm runtime module needs to /// ever create this type. Chain extensions merely consume it. pub(crate) fn new( runtime: &'a mut Runtime<'b, E>, + memory: &'a mut [u8], + id: u32, input_ptr: u32, input_len: u32, output_ptr: u32, output_len_ptr: u32, ) -> Self { Environment { - inner: Inner { runtime, input_ptr, input_len, output_ptr, output_len_ptr }, + inner: Inner { runtime, memory, id, input_ptr, input_len, output_ptr, output_len_ptr }, phantom: PhantomData, } } /// Use all arguments as integer values. - pub fn only_in(self) -> Environment<'a, 'b, E, state::OnlyIn> { + pub fn only_in(self) -> Environment<'a, 'b, E, OnlyInState> { Environment { inner: self.inner, phantom: PhantomData } } /// Use input arguments as integer and output arguments as pointer to a buffer. - pub fn prim_in_buf_out(self) -> Environment<'a, 'b, E, state::PrimInBufOut> { + pub fn prim_in_buf_out(self) -> Environment<'a, 'b, E, PrimInBufOutState> { Environment { inner: self.inner, phantom: PhantomData } } /// Use input and output arguments as pointers to a buffer. - pub fn buf_in_buf_out(self) -> Environment<'a, 'b, E, state::BufInBufOut> { + pub fn buf_in_buf_out(self) -> Environment<'a, 'b, E, BufInBufOutState> { Environment { inner: self.inner, phantom: PhantomData } } } /// Functions to use the input arguments as integers. -impl<'a, 'b, E: Ext, S: state::PrimIn> Environment<'a, 'b, E, S> { +impl<'a, 'b, E: Ext, S: PrimIn> Environment<'a, 'b, E, S> { /// The `input_ptr` argument. pub fn val0(&self) -> u32 { self.inner.input_ptr @@ -245,7 +313,7 @@ impl<'a, 'b, E: Ext, S: state::PrimIn> Environment<'a, 'b, E, S> { } /// Functions to use the output arguments as integers. -impl<'a, 'b, E: Ext, S: state::PrimOut> Environment<'a, 'b, E, S> { +impl<'a, 'b, E: Ext, S: PrimOut> Environment<'a, 'b, E, S> { /// The `output_ptr` argument. pub fn val2(&self) -> u32 { self.inner.output_ptr @@ -258,7 +326,7 @@ impl<'a, 'b, E: Ext, S: state::PrimOut> Environment<'a, 'b, E, S> { } /// Functions to use the input arguments as pointer to a buffer. -impl<'a, 'b, E: Ext, S: state::BufIn> Environment<'a, 'b, E, S> +impl<'a, 'b, E: Ext, S: BufIn> Environment<'a, 'b, E, S> where ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { @@ -271,9 +339,11 @@ where /// charge the overall costs either using `max_len` (worst case approximation) or using /// [`in_len()`](Self::in_len). pub fn read(&self, max_len: u32) -> Result> { - self.inner - .runtime - .read_sandbox_memory(self.inner.input_ptr, self.inner.input_len.min(max_len)) + self.inner.runtime.read_sandbox_memory( + self.inner.memory, + self.inner.input_ptr, + self.inner.input_len.min(max_len), + ) } /// Reads `min(buffer.len(), in_len) from contract memory. @@ -287,7 +357,11 @@ where let buffer = core::mem::take(buffer); &mut buffer[..len.min(self.inner.input_len as usize)] }; - self.inner.runtime.read_sandbox_memory_into_buf(self.inner.input_ptr, sliced)?; + self.inner.runtime.read_sandbox_memory_into_buf( + self.inner.memory, + self.inner.input_ptr, + sliced, + )?; *buffer = sliced; Ok(()) } @@ -299,14 +373,20 @@ where /// weight of the chain extension. This should usually be the case when fixed input types /// are used. pub fn read_as(&mut self) -> Result { - self.inner.runtime.read_sandbox_memory_as(self.inner.input_ptr) + self.inner + .runtime + .read_sandbox_memory_as(self.inner.memory, self.inner.input_ptr) } /// Reads and decodes a type with a dynamic size from contract memory. /// /// Make sure to include `len` in your weight calculations. pub fn read_as_unbounded(&mut self, len: u32) -> Result { - self.inner.runtime.read_sandbox_memory_as_unbounded(self.inner.input_ptr, len) + self.inner.runtime.read_sandbox_memory_as_unbounded( + self.inner.memory, + self.inner.input_ptr, + len, + ) } /// The length of the input as passed in as `input_len`. @@ -321,7 +401,7 @@ where } /// Functions to use the output arguments as pointer to a buffer. -impl<'a, 'b, E: Ext, S: state::BufOut> Environment<'a, 'b, E, S> +impl<'a, 'b, E: Ext, S: BufOut> Environment<'a, 'b, E, S> where ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { @@ -329,7 +409,7 @@ where /// /// If the contract supplied buffer is smaller than the passed `buffer` an `Err` is returned. /// If `allow_skip` is set to true the contract is allowed to skip the copying of the buffer - /// by supplying the guard value of `u32::MAX` as `out_ptr`. The + /// by supplying the guard value of `pallet-contracts::SENTINEL` as `out_ptr`. The /// `weight_per_byte` is only charged when the write actually happens and is not skipped or /// failed due to a too small output buffer. pub fn write( @@ -339,12 +419,14 @@ where weight_per_byte: Option, ) -> Result<()> { self.inner.runtime.write_sandbox_output( + self.inner.memory, self.inner.output_ptr, self.inner.output_len_ptr, buffer, allow_skip, |len| { - weight_per_byte.map(|w| RuntimeCosts::ChainExtension(w.saturating_mul(len.into()))) + weight_per_byte + .map(|w| RuntimeCosts::ChainExtension(w.ref_time().saturating_mul(len.into()))) }, ) } @@ -358,6 +440,10 @@ where struct Inner<'a, 'b, E: Ext> { /// The runtime contains all necessary functions to interact with the running contract. runtime: &'a mut Runtime<'b, E>, + /// Reference to the contracts memory. + memory: &'a mut [u8], + /// Verbatim argument passed to `seal_call_chain_extension`. + id: u32, /// Verbatim argument passed to `seal_call_chain_extension`. input_ptr: u32, /// Verbatim argument passed to `seal_call_chain_extension`. @@ -368,31 +454,54 @@ struct Inner<'a, 'b, E: Ext> { output_len_ptr: u32, } -/// Private submodule with public types to prevent other modules from naming them. -mod state { - pub trait State {} - - pub trait PrimIn: State {} - pub trait PrimOut: State {} - pub trait BufIn: State {} - pub trait BufOut: State {} - - /// The initial state of an [`Environment`](`super::Environment`). - /// See [typestate programming](https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html). - pub enum Init {} - pub enum OnlyIn {} - pub enum PrimInBufOut {} - pub enum BufInBufOut {} - - impl State for Init {} - impl State for OnlyIn {} - impl State for PrimInBufOut {} - impl State for BufInBufOut {} - - impl PrimIn for OnlyIn {} - impl PrimOut for OnlyIn {} - impl PrimIn for PrimInBufOut {} - impl BufOut for PrimInBufOut {} - impl BufIn for BufInBufOut {} - impl BufOut for BufInBufOut {} +/// Any state of an [`Environment`] implements this trait. +/// See [typestate programming](https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html). +pub trait State: sealed::Sealed {} + +/// A state that uses primitive inputs. +pub trait PrimIn: State {} + +/// A state that uses primitive outputs. +pub trait PrimOut: State {} + +/// A state that uses a buffer as input. +pub trait BufIn: State {} + +/// A state that uses a buffer as output. +pub trait BufOut: State {} + +/// The initial state of an [`Environment`]. +pub enum InitState {} + +/// A state that uses all arguments as primitive inputs. +pub enum OnlyInState {} + +/// A state that uses two arguments as primitive inputs and the other two as buffer output. +pub enum PrimInBufOutState {} + +/// Uses a buffer for input and a buffer for output. +pub enum BufInBufOutState {} + +mod sealed { + use super::*; + + /// Trait to prevent users from implementing `State` for anything else. + pub trait Sealed {} + + impl Sealed for InitState {} + impl Sealed for OnlyInState {} + impl Sealed for PrimInBufOutState {} + impl Sealed for BufInBufOutState {} + + impl State for InitState {} + impl State for OnlyInState {} + impl State for PrimInBufOutState {} + impl State for BufInBufOutState {} + + impl PrimIn for OnlyInState {} + impl PrimOut for OnlyInState {} + impl PrimIn for PrimInBufOutState {} + impl BufOut for PrimInBufOutState {} + impl BufIn for BufInBufOutState {} + impl BufOut for BufInBufOutState {} } diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index cc468466c2922..945095dc20329 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,39 +16,65 @@ // limitations under the License. use crate::{ - gas::GasMeter, storage::Storage, AccountCounter, BalanceOf, CodeHash, Config, ContractInfo, - ContractInfoOf, Error, Event, Pallet as Contracts, Schedule, + gas::GasMeter, + storage::{self, Storage, WriteOutcome}, + BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error, + Event, Nonce, Pallet as Contracts, Schedule, }; use frame_support::{ + crypto::ecdsa::ECDSAExt, dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable}, - ensure, storage::{with_transaction, TransactionOutcome}, - traits::{Contains, Currency, ExistenceRequirement, Get, OriginTrait, Randomness, Time}, + traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time}, weights::Weight, + Blake2_128Concat, BoundedVec, StorageHasher, }; use frame_system::RawOrigin; use pallet_contracts_primitives::ExecReturnValue; use smallvec::{Array, SmallVec}; -use sp_core::crypto::UncheckedFrom; -use sp_io::crypto::secp256k1_ecdsa_recover_compressed; -use sp_runtime::traits::{Convert, Saturating}; +use sp_core::{crypto::UncheckedFrom, ecdsa::Public as ECDSAPublic}; +use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; +use sp_runtime::traits::{Convert, Hash}; use sp_std::{marker::PhantomData, mem, prelude::*}; -/// When fields are added to the [`ContractInfo`] that can change during execution this -/// variable needs to be set to true. This will also force changes to the -/// `in_memory_changes_not_discarded` test. -const CONTRACT_INFO_CAN_CHANGE: bool = false; - pub type AccountIdOf = ::AccountId; pub type MomentOf = <::Time as Time>::Moment; pub type SeedOf = ::Hash; pub type BlockNumberOf = ::BlockNumber; -pub type StorageKey = [u8; 32]; pub type ExecResult = Result; /// A type that represents a topic of an event. At the moment a hash is used. pub type TopicOf = ::Hash; +/// Type for fix sized storage key. +pub type FixSizedKey = [u8; 32]; + +/// Type for variable sized storage key. Used for transparent hashing. +pub type VarSizedKey = BoundedVec::MaxStorageKeyLen>; + +/// Trait for hashing storage keys. +pub trait StorageKey +where + T: Config, +{ + fn hash(&self) -> Vec; +} + +impl StorageKey for FixSizedKey { + fn hash(&self) -> Vec { + blake2_256(self.as_slice()).to_vec() + } +} + +impl StorageKey for VarSizedKey +where + T: Config, +{ + fn hash(&self) -> Vec { + Blake2_128Concat::hash(self.as_slice()) + } +} + /// Origin of the error. /// /// Call or instantiate both called into other contracts and pass through errors happening @@ -95,10 +121,6 @@ pub trait Ext: sealing::Sealed { /// Call (possibly transferring some amount of funds) into the specified account. /// /// Returns the original code size of the called contract. - /// - /// # Return Value - /// - /// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)> fn call( &mut self, gas_limit: Weight, @@ -108,15 +130,20 @@ pub trait Ext: sealing::Sealed { allows_reentry: bool, ) -> Result; + /// Execute code in the current frame. + /// + /// Returns the original code size of the called contract. + fn delegate_call( + &mut self, + code: CodeHash, + input_data: Vec, + ) -> Result; + /// Instantiate a contract from the given code. /// /// Returns the original code size of the called contract. /// The newly created account will be associated with `code`. `value` specifies the amount of - /// value transferred from this to the newly created account (also known as endowment). - /// - /// # Return Value - /// - /// Result<(AccountId, ExecReturnValue, CodeSize), (ExecError, CodeSize)> + /// value transferred from this to the newly created account. fn instantiate( &mut self, gas_limit: Weight, @@ -142,15 +169,68 @@ pub trait Ext: sealing::Sealed { /// /// Returns `None` if the `key` wasn't previously set by `set_storage` or /// was deleted. - fn get_storage(&mut self, key: &StorageKey) -> Option>; + fn get_storage(&mut self, key: &FixSizedKey) -> Option>; + + /// This is a variation of `get_storage()` to be used with transparent hashing. + /// These two will be merged into a single function after some refactoring is done. + /// Returns the storage entry of the executing account by the given `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + fn get_storage_transparent(&mut self, key: &VarSizedKey) -> Option>; + + /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + fn get_storage_size(&mut self, key: &FixSizedKey) -> Option; + + /// This is the variation of `get_storage_size()` to be used with transparent hashing. + /// These two will be merged into a single function after some refactoring is done. + /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + fn get_storage_size_transparent(&mut self, key: &VarSizedKey) -> Option; /// Sets the storage entry by the given key to the specified value. If `value` is `None` then /// the storage entry is deleted. - fn set_storage(&mut self, key: StorageKey, value: Option>) -> DispatchResult; + fn set_storage( + &mut self, + key: &FixSizedKey, + value: Option>, + take_old: bool, + ) -> Result; + + /// This is the variation of `set_storage()` to be used with transparent hashing. + /// These two will be merged into a single function after some refactoring is done. + fn set_storage_transparent( + &mut self, + key: &VarSizedKey, + value: Option>, + take_old: bool, + ) -> Result; /// Returns a reference to the account id of the caller. fn caller(&self) -> &AccountIdOf; + /// Check if a contract lives at the specified `address`. + fn is_contract(&self, address: &AccountIdOf) -> bool; + + /// Returns the code hash of the contract for the given `address`. + /// + /// Returns `None` if the `address` does not belong to a contract. + fn code_hash(&self, address: &AccountIdOf) -> Option>; + + /// Returns the code hash of the contract being executed. + fn own_code_hash(&mut self) -> &CodeHash; + + /// Check if the caller of the current contract is the origin of the whole call stack. + /// + /// This can be checked with `is_contract(self.caller())` as well. + /// However, this function does not require any storage lookup and therefore uses less weight. + fn caller_is_origin(&self) -> bool; + /// Returns a reference to the account id of the current contract. fn address(&self) -> &AccountIdOf; @@ -159,7 +239,7 @@ pub trait Ext: sealing::Sealed { /// The `value_transferred` is already added. fn balance(&self) -> BalanceOf; - /// Returns the value transferred along with this call or as endowment. + /// Returns the value transferred along with this call. fn value_transferred(&self) -> BalanceOf; /// Returns a reference to the timestamp of the current block @@ -168,9 +248,6 @@ pub trait Ext: sealing::Sealed { /// Returns the minimum balance that is required for creating an account. fn minimum_balance(&self) -> BalanceOf; - /// Returns the deposit required to instantiate a contract. - fn contract_deposit(&self) -> BalanceOf; - /// Returns a random number for the current block with the given subject. fn random(&self, subject: &[u8]) -> (SeedOf, BlockNumberOf); @@ -202,13 +279,35 @@ pub trait Ext: sealing::Sealed { /// when the code is executing on-chain. /// /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. - fn append_debug_buffer(&mut self, msg: &str) -> bool; + fn append_debug_buffer(&mut self, msg: &str) -> Result; /// Call some dispatchable and return the result. - fn call_runtime(&self, call: ::Call) -> DispatchResultWithPostInfo; + fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; /// Recovers ECDSA compressed public key based on signature and message hash. fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>; + + /// Returns Ethereum address from the ECDSA compressed public key. + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>; + + /// Tests sometimes need to modify and inspect the contract info directly. + #[cfg(test)] + fn contract_info(&mut self) -> &mut ContractInfo; + + /// Sets new code hash for existing contract. + fn set_code_hash(&mut self, hash: CodeHash) -> Result<(), DispatchError>; + + /// Returns the number of times the currently executing contract exists on the call stack in + /// addition to the calling instance. A value of 0 means no reentrancy. + fn reentrance_count(&self) -> u32; + + /// Returns the number of times the specified contract exists on the call stack. Delegated calls + /// are not calculated as separate entrance. + /// A value of 0 means it does not exist on the call stack. + fn account_reentrance_count(&self, account_id: &AccountIdOf) -> u32; + + /// Returns a nonce that is incremented for every instantiated contract. + fn nonce(&mut self) -> u64; } /// Describes the different functions that can be exported by an [`Executable`]. @@ -235,37 +334,17 @@ pub trait Executable: Sized { gas_meter: &mut GasMeter, ) -> Result; - /// Load the module from storage without re-instrumenting it. - /// - /// A code module is re-instrumented on-load when it was originally instrumented with - /// an older schedule. This skips this step for cases where the code storage is - /// queried for purposes other than execution. - /// - /// # Note - /// - /// Does not charge from the gas meter. Do not call in contexts where this is important. - fn from_storage_noinstr(code_hash: CodeHash) -> Result; - - /// Increment the refcount by one. Fails if the code does not exist on-chain. + /// Increment the refcount of a code in-storage by one. /// - /// Returns the size of the original code. + /// This is needed when the code is not set via instantiate but `seal_set_code_hash`. /// - /// # Note + /// # Errors /// - /// Charges weight proportional to the code size from the gas meter. - fn add_user(code_hash: CodeHash, gas_meter: &mut GasMeter) -> Result<(), DispatchError>; + /// [`Error::CodeNotFound`] is returned if the specified `code_hash` does not exist. + fn add_user(code_hash: CodeHash) -> Result<(), DispatchError>; - /// Decrement the refcount by one and remove the code when it drops to zero. - /// - /// Returns the size of the original code. - /// - /// # Note - /// - /// Charges weight proportional to the code size from the gas meter - fn remove_user( - code_hash: CodeHash, - gas_meter: &mut GasMeter, - ) -> Result<(), DispatchError>; + /// Decrement the refcount by one if the code exists. + fn remove_user(code_hash: CodeHash); /// Execute the specified exported function and return the result. /// @@ -289,11 +368,8 @@ pub trait Executable: Sized { /// Size of the instrumented code in bytes. fn code_len(&self) -> u32; - /// Sum of instrumented and pristine code len. - fn aggregate_code_len(&self) -> u32; - - // The number of contracts using this executable. - fn refcount(&self) -> u32; + /// The code does not contain any instructions which could lead to indeterminism. + fn is_deterministic(&self) -> bool; } /// The complete call stack of a contract execution. @@ -314,13 +390,16 @@ pub struct Stack<'a, T: Config, E> { schedule: &'a Schedule, /// The gas meter where costs are charged to. gas_meter: &'a mut GasMeter, + /// The storage meter makes sure that the storage deposit limit is obeyed. + storage_meter: &'a mut storage::meter::Meter, /// The timestamp at the point of call stack instantiation. timestamp: MomentOf, /// The block number at the time of call stack instantiation. block_number: T::BlockNumber, - /// The account counter is cached here when accessed. It is written back when the call stack - /// finishes executing. - account_counter: Option, + /// The nonce is cached here when accessed. It is written back when the call stack + /// finishes executing. Please refer to [`Nonce`] to a description of + /// the nonce itself. + nonce: Option, /// The actual call stack. One entry per nested contract called/instantiated. /// This does **not** include the [`Self::first_frame`]. frames: SmallVec, @@ -330,7 +409,9 @@ pub struct Stack<'a, T: Config, E> { /// /// All the bytes added to this field should be valid UTF-8. The buffer has no defined /// structure and is intended to be shown to users as-is for debugging purposes. - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, + /// The determinism requirement of this call stack. + determinism: Determinism, /// No executable is held by the struct but influences its behaviour. _phantom: PhantomData, } @@ -354,9 +435,21 @@ pub struct Frame { /// Determines whether this is a call or instantiate frame. entry_point: ExportedFunction, /// The gas meter capped to the supplied gas limit. - nested_meter: GasMeter, + nested_gas: GasMeter, + /// The storage meter for the individual call. + nested_storage: storage::meter::NestedMeter, /// If `false` the contract enabled its defense against reentrance attacks. allows_reentry: bool, + /// The caller of the currently executing frame which was spawned by `delegate_call`. + delegate_caller: Option, +} + +/// Used in a delegate call frame arguments in order to override the executable and caller. +struct DelegatedCall { + /// The executable which is run instead of the contracts own `executable`. + executable: E, + /// The account id of the caller contract. + caller: T::AccountId, } /// Parameter passed in when creating a new `Frame`. @@ -368,12 +461,16 @@ enum FrameArgs<'a, T: Config, E> { dest: T::AccountId, /// If `None` the contract info needs to be reloaded from storage. cached_info: Option>, + /// This frame was created by `seal_delegate_call` and hence uses different code than + /// what is stored at [`Self::dest`]. Its caller ([`Frame::delegated_caller`]) is the + /// account which called the caller contract + delegated_call: Option>, }, Instantiate { /// The contract or signed origin which instantiates the new contract. sender: T::AccountId, - /// The seed that should be used to derive a new trie id for the contract. - trie_seed: u64, + /// The nonce that should be used to derive a new trie id for the contract. + nonce: u64, /// The executable whose `deploy` function is run. executable: E, /// A salt used in the contract address deriviation of the new contract. @@ -396,6 +493,26 @@ enum CachedContract { Terminated, } +impl CachedContract { + /// Return `Some(ContractInfo)` if the contract is in cached state. `None` otherwise. + fn into_contract(self) -> Option> { + if let CachedContract::Cached(contract) = self { + Some(contract) + } else { + None + } + } + + /// Return `Some(&mut ContractInfo)` if the contract is in cached state. `None` otherwise. + fn as_contract(&mut self) -> Option<&mut ContractInfo> { + if let CachedContract::Cached(contract) = self { + Some(contract) + } else { + None + } + } +} + impl Frame { /// Return the `contract_info` of the current contract. fn contract_info(&mut self) -> &mut ContractInfo { @@ -432,6 +549,26 @@ macro_rules! get_cached_or_panic_after_load { }}; } +/// Same as [`Stack::top_frame`]. +/// +/// We need this access as a macro because sometimes hiding the lifetimes behind +/// a function won't work out. +macro_rules! top_frame { + ($stack:expr) => { + $stack.frames.last().unwrap_or(&$stack.first_frame) + }; +} + +/// Same as [`Stack::top_frame_mut`]. +/// +/// We need this access as a macro because sometimes hiding the lifetimes behind +/// a function won't work out. +macro_rules! top_frame_mut { + ($stack:expr) => { + $stack.frames.last_mut().unwrap_or(&mut $stack.first_frame) + }; +} + impl CachedContract { /// Load the `contract_info` from storage if necessary. fn load(&mut self, account_id: &T::AccountId) { @@ -462,7 +599,7 @@ where T::AccountId: UncheckedFrom + AsRef<[u8]>, E: Executable, { - /// Create an run a new call stack by calling into `dest`. + /// Create and run a new call stack by calling into `dest`. /// /// # Note /// @@ -476,18 +613,22 @@ where origin: T::AccountId, dest: T::AccountId, gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, schedule: &'a Schedule, value: BalanceOf, input_data: Vec, - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, + determinism: Determinism, ) -> Result { let (mut stack, executable) = Self::new( - FrameArgs::Call { dest, cached_info: None }, + FrameArgs::Call { dest, cached_info: None, delegated_call: None }, origin, gas_meter, + storage_meter, schedule, value, debug_message, + determinism, )?; stack.run(executable, input_data) } @@ -506,24 +647,27 @@ where origin: T::AccountId, executable: E, gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, schedule: &'a Schedule, value: BalanceOf, input_data: Vec, salt: &[u8], - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, ) -> Result<(T::AccountId, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( FrameArgs::Instantiate { sender: origin.clone(), - trie_seed: Self::initial_trie_seed(), + nonce: >::get().wrapping_add(1), executable, salt, }, origin, gas_meter, + storage_meter, schedule, value, debug_message, + Determinism::Deterministic, )?; let account_id = stack.top_frame().account_id.clone(); stack.run(executable, input_data).map(|ret| (account_id, ret)) @@ -534,21 +678,33 @@ where args: FrameArgs, origin: T::AccountId, gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, schedule: &'a Schedule, value: BalanceOf, - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, + determinism: Determinism, ) -> Result<(Self, E), ExecError> { - let (first_frame, executable) = Self::new_frame(args, value, gas_meter, 0, &schedule)?; + let (first_frame, executable, nonce) = Self::new_frame( + args, + value, + gas_meter, + storage_meter, + Weight::zero(), + schedule, + determinism, + )?; let stack = Self { origin, schedule, gas_meter, + storage_meter, timestamp: T::Time::now(), block_number: >::block_number(), - account_counter: None, + nonce, first_frame, frames: Default::default(), debug_message, + determinism, _phantom: Default::default(), }; @@ -559,48 +715,71 @@ where /// /// This does not take `self` because when constructing the first frame `self` is /// not initialized, yet. - fn new_frame( + fn new_frame( frame_args: FrameArgs, value_transferred: BalanceOf, gas_meter: &mut GasMeter, + storage_meter: &mut storage::meter::GenericMeter, gas_limit: Weight, schedule: &Schedule, - ) -> Result<(Frame, E), ExecError> { - let (account_id, contract_info, executable, entry_point) = match frame_args { - FrameArgs::Call { dest, cached_info } => { - let contract = if let Some(contract) = cached_info { - contract - } else { - >::get(&dest).ok_or(>::ContractNotFound)? - }; - - let executable = E::from_storage(contract.code_hash, schedule, gas_meter)?; - - (dest, contract, executable, ExportedFunction::Call) - }, - FrameArgs::Instantiate { sender, trie_seed, executable, salt } => { - let account_id = - >::contract_address(&sender, executable.code_hash(), &salt); - let trie_id = Storage::::generate_trie_id(&account_id, trie_seed); - let contract = Storage::::new_contract( - &account_id, - trie_id, - executable.code_hash().clone(), - )?; - (account_id, contract, executable, ExportedFunction::Constructor) - }, - }; + determinism: Determinism, + ) -> Result<(Frame, E, Option), ExecError> { + let (account_id, contract_info, executable, delegate_caller, entry_point, nonce) = + match frame_args { + FrameArgs::Call { dest, cached_info, delegated_call } => { + let contract = if let Some(contract) = cached_info { + contract + } else { + >::get(&dest).ok_or(>::ContractNotFound)? + }; + + let (executable, delegate_caller) = + if let Some(DelegatedCall { executable, caller }) = delegated_call { + (executable, Some(caller)) + } else { + (E::from_storage(contract.code_hash, schedule, gas_meter)?, None) + }; + + (dest, contract, executable, delegate_caller, ExportedFunction::Call, None) + }, + FrameArgs::Instantiate { sender, nonce, executable, salt } => { + let account_id = + >::contract_address(&sender, executable.code_hash(), salt); + let trie_id = Storage::::generate_trie_id(&account_id, nonce); + let contract = + Storage::::new_contract(&account_id, trie_id, *executable.code_hash())?; + ( + account_id, + contract, + executable, + None, + ExportedFunction::Constructor, + Some(nonce), + ) + }, + }; + + // `AllowIndeterminism` will only be ever set in case of off-chain execution. + // Instantiations are never allowed even when executing off-chain. + if !(executable.is_deterministic() || + (matches!(determinism, Determinism::AllowIndeterminism) && + matches!(entry_point, ExportedFunction::Call))) + { + return Err(Error::::Indeterministic.into()) + } let frame = Frame { + delegate_caller, value_transferred, contract_info: CachedContract::Cached(contract_info), account_id, entry_point, - nested_meter: gas_meter.nested(gas_limit)?, + nested_gas: gas_meter.nested(gas_limit)?, + nested_storage: storage_meter.nested(), allows_reentry: true, }; - Ok((frame, executable)) + Ok((frame, executable, nonce)) } /// Create a subsequent nested frame. @@ -614,23 +793,29 @@ where return Err(Error::::MaxCallDepthReached.into()) } - if CONTRACT_INFO_CAN_CHANGE { - // We need to make sure that changes made to the contract info are not discarded. - // See the `in_memory_changes_not_discarded` test for more information. - // We do not store on instantiate because we do not allow to call into a contract - // from its own constructor. - let frame = self.top_frame(); - if let (CachedContract::Cached(contract), ExportedFunction::Call) = - (&frame.contract_info, frame.entry_point) - { - >::insert(frame.account_id.clone(), contract.clone()); - } + // We need to make sure that changes made to the contract info are not discarded. + // See the `in_memory_changes_not_discarded` test for more information. + // We do not store on instantiate because we do not allow to call into a contract + // from its own constructor. + let frame = self.top_frame(); + if let (CachedContract::Cached(contract), ExportedFunction::Call) = + (&frame.contract_info, frame.entry_point) + { + >::insert(frame.account_id.clone(), contract.clone()); } - let nested_meter = - &mut self.frames.last_mut().unwrap_or(&mut self.first_frame).nested_meter; - let (frame, executable) = - Self::new_frame(frame_args, value_transferred, nested_meter, gas_limit, self.schedule)?; + let frame = top_frame_mut!(self); + let nested_gas = &mut frame.nested_gas; + let nested_storage = &mut frame.nested_storage; + let (frame, executable, _) = Self::new_frame( + frame_args, + value_transferred, + nested_gas, + nested_storage, + gas_limit, + self.schedule, + self.determinism, + )?; self.frames.push(frame); Ok(executable) } @@ -639,9 +824,23 @@ where /// /// This can be either a call or an instantiate. fn run(&mut self, executable: E, input_data: Vec) -> Result { - let entry_point = self.top_frame().entry_point; + let frame = self.top_frame(); + let entry_point = frame.entry_point; + let delegated_code_hash = + if frame.delegate_caller.is_some() { Some(*executable.code_hash()) } else { None }; let do_transaction = || { - // Every call or instantiate also optionally transferres balance. + // We need to charge the storage deposit before the initial transfer so that + // it can create the account in case the initial transfer is < ed. + if entry_point == ExportedFunction::Constructor { + let frame = top_frame_mut!(self); + frame.nested_storage.charge_instantiate( + &self.origin, + &frame.account_id, + frame.contract_info.get(&frame.account_id), + )?; + } + + // Every non delegate call or instantiate also optionally transfers the balance. self.initial_transfer()?; // Call into the wasm blob. @@ -649,18 +848,51 @@ where .execute(self, &entry_point, input_data) .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; - // Additional work needs to be performed in case of an instantiation. - if output.is_success() && entry_point == ExportedFunction::Constructor { - let frame = self.top_frame_mut(); - let account_id = frame.account_id.clone(); + // Avoid useless work that would be reverted anyways. + if output.did_revert() { + return Ok(output) + } - // It is not allowed to terminate a contract inside its constructor. - if let CachedContract::Terminated = frame.contract_info { - return Err(Error::::TerminatedInConstructor.into()) - } + // Storage limit is enforced as late as possible (when the last frame returns) so that + // the ordering of storage accesses does not matter. + if self.frames.is_empty() { + let frame = &mut self.first_frame; + frame.contract_info.load(&frame.account_id); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_limit(contract)?; + } - // Deposit an instantiation event. - deposit_event::(vec![], Event::Instantiated(self.caller().clone(), account_id)); + let frame = self.top_frame(); + let account_id = &frame.account_id; + match (entry_point, delegated_code_hash) { + (ExportedFunction::Constructor, _) => { + // It is not allowed to terminate a contract inside its constructor. + if matches!(frame.contract_info, CachedContract::Terminated) { + return Err(Error::::TerminatedInConstructor.into()) + } + + // Deposit an instantiation event. + Contracts::::deposit_event( + vec![T::Hashing::hash_of(self.caller()), T::Hashing::hash_of(account_id)], + Event::Instantiated { + deployer: self.caller().clone(), + contract: account_id.clone(), + }, + ); + }, + (ExportedFunction::Call, Some(code_hash)) => { + Contracts::::deposit_event( + vec![T::Hashing::hash_of(account_id), T::Hashing::hash_of(&code_hash)], + Event::DelegateCalled { contract: account_id.clone(), code_hash }, + ); + }, + (ExportedFunction::Call, None) => { + let caller = self.caller(); + Contracts::::deposit_event( + vec![T::Hashing::hash_of(caller), T::Hashing::hash_of(account_id)], + Event::Called { caller: caller.clone(), contract: account_id.clone() }, + ); + }, } Ok(output) @@ -668,14 +900,28 @@ where // All changes performed by the contract are executed under a storage transaction. // This allows for roll back on error. Changes to the cached contract_info are - // comitted or rolled back when popping the frame. - let (success, output) = with_transaction(|| { - let output = do_transaction(); - match &output { - Ok(result) if result.is_success() => TransactionOutcome::Commit((true, output)), - _ => TransactionOutcome::Rollback((false, output)), - } - }); + // committed or rolled back when popping the frame. + // + // `with_transactional` may return an error caused by a limit in the + // transactional storage depth. + let transaction_outcome = + with_transaction(|| -> TransactionOutcome> { + let output = do_transaction(); + match &output { + Ok(result) if !result.did_revert() => + TransactionOutcome::Commit(Ok((true, output))), + _ => TransactionOutcome::Rollback(Ok((false, output))), + } + }); + + let (success, output) = match transaction_outcome { + // `with_transactional` executed successfully, and we have the expected output. + Ok((success, output)) => (success, output), + // `with_transactional` returned an error, and we propagate that error and note no state + // has changed. + Err(error) => (false, Err(error.into())), + }; + self.pop_frame(success); output } @@ -685,9 +931,9 @@ where /// This is called after running the current frame. It commits cached values to storage /// and invalidates all stale references to it that might exist further down the call stack. fn pop_frame(&mut self, persist: bool) { - // Revert the account counter in case of a failed instantiation. + // Revert changes to the nonce in case of a failed instantiation. if !persist && self.top_frame().entry_point == ExportedFunction::Constructor { - self.account_counter.as_mut().map(|c| *c = c.wrapping_sub(1)); + self.nonce.as_mut().map(|c| *c = c.wrapping_sub(1)); } // Pop the current frame from the stack and return it in case it needs to interact @@ -695,15 +941,29 @@ where // A `None` means that we are returning from the `first_frame`. let frame = self.frames.pop(); - if let Some(frame) = frame { - let prev = self.top_frame_mut(); + // Both branches do essentially the same with the exception. The difference is that + // the else branch does consume the hardcoded `first_frame`. + if let Some(mut frame) = frame { let account_id = &frame.account_id; - prev.nested_meter.absorb_nested(frame.nested_meter); + let prev = top_frame_mut!(self); + + prev.nested_gas.absorb_nested(frame.nested_gas); + // Only gas counter changes are persisted in case of a failure. if !persist { return } - if let CachedContract::Cached(contract) = frame.contract_info { + + // Record the storage meter changes of the nested call into the parent meter. + // If the dropped frame's contract wasn't terminated we update the deposit counter + // in its contract info. The load is necessary to to pull it from storage in case + // it was invalidated. + frame.contract_info.load(account_id); + let mut contract = frame.contract_info.into_contract(); + prev.nested_storage.absorb(frame.nested_storage, account_id, contract.as_mut()); + + // In case the contract wasn't terminated we need to persist changes made to it. + if let Some(contract) = contract { // optimization: Predecessor is the same contract. // We can just copy the contract into the predecessor without a storage write. // This is possible when there is no other contract in-between that could @@ -731,86 +991,59 @@ where core::str::from_utf8(msg).unwrap_or(""), ); } - // Write back to the root gas meter. - self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_meter)); - // Only gas counter changes are persisted in case of a failure. + self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas)); if !persist { return } - if let CachedContract::Cached(contract) = &self.first_frame.contract_info { - >::insert(&self.first_frame.account_id, contract.clone()); + let mut contract = self.first_frame.contract_info.as_contract(); + self.storage_meter.absorb( + mem::take(&mut self.first_frame.nested_storage), + &self.first_frame.account_id, + contract.as_deref_mut(), + ); + if let Some(contract) = contract { + >::insert(&self.first_frame.account_id, contract); } - if let Some(counter) = self.account_counter { - >::set(counter); + if let Some(nonce) = self.nonce { + >::set(nonce); } } } /// Transfer some funds from `from` to `to`. - /// - /// We only allow allow for draining all funds of the sender if `allow_death` is - /// is specified as `true`. Otherwise, any transfer that would bring the sender below the - /// subsistence threshold (for contracts) or the existential deposit (for plain accounts) - /// results in an error. fn transfer( - sender_is_contract: bool, - allow_death: bool, + existence_requirement: ExistenceRequirement, from: &T::AccountId, to: &T::AccountId, value: BalanceOf, ) -> DispatchResult { - if value == 0u32.into() { - return Ok(()) - } - - let existence_requirement = match (allow_death, sender_is_contract) { - (true, _) => ExistenceRequirement::AllowDeath, - (false, true) => { - ensure!( - T::Currency::total_balance(from).saturating_sub(value) >= - Contracts::::subsistence_threshold(), - Error::::BelowSubsistenceThreshold, - ); - ExistenceRequirement::KeepAlive - }, - (false, false) => ExistenceRequirement::KeepAlive, - }; - T::Currency::transfer(from, to, value, existence_requirement) .map_err(|_| Error::::TransferFailed)?; - Ok(()) } // The transfer as performed by a call or instantiate. fn initial_transfer(&self) -> DispatchResult { let frame = self.top_frame(); - let value = frame.value_transferred; - let subsistence_threshold = >::subsistence_threshold(); - // If the value transferred to a new contract is less than the subsistence threshold - // we can error out early. This avoids executing the constructor in cases where - // we already know that the contract has too little balance. - if frame.entry_point == ExportedFunction::Constructor && value < subsistence_threshold { - return Err(>::NewContractNotFunded.into()) + // If it is a delegate call, then we've already transferred tokens in the + // last non-delegate frame. + if frame.delegate_caller.is_some() { + return Ok(()) } - Self::transfer(self.caller_is_origin(), false, self.caller(), &frame.account_id, value) - } - - /// Wether the caller is the initiator of the call stack. - fn caller_is_origin(&self) -> bool { - !self.frames.is_empty() + let value = frame.value_transferred; + Self::transfer(ExistenceRequirement::KeepAlive, self.caller(), &frame.account_id, value) } /// Reference to the current (top) frame. fn top_frame(&self) -> &Frame { - self.frames.last().unwrap_or(&self.first_frame) + top_frame!(self) } /// Mutable reference to the current (top) frame. fn top_frame_mut(&mut self) -> &mut Frame { - self.frames.last_mut().unwrap_or(&mut self.first_frame) + top_frame_mut!(self) } /// Iterator over all frames. @@ -836,21 +1069,12 @@ where !self.frames().any(|f| &f.account_id == id && !f.allows_reentry) } - /// Increments the cached account id and returns the value to be used for the trie_id. - fn next_trie_seed(&mut self) -> u64 { - let next = if let Some(current) = self.account_counter { - current + 1 - } else { - Self::initial_trie_seed() - }; - self.account_counter = Some(next); + /// Increments and returns the next nonce. Pulls it from storage if it isn't in cache. + fn next_nonce(&mut self) -> u64 { + let next = self.nonce().wrapping_add(1); + self.nonce = Some(next); next } - - /// The account seed to be used to instantiate the account counter cache. - fn initial_trie_seed() -> u64 { - >::get().wrapping_add(1) - } } impl<'a, T, E> Ext for Stack<'a, T, E> @@ -888,8 +1112,11 @@ where CachedContract::Cached(contract) => Some(contract.clone()), _ => None, }); - let executable = - self.push_frame(FrameArgs::Call { dest: to, cached_info }, value, gas_limit)?; + let executable = self.push_frame( + FrameArgs::Call { dest: to, cached_info, delegated_call: None }, + value, + gas_limit, + )?; self.run(executable, input_data) }; @@ -902,24 +1129,46 @@ where result } + fn delegate_call( + &mut self, + code_hash: CodeHash, + input_data: Vec, + ) -> Result { + let executable = E::from_storage(code_hash, self.schedule, self.gas_meter())?; + let top_frame = self.top_frame_mut(); + let contract_info = top_frame.contract_info().clone(); + let account_id = top_frame.account_id.clone(); + let value = top_frame.value_transferred; + let executable = self.push_frame( + FrameArgs::Call { + dest: account_id, + cached_info: Some(contract_info), + delegated_call: Some(DelegatedCall { executable, caller: self.caller().clone() }), + }, + value, + Weight::zero(), + )?; + self.run(executable, input_data) + } + fn instantiate( &mut self, gas_limit: Weight, code_hash: CodeHash, - endowment: BalanceOf, + value: BalanceOf, input_data: Vec, salt: &[u8], ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { - let executable = E::from_storage(code_hash, &self.schedule, self.gas_meter())?; - let trie_seed = self.next_trie_seed(); + let executable = E::from_storage(code_hash, self.schedule, self.gas_meter())?; + let nonce = self.next_nonce(); let executable = self.push_frame( FrameArgs::Instantiate { sender: self.top_frame().account_id.clone(), - trie_seed, + nonce, executable, salt, }, - endowment, + value, gas_limit, )?; let account_id = self.top_frame().account_id.clone(); @@ -932,34 +1181,76 @@ where } let frame = self.top_frame_mut(); let info = frame.terminate(); + frame.nested_storage.terminate(&info); Storage::::queue_trie_for_deletion(&info)?; >::transfer( - true, - true, + ExistenceRequirement::AllowDeath, &frame.account_id, beneficiary, T::Currency::free_balance(&frame.account_id), )?; ContractInfoOf::::remove(&frame.account_id); - E::remove_user(info.code_hash, &mut frame.nested_meter)?; - Contracts::::deposit_event(Event::Terminated( - frame.account_id.clone(), - beneficiary.clone(), - )); + E::remove_user(info.code_hash); + Contracts::::deposit_event( + vec![T::Hashing::hash_of(&frame.account_id), T::Hashing::hash_of(&beneficiary)], + Event::Terminated { + contract: frame.account_id.clone(), + beneficiary: beneficiary.clone(), + }, + ); Ok(()) } fn transfer(&mut self, to: &T::AccountId, value: BalanceOf) -> DispatchResult { - Self::transfer(true, false, &self.top_frame().account_id, to, value) + Self::transfer(ExistenceRequirement::KeepAlive, &self.top_frame().account_id, to, value) + } + + fn get_storage(&mut self, key: &FixSizedKey) -> Option> { + Storage::::read(&self.top_frame_mut().contract_info().trie_id, key) } - fn get_storage(&mut self, key: &StorageKey) -> Option> { + fn get_storage_transparent(&mut self, key: &VarSizedKey) -> Option> { Storage::::read(&self.top_frame_mut().contract_info().trie_id, key) } - fn set_storage(&mut self, key: StorageKey, value: Option>) -> DispatchResult { + fn get_storage_size(&mut self, key: &FixSizedKey) -> Option { + Storage::::size(&self.top_frame_mut().contract_info().trie_id, key) + } + + fn get_storage_size_transparent(&mut self, key: &VarSizedKey) -> Option { + Storage::::size(&self.top_frame_mut().contract_info().trie_id, key) + } + + fn set_storage( + &mut self, + key: &FixSizedKey, + value: Option>, + take_old: bool, + ) -> Result { + let frame = self.top_frame_mut(); + Storage::::write( + &frame.contract_info.get(&frame.account_id).trie_id, + key, + value, + Some(&mut frame.nested_storage), + take_old, + ) + } + + fn set_storage_transparent( + &mut self, + key: &VarSizedKey, + value: Option>, + take_old: bool, + ) -> Result { let frame = self.top_frame_mut(); - Storage::::write(frame.contract_info(), &key, value) + Storage::::write( + &frame.contract_info.get(&frame.account_id).trie_id, + key, + value, + Some(&mut frame.nested_storage), + take_old, + ) } fn address(&self) -> &T::AccountId { @@ -967,7 +1258,27 @@ where } fn caller(&self) -> &T::AccountId { - self.frames().nth(1).map(|f| &f.account_id).unwrap_or(&self.origin) + if let Some(caller) = &self.top_frame().delegate_caller { + caller + } else { + self.frames().nth(1).map(|f| &f.account_id).unwrap_or(&self.origin) + } + } + + fn is_contract(&self, address: &T::AccountId) -> bool { + ContractInfoOf::::contains_key(&address) + } + + fn code_hash(&self, address: &T::AccountId) -> Option> { + >::get(&address).map(|contract| contract.code_hash) + } + + fn own_code_hash(&mut self) -> &CodeHash { + &self.top_frame_mut().contract_info().code_hash + } + + fn caller_is_origin(&self) -> bool { + self.caller() == &self.origin } fn balance(&self) -> BalanceOf { @@ -990,14 +1301,10 @@ where T::Currency::minimum_balance() } - fn contract_deposit(&self) -> BalanceOf { - T::ContractDeposit::get() - } - fn deposit_event(&mut self, topics: Vec, data: Vec) { - deposit_event::( + Contracts::::deposit_event( topics, - Event::ContractEmitted(self.top_frame().account_id.clone(), data), + Event::ContractEmitted { contract: self.top_frame().account_id.clone(), data }, ); } @@ -1006,7 +1313,7 @@ where } fn max_value_size(&self) -> u32 { - T::Schedule::get().limits.payload_len + self.schedule.limits.payload_len } fn get_weight_price(&self, weight: Weight) -> BalanceOf { @@ -1014,40 +1321,85 @@ where } fn schedule(&self) -> &Schedule { - &self.schedule + self.schedule } fn gas_meter(&mut self) -> &mut GasMeter { - &mut self.top_frame_mut().nested_meter + &mut self.top_frame_mut().nested_gas } - fn append_debug_buffer(&mut self, msg: &str) -> bool { + fn append_debug_buffer(&mut self, msg: &str) -> Result { if let Some(buffer) = &mut self.debug_message { if !msg.is_empty() { - buffer.extend(msg.as_bytes()); + buffer + .try_extend(&mut msg.bytes()) + .map_err(|_| Error::::DebugBufferExhausted)?; } - true + Ok(true) } else { - false + Ok(false) } } - fn call_runtime(&self, call: ::Call) -> DispatchResultWithPostInfo { - let mut origin: T::Origin = RawOrigin::Signed(self.address().clone()).into(); + fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo { + let mut origin: T::RuntimeOrigin = RawOrigin::Signed(self.address().clone()).into(); origin.add_filter(T::CallFilter::contains); call.dispatch(origin) } fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()> { - secp256k1_ecdsa_recover_compressed(&signature, &message_hash).map_err(|_| ()) + secp256k1_ecdsa_recover_compressed(signature, message_hash).map_err(|_| ()) + } + + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> { + ECDSAPublic(*pk).to_eth_address() + } + + #[cfg(test)] + fn contract_info(&mut self) -> &mut ContractInfo { + self.top_frame_mut().contract_info() + } + + fn set_code_hash(&mut self, hash: CodeHash) -> Result<(), DispatchError> { + let frame = top_frame_mut!(self); + if !E::from_storage(hash, self.schedule, &mut frame.nested_gas)?.is_deterministic() { + return Err(>::Indeterministic.into()) + } + E::add_user(hash)?; + let prev_hash = frame.contract_info().code_hash; + E::remove_user(prev_hash); + frame.contract_info().code_hash = hash; + Contracts::::deposit_event( + vec![T::Hashing::hash_of(&frame.account_id), hash, prev_hash], + Event::ContractCodeUpdated { + contract: frame.account_id.clone(), + new_code_hash: hash, + old_code_hash: prev_hash, + }, + ); + Ok(()) + } + + fn reentrance_count(&self) -> u32 { + let id: &AccountIdOf = &self.top_frame().account_id; + self.account_reentrance_count(id).saturating_sub(1) + } + + fn account_reentrance_count(&self, account_id: &AccountIdOf) -> u32 { + self.frames() + .filter(|f| f.delegate_caller.is_none() && &f.account_id == account_id) + .count() as u32 } -} -fn deposit_event(topics: Vec, event: Event) { - >::deposit_event_indexed( - &*topics, - ::Event::from(event).into(), - ) + fn nonce(&mut self) -> u64 { + if let Some(current) = self.nonce { + current + } else { + let current = >::get(); + self.nonce = Some(current); + current + } + } } mod sealing { @@ -1077,32 +1429,31 @@ mod tests { gas::GasMeter, storage::Storage, tests::{ - test_utils::{get_balance, place_contract, set_balance}, - Call, Event as MetaEvent, ExtBuilder, Test, TestFilter, ALICE, BOB, CHARLIE, + test_utils::{get_balance, hash, place_contract, set_balance}, + ExtBuilder, RuntimeCall, RuntimeEvent as MetaEvent, Test, TestFilter, ALICE, BOB, + CHARLIE, GAS_LIMIT, }, - Error, Weight, + Error, }; use assert_matches::assert_matches; use codec::{Decode, Encode}; - use frame_support::{assert_err, assert_ok}; + use frame_support::{assert_err, assert_ok, parameter_types}; use frame_system::{EventRecord, Phase}; use pallet_contracts_primitives::ReturnFlags; use pretty_assertions::assert_eq; - use sp_core::Bytes; - use sp_runtime::{ - traits::{BadOrigin, Hash}, - DispatchError, + use sp_runtime::{traits::Hash, DispatchError}; + use std::{ + cell::RefCell, + collections::hash_map::{Entry, HashMap}, + rc::Rc, }; - use std::{cell::RefCell, collections::HashMap, rc::Rc}; type System = frame_system::Pallet; type MockStack<'a> = Stack<'a, Test, MockExecutable>; - const GAS_LIMIT: Weight = 10_000_000_000; - - thread_local! { - static LOADER: RefCell = RefCell::new(MockLoader::default()); + parameter_types! { + static Loader: MockLoader = MockLoader::default(); } fn events() -> Vec> { @@ -1128,8 +1479,8 @@ mod tests { refcount: u64, } - #[derive(Default)] - struct MockLoader { + #[derive(Default, Clone)] + pub struct MockLoader { map: HashMap, MockExecutable>, counter: u64, } @@ -1139,39 +1490,31 @@ mod tests { func_type: ExportedFunction, f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static, ) -> CodeHash { - LOADER.with(|loader| { - let mut loader = loader.borrow_mut(); + Loader::mutate(|loader| { // Generate code hashes as monotonically increasing values. let hash = ::Hash::from_low_u64_be(loader.counter); loader.counter += 1; loader.map.insert( hash, - MockExecutable { - func: Rc::new(f), - func_type, - code_hash: hash.clone(), - refcount: 1, - }, + MockExecutable { func: Rc::new(f), func_type, code_hash: hash, refcount: 1 }, ); hash }) } - fn increment_refcount(code_hash: CodeHash) { - LOADER.with(|loader| { - let mut loader = loader.borrow_mut(); - loader - .map - .entry(code_hash) - .and_modify(|executable| executable.refcount += 1) - .or_insert_with(|| panic!("code_hash does not exist")); - }); + fn increment_refcount(code_hash: CodeHash) -> Result<(), DispatchError> { + Loader::mutate(|loader| { + match loader.map.entry(code_hash) { + Entry::Vacant(_) => Err(>::CodeNotFound)?, + Entry::Occupied(mut entry) => entry.get_mut().refcount += 1, + } + Ok(()) + }) } fn decrement_refcount(code_hash: CodeHash) { use std::collections::hash_map::Entry::Occupied; - LOADER.with(|loader| { - let mut loader = loader.borrow_mut(); + Loader::mutate(|loader| { let mut entry = match loader.map.entry(code_hash) { Occupied(e) => e, _ => panic!("code_hash does not exist"), @@ -1191,34 +1534,17 @@ mod tests { _schedule: &Schedule, _gas_meter: &mut GasMeter, ) -> Result { - Self::from_storage_noinstr(code_hash) - } - - fn from_storage_noinstr(code_hash: CodeHash) -> Result { - LOADER.with(|loader| { - loader - .borrow_mut() - .map - .get(&code_hash) - .cloned() - .ok_or(Error::::CodeNotFound.into()) + Loader::mutate(|loader| { + loader.map.get(&code_hash).cloned().ok_or(Error::::CodeNotFound.into()) }) } - fn add_user( - code_hash: CodeHash, - _: &mut GasMeter, - ) -> Result<(), DispatchError> { - MockLoader::increment_refcount(code_hash); - Ok(()) + fn add_user(code_hash: CodeHash) -> Result<(), DispatchError> { + MockLoader::increment_refcount(code_hash) } - fn remove_user( - code_hash: CodeHash, - _: &mut GasMeter, - ) -> Result<(), DispatchError> { + fn remove_user(code_hash: CodeHash) { MockLoader::decrement_refcount(code_hash); - Ok(()) } fn execute>( @@ -1228,7 +1554,7 @@ mod tests { input_data: Vec, ) -> ExecResult { if let &Constructor = function { - MockLoader::increment_refcount(self.code_hash); + Self::add_user(self.code_hash).unwrap(); } if function == &self.func_type { (self.func)(MockCtx { ext, input_data }, &self) @@ -1245,17 +1571,13 @@ mod tests { 0 } - fn aggregate_code_len(&self) -> u32 { - 0 - } - - fn refcount(&self) -> u32 { - self.refcount as u32 + fn is_deterministic(&self) -> bool { + true } } fn exec_success() -> ExecResult { - Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }) + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) } fn exec_trapped() -> ExecResult { @@ -1264,28 +1586,39 @@ mod tests { #[test] fn it_works() { - thread_local! { - static TEST_DATA: RefCell> = RefCell::new(vec![0]); + parameter_types! { + static TestData: Vec = vec![0]; } let value = Default::default(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let exec_ch = MockLoader::insert(Call, |_ctx, _executable| { - TEST_DATA.with(|data| data.borrow_mut().push(1)); + TestData::mutate(|data| data.push(1)); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, exec_ch); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), value).unwrap(); assert_matches!( - MockStack::run_call(ALICE, BOB, &mut gas_meter, &schedule, value, vec![], None,), + MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + value, + vec![], + None, + Determinism::Deterministic, + ), Ok(_) ); }); - TEST_DATA.with(|data| assert_eq!(*data.borrow(), vec![0, 1])); + assert_eq!(TestData::get(), vec![0, 1]); } #[test] @@ -1299,7 +1632,7 @@ mod tests { set_balance(&origin, 100); set_balance(&dest, 0); - MockStack::transfer(true, false, &origin, &dest, 55).unwrap(); + MockStack::transfer(ExistenceRequirement::KeepAlive, &origin, &dest, 55).unwrap(); assert_eq!(get_balance(&origin), 45); assert_eq!(get_balance(&dest), 55); @@ -1307,50 +1640,131 @@ mod tests { } #[test] - fn changes_are_reverted_on_failing_call() { - // This test verifies that changes are reverted on a call which fails (or equally, returns - // a non-zero status code). + fn correct_transfer_on_call() { let origin = ALICE; let dest = BOB; + let value = 55; - let return_ch = MockLoader::insert(Call, |_, _| { - Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Bytes(Vec::new()) }) + let success_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); - place_contract(&dest, return_ch); + place_contract(&dest, success_ch); set_balance(&origin, 100); let balance = get_balance(&dest); + let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 55).unwrap(); - let output = MockStack::run_call( + let _ = MockStack::run_call( origin.clone(), dest.clone(), &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, - 55, + value, vec![], None, + Determinism::Deterministic, ) .unwrap(); - assert!(!output.is_success()); - assert_eq!(get_balance(&origin), 100); - assert_eq!(get_balance(&dest), balance); + assert_eq!(get_balance(&origin), 100 - value); + assert_eq!(get_balance(&dest), balance + value); }); } #[test] - fn balance_too_low() { - // This test verifies that a contract can't send value if it's - // balance is too low. + fn correct_transfer_on_delegate_call() { let origin = ALICE; let dest = BOB; + let value = 35; - ExtBuilder::default().build().execute_with(|| { - set_balance(&origin, 0); + let success_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + }); - let result = MockStack::transfer(false, false, &origin, &dest, 100); + let delegate_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + let _ = ctx.ext.delegate_call(success_ch, Vec::new())?; + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&dest, delegate_ch); + set_balance(&origin, 100); + let balance = get_balance(&dest); + let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 55).unwrap(); + + let _ = MockStack::run_call( + origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + value, + vec![], + None, + Determinism::Deterministic, + ) + .unwrap(); + + assert_eq!(get_balance(&origin), 100 - value); + assert_eq!(get_balance(&dest), balance + value); + }); + } + + #[test] + fn changes_are_reverted_on_failing_call() { + // This test verifies that changes are reverted on a call which fails (or equally, returns + // a non-zero status code). + let origin = ALICE; + let dest = BOB; + + let return_ch = MockLoader::insert(Call, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() }) + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&dest, return_ch); + set_balance(&origin, 100); + let balance = get_balance(&dest); + let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 55).unwrap(); + + let output = MockStack::run_call( + origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 55, + vec![], + None, + Determinism::Deterministic, + ) + .unwrap(); + + assert!(output.did_revert()); + assert_eq!(get_balance(&origin), 100); + assert_eq!(get_balance(&dest), balance); + }); + } + + #[test] + fn balance_too_low() { + // This test verifies that a contract can't send value if it's + // balance is too low. + let origin = ALICE; + let dest = BOB; + + ExtBuilder::default().build().execute_with(|| { + set_balance(&origin, 0); + + let result = MockStack::transfer(ExistenceRequirement::KeepAlive, &origin, &dest, 100); assert_eq!(result, Err(Error::::TransferFailed.into())); assert_eq!(get_balance(&origin), 0); @@ -1365,26 +1779,29 @@ mod tests { let origin = ALICE; let dest = BOB; let return_ch = MockLoader::insert(Call, |_, _| { - Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(vec![1, 2, 3, 4]) }) + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] }) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); + let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 0).unwrap(); place_contract(&BOB, return_ch); let result = MockStack::run_call( origin, dest, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, 0, vec![], None, + Determinism::Deterministic, ); let output = result.unwrap(); - assert!(output.is_success()); - assert_eq!(output.data, Bytes(vec![1, 2, 3, 4])); + assert!(!output.did_revert()); + assert_eq!(output.data, vec![1, 2, 3, 4]); }); } @@ -1395,26 +1812,29 @@ mod tests { let origin = ALICE; let dest = BOB; let return_ch = MockLoader::insert(Call, |_, _| { - Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Bytes(vec![1, 2, 3, 4]) }) + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] }) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, return_ch); + let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 0).unwrap(); let result = MockStack::run_call( origin, dest, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, 0, vec![], None, + Determinism::Deterministic, ); let output = result.unwrap(); - assert!(!output.is_success()); - assert_eq!(output.data, Bytes(vec![1, 2, 3, 4])); + assert!(output.did_revert()); + assert_eq!(output.data, vec![1, 2, 3, 4]); }); } @@ -1429,15 +1849,18 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, input_data_ch); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); let result = MockStack::run_call( ALICE, BOB, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, 0, vec![1, 2, 3, 4], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); }); @@ -1453,19 +1876,21 @@ mod tests { // This one tests passing the input data into a contract via instantiate. ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); - let subsistence = Contracts::::subsistence_threshold(); + let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(input_data_ch, &schedule, &mut gas_meter).unwrap(); - - set_balance(&ALICE, subsistence * 10); + set_balance(&ALICE, min_balance * 1000); + let mut storage_meter = + storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap(); let result = MockStack::run_instantiate( ALICE, executable, &mut gas_meter, + &mut storage_meter, &schedule, - subsistence * 3, + min_balance, vec![1, 2, 3, 4], &[], None, @@ -1478,16 +1903,15 @@ mod tests { fn max_depth() { // This test verifies that when we reach the maximal depth creation of an // yet another context fails. - thread_local! { - static REACHED_BOTTOM: RefCell = RefCell::new(false); + parameter_types! { + static ReachedBottom: bool = false; } let value = Default::default(); let recurse_ch = MockLoader::insert(Call, |ctx, _| { // Try to call into yourself. - let r = ctx.ext.call(0, BOB, 0, vec![], true); + let r = ctx.ext.call(Weight::zero(), BOB, 0, vec![], true); - REACHED_BOTTOM.with(|reached_bottom| { - let mut reached_bottom = reached_bottom.borrow_mut(); + ReachedBottom::mutate(|reached_bottom| { if !*reached_bottom { // We are first time here, it means we just reached bottom. // Verify that we've got proper error and set `reached_bottom`. @@ -1506,15 +1930,18 @@ mod tests { let schedule = ::Schedule::get(); set_balance(&BOB, 1); place_contract(&BOB, recurse_ch); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), value).unwrap(); let result = MockStack::run_call( ALICE, BOB, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, value, vec![], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); @@ -1526,24 +1953,22 @@ mod tests { let origin = ALICE; let dest = BOB; - thread_local! { - static WITNESSED_CALLER_BOB: RefCell>> = RefCell::new(None); - static WITNESSED_CALLER_CHARLIE: RefCell>> = RefCell::new(None); + parameter_types! { + static WitnessedCallerBob: Option> = None; + static WitnessedCallerCharlie: Option> = None; } let bob_ch = MockLoader::insert(Call, |ctx, _| { // Record the caller for bob. - WITNESSED_CALLER_BOB - .with(|caller| *caller.borrow_mut() = Some(ctx.ext.caller().clone())); + WitnessedCallerBob::mutate(|caller| *caller = Some(ctx.ext.caller().clone())); // Call into CHARLIE contract. - assert_matches!(ctx.ext.call(0, CHARLIE, 0, vec![], true), Ok(_)); + assert_matches!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), Ok(_)); exec_success() }); let charlie_ch = MockLoader::insert(Call, |ctx, _| { // Record the caller for charlie. - WITNESSED_CALLER_CHARLIE - .with(|caller| *caller.borrow_mut() = Some(ctx.ext.caller().clone())); + WitnessedCallerCharlie::mutate(|caller| *caller = Some(ctx.ext.caller().clone())); exec_success() }); @@ -1551,22 +1976,149 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&dest, bob_ch); place_contract(&CHARLIE, charlie_ch); + let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 0).unwrap(); let result = MockStack::run_call( origin.clone(), dest.clone(), &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic, + ); + + assert_matches!(result, Ok(_)); + }); + + assert_eq!(WitnessedCallerBob::get(), Some(origin)); + assert_eq!(WitnessedCallerCharlie::get(), Some(dest)); + } + + #[test] + fn is_contract_returns_proper_values() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + // Verify that BOB is a contract + assert!(ctx.ext.is_contract(&BOB)); + // Verify that ALICE is not a contract + assert!(!ctx.ext.is_contract(&ALICE)); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, bob_ch); + + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let result = MockStack::run_call( + ALICE, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, 0, vec![], None, + Determinism::Deterministic, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn code_hash_returns_proper_values() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + // ALICE is not a contract and hence she does not have a code_hash + assert!(ctx.ext.code_hash(&ALICE).is_none()); + // BOB is a contract and hence he has a code_hash + assert!(ctx.ext.code_hash(&BOB).is_some()); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, code_bob); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + // ALICE (not contract) -> BOB (contract) + let result = MockStack::run_call( + ALICE, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![0], + None, + Determinism::Deterministic, ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn own_code_hash_returns_proper_values() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + let code_hash = ctx.ext.code_hash(&BOB).unwrap(); + assert_eq!(*ctx.ext.own_code_hash(), code_hash); + exec_success() + }); + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, bob_ch); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + // ALICE (not contract) -> BOB (contract) + let result = MockStack::run_call( + ALICE, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![0], + None, + Determinism::Deterministic, + ); assert_matches!(result, Ok(_)); }); + } + + #[test] + fn caller_is_origin_returns_proper_values() { + let code_charlie = MockLoader::insert(Call, |ctx, _| { + // BOB is not the origin of the stack call + assert!(!ctx.ext.caller_is_origin()); + exec_success() + }); + + let code_bob = MockLoader::insert(Call, |ctx, _| { + // ALICE is the origin of the call stack + assert!(ctx.ext.caller_is_origin()); + // BOB calls CHARLIE + ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true) + }); - WITNESSED_CALLER_BOB.with(|caller| assert_eq!(*caller.borrow(), Some(origin))); - WITNESSED_CALLER_CHARLIE.with(|caller| assert_eq!(*caller.borrow(), Some(dest))); + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + // ALICE -> BOB (caller is origin) -> CHARLIE (caller is not origin) + let result = MockStack::run_call( + ALICE, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![0], + None, + Determinism::Deterministic, + ); + assert_matches!(result, Ok(_)); + }); } #[test] @@ -1576,7 +2128,7 @@ mod tests { assert_eq!(*ctx.ext.address(), BOB); // Call into charlie contract. - assert_matches!(ctx.ext.call(0, CHARLIE, 0, vec![], true), Ok(_)); + assert_matches!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), Ok(_)); exec_success() }); let charlie_ch = MockLoader::insert(Call, |ctx, _| { @@ -1588,15 +2140,18 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&BOB, bob_ch); place_contract(&CHARLIE, charlie_ch); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); let result = MockStack::run_call( ALICE, BOB, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, 0, vec![], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); @@ -1612,14 +2167,16 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap(); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); assert_matches!( MockStack::run_instantiate( ALICE, executable, &mut gas_meter, + &mut storage_meter, &schedule, - 0, // <- zero endowment + 0, // <- zero value vec![], &[], None, @@ -1632,28 +2189,32 @@ mod tests { #[test] fn instantiation_work_with_success_output() { let dummy_ch = MockLoader::insert(Constructor, |_, _| { - Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(vec![80, 65, 83, 83]) }) + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] }) }); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let schedule = ::Schedule::get(); + let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap(); - set_balance(&ALICE, 1000); + set_balance(&ALICE, min_balance * 1000); + let mut storage_meter = + storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap(); let instantiated_contract_address = assert_matches!( MockStack::run_instantiate( ALICE, executable, &mut gas_meter, + &mut storage_meter, &schedule, - 100, + min_balance, vec![], &[], None, ), - Ok((address, ref output)) if output.data == Bytes(vec![80, 65, 83, 83]) => address + Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address ); // Check that the newly created account has the expected code hash and @@ -1662,35 +2223,42 @@ mod tests { Storage::::code_hash(&instantiated_contract_address).unwrap(), dummy_ch ); - assert_eq!(&events(), &[Event::Instantiated(ALICE, instantiated_contract_address)]); + assert_eq!( + &events(), + &[Event::Instantiated { deployer: ALICE, contract: instantiated_contract_address }] + ); }); } #[test] fn instantiation_fails_with_failing_output() { let dummy_ch = MockLoader::insert(Constructor, |_, _| { - Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Bytes(vec![70, 65, 73, 76]) }) + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] }) }); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let schedule = ::Schedule::get(); + let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap(); - set_balance(&ALICE, 1000); + set_balance(&ALICE, min_balance * 1000); + let mut storage_meter = + storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap(); let instantiated_contract_address = assert_matches!( MockStack::run_instantiate( ALICE, executable, &mut gas_meter, + &mut storage_meter, &schedule, - 100, + min_balance, vec![], &[], None, ), - Ok((address, ref output)) if output.data == Bytes(vec![70, 65, 73, 76]) => address + Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address ); // Check that the account has not been created. @@ -1704,16 +2272,15 @@ mod tests { let dummy_ch = MockLoader::insert(Call, |_, _| exec_success()); let instantiated_contract_address = Rc::new(RefCell::new(None::>)); let instantiator_ch = MockLoader::insert(Call, { - let dummy_ch = dummy_ch.clone(); let instantiated_contract_address = Rc::clone(&instantiated_contract_address); move |ctx, _| { // Instantiate a contract and save it's address in `instantiated_contract_address`. let (address, output) = ctx .ext .instantiate( - 0, + Weight::zero(), dummy_ch, - Contracts::::subsistence_threshold() * 3, + ::Currency::minimum_balance(), vec![], &[48, 49, 50], ) @@ -1726,18 +2293,24 @@ mod tests { ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let schedule = ::Schedule::get(); - set_balance(&ALICE, Contracts::::subsistence_threshold() * 100); + let min_balance = ::Currency::minimum_balance(); + set_balance(&ALICE, min_balance * 100); place_contract(&BOB, instantiator_ch); + let mut storage_meter = + storage::meter::Meter::new(&ALICE, Some(min_balance * 10), min_balance * 10) + .unwrap(); assert_matches!( MockStack::run_call( ALICE, BOB, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, - 20, + min_balance * 10, vec![], None, + Determinism::Deterministic, ), Ok(_) ); @@ -1751,7 +2324,13 @@ mod tests { Storage::::code_hash(&instantiated_contract_address).unwrap(), dummy_ch ); - assert_eq!(&events(), &[Event::Instantiated(BOB, instantiated_contract_address)]); + assert_eq!( + &events(), + &[ + Event::Instantiated { deployer: BOB, contract: instantiated_contract_address }, + Event::Called { caller: ALICE, contract: BOB }, + ] + ); }); } @@ -1759,14 +2338,13 @@ mod tests { fn instantiation_traps() { let dummy_ch = MockLoader::insert(Constructor, |_, _| Err("It's a trap!".into())); let instantiator_ch = MockLoader::insert(Call, { - let dummy_ch = dummy_ch.clone(); move |ctx, _| { // Instantiate a contract and save it's address in `instantiated_contract_address`. assert_matches!( ctx.ext.instantiate( - 0, + Weight::zero(), dummy_ch, - Contracts::::subsistence_threshold(), + ::Currency::minimum_balance(), vec![], &[], ), @@ -1785,23 +2363,26 @@ mod tests { set_balance(&ALICE, 1000); set_balance(&BOB, 100); place_contract(&BOB, instantiator_ch); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(100), 0).unwrap(); assert_matches!( MockStack::run_call( ALICE, BOB, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, - 20, + 0, vec![], None, + Determinism::Deterministic, ), Ok(_) ); // The contract wasn't instantiated so we don't expect to see an instantiation // event here. - assert_eq!(&events(), &[]); + assert_eq!(&events(), &[Event::Called { caller: ALICE, contract: BOB },]); }); } @@ -1818,12 +2399,14 @@ mod tests { let executable = MockExecutable::from_storage(terminate_ch, &schedule, &mut gas_meter).unwrap(); set_balance(&ALICE, 1000); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(100), 100).unwrap(); assert_eq!( MockStack::run_instantiate( ALICE, executable, &mut gas_meter, + &mut storage_meter, &schedule, 100, vec![], @@ -1839,10 +2422,6 @@ mod tests { #[test] fn in_memory_changes_not_discarded() { - // Remove this assert and fill out the "DO" stubs once fields are added to the - // contract info that can be modified during exection. - assert!(!CONTRACT_INFO_CAN_CHANGE); - // Call stack: BOB -> CHARLIE (trap) -> BOB' (success) // This tests verfies some edge case of the contract info cache: // We change some value in our contract info before calling into a contract @@ -1853,14 +2432,16 @@ mod tests { // are made before calling into CHARLIE are not discarded. let code_bob = MockLoader::insert(Call, |ctx, _| { if ctx.input_data[0] == 0 { - // DO: modify medata (ContractInfo) of own contract through ctx.ext functions - assert_eq!(ctx.ext.call(0, CHARLIE, 0, vec![], true), exec_trapped()); - // DO: check that the value is not discarded (query via ctx.ext) + let info = ctx.ext.contract_info(); + assert_eq!(info.storage_byte_deposit, 0); + info.storage_byte_deposit = 42; + assert_eq!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), exec_trapped()); + assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42); } exec_success() }); let code_charlie = MockLoader::insert(Call, |ctx, _| { - assert!(ctx.ext.call(0, BOB, 0, vec![99], true).is_ok()); + assert!(ctx.ext.call(Weight::zero(), BOB, 0, vec![99], true).is_ok()); exec_trapped() }); @@ -1869,15 +2450,18 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); let result = MockStack::run_call( ALICE, BOB, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, 0, vec![0], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); }); @@ -1887,7 +2471,7 @@ mod tests { fn recursive_call_during_constructor_fails() { let code = MockLoader::insert(Constructor, |ctx, _| { assert_matches!( - ctx.ext.call(0, ctx.ext.address().clone(), 0, vec![], true), + ctx.ext.call(Weight::zero(), ctx.ext.address().clone(), 0, vec![], true), Err(ExecError{error, ..}) if error == >::ContractNotFound.into() ); exec_success() @@ -1896,18 +2480,20 @@ mod tests { // This one tests passing the input data into a contract via instantiate. ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); - let subsistence = Contracts::::subsistence_threshold(); + let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(code, &schedule, &mut gas_meter).unwrap(); - - set_balance(&ALICE, subsistence * 10); + set_balance(&ALICE, min_balance * 1000); + let mut storage_meter = + storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap(); let result = MockStack::run_instantiate( ALICE, executable, &mut gas_meter, + &mut storage_meter, &schedule, - subsistence * 3, + min_balance, vec![], &[], None, @@ -1919,63 +2505,113 @@ mod tests { #[test] fn printing_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); + ctx.ext + .append_debug_buffer("This is a test") + .expect("Maximum allowed debug buffer size exhausted!"); + ctx.ext + .append_debug_buffer("More text") + .expect("Maximum allowed debug buffer size exhausted!"); exec_success() }); - let mut debug_buffer = Vec::new(); + let mut debug_buffer = DebugBufferVec::::try_from(Vec::new()).unwrap(); ExtBuilder::default().build().execute_with(|| { - let subsistence = Contracts::::subsistence_threshold(); + let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, subsistence * 10); + set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); MockStack::run_call( ALICE, BOB, &mut gas_meter, + &mut storage_meter, &schedule, 0, vec![], Some(&mut debug_buffer), + Determinism::Deterministic, ) .unwrap(); }); - assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text"); + assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); } #[test] fn printing_works_on_fail() { let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); + ctx.ext + .append_debug_buffer("This is a test") + .expect("Maximum allowed debug buffer size exhausted!"); + ctx.ext + .append_debug_buffer("More text") + .expect("Maximum allowed debug buffer size exhausted!"); exec_trapped() }); - let mut debug_buffer = Vec::new(); + let mut debug_buffer = DebugBufferVec::::try_from(Vec::new()).unwrap(); ExtBuilder::default().build().execute_with(|| { - let subsistence = Contracts::::subsistence_threshold(); + let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, subsistence * 10); + set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); let result = MockStack::run_call( ALICE, BOB, &mut gas_meter, + &mut storage_meter, &schedule, 0, vec![], Some(&mut debug_buffer), + Determinism::Deterministic, ); assert!(result.is_err()); }); - assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text"); + assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); + } + + #[test] + fn debug_buffer_is_limited() { + let code_hash = MockLoader::insert(Call, move |ctx, _| { + ctx.ext.append_debug_buffer("overflowing bytes")?; + exec_success() + }); + + // Pre-fill the buffer up to its limit + let mut debug_buffer = + DebugBufferVec::::try_from(vec![0u8; DebugBufferVec::::bound()]).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let schedule: Schedule = ::Schedule::get(); + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + assert_err!( + MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + Some(&mut debug_buffer), + Determinism::Deterministic, + ) + .map_err(|e| e.error), + Error::::DebugBufferExhausted + ); + }); } #[test] @@ -1983,7 +2619,7 @@ mod tests { // call the contract passed as input with disabled reentry let code_bob = MockLoader::insert(Call, |ctx, _| { let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap(); - ctx.ext.call(0, dest, 0, vec![], false) + ctx.ext.call(Weight::zero(), dest, 0, vec![], false) }); let code_charlie = MockLoader::insert(Call, |_, _| exec_success()); @@ -1992,16 +2628,19 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); // Calling another contract should succeed assert_ok!(MockStack::run_call( ALICE, BOB, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, 0, CHARLIE.encode(), None, + Determinism::Deterministic )); // Calling into oneself fails @@ -2010,10 +2649,12 @@ mod tests { ALICE, BOB, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, 0, BOB.encode(), None, + Determinism::Deterministic ) .map_err(|e| e.error), >::ReentranceDenied, @@ -2025,7 +2666,7 @@ mod tests { fn call_deny_reentry() { let code_bob = MockLoader::insert(Call, |ctx, _| { if ctx.input_data[0] == 0 { - ctx.ext.call(0, CHARLIE, 0, vec![], false) + ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], false) } else { exec_success() } @@ -2033,12 +2674,13 @@ mod tests { // call BOB with input set to '1' let code_charlie = - MockLoader::insert(Call, |ctx, _| ctx.ext.call(0, BOB, 0, vec![1], true)); + MockLoader::insert(Call, |ctx, _| ctx.ext.call(Weight::zero(), BOB, 0, vec![1], true)); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); // BOB -> CHARLIE -> BOB fails as BOB denies reentry. assert_err!( @@ -2046,10 +2688,12 @@ mod tests { ALICE, BOB, &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, &schedule, 0, vec![0], None, + Determinism::Deterministic ) .map_err(|e| e.error), >::ReentranceDenied, @@ -2060,7 +2704,7 @@ mod tests { #[test] fn call_runtime_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { - let call = Call::System(frame_system::Call::remark_with_event { + let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: b"Hello World".to_vec(), }); ctx.ext.call_runtime(call).unwrap(); @@ -2068,22 +2712,47 @@ mod tests { }); ExtBuilder::default().build().execute_with(|| { - let subsistence = Contracts::::subsistence_threshold(); + let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, subsistence * 10); + set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); System::reset_events(); - MockStack::run_call(ALICE, BOB, &mut gas_meter, &schedule, 0, vec![], None).unwrap(); + MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic, + ) + .unwrap(); let remark_hash = ::Hashing::hash(b"Hello World"); assert_eq!( System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: MetaEvent::System(frame_system::Event::Remarked(BOB, remark_hash)), - topics: vec![], - },] + vec![ + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::System(frame_system::Event::Remarked { + sender: BOB, + hash: remark_hash + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: BOB, + }), + topics: vec![hash(&ALICE), hash(&BOB)], + }, + ] ); }); } @@ -2097,16 +2766,20 @@ mod tests { // remark should still be allowed let allowed_call = - Call::System(SysCall::remark_with_event { remark: b"Hello".to_vec() }); + RuntimeCall::System(SysCall::remark_with_event { remark: b"Hello".to_vec() }); // transfers are disallowed by the `TestFiler` (see below) - let forbidden_call = Call::Balances(BalanceCall::transfer { dest: CHARLIE, value: 22 }); + let forbidden_call = + RuntimeCall::Balances(BalanceCall::transfer { dest: CHARLIE, value: 22 }); // simple cases: direct call - assert_err!(ctx.ext.call_runtime(forbidden_call.clone()), BadOrigin); + assert_err!( + ctx.ext.call_runtime(forbidden_call.clone()), + frame_system::Error::::CallFiltered + ); // as part of a patch: return is OK (but it interrupted the batch) - assert_ok!(ctx.ext.call_runtime(Call::Utility(UtilCall::batch { + assert_ok!(ctx.ext.call_runtime(RuntimeCall::Utility(UtilCall::batch { calls: vec![allowed_call.clone(), forbidden_call, allowed_call] })),); @@ -2117,18 +2790,30 @@ mod tests { }); TestFilter::set_filter(|call| match call { - Call::Balances(pallet_balances::Call::transfer { .. }) => false, + RuntimeCall::Balances(pallet_balances::Call::transfer { .. }) => false, _ => true, }); ExtBuilder::default().build().execute_with(|| { - let subsistence = Contracts::::subsistence_threshold(); + let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, subsistence * 10); + set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); System::reset_events(); - MockStack::run_call(ALICE, BOB, &mut gas_meter, &schedule, 0, vec![], None).unwrap(); + MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic, + ) + .unwrap(); let remark_hash = ::Hashing::hash(b"Hello"); assert_eq!( @@ -2136,7 +2821,10 @@ mod tests { vec![ EventRecord { phase: Phase::Initialization, - event: MetaEvent::System(frame_system::Event::Remarked(BOB, remark_hash)), + event: MetaEvent::System(frame_system::Event::Remarked { + sender: BOB, + hash: remark_hash + }), topics: vec![], }, EventRecord { @@ -2146,14 +2834,590 @@ mod tests { }, EventRecord { phase: Phase::Initialization, - event: MetaEvent::Utility(pallet_utility::Event::BatchInterrupted( - 1, - BadOrigin.into() - ),), + event: MetaEvent::Utility(pallet_utility::Event::BatchInterrupted { + index: 1, + error: frame_system::Error::::CallFiltered.into() + },), topics: vec![], }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: BOB, + }), + topics: vec![hash(&ALICE), hash(&BOB)], + }, ] ); }); } + + #[test] + fn nonce() { + let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped()); + let success_code = MockLoader::insert(Constructor, |_, _| exec_success()); + let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| { + ctx.ext + .instantiate( + Weight::zero(), + fail_code, + ctx.ext.minimum_balance() * 100, + vec![], + &[], + ) + .ok(); + exec_success() + }); + let succ_succ_code = MockLoader::insert(Constructor, move |ctx, _| { + let (account_id, _) = ctx + .ext + .instantiate( + Weight::zero(), + success_code, + ctx.ext.minimum_balance() * 100, + vec![], + &[], + ) + .unwrap(); + + // a plain call should not influence the account counter + ctx.ext.call(Weight::zero(), account_id, 0, vec![], false).unwrap(); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let fail_executable = + MockExecutable::from_storage(fail_code, &schedule, &mut gas_meter).unwrap(); + let success_executable = + MockExecutable::from_storage(success_code, &schedule, &mut gas_meter).unwrap(); + let succ_fail_executable = + MockExecutable::from_storage(succ_fail_code, &schedule, &mut gas_meter).unwrap(); + let succ_succ_executable = + MockExecutable::from_storage(succ_succ_code, &schedule, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 1000); + let mut storage_meter = + storage::meter::Meter::new(&ALICE, Some(min_balance * 500), min_balance * 100) + .unwrap(); + + MockStack::run_instantiate( + ALICE, + fail_executable, + &mut gas_meter, + &mut storage_meter, + &schedule, + min_balance * 100, + vec![], + &[], + None, + ) + .ok(); + assert_eq!(>::get(), 0); + + assert_ok!(MockStack::run_instantiate( + ALICE, + success_executable, + &mut gas_meter, + &mut storage_meter, + &schedule, + min_balance * 100, + vec![], + &[], + None, + )); + assert_eq!(>::get(), 1); + + assert_ok!(MockStack::run_instantiate( + ALICE, + succ_fail_executable, + &mut gas_meter, + &mut storage_meter, + &schedule, + min_balance * 200, + vec![], + &[], + None, + )); + assert_eq!(>::get(), 2); + + assert_ok!(MockStack::run_instantiate( + ALICE, + succ_succ_executable, + &mut gas_meter, + &mut storage_meter, + &schedule, + min_balance * 200, + vec![], + &[], + None, + )); + assert_eq!(>::get(), 4); + }); + } + + #[test] + fn set_storage_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + // Write + assert_eq!( + ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&[2; 32], Some(vec![4, 5, 6]), true), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.set_storage(&[3; 32], None, false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[4; 32], None, true), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[5; 32], Some(vec![]), false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[6; 32], Some(vec![]), true), Ok(WriteOutcome::New)); + + // Overwrite + assert_eq!( + ctx.ext.set_storage(&[1; 32], Some(vec![42]), false), + Ok(WriteOutcome::Overwritten(3)) + ); + assert_eq!( + ctx.ext.set_storage(&[2; 32], Some(vec![48]), true), + Ok(WriteOutcome::Taken(vec![4, 5, 6])) + ); + assert_eq!(ctx.ext.set_storage(&[3; 32], None, false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[4; 32], None, true), Ok(WriteOutcome::New)); + assert_eq!( + ctx.ext.set_storage(&[5; 32], Some(vec![]), false), + Ok(WriteOutcome::Overwritten(0)) + ); + assert_eq!( + ctx.ext.set_storage(&[6; 32], Some(vec![]), true), + Ok(WriteOutcome::Taken(vec![])) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic + )); + }); + } + + #[test] + fn set_storage_transparent_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + // Write + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([1; 64].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([2; 19].to_vec()).unwrap(), + Some(vec![4, 5, 6]), + true + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([3; 19].to_vec()).unwrap(), + None, + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([4; 64].to_vec()).unwrap(), + None, + true + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([5; 30].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([6; 128].to_vec()).unwrap(), + Some(vec![]), + true + ), + Ok(WriteOutcome::New) + ); + + // Overwrite + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([1; 64].to_vec()).unwrap(), + Some(vec![42, 43, 44]), + false + ), + Ok(WriteOutcome::Overwritten(3)) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([2; 19].to_vec()).unwrap(), + Some(vec![48]), + true + ), + Ok(WriteOutcome::Taken(vec![4, 5, 6])) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([3; 19].to_vec()).unwrap(), + None, + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([4; 64].to_vec()).unwrap(), + None, + true + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([5; 30].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::Overwritten(0)) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([6; 128].to_vec()).unwrap(), + Some(vec![]), + true + ), + Ok(WriteOutcome::Taken(vec![])) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic + )); + }); + } + + #[test] + fn get_storage_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.set_storage(&[2; 32], Some(vec![]), false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.get_storage(&[1; 32]), Some(vec![1, 2, 3])); + assert_eq!(ctx.ext.get_storage(&[2; 32]), Some(vec![])); + assert_eq!(ctx.ext.get_storage(&[3; 32]), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic + )); + }); + } + + #[test] + fn get_storage_size_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.set_storage(&[2; 32], Some(vec![]), false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.get_storage_size(&[1; 32]), Some(3)); + assert_eq!(ctx.ext.get_storage_size(&[2; 32]), Some(0)); + assert_eq!(ctx.ext.get_storage_size(&[3; 32]), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic + )); + }); + } + + #[test] + fn get_storage_transparent_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([1; 19].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([2; 16].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.get_storage_transparent( + &VarSizedKey::::try_from([1; 19].to_vec()).unwrap() + ), + Some(vec![1, 2, 3]) + ); + assert_eq!( + ctx.ext.get_storage_transparent( + &VarSizedKey::::try_from([2; 16].to_vec()).unwrap() + ), + Some(vec![]) + ); + assert_eq!( + ctx.ext.get_storage_transparent( + &VarSizedKey::::try_from([3; 8].to_vec()).unwrap() + ), + None + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic + )); + }); + } + + #[test] + fn get_storage_size_transparent_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([1; 19].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([2; 16].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.get_storage_size_transparent( + &VarSizedKey::::try_from([1; 19].to_vec()).unwrap() + ), + Some(3) + ); + assert_eq!( + ctx.ext.get_storage_size_transparent( + &VarSizedKey::::try_from([2; 16].to_vec()).unwrap() + ), + Some(0) + ); + assert_eq!( + ctx.ext.get_storage_size_transparent( + &VarSizedKey::::try_from([3; 8].to_vec()).unwrap() + ), + None + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic + )); + }); + } + + #[test] + fn ecdsa_to_eth_address_returns_proper_value() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + let pubkey_compressed = array_bytes::hex2array_unchecked( + "028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd91", + ); + assert_eq!( + ctx.ext.ecdsa_to_eth_address(&pubkey_compressed).unwrap(), + array_bytes::hex2array_unchecked::<20>("09231da7b19A016f9e576d23B16277062F4d46A8") + ); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, bob_ch); + + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let result = MockStack::run_call( + ALICE, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn nonce_api_works() { + let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped()); + let success_code = MockLoader::insert(Constructor, |_, _| exec_success()); + let code_hash = MockLoader::insert(Call, move |ctx, _| { + // It is set to one when this contract was instantiated by `place_contract` + assert_eq!(ctx.ext.nonce(), 1); + // Should not change without any instantation in-between + assert_eq!(ctx.ext.nonce(), 1); + // Should not change with a failed instantiation + assert_err!( + ctx.ext.instantiate(Weight::zero(), fail_code, 0, vec![], &[],), + ExecError { + error: >::ContractTrapped.into(), + origin: ErrorOrigin::Callee + } + ); + assert_eq!(ctx.ext.nonce(), 1); + // Successful instantation increments + ctx.ext.instantiate(Weight::zero(), success_code, 0, vec![], &[]).unwrap(); + assert_eq!(ctx.ext.nonce(), 2); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic + )); + }); + } } diff --git a/frame/contracts/src/gas.rs b/frame/contracts/src/gas.rs index 38d18c1e24c19..c0cc2db2aa3eb 100644 --- a/frame/contracts/src/gas.rs +++ b/frame/contracts/src/gas.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -107,32 +107,45 @@ where /// /// Passing `0` as amount is interpreted as "all remaining gas". pub fn nested(&mut self, amount: Weight) -> Result { - let amount = if amount == 0 { self.gas_left } else { amount }; - // NOTE that it is ok to allocate all available gas since it still ensured // by `charge` that it doesn't reach zero. - if self.gas_left < amount { - Err(>::OutOfGas.into()) - } else { - self.gas_left = self.gas_left - amount; - Ok(GasMeter::new(amount)) - } + let amount = Weight::from_parts( + if amount.ref_time().is_zero() { + self.gas_left().ref_time() + } else { + amount.ref_time() + }, + if amount.proof_size().is_zero() { + self.gas_left().proof_size() + } else { + amount.proof_size() + }, + ); + self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| >::OutOfGas)?; + Ok(GasMeter::new(amount)) } /// Absorb the remaining gas of a nested meter after we are done using it. pub fn absorb_nested(&mut self, nested: Self) { - if self.gas_left == 0 { + if self.gas_left.ref_time().is_zero() { // All of the remaining gas was inherited by the nested gas meter. When absorbing // we can therefore safely inherit the lowest gas that the nested gas meter experienced // as long as it is lower than the lowest gas that was experienced by the parent. // We cannot call `self.gas_left_lowest()` here because in the state that this // code is run the parent gas meter has `0` gas left. - self.gas_left_lowest = nested.gas_left_lowest().min(self.gas_left_lowest); + *self.gas_left_lowest.ref_time_mut() = + nested.gas_left_lowest().ref_time().min(self.gas_left_lowest.ref_time()); } else { // The nested gas meter was created with a fixed amount that did not consume all of the // parents (self) gas. The lowest gas that self will experience is when the nested // gas was pre charged with the fixed amount. - self.gas_left_lowest = self.gas_left_lowest(); + *self.gas_left_lowest.ref_time_mut() = self.gas_left_lowest().ref_time(); + } + if self.gas_left.proof_size().is_zero() { + *self.gas_left_lowest.proof_size_mut() = + nested.gas_left_lowest().proof_size().min(self.gas_left_lowest.proof_size()); + } else { + *self.gas_left_lowest.proof_size_mut() = self.gas_left_lowest().proof_size(); } self.gas_left += nested.gas_left; } @@ -155,17 +168,11 @@ where ErasedToken { description: format!("{:?}", token), token: Box::new(token) }; self.tokens.push(erased_tok); } - let amount = token.weight(); - let new_value = self.gas_left.checked_sub(amount); - - // We always consume the gas even if there is not enough gas. - self.gas_left = new_value.unwrap_or_else(Zero::zero); - - match new_value { - Some(_) => Ok(ChargedAmount(amount)), - None => Err(Error::::OutOfGas.into()), - } + // It is OK to not charge anything on failure because we always charge _before_ we perform + // any action + self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| Error::::OutOfGas)?; + Ok(ChargedAmount(amount)) } /// Adjust a previously charged amount down to its actual amount. @@ -227,7 +234,7 @@ where #[cfg(test)] mod tests { - use super::{GasMeter, Token}; + use super::{GasMeter, Token, Weight}; use crate::tests::Test; /// A simple utility macro that helps to match against a @@ -271,20 +278,20 @@ mod tests { #[derive(Copy, Clone, PartialEq, Eq, Debug)] struct SimpleToken(u64); impl Token for SimpleToken { - fn weight(&self) -> u64 { - self.0 + fn weight(&self) -> Weight { + Weight::from_ref_time(self.0) } } #[test] fn it_works() { - let gas_meter = GasMeter::::new(50000); - assert_eq!(gas_meter.gas_left(), 50000); + let gas_meter = GasMeter::::new(Weight::from_ref_time(50000)); + assert_eq!(gas_meter.gas_left(), Weight::from_ref_time(50000)); } #[test] fn tracing() { - let mut gas_meter = GasMeter::::new(50000); + let mut gas_meter = GasMeter::::new(Weight::from_ref_time(50000)); assert!(!gas_meter.charge(SimpleToken(1)).is_err()); let mut tokens = gas_meter.tokens().iter(); @@ -294,31 +301,27 @@ mod tests { // This test makes sure that nothing can be executed if there is no gas. #[test] fn refuse_to_execute_anything_if_zero() { - let mut gas_meter = GasMeter::::new(0); + let mut gas_meter = GasMeter::::new(Weight::zero()); assert!(gas_meter.charge(SimpleToken(1)).is_err()); } - // Make sure that if the gas meter is charged by exceeding amount then not only an error - // returned for that charge, but also for all consequent charges. - // - // This is not strictly necessary, because the execution should be interrupted immediately - // if the gas meter runs out of gas. However, this is just a nice property to have. + // Make sure that the gas meter does not charge in case of overcharger #[test] - fn overcharge_is_unrecoverable() { - let mut gas_meter = GasMeter::::new(200); + fn overcharge_does_not_charge() { + let mut gas_meter = GasMeter::::new(Weight::from_ref_time(200)); // The first charge is should lead to OOG. assert!(gas_meter.charge(SimpleToken(300)).is_err()); - // The gas meter is emptied at this moment, so this should also fail. - assert!(gas_meter.charge(SimpleToken(1)).is_err()); + // The gas meter should still contain the full 200. + assert!(gas_meter.charge(SimpleToken(200)).is_ok()); } // Charging the exact amount that the user paid for should be // possible. #[test] fn charge_exact_amount() { - let mut gas_meter = GasMeter::::new(25); + let mut gas_meter = GasMeter::::new(Weight::from_ref_time(25)); assert!(!gas_meter.charge(SimpleToken(25)).is_err()); } } diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 77efcc6986e64..b76acf9d1db08 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -98,39 +98,108 @@ pub mod weights; #[cfg(test)] mod tests; -pub use crate::{ - exec::Frame, - pallet::*, - schedule::{HostFnWeights, InstructionWeights, Limits, Schedule}, -}; use crate::{ - exec::{Executable, Stack as ExecStack}, + exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, gas::GasMeter, - storage::{ContractInfo, DeletedContract, Storage}, - wasm::PrefabWasmModule, + storage::{meter::Meter as StorageMeter, ContractInfo, DeletedContract, Storage}, + wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate}, weights::WeightInfo, }; +use codec::{Codec, Encode, HasCompact}; use frame_support::{ - dispatch::Dispatchable, - traits::{Contains, Currency, Get, Randomness, StorageVersion, Time}, - weights::{GetDispatchInfo, PostDispatchInfo, Weight}, + dispatch::{Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo}, + ensure, + traits::{ + tokens::fungible::Inspect, ConstU32, Contains, Currency, Get, Randomness, + ReservableCurrency, Time, + }, + weights::{OldWeight, Weight}, + BoundedVec, WeakBoundedVec, }; use frame_system::Pallet as System; use pallet_contracts_primitives::{ - Code, ContractAccessError, ContractExecResult, ContractInstantiateResult, GetStorageResult, - InstantiateReturnValue, + Code, CodeUploadResult, CodeUploadReturnValue, ContractAccessError, ContractExecResult, + ContractInstantiateResult, ExecReturnValue, GetStorageResult, InstantiateReturnValue, + StorageDeposit, }; -use sp_core::{crypto::UncheckedFrom, Bytes}; +use scale_info::TypeInfo; +use sp_core::crypto::UncheckedFrom; use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup}; -use sp_std::prelude::*; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; + +pub use crate::{ + exec::{Frame, VarSizedKey as StorageKey}, + migration::Migration, + pallet::*, + schedule::{HostFnWeights, InstructionWeights, Limits, Schedule}, + wasm::Determinism, +}; type CodeHash = ::Hash; -type TrieId = Vec; +type TrieId = BoundedVec>; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +type CodeVec = BoundedVec::MaxCodeLen>; +type RelaxedCodeVec = WeakBoundedVec::MaxCodeLen>; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +type DebugBufferVec = BoundedVec::MaxDebugBufferLen>; + +/// Used as a sentinel value when reading and writing contract memory. +/// +/// It is usually used to signal `None` to a contract when only a primitive is allowed +/// and we don't want to go through encoding a full Rust type. Using `u32::Max` is a safe +/// sentinel because contracts are never allowed to use such a large amount of resources +/// that this value makes sense for a memory location or length. +const SENTINEL: u32 = u32::MAX; -/// The current storage version. -const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); +/// Provides the contract address generation method. +/// +/// See [`DefaultAddressGenerator`] for the default implementation. +pub trait AddressGenerator { + /// Generate the address of a contract based on the given instantiate parameters. + /// + /// # Note for implementors + /// 1. Make sure that there are no collisions, different inputs never lead to the same output. + /// 2. Make sure that the same inputs lead to the same output. + /// 3. Changing the implementation through a runtime upgrade without a proper storage migration + /// would lead to catastrophic misbehavior. + fn generate_address( + deploying_address: &T::AccountId, + code_hash: &CodeHash, + salt: &[u8], + ) -> T::AccountId; +} + +/// Default address generator. +/// +/// This is the default address generator used by contract instantiation. Its result +/// is only dependant on its inputs. It can therefore be used to reliably predict the +/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There +/// is no CREATE equivalent because CREATE2 is strictly more powerful. +/// +/// Formula: `hash(deploying_address ++ code_hash ++ salt)` +pub struct DefaultAddressGenerator; + +impl AddressGenerator for DefaultAddressGenerator +where + T: frame_system::Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, +{ + fn generate_address( + deploying_address: &T::AccountId, + code_hash: &CodeHash, + salt: &[u8], + ) -> T::AccountId { + let buf: Vec<_> = deploying_address + .as_ref() + .iter() + .chain(code_hash.as_ref()) + .chain(salt) + .cloned() + .collect(); + UncheckedFrom::unchecked_from(T::Hashing::hash(&buf)) + } +} #[frame_support::pallet] pub mod pallet { @@ -138,25 +207,33 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(9); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + #[pallet::config] pub trait Config: frame_system::Config { - /// The time implementation used to supply timestamps to conntracts through `seal_now`. + /// The time implementation used to supply timestamps to contracts through `seal_now`. type Time: Time; - /// The generator used to supply randomness to contracts through `seal_random`. + /// The generator used to supply randomness to contracts through `seal_random` type Randomness: Randomness; /// The currency in which fees are paid and contract balances are held. - type Currency: Currency; + type Currency: ReservableCurrency + + Inspect>; /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The overarching call type. - type Call: Dispatchable + type RuntimeCall: Dispatchable + GetDispatchInfo + codec::Decode - + IsType<::Call>; + + IsType<::RuntimeCall>; /// Filter that is applied to calls dispatched by contracts. /// @@ -164,16 +241,10 @@ pub mod pallet { /// This is applied in **addition** to [`frame_system::Config::BaseCallFilter`]. /// It is recommended to treat this as a whitelist. /// - /// # Subsistence Threshold - /// - /// The runtime **must** make sure that any allowed dispatchable makes sure that the - /// `total_balance` of the contract stays above [`Pallet::subsistence_threshold()`]. - /// Otherwise users could clutter the storage with contracts. - /// /// # Stability /// /// The runtime **must** make sure that all dispatchables that are callable by - /// contracts remain stable. In addition [`Self::Call`] itself must remain stable. + /// contracts remain stable. In addition [`Self::RuntimeCall`] itself must remain stable. /// This means that no existing variants are allowed to switch their positions. /// /// # Note @@ -183,7 +254,7 @@ 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. - type CallFilter: Contains<::Call>; + 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. @@ -194,19 +265,12 @@ pub mod pallet { type WeightInfo: WeightInfo; /// Type that allows the runtime authors to add new host functions for a contract to call. - type ChainExtension: chain_extension::ChainExtension; + type ChainExtension: chain_extension::ChainExtension + Default; /// Cost schedule and limits. #[pallet::constant] type Schedule: Get>; - /// The deposit that must be placed into the contract's account to instantiate it. - /// This is in **addition** to the [`pallet_balances::Pallet::ExistenialDeposit`]. - /// The minimum balance for a contract's account can be queried using - /// [`Pallet::subsistence_threshold`]. - #[pallet::constant] - type ContractDeposit: Get>; - /// The type of the call stack determines the maximum nesting depth of contract calls. /// /// The allowed depth is `CallStack::size() + 1`. @@ -214,18 +278,78 @@ pub mod pallet { /// In other words only the origin called "root contract" is allowed to execute then. type CallStack: smallvec::Array>; - /// The maximum number of tries that can be queued for deletion. + /// The maximum number of contracts that can be pending for deletion. + /// + /// When a contract is deleted by calling `seal_terminate` it becomes inaccessible + /// immediately, but the deletion of the storage items it has accumulated is performed + /// later. The contract is put into the deletion queue. This defines how many + /// contracts can be queued up at the same time. If that limit is reached `seal_terminate` + /// will fail. The action must be retried in a later block in that case. + /// + /// The reasons for limiting the queue depth are: + /// + /// 1. The queue is in storage in order to be persistent between blocks. We want to limit + /// the amount of storage that can be consumed. + /// 2. The queue is stored in a vector and needs to be decoded as a whole when reading + /// it at the end of each block. Longer queues take more weight to decode and hence + /// limit the amount of items that can be deleted per block. #[pallet::constant] type DeletionQueueDepth: Get; /// The maximum amount of weight that can be consumed per block for lazy trie removal. + /// + /// The amount of weight that is dedicated per block to work on the deletion queue. Larger + /// values allow more trie keys to be deleted in each block but reduce the amount of + /// weight that is left for transactions. See [`Self::DeletionQueueDepth`] for more + /// information about the deletion queue. #[pallet::constant] type DeletionWeightLimit: Get; - } - #[pallet::pallet] - #[pallet::storage_version(STORAGE_VERSION)] - pub struct Pallet(PhantomData); + /// The amount of balance a caller has to pay for each byte of storage. + /// + /// # Note + /// + /// Changing this value for an existing chain might need a storage migration. + #[pallet::constant] + type DepositPerByte: Get>; + + /// The amount of balance a caller has to pay for each storage item. + /// + /// # Note + /// + /// Changing this value for an existing chain might need a storage migration. + #[pallet::constant] + type DepositPerItem: Get>; + + /// The address generator used to generate the addresses of contracts. + type AddressGenerator: AddressGenerator; + + /// The maximum length of a contract code in bytes. This limit applies to the instrumented + /// version of the code. Therefore `instantiate_with_code` can fail even when supplying + /// a wasm binary below this maximum size. + #[pallet::constant] + type MaxCodeLen: Get; + + /// The maximum allowable length in bytes for storage keys. + #[pallet::constant] + type MaxStorageKeyLen: Get; + + /// Make contract callable functions marked as `#[unstable]` available. + /// + /// Contracts that use `#[unstable]` functions won't be able to be uploaded unless + /// this is set to `true`. This is only meant for testnets and dev nodes in order to + /// experiment with new features. + /// + /// # Warning + /// + /// Do **not** set to `true` on productions chains. + #[pallet::constant] + type UnsafeUnstableInterface: Get; + + /// The maximum length of the debug buffer in bytes. + #[pallet::constant] + type MaxDebugBufferLen: Get; + } #[pallet::hooks] impl Hooks> for Pallet @@ -233,19 +357,28 @@ pub mod pallet { T::AccountId: UncheckedFrom, T::AccountId: AsRef<[u8]>, { - fn on_initialize(_block: T::BlockNumber) -> Weight { - // We do not want to go above the block limit and rather avoid lazy deletion - // in that case. This should only happen on runtime upgrades. - let weight_limit = T::BlockWeights::get() - .max_block - .saturating_sub(System::::block_weight().total()) - .min(T::DeletionWeightLimit::get()); - Storage::::process_deletion_queue_batch(weight_limit) - .saturating_add(T::WeightInfo::on_initialize()) + fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { + Storage::::process_deletion_queue_batch(remaining_weight) + .saturating_add(T::WeightInfo::on_process_deletion_queue_batch()) } - fn on_runtime_upgrade() -> Weight { - migration::migrate::() + fn on_initialize(_block: T::BlockNumber) -> Weight { + // We want to process the deletion_queue in the on_idle hook. Only in the case + // that the queue length has reached its maximal depth, we process it here. + let max_len = T::DeletionQueueDepth::get() as usize; + let queue_len = >::decode_len().unwrap_or(0); + if queue_len >= max_len { + // We do not want to go above the block limit and rather avoid lazy deletion + // in that case. This should only happen on runtime upgrades. + let weight_limit = T::BlockWeights::get() + .max_block + .saturating_sub(System::::block_weight().total()) + .min(T::DeletionWeightLimit::get()); + Storage::::process_deletion_queue_batch(weight_limit) + .saturating_add(T::WeightInfo::on_process_deletion_queue_batch()) + } else { + T::WeightInfo::on_process_deletion_queue_batch() + } } } @@ -254,47 +387,237 @@ pub mod pallet { where T::AccountId: UncheckedFrom, T::AccountId: AsRef<[u8]>, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, { + /// Deprecated version if [`Self::call`] for use in an in-storage `Call`. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::call().saturating_add(>::compat_weight(*gas_limit)))] + #[allow(deprecated)] + #[deprecated(note = "1D weight is used in this extrinsic, please migrate to `call`")] + pub fn call_old_weight( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: OldWeight, + storage_deposit_limit: Option< as codec::HasCompact>::Type>, + data: Vec, + ) -> DispatchResultWithPostInfo { + Self::call( + origin, + dest, + value, + >::compat_weight(gas_limit), + storage_deposit_limit, + data, + ) + } + + /// Deprecated version if [`Self::instantiate_with_code`] for use in an in-storage `Call`. + #[pallet::call_index(1)] + #[pallet::weight( + T::WeightInfo::instantiate_with_code(code.len() as u32, salt.len() as u32) + .saturating_add(>::compat_weight(*gas_limit)) + )] + #[allow(deprecated)] + #[deprecated( + note = "1D weight is used in this extrinsic, please migrate to `instantiate_with_code`" + )] + pub fn instantiate_with_code_old_weight( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: OldWeight, + storage_deposit_limit: Option< as codec::HasCompact>::Type>, + code: Vec, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo { + Self::instantiate_with_code( + origin, + value, + >::compat_weight(gas_limit), + storage_deposit_limit, + code, + data, + salt, + ) + } + + /// Deprecated version if [`Self::instantiate`] for use in an in-storage `Call`. + #[pallet::call_index(2)] + #[pallet::weight( + T::WeightInfo::instantiate(salt.len() as u32).saturating_add(>::compat_weight(*gas_limit)) + )] + #[allow(deprecated)] + #[deprecated(note = "1D weight is used in this extrinsic, please migrate to `instantiate`")] + pub fn instantiate_old_weight( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: OldWeight, + storage_deposit_limit: Option< as codec::HasCompact>::Type>, + code_hash: CodeHash, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo { + Self::instantiate( + origin, + value, + >::compat_weight(gas_limit), + storage_deposit_limit, + code_hash, + data, + salt, + ) + } + + /// Upload new `code` without instantiating a contract from it. + /// + /// If the code does not already exist a deposit is reserved from the caller + /// and unreserved only when [`Self::remove_code`] is called. The size of the reserve + /// depends on the instrumented size of the the supplied `code`. + /// + /// If the code already exists in storage it will still return `Ok` and upgrades + /// the in storage version to the current + /// [`InstructionWeights::version`](InstructionWeights). + /// + /// - `determinism`: If this is set to any other value but [`Determinism::Deterministic`] + /// then the only way to use this code is to delegate call into it from an offchain + /// execution. Set to [`Determinism::Deterministic`] if in doubt. + /// + /// # Note + /// + /// Anyone can instantiate a contract from any uploaded code and thus prevent its removal. + /// To avoid this situation a constructor could employ access control so that it can + /// only be instantiated by permissioned entities. The same is true when uploading + /// through [`Self::instantiate_with_code`]. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::upload_code(code.len() as u32))] + pub fn upload_code( + origin: OriginFor, + code: Vec, + storage_deposit_limit: Option< as codec::HasCompact>::Type>, + determinism: Determinism, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::bare_upload_code(origin, code, storage_deposit_limit.map(Into::into), determinism) + .map(|_| ()) + } + + /// Remove the code stored under `code_hash` and refund the deposit to its owner. + /// + /// A code can only be removed by its original uploader (its owner) and only if it is + /// not used by any contract. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::remove_code())] + pub fn remove_code( + origin: OriginFor, + code_hash: CodeHash, + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + >::remove(&origin, code_hash)?; + // we waive the fee because removing unused code is beneficial + Ok(Pays::No.into()) + } + + /// Privileged function that changes the code of an existing contract. + /// + /// This takes care of updating refcounts and all other necessary operations. Returns + /// an error if either the `code_hash` or `dest` do not exist. + /// + /// # Note + /// + /// This does **not** change the address of the contract in question. This means + /// that the contract address is no longer derived from its code hash after calling + /// this dispatchable. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::set_code())] + pub fn set_code( + origin: OriginFor, + dest: AccountIdLookupOf, + code_hash: CodeHash, + ) -> DispatchResult { + ensure_root(origin)?; + let dest = T::Lookup::lookup(dest)?; + >::try_mutate(&dest, |contract| { + let contract = if let Some(contract) = contract { + contract + } else { + return Err(>::ContractNotFound.into()) + }; + >::add_user(code_hash)?; + >::remove_user(contract.code_hash); + Self::deposit_event( + vec![T::Hashing::hash_of(&dest), code_hash, contract.code_hash], + Event::ContractCodeUpdated { + contract: dest.clone(), + new_code_hash: code_hash, + old_code_hash: contract.code_hash, + }, + ); + contract.code_hash = code_hash; + Ok(()) + }) + } + /// Makes a call to an account, optionally transferring some balance. /// + /// # Parameters + /// + /// * `dest`: Address of the contract to call. + /// * `value`: The balance to transfer from the `origin` to `dest`. + /// * `gas_limit`: The gas limit enforced when executing the constructor. + /// * `storage_deposit_limit`: The maximum amount of balance that can be charged from the + /// caller to pay for the storage consumed. + /// * `data`: The input data to pass to the contract. + /// /// * If the account is a smart-contract account, the associated code will be /// executed and any value will be transferred. /// * If the account is a regular account, any value will be transferred. /// * If no account exists and the call value is not less than `existential_deposit`, /// a regular account will be created and any value will be transferred. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))] pub fn call( origin: OriginFor, - dest: ::Source, + dest: AccountIdLookupOf, #[pallet::compact] value: BalanceOf, - #[pallet::compact] gas_limit: Weight, + gas_limit: Weight, + storage_deposit_limit: Option< as codec::HasCompact>::Type>, data: Vec, ) -> DispatchResultWithPostInfo { + let gas_limit: Weight = gas_limit.into(); let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; - let mut gas_meter = GasMeter::new(gas_limit); - let schedule = T::Schedule::get(); - let result = ExecStack::>::run_call( + let mut output = Self::internal_call( origin, dest, - &mut gas_meter, - &schedule, value, + gas_limit, + storage_deposit_limit.map(Into::into), data, None, + Determinism::Deterministic, ); - gas_meter.into_dispatch_result(result, T::WeightInfo::call()) + if let Ok(retval) = &output.result { + if retval.did_revert() { + output.result = Err(>::ContractReverted.into()); + } + } + output.gas_meter.into_dispatch_result(output.result, T::WeightInfo::call()) } /// Instantiates a new contract from the supplied `code` optionally transferring /// some balance. /// - /// This is the only function that can deploy new code to the chain. + /// This dispatchable has the same effect as calling [`Self::upload_code`] + + /// [`Self::instantiate`]. Bundling them together provides efficiency gains. Please + /// also check the documentation of [`Self::upload_code`]. /// /// # Parameters /// - /// * `endowment`: The balance to transfer from the `origin` to the newly created contract. + /// * `value`: The balance to transfer from the `origin` to the newly created contract. /// * `gas_limit`: The gas limit enforced when executing the constructor. + /// * `storage_deposit_limit`: The maximum amount of balance that can be charged/reserved + /// from the caller to pay for the storage consumed. /// * `code`: The contract code to deploy in raw bytes. /// * `data`: The input data to pass to the contract constructor. /// * `salt`: Used for the address derivation. See [`Pallet::contract_address`]. @@ -306,45 +629,43 @@ pub mod pallet { /// - If the `code_hash` already exists on the chain the underlying `code` will be shared. /// - The destination address is computed based on the sender, code_hash and the salt. /// - The smart-contract account is created at the computed address. - /// - The `endowment` is transferred to the new account. + /// - The `value` is transferred to the new account. /// - The `deploy` function is executed in the context of the newly-created account. + #[pallet::call_index(7)] #[pallet::weight( - T::WeightInfo::instantiate_with_code( - code.len() as u32 / 1024, - salt.len() as u32 / 1024, - ) + T::WeightInfo::instantiate_with_code(code.len() as u32, salt.len() as u32) .saturating_add(*gas_limit) )] pub fn instantiate_with_code( origin: OriginFor, - #[pallet::compact] endowment: BalanceOf, - #[pallet::compact] gas_limit: Weight, + #[pallet::compact] value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option< as codec::HasCompact>::Type>, code: Vec, data: Vec, salt: Vec, ) -> DispatchResultWithPostInfo { let origin = ensure_signed(origin)?; let code_len = code.len() as u32; - ensure!(code_len <= T::Schedule::get().limits.code_len, Error::::CodeTooLarge); - let mut gas_meter = GasMeter::new(gas_limit); - let schedule = T::Schedule::get(); - let executable = PrefabWasmModule::from_code(code, &schedule)?; - let code_len = executable.code_len(); - ensure!(code_len <= T::Schedule::get().limits.code_len, Error::::CodeTooLarge); - let result = ExecStack::>::run_instantiate( + let salt_len = salt.len() as u32; + let mut output = Self::internal_instantiate( origin, - executable, - &mut gas_meter, - &schedule, - endowment, + value, + gas_limit, + storage_deposit_limit.map(Into::into), + Code::Upload(code), data, - &salt, + salt, None, - ) - .map(|(_address, output)| output); - gas_meter.into_dispatch_result( - result, - T::WeightInfo::instantiate_with_code(code_len / 1024, salt.len() as u32 / 1024), + ); + if let Ok(retval) = &output.result { + if retval.1.did_revert() { + output.result = Err(>::ContractReverted.into()); + } + } + output.gas_meter.into_dispatch_result( + output.result.map(|(_address, result)| result), + T::WeightInfo::instantiate_with_code(code_len, salt_len), ) } @@ -353,103 +674,128 @@ pub mod pallet { /// This function is identical to [`Self::instantiate_with_code`] but without the /// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary /// must be supplied. + #[pallet::call_index(8)] #[pallet::weight( - T::WeightInfo::instantiate(salt.len() as u32 / 1024).saturating_add(*gas_limit) + T::WeightInfo::instantiate(salt.len() as u32).saturating_add(*gas_limit) )] pub fn instantiate( origin: OriginFor, - #[pallet::compact] endowment: BalanceOf, - #[pallet::compact] gas_limit: Weight, + #[pallet::compact] value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option< as codec::HasCompact>::Type>, code_hash: CodeHash, data: Vec, salt: Vec, ) -> DispatchResultWithPostInfo { let origin = ensure_signed(origin)?; - let mut gas_meter = GasMeter::new(gas_limit); - let schedule = T::Schedule::get(); - let executable = PrefabWasmModule::from_storage(code_hash, &schedule, &mut gas_meter)?; - let result = ExecStack::>::run_instantiate( + let salt_len = salt.len() as u32; + let mut output = Self::internal_instantiate( origin, - executable, - &mut gas_meter, - &schedule, - endowment, + value, + gas_limit, + storage_deposit_limit.map(Into::into), + Code::Existing(code_hash), data, - &salt, + salt, None, + ); + if let Ok(retval) = &output.result { + if retval.1.did_revert() { + output.result = Err(>::ContractReverted.into()); + } + } + output.gas_meter.into_dispatch_result( + output.result.map(|(_address, output)| output), + T::WeightInfo::instantiate(salt_len), ) - .map(|(_address, output)| output); - gas_meter - .into_dispatch_result(result, T::WeightInfo::instantiate(salt.len() as u32 / 1024)) } } #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Contract deployed by address at the specified address. \[deployer, contract\] - Instantiated(T::AccountId, T::AccountId), + /// Contract deployed by address at the specified address. + Instantiated { deployer: T::AccountId, contract: T::AccountId }, /// Contract has been removed. - /// \[contract, beneficiary\] - /// - /// # Params - /// - /// - `contract`: The contract that was terminated. - /// - `beneficiary`: The account that received the contracts remaining balance. /// /// # Note /// /// The only way for a contract to be removed and emitting this event is by calling /// `seal_terminate`. - Terminated(T::AccountId, T::AccountId), + Terminated { + /// The contract that was terminated. + contract: T::AccountId, + /// The account that received the contracts remaining balance + beneficiary: T::AccountId, + }, - /// Code with the specified hash has been stored. \[code_hash\] - CodeStored(T::Hash), - - /// Triggered when the current schedule is updated. - /// \[version\] - /// - /// # Params - /// - /// - `version`: The version of the newly set schedule. - ScheduleUpdated(u32), + /// Code with the specified hash has been stored. + CodeStored { code_hash: T::Hash }, /// A custom event emitted by the contract. - /// \[contract, data\] + ContractEmitted { + /// The contract that emitted the event. + contract: T::AccountId, + /// Data supplied by the contract. Metadata generated during contract compilation + /// is needed to decode it. + data: Vec, + }, + + /// A code with the specified hash was removed. + CodeRemoved { code_hash: T::Hash }, + + /// A contract's code was updated. + ContractCodeUpdated { + /// The contract that has been updated. + contract: T::AccountId, + /// New code hash that was set for the contract. + new_code_hash: T::Hash, + /// Previous code hash of the contract. + old_code_hash: T::Hash, + }, + + /// A contract was called either by a plain account or another contract. /// - /// # Params + /// # Note /// - /// - `contract`: The contract that emitted the event. - /// - `data`: Data supplied by the contract. Metadata generated during contract compilation - /// is needed to decode it. - ContractEmitted(T::AccountId, Vec), + /// Please keep in mind that like all events this is only emitted for successful + /// calls. This is because on failure all storage changes including events are + /// rolled back. + Called { + /// The account that called the `contract`. + caller: T::AccountId, + /// The contract that was called. + contract: T::AccountId, + }, - /// A code with the specified hash was removed. - /// \[code_hash\] + /// A contract delegate called a code hash. + /// + /// # Note /// - /// This happens when the last contract that uses this code hash was removed. - CodeRemoved(T::Hash), + /// Please keep in mind that like all events this is only emitted for successful + /// calls. This is because on failure all storage changes including events are + /// rolled back. + DelegateCalled { + /// The contract that performed the delegate call and hence in whose context + /// the `code_hash` is executed. + contract: T::AccountId, + /// The code hash that was delegate called. + code_hash: CodeHash, + }, } #[pallet::error] pub enum Error { /// A new schedule must have a greater version than the current one. InvalidScheduleVersion, + /// Invalid combination of flags supplied to `seal_call` or `seal_delegate_call`. + InvalidCallFlags, /// The executed contract exhausted its gas limit. OutOfGas, /// The output buffer supplied to a contract API call was too small. OutputBufferTooSmall, - /// Performing the requested transfer would have brought the contract below - /// the subsistence threshold. No transfer is allowed to do this. Use `seal_terminate` - /// to recover a deposit. - BelowSubsistenceThreshold, - /// The newly created contract is below the subsistence threshold after executing - /// its contructor. No contracts are allowed to exist below that threshold. - NewContractNotFunded, - /// Performing the requested transfer failed for a reason originating in the - /// chosen currency implementation of the runtime. Most probably the balance is - /// too low or locks are placed on it. + /// Performing the requested transfer failed. Probably because there isn't enough + /// free balance in the sender's account. TransferFailed, /// Performing a call was denied because the calling depth reached the limit /// of what is specified in the schedule. @@ -490,11 +836,6 @@ pub mod pallet { /// The queue is filled by deleting contracts and emptied by a fixed amount each block. /// Trying again during another block is the only way to resolve this issue. DeletionQueueFull, - /// A storage modification exhausted the 32bit type that holds the storage size. - /// - /// This can either happen when the accumulated storage in bytes is too large or - /// when number of storage items is too large. - StorageExhausted, /// A contract with the same AccountId already exists. DuplicateContract, /// A contract self destructed in its constructor. @@ -505,20 +846,70 @@ pub mod pallet { DebugMessageInvalidUTF8, /// A call tried to invoke a contract that is flagged as non-reentrant. ReentranceDenied, + /// Origin doesn't have enough balance to pay the required storage deposits. + StorageDepositNotEnoughFunds, + /// More storage was created than allowed by the storage deposit limit. + StorageDepositLimitExhausted, + /// Code removal was denied because the code is still in use by at least one contract. + CodeInUse, + /// The contract ran to completion but decided to revert its storage changes. + /// Please note that this error is only returned from extrinsics. When called directly + /// or via RPC an `Ok` will be returned. In this case the caller needs to inspect the flags + /// to determine whether a reversion has taken place. + ContractReverted, + /// The contract's code was found to be invalid during validation or instrumentation. + /// + /// The most likely cause of this is that an API was used which is not supported by the + /// node. This hapens if an older node is used with a new version of ink!. Try updating + /// your node to the newest available version. + /// + /// A more detailed error can be found on the node console if debug messages are enabled + /// by supplying `-lruntime::contracts=debug`. + CodeRejected, + /// An indetermistic code was used in a context where this is not permitted. + Indeterministic, + /// The debug buffer size used during contract execution exceeded the limit determined by + /// the `MaxDebugBufferLen` pallet config parameter. + DebugBufferExhausted, } /// A mapping from an original code hash to the original code, untouched by instrumentation. #[pallet::storage] - pub(crate) type PristineCode = StorageMap<_, Identity, CodeHash, Vec>; + pub(crate) type PristineCode = StorageMap<_, Identity, CodeHash, CodeVec>; /// A mapping between an original code hash and instrumented wasm code, ready for execution. #[pallet::storage] pub(crate) type CodeStorage = StorageMap<_, Identity, CodeHash, PrefabWasmModule>; - /// The subtrie counter. + /// A mapping between an original code hash and its owner information. + #[pallet::storage] + pub(crate) type OwnerInfoOf = StorageMap<_, Identity, CodeHash, OwnerInfo>; + + /// This is a **monotonic** counter incremented on contract instantiation. + /// + /// This is used in order to generate unique trie ids for contracts. + /// The trie id of a new contract is calculated from hash(account_id, nonce). + /// The nonce is required because otherwise the following sequence would lead to + /// a possible collision of storage: + /// + /// 1. Create a new contract. + /// 2. Terminate the contract. + /// 3. Immediately recreate the contract with the same account_id. + /// + /// This is bad because the contents of a trie are deleted lazily and there might be + /// storage of the old instantiation still in it when the new contract is created. Please + /// note that we can't replace the counter by the block number because the sequence above + /// can happen in the same block. We also can't keep the account counter in memory only + /// because storage is the only way to communicate across different extrinsics in the + /// same block. + /// + /// # Note + /// + /// Do not use it to determine the number of contracts. It won't be decremented if + /// a contract is destroyed. #[pallet::storage] - pub(crate) type AccountCounter = StorageValue<_, u64, ValueQuery>; + pub(crate) type Nonce = StorageValue<_, u64, ValueQuery>; /// The code associated with a given account. /// @@ -532,7 +923,24 @@ pub mod pallet { /// Child trie deletion is a heavy operation depending on the amount of storage items /// stored in said trie. Therefore this operation is performed lazily in `on_initialize`. #[pallet::storage] - pub(crate) type DeletionQueue = StorageValue<_, Vec, ValueQuery>; + pub(crate) type DeletionQueue = + StorageValue<_, BoundedVec, ValueQuery>; +} + +/// Return type of the private [`Pallet::internal_call`] function. +type InternalCallOutput = InternalOutput; + +/// Return type of the private [`Pallet::internal_instantiate`] function. +type InternalInstantiateOutput = InternalOutput, ExecReturnValue)>; + +/// Return type of private helper functions. +struct InternalOutput { + /// The gas meter that was used to execute the call. + gas_meter: GasMeter, + /// The storage deposit used by the call. + storage_deposit: StorageDeposit>, + /// The result of the call. + result: Result, } impl Pallet @@ -556,26 +964,28 @@ where dest: T::AccountId, value: BalanceOf, gas_limit: Weight, - input_data: Vec, + storage_deposit_limit: Option>, + data: Vec, debug: bool, - ) -> ContractExecResult { - let mut gas_meter = GasMeter::new(gas_limit); - let schedule = T::Schedule::get(); - let mut debug_message = if debug { Some(Vec::new()) } else { None }; - let result = ExecStack::>::run_call( + determinism: Determinism, + ) -> ContractExecResult> { + let mut debug_message = if debug { Some(DebugBufferVec::::default()) } else { None }; + let output = Self::internal_call( origin, dest, - &mut gas_meter, - &schedule, value, - input_data, + gas_limit, + storage_deposit_limit, + data, debug_message.as_mut(), + determinism, ); ContractExecResult { - result: result.map_err(|r| r.error), - gas_consumed: gas_meter.gas_consumed(), - gas_required: gas_meter.gas_required(), - debug_message: debug_message.unwrap_or_default(), + result: output.result.map_err(|r| r.error), + gas_consumed: output.gas_meter.gas_consumed(), + gas_required: output.gas_meter.gas_required(), + storage_deposit: output.storage_deposit, + debug_message: debug_message.unwrap_or_default().to_vec(), } } @@ -586,7 +996,6 @@ where /// /// It returns the execution result, account id and the amount of used weight. /// - /// /// # Note /// /// `debug` should only ever be set to `true` when executing as an RPC because @@ -594,110 +1003,102 @@ where /// If set to `true` it returns additional human readable debugging information. pub fn bare_instantiate( origin: T::AccountId, - endowment: BalanceOf, + value: BalanceOf, gas_limit: Weight, + storage_deposit_limit: Option>, code: Code>, data: Vec, salt: Vec, debug: bool, - ) -> ContractInstantiateResult { - let mut gas_meter = GasMeter::new(gas_limit); - let schedule = T::Schedule::get(); - let executable = match code { - Code::Upload(Bytes(binary)) => PrefabWasmModule::from_code(binary, &schedule), - Code::Existing(hash) => PrefabWasmModule::from_storage(hash, &schedule, &mut gas_meter), - }; - let executable = match executable { - Ok(executable) => executable, - Err(error) => - return ContractInstantiateResult { - result: Err(error.into()), - gas_consumed: gas_meter.gas_consumed(), - gas_required: gas_meter.gas_required(), - debug_message: Vec::new(), - }, - }; - let mut debug_message = if debug { Some(Vec::new()) } else { None }; - let result = ExecStack::>::run_instantiate( + ) -> ContractInstantiateResult> { + let mut debug_message = if debug { Some(DebugBufferVec::::default()) } else { None }; + let output = Self::internal_instantiate( origin, - executable, - &mut gas_meter, - &schedule, - endowment, + value, + gas_limit, + storage_deposit_limit, + code, data, - &salt, + salt, debug_message.as_mut(), - ) - .and_then(|(account_id, result)| Ok(InstantiateReturnValue { result, account_id })); + ); ContractInstantiateResult { - result: result.map_err(|e| e.error), - gas_consumed: gas_meter.gas_consumed(), - gas_required: gas_meter.gas_required(), - debug_message: debug_message.unwrap_or_default(), + result: output + .result + .map(|(account_id, result)| InstantiateReturnValue { result, account_id }) + .map_err(|e| e.error), + gas_consumed: output.gas_meter.gas_consumed(), + gas_required: output.gas_meter.gas_required(), + storage_deposit: output.storage_deposit, + debug_message: debug_message.unwrap_or_default().to_vec(), } } + /// Upload new code without instantiating a contract from it. + /// + /// This function is similar to [`Self::upload_code`], but doesn't perform any address lookups + /// and better suitable for calling directly from Rust. + pub fn bare_upload_code( + origin: T::AccountId, + code: Vec, + storage_deposit_limit: Option>, + determinism: Determinism, + ) -> CodeUploadResult, BalanceOf> { + let schedule = T::Schedule::get(); + let module = PrefabWasmModule::from_code( + code, + &schedule, + origin, + determinism, + TryInstantiate::Instantiate, + ) + .map_err(|(err, _)| err)?; + let deposit = module.open_deposit(); + if let Some(storage_deposit_limit) = storage_deposit_limit { + ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); + } + let result = CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }; + module.store()?; + Ok(result) + } + /// Query storage of a specified contract under a specified key. - pub fn get_storage(address: T::AccountId, key: [u8; 32]) -> GetStorageResult { + pub fn get_storage(address: T::AccountId, key: Vec) -> GetStorageResult { let contract_info = ContractInfoOf::::get(&address).ok_or(ContractAccessError::DoesntExist)?; - let maybe_value = Storage::::read(&contract_info.trie_id, &key); + let maybe_value = Storage::::read( + &contract_info.trie_id, + &StorageKey::::try_from(key).map_err(|_| ContractAccessError::KeyDecodingFailed)?, + ); Ok(maybe_value) } - /// Determine the address of a contract, + /// Determine the address of a contract. /// - /// This is the address generation function used by contract instantiation. Its result - /// is only dependend on its inputs. It can therefore be used to reliably predict the - /// address of a contract. This is akin to the formular of eth's CREATE2 opcode. There - /// is no CREATE equivalent because CREATE2 is strictly more powerful. - /// - /// Formula: `hash(deploying_address ++ code_hash ++ salt)` + /// This is the address generation function used by contract instantiation. See + /// [`DefaultAddressGenerator`] for the default implementation. pub fn contract_address( deploying_address: &T::AccountId, code_hash: &CodeHash, salt: &[u8], ) -> T::AccountId { - let buf: Vec<_> = deploying_address - .as_ref() - .iter() - .chain(code_hash.as_ref()) - .chain(salt) - .cloned() - .collect(); - UncheckedFrom::unchecked_from(T::Hashing::hash(&buf)) + T::AddressGenerator::generate_address(deploying_address, code_hash, salt) } - /// Subsistence threshold is the extension of the minimum balance (aka existential deposit) - /// by the contract deposit. It is the minimum balance any contract must hold. - /// - /// Any contract initiated balance transfer mechanism cannot make the balance lower - /// than the subsistence threshold. The only way to recover the balance is to remove - /// contract using `seal_terminate`. - pub fn subsistence_threshold() -> BalanceOf { - T::Currency::minimum_balance().saturating_add(T::ContractDeposit::get()) - } - - /// The in-memory size in bytes of the data structure associated with each contract. - /// - /// The data structure is also put into storage for each contract. The in-storage size - /// is never larger than the in-memory representation and usually smaller due to compact - /// encoding and lack of padding. - /// - /// # Note - /// - /// This returns the in-memory size because the in-storage size (SCALE encoded) cannot - /// be efficiently determined. Treat this as an upper bound of the in-storage size. - pub fn contract_info_size() -> u32 { - sp_std::mem::size_of::>() as u32 + /// Returns the code hash of the contract specified by `account` ID. + pub fn code_hash(account: &AccountIdOf) -> Option> { + Storage::::code_hash(account) } /// Store code for benchmarks which does not check nor instrument the code. #[cfg(feature = "runtime-benchmarks")] - fn store_code_raw(code: Vec) -> frame_support::dispatch::DispatchResult { + fn store_code_raw( + code: Vec, + owner: T::AccountId, + ) -> frame_support::dispatch::DispatchResult { let schedule = T::Schedule::get(); - PrefabWasmModule::store_code_unchecked(code, &schedule)?; + PrefabWasmModule::store_code_unchecked(code, &schedule, owner)?; Ok(()) } @@ -707,6 +1108,191 @@ where module: &mut PrefabWasmModule, schedule: &Schedule, ) -> frame_support::dispatch::DispatchResult { - self::wasm::reinstrument(module, schedule) + self::wasm::reinstrument(module, schedule).map(|_| ()) + } + + /// Internal function that does the actual call. + /// + /// Called by dispatchables and public functions. + fn internal_call( + origin: T::AccountId, + dest: T::AccountId, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option>, + data: Vec, + debug_message: Option<&mut DebugBufferVec>, + determinism: Determinism, + ) -> InternalCallOutput { + let mut gas_meter = GasMeter::new(gas_limit); + let mut storage_meter = match StorageMeter::new(&origin, storage_deposit_limit, value) { + Ok(meter) => meter, + Err(err) => + return InternalCallOutput { + result: Err(err.into()), + gas_meter, + storage_deposit: Default::default(), + }, + }; + let schedule = T::Schedule::get(); + let result = ExecStack::>::run_call( + origin.clone(), + dest, + &mut gas_meter, + &mut storage_meter, + &schedule, + value, + data, + debug_message, + determinism, + ); + InternalCallOutput { + result, + gas_meter, + storage_deposit: storage_meter.into_deposit(&origin), + } + } + + /// Internal function that does the actual instantiation. + /// + /// Called by dispatchables and public functions. + fn internal_instantiate( + origin: T::AccountId, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option>, + code: Code>, + data: Vec, + salt: Vec, + mut debug_message: Option<&mut DebugBufferVec>, + ) -> InternalInstantiateOutput { + let mut storage_deposit = Default::default(); + let mut gas_meter = GasMeter::new(gas_limit); + let try_exec = || { + let schedule = T::Schedule::get(); + let (extra_deposit, executable) = match code { + Code::Upload(binary) => { + let executable = PrefabWasmModule::from_code( + binary, + &schedule, + origin.clone(), + Determinism::Deterministic, + TryInstantiate::Skip, + ) + .map_err(|(err, msg)| { + debug_message.as_mut().map(|buffer| buffer.try_extend(&mut msg.bytes())); + err + })?; + // The open deposit will be charged during execution when the + // uploaded module does not already exist. This deposit is not part of the + // storage meter because it is not transferred to the contract but + // reserved on the uploading account. + (executable.open_deposit(), executable) + }, + Code::Existing(hash) => ( + Default::default(), + PrefabWasmModule::from_storage(hash, &schedule, &mut gas_meter)?, + ), + }; + let mut storage_meter = StorageMeter::new( + &origin, + storage_deposit_limit, + value.saturating_add(extra_deposit), + )?; + let result = ExecStack::>::run_instantiate( + origin.clone(), + executable, + &mut gas_meter, + &mut storage_meter, + &schedule, + value, + data, + &salt, + debug_message, + ); + storage_deposit = storage_meter + .into_deposit(&origin) + .saturating_add(&StorageDeposit::Charge(extra_deposit)); + result + }; + InternalInstantiateOutput { result: try_exec(), gas_meter, storage_deposit } + } + + /// Deposit a pallet contracts event. Handles the conversion to the overarching event type. + fn deposit_event(topics: Vec, event: Event) { + >::deposit_event_indexed( + &topics, + ::RuntimeEvent::from(event).into(), + ) + } + + /// Return the existential deposit of [`Config::Currency`]. + fn min_balance() -> BalanceOf { + >>::minimum_balance() + } + + /// Convert a 1D Weight to a 2D weight. + /// + /// Used by backwards compatible extrinsics. We cannot just set the proof to zero + /// or an old `Call` will just fail. + fn compat_weight(gas_limit: OldWeight) -> Weight { + Weight::from(gas_limit).set_proof_size(u64::from(T::MaxCodeLen::get()) * 2) + } +} + +sp_api::decl_runtime_apis! { + /// The API used to dry-run contract interactions. + #[api_version(2)] + pub trait ContractsApi where + AccountId: Codec, + Balance: Codec, + BlockNumber: Codec, + Hash: Codec, + { + /// Perform a call from a specified account to a given contract. + /// + /// See [`crate::Pallet::bare_call`]. + fn call( + origin: AccountId, + dest: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + input_data: Vec, + ) -> ContractExecResult; + + /// Instantiate a new contract. + /// + /// See `[crate::Pallet::bare_instantiate]`. + fn instantiate( + origin: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + code: Code, + data: Vec, + salt: Vec, + ) -> ContractInstantiateResult; + + + /// Upload new code without instantiating a contract from it. + /// + /// See [`crate::Pallet::bare_upload_code`]. + fn upload_code( + origin: AccountId, + code: Vec, + storage_deposit_limit: Option, + determinism: Determinism, + ) -> CodeUploadResult; + + /// Query a given storage key in a given contract. + /// + /// Returns `Ok(Some(Vec))` if the storage value exists under the given key in the + /// specified account and `Ok(None)` if it doesn't. If the account specified by the address + /// doesn't exist, or doesn't have a contract then `Err` is returned. + fn get_storage( + address: AccountId, + key: Vec, + ) -> GetStorageResult; } } diff --git a/frame/contracts/src/migration.rs b/frame/contracts/src/migration.rs index b7fa9575e23b5..56d688abc7309 100644 --- a/frame/contracts/src/migration.rs +++ b/frame/contracts/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,50 +15,88 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Config, Pallet, Weight}; +use crate::{BalanceOf, CodeHash, Config, Pallet, TrieId, Weight}; +use codec::{Decode, Encode}; use frame_support::{ + codec, + pallet_prelude::*, storage::migration, - traits::{Get, PalletInfoAccess}, + storage_alias, + traits::{Get, OnRuntimeUpgrade}, + Identity, Twox64Concat, }; -use sp_std::prelude::*; +use sp_runtime::traits::Saturating; +use sp_std::{marker::PhantomData, prelude::*}; -pub fn migrate() -> Weight { - use frame_support::traits::StorageVersion; +/// Performs all necessary migrations based on `StorageVersion`. +pub struct Migration(PhantomData); +impl OnRuntimeUpgrade for Migration { + fn on_runtime_upgrade() -> Weight { + let version = >::on_chain_storage_version(); + let mut weight = Weight::zero(); - let version = StorageVersion::get::>(); - let mut weight: Weight = 0; + if version < 4 { + v4::migrate::(&mut weight); + } - if version < 4 { - weight = weight.saturating_add(v4::migrate::()); - StorageVersion::new(4).put::>(); + if version < 5 { + v5::migrate::(&mut weight); + } + + if version < 6 { + v6::migrate::(&mut weight); + } + + if version < 7 { + v7::migrate::(&mut weight); + } + + if version < 8 { + v8::migrate::(&mut weight); + } + + if version < 9 { + v9::migrate::(&mut weight); + } + + StorageVersion::new(9).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + weight } - if version < 5 { - weight = weight.saturating_add(v5::migrate::()); - StorageVersion::new(5).put::>(); + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + let version = >::on_chain_storage_version(); + + if version == 7 { + v8::pre_upgrade::()?; + } + + Ok(version.encode()) } - weight + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + let version = Decode::decode(&mut state.as_ref()).map_err(|_| "Cannot decode version")?; + post_checks::post_upgrade::(version) + } } /// V4: `Schedule` is changed to be a config item rather than an in-storage value. mod v4 { use super::*; - pub fn migrate() -> Weight { + pub fn migrate(weight: &mut Weight) { + #[allow(deprecated)] migration::remove_storage_prefix(>::name().as_bytes(), b"CurrentSchedule", b""); - T::DbWeight::get().writes(1) + weight.saturating_accrue(T::DbWeight::get().writes(1)); } } /// V5: State rent is removed which obsoletes some fields in `ContractInfo`. mod v5 { use super::*; - use crate::{ - BalanceOf, CodeHash, ContractInfo, ContractInfoOf, DeletedContract, DeletionQueue, TrieId, - }; - use codec::Decode; - use sp_std::marker::PhantomData; type AliveContractInfo = RawAliveContractInfo, BalanceOf, ::BlockNumber>; @@ -95,11 +133,34 @@ mod v5 { trie_id: TrieId, } - pub fn migrate() -> Weight { - let mut weight: Weight = 0; + pub type ContractInfo = RawContractInfo>; + + #[derive(Encode, Decode)] + pub struct RawContractInfo { + pub trie_id: TrieId, + pub code_hash: CodeHash, + pub _reserved: Option<()>, + } + #[derive(Encode, Decode)] + struct DeletedContract { + trie_id: TrieId, + } + + #[storage_alias] + type ContractInfoOf = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + ContractInfo, + >; + + #[storage_alias] + type DeletionQueue = StorageValue, Vec>; + + pub fn migrate(weight: &mut Weight) { >::translate(|_key, old: OldContractInfo| { - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); match old { OldContractInfo::Alive(old) => Some(ContractInfo:: { trie_id: old.trie_id, @@ -110,12 +171,303 @@ mod v5 { } }); - >::translate(|old: Option>| { - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + DeletionQueue::::translate(|old: Option>| { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); old.map(|old| old.into_iter().map(|o| DeletedContract { trie_id: o.trie_id }).collect()) }) .ok(); + } +} - weight +/// V6: Added storage deposits +mod v6 { + use super::*; + + #[derive(Encode, Decode)] + struct OldPrefabWasmModule { + #[codec(compact)] + instruction_weights_version: u32, + #[codec(compact)] + initial: u32, + #[codec(compact)] + maximum: u32, + #[codec(compact)] + refcount: u64, + _reserved: Option<()>, + code: Vec, + original_code_len: u32, + } + + #[derive(Encode, Decode)] + pub struct PrefabWasmModule { + #[codec(compact)] + pub instruction_weights_version: u32, + #[codec(compact)] + pub initial: u32, + #[codec(compact)] + pub maximum: u32, + pub code: Vec, + } + + use v5::ContractInfo as OldContractInfo; + + #[derive(Encode, Decode)] + pub struct RawContractInfo { + pub trie_id: TrieId, + pub code_hash: CodeHash, + pub storage_deposit: Balance, + } + + #[derive(Encode, Decode)] + pub struct OwnerInfo { + owner: T::AccountId, + #[codec(compact)] + deposit: BalanceOf, + #[codec(compact)] + refcount: u64, + } + + pub type ContractInfo = RawContractInfo, BalanceOf>; + + #[storage_alias] + type ContractInfoOf = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + ContractInfo, + >; + + #[storage_alias] + type CodeStorage = StorageMap, Identity, CodeHash, PrefabWasmModule>; + + #[storage_alias] + type OwnerInfoOf = StorageMap, Identity, CodeHash, OwnerInfo>; + + pub fn migrate(weight: &mut Weight) { + >::translate(|_key, old: OldContractInfo| { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Some(ContractInfo:: { + trie_id: old.trie_id, + code_hash: old.code_hash, + storage_deposit: Default::default(), + }) + }); + + let nobody = T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) + .expect("Infinite input; no dead input space; qed"); + + >::translate(|key, old: OldPrefabWasmModule| { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + >::insert( + key, + OwnerInfo { + refcount: old.refcount, + owner: nobody.clone(), + deposit: Default::default(), + }, + ); + Some(PrefabWasmModule { + instruction_weights_version: old.instruction_weights_version, + initial: old.initial, + maximum: old.maximum, + code: old.code, + }) + }); + } +} + +/// Rename `AccountCounter` to `Nonce`. +mod v7 { + use super::*; + + pub fn migrate(weight: &mut Weight) { + #[storage_alias] + type AccountCounter = StorageValue, u64, ValueQuery>; + #[storage_alias] + type Nonce = StorageValue, u64, ValueQuery>; + + Nonce::::set(AccountCounter::::take()); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)) + } +} + +/// Update `ContractInfo` with new fields that track storage deposits. +mod v8 { + use super::*; + use sp_io::default_child_storage as child; + use v6::ContractInfo as OldContractInfo; + + #[derive(Encode, Decode)] + pub struct ContractInfo { + pub trie_id: TrieId, + pub code_hash: CodeHash, + pub storage_bytes: u32, + pub storage_items: u32, + pub storage_byte_deposit: BalanceOf, + pub storage_item_deposit: BalanceOf, + pub storage_base_deposit: BalanceOf, + } + + #[storage_alias] + type ContractInfoOf = + StorageMap, Twox64Concat, ::AccountId, V>; + + pub fn migrate(weight: &mut Weight) { + >>::translate_values(|old: OldContractInfo| { + // Count storage items of this contract + let mut storage_bytes = 0u32; + let mut storage_items = 0u32; + let mut key = Vec::new(); + while let Some(next) = child::next_key(&old.trie_id, &key) { + key = next; + let mut val_out = []; + let len = child::read(&old.trie_id, &key, &mut val_out, 0) + .expect("The loop conditions checks for existence of the key; qed"); + storage_bytes.saturating_accrue(len); + storage_items.saturating_accrue(1); + } + + let storage_byte_deposit = + T::DepositPerByte::get().saturating_mul(storage_bytes.into()); + let storage_item_deposit = + T::DepositPerItem::get().saturating_mul(storage_items.into()); + let storage_base_deposit = old + .storage_deposit + .saturating_sub(storage_byte_deposit) + .saturating_sub(storage_item_deposit); + + // Reads: One read for each storage item plus the contract info itself. + // Writes: Only the new contract info. + weight.saturating_accrue( + T::DbWeight::get().reads_writes(u64::from(storage_items) + 1, 1), + ); + + Some(ContractInfo { + trie_id: old.trie_id, + code_hash: old.code_hash, + storage_bytes, + storage_items, + storage_byte_deposit, + storage_item_deposit, + storage_base_deposit, + }) + }); + } + + #[cfg(feature = "try-runtime")] + pub fn pre_upgrade() -> Result<(), &'static str> { + use frame_support::traits::ReservableCurrency; + for (key, value) in ContractInfoOf::>::iter() { + let reserved = T::Currency::reserved_balance(&key); + ensure!(reserved >= value.storage_deposit, "Reserved balance out of sync."); + } + Ok(()) + } +} + +/// Update `CodeStorage` with the new `determinism` field. +mod v9 { + use super::*; + use crate::Determinism; + use v6::PrefabWasmModule as OldPrefabWasmModule; + + #[derive(Encode, Decode)] + pub struct PrefabWasmModule { + #[codec(compact)] + pub instruction_weights_version: u32, + #[codec(compact)] + pub initial: u32, + #[codec(compact)] + pub maximum: u32, + pub code: Vec, + pub determinism: Determinism, + } + + #[storage_alias] + type CodeStorage = StorageMap, Identity, CodeHash, PrefabWasmModule>; + + pub fn migrate(weight: &mut Weight) { + >::translate_values(|old: OldPrefabWasmModule| { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Some(PrefabWasmModule { + instruction_weights_version: old.instruction_weights_version, + initial: old.initial, + maximum: old.maximum, + code: old.code, + determinism: Determinism::Deterministic, + }) + }); + } +} + +// Post checks always need to be run against the latest storage version. This is why we +// do not scope them in the per version modules. They always need to be ported to the latest +// version. +#[cfg(feature = "try-runtime")] +mod post_checks { + use super::*; + use crate::Determinism; + use sp_io::default_child_storage as child; + use v8::ContractInfo; + use v9::PrefabWasmModule; + + #[storage_alias] + type CodeStorage = StorageMap, Identity, CodeHash, PrefabWasmModule>; + + #[storage_alias] + type ContractInfoOf = + StorageMap, Twox64Concat, ::AccountId, V>; + + pub fn post_upgrade(old_version: StorageVersion) -> Result<(), &'static str> { + if old_version < 7 { + return Ok(()) + } + + if old_version < 8 { + v8::()?; + } + + if old_version < 9 { + v9::()?; + } + + Ok(()) + } + + fn v8() -> Result<(), &'static str> { + use frame_support::traits::ReservableCurrency; + for (key, value) in ContractInfoOf::>::iter() { + let reserved = T::Currency::reserved_balance(&key); + let stored = value + .storage_base_deposit + .saturating_add(value.storage_byte_deposit) + .saturating_add(value.storage_item_deposit); + ensure!(reserved >= stored, "Reserved balance out of sync."); + + let mut storage_bytes = 0u32; + let mut storage_items = 0u32; + let mut key = Vec::new(); + while let Some(next) = child::next_key(&value.trie_id, &key) { + key = next; + let mut val_out = []; + let len = child::read(&value.trie_id, &key, &mut val_out, 0) + .expect("The loop conditions checks for existence of the key; qed"); + storage_bytes.saturating_accrue(len); + storage_items.saturating_accrue(1); + } + ensure!(storage_bytes == value.storage_bytes, "Storage bytes do not match.",); + ensure!(storage_items == value.storage_items, "Storage items do not match.",); + } + Ok(()) + } + + fn v9() -> Result<(), &'static str> { + for value in CodeStorage::::iter_values() { + ensure!( + value.determinism == Determinism::Deterministic, + "All pre-existing codes need to be deterministic." + ); + } + Ok(()) } } diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index c14165b4c6aec..4f2d3b61d0176 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,22 +18,22 @@ //! This module contains the cost schedule and supporting code that constructs a //! sane default schedule from a `WeightInfo` implementation. -use crate::{weights::WeightInfo, Config}; +use crate::{wasm::Determinism, weights::WeightInfo, Config}; use codec::{Decode, Encode}; -use frame_support::{weights::Weight, DefaultNoBound}; +use frame_support::DefaultNoBound; use pallet_contracts_proc_macro::{ScheduleDebug, WeightDebug}; -use pwasm_utils::{parity_wasm::elements, rules}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_runtime::RuntimeDebug; use sp_std::{marker::PhantomData, vec::Vec}; +use wasm_instrument::{gas_metering, parity_wasm::elements}; /// How many API calls are executed in a single batch. The reason for increasing the amount /// of API calls in batches (per benchmark component increase) is so that the linear regression /// has an easier time determining the contribution of that component. -pub const API_BENCHMARK_BATCH_SIZE: u32 = 100; +pub const API_BENCHMARK_BATCH_SIZE: u32 = 80; /// How many instructions are executed in a single batch. The reasoning is the same /// as for `API_BENCHMARK_BATCH_SIZE`. @@ -104,7 +104,13 @@ pub struct Limits { /// See to find out /// how the stack frame cost is calculated. Each element can be of one of the /// wasm value types. This means the maximum size per element is 64bit. - pub stack_height: u32, + /// + /// # Note + /// + /// It is safe to disable (pass `None`) the `stack_height` when the execution engine + /// is part of the runtime and hence there can be no indeterminism between different + /// client resident execution engines. + pub stack_height: Option, /// Maximum number of globals a module is allowed to declare. /// @@ -112,6 +118,12 @@ pub struct Limits { /// the linear memory limit `memory_pages` applies to them. pub globals: u32, + /// Maximum number of locals a function can have. + /// + /// As wasm engine initializes each of the local, we need to limit their number to confine + /// execution costs. + pub locals: u32, + /// Maximum numbers of parameters a function can have. /// /// Those need to be limited to prevent a potentially exploitable interaction with @@ -141,11 +153,6 @@ pub struct Limits { /// The maximum size of a storage value and event payload in bytes. pub payload_len: u32, - - /// The maximum length of a contract code in bytes. This limit applies to the instrumented - /// version of the code. Therefore `instantiate_with_code` can fail even when supplying - /// a wasm binary below this maximum size. - pub code_len: u32, } impl Limits { @@ -192,6 +199,13 @@ pub struct InstructionWeights { /// Changes to other parts of the schedule should not increment the version in /// order to avoid unnecessary re-instrumentations. pub version: u32, + /// Weight to be used for instructions which don't have benchmarks assigned. + /// + /// This weight is used whenever a code is uploaded with [`Determinism::AllowIndeterminism`] + /// and an instruction (usually a float instruction) is encountered. This weight is **not** + /// used if a contract is uploaded with [`Determinism::Deterministic`]. If this field is set to + /// `0` (the default) only deterministic codes are allowed to be uploaded. + pub fallback: u32, pub i64const: u32, pub i64load: u32, pub i64store: u32, @@ -204,6 +218,7 @@ pub struct InstructionWeights { pub call: u32, pub call_indirect: u32, pub call_indirect_per_param: u32, + pub call_per_local: u32, pub local_get: u32, pub local_set: u32, pub local_tee: u32, @@ -254,136 +269,175 @@ pub struct InstructionWeights { #[scale_info(skip_type_params(T))] pub struct HostFnWeights { /// Weight of calling `seal_caller`. - pub caller: Weight, + pub caller: u64, + + /// Weight of calling `seal_is_contract`. + pub is_contract: u64, + + /// Weight of calling `seal_code_hash`. + pub code_hash: u64, + + /// Weight of calling `seal_own_code_hash`. + pub own_code_hash: u64, + + /// Weight of calling `seal_caller_is_origin`. + pub caller_is_origin: u64, /// Weight of calling `seal_address`. - pub address: Weight, + pub address: u64, /// Weight of calling `seal_gas_left`. - pub gas_left: Weight, + pub gas_left: u64, /// Weight of calling `seal_balance`. - pub balance: Weight, + pub balance: u64, /// Weight of calling `seal_value_transferred`. - pub value_transferred: Weight, + pub value_transferred: u64, /// Weight of calling `seal_minimum_balance`. - pub minimum_balance: Weight, - - /// Weight of calling `seal_contract_deposit`. - pub contract_deposit: Weight, + pub minimum_balance: u64, /// Weight of calling `seal_block_number`. - pub block_number: Weight, + pub block_number: u64, /// Weight of calling `seal_now`. - pub now: Weight, + pub now: u64, /// Weight of calling `seal_weight_to_fee`. - pub weight_to_fee: Weight, + pub weight_to_fee: u64, /// Weight of calling `gas`. - pub gas: Weight, + pub gas: u64, /// Weight of calling `seal_input`. - pub input: Weight, + pub input: u64, /// Weight per input byte copied to contract memory by `seal_input`. - pub input_per_byte: Weight, + pub input_per_byte: u64, /// Weight of calling `seal_return`. - pub r#return: Weight, + pub r#return: u64, /// Weight per byte returned through `seal_return`. - pub return_per_byte: Weight, + pub return_per_byte: u64, /// Weight of calling `seal_terminate`. - pub terminate: Weight, + pub terminate: u64, /// Weight of calling `seal_random`. - pub random: Weight, + pub random: u64, /// Weight of calling `seal_reposit_event`. - pub deposit_event: Weight, + pub deposit_event: u64, /// Weight per topic supplied to `seal_deposit_event`. - pub deposit_event_per_topic: Weight, + pub deposit_event_per_topic: u64, /// Weight per byte of an event deposited through `seal_deposit_event`. - pub deposit_event_per_byte: Weight, + pub deposit_event_per_byte: u64, /// Weight of calling `seal_debug_message`. - pub debug_message: Weight, + pub debug_message: u64, /// Weight of calling `seal_set_storage`. - pub set_storage: Weight, + pub set_storage: u64, - /// Weight per byte of an item stored with `seal_set_storage`. - pub set_storage_per_byte: Weight, + /// Weight per written byten of an item stored with `seal_set_storage`. + pub set_storage_per_new_byte: u64, + + /// Weight per overwritten byte of an item stored with `seal_set_storage`. + pub set_storage_per_old_byte: u64, + + /// Weight of calling `seal_set_code_hash`. + pub set_code_hash: u64, /// Weight of calling `seal_clear_storage`. - pub clear_storage: Weight, + pub clear_storage: u64, + + /// Weight of calling `seal_clear_storage` per byte of the stored item. + pub clear_storage_per_byte: u64, + + /// Weight of calling `seal_contains_storage`. + pub contains_storage: u64, + + /// Weight of calling `seal_contains_storage` per byte of the stored item. + pub contains_storage_per_byte: u64, /// Weight of calling `seal_get_storage`. - pub get_storage: Weight, + pub get_storage: u64, /// Weight per byte of an item received via `seal_get_storage`. - pub get_storage_per_byte: Weight, + pub get_storage_per_byte: u64, + + /// Weight of calling `seal_take_storage`. + pub take_storage: u64, + + /// Weight per byte of an item received via `seal_take_storage`. + pub take_storage_per_byte: u64, /// Weight of calling `seal_transfer`. - pub transfer: Weight, + pub transfer: u64, /// Weight of calling `seal_call`. - pub call: Weight, + pub call: u64, - /// Weight surcharge that is claimed if `seal_call` does a balance transfer. - pub call_transfer_surcharge: Weight, + /// Weight of calling `seal_delegate_call`. + pub delegate_call: u64, - /// Weight per input byte supplied to `seal_call`. - pub call_per_input_byte: Weight, + /// Weight surcharge that is claimed if `seal_call` does a balance transfer. + pub call_transfer_surcharge: u64, - /// Weight per output byte received through `seal_call`. - pub call_per_output_byte: Weight, + /// Weight per byte that is cloned by supplying the `CLONE_INPUT` flag. + pub call_per_cloned_byte: u64, /// Weight of calling `seal_instantiate`. - pub instantiate: Weight, + pub instantiate: u64, - /// Weight per input byte supplied to `seal_instantiate`. - pub instantiate_per_input_byte: Weight, - - /// Weight per output byte received through `seal_instantiate`. - pub instantiate_per_output_byte: Weight, + /// Weight surcharge that is claimed if `seal_instantiate` does a balance transfer. + pub instantiate_transfer_surcharge: u64, /// Weight per salt byte supplied to `seal_instantiate`. - pub instantiate_per_salt_byte: Weight, + pub instantiate_per_salt_byte: u64, /// Weight of calling `seal_hash_sha_256`. - pub hash_sha2_256: Weight, + pub hash_sha2_256: u64, /// Weight per byte hashed by `seal_hash_sha_256`. - pub hash_sha2_256_per_byte: Weight, + pub hash_sha2_256_per_byte: u64, /// Weight of calling `seal_hash_keccak_256`. - pub hash_keccak_256: Weight, + pub hash_keccak_256: u64, /// Weight per byte hashed by `seal_hash_keccak_256`. - pub hash_keccak_256_per_byte: Weight, + pub hash_keccak_256_per_byte: u64, /// Weight of calling `seal_hash_blake2_256`. - pub hash_blake2_256: Weight, + pub hash_blake2_256: u64, /// Weight per byte hashed by `seal_hash_blake2_256`. - pub hash_blake2_256_per_byte: Weight, + pub hash_blake2_256_per_byte: u64, /// Weight of calling `seal_hash_blake2_128`. - pub hash_blake2_128: Weight, + pub hash_blake2_128: u64, /// Weight per byte hashed by `seal_hash_blake2_128`. - pub hash_blake2_128_per_byte: Weight, + pub hash_blake2_128_per_byte: u64, /// Weight of calling `seal_ecdsa_recover`. - pub ecdsa_recover: Weight, + pub ecdsa_recover: u64, + + /// Weight of calling `seal_ecdsa_to_eth_address`. + pub ecdsa_to_eth_address: u64, + + /// Weight of calling `reentrance_count`. + pub reentrance_count: u64, + + /// Weight of calling `account_reentrance_count`. + pub account_reentrance_count: u64, + + /// Weight of calling `instantiation_nonce`. + pub instantiation_nonce: u64, /// The type parameter is used in the default implementation. #[codec(skip)] @@ -404,19 +458,19 @@ macro_rules! call_zero { macro_rules! cost_args { ($name:ident, $( $arg: expr ),+) => { - (T::WeightInfo::$name($( $arg ),+).saturating_sub(call_zero!($name, $( $arg ),+))) + (T::WeightInfo::$name($( $arg ),+).saturating_sub(call_zero!($name, $( $arg ),+))).ref_time() } } macro_rules! cost_batched_args { ($name:ident, $( $arg: expr ),+) => { - cost_args!($name, $( $arg ),+) / Weight::from(API_BENCHMARK_BATCH_SIZE) + cost_args!($name, $( $arg ),+) / u64::from(API_BENCHMARK_BATCH_SIZE) } } macro_rules! cost_instr_no_params_with_batch_size { ($name:ident, $batch_size:expr) => { - (cost_args!($name, 1) / Weight::from($batch_size)) as u32 + (cost_args!($name, 1) / u64::from($batch_size)) as u32 }; } @@ -475,9 +529,10 @@ impl Default for Limits { fn default() -> Self { Self { event_topics: 4, - // 512 * sizeof(i64) will give us a 4k stack. - stack_height: 512, + // No stack limit required because we use a runtime resident execution engine. + stack_height: None, globals: 256, + locals: 1024, parameters: 128, memory_pages: 16, // 4k function pointers (This is in count not bytes). @@ -486,7 +541,6 @@ impl Default for Limits { subject_len: 32, call_depth: 32, payload_len: 16 * 1024, - code_len: 128 * 1024, } } } @@ -495,7 +549,8 @@ impl Default for InstructionWeights { fn default() -> Self { let max_pages = Limits::default().memory_pages; Self { - version: 2, + version: 4, + fallback: 0, i64const: cost_instr!(instr_i64const, 1), i64load: cost_instr!(instr_i64load, 2), i64store: cost_instr!(instr_i64store, 2), @@ -508,6 +563,7 @@ impl Default for InstructionWeights { call: cost_instr!(instr_call, 2), call_indirect: cost_instr!(instr_call_indirect, 3), call_indirect_per_param: cost_instr!(instr_call_indirect_per_param, 1), + call_per_local: cost_instr!(instr_call_per_local, 1), local_get: cost_instr!(instr_local_get, 1), local_set: cost_instr!(instr_local_set, 1), local_tee: cost_instr!(instr_local_tee, 2), @@ -556,12 +612,15 @@ impl Default for HostFnWeights { fn default() -> Self { Self { caller: cost_batched!(seal_caller), + is_contract: cost_batched!(seal_is_contract), + code_hash: cost_batched!(seal_code_hash), + own_code_hash: cost_batched!(seal_own_code_hash), + caller_is_origin: cost_batched!(seal_caller_is_origin), address: cost_batched!(seal_address), gas_left: cost_batched!(seal_gas_left), balance: cost_batched!(seal_balance), value_transferred: cost_batched!(seal_value_transferred), minimum_balance: cost_batched!(seal_minimum_balance), - contract_deposit: cost_batched!(seal_tombstone_deposit), block_number: cost_batched!(seal_block_number), now: cost_batched!(seal_now), weight_to_fee: cost_batched!(seal_weight_to_fee), @@ -581,46 +640,30 @@ impl Default for HostFnWeights { ), debug_message: cost_batched!(seal_debug_message), set_storage: cost_batched!(seal_set_storage), - set_storage_per_byte: cost_byte_batched!(seal_set_storage_per_kb), + set_code_hash: cost_batched!(seal_set_code_hash), + set_storage_per_new_byte: cost_byte_batched!(seal_set_storage_per_new_kb), + set_storage_per_old_byte: cost_byte_batched!(seal_set_storage_per_old_kb), clear_storage: cost_batched!(seal_clear_storage), + clear_storage_per_byte: cost_byte_batched!(seal_clear_storage_per_kb), + contains_storage: cost_batched!(seal_contains_storage), + contains_storage_per_byte: cost_byte_batched!(seal_contains_storage_per_kb), get_storage: cost_batched!(seal_get_storage), get_storage_per_byte: cost_byte_batched!(seal_get_storage_per_kb), + take_storage: cost_batched!(seal_take_storage), + take_storage_per_byte: cost_byte_batched!(seal_take_storage_per_kb), transfer: cost_batched!(seal_transfer), call: cost_batched!(seal_call), - call_transfer_surcharge: cost_batched_args!( - seal_call_per_transfer_input_output_kb, - 1, - 0, - 0 - ), - call_per_input_byte: cost_byte_batched_args!( - seal_call_per_transfer_input_output_kb, - 0, - 1, - 0 - ), - call_per_output_byte: cost_byte_batched_args!( - seal_call_per_transfer_input_output_kb, - 0, - 0, - 1 - ), + delegate_call: cost_batched!(seal_delegate_call), + call_transfer_surcharge: cost_batched_args!(seal_call_per_transfer_clone_kb, 1, 0), + call_per_cloned_byte: cost_batched_args!(seal_call_per_transfer_clone_kb, 0, 1), instantiate: cost_batched!(seal_instantiate), - instantiate_per_input_byte: cost_byte_batched_args!( - seal_instantiate_per_input_output_salt_kb, - 1, - 0, - 0 - ), - instantiate_per_output_byte: cost_byte_batched_args!( - seal_instantiate_per_input_output_salt_kb, - 0, + instantiate_transfer_surcharge: cost_byte_batched_args!( + seal_instantiate_per_transfer_salt_kb, 1, 0 ), instantiate_per_salt_byte: cost_byte_batched_args!( - seal_instantiate_per_input_output_salt_kb, - 0, + seal_instantiate_per_transfer_salt_kb, 0, 1 ), @@ -633,6 +676,10 @@ impl Default for HostFnWeights { hash_blake2_128: cost_batched!(seal_hash_blake2_128), hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb), ecdsa_recover: cost_batched!(seal_ecdsa_recover), + ecdsa_to_eth_address: cost_batched!(seal_ecdsa_to_eth_address), + reentrance_count: cost_batched!(seal_reentrance_count), + account_reentrance_count: cost_batched!(seal_account_reentrance_count), + instantiation_nonce: cost_batched!(seal_instantiation_nonce), _phantom: PhantomData, } } @@ -641,12 +688,17 @@ impl Default for HostFnWeights { struct ScheduleRules<'a, T: Config> { schedule: &'a Schedule, params: Vec, + determinism: Determinism, } impl Schedule { - pub(crate) fn rules(&self, module: &elements::Module) -> impl rules::Rules + '_ { + pub(crate) fn rules( + &self, + module: &elements::Module, + determinism: Determinism, + ) -> impl gas_metering::Rules + '_ { ScheduleRules { - schedule: &self, + schedule: self, params: module .type_section() .iter() @@ -656,11 +708,12 @@ impl Schedule { func.params().len() as u32 }) .collect(), + determinism, } } } -impl<'a, T: Config> rules::Rules for ScheduleRules<'a, T> { +impl<'a, T: Config> gas_metering::Rules for ScheduleRules<'a, T> { fn instruction_cost(&self, instruction: &elements::Instruction) -> Option { use self::elements::Instruction::*; let w = &self.schedule.instruction_weights; @@ -738,16 +791,23 @@ impl<'a, T: Config> rules::Rules for ScheduleRules<'a, T> { I32Rotr | I64Rotr => w.i64rotr, // Returning None makes the gas instrumentation fail which we intend for - // unsupported or unknown instructions. + // unsupported or unknown instructions. Offchain we might allow indeterminism and hence + // use the fallback weight for those instructions. + _ if matches!(self.determinism, Determinism::AllowIndeterminism) && w.fallback > 0 => + w.fallback, _ => return None, }; Some(weight) } - fn memory_grow_cost(&self) -> Option { + fn memory_grow_cost(&self) -> gas_metering::MemoryGrowCost { // We benchmarked the memory.grow instruction with the maximum allowed pages. // The cost for growing is therefore already included in the instruction cost. - None + gas_metering::MemoryGrowCost::Free + } + + fn call_per_local_cost(&self) -> u32 { + self.schedule.instruction_weights.call_per_local } } diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 41db0796717e4..c7644e696196f 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,44 +17,68 @@ //! This module contains routines for accessing and altering a contract related state. +pub mod meter; + use crate::{ exec::{AccountIdOf, StorageKey}, weights::WeightInfo, - CodeHash, Config, ContractInfoOf, DeletionQueue, Error, TrieId, + BalanceOf, CodeHash, Config, ContractInfoOf, DeletionQueue, Error, TrieId, SENTINEL, }; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::{DispatchError, DispatchResult}, - storage::child::{self, ChildInfo, KillStorageResult}, - traits::Get, + storage::child::{self, ChildInfo}, weights::Weight, }; use scale_info::TypeInfo; use sp_core::crypto::UncheckedFrom; -use sp_io::hashing::blake2_256; -use sp_runtime::{traits::Hash, RuntimeDebug}; +use sp_io::KillStorageResult; +use sp_runtime::{ + traits::{Hash, Saturating, Zero}, + RuntimeDebug, +}; use sp_std::{marker::PhantomData, prelude::*}; -pub type ContractInfo = RawContractInfo>; - /// Information for managing an account and its sub trie abstraction. /// This is the required info to cache for an account. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct RawContractInfo { +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(T))] +pub struct ContractInfo { /// Unique ID for the subtree encoded as a bytes vector. pub trie_id: TrieId, /// The code associated with a given account. - pub code_hash: CodeHash, - /// This field is reserved for future evolution of format. - pub _reserved: Option<()>, + pub code_hash: CodeHash, + /// How many bytes of storage are accumulated in this contract's child trie. + pub storage_bytes: u32, + /// How many items of storage are accumulated in this contract's child trie. + pub storage_items: u32, + /// This records to how much deposit the accumulated `storage_bytes` amount to. + pub storage_byte_deposit: BalanceOf, + /// This records to how much deposit the accumulated `storage_items` amount to. + pub storage_item_deposit: BalanceOf, + /// This records how much deposit is put down in order to pay for the contract itself. + /// + /// We need to store this information separately so it is not used when calculating any refunds + /// since the base deposit can only ever be refunded on contract termination. + pub storage_base_deposit: BalanceOf, } -impl RawContractInfo { +impl ContractInfo { /// Associated child trie unique id is built from the hash part of the trie id. #[cfg(test)] pub fn child_trie_info(&self) -> ChildInfo { child_trie_info(&self.trie_id[..]) } + + /// The deposit paying for the accumulated storage generated within the contract's child trie. + pub fn extra_deposit(&self) -> BalanceOf { + self.storage_byte_deposit.saturating_add(self.storage_item_deposit) + } + + /// Same as [`Self::extra_deposit`] but including the base deposit. + pub fn total_deposit(&self) -> BalanceOf { + self.extra_deposit().saturating_add(self.storage_base_deposit) + } } /// Associated child trie unique id is built from the hash part of the trie id. @@ -62,11 +86,53 @@ fn child_trie_info(trie_id: &[u8]) -> ChildInfo { ChildInfo::new_default(trie_id) } -#[derive(Encode, Decode, TypeInfo)] +#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct DeletedContract { pub(crate) trie_id: TrieId, } +/// Information about what happended to the pre-existing value when calling [`Storage::write`]. +#[cfg_attr(test, derive(Debug, PartialEq))] +pub enum WriteOutcome { + /// No value existed at the specified key. + New, + /// A value of the returned length was overwritten. + Overwritten(u32), + /// The returned value was taken out of storage before being overwritten. + /// + /// This is only returned when specifically requested because it causes additional work + /// depending on the size of the pre-existing value. When not requested [`Self::Overwritten`] + /// is returned instead. + Taken(Vec), +} + +impl WriteOutcome { + /// Extracts the size of the overwritten value or `0` if there + /// was no value in storage. + pub fn old_len(&self) -> u32 { + match self { + Self::New => 0, + Self::Overwritten(len) => *len, + Self::Taken(value) => value.len() as u32, + } + } + + /// Extracts the size of the overwritten value or `SENTINEL` if there + /// was no value in storage. + /// + /// # Note + /// + /// We cannot use `0` as sentinel value because there could be a zero sized + /// storage entry which is different from a non existing one. + pub fn old_len_with_sentinel(&self) -> u32 { + match self { + Self::New => SENTINEL, + Self::Overwritten(len) => *len, + Self::Taken(value) => value.len() as u32, + } + } +} + pub struct Storage(PhantomData); impl Storage @@ -78,31 +144,73 @@ where /// /// The read is performed from the `trie_id` only. The `address` is not necessary. If the /// contract doesn't store under the given `key` `None` is returned. - pub fn read(trie_id: &TrieId, key: &StorageKey) -> Option> { - child::get_raw(&child_trie_info(&trie_id), &blake2_256(key)) + pub fn read>(trie_id: &TrieId, key: &K) -> Option> { + child::get_raw(&child_trie_info(trie_id), key.hash().as_slice()) + } + + /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + pub fn size>(trie_id: &TrieId, key: &K) -> Option { + child::len(&child_trie_info(trie_id), key.hash().as_slice()) } /// Update a storage entry into a contract's kv storage. /// - /// If the `opt_new_value` is `None` then the kv pair is removed. + /// If the `new_value` is `None` then the kv pair is removed. If `take` is true + /// a [`WriteOutcome::Taken`] is returned instead of a [`WriteOutcome::Overwritten`]. /// - /// This function also updates the bookkeeping info such as: number of total non-empty pairs a - /// contract owns, the last block the storage was written to, etc. That's why, in contrast to - /// `read`, this function also requires the `account` ID. - pub fn write( - new_info: &mut ContractInfo, - key: &StorageKey, - opt_new_value: Option>, - ) -> DispatchResult { - let hashed_key = blake2_256(key); - let child_trie_info = &child_trie_info(&new_info.trie_id); - - match opt_new_value { - Some(new_value) => child::put_raw(&child_trie_info, &hashed_key, &new_value[..]), - None => child::kill(&child_trie_info, &hashed_key), + /// This function also records how much storage was created or removed if a `storage_meter` + /// is supplied. It should only be absent for testing or benchmarking code. + pub fn write>( + trie_id: &TrieId, + key: &K, + new_value: Option>, + storage_meter: Option<&mut meter::NestedMeter>, + take: bool, + ) -> Result { + let child_trie_info = &child_trie_info(trie_id); + let hashed_key = key.hash(); + let (old_len, old_value) = if take { + let val = child::get_raw(child_trie_info, &hashed_key); + (val.as_ref().map(|v| v.len() as u32), val) + } else { + (child::len(child_trie_info, &hashed_key), None) + }; + + if let Some(storage_meter) = storage_meter { + let mut diff = meter::Diff::default(); + match (old_len, new_value.as_ref().map(|v| v.len() as u32)) { + (Some(old_len), Some(new_len)) => + if new_len > old_len { + diff.bytes_added = new_len - old_len; + } else { + diff.bytes_removed = old_len - new_len; + }, + (None, Some(new_len)) => { + diff.bytes_added = new_len; + diff.items_added = 1; + }, + (Some(old_len), None) => { + diff.bytes_removed = old_len; + diff.items_removed = 1; + }, + (None, None) => (), + } + storage_meter.charge(&diff); + } + + match &new_value { + Some(new_value) => child::put_raw(child_trie_info, &hashed_key, new_value), + None => child::kill(child_trie_info, &hashed_key), } - Ok(()) + Ok(match (old_len, old_value) { + (None, _) => WriteOutcome::New, + (Some(old_len), None) => WriteOutcome::Overwritten(old_len), + (Some(_), Some(old_value)) => WriteOutcome::Taken(old_value), + }) } /// Creates a new contract descriptor in the storage with the given code hash at the given @@ -112,13 +220,21 @@ where pub fn new_contract( account: &AccountIdOf, trie_id: TrieId, - ch: CodeHash, + code_hash: CodeHash, ) -> Result, DispatchError> { if >::contains_key(account) { return Err(Error::::DuplicateContract.into()) } - let contract = ContractInfo:: { code_hash: ch, trie_id, _reserved: None }; + let contract = ContractInfo:: { + code_hash, + trie_id, + storage_bytes: 0, + storage_items: 0, + storage_byte_deposit: Zero::zero(), + storage_item_deposit: Zero::zero(), + storage_base_deposit: Zero::zero(), + }; Ok(contract) } @@ -127,24 +243,21 @@ where /// /// You must make sure that the contract is also removed when queuing the trie for deletion. pub fn queue_trie_for_deletion(contract: &ContractInfo) -> DispatchResult { - if >::decode_len().unwrap_or(0) >= T::DeletionQueueDepth::get() as usize { - Err(Error::::DeletionQueueFull.into()) - } else { - >::append(DeletedContract { trie_id: contract.trie_id.clone() }); - Ok(()) - } + >::try_append(DeletedContract { trie_id: contract.trie_id.clone() }) + .map_err(|_| >::DeletionQueueFull.into()) } /// Calculates the weight that is necessary to remove one key from the trie and how many /// of those keys can be deleted from the deletion queue given the supplied queue length /// and weight limit. pub fn deletion_budget(queue_len: usize, weight_limit: Weight) -> (u64, u32) { - let base_weight = T::WeightInfo::on_initialize(); + let base_weight = T::WeightInfo::on_process_deletion_queue_batch(); let weight_per_queue_item = T::WeightInfo::on_initialize_per_queue_item(1) - T::WeightInfo::on_initialize_per_queue_item(0); - let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) - - T::WeightInfo::on_initialize_per_trie_key(0); - let decoding_weight = weight_per_queue_item.saturating_mul(queue_len as Weight); + let weight_per_key = (T::WeightInfo::on_initialize_per_trie_key(1) - + T::WeightInfo::on_initialize_per_trie_key(0)) + .ref_time(); + let decoding_weight = weight_per_queue_item.saturating_mul(queue_len as u64); // `weight_per_key` being zero makes no sense and would constitute a failure to // benchmark properly. We opt for not removing any keys at all in this case. @@ -152,7 +265,8 @@ where .saturating_sub(base_weight) .saturating_sub(decoding_weight) .checked_div(weight_per_key) - .unwrap_or(0) as u32; + .unwrap_or(Weight::zero()) + .ref_time() as u32; (weight_per_key, key_budget) } @@ -163,7 +277,7 @@ where pub fn process_deletion_queue_batch(weight_limit: Weight) -> Weight { let queue_len = >::decode_len().unwrap_or(0); if queue_len == 0 { - return 0 + return Weight::zero() } let (weight_per_key, mut remaining_key_budget) = @@ -178,35 +292,42 @@ where let mut queue = >::get(); - if let (Some(trie), true) = (queue.get(0), remaining_key_budget > 0) { - let outcome = - child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget)); + while !queue.is_empty() && remaining_key_budget > 0 { + // Cannot panic due to loop condition + let trie = &mut queue[0]; + #[allow(deprecated)] + let outcome = child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget)); let keys_removed = match outcome { - // This should not happen as our budget was large enough to remove all keys. - KillStorageResult::SomeRemaining(count) => count, - KillStorageResult::AllRemoved(count) => { + // This happens when our budget wasn't large enough to remove all keys. + KillStorageResult::SomeRemaining(c) => c, + KillStorageResult::AllRemoved(c) => { // We do not care to preserve order. The contract is deleted already and - // noone waits for the trie to be deleted. + // no one waits for the trie to be deleted. queue.swap_remove(0); - count + c }, }; remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed); } >::put(queue); - weight_limit.saturating_sub(weight_per_key.saturating_mul(remaining_key_budget as Weight)) + let ref_time_weight = weight_limit + .ref_time() + .saturating_sub(weight_per_key.saturating_mul(u64::from(remaining_key_budget))); + Weight::from_ref_time(ref_time_weight) } - /// This generator uses inner counter for account id and applies the hash over `AccountId + - /// accountid_counter`. - pub fn generate_trie_id(account_id: &AccountIdOf, seed: u64) -> TrieId { - let buf: Vec<_> = account_id.as_ref().iter().chain(&seed.to_le_bytes()).cloned().collect(); - T::Hashing::hash(&buf).as_ref().into() + /// Generates a unique trie id by returning `hash(account_id ++ nonce)`. + pub fn generate_trie_id(account_id: &AccountIdOf, nonce: u64) -> TrieId { + let buf: Vec<_> = account_id.as_ref().iter().chain(&nonce.to_le_bytes()).cloned().collect(); + T::Hashing::hash(&buf) + .as_ref() + .to_vec() + .try_into() + .expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed") } /// Returns the code hash of the contract specified by `account` ID. - #[cfg(test)] pub fn code_hash(account: &AccountIdOf) -> Option> { >::get(account).map(|i| i.code_hash) } @@ -214,9 +335,11 @@ where /// Fill up the queue in order to exercise the limits during testing. #[cfg(test)] pub fn fill_queue_with_dummies() { - let queue: Vec<_> = (0..T::DeletionQueueDepth::get()) - .map(|_| DeletedContract { trie_id: vec![] }) + use frame_support::{traits::Get, BoundedVec}; + let queue: Vec = (0..T::DeletionQueueDepth::get()) + .map(|_| DeletedContract { trie_id: TrieId::default() }) .collect(); - >::put(queue); + let bounded: BoundedVec<_, _> = queue.try_into().map_err(|_| ()).unwrap(); + >::put(bounded); } } diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs new file mode 100644 index 0000000000000..0a63eb42b86cb --- /dev/null +++ b/frame/contracts/src/storage/meter.rs @@ -0,0 +1,797 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! This module contains functions to meter the storage deposit. + +use crate::{storage::ContractInfo, BalanceOf, Config, Error, Inspect, Pallet}; +use codec::Encode; +use frame_support::{ + dispatch::DispatchError, + ensure, + traits::{ + tokens::{BalanceStatus, WithdrawConsequence}, + Currency, ExistenceRequirement, Get, ReservableCurrency, + }, + DefaultNoBound, RuntimeDebugNoBound, +}; +use pallet_contracts_primitives::StorageDeposit as Deposit; +use sp_core::crypto::UncheckedFrom; +use sp_runtime::{ + traits::{Saturating, Zero}, + FixedPointNumber, FixedU128, +}; +use sp_std::{marker::PhantomData, vec::Vec}; + +/// Deposit that uses the native currency's balance type. +pub type DepositOf = Deposit>; + +/// A production root storage meter that actually charges from its origin. +pub type Meter = RawMeter; + +/// A production nested storage meter that actually charges from its origin. +pub type NestedMeter = RawMeter; + +/// A production storage meter that actually charges from its origin. +/// +/// This can be used where we want to be generic over the state (Root vs. Nested). +pub type GenericMeter = RawMeter; + +/// A trait that allows to decouple the metering from the charging of balance. +/// +/// This mostly exists for testing so that the charging can be mocked. +pub trait Ext { + /// This checks whether `origin` is able to afford the storage deposit limit. + /// + /// It is necessary to do this check beforehand so that the charge won't fail later on. + /// + /// `origin`: The origin of the call stack from which is responsible for putting down a deposit. + /// `limit`: The limit with which the meter was constructed. + /// `min_leftover`: How much `free_balance` in addition to the existential deposit (ed) should + /// be left inside the `origin` account. + /// + /// Returns the limit that should be used by the meter. If origin can't afford the `limit` + /// it returns `Err`. + fn check_limit( + origin: &T::AccountId, + limit: Option>, + min_leftover: BalanceOf, + ) -> Result, DispatchError>; + /// This is called to inform the implementer that some balance should be charged due to + /// some interaction of the `origin` with a `contract`. + /// + /// The balance transfer can either flow from `origin` to `contract` or the other way + /// around depending on whether `amount` constitutes a `Charge` or a `Refund`. + /// It is guaranteed that that this succeeds because no more balance than returned by + /// `check_limit` is ever charged. This is why this function is infallible. + /// `terminated` designates whether the `contract` was terminated. + fn charge( + origin: &T::AccountId, + contract: &T::AccountId, + amount: &DepositOf, + terminated: bool, + ); +} + +/// This [`Ext`] is used for actual on-chain execution when balance needs to be charged. +/// +/// It uses [`ReservableCurrency`] in order to do accomplish the reserves. +pub enum ReservingExt {} + +/// Used to implement a type state pattern for the meter. +/// +/// It is sealed and cannot be implemented outside of this module. +pub trait State: private::Sealed {} + +/// State parameter that constitutes a meter that is in its root state. +pub enum Root {} + +/// State parameter that constitutes a meter that is in its nested state. +pub enum Nested {} + +impl State for Root {} +impl State for Nested {} + +/// A type that allows the metering of consumed or freed storage of a single contract call stack. +#[derive(DefaultNoBound, RuntimeDebugNoBound)] +pub struct RawMeter { + /// The limit of how much balance this meter is allowed to consume. + limit: BalanceOf, + /// The amount of balance that was used in this meter and all of its already absorbed children. + total_deposit: DepositOf, + /// The amount of storage changes that were recorded in this meter alone. + own_contribution: Contribution, + /// List of charges that should be applied at the end of a contract stack execution. + /// + /// We only have one charge per contract hence the size of this vector is + /// limited by the maximum call depth. + charges: Vec>, + /// Type parameters are only used in impls. + _phantom: PhantomData<(E, S)>, +} + +/// This type is used to describe a storage change when charging from the meter. +#[derive(Default, RuntimeDebugNoBound)] +pub struct Diff { + /// How many bytes were added to storage. + pub bytes_added: u32, + /// How many bytes were removed from storage. + pub bytes_removed: u32, + /// How many storage items were added to storage. + pub items_added: u32, + /// How many storage items were removed from storage. + pub items_removed: u32, +} + +impl Diff { + /// Calculate how much of a charge or refund results from applying the diff and store it + /// in the passed `info` if any. + /// + /// # Note + /// + /// In case `None` is passed for `info` only charges are calculated. This is because refunds + /// are calculated pro rata of the existing storage within a contract and hence need extract + /// this information from the passed `info`. + pub fn update_contract(&self, info: Option<&mut ContractInfo>) -> DepositOf { + let per_byte = T::DepositPerByte::get(); + let per_item = T::DepositPerItem::get(); + let bytes_added = self.bytes_added.saturating_sub(self.bytes_removed); + let items_added = self.items_added.saturating_sub(self.items_removed); + let mut bytes_deposit = Deposit::Charge(per_byte.saturating_mul((bytes_added).into())); + let mut items_deposit = Deposit::Charge(per_item.saturating_mul((items_added).into())); + + // Without any contract info we can only calculate diffs which add storage + let info = if let Some(info) = info { + info + } else { + debug_assert_eq!(self.bytes_removed, 0); + debug_assert_eq!(self.items_removed, 0); + return bytes_deposit.saturating_add(&items_deposit) + }; + + // Refunds are calculated pro rata based on the accumulated storage within the contract + let bytes_removed = self.bytes_removed.saturating_sub(self.bytes_added); + let items_removed = self.items_removed.saturating_sub(self.items_added); + let ratio = FixedU128::checked_from_rational(bytes_removed, info.storage_bytes) + .unwrap_or_default() + .min(FixedU128::from_u32(1)); + bytes_deposit = bytes_deposit + .saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_byte_deposit))); + let ratio = FixedU128::checked_from_rational(items_removed, info.storage_items) + .unwrap_or_default() + .min(FixedU128::from_u32(1)); + items_deposit = items_deposit + .saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_item_deposit))); + + // We need to update the contract info structure with the new deposits + info.storage_bytes = + info.storage_bytes.saturating_add(bytes_added).saturating_sub(bytes_removed); + info.storage_items = + info.storage_items.saturating_add(items_added).saturating_sub(items_removed); + match &bytes_deposit { + Deposit::Charge(amount) => + info.storage_byte_deposit = info.storage_byte_deposit.saturating_add(*amount), + Deposit::Refund(amount) => + info.storage_byte_deposit = info.storage_byte_deposit.saturating_sub(*amount), + } + match &items_deposit { + Deposit::Charge(amount) => + info.storage_item_deposit = info.storage_item_deposit.saturating_add(*amount), + Deposit::Refund(amount) => + info.storage_item_deposit = info.storage_item_deposit.saturating_sub(*amount), + } + + bytes_deposit.saturating_add(&items_deposit) + } +} + +impl Diff { + fn saturating_add(&self, rhs: &Self) -> Self { + Self { + bytes_added: self.bytes_added.saturating_add(rhs.bytes_added), + bytes_removed: self.bytes_removed.saturating_add(rhs.bytes_removed), + items_added: self.items_added.saturating_add(rhs.items_added), + items_removed: self.items_removed.saturating_add(rhs.items_removed), + } + } +} + +/// Records information to charge or refund a plain account. +/// +/// All the charges are deferred to the end of a whole call stack. Reason is that by doing +/// this we can do all the refunds before doing any charge. This way a plain account can use +/// more deposit than it has balance as along as it is covered by a refund. This +/// essentially makes the order of storage changes irrelevant with regard to the deposit system. +#[derive(RuntimeDebugNoBound, Clone)] +struct Charge { + contract: T::AccountId, + amount: DepositOf, + terminated: bool, +} + +/// Records the storage changes of a storage meter. +#[derive(RuntimeDebugNoBound)] +enum Contribution { + /// The contract the meter belongs to is alive and accumulates changes using a [`Diff`]. + Alive(Diff), + /// The meter was checked against its limit using [`RawMeter::enforce_limit`] at the end of + /// its execution. In this process the [`Diff`] was converted into a [`Deposit`]. + Checked(DepositOf), + /// The contract was terminated. In this process the [`Diff`] was converted into a [`Deposit`] + /// in order to calculate the refund. + Terminated(DepositOf), +} + +impl Contribution { + /// See [`Diff::update_contract`]. + fn update_contract(&self, info: Option<&mut ContractInfo>) -> DepositOf { + match self { + Self::Alive(diff) => diff.update_contract::(info), + Self::Terminated(deposit) | Self::Checked(deposit) => deposit.clone(), + } + } +} + +impl Default for Contribution { + fn default() -> Self { + Self::Alive(Default::default()) + } +} + +/// Functions that apply to all states. +impl RawMeter +where + T: Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, + E: Ext, + S: State, +{ + /// Create a new child that has its `limit` set to whatever is remaining of it. + /// + /// This is called whenever a new subcall is initiated in order to track the storage + /// usage for this sub call separately. This is necessary because we want to exchange balance + /// with the current contract we are interacting with. + pub fn nested(&self) -> RawMeter { + debug_assert!(self.is_alive()); + RawMeter { limit: self.available(), ..Default::default() } + } + + /// Absorb a child that was spawned to handle a sub call. + /// + /// This should be called whenever a sub call comes to its end and it is **not** reverted. + /// This does the actual balance transfer from/to `origin` and `contract` based on the overall + /// storage consumption of the call. It also updates the supplied contract info. + /// + /// In case a contract reverted the child meter should just be dropped in order to revert + /// any changes it recorded. + /// + /// # Parameters + /// + /// - `absorbed`: The child storage meter that should be absorbed. + /// - `origin`: The origin that spawned the original root meter. + /// - `contract`: The contract that this sub call belongs to. + /// - `info`: The info of the contract in question. `None` if the contract was terminated. + pub fn absorb( + &mut self, + absorbed: RawMeter, + contract: &T::AccountId, + info: Option<&mut ContractInfo>, + ) { + let own_deposit = absorbed.own_contribution.update_contract(info); + self.total_deposit = self + .total_deposit + .saturating_add(&absorbed.total_deposit) + .saturating_add(&own_deposit); + if !own_deposit.is_zero() { + self.charges.extend_from_slice(&absorbed.charges); + self.charges.push(Charge { + contract: contract.clone(), + amount: own_deposit, + terminated: absorbed.is_terminated(), + }); + } + } + + /// The amount of balance that is still available from the original `limit`. + fn available(&self) -> BalanceOf { + self.total_deposit.available(&self.limit) + } + + /// True if the contract is alive. + fn is_alive(&self) -> bool { + matches!(self.own_contribution, Contribution::Alive(_)) + } + + /// True if the contract is terminated. + fn is_terminated(&self) -> bool { + matches!(self.own_contribution, Contribution::Terminated(_)) + } +} + +/// Functions that only apply to the root state. +impl RawMeter +where + T: Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, + E: Ext, +{ + /// Create new storage meter for the specified `origin` and `limit`. + /// + /// This tries to [`Ext::check_limit`] on `origin` and fails if this is not possible. + pub fn new( + origin: &T::AccountId, + limit: Option>, + min_leftover: BalanceOf, + ) -> Result { + let limit = E::check_limit(origin, limit, min_leftover)?; + Ok(Self { limit, ..Default::default() }) + } + + /// The total amount of deposit that should change hands as result of the execution + /// that this meter was passed into. This will also perform all the charges accumulated + /// in the whole contract stack. + /// + /// This drops the root meter in order to make sure it is only called when the whole + /// execution did finish. + pub fn into_deposit(self, origin: &T::AccountId) -> DepositOf { + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) { + E::charge(origin, &charge.contract, &charge.amount, charge.terminated); + } + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) { + E::charge(origin, &charge.contract, &charge.amount, charge.terminated); + } + self.total_deposit + } +} + +/// Functions that only apply to the nested state. +impl RawMeter +where + T: Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, + E: Ext, +{ + /// Try to charge the `diff` from the meter. Fails if this would exceed the original limit. + pub fn charge(&mut self, diff: &Diff) { + debug_assert!(self.is_alive()); + match &mut self.own_contribution { + Contribution::Alive(own) => *own = own.saturating_add(diff), + _ => panic!("Charge is never called after termination; qed"), + }; + } + + /// Charge from `origin` a storage deposit for contract instantiation. + /// + /// This immediately transfers the balance in order to create the account. + pub fn charge_instantiate( + &mut self, + origin: &T::AccountId, + contract: &T::AccountId, + info: &mut ContractInfo, + ) -> Result, DispatchError> { + debug_assert!(self.is_alive()); + let mut deposit = + Diff { bytes_added: info.encoded_size() as u32, items_added: 1, ..Default::default() } + .update_contract::(None); + + // Instantiate needs to transfer the minimum balance at least in order to pull the + // contract's account into existence. + deposit = deposit.max(Deposit::Charge(Pallet::::min_balance())); + if deposit.charge_or_zero() > self.limit { + return Err(>::StorageDepositLimitExhausted.into()) + } + + // We do not increase `own_contribution` because this will be charged later when the + // contract execution does conclude and hence would lead to a double charge. + self.total_deposit = deposit.clone(); + info.storage_base_deposit = deposit.charge_or_zero(); + if !deposit.is_zero() { + // We need to charge immediately so that the account is created before the `value` + // is transferred from the caller to the contract. + E::charge(origin, contract, &deposit, false); + } + Ok(deposit) + } + + /// Call to tell the meter that the currently executing contract was executed. + /// + /// This will manipulate the meter so that all storage deposit accumulated in + /// `contract_info` will be refunded to the `origin` of the meter. + pub fn terminate(&mut self, info: &ContractInfo) { + debug_assert!(self.is_alive()); + self.own_contribution = Contribution::Terminated(Deposit::Refund(info.total_deposit())); + } + + /// [`Self::charge`] does not enforce the storage limit since we want to do this check as late + /// as possible to allow later refunds to offset earlier charges. + /// + /// # Note + /// + /// We only need to call this **once** for every call stack and not for every cross contract + /// call. Hence this is only called when the last call frame returns. + pub fn enforce_limit( + &mut self, + info: Option<&mut ContractInfo>, + ) -> Result<(), DispatchError> { + let deposit = self.own_contribution.update_contract(info); + let total_deposit = self.total_deposit.saturating_add(&deposit); + // We don't want to override a `Terminated` with a `Checked`. + if self.is_alive() { + self.own_contribution = Contribution::Checked(deposit); + } + if let Deposit::Charge(amount) = total_deposit { + if amount > self.limit { + return Err(>::StorageDepositLimitExhausted.into()) + } + } + Ok(()) + } +} + +impl Ext for ReservingExt +where + T: Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, +{ + fn check_limit( + origin: &T::AccountId, + limit: Option>, + min_leftover: BalanceOf, + ) -> Result, DispatchError> { + let max = T::Currency::reducible_balance(origin, true).saturating_sub(min_leftover); + let limit = limit.unwrap_or(max); + ensure!( + limit <= max && + matches!(T::Currency::can_withdraw(origin, limit), WithdrawConsequence::Success), + >::StorageDepositNotEnoughFunds, + ); + Ok(limit) + } + + fn charge( + origin: &T::AccountId, + contract: &T::AccountId, + amount: &DepositOf, + terminated: bool, + ) { + // There is nothing we can do when this fails as this constitutes a bug in the runtime: + // Either the runtime does not hold up the invariant of never deleting a contract's account + // or it does not honor reserved balances. We need to settle for emitting an error log + // in this case. + match amount { + Deposit::Charge(amount) => { + // This will never fail because a contract's account is required to exist + // at all times. The pallet enforces this invariant by depositing at least the + // existential deposit when instantiating and never refunds it unless the contract + // is removed. This means the receiver always exists except when instantiating a + // contract. In this case we made sure that at least the existential deposit is + // sent. The sender always has enough balance because we checked that it had enough + // balance when instantiating the storage meter. + let result = T::Currency::transfer( + origin, + contract, + *amount, + ExistenceRequirement::KeepAlive, + ) + .and_then(|_| T::Currency::reserve(contract, *amount)); + if let Err(err) = result { + log::error!( + target: "runtime::contracts", + "Failed to transfer storage deposit {:?} from origin {:?} to contract {:?}: {:?}", + amount, origin, contract, err, + ); + if cfg!(debug_assertions) { + panic!("Unable to collect storage deposit. This is a bug."); + } + } + }, + // For `Refund(_)` no error happen because the initial value transfer from the + // origin to the contract has a keep alive existence requirement and when reserving we + // make sure to leave at least the ed in the free balance. Therefore the receiver always + // exists because there is no way for it to be removed in between. The sender always has + // enough reserved balance because we track it in the `ContractInfo` and never send more + // back than we have. + Deposit::Refund(amount) => { + let amount = if terminated { + *amount + } else { + // This is necessary when the `storage_deposit` tracked inside the account + // info is out of sync with the actual balance. That can only happen due to + // slashing. We make sure to never dust the contract's account through a + // refund because we consider this unexpected behaviour. + *amount.min( + &T::Currency::reserved_balance(contract) + .saturating_sub(Pallet::::min_balance()), + ) + }; + let result = + T::Currency::repatriate_reserved(contract, origin, amount, BalanceStatus::Free); + if matches!(result, Ok(val) if !val.is_zero()) || matches!(result, Err(_)) { + log::error!( + target: "runtime::contracts", + "Failed to repatriate storage deposit {:?} from contract {:?} to origin {:?}: {:?}", + amount, contract, origin, result, + ); + if cfg!(debug_assertions) { + panic!("Unable to refund storage deposit. This is a bug."); + } + } + }, + }; + } +} + +mod private { + pub trait Sealed {} + impl Sealed for super::Root {} + impl Sealed for super::Nested {} +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + exec::AccountIdOf, + tests::{Test, ALICE, BOB, CHARLIE}, + }; + use frame_support::parameter_types; + use pretty_assertions::assert_eq; + + type TestMeter = RawMeter; + + parameter_types! { + static TestExtTestValue: TestExt = Default::default(); + } + + #[derive(Debug, PartialEq, Eq, Clone)] + struct LimitCheck { + origin: AccountIdOf, + limit: BalanceOf, + min_leftover: BalanceOf, + } + + #[derive(Debug, PartialEq, Eq, Clone)] + struct Charge { + origin: AccountIdOf, + contract: AccountIdOf, + amount: DepositOf, + terminated: bool, + } + + #[derive(Default, Debug, PartialEq, Eq, Clone)] + pub struct TestExt { + limit_checks: Vec, + charges: Vec, + } + + impl TestExt { + fn clear(&mut self) { + self.limit_checks.clear(); + self.charges.clear(); + } + } + + impl Ext for TestExt { + fn check_limit( + origin: &AccountIdOf, + limit: Option>, + min_leftover: BalanceOf, + ) -> Result, DispatchError> { + let limit = limit.unwrap_or(42); + TestExtTestValue::mutate(|ext| { + ext.limit_checks + .push(LimitCheck { origin: origin.clone(), limit, min_leftover }) + }); + Ok(limit) + } + + fn charge( + origin: &AccountIdOf, + contract: &AccountIdOf, + amount: &DepositOf, + terminated: bool, + ) { + TestExtTestValue::mutate(|ext| { + ext.charges.push(Charge { + origin: origin.clone(), + contract: contract.clone(), + amount: amount.clone(), + terminated, + }) + }); + } + } + + fn clear_ext() { + TestExtTestValue::mutate(|ext| ext.clear()) + } + + #[derive(Default)] + struct StorageInfo { + bytes: u32, + items: u32, + bytes_deposit: BalanceOf, + items_deposit: BalanceOf, + } + + fn new_info(info: StorageInfo) -> ContractInfo { + use crate::storage::Storage; + use sp_runtime::traits::Hash; + + ContractInfo:: { + trie_id: >::generate_trie_id(&ALICE, 42), + code_hash: ::Hashing::hash(b"42"), + storage_bytes: info.bytes, + storage_items: info.items, + storage_byte_deposit: info.bytes_deposit, + storage_item_deposit: info.items_deposit, + storage_base_deposit: Default::default(), + } + } + + #[test] + fn new_reserves_balance_works() { + clear_ext(); + + TestMeter::new(&ALICE, Some(1_000), 0).unwrap(); + + assert_eq!( + TestExtTestValue::get(), + TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], + ..Default::default() + } + ) + } + + #[test] + fn empty_charge_works() { + clear_ext(); + + let mut meter = TestMeter::new(&ALICE, Some(1_000), 0).unwrap(); + assert_eq!(meter.available(), 1_000); + + // an empty charge does not create a `Charge` entry + let mut nested0 = meter.nested(); + nested0.charge(&Default::default()); + meter.absorb(nested0, &BOB, None); + + assert_eq!( + TestExtTestValue::get(), + TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], + ..Default::default() + } + ) + } + + #[test] + fn charging_works() { + clear_ext(); + + let mut meter = TestMeter::new(&ALICE, Some(100), 0).unwrap(); + assert_eq!(meter.available(), 100); + + let mut nested0_info = + new_info(StorageInfo { bytes: 100, items: 5, bytes_deposit: 100, items_deposit: 10 }); + let mut nested0 = meter.nested(); + nested0.charge(&Diff { + bytes_added: 108, + bytes_removed: 5, + items_added: 1, + items_removed: 2, + }); + nested0.charge(&Diff { bytes_removed: 99, ..Default::default() }); + + let mut nested1_info = + new_info(StorageInfo { bytes: 100, items: 10, bytes_deposit: 100, items_deposit: 20 }); + let mut nested1 = nested0.nested(); + nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info)); + + let mut nested2_info = + new_info(StorageInfo { bytes: 100, items: 7, bytes_deposit: 100, items_deposit: 20 }); + let mut nested2 = nested0.nested(); + nested2.charge(&Diff { items_removed: 7, ..Default::default() }); + nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info)); + + nested0.enforce_limit(Some(&mut nested0_info)).unwrap(); + meter.absorb(nested0, &BOB, Some(&mut nested0_info)); + + meter.into_deposit(&ALICE); + + assert_eq!(nested0_info.extra_deposit(), 112); + assert_eq!(nested1_info.extra_deposit(), 110); + assert_eq!(nested2_info.extra_deposit(), 100); + + assert_eq!( + TestExtTestValue::get(), + TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }], + charges: vec![ + Charge { + origin: ALICE, + contract: CHARLIE, + amount: Deposit::Refund(10), + terminated: false + }, + Charge { + origin: ALICE, + contract: CHARLIE, + amount: Deposit::Refund(20), + terminated: false + }, + Charge { + origin: ALICE, + contract: BOB, + amount: Deposit::Charge(2), + terminated: false + } + ] + } + ) + } + + #[test] + fn termination_works() { + clear_ext(); + + let mut meter = TestMeter::new(&ALICE, Some(1_000), 0).unwrap(); + assert_eq!(meter.available(), 1_000); + + let mut nested0 = meter.nested(); + nested0.charge(&Diff { + bytes_added: 5, + bytes_removed: 1, + items_added: 3, + items_removed: 1, + }); + nested0.charge(&Diff { items_added: 2, ..Default::default() }); + + let mut nested1_info = + new_info(StorageInfo { bytes: 100, items: 10, bytes_deposit: 100, items_deposit: 20 }); + let mut nested1 = nested0.nested(); + nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); + nested1.terminate(&nested1_info); + nested0.enforce_limit(Some(&mut nested1_info)).unwrap(); + nested0.absorb(nested1, &CHARLIE, None); + + meter.absorb(nested0, &BOB, None); + meter.into_deposit(&ALICE); + + assert_eq!( + TestExtTestValue::get(), + TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], + charges: vec![ + Charge { + origin: ALICE, + contract: CHARLIE, + amount: Deposit::Refund(120), + terminated: true + }, + Charge { + origin: ALICE, + contract: BOB, + amount: Deposit::Charge(12), + terminated: false + } + ] + } + ) + } +} diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index f5b95c192c42e..6121d880ca8c5 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,37 +15,43 @@ // See the License for the specific language governing permissions and // limitations under the License. +use self::test_utils::hash; use crate::{ chain_extension::{ - ChainExtension, Environment, Ext, InitState, Result as ExtensionResult, RetVal, - ReturnFlags, SysConfig, UncheckedFrom, + ChainExtension, Environment, Ext, InitState, RegisteredChainExtension, + Result as ExtensionResult, RetVal, ReturnFlags, SysConfig, UncheckedFrom, }, - exec::Frame, - storage::{RawContractInfo, Storage}, - wasm::{PrefabWasmModule, ReturnCode as RuntimeReturnCode}, + exec::{FixSizedKey, Frame}, + storage::Storage, + tests::test_utils::{get_contract, get_contract_checked}, + wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode}, weights::WeightInfo, - BalanceOf, Config, ContractInfoOf, Error, Pallet, Schedule, + BalanceOf, Code, CodeStorage, Config, ContractInfoOf, DefaultAddressGenerator, DeletionQueue, + Error, Pallet, Schedule, }; use assert_matches::assert_matches; use codec::Encode; use frame_support::{ - assert_err, assert_err_ignore_postinfo, assert_ok, - dispatch::DispatchErrorWithPostInfo, + assert_err, assert_err_ignore_postinfo, assert_noop, assert_ok, + dispatch::{DispatchClass, DispatchErrorWithPostInfo, PostDispatchInfo}, parameter_types, storage::child, - traits::{Contains, Currency, OnInitialize, ReservableCurrency}, - weights::{constants::WEIGHT_PER_SECOND, DispatchClass, PostDispatchInfo, Weight}, + traits::{ + BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, LockableCurrency, OnIdle, + OnInitialize, ReservableCurrency, WithdrawReasons, + }, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use frame_system::{self as system, EventRecord, Phase}; -use pretty_assertions::assert_eq; -use sp_core::Bytes; +use pretty_assertions::{assert_eq, assert_ne}; use sp_io::hashing::blake2_256; +use sp_keystore::{testing::KeyStore, KeystoreExt}; use sp_runtime::{ testing::{Header, H256}, traits::{BlakeTwo256, Convert, Hash, IdentityLookup}, AccountId32, }; -use std::cell::RefCell; +use std::sync::Arc; use crate as pallet_contracts; @@ -69,32 +75,20 @@ frame_support::construct_runtime!( #[macro_use] pub mod test_utils { - use super::{Balances, Test}; + use super::{Balances, Hash, SysConfig, Test}; use crate::{ - exec::{AccountIdOf, StorageKey}, - storage::Storage, - AccountCounter, CodeHash, ContractInfoOf, Pallet as Contracts, TrieId, + exec::AccountIdOf, storage::Storage, CodeHash, Config, ContractInfo, ContractInfoOf, Nonce, }; + use codec::Encode; use frame_support::traits::Currency; - pub fn set_storage(addr: &AccountIdOf, key: &StorageKey, value: Option>) { - let mut contract_info = >::get(&addr).unwrap(); - Storage::::write(&mut contract_info, key, value).unwrap(); - } - pub fn get_storage(addr: &AccountIdOf, key: &StorageKey) -> Option> { - let contract_info = >::get(&addr).unwrap(); - Storage::::read(&contract_info.trie_id, key) - } - pub fn generate_trie_id(address: &AccountIdOf) -> TrieId { - let seed = >::mutate(|counter| { + pub fn place_contract(address: &AccountIdOf, code_hash: CodeHash) { + let nonce = >::mutate(|counter| { *counter += 1; *counter }); - Storage::::generate_trie_id(address, seed) - } - pub fn place_contract(address: &AccountIdOf, code_hash: CodeHash) { - let trie_id = generate_trie_id(address); - set_balance(address, Contracts::::subsistence_threshold() * 10); + let trie_id = Storage::::generate_trie_id(address, nonce); + set_balance(address, ::Currency::minimum_balance() * 10); let contract = Storage::::new_contract(&address, trie_id, code_hash).unwrap(); >::insert(address, contract); } @@ -105,41 +99,68 @@ pub mod test_utils { pub fn get_balance(who: &AccountIdOf) -> u64 { Balances::free_balance(who) } + pub fn get_contract(addr: &AccountIdOf) -> ContractInfo { + get_contract_checked(addr).unwrap() + } + pub fn get_contract_checked(addr: &AccountIdOf) -> Option> { + ContractInfoOf::::get(addr) + } + pub fn hash(s: &S) -> <::Hashing as Hash>::Output { + <::Hashing as Hash>::hash_of(s) + } macro_rules! assert_return_code { ( $x:expr , $y:expr $(,)? ) => {{ - use sp_std::convert::TryInto; assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); }}; } + macro_rules! assert_refcount { ( $code_hash:expr , $should:expr $(,)? ) => {{ - let is = crate::CodeStorage::::get($code_hash).map(|m| m.refcount()).unwrap_or(0); + let is = crate::OwnerInfoOf::::get($code_hash).map(|m| m.refcount()).unwrap(); assert_eq!(is, $should); }}; } } -thread_local! { - static TEST_EXTENSION: RefCell = Default::default(); +impl Test { + pub fn set_unstable_interface(unstable_interface: bool) { + UNSTABLE_INTERFACE.with(|v| *v.borrow_mut() = unstable_interface); + } +} + +parameter_types! { + static TestExtensionTestValue: TestExtension = Default::default(); } +#[derive(Clone)] pub struct TestExtension { enabled: bool, last_seen_buffer: Vec, last_seen_inputs: (u32, u32, u32, u32), } +#[derive(Default)] +pub struct RevertingExtension; + +#[derive(Default)] +pub struct DisabledExtension; + +#[derive(Default)] +pub struct TempStorageExtension { + storage: u32, +} + impl TestExtension { fn disable() { - TEST_EXTENSION.with(|e| e.borrow_mut().enabled = false) + TestExtensionTestValue::mutate(|e| e.enabled = false) } fn last_seen_buffer() -> Vec { - TEST_EXTENSION.with(|e| e.borrow().last_seen_buffer.clone()) + TestExtensionTestValue::get().last_seen_buffer.clone() } fn last_seen_inputs() -> (u32, u32, u32, u32) { - TEST_EXTENSION.with(|e| e.borrow().last_seen_inputs.clone()) + TestExtensionTestValue::get().last_seen_inputs } } @@ -150,67 +171,140 @@ impl Default for TestExtension { } impl ChainExtension for TestExtension { - fn call(func_id: u32, env: Environment) -> ExtensionResult + fn call(&mut self, env: Environment) -> ExtensionResult where E: Ext, ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { + let func_id = env.func_id(); + let id = env.ext_id() as u32 | func_id as u32; match func_id { 0 => { let mut env = env.buf_in_buf_out(); - let input = env.read(2)?; + let input = env.read(8)?; env.write(&input, false, None)?; - TEST_EXTENSION.with(|e| e.borrow_mut().last_seen_buffer = input); - Ok(RetVal::Converging(func_id)) + TestExtensionTestValue::mutate(|e| e.last_seen_buffer = input); + Ok(RetVal::Converging(id)) }, 1 => { let env = env.only_in(); - TEST_EXTENSION.with(|e| { - e.borrow_mut().last_seen_inputs = - (env.val0(), env.val1(), env.val2(), env.val3()) + TestExtensionTestValue::mutate(|e| { + e.last_seen_inputs = (env.val0(), env.val1(), env.val2(), env.val3()) }); - Ok(RetVal::Converging(func_id)) + Ok(RetVal::Converging(id)) }, 2 => { let mut env = env.buf_in_buf_out(); - let weight = env.read(2)?[1].into(); + let weight = Weight::from_ref_time(env.read(5)?[4].into()); env.charge_weight(weight)?; - Ok(RetVal::Converging(func_id)) + Ok(RetVal::Converging(id)) }, 3 => Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![42, 99] }), _ => { - panic!("Passed unknown func_id to test chain extension: {}", func_id); + panic!("Passed unknown id to test chain extension: {}", func_id); + }, + } + } + + fn enabled() -> bool { + TestExtensionTestValue::get().enabled + } +} + +impl RegisteredChainExtension for TestExtension { + const ID: u16 = 0; +} + +impl ChainExtension for RevertingExtension { + fn call(&mut self, _env: Environment) -> ExtensionResult + where + E: Ext, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + { + Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![0x4B, 0x1D] }) + } + + fn enabled() -> bool { + TestExtensionTestValue::get().enabled + } +} + +impl RegisteredChainExtension for RevertingExtension { + const ID: u16 = 1; +} + +impl ChainExtension for DisabledExtension { + fn call(&mut self, _env: Environment) -> ExtensionResult + where + E: Ext, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + { + panic!("Disabled chain extensions are never called") + } + + fn enabled() -> bool { + false + } +} + +impl RegisteredChainExtension for DisabledExtension { + const ID: u16 = 2; +} + +impl ChainExtension for TempStorageExtension { + fn call(&mut self, env: Environment) -> ExtensionResult + where + E: Ext, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + { + let func_id = env.func_id(); + match func_id { + 0 => self.storage = 42, + 1 => assert_eq!(self.storage, 42, "Storage is preserved inside the same call."), + 2 => { + assert_eq!(self.storage, 0, "Storage is different for different calls."); + self.storage = 99; + }, + 3 => assert_eq!(self.storage, 99, "Storage is preserved inside the same call."), + _ => { + panic!("Passed unknown id to test chain extension: {}", func_id); }, } + Ok(RetVal::Converging(0)) } fn enabled() -> bool { - TEST_EXTENSION.with(|e| e.borrow().enabled) + TestExtensionTestValue::get().enabled } } +impl RegisteredChainExtension for TempStorageExtension { + const ID: u16 = 3; +} + parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(2 * WEIGHT_PER_SECOND); - pub static ExistentialDeposit: u64 = 0; + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + ); + pub static ExistentialDeposit: u64 = 1; } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = AccountId32; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -219,6 +313,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_randomness_collective_flip::Config for Test {} impl pallet_balances::Config for Test { @@ -226,76 +321,101 @@ impl pallet_balances::Config for Test { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); } -parameter_types! { - pub const MinimumPeriod: u64 = 1; -} + impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<1>; type WeightInfo = (); } impl pallet_utility::Config for Test { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; type WeightInfo = (); } parameter_types! { - pub const ContractDeposit: u64 = 16; - pub const MaxValueSize: u32 = 16_384; - pub const DeletionQueueDepth: u32 = 1024; - pub const DeletionWeightLimit: Weight = 500_000_000_000; - pub const MaxCodeSize: u32 = 2 * 1024; - pub MySchedule: Schedule = >::default(); - pub const TransactionByteFee: u64 = 0; + pub MySchedule: Schedule = { + let mut schedule = >::default(); + // We want stack height to be always enabled for tests so that this + // instrumentation path is always tested implicitly. + schedule.limits.stack_height = Some(512); + schedule.instruction_weights.fallback = 1; + schedule + }; + pub static DepositPerByte: BalanceOf = 1; + pub const DepositPerItem: BalanceOf = 2; } impl Convert> for Test { fn convert(w: Weight) -> BalanceOf { - w + w.ref_time() } } /// A filter whose filter function can be swapped at runtime. pub struct TestFilter; -thread_local! { - static CALL_FILTER: RefCell bool> = RefCell::new(|_| true); +#[derive(Clone)] +pub struct Filters { + filter: fn(&RuntimeCall) -> bool, +} + +impl Default for Filters { + fn default() -> Self { + Filters { filter: (|_| true) } + } +} + +parameter_types! { + static CallFilter: Filters = Default::default(); } impl TestFilter { - pub fn set_filter(filter: fn(&Call) -> bool) { - CALL_FILTER.with(|fltr| *fltr.borrow_mut() = filter); + pub fn set_filter(filter: fn(&RuntimeCall) -> bool) { + CallFilter::mutate(|fltr| fltr.filter = filter); } } -impl Contains for TestFilter { - fn contains(call: &Call) -> bool { - CALL_FILTER.with(|fltr| fltr.borrow()(call)) +impl Contains for TestFilter { + fn contains(call: &RuntimeCall) -> bool { + (CallFilter::get().filter)(call) } } +parameter_types! { + pub const DeletionWeightLimit: Weight = Weight::from_ref_time(500_000_000_000); + pub static UnstableInterface: bool = true; +} + impl Config for Test { type Time = Timestamp; type Randomness = Randomness; type Currency = Balances; - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type CallFilter = TestFilter; - type ContractDeposit = ContractDeposit; type CallStack = [Frame; 31]; type WeightPrice = Self; type WeightInfo = (); - type ChainExtension = TestExtension; - type DeletionQueueDepth = DeletionQueueDepth; + type ChainExtension = + (TestExtension, DisabledExtension, RevertingExtension, TempStorageExtension); + type DeletionQueueDepth = ConstU32<1024>; type DeletionWeightLimit = DeletionWeightLimit; type Schedule = MySchedule; + type DepositPerByte = DepositPerByte; + type DepositPerItem = DepositPerItem; + type AddressGenerator = DefaultAddressGenerator; + type MaxCodeLen = ConstU32<{ 128 * 1024 }>; + type MaxStorageKeyLen = ConstU32<128>; + type UnsafeUnstableInterface = UnstableInterface; + type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; } pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); @@ -303,14 +423,14 @@ pub const BOB: AccountId32 = AccountId32::new([2u8; 32]); pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]); pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]); -const GAS_LIMIT: Weight = 10_000_000_000; +pub const GAS_LIMIT: Weight = Weight::from_ref_time(100_000_000_000).set_proof_size(256 * 1024); pub struct ExtBuilder { existential_deposit: u64, } impl Default for ExtBuilder { fn default() -> Self { - Self { existential_deposit: 1 } + Self { existential_deposit: ExistentialDeposit::get() } } } impl ExtBuilder { @@ -322,12 +442,16 @@ impl ExtBuilder { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); } pub fn build(self) -> sp_io::TestExternalities { + use env_logger::{Builder, Env}; + let env = Env::new().default_filter_or("runtime=debug"); + let _ = Builder::from_env(env).is_test(true).try_init(); self.set_associated_consts(); let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![] } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); + ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); ext.execute_with(|| System::set_block_number(1)); ext } @@ -347,6 +471,34 @@ where Ok((wasm_binary, code_hash)) } +fn initialize_block(number: u64) { + System::reset_events(); + System::initialize(&number, &[0u8; 32].into(), &Default::default()); +} + +struct ExtensionInput<'a> { + extension_id: u16, + func_id: u16, + extra: &'a [u8], +} + +impl<'a> ExtensionInput<'a> { + fn to_vec(&self) -> Vec { + ((self.extension_id as u32) << 16 | (self.func_id as u32)) + .to_le_bytes() + .iter() + .chain(self.extra) + .cloned() + .collect() + } +} + +impl<'a> From> for Vec { + fn from(input: ExtensionInput) -> Vec { + input.to_vec() + } +} + // Perform a call to a plain account. // The actual transfer fails because we can only call contracts. // Then we check that at least the base costs where charged (no runtime gas costs.) @@ -357,7 +509,7 @@ fn calling_plain_account_fails() { let base_cost = <::WeightInfo as WeightInfo>::call(); assert_eq!( - Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, Vec::new()), + Contracts::call(RuntimeOrigin::signed(ALICE), BOB, 0, GAS_LIMIT, None, Vec::new()), Err(DispatchErrorWithPostInfo { error: Error::::ContractNotFound.into(), post_info: PostDispatchInfo { @@ -369,136 +521,102 @@ fn calling_plain_account_fails() { }); } -#[test] -fn account_removal_does_not_remove_storage() { - use self::test_utils::{get_storage, set_storage}; - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let trie_id1 = test_utils::generate_trie_id(&ALICE); - let trie_id2 = test_utils::generate_trie_id(&BOB); - let key1 = &[1; 32]; - let key2 = &[2; 32]; - - // Set up two accounts with free balance above the existential threshold. - { - let alice_contract_info = RawContractInfo { - trie_id: trie_id1.clone(), - code_hash: H256::repeat_byte(1), - _reserved: None, - }; - let _ = Balances::deposit_creating(&ALICE, 110); - ContractInfoOf::::insert(ALICE, &alice_contract_info); - set_storage(&ALICE, &key1, Some(b"1".to_vec())); - set_storage(&ALICE, &key2, Some(b"2".to_vec())); - - let bob_contract_info = RawContractInfo { - trie_id: trie_id2.clone(), - code_hash: H256::repeat_byte(2), - _reserved: None, - }; - let _ = Balances::deposit_creating(&BOB, 110); - ContractInfoOf::::insert(BOB, &bob_contract_info); - set_storage(&BOB, &key1, Some(b"3".to_vec())); - set_storage(&BOB, &key2, Some(b"4".to_vec())); - } - - // Transfer funds from ALICE account of such amount that after this transfer - // the balance of the ALICE account will be below the existential threshold. - // - // This does not remove the contract storage as we are not notified about a - // account removal. This cannot happen in reality because a contract can only - // remove itself by `seal_terminate`. There is no external event that can remove - // the account appart from that. - assert_ok!(Balances::transfer(Origin::signed(ALICE), BOB, 20)); - - // Verify that no entries are removed. - { - assert_eq!(get_storage(&ALICE, key1), Some(b"1".to_vec())); - assert_eq!(get_storage(&ALICE, key2), Some(b"2".to_vec())); - - assert_eq!(get_storage(&BOB, key1), Some(b"3".to_vec())); - assert_eq!(get_storage(&BOB, key2), Some(b"4".to_vec())); - } - }); -} - #[test] fn instantiate_and_call_and_deposit_event() { - let (wasm, code_hash) = compile_module::("return_from_start_fn").unwrap(); + let (wasm, code_hash) = compile_module::("event_and_return_on_deploy").unwrap(); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - let subsistence = Pallet::::subsistence_threshold(); + let min_balance = ::Currency::minimum_balance(); + let value = 100; + + // We determine the storage deposit limit after uploading because it depends on ALICEs free + // balance which is changed by uploading a module. + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + None, + Determinism::Deterministic + )); + + // Drop previous events + initialize_block(2); // Check at the end to get hash on error easily - let creation = Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + assert_ok!(Contracts::instantiate( + RuntimeOrigin::signed(ALICE), + value, GAS_LIMIT, - wasm, + None, + code_hash, vec![], vec![], - ); + )); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + assert!(ContractInfoOf::::contains_key(&addr)); assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, - event: Event::System(frame_system::Event::NewAccount(ALICE.clone())), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: Event::Balances(pallet_balances::Event::Endowed(ALICE, 1_000_000)), + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone() + }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::System(frame_system::Event::NewAccount(addr.clone())), + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, + }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::Balances(pallet_balances::Event::Endowed( - addr.clone(), - subsistence * 100 - )), + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, + }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::Balances(pallet_balances::Event::Transfer( - ALICE, - addr.clone(), - subsistence * 100 - )), + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: addr.clone(), + amount: min_balance, + }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::Contracts(crate::Event::CodeStored(code_hash.into())), + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: value, + }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::Contracts(crate::Event::ContractEmitted( - addr.clone(), - vec![1, 2, 3, 4] - )), + event: RuntimeEvent::Contracts(crate::Event::ContractEmitted { + contract: addr.clone(), + data: vec![1, 2, 3, 4] + }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::Contracts(crate::Event::Instantiated(ALICE, addr.clone())), - topics: vec![], + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE, + contract: addr.clone() + }), + topics: vec![hash(&ALICE), hash(&addr)], }, ] ); - - assert_ok!(creation); - assert!(ContractInfoOf::::contains_key(&addr)); }); } @@ -510,9 +628,10 @@ fn deposit_event_max_value_limit() { // Create let _ = Balances::deposit_creating(&ALICE, 1_000_000); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 30_000, GAS_LIMIT, + None, wasm, vec![], vec![], @@ -521,20 +640,22 @@ fn deposit_event_max_value_limit() { // Call contract with allowed storage value. assert_ok!(Contracts::call( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), addr.clone(), 0, - GAS_LIMIT * 2, // we are copying a huge buffer, + GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2), // we are copying a huge buffer, + None, ::Schedule::get().limits.payload_len.encode(), )); // Call contract with too large a storage value. assert_err_ignore_postinfo!( Contracts::call( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), addr, 0, GAS_LIMIT, + None, (::Schedule::get().limits.payload_len + 1).encode(), ), Error::::ValueTooLarge, @@ -545,15 +666,15 @@ fn deposit_event_max_value_limit() { #[test] fn run_out_of_gas() { let (wasm, code_hash) = compile_module::("run_out_of_gas").unwrap(); - let subsistence = Pallet::::subsistence_threshold(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1_000_000); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - 100 * subsistence, + RuntimeOrigin::signed(ALICE), + 100 * min_balance, GAS_LIMIT, + None, wasm, vec![], vec![], @@ -564,10 +685,11 @@ fn run_out_of_gas() { // loops forever. assert_err_ignore_postinfo!( Contracts::call( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), addr, // newly created account 0, - 1_000_000_000_000, + Weight::from_ref_time(1_000_000_000_000).set_proof_size(u64::MAX), + None, vec![], ), Error::::OutOfGas, @@ -575,8 +697,73 @@ fn run_out_of_gas() { }); } -fn initialize_block(number: u64) { - System::initialize(&number, &[0u8; 32].into(), &Default::default(), Default::default()); +/// Check that contracts with the same account id have different trie ids. +/// Check the `Nonce` storage item for more information. +#[test] +fn instantiate_unique_trie_id() { + let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + None, + Determinism::Deterministic, + ) + .unwrap(); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Instantiate the contract and store its trie id for later comparison. + assert_ok!(Contracts::instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + code_hash, + vec![], + vec![], + )); + let trie_id = get_contract(&addr).trie_id; + + // Try to instantiate it again without termination should yield an error. + assert_err_ignore_postinfo!( + Contracts::instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + code_hash, + vec![], + vec![], + ), + >::DuplicateContract, + ); + + // Terminate the contract. + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![] + )); + + // Re-Instantiate after termination. + assert_ok!(Contracts::instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + code_hash, + vec![], + vec![], + )); + + // Trie ids shouldn't match or we might have a collision + assert_ne!(trie_id, get_contract(&addr).trie_id); + }); } #[test] @@ -587,32 +774,35 @@ fn storage_max_value_limit() { // Create let _ = Balances::deposit_creating(&ALICE, 1_000_000); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 30_000, GAS_LIMIT, + None, wasm, vec![], vec![], )); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - ContractInfoOf::::get(&addr).unwrap(); + get_contract(&addr); // Call contract with allowed storage value. assert_ok!(Contracts::call( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), addr.clone(), 0, - GAS_LIMIT * 2, // we are copying a huge buffer + GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2), // we are copying a huge buffer + None, ::Schedule::get().limits.payload_len.encode(), )); // Call contract with too large a storage value. assert_err_ignore_postinfo!( Contracts::call( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), addr, 0, GAS_LIMIT, + None, (::Schedule::get().limits.payload_len + 1).encode(), ), Error::::ValueTooLarge, @@ -622,78 +812,181 @@ fn storage_max_value_limit() { #[test] fn deploy_and_call_other_contract() { - let (callee_wasm, callee_code_hash) = compile_module::("return_with_data").unwrap(); let (caller_wasm, caller_code_hash) = compile_module::("caller_contract").unwrap(); + let (callee_wasm, callee_code_hash) = compile_module::("return_with_data").unwrap(); + let caller_addr = Contracts::contract_address(&ALICE, &caller_code_hash, &[]); + let callee_addr = Contracts::contract_address(&caller_addr, &callee_code_hash, &[]); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { // Create let _ = Balances::deposit_creating(&ALICE, 1_000_000); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 100_000, GAS_LIMIT, + None, caller_wasm, vec![], vec![], )); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 100_000, GAS_LIMIT, + None, callee_wasm, 0u32.to_le_bytes().encode(), vec![42], )); + // Drop previous events + initialize_block(2); + // Call BOB contract, which attempts to instantiate and call the callee contract and // makes various assertions on the results from those calls. assert_ok!(Contracts::call( - Origin::signed(ALICE), - Contracts::contract_address(&ALICE, &caller_code_hash, &[]), + RuntimeOrigin::signed(ALICE), + caller_addr.clone(), 0, GAS_LIMIT, + None, callee_code_hash.as_ref().to_vec(), )); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: callee_addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: callee_addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: callee_addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: callee_addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: caller_addr.clone(), + to: callee_addr.clone(), + amount: 32768, // hard coded in wasm + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: caller_addr.clone(), + contract: callee_addr.clone(), + }), + topics: vec![hash(&caller_addr), hash(&callee_addr)], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: caller_addr.clone(), + to: callee_addr.clone(), + amount: 32768, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: caller_addr.clone(), + contract: callee_addr.clone(), + }), + topics: vec![hash(&caller_addr), hash(&callee_addr)], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: caller_addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&caller_addr)], + }, + ] + ); }); } #[test] -fn cannot_self_destruct_through_draning() { - let (wasm, code_hash) = compile_module::("drain").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { +fn delegate_call() { + let (caller_wasm, caller_code_hash) = compile_module::("delegate_call").unwrap(); + let (callee_wasm, callee_code_hash) = compile_module::("delegate_call_lib").unwrap(); + let caller_addr = Contracts::contract_address(&ALICE, &caller_code_hash, &[]); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - // Instantiate the BOB contract. + // Instantiate the 'caller' assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - 100_000, + RuntimeOrigin::signed(ALICE), + 300_000, GAS_LIMIT, - wasm, + None, + caller_wasm, vec![], vec![], )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - - // Check that the BOB contract has been instantiated. - assert_matches!(ContractInfoOf::::get(&addr), Some(_)); + // Only upload 'callee' code + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + callee_wasm, + Some(codec::Compact(100_000)), + Determinism::Deterministic, + )); - // Call BOB which makes it send all funds to the zero address - // The contract code asserts that the correct error value is returned. - assert_ok!(Contracts::call(Origin::signed(ALICE), addr, 0, GAS_LIMIT, vec![])); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + caller_addr.clone(), + 1337, + GAS_LIMIT, + None, + callee_code_hash.as_ref().to_vec(), + )); }); } #[test] -fn cannot_self_destruct_while_live() { - let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { +fn cannot_self_destruct_through_draning() { + let (wasm, code_hash) = compile_module::("drain").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); // Instantiate the BOB contract. assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - 100_000, + RuntimeOrigin::signed(ALICE), + 1_000, GAS_LIMIT, + None, wasm, vec![], vec![], @@ -701,86 +994,294 @@ fn cannot_self_destruct_while_live() { let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); // Check that the BOB contract has been instantiated. - assert_matches!(ContractInfoOf::::get(&addr), Some(_)); + get_contract(&addr); - // Call BOB with input data, forcing it make a recursive call to itself to - // self-destruct, resulting in a trap. - assert_err_ignore_postinfo!( - Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![0],), - Error::::ContractTrapped, - ); + // Call BOB which makes it send all funds to the zero address + // The contract code asserts that the transfer was successful + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![] + )); - // Check that BOB is still there. - assert_matches!(ContractInfoOf::::get(&addr), Some(_)); + // Make sure the account wasn't remove by sending all free balance away. + assert_eq!( + ::Currency::total_balance(&addr), + ::Currency::minimum_balance(), + ); }); } #[test] -fn self_destruct_works() { - let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { +fn cannot_self_destruct_through_storage_refund_after_price_change() { + let (wasm, code_hash) = compile_module::("store").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - let _ = Balances::deposit_creating(&DJANGO, 1_000_000); + let min_balance = ::Currency::minimum_balance(); // Instantiate the BOB contract. assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - 100_000, + RuntimeOrigin::signed(ALICE), + 0, GAS_LIMIT, + None, wasm, vec![], vec![], )); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - // Check that the BOB contract has been instantiated. - assert_matches!(ContractInfoOf::::get(&addr), Some(_)); - - // Drop all previous events - initialize_block(2); + // Check that the BOB contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), min_balance); + assert_eq!(get_contract(&addr).extra_deposit(), 0); + assert_eq!(::Currency::total_balance(&addr), min_balance); - // Call BOB without input data which triggers termination. + // Create 100 bytes of storage with a price of per byte and a single storage item of price 2 + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + 100u32.to_le_bytes().to_vec() + )); + assert_eq!(get_contract(&addr).total_deposit(), min_balance + 102); + + // Increase the byte price and trigger a refund. This should not have any influence because + // the removal is pro rata and exactly those 100 bytes should have been removed. + DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 500); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + 0u32.to_le_bytes().to_vec() + )); + + // Make sure the account wasn't removed by the refund + assert_eq!( + ::Currency::total_balance(&addr), + get_contract(&addr).total_deposit(), + ); + assert_eq!(get_contract(&addr).extra_deposit(), 2,); + }); +} + +#[test] +fn cannot_self_destruct_by_refund_after_slash() { + let (wasm, code_hash) = compile_module::("store").unwrap(); + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let min_balance = ::Currency::minimum_balance(); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // create 100 more reserved balance + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + 98u32.encode(), + )); + + // Drop previous events + initialize_block(2); + + // slash parts of the 100 so that the next refund ould remove the account + // because it the value it stored for `storage_deposit` becomes out of sync + let _ = ::Currency::slash(&addr, 90); + assert_eq!(::Currency::total_balance(&addr), min_balance + 10); + + // trigger a refund of 50 which would bring the contract below min when actually refunded + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + 48u32.encode(), + )); + + // Make sure the account kept the minimum balance and was not destroyed + assert_eq!(::Currency::total_balance(&addr), min_balance); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Slashed { + who: addr.clone(), + amount: 90, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&addr)], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated { + from: addr.clone(), + to: ALICE, + amount: 10, + destination_status: BalanceStatus::Free, + }), + topics: vec![], + }, + ] + ); + }); +} + +#[test] +fn cannot_self_destruct_while_live() { + let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Instantiate the BOB contract. + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 100_000, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + // Call BOB with input data, forcing it make a recursive call to itself to + // self-destruct, resulting in a trap. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![0], + ), + Error::::ContractTrapped, + ); + + // Check that BOB is still there. + get_contract(&addr); + }); +} + +#[test] +fn self_destruct_works() { + let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(1_000).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let _ = Balances::deposit_creating(&DJANGO, 1_000_000); + + // Instantiate the BOB contract. + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 100_000, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + // Drop all previous events + initialize_block(2); + + // Call BOB without input data which triggers termination. assert_matches!( - Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![],), + Contracts::call(RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, vec![],), Ok(_) ); + // Check that code is still there but refcount dropped to zero. + assert_refcount!(&code_hash, 0); + + // Check that account is gone + assert!(get_contract_checked(&addr).is_none()); + assert_eq!(Balances::total_balance(&addr), 0); + + // check that the beneficiary (django) got remaining balance + assert_eq!(Balances::free_balance(DJANGO), 1_000_000 + 100_000); + pretty_assertions::assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, - event: Event::System(frame_system::Event::KilledAccount(addr.clone())), + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: addr.clone(), + to: DJANGO, + amount: 100_000, + }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::Balances(pallet_balances::Event::Transfer( - addr.clone(), - DJANGO, - 100_000, - )), - topics: vec![], + event: RuntimeEvent::Contracts(crate::Event::Terminated { + contract: addr.clone(), + beneficiary: DJANGO + }), + topics: vec![hash(&addr), hash(&DJANGO)], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&addr)], }, EventRecord { phase: Phase::Initialization, - event: Event::Contracts(crate::Event::CodeRemoved(code_hash)), + event: RuntimeEvent::System(frame_system::Event::KilledAccount { + account: addr.clone() + }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::Contracts(crate::Event::Terminated(addr.clone(), DJANGO)), + event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated { + from: addr.clone(), + to: ALICE, + amount: 1_000, + destination_status: BalanceStatus::Free, + }), topics: vec![], }, ], ); - - // Check that account is gone - assert!(ContractInfoOf::::get(&addr).is_none()); - - // check that the beneficiary (django) got remaining balance - // some rent was deducted before termination - assert_eq!(Balances::free_balance(DJANGO), 1_000_000 + 100_000); }); } @@ -795,9 +1296,10 @@ fn destroy_contract_and_transfer_funds() { // Create let _ = Balances::deposit_creating(&ALICE, 1_000_000); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 200_000, GAS_LIMIT, + None, callee_wasm, vec![], vec![42] @@ -806,9 +1308,10 @@ fn destroy_contract_and_transfer_funds() { // This deploys the BOB contract, which in turn deploys the CHARLIE contract during // construction. assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 200_000, GAS_LIMIT, + None, caller_wasm, callee_code_hash.as_ref().to_vec(), vec![], @@ -817,19 +1320,20 @@ fn destroy_contract_and_transfer_funds() { let addr_charlie = Contracts::contract_address(&addr_bob, &callee_code_hash, &[0x47, 0x11]); // Check that the CHARLIE contract has been instantiated. - assert_matches!(ContractInfoOf::::get(&addr_charlie), Some(_)); + get_contract(&addr_charlie); // Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct. assert_ok!(Contracts::call( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), addr_bob, 0, GAS_LIMIT, + None, addr_charlie.encode(), )); // Check that CHARLIE has moved on to the great beyond (ie. died). - assert!(ContractInfoOf::::get(&addr_charlie).is_none()); + assert!(get_contract_checked(&addr_charlie).is_none()); }); } @@ -842,9 +1346,10 @@ fn cannot_self_destruct_in_constructor() { // Fail to instantiate the BOB because the contructor calls seal_terminate. assert_err_ignore_postinfo!( Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 100_000, GAS_LIMIT, + None, wasm, vec![], vec![], @@ -863,9 +1368,10 @@ fn crypto_hashes() { // Instantiate the CRYPTO_HASHES contract. assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 100_000, GAS_LIMIT, + None, wasm, vec![], vec![], @@ -892,11 +1398,19 @@ fn crypto_hashes() { // We offset data in the contract tables by 1. let mut params = vec![(n + 1) as u8]; params.extend_from_slice(input); - let result = - >::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, params, false) - .result - .unwrap(); - assert!(result.is_success()); + let result = >::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + params, + false, + Determinism::Deterministic, + ) + .result + .unwrap(); + assert!(!result.did_revert()); let expected = hash_fn(input.as_ref()); assert_eq!(&result.data[..*expected_size], &*expected); } @@ -907,32 +1421,53 @@ fn crypto_hashes() { fn transfer_return_code() { let (wasm, code_hash) = compile_module::("transfer_return_code").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, wasm, vec![], vec![], ),); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - // Contract has only the minimal balance so any transfer will return BelowSubsistence. - Balances::make_free_balance_be(&addr, subsistence); - let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![], false) - .result - .unwrap(); - assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold); + // Contract has only the minimal balance so any transfer will fail. + Balances::make_free_balance_be(&addr, min_balance); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![], + false, + Determinism::Deterministic, + ) + .result + .unwrap(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); - // Contract has enough total balance in order to not go below the subsistence + // Contract has enough total balance in order to not go below the min balance // threshold when transfering 100 balance but this balance is reserved so - // the transfer still fails but with another return code. - Balances::make_free_balance_be(&addr, subsistence + 100); - Balances::reserve(&addr, subsistence + 100).unwrap(); - let result = Contracts::bare_call(ALICE, addr, 0, GAS_LIMIT, vec![], false).result.unwrap(); + // the transfer still fails. + Balances::make_free_balance_be(&addr, min_balance + 100); + Balances::reserve(&addr, min_balance + 100).unwrap(); + let result = Contracts::bare_call( + ALICE, + addr, + 0, + GAS_LIMIT, + None, + vec![], + false, + Determinism::Deterministic, + ) + .result + .unwrap(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); }); } @@ -942,20 +1477,21 @@ fn call_return_code() { let (caller_code, caller_hash) = compile_module::("call_return_code").unwrap(); let (callee_code, callee_hash) = compile_module::("ok_trap_revert").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); - let _ = Balances::deposit_creating(&CHARLIE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + let _ = Balances::deposit_creating(&CHARLIE, 1000 * min_balance); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, caller_code, vec![0], vec![], ),); let addr_bob = Contracts::contract_address(&ALICE, &caller_hash, &[]); - Balances::make_free_balance_be(&addr_bob, subsistence); + Balances::make_free_balance_be(&addr_bob, min_balance); // Contract calls into Django which is no valid contract let result = Contracts::bare_call( @@ -963,75 +1499,84 @@ fn call_return_code() { addr_bob.clone(), 0, GAS_LIMIT, + None, AsRef::<[u8]>::as_ref(&DJANGO).to_vec(), false, + Determinism::Deterministic, ) .result .unwrap(); assert_return_code!(result, RuntimeReturnCode::NotCallable); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(CHARLIE), - subsistence * 100, + RuntimeOrigin::signed(CHARLIE), + min_balance * 100, GAS_LIMIT, + None, callee_code, vec![0], vec![], ),); let addr_django = Contracts::contract_address(&CHARLIE, &callee_hash, &[]); - Balances::make_free_balance_be(&addr_django, subsistence); + Balances::make_free_balance_be(&addr_django, min_balance); - // Contract has only the minimal balance so any transfer will return BelowSubsistence. + // Contract has only the minimal balance so any transfer will fail. let result = Contracts::bare_call( ALICE, addr_bob.clone(), 0, GAS_LIMIT, + None, AsRef::<[u8]>::as_ref(&addr_django) .iter() .chain(&0u32.to_le_bytes()) .cloned() .collect(), false, + Determinism::Deterministic, ) .result .unwrap(); - assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); - // Contract has enough total balance in order to not go below the subsistence + // Contract has enough total balance in order to not go below the min balance // threshold when transfering 100 balance but this balance is reserved so - // the transfer still fails but with another return code. - Balances::make_free_balance_be(&addr_bob, subsistence + 100); - Balances::reserve(&addr_bob, subsistence + 100).unwrap(); + // the transfer still fails. + Balances::make_free_balance_be(&addr_bob, min_balance + 100); + Balances::reserve(&addr_bob, min_balance + 100).unwrap(); let result = Contracts::bare_call( ALICE, addr_bob.clone(), 0, GAS_LIMIT, + None, AsRef::<[u8]>::as_ref(&addr_django) .iter() .chain(&0u32.to_le_bytes()) .cloned() .collect(), false, + Determinism::Deterministic, ) .result .unwrap(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); // Contract has enough balance but callee reverts because "1" is passed. - Balances::make_free_balance_be(&addr_bob, subsistence + 1000); + Balances::make_free_balance_be(&addr_bob, min_balance + 1000); let result = Contracts::bare_call( ALICE, addr_bob.clone(), 0, GAS_LIMIT, + None, AsRef::<[u8]>::as_ref(&addr_django) .iter() .chain(&1u32.to_le_bytes()) .cloned() .collect(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1043,12 +1588,14 @@ fn call_return_code() { addr_bob, 0, GAS_LIMIT, + None, AsRef::<[u8]>::as_ref(&addr_django) .iter() .chain(&2u32.to_le_bytes()) .cloned() .collect(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1061,54 +1608,81 @@ fn instantiate_return_code() { let (caller_code, caller_hash) = compile_module::("instantiate_return_code").unwrap(); let (callee_code, callee_hash) = compile_module::("ok_trap_revert").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); - let _ = Balances::deposit_creating(&CHARLIE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + let _ = Balances::deposit_creating(&CHARLIE, 1000 * min_balance); let callee_hash = callee_hash.as_ref().to_vec(); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, callee_code, vec![], vec![], ),); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, caller_code, vec![], vec![], ),); let addr = Contracts::contract_address(&ALICE, &caller_hash, &[]); - // Contract has only the minimal balance so any transfer will return BelowSubsistence. - Balances::make_free_balance_be(&addr, subsistence); - let result = - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, callee_hash.clone(), false) - .result - .unwrap(); - assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold); + // Contract has only the minimal balance so any transfer will fail. + Balances::make_free_balance_be(&addr, min_balance); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + callee_hash.clone(), + false, + Determinism::Deterministic, + ) + .result + .unwrap(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); - // Contract has enough total balance in order to not go below the subsistence + // Contract has enough total balance in order to not go below the min_balance // threshold when transfering the balance but this balance is reserved so - // the transfer still fails but with another return code. - Balances::make_free_balance_be(&addr, subsistence + 10_000); - Balances::reserve(&addr, subsistence + 10_000).unwrap(); - let result = - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, callee_hash.clone(), false) - .result - .unwrap(); + // the transfer still fails. + Balances::make_free_balance_be(&addr, min_balance + 10_000); + Balances::reserve(&addr, min_balance + 10_000).unwrap(); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + callee_hash.clone(), + false, + Determinism::Deterministic, + ) + .result + .unwrap(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); // Contract has enough balance but the passed code hash is invalid - Balances::make_free_balance_be(&addr, subsistence + 10_000); - let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![0; 33], false) - .result - .unwrap(); + Balances::make_free_balance_be(&addr, min_balance + 10_000); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![0; 33], + false, + Determinism::Deterministic, + ) + .result + .unwrap(); assert_return_code!(result, RuntimeReturnCode::CodeNotFound); // Contract has enough balance but callee reverts because "1" is passed. @@ -1117,8 +1691,10 @@ fn instantiate_return_code() { addr.clone(), 0, GAS_LIMIT, + None, callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1130,8 +1706,10 @@ fn instantiate_return_code() { addr, 0, GAS_LIMIT, + None, callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1143,19 +1721,20 @@ fn instantiate_return_code() { fn disabled_chain_extension_wont_deploy() { let (code, _hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); TestExtension::disable(); assert_err_ignore_postinfo!( Contracts::instantiate_with_code( - Origin::signed(ALICE), - 3 * subsistence, + RuntimeOrigin::signed(ALICE), + 3 * min_balance, GAS_LIMIT, + None, code, vec![], vec![], ), - "module uses chain extensions but chain extensions are disabled", + >::CodeRejected, ); }); } @@ -1164,12 +1743,13 @@ fn disabled_chain_extension_wont_deploy() { fn disabled_chain_extension_errors_on_call() { let (code, hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, code, vec![], vec![], @@ -1177,7 +1757,7 @@ fn disabled_chain_extension_errors_on_call() { let addr = Contracts::contract_address(&ALICE, &hash, &[]); TestExtension::disable(); assert_err_ignore_postinfo!( - Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![],), + Contracts::call(RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, vec![],), Error::::NoChainExtension, ); }); @@ -1187,74 +1767,213 @@ fn disabled_chain_extension_errors_on_call() { fn chain_extension_works() { let (code, hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, code, vec![], vec![], ),); let addr = Contracts::contract_address(&ALICE, &hash, &[]); - // The contract takes a up to 2 byte buffer where the first byte passed is used as - // as func_id to the chain extension which behaves differently based on the - // func_id. - // 0 = read input buffer and pass it through as output - let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![0, 99], false); - let gas_consumed = result.gas_consumed; - assert_eq!(TestExtension::last_seen_buffer(), vec![0, 99]); - assert_eq!(result.result.unwrap().data, Bytes(vec![0, 99])); + let input: Vec = ExtensionInput { extension_id: 0, func_id: 0, extra: &[99] }.into(); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + input.clone(), + false, + Determinism::Deterministic, + ); + assert_eq!(TestExtension::last_seen_buffer(), input); + assert_eq!(result.result.unwrap().data, input); // 1 = treat inputs as integer primitives and store the supplied integers - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![1], false) - .result - .unwrap(); + Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + ExtensionInput { extension_id: 0, func_id: 1, extra: &[] }.into(), + false, + Determinism::Deterministic, + ) + .result + .unwrap(); // those values passed in the fixture - assert_eq!(TestExtension::last_seen_inputs(), (4, 1, 16, 12)); + assert_eq!(TestExtension::last_seen_inputs(), (4, 4, 16, 12)); - // 2 = charge some extra weight (amount supplied in second byte) - let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![2, 42], false); + // 2 = charge some extra weight (amount supplied in the fifth byte) + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + ExtensionInput { extension_id: 0, func_id: 2, extra: &[0] }.into(), + false, + Determinism::Deterministic, + ); + assert_ok!(result.result); + let gas_consumed = result.gas_consumed; + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + ExtensionInput { extension_id: 0, func_id: 2, extra: &[42] }.into(), + false, + Determinism::Deterministic, + ); + assert_ok!(result.result); + assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 42); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + ExtensionInput { extension_id: 0, func_id: 2, extra: &[95] }.into(), + false, + Determinism::Deterministic, + ); assert_ok!(result.result); - assert_eq!(result.gas_consumed, gas_consumed + 42); + assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 95); // 3 = diverging chain extension call that sets flags to 0x1 and returns a fixed buffer - let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![3], false) - .result - .unwrap(); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + ExtensionInput { extension_id: 0, func_id: 3, extra: &[] }.into(), + false, + Determinism::Deterministic, + ) + .result + .unwrap(); assert_eq!(result.flags, ReturnFlags::REVERT); - assert_eq!(result.data, Bytes(vec![42, 99])); - }); -} - -#[test] -fn lazy_removal_works() { - let (code, hash) = compile_module::("self_destruct").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); + assert_eq!(result.data, vec![42, 99]); - assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, - GAS_LIMIT, - code, - vec![], + // diverging to second chain extension that sets flags to 0x1 and returns a fixed buffer + // We set the MSB part to 1 (instead of 0) which routes the request into the second + // extension + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + ExtensionInput { extension_id: 1, func_id: 0, extra: &[] }.into(), + false, + Determinism::Deterministic, + ) + .result + .unwrap(); + assert_eq!(result.flags, ReturnFlags::REVERT); + assert_eq!(result.data, vec![0x4B, 0x1D]); + + // Diverging to third chain extension that is disabled + // We set the MSB part to 2 (instead of 0) which routes the request into the third extension + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + ExtensionInput { extension_id: 2, func_id: 0, extra: &[] }.into(), + ), + Error::::NoChainExtension, + ); + }); +} + +#[test] +fn chain_extension_temp_storage_works() { + let (code, hash) = compile_module::("chain_extension_temp_storage").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + min_balance * 100, + GAS_LIMIT, + None, + code, + vec![], + vec![], + ),); + let addr = Contracts::contract_address(&ALICE, &hash, &[]); + + // Call func 0 and func 1 back to back. + let stop_recursion = 0u8; + let mut input: Vec = ExtensionInput { extension_id: 3, func_id: 0, extra: &[] }.into(); + input.extend_from_slice( + ExtensionInput { extension_id: 3, func_id: 1, extra: &[stop_recursion] } + .to_vec() + .as_ref(), + ); + + assert_ok!( + Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + input.clone(), + false, + Determinism::Deterministic + ) + .result + ); + }) +} + +#[test] +fn lazy_removal_works() { + let (code, hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + min_balance * 100, + GAS_LIMIT, + None, + code, + vec![], vec![], ),); let addr = Contracts::contract_address(&ALICE, &hash, &[]); - let info = >::get(&addr).unwrap(); + let info = get_contract(&addr); let trie = &info.child_trie_info(); // Put value into the contracts child trie child::put(trie, &[99], &42); // Terminate the contract - assert_ok!(Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![])); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![] + )); // Contract info should be gone assert!(!>::contains_key(&addr)); @@ -1263,20 +1982,92 @@ fn lazy_removal_works() { assert_matches!(child::get(trie, &[99]), Some(42)); // Run the lazy removal - Contracts::on_initialize(Weight::max_value()); + Contracts::on_idle(System::block_number(), Weight::MAX); // Value should be gone now assert_matches!(child::get::(trie, &[99]), None); }); } +#[test] +fn lazy_removal_on_full_queue_works_on_initialize() { + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Fill the deletion queue with dummy values, so that on_initialize attempts + // to clear the queue + Storage::::fill_queue_with_dummies(); + + let queue_len_initial = >::decode_len().unwrap_or(0); + + // Run the lazy removal + Contracts::on_initialize(System::block_number()); + + let queue_len_after_on_initialize = >::decode_len().unwrap_or(0); + + // Queue length should be decreased after call of on_initialize() + assert!(queue_len_initial - queue_len_after_on_initialize > 0); + }); +} + +#[test] +fn lazy_batch_removal_works() { + let (code, hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + let mut tries: Vec = vec![]; + + for i in 0..3u8 { + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + min_balance * 100, + GAS_LIMIT, + None, + code.clone(), + vec![], + vec![i], + ),); + + let addr = Contracts::contract_address(&ALICE, &hash, &[i]); + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract. Contract info should be gone, but value should be still there + // as the lazy removal did not run, yet. + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![] + )); + + assert!(!>::contains_key(&addr)); + assert_matches!(child::get(trie, &[99]), Some(42)); + + tries.push(trie.clone()) + } + + // Run single lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // The single lazy removal should have removed all queued tries + for trie in tries.iter() { + assert_matches!(child::get::(trie, &[99]), None); + } + }); +} + #[test] fn lazy_removal_partial_remove_works() { let (code, hash) = compile_module::("self_destruct").unwrap(); // We create a contract with some extra keys above the weight limit let extra_keys = 7u32; - let weight_limit = 5_000_000_000; + let weight_limit = Weight::from_ref_time(5_000_000_000); let (_, max_keys) = Storage::::deletion_budget(1, weight_limit); let vals: Vec<_> = (0..max_keys + extra_keys) .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) @@ -1285,29 +2076,44 @@ fn lazy_removal_partial_remove_works() { let mut ext = ExtBuilder::default().existential_deposit(50).build(); let trie = ext.execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, code, vec![], vec![], ),); let addr = Contracts::contract_address(&ALICE, &hash, &[]); - let mut info = >::get(&addr).unwrap(); + let info = get_contract(&addr); // Put value into the contracts child trie for val in &vals { - Storage::::write(&mut info, &val.0, Some(val.2.clone())).unwrap(); + Storage::::write( + &info.trie_id, + &val.0 as &FixSizedKey, + Some(val.2.clone()), + None, + false, + ) + .unwrap(); } >::insert(&addr, info.clone()); // Terminate the contract - assert_ok!(Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![])); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![] + )); // Contract info should be gone assert!(!>::contains_key(&addr)); @@ -1352,73 +2158,93 @@ fn lazy_removal_partial_remove_works() { } #[test] -fn lazy_removal_does_no_run_on_full_block() { +fn lazy_removal_does_no_run_on_full_queue_and_full_block() { + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Fill up the block which should prevent the lazy storage removal from running. + System::register_extra_weight_unchecked( + ::BlockWeights::get().max_block, + DispatchClass::Mandatory, + ); + + // Fill the deletion queue with dummy values, so that on_initialize attempts + // to clear the queue + Storage::::fill_queue_with_dummies(); + + // Check that on_initialize() tries to perform lazy removal but removes nothing + // as no more weight is left for that. + let weight_used = Contracts::on_initialize(System::block_number()); + let base = <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); + assert_eq!(weight_used, base); + + // Check that the deletion queue is still full after execution of the + // on_initialize() hook. + let max_len: u32 = ::DeletionQueueDepth::get(); + let queue_len: u32 = >::decode_len().unwrap_or(0).try_into().unwrap(); + assert_eq!(max_len, queue_len); + }); +} + +#[test] +fn lazy_removal_does_no_run_on_low_remaining_weight() { let (code, hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, code, vec![], vec![], ),); let addr = Contracts::contract_address(&ALICE, &hash, &[]); - let mut info = >::get(&addr).unwrap(); - let max_keys = 30; - - // Create some storage items for the contract. - let vals: Vec<_> = (0..max_keys) - .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) - .collect(); + let info = get_contract(&addr); + let trie = &info.child_trie_info(); // Put value into the contracts child trie - for val in &vals { - Storage::::write(&mut info, &val.0, Some(val.2.clone())).unwrap(); - } - >::insert(&addr, info.clone()); + child::put(trie, &[99], &42); // Terminate the contract - assert_ok!(Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![])); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![] + )); // Contract info should be gone assert!(!>::contains_key(&addr)); - let trie = info.child_trie_info(); - // But value should be still there as the lazy removal did not run, yet. - for val in &vals { - assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); - } + assert_matches!(child::get(trie, &[99]), Some(42)); - // Fill up the block which should prevent the lazy storage removal from running. - System::register_extra_weight_unchecked( - ::BlockWeights::get().max_block, - DispatchClass::Mandatory, - ); + // Assign a remaining weight which is too low for a successfull deletion of the contract + let low_remaining_weight = + <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); - // Run the lazy removal without any limit so that all keys would be removed if there - // had been some weight left in the block. - let weight_used = Contracts::on_initialize(Weight::max_value()); - let base = <::WeightInfo as WeightInfo>::on_initialize(); - assert_eq!(weight_used, base); + // Run the lazy removal + Contracts::on_idle(System::block_number(), low_remaining_weight); - // All the keys are still in place - for val in &vals { - assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); - } + // Value should still be there, since remaining weight was too low for removal + assert_matches!(child::get::(trie, &[99]), Some(42)); - // Run the lazy removal directly which disregards the block limits - Storage::::process_deletion_queue_batch(Weight::max_value()); + // Run the lazy removal while deletion_queue is not full + Contracts::on_initialize(System::block_number()); - // Now the keys should be gone - for val in &vals { - assert_eq!(child::get::(&trie, &blake2_256(&val.0)), None); - } + // Value should still be there, since deletion_queue was not full + assert_matches!(child::get::(trie, &[99]), Some(42)); + + // Run on_idle with max remaining weight, this should remove the value + Contracts::on_idle(System::block_number(), Weight::MAX); + + // Value should be gone + assert_matches!(child::get::(trie, &[99]), None); }); } @@ -1426,24 +2252,25 @@ fn lazy_removal_does_no_run_on_full_block() { fn lazy_removal_does_not_use_all_weight() { let (code, hash) = compile_module::("self_destruct").unwrap(); - let weight_limit = 5_000_000_000; + let weight_limit = Weight::from_ref_time(5_000_000_000); let mut ext = ExtBuilder::default().existential_deposit(50).build(); let (trie, vals, weight_per_key) = ext.execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, code, vec![], vec![], ),); let addr = Contracts::contract_address(&ALICE, &hash, &[]); - let mut info = >::get(&addr).unwrap(); + let info = get_contract(&addr); let (weight_per_key, max_keys) = Storage::::deletion_budget(1, weight_limit); // We create a contract with one less storage item than we can remove within the limit @@ -1453,12 +2280,26 @@ fn lazy_removal_does_not_use_all_weight() { // Put value into the contracts child trie for val in &vals { - Storage::::write(&mut info, &val.0, Some(val.2.clone())).unwrap(); + Storage::::write( + &info.trie_id, + &val.0 as &FixSizedKey, + Some(val.2.clone()), + None, + false, + ) + .unwrap(); } >::insert(&addr, info.clone()); // Terminate the contract - assert_ok!(Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![])); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![] + )); // Contract info should be gone assert!(!>::contains_key(&addr)); @@ -1482,7 +2323,7 @@ fn lazy_removal_does_not_use_all_weight() { let weight_used = Storage::::process_deletion_queue_batch(weight_limit); // We have one less key in our trie than our weight limit suffices for - assert_eq!(weight_used, weight_limit - weight_per_key); + assert_eq!(weight_used, weight_limit - Weight::from_ref_time(weight_per_key)); // All the keys are removed for val in vals { @@ -1495,13 +2336,14 @@ fn lazy_removal_does_not_use_all_weight() { fn deletion_queue_full() { let (code, hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, code, vec![], vec![], @@ -1514,12 +2356,12 @@ fn deletion_queue_full() { // Terminate the contract should fail assert_err_ignore_postinfo!( - Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![],), + Contracts::call(RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, vec![],), Error::::DeletionQueueFull, ); // Contract should exist because removal failed - >::get(&addr).unwrap(); + get_contract(&addr); }); } @@ -1528,21 +2370,23 @@ fn refcounter() { let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - let subsistence = Pallet::::subsistence_threshold(); + let min_balance = ::Currency::minimum_balance(); // Create two contracts with the same code and check that they do in fact share it. assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, wasm.clone(), vec![], vec![0], )); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, wasm.clone(), vec![], vec![1], @@ -1551,9 +2395,10 @@ fn refcounter() { // Sharing should also work with the usual instantiate call assert_ok!(Contracts::instantiate( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, code_hash, vec![], vec![2], @@ -1566,23 +2411,44 @@ fn refcounter() { let addr2 = Contracts::contract_address(&ALICE, &code_hash, &[2]); // Terminating one contract should decrement the refcount - assert_ok!(Contracts::call(Origin::signed(ALICE), addr0, 0, GAS_LIMIT, vec![])); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr0, + 0, + GAS_LIMIT, + None, + vec![] + )); assert_refcount!(code_hash, 2); // remove another one - assert_ok!(Contracts::call(Origin::signed(ALICE), addr1, 0, GAS_LIMIT, vec![])); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr1, + 0, + GAS_LIMIT, + None, + vec![] + )); assert_refcount!(code_hash, 1); // Pristine code should still be there crate::PristineCode::::get(code_hash).unwrap(); // remove the last contract - assert_ok!(Contracts::call(Origin::signed(ALICE), addr2, 0, GAS_LIMIT, vec![])); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr2, + 0, + GAS_LIMIT, + None, + vec![] + )); assert_refcount!(code_hash, 0); - // all code should be gone - assert_matches!(crate::PristineCode::::get(code_hash), None); - assert_matches!(crate::CodeStorage::::get(code_hash), None); + // refcount is `0` but code should still exists because it needs to be removed manually + assert!(crate::PristineCode::::contains_key(&code_hash)); + assert!(crate::CodeStorage::::contains_key(&code_hash)); }); } @@ -1591,14 +2457,15 @@ fn reinstrument_does_charge() { let (wasm, code_hash) = compile_module::("return_with_data").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - let subsistence = Pallet::::subsistence_threshold(); + let min_balance = ::Currency::minimum_balance(); let zero = 0u32.to_le_bytes().encode(); let code_len = wasm.len() as u32; assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, wasm, zero.clone(), vec![], @@ -1608,11 +2475,29 @@ fn reinstrument_does_charge() { // Call the contract two times without reinstrument - let result0 = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, zero.clone(), false); - assert!(result0.result.unwrap().is_success()); + let result0 = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + zero.clone(), + false, + Determinism::Deterministic, + ); + assert!(!result0.result.unwrap().did_revert()); - let result1 = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, zero.clone(), false); - assert!(result1.result.unwrap().is_success()); + let result1 = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + zero.clone(), + false, + Determinism::Deterministic, + ); + assert!(!result1.result.unwrap().did_revert()); // They should match because both where called with the same schedule. assert_eq!(result0.gas_consumed, result1.gas_consumed); @@ -1624,12 +2509,22 @@ fn reinstrument_does_charge() { }); // This call should trigger reinstrumentation - let result2 = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, zero.clone(), false); - assert!(result2.result.unwrap().is_success()); - assert!(result2.gas_consumed > result1.gas_consumed); + let result2 = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + zero.clone(), + false, + Determinism::Deterministic, + ); + assert!(!result2.result.unwrap().did_revert()); + assert!(result2.gas_consumed.ref_time() > result1.gas_consumed.ref_time()); assert_eq!( - result2.gas_consumed, - result1.gas_consumed + ::WeightInfo::instrument(code_len / 1024), + result2.gas_consumed.ref_time(), + result1.gas_consumed.ref_time() + + ::WeightInfo::reinstrument(code_len).ref_time(), ); }); } @@ -1641,15 +2536,25 @@ fn debug_message_works() { ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 30_000, GAS_LIMIT, + None, wasm, vec![], vec![], ),); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - let result = Contracts::bare_call(ALICE, addr, 0, GAS_LIMIT, vec![], true); + let result = Contracts::bare_call( + ALICE, + addr, + 0, + GAS_LIMIT, + None, + vec![], + true, + Determinism::Deterministic, + ); assert_matches!(result.result, Ok(_)); assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!"); @@ -1663,19 +2568,29 @@ fn debug_message_logging_disabled() { ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 30_000, GAS_LIMIT, + None, wasm, vec![], vec![], ),); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); // disable logging by passing `false` - let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![], false); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![], + false, + Determinism::Deterministic, + ); assert_matches!(result.result, Ok(_)); // the dispatchables always run without debugging - assert_ok!(Contracts::call(Origin::signed(ALICE), addr, 0, GAS_LIMIT, vec![])); + assert_ok!(Contracts::call(RuntimeOrigin::signed(ALICE), addr, 0, GAS_LIMIT, None, vec![])); assert!(result.debug_message.is_empty()); }); } @@ -1687,15 +2602,25 @@ fn debug_message_invalid_utf8() { ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 30_000, GAS_LIMIT, + None, wasm, vec![], vec![], ),); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - let result = Contracts::bare_call(ALICE, addr, 0, GAS_LIMIT, vec![], true); + let result = Contracts::bare_call( + ALICE, + addr, + 0, + GAS_LIMIT, + None, + vec![], + true, + Determinism::Deterministic, + ); assert_err!(result.result, >::DebugMessageInvalidUTF8); }); } @@ -1705,14 +2630,15 @@ fn gas_estimation_nested_call_fixed_limit() { let (caller_code, caller_hash) = compile_module::("call_with_limit").unwrap(); let (callee_code, callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); - let _ = Balances::deposit_creating(&CHARLIE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + let _ = Balances::deposit_creating(&CHARLIE, 1000 * min_balance); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, caller_code, vec![], vec![0], @@ -1720,9 +2646,10 @@ fn gas_estimation_nested_call_fixed_limit() { let addr_caller = Contracts::contract_address(&ALICE, &caller_hash, &[0]); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, callee_code, vec![], vec![1], @@ -1732,37 +2659,57 @@ fn gas_estimation_nested_call_fixed_limit() { let input: Vec = AsRef::<[u8]>::as_ref(&addr_callee) .iter() .cloned() - .chain((GAS_LIMIT / 5).to_le_bytes()) + .chain((GAS_LIMIT / 5).ref_time().to_le_bytes()) .collect(); // Call in order to determine the gas that is required for this call - let result = - Contracts::bare_call(ALICE, addr_caller.clone(), 0, GAS_LIMIT, input.clone(), false); + let result = Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + GAS_LIMIT, + None, + input.clone(), + false, + Determinism::Deterministic, + ); assert_ok!(&result.result); - assert!(result.gas_required > result.gas_consumed); + // We have a subcall with a fixed gas limit. This constitutes precharging. + assert!(result.gas_required.ref_time() > result.gas_consumed.ref_time()); // Make the same call using the estimated gas. Should succeed. assert_ok!( - Contracts::bare_call(ALICE, addr_caller, 0, result.gas_required, input, false,).result + Contracts::bare_call( + ALICE, + addr_caller, + 0, + result.gas_required, + Some(result.storage_deposit.charge_or_zero()), + input, + false, + Determinism::Deterministic, + ) + .result ); }); } #[test] -#[cfg(feature = "unstable-interface")] fn gas_estimation_call_runtime() { + use codec::Decode; let (caller_code, caller_hash) = compile_module::("call_runtime").unwrap(); let (callee_code, callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let subsistence = Pallet::::subsistence_threshold(); - let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence); - let _ = Balances::deposit_creating(&CHARLIE, 1000 * subsistence); + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + let _ = Balances::deposit_creating(&CHARLIE, 1000 * min_balance); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, caller_code, vec![], vec![0], @@ -1770,9 +2717,10 @@ fn gas_estimation_call_runtime() { let addr_caller = Contracts::contract_address(&ALICE, &caller_hash, &[0]); assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), - subsistence * 100, + RuntimeOrigin::signed(ALICE), + min_balance * 100, GAS_LIMIT, + None, callee_code, vec![], vec![1], @@ -1781,28 +2729,46 @@ fn gas_estimation_call_runtime() { // Call something trivial with a huge gas limit so that we can observe the effects // of pre-charging. This should create a difference between consumed and required. - let call = Call::Contracts(crate::Call::call { + let call = RuntimeCall::Contracts(crate::Call::call { dest: addr_callee, value: 0, - gas_limit: GAS_LIMIT / 3, + gas_limit: GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() / 3), + storage_deposit_limit: None, data: vec![], }); - let result = - Contracts::bare_call(ALICE, addr_caller.clone(), 0, GAS_LIMIT, call.encode(), false); - assert_ok!(&result.result); - - assert!(result.gas_required > result.gas_consumed); + let result = Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + GAS_LIMIT, + None, + call.encode(), + false, + Determinism::Deterministic, + ); + // contract encodes the result of the dispatch runtime + let outcome = u32::decode(&mut result.result.unwrap().data.as_ref()).unwrap(); + assert_eq!(outcome, 0); + assert!(result.gas_required.ref_time() > result.gas_consumed.ref_time()); // Make the same call using the required gas. Should succeed. assert_ok!( - Contracts::bare_call(ALICE, addr_caller, 0, result.gas_required, call.encode(), false,) - .result + Contracts::bare_call( + ALICE, + addr_caller, + 0, + result.gas_required, + None, + call.encode(), + false, + Determinism::Deterministic, + ) + .result ); }); } #[test] -#[cfg(feature = "unstable-interface")] fn ecdsa_recover() { let (wasm, code_hash) = compile_module::("ecdsa_recover").unwrap(); @@ -1811,9 +2777,10 @@ fn ecdsa_recover() { // Instantiate the ecdsa_recover contract. assert_ok!(Contracts::instantiate_with_code( - Origin::signed(ALICE), + RuntimeOrigin::signed(ALICE), 100_000, GAS_LIMIT, + None, wasm, vec![], vec![], @@ -1843,10 +2810,1746 @@ fn ecdsa_recover() { params.extend_from_slice(&signature); params.extend_from_slice(&message_hash); assert!(params.len() == 65 + 32); - let result = >::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, params, false) - .result - .unwrap(); - assert!(result.is_success()); - assert_eq!(result.data.as_ref(), &EXPECTED_COMPRESSED_PUBLIC_KEY); + let result = >::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + params, + false, + Determinism::Deterministic, + ) + .result + .unwrap(); + assert!(!result.did_revert()); + assert_eq!(result.data, EXPECTED_COMPRESSED_PUBLIC_KEY); }) } + +#[test] +fn upload_code_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert!(!>::contains_key(code_hash)); + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + Some(codec::Compact(1_000)), + Determinism::Deterministic, + )); + assert!(>::contains_key(code_hash)); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: ALICE, + amount: 241, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }), + topics: vec![code_hash], + }, + ] + ); + }); +} + +#[test] +fn upload_code_limit_too_low() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + Some(codec::Compact(100)), + Determinism::Deterministic + ), + >::StorageDepositLimitExhausted, + ); + + assert_eq!(System::events(), vec![]); + }); +} + +#[test] +fn upload_code_not_enough_balance() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 150); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + Some(codec::Compact(1_000)), + Determinism::Deterministic + ), + >::StorageDepositNotEnoughFunds, + ); + + assert_eq!(System::events(), vec![]); + }); +} + +#[test] +fn remove_code_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + Some(codec::Compact(1_000)), + Determinism::Deterministic, + )); + + assert!(>::contains_key(code_hash)); + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); + assert!(!>::contains_key(code_hash)); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: ALICE, + amount: 241, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }), + topics: vec![code_hash], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Unreserved { + who: ALICE, + amount: 241, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeRemoved { code_hash }), + topics: vec![code_hash], + }, + ] + ); + }); +} + +#[test] +fn remove_code_wrong_origin() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + Some(codec::Compact(1_000)), + Determinism::Deterministic, + )); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), + sp_runtime::traits::BadOrigin, + ); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: ALICE, + amount: 241, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }), + topics: vec![code_hash], + }, + ] + ); + }); +} + +#[test] +fn remove_code_in_use() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + >::CodeInUse, + ); + + assert_eq!(System::events(), vec![]); + }); +} + +#[test] +fn remove_code_not_found() { + let (_wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + >::CodeNotFound, + ); + + assert_eq!(System::events(), vec![]); + }); +} + +#[test] +fn instantiate_with_zero_balance_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let min_balance = ::Currency::minimum_balance(); + + // Drop previous events + initialize_block(2); + + // Instantiate the BOB contract. + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + // Make sure the account exists even though no free balance was send + assert_eq!(::Currency::free_balance(&addr), 0,); + assert_eq!( + ::Currency::total_balance(&addr), + ::Currency::minimum_balance(), + ); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: ALICE, + amount: 241, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }), + topics: vec![code_hash], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE, + contract: addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&addr)], + }, + ] + ); + }); +} + +#[test] +fn instantiate_with_below_existential_deposit_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let min_balance = ::Currency::minimum_balance(); + + // Drop previous events + initialize_block(2); + + // Instantiate the BOB contract. + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 50, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + // Make sure the account exists even though no free balance was send + assert_eq!(::Currency::free_balance(&addr), 50,); + assert_eq!( + ::Currency::total_balance(&addr), + ::Currency::minimum_balance() + 50, + ); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: 50, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: ALICE, + amount: 241, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }), + topics: vec![code_hash], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE, + contract: addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&addr)], + }, + ] + ); + }); +} + +#[test] +fn storage_deposit_works() { + let (wasm, code_hash) = compile_module::("multi_store").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let mut deposit = ::Currency::minimum_balance(); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Drop previous events + initialize_block(2); + + // Create storage + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 42, + GAS_LIMIT, + None, + (1_000u32, 5_000u32).encode(), + )); + // 4 is for creating 2 storage items + let charged0 = 4 + 1_000 + 5_000; + deposit += charged0; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + // Add more storage (but also remove some) + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + (2_000u32, 4_900u32).encode(), + )); + let charged1 = 1_000 - 100; + deposit += charged1; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + // Remove more storage (but also add some) + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + (2_100u32, 900u32).encode(), + )); + // -1 for numeric instability + let refunded0 = 4_000 - 100 - 1; + deposit -= refunded0; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: 42, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&addr)], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: charged0, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: addr.clone(), + amount: charged0, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&addr)], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: charged1, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: addr.clone(), + amount: charged1, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&addr)], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated { + from: addr.clone(), + to: ALICE, + amount: refunded0, + destination_status: BalanceStatus::Free, + }), + topics: vec![], + }, + ] + ); + }); +} + +#[test] +fn set_code_extrinsic() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + let (new_wasm, new_code_hash) = compile_module::("crypto_hashes").unwrap(); + + assert_ne!(code_hash, new_code_hash); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_wasm, + None, + Determinism::Deterministic + )); + + // Drop previous events + initialize_block(2); + + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + + // only root can execute this extrinsic + assert_noop!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), addr.clone(), new_code_hash), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![],); + + // contract must exist + assert_noop!( + Contracts::set_code(RuntimeOrigin::root(), BOB, new_code_hash), + >::ContractNotFound, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![],); + + // new code hash must exist + assert_noop!( + Contracts::set_code(RuntimeOrigin::root(), addr.clone(), Default::default()), + >::CodeNotFound, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![],); + + // successful call + assert_ok!(Contracts::set_code(RuntimeOrigin::root(), addr.clone(), new_code_hash)); + assert_eq!(get_contract(&addr).code_hash, new_code_hash); + assert_refcount!(&code_hash, 0); + assert_refcount!(&new_code_hash, 1); + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(pallet_contracts::Event::ContractCodeUpdated { + contract: addr.clone(), + new_code_hash, + old_code_hash: code_hash, + }), + topics: vec![hash(&addr), new_code_hash, code_hash], + },] + ); + }); +} + +#[test] +fn call_after_killed_account_needs_funding() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let min_balance = ::Currency::minimum_balance(); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 700, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Drop previous events + initialize_block(2); + + // Destroy the account of the contract by slashing. + // Slashing can actually happen if the contract takes part in staking. + // It is a corner case and we accept the destruction of the account. + let _ = ::Currency::slash( + &addr, + ::Currency::total_balance(&addr), + ); + + // Sending below the minimum balance will fail the call because it needs to create the + // account in order to send balance there. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + min_balance - 1, + GAS_LIMIT, + None, + vec![], + ), + >::TransferFailed + ); + + // Sending zero should work as it does not do a transfer + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![], + )); + + // Sending minimum balance should work + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + min_balance, + GAS_LIMIT, + None, + vec![], + )); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::KilledAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Slashed { + who: addr.clone(), + amount: min_balance + 700 + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&addr)], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&addr)], + }, + ] + ); + }); +} + +#[test] +fn contract_reverted() { + let (wasm, code_hash) = compile_module::("return_with_data").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let flags = ReturnFlags::REVERT; + let buffer = [4u8, 8, 15, 16, 23, 42]; + let input = (flags.bits(), buffer).encode(); + + // We just upload the code for later use + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + None, + Determinism::Deterministic + )); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + Contracts::instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + code_hash, + input.clone(), + vec![], + ), + >::ContractReverted, + ); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + input.clone(), + vec![], + ), + >::ContractReverted, + ); + + // Calling directly: revert leads to success but the flags indicate the error + // This is just a different way of transporting the error that allows the read out + // the `data` which is only there on success. Obviously, the contract isn't + // instantiated. + let result = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Existing(code_hash), + input.clone(), + vec![], + false, + ) + .result + .unwrap(); + assert_eq!(result.result.flags, flags); + assert_eq!(result.result.data, buffer); + assert!(!>::contains_key(result.account_id)); + + // Pass empty flags and therefore successfully instantiate the contract for later use. + let addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Existing(code_hash), + ReturnFlags::empty().bits().encode(), + vec![], + false, + ) + .result + .unwrap() + .account_id; + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + input.clone() + ), + >::ContractReverted, + ); + + // Calling directly: revert leads to success but the flags indicate the error + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + input, + false, + Determinism::Deterministic, + ) + .result + .unwrap(); + assert_eq!(result.flags, flags); + assert_eq!(result.data, buffer); + }); +} + +#[test] +fn code_rejected_error_works() { + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + let (wasm, _) = compile_module::("invalid_module").unwrap(); + assert_noop!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + None, + Determinism::Deterministic + ), + >::CodeRejected, + ); + let result = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm), + vec![], + vec![], + true, + ); + assert_err!(result.result, >::CodeRejected); + assert_eq!( + std::str::from_utf8(&result.debug_message).unwrap(), + "validation of new code failed" + ); + + let (wasm, _) = compile_module::("invalid_contract").unwrap(); + assert_noop!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + None, + Determinism::Deterministic + ), + >::CodeRejected, + ); + + let result = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm), + vec![], + vec![], + true, + ); + assert_err!(result.result, >::CodeRejected); + assert_eq!( + std::str::from_utf8(&result.debug_message).unwrap(), + "call function isn't exported" + ); + }); +} + +#[test] +fn set_code_hash() { + let (wasm, code_hash) = compile_module::("set_code_hash").unwrap(); + let (new_wasm, new_code_hash) = compile_module::("new_set_code_hash_contract").unwrap(); + + let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Instantiate the 'caller' + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 300_000, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + // upload new code + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_wasm.clone(), + None, + Determinism::Deterministic + )); + + System::reset_events(); + + // First call sets new code_hash and returns 1 + let result = Contracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + GAS_LIMIT, + None, + new_code_hash.as_ref().to_vec(), + true, + Determinism::Deterministic, + ) + .result + .unwrap(); + assert_return_code!(result, 1); + + // Second calls new contract code that returns 2 + let result = Contracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + GAS_LIMIT, + None, + vec![], + true, + Determinism::Deterministic, + ) + .result + .unwrap(); + assert_return_code!(result, 2); + + // Checking for the last event only + assert_eq!( + &System::events(), + &[ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::ContractCodeUpdated { + contract: contract_addr.clone(), + new_code_hash, + old_code_hash: code_hash, + }), + topics: vec![hash(&contract_addr), new_code_hash, code_hash], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: contract_addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&contract_addr)], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: ALICE, + contract: contract_addr.clone(), + }), + topics: vec![hash(&ALICE), hash(&contract_addr)], + }, + ], + ); + }); +} + +#[test] +fn storage_deposit_limit_is_enforced() { + let (wasm, code_hash) = compile_module::("store").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let min_balance = ::Currency::minimum_balance(); + + // Instantiate the BOB contract. + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Check that the BOB contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), min_balance); + assert_eq!(::Currency::total_balance(&addr), min_balance); + + // Create 100 bytes of storage with a price of per byte + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(1)), + 100u32.to_le_bytes().to_vec() + ), + >::StorageDepositLimitExhausted, + ); + }); +} + +#[test] +fn storage_deposit_limit_is_enforced_late() { + let (wasm_caller, code_hash_caller) = + compile_module::("create_storage_and_call").unwrap(); + let (wasm_callee, code_hash_callee) = compile_module::("store").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm_caller, + vec![], + vec![], + )); + let addr_caller = Contracts::contract_address(&ALICE, &code_hash_caller, &[]); + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm_callee, + vec![], + vec![], + )); + let addr_callee = Contracts::contract_address(&ALICE, &code_hash_callee, &[]); + + // Create 100 bytes of storage with a price of per byte + // This is 100 Balance + 2 Balance for the item + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_callee.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(102)), + 100u32.to_le_bytes().to_vec() + )); + + // We do not remove any storage but require 14 bytes of storage for the new + // storage created in the immediate contract. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(5)), + 100u32 + .to_le_bytes() + .as_ref() + .iter() + .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .cloned() + .collect(), + ), + >::StorageDepositLimitExhausted, + ); + + // Allow for the additional 14 bytes but demand an additional byte in the callee contract. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(14)), + 101u32 + .to_le_bytes() + .as_ref() + .iter() + .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .cloned() + .collect(), + ), + >::StorageDepositLimitExhausted, + ); + + // Refund in the callee contract but not enough to cover the 14 balance required by the + // caller. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(0)), + 87u32 + .to_le_bytes() + .as_ref() + .iter() + .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .cloned() + .collect(), + ), + >::StorageDepositLimitExhausted, + ); + + let _ = Balances::make_free_balance_be(&ALICE, 1_000); + + // Send more than the sender has balance. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(50)), + 1_200u32 + .to_le_bytes() + .as_ref() + .iter() + .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .cloned() + .collect(), + ), + >::StorageDepositLimitExhausted, + ); + + // Same as above but allow for the additional balance. + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(1)), + 87u32 + .to_le_bytes() + .as_ref() + .iter() + .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .cloned() + .collect(), + )); + }); +} + +#[test] +fn deposit_limit_honors_liquidity_restrictions() { + let (wasm, code_hash) = compile_module::("store").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let _ = Balances::deposit_creating(&BOB, 1_000); + let min_balance = ::Currency::minimum_balance(); + + // Instantiate the BOB contract. + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), min_balance); + assert_eq!(::Currency::total_balance(&addr), min_balance); + + // check that the lock ins honored + Balances::set_lock([0; 8], &BOB, 1_000, WithdrawReasons::TRANSFER); + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(200)), + 100u32.to_le_bytes().to_vec() + ), + >::StorageDepositNotEnoughFunds, + ); + assert_eq!(Balances::free_balance(&BOB), 1_000); + }); +} + +#[test] +fn deposit_limit_honors_existential_deposit() { + let (wasm, code_hash) = compile_module::("store").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let _ = Balances::deposit_creating(&BOB, 1_000); + let min_balance = ::Currency::minimum_balance(); + + // Instantiate the BOB contract. + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), min_balance); + assert_eq!(::Currency::total_balance(&addr), min_balance); + + // check that the deposit can't bring the account below the existential deposit + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(900)), + 100u32.to_le_bytes().to_vec() + ), + >::StorageDepositNotEnoughFunds, + ); + assert_eq!(Balances::free_balance(&BOB), 1_000); + }); +} + +#[test] +fn deposit_limit_honors_min_leftover() { + let (wasm, code_hash) = compile_module::("store").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let _ = Balances::deposit_creating(&BOB, 1_000); + let min_balance = ::Currency::minimum_balance(); + + // Instantiate the BOB contract. + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), min_balance); + assert_eq!(::Currency::total_balance(&addr), min_balance); + + // check that the minumum leftover (value send) is considered + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr.clone(), + 400, + GAS_LIMIT, + Some(codec::Compact(500)), + 100u32.to_le_bytes().to_vec() + ), + >::StorageDepositNotEnoughFunds, + ); + assert_eq!(Balances::free_balance(&BOB), 1_000); + }); +} + +#[test] +fn cannot_instantiate_indeterministic_code() { + let (wasm, code_hash) = compile_module::("float_instruction").unwrap(); + let (caller_wasm, _) = compile_module::("instantiate_return_code").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Try to instantiate directly from code + assert_err_ignore_postinfo!( + Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm.clone(), + vec![], + vec![], + ), + >::CodeRejected, + ); + assert_err!( + Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm.clone()), + vec![], + vec![], + false, + ) + .result, + >::CodeRejected, + ); + + // Try to upload a non deterministic code as deterministic + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + None, + Determinism::Deterministic + ), + >::CodeRejected, + ); + + // Try to instantiate from already stored indeterministic code hash + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + None, + Determinism::AllowIndeterminism, + )); + assert_err_ignore_postinfo!( + Contracts::instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + code_hash, + vec![], + vec![], + ), + >::Indeterministic, + ); + assert_err!( + Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Existing(code_hash), + vec![], + vec![], + false, + ) + .result, + >::Indeterministic, + ); + + // Deploy contract which instantiates another contract + let addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(caller_wasm), + vec![], + vec![], + false, + ) + .result + .unwrap() + .account_id; + + // Try to instantiate `code_hash` from another contract in deterministic mode + assert_err!( + >::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + code_hash.encode(), + false, + Determinism::Deterministic, + ) + .result, + >::Indeterministic, + ); + + // Instantiations are not allowed even in non determinism mode + assert_err!( + >::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + code_hash.encode(), + false, + Determinism::AllowIndeterminism, + ) + .result, + >::Indeterministic, + ); + }); +} + +#[test] +fn cannot_set_code_indeterministic_code() { + let (wasm, code_hash) = compile_module::("float_instruction").unwrap(); + let (caller_wasm, _) = compile_module::("set_code_hash").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Put the non deterministic contract on-chain + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + None, + Determinism::AllowIndeterminism, + )); + + // Create the contract that will call `seal_set_code_hash` + let caller_addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(caller_wasm), + vec![], + vec![], + false, + ) + .result + .unwrap() + .account_id; + + // We do not allow to set the code hash to a non determinstic wasm + assert_err!( + >::bare_call( + ALICE, + caller_addr.clone(), + 0, + GAS_LIMIT, + None, + code_hash.encode(), + false, + Determinism::AllowIndeterminism, + ) + .result, + >::Indeterministic, + ); + }); +} + +#[test] +fn delegate_call_indeterministic_code() { + let (wasm, code_hash) = compile_module::("float_instruction").unwrap(); + let (caller_wasm, _) = compile_module::("delegate_call_simple").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Put the non deterministic contract on-chain + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + None, + Determinism::AllowIndeterminism, + )); + + // Create the contract that will call `seal_delegate_call` + let caller_addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(caller_wasm), + vec![], + vec![], + false, + ) + .result + .unwrap() + .account_id; + + // The delegate call will fail in deterministic mode + assert_err!( + >::bare_call( + ALICE, + caller_addr.clone(), + 0, + GAS_LIMIT, + None, + code_hash.encode(), + false, + Determinism::Deterministic, + ) + .result, + >::Indeterministic, + ); + + // The delegate call will work on non deterministic mode + assert_ok!( + >::bare_call( + ALICE, + caller_addr.clone(), + 0, + GAS_LIMIT, + None, + code_hash.encode(), + false, + Determinism::AllowIndeterminism, + ) + .result + ); + }); +} + +#[test] +fn reentrance_count_works_with_call() { + let (wasm, code_hash) = compile_module::("reentrance_count_call").unwrap(); + let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 300_000, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + + // passing reentrant count to the input + let input = 0.encode(); + + Contracts::bare_call( + ALICE, + contract_addr, + 0, + GAS_LIMIT, + None, + input, + true, + Determinism::Deterministic, + ) + .result + .unwrap(); + }); +} + +#[test] +fn reentrance_count_works_with_delegated_call() { + let (wasm, code_hash) = compile_module::("reentrance_count_delegated_call").unwrap(); + let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 300_000, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + + // adding a callstack height to the input + let input = (code_hash, 1).encode(); + + Contracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + GAS_LIMIT, + None, + input, + true, + Determinism::Deterministic, + ) + .result + .unwrap(); + }); +} + +#[test] +fn account_reentrance_count_works() { + let (wasm, code_hash) = compile_module::("account_reentrance_count_call").unwrap(); + let (wasm_reentrance_count, code_hash_reentrance_count) = + compile_module::("reentrance_count_call").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 300_000, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 300_000, + GAS_LIMIT, + None, + wasm_reentrance_count, + vec![], + vec![] + )); + + let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + let another_contract_addr = + Contracts::contract_address(&ALICE, &code_hash_reentrance_count, &[]); + + let result1 = Contracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + GAS_LIMIT, + None, + contract_addr.encode(), + true, + Determinism::Deterministic, + ) + .result + .unwrap(); + + let result2 = Contracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + GAS_LIMIT, + None, + another_contract_addr.encode(), + true, + Determinism::Deterministic, + ) + .result + .unwrap(); + + assert_eq!(result1.data, 1.encode()); + assert_eq!(result2.data, 0.encode()); + }); +} diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs index 08a7449683ed6..eb337ac682860 100644 --- a/frame/contracts/src/wasm/code_cache.rs +++ b/frame/contracts/src/wasm/code_cache.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,54 +28,105 @@ //! this guarantees that every instrumented contract code in cache cannot have the version equal to //! the current one. Thus, before executing a contract it should be reinstrument with new schedule. -#[cfg(feature = "runtime-benchmarks")] -pub use self::private::reinstrument; use crate::{ gas::{GasMeter, Token}, wasm::{prepare, PrefabWasmModule}, weights::WeightInfo, - CodeHash, CodeStorage, Config, Error, Event, Pallet as Contracts, PristineCode, Schedule, + CodeHash, CodeStorage, Config, Error, Event, OwnerInfoOf, Pallet, PristineCode, Schedule, Weight, }; -use frame_support::dispatch::DispatchError; +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + ensure, + traits::{Get, ReservableCurrency}, + WeakBoundedVec, +}; use sp_core::crypto::UncheckedFrom; +use sp_runtime::traits::BadOrigin; +use sp_std::vec; /// Put the instrumented module in storage. /// /// Increments the refcount of the in-storage `prefab_module` if it already exists in storage /// under the specified `code_hash`. -pub fn store(mut prefab_module: PrefabWasmModule) +pub fn store(mut module: PrefabWasmModule, instantiated: bool) -> DispatchResult where T::AccountId: UncheckedFrom + AsRef<[u8]>, { - let code_hash = sp_std::mem::take(&mut prefab_module.code_hash); - - // original_code is only `Some` if the contract was instantiated from a new code - // but `None` if it was loaded from storage. - if let Some(code) = prefab_module.original_code.take() { - >::insert(&code_hash, code); - } + let code_hash = sp_std::mem::take(&mut module.code_hash); >::mutate(&code_hash, |existing| match existing { - Some(module) => increment_64(&mut module.refcount), + Some(existing) => { + // We instrument any uploaded contract anyways. We might as well store it to save + // a potential re-instrumentation later. + existing.code = module.code; + existing.instruction_weights_version = module.instruction_weights_version; + // When the code was merely uploaded but not instantiated we can skip this. + if instantiated { + >::mutate(&code_hash, |owner_info| { + if let Some(owner_info) = owner_info { + owner_info.refcount = owner_info.refcount.checked_add(1).expect( + " + refcount is 64bit. Generating this overflow would require to store + _at least_ 18 exabyte of data assuming that a contract consumes only + one byte of data. Any node would run out of storage space before hitting + this overflow. + qed + ", + ); + } + }) + } + Ok(()) + }, None => { - *existing = Some(prefab_module); - Contracts::::deposit_event(Event::CodeStored(code_hash)) + let orig_code = module.original_code.take().expect( + " + If an executable isn't in storage it was uploaded. + If it was uploaded the original code must exist. qed + ", + ); + let mut owner_info = module.owner_info.take().expect( + "If an executable isn't in storage it was uploaded. + If it was uploaded the owner info was generated and attached. qed + ", + ); + // This `None` case happens only in freshly uploaded modules. This means that + // the `owner` is always the origin of the current transaction. + T::Currency::reserve(&owner_info.owner, owner_info.deposit) + .map_err(|_| >::StorageDepositNotEnoughFunds)?; + owner_info.refcount = if instantiated { 1 } else { 0 }; + >::insert(&code_hash, orig_code); + >::insert(&code_hash, owner_info); + *existing = Some(module); + >::deposit_event(vec![code_hash], Event::CodeStored { code_hash }); + Ok(()) }, + }) +} + +/// Decrement the refcount of a code in-storage by one. +/// +/// # Note +/// +/// A contract whose refcount dropped to zero isn't automatically removed. A `remove_code` +/// transaction must be submitted by the original uploader to do so. +pub fn decrement_refcount(code_hash: CodeHash) { + >::mutate(code_hash, |existing| { + if let Some(info) = existing { + info.refcount = info.refcount.saturating_sub(1); + } }); } /// Increment the refcount of a code in-storage by one. -pub fn increment_refcount( - code_hash: CodeHash, - gas_meter: &mut GasMeter, -) -> Result<(), DispatchError> -where - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ - gas_meter.charge(CodeToken::UpdateRefcount(estimate_code_size::(&code_hash)?))?; - >::mutate(code_hash, |existing| { - if let Some(module) = existing { - increment_64(&mut module.refcount); +/// +/// # Errors +/// +/// [`Error::CodeNotFound`] is returned if the specified `code_hash` does not exist. +pub fn increment_refcount(code_hash: CodeHash) -> Result<(), DispatchError> { + >::mutate(code_hash, |existing| -> Result<(), DispatchError> { + if let Some(info) = existing { + info.refcount = info.refcount.saturating_add(1); Ok(()) } else { Err(Error::::CodeNotFound.into()) @@ -83,27 +134,25 @@ where }) } -/// Decrement the refcount of a code in-storage by one and remove the code when it drops to zero. -pub fn decrement_refcount( - code_hash: CodeHash, - gas_meter: &mut GasMeter, -) -> Result<(), DispatchError> +/// Try to remove code together with all associated information. +pub fn try_remove(origin: &T::AccountId, code_hash: CodeHash) -> DispatchResult where T::AccountId: UncheckedFrom + AsRef<[u8]>, { - if let Ok(len) = estimate_code_size::(&code_hash) { - gas_meter.charge(CodeToken::UpdateRefcount(len))?; - } - >::mutate_exists(code_hash, |existing| { - if let Some(module) = existing { - module.refcount = module.refcount.saturating_sub(1); - if module.refcount == 0 { - *existing = None; - finish_removal::(code_hash); - } + >::try_mutate_exists(&code_hash, |existing| { + if let Some(owner_info) = existing { + ensure!(owner_info.refcount == 0, >::CodeInUse); + ensure!(&owner_info.owner == origin, BadOrigin); + T::Currency::unreserve(&owner_info.owner, owner_info.deposit); + *existing = None; + >::remove(&code_hash); + >::remove(&code_hash); + >::deposit_event(vec![code_hash], Event::CodeRemoved { code_hash }); + Ok(()) + } else { + Err(>::CodeNotFound.into()) } - }); - Ok(()) + }) } /// Load code with the given code hash. @@ -111,129 +160,85 @@ where /// If the module was instrumented with a lower version of schedule than /// the current one given as an argument, then this function will perform /// re-instrumentation and update the cache in the storage. -/// -/// # Note -/// -/// If `reinstrument` is set it is assumed that the load is performed in the context of -/// a contract call: This means we charge the size based cased for loading the contract. pub fn load( code_hash: CodeHash, - mut reinstrument: Option<(&Schedule, &mut GasMeter)>, + schedule: &Schedule, + gas_meter: &mut GasMeter, ) -> Result, DispatchError> where T::AccountId: UncheckedFrom + AsRef<[u8]>, { - // The reinstrument case coincides with the cases where we need to charge extra - // based upon the code size: On-chain execution. - if let Some((_, gas_meter)) = &mut reinstrument { - gas_meter.charge(CodeToken::Load(estimate_code_size::(&code_hash)?))?; - } + let max_code_len = T::MaxCodeLen::get(); + let charged = gas_meter.charge(CodeToken::Load(max_code_len))?; - let mut prefab_module = - >::get(code_hash).ok_or_else(|| Error::::CodeNotFound)?; + let mut prefab_module = >::get(code_hash).ok_or(Error::::CodeNotFound)?; + gas_meter.adjust_gas(charged, CodeToken::Load(prefab_module.code.len() as u32)); prefab_module.code_hash = code_hash; - if let Some((schedule, gas_meter)) = reinstrument { - if prefab_module.instruction_weights_version < schedule.instruction_weights.version { - // The instruction weights have changed. - // We need to re-instrument the code with the new instruction weights. - gas_meter.charge(CodeToken::Instrument(prefab_module.original_code_len))?; - private::reinstrument(&mut prefab_module, schedule)?; - } - } - Ok(prefab_module) -} - -mod private { - use super::*; - - /// Instruments the passed prefab wasm module with the supplied schedule. - pub fn reinstrument( - prefab_module: &mut PrefabWasmModule, - schedule: &Schedule, - ) -> Result<(), DispatchError> - where - T::AccountId: UncheckedFrom + AsRef<[u8]>, - { - let original_code = >::get(&prefab_module.code_hash) - .ok_or_else(|| Error::::CodeNotFound)?; - prefab_module.code = prepare::reinstrument_contract::(original_code, schedule)?; - prefab_module.instruction_weights_version = schedule.instruction_weights.version; - >::insert(&prefab_module.code_hash, &*prefab_module); - Ok(()) + if prefab_module.instruction_weights_version < schedule.instruction_weights.version { + // The instruction weights have changed. + // We need to re-instrument the code with the new instruction weights. + let charged = gas_meter.charge(CodeToken::Reinstrument(max_code_len))?; + let code_size = reinstrument(&mut prefab_module, schedule)?; + gas_meter.adjust_gas(charged, CodeToken::Reinstrument(code_size)); } -} -/// Finish removal of a code by deleting the pristine code and emitting an event. -fn finish_removal(code_hash: CodeHash) -where - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ - >::remove(code_hash); - Contracts::::deposit_event(Event::CodeRemoved(code_hash)) -} - -/// Increment the refcount panicking if it should ever overflow (which will not happen). -/// -/// We try hard to be infallible here because otherwise more storage transactions would be -/// necessary to account for failures in storing code for an already instantiated contract. -fn increment_64(refcount: &mut u64) { - *refcount = refcount.checked_add(1).expect( - " - refcount is 64bit. Generating this overflow would require to store - _at least_ 18 exabyte of data assuming that a contract consumes only - one byte of data. Any node would run out of storage space before hitting - this overflow. - qed - ", - ); + Ok(prefab_module) } -/// Get the size of the instrumented code stored at `code_hash` without loading it. +/// Instruments the passed prefab wasm module with the supplied schedule. /// -/// The returned value is slightly too large because it also contains the fields apart from -/// `code` which are located inside [`PrefabWasmModule`]. However, those are negligible when -/// compared to the code size. Additionally, charging too much weight is completely safe. -fn estimate_code_size(code_hash: &CodeHash) -> Result +/// Returns the size in bytes of the uninstrumented code. +pub fn reinstrument( + prefab_module: &mut PrefabWasmModule, + schedule: &Schedule, +) -> Result where T::AccountId: UncheckedFrom + AsRef<[u8]>, { - let key = >::hashed_key_for(code_hash); - let mut data = [0u8; 0]; - let len = sp_io::storage::read(&key, &mut data, 0).ok_or_else(|| Error::::CodeNotFound)?; - Ok(len) + let original_code = + >::get(&prefab_module.code_hash).ok_or(Error::::CodeNotFound)?; + let original_code_len = original_code.len(); + // We need to allow contracts growing too big after re-instrumentation. Otherwise + // the contract can become inaccessible. The user has no influence over this size + // as the contract is already deployed and every change in size would be the result + // of changes in the instrumentation algorithm controlled by the chain authors. + prefab_module.code = WeakBoundedVec::force_from( + prepare::reinstrument::( + &original_code, + schedule, + prefab_module.determinism, + )?, + Some("Contract exceeds size limit after re-instrumentation."), + ); + prefab_module.instruction_weights_version = schedule.instruction_weights.version; + >::insert(&prefab_module.code_hash, &*prefab_module); + Ok(original_code_len as u32) } /// Costs for operations that are related to code handling. #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Clone, Copy)] enum CodeToken { - /// Weight for instrumenting a contract contract of the supplied size in bytes. - Instrument(u32), - /// Weight for loading a contract per kilobyte. + /// Weight for reinstrumenting a contract contract of the supplied size in bytes. + Reinstrument(u32), + /// Weight for loading a contract per byte. Load(u32), - /// Weight for changing the refcount of a contract per kilobyte. - UpdateRefcount(u32), } -impl Token for CodeToken -where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl Token for CodeToken { fn weight(&self) -> Weight { use self::CodeToken::*; - // In case of `Load` and `UpdateRefcount` we already covered the general costs of - // accessing the storage but still need to account for the actual size of the - // contract code. This is why we substract `T::*::(0)`. We need to do this at this - // point because when charging the general weight we do not know the size of - // the contract. + // In case of `Load` we already covered the general costs of + // calling the storage but still need to account for the actual size of the + // contract code. This is why we subtract `T::*::(0)`. We need to do this at this + // point because when charging the general weight for calling the contract we not know the + // size of the contract. match *self { - Instrument(len) => T::WeightInfo::instrument(len / 1024), - Load(len) => - T::WeightInfo::code_load(len / 1024).saturating_sub(T::WeightInfo::code_load(0)), - UpdateRefcount(len) => T::WeightInfo::code_refcount(len / 1024) - .saturating_sub(T::WeightInfo::code_refcount(0)), + Reinstrument(len) => T::WeightInfo::reinstrument(len), + Load(len) => T::WeightInfo::call_with_code_per_byte(len) + .saturating_sub(T::WeightInfo::call_with_code_per_byte(0)) + .set_proof_size(len.into()), } } } diff --git a/frame/contracts/src/wasm/env_def/macros.rs b/frame/contracts/src/wasm/env_def/macros.rs deleted file mode 100644 index ea7f51da75264..0000000000000 --- a/frame/contracts/src/wasm/env_def/macros.rs +++ /dev/null @@ -1,396 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 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. - -//! Definition of macros that hides boilerplate of defining external environment -//! for a wasm module. -//! -//! Most likely you should use `define_env` macro. - -macro_rules! convert_args { - () => (vec![]); - ( $( $t:ty ),* ) => ( vec![ $( { use $crate::wasm::env_def::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] ); -} - -macro_rules! gen_signature { - ( ( $( $params: ty ),* ) ) => ( - { - pwasm_utils::parity_wasm::elements::FunctionType::new( - convert_args!($($params),*), vec![], - ) - } - ); - - ( ( $( $params: ty ),* ) -> $returns: ty ) => ( - { - pwasm_utils::parity_wasm::elements::FunctionType::new( - convert_args!($($params),*), - vec![{use $crate::wasm::env_def::ConvertibleToWasm; <$returns>::VALUE_TYPE}], - ) - } - ); -} - -macro_rules! gen_signature_dispatch { - ( - $needle_module:ident, - $needle_name:ident, - $needle_sig:ident ; - $module:ident, - $name:ident - ( $ctx:ident $( , $names:ident : $params:ty )* ) $( -> $returns:ty )* , $($rest:tt)* - ) => { - let module = stringify!($module).as_bytes(); - if module == $needle_module && stringify!($name).as_bytes() == $needle_name { - let signature = gen_signature!( ( $( $params ),* ) $( -> $returns )* ); - if $needle_sig == &signature { - return true; - } - } else { - gen_signature_dispatch!($needle_module, $needle_name, $needle_sig ; $($rest)*); - } - }; - ( $needle_module:ident, $needle_name:ident, $needle_sig:ident ; ) => {}; -} - -/// Unmarshall arguments and then execute `body` expression and return its result. -macro_rules! unmarshall_then_body { - ( $body:tt, $ctx:ident, $args_iter:ident, $( $names:ident : $params:ty ),* ) => ({ - $( - let $names : <$params as $crate::wasm::env_def::ConvertibleToWasm>::NativeType = - $args_iter.next() - .and_then(|v| <$params as $crate::wasm::env_def::ConvertibleToWasm> - ::from_typed_value(v.clone())) - .expect( - "precondition: all imports should be checked against the signatures of corresponding - functions defined by `define_env!` macro by the user of the macro; - signatures of these functions defined by `$params`; - calls always made with arguments types of which are defined by the corresponding imports; - thus types of arguments should be equal to type list in `$params` and - length of argument list and $params should be equal; - thus this can never be `None`; - qed; - " - ); - )* - $body - }) -} - -/// Since we can't specify the type of closure directly at binding site: -/// -/// ```nocompile -/// let f: FnOnce() -> Result<::NativeType, _> = || { /* ... */ }; -/// ``` -/// -/// we use this function to constrain the type of the closure. -#[inline(always)] -pub fn constrain_closure(f: F) -> F -where - F: FnOnce() -> Result, -{ - f -} - -macro_rules! unmarshall_then_body_then_marshall { - ( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) -> $returns:ty => $body:tt ) => ({ - let body = $crate::wasm::env_def::macros::constrain_closure::< - <$returns as $crate::wasm::env_def::ConvertibleToWasm>::NativeType, _ - >(|| { - unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*) - }); - let r = body().map_err(|reason| { - $ctx.set_trap_reason(reason); - sp_sandbox::HostError - })?; - return Ok(sp_sandbox::ReturnValue::Value({ use $crate::wasm::env_def::ConvertibleToWasm; r.to_typed_value() })) - }); - ( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({ - let body = $crate::wasm::env_def::macros::constrain_closure::<(), _>(|| { - unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*) - }); - body().map_err(|reason| { - $ctx.set_trap_reason(reason); - sp_sandbox::HostError - })?; - return Ok(sp_sandbox::ReturnValue::Unit) - }) -} - -macro_rules! define_func { - ( $trait:tt $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => { - fn $name< E: $trait >( - $ctx: &mut $crate::wasm::Runtime, - args: &[sp_sandbox::Value], - ) -> Result - where - ::AccountId: - sp_core::crypto::UncheckedFrom<::Hash> + - AsRef<[u8]> - { - #[allow(unused)] - let mut args = args.iter(); - - unmarshall_then_body_then_marshall!( - args, - $ctx, - ( $( $names : $params ),* ) $( -> $returns )* => $body - ) - } - }; -} - -macro_rules! register_body { - ( $reg_cb:ident, $trait:tt; - $module:ident $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* ) - $( -> $returns:ty )* => $body:tt - ) => { - $reg_cb( - stringify!($module).as_bytes(), - stringify!($name).as_bytes(), - { - define_func!( - $trait $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body - ); - $name:: - } - ); - } -} - -macro_rules! register_func { - ( $reg_cb:ident, $trait:tt; ) => {}; - - ( $reg_cb:ident, $trait:tt; - __unstable__ $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* ) - $( -> $returns:ty )* => $body:tt $($rest:tt)* - ) => { - #[cfg(feature = "unstable-interface")] - register_body!( - $reg_cb, $trait; - __unstable__ $name - ( $ctx $( , $names : $params )* ) - $( -> $returns )* => $body - ); - register_func!( $reg_cb, $trait; $($rest)* ); - }; - - ( $reg_cb:ident, $trait:tt; - $module:ident $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* ) - $( -> $returns:ty )* => $body:tt $($rest:tt)* - ) => { - register_body!( - $reg_cb, $trait; - $module $name - ( $ctx $( , $names : $params )* ) - $( -> $returns )* => $body - ); - register_func!( $reg_cb, $trait; $($rest)* ); - }; -} - -/// Define a function set that can be imported by executing wasm code. -/// -/// **NB**: Be advised that all functions defined by this macro -/// will panic if called with unexpected arguments. -/// -/// It's up to the user of this macro to check signatures of wasm code to be executed -/// and reject the code if any imported function has a mismatched signature. -macro_rules! define_env { - ( $init_name:ident , < E: $trait:tt > , - $( [$module:ident] $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* ) - $( -> $returns:ty )* => $body:tt , )* - ) => { - pub struct $init_name; - - impl $crate::wasm::env_def::ImportSatisfyCheck for $init_name { - fn can_satisfy( - module: &[u8], - name: &[u8], - func_type: &pwasm_utils::parity_wasm::elements::FunctionType, - ) -> bool - { - #[cfg(not(feature = "unstable-interface"))] - if module == b"__unstable__" { - return false; - } - gen_signature_dispatch!( - module, name, func_type ; - $( $module, $name ( $ctx $(, $names : $params )* ) $( -> $returns )* , )* - ); - - return false; - } - } - - impl $crate::wasm::env_def::FunctionImplProvider for $init_name - where - ::AccountId: - sp_core::crypto::UncheckedFrom<::Hash> + - AsRef<[u8]> - { - fn impls)>(f: &mut F) { - register_func!( - f, - $trait; - $( $module $name ( $ctx $( , $names : $params )* ) $( -> $returns)* => $body )* - ); - } - } - }; -} - -#[cfg(test)] -mod tests { - use crate::{ - exec::Ext, - wasm::{runtime::TrapReason, tests::MockExt, Runtime}, - Weight, - }; - use pwasm_utils::parity_wasm::elements::{FunctionType, ValueType}; - use sp_runtime::traits::Zero; - use sp_sandbox::{ReturnValue, Value}; - - struct TestRuntime { - value: u32, - } - - impl TestRuntime { - fn set_trap_reason(&mut self, _reason: TrapReason) {} - } - - #[test] - fn macro_unmarshall_then_body_then_marshall_value_or_trap() { - fn test_value( - _ctx: &mut TestRuntime, - args: &[sp_sandbox::Value], - ) -> Result { - let mut args = args.iter(); - unmarshall_then_body_then_marshall!( - args, - _ctx, - (a: u32, b: u32) -> u32 => { - if b == 0 { - Err(crate::wasm::runtime::TrapReason::Termination) - } else { - Ok(a / b) - } - } - ) - } - - let ctx = &mut TestRuntime { value: 0 }; - assert_eq!( - test_value(ctx, &[Value::I32(15), Value::I32(3)]).unwrap(), - ReturnValue::Value(Value::I32(5)), - ); - assert!(test_value(ctx, &[Value::I32(15), Value::I32(0)]).is_err()); - } - - #[test] - fn macro_unmarshall_then_body_then_marshall_unit() { - fn test_unit( - ctx: &mut TestRuntime, - args: &[sp_sandbox::Value], - ) -> Result { - let mut args = args.iter(); - unmarshall_then_body_then_marshall!( - args, - ctx, - (a: u32, b: u32) => { - ctx.value = a + b; - Ok(()) - } - ) - } - - let ctx = &mut TestRuntime { value: 0 }; - let result = test_unit(ctx, &[Value::I32(2), Value::I32(3)]).unwrap(); - assert_eq!(result, ReturnValue::Unit); - assert_eq!(ctx.value, 5); - } - - #[test] - fn macro_define_func() { - define_func!( Ext seal_gas (_ctx, amount: u32) => { - let amount = Weight::from(amount); - if !amount.is_zero() { - Ok(()) - } else { - Err(TrapReason::Termination) - } - }); - let _f: fn( - &mut Runtime, - &[sp_sandbox::Value], - ) -> Result = seal_gas::; - } - - #[test] - fn macro_gen_signature() { - assert_eq!(gen_signature!((i32)), FunctionType::new(vec![ValueType::I32], vec![])); - - assert_eq!( - gen_signature!( (i32, u32) -> u32 ), - FunctionType::new(vec![ValueType::I32, ValueType::I32], vec![ValueType::I32]), - ); - } - - #[test] - fn macro_unmarshall_then_body() { - let args = vec![Value::I32(5), Value::I32(3)]; - let mut args = args.iter(); - - let ctx: &mut u32 = &mut 0; - - let r = unmarshall_then_body!( - { - *ctx = a + b; - a * b - }, - ctx, - args, - a: u32, - b: u32 - ); - - assert_eq!(*ctx, 8); - assert_eq!(r, 15); - } - - #[test] - fn macro_define_env() { - use crate::wasm::env_def::ImportSatisfyCheck; - - define_env!(Env, , - [seal0] seal_gas( _ctx, amount: u32 ) => { - let amount = Weight::from(amount); - if !amount.is_zero() { - Ok(()) - } else { - Err(crate::wasm::runtime::TrapReason::Termination) - } - }, - ); - - assert!(Env::can_satisfy( - b"seal0", - b"seal_gas", - &FunctionType::new(vec![ValueType::I32], vec![]) - )); - assert!(!Env::can_satisfy(b"seal0", b"not_exists", &FunctionType::new(vec![], vec![]))); - } -} diff --git a/frame/contracts/src/wasm/env_def/mod.rs b/frame/contracts/src/wasm/env_def/mod.rs deleted file mode 100644 index 6a55677f69a01..0000000000000 --- a/frame/contracts/src/wasm/env_def/mod.rs +++ /dev/null @@ -1,86 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 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::Runtime; -use crate::exec::Ext; - -use pwasm_utils::parity_wasm::elements::{FunctionType, ValueType}; -use sp_sandbox::Value; - -#[macro_use] -pub mod macros; - -pub trait ConvertibleToWasm: Sized { - const VALUE_TYPE: ValueType; - type NativeType; - fn to_typed_value(self) -> Value; - fn from_typed_value(_: Value) -> Option; -} -impl ConvertibleToWasm for i32 { - type NativeType = i32; - const VALUE_TYPE: ValueType = ValueType::I32; - fn to_typed_value(self) -> Value { - Value::I32(self) - } - fn from_typed_value(v: Value) -> Option { - v.as_i32() - } -} -impl ConvertibleToWasm for u32 { - type NativeType = u32; - const VALUE_TYPE: ValueType = ValueType::I32; - fn to_typed_value(self) -> Value { - Value::I32(self as i32) - } - fn from_typed_value(v: Value) -> Option { - match v { - Value::I32(v) => Some(v as u32), - _ => None, - } - } -} -impl ConvertibleToWasm for u64 { - type NativeType = u64; - const VALUE_TYPE: ValueType = ValueType::I64; - fn to_typed_value(self) -> Value { - Value::I64(self as i64) - } - fn from_typed_value(v: Value) -> Option { - match v { - Value::I64(v) => Some(v as u64), - _ => None, - } - } -} - -pub type HostFunc = fn( - &mut Runtime, - &[sp_sandbox::Value], -) -> Result; - -pub trait FunctionImplProvider { - fn impls)>(f: &mut F); -} - -/// This trait can be used to check whether the host environment can satisfy -/// a requested function import. -pub trait ImportSatisfyCheck { - /// Returns `true` if the host environment contains a function with - /// the specified name and its type matches to the given type, or `false` - /// otherwise. - fn can_satisfy(module: &[u8], name: &[u8], func_type: &FunctionType) -> bool; -} diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 855cb6e45091f..d85dac95cc712 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,39 +18,44 @@ //! This module provides a means for executing contracts //! represented in wasm. -#[macro_use] -mod env_def; mod code_cache; mod prepare; mod runtime; #[cfg(feature = "runtime-benchmarks")] -pub use self::code_cache::reinstrument; -pub use self::runtime::{ReturnCode, Runtime, RuntimeCosts}; +pub use crate::wasm::code_cache::reinstrument; +pub use crate::wasm::{ + prepare::TryInstantiate, + runtime::{CallFlags, Environment, ReturnCode, Runtime, RuntimeCosts}, +}; use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::GasMeter, - wasm::env_def::FunctionImplProvider, - CodeHash, Config, Schedule, + AccountIdOf, BalanceOf, CodeHash, CodeStorage, CodeVec, Config, Error, RelaxedCodeVec, + Schedule, }; -use codec::{Decode, Encode}; -use frame_support::dispatch::DispatchError; -use sp_core::crypto::UncheckedFrom; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::dispatch::{DispatchError, DispatchResult}; +use sp_core::{crypto::UncheckedFrom, Get}; +use sp_runtime::RuntimeDebug; use sp_std::prelude::*; #[cfg(test)] pub use tests::MockExt; +use wasmi::{ + Config as WasmiConfig, Engine, Instance, Linker, Memory, MemoryType, Module, StackLimits, Store, +}; /// A prepared wasm module ready for execution. /// /// # Note /// /// This data structure is mostly immutable once created and stored. The exceptions that -/// can be changed by calling a contract are `refcount`, `instruction_weights_version` and `code`. -/// `refcount` can change when a contract instantiates a new contract or self terminates. -/// `instruction_weights_version` and `code` when a contract with an outdated instrumention is -/// called. Therefore one must be careful when holding any in-memory representation of this +/// can be changed by calling a contract are `instruction_weights_version` and `code`. +/// `instruction_weights_version` and `code` change when a contract with an outdated instrumentation +/// is called. Therefore one must be careful when holding any in-memory representation of this /// type while calling into a contract as those fields can get out of date. -#[derive(Clone, Encode, Decode, scale_info::TypeInfo)] +#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[codec(mel_bound())] #[scale_info(skip_type_params(T))] pub struct PrefabWasmModule { /// Version of the instruction weights with which the code was instrumented. @@ -62,39 +67,67 @@ pub struct PrefabWasmModule { /// The maximum memory size of a contract's sandbox. #[codec(compact)] maximum: u32, - /// The number of contracts that use this as their contract code. - /// - /// If this number drops to zero this module is removed from storage. - #[codec(compact)] - refcount: u64, - /// This field is reserved for future evolution of format. - /// - /// For now this field is serialized as `None`. In the future we are able to change the - /// type parameter to a new struct that contains the fields that we want to add. - /// That new struct would also contain a reserved field for its future extensions. - /// This works because in SCALE `None` is encoded independently from the type parameter - /// of the option. - _reserved: Option<()>, /// Code instrumented with the latest schedule. - code: Vec, - /// The size of the uninstrumented code. - /// - /// We cache this value here in order to avoid the need to pull the pristine code - /// from storage when we only need its length for rent calculations. - original_code_len: u32, + code: RelaxedCodeVec, + /// A code that might contain non deterministic features and is therefore never allowed + /// to be run on chain. Specifically this code can never be instantiated into a contract + /// and can just be used through a delegate call. + determinism: Determinism, /// The uninstrumented, pristine version of the code. /// /// It is not stored because the pristine code has its own storage item. The value /// is only `Some` when this module was created from an `original_code` and `None` if /// it was loaded from storage. #[codec(skip)] - original_code: Option>, + original_code: Option>, /// The code hash of the stored code which is defined as the hash over the `original_code`. /// /// As the map key there is no need to store the hash in the value, too. It is set manually /// when loading the module from storage. #[codec(skip)] code_hash: CodeHash, + // This isn't needed for contract execution and does not get loaded from storage by default. + // It is `Some` if and only if this struct was generated from code. + #[codec(skip)] + owner_info: Option>, +} + +/// Information that belongs to a [`PrefabWasmModule`] but is stored separately. +/// +/// It is stored in a separate storage entry to avoid loading the code when not necessary. +#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T))] +pub struct OwnerInfo { + /// The account that has deployed the contract and hence is allowed to remove it. + owner: AccountIdOf, + /// The amount of balance that was deposited by the owner in order to deploy it. + #[codec(compact)] + deposit: BalanceOf, + /// The number of contracts that use this as their code. + #[codec(compact)] + refcount: u64, +} + +/// Defines the required determinism level of a wasm blob when either running or uploading code. +#[derive( + Clone, Copy, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, +)] +pub enum Determinism { + /// The execution should be deterministic and hence no indeterministic instructions are + /// allowed. + /// + /// Dispatchables always use this mode in order to make on-chain execution deterministic. + Deterministic, + /// Allow calling or uploading an indeterministic code. + /// + /// This is only possible when calling into `pallet-contracts` directly via + /// [`crate::Pallet::bare_call`]. + /// + /// # Note + /// + /// **Never** use this mode for on-chain execution. + AllowIndeterminism, } impl ExportedFunction { @@ -112,11 +145,90 @@ where T::AccountId: UncheckedFrom + AsRef<[u8]>, { /// Create the module by checking and instrumenting `original_code`. + /// + /// This does **not** store the module. For this one need to either call [`Self::store`] + /// or [`::execute`]. pub fn from_code( original_code: Vec, schedule: &Schedule, - ) -> Result { - prepare::prepare_contract(original_code, schedule).map_err(Into::into) + owner: AccountIdOf, + determinism: Determinism, + try_instantiate: TryInstantiate, + ) -> Result { + let module = prepare::prepare::( + original_code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?, + schedule, + owner, + determinism, + try_instantiate, + )?; + Ok(module) + } + + /// Store the code without instantiating it. + /// + /// Otherwise the code is stored when [`::execute`] is called. + pub fn store(self) -> DispatchResult { + code_cache::store(self, false) + } + + /// Remove the code from storage and refund the deposit to its owner. + /// + /// Applies all necessary checks before removing the code. + pub fn remove(origin: &T::AccountId, code_hash: CodeHash) -> DispatchResult { + code_cache::try_remove::(origin, code_hash) + } + + /// Returns whether there is a deposit to be payed for this module. + /// + /// Returns `0` if the module is already in storage and hence no deposit will + /// be charged when storing it. + pub fn open_deposit(&self) -> BalanceOf { + if >::contains_key(&self.code_hash) { + 0u32.into() + } else { + // Only already in-storage contracts have their `owner_info` set to `None`. + // Therefore it is correct to return `0` in this case. + self.owner_info.as_ref().map(|i| i.deposit).unwrap_or_default() + } + } + + /// Creates and returns an instance of the supplied code. + /// + /// This is either used for later executing a contract or for validation of a contract. + /// When validating we pass `()` as `host_state`. Please note that such a dummy instance must + /// **never** be called/executed since it will panic the executor. + pub fn instantiate( + code: &[u8], + host_state: H, + memory: (u32, u32), + stack_limits: StackLimits, + ) -> Result<(Store, Memory, Instance), wasmi::Error> + where + E: Environment, + { + let mut config = WasmiConfig::default(); + config + .set_stack_limits(stack_limits) + .wasm_multi_value(false) + .wasm_mutable_global(false) + .wasm_sign_extension(false) + .wasm_saturating_float_to_int(false); + let engine = Engine::new(&config); + let module = Module::new(&engine, code)?; + let mut store = Store::new(&engine, host_state); + let mut linker = Linker::new(); + E::define(&mut store, &mut linker, T::UnsafeUnstableInterface::get())?; + let memory = Memory::new(&mut store, MemoryType::new(memory.0, Some(memory.1))?).expect( + "The limits defined in our `Schedule` limit the amount of memory well below u32::MAX; qed", + ); + linker + .define("env", "memory", memory) + .expect("We just created the linker. It has no define with this name attached; qed"); + + let instance = linker.instantiate(&mut store, &module)?.ensure_no_start(&mut store)?; + + Ok((store, memory, instance)) } /// Create and store the module without checking nor instrumenting the passed code. @@ -124,22 +236,16 @@ where /// # Note /// /// This is useful for benchmarking where we don't want instrumentation to skew - /// our results. + /// our results. This also does not collect any deposit from the `owner`. #[cfg(feature = "runtime-benchmarks")] pub fn store_code_unchecked( original_code: Vec, schedule: &Schedule, - ) -> Result<(), DispatchError> { - let executable = prepare::benchmarking::prepare_contract(original_code, schedule) + owner: T::AccountId, + ) -> DispatchResult { + let executable = prepare::benchmarking::prepare(original_code, schedule, owner) .map_err::(Into::into)?; - code_cache::store(executable); - Ok(()) - } - - /// Return the refcount of the module. - #[cfg(test)] - pub fn refcount(&self) -> u64 { - self.refcount + code_cache::store(executable, false) } /// Decrement instruction_weights_version by 1. Panics if it is already 0. @@ -149,6 +255,14 @@ where } } +impl OwnerInfo { + /// Return the refcount of the module. + #[cfg(test)] + pub fn refcount(&self) -> u64 { + self.refcount + } +} + impl Executable for PrefabWasmModule where T::AccountId: UncheckedFrom + AsRef<[u8]>, @@ -158,22 +272,15 @@ where schedule: &Schedule, gas_meter: &mut GasMeter, ) -> Result { - code_cache::load(code_hash, Some((schedule, gas_meter))) - } - - fn from_storage_noinstr(code_hash: CodeHash) -> Result { - code_cache::load(code_hash, None) + code_cache::load(code_hash, schedule, gas_meter) } - fn add_user(code_hash: CodeHash, gas_meter: &mut GasMeter) -> Result<(), DispatchError> { - code_cache::increment_refcount::(code_hash, gas_meter) + fn add_user(code_hash: CodeHash) -> Result<(), DispatchError> { + code_cache::increment_refcount::(code_hash) } - fn remove_user( - code_hash: CodeHash, - gas_meter: &mut GasMeter, - ) -> Result<(), DispatchError> { - code_cache::decrement_refcount::(code_hash, gas_meter) + fn remove_user(code_hash: CodeHash) { + code_cache::decrement_refcount::(code_hash) } fn execute>( @@ -182,37 +289,35 @@ where function: &ExportedFunction, input_data: Vec, ) -> ExecResult { - let memory = - sp_sandbox::Memory::new(self.initial, Some(self.maximum)).unwrap_or_else(|_| { - // unlike `.expect`, explicit panic preserves the source location. - // Needed as we can't use `RUST_BACKTRACE` in here. - panic!( - "exec.prefab_module.initial can't be greater than exec.prefab_module.maximum; - thus Memory::new must not fail; - qed" - ) - }); - - let mut imports = sp_sandbox::EnvironmentDefinitionBuilder::new(); - imports.add_memory(self::prepare::IMPORT_MODULE_MEMORY, "memory", memory.clone()); - runtime::Env::impls(&mut |module, name, func_ptr| { - imports.add_host_func(module, name, func_ptr); - }); - - let mut runtime = Runtime::new(ext, input_data, memory); + let runtime = Runtime::new(ext, input_data); + let (mut store, memory, instance) = Self::instantiate::( + self.code.as_slice(), + runtime, + (self.initial, self.maximum), + StackLimits::default(), + ) + .map_err(|msg| { + log::debug!(target: "runtime::contracts", "failed to instantiate code: {}", msg); + Error::::CodeRejected + })?; + store.state_mut().set_memory(memory); + + let exported_func = instance + .get_export(&store, function.identifier()) + .and_then(|export| export.into_func()) + .ok_or_else(|| { + log::error!(target: "runtime::contracts", "failed to find entry point"); + Error::::CodeRejected + })?; // We store before executing so that the code hash is available in the constructor. - let code = self.code.clone(); if let &ExportedFunction::Constructor = function { - code_cache::store(self) + code_cache::store(self, true)?; } - // Instantiate the instance from the instrumented module code and invoke the contract - // entrypoint. - let result = sp_sandbox::Instance::new(&code, &imports, &mut runtime) - .and_then(|mut instance| instance.invoke(function.identifier(), &[], &mut runtime)); + let result = exported_func.call(&mut store, &[], &mut []); - runtime.to_execution_result(result) + store.into_state().to_execution_result(result) } fn code_hash(&self) -> &CodeHash { @@ -223,12 +328,8 @@ where self.code.len() as u32 } - fn aggregate_code_len(&self) -> u32 { - self.original_code_len.saturating_add(self.code_len()) - } - - fn refcount(&self) -> u32 { - self.refcount as u32 + fn is_deterministic(&self) -> bool { + matches!(self.determinism, Determinism::Deterministic) } } @@ -237,29 +338,34 @@ mod tests { use super::*; use crate::{ exec::{ - AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, SeedOf, StorageKey, + AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, FixSizedKey, + SeedOf, VarSizedKey, }, gas::GasMeter, - tests::{Call, Test, ALICE, BOB}, + storage::WriteOutcome, + tests::{RuntimeCall, Test, ALICE, BOB}, BalanceOf, CodeHash, Error, Pallet as Contracts, }; use assert_matches::assert_matches; use frame_support::{ - assert_ok, - dispatch::{DispatchResult, DispatchResultWithPostInfo}, - weights::Weight, + assert_err, assert_ok, + dispatch::DispatchResultWithPostInfo, + weights::{OldWeight, Weight}, }; - use hex_literal::hex; use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; use pretty_assertions::assert_eq; - use sp_core::{Bytes, H256}; + use sp_core::H256; use sp_runtime::DispatchError; - use std::{borrow::BorrowMut, cell::RefCell, collections::HashMap}; + use std::{ + borrow::BorrowMut, + cell::RefCell, + collections::hash_map::{Entry, HashMap}, + }; #[derive(Debug, PartialEq, Eq)] struct InstantiateEntry { code_hash: H256, - endowment: u64, + value: u64, data: Vec, gas_left: u64, salt: Vec, @@ -284,38 +390,48 @@ mod tests { allows_reentry: bool, } + #[derive(Debug, PartialEq, Eq)] + struct CallCodeEntry { + code_hash: H256, + data: Vec, + } + pub struct MockExt { - storage: HashMap>, + storage: HashMap, Vec>, instantiates: Vec, terminations: Vec, calls: Vec, + code_calls: Vec, transfers: Vec, // (topics, data) events: Vec<(Vec, Vec)>, - runtime_calls: RefCell>, + runtime_calls: RefCell>, schedule: Schedule, gas_meter: GasMeter, debug_buffer: Vec, ecdsa_recover: RefCell>, + code_hashes: Vec>, } /// The call is mocked and just returns this hardcoded value. - fn call_return_data() -> Bytes { - Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]) + fn call_return_data() -> Vec { + vec![0xDE, 0xAD, 0xBE, 0xEF] } impl Default for MockExt { fn default() -> Self { Self { + code_hashes: Default::default(), storage: Default::default(), instantiates: Default::default(), terminations: Default::default(), calls: Default::default(), + code_calls: Default::default(), transfers: Default::default(), events: Default::default(), runtime_calls: Default::default(), schedule: Default::default(), - gas_meter: GasMeter::new(10_000_000_000), + gas_meter: GasMeter::new(Weight::from_ref_time(10_000_000_000)), debug_buffer: Default::default(), ecdsa_recover: Default::default(), } @@ -336,26 +452,38 @@ mod tests { self.calls.push(CallEntry { to, value, data, allows_reentry }); Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: call_return_data() }) } + fn delegate_call( + &mut self, + code_hash: CodeHash, + data: Vec, + ) -> Result { + self.code_calls.push(CallCodeEntry { code_hash, data }); + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: call_return_data() }) + } fn instantiate( &mut self, gas_limit: Weight, code_hash: CodeHash, - endowment: u64, + value: u64, data: Vec, salt: &[u8], ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { self.instantiates.push(InstantiateEntry { - code_hash: code_hash.clone(), - endowment, + code_hash, + value, data: data.to_vec(), - gas_left: gas_limit, + gas_left: gas_limit.ref_time(), salt: salt.to_vec(), }); Ok(( Contracts::::contract_address(&ALICE, &code_hash, salt), - ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }, + ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }, )) } + fn set_code_hash(&mut self, hash: CodeHash) -> Result<(), DispatchError> { + self.code_hashes.push(hash); + Ok(()) + } fn transfer(&mut self, to: &AccountIdOf, value: u64) -> Result<(), DispatchError> { self.transfers.push(TransferEntry { to: to.clone(), value }); Ok(()) @@ -364,16 +492,72 @@ mod tests { self.terminations.push(TerminationEntry { beneficiary: beneficiary.clone() }); Ok(()) } - fn get_storage(&mut self, key: &StorageKey) -> Option> { - self.storage.get(key).cloned() + fn get_storage(&mut self, key: &FixSizedKey) -> Option> { + self.storage.get(&key.to_vec()).cloned() } - fn set_storage(&mut self, key: StorageKey, value: Option>) -> DispatchResult { - *self.storage.entry(key).or_insert(Vec::new()) = value.unwrap_or(Vec::new()); - Ok(()) + fn get_storage_transparent(&mut self, key: &VarSizedKey) -> Option> { + self.storage.get(&key.to_vec()).cloned() + } + fn get_storage_size(&mut self, key: &FixSizedKey) -> Option { + self.storage.get(&key.to_vec()).map(|val| val.len() as u32) + } + fn get_storage_size_transparent(&mut self, key: &VarSizedKey) -> Option { + self.storage.get(&key.to_vec()).map(|val| val.len() as u32) + } + fn set_storage( + &mut self, + key: &FixSizedKey, + value: Option>, + take_old: bool, + ) -> Result { + let key = key.to_vec(); + let entry = self.storage.entry(key.clone()); + let result = match (entry, take_old) { + (Entry::Vacant(_), _) => WriteOutcome::New, + (Entry::Occupied(entry), false) => + WriteOutcome::Overwritten(entry.remove().len() as u32), + (Entry::Occupied(entry), true) => WriteOutcome::Taken(entry.remove()), + }; + if let Some(value) = value { + self.storage.insert(key, value); + } + Ok(result) + } + fn set_storage_transparent( + &mut self, + key: &VarSizedKey, + value: Option>, + take_old: bool, + ) -> Result { + let key = key.to_vec(); + let entry = self.storage.entry(key.clone()); + let result = match (entry, take_old) { + (Entry::Vacant(_), _) => WriteOutcome::New, + (Entry::Occupied(entry), false) => + WriteOutcome::Overwritten(entry.remove().len() as u32), + (Entry::Occupied(entry), true) => WriteOutcome::Taken(entry.remove()), + }; + if let Some(value) = value { + self.storage.insert(key, value); + } + Ok(result) } fn caller(&self) -> &AccountIdOf { &ALICE } + fn is_contract(&self, _address: &AccountIdOf) -> bool { + true + } + fn code_hash(&self, _address: &AccountIdOf) -> Option> { + Some(H256::from_slice(&[0x11; 32])) + } + fn own_code_hash(&mut self) -> &CodeHash { + const HASH: H256 = H256::repeat_byte(0x10); + &HASH + } + fn caller_is_origin(&self) -> bool { + false + } fn address(&self) -> &AccountIdOf { &BOB } @@ -389,9 +573,6 @@ mod tests { fn minimum_balance(&self) -> u64 { 666 } - fn contract_deposit(&self) -> u64 { - 16 - } fn random(&self, subject: &[u8]) -> (SeedOf, BlockNumberOf) { (H256::from_slice(subject), 42) } @@ -405,7 +586,7 @@ mod tests { 16_384 } fn get_weight_price(&self, weight: Weight) -> BalanceOf { - BalanceOf::::from(1312_u32).saturating_mul(weight.into()) + BalanceOf::::from(1312_u32).saturating_mul(weight.ref_time().into()) } fn schedule(&self) -> &Schedule { &self.schedule @@ -413,33 +594,76 @@ mod tests { fn gas_meter(&mut self) -> &mut GasMeter { &mut self.gas_meter } - fn append_debug_buffer(&mut self, msg: &str) -> bool { + fn append_debug_buffer(&mut self, msg: &str) -> Result { self.debug_buffer.extend(msg.as_bytes()); - true + Ok(true) } - fn call_runtime(&self, call: ::Call) -> DispatchResultWithPostInfo { + fn call_runtime( + &self, + call: ::RuntimeCall, + ) -> DispatchResultWithPostInfo { self.runtime_calls.borrow_mut().push(call); Ok(Default::default()) } - fn ecdsa_recover( &self, signature: &[u8; 65], message_hash: &[u8; 32], ) -> Result<[u8; 33], ()> { - self.ecdsa_recover.borrow_mut().push((signature.clone(), message_hash.clone())); + self.ecdsa_recover.borrow_mut().push((*signature, *message_hash)); Ok([3; 33]) } + fn contract_info(&mut self) -> &mut crate::ContractInfo { + unimplemented!() + } + fn ecdsa_to_eth_address(&self, _pk: &[u8; 33]) -> Result<[u8; 20], ()> { + Ok([2u8; 20]) + } + fn reentrance_count(&self) -> u32 { + 12 + } + fn account_reentrance_count(&self, _account_id: &AccountIdOf) -> u32 { + 12 + } + fn nonce(&mut self) -> u64 { + 995 + } } - fn execute>(wat: &str, input_data: Vec, mut ext: E) -> ExecResult { + fn execute_internal>( + wat: &str, + input_data: Vec, + mut ext: E, + unstable_interface: bool, + ) -> ExecResult { + type RuntimeConfig = ::T; + RuntimeConfig::set_unstable_interface(unstable_interface); let wasm = wat::parse_str(wat).unwrap(); let schedule = crate::Schedule::default(); - let executable = - PrefabWasmModule::<::T>::from_code(wasm, &schedule).unwrap(); + let executable = PrefabWasmModule::::from_code( + wasm, + &schedule, + ALICE, + Determinism::Deterministic, + TryInstantiate::Skip, + ) + .map_err(|err| err.0)?; executable.execute(ext.borrow_mut(), &ExportedFunction::Call, input_data) } + fn execute>(wat: &str, input_data: Vec, ext: E) -> ExecResult { + execute_internal(wat, input_data, ext, true) + } + + #[cfg(not(feature = "runtime-benchmarks"))] + fn execute_no_unstable>( + wat: &str, + input_data: Vec, + ext: E, + ) -> ExecResult { + execute_internal(wat, input_data, ext, false) + } + const CODE_TRANSFER: &str = r#" (module ;; seal_transfer( @@ -540,11 +764,56 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] + fn contract_delegate_call() { + const CODE: &str = r#" +(module + ;; seal_delegate_call( + ;; flags: u32, + ;; code_hash_ptr: u32, + ;; input_data_ptr: u32, + ;; input_data_len: u32, + ;; output_ptr: u32, + ;; output_len_ptr: u32 + ;;) -> u32 + (import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (drop + (call $seal_delegate_call + (i32.const 0) ;; No flags are set + (i32.const 4) ;; Pointer to "callee" code_hash. + (i32.const 36) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + (i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + ) + ) + (func (export "deploy")) + + ;; Callee code_hash + (data (i32.const 4) + "\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11" + "\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11" + ) + + (data (i32.const 36) "\01\02\03\04") +) +"#; + let mut mock_ext = MockExt::default(); + assert_ok!(execute(CODE, vec![], &mut mock_ext)); + + assert_eq!( + &mock_ext.code_calls, + &[CallCodeEntry { code_hash: [0x11; 32].into(), data: vec![1, 2, 3, 4] }] + ); + } + + #[test] fn contract_call_forward_input() { const CODE: &str = r#" (module - (import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "env" "memory" (memory 1 1)) (func (export "call") @@ -583,10 +852,7 @@ mod tests { "#; let mut mock_ext = MockExt::default(); let input = vec![0xff, 0x2a, 0x99, 0x88]; - frame_support::assert_err!( - execute(CODE, input.clone(), &mut mock_ext), - >::InputForwarded, - ); + assert_err!(execute(CODE, input.clone(), &mut mock_ext), >::InputForwarded,); assert_eq!( &mock_ext.calls, @@ -595,11 +861,10 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn contract_call_clone_input() { const CODE: &str = r#" (module - (import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) @@ -643,7 +908,7 @@ mod tests { let mut mock_ext = MockExt::default(); let input = vec![0xff, 0x2a, 0x99, 0x88]; let result = execute(CODE, input.clone(), &mut mock_ext).unwrap(); - assert_eq!(result.data.0, input); + assert_eq!(result.data, input); assert_eq!( &mock_ext.calls, &[CallEntry { to: ALICE, value: 0x2a, data: input, allows_reentry: true }] @@ -651,11 +916,10 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn contract_call_tail_call() { const CODE: &str = r#" (module - (import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (func (export "call") (drop @@ -698,6 +962,83 @@ mod tests { ); } + #[test] + fn contains_storage_works() { + const CODE: &str = r#" +(module + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal1" "contains_storage" (func $contains_storage (param i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + + ;; size of input buffer + ;; [0, 4) size of input buffer (128+32 = 160 bytes = 0xA0) + (data (i32.const 0) "\A0") + + ;; [4, 164) input buffer + + (func (export "call") + ;; Receive key + (call $seal_input + (i32.const 4) ;; Where we take input and store it + (i32.const 0) ;; Where we take and store the length of the data + ) + ;; Call seal_clear_storage and save what it returns at 0 + (i32.store (i32.const 0) + (call $contains_storage + (i32.const 8) ;; key_ptr + (i32.load (i32.const 4)) ;; key_len + ) + ) + (call $seal_return + (i32.const 0) ;; flags + (i32.const 0) ;; returned value + (i32.const 4) ;; length of returned value + ) + ) + + (func (export "deploy")) +) +"#; + + let mut ext = MockExt::default(); + ext.set_storage_transparent( + &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + Some(vec![42u8]), + false, + ) + .unwrap(); + ext.set_storage_transparent( + &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + Some(vec![]), + false, + ) + .unwrap(); + + //value does not exist (wrong key length) + let input = (63, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // sentinel returned + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), crate::SENTINEL); + + // value exists + let input = (64, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // true as u32 returned + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 1); + // getter does not remove the value from storage + assert_eq!(ext.storage.get(&[1u8; 64].to_vec()).unwrap(), &[42u8]); + + // value exists (test for 0 sized) + let input = (19, [2u8; 19]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // true as u32 returned + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 0); + // getter does not remove the value from storage + assert_eq!(ext.storage.get(&[2u8; 19].to_vec()).unwrap(), &([] as [u8; 0])); + } + const CODE_INSTANTIATE: &str = r#" (module ;; seal_instantiate( @@ -763,7 +1104,7 @@ mod tests { &mock_ext.instantiates[..], [InstantiateEntry { code_hash, - endowment: 3, + value: 3, data, gas_left: _, salt, @@ -862,7 +1203,6 @@ mod tests { ); } - #[cfg(feature = "unstable-interface")] const CODE_ECDSA_RECOVER: &str = r#" (module ;; seal_ecdsa_recover( @@ -870,7 +1210,7 @@ mod tests { ;; message_hash_ptr: u32, ;; output_ptr: u32 ;; ) -> u32 - (import "__unstable__" "seal_ecdsa_recover" (func $seal_ecdsa_recover (param i32 i32 i32) (result i32))) + (import "seal0" "seal_ecdsa_recover" (func $seal_ecdsa_recover (param i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (func (export "call") (drop @@ -900,13 +1240,47 @@ mod tests { "#; #[test] - #[cfg(feature = "unstable-interface")] fn contract_ecdsa_recover() { let mut mock_ext = MockExt::default(); assert_ok!(execute(&CODE_ECDSA_RECOVER, vec![], &mut mock_ext)); assert_eq!(mock_ext.ecdsa_recover.into_inner(), [([1; 65], [1; 32])]); } + #[test] + fn contract_ecdsa_to_eth_address() { + /// calls `seal_ecdsa_to_eth_address` for the contstant and ensures the result equals the + /// expected one. + const CODE_ECDSA_TO_ETH_ADDRESS: &str = r#" +(module + (import "seal0" "seal_ecdsa_to_eth_address" (func $seal_ecdsa_to_eth_address (param i32 i32) (result i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "call") + ;; fill the buffer with the eth address. + (call $seal_ecdsa_to_eth_address (i32.const 0) (i32.const 0)) + + ;; Return the contents of the buffer + (call $seal_return + (i32.const 0) + (i32.const 0) + (i32.const 20) + ) + + ;; seal_return doesn't return, so this is effectively unreachable. + (unreachable) + ) + (func (export "deploy")) +) +"#; + + let output = execute(CODE_ECDSA_TO_ETH_ADDRESS, vec![], MockExt::default()).unwrap(); + assert_eq!( + output, + ExecReturnValue { flags: ReturnFlags::empty(), data: [0x02; 20].to_vec() } + ); + } + const CODE_GET_STORAGE: &str = r#" (module (import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32))) @@ -919,8 +1293,8 @@ mod tests { "\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11" ) - ;; [32, 36) buffer size = 128 bytes - (data (i32.const 32) "\80") + ;; [32, 36) buffer size = 4k in little endian + (data (i32.const 32) "\00\10") ;; [36; inf) buffer where the result is copied @@ -974,17 +1348,17 @@ mod tests { #[test] fn get_storage_puts_data_into_buf() { let mut mock_ext = MockExt::default(); - mock_ext.storage.insert([0x11; 32], [0x22; 32].to_vec()); + mock_ext.storage.insert([0x11; 32].to_vec(), [0x22; 32].to_vec()); let output = execute(CODE_GET_STORAGE, vec![], mock_ext).unwrap(); assert_eq!( output, - ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes([0x22; 32].to_vec()) } + ExecReturnValue { flags: ReturnFlags::empty(), data: [0x22; 32].to_vec() } ); } - /// calls `seal_caller` and compares the result with the constant 42. + /// calls `seal_caller` and compares the result with the constant (ALICE's address part). const CODE_CALLER: &str = r#" (module (import "seal0" "seal_caller" (func $seal_caller (param i32 i32))) @@ -1014,7 +1388,7 @@ mod tests { ) ) - ;; assert that the first 64 byte are the beginning of "ALICE" + ;; assert that the first 8 bytes are the beginning of "ALICE" (call $assert (i64.eq (i64.load (i32.const 0)) @@ -1032,7 +1406,7 @@ mod tests { assert_ok!(execute(CODE_CALLER, vec![], MockExt::default())); } - /// calls `seal_address` and compares the result with the constant 69. + /// calls `seal_address` and compares the result with the constant (BOB's address part). const CODE_ADDRESS: &str = r#" (module (import "seal0" "seal_address" (func $seal_address (param i32 i32))) @@ -1062,7 +1436,7 @@ mod tests { ) ) - ;; assert that the first 64 byte are the beginning of "BOB" + ;; assert that the first 8 bytes are the beginning of "BOB" (call $assert (i64.eq (i64.load (i32.const 0)) @@ -1218,10 +1592,11 @@ mod tests { let output = execute(CODE_GAS_LEFT, vec![], &mut ext).unwrap(); - let gas_left = Weight::decode(&mut &*output.data).unwrap(); + let OldWeight(gas_left) = OldWeight::decode(&mut &*output.data).unwrap(); let actual_left = ext.gas_meter.gas_left(); - assert!(gas_left < gas_limit, "gas_left must be less than initial"); - assert!(gas_left > actual_left, "gas_left must be greater than final"); + // TODO: account for proof size weight + assert!(gas_left < gas_limit.ref_time(), "gas_left must be less than initial"); + assert!(gas_left > actual_left.ref_time(), "gas_left must be greater than final"); } const CODE_VALUE_TRANSFERRED: &str = r#" @@ -1270,38 +1645,32 @@ mod tests { assert_ok!(execute(CODE_VALUE_TRANSFERRED, vec![], MockExt::default())); } - const CODE_RETURN_FROM_START_FN: &str = r#" + const START_FN_ILLEGAL: &str = r#" (module (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (start $start) (func $start - (call $seal_return - (i32.const 0) - (i32.const 8) - (i32.const 4) - ) (unreachable) ) (func (export "call") (unreachable) ) - (func (export "deploy")) + + (func (export "deploy") + (unreachable) + ) (data (i32.const 8) "\01\02\03\04") ) "#; #[test] - fn return_from_start_fn() { - let output = execute(CODE_RETURN_FROM_START_FN, vec![], MockExt::default()).unwrap(); - - assert_eq!( - output, - ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(vec![1, 2, 3, 4]) } - ); + fn start_fn_illegal() { + let output = execute(START_FN_ILLEGAL, vec![], MockExt::default()); + assert_err!(output, >::CodeRejected,); } const CODE_TIMESTAMP_NOW: &str = r#" @@ -1345,14 +1714,9 @@ mod tests { ) "#; - #[test] - fn now() { - assert_ok!(execute(CODE_TIMESTAMP_NOW, vec![], MockExt::default())); - } - - const CODE_MINIMUM_BALANCE: &str = r#" + const CODE_TIMESTAMP_NOW_UNPREFIXED: &str = r#" (module - (import "seal0" "seal_minimum_balance" (func $seal_minimum_balance (param i32 i32))) + (import "seal0" "now" (func $now (param i32 i32))) (import "env" "memory" (memory 1 1)) ;; size of our buffer is 32 bytes @@ -1368,7 +1732,8 @@ mod tests { ) (func (export "call") - (call $seal_minimum_balance (i32.const 0) (i32.const 32)) + ;; This stores the block timestamp in the buffer + (call $now (i32.const 0) (i32.const 32)) ;; assert len == 8 (call $assert @@ -1378,11 +1743,11 @@ mod tests { ) ) - ;; assert that contents of the buffer is equal to the i64 value of 666. + ;; assert that contents of the buffer is equal to the i64 value of 1111. (call $assert (i64.eq (i64.load (i32.const 0)) - (i64.const 666) + (i64.const 1111) ) ) ) @@ -1391,13 +1756,14 @@ mod tests { "#; #[test] - fn minimum_balance() { - assert_ok!(execute(CODE_MINIMUM_BALANCE, vec![], MockExt::default())); + fn now() { + assert_ok!(execute(CODE_TIMESTAMP_NOW, vec![], MockExt::default())); + assert_ok!(execute(CODE_TIMESTAMP_NOW_UNPREFIXED, vec![], MockExt::default())); } - const CODE_CONTRACT_DEPOSIT: &str = r#" + const CODE_MINIMUM_BALANCE: &str = r#" (module - (import "seal0" "seal_contract_deposit" (func $seal_contract_deposit (param i32 i32))) + (import "seal0" "seal_minimum_balance" (func $seal_minimum_balance (param i32 i32))) (import "env" "memory" (memory 1 1)) ;; size of our buffer is 32 bytes @@ -1413,7 +1779,7 @@ mod tests { ) (func (export "call") - (call $seal_contract_deposit (i32.const 0) (i32.const 32)) + (call $seal_minimum_balance (i32.const 0) (i32.const 32)) ;; assert len == 8 (call $assert @@ -1423,11 +1789,11 @@ mod tests { ) ) - ;; assert that contents of the buffer is equal to the i64 value of 16. + ;; assert that contents of the buffer is equal to the i64 value of 666. (call $assert (i64.eq (i64.load (i32.const 0)) - (i64.const 16) + (i64.const 666) ) ) ) @@ -1436,8 +1802,8 @@ mod tests { "#; #[test] - fn contract_deposit() { - assert_ok!(execute(CODE_CONTRACT_DEPOSIT, vec![], MockExt::default())); + fn minimum_balance() { + assert_ok!(execute(CODE_MINIMUM_BALANCE, vec![], MockExt::default())); } const CODE_RANDOM: &str = r#" @@ -1503,10 +1869,9 @@ mod tests { output, ExecReturnValue { flags: ReturnFlags::empty(), - data: Bytes( - hex!("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F") - .to_vec() - ), + data: array_bytes::hex_into_unchecked( + "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + ) }, ); } @@ -1574,13 +1939,13 @@ mod tests { output, ExecReturnValue { flags: ReturnFlags::empty(), - data: Bytes( - ( - hex!("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"), - 42u64, - ) - .encode() - ), + data: ( + array_bytes::hex2array_unchecked::<32>( + "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + ), + 42u64, + ) + .encode() }, ); } @@ -1621,7 +1986,7 @@ mod tests { )] ); - assert!(mock_ext.gas_meter.gas_left() > 0); + assert!(mock_ext.gas_meter.gas_left().ref_time() > 0); } const CODE_DEPOSIT_EVENT_MAX_TOPICS: &str = r#" @@ -1785,7 +2150,7 @@ mod tests { fn seal_return_with_success_status() { let output = execute( CODE_RETURN_WITH_DATA, - hex!("00000000445566778899").to_vec(), + array_bytes::hex2bytes_unchecked("00000000445566778899"), MockExt::default(), ) .unwrap(); @@ -1794,26 +2159,29 @@ mod tests { output, ExecReturnValue { flags: ReturnFlags::empty(), - data: Bytes(hex!("445566778899").to_vec()), + data: array_bytes::hex2bytes_unchecked("445566778899"), } ); - assert!(output.is_success()); + assert!(!output.did_revert()); } #[test] fn return_with_revert_status() { - let output = - execute(CODE_RETURN_WITH_DATA, hex!("010000005566778899").to_vec(), MockExt::default()) - .unwrap(); + let output = execute( + CODE_RETURN_WITH_DATA, + array_bytes::hex2bytes_unchecked("010000005566778899"), + MockExt::default(), + ) + .unwrap(); assert_eq!( output, ExecReturnValue { flags: ReturnFlags::REVERT, - data: Bytes(hex!("5566778899").to_vec()), + data: array_bytes::hex2bytes_unchecked("5566778899"), } ); - assert!(!output.is_success()); + assert!(output.did_revert()); } const CODE_OUT_OF_BOUNDS_ACCESS: &str = r#" @@ -1928,10 +2296,9 @@ mod tests { ); } - #[cfg(feature = "unstable-interface")] const CODE_CALL_RUNTIME: &str = r#" (module - (import "__unstable__" "seal_call_runtime" (func $seal_call_runtime (param i32 i32) (result i32))) + (import "seal0" "call_runtime" (func $call_runtime (param i32 i32) (result i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) @@ -1948,7 +2315,7 @@ mod tests { ) ;; Just use the call passed as input and store result to memory (i32.store (i32.const 0) - (call $seal_call_runtime + (call $call_runtime (i32.const 4) ;; Pointer where the call is stored (i32.load (i32.const 0)) ;; Size of the call ) @@ -1965,19 +2332,17 @@ mod tests { "#; #[test] - #[cfg(feature = "unstable-interface")] fn call_runtime_works() { - use std::convert::TryInto; - let call = Call::System(frame_system::Call::remark { remark: b"Hello World".to_vec() }); + let call = + RuntimeCall::System(frame_system::Call::remark { remark: b"Hello World".to_vec() }); let mut ext = MockExt::default(); let result = execute(CODE_CALL_RUNTIME, call.encode(), &mut ext).unwrap(); assert_eq!(*ext.runtime_calls.borrow(), vec![call]); // 0 = ReturnCode::Success - assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0); + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 0); } #[test] - #[cfg(feature = "unstable-interface")] fn call_runtime_panics_on_invalid_call() { let mut ext = MockExt::default(); let result = execute(CODE_CALL_RUNTIME, vec![0x42], &mut ext); @@ -1990,4 +2355,668 @@ mod tests { ); assert_eq!(*ext.runtime_calls.borrow(), vec![]); } + + #[test] + fn set_storage_works() { + const CODE: &str = r#" +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "seal2" "set_storage" (func $set_storage (param i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 4) size of input buffer + ;; 4k in little endian + (data (i32.const 0) "\00\10") + + ;; [4, 4100) input buffer + + (func (export "call") + ;; Receive (key ++ value_to_write) + (call $seal_input + (i32.const 4) ;; Pointer to the input buffer + (i32.const 0) ;; Size of the input buffer + ) + ;; Store the passed value to the passed key and store result to memory + (i32.store (i32.const 168) + (call $set_storage + (i32.const 8) ;; key_ptr + (i32.load (i32.const 4)) ;; key_len + (i32.add ;; value_ptr = 8 + key_len + (i32.const 8) + (i32.load (i32.const 4))) + (i32.sub ;; value_len (input_size - (key_len + key_len_len)) + (i32.load (i32.const 0)) + (i32.add + (i32.load (i32.const 4)) + (i32.const 4) + ) + ) + ) + ) + (call $seal_return + (i32.const 0) ;; flags + (i32.const 168) ;; ptr to returned value + (i32.const 4) ;; length of returned value + ) + ) + + (func (export "deploy")) +) +"#; + + let mut ext = MockExt::default(); + + // value did not exist before -> sentinel returned + let input = (32, [1u8; 32], [42u8, 48]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), crate::SENTINEL); + assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[42u8, 48]); + + // value do exist -> length of old value returned + let input = (32, [1u8; 32], [0u8; 0]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 2); + assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[0u8; 0]); + + // value do exist -> length of old value returned (test for zero sized val) + let input = (32, [1u8; 32], [99u8]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 0); + assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[99u8]); + } + + #[test] + fn get_storage_works() { + const CODE: &str = r#" +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "seal1" "get_storage" (func $get_storage (param i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 4) size of input buffer (160 bytes as we copy the key+len here) + (data (i32.const 0) "\A0") + + ;; [4, 8) size of output buffer + ;; 4k in little endian + (data (i32.const 4) "\00\10") + + ;; [8, 168) input buffer + ;; [168, 4264) output buffer + + (func (export "call") + ;; Receive (key ++ value_to_write) + (call $seal_input + (i32.const 8) ;; Pointer to the input buffer + (i32.const 0) ;; Size of the input buffer + ) + ;; Load a storage value and result of this call into the output buffer + (i32.store (i32.const 168) + (call $get_storage + (i32.const 12) ;; key_ptr + (i32.load (i32.const 8)) ;; key_len + (i32.const 172) ;; Pointer to the output buffer + (i32.const 4) ;; Pointer to the size of the buffer + ) + ) + (call $seal_return + (i32.const 0) ;; flags + (i32.const 168) ;; output buffer ptr + (i32.add ;; length: output size + 4 (retval) + (i32.load (i32.const 4)) + (i32.const 4) + ) + ) + ) + + (func (export "deploy")) +) +"#; + + let mut ext = MockExt::default(); + + ext.set_storage_transparent( + &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + Some(vec![42u8]), + false, + ) + .unwrap(); + + ext.set_storage_transparent( + &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + Some(vec![]), + false, + ) + .unwrap(); + + // value does not exist + let input = (63, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!( + u32::from_le_bytes(result.data[0..4].try_into().unwrap()), + ReturnCode::KeyNotFound as u32 + ); + + // value exists + let input = (64, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!( + u32::from_le_bytes(result.data[0..4].try_into().unwrap()), + ReturnCode::Success as u32 + ); + assert_eq!(ext.storage.get(&[1u8; 64].to_vec()).unwrap(), &[42u8]); + assert_eq!(&result.data[4..], &[42u8]); + + // value exists (test for 0 sized) + let input = (19, [2u8; 19]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!( + u32::from_le_bytes(result.data[0..4].try_into().unwrap()), + ReturnCode::Success as u32 + ); + assert_eq!(ext.storage.get(&[2u8; 19].to_vec()), Some(&vec![])); + assert_eq!(&result.data[4..], &([] as [u8; 0])); + } + + #[test] + fn clear_storage_works() { + const CODE: &str = r#" +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "seal1" "clear_storage" (func $clear_storage (param i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; size of input buffer + ;; [0, 4) size of input buffer (128+32 = 160 bytes = 0xA0) + (data (i32.const 0) "\A0") + + ;; [4, 164) input buffer + + (func (export "call") + ;; Receive key + (call $seal_input + (i32.const 4) ;; Where we take input and store it + (i32.const 0) ;; Where we take and store the length of thedata + ) + ;; Call seal_clear_storage and save what it returns at 0 + (i32.store (i32.const 0) + (call $clear_storage + (i32.const 8) ;; key_ptr + (i32.load (i32.const 4)) ;; key_len + ) + ) + (call $seal_return + (i32.const 0) ;; flags + (i32.const 0) ;; returned value + (i32.const 4) ;; length of returned value + ) + ) + + (func (export "deploy")) +) +"#; + + let mut ext = MockExt::default(); + + ext.set_storage_transparent( + &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + Some(vec![42u8]), + false, + ) + .unwrap(); + ext.set_storage_transparent( + &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + Some(vec![]), + false, + ) + .unwrap(); + + // value did not exist + let input = (32, [3u8; 32]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // sentinel returned + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), crate::SENTINEL); + assert_eq!(ext.storage.get(&[3u8; 32].to_vec()), None); + + // value did exist + let input = (64, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // length returned + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 1); + // value cleared + assert_eq!(ext.storage.get(&[1u8; 64].to_vec()), None); + + //value did not exist (wrong key length) + let input = (63, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // sentinel returned + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), crate::SENTINEL); + assert_eq!(ext.storage.get(&[1u8; 64].to_vec()), None); + + // value exists + let input = (19, [2u8; 19]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // length returned (test for 0 sized) + assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 0); + // value cleared + assert_eq!(ext.storage.get(&[2u8; 19].to_vec()), None); + } + + #[test] + fn take_storage_works() { + const CODE: &str = r#" +(module + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "take_storage" (func $take_storage (param i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 4) size of input buffer (160 bytes as we copy the key+len here) + (data (i32.const 0) "\A0") + + ;; [4, 8) size of output buffer + ;; 4k in little endian + (data (i32.const 4) "\00\10") + + ;; [8, 168) input buffer + ;; [168, 4264) output buffer + + (func (export "call") + ;; Receive key + (call $seal_input + (i32.const 8) ;; Pointer to the input buffer + (i32.const 0) ;; Size of the length buffer + ) + + ;; Load a storage value and result of this call into the output buffer + (i32.store (i32.const 168) + (call $take_storage + (i32.const 12) ;; key_ptr + (i32.load (i32.const 8)) ;; key_len + (i32.const 172) ;; Pointer to the output buffer + (i32.const 4) ;; Pointer to the size of the buffer + ) + ) + + ;; Return the contents of the buffer + (call $seal_return + (i32.const 0) ;; flags + (i32.const 168) ;; output buffer ptr + (i32.add ;; length: storage size + 4 (retval) + (i32.load (i32.const 4)) + (i32.const 4) + ) + ) + ) + + (func (export "deploy")) +) +"#; + + let mut ext = MockExt::default(); + + ext.set_storage_transparent( + &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + Some(vec![42u8]), + false, + ) + .unwrap(); + + ext.set_storage_transparent( + &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + Some(vec![]), + false, + ) + .unwrap(); + + // value does not exist -> error returned + let input = (63, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!( + u32::from_le_bytes(result.data[0..4].try_into().unwrap()), + ReturnCode::KeyNotFound as u32 + ); + + // value did exist -> value returned + let input = (64, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!( + u32::from_le_bytes(result.data[0..4].try_into().unwrap()), + ReturnCode::Success as u32 + ); + assert_eq!(ext.storage.get(&[1u8; 64].to_vec()), None); + assert_eq!(&result.data[4..], &[42u8]); + + // value did exist -> length returned (test for 0 sized) + let input = (19, [2u8; 19]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!( + u32::from_le_bytes(result.data[0..4].try_into().unwrap()), + ReturnCode::Success as u32 + ); + assert_eq!(ext.storage.get(&[2u8; 19].to_vec()), None); + assert_eq!(&result.data[4..], &[0u8; 0]); + } + + #[test] + fn is_contract_works() { + const CODE_IS_CONTRACT: &str = r#" +;; This runs `is_contract` check on zero account address +(module + (import "seal0" "seal_is_contract" (func $seal_is_contract (param i32) (result i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 32) zero-adress + (data (i32.const 0) + "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" + "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" + ) + + ;; [32, 36) here we store the return code of the `seal_is_contract` + + (func (export "deploy")) + + (func (export "call") + (i32.store + (i32.const 32) + (call $seal_is_contract + (i32.const 0) ;; ptr to destination address + ) + ) + ;; exit with success and take `seal_is_contract` return code to the output buffer + (call $seal_return (i32.const 0) (i32.const 32) (i32.const 4)) + ) +) +"#; + let output = execute(CODE_IS_CONTRACT, vec![], MockExt::default()).unwrap(); + + // The mock ext just always returns 1u32 (`true`). + assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: 1u32.encode() },); + } + + #[test] + fn code_hash_works() { + /// calls `seal_code_hash` and compares the result with the constant. + const CODE_CODE_HASH: &str = r#" +(module + (import "seal0" "seal_code_hash" (func $seal_code_hash (param i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; size of our buffer is 32 bytes + (data (i32.const 32) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; fill the buffer with the code hash. + (call $seal_code_hash + (i32.const 0) ;; input: address_ptr (before call) + (i32.const 0) ;; output: code_hash_ptr (after call) + (i32.const 32) ;; same 32 bytes length for input and output + ) + + ;; assert size == 32 + (call $assert + (i32.eq + (i32.load (i32.const 32)) + (i32.const 32) + ) + ) + + ;; assert that the first 8 bytes are "1111111111111111" + (call $assert + (i64.eq + (i64.load (i32.const 0)) + (i64.const 0x1111111111111111) + ) + ) + drop + ) + + (func (export "deploy")) +) +"#; + assert_ok!(execute(CODE_CODE_HASH, vec![], MockExt::default())); + } + + #[test] + fn own_code_hash_works() { + /// calls `seal_own_code_hash` and compares the result with the constant. + const CODE_OWN_CODE_HASH: &str = r#" +(module + (import "seal0" "seal_own_code_hash" (func $seal_own_code_hash (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; size of our buffer is 32 bytes + (data (i32.const 32) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; fill the buffer with the code hash + (call $seal_own_code_hash + (i32.const 0) ;; output: code_hash_ptr + (i32.const 32) ;; 32 bytes length of code_hash output + ) + + ;; assert size == 32 + (call $assert + (i32.eq + (i32.load (i32.const 32)) + (i32.const 32) + ) + ) + + ;; assert that the first 8 bytes are "1010101010101010" + (call $assert + (i64.eq + (i64.load (i32.const 0)) + (i64.const 0x1010101010101010) + ) + ) + ) + + (func (export "deploy")) +) +"#; + assert_ok!(execute(CODE_OWN_CODE_HASH, vec![], MockExt::default())); + } + + #[test] + fn caller_is_origin_works() { + const CODE_CALLER_IS_ORIGIN: &str = r#" +;; This runs `caller_is_origin` check on zero account address +(module + (import "seal0" "seal_caller_is_origin" (func $seal_caller_is_origin (result i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 4) here the return code of the `seal_caller_is_origin` will be stored + ;; we initialize it with non-zero value to be sure that it's being overwritten below + (data (i32.const 0) "\10\10\10\10") + + (func (export "deploy")) + + (func (export "call") + (i32.store + (i32.const 0) + (call $seal_caller_is_origin) + ) + ;; exit with success and take `seal_caller_is_origin` return code to the output buffer + (call $seal_return (i32.const 0) (i32.const 0) (i32.const 4)) + ) +) +"#; + let output = execute(CODE_CALLER_IS_ORIGIN, vec![], MockExt::default()).unwrap(); + + // The mock ext just always returns 0u32 (`false`) + assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: 0u32.encode() },); + } + + #[test] + fn set_code_hash() { + const CODE: &str = r#" +(module + (import "seal0" "seal_set_code_hash" (func $seal_set_code_hash (param i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (local $exit_code i32) + (set_local $exit_code + (call $seal_set_code_hash (i32.const 0)) + ) + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0)) ;; ReturnCode::Success + ) + ) + + (func (export "deploy")) + + ;; Hash of code. + (data (i32.const 0) + "\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11" + "\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11" + ) +) +"#; + + let mut mock_ext = MockExt::default(); + execute(CODE, [0u8; 32].encode(), &mut mock_ext).unwrap(); + + assert_eq!(mock_ext.code_hashes.pop().unwrap(), H256::from_slice(&[17u8; 32])); + } + + #[test] + fn reentrance_count_works() { + const CODE: &str = r#" +(module + (import "seal0" "reentrance_count" (func $reentrance_count (result i32))) + (import "env" "memory" (memory 1 1)) + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (local $return_val i32) + (set_local $return_val + (call $reentrance_count) + ) + (call $assert + (i32.eq (get_local $return_val) (i32.const 12)) + ) + ) + + (func (export "deploy")) +) +"#; + + let mut mock_ext = MockExt::default(); + execute(CODE, vec![], &mut mock_ext).unwrap(); + } + + #[test] + fn account_reentrance_count_works() { + const CODE: &str = r#" +(module + (import "seal0" "account_reentrance_count" (func $account_reentrance_count (param i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (local $return_val i32) + (set_local $return_val + (call $account_reentrance_count (i32.const 0)) + ) + (call $assert + (i32.eq (get_local $return_val) (i32.const 12)) + ) + ) + + (func (export "deploy")) +) +"#; + + let mut mock_ext = MockExt::default(); + execute(CODE, vec![], &mut mock_ext).unwrap(); + } + + #[test] + fn instantiation_nonce_works() { + const CODE: &str = r#" +(module + (import "seal0" "instantiation_nonce" (func $nonce (result i64))) + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (call $assert + (i64.eq (call $nonce) (i64.const 995)) + ) + ) + (func (export "deploy")) +) +"#; + + let mut mock_ext = MockExt::default(); + execute(CODE, vec![], &mut mock_ext).unwrap(); + } + + /// This test check that an unstable interface cannot be deployed. In case of runtime + /// benchmarks we always allow unstable interfaces. This is why this test does not + /// work when this feature is enabled. + #[cfg(not(feature = "runtime-benchmarks"))] + #[test] + fn cannot_deploy_unstable() { + const CANNOT_DEPLOY_UNSTABLE: &str = r#" +(module + (import "seal0" "reentrance_count" (func $reentrance_count (result i32))) + (func (export "call")) + (func (export "deploy")) +) +"#; + assert_err!( + execute_no_unstable(CANNOT_DEPLOY_UNSTABLE, vec![], MockExt::default()), + >::CodeRejected, + ); + assert_ok!(execute(CANNOT_DEPLOY_UNSTABLE, vec![], MockExt::default())); + } } diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index c766914f3d46e..c63a5b1e135d9 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,17 +21,40 @@ use crate::{ chain_extension::ChainExtension, - wasm::{env_def::ImportSatisfyCheck, PrefabWasmModule}, - Config, Schedule, + storage::meter::Diff, + wasm::{Determinism, Environment, OwnerInfo, PrefabWasmModule}, + AccountIdOf, CodeVec, Config, Error, Schedule, }; -use pwasm_utils::parity_wasm::elements::{self, External, Internal, MemoryType, Type, ValueType}; -use sp_runtime::traits::Hash; +use codec::{Encode, MaxEncodedLen}; +use sp_core::crypto::UncheckedFrom; +use sp_runtime::{traits::Hash, DispatchError}; use sp_std::prelude::*; +use wasm_instrument::{ + gas_metering, + parity_wasm::elements::{self, External, Internal, MemoryType, Type, ValueType}, +}; +use wasmi::StackLimits; +use wasmparser::{Validator, WasmFeatures}; /// Imported memory must be located inside this module. The reason for hardcoding is that current /// compiler toolchains might not support specifying other modules than "env" for memory imports. pub const IMPORT_MODULE_MEMORY: &str = "env"; +/// Determines whether a module should be instantiated during preparation. +pub enum TryInstantiate { + /// Do the instantiation to make sure that the module is valid. + /// + /// This should be used if a module is only uploaded but not executed. We need + /// to make sure that it can be actually instantiated. + Instantiate, + /// Skip the instantiation during preparation. + /// + /// This makes sense when the preparation takes place as part of an instantation. Then + /// this instantiation would fail the whole transaction and an extra check is not + /// necessary. + Skip, +} + struct ContractModule<'a, T: Config> { /// A deserialized module. The module is valid (this is Guaranteed by `new` method). module: elements::Module, @@ -44,14 +67,9 @@ impl<'a, T: Config> ContractModule<'a, T> { /// Returns `Err` if the `original_code` couldn't be decoded or /// if it contains an invalid module. fn new(original_code: &[u8], schedule: &'a Schedule) -> Result { - use wasmi_validation::{validate_module, PlainValidator}; - let module = elements::deserialize_buffer(original_code).map_err(|_| "Can't decode wasm code")?; - // Make sure that the module is valid. - validate_module::(&module).map_err(|_| "Module is not valid")?; - // Return a `ContractModule` instance with // __valid__ module. Ok(ContractModule { module, schedule }) @@ -115,6 +133,19 @@ impl<'a, T: Config> ContractModule<'a, T> { Ok(()) } + fn ensure_local_variable_limit(&self, limit: u32) -> Result<(), &'static str> { + if let Some(code_section) = self.module.code_section() { + for func_body in code_section.bodies() { + let locals_count: u32 = + func_body.locals().iter().map(|val_type| val_type.count()).sum(); + if locals_count > limit { + return Err("single function declares too many locals") + } + } + } + Ok(()) + } + /// Ensures that no floating point types are in use. fn ensure_no_floating_types(&self) -> Result<(), &'static str> { if let Some(global_section) = self.module.global_section() { @@ -178,20 +209,22 @@ impl<'a, T: Config> ContractModule<'a, T> { Ok(()) } - fn inject_gas_metering(self) -> Result { - let gas_rules = self.schedule.rules(&self.module); - let contract_module = pwasm_utils::inject_gas_counter(self.module, &gas_rules, "seal0") + fn inject_gas_metering(self, determinism: Determinism) -> Result { + let gas_rules = self.schedule.rules(&self.module, determinism); + let backend = gas_metering::host_function::Injector::new("seal0", "gas"); + let contract_module = gas_metering::inject(self.module, backend, &gas_rules) .map_err(|_| "gas instrumentation failed")?; Ok(ContractModule { module: contract_module, schedule: self.schedule }) } fn inject_stack_height_metering(self) -> Result { - let contract_module = pwasm_utils::stack_height::inject_limiter( - self.module, - self.schedule.limits.stack_height, - ) - .map_err(|_| "stack height instrumentation failed")?; - Ok(ContractModule { module: contract_module, schedule: self.schedule }) + if let Some(limit) = self.schedule.limits.stack_height { + let contract_module = wasm_instrument::inject_stack_limiter(self.module, limit) + .map_err(|_| "stack height instrumentation failed")?; + Ok(ContractModule { module: contract_module, schedule: self.schedule }) + } else { + Ok(ContractModule { module: self.module, schedule: self.schedule }) + } } /// Check that the module has required exported functions. For now @@ -219,10 +252,7 @@ impl<'a, T: Config> ContractModule<'a, T> { .map(|is| is.entries()) .unwrap_or(&[]) .iter() - .filter(|entry| match *entry.external() { - External::Function(_) => true, - _ => false, - }) + .filter(|entry| matches!(*entry.external(), External::Function(_))) .count(); for export in export_entries { @@ -253,11 +283,10 @@ impl<'a, T: Config> ContractModule<'a, T> { // We still support () -> (i32) for backwards compatibility. let func_ty_idx = func_entries .get(fn_idx as usize) - .ok_or_else(|| "export refers to non-existent function")? + .ok_or("export refers to non-existent function")? .type_ref(); - let Type::Function(ref func_ty) = types - .get(func_ty_idx as usize) - .ok_or_else(|| "function has a non-existent type")?; + let Type::Function(ref func_ty) = + types.get(func_ty_idx as usize).ok_or("function has a non-existent type")?; if !(func_ty.params().is_empty() && (func_ty.results().is_empty() || func_ty.results() == [ValueType::I32])) { @@ -277,28 +306,34 @@ impl<'a, T: Config> ContractModule<'a, T> { /// Scan an import section if any. /// - /// This accomplishes two tasks: + /// This makes sure that the import section looks as we expect it from a contract + /// and enforces and returns the memory type declared by the contract if any. /// - /// - checks any imported function against defined host functions set, incl. their signatures. - /// - if there is a memory import, returns it's descriptor /// `import_fn_banlist`: list of function names that are disallowed to be imported - fn scan_imports( + fn scan_imports( &self, import_fn_banlist: &[&[u8]], ) -> Result, &'static str> { let module = &self.module; - - let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]); let import_entries = module.import_section().map(|is| is.entries()).unwrap_or(&[]); - let mut imported_mem_type = None; for import in import_entries { - let type_idx = match import.external() { - &External::Table(_) => return Err("Cannot import tables"), - &External::Global(_) => return Err("Cannot import globals"), - &External::Function(ref type_idx) => type_idx, - &External::Memory(ref memory_type) => { + match *import.external() { + External::Table(_) => return Err("Cannot import tables"), + External::Global(_) => return Err("Cannot import globals"), + External::Function(_) => { + if !T::ChainExtension::enabled() && + import.field().as_bytes() == b"seal_call_chain_extension" + { + return Err("module uses chain extensions but chain extensions are disabled") + } + + if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f) { + return Err("module imports a banned function") + } + }, + External::Memory(ref memory_type) => { if import.module() != IMPORT_MODULE_MEMORY { return Err("Invalid module for imported memory") } @@ -311,22 +346,6 @@ impl<'a, T: Config> ContractModule<'a, T> { imported_mem_type = Some(memory_type); continue }, - }; - - let Type::Function(ref func_ty) = types - .get(*type_idx as usize) - .ok_or_else(|| "validation: import entry points to a non-existent type")?; - - if !T::ChainExtension::enabled() && - import.field().as_bytes() == b"seal_call_chain_extension" - { - return Err("module uses chain extensions but chain extensions are disabled") - } - - if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f) || - !C::can_satisfy(import.module().as_bytes(), import.field().as_bytes(), func_ty) - { - return Err("module imports a non-existent function") } } Ok(imported_mem_type) @@ -346,17 +365,15 @@ fn get_memory_limits( let limits = memory_type.limits(); match (limits.initial(), limits.maximum()) { (initial, Some(maximum)) if initial > maximum => - return Err( - "Requested initial number of pages should not exceed the requested maximum", - ), + Err("Requested initial number of pages should not exceed the requested maximum"), (_, Some(maximum)) if maximum > schedule.limits.memory_pages => - return Err("Maximum number of pages should not exceed the configured maximum."), + Err("Maximum number of pages should not exceed the configured maximum."), (initial, Some(maximum)) => Ok((initial, maximum)), (_, None) => { // Maximum number of pages should be always declared. // This isn't a hard requirement and can be treated as a maximum set // to configured maximum. - return Err("Maximum number of pages should be always declared.") + Err("Maximum number of pages should be always declared.") }, } } else { @@ -366,49 +383,100 @@ fn get_memory_limits( } } -fn check_and_instrument( +/// Check and instrument the given `original_code`. +/// +/// On success it returns the instrumented versions together with its `(initial, maximum)` +/// error requirement. The memory requirement was also validated against the `schedule`. +fn instrument( original_code: &[u8], schedule: &Schedule, -) -> Result<(Vec, (u32, u32)), &'static str> { - let contract_module = ContractModule::new(&original_code, schedule)?; - contract_module.scan_exports()?; - contract_module.ensure_no_internal_memory()?; - contract_module.ensure_table_size_limit(schedule.limits.table_size)?; - contract_module.ensure_global_variable_limit(schedule.limits.globals)?; - contract_module.ensure_no_floating_types()?; - contract_module.ensure_parameter_limit(schedule.limits.parameters)?; - contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?; - - // We disallow importing `gas` function here since it is treated as implementation detail. - let disallowed_imports = [b"gas".as_ref()]; - let memory_limits = - get_memory_limits(contract_module.scan_imports::(&disallowed_imports)?, schedule)?; - - let code = contract_module - .inject_gas_metering()? - .inject_stack_height_metering()? - .into_wasm_code()?; - - Ok((code, memory_limits)) -} - -fn do_preparation( - original_code: Vec, - schedule: &Schedule, -) -> Result, &'static str> { - let (code, (initial, maximum)) = - check_and_instrument::(original_code.as_ref(), schedule)?; - Ok(PrefabWasmModule { - instruction_weights_version: schedule.instruction_weights.version, - initial, - maximum, - _reserved: None, - code, - original_code_len: original_code.len() as u32, - refcount: 1, - code_hash: T::Hashing::hash(&original_code), - original_code: Some(original_code), + determinism: Determinism, + try_instantiate: TryInstantiate, +) -> Result<(Vec, (u32, u32)), (DispatchError, &'static str)> +where + E: Environment<()>, + T: Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, +{ + // Do not enable any features here. Any additional feature needs to be carefully + // checked for potential security issues. For example, enabling multi value could lead + // to a DoS vector: It breaks our assumption that branch instructions are of constant time. + // Depending on the implementation they can linearly depend on the amount of values returned + // from a block. + Validator::new_with_features(WasmFeatures { + relaxed_simd: false, + threads: false, + tail_call: false, + multi_memory: false, + exceptions: false, + memory64: false, + extended_const: false, + component_model: false, + // This is not our only defense: We check for float types later in the preparation + // process. Additionally, all instructions explictily need to have weights assigned + // or the deployment will fail. We have none assigned for float instructions. + deterministic_only: matches!(determinism, Determinism::Deterministic), + mutable_global: false, + saturating_float_to_int: false, + sign_extension: false, + bulk_memory: false, + multi_value: false, + reference_types: false, + simd: false, }) + .validate_all(original_code) + .map_err(|err| { + log::debug!(target: "runtime::contracts", "{}", err); + (Error::::CodeRejected.into(), "validation of new code failed") + })?; + + let (code, (initial, maximum)) = (|| { + let contract_module = ContractModule::new(original_code, schedule)?; + contract_module.scan_exports()?; + contract_module.ensure_no_internal_memory()?; + contract_module.ensure_table_size_limit(schedule.limits.table_size)?; + contract_module.ensure_global_variable_limit(schedule.limits.globals)?; + contract_module.ensure_local_variable_limit(schedule.limits.locals)?; + contract_module.ensure_parameter_limit(schedule.limits.parameters)?; + contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?; + + if matches!(determinism, Determinism::Deterministic) { + contract_module.ensure_no_floating_types()?; + } + + // We disallow importing `gas` function here since it is treated as implementation detail. + let disallowed_imports = [b"gas".as_ref()]; + let memory_limits = + get_memory_limits(contract_module.scan_imports(&disallowed_imports)?, schedule)?; + + let code = contract_module + .inject_gas_metering(determinism)? + .inject_stack_height_metering()? + .into_wasm_code()?; + + Ok((code, memory_limits)) + })() + .map_err(|msg: &str| { + log::debug!(target: "runtime::contracts", "new code rejected: {}", msg); + (Error::::CodeRejected.into(), msg) + })?; + + // This will make sure that the module can be actually run within wasmi: + // + // - Doesn't use any unknown imports. + // - Doesn't explode the wasmi bytecode generation. + if matches!(try_instantiate, TryInstantiate::Instantiate) { + // We don't actually ever run any code so we can get away with a minimal stack which + // reduces the amount of memory that needs to be zeroed. + let stack_limits = StackLimits::new(1, 1, 0).expect("initial <= max; qed"); + PrefabWasmModule::::instantiate::(&code, (), (initial, maximum), stack_limits) + .map_err(|err| { + log::debug!(target: "runtime::contracts", "{}", err); + (Error::::CodeRejected.into(), "new code rejected after instrumentation") + })?; + } + + Ok((code, (initial, maximum))) } /// Loads the given module given in `original_code`, performs some checks on it and @@ -416,29 +484,77 @@ fn do_preparation( /// /// The checks are: /// -/// - provided code is a valid wasm module. -/// - the module doesn't define an internal memory instance, -/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`, -/// - all imported functions from the external environment matches defined by `env` module, +/// - the provided code is a valid wasm module +/// - the module doesn't define an internal memory instance +/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule` +/// - all imported functions from the external environment matches defined by `env` module /// /// The preprocessing includes injecting code for gas metering and metering the height of stack. -pub fn prepare_contract( - original_code: Vec, +pub fn prepare( + original_code: CodeVec, schedule: &Schedule, -) -> Result, &'static str> { - do_preparation::(original_code, schedule) + owner: AccountIdOf, + determinism: Determinism, + try_instantiate: TryInstantiate, +) -> Result, (DispatchError, &'static str)> +where + E: Environment<()>, + T: Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, +{ + let (code, (initial, maximum)) = + instrument::(original_code.as_ref(), schedule, determinism, try_instantiate)?; + + let original_code_len = original_code.len(); + + let mut module = PrefabWasmModule { + instruction_weights_version: schedule.instruction_weights.version, + initial, + maximum, + code: code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?, + code_hash: T::Hashing::hash(&original_code), + original_code: Some(original_code), + owner_info: None, + determinism, + }; + + // We need to add the sizes of the `#[codec(skip)]` fields which are stored in different + // storage items. This is also why we have `3` items added and not only one. + let bytes_added = module + .encoded_size() + .saturating_add(original_code_len) + .saturating_add(>::max_encoded_len()) as u32; + let deposit = Diff { bytes_added, items_added: 3, ..Default::default() } + .update_contract::(None) + .charge_or_zero(); + + module.owner_info = Some(OwnerInfo { owner, deposit, refcount: 0 }); + + Ok(module) } -/// The same as [`prepare_contract`] but without constructing a new [`PrefabWasmModule`] -/// -/// # Note +/// Same as [`prepare`] but without constructing a new module. /// -/// Use this when an existing contract should be re-instrumented with a newer schedule version. -pub fn reinstrument_contract( - original_code: Vec, +/// Used to update the code of an existing module to the newest [`Schedule`] version. +/// Stictly speaking is not necessary to check the existing code before reinstrumenting because +/// it can't change in the meantime. However, since we recently switched the validation library +/// we want to re-validate to weed out any bugs that were lurking in the old version. +pub fn reinstrument( + original_code: &[u8], schedule: &Schedule, -) -> Result, &'static str> { - Ok(check_and_instrument::(&original_code, schedule)?.0) + determinism: Determinism, +) -> Result, DispatchError> +where + E: Environment<()>, + T: Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, +{ + instrument::(original_code, schedule, determinism, TryInstantiate::Skip) + .map_err(|(err, msg)| { + log::error!(target: "runtime::contracts", "CodeRejected during reinstrument: {}", msg); + err + }) + .map(|(code, _)| code) } /// Alternate (possibly unsafe) preparation functions used only for benchmarking. @@ -449,31 +565,33 @@ pub fn reinstrument_contract( /// in production code. #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking { - use super::{elements::FunctionType, *}; - - impl ImportSatisfyCheck for () { - fn can_satisfy(_module: &[u8], _name: &[u8], _func_type: &FunctionType) -> bool { - true - } - } + use super::*; /// Prepare function that neither checks nor instruments the passed in code. - pub fn prepare_contract( + pub fn prepare( original_code: Vec, schedule: &Schedule, + owner: AccountIdOf, ) -> Result, &'static str> { let contract_module = ContractModule::new(&original_code, schedule)?; - let memory_limits = get_memory_limits(contract_module.scan_imports::<()>(&[])?, schedule)?; + let memory_limits = get_memory_limits(contract_module.scan_imports(&[])?, schedule)?; Ok(PrefabWasmModule { instruction_weights_version: schedule.instruction_weights.version, initial: memory_limits.0, maximum: memory_limits.1, - _reserved: None, - code: contract_module.into_wasm_code()?, - original_code_len: original_code.len() as u32, - refcount: 1, code_hash: T::Hashing::hash(&original_code), - original_code: Some(original_code), + original_code: Some(original_code.try_into().map_err(|_| "Original code too large")?), + code: contract_module + .into_wasm_code()? + .try_into() + .map_err(|_| "Instrumented code too large")?, + owner_info: Some(OwnerInfo { + owner, + // this is a helper function for benchmarking which skips deposit collection + deposit: Default::default(), + refcount: 0, + }), + determinism: Determinism::Deterministic, }) } } @@ -481,10 +599,15 @@ pub mod benchmarking { #[cfg(test)] mod tests { use super::*; - use crate::{exec::Ext, schedule::Limits}; + use crate::{ + exec::Ext, + schedule::Limits, + tests::{Test, ALICE}, + }; + use pallet_contracts_proc_macro::define_env; use std::fmt; - impl fmt::Debug for PrefabWasmModule { + impl fmt::Debug for PrefabWasmModule { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "PreparedContract {{ .. }}") } @@ -494,30 +617,42 @@ mod tests { #[allow(unreachable_code)] mod env { use super::*; + use crate::wasm::runtime::TrapReason; // Define test environment for tests. We need ImportSatisfyCheck // implementation from it. So actual implementations doesn't matter. - define_env!(Test, , - [seal0] panic(_ctx) => { unreachable!(); }, + #[define_env] + pub mod test_env { + fn panic(_ctx: _, _memory: _) -> Result<(), TrapReason> { + Ok(()) + } // gas is an implementation defined function and a contract can't import it. - [seal0] gas(_ctx, _amount: u32) => { unreachable!(); }, + fn gas(_ctx: _, _memory: _, _amount: u64) -> Result<(), TrapReason> { + Ok(()) + } - [seal0] nop(_ctx, _unused: u64) => { unreachable!(); }, + fn nop(_ctx: _, _memory: _, _unused: u64) -> Result<(), TrapReason> { + Ok(()) + } - // new version of nop with other data type for argumebt - [seal1] nop(_ctx, _unused: i32) => { unreachable!(); }, - ); + // new version of nop with other data type for argument + #[version(1)] + fn nop(_ctx: _, _memory: _, _unused: i32) -> Result<(), TrapReason> { + Ok(()) + } + } } macro_rules! prepare_test { ($name:ident, $wat:expr, $($expected:tt)*) => { #[test] fn $name() { - let wasm = wat::parse_str($wat).unwrap(); + let wasm = wat::parse_str($wat).unwrap().try_into().unwrap(); let schedule = Schedule { limits: Limits { - globals: 3, + globals: 3, + locals: 3, parameters: 3, memory_pages: 16, table_size: 3, @@ -526,8 +661,14 @@ mod tests { }, .. Default::default() }; - let r = do_preparation::(wasm, &schedule); - assert_matches::assert_matches!(r, $($expected)*); + let r = prepare::( + wasm, + &schedule, + ALICE, + Determinism::Deterministic, + TryInstantiate::Instantiate, + ); + assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*); } }; } @@ -611,6 +752,43 @@ mod tests { ); } + mod locals { + use super::*; + + prepare_test!( + local_number_valid, + r#" + (module + (func + (local i32) + (local i32) + (local i32) + ) + (func (export "call")) + (func (export "deploy")) + ) + "#, + Ok(_) + ); + + prepare_test!( + local_number_too_high, + r#" + (module + (func + (local i32) + (local i32) + (local i32) + (local i32) + ) + (func (export "call")) + (func (export "deploy")) + ) + "#, + Err("single function declares too many locals") + ); + } + mod memories { use super::*; @@ -662,7 +840,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("Module is not valid") + Err("validation of new code failed") ); prepare_test!( @@ -728,7 +906,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("Module is not valid") + Err("validation of new code failed") ); prepare_test!( @@ -854,7 +1032,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("module imports a non-existent function") + Err("module imports a banned function") ); // memory is in "env" and not in "seal0" @@ -909,7 +1087,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("module imports a non-existent function") + Err("module imports a banned function") ); prepare_test!( @@ -922,7 +1100,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("module imports a non-existent function") + Err("new code rejected after instrumentation") ); } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 52b864bf18eac..50ad9996e6eb6 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,21 +18,59 @@ //! Environment definition of the wasm smart-contract runtime. use crate::{ - exec::{ExecError, ExecResult, Ext, StorageKey, TopicOf}, + exec::{ExecError, ExecResult, Ext, FixSizedKey, TopicOf, VarSizedKey}, gas::{ChargedAmount, Token}, schedule::HostFnWeights, - wasm::env_def::ConvertibleToWasm, - BalanceOf, CodeHash, Config, Error, + BalanceOf, CodeHash, Config, Error, SENTINEL, }; + use bitflags::bitflags; -use codec::{Decode, DecodeAll, Encode, MaxEncodedLen}; -use frame_support::{dispatch::DispatchError, ensure, weights::Weight}; +use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; +use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight, RuntimeDebug}; use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; -use pwasm_utils::parity_wasm::elements::ValueType; -use sp_core::{crypto::UncheckedFrom, Bytes}; +use pallet_contracts_proc_macro::define_env; +use sp_core::crypto::UncheckedFrom; use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256}; -use sp_runtime::traits::Bounded; -use sp_std::prelude::*; +use sp_runtime::traits::{Bounded, Zero}; +use sp_std::{fmt, prelude::*}; +use wasmi::{core::HostError, errors::LinkerError, Linker, Memory, Store}; + +/// The maximum nesting depth a contract can use when encoding types. +const MAX_DECODE_NESTING: u32 = 256; + +/// Trait implemented by the [`define_env`](pallet_contracts_proc_macro::define_env) macro for the +/// emitted `Env` struct. +pub trait Environment { + /// Adds all declared functions to the supplied [`Linker`](wasmi::Linker) and + /// [`Store`](wasmi::Store). + fn define( + store: &mut Store, + linker: &mut Linker, + allow_unstable: bool, + ) -> Result<(), LinkerError>; +} + +/// Type of a storage key. +#[allow(dead_code)] +enum KeyType { + /// Deprecated fix sized key [0;32]. + Fix, + /// Variable sized key used in transparent hashing, + /// cannot be larger than MaxStorageKeyLen. + Variable(u32), +} + +impl KeyType { + fn len(&self) -> Result { + match self { + KeyType::Fix => Ok(32u32), + KeyType::Variable(len) => { + ensure!(len <= &::MaxStorageKeyLen::get(), Error::::DecodingFailed); + Ok(*len) + }, + } + } +} /// Every error that can be returned to a contract when it calls any of the host functions. /// @@ -41,7 +79,7 @@ use sp_std::prelude::*; /// This enum can be extended in the future: New codes can be added but existing codes /// will not be changed or removed. This means that any contract **must not** exhaustively /// match return codes. Instead, contracts should prepare for unknown variants and deal with -/// those errors gracefuly in order to be forward compatible. +/// those errors gracefully in order to be forward compatible. #[repr(u32)] pub enum ReturnCode { /// API call successful. @@ -54,15 +92,12 @@ pub enum ReturnCode { CalleeReverted = 2, /// The passed key does not exist in storage. KeyNotFound = 3, - /// Transfer failed because it would have brought the sender's total balance below the - /// subsistence threshold. - BelowSubsistenceThreshold = 4, - /// Transfer failed for other reasons. Most probably reserved or locked balance of the - /// sender prevents the transfer. + /// Deprecated and no longer returned: There is only the minimum balance. + _BelowSubsistenceThreshold = 4, + /// See [`Error::TransferFailed`]. TransferFailed = 5, - /// The newly created contract is below the subsistence threshold after executing - /// its constructor. - NewContractNotFunded = 6, + /// Deprecated and no longer returned: Endowment is no longer required. + _EndowmentTooLow = 6, /// No code could be found at the supplied code hash. CodeNotFound = 7, /// The contract that was called is no contract (a plain account). @@ -71,25 +106,13 @@ pub enum ReturnCode { /// recording was disabled. LoggingDisabled = 9, /// The call dispatched by `seal_call_runtime` was executed but returned an error. - #[cfg(feature = "unstable-interface")] CallRuntimeReturnedError = 10, - /// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature. - #[cfg(feature = "unstable-interface")] + /// ECDSA pubkey recovery failed (most probably wrong recovery id or signature), or + /// ECDSA compressed pubkey conversion into Ethereum address failed (most probably + /// wrong pubkey provided). EcdsaRecoverFailed = 11, } -impl ConvertibleToWasm for ReturnCode { - type NativeType = Self; - const VALUE_TYPE: ValueType = ValueType::I32; - fn to_typed_value(self) -> sp_sandbox::Value { - sp_sandbox::Value::I32(self as i32) - } - fn from_typed_value(_: sp_sandbox::Value) -> Option { - debug_assert!(false, "We will never receive a ReturnCode but only send it to wasm."); - None - } -} - impl From for ReturnCode { fn from(from: ExecReturnValue) -> Self { if from.flags.contains(ReturnFlags::REVERT) { @@ -100,7 +123,14 @@ impl From for ReturnCode { } } +impl From for u32 { + fn from(code: ReturnCode) -> u32 { + code as u32 + } +} + /// The data passed through when a contract uses `seal_return`. +#[derive(RuntimeDebug)] pub struct ReturnData { /// The flags as passed through by the contract. They are still unchecked and /// will later be parsed into a `ReturnFlags` bitflags struct. @@ -115,6 +145,7 @@ pub struct ReturnData { /// occurred (the SupervisorError variant). /// The other case is where the trap does not constitute an error but rather was invoked /// as a quick way to terminate the application (all other variants). +#[derive(RuntimeDebug)] pub enum TrapReason { /// The supervisor trapped the contract because of an error condition occurred during /// execution in privileged code. @@ -132,14 +163,34 @@ impl> From for TrapReason { } } +impl fmt::Display for TrapReason { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Ok(()) + } +} + +impl HostError for TrapReason {} + #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Copy, Clone)] pub enum RuntimeCosts { /// Charge the gas meter with the cost of a metering block. The charged costs are /// the supplied cost of the block plus the overhead of the metering itself. - MeteringBlock(u32), + MeteringBlock(u64), + /// Weight charged for copying data from the sandbox. + CopyFromContract(u32), + /// Weight charged for copying data to the sandbox. + CopyToContract(u32), /// Weight of calling `seal_caller`. Caller, + /// Weight of calling `seal_is_contract`. + IsContract, + /// Weight of calling `seal_code_hash`. + CodeHash, + /// Weight of calling `seal_own_code_hash`. + OwnCodeHash, + /// Weight of calling `seal_caller_is_origin`. + CallerIsOrigin, /// Weight of calling `seal_address`. Address, /// Weight of calling `seal_gas_left`. @@ -150,8 +201,6 @@ pub enum RuntimeCosts { ValueTransferred, /// Weight of calling `seal_minimum_balance`. MinimumBalance, - /// Weight of calling `seal_contract_deposit`. - ContractDeposit, /// Weight of calling `seal_block_number`. BlockNumber, /// Weight of calling `seal_now`. @@ -160,8 +209,6 @@ pub enum RuntimeCosts { WeightToFee, /// Weight of calling `seal_input` without the weight of copying the input. InputBase, - /// Weight of copying the input data for the given size. - InputCopyOut(u32), /// Weight of calling `seal_return` for the given output size. Return(u32), /// Weight of calling `seal_terminate`. @@ -172,28 +219,30 @@ pub enum RuntimeCosts { DepositEvent { num_topic: u32, len: u32 }, /// Weight of calling `seal_debug_message`. DebugMessage, - /// Weight of calling `seal_set_storage` for the given storage item size. - SetStorage(u32), - /// Weight of calling `seal_clear_storage`. - ClearStorage, - /// Weight of calling `seal_get_storage` without output weight. - GetStorageBase, - /// Weight of an item received via `seal_get_storage` for the given size. - GetStorageCopyOut(u32), + /// Weight of calling `seal_set_storage` for the given storage item sizes. + SetStorage { old_bytes: u32, new_bytes: u32 }, + /// Weight of calling `seal_clear_storage` per cleared byte. + ClearStorage(u32), + /// Weight of calling `seal_contains_storage` per byte of the checked item. + ContainsStorage(u32), + /// Weight of calling `seal_get_storage` with the specified size in storage. + GetStorage(u32), + /// Weight of calling `seal_take_storage` for the given size. + TakeStorage(u32), /// Weight of calling `seal_transfer`. Transfer, - /// Weight of calling `seal_call` for the given input size. - CallBase(u32), + /// Base weight of calling `seal_call`. + CallBase, + /// Weight of calling `seal_delegate_call` for the given input size. + DelegateCallBase, /// Weight of the transfer performed during a call. CallSurchargeTransfer, - /// Weight of output received through `seal_call` for the given size. - CallCopyOut(u32), - /// Weight of calling `seal_instantiate` for the given input and salt without output weight. - /// This includes the transfer as an instantiate without a value will always be below - /// the existential deposit and is disregarded as corner case. + /// Weight per byte that is cloned by supplying the `CLONE_INPUT` flag. + CallInputCloned(u32), + /// Weight of calling `seal_instantiate` for the given input length and salt. InstantiateBase { input_data_len: u32, salt_len: u32 }, - /// Weight of output received through `seal_instantiate` for the given size. - InstantiateCopyOut(u32), + /// Weight of the transfer performed during an instantiate. + InstantiateSurchargeTransfer, /// Weight of calling `seal_hash_sha_256` for the given input size. HashSha256(u32), /// Weight of calling `seal_hash_keccak_256` for the given input size. @@ -203,16 +252,21 @@ pub enum RuntimeCosts { /// Weight of calling `seal_hash_blake2_128` for the given input size. HashBlake128(u32), /// Weight of calling `seal_ecdsa_recover`. - #[cfg(feature = "unstable-interface")] EcdsaRecovery, /// Weight charged by a chain extension through `seal_call_chain_extension`. ChainExtension(u64), - /// Weight charged for copying data from the sandbox. - #[cfg(feature = "unstable-interface")] - CopyIn(u32), /// Weight charged for calling into the runtime. - #[cfg(feature = "unstable-interface")] CallRuntime(Weight), + /// Weight of calling `seal_set_code_hash` + SetCodeHash, + /// Weight of calling `ecdsa_to_eth_address` + EcdsaToEthAddress, + /// Weight of calling `reentrance_count` + ReentrantCount, + /// Weight of calling `account_reentrance_count` + AccountEntranceCount, + /// Weight of calling `instantiation_nonce` + InstantationNonce, } impl RuntimeCosts { @@ -223,19 +277,23 @@ impl RuntimeCosts { { use self::RuntimeCosts::*; let weight = match *self { - MeteringBlock(amount) => s.gas.saturating_add(amount.into()), + MeteringBlock(amount) => s.gas.saturating_add(amount), + CopyFromContract(len) => s.return_per_byte.saturating_mul(len.into()), + CopyToContract(len) => s.input_per_byte.saturating_mul(len.into()), Caller => s.caller, + IsContract => s.is_contract, + CodeHash => s.code_hash, + OwnCodeHash => s.own_code_hash, + CallerIsOrigin => s.caller_is_origin, Address => s.address, GasLeft => s.gas_left, Balance => s.balance, ValueTransferred => s.value_transferred, MinimumBalance => s.minimum_balance, - ContractDeposit => s.contract_deposit, BlockNumber => s.block_number, Now => s.now, WeightToFee => s.weight_to_fee, InputBase => s.input, - InputCopyOut(len) => s.input_per_byte.saturating_mul(len.into()), Return(len) => s.r#return.saturating_add(s.return_per_byte.saturating_mul(len.into())), Terminate => s.terminate, Random => s.random, @@ -244,21 +302,31 @@ impl RuntimeCosts { .saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into())) .saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())), DebugMessage => s.debug_message, - SetStorage(len) => - s.set_storage.saturating_add(s.set_storage_per_byte.saturating_mul(len.into())), - ClearStorage => s.clear_storage, - GetStorageBase => s.get_storage, - GetStorageCopyOut(len) => s.get_storage_per_byte.saturating_mul(len.into()), + SetStorage { new_bytes, old_bytes } => s + .set_storage + .saturating_add(s.set_storage_per_new_byte.saturating_mul(new_bytes.into())) + .saturating_add(s.set_storage_per_old_byte.saturating_mul(old_bytes.into())), + ClearStorage(len) => s + .clear_storage + .saturating_add(s.clear_storage_per_byte.saturating_mul(len.into())), + ContainsStorage(len) => s + .contains_storage + .saturating_add(s.contains_storage_per_byte.saturating_mul(len.into())), + GetStorage(len) => + s.get_storage.saturating_add(s.get_storage_per_byte.saturating_mul(len.into())), + TakeStorage(len) => s + .take_storage + .saturating_add(s.take_storage_per_byte.saturating_mul(len.into())), Transfer => s.transfer, - CallBase(len) => - s.call.saturating_add(s.call_per_input_byte.saturating_mul(len.into())), + CallBase => s.call, + DelegateCallBase => s.delegate_call, CallSurchargeTransfer => s.call_transfer_surcharge, - CallCopyOut(len) => s.call_per_output_byte.saturating_mul(len.into()), + CallInputCloned(len) => s.call_per_cloned_byte.saturating_mul(len.into()), InstantiateBase { input_data_len, salt_len } => s .instantiate - .saturating_add(s.instantiate_per_input_byte.saturating_mul(input_data_len.into())) + .saturating_add(s.return_per_byte.saturating_mul(input_data_len.into())) .saturating_add(s.instantiate_per_salt_byte.saturating_mul(salt_len.into())), - InstantiateCopyOut(len) => s.instantiate_per_output_byte.saturating_mul(len.into()), + InstantiateSurchargeTransfer => s.instantiate_transfer_surcharge, HashSha256(len) => s .hash_sha2_256 .saturating_add(s.hash_sha2_256_per_byte.saturating_mul(len.into())), @@ -271,22 +339,34 @@ impl RuntimeCosts { HashBlake128(len) => s .hash_blake2_128 .saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())), - #[cfg(feature = "unstable-interface")] EcdsaRecovery => s.ecdsa_recover, ChainExtension(amount) => amount, - #[cfg(feature = "unstable-interface")] - CopyIn(len) => s.return_per_byte.saturating_mul(len.into()), - #[cfg(feature = "unstable-interface")] - CallRuntime(weight) => weight, + CallRuntime(weight) => weight.ref_time(), + SetCodeHash => s.set_code_hash, + EcdsaToEthAddress => s.ecdsa_to_eth_address, + ReentrantCount => s.reentrance_count, + AccountEntranceCount => s.account_reentrance_count, + InstantationNonce => s.instantiation_nonce, }; RuntimeToken { #[cfg(test)] _created_from: *self, - weight, + weight: Weight::from_ref_time(weight), } } } +/// Same as [`Runtime::charge_gas`]. +/// +/// We need this access as a macro because sometimes hiding the lifetimes behind +/// a function won't work out. +macro_rules! charge_gas { + ($runtime:expr, $costs:expr) => {{ + let token = $costs.token(&$runtime.ext.schedule().host_fn_weights); + $runtime.ext.gas_meter().charge(token) + }}; +} + #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Copy, Clone)] struct RuntimeToken { @@ -306,8 +386,8 @@ where } bitflags! { - /// Flags used to change the behaviour of `seal_call`. - struct CallFlags: u32 { + /// Flags used to change the behaviour of `seal_call` and `seal_delegate_call`. + pub struct CallFlags: u32 { /// Forward the input of current function to the callee. /// /// Supplied input pointers are ignored when set. @@ -342,10 +422,32 @@ bitflags! { /// Without this flag any reentrancy into the current contract that originates from /// the callee (or any of its callees) is denied. This includes the first callee: /// You cannot call into yourself with this flag set. + /// + /// # Note + /// + /// For `seal_delegate_call` should be always unset, otherwise + /// [`Error::InvalidCallFlags`] is returned. const ALLOW_REENTRY = 0b0000_1000; } } +/// The kind of call that should be performed. +enum CallType { + /// Execute another instantiated contract + Call { callee_ptr: u32, value_ptr: u32, gas: u64 }, + /// Execute deployed code in the context (storage, account ID, value) of the caller contract + DelegateCall { code_hash_ptr: u32 }, +} + +impl CallType { + fn cost(&self) -> RuntimeCosts { + match self { + CallType::Call { .. } => RuntimeCosts::CallBase, + CallType::DelegateCall { .. } => RuntimeCosts::DelegateCallBase, + } + } +} + /// This is only appropriate when writing out data of constant size that does not depend on user /// input. In this case the costs for this copy was already charged as part of the token at /// the beginning of the API entry point. @@ -357,8 +459,8 @@ fn already_charged(_: u32) -> Option { pub struct Runtime<'a, E: Ext + 'a> { ext: &'a mut E, input_data: Option>, - memory: sp_sandbox::Memory, - trap_reason: Option, + memory: Option, + chain_extension: Option::ChainExtension>>, } impl<'a, E> Runtime<'a, E> @@ -367,48 +469,56 @@ where ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { - pub fn new(ext: &'a mut E, input_data: Vec, memory: sp_sandbox::Memory) -> Self { - Runtime { ext, input_data: Some(input_data), memory, trap_reason: None } + pub fn new(ext: &'a mut E, input_data: Vec) -> Self { + Runtime { + ext, + input_data: Some(input_data), + memory: None, + chain_extension: Some(Box::new(Default::default())), + } } - /// Converts the sandbox result and the runtime state into the execution outcome. - /// - /// It evaluates information stored in the `trap_reason` variable of the runtime and - /// bases the outcome on the value if this variable. Only if `trap_reason` is `None` - /// the result of the sandbox is evaluated. - pub fn to_execution_result( - self, - sandbox_result: Result, - ) -> ExecResult { - // If a trap reason is set we base our decision solely on that. - if let Some(trap_reason) = self.trap_reason { - return match trap_reason { - // The trap was the result of the execution `return` host function. - TrapReason::Return(ReturnData { flags, data }) => { - let flags = ReturnFlags::from_bits(flags) - .ok_or_else(|| "used reserved bit in return flags")?; - Ok(ExecReturnValue { flags, data: Bytes(data) }) - }, - TrapReason::Termination => - Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }), - TrapReason::SupervisorError(error) => Err(error)?, - } - } + pub fn memory(&self) -> Option { + self.memory + } + + pub fn set_memory(&mut self, memory: Memory) { + self.memory = Some(memory); + } - // Check the exact type of the error. + /// Converts the sandbox result and the runtime state into the execution outcome. + pub fn to_execution_result(self, sandbox_result: Result<(), wasmi::Error>) -> ExecResult { + use TrapReason::*; match sandbox_result { - // No traps were generated. Proceed normally. - Ok(_) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }), - // `Error::Module` is returned only if instantiation or linking failed (i.e. + // Contract returned from main function -> no data was returned. + Ok(_) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }), + // Contract either trapped or some host function aborted the execution. + Err(wasmi::Error::Trap(trap)) => { + // If we encoded a reason then it is some abort generated by a host function. + // Otherwise the trap came from the contract. + let reason: TrapReason = *trap + .into_host() + .ok_or(Error::::ContractTrapped)? + .downcast() + .expect("`TrapReason` is the only type we use to encode host errors; qed"); + match reason { + Return(ReturnData { flags, data }) => { + let flags = + ReturnFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?; + Ok(ExecReturnValue { flags, data }) + }, + Termination => + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }), + SupervisorError(error) => return Err(error.into()), + } + }, + // Any other error is returned only if instantiation or linking failed (i.e. // wasm binary tried to import a function that is not provided by the host). // This shouldn't happen because validation process ought to reject such binaries. // // Because panics are really undesirable in the runtime code, we treat this as // a trap for now. Eventually, we might want to revisit this. - Err(sp_sandbox::Error::Module) => Err("validation error")?, - // Any other kind of a trap should result in a failure. - Err(sp_sandbox::Error::Execution) | Err(sp_sandbox::Error::OutOfBounds) => - Err(Error::::ContractTrapped)?, + Err(_) => Err(Error::::CodeRejected.into()), } } @@ -420,21 +530,11 @@ where self.ext } - /// Store the reason for a host function triggered trap. - /// - /// This is called by the `define_env` macro in order to store any error returned by - /// the host functions defined through the said macro. It should **not** be called - /// manually. - pub fn set_trap_reason(&mut self, reason: TrapReason) { - self.trap_reason = Some(reason); - } - /// Charge the gas meter with the specified token. /// /// Returns `Err(HostError)` if there is not enough gas. pub fn charge_gas(&mut self, costs: RuntimeCosts) -> Result { - let token = costs.token(&self.ext.schedule().host_fn_weights); - self.ext.gas_meter().charge(token) + charge_gas!(self, costs) } /// Adjust a previously charged amount down to its actual amount. @@ -451,12 +551,15 @@ where /// Returns `Err` if one of the following conditions occurs: /// /// - requested buffer is not within the bounds of the sandbox memory. - pub fn read_sandbox_memory(&self, ptr: u32, len: u32) -> Result, DispatchError> { + pub fn read_sandbox_memory( + &self, + memory: &[u8], + ptr: u32, + len: u32, + ) -> Result, DispatchError> { ensure!(len <= self.ext.schedule().limits.max_memory_size(), Error::::OutOfBounds); let mut buf = vec![0u8; len as usize]; - self.memory - .get(ptr, buf.as_mut_slice()) - .map_err(|_| Error::::OutOfBounds)?; + self.read_sandbox_memory_into_buf(memory, ptr, buf.as_mut_slice())?; Ok(buf) } @@ -467,10 +570,15 @@ where /// - requested buffer is not within the bounds of the sandbox memory. pub fn read_sandbox_memory_into_buf( &self, + memory: &[u8], ptr: u32, buf: &mut [u8], ) -> Result<(), DispatchError> { - self.memory.get(ptr, buf).map_err(|_| Error::::OutOfBounds.into()) + let ptr = ptr as usize; + let bound_checked = + memory.get(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; + buf.copy_from_slice(bound_checked); + Ok(()) } /// Reads and decodes a type with a size fixed at compile time from contract memory. @@ -481,10 +589,14 @@ where /// contract callable function. pub fn read_sandbox_memory_as( &self, + memory: &[u8], ptr: u32, ) -> Result { - let buf = self.read_sandbox_memory(ptr, D::max_encoded_len() as u32)?; - let decoded = D::decode_all(&mut &buf[..]) + let ptr = ptr as usize; + let mut bound_checked = memory + .get(ptr..ptr + D::max_encoded_len() as usize) + .ok_or_else(|| Error::::OutOfBounds)?; + let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut bound_checked) .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; Ok(decoded) } @@ -502,11 +614,14 @@ where /// regard to the overall weight. pub fn read_sandbox_memory_as_unbounded( &self, + memory: &[u8], ptr: u32, len: u32, ) -> Result { - let buf = self.read_sandbox_memory(ptr, len)?; - let decoded = D::decode_all(&mut &buf[..]) + let ptr = ptr as usize; + let mut bound_checked = + memory.get(ptr..ptr + len as usize).ok_or_else(|| Error::::OutOfBounds)?; + let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut bound_checked) .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; Ok(decoded) } @@ -519,7 +634,7 @@ where /// length of the buffer located at `out_ptr`. If that buffer is large enough the actual /// `buf.len()` is written to this location. /// - /// If `out_ptr` is set to the sentinel value of `u32::MAX` and `allow_skip` is true the + /// If `out_ptr` is set to the sentinel value of `SENTINEL` and `allow_skip` is true the /// operation is skipped and `Ok` is returned. This is supposed to help callers to make copying /// output optional. For example to skip copying back the output buffer of an `seal_call` /// when the caller is not interested in the result. @@ -532,33 +647,30 @@ where /// `Err` if the size of the buffer located at `out_ptr` is too small to fit `buf`. pub fn write_sandbox_output( &mut self, + memory: &mut [u8], out_ptr: u32, out_len_ptr: u32, buf: &[u8], allow_skip: bool, create_token: impl FnOnce(u32) -> Option, ) -> Result<(), DispatchError> { - if allow_skip && out_ptr == u32::MAX { + if allow_skip && out_ptr == SENTINEL { return Ok(()) } let buf_len = buf.len() as u32; - let len: u32 = self.read_sandbox_memory_as(out_len_ptr)?; + let len: u32 = self.read_sandbox_memory_as(memory, out_len_ptr)?; if len < buf_len { - Err(Error::::OutputBufferTooSmall)? + return Err(Error::::OutputBufferTooSmall.into()) } if let Some(costs) = create_token(buf_len) { self.charge_gas(costs)?; } - self.memory - .set(out_ptr, buf) - .and_then(|_| self.memory.set(out_len_ptr, &buf_len.encode())) - .map_err(|_| Error::::OutOfBounds)?; - - Ok(()) + self.write_sandbox_memory(memory, out_ptr, buf)?; + self.write_sandbox_memory(memory, out_len_ptr, &buf_len.encode()) } /// Write the given buffer to the designated location in the sandbox memory. @@ -566,8 +678,17 @@ where /// Returns `Err` if one of the following conditions occurs: /// /// - designated area is not within the bounds of the sandbox memory. - fn write_sandbox_memory(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { - self.memory.set(ptr, buf).map_err(|_| Error::::OutOfBounds.into()) + fn write_sandbox_memory( + &self, + memory: &mut [u8], + ptr: u32, + buf: &[u8], + ) -> Result<(), DispatchError> { + let ptr = ptr as usize; + let bound_checked = + memory.get_mut(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; + bound_checked.copy_from_slice(buf); + Ok(()) } /// Computes the given hash function on the supplied input. @@ -583,7 +704,8 @@ where /// /// The `input` and `output` buffers may overlap. fn compute_hash_on_intermediate_buffer( - &mut self, + &self, + memory: &mut [u8], hash_fn: F, input_ptr: u32, input_len: u32, @@ -594,11 +716,11 @@ where R: AsRef<[u8]>, { // Copy input into supervisor memory. - let input = self.read_sandbox_memory(input_ptr, input_len)?; + let input = self.read_sandbox_memory(memory, input_ptr, input_len)?; // Compute the hash on the input buffer using the given hash function. let hash = hash_fn(&input); // Write the resulting hash back into the sandboxed output buffer. - self.write_sandbox_memory(output_ptr, hash.as_ref())?; + self.write_sandbox_memory(memory, output_ptr, hash.as_ref())?; Ok(()) } @@ -606,16 +728,12 @@ where fn err_into_return_code(from: DispatchError) -> Result { use ReturnCode::*; - let below_sub = Error::::BelowSubsistenceThreshold.into(); let transfer_failed = Error::::TransferFailed.into(); - let not_funded = Error::::NewContractNotFunded.into(); let no_code = Error::::CodeNotFound.into(); let not_found = Error::::ContractNotFound.into(); match from { - x if x == below_sub => Ok(BelowSubsistenceThreshold), x if x == transfer_failed => Ok(TransferFailed), - x if x == not_funded => Ok(NewContractNotFunded), x if x == no_code => Ok(CodeNotFound), x if x == not_found => Ok(NotCallable), err => Err(err), @@ -637,34 +755,171 @@ where } } + fn set_storage( + &mut self, + memory: &[u8], + key_type: KeyType, + key_ptr: u32, + value_ptr: u32, + value_len: u32, + ) -> Result { + let max_size = self.ext.max_value_size(); + let charged = self + .charge_gas(RuntimeCosts::SetStorage { new_bytes: value_len, old_bytes: max_size })?; + if value_len > max_size { + return Err(Error::::ValueTooLarge.into()) + } + let key = self.read_sandbox_memory(memory, key_ptr, key_type.len::()?)?; + let value = Some(self.read_sandbox_memory(memory, value_ptr, value_len)?); + let write_outcome = match key_type { + KeyType::Fix => self.ext.set_storage( + &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, + value, + false, + )?, + KeyType::Variable(_) => self.ext.set_storage_transparent( + &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, + value, + false, + )?, + }; + + self.adjust_gas( + charged, + RuntimeCosts::SetStorage { new_bytes: value_len, old_bytes: write_outcome.old_len() }, + ); + Ok(write_outcome.old_len_with_sentinel()) + } + + fn clear_storage( + &mut self, + memory: &[u8], + key_type: KeyType, + key_ptr: u32, + ) -> Result { + let charged = self.charge_gas(RuntimeCosts::ClearStorage(self.ext.max_value_size()))?; + let key = self.read_sandbox_memory(memory, key_ptr, key_type.len::()?)?; + let outcome = match key_type { + KeyType::Fix => self.ext.set_storage( + &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, + None, + false, + )?, + KeyType::Variable(_) => self.ext.set_storage_transparent( + &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, + None, + false, + )?, + }; + + self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.old_len())); + Ok(outcome.old_len_with_sentinel()) + } + + fn get_storage( + &mut self, + memory: &mut [u8], + key_type: KeyType, + key_ptr: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + let charged = self.charge_gas(RuntimeCosts::GetStorage(self.ext.max_value_size()))?; + let key = self.read_sandbox_memory(memory, key_ptr, key_type.len::()?)?; + let outcome = match key_type { + KeyType::Fix => self.ext.get_storage( + &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, + ), + KeyType::Variable(_) => self.ext.get_storage_transparent( + &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, + ), + }; + + if let Some(value) = outcome { + self.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32)); + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value, + false, + already_charged, + )?; + Ok(ReturnCode::Success) + } else { + self.adjust_gas(charged, RuntimeCosts::GetStorage(0)); + Ok(ReturnCode::KeyNotFound) + } + } + + fn contains_storage( + &mut self, + memory: &[u8], + key_type: KeyType, + key_ptr: u32, + ) -> Result { + let charged = self.charge_gas(RuntimeCosts::ContainsStorage(self.ext.max_value_size()))?; + let key = self.read_sandbox_memory(memory, key_ptr, key_type.len::()?)?; + let outcome = match key_type { + KeyType::Fix => self.ext.get_storage_size( + &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, + ), + KeyType::Variable(_) => self.ext.get_storage_size_transparent( + &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, + ), + }; + + self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.unwrap_or(0))); + Ok(outcome.unwrap_or(SENTINEL)) + } + fn call( &mut self, + memory: &mut [u8], flags: CallFlags, - callee_ptr: u32, - gas: u64, - value_ptr: u32, + call_type: CallType, input_data_ptr: u32, input_data_len: u32, output_ptr: u32, output_len_ptr: u32, ) -> Result { - self.charge_gas(RuntimeCosts::CallBase(input_data_len))?; - let callee: <::T as frame_system::Config>::AccountId = - self.read_sandbox_memory_as(callee_ptr)?; - let value: BalanceOf<::T> = self.read_sandbox_memory_as(value_ptr)?; + self.charge_gas(call_type.cost())?; let input_data = if flags.contains(CallFlags::CLONE_INPUT) { - self.input_data.as_ref().ok_or_else(|| Error::::InputForwarded)?.clone() + let input = self.input_data.as_ref().ok_or(Error::::InputForwarded)?; + charge_gas!(self, RuntimeCosts::CallInputCloned(input.len() as u32))?; + input.clone() } else if flags.contains(CallFlags::FORWARD_INPUT) { - self.input_data.take().ok_or_else(|| Error::::InputForwarded)? + self.input_data.take().ok_or(Error::::InputForwarded)? } else { - self.read_sandbox_memory(input_data_ptr, input_data_len)? + self.charge_gas(RuntimeCosts::CopyFromContract(input_data_len))?; + self.read_sandbox_memory(memory, input_data_ptr, input_data_len)? + }; + + let call_outcome = match call_type { + CallType::Call { callee_ptr, value_ptr, gas } => { + let callee: <::T as frame_system::Config>::AccountId = + self.read_sandbox_memory_as(memory, callee_ptr)?; + let value: BalanceOf<::T> = + self.read_sandbox_memory_as(memory, value_ptr)?; + if value > 0u32.into() { + self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?; + } + self.ext.call( + Weight::from_ref_time(gas), + callee, + value, + input_data, + flags.contains(CallFlags::ALLOW_REENTRY), + ) + }, + CallType::DelegateCall { code_hash_ptr } => { + if flags.contains(CallFlags::ALLOW_REENTRY) { + return Err(Error::::InvalidCallFlags.into()) + } + let code_hash = self.read_sandbox_memory_as(memory, code_hash_ptr)?; + self.ext.delegate_call(code_hash, input_data) + }, }; - if value > 0u32.into() { - self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?; - } - let ext = &mut self.ext; - let call_outcome = - ext.call(gas, callee, value, input_data, flags.contains(CallFlags::ALLOW_REENTRY)); // `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to // a halt anyways without anymore code being executed. @@ -672,21 +927,27 @@ where if let Ok(return_value) = call_outcome { return Err(TrapReason::Return(ReturnData { flags: return_value.flags.bits(), - data: return_value.data.0, + data: return_value.data, })) } } if let Ok(output) = &call_outcome { - self.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| { - Some(RuntimeCosts::CallCopyOut(len)) - })?; + self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + )?; } Ok(Runtime::::exec_into_return_code(call_outcome)?) } fn instantiate( &mut self, + memory: &mut [u8], code_hash_ptr: u32, gas: u64, value_ptr: u32, @@ -699,15 +960,21 @@ where salt_ptr: u32, salt_len: u32, ) -> Result { + let gas = Weight::from_ref_time(gas); self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?; - let code_hash: CodeHash<::T> = self.read_sandbox_memory_as(code_hash_ptr)?; - let value: BalanceOf<::T> = self.read_sandbox_memory_as(value_ptr)?; - let input_data = self.read_sandbox_memory(input_data_ptr, input_data_len)?; - let salt = self.read_sandbox_memory(salt_ptr, salt_len)?; + let value: BalanceOf<::T> = self.read_sandbox_memory_as(memory, value_ptr)?; + if value > 0u32.into() { + self.charge_gas(RuntimeCosts::InstantiateSurchargeTransfer)?; + } + let code_hash: CodeHash<::T> = + self.read_sandbox_memory_as(memory, code_hash_ptr)?; + let input_data = self.read_sandbox_memory(memory, input_data_ptr, input_data_len)?; + let salt = self.read_sandbox_memory(memory, salt_ptr, salt_len)?; let instantiate_outcome = self.ext.instantiate(gas, code_hash, value, input_data, &salt); if let Ok((address, output)) = &instantiate_outcome { if !output.flags.contains(ReturnFlags::REVERT) { self.write_sandbox_output( + memory, address_ptr, address_len_ptr, &address.encode(), @@ -715,162 +982,333 @@ where already_charged, )?; } - self.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| { - Some(RuntimeCosts::InstantiateCopyOut(len)) - })?; + self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + )?; } Ok(Runtime::::exec_into_return_code(instantiate_outcome.map(|(_, retval)| retval))?) } - fn terminate(&mut self, beneficiary_ptr: u32) -> Result<(), TrapReason> { + fn terminate(&mut self, memory: &[u8], beneficiary_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Terminate)?; let beneficiary: <::T as frame_system::Config>::AccountId = - self.read_sandbox_memory_as(beneficiary_ptr)?; + self.read_sandbox_memory_as(memory, beneficiary_ptr)?; self.ext.terminate(&beneficiary)?; Err(TrapReason::Termination) } } -// *********************************************************** -// * AFTER MAKING A CHANGE MAKE SURE TO UPDATE COMPLEXITY.MD * -// *********************************************************** - -// Define a function `fn init_env() -> HostFunctionSet` that returns -// a function set which can be imported by an executed contract. +// This is the API exposed to contracts. // // # Note // // Any input that leads to a out of bound error (reading or writing) or failing to decode // data passed to the supervisor will lead to a trap. This is not documented explicitly // for every function. -define_env!(Env, , - // Account for used gas. Traps if gas used is greater than gas limit. - // - // NOTE: This is a implementation defined call and is NOT a part of the public API. - // This call is supposed to be called only by instrumentation injected code. - // - // - amount: How much gas is used. - [seal0] gas(ctx, amount: u32) => { +#[define_env] +pub mod env { + /// Account for used gas. Traps if gas used is greater than gas limit. + /// + /// NOTE: This is a implementation defined call and is NOT a part of the public API. + /// This call is supposed to be called only by instrumentation injected code. + /// + /// - amount: How much gas is used. + fn gas(ctx: _, _memory: _, amount: u64) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::MeteringBlock(amount))?; Ok(()) - }, + } - // Set the value at the given key in the contract storage. - // - // The value length must not exceed the maximum defined by the contracts module parameters. - // Storing an empty value is disallowed. - // - // # Parameters - // - // - `key_ptr`: pointer into the linear memory where the location to store the value is placed. - // - `value_ptr`: pointer into the linear memory where the value to set is placed. - // - `value_len`: the length of the value in bytes. - // - // # Traps - // - // - If value length exceeds the configured maximum value length of a storage entry. - // - Upon trying to set an empty storage entry (value length is 0). - [seal0] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => { - ctx.charge_gas(RuntimeCosts::SetStorage(value_len))?; - if value_len > ctx.ext.max_value_size() { - Err(Error::::ValueTooLarge)?; - } - let mut key: StorageKey = [0; 32]; - ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?; - let value = Some(ctx.read_sandbox_memory(value_ptr, value_len)?); - ctx.ext.set_storage(key, value).map_err(Into::into) - }, + /// Set the value at the given key in the contract storage. + /// + /// Equivalent to the newer version of `seal_set_storage` with the exception of the return + /// type. Still a valid thing to call when not interested in the return value. + #[prefixed_alias] + fn set_storage( + ctx: _, + memory: _, + key_ptr: u32, + value_ptr: u32, + value_len: u32, + ) -> Result<(), TrapReason> { + ctx.set_storage(memory, KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) + } - // Clear the value at the given key in the contract storage. - // - // # Parameters - // - // - `key_ptr`: pointer into the linear memory where the location to clear the value is placed. - [seal0] seal_clear_storage(ctx, key_ptr: u32) => { - ctx.charge_gas(RuntimeCosts::ClearStorage)?; - let mut key: StorageKey = [0; 32]; - ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?; - ctx.ext.set_storage(key, None).map_err(Into::into) - }, - - // Retrieve the value under the given key from storage. - // - // # Parameters - // - // - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. - // - `out_ptr`: pointer to the linear memory where the value is written to. - // - `out_len_ptr`: in-out pointer into linear memory where the buffer length - // is read from and the value length is written to. - // - // # Errors - // - // `ReturnCode::KeyNotFound` - [seal0] seal_get_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => { - ctx.charge_gas(RuntimeCosts::GetStorageBase)?; - let mut key: StorageKey = [0; 32]; - ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?; - if let Some(value) = ctx.ext.get_storage(&key) { - ctx.write_sandbox_output(out_ptr, out_len_ptr, &value, false, |len| { - Some(RuntimeCosts::GetStorageCopyOut(len)) - })?; + /// Set the value at the given key in the contract storage. + /// + /// This version is to be used with a fixed sized storage key. For runtimes supporting + /// transparent hashing, please use the newer version of this function. + /// + /// The value length must not exceed the maximum defined by the contracts module parameters. + /// Specifying a `value_len` of zero will store an empty value. + /// + /// # Parameters + /// + /// - `key_ptr`: pointer into the linear memory where the location to store the value is placed. + /// - `value_ptr`: pointer into the linear memory where the value to set is placed. + /// - `value_len`: the length of the value in bytes. + /// + /// # Return Value + /// + /// Returns the size of the pre-existing value at the specified key if any. Otherwise + /// `SENTINEL` is returned as a sentinel value. + #[version(1)] + #[prefixed_alias] + fn set_storage( + ctx: _, + memory: _, + key_ptr: u32, + value_ptr: u32, + value_len: u32, + ) -> Result { + ctx.set_storage(memory, KeyType::Fix, key_ptr, value_ptr, value_len) + } + + /// Set the value at the given key in the contract storage. + /// + /// The key and value lengths must not exceed the maximums defined by the contracts module + /// parameters. Specifying a `value_len` of zero will store an empty value. + /// + /// # Parameters + /// + /// - `key_ptr`: pointer into the linear memory where the location to store the value is placed. + /// - `key_len`: the length of the key in bytes. + /// - `value_ptr`: pointer into the linear memory where the value to set is placed. + /// - `value_len`: the length of the value in bytes. + /// + /// # Return Value + /// + /// Returns the size of the pre-existing value at the specified key if any. Otherwise + /// `SENTINEL` is returned as a sentinel value. + #[version(2)] + #[prefixed_alias] + fn set_storage( + ctx: _, + memory: _, + key_ptr: u32, + key_len: u32, + value_ptr: u32, + value_len: u32, + ) -> Result { + ctx.set_storage(memory, KeyType::Variable(key_len), key_ptr, value_ptr, value_len) + } + + /// Clear the value at the given key in the contract storage. + /// + /// Equivalent to the newer version of `seal_clear_storage` with the exception of the return + /// type. Still a valid thing to call when not interested in the return value. + #[prefixed_alias] + fn clear_storage(ctx: _, memory: _, key_ptr: u32) -> Result<(), TrapReason> { + ctx.clear_storage(memory, KeyType::Fix, key_ptr).map(|_| ()) + } + + /// Clear the value at the given key in the contract storage. + /// + /// # Parameters + /// + /// - `key_ptr`: pointer into the linear memory where the key is placed. + /// - `key_len`: the length of the key in bytes. + /// + /// # Return Value + /// + /// Returns the size of the pre-existing value at the specified key if any. Otherwise + /// `SENTINEL` is returned as a sentinel value. + #[version(1)] + #[prefixed_alias] + fn clear_storage(ctx: _, memory: _, key_ptr: u32, key_len: u32) -> Result { + ctx.clear_storage(memory, KeyType::Variable(key_len), key_ptr) + } + + /// Retrieve the value under the given key from storage. + /// + /// This version is to be used with a fixed sized storage key. For runtimes supporting + /// transparent hashing, please use the newer version of this function. + /// + /// # Parameters + /// + /// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. + /// - `out_ptr`: pointer to the linear memory where the value is written to. + /// - `out_len_ptr`: in-out pointer into linear memory where the buffer length is read from and + /// the value length is written to. + /// + /// # Errors + /// + /// `ReturnCode::KeyNotFound` + #[prefixed_alias] + fn get_storage( + ctx: _, + memory: _, + key_ptr: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + ctx.get_storage(memory, KeyType::Fix, key_ptr, out_ptr, out_len_ptr) + } + + /// Retrieve the value under the given key from storage. + /// + /// This version is to be used with a fixed sized storage key. For runtimes supporting + /// transparent hashing, please use the newer version of this function. + /// + /// The key length must not exceed the maximum defined by the contracts module parameter. + /// + /// # Parameters + /// + /// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. + /// - `key_len`: the length of the key in bytes. + /// - `out_ptr`: pointer to the linear memory where the value is written to. + /// - `out_len_ptr`: in-out pointer into linear memory where the buffer length is read from and + /// the value length is written to. + /// + /// # Errors + /// + /// `ReturnCode::KeyNotFound` + #[version(1)] + #[prefixed_alias] + fn get_storage( + ctx: _, + memory: _, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + ctx.get_storage(memory, KeyType::Variable(key_len), key_ptr, out_ptr, out_len_ptr) + } + + /// Checks whether there is a value stored under the given key. + /// + /// This version is to be used with a fixed sized storage key. For runtimes supporting + /// transparent hashing, please use the newer version of this function. + /// + /// # Parameters + /// + /// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. + /// + /// # Return Value + /// + /// Returns the size of the pre-existing value at the specified key if any. Otherwise + /// `SENTINEL` is returned as a sentinel value. + #[prefixed_alias] + fn contains_storage(ctx: _, memory: _, key_ptr: u32) -> Result { + ctx.contains_storage(memory, KeyType::Fix, key_ptr) + } + + /// Checks whether there is a value stored under the given key. + /// + /// The key length must not exceed the maximum defined by the contracts module parameter. + /// + /// # Parameters + /// + /// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. + /// - `key_len`: the length of the key in bytes. + /// + /// # Return Value + /// + /// Returns the size of the pre-existing value at the specified key if any. Otherwise + /// `SENTINEL` is returned as a sentinel value. + #[version(1)] + #[prefixed_alias] + fn contains_storage(ctx: _, memory: _, key_ptr: u32, key_len: u32) -> Result { + ctx.contains_storage(memory, KeyType::Variable(key_len), key_ptr) + } + + /// Retrieve and remove the value under the given key from storage. + /// + /// # Parameters + /// + /// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. + /// - `key_len`: the length of the key in bytes. + /// - `out_ptr`: pointer to the linear memory where the value is written to. + /// - `out_len_ptr`: in-out pointer into linear memory where the buffer length is read from and + /// the value length is written to. + /// + /// # Errors + /// + /// `ReturnCode::KeyNotFound` + #[unstable] + #[prefixed_alias] + fn take_storage( + ctx: _, + memory: _, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + let charged = ctx.charge_gas(RuntimeCosts::TakeStorage(ctx.ext.max_value_size()))?; + let key = ctx.read_sandbox_memory(memory, key_ptr, key_len)?; + if let crate::storage::WriteOutcome::Taken(value) = ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, + None, + true, + )? { + ctx.adjust_gas(charged, RuntimeCosts::TakeStorage(value.len() as u32)); + ctx.write_sandbox_output(memory, out_ptr, out_len_ptr, &value, false, already_charged)?; Ok(ReturnCode::Success) } else { + ctx.adjust_gas(charged, RuntimeCosts::TakeStorage(0)); Ok(ReturnCode::KeyNotFound) } - }, + } - // Transfer some value to another account. - // - // # Parameters - // - // - account_ptr: a pointer to the address of the beneficiary account - // Should be decodable as an `T::AccountId`. Traps otherwise. - // - account_len: length of the address buffer. - // - value_ptr: a pointer to the buffer with value, how much value to send. - // Should be decodable as a `T::Balance`. Traps otherwise. - // - value_len: length of the value buffer. - // - // # Errors - // - // `ReturnCode::BelowSubsistenceThreshold` - // `ReturnCode::TransferFailed` - [seal0] seal_transfer( - ctx, + /// Transfer some value to another account. + /// + /// # Parameters + /// + /// - account_ptr: a pointer to the address of the beneficiary account Should be decodable as an + /// `T::AccountId`. Traps otherwise. + /// - account_len: length of the address buffer. + /// - value_ptr: a pointer to the buffer with value, how much value to send. Should be decodable + /// as a `T::Balance`. Traps otherwise. + /// - value_len: length of the value buffer. + /// + /// # Errors + /// + /// `ReturnCode::TransferFailed` + #[prefixed_alias] + fn transfer( + ctx: _, + memory: _, account_ptr: u32, _account_len: u32, value_ptr: u32, - _value_len: u32 - ) -> ReturnCode => { + _value_len: u32, + ) -> Result { ctx.charge_gas(RuntimeCosts::Transfer)?; let callee: <::T as frame_system::Config>::AccountId = - ctx.read_sandbox_memory_as(account_ptr)?; - let value: BalanceOf<::T> = - ctx.read_sandbox_memory_as(value_ptr)?; - + ctx.read_sandbox_memory_as(memory, account_ptr)?; + let value: BalanceOf<::T> = ctx.read_sandbox_memory_as(memory, value_ptr)?; let result = ctx.ext.transfer(&callee, value); match result { Ok(()) => Ok(ReturnCode::Success), Err(err) => { let code = Runtime::::err_into_return_code(err)?; Ok(code) - } + }, } - }, + } - // Make a call to another contract. - // - // # Deprecation - // - // This is equivalent to calling the newer version of this function with - // `flags` set to `ALLOW_REENTRY`. See the newer version for documentation. - // - // # Note - // - // The values `_callee_len` and `_value_len` are ignored because the encoded sizes - // of those types are fixed through `[`MaxEncodedLen`]. The fields exist for backwards - // compatibility. Consider switching to the newest version of this function. - [seal0] seal_call( - ctx, + /// Make a call to another contract. + /// + /// # Deprecation + /// + /// This is equivalent to calling the newer version of this function with + /// `flags` set to `ALLOW_REENTRY`. See the newer version for documentation. + /// + /// # Note + /// + /// The values `_callee_len` and `_value_len` are ignored because the encoded sizes + /// of those types are fixed through `[`MaxEncodedLen`]. The fields exist for backwards + /// compatibility. Consider switching to the newest version of this function. + #[prefixed_alias] + fn call( + ctx: _, + memory: _, callee_ptr: u32, _callee_len: u32, gas: u64, @@ -879,52 +1317,53 @@ define_env!(Env, , input_data_ptr: u32, input_data_len: u32, output_ptr: u32, - output_len_ptr: u32 - ) -> ReturnCode => { + output_len_ptr: u32, + ) -> Result { ctx.call( + memory, CallFlags::ALLOW_REENTRY, - callee_ptr, - gas, - value_ptr, + CallType::Call { callee_ptr, value_ptr, gas }, input_data_ptr, input_data_len, output_ptr, output_len_ptr, ) - }, + } - // Make a call to another contract. - // - // The callees output buffer is copied to `output_ptr` and its length to `output_len_ptr`. - // The copy of the output buffer can be skipped by supplying the sentinel value - // of `u32::MAX` to `output_ptr`. - // - // # Parameters - // - // - flags: See [`CallFlags`] for a documenation of the supported flags. - // - callee_ptr: a pointer to the address of the callee contract. - // Should be decodable as an `T::AccountId`. Traps otherwise. - // - gas: how much gas to devote to the execution. - // - value_ptr: a pointer to the buffer with value, how much value to send. - // Should be decodable as a `T::Balance`. Traps otherwise. - // - input_data_ptr: a pointer to a buffer to be used as input data to the callee. - // - input_data_len: length of the input data buffer. - // - output_ptr: a pointer where the output buffer is copied to. - // - output_len_ptr: in-out pointer to where the length of the buffer is read from - // and the actual length is written to. - // - // # Errors - // - // An error means that the call wasn't successful output buffer is returned unless - // stated otherwise. - // - // `ReturnCode::CalleeReverted`: Output buffer is returned. - // `ReturnCode::CalleeTrapped` - // `ReturnCode::BelowSubsistenceThreshold` - // `ReturnCode::TransferFailed` - // `ReturnCode::NotCallable` - [__unstable__] seal_call( - ctx, + /// Make a call to another contract. + /// + /// The callees output buffer is copied to `output_ptr` and its length to `output_len_ptr`. + /// The copy of the output buffer can be skipped by supplying the sentinel value + /// of `SENTINEL` to `output_ptr`. + /// + /// # Parameters + /// + /// - flags: See [`CallFlags`] for a documenation of the supported flags. + /// - callee_ptr: a pointer to the address of the callee contract. Should be decodable as an + /// `T::AccountId`. Traps otherwise. + /// - gas: how much gas to devote to the execution. + /// - value_ptr: a pointer to the buffer with value, how much value to send. Should be decodable + /// as a `T::Balance`. Traps otherwise. + /// - input_data_ptr: a pointer to a buffer to be used as input data to the callee. + /// - input_data_len: length of the input data buffer. + /// - output_ptr: a pointer where the output buffer is copied to. + /// - output_len_ptr: in-out pointer to where the length of the buffer is read from and the + /// actual length is written to. + /// + /// # Errors + /// + /// An error means that the call wasn't successful output buffer is returned unless + /// stated otherwise. + /// + /// `ReturnCode::CalleeReverted`: Output buffer is returned. + /// `ReturnCode::CalleeTrapped` + /// `ReturnCode::TransferFailed` + /// `ReturnCode::NotCallable` + #[version(1)] + #[prefixed_alias] + fn call( + ctx: _, + memory: _, flags: u32, callee_ptr: u32, gas: u64, @@ -932,38 +1371,85 @@ define_env!(Env, , input_data_ptr: u32, input_data_len: u32, output_ptr: u32, - output_len_ptr: u32 - ) -> ReturnCode => { + output_len_ptr: u32, + ) -> Result { ctx.call( - CallFlags::from_bits(flags).ok_or_else(|| "used rerved bit in CallFlags")?, - callee_ptr, - gas, - value_ptr, + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::Call { callee_ptr, value_ptr, gas }, input_data_ptr, input_data_len, output_ptr, output_len_ptr, ) - }, + } - // Instantiate a contract with the specified code hash. - // - // # Deprecation - // - // This is equivalent to calling the newer version of this function. The newer version - // drops the now unnecessary length fields. - // - // # Note - // - // The values `_code_hash_len` and `_value_len` are ignored because the encoded sizes - // of those types are fixed through `[`MaxEncodedLen`]. The fields exist for backwards - // compatibility. Consider switching to the newest version of this function. - [seal0] seal_instantiate( - ctx, - code_hash_ptr: u32, - _code_hash_len: u32, - gas: u64, - value_ptr: u32, + /// Execute code in the context (storage, caller, value) of the current contract. + /// + /// Reentrancy protection is always disabled since the callee is allowed + /// to modify the callers storage. This makes going through a reentrancy attack + /// unnecessary for the callee when it wants to exploit the caller. + /// + /// # Parameters + /// + /// - flags: See [`CallFlags`] for a documentation of the supported flags. + /// - code_hash: a pointer to the hash of the code to be called. + /// - input_data_ptr: a pointer to a buffer to be used as input data to the callee. + /// - input_data_len: length of the input data buffer. + /// - output_ptr: a pointer where the output buffer is copied to. + /// - output_len_ptr: in-out pointer to where the length of the buffer is read from and the + /// actual length is written to. + /// + /// # Errors + /// + /// An error means that the call wasn't successful and no output buffer is returned unless + /// stated otherwise. + /// + /// `ReturnCode::CalleeReverted`: Output buffer is returned. + /// `ReturnCode::CalleeTrapped` + /// `ReturnCode::CodeNotFound` + #[prefixed_alias] + fn delegate_call( + ctx: _, + memory: _, + flags: u32, + code_hash_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + ctx.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::DelegateCall { code_hash_ptr }, + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// Instantiate a contract with the specified code hash. + /// + /// # Deprecation + /// + /// This is equivalent to calling the newer version of this function. The newer version + /// drops the now unnecessary length fields. + /// + /// # Note + /// + /// The values `_code_hash_len` and `_value_len` are ignored because the encoded sizes + /// of those types are fixed through `[`MaxEncodedLen`]. The fields exist for backwards + /// compatibility. Consider switching to the newest version of this function. + #[prefixed_alias] + fn instantiate( + ctx: _, + memory: _, + code_hash_ptr: u32, + _code_hash_len: u32, + gas: u64, + value_ptr: u32, _value_len: u32, input_data_ptr: u32, input_data_len: u32, @@ -972,9 +1458,10 @@ define_env!(Env, , output_ptr: u32, output_len_ptr: u32, salt_ptr: u32, - salt_len: u32 - ) -> ReturnCode => { - ctx.instantiate ( + salt_len: u32, + ) -> Result { + ctx.instantiate( + memory, code_hash_ptr, gas, value_ptr, @@ -987,53 +1474,53 @@ define_env!(Env, , salt_ptr, salt_len, ) - }, + } - // Instantiate a contract with the specified code hash. - // - // This function creates an account and executes the constructor defined in the code specified - // by the code hash. The address of this new account is copied to `address_ptr` and its length - // to `address_len_ptr`. The constructors output buffer is copied to `output_ptr` and its - // length to `output_len_ptr`. The copy of the output buffer and address can be skipped by - // supplying the sentinel value of `u32::MAX` to `output_ptr` or `address_ptr`. - // - // After running the constructor it is verified that the contract account holds at - // least the subsistence threshold. If that is not the case the instantiation fails and - // the contract is not created. - // - // # Parameters - // - // - code_hash_ptr: a pointer to the buffer that contains the initializer code. - // - gas: how much gas to devote to the execution of the initializer code. - // - value_ptr: a pointer to the buffer with value, how much value to send. - // Should be decodable as a `T::Balance`. Traps otherwise. - // - input_data_ptr: a pointer to a buffer to be used as input data to the initializer code. - // - input_data_len: length of the input data buffer. - // - address_ptr: a pointer where the new account's address is copied to. - // - address_len_ptr: in-out pointer to where the length of the buffer is read from - // and the actual length is written to. - // - output_ptr: a pointer where the output buffer is copied to. - // - output_len_ptr: in-out pointer to where the length of the buffer is read from - // and the actual length is written to. - // - salt_ptr: Pointer to raw bytes used for address derivation. See `fn contract_address`. - // - salt_len: length in bytes of the supplied salt. - // - // # Errors - // - // Please consult the `ReturnCode` enum declaration for more information on those - // errors. Here we only note things specific to this function. - // - // An error means that the account wasn't created and no address or output buffer - // is returned unless stated otherwise. - // - // `ReturnCode::CalleeReverted`: Output buffer is returned. - // `ReturnCode::CalleeTrapped` - // `ReturnCode::BelowSubsistenceThreshold` - // `ReturnCode::TransferFailed` - // `ReturnCode::NewContractNotFunded` - // `ReturnCode::CodeNotFound` - [seal1] seal_instantiate( - ctx, + /// Instantiate a contract with the specified code hash. + /// + /// This function creates an account and executes the constructor defined in the code specified + /// by the code hash. The address of this new account is copied to `address_ptr` and its length + /// to `address_len_ptr`. The constructors output buffer is copied to `output_ptr` and its + /// length to `output_len_ptr`. The copy of the output buffer and address can be skipped by + /// supplying the sentinel value of `SENTINEL` to `output_ptr` or `address_ptr`. + /// + /// `value` must be at least the minimum balance. Otherwise the instantiation fails and the + /// contract is not created. + /// + /// # Parameters + /// + /// - code_hash_ptr: a pointer to the buffer that contains the initializer code. + /// - gas: how much gas to devote to the execution of the initializer code. + /// - value_ptr: a pointer to the buffer with value, how much value to send. Should be decodable + /// as a `T::Balance`. Traps otherwise. + /// - input_data_ptr: a pointer to a buffer to be used as input data to the initializer code. + /// - input_data_len: length of the input data buffer. + /// - address_ptr: a pointer where the new account's address is copied to. + /// - address_len_ptr: in-out pointer to where the length of the buffer is read from and the + /// actual length is written to. + /// - output_ptr: a pointer where the output buffer is copied to. + /// - output_len_ptr: in-out pointer to where the length of the buffer is read from and the + /// actual length is written to. + /// - salt_ptr: Pointer to raw bytes used for address derivation. See `fn contract_address`. + /// - salt_len: length in bytes of the supplied salt. + /// + /// # Errors + /// + /// Please consult the `ReturnCode` enum declaration for more information on those + /// errors. Here we only note things specific to this function. + /// + /// An error means that the account wasn't created and no address or output buffer + /// is returned unless stated otherwise. + /// + /// `ReturnCode::CalleeReverted`: Output buffer is returned. + /// `ReturnCode::CalleeTrapped` + /// `ReturnCode::TransferFailed` + /// `ReturnCode::CodeNotFound` + #[version(1)] + #[prefixed_alias] + fn instantiate( + ctx: _, + memory: _, code_hash_ptr: u32, gas: u64, value_ptr: u32, @@ -1044,9 +1531,10 @@ define_env!(Env, , output_ptr: u32, output_len_ptr: u32, salt_ptr: u32, - salt_len: u32 - ) -> ReturnCode => { + salt_len: u32, + ) -> Result { ctx.instantiate( + memory, code_hash_ptr, gas, value_ptr, @@ -1059,307 +1547,504 @@ define_env!(Env, , salt_ptr, salt_len, ) - }, + } - // Remove the calling account and transfer remaining balance. - // - // # Deprecation - // - // This is equivalent to calling the newer version of this function. The newer version - // drops the now unnecessary length fields. - // - // # Note - // - // The value `_beneficiary_len` is ignored because the encoded sizes - // this type is fixed through `[`MaxEncodedLen`]. The field exist for backwards - // compatibility. Consider switching to the newest version of this function. - [seal0] seal_terminate(ctx, beneficiary_ptr: u32, _beneficiary_len: u32) => { - ctx.terminate(beneficiary_ptr) - }, - - // Remove the calling account and transfer remaining balance. - // - // This function never returns. Either the termination was successful and the - // execution of the destroyed contract is halted. Or it failed during the termination - // which is considered fatal and results in a trap + rollback. - // - // - beneficiary_ptr: a pointer to the address of the beneficiary account where all - // where all remaining funds of the caller are transferred. - // Should be decodable as an `T::AccountId`. Traps otherwise. - // - // # Traps - // - // - The contract is live i.e is already on the call stack. - // - Failed to send the balance to the beneficiary. - // - The deletion queue is full. - [seal1] seal_terminate(ctx, beneficiary_ptr: u32) => { - ctx.terminate(beneficiary_ptr) - }, - - // Stores the input passed by the caller into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - // - // # Note - // - // This function traps if the input was previously forwarded by a `seal_call`. - [seal0] seal_input(ctx, out_ptr: u32, out_len_ptr: u32) => { + /// Remove the calling account and transfer remaining balance. + /// + /// # Deprecation + /// + /// This is equivalent to calling the newer version of this function. The newer version + /// drops the now unnecessary length fields. + /// + /// # Note + /// + /// The value `_beneficiary_len` is ignored because the encoded sizes + /// this type is fixed through `[`MaxEncodedLen`]. The field exist for backwards + /// compatibility. Consider switching to the newest version of this function. + #[prefixed_alias] + fn terminate( + ctx: _, + memory: _, + beneficiary_ptr: u32, + _beneficiary_len: u32, + ) -> Result<(), TrapReason> { + ctx.terminate(memory, beneficiary_ptr) + } + + /// Remove the calling account and transfer remaining **free** balance. + /// + /// This function never returns. Either the termination was successful and the + /// execution of the destroyed contract is halted. Or it failed during the termination + /// which is considered fatal and results in a trap + rollback. + /// + /// - beneficiary_ptr: a pointer to the address of the beneficiary account where all where all + /// remaining funds of the caller are transferred. Should be decodable as an `T::AccountId`. + /// Traps otherwise. + /// + /// # Traps + /// + /// - The contract is live i.e is already on the call stack. + /// - Failed to send the balance to the beneficiary. + /// - The deletion queue is full. + #[version(1)] + #[prefixed_alias] + fn terminate(ctx: _, memory: _, beneficiary_ptr: u32) -> Result<(), TrapReason> { + ctx.terminate(memory, beneficiary_ptr) + } + + /// Stores the input passed by the caller into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + /// + /// # Note + /// + /// This function traps if the input was previously forwarded by a `seal_call`. + #[prefixed_alias] + fn input(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::InputBase)?; if let Some(input) = ctx.input_data.take() { - ctx.write_sandbox_output(out_ptr, out_len_ptr, &input, false, |len| { - Some(RuntimeCosts::InputCopyOut(len)) + ctx.write_sandbox_output(memory, out_ptr, out_len_ptr, &input, false, |len| { + Some(RuntimeCosts::CopyToContract(len)) })?; ctx.input_data = Some(input); Ok(()) } else { Err(Error::::InputForwarded.into()) } - }, + } - // Cease contract execution and save a data buffer as a result of the execution. - // - // This function never returns as it stops execution of the caller. - // This is the only way to return a data buffer to the caller. Returning from - // execution without calling this function is equivalent to calling: - // ``` - // seal_return(0, 0, 0); - // ``` - // - // The flags argument is a bitfield that can be used to signal special return - // conditions to the supervisor: - // --- lsb --- - // bit 0 : REVERT - Revert all storage changes made by the caller. - // bit [1, 31]: Reserved for future use. - // --- msb --- - // - // Using a reserved bit triggers a trap. - [seal0] seal_return(ctx, flags: u32, data_ptr: u32, data_len: u32) => { + /// Cease contract execution and save a data buffer as a result of the execution. + /// + /// This function never returns as it stops execution of the caller. + /// This is the only way to return a data buffer to the caller. Returning from + /// execution without calling this function is equivalent to calling: + /// ``` + /// seal_return(0, 0, 0); + /// ``` + /// + /// The flags argument is a bitfield that can be used to signal special return + /// conditions to the supervisor: + /// --- lsb --- + /// bit 0 : REVERT - Revert all storage changes made by the caller. + /// bit [1, 31]: Reserved for future use. + /// --- msb --- + /// + /// Using a reserved bit triggers a trap. + fn seal_return( + ctx: _, + memory: _, + flags: u32, + data_ptr: u32, + data_len: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Return(data_len))?; Err(TrapReason::Return(ReturnData { flags, - data: ctx.read_sandbox_memory(data_ptr, data_len)?, + data: ctx.read_sandbox_memory(memory, data_ptr, data_len)?, })) - }, + } - // Stores the address of the caller into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - // - // If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the - // extrinsic will be returned. Otherwise, if this call is initiated by another contract then the - // address of the contract will be returned. The value is encoded as T::AccountId. - [seal0] seal_caller(ctx, out_ptr: u32, out_len_ptr: u32) => { + /// Stores the address of the caller into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + /// + /// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the + /// extrinsic will be returned. Otherwise, if this call is initiated by another contract then + /// the address of the contract will be returned. The value is encoded as T::AccountId. + #[prefixed_alias] + fn caller(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Caller)?; Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &ctx.ext.caller().encode(), + false, + already_charged, )?) - }, + } - // Stores the address of the current contract into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - [seal0] seal_address(ctx, out_ptr: u32, out_len_ptr: u32) => { + /// Checks whether a specified address belongs to a contract. + /// + /// # Parameters + /// + /// - account_ptr: a pointer to the address of the beneficiary account Should be decodable as an + /// `T::AccountId`. Traps otherwise. + /// + /// Returned value is a u32-encoded boolean: (0 = false, 1 = true). + #[prefixed_alias] + fn is_contract(ctx: _, memory: _, account_ptr: u32) -> Result { + ctx.charge_gas(RuntimeCosts::IsContract)?; + let address: <::T as frame_system::Config>::AccountId = + ctx.read_sandbox_memory_as(memory, account_ptr)?; + + Ok(ctx.ext.is_contract(&address) as u32) + } + + /// Retrieve the code hash for a specified contract address. + /// + /// # Parameters + /// + /// - `account_ptr`: a pointer to the address in question. Should be decodable as an + /// `T::AccountId`. Traps otherwise. + /// - `out_ptr`: pointer to the linear memory where the returning value is written to. + /// - `out_len_ptr`: in-out pointer into linear memory where the buffer length is read from and + /// the value length is written to. + /// + /// # Errors + /// + /// `ReturnCode::KeyNotFound` + #[prefixed_alias] + fn code_hash( + ctx: _, + memory: _, + account_ptr: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + ctx.charge_gas(RuntimeCosts::CodeHash)?; + let address: <::T as frame_system::Config>::AccountId = + ctx.read_sandbox_memory_as(memory, account_ptr)?; + if let Some(value) = ctx.ext.code_hash(&address) { + ctx.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value.encode(), + false, + already_charged, + )?; + Ok(ReturnCode::Success) + } else { + Ok(ReturnCode::KeyNotFound) + } + } + + /// Retrieve the code hash of the currently executing contract. + /// + /// # Parameters + /// + /// - `out_ptr`: pointer to the linear memory where the returning value is written to. + /// - `out_len_ptr`: in-out pointer into linear memory where the buffer length is read from and + /// the value length is written to. + #[prefixed_alias] + fn own_code_hash(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + ctx.charge_gas(RuntimeCosts::OwnCodeHash)?; + let code_hash_encoded = &ctx.ext.own_code_hash().encode(); + Ok(ctx.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + code_hash_encoded, + false, + already_charged, + )?) + } + + /// Checks whether the caller of the current contract is the origin of the whole call stack. + /// + /// Prefer this over `seal_is_contract` when checking whether your contract is being called by a + /// contract or a plain account. The reason is that it performs better since it does not need to + /// do any storage lookups. + /// + /// A return value of`true` indicates that this contract is being called by a plain account + /// and `false` indicates that the caller is another contract. + /// + /// Returned value is a u32-encoded boolean: (0 = false, 1 = true). + #[prefixed_alias] + fn caller_is_origin(ctx: _, _memory: _) -> Result { + ctx.charge_gas(RuntimeCosts::CallerIsOrigin)?; + Ok(ctx.ext.caller_is_origin() as u32) + } + + /// Stores the address of the current contract into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + #[prefixed_alias] + fn address(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Address)?; Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.address().encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &ctx.ext.address().encode(), + false, + already_charged, )?) - }, + } - // Stores the price for the specified amount of gas into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - // - // The data is encoded as T::Balance. - // - // # Note - // - // It is recommended to avoid specifying very small values for `gas` as the prices for a single - // gas can be smaller than one. - [seal0] seal_weight_to_fee(ctx, gas: u64, out_ptr: u32, out_len_ptr: u32) => { + /// Stores the price for the specified amount of gas into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + /// + /// The data is encoded as T::Balance. + /// + /// # Note + /// + /// It is recommended to avoid specifying very small values for `gas` as the prices for a single + /// gas can be smaller than one. + #[prefixed_alias] + fn weight_to_fee( + ctx: _, + memory: _, + gas: u64, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + let gas = Weight::from_ref_time(gas); ctx.charge_gas(RuntimeCosts::WeightToFee)?; Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &ctx.ext.get_weight_price(gas).encode(), + false, + already_charged, )?) - }, + } - // Stores the amount of gas left into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - // - // The data is encoded as Gas. - [seal0] seal_gas_left(ctx, out_ptr: u32, out_len_ptr: u32) => { + /// Stores the amount of gas left into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + /// + /// The data is encoded as Gas. + #[prefixed_alias] + fn gas_left(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::GasLeft)?; - let gas_left = &ctx.ext.gas_meter().gas_left().encode(); + let gas_left = &ctx.ext.gas_meter().gas_left().ref_time().encode(); Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &gas_left, false, already_charged, + memory, + out_ptr, + out_len_ptr, + gas_left, + false, + already_charged, )?) - }, + } - // Stores the balance of the current account into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - // - // The data is encoded as T::Balance. - [seal0] seal_balance(ctx, out_ptr: u32, out_len_ptr: u32) => { + /// Stores the **free* balance of the current account into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + /// + /// The data is encoded as T::Balance. + #[prefixed_alias] + fn balance(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Balance)?; Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &ctx.ext.balance().encode(), + false, + already_charged, )?) - }, + } - // Stores the value transferred along with this call or as endowment into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - // - // The data is encoded as T::Balance. - [seal0] seal_value_transferred(ctx, out_ptr: u32, out_len_ptr: u32) => { + /// Stores the value transferred along with this call/instantiate into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + /// + /// The data is encoded as T::Balance. + #[prefixed_alias] + fn value_transferred( + ctx: _, + memory: _, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::ValueTransferred)?; Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &ctx.ext.value_transferred().encode(), + false, + already_charged, )?) - }, + } - // Stores a random number for the current block and the given subject into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - // - // The data is encoded as T::Hash. - // - // # Deprecation - // - // This function is deprecated. Users should migrate to the version in the "seal1" module. - [seal0] seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => { + /// Stores a random number for the current block and the given subject into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + /// + /// The data is encoded as T::Hash. + /// + /// # Deprecation + /// + /// This function is deprecated. Users should migrate to the version in the "seal1" module. + #[prefixed_alias] + fn random( + ctx: _, + memory: _, + subject_ptr: u32, + subject_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Random)?; if subject_len > ctx.ext.schedule().limits.subject_len { - Err(Error::::RandomSubjectTooLong)?; + return Err(Error::::RandomSubjectTooLong.into()) } - let subject_buf = ctx.read_sandbox_memory(subject_ptr, subject_len)?; + let subject_buf = ctx.read_sandbox_memory(memory, subject_ptr, subject_len)?; Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).0.encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &ctx.ext.random(&subject_buf).0.encode(), + false, + already_charged, )?) - }, + } - // Stores a random number for the current block and the given subject into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - // - // The data is encoded as (T::Hash, T::BlockNumber). - // - // # Changes from v0 - // - // In addition to the seed it returns the block number since which it was determinable - // by chain observers. - // - // # Note - // - // The returned seed should only be used to distinguish commitments made before - // the returned block number. If the block number is too early (i.e. commitments were - // made afterwards), then ensure no further commitments may be made and repeatedly - // call this on later blocks until the block number returned is later than the latest - // commitment. - [seal1] seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => { + /// Stores a random number for the current block and the given subject into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + /// + /// The data is encoded as (T::Hash, T::BlockNumber). + /// + /// # Changes from v0 + /// + /// In addition to the seed it returns the block number since which it was determinable + /// by chain observers. + /// + /// # Note + /// + /// The returned seed should only be used to distinguish commitments made before + /// the returned block number. If the block number is too early (i.e. commitments were + /// made afterwards), then ensure no further commitments may be made and repeatedly + /// call this on later blocks until the block number returned is later than the latest + /// commitment. + #[version(1)] + #[prefixed_alias] + fn random( + ctx: _, + memory: _, + subject_ptr: u32, + subject_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Random)?; if subject_len > ctx.ext.schedule().limits.subject_len { - Err(Error::::RandomSubjectTooLong)?; + return Err(Error::::RandomSubjectTooLong.into()) } - let subject_buf = ctx.read_sandbox_memory(subject_ptr, subject_len)?; + let subject_buf = ctx.read_sandbox_memory(memory, subject_ptr, subject_len)?; Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &ctx.ext.random(&subject_buf).encode(), + false, + already_charged, )?) - }, + } - // Load the latest block timestamp into the supplied buffer - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - [seal0] seal_now(ctx, out_ptr: u32, out_len_ptr: u32) => { + /// Load the latest block timestamp into the supplied buffer + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + #[prefixed_alias] + fn now(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Now)?; Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.now().encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &ctx.ext.now().encode(), + false, + already_charged, )?) - }, + } - // Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. - // - // The data is encoded as T::Balance. - [seal0] seal_minimum_balance(ctx, out_ptr: u32, out_len_ptr: u32) => { + /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// + /// The data is encoded as T::Balance. + #[prefixed_alias] + fn minimum_balance( + ctx: _, + memory: _, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::MinimumBalance)?; Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false, already_charged - )?) - }, - - // Stores the contract deposit into the supplied buffer. - // - // # Deprecation - // - // This is equivalent to calling `seal_contract_deposit` and only exists for backwards - // compatibility. See that function for documentation. - [seal0] seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => { - ctx.charge_gas(RuntimeCosts::ContractDeposit)?; - Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.contract_deposit().encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &ctx.ext.minimum_balance().encode(), + false, + already_charged, )?) - }, + } - // Stores the contract deposit into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - // - // The data is encoded as T::Balance. - // - // # Note - // - // The contract deposit is on top of the existential deposit. The sum - // is commonly referred as subsistence threshold in code. No contract initiated - // balance transfer can go below this threshold. - [seal0] seal_contract_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => { - ctx.charge_gas(RuntimeCosts::ContractDeposit)?; + /// Stores the tombstone deposit into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + /// + /// # Deprecation + /// + /// There is no longer a tombstone deposit. This function always returns 0. + #[prefixed_alias] + fn tombstone_deposit( + ctx: _, + memory: _, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + ctx.charge_gas(RuntimeCosts::Balance)?; + let deposit = >::zero().encode(); Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.contract_deposit().encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &deposit, + false, + already_charged, )?) - }, + } - // Was used to restore the given destination contract sacrificing the caller. - // - // # Note - // - // The state rent functionality was removed. This is stub only exists for - // backwards compatiblity - [seal0] seal_restore_to( - ctx, + /// Was used to restore the given destination contract sacrificing the caller. + /// + /// # Note + /// + /// The state rent functionality was removed. This is stub only exists for + /// backwards compatiblity + #[prefixed_alias] + fn restore_to( + ctx: _, + memory: _, _dest_ptr: u32, _dest_len: u32, _code_hash_ptr: u32, @@ -1367,349 +2052,408 @@ define_env!(Env, , _rent_allowance_ptr: u32, _rent_allowance_len: u32, _delta_ptr: u32, - _delta_count: u32 - ) => { + _delta_count: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::DebugMessage)?; Ok(()) - }, + } - // Was used to restore the given destination contract sacrificing the caller. - // - // # Note - // - // The state rent functionality was removed. This is stub only exists for - // backwards compatiblity - [seal1] seal_restore_to( - ctx, + /// Was used to restore the given destination contract sacrificing the caller. + /// + /// # Note + /// + /// The state rent functionality was removed. This is stub only exists for + /// backwards compatiblity + #[version(1)] + #[prefixed_alias] + fn restore_to( + ctx: _, + memory: _, _dest_ptr: u32, _code_hash_ptr: u32, _rent_allowance_ptr: u32, _delta_ptr: u32, - _delta_count: u32 - ) => { + _delta_count: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::DebugMessage)?; Ok(()) - }, + } - // Deposit a contract event with the data buffer and optional list of topics. There is a limit - // on the maximum number of topics specified by `event_topics`. - // - // - topics_ptr - a pointer to the buffer of topics encoded as `Vec`. The value of this - // is ignored if `topics_len` is set to 0. The topics list can't contain duplicates. - // - topics_len - the length of the topics buffer. Pass 0 if you want to pass an empty vector. - // - data_ptr - a pointer to a raw data buffer which will saved along the event. - // - data_len - the length of the data buffer. - [seal0] seal_deposit_event( - ctx, + /// Deposit a contract event with the data buffer and optional list of topics. There is a limit + /// on the maximum number of topics specified by `event_topics`. + /// + /// - topics_ptr - a pointer to the buffer of topics encoded as `Vec`. The value of + /// this is ignored if `topics_len` is set to 0. The topics list can't contain duplicates. + /// - topics_len - the length of the topics buffer. Pass 0 if you want to pass an empty vector. + /// - data_ptr - a pointer to a raw data buffer which will saved along the event. + /// - data_len - the length of the data buffer. + #[prefixed_alias] + fn deposit_event( + ctx: _, + memory: _, topics_ptr: u32, topics_len: u32, data_ptr: u32, - data_len: u32 - ) => { + data_len: u32, + ) -> Result<(), TrapReason> { fn has_duplicates(items: &mut Vec) -> bool { - // # Warning - // - // Unstable sorts are non-deterministic across architectures. The usage here is OK - // because we are rejecting duplicates which removes the non determinism. - items.sort_unstable(); + items.sort(); // Find any two consecutive equal elements. - items.windows(2).any(|w| { - match &w { - &[a, b] => a == b, - _ => false, - } + items.windows(2).any(|w| match &w { + &[a, b] => a == b, + _ => false, }) } let num_topic = topics_len .checked_div(sp_std::mem::size_of::>() as u32) - .ok_or_else(|| "Zero sized topics are not allowed")?; - ctx.charge_gas(RuntimeCosts::DepositEvent { - num_topic, - len: data_len, - })?; + .ok_or("Zero sized topics are not allowed")?; + ctx.charge_gas(RuntimeCosts::DepositEvent { num_topic, len: data_len })?; if data_len > ctx.ext.max_value_size() { - Err(Error::::ValueTooLarge)?; + return Err(Error::::ValueTooLarge.into()) } - let mut topics: Vec::::T>> = match topics_len { + let mut topics: Vec::T>> = match topics_len { 0 => Vec::new(), - _ => ctx.read_sandbox_memory_as_unbounded(topics_ptr, topics_len)?, + _ => ctx.read_sandbox_memory_as_unbounded(memory, topics_ptr, topics_len)?, }; // If there are more than `event_topics`, then trap. if topics.len() > ctx.ext.schedule().limits.event_topics as usize { - Err(Error::::TooManyTopics)?; + return Err(Error::::TooManyTopics.into()) } // Check for duplicate topics. If there are any, then trap. // Complexity O(n * log(n)) and no additional allocations. // This also sorts the topics. if has_duplicates(&mut topics) { - Err(Error::::DuplicateTopics)?; + return Err(Error::::DuplicateTopics.into()) } - let event_data = ctx.read_sandbox_memory(data_ptr, data_len)?; + let event_data = ctx.read_sandbox_memory(memory, data_ptr, data_len)?; ctx.ext.deposit_event(topics, event_data); Ok(()) - }, + } - // Was used to set rent allowance of the contract. - // - // # Note - // - // The state rent functionality was removed. This is stub only exists for - // backwards compatiblity. - [seal0] seal_set_rent_allowance(ctx, _value_ptr: u32, _value_len: u32) => { + /// Was used to set rent allowance of the contract. + /// + /// # Note + /// + /// The state rent functionality was removed. This is stub only exists for + /// backwards compatiblity. + #[prefixed_alias] + fn set_rent_allowance( + ctx: _, + memory: _, + _value_ptr: u32, + _value_len: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::DebugMessage)?; Ok(()) - }, + } - // Was used to set rent allowance of the contract. - // - // # Note - // - // The state rent functionality was removed. This is stub only exists for - // backwards compatiblity. - [seal1] seal_set_rent_allowance(ctx, _value_ptr: u32) => { + /// Was used to set rent allowance of the contract. + /// + /// # Note + /// + /// The state rent functionality was removed. This is stub only exists for + /// backwards compatiblity. + #[version(1)] + #[prefixed_alias] + fn set_rent_allowance(ctx: _, _memory: _, _value_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::DebugMessage)?; Ok(()) - }, + } - // Was used to store the rent allowance into the supplied buffer. - // - // # Note - // - // The state rent functionality was removed. This is stub only exists for - // backwards compatiblity. - [seal0] seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => { + /// Was used to store the rent allowance into the supplied buffer. + /// + /// # Note + /// + /// The state rent functionality was removed. This is stub only exists for + /// backwards compatiblity. + #[prefixed_alias] + fn rent_allowance(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Balance)?; let rent_allowance = >::max_value().encode(); Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &rent_allowance, false, already_charged + memory, + out_ptr, + out_len_ptr, + &rent_allowance, + false, + already_charged, )?) - }, + } - // Stores the current block number of the current contract into the supplied buffer. - // - // The value is stored to linear memory at the address pointed to by `out_ptr`. - // `out_len_ptr` must point to a u32 value that describes the available space at - // `out_ptr`. This call overwrites it with the size of the value. If the available - // space at `out_ptr` is less than the size of the value a trap is triggered. - [seal0] seal_block_number(ctx, out_ptr: u32, out_len_ptr: u32) => { + /// Stores the current block number of the current contract into the supplied buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_ptr`. This call overwrites it with the size of the value. If the available + /// space at `out_ptr` is less than the size of the value a trap is triggered. + #[prefixed_alias] + fn block_number(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::BlockNumber)?; Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false, already_charged + memory, + out_ptr, + out_len_ptr, + &ctx.ext.block_number().encode(), + false, + already_charged, )?) - }, + } - // Computes the SHA2 256-bit hash on the given input buffer. - // - // Returns the result directly into the given output buffer. - // - // # Note - // - // - The `input` and `output` buffer may overlap. - // - The output buffer is expected to hold at least 32 bytes (256 bits). - // - It is the callers responsibility to provide an output buffer that - // is large enough to hold the expected amount of bytes returned by the - // chosen hash function. - // - // # Parameters - // - // - `input_ptr`: the pointer into the linear memory where the input - // data is placed. - // - `input_len`: the length of the input data in bytes. - // - `output_ptr`: the pointer into the linear memory where the output - // data is placed. The function will write the result - // directly into this buffer. - [seal0] seal_hash_sha2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + /// Computes the SHA2 256-bit hash on the given input buffer. + /// + /// Returns the result directly into the given output buffer. + /// + /// # Note + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 32 bytes (256 bits). + /// - It is the callers responsibility to provide an output buffer that is large enough to hold + /// the expected amount of bytes returned by the chosen hash function. + /// + /// # Parameters + /// + /// - `input_ptr`: the pointer into the linear memory where the input data is placed. + /// - `input_len`: the length of the input data in bytes. + /// - `output_ptr`: the pointer into the linear memory where the output data is placed. The + /// function will write the result directly into this buffer. + #[prefixed_alias] + fn hash_sha2_256( + ctx: _, + memory: _, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::HashSha256(input_len))?; - Ok(ctx.compute_hash_on_intermediate_buffer(sha2_256, input_ptr, input_len, output_ptr)?) - }, + Ok(ctx.compute_hash_on_intermediate_buffer( + memory, sha2_256, input_ptr, input_len, output_ptr, + )?) + } - // Computes the KECCAK 256-bit hash on the given input buffer. - // - // Returns the result directly into the given output buffer. - // - // # Note - // - // - The `input` and `output` buffer may overlap. - // - The output buffer is expected to hold at least 32 bytes (256 bits). - // - It is the callers responsibility to provide an output buffer that - // is large enough to hold the expected amount of bytes returned by the - // chosen hash function. - // - // # Parameters - // - // - `input_ptr`: the pointer into the linear memory where the input - // data is placed. - // - `input_len`: the length of the input data in bytes. - // - `output_ptr`: the pointer into the linear memory where the output - // data is placed. The function will write the result - // directly into this buffer. - [seal0] seal_hash_keccak_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + /// Computes the KECCAK 256-bit hash on the given input buffer. + /// + /// Returns the result directly into the given output buffer. + /// + /// # Note + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 32 bytes (256 bits). + /// - It is the callers responsibility to provide an output buffer that is large enough to hold + /// the expected amount of bytes returned by the chosen hash function. + /// + /// # Parameters + /// + /// - `input_ptr`: the pointer into the linear memory where the input data is placed. + /// - `input_len`: the length of the input data in bytes. + /// - `output_ptr`: the pointer into the linear memory where the output data is placed. The + /// function will write the result directly into this buffer. + #[prefixed_alias] + fn hash_keccak_256( + ctx: _, + memory: _, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::HashKeccak256(input_len))?; - Ok(ctx.compute_hash_on_intermediate_buffer(keccak_256, input_ptr, input_len, output_ptr)?) - }, + Ok(ctx.compute_hash_on_intermediate_buffer( + memory, keccak_256, input_ptr, input_len, output_ptr, + )?) + } - // Computes the BLAKE2 256-bit hash on the given input buffer. - // - // Returns the result directly into the given output buffer. - // - // # Note - // - // - The `input` and `output` buffer may overlap. - // - The output buffer is expected to hold at least 32 bytes (256 bits). - // - It is the callers responsibility to provide an output buffer that - // is large enough to hold the expected amount of bytes returned by the - // chosen hash function. - // - // # Parameters - // - // - `input_ptr`: the pointer into the linear memory where the input - // data is placed. - // - `input_len`: the length of the input data in bytes. - // - `output_ptr`: the pointer into the linear memory where the output - // data is placed. The function will write the result - // directly into this buffer. - [seal0] seal_hash_blake2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + /// Computes the BLAKE2 256-bit hash on the given input buffer. + /// + /// Returns the result directly into the given output buffer. + /// + /// # Note + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 32 bytes (256 bits). + /// - It is the callers responsibility to provide an output buffer that is large enough to hold + /// the expected amount of bytes returned by the chosen hash function. + /// + /// # Parameters + /// + /// - `input_ptr`: the pointer into the linear memory where the input data is placed. + /// - `input_len`: the length of the input data in bytes. + /// - `output_ptr`: the pointer into the linear memory where the output data is placed. The + /// function will write the result directly into this buffer. + #[prefixed_alias] + fn hash_blake2_256( + ctx: _, + memory: _, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::HashBlake256(input_len))?; - Ok(ctx.compute_hash_on_intermediate_buffer(blake2_256, input_ptr, input_len, output_ptr)?) - }, + Ok(ctx.compute_hash_on_intermediate_buffer( + memory, blake2_256, input_ptr, input_len, output_ptr, + )?) + } - // Computes the BLAKE2 128-bit hash on the given input buffer. - // - // Returns the result directly into the given output buffer. - // - // # Note - // - // - The `input` and `output` buffer may overlap. - // - The output buffer is expected to hold at least 16 bytes (128 bits). - // - It is the callers responsibility to provide an output buffer that - // is large enough to hold the expected amount of bytes returned by the - // chosen hash function. - // - // # Parameters - // - // - `input_ptr`: the pointer into the linear memory where the input - // data is placed. - // - `input_len`: the length of the input data in bytes. - // - `output_ptr`: the pointer into the linear memory where the output - // data is placed. The function will write the result - // directly into this buffer. - [seal0] seal_hash_blake2_128(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + /// Computes the BLAKE2 128-bit hash on the given input buffer. + /// + /// Returns the result directly into the given output buffer. + /// + /// # Note + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 16 bytes (128 bits). + /// - It is the callers responsibility to provide an output buffer that is large enough to hold + /// the expected amount of bytes returned by the chosen hash function. + /// + /// # Parameters + /// + /// - `input_ptr`: the pointer into the linear memory where the input data is placed. + /// - `input_len`: the length of the input data in bytes. + /// - `output_ptr`: the pointer into the linear memory where the output data is placed. The + /// function will write the result directly into this buffer. + #[prefixed_alias] + fn hash_blake2_128( + ctx: _, + memory: _, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::HashBlake128(input_len))?; - Ok(ctx.compute_hash_on_intermediate_buffer(blake2_128, input_ptr, input_len, output_ptr)?) - }, + Ok(ctx.compute_hash_on_intermediate_buffer( + memory, blake2_128, input_ptr, input_len, output_ptr, + )?) + } - // Call into the chain extension provided by the chain if any. - // - // Handling of the input values is up to the specific chain extension and so is the - // return value. The extension can decide to use the inputs as primitive inputs or as - // in/out arguments by interpreting them as pointers. Any caller of this function - // must therefore coordinate with the chain that it targets. - // - // # Note - // - // If no chain extension exists the contract will trap with the `NoChainExtension` - // module error. - [seal0] seal_call_chain_extension( - ctx, - func_id: u32, + /// Call into the chain extension provided by the chain if any. + /// + /// Handling of the input values is up to the specific chain extension and so is the + /// return value. The extension can decide to use the inputs as primitive inputs or as + /// in/out arguments by interpreting them as pointers. Any caller of this function + /// must therefore coordinate with the chain that it targets. + /// + /// # Note + /// + /// If no chain extension exists the contract will trap with the `NoChainExtension` + /// module error. + #[prefixed_alias] + fn call_chain_extension( + ctx: _, + memory: _, + id: u32, input_ptr: u32, input_len: u32, output_ptr: u32, - output_len_ptr: u32 - ) -> u32 => { + output_len_ptr: u32, + ) -> Result { use crate::chain_extension::{ChainExtension, Environment, RetVal}; - if ::ChainExtension::enabled() == false { - Err(Error::::NoChainExtension)?; + if !::ChainExtension::enabled() { + return Err(Error::::NoChainExtension.into()) } - let env = Environment::new(ctx, input_ptr, input_len, output_ptr, output_len_ptr); - match ::ChainExtension::call(func_id, env)? { + let mut chain_extension = ctx.chain_extension.take().expect( + "Constructor initializes with `Some`. This is the only place where it is set to `None`.\ + It is always reset to `Some` afterwards. qed" + ); + let env = + Environment::new(ctx, memory, id, input_ptr, input_len, output_ptr, output_len_ptr); + let ret = match chain_extension.call(env)? { RetVal::Converging(val) => Ok(val), - RetVal::Diverging{flags, data} => Err(TrapReason::Return(ReturnData { - flags: flags.bits(), - data, - })), - } - }, + RetVal::Diverging { flags, data } => + Err(TrapReason::Return(ReturnData { flags: flags.bits(), data })), + }; + ctx.chain_extension = Some(chain_extension); + ret + } - // Emit a custom debug message. - // - // No newlines are added to the supplied message. - // Specifying invalid UTF-8 triggers a trap. - // - // This is a no-op if debug message recording is disabled which is always the case - // when the code is executing on-chain. The message is interpreted as UTF-8 and - // appended to the debug buffer which is then supplied to the calling RPC client. - // - // # Note - // - // Even though no action is taken when debug message recording is disabled there is still - // a non trivial overhead (and weight cost) associated with calling this function. Contract - // languages should remove calls to this function (either at runtime or compile time) when - // not being executed as an RPC. For example, they could allow users to disable logging - // through compile time flags (cargo features) for on-chain deployment. Additionally, the - // return value of this function can be cached in order to prevent further calls at runtime. - [seal0] seal_debug_message(ctx, str_ptr: u32, str_len: u32) -> ReturnCode => { + /// Emit a custom debug message. + /// + /// No newlines are added to the supplied message. + /// Specifying invalid UTF-8 triggers a trap. + /// + /// This is a no-op if debug message recording is disabled which is always the case + /// when the code is executing on-chain. The message is interpreted as UTF-8 and + /// appended to the debug buffer which is then supplied to the calling RPC client. + /// + /// # Note + /// + /// Even though no action is taken when debug message recording is disabled there is still + /// a non trivial overhead (and weight cost) associated with calling this function. Contract + /// languages should remove calls to this function (either at runtime or compile time) when + /// not being executed as an RPC. For example, they could allow users to disable logging + /// through compile time flags (cargo features) for on-chain deployment. Additionally, the + /// return value of this function can be cached in order to prevent further calls at runtime. + #[prefixed_alias] + fn debug_message( + ctx: _, + memory: _, + str_ptr: u32, + str_len: u32, + ) -> Result { ctx.charge_gas(RuntimeCosts::DebugMessage)?; - if ctx.ext.append_debug_buffer("") { - let data = ctx.read_sandbox_memory(str_ptr, str_len)?; - let msg = core::str::from_utf8(&data) - .map_err(|_| >::DebugMessageInvalidUTF8)?; - ctx.ext.append_debug_buffer(msg); - return Ok(ReturnCode::Success); + if ctx.ext.append_debug_buffer("")? { + let data = ctx.read_sandbox_memory(memory, str_ptr, str_len)?; + let msg = + core::str::from_utf8(&data).map_err(|_| >::DebugMessageInvalidUTF8)?; + ctx.ext.append_debug_buffer(msg)?; + return Ok(ReturnCode::Success) } Ok(ReturnCode::LoggingDisabled) - }, + } - // Call some dispatchable of the runtime. - // - // This function decodes the passed in data as the overarching `Call` type of the - // runtime and dispatches it. The weight as specified in the runtime is charged - // from the gas meter. Any weight refunds made by the dispatchable are considered. - // - // The filter specified by `Config::CallFilter` is attached to the origin of - // the dispatched call. - // - // # Parameters - // - // - `input_ptr`: the pointer into the linear memory where the input data is placed. - // - `input_len`: the length of the input data in bytes. - // - // # Return Value - // - // Returns `ReturnCode::Success` when the dispatchable was succesfully executed and - // returned `Ok`. When the dispatchable was exeuted but returned an error - // `ReturnCode::CallRuntimeReturnedError` is returned. The full error is not - // provided because it is not guaranteed to be stable. - // - // # Comparison with `ChainExtension` - // - // Just as a chain extension this API allows the runtime to extend the functionality - // of contracts. While making use of this function is generelly easier it cannot be - // used in call cases. Consider writing a chain extension if you need to do perform - // one of the following tasks: - // - // - Return data. - // - Provide functionality **exclusively** to contracts. - // - Provide custom weights. - // - Avoid the need to keep the `Call` data structure stable. - // - // # Unstable - // - // This function is unstable and subject to change (or removal) in the future. Do not - // deploy a contract using it to a production chain. - [__unstable__] seal_call_runtime(ctx, call_ptr: u32, call_len: u32) -> ReturnCode => { - use frame_support::{dispatch::GetDispatchInfo, weights::extract_actual_weight}; - ctx.charge_gas(RuntimeCosts::CopyIn(call_len))?; - let call: ::Call = ctx.read_sandbox_memory_as_unbounded( - call_ptr, call_len - )?; + /// Call some dispatchable of the runtime. + /// + /// This function decodes the passed in data as the overarching `Call` type of the + /// runtime and dispatches it. The weight as specified in the runtime is charged + /// from the gas meter. Any weight refunds made by the dispatchable are considered. + /// + /// The filter specified by `Config::CallFilter` is attached to the origin of + /// the dispatched call. + /// + /// # Parameters + /// + /// - `input_ptr`: the pointer into the linear memory where the input data is placed. + /// - `input_len`: the length of the input data in bytes. + /// + /// # Return Value + /// + /// Returns `ReturnCode::Success` when the dispatchable was succesfully executed and + /// returned `Ok`. When the dispatchable was exeuted but returned an error + /// `ReturnCode::CallRuntimeReturnedError` is returned. The full error is not + /// provided because it is not guaranteed to be stable. + /// + /// # Comparison with `ChainExtension` + /// + /// Just as a chain extension this API allows the runtime to extend the functionality + /// of contracts. While making use of this function is generelly easier it cannot be + /// used in call cases. Consider writing a chain extension if you need to do perform + /// one of the following tasks: + /// + /// - Return data. + /// - Provide functionality **exclusively** to contracts. + /// - Provide custom weights. + /// - Avoid the need to keep the `Call` data structure stable. + /// + /// # Unstable + /// + /// This function is unstable and subject to change (or removal) in the future. Do not + /// deploy a contract using it to a production chain. + #[unstable] + #[prefixed_alias] + fn call_runtime( + ctx: _, + memory: _, + call_ptr: u32, + call_len: u32, + ) -> Result { + use frame_support::dispatch::{extract_actual_weight, GetDispatchInfo}; + ctx.charge_gas(RuntimeCosts::CopyFromContract(call_len))?; + let call: ::RuntimeCall = + ctx.read_sandbox_memory_as_unbounded(memory, call_ptr, call_len)?; let dispatch_info = call.get_dispatch_info(); let charged = ctx.charge_gas(RuntimeCosts::CallRuntime(dispatch_info.weight))?; let result = ctx.ext.call_runtime(call); @@ -1719,33 +2463,39 @@ define_env!(Env, , Ok(_) => Ok(ReturnCode::Success), Err(_) => Ok(ReturnCode::CallRuntimeReturnedError), } - }, + } - // Recovers the ECDSA public key from the given message hash and signature. - // - // Writes the public key into the given output buffer. - // Assumes the secp256k1 curve. - // - // # Parameters - // - // - `signature_ptr`: the pointer into the linear memory where the signature - // is placed. Should be decodable as a 65 bytes. Traps otherwise. - // - `message_hash_ptr`: the pointer into the linear memory where the message - // hash is placed. Should be decodable as a 32 bytes. Traps otherwise. - // - `output_ptr`: the pointer into the linear memory where the output - // data is placed. The buffer should be 33 bytes. Traps otherwise. - // The function will write the result directly into this buffer. - // - // # Errors - // - // `ReturnCode::EcdsaRecoverFailed` - [__unstable__] seal_ecdsa_recover(ctx, signature_ptr: u32, message_hash_ptr: u32, output_ptr: u32) -> ReturnCode => { + /// Recovers the ECDSA public key from the given message hash and signature. + /// + /// Writes the public key into the given output buffer. + /// Assumes the secp256k1 curve. + /// + /// # Parameters + /// + /// - `signature_ptr`: the pointer into the linear memory where the signature is placed. Should + /// be decodable as a 65 bytes. Traps otherwise. + /// - `message_hash_ptr`: the pointer into the linear memory where the message hash is placed. + /// Should be decodable as a 32 bytes. Traps otherwise. + /// - `output_ptr`: the pointer into the linear memory where the output data is placed. The + /// buffer should be 33 bytes. The function will write the result directly into this buffer. + /// + /// # Errors + /// + /// `ReturnCode::EcdsaRecoverFailed` + #[prefixed_alias] + fn ecdsa_recover( + ctx: _, + memory: _, + signature_ptr: u32, + message_hash_ptr: u32, + output_ptr: u32, + ) -> Result { ctx.charge_gas(RuntimeCosts::EcdsaRecovery)?; let mut signature: [u8; 65] = [0; 65]; - ctx.read_sandbox_memory_into_buf(signature_ptr, &mut signature)?; + ctx.read_sandbox_memory_into_buf(memory, signature_ptr, &mut signature)?; let mut message_hash: [u8; 32] = [0; 32]; - ctx.read_sandbox_memory_into_buf(message_hash_ptr, &mut message_hash)?; + ctx.read_sandbox_memory_into_buf(memory, message_hash_ptr, &mut message_hash)?; let result = ctx.ext.ecdsa_recover(&signature, &message_hash); @@ -1753,11 +2503,128 @@ define_env!(Env, , Ok(pub_key) => { // Write the recovered compressed ecdsa public key back into the sandboxed output // buffer. - ctx.write_sandbox_memory(output_ptr, pub_key.as_ref())?; + ctx.write_sandbox_memory(memory, output_ptr, pub_key.as_ref())?; + + Ok(ReturnCode::Success) + }, + Err(_) => Ok(ReturnCode::EcdsaRecoverFailed), + } + } + + /// Replace the contract code at the specified address with new code. + /// + /// # Note + /// + /// There are a couple of important considerations which must be taken into account when + /// using this API: + /// + /// 1. The storage at the code address will remain untouched. This means that contract + /// developers must ensure that the storage layout of the new code is compatible with that of + /// the old code. + /// + /// 2. Contracts using this API can't be assumed as having deterministic addresses. Said another + /// way, when using this API you lose the guarantee that an address always identifies a specific + /// code hash. + /// + /// 3. If a contract calls into itself after changing its code the new call would use + /// the new code. However, if the original caller panics after returning from the sub call it + /// would revert the changes made by `seal_set_code_hash` and the next caller would use + /// the old code. + /// + /// # Parameters + /// + /// - `code_hash_ptr`: A pointer to the buffer that contains the new code hash. + /// + /// # Errors + /// + /// `ReturnCode::CodeNotFound` + #[prefixed_alias] + fn set_code_hash(ctx: _, memory: _, code_hash_ptr: u32) -> Result { + ctx.charge_gas(RuntimeCosts::SetCodeHash)?; + let code_hash: CodeHash<::T> = + ctx.read_sandbox_memory_as(memory, code_hash_ptr)?; + match ctx.ext.set_code_hash(code_hash) { + Err(err) => { + let code = Runtime::::err_into_return_code(err)?; + Ok(code) + }, + Ok(()) => Ok(ReturnCode::Success), + } + } + /// Calculates Ethereum address from the ECDSA compressed public key and stores + /// it into the supplied buffer. + /// + /// # Parameters + /// + /// - `key_ptr`: a pointer to the ECDSA compressed public key. Should be decodable as a 33 bytes + /// value. Traps otherwise. + /// - `out_ptr`: the pointer into the linear memory where the output data is placed. The + /// function will write the result directly into this buffer. + /// + /// The value is stored to linear memory at the address pointed to by `out_ptr`. + /// If the available space at `out_ptr` is less than the size of the value a trap is triggered. + /// + /// # Errors + /// + /// `ReturnCode::EcdsaRecoverFailed` + #[prefixed_alias] + fn ecdsa_to_eth_address( + ctx: _, + memory: _, + key_ptr: u32, + out_ptr: u32, + ) -> Result { + ctx.charge_gas(RuntimeCosts::EcdsaToEthAddress)?; + let mut compressed_key: [u8; 33] = [0; 33]; + ctx.read_sandbox_memory_into_buf(memory, key_ptr, &mut compressed_key)?; + let result = ctx.ext.ecdsa_to_eth_address(&compressed_key); + match result { + Ok(eth_address) => { + ctx.write_sandbox_memory(memory, out_ptr, eth_address.as_ref())?; Ok(ReturnCode::Success) }, Err(_) => Ok(ReturnCode::EcdsaRecoverFailed), } - }, -); + } + + /// Returns the number of times the currently executing contract exists on the call stack in + /// addition to the calling instance. + /// + /// # Return Value + /// + /// Returns 0 when there is no reentrancy. + #[unstable] + fn reentrance_count(ctx: _, memory: _) -> Result { + ctx.charge_gas(RuntimeCosts::ReentrantCount)?; + Ok(ctx.ext.reentrance_count()) + } + + /// Returns the number of times specified contract exists on the call stack. Delegated calls are + /// not counted as separate calls. + /// + /// # Parameters + /// + /// - `account_ptr`: a pointer to the contract address. + /// + /// # Return Value + /// + /// Returns 0 when the contract does not exist on the call stack. + #[unstable] + fn account_reentrance_count(ctx: _, memory: _, account_ptr: u32) -> Result { + ctx.charge_gas(RuntimeCosts::AccountEntranceCount)?; + let account_id: <::T as frame_system::Config>::AccountId = + ctx.read_sandbox_memory_as(memory, account_ptr)?; + Ok(ctx.ext.account_reentrance_count(&account_id)) + } + + /// Returns a nonce that is unique per contract instantiation. + /// + /// The nonce is incremented for each succesful contract instantiation. This is a + /// sensible default salt for contract instantiations. + #[unstable] + fn instantiation_nonce(ctx: _, _memory: _) -> Result { + ctx.charge_gas(RuntimeCosts::InstantationNonce)?; + Ok(ctx.ext.nonce()) + } +} diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index 1cebcb3b5d9a0..c3f3b50097278 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,24 +18,27 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-09-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-12-01, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark -// --chain=dev +// pallet // --steps=50 // --repeat=20 -// --pallet=pallet_contracts // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_contracts +// --chain=dev +// --header=./HEADER-APACHE2 // --output=./frame/contracts/src/weights.rs // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -45,22 +48,27 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_contracts. pub trait WeightInfo { - fn on_initialize() -> Weight; + fn on_process_deletion_queue_batch() -> Weight; fn on_initialize_per_trie_key(k: u32, ) -> Weight; fn on_initialize_per_queue_item(q: u32, ) -> Weight; - fn instrument(c: u32, ) -> Weight; - fn code_load(c: u32, ) -> Weight; - fn code_refcount(c: u32, ) -> Weight; + fn reinstrument(c: u32, ) -> Weight; + fn call_with_code_per_byte(c: u32, ) -> Weight; fn instantiate_with_code(c: u32, s: u32, ) -> Weight; fn instantiate(s: u32, ) -> Weight; fn call() -> Weight; + fn upload_code(c: u32, ) -> Weight; + fn remove_code() -> Weight; + fn set_code() -> Weight; fn seal_caller(r: u32, ) -> Weight; + fn seal_is_contract(r: u32, ) -> Weight; + fn seal_code_hash(r: u32, ) -> Weight; + fn seal_own_code_hash(r: u32, ) -> Weight; + fn seal_caller_is_origin(r: u32, ) -> Weight; fn seal_address(r: u32, ) -> Weight; fn seal_gas_left(r: u32, ) -> Weight; fn seal_balance(r: u32, ) -> Weight; fn seal_value_transferred(r: u32, ) -> Weight; fn seal_minimum_balance(r: u32, ) -> Weight; - fn seal_tombstone_deposit(r: u32, ) -> Weight; fn seal_block_number(r: u32, ) -> Weight; fn seal_now(r: u32, ) -> Weight; fn seal_weight_to_fee(r: u32, ) -> Weight; @@ -75,15 +83,22 @@ pub trait WeightInfo { fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight; fn seal_debug_message(r: u32, ) -> Weight; fn seal_set_storage(r: u32, ) -> Weight; - fn seal_set_storage_per_kb(n: u32, ) -> Weight; + fn seal_set_storage_per_new_kb(n: u32, ) -> Weight; + fn seal_set_storage_per_old_kb(n: u32, ) -> Weight; fn seal_clear_storage(r: u32, ) -> Weight; + fn seal_clear_storage_per_kb(n: u32, ) -> Weight; fn seal_get_storage(r: u32, ) -> Weight; fn seal_get_storage_per_kb(n: u32, ) -> Weight; + fn seal_contains_storage(r: u32, ) -> Weight; + fn seal_contains_storage_per_kb(n: u32, ) -> Weight; + fn seal_take_storage(r: u32, ) -> Weight; + fn seal_take_storage_per_kb(n: u32, ) -> Weight; fn seal_transfer(r: u32, ) -> Weight; fn seal_call(r: u32, ) -> Weight; - fn seal_call_per_transfer_input_output_kb(t: u32, i: u32, o: u32, ) -> Weight; + fn seal_delegate_call(r: u32, ) -> Weight; + fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight; fn seal_instantiate(r: u32, ) -> Weight; - fn seal_instantiate_per_input_output_salt_kb(i: u32, o: u32, s: u32, ) -> Weight; + fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight; fn seal_hash_sha2_256(r: u32, ) -> Weight; fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight; fn seal_hash_keccak_256(r: u32, ) -> Weight; @@ -93,6 +108,11 @@ pub trait WeightInfo { fn seal_hash_blake2_128(r: u32, ) -> Weight; fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight; fn seal_ecdsa_recover(r: u32, ) -> Weight; + fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight; + fn seal_set_code_hash(r: u32, ) -> Weight; + fn seal_reentrance_count(r: u32, ) -> Weight; + fn seal_account_reentrance_count(r: u32, ) -> Weight; + fn seal_instantiation_nonce(r: u32, ) -> Weight; fn instr_i64const(r: u32, ) -> Weight; fn instr_i64load(r: u32, ) -> Weight; fn instr_i64store(r: u32, ) -> Weight; @@ -105,6 +125,7 @@ pub trait WeightInfo { fn instr_call(r: u32, ) -> Weight; fn instr_call_indirect(r: u32, ) -> Weight; fn instr_call_indirect_per_param(p: u32, ) -> Weight; + fn instr_call_per_local(l: u32, ) -> Weight; fn instr_local_get(r: u32, ) -> Weight; fn instr_local_set(r: u32, ) -> Weight; fn instr_local_tee(r: u32, ) -> Weight; @@ -150,1523 +171,2535 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) - fn on_initialize() -> Weight { - (3_226_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) + fn on_process_deletion_queue_batch() -> Weight { + // Minimum execution time: 3_148 nanoseconds. + Weight::from_ref_time(3_326_000) + .saturating_add(T::DbWeight::get().reads(1)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 2_000 - .saturating_add((2_178_000 as Weight).saturating_mul(k as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) + // Minimum execution time: 15_318 nanoseconds. + Weight::from_ref_time(14_905_070) + // Standard Error: 1_055 + .saturating_add(Weight::from_ref_time(941_176).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) } // Storage: Contracts DeletionQueue (r:1 w:0) + /// The range of component `q` is `[0, 128]`. fn on_initialize_per_queue_item(q: u32, ) -> Weight { - (78_329_000 as Weight) - // Standard Error: 1_000 - .saturating_add((353_000 as Weight).saturating_mul(q as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 3_182 nanoseconds. + Weight::from_ref_time(14_837_078) + // Standard Error: 3_423 + .saturating_add(Weight::from_ref_time(1_203_909).saturating_mul(q.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Contracts PristineCode (r:1 w:0) // Storage: Contracts CodeStorage (r:0 w:1) - fn instrument(c: u32, ) -> Weight { - (37_190_000 as Weight) - // Standard Error: 80_000 - .saturating_add((72_791_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + /// The range of component `c` is `[0, 64226]`. + fn reinstrument(c: u32, ) -> Weight { + // Minimum execution time: 34_272 nanoseconds. + Weight::from_ref_time(33_159_915) + // Standard Error: 60 + .saturating_add(Weight::from_ref_time(46_967).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } + // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) - fn code_load(c: u32, ) -> Weight { - (6_191_000 as Weight) - // Standard Error: 0 - .saturating_add((1_426_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `c` is `[0, 131072]`. + fn call_with_code_per_byte(c: u32, ) -> Weight { + // Minimum execution time: 395_141 nanoseconds. + Weight::from_ref_time(413_672_628) + // Standard Error: 25 + .saturating_add(Weight::from_ref_time(30_570).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Contracts CodeStorage (r:1 w:1) - fn code_refcount(c: u32, ) -> Weight { - (10_333_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_275_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Contracts AccountCounter (r:1 w:0) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:1) + // Storage: System EventTopics (r:3 w:3) // Storage: Contracts PristineCode (r:0 w:1) + // Storage: Contracts OwnerInfoOf (r:0 w:1) + /// The range of component `c` is `[0, 64226]`. + /// The range of component `s` is `[0, 1048576]`. fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - (438_556_000 as Weight) - // Standard Error: 147_000 - .saturating_add((179_307_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 9_000 - .saturating_add((2_159_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 2_259_439 nanoseconds. + Weight::from_ref_time(407_506_250) + // Standard Error: 79 + .saturating_add(Weight::from_ref_time(89_557).saturating_mul(c.into())) + // Standard Error: 4 + .saturating_add(Weight::from_ref_time(1_804).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(9)) } // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: Contracts AccountCounter (r:1 w:0) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) + // Storage: Contracts OwnerInfoOf (r:1 w:1) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `s` is `[0, 1048576]`. fn instantiate(s: u32, ) -> Weight { - (186_776_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_033_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 185_950 nanoseconds. + Weight::from_ref_time(173_152_122) + // Standard Error: 4 + .saturating_add(Weight::from_ref_time(1_559).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(7)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) + // Storage: System EventTopics (r:2 w:2) fn call() -> Weight { - (159_247_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 154_738 nanoseconds. + Weight::from_ref_time(159_212_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Contracts CodeStorage (r:1 w:1) + // Storage: System EventTopics (r:1 w:1) + // Storage: Contracts PristineCode (r:0 w:1) + // Storage: Contracts OwnerInfoOf (r:0 w:1) + /// The range of component `c` is `[0, 64226]`. + fn upload_code(c: u32, ) -> Weight { + // Minimum execution time: 392_302 nanoseconds. + Weight::from_ref_time(402_889_681) + // Standard Error: 84 + .saturating_add(Weight::from_ref_time(89_393).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Contracts OwnerInfoOf (r:1 w:1) + // Storage: System EventTopics (r:1 w:1) + // Storage: Contracts CodeStorage (r:0 w:1) + // Storage: Contracts PristineCode (r:0 w:1) + fn remove_code() -> Weight { + // Minimum execution time: 39_892 nanoseconds. + Weight::from_ref_time(40_258_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts OwnerInfoOf (r:2 w:2) + // Storage: System EventTopics (r:3 w:3) + fn set_code() -> Weight { + // Minimum execution time: 41_441 nanoseconds. + Weight::from_ref_time(42_011_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(6)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_caller(r: u32, ) -> Weight { - (422_263_000 as Weight) - // Standard Error: 159_000 - .saturating_add((125_490_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_919 nanoseconds. + Weight::from_ref_time(387_844_956) + // Standard Error: 38_460 + .saturating_add(Weight::from_ref_time(16_014_536).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_address(r: u32, ) -> Weight { - (423_009_000 as Weight) - // Standard Error: 183_000 - .saturating_add((125_795_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_is_contract(r: u32, ) -> Weight { + // Minimum execution time: 384_047 nanoseconds. + Weight::from_ref_time(316_828_665) + // Standard Error: 571_260 + .saturating_add(Weight::from_ref_time(197_635_022).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_gas_left(r: u32, ) -> Weight { - (429_297_000 as Weight) - // Standard Error: 164_000 - .saturating_add((124_324_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_code_hash(r: u32, ) -> Weight { + // Minimum execution time: 385_259 nanoseconds. + Weight::from_ref_time(338_849_650) + // Standard Error: 447_004 + .saturating_add(Weight::from_ref_time(235_734_380).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_own_code_hash(r: u32, ) -> Weight { + // Minimum execution time: 385_528 nanoseconds. + Weight::from_ref_time(388_332_749) + // Standard Error: 43_017 + .saturating_add(Weight::from_ref_time(20_406_602).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } // Storage: System Account (r:1 w:0) - fn seal_balance(r: u32, ) -> Weight { - (442_330_000 as Weight) - // Standard Error: 187_000 - .saturating_add((354_665_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_caller_is_origin(r: u32, ) -> Weight { + // Minimum execution time: 382_243 nanoseconds. + Weight::from_ref_time(387_692_764) + // Standard Error: 32_726 + .saturating_add(Weight::from_ref_time(10_753_573).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_value_transferred(r: u32, ) -> Weight { - (411_893_000 as Weight) - // Standard Error: 178_000 - .saturating_add((125_971_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_address(r: u32, ) -> Weight { + // Minimum execution time: 383_993 nanoseconds. + Weight::from_ref_time(389_189_394) + // Standard Error: 55_885 + .saturating_add(Weight::from_ref_time(15_943_739).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_minimum_balance(r: u32, ) -> Weight { - (413_273_000 as Weight) - // Standard Error: 180_000 - .saturating_add((125_103_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_gas_left(r: u32, ) -> Weight { + // Minimum execution time: 383_057 nanoseconds. + Weight::from_ref_time(387_466_678) + // Standard Error: 38_570 + .saturating_add(Weight::from_ref_time(15_739_675).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_balance(r: u32, ) -> Weight { + // Minimum execution time: 383_688 nanoseconds. + Weight::from_ref_time(394_708_428) + // Standard Error: 101_035 + .saturating_add(Weight::from_ref_time(89_822_613).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_tombstone_deposit(r: u32, ) -> Weight { - (415_613_000 as Weight) - // Standard Error: 192_000 - .saturating_add((126_106_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_value_transferred(r: u32, ) -> Weight { + // Minimum execution time: 383_511 nanoseconds. + Weight::from_ref_time(387_270_075) + // Standard Error: 33_383 + .saturating_add(Weight::from_ref_time(15_672_963).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_minimum_balance(r: u32, ) -> Weight { + // Minimum execution time: 383_483 nanoseconds. + Weight::from_ref_time(386_995_457) + // Standard Error: 28_781 + .saturating_add(Weight::from_ref_time(15_632_597).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_block_number(r: u32, ) -> Weight { - (414_718_000 as Weight) - // Standard Error: 170_000 - .saturating_add((124_962_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_630 nanoseconds. + Weight::from_ref_time(387_801_190) + // Standard Error: 41_234 + .saturating_add(Weight::from_ref_time(15_531_236).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_now(r: u32, ) -> Weight { - (419_120_000 as Weight) - // Standard Error: 178_000 - .saturating_add((125_188_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_668 nanoseconds. + Weight::from_ref_time(387_490_160) + // Standard Error: 28_070 + .saturating_add(Weight::from_ref_time(15_639_764).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + /// The range of component `r` is `[0, 20]`. fn seal_weight_to_fee(r: u32, ) -> Weight { - (419_125_000 as Weight) - // Standard Error: 216_000 - .saturating_add((290_592_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_401 nanoseconds. + Weight::from_ref_time(393_140_360) + // Standard Error: 95_092 + .saturating_add(Weight::from_ref_time(83_864_160).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_gas(r: u32, ) -> Weight { - (149_609_000 as Weight) - // Standard Error: 117_000 - .saturating_add((56_860_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 142_684 nanoseconds. + Weight::from_ref_time(145_540_019) + // Standard Error: 18_177 + .saturating_add(Weight::from_ref_time(8_061_360).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_input(r: u32, ) -> Weight { - (423_570_000 as Weight) - // Standard Error: 151_000 - .saturating_add((106_985_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_472 nanoseconds. + Weight::from_ref_time(386_518_915) + // Standard Error: 46_174 + .saturating_add(Weight::from_ref_time(14_052_552).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_input_per_kb(n: u32, ) -> Weight { - (566_496_000 as Weight) - // Standard Error: 6_000 - .saturating_add((38_091_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 398_912 nanoseconds. + Weight::from_ref_time(426_793_015) + // Standard Error: 5_524 + .saturating_add(Weight::from_ref_time(9_645_931).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_return(r: u32, ) -> Weight { - (406_811_000 as Weight) - // Standard Error: 1_833_000 - .saturating_add((6_551_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 380_288 nanoseconds. + Weight::from_ref_time(382_064_302) + // Standard Error: 274_854 + .saturating_add(Weight::from_ref_time(5_341_497).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_return_per_kb(n: u32, ) -> Weight { - (412_094_000 as Weight) - // Standard Error: 1_000 - .saturating_add((631_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 382_104 nanoseconds. + Weight::from_ref_time(383_966_376) + // Standard Error: 668 + .saturating_add(Weight::from_ref_time(230_898).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) // Storage: Contracts DeletionQueue (r:1 w:1) - // Storage: System Account (r:2 w:2) + // Storage: Contracts OwnerInfoOf (r:1 w:1) + /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { - (415_716_000 as Weight) - // Standard Error: 1_608_000 - .saturating_add((72_648_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 382_423 nanoseconds. + Weight::from_ref_time(384_162_748) + // Standard Error: 403_637 + .saturating_add(Weight::from_ref_time(57_465_451).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((6_u64).saturating_mul(r.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) + /// The range of component `r` is `[0, 20]`. fn seal_random(r: u32, ) -> Weight { - (421_387_000 as Weight) - // Standard Error: 275_000 - .saturating_add((393_452_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 382_853 nanoseconds. + Weight::from_ref_time(391_962_017) + // Standard Error: 102_169 + .saturating_add(Weight::from_ref_time(108_325_188).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_deposit_event(r: u32, ) -> Weight { - (428_591_000 as Weight) - // Standard Error: 293_000 - .saturating_add((690_833_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 380_999 nanoseconds. + Weight::from_ref_time(392_336_632) + // Standard Error: 168_846 + .saturating_add(Weight::from_ref_time(218_950_403).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:100 w:100) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `t` is `[0, 4]`. + /// The range of component `n` is `[0, 16]`. fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - (1_245_676_000 as Weight) - // Standard Error: 2_636_000 - .saturating_add((484_691_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 519_000 - .saturating_add((165_836_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(t as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(t as Weight))) + // Minimum execution time: 1_276_841 nanoseconds. + Weight::from_ref_time(587_558_952) + // Standard Error: 504_583 + .saturating_add(Weight::from_ref_time(178_141_140).saturating_mul(t.into())) + // Standard Error: 138_583 + .saturating_add(Weight::from_ref_time(71_194_319).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(t.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(t.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_debug_message(r: u32, ) -> Weight { - (162_162_000 as Weight) - // Standard Error: 127_000 - .saturating_add((72_828_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 161_230 nanoseconds. + Weight::from_ref_time(168_508_241) + // Standard Error: 31_112 + .saturating_add(Weight::from_ref_time(12_496_531).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_set_storage(r: u32, ) -> Weight { - (399_301_000 as Weight) - // Standard Error: 221_000 - .saturating_add((245_222_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 383_055 nanoseconds. + Weight::from_ref_time(339_358_786) + // Standard Error: 486_941 + .saturating_add(Weight::from_ref_time(412_066_056).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) } - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x7afa01283080ef247df84e0ba38ea5a587d25ce6633a6bfbba02068c14023441] (r:0 w:1) - fn seal_set_storage_per_kb(n: u32, ) -> Weight { - (623_011_000 as Weight) - // Standard Error: 246_000 - .saturating_add((72_051_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. + fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { + // Minimum execution time: 512_301 nanoseconds. + Weight::from_ref_time(670_220_816) + // Standard Error: 1_460_983 + .saturating_add(Weight::from_ref_time(97_308_816).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(52)) + .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(50)) + .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. + fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { + // Minimum execution time: 510_820 nanoseconds. + Weight::from_ref_time(638_537_372) + // Standard Error: 1_195_632 + .saturating_add(Weight::from_ref_time(65_979_491).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(51)) + .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(49)) + .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_clear_storage(r: u32, ) -> Weight { - (445_102_000 as Weight) - // Standard Error: 247_000 - .saturating_add((224_384_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 383_596 nanoseconds. + Weight::from_ref_time(341_575_167) + // Standard Error: 478_894 + .saturating_add(Weight::from_ref_time(407_044_103).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. + fn seal_clear_storage_per_kb(n: u32, ) -> Weight { + // Minimum execution time: 481_757 nanoseconds. + Weight::from_ref_time(628_126_550) + // Standard Error: 1_363_017 + .saturating_add(Weight::from_ref_time(67_242_851).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(51)) + .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(48)) + .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_get_storage(r: u32, ) -> Weight { - (290_227_000 as Weight) - // Standard Error: 694_000 - .saturating_add((547_193_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_868 nanoseconds. + Weight::from_ref_time(359_800_153) + // Standard Error: 338_998 + .saturating_add(Weight::from_ref_time(323_351_220).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) } - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x7afa01283080ef247df84e0ba38ea5a587d25ce6633a6bfbba02068c14023441] (r:1 w:0) + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_get_storage_per_kb(n: u32, ) -> Weight { - (737_772_000 as Weight) - // Standard Error: 267_000 - .saturating_add((112_216_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 462_237 nanoseconds. + Weight::from_ref_time(585_809_405) + // Standard Error: 1_181_517 + .saturating_add(Weight::from_ref_time(160_905_409).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(51)) + .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. + fn seal_contains_storage(r: u32, ) -> Weight { + // Minimum execution time: 383_794 nanoseconds. + Weight::from_ref_time(355_233_888) + // Standard Error: 416_492 + .saturating_add(Weight::from_ref_time(317_857_887).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. + fn seal_contains_storage_per_kb(n: u32, ) -> Weight { + // Minimum execution time: 462_530 nanoseconds. + Weight::from_ref_time(571_276_165) + // Standard Error: 1_035_339 + .saturating_add(Weight::from_ref_time(63_481_618).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(51)) + .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. + fn seal_take_storage(r: u32, ) -> Weight { + // Minimum execution time: 385_343 nanoseconds. + Weight::from_ref_time(341_897_876) + // Standard Error: 451_948 + .saturating_add(Weight::from_ref_time(417_987_655).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. + fn seal_take_storage_per_kb(n: u32, ) -> Weight { + // Minimum execution time: 485_507 nanoseconds. + Weight::from_ref_time(646_265_325) + // Standard Error: 1_495_172 + .saturating_add(Weight::from_ref_time(166_973_554).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(51)) + .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(48)) + .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) + } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:101 w:101) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_transfer(r: u32, ) -> Weight { - (383_402_000 as Weight) - // Standard Error: 2_184_000 - .saturating_add((4_335_681_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 384_834 nanoseconds. + Weight::from_ref_time(348_341_375) + // Standard Error: 792_708 + .saturating_add(Weight::from_ref_time(1_336_691_822).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_call(r: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 11_019_000 - .saturating_add((39_806_777_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) - } - // Storage: Contracts ContractInfoOf (r:101 w:101) + // Minimum execution time: 386_112 nanoseconds. + Weight::from_ref_time(386_971_000) + // Standard Error: 5_920_386 + .saturating_add(Weight::from_ref_time(28_051_657_660).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().reads((160_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((160_u64).saturating_mul(r.into()))) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_delegate_call(r: u32, ) -> Weight { + // Minimum execution time: 385_776 nanoseconds. + Weight::from_ref_time(387_017_000) + // Standard Error: 6_680_801 + .saturating_add(Weight::from_ref_time(27_761_537_154).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((150_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((75_u64).saturating_mul(r.into()))) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:101 w:101) - fn seal_call_per_transfer_input_output_kb(t: u32, i: u32, o: u32, ) -> Weight { - (38_662_592_000 as Weight) - // Standard Error: 52_762_000 - .saturating_add((3_888_801_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 18_000 - .saturating_add((63_571_000 as Weight).saturating_mul(i as Weight)) - // Standard Error: 20_000 - .saturating_add((101_610_000 as Weight).saturating_mul(o as Weight)) - .saturating_add(T::DbWeight::get().reads(104 as Weight)) - .saturating_add(T::DbWeight::get().reads((101 as Weight).saturating_mul(t as Weight))) - .saturating_add(T::DbWeight::get().writes(101 as Weight)) - .saturating_add(T::DbWeight::get().writes((101 as Weight).saturating_mul(t as Weight))) + // Storage: System EventTopics (r:82 w:82) + /// The range of component `t` is `[0, 1]`. + /// The range of component `c` is `[0, 1024]`. + fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { + // Minimum execution time: 9_623_952 nanoseconds. + Weight::from_ref_time(8_552_923_566) + // Standard Error: 6_582_866 + .saturating_add(Weight::from_ref_time(1_283_786_003).saturating_mul(t.into())) + // Standard Error: 9_870 + .saturating_add(Weight::from_ref_time(9_833_844).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(167)) + .saturating_add(T::DbWeight::get().reads((81_u64).saturating_mul(t.into()))) + .saturating_add(T::DbWeight::get().writes(163)) + .saturating_add(T::DbWeight::get().writes((81_u64).saturating_mul(t.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts AccountCounter (r:1 w:1) - // Storage: System Account (r:101 w:101) + // Storage: System EventTopics (r:2 w:2) + // Storage: Contracts Nonce (r:1 w:1) + // Storage: Contracts OwnerInfoOf (r:80 w:80) + /// The range of component `r` is `[0, 20]`. fn seal_instantiate(r: u32, ) -> Weight { - (626_132_000 as Weight) - // Standard Error: 39_245_000 - .saturating_add((46_398_859_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().reads((300 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - .saturating_add(T::DbWeight::get().writes((300 as Weight).saturating_mul(r as Weight))) - } - // Storage: Contracts ContractInfoOf (r:101 w:101) + // Minimum execution time: 386_667 nanoseconds. + Weight::from_ref_time(387_559_000) + // Standard Error: 18_953_118 + .saturating_add(Weight::from_ref_time(33_188_342_429).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads((400_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes((400_u64).saturating_mul(r.into()))) + } + // Storage: System Account (r:81 w:81) + // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:1) // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts AccountCounter (r:1 w:1) - // Storage: System Account (r:101 w:101) - fn seal_instantiate_per_input_output_salt_kb(i: u32, o: u32, s: u32, ) -> Weight { - (46_649_369_000 as Weight) - // Standard Error: 26_000 - .saturating_add((63_469_000 as Weight).saturating_mul(i as Weight)) - // Standard Error: 26_000 - .saturating_add((100_694_000 as Weight).saturating_mul(o as Weight)) - // Standard Error: 26_000 - .saturating_add((201_705_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(206 as Weight)) - .saturating_add(T::DbWeight::get().writes(204 as Weight)) + // Storage: Contracts Nonce (r:1 w:1) + // Storage: Contracts OwnerInfoOf (r:1 w:1) + // Storage: System EventTopics (r:82 w:82) + /// The range of component `t` is `[0, 1]`. + /// The range of component `s` is `[0, 960]`. + fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { + // Minimum execution time: 11_664_478 nanoseconds. + Weight::from_ref_time(11_359_540_086) + // Standard Error: 45_626_277 + .saturating_add(Weight::from_ref_time(19_120_579).saturating_mul(t.into())) + // Standard Error: 72_976 + .saturating_add(Weight::from_ref_time(125_731_953).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(249)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(T::DbWeight::get().writes(247)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_hash_sha2_256(r: u32, ) -> Weight { - (417_820_000 as Weight) - // Standard Error: 160_000 - .saturating_add((133_795_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 384_826 nanoseconds. + Weight::from_ref_time(387_293_630) + // Standard Error: 437_875 + .saturating_add(Weight::from_ref_time(48_464_369).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - (609_012_000 as Weight) - // Standard Error: 23_000 - .saturating_add((499_227_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 426_531 nanoseconds. + Weight::from_ref_time(427_315_000) + // Standard Error: 48_058 + .saturating_add(Weight::from_ref_time(327_318_884).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { - (419_043_000 as Weight) - // Standard Error: 177_000 - .saturating_add((140_704_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 384_084 nanoseconds. + Weight::from_ref_time(386_354_628) + // Standard Error: 195_951 + .saturating_add(Weight::from_ref_time(52_991_271).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - (564_451_000 as Weight) - // Standard Error: 19_000 - .saturating_add((346_948_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 438_319 nanoseconds. + Weight::from_ref_time(439_001_000) + // Standard Error: 53_445 + .saturating_add(Weight::from_ref_time(251_353_803).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { - (420_951_000 as Weight) - // Standard Error: 163_000 - .saturating_add((113_596_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 384_397 nanoseconds. + Weight::from_ref_time(386_532_859) + // Standard Error: 141_195 + .saturating_add(Weight::from_ref_time(31_116_440).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - (563_168_000 as Weight) - // Standard Error: 17_000 - .saturating_add((164_114_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 416_504 nanoseconds. + Weight::from_ref_time(417_686_000) + // Standard Error: 47_003 + .saturating_add(Weight::from_ref_time(103_095_636).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_128(r: u32, ) -> Weight { - (418_794_000 as Weight) - // Standard Error: 167_000 - .saturating_add((113_205_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 382_479 nanoseconds. + Weight::from_ref_time(384_623_057) + // Standard Error: 243_054 + .saturating_add(Weight::from_ref_time(37_025_542).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - (584_668_000 as Weight) - // Standard Error: 15_000 - .saturating_add((164_127_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 414_863 nanoseconds. + Weight::from_ref_time(415_728_000) + // Standard Error: 48_764 + .saturating_add(Weight::from_ref_time(103_050_672).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { - (435_443_000 as Weight) - // Standard Error: 1_408_000 - .saturating_add((15_624_877_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 384_418 nanoseconds. + Weight::from_ref_time(387_283_069) + // Standard Error: 526_301 + .saturating_add(Weight::from_ref_time(2_993_987_030).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. + fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { + // Minimum execution time: 383_686 nanoseconds. + Weight::from_ref_time(385_812_638) + // Standard Error: 539_029 + .saturating_add(Weight::from_ref_time(2_098_063_561).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + // Storage: Contracts OwnerInfoOf (r:16 w:16) + /// The range of component `r` is `[0, 20]`. + fn seal_set_code_hash(r: u32, ) -> Weight { + // Minimum execution time: 384_399 nanoseconds. + Weight::from_ref_time(385_337_000) + // Standard Error: 2_827_655 + .saturating_add(Weight::from_ref_time(1_383_659_432).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((225_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((150_u64).saturating_mul(r.into()))) } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_reentrance_count(r: u32, ) -> Weight { + // Minimum execution time: 385_165 nanoseconds. + Weight::from_ref_time(389_255_026) + // Standard Error: 25_918 + .saturating_add(Weight::from_ref_time(10_716_905).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_account_reentrance_count(r: u32, ) -> Weight { + // Minimum execution time: 386_959 nanoseconds. + Weight::from_ref_time(423_364_524) + // Standard Error: 127_096 + .saturating_add(Weight::from_ref_time(25_552_186).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + // Storage: Contracts Nonce (r:1 w:1) + /// The range of component `r` is `[0, 20]`. + fn seal_instantiation_nonce(r: u32, ) -> Weight { + // Minimum execution time: 293_987 nanoseconds. + Weight::from_ref_time(307_154_849) + // Standard Error: 27_486 + .saturating_add(Weight::from_ref_time(8_759_333).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// The range of component `r` is `[0, 50]`. fn instr_i64const(r: u32, ) -> Weight { - (45_937_000 as Weight) - // Standard Error: 10_000 - .saturating_add((1_108_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 688 nanoseconds. + Weight::from_ref_time(914_830) + // Standard Error: 222 + .saturating_add(Weight::from_ref_time(343_835).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64load(r: u32, ) -> Weight { - (44_001_000 as Weight) - // Standard Error: 11_000 - .saturating_add((2_412_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 775 nanoseconds. + Weight::from_ref_time(1_286_008) + // Standard Error: 391 + .saturating_add(Weight::from_ref_time(984_759).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64store(r: u32, ) -> Weight { - (43_157_000 as Weight) - // Standard Error: 12_000 - .saturating_add((2_677_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 779 nanoseconds. + Weight::from_ref_time(1_162_588) + // Standard Error: 694 + .saturating_add(Weight::from_ref_time(883_828).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_select(r: u32, ) -> Weight { - (48_475_000 as Weight) - // Standard Error: 8_000 - .saturating_add((2_604_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 687 nanoseconds. + Weight::from_ref_time(965_966) + // Standard Error: 290 + .saturating_add(Weight::from_ref_time(955_392).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_if(r: u32, ) -> Weight { - (50_649_000 as Weight) - // Standard Error: 12_000 - .saturating_add((2_553_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 681 nanoseconds. + Weight::from_ref_time(778_970) + // Standard Error: 670 + .saturating_add(Weight::from_ref_time(1_265_116).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_br(r: u32, ) -> Weight { - (48_433_000 as Weight) - // Standard Error: 8_000 - .saturating_add((1_670_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 673 nanoseconds. + Weight::from_ref_time(1_125_562) + // Standard Error: 818 + .saturating_add(Weight::from_ref_time(528_126).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_br_if(r: u32, ) -> Weight { - (49_244_000 as Weight) - // Standard Error: 16_000 - .saturating_add((1_946_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 649 nanoseconds. + Weight::from_ref_time(780_802) + // Standard Error: 943 + .saturating_add(Weight::from_ref_time(800_988).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_br_table(r: u32, ) -> Weight { - (46_117_000 as Weight) - // Standard Error: 17_000 - .saturating_add((2_387_000 as Weight).saturating_mul(r as Weight)) - } - fn instr_br_table_per_entry(_e: u32, ) -> Weight { - (55_204_000 as Weight) - } + // Minimum execution time: 662 nanoseconds. + Weight::from_ref_time(555_078) + // Standard Error: 1_540 + .saturating_add(Weight::from_ref_time(1_078_705).saturating_mul(r.into())) + } + /// The range of component `e` is `[1, 256]`. + fn instr_br_table_per_entry(e: u32, ) -> Weight { + // Minimum execution time: 2_177 nanoseconds. + Weight::from_ref_time(2_581_121) + // Standard Error: 67 + .saturating_add(Weight::from_ref_time(5_001).saturating_mul(e.into())) + } + /// The range of component `r` is `[0, 50]`. fn instr_call(r: u32, ) -> Weight { - (43_651_000 as Weight) - // Standard Error: 26_000 - .saturating_add((19_163_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 702 nanoseconds. + Weight::from_ref_time(1_570_695) + // Standard Error: 1_524 + .saturating_add(Weight::from_ref_time(2_192_040).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_call_indirect(r: u32, ) -> Weight { - (54_063_000 as Weight) - // Standard Error: 32_000 - .saturating_add((27_970_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 745 nanoseconds. + Weight::from_ref_time(1_694_451) + // Standard Error: 4_170 + .saturating_add(Weight::from_ref_time(2_813_621).saturating_mul(r.into())) } + /// The range of component `p` is `[0, 128]`. fn instr_call_indirect_per_param(p: u32, ) -> Weight { - (88_527_000 as Weight) - // Standard Error: 6_000 - .saturating_add((958_000 as Weight).saturating_mul(p as Weight)) - } + // Minimum execution time: 4_255 nanoseconds. + Weight::from_ref_time(5_064_243) + // Standard Error: 289 + .saturating_add(Weight::from_ref_time(178_868).saturating_mul(p.into())) + } + /// The range of component `l` is `[0, 1024]`. + fn instr_call_per_local(l: u32, ) -> Weight { + // Minimum execution time: 2_802 nanoseconds. + Weight::from_ref_time(3_474_642) + // Standard Error: 28 + .saturating_add(Weight::from_ref_time(92_517).saturating_mul(l.into())) + } + /// The range of component `r` is `[0, 50]`. fn instr_local_get(r: u32, ) -> Weight { - (55_066_000 as Weight) - // Standard Error: 12_000 - .saturating_add((682_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 2_973 nanoseconds. + Weight::from_ref_time(3_218_977) + // Standard Error: 183 + .saturating_add(Weight::from_ref_time(364_688).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_local_set(r: u32, ) -> Weight { - (55_298_000 as Weight) - // Standard Error: 13_000 - .saturating_add((778_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 2_912 nanoseconds. + Weight::from_ref_time(3_173_203) + // Standard Error: 260 + .saturating_add(Weight::from_ref_time(381_853).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_local_tee(r: u32, ) -> Weight { - (56_302_000 as Weight) - // Standard Error: 11_000 - .saturating_add((1_079_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 2_916 nanoseconds. + Weight::from_ref_time(3_228_548) + // Standard Error: 311 + .saturating_add(Weight::from_ref_time(526_008).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_global_get(r: u32, ) -> Weight { - (71_567_000 as Weight) - // Standard Error: 11_000 - .saturating_add((1_107_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 739 nanoseconds. + Weight::from_ref_time(1_128_919) + // Standard Error: 479 + .saturating_add(Weight::from_ref_time(810_765).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_global_set(r: u32, ) -> Weight { - (71_186_000 as Weight) - // Standard Error: 12_000 - .saturating_add((1_151_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 724 nanoseconds. + Weight::from_ref_time(1_044_382) + // Standard Error: 371 + .saturating_add(Weight::from_ref_time(828_530).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_memory_current(r: u32, ) -> Weight { - (46_240_000 as Weight) - // Standard Error: 10_000 - .saturating_add((1_044_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 770 nanoseconds. + Weight::from_ref_time(988_307) + // Standard Error: 587 + .saturating_add(Weight::from_ref_time(699_091).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 1]`. fn instr_memory_grow(r: u32, ) -> Weight { - (52_369_000 as Weight) - // Standard Error: 2_508_000 - .saturating_add((615_448_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 688 nanoseconds. + Weight::from_ref_time(747_995) + // Standard Error: 3_894 + .saturating_add(Weight::from_ref_time(234_512_504).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64clz(r: u32, ) -> Weight { - (47_623_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_583_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 643 nanoseconds. + Weight::from_ref_time(946_893) + // Standard Error: 188 + .saturating_add(Weight::from_ref_time(505_004).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ctz(r: u32, ) -> Weight { - (47_670_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_583_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 663 nanoseconds. + Weight::from_ref_time(965_194) + // Standard Error: 343 + .saturating_add(Weight::from_ref_time(505_213).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64popcnt(r: u32, ) -> Weight { - (47_508_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_583_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 675 nanoseconds. + Weight::from_ref_time(903_705) + // Standard Error: 425 + .saturating_add(Weight::from_ref_time(507_749).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64eqz(r: u32, ) -> Weight { - (48_109_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_580_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(946_419) + // Standard Error: 301 + .saturating_add(Weight::from_ref_time(522_387).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64extendsi32(r: u32, ) -> Weight { - (55_270_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_102_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 674 nanoseconds. + Weight::from_ref_time(963_566) + // Standard Error: 952 + .saturating_add(Weight::from_ref_time(504_528).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64extendui32(r: u32, ) -> Weight { - (55_093_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_108_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 663 nanoseconds. + Weight::from_ref_time(927_099) + // Standard Error: 336 + .saturating_add(Weight::from_ref_time(505_200).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i32wrapi64(r: u32, ) -> Weight { - (48_265_000 as Weight) - // Standard Error: 10_000 - .saturating_add((1_573_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 660 nanoseconds. + Weight::from_ref_time(901_114) + // Standard Error: 255 + .saturating_add(Weight::from_ref_time(503_803).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64eq(r: u32, ) -> Weight { - (48_733_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_088_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 636 nanoseconds. + Weight::from_ref_time(906_526) + // Standard Error: 246 + .saturating_add(Weight::from_ref_time(730_299).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ne(r: u32, ) -> Weight { - (48_831_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_085_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 659 nanoseconds. + Weight::from_ref_time(947_772) + // Standard Error: 312 + .saturating_add(Weight::from_ref_time(729_463).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64lts(r: u32, ) -> Weight { - (49_147_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_056_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 646 nanoseconds. + Weight::from_ref_time(923_694) + // Standard Error: 243 + .saturating_add(Weight::from_ref_time(738_995).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ltu(r: u32, ) -> Weight { - (49_596_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_049_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(955_453) + // Standard Error: 1_430 + .saturating_add(Weight::from_ref_time(741_624).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64gts(r: u32, ) -> Weight { - (49_872_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_038_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 642 nanoseconds. + Weight::from_ref_time(900_107) + // Standard Error: 376 + .saturating_add(Weight::from_ref_time(740_016).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64gtu(r: u32, ) -> Weight { - (48_843_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_081_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 625 nanoseconds. + Weight::from_ref_time(946_744) + // Standard Error: 252 + .saturating_add(Weight::from_ref_time(743_532).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64les(r: u32, ) -> Weight { - (48_765_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_089_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(918_551) + // Standard Error: 313 + .saturating_add(Weight::from_ref_time(731_451).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64leu(r: u32, ) -> Weight { - (48_720_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_083_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 651 nanoseconds. + Weight::from_ref_time(923_475) + // Standard Error: 341 + .saturating_add(Weight::from_ref_time(738_353).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ges(r: u32, ) -> Weight { - (48_736_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_097_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 666 nanoseconds. + Weight::from_ref_time(1_136_987) + // Standard Error: 1_482 + .saturating_add(Weight::from_ref_time(727_254).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64geu(r: u32, ) -> Weight { - (48_772_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_093_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 633 nanoseconds. + Weight::from_ref_time(934_201) + // Standard Error: 332 + .saturating_add(Weight::from_ref_time(731_804).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64add(r: u32, ) -> Weight { - (48_827_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_082_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 673 nanoseconds. + Weight::from_ref_time(983_317) + // Standard Error: 492 + .saturating_add(Weight::from_ref_time(718_126).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64sub(r: u32, ) -> Weight { - (48_961_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_072_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 647 nanoseconds. + Weight::from_ref_time(925_490) + // Standard Error: 1_799 + .saturating_add(Weight::from_ref_time(711_178).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64mul(r: u32, ) -> Weight { - (49_069_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_067_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(955_546) + // Standard Error: 283 + .saturating_add(Weight::from_ref_time(710_844).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64divs(r: u32, ) -> Weight { - (49_035_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_677_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(982_314) + // Standard Error: 267 + .saturating_add(Weight::from_ref_time(1_344_080).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64divu(r: u32, ) -> Weight { - (48_842_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_449_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(913_421) + // Standard Error: 737 + .saturating_add(Weight::from_ref_time(1_285_749).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rems(r: u32, ) -> Weight { - (48_536_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_723_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(939_041) + // Standard Error: 354 + .saturating_add(Weight::from_ref_time(1_391_470).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64remu(r: u32, ) -> Weight { - (48_851_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_432_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 656 nanoseconds. + Weight::from_ref_time(951_030) + // Standard Error: 342 + .saturating_add(Weight::from_ref_time(1_287_904).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64and(r: u32, ) -> Weight { - (48_624_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_093_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 682 nanoseconds. + Weight::from_ref_time(940_975) + // Standard Error: 195 + .saturating_add(Weight::from_ref_time(717_132).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64or(r: u32, ) -> Weight { - (49_348_000 as Weight) - // Standard Error: 8_000 - .saturating_add((2_073_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 704 nanoseconds. + Weight::from_ref_time(941_860) + // Standard Error: 200 + .saturating_add(Weight::from_ref_time(717_696).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64xor(r: u32, ) -> Weight { - (49_112_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_055_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 648 nanoseconds. + Weight::from_ref_time(917_135) + // Standard Error: 237 + .saturating_add(Weight::from_ref_time(717_979).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shl(r: u32, ) -> Weight { - (49_654_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_051_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(1_031_822) + // Standard Error: 937 + .saturating_add(Weight::from_ref_time(730_965).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shrs(r: u32, ) -> Weight { - (48_848_000 as Weight) - // Standard Error: 8_000 - .saturating_add((2_089_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 671 nanoseconds. + Weight::from_ref_time(935_833) + // Standard Error: 184 + .saturating_add(Weight::from_ref_time(732_227).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shru(r: u32, ) -> Weight { - (49_455_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_054_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(962_491) + // Standard Error: 488 + .saturating_add(Weight::from_ref_time(733_218).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rotl(r: u32, ) -> Weight { - (49_640_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_048_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 643 nanoseconds. + Weight::from_ref_time(951_949) + // Standard Error: 321 + .saturating_add(Weight::from_ref_time(732_212).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rotr(r: u32, ) -> Weight { - (49_498_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_068_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 665 nanoseconds. + Weight::from_ref_time(951_447) + // Standard Error: 346 + .saturating_add(Weight::from_ref_time(732_549).saturating_mul(r.into())) } } // For backwards compatibility and tests impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) - fn on_initialize() -> Weight { - (3_226_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + fn on_process_deletion_queue_batch() -> Weight { + // Minimum execution time: 3_148 nanoseconds. + Weight::from_ref_time(3_326_000) + .saturating_add(RocksDbWeight::get().reads(1)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 2_000 - .saturating_add((2_178_000 as Weight).saturating_mul(k as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) + // Minimum execution time: 15_318 nanoseconds. + Weight::from_ref_time(14_905_070) + // Standard Error: 1_055 + .saturating_add(Weight::from_ref_time(941_176).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) } // Storage: Contracts DeletionQueue (r:1 w:0) + /// The range of component `q` is `[0, 128]`. fn on_initialize_per_queue_item(q: u32, ) -> Weight { - (78_329_000 as Weight) - // Standard Error: 1_000 - .saturating_add((353_000 as Weight).saturating_mul(q as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 3_182 nanoseconds. + Weight::from_ref_time(14_837_078) + // Standard Error: 3_423 + .saturating_add(Weight::from_ref_time(1_203_909).saturating_mul(q.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Contracts PristineCode (r:1 w:0) // Storage: Contracts CodeStorage (r:0 w:1) - fn instrument(c: u32, ) -> Weight { - (37_190_000 as Weight) - // Standard Error: 80_000 - .saturating_add((72_791_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + /// The range of component `c` is `[0, 64226]`. + fn reinstrument(c: u32, ) -> Weight { + // Minimum execution time: 34_272 nanoseconds. + Weight::from_ref_time(33_159_915) + // Standard Error: 60 + .saturating_add(Weight::from_ref_time(46_967).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } + // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) - fn code_load(c: u32, ) -> Weight { - (6_191_000 as Weight) - // Standard Error: 0 - .saturating_add((1_426_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `c` is `[0, 131072]`. + fn call_with_code_per_byte(c: u32, ) -> Weight { + // Minimum execution time: 395_141 nanoseconds. + Weight::from_ref_time(413_672_628) + // Standard Error: 25 + .saturating_add(Weight::from_ref_time(30_570).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Contracts CodeStorage (r:1 w:1) - fn code_refcount(c: u32, ) -> Weight { - (10_333_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_275_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Contracts AccountCounter (r:1 w:0) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:1) + // Storage: System EventTopics (r:3 w:3) // Storage: Contracts PristineCode (r:0 w:1) + // Storage: Contracts OwnerInfoOf (r:0 w:1) + /// The range of component `c` is `[0, 64226]`. + /// The range of component `s` is `[0, 1048576]`. fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - (438_556_000 as Weight) - // Standard Error: 147_000 - .saturating_add((179_307_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 9_000 - .saturating_add((2_159_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 2_259_439 nanoseconds. + Weight::from_ref_time(407_506_250) + // Standard Error: 79 + .saturating_add(Weight::from_ref_time(89_557).saturating_mul(c.into())) + // Standard Error: 4 + .saturating_add(Weight::from_ref_time(1_804).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(9)) } // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: Contracts AccountCounter (r:1 w:0) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) + // Storage: Contracts OwnerInfoOf (r:1 w:1) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `s` is `[0, 1048576]`. fn instantiate(s: u32, ) -> Weight { - (186_776_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_033_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 185_950 nanoseconds. + Weight::from_ref_time(173_152_122) + // Standard Error: 4 + .saturating_add(Weight::from_ref_time(1_559).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(7)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) + // Storage: System EventTopics (r:2 w:2) fn call() -> Weight { - (159_247_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 154_738 nanoseconds. + Weight::from_ref_time(159_212_000) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Contracts CodeStorage (r:1 w:1) + // Storage: System EventTopics (r:1 w:1) + // Storage: Contracts PristineCode (r:0 w:1) + // Storage: Contracts OwnerInfoOf (r:0 w:1) + /// The range of component `c` is `[0, 64226]`. + fn upload_code(c: u32, ) -> Weight { + // Minimum execution time: 392_302 nanoseconds. + Weight::from_ref_time(402_889_681) + // Standard Error: 84 + .saturating_add(Weight::from_ref_time(89_393).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Contracts OwnerInfoOf (r:1 w:1) + // Storage: System EventTopics (r:1 w:1) + // Storage: Contracts CodeStorage (r:0 w:1) + // Storage: Contracts PristineCode (r:0 w:1) + fn remove_code() -> Weight { + // Minimum execution time: 39_892 nanoseconds. + Weight::from_ref_time(40_258_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts OwnerInfoOf (r:2 w:2) + // Storage: System EventTopics (r:3 w:3) + fn set_code() -> Weight { + // Minimum execution time: 41_441 nanoseconds. + Weight::from_ref_time(42_011_000) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(6)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_caller(r: u32, ) -> Weight { - (422_263_000 as Weight) - // Standard Error: 159_000 - .saturating_add((125_490_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_919 nanoseconds. + Weight::from_ref_time(387_844_956) + // Standard Error: 38_460 + .saturating_add(Weight::from_ref_time(16_014_536).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_address(r: u32, ) -> Weight { - (423_009_000 as Weight) - // Standard Error: 183_000 - .saturating_add((125_795_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_is_contract(r: u32, ) -> Weight { + // Minimum execution time: 384_047 nanoseconds. + Weight::from_ref_time(316_828_665) + // Standard Error: 571_260 + .saturating_add(Weight::from_ref_time(197_635_022).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_gas_left(r: u32, ) -> Weight { - (429_297_000 as Weight) - // Standard Error: 164_000 - .saturating_add((124_324_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_code_hash(r: u32, ) -> Weight { + // Minimum execution time: 385_259 nanoseconds. + Weight::from_ref_time(338_849_650) + // Standard Error: 447_004 + .saturating_add(Weight::from_ref_time(235_734_380).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_own_code_hash(r: u32, ) -> Weight { + // Minimum execution time: 385_528 nanoseconds. + Weight::from_ref_time(388_332_749) + // Standard Error: 43_017 + .saturating_add(Weight::from_ref_time(20_406_602).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) + } // Storage: System Account (r:1 w:0) - fn seal_balance(r: u32, ) -> Weight { - (442_330_000 as Weight) - // Standard Error: 187_000 - .saturating_add((354_665_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_caller_is_origin(r: u32, ) -> Weight { + // Minimum execution time: 382_243 nanoseconds. + Weight::from_ref_time(387_692_764) + // Standard Error: 32_726 + .saturating_add(Weight::from_ref_time(10_753_573).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_value_transferred(r: u32, ) -> Weight { - (411_893_000 as Weight) - // Standard Error: 178_000 - .saturating_add((125_971_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_address(r: u32, ) -> Weight { + // Minimum execution time: 383_993 nanoseconds. + Weight::from_ref_time(389_189_394) + // Standard Error: 55_885 + .saturating_add(Weight::from_ref_time(15_943_739).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_minimum_balance(r: u32, ) -> Weight { - (413_273_000 as Weight) - // Standard Error: 180_000 - .saturating_add((125_103_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_gas_left(r: u32, ) -> Weight { + // Minimum execution time: 383_057 nanoseconds. + Weight::from_ref_time(387_466_678) + // Standard Error: 38_570 + .saturating_add(Weight::from_ref_time(15_739_675).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_tombstone_deposit(r: u32, ) -> Weight { - (415_613_000 as Weight) - // Standard Error: 192_000 - .saturating_add((126_106_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_balance(r: u32, ) -> Weight { + // Minimum execution time: 383_688 nanoseconds. + Weight::from_ref_time(394_708_428) + // Standard Error: 101_035 + .saturating_add(Weight::from_ref_time(89_822_613).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(7)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_value_transferred(r: u32, ) -> Weight { + // Minimum execution time: 383_511 nanoseconds. + Weight::from_ref_time(387_270_075) + // Standard Error: 33_383 + .saturating_add(Weight::from_ref_time(15_672_963).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_minimum_balance(r: u32, ) -> Weight { + // Minimum execution time: 383_483 nanoseconds. + Weight::from_ref_time(386_995_457) + // Standard Error: 28_781 + .saturating_add(Weight::from_ref_time(15_632_597).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_block_number(r: u32, ) -> Weight { - (414_718_000 as Weight) - // Standard Error: 170_000 - .saturating_add((124_962_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_630 nanoseconds. + Weight::from_ref_time(387_801_190) + // Standard Error: 41_234 + .saturating_add(Weight::from_ref_time(15_531_236).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_now(r: u32, ) -> Weight { - (419_120_000 as Weight) - // Standard Error: 178_000 - .saturating_add((125_188_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_668 nanoseconds. + Weight::from_ref_time(387_490_160) + // Standard Error: 28_070 + .saturating_add(Weight::from_ref_time(15_639_764).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + /// The range of component `r` is `[0, 20]`. fn seal_weight_to_fee(r: u32, ) -> Weight { - (419_125_000 as Weight) - // Standard Error: 216_000 - .saturating_add((290_592_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_401 nanoseconds. + Weight::from_ref_time(393_140_360) + // Standard Error: 95_092 + .saturating_add(Weight::from_ref_time(83_864_160).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(7)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_gas(r: u32, ) -> Weight { - (149_609_000 as Weight) - // Standard Error: 117_000 - .saturating_add((56_860_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 142_684 nanoseconds. + Weight::from_ref_time(145_540_019) + // Standard Error: 18_177 + .saturating_add(Weight::from_ref_time(8_061_360).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_input(r: u32, ) -> Weight { - (423_570_000 as Weight) - // Standard Error: 151_000 - .saturating_add((106_985_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_472 nanoseconds. + Weight::from_ref_time(386_518_915) + // Standard Error: 46_174 + .saturating_add(Weight::from_ref_time(14_052_552).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_input_per_kb(n: u32, ) -> Weight { - (566_496_000 as Weight) - // Standard Error: 6_000 - .saturating_add((38_091_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 398_912 nanoseconds. + Weight::from_ref_time(426_793_015) + // Standard Error: 5_524 + .saturating_add(Weight::from_ref_time(9_645_931).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_return(r: u32, ) -> Weight { - (406_811_000 as Weight) - // Standard Error: 1_833_000 - .saturating_add((6_551_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 380_288 nanoseconds. + Weight::from_ref_time(382_064_302) + // Standard Error: 274_854 + .saturating_add(Weight::from_ref_time(5_341_497).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_return_per_kb(n: u32, ) -> Weight { - (412_094_000 as Weight) - // Standard Error: 1_000 - .saturating_add((631_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 382_104 nanoseconds. + Weight::from_ref_time(383_966_376) + // Standard Error: 668 + .saturating_add(Weight::from_ref_time(230_898).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) // Storage: Contracts DeletionQueue (r:1 w:1) - // Storage: System Account (r:2 w:2) + // Storage: Contracts OwnerInfoOf (r:1 w:1) + /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { - (415_716_000 as Weight) - // Standard Error: 1_608_000 - .saturating_add((72_648_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((4 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 382_423 nanoseconds. + Weight::from_ref_time(384_162_748) + // Standard Error: 403_637 + .saturating_add(Weight::from_ref_time(57_465_451).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((6_u64).saturating_mul(r.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) + /// The range of component `r` is `[0, 20]`. fn seal_random(r: u32, ) -> Weight { - (421_387_000 as Weight) - // Standard Error: 275_000 - .saturating_add((393_452_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 382_853 nanoseconds. + Weight::from_ref_time(391_962_017) + // Standard Error: 102_169 + .saturating_add(Weight::from_ref_time(108_325_188).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(7)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_deposit_event(r: u32, ) -> Weight { - (428_591_000 as Weight) - // Standard Error: 293_000 - .saturating_add((690_833_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 380_999 nanoseconds. + Weight::from_ref_time(392_336_632) + // Standard Error: 168_846 + .saturating_add(Weight::from_ref_time(218_950_403).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:100 w:100) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `t` is `[0, 4]`. + /// The range of component `n` is `[0, 16]`. fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - (1_245_676_000 as Weight) - // Standard Error: 2_636_000 - .saturating_add((484_691_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 519_000 - .saturating_add((165_836_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(t as Weight))) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(t as Weight))) + // Minimum execution time: 1_276_841 nanoseconds. + Weight::from_ref_time(587_558_952) + // Standard Error: 504_583 + .saturating_add(Weight::from_ref_time(178_141_140).saturating_mul(t.into())) + // Standard Error: 138_583 + .saturating_add(Weight::from_ref_time(71_194_319).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(t.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((80_u64).saturating_mul(t.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_debug_message(r: u32, ) -> Weight { - (162_162_000 as Weight) - // Standard Error: 127_000 - .saturating_add((72_828_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 161_230 nanoseconds. + Weight::from_ref_time(168_508_241) + // Standard Error: 31_112 + .saturating_add(Weight::from_ref_time(12_496_531).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_set_storage(r: u32, ) -> Weight { - (399_301_000 as Weight) - // Standard Error: 221_000 - .saturating_add((245_222_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 383_055 nanoseconds. + Weight::from_ref_time(339_358_786) + // Standard Error: 486_941 + .saturating_add(Weight::from_ref_time(412_066_056).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((80_u64).saturating_mul(r.into()))) } - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x7afa01283080ef247df84e0ba38ea5a587d25ce6633a6bfbba02068c14023441] (r:0 w:1) - fn seal_set_storage_per_kb(n: u32, ) -> Weight { - (623_011_000 as Weight) - // Standard Error: 246_000 - .saturating_add((72_051_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. + fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { + // Minimum execution time: 512_301 nanoseconds. + Weight::from_ref_time(670_220_816) + // Standard Error: 1_460_983 + .saturating_add(Weight::from_ref_time(97_308_816).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(52)) + .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(50)) + .saturating_add(RocksDbWeight::get().writes((7_u64).saturating_mul(n.into()))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. + fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { + // Minimum execution time: 510_820 nanoseconds. + Weight::from_ref_time(638_537_372) + // Standard Error: 1_195_632 + .saturating_add(Weight::from_ref_time(65_979_491).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(51)) + .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(49)) + .saturating_add(RocksDbWeight::get().writes((7_u64).saturating_mul(n.into()))) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_clear_storage(r: u32, ) -> Weight { - (445_102_000 as Weight) - // Standard Error: 247_000 - .saturating_add((224_384_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 383_596 nanoseconds. + Weight::from_ref_time(341_575_167) + // Standard Error: 478_894 + .saturating_add(Weight::from_ref_time(407_044_103).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((80_u64).saturating_mul(r.into()))) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. + fn seal_clear_storage_per_kb(n: u32, ) -> Weight { + // Minimum execution time: 481_757 nanoseconds. + Weight::from_ref_time(628_126_550) + // Standard Error: 1_363_017 + .saturating_add(Weight::from_ref_time(67_242_851).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(51)) + .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(48)) + .saturating_add(RocksDbWeight::get().writes((7_u64).saturating_mul(n.into()))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_get_storage(r: u32, ) -> Weight { - (290_227_000 as Weight) - // Standard Error: 694_000 - .saturating_add((547_193_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 383_868 nanoseconds. + Weight::from_ref_time(359_800_153) + // Standard Error: 338_998 + .saturating_add(Weight::from_ref_time(323_351_220).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) } - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x7afa01283080ef247df84e0ba38ea5a587d25ce6633a6bfbba02068c14023441] (r:1 w:0) + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_get_storage_per_kb(n: u32, ) -> Weight { - (737_772_000 as Weight) - // Standard Error: 267_000 - .saturating_add((112_216_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 462_237 nanoseconds. + Weight::from_ref_time(585_809_405) + // Standard Error: 1_181_517 + .saturating_add(Weight::from_ref_time(160_905_409).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(51)) + .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. + fn seal_contains_storage(r: u32, ) -> Weight { + // Minimum execution time: 383_794 nanoseconds. + Weight::from_ref_time(355_233_888) + // Standard Error: 416_492 + .saturating_add(Weight::from_ref_time(317_857_887).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. + fn seal_contains_storage_per_kb(n: u32, ) -> Weight { + // Minimum execution time: 462_530 nanoseconds. + Weight::from_ref_time(571_276_165) + // Standard Error: 1_035_339 + .saturating_add(Weight::from_ref_time(63_481_618).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(51)) + .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. + fn seal_take_storage(r: u32, ) -> Weight { + // Minimum execution time: 385_343 nanoseconds. + Weight::from_ref_time(341_897_876) + // Standard Error: 451_948 + .saturating_add(Weight::from_ref_time(417_987_655).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((80_u64).saturating_mul(r.into()))) + } + // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. + fn seal_take_storage_per_kb(n: u32, ) -> Weight { + // Minimum execution time: 485_507 nanoseconds. + Weight::from_ref_time(646_265_325) + // Standard Error: 1_495_172 + .saturating_add(Weight::from_ref_time(166_973_554).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(51)) + .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(48)) + .saturating_add(RocksDbWeight::get().writes((7_u64).saturating_mul(n.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:101 w:101) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_transfer(r: u32, ) -> Weight { - (383_402_000 as Weight) - // Standard Error: 2_184_000 - .saturating_add((4_335_681_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 384_834 nanoseconds. + Weight::from_ref_time(348_341_375) + // Standard Error: 792_708 + .saturating_add(Weight::from_ref_time(1_336_691_822).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(7)) + .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(4)) + .saturating_add(RocksDbWeight::get().writes((80_u64).saturating_mul(r.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. fn seal_call(r: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 11_019_000 - .saturating_add((39_806_777_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) - } - // Storage: Contracts ContractInfoOf (r:101 w:101) + // Minimum execution time: 386_112 nanoseconds. + Weight::from_ref_time(386_971_000) + // Standard Error: 5_920_386 + .saturating_add(Weight::from_ref_time(28_051_657_660).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(7)) + .saturating_add(RocksDbWeight::get().reads((160_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((160_u64).saturating_mul(r.into()))) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_delegate_call(r: u32, ) -> Weight { + // Minimum execution time: 385_776 nanoseconds. + Weight::from_ref_time(387_017_000) + // Standard Error: 6_680_801 + .saturating_add(Weight::from_ref_time(27_761_537_154).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((150_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((75_u64).saturating_mul(r.into()))) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:101 w:101) - fn seal_call_per_transfer_input_output_kb(t: u32, i: u32, o: u32, ) -> Weight { - (38_662_592_000 as Weight) - // Standard Error: 52_762_000 - .saturating_add((3_888_801_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 18_000 - .saturating_add((63_571_000 as Weight).saturating_mul(i as Weight)) - // Standard Error: 20_000 - .saturating_add((101_610_000 as Weight).saturating_mul(o as Weight)) - .saturating_add(RocksDbWeight::get().reads(104 as Weight)) - .saturating_add(RocksDbWeight::get().reads((101 as Weight).saturating_mul(t as Weight))) - .saturating_add(RocksDbWeight::get().writes(101 as Weight)) - .saturating_add(RocksDbWeight::get().writes((101 as Weight).saturating_mul(t as Weight))) + // Storage: System EventTopics (r:82 w:82) + /// The range of component `t` is `[0, 1]`. + /// The range of component `c` is `[0, 1024]`. + fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { + // Minimum execution time: 9_623_952 nanoseconds. + Weight::from_ref_time(8_552_923_566) + // Standard Error: 6_582_866 + .saturating_add(Weight::from_ref_time(1_283_786_003).saturating_mul(t.into())) + // Standard Error: 9_870 + .saturating_add(Weight::from_ref_time(9_833_844).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(167)) + .saturating_add(RocksDbWeight::get().reads((81_u64).saturating_mul(t.into()))) + .saturating_add(RocksDbWeight::get().writes(163)) + .saturating_add(RocksDbWeight::get().writes((81_u64).saturating_mul(t.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts AccountCounter (r:1 w:1) - // Storage: System Account (r:101 w:101) + // Storage: System EventTopics (r:2 w:2) + // Storage: Contracts Nonce (r:1 w:1) + // Storage: Contracts OwnerInfoOf (r:80 w:80) + /// The range of component `r` is `[0, 20]`. fn seal_instantiate(r: u32, ) -> Weight { - (626_132_000 as Weight) - // Standard Error: 39_245_000 - .saturating_add((46_398_859_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().reads((300 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes((300 as Weight).saturating_mul(r as Weight))) - } - // Storage: Contracts ContractInfoOf (r:101 w:101) + // Minimum execution time: 386_667 nanoseconds. + Weight::from_ref_time(387_559_000) + // Standard Error: 18_953_118 + .saturating_add(Weight::from_ref_time(33_188_342_429).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().reads((400_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(5)) + .saturating_add(RocksDbWeight::get().writes((400_u64).saturating_mul(r.into()))) + } + // Storage: System Account (r:81 w:81) + // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:1) // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts AccountCounter (r:1 w:1) - // Storage: System Account (r:101 w:101) - fn seal_instantiate_per_input_output_salt_kb(i: u32, o: u32, s: u32, ) -> Weight { - (46_649_369_000 as Weight) - // Standard Error: 26_000 - .saturating_add((63_469_000 as Weight).saturating_mul(i as Weight)) - // Standard Error: 26_000 - .saturating_add((100_694_000 as Weight).saturating_mul(o as Weight)) - // Standard Error: 26_000 - .saturating_add((201_705_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(206 as Weight)) - .saturating_add(RocksDbWeight::get().writes(204 as Weight)) + // Storage: Contracts Nonce (r:1 w:1) + // Storage: Contracts OwnerInfoOf (r:1 w:1) + // Storage: System EventTopics (r:82 w:82) + /// The range of component `t` is `[0, 1]`. + /// The range of component `s` is `[0, 960]`. + fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { + // Minimum execution time: 11_664_478 nanoseconds. + Weight::from_ref_time(11_359_540_086) + // Standard Error: 45_626_277 + .saturating_add(Weight::from_ref_time(19_120_579).saturating_mul(t.into())) + // Standard Error: 72_976 + .saturating_add(Weight::from_ref_time(125_731_953).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(249)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(RocksDbWeight::get().writes(247)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_hash_sha2_256(r: u32, ) -> Weight { - (417_820_000 as Weight) - // Standard Error: 160_000 - .saturating_add((133_795_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 384_826 nanoseconds. + Weight::from_ref_time(387_293_630) + // Standard Error: 437_875 + .saturating_add(Weight::from_ref_time(48_464_369).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - (609_012_000 as Weight) - // Standard Error: 23_000 - .saturating_add((499_227_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 426_531 nanoseconds. + Weight::from_ref_time(427_315_000) + // Standard Error: 48_058 + .saturating_add(Weight::from_ref_time(327_318_884).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { - (419_043_000 as Weight) - // Standard Error: 177_000 - .saturating_add((140_704_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 384_084 nanoseconds. + Weight::from_ref_time(386_354_628) + // Standard Error: 195_951 + .saturating_add(Weight::from_ref_time(52_991_271).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - (564_451_000 as Weight) - // Standard Error: 19_000 - .saturating_add((346_948_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 438_319 nanoseconds. + Weight::from_ref_time(439_001_000) + // Standard Error: 53_445 + .saturating_add(Weight::from_ref_time(251_353_803).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { - (420_951_000 as Weight) - // Standard Error: 163_000 - .saturating_add((113_596_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 384_397 nanoseconds. + Weight::from_ref_time(386_532_859) + // Standard Error: 141_195 + .saturating_add(Weight::from_ref_time(31_116_440).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - (563_168_000 as Weight) - // Standard Error: 17_000 - .saturating_add((164_114_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 416_504 nanoseconds. + Weight::from_ref_time(417_686_000) + // Standard Error: 47_003 + .saturating_add(Weight::from_ref_time(103_095_636).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_128(r: u32, ) -> Weight { - (418_794_000 as Weight) - // Standard Error: 167_000 - .saturating_add((113_205_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 382_479 nanoseconds. + Weight::from_ref_time(384_623_057) + // Standard Error: 243_054 + .saturating_add(Weight::from_ref_time(37_025_542).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - (584_668_000 as Weight) - // Standard Error: 15_000 - .saturating_add((164_127_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 414_863 nanoseconds. + Weight::from_ref_time(415_728_000) + // Standard Error: 48_764 + .saturating_add(Weight::from_ref_time(103_050_672).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { - (435_443_000 as Weight) - // Standard Error: 1_408_000 - .saturating_add((15_624_877_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 384_418 nanoseconds. + Weight::from_ref_time(387_283_069) + // Standard Error: 526_301 + .saturating_add(Weight::from_ref_time(2_993_987_030).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 1]`. + fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { + // Minimum execution time: 383_686 nanoseconds. + Weight::from_ref_time(385_812_638) + // Standard Error: 539_029 + .saturating_add(Weight::from_ref_time(2_098_063_561).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + // Storage: Contracts OwnerInfoOf (r:16 w:16) + /// The range of component `r` is `[0, 20]`. + fn seal_set_code_hash(r: u32, ) -> Weight { + // Minimum execution time: 384_399 nanoseconds. + Weight::from_ref_time(385_337_000) + // Standard Error: 2_827_655 + .saturating_add(Weight::from_ref_time(1_383_659_432).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().reads((225_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((150_u64).saturating_mul(r.into()))) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_reentrance_count(r: u32, ) -> Weight { + // Minimum execution time: 385_165 nanoseconds. + Weight::from_ref_time(389_255_026) + // Standard Error: 25_918 + .saturating_add(Weight::from_ref_time(10_716_905).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + /// The range of component `r` is `[0, 20]`. + fn seal_account_reentrance_count(r: u32, ) -> Weight { + // Minimum execution time: 386_959 nanoseconds. + Weight::from_ref_time(423_364_524) + // Standard Error: 127_096 + .saturating_add(Weight::from_ref_time(25_552_186).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: System EventTopics (r:2 w:2) + // Storage: Contracts Nonce (r:1 w:1) + /// The range of component `r` is `[0, 20]`. + fn seal_instantiation_nonce(r: u32, ) -> Weight { + // Minimum execution time: 293_987 nanoseconds. + Weight::from_ref_time(307_154_849) + // Standard Error: 27_486 + .saturating_add(Weight::from_ref_time(8_759_333).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(7)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + /// The range of component `r` is `[0, 50]`. fn instr_i64const(r: u32, ) -> Weight { - (45_937_000 as Weight) - // Standard Error: 10_000 - .saturating_add((1_108_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 688 nanoseconds. + Weight::from_ref_time(914_830) + // Standard Error: 222 + .saturating_add(Weight::from_ref_time(343_835).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64load(r: u32, ) -> Weight { - (44_001_000 as Weight) - // Standard Error: 11_000 - .saturating_add((2_412_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 775 nanoseconds. + Weight::from_ref_time(1_286_008) + // Standard Error: 391 + .saturating_add(Weight::from_ref_time(984_759).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64store(r: u32, ) -> Weight { - (43_157_000 as Weight) - // Standard Error: 12_000 - .saturating_add((2_677_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 779 nanoseconds. + Weight::from_ref_time(1_162_588) + // Standard Error: 694 + .saturating_add(Weight::from_ref_time(883_828).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_select(r: u32, ) -> Weight { - (48_475_000 as Weight) - // Standard Error: 8_000 - .saturating_add((2_604_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 687 nanoseconds. + Weight::from_ref_time(965_966) + // Standard Error: 290 + .saturating_add(Weight::from_ref_time(955_392).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_if(r: u32, ) -> Weight { - (50_649_000 as Weight) - // Standard Error: 12_000 - .saturating_add((2_553_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 681 nanoseconds. + Weight::from_ref_time(778_970) + // Standard Error: 670 + .saturating_add(Weight::from_ref_time(1_265_116).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_br(r: u32, ) -> Weight { - (48_433_000 as Weight) - // Standard Error: 8_000 - .saturating_add((1_670_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 673 nanoseconds. + Weight::from_ref_time(1_125_562) + // Standard Error: 818 + .saturating_add(Weight::from_ref_time(528_126).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_br_if(r: u32, ) -> Weight { - (49_244_000 as Weight) - // Standard Error: 16_000 - .saturating_add((1_946_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 649 nanoseconds. + Weight::from_ref_time(780_802) + // Standard Error: 943 + .saturating_add(Weight::from_ref_time(800_988).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_br_table(r: u32, ) -> Weight { - (46_117_000 as Weight) - // Standard Error: 17_000 - .saturating_add((2_387_000 as Weight).saturating_mul(r as Weight)) - } - fn instr_br_table_per_entry(_e: u32, ) -> Weight { - (55_204_000 as Weight) - } + // Minimum execution time: 662 nanoseconds. + Weight::from_ref_time(555_078) + // Standard Error: 1_540 + .saturating_add(Weight::from_ref_time(1_078_705).saturating_mul(r.into())) + } + /// The range of component `e` is `[1, 256]`. + fn instr_br_table_per_entry(e: u32, ) -> Weight { + // Minimum execution time: 2_177 nanoseconds. + Weight::from_ref_time(2_581_121) + // Standard Error: 67 + .saturating_add(Weight::from_ref_time(5_001).saturating_mul(e.into())) + } + /// The range of component `r` is `[0, 50]`. fn instr_call(r: u32, ) -> Weight { - (43_651_000 as Weight) - // Standard Error: 26_000 - .saturating_add((19_163_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 702 nanoseconds. + Weight::from_ref_time(1_570_695) + // Standard Error: 1_524 + .saturating_add(Weight::from_ref_time(2_192_040).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_call_indirect(r: u32, ) -> Weight { - (54_063_000 as Weight) - // Standard Error: 32_000 - .saturating_add((27_970_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 745 nanoseconds. + Weight::from_ref_time(1_694_451) + // Standard Error: 4_170 + .saturating_add(Weight::from_ref_time(2_813_621).saturating_mul(r.into())) } + /// The range of component `p` is `[0, 128]`. fn instr_call_indirect_per_param(p: u32, ) -> Weight { - (88_527_000 as Weight) - // Standard Error: 6_000 - .saturating_add((958_000 as Weight).saturating_mul(p as Weight)) - } + // Minimum execution time: 4_255 nanoseconds. + Weight::from_ref_time(5_064_243) + // Standard Error: 289 + .saturating_add(Weight::from_ref_time(178_868).saturating_mul(p.into())) + } + /// The range of component `l` is `[0, 1024]`. + fn instr_call_per_local(l: u32, ) -> Weight { + // Minimum execution time: 2_802 nanoseconds. + Weight::from_ref_time(3_474_642) + // Standard Error: 28 + .saturating_add(Weight::from_ref_time(92_517).saturating_mul(l.into())) + } + /// The range of component `r` is `[0, 50]`. fn instr_local_get(r: u32, ) -> Weight { - (55_066_000 as Weight) - // Standard Error: 12_000 - .saturating_add((682_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 2_973 nanoseconds. + Weight::from_ref_time(3_218_977) + // Standard Error: 183 + .saturating_add(Weight::from_ref_time(364_688).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_local_set(r: u32, ) -> Weight { - (55_298_000 as Weight) - // Standard Error: 13_000 - .saturating_add((778_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 2_912 nanoseconds. + Weight::from_ref_time(3_173_203) + // Standard Error: 260 + .saturating_add(Weight::from_ref_time(381_853).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_local_tee(r: u32, ) -> Weight { - (56_302_000 as Weight) - // Standard Error: 11_000 - .saturating_add((1_079_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 2_916 nanoseconds. + Weight::from_ref_time(3_228_548) + // Standard Error: 311 + .saturating_add(Weight::from_ref_time(526_008).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_global_get(r: u32, ) -> Weight { - (71_567_000 as Weight) - // Standard Error: 11_000 - .saturating_add((1_107_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 739 nanoseconds. + Weight::from_ref_time(1_128_919) + // Standard Error: 479 + .saturating_add(Weight::from_ref_time(810_765).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_global_set(r: u32, ) -> Weight { - (71_186_000 as Weight) - // Standard Error: 12_000 - .saturating_add((1_151_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 724 nanoseconds. + Weight::from_ref_time(1_044_382) + // Standard Error: 371 + .saturating_add(Weight::from_ref_time(828_530).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_memory_current(r: u32, ) -> Weight { - (46_240_000 as Weight) - // Standard Error: 10_000 - .saturating_add((1_044_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 770 nanoseconds. + Weight::from_ref_time(988_307) + // Standard Error: 587 + .saturating_add(Weight::from_ref_time(699_091).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 1]`. fn instr_memory_grow(r: u32, ) -> Weight { - (52_369_000 as Weight) - // Standard Error: 2_508_000 - .saturating_add((615_448_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 688 nanoseconds. + Weight::from_ref_time(747_995) + // Standard Error: 3_894 + .saturating_add(Weight::from_ref_time(234_512_504).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64clz(r: u32, ) -> Weight { - (47_623_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_583_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 643 nanoseconds. + Weight::from_ref_time(946_893) + // Standard Error: 188 + .saturating_add(Weight::from_ref_time(505_004).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ctz(r: u32, ) -> Weight { - (47_670_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_583_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 663 nanoseconds. + Weight::from_ref_time(965_194) + // Standard Error: 343 + .saturating_add(Weight::from_ref_time(505_213).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64popcnt(r: u32, ) -> Weight { - (47_508_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_583_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 675 nanoseconds. + Weight::from_ref_time(903_705) + // Standard Error: 425 + .saturating_add(Weight::from_ref_time(507_749).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64eqz(r: u32, ) -> Weight { - (48_109_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_580_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(946_419) + // Standard Error: 301 + .saturating_add(Weight::from_ref_time(522_387).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64extendsi32(r: u32, ) -> Weight { - (55_270_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_102_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 674 nanoseconds. + Weight::from_ref_time(963_566) + // Standard Error: 952 + .saturating_add(Weight::from_ref_time(504_528).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64extendui32(r: u32, ) -> Weight { - (55_093_000 as Weight) - // Standard Error: 9_000 - .saturating_add((1_108_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 663 nanoseconds. + Weight::from_ref_time(927_099) + // Standard Error: 336 + .saturating_add(Weight::from_ref_time(505_200).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i32wrapi64(r: u32, ) -> Weight { - (48_265_000 as Weight) - // Standard Error: 10_000 - .saturating_add((1_573_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 660 nanoseconds. + Weight::from_ref_time(901_114) + // Standard Error: 255 + .saturating_add(Weight::from_ref_time(503_803).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64eq(r: u32, ) -> Weight { - (48_733_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_088_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 636 nanoseconds. + Weight::from_ref_time(906_526) + // Standard Error: 246 + .saturating_add(Weight::from_ref_time(730_299).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ne(r: u32, ) -> Weight { - (48_831_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_085_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 659 nanoseconds. + Weight::from_ref_time(947_772) + // Standard Error: 312 + .saturating_add(Weight::from_ref_time(729_463).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64lts(r: u32, ) -> Weight { - (49_147_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_056_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 646 nanoseconds. + Weight::from_ref_time(923_694) + // Standard Error: 243 + .saturating_add(Weight::from_ref_time(738_995).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ltu(r: u32, ) -> Weight { - (49_596_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_049_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(955_453) + // Standard Error: 1_430 + .saturating_add(Weight::from_ref_time(741_624).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64gts(r: u32, ) -> Weight { - (49_872_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_038_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 642 nanoseconds. + Weight::from_ref_time(900_107) + // Standard Error: 376 + .saturating_add(Weight::from_ref_time(740_016).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64gtu(r: u32, ) -> Weight { - (48_843_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_081_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 625 nanoseconds. + Weight::from_ref_time(946_744) + // Standard Error: 252 + .saturating_add(Weight::from_ref_time(743_532).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64les(r: u32, ) -> Weight { - (48_765_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_089_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(918_551) + // Standard Error: 313 + .saturating_add(Weight::from_ref_time(731_451).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64leu(r: u32, ) -> Weight { - (48_720_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_083_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 651 nanoseconds. + Weight::from_ref_time(923_475) + // Standard Error: 341 + .saturating_add(Weight::from_ref_time(738_353).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ges(r: u32, ) -> Weight { - (48_736_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_097_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 666 nanoseconds. + Weight::from_ref_time(1_136_987) + // Standard Error: 1_482 + .saturating_add(Weight::from_ref_time(727_254).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64geu(r: u32, ) -> Weight { - (48_772_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_093_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 633 nanoseconds. + Weight::from_ref_time(934_201) + // Standard Error: 332 + .saturating_add(Weight::from_ref_time(731_804).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64add(r: u32, ) -> Weight { - (48_827_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_082_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 673 nanoseconds. + Weight::from_ref_time(983_317) + // Standard Error: 492 + .saturating_add(Weight::from_ref_time(718_126).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64sub(r: u32, ) -> Weight { - (48_961_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_072_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 647 nanoseconds. + Weight::from_ref_time(925_490) + // Standard Error: 1_799 + .saturating_add(Weight::from_ref_time(711_178).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64mul(r: u32, ) -> Weight { - (49_069_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_067_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(955_546) + // Standard Error: 283 + .saturating_add(Weight::from_ref_time(710_844).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64divs(r: u32, ) -> Weight { - (49_035_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_677_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(982_314) + // Standard Error: 267 + .saturating_add(Weight::from_ref_time(1_344_080).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64divu(r: u32, ) -> Weight { - (48_842_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_449_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(913_421) + // Standard Error: 737 + .saturating_add(Weight::from_ref_time(1_285_749).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rems(r: u32, ) -> Weight { - (48_536_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_723_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(939_041) + // Standard Error: 354 + .saturating_add(Weight::from_ref_time(1_391_470).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64remu(r: u32, ) -> Weight { - (48_851_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_432_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 656 nanoseconds. + Weight::from_ref_time(951_030) + // Standard Error: 342 + .saturating_add(Weight::from_ref_time(1_287_904).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64and(r: u32, ) -> Weight { - (48_624_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_093_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 682 nanoseconds. + Weight::from_ref_time(940_975) + // Standard Error: 195 + .saturating_add(Weight::from_ref_time(717_132).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64or(r: u32, ) -> Weight { - (49_348_000 as Weight) - // Standard Error: 8_000 - .saturating_add((2_073_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 704 nanoseconds. + Weight::from_ref_time(941_860) + // Standard Error: 200 + .saturating_add(Weight::from_ref_time(717_696).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64xor(r: u32, ) -> Weight { - (49_112_000 as Weight) - // Standard Error: 6_000 - .saturating_add((2_055_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 648 nanoseconds. + Weight::from_ref_time(917_135) + // Standard Error: 237 + .saturating_add(Weight::from_ref_time(717_979).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shl(r: u32, ) -> Weight { - (49_654_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_051_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(1_031_822) + // Standard Error: 937 + .saturating_add(Weight::from_ref_time(730_965).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shrs(r: u32, ) -> Weight { - (48_848_000 as Weight) - // Standard Error: 8_000 - .saturating_add((2_089_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 671 nanoseconds. + Weight::from_ref_time(935_833) + // Standard Error: 184 + .saturating_add(Weight::from_ref_time(732_227).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shru(r: u32, ) -> Weight { - (49_455_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_054_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(962_491) + // Standard Error: 488 + .saturating_add(Weight::from_ref_time(733_218).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rotl(r: u32, ) -> Weight { - (49_640_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_048_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 643 nanoseconds. + Weight::from_ref_time(951_949) + // Standard Error: 321 + .saturating_add(Weight::from_ref_time(732_212).saturating_mul(r.into())) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rotr(r: u32, ) -> Weight { - (49_498_000 as Weight) - // Standard Error: 7_000 - .saturating_add((2_068_000 as Weight).saturating_mul(r as Weight)) + // Minimum execution time: 665 nanoseconds. + Weight::from_ref_time(951_447) + // Standard Error: 346 + .saturating_add(Weight::from_ref_time(732_549).saturating_mul(r.into())) } } diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml new file mode 100644 index 0000000000000..9bfc93f2d9ff5 --- /dev/null +++ b/frame/conviction-voting/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "pallet-conviction-voting" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet for conviction voting in referenda" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +assert_matches = "1.3.0" +codec = { package = "parity-scale-codec", version = "3.0.3", default-features = false, features = [ + "derive", + "max-encoded-len", +] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "serde", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/conviction-voting/README.md b/frame/conviction-voting/README.md new file mode 100644 index 0000000000000..5dc5d526d5c23 --- /dev/null +++ b/frame/conviction-voting/README.md @@ -0,0 +1,8 @@ +# Voting Pallet + +- [`assembly::Config`](https://docs.rs/pallet-assembly/latest/pallet_assembly/trait.Config.html) +- [`Call`](https://docs.rs/pallet-assembly/latest/pallet_assembly/enum.Call.html) + +## Overview + +Pallet for voting in referenda. diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs new file mode 100644 index 0000000000000..117bb7fe22989 --- /dev/null +++ b/frame/conviction-voting/src/benchmarking.rs @@ -0,0 +1,283 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2021 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. + +//! ConvictionVoting pallet benchmarking. + +use super::*; + +use assert_matches::assert_matches; +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelist_account}; +use frame_support::{ + dispatch::RawOrigin, + traits::{fungible, Currency, Get}, +}; +use sp_runtime::traits::Bounded; +use sp_std::collections::btree_map::BTreeMap; + +use crate::Pallet as ConvictionVoting; + +const SEED: u32 = 0; + +/// Fill all classes as much as possible up to `MaxVotes` and return the Class with the most votes +/// ongoing. +fn fill_voting, I: 'static>( +) -> (ClassOf, BTreeMap, Vec>>) { + let mut r = BTreeMap::, Vec>>::new(); + for class in T::Polls::classes().into_iter() { + for _ in 0..T::MaxVotes::get() { + match T::Polls::create_ongoing(class.clone()) { + Ok(i) => r.entry(class.clone()).or_default().push(i), + Err(()) => break, + } + } + } + let c = r.iter().max_by_key(|(_, v)| v.len()).unwrap().0.clone(); + (c, r) +} + +fn funded_account, I: 'static>(name: &'static str, index: u32) -> T::AccountId { + let caller: T::AccountId = account(name, index, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + caller +} + +fn account_vote, I: 'static>(b: BalanceOf) -> AccountVote> { + let v = Vote { aye: true, conviction: Conviction::Locked1x }; + + AccountVote::Standard { vote: v, balance: b } +} + +benchmarks_instance_pallet! { + where_clause { where T::MaxVotes: core::fmt::Debug } + + vote_new { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let account_vote = account_vote::(100u32.into()); + + let (class, all_polls) = fill_voting::(); + let polls = &all_polls[&class]; + let r = polls.len() - 1; + // We need to create existing votes + for i in polls.iter().skip(1) { + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, account_vote)?; + } + let votes = match VotingFor::::get(&caller, &class) { + Voting::Casting(Casting { votes, .. }) => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r as usize, "Votes were not recorded."); + + let index = polls[0]; + }: vote(RawOrigin::Signed(caller.clone()), index, account_vote) + verify { + assert_matches!( + VotingFor::::get(&caller, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == (r + 1) as usize + ); + } + + vote_existing { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let old_account_vote = account_vote::(100u32.into()); + + let (class, all_polls) = fill_voting::(); + let polls = &all_polls[&class]; + let r = polls.len(); + // We need to create existing votes + for i in polls.iter() { + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote)?; + } + let votes = match VotingFor::::get(&caller, &class) { + Voting::Casting(Casting { votes, .. }) => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r, "Votes were not recorded."); + + let new_account_vote = account_vote::(200u32.into()); + let index = polls[0]; + }: vote(RawOrigin::Signed(caller.clone()), index, new_account_vote) + verify { + assert_matches!( + VotingFor::::get(&caller, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize + ); + } + + remove_vote { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let old_account_vote = account_vote::(100u32.into()); + + let (class, all_polls) = fill_voting::(); + let polls = &all_polls[&class]; + let r = polls.len(); + // We need to create existing votes + for i in polls.iter() { + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote)?; + } + let votes = match VotingFor::::get(&caller, &class) { + Voting::Casting(Casting { votes, .. }) => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r, "Votes were not recorded."); + + let index = polls[0]; + }: _(RawOrigin::Signed(caller.clone()), Some(class.clone()), index) + verify { + assert_matches!( + VotingFor::::get(&caller, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == (r - 1) as usize + ); + } + + remove_other_vote { + let caller = funded_account::("caller", 0); + let voter = funded_account::("caller", 0); + let voter_lookup = T::Lookup::unlookup(voter.clone()); + whitelist_account!(caller); + let old_account_vote = account_vote::(100u32.into()); + + let (class, all_polls) = fill_voting::(); + let polls = &all_polls[&class]; + let r = polls.len(); + // We need to create existing votes + for i in polls.iter() { + ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, old_account_vote)?; + } + let votes = match VotingFor::::get(&caller, &class) { + Voting::Casting(Casting { votes, .. }) => votes, + _ => return Err("Votes are not direct".into()), + }; + assert_eq!(votes.len(), r, "Votes were not recorded."); + + let index = polls[0]; + assert!(T::Polls::end_ongoing(index, false).is_ok()); + }: _(RawOrigin::Signed(caller.clone()), voter_lookup, class.clone(), index) + verify { + assert_matches!( + VotingFor::::get(&voter, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == (r - 1) as usize + ); + } + + delegate { + let r in 0 .. T::MaxVotes::get().min(T::Polls::max_ongoing().1); + + let all_polls = fill_voting::().1; + let class = T::Polls::max_ongoing().0; + let polls = &all_polls[&class]; + let voter = funded_account::("voter", 0); + let voter_lookup = T::Lookup::unlookup(voter.clone()); + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + + let delegated_balance: BalanceOf = 1000u32.into(); + let delegate_vote = account_vote::(delegated_balance); + + // We need to create existing delegations + for i in polls.iter().take(r as usize) { + ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote)?; + } + assert_matches!( + VotingFor::::get(&voter, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize + ); + + }: _(RawOrigin::Signed(caller.clone()), class.clone(), voter_lookup, Conviction::Locked1x, delegated_balance) + verify { + assert_matches!(VotingFor::::get(&caller, &class), Voting::Delegating(_)); + } + + undelegate { + let r in 0 .. T::MaxVotes::get().min(T::Polls::max_ongoing().1); + + let all_polls = fill_voting::().1; + let class = T::Polls::max_ongoing().0; + let polls = &all_polls[&class]; + let voter = funded_account::("voter", 0); + let voter_lookup = T::Lookup::unlookup(voter.clone()); + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + + let delegated_balance: BalanceOf = 1000u32.into(); + let delegate_vote = account_vote::(delegated_balance); + + ConvictionVoting::::delegate( + RawOrigin::Signed(caller.clone()).into(), + class.clone(), + voter_lookup, + Conviction::Locked1x, + delegated_balance, + )?; + + // We need to create delegations + for i in polls.iter().take(r as usize) { + ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote)?; + } + assert_matches!( + VotingFor::::get(&voter, &class), + Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize + ); + assert_matches!(VotingFor::::get(&caller, &class), Voting::Delegating(_)); + }: _(RawOrigin::Signed(caller.clone()), class.clone()) + verify { + assert_matches!(VotingFor::::get(&caller, &class), Voting::Casting(_)); + } + + unlock { + let caller = funded_account::("caller", 0); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + whitelist_account!(caller); + let normal_account_vote = account_vote::(T::Currency::free_balance(&caller) - 100u32.into()); + let big_account_vote = account_vote::(T::Currency::free_balance(&caller)); + + // Fill everything up to the max by filling all classes with votes and voting on them all. + let (class, all_polls) = fill_voting::(); + assert!(all_polls.len() > 0); + for (class, polls) in all_polls.iter() { + assert!(polls.len() > 0); + for i in polls.iter() { + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, normal_account_vote)?; + } + } + + let orig_usable = >::reducible_balance(&caller, false); + let polls = &all_polls[&class]; + + // Vote big on the class with the most ongoing votes of them to bump the lock and make it + // hard to recompute when removed. + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), polls[0], big_account_vote)?; + let now_usable = >::reducible_balance(&caller, false); + assert_eq!(orig_usable - now_usable, 100u32.into()); + + // Remove the vote + ConvictionVoting::::remove_vote(RawOrigin::Signed(caller.clone()).into(), Some(class.clone()), polls[0])?; + + // We can now unlock on `class` from 200 to 100... + }: _(RawOrigin::Signed(caller.clone()), class, caller_lookup) + verify { + assert_eq!(orig_usable, >::reducible_balance(&caller, false)); + } + + impl_benchmark_test_suite!( + ConvictionVoting, + crate::tests::new_test_ext(), + crate::tests::Test + ); +} diff --git a/frame/conviction-voting/src/conviction.rs b/frame/conviction-voting/src/conviction.rs new file mode 100644 index 0000000000000..1feff35b19fcc --- /dev/null +++ b/frame/conviction-voting/src/conviction.rs @@ -0,0 +1,131 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! The conviction datatype. + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Bounded, CheckedDiv, CheckedMul, Zero}, + RuntimeDebug, +}; + +use crate::types::Delegations; + +/// A value denoting the strength of conviction of a vote. +#[derive( + Encode, + Decode, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + RuntimeDebug, + TypeInfo, + MaxEncodedLen, +)] +pub enum Conviction { + /// 0.1x votes, unlocked. + None, + /// 1x votes, locked for an enactment period following a successful vote. + Locked1x, + /// 2x votes, locked for 2x enactment periods following a successful vote. + Locked2x, + /// 3x votes, locked for 4x... + Locked3x, + /// 4x votes, locked for 8x... + Locked4x, + /// 5x votes, locked for 16x... + Locked5x, + /// 6x votes, locked for 32x... + Locked6x, +} + +impl Default for Conviction { + fn default() -> Self { + Conviction::None + } +} + +impl From for u8 { + fn from(c: Conviction) -> u8 { + match c { + Conviction::None => 0, + Conviction::Locked1x => 1, + Conviction::Locked2x => 2, + Conviction::Locked3x => 3, + Conviction::Locked4x => 4, + Conviction::Locked5x => 5, + Conviction::Locked6x => 6, + } + } +} + +impl TryFrom for Conviction { + type Error = (); + fn try_from(i: u8) -> Result { + Ok(match i { + 0 => Conviction::None, + 1 => Conviction::Locked1x, + 2 => Conviction::Locked2x, + 3 => Conviction::Locked3x, + 4 => Conviction::Locked4x, + 5 => Conviction::Locked5x, + 6 => Conviction::Locked6x, + _ => return Err(()), + }) + } +} + +impl Conviction { + /// The amount of time (in number of periods) that our conviction implies a successful voter's + /// balance should be locked for. + pub fn lock_periods(self) -> u32 { + match self { + Conviction::None => 0, + Conviction::Locked1x => 1, + Conviction::Locked2x => 2, + Conviction::Locked3x => 4, + Conviction::Locked4x => 8, + Conviction::Locked5x => 16, + Conviction::Locked6x => 32, + } + } + + /// The votes of a voter of the given `balance` with our conviction. + pub fn votes + Zero + Copy + CheckedMul + CheckedDiv + Bounded>( + self, + capital: B, + ) -> Delegations { + let votes = match self { + Conviction::None => capital.checked_div(&10u8.into()).unwrap_or_else(Zero::zero), + x => capital.checked_mul(&u8::from(x).into()).unwrap_or_else(B::max_value), + }; + Delegations { votes, capital } + } +} + +impl Bounded for Conviction { + fn min_value() -> Self { + Conviction::None + } + fn max_value() -> Self { + Conviction::Locked6x + } +} diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs new file mode 100644 index 0000000000000..141e9690fa29d --- /dev/null +++ b/frame/conviction-voting/src/lib.rs @@ -0,0 +1,674 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! # Voting Pallet +//! +//! - [`Config`] +//! - [`Call`] +//! +//! ## Overview +//! +//! Pallet for managing actual voting in polls. + +#![recursion_limit = "256"] +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + ensure, + traits::{ + fungible, Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling, + ReservableCurrency, WithdrawReasons, + }, +}; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, Saturating, StaticLookup, Zero}, + ArithmeticError, Perbill, +}; +use sp_std::prelude::*; + +mod conviction; +mod types; +mod vote; +pub mod weights; + +pub use self::{ + conviction::Conviction, + pallet::*, + types::{Delegations, Tally, UnvoteScope}, + vote::{AccountVote, Casting, Delegating, Vote, Voting}, + weights::WeightInfo, +}; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +const CONVICTION_VOTING_ID: LockIdentifier = *b"pyconvot"; + +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +type BalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +type VotingOf = Voting< + BalanceOf, + ::AccountId, + ::BlockNumber, + PollIndexOf, + >::MaxVotes, +>; +#[allow(dead_code)] +type DelegatingOf = Delegating< + BalanceOf, + ::AccountId, + ::BlockNumber, +>; +pub type TallyOf = Tally, >::MaxTurnout>; +pub type VotesOf = BalanceOf; +type PollIndexOf = <>::Polls as Polling>>::Index; +#[cfg(feature = "runtime-benchmarks")] +type IndexOf = <>::Polls as Polling>>::Index; +type ClassOf = <>::Polls as Polling>>::Class; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{pallet_prelude::*, traits::ClassCountOf}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + Sized { + // System level stuff. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + /// Currency type with which voting happens. + type Currency: ReservableCurrency + + LockableCurrency + + fungible::Inspect; + + /// The implementation of the logic which conducts polls. + type Polls: Polling< + TallyOf, + Votes = BalanceOf, + Moment = Self::BlockNumber, + >; + + /// The maximum amount of tokens which may be used for voting. May just be + /// `Currency::total_issuance`, but you might want to reduce this in order to account for + /// funds in the system which are unable to vote (e.g. parachain auction deposits). + type MaxTurnout: Get>; + + /// The maximum number of concurrent votes an account may have. + /// + /// Also used to compute weight, an overly large value can lead to extrinsics with large + /// weight estimation: see `delegate` for instance. + #[pallet::constant] + type MaxVotes: Get; + + /// The minimum period of vote locking. + /// + /// It should be no shorter than enactment period to ensure that in the case of an approval, + /// those successful voters are locked into the consequences that their votes entail. + #[pallet::constant] + type VoteLockingPeriod: Get; + } + + /// All voting for a particular voter in a particular voting class. We store the balance for the + /// number of votes that we have recorded. + #[pallet::storage] + pub type VotingFor, I: 'static = ()> = StorageDoubleMap< + _, + Twox64Concat, + T::AccountId, + Twox64Concat, + ClassOf, + VotingOf, + ValueQuery, + >; + + /// The voting classes which have a non-zero lock requirement and the lock amounts which they + /// require. The actual amount locked on behalf of this pallet should always be the maximum of + /// this list. + #[pallet::storage] + pub type ClassLocksFor, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + T::AccountId, + BoundedVec<(ClassOf, BalanceOf), ClassCountOf>>, + ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// An account has delegated their vote to another account. \[who, target\] + Delegated(T::AccountId, T::AccountId), + /// An \[account\] has cancelled a previous delegation operation. + Undelegated(T::AccountId), + } + + #[pallet::error] + pub enum Error { + /// Poll is not ongoing. + NotOngoing, + /// The given account did not vote on the poll. + NotVoter, + /// The actor has no permission to conduct the action. + NoPermission, + /// The actor has no permission to conduct the action right now but will do in the future. + NoPermissionYet, + /// The account is already delegating. + AlreadyDelegating, + /// The account currently has votes attached to it and the operation cannot succeed until + /// these are removed, either through `unvote` or `reap_vote`. + AlreadyVoting, + /// Too high a balance was provided that the account cannot afford. + InsufficientFunds, + /// The account is not currently delegating. + NotDelegating, + /// Delegation to oneself makes no sense. + Nonsense, + /// Maximum number of votes reached. + MaxVotesReached, + /// The class must be supplied since it is not easily determinable from the state. + ClassNeeded, + /// The class ID supplied is invalid. + BadClass, + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Vote in a poll. If `vote.is_aye()`, the vote is to enact the proposal; + /// otherwise it is a vote to keep the status quo. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `poll_index`: The index of the poll to vote for. + /// - `vote`: The vote configuration. + /// + /// Weight: `O(R)` where R is the number of polls the voter has voted on. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))] + pub fn vote( + origin: OriginFor, + #[pallet::compact] poll_index: PollIndexOf, + vote: AccountVote>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::try_vote(&who, poll_index, vote) + } + + /// Delegate the voting power (with some given conviction) of the sending account for a + /// particular class of polls. + /// + /// The balance delegated is locked for as long as it's delegated, and thereafter for the + /// time appropriate for the conviction's lock period. + /// + /// The dispatch origin of this call must be _Signed_, and the signing account must either: + /// - be delegating already; or + /// - have no voting activity (if there is, then it will need to be removed/consolidated + /// through `reap_vote` or `unvote`). + /// + /// - `to`: The account whose voting the `target` account's voting power will follow. + /// - `class`: The class of polls to delegate. To delegate multiple classes, multiple calls + /// to this function are required. + /// - `conviction`: The conviction that will be attached to the delegated votes. When the + /// account is undelegated, the funds will be locked for the corresponding period. + /// - `balance`: The amount of the account's balance to be used in delegating. This must not + /// be more than the account's current balance. + /// + /// Emits `Delegated`. + /// + /// Weight: `O(R)` where R is the number of polls the voter delegating to has + /// voted on. Weight is initially charged as if maximum votes, but is refunded later. + // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure + // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] + pub fn delegate( + origin: OriginFor, + class: ClassOf, + to: AccountIdLookupOf, + conviction: Conviction, + balance: BalanceOf, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let to = T::Lookup::lookup(to)?; + let votes = Self::try_delegate(who, class, to, conviction, balance)?; + + Ok(Some(T::WeightInfo::delegate(votes)).into()) + } + + /// Undelegate the voting power of the sending account for a particular class of polls. + /// + /// Tokens may be unlocked following once an amount of time consistent with the lock period + /// of the conviction with which the delegation was issued has passed. + /// + /// The dispatch origin of this call must be _Signed_ and the signing account must be + /// currently delegating. + /// + /// - `class`: The class of polls to remove the delegation from. + /// + /// Emits `Undelegated`. + /// + /// Weight: `O(R)` where R is the number of polls the voter delegating to has + /// voted on. Weight is initially charged as if maximum votes, but is refunded later. + // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure + // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))] + pub fn undelegate( + origin: OriginFor, + class: ClassOf, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let votes = Self::try_undelegate(who, class)?; + Ok(Some(T::WeightInfo::undelegate(votes)).into()) + } + + /// Remove the lock caused by prior voting/delegating which has expired within a particular + /// class. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `class`: The class of polls to unlock. + /// - `target`: The account to remove the lock on. + /// + /// Weight: `O(R)` with R number of vote of target. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::unlock())] + pub fn unlock( + origin: OriginFor, + class: ClassOf, + target: AccountIdLookupOf, + ) -> DispatchResult { + ensure_signed(origin)?; + let target = T::Lookup::lookup(target)?; + Self::update_lock(&class, &target); + Ok(()) + } + + /// Remove a vote for a poll. + /// + /// If: + /// - the poll was cancelled, or + /// - the poll is ongoing, or + /// - the poll has ended such that + /// - the vote of the account was in opposition to the result; or + /// - there was no conviction to the account's vote; or + /// - the account made a split vote + /// ...then the vote is removed cleanly and a following call to `unlock` may result in more + /// funds being available. + /// + /// If, however, the poll has ended and: + /// - it finished corresponding to the vote of the account, and + /// - the account made a standard vote with conviction, and + /// - the lock period of the conviction is not over + /// ...then the lock will be aggregated into the overall account's lock, which may involve + /// *overlocking* (where the two locks are combined into a single lock that is the maximum + /// of both the amount locked and the time is it locked for). + /// + /// The dispatch origin of this call must be _Signed_, and the signer must have a vote + /// registered for poll `index`. + /// + /// - `index`: The index of poll of the vote to be removed. + /// - `class`: Optional parameter, if given it indicates the class of the poll. For polls + /// which have finished or are cancelled, this must be `Some`. + /// + /// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on. + /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::remove_vote())] + pub fn remove_vote( + origin: OriginFor, + class: Option>, + index: PollIndexOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::try_remove_vote(&who, index, class, UnvoteScope::Any) + } + + /// Remove a vote for a poll. + /// + /// If the `target` is equal to the signer, then this function is exactly equivalent to + /// `remove_vote`. If not equal to the signer, then the vote must have expired, + /// either because the poll was cancelled, because the voter lost the poll or + /// because the conviction period is over. + /// + /// The dispatch origin of this call must be _Signed_. + /// + /// - `target`: The account of the vote to be removed; this account must have voted for poll + /// `index`. + /// - `index`: The index of poll of the vote to be removed. + /// - `class`: The class of the poll. + /// + /// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on. + /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::remove_other_vote())] + pub fn remove_other_vote( + origin: OriginFor, + target: AccountIdLookupOf, + class: ClassOf, + index: PollIndexOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let target = T::Lookup::lookup(target)?; + let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; + Self::try_remove_vote(&target, index, Some(class), scope)?; + Ok(()) + } + } +} + +impl, I: 'static> Pallet { + /// Actually enact a vote, if legit. + fn try_vote( + who: &T::AccountId, + poll_index: PollIndexOf, + vote: AccountVote>, + ) -> DispatchResult { + ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); + T::Polls::try_access_poll(poll_index, |poll_status| { + let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; + VotingFor::::try_mutate(who, &class, |voting| { + if let Voting::Casting(Casting { ref mut votes, delegations, .. }) = voting { + match votes.binary_search_by_key(&poll_index, |i| i.0) { + Ok(i) => { + // Shouldn't be possible to fail, but we handle it gracefully. + tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?; + if let Some(approve) = votes[i].1.as_standard() { + tally.reduce(approve, *delegations); + } + votes[i].1 = vote; + }, + Err(i) => { + votes + .try_insert(i, (poll_index, vote)) + .map_err(|_| Error::::MaxVotesReached)?; + }, + } + // Shouldn't be possible to fail, but we handle it gracefully. + tally.add(vote).ok_or(ArithmeticError::Overflow)?; + if let Some(approve) = vote.as_standard() { + tally.increase(approve, *delegations); + } + } else { + return Err(Error::::AlreadyDelegating.into()) + } + // Extend the lock to `balance` (rather than setting it) since we don't know what + // other votes are in place. + Self::extend_lock(who, &class, vote.balance()); + Ok(()) + }) + }) + } + + /// Remove the account's vote for the given poll if possible. This is possible when: + /// - The poll has not finished. + /// - The poll has finished and the voter lost their direction. + /// - The poll has finished and the voter's lock period is up. + /// + /// This will generally be combined with a call to `unlock`. + fn try_remove_vote( + who: &T::AccountId, + poll_index: PollIndexOf, + class_hint: Option>, + scope: UnvoteScope, + ) -> DispatchResult { + let class = class_hint + .or_else(|| Some(T::Polls::as_ongoing(poll_index)?.1)) + .ok_or(Error::::ClassNeeded)?; + VotingFor::::try_mutate(who, class, |voting| { + if let Voting::Casting(Casting { ref mut votes, delegations, ref mut prior }) = voting { + let i = votes + .binary_search_by_key(&poll_index, |i| i.0) + .map_err(|_| Error::::NotVoter)?; + let v = votes.remove(i); + + T::Polls::try_access_poll(poll_index, |poll_status| match poll_status { + PollStatus::Ongoing(tally, _) => { + ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); + // Shouldn't be possible to fail, but we handle it gracefully. + tally.remove(v.1).ok_or(ArithmeticError::Underflow)?; + if let Some(approve) = v.1.as_standard() { + tally.reduce(approve, *delegations); + } + Ok(()) + }, + PollStatus::Completed(end, approved) => { + if let Some((lock_periods, balance)) = v.1.locked_if(approved) { + let unlock_at = end.saturating_add( + T::VoteLockingPeriod::get().saturating_mul(lock_periods.into()), + ); + let now = frame_system::Pallet::::block_number(); + if now < unlock_at { + ensure!( + matches!(scope, UnvoteScope::Any), + Error::::NoPermissionYet + ); + prior.accumulate(unlock_at, balance) + } + } + Ok(()) + }, + PollStatus::None => Ok(()), // Poll was cancelled. + }) + } else { + Ok(()) + } + }) + } + + /// Return the number of votes for `who`. + fn increase_upstream_delegation( + who: &T::AccountId, + class: &ClassOf, + amount: Delegations>, + ) -> u32 { + VotingFor::::mutate(who, class, |voting| match voting { + Voting::Delegating(Delegating { delegations, .. }) => { + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_add(amount); + 1 + }, + Voting::Casting(Casting { votes, delegations, .. }) => { + *delegations = delegations.saturating_add(amount); + for &(poll_index, account_vote) in votes.iter() { + if let AccountVote::Standard { vote, .. } = account_vote { + T::Polls::access_poll(poll_index, |poll_status| { + if let PollStatus::Ongoing(tally, _) = poll_status { + tally.increase(vote.aye, amount); + } + }); + } + } + votes.len() as u32 + }, + }) + } + + /// Return the number of votes for `who`. + fn reduce_upstream_delegation( + who: &T::AccountId, + class: &ClassOf, + amount: Delegations>, + ) -> u32 { + VotingFor::::mutate(who, class, |voting| match voting { + Voting::Delegating(Delegating { delegations, .. }) => { + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_sub(amount); + 1 + }, + Voting::Casting(Casting { votes, delegations, .. }) => { + *delegations = delegations.saturating_sub(amount); + for &(poll_index, account_vote) in votes.iter() { + if let AccountVote::Standard { vote, .. } = account_vote { + T::Polls::access_poll(poll_index, |poll_status| { + if let PollStatus::Ongoing(tally, _) = poll_status { + tally.reduce(vote.aye, amount); + } + }); + } + } + votes.len() as u32 + }, + }) + } + + /// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`. + /// + /// Return the upstream number of votes. + fn try_delegate( + who: T::AccountId, + class: ClassOf, + target: T::AccountId, + conviction: Conviction, + balance: BalanceOf, + ) -> Result { + ensure!(who != target, Error::::Nonsense); + T::Polls::classes().binary_search(&class).map_err(|_| Error::::BadClass)?; + ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); + let votes = + VotingFor::::try_mutate(&who, &class, |voting| -> Result { + let old = sp_std::mem::replace( + voting, + Voting::Delegating(Delegating { + balance, + target: target.clone(), + conviction, + delegations: Default::default(), + prior: Default::default(), + }), + ); + match old { + Voting::Delegating(Delegating { .. }) => + return Err(Error::::AlreadyDelegating.into()), + Voting::Casting(Casting { votes, delegations, prior }) => { + // here we just ensure that we're currently idling with no votes recorded. + ensure!(votes.is_empty(), Error::::AlreadyVoting); + voting.set_common(delegations, prior); + }, + } + + let votes = + Self::increase_upstream_delegation(&target, &class, conviction.votes(balance)); + // Extend the lock to `balance` (rather than setting it) since we don't know what + // other votes are in place. + Self::extend_lock(&who, &class, balance); + Ok(votes) + })?; + Self::deposit_event(Event::::Delegated(who, target)); + Ok(votes) + } + + /// Attempt to end the current delegation. + /// + /// Return the number of votes of upstream. + fn try_undelegate(who: T::AccountId, class: ClassOf) -> Result { + let votes = + VotingFor::::try_mutate(&who, &class, |voting| -> Result { + match sp_std::mem::replace(voting, Voting::default()) { + Voting::Delegating(Delegating { + balance, + target, + conviction, + delegations, + mut prior, + }) => { + // remove any delegation votes to our current target. + let votes = Self::reduce_upstream_delegation( + &target, + &class, + conviction.votes(balance), + ); + let now = frame_system::Pallet::::block_number(); + let lock_periods = conviction.lock_periods().into(); + prior.accumulate( + now.saturating_add( + T::VoteLockingPeriod::get().saturating_mul(lock_periods), + ), + balance, + ); + voting.set_common(delegations, prior); + + Ok(votes) + }, + Voting::Casting(_) => Err(Error::::NotDelegating.into()), + } + })?; + Self::deposit_event(Event::::Undelegated(who)); + Ok(votes) + } + + fn extend_lock(who: &T::AccountId, class: &ClassOf, amount: BalanceOf) { + ClassLocksFor::::mutate(who, |locks| { + match locks.iter().position(|x| &x.0 == class) { + Some(i) => locks[i].1 = locks[i].1.max(amount), + None => { + let ok = locks.try_push((class.clone(), amount)).is_ok(); + debug_assert!( + ok, + "Vec bounded by number of classes; \ + all items in Vec associated with a unique class; \ + qed" + ); + }, + } + }); + T::Currency::extend_lock(CONVICTION_VOTING_ID, who, amount, WithdrawReasons::TRANSFER); + } + + /// Rejig the lock on an account. It will never get more stringent (since that would indicate + /// a security hole) but may be reduced from what they are currently. + fn update_lock(class: &ClassOf, who: &T::AccountId) { + let class_lock_needed = VotingFor::::mutate(who, class, |voting| { + voting.rejig(frame_system::Pallet::::block_number()); + voting.locked_balance() + }); + let lock_needed = ClassLocksFor::::mutate(who, |locks| { + locks.retain(|x| &x.0 != class); + if !class_lock_needed.is_zero() { + let ok = locks.try_push((class.clone(), class_lock_needed)).is_ok(); + debug_assert!( + ok, + "Vec bounded by number of classes; \ + all items in Vec associated with a unique class; \ + qed" + ); + } + locks.iter().map(|x| x.1).max().unwrap_or(Zero::zero()) + }); + if lock_needed.is_zero() { + T::Currency::remove_lock(CONVICTION_VOTING_ID, who); + } else { + T::Currency::set_lock( + CONVICTION_VOTING_ID, + who, + lock_needed, + WithdrawReasons::TRANSFER, + ); + } + } +} diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs new file mode 100644 index 0000000000000..d20dcb66d6198 --- /dev/null +++ b/frame/conviction-voting/src/tests.rs @@ -0,0 +1,888 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! The crate's tests. + +use std::collections::BTreeMap; + +use frame_support::{ + assert_noop, assert_ok, parameter_types, + traits::{ConstU32, ConstU64, Contains, Polling, VoteTally}, + weights::Weight, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +use super::*; +use crate as pallet_conviction_voting; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Voting: pallet_conviction_voting::{Pallet, Call, Storage, Event}, + } +); + +// Test that a fitlered call can be dispatched. +pub struct BaseFilter; +impl Contains for BaseFilter { + fn contains(call: &RuntimeCall) -> bool { + !matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. })) + } +} + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(1_000_000)); +} +impl frame_system::Config for Test { + type BaseCallFilter = BaseFilter; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type MaxLocks = ConstU32<10>; + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum TestPollState { + Ongoing(TallyOf, u8), + Completed(u64, bool), +} +use TestPollState::*; + +parameter_types! { + pub static Polls: BTreeMap = vec![ + (1, Completed(1, true)), + (2, Completed(2, false)), + (3, Ongoing(Tally::from_parts(0, 0, 0), 0)), + ].into_iter().collect(); +} + +pub struct TestPolls; +impl Polling> for TestPolls { + type Index = u8; + type Votes = u64; + type Moment = u64; + type Class = u8; + fn classes() -> Vec { + vec![0, 1, 2] + } + fn as_ongoing(index: u8) -> Option<(TallyOf, Self::Class)> { + Polls::get().remove(&index).and_then(|x| { + if let TestPollState::Ongoing(t, c) = x { + Some((t, c)) + } else { + None + } + }) + } + fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut TallyOf, u64, u8>) -> R, + ) -> R { + let mut polls = Polls::get(); + let entry = polls.get_mut(&index); + let r = match entry { + Some(Ongoing(ref mut tally_mut_ref, class)) => + f(PollStatus::Ongoing(tally_mut_ref, *class)), + Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), + None => f(PollStatus::None), + }; + Polls::set(polls); + r + } + fn try_access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut TallyOf, u64, u8>) -> Result, + ) -> Result { + let mut polls = Polls::get(); + let entry = polls.get_mut(&index); + let r = match entry { + Some(Ongoing(ref mut tally_mut_ref, class)) => + f(PollStatus::Ongoing(tally_mut_ref, *class)), + Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), + None => f(PollStatus::None), + }?; + Polls::set(polls); + Ok(r) + } + + #[cfg(feature = "runtime-benchmarks")] + fn create_ongoing(class: Self::Class) -> Result { + let mut polls = Polls::get(); + let i = polls.keys().rev().next().map_or(0, |x| x + 1); + polls.insert(i, Ongoing(Tally::new(0), class)); + Polls::set(polls); + Ok(i) + } + + #[cfg(feature = "runtime-benchmarks")] + fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> { + let mut polls = Polls::get(); + match polls.get(&index) { + Some(Ongoing(..)) => {}, + _ => return Err(()), + } + let now = frame_system::Pallet::::block_number(); + polls.insert(index, Completed(now, approved)); + Polls::set(polls); + Ok(()) + } +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = pallet_balances::Pallet; + type VoteLockingPeriod = ConstU64<3>; + type MaxVotes = ConstU32<3>; + type WeightInfo = (); + type MaxTurnout = frame_support::traits::TotalIssuanceOf; + type Polls = TestPolls; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +#[test] +fn params_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_eq!(Balances::total_issuance(), 210); + }); +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); +} + +#[allow(dead_code)] +fn run_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +fn aye(amount: u64, conviction: u8) -> AccountVote { + let vote = Vote { aye: true, conviction: conviction.try_into().unwrap() }; + AccountVote::Standard { vote, balance: amount } +} + +fn nay(amount: u64, conviction: u8) -> AccountVote { + let vote = Vote { aye: false, conviction: conviction.try_into().unwrap() }; + AccountVote::Standard { vote, balance: amount } +} + +fn split(aye: u64, nay: u64) -> AccountVote { + AccountVote::Split { aye, nay } +} + +fn split_abstain(aye: u64, nay: u64, abstain: u64) -> AccountVote { + AccountVote::SplitAbstain { aye, nay, abstain } +} + +fn tally(index: u8) -> TallyOf { + >>::as_ongoing(index).expect("No poll").0 +} + +fn class(index: u8) -> u8 { + >>::as_ongoing(index).expect("No poll").1 +} + +#[test] +#[ignore] +#[should_panic(expected = "No poll")] +fn unknown_poll_should_panic() { + let _ = tally(0); +} + +#[test] +#[ignore] +#[should_panic(expected = "No poll")] +fn completed_poll_should_panic() { + let _ = tally(1); +} + +#[test] +fn basic_stuff() { + new_test_ext().execute_with(|| { + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + }); +} + +#[test] +fn basic_voting_works() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(2, 5))); + assert_eq!(tally(3), Tally::from_parts(10, 0, 2)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, nay(2, 5))); + assert_eq!(tally(3), Tally::from_parts(0, 10, 0)); + assert_eq!(Balances::usable_balance(1), 8); + + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(5, 1))); + assert_eq!(tally(3), Tally::from_parts(5, 0, 5)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, nay(5, 1))); + assert_eq!(tally(3), Tally::from_parts(0, 5, 0)); + assert_eq!(Balances::usable_balance(1), 5); + + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(10, 0))); + assert_eq!(tally(3), Tally::from_parts(1, 0, 10)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, nay(10, 0))); + assert_eq!(tally(3), Tally::from_parts(0, 1, 0)); + assert_eq!(Balances::usable_balance(1), 0); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), None, 3)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), class(3), 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn split_voting_works() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, split(10, 0))); + assert_eq!(tally(3), Tally::from_parts(1, 0, 10)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, split(5, 5))); + assert_eq!(tally(3), Tally::from_parts(0, 0, 5)); + assert_eq!(Balances::usable_balance(1), 0); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), None, 3)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), class(3), 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn abstain_voting_works() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, split_abstain(0, 0, 10))); + assert_eq!(tally(3), Tally::from_parts(0, 0, 10)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 3, split_abstain(0, 0, 20))); + assert_eq!(tally(3), Tally::from_parts(0, 0, 30)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 3, split_abstain(10, 0, 10))); + assert_eq!(tally(3), Tally::from_parts(1, 0, 30)); + assert_eq!(Balances::usable_balance(1), 0); + assert_eq!(Balances::usable_balance(2), 0); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), None, 3)); + assert_eq!(tally(3), Tally::from_parts(1, 0, 20)); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(2), None, 3)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), class(3), 1)); + assert_eq!(Balances::usable_balance(1), 10); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(2), class(3), 2)); + assert_eq!(Balances::usable_balance(2), 20); + }); +} + +#[test] +fn voting_balance_gets_locked() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(2, 5))); + assert_eq!(tally(3), Tally::from_parts(10, 0, 2)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, nay(2, 5))); + assert_eq!(tally(3), Tally::from_parts(0, 10, 0)); + assert_eq!(Balances::usable_balance(1), 8); + + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(5, 1))); + assert_eq!(tally(3), Tally::from_parts(5, 0, 5)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, nay(5, 1))); + assert_eq!(tally(3), Tally::from_parts(0, 5, 0)); + assert_eq!(Balances::usable_balance(1), 5); + + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(10, 0))); + assert_eq!(tally(3), Tally::from_parts(1, 0, 10)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, nay(10, 0))); + assert_eq!(tally(3), Tally::from_parts(0, 1, 0)); + assert_eq!(Balances::usable_balance(1), 0); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), None, 3)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), class(3), 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn successful_but_zero_conviction_vote_balance_can_be_unlocked() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(1, 1))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 3, nay(20, 0))); + let c = class(3); + Polls::set(vec![(3, Completed(3, false))].into_iter().collect()); + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(2), Some(c), 3)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(2), c, 2)); + assert_eq!(Balances::usable_balance(2), 20); + }); +} + +#[test] +fn unsuccessful_conviction_vote_balance_can_be_unlocked() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(1, 1))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 3, nay(20, 0))); + let c = class(3); + Polls::set(vec![(3, Completed(3, false))].into_iter().collect()); + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), Some(c), 3)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), c, 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn successful_conviction_vote_balance_stays_locked_for_correct_time() { + new_test_ext().execute_with(|| { + for i in 1..=5 { + assert_ok!(Voting::vote(RuntimeOrigin::signed(i), 3, aye(10, i as u8))); + } + let c = class(3); + Polls::set(vec![(3, Completed(3, true))].into_iter().collect()); + for i in 1..=5 { + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(i), Some(c), 3)); + } + for block in 1..=(3 + 5 * 3) { + run_to(block); + for i in 1..=5 { + assert_ok!(Voting::unlock(RuntimeOrigin::signed(i), c, i)); + let expired = block >= (3 << (i - 1)) + 3; + assert_eq!(Balances::usable_balance(i), i * 10 - if expired { 0 } else { 10 }); + } + } + }); +} + +#[test] +fn classwise_delegation_works() { + new_test_ext().execute_with(|| { + Polls::set( + vec![ + (0, Ongoing(Tally::new(0), 0)), + (1, Ongoing(Tally::new(0), 1)), + (2, Ongoing(Tally::new(0), 2)), + (3, Ongoing(Tally::new(0), 2)), + ] + .into_iter() + .collect(), + ); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked1x, 5)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 1, 3, Conviction::Locked1x, 5)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 2, 4, Conviction::Locked1x, 5)); + assert_eq!(Balances::usable_balance(1), 5); + + assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 0, aye(10, 0))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 1, nay(10, 0))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 2, nay(10, 0))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(3), 0, nay(10, 0))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(3), 1, aye(10, 0))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(3), 2, nay(10, 0))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(4), 0, nay(10, 0))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(4), 1, nay(10, 0))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(4), 2, aye(10, 0))); + // 4 hasn't voted yet + + assert_eq!( + Polls::get(), + vec![ + (0, Ongoing(Tally::from_parts(6, 2, 15), 0)), + (1, Ongoing(Tally::from_parts(6, 2, 15), 1)), + (2, Ongoing(Tally::from_parts(6, 2, 15), 2)), + (3, Ongoing(Tally::from_parts(0, 0, 0), 2)), + ] + .into_iter() + .collect() + ); + + // 4 votes nay to 3. + assert_ok!(Voting::vote(RuntimeOrigin::signed(4), 3, nay(10, 0))); + assert_eq!( + Polls::get(), + vec![ + (0, Ongoing(Tally::from_parts(6, 2, 15), 0)), + (1, Ongoing(Tally::from_parts(6, 2, 15), 1)), + (2, Ongoing(Tally::from_parts(6, 2, 15), 2)), + (3, Ongoing(Tally::from_parts(0, 6, 0), 2)), + ] + .into_iter() + .collect() + ); + + // Redelegate for class 2 to account 3. + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 2)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 2, 3, Conviction::Locked1x, 5)); + assert_eq!( + Polls::get(), + vec![ + (0, Ongoing(Tally::from_parts(6, 2, 15), 0)), + (1, Ongoing(Tally::from_parts(6, 2, 15), 1)), + (2, Ongoing(Tally::from_parts(1, 7, 10), 2)), + (3, Ongoing(Tally::from_parts(0, 1, 0), 2)), + ] + .into_iter() + .collect() + ); + + // Redelegating with a lower lock does not forget previous lock and updates correctly. + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 1)); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 2)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked1x, 3)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 1, 3, Conviction::Locked1x, 3)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 2, 4, Conviction::Locked1x, 3)); + assert_eq!( + Polls::get(), + vec![ + (0, Ongoing(Tally::from_parts(4, 2, 13), 0)), + (1, Ongoing(Tally::from_parts(4, 2, 13), 1)), + (2, Ongoing(Tally::from_parts(4, 2, 13), 2)), + (3, Ongoing(Tally::from_parts(0, 4, 0), 2)), + ] + .into_iter() + .collect() + ); + assert_eq!(Balances::usable_balance(1), 5); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 1, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 2, 1)); + // unlock does nothing since the delegation already took place. + assert_eq!(Balances::usable_balance(1), 5); + + // Redelegating with higher amount extends previous lock. + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked1x, 6)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 4); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 1)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 1, 3, Conviction::Locked1x, 7)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 1, 1)); + assert_eq!(Balances::usable_balance(1), 3); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 2)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 2, 4, Conviction::Locked1x, 8)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 2, 1)); + assert_eq!(Balances::usable_balance(1), 2); + assert_eq!( + Polls::get(), + vec![ + (0, Ongoing(Tally::from_parts(7, 2, 16), 0)), + (1, Ongoing(Tally::from_parts(8, 2, 17), 1)), + (2, Ongoing(Tally::from_parts(9, 2, 18), 2)), + (3, Ongoing(Tally::from_parts(0, 9, 0), 2)), + ] + .into_iter() + .collect() + ); + }); +} + +#[test] +fn redelegation_after_vote_ending_should_keep_lock() { + new_test_ext().execute_with(|| { + Polls::set(vec![(0, Ongoing(Tally::new(0), 0))].into_iter().collect()); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked1x, 5)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 0, aye(10, 1))); + Polls::set(vec![(0, Completed(1, true))].into_iter().collect()); + assert_eq!(Balances::usable_balance(1), 5); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 3, Conviction::Locked1x, 3)); + assert_eq!(Balances::usable_balance(1), 5); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 5); + }); +} + +#[test] +fn lock_amalgamation_valid_with_multiple_removed_votes() { + new_test_ext().execute_with(|| { + Polls::set( + vec![ + (0, Ongoing(Tally::new(0), 0)), + (1, Ongoing(Tally::new(0), 0)), + (2, Ongoing(Tally::new(0), 0)), + ] + .into_iter() + .collect(), + ); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 0, aye(5, 1))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 1, aye(10, 1))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 2, aye(5, 2))); + assert_eq!(Balances::usable_balance(1), 0); + + Polls::set( + vec![(0, Completed(1, true)), (1, Completed(1, true)), (2, Completed(1, true))] + .into_iter() + .collect(), + ); + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), Some(0), 0)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), Some(0), 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), Some(0), 2)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + run_to(3); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + run_to(6); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert!(Balances::usable_balance(1) <= 5); + + run_to(7); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn lock_amalgamation_valid_with_multiple_delegations() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked1x, 5)); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked1x, 10)); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked2x, 5)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 0); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + + run_to(3); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + run_to(6); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert!(Balances::usable_balance(1) <= 5); + + run_to(7); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn lock_amalgamation_valid_with_move_roundtrip_to_delegation() { + new_test_ext().execute_with(|| { + Polls::set(vec![(0, Ongoing(Tally::new(0), 0))].into_iter().collect()); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 0, aye(5, 1))); + Polls::set(vec![(0, Completed(1, true))].into_iter().collect()); + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), Some(0), 0)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 5); + + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked1x, 10)); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + Polls::set(vec![(1, Ongoing(Tally::new(0), 0))].into_iter().collect()); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 1, aye(5, 2))); + Polls::set(vec![(1, Completed(1, true))].into_iter().collect()); + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), Some(0), 1)); + + run_to(3); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + run_to(6); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert!(Balances::usable_balance(1) <= 5); + + run_to(7); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn lock_amalgamation_valid_with_move_roundtrip_to_casting() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked1x, 5)); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 5); + + Polls::set(vec![(0, Ongoing(Tally::new(0), 0))].into_iter().collect()); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 0, aye(10, 1))); + Polls::set(vec![(0, Completed(1, true))].into_iter().collect()); + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), Some(0), 0)); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked2x, 10)); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + + run_to(3); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + run_to(6); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert!(Balances::usable_balance(1) <= 5); + + run_to(7); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn lock_aggregation_over_different_classes_with_delegation_works() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::Locked1x, 5)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 1, 2, Conviction::Locked2x, 5)); + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 2, 2, Conviction::Locked1x, 10)); + + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 1)); + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 2)); + + run_to(3); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 1, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 2, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + run_to(6); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 1, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 2, 1)); + assert_eq!(Balances::usable_balance(1), 5); + + run_to(7); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 1, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 2, 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn lock_aggregation_over_different_classes_with_casting_works() { + new_test_ext().execute_with(|| { + Polls::set( + vec![ + (0, Ongoing(Tally::new(0), 0)), + (1, Ongoing(Tally::new(0), 1)), + (2, Ongoing(Tally::new(0), 2)), + ] + .into_iter() + .collect(), + ); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 0, aye(5, 1))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 1, aye(10, 1))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 2, aye(5, 2))); + Polls::set( + vec![(0, Completed(1, true)), (1, Completed(1, true)), (2, Completed(1, true))] + .into_iter() + .collect(), + ); + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), Some(0), 0)); + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), Some(1), 1)); + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), Some(2), 2)); + + run_to(3); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 1, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 2, 1)); + assert_eq!(Balances::usable_balance(1), 0); + + run_to(6); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 1, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 2, 1)); + assert_eq!(Balances::usable_balance(1), 5); + + run_to(7); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 1, 1)); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), 2, 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn errors_with_vote_work() { + new_test_ext().execute_with(|| { + assert_noop!( + Voting::vote(RuntimeOrigin::signed(1), 0, aye(10, 0)), + Error::::NotOngoing + ); + assert_noop!( + Voting::vote(RuntimeOrigin::signed(1), 1, aye(10, 0)), + Error::::NotOngoing + ); + assert_noop!( + Voting::vote(RuntimeOrigin::signed(1), 2, aye(10, 0)), + Error::::NotOngoing + ); + assert_noop!( + Voting::vote(RuntimeOrigin::signed(1), 3, aye(11, 0)), + Error::::InsufficientFunds + ); + + assert_ok!(Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::None, 10)); + assert_noop!( + Voting::vote(RuntimeOrigin::signed(1), 3, aye(10, 0)), + Error::::AlreadyDelegating + ); + + assert_ok!(Voting::undelegate(RuntimeOrigin::signed(1), 0)); + Polls::set( + vec![ + (0, Ongoing(Tally::new(0), 0)), + (1, Ongoing(Tally::new(0), 0)), + (2, Ongoing(Tally::new(0), 0)), + (3, Ongoing(Tally::new(0), 0)), + ] + .into_iter() + .collect(), + ); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 0, aye(10, 0))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 1, aye(10, 0))); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 2, aye(10, 0))); + assert_noop!( + Voting::vote(RuntimeOrigin::signed(1), 3, aye(10, 0)), + Error::::MaxVotesReached + ); + }); +} + +#[test] +fn errors_with_delegating_work() { + new_test_ext().execute_with(|| { + assert_noop!( + Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::None, 11), + Error::::InsufficientFunds + ); + assert_noop!( + Voting::delegate(RuntimeOrigin::signed(1), 3, 2, Conviction::None, 10), + Error::::BadClass + ); + + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(10, 0))); + assert_noop!( + Voting::delegate(RuntimeOrigin::signed(1), 0, 2, Conviction::None, 10), + Error::::AlreadyVoting + ); + + assert_noop!(Voting::undelegate(RuntimeOrigin::signed(1), 0), Error::::NotDelegating); + }); +} + +#[test] +fn remove_other_vote_works() { + new_test_ext().execute_with(|| { + assert_noop!( + Voting::remove_other_vote(RuntimeOrigin::signed(2), 1, 0, 3), + Error::::NotVoter + ); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(10, 2))); + assert_noop!( + Voting::remove_other_vote(RuntimeOrigin::signed(2), 1, 0, 3), + Error::::NoPermission + ); + Polls::set(vec![(3, Completed(1, true))].into_iter().collect()); + run_to(6); + assert_noop!( + Voting::remove_other_vote(RuntimeOrigin::signed(2), 1, 0, 3), + Error::::NoPermissionYet + ); + run_to(7); + assert_ok!(Voting::remove_other_vote(RuntimeOrigin::signed(2), 1, 0, 3)); + }); +} + +#[test] +fn errors_with_remove_vote_work() { + new_test_ext().execute_with(|| { + assert_noop!( + Voting::remove_vote(RuntimeOrigin::signed(1), Some(0), 3), + Error::::NotVoter + ); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(10, 2))); + Polls::set(vec![(3, Completed(1, true))].into_iter().collect()); + assert_noop!( + Voting::remove_vote(RuntimeOrigin::signed(1), None, 3), + Error::::ClassNeeded + ); + }); +} diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs new file mode 100644 index 0000000000000..aa2406eef7110 --- /dev/null +++ b/frame/conviction-voting/src/types.rs @@ -0,0 +1,264 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Miscellaneous additional datatypes. + +use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use frame_support::{ + traits::VoteTally, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Saturating, Zero}, + RuntimeDebug, +}; +use sp_std::{fmt::Debug, marker::PhantomData}; + +use super::*; +use crate::{AccountVote, Conviction, Vote}; + +/// Info regarding an ongoing referendum. +#[derive( + CloneNoBound, + PartialEqNoBound, + EqNoBound, + RuntimeDebugNoBound, + TypeInfo, + Encode, + Decode, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(Total))] +#[codec(mel_bound(Votes: MaxEncodedLen))] +pub struct Tally { + /// The number of aye votes, expressed in terms of post-conviction lock-vote. + pub ayes: Votes, + /// The number of nay votes, expressed in terms of post-conviction lock-vote. + pub nays: Votes, + /// The basic number of aye votes, expressed pre-conviction. + pub support: Votes, + /// Dummy. + dummy: PhantomData, +} + +impl< + Votes: Clone + Default + PartialEq + Eq + Debug + Copy + AtLeast32BitUnsigned + TypeInfo + Codec, + Total: Get, + Class, + > VoteTally for Tally +{ + fn new(_: Class) -> Self { + Self { ayes: Zero::zero(), nays: Zero::zero(), support: Zero::zero(), dummy: PhantomData } + } + + fn ayes(&self, _: Class) -> Votes { + self.ayes + } + + fn support(&self, _: Class) -> Perbill { + Perbill::from_rational(self.support, Total::get()) + } + + fn approval(&self, _: Class) -> Perbill { + Perbill::from_rational(self.ayes, self.ayes.saturating_add(self.nays)) + } + + #[cfg(feature = "runtime-benchmarks")] + fn unanimity(_: Class) -> Self { + Self { ayes: Total::get(), nays: Zero::zero(), support: Total::get(), dummy: PhantomData } + } + + #[cfg(feature = "runtime-benchmarks")] + fn rejection(_: Class) -> Self { + Self { ayes: Zero::zero(), nays: Total::get(), support: Total::get(), dummy: PhantomData } + } + + #[cfg(feature = "runtime-benchmarks")] + fn from_requirements(support: Perbill, approval: Perbill, _: Class) -> Self { + let support = support.mul_ceil(Total::get()); + let ayes = approval.mul_ceil(support); + Self { ayes, nays: support - ayes, support, dummy: PhantomData } + } + + #[cfg(feature = "runtime-benchmarks")] + fn setup(_: Class, _: Perbill) {} +} + +impl< + Votes: Clone + Default + PartialEq + Eq + Debug + Copy + AtLeast32BitUnsigned + TypeInfo + Codec, + Total: Get, + > Tally +{ + /// Create a new tally. + pub fn from_vote(vote: Vote, balance: Votes) -> Self { + let Delegations { votes, capital } = vote.conviction.votes(balance); + Self { + ayes: if vote.aye { votes } else { Zero::zero() }, + nays: if vote.aye { Zero::zero() } else { votes }, + support: capital, + dummy: PhantomData, + } + } + + pub fn from_parts( + ayes_with_conviction: Votes, + nays_with_conviction: Votes, + ayes: Votes, + ) -> Self { + Self { + ayes: ayes_with_conviction, + nays: nays_with_conviction, + support: ayes, + dummy: PhantomData, + } + } + + /// Add an account's vote into the tally. + pub fn add(&mut self, vote: AccountVote) -> Option<()> { + match vote { + AccountVote::Standard { vote, balance } => { + let Delegations { votes, capital } = vote.conviction.votes(balance); + match vote.aye { + true => { + self.support = self.support.checked_add(&capital)?; + self.ayes = self.ayes.checked_add(&votes)? + }, + false => self.nays = self.nays.checked_add(&votes)?, + } + }, + AccountVote::Split { aye, nay } => { + let aye = Conviction::None.votes(aye); + let nay = Conviction::None.votes(nay); + self.support = self.support.checked_add(&aye.capital)?; + self.ayes = self.ayes.checked_add(&aye.votes)?; + self.nays = self.nays.checked_add(&nay.votes)?; + }, + AccountVote::SplitAbstain { aye, nay, abstain } => { + let aye = Conviction::None.votes(aye); + let nay = Conviction::None.votes(nay); + let abstain = Conviction::None.votes(abstain); + self.support = + self.support.checked_add(&aye.capital)?.checked_add(&abstain.capital)?; + self.ayes = self.ayes.checked_add(&aye.votes)?; + self.nays = self.nays.checked_add(&nay.votes)?; + }, + } + Some(()) + } + + /// Remove an account's vote from the tally. + pub fn remove(&mut self, vote: AccountVote) -> Option<()> { + match vote { + AccountVote::Standard { vote, balance } => { + let Delegations { votes, capital } = vote.conviction.votes(balance); + match vote.aye { + true => { + self.support = self.support.checked_sub(&capital)?; + self.ayes = self.ayes.checked_sub(&votes)? + }, + false => self.nays = self.nays.checked_sub(&votes)?, + } + }, + AccountVote::Split { aye, nay } => { + let aye = Conviction::None.votes(aye); + let nay = Conviction::None.votes(nay); + self.support = self.support.checked_sub(&aye.capital)?; + self.ayes = self.ayes.checked_sub(&aye.votes)?; + self.nays = self.nays.checked_sub(&nay.votes)?; + }, + AccountVote::SplitAbstain { aye, nay, abstain } => { + let aye = Conviction::None.votes(aye); + let nay = Conviction::None.votes(nay); + let abstain = Conviction::None.votes(abstain); + self.support = + self.support.checked_sub(&aye.capital)?.checked_sub(&abstain.capital)?; + self.ayes = self.ayes.checked_sub(&aye.votes)?; + self.nays = self.nays.checked_sub(&nay.votes)?; + }, + } + Some(()) + } + + /// Increment some amount of votes. + pub fn increase(&mut self, approve: bool, delegations: Delegations) { + match approve { + true => { + self.support = self.support.saturating_add(delegations.capital); + self.ayes = self.ayes.saturating_add(delegations.votes); + }, + false => self.nays = self.nays.saturating_add(delegations.votes), + } + } + + /// Decrement some amount of votes. + pub fn reduce(&mut self, approve: bool, delegations: Delegations) { + match approve { + true => { + self.support = self.support.saturating_sub(delegations.capital); + self.ayes = self.ayes.saturating_sub(delegations.votes); + }, + false => self.nays = self.nays.saturating_sub(delegations.votes), + } + } +} + +/// Amount of votes and capital placed in delegation for an account. +#[derive( + Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] +pub struct Delegations { + /// The number of votes (this is post-conviction). + pub votes: Balance, + /// The amount of raw capital, used for the support. + pub capital: Balance, +} + +impl Saturating for Delegations { + fn saturating_add(self, o: Self) -> Self { + Self { + votes: self.votes.saturating_add(o.votes), + capital: self.capital.saturating_add(o.capital), + } + } + + fn saturating_sub(self, o: Self) -> Self { + Self { + votes: self.votes.saturating_sub(o.votes), + capital: self.capital.saturating_sub(o.capital), + } + } + + fn saturating_mul(self, o: Self) -> Self { + Self { + votes: self.votes.saturating_mul(o.votes), + capital: self.capital.saturating_mul(o.capital), + } + } + + fn saturating_pow(self, exp: usize) -> Self { + Self { votes: self.votes.saturating_pow(exp), capital: self.capital.saturating_pow(exp) } + } +} + +/// Whether an `unvote` operation is able to make actions that are not strictly always in the +/// interest of an account. +pub enum UnvoteScope { + /// Permitted to do everything. + Any, + /// Permitted to do only the changes that do not need the owner's permission. + OnlyExpired, +} diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs new file mode 100644 index 0000000000000..1fed70536d246 --- /dev/null +++ b/frame/conviction-voting/src/vote.rs @@ -0,0 +1,265 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! The vote datatype. + +use crate::{Conviction, Delegations}; +use codec::{Decode, Encode, EncodeLike, Input, MaxEncodedLen, Output}; +use frame_support::{pallet_prelude::Get, BoundedVec}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Saturating, Zero}, + RuntimeDebug, +}; +use sp_std::prelude::*; + +/// A number of lock periods, plus a vote, one way or the other. +#[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug, MaxEncodedLen)] +pub struct Vote { + pub aye: bool, + pub conviction: Conviction, +} + +impl Encode for Vote { + fn encode_to(&self, output: &mut T) { + output.push_byte(u8::from(self.conviction) | if self.aye { 0b1000_0000 } else { 0 }); + } +} + +impl EncodeLike for Vote {} + +impl Decode for Vote { + fn decode(input: &mut I) -> Result { + let b = input.read_byte()?; + Ok(Vote { + aye: (b & 0b1000_0000) == 0b1000_0000, + conviction: Conviction::try_from(b & 0b0111_1111) + .map_err(|_| codec::Error::from("Invalid conviction"))?, + }) + } +} + +impl TypeInfo for Vote { + type Identity = Self; + + fn type_info() -> scale_info::Type { + scale_info::Type::builder() + .path(scale_info::Path::new("Vote", module_path!())) + .composite( + scale_info::build::Fields::unnamed() + .field(|f| f.ty::().docs(&["Raw vote byte, encodes aye + conviction"])), + ) + } +} + +/// A vote for a referendum of a particular account. +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum AccountVote { + /// A standard vote, one-way (approve or reject) with a given amount of conviction. + Standard { vote: Vote, balance: Balance }, + /// A split vote with balances given for both ways, and with no conviction, useful for + /// parachains when voting. + Split { aye: Balance, nay: Balance }, + /// A split vote with balances given for both ways as well as abstentions, and with no + /// conviction, useful for parachains when voting, other off-chain aggregate accounts and + /// individuals who wish to abstain. + SplitAbstain { aye: Balance, nay: Balance, abstain: Balance }, +} + +impl AccountVote { + /// Returns `Some` of the lock periods that the account is locked for, assuming that the + /// referendum passed iff `approved` is `true`. + pub fn locked_if(self, approved: bool) -> Option<(u32, Balance)> { + // winning side: can only be removed after the lock period ends. + match self { + AccountVote::Standard { vote: Vote { conviction: Conviction::None, .. }, .. } => None, + AccountVote::Standard { vote, balance } if vote.aye == approved => + Some((vote.conviction.lock_periods(), balance)), + _ => None, + } + } + + /// The total balance involved in this vote. + pub fn balance(self) -> Balance { + match self { + AccountVote::Standard { balance, .. } => balance, + AccountVote::Split { aye, nay } => aye.saturating_add(nay), + AccountVote::SplitAbstain { aye, nay, abstain } => + aye.saturating_add(nay).saturating_add(abstain), + } + } + + /// Returns `Some` with whether the vote is an aye vote if it is standard, otherwise `None` if + /// it is split. + pub fn as_standard(self) -> Option { + match self { + AccountVote::Standard { vote, .. } => Some(vote.aye), + _ => None, + } + } +} + +/// A "prior" lock, i.e. a lock for some now-forgotten reason. +#[derive( + Encode, + Decode, + Default, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + RuntimeDebug, + TypeInfo, + MaxEncodedLen, +)] +pub struct PriorLock(BlockNumber, Balance); + +impl PriorLock { + /// Accumulates an additional lock. + pub fn accumulate(&mut self, until: BlockNumber, amount: Balance) { + self.0 = self.0.max(until); + self.1 = self.1.max(amount); + } + + pub fn locked(&self) -> Balance { + self.1 + } + + pub fn rejig(&mut self, now: BlockNumber) { + if now >= self.0 { + self.0 = Zero::zero(); + self.1 = Zero::zero(); + } + } +} + +/// Information concerning the delegation of some voting power. +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct Delegating { + /// The amount of balance delegated. + pub balance: Balance, + /// The account to which the voting power is delegated. + pub target: AccountId, + /// The conviction with which the voting power is delegated. When this gets undelegated, the + /// relevant lock begins. + pub conviction: Conviction, + /// The total amount of delegations that this account has received, post-conviction-weighting. + pub delegations: Delegations, + /// Any pre-existing locks from past voting/delegating activity. + pub prior: PriorLock, +} + +/// Information concerning the direct vote-casting of some voting power. +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxVotes))] +#[codec(mel_bound(Balance: MaxEncodedLen, BlockNumber: MaxEncodedLen, PollIndex: MaxEncodedLen))] +pub struct Casting +where + MaxVotes: Get, +{ + /// The current votes of the account. + pub votes: BoundedVec<(PollIndex, AccountVote), MaxVotes>, + /// The total amount of delegations that this account has received, post-conviction-weighting. + pub delegations: Delegations, + /// Any pre-existing locks from past voting/delegating activity. + pub prior: PriorLock, +} + +/// An indicator for what an account is doing; it can either be delegating or voting. +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxVotes))] +#[codec(mel_bound( + Balance: MaxEncodedLen, AccountId: MaxEncodedLen, BlockNumber: MaxEncodedLen, + PollIndex: MaxEncodedLen, +))] +pub enum Voting +where + MaxVotes: Get, +{ + /// The account is voting directly. + Casting(Casting), + /// The account is delegating `balance` of its balance to a `target` account with `conviction`. + Delegating(Delegating), +} + +impl Default + for Voting +where + MaxVotes: Get, +{ + fn default() -> Self { + Voting::Casting(Casting { + votes: Default::default(), + delegations: Default::default(), + prior: PriorLock(Zero::zero(), Default::default()), + }) + } +} + +impl AsMut> + for Voting +where + MaxVotes: Get, +{ + fn as_mut(&mut self) -> &mut PriorLock { + match self { + Voting::Casting(Casting { prior, .. }) => prior, + Voting::Delegating(Delegating { prior, .. }) => prior, + } + } +} + +impl< + Balance: Saturating + Ord + Zero + Copy, + BlockNumber: Ord + Copy + Zero, + AccountId, + PollIndex, + MaxVotes, + > Voting +where + MaxVotes: Get, +{ + pub fn rejig(&mut self, now: BlockNumber) { + AsMut::>::as_mut(self).rejig(now); + } + + /// The amount of this account's balance that must currently be locked due to voting. + pub fn locked_balance(&self) -> Balance { + match self { + Voting::Casting(Casting { votes, prior, .. }) => + votes.iter().map(|i| i.1.balance()).fold(prior.locked(), |a, i| a.max(i)), + Voting::Delegating(Delegating { balance, prior, .. }) => *balance.max(&prior.locked()), + } + } + + pub fn set_common( + &mut self, + delegations: Delegations, + prior: PriorLock, + ) { + let (d, p) = match self { + Voting::Casting(Casting { ref mut delegations, ref mut prior, .. }) => + (delegations, prior), + Voting::Delegating(Delegating { ref mut delegations, ref mut prior, .. }) => + (delegations, prior), + }; + *d = delegations; + *p = prior; + } +} diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs new file mode 100644 index 0000000000000..e50842449f88a --- /dev/null +++ b/frame/conviction-voting/src/weights.rs @@ -0,0 +1,222 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_conviction_voting +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_conviction_voting +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/conviction-voting/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_conviction_voting. +pub trait WeightInfo { + fn vote_new() -> Weight; + fn vote_existing() -> Weight; + fn remove_vote() -> Weight; + fn remove_other_vote() -> Weight; + fn delegate(r: u32, ) -> Weight; + fn undelegate(r: u32, ) -> Weight; + fn unlock() -> Weight; +} + +/// Weights for pallet_conviction_voting using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: ConvictionVoting VotingFor (r:1 w:1) + // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn vote_new() -> Weight { + // Minimum execution time: 131_633 nanoseconds. + Weight::from_ref_time(132_742_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: ConvictionVoting VotingFor (r:1 w:1) + // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn vote_existing() -> Weight { + // Minimum execution time: 176_240 nanoseconds. + Weight::from_ref_time(183_274_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: ConvictionVoting VotingFor (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn remove_vote() -> Weight { + // Minimum execution time: 158_880 nanoseconds. + Weight::from_ref_time(164_648_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: ConvictionVoting VotingFor (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:1 w:0) + fn remove_other_vote() -> Weight { + // Minimum execution time: 60_330 nanoseconds. + Weight::from_ref_time(61_588_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ConvictionVoting VotingFor (r:2 w:2) + // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + /// The range of component `r` is `[0, 1]`. + fn delegate(r: u32, ) -> Weight { + // Minimum execution time: 63_088 nanoseconds. + Weight::from_ref_time(67_803_536 as u64) + // Standard Error: 197_102 + .saturating_add(Weight::from_ref_time(31_557_563 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(r as u64))) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + } + // Storage: ConvictionVoting VotingFor (r:2 w:2) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + /// The range of component `r` is `[0, 1]`. + fn undelegate(r: u32, ) -> Weight { + // Minimum execution time: 45_150 nanoseconds. + Weight::from_ref_time(51_547_530 as u64) + // Standard Error: 771_127 + .saturating_add(Weight::from_ref_time(26_927_969 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(r as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + } + // Storage: ConvictionVoting VotingFor (r:1 w:1) + // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn unlock() -> Weight { + // Minimum execution time: 75_067 nanoseconds. + Weight::from_ref_time(76_888_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: ConvictionVoting VotingFor (r:1 w:1) + // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn vote_new() -> Weight { + // Minimum execution time: 131_633 nanoseconds. + Weight::from_ref_time(132_742_000 as u64) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: ConvictionVoting VotingFor (r:1 w:1) + // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn vote_existing() -> Weight { + // Minimum execution time: 176_240 nanoseconds. + Weight::from_ref_time(183_274_000 as u64) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: ConvictionVoting VotingFor (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn remove_vote() -> Weight { + // Minimum execution time: 158_880 nanoseconds. + Weight::from_ref_time(164_648_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: ConvictionVoting VotingFor (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:1 w:0) + fn remove_other_vote() -> Weight { + // Minimum execution time: 60_330 nanoseconds. + Weight::from_ref_time(61_588_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: ConvictionVoting VotingFor (r:2 w:2) + // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + /// The range of component `r` is `[0, 1]`. + fn delegate(r: u32, ) -> Weight { + // Minimum execution time: 63_088 nanoseconds. + Weight::from_ref_time(67_803_536 as u64) + // Standard Error: 197_102 + .saturating_add(Weight::from_ref_time(31_557_563 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(r as u64))) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + } + // Storage: ConvictionVoting VotingFor (r:2 w:2) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + /// The range of component `r` is `[0, 1]`. + fn undelegate(r: u32, ) -> Weight { + // Minimum execution time: 45_150 nanoseconds. + Weight::from_ref_time(51_547_530 as u64) + // Standard Error: 771_127 + .saturating_add(Weight::from_ref_time(26_927_969 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(r as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + } + // Storage: ConvictionVoting VotingFor (r:1 w:1) + // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + fn unlock() -> Weight { + // Minimum execution time: 75_067 nanoseconds. + Weight::from_ref_time(76_888_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } +} diff --git a/frame/democracy/Cargo.toml b/frame/democracy/Cargo.toml index 94719553e28aa..49dbe133d6919 100644 --- a/frame/democracy/Cargo.toml +++ b/frame/democracy/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-democracy" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for democracy" readme = "README.md" @@ -13,40 +13,43 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.126", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +log = { version = "0.4.17", default-features = false } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } +pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } [features] default = ["std"] std = [ - "serde", "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", "scale-info/std", - "sp-std/std", + "serde", "sp-io/std", - "frame-benchmarking/std", - "frame-support/std", "sp-runtime/std", - "frame-system/std", + "sp-std/std", + "sp-core/std", ] runtime-benchmarks = [ - "frame-benchmarking", - "frame-system/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] -try-runtime = ["frame-support/try-runtime"] +try-runtime = ["frame-support/try-runtime",] diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index 7d4d7aee140b9..424192e2521da 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,64 +19,54 @@ use super::*; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelist_account}; +use frame_benchmarking::{account, benchmarks, whitelist_account}; use frame_support::{ assert_noop, assert_ok, - traits::{ - schedule::DispatchTime, Currency, EnsureOrigin, Get, OnInitialize, UnfilteredDispatchable, - }, + traits::{Currency, EnsureOrigin, Get, OnInitialize, UnfilteredDispatchable}, }; -use frame_system::{Pallet as System, RawOrigin}; -use sp_runtime::traits::{BadOrigin, Bounded, One}; +use frame_system::RawOrigin; +use sp_core::H256; +use sp_runtime::{traits::Bounded, BoundedVec}; use crate::Pallet as Democracy; +const REFERENDUM_COUNT_HINT: u32 = 10; const SEED: u32 = 0; -const MAX_REFERENDUMS: u32 = 99; -const MAX_SECONDERS: u32 = 100; -const MAX_BYTES: u32 = 16_384; - -fn assert_last_event(generic_event: ::Event) { - frame_system::Pallet::::assert_last_event(generic_event.into()); -} fn funded_account(name: &'static str, index: u32) -> T::AccountId { let caller: T::AccountId = account(name, index, SEED); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + // Give the account half of the maximum value of the `Balance` type. + // Otherwise some transfers will fail with an overflow error. + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); caller } -fn add_proposal(n: u32) -> Result { +fn make_proposal(n: u32) -> BoundedCallOf { + let call: CallOf = frame_system::Call::remark { remark: n.encode() }.into(); + ::Preimages::bound(call).unwrap() +} + +fn add_proposal(n: u32) -> Result { let other = funded_account::("proposer", n); let value = T::MinimumDeposit::get(); - let proposal_hash: T::Hash = T::Hashing::hash_of(&n); - - Democracy::::propose(RawOrigin::Signed(other).into(), proposal_hash, value.into())?; - - Ok(proposal_hash) + let proposal = make_proposal::(n); + Democracy::::propose(RawOrigin::Signed(other).into(), proposal.clone(), value)?; + Ok(proposal.hash()) } -fn add_referendum(n: u32) -> Result { - let proposal_hash: T::Hash = T::Hashing::hash_of(&n); +fn add_referendum(n: u32) -> (ReferendumIndex, H256) { let vote_threshold = VoteThreshold::SimpleMajority; - - Democracy::::inject_referendum( - T::LaunchPeriod::get(), - proposal_hash, - vote_threshold, - 0u32.into(), - ); - let referendum_index: ReferendumIndex = ReferendumCount::::get() - 1; - T::Scheduler::schedule_named( - (DEMOCRACY_ID, referendum_index).encode(), - DispatchTime::At(1u32.into()), - None, - 63, - frame_system::RawOrigin::Root.into(), - Call::enact_proposal { proposal_hash, index: referendum_index }.into(), + let proposal = make_proposal::(n); + let hash = proposal.hash(); + ( + Democracy::::inject_referendum( + T::LaunchPeriod::get(), + proposal, + vote_threshold, + 0u32.into(), + ), + hash, ) - .map_err(|_| "failed to schedule named")?; - Ok(referendum_index) } fn account_vote(b: BalanceOf) -> AccountVote> { @@ -94,95 +84,90 @@ benchmarks! { } let caller = funded_account::("caller", 0); - let proposal_hash: T::Hash = T::Hashing::hash_of(&0); + let proposal = make_proposal::(0); let value = T::MinimumDeposit::get(); whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), proposal_hash, value.into()) + }: _(RawOrigin::Signed(caller), proposal, value) verify { assert_eq!(Democracy::::public_props().len(), p as usize, "Proposals not created."); } second { - let s in 0 .. MAX_SECONDERS; - let caller = funded_account::("caller", 0); - let proposal_hash = add_proposal::(s)?; + add_proposal::(0)?; // Create s existing "seconds" - for i in 0 .. s { + // we must reserve one deposit for the `proposal` and one for our benchmarked `second` call. + for i in 0 .. T::MaxDeposits::get() - 2 { let seconder = funded_account::("seconder", i); - Democracy::::second(RawOrigin::Signed(seconder).into(), 0, u32::MAX)?; + Democracy::::second(RawOrigin::Signed(seconder).into(), 0)?; } let deposits = Democracy::::deposit_of(0).ok_or("Proposal not created")?; - assert_eq!(deposits.0.len(), (s + 1) as usize, "Seconds not recorded"); + assert_eq!(deposits.0.len(), (T::MaxDeposits::get() - 1) as usize, "Seconds not recorded"); whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), 0, u32::MAX) + }: _(RawOrigin::Signed(caller), 0) verify { let deposits = Democracy::::deposit_of(0).ok_or("Proposal not created")?; - assert_eq!(deposits.0.len(), (s + 2) as usize, "`second` benchmark did not work"); + assert_eq!(deposits.0.len(), (T::MaxDeposits::get()) as usize, "`second` benchmark did not work"); } vote_new { - let r in 1 .. MAX_REFERENDUMS; - let caller = funded_account::("caller", 0); let account_vote = account_vote::(100u32.into()); // We need to create existing direct votes - for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + for i in 0 .. T::MaxVotes::get() - 1 { + let ref_index = add_referendum::(i).0; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_index, account_vote)?; } let votes = match VotingOf::::get(&caller) { Voting::Direct { votes, .. } => votes, _ => return Err("Votes are not direct".into()), }; - assert_eq!(votes.len(), r as usize, "Votes were not recorded."); + assert_eq!(votes.len(), (T::MaxVotes::get() - 1) as usize, "Votes were not recorded."); - let referendum_index = add_referendum::(r)?; + let ref_index = add_referendum::(T::MaxVotes::get() - 1).0; whitelist_account!(caller); - }: vote(RawOrigin::Signed(caller.clone()), referendum_index, account_vote) + }: vote(RawOrigin::Signed(caller.clone()), ref_index, account_vote) verify { let votes = match VotingOf::::get(&caller) { Voting::Direct { votes, .. } => votes, _ => return Err("Votes are not direct".into()), }; - assert_eq!(votes.len(), (r + 1) as usize, "Vote was not recorded."); + assert_eq!(votes.len(), T::MaxVotes::get() as usize, "Vote was not recorded."); } vote_existing { - let r in 1 .. MAX_REFERENDUMS; - let caller = funded_account::("caller", 0); let account_vote = account_vote::(100u32.into()); // We need to create existing direct votes - for i in 0 ..=r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + for i in 0..T::MaxVotes::get() { + let ref_index = add_referendum::(i).0; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_index, account_vote)?; } let votes = match VotingOf::::get(&caller) { Voting::Direct { votes, .. } => votes, _ => return Err("Votes are not direct".into()), }; - assert_eq!(votes.len(), (r + 1) as usize, "Votes were not recorded."); + assert_eq!(votes.len(), T::MaxVotes::get() as usize, "Votes were not recorded."); // Change vote from aye to nay let nay = Vote { aye: false, conviction: Conviction::Locked1x }; let new_vote = AccountVote::Standard { vote: nay, balance: 1000u32.into() }; - let referendum_index = Democracy::::referendum_count() - 1; + let ref_index = Democracy::::referendum_count() - 1; // This tests when a user changes a vote whitelist_account!(caller); - }: vote(RawOrigin::Signed(caller.clone()), referendum_index, new_vote) + }: vote(RawOrigin::Signed(caller.clone()), ref_index, new_vote) verify { let votes = match VotingOf::::get(&caller) { Voting::Direct { votes, .. } => votes, _ => return Err("Votes are not direct".into()), }; - assert_eq!(votes.len(), (r + 1) as usize, "Vote was incorrectly added"); - let referendum_info = Democracy::::referendum_info(referendum_index) + assert_eq!(votes.len(), T::MaxVotes::get() as usize, "Vote was incorrectly added"); + let referendum_info = Democracy::::referendum_info(ref_index) .ok_or("referendum doesn't exist")?; let tally = match referendum_info { ReferendumInfo::Ongoing(r) => r.tally, @@ -193,62 +178,55 @@ benchmarks! { emergency_cancel { let origin = T::CancellationOrigin::successful_origin(); - let referendum_index = add_referendum::(0)?; - let call = Call::::emergency_cancel { ref_index: referendum_index }; - assert_ok!(Democracy::::referendum_status(referendum_index)); - }: { call.dispatch_bypass_filter(origin)? } + let ref_index = add_referendum::(0).0; + assert_ok!(Democracy::::referendum_status(ref_index)); + }: _(origin, ref_index) verify { // Referendum has been canceled assert_noop!( - Democracy::::referendum_status(referendum_index), + Democracy::::referendum_status(ref_index), Error::::ReferendumInvalid, ); } blacklist { - let p in 1 .. T::MaxProposals::get(); - // Place our proposal at the end to make sure it's worst case. - for i in 0 .. p - 1 { + for i in 0 .. T::MaxProposals::get() - 1 { add_proposal::(i)?; } // We should really add a lot of seconds here, but we're not doing it elsewhere. + // Add a referendum of our proposal. + let (ref_index, hash) = add_referendum::(0); + assert_ok!(Democracy::::referendum_status(ref_index)); // Place our proposal in the external queue, too. - let hash = T::Hashing::hash_of(&0); assert_ok!( - Democracy::::external_propose(T::ExternalOrigin::successful_origin(), hash.clone()) + Democracy::::external_propose(T::ExternalOrigin::successful_origin(), make_proposal::(0)) ); - - // Add a referendum of our proposal. - let referendum_index = add_referendum::(0)?; - assert_ok!(Democracy::::referendum_status(referendum_index)); - - let call = Call::::blacklist { proposal_hash: hash, maybe_ref_index: Some(referendum_index) }; let origin = T::BlacklistOrigin::successful_origin(); - }: { call.dispatch_bypass_filter(origin)? } + }: _(origin, hash, Some(ref_index)) verify { // Referendum has been canceled assert_noop!( - Democracy::::referendum_status(referendum_index), + Democracy::::referendum_status(ref_index), Error::::ReferendumInvalid ); } // Worst case scenario, we external propose a previously blacklisted proposal external_propose { - let v in 1 .. MAX_VETOERS as u32; - let origin = T::ExternalOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&0); + let proposal = make_proposal::(0); // Add proposal to blacklist with block number 0 - Blacklist::::insert( - proposal_hash, - (T::BlockNumber::zero(), vec![T::AccountId::default(); v as usize]) - ); - let call = Call::::external_propose { proposal_hash }; - }: { call.dispatch_bypass_filter(origin)? } + let addresses: BoundedVec<_, _> = (0..(T::MaxBlacklisted::get() - 1)) + .into_iter() + .map(|i| account::("blacklist", i, SEED)) + .collect::>() + .try_into() + .unwrap(); + Blacklist::::insert(proposal.hash(), (T::BlockNumber::zero(), addresses)); + }: _(origin, proposal) verify { // External proposal created ensure!(>::exists(), "External proposal didn't work"); @@ -256,9 +234,8 @@ benchmarks! { external_propose_majority { let origin = T::ExternalMajorityOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&0); - let call = Call::::external_propose_majority { proposal_hash }; - }: { call.dispatch_bypass_filter(origin)? } + let proposal = make_proposal::(0); + }: _(origin, proposal) verify { // External proposal created ensure!(>::exists(), "External proposal didn't work"); @@ -266,9 +243,8 @@ benchmarks! { external_propose_default { let origin = T::ExternalDefaultOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&0); - let call = Call::::external_propose_default { proposal_hash }; - }: { call.dispatch_bypass_filter(origin)? } + let proposal = make_proposal::(0); + }: _(origin, proposal) verify { // External proposal created ensure!(>::exists(), "External proposal didn't work"); @@ -276,81 +252,60 @@ benchmarks! { fast_track { let origin_propose = T::ExternalDefaultOrigin::successful_origin(); - let proposal_hash: T::Hash = T::Hashing::hash_of(&0); - Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; + let proposal = make_proposal::(0); + let proposal_hash = proposal.hash(); + Democracy::::external_propose_default(origin_propose, proposal)?; // NOTE: Instant origin may invoke a little bit more logic, but may not always succeed. let origin_fast_track = T::FastTrackOrigin::successful_origin(); let voting_period = T::FastTrackVotingPeriod::get(); let delay = 0u32; - let call = Call::::fast_track { - proposal_hash, - voting_period: voting_period.into(), - delay: delay.into() - }; - - }: { call.dispatch_bypass_filter(origin_fast_track)? } + }: _(origin_fast_track, proposal_hash, voting_period, delay.into()) verify { assert_eq!(Democracy::::referendum_count(), 1, "referendum not created") } veto_external { - // Existing veto-ers - let v in 0 .. MAX_VETOERS as u32; - - let proposal_hash: T::Hash = T::Hashing::hash_of(&v); + let proposal = make_proposal::(0); + let proposal_hash = proposal.hash(); let origin_propose = T::ExternalDefaultOrigin::successful_origin(); - Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; + Democracy::::external_propose_default(origin_propose, proposal)?; - let mut vetoers: Vec = Vec::new(); - for i in 0 .. v { - vetoers.push(account("vetoer", i, SEED)); + let mut vetoers: BoundedVec = Default::default(); + for i in 0 .. (T::MaxBlacklisted::get() - 1) { + vetoers.try_push(account::("vetoer", i, SEED)).unwrap(); } vetoers.sort(); Blacklist::::insert(proposal_hash, (T::BlockNumber::zero(), vetoers)); - let call = Call::::veto_external { proposal_hash }; let origin = T::VetoOrigin::successful_origin(); ensure!(NextExternal::::get().is_some(), "no external proposal"); - }: { call.dispatch_bypass_filter(origin)? } + }: _(origin, proposal_hash) verify { assert!(NextExternal::::get().is_none()); let (_, new_vetoers) = >::get(&proposal_hash).ok_or("no blacklist")?; - assert_eq!(new_vetoers.len(), (v + 1) as usize, "vetoers not added"); + assert_eq!(new_vetoers.len(), T::MaxBlacklisted::get() as usize, "vetoers not added"); } cancel_proposal { - let p in 1 .. T::MaxProposals::get(); - // Place our proposal at the end to make sure it's worst case. - for i in 0 .. p { + for i in 0 .. T::MaxProposals::get() { add_proposal::(i)?; } - }: _(RawOrigin::Root, 0) + let cancel_origin = T::CancelProposalOrigin::successful_origin(); + }: _(cancel_origin, 0) cancel_referendum { - let referendum_index = add_referendum::(0)?; - }: _(RawOrigin::Root, referendum_index) - - cancel_queued { - let r in 1 .. MAX_REFERENDUMS; - - for i in 0..r { - add_referendum::(i)?; // This add one element in the scheduler - } + let ref_index = add_referendum::(0).0; + }: _(RawOrigin::Root, ref_index) - let referendum_index = add_referendum::(r)?; - }: _(RawOrigin::Root, referendum_index) - - // This measures the path of `launch_next` external. Not currently used as we simply - // assume the weight is `MaxBlockWeight` when executing. #[extra] on_initialize_external { - let r in 0 .. MAX_REFERENDUMS; + let r in 0 .. REFERENDUM_COUNT_HINT; for i in 0..r { - add_referendum::(i)?; + add_referendum::(i); } assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); @@ -359,8 +314,8 @@ benchmarks! { LastTabledWasExternal::::put(false); let origin = T::ExternalMajorityOrigin::successful_origin(); - let proposal_hash = T::Hashing::hash_of(&r); - let call = Call::::external_propose_majority { proposal_hash }; + let proposal = make_proposal::(r); + let call = Call::::external_propose_majority { proposal }; call.dispatch_bypass_filter(origin)?; // External proposal created ensure!(>::exists(), "External proposal didn't work"); @@ -384,14 +339,12 @@ benchmarks! { } } - // This measures the path of `launch_next` public. Not currently used as we simply - // assume the weight is `MaxBlockWeight` when executing. #[extra] on_initialize_public { - let r in 1 .. MAX_REFERENDUMS; + let r in 0 .. (T::MaxVotes::get() - 1); for i in 0..r { - add_referendum::(i)?; + add_referendum::(i); } assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); @@ -420,10 +373,40 @@ benchmarks! { // No launch no maturing referenda. on_initialize_base { - let r in 1 .. MAX_REFERENDUMS; + let r in 0 .. (T::MaxVotes::get() - 1); + + for i in 0..r { + add_referendum::(i); + } + + for (key, mut info) in ReferendumInfoOf::::iter() { + if let ReferendumInfo::Ongoing(ref mut status) = info { + status.end += 100u32.into(); + } + ReferendumInfoOf::::insert(key, info); + } + + assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); + assert_eq!(Democracy::::lowest_unbaked(), 0, "invalid referenda init"); + + }: { Democracy::::on_initialize(1u32.into()) } + verify { + // All should be on going + for i in 0 .. r { + if let Some(value) = ReferendumInfoOf::::get(i) { + match value { + ReferendumInfo::Finished { .. } => return Err("Referendum has been finished".into()), + ReferendumInfo::Ongoing(_) => (), + } + } + } + } + + on_initialize_base_with_launch_period { + let r in 0 .. (T::MaxVotes::get() - 1); for i in 0..r { - add_referendum::(i)?; + add_referendum::(i); } for (key, mut info) in ReferendumInfoOf::::iter() { @@ -436,7 +419,9 @@ benchmarks! { assert_eq!(Democracy::::referendum_count(), r, "referenda not created"); assert_eq!(Democracy::::lowest_unbaked(), 0, "invalid referenda init"); - }: { Democracy::::on_initialize(0u32.into()) } + let block_number = T::LaunchPeriod::get(); + + }: { Democracy::::on_initialize(block_number) } verify { // All should be on going for i in 0 .. r { @@ -450,7 +435,7 @@ benchmarks! { } delegate { - let r in 1 .. MAX_REFERENDUMS; + let r in 0 .. (T::MaxVotes::get() - 1); let initial_balance: BalanceOf = 100u32.into(); let delegated_balance: BalanceOf = 1000u32.into(); @@ -458,9 +443,10 @@ benchmarks! { let caller = funded_account::("caller", 0); // Caller will initially delegate to `old_delegate` let old_delegate: T::AccountId = funded_account::("old_delegate", r); + let old_delegate_lookup = T::Lookup::unlookup(old_delegate.clone()); Democracy::::delegate( RawOrigin::Signed(caller.clone()).into(), - old_delegate.clone(), + old_delegate_lookup, Conviction::Locked1x, delegated_balance, )?; @@ -472,11 +458,12 @@ benchmarks! { assert_eq!(balance, delegated_balance, "delegation balance didn't work"); // Caller will now switch to `new_delegate` let new_delegate: T::AccountId = funded_account::("new_delegate", r); + let new_delegate_lookup = T::Lookup::unlookup(new_delegate.clone()); let account_vote = account_vote::(initial_balance); // We need to create existing direct votes for the `new_delegate` for i in 0..r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_idx, account_vote.clone())?; + let ref_index = add_referendum::(i).0; + Democracy::::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_index, account_vote)?; } let votes = match VotingOf::::get(&new_delegate) { Voting::Direct { votes, .. } => votes, @@ -484,7 +471,7 @@ benchmarks! { }; assert_eq!(votes.len(), r as usize, "Votes were not recorded."); whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone()), new_delegate.clone(), Conviction::Locked1x, delegated_balance) + }: _(RawOrigin::Signed(caller.clone()), new_delegate_lookup, Conviction::Locked1x, delegated_balance) verify { let (target, balance) = match VotingOf::::get(&caller) { Voting::Delegating { target, balance, .. } => (target, balance), @@ -500,7 +487,7 @@ benchmarks! { } undelegate { - let r in 1 .. MAX_REFERENDUMS; + let r in 0 .. (T::MaxVotes::get() - 1); let initial_balance: BalanceOf = 100u32.into(); let delegated_balance: BalanceOf = 1000u32.into(); @@ -508,9 +495,10 @@ benchmarks! { let caller = funded_account::("caller", 0); // Caller will delegate let the_delegate: T::AccountId = funded_account::("delegate", r); + let the_delegate_lookup = T::Lookup::unlookup(the_delegate.clone()); Democracy::::delegate( RawOrigin::Signed(caller.clone()).into(), - the_delegate.clone(), + the_delegate_lookup, Conviction::Locked1x, delegated_balance, )?; @@ -523,11 +511,11 @@ benchmarks! { // We need to create votes direct votes for the `delegate` let account_vote = account_vote::(initial_balance); for i in 0..r { - let ref_idx = add_referendum::(i)?; + let ref_index = add_referendum::(i).0; Democracy::::vote( RawOrigin::Signed(the_delegate.clone()).into(), - ref_idx, - account_vote.clone() + ref_index, + account_vote )?; } let votes = match VotingOf::::get(&the_delegate) { @@ -550,86 +538,25 @@ benchmarks! { }: _(RawOrigin::Root) - note_preimage { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let caller = funded_account::("caller", 0); - let encoded_proposal = vec![1; b as usize]; - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), encoded_proposal.clone()) - verify { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - } - - note_imminent_preimage { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - // d + 1 to include the one we are testing - let encoded_proposal = vec![1; b as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - let block_number = T::BlockNumber::one(); - Preimages::::insert(&proposal_hash, PreimageStatus::Missing(block_number)); - - let caller = funded_account::("caller", 0); - let encoded_proposal = vec![1; b as usize]; - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), encoded_proposal.clone()) - verify { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - } - - reap_preimage { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let encoded_proposal = vec![1; b as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - - let submitter = funded_account::("submitter", b); - Democracy::::note_preimage(RawOrigin::Signed(submitter.clone()).into(), encoded_proposal.clone())?; - - // We need to set this otherwise we get `Early` error. - let block_number = T::VotingPeriod::get() + T::EnactmentPeriod::get() + T::BlockNumber::one(); - System::::set_block_number(block_number.into()); - - assert!(Preimages::::contains_key(proposal_hash)); - - let caller = funded_account::("caller", 0); - whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), proposal_hash.clone(), u32::MAX) - verify { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - assert!(!Preimages::::contains_key(proposal_hash)); - } - // Test when unlock will remove locks unlock_remove { - let r in 1 .. MAX_REFERENDUMS; + let r in 0 .. (T::MaxVotes::get() - 1); let locker = funded_account::("locker", 0); + let locker_lookup = T::Lookup::unlookup(locker.clone()); // Populate votes so things are locked let base_balance: BalanceOf = 100u32.into(); let small_vote = account_vote::(base_balance); // Vote and immediately unvote for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; - Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), ref_idx)?; + let ref_index = add_referendum::(i).0; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_index, small_vote)?; + Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), ref_index)?; } let caller = funded_account::("caller", 0); whitelist_account!(caller); - }: unlock(RawOrigin::Signed(caller), locker.clone()) + }: unlock(RawOrigin::Signed(caller), locker_lookup) verify { // Note that we may want to add a `get_lock` api to actually verify let voting = VotingOf::::get(&locker); @@ -638,21 +565,22 @@ benchmarks! { // Test when unlock will set a new value unlock_set { - let r in 1 .. MAX_REFERENDUMS; + let r in 0 .. (T::MaxVotes::get() - 1); let locker = funded_account::("locker", 0); + let locker_lookup = T::Lookup::unlookup(locker.clone()); // Populate votes so things are locked let base_balance: BalanceOf = 100u32.into(); let small_vote = account_vote::(base_balance); for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; + let ref_index = add_referendum::(i).0; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_index, small_vote)?; } // Create a big vote so lock increases let big_vote = account_vote::(base_balance * 10u32.into()); - let referendum_index = add_referendum::(r)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), referendum_index, big_vote)?; + let ref_index = add_referendum::(r).0; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_index, big_vote)?; let votes = match VotingOf::::get(&locker) { Voting::Direct { votes, .. } => votes, @@ -663,11 +591,11 @@ benchmarks! { let voting = VotingOf::::get(&locker); assert_eq!(voting.locked_balance(), base_balance * 10u32.into()); - Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), referendum_index)?; + Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), ref_index)?; let caller = funded_account::("caller", 0); whitelist_account!(caller); - }: unlock(RawOrigin::Signed(caller), locker.clone()) + }: unlock(RawOrigin::Signed(caller), locker_lookup) verify { let votes = match VotingOf::::get(&locker) { Voting::Direct { votes, .. } => votes, @@ -677,18 +605,18 @@ benchmarks! { let voting = VotingOf::::get(&locker); // Note that we may want to add a `get_lock` api to actually verify - assert_eq!(voting.locked_balance(), base_balance); + assert_eq!(voting.locked_balance(), if r > 0 { base_balance } else { 0u32.into() }); } remove_vote { - let r in 1 .. MAX_REFERENDUMS; + let r in 1 .. T::MaxVotes::get(); let caller = funded_account::("caller", 0); let account_vote = account_vote::(100u32.into()); for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + let ref_index = add_referendum::(i).0; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_index, account_vote)?; } let votes = match VotingOf::::get(&caller) { @@ -697,9 +625,9 @@ benchmarks! { }; assert_eq!(votes.len(), r as usize, "Votes not created"); - let referendum_index = r - 1; + let ref_index = r - 1; whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone()), referendum_index) + }: _(RawOrigin::Signed(caller.clone()), ref_index) verify { let votes = match VotingOf::::get(&caller) { Voting::Direct { votes, .. } => votes, @@ -710,14 +638,15 @@ benchmarks! { // Worst case is when target == caller and referendum is ongoing remove_other_vote { - let r in 1 .. MAX_REFERENDUMS; + let r in 1 .. T::MaxVotes::get(); let caller = funded_account::("caller", r); + let caller_lookup = T::Lookup::unlookup(caller.clone()); let account_vote = account_vote::(100u32.into()); for i in 0 .. r { - let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + let ref_index = add_referendum::(i).0; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_index, account_vote)?; } let votes = match VotingOf::::get(&caller) { @@ -726,9 +655,9 @@ benchmarks! { }; assert_eq!(votes.len(), r as usize, "Votes not created"); - let referendum_index = r - 1; + let ref_index = r - 1; whitelist_account!(caller); - }: _(RawOrigin::Signed(caller.clone()), caller.clone(), referendum_index) + }: _(RawOrigin::Signed(caller.clone()), caller_lookup, ref_index) verify { let votes = match VotingOf::::get(&caller) { Voting::Direct { votes, .. } => votes, @@ -737,49 +666,9 @@ benchmarks! { assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); } - #[extra] - enact_proposal_execute { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let proposer = funded_account::("proposer", 0); - let raw_call = Call::note_preimage { encoded_proposal: vec![1; b as usize] }; - let generic_call: T::Proposal = raw_call.into(); - let encoded_proposal = generic_call.encode(); - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - Democracy::::note_preimage(RawOrigin::Signed(proposer).into(), encoded_proposal)?; - - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - }: enact_proposal(RawOrigin::Root, proposal_hash, 0) - verify { - // Fails due to mismatched origin - assert_last_event::(Event::::Executed(0, Err(BadOrigin.into())).into()); - } - - #[extra] - enact_proposal_slash { - // Num of bytes in encoded proposal - let b in 0 .. MAX_BYTES; - - let proposer = funded_account::("proposer", 0); - // Random invalid bytes - let encoded_proposal = vec![200; b as usize]; - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - Democracy::::note_preimage(RawOrigin::Signed(proposer).into(), encoded_proposal)?; - - match Preimages::::get(proposal_hash) { - Some(PreimageStatus::Available { .. }) => (), - _ => return Err("preimage not available".into()) - } - }: { - assert_eq!( - Democracy::::enact_proposal(RawOrigin::Root.into(), proposal_hash, 0), - Err(Error::::PreimageInvalid.into()) - ); - } + impl_benchmark_test_suite!( + Democracy, + crate::tests::new_test_ext(), + crate::tests::Test + ); } - -impl_benchmark_test_suite!(Democracy, crate::tests::new_test_ext(), crate::tests::Test); diff --git a/frame/democracy/src/conviction.rs b/frame/democracy/src/conviction.rs index b4f24c93bb40f..a938d8a4e6852 100644 --- a/frame/democracy/src/conviction.rs +++ b/frame/democracy/src/conviction.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,16 +18,28 @@ //! The conviction datatype. use crate::types::Delegations; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ traits::{Bounded, CheckedDiv, CheckedMul, Zero}, RuntimeDebug, }; -use sp_std::{convert::TryFrom, result::Result}; +use sp_std::{prelude::*, result::Result}; /// A value denoting the strength of conviction of a vote. -#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo)] +#[derive( + Encode, + MaxEncodedLen, + Decode, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + RuntimeDebug, + TypeInfo, +)] pub enum Conviction { /// 0.1x votes, unlocked. None, diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 8bc6921c4f8ad..2c65e5d94bc46 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -149,23 +149,23 @@ //! - `cancel_queued` - Cancels a proposal that is queued for enactment. //! - `clear_public_proposal` - Removes all public proposals. -#![recursion_limit = "128"] +#![recursion_limit = "256"] #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode, Input}; +use codec::{Decode, Encode}; use frame_support::{ ensure, traits::{ - schedule::{DispatchTime, Named as ScheduleNamed}, - BalanceStatus, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, - ReservableCurrency, WithdrawReasons, + defensive_prelude::*, + schedule::{v3::Named as ScheduleNamed, DispatchTime}, + Bounded, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, QueryPreimage, + ReservableCurrency, StorePreimage, WithdrawReasons, }, weights::Weight, }; -use scale_info::TypeInfo; use sp_runtime::{ - traits::{Bounded, Dispatchable, Hash, Saturating, Zero}, - ArithmeticError, DispatchError, DispatchResult, RuntimeDebug, + traits::{Bounded as ArithBounded, One, Saturating, StaticLookup, Zero}, + ArithmeticError, DispatchError, DispatchResult, }; use sp_std::prelude::*; @@ -187,12 +187,9 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; -const DEMOCRACY_ID: LockIdentifier = *b"democrac"; +pub mod migrations; -/// The maximum number of vetoers on a single proposal used to compute Weight. -/// -/// NOTE: This is not enforced by any logic. -pub const MAX_VETOERS: u32 = 100; +const DEMOCRACY_ID: LockIdentifier = *b"democrac"; /// A proposal index. pub type PropIndex = u32; @@ -205,60 +202,35 @@ type BalanceOf = type NegativeImbalanceOf = <::Currency as Currency< ::AccountId, >>::NegativeImbalance; - -#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub enum PreimageStatus { - /// The preimage is imminently needed at the argument. - Missing(BlockNumber), - /// The preimage is available. - Available { - data: Vec, - provider: AccountId, - deposit: Balance, - since: BlockNumber, - /// None if it's not imminent. - expiry: Option, - }, -} - -impl PreimageStatus { - fn to_missing_expiry(self) -> Option { - match self { - PreimageStatus::Missing(expiry) => Some(expiry), - _ => None, - } - } -} - -// A value placed in storage that represents the current version of the Democracy storage. -// This value is used by the `on_runtime_upgrade` logic to determine whether we run -// storage migration logic. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] -enum Releases { - V1, -} +pub type CallOf = ::RuntimeCall; +pub type BoundedCallOf = Bounded>; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] pub mod pallet { - use super::*; - use frame_support::{ - dispatch::DispatchResultWithPostInfo, - pallet_prelude::*, - traits::EnsureOrigin, - weights::{DispatchClass, Pays}, - Parameter, - }; - use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; - use sp_runtime::DispatchResult; + use super::{DispatchResult, *}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_core::H256; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] pub trait Config: frame_system::Config + Sized { - type Proposal: Parameter + Dispatchable + From>; - type Event: From> + IsType<::Event>; + type WeightInfo: WeightInfo; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The Scheduler. + type Scheduler: ScheduleNamed, Self::PalletsOrigin>; + + /// The Preimage provider. + type Preimages: QueryPreimage + StorePreimage; /// Currency type for this pallet. type Currency: ReservableCurrency @@ -291,121 +263,104 @@ pub mod pallet { #[pallet::constant] type MinimumDeposit: Get>; + /// Indicator for whether an emergency origin is even allowed to happen. Some chains may + /// want to set this permanently to `false`, others may want to condition it on things such + /// as an upgrade having happened recently. + #[pallet::constant] + type InstantAllowed: Get; + + /// Minimum voting period allowed for a fast-track referendum. + #[pallet::constant] + type FastTrackVotingPeriod: Get; + + /// Period in blocks where an external proposal may not be re-submitted after being vetoed. + #[pallet::constant] + type CooloffPeriod: Get; + + /// The maximum number of votes for an account. + /// + /// Also used to compute weight, an overly big value can + /// lead to extrinsic with very big weight: see `delegate` for instance. + #[pallet::constant] + type MaxVotes: Get; + + /// The maximum number of public proposals that can exist at any time. + #[pallet::constant] + type MaxProposals: Get; + + /// The maximum number of deposits a public proposal may have at any time. + #[pallet::constant] + type MaxDeposits: Get; + + /// The maximum number of items which can be blacklisted. + #[pallet::constant] + type MaxBlacklisted: Get; + /// Origin from which the next tabled referendum may be forced. This is a normal /// "super-majority-required" referendum. - type ExternalOrigin: EnsureOrigin; + type ExternalOrigin: EnsureOrigin; /// Origin from which the next tabled referendum may be forced; this allows for the tabling /// of a majority-carries referendum. - type ExternalMajorityOrigin: EnsureOrigin; + type ExternalMajorityOrigin: EnsureOrigin; /// Origin from which the next tabled referendum may be forced; this allows for the tabling /// of a negative-turnout-bias (default-carries) referendum. - type ExternalDefaultOrigin: EnsureOrigin; + type ExternalDefaultOrigin: EnsureOrigin; /// Origin from which the next majority-carries (or more permissive) referendum may be /// tabled to vote according to the `FastTrackVotingPeriod` asynchronously in a similar /// manner to the emergency origin. It retains its threshold method. - type FastTrackOrigin: EnsureOrigin; + type FastTrackOrigin: EnsureOrigin; /// Origin from which the next majority-carries (or more permissive) referendum may be /// tabled to vote immediately and asynchronously in a similar manner to the emergency /// origin. It retains its threshold method. - type InstantOrigin: EnsureOrigin; - - /// Indicator for whether an emergency origin is even allowed to happen. Some chains may - /// want to set this permanently to `false`, others may want to condition it on things such - /// as an upgrade having happened recently. - #[pallet::constant] - type InstantAllowed: Get; - - /// Minimum voting period allowed for a fast-track referendum. - #[pallet::constant] - type FastTrackVotingPeriod: Get; + type InstantOrigin: EnsureOrigin; /// Origin from which any referendum may be cancelled in an emergency. - type CancellationOrigin: EnsureOrigin; + type CancellationOrigin: EnsureOrigin; /// Origin from which proposals may be blacklisted. - type BlacklistOrigin: EnsureOrigin; + type BlacklistOrigin: EnsureOrigin; /// Origin from which a proposal may be cancelled and its backers slashed. - type CancelProposalOrigin: EnsureOrigin; + type CancelProposalOrigin: EnsureOrigin; /// Origin for anyone able to veto proposals. - /// - /// # Warning - /// - /// The number of Vetoers for a proposal must be small, extrinsics are weighted according to - /// [MAX_VETOERS](./const.MAX_VETOERS.html) - type VetoOrigin: EnsureOrigin; - - /// Period in blocks where an external proposal may not be re-submitted after being vetoed. - #[pallet::constant] - type CooloffPeriod: Get; - - /// The amount of balance that must be deposited per byte of preimage stored. - #[pallet::constant] - type PreimageByteDeposit: Get>; - - /// An origin that can provide a preimage using operational extrinsics. - type OperationalPreimageOrigin: EnsureOrigin; - - /// Handler for the unbalanced reduction when slashing a preimage deposit. - type Slash: OnUnbalanced>; - - /// The Scheduler. - type Scheduler: ScheduleNamed; + type VetoOrigin: EnsureOrigin; /// Overarching type of all pallets origins. type PalletsOrigin: From>; - /// The maximum number of votes for an account. - /// - /// Also used to compute weight, an overly big value can - /// lead to extrinsic with very big weight: see `delegate` for instance. - #[pallet::constant] - type MaxVotes: Get; - - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; - - /// The maximum number of public proposals that can exist at any time. - #[pallet::constant] - type MaxProposals: Get; + /// Handler for the unbalanced reduction when slashing a preimage deposit. + type Slash: OnUnbalanced>; } - // TODO: Refactor public proposal queue into its own pallet. - // https://github.com/paritytech/substrate/issues/5322 /// The number of (public) proposals that have been made so far. #[pallet::storage] #[pallet::getter(fn public_prop_count)] pub type PublicPropCount = StorageValue<_, PropIndex, ValueQuery>; - /// The public proposals. Unsorted. The second item is the proposal's hash. + /// The public proposals. Unsorted. The second item is the proposal. #[pallet::storage] #[pallet::getter(fn public_props)] - pub type PublicProps = - StorageValue<_, Vec<(PropIndex, T::Hash, T::AccountId)>, ValueQuery>; + pub type PublicProps = StorageValue< + _, + BoundedVec<(PropIndex, BoundedCallOf, T::AccountId), T::MaxProposals>, + ValueQuery, + >; /// Those who have locked a deposit. /// /// TWOX-NOTE: Safe, as increasing integer keys are safe. #[pallet::storage] #[pallet::getter(fn deposit_of)] - pub type DepositOf = - StorageMap<_, Twox64Concat, PropIndex, (Vec, BalanceOf)>; - - /// Map of hashes to the proposal preimage, along with who registered it and their deposit. - /// The block number is the block at which it was deposited. - // TODO: Refactor Preimages into its own pallet. - // https://github.com/paritytech/substrate/issues/5322 - #[pallet::storage] - pub type Preimages = StorageMap< + pub type DepositOf = StorageMap< _, - Identity, - T::Hash, - PreimageStatus, T::BlockNumber>, + Twox64Concat, + PropIndex, + (BoundedVec, BalanceOf), >; /// The next free referendum index, aka the number of referenda started so far. @@ -428,7 +383,7 @@ pub mod pallet { _, Twox64Concat, ReferendumIndex, - ReferendumInfo>, + ReferendumInfo, BalanceOf>, >; /// All votes for a particular voter. We store the balance for the number of votes that we @@ -440,22 +395,12 @@ pub mod pallet { _, Twox64Concat, T::AccountId, - Voting, T::AccountId, T::BlockNumber>, + Voting, T::AccountId, T::BlockNumber, T::MaxVotes>, ValueQuery, >; - /// Accounts for which there are locks in action which may be removed at some point in the - /// future. The value is the block number at which the lock expires and may be removed. - /// - /// TWOX-NOTE: OK ― `AccountId` is a secure hash. - #[pallet::storage] - #[pallet::getter(fn locks)] - pub type Locks = StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber>; - /// True if the last referendum tabled was submitted externally. False if it was a public /// proposal. - // TODO: There should be any number of tabling origins, not just public and "external" - // (council). https://github.com/paritytech/substrate/issues/5322 #[pallet::storage] pub type LastTabledWasExternal = StorageValue<_, bool, ValueQuery>; @@ -464,23 +409,21 @@ pub mod pallet { /// - `LastTabledWasExternal` is `false`; or /// - `PublicProps` is empty. #[pallet::storage] - pub type NextExternal = StorageValue<_, (T::Hash, VoteThreshold)>; + pub type NextExternal = StorageValue<_, (BoundedCallOf, VoteThreshold)>; /// A record of who vetoed what. Maps proposal hash to a possible existent block number /// (until when it may not be resubmitted) and who vetoed it. #[pallet::storage] - pub type Blacklist = - StorageMap<_, Identity, T::Hash, (T::BlockNumber, Vec)>; + pub type Blacklist = StorageMap< + _, + Identity, + H256, + (T::BlockNumber, BoundedVec), + >; /// Record of all proposals that have been subject to emergency cancellation. #[pallet::storage] - pub type Cancellations = StorageMap<_, Identity, T::Hash, bool, ValueQuery>; - - /// Storage version of the pallet. - /// - /// New networks start with last version. - #[pallet::storage] - pub(crate) type StorageVersion = StorageValue<_, Releases>; + pub type Cancellations = StorageMap<_, Identity, H256, bool, ValueQuery>; #[pallet::genesis_config] pub struct GenesisConfig { @@ -500,52 +443,40 @@ pub mod pallet { PublicPropCount::::put(0 as PropIndex); ReferendumCount::::put(0 as ReferendumIndex); LowestUnbaked::::put(0 as ReferendumIndex); - StorageVersion::::put(Releases::V1); } } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// A motion has been proposed by a public account. \[proposal_index, deposit\] - Proposed(PropIndex, BalanceOf), - /// A public proposal has been tabled for referendum vote. \[proposal_index, deposit, - /// depositors\] - Tabled(PropIndex, BalanceOf, Vec), + /// A motion has been proposed by a public account. + Proposed { proposal_index: PropIndex, deposit: BalanceOf }, + /// A public proposal has been tabled for referendum vote. + Tabled { proposal_index: PropIndex, deposit: BalanceOf }, /// An external proposal has been tabled. ExternalTabled, - /// A referendum has begun. \[ref_index, threshold\] - Started(ReferendumIndex, VoteThreshold), - /// A proposal has been approved by referendum. \[ref_index\] - Passed(ReferendumIndex), - /// A proposal has been rejected by referendum. \[ref_index\] - NotPassed(ReferendumIndex), - /// A referendum has been cancelled. \[ref_index\] - Cancelled(ReferendumIndex), - /// A proposal has been enacted. \[ref_index, result\] - Executed(ReferendumIndex, DispatchResult), - /// An account has delegated their vote to another account. \[who, target\] - Delegated(T::AccountId, T::AccountId), - /// An \[account\] has cancelled a previous delegation operation. - Undelegated(T::AccountId), - /// An external proposal has been vetoed. \[who, proposal_hash, until\] - Vetoed(T::AccountId, T::Hash, T::BlockNumber), - /// A proposal's preimage was noted, and the deposit taken. \[proposal_hash, who, deposit\] - PreimageNoted(T::Hash, T::AccountId, BalanceOf), - /// A proposal preimage was removed and used (the deposit was returned). - /// \[proposal_hash, provider, deposit\] - PreimageUsed(T::Hash, T::AccountId, BalanceOf), - /// A proposal could not be executed because its preimage was invalid. - /// \[proposal_hash, ref_index\] - PreimageInvalid(T::Hash, ReferendumIndex), - /// A proposal could not be executed because its preimage was missing. - /// \[proposal_hash, ref_index\] - PreimageMissing(T::Hash, ReferendumIndex), - /// A registered preimage was removed and the deposit collected by the reaper. - /// \[proposal_hash, provider, deposit, reaper\] - PreimageReaped(T::Hash, T::AccountId, BalanceOf, T::AccountId), - /// A proposal \[hash\] has been blacklisted permanently. - Blacklisted(T::Hash), + /// A referendum has begun. + Started { ref_index: ReferendumIndex, threshold: VoteThreshold }, + /// A proposal has been approved by referendum. + Passed { ref_index: ReferendumIndex }, + /// A proposal has been rejected by referendum. + NotPassed { ref_index: ReferendumIndex }, + /// A referendum has been cancelled. + Cancelled { ref_index: ReferendumIndex }, + /// An account has delegated their vote to another account. + Delegated { who: T::AccountId, target: T::AccountId }, + /// An account has cancelled a previous delegation operation. + Undelegated { account: T::AccountId }, + /// An external proposal has been vetoed. + Vetoed { who: T::AccountId, proposal_hash: H256, until: T::BlockNumber }, + /// A proposal_hash has been blacklisted permanently. + Blacklisted { proposal_hash: H256 }, + /// An account has voted in a referendum + Voted { voter: T::AccountId, ref_index: ReferendumIndex, vote: AccountVote> }, + /// An account has secconded a proposal + Seconded { seconder: T::AccountId, prop_index: PropIndex }, + /// A proposal got canceled. + ProposalCanceled { prop_index: PropIndex }, } #[pallet::error] @@ -568,20 +499,8 @@ pub mod pallet { NoProposal, /// Identity may not veto a proposal twice AlreadyVetoed, - /// Preimage already noted - DuplicatePreimage, - /// Not imminent - NotImminent, - /// Too early - TooEarly, - /// Imminent - Imminent, - /// Preimage not found - PreimageMissing, /// Vote given for invalid referendum ReferendumInvalid, - /// Invalid preimage - PreimageInvalid, /// No proposals waiting NoneWaiting, /// The given account did not vote on the referendum. @@ -605,18 +524,17 @@ pub mod pallet { WrongUpperBound, /// Maximum number of votes reached. MaxVotesReached, - /// Maximum number of proposals reached. - TooManyProposals, + /// Maximum number of items reached. + TooMany, + /// Voting period too low + VotingPeriodLow, } #[pallet::hooks] impl Hooks> for Pallet { /// Weight: see `begin_block` fn on_initialize(n: T::BlockNumber) -> Weight { - Self::begin_block(n).unwrap_or_else(|e| { - sp_runtime::print(e); - 0 - }) + Self::begin_block(n) } } @@ -631,12 +549,11 @@ pub mod pallet { /// - `value`: The amount of deposit (must be at least `MinimumDeposit`). /// /// Emits `Proposed`. - /// - /// Weight: `O(p)` + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::propose())] pub fn propose( origin: OriginFor, - proposal_hash: T::Hash, + proposal: BoundedCallOf, #[pallet::compact] value: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -645,7 +562,8 @@ pub mod pallet { let index = Self::public_prop_count(); let real_prop_count = PublicProps::::decode_len().unwrap_or(0) as u32; let max_proposals = T::MaxProposals::get(); - ensure!(real_prop_count < max_proposals, Error::::TooManyProposals); + ensure!(real_prop_count < max_proposals, Error::::TooMany); + let proposal_hash = proposal.hash(); if let Some((until, _)) = >::get(proposal_hash) { ensure!( @@ -655,12 +573,16 @@ pub mod pallet { } T::Currency::reserve(&who, value)?; + + let depositors = BoundedVec::<_, T::MaxDeposits>::truncate_from(vec![who.clone()]); + DepositOf::::insert(index, (depositors, value)); + PublicPropCount::::put(index + 1); - >::insert(index, (&[&who][..], value)); - >::append((index, proposal_hash, who)); + PublicProps::::try_append((index, proposal, who)) + .map_err(|_| Error::::TooMany)?; - Self::deposit_event(Event::::Proposed(index, value)); + Self::deposit_event(Event::::Proposed { proposal_index: index, deposit: value }); Ok(()) } @@ -670,25 +592,22 @@ pub mod pallet { /// must have funds to cover the deposit, equal to the original deposit. /// /// - `proposal`: The index of the proposal to second. - /// - `seconds_upper_bound`: an upper bound on the current number of seconds on this - /// proposal. Extrinsic is weighted according to this value with no refund. - /// - /// Weight: `O(S)` where S is the number of seconds a proposal already has. - #[pallet::weight(T::WeightInfo::second(*seconds_upper_bound))] + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::second())] pub fn second( origin: OriginFor, #[pallet::compact] proposal: PropIndex, - #[pallet::compact] seconds_upper_bound: u32, ) -> DispatchResult { let who = ensure_signed(origin)?; - let seconds = - Self::len_of_deposit_of(proposal).ok_or_else(|| Error::::ProposalMissing)?; - ensure!(seconds <= seconds_upper_bound, Error::::WrongUpperBound); + let seconds = Self::len_of_deposit_of(proposal).ok_or(Error::::ProposalMissing)?; + ensure!(seconds < T::MaxDeposits::get(), Error::::TooMany); let mut deposit = Self::deposit_of(proposal).ok_or(Error::::ProposalMissing)?; T::Currency::reserve(&who, deposit.1)?; - deposit.0.push(who); + let ok = deposit.0.try_push(who.clone()).is_ok(); + debug_assert!(ok, "`seconds` is below static limit; `try_insert` should succeed; qed"); >::insert(proposal, deposit); + Self::deposit_event(Event::::Seconded { seconder: who, prop_index: proposal }); Ok(()) } @@ -699,12 +618,8 @@ pub mod pallet { /// /// - `ref_index`: The index of the referendum to vote for. /// - `vote`: The vote configuration. - /// - /// Weight: `O(R)` where R is the number of referendums the voter has voted on. - #[pallet::weight( - T::WeightInfo::vote_new(T::MaxVotes::get()) - .max(T::WeightInfo::vote_existing(T::MaxVotes::get())) - )] + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))] pub fn vote( origin: OriginFor, #[pallet::compact] ref_index: ReferendumIndex, @@ -722,6 +637,7 @@ pub mod pallet { /// -`ref_index`: The index of the referendum to cancel. /// /// Weight: `O(1)`. + #[pallet::call_index(3)] #[pallet::weight((T::WeightInfo::emergency_cancel(), DispatchClass::Operational))] pub fn emergency_cancel( origin: OriginFor, @@ -730,7 +646,7 @@ pub mod pallet { T::CancellationOrigin::ensure_origin(origin)?; let status = Self::referendum_status(ref_index)?; - let h = status.proposal_hash; + let h = status.proposal.hash(); ensure!(!>::contains_key(h), Error::::AlreadyCanceled); >::insert(h, true); @@ -744,20 +660,21 @@ pub mod pallet { /// The dispatch origin of this call must be `ExternalOrigin`. /// /// - `proposal_hash`: The preimage hash of the proposal. - /// - /// Weight: `O(V)` with V number of vetoers in the blacklist of proposal. - /// Decoding vec of length V. Charged as maximum - #[pallet::weight(T::WeightInfo::external_propose(MAX_VETOERS))] - pub fn external_propose(origin: OriginFor, proposal_hash: T::Hash) -> DispatchResult { + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::external_propose())] + pub fn external_propose( + origin: OriginFor, + proposal: BoundedCallOf, + ) -> DispatchResult { T::ExternalOrigin::ensure_origin(origin)?; ensure!(!>::exists(), Error::::DuplicateProposal); - if let Some((until, _)) = >::get(proposal_hash) { + if let Some((until, _)) = >::get(proposal.hash()) { ensure!( >::block_number() >= until, Error::::ProposalBlacklisted, ); } - >::put((proposal_hash, VoteThreshold::SuperMajorityApprove)); + >::put((proposal, VoteThreshold::SuperMajorityApprove)); Ok(()) } @@ -772,13 +689,14 @@ pub mod pallet { /// pre-scheduled `external_propose` call. /// /// Weight: `O(1)` + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::external_propose_majority())] pub fn external_propose_majority( origin: OriginFor, - proposal_hash: T::Hash, + proposal: BoundedCallOf, ) -> DispatchResult { T::ExternalMajorityOrigin::ensure_origin(origin)?; - >::put((proposal_hash, VoteThreshold::SimpleMajority)); + >::put((proposal, VoteThreshold::SimpleMajority)); Ok(()) } @@ -793,13 +711,14 @@ pub mod pallet { /// pre-scheduled `external_propose` call. /// /// Weight: `O(1)` + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::external_propose_default())] pub fn external_propose_default( origin: OriginFor, - proposal_hash: T::Hash, + proposal: BoundedCallOf, ) -> DispatchResult { T::ExternalDefaultOrigin::ensure_origin(origin)?; - >::put((proposal_hash, VoteThreshold::SuperMajorityAgainst)); + >::put((proposal, VoteThreshold::SuperMajorityAgainst)); Ok(()) } @@ -811,17 +730,19 @@ pub mod pallet { /// /// - `proposal_hash`: The hash of the current external proposal. /// - `voting_period`: The period that is allowed for voting on this proposal. Increased to - /// `FastTrackVotingPeriod` if too low. + /// Must be always greater than zero. + /// For `FastTrackOrigin` must be equal or greater than `FastTrackVotingPeriod`. /// - `delay`: The number of block after voting has ended in approval and this should be /// enacted. This doesn't have a minimum amount. /// /// Emits `Started`. /// /// Weight: `O(1)` + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::fast_track())] pub fn fast_track( origin: OriginFor, - proposal_hash: T::Hash, + proposal_hash: H256, voting_period: T::BlockNumber, delay: T::BlockNumber, ) -> DispatchResult { @@ -831,29 +752,33 @@ pub mod pallet { // - `InstantAllowed` is `true` and `origin` is `InstantOrigin`. let maybe_ensure_instant = if voting_period < T::FastTrackVotingPeriod::get() { Some(origin) + } else if let Err(origin) = T::FastTrackOrigin::try_origin(origin) { + Some(origin) } else { - if let Err(origin) = T::FastTrackOrigin::try_origin(origin) { - Some(origin) - } else { - None - } + None }; if let Some(ensure_instant) = maybe_ensure_instant { T::InstantOrigin::ensure_origin(ensure_instant)?; ensure!(T::InstantAllowed::get(), Error::::InstantNotAllowed); } - let (e_proposal_hash, threshold) = + ensure!(voting_period > T::BlockNumber::zero(), Error::::VotingPeriodLow); + let (ext_proposal, threshold) = >::get().ok_or(Error::::ProposalMissing)?; ensure!( threshold != VoteThreshold::SuperMajorityApprove, Error::::NotSimpleMajority, ); - ensure!(proposal_hash == e_proposal_hash, Error::::InvalidHash); + ensure!(proposal_hash == ext_proposal.hash(), Error::::InvalidHash); >::kill(); let now = >::block_number(); - Self::inject_referendum(now + voting_period, proposal_hash, threshold, delay); + Self::inject_referendum( + now.saturating_add(voting_period), + ext_proposal, + threshold, + delay, + ); Ok(()) } @@ -866,26 +791,30 @@ pub mod pallet { /// Emits `Vetoed`. /// /// Weight: `O(V + log(V))` where V is number of `existing vetoers` - #[pallet::weight(T::WeightInfo::veto_external(MAX_VETOERS))] - pub fn veto_external(origin: OriginFor, proposal_hash: T::Hash) -> DispatchResult { + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::veto_external())] + pub fn veto_external(origin: OriginFor, proposal_hash: H256) -> DispatchResult { let who = T::VetoOrigin::ensure_origin(origin)?; - if let Some((e_proposal_hash, _)) = >::get() { - ensure!(proposal_hash == e_proposal_hash, Error::::ProposalMissing); + if let Some((ext_proposal, _)) = NextExternal::::get() { + ensure!(proposal_hash == ext_proposal.hash(), Error::::ProposalMissing); } else { - Err(Error::::NoProposal)?; + return Err(Error::::NoProposal.into()) } let mut existing_vetoers = - >::get(&proposal_hash).map(|pair| pair.1).unwrap_or_else(Vec::new); + >::get(&proposal_hash).map(|pair| pair.1).unwrap_or_default(); let insert_position = existing_vetoers.binary_search(&who).err().ok_or(Error::::AlreadyVetoed)?; + existing_vetoers + .try_insert(insert_position, who.clone()) + .map_err(|_| Error::::TooMany)?; - existing_vetoers.insert(insert_position, who.clone()); - let until = >::block_number() + T::CooloffPeriod::get(); + let until = + >::block_number().saturating_add(T::CooloffPeriod::get()); >::insert(&proposal_hash, (until, existing_vetoers)); - Self::deposit_event(Event::::Vetoed(who, proposal_hash, until)); + Self::deposit_event(Event::::Vetoed { who, proposal_hash, until }); >::kill(); Ok(()) } @@ -897,6 +826,7 @@ pub mod pallet { /// - `ref_index`: The index of the referendum to cancel. /// /// # Weight: `O(1)`. + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::cancel_referendum())] pub fn cancel_referendum( origin: OriginFor, @@ -907,21 +837,6 @@ pub mod pallet { Ok(()) } - /// Cancel a proposal queued for enactment. - /// - /// The dispatch origin of this call must be _Root_. - /// - /// - `which`: The index of the referendum to cancel. - /// - /// Weight: `O(D)` where `D` is the items in the dispatch queue. Weighted as `D = 10`. - #[pallet::weight((T::WeightInfo::cancel_queued(10), DispatchClass::Operational))] - pub fn cancel_queued(origin: OriginFor, which: ReferendumIndex) -> DispatchResult { - ensure_root(origin)?; - T::Scheduler::cancel_named((DEMOCRACY_ID, which).encode()) - .map_err(|_| Error::::ProposalMissing)?; - Ok(()) - } - /// Delegate the voting power (with some given conviction) of the sending account. /// /// The balance delegated is locked for as long as it's delegated, and thereafter for the @@ -944,14 +859,16 @@ pub mod pallet { /// voted on. Weight is charged as if maximum votes. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] pub fn delegate( origin: OriginFor, - to: T::AccountId, + to: AccountIdLookupOf, conviction: Conviction, balance: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; + let to = T::Lookup::lookup(to)?; let votes = Self::try_delegate(who, to, conviction, balance)?; Ok(Some(T::WeightInfo::delegate(votes)).into()) @@ -971,7 +888,8 @@ pub mod pallet { /// voted on. Weight is charged as if maximum votes. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. - #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))] + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get()))] pub fn undelegate(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let votes = Self::try_undelegate(who)?; @@ -983,6 +901,7 @@ pub mod pallet { /// The dispatch origin of this call must be _Root_. /// /// Weight: `O(1)`. + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::clear_public_proposals())] pub fn clear_public_proposals(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -990,127 +909,6 @@ pub mod pallet { Ok(()) } - /// Register the preimage for an upcoming proposal. This doesn't require the proposal to be - /// in the dispatch queue but does require a deposit, returned once enacted. - /// - /// The dispatch origin of this call must be _Signed_. - /// - /// - `encoded_proposal`: The preimage of a proposal. - /// - /// Emits `PreimageNoted`. - /// - /// Weight: `O(E)` with E size of `encoded_proposal` (protected by a required deposit). - #[pallet::weight(T::WeightInfo::note_preimage(encoded_proposal.len() as u32))] - pub fn note_preimage(origin: OriginFor, encoded_proposal: Vec) -> DispatchResult { - Self::note_preimage_inner(ensure_signed(origin)?, encoded_proposal)?; - Ok(()) - } - - /// Same as `note_preimage` but origin is `OperationalPreimageOrigin`. - #[pallet::weight(( - T::WeightInfo::note_preimage(encoded_proposal.len() as u32), - DispatchClass::Operational, - ))] - pub fn note_preimage_operational( - origin: OriginFor, - encoded_proposal: Vec, - ) -> DispatchResult { - let who = T::OperationalPreimageOrigin::ensure_origin(origin)?; - Self::note_preimage_inner(who, encoded_proposal)?; - Ok(()) - } - - /// Register the preimage for an upcoming proposal. This requires the proposal to be - /// in the dispatch queue. No deposit is needed. When this call is successful, i.e. - /// the preimage has not been uploaded before and matches some imminent proposal, - /// no fee is paid. - /// - /// The dispatch origin of this call must be _Signed_. - /// - /// - `encoded_proposal`: The preimage of a proposal. - /// - /// Emits `PreimageNoted`. - /// - /// Weight: `O(E)` with E size of `encoded_proposal` (protected by a required deposit). - #[pallet::weight(T::WeightInfo::note_imminent_preimage(encoded_proposal.len() as u32))] - pub fn note_imminent_preimage( - origin: OriginFor, - encoded_proposal: Vec, - ) -> DispatchResultWithPostInfo { - Self::note_imminent_preimage_inner(ensure_signed(origin)?, encoded_proposal)?; - // We check that this preimage was not uploaded before in - // `note_imminent_preimage_inner`, thus this call can only be successful once. If - // successful, user does not pay a fee. - Ok(Pays::No.into()) - } - - /// Same as `note_imminent_preimage` but origin is `OperationalPreimageOrigin`. - #[pallet::weight(( - T::WeightInfo::note_imminent_preimage(encoded_proposal.len() as u32), - DispatchClass::Operational, - ))] - pub fn note_imminent_preimage_operational( - origin: OriginFor, - encoded_proposal: Vec, - ) -> DispatchResultWithPostInfo { - let who = T::OperationalPreimageOrigin::ensure_origin(origin)?; - Self::note_imminent_preimage_inner(who, encoded_proposal)?; - // We check that this preimage was not uploaded before in - // `note_imminent_preimage_inner`, thus this call can only be successful once. If - // successful, user does not pay a fee. - Ok(Pays::No.into()) - } - - /// Remove an expired proposal preimage and collect the deposit. - /// - /// The dispatch origin of this call must be _Signed_. - /// - /// - `proposal_hash`: The preimage hash of a proposal. - /// - `proposal_length_upper_bound`: an upper bound on length of the proposal. Extrinsic is - /// weighted according to this value with no refund. - /// - /// This will only work after `VotingPeriod` blocks from the time that the preimage was - /// noted, if it's the same account doing it. If it's a different account, then it'll only - /// work an additional `EnactmentPeriod` later. - /// - /// Emits `PreimageReaped`. - /// - /// Weight: `O(D)` where D is length of proposal. - #[pallet::weight(T::WeightInfo::reap_preimage(*proposal_len_upper_bound))] - pub fn reap_preimage( - origin: OriginFor, - proposal_hash: T::Hash, - #[pallet::compact] proposal_len_upper_bound: u32, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!( - Self::pre_image_data_len(proposal_hash)? <= proposal_len_upper_bound, - Error::::WrongUpperBound, - ); - - let (provider, deposit, since, expiry) = >::get(&proposal_hash) - .and_then(|m| match m { - PreimageStatus::Available { provider, deposit, since, expiry, .. } => - Some((provider, deposit, since, expiry)), - _ => None, - }) - .ok_or(Error::::PreimageMissing)?; - - let now = >::block_number(); - let (voting, enactment) = (T::VotingPeriod::get(), T::EnactmentPeriod::get()); - let additional = if who == provider { Zero::zero() } else { enactment }; - ensure!(now >= since + voting + additional, Error::::TooEarly); - ensure!(expiry.map_or(true, |e| now > e), Error::::Imminent); - - let res = - T::Currency::repatriate_reserved(&provider, &who, deposit, BalanceStatus::Free); - debug_assert!(res.is_ok()); - >::remove(&proposal_hash); - Self::deposit_event(Event::::PreimageReaped(proposal_hash, provider, deposit, who)); - Ok(()) - } - /// Unlock tokens that have an expired lock. /// /// The dispatch origin of this call must be _Signed_. @@ -1118,12 +916,11 @@ pub mod pallet { /// - `target`: The account to remove the lock on. /// /// Weight: `O(R)` with R number of vote of target. - #[pallet::weight( - T::WeightInfo::unlock_set(T::MaxVotes::get()) - .max(T::WeightInfo::unlock_remove(T::MaxVotes::get())) - )] - pub fn unlock(origin: OriginFor, target: T::AccountId) -> DispatchResult { + #[pallet::call_index(13)] + #[pallet::weight(T::WeightInfo::unlock_set(T::MaxVotes::get()).max(T::WeightInfo::unlock_remove(T::MaxVotes::get())))] + pub fn unlock(origin: OriginFor, target: AccountIdLookupOf) -> DispatchResult { ensure_signed(origin)?; + let target = T::Lookup::lookup(target)?; Self::update_lock(&target); Ok(()) } @@ -1155,6 +952,7 @@ pub mod pallet { /// /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::remove_vote(T::MaxVotes::get()))] pub fn remove_vote(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { let who = ensure_signed(origin)?; @@ -1176,29 +974,20 @@ pub mod pallet { /// /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::remove_other_vote(T::MaxVotes::get()))] pub fn remove_other_vote( origin: OriginFor, - target: T::AccountId, + target: AccountIdLookupOf, index: ReferendumIndex, ) -> DispatchResult { let who = ensure_signed(origin)?; + let target = T::Lookup::lookup(target)?; let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; Self::try_remove_vote(&target, index, scope)?; Ok(()) } - /// Enact a proposal from a referendum. For now we just make the weight be the maximum. - #[pallet::weight(T::BlockWeights::get().max_block)] - pub fn enact_proposal( - origin: OriginFor, - proposal_hash: T::Hash, - index: ReferendumIndex, - ) -> DispatchResult { - ensure_root(origin)?; - Self::do_enact_proposal(proposal_hash, index) - } - /// Permanently place a proposal into the blacklist. This prevents it from ever being /// proposed again. /// @@ -1214,21 +1003,22 @@ pub mod pallet { /// /// Weight: `O(p)` (though as this is an high-privilege dispatch, we assume it has a /// reasonable value). - #[pallet::weight((T::WeightInfo::blacklist(T::MaxProposals::get()), DispatchClass::Operational))] + #[pallet::call_index(16)] + #[pallet::weight((T::WeightInfo::blacklist(), DispatchClass::Operational))] pub fn blacklist( origin: OriginFor, - proposal_hash: T::Hash, + proposal_hash: H256, maybe_ref_index: Option, ) -> DispatchResult { T::BlacklistOrigin::ensure_origin(origin)?; // Insert the proposal into the blacklist. - let permanent = (T::BlockNumber::max_value(), Vec::::new()); + let permanent = (T::BlockNumber::max_value(), BoundedVec::::default()); Blacklist::::insert(&proposal_hash, permanent); // Remove the queued proposal, if it's there. PublicProps::::mutate(|props| { - if let Some(index) = props.iter().position(|p| p.1 == proposal_hash) { + if let Some(index) = props.iter().position(|p| p.1.hash() == proposal_hash) { let (prop_index, ..) = props.remove(index); if let Some((whos, amount)) = DepositOf::::take(prop_index) { for who in whos.into_iter() { @@ -1239,20 +1029,20 @@ pub mod pallet { }); // Remove the external queued referendum, if it's there. - if matches!(NextExternal::::get(), Some((h, ..)) if h == proposal_hash) { + if matches!(NextExternal::::get(), Some((p, ..)) if p.hash() == proposal_hash) { NextExternal::::kill(); } // Remove the referendum, if it's there. if let Some(ref_index) = maybe_ref_index { if let Ok(status) = Self::referendum_status(ref_index) { - if status.proposal_hash == proposal_hash { + if status.proposal.hash() == proposal_hash { Self::internal_cancel_referendum(ref_index); } } } - Self::deposit_event(Event::::Blacklisted(proposal_hash)); + Self::deposit_event(Event::::Blacklisted { proposal_hash }); Ok(()) } @@ -1263,7 +1053,8 @@ pub mod pallet { /// - `prop_index`: The index of the proposal to cancel. /// /// Weight: `O(p)` where `p = PublicProps::::decode_len()` - #[pallet::weight(T::WeightInfo::cancel_proposal(T::MaxProposals::get()))] + #[pallet::call_index(17)] + #[pallet::weight(T::WeightInfo::cancel_proposal())] pub fn cancel_proposal( origin: OriginFor, #[pallet::compact] prop_index: PropIndex, @@ -1277,24 +1068,44 @@ pub mod pallet { } } + Self::deposit_event(Event::::ProposalCanceled { prop_index }); Ok(()) } } } +pub trait EncodeInto: Encode { + fn encode_into + Default>(&self) -> T { + let mut t = T::default(); + self.using_encoded(|data| { + if data.len() <= t.as_mut().len() { + t.as_mut()[0..data.len()].copy_from_slice(data); + } else { + // encoded self is too big to fit into a T. hash it and use the first bytes of that + // instead. + let hash = sp_io::hashing::blake2_256(data); + let l = t.as_mut().len().min(hash.len()); + t.as_mut()[0..l].copy_from_slice(&hash[0..l]); + } + }); + t + } +} +impl EncodeInto for T {} + impl Pallet { // exposed immutables. /// Get the amount locked in support of `proposal`; `None` if proposal isn't a valid proposal /// index. pub fn backing_for(proposal: PropIndex) -> Option> { - Self::deposit_of(proposal).map(|(l, d)| d * (l.len() as u32).into()) + Self::deposit_of(proposal).map(|(l, d)| d.saturating_mul((l.len() as u32).into())) } /// Get all referenda ready for tally at block `n`. pub fn maturing_referenda_at( n: T::BlockNumber, - ) -> Vec<(ReferendumIndex, ReferendumStatus>)> { + ) -> Vec<(ReferendumIndex, ReferendumStatus, BalanceOf>)> { let next = Self::lowest_unbaked(); let last = Self::referendum_count(); Self::maturing_referenda_at_inner(n, next..last) @@ -1303,7 +1114,7 @@ impl Pallet { fn maturing_referenda_at_inner( n: T::BlockNumber, range: core::ops::Range, - ) -> Vec<(ReferendumIndex, ReferendumStatus>)> { + ) -> Vec<(ReferendumIndex, ReferendumStatus, BalanceOf>)> { range .into_iter() .map(|i| (i, Self::referendum_info(i))) @@ -1319,13 +1130,13 @@ impl Pallet { /// Start a referendum. pub fn internal_start_referendum( - proposal_hash: T::Hash, + proposal: BoundedCallOf, threshold: VoteThreshold, delay: T::BlockNumber, ) -> ReferendumIndex { >::inject_referendum( - >::block_number() + T::VotingPeriod::get(), - proposal_hash, + >::block_number().saturating_add(T::VotingPeriod::get()), + proposal, threshold, delay, ) @@ -1333,7 +1144,7 @@ impl Pallet { /// Remove a referendum. pub fn internal_cancel_referendum(ref_index: ReferendumIndex) { - Self::deposit_event(Event::::Cancelled(ref_index)); + Self::deposit_event(Event::::Cancelled { ref_index }); ReferendumInfoOf::::remove(ref_index); } @@ -1341,8 +1152,8 @@ impl Pallet { /// Ok if the given referendum is active, Err otherwise fn ensure_ongoing( - r: ReferendumInfo>, - ) -> Result>, DispatchError> { + r: ReferendumInfo, BalanceOf>, + ) -> Result, BalanceOf>, DispatchError> { match r { ReferendumInfo::Ongoing(s) => Ok(s), _ => Err(Error::::ReferendumInvalid.into()), @@ -1351,7 +1162,7 @@ impl Pallet { fn referendum_status( ref_index: ReferendumIndex, - ) -> Result>, DispatchError> { + ) -> Result, BalanceOf>, DispatchError> { let info = ReferendumInfoOf::::get(ref_index).ok_or(Error::::ReferendumInvalid)?; Self::ensure_ongoing(info) } @@ -1376,13 +1187,12 @@ impl Pallet { votes[i].1 = vote; }, Err(i) => { - ensure!( - votes.len() as u32 <= T::MaxVotes::get(), - Error::::MaxVotesReached - ); - votes.insert(i, (ref_index, vote)); + votes + .try_insert(i, (ref_index, vote)) + .map_err(|_| Error::::MaxVotesReached)?; }, } + Self::deposit_event(Event::::Voted { voter: who.clone(), ref_index, vote }); // Shouldn't be possible to fail, but we handle it gracefully. status.tally.add(vote).ok_or(ArithmeticError::Overflow)?; if let Some(approve) = vote.as_standard() { @@ -1429,7 +1239,9 @@ impl Pallet { }, Some(ReferendumInfo::Finished { end, approved }) => { if let Some((lock_periods, balance)) = votes[i].1.locked_if(approved) { - let unlock_at = end + T::VoteLockingPeriod::get() * lock_periods.into(); + let unlock_at = end.saturating_add( + T::VoteLockingPeriod::get().saturating_mul(lock_periods.into()), + ); let now = frame_system::Pallet::::block_number(); if now < unlock_at { ensure!( @@ -1518,9 +1330,16 @@ impl Pallet { }; sp_std::mem::swap(&mut old, voting); match old { - Voting::Delegating { balance, target, conviction, delegations, prior, .. } => { + Voting::Delegating { + balance, target, conviction, delegations, mut prior, .. + } => { // remove any delegation votes to our current target. Self::reduce_upstream_delegation(&target, conviction.votes(balance)); + let now = frame_system::Pallet::::block_number(); + let lock_periods = conviction.lock_periods().into(); + let unlock_block = now + .saturating_add(T::VoteLockingPeriod::get().saturating_mul(lock_periods)); + prior.accumulate(unlock_block, balance); voting.set_common(delegations, prior); }, Voting::Direct { votes, delegations, prior } => { @@ -1535,7 +1354,7 @@ impl Pallet { T::Currency::extend_lock(DEMOCRACY_ID, &who, balance, WithdrawReasons::TRANSFER); Ok(votes) })?; - Self::deposit_event(Event::::Delegated(who, target)); + Self::deposit_event(Event::::Delegated { who, target }); Ok(votes) } @@ -1553,7 +1372,9 @@ impl Pallet { Self::reduce_upstream_delegation(&target, conviction.votes(balance)); let now = frame_system::Pallet::::block_number(); let lock_periods = conviction.lock_periods().into(); - prior.accumulate(now + T::VoteLockingPeriod::get() * lock_periods, balance); + let unlock_block = now + .saturating_add(T::VoteLockingPeriod::get().saturating_mul(lock_periods)); + prior.accumulate(unlock_block, balance); voting.set_common(delegations, prior); Ok(votes) @@ -1561,7 +1382,7 @@ impl Pallet { Voting::Direct { .. } => Err(Error::::NotDelegating.into()), } })?; - Self::deposit_event(Event::::Undelegated(who)); + Self::deposit_event(Event::::Undelegated { account: who }); Ok(votes) } @@ -1582,17 +1403,17 @@ impl Pallet { /// Start a referendum fn inject_referendum( end: T::BlockNumber, - proposal_hash: T::Hash, + proposal: BoundedCallOf, threshold: VoteThreshold, delay: T::BlockNumber, ) -> ReferendumIndex { let ref_index = Self::referendum_count(); ReferendumCount::::put(ref_index + 1); let status = - ReferendumStatus { end, proposal_hash, threshold, delay, tally: Default::default() }; + ReferendumStatus { end, proposal, threshold, delay, tally: Default::default() }; let item = ReferendumInfo::Ongoing(status); >::insert(ref_index, item); - Self::deposit_event(Event::::Started(ref_index, threshold)); + Self::deposit_event(Event::::Started { ref_index, threshold }); ref_index } @@ -1612,14 +1433,14 @@ impl Pallet { LastTabledWasExternal::::put(true); Self::deposit_event(Event::::ExternalTabled); Self::inject_referendum( - now + T::VotingPeriod::get(), + now.saturating_add(T::VotingPeriod::get()), proposal, threshold, T::EnactmentPeriod::get(), ); Ok(()) } else { - Err(Error::::NoneWaiting)? + return Err(Error::::NoneWaiting.into()) } } @@ -1628,19 +1449,19 @@ impl Pallet { let mut public_props = Self::public_props(); if let Some((winner_index, _)) = public_props.iter().enumerate().max_by_key( // defensive only: All current public proposals have an amount locked - |x| Self::backing_for((x.1).0).unwrap_or_else(Zero::zero), + |x| Self::backing_for((x.1).0).defensive_unwrap_or_else(Zero::zero), ) { let (prop_index, proposal, _) = public_props.swap_remove(winner_index); >::put(public_props); if let Some((depositors, deposit)) = >::take(prop_index) { // refund depositors - for d in &depositors { + for d in depositors.iter() { T::Currency::unreserve(d, deposit); } - Self::deposit_event(Event::::Tabled(prop_index, deposit, depositors)); + Self::deposit_event(Event::::Tabled { proposal_index: prop_index, deposit }); Self::inject_referendum( - now + T::VotingPeriod::get(), + now.saturating_add(T::VotingPeriod::get()), proposal, VoteThreshold::SuperMajorityApprove, T::EnactmentPeriod::get(), @@ -1648,115 +1469,105 @@ impl Pallet { } Ok(()) } else { - Err(Error::::NoneWaiting)? - } - } - - fn do_enact_proposal(proposal_hash: T::Hash, index: ReferendumIndex) -> DispatchResult { - let preimage = >::take(&proposal_hash); - if let Some(PreimageStatus::Available { data, provider, deposit, .. }) = preimage { - if let Ok(proposal) = T::Proposal::decode(&mut &data[..]) { - let err_amount = T::Currency::unreserve(&provider, deposit); - debug_assert!(err_amount.is_zero()); - Self::deposit_event(Event::::PreimageUsed(proposal_hash, provider, deposit)); - - let res = proposal - .dispatch(frame_system::RawOrigin::Root.into()) - .map(|_| ()) - .map_err(|e| e.error); - Self::deposit_event(Event::::Executed(index, res)); - - Ok(()) - } else { - T::Slash::on_unbalanced(T::Currency::slash_reserved(&provider, deposit).0); - Self::deposit_event(Event::::PreimageInvalid(proposal_hash, index)); - Err(Error::::PreimageInvalid.into()) - } - } else { - Self::deposit_event(Event::::PreimageMissing(proposal_hash, index)); - Err(Error::::PreimageMissing.into()) + return Err(Error::::NoneWaiting.into()) } } fn bake_referendum( now: T::BlockNumber, index: ReferendumIndex, - status: ReferendumStatus>, - ) -> Result { + status: ReferendumStatus, BalanceOf>, + ) -> bool { let total_issuance = T::Currency::total_issuance(); let approved = status.threshold.approved(status.tally, total_issuance); if approved { - Self::deposit_event(Event::::Passed(index)); - if status.delay.is_zero() { - let _ = Self::do_enact_proposal(status.proposal_hash, index); - } else { - let when = now + status.delay; - // Note that we need the preimage now. - Preimages::::mutate_exists( - &status.proposal_hash, - |maybe_pre| match *maybe_pre { - Some(PreimageStatus::Available { ref mut expiry, .. }) => - *expiry = Some(when), - ref mut a => *a = Some(PreimageStatus::Missing(when)), - }, - ); - - if T::Scheduler::schedule_named( - (DEMOCRACY_ID, index).encode(), - DispatchTime::At(when), - None, - 63, - frame_system::RawOrigin::Root.into(), - Call::enact_proposal { proposal_hash: status.proposal_hash, index }.into(), - ) - .is_err() - { - frame_support::print("LOGIC ERROR: bake_referendum/schedule_named failed"); - } + Self::deposit_event(Event::::Passed { ref_index: index }); + // Actually `hold` the proposal now since we didn't hold it when it came in via the + // submit extrinsic and we now know that it will be needed. This will be reversed by + // Scheduler pallet once it is executed which assumes that we will already have placed + // a `hold` on it. + T::Preimages::hold(&status.proposal); + + // Earliest it can be scheduled for is next block. + let when = now.saturating_add(status.delay.max(One::one())); + if T::Scheduler::schedule_named( + (DEMOCRACY_ID, index).encode_into(), + DispatchTime::At(when), + None, + 63, + frame_system::RawOrigin::Root.into(), + status.proposal, + ) + .is_err() + { + frame_support::print("LOGIC ERROR: bake_referendum/schedule_named failed"); } } else { - Self::deposit_event(Event::::NotPassed(index)); + Self::deposit_event(Event::::NotPassed { ref_index: index }); } - Ok(approved) + approved } /// Current era is ending; we should finish up any proposals. /// /// /// # - /// If a referendum is launched or maturing, this will take full block weight. Otherwise: + /// If a referendum is launched or maturing, this will take full block weight if queue is not + /// empty. Otherwise: /// - Complexity: `O(R)` where `R` is the number of unbaked referenda. /// - Db reads: `LastTabledWasExternal`, `NextExternal`, `PublicProps`, `account`, /// `ReferendumCount`, `LowestUnbaked` /// - Db writes: `PublicProps`, `account`, `ReferendumCount`, `DepositOf`, `ReferendumInfoOf` /// - Db reads per R: `DepositOf`, `ReferendumInfoOf` /// # - fn begin_block(now: T::BlockNumber) -> Result { + fn begin_block(now: T::BlockNumber) -> Weight { let max_block_weight = T::BlockWeights::get().max_block; - let mut weight = 0; + let mut weight = Weight::zero(); + + let next = Self::lowest_unbaked(); + let last = Self::referendum_count(); + let r = last.saturating_sub(next); // pick out another public referendum if it's time. if (now % T::LaunchPeriod::get()).is_zero() { - // Errors come from the queue being empty. we don't really care about that, and even if - // we did, there is nothing we can do here. - let _ = Self::launch_next(now); - weight = max_block_weight; + // Errors come from the queue being empty. If the queue is not empty, it will take + // full block weight. + if Self::launch_next(now).is_ok() { + weight = max_block_weight; + } else { + weight.saturating_accrue(T::WeightInfo::on_initialize_base_with_launch_period(r)); + } + } else { + weight.saturating_accrue(T::WeightInfo::on_initialize_base(r)); } - let next = Self::lowest_unbaked(); - let last = Self::referendum_count(); - let r = last.saturating_sub(next); - weight = weight.saturating_add(T::WeightInfo::on_initialize_base(r)); // tally up votes for any expiring referenda. for (index, info) in Self::maturing_referenda_at_inner(now, next..last).into_iter() { - let approved = Self::bake_referendum(now, index, info)?; + let approved = Self::bake_referendum(now, index, info); ReferendumInfoOf::::insert(index, ReferendumInfo::Finished { end: now, approved }); weight = max_block_weight; } - Ok(weight) + // Notes: + // * We don't consider the lowest unbaked to be the last maturing in case some referenda + // have a longer voting period than others. + // * The iteration here shouldn't trigger any storage read that are not in cache, due to + // `maturing_referenda_at_inner` having already read them. + // * We shouldn't iterate more than `LaunchPeriod/VotingPeriod + 1` times because the number + // of unbaked referendum is bounded by this number. In case those number have changed in a + // runtime upgrade the formula should be adjusted but the bound should still be sensible. + >::mutate(|ref_index| { + while *ref_index < last && + Self::referendum_info(*ref_index) + .map_or(true, |info| matches!(info, ReferendumInfo::Finished { .. })) + { + *ref_index += 1 + } + }); + + weight } /// Reads the length of account in DepositOf without getting the complete value in the runtime. @@ -1767,125 +1578,13 @@ impl Pallet { // `Compact`. decode_compact_u32_at(&>::hashed_key_for(proposal)) } - - /// Check that pre image exists and its value is variant `PreimageStatus::Missing`. - /// - /// This check is done without getting the complete value in the runtime to avoid copying a big - /// value in the runtime. - fn check_pre_image_is_missing(proposal_hash: T::Hash) -> DispatchResult { - // To decode the enum variant we only need the first byte. - let mut buf = [0u8; 1]; - let key = >::hashed_key_for(proposal_hash); - let bytes = - sp_io::storage::read(&key, &mut buf, 0).ok_or_else(|| Error::::NotImminent)?; - // The value may be smaller that 1 byte. - let mut input = &buf[0..buf.len().min(bytes as usize)]; - - match input.read_byte() { - Ok(0) => Ok(()), // PreimageStatus::Missing is variant 0 - Ok(1) => Err(Error::::DuplicatePreimage.into()), - _ => { - sp_runtime::print("Failed to decode `PreimageStatus` variant"); - Err(Error::::NotImminent.into()) - }, - } - } - - /// Check that pre image exists, its value is variant `PreimageStatus::Available` and decode - /// the length of `data: Vec` fields. - /// - /// This check is done without getting the complete value in the runtime to avoid copying a big - /// value in the runtime. - /// - /// If the pre image is missing variant or doesn't exist then the error `PreimageMissing` is - /// returned. - fn pre_image_data_len(proposal_hash: T::Hash) -> Result { - // To decode the `data` field of Available variant we need: - // * one byte for the variant - // * at most 5 bytes to decode a `Compact` - let mut buf = [0u8; 6]; - let key = >::hashed_key_for(proposal_hash); - let bytes = - sp_io::storage::read(&key, &mut buf, 0).ok_or_else(|| Error::::PreimageMissing)?; - // The value may be smaller that 6 bytes. - let mut input = &buf[0..buf.len().min(bytes as usize)]; - - match input.read_byte() { - Ok(1) => (), // Check that input exists and is second variant. - Ok(0) => return Err(Error::::PreimageMissing.into()), - _ => { - sp_runtime::print("Failed to decode `PreimageStatus` variant"); - return Err(Error::::PreimageMissing.into()) - }, - } - - // Decode the length of the vector. - let len = codec::Compact::::decode(&mut input) - .map_err(|_| { - sp_runtime::print("Failed to decode `PreimageStatus` variant"); - DispatchError::from(Error::::PreimageMissing) - })? - .0; - - Ok(len) - } - - // See `note_preimage` - fn note_preimage_inner(who: T::AccountId, encoded_proposal: Vec) -> DispatchResult { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - ensure!(!>::contains_key(&proposal_hash), Error::::DuplicatePreimage); - - let deposit = >::from(encoded_proposal.len() as u32) - .saturating_mul(T::PreimageByteDeposit::get()); - T::Currency::reserve(&who, deposit)?; - - let now = >::block_number(); - let a = PreimageStatus::Available { - data: encoded_proposal, - provider: who.clone(), - deposit, - since: now, - expiry: None, - }; - >::insert(proposal_hash, a); - - Self::deposit_event(Event::::PreimageNoted(proposal_hash, who, deposit)); - - Ok(()) - } - - // See `note_imminent_preimage` - fn note_imminent_preimage_inner( - who: T::AccountId, - encoded_proposal: Vec, - ) -> DispatchResult { - let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); - Self::check_pre_image_is_missing(proposal_hash)?; - let status = Preimages::::get(&proposal_hash).ok_or(Error::::NotImminent)?; - let expiry = status.to_missing_expiry().ok_or(Error::::DuplicatePreimage)?; - - let now = >::block_number(); - let free = >::zero(); - let a = PreimageStatus::Available { - data: encoded_proposal, - provider: who.clone(), - deposit: Zero::zero(), - since: now, - expiry: Some(expiry), - }; - >::insert(proposal_hash, a); - - Self::deposit_event(Event::::PreimageNoted(proposal_hash, who, free)); - - Ok(()) - } } /// Decode `Compact` from the trie at given key. fn decode_compact_u32_at(key: &[u8]) -> Option { // `Compact` takes at most 5 bytes. let mut buf = [0u8; 5]; - let bytes = sp_io::storage::read(&key, &mut buf, 0)?; + let bytes = sp_io::storage::read(key, &mut buf, 0)?; // The value may be smaller than 5 bytes. let mut input = &buf[0..buf.len().min(bytes as usize)]; match codec::Compact::::decode(&mut input) { diff --git a/frame/democracy/src/migrations.rs b/frame/democracy/src/migrations.rs new file mode 100644 index 0000000000000..3ec249c1d981c --- /dev/null +++ b/frame/democracy/src/migrations.rs @@ -0,0 +1,236 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Storage migrations for the preimage pallet. + +use super::*; +use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade, BoundedVec}; +use sp_core::H256; + +/// The log target. +const TARGET: &'static str = "runtime::democracy::migration::v1"; + +/// The original data layout of the democracy pallet without a specific version number. +mod v0 { + use super::*; + + #[storage_alias] + pub type PublicProps = StorageValue< + Pallet, + Vec<(PropIndex, ::Hash, ::AccountId)>, + ValueQuery, + >; + + #[storage_alias] + pub type NextExternal = + StorageValue, (::Hash, VoteThreshold)>; + + #[cfg(feature = "try-runtime")] + #[storage_alias] + pub type ReferendumInfoOf = StorageMap< + Pallet, + frame_support::Twox64Concat, + ReferendumIndex, + ReferendumInfo< + ::BlockNumber, + ::Hash, + BalanceOf, + >, + >; +} + +pub mod v1 { + use super::*; + + /// Migration for translating bare `Hash`es into `Bounded`s. + pub struct Migration(sp_std::marker::PhantomData); + + impl> OnRuntimeUpgrade for Migration { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + assert_eq!(StorageVersion::get::>(), 0, "can only upgrade from version 0"); + + let props_count = v0::PublicProps::::get().len(); + log::info!(target: TARGET, "{} public proposals will be migrated.", props_count,); + ensure!(props_count <= T::MaxProposals::get() as usize, "too many proposals"); + + let referenda_count = v0::ReferendumInfoOf::::iter().count(); + log::info!(target: TARGET, "{} referenda will be migrated.", referenda_count); + + Ok((props_count as u32, referenda_count as u32).encode()) + } + + #[allow(deprecated)] + fn on_runtime_upgrade() -> Weight { + let mut weight = T::DbWeight::get().reads(1); + if StorageVersion::get::>() != 0 { + log::warn!( + target: TARGET, + "skipping on_runtime_upgrade: executed on wrong storage version.\ + Expected version 0" + ); + return weight + } + + ReferendumInfoOf::::translate( + |index, old: ReferendumInfo>| { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + log::info!(target: TARGET, "migrating referendum #{:?}", &index); + Some(match old { + ReferendumInfo::Ongoing(status) => + ReferendumInfo::Ongoing(ReferendumStatus { + end: status.end, + proposal: Bounded::from_legacy_hash(status.proposal), + threshold: status.threshold, + delay: status.delay, + tally: status.tally, + }), + ReferendumInfo::Finished { approved, end } => + ReferendumInfo::Finished { approved, end }, + }) + }, + ); + + let props = v0::PublicProps::::take() + .into_iter() + .map(|(i, hash, a)| (i, Bounded::from_legacy_hash(hash), a)) + .collect::>(); + let bounded = BoundedVec::<_, T::MaxProposals>::truncate_from(props.clone()); + PublicProps::::put(bounded); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + if props.len() as u32 > T::MaxProposals::get() { + log::error!( + target: TARGET, + "truncated {} public proposals to {}; continuing", + props.len(), + T::MaxProposals::get() + ); + } + + if let Some((hash, threshold)) = v0::NextExternal::::take() { + log::info!(target: TARGET, "migrating next external proposal"); + NextExternal::::put((Bounded::from_legacy_hash(hash), threshold)); + } + + StorageVersion::new(1).put::>(); + + weight.saturating_add(T::DbWeight::get().reads_writes(1, 2)) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + assert_eq!(StorageVersion::get::>(), 1, "must upgrade"); + + let (old_props_count, old_ref_count): (u32, u32) = + Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed"); + let new_props_count = crate::PublicProps::::get().len() as u32; + assert_eq!(new_props_count, old_props_count, "must migrate all public proposals"); + let new_ref_count = crate::ReferendumInfoOf::::iter().count() as u32; + assert_eq!(new_ref_count, old_ref_count, "must migrate all referenda"); + + log::info!( + target: TARGET, + "{} public proposals migrated, {} referenda migrated", + new_props_count, + new_ref_count, + ); + Ok(()) + } + } +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod test { + use super::*; + use crate::{ + tests::{Test as T, *}, + types::*, + }; + use frame_support::bounded_vec; + + #[allow(deprecated)] + #[test] + fn migration_works() { + new_test_ext().execute_with(|| { + assert_eq!(StorageVersion::get::>(), 0); + // Insert some values into the v0 storage: + + // Case 1: Ongoing referendum + let hash = H256::repeat_byte(1); + let status = ReferendumStatus { + end: 1u32.into(), + proposal: hash.clone(), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 1u32.into(), + tally: Tally { ayes: 1u32.into(), nays: 1u32.into(), turnout: 1u32.into() }, + }; + v0::ReferendumInfoOf::::insert(1u32, ReferendumInfo::Ongoing(status)); + + // Case 2: Finished referendum + v0::ReferendumInfoOf::::insert( + 2u32, + ReferendumInfo::Finished { approved: true, end: 123u32.into() }, + ); + + // Case 3: Public proposals + let hash2 = H256::repeat_byte(2); + v0::PublicProps::::put(vec![ + (3u32, hash.clone(), 123u64), + (4u32, hash2.clone(), 123u64), + ]); + + // Case 4: Next external + v0::NextExternal::::put((hash.clone(), VoteThreshold::SuperMajorityApprove)); + + // Migrate. + let state = v1::Migration::::pre_upgrade().unwrap(); + let _weight = v1::Migration::::on_runtime_upgrade(); + v1::Migration::::post_upgrade(state).unwrap(); + // Check that all values got migrated. + + // Case 1: Ongoing referendum + assert_eq!( + ReferendumInfoOf::::get(1u32), + Some(ReferendumInfo::Ongoing(ReferendumStatus { + end: 1u32.into(), + proposal: Bounded::from_legacy_hash(hash), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 1u32.into(), + tally: Tally { ayes: 1u32.into(), nays: 1u32.into(), turnout: 1u32.into() }, + })) + ); + // Case 2: Finished referendum + assert_eq!( + ReferendumInfoOf::::get(2u32), + Some(ReferendumInfo::Finished { approved: true, end: 123u32.into() }) + ); + // Case 3: Public proposals + let props: BoundedVec<_, ::MaxProposals> = bounded_vec![ + (3u32, Bounded::from_legacy_hash(hash), 123u64), + (4u32, Bounded::from_legacy_hash(hash2), 123u64) + ]; + assert_eq!(PublicProps::::get(), props); + // Case 4: Next external + assert_eq!( + NextExternal::::get(), + Some((Bounded::from_legacy_hash(hash), VoteThreshold::SuperMajorityApprove)) + ); + }); + } +} diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index 75104db51b971..41b279035028e 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,10 +19,12 @@ use super::*; use crate as pallet_democracy; -use codec::Encode; use frame_support::{ assert_noop, assert_ok, ord_parameter_types, parameter_types, - traits::{Contains, GenesisBuild, OnInitialize, SortedMembers}, + traits::{ + ConstU32, ConstU64, Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, + SortedMembers, StorePreimage, + }, weights::Weight, }; use frame_system::{EnsureRoot, EnsureSignedBy}; @@ -33,14 +35,12 @@ use sp_runtime::{ traits::{BadOrigin, BlakeTwo256, IdentityLookup}, Perbill, }; - mod cancellation; mod decoders; mod delegation; mod external_proposing; mod fast_tracking; mod lock_voting; -mod preimage; mod public_proposals; mod scheduling; mod voting; @@ -50,8 +50,6 @@ const NAY: Vote = Vote { aye: false, conviction: Conviction::None }; const BIG_AYE: Vote = Vote { aye: true, conviction: Conviction::Locked1x }; const BIG_NAY: Vote = Vote { aye: false, conviction: Conviction::Locked1x }; -const MAX_PROPOSALS: u32 = 100; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -63,40 +61,42 @@ frame_support::construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Scheduler: pallet_scheduler::{Pallet, Call, Storage, Config, Event}, + Preimage: pallet_preimage, + Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, Democracy: pallet_democracy::{Pallet, Call, Storage, Config, Event}, } ); // Test that a fitlered call can be dispatched. pub struct BaseFilter; -impl Contains for BaseFilter { - fn contains(call: &Call) -> bool { - !matches!(call, &Call::Balances(pallet_balances::Call::set_balance { .. })) +impl Contains for BaseFilter { + fn contains(call: &RuntimeCall) -> bool { + !matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. })) } } parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1_000_000); + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + ); } impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; - type BlockWeights = (); + type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -105,45 +105,46 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; } + +impl pallet_preimage::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type ManagerOrigin = EnsureRoot; + type BaseDeposit = ConstU64<0>; + type ByteDeposit = ConstU64<0>; +} + impl pallet_scheduler::Config for Test { - type Event = Event; - type Origin = Origin; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; type PalletsOrigin = OriginCaller; - type Call = Call; + type RuntimeCall = RuntimeCall; type MaximumWeight = MaximumSchedulerWeight; type ScheduleOrigin = EnsureRoot; - type MaxScheduledPerBlock = (); + type MaxScheduledPerBlock = ConstU32<100>; type WeightInfo = (); + type OriginPrivilegeCmp = EqualPrivilegeOnly; + type Preimages = (); } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxLocks: u32 = 10; -} + impl pallet_balances::Config for Test { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; - type MaxLocks = MaxLocks; + type MaxLocks = ConstU32<10>; type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } parameter_types! { - pub const LaunchPeriod: u64 = 2; - pub const VotingPeriod: u64 = 2; - pub const FastTrackVotingPeriod: u64 = 2; - pub const MinimumDeposit: u64 = 1; - pub const EnactmentPeriod: u64 = 2; - pub const VoteLockingPeriod: u64 = 3; - pub const CooloffPeriod: u64 = 2; - pub const MaxVotes: u32 = 100; - pub const MaxProposals: u32 = MAX_PROPOSALS; pub static PreimageByteDeposit: u64 = 0; pub static InstantAllowed: bool = false; } @@ -165,15 +166,16 @@ impl SortedMembers for OneToFive { } impl Config for Test { - type Proposal = Call; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = pallet_balances::Pallet; - type EnactmentPeriod = EnactmentPeriod; - type LaunchPeriod = LaunchPeriod; - type VotingPeriod = VotingPeriod; - type VoteLockingPeriod = VoteLockingPeriod; - type FastTrackVotingPeriod = FastTrackVotingPeriod; - type MinimumDeposit = MinimumDeposit; + type EnactmentPeriod = ConstU64<2>; + type LaunchPeriod = ConstU64<2>; + type VotingPeriod = ConstU64<2>; + type VoteLockingPeriod = ConstU64<3>; + type FastTrackVotingPeriod = ConstU64<2>; + type MinimumDeposit = ConstU64<1>; + type MaxDeposits = ConstU32<1000>; + type MaxBlacklisted = ConstU32<5>; type ExternalOrigin = EnsureSignedBy; type ExternalMajorityOrigin = EnsureSignedBy; type ExternalDefaultOrigin = EnsureSignedBy; @@ -182,17 +184,16 @@ impl Config for Test { type BlacklistOrigin = EnsureRoot; type CancelProposalOrigin = EnsureRoot; type VetoOrigin = EnsureSignedBy; - type CooloffPeriod = CooloffPeriod; - type PreimageByteDeposit = PreimageByteDeposit; + type CooloffPeriod = ConstU64<2>; type Slash = (); type InstantOrigin = EnsureSignedBy; type InstantAllowed = InstantAllowed; type Scheduler = Scheduler; - type MaxVotes = MaxVotes; - type OperationalPreimageOrigin = EnsureSignedBy; + type MaxVotes = ConstU32<100>; type PalletsOrigin = OriginCaller; type WeightInfo = (); - type MaxProposals = MaxProposals; + type MaxProposals = ConstU32<100>; + type Preimages = Preimage; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -210,12 +211,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext } -/// Execute the function two times, with `true` and with `false`. -pub fn new_test_ext_execute_with_cond(execute: impl FnOnce(bool) -> () + Clone) { - new_test_ext().execute_with(|| (execute.clone())(false)); - new_test_ext().execute_with(|| execute(true)); -} - #[test] fn params_should_work() { new_test_ext().execute_with(|| { @@ -225,46 +220,28 @@ fn params_should_work() { }); } -fn set_balance_proposal(value: u64) -> Vec { - Call::Balances(pallet_balances::Call::set_balance { who: 42, new_free: value, new_reserved: 0 }) - .encode() +fn set_balance_proposal(value: u64) -> BoundedCallOf { + let inner = pallet_balances::Call::set_balance { who: 42, new_free: value, new_reserved: 0 }; + let outer = RuntimeCall::Balances(inner); + Preimage::bound(outer).unwrap() } #[test] fn set_balance_proposal_is_correctly_filtered_out() { for i in 0..10 { - let call = Call::decode(&mut &set_balance_proposal(i)[..]).unwrap(); + let call = Preimage::realize(&set_balance_proposal(i)).unwrap().0; assert!(!::BaseCallFilter::contains(&call)); } } -fn set_balance_proposal_hash(value: u64) -> H256 { - BlakeTwo256::hash(&set_balance_proposal(value)[..]) -} - -fn set_balance_proposal_hash_and_note(value: u64) -> H256 { - let p = set_balance_proposal(value); - let h = BlakeTwo256::hash(&p[..]); - match Democracy::note_preimage(Origin::signed(6), p) { - Ok(_) => (), - Err(x) if x == Error::::DuplicatePreimage.into() => (), - Err(x) => panic!("{:?}", x), - } - h -} - fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { - Democracy::propose(Origin::signed(who), set_balance_proposal_hash(value), delay) -} - -fn propose_set_balance_and_note(who: u64, value: u64, delay: u64) -> DispatchResult { - Democracy::propose(Origin::signed(who), set_balance_proposal_hash_and_note(value), delay) + Democracy::propose(RuntimeOrigin::signed(who), set_balance_proposal(value), delay) } fn next_block() { System::set_block_number(System::block_number() + 1); Scheduler::on_initialize(System::block_number()); - assert!(Democracy::begin_block(System::block_number()).is_ok()); + Democracy::begin_block(System::block_number()); } fn fast_forward_to(n: u64) { @@ -275,7 +252,7 @@ fn fast_forward_to(n: u64) { fn begin_referendum() -> ReferendumIndex { System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); + assert_ok!(propose_set_balance(1, 2, 1)); fast_forward_to(2); 0 } diff --git a/frame/democracy/src/tests/cancellation.rs b/frame/democracy/src/tests/cancellation.rs index c2bd725ce934a..ff046d612c026 100644 --- a/frame/democracy/src/tests/cancellation.rs +++ b/frame/democracy/src/tests/cancellation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,68 +24,51 @@ fn cancel_referendum_should_work() { new_test_ext().execute_with(|| { let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0, ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::cancel_referendum(Origin::root(), r.into())); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); + assert_ok!(Democracy::cancel_referendum(RuntimeOrigin::root(), r.into())); + assert_eq!(Democracy::lowest_unbaked(), 0); next_block(); + next_block(); + assert_eq!(Democracy::lowest_unbaked(), 1); + assert_eq!(Democracy::lowest_unbaked(), Democracy::referendum_count()); assert_eq!(Balances::free_balance(42), 0); }); } -#[test] -fn cancel_queued_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - // start of 2 => next referendum scheduled. - fast_forward_to(2); - - assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); - - fast_forward_to(4); - - assert!(pallet_scheduler::Agenda::::get(6)[0].is_some()); - - assert_noop!(Democracy::cancel_queued(Origin::root(), 1), Error::::ProposalMissing); - assert_ok!(Democracy::cancel_queued(Origin::root(), 0)); - assert!(pallet_scheduler::Agenda::::get(6)[0].is_none()); - }); -} - #[test] fn emergency_cancel_should_work() { new_test_ext().execute_with(|| { System::set_block_number(0); let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 2, ); assert!(Democracy::referendum_status(r).is_ok()); - assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); - assert_ok!(Democracy::emergency_cancel(Origin::signed(4), r)); + assert_noop!(Democracy::emergency_cancel(RuntimeOrigin::signed(3), r), BadOrigin); + assert_ok!(Democracy::emergency_cancel(RuntimeOrigin::signed(4), r)); assert!(Democracy::referendum_info(r).is_none()); // some time later... let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 2, ); assert!(Democracy::referendum_status(r).is_ok()); assert_noop!( - Democracy::emergency_cancel(Origin::signed(4), r), + Democracy::emergency_cancel(RuntimeOrigin::signed(4), r), Error::::AlreadyCanceled, ); }); diff --git a/frame/democracy/src/tests/decoders.rs b/frame/democracy/src/tests/decoders.rs index 3c1729c4355c0..1c8b9c3d980f9 100644 --- a/frame/democracy/src/tests/decoders.rs +++ b/frame/democracy/src/tests/decoders.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,10 @@ //! The for various partial storage decoders use super::*; -use frame_support::storage::{migration, unhashed}; +use frame_support::{ + storage::{migration, unhashed}, + BoundedVec, +}; #[test] fn test_decode_compact_u32_at() { @@ -42,7 +45,8 @@ fn test_decode_compact_u32_at() { fn len_of_deposit_of() { new_test_ext().execute_with(|| { for l in vec![0, 1, 200, 1000] { - let value: (Vec, u64) = ((0..l).map(|_| Default::default()).collect(), 3u64); + let value: (BoundedVec, u64) = + ((0..l).map(|_| Default::default()).collect::>().try_into().unwrap(), 3u64); DepositOf::::insert(2, value); assert_eq!(Democracy::len_of_deposit_of(2), Some(l)); } @@ -51,35 +55,3 @@ fn len_of_deposit_of() { assert_eq!(Democracy::len_of_deposit_of(2), None); }) } - -#[test] -fn pre_image() { - new_test_ext().execute_with(|| { - let key = Default::default(); - let missing = PreimageStatus::Missing(0); - Preimages::::insert(key, missing); - assert_noop!(Democracy::pre_image_data_len(key), Error::::PreimageMissing); - assert_eq!(Democracy::check_pre_image_is_missing(key), Ok(())); - - Preimages::::remove(key); - assert_noop!(Democracy::pre_image_data_len(key), Error::::PreimageMissing); - assert_noop!(Democracy::check_pre_image_is_missing(key), Error::::NotImminent); - - for l in vec![0, 10, 100, 1000u32] { - let available = PreimageStatus::Available { - data: (0..l).map(|i| i as u8).collect(), - provider: 0, - deposit: 0, - since: 0, - expiry: None, - }; - - Preimages::::insert(key, available); - assert_eq!(Democracy::pre_image_data_len(key), Ok(l)); - assert_noop!( - Democracy::check_pre_image_is_missing(key), - Error::::DuplicatePreimage - ); - } - }) -} diff --git a/frame/democracy/src/tests/delegation.rs b/frame/democracy/src/tests/delegation.rs index d3afa1c13f90b..bca7cb9524112 100644 --- a/frame/democracy/src/tests/delegation.rs +++ b/frame/democracy/src/tests/delegation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,38 +24,38 @@ fn single_proposal_should_work_with_delegation() { new_test_ext().execute_with(|| { System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); + assert_ok!(propose_set_balance(1, 2, 1)); fast_forward_to(2); // Delegate first vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::None, 20)); let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); // Delegate a second vote. - assert_ok!(Democracy::delegate(Origin::signed(3), 1, Conviction::None, 30)); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(3), 1, Conviction::None, 30)); assert_eq!(tally(r), Tally { ayes: 6, nays: 0, turnout: 60 }); // Reduce first vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 10)); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::None, 10)); assert_eq!(tally(r), Tally { ayes: 5, nays: 0, turnout: 50 }); // Second vote delegates to first; we don't do tiered delegation, so it doesn't get used. - assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(3), 2, Conviction::None, 30)); assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 20 }); // Main voter cancels their vote - assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(1), r)); assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); // First delegator delegates half funds with conviction; nothing changes yet. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked1x, 10)); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::Locked1x, 10)); assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); // Main voter reinstates their vote - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); assert_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 20 }); }); } @@ -64,7 +64,7 @@ fn single_proposal_should_work_with_delegation() { fn self_delegation_not_allowed() { new_test_ext().execute_with(|| { assert_noop!( - Democracy::delegate(Origin::signed(1), 1, Conviction::None, 10), + Democracy::delegate(RuntimeOrigin::signed(1), 1, Conviction::None, 10), Error::::Nonsense, ); }); @@ -75,19 +75,19 @@ fn cyclic_delegation_should_unwind() { new_test_ext().execute_with(|| { System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); + assert_ok!(propose_set_balance(1, 2, 1)); fast_forward_to(2); // Check behavior with cycle. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::None, 30)); - assert_ok!(Democracy::delegate(Origin::signed(1), 3, Conviction::None, 10)); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(3), 2, Conviction::None, 30)); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(1), 3, Conviction::None, 10)); let r = 0; - assert_ok!(Democracy::undelegate(Origin::signed(3))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); - assert_ok!(Democracy::undelegate(Origin::signed(1))); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); + assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(3))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(3), r, aye(3))); + assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, nay(1))); // Delegated vote is counted. assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 60 }); @@ -100,18 +100,18 @@ fn single_proposal_should_work_with_vote_and_delegation() { new_test_ext().execute_with(|| { System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); + assert_ok!(propose_set_balance(1, 2, 1)); fast_forward_to(2); let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, nay(2))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, nay(2))); assert_eq!(tally(r), Tally { ayes: 1, nays: 2, turnout: 30 }); // Delegate vote. - assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(2), r)); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::None, 20)); // Delegated vote replaces the explicit vote. assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); }); @@ -122,15 +122,15 @@ fn single_proposal_should_work_with_undelegation() { new_test_ext().execute_with(|| { System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); + assert_ok!(propose_set_balance(1, 2, 1)); // Delegate and undelegate vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); - assert_ok!(Democracy::undelegate(Origin::signed(2))); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(2))); fast_forward_to(2); let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); // Delegated vote is not counted. assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); @@ -143,11 +143,11 @@ fn single_proposal_should_work_with_delegation_and_vote() { new_test_ext().execute_with(|| { let r = begin_referendum(); // Delegate, undelegate and vote. - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::None, 20)); assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); - assert_ok!(Democracy::undelegate(Origin::signed(2))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); + assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(2))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, aye(2))); // Delegated vote is not counted. assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); }); @@ -158,9 +158,9 @@ fn conviction_should_be_honored_in_delegation() { // If transactor voted, delegated vote is overwritten. new_test_ext().execute_with(|| { let r = begin_referendum(); - // Delegate, undelegate and vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + // Delegate and vote. + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::Locked6x, 20)); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); // Delegated vote is huge. assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 30 }); }); @@ -171,9 +171,52 @@ fn split_vote_delegation_should_be_ignored() { // If transactor voted, delegated vote is overwritten. new_test_ext().execute_with(|| { let r = begin_referendum(); - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::Locked6x, 20)); - assert_ok!(Democracy::vote(Origin::signed(1), r, AccountVote::Split { aye: 10, nay: 0 })); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::Locked6x, 20)); + assert_ok!(Democracy::vote( + RuntimeOrigin::signed(1), + r, + AccountVote::Split { aye: 10, nay: 0 } + )); // Delegated vote is huge. assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); }); } + +#[test] +fn redelegation_keeps_lock() { + // If transactor voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + let r = begin_referendum(); + // Delegate and vote. + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 1, Conviction::Locked6x, 20)); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); + // Delegated vote is huge. + assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 30 }); + + let mut prior_lock = vote::PriorLock::default(); + + // Locked balance of delegator exists + assert_eq!(VotingOf::::get(2).locked_balance(), 20); + assert_eq!(VotingOf::::get(2).prior(), &prior_lock); + + // Delegate someone else at a lower conviction and amount + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(2), 3, Conviction::None, 10)); + + // 6x prior should appear w/ locked balance. + prior_lock.accumulate(98, 20); + assert_eq!(VotingOf::::get(2).prior(), &prior_lock); + assert_eq!(VotingOf::::get(2).locked_balance(), 20); + // Unlock shouldn't work + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(2), 2)); + assert_eq!(VotingOf::::get(2).prior(), &prior_lock); + assert_eq!(VotingOf::::get(2).locked_balance(), 20); + + fast_forward_to(100); + + // Now unlock can remove the prior lock and reduce the locked amount. + assert_eq!(VotingOf::::get(2).prior(), &prior_lock); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(2), 2)); + assert_eq!(VotingOf::::get(2).prior(), &vote::PriorLock::default()); + assert_eq!(VotingOf::::get(2).locked_balance(), 10); + }); +} diff --git a/frame/democracy/src/tests/external_proposing.rs b/frame/democracy/src/tests/external_proposing.rs index 7442964584fa9..4cfdd2aa74a3d 100644 --- a/frame/democracy/src/tests/external_proposing.rs +++ b/frame/democracy/src/tests/external_proposing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,59 +23,50 @@ use super::*; fn veto_external_works() { new_test_ext().execute_with(|| { System::set_block_number(0); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),)); assert!(>::exists()); - let h = set_balance_proposal_hash_and_note(2); - assert_ok!(Democracy::veto_external(Origin::signed(3), h.clone())); + let h = set_balance_proposal(2).hash(); + assert_ok!(Democracy::veto_external(RuntimeOrigin::signed(3), h)); // cancelled. assert!(!>::exists()); // fails - same proposal can't be resubmitted. assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), + Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),), Error::::ProposalBlacklisted ); fast_forward_to(1); // fails as we're still in cooloff period. assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), + Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),), Error::::ProposalBlacklisted ); fast_forward_to(2); // works; as we're out of the cooloff period. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),)); assert!(>::exists()); // 3 can't veto the same thing twice. assert_noop!( - Democracy::veto_external(Origin::signed(3), h.clone()), + Democracy::veto_external(RuntimeOrigin::signed(3), h), Error::::AlreadyVetoed ); // 4 vetoes. - assert_ok!(Democracy::veto_external(Origin::signed(4), h.clone())); + assert_ok!(Democracy::veto_external(RuntimeOrigin::signed(4), h)); // cancelled again. assert!(!>::exists()); fast_forward_to(3); // same proposal fails as we're still in cooloff assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),), + Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2)), Error::::ProposalBlacklisted ); // different proposal works fine. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(3),)); }); } @@ -84,19 +75,16 @@ fn external_blacklisting_should_work() { new_test_ext().execute_with(|| { System::set_block_number(0); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),)); - let hash = set_balance_proposal_hash(2); - assert_ok!(Democracy::blacklist(Origin::root(), hash, None)); + let hash = set_balance_proposal(2).hash(); + assert_ok!(Democracy::blacklist(RuntimeOrigin::root(), hash, None)); fast_forward_to(2); assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash_and_note(2),), + Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2)), Error::::ProposalBlacklisted, ); }); @@ -107,15 +95,12 @@ fn external_referendum_works() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_noop!( - Democracy::external_propose(Origin::signed(1), set_balance_proposal_hash(2),), + Democracy::external_propose(RuntimeOrigin::signed(1), set_balance_proposal(2),), BadOrigin, ); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2),)); assert_noop!( - Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(1),), + Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(1),), Error::::DuplicateProposal ); fast_forward_to(2); @@ -123,7 +108,7 @@ fn external_referendum_works() { Democracy::referendum_status(0), Ok(ReferendumStatus { end: 4, - proposal_hash: set_balance_proposal_hash(2), + proposal: set_balance_proposal(2), threshold: VoteThreshold::SuperMajorityApprove, delay: 2, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, @@ -137,19 +122,19 @@ fn external_majority_referendum_works() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_noop!( - Democracy::external_propose_majority(Origin::signed(1), set_balance_proposal_hash(2)), + Democracy::external_propose_majority(RuntimeOrigin::signed(1), set_balance_proposal(2)), BadOrigin, ); assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) + RuntimeOrigin::signed(3), + set_balance_proposal(2) )); fast_forward_to(2); assert_eq!( Democracy::referendum_status(0), Ok(ReferendumStatus { end: 4, - proposal_hash: set_balance_proposal_hash(2), + proposal: set_balance_proposal(2), threshold: VoteThreshold::SimpleMajority, delay: 2, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, @@ -163,19 +148,19 @@ fn external_default_referendum_works() { new_test_ext().execute_with(|| { System::set_block_number(0); assert_noop!( - Democracy::external_propose_default(Origin::signed(3), set_balance_proposal_hash(2)), + Democracy::external_propose_default(RuntimeOrigin::signed(3), set_balance_proposal(2)), BadOrigin, ); assert_ok!(Democracy::external_propose_default( - Origin::signed(1), - set_balance_proposal_hash_and_note(2) + RuntimeOrigin::signed(1), + set_balance_proposal(2) )); fast_forward_to(2); assert_eq!( Democracy::referendum_status(0), Ok(ReferendumStatus { end: 4, - proposal_hash: set_balance_proposal_hash(2), + proposal: set_balance_proposal(2), threshold: VoteThreshold::SuperMajorityAgainst, delay: 2, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, @@ -188,11 +173,8 @@ fn external_default_referendum_works() { fn external_and_public_interleaving_works() { new_test_ext().execute_with(|| { System::set_block_number(0); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(1), - )); - assert_ok!(propose_set_balance_and_note(6, 2, 2)); + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(1),)); + assert_ok!(propose_set_balance(6, 2, 2)); fast_forward_to(2); @@ -201,17 +183,14 @@ fn external_and_public_interleaving_works() { Democracy::referendum_status(0), Ok(ReferendumStatus { end: 4, - proposal_hash: set_balance_proposal_hash_and_note(1), + proposal: set_balance_proposal(1), threshold: VoteThreshold::SuperMajorityApprove, delay: 2, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(3),)); fast_forward_to(4); @@ -220,7 +199,7 @@ fn external_and_public_interleaving_works() { Democracy::referendum_status(1), Ok(ReferendumStatus { end: 6, - proposal_hash: set_balance_proposal_hash_and_note(2), + proposal: set_balance_proposal(2), threshold: VoteThreshold::SuperMajorityApprove, delay: 2, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, @@ -235,17 +214,14 @@ fn external_and_public_interleaving_works() { Democracy::referendum_status(2), Ok(ReferendumStatus { end: 8, - proposal_hash: set_balance_proposal_hash_and_note(3), + proposal: set_balance_proposal(3), threshold: VoteThreshold::SuperMajorityApprove, delay: 2, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(5), - )); + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(5),)); fast_forward_to(8); @@ -254,18 +230,15 @@ fn external_and_public_interleaving_works() { Democracy::referendum_status(3), Ok(ReferendumStatus { end: 10, - proposal_hash: set_balance_proposal_hash_and_note(5), + proposal: set_balance_proposal(5), threshold: VoteThreshold::SuperMajorityApprove, delay: 2, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); // replenish both - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(7), - )); - assert_ok!(propose_set_balance_and_note(6, 4, 2)); + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(7),)); + assert_ok!(propose_set_balance(6, 4, 2)); fast_forward_to(10); @@ -274,17 +247,17 @@ fn external_and_public_interleaving_works() { Democracy::referendum_status(4), Ok(ReferendumStatus { end: 12, - proposal_hash: set_balance_proposal_hash_and_note(4), + proposal: set_balance_proposal(4), threshold: VoteThreshold::SuperMajorityApprove, delay: 2, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); // replenish public again - assert_ok!(propose_set_balance_and_note(6, 6, 2)); + assert_ok!(propose_set_balance(6, 6, 2)); // cancel external - let h = set_balance_proposal_hash_and_note(7); - assert_ok!(Democracy::veto_external(Origin::signed(3), h)); + let h = set_balance_proposal(7).hash(); + assert_ok!(Democracy::veto_external(RuntimeOrigin::signed(3), h)); fast_forward_to(12); @@ -293,7 +266,7 @@ fn external_and_public_interleaving_works() { Democracy::referendum_status(5), Ok(ReferendumStatus { end: 14, - proposal_hash: set_balance_proposal_hash_and_note(6), + proposal: set_balance_proposal(6), threshold: VoteThreshold::SuperMajorityApprove, delay: 2, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, diff --git a/frame/democracy/src/tests/fast_tracking.rs b/frame/democracy/src/tests/fast_tracking.rs index 9b2f2760bde1c..97bb7a63908ab 100644 --- a/frame/democracy/src/tests/fast_tracking.rs +++ b/frame/democracy/src/tests/fast_tracking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,22 +23,22 @@ use super::*; fn fast_track_referendum_works() { new_test_ext().execute_with(|| { System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); + let h = set_balance_proposal(2).hash(); assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), + Democracy::fast_track(RuntimeOrigin::signed(5), h, 3, 2), Error::::ProposalMissing ); assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) + RuntimeOrigin::signed(3), + set_balance_proposal(2) )); - assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_ok!(Democracy::fast_track(Origin::signed(5), h, 2, 0)); + assert_noop!(Democracy::fast_track(RuntimeOrigin::signed(1), h, 3, 2), BadOrigin); + assert_ok!(Democracy::fast_track(RuntimeOrigin::signed(5), h, 2, 0)); assert_eq!( Democracy::referendum_status(0), Ok(ReferendumStatus { end: 2, - proposal_hash: set_balance_proposal_hash_and_note(2), + proposal: set_balance_proposal(2), threshold: VoteThreshold::SimpleMajority, delay: 0, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, @@ -51,28 +51,32 @@ fn fast_track_referendum_works() { fn instant_referendum_works() { new_test_ext().execute_with(|| { System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); + let h = set_balance_proposal(2).hash(); assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), + Democracy::fast_track(RuntimeOrigin::signed(5), h, 3, 2), Error::::ProposalMissing ); assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) + RuntimeOrigin::signed(3), + set_balance_proposal(2) )); - assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_noop!(Democracy::fast_track(Origin::signed(5), h, 1, 0), BadOrigin); + assert_noop!(Democracy::fast_track(RuntimeOrigin::signed(1), h, 3, 2), BadOrigin); + assert_noop!(Democracy::fast_track(RuntimeOrigin::signed(5), h, 1, 0), BadOrigin); assert_noop!( - Democracy::fast_track(Origin::signed(6), h, 1, 0), + Democracy::fast_track(RuntimeOrigin::signed(6), h, 1, 0), Error::::InstantNotAllowed ); INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true); - assert_ok!(Democracy::fast_track(Origin::signed(6), h, 1, 0)); + assert_noop!( + Democracy::fast_track(RuntimeOrigin::signed(6), h, 0, 0), + Error::::VotingPeriodLow + ); + assert_ok!(Democracy::fast_track(RuntimeOrigin::signed(6), h, 1, 0)); assert_eq!( Democracy::referendum_status(0), Ok(ReferendumStatus { end: 1, - proposal_hash: set_balance_proposal_hash_and_note(2), + proposal: set_balance_proposal(2), threshold: VoteThreshold::SimpleMajority, delay: 0, tally: Tally { ayes: 0, nays: 0, turnout: 0 }, @@ -81,17 +85,68 @@ fn instant_referendum_works() { }); } +#[test] +fn instant_next_block_referendum_backed() { + new_test_ext().execute_with(|| { + // arrange + let start_block_number = 10; + let majority_origin_id = 3; + let instant_origin_id = 6; + let voting_period = 1; + let proposal = set_balance_proposal(2); + let delay = 2; // has no effect on test + + // init + System::set_block_number(start_block_number); + InstantAllowed::set(true); + + // propose with majority origin + assert_ok!(Democracy::external_propose_majority( + RuntimeOrigin::signed(majority_origin_id), + proposal.clone() + )); + + // fast track with instant origin and voting period pointing to the next block + assert_ok!(Democracy::fast_track( + RuntimeOrigin::signed(instant_origin_id), + proposal.hash(), + voting_period, + delay + )); + + // fetch the status of the only referendum at index 0 + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: start_block_number + voting_period, + proposal, + threshold: VoteThreshold::SimpleMajority, + delay, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + + // referendum expected to be baked with the start of the next block + next_block(); + + // assert no active referendums + assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); + // the only referendum in the storage is finished and not approved + assert_eq!( + ReferendumInfoOf::::get(0).unwrap(), + ReferendumInfo::Finished { approved: false, end: start_block_number + voting_period } + ); + }); +} + #[test] fn fast_track_referendum_fails_when_no_simple_majority() { new_test_ext().execute_with(|| { System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2) - )); + let h = set_balance_proposal(2).hash(); + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2))); assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), + Democracy::fast_track(RuntimeOrigin::signed(5), h, 3, 2), Error::::NotSimpleMajority ); }); diff --git a/frame/democracy/src/tests/lock_voting.rs b/frame/democracy/src/tests/lock_voting.rs index 8b80b39c14aab..540198ecf33a1 100644 --- a/frame/democracy/src/tests/lock_voting.rs +++ b/frame/democracy/src/tests/lock_voting.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ //! The tests for functionality concerning locking and lock-voting. use super::*; -use std::convert::TryFrom; fn aye(x: u8, balance: u64) -> AccountVote { AccountVote::Standard { @@ -44,15 +43,15 @@ fn lock_voting_should_work() { System::set_block_number(0); let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0, ); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); - assert_ok!(Democracy::vote(Origin::signed(4), r, aye(2, 40))); - assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, nay(5, 10))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, aye(4, 20))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(3), r, aye(3, 30))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(4), r, aye(2, 40))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, nay(1, 50))); assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); // All balances are currently locked. @@ -60,23 +59,23 @@ fn lock_voting_should_work() { assert_eq!(Balances::locks(i), vec![the_lock(i * 10)]); } - fast_forward_to(2); + fast_forward_to(3); // Referendum passed; 1 and 5 didn't get their way and can now reap and unlock. - assert_ok!(Democracy::remove_vote(Origin::signed(1), r)); - assert_ok!(Democracy::unlock(Origin::signed(1), 1)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(1), r)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 1)); // Anyone can reap and unlock anyone else's in this context. - assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 5, r)); - assert_ok!(Democracy::unlock(Origin::signed(2), 5)); + assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(2), 5, r)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(2), 5)); // 2, 3, 4 got their way with the vote, so they cannot be reaped by others. assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 2, r), + Democracy::remove_other_vote(RuntimeOrigin::signed(1), 2, r), Error::::NoPermission ); // However, they can be unvoted by the owner, though it will make no difference to the lock. - assert_ok!(Democracy::remove_vote(Origin::signed(2), r)); - assert_ok!(Democracy::unlock(Origin::signed(2), 2)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(2), r)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(2), 2)); assert_eq!(Balances::locks(1), vec![]); assert_eq!(Balances::locks(2), vec![the_lock(20)]); @@ -88,35 +87,35 @@ fn lock_voting_should_work() { fast_forward_to(7); // No change yet... assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 4, r), + Democracy::remove_other_vote(RuntimeOrigin::signed(1), 4, r), Error::::NoPermission ); - assert_ok!(Democracy::unlock(Origin::signed(1), 4)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 4)); assert_eq!(Balances::locks(4), vec![the_lock(40)]); fast_forward_to(8); // 4 should now be able to reap and unlock - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 4, r)); - assert_ok!(Democracy::unlock(Origin::signed(1), 4)); + assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(1), 4, r)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 4)); assert_eq!(Balances::locks(4), vec![]); fast_forward_to(13); assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 3, r), + Democracy::remove_other_vote(RuntimeOrigin::signed(1), 3, r), Error::::NoPermission ); - assert_ok!(Democracy::unlock(Origin::signed(1), 3)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 3)); assert_eq!(Balances::locks(3), vec![the_lock(30)]); fast_forward_to(14); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 3, r)); - assert_ok!(Democracy::unlock(Origin::signed(1), 3)); + assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(1), 3, r)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 3)); assert_eq!(Balances::locks(3), vec![]); // 2 doesn't need to reap_vote here because it was already done before. fast_forward_to(25); - assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 2)); assert_eq!(Balances::locks(2), vec![the_lock(20)]); fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(1), 2)); assert_eq!(Balances::locks(2), vec![]); }); } @@ -127,17 +126,17 @@ fn no_locks_without_conviction_should_work() { System::set_block_number(0); let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0, ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(0, 10))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(0, 10))); - fast_forward_to(2); + fast_forward_to(3); assert_eq!(Balances::free_balance(42), 2); - assert_ok!(Democracy::remove_other_vote(Origin::signed(2), 1, r)); - assert_ok!(Democracy::unlock(Origin::signed(2), 1)); + assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(2), 1, r)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(2), 1)); assert_eq!(Balances::locks(1), vec![]); }); } @@ -147,15 +146,15 @@ fn lock_voting_should_work_with_delegation() { new_test_ext().execute_with(|| { let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0, ); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3, 30))); - assert_ok!(Democracy::delegate(Origin::signed(4), 2, Conviction::Locked2x, 40)); - assert_ok!(Democracy::vote(Origin::signed(5), r, nay(1, 50))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, nay(5, 10))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, aye(4, 20))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(3), r, aye(3, 30))); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(4), 2, Conviction::Locked2x, 40)); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, nay(1, 50))); assert_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); @@ -168,29 +167,17 @@ fn lock_voting_should_work_with_delegation() { fn setup_three_referenda() -> (u32, u32, u32) { System::set_block_number(0); - let r1 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r1, aye(4, 10))); - - let r2 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r2, aye(3, 20))); - - let r3 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SimpleMajority, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(5), r3, aye(2, 50))); + let r1 = + Democracy::inject_referendum(2, set_balance_proposal(2), VoteThreshold::SimpleMajority, 0); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r1, aye(4, 10))); + + let r2 = + Democracy::inject_referendum(2, set_balance_proposal(2), VoteThreshold::SimpleMajority, 0); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r2, aye(3, 20))); + + let r3 = + Democracy::inject_referendum(2, set_balance_proposal(2), VoteThreshold::SimpleMajority, 0); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r3, aye(2, 50))); fast_forward_to(2); @@ -207,36 +194,36 @@ fn prior_lockvotes_should_be_enforced() { fast_forward_to(7); assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 5, r.2), + Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.2), Error::::NoPermission ); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![the_lock(50)]); fast_forward_to(8); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.2)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.2)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![the_lock(20)]); fast_forward_to(13); assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 5, r.1), + Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.1), Error::::NoPermission ); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![the_lock(20)]); fast_forward_to(14); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.1)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.1)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![the_lock(10)]); fast_forward_to(25); assert_noop!( - Democracy::remove_other_vote(Origin::signed(1), 5, r.0), + Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.0), Error::::NoPermission ); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![the_lock(10)]); fast_forward_to(26); - assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.0)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::remove_other_vote(RuntimeOrigin::signed(1), 5, r.0)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![]); }); } @@ -250,27 +237,27 @@ fn single_consolidation_of_lockvotes_should_work_as_before() { // r.2 locked 50 until 2 + 2 * 3 = #8 fast_forward_to(7); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.2)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![the_lock(50)]); fast_forward_to(8); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![the_lock(20)]); fast_forward_to(13); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.1)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![the_lock(20)]); fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![the_lock(10)]); fast_forward_to(25); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.0)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![the_lock(10)]); fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![]); }); } @@ -283,20 +270,20 @@ fn multi_consolidation_of_lockvotes_should_be_conservative() { // r.1 locked 20 until 2 + 4 * 3 = #14 // r.2 locked 50 until 2 + 2 * 3 = #8 - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.2)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.1)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.0)); fast_forward_to(8); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert!(Balances::locks(5)[0].amount >= 20); fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert!(Balances::locks(5)[0].amount >= 10); fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![]); }); } @@ -307,36 +294,36 @@ fn locks_should_persist_from_voting_to_delegation() { System::set_block_number(0); let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SimpleMajority, 0, ); - assert_ok!(Democracy::vote(Origin::signed(5), r, aye(4, 10))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, aye(4, 10))); fast_forward_to(2); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r)); // locked 10 until #26. - assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked3x, 20)); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(5), 1, Conviction::Locked3x, 20)); // locked 20. assert!(Balances::locks(5)[0].amount == 20); - assert_ok!(Democracy::undelegate(Origin::signed(5))); + assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(5))); // locked 20 until #14 fast_forward_to(13); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert!(Balances::locks(5)[0].amount == 20); fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert!(Balances::locks(5)[0].amount >= 10); fast_forward_to(25); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert!(Balances::locks(5)[0].amount >= 10); fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![]); }); } @@ -345,8 +332,8 @@ fn locks_should_persist_from_voting_to_delegation() { fn locks_should_persist_from_delegation_to_voting() { new_test_ext().execute_with(|| { System::set_block_number(0); - assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked5x, 5)); - assert_ok!(Democracy::undelegate(Origin::signed(5))); + assert_ok!(Democracy::delegate(RuntimeOrigin::signed(5), 1, Conviction::Locked5x, 5)); + assert_ok!(Democracy::undelegate(RuntimeOrigin::signed(5))); // locked 5 until 16 * 3 = #48 let r = setup_three_referenda(); @@ -354,24 +341,24 @@ fn locks_should_persist_from_delegation_to_voting() { // r.1 locked 20 until 2 + 4 * 3 = #14 // r.2 locked 50 until 2 + 2 * 3 = #8 - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.2)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.1)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r.0)); fast_forward_to(8); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert!(Balances::locks(5)[0].amount >= 20); fast_forward_to(14); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert!(Balances::locks(5)[0].amount >= 10); fast_forward_to(26); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert!(Balances::locks(5)[0].amount >= 5); fast_forward_to(48); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![]); }); } diff --git a/frame/democracy/src/tests/preimage.rs b/frame/democracy/src/tests/preimage.rs deleted file mode 100644 index 6d478fcaa68c7..0000000000000 --- a/frame/democracy/src/tests/preimage.rs +++ /dev/null @@ -1,219 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! The preimage tests. - -use super::*; - -#[test] -fn missing_preimage_should_fail() { - new_test_ext().execute_with(|| { - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); -} - -#[test] -fn preimage_deposit_should_be_required_and_returned() { - new_test_ext_execute_with_cond(|operational| { - // fee of 100 is too much. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); - assert_noop!( - if operational { - Democracy::note_preimage_operational(Origin::signed(6), vec![0; 500]) - } else { - Democracy::note_preimage(Origin::signed(6), vec![0; 500]) - }, - BalancesError::::InsufficientBalance, - ); - // fee of 1 is reasonable. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - next_block(); - - assert_eq!(Balances::reserved_balance(6), 0); - assert_eq!(Balances::free_balance(6), 60); - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn preimage_deposit_should_be_reapable_earlier_by_owner() { - new_test_ext_execute_with_cond(|operational| { - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(if operational { - Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) - } else { - Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) - }); - - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2), u32::MAX), - Error::::TooEarly - ); - next_block(); - assert_ok!(Democracy::reap_preimage( - Origin::signed(6), - set_balance_proposal_hash(2), - u32::MAX - )); - - assert_eq!(Balances::free_balance(6), 60); - assert_eq!(Balances::reserved_balance(6), 0); - }); -} - -#[test] -fn preimage_deposit_should_be_reapable() { - new_test_ext_execute_with_cond(|operational| { - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX), - Error::::PreimageMissing - ); - - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(if operational { - Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) - } else { - Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) - }); - assert_eq!(Balances::reserved_balance(6), 12); - - next_block(); - next_block(); - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX), - Error::::TooEarly - ); - - next_block(); - assert_ok!(Democracy::reap_preimage( - Origin::signed(5), - set_balance_proposal_hash(2), - u32::MAX - )); - assert_eq!(Balances::reserved_balance(6), 0); - assert_eq!(Balances::free_balance(6), 48); - assert_eq!(Balances::free_balance(5), 62); - }); -} - -#[test] -fn noting_imminent_preimage_for_free_should_work() { - new_test_ext_execute_with_cond(|operational| { - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 1, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - - assert_noop!( - if operational { - Democracy::note_imminent_preimage_operational( - Origin::signed(6), - set_balance_proposal(2), - ) - } else { - Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)) - }, - Error::::NotImminent - ); - - next_block(); - - // Now we're in the dispatch queue it's all good. - assert_ok!(Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))); - - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); -} - -#[test] -fn reaping_imminent_preimage_should_fail() { - new_test_ext().execute_with(|| { - let h = set_balance_proposal_hash_and_note(2); - let r = Democracy::inject_referendum(3, h, VoteThreshold::SuperMajorityApprove, 1); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - next_block(); - next_block(); - assert_noop!( - Democracy::reap_preimage(Origin::signed(6), h, u32::MAX), - Error::::Imminent - ); - }); -} - -#[test] -fn note_imminent_preimage_can_only_be_successful_once() { - new_test_ext().execute_with(|| { - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 1, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - next_block(); - - // First time works - assert_ok!(Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))); - - // Second time fails - assert_noop!( - Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)), - Error::::DuplicatePreimage - ); - - // Fails from any user - assert_noop!( - Democracy::note_imminent_preimage(Origin::signed(5), set_balance_proposal(2)), - Error::::DuplicatePreimage - ); - }); -} diff --git a/frame/democracy/src/tests/public_proposals.rs b/frame/democracy/src/tests/public_proposals.rs index 34713c3e15725..f48824dc95c5d 100644 --- a/frame/democracy/src/tests/public_proposals.rs +++ b/frame/democracy/src/tests/public_proposals.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,9 +22,9 @@ use super::*; #[test] fn backing_for_should_work() { new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_ok!(propose_set_balance_and_note(1, 3, 3)); + assert_ok!(propose_set_balance(1, 2, 2)); + assert_ok!(propose_set_balance(1, 4, 4)); + assert_ok!(propose_set_balance(1, 3, 3)); assert_eq!(Democracy::backing_for(0), Some(2)); assert_eq!(Democracy::backing_for(1), Some(4)); assert_eq!(Democracy::backing_for(2), Some(3)); @@ -34,11 +34,11 @@ fn backing_for_should_work() { #[test] fn deposit_for_proposals_should_be_taken() { new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_ok!(Democracy::second(Origin::signed(2), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_ok!(propose_set_balance(1, 2, 5)); + assert_ok!(Democracy::second(RuntimeOrigin::signed(2), 0)); + assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0)); + assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0)); + assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0)); assert_eq!(Balances::free_balance(1), 5); assert_eq!(Balances::free_balance(2), 15); assert_eq!(Balances::free_balance(5), 35); @@ -48,11 +48,11 @@ fn deposit_for_proposals_should_be_taken() { #[test] fn deposit_for_proposals_should_be_returned() { new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_ok!(Democracy::second(Origin::signed(2), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); - assert_ok!(Democracy::second(Origin::signed(5), 0, u32::MAX)); + assert_ok!(propose_set_balance(1, 2, 5)); + assert_ok!(Democracy::second(RuntimeOrigin::signed(2), 0)); + assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0)); + assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0)); + assert_ok!(Democracy::second(RuntimeOrigin::signed(5), 0)); fast_forward_to(3); assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::free_balance(2), 20); @@ -77,30 +77,22 @@ fn poor_proposer_should_not_work() { #[test] fn poor_seconder_should_not_work() { new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(2, 2, 11)); + assert_ok!(propose_set_balance(2, 2, 11)); assert_noop!( - Democracy::second(Origin::signed(1), 0, u32::MAX), + Democracy::second(RuntimeOrigin::signed(1), 0), BalancesError::::InsufficientBalance ); }); } -#[test] -fn invalid_seconds_upper_bound_should_not_work() { - new_test_ext().execute_with(|| { - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_noop!(Democracy::second(Origin::signed(2), 0, 0), Error::::WrongUpperBound); - }); -} - #[test] fn cancel_proposal_should_work() { new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_noop!(Democracy::cancel_proposal(Origin::signed(1), 0), BadOrigin); - assert_ok!(Democracy::cancel_proposal(Origin::root(), 0)); + assert_ok!(propose_set_balance(1, 2, 2)); + assert_ok!(propose_set_balance(1, 4, 4)); + assert_noop!(Democracy::cancel_proposal(RuntimeOrigin::signed(1), 0), BadOrigin); + assert_ok!(Democracy::cancel_proposal(RuntimeOrigin::root(), 0)); + System::assert_last_event(crate::Event::ProposalCanceled { prop_index: 0 }.into()); assert_eq!(Democracy::backing_for(0), None); assert_eq!(Democracy::backing_for(1), Some(4)); }); @@ -110,24 +102,24 @@ fn cancel_proposal_should_work() { fn blacklisting_should_work() { new_test_ext().execute_with(|| { System::set_block_number(0); - let hash = set_balance_proposal_hash(2); + let hash = set_balance_proposal(2).hash(); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); + assert_ok!(propose_set_balance(1, 2, 2)); + assert_ok!(propose_set_balance(1, 4, 4)); - assert_noop!(Democracy::blacklist(Origin::signed(1), hash.clone(), None), BadOrigin); - assert_ok!(Democracy::blacklist(Origin::root(), hash, None)); + assert_noop!(Democracy::blacklist(RuntimeOrigin::signed(1), hash, None), BadOrigin); + assert_ok!(Democracy::blacklist(RuntimeOrigin::root(), hash, None)); assert_eq!(Democracy::backing_for(0), None); assert_eq!(Democracy::backing_for(1), Some(4)); - assert_noop!(propose_set_balance_and_note(1, 2, 2), Error::::ProposalBlacklisted); + assert_noop!(propose_set_balance(1, 2, 2), Error::::ProposalBlacklisted); fast_forward_to(2); - let hash = set_balance_proposal_hash(4); + let hash = set_balance_proposal(4).hash(); assert_ok!(Democracy::referendum_status(0)); - assert_ok!(Democracy::blacklist(Origin::root(), hash, Some(0))); + assert_ok!(Democracy::blacklist(RuntimeOrigin::root(), hash, Some(0))); assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); }); } @@ -136,14 +128,14 @@ fn blacklisting_should_work() { fn runners_up_should_come_after() { new_test_ext().execute_with(|| { System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 2)); - assert_ok!(propose_set_balance_and_note(1, 4, 4)); - assert_ok!(propose_set_balance_and_note(1, 3, 3)); + assert_ok!(propose_set_balance(1, 2, 2)); + assert_ok!(propose_set_balance(1, 4, 4)); + assert_ok!(propose_set_balance(1, 3, 3)); fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), 0, aye(1))); fast_forward_to(4); - assert_ok!(Democracy::vote(Origin::signed(1), 1, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), 1, aye(1))); fast_forward_to(6); - assert_ok!(Democracy::vote(Origin::signed(1), 2, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), 2, aye(1))); }); } diff --git a/frame/democracy/src/tests/scheduling.rs b/frame/democracy/src/tests/scheduling.rs index 06b492bc6093c..5e133f38945d6 100644 --- a/frame/democracy/src/tests/scheduling.rs +++ b/frame/democracy/src/tests/scheduling.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,14 +24,16 @@ fn simple_passing_should_work() { new_test_ext().execute_with(|| { let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0, ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + assert_eq!(Democracy::lowest_unbaked(), 0); next_block(); next_block(); + assert_eq!(Democracy::lowest_unbaked(), 1); assert_eq!(Balances::free_balance(42), 2); }); } @@ -41,11 +43,11 @@ fn simple_failing_should_work() { new_test_ext().execute_with(|| { let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0, ); - assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, nay(1))); assert_eq!(tally(r), Tally { ayes: 0, nays: 1, turnout: 10 }); next_block(); @@ -60,26 +62,28 @@ fn ooo_inject_referendums_should_work() { new_test_ext().execute_with(|| { let r1 = Democracy::inject_referendum( 3, - set_balance_proposal_hash_and_note(3), + set_balance_proposal(3), VoteThreshold::SuperMajorityApprove, 0, ); let r2 = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0, ); - assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r2, aye(1))); assert_eq!(tally(r2), Tally { ayes: 1, nays: 0, turnout: 10 }); next_block(); - assert_eq!(Balances::free_balance(42), 2); - assert_ok!(Democracy::vote(Origin::signed(1), r1, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r1, aye(1))); assert_eq!(tally(r1), Tally { ayes: 1, nays: 0, turnout: 10 }); + next_block(); + assert_eq!(Balances::free_balance(42), 2); + next_block(); assert_eq!(Balances::free_balance(42), 3); }); @@ -90,16 +94,16 @@ fn delayed_enactment_should_work() { new_test_ext().execute_with(|| { let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 1, ); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); - assert_ok!(Democracy::vote(Origin::signed(3), r, aye(3))); - assert_ok!(Democracy::vote(Origin::signed(4), r, aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, aye(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, aye(6))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, aye(2))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(3), r, aye(3))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(4), r, aye(4))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, aye(5))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(6), r, aye(6))); assert_eq!(tally(r), Tally { ayes: 21, nays: 0, turnout: 210 }); @@ -110,3 +114,48 @@ fn delayed_enactment_should_work() { assert_eq!(Balances::free_balance(42), 2); }); } + +#[test] +fn lowest_unbaked_should_be_sensible() { + new_test_ext().execute_with(|| { + let r1 = Democracy::inject_referendum( + 3, + set_balance_proposal(1), + VoteThreshold::SuperMajorityApprove, + 0, + ); + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + let r3 = Democracy::inject_referendum( + 10, + set_balance_proposal(3), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r1, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r2, aye(1))); + // r3 is canceled + assert_ok!(Democracy::cancel_referendum(RuntimeOrigin::root(), r3.into())); + assert_eq!(Democracy::lowest_unbaked(), 0); + + next_block(); + // r2 ends with approval + assert_eq!(Democracy::lowest_unbaked(), 0); + + next_block(); + // r1 ends with approval + assert_eq!(Democracy::lowest_unbaked(), 3); + assert_eq!(Democracy::lowest_unbaked(), Democracy::referendum_count()); + + // r2 is executed + assert_eq!(Balances::free_balance(42), 2); + + next_block(); + // r1 is executed + assert_eq!(Balances::free_balance(42), 1); + }); +} diff --git a/frame/democracy/src/tests/voting.rs b/frame/democracy/src/tests/voting.rs index e035c2d46c1b6..482cd430e0e7f 100644 --- a/frame/democracy/src/tests/voting.rs +++ b/frame/democracy/src/tests/voting.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ fn overvoting_should_fail() { new_test_ext().execute_with(|| { let r = begin_referendum(); assert_noop!( - Democracy::vote(Origin::signed(1), r, aye(2)), + Democracy::vote(RuntimeOrigin::signed(1), r, aye(2)), Error::::InsufficientFunds ); }); @@ -35,9 +35,12 @@ fn split_voting_should_work() { new_test_ext().execute_with(|| { let r = begin_referendum(); let v = AccountVote::Split { aye: 40, nay: 20 }; - assert_noop!(Democracy::vote(Origin::signed(5), r, v), Error::::InsufficientFunds); + assert_noop!( + Democracy::vote(RuntimeOrigin::signed(5), r, v), + Error::::InsufficientFunds + ); let v = AccountVote::Split { aye: 30, nay: 20 }; - assert_ok!(Democracy::vote(Origin::signed(5), r, v)); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, v)); assert_eq!(tally(r), Tally { ayes: 3, nays: 2, turnout: 50 }); }); @@ -48,10 +51,10 @@ fn split_vote_cancellation_should_work() { new_test_ext().execute_with(|| { let r = begin_referendum(); let v = AccountVote::Split { aye: 30, nay: 20 }; - assert_ok!(Democracy::vote(Origin::signed(5), r, v)); - assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, v)); + assert_ok!(Democracy::remove_vote(RuntimeOrigin::signed(5), r)); assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); - assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_ok!(Democracy::unlock(RuntimeOrigin::signed(5), 5)); assert_eq!(Balances::locks(5), vec![]); }); } @@ -60,20 +63,20 @@ fn split_vote_cancellation_should_work() { fn single_proposal_should_work() { new_test_ext().execute_with(|| { System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); + assert_ok!(propose_set_balance(1, 2, 1)); let r = 0; assert!(Democracy::referendum_info(r).is_none()); // start of 2 => next referendum scheduled. fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, aye(1))); assert_eq!(Democracy::referendum_count(), 1); assert_eq!( Democracy::referendum_status(0), Ok(ReferendumStatus { end: 4, - proposal_hash: set_balance_proposal_hash_and_note(2), + proposal: set_balance_proposal(2), threshold: VoteThreshold::SuperMajorityApprove, delay: 2, tally: Tally { ayes: 1, nays: 0, turnout: 10 }, @@ -103,17 +106,17 @@ fn controversial_voting_should_work() { new_test_ext().execute_with(|| { let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0, ); - assert_ok!(Democracy::vote(Origin::signed(1), r, big_aye(1))); - assert_ok!(Democracy::vote(Origin::signed(2), r, big_nay(2))); - assert_ok!(Democracy::vote(Origin::signed(3), r, big_nay(3))); - assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, big_aye(1))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(2), r, big_nay(2))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(3), r, big_nay(3))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(4), r, big_aye(4))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(6), r, big_aye(6))); assert_eq!(tally(r), Tally { ayes: 110, nays: 100, turnout: 210 }); @@ -129,12 +132,12 @@ fn controversial_low_turnout_voting_should_work() { new_test_ext().execute_with(|| { let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0, ); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(6), r, big_aye(6))); assert_eq!(tally(r), Tally { ayes: 60, nays: 50, turnout: 110 }); @@ -153,13 +156,13 @@ fn passing_low_turnout_voting_should_work() { let r = Democracy::inject_referendum( 2, - set_balance_proposal_hash_and_note(2), + set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0, ); - assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4))); - assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5))); - assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(4), r, big_aye(4))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(5), r, big_nay(5))); + assert_ok!(Democracy::vote(RuntimeOrigin::signed(6), r, big_aye(6))); assert_eq!(tally(r), Tally { ayes: 100, nays: 50, turnout: 150 }); next_block(); diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs index 2eb004ba61bc4..4b7f1a0fac45c 100644 --- a/frame/democracy/src/types.rs +++ b/frame/democracy/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Miscellaneous additional datatypes. use crate::{AccountVote, Conviction, Vote, VoteThreshold}; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ traits::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Saturating, Zero}, @@ -26,7 +26,7 @@ use sp_runtime::{ }; /// Info regarding an ongoing referendum. -#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, MaxEncodedLen, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct Tally { /// The number of aye votes, expressed in terms of post-conviction lock-vote. pub ayes: Balance, @@ -37,7 +37,9 @@ pub struct Tally { } /// Amount of votes and capital placed in delegation for an account. -#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive( + Encode, MaxEncodedLen, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, +)] pub struct Delegations { /// The number of votes (this is post-conviction). pub votes: Balance, @@ -160,12 +162,12 @@ impl< } /// Info regarding an ongoing referendum. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct ReferendumStatus { +#[derive(Encode, MaxEncodedLen, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct ReferendumStatus { /// When voting on this referendum will end. pub end: BlockNumber, - /// The hash of the proposal being voted on. - pub proposal_hash: Hash, + /// The proposal being voted on. + pub proposal: Proposal, /// The thresholding mechanism to determine whether it passed. pub threshold: VoteThreshold, /// The delay (in blocks) to wait after a successful referendum before deploying. @@ -175,23 +177,23 @@ pub struct ReferendumStatus { } /// Info regarding a referendum, present or past. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub enum ReferendumInfo { +#[derive(Encode, MaxEncodedLen, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum ReferendumInfo { /// Referendum is happening, the arg is the block number at which it will end. - Ongoing(ReferendumStatus), + Ongoing(ReferendumStatus), /// Referendum finished at `end`, and has been `approved` or rejected. Finished { approved: bool, end: BlockNumber }, } -impl ReferendumInfo { +impl ReferendumInfo { /// Create a new instance. pub fn new( end: BlockNumber, - proposal_hash: Hash, + proposal: Proposal, threshold: VoteThreshold, delay: BlockNumber, ) -> Self { - let s = ReferendumStatus { end, proposal_hash, threshold, delay, tally: Tally::default() }; + let s = ReferendumStatus { end, proposal, threshold, delay, tally: Tally::default() }; ReferendumInfo::Ongoing(s) } } diff --git a/frame/democracy/src/vote.rs b/frame/democracy/src/vote.rs index 03ca020ca0949..122f54febd8cf 100644 --- a/frame/democracy/src/vote.rs +++ b/frame/democracy/src/vote.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,13 +18,14 @@ //! The vote datatype. use crate::{Conviction, Delegations, ReferendumIndex}; -use codec::{Decode, Encode, EncodeLike, Input, Output}; +use codec::{Decode, Encode, EncodeLike, Input, MaxEncodedLen, Output}; +use frame_support::traits::Get; use scale_info::TypeInfo; use sp_runtime::{ traits::{Saturating, Zero}, - RuntimeDebug, + BoundedVec, RuntimeDebug, }; -use sp_std::{convert::TryFrom, prelude::*, result::Result}; +use sp_std::prelude::*; /// A number of lock periods, plus a vote, one way or the other. #[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)] @@ -39,6 +40,12 @@ impl Encode for Vote { } } +impl MaxEncodedLen for Vote { + fn max_encoded_len() -> usize { + 1 + } +} + impl EncodeLike for Vote {} impl Decode for Vote { @@ -66,7 +73,7 @@ impl TypeInfo for Vote { } /// A vote for a referendum of a particular account. -#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] +#[derive(Encode, MaxEncodedLen, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] pub enum AccountVote { /// A standard vote, one-way (approve or reject) with a given amount of conviction. Standard { vote: Vote, balance: Balance }, @@ -107,7 +114,18 @@ impl AccountVote { /// A "prior" lock, i.e. a lock for some now-forgotten reason. #[derive( - Encode, Decode, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, + Encode, + MaxEncodedLen, + Decode, + Default, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + RuntimeDebug, + TypeInfo, )] pub struct PriorLock(BlockNumber, Balance); @@ -131,13 +149,15 @@ impl PriorLock { +#[derive(Clone, Encode, Decode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +#[codec(mel_bound(skip_type_params(MaxVotes)))] +#[scale_info(skip_type_params(MaxVotes))] +pub enum Voting> { /// The account is voting directly. `delegations` is the total amount of post-conviction voting /// weight that it controls from those that have delegated to it. Direct { /// The current votes of the account. - votes: Vec<(ReferendumIndex, AccountVote)>, + votes: BoundedVec<(ReferendumIndex, AccountVote), MaxVotes>, /// The total amount of delegations that this account has received. delegations: Delegations, /// Any pre-existing locks from past voting/delegating activity. @@ -155,20 +175,24 @@ pub enum Voting { }, } -impl Default - for Voting +impl> Default + for Voting { fn default() -> Self { Voting::Direct { - votes: Vec::new(), + votes: Default::default(), delegations: Default::default(), prior: PriorLock(Zero::zero(), Default::default()), } } } -impl - Voting +impl< + Balance: Saturating + Ord + Zero + Copy, + BlockNumber: Ord + Copy + Zero, + AccountId, + MaxVotes: Get, + > Voting { pub fn rejig(&mut self, now: BlockNumber) { match self { @@ -183,7 +207,7 @@ impl votes.iter().map(|i| i.1.balance()).fold(prior.locked(), |a, i| a.max(i)), - Voting::Delegating { balance, .. } => *balance, + Voting::Delegating { balance, prior, .. } => *balance.max(&prior.locked()), } } @@ -199,4 +223,11 @@ impl &PriorLock { + match self { + Voting::Direct { prior, .. } => prior, + Voting::Delegating { prior, .. } => prior, + } + } } diff --git a/frame/democracy/src/vote_threshold.rs b/frame/democracy/src/vote_threshold.rs index ad8bce290ed4f..e8ef91def9820 100644 --- a/frame/democracy/src/vote_threshold.rs +++ b/frame/democracy/src/vote_threshold.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Voting thresholds. use crate::Tally; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; @@ -26,7 +26,9 @@ use sp_runtime::traits::{IntegerSquareRoot, Zero}; use sp_std::ops::{Add, Div, Mul, Rem}; /// A means of determining if a vote is past pass threshold. -#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, sp_runtime::RuntimeDebug, TypeInfo)] +#[derive( + Clone, Copy, PartialEq, Eq, Encode, MaxEncodedLen, Decode, sp_runtime::RuntimeDebug, TypeInfo, +)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum VoteThreshold { /// A supermajority of approvals is needed to pass this vote. diff --git a/frame/democracy/src/weights.rs b/frame/democracy/src/weights.rs index e3f22f4fc0ab3..db3969d400b97 100644 --- a/frame/democracy/src/weights.rs +++ b/frame/democracy/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_democracy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/democracy/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -46,26 +48,23 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_democracy. pub trait WeightInfo { fn propose() -> Weight; - fn second(s: u32, ) -> Weight; - fn vote_new(r: u32, ) -> Weight; - fn vote_existing(r: u32, ) -> Weight; + fn second() -> Weight; + fn vote_new() -> Weight; + fn vote_existing() -> Weight; fn emergency_cancel() -> Weight; - fn blacklist(p: u32, ) -> Weight; - fn external_propose(v: u32, ) -> Weight; + fn blacklist() -> Weight; + fn external_propose() -> Weight; fn external_propose_majority() -> Weight; fn external_propose_default() -> Weight; fn fast_track() -> Weight; - fn veto_external(v: u32, ) -> Weight; - fn cancel_proposal(p: u32, ) -> Weight; + fn veto_external() -> Weight; + fn cancel_proposal() -> Weight; fn cancel_referendum() -> Weight; - fn cancel_queued(r: u32, ) -> Weight; fn on_initialize_base(r: u32, ) -> Weight; + fn on_initialize_base_with_launch_period(r: u32, ) -> Weight; fn delegate(r: u32, ) -> Weight; fn undelegate(r: u32, ) -> Weight; fn clear_public_proposals() -> Weight; - fn note_preimage(b: u32, ) -> Weight; - fn note_imminent_preimage(b: u32, ) -> Weight; - fn reap_preimage(b: u32, ) -> Weight; fn unlock_remove(r: u32, ) -> Weight; fn unlock_set(r: u32, ) -> Weight; fn remove_vote(r: u32, ) -> Weight; @@ -80,221 +79,215 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy Blacklist (r:1 w:0) // Storage: Democracy DepositOf (r:0 w:1) fn propose() -> Weight { - (65_665_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 56_868 nanoseconds. + Weight::from_ref_time(57_788_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Democracy DepositOf (r:1 w:1) - fn second(s: u32, ) -> Weight { - (40_003_000 as Weight) - // Standard Error: 1_000 - .saturating_add((180_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + fn second() -> Weight { + // Minimum execution time: 49_328 nanoseconds. + Weight::from_ref_time(49_764_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) - fn vote_new(r: u32, ) -> Weight { - (45_465_000 as Weight) - // Standard Error: 1_000 - .saturating_add((220_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + fn vote_new() -> Weight { + // Minimum execution time: 60_323 nanoseconds. + Weight::from_ref_time(61_389_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) - fn vote_existing(r: u32, ) -> Weight { - (45_112_000 as Weight) - // Standard Error: 1_000 - .saturating_add((222_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + fn vote_existing() -> Weight { + // Minimum execution time: 60_612 nanoseconds. + Weight::from_ref_time(61_282_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy Cancellations (r:1 w:1) fn emergency_cancel() -> Weight { - (26_651_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 24_780 nanoseconds. + Weight::from_ref_time(25_194_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy Blacklist (r:0 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn blacklist(p: u32, ) -> Weight { - (77_737_000 as Weight) - // Standard Error: 4_000 - .saturating_add((512_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) + fn blacklist() -> Weight { + // Minimum execution time: 85_177 nanoseconds. + Weight::from_ref_time(91_733_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:0) - fn external_propose(v: u32, ) -> Weight { - (13_126_000 as Weight) - // Standard Error: 0 - .saturating_add((89_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + fn external_propose() -> Weight { + // Minimum execution time: 19_483 nanoseconds. + Weight::from_ref_time(19_914_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_majority() -> Weight { - (2_923_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 4_963 nanoseconds. + Weight::from_ref_time(5_250_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_default() -> Weight { - (2_889_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_075 nanoseconds. + Weight::from_ref_time(5_187_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy ReferendumCount (r:1 w:1) // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn fast_track() -> Weight { - (27_598_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 23_956 nanoseconds. + Weight::from_ref_time(24_814_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:1) - fn veto_external(v: u32, ) -> Weight { - (28_416_000 as Weight) - // Standard Error: 0 - .saturating_add((132_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + fn veto_external() -> Weight { + // Minimum execution time: 31_472 nanoseconds. + Weight::from_ref_time(31_770_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Democracy PublicProps (r:1 w:1) // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) - fn cancel_proposal(p: u32, ) -> Weight { - (52_836_000 as Weight) - // Standard Error: 2_000 - .saturating_add((478_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + fn cancel_proposal() -> Weight { + // Minimum execution time: 73_811 nanoseconds. + Weight::from_ref_time(78_943_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn cancel_referendum() -> Weight { - (16_891_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) - fn cancel_queued(r: u32, ) -> Weight { - (30_504_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_480_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 16_074 nanoseconds. + Weight::from_ref_time(16_409_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } + // Storage: Democracy LowestUnbaked (r:1 w:1) + // Storage: Democracy ReferendumCount (r:1 w:0) + // Storage: Democracy ReferendumInfoOf (r:2 w:0) + /// The range of component `r` is `[0, 99]`. + fn on_initialize_base(r: u32, ) -> Weight { + // Minimum execution time: 7_430 nanoseconds. + Weight::from_ref_time(12_086_064 as u64) + // Standard Error: 3_474 + .saturating_add(Weight::from_ref_time(2_283_457 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(r as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Democracy LowestUnbaked (r:1 w:1) + // Storage: Democracy ReferendumCount (r:1 w:0) // Storage: Democracy LastTabledWasExternal (r:1 w:0) // Storage: Democracy NextExternal (r:1 w:0) // Storage: Democracy PublicProps (r:1 w:0) - // Storage: Democracy LowestUnbaked (r:1 w:0) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:1 w:0) - fn on_initialize_base(r: u32, ) -> Weight { - (6_259_000 as Weight) - // Standard Error: 4_000 - .saturating_add((5_032_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + // Storage: Democracy ReferendumInfoOf (r:2 w:0) + /// The range of component `r` is `[0, 99]`. + fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { + // Minimum execution time: 9_882 nanoseconds. + Weight::from_ref_time(14_566_711 as u64) + // Standard Error: 3_354 + .saturating_add(Weight::from_ref_time(2_282_038 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(r as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Democracy VotingOf (r:3 w:3) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:2 w:2) + /// The range of component `r` is `[0, 99]`. fn delegate(r: u32, ) -> Weight { - (51_719_000 as Weight) - // Standard Error: 5_000 - .saturating_add((7_210_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 48_840 nanoseconds. + Weight::from_ref_time(56_403_092 as u64) + // Standard Error: 6_093 + .saturating_add(Weight::from_ref_time(3_344_243 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(r as u64))) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(r as u64))) } // Storage: Democracy VotingOf (r:2 w:2) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:2 w:2) + /// The range of component `r` is `[0, 99]`. fn undelegate(r: u32, ) -> Weight { - (23_203_000 as Weight) - // Standard Error: 5_000 - .saturating_add((7_206_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 30_483 nanoseconds. + Weight::from_ref_time(32_035_405 as u64) + // Standard Error: 4_383 + .saturating_add(Weight::from_ref_time(3_347_667 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(r as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(r as u64))) } // Storage: Democracy PublicProps (r:0 w:1) fn clear_public_proposals() -> Weight { - (3_127_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_preimage(b: u32, ) -> Weight { - (44_130_000 as Weight) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_imminent_preimage(b: u32, ) -> Weight { - (28_756_000 as Weight) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - // Storage: System Account (r:1 w:0) - fn reap_preimage(b: u32, ) -> Weight { - (39_922_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 6_421 nanoseconds. + Weight::from_ref_time(6_638_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `r` is `[0, 99]`. fn unlock_remove(r: u32, ) -> Weight { - (38_621_000 as Weight) - // Standard Error: 1_000 - .saturating_add((110_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 30_291 nanoseconds. + Weight::from_ref_time(37_071_950 as u64) + // Standard Error: 1_619 + .saturating_add(Weight::from_ref_time(59_302 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `r` is `[0, 99]`. fn unlock_set(r: u32, ) -> Weight { - (36_631_000 as Weight) - // Standard Error: 1_000 - .saturating_add((214_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 34_888 nanoseconds. + Weight::from_ref_time(36_418_789 as u64) + // Standard Error: 906 + .saturating_add(Weight::from_ref_time(109_602 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) + /// The range of component `r` is `[1, 100]`. fn remove_vote(r: u32, ) -> Weight { - (21_025_000 as Weight) - // Standard Error: 1_000 - .saturating_add((195_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 18_739 nanoseconds. + Weight::from_ref_time(21_004_077 as u64) + // Standard Error: 1_075 + .saturating_add(Weight::from_ref_time(116_457 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) + /// The range of component `r` is `[1, 100]`. fn remove_other_vote(r: u32, ) -> Weight { - (20_628_000 as Weight) - // Standard Error: 1_000 - .saturating_add((214_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 18_514 nanoseconds. + Weight::from_ref_time(21_030_667 as u64) + // Standard Error: 1_102 + .saturating_add(Weight::from_ref_time(118_039 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } } @@ -305,220 +298,214 @@ impl WeightInfo for () { // Storage: Democracy Blacklist (r:1 w:0) // Storage: Democracy DepositOf (r:0 w:1) fn propose() -> Weight { - (65_665_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 56_868 nanoseconds. + Weight::from_ref_time(57_788_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Democracy DepositOf (r:1 w:1) - fn second(s: u32, ) -> Weight { - (40_003_000 as Weight) - // Standard Error: 1_000 - .saturating_add((180_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + fn second() -> Weight { + // Minimum execution time: 49_328 nanoseconds. + Weight::from_ref_time(49_764_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) - fn vote_new(r: u32, ) -> Weight { - (45_465_000 as Weight) - // Standard Error: 1_000 - .saturating_add((220_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + fn vote_new() -> Weight { + // Minimum execution time: 60_323 nanoseconds. + Weight::from_ref_time(61_389_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) - fn vote_existing(r: u32, ) -> Weight { - (45_112_000 as Weight) - // Standard Error: 1_000 - .saturating_add((222_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + fn vote_existing() -> Weight { + // Minimum execution time: 60_612 nanoseconds. + Weight::from_ref_time(61_282_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy Cancellations (r:1 w:1) fn emergency_cancel() -> Weight { - (26_651_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 24_780 nanoseconds. + Weight::from_ref_time(25_194_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Democracy PublicProps (r:1 w:1) + // Storage: Democracy DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy Blacklist (r:0 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn blacklist(p: u32, ) -> Weight { - (77_737_000 as Weight) - // Standard Error: 4_000 - .saturating_add((512_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + fn blacklist() -> Weight { + // Minimum execution time: 85_177 nanoseconds. + Weight::from_ref_time(91_733_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:0) - fn external_propose(v: u32, ) -> Weight { - (13_126_000 as Weight) - // Standard Error: 0 - .saturating_add((89_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + fn external_propose() -> Weight { + // Minimum execution time: 19_483 nanoseconds. + Weight::from_ref_time(19_914_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_majority() -> Weight { - (2_923_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 4_963 nanoseconds. + Weight::from_ref_time(5_250_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_default() -> Weight { - (2_889_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_075 nanoseconds. + Weight::from_ref_time(5_187_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy ReferendumCount (r:1 w:1) // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn fast_track() -> Weight { - (27_598_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 23_956 nanoseconds. + Weight::from_ref_time(24_814_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:1) - fn veto_external(v: u32, ) -> Weight { - (28_416_000 as Weight) - // Standard Error: 0 - .saturating_add((132_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + fn veto_external() -> Weight { + // Minimum execution time: 31_472 nanoseconds. + Weight::from_ref_time(31_770_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Democracy PublicProps (r:1 w:1) // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) - fn cancel_proposal(p: u32, ) -> Weight { - (52_836_000 as Weight) - // Standard Error: 2_000 - .saturating_add((478_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + fn cancel_proposal() -> Weight { + // Minimum execution time: 73_811 nanoseconds. + Weight::from_ref_time(78_943_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn cancel_referendum() -> Weight { - (16_891_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) - fn cancel_queued(r: u32, ) -> Weight { - (30_504_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_480_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 16_074 nanoseconds. + Weight::from_ref_time(16_409_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } + // Storage: Democracy LowestUnbaked (r:1 w:1) + // Storage: Democracy ReferendumCount (r:1 w:0) + // Storage: Democracy ReferendumInfoOf (r:2 w:0) + /// The range of component `r` is `[0, 99]`. + fn on_initialize_base(r: u32, ) -> Weight { + // Minimum execution time: 7_430 nanoseconds. + Weight::from_ref_time(12_086_064 as u64) + // Standard Error: 3_474 + .saturating_add(Weight::from_ref_time(2_283_457 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(r as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Democracy LowestUnbaked (r:1 w:1) + // Storage: Democracy ReferendumCount (r:1 w:0) // Storage: Democracy LastTabledWasExternal (r:1 w:0) // Storage: Democracy NextExternal (r:1 w:0) // Storage: Democracy PublicProps (r:1 w:0) - // Storage: Democracy LowestUnbaked (r:1 w:0) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:1 w:0) - fn on_initialize_base(r: u32, ) -> Weight { - (6_259_000 as Weight) - // Standard Error: 4_000 - .saturating_add((5_032_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) + // Storage: Democracy ReferendumInfoOf (r:2 w:0) + /// The range of component `r` is `[0, 99]`. + fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { + // Minimum execution time: 9_882 nanoseconds. + Weight::from_ref_time(14_566_711 as u64) + // Standard Error: 3_354 + .saturating_add(Weight::from_ref_time(2_282_038 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(r as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Democracy VotingOf (r:3 w:3) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:2 w:2) + /// The range of component `r` is `[0, 99]`. fn delegate(r: u32, ) -> Weight { - (51_719_000 as Weight) - // Standard Error: 5_000 - .saturating_add((7_210_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 48_840 nanoseconds. + Weight::from_ref_time(56_403_092 as u64) + // Standard Error: 6_093 + .saturating_add(Weight::from_ref_time(3_344_243 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(r as u64))) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(r as u64))) } // Storage: Democracy VotingOf (r:2 w:2) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) + // Storage: Democracy ReferendumInfoOf (r:2 w:2) + /// The range of component `r` is `[0, 99]`. fn undelegate(r: u32, ) -> Weight { - (23_203_000 as Weight) - // Standard Error: 5_000 - .saturating_add((7_206_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(r as Weight))) + // Minimum execution time: 30_483 nanoseconds. + Weight::from_ref_time(32_035_405 as u64) + // Standard Error: 4_383 + .saturating_add(Weight::from_ref_time(3_347_667 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(r as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(r as u64))) } // Storage: Democracy PublicProps (r:0 w:1) fn clear_public_proposals() -> Weight { - (3_127_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_preimage(b: u32, ) -> Weight { - (44_130_000 as Weight) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - fn note_imminent_preimage(b: u32, ) -> Weight { - (28_756_000 as Weight) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Democracy Preimages (r:1 w:1) - // Storage: System Account (r:1 w:0) - fn reap_preimage(b: u32, ) -> Weight { - (39_922_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 6_421 nanoseconds. + Weight::from_ref_time(6_638_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `r` is `[0, 99]`. fn unlock_remove(r: u32, ) -> Weight { - (38_621_000 as Weight) - // Standard Error: 1_000 - .saturating_add((110_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 30_291 nanoseconds. + Weight::from_ref_time(37_071_950 as u64) + // Standard Error: 1_619 + .saturating_add(Weight::from_ref_time(59_302 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `r` is `[0, 99]`. fn unlock_set(r: u32, ) -> Weight { - (36_631_000 as Weight) - // Standard Error: 1_000 - .saturating_add((214_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 34_888 nanoseconds. + Weight::from_ref_time(36_418_789 as u64) + // Standard Error: 906 + .saturating_add(Weight::from_ref_time(109_602 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) + /// The range of component `r` is `[1, 100]`. fn remove_vote(r: u32, ) -> Weight { - (21_025_000 as Weight) - // Standard Error: 1_000 - .saturating_add((195_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 18_739 nanoseconds. + Weight::from_ref_time(21_004_077 as u64) + // Standard Error: 1_075 + .saturating_add(Weight::from_ref_time(116_457 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) + /// The range of component `r` is `[1, 100]`. fn remove_other_vote(r: u32, ) -> Weight { - (20_628_000 as Weight) - // Standard Error: 1_000 - .saturating_add((214_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 18_514 nanoseconds. + Weight::from_ref_time(21_030_667 as u64) + // Standard Error: 1_102 + .saturating_add(Weight::from_ref_time(118_039 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } } diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index b2d50321e8cd3..42cd682a0ff02 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -2,59 +2,56 @@ name = "pallet-election-provider-multi-phase" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "PALLET two phase election providers" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] static_assertions = "1.1.0" -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -log = { version = "0.4.14", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = [ + "derive", +] } +log = { version = "0.4.17", default-features = false } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../../primitives/arithmetic" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } # Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -rand = { version = "0.7.3", default-features = false, optional = true, features = [ - "alloc", - "small_rng", -] } -strum = { optional = true, version = "0.21.0" } -strum_macros = { optional = true, version = "0.21.1" } +pallet-election-provider-support-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support/benchmarking", optional = true } +rand = { version = "0.7.3", default-features = false, features = ["alloc", "small_rng"], optional = true } +strum = { version = "0.24.1", default-features = false, features = ["derive"], optional = true } [dev-dependencies] -parking_lot = "0.11.0" +parking_lot = "0.12.1" rand = { version = "0.7.3" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } -frame-election-provider-support = { version = "4.0.0-dev", features = [ -], path = "../election-provider-support" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } [features] default = ["std"] std = [ + "pallet-election-provider-support-benchmarking?/std", "codec/std", "scale-info/std", "log/std", @@ -70,12 +67,15 @@ std = [ "sp-arithmetic/std", "frame-election-provider-support/std", "log/std", + + "frame-benchmarking?/std", + "rand/std", + "strum/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-election-provider-support/runtime-benchmarks", "rand", "strum", - "strum_macros", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index fb5adda52e166..10041f6aec07c 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,17 +19,16 @@ use super::*; use crate::{unsigned::IndexAssignmentOf, Pallet as MultiPhase}; -use frame_benchmarking::{account, impl_benchmark_test_suite}; -use frame_support::{assert_ok, traits::Hooks}; +use frame_benchmarking::account; +use frame_support::{ + assert_ok, + traits::{Hooks, TryCollect}, + BoundedVec, +}; use frame_system::RawOrigin; use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng}; use sp_arithmetic::{per_things::Percent, traits::One}; -use sp_npos_elections::IndexAssignment; use sp_runtime::InnerOf; -use sp_std::{ - boxed::Box, - convert::{TryFrom, TryInto}, -}; const SEED: u32 = 999; @@ -40,15 +39,15 @@ fn solution_with_size( size: SolutionOrSnapshotSize, active_voters_count: u32, desired_targets: u32, -) -> Result>, &'static str> { +) -> Result>, &'static str> { ensure!(size.targets >= desired_targets, "must have enough targets"); ensure!( - size.targets >= (>::LIMIT * 2) as u32, + size.targets >= (>::LIMIT * 2) as u32, "must have enough targets for unique votes." ); ensure!(size.voters >= active_voters_count, "must have enough voters"); ensure!( - (>::LIMIT as u32) < desired_targets, + (>::LIMIT as u32) < desired_targets, "must have enough winners to give them votes." ); @@ -73,11 +72,12 @@ fn solution_with_size( let active_voters = (0..active_voters_count) .map(|i| { // chose a random subset of winners. - let winner_votes = winners + let winner_votes: BoundedVec<_, _> = winners .as_slice() - .choose_multiple(&mut rng, >::LIMIT) + .choose_multiple(&mut rng, >::LIMIT) .cloned() - .collect::>(); + .try_collect() + .expect(">::LIMIT is the correct bound; qed."); let voter = frame_benchmarking::account::("Voter", i, SEED); (voter, stake, winner_votes) }) @@ -91,10 +91,11 @@ fn solution_with_size( .collect::>(); let rest_voters = (active_voters_count..size.voters) .map(|i| { - let votes = (&non_winners) - .choose_multiple(&mut rng, >::LIMIT) + let votes: BoundedVec<_, _> = (&non_winners) + .choose_multiple(&mut rng, >::LIMIT) .cloned() - .collect::>(); + .try_collect() + .expect(">::LIMIT is the correct bound; qed."); let voter = frame_benchmarking::account::("Voter", i, SEED); (voter, stake, votes) }) @@ -119,12 +120,12 @@ fn solution_with_size( // down the road. T::DataProvider::put_snapshot(all_voters.clone(), targets.clone(), Some(stake)); - let cache = helpers::generate_voter_cache::(&all_voters); - let stake_of = helpers::stake_of_fn::(&all_voters, &cache); - let voter_index = helpers::voter_index_fn::(&cache); - let target_index = helpers::target_index_fn::(&targets); - let voter_at = helpers::voter_at_fn::(&all_voters); - let target_at = helpers::target_at_fn::(&targets); + let cache = helpers::generate_voter_cache::(&all_voters); + let stake_of = helpers::stake_of_fn::(&all_voters, &cache); + let voter_index = helpers::voter_index_fn::(&cache); + let target_index = helpers::target_index_fn::(&targets); + let voter_at = helpers::voter_at_fn::(&all_voters); + let target_at = helpers::target_at_fn::(&targets); let assignments = active_voters .iter() @@ -142,11 +143,15 @@ fn solution_with_size( .collect::>(); let solution = - >::from_assignment(&assignments, &voter_index, &target_index).unwrap(); + >::from_assignment(&assignments, &voter_index, &target_index) + .unwrap(); let score = solution.clone().score(stake_of, voter_at, target_at).unwrap(); let round = >::round(); - assert!(score[0] > 0, "score is zero, this probably means that the stakes are not set."); + assert!( + score.minimal_stake > 0, + "score is zero, this probably means that the stakes are not set." + ); Ok(RawSolution { solution, score, round }) } @@ -156,7 +161,7 @@ fn set_up_data_provider(v: u32, t: u32) { info, "setting up with voters = {} [degree = {}], targets = {}", v, - T::DataProvider::MAXIMUM_VOTES_PER_VOTER, + ::MaxVotesPerVoter::get(), t ); @@ -169,14 +174,16 @@ fn set_up_data_provider(v: u32, t: u32) { }) .collect::>(); // we should always have enough voters to fill. - assert!(targets.len() > T::DataProvider::MAXIMUM_VOTES_PER_VOTER as usize); - targets.truncate(T::DataProvider::MAXIMUM_VOTES_PER_VOTER as usize); + assert!( + targets.len() > ::MaxVotesPerVoter::get() as usize + ); + targets.truncate(::MaxVotesPerVoter::get() as usize); // fill voters. (0..v).for_each(|i| { let voter = frame_benchmarking::account::("Voter", i, SEED); let weight = T::Currency::minimum_balance().saturated_into::() * 1000; - T::DataProvider::add_voter(voter, weight, targets.clone()); + T::DataProvider::add_voter(voter, weight, targets.clone().try_into().unwrap()); }); } @@ -213,16 +220,26 @@ frame_benchmarking::benchmarks! { let receiver = account("receiver", 0, SEED); let initial_balance = T::Currency::minimum_balance() * 10u32.into(); T::Currency::make_free_balance_be(&receiver, initial_balance); - let ready: ReadySolution = Default::default(); + let ready = Default::default(); let deposit: BalanceOf = 10u32.into(); - let reward: BalanceOf = 20u32.into(); + + let reward: BalanceOf = T::SignedRewardBase::get(); + let call_fee: BalanceOf = 30u32.into(); assert_ok!(T::Currency::reserve(&receiver, deposit)); assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into()); }: { - >::finalize_signed_phase_accept_solution(ready, &receiver, deposit, reward) + >::finalize_signed_phase_accept_solution( + ready, + &receiver, + deposit, + call_fee + ) } verify { - assert_eq!(T::Currency::free_balance(&receiver), initial_balance + 20u32.into()); + assert_eq!( + T::Currency::free_balance(&receiver), + initial_balance + reward + call_fee + ); assert_eq!(T::Currency::reserved_balance(&receiver), 0u32.into()); } @@ -243,15 +260,15 @@ frame_benchmarking::benchmarks! { } create_snapshot_internal { - // number of votes in snapshot. Fixed to maximum. - let v = T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS; - // number of targets in snapshot. Fixed to maximum. - let t = T::BenchmarkingConfig::MAXIMUM_TARGETS; + // number of votes in snapshot. + let v in (T::BenchmarkingConfig::VOTERS[0]) .. T::BenchmarkingConfig::VOTERS[1]; + // number of targets in snapshot. + let t in (T::BenchmarkingConfig::TARGETS[0]) .. T::BenchmarkingConfig::TARGETS[1]; // we don't directly need the data-provider to be populated, but it is just easy to use it. set_up_data_provider::(v, t); - let targets = T::DataProvider::targets(None)?; - let voters = T::DataProvider::voters(None)?; + let targets = T::DataProvider::electable_targets(None)?; + let voters = T::DataProvider::electing_voters(None)?; let desired_targets = T::DataProvider::desired_targets()?; assert!(>::snapshot().is_none()); }: { @@ -289,7 +306,7 @@ frame_benchmarking::benchmarks! { assert!(>::get().is_some()); assert!(>::get().is_some()); }: { - assert_ok!( as ElectionProvider>::elect()); + assert_ok!( as ElectionProvider>::elect()); } verify { assert!(>::queued_solution().is_none()); assert!(>::get().is_none()); @@ -299,36 +316,45 @@ frame_benchmarking::benchmarks! { } submit { - let c in 1 .. (T::SignedMaxSubmissions::get() - 1); - - // the solution will be worse than all of them meaning the score need to be checked against - // ~ log2(c) - let solution = RawSolution { - score: [(10_000_000u128 - 1).into(), 0, 0], - ..Default::default() - }; - + // the queue is full and the solution is only better than the worse. >::create_snapshot().map_err(<&str>::from)?; MultiPhase::::on_initialize_open_signed(); >::put(1); let mut signed_submissions = SignedSubmissions::::get(); - for i in 0..c { + + // Insert `max` submissions + for i in 0..(T::SignedMaxSubmissions::get() - 1) { let raw_solution = RawSolution { - score: [(10_000_000 + i).into(), 0, 0], + score: ElectionScore { minimal_stake: 10_000_000u128 + (i as u128), ..Default::default() }, ..Default::default() }; - let signed_submission = SignedSubmission { raw_solution, ..Default::default() }; + let signed_submission = SignedSubmission { + raw_solution, + who: account("submitters", i, SEED), + deposit: Default::default(), + call_fee: Default::default(), + }; signed_submissions.insert(signed_submission); } signed_submissions.put(); + // this score will eject the weakest one. + let solution = RawSolution { + score: ElectionScore { minimal_stake: 10_000_000u128 + 1, ..Default::default() }, + ..Default::default() + }; + let caller = frame_benchmarking::whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance() * 10u32.into()); + let deposit = MultiPhase::::deposit_for( + &solution, + MultiPhase::::snapshot_metadata().unwrap_or_default(), + ); + T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance() * 1000u32.into() + deposit); - }: _(RawOrigin::Signed(caller), Box::new(solution), c) + }: _(RawOrigin::Signed(caller), Box::new(solution)) verify { - assert!(>::signed_submissions().len() as u32 == c + 1); + assert!(>::signed_submissions().len() as u32 == T::SignedMaxSubmissions::get()); } submit_unsigned { @@ -350,25 +376,8 @@ frame_benchmarking::benchmarks! { assert!(>::queued_solution().is_none()); >::put(Phase::Unsigned((true, 1u32.into()))); - - // encode the most significant storage item that needs to be decoded in the dispatch. - let encoded_snapshot = >::snapshot().ok_or("missing snapshot")?.encode(); - let encoded_call = Call::::submit_unsigned { - raw_solution: Box::new(raw_solution.clone()), - witness - }.encode(); - }: { - assert_ok!( - >::submit_unsigned( - RawOrigin::None.into(), - Box::new(raw_solution), - witness, - ) - ); - let _decoded_snap = as Decode>::decode(&mut &*encoded_snapshot) - .expect("decoding should not fail; qed."); - let _decoded_call = as Decode>::decode(&mut &*encoded_call).expect("decoding should not fail; qed."); - } verify { + }: _(RawOrigin::None, Box::new(raw_solution), witness) + verify { assert!(>::queued_solution().is_some()); } @@ -389,13 +398,8 @@ frame_benchmarking::benchmarks! { assert_eq!(raw_solution.solution.voter_count() as u32, a); assert_eq!(raw_solution.solution.unique_targets().len() as u32, d); - - // encode the most significant storage item that needs to be decoded in the dispatch. - let encoded_snapshot = >::snapshot().ok_or("snapshot missing")?.encode(); }: { - assert_ok!(>::feasibility_check(raw_solution, ElectionCompute::Unsigned)); - let _decoded_snap = as Decode>::decode(&mut &*encoded_snapshot) - .expect("decoding should not fail; qed."); + assert!(>::feasibility_check(raw_solution, ElectionCompute::Unsigned).is_ok()); } // NOTE: this weight is not used anywhere, but the fact that it should succeed when execution in @@ -465,24 +469,25 @@ frame_benchmarking::benchmarks! { T::BenchmarkingConfig::DESIRED_TARGETS[1]; // Subtract this percentage from the actual encoded size let f in 0 .. 95; + use frame_election_provider_support::IndexAssignment; // Compute a random solution, then work backwards to get the lists of voters, targets, and // assignments let witness = SolutionOrSnapshotSize { voters: v, targets: t }; let RawSolution { solution, .. } = solution_with_size::(witness, a, d)?; let RoundSnapshot { voters, targets } = MultiPhase::::snapshot().ok_or("snapshot missing")?; - let voter_at = helpers::voter_at_fn::(&voters); - let target_at = helpers::target_at_fn::(&targets); + let voter_at = helpers::voter_at_fn::(&voters); + let target_at = helpers::target_at_fn::(&targets); let mut assignments = solution.into_assignment(voter_at, target_at).expect("solution generated by `solution_with_size` must be valid."); // make a voter cache and some helper functions for access - let cache = helpers::generate_voter_cache::(&voters); - let voter_index = helpers::voter_index_fn::(&cache); - let target_index = helpers::target_index_fn::(&targets); + let cache = helpers::generate_voter_cache::(&voters); + let voter_index = helpers::voter_index_fn::(&cache); + let target_index = helpers::target_index_fn::(&targets); // sort assignments by decreasing voter stake assignments.sort_by_key(|crate::unsigned::Assignment:: { who, .. }| { - let stake = cache.get(&who).map(|idx| { + let stake = cache.get(who).map(|idx| { let (_, stake, _) = voters[*idx]; stake }).unwrap_or_default(); @@ -495,21 +500,21 @@ frame_benchmarking::benchmarks! { .collect::, _>>() .unwrap(); - let encoded_size_of = |assignments: &[IndexAssignmentOf]| { - SolutionOf::::try_from(assignments).map(|solution| solution.encoded_size()) + let encoded_size_of = |assignments: &[IndexAssignmentOf]| { + SolutionOf::::try_from(assignments).map(|solution| solution.encoded_size()) }; let desired_size = Percent::from_percent(100 - f.saturated_into::()) .mul_ceil(encoded_size_of(index_assignments.as_slice()).unwrap()); log!(trace, "desired_size = {}", desired_size); }: { - MultiPhase::::trim_assignments_length( + crate::Miner::::trim_assignments_length( desired_size.saturated_into(), &mut index_assignments, &encoded_size_of, ).unwrap(); } verify { - let solution = SolutionOf::::try_from(index_assignments.as_slice()).unwrap(); + let solution = SolutionOf::::try_from(index_assignments.as_slice()).unwrap(); let encoding = solution.encode(); log!( trace, @@ -519,10 +524,10 @@ frame_benchmarking::benchmarks! { log!(trace, "actual encoded size = {}", encoding.len()); assert!(encoding.len() <= desired_size); } -} -impl_benchmark_test_suite!( - MultiPhase, - crate::mock::ExtBuilder::default().build_offchainify(10).0, - crate::mock::Runtime, -); + impl_benchmark_test_suite!( + MultiPhase, + crate::mock::ExtBuilder::default().build_offchainify(10).0, + crate::mock::Runtime, + ); +} diff --git a/frame/election-provider-multi-phase/src/helpers.rs b/frame/election-provider-multi-phase/src/helpers.rs index 72b1b23f27f3c..0a7240c5d27af 100644 --- a/frame/election-provider-multi-phase/src/helpers.rs +++ b/frame/election-provider-multi-phase/src/helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,11 @@ //! Some helper functions/macros for this crate. -use super::{Config, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight}; -use sp_std::{collections::btree_map::BTreeMap, convert::TryInto, prelude::*}; +use crate::{ + unsigned::{MinerConfig, MinerVoterOf}, + SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight, +}; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; #[macro_export] macro_rules! log { @@ -30,11 +33,22 @@ macro_rules! log { }; } +// This is only useful for a context where a `` is not in scope. +#[macro_export] +macro_rules! log_no_system { + ($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: $crate::LOG_TARGET, + concat!("🗳 ", $pattern) $(, $values)* + ) + }; +} + /// Generate a btree-map cache of the voters and their indices. /// /// This can be used to efficiently build index getter closures. -pub fn generate_voter_cache( - snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, +pub fn generate_voter_cache( + snapshot: &Vec>, ) -> BTreeMap { let mut cache: BTreeMap = BTreeMap::new(); snapshot.iter().enumerate().for_each(|(i, (x, _, _))| { @@ -54,7 +68,7 @@ pub fn generate_voter_cache( /// ## Warning /// /// Note that this will represent the snapshot data from which the `cache` is generated. -pub fn voter_index_fn( +pub fn voter_index_fn( cache: &BTreeMap, ) -> impl Fn(&T::AccountId) -> Option> + '_ { move |who| { @@ -68,7 +82,7 @@ pub fn voter_index_fn( /// /// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is /// borrowed. -pub fn voter_index_fn_owned( +pub fn voter_index_fn_owned( cache: BTreeMap, ) -> impl Fn(&T::AccountId) -> Option> { move |who| { @@ -83,7 +97,7 @@ pub fn voter_index_fn_owned( /// ## Warning /// /// Note that this will represent the snapshot data from which the `cache` is generated. -pub fn voter_index_fn_usize( +pub fn voter_index_fn_usize( cache: &BTreeMap, ) -> impl Fn(&T::AccountId) -> Option + '_ { move |who| cache.get(who).cloned() @@ -96,8 +110,8 @@ pub fn voter_index_fn_usize( /// /// Not meant to be used in production. #[cfg(test)] -pub fn voter_index_fn_linear( - snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, +pub fn voter_index_fn_linear( + snapshot: &Vec>, ) -> impl Fn(&T::AccountId) -> Option> + '_ { move |who| { snapshot @@ -114,7 +128,7 @@ pub fn voter_index_fn_linear( /// Note: to the extent possible, the returned function should be cached and reused. Producing that /// function requires a `O(n log n)` data transform. Each invocation of that function completes /// in `O(log n)`. -pub fn target_index_fn( +pub fn target_index_fn( snapshot: &Vec, ) -> impl Fn(&T::AccountId) -> Option> + '_ { let cache: BTreeMap<_, _> = @@ -134,7 +148,7 @@ pub fn target_index_fn( /// /// Not meant to be used in production. #[cfg(test)] -pub fn target_index_fn_linear( +pub fn target_index_fn_linear( snapshot: &Vec, ) -> impl Fn(&T::AccountId) -> Option> + '_ { move |who| { @@ -147,8 +161,8 @@ pub fn target_index_fn_linear( /// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter /// account using a linearly indexible snapshot. -pub fn voter_at_fn( - snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, +pub fn voter_at_fn( + snapshot: &Vec>, ) -> impl Fn(SolutionVoterIndexOf) -> Option + '_ { move |i| { as TryInto>::try_into(i) @@ -159,7 +173,7 @@ pub fn voter_at_fn( /// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target /// account using a linearly indexible snapshot. -pub fn target_at_fn( +pub fn target_at_fn( snapshot: &Vec, ) -> impl Fn(SolutionTargetIndexOf) -> Option + '_ { move |i| { @@ -173,8 +187,8 @@ pub fn target_at_fn( /// /// This is not optimized and uses a linear search. #[cfg(test)] -pub fn stake_of_fn_linear( - snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, +pub fn stake_of_fn_linear( + snapshot: &Vec>, ) -> impl Fn(&T::AccountId) -> VoteWeight + '_ { move |who| { snapshot @@ -191,8 +205,8 @@ pub fn stake_of_fn_linear( /// /// The cache need must be derived from the same snapshot. Zero is returned if a voter is /// non-existent. -pub fn stake_of_fn<'a, T: Config>( - snapshot: &'a Vec<(T::AccountId, VoteWeight, Vec)>, +pub fn stake_of_fn<'a, T: MinerConfig>( + snapshot: &'a Vec>, cache: &'a BTreeMap, ) -> impl Fn(&T::AccountId) -> VoteWeight + 'a { move |who| { diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 269057b55b094..6c4a55800f7e8 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,7 +51,7 @@ //! In the signed phase, solutions (of type [`RawSolution`]) are submitted and queued on chain. A //! deposit is reserved, based on the size of the solution, for the cost of keeping this solution //! on-chain for a number of blocks, and the potential weight of the solution upon being checked. A -//! maximum of `pallet::Config::MaxSignedSubmissions` solutions are stored. The queue is always +//! maximum of `pallet::Config::SignedMaxSubmissions` solutions are stored. The queue is always //! sorted based on score (worse to best). //! //! Upon arrival of a new solution: @@ -67,11 +67,11 @@ //! origin can not bail out in any way, if their solution is queued. //! //! Upon the end of the signed phase, the solutions are examined from best to worse (i.e. `pop()`ed -//! until drained). Each solution undergoes an expensive `Pallet::feasibility_check`, which -//! ensures the score claimed by this score was correct, and it is valid based on the election data -//! (i.e. votes and candidates). At each step, if the current best solution passes the feasibility -//! check, it is considered to be the best one. The sender of the origin is rewarded, and the rest -//! of the queued solutions get their deposit back and are discarded, without being checked. +//! until drained). Each solution undergoes an expensive `Pallet::feasibility_check`, which ensures +//! the score claimed by this score was correct, and it is valid based on the election data (i.e. +//! votes and targets). At each step, if the current best solution passes the feasibility check, +//! it is considered to be the best one. The sender of the origin is rewarded, and the rest of the +//! queued solutions get their deposit back and are discarded, without being checked. //! //! The following example covers all of the cases at the end of the signed phase: //! @@ -102,8 +102,8 @@ //! valid if propagated, and it acts similar to an inherent. //! //! Validators will only submit solutions if the one that they have computed is sufficiently better -//! than the best queued one (see [`pallet::Config::SolutionImprovementThreshold`]) and will limit -//! the weigh of the solution to [`pallet::Config::MinerMaxWeight`]. +//! than the best queued one (see [`pallet::Config::BetterUnsignedThreshold`]) and will limit the +//! weight of the solution to [`MinerConfig::MaxWeight`]. //! //! The unsigned phase can be made passive depending on how the previous signed phase went, by //! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always @@ -114,28 +114,46 @@ //! If we reach the end of both phases (i.e. call to [`ElectionProvider::elect`] happens) and no //! good solution is queued, then the fallback strategy [`pallet::Config::Fallback`] is used to //! determine what needs to be done. The on-chain election is slow, and contains no balancing or -//! reduction post-processing. [`NoFallback`] does nothing and enables [`Phase::Emergency`], which -//! is a more *fail-safe* approach. +//! reduction post-processing. If [`pallet::Config::Fallback`] fails, the next phase +//! [`Phase::Emergency`] is enabled, which is a more *fail-safe* approach. //! //! ### Emergency Phase //! //! If, for any of the below reasons: //! -//! 1. No signed or unsigned solution submitted & Fallback is `None` or failed -//! 2. Internal error +//! 1. No **signed** or **unsigned** solution submitted, and no successful [`Config::Fallback`] is +//! provided +//! 2. Any other unforeseen internal error //! //! A call to `T::ElectionProvider::elect` is made, and `Ok(_)` cannot be returned, then the pallet //! proceeds to the [`Phase::Emergency`]. During this phase, any solution can be submitted from -//! [`Config::ForceOrigin`], without any checking. Once submitted, the forced solution is kept in -//! [`QueuedSolution`] until the next call to `T::ElectionProvider::elect`, where it is returned and -//! [`Phase`] goes back to `Off`. +//! [`Config::ForceOrigin`], without any checking, via [`Pallet::set_emergency_election_result`] +//! transaction. Hence, `[`Config::ForceOrigin`]` should only be set to a trusted origin, such as +//! the council or root. Once submitted, the forced solution is kept in [`QueuedSolution`] until the +//! next call to `T::ElectionProvider::elect`, where it is returned and [`Phase`] goes back to +//! `Off`. //! //! This implies that the user of this pallet (i.e. a staking pallet) should re-try calling -//! `T::ElectionProvider::elect` in case of error until `OK(_)` is returned. +//! `T::ElectionProvider::elect` in case of error, until `OK(_)` is returned. +//! +//! To generate an emergency solution, one must only provide one argument: [`Supports`]. This is +//! essentially a collection of elected winners for the election, and voters who support them. The +//! supports can be generated by any means. In the simplest case, it could be manual. For example, +//! in the case of massive network failure or misbehavior, [`Config::ForceOrigin`] might decide to +//! select only a small number of emergency winners (which would greatly restrict the next validator +//! set, if this pallet is used with `pallet-staking`). If the failure is for other technical +//! reasons, then a simple and safe way to generate supports is using the staking-miner binary +//! provided in the Polkadot repository. This binary has a subcommand named `emergency-solution` +//! which is capable of connecting to a live network, and generating appropriate `supports` using a +//! standard algorithm, and outputting the `supports` in hex format, ready for submission. Note that +//! while this binary lives in the Polkadot repository, this particular subcommand of it can work +//! against any substrate-based chain. +//! +//! See the `staking-miner` documentation in the Polkadot repository for more information. //! //! ## Feasible Solution (correct solution) //! -//! All submissions must undergo a feasibility check. Signed solutions are checked on by one at the +//! All submissions must undergo a feasibility check. Signed solutions are checked one by one at the //! end of the signed phase, and the unsigned solutions are checked on the spot. A feasible solution //! is as follows: //! @@ -146,16 +164,16 @@ //! //! ## Accuracy //! -//! The accuracy of the election is configured via -//! [`SolutionAccuracyOf`] which is the accuracy that the submitted solutions must adhere to. +//! The accuracy of the election is configured via [`SolutionAccuracyOf`] which is the accuracy that +//! the submitted solutions must adhere to. //! //! Note that the accuracy is of great importance. The offchain solution should be as small as //! possible, reducing solutions size/weight. //! //! ## Error types //! -//! This pallet provides a verbose error system to ease future debugging and debugging. The -//! overall hierarchy of errors is as follows: +//! This pallet provides a verbose error system to ease future debugging and debugging. The overall +//! hierarchy of errors is as follows: //! //! 1. [`pallet::Error`]: These are the errors that can be returned in the dispatchables of the //! pallet, either signed or unsigned. Since decomposition with nested enums is not possible @@ -165,14 +183,17 @@ //! are helpful for logging and are thus nested as: //! - [`ElectionError::Miner`]: wraps a [`unsigned::MinerError`]. //! - [`ElectionError::Feasibility`]: wraps a [`FeasibilityError`]. -//! - [`ElectionError::OnChainFallback`]: wraps a -//! [`frame_election_provider_support::onchain::Error`]. +//! - [`ElectionError::Fallback`]: wraps a fallback error. +//! - [`ElectionError::DataProvider`]: wraps a static str. //! //! Note that there could be an overlap between these sub-errors. For example, A //! `SnapshotUnavailable` can happen in both miner and feasibility check phase. //! //! ## Future Plans //! +//! **Emergency-phase recovery script**: This script should be taken out of staking-miner in +//! polkadot and ideally live in `substrate/utils/frame/elections`. +//! //! **Challenge Phase**. We plan on adding a third phase to the pallet, called the challenge phase. //! This is a phase in which no further solutions are processed, and the current best solution might //! be challenged by anyone (signed or unsigned). The main plan here is to enforce the solution to @@ -209,32 +230,35 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; -use frame_election_provider_support::{ElectionDataProvider, ElectionProvider}; +use frame_election_provider_support::{ + BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ElectionProviderBase, + InstantElectionProvider, NposSolution, +}; use frame_support::{ - dispatch::DispatchResultWithPostInfo, + dispatch::DispatchClass, ensure, - traits::{Currency, Get, OnUnbalanced, ReservableCurrency}, - weights::{DispatchClass, Weight}, + traits::{Currency, DefensiveResult, Get, OnUnbalanced, ReservableCurrency}, + weights::Weight, + DefaultNoBound, EqNoBound, PartialEqNoBound, }; use frame_system::{ensure_none, offchain::SendTransactionTypes}; use scale_info::TypeInfo; use sp_arithmetic::{ - traits::{CheckedAdd, Saturating, Zero}, + traits::{CheckedAdd, Zero}, UpperOf, }; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, ElectionScore, EvaluateSupport, NposSolution, Supports, - VoteWeight, + assignment_ratio_to_staked_normalized, BoundedSupports, ElectionScore, EvaluateSupport, + Supports, VoteWeight, }; use sp_runtime::{ - traits::Bounded, transaction_validity::{ InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, }, - DispatchError, PerThing, Perbill, RuntimeDebug, SaturatedConversion, + DispatchError, ModuleError, PerThing, Perbill, RuntimeDebug, SaturatedConversion, }; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -243,32 +267,33 @@ mod mock; #[macro_use] pub mod helpers; -const LOG_TARGET: &'static str = "runtime::election-provider"; +const LOG_TARGET: &str = "runtime::election-provider"; +pub mod migrations; pub mod signed; pub mod unsigned; pub mod weights; +use unsigned::VoterOf; pub use weights::WeightInfo; pub use signed::{ BalanceOf, NegativeImbalanceOf, PositiveImbalanceOf, SignedSubmission, SignedSubmissionOf, SignedSubmissions, SubmissionIndicesOf, }; +pub use unsigned::{Miner, MinerConfig}; /// The solution type used by this crate. -pub type SolutionOf = ::Solution; +pub type SolutionOf = ::Solution; /// The voter index. Derived from [`SolutionOf`]. pub type SolutionVoterIndexOf = as NposSolution>::VoterIndex; /// The target index. Derived from [`SolutionOf`]. pub type SolutionTargetIndexOf = as NposSolution>::TargetIndex; /// The accuracy of the election, when submitted from offchain. Derived from [`SolutionOf`]. -pub type SolutionAccuracyOf = as NposSolution>::Accuracy; +pub type SolutionAccuracyOf = + ::MinerConfig> as NposSolution>::Accuracy; /// The fallback election type. -pub type FallbackErrorOf = <::Fallback as ElectionProvider< - ::AccountId, - ::BlockNumber, ->>::Error; +pub type FallbackErrorOf = <::Fallback as ElectionProviderBase>::Error; /// Configuration for the benchmarks of the pallet. pub trait BenchmarkingConfig { @@ -288,29 +313,6 @@ pub trait BenchmarkingConfig { const MAXIMUM_TARGETS: u32; } -impl BenchmarkingConfig for () { - const VOTERS: [u32; 2] = [4000, 6000]; - const TARGETS: [u32; 2] = [1000, 1600]; - const ACTIVE_VOTERS: [u32; 2] = [1000, 3000]; - const DESIRED_TARGETS: [u32; 2] = [400, 800]; - const SNAPSHOT_MAXIMUM_VOTERS: u32 = 10_000; - const MINER_MAXIMUM_VOTERS: u32 = 10_000; - const MAXIMUM_TARGETS: u32 = 2_000; -} - -/// A fallback implementation that transitions the pallet to the emergency phase. -pub struct NoFallback(sp_std::marker::PhantomData); - -impl ElectionProvider for NoFallback { - type DataProvider = T::DataProvider; - type Error = &'static str; - - fn elect() -> Result, Self::Error> { - // Do nothing, this will enable the emergency phase. - Err("NoFallback.") - } -} - /// Current phase of the pallet. #[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] pub enum Phase { @@ -418,13 +420,23 @@ impl Default for RawSolution { } /// A checked solution, ready to be enacted. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)] -pub struct ReadySolution { +#[derive( + PartialEqNoBound, + EqNoBound, + Clone, + Encode, + Decode, + RuntimeDebug, + DefaultNoBound, + scale_info::TypeInfo, +)] +#[scale_info(skip_type_params(T))] +pub struct ReadySolution { /// The final supports of the solution. /// /// This is target-major vector, storing each winners, total backing, and each individual /// backer. - pub supports: Supports, + pub supports: BoundedSupports, /// The score of the solution. /// /// This is needed to potentially challenge the solution. @@ -438,11 +450,12 @@ pub struct ReadySolution { /// /// These are stored together because they are often accessed together. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)] -pub struct RoundSnapshot { +#[scale_info(skip_type_params(T))] +pub struct RoundSnapshot { /// All of the voters. - pub voters: Vec<(A, VoteWeight, Vec)>, + pub voters: Vec>, /// All of the targets. - pub targets: Vec, + pub targets: Vec, } /// Encodes the length of a solution or a snapshot. @@ -464,16 +477,18 @@ pub struct SolutionOrSnapshotSize { /// /// Note that this is different from [`pallet::Error`]. #[derive(frame_support::DebugNoBound)] -#[cfg_attr(feature = "runtime-benchmarks", derive(strum_macros::IntoStaticStr))] +#[cfg_attr(feature = "runtime-benchmarks", derive(strum::IntoStaticStr))] pub enum ElectionError { /// An error happened in the feasibility check sub-system. Feasibility(FeasibilityError), /// An error in the miner (offchain) sub-system. - Miner(unsigned::MinerError), + Miner(unsigned::MinerError), /// An error happened in the data provider. DataProvider(&'static str), /// An error nested in the fallback. Fallback(FallbackErrorOf), + /// No solution has been queued. + NothingQueued, } // NOTE: we have to do this manually because of the additional where clause needed on @@ -501,15 +516,15 @@ impl From for ElectionError { } } -impl From> for ElectionError { - fn from(e: unsigned::MinerError) -> Self { +impl From for ElectionError { + fn from(e: unsigned::MinerError) -> Self { ElectionError::Miner(e) } } /// Errors that can happen in the feasibility check. #[derive(Debug, Eq, PartialEq)] -#[cfg_attr(feature = "runtime-benchmarks", derive(strum_macros::IntoStaticStr))] +#[cfg_attr(feature = "runtime-benchmarks", derive(strum::IntoStaticStr))] pub enum FeasibilityError { /// Wrong number of winners presented. WrongWinnerCount, @@ -530,6 +545,12 @@ pub enum FeasibilityError { InvalidRound, /// Comparison against `MinimumUntrustedScore` failed. UntrustedScoreTooLow, + /// Data Provider returned too many desired targets + TooManyDesiredTargets, + /// Conversion into bounded types failed. + /// + /// Should never happen under correct configurations. + BoundedConversionFailed, } impl From for FeasibilityError { @@ -542,14 +563,14 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_election_provider_support::NposSolver; + use frame_election_provider_support::{InstantElectionProvider, NposSolver}; use frame_support::{pallet_prelude::*, traits::EstimateCallFee}; use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config + SendTransactionTypes> { - type Event: From> - + IsType<::Event> + type RuntimeEvent: From> + + IsType<::RuntimeEvent> + TryInto>; /// Currency type. @@ -566,9 +587,14 @@ pub mod pallet { type SignedPhase: Get; /// The minimum amount of improvement to the solution score that defines a solution as - /// "better" (in any phase). + /// "better" in the Signed phase. #[pallet::constant] - type SolutionImprovementThreshold: Get; + type BetterSignedThreshold: Get; + + /// The minimum amount of improvement to the solution score that defines a solution as + /// "better" in the Unsigned phase. + #[pallet::constant] + type BetterUnsignedThreshold: Get; /// The repeat threshold of the offchain worker. /// @@ -581,12 +607,14 @@ pub mod pallet { #[pallet::constant] type MinerTxPriority: Get; - /// Maximum weight that the miner should consume. + /// Configurations of the embedded miner. /// - /// The miner will ensure that the total weight of the unsigned solution will not exceed - /// this value, based on [`WeightInfo::submit_unsigned`]. - #[pallet::constant] - type MinerMaxWeight: Get; + /// Any external software implementing this can use the [`unsigned::Miner`] type provided, + /// which can mine new solutions and trim them accordingly. + type MinerConfig: crate::unsigned::MinerConfig< + AccountId = Self::AccountId, + MaxVotesPerVoter = ::MaxVotesPerVoter, + >; /// Maximum number of signed submissions that can be queued. /// @@ -600,10 +628,16 @@ pub mod pallet { /// Maximum weight of a signed solution. /// - /// This should probably be similar to [`Config::MinerMaxWeight`]. + /// If [`Config::MinerConfig`] is being implemented to submit signed solutions (outside of + /// this pallet), then [`MinerConfig::solution_weight`] is used to compare against + /// this value. #[pallet::constant] type SignedMaxWeight: Get; + /// The maximum amount of unchecked solutions to refund the call fee for. + #[pallet::constant] + type SignedMaxRefunds: Get; + /// Base reward for a signed solution #[pallet::constant] type SignedRewardBase: Get>; @@ -620,38 +654,52 @@ pub mod pallet { #[pallet::constant] type SignedDepositWeight: Get>; + /// The maximum number of electing voters to put in the snapshot. At the moment, snapshots + /// are only over a single block, but once multi-block elections are introduced they will + /// take place over multiple blocks. + #[pallet::constant] + type MaxElectingVoters: Get>; + + /// The maximum number of electable targets to put in the snapshot. + #[pallet::constant] + type MaxElectableTargets: Get>; + + /// The maximum number of winners that can be elected by this `ElectionProvider` + /// implementation. + /// + /// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`. + #[pallet::constant] + type MaxWinners: Get; + /// Handler for the slashed deposits. type SlashHandler: OnUnbalanced>; /// Handler for the rewards. type RewardHandler: OnUnbalanced>; - /// Maximum length (bytes) that the mined solution should consume. - /// - /// The miner will ensure that the total length of the unsigned solution will not exceed - /// this value. - #[pallet::constant] - type MinerMaxLength: Get; - /// Something that will provide the election data. - type DataProvider: ElectionDataProvider; - - /// The solution type. - type Solution: codec::Codec - + Default - + PartialEq - + Eq - + Clone - + sp_std::fmt::Debug - + Ord - + NposSolution - + TypeInfo; - - /// Configuration for the fallback - type Fallback: ElectionProvider< - Self::AccountId, - Self::BlockNumber, + type DataProvider: ElectionDataProvider< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, + >; + + /// Configuration for the fallback. + type Fallback: InstantElectionProvider< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, + DataProvider = Self::DataProvider, + MaxWinners = Self::MaxWinners, + >; + + /// Configuration of the governance-only fallback. + /// + /// As a side-note, it is recommend for test-nets to use `type ElectionProvider = + /// BoundedExecution<_>` if the test-net is not expected to have thousands of nominators. + type GovernanceFallback: InstantElectionProvider< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, DataProvider = Self::DataProvider, + MaxWinners = Self::MaxWinners, >; /// OCW election solution miner algorithm implementation. @@ -659,7 +707,7 @@ pub mod pallet { /// Origin that can control this pallet. Note that any action taken by this origin (such) /// as providing an emergency solution is not checked. Thus, it must be a trusted origin. - type ForceOrigin: EnsureOrigin; + type ForceOrigin: EnsureOrigin; /// The configuration of benchmarking. type BenchmarkingConfig: BenchmarkingConfig; @@ -668,6 +716,25 @@ pub mod pallet { type WeightInfo: WeightInfo; } + // Expose miner configs over the metadata such that they can be re-implemented. + #[pallet::extra_constants] + impl Pallet { + #[pallet::constant_name(MinerMaxLength)] + fn max_length() -> u32 { + ::MaxLength::get() + } + + #[pallet::constant_name(MinerMaxWeight)] + fn max_weight() -> Weight { + ::MaxWeight::get() + } + + #[pallet::constant_name(MinerMaxVotesPerVoter)] + fn max_votes_per_voter() -> u32 { + ::MaxVotesPerVoter::get() + } + } + #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: T::BlockNumber) -> Weight { @@ -742,7 +809,7 @@ pub mod pallet { Self::on_initialize_open_unsigned(enabled, now); T::WeightInfo::on_initialize_open_unsigned() } - } + }, _ => T::WeightInfo::on_initialize_nothing(), } } @@ -772,13 +839,13 @@ pub mod pallet { fn integrity_test() { use sp_std::mem::size_of; // The index type of both voters and targets need to be smaller than that of usize (very - // unlikely to be the case, but anyhow). - assert!(size_of::>() <= size_of::()); - assert!(size_of::>() <= size_of::()); + // unlikely to be the case, but anyhow).. + assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); // ---------------------------- // Based on the requirements of [`sp_npos_elections::Assignment::try_normalize`]. - let max_vote: usize = as NposSolution>::LIMIT; + let max_vote: usize = as NposSolution>::LIMIT; // 2. Maximum sum of [SolutionAccuracy; 16] must fit into `UpperOf`. let maximum_chain_accuracy: Vec>> = (0..max_vote) @@ -798,9 +865,14 @@ pub mod pallet { // NOTE that this pallet does not really need to enforce this in runtime. The // solution cannot represent any voters more than `LIMIT` anyhow. assert_eq!( - >::MAXIMUM_VOTES_PER_VOTER, - as NposSolution>::LIMIT as u32, + ::MaxVotesPerVoter::get(), + as NposSolution>::LIMIT as u32, ); + + // While it won't cause any failures, setting `SignedMaxRefunds` gt + // `SignedMaxSubmissions` is a red flag that the developer does not understand how to + // configure this pallet. + assert!(T::SignedMaxSubmissions::get() >= T::SignedMaxRefunds::get()); } } @@ -820,6 +892,7 @@ pub mod pallet { /// putting their authoring reward at risk. /// /// No deposit or reward is associated with this submission. + #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::submit_unsigned( witness.voters, @@ -831,9 +904,9 @@ pub mod pallet { ))] pub fn submit_unsigned( origin: OriginFor, - raw_solution: Box>>, + raw_solution: Box>>, witness: SolutionOrSnapshotSize, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { ensure_none(origin)?; let error_message = "Invalid unsigned submission must produce invalid block and \ deprive validator from their authoring reward."; @@ -856,12 +929,12 @@ pub mod pallet { log!(info, "queued unsigned solution with score {:?}", ready.score); let ejected_a_solution = >::exists(); >::put(ready); - Self::deposit_event(Event::SolutionStored( - ElectionCompute::Unsigned, - ejected_a_solution, - )); + Self::deposit_event(Event::SolutionStored { + compute: ElectionCompute::Unsigned, + prev_ejected: ejected_a_solution, + }); - Ok(None.into()) + Ok(()) } /// Set a new value for `MinimumUntrustedScore`. @@ -869,6 +942,7 @@ pub mod pallet { /// Dispatch origin must be aligned with `T::ForceOrigin`. /// /// This check can be turned off by setting the value to `None`. + #[pallet::call_index(1)] #[pallet::weight(T::DbWeight::get().writes(1))] pub fn set_minimum_untrusted_score( origin: OriginFor, @@ -887,6 +961,7 @@ pub mod pallet { /// The solution is not checked for any feasibility and is assumed to be trustworthy, as any /// feasibility check itself can in principle cause the election process to fail (due to /// memory/weight constrains). + #[pallet::call_index(2)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn set_emergency_election_result( origin: OriginFor, @@ -895,11 +970,21 @@ pub mod pallet { T::ForceOrigin::ensure_origin(origin)?; ensure!(Self::current_phase().is_emergency(), >::CallNotAllowed); + // bound supports with T::MaxWinners + let supports = supports.try_into().map_err(|_| Error::::TooManyWinners)?; + // Note: we don't `rotate_round` at this point; the next call to // `ElectionProvider::elect` will succeed and take care of that. + let solution = ReadySolution { + supports, + score: Default::default(), + compute: ElectionCompute::Emergency, + }; - let solution = - ReadySolution { supports, score: [0, 0, 0], compute: ElectionCompute::Emergency }; + Self::deposit_event(Event::SolutionStored { + compute: ElectionCompute::Emergency, + prev_ejected: QueuedSolution::::exists(), + }); >::put(solution); Ok(()) @@ -914,25 +999,14 @@ pub mod pallet { /// /// A deposit is reserved and recorded for the solution. Based on the outcome, the solution /// might be rewarded, slashed, or get all or a part of the deposit back. - /// - /// # - /// Queue size must be provided as witness data. - /// # - #[pallet::weight(T::WeightInfo::submit(*num_signed_submissions))] + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::submit())] pub fn submit( origin: OriginFor, - raw_solution: Box>>, - num_signed_submissions: u32, + raw_solution: Box>>, ) -> DispatchResult { let who = ensure_signed(origin)?; - // ensure witness data is correct. - ensure!( - num_signed_submissions >= - >::decode_len().unwrap_or_default() as u32, - Error::::SignedInvalidWitness, - ); - // ensure solution is timely. ensure!(Self::current_phase().is_signed(), Error::::PreDispatchEarlySubmission); @@ -944,21 +1018,23 @@ pub mod pallet { let size = Self::snapshot_metadata().ok_or(Error::::MissingSnapshotMetadata)?; ensure!( - Self::feasibility_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(), + Self::solution_weight_of(&raw_solution, size).all_lt(T::SignedMaxWeight::get()), Error::::SignedTooMuchWeight, ); // create the submission let deposit = Self::deposit_for(&raw_solution, size); - let reward = { - let call = - Call::submit { raw_solution: raw_solution.clone(), num_signed_submissions }; - let call_fee = T::EstimateCallFee::estimate_call_fee(&call, None.into()); - T::SignedRewardBase::get().saturating_add(call_fee) + let call_fee = { + let call = Call::submit { raw_solution: raw_solution.clone() }; + T::EstimateCallFee::estimate_call_fee(&call, None::.into()) }; - let submission = - SignedSubmission { who: who.clone(), deposit, raw_solution: *raw_solution, reward }; + let submission = SignedSubmission { + who: who.clone(), + deposit, + raw_solution: *raw_solution, + call_fee, + }; // insert the submission if the queue has space or it's better than the weakest // eject the weakest if the queue was full @@ -982,7 +1058,54 @@ pub mod pallet { } signed_submissions.put(); - Self::deposit_event(Event::SolutionStored(ElectionCompute::Signed, ejected_a_solution)); + Self::deposit_event(Event::SolutionStored { + compute: ElectionCompute::Signed, + prev_ejected: ejected_a_solution, + }); + Ok(()) + } + + /// Trigger the governance fallback. + /// + /// This can only be called when [`Phase::Emergency`] is enabled, as an alternative to + /// calling [`Call::set_emergency_election_result`]. + #[pallet::call_index(4)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] + pub fn governance_fallback( + origin: OriginFor, + maybe_max_voters: Option, + maybe_max_targets: Option, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + ensure!(Self::current_phase().is_emergency(), >::CallNotAllowed); + + let supports = + T::GovernanceFallback::instant_elect(maybe_max_voters, maybe_max_targets).map_err( + |e| { + log!(error, "GovernanceFallback failed: {:?}", e); + Error::::FallbackFailed + }, + )?; + + // transform BoundedVec<_, T::GovernanceFallback::MaxWinners> into + // `BoundedVec<_, T::MaxWinners>` + let supports: BoundedVec<_, T::MaxWinners> = supports + .into_inner() + .try_into() + .defensive_map_err(|_| Error::::BoundNotMet)?; + + let solution = ReadySolution { + supports, + score: Default::default(), + compute: ElectionCompute::Fallback, + }; + + Self::deposit_event(Event::SolutionStored { + compute: ElectionCompute::Fallback, + prev_ejected: QueuedSolution::::exists(), + }); + + >::put(solution); Ok(()) } } @@ -996,18 +1119,21 @@ pub mod pallet { /// solution is unsigned, this means that it has also been processed. /// /// The `bool` is `true` when a previous solution was ejected to make room for this one. - SolutionStored(ElectionCompute, bool), - /// The election has been finalized, with `Some` of the given computation, or else if the - /// election failed, `None`. - ElectionFinalized(Option), + SolutionStored { compute: ElectionCompute, prev_ejected: bool }, + /// The election has been finalized, with the given computation and score. + ElectionFinalized { compute: ElectionCompute, score: ElectionScore }, + /// An election failed. + /// + /// Not much can be said about which computes failed in the process. + ElectionFailed, /// An account has been rewarded for their signed submission being finalized. - Rewarded(::AccountId, BalanceOf), + Rewarded { account: ::AccountId, value: BalanceOf }, /// An account has been slashed for submitting an invalid signed submission. - Slashed(::AccountId, BalanceOf), + Slashed { account: ::AccountId, value: BalanceOf }, /// The signed phase of the given round has started. - SignedPhaseStarted(u32), + SignedPhaseStarted { round: u32 }, /// The unsigned phase of the given round has started. - UnsignedPhaseStarted(u32), + UnsignedPhaseStarted { round: u32 }, } /// Error of the pallet that can be returned in response to dispatches. @@ -1035,6 +1161,12 @@ pub mod pallet { InvalidSubmissionIndex, /// The call is not allowed at this point. CallNotAllowed, + /// The fallback failed + FallbackFailed, + /// Some bound not met + BoundNotMet, + /// Submitted solution has too many winners + TooManyWinners, } #[pallet::validate_unsigned] @@ -1056,10 +1188,10 @@ pub mod pallet { .map_err(dispatch_error_to_invalid)?; ValidTransaction::with_tag_prefix("OffchainElection") - // The higher the score[0], the better a solution is. + // The higher the score.minimal_stake, the better a solution is. .priority( T::MinerTxPriority::get() - .saturating_add(raw_solution.score[0].saturated_into()), + .saturating_add(raw_solution.score.minimal_stake.saturated_into()), ) // Used to deduplicate unsigned solutions: each validator should produce one // solution per round at most, and solutions are not propagate. @@ -1108,14 +1240,14 @@ pub mod pallet { /// Current best solution, signed or unsigned, queued to be returned upon `elect`. #[pallet::storage] #[pallet::getter(fn queued_solution)] - pub type QueuedSolution = StorageValue<_, ReadySolution>; + pub type QueuedSolution = StorageValue<_, ReadySolution>; /// Snapshot data of the round. /// /// This is created at the beginning of the signed phase and cleared upon calling `elect`. #[pallet::storage] #[pallet::getter(fn snapshot)] - pub type Snapshot = StorageValue<_, RoundSnapshot>; + pub type Snapshot = StorageValue<_, RoundSnapshot>; /// Desired number of targets to elect for this round. /// @@ -1145,16 +1277,16 @@ pub mod pallet { /// capacity, it will simply saturate. We can't just iterate over `SignedSubmissionsMap`, /// because iteration is slow. Instead, we store the value here. #[pallet::storage] - pub(crate) type SignedSubmissionNextIndex = StorageValue<_, u32, ValueQuery>; + pub type SignedSubmissionNextIndex = StorageValue<_, u32, ValueQuery>; - /// A sorted, bounded set of `(score, index)`, where each `index` points to a value in - /// `SignedSubmissions`. + /// A sorted, bounded vector of `(score, block_number, index)`, where each `index` points to a + /// value in `SignedSubmissions`. /// /// We never need to process more than a single signed submission at a time. Signed submissions /// can be quite large, so we're willing to pay the cost of multiple database accesses to access /// them one at a time instead of reading and decoding all of them at once. #[pallet::storage] - pub(crate) type SignedSubmissionIndices = + pub type SignedSubmissionIndices = StorageValue<_, SubmissionIndicesOf, ValueQuery>; /// Unchecked, signed solutions. @@ -1165,8 +1297,8 @@ pub mod pallet { /// Twox note: the key of the map is an auto-incrementing index which users cannot inspect or /// affect; we shouldn't need a cryptographically secure hasher. #[pallet::storage] - pub(crate) type SignedSubmissionsMap = - StorageMap<_, Twox64Concat, u32, SignedSubmissionOf, ValueQuery>; + pub type SignedSubmissionsMap = + StorageMap<_, Twox64Concat, u32, SignedSubmissionOf, OptionQuery>; // `SignedSubmissions` items end here. @@ -1178,8 +1310,14 @@ pub mod pallet { #[pallet::getter(fn minimum_untrusted_score)] pub type MinimumUntrustedScore = StorageValue<_, ElectionScore>; + /// The current storage version. + /// + /// v1: https://github.com/paritytech/substrate/pull/12237/ + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); } @@ -1192,8 +1330,12 @@ impl Pallet { match current_phase { Phase::Unsigned((true, opened)) if opened == now => { // Mine a new solution, cache it, and attempt to submit it - let initial_output = Self::ensure_offchain_repeat_frequency(now) - .and_then(|_| Self::mine_check_save_submit()); + let initial_output = Self::ensure_offchain_repeat_frequency(now).and_then(|_| { + // This is executed at the beginning of each round. Any cache is now invalid. + // Clear it. + unsigned::kill_ocw_solution::(); + Self::mine_check_save_submit() + }); log!(debug, "initial offchain thread output: {:?}", initial_output); }, Phase::Unsigned((true, opened)) if opened < now => { @@ -1205,35 +1347,21 @@ impl Pallet { }, _ => {}, } - - // After election finalization, clear OCW solution storage. - // - // We can read the events here because offchain worker doesn't affect PoV. - if >::read_events_no_consensus() - .into_iter() - .filter_map(|event_record| { - let local_event = ::Event::from(event_record.event); - local_event.try_into().ok() - }) - .any(|event| matches!(event, Event::ElectionFinalized(_))) - { - unsigned::kill_ocw_solution::(); - } } - /// Logic for [`::on_initialize`] when signed phase is being opened. + /// Logic for `::on_initialize` when signed phase is being opened. pub fn on_initialize_open_signed() { log!(info, "Starting signed phase round {}.", Self::round()); >::put(Phase::Signed); - Self::deposit_event(Event::SignedPhaseStarted(Self::round())); + Self::deposit_event(Event::SignedPhaseStarted { round: Self::round() }); } - /// Logic for [`>::on_initialize`] when unsigned phase is being opened. + /// Logic for `>::on_initialize` when unsigned phase is being opened. pub fn on_initialize_open_unsigned(enabled: bool, now: T::BlockNumber) { let round = Self::round(); log!(info, "Starting unsigned phase round {} enabled {}.", round, enabled); >::put(Phase::Unsigned((enabled, now))); - Self::deposit_event(Event::UnsignedPhaseStarted(round)); + Self::deposit_event(Event::UnsignedPhaseStarted { round }); } /// Parts of [`create_snapshot`] that happen inside of this pallet. @@ -1241,7 +1369,7 @@ impl Pallet { /// Extracted for easier weight calculation. fn create_snapshot_internal( targets: Vec, - voters: Vec>, + voters: Vec>, desired_targets: u32, ) { let metadata = @@ -1254,7 +1382,7 @@ impl Pallet { // instead of using storage APIs, we do a manual encoding into a fixed-size buffer. // `encoded_size` encodes it without storing it anywhere, this should not cause any // allocation. - let snapshot = RoundSnapshot { voters, targets }; + let snapshot = RoundSnapshot:: { voters, targets }; let size = snapshot.encoded_size(); log!(debug, "snapshot pre-calculated size {:?}", size); let mut buffer = Vec::with_capacity(size); @@ -1272,23 +1400,36 @@ impl Pallet { /// /// Extracted for easier weight calculation. fn create_snapshot_external( - ) -> Result<(Vec, Vec>, u32), ElectionError> { - let target_limit = >::max_value().saturated_into::(); - let voter_limit = >::max_value().saturated_into::(); - - let targets = - T::DataProvider::targets(Some(target_limit)).map_err(ElectionError::DataProvider)?; - let voters = - T::DataProvider::voters(Some(voter_limit)).map_err(ElectionError::DataProvider)?; - let desired_targets = - T::DataProvider::desired_targets().map_err(ElectionError::DataProvider)?; + ) -> Result<(Vec, Vec>, u32), ElectionError> { + let target_limit = T::MaxElectableTargets::get().saturated_into::(); + let voter_limit = T::MaxElectingVoters::get().saturated_into::(); + + let targets = T::DataProvider::electable_targets(Some(target_limit)) + .map_err(ElectionError::DataProvider)?; + + let voters = T::DataProvider::electing_voters(Some(voter_limit)) + .map_err(ElectionError::DataProvider)?; - // Defensive-only. if targets.len() > target_limit || voters.len() > voter_limit { - debug_assert!(false, "Snapshot limit has not been respected."); return Err(ElectionError::DataProvider("Snapshot too big for submission.")) } + let mut desired_targets = as ElectionProviderBase>::desired_targets_checked() + .map_err(|e| ElectionError::DataProvider(e))?; + + // If `desired_targets` > `targets.len()`, cap `desired_targets` to that level and emit a + // warning + let max_desired_targets: u32 = targets.len() as u32; + if desired_targets > max_desired_targets { + log!( + warn, + "desired_targets: {} > targets.len(): {}, capping desired_targets", + desired_targets, + max_desired_targets + ); + desired_targets = max_desired_targets; + } + Ok((targets, voters, desired_targets)) } @@ -1307,8 +1448,10 @@ impl Pallet { let (targets, voters, desired_targets) = Self::create_snapshot_external()?; // ..therefore we only measure the weight of this and add it. + let internal_weight = + T::WeightInfo::create_snapshot_internal(voters.len() as u32, targets.len() as u32); Self::create_snapshot_internal(targets, voters, desired_targets); - Self::register_weight(T::WeightInfo::create_snapshot_internal()); + Self::register_weight(internal_weight); Ok(()) } @@ -1331,9 +1474,9 @@ impl Pallet { /// Checks the feasibility of a solution. pub fn feasibility_check( - raw_solution: RawSolution>, + raw_solution: RawSolution>, compute: ElectionCompute, - ) -> Result, FeasibilityError> { + ) -> Result, FeasibilityError> { let RawSolution { solution, score, round } = raw_solution; // First, check round. @@ -1345,16 +1488,18 @@ impl Pallet { let desired_targets = Self::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; - // NOTE: this is a bit of duplicate, but we keep it around for veracity. The unsigned path - // already checked this in `unsigned_per_dispatch_checks`. The signed path *could* check it - // upon arrival, thus we would then remove it here. Given overlay it is cheap anyhow ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount); + // Fail early if targets requested by data provider exceed maximum winners supported. + ensure!( + desired_targets <= ::MaxWinners::get(), + FeasibilityError::TooManyDesiredTargets + ); // Ensure that the solution's score can pass absolute min-score. - let submitted_score = raw_solution.score.clone(); + let submitted_score = raw_solution.score; ensure!( Self::minimum_untrusted_score().map_or(true, |min_score| { - sp_npos_elections::is_score_better(submitted_score, min_score, Perbill::zero()) + submitted_score.strict_threshold_better(min_score, Perbill::zero()) }), FeasibilityError::UntrustedScoreTooLow ); @@ -1364,10 +1509,10 @@ impl Pallet { Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?; // ----- Start building. First, we need some closures. - let cache = helpers::generate_voter_cache::(&snapshot_voters); - let voter_at = helpers::voter_at_fn::(&snapshot_voters); - let target_at = helpers::target_at_fn::(&snapshot_targets); - let voter_index = helpers::voter_index_fn_usize::(&cache); + let cache = helpers::generate_voter_cache::(&snapshot_voters); + let voter_at = helpers::voter_at_fn::(&snapshot_voters); + let target_at = helpers::target_at_fn::(&snapshot_targets); + let voter_index = helpers::voter_index_fn_usize::(&cache); // Then convert solution -> assignment. This will fail if any of the indices are gibberish, // namely any of the voters or targets. @@ -1376,32 +1521,29 @@ impl Pallet { .map_err::(Into::into)?; // Ensure that assignments is correct. - let _ = assignments - .iter() - .map(|ref assignment| { - // Check that assignment.who is actually a voter (defensive-only). - // NOTE: while using the index map from `voter_index` is better than a blind linear - // search, this *still* has room for optimization. Note that we had the index when - // we did `solution -> assignment` and we lost it. Ideal is to keep the index - // around. - - // Defensive-only: must exist in the snapshot. - let snapshot_index = - voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; - // Defensive-only: index comes from the snapshot, must exist. - let (_voter, _stake, targets) = - snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; - - // Check that all of the targets are valid based on the snapshot. - if assignment.distribution.iter().any(|(d, _)| !targets.contains(d)) { - return Err(FeasibilityError::InvalidVote) - } - Ok(()) - }) - .collect::>()?; + let _ = assignments.iter().try_for_each(|assignment| { + // Check that assignment.who is actually a voter (defensive-only). + // NOTE: while using the index map from `voter_index` is better than a blind linear + // search, this *still* has room for optimization. Note that we had the index when + // we did `solution -> assignment` and we lost it. Ideal is to keep the index + // around. + + // Defensive-only: must exist in the snapshot. + let snapshot_index = + voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; + // Defensive-only: index comes from the snapshot, must exist. + let (_voter, _stake, targets) = + snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; + + // Check that all of the targets are valid based on the snapshot. + if assignment.distribution.iter().any(|(d, _)| !targets.contains(d)) { + return Err(FeasibilityError::InvalidVote) + } + Ok(()) + })?; // ----- Start building support. First, we need one more closure. - let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); + let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); // This might fail if the normalization fails. Very unlikely. See `integrity_test`. let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of) @@ -1412,6 +1554,10 @@ impl Pallet { let known_score = supports.evaluate(); ensure!(known_score == score, FeasibilityError::InvalidScore); + // Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`. + let supports = supports + .try_into() + .defensive_map_err(|_| FeasibilityError::BoundedConversionFailed)?; Ok(ReadySolution { supports, compute, score }) } @@ -1431,7 +1577,7 @@ impl Pallet { Self::kill_snapshot(); } - fn do_elect() -> Result, ElectionError> { + fn do_elect() -> Result, ElectionError> { // We have to unconditionally try finalizing the signed phase here. There are only two // possibilities: // @@ -1441,23 +1587,27 @@ impl Pallet { // inexpensive (1 read of an empty vector). let _ = Self::finalize_signed_phase(); >::take() - .map_or_else( - || { - T::Fallback::elect() - .map_err(|fe| ElectionError::Fallback(fe)) - .map(|supports| (supports, ElectionCompute::Fallback)) - }, - |ReadySolution { supports, compute, .. }| Ok((supports, compute)), - ) - .map(|(supports, compute)| { - Self::deposit_event(Event::ElectionFinalized(Some(compute))); + .ok_or(ElectionError::::NothingQueued) + .or_else(|_| { + T::Fallback::instant_elect(None, None) + .map_err(|fe| ElectionError::Fallback(fe)) + .and_then(|supports| { + Ok(ReadySolution { + supports, + score: Default::default(), + compute: ElectionCompute::Fallback, + }) + }) + }) + .map(|ReadySolution { compute, score, supports }| { + Self::deposit_event(Event::ElectionFinalized { compute, score }); if Self::round() != 1 { log!(info, "Finalized election round with compute {:?}.", compute); } supports }) .map_err(|err| { - Self::deposit_event(Event::ElectionFinalized(None)); + Self::deposit_event(Event::ElectionFailed); if Self::round() != 1 { log!(warn, "Failed to finalize election round. reason {:?}", err); } @@ -1476,11 +1626,23 @@ impl Pallet { } } -impl ElectionProvider for Pallet { +impl ElectionProviderBase for Pallet { + type AccountId = T::AccountId; + type BlockNumber = T::BlockNumber; type Error = ElectionError; + type MaxWinners = T::MaxWinners; type DataProvider = T::DataProvider; +} + +impl ElectionProvider for Pallet { + fn ongoing() -> bool { + match Self::current_phase() { + Phase::Off => false, + _ => true, + } + } - fn elect() -> Result, Self::Error> { + fn elect() -> Result, Self::Error> { match Self::do_elect() { Ok(supports) => { // All went okay, record the weight, put sign to be Off, clean snapshot, etc. @@ -1501,7 +1663,7 @@ impl ElectionProvider for Pallet { /// number. pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction { let error_number = match error { - DispatchError::Module { error, .. } => error, + DispatchError::Module(ModuleError { error, .. }) => error[0], _ => 0, }; InvalidTransaction::Custom(error_number) @@ -1518,7 +1680,7 @@ mod feasibility_check { raw_solution, roll_to, EpochLength, ExtBuilder, MultiPhase, Runtime, SignedPhase, TargetIndex, UnsignedPhase, VoterIndex, }; - use frame_support::assert_noop; + use frame_support::{assert_noop, assert_ok}; const COMPUTE: ElectionCompute = ElectionCompute::OnChain; @@ -1555,7 +1717,7 @@ mod feasibility_check { } #[test] - fn desired_targets() { + fn desired_targets_gets_capped() { ExtBuilder::default().desired_targets(8).build_and_execute(|| { roll_to(::get() - ::get() - ::get()); assert!(MultiPhase::current_phase().is_signed()); @@ -1563,8 +1725,30 @@ mod feasibility_check { let raw = raw_solution(); assert_eq!(raw.solution.unique_targets().len(), 4); - assert_eq!(MultiPhase::desired_targets().unwrap(), 8); + // desired_targets is capped to the number of targets which is 4 + assert_eq!(MultiPhase::desired_targets().unwrap(), 4); + + // It should succeed + assert_ok!(MultiPhase::feasibility_check(raw, COMPUTE)); + }) + } + + #[test] + fn less_than_desired_targets_fails() { + ExtBuilder::default().desired_targets(8).build_and_execute(|| { + roll_to(::get() - ::get() - ::get()); + assert!(MultiPhase::current_phase().is_signed()); + let mut raw = raw_solution(); + + assert_eq!(raw.solution.unique_targets().len(), 4); + // desired_targets is capped to the number of targets which is 4 + assert_eq!(MultiPhase::desired_targets().unwrap(), 4); + + // Force the number of winners to be bigger to fail + raw.solution.votes1[0].1 = 4; + + // It should succeed assert_noop!( MultiPhase::feasibility_check(raw, COMPUTE), FeasibilityError::WrongWinnerCount, @@ -1672,7 +1856,7 @@ mod feasibility_check { assert_eq!(MultiPhase::snapshot().unwrap().voters.len(), 8); // Simply faff with the score. - solution.score[0] += 1; + solution.score.minimal_stake += 1; assert_noop!( MultiPhase::feasibility_check(solution, COMPUTE), @@ -1687,14 +1871,14 @@ mod tests { use super::*; use crate::{ mock::{ - multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MultiPhase, - Runtime, SignedMaxSubmissions, System, TargetIndex, Targets, + multi_phase_events, raw_solution, roll_to, roll_to_signed, roll_to_unsigned, AccountId, + ExtBuilder, MockWeightInfo, MockedWeightInfo, MultiPhase, Runtime, RuntimeOrigin, + SignedMaxSubmissions, System, TargetIndex, Targets, }, Phase, }; - use frame_election_provider_support::ElectionProvider; use frame_support::{assert_noop, assert_ok}; - use sp_npos_elections::Support; + use sp_npos_elections::{BalancingConfig, Support}; #[test] fn phase_rotation_works() { @@ -1712,9 +1896,9 @@ mod tests { assert!(MultiPhase::snapshot().is_none()); assert_eq!(MultiPhase::round(), 1); - roll_to(15); + roll_to_signed(); assert_eq!(MultiPhase::current_phase(), Phase::Signed); - assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted(1)]); + assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted { round: 1 }]); assert!(MultiPhase::snapshot().is_some()); assert_eq!(MultiPhase::round(), 1); @@ -1723,11 +1907,14 @@ mod tests { assert!(MultiPhase::snapshot().is_some()); assert_eq!(MultiPhase::round(), 1); - roll_to(25); + roll_to_unsigned(); assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); assert_eq!( multi_phase_events(), - vec![Event::SignedPhaseStarted(1), Event::UnsignedPhaseStarted(1)], + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::UnsignedPhaseStarted { round: 1 } + ], ); assert!(MultiPhase::snapshot().is_some()); @@ -1753,11 +1940,29 @@ mod tests { roll_to(44); assert!(MultiPhase::current_phase().is_off()); - roll_to(45); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); roll_to(55); assert!(MultiPhase::current_phase().is_unsigned_open_at(55)); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: ElectionScore { + minimal_stake: 0, + sum_stake: 0, + sum_stake_squared: 0 + } + }, + Event::SignedPhaseStarted { round: 2 }, + Event::UnsignedPhaseStarted { round: 2 } + ] + ); }) } @@ -1781,6 +1986,21 @@ mod tests { assert!(MultiPhase::current_phase().is_off()); assert!(MultiPhase::snapshot().is_none()); + + assert_eq!( + multi_phase_events(), + vec![ + Event::UnsignedPhaseStarted { round: 1 }, + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: ElectionScore { + minimal_stake: 0, + sum_stake: 0, + sum_stake_squared: 0 + } + } + ] + ); }); } @@ -1793,7 +2013,7 @@ mod tests { roll_to(19); assert!(MultiPhase::current_phase().is_off()); - roll_to(20); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); assert!(MultiPhase::snapshot().is_some()); @@ -1804,6 +2024,21 @@ mod tests { assert!(MultiPhase::current_phase().is_off()); assert!(MultiPhase::snapshot().is_none()); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: ElectionScore { + minimal_stake: 0, + sum_stake: 0, + sum_stake_squared: 0 + } + } + ] + ) }); } @@ -1826,6 +2061,14 @@ mod tests { assert_ok!(MultiPhase::elect()); assert!(MultiPhase::current_phase().is_off()); + + assert_eq!( + multi_phase_events(), + vec![Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: ElectionScore { minimal_stake: 0, sum_stake: 0, sum_stake_squared: 0 } + }] + ); }); } @@ -1834,24 +2077,24 @@ mod tests { // An early termination in the signed phase, with no queued solution. ExtBuilder::default().build_and_execute(|| { // Signed phase started at block 15 and will end at 25. - roll_to(14); - assert_eq!(MultiPhase::current_phase(), Phase::Off); - roll_to(15); - assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted(1)]); + roll_to_signed(); + assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted { round: 1 }]); assert_eq!(MultiPhase::current_phase(), Phase::Signed); assert_eq!(MultiPhase::round(), 1); // An unexpected call to elect. - roll_to(20); assert_ok!(MultiPhase::elect()); // We surely can't have any feasible solutions. This will cause an on-chain election. assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted(1), - Event::ElectionFinalized(Some(ElectionCompute::Fallback)) + Event::SignedPhaseStarted { round: 1 }, + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: Default::default() + } ], ); // All storage items must be cleared. @@ -1869,26 +2112,25 @@ mod tests { // an early termination in the signed phase, with no queued solution. ExtBuilder::default().build_and_execute(|| { // signed phase started at block 15 and will end at 25. - roll_to(14); - assert_eq!(MultiPhase::current_phase(), Phase::Off); - roll_to(15); - assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted(1)]); + roll_to_signed(); + assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted { round: 1 }]); assert_eq!(MultiPhase::current_phase(), Phase::Signed); assert_eq!(MultiPhase::round(), 1); // fill the queue with signed submissions for s in 0..SignedMaxSubmissions::get() { - let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() }; + let solution = RawSolution { + score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() }, + ..Default::default() + }; assert_ok!(MultiPhase::submit( - crate::mock::Origin::signed(99), - Box::new(solution), - MultiPhase::signed_submissions().len() as u32 + crate::mock::RuntimeOrigin::signed(99), + Box::new(solution) )); } // an unexpected call to elect. - roll_to(20); assert_ok!(MultiPhase::elect()); // all storage items must be cleared. @@ -1898,13 +2140,119 @@ mod tests { assert!(MultiPhase::desired_targets().is_none()); assert!(MultiPhase::queued_solution().is_none()); assert!(MultiPhase::signed_submissions().is_empty()); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Slashed { account: 99, value: 5 }, + Event::Slashed { account: 99, value: 5 }, + Event::Slashed { account: 99, value: 5 }, + Event::Slashed { account: 99, value: 5 }, + Event::Slashed { account: 99, value: 5 }, + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: ElectionScore { + minimal_stake: 0, + sum_stake: 0, + sum_stake_squared: 0 + } + } + ] + ); + }) + } + + #[test] + fn check_events_with_compute_signed() { + ExtBuilder::default().build_and_execute(|| { + roll_to_signed(); + assert!(MultiPhase::current_phase().is_signed()); + + let solution = raw_solution(); + assert_ok!(MultiPhase::submit( + crate::mock::RuntimeOrigin::signed(99), + Box::new(solution) + )); + + roll_to(30); + assert_ok!(MultiPhase::elect()); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Rewarded { account: 99, value: 7 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::ElectionFinalized { + compute: ElectionCompute::Signed, + score: ElectionScore { + minimal_stake: 40, + sum_stake: 100, + sum_stake_squared: 5200 + } + } + ], + ); + }) + } + + #[test] + fn check_events_with_compute_unsigned() { + ExtBuilder::default().build_and_execute(|| { + roll_to_unsigned(); + assert!(MultiPhase::current_phase().is_unsigned()); + + // ensure we have snapshots in place. + assert!(MultiPhase::snapshot().is_some()); + assert_eq!(MultiPhase::desired_targets().unwrap(), 2); + + // mine seq_phragmen solution with 2 iters. + let (solution, witness) = MultiPhase::mine_solution().unwrap(); + + // ensure this solution is valid. + assert!(MultiPhase::queued_solution().is_none()); + assert_ok!(MultiPhase::submit_unsigned( + crate::mock::RuntimeOrigin::none(), + Box::new(solution), + witness + )); + assert!(MultiPhase::queued_solution().is_some()); + + assert_ok!(MultiPhase::elect()); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Unsigned, + prev_ejected: false + }, + Event::ElectionFinalized { + compute: ElectionCompute::Unsigned, + score: ElectionScore { + minimal_stake: 40, + sum_stake: 100, + sum_stake_squared: 5200 + } + } + ], + ); }) } #[test] fn fallback_strategy_works() { ExtBuilder::default().onchain_fallback(true).build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); // Zilch solutions thus far, but we get a result. @@ -1917,11 +2265,27 @@ mod tests { (30, Support { total: 40, voters: vec![(2, 5), (4, 5), (30, 30)] }), (40, Support { total: 60, voters: vec![(2, 5), (3, 10), (4, 5), (40, 40)] }) ] - ) + ); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: ElectionScore { + minimal_stake: 0, + sum_stake: 0, + sum_stake_squared: 0 + } + } + ] + ); }); ExtBuilder::default().onchain_fallback(false).build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); // Zilch solutions thus far. @@ -1929,11 +2293,71 @@ mod tests { assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback.")); // phase is now emergency. assert_eq!(MultiPhase::current_phase(), Phase::Emergency); + // snapshot is still there until election finalizes. + assert!(MultiPhase::snapshot().is_some()); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::ElectionFailed + ] + ); + }) + } + + #[test] + fn governance_fallback_works() { + ExtBuilder::default().onchain_fallback(false).build_and_execute(|| { + roll_to_unsigned(); + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); + + // Zilch solutions thus far. + assert!(MultiPhase::queued_solution().is_none()); + assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback.")); + + // phase is now emergency. + assert_eq!(MultiPhase::current_phase(), Phase::Emergency); + assert!(MultiPhase::queued_solution().is_none()); + assert!(MultiPhase::snapshot().is_some()); + + // no single account can trigger this + assert_noop!( + MultiPhase::governance_fallback(RuntimeOrigin::signed(99), None, None), + DispatchError::BadOrigin + ); + + // only root can + assert_ok!(MultiPhase::governance_fallback(RuntimeOrigin::root(), None, None)); + // something is queued now + assert!(MultiPhase::queued_solution().is_some()); + // next election call with fix everything.; + assert!(MultiPhase::elect().is_ok()); + assert_eq!(MultiPhase::current_phase(), Phase::Off); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::ElectionFailed, + Event::SolutionStored { + compute: ElectionCompute::Fallback, + prev_ejected: false + }, + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: Default::default() + } + ] + ); }) } #[test] - fn snapshot_creation_fails_if_too_big() { + fn snapshot_too_big_failure_onchain_fallback() { + // the `MockStaking` is designed such that if it has too many targets, it simply fails. ExtBuilder::default().build_and_execute(|| { Targets::set((0..(TargetIndex::max_value() as AccountId) + 1).collect::>()); @@ -1946,29 +2370,87 @@ mod tests { assert_eq!(MultiPhase::current_phase(), Phase::Off); // On-chain backup works though. - roll_to(29); let supports = MultiPhase::elect().unwrap(); assert!(supports.len() > 0); + + assert_eq!( + multi_phase_events(), + vec![Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: ElectionScore { minimal_stake: 0, sum_stake: 0, sum_stake_squared: 0 } + }] + ); + }); + } + + #[test] + fn snapshot_too_big_failure_no_fallback() { + // and if the backup mode is nothing, we go into the emergency mode.. + ExtBuilder::default().onchain_fallback(false).build_and_execute(|| { + crate::mock::Targets::set( + (0..(TargetIndex::max_value() as AccountId) + 1).collect::>(), + ); + + // Signed phase failed to open. + roll_to(15); + assert_eq!(MultiPhase::current_phase(), Phase::Off); + + // Unsigned phase failed to open. + roll_to(25); + assert_eq!(MultiPhase::current_phase(), Phase::Off); + + roll_to(29); + let err = MultiPhase::elect().unwrap_err(); + assert_eq!(err, ElectionError::Fallback("NoFallback.")); + assert_eq!(MultiPhase::current_phase(), Phase::Emergency); + + assert_eq!(multi_phase_events(), vec![Event::ElectionFailed]); + }); + } + + #[test] + fn snapshot_too_big_truncate() { + // but if there are too many voters, we simply truncate them. + ExtBuilder::default().build_and_execute(|| { + // we have 8 voters in total. + assert_eq!(crate::mock::Voters::get().len(), 8); + // but we want to take 2. + crate::mock::MaxElectingVoters::set(2); + + // Signed phase opens just fine. + roll_to_signed(); + assert_eq!(MultiPhase::current_phase(), Phase::Signed); + + assert_eq!( + MultiPhase::snapshot_metadata().unwrap(), + SolutionOrSnapshotSize { voters: 2, targets: 4 } + ); }) } #[test] fn untrusted_score_verification_is_respected() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert_eq!(MultiPhase::current_phase(), Phase::Signed); // set the solution balancing to get the desired score. - crate::mock::Balancing::set(Some((2, 0))); + crate::mock::Balancing::set(Some(BalancingConfig { iterations: 2, tolerance: 0 })); - let (solution, _) = MultiPhase::mine_solution::<::Solver>().unwrap(); - // Default solution has a score of [50, 100, 5000]. - assert_eq!(solution.score, [50, 100, 5000]); + let (solution, _) = MultiPhase::mine_solution().unwrap(); + // Default solution's score. + assert!(matches!(solution.score, ElectionScore { minimal_stake: 50, .. })); - >::put([49, 0, 0]); + >::put(ElectionScore { + minimal_stake: 49, + ..Default::default() + }); assert_ok!(MultiPhase::feasibility_check(solution.clone(), ElectionCompute::Signed)); - >::put([51, 0, 0]); + >::put(ElectionScore { + minimal_stake: 51, + ..Default::default() + }); assert_noop!( MultiPhase::feasibility_check(solution, ElectionCompute::Signed), FeasibilityError::UntrustedScoreTooLow, @@ -1979,7 +2461,7 @@ mod tests { #[test] fn number_of_voters_allowed_2sec_block() { // Just a rough estimate with the substrate weights. - assert!(!MockWeightInfo::get()); + assert_eq!(MockWeightInfo::get(), MockedWeightInfo::Real); let all_voters: u32 = 10_000; let all_targets: u32 = 5_000; @@ -1994,8 +2476,8 @@ mod tests { }; let mut active = 1; - while weight_with(active) <= - ::BlockWeights::get().max_block || + while weight_with(active) + .all_lte(::BlockWeights::get().max_block) || active == all_voters { active += 1; diff --git a/frame/election-provider-multi-phase/src/migrations.rs b/frame/election-provider-multi-phase/src/migrations.rs new file mode 100644 index 0000000000000..77efe0d0c5e92 --- /dev/null +++ b/frame/election-provider-multi-phase/src/migrations.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 mod v1 { + use frame_support::{ + storage::unhashed, + traits::{Defensive, GetStorageVersion, OnRuntimeUpgrade}, + BoundedVec, + }; + use sp_std::collections::btree_map::BTreeMap; + + use crate::*; + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log!( + info, + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + + if current == 1 && onchain == 0 { + if SignedSubmissionIndices::::exists() { + // This needs to be tested at a both a block height where this value exists, and + // when it doesn't. + let now = frame_system::Pallet::::block_number(); + let map = unhashed::get::>( + &SignedSubmissionIndices::::hashed_key(), + ) + .defensive_unwrap_or_default(); + let vector = map + .into_iter() + .map(|(score, index)| (score, now, index)) + .collect::>(); + + log!( + debug, + "{:?} SignedSubmissionIndices read from storage (max: {:?})", + vector.len(), + T::SignedMaxSubmissions::get() + ); + + // defensive-only, assuming a constant `SignedMaxSubmissions`. + let bounded = BoundedVec::<_, _>::truncate_from(vector); + SignedSubmissionIndices::::put(bounded); + + log!(info, "SignedSubmissionIndices existed and got migrated"); + } else { + log!(info, "SignedSubmissionIndices did NOT exist."); + } + + current.put::>(); + T::DbWeight::get().reads_writes(2, 1) + } else { + log!(info, "Migration did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + } +} diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 28a15291e6520..347a4f19185f9 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,13 +16,20 @@ // limitations under the License. use super::*; -use crate as multi_phase; +use crate::{self as multi_phase, unsigned::MinerConfig}; use frame_election_provider_support::{ - data_provider, onchain, ElectionDataProvider, SequentialPhragmen, + data_provider, + onchain::{self}, + ElectionDataProvider, NposSolution, SequentialPhragmen, }; -pub use frame_support::{assert_noop, assert_ok}; -use frame_support::{parameter_types, traits::Hooks, weights::Weight}; -use multi_phase::unsigned::{IndexAssignmentOf, Voter}; +pub use frame_support::{assert_noop, assert_ok, pallet_prelude::GetDefault}; +use frame_support::{ + bounded_vec, parameter_types, + traits::{ConstU32, Hooks}, + weights::{constants, Weight}, + BoundedVec, +}; +use multi_phase::unsigned::{IndexAssignmentOf, VoterOf}; use parking_lot::RwLock; use sp_core::{ offchain::{ @@ -32,18 +39,19 @@ use sp_core::{ H256, }; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, ElectionResult, - EvaluateSupport, ExtendedBalance, NposSolution, + assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, BalancingConfig, + ElectionResult, EvaluateSupport, }; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, PerU16, }; -use std::{convert::TryFrom, sync::Arc}; +use std::sync::Arc; pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = + sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( pub enum Runtime where @@ -63,9 +71,14 @@ pub(crate) type BlockNumber = u64; pub(crate) type VoterIndex = u32; pub(crate) type TargetIndex = u16; -sp_npos_elections::generate_solution_type!( +frame_election_provider_support::generate_solution_type!( #[compact] - pub struct TestNposSolution::(16) + pub struct TestNposSolution::< + VoterIndex = VoterIndex, + TargetIndex = TargetIndex, + Accuracy = PerU16, + MaxVoters = ConstU32::<2_000> + >(16) ); /// All events of this pallet. @@ -73,7 +86,7 @@ pub(crate) fn multi_phase_events() -> Vec> { System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let Event::MultiPhase(inner) = e { Some(inner) } else { None }) + .filter_map(|e| if let RuntimeEvent::MultiPhase(inner) = e { Some(inner) } else { None }) .collect::>() } @@ -86,6 +99,17 @@ pub fn roll_to(n: BlockNumber) { } } +pub fn roll_to_unsigned() { + while !matches!(MultiPhase::current_phase(), Phase::Unsigned(_)) { + roll_to(System::block_number() + 1); + } +} +pub fn roll_to_signed() { + while !matches!(MultiPhase::current_phase(), Phase::Signed) { + roll_to(System::block_number() + 1); + } +} + pub fn roll_to_with_ocw(n: BlockNumber) { let now = System::block_number(); for i in now + 1..=n { @@ -96,7 +120,7 @@ pub fn roll_to_with_ocw(n: BlockNumber) { } pub struct TrimHelpers { - pub voters: Vec>, + pub voters: Vec>, pub assignments: Vec>, pub encoded_size_of: Box]) -> Result>, @@ -127,16 +151,11 @@ pub fn trim_helpers() -> TrimHelpers { let desired_targets = MultiPhase::desired_targets().unwrap(); - let ElectionResult { mut assignments, .. } = seq_phragmen::<_, SolutionAccuracyOf>( - desired_targets as usize, - targets.clone(), - voters.clone(), - None, - ) - .unwrap(); + let ElectionResult::<_, SolutionAccuracyOf> { mut assignments, .. } = + seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap(); // sort by decreasing order of stake - assignments.sort_unstable_by_key(|assignment| { + assignments.sort_by_key(|assignment| { std::cmp::Reverse(stakes.get(&assignment.who).cloned().unwrap_or_default()) }); @@ -159,14 +178,8 @@ pub fn raw_solution() -> RawSolution> { let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap(); let desired_targets = MultiPhase::desired_targets().unwrap(); - let ElectionResult { winners: _, assignments } = - seq_phragmen::<_, SolutionAccuracyOf>( - desired_targets as usize, - targets.clone(), - voters.clone(), - None, - ) - .unwrap(); + let ElectionResult::<_, SolutionAccuracyOf> { winners: _, assignments } = + seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap(); // closures let cache = helpers::generate_voter_cache::(&voters); @@ -197,16 +210,16 @@ pub fn witness() -> SolutionOrSnapshotSize { impl frame_system::Config for Runtime { type SS58Prefix = (); type BaseCallFilter = frame_support::traits::Everything; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = BlockNumber; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type DbWeight = (); type BlockLength = (); @@ -218,18 +231,22 @@ impl frame_system::Config for Runtime { type OnKilledAccount = (); type SystemWeightInfo = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); parameter_types! { pub const ExistentialDeposit: u64 = 1; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights - ::with_sensible_defaults(2 * frame_support::weights::constants::WEIGHT_PER_SECOND, NORMAL_DISPATCH_RATIO); + ::with_sensible_defaults( + Weight::from_parts(2u64 * constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + NORMAL_DISPATCH_RATIO, + ); } impl pallet_balances::Config for Runtime { type Balance = Balance; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; @@ -239,153 +256,133 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } +#[derive(Default, Eq, PartialEq, Debug, Clone, Copy)] +pub enum MockedWeightInfo { + #[default] + Basic, + Complex, + Real, +} + parameter_types! { pub static Targets: Vec = vec![10, 20, 30, 40]; - pub static Voters: Vec<(AccountId, VoteWeight, Vec)> = vec![ - (1, 10, vec![10, 20]), - (2, 10, vec![30, 40]), - (3, 10, vec![40]), - (4, 10, vec![10, 20, 30, 40]), + pub static Voters: Vec> = vec![ + (1, 10, bounded_vec![10, 20]), + (2, 10, bounded_vec![30, 40]), + (3, 10, bounded_vec![40]), + (4, 10, bounded_vec![10, 20, 30, 40]), // self votes. - (10, 10, vec![10]), - (20, 20, vec![20]), - (30, 30, vec![30]), - (40, 40, vec![40]), + (10, 10, bounded_vec![10]), + (20, 20, bounded_vec![20]), + (30, 30, bounded_vec![30]), + (40, 40, bounded_vec![40]), ]; pub static DesiredTargets: u32 = 2; pub static SignedPhase: BlockNumber = 10; pub static UnsignedPhase: BlockNumber = 5; pub static SignedMaxSubmissions: u32 = 5; + pub static SignedMaxRefunds: u32 = 1; pub static SignedDepositBase: Balance = 5; pub static SignedDepositByte: Balance = 0; pub static SignedDepositWeight: Balance = 0; pub static SignedRewardBase: Balance = 7; pub static SignedMaxWeight: Weight = BlockWeights::get().max_block; pub static MinerTxPriority: u64 = 100; - pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); + pub static BetterSignedThreshold: Perbill = Perbill::zero(); + pub static BetterUnsignedThreshold: Perbill = Perbill::zero(); pub static OffchainRepeat: BlockNumber = 5; pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; pub static MinerMaxLength: u32 = 256; - pub static MockWeightInfo: bool = false; + pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real; + pub static MaxElectingVoters: VoterIndex = u32::max_value(); + pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value(); + pub static MaxWinners: u32 = 200; pub static EpochLength: u64 = 30; - pub static OnChianFallback: bool = true; + pub static OnChainFallback: bool = true; } -impl onchain::Config for Runtime { - type Accuracy = sp_runtime::Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Runtime; + type Solver = SequentialPhragmen, Balancing>; type DataProvider = StakingMock; + type WeightInfo = (); + type MaxWinners = MaxWinners; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; } pub struct MockFallback; -impl ElectionProvider for MockFallback { +impl ElectionProviderBase for MockFallback { + type AccountId = AccountId; + type BlockNumber = u64; type Error = &'static str; type DataProvider = StakingMock; - - fn elect() -> Result, Self::Error> { - if OnChianFallback::get() { - onchain::OnChainSequentialPhragmen::::elect() - .map_err(|_| "OnChainSequentialPhragmen failed") - } else { - super::NoFallback::::elect() - } - } + type MaxWinners = MaxWinners; } -// Hopefully this won't be too much of a hassle to maintain. -pub struct DualMockWeightInfo; -impl multi_phase::weights::WeightInfo for DualMockWeightInfo { - fn on_initialize_nothing() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::on_initialize_nothing() - } - } - fn create_snapshot_internal() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::create_snapshot_internal() - } - } - fn on_initialize_open_signed() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::on_initialize_open_signed() - } - } - fn on_initialize_open_unsigned() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::on_initialize_open_unsigned() - } - } - fn elect_queued(a: u32, d: u32) -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::elect_queued(a, d) - } - } - fn finalize_signed_phase_accept_solution() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::finalize_signed_phase_accept_solution() - } - } - fn finalize_signed_phase_reject_solution() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::finalize_signed_phase_reject_solution() - } - } - fn submit(c: u32) -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::submit(c) - } - } - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight { - if MockWeightInfo::get() { - // 10 base - // 5 per edge. - (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) - } else { - <() as multi_phase::weights::WeightInfo>::submit_unsigned(v, t, a, d) - } - } - fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight { - if MockWeightInfo::get() { - // 10 base - // 5 per edge. - (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) +impl InstantElectionProvider for MockFallback { + fn instant_elect( + max_voters: Option, + max_targets: Option, + ) -> Result, Self::Error> { + if OnChainFallback::get() { + onchain::OnChainExecution::::instant_elect(max_voters, max_targets) + .map_err(|_| "onchain::OnChainExecution failed.") } else { - <() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d) + Err("NoFallback.") } } } parameter_types! { - pub static Balancing: Option<(usize, ExtendedBalance)> = Some((0, 0)); + pub static Balancing: Option = Some( BalancingConfig { iterations: 0, tolerance: 0 } ); +} + +pub struct TestBenchmarkingConfig; +impl BenchmarkingConfig for TestBenchmarkingConfig { + const VOTERS: [u32; 2] = [400, 600]; + const ACTIVE_VOTERS: [u32; 2] = [100, 300]; + const TARGETS: [u32; 2] = [200, 400]; + const DESIRED_TARGETS: [u32; 2] = [100, 180]; + + const SNAPSHOT_MAXIMUM_VOTERS: u32 = 1000; + const MINER_MAXIMUM_VOTERS: u32 = 1000; + + const MAXIMUM_TARGETS: u32 = 200; +} + +impl MinerConfig for Runtime { + type AccountId = AccountId; + type MaxLength = MinerMaxLength; + type MaxWeight = MinerMaxWeight; + type MaxVotesPerVoter = ::MaxVotesPerVoter; + type Solution = TestNposSolution; + + fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight { + match MockWeightInfo::get() { + MockedWeightInfo::Basic => Weight::from_ref_time( + (10 as u64).saturating_add((5 as u64).saturating_mul(a as u64)), + ), + MockedWeightInfo::Complex => + Weight::from_ref_time((0 * v + 0 * t + 1000 * a + 0 * d) as u64), + MockedWeightInfo::Real => + <() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d), + } + } } impl crate::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; type EstimateCallFee = frame_support::traits::ConstU32<8>; type SignedPhase = SignedPhase; type UnsignedPhase = UnsignedPhase; - type SolutionImprovementThreshold = SolutionImprovementThreshold; + type BetterUnsignedThreshold = BetterUnsignedThreshold; + type BetterSignedThreshold = BetterSignedThreshold; type OffchainRepeat = OffchainRepeat; - type MinerMaxWeight = MinerMaxWeight; - type MinerMaxLength = MinerMaxLength; type MinerTxPriority = MinerTxPriority; type SignedRewardBase = SignedRewardBase; type SignedDepositBase = SignedDepositBase; @@ -393,49 +390,68 @@ impl crate::Config for Runtime { type SignedDepositWeight = (); type SignedMaxWeight = SignedMaxWeight; type SignedMaxSubmissions = SignedMaxSubmissions; + type SignedMaxRefunds = SignedMaxRefunds; type SlashHandler = (); type RewardHandler = (); type DataProvider = StakingMock; - type WeightInfo = DualMockWeightInfo; - type BenchmarkingConfig = (); + type WeightInfo = (); + type BenchmarkingConfig = TestBenchmarkingConfig; type Fallback = MockFallback; + type GovernanceFallback = + frame_election_provider_support::onchain::OnChainExecution; type ForceOrigin = frame_system::EnsureRoot; - type Solution = TestNposSolution; + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; + type MaxWinners = MaxWinners; + type MinerConfig = Self; type Solver = SequentialPhragmen, Balancing>; } impl frame_system::offchain::SendTransactionTypes for Runtime where - Call: From, + RuntimeCall: From, { - type OverarchingCall = Call; + type OverarchingCall = RuntimeCall; type Extrinsic = Extrinsic; } -pub type Extrinsic = sp_runtime::testing::TestXt; +pub type Extrinsic = sp_runtime::testing::TestXt; + +parameter_types! { + pub MaxNominations: u32 = ::LIMIT as u32; + // only used in testing to manipulate mock behaviour + pub static DataProviderAllowBadData: bool = false; +} #[derive(Default)] pub struct ExtBuilder {} pub struct StakingMock; -impl ElectionDataProvider for StakingMock { - const MAXIMUM_VOTES_PER_VOTER: u32 = ::LIMIT as u32; - fn targets(maybe_max_len: Option) -> data_provider::Result> { +impl ElectionDataProvider for StakingMock { + type AccountId = AccountId; + type BlockNumber = u64; + type MaxVotesPerVoter = MaxNominations; + + fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { let targets = Targets::get(); - if maybe_max_len.map_or(false, |max_len| targets.len() > max_len) { + if !DataProviderAllowBadData::get() && + maybe_max_len.map_or(false, |max_len| targets.len() > max_len) + { return Err("Targets too big") } Ok(targets) } - fn voters( + fn electing_voters( maybe_max_len: Option, - ) -> data_provider::Result)>> { - let voters = Voters::get(); - if maybe_max_len.map_or(false, |max_len| voters.len() > max_len) { - return Err("Voters too big") + ) -> data_provider::Result>> { + let mut voters = Voters::get(); + if !DataProviderAllowBadData::get() { + if let Some(max_len) = maybe_max_len { + voters.truncate(max_len) + } } Ok(voters) @@ -451,7 +467,7 @@ impl ElectionDataProvider for StakingMock { #[cfg(feature = "runtime-benchmarks")] fn put_snapshot( - voters: Vec<(AccountId, VoteWeight, Vec)>, + voters: Vec>, targets: Vec, _target_stake: Option, ) { @@ -466,7 +482,11 @@ impl ElectionDataProvider for StakingMock { } #[cfg(feature = "runtime-benchmarks")] - fn add_voter(voter: AccountId, weight: VoteWeight, targets: Vec) { + fn add_voter( + voter: AccountId, + weight: VoteWeight, + targets: frame_support::BoundedVec, + ) { let mut current = Voters::get(); current.push((voter, weight, targets)); Voters::set(current); @@ -481,7 +501,7 @@ impl ElectionDataProvider for StakingMock { // to be on-par with staking, we add a self vote as well. the stake is really not that // important. let mut current = Voters::get(); - current.push((target, ExistentialDeposit::get() as u64, vec![target])); + current.push((target, ExistentialDeposit::get() as u64, bounded_vec![target])); Voters::set(current); } } @@ -491,8 +511,12 @@ impl ExtBuilder { ::set(p); self } - pub fn solution_improvement_threshold(self, p: Perbill) -> Self { - ::set(p); + pub fn better_signed_threshold(self, p: Perbill) -> Self { + ::set(p); + self + } + pub fn better_unsigned_threshold(self, p: Perbill) -> Self { + ::set(p); self } pub fn phases(self, signed: BlockNumber, unsigned: BlockNumber) -> Self { @@ -501,14 +525,14 @@ impl ExtBuilder { self } pub fn onchain_fallback(self, onchain: bool) -> Self { - ::set(onchain); + ::set(onchain); self } pub fn miner_weight(self, weight: Weight) -> Self { ::set(weight); self } - pub fn mock_weight_info(self, mock: bool) -> Self { + pub fn mock_weight_info(self, mock: MockedWeightInfo) -> Self { ::set(mock); self } @@ -516,7 +540,12 @@ impl ExtBuilder { ::set(t); self } - pub fn add_voter(self, who: AccountId, stake: Balance, targets: Vec) -> Self { + pub fn add_voter( + self, + who: AccountId, + stake: Balance, + targets: BoundedVec, + ) -> Self { VOTERS.with(|v| v.borrow_mut().push((who, stake, targets))); self } @@ -543,6 +572,12 @@ impl ExtBuilder { balances: vec![ // bunch of account for submitting stuff only. (99, 100), + (100, 100), + (101, 100), + (102, 100), + (103, 100), + (104, 100), + (105, 100), (999, 100), (9999, 100), ], diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 61215059c53a6..12d39e83b6c09 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,17 +18,18 @@ //! The signed phase implementation. use crate::{ - Config, ElectionCompute, Pallet, QueuedSolution, RawSolution, ReadySolution, - SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap, SolutionOf, - SolutionOrSnapshotSize, Weight, WeightInfo, + unsigned::MinerConfig, Config, ElectionCompute, Pallet, QueuedSolution, RawSolution, + ReadySolution, SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap, + SolutionOf, SolutionOrSnapshotSize, Weight, WeightInfo, }; use codec::{Decode, Encode, HasCompact}; -use frame_support::{ - storage::bounded_btree_map::BoundedBTreeMap, - traits::{Currency, Get, OnUnbalanced, ReservableCurrency}, +use frame_election_provider_support::NposSolution; +use frame_support::traits::{ + defensive_prelude::*, Currency, Get, OnUnbalanced, ReservableCurrency, }; use sp_arithmetic::traits::SaturatedConversion; -use sp_npos_elections::{is_score_better, ElectionScore, NposSolution}; +use sp_core::bounded::BoundedVec; +use sp_npos_elections::ElectionScore; use sp_runtime::{ traits::{Saturating, Zero}, RuntimeDebug, @@ -36,13 +37,13 @@ use sp_runtime::{ use sp_std::{ cmp::Ordering, collections::{btree_map::BTreeMap, btree_set::BTreeSet}, - ops::Deref, + vec::Vec, }; /// A raw, unchecked signed submission. /// /// This is just a wrapper around [`RawSolution`] and some additional info. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, scale_info::TypeInfo)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] pub struct SignedSubmission { /// Who submitted this solution. pub who: AccountId, @@ -50,8 +51,8 @@ pub struct SignedSubmission { pub deposit: Balance, /// The raw solution itself. pub raw_solution: RawSolution, - /// The reward that should potentially be paid for this solution, if accepted. - pub reward: Balance, + // The estimated fee `who` paid to submit the solution. + pub call_fee: Balance, } impl Ord for SignedSubmission @@ -91,11 +92,18 @@ pub type PositiveImbalanceOf = <::Currency as Currency< pub type NegativeImbalanceOf = <::Currency as Currency< ::AccountId, >>::NegativeImbalance; -pub type SignedSubmissionOf = - SignedSubmission<::AccountId, BalanceOf, SolutionOf>; +pub type SignedSubmissionOf = SignedSubmission< + ::AccountId, + BalanceOf, + <::MinerConfig as MinerConfig>::Solution, +>; -pub type SubmissionIndicesOf = - BoundedBTreeMap::SignedMaxSubmissions>; +/// Always sorted vector of a score, submitted at the given block number, which can be found at the +/// given index (`u32`) of the `SignedSubmissionsMap`. +pub type SubmissionIndicesOf = BoundedVec< + (ElectionScore, ::BlockNumber, u32), + ::SignedMaxSubmissions, +>; /// Outcome of [`SignedSubmissions::insert`]. pub enum InsertResult { @@ -121,6 +129,16 @@ pub struct SignedSubmissions { } impl SignedSubmissions { + /// `true` if the structure is empty. + pub fn is_empty(&self) -> bool { + self.indices.is_empty() + } + + /// Get the length of submitted solutions. + pub fn len(&self) -> usize { + self.indices.len() + } + /// Get the signed submissions from storage. pub fn get() -> Self { let submissions = SignedSubmissions { @@ -129,10 +147,12 @@ impl SignedSubmissions { insertion_overlay: BTreeMap::new(), deletion_overlay: BTreeSet::new(), }; + // validate that the stored state is sane debug_assert!(submissions .indices - .values() + .iter() + .map(|(_, _, index)| index) .copied() .max() .map_or(true, |max_idx| submissions.next_idx > max_idx,)); @@ -150,7 +170,8 @@ impl SignedSubmissions { .map_or(true, |max_idx| self.next_idx > max_idx,)); debug_assert!(self .indices - .values() + .iter() + .map(|(_, _, index)| index) .copied() .max() .map_or(true, |max_idx| self.next_idx > max_idx,)); @@ -167,87 +188,100 @@ impl SignedSubmissions { } /// Get the submission at a particular index. - fn get_submission(&self, idx: u32) -> Option> { - if self.deletion_overlay.contains(&idx) { - // Note: can't actually remove the item from the insertion overlay (if present) - // because we don't want to use `&mut self` here. There may be some kind of - // `RefCell` optimization possible here in the future. + fn get_submission(&self, index: u32) -> Option> { + if self.deletion_overlay.contains(&index) { + // Note: can't actually remove the item from the insertion overlay (if present) because + // we don't want to use `&mut self` here. There may be some kind of `RefCell` + // optimization possible here in the future. None } else { self.insertion_overlay - .get(&idx) + .get(&index) .cloned() - .or_else(|| SignedSubmissionsMap::::try_get(idx).ok()) + .or_else(|| SignedSubmissionsMap::::get(index)) } } /// Perform three operations: /// - /// - Remove a submission (identified by score) - /// - Insert a new submission (identified by score and insertion index) - /// - Return the submission which was removed. - /// - /// Note: in the case that `weakest_score` is not present in `self.indices`, this will return - /// `None` without inserting the new submission and without further notice. + /// - Remove the solution at the given position of `self.indices`. + /// - Insert a new submission (identified by score and insertion index), if provided. + /// - Return the submission which was removed, if any. /// - /// Note: this does not enforce any ordering relation between the submission removed and that - /// inserted. + /// The call site must ensure that `remove_pos` is a valid index. If otherwise, `None` is + /// silently returned. /// /// Note: this doesn't insert into `insertion_overlay`, the optional new insertion must be - /// inserted into `insertion_overlay` to keep the variable `self` in a valid state. + /// inserted into `insertion_overlay` to keep the variable `self` in a valid state. fn swap_out_submission( &mut self, - remove_score: ElectionScore, - insert: Option<(ElectionScore, u32)>, + remove_pos: usize, + insert: Option<(ElectionScore, T::BlockNumber, u32)>, ) -> Option> { - let remove_idx = self.indices.remove(&remove_score)?; - if let Some((insert_score, insert_idx)) = insert { + if remove_pos >= self.indices.len() { + return None + } + + // safe: index was just checked in the line above. + let (_, _, remove_index) = self.indices.remove(remove_pos); + + if let Some((insert_score, block_number, insert_idx)) = insert { self.indices - .try_insert(insert_score, insert_idx) + .try_push((insert_score, block_number, insert_idx)) .expect("just removed an item, we must be under capacity; qed"); } - self.insertion_overlay.remove(&remove_idx).or_else(|| { - (!self.deletion_overlay.contains(&remove_idx)) + self.insertion_overlay.remove(&remove_index).or_else(|| { + (!self.deletion_overlay.contains(&remove_index)) .then(|| { - self.deletion_overlay.insert(remove_idx); - SignedSubmissionsMap::::try_get(remove_idx).ok() + self.deletion_overlay.insert(remove_index); + SignedSubmissionsMap::::get(remove_index) }) .flatten() }) } + /// Remove the signed submission with the highest score from the set. + pub fn pop_last(&mut self) -> Option> { + let best_index = self.indices.len().checked_sub(1)?; + self.swap_out_submission(best_index, None) + } + /// Iterate through the set of signed submissions in order of increasing score. pub fn iter(&self) -> impl '_ + Iterator> { - self.indices.iter().filter_map(move |(_score, &idx)| { - let maybe_submission = self.get_submission(idx); - if maybe_submission.is_none() { - log!( - error, - "SignedSubmissions internal state is invalid (idx {}); \ - there is a logic error in code handling signed solution submissions", - idx, - ) - } - maybe_submission - }) + self.indices + .iter() + .filter_map(move |(_score, _bn, idx)| self.get_submission(*idx).defensive()) } /// Empty the set of signed submissions, returning an iterator of signed submissions in - /// arbitrary order. + /// order of submission. /// /// Note that if the iterator is dropped without consuming all elements, not all may be removed /// from the underlying `SignedSubmissionsMap`, putting the storages into an invalid state. /// /// Note that, like `put`, this function consumes `Self` and modifies storage. - fn drain(mut self) -> impl Iterator> { + fn drain_submitted_order(mut self) -> impl Iterator> { + let mut keys = SignedSubmissionsMap::::iter_keys() + .filter(|k| { + if self.deletion_overlay.contains(k) { + // Remove submissions that should be deleted. + SignedSubmissionsMap::::remove(k); + false + } else { + true + } + }) + .chain(self.insertion_overlay.keys().copied()) + .collect::>(); + keys.sort(); + SignedSubmissionIndices::::kill(); SignedSubmissionNextIndex::::kill(); - let insertion_overlay = sp_std::mem::take(&mut self.insertion_overlay); - SignedSubmissionsMap::::drain() - .filter(move |(k, _v)| !self.deletion_overlay.contains(k)) - .map(|(_k, v)| v) - .chain(insertion_overlay.into_iter().map(|(_k, v)| v)) + + keys.into_iter().filter_map(move |index| { + SignedSubmissionsMap::::take(index).or_else(|| self.insertion_overlay.remove(&index)) + }) } /// Decode the length of the signed submissions without actually reading the entire struct into @@ -265,68 +299,54 @@ impl SignedSubmissions { /// to `is_score_better`, we do not change anything. pub fn insert(&mut self, submission: SignedSubmissionOf) -> InsertResult { // verify the expectation that we never reuse an index - debug_assert!(!self.indices.values().any(|&idx| idx == self.next_idx)); - - let weakest = match self.indices.try_insert(submission.raw_solution.score, self.next_idx) { - Ok(Some(prev_idx)) => { - // a submission of equal score was already present in the set; - // no point editing the actual backing map as we know that the newer solution can't - // be better than the old. However, we do need to put the old value back. - self.indices - .try_insert(submission.raw_solution.score, prev_idx) - .expect("didn't change the map size; qed"); - return InsertResult::NotInserted - }, - Ok(None) => { - // successfully inserted into the set; no need to take out weakest member - None - }, - Err((insert_score, insert_idx)) => { - // could not insert into the set because it is full. - // note that we short-circuit return here in case the iteration produces `None`. - // If there wasn't a weakest entry to remove, then there must be a capacity of 0, - // which means that we can't meaningfully proceed. - let weakest_score = match self.indices.iter().next() { + debug_assert!(!self.indices.iter().map(|(_, _, x)| x).any(|&idx| idx == self.next_idx)); + let block_number = frame_system::Pallet::::block_number(); + + let maybe_weakest = match self.indices.try_push(( + submission.raw_solution.score, + block_number, + self.next_idx, + )) { + Ok(_) => None, + Err(_) => { + // the queue is full -- if this is better, insert it. + let weakest_score = match self.indices.iter().next().defensive() { None => return InsertResult::NotInserted, - Some((score, _)) => *score, + Some((score, _, _)) => *score, }; - let threshold = T::SolutionImprovementThreshold::get(); + let threshold = T::BetterSignedThreshold::get(); // if we haven't improved on the weakest score, don't change anything. - if !is_score_better(insert_score, weakest_score, threshold) { + if !submission.raw_solution.score.strict_threshold_better(weakest_score, threshold) + { return InsertResult::NotInserted } - self.swap_out_submission(weakest_score, Some((insert_score, insert_idx))) + self.swap_out_submission( + 0, // swap out the worse one, which is always index 0. + Some((submission.raw_solution.score, block_number, self.next_idx)), + ) }, }; + // this is the ONLY place that we insert, and we sort post insertion. If scores are the + // same, we sort based on reverse of submission block number. + self.indices + .sort_by(|(score1, bn1, _), (score2, bn2, _)| match score1.cmp(score2) { + Ordering::Equal => bn1.cmp(&bn2).reverse(), + x => x, + }); + // we've taken out the weakest, so update the storage map and the next index debug_assert!(!self.insertion_overlay.contains_key(&self.next_idx)); self.insertion_overlay.insert(self.next_idx, submission); debug_assert!(!self.deletion_overlay.contains(&self.next_idx)); self.next_idx += 1; - match weakest { + match maybe_weakest { Some(weakest) => InsertResult::InsertedEjecting(weakest), None => InsertResult::Inserted, } } - - /// Remove the signed submission with the highest score from the set. - pub fn pop_last(&mut self) -> Option> { - let (score, _) = self.indices.iter().rev().next()?; - // deref in advance to prevent mutable-immutable borrow conflict - let score = *score; - self.swap_out_submission(score, None) - } -} - -impl Deref for SignedSubmissions { - type Target = SubmissionIndicesOf; - - fn deref(&self) -> &Self::Target { - &self.indices - } } impl Pallet { @@ -361,13 +381,20 @@ impl Pallet { Self::snapshot_metadata().unwrap_or_default(); while let Some(best) = all_submissions.pop_last() { - let SignedSubmission { raw_solution, who, deposit, reward } = best; + log!( + debug, + "finalized_signed: trying to verify from {:?} score {:?}", + best.who, + best.raw_solution.score + ); + let SignedSubmission { raw_solution, who, deposit, call_fee } = best; let active_voters = raw_solution.solution.voter_count() as u32; let feasibility_weight = { // defensive only: at the end of signed phase, snapshot will exits. - let desired_targets = Self::desired_targets().unwrap_or_default(); + let desired_targets = Self::desired_targets().defensive_unwrap_or_default(); T::WeightInfo::feasibility_check(voters, targets, active_voters, desired_targets) }; + // the feasibility check itself has some weight weight = weight.saturating_add(feasibility_weight); match Self::feasibility_check(raw_solution, ElectionCompute::Signed) { @@ -376,15 +403,17 @@ impl Pallet { ready_solution, &who, deposit, - reward, + call_fee, ); found_solution = true; + log!(debug, "finalized_signed: found a valid solution"); weight = weight .saturating_add(T::WeightInfo::finalize_signed_phase_accept_solution()); break }, Err(_) => { + log!(warn, "finalized_signed: invalid signed submission found, slashing."); Self::finalize_signed_phase_reject_solution(&who, deposit); weight = weight .saturating_add(T::WeightInfo::finalize_signed_phase_reject_solution()); @@ -395,10 +424,23 @@ impl Pallet { // Any unprocessed solution is pointless to even consider. Feasible or malicious, // they didn't end up being used. Unreserve the bonds. let discarded = all_submissions.len(); - for SignedSubmission { who, deposit, .. } in all_submissions.drain() { + let mut refund_count = 0; + let max_refunds = T::SignedMaxRefunds::get(); + + for SignedSubmission { who, deposit, call_fee, .. } in + all_submissions.drain_submitted_order() + { + if refund_count < max_refunds { + // Refund fee + let positive_imbalance = T::Currency::deposit_creating(&who, call_fee); + T::RewardHandler::on_unbalanced(positive_imbalance); + refund_count += 1; + } + + // Unreserve deposit let _remaining = T::Currency::unreserve(&who, deposit); - weight = weight.saturating_add(T::DbWeight::get().writes(1)); debug_assert!(_remaining.is_zero()); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2)); } debug_assert!(!SignedSubmissionIndices::::exists()); @@ -420,23 +462,25 @@ impl Pallet { /// /// Infallible pub fn finalize_signed_phase_accept_solution( - ready_solution: ReadySolution, + ready_solution: ReadySolution, who: &T::AccountId, deposit: BalanceOf, - reward: BalanceOf, + call_fee: BalanceOf, ) { // write this ready solution. >::put(ready_solution); + let reward = T::SignedRewardBase::get(); // emit reward event - Self::deposit_event(crate::Event::Rewarded(who.clone(), reward)); + Self::deposit_event(crate::Event::Rewarded { account: who.clone(), value: reward }); - // unreserve deposit. + // Unreserve deposit. let _remaining = T::Currency::unreserve(who, deposit); debug_assert!(_remaining.is_zero()); - // Reward. - let positive_imbalance = T::Currency::deposit_creating(who, reward); + // Reward and refund the call fee. + let positive_imbalance = + T::Currency::deposit_creating(who, reward.saturating_add(call_fee)); T::RewardHandler::on_unbalanced(positive_imbalance); } @@ -446,18 +490,18 @@ impl Pallet { /// /// Infallible pub fn finalize_signed_phase_reject_solution(who: &T::AccountId, deposit: BalanceOf) { - Self::deposit_event(crate::Event::Slashed(who.clone(), deposit)); + Self::deposit_event(crate::Event::Slashed { account: who.clone(), value: deposit }); let (negative_imbalance, _remaining) = T::Currency::slash_reserved(who, deposit); debug_assert!(_remaining.is_zero()); T::SlashHandler::on_unbalanced(negative_imbalance); } - /// The feasibility weight of the given raw solution. - pub fn feasibility_weight_of( - raw_solution: &RawSolution>, + /// The weight of the given raw solution. + pub fn solution_weight_of( + raw_solution: &RawSolution>, size: SolutionOrSnapshotSize, ) -> Weight { - T::WeightInfo::feasibility_check( + T::MinerConfig::solution_weight( size.voters, size.targets, raw_solution.solution.voter_count() as u32, @@ -473,16 +517,16 @@ impl Pallet { /// 2. a per-byte deposit, for renting the state usage. /// 3. a per-weight deposit, for the potential weight usage in an upcoming on_initialize pub fn deposit_for( - raw_solution: &RawSolution>, + raw_solution: &RawSolution>, size: SolutionOrSnapshotSize, ) -> BalanceOf { let encoded_len: u32 = raw_solution.encoded_size().saturated_into(); let encoded_len: BalanceOf = encoded_len.into(); - let feasibility_weight = Self::feasibility_weight_of(raw_solution, size); + let feasibility_weight = Self::solution_weight_of(raw_solution, size); let len_deposit = T::SignedDepositByte::get().saturating_mul(encoded_len); - let weight_deposit = - T::SignedDepositWeight::get().saturating_mul(feasibility_weight.saturated_into()); + let weight_deposit = T::SignedDepositWeight::get() + .saturating_mul(feasibility_weight.ref_time().saturated_into()); T::SignedDepositBase::get() .saturating_add(len_deposit) @@ -493,25 +537,8 @@ impl Pallet { #[cfg(test)] mod tests { use super::*; - use crate::{ - mock::{ - balances, raw_solution, roll_to, ExtBuilder, MultiPhase, Origin, Runtime, - SignedMaxSubmissions, SignedMaxWeight, - }, - Error, Phase, - }; - use frame_support::{assert_noop, assert_ok, assert_storage_noop, dispatch::DispatchResult}; - - fn submit_with_witness( - origin: Origin, - solution: RawSolution>, - ) -> DispatchResult { - MultiPhase::submit( - origin, - Box::new(solution), - MultiPhase::signed_submissions().len() as u32, - ) - } + use crate::{mock::*, ElectionCompute, ElectionError, Error, Event, Perbill, Phase}; + use frame_support::{assert_noop, assert_ok, assert_storage_noop}; #[test] fn cannot_submit_too_early() { @@ -524,27 +551,54 @@ mod tests { let solution = raw_solution(); assert_noop!( - submit_with_witness(Origin::signed(10), solution), + MultiPhase::submit(RuntimeOrigin::signed(10), Box::new(solution)), Error::::PreDispatchEarlySubmission, ); }) } #[test] - fn wrong_witness_fails() { + fn data_provider_should_respect_target_limits() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); - assert!(MultiPhase::current_phase().is_signed()); + // given a reduced expectation of maximum electable targets + MaxElectableTargets::set(2); + // and a data provider that does not respect limits + DataProviderAllowBadData::set(true); - let solution = raw_solution(); - // submit this once correctly - assert_ok!(submit_with_witness(Origin::signed(99), solution.clone())); - assert_eq!(MultiPhase::signed_submissions().len(), 1); + assert_noop!( + MultiPhase::create_snapshot(), + ElectionError::DataProvider("Snapshot too big for submission."), + ); + }) + } + + #[test] + fn data_provider_should_respect_voter_limits() { + ExtBuilder::default().build_and_execute(|| { + // given a reduced expectation of maximum electing voters + MaxElectingVoters::set(2); + // and a data provider that does not respect limits + DataProviderAllowBadData::set(true); - // now try and cheat by passing a lower queue length assert_noop!( - MultiPhase::submit(Origin::signed(99), Box::new(solution), 0), - Error::::SignedInvalidWitness, + MultiPhase::create_snapshot(), + ElectionError::DataProvider("Snapshot too big for submission."), + ); + }) + } + + #[test] + fn desired_targets_greater_than_max_winners() { + ExtBuilder::default().build_and_execute(|| { + // given desired_targets bigger than MaxWinners + DesiredTargets::set(4); + MaxWinners::set(3); + + // snapshot not created because data provider returned an unexpected number of + // desired_targets + assert_noop!( + MultiPhase::create_snapshot_external(), + ElectionError::DataProvider("desired_targets must not be greater than MaxWinners."), ); }) } @@ -552,62 +606,88 @@ mod tests { #[test] fn should_pay_deposit() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); let solution = raw_solution(); assert_eq!(balances(&99), (100, 0)); - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); assert_eq!(balances(&99), (95, 5)); assert_eq!(MultiPhase::signed_submissions().iter().next().unwrap().deposit, 5); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false } + ] + ); }) } #[test] fn good_solution_is_rewarded() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); let solution = raw_solution(); assert_eq!(balances(&99), (100, 0)); - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); assert_eq!(balances(&99), (95, 5)); assert!(MultiPhase::finalize_signed_phase()); assert_eq!(balances(&99), (100 + 7 + 8, 0)); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Rewarded { account: 99, value: 7 } + ] + ); }) } #[test] fn bad_solution_is_slashed() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); let mut solution = raw_solution(); assert_eq!(balances(&99), (100, 0)); // make the solution invalid. - solution.score[0] += 1; + solution.score.minimal_stake += 1; - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); assert_eq!(balances(&99), (95, 5)); // no good solution was stored. assert!(!MultiPhase::finalize_signed_phase()); // and the bond is gone. assert_eq!(balances(&99), (95, 0)); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Slashed { account: 99, value: 5 } + ] + ); }) } #[test] fn suppressed_solution_gets_bond_back() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); let mut solution = raw_solution(); @@ -615,11 +695,11 @@ mod tests { assert_eq!(balances(&999), (100, 0)); // submit as correct. - assert_ok!(submit_with_witness(Origin::signed(99), solution.clone())); + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution.clone()))); // make the solution invalid and weaker. - solution.score[0] -= 1; - assert_ok!(submit_with_witness(Origin::signed(999), solution)); + solution.score.minimal_stake -= 1; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(999), Box::new(solution))); assert_eq!(balances(&99), (95, 5)); assert_eq!(balances(&999), (95, 5)); @@ -628,102 +708,258 @@ mod tests { // 99 is rewarded. assert_eq!(balances(&99), (100 + 7 + 8, 0)); - // 999 gets everything back. - assert_eq!(balances(&999), (100, 0)); + // 999 gets everything back, including the call fee. + assert_eq!(balances(&999), (100 + 8, 0)); + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Rewarded { account: 99, value: 7 } + ] + ); }) } #[test] fn cannot_submit_worse_with_full_queue() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); for s in 0..SignedMaxSubmissions::get() { // score is always getting better - let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() }; - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + let solution = RawSolution { + score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); } // weaker. - let solution = RawSolution { score: [4, 0, 0], ..Default::default() }; + let solution = RawSolution { + score: ElectionScore { minimal_stake: 4, ..Default::default() }, + ..Default::default() + }; assert_noop!( - submit_with_witness(Origin::signed(99), solution), + MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution)), Error::::SignedQueueFull, ); }) } + #[test] + fn call_fee_refund_is_limited_by_signed_max_refunds() { + ExtBuilder::default().build_and_execute(|| { + roll_to_signed(); + assert!(MultiPhase::current_phase().is_signed()); + assert_eq!(SignedMaxRefunds::get(), 1); + assert!(SignedMaxSubmissions::get() > 2); + + for s in 0..SignedMaxSubmissions::get() { + let account = 99 + s as u64; + Balances::make_free_balance_be(&account, 100); + // score is always decreasing + let mut solution = raw_solution(); + solution.score.minimal_stake -= s as u128; + + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(account), Box::new(solution))); + assert_eq!(balances(&account), (95, 5)); + } + + assert_ok!(MultiPhase::do_elect()); + + for s in 0..SignedMaxSubmissions::get() { + let account = 99 + s as u64; + // lower accounts have higher scores + if s == 0 { + // winning solution always gets call fee + reward + assert_eq!(balances(&account), (100 + 8 + 7, 0)) + } else if s == 1 { + // 1 runner up gets their call fee refunded + assert_eq!(balances(&account), (100 + 8, 0)) + } else { + // all other solutions don't get a call fee refund + assert_eq!(balances(&account), (100, 0)); + } + } + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Rewarded { account: 99, value: 7 }, + Event::ElectionFinalized { + compute: ElectionCompute::Signed, + score: ElectionScore { + minimal_stake: 40, + sum_stake: 100, + sum_stake_squared: 5200 + } + } + ] + ); + }); + } + + #[test] + fn cannot_submit_worse_with_full_queue_depends_on_threshold() { + ExtBuilder::default() + .signed_max_submission(1) + .better_signed_threshold(Perbill::from_percent(20)) + .build_and_execute(|| { + roll_to_signed(); + assert!(MultiPhase::current_phase().is_signed()); + + let mut solution = RawSolution { + score: ElectionScore { + minimal_stake: 5u128, + sum_stake: 0u128, + sum_stake_squared: 10u128, + }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); + + // This is 10% better, so does not meet the 20% threshold and is therefore rejected. + solution = RawSolution { + score: ElectionScore { + minimal_stake: 5u128, + sum_stake: 0u128, + sum_stake_squared: 9u128, + }, + ..Default::default() + }; + + assert_noop!( + MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution)), + Error::::SignedQueueFull, + ); + + // This is however 30% better and should therefore be accepted. + solution = RawSolution { + score: ElectionScore { + minimal_stake: 5u128, + sum_stake: 0u128, + sum_stake_squared: 7u128, + }, + ..Default::default() + }; + + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + prev_ejected: true + } + ] + ); + }) + } + #[test] fn weakest_is_removed_if_better_provided() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); for s in 0..SignedMaxSubmissions::get() { + let account = 99 + s as u64; + Balances::make_free_balance_be(&account, 100); // score is always getting better - let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() }; - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + let solution = RawSolution { + score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(account), Box::new(solution))); + assert_eq!(balances(&account), (95, 5)); } assert_eq!( MultiPhase::signed_submissions() .iter() - .map(|s| s.raw_solution.score[0]) + .map(|s| s.raw_solution.score.minimal_stake) .collect::>(), vec![5, 6, 7, 8, 9] ); // better. - let solution = RawSolution { score: [20, 0, 0], ..Default::default() }; - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + let solution = RawSolution { + score: ElectionScore { minimal_stake: 20, ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(999), Box::new(solution))); // the one with score 5 was rejected, the new one inserted. assert_eq!( MultiPhase::signed_submissions() .iter() - .map(|s| s.raw_solution.score[0]) + .map(|s| s.raw_solution.score.minimal_stake) .collect::>(), vec![6, 7, 8, 9, 20] ); + + // the submitter of the ejected solution does *not* get a call fee refund + assert_eq!(balances(&(99 + 0)), (100, 0)); }) } #[test] - fn replace_weakest_works() { - ExtBuilder::default().build_and_execute(|| { - roll_to(15); + fn replace_weakest_by_score_works() { + ExtBuilder::default().signed_max_submission(3).build_and_execute(|| { + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); for s in 1..SignedMaxSubmissions::get() { // score is always getting better - let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() }; - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + let solution = RawSolution { + score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); } - let solution = RawSolution { score: [4, 0, 0], ..Default::default() }; - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + let solution = RawSolution { + score: ElectionScore { minimal_stake: 4, ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); assert_eq!( MultiPhase::signed_submissions() .iter() - .map(|s| s.raw_solution.score[0]) + .map(|s| s.raw_solution.score.minimal_stake) .collect::>(), - vec![4, 6, 7, 8, 9], + vec![4, 6, 7], ); // better. - let solution = RawSolution { score: [5, 0, 0], ..Default::default() }; - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + let solution = RawSolution { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); // the one with score 5 was rejected, the new one inserted. assert_eq!( MultiPhase::signed_submissions() .iter() - .map(|s| s.raw_solution.score[0]) + .map(|s| s.raw_solution.score.minimal_stake) .collect::>(), - vec![5, 6, 7, 8, 9], + vec![5, 6, 7], ); }) } @@ -731,21 +967,27 @@ mod tests { #[test] fn early_ejected_solution_gets_bond_back() { ExtBuilder::default().signed_deposit(2, 0, 0).build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); for s in 0..SignedMaxSubmissions::get() { // score is always getting better - let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() }; - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + let solution = RawSolution { + score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); } assert_eq!(balances(&99).1, 2 * 5); assert_eq!(balances(&999).1, 0); // better. - let solution = RawSolution { score: [20, 0, 0], ..Default::default() }; - assert_ok!(submit_with_witness(Origin::signed(999), solution)); + let solution = RawSolution { + score: ElectionScore { minimal_stake: 20, ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(999), Box::new(solution))); // got one bond back. assert_eq!(balances(&99).1, 2 * 4); @@ -754,32 +996,133 @@ mod tests { } #[test] - fn equally_good_solution_is_not_accepted() { + fn equally_good_solution_is_not_accepted_when_queue_full() { + // because in ordering of solutions, an older solution has higher priority and should stay. ExtBuilder::default().signed_max_submission(3).build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); for i in 0..SignedMaxSubmissions::get() { - let solution = RawSolution { score: [(5 + i).into(), 0, 0], ..Default::default() }; - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + let solution = RawSolution { + score: ElectionScore { minimal_stake: (5 + i).into(), ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); } + assert_eq!( MultiPhase::signed_submissions() .iter() - .map(|s| s.raw_solution.score[0]) + .map(|s| s.raw_solution.score.minimal_stake) .collect::>(), vec![5, 6, 7] ); // 5 is not accepted. This will only cause processing with no benefit. - let solution = RawSolution { score: [5, 0, 0], ..Default::default() }; + let solution = RawSolution { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; assert_noop!( - submit_with_witness(Origin::signed(99), solution), + MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution)), Error::::SignedQueueFull, ); }) } + #[test] + fn equally_good_solution_is_accepted_when_queue_not_full() { + // because in ordering of solutions, an older solution has higher priority and should stay. + ExtBuilder::default().signed_max_submission(3).build_and_execute(|| { + roll_to(15); + assert!(MultiPhase::current_phase().is_signed()); + + let solution = RawSolution { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); + + assert_eq!( + MultiPhase::signed_submissions() + .iter() + .map(|s| (s.who, s.raw_solution.score.minimal_stake,)) + .collect::>(), + vec![(99, 5)] + ); + + roll_to(16); + let solution = RawSolution { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(999), Box::new(solution))); + + assert_eq!( + MultiPhase::signed_submissions() + .iter() + .map(|s| (s.who, s.raw_solution.score.minimal_stake,)) + .collect::>(), + vec![(999, 5), (99, 5)] + ); + + let solution = RawSolution { + score: ElectionScore { minimal_stake: 6, ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(9999), Box::new(solution))); + + assert_eq!( + MultiPhase::signed_submissions() + .iter() + .map(|s| (s.who, s.raw_solution.score.minimal_stake,)) + .collect::>(), + vec![(999, 5), (99, 5), (9999, 6)] + ); + }) + } + + #[test] + fn all_equal_score() { + // because in ordering of solutions, an older solution has higher priority and should stay. + ExtBuilder::default().signed_max_submission(3).build_and_execute(|| { + roll_to(15); + assert!(MultiPhase::current_phase().is_signed()); + + for i in 0..SignedMaxSubmissions::get() { + roll_to((15 + i).into()); + let solution = raw_solution(); + assert_ok!(MultiPhase::submit( + RuntimeOrigin::signed(100 + i as AccountId), + Box::new(solution) + )); + } + + assert_eq!( + MultiPhase::signed_submissions() + .iter() + .map(|s| (s.who, s.raw_solution.score.minimal_stake)) + .collect::>(), + vec![(102, 40), (101, 40), (100, 40)] + ); + + roll_to(25); + + // The first one that will actually get verified is the last one. + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Rewarded { account: 100, value: 7 }, + Event::UnsignedPhaseStarted { round: 1 } + ] + ); + }) + } + #[test] fn all_in_one_signed_submission_scenario() { // a combination of: @@ -787,27 +1130,28 @@ mod tests { // - bad_solution_is_slashed // - suppressed_solution_gets_bond_back ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); assert_eq!(balances(&99), (100, 0)); assert_eq!(balances(&999), (100, 0)); assert_eq!(balances(&9999), (100, 0)); + let solution = raw_solution(); // submit a correct one. - assert_ok!(submit_with_witness(Origin::signed(99), solution.clone())); + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution.clone()))); // make the solution invalidly better and submit. This ought to be slashed. let mut solution_999 = solution.clone(); - solution_999.score[0] += 1; - assert_ok!(submit_with_witness(Origin::signed(999), solution_999)); + solution_999.score.minimal_stake += 1; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(999), Box::new(solution_999))); // make the solution invalidly worse and submit. This ought to be suppressed and // returned. let mut solution_9999 = solution.clone(); - solution_9999.score[0] -= 1; - assert_ok!(submit_with_witness(Origin::signed(9999), solution_9999)); + solution_9999.score.minimal_stake -= 1; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(9999), Box::new(solution_9999))); assert_eq!( MultiPhase::signed_submissions().iter().map(|x| x.who).collect::>(), @@ -821,41 +1165,54 @@ mod tests { assert_eq!(balances(&99), (100 + 7 + 8, 0)); // 999 is slashed. assert_eq!(balances(&999), (95, 0)); - // 9999 gets everything back. - assert_eq!(balances(&9999), (100, 0)); + // 9999 gets everything back, including the call fee. + assert_eq!(balances(&9999), (100 + 8, 0)); + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Slashed { account: 999, value: 5 }, + Event::Rewarded { account: 99, value: 7 } + ] + ); }) } #[test] fn cannot_consume_too_much_future_weight() { ExtBuilder::default() - .signed_weight(40) - .mock_weight_info(true) + .signed_weight(Weight::from_ref_time(40).set_proof_size(u64::MAX)) + .mock_weight_info(MockedWeightInfo::Basic) .build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); - let (raw, witness) = - MultiPhase::mine_solution::<::Solver>().unwrap(); - let solution_weight = ::WeightInfo::feasibility_check( + let (raw, witness) = MultiPhase::mine_solution().unwrap(); + let solution_weight = ::solution_weight( witness.voters, witness.targets, raw.solution.voter_count() as u32, raw.solution.unique_targets().len() as u32, ); // default solution will have 5 edges (5 * 5 + 10) - assert_eq!(solution_weight, 35); + assert_eq!(solution_weight, Weight::from_ref_time(35)); assert_eq!(raw.solution.voter_count(), 5); - assert_eq!(::SignedMaxWeight::get(), 40); + assert_eq!( + ::SignedMaxWeight::get(), + Weight::from_ref_time(40).set_proof_size(u64::MAX) + ); - assert_ok!(submit_with_witness(Origin::signed(99), raw.clone())); + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(raw.clone()))); - ::set(30); + ::set(Weight::from_ref_time(30).set_proof_size(u64::MAX)); // note: resubmitting the same solution is technically okay as long as the queue has // space. assert_noop!( - submit_with_witness(Origin::signed(99), raw), + MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(raw)), Error::::SignedTooMuchWeight, ); }) @@ -864,14 +1221,14 @@ mod tests { #[test] fn insufficient_deposit_does_not_store_submission() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); let solution = raw_solution(); assert_eq!(balances(&123), (0, 0)); assert_noop!( - submit_with_witness(Origin::signed(123), solution), + MultiPhase::submit(RuntimeOrigin::signed(123), Box::new(solution)), Error::::SignedCannotPayDeposit, ); @@ -884,24 +1241,30 @@ mod tests { #[test] fn insufficient_deposit_with_full_queue_works_properly() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); for s in 0..SignedMaxSubmissions::get() { // score is always getting better - let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() }; - assert_ok!(submit_with_witness(Origin::signed(99), solution)); + let solution = RawSolution { + score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); } // this solution has a higher score than any in the queue let solution = RawSolution { - score: [(5 + SignedMaxSubmissions::get()).into(), 0, 0], + score: ElectionScore { + minimal_stake: (5 + SignedMaxSubmissions::get()).into(), + ..Default::default() + }, ..Default::default() }; assert_eq!(balances(&123), (0, 0)); assert_noop!( - submit_with_witness(Origin::signed(123), solution), + MultiPhase::submit(RuntimeOrigin::signed(123), Box::new(solution)), Error::::SignedCannotPayDeposit, ); @@ -924,19 +1287,28 @@ mod tests { #[test] fn finalize_signed_phase_is_idempotent_given_submissions() { ExtBuilder::default().build_and_execute(|| { - roll_to(15); + roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); let solution = raw_solution(); // submit a correct one. - assert_ok!(submit_with_witness(Origin::signed(99), solution.clone())); + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); // _some_ good solution was stored. assert!(MultiPhase::finalize_signed_phase()); // calling it again doesn't change anything assert_storage_noop!(MultiPhase::finalize_signed_phase()); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Rewarded { account: 99, value: 7 } + ] + ); }) } } diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index af0b79177d86c..7340605dfe621 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,22 +20,21 @@ use crate::{ helpers, Call, Config, ElectionCompute, Error, FeasibilityError, Pallet, RawSolution, ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight, - WeightInfo, }; use codec::Encode; -use frame_election_provider_support::{NposSolver, PerThing128}; -use frame_support::{dispatch::DispatchResult, ensure, traits::Get}; +use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight}; +use frame_support::{dispatch::DispatchResult, ensure, traits::Get, BoundedVec}; use frame_system::offchain::SubmitTransaction; -use sp_arithmetic::Perbill; +use scale_info::TypeInfo; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, is_score_better, - ElectionResult, NposSolution, + assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult, + ElectionScore, }; use sp_runtime::{ offchain::storage::{MutateStorageError, StorageValueRef}, DispatchError, SaturatedConversion, }; -use sp_std::{boxed::Box, cmp::Ordering, convert::TryFrom, vec::Vec}; +use sp_std::prelude::*; /// Storage key used to store the last block number at which offchain worker ran. pub(crate) const OFFCHAIN_LAST_BLOCK: &[u8] = b"parity/multi-phase-unsigned-election"; @@ -47,25 +46,27 @@ pub(crate) const OFFCHAIN_CACHED_CALL: &[u8] = b"parity/multi-phase-unsigned-ele /// A voter's fundamental data: their ID, their stake, and the list of candidates for whom they /// voted. -pub type Voter = ( - ::AccountId, - sp_npos_elections::VoteWeight, - Vec<::AccountId>, -); +pub type VoterOf = frame_election_provider_support::VoterOf<::DataProvider>; + +/// Same as [`VoterOf`], but parameterized by the `MinerConfig`. +pub type MinerVoterOf = frame_election_provider_support::Voter< + ::AccountId, + ::MaxVotesPerVoter, +>; /// The relative distribution of a voter's stake among the winning targets. pub type Assignment = sp_npos_elections::Assignment<::AccountId, SolutionAccuracyOf>; -/// The [`IndexAssignment`][sp_npos_elections::IndexAssignment] type specialized for a particular -/// runtime `T`. -pub type IndexAssignmentOf = sp_npos_elections::IndexAssignmentOf>; +/// The [`IndexAssignment`][frame_election_provider_support::IndexAssignment] type specialized for a +/// particular runtime `T`. +pub type IndexAssignmentOf = frame_election_provider_support::IndexAssignmentOf>; /// Error type of the pallet's [`crate::Config::Solver`]. pub type SolverErrorOf = <::Solver as NposSolver>::Error; /// Error type for operations related to the OCW npos solution miner. #[derive(frame_support::DebugNoBound, frame_support::PartialEqNoBound)] -pub enum MinerError { +pub enum MinerError { /// An internal error in the NPoS elections crate. NposElections(sp_npos_elections::Error), /// Snapshot data was unavailable unexpectedly. @@ -87,25 +88,25 @@ pub enum MinerError { /// There are no more voters to remove to trim the solution. NoMoreVoters, /// An error from the solver. - Solver(SolverErrorOf), + Solver, } -impl From for MinerError { +impl From for MinerError { fn from(e: sp_npos_elections::Error) -> Self { MinerError::NposElections(e) } } -impl From for MinerError { +impl From for MinerError { fn from(e: FeasibilityError) -> Self { MinerError::Feasibility(e) } } /// Save a given call into OCW storage. -fn save_solution(call: &Call) -> Result<(), MinerError> { +fn save_solution(call: &Call) -> Result<(), MinerError> { log!(debug, "saving a call to the offchain storage."); - let storage = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL); + let storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL); match storage.mutate::<_, (), _>(|_| Ok(call.clone())) { Ok(_) => Ok(()), Err(MutateStorageError::ConcurrentModification(_)) => @@ -121,8 +122,8 @@ fn save_solution(call: &Call) -> Result<(), MinerError> { } /// Get a saved solution from OCW storage if it exists. -fn restore_solution() -> Result, MinerError> { - StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL) +fn restore_solution() -> Result, MinerError> { + StorageValueRef::persistent(OFFCHAIN_CACHED_CALL) .get() .ok() .flatten() @@ -132,7 +133,7 @@ fn restore_solution() -> Result, MinerError> { /// Clear a saved solution from OCW storage. pub(super) fn kill_ocw_solution() { log!(debug, "clearing offchain call cache storage."); - let mut storage = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL); + let mut storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL); storage.clear(); } @@ -141,20 +142,36 @@ pub(super) fn kill_ocw_solution() { /// After calling this, the next offchain worker is guaranteed to work, with respect to the /// frequency repeat. fn clear_offchain_repeat_frequency() { - let mut last_block = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK); + let mut last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK); last_block.clear(); } /// `true` when OCW storage contains a solution #[cfg(test)] fn ocw_solution_exists() -> bool { - matches!(StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL).get::>(), Ok(Some(_))) + matches!(StorageValueRef::persistent(OFFCHAIN_CACHED_CALL).get::>(), Ok(Some(_))) } impl Pallet { + /// Mine a new npos solution. + /// + /// The Npos Solver type, `S`, must have the same AccountId and Error type as the + /// [`crate::Config::Solver`] in order to create a unified return type. + pub fn mine_solution( + ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { + let RoundSnapshot { voters, targets } = + Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; + let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; + let (solution, score, size) = Miner::::mine_solution_with_snapshot::< + T::Solver, + >(voters, targets, desired_targets)?; + let round = Self::round(); + Ok((RawSolution { solution, score, round }, size)) + } + /// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit /// if our call's score is greater than that of the cached solution. - pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> { + pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> { log!(debug, "miner attempting to restore or compute an unsigned solution."); let call = restore_solution::() @@ -168,7 +185,7 @@ impl Pallet { Err(MinerError::SolutionCallInvalid) } }) - .or_else::, _>(|error| { + .or_else::(|error| { log!(debug, "restoring solution failed due to {:?}", error); match error { MinerError::NoStoredSolution => { @@ -199,7 +216,7 @@ impl Pallet { } /// Mine a new solution, cache it, and submit it back to the chain as an unsigned transaction. - pub fn mine_check_save_submit() -> Result<(), MinerError> { + pub fn mine_check_save_submit() -> Result<(), MinerError> { log!(debug, "miner attempting to compute an unsigned solution."); let call = Self::mine_checked_call()?; @@ -208,13 +225,12 @@ impl Pallet { } /// Mine a new solution as a call. Performs all checks. - pub fn mine_checked_call() -> Result, MinerError> { + pub fn mine_checked_call() -> Result, MinerError> { // get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID. let (raw_solution, witness) = Self::mine_and_check()?; - let score = raw_solution.score.clone(); - let call: Call = - Call::submit_unsigned { raw_solution: Box::new(raw_solution), witness }.into(); + let score = raw_solution.score; + let call: Call = Call::submit_unsigned { raw_solution: Box::new(raw_solution), witness }; log!( debug, @@ -226,7 +242,7 @@ impl Pallet { Ok(call) } - fn submit_call(call: Call) -> Result<(), MinerError> { + fn submit_call(call: Call) -> Result<(), MinerError> { log!(debug, "miner submitting a solution as an unsigned transaction"); SubmitTransaction::>::submit_unsigned_transaction(call.into()) @@ -237,9 +253,9 @@ impl Pallet { // // Performance: note that it internally clones the provided solution. pub fn basic_checks( - raw_solution: &RawSolution>, + raw_solution: &RawSolution>, solution_type: &str, - ) -> Result<(), MinerError> { + ) -> Result<(), MinerError> { Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| { log!(debug, "pre-dispatch checks failed for {} solution: {:?}", solution_type, err); MinerError::PreDispatchChecksFailed(err) @@ -262,45 +278,158 @@ impl Pallet { /// If you want a checked solution and submit it at the same time, use /// [`Pallet::mine_check_save_submit`]. pub fn mine_and_check( - ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { - let (raw_solution, witness) = Self::mine_solution::()?; + ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { + let (raw_solution, witness) = Self::mine_solution()?; Self::basic_checks(&raw_solution, "mined")?; Ok((raw_solution, witness)) } - /// Mine a new npos solution. + /// Checks if an execution of the offchain worker is permitted at the given block number, or + /// not. /// - /// The Npos Solver type, `S`, must have the same AccountId and Error type as the - /// [`crate::Config::Solver`] in order to create a unified return type. - pub fn mine_solution( - ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> + /// This makes sure that + /// 1. we don't run on previous blocks in case of a re-org + /// 2. we don't run twice within a window of length `T::OffchainRepeat`. + /// + /// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()` + /// is returned, `now` is written in storage and will be used in further calls as the baseline. + pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> { + let threshold = T::OffchainRepeat::get(); + let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK); + + let mutate_stat = last_block.mutate::<_, &'static str, _>( + |maybe_head: Result, _>| { + match maybe_head { + Ok(Some(head)) if now < head => Err("fork."), + Ok(Some(head)) if now >= head && now <= head + threshold => + Err("recently executed."), + Ok(Some(head)) if now > head + threshold => { + // we can run again now. Write the new head. + Ok(now) + }, + _ => { + // value doesn't exists. Probably this node just booted up. Write, and run + Ok(now) + }, + } + }, + ); + + match mutate_stat { + // all good + Ok(_) => Ok(()), + // failed to write. + Err(MutateStorageError::ConcurrentModification(_)) => + Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")), + // fork etc. + Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)), + } + } + + /// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned + /// transaction. + /// + /// Can optionally also be called during dispatch, if needed. + /// + /// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's + /// code, so that we do less and less storage reads here. + pub fn unsigned_pre_dispatch_checks( + raw_solution: &RawSolution>, + ) -> DispatchResult { + // ensure solution is timely. Don't panic yet. This is a cheap check. + ensure!(Self::current_phase().is_unsigned_open(), Error::::PreDispatchEarlySubmission); + + // ensure round is current + ensure!(Self::round() == raw_solution.round, Error::::OcwCallWrongEra); + + // ensure correct number of winners. + ensure!( + Self::desired_targets().unwrap_or_default() == + raw_solution.solution.unique_targets().len() as u32, + Error::::PreDispatchWrongWinnerCount, + ); + + // ensure score is being improved. Panic henceforth. + ensure!( + Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution + .score + .strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())), + Error::::PreDispatchWeakSubmission, + ); + + Ok(()) + } +} + +/// Configurations for a miner that comes with this pallet. +pub trait MinerConfig { + /// The account id type. + type AccountId: Ord + Clone + codec::Codec + sp_std::fmt::Debug; + /// The solution that the miner is mining. + type Solution: codec::Codec + + Default + + PartialEq + + Eq + + Clone + + sp_std::fmt::Debug + + Ord + + NposSolution + + TypeInfo; + /// Maximum number of votes per voter in the snapshots. + type MaxVotesPerVoter; + /// Maximum length of the solution that the miner is allowed to generate. + /// + /// Solutions are trimmed to respect this. + type MaxLength: Get; + /// Maximum weight of the solution that the miner is allowed to generate. + /// + /// Solutions are trimmed to respect this. + /// + /// The weight is computed using `solution_weight`. + type MaxWeight: Get; + /// Something that can compute the weight of a solution. + /// + /// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`]. + fn solution_weight(voters: u32, targets: u32, active_voters: u32, degree: u32) -> Weight; +} + +/// A base miner, suitable to be used for both signed and unsigned submissions. +pub struct Miner(sp_std::marker::PhantomData); +impl Miner { + /// Same as [`Pallet::mine_solution`], but the input snapshot data must be given. + pub fn mine_solution_with_snapshot( + voters: Vec<(T::AccountId, VoteWeight, BoundedVec)>, + targets: Vec, + desired_targets: u32, + ) -> Result<(SolutionOf, ElectionScore, SolutionOrSnapshotSize), MinerError> where - S: NposSolver>, + S: NposSolver, { - let RoundSnapshot { voters, targets } = - Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; - let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; - - S::solve(desired_targets as usize, targets, voters) - .map_err(|e| MinerError::Solver::(e)) - .and_then(|e| Self::prepare_election_result::(e)) + S::solve(desired_targets as usize, targets.clone(), voters.clone()) + .map_err(|e| { + log_no_system!(error, "solver error: {:?}", e); + MinerError::Solver + }) + .and_then(|e| { + Self::prepare_election_result_with_snapshot::( + e, + voters, + targets, + desired_targets, + ) + }) } /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which /// is ready to be submitted to the chain. /// /// Will always reduce the solution as well. - pub fn prepare_election_result( + pub fn prepare_election_result_with_snapshot( election_result: ElectionResult, - ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { - // NOTE: This code path is generally not optimized as it is run offchain. Could use some at - // some point though. - - // storage items. Note: we have already read this from storage, they must be in cache. - let RoundSnapshot { voters, targets } = - Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; - let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; - + voters: Vec<(T::AccountId, VoteWeight, BoundedVec)>, + targets: Vec, + desired_targets: u32, + ) -> Result<(SolutionOf, ElectionScore, SolutionOrSnapshotSize), MinerError> { // now make some helper closures. let cache = helpers::generate_voter_cache::(&voters); let voter_index = helpers::voter_index_fn::(&cache); @@ -362,11 +491,11 @@ impl Pallet { Self::trim_assignments_weight( desired_targets, size, - T::MinerMaxWeight::get(), + T::MaxWeight::get(), &mut index_assignments, ); Self::trim_assignments_length( - T::MinerMaxLength::get(), + T::MaxLength::get(), &mut index_assignments, &encoded_size_of, )?; @@ -377,43 +506,7 @@ impl Pallet { // re-calc score. let score = solution.clone().score(stake_of, voter_at, target_at)?; - let round = Self::round(); - Ok((RawSolution { solution, score, round }, size)) - } - - /// Greedily reduce the size of the solution to fit into the block w.r.t. weight. - /// - /// The weight of the solution is foremost a function of the number of voters (i.e. - /// `assignments.len()`). Aside from this, the other components of the weight are invariant. The - /// number of winners shall not be changed (otherwise the solution is invalid) and the - /// `ElectionSize` is merely a representation of the total number of stakers. - /// - /// Thus, we reside to stripping away some voters from the `assignments`. - /// - /// Note that the solution is already computed, and the winners are elected based on the merit - /// of the entire stake in the system. Nonetheless, some of the voters will be removed further - /// down the line. - /// - /// Indeed, the score must be computed **after** this step. If this step reduces the score too - /// much or remove a winner, then the solution must be discarded **after** this step. - pub fn trim_assignments_weight( - desired_targets: u32, - size: SolutionOrSnapshotSize, - max_weight: Weight, - assignments: &mut Vec>, - ) { - let maximum_allowed_voters = - Self::maximum_voter_for_weight::(desired_targets, size, max_weight); - let removing: usize = - assignments.len().saturating_sub(maximum_allowed_voters.saturated_into()); - log!( - debug, - "from {} assignments, truncating to {} for weight, removing {}", - assignments.len(), - maximum_allowed_voters, - removing, - ); - assignments.truncate(maximum_allowed_voters as usize); + Ok((solution, score, size)) } /// Greedily reduce the size of the solution to fit into the block w.r.t length. @@ -434,7 +527,7 @@ impl Pallet { max_allowed_length: u32, assignments: &mut Vec>, encoded_size_of: impl Fn(&[IndexAssignmentOf]) -> Result, - ) -> Result<(), MinerError> { + ) -> Result<(), MinerError> { // Perform a binary search for the max subset of which can fit into the allowed // length. Having discovered that, we can truncate efficiently. let max_allowed_length: usize = max_allowed_length.saturated_into(); @@ -477,7 +570,7 @@ impl Pallet { // after this point, we never error. // check before edit. - log!( + log_no_system!( debug, "from {} assignments, truncating to {} for length, removing {}", assignments.len(), @@ -489,10 +582,45 @@ impl Pallet { Ok(()) } + /// Greedily reduce the size of the solution to fit into the block w.r.t. weight. + /// + /// The weight of the solution is foremost a function of the number of voters (i.e. + /// `assignments.len()`). Aside from this, the other components of the weight are invariant. The + /// number of winners shall not be changed (otherwise the solution is invalid) and the + /// `ElectionSize` is merely a representation of the total number of stakers. + /// + /// Thus, we reside to stripping away some voters from the `assignments`. + /// + /// Note that the solution is already computed, and the winners are elected based on the merit + /// of the entire stake in the system. Nonetheless, some of the voters will be removed further + /// down the line. + /// + /// Indeed, the score must be computed **after** this step. If this step reduces the score too + /// much or remove a winner, then the solution must be discarded **after** this step. + pub fn trim_assignments_weight( + desired_targets: u32, + size: SolutionOrSnapshotSize, + max_weight: Weight, + assignments: &mut Vec>, + ) { + let maximum_allowed_voters = + Self::maximum_voter_for_weight(desired_targets, size, max_weight); + let removing: usize = + assignments.len().saturating_sub(maximum_allowed_voters.saturated_into()); + log_no_system!( + debug, + "from {} assignments, truncating to {} for weight, removing {}", + assignments.len(), + maximum_allowed_voters, + removing, + ); + assignments.truncate(maximum_allowed_voters as usize); + } + /// Find the maximum `len` that a solution can have in order to fit into the block weight. /// /// This only returns a value between zero and `size.nominators`. - pub fn maximum_voter_for_weight( + pub fn maximum_voter_for_weight( desired_winners: u32, size: SolutionOrSnapshotSize, max_weight: Weight, @@ -506,20 +634,21 @@ impl Pallet { // helper closures. let weight_with = |active_voters: u32| -> Weight { - W::submit_unsigned(size.voters, size.targets, active_voters, desired_winners) + T::solution_weight(size.voters, size.targets, active_voters, desired_winners) }; let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result { - match current_weight.cmp(&max_weight) { - Ordering::Less => { - let next_voters = voters.checked_add(step); - match next_voters { - Some(voters) if voters < max_voters => Ok(voters), - _ => Err(()), - } - }, - Ordering::Greater => voters.checked_sub(step).ok_or(()), - Ordering::Equal => Ok(voters), + if current_weight.all_lt(max_weight) { + let next_voters = voters.checked_add(step); + match next_voters { + Some(voters) if voters < max_voters => Ok(voters), + _ => Err(()), + } + } else if current_weight.any_gt(max_weight) { + voters.checked_sub(step).ok_or(()) + } else { + // If any of the constituent weights is equal to the max weight, we're at max + Ok(voters) } }; @@ -538,200 +667,381 @@ impl Pallet { // we found the right value - early exit the function. Ok(next) => return next, } - step = step / 2; + step /= 2; current_weight = weight_with(voters); } // Time to finish. We might have reduced less than expected due to rounding error. Increase // one last time if we have any room left, the reduce until we are sure we are below limit. - while voters + 1 <= max_voters && weight_with(voters + 1) < max_weight { + while voters < max_voters && weight_with(voters + 1).all_lt(max_weight) { voters += 1; } - while voters.checked_sub(1).is_some() && weight_with(voters) > max_weight { + while voters.checked_sub(1).is_some() && weight_with(voters).any_gt(max_weight) { voters -= 1; } let final_decision = voters.min(size.voters); debug_assert!( - weight_with(final_decision) <= max_weight, + weight_with(final_decision).all_lte(max_weight), "weight_with({}) <= {}", final_decision, max_weight, ); final_decision } - - /// Checks if an execution of the offchain worker is permitted at the given block number, or - /// not. - /// - /// This makes sure that - /// 1. we don't run on previous blocks in case of a re-org - /// 2. we don't run twice within a window of length `T::OffchainRepeat`. - /// - /// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()` - /// is returned, `now` is written in storage and will be used in further calls as the baseline. - pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> { - let threshold = T::OffchainRepeat::get(); - let last_block = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK); - - let mutate_stat = last_block.mutate::<_, &'static str, _>( - |maybe_head: Result, _>| { - match maybe_head { - Ok(Some(head)) if now < head => Err("fork."), - Ok(Some(head)) if now >= head && now <= head + threshold => - Err("recently executed."), - Ok(Some(head)) if now > head + threshold => { - // we can run again now. Write the new head. - Ok(now) - }, - _ => { - // value doesn't exists. Probably this node just booted up. Write, and run - Ok(now) - }, - } - }, - ); - - match mutate_stat { - // all good - Ok(_) => Ok(()), - // failed to write. - Err(MutateStorageError::ConcurrentModification(_)) => - Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")), - // fork etc. - Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)), - } - } - - /// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned - /// transaction. - /// - /// Can optionally also be called during dispatch, if needed. - /// - /// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's - /// code, so that we do less and less storage reads here. - pub fn unsigned_pre_dispatch_checks( - raw_solution: &RawSolution>, - ) -> DispatchResult { - // ensure solution is timely. Don't panic yet. This is a cheap check. - ensure!(Self::current_phase().is_unsigned_open(), Error::::PreDispatchEarlySubmission); - - // ensure round is current - ensure!(Self::round() == raw_solution.round, Error::::OcwCallWrongEra); - - // ensure correct number of winners. - ensure!( - Self::desired_targets().unwrap_or_default() == - raw_solution.solution.unique_targets().len() as u32, - Error::::PreDispatchWrongWinnerCount, - ); - - // ensure score is being improved. Panic henceforth. - ensure!( - Self::queued_solution().map_or(true, |q: ReadySolution<_>| is_score_better::( - raw_solution.score, - q.score, - T::SolutionImprovementThreshold::get() - )), - Error::::PreDispatchWeakSubmission, - ); - - Ok(()) - } } #[cfg(test)] mod max_weight { #![allow(unused_variables)] use super::*; - use crate::mock::MultiPhase; - - struct TestWeight; - impl crate::weights::WeightInfo for TestWeight { - fn elect_queued(a: u32, d: u32) -> Weight { - unreachable!() - } - fn create_snapshot_internal() -> Weight { - unreachable!() - } - fn on_initialize_nothing() -> Weight { - unreachable!() - } - fn on_initialize_open_signed() -> Weight { - unreachable!() - } - fn on_initialize_open_unsigned() -> Weight { - unreachable!() - } - fn finalize_signed_phase_accept_solution() -> Weight { - unreachable!() - } - fn finalize_signed_phase_reject_solution() -> Weight { - unreachable!() - } - fn submit(c: u32) -> Weight { - unreachable!() - } - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight { - (0 * v + 0 * t + 1000 * a + 0 * d) as Weight - } - fn feasibility_check(v: u32, _t: u32, a: u32, d: u32) -> Weight { - unreachable!() - } - } - + use crate::mock::{MockWeightInfo, Runtime}; #[test] fn find_max_voter_binary_search_works() { let w = SolutionOrSnapshotSize { voters: 10, targets: 0 }; - - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 0), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 999), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1990), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1999), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2000), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2001), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2010), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2990), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2999), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3000), 3); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3333), 3); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 5500), 5); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 7777), 7); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 9999), 9); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 10_000), 10); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 10_999), 10); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 11_000), 10); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 22_000), 10); + MockWeightInfo::set(crate::mock::MockedWeightInfo::Complex); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::zero().set_proof_size(u64::MAX) + ), + 0 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1).set_proof_size(u64::MAX) + ), + 0 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(999).set_proof_size(u64::MAX) + ), + 0 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1000).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1001).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1990).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1999).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2000).set_proof_size(u64::MAX) + ), + 2 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2001).set_proof_size(u64::MAX) + ), + 2 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2010).set_proof_size(u64::MAX) + ), + 2 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2990).set_proof_size(u64::MAX) + ), + 2 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2999).set_proof_size(u64::MAX) + ), + 2 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(3000).set_proof_size(u64::MAX) + ), + 3 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(3333).set_proof_size(u64::MAX) + ), + 3 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(5500).set_proof_size(u64::MAX) + ), + 5 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(7777).set_proof_size(u64::MAX) + ), + 7 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(9999).set_proof_size(u64::MAX) + ), + 9 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(10_000).set_proof_size(u64::MAX) + ), + 10 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(10_999).set_proof_size(u64::MAX) + ), + 10 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(11_000).set_proof_size(u64::MAX) + ), + 10 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(22_000).set_proof_size(u64::MAX) + ), + 10 + ); let w = SolutionOrSnapshotSize { voters: 1, targets: 0 }; - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 0), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 999), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1990), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1999), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2010), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3333), 1); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(0).set_proof_size(u64::MAX) + ), + 0 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1).set_proof_size(u64::MAX) + ), + 0 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(999).set_proof_size(u64::MAX) + ), + 0 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1000).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1001).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1990).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1999).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2000).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2001).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2010).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(3333).set_proof_size(u64::MAX) + ), + 1 + ); let w = SolutionOrSnapshotSize { voters: 2, targets: 0 }; - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 0), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 999), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1999), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2000), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2001), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2010), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3333), 2); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(0).set_proof_size(u64::MAX) + ), + 0 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1).set_proof_size(u64::MAX) + ), + 0 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(999).set_proof_size(u64::MAX) + ), + 0 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1000).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1001).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(1999).set_proof_size(u64::MAX) + ), + 1 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2000).set_proof_size(u64::MAX) + ), + 2 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2001).set_proof_size(u64::MAX) + ), + 2 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(2010).set_proof_size(u64::MAX) + ), + 2 + ); + assert_eq!( + Miner::::maximum_voter_for_weight( + 0, + w, + Weight::from_ref_time(3333).set_proof_size(u64::MAX) + ), + 2 + ); } } @@ -740,21 +1050,25 @@ mod tests { use super::*; use crate::{ mock::{ - roll_to, roll_to_with_ocw, trim_helpers, witness, BlockNumber, Call as OuterCall, - ExtBuilder, Extrinsic, MinerMaxWeight, MultiPhase, Origin, Runtime, System, - TestNposSolution, TrimHelpers, UnsignedPhase, + multi_phase_events, roll_to, roll_to_signed, roll_to_unsigned, roll_to_with_ocw, + trim_helpers, witness, BlockNumber, ExtBuilder, Extrinsic, MinerMaxWeight, MultiPhase, + Runtime, RuntimeCall, RuntimeOrigin, System, TestNposSolution, TrimHelpers, + UnsignedPhase, }, - CurrentPhase, InvalidTransaction, Phase, QueuedSolution, TransactionSource, + CurrentPhase, Event, InvalidTransaction, Phase, QueuedSolution, TransactionSource, TransactionValidityError, }; use codec::Decode; use frame_benchmarking::Zero; - use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::OffchainWorker}; - use sp_npos_elections::IndexAssignment; + use frame_election_provider_support::IndexAssignment; + use frame_support::{ + assert_noop, assert_ok, bounded_vec, dispatch::Dispatchable, traits::OffchainWorker, + }; + use sp_npos_elections::ElectionScore; use sp_runtime::{ offchain::storage_lock::{BlockAndTime, StorageLock}, traits::ValidateUnsigned, - PerU16, + ModuleError, PerU16, Perbill, }; type Assignment = crate::unsigned::Assignment; @@ -762,8 +1076,10 @@ mod tests { #[test] fn validate_unsigned_retracts_wrong_phase() { ExtBuilder::default().desired_targets(0).build_and_execute(|| { - let solution = - RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let solution = RawSolution:: { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; let call = Call::submit_unsigned { raw_solution: Box::new(solution.clone()), witness: witness(), @@ -785,7 +1101,7 @@ mod tests { )); // signed - roll_to(15); + roll_to_signed(); assert_eq!(MultiPhase::current_phase(), Phase::Signed); assert!(matches!( ::validate_unsigned( @@ -801,7 +1117,7 @@ mod tests { )); // unsigned - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); assert!(::validate_unsigned( @@ -832,11 +1148,13 @@ mod tests { #[test] fn validate_unsigned_retracts_low_score() { ExtBuilder::default().desired_targets(0).build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); - let solution = - RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let solution = RawSolution:: { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; let call = Call::submit_unsigned { raw_solution: Box::new(solution.clone()), witness: witness(), @@ -851,7 +1169,10 @@ mod tests { assert!(::pre_dispatch(&call).is_ok()); // set a better score - let ready = ReadySolution { score: [10, 0, 0], ..Default::default() }; + let ready = ReadySolution { + score: ElectionScore { minimal_stake: 10, ..Default::default() }, + ..Default::default() + }; >::put(ready); // won't work anymore. @@ -873,10 +1194,13 @@ mod tests { #[test] fn validate_unsigned_retracts_incorrect_winner_count() { ExtBuilder::default().desired_targets(1).build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); - let raw = RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let raw = RawSolution:: { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; let call = Call::submit_unsigned { raw_solution: Box::new(raw.clone()), witness: witness() }; assert_eq!(raw.solution.unique_targets().len(), 0); @@ -899,11 +1223,13 @@ mod tests { .miner_tx_priority(20) .desired_targets(0) .build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); - let solution = - RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let solution = RawSolution:: { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; let call = Call::submit_unsigned { raw_solution: Box::new(solution.clone()), witness: witness(), @@ -924,22 +1250,24 @@ mod tests { #[test] #[should_panic(expected = "Invalid unsigned submission must produce invalid block and \ deprive validator from their authoring reward.: \ - Module { index: 2, error: 1, message: \ - Some(\"PreDispatchWrongWinnerCount\") }")] + Module(ModuleError { index: 2, error: [1, 0, 0, 0], message: \ + Some(\"PreDispatchWrongWinnerCount\") })")] fn unfeasible_solution_panics() { ExtBuilder::default().build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); // This is in itself an invalid BS solution. - let solution = - RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let solution = RawSolution:: { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; let call = Call::submit_unsigned { raw_solution: Box::new(solution.clone()), witness: witness(), }; - let outer_call: OuterCall = call.into(); - let _ = outer_call.dispatch(Origin::none()); + let runtime_call: RuntimeCall = call.into(); + let _ = runtime_call.dispatch(RuntimeOrigin::none()); }) } @@ -948,12 +1276,14 @@ mod tests { deprive validator from their authoring reward.")] fn wrong_witness_panics() { ExtBuilder::default().build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); // This solution is unfeasible as well, but we won't even get there. - let solution = - RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let solution = RawSolution:: { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; let mut correct_witness = witness(); correct_witness.voters += 1; @@ -962,15 +1292,15 @@ mod tests { raw_solution: Box::new(solution.clone()), witness: correct_witness, }; - let outer_call: OuterCall = call.into(); - let _ = outer_call.dispatch(Origin::none()); + let runtime_call: RuntimeCall = call.into(); + let _ = runtime_call.dispatch(RuntimeOrigin::none()); }) } #[test] fn miner_works() { ExtBuilder::default().build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); // ensure we have snapshots in place. @@ -978,50 +1308,62 @@ mod tests { assert_eq!(MultiPhase::desired_targets().unwrap(), 2); // mine seq_phragmen solution with 2 iters. - let (solution, witness) = - MultiPhase::mine_solution::<::Solver>().unwrap(); + let (solution, witness) = MultiPhase::mine_solution().unwrap(); // ensure this solution is valid. assert!(MultiPhase::queued_solution().is_none()); - assert_ok!(MultiPhase::submit_unsigned(Origin::none(), Box::new(solution), witness)); + assert_ok!(MultiPhase::submit_unsigned( + RuntimeOrigin::none(), + Box::new(solution), + witness + )); assert!(MultiPhase::queued_solution().is_some()); + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Unsigned, + prev_ejected: false + } + ] + ); }) } #[test] fn miner_trims_weight() { ExtBuilder::default() - .miner_weight(100) - .mock_weight_info(true) + .miner_weight(Weight::from_ref_time(100).set_proof_size(u64::MAX)) + .mock_weight_info(crate::mock::MockedWeightInfo::Basic) .build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); - let (raw, witness) = - MultiPhase::mine_solution::<::Solver>().unwrap(); - let solution_weight = ::WeightInfo::submit_unsigned( + let (raw, witness) = MultiPhase::mine_solution().unwrap(); + let solution_weight = ::solution_weight( witness.voters, witness.targets, raw.solution.voter_count() as u32, raw.solution.unique_targets().len() as u32, ); // default solution will have 5 edges (5 * 5 + 10) - assert_eq!(solution_weight, 35); + assert_eq!(solution_weight, Weight::from_ref_time(35)); assert_eq!(raw.solution.voter_count(), 5); // now reduce the max weight - ::set(25); + ::set(Weight::from_ref_time(25).set_proof_size(u64::MAX)); - let (raw, witness) = - MultiPhase::mine_solution::<::Solver>().unwrap(); - let solution_weight = ::WeightInfo::submit_unsigned( + let (raw, witness) = MultiPhase::mine_solution().unwrap(); + let solution_weight = ::solution_weight( witness.voters, witness.targets, raw.solution.voter_count() as u32, raw.solution.unique_targets().len() as u32, ); // default solution will have 5 edges (5 * 5 + 10) - assert_eq!(solution_weight, 25); + assert_eq!(solution_weight, Weight::from_ref_time(25)); assert_eq!(raw.solution.voter_count(), 3); }) } @@ -1030,16 +1372,20 @@ mod tests { fn miner_will_not_submit_if_not_enough_winners() { let (mut ext, _) = ExtBuilder::default().desired_targets(8).build_offchainify(0); ext.execute_with(|| { - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); + // Force the number of winners to be bigger to fail + let (mut solution, _) = MultiPhase::mine_solution().unwrap(); + solution.solution.votes1[0].1 = 4; + assert_eq!( - MultiPhase::mine_check_save_submit().unwrap_err(), - MinerError::PreDispatchChecksFailed(DispatchError::Module { + MultiPhase::basic_checks(&solution, "mined").unwrap_err(), + MinerError::PreDispatchChecksFailed(DispatchError::Module(ModuleError { index: 2, - error: 1, + error: [1, 0, 0, 0], message: Some("PreDispatchWrongWinnerCount"), - }), + })), ); }) } @@ -1048,11 +1394,11 @@ mod tests { fn unsigned_per_dispatch_checks_can_only_submit_threshold_better() { ExtBuilder::default() .desired_targets(1) - .add_voter(7, 2, vec![10]) - .add_voter(8, 5, vec![10]) - .solution_improvement_threshold(Perbill::from_percent(50)) + .add_voter(7, 2, bounded_vec![10]) + .add_voter(8, 5, bounded_vec![10]) + .better_unsigned_threshold(Perbill::from_percent(50)) .build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); assert_eq!(MultiPhase::desired_targets().unwrap(), 1); @@ -1065,14 +1411,26 @@ mod tests { distribution: vec![(10, PerU16::one())], }], }; - let (solution, witness) = MultiPhase::prepare_election_result(result).unwrap(); + + let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap(); + let desired_targets = MultiPhase::desired_targets().unwrap(); + + let (raw, score, witness) = + Miner::::prepare_election_result_with_snapshot( + result, + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); + let solution = RawSolution { solution: raw, score, round: MultiPhase::round() }; assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution)); assert_ok!(MultiPhase::submit_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(solution), witness )); - assert_eq!(MultiPhase::queued_solution().unwrap().score[0], 10); + assert_eq!(MultiPhase::queued_solution().unwrap().score.minimal_stake, 10); // trial 1: a solution who's score is only 2, i.e. 20% better in the first element. let result = ElectionResult { @@ -1086,9 +1444,16 @@ mod tests { }, ], }; - let (solution, _) = MultiPhase::prepare_election_result(result).unwrap(); + let (raw, score, _) = Miner::::prepare_election_result_with_snapshot( + result, + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); + let solution = RawSolution { solution: raw, score, round: MultiPhase::round() }; // 12 is not 50% more than 10 - assert_eq!(solution.score[0], 12); + assert_eq!(solution.score.minimal_stake, 12); assert_noop!( MultiPhase::unsigned_pre_dispatch_checks(&solution), Error::::PreDispatchWeakSubmission, @@ -1108,13 +1473,21 @@ mod tests { }, ], }; - let (solution, witness) = MultiPhase::prepare_election_result(result).unwrap(); - assert_eq!(solution.score[0], 17); + let (raw, score, witness) = + Miner::::prepare_election_result_with_snapshot( + result, + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); + let solution = RawSolution { solution: raw, score, round: MultiPhase::round() }; + assert_eq!(solution.score.minimal_stake, 17); // and it is fine assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution)); assert_ok!(MultiPhase::submit_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(solution), witness )); @@ -1127,7 +1500,7 @@ mod tests { ext.execute_with(|| { let offchain_repeat = ::OffchainRepeat::get(); - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); // first execution -- okay. @@ -1168,7 +1541,7 @@ mod tests { let guard = StorageValueRef::persistent(&OFFCHAIN_LOCK); let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK); - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); // initially, the lock is not set. @@ -1189,7 +1562,7 @@ mod tests { // ensure that if the guard is in hold, a new execution is not allowed. let (mut ext, pool) = ExtBuilder::default().build_offchainify(0); ext.execute_with(|| { - roll_to(25); + roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); // artificially set the value, as if another thread is mid-way. @@ -1217,7 +1590,7 @@ mod tests { fn ocw_only_runs_when_unsigned_open_now() { let (mut ext, pool) = ExtBuilder::default().build_offchainify(0); ext.execute_with(|| { - roll_to(25); + roll_to_unsigned(); assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); // we must clear the offchain storage to ensure the offchain execution check doesn't get @@ -1241,35 +1614,77 @@ mod tests { } #[test] - fn ocw_clears_cache_after_election() { - let (mut ext, _pool) = ExtBuilder::default().build_offchainify(0); + fn ocw_clears_cache_on_unsigned_phase_open() { + let (mut ext, pool) = ExtBuilder::default().build_offchainify(0); ext.execute_with(|| { - roll_to(25); - assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); + const BLOCK: u64 = 25; + let block_plus = |delta: u64| BLOCK + delta; + let offchain_repeat = ::OffchainRepeat::get(); - // we must clear the offchain storage to ensure the offchain execution check doesn't get - // in the way. - let mut storage = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK); - storage.clear(); + roll_to(BLOCK); + // we are on the first block of the unsigned phase + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, BLOCK))); assert!( !ocw_solution_exists::(), "no solution should be present before we mine one", ); - // creates and cache a solution - MultiPhase::offchain_worker(25); + // create and cache a solution on the first block of the unsigned phase + MultiPhase::offchain_worker(BLOCK); assert!( ocw_solution_exists::(), "a solution must be cached after running the worker", ); - // after an election, the solution must be cleared + // record the submitted tx, + let tx_cache_1 = pool.read().transactions[0].clone(); + // and assume it has been processed. + pool.try_write().unwrap().transactions.clear(); + + // after an election, the solution is not cleared // we don't actually care about the result of the election - roll_to(26); let _ = MultiPhase::do_elect(); - MultiPhase::offchain_worker(26); - assert!(!ocw_solution_exists::(), "elections must clear the ocw cache"); + MultiPhase::offchain_worker(block_plus(1)); + assert!(ocw_solution_exists::(), "elections does not clear the ocw cache"); + + // submit a solution with the offchain worker after the repeat interval + MultiPhase::offchain_worker(block_plus(offchain_repeat + 1)); + + // record the submitted tx, + let tx_cache_2 = pool.read().transactions[0].clone(); + // and assume it has been processed. + pool.try_write().unwrap().transactions.clear(); + + // the OCW submitted the same solution twice since the cache was not cleared. + assert_eq!(tx_cache_1, tx_cache_2); + + let current_block = block_plus(offchain_repeat * 2 + 2); + // force the unsigned phase to start on the current block. + CurrentPhase::::set(Phase::Unsigned((true, current_block))); + + // clear the cache and create a solution since we are on the first block of the unsigned + // phase. + MultiPhase::offchain_worker(current_block); + let tx_cache_3 = pool.read().transactions[0].clone(); + + // the submitted solution changes because the cache was cleared. + assert_eq!(tx_cache_1, tx_cache_3); + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: ElectionScore { + minimal_stake: 0, + sum_stake: 0, + sum_stake_squared: 0 + } + } + ] + ); }) } @@ -1367,7 +1782,7 @@ mod tests { let encoded = pool.read().transactions[0].clone(); let extrinsic: Extrinsic = codec::Decode::decode(&mut &*encoded).unwrap(); let call = extrinsic.call; - assert!(matches!(call, OuterCall::MultiPhase(Call::submit_unsigned { .. }))); + assert!(matches!(call, RuntimeCall::MultiPhase(Call::submit_unsigned { .. }))); }) } @@ -1384,7 +1799,7 @@ mod tests { let encoded = pool.read().transactions[0].clone(); let extrinsic = Extrinsic::decode(&mut &*encoded).unwrap(); let call = match extrinsic.call { - OuterCall::MultiPhase(call @ Call::submit_unsigned { .. }) => call, + RuntimeCall::MultiPhase(call @ Call::submit_unsigned { .. }) => call, _ => panic!("bad call: unexpected submission"), }; @@ -1409,7 +1824,7 @@ mod tests { #[test] fn trim_assignments_length_does_not_modify_when_short_enough() { ExtBuilder::default().build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); // given let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers(); @@ -1418,8 +1833,12 @@ mod tests { let solution_clone = solution.clone(); // when - MultiPhase::trim_assignments_length(encoded_len, &mut assignments, encoded_size_of) - .unwrap(); + Miner::::trim_assignments_length( + encoded_len, + &mut assignments, + encoded_size_of, + ) + .unwrap(); // then let solution = SolutionOf::::try_from(assignments.as_slice()).unwrap(); @@ -1430,7 +1849,7 @@ mod tests { #[test] fn trim_assignments_length_modifies_when_too_long() { ExtBuilder::default().build().execute_with(|| { - roll_to(25); + roll_to_unsigned(); // given let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers(); @@ -1439,7 +1858,7 @@ mod tests { let solution_clone = solution.clone(); // when - MultiPhase::trim_assignments_length( + Miner::::trim_assignments_length( encoded_len as u32 - 1, &mut assignments, encoded_size_of, @@ -1456,7 +1875,7 @@ mod tests { #[test] fn trim_assignments_length_trims_lowest_stake() { ExtBuilder::default().build().execute_with(|| { - roll_to(25); + roll_to_unsigned(); // given let TrimHelpers { voters, mut assignments, encoded_size_of, voter_index } = @@ -1472,8 +1891,12 @@ mod tests { .unwrap(); // when - MultiPhase::trim_assignments_length(encoded_len - 1, &mut assignments, encoded_size_of) - .unwrap(); + Miner::::trim_assignments_length( + encoded_len - 1, + &mut assignments, + encoded_size_of, + ) + .unwrap(); // then assert_eq!(assignments.len(), count - 1, "we must have removed exactly one assignment"); @@ -1500,11 +1923,11 @@ mod tests { assert_eq!(min_solution_size, SolutionOf::::LIMIT); // all of this should not panic. - MultiPhase::trim_assignments_length(0, &mut assignments, encoded_size_of.clone()) + Miner::::trim_assignments_length(0, &mut assignments, encoded_size_of.clone()) .unwrap(); - MultiPhase::trim_assignments_length(1, &mut assignments, encoded_size_of.clone()) + Miner::::trim_assignments_length(1, &mut assignments, encoded_size_of.clone()) .unwrap(); - MultiPhase::trim_assignments_length( + Miner::::trim_assignments_length( min_solution_size as u32, &mut assignments, encoded_size_of, @@ -1515,13 +1938,13 @@ mod tests { // or when we trim it to zero. ExtBuilder::default().build_and_execute(|| { // we need snapshot for `trim_helpers` to work. - roll_to(25); + roll_to_unsigned(); let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers(); assert!(assignments.len() > 0); // trim to min solution size. let min_solution_size = SolutionOf::::LIMIT as u32; - MultiPhase::trim_assignments_length( + Miner::::trim_assignments_length( min_solution_size, &mut assignments, encoded_size_of, @@ -1537,18 +1960,18 @@ mod tests { #[test] fn mine_solution_solutions_always_within_acceptable_length() { ExtBuilder::default().build_and_execute(|| { - roll_to(25); + roll_to_unsigned(); // how long would the default solution be? - let solution = MultiPhase::mine_solution::<::Solver>().unwrap(); - let max_length = ::MinerMaxLength::get(); + let solution = MultiPhase::mine_solution().unwrap(); + let max_length = ::MaxLength::get(); let solution_size = solution.0.solution.encoded_size(); assert!(solution_size <= max_length as usize); // now set the max size to less than the actual size and regenerate - ::MinerMaxLength::set(solution_size as u32 - 1); - let solution = MultiPhase::mine_solution::<::Solver>().unwrap(); - let max_length = ::MinerMaxLength::get(); + ::MaxLength::set(solution_size as u32 - 1); + let solution = MultiPhase::mine_solution().unwrap(); + let max_length = ::MaxLength::get(); let solution_size = solution.0.solution.encoded_size(); assert!(solution_size <= max_length as usize); }); diff --git a/frame/election-provider-multi-phase/src/weights.rs b/frame/election-provider-multi-phase/src/weights.rs index 262838bcb9e70..221fd5837f7b7 100644 --- a/frame/election-provider-multi-phase/src/weights.rs +++ b/frame/election-provider-multi-phase/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_election_provider_multi_phase //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-18, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/election-provider-multi-phase/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -50,9 +52,9 @@ pub trait WeightInfo { fn on_initialize_open_unsigned() -> Weight; fn finalize_signed_phase_accept_solution() -> Weight; fn finalize_signed_phase_reject_solution() -> Weight; - fn create_snapshot_internal() -> Weight; + fn create_snapshot_internal(v: u32, t: u32, ) -> Weight; fn elect_queued(a: u32, d: u32, ) -> Weight; - fn submit(c: u32, ) -> Weight; + fn submit() -> Weight; fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight; fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight; } @@ -69,42 +71,52 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ForceEra (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) fn on_initialize_nothing() -> Weight { - (23_878_000 as Weight) - .saturating_add(T::DbWeight::get().reads(8 as Weight)) + // Minimum execution time: 17_309 nanoseconds. + Weight::from_ref_time(17_646_000 as u64) + .saturating_add(T::DbWeight::get().reads(8 as u64)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_signed() -> Weight { - (34_547_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 17_992 nanoseconds. + Weight::from_ref_time(18_426_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned() -> Weight { - (33_568_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 17_340 nanoseconds. + Weight::from_ref_time(17_881_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: System Account (r:1 w:1) // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) fn finalize_signed_phase_accept_solution() -> Weight { - (50_596_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 35_571 nanoseconds. + Weight::from_ref_time(35_989_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: System Account (r:1 w:1) fn finalize_signed_phase_reject_solution() -> Weight { - (33_389_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 27_403 nanoseconds. + Weight::from_ref_time(27_879_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) - fn create_snapshot_internal() -> Weight { - (8_835_233_000 as Weight) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + /// The range of component `v` is `[1000, 2000]`. + /// The range of component `t` is `[500, 1000]`. + fn create_snapshot_internal(v: u32, _t: u32, ) -> Weight { + // Minimum execution time: 571_900 nanoseconds. + Weight::from_ref_time(589_170_000 as u64) + // Standard Error: 7_123 + .saturating_add(Weight::from_ref_time(384_767 as u64).saturating_mul(v as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) @@ -115,27 +127,29 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + /// The range of component `a` is `[500, 800]`. + /// The range of component `d` is `[200, 400]`. fn elect_queued(a: u32, d: u32, ) -> Weight { - (82_395_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_769_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 13_000 - .saturating_add((320_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(8 as Weight)) + // Minimum execution time: 1_296_481 nanoseconds. + Weight::from_ref_time(1_076_121_575 as u64) + // Standard Error: 5_708 + .saturating_add(Weight::from_ref_time(474_995 as u64).saturating_mul(a as u64)) + // Standard Error: 8_556 + .saturating_add(Weight::from_ref_time(39_224 as u64).saturating_mul(d as u64)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(8 as u64)) } - // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) - fn submit(c: u32, ) -> Weight { - (77_368_000 as Weight) - // Standard Error: 9_000 - .saturating_add((369_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + fn submit() -> Weight { + // Minimum execution time: 58_716 nanoseconds. + Weight::from_ref_time(59_480_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) // Storage: ElectionProviderMultiPhase Round (r:1 w:0) @@ -144,32 +158,36 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 4_000 - .saturating_add((3_553_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 23_000 - .saturating_add((35_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 7_000 - .saturating_add((10_600_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 59_000 - .saturating_add((6_128_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + /// The range of component `v` is `[1000, 2000]`. + /// The range of component `t` is `[500, 1000]`. + /// The range of component `a` is `[500, 800]`. + /// The range of component `d` is `[200, 400]`. + fn submit_unsigned(v: u32, _t: u32, a: u32, _d: u32, ) -> Weight { + // Minimum execution time: 5_540_737 nanoseconds. + Weight::from_ref_time(5_567_381_000 as u64) + // Standard Error: 18_563 + .saturating_add(Weight::from_ref_time(603_280 as u64).saturating_mul(v as u64)) + // Standard Error: 55_009 + .saturating_add(Weight::from_ref_time(3_164_053 as u64).saturating_mul(a as u64)) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - fn feasibility_check(v: u32, _t: u32, a: u32, d: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 3_000 - .saturating_add((3_478_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 6_000 - .saturating_add((8_930_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 47_000 - .saturating_add((5_199_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) + /// The range of component `v` is `[1000, 2000]`. + /// The range of component `t` is `[500, 1000]`. + /// The range of component `a` is `[500, 800]`. + /// The range of component `d` is `[200, 400]`. + fn feasibility_check(v: u32, _t: u32, a: u32, _d: u32, ) -> Weight { + // Minimum execution time: 5_021_808 nanoseconds. + Weight::from_ref_time(5_051_856_000 as u64) + // Standard Error: 16_650 + .saturating_add(Weight::from_ref_time(683_344 as u64).saturating_mul(v as u64)) + // Standard Error: 49_342 + .saturating_add(Weight::from_ref_time(2_190_098 as u64).saturating_mul(a as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) } } @@ -184,42 +202,52 @@ impl WeightInfo for () { // Storage: Staking ForceEra (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) fn on_initialize_nothing() -> Weight { - (23_878_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + // Minimum execution time: 17_309 nanoseconds. + Weight::from_ref_time(17_646_000 as u64) + .saturating_add(RocksDbWeight::get().reads(8 as u64)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_signed() -> Weight { - (34_547_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 17_992 nanoseconds. + Weight::from_ref_time(18_426_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned() -> Weight { - (33_568_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 17_340 nanoseconds. + Weight::from_ref_time(17_881_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: System Account (r:1 w:1) // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) fn finalize_signed_phase_accept_solution() -> Weight { - (50_596_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 35_571 nanoseconds. + Weight::from_ref_time(35_989_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: System Account (r:1 w:1) fn finalize_signed_phase_reject_solution() -> Weight { - (33_389_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 27_403 nanoseconds. + Weight::from_ref_time(27_879_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) - fn create_snapshot_internal() -> Weight { - (8_835_233_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + /// The range of component `v` is `[1000, 2000]`. + /// The range of component `t` is `[500, 1000]`. + fn create_snapshot_internal(v: u32, _t: u32, ) -> Weight { + // Minimum execution time: 571_900 nanoseconds. + Weight::from_ref_time(589_170_000 as u64) + // Standard Error: 7_123 + .saturating_add(Weight::from_ref_time(384_767 as u64).saturating_mul(v as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) @@ -230,27 +258,29 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + /// The range of component `a` is `[500, 800]`. + /// The range of component `d` is `[200, 400]`. fn elect_queued(a: u32, d: u32, ) -> Weight { - (82_395_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_769_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 13_000 - .saturating_add((320_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(8 as Weight)) + // Minimum execution time: 1_296_481 nanoseconds. + Weight::from_ref_time(1_076_121_575 as u64) + // Standard Error: 5_708 + .saturating_add(Weight::from_ref_time(474_995 as u64).saturating_mul(a as u64)) + // Standard Error: 8_556 + .saturating_add(Weight::from_ref_time(39_224 as u64).saturating_mul(d as u64)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(8 as u64)) } - // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) - fn submit(c: u32, ) -> Weight { - (77_368_000 as Weight) - // Standard Error: 9_000 - .saturating_add((369_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + fn submit() -> Weight { + // Minimum execution time: 58_716 nanoseconds. + Weight::from_ref_time(59_480_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) // Storage: ElectionProviderMultiPhase Round (r:1 w:0) @@ -259,31 +289,35 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 4_000 - .saturating_add((3_553_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 23_000 - .saturating_add((35_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 7_000 - .saturating_add((10_600_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 59_000 - .saturating_add((6_128_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + /// The range of component `v` is `[1000, 2000]`. + /// The range of component `t` is `[500, 1000]`. + /// The range of component `a` is `[500, 800]`. + /// The range of component `d` is `[200, 400]`. + fn submit_unsigned(v: u32, _t: u32, a: u32, _d: u32, ) -> Weight { + // Minimum execution time: 5_540_737 nanoseconds. + Weight::from_ref_time(5_567_381_000 as u64) + // Standard Error: 18_563 + .saturating_add(Weight::from_ref_time(603_280 as u64).saturating_mul(v as u64)) + // Standard Error: 55_009 + .saturating_add(Weight::from_ref_time(3_164_053 as u64).saturating_mul(a as u64)) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - fn feasibility_check(v: u32, _t: u32, a: u32, d: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 3_000 - .saturating_add((3_478_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 6_000 - .saturating_add((8_930_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 47_000 - .saturating_add((5_199_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + /// The range of component `v` is `[1000, 2000]`. + /// The range of component `t` is `[500, 1000]`. + /// The range of component `a` is `[500, 800]`. + /// The range of component `d` is `[200, 400]`. + fn feasibility_check(v: u32, _t: u32, a: u32, _d: u32, ) -> Weight { + // Minimum execution time: 5_021_808 nanoseconds. + Weight::from_ref_time(5_051_856_000 as u64) + // Standard Error: 16_650 + .saturating_add(Weight::from_ref_time(683_344 as u64).saturating_mul(v as u64)) + // Standard Error: 49_342 + .saturating_add(Weight::from_ref_time(2_190_098 as u64).saturating_mul(a as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) } } diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index dfe2b11024334..b9584c899e1b1 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -2,39 +2,43 @@ name = "frame-election-provider-support" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "election provider supporting traits" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../../primitives/arithmetic" } -sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solution-type" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] +rand = "0.7.3" +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } [features] default = ["std"] +fuzz = ["default"] std = [ "codec/std", - "sp-std/std", - "sp-npos-elections/std", - "sp-arithmetic/std", "frame-support/std", "frame-system/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-npos-elections/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [] diff --git a/frame/election-provider-support/benchmarking/Cargo.toml b/frame/election-provider-support/benchmarking/Cargo.toml new file mode 100644 index 0000000000000..60538997773d4 --- /dev/null +++ b/frame/election-provider-support/benchmarking/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "pallet-election-provider-support-benchmarking" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Benchmarking for election provider support onchain config trait" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ + "derive", +] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = ".." } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-election-provider-support/std", + "frame-system/std", + "sp-npos-elections/std", + "sp-runtime/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", +] diff --git a/frame/election-provider-support/benchmarking/src/lib.rs b/frame/election-provider-support/benchmarking/src/lib.rs new file mode 100644 index 0000000000000..547e35bed36e8 --- /dev/null +++ b/frame/election-provider-support/benchmarking/src/lib.rs @@ -0,0 +1,91 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +#![cfg(feature = "runtime-benchmarks")] +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Decode; +use frame_benchmarking::{benchmarks, Vec}; +use frame_election_provider_support::{NposSolver, PhragMMS, SequentialPhragmen}; + +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/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml new file mode 100644 index 0000000000000..5a0c46cb83a0d --- /dev/null +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "frame-election-provider-solution-type" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "NPoS Solution Type" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0.98", features = ["full", "visit"] } +quote = "1.0" +proc-macro2 = "1.0.37" +proc-macro-crate = "1.1.3" + +[dev-dependencies] +parity-scale-codec = "3.0.0" +scale-info = "2.1.1" +sp-arithmetic = { version = "6.0.0", path = "../../../primitives/arithmetic" } +# used by generate_solution_type: +frame-election-provider-support = { version = "4.0.0-dev", path = ".." } +frame-support = { version = "4.0.0-dev", path = "../../support" } +trybuild = "1.0.60" diff --git a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml new file mode 100644 index 0000000000000..34aeaf9300352 --- /dev/null +++ b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "frame-election-solution-type-fuzzer" +version = "2.0.0-alpha.5" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Fuzzer for phragmén solution type implementation." +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +clap = { version = "4.0.9", features = ["derive"] } +honggfuzz = "0.5" +rand = { version = "0.8", features = ["std", "small_rng"] } + +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-election-provider-solution-type = { version = "4.0.0-dev", path = ".." } +frame-election-provider-support = { version = "4.0.0-dev", path = "../.." } +sp-arithmetic = { version = "6.0.0", path = "../../../../primitives/arithmetic" } +sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +# used by generate_solution_type: +sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/npos-elections" } +frame-support = { version = "4.0.0-dev", path = "../../../support" } + +[[bin]] +name = "compact" +path = "src/compact.rs" diff --git a/primitives/npos-elections/fuzzer/src/compact.rs b/frame/election-provider-support/solution-type/fuzzer/src/compact.rs similarity index 86% rename from primitives/npos-elections/fuzzer/src/compact.rs rename to frame/election-provider-support/solution-type/fuzzer/src/compact.rs index 4e78c94b82572..e7ef440ff2195 100644 --- a/primitives/npos-elections/fuzzer/src/compact.rs +++ b/frame/election-provider-support/solution-type/fuzzer/src/compact.rs @@ -1,5 +1,6 @@ +use frame_election_provider_solution_type::generate_solution_type; use honggfuzz::fuzz; -use sp_npos_elections::{generate_solution_type, sp_arithmetic::Percent}; +use sp_arithmetic::Percent; use sp_runtime::codec::{Encode, Error}; fn main() { @@ -7,11 +8,12 @@ fn main() { VoterIndex = u32, TargetIndex = u32, Accuracy = Percent, + MaxVoters = frame_support::traits::ConstU32::<100_000>, >(16)); loop { fuzz!(|fuzzer_data: &[u8]| { let result_decoded: Result = - ::decode(&mut &fuzzer_data[..]); + ::decode(&mut &*fuzzer_data); // Ignore errors as not every random sequence of bytes can be decoded as // InnerTestSolutionCompact if let Ok(decoded) = result_decoded { diff --git a/primitives/npos-elections/solution-type/src/codec.rs b/frame/election-provider-support/solution-type/src/codec.rs similarity index 70% rename from primitives/npos-elections/solution-type/src/codec.rs rename to frame/election-provider-support/solution-type/src/codec.rs index 2dac076fcde42..05d753d3d8456 100644 --- a/primitives/npos-elections/solution-type/src/codec.rs +++ b/frame/election-provider-support/solution-type/src/codec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,14 +51,14 @@ fn decode_impl( quote! { let #name = < - _npos::sp_std::prelude::Vec<(_npos::codec::Compact<#voter_type>, _npos::codec::Compact<#target_type>)> + _feps::sp_std::prelude::Vec<(_feps::codec::Compact<#voter_type>, _feps::codec::Compact<#target_type>)> as - _npos::codec::Decode + _feps::codec::Decode >::decode(value)?; let #name = #name .into_iter() .map(|(v, t)| (v.0, t.0)) - .collect::<_npos::sp_std::prelude::Vec<_>>(); + .collect::<_feps::sp_std::prelude::Vec<_>>(); } }; @@ -73,12 +73,12 @@ fn decode_impl( quote! { let #name = < - _npos::sp_std::prelude::Vec<( - _npos::codec::Compact<#voter_type>, - [(_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>); #c-1], - _npos::codec::Compact<#target_type>, + _feps::sp_std::prelude::Vec<( + _feps::codec::Compact<#voter_type>, + [(_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>); #c-1], + _feps::codec::Compact<#target_type>, )> - as _npos::codec::Decode + as _feps::codec::Decode >::decode(value)?; let #name = #name .into_iter() @@ -87,7 +87,7 @@ fn decode_impl( [ #inner_impl ], t_last.0, )) - .collect::<_npos::sp_std::prelude::Vec<_>>(); + .collect::<_feps::sp_std::prelude::Vec<_>>(); } }) .collect::(); @@ -100,8 +100,8 @@ fn decode_impl( .collect::(); quote!( - impl _npos::codec::Decode for #ident { - fn decode(value: &mut I) -> Result { + impl _feps::codec::Decode for #ident { + fn decode(value: &mut I) -> Result { #decode_impl_single #decode_impl_rest @@ -123,10 +123,10 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 { let #name = self.#name .iter() .map(|(v, t)| ( - _npos::codec::Compact(v.clone()), - _npos::codec::Compact(t.clone()), + _feps::codec::Compact(v.clone()), + _feps::codec::Compact(t.clone()), )) - .collect::<_npos::sp_std::prelude::Vec<_>>(); + .collect::<_feps::sp_std::prelude::Vec<_>>(); #name.encode_to(&mut r); } }; @@ -139,8 +139,8 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 { let inners_solution_array = (0..c - 1) .map(|i| { quote! {( - _npos::codec::Compact(inner[#i].0.clone()), - _npos::codec::Compact(inner[#i].1.clone()), + _feps::codec::Compact(inner[#i].0.clone()), + _feps::codec::Compact(inner[#i].1.clone()), ),} }) .collect::(); @@ -149,19 +149,19 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 { let #name = self.#name .iter() .map(|(v, inner, t_last)| ( - _npos::codec::Compact(v.clone()), + _feps::codec::Compact(v.clone()), [ #inners_solution_array ], - _npos::codec::Compact(t_last.clone()), + _feps::codec::Compact(t_last.clone()), )) - .collect::<_npos::sp_std::prelude::Vec<_>>(); + .collect::<_feps::sp_std::prelude::Vec<_>>(); #name.encode_to(&mut r); } }) .collect::(); quote!( - impl _npos::codec::Encode for #ident { - fn encode(&self) -> _npos::sp_std::prelude::Vec { + impl _feps::codec::Encode for #ident { + fn encode(&self) -> _feps::sp_std::prelude::Vec { let mut r = vec![]; #encode_impl_single #encode_impl_rest @@ -182,8 +182,8 @@ fn scale_info_impl( let name = format!("{}", vote_field(1)); quote! { .field(|f| - f.ty::<_npos::sp_std::prelude::Vec< - (_npos::codec::Compact<#voter_type>, _npos::codec::Compact<#target_type>) + f.ty::<_feps::sp_std::prelude::Vec< + (_feps::codec::Compact<#voter_type>, _feps::codec::Compact<#target_type>) >>() .name(#name) ) @@ -194,10 +194,10 @@ fn scale_info_impl( let name = format!("{}", vote_field(2)); quote! { .field(|f| - f.ty::<_npos::sp_std::prelude::Vec<( - _npos::codec::Compact<#voter_type>, - (_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>), - _npos::codec::Compact<#target_type> + f.ty::<_feps::sp_std::prelude::Vec<( + _feps::codec::Compact<#voter_type>, + (_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>), + _feps::codec::Compact<#target_type> )>>() .name(#name) ) @@ -209,13 +209,13 @@ fn scale_info_impl( let name = format!("{}", vote_field(c)); quote! { .field(|f| - f.ty::<_npos::sp_std::prelude::Vec<( - _npos::codec::Compact<#voter_type>, + f.ty::<_feps::sp_std::prelude::Vec<( + _feps::codec::Compact<#voter_type>, [ - (_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>); + (_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>); #c - 1 ], - _npos::codec::Compact<#target_type> + _feps::codec::Compact<#target_type> )>>() .name(#name) ) @@ -224,14 +224,14 @@ fn scale_info_impl( .collect::(); quote!( - impl _npos::scale_info::TypeInfo for #ident { + impl _feps::scale_info::TypeInfo for #ident { type Identity = Self; - fn type_info() -> _npos::scale_info::Type<_npos::scale_info::form::MetaForm> { - _npos::scale_info::Type::builder() - .path(_npos::scale_info::Path::new(stringify!(#ident), module_path!())) + fn type_info() -> _feps::scale_info::Type<_feps::scale_info::form::MetaForm> { + _feps::scale_info::Type::builder() + .path(_feps::scale_info::Path::new(stringify!(#ident), module_path!())) .composite( - _npos::scale_info::build::Fields::named() + _feps::scale_info::build::Fields::named() #scale_info_impl_single #scale_info_impl_double #scale_info_impl_rest diff --git a/primitives/npos-elections/solution-type/src/from_assignment_helpers.rs b/frame/election-provider-support/solution-type/src/from_assignment_helpers.rs similarity index 96% rename from primitives/npos-elections/solution-type/src/from_assignment_helpers.rs rename to frame/election-provider-support/solution-type/src/from_assignment_helpers.rs index dc194baa6d9ea..e0613afc0d392 100644 --- a/primitives/npos-elections/solution-type/src/from_assignment_helpers.rs +++ b/frame/election-provider-support/solution-type/src/from_assignment_helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/solution-type/src/index_assignment.rs b/frame/election-provider-support/solution-type/src/index_assignment.rs similarity index 96% rename from primitives/npos-elections/solution-type/src/index_assignment.rs rename to frame/election-provider-support/solution-type/src/index_assignment.rs index d38dc3ec309d9..ccdcb4ba25cdb 100644 --- a/primitives/npos-elections/solution-type/src/index_assignment.rs +++ b/frame/election-provider-support/solution-type/src/index_assignment.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/solution-type/src/lib.rs b/frame/election-provider-support/solution-type/src/lib.rs similarity index 76% rename from primitives/npos-elections/solution-type/src/lib.rs rename to frame/election-provider-support/solution-type/src/lib.rs index 9b0ec56fc74de..0a5c11e76dedb 100644 --- a/primitives/npos-elections/solution-type/src/lib.rs +++ b/frame/election-provider-support/solution-type/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,6 +49,12 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// compact encoding. /// - The accuracy of the ratios. This must be one of the `PerThing` types defined in /// `sp-arithmetic`. +/// - The maximum number of voters. This must be of type `Get`. Check +/// for more details. This is used to bound the struct, by leveraging the fact that `votes1.len() +/// < votes2.len() < ... < votesn.len()` (the details of the struct is explained further below). +/// We know that `sum_i votes_i.len() <= MaxVoters`, and we know that the maximum size of the +/// struct would be achieved if all voters fall in the last bucket. One can also check the tests +/// and more specifically `max_encoded_len_exact` for a concrete example. /// /// Moreover, the maximum number of edges per voter (distribution per assignment) also need to be /// specified. Attempting to convert from/to an assignment with more distributions will fail. @@ -57,12 +63,14 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// type, `u8` target type and `Perbill` accuracy with maximum of 4 edges per voter. /// /// ``` -/// # use sp_npos_elections_solution_type::generate_solution_type; +/// # use frame_election_provider_solution_type::generate_solution_type; /// # use sp_arithmetic::per_things::Perbill; +/// # use frame_support::traits::ConstU32; /// generate_solution_type!(pub struct TestSolution::< /// VoterIndex = u16, /// TargetIndex = u8, /// Accuracy = Perbill, +/// MaxVoters = ConstU32::<10>, /// >(4)); /// ``` /// @@ -88,7 +96,7 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// ``` /// /// The given struct provides function to convert from/to `Assignment` as part of -/// [`sp_npos_elections::Solution`] trait: +/// `frame_election_provider_support::NposSolution` trait: /// /// - `fn from_assignment<..>(..)` /// - `fn into_assignment<..>(..)` @@ -100,12 +108,18 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// for numbers will be used, similar to how `parity-scale-codec`'s `Compact` works. /// /// ``` -/// # use sp_npos_elections_solution_type::generate_solution_type; -/// # use sp_npos_elections::NposSolution; +/// # use frame_election_provider_solution_type::generate_solution_type; +/// # use frame_election_provider_support::NposSolution; /// # use sp_arithmetic::per_things::Perbill; +/// # use frame_support::traits::ConstU32; /// generate_solution_type!( /// #[compact] -/// pub struct TestSolutionCompact::(8) +/// pub struct TestSolutionCompact::< +/// VoterIndex = u16, +/// TargetIndex = u8, +/// Accuracy = Perbill, +/// MaxVoters = ConstU32::<10>, +/// >(8) /// ); /// ``` #[proc_macro] @@ -129,6 +143,7 @@ struct SolutionDef { voter_type: syn::Type, target_type: syn::Type, weight_type: syn::Type, + max_voters: syn::Type, count: usize, compact_encoding: bool, } @@ -138,7 +153,7 @@ fn check_attributes(input: ParseStream) -> syn::Result { if attrs.len() > 1 { let extra_attr = attrs.pop().expect("attributes vec with len > 1 can be popped"); return Err(syn::Error::new_spanned( - extra_attr.clone(), + extra_attr, "compact solution can accept only #[compact]", )) } @@ -149,7 +164,7 @@ fn check_attributes(input: ParseStream) -> syn::Result { if attr.path.is_ident("compact") { Ok(true) } else { - Err(syn::Error::new_spanned(attr.clone(), "compact solution can accept only #[compact]")) + Err(syn::Error::new_spanned(attr, "compact solution can accept only #[compact]")) } } @@ -167,11 +182,11 @@ impl Parse for SolutionDef { let _ = ::parse(input)?; let generics: syn::AngleBracketedGenericArguments = input.parse()?; - if generics.args.len() != 3 { - return Err(syn_err("Must provide 3 generic args.")) + if generics.args.len() != 4 { + return Err(syn_err("Must provide 4 generic args.")) } - let expected_types = ["VoterIndex", "TargetIndex", "Accuracy"]; + let expected_types = ["VoterIndex", "TargetIndex", "Accuracy", "MaxVoters"]; let mut types: Vec = generics .args @@ -197,6 +212,7 @@ impl Parse for SolutionDef { }) .collect::>()?; + let max_voters = types.pop().expect("Vector of length 4 can be popped; qed"); let weight_type = types.pop().expect("Vector of length 3 can be popped; qed"); let target_type = types.pop().expect("Vector of length 2 can be popped; qed"); let voter_type = types.pop().expect("Vector of length 1 can be popped; qed"); @@ -205,7 +221,16 @@ impl Parse for SolutionDef { let count_expr: syn::ExprParen = input.parse()?; let count = parse_parenthesized_number::(count_expr)?; - Ok(Self { vis, ident, voter_type, target_type, weight_type, count, compact_encoding }) + Ok(Self { + vis, + ident, + voter_type, + target_type, + weight_type, + max_voters, + count, + compact_encoding, + }) } } @@ -226,11 +251,11 @@ where } fn imports() -> Result { - match crate_name("sp-npos-elections") { - Ok(FoundCrate::Itself) => Ok(quote! { use crate as _npos; }), - Ok(FoundCrate::Name(sp_npos_elections)) => { - let ident = syn::Ident::new(&sp_npos_elections, Span::call_site()); - Ok(quote!( extern crate #ident as _npos; )) + match crate_name("frame-election-provider-support") { + Ok(FoundCrate::Itself) => Ok(quote! { use crate as _feps; }), + Ok(FoundCrate::Name(frame_election_provider_support)) => { + let ident = syn::Ident::new(&frame_election_provider_support, Span::call_site()); + Ok(quote!( extern crate #ident as _feps; )) }, Err(e) => Err(syn::Error::new(Span::call_site(), e)), } @@ -240,6 +265,11 @@ fn imports() -> Result { mod tests { #[test] fn ui_fail() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + let cases = trybuild::TestCases::new(); cases.compile_fail("tests/ui/fail/*.rs"); } diff --git a/primitives/npos-elections/solution-type/src/single_page.rs b/frame/election-provider-support/solution-type/src/single_page.rs similarity index 73% rename from primitives/npos-elections/solution-type/src/single_page.rs rename to frame/election-provider-support/solution-type/src/single_page.rs index 33017d558331a..a7ccf5085d2b1 100644 --- a/primitives/npos-elections/solution-type/src/single_page.rs +++ b/frame/election-provider-support/solution-type/src/single_page.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,18 +28,19 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { voter_type, target_type, weight_type, + max_voters, compact_encoding, } = def; if count <= 2 { - Err(syn_err("cannot build solution struct with capacity less than 3."))? + return Err(syn_err("cannot build solution struct with capacity less than 3.")) } let single = { let name = vote_field(1); // NOTE: we use the visibility of the struct for the fields as well.. could be made better. quote!( - #vis #name: _npos::sp_std::prelude::Vec<(#voter_type, #target_type)>, + #vis #name: _feps::sp_std::prelude::Vec<(#voter_type, #target_type)>, ) }; @@ -48,7 +49,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { let field_name = vote_field(c); let array_len = c - 1; quote!( - #vis #field_name: _npos::sp_std::prelude::Vec<( + #vis #field_name: _feps::sp_std::prelude::Vec<( #voter_type, [(#target_type, #weight_type); #array_len], #target_type @@ -83,9 +84,9 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { Eq, Clone, Debug, - _npos::codec::Encode, - _npos::codec::Decode, - _npos::scale_info::TypeInfo, + _feps::codec::Encode, + _feps::codec::Decode, + _feps::scale_info::TypeInfo, )]) }; @@ -101,8 +102,8 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { #derives_and_maybe_compact_encoding #vis struct #ident { #single #rest } - use _npos::__OrInvalidIndex; - impl _npos::NposSolution for #ident { + use _feps::__OrInvalidIndex; + impl _feps::NposSolution for #ident { const LIMIT: usize = #count; type VoterIndex = #voter_type; type TargetIndex = #target_type; @@ -114,34 +115,40 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { } fn from_assignment( - assignments: &[_npos::Assignment], + assignments: &[_feps::Assignment], voter_index: FV, target_index: FT, - ) -> Result + ) -> Result where - A: _npos::IdentifierT, + A: _feps::IdentifierT, for<'r> FV: Fn(&'r A) -> Option, for<'r> FT: Fn(&'r A) -> Option, { + // Make sure that the voter bound is binding. + // `assignments.len()` actually represents the number of voters + if assignments.len() as u32 > <#max_voters as _feps::Get>::get() { + return Err(_feps::Error::TooManyVoters); + } let mut #struct_name: #ident = Default::default(); - for _npos::Assignment { who, distribution } in assignments { + for _feps::Assignment { who, distribution } in assignments { match distribution.len() { 0 => continue, #from_impl _ => { - return Err(_npos::Error::SolutionTargetOverflow); + return Err(_feps::Error::SolutionTargetOverflow); } } }; + Ok(#struct_name) } - fn into_assignment( + fn into_assignment( self, voter_at: impl Fn(Self::VoterIndex) -> Option, target_at: impl Fn(Self::TargetIndex) -> Option, - ) -> Result<_npos::sp_std::prelude::Vec<_npos::Assignment>, _npos::Error> { - let mut #assignment_name: _npos::sp_std::prelude::Vec<_npos::Assignment> = Default::default(); + ) -> Result<_feps::sp_std::prelude::Vec<_feps::Assignment>, _feps::Error> { + let mut #assignment_name: _feps::sp_std::prelude::Vec<_feps::Assignment> = Default::default(); #into_impl Ok(#assignment_name) } @@ -158,10 +165,10 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { all_edges } - fn unique_targets(&self) -> _npos::sp_std::prelude::Vec { + fn unique_targets(&self) -> _feps::sp_std::prelude::Vec { // NOTE: this implementation returns the targets sorted, but we don't use it yet per // se, nor is the API enforcing it. - use _npos::sp_std::collections::btree_set::BTreeSet; + use _feps::sp_std::collections::btree_set::BTreeSet; let mut all_targets: BTreeSet = BTreeSet::new(); let mut maybe_insert_target = |t: Self::TargetIndex| { all_targets.insert(t); @@ -173,22 +180,43 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { } } - type __IndexAssignment = _npos::IndexAssignment< - <#ident as _npos::NposSolution>::VoterIndex, - <#ident as _npos::NposSolution>::TargetIndex, - <#ident as _npos::NposSolution>::Accuracy, + type __IndexAssignment = _feps::IndexAssignment< + <#ident as _feps::NposSolution>::VoterIndex, + <#ident as _feps::NposSolution>::TargetIndex, + <#ident as _feps::NposSolution>::Accuracy, >; - impl<'a> _npos::sp_std::convert::TryFrom<&'a [__IndexAssignment]> for #ident { - type Error = _npos::Error; + impl _feps::codec::MaxEncodedLen for #ident { + fn max_encoded_len() -> usize { + use frame_support::traits::Get; + use _feps::codec::Encode; + let s: u32 = #max_voters::get(); + let max_element_size = + // the first voter.. + #voter_type::max_encoded_len() + // #count - 1 tuples.. + .saturating_add( + (#count - 1).saturating_mul( + #target_type::max_encoded_len().saturating_add(#weight_type::max_encoded_len()))) + // and the last target. + .saturating_add(#target_type::max_encoded_len()); + // The assumption is that it contains #count-1 empty elements + // and then last element with full size + #count + .saturating_mul(_feps::codec::Compact(0u32).encoded_size()) + .saturating_add((s as usize).saturating_mul(max_element_size)) + } + } + impl<'a> _feps::sp_std::convert::TryFrom<&'a [__IndexAssignment]> for #ident { + type Error = _feps::Error; fn try_from(index_assignments: &'a [__IndexAssignment]) -> Result { let mut #struct_name = #ident::default(); - for _npos::IndexAssignment { who, distribution } in index_assignments { + for _feps::IndexAssignment { who, distribution } in index_assignments { match distribution.len() { 0 => {} #from_index_impl _ => { - return Err(_npos::Error::SolutionTargetOverflow); + return Err(_feps::Error::SolutionTargetOverflow); } } }; @@ -310,7 +338,7 @@ pub(crate) fn into_impl( let name = vote_field(1); quote!( for (voter_index, target_index) in self.#name { - #assignments.push(_npos::Assignment { + #assignments.push(_feps::Assignment { who: voter_at(voter_index).or_invalid_index()?, distribution: vec![ (target_at(target_index).or_invalid_index()?, #per_thing::one()) @@ -329,25 +357,25 @@ pub(crate) fn into_impl( let mut inners_parsed = inners .iter() .map(|(ref t_idx, p)| { - sum = _npos::sp_arithmetic::traits::Saturating::saturating_add(sum, *p); + sum = _feps::sp_arithmetic::traits::Saturating::saturating_add(sum, *p); let target = target_at(*t_idx).or_invalid_index()?; Ok((target, *p)) }) - .collect::, _npos::Error>>()?; + .collect::, _feps::Error>>()?; if sum >= #per_thing::one() { - return Err(_npos::Error::SolutionWeightOverflow); + return Err(_feps::Error::SolutionWeightOverflow); } // defensive only. Since Percent doesn't have `Sub`. - let p_last = _npos::sp_arithmetic::traits::Saturating::saturating_sub( + let p_last = _feps::sp_arithmetic::traits::Saturating::saturating_sub( #per_thing::one(), sum, ); inners_parsed.push((target_at(t_last_idx).or_invalid_index()?, p_last)); - #assignments.push(_npos::Assignment { + #assignments.push(_feps::Assignment { who: voter_at(voter_index).or_invalid_index()?, distribution: inners_parsed, }); diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_accuracy.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.rs similarity index 55% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_accuracy.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.rs index b74b857e45815..52ae9623fd384 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/missing_accuracy.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.rs @@ -1,9 +1,10 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!(pub struct TestSolution::< VoterIndex = u16, TargetIndex = u8, Perbill, + MaxVoters = ConstU32::<10>, >(8)); fn main() {} diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_accuracy.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_accuracy.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.stderr diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.rs new file mode 100644 index 0000000000000..fe8ac04cc8d61 --- /dev/null +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.rs @@ -0,0 +1,10 @@ +use frame_election_provider_solution_type::generate_solution_type; + +generate_solution_type!(pub struct TestSolution::< + VoterIndex = u16, + TargetIndex = u8, + Accuracy = Perbill, + ConstU32::<10>, +>(8)); + +fn main() {} diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.stderr new file mode 100644 index 0000000000000..c685ab816d399 --- /dev/null +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.stderr @@ -0,0 +1,5 @@ +error: Expected binding: `MaxVoters = ...` + --> tests/ui/fail/missing_size_bound.rs:7:2 + | +7 | ConstU32::<10>, + | ^^^^^^^^^^^^^^ diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_target.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_target.rs similarity index 55% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_target.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_target.rs index 4c9cd51a32096..b457c4abada6c 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/missing_target.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_target.rs @@ -1,9 +1,10 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!(pub struct TestSolution::< VoterIndex = u16, u8, Accuracy = Perbill, + MaxVoters = ConstU32::<10>, >(8)); fn main() {} diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_target.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/missing_target.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_target.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_target.stderr diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_voter.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.rs similarity index 55% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_voter.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.rs index b87037f77f1e3..3d12e3e6b5ec4 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/missing_voter.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.rs @@ -1,9 +1,10 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!(pub struct TestSolution::< u16, TargetIndex = u8, Accuracy = Perbill, + MaxVoters = ConstU32::<10>, >(8)); fn main() {} diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_voter.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_voter.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.stderr diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.rs b/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.rs new file mode 100644 index 0000000000000..9aab15e7ec9a1 --- /dev/null +++ b/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.rs @@ -0,0 +1,10 @@ +use frame_election_provider_solution_type::generate_solution_type; + +generate_solution_type!(pub struct TestSolution::< + u16, + u8, + Perbill, + MaxVoters = ConstU32::<10>, +>(8)); + +fn main() {} diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/no_annotations.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/no_annotations.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.stderr diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/swap_voter_target.rs b/frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.rs similarity index 57% rename from primitives/npos-elections/solution-type/tests/ui/fail/swap_voter_target.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.rs index 443202d11b39b..4275aae045a60 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/swap_voter_target.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.rs @@ -1,9 +1,10 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!(pub struct TestSolution::< TargetIndex = u16, VoterIndex = u8, Accuracy = Perbill, + MaxVoters = ConstU32::<10>, >(8)); fn main() {} diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/swap_voter_target.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/swap_voter_target.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.stderr diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/wrong_attribute.rs b/frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.rs similarity index 60% rename from primitives/npos-elections/solution-type/tests/ui/fail/wrong_attribute.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.rs index 3008277e36b74..a51cc724ad158 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/wrong_attribute.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.rs @@ -1,10 +1,11 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!( #[pages(1)] pub struct TestSolution::< VoterIndex = u8, TargetIndex = u16, Accuracy = Perbill, + MaxVoters = ConstU32::<10>, >(8) ); diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/wrong_attribute.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/wrong_attribute.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.stderr diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index d2c4b1053cc6d..8b26148844c39 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,10 +20,11 @@ //! This crate provides two traits that could interact to enable extensible election functionality //! within FRAME pallets. //! -//! Something that will provide the functionality of election will implement [`ElectionProvider`], -//! whilst needing an associated [`ElectionProvider::DataProvider`], which needs to be fulfilled by -//! an entity implementing [`ElectionDataProvider`]. Most often, *the data provider is* the receiver -//! of the election, resulting in a diagram as below: +//! Something that will provide the functionality of election will implement +//! [`ElectionProvider`] and its parent-trait [`ElectionProviderBase`], whilst needing an +//! associated [`ElectionProviderBase::DataProvider`], which needs to be +//! fulfilled by an entity implementing [`ElectionDataProvider`]. Most often, *the data provider is* +//! the receiver of the election, resulting in a diagram as below: //! //! ```ignore //! ElectionDataProvider @@ -80,6 +81,8 @@ //! ```rust //! # use frame_election_provider_support::{*, data_provider}; //! # use sp_npos_elections::{Support, Assignment}; +//! # use frame_support::traits::ConstU32; +//! # use frame_support::bounded_vec; //! //! type AccountId = u64; //! type Balance = u64; @@ -90,25 +93,28 @@ //! //! pub trait Config: Sized { //! type ElectionProvider: ElectionProvider< -//! AccountId, -//! BlockNumber, -//! DataProvider = Module, +//! AccountId = AccountId, +//! BlockNumber = BlockNumber, +//! DataProvider = Pallet, //! >; //! } //! -//! pub struct Module(std::marker::PhantomData); +//! pub struct Pallet(std::marker::PhantomData); +//! +//! impl ElectionDataProvider for Pallet { +//! type AccountId = AccountId; +//! type BlockNumber = BlockNumber; +//! type MaxVotesPerVoter = ConstU32<1>; //! -//! impl ElectionDataProvider for Module { -//! const MAXIMUM_VOTES_PER_VOTER: u32 = 1; //! fn desired_targets() -> data_provider::Result { //! Ok(1) //! } -//! fn voters(maybe_max_len: Option) -//! -> data_provider::Result)>> +//! fn electing_voters(maybe_max_len: Option) +//! -> data_provider::Result>> //! { //! Ok(Default::default()) //! } -//! fn targets(maybe_max_len: Option) -> data_provider::Result> { +//! fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { //! Ok(vec![10, 20, 30]) //! } //! fn next_election_prediction(now: BlockNumber) -> BlockNumber { @@ -124,17 +130,24 @@ //! pub struct GenericElectionProvider(std::marker::PhantomData); //! //! pub trait Config { -//! type DataProvider: ElectionDataProvider; +//! type DataProvider: ElectionDataProvider; //! } //! -//! impl ElectionProvider for GenericElectionProvider { +//! impl ElectionProviderBase for GenericElectionProvider { +//! type AccountId = AccountId; +//! type BlockNumber = BlockNumber; //! type Error = &'static str; //! type DataProvider = T::DataProvider; +//! type MaxWinners = ConstU32<{ u32::MAX }>; +//! +//! } //! -//! fn elect() -> Result, Self::Error> { -//! Self::DataProvider::targets(None) +//! impl ElectionProvider for GenericElectionProvider { +//! fn ongoing() -> bool { false } +//! fn elect() -> Result, Self::Error> { +//! Self::DataProvider::electable_targets(None) //! .map_err(|_| "failed to elect") -//! .map(|t| vec![(t[0], Support::default())]) +//! .map(|t| bounded_vec![(t[0], Support::default())]) //! } //! } //! } @@ -146,7 +159,7 @@ //! //! struct Runtime; //! impl generic_election_provider::Config for Runtime { -//! type DataProvider = data_provider_mod::Module; +//! type DataProvider = data_provider_mod::Pallet; //! } //! //! impl data_provider_mod::Config for Runtime { @@ -161,15 +174,93 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod onchain; -use frame_support::traits::Get; +pub mod traits; +use sp_runtime::traits::{Bounded, Saturating, Zero}; use sp_std::{fmt::Debug, prelude::*}; +/// Re-export the solution generation macro. +pub use frame_election_provider_solution_type::generate_solution_type; +pub use frame_support::{traits::Get, weights::Weight, BoundedVec, RuntimeDebug}; /// Re-export some type as they are used in the interface. pub use sp_arithmetic::PerThing; pub use sp_npos_elections::{ - Assignment, ElectionResult, ExtendedBalance, IdentifierT, PerThing128, Support, Supports, - VoteWeight, + Assignment, BalancingConfig, BoundedSupports, ElectionResult, Error, ExtendedBalance, + IdentifierT, PerThing128, Support, Supports, VoteWeight, }; +pub use traits::NposSolution; + +// re-export for the solution macro, with the dependencies of the macro. +#[doc(hidden)] +pub use codec; +#[doc(hidden)] +pub use scale_info; +#[doc(hidden)] +pub use sp_arithmetic; +#[doc(hidden)] +pub use sp_std; + +pub mod weights; +pub use weights::WeightInfo; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +// Simple Extension trait to easily convert `None` from index closures to `Err`. +// +// This is only generated and re-exported for the solution code to use. +#[doc(hidden)] +pub trait __OrInvalidIndex { + fn or_invalid_index(self) -> Result; +} + +impl __OrInvalidIndex for Option { + fn or_invalid_index(self) -> Result { + self.ok_or(Error::SolutionInvalidIndex) + } +} + +/// The [`IndexAssignment`] type is an intermediate between the assignments list +/// ([`&[Assignment]`][Assignment]) and `SolutionOf`. +/// +/// The voter and target identifiers have already been replaced with appropriate indices, +/// making it fast to repeatedly encode into a `SolutionOf`. This property turns out +/// to be important when trimming for solution length. +#[derive(RuntimeDebug, Clone, Default)] +#[cfg_attr(feature = "std", derive(PartialEq, Eq, codec::Encode, codec::Decode))] +pub struct IndexAssignment { + /// Index of the voter among the voters list. + pub who: VoterIndex, + /// The distribution of the voter's stake among winning targets. + /// + /// Targets are identified by their index in the canonical list. + pub distribution: Vec<(TargetIndex, P)>, +} + +impl IndexAssignment { + pub fn new( + assignment: &Assignment, + voter_index: impl Fn(&AccountId) -> Option, + target_index: impl Fn(&AccountId) -> Option, + ) -> Result { + Ok(Self { + who: voter_index(&assignment.who).or_invalid_index()?, + distribution: assignment + .distribution + .iter() + .map(|(target, proportion)| Some((target_index(target)?, *proportion))) + .collect::>>() + .or_invalid_index()?, + }) + } +} + +/// A type alias for [`IndexAssignment`] made from [`NposSolution`]. +pub type IndexAssignmentOf = IndexAssignment< + ::VoterIndex, + ::TargetIndex, + ::Accuracy, +>; /// Types that are used by the data provider trait. pub mod data_provider { @@ -178,20 +269,29 @@ pub mod data_provider { } /// Something that can provide the data to an [`ElectionProvider`]. -pub trait ElectionDataProvider { +pub trait ElectionDataProvider { + /// The account identifier type. + type AccountId; + + /// The block number type. + type BlockNumber; + /// Maximum number of votes per voter that this data provider is providing. - const MAXIMUM_VOTES_PER_VOTER: u32; + type MaxVotesPerVoter: Get; - /// All possible targets for the election, i.e. the candidates. + /// All possible targets for the election, i.e. the targets that could become elected, thus + /// "electable". /// /// If `maybe_max_len` is `Some(v)` then the resulting vector MUST NOT be longer than `v` items /// long. /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. - fn targets(maybe_max_len: Option) -> data_provider::Result>; + fn electable_targets( + maybe_max_len: Option, + ) -> data_provider::Result>; - /// All possible voters for the election. + /// All the voters that participate in the election, thus "electing". /// /// Note that if a notion of self-vote exists, it should be represented here. /// @@ -200,14 +300,18 @@ pub trait ElectionDataProvider { /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. - fn voters( - maybe_max_len: Option, - ) -> data_provider::Result)>>; + fn electing_voters(maybe_max_len: Option) -> data_provider::Result>>; /// The number of targets to elect. /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. + /// + /// A sensible implementation should use the minimum between this value and + /// [`Self::targets().len()`], since desiring a winner set larger than candidates is not + /// feasible. + /// + /// This is documented further in issue: fn desired_targets() -> data_provider::Result; /// Provide a best effort prediction about when the next election is about to happen. @@ -216,14 +320,14 @@ pub trait ElectionDataProvider { /// [`ElectionProvider::elect`]. /// /// This is only useful for stateful election providers. - fn next_election_prediction(now: BlockNumber) -> BlockNumber; + fn next_election_prediction(now: Self::BlockNumber) -> Self::BlockNumber; /// Utility function only to be used in benchmarking scenarios, to be implemented optionally, /// else a noop. #[cfg(any(feature = "runtime-benchmarks", test))] fn put_snapshot( - _voters: Vec<(AccountId, VoteWeight, Vec)>, - _targets: Vec, + _voters: Vec>, + _targets: Vec, _target_stake: Option, ) { } @@ -233,68 +337,252 @@ pub trait ElectionDataProvider { /// /// Same as `put_snapshot`, but can add a single voter one by one. #[cfg(any(feature = "runtime-benchmarks", test))] - fn add_voter(_voter: AccountId, _weight: VoteWeight, _targets: Vec) {} + fn add_voter( + _voter: Self::AccountId, + _weight: VoteWeight, + _targets: BoundedVec, + ) { + } /// Utility function only to be used in benchmarking scenarios, to be implemented optionally, /// else a noop. /// /// Same as `put_snapshot`, but can add a single voter one by one. #[cfg(any(feature = "runtime-benchmarks", test))] - fn add_target(_target: AccountId) {} + fn add_target(_target: Self::AccountId) {} /// Clear all voters and targets. #[cfg(any(feature = "runtime-benchmarks", test))] fn clear() {} } -#[cfg(feature = "std")] -impl ElectionDataProvider for () { - const MAXIMUM_VOTES_PER_VOTER: u32 = 0; - fn targets(_maybe_max_len: Option) -> data_provider::Result> { - Ok(Default::default()) +/// Base trait for types that can provide election +pub trait ElectionProviderBase { + /// The account identifier type. + type AccountId; + + /// The block number type. + type BlockNumber; + + /// The error type that is returned by the provider. + type Error: Debug; + + /// The upper bound on election winners that can be returned. + /// + /// # WARNING + /// + /// when communicating with the data provider, one must ensure that + /// `DataProvider::desired_targets` returns a value less than this bound. An + /// implementation can chose to either return an error and/or sort and + /// truncate the output to meet this bound. + type MaxWinners: Get; + + /// The data provider of the election. + type DataProvider: ElectionDataProvider< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, + >; + + /// checked call to `Self::DataProvider::desired_targets()` ensuring the value never exceeds + /// [`Self::MaxWinners`]. + fn desired_targets_checked() -> data_provider::Result { + Self::DataProvider::desired_targets().and_then(|desired_targets| { + if desired_targets <= Self::MaxWinners::get() { + Ok(desired_targets) + } else { + Err("desired_targets must not be greater than MaxWinners.") + } + }) } - fn voters( - _maybe_max_len: Option, - ) -> data_provider::Result)>> { - Ok(Default::default()) +} + +/// Elect a new set of winners, bounded by `MaxWinners`. +/// +/// It must always use [`ElectionProviderBase::DataProvider`] to fetch the data it needs. +/// +/// This election provider that could function asynchronously. This implies that this election might +/// needs data ahead of time (ergo, receives no arguments to `elect`), and might be `ongoing` at +/// times. +pub trait ElectionProvider: ElectionProviderBase { + /// Indicate if this election provider is currently ongoing an asynchronous election or not. + fn ongoing() -> bool; + + /// Performs the election. This should be implemented as a self-weighing function. The + /// implementor should register its appropriate weight at the end of execution with the + /// system pallet directly. + fn elect() -> Result, Self::Error>; +} + +/// A (almost) marker trait that signifies an election provider as working synchronously. i.e. being +/// *instant*. +/// +/// This must still use the same data provider as with [`ElectionProviderBase::DataProvider`]. +/// However, it can optionally overwrite the amount of voters and targets that are fetched from the +/// data provider at runtime via `forced_input_voters_bound` and `forced_input_target_bound`. +pub trait InstantElectionProvider: ElectionProviderBase { + fn instant_elect( + forced_input_voters_bound: Option, + forced_input_target_bound: Option, + ) -> Result, Self::Error>; +} + +/// An election provider that does nothing whatsoever. +pub struct NoElection(sp_std::marker::PhantomData); + +impl ElectionProviderBase + for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> +where + DataProvider: ElectionDataProvider, + MaxWinners: Get, +{ + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type Error = &'static str; + type MaxWinners = MaxWinners; + type DataProvider = DataProvider; +} + +impl ElectionProvider + for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> +where + DataProvider: ElectionDataProvider, + MaxWinners: Get, +{ + fn ongoing() -> bool { + false } - fn desired_targets() -> data_provider::Result { - Ok(Default::default()) + + fn elect() -> Result, Self::Error> { + Err("`NoElection` cannot do anything.") } - fn next_election_prediction(now: BlockNumber) -> BlockNumber { - now +} + +impl InstantElectionProvider + for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> +where + DataProvider: ElectionDataProvider, + MaxWinners: Get, +{ + fn instant_elect( + _: Option, + _: Option, + ) -> Result, Self::Error> { + Err("`NoElection` cannot do anything.") } } -/// Something that can compute the result of an election and pass it back to the caller. +/// A utility trait for something to implement `ElectionDataProvider` in a sensible way. /// -/// This trait only provides an interface to _request_ an election, i.e. -/// [`ElectionProvider::elect`]. That data required for the election need to be passed to the -/// implemented of this trait through [`ElectionProvider::DataProvider`]. -pub trait ElectionProvider { - /// The error type that is returned by the provider. - type Error: Debug; +/// This is generic over `AccountId` and it can represent a validator, a nominator, or any other +/// entity. +/// +/// The scores (see [`Self::Score`]) are ascending, the higher, the better. +/// +/// Something that implements this trait will do a best-effort sort over ids, and thus can be +/// used on the implementing side of [`ElectionDataProvider`]. +pub trait SortedListProvider { + /// The list's error type. + type Error: sp_std::fmt::Debug; - /// The data provider of the election. - type DataProvider: ElectionDataProvider; + /// The type used by the list to compare nodes for ordering. + type Score: Bounded + Saturating + Zero; - /// Elect a new set of winners. + /// An iterator over the list, which can have `take` called on it. + fn iter() -> Box>; + + /// Returns an iterator over the list, starting right after from the given voter. /// - /// The result is returned in a target major format, namely as vector of supports. + /// May return an error if `start` is invalid. + fn iter_from(start: &AccountId) -> Result>, Self::Error>; + + /// The current count of ids in the list. + fn count() -> u32; + + /// Return true if the list already contains `id`. + fn contains(id: &AccountId) -> bool; + + /// Hook for inserting a new id. /// - /// This should be implemented as a self-weighing function. The implementor should register its - /// appropriate weight at the end of execution with the system pallet directly. - fn elect() -> Result, Self::Error>; -} + /// Implementation should return an error if duplicate item is being inserted. + fn on_insert(id: AccountId, score: Self::Score) -> Result<(), Self::Error>; -#[cfg(feature = "std")] -impl ElectionProvider for () { - type Error = &'static str; - type DataProvider = (); + /// Hook for updating a single id. + /// + /// The `new` score is given. + /// + /// Returns `Ok(())` iff it successfully updates an item, an `Err(_)` otherwise. + fn on_update(id: &AccountId, score: Self::Score) -> Result<(), Self::Error>; + + /// Get the score of `id`. + fn get_score(id: &AccountId) -> Result; + + /// Same as `on_update`, but incorporate some increased score. + fn on_increase(id: &AccountId, additional: Self::Score) -> Result<(), Self::Error> { + let old_score = Self::get_score(id)?; + let new_score = old_score.saturating_add(additional); + Self::on_update(id, new_score) + } - fn elect() -> Result, Self::Error> { - Err("<() as ElectionProvider> cannot do anything.") + /// Same as `on_update`, but incorporate some decreased score. + /// + /// If the new score of the item is `Zero`, it is removed. + fn on_decrease(id: &AccountId, decreased: Self::Score) -> Result<(), Self::Error> { + let old_score = Self::get_score(id)?; + let new_score = old_score.saturating_sub(decreased); + if new_score.is_zero() { + Self::on_remove(id) + } else { + Self::on_update(id, new_score) + } } + + /// Hook for removing am id from the list. + /// + /// Returns `Ok(())` iff it successfully removes an item, an `Err(_)` otherwise. + fn on_remove(id: &AccountId) -> Result<(), Self::Error>; + + /// Regenerate this list from scratch. Returns the count of items inserted. + /// + /// This should typically only be used at a runtime upgrade. + /// + /// ## WARNING + /// + /// This function should be called with care, regenerate will remove the current list write the + /// new list, which can lead to too many storage accesses, exhausting the block weight. + fn unsafe_regenerate( + all: impl IntoIterator, + score_of: Box Self::Score>, + ) -> u32; + + /// Remove all items from the list. + /// + /// ## WARNING + /// + /// This function should never be called in production settings because it can lead to an + /// unbounded amount of storage accesses. + fn unsafe_clear(); + + /// Check internal state of list. Only meant for debugging. + fn try_state() -> Result<(), &'static str>; + + /// If `who` changes by the returned amount they are guaranteed to have a worst case change + /// in their list position. + #[cfg(feature = "runtime-benchmarks")] + fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score; +} + +/// Something that can provide the `Score` of an account. Similar to [`ElectionProvider`] and +/// [`ElectionDataProvider`], this should typically be implementing by whoever is supposed to *use* +/// `SortedListProvider`. +pub trait ScoreProvider { + type Score; + + /// Get the current `Score` of `who`. + fn score(who: &AccountId) -> Self::Score; + + /// For tests, benchmarks and fuzzing, set the `score`. + #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", test))] + fn set_score_of(_: &AccountId, _: Self::Score) {} } /// Something that can compute the result to an NPoS solution. @@ -311,21 +599,24 @@ pub trait NposSolver { fn solve( to_elect: usize, targets: Vec, - voters: Vec<(Self::AccountId, VoteWeight, Vec)>, + voters: Vec<(Self::AccountId, VoteWeight, impl IntoIterator)>, ) -> Result, Self::Error>; + + /// Measure the weight used in the calculation of the solver. + /// - `voters` is the number of voters. + /// - `targets` is the number of targets. + /// - `vote_degree` is the degree ie the maximum numbers of votes per voter. + fn weight(voters: u32, targets: u32, vote_degree: u32) -> Weight; } -/// A wrapper for [`sp_npos_elections::seq_phragmen`] that implements [`super::NposSolver`]. See the +/// A wrapper for [`sp_npos_elections::seq_phragmen`] that implements [`NposSolver`]. See the /// documentation of [`sp_npos_elections::seq_phragmen`] for more info. pub struct SequentialPhragmen( sp_std::marker::PhantomData<(AccountId, Accuracy, Balancing)>, ); -impl< - AccountId: IdentifierT, - Accuracy: PerThing128, - Balancing: Get>, - > NposSolver for SequentialPhragmen +impl>> + NposSolver for SequentialPhragmen { type AccountId = AccountId; type Accuracy = Accuracy; @@ -333,23 +624,24 @@ impl< fn solve( winners: usize, targets: Vec, - voters: Vec<(Self::AccountId, VoteWeight, Vec)>, + voters: Vec<(Self::AccountId, VoteWeight, impl IntoIterator)>, ) -> Result, Self::Error> { sp_npos_elections::seq_phragmen(winners, targets, voters, Balancing::get()) } + + fn weight(voters: u32, targets: u32, vote_degree: u32) -> Weight { + T::phragmen(voters, targets, vote_degree) + } } -/// A wrapper for [`sp_npos_elections::phragmms`] that implements [`NposSolver`]. See the -/// documentation of [`sp_npos_elections::phragmms`] for more info. +/// A wrapper for [`sp_npos_elections::phragmms()`] that implements [`NposSolver`]. See the +/// documentation of [`sp_npos_elections::phragmms()`] for more info. pub struct PhragMMS( sp_std::marker::PhantomData<(AccountId, Accuracy, Balancing)>, ); -impl< - AccountId: IdentifierT, - Accuracy: PerThing128, - Balancing: Get>, - > NposSolver for PhragMMS +impl>> + NposSolver for PhragMMS { type AccountId = AccountId; type Accuracy = Accuracy; @@ -357,8 +649,25 @@ impl< fn solve( winners: usize, targets: Vec, - voters: Vec<(Self::AccountId, VoteWeight, Vec)>, + voters: Vec<(Self::AccountId, VoteWeight, impl IntoIterator)>, ) -> Result, Self::Error> { sp_npos_elections::phragmms(winners, targets, voters, Balancing::get()) } + + fn weight(voters: u32, targets: u32, vote_degree: u32) -> Weight { + T::phragmms(voters, targets, vote_degree) + } } + +/// A voter, at the level of abstraction of this crate. +pub type Voter = (AccountId, VoteWeight, BoundedVec); + +/// Same as [`Voter`], but parameterized by an [`ElectionDataProvider`]. +pub type VoterOf = + Voter<::AccountId, ::MaxVotesPerVoter>; + +/// Same as `BoundedSupports` but parameterized by a `ElectionProviderBase`. +pub type BoundedSupportsOf = BoundedSupports< + ::AccountId, + ::MaxWinners, +>; diff --git a/frame/election-provider-support/src/mock.rs b/frame/election-provider-support/src/mock.rs new file mode 100644 index 0000000000000..7c834f06f3cdf --- /dev/null +++ b/frame/election-provider-support/src/mock.rs @@ -0,0 +1,184 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Mock file for solution-type. + +#![cfg(test)] + +use std::{ + collections::{HashMap, HashSet}, + hash::Hash, +}; + +use rand::{seq::SliceRandom, Rng}; + +pub type AccountId = u64; + +/// The candidate mask allows easy disambiguation between voters and candidates: accounts +/// for which this bit is set are candidates, and without it, are voters. +pub const CANDIDATE_MASK: AccountId = 1 << ((std::mem::size_of::() * 8) - 1); + +pub type TestAccuracy = sp_runtime::Perbill; + +pub fn p(p: u8) -> TestAccuracy { + TestAccuracy::from_percent(p.into()) +} + +pub type MockAssignment = crate::Assignment; +pub type Voter = (AccountId, crate::VoteWeight, Vec); + +crate::generate_solution_type! { + pub struct TestSolution::< + VoterIndex = u32, + TargetIndex = u16, + Accuracy = TestAccuracy, + MaxVoters = frame_support::traits::ConstU32::<2_500>, + >(16) +} + +/// Generate voter and assignment lists. Makes no attempt to be realistic about winner or assignment +/// fairness. +/// +/// Maintains these invariants: +/// +/// - candidate ids have `CANDIDATE_MASK` bit set +/// - voter ids do not have `CANDIDATE_MASK` bit set +/// - assignments have the same ordering as voters +/// - `assignments.distribution.iter().map(|(_, frac)| frac).sum() == One::one()` +/// - a coherent set of winners is chosen. +/// - the winner set is a subset of the candidate set. +/// - `assignments.distribution.iter().all(|(who, _)| winners.contains(who))` +pub fn generate_random_votes( + candidate_count: usize, + voter_count: usize, + mut rng: impl Rng, +) -> (Vec, Vec, Vec) { + // cache for fast generation of unique candidate and voter ids + let mut used_ids = HashSet::with_capacity(candidate_count + voter_count); + + // candidates are easy: just a completely random set of IDs + let mut candidates: Vec = Vec::with_capacity(candidate_count); + while candidates.len() < candidate_count { + let mut new = || rng.gen::() | CANDIDATE_MASK; + let mut id = new(); + // insert returns `false` when the value was already present + while !used_ids.insert(id) { + id = new(); + } + candidates.push(id); + } + + // voters are random ids, random weights, random selection from the candidates + let mut voters = Vec::with_capacity(voter_count); + while voters.len() < voter_count { + let mut new = || rng.gen::() & !CANDIDATE_MASK; + let mut id = new(); + // insert returns `false` when the value was already present + while !used_ids.insert(id) { + id = new(); + } + + let vote_weight = rng.gen(); + + // it's not interesting if a voter chooses 0 or all candidates, so rule those cases out. + // also, let's not generate any cases which result in a compact overflow. + let n_candidates_chosen = + rng.gen_range(1, candidates.len().min(::LIMIT)); + + let mut chosen_candidates = Vec::with_capacity(n_candidates_chosen); + chosen_candidates.extend(candidates.choose_multiple(&mut rng, n_candidates_chosen)); + voters.push((id, vote_weight, chosen_candidates)); + } + + // always generate a sensible number of winners: elections are uninteresting if nobody wins, + // or everybody wins + let num_winners = rng.gen_range(1, candidate_count); + let mut winners: HashSet = HashSet::with_capacity(num_winners); + winners.extend(candidates.choose_multiple(&mut rng, num_winners)); + assert_eq!(winners.len(), num_winners); + + let mut assignments = Vec::with_capacity(voters.len()); + for (voter_id, _, votes) in voters.iter() { + let chosen_winners = votes.iter().filter(|vote| winners.contains(vote)).cloned(); + let num_chosen_winners = chosen_winners.clone().count(); + + // distribute the available stake randomly + let stake_distribution = if num_chosen_winners == 0 { + continue + } else { + let mut available_stake = 1000; + let mut stake_distribution = Vec::with_capacity(num_chosen_winners); + for _ in 0..num_chosen_winners - 1 { + let stake = rng.gen_range(0, available_stake).min(1); + stake_distribution.push(TestAccuracy::from_perthousand(stake)); + available_stake -= stake; + } + stake_distribution.push(TestAccuracy::from_perthousand(available_stake)); + stake_distribution.shuffle(&mut rng); + stake_distribution + }; + + assignments.push(MockAssignment { + who: *voter_id, + distribution: chosen_winners.zip(stake_distribution).collect(), + }); + } + + (voters, assignments, candidates) +} + +fn generate_cache(voters: Voters) -> HashMap +where + Voters: Iterator, + Item: Hash + Eq + Copy, +{ + let mut cache = HashMap::new(); + for (idx, voter_id) in voters.enumerate() { + cache.insert(voter_id, idx); + } + cache +} + +/// Create a function that returns the index of a voter in the voters list. +pub fn make_voter_fn(voters: &[Voter]) -> impl Fn(&AccountId) -> Option +where + usize: TryInto, +{ + let cache = generate_cache(voters.iter().map(|(id, _, _)| *id)); + move |who| { + if cache.get(who).is_none() { + println!("WARNING: voter {} will raise InvalidIndex", who); + } + cache.get(who).cloned().and_then(|i| i.try_into().ok()) + } +} + +/// Create a function that returns the index of a candidate in the candidates list. +pub fn make_target_fn( + candidates: &[AccountId], +) -> impl Fn(&AccountId) -> Option +where + usize: TryInto, +{ + let cache = generate_cache(candidates.iter().cloned()); + move |who| { + if cache.get(who).is_none() { + println!("WARNING: target {} will raise InvalidIndex", who); + } + cache.get(who).cloned().and_then(|i| i.try_into().ok()) + } +} diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs index fb1ccfdfe2566..483c402fe249c 100644 --- a/frame/election-provider-support/src/onchain.rs +++ b/frame/election-provider-support/src/onchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,11 +15,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! An implementation of [`ElectionProvider`] that does an on-chain sequential phragmen. +//! An implementation of [`ElectionProvider`] that uses an `NposSolver` to do the election. As the +//! name suggests, this is meant to be used onchain. Given how heavy the calculations are, please be +//! careful when using it onchain. -use crate::{ElectionDataProvider, ElectionProvider}; -use frame_support::{traits::Get, weights::DispatchClass}; -use sp_npos_elections::*; +use crate::{ + BoundedSupportsOf, Debug, ElectionDataProvider, ElectionProvider, ElectionProviderBase, + InstantElectionProvider, NposSolver, WeightInfo, +}; +use frame_support::{dispatch::DispatchClass, traits::Get}; +use sp_npos_elections::{ + assignment_ratio_to_staked_normalized, to_supports, BoundedSupports, ElectionResult, VoteWeight, +}; use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*}; /// Errors of the on-chain election. @@ -29,6 +36,9 @@ pub enum Error { NposElections(sp_npos_elections::Error), /// Errors from the data provider. DataProvider(&'static str), + /// Configurational error caused by `desired_targets` requested by data provider exceeding + /// `MaxWinners`. + TooManyWinners, } impl From for Error { @@ -39,71 +49,146 @@ impl From for Error { /// A simple on-chain implementation of the election provider trait. /// -/// This will accept voting data on the fly and produce the results immediately. +/// This implements both `ElectionProvider` and `InstantElectionProvider`. /// -/// ### Warning -/// -/// This can be very expensive to run frequently on-chain. Use with care. Moreover, this -/// implementation ignores the additional data of the election data provider and gives no insight on -/// how much weight was consumed. -/// -/// Finally, this implementation does not impose any limits on the number of voters and targets that -/// are provided. -pub struct OnChainSequentialPhragmen(PhantomData); +/// This type has some utilities to make it safe. Nonetheless, it should be used with utmost care. A +/// thoughtful value must be set as [`Config::VotersBound`] and [`Config::TargetsBound`] to ensure +/// the size of the input is sensible. +pub struct OnChainExecution(PhantomData); + +#[deprecated(note = "use OnChainExecution, which is bounded by default")] +pub type BoundedExecution = OnChainExecution; + +/// Configuration trait for an onchain election execution. +pub trait Config { + /// Needed for weight registration. + type System: frame_system::Config; + + /// `NposSolver` that should be used, an example would be `PhragMMS`. + type Solver: NposSolver< + AccountId = ::AccountId, + Error = sp_npos_elections::Error, + >; -/// Configuration trait of [`OnChainSequentialPhragmen`]. -/// -/// Note that this is similar to a pallet traits, but [`OnChainSequentialPhragmen`] is not a pallet. -/// -/// WARNING: the user of this pallet must ensure that the `Accuracy` type will work nicely with the -/// normalization operation done inside `seq_phragmen`. See -/// [`sp_npos_elections::assignment::try_normalize`] for more info. -pub trait Config: frame_system::Config { - /// The accuracy used to compute the election: - type Accuracy: PerThing128; /// Something that provides the data for election. - type DataProvider: ElectionDataProvider; + type DataProvider: ElectionDataProvider< + AccountId = ::AccountId, + BlockNumber = ::BlockNumber, + >; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Upper bound on maximum winners from electable targets. + /// + /// As noted in the documentation of [`ElectionProviderBase::MaxWinners`], this value should + /// always be more than `DataProvider::desired_target`. + type MaxWinners: Get; + + /// Bounds the number of voters, when calling into [`Config::DataProvider`]. It might be + /// overwritten in the `InstantElectionProvider` impl. + type VotersBound: Get; + + /// Bounds the number of targets, when calling into [`Config::DataProvider`]. It might be + /// overwritten in the `InstantElectionProvider` impl. + type TargetsBound: Get; } -impl ElectionProvider for OnChainSequentialPhragmen { - type Error = Error; - type DataProvider = T::DataProvider; +/// Same as `BoundedSupportsOf` but for `onchain::Config`. +pub type OnChainBoundedSupportsOf = BoundedSupports< + <::System as frame_system::Config>::AccountId, + ::MaxWinners, +>; + +fn elect_with_input_bounds( + maybe_max_voters: Option, + maybe_max_targets: Option, +) -> Result, Error> { + let voters = T::DataProvider::electing_voters(maybe_max_voters).map_err(Error::DataProvider)?; + let targets = + T::DataProvider::electable_targets(maybe_max_targets).map_err(Error::DataProvider)?; + let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; - fn elect() -> Result, Self::Error> { - let voters = Self::DataProvider::voters(None).map_err(Error::DataProvider)?; - let targets = Self::DataProvider::targets(None).map_err(Error::DataProvider)?; - let desired_targets = Self::DataProvider::desired_targets().map_err(Error::DataProvider)?; + if desired_targets > T::MaxWinners::get() { + // early exit + return Err(Error::TooManyWinners) + } + + let voters_len = voters.len() as u32; + let targets_len = targets.len() as u32; + + let stake_map: BTreeMap<_, _> = voters + .iter() + .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight)) + .collect(); + + let stake_of = |w: &::AccountId| -> VoteWeight { + stake_map.get(w).cloned().unwrap_or_default() + }; - let stake_map: BTreeMap = voters - .iter() - .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight)) - .collect(); + let ElectionResult { winners: _, assignments } = + T::Solver::solve(desired_targets as usize, targets, voters).map_err(Error::from)?; - let stake_of = - |w: &T::AccountId| -> VoteWeight { stake_map.get(w).cloned().unwrap_or_default() }; + let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; + + let weight = T::Solver::weight::( + voters_len, + targets_len, + ::MaxVotesPerVoter::get(), + ); + frame_system::Pallet::::register_extra_weight_unchecked( + weight, + DispatchClass::Mandatory, + ); - let ElectionResult { winners: _, assignments } = - seq_phragmen::<_, T::Accuracy>(desired_targets as usize, targets, voters, None) - .map_err(Error::from)?; + // defensive: Since npos solver returns a result always bounded by `desired_targets`, this is + // never expected to happen as long as npos solver does what is expected for it to do. + let supports: OnChainBoundedSupportsOf = + to_supports(&staked).try_into().map_err(|_| Error::TooManyWinners)?; - let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; + Ok(supports) +} - let weight = T::BlockWeights::get().max_block; - frame_system::Pallet::::register_extra_weight_unchecked( - weight, - DispatchClass::Mandatory, - ); +impl ElectionProviderBase for OnChainExecution { + type AccountId = ::AccountId; + type BlockNumber = ::BlockNumber; + type Error = Error; + type MaxWinners = T::MaxWinners; + type DataProvider = T::DataProvider; +} - Ok(to_supports(&staked)) +impl InstantElectionProvider for OnChainExecution { + fn instant_elect( + forced_input_voters_bound: Option, + forced_input_target_bound: Option, + ) -> Result, Self::Error> { + elect_with_input_bounds::( + Some(T::VotersBound::get().min(forced_input_voters_bound.unwrap_or(u32::MAX)) as usize), + Some(T::TargetsBound::get().min(forced_input_target_bound.unwrap_or(u32::MAX)) as usize), + ) + } +} + +impl ElectionProvider for OnChainExecution { + fn ongoing() -> bool { + false + } + + fn elect() -> Result, Self::Error> { + elect_with_input_bounds::( + Some(T::VotersBound::get() as usize), + Some(T::TargetsBound::get() as usize), + ) } } #[cfg(test)] mod tests { use super::*; + use crate::{ElectionProvider, PhragMMS, SequentialPhragmen}; + use frame_support::{assert_noop, parameter_types, traits::ConstU32}; use sp_npos_elections::Support; use sp_runtime::Perbill; - type AccountId = u64; type BlockNumber = u64; @@ -124,16 +209,16 @@ mod tests { impl frame_system::Config for Runtime { type SS58Prefix = (); type BaseCallFilter = frame_support::traits::Everything; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = AccountId; type BlockNumber = BlockNumber; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = sp_runtime::traits::IdentityLookup; type Header = sp_runtime::testing::Header; - type Event = (); + type RuntimeEvent = (); type BlockHashCount = (); type DbWeight = (); type BlockLength = (); @@ -145,34 +230,62 @@ mod tests { type OnKilledAccount = (); type SystemWeightInfo = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } - impl Config for Runtime { - type Accuracy = Perbill; + struct PhragmenParams; + struct PhragMMSParams; + + parameter_types! { + pub static MaxWinners: u32 = 10; + pub static DesiredTargets: u32 = 2; + } + + impl Config for PhragmenParams { + type System = Runtime; + type Solver = SequentialPhragmen; type DataProvider = mock_data_provider::DataProvider; + type WeightInfo = (); + type MaxWinners = MaxWinners; + type VotersBound = ConstU32<600>; + type TargetsBound = ConstU32<400>; } - type OnChainPhragmen = OnChainSequentialPhragmen; + impl Config for PhragMMSParams { + type System = Runtime; + type Solver = PhragMMS; + type DataProvider = mock_data_provider::DataProvider; + type WeightInfo = (); + type MaxWinners = MaxWinners; + type VotersBound = ConstU32<600>; + type TargetsBound = ConstU32<400>; + } mod mock_data_provider { + use frame_support::{bounded_vec, traits::ConstU32}; + use super::*; - use crate::data_provider; + use crate::{data_provider, VoterOf}; pub struct DataProvider; - impl ElectionDataProvider for DataProvider { - const MAXIMUM_VOTES_PER_VOTER: u32 = 2; - fn voters( - _: Option, - ) -> data_provider::Result)>> { - Ok(vec![(1, 10, vec![10, 20]), (2, 20, vec![30, 20]), (3, 30, vec![10, 30])]) + impl ElectionDataProvider for DataProvider { + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type MaxVotesPerVoter = ConstU32<2>; + fn electing_voters(_: Option) -> data_provider::Result>> { + Ok(vec![ + (1, 10, bounded_vec![10, 20]), + (2, 20, bounded_vec![30, 20]), + (3, 30, bounded_vec![10, 30]), + ]) } - fn targets(_: Option) -> data_provider::Result> { + fn electable_targets(_: Option) -> data_provider::Result> { Ok(vec![10, 20, 30]) } fn desired_targets() -> data_provider::Result { - Ok(2) + Ok(DesiredTargets::get()) } fn next_election_prediction(_: BlockNumber) -> BlockNumber { @@ -185,7 +298,34 @@ mod tests { fn onchain_seq_phragmen_works() { sp_io::TestExternalities::new_empty().execute_with(|| { assert_eq!( - OnChainPhragmen::elect().unwrap(), + as ElectionProvider>::elect().unwrap(), + vec![ + (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), + (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) + ] + ); + }) + } + + #[test] + fn too_many_winners_when_desired_targets_exceed_max_winners() { + sp_io::TestExternalities::new_empty().execute_with(|| { + // given desired targets larger than max winners + DesiredTargets::set(10); + MaxWinners::set(9); + + assert_noop!( + as ElectionProvider>::elect(), + Error::TooManyWinners, + ); + }) + } + + #[test] + fn onchain_phragmms_works() { + sp_io::TestExternalities::new_empty().execute_with(|| { + assert_eq!( + as ElectionProvider>::elect().unwrap(), vec![ (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) diff --git a/frame/election-provider-support/src/tests.rs b/frame/election-provider-support/src/tests.rs new file mode 100644 index 0000000000000..1ccff79f3efd4 --- /dev/null +++ b/frame/election-provider-support/src/tests.rs @@ -0,0 +1,454 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Tests for solution-type. + +#![cfg(test)] + +use crate::{mock::*, IndexAssignment, NposSolution}; +use frame_support::traits::ConstU32; +use rand::SeedableRng; + +mod solution_type { + use super::*; + use codec::{Decode, Encode, MaxEncodedLen}; + // these need to come from the same dev-dependency `frame-election-provider-support`, not from + // the crate. + use crate::{generate_solution_type, Assignment, Error as NposError, NposSolution}; + use sp_std::fmt::Debug; + + #[allow(dead_code)] + mod __private { + // This is just to make sure that the solution can be generated in a scope without any + // imports. + use crate::generate_solution_type; + generate_solution_type!( + #[compact] + struct InnerTestSolutionIsolated::< + VoterIndex = u32, + TargetIndex = u8, + Accuracy = sp_runtime::Percent, + MaxVoters = crate::tests::ConstU32::<20>, + >(12) + ); + } + + #[test] + fn solution_struct_works_with_and_without_compact() { + // we use u32 size to make sure compact is smaller. + let without_compact = { + generate_solution_type!( + pub struct InnerTestSolution::< + VoterIndex = u32, + TargetIndex = u32, + Accuracy = TestAccuracy, + MaxVoters = ConstU32::<20>, + >(16) + ); + let solution = InnerTestSolution { + votes1: vec![(2, 20), (4, 40)], + votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], + ..Default::default() + }; + + solution.encode().len() + }; + + let with_compact = { + generate_solution_type!( + #[compact] + pub struct InnerTestSolutionCompact::< + VoterIndex = u32, + TargetIndex = u32, + Accuracy = TestAccuracy, + MaxVoters = ConstU32::<20>, + >(16) + ); + let compact = InnerTestSolutionCompact { + votes1: vec![(2, 20), (4, 40)], + votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], + ..Default::default() + }; + + compact.encode().len() + }; + + assert!(with_compact < without_compact); + } + + #[test] + fn from_assignment_fail_too_many_voters() { + let rng = rand::rngs::SmallRng::seed_from_u64(0); + + // This will produce 24 voters.. + let (voters, assignments, candidates) = generate_random_votes(10, 25, rng); + let voter_index = make_voter_fn(&voters); + let target_index = make_target_fn(&candidates); + + // Limit the voters to 20.. + generate_solution_type!( + pub struct InnerTestSolution::< + VoterIndex = u32, + TargetIndex = u16, + Accuracy = TestAccuracy, + MaxVoters = frame_support::traits::ConstU32::<20>, + >(16) + ); + + // 24 > 20, so this should fail. + assert_eq!( + InnerTestSolution::from_assignment(&assignments, &voter_index, &target_index) + .unwrap_err(), + NposError::TooManyVoters, + ); + } + + #[test] + fn max_encoded_len_too_small() { + generate_solution_type!( + pub struct InnerTestSolution::< + VoterIndex = u32, + TargetIndex = u32, + Accuracy = TestAccuracy, + MaxVoters = ConstU32::<1>, + >(3) + ); + let solution = InnerTestSolution { + votes1: vec![(2, 20), (4, 40)], + votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], + ..Default::default() + }; + + // We actually have 4 voters, but the bound is 1 voter, so the implemented bound is too + // small. + assert!(solution.encode().len() > InnerTestSolution::max_encoded_len()); + } + + #[test] + fn max_encoded_len_upper_bound() { + generate_solution_type!( + pub struct InnerTestSolution::< + VoterIndex = u32, + TargetIndex = u32, + Accuracy = TestAccuracy, + MaxVoters = ConstU32::<4>, + >(3) + ); + let solution = InnerTestSolution { + votes1: vec![(2, 20), (4, 40)], + votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], + ..Default::default() + }; + + // We actually have 4 voters, and the bound is 4 voters, so the implemented bound should be + // larger than the encoded len. + assert!(solution.encode().len() < InnerTestSolution::max_encoded_len()); + } + + #[test] + fn max_encoded_len_exact() { + generate_solution_type!( + pub struct InnerTestSolution::< + VoterIndex = u32, + TargetIndex = u32, + Accuracy = TestAccuracy, + MaxVoters = ConstU32::<4>, + >(3) + ); + let solution = InnerTestSolution { + votes1: vec![], + votes2: vec![], + votes3: vec![ + (1, [(10, p(50)), (11, p(20))], 12), + (2, [(20, p(50)), (21, p(20))], 22), + (3, [(30, p(50)), (31, p(20))], 32), + (4, [(40, p(50)), (41, p(20))], 42), + ], + }; + + // We have 4 voters, the bound is 4 voters, and all the voters voted for 3 targets, which is + // the max number of targets. This should represent the upper bound that `max_encoded_len` + // represents. + assert_eq!(solution.encode().len(), InnerTestSolution::max_encoded_len()); + } + + #[test] + fn solution_struct_is_codec() { + let solution = TestSolution { + votes1: vec![(2, 20), (4, 40)], + votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], + ..Default::default() + }; + + let encoded = solution.encode(); + + assert_eq!(solution, Decode::decode(&mut &encoded[..]).unwrap()); + assert_eq!(solution.voter_count(), 4); + assert_eq!(solution.edge_count(), 2 + 4); + assert_eq!(solution.unique_targets(), vec![10, 11, 20, 40, 50, 51]); + } + + #[test] + fn remove_voter_works() { + let mut solution = TestSolution { + votes1: vec![(0, 2), (1, 6)], + votes2: vec![(2, [(0, p(80))], 1), (3, [(7, p(85))], 8)], + votes3: vec![(4, [(3, p(50)), (4, p(25))], 5)], + ..Default::default() + }; + + assert!(!solution.remove_voter(11)); + assert!(solution.remove_voter(2)); + assert_eq!( + solution, + TestSolution { + votes1: vec![(0, 2), (1, 6)], + votes2: vec![(3, [(7, p(85))], 8)], + votes3: vec![(4, [(3, p(50)), (4, p(25))], 5,)], + ..Default::default() + }, + ); + + assert!(solution.remove_voter(4)); + assert_eq!( + solution, + TestSolution { + votes1: vec![(0, 2), (1, 6)], + votes2: vec![(3, [(7, p(85))], 8)], + ..Default::default() + }, + ); + + assert!(solution.remove_voter(1)); + assert_eq!( + solution, + TestSolution { + votes1: vec![(0, 2)], + votes2: vec![(3, [(7, p(85))], 8),], + ..Default::default() + }, + ); + } + + #[test] + fn from_and_into_assignment_works() { + let voters = vec![2 as AccountId, 4, 1, 5, 3]; + let targets = vec![ + 10 as AccountId, + 11, + 20, // 2 + 30, + 31, // 4 + 32, + 40, // 6 + 50, + 51, // 8 + ]; + + let assignments = vec![ + Assignment { who: 2 as AccountId, distribution: vec![(20u64, p(100))] }, + Assignment { who: 4, distribution: vec![(40, p(100))] }, + Assignment { who: 1, distribution: vec![(10, p(80)), (11, p(20))] }, + Assignment { who: 5, distribution: vec![(50, p(85)), (51, p(15))] }, + Assignment { who: 3, distribution: vec![(30, p(50)), (31, p(25)), (32, p(25))] }, + ]; + + let voter_index = |a: &AccountId| -> Option { + voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + let target_index = |a: &AccountId| -> Option { + targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + + let solution = + TestSolution::from_assignment(&assignments, voter_index, target_index).unwrap(); + + // basically number of assignments that it is encoding. + assert_eq!(solution.voter_count(), assignments.len()); + assert_eq!( + solution.edge_count(), + assignments.iter().fold(0, |a, b| a + b.distribution.len()), + ); + + assert_eq!( + solution, + TestSolution { + votes1: vec![(0, 2), (1, 6)], + votes2: vec![(2, [(0, p(80))], 1), (3, [(7, p(85))], 8)], + votes3: vec![(4, [(3, p(50)), (4, p(25))], 5)], + ..Default::default() + } + ); + + assert_eq!(solution.unique_targets(), vec![0, 1, 2, 3, 4, 5, 6, 7, 8]); + + let voter_at = |a: u32| -> Option { + voters.get(>::try_into(a).unwrap()).cloned() + }; + let target_at = |a: u16| -> Option { + targets.get(>::try_into(a).unwrap()).cloned() + }; + + assert_eq!(solution.into_assignment(voter_at, target_at).unwrap(), assignments); + } + + #[test] + fn unique_targets_len_edge_count_works() { + // we don't really care about voters here so all duplicates. This is not invalid per se. + let solution = TestSolution { + votes1: vec![(99, 1), (99, 2)], + votes2: vec![(99, [(3, p(10))], 7), (99, [(4, p(10))], 8)], + votes3: vec![(99, [(11, p(10)), (12, p(10))], 13)], + // ensure the last one is also counted. + votes16: vec![( + 99, + [ + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + ], + 67, + )], + ..Default::default() + }; + + assert_eq!(solution.unique_targets(), vec![1, 2, 3, 4, 7, 8, 11, 12, 13, 66, 67]); + assert_eq!(solution.edge_count(), 2 + (2 * 2) + 3 + 16); + assert_eq!(solution.voter_count(), 6); + + // this one has some duplicates. + let solution = TestSolution { + votes1: vec![(99, 1), (99, 1)], + votes2: vec![(99, [(3, p(10))], 7), (99, [(4, p(10))], 8)], + votes3: vec![(99, [(11, p(10)), (11, p(10))], 13)], + ..Default::default() + }; + + assert_eq!(solution.unique_targets(), vec![1, 3, 4, 7, 8, 11, 13]); + assert_eq!(solution.edge_count(), 2 + (2 * 2) + 3); + assert_eq!(solution.voter_count(), 5); + } + + #[test] + fn solution_into_assignment_must_report_overflow() { + // in votes2 + let solution = TestSolution { + votes1: Default::default(), + votes2: vec![(0, [(1, p(100))], 2)], + ..Default::default() + }; + + let voter_at = |a: u32| -> Option { Some(a as AccountId) }; + let target_at = |a: u16| -> Option { Some(a as AccountId) }; + + assert_eq!( + solution.into_assignment(&voter_at, &target_at).unwrap_err(), + NposError::SolutionWeightOverflow, + ); + + // in votes3 onwards + let solution = TestSolution { + votes1: Default::default(), + votes2: Default::default(), + votes3: vec![(0, [(1, p(70)), (2, p(80))], 3)], + ..Default::default() + }; + + assert_eq!( + solution.into_assignment(&voter_at, &target_at).unwrap_err(), + NposError::SolutionWeightOverflow, + ); + } + + #[test] + fn target_count_overflow_is_detected() { + let voter_index = |a: &AccountId| -> Option { Some(*a as u32) }; + let target_index = |a: &AccountId| -> Option { Some(*a as u16) }; + + let assignments = vec![Assignment { + who: 1 as AccountId, + distribution: (10..27).map(|i| (i as AccountId, p(i as u8))).collect::>(), + }]; + + let solution = TestSolution::from_assignment(&assignments, voter_index, target_index); + assert_eq!(solution.unwrap_err(), NposError::SolutionTargetOverflow); + } + + #[test] + fn zero_target_count_is_ignored() { + let voters = vec![1 as AccountId, 2]; + let targets = vec![10 as AccountId, 11]; + + let assignments = vec![ + Assignment { who: 1 as AccountId, distribution: vec![(10, p(50)), (11, p(50))] }, + Assignment { who: 2, distribution: vec![] }, + ]; + + let voter_index = |a: &AccountId| -> Option { + voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + let target_index = |a: &AccountId| -> Option { + targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + + let solution = + TestSolution::from_assignment(&assignments, voter_index, target_index).unwrap(); + + assert_eq!( + solution, + TestSolution { + votes1: Default::default(), + votes2: vec![(0, [(0, p(50))], 1)], + ..Default::default() + } + ); + } +} + +#[test] +fn index_assignments_generate_same_solution_as_plain_assignments() { + let rng = rand::rngs::SmallRng::seed_from_u64(0); + + let (voters, assignments, candidates) = generate_random_votes(1000, 2500, rng); + let voter_index = make_voter_fn(&voters); + let target_index = make_target_fn(&candidates); + + let solution = + TestSolution::from_assignment(&assignments, &voter_index, &target_index).unwrap(); + + let index_assignments = assignments + .into_iter() + .map(|assignment| IndexAssignment::new(&assignment, &voter_index, &target_index)) + .collect::, _>>() + .unwrap(); + + let index_compact = index_assignments.as_slice().try_into().unwrap(); + + assert_eq!(solution, index_compact); +} diff --git a/frame/election-provider-support/src/traits.rs b/frame/election-provider-support/src/traits.rs new file mode 100644 index 0000000000000..ed812e2e0f2c4 --- /dev/null +++ b/frame/election-provider-support/src/traits.rs @@ -0,0 +1,125 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits for the election operations. + +use crate::{Assignment, IdentifierT, IndexAssignmentOf, PerThing128, VoteWeight}; +use codec::Encode; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{Bounded, UniqueSaturatedInto}; +use sp_npos_elections::{ElectionScore, Error, EvaluateSupport}; +use sp_std::{fmt::Debug, prelude::*}; + +/// An opaque index-based, NPoS solution type. +pub trait NposSolution +where + Self: Sized + for<'a> TryFrom<&'a [IndexAssignmentOf], Error = Error>, +{ + /// The maximum number of votes that are allowed. + const LIMIT: usize; + + /// The voter type. Needs to be an index (convert to usize). + type VoterIndex: UniqueSaturatedInto + + TryInto + + TryFrom + + Debug + + Copy + + Clone + + Bounded + + Encode + + TypeInfo; + + /// The target type. Needs to be an index (convert to usize). + type TargetIndex: UniqueSaturatedInto + + TryInto + + TryFrom + + Debug + + Copy + + Clone + + Bounded + + Encode + + TypeInfo; + + /// The weight/accuracy type of each vote. + type Accuracy: PerThing128; + + /// Get the length of all the voters that this type is encoding. + /// + /// This is basically the same as the number of assignments, or number of active voters. + fn voter_count(&self) -> usize; + + /// Get the total count of edges. + /// + /// This is effectively in the range of {[`Self::voter_count`], [`Self::voter_count`] * + /// [`Self::LIMIT`]}. + fn edge_count(&self) -> usize; + + /// Get the number of unique targets in the whole struct. + /// + /// Once presented with a list of winners, this set and the set of winners must be + /// equal. + fn unique_targets(&self) -> Vec; + + /// Get the average edge count. + fn average_edge_count(&self) -> usize { + self.edge_count().checked_div(self.voter_count()).unwrap_or(0) + } + + /// Compute the score of this solution type. + fn score( + self, + stake_of: FS, + voter_at: impl Fn(Self::VoterIndex) -> Option, + target_at: impl Fn(Self::TargetIndex) -> Option, + ) -> Result + where + for<'r> FS: Fn(&'r A) -> VoteWeight, + A: IdentifierT, + { + let ratio = self.into_assignment(voter_at, target_at)?; + let staked = + sp_npos_elections::helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?; + let supports = sp_npos_elections::to_supports(&staked); + Ok(supports.evaluate()) + } + + /// Remove a certain voter. + /// + /// This will only search until the first instance of `to_remove`, and return true. If + /// no instance is found (no-op), then it returns false. + /// + /// In other words, if this return true, exactly **one** element must have been removed self. + fn remove_voter(&mut self, to_remove: Self::VoterIndex) -> bool; + + /// Build self from a list of assignments. + fn from_assignment( + assignments: &[Assignment], + voter_index: FV, + target_index: FT, + ) -> Result + where + A: IdentifierT, + for<'r> FV: Fn(&'r A) -> Option, + for<'r> FT: Fn(&'r A) -> Option; + + /// Convert self into a `Vec>` + fn into_assignment( + self, + voter_at: impl Fn(Self::VoterIndex) -> Option, + target_at: impl Fn(Self::TargetIndex) -> Option, + ) -> Result>, Error>; +} diff --git a/frame/election-provider-support/src/weights.rs b/frame/election-provider-support/src/weights.rs new file mode 100644 index 0000000000000..44075ba871228 --- /dev/null +++ b/frame/election-provider-support/src/weights.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_election_provider_support_benchmarking +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-04-23, STEPS: `1`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/substrate +// benchmark +// pallet +// --chain=dev +// --steps=1 +// --repeat=1 +// --pallet=pallet_election_provider_support_benchmarking +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=frame/election-provider-support/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_election_provider_support_benchmarking. +pub trait WeightInfo { + fn phragmen(v: u32, t: u32, d: u32, ) -> Weight; + fn phragmms(v: u32, t: u32, d: u32, ) -> Weight; +} + +/// Weights for pallet_election_provider_support_benchmarking using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn phragmen(v: u32, t: u32, d: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 667_000 + .saturating_add(Weight::from_ref_time(32_973_000 as u64).saturating_mul(v as u64)) + // Standard Error: 1_334_000 + .saturating_add(Weight::from_ref_time(1_334_000 as u64).saturating_mul(t as u64)) + // Standard Error: 60_644_000 + .saturating_add(Weight::from_ref_time(2_636_364_000 as u64).saturating_mul(d as u64)) + } + fn phragmms(v: u32, t: u32, d: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 73_000 + .saturating_add(Weight::from_ref_time(21_073_000 as u64).saturating_mul(v as u64)) + // Standard Error: 146_000 + .saturating_add(Weight::from_ref_time(65_000 as u64).saturating_mul(t as u64)) + // Standard Error: 6_649_000 + .saturating_add(Weight::from_ref_time(1_711_424_000 as u64).saturating_mul(d as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn phragmen(v: u32, t: u32, d: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 667_000 + .saturating_add(Weight::from_ref_time(32_973_000 as u64).saturating_mul(v as u64)) + // Standard Error: 1_334_000 + .saturating_add(Weight::from_ref_time(1_334_000 as u64).saturating_mul(t as u64)) + // Standard Error: 60_644_000 + .saturating_add(Weight::from_ref_time(2_636_364_000 as u64).saturating_mul(d as u64)) + } + fn phragmms(v: u32, t: u32, d: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 73_000 + .saturating_add(Weight::from_ref_time(21_073_000 as u64).saturating_mul(v as u64)) + // Standard Error: 146_000 + .saturating_add(Weight::from_ref_time(65_000 as u64).saturating_mul(t as u64)) + // Standard Error: 6_649_000 + .saturating_add(Weight::from_ref_time(1_711_424_000 as u64).saturating_mul(d as u64)) + } +} diff --git a/frame/elections-phragmen/Cargo.toml b/frame/elections-phragmen/Cargo.toml index f2771a9f72783..fb1d924dbd1bd 100644 --- a/frame/elections-phragmen/Cargo.toml +++ b/frame/elections-phragmen/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-elections-phragmen" version = "5.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet based on seq-Phragmén election method." readme = "README.md" @@ -13,41 +13,43 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } +log = { version = "0.4.14", default-features = false } +scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.14", default-features = false } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-tracing = { path = "../../primitives/tracing" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "codec/std", - "scale-info/std", "frame-support/std", - "sp-runtime/std", - "sp-npos-elections/std", "frame-system/std", - "sp-std/std", - "sp-io/std", - "sp-core/std", "log/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-npos-elections/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 7cb83b3dd7799..06ac8d7c60162 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,24 +21,20 @@ use super::*; -use frame_benchmarking::{ - account, benchmarks, impl_benchmark_test_suite, whitelist, BenchmarkError, BenchmarkResult, -}; +use frame_benchmarking::{account, benchmarks, whitelist, BenchmarkError, BenchmarkResult}; use frame_support::{dispatch::DispatchResultWithPostInfo, traits::OnInitialize}; use frame_system::RawOrigin; use crate::Pallet as Elections; const BALANCE_FACTOR: u32 = 250; -const MAX_VOTERS: u32 = 500; -const MAX_CANDIDATES: u32 = 200; - -type Lookup = <::Lookup as StaticLookup>::Source; /// grab new account with infinite balance. fn endowed_account(name: &'static str, index: u32) -> T::AccountId { let account: T::AccountId = account(name, index, 0); - let amount = default_stake::(BALANCE_FACTOR); + // Fund each account with at-least his stake but still a sane amount as to not mess up + // the vote calculation. + let amount = default_stake::(T::MaxVoters::get()) * BalanceOf::::from(BALANCE_FACTOR); let _ = T::Currency::make_free_balance_be(&account, amount); // important to increase the total issuance since T::CurrencyToVote will need it to be sane for // phragmen to work. @@ -48,14 +44,14 @@ fn endowed_account(name: &'static str, index: u32) -> T::AccountId { } /// Account to lookup type of system trait. -fn as_lookup(account: T::AccountId) -> Lookup { +fn as_lookup(account: T::AccountId) -> AccountIdLookupOf { T::Lookup::unlookup(account) } /// Get a reasonable amount of stake based on the execution trait's configuration -fn default_stake(factor: u32) -> BalanceOf { - let factor = BalanceOf::::from(factor); - T::Currency::minimum_balance() * factor +fn default_stake(num_votes: u32) -> BalanceOf { + let min = T::Currency::minimum_balance(); + Elections::::deposit_of(num_votes as usize).max(min) } /// Get the current number of candidates. @@ -87,11 +83,10 @@ fn submit_candidates_with_self_vote( prefix: &'static str, ) -> Result, &'static str> { let candidates = submit_candidates::(c, prefix)?; - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(c); let _ = candidates .iter() - .map(|c| submit_voter::(c.clone(), vec![c.clone()], stake).map(|_| ())) - .collect::>()?; + .try_for_each(|c| submit_voter::(c.clone(), vec![c.clone()], stake).map(|_| ()))?; Ok(candidates) } @@ -111,7 +106,7 @@ fn distribute_voters( num_voters: u32, votes: usize, ) -> Result<(), &'static str> { - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(num_voters); for i in 0..num_voters { // to ensure that votes are different all_candidates.rotate_left(1); @@ -146,6 +141,7 @@ fn clean() { >::kill(); >::kill(); >::kill(); + #[allow(deprecated)] >::remove_all(None); } @@ -159,7 +155,7 @@ benchmarks! { let all_candidates = submit_candidates::(v, "candidates")?; let caller = endowed_account::("caller", 0); - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(v); // original votes. let mut votes = all_candidates; @@ -172,14 +168,15 @@ benchmarks! { }: vote(RawOrigin::Signed(caller), votes, stake) vote_more { - let v in 2 .. (MAXIMUM_VOTE as u32); + let v in 2 .. (MAXIMUM_VOTE as u32); clean::(); // create a bunch of candidates. let all_candidates = submit_candidates::(v, "candidates")?; let caller = endowed_account::("caller", 0); - let stake = default_stake::(BALANCE_FACTOR); + // Multiply the stake with 10 since we want to be able to divide it by 10 again. + let stake = default_stake::(v) * BalanceOf::::from(10u32); // original votes. let mut votes = all_candidates.iter().skip(1).cloned().collect::>(); @@ -193,14 +190,14 @@ benchmarks! { }: vote(RawOrigin::Signed(caller), votes, stake / >::from(10u32)) vote_less { - let v in 2 .. (MAXIMUM_VOTE as u32); + let v in 2 .. (MAXIMUM_VOTE as u32); clean::(); // create a bunch of candidates. let all_candidates = submit_candidates::(v, "candidates")?; let caller = endowed_account::("caller", 0); - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(v); // original votes. let mut votes = all_candidates; @@ -223,7 +220,7 @@ benchmarks! { let caller = endowed_account::("caller", 0); - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(v); submit_voter::(caller.clone(), all_candidates, stake)?; whitelist!(caller); @@ -231,13 +228,13 @@ benchmarks! { submit_candidacy { // number of already existing candidates. - let c in 1 .. MAX_CANDIDATES; + let c in 1 .. T::MaxCandidates::get(); // we fix the number of members to the number of desired members and runners-up. We'll be in // this state almost always. let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); clean::(); - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(c); // create m members and runners combined. let _ = fill_seats_up_to::(m)?; @@ -262,7 +259,7 @@ benchmarks! { // this will check members, runners-up and candidate for removal. Members and runners-up are // limited by the runtime bound, nonetheless we fill them by `m`. // number of already existing candidates. - let c in 1 .. MAX_CANDIDATES; + let c in 1 .. T::MaxCandidates::get(); // we fix the number of members to the number of desired members and runners-up. We'll be in // this state almost always. let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); @@ -341,38 +338,6 @@ benchmarks! { ))?; } - // -- Root ones - #[extra] // this calls into phragmen and consumes a full block for now. - remove_member_without_replacement_extra { - // worse case is when we remove a member and we have no runner as a replacement. This - // triggers phragmen again. The only parameter is how many candidates will compete for the - // new slot. - let c in 1 .. MAX_CANDIDATES; - clean::(); - - // fill only desired members. no runners-up. - let all_members = fill_seats_up_to::(T::DesiredMembers::get())?; - assert_eq!(>::members().len() as u32, T::DesiredMembers::get()); - - // submit a new one to compensate, with self-vote. - let replacements = submit_candidates_with_self_vote::(c, "new_candidate")?; - - // create some voters for these replacements. - distribute_voters::(replacements, MAX_VOTERS, MAXIMUM_VOTE)?; - - let to_remove = as_lookup::(all_members[0].clone()); - }: remove_member(RawOrigin::Root, to_remove, false) - verify { - // must still have the desired number of members members. - assert_eq!(>::members().len() as u32, T::DesiredMembers::get()); - #[cfg(test)] - { - // reset members in between benchmark tests. - use crate::tests::MEMBERS; - MEMBERS.with(|m| *m.borrow_mut() = vec![]); - } - } - remove_member_with_replacement { // easy case. We have a runner up. Nothing will have that much of an impact. m will be // number of members and runners. There is always at least one runner. @@ -381,32 +346,7 @@ benchmarks! { let _ = fill_seats_up_to::(m)?; let removing = as_lookup::(>::members_ids()[0].clone()); - }: remove_member(RawOrigin::Root, removing, true) - verify { - // must still have enough members. - assert_eq!(>::members().len() as u32, T::DesiredMembers::get()); - #[cfg(test)] - { - // reset members in between benchmark tests. - use crate::tests::MEMBERS; - MEMBERS.with(|m| *m.borrow_mut() = vec![]); - } - } - - remove_member_wrong_refund { - // The root call by mistake indicated that this will have no replacement, while it has! - // this has now consumed a lot of weight and need to refund. - let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); - clean::(); - - let _ = fill_seats_up_to::(m)?; - let removing = as_lookup::(>::members_ids()[0].clone()); - }: { - assert_eq!( - >::remove_member(RawOrigin::Root.into(), removing, false).unwrap_err().error, - Error::::InvalidReplacement.into(), - ); - } + }: remove_member(RawOrigin::Root, removing, true, false) verify { // must still have enough members. assert_eq!(>::members().len() as u32, T::DesiredMembers::get()); @@ -420,14 +360,14 @@ benchmarks! { clean_defunct_voters { // total number of voters. - let v in (MAX_VOTERS / 2) .. MAX_VOTERS; + let v in (T::MaxVoters::get() / 2) .. T::MaxVoters::get(); // those that are defunct and need removal. - let d in 1 .. (MAX_VOTERS / 2); + let d in 0 .. (T::MaxVoters::get() / 2); // remove any previous stuff. clean::(); - let all_candidates = submit_candidates::(v, "candidates")?; + let all_candidates = submit_candidates::(T::MaxCandidates::get(), "candidates")?; distribute_voters::(all_candidates, v, MAXIMUM_VOTE)?; // all candidates leave. @@ -447,9 +387,9 @@ benchmarks! { // members, this is hard-coded in the runtime and cannot be trivially changed at this stage. // Yet, change the number of voters, candidates and edge per voter to see the impact. Note // that we give all candidates a self vote to make sure they are all considered. - let c in 1 .. MAX_CANDIDATES; - let v in 1 .. MAX_VOTERS; - let e in MAX_VOTERS .. MAX_VOTERS * MAXIMUM_VOTE as u32; + let c in 1 .. T::MaxCandidates::get(); + let v in 1 .. T::MaxVoters::get(); + let e in (T::MaxVoters::get()) .. T::MaxVoters::get() as u32 * MAXIMUM_VOTE as u32; clean::(); // so we have a situation with v and e. we want e to basically always be in the range of `e @@ -462,7 +402,7 @@ benchmarks! { let votes_per_voter = (e / v).min(MAXIMUM_VOTE as u32); let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; - let _ = distribute_voters::(all_candidates, v, votes_per_voter as usize)?; + let _ = distribute_voters::(all_candidates, v.saturating_sub(c), votes_per_voter as usize)?; }: { >::on_initialize(T::TermDuration::get()); } @@ -483,15 +423,19 @@ benchmarks! { #[extra] election_phragmen_c_e { - let c in 1 .. MAX_CANDIDATES; - let e in MAX_VOTERS .. MAX_VOTERS * MAXIMUM_VOTE as u32; - let fixed_v = MAX_VOTERS; + let c in 1 .. T::MaxCandidates::get(); + let e in (T::MaxVoters::get()) .. T::MaxVoters::get() * MAXIMUM_VOTE as u32; + let fixed_v = T::MaxVoters::get(); clean::(); let votes_per_voter = e / fixed_v; let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; - let _ = distribute_voters::(all_candidates, fixed_v, votes_per_voter as usize)?; + let _ = distribute_voters::( + all_candidates, + fixed_v - c, + votes_per_voter as usize, + )?; }: { >::on_initialize(T::TermDuration::get()); } @@ -513,7 +457,7 @@ benchmarks! { #[extra] election_phragmen_v { let v in 4 .. 16; - let fixed_c = MAX_CANDIDATES; + let fixed_c = T::MaxCandidates::get() / 10; let fixed_e = 64; clean::(); @@ -538,11 +482,11 @@ benchmarks! { MEMBERS.with(|m| *m.borrow_mut() = vec![]); } } -} -impl_benchmark_test_suite!( - Elections, - crate::tests::ExtBuilder::default().desired_members(13).desired_runners_up(7), - crate::tests::Test, - exec_name = build_and_execute, -); + impl_benchmark_test_suite!( + Elections, + crate::tests::ExtBuilder::default().desired_members(13).desired_runners_up(7), + crate::tests::Test, + exec_name = build_and_execute, + ); +} diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index d7b42383da757..1cfdc25fd9b47 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -100,11 +100,10 @@ use codec::{Decode, Encode}; use frame_support::{ - dispatch::WithPostDispatchInfo, traits::{ - ChangeMembers, Contains, ContainsLengthBound, Currency, CurrencyToVote, Get, - InitializeMembers, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, - SortedMembers, StorageVersion, WithdrawReasons, + defensive_prelude::*, ChangeMembers, Contains, ContainsLengthBound, Currency, + CurrencyToVote, Get, InitializeMembers, LockIdentifier, LockableCurrency, OnUnbalanced, + ReservableCurrency, SortedMembers, WithdrawReasons, }, weights::Weight, }; @@ -123,9 +122,6 @@ pub use weights::WeightInfo; /// All migrations. pub mod migrations; -/// The current storage version. -const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); - /// The maximum votes allowed per voter. pub const MAXIMUM_VOTE: usize = 16; @@ -147,7 +143,7 @@ pub enum Renouncing { } /// An active voter. -#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, TypeInfo)] +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, TypeInfo)] pub struct Voter { /// The members being backed. pub votes: Vec, @@ -159,6 +155,12 @@ pub struct Voter { pub deposit: Balance, } +impl Default for Voter { + fn default() -> Self { + Self { votes: vec![], stake: Default::default(), deposit: Default::default() } + } +} + /// A holder of a seat as either a member or a runner-up. #[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, TypeInfo)] pub struct SeatHolder { @@ -174,15 +176,26 @@ pub struct SeatHolder { pub use pallet::*; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + #[pallet::config] pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Identifier for the elections-phragmen pallet's lock #[pallet::constant] @@ -237,15 +250,25 @@ pub mod pallet { #[pallet::constant] type TermDuration: Get; + /// The maximum number of candidates in a phragmen election. + /// + /// Warning: The election happens onchain, and this value will determine + /// the size of the election. When this limit is reached no more + /// candidates are accepted in the election. + #[pallet::constant] + type MaxCandidates: Get; + + /// The maximum number of voters to allow in a phragmen election. + /// + /// Warning: This impacts the size of the election which is run onchain. + /// When the limit is reached the new voters are ignored. + #[pallet::constant] + type MaxVoters: Get; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - #[pallet::storage_version(STORAGE_VERSION)] - pub struct Pallet(PhantomData); - #[pallet::hooks] impl Hooks> for Pallet { /// What to do at the end of each block. @@ -256,7 +279,7 @@ pub mod pallet { if !term_duration.is_zero() && (n % term_duration).is_zero() { Self::do_phragmen() } else { - 0 + Weight::zero() } } } @@ -274,7 +297,7 @@ pub mod pallet { /// - be less than the number of possible candidates. Note that all current members and /// runners-up are also automatically candidates for the next round. /// - /// If `value` is more than `who`'s total balance, then the maximum of the two is used. + /// If `value` is more than `who`'s free balance, then the maximum of the two is used. /// /// The dispatch origin of this call must be signed. /// @@ -286,6 +309,7 @@ pub mod pallet { /// # /// We assume the maximum weight among all 3 cases: vote_equal, vote_more and vote_less. /// # + #[pallet::call_index(0)] #[pallet::weight( T::WeightInfo::vote_more(votes.len() as u32) .max(T::WeightInfo::vote_less(votes.len() as u32)) @@ -336,11 +360,11 @@ pub mod pallet { }; // Amount to be locked up. - let locked_stake = value.min(T::Currency::total_balance(&who)); + let locked_stake = value.min(T::Currency::free_balance(&who)); T::Currency::set_lock(T::PalletId::get(), &who, locked_stake, WithdrawReasons::all()); Voting::::insert(&who, Voter { votes, deposit: new_deposit, stake: locked_stake }); - Ok(None.into()) + Ok(None::.into()) } /// Remove `origin` as a voter. @@ -348,12 +372,13 @@ pub mod pallet { /// This removes the lock and returns the deposit. /// /// The dispatch origin of this call must be signed and be a voter. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::remove_voter())] - pub fn remove_voter(origin: OriginFor) -> DispatchResultWithPostInfo { + pub fn remove_voter(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; ensure!(Self::is_voter(&who), Error::::MustBeVoter); Self::do_remove_voter(&who); - Ok(None.into()) + Ok(()) } /// Submit oneself for candidacy. A fixed amount of deposit is recorded. @@ -371,15 +396,20 @@ pub mod pallet { /// # /// The number of current candidates must be provided as witness data. /// # + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::submit_candidacy(*candidate_count))] pub fn submit_candidacy( origin: OriginFor, #[pallet::compact] candidate_count: u32, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let who = ensure_signed(origin)?; - let actual_count = >::decode_len().unwrap_or(0); - ensure!(actual_count as u32 <= candidate_count, Error::::InvalidWitnessData); + let actual_count = >::decode_len().unwrap_or(0) as u32; + ensure!(actual_count <= candidate_count, Error::::InvalidWitnessData); + ensure!( + actual_count <= ::MaxCandidates::get(), + Error::::TooManyCandidates + ); let index = Self::is_candidate(&who).err().ok_or(Error::::DuplicatedCandidate)?; @@ -390,7 +420,7 @@ pub mod pallet { .map_err(|_| Error::::InsufficientCandidateFunds)?; >::mutate(|c| c.insert(index, (who, T::CandidacyBond::get()))); - Ok(None.into()) + Ok(()) } /// Renounce one's intention to be a candidate for the next election round. 3 potential @@ -411,21 +441,19 @@ pub mod pallet { /// # /// The type of renouncing must be provided as witness data. /// # + #[pallet::call_index(3)] #[pallet::weight(match *renouncing { Renouncing::Candidate(count) => T::WeightInfo::renounce_candidacy_candidate(count), Renouncing::Member => T::WeightInfo::renounce_candidacy_members(), Renouncing::RunnerUp => T::WeightInfo::renounce_candidacy_runners_up(), })] - pub fn renounce_candidacy( - origin: OriginFor, - renouncing: Renouncing, - ) -> DispatchResultWithPostInfo { + pub fn renounce_candidacy(origin: OriginFor, renouncing: Renouncing) -> DispatchResult { let who = ensure_signed(origin)?; match renouncing { Renouncing::Member => { let _ = Self::remove_and_replace_member(&who, false) .map_err(|_| Error::::InvalidRenouncing)?; - Self::deposit_event(Event::Renounced(who)); + Self::deposit_event(Event::Renounced { candidate: who }); }, Renouncing::RunnerUp => { >::try_mutate::<_, Error, _>(|runners_up| { @@ -437,7 +465,7 @@ pub mod pallet { let SeatHolder { deposit, .. } = runners_up.remove(index); let _remainder = T::Currency::unreserve(&who, deposit); debug_assert!(_remainder.is_zero()); - Self::deposit_event(Event::Renounced(who)); + Self::deposit_event(Event::Renounced { candidate: who }); Ok(()) })?; }, @@ -450,19 +478,23 @@ pub mod pallet { let (_removed, deposit) = candidates.remove(index); let _remainder = T::Currency::unreserve(&who, deposit); debug_assert!(_remainder.is_zero()); - Self::deposit_event(Event::Renounced(who)); + Self::deposit_event(Event::Renounced { candidate: who }); Ok(()) })?; }, }; - Ok(None.into()) + Ok(()) } /// Remove a particular member from the set. This is effective immediately and the bond of /// the outgoing member is slashed. /// /// If a runner-up is available, then the best runner-up will be removed and replaces the - /// outgoing member. Otherwise, a new phragmen election is started. + /// outgoing member. Otherwise, if `rerun_election` is `true`, a new phragmen election is + /// started, else, nothing happens. + /// + /// If `slash_bond` is set to true, the bond of the member being removed is slashed. Else, + /// it is returned. /// /// The dispatch origin of this call must be root. /// @@ -472,38 +504,30 @@ pub mod pallet { /// If we have a replacement, we use a small weight. Else, since this is a root call and /// will go into phragmen, we assume full block for now. /// # - #[pallet::weight(if *has_replacement { - T::WeightInfo::remove_member_with_replacement() - } else { + #[pallet::call_index(4)] + #[pallet::weight(if *rerun_election { T::WeightInfo::remove_member_without_replacement() + } else { + T::WeightInfo::remove_member_with_replacement() })] pub fn remove_member( origin: OriginFor, - who: ::Source, - has_replacement: bool, - ) -> DispatchResultWithPostInfo { + who: AccountIdLookupOf, + slash_bond: bool, + rerun_election: bool, + ) -> DispatchResult { ensure_root(origin)?; let who = T::Lookup::lookup(who)?; - let will_have_replacement = >::decode_len().map_or(false, |l| l > 0); - if will_have_replacement != has_replacement { - // In both cases, we will change more weight than need. Refund and abort. - return Err(Error::::InvalidReplacement.with_weight( - // refund. The weight value comes from a benchmark which is special to this. - T::WeightInfo::remove_member_wrong_refund(), - )) - } + let _ = Self::remove_and_replace_member(&who, slash_bond)?; + Self::deposit_event(Event::MemberKicked { member: who }); - let had_replacement = Self::remove_and_replace_member(&who, true)?; - debug_assert_eq!(has_replacement, had_replacement); - Self::deposit_event(Event::MemberKicked(who.clone())); - - if !had_replacement { + if rerun_election { Self::do_phragmen(); } // no refund needed. - Ok(None.into()) + Ok(()) } /// Clean all voters who are defunct (i.e. they do not serve any purpose at all). The @@ -516,52 +540,53 @@ pub mod pallet { /// # /// The total number of voters and those that are defunct must be provided as witness data. /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::clean_defunct_voters(*_num_voters, *_num_defunct))] pub fn clean_defunct_voters( origin: OriginFor, _num_voters: u32, _num_defunct: u32, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let _ = ensure_root(origin)?; >::iter() .filter(|(_, x)| Self::is_defunct_voter(&x.votes)) .for_each(|(dv, _)| Self::do_remove_voter(&dv)); - Ok(None.into()) + Ok(()) } } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// A new term with \[new_members\]. This indicates that enough candidates existed to run + /// A new term with new_members. This indicates that enough candidates existed to run /// the election, not that enough have has been elected. The inner value must be examined /// for this purpose. A `NewTerm(\[\])` indicates that some candidates got their bond /// slashed and none were elected, whilst `EmptyTerm` means that no candidates existed to /// begin with. - NewTerm(Vec<(::AccountId, BalanceOf)>), + NewTerm { new_members: Vec<(::AccountId, BalanceOf)> }, /// No (or not enough) candidates existed for this round. This is different from /// `NewTerm(\[\])`. See the description of `NewTerm`. EmptyTerm, /// Internal error happened while trying to perform election. ElectionError, - /// A \[member\] has been removed. This should always be followed by either `NewTerm` or + /// A member has been removed. This should always be followed by either `NewTerm` or /// `EmptyTerm`. - MemberKicked(::AccountId), + MemberKicked { member: ::AccountId }, /// Someone has renounced their candidacy. - Renounced(::AccountId), - /// A \[candidate\] was slashed by \[amount\] due to failing to obtain a seat as member or + Renounced { candidate: ::AccountId }, + /// A candidate was slashed by amount due to failing to obtain a seat as member or /// runner-up. /// /// Note that old members and runners-up are also candidates. - CandidateSlashed(::AccountId, BalanceOf), - /// A \[seat holder\] was slashed by \[amount\] by being forcefully removed from the set. - SeatHolderSlashed(::AccountId, BalanceOf), + CandidateSlashed { candidate: ::AccountId, amount: BalanceOf }, + /// A seat holder was slashed by amount by being forcefully removed from the set. + SeatHolderSlashed { + seat_holder: ::AccountId, + amount: BalanceOf, + }, } - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// Cannot vote when no candidates or members exist. @@ -578,10 +603,10 @@ pub mod pallet { UnableToPayBond, /// Must be a voter. MustBeVoter, - /// Cannot report self. - ReportSelf, /// Duplicated candidate submission. DuplicatedCandidate, + /// Too many candidates have been created. + TooManyCandidates, /// Member cannot re-submit candidacy. MemberSubmit, /// Runner cannot re-submit candidacy. @@ -748,7 +773,10 @@ impl Pallet { let (imbalance, _remainder) = T::Currency::slash_reserved(who, removed.deposit); debug_assert!(_remainder.is_zero()); T::LoserCandidate::on_unbalanced(imbalance); - Self::deposit_event(Event::SeatHolderSlashed(who.clone(), removed.deposit)); + Self::deposit_event(Event::SeatHolderSlashed { + seat_holder: who.clone(), + amount: removed.deposit, + }); } else { T::Currency::unreserve(who, removed.deposit); } @@ -772,7 +800,7 @@ impl Pallet { })?; let remaining_member_ids_sorted = - Self::members().into_iter().map(|x| x.who.clone()).collect::>(); + Self::members().into_iter().map(|x| x.who).collect::>(); let outgoing = &[who.clone()]; let maybe_current_prime = T::ChangeMembers::get_prime(); let return_value = match maybe_replacement { @@ -824,7 +852,7 @@ impl Pallet { /// Check if `who` is currently an active runner-up. fn is_runner_up(who: &T::AccountId) -> bool { - Self::runners_up().iter().position(|r| &r.who == who).is_some() + Self::runners_up().iter().any(|r| &r.who == who) } /// Get the members' account ids. @@ -851,7 +879,7 @@ impl Pallet { /// Reads Members, RunnersUp, Candidates and Voting(who) from database. fn is_defunct_voter(votes: &[T::AccountId]) -> bool { votes.iter().all(|v| { - !Self::is_member(v) && !Self::is_runner_up(v) && !Self::is_candidate(v).is_ok() + !Self::is_member(v) && !Self::is_runner_up(v) && Self::is_candidate(v).is_err() }) } @@ -883,7 +911,7 @@ impl Pallet { if candidates_and_deposit.len().is_zero() { Self::deposit_event(Event::EmptyTerm); - return T::DbWeight::get().reads(5) + return T::DbWeight::get().reads(3) } // All of the new winners that come out of phragmen will thus have a deposit recorded. @@ -896,10 +924,29 @@ impl Pallet { let to_balance = |e: ExtendedBalance| T::CurrencyToVote::to_currency(e, total_issuance); let mut num_edges: u32 = 0; + + let max_voters = ::MaxVoters::get() as usize; // used for prime election. - let voters_and_stakes = Voting::::iter() - .map(|(voter, Voter { stake, votes, .. })| (voter, stake, votes)) - .collect::>(); + let mut voters_and_stakes = Vec::new(); + match Voting::::iter().try_for_each(|(voter, Voter { stake, votes, .. })| { + if voters_and_stakes.len() < max_voters { + voters_and_stakes.push((voter, stake, votes)); + Ok(()) + } else { + Err(()) + } + }) { + Ok(_) => (), + Err(_) => { + log::error!( + target: "runtime::elections-phragmen", + "Failed to run election. Number of voters exceeded", + ); + Self::deposit_event(Event::ElectionError); + return T::DbWeight::get().reads(3 + max_voters as u64) + }, + } + // used for phragmen. let voters_and_votes = voters_and_stakes .iter() @@ -913,145 +960,156 @@ impl Pallet { let weight_candidates = candidates_and_deposit.len() as u32; let weight_voters = voters_and_votes.len() as u32; let weight_edges = num_edges; - let _ = sp_npos_elections::seq_phragmen::( - num_to_elect, - candidate_ids, - voters_and_votes.clone(), - None, - ) - .map(|ElectionResult { winners, assignments: _ }| { - // this is already sorted by id. - let old_members_ids_sorted = - >::take().into_iter().map(|m| m.who).collect::>(); - // this one needs a sort by id. - let mut old_runners_up_ids_sorted = - >::take().into_iter().map(|r| r.who).collect::>(); - old_runners_up_ids_sorted.sort(); - - // filter out those who end up with no backing stake. - let mut new_set_with_stake = winners - .into_iter() - .filter_map(|(m, b)| if b.is_zero() { None } else { Some((m, to_balance(b))) }) - .collect::)>>(); - - // OPTIMIZATION NOTE: we could bail out here if `new_set.len() == 0`. There isn't - // much left to do. Yet, re-arranging the code would require duplicating the - // slashing of exposed candidates, cleaning any previous members, and so on. For - // now, in favor of readability and veracity, we keep it simple. - - // split new set into winners and runners up. - let split_point = desired_seats.min(new_set_with_stake.len()); - let mut new_members_sorted_by_id = - new_set_with_stake.drain(..split_point).collect::>(); - new_members_sorted_by_id.sort_by(|i, j| i.0.cmp(&j.0)); - - // all the rest will be runners-up - new_set_with_stake.reverse(); - let new_runners_up_sorted_by_rank = new_set_with_stake; - let mut new_runners_up_ids_sorted = - new_runners_up_sorted_by_rank.iter().map(|(r, _)| r.clone()).collect::>(); - new_runners_up_ids_sorted.sort(); - - // Now we select a prime member using a [Borda - // count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for - // that new member by a multiplier based on the order of the votes. i.e. the first - // person a voter votes for gets a 16x multiplier, the next person gets a 15x - // multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16) - let mut prime_votes = new_members_sorted_by_id - .iter() - .map(|c| (&c.0, BalanceOf::::zero())) - .collect::>(); - for (_, stake, votes) in voters_and_stakes.into_iter() { - for (vote_multiplier, who) in votes - .iter() - .enumerate() - .map(|(vote_position, who)| ((MAXIMUM_VOTE - vote_position) as u32, who)) - { - if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) { - prime_votes[i].1 = prime_votes[i] - .1 - .saturating_add(stake.saturating_mul(vote_multiplier.into())); + let _ = + sp_npos_elections::seq_phragmen(num_to_elect, candidate_ids, voters_and_votes, None) + .map(|ElectionResult:: { winners, assignments: _ }| { + // this is already sorted by id. + let old_members_ids_sorted = >::take() + .into_iter() + .map(|m| m.who) + .collect::>(); + // this one needs a sort by id. + let mut old_runners_up_ids_sorted = >::take() + .into_iter() + .map(|r| r.who) + .collect::>(); + old_runners_up_ids_sorted.sort(); + + // filter out those who end up with no backing stake. + let mut new_set_with_stake = winners + .into_iter() + .filter_map( + |(m, b)| if b.is_zero() { None } else { Some((m, to_balance(b))) }, + ) + .collect::)>>(); + + // OPTIMIZATION NOTE: we could bail out here if `new_set.len() == 0`. There + // isn't much left to do. Yet, re-arranging the code would require duplicating + // the slashing of exposed candidates, cleaning any previous members, and so on. + // For now, in favor of readability and veracity, we keep it simple. + + // split new set into winners and runners up. + let split_point = desired_seats.min(new_set_with_stake.len()); + let mut new_members_sorted_by_id = + new_set_with_stake.drain(..split_point).collect::>(); + new_members_sorted_by_id.sort_by(|i, j| i.0.cmp(&j.0)); + + // all the rest will be runners-up + new_set_with_stake.reverse(); + let new_runners_up_sorted_by_rank = new_set_with_stake; + let mut new_runners_up_ids_sorted = new_runners_up_sorted_by_rank + .iter() + .map(|(r, _)| r.clone()) + .collect::>(); + new_runners_up_ids_sorted.sort(); + + // Now we select a prime member using a [Borda + // count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for + // that new member by a multiplier based on the order of the votes. i.e. the + // first person a voter votes for gets a 16x multiplier, the next person gets a + // 15x multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16) + let mut prime_votes = new_members_sorted_by_id + .iter() + .map(|c| (&c.0, BalanceOf::::zero())) + .collect::>(); + for (_, stake, votes) in voters_and_stakes.into_iter() { + for (vote_multiplier, who) in + votes.iter().enumerate().map(|(vote_position, who)| { + ((MAXIMUM_VOTE - vote_position) as u32, who) + }) { + if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) { + prime_votes[i].1 = prime_votes[i] + .1 + .saturating_add(stake.saturating_mul(vote_multiplier.into())); + } + } } - } - } - // We then select the new member with the highest weighted stake. In the case of a tie, - // the last person in the list with the tied score is selected. This is the person with - // the "highest" account id based on the sort above. - let prime = prime_votes.into_iter().max_by_key(|x| x.1).map(|x| x.0.clone()); - - // new_members_sorted_by_id is sorted by account id. - let new_members_ids_sorted = new_members_sorted_by_id - .iter() - .map(|(m, _)| m.clone()) - .collect::>(); - - // report member changes. We compute diff because we need the outgoing list. - let (incoming, outgoing) = T::ChangeMembers::compute_members_diff_sorted( - &new_members_ids_sorted, - &old_members_ids_sorted, - ); - T::ChangeMembers::change_members_sorted(&incoming, &outgoing, &new_members_ids_sorted); - T::ChangeMembers::set_prime(prime); - - // All candidates/members/runners-up who are no longer retaining a position as a - // seat holder will lose their bond. - candidates_and_deposit.iter().for_each(|(c, d)| { - if new_members_ids_sorted.binary_search(c).is_err() && - new_runners_up_ids_sorted.binary_search(c).is_err() - { - let (imbalance, _) = T::Currency::slash_reserved(c, *d); - T::LoserCandidate::on_unbalanced(imbalance); - Self::deposit_event(Event::CandidateSlashed(c.clone(), *d)); - } - }); + // We then select the new member with the highest weighted stake. In the case of + // a tie, the last person in the list with the tied score is selected. This is + // the person with the "highest" account id based on the sort above. + let prime = prime_votes.into_iter().max_by_key(|x| x.1).map(|x| x.0.clone()); + + // new_members_sorted_by_id is sorted by account id. + let new_members_ids_sorted = new_members_sorted_by_id + .iter() + .map(|(m, _)| m.clone()) + .collect::>(); + + // report member changes. We compute diff because we need the outgoing list. + let (incoming, outgoing) = T::ChangeMembers::compute_members_diff_sorted( + &new_members_ids_sorted, + &old_members_ids_sorted, + ); + T::ChangeMembers::change_members_sorted( + &incoming, + &outgoing, + &new_members_ids_sorted, + ); + T::ChangeMembers::set_prime(prime); + + // All candidates/members/runners-up who are no longer retaining a position as a + // seat holder will lose their bond. + candidates_and_deposit.iter().for_each(|(c, d)| { + if new_members_ids_sorted.binary_search(c).is_err() && + new_runners_up_ids_sorted.binary_search(c).is_err() + { + let (imbalance, _) = T::Currency::slash_reserved(c, *d); + T::LoserCandidate::on_unbalanced(imbalance); + Self::deposit_event(Event::CandidateSlashed { + candidate: c.clone(), + amount: *d, + }); + } + }); - // write final values to storage. - let deposit_of_candidate = |x: &T::AccountId| -> BalanceOf { - // defensive-only. This closure is used against the new members and new runners-up, - // both of which are phragmen winners and thus must have deposit. - candidates_and_deposit - .iter() - .find_map(|(c, d)| if c == x { Some(*d) } else { None }) - .unwrap_or_default() - }; - // fetch deposits from the one recorded one. This will make sure that a candidate who - // submitted candidacy before a change to candidacy deposit will have the correct amount - // recorded. - >::put( - new_members_sorted_by_id - .iter() - .map(|(who, stake)| SeatHolder { - deposit: deposit_of_candidate(&who), - who: who.clone(), - stake: stake.clone(), - }) - .collect::>(), - ); - >::put( - new_runners_up_sorted_by_rank - .into_iter() - .map(|(who, stake)| SeatHolder { - deposit: deposit_of_candidate(&who), - who, - stake, - }) - .collect::>(), - ); + // write final values to storage. + let deposit_of_candidate = |x: &T::AccountId| -> BalanceOf { + // defensive-only. This closure is used against the new members and new + // runners-up, both of which are phragmen winners and thus must have + // deposit. + candidates_and_deposit + .iter() + .find_map(|(c, d)| if c == x { Some(*d) } else { None }) + .defensive_unwrap_or_default() + }; + // fetch deposits from the one recorded one. This will make sure that a + // candidate who submitted candidacy before a change to candidacy deposit will + // have the correct amount recorded. + >::put( + new_members_sorted_by_id + .iter() + .map(|(who, stake)| SeatHolder { + deposit: deposit_of_candidate(who), + who: who.clone(), + stake: *stake, + }) + .collect::>(), + ); + >::put( + new_runners_up_sorted_by_rank + .into_iter() + .map(|(who, stake)| SeatHolder { + deposit: deposit_of_candidate(&who), + who, + stake, + }) + .collect::>(), + ); - // clean candidates. - >::kill(); + // clean candidates. + >::kill(); - Self::deposit_event(Event::NewTerm(new_members_sorted_by_id)); - >::mutate(|v| *v += 1); - }) - .map_err(|e| { - log::error!( - target: "runtime::elections-phragmen", - "Failed to run election [{:?}].", - e, - ); - Self::deposit_event(Event::ElectionError); - }); + Self::deposit_event(Event::NewTerm { new_members: new_members_sorted_by_id }); + >::mutate(|v| *v += 1); + }) + .map_err(|e| { + log::error!( + target: "runtime::elections-phragmen", + "Failed to run election [{:?}].", + e, + ); + Self::deposit_event(Event::ElectionError); + }); T::WeightInfo::election_phragmen(weight_candidates, weight_voters, weight_edges) } @@ -1078,7 +1136,14 @@ impl SortedMembers for Pallet { fn add(who: &T::AccountId) { Members::::mutate(|members| match members.binary_search_by(|m| m.who.cmp(who)) { Ok(_) => (), - Err(pos) => members.insert(pos, SeatHolder { who: who.clone(), ..Default::default() }), + Err(pos) => { + let s = SeatHolder { + who: who.clone(), + stake: Default::default(), + deposit: Default::default(), + }; + members.insert(pos, s) + }, }) } } @@ -1099,8 +1164,10 @@ mod tests { use super::*; use crate as elections_phragmen; use frame_support::{ - assert_noop, assert_ok, dispatch::DispatchResultWithPostInfo, parameter_types, - traits::OnInitialize, + assert_noop, assert_ok, + dispatch::DispatchResultWithPostInfo, + parameter_types, + traits::{ConstU32, ConstU64, OnInitialize}, }; use frame_system::ensure_signed; use sp_core::H256; @@ -1112,9 +1179,10 @@ mod tests { use substrate_test_utils::assert_eq_uvec; parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max( + frame_support::weights::Weight::from_ref_time(1024).set_proof_size(u64::MAX), + ); } impl frame_system::Config for Test { @@ -1122,17 +1190,17 @@ mod tests { type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -1141,17 +1209,14 @@ mod tests { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); - } - - parameter_types! { - pub const ExistentialDeposit: u64 = 1; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = frame_system::Pallet; type MaxLocks = (); type MaxReserves = (); @@ -1216,11 +1281,13 @@ mod tests { parameter_types! { pub const ElectionsPhragmenPalletId: LockIdentifier = *b"phrelect"; + pub const PhragmenMaxVoters: u32 = 1000; + pub const PhragmenMaxCandidates: u32 = 100; } impl Config for Test { type PalletId = ElectionsPhragmenPalletId; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type ChangeMembers = TestChangeMembers; @@ -1234,10 +1301,13 @@ mod tests { type LoserCandidate = (); type KickedMember = (); type WeightInfo = (); + type MaxVoters = PhragmenMaxVoters; + type MaxCandidates = PhragmenMaxCandidates; } pub type Block = sp_runtime::generic::Block; - pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + pub type UncheckedExtrinsic = + sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( pub enum Test where @@ -1280,9 +1350,7 @@ mod tests { self } pub fn genesis_members(mut self, members: Vec<(u64, u64)>) -> Self { - MEMBERS.with(|m| { - *m.borrow_mut() = members.iter().map(|(m, _)| m.clone()).collect::>() - }); + MEMBERS.with(|m| *m.borrow_mut() = members.iter().map(|(m, _)| *m).collect::>()); self.genesis_members = members; self } @@ -1295,9 +1363,9 @@ mod tests { self } pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + sp_tracing::try_init_simple(); MEMBERS.with(|m| { - *m.borrow_mut() = - self.genesis_members.iter().map(|(m, _)| m.clone()).collect::>() + *m.borrow_mut() = self.genesis_members.iter().map(|(m, _)| *m).collect::>() }); let mut ext: sp_io::TestExternalities = GenesisConfig { balances: pallet_balances::GenesisConfig:: { @@ -1426,11 +1494,11 @@ mod tests { ensure_members_has_approval_stake(); } - fn submit_candidacy(origin: Origin) -> DispatchResultWithPostInfo { + fn submit_candidacy(origin: RuntimeOrigin) -> sp_runtime::DispatchResult { Elections::submit_candidacy(origin, Elections::candidates().len() as u32) } - fn vote(origin: Origin, votes: Vec, stake: u64) -> DispatchResultWithPostInfo { + fn vote(origin: RuntimeOrigin, votes: Vec, stake: u64) -> DispatchResultWithPostInfo { // historical note: helper function was created in a period of time in which the API of vote // call was changing. Currently it is a wrapper for the original call and does not do much. // Nonetheless, totally harmless. @@ -1512,8 +1580,8 @@ mod tests { Voter { stake: 20u64, votes: vec![2], deposit: 0 } ); - assert_ok!(Elections::remove_voter(Origin::signed(1))); - assert_ok!(Elections::remove_voter(Origin::signed(2))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(1))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(2))); assert_eq!(Elections::voting(1), Default::default()); assert_eq!(Elections::voting(2), Default::default()); @@ -1606,7 +1674,7 @@ mod tests { assert!(Elections::is_candidate(&2).is_err()); assert_eq!(balances(&1), (10, 0)); - assert_ok!(submit_candidacy(Origin::signed(1))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(1))); assert_eq!(balances(&1), (7, 3)); assert_eq!(candidate_ids(), vec![1]); @@ -1615,7 +1683,7 @@ mod tests { assert!(Elections::is_candidate(&2).is_err()); assert_eq!(balances(&2), (20, 0)); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); assert_eq!(balances(&2), (17, 3)); assert_eq!(candidate_ids(), vec![1, 2]); @@ -1632,26 +1700,28 @@ mod tests { #[test] fn updating_candidacy_bond_works() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); assert_eq!(Elections::candidates(), vec![(5, 3)]); // a runtime upgrade changes the bond. CANDIDACY_BOND.with(|v| *v.borrow_mut() = 4); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); assert_eq!(Elections::candidates(), vec![(4, 4), (5, 3)]); // once elected, they each hold their candidacy bond, no more. System::set_block_number(5); Elections::on_initialize(System::block_number()); + assert_eq!(balances(&4), (34, 6)); + assert_eq!(balances(&5), (45, 5)); assert_eq!( Elections::members(), vec![ - SeatHolder { who: 4, stake: 40, deposit: 4 }, - SeatHolder { who: 5, stake: 50, deposit: 3 }, + SeatHolder { who: 4, stake: 34, deposit: 4 }, + SeatHolder { who: 5, stake: 45, deposit: 3 }, ] ); }) @@ -1662,13 +1732,13 @@ mod tests { ExtBuilder::default().build_and_execute(|| { assert_eq!(candidate_ids(), Vec::::new()); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); assert_eq!(candidate_ids(), vec![3]); - assert_ok!(submit_candidacy(Origin::signed(1))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(1))); assert_eq!(candidate_ids(), vec![1, 3]); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); assert_eq!(candidate_ids(), vec![1, 2, 3]); - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); assert_eq!(candidate_ids(), vec![1, 2, 3, 4]); }); } @@ -1677,9 +1747,12 @@ mod tests { fn dupe_candidate_submission_should_not_work() { ExtBuilder::default().build_and_execute(|| { assert_eq!(candidate_ids(), Vec::::new()); - assert_ok!(submit_candidacy(Origin::signed(1))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(1))); assert_eq!(candidate_ids(), vec![1]); - assert_noop!(submit_candidacy(Origin::signed(1)), Error::::DuplicatedCandidate); + assert_noop!( + submit_candidacy(RuntimeOrigin::signed(1)), + Error::::DuplicatedCandidate + ); }); } @@ -1687,8 +1760,8 @@ mod tests { fn member_candidacy_submission_should_not_work() { // critically important to make sure that outgoing candidates and losers are not mixed up. ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(vote(Origin::signed(2), vec![5], 20)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 20)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -1697,19 +1770,19 @@ mod tests { assert!(Elections::runners_up().is_empty()); assert!(candidate_ids().is_empty()); - assert_noop!(submit_candidacy(Origin::signed(5)), Error::::MemberSubmit); + assert_noop!(submit_candidacy(RuntimeOrigin::signed(5)), Error::::MemberSubmit); }); } #[test] fn runner_candidate_submission_should_not_work() { ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); - assert_ok!(vote(Origin::signed(2), vec![5, 4], 20)); - assert_ok!(vote(Origin::signed(1), vec![3], 10)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5, 4], 20)); + assert_ok!(vote(RuntimeOrigin::signed(1), vec![3], 10)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -1717,7 +1790,7 @@ mod tests { assert_eq!(members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![3]); - assert_noop!(submit_candidacy(Origin::signed(3)), Error::::RunnerUpSubmit); + assert_noop!(submit_candidacy(RuntimeOrigin::signed(3)), Error::::RunnerUpSubmit); }); } @@ -1726,7 +1799,7 @@ mod tests { ExtBuilder::default().build_and_execute(|| { assert_eq!(candidate_ids(), Vec::::new()); assert_noop!( - submit_candidacy(Origin::signed(7)), + submit_candidacy(RuntimeOrigin::signed(7)), Error::::InsufficientCandidateFunds, ); }); @@ -1738,11 +1811,11 @@ mod tests { assert_eq!(candidate_ids(), Vec::::new()); assert_eq!(balances(&2), (20, 0)); - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(vote(Origin::signed(2), vec![5], 20)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 20)); assert_eq!(balances(&2), (18, 2)); - assert_eq!(has_lock(&2), 20); + assert_eq!(has_lock(&2), 18); }); } @@ -1752,8 +1825,8 @@ mod tests { assert_eq!(candidate_ids(), Vec::::new()); assert_eq!(balances(&2), (20, 0)); - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(vote(Origin::signed(2), vec![5], 12)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 12)); assert_eq!(balances(&2), (18, 2)); assert_eq!(has_lock(&2), 12); @@ -1765,16 +1838,17 @@ mod tests { ExtBuilder::default().build_and_execute(|| { assert_eq!(balances(&2), (20, 0)); - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(vote(Origin::signed(2), vec![5], 20)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 20)); + // User only locks up to their free balance. assert_eq!(balances(&2), (18, 2)); - assert_eq!(has_lock(&2), 20); - assert_eq!(locked_stake_of(&2), 20); + assert_eq!(has_lock(&2), 18); + assert_eq!(locked_stake_of(&2), 18); // can update; different stake; different lock and reserve. - assert_ok!(vote(Origin::signed(2), vec![5, 4], 15)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5, 4], 15)); assert_eq!(balances(&2), (18, 2)); assert_eq!(has_lock(&2), 15); assert_eq!(locked_stake_of(&2), 15); @@ -1784,10 +1858,10 @@ mod tests { #[test] fn updated_voting_bond_works() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); assert_eq!(balances(&2), (20, 0)); - assert_ok!(vote(Origin::signed(2), vec![5], 5)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 5)); assert_eq!(balances(&2), (18, 2)); assert_eq!(voter_deposit(&2), 2); @@ -1797,11 +1871,11 @@ mod tests { // proof that bond changed. assert_eq!(balances(&1), (10, 0)); - assert_ok!(vote(Origin::signed(1), vec![5], 5)); + assert_ok!(vote(RuntimeOrigin::signed(1), vec![5], 5)); assert_eq!(balances(&1), (9, 1)); assert_eq!(voter_deposit(&1), 1); - assert_ok!(Elections::remove_voter(Origin::signed(2))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(2))); assert_eq!(balances(&2), (20, 0)); }) } @@ -1811,11 +1885,11 @@ mod tests { ExtBuilder::default().voter_bond_factor(1).build_and_execute(|| { assert_eq!(balances(&2), (20, 0)); - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); // initial vote. - assert_ok!(vote(Origin::signed(2), vec![5], 10)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 10)); // 2 + 1 assert_eq!(balances(&2), (17, 3)); @@ -1824,7 +1898,7 @@ mod tests { assert_eq!(locked_stake_of(&2), 10); // can update; different stake; different lock and reserve. - assert_ok!(vote(Origin::signed(2), vec![5, 4], 15)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5, 4], 15)); // 2 + 2 assert_eq!(balances(&2), (16, 4)); assert_eq!(Elections::voting(&2).deposit, 4); @@ -1832,15 +1906,15 @@ mod tests { assert_eq!(locked_stake_of(&2), 15); // stay at two votes with different stake. - assert_ok!(vote(Origin::signed(2), vec![5, 3], 18)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5, 3], 18)); // 2 + 2 assert_eq!(balances(&2), (16, 4)); assert_eq!(Elections::voting(&2).deposit, 4); - assert_eq!(has_lock(&2), 18); - assert_eq!(locked_stake_of(&2), 18); + assert_eq!(has_lock(&2), 16); + assert_eq!(locked_stake_of(&2), 16); // back to 1 vote. - assert_ok!(vote(Origin::signed(2), vec![4], 12)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![4], 12)); // 2 + 1 assert_eq!(balances(&2), (17, 3)); assert_eq!(Elections::voting(&2).deposit, 3); @@ -1852,17 +1926,17 @@ mod tests { #[test] fn cannot_vote_for_no_candidate() { ExtBuilder::default().build_and_execute(|| { - assert_noop!(vote(Origin::signed(2), vec![], 20), Error::::NoVotes); + assert_noop!(vote(RuntimeOrigin::signed(2), vec![], 20), Error::::NoVotes); }); } #[test] fn can_vote_for_old_members_even_when_no_new_candidates() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); - assert_ok!(vote(Origin::signed(2), vec![4, 5], 20)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![4, 5], 20)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -1870,22 +1944,22 @@ mod tests { assert_eq!(members_ids(), vec![4, 5]); assert!(candidate_ids().is_empty()); - assert_ok!(vote(Origin::signed(3), vec![4, 5], 10)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![4, 5], 10)); }); } #[test] fn prime_works() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); - assert_ok!(vote(Origin::signed(1), vec![4, 3], 10)); - assert_ok!(vote(Origin::signed(2), vec![4], 20)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(1), vec![4, 3], 10)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![4], 20)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -1893,7 +1967,7 @@ mod tests { assert_eq!(members_ids(), vec![4, 5]); assert!(candidate_ids().is_empty()); - assert_ok!(vote(Origin::signed(3), vec![4, 5], 10)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![4, 5], 10)); assert_eq!(PRIME.with(|p| *p.borrow()), Some(4)); }); } @@ -1901,17 +1975,20 @@ mod tests { #[test] fn prime_votes_for_exiting_members_are_removed() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(5))); - - assert_ok!(vote(Origin::signed(1), vec![4, 3], 10)); - assert_ok!(vote(Origin::signed(2), vec![4], 20)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - - assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Candidate(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + + assert_ok!(vote(RuntimeOrigin::signed(1), vec![4, 3], 10)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![4], 20)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); + + assert_ok!(Elections::renounce_candidacy( + RuntimeOrigin::signed(4), + Renouncing::Candidate(3) + )); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -1926,18 +2003,18 @@ mod tests { #[test] fn prime_is_kept_if_other_members_leave() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); assert_eq!(members_ids(), vec![4, 5]); assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); - assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Member)); + assert_ok!(Elections::renounce_candidacy(RuntimeOrigin::signed(4), Renouncing::Member)); assert_eq!(members_ids(), vec![5]); assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); @@ -1947,18 +2024,18 @@ mod tests { #[test] fn prime_is_gone_if_renouncing() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); assert_eq!(members_ids(), vec![4, 5]); assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); - assert_ok!(Elections::renounce_candidacy(Origin::signed(5), Renouncing::Member)); + assert_ok!(Elections::renounce_candidacy(RuntimeOrigin::signed(5), Renouncing::Member)); assert_eq!(members_ids(), vec![4]); assert_eq!(PRIME.with(|p| *p.borrow()), None); @@ -1972,29 +2049,29 @@ mod tests { .balance_factor(10) .build_and_execute(|| { // when we have only candidates - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); assert_noop!( // content of the vote is irrelevant. - vote(Origin::signed(1), vec![9, 99, 999, 9999], 5), + vote(RuntimeOrigin::signed(1), vec![9, 99, 999, 9999], 5), Error::::TooManyVotes, ); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); // now we have 2 members, 1 runner-up, and 1 new candidate - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(1), vec![9, 99, 999, 9999], 5)); + assert_ok!(vote(RuntimeOrigin::signed(1), vec![9, 99, 999, 9999], 5)); assert_noop!( - vote(Origin::signed(1), vec![9, 99, 999, 9_999, 99_999], 5), + vote(RuntimeOrigin::signed(1), vec![9, 99, 999, 9_999, 99_999], 5), Error::::TooManyVotes, ); }); @@ -2003,41 +2080,46 @@ mod tests { #[test] fn cannot_vote_for_less_than_ed() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); - assert_noop!(vote(Origin::signed(2), vec![4], 1), Error::::LowBalance); + assert_noop!(vote(RuntimeOrigin::signed(2), vec![4], 1), Error::::LowBalance); }) } #[test] - fn can_vote_for_more_than_total_balance_but_moot() { + fn can_vote_for_more_than_free_balance_but_moot() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - - assert_ok!(vote(Origin::signed(2), vec![4, 5], 30)); - // you can lie but won't get away with it. - assert_eq!(locked_stake_of(&2), 20); - assert_eq!(has_lock(&2), 20); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + + // User has 100 free and 50 reserved. + assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 100, 50)); + // User tries to vote with 150 tokens. + assert_ok!(vote(RuntimeOrigin::signed(2), vec![4, 5], 150)); + // We truncate to only their free balance, after reserving additional for voting. + assert_eq!(locked_stake_of(&2), 98); + assert_eq!(has_lock(&2), 98); }); } #[test] fn remove_voter_should_work() { ExtBuilder::default().voter_bond(8).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); - assert_ok!(vote(Origin::signed(2), vec![5], 20)); - assert_ok!(vote(Origin::signed(3), vec![5], 30)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 20)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![5], 30)); assert_eq_uvec!(all_voters(), vec![2, 3]); - assert_eq!(locked_stake_of(&2), 20); - assert_eq!(locked_stake_of(&3), 30); + assert_eq!(balances(&2), (12, 8)); + assert_eq!(locked_stake_of(&2), 12); + assert_eq!(balances(&3), (22, 8)); + assert_eq!(locked_stake_of(&3), 22); assert_eq!(votes_of(&2), vec![5]); assert_eq!(votes_of(&3), vec![5]); - assert_ok!(Elections::remove_voter(Origin::signed(2))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(2))); assert_eq_uvec!(all_voters(), vec![3]); assert!(votes_of(&2).is_empty()); @@ -2051,35 +2133,41 @@ mod tests { #[test] fn non_voter_remove_should_not_work() { ExtBuilder::default().build_and_execute(|| { - assert_noop!(Elections::remove_voter(Origin::signed(3)), Error::::MustBeVoter); + assert_noop!( + Elections::remove_voter(RuntimeOrigin::signed(3)), + Error::::MustBeVoter + ); }); } #[test] fn dupe_remove_should_fail() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(vote(Origin::signed(2), vec![5], 20)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 20)); - assert_ok!(Elections::remove_voter(Origin::signed(2))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(2))); assert!(all_voters().is_empty()); - assert_noop!(Elections::remove_voter(Origin::signed(2)), Error::::MustBeVoter); + assert_noop!( + Elections::remove_voter(RuntimeOrigin::signed(2)), + Error::::MustBeVoter + ); }); } #[test] fn removed_voter_should_not_be_counted() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); - assert_ok!(Elections::remove_voter(Origin::signed(4))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(4))); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2091,13 +2179,13 @@ mod tests { #[test] fn simple_voting_rounds_should_work() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); - assert_ok!(vote(Origin::signed(2), vec![5], 20)); - assert_ok!(vote(Origin::signed(4), vec![4], 15)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 20)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 15)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); assert_eq_uvec!(all_voters(), vec![2, 3, 4]); @@ -2113,7 +2201,10 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(members_and_stake(), vec![(3, 30), (5, 20)]); + assert_eq!(balances(&3), (25, 5)); + // votes for 5 + assert_eq!(balances(&2), (18, 2)); + assert_eq!(members_and_stake(), vec![(3, 25), (5, 18)]); assert!(Elections::runners_up().is_empty()); assert_eq_uvec!(all_voters(), vec![2, 3, 4]); @@ -2131,37 +2222,38 @@ mod tests { System::set_block_number(5); Elections::on_initialize(System::block_number()); - System::assert_last_event(Event::Elections(super::Event::EmptyTerm)); + System::assert_last_event(RuntimeEvent::Elections(super::Event::EmptyTerm)); }) } #[test] fn all_outgoing() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); System::set_block_number(5); Elections::on_initialize(System::block_number()); - System::assert_last_event(Event::Elections(super::Event::NewTerm(vec![ - (4, 40), - (5, 50), - ]))); + System::assert_last_event(RuntimeEvent::Elections(super::Event::NewTerm { + new_members: vec![(4, 35), (5, 45)], + })); - assert_eq!(members_and_stake(), vec![(4, 40), (5, 50)]); + assert_eq!(members_and_stake(), vec![(4, 35), (5, 45)]); assert_eq!(runners_up_and_stake(), vec![]); - assert_ok!(Elections::remove_voter(Origin::signed(5))); - assert_ok!(Elections::remove_voter(Origin::signed(4))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(5))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(4))); System::set_block_number(10); Elections::on_initialize(System::block_number()); - System::assert_last_event(Event::Elections(super::Event::NewTerm(vec![]))); + System::assert_last_event(RuntimeEvent::Elections(super::Event::NewTerm { + new_members: vec![], + })); // outgoing have lost their bond. assert_eq!(balances(&4), (37, 0)); @@ -2172,26 +2264,26 @@ mod tests { #[test] fn defunct_voter_will_be_counted() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); // This guy's vote is pointless for this round. - assert_ok!(vote(Origin::signed(3), vec![4], 30)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![4], 30)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(members_and_stake(), vec![(5, 50)]); + assert_eq!(members_and_stake(), vec![(5, 45)]); assert_eq!(Elections::election_rounds(), 1); // but now it has a valid target. - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); System::set_block_number(10); Elections::on_initialize(System::block_number()); // candidate 4 is affected by an old vote. - assert_eq!(members_and_stake(), vec![(4, 30), (5, 50)]); + assert_eq!(members_and_stake(), vec![(4, 28), (5, 45)]); assert_eq!(Elections::election_rounds(), 2); assert_eq_uvec!(all_voters(), vec![3, 5]); }); @@ -2200,15 +2292,15 @@ mod tests { #[test] fn only_desired_seats_are_chosen() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2221,8 +2313,8 @@ mod tests { #[test] fn phragmen_should_not_self_vote() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2231,22 +2323,24 @@ mod tests { assert_eq!(Elections::election_rounds(), 1); assert!(members_ids().is_empty()); - System::assert_last_event(Event::Elections(super::Event::NewTerm(vec![]))); + System::assert_last_event(RuntimeEvent::Elections(super::Event::NewTerm { + new_members: vec![], + })); }); } #[test] fn runners_up_should_be_kept() { ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![3], 20)); - assert_ok!(vote(Origin::signed(3), vec![2], 30)); - assert_ok!(vote(Origin::signed(4), vec![5], 40)); - assert_ok!(vote(Origin::signed(5), vec![4], 50)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![3], 20)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![2], 30)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![5], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![4], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2265,41 +2359,41 @@ mod tests { #[test] fn runners_up_should_be_next_candidates() { ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); - assert_eq!(members_and_stake(), vec![(4, 40), (5, 50)]); - assert_eq!(runners_up_and_stake(), vec![(2, 20), (3, 30)]); + assert_eq!(members_and_stake(), vec![(4, 35), (5, 45)]); + assert_eq!(runners_up_and_stake(), vec![(2, 15), (3, 25)]); - assert_ok!(vote(Origin::signed(5), vec![5], 15)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 10)); System::set_block_number(10); Elections::on_initialize(System::block_number()); - assert_eq!(members_and_stake(), vec![(3, 30), (4, 40)]); - assert_eq!(runners_up_and_stake(), vec![(5, 15), (2, 20)]); + assert_eq!(members_and_stake(), vec![(3, 25), (4, 35)]); + assert_eq!(runners_up_and_stake(), vec![(5, 10), (2, 15)]); }); } #[test] fn runners_up_lose_bond_once_outgoing() { ExtBuilder::default().desired_runners_up(1).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2307,8 +2401,8 @@ mod tests { assert_eq!(runners_up_ids(), vec![2]); assert_eq!(balances(&2), (15, 5)); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); System::set_block_number(10); Elections::on_initialize(System::block_number()); @@ -2323,17 +2417,17 @@ mod tests { ExtBuilder::default().build_and_execute(|| { assert_eq!(balances(&5), (50, 0)); - assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); assert_eq!(balances(&5), (47, 3)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); assert_eq!(balances(&5), (45, 5)); System::set_block_number(5); Elections::on_initialize(System::block_number()); assert_eq!(members_ids(), vec![5]); - assert_ok!(Elections::remove_voter(Origin::signed(5))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(5))); assert_eq!(balances(&5), (47, 3)); System::set_block_number(10); @@ -2347,10 +2441,10 @@ mod tests { #[test] fn candidates_lose_the_bond_when_outgoing() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); - assert_ok!(vote(Origin::signed(4), vec![5], 40)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![5], 40)); assert_eq!(balances(&5), (47, 3)); assert_eq!(balances(&3), (27, 3)); @@ -2370,11 +2464,11 @@ mod tests { #[test] fn current_members_are_always_next_candidate() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2382,13 +2476,13 @@ mod tests { assert_eq!(members_ids(), vec![4, 5]); assert_eq!(Elections::election_rounds(), 1); - assert_ok!(submit_candidacy(Origin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); - assert_ok!(Elections::remove_voter(Origin::signed(4))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(4))); // 5 will persist as candidates despite not being in the list. assert_eq!(candidate_ids(), vec![2, 3]); @@ -2406,22 +2500,22 @@ mod tests { // what I mean by uninterrupted: // given no input or stimulants the same members are re-elected. ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); let check_at_block = |b: u32| { System::set_block_number(b.into()); Elections::on_initialize(System::block_number()); // we keep re-electing the same folks. - assert_eq!(members_and_stake(), vec![(4, 40), (5, 50)]); - assert_eq!(runners_up_and_stake(), vec![(2, 20), (3, 30)]); + assert_eq!(members_and_stake(), vec![(4, 35), (5, 45)]); + assert_eq!(runners_up_and_stake(), vec![(2, 15), (3, 25)]); // no new candidates but old members and runners-up are always added. assert!(candidate_ids().is_empty()); assert_eq!(Elections::election_rounds(), b / 5); @@ -2439,11 +2533,11 @@ mod tests { #[test] fn remove_members_triggers_election() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2451,10 +2545,10 @@ mod tests { assert_eq!(Elections::election_rounds(), 1); // a new candidate - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); - assert_ok!(Elections::remove_member(Origin::root(), 4, false)); + assert_ok!(Elections::remove_member(RuntimeOrigin::root(), 4, true, true)); assert_eq!(balances(&4), (35, 2)); // slashed assert_eq!(Elections::election_rounds(), 2); // new election round @@ -2462,63 +2556,17 @@ mod tests { }); } - #[test] - fn remove_member_should_indicate_replacement() { - ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - - System::set_block_number(5); - Elections::on_initialize(System::block_number()); - assert_eq!(members_ids(), vec![4, 5]); - - // no replacement yet. - let unwrapped_error = Elections::remove_member(Origin::root(), 4, true).unwrap_err(); - assert!(matches!( - unwrapped_error.error, - DispatchError::Module { message: Some("InvalidReplacement"), .. } - )); - assert!(unwrapped_error.post_info.actual_weight.is_some()); - }); - - ExtBuilder::default().desired_runners_up(1).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - - System::set_block_number(5); - Elections::on_initialize(System::block_number()); - assert_eq!(members_ids(), vec![4, 5]); - assert_eq!(runners_up_ids(), vec![3]); - - // there is a replacement! and this one needs a weight refund. - let unwrapped_error = Elections::remove_member(Origin::root(), 4, false).unwrap_err(); - assert!(matches!( - unwrapped_error.error, - DispatchError::Module { message: Some("InvalidReplacement"), .. } - )); - assert!(unwrapped_error.post_info.actual_weight.is_some()); - }); - } - #[test] fn seats_should_be_released_when_no_vote() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); - assert_ok!(vote(Origin::signed(2), vec![3], 20)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![3], 20)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); assert_eq!(>::decode_len().unwrap(), 3); @@ -2529,10 +2577,10 @@ mod tests { assert_eq!(members_ids(), vec![3, 5]); assert_eq!(Elections::election_rounds(), 1); - assert_ok!(Elections::remove_voter(Origin::signed(2))); - assert_ok!(Elections::remove_voter(Origin::signed(3))); - assert_ok!(Elections::remove_voter(Origin::signed(4))); - assert_ok!(Elections::remove_voter(Origin::signed(5))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(2))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(3))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(4))); + assert_ok!(Elections::remove_voter(RuntimeOrigin::signed(5))); // meanwhile, no one cares to become a candidate again. System::set_block_number(10); @@ -2545,35 +2593,35 @@ mod tests { #[test] fn incoming_outgoing_are_reported() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); assert_eq!(members_ids(), vec![4, 5]); - assert_ok!(submit_candidacy(Origin::signed(1))); - assert_ok!(submit_candidacy(Origin::signed(2))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(1))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); // 5 will change their vote and becomes an `outgoing` - assert_ok!(vote(Origin::signed(5), vec![4], 8)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![4], 8)); // 4 will stay in the set - assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); // 3 will become a winner - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); // these two are losers. - assert_ok!(vote(Origin::signed(2), vec![2], 20)); - assert_ok!(vote(Origin::signed(1), vec![1], 10)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(1), vec![1], 10)); System::set_block_number(10); Elections::on_initialize(System::block_number()); // 3, 4 are new members, must still be bonded, nothing slashed. - assert_eq!(members_and_stake(), vec![(3, 30), (4, 48)]); + assert_eq!(members_and_stake(), vec![(3, 25), (4, 43)]); assert_eq!(balances(&3), (25, 5)); assert_eq!(balances(&4), (35, 5)); @@ -2583,22 +2631,21 @@ mod tests { // 5 is an outgoing loser. will also get slashed. assert_eq!(balances(&5), (45, 2)); - System::assert_has_event(Event::Elections(super::Event::NewTerm(vec![ - (4, 40), - (5, 50), - ]))); + System::assert_has_event(RuntimeEvent::Elections(super::Event::NewTerm { + new_members: vec![(4, 35), (5, 45)], + })); }) } #[test] fn invalid_votes_are_moot() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![10], 50)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![10], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2611,41 +2658,41 @@ mod tests { #[test] fn members_are_sorted_based_on_id_runners_on_merit() { ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![3], 20)); - assert_ok!(vote(Origin::signed(3), vec![2], 30)); - assert_ok!(vote(Origin::signed(4), vec![5], 40)); - assert_ok!(vote(Origin::signed(5), vec![4], 50)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![3], 20)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![2], 30)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![5], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![4], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); // id: low -> high. - assert_eq!(members_and_stake(), vec![(4, 50), (5, 40)]); + assert_eq!(members_and_stake(), vec![(4, 45), (5, 35)]); // merit: low -> high. - assert_eq!(runners_up_and_stake(), vec![(3, 20), (2, 30)]); + assert_eq!(runners_up_and_stake(), vec![(3, 15), (2, 25)]); }); } #[test] fn runner_up_replacement_maintains_members_order() { ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![5], 20)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![2], 50)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 20)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![2], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); assert_eq!(members_ids(), vec![2, 4]); - assert_ok!(Elections::remove_member(Origin::root(), 2, true)); + assert_ok!(Elections::remove_member(RuntimeOrigin::root(), 2, true, false)); assert_eq!(members_ids(), vec![4, 5]); }); } @@ -2653,15 +2700,15 @@ mod tests { #[test] fn can_renounce_candidacy_member_with_runners_bond_is_refunded() { ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2669,7 +2716,7 @@ mod tests { assert_eq!(members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![2, 3]); - assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Member)); + assert_ok!(Elections::renounce_candidacy(RuntimeOrigin::signed(4), Renouncing::Member)); assert_eq!(balances(&4), (38, 2)); // 2 is voting bond. assert_eq!(members_ids(), vec![3, 5]); @@ -2680,11 +2727,11 @@ mod tests { #[test] fn can_renounce_candidacy_member_without_runners_bond_is_refunded() { ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2692,7 +2739,7 @@ mod tests { assert_eq!(members_ids(), vec![4, 5]); assert!(runners_up_ids().is_empty()); - assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Member)); + assert_ok!(Elections::renounce_candidacy(RuntimeOrigin::signed(4), Renouncing::Member)); assert_eq!(balances(&4), (38, 2)); // 2 is voting bond. // no replacement @@ -2704,15 +2751,15 @@ mod tests { #[test] fn can_renounce_candidacy_runner_up() { ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(5), vec![4], 50)); - assert_ok!(vote(Origin::signed(4), vec![5], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![4], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![5], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2720,7 +2767,10 @@ mod tests { assert_eq!(members_ids(), vec![4, 5]); assert_eq!(runners_up_ids(), vec![2, 3]); - assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::RunnerUp)); + assert_ok!(Elections::renounce_candidacy( + RuntimeOrigin::signed(3), + Renouncing::RunnerUp + )); assert_eq!(balances(&3), (28, 2)); // 2 is voting bond. assert_eq!(members_ids(), vec![4, 5]); @@ -2731,22 +2781,25 @@ mod tests { #[test] fn runner_up_replacement_works_when_out_of_order() { ExtBuilder::default().desired_runners_up(2).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![5], 20)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![2], 50)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![5], 20)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![2], 50)); System::set_block_number(5); Elections::on_initialize(System::block_number()); assert_eq!(members_ids(), vec![2, 4]); assert_eq!(runners_up_ids(), vec![5, 3]); - assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::RunnerUp)); + assert_ok!(Elections::renounce_candidacy( + RuntimeOrigin::signed(3), + Renouncing::RunnerUp + )); assert_eq!(members_ids(), vec![2, 4]); assert_eq!(runners_up_ids(), vec![5]); }); @@ -2755,11 +2808,14 @@ mod tests { #[test] fn can_renounce_candidacy_candidate() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); assert_eq!(balances(&5), (47, 3)); assert_eq!(candidate_ids(), vec![5]); - assert_ok!(Elections::renounce_candidacy(Origin::signed(5), Renouncing::Candidate(1))); + assert_ok!(Elections::renounce_candidacy( + RuntimeOrigin::signed(5), + Renouncing::Candidate(1) + )); assert_eq!(balances(&5), (50, 0)); assert!(candidate_ids().is_empty()); }) @@ -2769,15 +2825,15 @@ mod tests { fn wrong_renounce_candidacy_should_fail() { ExtBuilder::default().build_and_execute(|| { assert_noop!( - Elections::renounce_candidacy(Origin::signed(5), Renouncing::Candidate(0)), + Elections::renounce_candidacy(RuntimeOrigin::signed(5), Renouncing::Candidate(0)), Error::::InvalidRenouncing, ); assert_noop!( - Elections::renounce_candidacy(Origin::signed(5), Renouncing::Member), + Elections::renounce_candidacy(RuntimeOrigin::signed(5), Renouncing::Member), Error::::InvalidRenouncing, ); assert_noop!( - Elections::renounce_candidacy(Origin::signed(5), Renouncing::RunnerUp), + Elections::renounce_candidacy(RuntimeOrigin::signed(5), Renouncing::RunnerUp), Error::::InvalidRenouncing, ); }) @@ -2786,13 +2842,13 @@ mod tests { #[test] fn non_member_renounce_member_should_fail() { ExtBuilder::default().desired_runners_up(1).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2801,7 +2857,7 @@ mod tests { assert_eq!(runners_up_ids(), vec![3]); assert_noop!( - Elections::renounce_candidacy(Origin::signed(3), Renouncing::Member), + Elections::renounce_candidacy(RuntimeOrigin::signed(3), Renouncing::Member), Error::::InvalidRenouncing, ); }) @@ -2810,13 +2866,13 @@ mod tests { #[test] fn non_runner_up_renounce_runner_up_should_fail() { ExtBuilder::default().desired_runners_up(1).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2825,7 +2881,7 @@ mod tests { assert_eq!(runners_up_ids(), vec![3]); assert_noop!( - Elections::renounce_candidacy(Origin::signed(4), Renouncing::RunnerUp), + Elections::renounce_candidacy(RuntimeOrigin::signed(4), Renouncing::RunnerUp), Error::::InvalidRenouncing, ); }) @@ -2834,27 +2890,33 @@ mod tests { #[test] fn wrong_candidate_count_renounce_should_fail() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); assert_noop!( - Elections::renounce_candidacy(Origin::signed(4), Renouncing::Candidate(2)), + Elections::renounce_candidacy(RuntimeOrigin::signed(4), Renouncing::Candidate(2)), Error::::InvalidWitnessData, ); - assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Candidate(3))); + assert_ok!(Elections::renounce_candidacy( + RuntimeOrigin::signed(4), + Renouncing::Candidate(3) + )); }) } #[test] fn renounce_candidacy_count_can_overestimate() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); // while we have only 3 candidates. - assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Candidate(4))); + assert_ok!(Elections::renounce_candidacy( + RuntimeOrigin::signed(4), + Renouncing::Candidate(4) + )); }) } @@ -2864,13 +2926,13 @@ mod tests { .desired_runners_up(2) .desired_members(1) .build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 5)); - assert_ok!(vote(Origin::signed(3), vec![3], 15)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 5)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 15)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2878,8 +2940,8 @@ mod tests { assert_eq!(members_ids(), vec![5]); assert_eq!(runners_up_ids(), vec![4, 3]); - assert_ok!(submit_candidacy(Origin::signed(2))); - assert_ok!(vote(Origin::signed(2), vec![2], 10)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 10)); System::set_block_number(10); Elections::on_initialize(System::block_number()); @@ -2900,13 +2962,13 @@ mod tests { .desired_runners_up(2) .desired_members(1) .build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2919,8 +2981,8 @@ mod tests { assert_eq!(balances(&2), (15, 5)); // this guy will shift everyone down. - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); System::set_block_number(10); Elections::on_initialize(System::block_number()); @@ -2943,13 +3005,13 @@ mod tests { .desired_runners_up(2) .desired_members(1) .build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -2962,8 +3024,8 @@ mod tests { assert_eq!(balances(&2), (15, 5)); // swap some votes. - assert_ok!(vote(Origin::signed(4), vec![2], 40)); - assert_ok!(vote(Origin::signed(2), vec![4], 20)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![2], 40)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![4], 20)); System::set_block_number(10); Elections::on_initialize(System::block_number()); @@ -2983,13 +3045,13 @@ mod tests { #[test] fn remove_and_replace_member_works() { let setup = || { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5], 50)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -3010,7 +3072,10 @@ mod tests { // member removed, no replacement found. ExtBuilder::default().desired_runners_up(1).build_and_execute(|| { setup(); - assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::RunnerUp)); + assert_ok!(Elections::renounce_candidacy( + RuntimeOrigin::signed(3), + Renouncing::RunnerUp + )); assert_eq!(Elections::remove_and_replace_member(&4, false), Ok(false)); assert_eq!(members_ids(), vec![5]); @@ -3033,15 +3098,15 @@ mod tests { .build_and_execute(|| { assert_eq!(Elections::candidates().len(), 0); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); assert_eq!(Elections::candidates().len(), 3); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -3059,15 +3124,15 @@ mod tests { .build_and_execute(|| { assert_eq!(Elections::candidates().len(), 0); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); assert_eq!(Elections::candidates().len(), 3); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -3085,15 +3150,15 @@ mod tests { .build_and_execute(|| { assert_eq!(Elections::candidates().len(), 0); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); assert_eq!(Elections::candidates().len(), 3); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(2), vec![2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 40)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2], 20)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -3108,17 +3173,17 @@ mod tests { #[test] fn dupe_vote_is_moot() { ExtBuilder::default().desired_members(1).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - assert_ok!(submit_candidacy(Origin::signed(2))); - assert_ok!(submit_candidacy(Origin::signed(1))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(2))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(1))); // all these duplicate votes will not cause 2 to win. - assert_ok!(vote(Origin::signed(1), vec![2, 2, 2, 2], 5)); - assert_ok!(vote(Origin::signed(2), vec![2, 2, 2, 2], 20)); + assert_ok!(vote(RuntimeOrigin::signed(1), vec![2, 2, 2, 2], 5)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![2, 2, 2, 2], 20)); - assert_ok!(vote(Origin::signed(3), vec![3], 30)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 30)); System::set_block_number(5); Elections::on_initialize(System::block_number()); @@ -3130,24 +3195,33 @@ mod tests { #[test] fn remove_defunct_voter_works() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(5))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); + assert_ok!(submit_candidacy(RuntimeOrigin::signed(3))); // defunct - assert_ok!(vote(Origin::signed(5), vec![5, 4], 5)); + assert_ok!(vote(RuntimeOrigin::signed(5), vec![5, 4], 5)); // defunct - assert_ok!(vote(Origin::signed(4), vec![4], 5)); + assert_ok!(vote(RuntimeOrigin::signed(4), vec![4], 5)); // ok - assert_ok!(vote(Origin::signed(3), vec![3], 5)); + assert_ok!(vote(RuntimeOrigin::signed(3), vec![3], 5)); // ok - assert_ok!(vote(Origin::signed(2), vec![3, 4], 5)); + assert_ok!(vote(RuntimeOrigin::signed(2), vec![3, 4], 5)); - assert_ok!(Elections::renounce_candidacy(Origin::signed(5), Renouncing::Candidate(3))); - assert_ok!(Elections::renounce_candidacy(Origin::signed(4), Renouncing::Candidate(2))); - assert_ok!(Elections::renounce_candidacy(Origin::signed(3), Renouncing::Candidate(1))); + assert_ok!(Elections::renounce_candidacy( + RuntimeOrigin::signed(5), + Renouncing::Candidate(3) + )); + assert_ok!(Elections::renounce_candidacy( + RuntimeOrigin::signed(4), + Renouncing::Candidate(2) + )); + assert_ok!(Elections::renounce_candidacy( + RuntimeOrigin::signed(3), + Renouncing::Candidate(1) + )); - assert_ok!(Elections::clean_defunct_voters(Origin::root(), 4, 2)); + assert_ok!(Elections::clean_defunct_voters(RuntimeOrigin::root(), 4, 2)); }) } } diff --git a/frame/elections-phragmen/src/migrations/mod.rs b/frame/elections-phragmen/src/migrations/mod.rs index 9a1f86a1ad7ce..7c62e8fa93067 100644 --- a/frame/elections-phragmen/src/migrations/mod.rs +++ b/frame/elections-phragmen/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,3 +21,5 @@ pub mod v3; /// Version 4. pub mod v4; +/// Version 5. +pub mod v5; diff --git a/frame/elections-phragmen/src/migrations/v3.rs b/frame/elections-phragmen/src/migrations/v3.rs index 728e0c4b0c915..9ec9c6e7eea6c 100644 --- a/frame/elections-phragmen/src/migrations/v3.rs +++ b/frame/elections-phragmen/src/migrations/v3.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,12 +17,10 @@ //! Migrations to version [`3.0.0`], as denoted by the changelog. +use crate::{Config, Pallet}; use codec::{Decode, Encode, FullCodec}; use frame_support::{ - pallet_prelude::ValueQuery, - traits::{PalletInfoAccess, StorageVersion}, - weights::Weight, - RuntimeDebug, Twox64Concat, + pallet_prelude::ValueQuery, traits::StorageVersion, weights::Weight, RuntimeDebug, Twox64Concat, }; use sp_std::prelude::*; @@ -42,9 +40,6 @@ struct Voter { /// Trait to implement to give information about types used for migration pub trait V2ToV3 { - /// The elections-phragmen pallet. - type Pallet: 'static + PalletInfoAccess; - /// System config account id type AccountId: 'static + FullCodec; @@ -52,30 +47,31 @@ pub trait V2ToV3 { type Balance: 'static + FullCodec + Copy; } -frame_support::generate_storage_alias!( - PhragmenElection, Candidates => Value< - Vec<(T::AccountId, T::Balance)>, - ValueQuery - > -); -frame_support::generate_storage_alias!( - PhragmenElection, Members => Value< - Vec>, - ValueQuery - > -); -frame_support::generate_storage_alias!( - PhragmenElection, RunnersUp => Value< - Vec>, - ValueQuery - > -); -frame_support::generate_storage_alias!( - PhragmenElection, Voting => Map< - (Twox64Concat, T::AccountId), - Voter - > -); +#[frame_support::storage_alias] +type Candidates = + StorageValue, Vec<(::AccountId, ::Balance)>, ValueQuery>; + +#[frame_support::storage_alias] +type Members = StorageValue< + Pallet, + Vec::AccountId, ::Balance>>, + ValueQuery, +>; + +#[frame_support::storage_alias] +type RunnersUp = StorageValue< + Pallet, + Vec::AccountId, ::Balance>>, + ValueQuery, +>; + +#[frame_support::storage_alias] +type Voting = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + Voter<::AccountId, ::Balance>, +>; /// Apply all of the migrations from 2 to 3. /// @@ -86,8 +82,11 @@ frame_support::generate_storage_alias!( /// /// Be aware that this migration is intended to be used only for the mentioned versions. Use /// with care and run at your own risk. -pub fn apply(old_voter_bond: T::Balance, old_candidacy_bond: T::Balance) -> Weight { - let storage_version = StorageVersion::get::(); +pub fn apply( + old_voter_bond: V::Balance, + old_candidacy_bond: V::Balance, +) -> Weight { + let storage_version = StorageVersion::get::>(); log::info!( target: "runtime::elections-phragmen", "Running migration for elections-phragmen with storage version {:?}", @@ -95,40 +94,40 @@ pub fn apply(old_voter_bond: T::Balance, old_candidacy_bond: T::Balan ); if storage_version <= 2 { - migrate_voters_to_recorded_deposit::(old_voter_bond); - migrate_candidates_to_recorded_deposit::(old_candidacy_bond); - migrate_runners_up_to_recorded_deposit::(old_candidacy_bond); - migrate_members_to_recorded_deposit::(old_candidacy_bond); + migrate_voters_to_recorded_deposit::(old_voter_bond); + migrate_candidates_to_recorded_deposit::(old_candidacy_bond); + migrate_runners_up_to_recorded_deposit::(old_candidacy_bond); + migrate_members_to_recorded_deposit::(old_candidacy_bond); - StorageVersion::new(3).put::(); + StorageVersion::new(3).put::>(); - Weight::max_value() + Weight::MAX } else { log::warn!( target: "runtime::elections-phragmen", "Attempted to apply migration to V3 but failed because storage version is {:?}", storage_version, ); - 0 + Weight::zero() } } /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). -pub fn migrate_voters_to_recorded_deposit(old_deposit: T::Balance) { - >::translate::<(T::Balance, Vec), _>(|_who, (stake, votes)| { +pub fn migrate_voters_to_recorded_deposit(old_deposit: V::Balance) { + >::translate::<(V::Balance, Vec), _>(|_who, (stake, votes)| { Some(Voter { votes, stake, deposit: old_deposit }) }); log::info!( target: "runtime::elections-phragmen", "migrated {} voter accounts.", - >::iter().count(), + >::iter().count(), ); } /// Migrate all candidates to recorded deposit. -pub fn migrate_candidates_to_recorded_deposit(old_deposit: T::Balance) { - let _ = >::translate::, _>(|maybe_old_candidates| { +pub fn migrate_candidates_to_recorded_deposit(old_deposit: V::Balance) { + let _ = >::translate::, _>(|maybe_old_candidates| { maybe_old_candidates.map(|old_candidates| { log::info!( target: "runtime::elections-phragmen", @@ -141,8 +140,8 @@ pub fn migrate_candidates_to_recorded_deposit(old_deposit: T::Balance } /// Migrate all members to recorded deposit. -pub fn migrate_members_to_recorded_deposit(old_deposit: T::Balance) { - let _ = >::translate::, _>(|maybe_old_members| { +pub fn migrate_members_to_recorded_deposit(old_deposit: V::Balance) { + let _ = >::translate::, _>(|maybe_old_members| { maybe_old_members.map(|old_members| { log::info!( target: "runtime::elections-phragmen", @@ -158,9 +157,9 @@ pub fn migrate_members_to_recorded_deposit(old_deposit: T::Balance) { } /// Migrate all runners-up to recorded deposit. -pub fn migrate_runners_up_to_recorded_deposit(old_deposit: T::Balance) { - let _ = - >::translate::, _>(|maybe_old_runners_up| { +pub fn migrate_runners_up_to_recorded_deposit(old_deposit: V::Balance) { + let _ = >::translate::, _>( + |maybe_old_runners_up| { maybe_old_runners_up.map(|old_runners_up| { log::info!( target: "runtime::elections-phragmen", @@ -172,5 +171,6 @@ pub fn migrate_runners_up_to_recorded_deposit(old_deposit: T::Balance .map(|(who, stake)| SeatHolder { who, stake, deposit: old_deposit }) .collect::>() }) - }); + }, + ); } diff --git a/frame/elections-phragmen/src/migrations/v4.rs b/frame/elections-phragmen/src/migrations/v4.rs index 9acc1297294d9..76ef630706c50 100644 --- a/frame/elections-phragmen/src/migrations/v4.rs +++ b/frame/elections-phragmen/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +38,7 @@ pub fn migrate>(new_pallet_name: N) -> Weight { target: "runtime::elections-phragmen", "New pallet name is equal to the old prefix. No migration needs to be done.", ); - return 0 + return Weight::zero() } let storage_version = StorageVersion::get::>(); log::info!( @@ -63,7 +63,7 @@ pub fn migrate>(new_pallet_name: N) -> Weight { "Attempted to apply migration to v4 but failed because storage version is {:?}", storage_version, ); - 0 + Weight::zero() } } diff --git a/frame/elections-phragmen/src/migrations/v5.rs b/frame/elections-phragmen/src/migrations/v5.rs new file mode 100644 index 0000000000000..eb35c1fae0f29 --- /dev/null +++ b/frame/elections-phragmen/src/migrations/v5.rs @@ -0,0 +1,70 @@ +use super::super::*; + +/// Migrate the locks and vote stake on accounts (as specified with param `to_migrate`) that have +/// more than their free balance locked. +/// +/// This migration addresses a bug were a voter could lock up to their reserved balance + free +/// balance. Since locks are only designed to operate on free balance, this put those affected in a +/// situation where they could increase their free balance but still not be able to use their funds +/// because they were less than the lock. +pub fn migrate(to_migrate: Vec) -> Weight { + let mut weight = Weight::zero(); + + for who in to_migrate.iter() { + if let Ok(mut voter) = Voting::::try_get(who) { + let free_balance = T::Currency::free_balance(who); + + weight = weight.saturating_add(T::DbWeight::get().reads(2)); + + if voter.stake > free_balance { + voter.stake = free_balance; + Voting::::insert(&who, voter); + + let pallet_id = T::PalletId::get(); + T::Currency::set_lock(pallet_id, who, free_balance, WithdrawReasons::all()); + + weight = weight.saturating_add(T::DbWeight::get().writes(2)); + } + } + } + + weight +} + +/// Given the list of voters to migrate return a function that does some checks and information +/// prior to migration. This can be linked to [`frame_support::traits::OnRuntimeUpgrade:: +/// pre_upgrade`] for further testing. +pub fn pre_migrate_fn(to_migrate: Vec) -> Box ()> { + Box::new(move || { + for who in to_migrate.iter() { + if let Ok(voter) = Voting::::try_get(who) { + let free_balance = T::Currency::free_balance(who); + + if voter.stake > free_balance { + // all good + } else { + log::warn!("pre-migrate elections-phragmen: voter={:?} has less stake then free balance", who); + } + } else { + log::warn!("pre-migrate elections-phragmen: cannot find voter={:?}", who); + } + } + log::info!("pre-migrate elections-phragmen complete"); + }) +} + +/// Some checks for after migration. This can be linked to +/// [`frame_support::traits::OnRuntimeUpgrade::post_upgrade`] for further testing. +/// +/// Panics if anything goes wrong. +pub fn post_migrate() { + for (who, voter) in Voting::::iter() { + let free_balance = T::Currency::free_balance(&who); + + assert!(voter.stake <= free_balance, "migration should have made locked <= free_balance"); + // Ideally we would also check that the locks and AccountData.misc_frozen where correctly + // updated, but since both of those are generic we can't do that without further bounding T. + } + + log::info!("post-migrate elections-phragmen complete"); +} diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index b60308c4f0a64..ddc55b08750d5 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_elections_phragmen //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/elections-phragmen/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -53,9 +55,8 @@ pub trait WeightInfo { fn renounce_candidacy_candidate(c: u32, ) -> Weight; fn renounce_candidacy_members() -> Weight; fn renounce_candidacy_runners_up() -> Weight; - fn remove_member_with_replacement() -> Weight; fn remove_member_without_replacement() -> Weight; - fn remove_member_wrong_refund() -> Weight; + fn remove_member_with_replacement() -> Weight; fn clean_defunct_voters(v: u32, d: u32, ) -> Weight; fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight; } @@ -68,131 +69,149 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[1, 16]`. fn vote_equal(v: u32, ) -> Weight { - (42_509_000 as Weight) - // Standard Error: 4_000 - .saturating_add((372_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 38_496 nanoseconds. + Weight::from_ref_time(39_424_348 as u64) + // Standard Error: 3_547 + .saturating_add(Weight::from_ref_time(119_971 as u64).saturating_mul(v as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Elections Candidates (r:1 w:0) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[2, 16]`. fn vote_more(v: u32, ) -> Weight { - (65_311_000 as Weight) - // Standard Error: 6_000 - .saturating_add((419_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 49_459 nanoseconds. + Weight::from_ref_time(50_225_486 as u64) + // Standard Error: 3_160 + .saturating_add(Weight::from_ref_time(170_360 as u64).saturating_mul(v as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Elections Candidates (r:1 w:0) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[2, 16]`. fn vote_less(v: u32, ) -> Weight { - (65_444_000 as Weight) - // Standard Error: 5_000 - .saturating_add((376_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 48_712 nanoseconds. + Weight::from_ref_time(49_463_298 as u64) + // Standard Error: 2_678 + .saturating_add(Weight::from_ref_time(231_771 as u64).saturating_mul(v as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - (61_585_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 48_359 nanoseconds. + Weight::from_ref_time(48_767_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Elections Candidates (r:1 w:1) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) + /// The range of component `c` is `[1, 1000]`. fn submit_candidacy(c: u32, ) -> Weight { - (53_333_000 as Weight) - // Standard Error: 1_000 - .saturating_add((267_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 43_369 nanoseconds. + Weight::from_ref_time(49_587_113 as u64) + // Standard Error: 1_008 + .saturating_add(Weight::from_ref_time(77_752 as u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Elections Candidates (r:1 w:1) + /// The range of component `c` is `[1, 1000]`. fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (49_128_000 as Weight) - // Standard Error: 1_000 - .saturating_add((144_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 41_321 nanoseconds. + Weight::from_ref_time(50_803_289 as u64) + // Standard Error: 1_159 + .saturating_add(Weight::from_ref_time(57_239 as u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Elections Members (r:1 w:1) // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Instance1Collective Prime (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:0) - // Storage: Instance1Collective Members (r:0 w:1) + // Storage: Council Prime (r:1 w:1) + // Storage: Council Proposals (r:1 w:0) + // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - (70_685_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 53_542 nanoseconds. + Weight::from_ref_time(54_481_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - (49_766_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 41_825 nanoseconds. + Weight::from_ref_time(42_248_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Benchmark Override (r:0 w:0) + fn remove_member_without_replacement() -> Weight { + // Minimum execution time: 2_000_000_000 nanoseconds. + Weight::from_ref_time(2_000_000_000_000 as u64) } - // Storage: Elections RunnersUp (r:1 w:1) // Storage: Elections Members (r:1 w:1) // Storage: System Account (r:1 w:1) - // Storage: Instance1Collective Prime (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:0) - // Storage: Instance1Collective Members (r:0 w:1) + // Storage: Elections RunnersUp (r:1 w:1) + // Storage: Council Prime (r:1 w:1) + // Storage: Council Proposals (r:1 w:0) + // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - (76_153_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) - } - fn remove_member_without_replacement() -> Weight { - T::BlockWeights::get().max_block - } - // Storage: Elections RunnersUp (r:1 w:0) - fn remove_member_wrong_refund() -> Weight { - (6_697_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) + // Minimum execution time: 62_600 nanoseconds. + Weight::from_ref_time(63_152_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) } - // Storage: Elections Voting (r:251 w:250) + // Storage: Elections Voting (r:5001 w:5000) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Candidates (r:1 w:0) - // Storage: Balances Locks (r:250 w:250) - // Storage: System Account (r:250 w:250) + // Storage: Balances Locks (r:5000 w:5000) + // Storage: System Account (r:5000 w:5000) + /// The range of component `v` is `[5000, 10000]`. + /// The range of component `d` is `[0, 5000]`. fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 60_000 - .saturating_add((107_467_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) - .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) + // Minimum execution time: 297_149_264 nanoseconds. + Weight::from_ref_time(297_898_499_000 as u64) + // Standard Error: 263_819 + .saturating_add(Weight::from_ref_time(37_914_985 as u64).saturating_mul(v as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(v as u64))) + .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(v as u64))) } // Storage: Elections Candidates (r:1 w:1) // Storage: Elections Members (r:1 w:1) // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Elections Voting (r:502 w:0) - // Storage: Instance1Collective Proposals (r:1 w:0) + // Storage: Elections Voting (r:10001 w:0) + // Storage: Council Proposals (r:1 w:0) // Storage: Elections ElectionRounds (r:1 w:1) - // Storage: Instance1Collective Members (r:0 w:1) - // Storage: Instance1Collective Prime (r:0 w:1) - // Storage: System Account (r:2 w:2) + // Storage: Council Members (r:0 w:1) + // Storage: Council Prime (r:0 w:1) + // Storage: System Account (r:1 w:1) + /// The range of component `c` is `[1, 1000]`. + /// The range of component `v` is `[1, 10000]`. + /// The range of component `e` is `[10000, 160000]`. fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 1_846_000 - .saturating_add((39_843_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 768_000 - .saturating_add((60_623_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 52_000 - .saturating_add((3_884_000 as Weight).saturating_mul(e as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) + // Minimum execution time: 22_034_317 nanoseconds. + Weight::from_ref_time(22_110_020_000 as u64) + // Standard Error: 235_528 + .saturating_add(Weight::from_ref_time(25_553_585 as u64).saturating_mul(v as u64)) + // Standard Error: 15_114 + .saturating_add(Weight::from_ref_time(1_032_330 as u64).saturating_mul(e as u64)) + .saturating_add(T::DbWeight::get().reads(280 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(c as u64))) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(v as u64))) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(c as u64))) } } @@ -203,132 +222,148 @@ impl WeightInfo for () { // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[1, 16]`. fn vote_equal(v: u32, ) -> Weight { - (42_509_000 as Weight) - // Standard Error: 4_000 - .saturating_add((372_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 38_496 nanoseconds. + Weight::from_ref_time(39_424_348 as u64) + // Standard Error: 3_547 + .saturating_add(Weight::from_ref_time(119_971 as u64).saturating_mul(v as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Elections Candidates (r:1 w:0) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[2, 16]`. fn vote_more(v: u32, ) -> Weight { - (65_311_000 as Weight) - // Standard Error: 6_000 - .saturating_add((419_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 49_459 nanoseconds. + Weight::from_ref_time(50_225_486 as u64) + // Standard Error: 3_160 + .saturating_add(Weight::from_ref_time(170_360 as u64).saturating_mul(v as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Elections Candidates (r:1 w:0) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[2, 16]`. fn vote_less(v: u32, ) -> Weight { - (65_444_000 as Weight) - // Standard Error: 5_000 - .saturating_add((376_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 48_712 nanoseconds. + Weight::from_ref_time(49_463_298 as u64) + // Standard Error: 2_678 + .saturating_add(Weight::from_ref_time(231_771 as u64).saturating_mul(v as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - (61_585_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 48_359 nanoseconds. + Weight::from_ref_time(48_767_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Elections Candidates (r:1 w:1) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) + /// The range of component `c` is `[1, 1000]`. fn submit_candidacy(c: u32, ) -> Weight { - (53_333_000 as Weight) - // Standard Error: 1_000 - .saturating_add((267_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 43_369 nanoseconds. + Weight::from_ref_time(49_587_113 as u64) + // Standard Error: 1_008 + .saturating_add(Weight::from_ref_time(77_752 as u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Elections Candidates (r:1 w:1) + /// The range of component `c` is `[1, 1000]`. fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (49_128_000 as Weight) - // Standard Error: 1_000 - .saturating_add((144_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 41_321 nanoseconds. + Weight::from_ref_time(50_803_289 as u64) + // Standard Error: 1_159 + .saturating_add(Weight::from_ref_time(57_239 as u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Elections Members (r:1 w:1) // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Instance1Collective Prime (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:0) - // Storage: Instance1Collective Members (r:0 w:1) + // Storage: Council Prime (r:1 w:1) + // Storage: Council Proposals (r:1 w:0) + // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - (70_685_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 53_542 nanoseconds. + Weight::from_ref_time(54_481_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - (49_766_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 41_825 nanoseconds. + Weight::from_ref_time(42_248_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Benchmark Override (r:0 w:0) + fn remove_member_without_replacement() -> Weight { + // Minimum execution time: 2_000_000_000 nanoseconds. + Weight::from_ref_time(2_000_000_000_000 as u64) } - // Storage: Elections RunnersUp (r:1 w:1) // Storage: Elections Members (r:1 w:1) // Storage: System Account (r:1 w:1) - // Storage: Instance1Collective Prime (r:1 w:1) - // Storage: Instance1Collective Proposals (r:1 w:0) - // Storage: Instance1Collective Members (r:0 w:1) + // Storage: Elections RunnersUp (r:1 w:1) + // Storage: Council Prime (r:1 w:1) + // Storage: Council Proposals (r:1 w:0) + // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - (76_153_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + // Minimum execution time: 62_600 nanoseconds. + Weight::from_ref_time(63_152_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) } - fn remove_member_without_replacement() -> Weight { - (76_153_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) - } - // Storage: Elections RunnersUp (r:1 w:0) - fn remove_member_wrong_refund() -> Weight { - (6_697_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - } - // Storage: Elections Voting (r:251 w:250) + // Storage: Elections Voting (r:5001 w:5000) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Candidates (r:1 w:0) - // Storage: Balances Locks (r:250 w:250) - // Storage: System Account (r:250 w:250) + // Storage: Balances Locks (r:5000 w:5000) + // Storage: System Account (r:5000 w:5000) + /// The range of component `v` is `[5000, 10000]`. + /// The range of component `d` is `[0, 5000]`. fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 60_000 - .saturating_add((107_467_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) - .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) + // Minimum execution time: 297_149_264 nanoseconds. + Weight::from_ref_time(297_898_499_000 as u64) + // Standard Error: 263_819 + .saturating_add(Weight::from_ref_time(37_914_985 as u64).saturating_mul(v as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(v as u64))) + .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(v as u64))) } // Storage: Elections Candidates (r:1 w:1) // Storage: Elections Members (r:1 w:1) // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Elections Voting (r:502 w:0) - // Storage: Instance1Collective Proposals (r:1 w:0) + // Storage: Elections Voting (r:10001 w:0) + // Storage: Council Proposals (r:1 w:0) // Storage: Elections ElectionRounds (r:1 w:1) - // Storage: Instance1Collective Members (r:0 w:1) - // Storage: Instance1Collective Prime (r:0 w:1) - // Storage: System Account (r:2 w:2) + // Storage: Council Members (r:0 w:1) + // Storage: Council Prime (r:0 w:1) + // Storage: System Account (r:1 w:1) + /// The range of component `c` is `[1, 1000]`. + /// The range of component `v` is `[1, 10000]`. + /// The range of component `e` is `[10000, 160000]`. fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 1_846_000 - .saturating_add((39_843_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 768_000 - .saturating_add((60_623_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 52_000 - .saturating_add((3_884_000 as Weight).saturating_mul(e as Weight)) - .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) + // Minimum execution time: 22_034_317 nanoseconds. + Weight::from_ref_time(22_110_020_000 as u64) + // Standard Error: 235_528 + .saturating_add(Weight::from_ref_time(25_553_585 as u64).saturating_mul(v as u64)) + // Standard Error: 15_114 + .saturating_add(Weight::from_ref_time(1_032_330 as u64).saturating_mul(e as u64)) + .saturating_add(RocksDbWeight::get().reads(280 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(c as u64))) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(v as u64))) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(c as u64))) } } diff --git a/frame/elections/Cargo.toml b/frame/elections/Cargo.toml deleted file mode 100644 index 8557cfba6b58c..0000000000000 --- a/frame/elections/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "pallet-elections" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "FRAME pallet for elections" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ - "derive", -] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } - -[dev-dependencies] -pallet-balances = { version = "4.0.0-dev", path = "../balances" } - -[features] -default = ["std"] -std = [ - "codec/std", - "scale-info/std", - "sp-core/std", - "sp-std/std", - "sp-io/std", - "frame-support/std", - "sp-runtime/std", - "frame-system/std", -] -try-runtime = ["frame-support/try-runtime"] diff --git a/frame/elections/README.md b/frame/elections/README.md deleted file mode 100644 index 1f6fd42331c15..0000000000000 --- a/frame/elections/README.md +++ /dev/null @@ -1,7 +0,0 @@ -Election module for stake-weighted membership selection of a collective. - -The composition of a set of account IDs works according to one or more approval votes -weighted by stake. There is a partial carry-over facility to give greater weight to those -whose voting is serially unsuccessful. - -License: Apache-2.0 \ No newline at end of file diff --git a/frame/elections/src/lib.rs b/frame/elections/src/lib.rs deleted file mode 100644 index ac13bce31b0f6..0000000000000 --- a/frame/elections/src/lib.rs +++ /dev/null @@ -1,1325 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! # WARNING: NOT ACTIVELY MAINTAINED -//! -//! This pallet is currently not maintained and should not be used in production until further -//! notice. -//! -//! --- -//! -//! Election pallet for stake-weighted membership selection of a collective. -//! -//! The composition of a set of account IDs works according to one or more approval votes -//! weighted by stake. There is a partial carry-over facility to give greater weight to those -//! whose voting is serially unsuccessful. - -#![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "128"] - -use codec::{Decode, Encode}; -use frame_support::{ - ensure, - pallet_prelude::*, - traits::{ - BalanceStatus, ChangeMembers, Currency, ExistenceRequirement, LockIdentifier, - LockableCurrency, OnUnbalanced, ReservableCurrency, WithdrawReasons, - }, - weights::{DispatchClass, Weight}, -}; -use frame_system::pallet_prelude::*; -pub use pallet::*; -use sp_runtime::{ - print, - traits::{One, Saturating, StaticLookup, Zero}, - RuntimeDebug, -}; -use sp_std::prelude::*; - -mod mock; -mod tests; - -// no polynomial attacks: -// -// all unbonded public operations should be constant time. -// all other public operations must be linear time in terms of prior public operations and: -// - those "valid" ones that cost nothing be limited to a constant number per single protected -// operation -// - the rest costing the same order as the computational complexity -// all protected operations must complete in at most O(public operations) -// -// we assume "beneficial" transactions will have the same access as attack transactions. -// -// any storage requirements should be bonded by the same order as the volume. - -// public operations: -// - express approvals (you pay in a "voter" bond the first time you do this; O(1); one extra DB -// entry, one DB change) -// - remove active voter (you get your "voter" bond back; O(1); one fewer DB entry, one DB change) -// - remove inactive voter (either you or the target is removed; if the target, you get their -// "voter" bond back; O(1); one fewer DB entry, one DB change) -// - submit candidacy (you pay a "candidate" bond; O(1); one extra DB entry, two DB changes) -// - present winner/runner-up (you may pay a "presentation" bond of O(voters) if the presentation is -// invalid; O(voters) compute; ) protected operations: -// - remove candidacy (remove all votes for a candidate) (one fewer DB entry, two DB changes) - -// to avoid a potentially problematic case of not-enough approvals prior to voting causing a -// back-to-back votes that have no way of ending, then there's a forced grace period between votes. -// to keep the system as stateless as possible (making it a bit easier to reason about), we just -// restrict when votes can begin to blocks that lie on boundaries (`voting_period`). - -// for an approval vote of C members: - -// top K runners-up are maintained between votes. all others are discarded. -// - candidate removed & bond returned when elected. -// - candidate removed & bond burned when discarded. - -// at the point that the vote ends (), all voters' balances are snapshotted. - -// for B blocks following, there's a counting period whereby each of the candidates that believe -// they fall in the top K+C voted can present themselves. they get the total stake -// recorded (based on the snapshot); an ordered list is maintained (the leaderboard). No one may -// present themselves that, if elected, would result in being included twice in the collective -// (important since existing members will have their approval votes as it may be that they -// don't get removed), nor if existing presenters would mean they're not in the top K+C. - -// following B blocks, the top C candidates are elected and have their bond returned. the top C -// candidates and all other candidates beyond the top C+K are cleared. - -// vote-clearing happens lazily; for an approval to count, the most recent vote at the time of the -// voter's most recent vote must be no later than the most recent vote at the time that the -// candidate in the approval position was registered there. as candidates are removed from the -// register and others join in their place, this prevents an approval meant for an earlier candidate -// being used to elect a new candidate. - -// the candidate list increases as needed, but the contents (though not really the capacity) reduce -// after each vote as all but K entries are cleared. newly registering candidates must use cleared -// entries before they increase the capacity. - -/// The activity status of a voter. -#[derive( - PartialEq, Eq, Copy, Clone, Encode, Decode, Default, RuntimeDebug, scale_info::TypeInfo, -)] -pub struct VoterInfo { - /// Last VoteIndex in which this voter assigned (or initialized) approvals. - last_active: VoteIndex, - /// Last VoteIndex in which one of this voter's approvals won. - /// Note that `last_win = N` indicates a last win at index `N-1`, hence `last_win = 0` means no - /// win ever. - last_win: VoteIndex, - /// The amount of stored weight as a result of not winning but changing approvals. - pot: Balance, - /// Current staked amount. A lock equal to this value always exists. - stake: Balance, -} - -/// Used to demonstrate the status of a particular index in the global voter list. -#[derive(PartialEq, Eq, RuntimeDebug)] -pub enum CellStatus { - /// Any out of bound index. Means a push a must happen to the chunk pointed by - /// `NextVoterSet`. Voting fee is applied in case a new chunk is created. - Head, - /// Already occupied by another voter. Voting fee is applied. - Occupied, - /// Empty hole which should be filled. No fee will be applied. - Hole, -} - -/// Number of voters grouped in one chunk. -pub const VOTER_SET_SIZE: usize = 64; -/// NUmber of approvals grouped in one chunk. -pub const APPROVAL_SET_SIZE: usize = 8; - -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; -type NegativeImbalanceOf = <::Currency as Currency< - ::AccountId, ->>::NegativeImbalance; - -/// Index used to access chunks. -type SetIndex = u32; -/// Index used to count voting rounds. -pub type VoteIndex = u32; -/// Underlying data type of the approvals. -type ApprovalFlag = u32; -/// Number of approval flags that can fit into [`ApprovalFlag`] type. -const APPROVAL_FLAG_LEN: usize = 32; - -#[frame_support::pallet] -pub mod pallet { - use super::*; - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - #[pallet::config] - pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; - - /// Identifier for the elections pallet's lock - #[pallet::constant] - type PalletId: Get; - - /// The currency that people are electing with. - type Currency: LockableCurrency - + ReservableCurrency; - - /// Handler for the unbalanced reduction when slashing a validator. - type BadPresentation: OnUnbalanced>; - - /// Handler for the unbalanced reduction when slashing an invalid reaping attempt. - type BadReaper: OnUnbalanced>; - - /// Handler for the unbalanced reduction when submitting a bad `voter_index`. - type BadVoterIndex: OnUnbalanced>; - - /// Handler for the unbalanced reduction when a candidate has lost (and is not a runner up) - type LoserCandidate: OnUnbalanced>; - - /// What to do when the members change. - type ChangeMembers: ChangeMembers; - - /// How much should be locked up in order to submit one's candidacy. A reasonable - /// default value is 9. - #[pallet::constant] - type CandidacyBond: Get>; - - /// How much should be locked up in order to be able to submit votes. - #[pallet::constant] - type VotingBond: Get>; - - /// The amount of fee paid upon each vote submission, unless if they submit a - /// _hole_ index and replace it. - #[pallet::constant] - type VotingFee: Get>; - - /// Minimum about that can be used as the locked value for voting. - #[pallet::constant] - type MinimumVotingLock: Get>; - - /// The punishment, per voter, if you provide an invalid presentation. A - /// reasonable default value is 1. - #[pallet::constant] - type PresentSlashPerVoter: Get>; - - /// How many runners-up should have their approvals persist until the next - /// vote. A reasonable default value is 2. - #[pallet::constant] - type CarryCount: Get; - - /// How many vote indices need to go by after a target voter's last vote before - /// they can be reaped if their approvals are moot. A reasonable default value - /// is 1. - #[pallet::constant] - type InactiveGracePeriod: Get; - - /// How often (in blocks) to check for new votes. A reasonable default value - /// is 1000. - #[pallet::constant] - type VotingPeriod: Get; - - /// Decay factor of weight when being accumulated. It should typically be set to - /// __at least__ `membership_size -1` to keep the collective secure. - /// When set to `N`, it indicates `(1/N)^t` of staked is decayed at weight - /// increment step `t`. 0 will result in no weight being added at all (normal - /// approval voting). A reasonable default value is 24. - #[pallet::constant] - type DecayRatio: Get; - } - - #[pallet::extra_constants] - impl Pallet { - // TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed. - /// The chunk size of the voter vector. - #[allow(non_snake_case)] - fn VOTER_SET_SIZE() -> u32 { - VOTER_SET_SIZE as u32 - } - - // TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed. - /// The chunk size of the approval vector. - #[allow(non_snake_case)] - fn APPROVAL_SET_SIZE() -> u32 { - APPROVAL_SET_SIZE as u32 - } - } - - // ---- permanent state (always relevant, changes only at the finalization of voting) - - /// How long to give each top candidate to present themselves after the vote ends. - #[pallet::storage] - #[pallet::getter(fn presentation_duration)] - pub type PresentationDuration = StorageValue<_, T::BlockNumber, ValueQuery>; - - /// How long each position is active for. - #[pallet::storage] - #[pallet::getter(fn term_duration)] - pub type TermDuration = StorageValue<_, T::BlockNumber, ValueQuery>; - - /// Number of accounts that should constitute the collective. - #[pallet::storage] - #[pallet::getter(fn desired_seats)] - pub type DesiredSeats = StorageValue<_, u32, ValueQuery>; - - // ---- permanent state (always relevant, changes only at the finalization of voting) - - /// The current membership. When there's a vote going on, this should still be used for - /// executive matters. The block number (second element in the tuple) is the block that - /// their position is active until (calculated by the sum of the block number when the - /// member was elected and their term duration). - #[pallet::storage] - #[pallet::getter(fn members)] - pub type Members = StorageValue<_, Vec<(T::AccountId, T::BlockNumber)>, ValueQuery>; - - /// The total number of vote rounds that have happened or are in progress. - #[pallet::storage] - #[pallet::getter(fn vote_index)] - pub type VoteCount = StorageValue<_, VoteIndex, ValueQuery>; - - // ---- persistent state (always relevant, changes constantly) - - // A list of votes for each voter. The votes are stored as numeric values and parsed in a - // bit-wise manner. In order to get a human-readable representation (`Vec`), use - // [`all_approvals_of`]. Furthermore, each vector of scalars is chunked with the cap of - // `APPROVAL_SET_SIZE`. - /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash and `SetIndex` is not - /// attacker-controlled. - #[pallet::storage] - #[pallet::getter(fn approvals_of)] - pub type ApprovalsOf = - StorageMap<_, Twox64Concat, (T::AccountId, SetIndex), Vec, ValueQuery>; - - /// The vote index and list slot that the candidate `who` was registered or `None` if they - /// are not currently registered. - /// - /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash. - #[pallet::storage] - #[pallet::getter(fn candidate_reg_info)] - pub type RegisterInfoOf = - StorageMap<_, Twox64Concat, T::AccountId, (VoteIndex, u32)>; - - /// Basic information about a voter. - /// - /// TWOX-NOTE: SAFE as `AccountId` is a crypto hash. - #[pallet::storage] - #[pallet::getter(fn voter_info)] - pub type VoterInfoOf = - StorageMap<_, Twox64Concat, T::AccountId, VoterInfo>>; - - /// The present voter list (chunked and capped at [`VOTER_SET_SIZE`]). - /// - /// TWOX-NOTE: OKAY ― `SetIndex` is not user-controlled data. - #[pallet::storage] - #[pallet::getter(fn voters)] - pub type Voters = - StorageMap<_, Twox64Concat, SetIndex, Vec>, ValueQuery>; - - /// the next free set to store a voter in. This will keep growing. - #[pallet::storage] - #[pallet::getter(fn next_nonfull_voter_set)] - pub type NextVoterSet = StorageValue<_, SetIndex, ValueQuery>; - - /// Current number of Voters. - #[pallet::storage] - #[pallet::getter(fn voter_count)] - pub type VoterCount = StorageValue<_, SetIndex, ValueQuery>; - - /// The present candidate list. - #[pallet::storage] - #[pallet::getter(fn candidates)] - pub type Candidates = StorageValue<_, Vec, ValueQuery>; // has holes - - /// Current number of active candidates - #[pallet::storage] - #[pallet::getter(fn candidate_count)] - pub type CandidateCount = StorageValue<_, u32, ValueQuery>; - - // ---- temporary state (only relevant during finalization/presentation) - - /// The accounts holding the seats that will become free on the next tally. - #[pallet::storage] - #[pallet::getter(fn next_finalize)] - pub type NextFinalize = StorageValue<_, (T::BlockNumber, u32, Vec)>; - - /// Get the leaderboard if we're in the presentation phase. The first element is the weight - /// of each entry; It may be the direct summed approval stakes, or a weighted version of it. - /// Sorted from low to high. - #[pallet::storage] - #[pallet::getter(fn leaderboard)] - pub type Leaderboard = StorageValue<_, Vec<(BalanceOf, T::AccountId)>>; - - #[pallet::genesis_config] - pub struct GenesisConfig { - pub presentation_duration: T::BlockNumber, - pub term_duration: T::BlockNumber, - pub desired_seats: u32, - pub members: Vec<(T::AccountId, T::BlockNumber)>, - } - - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { - presentation_duration: Default::default(), - term_duration: Default::default(), - desired_seats: Default::default(), - members: Default::default(), - } - } - } - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - PresentationDuration::::put(self.presentation_duration); - TermDuration::::put(self.term_duration); - DesiredSeats::::put(self.desired_seats); - Members::::put(&self.members); - } - } - - #[pallet::error] - pub enum Error { - /// Reporter must be a voter. - NotVoter, - /// Target for inactivity cleanup must be active. - InactiveTarget, - /// Cannot reap during presentation period. - CannotReapPresenting, - /// Cannot reap during grace period. - ReapGrace, - /// Invalid reporter index. - InvalidReporterIndex, - /// Invalid target index. - InvalidTargetIndex, - /// Invalid vote index. - InvalidVoteIndex, - /// Cannot retract when presenting. - CannotRetractPresenting, - /// Cannot retract non-voter. - RetractNonVoter, - /// Invalid retraction index. - InvalidRetractionIndex, - /// Duplicate candidate submission. - DuplicatedCandidate, - /// Invalid candidate slot. - InvalidCandidateSlot, - /// Candidate has not enough funds. - InsufficientCandidateFunds, - /// Presenter must have sufficient slashable funds. - InsufficientPresenterFunds, - /// Stake deposited to present winner and be added to leaderboard should be non-zero. - ZeroDeposit, - /// Candidate not worthy of leaderboard. - UnworthyCandidate, - /// Leaderboard must exist while present phase active. - LeaderboardMustExist, - /// Cannot present outside of presentation period. - NotPresentationPeriod, - /// Presented candidate must be current. - InvalidCandidate, - /// Duplicated presentation. - DuplicatedPresentation, - /// Incorrect total. - IncorrectTotal, - /// Invalid voter index. - InvalidVoterIndex, - /// New voter must have sufficient funds to pay the bond. - InsufficientVoterFunds, - /// Locked value must be more than limit. - InsufficientLockedValue, - /// Amount of candidate votes cannot exceed amount of candidates. - TooManyVotes, - /// Amount of candidates to receive approval votes should be non-zero. - ZeroCandidates, - /// No approval changes during presentation period. - ApprovalPresentation, - } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(n: T::BlockNumber) -> Weight { - if let Err(e) = Self::end_block(n) { - print("Guru meditation"); - print(e); - } - 0 - } - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// Reaped \[voter, reaper\]. - VoterReaped(T::AccountId, T::AccountId), - /// Slashed \[reaper\]. - BadReaperSlashed(T::AccountId), - /// A tally (for approval votes of \[seats\]) has started. - TallyStarted(u32), - /// A tally (for approval votes of seat(s)) has ended (with one or more new members). - /// \[incoming, outgoing\] - TallyFinalized(Vec, Vec), - } - - #[pallet::call] - impl Pallet { - /// Set candidate approvals. Approval slots stay valid as long as candidates in those slots - /// are registered. - /// - /// Locks `value` from the balance of `origin` indefinitely. Only - /// [`retract_voter`](Self::retract_voter) or - /// [`reap_inactive_voter`](Self::reap_inactive_voter) can unlock the balance. - /// - /// `hint` argument is interpreted differently based on: - /// - if `origin` is setting approvals for the first time: The index will be checked for - /// being a valid _hole_ in the voter list. - /// - if the hint is correctly pointing to a hole, no fee is deducted from `origin`. - /// - Otherwise, the call will succeed but the index is ignored and simply a push to the - /// last chunk with free space happens. If the new push causes a new chunk to be - /// created, a fee indicated by [`Config::VotingFee`] is deducted. - /// - if `origin` is already a voter: the index __must__ be valid and point to the correct - /// position of the `origin` in the current voters list. - /// - /// Note that any trailing `false` votes in `votes` is ignored; In approval voting, not - /// voting for a candidate and voting false, are equal. - /// - /// # - /// - O(1). - /// - Two extra DB entries, one DB change. - /// - Argument `votes` is limited in length to number of candidates. - /// # - #[pallet::weight(2_500_000_000)] - pub fn set_approvals( - origin: OriginFor, - votes: Vec, - #[pallet::compact] index: VoteIndex, - hint: SetIndex, - #[pallet::compact] value: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::do_set_approvals(who, votes, index, hint, value) - } - - /// Remove a voter. For it not to be a bond-consuming no-op, all approved candidate indices - /// must now be either unregistered or registered to a candidate that registered the slot - /// after the voter gave their last approval set. - /// - /// Both indices must be provided according to the following principle: - /// Voter index does not take holes into account. This means that any account submitting an - /// index at any point in time should submit: - /// `VOTER_SET_SIZE * set_index + local_index`, meaning that you are ignoring all holes in - /// the first `set_index` sets. - /// - /// May be called by anyone. Returns the voter deposit to `signed`. - /// - /// # - /// - O(1). - /// - Two fewer DB entries, one DB change. - /// # - #[pallet::weight(2_500_000_000)] - pub fn reap_inactive_voter( - origin: OriginFor, - #[pallet::compact] reporter_index: u32, - who: ::Source, - #[pallet::compact] who_index: u32, - #[pallet::compact] assumed_vote_index: VoteIndex, - ) -> DispatchResult { - let reporter = ensure_signed(origin)?; - let who = T::Lookup::lookup(who)?; - - ensure!(!Self::presentation_active(), Error::::CannotReapPresenting); - ensure!(Self::voter_info(&reporter).is_some(), Error::::NotVoter); - - let info = Self::voter_info(&who).ok_or(Error::::InactiveTarget)?; - let last_active = info.last_active; - - ensure!(assumed_vote_index == Self::vote_index(), Error::::InvalidVoteIndex); - ensure!( - assumed_vote_index > last_active + T::InactiveGracePeriod::get(), - Error::::ReapGrace, - ); - - let reporter_index = reporter_index as usize; - let who_index = who_index as usize; - let assumed_reporter = - Self::voter_at(reporter_index).ok_or(Error::::InvalidReporterIndex)?; - let assumed_who = Self::voter_at(who_index).ok_or(Error::::InvalidTargetIndex)?; - - ensure!(assumed_reporter == reporter, Error::::InvalidReporterIndex); - ensure!(assumed_who == who, Error::::InvalidTargetIndex); - - // will definitely kill one of reporter or who now. - - let valid = !Self::all_approvals_of(&who).iter().zip(Self::candidates().iter()).any( - |(&appr, addr)| { - appr && - *addr != T::AccountId::default() && - // defensive only: all items in candidates list are registered - Self::candidate_reg_info(addr).map_or(false, |x| x.0 <= last_active) - }, - ); - - Self::remove_voter( - if valid { &who } else { &reporter }, - if valid { who_index } else { reporter_index }, - ); - - T::Currency::remove_lock(T::PalletId::get(), if valid { &who } else { &reporter }); - - if valid { - // This only fails if `reporter` doesn't exist, which it clearly must do since its - // the origin. Still, it's no more harmful to propagate any error at this point. - T::Currency::repatriate_reserved( - &who, - &reporter, - T::VotingBond::get(), - BalanceStatus::Free, - )?; - Self::deposit_event(Event::::VoterReaped(who, reporter)); - } else { - let imbalance = T::Currency::slash_reserved(&reporter, T::VotingBond::get()).0; - T::BadReaper::on_unbalanced(imbalance); - Self::deposit_event(Event::::BadReaperSlashed(reporter)); - } - Ok(()) - } - - /// Remove a voter. All votes are cancelled and the voter deposit is returned. - /// - /// The index must be provided according to the following principle: - /// Voter index does not take holes into account. This means that any account submitting an - /// index at any point in time should submit: - /// `VOTER_SET_SIZE * set_index + local_index`, meaning that you are ignoring all holes in - /// the first `set_index` sets. - /// - /// Also removes the lock on the balance of the voter. - /// - /// # - /// - O(1). - /// - Two fewer DB entries, one DB change. - /// # - #[pallet::weight(1_250_000_000)] - pub fn retract_voter( - origin: OriginFor, - #[pallet::compact] index: u32, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!(!Self::presentation_active(), Error::::CannotRetractPresenting); - ensure!(>::contains_key(&who), Error::::RetractNonVoter); - let index = index as usize; - let voter = Self::voter_at(index).ok_or(Error::::InvalidRetractionIndex)?; - ensure!(voter == who, Error::::InvalidRetractionIndex); - - Self::remove_voter(&who, index); - T::Currency::unreserve(&who, T::VotingBond::get()); - T::Currency::remove_lock(T::PalletId::get(), &who); - Ok(()) - } - - /// Submit oneself for candidacy. - /// - /// Account must have enough transferrable funds in it to pay the bond. - /// - /// NOTE: if `origin` has already assigned approvals via - /// [`set_approvals`](Self::set_approvals), it will NOT have any usable funds to pass - /// candidacy bond and must first retract. - /// Note that setting approvals will lock the entire balance of the voter until - /// retraction or being reported. - /// - /// # - /// - Independent of input. - /// - Three DB changes. - /// # - #[pallet::weight(2_500_000_000)] - pub fn submit_candidacy( - origin: OriginFor, - #[pallet::compact] slot: u32, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!(!Self::is_a_candidate(&who), Error::::DuplicatedCandidate); - let slot = slot as usize; - let count = Self::candidate_count() as usize; - let candidates = Self::candidates(); - ensure!( - (slot == count && count == candidates.len()) || - (slot < candidates.len() && candidates[slot] == T::AccountId::default()), - Error::::InvalidCandidateSlot, - ); - // NOTE: This must be last as it has side-effects. - T::Currency::reserve(&who, T::CandidacyBond::get()) - .map_err(|_| Error::::InsufficientCandidateFunds)?; - - >::insert(&who, (Self::vote_index(), slot as u32)); - let mut candidates = candidates; - if slot == candidates.len() { - candidates.push(who); - } else { - candidates[slot] = who; - } - >::put(candidates); - CandidateCount::::put(count as u32 + 1); - Ok(()) - } - - /// Claim that `candidate` is one of the top `carry_count + desired_seats` candidates. Only - /// works iff the presentation period is active. `candidate` should have at least collected - /// some non-zero `total` votes and `origin` must have enough funds to pay for a potential - /// slash. - /// - /// # - /// - O(voters) compute. - /// - One DB change. - /// # - #[pallet::weight(10_000_000_000)] - pub fn present_winner( - origin: OriginFor, - candidate: ::Source, - #[pallet::compact] total: BalanceOf, - #[pallet::compact] index: VoteIndex, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - ensure!(!total.is_zero(), Error::::ZeroDeposit); - - let candidate = T::Lookup::lookup(candidate)?; - ensure!(index == Self::vote_index(), Error::::InvalidVoteIndex); - let (_, _, expiring) = - Self::next_finalize().ok_or(Error::::NotPresentationPeriod)?; - let bad_presentation_punishment = - T::PresentSlashPerVoter::get() * BalanceOf::::from(Self::voter_count() as u32); - ensure!( - T::Currency::can_slash(&who, bad_presentation_punishment), - Error::::InsufficientPresenterFunds, - ); - - let mut leaderboard = Self::leaderboard().ok_or(Error::::LeaderboardMustExist)?; - ensure!(total > leaderboard[0].0, Error::::UnworthyCandidate); - - if let Some(p) = Self::members().iter().position(|&(ref c, _)| c == &candidate) { - ensure!(p < expiring.len(), Error::::DuplicatedCandidate); - } - - let voters = Self::all_voters(); - let (registered_since, candidate_index): (VoteIndex, u32) = - Self::candidate_reg_info(&candidate).ok_or(Error::::InvalidCandidate)?; - let actual_total = voters - .iter() - .filter_map(|maybe_voter| maybe_voter.as_ref()) - .filter_map(|voter| match Self::voter_info(voter) { - Some(b) if b.last_active >= registered_since => { - let last_win = b.last_win; - let now = Self::vote_index(); - let stake = b.stake; - let offset = Self::get_offset(stake, now - last_win); - let weight = stake + offset + b.pot; - if Self::approvals_of_at(voter, candidate_index as usize) { - Some(weight) - } else { - None - } - }, - _ => None, - }) - .fold(Zero::zero(), |acc, n| acc + n); - let dupe = leaderboard.iter().find(|&&(_, ref c)| c == &candidate).is_some(); - if total == actual_total && !dupe { - // insert into leaderboard - leaderboard[0] = (total, candidate); - leaderboard.sort_by_key(|&(t, _)| t); - >::put(leaderboard); - Ok(()) - } else { - // we can rest assured it will be Ok since we checked `can_slash` earlier; still - // better safe than sorry. - let imbalance = T::Currency::slash(&who, bad_presentation_punishment).0; - T::BadPresentation::on_unbalanced(imbalance); - Err(if dupe { - Error::::DuplicatedPresentation - } else { - Error::::IncorrectTotal - })? - } - } - - /// Set the desired member count; if lower than the current count, then seats will not be up - /// election when they expire. If more, then a new vote will be started if one is not - /// already in progress. - #[pallet::weight((0, DispatchClass::Operational))] - pub fn set_desired_seats( - origin: OriginFor, - #[pallet::compact] count: u32, - ) -> DispatchResult { - ensure_root(origin)?; - DesiredSeats::::put(count); - Ok(()) - } - - /// Remove a particular member from the set. This is effective immediately. - /// - /// Note: A tally should happen instantly (if not already in a presentation - /// period) to fill the seat if removal means that the desired members are not met. - #[pallet::weight((0, DispatchClass::Operational))] - pub fn remove_member( - origin: OriginFor, - who: ::Source, - ) -> DispatchResult { - ensure_root(origin)?; - let who = T::Lookup::lookup(who)?; - let new_set: Vec<(T::AccountId, T::BlockNumber)> = - Self::members().into_iter().filter(|i| i.0 != who).collect(); - >::put(&new_set); - let new_set = new_set.into_iter().map(|x| x.0).collect::>(); - T::ChangeMembers::change_members(&[], &[who], new_set); - Ok(()) - } - - /// Set the presentation duration. If there is currently a vote being presented for, will - /// invoke `finalize_vote`. - #[pallet::weight((0, DispatchClass::Operational))] - pub fn set_presentation_duration( - origin: OriginFor, - #[pallet::compact] count: T::BlockNumber, - ) -> DispatchResult { - ensure_root(origin)?; - >::put(count); - Ok(()) - } - - /// Set the presentation duration. If there is current a vote being presented for, will - /// invoke `finalize_vote`. - #[pallet::weight((0, DispatchClass::Operational))] - pub fn set_term_duration( - origin: OriginFor, - #[pallet::compact] count: T::BlockNumber, - ) -> DispatchResult { - ensure_root(origin)?; - >::put(count); - Ok(()) - } - } -} - -impl Pallet { - // exposed immutables. - - /// True if we're currently in a presentation period. - pub fn presentation_active() -> bool { - >::exists() - } - - /// If `who` a candidate at the moment? - pub fn is_a_candidate(who: &T::AccountId) -> bool { - >::contains_key(who) - } - - /// Iff the member `who` still has a seat at blocknumber `n` returns `true`. - pub fn will_still_be_member_at(who: &T::AccountId, n: T::BlockNumber) -> bool { - Self::members() - .iter() - .find(|&&(ref a, _)| a == who) - .map(|&(_, expires)| expires > n) - .unwrap_or(false) - } - - /// Determine the block that a vote can happen on which is no less than `n`. - pub fn next_vote_from(n: T::BlockNumber) -> T::BlockNumber { - let voting_period = T::VotingPeriod::get(); - (n + voting_period - One::one()) / voting_period * voting_period - } - - /// The block number on which the tally for the next election will happen. `None` only if the - /// desired seats of the set is zero. - pub fn next_tally() -> Option { - let desired_seats = Self::desired_seats(); - if desired_seats == 0 { - None - } else { - let c = Self::members(); - let (next_possible, count, coming) = if let Some((tally_end, comers, leavers)) = - Self::next_finalize() - { - // if there's a tally in progress, then next tally can begin immediately afterwards - (tally_end, c.len() - leavers.len() + comers as usize, comers) - } else { - (>::block_number(), c.len(), 0) - }; - if count < desired_seats as usize { - Some(next_possible) - } else { - // next tally begins once enough members expire to bring members below desired. - if desired_seats <= coming { - // the entire amount of desired seats is less than those new members - we'll - // have to wait until they expire. - Some(next_possible + Self::term_duration()) - } else { - Some(c[c.len() - (desired_seats - coming) as usize].1) - } - } - .map(Self::next_vote_from) - } - } - - // Private - /// Check there's nothing to do this block - fn end_block(block_number: T::BlockNumber) -> DispatchResult { - if (block_number % T::VotingPeriod::get()).is_zero() { - if let Some(number) = Self::next_tally() { - if block_number == number { - Self::start_tally(); - } - } - } - if let Some((number, _, _)) = Self::next_finalize() { - if block_number == number { - Self::finalize_tally()? - } - } - Ok(()) - } - - /// Remove a voter at a specified index from the system. - fn remove_voter(voter: &T::AccountId, index: usize) { - let (set_index, vec_index) = Self::split_index(index, VOTER_SET_SIZE); - let mut set = Self::voters(set_index); - set[vec_index] = None; - >::insert(set_index, set); - VoterCount::::mutate(|c| *c = *c - 1); - Self::remove_all_approvals_of(voter); - >::remove(voter); - } - - /// Actually do the voting. - /// - /// The voter index must be provided as explained in [`voter_at`] function. - fn do_set_approvals( - who: T::AccountId, - votes: Vec, - index: VoteIndex, - hint: SetIndex, - value: BalanceOf, - ) -> DispatchResult { - let candidates_len = ::Candidates::decode_len().unwrap_or(0_usize); - - ensure!(!Self::presentation_active(), Error::::ApprovalPresentation); - ensure!(index == Self::vote_index(), Error::::InvalidVoteIndex); - ensure!(!candidates_len.is_zero(), Error::::ZeroCandidates); - // Prevent a vote from voters that provide a list of votes that exceeds the candidates - // length since otherwise an attacker may be able to submit a very long list of `votes` that - // far exceeds the amount of candidates and waste more computation than a reasonable voting - // bond would cover. - ensure!(candidates_len >= votes.len(), Error::::TooManyVotes); - ensure!(value >= T::MinimumVotingLock::get(), Error::::InsufficientLockedValue); - - // Amount to be locked up. - let mut locked_balance = value.min(T::Currency::total_balance(&who)); - let mut pot_to_set = Zero::zero(); - let hint = hint as usize; - - if let Some(info) = Self::voter_info(&who) { - // already a voter. Index must be valid. No fee. update pot. O(1) - let voter = Self::voter_at(hint).ok_or(Error::::InvalidVoterIndex)?; - ensure!(voter == who, Error::::InvalidVoterIndex); - - // write new accumulated offset. - let last_win = info.last_win; - let now = index; - let offset = Self::get_offset(info.stake, now - last_win); - pot_to_set = info.pot + offset; - } else { - // not yet a voter. Index _could be valid_. Fee might apply. Bond will be reserved O(1). - ensure!( - T::Currency::free_balance(&who) > T::VotingBond::get(), - Error::::InsufficientVoterFunds, - ); - - let (set_index, vec_index) = Self::split_index(hint, VOTER_SET_SIZE); - match Self::cell_status(set_index, vec_index) { - CellStatus::Hole => { - // requested cell was a valid hole. - >::mutate(set_index, |set| set[vec_index] = Some(who.clone())); - }, - CellStatus::Head | CellStatus::Occupied => { - // Either occupied or out-of-range. - let next = Self::next_nonfull_voter_set(); - let set_len = >::decode_len(next).unwrap_or(0_usize); - // Caused a new set to be created. Pay for it. - // This is the last potential error. Writes will begin afterwards. - if set_len == 0 { - let imbalance = T::Currency::withdraw( - &who, - T::VotingFee::get(), - WithdrawReasons::FEE, - ExistenceRequirement::KeepAlive, - )?; - T::BadVoterIndex::on_unbalanced(imbalance); - // NOTE: this is safe since the `withdraw()` will check this. - locked_balance -= T::VotingFee::get(); - } - if set_len + 1 == VOTER_SET_SIZE { - NextVoterSet::::put(next + 1); - } - >::append(next, Some(who.clone())); - }, - } - - T::Currency::reserve(&who, T::VotingBond::get())?; - VoterCount::::mutate(|c| *c = *c + 1); - } - - T::Currency::set_lock(T::PalletId::get(), &who, locked_balance, WithdrawReasons::all()); - - >::insert( - &who, - VoterInfo::> { - last_active: index, - last_win: index, - stake: locked_balance, - pot: pot_to_set, - }, - ); - Self::set_approvals_chunked(&who, votes); - - Ok(()) - } - - /// Close the voting, record the number of seats that are actually up for grabs. - fn start_tally() { - let members = Self::members(); - let desired_seats = Self::desired_seats() as usize; - let number = >::block_number(); - let expiring = members - .iter() - .take_while(|i| i.1 <= number) - .map(|i| i.0.clone()) - .collect::>(); - let retaining_seats = members.len() - expiring.len(); - if retaining_seats < desired_seats { - let empty_seats = desired_seats - retaining_seats; - >::put(( - number + Self::presentation_duration(), - empty_seats as u32, - expiring, - )); - - // initialize leaderboard. - let leaderboard_size = empty_seats + T::CarryCount::get() as usize; - >::put(vec![ - (BalanceOf::::zero(), T::AccountId::default()); - leaderboard_size - ]); - - Self::deposit_event(Event::::TallyStarted(empty_seats as u32)); - } - } - - /// Finalize the vote, removing each of the `removals` and inserting `seats` of the most - /// approved candidates in their place. If the total number of members is less than the desired - /// membership a new vote is started. Clears all presented candidates, returning the bond of the - /// elected ones. - fn finalize_tally() -> DispatchResult { - let (_, coming, expiring): (T::BlockNumber, u32, Vec) = - >::take() - .ok_or("finalize can only be called after a tally is started.")?; - let leaderboard: Vec<(BalanceOf, T::AccountId)> = - >::take().unwrap_or_default(); - let new_expiry = >::block_number() + Self::term_duration(); - - // return bond to winners. - let candidacy_bond = T::CandidacyBond::get(); - let incoming: Vec<_> = leaderboard - .iter() - .rev() - .take_while(|&&(b, _)| !b.is_zero()) - .take(coming as usize) - .map(|(_, a)| a) - .cloned() - .inspect(|a| { - T::Currency::unreserve(a, candidacy_bond); - }) - .collect(); - - // Update last win index for anyone voted for any of the incomings. - incoming.iter().filter_map(|i| Self::candidate_reg_info(i)).for_each(|r| { - let index = r.1 as usize; - Self::all_voters() - .iter() - .filter_map(|mv| mv.as_ref()) - .filter(|v| Self::approvals_of_at(*v, index)) - .for_each(|v| { - >::mutate(v, |a| { - if let Some(activity) = a { - activity.last_win = Self::vote_index() + 1; - } - }) - }); - }); - let members = Self::members(); - let outgoing: Vec<_> = members.iter().take(expiring.len()).map(|a| a.0.clone()).collect(); - - // set the new membership set. - let mut new_set: Vec<_> = members - .into_iter() - .skip(expiring.len()) - .chain(incoming.iter().cloned().map(|a| (a, new_expiry))) - .collect(); - new_set.sort_by_key(|&(_, expiry)| expiry); - >::put(&new_set); - - let new_set = new_set.into_iter().map(|x| x.0).collect::>(); - T::ChangeMembers::change_members(&incoming, &outgoing, new_set); - - // clear all except runners-up from candidate list. - let candidates = Self::candidates(); - let mut new_candidates = vec![T::AccountId::default(); candidates.len()]; // shrink later. - let runners_up = leaderboard - .into_iter() - .rev() - .take_while(|&(b, _)| !b.is_zero()) - .skip(coming as usize) - .filter_map(|(_, a)| Self::candidate_reg_info(&a).map(|i| (a, i.1))); - let mut count = 0u32; - for (address, slot) in runners_up { - new_candidates[slot as usize] = address; - count += 1; - } - for (old, new) in candidates.iter().zip(new_candidates.iter()) { - // candidate is not a runner up. - if old != new { - // removed - kill it - >::remove(old); - - // and candidate is not a winner. - if incoming.iter().find(|e| *e == old).is_none() { - // slash the bond. - let (imbalance, _) = T::Currency::slash_reserved(&old, T::CandidacyBond::get()); - T::LoserCandidate::on_unbalanced(imbalance); - } - } - } - // discard any superfluous slots. - if let Some(last_index) = new_candidates.iter().rposition(|c| *c != T::AccountId::default()) - { - new_candidates.truncate(last_index + 1); - } - - Self::deposit_event(Event::::TallyFinalized(incoming, outgoing)); - - >::put(new_candidates); - CandidateCount::::put(count); - VoteCount::::put(Self::vote_index() + 1); - Ok(()) - } - - /// Get the set and vector index of a global voter index. - /// - /// Note that this function does not take holes into account. - /// See [`voter_at`]. - fn split_index(index: usize, scale: usize) -> (SetIndex, usize) { - let set_index = (index / scale) as u32; - let vec_index = index % scale; - (set_index, vec_index) - } - - /// Return a concatenated vector over all voter sets. - fn all_voters() -> Vec> { - let mut all = >::get(0); - let mut index = 1; - // NOTE: we could also use `Self::next_nonfull_voter_set()` here but that might change based - // on how we do chunking. This is more generic. - loop { - let next_set = >::get(index); - if next_set.is_empty() { - break - } else { - index += 1; - all.extend(next_set); - } - } - all - } - - /// Shorthand for fetching a voter at a specific (global) index. - /// - /// NOTE: this function is used for checking indices. Yet, it does not take holes into account. - /// This means that any account submitting an index at any point in time should submit: - /// `VOTER_SET_SIZE * set_index + local_index`, meaning that you are ignoring all holes in the - /// first `set_index` sets. - fn voter_at(index: usize) -> Option { - let (set_index, vec_index) = Self::split_index(index, VOTER_SET_SIZE); - let set = Self::voters(set_index); - if vec_index < set.len() { - set[vec_index].clone() - } else { - None - } - } - - /// A more sophisticated version of `voter_at`. Will be kept separate as most often it is an - /// overdue compared to `voter_at`. Only used when setting approvals. - fn cell_status(set_index: SetIndex, vec_index: usize) -> CellStatus { - let set = Self::voters(set_index); - if vec_index < set.len() { - if let Some(_) = set[vec_index] { - CellStatus::Occupied - } else { - CellStatus::Hole - } - } else { - CellStatus::Head - } - } - - /// Sets the approval of a voter in a chunked manner. - fn set_approvals_chunked(who: &T::AccountId, approvals: Vec) { - let approvals_flag_vec = Self::bool_to_flag(approvals); - approvals_flag_vec - .chunks(APPROVAL_SET_SIZE) - .enumerate() - .for_each(|(index, slice)| >::insert((&who, index as SetIndex), slice)); - } - - /// shorthand for fetching a specific approval of a voter at a specific (global) index. - /// - /// Using this function to read a vote is preferred as it reads `APPROVAL_SET_SIZE` items of - /// type `ApprovalFlag` from storage at most; not all of them. - /// - /// Note that false is returned in case of no-vote or an explicit `false`. - fn approvals_of_at(who: &T::AccountId, index: usize) -> bool { - let (flag_index, bit) = Self::split_index(index, APPROVAL_FLAG_LEN); - let (set_index, vec_index) = Self::split_index(flag_index as usize, APPROVAL_SET_SIZE); - let set = Self::approvals_of((who.clone(), set_index)); - if vec_index < set.len() { - // This is because bit_at treats numbers in lsb -> msb order. - let reversed_index = set.len() - 1 - vec_index; - Self::bit_at(set[reversed_index], bit) - } else { - false - } - } - - /// Return true of the bit `n` of scalar `x` is set to `1` and false otherwise. - fn bit_at(x: ApprovalFlag, n: usize) -> bool { - if n < APPROVAL_FLAG_LEN { - x & (1 << n) != 0 - } else { - false - } - } - - /// Convert a vec of boolean approval flags to a vec of integers, as denoted by - /// the type `ApprovalFlag`. see `bool_to_flag_should_work` test for examples. - pub fn bool_to_flag(x: Vec) -> Vec { - let mut result: Vec = Vec::with_capacity(x.len() / APPROVAL_FLAG_LEN); - if x.is_empty() { - return result - } - result.push(0); - let mut index = 0; - let mut counter = 0; - loop { - let shl_index = counter % APPROVAL_FLAG_LEN; - result[index] += (if x[counter] { 1 } else { 0 }) << shl_index; - counter += 1; - if counter > x.len() - 1 { - break - } - if counter % APPROVAL_FLAG_LEN == 0 { - result.push(0); - index += 1; - } - } - result - } - - /// Convert a vec of flags (u32) to boolean. - pub fn flag_to_bool(chunk: Vec) -> Vec { - let mut result = Vec::with_capacity(chunk.len()); - if chunk.is_empty() { - return vec![] - } - chunk - .into_iter() - .map(|num| { - (0..APPROVAL_FLAG_LEN).map(|bit| Self::bit_at(num, bit)).collect::>() - }) - .for_each(|c| { - let last_approve = match c.iter().rposition(|n| *n) { - Some(index) => index + 1, - None => 0, - }; - result.extend(c.into_iter().take(last_approve)); - }); - result - } - - /// Return a concatenated vector over all approvals of a voter as boolean. - /// The trailing zeros are removed. - fn all_approvals_of(who: &T::AccountId) -> Vec { - let mut all: Vec = vec![]; - let mut index = 0_u32; - loop { - let chunk = Self::approvals_of((who.clone(), index)); - if chunk.is_empty() { - break - } - all.extend(Self::flag_to_bool(chunk)); - index += 1; - } - all - } - - /// Remove all approvals associated with one account. - fn remove_all_approvals_of(who: &T::AccountId) { - let mut index = 0; - loop { - let set = Self::approvals_of((who.clone(), index)); - if set.len() > 0 { - >::remove((who.clone(), index)); - index += 1; - } else { - break - } - } - } - - /// Calculates the offset value (stored pot) of a stake, based on the distance - /// to the last win_index, `t`. Regardless of the internal implementation, - /// it should always be used with the following structure: - /// - /// Given Stake of voter `V` being `x` and distance to last_win index `t`, the new weight - /// of `V` is `x + get_offset(x, t)`. - /// - /// In other words, this function returns everything extra that should be added - /// to a voter's stake value to get the correct weight. Indeed, zero is - /// returned if `t` is zero. - fn get_offset(stake: BalanceOf, t: VoteIndex) -> BalanceOf { - let decay_ratio: BalanceOf = T::DecayRatio::get().into(); - if t > 150 { - return stake * decay_ratio - } - let mut offset = stake; - let mut r = Zero::zero(); - let decay = decay_ratio + One::one(); - for _ in 0..t { - offset = offset.saturating_sub(offset / decay); - r += offset - } - r - } -} diff --git a/frame/elections/src/mock.rs b/frame/elections/src/mock.rs deleted file mode 100644 index 91318e1e07bcc..0000000000000 --- a/frame/elections/src/mock.rs +++ /dev/null @@ -1,273 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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. - -//! Mock file for election module. - -#![cfg(test)] - -use crate as elections; -use frame_support::{ - assert_ok, parameter_types, - traits::{ChangeMembers, Currency, LockIdentifier}, -}; -use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); -} -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} -impl pallet_balances::Config for Test { - type MaxLocks = (); - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; - type Balance = u64; - type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); -} - -parameter_types! { - pub const CandidacyBond: u64 = 3; - pub const CarryCount: u32 = 2; - pub const InactiveGracePeriod: u32 = 1; - pub const VotingPeriod: u64 = 4; - pub const MinimumVotingLock: u64 = 5; - pub static VotingBond: u64 = 0; - pub static VotingFee: u64 = 0; - pub static PresentSlashPerVoter: u64 = 0; - pub static DecayRatio: u32 = 0; - pub static Members: Vec = vec![]; -} - -pub struct TestChangeMembers; -impl ChangeMembers for TestChangeMembers { - fn change_members_sorted(incoming: &[u64], outgoing: &[u64], new: &[u64]) { - let mut old_plus_incoming = MEMBERS.with(|m| m.borrow().to_vec()); - old_plus_incoming.extend_from_slice(incoming); - old_plus_incoming.sort(); - let mut new_plus_outgoing = new.to_vec(); - new_plus_outgoing.extend_from_slice(outgoing); - new_plus_outgoing.sort(); - assert_eq!(old_plus_incoming, new_plus_outgoing); - - MEMBERS.with(|m| *m.borrow_mut() = new.to_vec()); - } -} - -parameter_types! { - pub const ElectionPalletId: LockIdentifier = *b"py/elect"; -} - -impl elections::Config for Test { - type Event = Event; - type Currency = Balances; - type BadPresentation = (); - type BadReaper = (); - type BadVoterIndex = (); - type LoserCandidate = (); - type ChangeMembers = TestChangeMembers; - type CandidacyBond = CandidacyBond; - type VotingBond = VotingBond; - type VotingFee = VotingFee; - type MinimumVotingLock = MinimumVotingLock; - type PresentSlashPerVoter = PresentSlashPerVoter; - type CarryCount = CarryCount; - type InactiveGracePeriod = InactiveGracePeriod; - type VotingPeriod = VotingPeriod; - type DecayRatio = DecayRatio; - type PalletId = ElectionPalletId; -} - -pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; - -use frame_system as system; -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: system::{Pallet, Call, Event}, - Balances: pallet_balances::{Pallet, Call, Event, Config}, - Elections: elections::{Pallet, Call, Event, Config}, - } -); - -pub struct ExtBuilder { - balance_factor: u64, - decay_ratio: u32, - desired_seats: u32, - voting_fee: u64, - voting_bond: u64, - bad_presentation_punishment: u64, -} - -impl Default for ExtBuilder { - fn default() -> Self { - Self { - balance_factor: 1, - decay_ratio: 24, - desired_seats: 2, - voting_fee: 0, - voting_bond: 0, - bad_presentation_punishment: 1, - } - } -} - -impl ExtBuilder { - pub fn balance_factor(mut self, factor: u64) -> Self { - self.balance_factor = factor; - self - } - pub fn decay_ratio(mut self, ratio: u32) -> Self { - self.decay_ratio = ratio; - self - } - pub fn voting_fee(mut self, fee: u64) -> Self { - self.voting_fee = fee; - self - } - pub fn bad_presentation_punishment(mut self, fee: u64) -> Self { - self.bad_presentation_punishment = fee; - self - } - pub fn voting_bond(mut self, fee: u64) -> Self { - self.voting_bond = fee; - self - } - pub fn desired_seats(mut self, seats: u32) -> Self { - self.desired_seats = seats; - self - } - pub fn build(self) -> sp_io::TestExternalities { - VOTING_BOND.with(|v| *v.borrow_mut() = self.voting_bond); - VOTING_FEE.with(|v| *v.borrow_mut() = self.voting_fee); - PRESENT_SLASH_PER_VOTER.with(|v| *v.borrow_mut() = self.bad_presentation_punishment); - DECAY_RATIO.with(|v| *v.borrow_mut() = self.decay_ratio); - let mut ext: sp_io::TestExternalities = GenesisConfig { - balances: pallet_balances::GenesisConfig:: { - balances: vec![ - (1, 10 * self.balance_factor), - (2, 20 * self.balance_factor), - (3, 30 * self.balance_factor), - (4, 40 * self.balance_factor), - (5, 50 * self.balance_factor), - (6, 60 * self.balance_factor), - ], - }, - elections: elections::GenesisConfig:: { - members: vec![], - desired_seats: self.desired_seats, - presentation_duration: 2, - term_duration: 5, - }, - } - .build_storage() - .unwrap() - .into(); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} - -pub(crate) fn voter_ids() -> Vec { - Elections::all_voters().iter().map(|v| v.unwrap_or(0)).collect::>() -} - -pub(crate) fn vote(i: u64, l: usize) { - let _ = Balances::make_free_balance_be(&i, 20); - assert_ok!(Elections::set_approvals( - Origin::signed(i), - (0..l).map(|_| true).collect::>(), - 0, - 0, - 20, - )); -} - -pub(crate) fn vote_at(i: u64, l: usize, index: elections::VoteIndex) { - let _ = Balances::make_free_balance_be(&i, 20); - assert_ok!(Elections::set_approvals( - Origin::signed(i), - (0..l).map(|_| true).collect::>(), - 0, - index, - 20, - )); -} - -pub(crate) fn create_candidate(i: u64, index: u32) { - let _ = Balances::make_free_balance_be(&i, 20); - assert_ok!(Elections::submit_candidacy(Origin::signed(i), index)); -} - -pub(crate) fn balances(who: &u64) -> (u64, u64) { - (Balances::free_balance(who), Balances::reserved_balance(who)) -} - -pub(crate) fn locks(who: &u64) -> Vec { - Balances::locks(who).iter().map(|l| l.amount).collect::>() -} - -pub(crate) fn new_test_ext_with_candidate_holes() -> sp_io::TestExternalities { - let mut t = ExtBuilder::default().build(); - t.execute_with(|| { - >::put(vec![0, 0, 1]); - elections::CandidateCount::::put(1); - >::insert(1, (0, 2)); - }); - t -} diff --git a/frame/elections/src/tests.rs b/frame/elections/src/tests.rs deleted file mode 100644 index 0df84c6d79baf..0000000000000 --- a/frame/elections/src/tests.rs +++ /dev/null @@ -1,1881 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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. - -//! Tests for election module. - -#![cfg(test)] - -use crate::{mock::*, *}; - -use frame_support::{assert_err, assert_noop, assert_ok}; - -#[test] -fn params_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(Elections::next_vote_from(1), 4); - assert_eq!(Elections::next_vote_from(4), 4); - assert_eq!(Elections::next_vote_from(5), 8); - assert_eq!(Elections::vote_index(), 0); - assert_eq!(Elections::presentation_duration(), 2); - assert_eq!(Elections::term_duration(), 5); - assert_eq!(Elections::desired_seats(), 2); - - assert_eq!(Elections::members(), vec![]); - assert_eq!(Elections::next_tally(), Some(4)); - assert_eq!(Elections::presentation_active(), false); - assert_eq!(Elections::next_finalize(), None); - - assert_eq!(Elections::candidates(), Vec::::new()); - assert_eq!(Elections::is_a_candidate(&1), false); - assert_eq!(Elections::candidate_reg_info(1), None); - - assert_eq!(Elections::voters(0), Vec::>::new()); - assert_eq!(Elections::voter_info(1), None); - assert!(Elections::all_approvals_of(&1).is_empty()); - }); -} - -#[test] -fn chunking_bool_to_flag_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert!(Elections::bool_to_flag(vec![]).is_empty()); - assert_eq!(Elections::bool_to_flag(vec![false]), vec![0]); - assert_eq!(Elections::bool_to_flag(vec![true]), vec![1]); - assert_eq!(Elections::bool_to_flag(vec![true, true, true, true]), vec![15]); - assert_eq!(Elections::bool_to_flag(vec![true, true, true, true, true]), vec![15 + 16]); - - let set_1 = vec![ - true, false, false, false, // 0x1 - false, true, true, true, // 0xE - ]; - assert_eq!(Elections::bool_to_flag(set_1.clone()), vec![0x00_00_00_E1_u32]); - assert_eq!(Elections::flag_to_bool(vec![0x00_00_00_E1_u32]), set_1); - - let set_2 = vec![ - false, false, false, true, // 0x8 - false, true, false, true, // 0xA - ]; - assert_eq!(Elections::bool_to_flag(set_2.clone()), vec![0x00_00_00_A8_u32]); - assert_eq!(Elections::flag_to_bool(vec![0x00_00_00_A8_u32]), set_2); - - let mut rhs = (0..100 / APPROVAL_FLAG_LEN).map(|_| 0xFFFFFFFF_u32).collect::>(); - // NOTE: this might be need change based on `APPROVAL_FLAG_LEN`. - rhs.extend(vec![0x00_00_00_0F]); - assert_eq!(Elections::bool_to_flag((0..100).map(|_| true).collect()), rhs) - }) -} - -#[test] -fn chunking_voter_set_growth_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - - // create 65. 64 (set0) + 1 (set1) - (1..=63).for_each(|i| vote(i, 0)); - assert_eq!(Elections::next_nonfull_voter_set(), 0); - vote(64, 0); - assert_eq!(Elections::next_nonfull_voter_set(), 1); - vote(65, 0); - - let set1 = Elections::voters(0); - let set2 = Elections::voters(1); - - assert_eq!(set1.len(), 64); - assert_eq!(set2.len(), 1); - - assert_eq!(set1[0], Some(1)); - assert_eq!(set1[10], Some(11)); - assert_eq!(set2[0], Some(65)); - }) -} - -#[test] -fn chunking_voter_set_reclaim_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - - (1..=129).for_each(|i| vote(i, 0)); - assert_eq!(Elections::next_nonfull_voter_set(), 2); - - assert_ok!(Elections::retract_voter(Origin::signed(11), 10)); - - assert_ok!(Elections::retract_voter(Origin::signed(66), 65)); - assert_ok!(Elections::retract_voter(Origin::signed(67), 66)); - - // length does not show it but holes do exist. - assert_eq!(Elections::voters(0).len(), 64); - assert_eq!(Elections::voters(1).len(), 64); - assert_eq!(Elections::voters(2).len(), 1); - - assert_eq!(Elections::voters(0)[10], None); - assert_eq!(Elections::voters(1)[1], None); - assert_eq!(Elections::voters(1)[2], None); - // Next set with capacity is 2. - assert_eq!(Elections::next_nonfull_voter_set(), 2); - - // But we can fill a hole. - vote_at(130, 0, 10); - - // Nothing added to set 2. A hole was filled. - assert_eq!(Elections::voters(0).len(), 64); - assert_eq!(Elections::voters(1).len(), 64); - assert_eq!(Elections::voters(2).len(), 1); - - // and the next two (scheduled) to the second set. - assert_eq!(Elections::next_nonfull_voter_set(), 2); - }) -} - -#[test] -fn chunking_approvals_set_growth_should_work() { - ExtBuilder::default().build().execute_with(|| { - // create candidates and voters. - (1..=250).for_each(|i| create_candidate(i, (i - 1) as u32)); - (1..=250).for_each(|i| vote(i, i as usize)); - - // all approvals of should return the exact expected vector. - assert_eq!( - Elections::all_approvals_of(&180), - (0..180).map(|_| true).collect::>() - ); - assert_eq!(Elections::all_approvals_of(&32), (0..32).map(|_| true).collect::>()); - assert_eq!(Elections::all_approvals_of(&8), (0..8).map(|_| true).collect::>()); - assert_eq!(Elections::all_approvals_of(&64), (0..64).map(|_| true).collect::>()); - assert_eq!(Elections::all_approvals_of(&65), (0..65).map(|_| true).collect::>()); - assert_eq!(Elections::all_approvals_of(&63), (0..63).map(|_| true).collect::>()); - - // NOTE: assuming that APPROVAL_SET_SIZE is more or less small-ish. Might fail otherwise. - let full_sets = (180 / APPROVAL_FLAG_LEN) / APPROVAL_SET_SIZE; - let left_over = (180 / APPROVAL_FLAG_LEN) / APPROVAL_SET_SIZE; - let rem = 180 % APPROVAL_FLAG_LEN; - - // grab and check the last full set, if it exists. - if full_sets > 0 { - assert_eq!( - Elections::approvals_of((180, (full_sets - 1) as SetIndex)), - Elections::bool_to_flag( - (0..APPROVAL_SET_SIZE * APPROVAL_FLAG_LEN).map(|_| true).collect::>() - ) - ); - } - - // grab and check the last, half-empty, set. - if left_over > 0 { - assert_eq!( - Elections::approvals_of((180, full_sets as SetIndex)), - Elections::bool_to_flag( - (0..left_over * APPROVAL_FLAG_LEN + rem).map(|_| true).collect::>() - ) - ); - } - }) -} - -#[test] -fn chunking_cell_status_works() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - - (1..=63).for_each(|i| vote(i, 0)); - - assert_ok!(Elections::retract_voter(Origin::signed(11), 10)); - assert_ok!(Elections::retract_voter(Origin::signed(21), 20)); - - assert_eq!(Elections::cell_status(0, 10), CellStatus::Hole); - assert_eq!(Elections::cell_status(0, 0), CellStatus::Occupied); - assert_eq!(Elections::cell_status(0, 20), CellStatus::Hole); - assert_eq!(Elections::cell_status(0, 63), CellStatus::Head); - assert_eq!(Elections::cell_status(1, 0), CellStatus::Head); - assert_eq!(Elections::cell_status(1, 10), CellStatus::Head); - }) -} - -#[test] -fn chunking_voter_index_does_not_take_holes_into_account() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - - // create 65. 64 (set0) + 1 (set1) - (1..=65).for_each(|i| vote(i, 0)); - - // account 65 has global index 65. - assert_eq!(Elections::voter_at(64).unwrap(), 65); - - assert_ok!(Elections::retract_voter(Origin::signed(1), 0)); - assert_ok!(Elections::retract_voter(Origin::signed(2), 1)); - - // still the same. These holes are in some other set. - assert_eq!(Elections::voter_at(64).unwrap(), 65); - // proof: can submit a new approval with the old index. - assert_noop!( - Elections::set_approvals(Origin::signed(65), vec![], 0, 64 - 2, 10), - Error::::InvalidVoterIndex, - ); - assert_ok!(Elections::set_approvals(Origin::signed(65), vec![], 0, 64, 10)); - }) -} - -#[test] -fn chunking_approval_storage_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 1)); - - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true, false], 0, 0, 20)); - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![false, false], 0, 0, 30)); - assert_ok!(Elections::set_approvals(Origin::signed(4), vec![], 0, 0, 40)); - - assert_eq!(Elections::all_approvals_of(&2), vec![true]); - // NOTE: these two are stored in mem differently though. - assert!(Elections::all_approvals_of(&3).is_empty()); - assert!(Elections::all_approvals_of(&4).is_empty()); - - assert_eq!(Elections::approvals_of((3, 0)), vec![0]); - assert!(Elections::approvals_of((4, 0)).is_empty()); - }); -} - -#[test] -fn voting_initial_set_approvals_ignores_voter_index() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - - // Last argument is essentially irrelevant. You might get or miss a tip. - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![], 0, 0, 30)); - assert_ok!(Elections::set_approvals(Origin::signed(4), vec![], 0, 5, 40)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![], 0, 100, 50)); - - // indices are more or less ignored. all is pushed. - assert_eq!(voter_ids(), vec![3, 4, 5]); - }) -} -#[test] -fn voting_bad_approval_index_slashes_voters_and_bond_reduces_stake() { - ExtBuilder::default().voting_fee(5).voting_bond(2).build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - - (1..=63).for_each(|i| vote(i, 0)); - assert_eq!(balances(&1), (13, 2)); - assert_eq!(balances(&10), (18, 2)); - assert_eq!(balances(&60), (18, 2)); - - // still no fee - vote(64, 0); - assert_eq!(balances(&64), (18, 2)); - assert_eq!( - Elections::voter_info(&64).unwrap(), - VoterInfo { last_win: 0, last_active: 0, stake: 20, pot: 0 } - ); - - assert_eq!(Elections::next_nonfull_voter_set(), 1); - - // now we charge the next voter. - vote(65, 0); - assert_eq!(balances(&65), (13, 2)); - assert_eq!( - Elections::voter_info(&65).unwrap(), - VoterInfo { last_win: 0, last_active: 0, stake: 15, pot: 0 } - ); - }); -} - -#[test] -fn voting_subsequent_set_approvals_checks_voter_index() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![], 0, 0, 30)); - assert_ok!(Elections::set_approvals(Origin::signed(4), vec![], 0, 5, 40)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![], 0, 100, 50)); - - // invalid index - assert_noop!( - Elections::set_approvals(Origin::signed(4), vec![true], 0, 5, 40), - Error::::InvalidVoterIndex, - ); - // wrong index - assert_noop!( - Elections::set_approvals(Origin::signed(4), vec![true], 0, 0, 40), - Error::::InvalidVoterIndex, - ); - // correct - assert_ok!(Elections::set_approvals(Origin::signed(4), vec![true], 0, 1, 40)); - }) -} - -#[test] -fn voting_cannot_lock_less_than_limit() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - - assert_noop!( - Elections::set_approvals(Origin::signed(3), vec![], 0, 0, 4), - Error::::InsufficientLockedValue, - ); - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![], 0, 0, 5)); - }); -} - -#[test] -fn voting_locking_more_than_total_balance_is_moot() { - ExtBuilder::default().voting_bond(2).build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - - assert_eq!(balances(&3), (30, 0)); - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![], 0, 0, 35)); - - assert_eq!(balances(&3), (28, 2)); - assert_eq!( - Elections::voter_info(&3).unwrap(), - VoterInfo { last_win: 0, last_active: 0, stake: 30, pot: 0 } - ); - }); -} - -#[test] -fn voting_locking_stake_and_reserving_bond_works() { - ExtBuilder::default().voting_bond(2).build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); - - assert_eq!(balances(&2), (20, 0)); - assert!(locks(&2).is_empty()); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![], 0, 0, 15)); - assert_eq!(balances(&2), (18, 2)); - assert_eq!(locks(&2), vec![15]); - - // deposit a bit more. - let _ = Balances::make_free_balance_be(&2, 100); - - // change vote - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 0, 70)); - assert_eq!(balances(&2), (100, 2)); - assert_eq!(locks(&2), vec![70]); - - assert_ok!(Elections::retract_voter(Origin::signed(2), 0)); - - assert_eq!(balances(&2), (102, 0)); - assert!(locks(&2).is_empty()); - }); -} - -#[test] -fn voting_without_any_candidate_count_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(Elections::candidates().len(), 0); - - assert_noop!( - Elections::set_approvals(Origin::signed(4), vec![], 0, 0, 40), - Error::::ZeroCandidates, - ); - }); -} - -#[test] -fn voting_setting_an_approval_vote_count_more_than_candidate_count_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); - assert_eq!(Elections::candidates().len(), 1); - - assert_noop!( - Elections::set_approvals(Origin::signed(4), vec![true, true], 0, 0, 40), - Error::::TooManyVotes, - ); - }); -} - -#[test] -fn voting_resubmitting_approvals_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(4), vec![true], 0, 0, 40)); - - assert_eq!(Elections::all_approvals_of(&4), vec![true]); - - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 2)); - assert_eq!(Elections::candidates().len(), 3); - assert_ok!(Elections::set_approvals(Origin::signed(4), vec![true, false, true], 0, 0, 40)); - - assert_eq!(Elections::all_approvals_of(&4), vec![true, false, true]); - }); -} - -#[test] -fn voting_retracting_voter_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); - assert_eq!(Elections::candidates().len(), 1); - - assert_ok!(Elections::set_approvals(Origin::signed(1), vec![true], 0, 0, 10)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 1, 20)); - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![true], 0, 2, 30)); - assert_ok!(Elections::set_approvals(Origin::signed(4), vec![true], 0, 3, 40)); - - assert_eq!(voter_ids(), vec![1, 2, 3, 4]); - assert_eq!(Elections::all_approvals_of(&1), vec![true]); - assert_eq!(Elections::all_approvals_of(&2), vec![true]); - assert_eq!(Elections::all_approvals_of(&3), vec![true]); - assert_eq!(Elections::all_approvals_of(&4), vec![true]); - - assert_ok!(Elections::retract_voter(Origin::signed(1), 0)); - - assert_eq!(voter_ids(), vec![0, 2, 3, 4]); - assert_eq!(Elections::all_approvals_of(&1), Vec::::new()); - assert_eq!(Elections::all_approvals_of(&2), vec![true]); - assert_eq!(Elections::all_approvals_of(&3), vec![true]); - assert_eq!(Elections::all_approvals_of(&4), vec![true]); - - assert_ok!(Elections::retract_voter(Origin::signed(2), 1)); - - assert_eq!(voter_ids(), vec![0, 0, 3, 4]); - assert_eq!(Elections::all_approvals_of(&1), Vec::::new()); - assert_eq!(Elections::all_approvals_of(&2), Vec::::new()); - assert_eq!(Elections::all_approvals_of(&3), vec![true]); - assert_eq!(Elections::all_approvals_of(&4), vec![true]); - - assert_ok!(Elections::retract_voter(Origin::signed(3), 2)); - - assert_eq!(voter_ids(), vec![0, 0, 0, 4]); - assert_eq!(Elections::all_approvals_of(&1), Vec::::new()); - assert_eq!(Elections::all_approvals_of(&2), Vec::::new()); - assert_eq!(Elections::all_approvals_of(&3), Vec::::new()); - assert_eq!(Elections::all_approvals_of(&4), vec![true]); - }); -} - -#[test] -fn voting_invalid_retraction_index_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 0)); - - assert_ok!(Elections::set_approvals(Origin::signed(1), vec![true], 0, 0, 10)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 0, 20)); - assert_eq!(voter_ids(), vec![1, 2]); - assert_noop!( - Elections::retract_voter(Origin::signed(1), 1), - Error::::InvalidRetractionIndex - ); - }); -} - -#[test] -fn voting_overflow_retraction_index_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 0)); - - assert_ok!(Elections::set_approvals(Origin::signed(1), vec![true], 0, 0, 10)); - assert_noop!( - Elections::retract_voter(Origin::signed(1), 1), - Error::::InvalidRetractionIndex - ); - }); -} - -#[test] -fn voting_non_voter_retraction_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 0)); - - assert_ok!(Elections::set_approvals(Origin::signed(1), vec![true], 0, 0, 10)); - assert_noop!( - Elections::retract_voter(Origin::signed(2), 0), - Error::::RetractNonVoter - ); - }); -} - -#[test] -fn retracting_inactive_voter_should_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 0, 20)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 2, 20, 0)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(8); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![true], 1, 0, 50)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(10); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 1)); - assert_ok!(Elections::end_block(System::block_number())); - - assert_ok!(Elections::reap_inactive_voter( - Origin::signed(5), - (voter_ids().iter().position(|&i| i == 5).unwrap() as u32).into(), - 2, - (voter_ids().iter().position(|&i| i == 2).unwrap() as u32).into(), - 2 - )); - - assert_eq!(voter_ids(), vec![0, 5]); - assert_eq!(Elections::all_approvals_of(&2).len(), 0); - assert_eq!(Balances::total_balance(&2), 20); - assert_eq!(Balances::total_balance(&5), 50); - }); -} - -#[test] -fn retracting_inactive_voter_with_other_candidates_in_slots_should_work() { - ExtBuilder::default().voting_bond(2).build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 0, 20)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 2, 20, 0)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(8); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![true], 1, 0, 50)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(10); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 1)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(11); - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); - - assert_ok!(Elections::reap_inactive_voter( - Origin::signed(5), - (voter_ids().iter().position(|&i| i == 5).unwrap() as u32).into(), - 2, - (voter_ids().iter().position(|&i| i == 2).unwrap() as u32).into(), - 2 - )); - - assert_eq!(voter_ids(), vec![0, 5]); - assert_eq!(Elections::all_approvals_of(&2).len(), 0); - }); -} - -#[test] -fn retracting_inactive_voter_with_bad_reporter_index_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 0, 20)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 2, 20, 0)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(8); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![true], 1, 0, 50)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(10); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 1)); - assert_ok!(Elections::end_block(System::block_number())); - - assert_noop!( - Elections::reap_inactive_voter( - Origin::signed(2), - 42, - 2, - (voter_ids().iter().position(|&i| i == 2).unwrap() as u32).into(), - 2 - ), - Error::::InvalidReporterIndex - ); - }); -} - -#[test] -fn retracting_inactive_voter_with_bad_target_index_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 0, 20)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 2, 20, 0)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(8); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![true], 1, 0, 50)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(10); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 1)); - assert_ok!(Elections::end_block(System::block_number())); - - assert_noop!( - Elections::reap_inactive_voter( - Origin::signed(2), - (voter_ids().iter().position(|&i| i == 2).unwrap() as u32).into(), - 2, - 42, - 2 - ), - Error::::InvalidTargetIndex - ); - }); -} - -#[test] -fn retracting_active_voter_should_slash_reporter() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(4), 2)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 3)); - assert_ok!(Elections::set_approvals( - Origin::signed(2), - vec![true, false, false, false], - 0, - 0, - 20 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(3), - vec![false, true, false, false], - 0, - 0, - 30 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(4), - vec![false, false, true, false], - 0, - 0, - 40 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, false, false, true], - 0, - 0, - 50 - )); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 2, 20, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 3, 30, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 4, 40, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 0)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(8); - assert_ok!(Elections::set_desired_seats(Origin::root(), 3)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(10); - assert_ok!(Elections::present_winner( - Origin::signed(4), - 2, - 20 + Elections::get_offset(20, 1), - 1 - )); - assert_ok!(Elections::present_winner( - Origin::signed(4), - 3, - 30 + Elections::get_offset(30, 1), - 1 - )); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::vote_index(), 2); - assert_eq!(::InactiveGracePeriod::get(), 1); - assert_eq!(::VotingPeriod::get(), 4); - assert_eq!( - Elections::voter_info(4), - Some(VoterInfo { last_win: 1, last_active: 0, stake: 40, pot: 0 }) - ); - - assert_ok!(Elections::reap_inactive_voter( - Origin::signed(4), - (voter_ids().iter().position(|&i| i == 4).unwrap() as u32).into(), - 2, - (voter_ids().iter().position(|&i| i == 2).unwrap() as u32).into(), - 2 - )); - - assert_eq!(voter_ids(), vec![2, 3, 0, 5]); - assert_eq!(Elections::all_approvals_of(&4).len(), 0); - assert_eq!(Balances::total_balance(&4), 40); - }); -} - -#[test] -fn retracting_inactive_voter_by_nonvoter_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 0, 20)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 2, 20, 0)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(8); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![true], 1, 0, 50)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(10); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 1)); - assert_ok!(Elections::end_block(System::block_number())); - - assert_noop!( - Elections::reap_inactive_voter( - Origin::signed(4), - 0, - 2, - (voter_ids().iter().position(|&i| i == 2).unwrap() as u32).into(), - 2 - ), - Error::::NotVoter - ); - }); -} - -#[test] -fn candidacy_simple_candidate_submission_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(Elections::candidates(), Vec::::new()); - assert_eq!(Elections::candidate_reg_info(1), None); - assert_eq!(Elections::candidate_reg_info(2), None); - assert_eq!(Elections::is_a_candidate(&1), false); - assert_eq!(Elections::is_a_candidate(&2), false); - - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); - assert_eq!(Elections::candidates(), vec![1]); - assert_eq!(Elections::candidate_reg_info(1), Some((0, 0))); - assert_eq!(Elections::candidate_reg_info(2), None); - assert_eq!(Elections::is_a_candidate(&1), true); - assert_eq!(Elections::is_a_candidate(&2), false); - - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1)); - assert_eq!(Elections::candidates(), vec![1, 2]); - assert_eq!(Elections::candidate_reg_info(1), Some((0, 0))); - assert_eq!(Elections::candidate_reg_info(2), Some((0, 1))); - assert_eq!(Elections::is_a_candidate(&1), true); - assert_eq!(Elections::is_a_candidate(&2), true); - }); -} - -#[test] -fn candidacy_submission_using_free_slot_should_work() { - let mut t = new_test_ext_with_candidate_holes(); - - t.execute_with(|| { - assert_eq!(Elections::candidates(), vec![0, 0, 1]); - - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1)); - assert_eq!(Elections::candidates(), vec![0, 2, 1]); - - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 0)); - assert_eq!(Elections::candidates(), vec![3, 2, 1]); - }); -} - -#[test] -fn candidacy_submission_using_alternative_free_slot_should_work() { - let mut t = new_test_ext_with_candidate_holes(); - - t.execute_with(|| { - assert_eq!(Elections::candidates(), vec![0, 0, 1]); - - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_eq!(Elections::candidates(), vec![2, 0, 1]); - - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 1)); - assert_eq!(Elections::candidates(), vec![2, 3, 1]); - }); -} - -#[test] -fn candidacy_submission_not_using_free_slot_should_not_work() { - let mut t = new_test_ext_with_candidate_holes(); - - t.execute_with(|| { - assert_noop!( - Elections::submit_candidacy(Origin::signed(4), 3), - Error::::InvalidCandidateSlot - ); - }); -} - -#[test] -fn candidacy_bad_candidate_slot_submission_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(Elections::candidates(), Vec::::new()); - assert_noop!( - Elections::submit_candidacy(Origin::signed(1), 1), - Error::::InvalidCandidateSlot - ); - }); -} - -#[test] -fn candidacy_non_free_candidate_slot_submission_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(Elections::candidates(), Vec::::new()); - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); - assert_eq!(Elections::candidates(), vec![1]); - assert_noop!( - Elections::submit_candidacy(Origin::signed(2), 0), - Error::::InvalidCandidateSlot - ); - }); -} - -#[test] -fn candidacy_dupe_candidate_submission_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(Elections::candidates(), Vec::::new()); - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); - assert_eq!(Elections::candidates(), vec![1]); - assert_noop!( - Elections::submit_candidacy(Origin::signed(1), 1), - Error::::DuplicatedCandidate, - ); - }); -} - -#[test] -fn candidacy_poor_candidate_submission_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(Elections::candidates(), Vec::::new()); - assert_noop!( - Elections::submit_candidacy(Origin::signed(7), 0), - Error::::InsufficientCandidateFunds, - ); - }); -} - -#[test] -fn election_voting_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); - - assert_ok!(Elections::set_approvals(Origin::signed(1), vec![true], 0, 0, 10)); - assert_ok!(Elections::set_approvals(Origin::signed(4), vec![true], 0, 1, 40)); - - assert_eq!(Elections::all_approvals_of(&1), vec![true]); - assert_eq!(Elections::all_approvals_of(&4), vec![true]); - assert_eq!(voter_ids(), vec![1, 4]); - - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 2)); - - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![false, true, true], 0, 2, 20)); - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![false, true, true], 0, 3, 30)); - - assert_eq!(Elections::all_approvals_of(&1), vec![true]); - assert_eq!(Elections::all_approvals_of(&4), vec![true]); - assert_eq!(Elections::all_approvals_of(&2), vec![false, true, true]); - assert_eq!(Elections::all_approvals_of(&3), vec![false, true, true]); - - assert_eq!(voter_ids(), vec![1, 4, 2, 3]); - }); -} - -#[test] -fn election_simple_tally_should_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert!(!Elections::presentation_active()); - - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 0, 20)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![false, true], 0, 0, 50)); - assert_eq!(voter_ids(), vec![2, 5]); - assert_eq!(Elections::all_approvals_of(&2), vec![true]); - assert_eq!(Elections::all_approvals_of(&5), vec![false, true]); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert!(Elections::presentation_active()); - assert_eq!(Elections::present_winner(Origin::signed(4), 2, 20, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(4), 5, 50, 0), Ok(())); - assert_eq!(Elections::leaderboard(), Some(vec![(0, 0), (0, 0), (20, 2), (50, 5)])); - assert_ok!(Elections::end_block(System::block_number())); - - assert!(!Elections::presentation_active()); - assert_eq!(Elections::members(), vec![(5, 11), (2, 11)]); - - assert!(!Elections::is_a_candidate(&2)); - assert!(!Elections::is_a_candidate(&5)); - assert_eq!(Elections::vote_index(), 1); - assert_eq!( - Elections::voter_info(2), - Some(VoterInfo { last_win: 1, last_active: 0, stake: 20, pot: 0 }) - ); - assert_eq!( - Elections::voter_info(5), - Some(VoterInfo { last_win: 1, last_active: 0, stake: 50, pot: 0 }) - ); - }); -} - -#[test] -fn election_seats_should_be_released() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true, false], 0, 0, 20)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![false, true], 0, 0, 50)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert!(Elections::presentation_active()); - assert_eq!(Elections::present_winner(Origin::signed(4), 2, 20, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(4), 5, 50, 0), Ok(())); - assert_eq!(Elections::leaderboard(), Some(vec![(0, 0), (0, 0), (20, 2), (50, 5)])); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(5, 11), (2, 11)]); - let mut current = System::block_number(); - let free_block; - loop { - current += 1; - System::set_block_number(current); - assert_ok!(Elections::end_block(System::block_number())); - if Elections::members().len() == 0 { - free_block = current; - break - } - } - // 11 + 2 which is the next voting period. - assert_eq!(free_block, 14); - }); -} - -#[test] -fn election_presentations_with_zero_staked_deposit_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 0, 20)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_noop!( - Elections::present_winner(Origin::signed(4), 2, 0, 0), - Error::::ZeroDeposit, - ); - }); -} - -#[test] -fn election_double_presentations_should_be_punished() { - ExtBuilder::default().build().execute_with(|| { - assert!(Balances::can_slash(&4, 10)); - - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true, false], 0, 0, 20)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![false, true], 0, 0, 50)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 2, 20, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 0)); - assert_eq!( - Elections::present_winner(Origin::signed(4), 5, 50, 0), - Err(Error::::DuplicatedPresentation.into()), - ); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(5, 11), (2, 11)]); - assert_eq!(Balances::total_balance(&4), 38); - }); -} - -#[test] -fn election_presenting_for_double_election_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_eq!(Elections::submit_candidacy(Origin::signed(2), 0), Ok(())); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 0, 0, 20)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 2, 20, 0)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(8); - // NOTE: This is now mandatory to disable the lock - assert_ok!(Elections::retract_voter(Origin::signed(2), 0)); - assert_eq!(Elections::submit_candidacy(Origin::signed(2), 0), Ok(())); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true], 1, 0, 20)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(10); - assert_noop!( - Elections::present_winner(Origin::signed(4), 2, 20, 1), - Error::::DuplicatedCandidate, - ); - }); -} - -#[test] -fn election_presenting_loser_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(6), vec![true], 0, 0, 60)); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![false, true], 0, 0, 20)); - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 2)); - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![false, false, true], 0, 0, 30)); - assert_ok!(Elections::submit_candidacy(Origin::signed(4), 3)); - assert_ok!(Elections::set_approvals( - Origin::signed(4), - vec![false, false, false, true], - 0, - 0, - 40 - )); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 4)); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, false, false, false, true], - 0, - 0, - 50 - )); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 1, 60, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 3, 30, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 4, 40, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 0)); - - assert_eq!(Elections::leaderboard(), Some(vec![(30, 3), (40, 4), (50, 5), (60, 1)])); - - assert_noop!( - Elections::present_winner(Origin::signed(4), 2, 20, 0), - Error::::UnworthyCandidate - ); - }); -} - -#[test] -fn election_presenting_loser_first_should_not_matter() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(6), vec![true], 0, 0, 60)); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![false, true], 0, 0, 20)); - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 2)); - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![false, false, true], 0, 0, 30)); - assert_ok!(Elections::submit_candidacy(Origin::signed(4), 3)); - assert_ok!(Elections::set_approvals( - Origin::signed(4), - vec![false, false, false, true], - 0, - 0, - 40 - )); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 4)); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, false, false, false, true], - 0, - 0, - 50 - )); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 2, 20, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 1, 60, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 3, 30, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 4, 40, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 0)); - - assert_eq!(Elections::leaderboard(), Some(vec![(30, 3), (40, 4), (50, 5), (60, 1)])); - }); -} - -#[test] -fn election_present_outside_of_presentation_period_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert!(!Elections::presentation_active()); - assert_noop!( - Elections::present_winner(Origin::signed(5), 5, 1, 0), - Error::::NotPresentationPeriod, - ); - }); -} - -#[test] -fn election_present_with_invalid_vote_index_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true, false], 0, 0, 20)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![false, true], 0, 0, 50)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_noop!( - Elections::present_winner(Origin::signed(4), 2, 20, 1), - Error::::InvalidVoteIndex - ); - }); -} - -#[test] -fn election_present_when_presenter_is_poor_should_not_work() { - let test_present = |p| { - ExtBuilder::default() - .voting_fee(5) - .voting_bond(2) - .bad_presentation_punishment(p) - .build() - .execute_with(|| { - System::set_block_number(4); - let _ = Balances::make_free_balance_be(&1, 15); - assert!(!Elections::presentation_active()); - - // -3 - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); - assert_eq!(Balances::free_balance(1), 12); - // -2 -5 - assert_ok!(Elections::set_approvals(Origin::signed(1), vec![true], 0, 0, 15)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_eq!(Balances::free_balance(1), 5); - assert_eq!(Balances::reserved_balance(1), 5); - if p > 5 { - assert_noop!( - Elections::present_winner(Origin::signed(1), 1, 10, 0), - Error::::InsufficientPresenterFunds, - ); - } else { - assert_ok!(Elections::present_winner(Origin::signed(1), 1, 10, 0)); - } - }); - }; - test_present(4); - test_present(6); -} - -#[test] -fn election_invalid_present_tally_should_slash() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert!(!Elections::presentation_active()); - assert_eq!(Balances::total_balance(&4), 40); - - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![true, false], 0, 0, 20)); - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![false, true], 0, 0, 50)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_err!( - Elections::present_winner(Origin::signed(4), 2, 80, 0), - Error::::IncorrectTotal - ); - - assert_eq!(Balances::total_balance(&4), 38); - }); -} - -#[test] -fn election_runners_up_should_be_kept() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert!(!Elections::presentation_active()); - - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(6), vec![true], 0, 0, 60)); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![false, true], 0, 0, 20)); - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 2)); - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![false, false, true], 0, 0, 30)); - assert_ok!(Elections::submit_candidacy(Origin::signed(4), 3)); - assert_ok!(Elections::set_approvals( - Origin::signed(4), - vec![false, false, false, true], - 0, - 0, - 40 - )); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 4)); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, false, false, false, true], - 0, - 0, - 50 - )); - - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert!(Elections::presentation_active()); - assert_ok!(Elections::present_winner(Origin::signed(4), 1, 60, 0)); - // leaderboard length is the empty seats plus the carry count (i.e. 5 + 2), where those - // to be carried are the lowest and stored in lowest indices - assert_eq!(Elections::leaderboard(), Some(vec![(0, 0), (0, 0), (0, 0), (60, 1)])); - assert_ok!(Elections::present_winner(Origin::signed(4), 3, 30, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 4, 40, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 0)); - assert_eq!(Elections::leaderboard(), Some(vec![(30, 3), (40, 4), (50, 5), (60, 1)])); - - assert_ok!(Elections::end_block(System::block_number())); - - assert!(!Elections::presentation_active()); - assert_eq!(Elections::members(), vec![(1, 11), (5, 11)]); - - assert!(!Elections::is_a_candidate(&1)); - assert!(!Elections::is_a_candidate(&5)); - assert!(!Elections::is_a_candidate(&2)); - assert!(Elections::is_a_candidate(&3)); - assert!(Elections::is_a_candidate(&4)); - assert_eq!(Elections::vote_index(), 1); - assert_eq!( - Elections::voter_info(2), - Some(VoterInfo { last_win: 0, last_active: 0, stake: 20, pot: 0 }) - ); - assert_eq!( - Elections::voter_info(3), - Some(VoterInfo { last_win: 0, last_active: 0, stake: 30, pot: 0 }) - ); - assert_eq!( - Elections::voter_info(4), - Some(VoterInfo { last_win: 0, last_active: 0, stake: 40, pot: 0 }) - ); - assert_eq!( - Elections::voter_info(5), - Some(VoterInfo { last_win: 1, last_active: 0, stake: 50, pot: 0 }) - ); - assert_eq!( - Elections::voter_info(6), - Some(VoterInfo { last_win: 1, last_active: 0, stake: 60, pot: 0 }) - ); - assert_eq!(Elections::candidate_reg_info(3), Some((0, 2))); - assert_eq!(Elections::candidate_reg_info(4), Some((0, 3))); - }); -} - -#[test] -fn election_second_tally_should_use_runners_up() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(4); - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); - assert_ok!(Elections::set_approvals(Origin::signed(6), vec![true], 0, 0, 60)); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1)); - assert_ok!(Elections::set_approvals(Origin::signed(2), vec![false, true], 0, 0, 20)); - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 2)); - assert_ok!(Elections::set_approvals(Origin::signed(3), vec![false, false, true], 0, 0, 30)); - assert_ok!(Elections::submit_candidacy(Origin::signed(4), 3)); - assert_ok!(Elections::set_approvals( - Origin::signed(4), - vec![false, false, false, true], - 0, - 0, - 40 - )); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 4)); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, false, false, false, true], - 0, - 0, - 50 - )); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert_ok!(Elections::present_winner(Origin::signed(4), 1, 60, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 3, 30, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 4, 40, 0)); - assert_ok!(Elections::present_winner(Origin::signed(4), 5, 50, 0)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(8); - assert_ok!(Elections::set_approvals( - Origin::signed(6), - vec![false, false, true, false], - 1, - 0, - 60 - )); - assert_ok!(Elections::set_desired_seats(Origin::root(), 3)); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(10); - assert_ok!(Elections::present_winner( - Origin::signed(4), - 3, - 30 + Elections::get_offset(30, 1) + 60, - 1 - )); - assert_ok!(Elections::present_winner( - Origin::signed(4), - 4, - 40 + Elections::get_offset(40, 1), - 1 - )); - assert_ok!(Elections::end_block(System::block_number())); - - assert!(!Elections::presentation_active()); - assert_eq!(Elections::members(), vec![(1, 11), (5, 11), (3, 15)]); - - assert!(!Elections::is_a_candidate(&1)); - assert!(!Elections::is_a_candidate(&2)); - assert!(!Elections::is_a_candidate(&3)); - assert!(!Elections::is_a_candidate(&5)); - assert!(Elections::is_a_candidate(&4)); - assert_eq!(Elections::vote_index(), 2); - assert_eq!( - Elections::voter_info(2), - Some(VoterInfo { last_win: 0, last_active: 0, stake: 20, pot: 0 }) - ); - assert_eq!( - Elections::voter_info(3), - Some(VoterInfo { last_win: 2, last_active: 0, stake: 30, pot: 0 }) - ); - assert_eq!( - Elections::voter_info(4), - Some(VoterInfo { last_win: 0, last_active: 0, stake: 40, pot: 0 }) - ); - assert_eq!( - Elections::voter_info(5), - Some(VoterInfo { last_win: 1, last_active: 0, stake: 50, pot: 0 }) - ); - assert_eq!( - Elections::voter_info(6), - Some(VoterInfo { last_win: 2, last_active: 1, stake: 60, pot: 0 }) - ); - - assert_eq!(Elections::candidate_reg_info(4), Some((0, 3))); - }); -} - -#[test] -fn election_loser_candidates_bond_gets_slashed() { - ExtBuilder::default().desired_seats(1).build().execute_with(|| { - System::set_block_number(4); - assert!(!Elections::presentation_active()); - - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 2)); - assert_ok!(Elections::submit_candidacy(Origin::signed(4), 3)); - - assert_eq!(balances(&2), (17, 3)); - - assert_ok!(Elections::set_approvals(Origin::signed(5), vec![true], 0, 0, 50)); - assert_ok!(Elections::set_approvals( - Origin::signed(1), - vec![false, true, true, true], - 0, - 0, - 10 - )); - - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert!(Elections::presentation_active()); - assert_eq!(Elections::present_winner(Origin::signed(4), 4, 10, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(3), 3, 10, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(2), 2, 10, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(1), 1, 50, 0), Ok(())); - - // winner + carry - assert_eq!(Elections::leaderboard(), Some(vec![(10, 3), (10, 4), (50, 1)])); - assert_ok!(Elections::end_block(System::block_number())); - assert!(!Elections::presentation_active()); - assert_eq!(Elections::members(), vec![(1, 11)]); - - // account 2 is not a runner up or in leaderboard. - assert_eq!(balances(&2), (17, 0)); - }); -} - -#[test] -fn pot_accumulating_weight_and_decaying_should_work() { - ExtBuilder::default().balance_factor(10).build().execute_with(|| { - System::set_block_number(4); - assert!(!Elections::presentation_active()); - - assert_ok!(Elections::submit_candidacy(Origin::signed(6), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 2)); - - assert_ok!(Elections::set_approvals( - Origin::signed(6), - vec![true, false, false], - 0, - 0, - 600 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, true, false], - 0, - 0, - 500 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(1), - vec![false, false, true], - 0, - 0, - 100 - )); - - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert!(Elections::presentation_active()); - - assert_eq!(Elections::present_winner(Origin::signed(6), 6, 600, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(5), 5, 500, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(1), 1, 100, 0), Ok(())); - assert_eq!(Elections::leaderboard(), Some(vec![(0, 0), (100, 1), (500, 5), (600, 6)])); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(6, 11), (5, 11)]); - assert_eq!( - Elections::voter_info(6).unwrap(), - VoterInfo { last_win: 1, last_active: 0, stake: 600, pot: 0 }, - ); - assert_eq!( - Elections::voter_info(5).unwrap(), - VoterInfo { last_win: 1, last_active: 0, stake: 500, pot: 0 }, - ); - assert_eq!( - Elections::voter_info(1).unwrap(), - VoterInfo { last_win: 0, last_active: 0, stake: 100, pot: 0 }, - ); - - System::set_block_number(12); - // retract needed to unlock approval funds => submit candidacy again. - assert_ok!(Elections::retract_voter(Origin::signed(6), 0)); - assert_ok!(Elections::retract_voter(Origin::signed(5), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(6), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::set_approvals( - Origin::signed(6), - vec![true, false, false], - 1, - 0, - 600 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, true, false], - 1, - 1, - 500 - )); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(14); - assert!(Elections::presentation_active()); - assert_eq!(Elections::present_winner(Origin::signed(6), 6, 600, 1), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(5), 5, 500, 1), Ok(())); - assert_eq!( - Elections::present_winner(Origin::signed(1), 1, 100 + Elections::get_offset(100, 1), 1), - Ok(()) - ); - assert_eq!(Elections::leaderboard(), Some(vec![(0, 0), (100 + 96, 1), (500, 5), (600, 6)])); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(6, 19), (5, 19)]); - assert_eq!( - Elections::voter_info(6).unwrap(), - VoterInfo { last_win: 2, last_active: 1, stake: 600, pot: 0 } - ); - assert_eq!( - Elections::voter_info(5).unwrap(), - VoterInfo { last_win: 2, last_active: 1, stake: 500, pot: 0 } - ); - assert_eq!( - Elections::voter_info(1).unwrap(), - VoterInfo { last_win: 0, last_active: 0, stake: 100, pot: 0 } - ); - - System::set_block_number(20); - assert_ok!(Elections::retract_voter(Origin::signed(6), 0)); - assert_ok!(Elections::retract_voter(Origin::signed(5), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(6), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::set_approvals( - Origin::signed(6), - vec![true, false, false], - 2, - 0, - 600 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, true, false], - 2, - 1, - 500 - )); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(22); - assert!(Elections::presentation_active()); - assert_eq!(Elections::present_winner(Origin::signed(6), 6, 600, 2), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(5), 5, 500, 2), Ok(())); - assert_eq!( - Elections::present_winner(Origin::signed(1), 1, 100 + Elections::get_offset(100, 2), 2), - Ok(()) - ); - assert_eq!( - Elections::leaderboard(), - Some(vec![(0, 0), (100 + 96 + 93, 1), (500, 5), (600, 6)]) - ); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(6, 27), (5, 27)]); - assert_eq!( - Elections::voter_info(6).unwrap(), - VoterInfo { last_win: 3, last_active: 2, stake: 600, pot: 0 } - ); - assert_eq!( - Elections::voter_info(5).unwrap(), - VoterInfo { last_win: 3, last_active: 2, stake: 500, pot: 0 } - ); - assert_eq!( - Elections::voter_info(1).unwrap(), - VoterInfo { last_win: 0, last_active: 0, stake: 100, pot: 0 } - ); - - System::set_block_number(28); - assert_ok!(Elections::retract_voter(Origin::signed(6), 0)); - assert_ok!(Elections::retract_voter(Origin::signed(5), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(6), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::set_approvals( - Origin::signed(6), - vec![true, false, false], - 3, - 0, - 600 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, true, false], - 3, - 1, - 500 - )); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(30); - assert!(Elections::presentation_active()); - assert_eq!(Elections::present_winner(Origin::signed(6), 6, 600, 3), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(5), 5, 500, 3), Ok(())); - assert_eq!( - Elections::present_winner(Origin::signed(1), 1, 100 + Elections::get_offset(100, 3), 3), - Ok(()) - ); - assert_eq!( - Elections::leaderboard(), - Some(vec![(0, 0), (100 + 96 + 93 + 90, 1), (500, 5), (600, 6)]) - ); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(6, 35), (5, 35)]); - assert_eq!( - Elections::voter_info(6).unwrap(), - VoterInfo { last_win: 4, last_active: 3, stake: 600, pot: 0 } - ); - assert_eq!( - Elections::voter_info(5).unwrap(), - VoterInfo { last_win: 4, last_active: 3, stake: 500, pot: 0 } - ); - assert_eq!( - Elections::voter_info(1).unwrap(), - VoterInfo { last_win: 0, last_active: 0, stake: 100, pot: 0 } - ); - }) -} - -#[test] -fn pot_winning_resets_accumulated_pot() { - ExtBuilder::default().balance_factor(10).build().execute_with(|| { - System::set_block_number(4); - assert!(!Elections::presentation_active()); - - assert_ok!(Elections::submit_candidacy(Origin::signed(6), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(4), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(3), 2)); - assert_ok!(Elections::submit_candidacy(Origin::signed(2), 3)); - - assert_ok!(Elections::set_approvals( - Origin::signed(6), - vec![true, false, false, false], - 0, - 0, - 600 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(4), - vec![false, true, false, false], - 0, - 1, - 400 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(3), - vec![false, false, true, true], - 0, - 2, - 300 - )); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert!(Elections::presentation_active()); - assert_eq!(Elections::present_winner(Origin::signed(6), 6, 600, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(4), 4, 400, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(3), 3, 300, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(2), 2, 300, 0), Ok(())); - assert_eq!(Elections::leaderboard(), Some(vec![(300, 2), (300, 3), (400, 4), (600, 6)])); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(6, 11), (4, 11)]); - - System::set_block_number(12); - assert_ok!(Elections::retract_voter(Origin::signed(6), 0)); - assert_ok!(Elections::retract_voter(Origin::signed(4), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(6), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(4), 1)); - assert_ok!(Elections::set_approvals( - Origin::signed(6), - vec![true, false, false, false], - 1, - 0, - 600 - )); - assert_ok!(Elections::set_approvals( - Origin::signed(4), - vec![false, true, false, false], - 1, - 1, - 400 - )); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(14); - assert!(Elections::presentation_active()); - assert_eq!(Elections::present_winner(Origin::signed(6), 6, 600, 1), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(4), 4, 400, 1), Ok(())); - assert_eq!( - Elections::present_winner(Origin::signed(3), 3, 300 + Elections::get_offset(300, 1), 1), - Ok(()) - ); - assert_eq!( - Elections::present_winner(Origin::signed(2), 2, 300 + Elections::get_offset(300, 1), 1), - Ok(()) - ); - assert_eq!(Elections::leaderboard(), Some(vec![(400, 4), (588, 2), (588, 3), (600, 6)])); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(6, 19), (3, 19)]); - - System::set_block_number(20); - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(22); - // 2 will not get re-elected with 300 + 288, instead just 300. - // because one of 3's candidates (3) won in previous round - // 4 on the other hand will get extra weight since it was unlucky. - assert_eq!(Elections::present_winner(Origin::signed(3), 2, 300, 2), Ok(())); - assert_eq!( - Elections::present_winner(Origin::signed(4), 4, 400 + Elections::get_offset(400, 1), 2), - Ok(()) - ); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(4, 27), (2, 27)]); - }) -} - -#[test] -fn pot_resubmitting_approvals_stores_pot() { - ExtBuilder::default() - .voting_bond(0) - .voting_fee(0) - .balance_factor(10) - .build() - .execute_with(|| { - System::set_block_number(4); - assert!(!Elections::presentation_active()); - - assert_ok!(Elections::submit_candidacy(Origin::signed(6), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(1), 2)); - - assert_ok!(Elections::set_approvals( - Origin::signed(6), - vec![true, false, false], - 0, - 0, - 600 - ),); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, true, false], - 0, - 1, - 500 - ),); - assert_ok!(Elections::set_approvals( - Origin::signed(1), - vec![false, false, true], - 0, - 2, - 100 - ),); - - assert_ok!(Elections::end_block(System::block_number())); - - System::set_block_number(6); - assert!(Elections::presentation_active()); - - assert_eq!(Elections::present_winner(Origin::signed(6), 6, 600, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(5), 5, 500, 0), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(1), 1, 100, 0), Ok(())); - assert_eq!(Elections::leaderboard(), Some(vec![(0, 0), (100, 1), (500, 5), (600, 6)])); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(6, 11), (5, 11)]); - - System::set_block_number(12); - assert_ok!(Elections::retract_voter(Origin::signed(6), 0)); - assert_ok!(Elections::retract_voter(Origin::signed(5), 1)); - assert_ok!(Elections::submit_candidacy(Origin::signed(6), 0)); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 1)); - assert_ok!(Elections::set_approvals( - Origin::signed(6), - vec![true, false, false], - 1, - 0, - 600 - ),); - assert_ok!(Elections::set_approvals( - Origin::signed(5), - vec![false, true, false], - 1, - 1, - 500 - ),); - // give 1 some new high balance - let _ = Balances::make_free_balance_be(&1, 997); - assert_ok!(Elections::set_approvals( - Origin::signed(1), - vec![false, false, true], - 1, - 2, - 1000 - ),); - assert_eq!( - Elections::voter_info(1).unwrap(), - VoterInfo { - stake: 1000, // 997 + 3 which is candidacy bond. - pot: Elections::get_offset(100, 1), - last_active: 1, - last_win: 1, - } - ); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(6, 11), (5, 11)]); - - System::set_block_number(14); - assert!(Elections::presentation_active()); - assert_eq!(Elections::present_winner(Origin::signed(6), 6, 600, 1), Ok(())); - assert_eq!(Elections::present_winner(Origin::signed(5), 5, 500, 1), Ok(())); - assert_eq!( - Elections::present_winner(Origin::signed(1), 1, 1000 + 96 /* pot */, 1), - Ok(()), - ); - assert_eq!(Elections::leaderboard(), Some(vec![(0, 0), (500, 5), (600, 6), (1096, 1)])); - assert_ok!(Elections::end_block(System::block_number())); - - assert_eq!(Elections::members(), vec![(1, 19), (6, 19)]); - }) -} - -#[test] -fn pot_get_offset_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(Elections::get_offset(100, 0), 0); - assert_eq!(Elections::get_offset(100, 1), 96); - assert_eq!(Elections::get_offset(100, 2), 96 + 93); - assert_eq!(Elections::get_offset(100, 3), 96 + 93 + 90); - assert_eq!(Elections::get_offset(100, 4), 96 + 93 + 90 + 87); - // limit - assert_eq!(Elections::get_offset(100, 1000), 100 * 24); - - assert_eq!(Elections::get_offset(50_000_000_000, 0), 0); - assert_eq!(Elections::get_offset(50_000_000_000, 1), 48_000_000_000); - assert_eq!(Elections::get_offset(50_000_000_000, 2), 48_000_000_000 + 46_080_000_000); - assert_eq!( - Elections::get_offset(50_000_000_000, 3), - 48_000_000_000 + 46_080_000_000 + 44_236_800_000 - ); - assert_eq!( - Elections::get_offset(50_000_000_000, 4), - 48_000_000_000 + 46_080_000_000 + 44_236_800_000 + 42_467_328_000 - ); - // limit - assert_eq!(Elections::get_offset(50_000_000_000, 1000), 50_000_000_000 * 24); - }) -} - -#[test] -fn pot_get_offset_with_zero_decay() { - ExtBuilder::default().decay_ratio(0).build().execute_with(|| { - assert_eq!(Elections::get_offset(100, 0), 0); - assert_eq!(Elections::get_offset(100, 1), 0); - assert_eq!(Elections::get_offset(100, 2), 0); - assert_eq!(Elections::get_offset(100, 3), 0); - // limit - assert_eq!(Elections::get_offset(100, 1000), 0); - }) -} diff --git a/frame/example-offchain-worker/Cargo.toml b/frame/example-offchain-worker/Cargo.toml deleted file mode 100644 index 1ccd9f33f0318..0000000000000 --- a/frame/example-offchain-worker/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "pallet-example-offchain-worker" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Unlicense" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "FRAME example pallet for offchain worker" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", path = "../../primitives/keystore", optional = true } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -lite-json = { version = "0.1", default-features = false } -log = { version = "0.4.14", default-features = false } - -[features] -default = ["std"] -std = [ - "codec/std", - "scale-info/std", - "frame-support/std", - "frame-system/std", - "lite-json/std", - "sp-core/std", - "sp-io/std", - "sp-keystore", - "sp-runtime/std", - "sp-std/std", - "log/std", -] -try-runtime = ["frame-support/try-runtime"] diff --git a/frame/example-parallel/Cargo.toml b/frame/example-parallel/Cargo.toml deleted file mode 100644 index 5e0f6d4bc255a..0000000000000 --- a/frame/example-parallel/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "pallet-example-parallel" -version = "3.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Unlicense" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "FRAME example pallet using runtime worker threads" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-tasks = { version = "4.0.0-dev", default-features = false, path = "../../primitives/tasks" } - -[features] -default = ["std"] -std = [ - "codec/std", - "scale-info/std", - "frame-support/std", - "frame-system/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std", - "sp-tasks/std", -] -try-runtime = ["frame-support/try-runtime"] diff --git a/frame/example-parallel/src/lib.rs b/frame/example-parallel/src/lib.rs deleted file mode 100644 index c86cac4295684..0000000000000 --- a/frame/example-parallel/src/lib.rs +++ /dev/null @@ -1,149 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 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. - -//! Parallel tasks example -//! -//! This example pallet parallelizes validation of the enlisted participants -//! (see `enlist_participants` dispatch). - -#![cfg_attr(not(feature = "std"), no_std)] - -use sp_runtime::RuntimeDebug; - -use codec::{Decode, Encode}; -use sp_std::vec::Vec; - -#[cfg(test)] -mod tests; - -pub use pallet::*; - -#[frame_support::pallet] -pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching dispatch call type. - type Call: From>; - } - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - /// A public part of the pallet. - #[pallet::call] - impl Pallet { - /// Get the new event running. - #[pallet::weight(0)] - pub fn run_event(origin: OriginFor, id: Vec) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; - >::kill(); - >::mutate(move |event_id| *event_id = id); - Ok(().into()) - } - - /// Submit list of participants to the current event. - /// - /// The example utilizes parallel execution by checking half of the - /// signatures in spawned task. - #[pallet::weight(0)] - pub fn enlist_participants( - origin: OriginFor, - participants: Vec, - ) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; - - if validate_participants_parallel(&>::get(), &participants[..]) { - for participant in participants { - >::append(participant.account); - } - } - Ok(().into()) - } - } - - /// A vector of current participants - /// - /// To enlist someone to participate, signed payload should be - /// sent to `enlist`. - #[pallet::storage] - #[pallet::getter(fn participants)] - pub(super) type Participants = StorageValue<_, Vec>, ValueQuery>; - - /// Current event id to enlist participants to. - #[pallet::storage] - #[pallet::getter(fn get_current_event_id)] - pub(super) type CurrentEventId = StorageValue<_, Vec, ValueQuery>; -} - -/// Request to enlist participant. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] -pub struct EnlistedParticipant { - pub account: Vec, - pub signature: Vec, -} - -impl EnlistedParticipant { - fn verify(&self, event_id: &[u8]) -> bool { - use sp_core::Public; - use sp_runtime::traits::Verify; - use std::convert::TryFrom; - - match sp_core::sr25519::Signature::try_from(&self.signature[..]) { - Ok(signature) => { - let public = sp_core::sr25519::Public::from_slice(self.account.as_ref()); - signature.verify(event_id, &public) - }, - _ => false, - } - } -} - -fn validate_participants_parallel(event_id: &[u8], participants: &[EnlistedParticipant]) -> bool { - fn spawn_verify(data: Vec) -> Vec { - let stream = &mut &data[..]; - let event_id = Vec::::decode(stream).expect("Failed to decode"); - let participants = Vec::::decode(stream).expect("Failed to decode"); - - for participant in participants { - if !participant.verify(&event_id) { - return false.encode() - } - } - true.encode() - } - - let mut async_payload = Vec::new(); - event_id.encode_to(&mut async_payload); - participants[..participants.len() / 2].encode_to(&mut async_payload); - - let handle = sp_tasks::spawn(spawn_verify, async_payload); - let mut result = true; - - for participant in &participants[participants.len() / 2 + 1..] { - if !participant.verify(event_id) { - result = false; - break - } - } - - bool::decode(&mut &handle.join()[..]).expect("Failed to decode result") && result -} diff --git a/frame/example-parallel/src/tests.rs b/frame/example-parallel/src/tests.rs deleted file mode 100644 index 4c36f0d6eb858..0000000000000 --- a/frame/example-parallel/src/tests.rs +++ /dev/null @@ -1,149 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 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::{self as pallet_example_parallel, *}; - -use frame_support::parameter_types; -use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - Perbill, -}; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Example: pallet_example_parallel::{Pallet, Call, Storage}, - } -); - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const AvailableBlockRatio: Perbill = Perbill::one(); -} - -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type Origin = Origin; - type Call = Call; - type PalletInfo = PalletInfo; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = sp_core::sr25519::Public; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type DbWeight = (); - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} - -parameter_types! { - pub const GracePeriod: u64 = 5; - pub const UnsignedInterval: u64 = 128; - pub const UnsignedPriority: u64 = 1 << 20; -} - -impl Config for Test { - type Call = Call; -} - -#[test] -fn it_can_enlist() { - use sp_core::Pair; - - sp_io::TestExternalities::default().execute_with(|| { - let (pair1, _) = sp_core::sr25519::Pair::generate(); - let (pair2, _) = sp_core::sr25519::Pair::generate(); - - let event_name = b"test"; - - Example::run_event(Origin::signed(Default::default()), event_name.to_vec()) - .expect("Failed to enlist"); - - let participants = vec![ - EnlistedParticipant { - account: pair1.public().to_vec(), - signature: AsRef::<[u8]>::as_ref(&pair1.sign(event_name)).to_vec(), - }, - EnlistedParticipant { - account: pair2.public().to_vec(), - signature: AsRef::<[u8]>::as_ref(&pair2.sign(event_name)).to_vec(), - }, - ]; - - Example::enlist_participants(Origin::signed(Default::default()), participants) - .expect("Failed to enlist"); - - assert_eq!(Example::participants().len(), 2); - }); -} - -#[test] -fn one_wrong_will_not_enlist_anyone() { - use sp_core::Pair; - - sp_io::TestExternalities::default().execute_with(|| { - let (pair1, _) = sp_core::sr25519::Pair::generate(); - let (pair2, _) = sp_core::sr25519::Pair::generate(); - let (pair3, _) = sp_core::sr25519::Pair::generate(); - - let event_name = b"test"; - - Example::run_event(Origin::signed(Default::default()), event_name.to_vec()) - .expect("Failed to enlist"); - - let participants = vec![ - EnlistedParticipant { - account: pair1.public().to_vec(), - signature: AsRef::<[u8]>::as_ref(&pair1.sign(event_name)).to_vec(), - }, - EnlistedParticipant { - account: pair2.public().to_vec(), - signature: AsRef::<[u8]>::as_ref(&pair2.sign(event_name)).to_vec(), - }, - // signing wrong event - EnlistedParticipant { - account: pair3.public().to_vec(), - signature: AsRef::<[u8]>::as_ref(&pair3.sign(&[])).to_vec(), - }, - ]; - - Example::enlist_participants(Origin::signed(Default::default()), participants) - .expect("Failed to enlist"); - - assert_eq!(Example::participants().len(), 0); - }); -} diff --git a/frame/example/Cargo.toml b/frame/example/Cargo.toml deleted file mode 100644 index 58daaf1c75558..0000000000000 --- a/frame/example/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "pallet-example" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Unlicense" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "FRAME example pallet" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.14", default-features = false } - -[dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core", default-features = false } - -[features] -default = ["std"] -std = [ - "codec/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", - "log/std", - "pallet-balances/std", - "scale-info/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std" -] -runtime-benchmarks = ["frame-benchmarking"] -try-runtime = ["frame-support/try-runtime"] diff --git a/frame/example/src/weights.rs b/frame/example/src/weights.rs deleted file mode 100644 index efcfdc6729b53..0000000000000 --- a/frame/example/src/weights.rs +++ /dev/null @@ -1,101 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 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_example -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-03-15, STEPS: `[100, ]`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// ./target/release/substrate -// benchmark -// --chain -// dev -// --execution -// wasm -// --wasm-execution -// compiled -// --pallet -// pallet_example -// --extrinsic -// * -// --steps -// 100 -// --repeat -// 10 -// --raw -// --output -// ./ -// --template -// ./.maintain/frame-weight-template.hbs - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for pallet_example. -pub trait WeightInfo { - fn set_dummy_benchmark(b: u32, ) -> Weight; - fn accumulate_dummy(b: u32, ) -> Weight; - fn sort_vector(x: u32, ) -> Weight; -} - -/// Weights for pallet_example using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - fn set_dummy_benchmark(b: u32, ) -> Weight { - (5_834_000 as Weight) - .saturating_add((24_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn accumulate_dummy(b: u32, ) -> Weight { - (51_353_000 as Weight) - .saturating_add((14_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn sort_vector(x: u32, ) -> Weight { - (2_569_000 as Weight) - // Standard Error: 0 - .saturating_add((4_000 as Weight).saturating_mul(x as Weight)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - fn set_dummy_benchmark(b: u32, ) -> Weight { - (5_834_000 as Weight) - .saturating_add((24_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn accumulate_dummy(b: u32, ) -> Weight { - (51_353_000 as Weight) - .saturating_add((14_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn sort_vector(x: u32, ) -> Weight { - (2_569_000 as Weight) - // Standard Error: 0 - .saturating_add((4_000 as Weight).saturating_mul(x as Weight)) - } -} diff --git a/frame/examples/basic/Cargo.toml b/frame/examples/basic/Cargo.toml new file mode 100644 index 0000000000000..8c69dc6c3e9ce --- /dev/null +++ b/frame/examples/basic/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "pallet-example-basic" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Unlicense" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME example pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } +sp-io = { version = "7.0.0", default-features = false, path = "../../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } + +[dev-dependencies] +sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/example/README.md b/frame/examples/basic/README.md similarity index 94% rename from frame/example/README.md rename to frame/examples/basic/README.md index e06dee78c3f81..358829192f11d 100644 --- a/frame/example/README.md +++ b/frame/examples/basic/README.md @@ -1,11 +1,13 @@ -# Example Pallet +# Basic Example Pallet The Example: A simple example of a FRAME pallet demonstrating concepts, APIs and structures common to most FRAME runtimes. -Run `cargo doc --package pallet-example --open` to view this pallet's documentation. +Run `cargo doc --package pallet-example-basic --open` to view this pallet's documentation. + +**This pallet serves as an example and is not meant to be used in production.** ### Documentation Guidelines: @@ -34,7 +36,7 @@ Run `cargo doc --package pallet-example --open` to view this pallet's documentat ### Documentation Template:
-Copy and paste this template from frame/example/src/lib.rs into file +Copy and paste this template from frame/examples/basic/src/lib.rs into file `frame//src/lib.rs` of your own custom pallet and complete it.

 // Add heading with custom pallet name
@@ -46,9 +48,9 @@ Copy and paste this template from frame/example/src/lib.rs into file
 // Include the following links that shows what trait needs to be implemented to use the pallet
 // and the supported dispatchables that are documented in the Call enum.
 
-- \[`::Config`](https://docs.rs/pallet-example/latest/pallet_example/trait.Config.html)
-- \[`Call`](https://docs.rs/pallet-example/latest/pallet_example/enum.Call.html)
-- \[`Module`](https://docs.rs/pallet-example/latest/pallet_example/struct.Module.html)
+- \[`::Config`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/trait.Config.html)
+- \[`Call`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/enum.Call.html)
+- \[`Module`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/struct.Module.html)
 
 \## Overview
 
diff --git a/frame/example/src/benchmarking.rs b/frame/examples/basic/src/benchmarking.rs
similarity index 55%
rename from frame/example/src/benchmarking.rs
rename to frame/examples/basic/src/benchmarking.rs
index cdf6c152a4880..13f069c23e27b 100644
--- a/frame/example/src/benchmarking.rs
+++ b/frame/examples/basic/src/benchmarking.rs
@@ -1,6 +1,6 @@
 // This file is part of Substrate.
 
-// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
+// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
 // SPDX-License-Identifier: Apache-2.0
 
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,44 +15,45 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! Benchmarking for pallet-example.
+//! Benchmarking for pallet-example-basic.
 
 #![cfg(feature = "runtime-benchmarks")]
 
 use crate::*;
-use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller};
+use frame_benchmarking::{benchmarks, whitelisted_caller};
 use frame_system::RawOrigin;
 
-// To actually run this benchmark on pallet-example, we need to put this pallet into the
+// To actually run this benchmark on pallet-example-basic, we need to put this pallet into the
 //   runtime and compile it with `runtime-benchmarks` feature. The detail procedures are
 //   documented at:
-//   https://substrate.dev/docs/en/knowledgebase/runtime/benchmarking#how-to-benchmark
+//   https://docs.substrate.io/reference/how-to-guides/weights/add-benchmarks/
 //
 // The auto-generated weight estimate of this pallet is copied over to the `weights.rs` file.
 // The exact command of how the estimate generated is printed at the top of the file.
 
 // Details on using the benchmarks macro can be seen at:
-//   https://substrate.dev/rustdocs/v3.0.0/frame_benchmarking/macro.benchmarks.html
+//   https://paritytech.github.io/substrate/master/frame_benchmarking/trait.Benchmarking.html#tymethod.benchmarks
 benchmarks! {
-	// This will measure the execution time of `set_dummy` for b in [1..1000] range.
+	// This will measure the execution time of `set_dummy`.
 	set_dummy_benchmark {
-		// This is the benchmark setup phase
-		let b in 1 .. 1000;
-	}: set_dummy(RawOrigin::Root, b.into()) // The execution phase is just running `set_dummy` extrinsic call
+		// This is the benchmark setup phase.
+		// `set_dummy` is a constant time function, hence we hard-code some random value here.
+		let value = 1000u32.into();
+	}: set_dummy(RawOrigin::Root, value) // The execution phase is just running `set_dummy` extrinsic call
 	verify {
 		// This is the optional benchmark verification phase, asserting certain states.
-		assert_eq!(Pallet::::dummy(), Some(b.into()))
+		assert_eq!(Pallet::::dummy(), Some(value))
 	}
 
-	// This will measure the execution time of `accumulate_dummy` for b in [1..1000] range.
+	// This will measure the execution time of `accumulate_dummy`.
 	// The benchmark execution phase is shorthanded. When the name of the benchmark case is the same
 	// as the extrinsic call. `_(...)` is used to represent the extrinsic name.
 	// The benchmark verification phase is omitted.
 	accumulate_dummy {
-		let b in 1 .. 1000;
+		let value = 1000u32.into();
 		// The caller account is whitelisted for DB reads/write by the benchmarking macro.
 		let caller: T::AccountId = whitelisted_caller();
-	}: _(RawOrigin::Signed(caller), b.into())
+	}: _(RawOrigin::Signed(caller), value)
 
 	// This will measure the execution time of sorting a vector.
 	sort_vector {
@@ -65,12 +66,14 @@ benchmarks! {
 		// The benchmark execution phase could also be a closure with custom code
 		m.sort();
 	}
-}
 
-// This line generates test cases for benchmarking, and could be run by:
-//   `cargo test -p pallet-example --all-features`, you will see an additional line of:
-//   `test benchmarking::benchmark_tests::test_benchmarks ... ok` in the result.
-//
-// The line generates three steps per benchmark, with repeat=1 and the three steps are
-//   [low, mid, high] of the range.
-impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test);
+	// This line generates test cases for benchmarking, and could be run by:
+	//   `cargo test -p pallet-example-basic --all-features`, you will see one line per case:
+	//   `test benchmarking::bench_sort_vector ... ok`
+	//   `test benchmarking::bench_accumulate_dummy ... ok`
+	//   `test benchmarking::bench_set_dummy_benchmark ... ok` in the result.
+	//
+	// The line generates three steps per benchmark, with repeat=1 and the three steps are
+	//   [low, mid, high] of the range.
+	impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test)
+}
diff --git a/frame/example/src/lib.rs b/frame/examples/basic/src/lib.rs
similarity index 92%
rename from frame/example/src/lib.rs
rename to frame/examples/basic/src/lib.rs
index 23c4951c1a603..d5045157dade7 100644
--- a/frame/example/src/lib.rs
+++ b/frame/examples/basic/src/lib.rs
@@ -1,6 +1,6 @@
 // This file is part of Substrate.
 
-// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
+// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
 // SPDX-License-Identifier: Apache-2.0
 
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,15 @@
 // limitations under the License.
 
 //! 
-//! # Example Pallet
+//! # Basic Example Pallet
 //!
 //! 
 //! The Example: A simple example of a FRAME pallet demonstrating
 //! concepts, APIs and structures common to most FRAME runtimes.
 //!
-//! Run `cargo doc --package pallet-example --open` to view this pallet's documentation.
+//! Run `cargo doc --package pallet-example-basic --open` to view this pallet's documentation.
+//!
+//! **This pallet serves as an example and is not meant to be used in production.**
 //!
 //! ### Documentation Guidelines:
 //!
@@ -59,7 +61,7 @@
 //!
 //! ### Documentation Template:
//! -//! Copy and paste this template from frame/example/src/lib.rs into file +//! Copy and paste this template from frame/examples/basic/src/lib.rs into file //! `frame//src/lib.rs` of your own custom pallet and complete it. //!

 //! // Add heading with custom pallet name
@@ -271,9 +273,9 @@
 
 use codec::{Decode, Encode};
 use frame_support::{
-	dispatch::DispatchResult,
+	dispatch::{ClassifyDispatch, DispatchClass, DispatchResult, Pays, PaysFee, WeighData},
 	traits::IsSubType,
-	weights::{ClassifyDispatch, DispatchClass, Pays, PaysFee, WeighData, Weight},
+	weights::Weight,
 };
 use frame_system::ensure_signed;
 use log::info;
@@ -316,8 +318,7 @@ const MILLICENTS: u32 = 1_000_000_000;
 // - assigns a dispatch class `operational` if the argument of the call is more than 1000.
 //
 // More information can be read at:
-//   - https://substrate.dev/docs/en/knowledgebase/learn-substrate/weight
-//   - https://substrate.dev/docs/en/knowledgebase/runtime/fees#default-weight-annotations
+//   - https://docs.substrate.io/main-docs/build/tx-weights-fees/
 //
 // Manually configuring weight is an advanced operation and what you really need may well be
 //   fulfilled by running the benchmarking toolchain. Refer to `benchmarking.rs` file.
@@ -328,7 +329,7 @@ impl WeighData<(&BalanceOf,)> for WeightForSetDum
 		let multiplier = self.0;
 		// *target.0 is the amount passed into the extrinsic
 		let cents = *target.0 / >::from(MILLICENTS);
-		(cents * multiplier).saturated_into::()
+		Weight::from_ref_time((cents * multiplier).saturated_into::())
 	}
 }
 
@@ -369,7 +370,7 @@ pub mod pallet {
 		type MagicNumber: Get;
 
 		/// The overarching event type.
-		type Event: From> + IsType<::Event>;
+		type RuntimeEvent: From> + IsType<::RuntimeEvent>;
 
 		/// Type representing the weight of this pallet
 		type WeightInfo: WeightInfo;
@@ -391,7 +392,7 @@ pub mod pallet {
 		fn on_initialize(_n: T::BlockNumber) -> Weight {
 			// Anything that needs to be done at the start of the block.
 			// We don't do anything here.
-			0
+			Weight::zero()
 		}
 
 		// `on_finalize` is executed at the end of block after all extrinsic are dispatched.
@@ -487,16 +488,18 @@ pub mod pallet {
 		// the chain in a moderate rate.
 		//
 		// The parenthesized value of the `#[pallet::weight(..)]` attribute can be any type that
-		// implements a set of traits, namely [`WeighData`] and [`ClassifyDispatch`].
-		// The former conveys the weight (a numeric representation of pure execution time and
-		// difficulty) of the transaction and the latter demonstrates the [`DispatchClass`] of the
-		// call. A higher weight means a larger transaction (less of which can be placed in a
-		// single block).
+		// implements a set of traits, namely [`WeighData`], [`ClassifyDispatch`], and
+		// [`PaysFee`]. The first conveys the weight (a numeric representation of pure
+		// execution time and difficulty) of the transaction and the second demonstrates the
+		// [`DispatchClass`] of the call, the third gives whereas extrinsic must pay fees or not.
+		// A higher weight means a larger transaction (less of which can be placed in a single
+		// block).
 		//
 		// The weight for this extrinsic we rely on the auto-generated `WeightInfo` from the
 		// benchmark toolchain.
+		#[pallet::call_index(0)]
 		#[pallet::weight(
-			::WeightInfo::accumulate_dummy((*increase_by).saturated_into())
+			::WeightInfo::accumulate_dummy()
 		)]
 		pub fn accumulate_dummy(origin: OriginFor, increase_by: T::Balance) -> DispatchResult {
 			// This is a public call, so we ensure that the origin is some signed account.
@@ -523,7 +526,7 @@ pub mod pallet {
 			});
 
 			// Let's deposit an event to let the outside world know this happened.
-			Self::deposit_event(Event::AccumulateDummy(increase_by));
+			Self::deposit_event(Event::AccumulateDummy { balance: increase_by });
 
 			// All good, no refund.
 			Ok(())
@@ -539,6 +542,7 @@ pub mod pallet {
 		//
 		// The weight for this extrinsic we use our own weight object `WeightForSetDummy` to
 		// determine its weight
+		#[pallet::call_index(1)]
 		#[pallet::weight(WeightForSetDummy::(>::from(100u32)))]
 		pub fn set_dummy(
 			origin: OriginFor,
@@ -548,13 +552,14 @@ pub mod pallet {
 
 			// Print out log or debug message in the console via log::{error, warn, info, debug,
 			// trace}, accepting format strings similar to `println!`.
-			// https://substrate.dev/rustdocs/v3.0.0/log/index.html
+			// https://paritytech.github.io/substrate/master/sp_io/logging/fn.log.html
+			// https://paritytech.github.io/substrate/master/frame_support/constant.LOG_TARGET.html
 			info!("New value is now: {:?}", new_value);
 
 			// Put the new value into storage.
 			>::put(new_value);
 
-			Self::deposit_event(Event::SetDummy(new_value));
+			Self::deposit_event(Event::SetDummy { balance: new_value });
 
 			// All good, no refund.
 			Ok(())
@@ -571,9 +576,16 @@ pub mod pallet {
 	pub enum Event {
 		// Just a normal `enum`, here's a dummy event to ensure it compiles.
 		/// Dummy event, just here so there's a generic type that's used.
-		AccumulateDummy(BalanceOf),
-		SetDummy(BalanceOf),
-		SetBar(T::AccountId, BalanceOf),
+		AccumulateDummy {
+			balance: BalanceOf,
+		},
+		SetDummy {
+			balance: BalanceOf,
+		},
+		SetBar {
+			account: T::AccountId,
+			balance: BalanceOf,
+		},
 	}
 
 	// pallet::storage attributes allow for type-safe usage of the Substrate storage database,
@@ -645,7 +657,7 @@ pub mod pallet {
 impl Pallet {
 	// Add public immutables and private mutables.
 	#[allow(dead_code)]
-	fn accumulate_foo(origin: T::Origin, increase_by: T::Balance) -> DispatchResult {
+	fn accumulate_foo(origin: T::RuntimeOrigin, increase_by: T::Balance) -> DispatchResult {
 		let _sender = ensure_signed(origin)?;
 
 		let prev = >::get();
@@ -709,11 +721,11 @@ impl sp_std::fmt::Debug for WatchDummy {
 
 impl SignedExtension for WatchDummy
 where
-	::Call: IsSubType>,
+	::RuntimeCall: IsSubType>,
 {
 	const IDENTIFIER: &'static str = "WatchDummy";
 	type AccountId = T::AccountId;
-	type Call = ::Call;
+	type Call = ::RuntimeCall;
 	type AdditionalSigned = ();
 	type Pre = ();
 
@@ -721,6 +733,16 @@ where
 		Ok(())
 	}
 
+	fn pre_dispatch(
+		self,
+		who: &Self::AccountId,
+		call: &Self::Call,
+		info: &DispatchInfoOf,
+		len: usize,
+	) -> Result {
+		self.validate(who, call, info, len).map(|_| ())
+	}
+
 	fn validate(
 		&self,
 		_who: &Self::AccountId,
@@ -738,8 +760,8 @@ where
 			Some(Call::set_dummy { .. }) => {
 				sp_runtime::print("set_dummy was received.");
 
-				let mut valid_tx = ValidTransaction::default();
-				valid_tx.priority = Bounded::max_value();
+				let valid_tx =
+					ValidTransaction { priority: Bounded::max_value(), ..Default::default() };
 				Ok(valid_tx)
 			},
 			_ => Ok(Default::default()),
diff --git a/frame/example/src/tests.rs b/frame/examples/basic/src/tests.rs
similarity index 74%
rename from frame/example/src/tests.rs
rename to frame/examples/basic/src/tests.rs
index 4c2274572db81..97fbddfbc41e0 100644
--- a/frame/example/src/tests.rs
+++ b/frame/examples/basic/src/tests.rs
@@ -1,6 +1,6 @@
 // This file is part of Substrate.
 
-// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
+// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
 // SPDX-License-Identifier: Apache-2.0
 
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,13 +15,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! Tests for pallet-example.
+//! Tests for pallet-example-basic.
 
 use crate::*;
 use frame_support::{
-	assert_ok, parameter_types,
-	traits::OnInitialize,
-	weights::{DispatchInfo, GetDispatchInfo},
+	assert_ok,
+	dispatch::{DispatchInfo, GetDispatchInfo},
+	parameter_types,
+	traits::{ConstU64, OnInitialize},
 };
 use sp_core::H256;
 // The testing primitives are very useful for avoiding having to work with signatures
@@ -32,7 +33,7 @@ use sp_runtime::{
 	BuildStorage,
 };
 // Reexport crate as its pallet name for construct_runtime.
-use crate as pallet_example;
+use crate as pallet_example_basic;
 
 type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic;
 type Block = frame_system::mocking::MockBlock;
@@ -46,31 +47,30 @@ frame_support::construct_runtime!(
 	{
 		System: frame_system::{Pallet, Call, Config, Storage, Event},
 		Balances: pallet_balances::{Pallet, Call, Storage, Config, Event},
-		Example: pallet_example::{Pallet, Call, Storage, Config, Event},
+		Example: pallet_example_basic::{Pallet, Call, Storage, Config, Event},
 	}
 );
 
 parameter_types! {
-	pub const BlockHashCount: u64 = 250;
 	pub BlockWeights: frame_system::limits::BlockWeights =
-		frame_system::limits::BlockWeights::simple_max(1024);
+		frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024));
 }
 impl frame_system::Config for Test {
 	type BaseCallFilter = frame_support::traits::Everything;
 	type BlockWeights = ();
 	type BlockLength = ();
 	type DbWeight = ();
-	type Origin = Origin;
+	type RuntimeOrigin = RuntimeOrigin;
 	type Index = u64;
 	type BlockNumber = u64;
 	type Hash = H256;
-	type Call = Call;
+	type RuntimeCall = RuntimeCall;
 	type Hashing = BlakeTwo256;
 	type AccountId = u64;
 	type Lookup = IdentityLookup;
 	type Header = Header;
-	type Event = Event;
-	type BlockHashCount = BlockHashCount;
+	type RuntimeEvent = RuntimeEvent;
+	type BlockHashCount = ConstU64<250>;
 	type Version = ();
 	type PalletInfo = PalletInfo;
 	type AccountData = pallet_balances::AccountData;
@@ -79,28 +79,24 @@ impl frame_system::Config for Test {
 	type SystemWeightInfo = ();
 	type SS58Prefix = ();
 	type OnSetCode = ();
+	type MaxConsumers = frame_support::traits::ConstU32<16>;
 }
-parameter_types! {
-	pub const ExistentialDeposit: u64 = 1;
-}
+
 impl pallet_balances::Config for Test {
 	type MaxLocks = ();
 	type MaxReserves = ();
 	type ReserveIdentifier = [u8; 8];
 	type Balance = u64;
 	type DustRemoval = ();
-	type Event = Event;
-	type ExistentialDeposit = ExistentialDeposit;
+	type RuntimeEvent = RuntimeEvent;
+	type ExistentialDeposit = ConstU64<1>;
 	type AccountStore = System;
 	type WeightInfo = ();
 }
 
-parameter_types! {
-	pub const MagicNumber: u64 = 1_000_000_000;
-}
 impl Config for Test {
-	type MagicNumber = MagicNumber;
-	type Event = Event;
+	type MagicNumber = ConstU64<1_000_000_000>;
+	type RuntimeEvent = RuntimeEvent;
 	type WeightInfo = ();
 }
 
@@ -111,7 +107,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
 		// We use default for brevity, but you can configure as desired if needed.
 		system: Default::default(),
 		balances: Default::default(),
-		example: pallet_example::GenesisConfig {
+		example: pallet_example_basic::GenesisConfig {
 			dummy: 42,
 			// we configure the map with (key, value) pairs.
 			bar: vec![(1, 2), (2, 3)],
@@ -132,12 +128,12 @@ fn it_works_for_optional_value() {
 		assert_eq!(Example::dummy(), Some(val1));
 
 		// Check that accumulate works when we have Some value in Dummy already.
-		assert_ok!(Example::accumulate_dummy(Origin::signed(1), val2));
+		assert_ok!(Example::accumulate_dummy(RuntimeOrigin::signed(1), val2));
 		assert_eq!(Example::dummy(), Some(val1 + val2));
 
 		// Check that accumulate works when we Dummy has None in it.
 		>::on_initialize(2);
-		assert_ok!(Example::accumulate_dummy(Origin::signed(1), val1));
+		assert_ok!(Example::accumulate_dummy(RuntimeOrigin::signed(1), val1));
 		assert_eq!(Example::dummy(), Some(val1 + val2 + val1));
 	});
 }
@@ -146,7 +142,7 @@ fn it_works_for_optional_value() {
 fn it_works_for_default_value() {
 	new_test_ext().execute_with(|| {
 		assert_eq!(Example::foo(), 24);
-		assert_ok!(Example::accumulate_foo(Origin::signed(1), 1));
+		assert_ok!(Example::accumulate_foo(RuntimeOrigin::signed(1), 1));
 		assert_eq!(Example::foo(), 25);
 	});
 }
@@ -155,7 +151,7 @@ fn it_works_for_default_value() {
 fn set_dummy_works() {
 	new_test_ext().execute_with(|| {
 		let test_val = 133;
-		assert_ok!(Example::set_dummy(Origin::root(), test_val.into()));
+		assert_ok!(Example::set_dummy(RuntimeOrigin::root(), test_val.into()));
 		assert_eq!(Example::dummy(), Some(test_val));
 	});
 }
@@ -163,7 +159,7 @@ fn set_dummy_works() {
 #[test]
 fn signed_ext_watch_dummy_works() {
 	new_test_ext().execute_with(|| {
-		let call = pallet_example::Call::set_dummy { new_value: 10 }.into();
+		let call = pallet_example_basic::Call::set_dummy { new_value: 10 }.into();
 		let info = DispatchInfo::default();
 
 		assert_eq!(
@@ -192,14 +188,16 @@ fn counted_map_works() {
 #[test]
 fn weights_work() {
 	// must have a defined weight.
-	let default_call = pallet_example::Call::::accumulate_dummy { increase_by: 10 };
+	let default_call = pallet_example_basic::Call::::accumulate_dummy { increase_by: 10 };
 	let info1 = default_call.get_dispatch_info();
 	// aka. `let info =  as GetDispatchInfo>::get_dispatch_info(&default_call);`
-	assert!(info1.weight > 0);
+	// TODO: account for proof size weight
+	assert!(info1.weight.ref_time() > 0);
 
 	// `set_dummy` is simpler than `accumulate_dummy`, and the weight
 	//   should be less.
-	let custom_call = pallet_example::Call::::set_dummy { new_value: 20 };
+	let custom_call = pallet_example_basic::Call::::set_dummy { new_value: 20 };
 	let info2 = custom_call.get_dispatch_info();
-	assert!(info1.weight > info2.weight);
+	// TODO: account for proof size weight
+	assert!(info1.weight.ref_time() > info2.weight.ref_time());
 }
diff --git a/frame/examples/basic/src/weights.rs b/frame/examples/basic/src/weights.rs
new file mode 100644
index 0000000000000..a69f0824eac11
--- /dev/null
+++ b/frame/examples/basic/src/weights.rs
@@ -0,0 +1,95 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2022 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_example_basic
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2022-10-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! HOSTNAME: `Shawns-MacBook-Pro.local`, CPU: ``
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
+
+// Executed Command:
+// ./target/release/substrate
+// benchmark
+// pallet
+// --chain=dev
+// --execution=wasm
+// --wasm-execution=compiled
+// --pallet=pallet_example_basic
+// --extrinsic=*
+// --steps=50
+// --repeat=20
+// --output=./
+// --template
+// ./.maintain/frame-weight-template.hbs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use sp_std::marker::PhantomData;
+
+/// Weight functions needed for pallet_example_basic.
+pub trait WeightInfo {
+	fn set_dummy_benchmark() -> Weight;
+	fn accumulate_dummy() -> Weight;
+	fn sort_vector(x: u32, ) -> Weight;
+}
+
+/// Weights for pallet_example_basic using the Substrate node and recommended hardware.
+pub struct SubstrateWeight(PhantomData);
+impl WeightInfo for SubstrateWeight {
+	// Storage: BasicExample Dummy (r:0 w:1)
+	fn set_dummy_benchmark() -> Weight {
+		Weight::from_ref_time(19_000_000 as u64)
+			.saturating_add(T::DbWeight::get().writes(1 as u64))
+	}
+	// Storage: BasicExample Dummy (r:1 w:1)
+	fn accumulate_dummy() -> Weight {
+		Weight::from_ref_time(18_000_000 as u64)
+			.saturating_add(T::DbWeight::get().reads(1 as u64))
+			.saturating_add(T::DbWeight::get().writes(1 as u64))
+	}
+	/// The range of component `x` is `[0, 10000]`.
+	fn sort_vector(x: u32, ) -> Weight {
+		Weight::from_ref_time(0 as u64)
+			// Standard Error: 2
+			.saturating_add(Weight::from_ref_time(520 as u64).saturating_mul(x as u64))
+	}
+}
+
+// For backwards compatibility and tests
+impl WeightInfo for () {
+	// Storage: BasicExample Dummy (r:0 w:1)
+	fn set_dummy_benchmark() -> Weight {
+		Weight::from_ref_time(19_000_000 as u64)
+			.saturating_add(RocksDbWeight::get().writes(1 as u64))
+	}
+	// Storage: BasicExample Dummy (r:1 w:1)
+	fn accumulate_dummy() -> Weight {
+		Weight::from_ref_time(18_000_000 as u64)
+			.saturating_add(RocksDbWeight::get().reads(1 as u64))
+			.saturating_add(RocksDbWeight::get().writes(1 as u64))
+	}
+	/// The range of component `x` is `[0, 10000]`.
+	fn sort_vector(x: u32, ) -> Weight {
+		Weight::from_ref_time(0 as u64)
+			// Standard Error: 2
+			.saturating_add(Weight::from_ref_time(520 as u64).saturating_mul(x as u64))
+	}
+}
diff --git a/frame/examples/offchain-worker/Cargo.toml b/frame/examples/offchain-worker/Cargo.toml
new file mode 100644
index 0000000000000..446af8dda9198
--- /dev/null
+++ b/frame/examples/offchain-worker/Cargo.toml
@@ -0,0 +1,43 @@
+[package]
+name = "pallet-example-offchain-worker"
+version = "4.0.0-dev"
+authors = ["Parity Technologies "]
+edition = "2021"
+license = "Unlicense"
+homepage = "https://substrate.io"
+repository = "https://github.com/paritytech/substrate/"
+description = "FRAME example pallet for offchain worker"
+readme = "README.md"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
+lite-json = { version = "0.2.0", default-features = false }
+log = { version = "0.4.17", default-features = false }
+scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
+frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" }
+frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" }
+sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" }
+sp-io = { version = "7.0.0", default-features = false, path = "../../../primitives/io" }
+sp-keystore = { version = "0.13.0", optional = true, path = "../../../primitives/keystore" }
+sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" }
+sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" }
+
+[features]
+default = ["std"]
+std = [
+	"codec/std",
+	"frame-support/std",
+	"frame-system/std",
+	"lite-json/std",
+	"log/std",
+	"scale-info/std",
+	"sp-core/std",
+	"sp-io/std",
+	"sp-keystore",
+	"sp-runtime/std",
+	"sp-std/std",
+]
+try-runtime = ["frame-support/try-runtime"]
diff --git a/frame/example-offchain-worker/README.md b/frame/examples/offchain-worker/README.md
similarity index 87%
rename from frame/example-offchain-worker/README.md
rename to frame/examples/offchain-worker/README.md
index 5299027f39250..587431c92c0ed 100644
--- a/frame/example-offchain-worker/README.md
+++ b/frame/examples/offchain-worker/README.md
@@ -1,5 +1,5 @@
 
-# Offchain Worker Example Module
+# Offchain Worker Example Pallet
 
 The Offchain Worker Example: A simple pallet demonstrating
 concepts, APIs and structures common to most offchain workers.
@@ -11,6 +11,8 @@ documentation.
 - [`Call`](./enum.Call.html)
 - [`Module`](./struct.Module.html)
 
+**This pallet serves as an example showcasing Substrate off-chain worker and is not meant to be
+used in production.**
 
 ## Overview
 
diff --git a/frame/example-offchain-worker/src/lib.rs b/frame/examples/offchain-worker/src/lib.rs
similarity index 95%
rename from frame/example-offchain-worker/src/lib.rs
rename to frame/examples/offchain-worker/src/lib.rs
index 644e1ca299a3c..46ff7725e3b16 100644
--- a/frame/example-offchain-worker/src/lib.rs
+++ b/frame/examples/offchain-worker/src/lib.rs
@@ -1,6 +1,6 @@
 // This file is part of Substrate.
 
-// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd.
 // SPDX-License-Identifier: Apache-2.0
 
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,6 +28,8 @@
 //! - [`Call`]
 //! - [`Pallet`]
 //!
+//! **This pallet serves as an example showcasing Substrate off-chain worker and is not meant to
+//! be used in production.**
 //!
 //! ## Overview
 //!
@@ -40,6 +42,7 @@
 //! Additional logic in OCW is put in place to prevent spamming the network with both signed
 //! and unsigned transactions, and custom `UnsignedValidator` makes sure that there is only
 //! one unsigned transaction floating in the network.
+
 #![cfg_attr(not(feature = "std"), no_std)]
 
 use codec::{Decode, Encode};
@@ -86,10 +89,19 @@ pub mod crypto {
 	use sp_runtime::{
 		app_crypto::{app_crypto, sr25519},
 		traits::Verify,
+		MultiSignature, MultiSigner,
 	};
 	app_crypto!(sr25519, KEY_TYPE);
 
 	pub struct TestAuthId;
+
+	impl frame_system::offchain::AppCrypto for TestAuthId {
+		type RuntimeAppPublic = Public;
+		type GenericSignature = sp_core::sr25519::Signature;
+		type GenericPublic = sp_core::sr25519::Public;
+	}
+
+	// implemented for mock runtime in test
 	impl frame_system::offchain::AppCrypto<::Signer, Sr25519Signature>
 		for TestAuthId
 	{
@@ -114,10 +126,7 @@ pub mod pallet {
 		type AuthorityId: AppCrypto;
 
 		/// The overarching event type.
-		type Event: From> + IsType<::Event>;
-
-		/// The overarching dispatch call type.
-		type Call: From>;
+		type RuntimeEvent: From> + IsType<::RuntimeEvent>;
 
 		// Configuration parameters
 
@@ -142,6 +151,10 @@ pub mod pallet {
 		/// multiple pallets send unsigned transactions.
 		#[pallet::constant]
 		type UnsignedPriority: Get;
+
+		/// Maximum number of prices.
+		#[pallet::constant]
+		type MaxPrices: Get;
 	}
 
 	#[pallet::pallet]
@@ -216,12 +229,13 @@ pub mod pallet {
 		/// working and receives (and provides) meaningful data.
 		/// This example is not focused on correctness of the oracle itself, but rather its
 		/// purpose is to showcase offchain worker capabilities.
+		#[pallet::call_index(0)]
 		#[pallet::weight(0)]
 		pub fn submit_price(origin: OriginFor, price: u32) -> DispatchResultWithPostInfo {
 			// Retrieve sender of the transaction.
 			let who = ensure_signed(origin)?;
 			// Add the price to the on-chain list.
-			Self::add_price(who, price);
+			Self::add_price(Some(who), price);
 			Ok(().into())
 		}
 
@@ -241,6 +255,7 @@ pub mod pallet {
 		///
 		/// This example is not focused on correctness of the oracle itself, but rather its
 		/// purpose is to showcase offchain worker capabilities.
+		#[pallet::call_index(1)]
 		#[pallet::weight(0)]
 		pub fn submit_price_unsigned(
 			origin: OriginFor,
@@ -250,13 +265,14 @@ pub mod pallet {
 			// This ensures that the function can only be called via unsigned transaction.
 			ensure_none(origin)?;
 			// Add the price to the on-chain list, but mark it as coming from an empty address.
-			Self::add_price(Default::default(), price);
+			Self::add_price(None, price);
 			// now increment the block number at which we expect next unsigned transaction.
 			let current_block = >::block_number();
 			>::put(current_block + T::UnsignedInterval::get());
 			Ok(().into())
 		}
 
+		#[pallet::call_index(2)]
 		#[pallet::weight(0)]
 		pub fn submit_price_unsigned_with_signed_payload(
 			origin: OriginFor,
@@ -266,7 +282,7 @@ pub mod pallet {
 			// This ensures that the function can only be called via unsigned transaction.
 			ensure_none(origin)?;
 			// Add the price to the on-chain list, but mark it as coming from an empty address.
-			Self::add_price(Default::default(), price_payload.price);
+			Self::add_price(None, price_payload.price);
 			// now increment the block number at which we expect next unsigned transaction.
 			let current_block = >::block_number();
 			>::put(current_block + T::UnsignedInterval::get());
@@ -279,8 +295,7 @@ pub mod pallet {
 	#[pallet::generate_deposit(pub(super) fn deposit_event)]
 	pub enum Event {
 		/// Event generated when new price is accepted to contribute to the average.
-		/// \[price, who\]
-		NewPrice(u32, T::AccountId),
+		NewPrice { price: u32, maybe_who: Option },
 	}
 
 	#[pallet::validate_unsigned]
@@ -318,7 +333,7 @@ pub mod pallet {
 	/// This is used to calculate average price, should have bounded size.
 	#[pallet::storage]
 	#[pallet::getter(fn prices)]
-	pub(super) type Prices = StorageValue<_, Vec, ValueQuery>;
+	pub(super) type Prices = StorageValue<_, BoundedVec, ValueQuery>;
 
 	/// Defines the block when next unsigned transaction will be accepted.
 	///
@@ -431,7 +446,7 @@ impl Pallet {
 		if !signer.can_sign() {
 			return Err(
 				"No local accounts available. Consider adding one via `author_insertKey` RPC.",
-			)?
+			)
 		}
 		// Make an external HTTP request to fetch the current price.
 		// Note this call will block until response is received.
@@ -625,20 +640,16 @@ impl Pallet {
 			_ => return None,
 		};
 
-		let exp = price.fraction_length.checked_sub(2).unwrap_or(0);
+		let exp = price.fraction_length.saturating_sub(2);
 		Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32)
 	}
 
 	/// Add new price to the list.
-	fn add_price(who: T::AccountId, price: u32) {
+	fn add_price(maybe_who: Option, price: u32) {
 		log::info!("Adding to the average: {}", price);
 		>::mutate(|prices| {
-			const MAX_LEN: usize = 64;
-
-			if prices.len() < MAX_LEN {
-				prices.push(price);
-			} else {
-				prices[price as usize % MAX_LEN] = price;
+			if prices.try_push(price).is_err() {
+				prices[(price % T::MaxPrices::get()) as usize] = price;
 			}
 		});
 
@@ -646,7 +657,7 @@ impl Pallet {
 			.expect("The average is not empty, because it was just mutated; qed");
 		log::info!("Current average price is: {}", average);
 		// here we are raising the NewPrice event
-		Self::deposit_event(Event::NewPrice(price, who));
+		Self::deposit_event(Event::NewPrice { price, maybe_who });
 	}
 
 	/// Calculate current average price.
diff --git a/frame/example-offchain-worker/src/tests.rs b/frame/examples/offchain-worker/src/tests.rs
similarity index 86%
rename from frame/example-offchain-worker/src/tests.rs
rename to frame/examples/offchain-worker/src/tests.rs
index 1dde8a1df60c8..72c001fd6e6cc 100644
--- a/frame/example-offchain-worker/src/tests.rs
+++ b/frame/examples/offchain-worker/src/tests.rs
@@ -1,6 +1,6 @@
 // This file is part of Substrate.
 
-// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd.
 // SPDX-License-Identifier: Apache-2.0
 
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,10 @@
 use crate as example_offchain_worker;
 use crate::*;
 use codec::Decode;
-use frame_support::{assert_ok, parameter_types};
+use frame_support::{
+	assert_ok, parameter_types,
+	traits::{ConstU32, ConstU64},
+};
 use sp_core::{
 	offchain::{testing, OffchainWorkerExt, TransactionPoolExt},
 	sr25519::Signature,
@@ -49,17 +52,16 @@ frame_support::construct_runtime!(
 );
 
 parameter_types! {
-	pub const BlockHashCount: u64 = 250;
 	pub BlockWeights: frame_system::limits::BlockWeights =
-		frame_system::limits::BlockWeights::simple_max(1024);
+		frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024));
 }
 impl frame_system::Config for Test {
 	type BaseCallFilter = frame_support::traits::Everything;
 	type BlockWeights = ();
 	type BlockLength = ();
 	type DbWeight = ();
-	type Origin = Origin;
-	type Call = Call;
+	type RuntimeOrigin = RuntimeOrigin;
+	type RuntimeCall = RuntimeCall;
 	type Index = u64;
 	type BlockNumber = u64;
 	type Hash = H256;
@@ -67,8 +69,8 @@ impl frame_system::Config for Test {
 	type AccountId = sp_core::sr25519::Public;
 	type Lookup = IdentityLookup;
 	type Header = Header;
-	type Event = Event;
-	type BlockHashCount = BlockHashCount;
+	type RuntimeEvent = RuntimeEvent;
+	type BlockHashCount = ConstU64<250>;
 	type Version = ();
 	type PalletInfo = PalletInfo;
 	type AccountData = ();
@@ -77,9 +79,10 @@ impl frame_system::Config for Test {
 	type SystemWeightInfo = ();
 	type SS58Prefix = ();
 	type OnSetCode = ();
+	type MaxConsumers = ConstU32<16>;
 }
 
-type Extrinsic = TestXt;
+type Extrinsic = TestXt;
 type AccountId = <::Signer as IdentifyAccount>::AccountId;
 
 impl frame_system::offchain::SigningTypes for Test {
@@ -89,39 +92,41 @@ impl frame_system::offchain::SigningTypes for Test {
 
 impl frame_system::offchain::SendTransactionTypes for Test
 where
-	Call: From,
+	RuntimeCall: From,
 {
-	type OverarchingCall = Call;
+	type OverarchingCall = RuntimeCall;
 	type Extrinsic = Extrinsic;
 }
 
 impl frame_system::offchain::CreateSignedTransaction for Test
 where
-	Call: From,
+	RuntimeCall: From,
 {
 	fn create_transaction>(
-		call: Call,
+		call: RuntimeCall,
 		_public: ::Signer,
 		_account: AccountId,
 		nonce: u64,
-	) -> Option<(Call, ::SignaturePayload)> {
+	) -> Option<(RuntimeCall, ::SignaturePayload)> {
 		Some((call, (nonce, ())))
 	}
 }
 
 parameter_types! {
-	pub const GracePeriod: u64 = 5;
-	pub const UnsignedInterval: u64 = 128;
 	pub const UnsignedPriority: u64 = 1 << 20;
 }
 
 impl Config for Test {
-	type Event = Event;
+	type RuntimeEvent = RuntimeEvent;
 	type AuthorityId = crypto::TestAuthId;
-	type Call = Call;
-	type GracePeriod = GracePeriod;
-	type UnsignedInterval = UnsignedInterval;
+	type GracePeriod = ConstU64<5>;
+	type UnsignedInterval = ConstU64<128>;
 	type UnsignedPriority = UnsignedPriority;
+	type MaxPrices = ConstU32<64>;
+}
+
+fn test_pub() -> sp_core::sr25519::Public {
+	sp_core::sr25519::Public::from_raw([1u8; 32])
 }
 
 #[test]
@@ -129,10 +134,10 @@ fn it_aggregates_the_price() {
 	sp_io::TestExternalities::default().execute_with(|| {
 		assert_eq!(Example::average_price(), None);
 
-		assert_ok!(Example::submit_price(Origin::signed(Default::default()), 27));
+		assert_ok!(Example::submit_price(RuntimeOrigin::signed(test_pub()), 27));
 		assert_eq!(Example::average_price(), Some(27));
 
-		assert_ok!(Example::submit_price(Origin::signed(Default::default()), 43));
+		assert_ok!(Example::submit_price(RuntimeOrigin::signed(test_pub()), 43));
 		assert_eq!(Example::average_price(), Some(35));
 	});
 }
@@ -227,7 +232,7 @@ fn should_submit_signed_transaction_on_chain() {
 		assert!(pool_state.read().transactions.is_empty());
 		let tx = Extrinsic::decode(&mut &*tx).unwrap();
 		assert_eq!(tx.signature.unwrap().0, 0);
-		assert_eq!(tx.call, Call::Example(crate::Call::submit_price { price: 15523 }));
+		assert_eq!(tx.call, RuntimeCall::Example(crate::Call::submit_price { price: 15523 }));
 	});
 }
 
@@ -247,10 +252,9 @@ fn should_submit_unsigned_transaction_on_chain_for_any_account() {
 	)
 	.unwrap();
 
-	let public_key = SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID)
+	let public_key = *SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID)
 		.get(0)
-		.unwrap()
-		.clone();
+		.unwrap();
 
 	let mut t = sp_io::TestExternalities::default();
 	t.register_extension(OffchainWorkerExt::new(offchain));
@@ -273,7 +277,7 @@ fn should_submit_unsigned_transaction_on_chain_for_any_account() {
 		let tx = pool_state.write().transactions.pop().unwrap();
 		let tx = Extrinsic::decode(&mut &*tx).unwrap();
 		assert_eq!(tx.signature, None);
-		if let Call::Example(crate::Call::submit_price_unsigned_with_signed_payload {
+		if let RuntimeCall::Example(crate::Call::submit_price_unsigned_with_signed_payload {
 			price_payload: body,
 			signature,
 		}) = tx.call
@@ -307,10 +311,9 @@ fn should_submit_unsigned_transaction_on_chain_for_all_accounts() {
 	)
 	.unwrap();
 
-	let public_key = SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID)
+	let public_key = *SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID)
 		.get(0)
-		.unwrap()
-		.clone();
+		.unwrap();
 
 	let mut t = sp_io::TestExternalities::default();
 	t.register_extension(OffchainWorkerExt::new(offchain));
@@ -333,7 +336,7 @@ fn should_submit_unsigned_transaction_on_chain_for_all_accounts() {
 		let tx = pool_state.write().transactions.pop().unwrap();
 		let tx = Extrinsic::decode(&mut &*tx).unwrap();
 		assert_eq!(tx.signature, None);
-		if let Call::Example(crate::Call::submit_price_unsigned_with_signed_payload {
+		if let RuntimeCall::Example(crate::Call::submit_price_unsigned_with_signed_payload {
 			price_payload: body,
 			signature,
 		}) = tx.call
@@ -375,7 +378,10 @@ fn should_submit_raw_unsigned_transaction_on_chain() {
 		assert_eq!(tx.signature, None);
 		assert_eq!(
 			tx.call,
-			Call::Example(crate::Call::submit_price_unsigned { block_number: 1, price: 15523 })
+			RuntimeCall::Example(crate::Call::submit_price_unsigned {
+				block_number: 1,
+				price: 15523
+			})
 		);
 	});
 }
diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml
index 1abbf50e6a4c4..b3e4247445710 100644
--- a/frame/executive/Cargo.toml
+++ b/frame/executive/Cargo.toml
@@ -2,9 +2,9 @@
 name = "frame-executive"
 version = "4.0.0-dev"
 authors = ["Parity Technologies "]
-edition = "2018"
+edition = "2021"
 license = "Apache-2.0"
-homepage = "https://substrate.dev"
+homepage = "https://substrate.io"
 repository = "https://github.com/paritytech/substrate/"
 description = "FRAME executives engine"
 readme = "README.md"
@@ -13,38 +13,40 @@ readme = "README.md"
 targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
-codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [
+codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [
 	"derive",
 ] }
-scale-info = { version = "1.0", default-features = false, features = ["derive"] }
+scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
 frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
 frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
-sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" }
-sp-tracing = { version = "4.0.0-dev", default-features = false, path = "../../primitives/tracing" }
-sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" }
-sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" }
-sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" }
+frame-try-runtime = { version = "0.10.0-dev", default-features = false, path = "../try-runtime", optional = true }
+sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" }
+sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" }
+sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" }
+sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" }
+sp-tracing = { version = "6.0.0", default-features = false, path = "../../primitives/tracing" }
 
 [dev-dependencies]
-hex-literal = "0.3.1"
-sp-core = { version = "4.0.0-dev", path = "../../primitives/core" }
-sp-io = { version = "4.0.0-dev", path = "../../primitives/io" }
+array-bytes = "4.1"
 pallet-balances = { version = "4.0.0-dev", path = "../balances" }
 pallet-transaction-payment = { version = "4.0.0-dev", path = "../transaction-payment" }
-sp-version = { version = "4.0.0-dev", path = "../../primitives/version" }
+sp-core = { version = "7.0.0", path = "../../primitives/core" }
 sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" }
+sp-io = { version = "7.0.0", path = "../../primitives/io" }
+sp-version = { version = "5.0.0", path = "../../primitives/version" }
 
 [features]
 default = ["std"]
 with-tracing = ["sp-tracing/with-tracing"]
 std = [
 	"codec/std",
-	"scale-info/std",
 	"frame-support/std",
 	"frame-system/std",
+	"scale-info/std",
 	"sp-core/std",
+	"sp-io/std",
 	"sp-runtime/std",
-	"sp-tracing/std",
 	"sp-std/std",
+	"sp-tracing/std",
 ]
-try-runtime = ["frame-support/try-runtime"]
+try-runtime = ["frame-support/try-runtime", "frame-try-runtime/try-runtime" ]
diff --git a/frame/executive/README.md b/frame/executive/README.md
index ae3bbf1a9d994..c14c3912b082d 100644
--- a/frame/executive/README.md
+++ b/frame/executive/README.md
@@ -35,7 +35,13 @@ The default Substrate node template declares the [`Executive`](https://docs.rs/f
 ```rust
 #
 /// Executive: handles dispatch to the various modules.
-pub type Executive = executive::Executive;
+pub type Executive = executive::Executive<
+    Runtime,
+    Block,
+    Context,
+    Runtime,
+    AllPallets,
+>;
 ```
 
 ### Custom `OnRuntimeUpgrade` logic
@@ -50,11 +56,18 @@ struct CustomOnRuntimeUpgrade;
 impl frame_support::traits::OnRuntimeUpgrade for CustomOnRuntimeUpgrade {
     fn on_runtime_upgrade() -> frame_support::weights::Weight {
         // Do whatever you want.
-        0
+        frame_support::weights::Weight::zero()
     }
 }
 
-pub type Executive = executive::Executive;
+pub type Executive = executive::Executive<
+    Runtime,
+    Block,
+    Context,
+    Runtime,
+    AllPallets,
+    CustomOnRuntimeUpgrade,
+>;
 ```
 
 License: Apache-2.0
diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs
index 655a38fe1b540..6f59ac72eb2fd 100644
--- a/frame/executive/src/lib.rs
+++ b/frame/executive/src/lib.rs
@@ -1,6 +1,6 @@
 // This file is part of Substrate.
 
-// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
+// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
 // SPDX-License-Identifier: Apache-2.0
 
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -59,7 +59,7 @@
 //! # type Context = frame_system::ChainContext;
 //! # pub type Block = generic::Block;
 //! # pub type Balances = u64;
-//! # pub type AllPallets = u64;
+//! # pub type AllPalletsWithSystem = u64;
 //! # pub enum Runtime {};
 //! # use sp_runtime::transaction_validity::{
 //! #    TransactionValidity, UnknownTransaction, TransactionSource,
@@ -73,7 +73,7 @@
 //! #     }
 //! # }
 //! /// Executive: handles dispatch to the various modules.
-//! pub type Executive = executive::Executive;
+//! pub type Executive = executive::Executive;
 //! ```
 //!
 //! ### Custom `OnRuntimeUpgrade` logic
@@ -90,7 +90,7 @@
 //! # type Context = frame_system::ChainContext;
 //! # pub type Block = generic::Block;
 //! # pub type Balances = u64;
-//! # pub type AllPallets = u64;
+//! # pub type AllPalletsWithSystem = u64;
 //! # pub enum Runtime {};
 //! # use sp_runtime::transaction_validity::{
 //! #    TransactionValidity, UnknownTransaction, TransactionSource,
@@ -107,29 +107,29 @@
 //! impl frame_support::traits::OnRuntimeUpgrade for CustomOnRuntimeUpgrade {
 //!     fn on_runtime_upgrade() -> frame_support::weights::Weight {
 //!         // Do whatever you want.
-//!         0
+//!         frame_support::weights::Weight::zero()
 //!     }
 //! }
 //!
-//! pub type Executive = executive::Executive;
+//! pub type Executive = executive::Executive;
 //! ```
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
 use codec::{Codec, Encode};
 use frame_support::{
-	dispatch::PostDispatchInfo,
+	dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo},
+	pallet_prelude::InvalidTransaction,
 	traits::{
 		EnsureInherentsAreFirst, ExecuteBlock, OffchainWorker, OnFinalize, OnIdle, OnInitialize,
 		OnRuntimeUpgrade,
 	},
-	weights::{DispatchClass, DispatchInfo, GetDispatchInfo},
+	weights::Weight,
 };
-use frame_system::DigestOf;
 use sp_runtime::{
 	generic::Digest,
 	traits::{
-		self, Applyable, CheckEqual, Checkable, Dispatchable, Header, NumberFor, One, Saturating,
+		self, Applyable, CheckEqual, Checkable, Dispatchable, Header, NumberFor, One,
 		ValidateUnsigned, Zero,
 	},
 	transaction_validity::{TransactionSource, TransactionValidity},
@@ -139,7 +139,7 @@ use sp_std::{marker::PhantomData, prelude::*};
 
 pub type CheckedOf = >::Checked;
 pub type CallOf =  as Applyable>::Call;
-pub type OriginOf =  as Dispatchable>::Origin;
+pub type OriginOf =  as Dispatchable>::RuntimeOrigin;
 
 /// Main entry point for certain runtime actions as e.g. `execute_block`.
 ///
@@ -148,11 +148,26 @@ pub type OriginOf =  as Dispatchable>::Origin;
 /// - `Block`: The block type of the runtime
 /// - `Context`: The context that is used when checking an extrinsic.
 /// - `UnsignedValidator`: The unsigned transaction validator of the runtime.
-/// - `AllPallets`: Tuple that contains all modules. Will be used to call e.g. `on_initialize`.
+/// - `AllPalletsWithSystem`: Tuple that contains all pallets including frame system pallet. Will be
+///   used to call hooks e.g. `on_initialize`.
 /// - `OnRuntimeUpgrade`: Custom logic that should be called after a runtime upgrade. Modules are
-///   already called by `AllPallets`. It will be called before all modules will be called.
-pub struct Executive(
-	PhantomData<(System, Block, Context, UnsignedValidator, AllPallets, OnRuntimeUpgrade)>,
+///   already called by `AllPalletsWithSystem`. It will be called before all modules will be called.
+pub struct Executive<
+	System,
+	Block,
+	Context,
+	UnsignedValidator,
+	AllPalletsWithSystem,
+	OnRuntimeUpgrade = (),
+>(
+	PhantomData<(
+		System,
+		Block,
+		Context,
+		UnsignedValidator,
+		AllPalletsWithSystem,
+		OnRuntimeUpgrade,
+	)>,
 );
 
 impl<
@@ -160,14 +175,14 @@ impl<
 		Block: traits::Block
, Context: Default, UnsignedValidator, - AllPallets: OnRuntimeUpgrade + AllPalletsWithSystem: OnRuntimeUpgrade + OnInitialize + OnIdle + OnFinalize + OffchainWorker, COnRuntimeUpgrade: OnRuntimeUpgrade, > ExecuteBlock - for Executive + for Executive where Block::Extrinsic: Checkable + Codec, CheckedOf: Applyable + GetDispatchInfo, @@ -182,24 +197,26 @@ where Block, Context, UnsignedValidator, - AllPallets, + AllPalletsWithSystem, COnRuntimeUpgrade, >::execute_block(block); } } +#[cfg(feature = "try-runtime")] impl< System: frame_system::Config + EnsureInherentsAreFirst, Block: traits::Block
, Context: Default, UnsignedValidator, - AllPallets: OnRuntimeUpgrade + AllPalletsWithSystem: OnRuntimeUpgrade + OnInitialize + OnIdle + OnFinalize - + OffchainWorker, + + OffchainWorker + + frame_support::traits::TryState, COnRuntimeUpgrade: OnRuntimeUpgrade, - > Executive + > Executive where Block::Extrinsic: Checkable + Codec, CheckedOf: Applyable + GetDispatchInfo, @@ -208,50 +225,109 @@ where OriginOf: From>, UnsignedValidator: ValidateUnsigned>, { - /// Execute all `OnRuntimeUpgrade` of this runtime, and return the aggregate weight. - pub fn execute_on_runtime_upgrade() -> frame_support::weights::Weight { - let mut weight = 0; - weight = weight.saturating_add(COnRuntimeUpgrade::on_runtime_upgrade()); - weight = weight.saturating_add( - as OnRuntimeUpgrade>::on_runtime_upgrade(), - ); - weight = weight.saturating_add(::on_runtime_upgrade()); + /// Execute given block, but don't as strict is the normal block execution. + /// + /// Some consensus related checks such as the state root check can be switched off via + /// `try_state_root`. Some additional non-consensus checks can be additionally enabled via + /// `try_state`. + /// + /// Should only be used for testing ONLY. + pub fn try_execute_block( + block: Block, + try_state_root: bool, + select: frame_try_runtime::TryStateSelect, + ) -> Result { + use frame_support::traits::TryState; + + Self::initialize_block(block.header()); + Self::initial_checks(&block); + + let (header, extrinsics) = block.deconstruct(); + + Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number()); - weight + // run the try-state checks of all pallets. + >::try_state( + *header.number(), + select, + ) + .map_err(|e| { + frame_support::log::error!(target: "runtime::executive", "failure: {:?}", e); + e + })?; + + // do some of the checks that would normally happen in `final_checks`, but perhaps skip + // the state root check. + { + let new_header = >::finalize(); + let items_zip = header.digest().logs().iter().zip(new_header.digest().logs().iter()); + for (header_item, computed_item) in items_zip { + header_item.check_equal(computed_item); + assert!(header_item == computed_item, "Digest item must match that calculated."); + } + + if try_state_root { + let storage_root = new_header.state_root(); + header.state_root().check_equal(storage_root); + assert!( + header.state_root() == storage_root, + "Storage root must match that calculated." + ); + } + + assert!( + header.extrinsics_root() == new_header.extrinsics_root(), + "Transaction trie root must be valid.", + ); + } + + Ok(frame_system::Pallet::::block_weight().total()) } /// Execute all `OnRuntimeUpgrade` of this runtime, including the pre and post migration checks. /// /// This should only be used for testing. - #[cfg(feature = "try-runtime")] pub fn try_runtime_upgrade() -> Result { - < - (frame_system::Pallet::, COnRuntimeUpgrade, AllPallets) - as - OnRuntimeUpgrade - >::pre_upgrade()?; - let weight = Self::execute_on_runtime_upgrade(); - - < - (frame_system::Pallet::, COnRuntimeUpgrade, AllPallets) - as - OnRuntimeUpgrade - >::post_upgrade()?; - Ok(weight) } +} + +impl< + System: frame_system::Config + EnsureInherentsAreFirst, + Block: traits::Block
, + Context: Default, + UnsignedValidator, + AllPalletsWithSystem: OnRuntimeUpgrade + + OnInitialize + + OnIdle + + OnFinalize + + OffchainWorker, + COnRuntimeUpgrade: OnRuntimeUpgrade, + > Executive +where + Block::Extrinsic: Checkable + Codec, + CheckedOf: Applyable + GetDispatchInfo, + CallOf: + Dispatchable, + OriginOf: From>, + UnsignedValidator: ValidateUnsigned>, +{ + /// Execute all `OnRuntimeUpgrade` of this runtime, and return the aggregate weight. + pub fn execute_on_runtime_upgrade() -> frame_support::weights::Weight { + <(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::on_runtime_upgrade() + } /// Start the execution of a particular block. pub fn initialize_block(header: &System::Header) { sp_io::init_tracing(); sp_tracing::enter_span!(sp_tracing::Level::TRACE, "init_block"); - let digests = Self::extract_pre_digest(&header); + let digests = Self::extract_pre_digest(header); Self::initialize_block_impl(header.number(), header.parent_hash(), &digests); } - fn extract_pre_digest(header: &System::Header) -> DigestOf { - let mut digest = >::default(); + fn extract_pre_digest(header: &System::Header) -> Digest { + let mut digest = ::default(); header.digest().logs().iter().for_each(|d| { if d.as_pre_runtime().is_some() { digest.push(d.clone()) @@ -263,24 +339,21 @@ where fn initialize_block_impl( block_number: &System::BlockNumber, parent_hash: &System::Hash, - digest: &Digest, + digest: &Digest, ) { - let mut weight = 0; + // Reset events before apply runtime upgrade hook. + // This is required to preserve events from runtime upgrade hook. + // This means the format of all the event related storages must always be compatible. + >::reset_events(); + + let mut weight = Weight::zero(); if Self::runtime_upgraded() { weight = weight.saturating_add(Self::execute_on_runtime_upgrade()); } - >::initialize( - block_number, - parent_hash, - digest, - frame_system::InitKind::Full, - ); - weight = weight.saturating_add( as OnInitialize< + >::initialize(block_number, parent_hash, digest); + weight = weight.saturating_add(>::on_initialize(*block_number)); - weight = weight.saturating_add( - >::on_initialize(*block_number), - ); weight = weight.saturating_add( >::get().base_block, ); @@ -312,7 +385,7 @@ where let header = block.header(); // Check that `parent_hash` is correct. - let n = header.number().clone(); + let n = *header.number(); assert!( n > System::BlockNumber::zero() && >::block_hash(n - System::BlockNumber::one()) == @@ -385,30 +458,20 @@ where fn idle_and_finalize_hook(block_number: NumberFor) { let weight = >::block_weight(); let max_weight = >::get().max_block; - let mut remaining_weight = max_weight.saturating_sub(weight.total()); + let remaining_weight = max_weight.saturating_sub(weight.total()); - if remaining_weight > 0 { - let mut used_weight = - as OnIdle>::on_idle( - block_number, - remaining_weight, - ); - remaining_weight = remaining_weight.saturating_sub(used_weight); - used_weight = >::on_idle( + if remaining_weight.all_gt(Weight::zero()) { + let used_weight = >::on_idle( block_number, remaining_weight, - ) - .saturating_add(used_weight); + ); >::register_extra_weight_unchecked( used_weight, DispatchClass::Mandatory, ); } - as OnFinalize>::on_finalize( - block_number, - ); - >::on_finalize(block_number); + >::on_finalize(block_number); } /// Apply extrinsic outside of the block execution function. @@ -419,24 +482,15 @@ where sp_io::init_tracing(); let encoded = uxt.encode(); let encoded_len = encoded.len(); - Self::apply_extrinsic_with_len(uxt, encoded_len, encoded) - } - - /// Actually apply an extrinsic given its `encoded_len`; this doesn't note its hash. - fn apply_extrinsic_with_len( - uxt: Block::Extrinsic, - encoded_len: usize, - to_note: Vec, - ) -> ApplyExtrinsicResult { sp_tracing::enter_span!(sp_tracing::info_span!("apply_extrinsic", - ext=?sp_core::hexdisplay::HexDisplay::from(&uxt.encode()))); + ext=?sp_core::hexdisplay::HexDisplay::from(&encoded))); // Verify that the signature is good. let xt = uxt.check(&Default::default())?; // We don't need to make sure to `note_extrinsic` only after we know it's going to be // executed to prevent it from leaking in storage since at this point, it will either // execute or panic (and revert storage changes). - >::note_extrinsic(to_note); + >::note_extrinsic(encoded); // AUDIT: Under no circumstances may this function panic from here onwards. @@ -444,6 +498,14 @@ where let dispatch_info = xt.get_dispatch_info(); let r = Applyable::apply::(xt, &dispatch_info, encoded_len)?; + // Mandatory(inherents) are not allowed to fail. + // + // The entire block should be discarded if an inherent fails to apply. Otherwise + // it may open an attack vector. + if r.is_err() && dispatch_info.class == DispatchClass::Mandatory { + return Err(InvalidTransaction::BadMandatory.into()) + } + >::note_applied_extrinsic(&r, dispatch_info); Ok(r.map(|_| ()).map_err(|e| e.error)) @@ -462,13 +524,13 @@ where ); let items_zip = header.digest().logs().iter().zip(new_header.digest().logs().iter()); for (header_item, computed_item) in items_zip { - header_item.check_equal(&computed_item); + header_item.check_equal(computed_item); assert!(header_item == computed_item, "Digest item must match that calculated."); } // check storage root. let storage_root = new_header.state_root(); - header.state_root().check_equal(&storage_root); + header.state_root().check_equal(storage_root); assert!(header.state_root() == storage_root, "Storage root must match that calculated."); assert!( @@ -494,7 +556,6 @@ where &(frame_system::Pallet::::block_number() + One::one()), &block_hash, &Default::default(), - frame_system::InitKind::Inspection, ); enter_span! { sp_tracing::Level::TRACE, "validate_transaction" }; @@ -511,6 +572,10 @@ where xt.get_dispatch_info() }; + if dispatch_info.class == DispatchClass::Mandatory { + return Err(InvalidTransaction::MandatoryValidation.into()) + } + within_span! { sp_tracing::Level::TRACE, "validate"; xt.validate::(source, &dispatch_info, encoded_len) @@ -525,34 +590,23 @@ where // OffchainWorker RuntimeApi should skip initialization. let digests = header.digest().clone(); - >::initialize( - header.number(), - header.parent_hash(), - &digests, - frame_system::InitKind::Inspection, - ); + >::initialize(header.number(), header.parent_hash(), &digests); // Frame system only inserts the parent hash into the block hashes as normally we don't know // the hash for the header before. However, here we are aware of the hash and we can add it // as well. frame_system::BlockHash::::insert(header.number(), header.hash()); - >::offchain_worker(*header.number()) + >::offchain_worker( + *header.number(), + ) } } #[cfg(test)] mod tests { use super::*; - use frame_support::{ - assert_err, parameter_types, - traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons}, - weights::{IdentityFee, RuntimeDbWeight, Weight, WeightToFeePolynomial}, - }; - use frame_system::{Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo}; - use hex_literal::hex; - use pallet_balances::Call as BalancesCall; - use pallet_transaction_payment::CurrencyAdapter; + use sp_core::H256; use sp_runtime::{ generic::{DigestItem, Era}, @@ -563,95 +617,145 @@ mod tests { }, DispatchError, }; - const TEST_KEY: &[u8] = &*b":test:key:"; + use frame_support::{ + assert_err, parameter_types, + traits::{ + ConstU32, ConstU64, ConstU8, Currency, LockIdentifier, LockableCurrency, + WithdrawReasons, + }, + weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightToFee}, + }; + use frame_system::{Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo}; + use pallet_balances::Call as BalancesCall; + use pallet_transaction_payment::CurrencyAdapter; + + const TEST_KEY: &[u8] = b":test:key:"; + + #[frame_support::pallet] mod custom { - use frame_support::weights::{DispatchClass, Weight}; - use sp_runtime::transaction_validity::{ - TransactionSource, TransactionValidity, TransactionValidityError, UnknownTransaction, - }; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] pub trait Config: frame_system::Config {} - frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin { - #[weight = 100] - fn some_function(origin) { - // NOTE: does not make any different. - frame_system::ensure_signed(origin)?; - } - #[weight = (200, DispatchClass::Operational)] - fn some_root_operation(origin) { - frame_system::ensure_root(origin)?; - } - #[weight = 0] - fn some_unsigned_message(origin) { - frame_system::ensure_none(origin)?; - } + #[pallet::hooks] + impl Hooks> for Pallet { + // module hooks. + // one with block number arg and one without + fn on_initialize(n: T::BlockNumber) -> Weight { + println!("on_initialize({})", n); + Weight::from_ref_time(175) + } - #[weight = 0] - fn allowed_unsigned(origin) { - frame_system::ensure_root(origin)?; - } + fn on_idle(n: T::BlockNumber, remaining_weight: Weight) -> Weight { + println!("on_idle{}, {})", n, remaining_weight); + Weight::from_ref_time(175) + } - #[weight = 0] - fn unallowed_unsigned(origin) { - frame_system::ensure_root(origin)?; - } + fn on_finalize(n: T::BlockNumber) { + println!("on_finalize({})", n); + } - #[weight = 0] - fn inherent_call(origin) { - let _ = frame_system::ensure_none(origin)?; - } + fn on_runtime_upgrade() -> Weight { + sp_io::storage::set(super::TEST_KEY, "module".as_bytes()); + Weight::from_ref_time(200) + } - // module hooks. - // one with block number arg and one without - fn on_initialize(n: T::BlockNumber) -> Weight { - println!("on_initialize({})", n); - 175 - } + fn offchain_worker(n: T::BlockNumber) { + assert_eq!(T::BlockNumber::from(1u32), n); + } + } - fn on_idle(n: T::BlockNumber, remaining_weight: Weight) -> Weight { - println!("on_idle{}, {})", n, remaining_weight); - 175 - } + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(100)] + pub fn some_function(origin: OriginFor) -> DispatchResult { + // NOTE: does not make any different. + frame_system::ensure_signed(origin)?; + Ok(()) + } - fn on_finalize() { - println!("on_finalize(?)"); - } + #[pallet::call_index(1)] + #[pallet::weight((200, DispatchClass::Operational))] + pub fn some_root_operation(origin: OriginFor) -> DispatchResult { + frame_system::ensure_root(origin)?; + Ok(()) + } - fn on_runtime_upgrade() -> Weight { - sp_io::storage::set(super::TEST_KEY, "module".as_bytes()); - 200 - } + #[pallet::call_index(2)] + #[pallet::weight(0)] + pub fn some_unsigned_message(origin: OriginFor) -> DispatchResult { + frame_system::ensure_none(origin)?; + Ok(()) + } - fn offchain_worker(n: T::BlockNumber) { - assert_eq!(T::BlockNumber::from(1u32), n); - } + #[pallet::call_index(3)] + #[pallet::weight(0)] + pub fn allowed_unsigned(origin: OriginFor) -> DispatchResult { + frame_system::ensure_root(origin)?; + Ok(()) + } - #[weight = 0] - fn calculate_storage_root(_origin) { - let root = sp_io::storage::root(); - sp_io::storage::set("storage_root".as_bytes(), &root); - } + #[pallet::call_index(4)] + #[pallet::weight(0)] + pub fn unallowed_unsigned(origin: OriginFor) -> DispatchResult { + frame_system::ensure_root(origin)?; + Ok(()) + } + + #[pallet::call_index(5)] + #[pallet::weight((0, DispatchClass::Mandatory))] + pub fn inherent_call(origin: OriginFor) -> DispatchResult { + frame_system::ensure_none(origin)?; + Ok(()) + } + + #[pallet::call_index(6)] + #[pallet::weight(0)] + pub fn calculate_storage_root(_origin: OriginFor) -> DispatchResult { + let root = sp_io::storage::root(sp_runtime::StateVersion::V1); + sp_io::storage::set("storage_root".as_bytes(), &root); + Ok(()) } } - impl frame_support::inherent::ProvideInherent for Module { + #[pallet::inherent] + impl ProvideInherent for Pallet { type Call = Call; + type Error = sp_inherents::MakeFatalError<()>; + const INHERENT_IDENTIFIER: [u8; 8] = *b"test1234"; - fn create_inherent(_data: &sp_inherents::InherentData) -> Option { + + fn create_inherent(_data: &InherentData) -> Option { None } + fn is_inherent(call: &Self::Call) -> bool { *call == Call::::inherent_call {} } } - impl sp_runtime::traits::ValidateUnsigned for Module { + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { type Call = Call; + // Inherent call is accepted for being dispatched + fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { + match call { + Call::allowed_unsigned { .. } => Ok(()), + Call::inherent_call { .. } => Ok(()), + _ => Err(UnknownTransaction::NoUnsignedValidator.into()), + } + } + // Inherent call is not validated as unsigned fn validate_unsigned( _source: TransactionSource, @@ -662,15 +766,6 @@ mod tests { _ => UnknownTransaction::NoUnsignedValidator.into(), } } - - // Inherent call is accepted for being dispatched - fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { - match call { - Call::allowed_unsigned { .. } => Ok(()), - Call::inherent_call { .. } => Ok(()), - _ => Err(UnknownTransaction::NoUnsignedValidator.into()), - } - } } } @@ -682,18 +777,17 @@ mod tests { { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, Custom: custom::{Pallet, Call, ValidateUnsigned, Inherent}, } ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::builder() - .base_block(10) - .for_class(DispatchClass::all(), |weights| weights.base_extrinsic = 5) - .for_class(DispatchClass::non_mandatory(), |weights| weights.max_total = 1024.into()) + .base_block(Weight::from_ref_time(10)) + .for_class(DispatchClass::all(), |weights| weights.base_extrinsic = Weight::from_ref_time(5)) + .for_class(DispatchClass::non_mandatory(), |weights| weights.max_total = Weight::from_ref_time(1024).set_proof_size(u64::MAX).into()) .build_or_panic(); pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 10, @@ -705,17 +799,17 @@ mod tests { type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type BlockNumber = u64; type Hash = sp_core::H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = RuntimeVersion; type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -724,17 +818,15 @@ mod tests { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } type Balance = u64; - parameter_types! { - pub const ExistentialDeposit: Balance = 1; - } impl pallet_balances::Config for Runtime { type Balance = Balance; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type MaxLocks = (); type MaxReserves = (); @@ -746,9 +838,11 @@ mod tests { pub const TransactionByteFee: Balance = 0; } impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = CurrencyAdapter; - type TransactionByteFee = TransactionByteFee; + type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; + type LengthToFee = ConstantMultiplier; type FeeMultiplierUpdate = (); } impl custom::Config for Runtime {} @@ -756,12 +850,12 @@ mod tests { pub struct RuntimeVersion; impl frame_support::traits::Get for RuntimeVersion { fn get() -> sp_version::RuntimeVersion { - RUNTIME_VERSION.with(|v| v.borrow().clone()) + RuntimeVersionTestValues::get().clone() } } - thread_local! { - pub static RUNTIME_VERSION: std::cell::RefCell = + parameter_types! { + pub static RuntimeVersionTestValues: sp_version::RuntimeVersion = Default::default(); } @@ -771,19 +865,20 @@ mod tests { frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, ); - type TestXt = sp_runtime::testing::TestXt; + type TestXt = sp_runtime::testing::TestXt; type TestBlock = Block; type TestUncheckedExtrinsic = TestXt; // Will contain `true` when the custom runtime logic was called. - const CUSTOM_ON_RUNTIME_KEY: &[u8] = &*b":custom:on_runtime"; + const CUSTOM_ON_RUNTIME_KEY: &[u8] = b":custom:on_runtime"; struct CustomOnRuntimeUpgrade; impl OnRuntimeUpgrade for CustomOnRuntimeUpgrade { fn on_runtime_upgrade() -> Weight { sp_io::storage::set(TEST_KEY, "custom_upgrade".as_bytes()); sp_io::storage::set(CUSTOM_ON_RUNTIME_KEY, &true.encode()); - 100 + System::deposit_event(frame_system::Event::CodeUpdated); + Weight::from_ref_time(100) } } @@ -792,7 +887,7 @@ mod tests { Block, ChainContext, Runtime, - AllPallets, + AllPalletsWithSystem, CustomOnRuntimeUpgrade, >; @@ -809,8 +904,8 @@ mod tests { Some((who, extra(nonce, fee))) } - fn call_transfer(dest: u64, value: u64) -> Call { - Call::Balances(BalancesCall::transfer { dest, value }) + fn call_transfer(dest: u64, value: u64) -> RuntimeCall { + RuntimeCall::Balances(BalancesCall::transfer { dest, value }) } #[test] @@ -825,7 +920,7 @@ mod tests { .get(DispatchClass::Normal) .base_extrinsic; let fee: Balance = - ::WeightToFee::calc(&weight); + ::WeightToFee::weight_to_fee(&weight); let mut t = sp_io::TestExternalities::new(t); t.execute_with(|| { Executive::initialize_block(&Header::new( @@ -850,21 +945,39 @@ mod tests { t.into() } + fn new_test_ext_v0(balance_factor: Balance) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { balances: vec![(1, 111 * balance_factor)] } + .assimilate_storage(&mut t) + .unwrap(); + (t, sp_runtime::StateVersion::V0).into() + } + #[test] fn block_import_works() { - new_test_ext(1).execute_with(|| { + block_import_works_inner( + new_test_ext_v0(1), + array_bytes::hex_n_into_unchecked( + "216e61b2689d1243eb56d89c9084db48e50ebebc4871d758db131432c675d7c0", + ), + ); + block_import_works_inner( + new_test_ext(1), + array_bytes::hex_n_into_unchecked( + "4738b4c0aab02d6ddfa62a2a6831ccc975a9f978f7db8d7ea8e68eba8639530a", + ), + ); + } + fn block_import_works_inner(mut ext: sp_io::TestExternalities, state_root: H256) { + ext.execute_with(|| { Executive::execute_block(Block { header: Header { parent_hash: [69u8; 32].into(), number: 1, - state_root: hex!( - "1039e1a4bd0cf5deefe65f313577e70169c41c7773d6acf31ca8d671397559f5" - ) - .into(), - extrinsics_root: hex!( - "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314" - ) - .into(), + state_root, + extrinsics_root: array_bytes::hex_n_into_unchecked( + "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314", + ), digest: Digest { logs: vec![] }, }, extrinsics: vec![], @@ -881,10 +994,9 @@ mod tests { parent_hash: [69u8; 32].into(), number: 1, state_root: [0u8; 32].into(), - extrinsics_root: hex!( - "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314" - ) - .into(), + extrinsics_root: array_bytes::hex_n_into_unchecked( + "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314", + ), digest: Digest { logs: vec![] }, }, extrinsics: vec![], @@ -900,10 +1012,9 @@ mod tests { header: Header { parent_hash: [69u8; 32].into(), number: 1, - state_root: hex!( - "49cd58a254ccf6abc4a023d9a22dcfc421e385527a250faec69f8ad0d8ed3e48" - ) - .into(), + state_root: array_bytes::hex_n_into_unchecked( + "75e7d8f360d375bbe91bcf8019c01ab6362448b4a89e3b329717eb9d910340e5", + ), extrinsics_root: [0u8; 32].into(), digest: Digest { logs: vec![] }, }, @@ -938,16 +1049,16 @@ mod tests { let mut t = new_test_ext(10000); // given: TestXt uses the encoded len as fixed Len: let xt = TestXt::new( - Call::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), sign_extra(1, 0, 0), ); let encoded = xt.encode(); - let encoded_len = encoded.len() as Weight; + let encoded_len = encoded.len() as u64; // on_initialize weight + base block execution weight let block_weights = ::BlockWeights::get(); - let base_block_weight = 175 + block_weights.base_block; + let base_block_weight = Weight::from_ref_time(175) + block_weights.base_block; let limit = block_weights.get(DispatchClass::Normal).max_total.unwrap() - base_block_weight; - let num_to_exhaust_block = limit / (encoded_len + 5); + let num_to_exhaust_block = limit.ref_time() / (encoded_len + 5); t.execute_with(|| { Executive::initialize_block(&Header::new( 1, @@ -961,7 +1072,7 @@ mod tests { for nonce in 0..=num_to_exhaust_block { let xt = TestXt::new( - Call::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), sign_extra(1, nonce.into(), 0), ); let res = Executive::apply_extrinsic(xt); @@ -970,7 +1081,7 @@ mod tests { assert_eq!( >::block_weight().total(), //--------------------- on_initialize + block_execution + extrinsic_base weight - (encoded_len + 5) * (nonce + 1) + base_block_weight, + Weight::from_ref_time((encoded_len + 5) * (nonce + 1)) + base_block_weight, ); assert_eq!( >::extrinsic_index(), @@ -986,23 +1097,23 @@ mod tests { #[test] fn block_weight_and_size_is_stored_per_tx() { let xt = TestXt::new( - Call::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), sign_extra(1, 0, 0), ); let x1 = TestXt::new( - Call::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), sign_extra(1, 1, 0), ); let x2 = TestXt::new( - Call::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), sign_extra(1, 2, 0), ); let len = xt.clone().encode().len() as u32; let mut t = new_test_ext(1); t.execute_with(|| { // Block execution weight + on_initialize weight from custom module - let base_block_weight = - 175 + ::BlockWeights::get().base_block; + let base_block_weight = Weight::from_ref_time(175) + + ::BlockWeights::get().base_block; Executive::initialize_block(&Header::new( 1, @@ -1020,13 +1131,13 @@ mod tests { assert!(Executive::apply_extrinsic(x2.clone()).unwrap().is_ok()); // default weight for `TestXt` == encoded length. - let extrinsic_weight = len as Weight + + let extrinsic_weight = Weight::from_ref_time(len as u64) + ::BlockWeights::get() .get(DispatchClass::Normal) .base_extrinsic; assert_eq!( >::block_weight().total(), - base_block_weight + 3 * extrinsic_weight, + base_block_weight + 3u64 * extrinsic_weight, ); assert_eq!(>::all_extrinsics_len(), 3 * len); @@ -1050,12 +1161,10 @@ mod tests { #[test] fn validate_unsigned() { - let valid = TestXt::new(Call::Custom(custom::Call::allowed_unsigned {}), None); - let invalid = TestXt::new(Call::Custom(custom::Call::unallowed_unsigned {}), None); + let valid = TestXt::new(RuntimeCall::Custom(custom::Call::allowed_unsigned {}), None); + let invalid = TestXt::new(RuntimeCall::Custom(custom::Call::unallowed_unsigned {}), None); let mut t = new_test_ext(1); - let mut default_with_prio_3 = ValidTransaction::default(); - default_with_prio_3.priority = 3; t.execute_with(|| { assert_eq!( Executive::validate_transaction( @@ -1063,7 +1172,7 @@ mod tests { valid.clone(), Default::default(), ), - Ok(default_with_prio_3), + Ok(ValidTransaction::default()), ); assert_eq!( Executive::validate_transaction( @@ -1091,7 +1200,7 @@ mod tests { id, &1, 110, lock, ); let xt = TestXt::new( - Call::System(SystemCall::remark { remark: vec![1u8] }), + RuntimeCall::System(SystemCall::remark { remark: vec![1u8] }), sign_extra(1, 0, 0), ); let weight = xt.get_dispatch_info().weight + @@ -1099,7 +1208,9 @@ mod tests { .get(DispatchClass::Normal) .base_extrinsic; let fee: Balance = - ::WeightToFee::calc(&weight); + ::WeightToFee::weight_to_fee( + &weight, + ); Executive::initialize_block(&Header::new( 1, H256::default(), @@ -1134,21 +1245,23 @@ mod tests { // NOTE: might need updates over time if new weights are introduced. // For now it only accounts for the base block execution weight and // the `on_initialize` weight defined in the custom test module. - assert_eq!(>::block_weight().total(), 175 + 175 + 10); + assert_eq!( + >::block_weight().total(), + Weight::from_ref_time(175 + 175 + 10) + ); }) } #[test] fn runtime_upgraded_should_work() { new_test_ext(1).execute_with(|| { - RUNTIME_VERSION.with(|v| *v.borrow_mut() = Default::default()); + RuntimeVersionTestValues::mutate(|v| *v = Default::default()); // It should be added at genesis assert!(frame_system::LastRuntimeUpgrade::::exists()); assert!(!Executive::runtime_upgraded()); - RUNTIME_VERSION.with(|v| { - *v.borrow_mut() = - sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } + RuntimeVersionTestValues::mutate(|v| { + *v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } }); assert!(Executive::runtime_upgraded()); assert_eq!( @@ -1156,8 +1269,8 @@ mod tests { frame_system::LastRuntimeUpgrade::::get(), ); - RUNTIME_VERSION.with(|v| { - *v.borrow_mut() = sp_version::RuntimeVersion { + RuntimeVersionTestValues::mutate(|v| { + *v = sp_version::RuntimeVersion { spec_version: 1, spec_name: "test".into(), ..Default::default() @@ -1169,8 +1282,8 @@ mod tests { frame_system::LastRuntimeUpgrade::::get(), ); - RUNTIME_VERSION.with(|v| { - *v.borrow_mut() = sp_version::RuntimeVersion { + RuntimeVersionTestValues::mutate(|v| { + *v = sp_version::RuntimeVersion { spec_version: 1, spec_name: "test".into(), impl_version: 2, @@ -1218,9 +1331,8 @@ mod tests { fn custom_runtime_upgrade_is_called_before_modules() { new_test_ext(1).execute_with(|| { // Make sure `on_runtime_upgrade` is called. - RUNTIME_VERSION.with(|v| { - *v.borrow_mut() = - sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } + RuntimeVersionTestValues::mutate(|v| { + *v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } }); Executive::initialize_block(&Header::new( @@ -1236,20 +1348,42 @@ mod tests { }); } + #[test] + fn event_from_runtime_upgrade_is_included() { + new_test_ext(1).execute_with(|| { + // Make sure `on_runtime_upgrade` is called. + RuntimeVersionTestValues::mutate(|v| { + *v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } + }); + + // set block number to non zero so events are not excluded + System::set_block_number(1); + + Executive::initialize_block(&Header::new( + 2, + H256::default(), + H256::default(), + [69u8; 32].into(), + Digest::default(), + )); + + System::assert_last_event(frame_system::Event::::CodeUpdated.into()); + }); + } + /// Regression test that ensures that the custom on runtime upgrade is called when executive is /// used through the `ExecuteBlock` trait. #[test] fn custom_runtime_upgrade_is_called_when_using_execute_block_trait() { let xt = TestXt::new( - Call::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), sign_extra(1, 0, 0), ); let header = new_test_ext(1).execute_with(|| { // Make sure `on_runtime_upgrade` is called. - RUNTIME_VERSION.with(|v| { - *v.borrow_mut() = - sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } + RuntimeVersionTestValues::mutate(|v| { + *v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } }); // Let's build some fake block. @@ -1267,15 +1401,14 @@ mod tests { }); // Reset to get the correct new genesis below. - RUNTIME_VERSION.with(|v| { - *v.borrow_mut() = sp_version::RuntimeVersion { spec_version: 0, ..Default::default() } + RuntimeVersionTestValues::mutate(|v| { + *v = sp_version::RuntimeVersion { spec_version: 0, ..Default::default() } }); new_test_ext(1).execute_with(|| { // Make sure `on_runtime_upgrade` is called. - RUNTIME_VERSION.with(|v| { - *v.borrow_mut() = - sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } + RuntimeVersionTestValues::mutate(|v| { + *v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } }); >>::execute_block(Block::new(header, vec![xt])); @@ -1289,9 +1422,8 @@ mod tests { fn all_weights_are_recorded_correctly() { new_test_ext(1).execute_with(|| { // Make sure `on_runtime_upgrade` is called for maximum complexity - RUNTIME_VERSION.with(|v| { - *v.borrow_mut() = - sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } + RuntimeVersionTestValues::mutate(|v| { + *v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } }); let block_number = 1; @@ -1305,23 +1437,19 @@ mod tests { )); // All weights that show up in the `initialize_block_impl` - let frame_system_upgrade_weight = frame_system::Pallet::::on_runtime_upgrade(); let custom_runtime_upgrade_weight = CustomOnRuntimeUpgrade::on_runtime_upgrade(); - let runtime_upgrade_weight = ::on_runtime_upgrade(); - let frame_system_on_initialize_weight = - frame_system::Pallet::::on_initialize(block_number); + let runtime_upgrade_weight = + ::on_runtime_upgrade(); let on_initialize_weight = - >::on_initialize(block_number); + >::on_initialize(block_number); let base_block_weight = ::BlockWeights::get().base_block; // Weights are recorded correctly assert_eq!( frame_system::Pallet::::block_weight().total(), - frame_system_upgrade_weight + - custom_runtime_upgrade_weight + + custom_runtime_upgrade_weight + runtime_upgrade_weight + - frame_system_on_initialize_weight + on_initialize_weight + base_block_weight, ); }); @@ -1347,7 +1475,7 @@ mod tests { #[test] fn calculating_storage_root_twice_works() { - let call = Call::Custom(custom::Call::calculate_storage_root {}); + let call = RuntimeCall::Custom(custom::Call::calculate_storage_root {}); let xt = TestXt::new(call, sign_extra(1, 0, 0)); let header = new_test_ext(1).execute_with(|| { @@ -1374,10 +1502,10 @@ mod tests { #[should_panic(expected = "Invalid inherent position for extrinsic at index 1")] fn invalid_inherent_position_fail() { let xt1 = TestXt::new( - Call::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), sign_extra(1, 0, 0), ); - let xt2 = TestXt::new(Call::Custom(custom::Call::inherent_call {}), None); + let xt2 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), None); let header = new_test_ext(1).execute_with(|| { // Let's build some fake block. @@ -1402,7 +1530,7 @@ mod tests { #[test] fn valid_inherents_position_works() { - let xt1 = TestXt::new(Call::Custom(custom::Call::inherent_call {}), None); + let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), None); let xt2 = TestXt::new(call_transfer(33, 0), sign_extra(1, 0, 0)); let header = new_test_ext(1).execute_with(|| { @@ -1425,4 +1553,38 @@ mod tests { Executive::execute_block(Block::new(header, vec![xt1, xt2])); }); } + + #[test] + #[should_panic(expected = "A call was labelled as mandatory, but resulted in an Error.")] + fn invalid_inherents_fail_block_execution() { + let xt1 = + TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), sign_extra(1, 0, 0)); + + new_test_ext(1).execute_with(|| { + Executive::execute_block(Block::new( + Header::new( + 1, + H256::default(), + H256::default(), + [69u8; 32].into(), + Digest::default(), + ), + vec![xt1], + )); + }); + } + + // Inherents are created by the runtime and don't need to be validated. + #[test] + fn inherents_fail_validate_block() { + let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), None); + + new_test_ext(1).execute_with(|| { + assert_eq!( + Executive::validate_transaction(TransactionSource::External, xt1, H256::random()) + .unwrap_err(), + InvalidTransaction::MandatoryValidation.into() + ); + }) + } } diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml new file mode 100644 index 0000000000000..61bc823cc11e5 --- /dev/null +++ b/frame/fast-unstake/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "pallet-fast-unstake" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Unlicense" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME fast unstake pallet" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } + +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-staking = { default-features = false, path = "../../primitives/staking" } +frame-election-provider-support = { default-features = false, path = "../election-provider-support" } + +# optional dependencies for cargo features +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +pallet-staking = { default-features = false, optional = true, path = "../staking" } +pallet-assets = { default-features = false, optional = true, path = "../assets" } + +[dev-dependencies] +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +pallet-staking = { path = "../staking" } +pallet-balances = { path = "../balances" } +pallet-timestamp = { path = "../timestamp" } +pallet-assets = { path = "../assets" } + + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "scale-info/std", + + "frame-support/std", + "frame-system/std", + + "sp-io/std", + "sp-staking/std", + "sp-runtime/std", + "sp-std/std", + + "frame-election-provider-support/std", + + "frame-benchmarking/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-staking/runtime-benchmarks", + "pallet-staking/runtime-benchmarks" +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs new file mode 100644 index 0000000000000..b4a5e21dcfc13 --- /dev/null +++ b/frame/fast-unstake/src/benchmarking.rs @@ -0,0 +1,201 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 for pallet-fast-unstake. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::{types::*, Pallet as FastUnstake, *}; +use frame_benchmarking::{benchmarks, whitelist_account}; +use frame_support::{ + assert_ok, + traits::{Currency, EnsureOrigin, Get, Hooks}, +}; +use frame_system::RawOrigin; +use sp_runtime::traits::Zero; +use sp_staking::{EraIndex, StakingInterface}; +use sp_std::prelude::*; + +const USER_SEED: u32 = 0; +const DEFAULT_BACKER_PER_VALIDATOR: u32 = 128; +const MAX_VALIDATORS: u32 = 128; + +type CurrencyOf = ::Currency; + +fn create_unexposed_nominators() -> Vec { + (0..T::BatchSize::get()) + .map(|i| { + let account = + frame_benchmarking::account::("unexposed_nominator", i, USER_SEED); + fund_and_bond_account::(&account); + account + }) + .collect() +} + +fn fund_and_bond_account(account: &T::AccountId) { + let stake = CurrencyOf::::minimum_balance() * 100u32.into(); + CurrencyOf::::make_free_balance_be(&account, stake * 10u32.into()); + + // bond and nominate ourselves, this will guarantee that we are not backing anyone. + assert_ok!(T::Staking::bond(account, stake, account)); + assert_ok!(T::Staking::nominate(account, vec![account.clone()])); +} + +pub(crate) fn fast_unstake_events() -> Vec> { + frame_system::Pallet::::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| ::RuntimeEvent::from(e).try_into().ok()) + .collect::>() +} + +fn setup_staking(v: u32, until: EraIndex) { + let ed = CurrencyOf::::minimum_balance(); + + log!(debug, "registering {} validators and {} eras.", v, until); + + // our validators don't actually need to registered in staking -- just generate `v` random + // accounts. + let validators = (0..v) + .map(|x| frame_benchmarking::account::("validator", x, USER_SEED)) + .collect::>(); + + for era in 0..=until { + let others = (0..DEFAULT_BACKER_PER_VALIDATOR) + .map(|s| { + let who = frame_benchmarking::account::("nominator", era, s); + let value = ed; + (who, value) + }) + .collect::>(); + validators.iter().for_each(|v| { + T::Staking::add_era_stakers(&era, &v, others.clone()); + }); + } +} + +fn on_idle_full_block() { + let remaining_weight = ::BlockWeights::get().max_block; + FastUnstake::::on_idle(Zero::zero(), remaining_weight); +} + +benchmarks! { + // on_idle, we don't check anyone, but fully unbond them. + on_idle_unstake { + ErasToCheckPerBlock::::put(1); + for who in create_unexposed_nominators::() { + assert_ok!(FastUnstake::::register_fast_unstake( + RawOrigin::Signed(who.clone()).into(), + )); + } + + // run on_idle once. This will check era 0. + assert_eq!(Head::::get(), None); + on_idle_full_block::(); + + assert!(matches!( + Head::::get(), + Some(UnstakeRequest { + checked, + stashes, + .. + }) if checked.len() == 1 && stashes.len() as u32 == T::BatchSize::get() + )); + } + : { + on_idle_full_block::(); + } + verify { + assert!(matches!( + fast_unstake_events::().last(), + Some(Event::BatchFinished) + )); + } + + // on_idle, when we check some number of eras, + on_idle_check { + // number of eras multiplied by validators in that era. + let x in (T::Staking::bonding_duration() * 1) .. (T::Staking::bonding_duration() * MAX_VALIDATORS); + + let u = T::Staking::bonding_duration(); + let v = x / u; + + ErasToCheckPerBlock::::put(u); + T::Staking::set_current_era(u); + + // setup staking with v validators and u eras of data (0..=u) + setup_staking::(v, u); + + let stashes = create_unexposed_nominators::().into_iter().map(|s| { + assert_ok!(FastUnstake::::register_fast_unstake( + RawOrigin::Signed(s.clone()).into(), + )); + (s, T::Deposit::get()) + }).collect::>(); + + // no one is queued thus far. + assert_eq!(Head::::get(), None); + } + : { + on_idle_full_block::(); + } + verify { + let checked = (1..=u).rev().collect::>(); + let request = Head::::get().unwrap(); + assert_eq!(checked, request.checked.into_inner()); + assert!(matches!( + fast_unstake_events::().last(), + Some(Event::BatchChecked { .. }) + )); + assert!(stashes.iter().all(|(s, _)| request.stashes.iter().find(|(ss, _)| ss == s).is_some())); + } + + register_fast_unstake { + ErasToCheckPerBlock::::put(1); + let who = create_unexposed_nominators::().get(0).cloned().unwrap(); + whitelist_account!(who); + assert_eq!(Queue::::count(), 0); + + } + :_(RawOrigin::Signed(who.clone())) + verify { + assert_eq!(Queue::::count(), 1); + } + + deregister { + ErasToCheckPerBlock::::put(1); + let who = create_unexposed_nominators::().get(0).cloned().unwrap(); + assert_ok!(FastUnstake::::register_fast_unstake( + RawOrigin::Signed(who.clone()).into(), + )); + assert_eq!(Queue::::count(), 1); + whitelist_account!(who); + } + :_(RawOrigin::Signed(who.clone())) + verify { + assert_eq!(Queue::::count(), 0); + } + + control { + let origin = ::ControlOrigin::successful_origin(); + } + : _(origin, 128) + verify {} + + impl_benchmark_test_suite!(Pallet, crate::mock::ExtBuilder::default().build(), crate::mock::Runtime) +} diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs new file mode 100644 index 0000000000000..7f226826cbc53 --- /dev/null +++ b/frame/fast-unstake/src/lib.rs @@ -0,0 +1,496 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! A pallet that's designed to JUST do the following: +//! +//! If a nominator is not exposed in any `ErasStakers` (i.e. "has not actively backed any +//! validators in the last `BondingDuration` days"), then they can register themselves in this +//! pallet, unstake faster than having to wait an entire bonding duration. +//! +//! Appearing in the exposure of a validator means being exposed equal to that validator from the +//! point of view of the staking system. This usually means earning rewards with the validator, and +//! also being at the risk of slashing with the validator. This is equivalent to the "Active +//! Nominator" role explained in the +//! [February Staking Update](https://polkadot.network/blog/staking-update-february-2022/). +//! +//! This pallet works off the basis of `on_idle`, meaning that it provides no guarantee about when +//! it will succeed, if at all. Moreover, the queue implementation is unordered. In case of +//! congestion, no FIFO ordering is provided. +//! +//! Stakers who are certain about NOT being exposed can register themselves with +//! [`Call::register_fast_unstake`]. This will chill, and fully unbond the staker, and place them in +//! the queue to be checked. +//! +//! Once queued, but not being actively processed, stakers can withdraw their request via +//! [`Call::deregister`]. +//! +//! Once queued, a staker wishing to unbond can perform no further action in pallet-staking. This is +//! to prevent them from accidentally exposing themselves behind a validator etc. +//! +//! Once processed, if successful, no additional fee for the checking process is taken, and the +//! staker is instantly unbonded. +//! +//! If unsuccessful, meaning that the staker was exposed sometime in the last `BondingDuration` eras +//! they will end up being slashed for the amount of wasted work they have inflicted on the chian. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +// NOTE: enable benchmarking in tests as well. +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod migrations; +pub mod types; +pub mod weights; + +pub const LOG_TARGET: &'static str = "runtime::fast-unstake"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 💨 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use crate::types::*; + use frame_support::{ + pallet_prelude::*, + traits::{Defensive, ReservableCurrency, StorageVersion}, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::{ + traits::{Saturating, Zero}, + DispatchResult, + }; + use sp_staking::{EraIndex, StakingInterface}; + use sp_std::{prelude::*, vec::Vec}; + pub use weights::WeightInfo; + + #[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)] + #[codec(mel_bound(T: Config))] + #[scale_info(skip_type_params(T))] + pub struct MaxChecking(sp_std::marker::PhantomData); + impl frame_support::traits::Get for MaxChecking { + fn get() -> u32 { + T::Staking::bonding_duration() + 1 + } + } + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent> + + TryInto>; + + /// The currency used for deposits. + type Currency: ReservableCurrency; + + /// Deposit to take for unstaking, to make sure we're able to slash the it in order to cover + /// the costs of resources on unsuccessful unstake. + #[pallet::constant] + type Deposit: Get>; + + /// The origin that can control this pallet. + type ControlOrigin: frame_support::traits::EnsureOrigin; + + /// Batch size. + /// + /// This many stashes are processed in each unstake request. + type BatchSize: Get; + + /// The access to staking functionality. + type Staking: StakingInterface, AccountId = Self::AccountId>; + + /// The weight information of this pallet. + type WeightInfo: WeightInfo; + } + + /// The current "head of the queue" being unstaked. + #[pallet::storage] + pub type Head = StorageValue<_, UnstakeRequest, OptionQuery>; + + /// The map of all accounts wishing to be unstaked. + /// + /// Keeps track of `AccountId` wishing to unstake and it's corresponding deposit. + #[pallet::storage] + pub type Queue = CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf>; + + /// Number of eras to check per block. + /// + /// If set to 0, this pallet does absolutely nothing. + /// + /// Based on the amount of weight available at `on_idle`, up to this many eras of a single + /// nominator might be checked. + #[pallet::storage] + pub type ErasToCheckPerBlock = StorageValue<_, u32, ValueQuery>; + + /// The events of this pallet. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A staker was unstaked. + Unstaked { stash: T::AccountId, result: DispatchResult }, + /// A staker was slashed for requesting fast-unstake whilst being exposed. + Slashed { stash: T::AccountId, amount: BalanceOf }, + /// An internal error happened. Operations will be paused now. + InternalError, + /// A batch was partially checked for the given eras, but the process did not finish. + BatchChecked { eras: Vec }, + /// A batch was terminated. + /// + /// This is always follows by a number of `Unstaked` or `Slashed` events, marking the end + /// of the batch. A new batch will be created upon next block. + BatchFinished, + } + + #[pallet::error] + #[cfg_attr(test, derive(PartialEq))] + pub enum Error { + /// The provided Controller account was not found. + /// + /// This means that the given account is not bonded. + NotController, + /// The bonded account has already been queued. + AlreadyQueued, + /// The bonded account has active unlocking chunks. + NotFullyBonded, + /// The provided un-staker is not in the `Queue`. + NotQueued, + /// The provided un-staker is already in Head, and cannot deregister. + AlreadyHead, + /// The call is not allowed at this point because the pallet is not active. + CallNotAllowed, + } + + #[pallet::hooks] + impl Hooks for Pallet { + fn on_idle(_: T::BlockNumber, remaining_weight: Weight) -> Weight { + if remaining_weight.any_lt(T::DbWeight::get().reads(2)) { + return Weight::from_ref_time(0) + } + + Self::do_on_idle(remaining_weight) + } + } + + #[pallet::call] + impl Pallet { + /// Register oneself for fast-unstake. + /// + /// The dispatch origin of this call must be signed by the controller account, similar to + /// `staking::unbond`. + /// + /// The stash associated with the origin must have no ongoing unlocking chunks. If + /// successful, this will fully unbond and chill the stash. Then, it will enqueue the stash + /// to be checked in further blocks. + /// + /// If by the time this is called, the stash is actually eligible for fast-unstake, then + /// they are guaranteed to remain eligible, because the call will chill them as well. + /// + /// If the check works, the entire staking data is removed, i.e. the stash is fully + /// unstaked. + /// + /// If the check fails, the stash remains chilled and waiting for being unbonded as in with + /// the normal staking system, but they lose part of their unbonding chunks due to consuming + /// the chain's resources. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::register_fast_unstake())] + pub fn register_fast_unstake(origin: OriginFor) -> DispatchResult { + let ctrl = ensure_signed(origin)?; + + ensure!(ErasToCheckPerBlock::::get() != 0, >::CallNotAllowed); + let stash_account = + T::Staking::stash_by_ctrl(&ctrl).map_err(|_| Error::::NotController)?; + ensure!(!Queue::::contains_key(&stash_account), Error::::AlreadyQueued); + ensure!(!Self::is_head(&stash_account), Error::::AlreadyHead); + ensure!(!T::Staking::is_unbonding(&stash_account)?, Error::::NotFullyBonded); + + // chill and fully unstake. + T::Staking::chill(&stash_account)?; + T::Staking::fully_unbond(&stash_account)?; + + T::Currency::reserve(&stash_account, T::Deposit::get())?; + + // enqueue them. + Queue::::insert(stash_account, T::Deposit::get()); + Ok(()) + } + + /// Deregister oneself from the fast-unstake. + /// + /// This is useful if one is registered, they are still waiting, and they change their mind. + /// + /// Note that the associated stash is still fully unbonded and chilled as a consequence of + /// calling `register_fast_unstake`. This should probably be followed by a call to + /// `Staking::rebond`. + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::deregister())] + pub fn deregister(origin: OriginFor) -> DispatchResult { + let ctrl = ensure_signed(origin)?; + + ensure!(ErasToCheckPerBlock::::get() != 0, >::CallNotAllowed); + + let stash_account = + T::Staking::stash_by_ctrl(&ctrl).map_err(|_| Error::::NotController)?; + ensure!(Queue::::contains_key(&stash_account), Error::::NotQueued); + ensure!(!Self::is_head(&stash_account), Error::::AlreadyHead); + let deposit = Queue::::take(stash_account.clone()); + + if let Some(deposit) = deposit.defensive() { + let remaining = T::Currency::unreserve(&stash_account, deposit); + if !remaining.is_zero() { + Self::halt("not enough balance to unreserve"); + } + } + + Ok(()) + } + + /// Control the operation of this pallet. + /// + /// Dispatch origin must be signed by the [`Config::ControlOrigin`]. + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::control())] + pub fn control(origin: OriginFor, unchecked_eras_to_check: EraIndex) -> DispatchResult { + let _ = T::ControlOrigin::ensure_origin(origin)?; + ErasToCheckPerBlock::::put(unchecked_eras_to_check); + Ok(()) + } + } + + impl Pallet { + /// Returns `true` if `staker` is anywhere to be found in the `head`. + pub(crate) fn is_head(staker: &T::AccountId) -> bool { + Head::::get().map_or(false, |UnstakeRequest { stashes, .. }| { + stashes.iter().any(|(stash, _)| stash == staker) + }) + } + + /// Halt the operations of this pallet. + pub(crate) fn halt(reason: &'static str) { + frame_support::defensive!(reason); + ErasToCheckPerBlock::::put(0); + Self::deposit_event(Event::::InternalError) + } + + /// process up to `remaining_weight`. + /// + /// Returns the actual weight consumed. + /// + /// Written for readability in mind, not efficiency. For example: + /// + /// 1. We assume this is only ever called once per `on_idle`. This is because we know that + /// in all use cases, even a single nominator cannot be unbonded in a single call. Multiple + /// calls to this function are thus not needed. + /// + /// 2. We will only mark a staker as unstaked if at the beginning of a check cycle, they are + /// found out to have no eras to check. At the end of a check cycle, even if they are fully + /// checked, we don't finish the process. + pub(crate) fn do_on_idle(remaining_weight: Weight) -> Weight { + let mut eras_to_check_per_block = ErasToCheckPerBlock::::get(); + if eras_to_check_per_block.is_zero() { + return T::DbWeight::get().reads(1) + } + + // NOTE: here we're assuming that the number of validators has only ever increased, + // meaning that the number of exposures to check is either this per era, or less. + let validator_count = T::Staking::desired_validator_count(); + + // determine the number of eras to check. This is based on both `ErasToCheckPerBlock` + // and `remaining_weight` passed on to us from the runtime executive. + let max_weight = |v, u| { + ::WeightInfo::on_idle_check(v * u) + .max(::WeightInfo::on_idle_unstake()) + }; + while max_weight(validator_count, eras_to_check_per_block).any_gt(remaining_weight) { + eras_to_check_per_block.saturating_dec(); + if eras_to_check_per_block.is_zero() { + log!(debug, "early existing because eras_to_check_per_block is zero"); + return T::DbWeight::get().reads(2) + } + } + + if T::Staking::election_ongoing() { + // NOTE: we assume `ongoing` does not consume any weight. + // there is an ongoing election -- we better not do anything. Imagine someone is not + // exposed anywhere in the last era, and the snapshot for the election is already + // taken. In this time period, we don't want to accidentally unstake them. + return T::DbWeight::get().reads(2) + } + + let UnstakeRequest { stashes, mut checked } = match Head::::take().or_else(|| { + // NOTE: there is no order guarantees in `Queue`. + let stashes: BoundedVec<_, T::BatchSize> = Queue::::drain() + .take(T::BatchSize::get() as usize) + .collect::>() + .try_into() + .expect("take ensures bound is met; qed"); + if stashes.is_empty() { + None + } else { + Some(UnstakeRequest { stashes, checked: Default::default() }) + } + }) { + None => { + // There's no `Head` and nothing in the `Queue`, nothing to do here. + return T::DbWeight::get().reads(4) + }, + Some(head) => head, + }; + + log!( + debug, + "checking {:?} stashes, eras_to_check_per_block = {:?}, remaining_weight = {:?}", + stashes.len(), + eras_to_check_per_block, + remaining_weight + ); + + // the range that we're allowed to check in this round. + let current_era = T::Staking::current_era(); + let bonding_duration = T::Staking::bonding_duration(); + + // prune all the old eras that we don't care about. This will help us keep the bound + // of `checked`. + checked.retain(|e| *e >= current_era.saturating_sub(bonding_duration)); + + let unchecked_eras_to_check = { + // get the last available `bonding_duration` eras up to current era in reverse + // order. + let total_check_range = (current_era.saturating_sub(bonding_duration)..= + current_era) + .rev() + .collect::>(); + debug_assert!( + total_check_range.len() <= (bonding_duration + 1) as usize, + "{:?}", + total_check_range + ); + + // remove eras that have already been checked, take a maximum of + // eras_to_check_per_block. + total_check_range + .into_iter() + .filter(|e| !checked.contains(e)) + .take(eras_to_check_per_block as usize) + .collect::>() + }; + + log!( + debug, + "{} eras to check: {:?}", + unchecked_eras_to_check.len(), + unchecked_eras_to_check + ); + + let unstake_stash = |stash: T::AccountId, deposit| { + let result = T::Staking::force_unstake(stash.clone()); + let remaining = T::Currency::unreserve(&stash, deposit); + if !remaining.is_zero() { + Self::halt("not enough balance to unreserve"); + } else { + log!(info, "unstaked {:?}, outcome: {:?}", stash, result); + Self::deposit_event(Event::::Unstaked { stash, result }); + } + }; + + let check_stash = |stash, deposit, eras_checked: &mut u32| { + let is_exposed = unchecked_eras_to_check.iter().any(|e| { + eras_checked.saturating_inc(); + T::Staking::is_exposed_in_era(&stash, e) + }); + + if is_exposed { + T::Currency::slash_reserved(&stash, deposit); + log!(info, "slashed {:?} by {:?}", stash, deposit); + Self::deposit_event(Event::::Slashed { stash, amount: deposit }); + false + } else { + true + } + }; + + if unchecked_eras_to_check.is_empty() { + // `stash` is not exposed in any era now -- we can let go of them now. + stashes.into_iter().for_each(|(stash, deposit)| unstake_stash(stash, deposit)); + Self::deposit_event(Event::::BatchFinished); + ::WeightInfo::on_idle_unstake() + } else { + // eras checked so far. + let mut eras_checked = 0u32; + + let pre_length = stashes.len(); + let stashes: BoundedVec<(T::AccountId, BalanceOf), T::BatchSize> = stashes + .into_iter() + .filter(|(stash, deposit)| { + check_stash(stash.clone(), *deposit, &mut eras_checked) + }) + .collect::>() + .try_into() + .expect("filter can only lessen the length; still in bound; qed"); + let post_length = stashes.len(); + + log!( + debug, + "checked {:?} eras, pre stashes: {:?}, post: {:?}", + eras_checked, + pre_length, + post_length, + ); + + match checked.try_extend(unchecked_eras_to_check.clone().into_iter()) { + Ok(_) => + if stashes.is_empty() { + Self::deposit_event(Event::::BatchFinished); + } else { + Head::::put(UnstakeRequest { stashes, checked }); + Self::deposit_event(Event::::BatchChecked { + eras: unchecked_eras_to_check, + }); + }, + Err(_) => { + // don't put the head back in -- there is an internal error in the pallet. + Self::halt("checked is pruned via retain above") + }, + } + + ::WeightInfo::on_idle_check(validator_count * eras_checked) + } + } + } +} diff --git a/frame/fast-unstake/src/migrations.rs b/frame/fast-unstake/src/migrations.rs new file mode 100644 index 0000000000000..d2778d48b1b6e --- /dev/null +++ b/frame/fast-unstake/src/migrations.rs @@ -0,0 +1,79 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 mod v1 { + use crate::{types::BalanceOf, *}; + use frame_support::{ + storage::unhashed, + traits::{Defensive, Get, GetStorageVersion, OnRuntimeUpgrade}, + weights::Weight, + }; + use sp_staking::EraIndex; + use sp_std::prelude::*; + + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log!( + info, + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + + if current == 1 && onchain == 0 { + // update the version nonetheless. + current.put::>(); + + // if a head exists, then we put them back into the queue. + if Head::::exists() { + if let Some((stash, _, deposit)) = + unhashed::take::<(T::AccountId, Vec, BalanceOf)>( + &Head::::hashed_key(), + ) + .defensive() + { + Queue::::insert(stash, deposit); + } else { + // not much we can do here -- head is already deleted. + } + T::DbWeight::get().reads_writes(2, 3) + } else { + T::DbWeight::get().reads(2) + } + } else { + log!(info, "Migration did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + assert_eq!(Pallet::::on_chain_storage_version(), 0); + Ok(Default::default()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), &'static str> { + assert_eq!(Pallet::::on_chain_storage_version(), 1); + Ok(()) + } + } +} diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs new file mode 100644 index 0000000000000..b67dcf581ed97 --- /dev/null +++ b/frame/fast-unstake/src/mock.rs @@ -0,0 +1,379 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::{self as fast_unstake}; +use frame_benchmarking::frame_support::assert_ok; +use frame_support::{ + pallet_prelude::*, + parameter_types, + traits::{ConstU64, Currency}, + weights::constants::WEIGHT_REF_TIME_PER_SECOND, +}; +use sp_runtime::traits::{Convert, IdentityLookup}; + +use pallet_staking::{Exposure, IndividualExposure, StakerStatus}; +use sp_std::prelude::*; + +pub type AccountId = u128; +pub type AccountIndex = u32; +pub type BlockNumber = u64; +pub type Balance = u128; +pub type T = Runtime; + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + ); +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = BlockWeights; + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = AccountIndex; + type BlockNumber = BlockNumber; + type RuntimeCall = RuntimeCall; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = sp_runtime::testing::Header; + type RuntimeEvent = RuntimeEvent; + type 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>; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +parameter_types! { + pub static ExistentialDeposit: Balance = 1; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = ConstU32<128>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +pallet_staking_reward_curve::build! { + const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} + +parameter_types! { + pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; + pub static BondingDuration: u32 = 3; + pub static CurrentEra: u32 = 0; + pub static Ongoing: bool = false; + pub static MaxWinners: u32 = 100; +} + +pub struct MockElection; +impl frame_election_provider_support::ElectionProviderBase for MockElection { + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type MaxWinners = MaxWinners; + type DataProvider = Staking; + type Error = (); +} + +impl frame_election_provider_support::ElectionProvider for MockElection { + fn ongoing() -> bool { + Ongoing::get() + } + fn elect() -> Result, Self::Error> { + Err(()) + } +} + +impl pallet_staking::Config for Runtime { + type MaxNominations = ConstU32<16>; + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type RewardRemainder = (); + type RuntimeEvent = RuntimeEvent; + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = BondingDuration; + type SessionInterface = (); + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = (); + type HistoryDepth = ConstU32<84>; + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = MockElection; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = (); + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub static Deposit: u128 = 7; + pub static BatchSize: u32 = 1; +} + +impl fast_unstake::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Deposit = Deposit; + type Currency = Balances; + type Staking = Staking; + type ControlOrigin = frame_system::EnsureRoot; + type BatchSize = BatchSize; + type WeightInfo = (); +} + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + Staking: pallet_staking, + FastUnstake: fast_unstake, + } +); + +parameter_types! { + static FastUnstakeEvents: u32 = 0; +} + +pub(crate) fn fast_unstake_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::FastUnstake(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = FastUnstakeEvents::get(); + FastUnstakeEvents::set(events.len() as u32); + events.into_iter().skip(already_seen as usize).collect() +} + +pub struct ExtBuilder { + unexposed: Vec<(AccountId, AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + unexposed: vec![ + (1, 2, 7 + 100), + (3, 4, 7 + 100), + (5, 6, 7 + 100), + (7, 8, 7 + 100), + (9, 10, 7 + 100), + ], + } + } +} + +pub(crate) const VALIDATORS_PER_ERA: AccountId = 32; +pub(crate) const VALIDATOR_PREFIX: AccountId = 100; +pub(crate) const NOMINATORS_PER_VALIDATOR_PER_ERA: AccountId = 4; +pub(crate) const NOMINATOR_PREFIX: AccountId = 1000; + +impl ExtBuilder { + pub(crate) fn register_stakers_for_era(era: u32) { + // validators are prefixed with 100 and nominators with 1000 to prevent conflict. Make sure + // all the other accounts used in tests are below 100. Also ensure here that we don't + // overlap. + assert!(VALIDATOR_PREFIX + VALIDATORS_PER_ERA < NOMINATOR_PREFIX); + + (VALIDATOR_PREFIX..VALIDATOR_PREFIX + VALIDATORS_PER_ERA) + .map(|v| { + // for the sake of sanity, let's register this taker as an actual validator. + let others = (NOMINATOR_PREFIX.. + (NOMINATOR_PREFIX + NOMINATORS_PER_VALIDATOR_PER_ERA)) + .map(|n| IndividualExposure { who: n, value: 0 as Balance }) + .collect::>(); + (v, Exposure { total: 0, own: 0, others }) + }) + .for_each(|(validator, exposure)| { + pallet_staking::ErasStakers::::insert(era, validator, exposure); + }); + } + + pub(crate) fn batch(self, size: u32) -> Self { + BatchSize::set(size); + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = + frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let validators_range = VALIDATOR_PREFIX..VALIDATOR_PREFIX + VALIDATORS_PER_ERA; + let nominators_range = + NOMINATOR_PREFIX..NOMINATOR_PREFIX + NOMINATORS_PER_VALIDATOR_PER_ERA; + + let _ = pallet_balances::GenesisConfig:: { + balances: self + .unexposed + .clone() + .into_iter() + .map(|(stash, _, balance)| (stash, balance * 2)) + .chain( + self.unexposed + .clone() + .into_iter() + .map(|(_, ctrl, balance)| (ctrl, balance * 2)), + ) + .chain(validators_range.clone().map(|x| (x, 7 + 100))) + .chain(nominators_range.clone().map(|x| (x, 7 + 100))) + .collect::>(), + } + .assimilate_storage(&mut storage); + + let _ = pallet_staking::GenesisConfig:: { + stakers: self + .unexposed + .into_iter() + .map(|(x, y, z)| (x, y, z, pallet_staking::StakerStatus::Nominator(vec![42]))) + .chain(validators_range.map(|x| (x, x, 100, StakerStatus::Validator))) + .chain(nominators_range.map(|x| (x, x, 100, StakerStatus::Nominator(vec![x])))) + .collect::>(), + ..Default::default() + } + .assimilate_storage(&mut storage); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // for events to be deposited. + frame_system::Pallet::::set_block_number(1); + + for era in 0..=(BondingDuration::get()) { + Self::register_stakers_for_era(era); + } + + // because we read this value as a measure of how many validators we have. + pallet_staking::ValidatorCount::::put(VALIDATORS_PER_ERA as u32); + }); + + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + }) + } +} + +pub(crate) fn run_to_block(n: u64, on_idle: bool) { + let current_block = System::block_number(); + assert!(n > current_block); + while System::block_number() < n { + Balances::on_finalize(System::block_number()); + Staking::on_finalize(System::block_number()); + FastUnstake::on_finalize(System::block_number()); + + System::set_block_number(System::block_number() + 1); + + Balances::on_initialize(System::block_number()); + Staking::on_initialize(System::block_number()); + FastUnstake::on_initialize(System::block_number()); + if on_idle { + FastUnstake::on_idle(System::block_number(), BlockWeights::get().max_block); + } + } +} + +pub(crate) fn next_block(on_idle: bool) { + let current = System::block_number(); + run_to_block(current + 1, on_idle); +} + +pub fn assert_unstaked(stash: &AccountId) { + assert!(!pallet_staking::Bonded::::contains_key(stash)); + assert!(!pallet_staking::Payee::::contains_key(stash)); + assert!(!pallet_staking::Validators::::contains_key(stash)); + assert!(!pallet_staking::Nominators::::contains_key(stash)); +} + +pub fn create_exposed_nominator(exposed: AccountId, era: u32) { + // create an exposed nominator in era 1 + pallet_staking::ErasStakers::::mutate(era, VALIDATORS_PER_ERA, |expo| { + expo.others.push(IndividualExposure { who: exposed, value: 0 as Balance }); + }); + Balances::make_free_balance_be(&exposed, 100); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(exposed), + exposed, + 10, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(exposed), vec![exposed])); + // register the exposed one. + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(exposed))); +} diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs new file mode 100644 index 0000000000000..522aa1d0fac28 --- /dev/null +++ b/frame/fast-unstake/src/tests.rs @@ -0,0 +1,1200 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Tests for pallet-fast-unstake. + +use super::*; +use crate::{mock::*, types::*, weights::WeightInfo, Event}; +use frame_support::{assert_noop, assert_ok, bounded_vec, pallet_prelude::*, traits::Currency}; +use pallet_staking::{CurrentEra, RewardDestination}; + +use sp_runtime::traits::BadOrigin; +use sp_staking::StakingInterface; + +#[test] +fn test_setup_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(Staking::bonding_duration(), 3); + }); +} + +#[test] +fn register_works() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + // Controller account registers for fast unstake. + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + // Ensure stash is in the queue. + assert_ne!(Queue::::get(1), None); + }); +} + +#[test] +fn register_insufficient_funds_fails() { + use pallet_balances::Error as BalancesError; + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + ::Currency::make_free_balance_be(&1, 3); + + // Controller account registers for fast unstake. + assert_noop!( + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), + BalancesError::::InsufficientBalance, + ); + + // Ensure stash is in the queue. + assert_eq!(Queue::::get(1), None); + }); +} + +#[test] +fn register_disabled_fails() { + ExtBuilder::default().build_and_execute(|| { + assert_noop!( + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), + Error::::CallNotAllowed + ); + }); +} + +#[test] +fn cannot_register_if_not_bonded() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + // Mint accounts 1 and 2 with 200 tokens. + for _ in 1..2 { + let _ = Balances::make_free_balance_be(&1, 200); + } + // Attempt to fast unstake. + assert_noop!( + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1)), + Error::::NotController + ); + }); +} + +#[test] +fn cannot_register_if_in_queue() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + // Insert some Queue item + Queue::::insert(1, 10); + // Cannot re-register, already in queue + assert_noop!( + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), + Error::::AlreadyQueued + ); + }); +} + +#[test] +fn cannot_register_if_head() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + // Insert some Head item for stash + Head::::put(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![], + }); + // Controller attempts to regsiter + assert_noop!( + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), + Error::::AlreadyHead + ); + }); +} + +#[test] +fn cannot_register_if_has_unlocking_chunks() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + // Start unbonding half of staked tokens + assert_ok!(Staking::unbond(RuntimeOrigin::signed(2), 50_u128)); + // Cannot register for fast unstake with unlock chunks active + assert_noop!( + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), + Error::::NotFullyBonded + ); + }); +} + +#[test] +fn deregister_works() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + + assert_eq!(::Currency::reserved_balance(&1), 0); + + // Controller account registers for fast unstake. + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_eq!(::Currency::reserved_balance(&1), Deposit::get()); + + // Controller then changes mind and deregisters. + assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(2))); + assert_eq!(::Currency::reserved_balance(&1), 0); + + // Ensure stash no longer exists in the queue. + assert_eq!(Queue::::get(1), None); + }); +} + +#[test] +fn deregister_disabled_fails() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + ErasToCheckPerBlock::::put(0); + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::::CallNotAllowed); + }); +} + +#[test] +fn cannot_deregister_if_not_controller() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + // Controller account registers for fast unstake. + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + // Stash tries to deregister. + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(1)), Error::::NotController); + }); +} + +#[test] +fn cannot_deregister_if_not_queued() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + // Controller tries to deregister without first registering + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::::NotQueued); + }); +} + +#[test] +fn cannot_deregister_already_head() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + // Controller attempts to register, should fail + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + // Insert some Head item for stash. + Head::::put(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![], + }); + // Controller attempts to deregister + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::::AlreadyHead); + }); +} + +#[test] +fn control_works() { + ExtBuilder::default().build_and_execute(|| { + // account with control (root) origin wants to only check 1 era per block. + assert_ok!(FastUnstake::control(RuntimeOrigin::root(), 1_u32)); + }); +} + +#[test] +fn control_must_be_control_origin() { + ExtBuilder::default().build_and_execute(|| { + // account without control (root) origin wants to only check 1 era per block. + assert_noop!(FastUnstake::control(RuntimeOrigin::signed(1), 1_u32), BadOrigin); + }); +} + +mod on_idle { + use super::*; + + #[test] + fn early_exit() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // set up Queue item + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_eq!(Queue::::get(1), Some(Deposit::get())); + + // call on_idle with no remaining weight + FastUnstake::on_idle(System::block_number(), Weight::from_ref_time(0)); + + // assert nothing changed in Queue and Head + assert_eq!(Head::::get(), None); + assert_eq!(Queue::::get(1), Some(Deposit::get())); + }); + } + + #[test] + fn respects_weight() { + ExtBuilder::default().build_and_execute(|| { + // we want to check all eras in one block... + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // given + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_eq!(Queue::::get(1), Some(Deposit::get())); + + assert_eq!(Queue::::count(), 1); + assert_eq!(Head::::get(), None); + + // when: call fast unstake with not enough weight to process the whole thing, just one + // era. + let remaining_weight = ::WeightInfo::on_idle_check( + pallet_staking::ValidatorCount::::get() * 1, + ); + assert_eq!(FastUnstake::on_idle(0, remaining_weight), remaining_weight); + + // then + assert_eq!( + fast_unstake_events_since_last_call(), + vec![Event::BatchChecked { eras: vec![3] }] + ); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3] + }) + ); + + // when: another 1 era. + let remaining_weight = ::WeightInfo::on_idle_check( + pallet_staking::ValidatorCount::::get() * 1, + ); + assert_eq!(FastUnstake::on_idle(0, remaining_weight), remaining_weight); + + // then: + assert_eq!( + fast_unstake_events_since_last_call(), + vec![Event::BatchChecked { eras: bounded_vec![2] }] + ); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2] + }) + ); + + // when: then 5 eras, we only need 2 more. + let remaining_weight = ::WeightInfo::on_idle_check( + pallet_staking::ValidatorCount::::get() * 5, + ); + assert_eq!( + FastUnstake::on_idle(0, remaining_weight), + // note the amount of weight consumed: 2 eras worth of weight. + ::WeightInfo::on_idle_check( + pallet_staking::ValidatorCount::::get() * 2, + ) + ); + + // then: + assert_eq!( + fast_unstake_events_since_last_call(), + vec![Event::BatchChecked { eras: vec![1, 0] }] + ); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + + // when: not enough weight to unstake: + let remaining_weight = + ::WeightInfo::on_idle_unstake() - Weight::from_ref_time(1); + assert_eq!(FastUnstake::on_idle(0, remaining_weight), Weight::from_ref_time(0)); + + // then nothing happens: + assert_eq!(fast_unstake_events_since_last_call(), vec![]); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + + // when: enough weight to get over at least one iteration: then we are unblocked and can + // unstake. + let remaining_weight = ::WeightInfo::on_idle_check( + pallet_staking::ValidatorCount::::get() * 1, + ); + assert_eq!( + FastUnstake::on_idle(0, remaining_weight), + ::WeightInfo::on_idle_unstake() + ); + + // then we finish the unbonding: + assert_eq!( + fast_unstake_events_since_last_call(), + vec![Event::Unstaked { stash: 1, result: Ok(()) }, Event::BatchFinished], + ); + assert_eq!(Head::::get(), None,); + + assert_unstaked(&1); + }); + } + + #[test] + fn if_head_not_set_one_random_fetched_from_queue() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // given + assert_eq!(::Currency::reserved_balance(&1), 0); + + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(6))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(10))); + + assert_eq!(::Currency::reserved_balance(&1), Deposit::get()); + + assert_eq!(Queue::::count(), 5); + assert_eq!(Head::::get(), None); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + assert_eq!(Queue::::count(), 4); + + // when + next_block(true); + + // then + assert_eq!(Head::::get(), None,); + assert_eq!(Queue::::count(), 4); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(5, Deposit::get())], + checked: bounded_vec![3, 2, 1, 0] + }), + ); + assert_eq!(Queue::::count(), 3); + + assert_eq!(::Currency::reserved_balance(&1), 0); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished, + Event::BatchChecked { eras: vec![3, 2, 1, 0] } + ] + ); + }); + } + + #[test] + fn successful_multi_queue() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // register multi accounts for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_eq!(Queue::::get(1), Some(Deposit::get())); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); + assert_eq!(Queue::::get(3), Some(Deposit::get())); + + // assert 2 queue items are in Queue & None in Head to start with + assert_eq!(Queue::::count(), 2); + assert_eq!(Head::::get(), None); + + // process on idle and check eras for next Queue item + next_block(true); + + // process on idle & let go of current Head + next_block(true); + + // confirm Head / Queue items remaining + assert_eq!(Queue::::count(), 1); + assert_eq!(Head::::get(), None); + + // process on idle and check eras for next Queue item + next_block(true); + + // process on idle & let go of current Head + next_block(true); + + // Head & Queue should now be empty + assert_eq!(Head::::get(), None); + assert_eq!(Queue::::count(), 0); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished, + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 3, result: Ok(()) }, + Event::BatchFinished, + ] + ); + + assert_unstaked(&1); + assert_unstaked(&3); + }); + } + + #[test] + fn successful_unstake() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_eq!(Queue::::get(1), Some(Deposit::get())); + + // process on idle + next_block(true); + + // assert queue item has been moved to head + assert_eq!(Queue::::get(1), None); + + // assert head item present + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + + next_block(true); + assert_eq!(Head::::get(), None,); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished + ] + ); + assert_unstaked(&1); + }); + } + + #[test] + fn successful_unstake_all_eras_per_block() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + Balances::make_free_balance_be(&2, 100); + + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_eq!(Queue::::get(1), Some(Deposit::get())); + + // process on idle + next_block(true); + + // assert queue item has been moved to head + assert_eq!(Queue::::get(1), None); + + // assert head item present + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + + next_block(true); + assert_eq!(Head::::get(), None,); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished + ] + ); + assert_unstaked(&1); + }); + } + + #[test] + fn successful_unstake_one_era_per_block() { + ExtBuilder::default().build_and_execute(|| { + // put 1 era per block + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); + + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_eq!(Queue::::get(1), Some(Deposit::get())); + + // process on idle + next_block(true); + + // assert queue item has been moved to head + assert_eq!(Queue::::get(1), None); + + // assert head item present + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3] + }) + ); + + next_block(true); + + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2] + }) + ); + + next_block(true); + + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 1] + }) + ); + + next_block(true); + + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + + next_block(true); + + assert_eq!(Head::::get(), None,); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3] }, + Event::BatchChecked { eras: vec![2] }, + Event::BatchChecked { eras: vec![1] }, + Event::BatchChecked { eras: vec![0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished + ] + ); + assert_unstaked(&1); + }); + } + + #[test] + fn old_checked_era_pruned() { + // the only scenario where checked era pruning (checked.retain) comes handy is a follows: + // the whole vector is full and at capacity and in the next call we are ready to unstake, + // but then a new era happens. + ExtBuilder::default().build_and_execute(|| { + // given + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); + + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_eq!(Queue::::get(1), Some(Deposit::get())); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3] + }) + ); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2] + }) + ); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 1] + }) + ); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + + // when: a new era happens right before one is free. + CurrentEra::::put(CurrentEra::::get().unwrap() + 1); + ExtBuilder::register_stakers_for_era(CurrentEra::::get().unwrap()); + + // then + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + // note era 0 is pruned to keep the vector length sane. + checked: bounded_vec![3, 2, 1, 4], + }) + ); + + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3] }, + Event::BatchChecked { eras: vec![2] }, + Event::BatchChecked { eras: vec![1] }, + Event::BatchChecked { eras: vec![0] }, + Event::BatchChecked { eras: vec![4] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished + ] + ); + assert_unstaked(&1); + }); + } + + #[test] + fn unstake_paused_mid_election() { + ExtBuilder::default().build_and_execute(|| { + // give: put 1 era per block + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); + + // register for fast unstake + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + + // process 2 blocks + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3] + }) + ); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2] + }) + ); + + // when + Ongoing::set(true); + + // then nothing changes + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2] + }) + ); + + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2] + }) + ); + + // then we register a new era. + Ongoing::set(false); + CurrentEra::::put(CurrentEra::::get().unwrap() + 1); + ExtBuilder::register_stakers_for_era(CurrentEra::::get().unwrap()); + + // then we can progress again, but notice that the new era that had to be checked. + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 4] + }) + ); + + // progress to end + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get())], + checked: bounded_vec![3, 2, 4, 1] + }) + ); + + // but notice that we don't care about era 0 instead anymore! we're done. + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3] }, + Event::BatchChecked { eras: vec![2] }, + Event::BatchChecked { eras: vec![4] }, + Event::BatchChecked { eras: vec![1] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished + ] + ); + + assert_unstaked(&1); + }); + } + + #[test] + fn exposed_nominator_cannot_unstake() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); + + // create an exposed nominator in era 1 + let exposed = 666; + create_exposed_nominator(exposed, 1); + + // a few blocks later, we realize they are slashed + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(exposed, Deposit::get())], + checked: bounded_vec![3] + }) + ); + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(exposed, Deposit::get())], + checked: bounded_vec![3, 2] + }) + ); + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3] }, + Event::BatchChecked { eras: vec![2] }, + Event::Slashed { stash: exposed, amount: Deposit::get() }, + Event::BatchFinished + ] + ); + }); + } + + #[test] + fn exposed_nominator_cannot_unstake_multi_check() { + ExtBuilder::default().build_and_execute(|| { + // same as the previous check, but we check 2 eras per block, and we make the exposed be + // exposed in era 0, so that it is detected halfway in a check era. + ErasToCheckPerBlock::::put(2); + CurrentEra::::put(BondingDuration::get()); + + // create an exposed nominator in era 0 + let exposed = 666; + create_exposed_nominator(exposed, 0); + + // a few blocks later, we realize they are slashed + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(exposed, Deposit::get())], + checked: bounded_vec![3, 2] + }) + ); + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + // we slash them + vec![ + Event::BatchChecked { eras: vec![3, 2] }, + Event::Slashed { stash: exposed, amount: Deposit::get() }, + Event::BatchFinished + ] + ); + }); + } + + #[test] + fn validators_cannot_bail() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // a validator switches role and register... + assert_ok!(Staking::nominate( + RuntimeOrigin::signed(VALIDATOR_PREFIX), + vec![VALIDATOR_PREFIX] + )); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(VALIDATOR_PREFIX))); + + // but they indeed are exposed! + assert!(pallet_staking::ErasStakers::::contains_key( + BondingDuration::get() - 1, + VALIDATOR_PREFIX + )); + + // process a block, this validator is exposed and has been slashed. + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![Event::Slashed { stash: 100, amount: Deposit::get() }, Event::BatchFinished] + ); + }); + } + + #[test] + fn unexposed_validator_can_fast_unstake() { + ExtBuilder::default().build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + // create a new validator that 100% not exposed. + Balances::make_free_balance_be(&42, 100 + Deposit::get()); + assert_ok!(Staking::bond(RuntimeOrigin::signed(42), 42, 10, RewardDestination::Staked)); + assert_ok!(Staking::validate(RuntimeOrigin::signed(42), Default::default())); + + // let them register: + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(42))); + + // 2 block's enough to unstake them. + next_block(true); + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(42, Deposit::get())], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + next_block(true); + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 42, result: Ok(()) }, + Event::BatchFinished + ] + ); + }); + } +} + +mod batched { + use super::*; + + #[test] + fn single_block_batched_successful() { + ExtBuilder::default().batch(3).build_and_execute(|| { + ErasToCheckPerBlock::::put(BondingDuration::get() + 1); + CurrentEra::::put(BondingDuration::get()); + + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(6))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8))); + + assert_eq!(Queue::::count(), 4); + assert_eq!(Head::::get(), None); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![ + (1, Deposit::get()), + (5, Deposit::get()), + (7, Deposit::get()) + ], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + assert_eq!(Queue::::count(), 1); + + // when + next_block(true); + + // then + assert_eq!(Head::::get(), None); + assert_eq!(Queue::::count(), 1); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::Unstaked { stash: 5, result: Ok(()) }, + Event::Unstaked { stash: 7, result: Ok(()) }, + Event::BatchFinished + ] + ); + }); + } + + #[test] + fn multi_block_batched_successful() { + ExtBuilder::default().batch(3).build_and_execute(|| { + ErasToCheckPerBlock::::put(2); + CurrentEra::::put(BondingDuration::get()); + + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(6))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8))); + + assert_eq!(Queue::::count(), 4); + assert_eq!(Head::::get(), None); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![ + (1, Deposit::get()), + (5, Deposit::get()), + (7, Deposit::get()) + ], + checked: bounded_vec![3, 2] + }) + ); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![ + (1, Deposit::get()), + (5, Deposit::get()), + (7, Deposit::get()) + ], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + + // when + next_block(true); + + // then + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3, 2] }, + Event::BatchChecked { eras: vec![1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::Unstaked { stash: 5, result: Ok(()) }, + Event::Unstaked { stash: 7, result: Ok(()) }, + Event::BatchFinished + ] + ); + }); + } + + #[test] + fn multi_block_batched_some_fail() { + ExtBuilder::default().batch(4).build_and_execute(|| { + ErasToCheckPerBlock::::put(2); + CurrentEra::::put(BondingDuration::get()); + + // register two good ones. + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); + create_exposed_nominator(666, 1); + create_exposed_nominator(667, 3); + + // then + assert_eq!(Queue::::count(), 4); + assert_eq!(Head::::get(), None); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![ + (1, Deposit::get()), + (3, Deposit::get()), + (666, Deposit::get()) + ], + checked: bounded_vec![3, 2] + }) + ); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get()), (3, Deposit::get()),], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + + // when + next_block(true); + + // then + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Slashed { stash: 667, amount: 7 }, + Event::BatchChecked { eras: vec![3, 2] }, + Event::Slashed { stash: 666, amount: 7 }, + Event::BatchChecked { eras: vec![1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::Unstaked { stash: 3, result: Ok(()) }, + Event::BatchFinished + ] + ); + }); + } + + #[test] + fn multi_block_batched_all_fail_early_exit() { + ExtBuilder::default().batch(2).build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); + + // register two bad ones. + create_exposed_nominator(666, 3); + create_exposed_nominator(667, 2); + + // then + assert_eq!(Queue::::count(), 2); + assert_eq!(Head::::get(), None); + + // when we progress a block.. + next_block(true); + + // ..and register two good ones. + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); + + // then one of the bad ones is reaped. + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(667, Deposit::get())], + checked: bounded_vec![3] + }) + ); + + // when we go to next block + next_block(true); + + // then the head is empty, we early terminate the batch. + assert_eq!(Head::::get(), None); + + // upon next block, we will assemble a new head. + next_block(true); + + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get()), (3, Deposit::get()),], + checked: bounded_vec![3] + }) + ); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Slashed { stash: 666, amount: Deposit::get() }, + Event::BatchChecked { eras: vec![3] }, + Event::Slashed { stash: 667, amount: Deposit::get() }, + Event::BatchFinished, + Event::BatchChecked { eras: vec![3] } + ] + ); + }); + } +} diff --git a/frame/fast-unstake/src/types.rs b/frame/fast-unstake/src/types.rs new file mode 100644 index 0000000000000..34ca6517f3168 --- /dev/null +++ b/frame/fast-unstake/src/types.rs @@ -0,0 +1,41 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Types used in the Fast Unstake pallet. + +use crate::{Config, MaxChecking}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + traits::Currency, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use scale_info::TypeInfo; +use sp_staking::EraIndex; +use sp_std::prelude::*; + +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +/// An unstake request. +#[derive( + Encode, Decode, EqNoBound, PartialEqNoBound, Clone, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen, +)] +#[scale_info(skip_type_params(T))] +pub struct UnstakeRequest { + /// This list of stashes being processed in this request, and their corresponding deposit. + pub(crate) stashes: BoundedVec<(T::AccountId, BalanceOf), T::BatchSize>, + /// The list of eras for which they have been checked. + pub(crate) checked: BoundedVec>, +} diff --git a/frame/fast-unstake/src/weights.rs b/frame/fast-unstake/src/weights.rs new file mode 100644 index 0000000000000..6001250e8c24d --- /dev/null +++ b/frame/fast-unstake/src/weights.rs @@ -0,0 +1,212 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_fast_unstake +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_fast_unstake +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/fast-unstake/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_fast_unstake. +pub trait WeightInfo { + fn on_idle_unstake() -> Weight; + fn on_idle_check(x: u32, ) -> Weight; + fn register_fast_unstake() -> Weight; + fn deregister() -> Weight; + fn control() -> Weight; +} + +/// Weights for pallet_fast_unstake using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: FastUnstake Head (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: Staking Ledger (r:0 w:1) + // Storage: Staking Payee (r:0 w:1) + fn on_idle_unstake() -> Weight { + // Minimum execution time: 82_426 nanoseconds. + Weight::from_ref_time(83_422_000 as u64) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: FastUnstake Head (r:1 w:1) + // Storage: FastUnstake Queue (r:2 w:1) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking ErasStakers (r:1344 w:0) + /// The range of component `x` is `[672, 86016]`. + fn on_idle_check(x: u32, ) -> Weight { + // Minimum execution time: 13_932_777 nanoseconds. + Weight::from_ref_time(13_996_029_000 as u64) + // Standard Error: 16_878 + .saturating_add(Weight::from_ref_time(18_113_540 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(345 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: FastUnstake Queue (r:1 w:1) + // Storage: FastUnstake Head (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + fn register_fast_unstake() -> Weight { + // Minimum execution time: 120_190 nanoseconds. + Weight::from_ref_time(121_337_000 as u64) + .saturating_add(T::DbWeight::get().reads(14 as u64)) + .saturating_add(T::DbWeight::get().writes(9 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: FastUnstake Queue (r:1 w:1) + // Storage: FastUnstake Head (r:1 w:0) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + fn deregister() -> Weight { + // Minimum execution time: 49_897 nanoseconds. + Weight::from_ref_time(50_080_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1) + fn control() -> Weight { + // Minimum execution time: 4_814 nanoseconds. + Weight::from_ref_time(4_997_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: FastUnstake Head (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: Staking Ledger (r:0 w:1) + // Storage: Staking Payee (r:0 w:1) + fn on_idle_unstake() -> Weight { + // Minimum execution time: 82_426 nanoseconds. + Weight::from_ref_time(83_422_000 as u64) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: FastUnstake Head (r:1 w:1) + // Storage: FastUnstake Queue (r:2 w:1) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking ErasStakers (r:1344 w:0) + /// The range of component `x` is `[672, 86016]`. + fn on_idle_check(x: u32, ) -> Weight { + // Minimum execution time: 13_932_777 nanoseconds. + Weight::from_ref_time(13_996_029_000 as u64) + // Standard Error: 16_878 + .saturating_add(Weight::from_ref_time(18_113_540 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(345 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: FastUnstake Queue (r:1 w:1) + // Storage: FastUnstake Head (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + fn register_fast_unstake() -> Weight { + // Minimum execution time: 120_190 nanoseconds. + Weight::from_ref_time(121_337_000 as u64) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(9 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: FastUnstake Queue (r:1 w:1) + // Storage: FastUnstake Head (r:1 w:0) + // Storage: FastUnstake CounterForQueue (r:1 w:1) + fn deregister() -> Weight { + // Minimum execution time: 49_897 nanoseconds. + Weight::from_ref_time(50_080_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1) + fn control() -> Weight { + // Minimum execution time: 4_814 nanoseconds. + Weight::from_ref_time(4_997_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/frame/gilt/src/benchmarking.rs b/frame/gilt/src/benchmarking.rs deleted file mode 100644 index 55d34a35a7ce4..0000000000000 --- a/frame/gilt/src/benchmarking.rs +++ /dev/null @@ -1,136 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 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 Gilt Pallet - -#![cfg(feature = "runtime-benchmarks")] - -use super::*; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -use frame_support::{ - dispatch::UnfilteredDispatchable, - traits::{Currency, EnsureOrigin, Get}, -}; -use frame_system::RawOrigin; -use sp_arithmetic::Perquintill; -use sp_runtime::traits::{Bounded, Zero}; -use sp_std::prelude::*; - -use crate::Pallet as Gilt; - -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; - -benchmarks! { - place_bid { - let l in 0..(T::MaxQueueLen::get() - 1); - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - for i in 0..l { - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - } - }: _(RawOrigin::Signed(caller.clone()), T::MinFreeze::get() * BalanceOf::::from(2u32), 1) - verify { - assert_eq!(QueueTotals::::get()[0], (l + 1, T::MinFreeze::get() * BalanceOf::::from(l + 2))); - } - - place_bid_max { - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - for i in 0..T::MaxQueueLen::get() { - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - } - }: { - Gilt::::place_bid( - RawOrigin::Signed(caller.clone()).into(), - T::MinFreeze::get() * BalanceOf::::from(2u32), - 1, - )? - } - verify { - assert_eq!(QueueTotals::::get()[0], ( - T::MaxQueueLen::get(), - T::MinFreeze::get() * BalanceOf::::from(T::MaxQueueLen::get() + 1), - )); - } - - retract_bid { - let l in 1..T::MaxQueueLen::get(); - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - for i in 0..l { - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - } - }: _(RawOrigin::Signed(caller.clone()), T::MinFreeze::get(), 1) - verify { - assert_eq!(QueueTotals::::get()[0], (l - 1, T::MinFreeze::get() * BalanceOf::::from(l - 1))); - } - - set_target { - let call = Call::::set_target { target: Default::default() }; - let origin = T::AdminOrigin::successful_origin(); - }: { call.dispatch_bypass_filter(origin)? } - - thaw { - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::MinFreeze::get() * BalanceOf::::from(3u32)); - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - Gilt::::enlarge(T::MinFreeze::get() * BalanceOf::::from(2u32), 2); - Active::::mutate(0, |m_g| if let Some(ref mut g) = m_g { g.expiry = Zero::zero() }); - }: _(RawOrigin::Signed(caller.clone()), 0) - verify { - assert!(Active::::get(0).is_none()); - } - - pursue_target_noop { - }: { Gilt::::pursue_target(0) } - - pursue_target_per_item { - // bids taken - let b in 1..T::MaxQueueLen::get(); - - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::MinFreeze::get() * BalanceOf::::from(b + 1)); - - for _ in 0..b { - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - } - - Call::::set_target { target: Perquintill::from_percent(100) } - .dispatch_bypass_filter(T::AdminOrigin::successful_origin())?; - - }: { Gilt::::pursue_target(b) } - - pursue_target_per_queue { - // total queues hit - let q in 1..T::QueueCount::get(); - - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::MinFreeze::get() * BalanceOf::::from(q + 1)); - - for i in 0..q { - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), i + 1)?; - } - - Call::::set_target { target: Perquintill::from_percent(100) } - .dispatch_bypass_filter(T::AdminOrigin::successful_origin())?; - - }: { Gilt::::pursue_target(q) } -} - -impl_benchmark_test_suite!(Gilt, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/frame/gilt/src/lib.rs b/frame/gilt/src/lib.rs deleted file mode 100644 index de114e4bb87de..0000000000000 --- a/frame/gilt/src/lib.rs +++ /dev/null @@ -1,631 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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. - -//! # Gilt Pallet -//! A pallet allowing accounts to auction for being frozen and receive open-ended -//! inflation-protection in return. -//! -//! ## Overview -//! -//! Lock up tokens, for at least as long as you offer, and be free from both inflation and -//! intermediate reward or exchange until the tokens become unlocked. -//! -//! ## Design -//! -//! Queues for each of 1-`QueueCount` periods, given in blocks (`Period`). Queues are limited in -//! size to something sensible, `MaxQueueLen`. A secondary storage item with `QueueCount` x `u32` -//! elements with the number of items in each queue. -//! -//! Queues are split into two parts. The first part is a priority queue based on bid size. The -//! second part is just a FIFO (the size of the second part is set with `FifoQueueLen`). Items are -//! always prepended so that removal is always O(1) since removal often happens many times under a -//! single weighed function (`on_initialize`) yet placing bids only ever happens once per weighed -//! function (`place_bid`). If the queue has a priority portion, then it remains sorted in order of -//! bid size so that smaller bids fall off as it gets too large. -//! -//! Account may enqueue a balance with some number of `Period`s lock up, up to a maximum of -//! `QueueCount`. The balance gets reserved. There's a minimum of `MinFreeze` to avoid dust. -//! -//! Until your bid is turned into an issued gilt you can retract it instantly and the funds are -//! unreserved. -//! -//! There's a target proportion of effective total issuance (i.e. accounting for existing gilts) -//! which the we attempt to have frozen at any one time. It will likely be gradually increased over -//! time by governance. -//! -//! As the total funds frozen under gilts drops below `FrozenFraction` of the total effective -//! issuance, then bids are taken from queues, with the queue of the greatest period taking -//! priority. If the item in the queue's locked amount is greater than the amount left to be -//! frozen, then it is split up into multiple bids and becomes partially frozen under gilt. -//! -//! Once an account's balance is frozen, it remains frozen until the owner thaws the balance of the -//! account. This may happen no earlier than queue's period after the point at which the gilt is -//! issued. -//! -//! ## Suggested Values -//! -//! - `QueueCount`: 300 -//! - `Period`: 432,000 -//! - `MaxQueueLen`: 1000 -//! - `MinFreeze`: Around CHF 100 in value. - -#![cfg_attr(not(feature = "std"), no_std)] - -pub use pallet::*; - -mod benchmarking; -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; -pub mod weights; - -#[frame_support::pallet] -pub mod pallet { - pub use crate::weights::WeightInfo; - use frame_support::{ - pallet_prelude::*, - traits::{Currency, OnUnbalanced, ReservableCurrency}, - }; - use frame_system::pallet_prelude::*; - use scale_info::TypeInfo; - use sp_arithmetic::{PerThing, Perquintill}; - use sp_runtime::traits::{Saturating, Zero}; - use sp_std::prelude::*; - - type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; - type PositiveImbalanceOf = <::Currency as Currency< - ::AccountId, - >>::PositiveImbalance; - type NegativeImbalanceOf = <::Currency as Currency< - ::AccountId, - >>::NegativeImbalance; - - #[pallet::config] - pub trait Config: frame_system::Config { - /// Overarching event type. - type Event: From> + IsType<::Event>; - - /// Currency type that this works on. - type Currency: ReservableCurrency; - - /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to - /// `From`. - type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned - + codec::FullCodec - + Copy - + MaybeSerializeDeserialize - + sp_std::fmt::Debug - + Default - + From - + TypeInfo; - - /// Origin required for setting the target proportion to be under gilt. - type AdminOrigin: EnsureOrigin; - - /// Unbalanced handler to account for funds created (in case of a higher total issuance over - /// freezing period). - type Deficit: OnUnbalanced>; - - /// Unbalanced handler to account for funds destroyed (in case of a lower total issuance - /// over freezing period). - type Surplus: OnUnbalanced>; - - /// The issuance to ignore. This is subtracted from the `Currency`'s `total_issuance` to get - /// the issuance by which we inflate or deflate the gilt. - #[pallet::constant] - type IgnoredIssuance: Get>; - - /// Number of duration queues in total. This sets the maximum duration supported, which is - /// this value multiplied by `Period`. - #[pallet::constant] - type QueueCount: Get; - - /// Maximum number of items that may be in each duration queue. - #[pallet::constant] - type MaxQueueLen: Get; - - /// Portion of the queue which is free from ordering and just a FIFO. - /// - /// Must be no greater than `MaxQueueLen`. - #[pallet::constant] - type FifoQueueLen: Get; - - /// The base period for the duration queues. This is the common multiple across all - /// supported freezing durations that can be bid upon. - #[pallet::constant] - type Period: Get; - - /// The minimum amount of funds that may be offered to freeze for a gilt. Note that this - /// does not actually limit the amount which may be frozen in a gilt since gilts may be - /// split up in order to satisfy the desired amount of funds under gilts. - /// - /// It should be at least big enough to ensure that there is no possible storage spam attack - /// or queue-filling attack. - #[pallet::constant] - type MinFreeze: Get>; - - /// The number of blocks between consecutive attempts to issue more gilts in an effort to - /// get to the target amount to be frozen. - /// - /// A larger value results in fewer storage hits each block, but a slower period to get to - /// the target. - #[pallet::constant] - type IntakePeriod: Get; - - /// The maximum amount of bids that can be turned into issued gilts each block. A larger - /// value here means less of the block available for transactions should there be a glut of - /// bids to make into gilts to reach the target. - #[pallet::constant] - type MaxIntakeBids: Get; - - /// Information on runtime weights. - type WeightInfo: WeightInfo; - } - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - /// A single bid on a gilt, an item of a *queue* in `Queues`. - #[derive(Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct GiltBid { - /// The amount bid. - pub amount: Balance, - /// The owner of the bid. - pub who: AccountId, - } - - /// Information representing an active gilt. - #[derive(Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct ActiveGilt { - /// The proportion of the effective total issuance (i.e. accounting for any eventual gilt - /// expansion or contraction that may eventually be claimed). - pub proportion: Perquintill, - /// The amount reserved under this gilt. - pub amount: Balance, - /// The account to whom this gilt belongs. - pub who: AccountId, - /// The time after which this gilt can be redeemed for the proportional amount of balance. - pub expiry: BlockNumber, - } - - /// An index for a gilt. - pub type ActiveIndex = u32; - - /// Overall information package on the active gilts. - /// - /// The way of determining the net issuance (i.e. after factoring in all maturing frozen funds) - /// is: - /// - /// `issuance - frozen + proportion * issuance` - /// - /// where `issuance = total_issuance - IgnoredIssuance` - #[derive(Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct ActiveGiltsTotal { - /// The total amount of funds held in reserve for all active gilts. - pub frozen: Balance, - /// The proportion of funds that the `frozen` balance represents to total issuance. - pub proportion: Perquintill, - /// The total number of gilts issued so far. - pub index: ActiveIndex, - /// The target proportion of gilts within total issuance. - pub target: Perquintill, - } - - /// The totals of items and balances within each queue. Saves a lot of storage reads in the - /// case of sparsely packed queues. - /// - /// The vector is indexed by duration in `Period`s, offset by one, so information on the queue - /// whose duration is one `Period` would be storage `0`. - #[pallet::storage] - pub type QueueTotals = StorageValue<_, Vec<(u32, BalanceOf)>, ValueQuery>; - - /// The queues of bids ready to become gilts. Indexed by duration (in `Period`s). - #[pallet::storage] - pub type Queues = - StorageMap<_, Blake2_128Concat, u32, Vec, T::AccountId>>, ValueQuery>; - - /// Information relating to the gilts currently active. - #[pallet::storage] - pub type ActiveTotal = StorageValue<_, ActiveGiltsTotal>, ValueQuery>; - - /// The currently active gilts, indexed according to the order of creation. - #[pallet::storage] - pub type Active = StorageMap< - _, - Blake2_128Concat, - ActiveIndex, - ActiveGilt< - BalanceOf, - ::AccountId, - ::BlockNumber, - >, - OptionQuery, - >; - - #[pallet::genesis_config] - #[derive(Default)] - pub struct GenesisConfig; - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - QueueTotals::::put(vec![(0, BalanceOf::::zero()); T::QueueCount::get() as usize]); - } - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A bid was successfully placed. - /// \[ who, amount, duration \] - BidPlaced(T::AccountId, BalanceOf, u32), - /// A bid was successfully removed (before being accepted as a gilt). - /// \[ who, amount, duration \] - BidRetracted(T::AccountId, BalanceOf, u32), - /// A bid was accepted as a gilt. The balance may not be released until expiry. - /// \[ index, expiry, who, amount \] - GiltIssued(ActiveIndex, T::BlockNumber, T::AccountId, BalanceOf), - /// An expired gilt has been thawed. - /// \[ index, who, original_amount, additional_amount \] - GiltThawed(ActiveIndex, T::AccountId, BalanceOf, BalanceOf), - } - - #[pallet::error] - pub enum Error { - /// The duration of the bid is less than one. - DurationTooSmall, - /// The duration is the bid is greater than the number of queues. - DurationTooBig, - /// The amount of the bid is less than the minimum allowed. - AmountTooSmall, - /// The queue for the bid's duration is full and the amount bid is too low to get in - /// through replacing an existing bid. - BidTooLow, - /// Gilt index is unknown. - Unknown, - /// Not the owner of the gilt. - NotOwner, - /// Gilt not yet at expiry date. - NotExpired, - /// The given bid for retraction is not found. - NotFound, - } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(n: T::BlockNumber) -> Weight { - if (n % T::IntakePeriod::get()).is_zero() { - Self::pursue_target(T::MaxIntakeBids::get()) - } else { - 0 - } - } - } - - #[pallet::call] - impl Pallet { - /// Place a bid for a gilt to be issued. - /// - /// Origin must be Signed, and account must have at least `amount` in free balance. - /// - /// - `amount`: The amount of the bid; these funds will be reserved. If the bid is - /// successfully elevated into an issued gilt, then these funds will continue to be - /// reserved until the gilt expires. Must be at least `MinFreeze`. - /// - `duration`: The number of periods for which the funds will be locked if the gilt is - /// issued. It will expire only after this period has elapsed after the point of issuance. - /// Must be greater than 1 and no more than `QueueCount`. - /// - /// Complexities: - /// - `Queues[duration].len()` (just take max). - #[pallet::weight(T::WeightInfo::place_bid_max())] - pub fn place_bid( - origin: OriginFor, - #[pallet::compact] amount: BalanceOf, - duration: u32, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - ensure!(amount >= T::MinFreeze::get(), Error::::AmountTooSmall); - let queue_count = T::QueueCount::get() as usize; - let queue_index = duration.checked_sub(1).ok_or(Error::::DurationTooSmall)? as usize; - ensure!(queue_index < queue_count, Error::::DurationTooBig); - - let net = Queues::::try_mutate( - duration, - |q| -> Result<(u32, BalanceOf), DispatchError> { - let queue_full = q.len() == T::MaxQueueLen::get() as usize; - ensure!(!queue_full || q[0].amount < amount, Error::::BidTooLow); - T::Currency::reserve(&who, amount)?; - - // queue is - let mut bid = GiltBid { amount, who: who.clone() }; - let net = if queue_full { - sp_std::mem::swap(&mut q[0], &mut bid); - T::Currency::unreserve(&bid.who, bid.amount); - (0, amount - bid.amount) - } else { - q.insert(0, bid); - (1, amount) - }; - - let sorted_item_count = q.len().saturating_sub(T::FifoQueueLen::get() as usize); - if sorted_item_count > 1 { - q[0..sorted_item_count].sort_by_key(|x| x.amount); - } - - Ok(net) - }, - )?; - QueueTotals::::mutate(|qs| { - qs.resize(queue_count, (0, Zero::zero())); - qs[queue_index].0 += net.0; - qs[queue_index].1 = qs[queue_index].1.saturating_add(net.1); - }); - Self::deposit_event(Event::BidPlaced(who.clone(), amount, duration)); - - Ok(().into()) - } - - /// Retract a previously placed bid. - /// - /// Origin must be Signed, and the account should have previously issued a still-active bid - /// of `amount` for `duration`. - /// - /// - `amount`: The amount of the previous bid. - /// - `duration`: The duration of the previous bid. - #[pallet::weight(T::WeightInfo::place_bid(T::MaxQueueLen::get()))] - pub fn retract_bid( - origin: OriginFor, - #[pallet::compact] amount: BalanceOf, - duration: u32, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - let queue_count = T::QueueCount::get() as usize; - let queue_index = duration.checked_sub(1).ok_or(Error::::DurationTooSmall)? as usize; - ensure!(queue_index < queue_count, Error::::DurationTooBig); - - let bid = GiltBid { amount, who }; - let new_len = Queues::::try_mutate(duration, |q| -> Result { - let pos = q.iter().position(|i| i == &bid).ok_or(Error::::NotFound)?; - q.remove(pos); - Ok(q.len() as u32) - })?; - - QueueTotals::::mutate(|qs| { - qs.resize(queue_count, (0, Zero::zero())); - qs[queue_index].0 = new_len; - qs[queue_index].1 = qs[queue_index].1.saturating_sub(bid.amount); - }); - - T::Currency::unreserve(&bid.who, bid.amount); - Self::deposit_event(Event::BidRetracted(bid.who, bid.amount, duration)); - - Ok(().into()) - } - - /// Set target proportion of gilt-funds. - /// - /// Origin must be `AdminOrigin`. - /// - /// - `target`: The target proportion of effective issued funds that should be under gilts - /// at any one time. - #[pallet::weight(T::WeightInfo::set_target())] - pub fn set_target( - origin: OriginFor, - #[pallet::compact] target: Perquintill, - ) -> DispatchResultWithPostInfo { - T::AdminOrigin::ensure_origin(origin)?; - ActiveTotal::::mutate(|totals| totals.target = target); - Ok(().into()) - } - - /// Remove an active but expired gilt. Reserved funds under gilt are freed and balance is - /// adjusted to ensure that the funds grow or shrink to maintain the equivalent proportion - /// of effective total issued funds. - /// - /// Origin must be Signed and the account must be the owner of the gilt of the given index. - /// - /// - `index`: The index of the gilt to be thawed. - #[pallet::weight(T::WeightInfo::thaw())] - pub fn thaw( - origin: OriginFor, - #[pallet::compact] index: ActiveIndex, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - // Look for `index` - let gilt = Active::::get(index).ok_or(Error::::Unknown)?; - // If found, check the owner is `who`. - ensure!(gilt.who == who, Error::::NotOwner); - let now = frame_system::Pallet::::block_number(); - ensure!(now >= gilt.expiry, Error::::NotExpired); - // Remove it - Active::::remove(index); - - // Multiply the proportion it is by the total issued. - let total_issuance = - T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get()); - ActiveTotal::::mutate(|totals| { - let nongilt_issuance = total_issuance.saturating_sub(totals.frozen); - let effective_issuance = - totals.proportion.left_from_one().saturating_reciprocal_mul(nongilt_issuance); - let gilt_value = gilt.proportion * effective_issuance; - - totals.frozen = totals.frozen.saturating_sub(gilt.amount); - totals.proportion = totals.proportion.saturating_sub(gilt.proportion); - - // Remove or mint the additional to the amount using `Deficit`/`Surplus`. - if gilt_value > gilt.amount { - // Unreserve full amount. - T::Currency::unreserve(&gilt.who, gilt.amount); - let amount = gilt_value - gilt.amount; - let deficit = T::Currency::deposit_creating(&gilt.who, amount); - T::Deficit::on_unbalanced(deficit); - } else { - if gilt_value < gilt.amount { - // We take anything reserved beyond the gilt's final value. - let rest = gilt.amount - gilt_value; - // `slash` might seem a little aggressive, but it's the only way to do it - // in case it's locked into the staking system. - let surplus = T::Currency::slash_reserved(&gilt.who, rest).0; - T::Surplus::on_unbalanced(surplus); - } - // Unreserve only its new value (less than the amount reserved). Everything - // should add up, but (defensive) in case it doesn't, unreserve takes lower - // priority over the funds. - let err_amt = T::Currency::unreserve(&gilt.who, gilt_value); - debug_assert!(err_amt.is_zero()); - } - - let e = Event::GiltThawed(index, gilt.who, gilt.amount, gilt_value); - Self::deposit_event(e); - }); - - Ok(().into()) - } - } - - /// Issuance information returned by `issuance()`. - pub struct IssuanceInfo { - /// The balance held in reserve over all active gilts. - pub reserved: Balance, - /// The issuance not held in reserve for active gilts. Together with `reserved` this sums - /// to `Currency::total_issuance`. - pub non_gilt: Balance, - /// The balance that `reserved` is effectively worth, at present. This is not issued funds - /// and could be less than `reserved` (though in most cases should be greater). - pub effective: Balance, - } - - impl Pallet { - /// Get the target amount of Gilts that we're aiming for. - pub fn target() -> Perquintill { - ActiveTotal::::get().target - } - - /// Returns information on the issuance of gilts. - pub fn issuance() -> IssuanceInfo> { - let totals = ActiveTotal::::get(); - - let total_issuance = T::Currency::total_issuance(); - let non_gilt = total_issuance.saturating_sub(totals.frozen); - let effective = totals.proportion.left_from_one().saturating_reciprocal_mul(non_gilt); - - IssuanceInfo { reserved: totals.frozen, non_gilt, effective } - } - - /// Attempt to enlarge our gilt-set from bids in order to satisfy our desired target amount - /// of funds frozen into gilts. - pub fn pursue_target(max_bids: u32) -> Weight { - let totals = ActiveTotal::::get(); - if totals.proportion < totals.target { - let missing = totals.target.saturating_sub(totals.proportion); - - let total_issuance = - T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get()); - let nongilt_issuance = total_issuance.saturating_sub(totals.frozen); - let effective_issuance = - totals.proportion.left_from_one().saturating_reciprocal_mul(nongilt_issuance); - let intake = missing * effective_issuance; - - let (bids_taken, queues_hit) = Self::enlarge(intake, max_bids); - let first_from_each_queue = T::WeightInfo::pursue_target_per_queue(queues_hit); - let rest_from_each_queue = T::WeightInfo::pursue_target_per_item(bids_taken) - .saturating_sub(T::WeightInfo::pursue_target_per_item(queues_hit)); - first_from_each_queue + rest_from_each_queue - } else { - T::WeightInfo::pursue_target_noop() - } - } - - /// Freeze additional funds from queue of bids up to `amount`. Use at most `max_bids` - /// from the queue. - /// - /// Return the number of bids taken and the number of distinct queues taken from. - pub fn enlarge(amount: BalanceOf, max_bids: u32) -> (u32, u32) { - let total_issuance = - T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get()); - let mut remaining = amount; - let mut bids_taken = 0; - let mut queues_hit = 0; - let now = frame_system::Pallet::::block_number(); - - ActiveTotal::::mutate(|totals| { - QueueTotals::::mutate(|qs| { - for duration in (1..=T::QueueCount::get()).rev() { - if qs[duration as usize - 1].0 == 0 { - continue - } - let queue_index = duration as usize - 1; - let expiry = - now.saturating_add(T::Period::get().saturating_mul(duration.into())); - Queues::::mutate(duration, |q| { - while let Some(mut bid) = q.pop() { - if remaining < bid.amount { - let overflow = bid.amount - remaining; - bid.amount = remaining; - q.push(GiltBid { amount: overflow, who: bid.who.clone() }); - } - let amount = bid.amount; - // Can never overflow due to block above. - remaining -= amount; - // Should never underflow since it should track the total of the - // bids exactly, but we'll be defensive. - qs[queue_index].1 = qs[queue_index].1.saturating_sub(bid.amount); - - // Now to activate the bid... - let nongilt_issuance = total_issuance.saturating_sub(totals.frozen); - let effective_issuance = totals - .proportion - .left_from_one() - .saturating_reciprocal_mul(nongilt_issuance); - let n = amount; - let d = effective_issuance; - let proportion = Perquintill::from_rational(n, d); - let who = bid.who; - let index = totals.index; - totals.frozen += bid.amount; - totals.proportion = totals.proportion.saturating_add(proportion); - totals.index += 1; - let e = Event::GiltIssued(index, expiry, who.clone(), amount); - Self::deposit_event(e); - let gilt = ActiveGilt { amount, proportion, who, expiry }; - Active::::insert(index, gilt); - - bids_taken += 1; - - if remaining.is_zero() || bids_taken == max_bids { - break - } - } - queues_hit += 1; - qs[queue_index].0 = q.len() as u32; - }); - if remaining.is_zero() || bids_taken == max_bids { - break - } - } - }); - }); - (bids_taken, queues_hit) - } - } -} diff --git a/frame/gilt/src/tests.rs b/frame/gilt/src/tests.rs deleted file mode 100644 index 80315141e2325..0000000000000 --- a/frame/gilt/src/tests.rs +++ /dev/null @@ -1,567 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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. - -//! Tests for Gilt pallet. - -use super::*; -use crate::{mock::*, Error}; -use frame_support::{assert_noop, assert_ok, dispatch::DispatchError, traits::Currency}; -use pallet_balances::Error as BalancesError; -use sp_arithmetic::Perquintill; - -#[test] -fn basic_setup_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - - for q in 0..3 { - assert!(Queues::::get(q).is_empty()); - } - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 0, - proportion: Perquintill::zero(), - index: 0, - target: Perquintill::zero(), - } - ); - assert_eq!(QueueTotals::::get(), vec![(0, 0); 3]); - }); -} - -#[test] -fn set_target_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - let e = DispatchError::BadOrigin; - assert_noop!(Gilt::set_target(Origin::signed(2), Perquintill::from_percent(50)), e); - assert_ok!(Gilt::set_target(Origin::signed(1), Perquintill::from_percent(50))); - - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 0, - proportion: Perquintill::zero(), - index: 0, - target: Perquintill::from_percent(50), - } - ); - }); -} - -#[test] -fn place_bid_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_noop!(Gilt::place_bid(Origin::signed(1), 1, 2), Error::::AmountTooSmall); - assert_noop!( - Gilt::place_bid(Origin::signed(1), 101, 2), - BalancesError::::InsufficientBalance - ); - assert_noop!(Gilt::place_bid(Origin::signed(1), 10, 4), Error::::DurationTooBig); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 2)); - assert_eq!(Balances::reserved_balance(1), 10); - assert_eq!(Queues::::get(2), vec![GiltBid { amount: 10, who: 1 }]); - assert_eq!(QueueTotals::::get(), vec![(0, 0), (1, 10), (0, 0)]); - }); -} - -#[test] -fn place_bid_queuing_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 20, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 5, 2)); - assert_noop!(Gilt::place_bid(Origin::signed(1), 5, 2), Error::::BidTooLow); - assert_ok!(Gilt::place_bid(Origin::signed(1), 15, 2)); - assert_eq!(Balances::reserved_balance(1), 45); - - assert_ok!(Gilt::place_bid(Origin::signed(1), 25, 2)); - assert_eq!(Balances::reserved_balance(1), 60); - assert_noop!(Gilt::place_bid(Origin::signed(1), 10, 2), Error::::BidTooLow); - assert_eq!( - Queues::::get(2), - vec![ - GiltBid { amount: 15, who: 1 }, - GiltBid { amount: 25, who: 1 }, - GiltBid { amount: 20, who: 1 }, - ] - ); - assert_eq!(QueueTotals::::get(), vec![(0, 0), (3, 60), (0, 0)]); - }); -} - -#[test] -fn place_bid_fails_when_queue_full() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(2), 10, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(3), 10, 2)); - assert_noop!(Gilt::place_bid(Origin::signed(4), 10, 2), Error::::BidTooLow); - assert_ok!(Gilt::place_bid(Origin::signed(4), 10, 3)); - }); -} - -#[test] -fn multiple_place_bids_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 1)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 3)); - assert_ok!(Gilt::place_bid(Origin::signed(2), 10, 2)); - - assert_eq!(Balances::reserved_balance(1), 40); - assert_eq!(Balances::reserved_balance(2), 10); - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 10, who: 1 },]); - assert_eq!( - Queues::::get(2), - vec![ - GiltBid { amount: 10, who: 2 }, - GiltBid { amount: 10, who: 1 }, - GiltBid { amount: 10, who: 1 }, - ] - ); - assert_eq!(Queues::::get(3), vec![GiltBid { amount: 10, who: 1 },]); - assert_eq!(QueueTotals::::get(), vec![(1, 10), (3, 30), (1, 10)]); - }); -} - -#[test] -fn retract_single_item_queue_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 1)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 2)); - assert_ok!(Gilt::retract_bid(Origin::signed(1), 10, 1)); - - assert_eq!(Balances::reserved_balance(1), 10); - assert_eq!(Queues::::get(1), vec![]); - assert_eq!(Queues::::get(2), vec![GiltBid { amount: 10, who: 1 }]); - assert_eq!(QueueTotals::::get(), vec![(0, 0), (1, 10), (0, 0)]); - }); -} - -#[test] -fn retract_with_other_and_duplicate_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 1)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(2), 10, 2)); - - assert_ok!(Gilt::retract_bid(Origin::signed(1), 10, 2)); - assert_eq!(Balances::reserved_balance(1), 20); - assert_eq!(Balances::reserved_balance(2), 10); - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 10, who: 1 },]); - assert_eq!( - Queues::::get(2), - vec![GiltBid { amount: 10, who: 2 }, GiltBid { amount: 10, who: 1 },] - ); - assert_eq!(QueueTotals::::get(), vec![(1, 10), (2, 20), (0, 0)]); - }); -} - -#[test] -fn retract_non_existent_item_fails() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_noop!(Gilt::retract_bid(Origin::signed(1), 10, 1), Error::::NotFound); - assert_ok!(Gilt::place_bid(Origin::signed(1), 10, 1)); - assert_noop!(Gilt::retract_bid(Origin::signed(1), 20, 1), Error::::NotFound); - assert_noop!(Gilt::retract_bid(Origin::signed(1), 10, 2), Error::::NotFound); - assert_noop!(Gilt::retract_bid(Origin::signed(2), 10, 1), Error::::NotFound); - }); -} - -#[test] -fn basic_enlarge_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 40, 1)); - assert_ok!(Gilt::place_bid(Origin::signed(2), 40, 2)); - Gilt::enlarge(40, 2); - - // Takes 2/2, then stopped because it reaches its max amount - assert_eq!(Balances::reserved_balance(1), 40); - assert_eq!(Balances::reserved_balance(2), 40); - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 40, who: 1 }]); - assert_eq!(Queues::::get(2), vec![]); - assert_eq!(QueueTotals::::get(), vec![(1, 40), (0, 0), (0, 0)]); - - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 40, - proportion: Perquintill::from_percent(10), - index: 1, - target: Perquintill::zero(), - } - ); - assert_eq!( - Active::::get(0).unwrap(), - ActiveGilt { proportion: Perquintill::from_percent(10), amount: 40, who: 2, expiry: 7 } - ); - }); -} - -#[test] -fn enlarge_respects_bids_limit() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 40, 1)); - assert_ok!(Gilt::place_bid(Origin::signed(2), 40, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(3), 40, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(4), 40, 3)); - Gilt::enlarge(100, 2); - - // Should have taken 4/3 and 2/2, then stopped because it's only allowed 2. - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 40, who: 1 }]); - assert_eq!(Queues::::get(2), vec![GiltBid { amount: 40, who: 3 }]); - assert_eq!(Queues::::get(3), vec![]); - assert_eq!(QueueTotals::::get(), vec![(1, 40), (1, 40), (0, 0)]); - - assert_eq!( - Active::::get(0).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 4, - expiry: 10, - } - ); - assert_eq!( - Active::::get(1).unwrap(), - ActiveGilt { proportion: Perquintill::from_percent(10), amount: 40, who: 2, expiry: 7 } - ); - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 80, - proportion: Perquintill::from_percent(20), - index: 2, - target: Perquintill::zero(), - } - ); - }); -} - -#[test] -fn enlarge_respects_amount_limit_and_will_split() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 80, 1)); - Gilt::enlarge(40, 2); - - // Takes 2/2, then stopped because it reaches its max amount - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 40, who: 1 }]); - assert_eq!(QueueTotals::::get(), vec![(1, 40), (0, 0), (0, 0)]); - - assert_eq!( - Active::::get(0).unwrap(), - ActiveGilt { proportion: Perquintill::from_percent(10), amount: 40, who: 1, expiry: 4 } - ); - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 40, - proportion: Perquintill::from_percent(10), - index: 1, - target: Perquintill::zero(), - } - ); - }); -} - -#[test] -fn basic_thaw_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 40, 1)); - Gilt::enlarge(40, 1); - run_to_block(3); - assert_noop!(Gilt::thaw(Origin::signed(1), 0), Error::::NotExpired); - run_to_block(4); - assert_noop!(Gilt::thaw(Origin::signed(1), 1), Error::::Unknown); - assert_noop!(Gilt::thaw(Origin::signed(2), 0), Error::::NotOwner); - assert_ok!(Gilt::thaw(Origin::signed(1), 0)); - - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 0, - proportion: Perquintill::zero(), - index: 1, - target: Perquintill::zero(), - } - ); - assert_eq!(Active::::get(0), None); - assert_eq!(Balances::free_balance(1), 100); - assert_eq!(Balances::reserved_balance(1), 0); - }); -} - -#[test] -fn thaw_when_issuance_higher_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 100, 1)); - Gilt::enlarge(100, 1); - - // Everybody else's balances goes up by 50% - Balances::make_free_balance_be(&2, 150); - Balances::make_free_balance_be(&3, 150); - Balances::make_free_balance_be(&4, 150); - - run_to_block(4); - assert_ok!(Gilt::thaw(Origin::signed(1), 0)); - - assert_eq!(Balances::free_balance(1), 150); - assert_eq!(Balances::reserved_balance(1), 0); - }); -} - -#[test] -fn thaw_with_ignored_issuance_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - // Give account zero some balance. - Balances::make_free_balance_be(&0, 200); - - assert_ok!(Gilt::place_bid(Origin::signed(1), 100, 1)); - Gilt::enlarge(100, 1); - - // Account zero transfers 50 into everyone else's accounts. - assert_ok!(Balances::transfer(Origin::signed(0), 2, 50)); - assert_ok!(Balances::transfer(Origin::signed(0), 3, 50)); - assert_ok!(Balances::transfer(Origin::signed(0), 4, 50)); - - run_to_block(4); - assert_ok!(Gilt::thaw(Origin::signed(1), 0)); - - // Account zero changes have been ignored. - assert_eq!(Balances::free_balance(1), 150); - assert_eq!(Balances::reserved_balance(1), 0); - }); -} - -#[test] -fn thaw_when_issuance_lower_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 100, 1)); - Gilt::enlarge(100, 1); - - // Everybody else's balances goes down by 25% - Balances::make_free_balance_be(&2, 75); - Balances::make_free_balance_be(&3, 75); - Balances::make_free_balance_be(&4, 75); - - run_to_block(4); - assert_ok!(Gilt::thaw(Origin::signed(1), 0)); - - assert_eq!(Balances::free_balance(1), 75); - assert_eq!(Balances::reserved_balance(1), 0); - }); -} - -#[test] -fn multiple_thaws_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 40, 1)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 60, 1)); - assert_ok!(Gilt::place_bid(Origin::signed(2), 50, 1)); - Gilt::enlarge(200, 3); - - // Double everyone's free balances. - Balances::make_free_balance_be(&2, 100); - Balances::make_free_balance_be(&3, 200); - Balances::make_free_balance_be(&4, 200); - - run_to_block(4); - assert_ok!(Gilt::thaw(Origin::signed(1), 0)); - assert_ok!(Gilt::thaw(Origin::signed(1), 1)); - assert_ok!(Gilt::thaw(Origin::signed(2), 2)); - - assert_eq!(Balances::free_balance(1), 200); - assert_eq!(Balances::free_balance(2), 200); - }); -} - -#[test] -fn multiple_thaws_works_in_alternative_thaw_order() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(Origin::signed(1), 40, 1)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 60, 1)); - assert_ok!(Gilt::place_bid(Origin::signed(2), 50, 1)); - Gilt::enlarge(200, 3); - - // Double everyone's free balances. - Balances::make_free_balance_be(&2, 100); - Balances::make_free_balance_be(&3, 200); - Balances::make_free_balance_be(&4, 200); - - run_to_block(4); - assert_ok!(Gilt::thaw(Origin::signed(2), 2)); - assert_ok!(Gilt::thaw(Origin::signed(1), 1)); - assert_ok!(Gilt::thaw(Origin::signed(1), 0)); - - assert_eq!(Balances::free_balance(1), 200); - assert_eq!(Balances::free_balance(2), 200); - }); -} - -#[test] -fn enlargement_to_target_works() { - new_test_ext().execute_with(|| { - run_to_block(2); - assert_ok!(Gilt::place_bid(Origin::signed(1), 40, 1)); - assert_ok!(Gilt::place_bid(Origin::signed(1), 40, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(2), 40, 2)); - assert_ok!(Gilt::place_bid(Origin::signed(2), 40, 3)); - assert_ok!(Gilt::place_bid(Origin::signed(3), 40, 3)); - assert_ok!(Gilt::set_target(Origin::signed(1), Perquintill::from_percent(40))); - - run_to_block(3); - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 40, who: 1 },]); - assert_eq!( - Queues::::get(2), - vec![GiltBid { amount: 40, who: 2 }, GiltBid { amount: 40, who: 1 },] - ); - assert_eq!( - Queues::::get(3), - vec![GiltBid { amount: 40, who: 3 }, GiltBid { amount: 40, who: 2 },] - ); - assert_eq!(QueueTotals::::get(), vec![(1, 40), (2, 80), (2, 80)]); - - run_to_block(4); - // Two new gilts should have been issued to 2 & 3 for 40 each & duration of 3. - assert_eq!( - Active::::get(0).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 2, - expiry: 13, - } - ); - assert_eq!( - Active::::get(1).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 3, - expiry: 13, - } - ); - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 80, - proportion: Perquintill::from_percent(20), - index: 2, - target: Perquintill::from_percent(40), - } - ); - - run_to_block(5); - // No change - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 80, - proportion: Perquintill::from_percent(20), - index: 2, - target: Perquintill::from_percent(40), - } - ); - - run_to_block(6); - // Two new gilts should have been issued to 1 & 2 for 40 each & duration of 2. - assert_eq!( - Active::::get(2).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 1, - expiry: 12, - } - ); - assert_eq!( - Active::::get(3).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 2, - expiry: 12, - } - ); - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 160, - proportion: Perquintill::from_percent(40), - index: 4, - target: Perquintill::from_percent(40), - } - ); - - run_to_block(8); - // No change now. - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 160, - proportion: Perquintill::from_percent(40), - index: 4, - target: Perquintill::from_percent(40), - } - ); - - // Set target a bit higher to use up the remaining bid. - assert_ok!(Gilt::set_target(Origin::signed(1), Perquintill::from_percent(60))); - run_to_block(10); - - // Two new gilts should have been issued to 1 & 2 for 40 each & duration of 2. - assert_eq!( - Active::::get(4).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 1, - expiry: 13, - } - ); - - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 200, - proportion: Perquintill::from_percent(50), - index: 5, - target: Perquintill::from_percent(60), - } - ); - }); -} diff --git a/frame/gilt/src/weights.rs b/frame/gilt/src/weights.rs deleted file mode 100644 index f54d917cc160c..0000000000000 --- a/frame/gilt/src/weights.rs +++ /dev/null @@ -1,201 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 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_gilt -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// target/release/substrate -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_gilt -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/gilt/src/weights.rs -// --template=./.maintain/frame-weight-template.hbs - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for pallet_gilt. -pub trait WeightInfo { - fn place_bid(l: u32, ) -> Weight; - fn place_bid_max() -> Weight; - fn retract_bid(l: u32, ) -> Weight; - fn set_target() -> Weight; - fn thaw() -> Weight; - fn pursue_target_noop() -> Weight; - fn pursue_target_per_item(b: u32, ) -> Weight; - fn pursue_target_per_queue(q: u32, ) -> Weight; -} - -/// Weights for pallet_gilt using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn place_bid(l: u32, ) -> Weight { - (59_219_000 as Weight) - // Standard Error: 0 - .saturating_add((156_000 as Weight).saturating_mul(l as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn place_bid_max() -> Weight { - (184_943_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn retract_bid(l: u32, ) -> Weight { - (59_352_000 as Weight) - // Standard Error: 0 - .saturating_add((129_000 as Weight).saturating_mul(l as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - fn set_target() -> Weight { - (5_444_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Gilt Active (r:1 w:1) - // Storage: Gilt ActiveTotal (r:1 w:1) - fn thaw() -> Weight { - (71_399_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Gilt ActiveTotal (r:1 w:0) - fn pursue_target_noop() -> Weight { - (3_044_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt Active (r:0 w:1) - fn pursue_target_per_item(b: u32, ) -> Weight { - (54_478_000 as Weight) - // Standard Error: 2_000 - .saturating_add((10_150_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(b as Weight))) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt Active (r:0 w:1) - fn pursue_target_per_queue(q: u32, ) -> Weight { - (20_099_000 as Weight) - // Standard Error: 7_000 - .saturating_add((16_603_000 as Weight).saturating_mul(q as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(q as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(q as Weight))) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn place_bid(l: u32, ) -> Weight { - (59_219_000 as Weight) - // Standard Error: 0 - .saturating_add((156_000 as Weight).saturating_mul(l as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn place_bid_max() -> Weight { - (184_943_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn retract_bid(l: u32, ) -> Weight { - (59_352_000 as Weight) - // Standard Error: 0 - .saturating_add((129_000 as Weight).saturating_mul(l as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - fn set_target() -> Weight { - (5_444_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Gilt Active (r:1 w:1) - // Storage: Gilt ActiveTotal (r:1 w:1) - fn thaw() -> Weight { - (71_399_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Gilt ActiveTotal (r:1 w:0) - fn pursue_target_noop() -> Weight { - (3_044_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt Active (r:0 w:1) - fn pursue_target_per_item(b: u32, ) -> Weight { - (54_478_000 as Weight) - // Standard Error: 2_000 - .saturating_add((10_150_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(b as Weight))) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt Active (r:0 w:1) - fn pursue_target_per_queue(q: u32, ) -> Weight { - (20_099_000 as Weight) - // Standard Error: 7_000 - .saturating_add((16_603_000 as Weight).saturating_mul(q as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(q as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(q as Weight))) - } -} diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index 53ab443783e5d..5a85a3682db82 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-grandpa" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for GRANDPA finality gadget" readme = "README.md" @@ -13,52 +13,53 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../../primitives/application-crypto" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-finality-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../primitives/finality-grandpa" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } -log = { version = "0.4.14", default-features = false } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-finality-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../primitives/finality-grandpa" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] +grandpa = { package = "finality-grandpa", version = "0.16.0", features = ["derive-codec"] } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } -grandpa = { package = "finality-grandpa", version = "0.14.1", features = ["derive-codec"] } -sp-keyring = { version = "4.0.0-dev", path = "../../primitives/keyring" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-offences = { version = "4.0.0-dev", path = "../offences" } pallet-staking = { version = "4.0.0-dev", path = "../staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } -frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } +sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } [features] default = ["std"] std = [ "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-authorship/std", + "pallet-session/std", "scale-info/std", - "frame-benchmarking/std", "sp-application-crypto/std", "sp-core/std", "sp-finality-grandpa/std", - "sp-session/std", - "sp-std/std", - "frame-support/std", + "sp-io/std", "sp-runtime/std", + "sp-session/std", "sp-staking/std", - "frame-system/std", - "pallet-authorship/std", - "pallet-session/std", - "log/std", + "sp-std/std", ] -runtime-benchmarks = ["frame-benchmarking"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/grandpa/src/benchmarking.rs b/frame/grandpa/src/benchmarking.rs index b0f70adb6061d..1240859149920 100644 --- a/frame/grandpa/src/benchmarking.rs +++ b/frame/grandpa/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,6 @@ //! Benchmarks for the GRANDPA pallet. -#![cfg_attr(not(feature = "std"), no_std)] - use super::{Pallet as Grandpa, *}; use frame_benchmarking::benchmarks; use frame_system::RawOrigin; @@ -70,6 +68,12 @@ benchmarks! { verify { assert!(Grandpa::::stalled().is_some()); } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), + crate::mock::Test, + ); } #[cfg(test)] @@ -77,12 +81,6 @@ mod tests { use super::*; use crate::mock::*; - frame_benchmarking::impl_benchmark_test_suite!( - Pallet, - crate::mock::new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), - crate::mock::Test, - ); - #[test] fn test_generate_equivocation_report_blob() { let authorities = crate::tests::test_authorities(); diff --git a/frame/grandpa/src/default_weights.rs b/frame/grandpa/src/default_weights.rs index edc18a7ff8c93..ba343cabac622 100644 --- a/frame/grandpa/src/default_weights.rs +++ b/frame/grandpa/src/default_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ //! This file was not auto-generated. use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}, + constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_MICROS, WEIGHT_REF_TIME_PER_NANOS}, Weight, }; @@ -34,14 +34,19 @@ impl crate::WeightInfo for () { const MAX_NOMINATORS: u64 = 200; // checking membership proof - (35 * WEIGHT_PER_MICROS) - .saturating_add((175 * WEIGHT_PER_NANOS).saturating_mul(validator_count)) + Weight::from_ref_time(35u64 * WEIGHT_REF_TIME_PER_MICROS) + .saturating_add( + Weight::from_ref_time(175u64 * WEIGHT_REF_TIME_PER_NANOS) + .saturating_mul(validator_count), + ) .saturating_add(DbWeight::get().reads(5)) // check equivocation proof - .saturating_add(95 * WEIGHT_PER_MICROS) + .saturating_add(Weight::from_ref_time(95u64 * WEIGHT_REF_TIME_PER_MICROS)) // report offence - .saturating_add(110 * WEIGHT_PER_MICROS) - .saturating_add(25 * WEIGHT_PER_MICROS * MAX_NOMINATORS) + .saturating_add(Weight::from_ref_time(110u64 * WEIGHT_REF_TIME_PER_MICROS)) + .saturating_add(Weight::from_ref_time( + 25u64 * WEIGHT_REF_TIME_PER_MICROS * MAX_NOMINATORS, + )) .saturating_add(DbWeight::get().reads(14 + 3 * MAX_NOMINATORS)) .saturating_add(DbWeight::get().writes(10 + 3 * MAX_NOMINATORS)) // fetching set id -> session index mappings @@ -49,6 +54,7 @@ impl crate::WeightInfo for () { } fn note_stalled() -> Weight { - (3 * WEIGHT_PER_MICROS).saturating_add(DbWeight::get().writes(1)) + Weight::from_ref_time(3u64 * WEIGHT_REF_TIME_PER_MICROS) + .saturating_add(DbWeight::get().writes(1)) } } diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 8a23ce6e1ef1e..181d22fba545c 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -185,7 +185,7 @@ where } fn block_author() -> Option { - Some(>::author()) + >::author() } } @@ -353,9 +353,9 @@ impl Offence self.time_slot } - fn slash_fraction(offenders_count: u32, validator_set_count: u32) -> Perbill { + fn slash_fraction(&self, offenders_count: u32) -> Perbill { // the formula is min((3k / n)^2, 1) - let x = Perbill::from_rational(3 * offenders_count, validator_set_count); + let x = Perbill::from_rational(3 * offenders_count, self.validator_set_count); // _ ^ 2 x.square() } diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index cd75deea770b4..c6b7fd251661f 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,18 +33,21 @@ pub use sp_finality_grandpa as fg_primitives; use sp_std::prelude::*; -use codec::{self as codec, Decode, Encode}; +use codec::{self as codec, Decode, Encode, MaxEncodedLen}; pub use fg_primitives::{AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList}; use fg_primitives::{ ConsensusLog, EquivocationProof, ScheduledChange, SetId, GRANDPA_AUTHORITIES_KEY, GRANDPA_ENGINE_ID, }; use frame_support::{ - dispatch::DispatchResultWithPostInfo, + dispatch::{DispatchResultWithPostInfo, Pays}, + pallet_prelude::Get, storage, - traits::{KeyOwnerProofSystem, OneSessionHandler, StorageVersion}, - weights::{Pays, Weight}, + traits::{KeyOwnerProofSystem, OneSessionHandler}, + weights::Weight, + WeakBoundedVec, }; +use scale_info::TypeInfo; use sp_runtime::{generic::DigestItem, traits::Zero, DispatchResult, KeyTypeId}; use sp_session::{GetSessionNumber, GetValidatorCount}; use sp_staking::SessionIndex; @@ -67,17 +70,15 @@ pub use equivocation::{ pub use pallet::*; -use scale_info::TypeInfo; - -/// The current storage version. -const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); - #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] @@ -86,12 +87,9 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The event type of this module. - type Event: From - + Into<::Event> - + IsType<::Event>; - - /// The function call. - type Call: From>; + type RuntimeEvent: From + + Into<::RuntimeEvent> + + IsType<::RuntimeEvent>; /// The proof of key ownership, used for validating equivocation reports /// The proof must include the session index and validator count of the @@ -119,6 +117,10 @@ pub mod pallet { /// Weights for this pallet. type WeightInfo: WeightInfo; + + /// Max Authorities in use + #[pallet::constant] + type MaxAuthorities: Get; } #[pallet::hooks] @@ -133,13 +135,13 @@ pub mod pallet { median, ScheduledChange { delay: pending_change.delay, - next_authorities: pending_change.next_authorities.clone(), + next_authorities: pending_change.next_authorities.to_vec(), }, )) } else { Self::deposit_log(ConsensusLog::ScheduledChange(ScheduledChange { delay: pending_change.delay, - next_authorities: pending_change.next_authorities.clone(), + next_authorities: pending_change.next_authorities.to_vec(), })); } } @@ -147,7 +149,9 @@ pub mod pallet { // enact the change if we've reached the enacting block if block_number == pending_change.scheduled_at + pending_change.delay { Self::set_grandpa_authorities(&pending_change.next_authorities); - Self::deposit_event(Event::NewAuthorities(pending_change.next_authorities)); + Self::deposit_event(Event::NewAuthorities { + authority_set: pending_change.next_authorities.to_vec(), + }); >::kill(); } } @@ -189,6 +193,7 @@ pub mod pallet { /// equivocation proof and validate the given key ownership proof /// against the extracted offender. If both are valid, the offence /// will be reported. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::report_equivocation(key_owner_proof.validator_count()))] pub fn report_equivocation( origin: OriginFor, @@ -209,6 +214,7 @@ pub mod pallet { /// block authors will call it (validated in `ValidateUnsigned`), as such /// if the block author is defined it will be defined as the equivocation /// reporter. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::report_equivocation(key_owner_proof.validator_count()))] pub fn report_equivocation_unsigned( origin: OriginFor, @@ -224,39 +230,43 @@ pub mod pallet { ) } - /// Note that the current authority set of the GRANDPA finality gadget has - /// stalled. This will trigger a forced authority set change at the beginning - /// of the next session, to be enacted `delay` blocks after that. The delay - /// should be high enough to safely assume that the block signalling the - /// forced change will not be re-orged (e.g. 1000 blocks). The GRANDPA voters - /// will start the new authority set using the given finalized block as base. + /// Note that the current authority set of the GRANDPA finality gadget has stalled. + /// + /// This will trigger a forced authority set change at the beginning of the next session, to + /// be enacted `delay` blocks after that. The `delay` should be high enough to safely assume + /// that the block signalling the forced change will not be re-orged e.g. 1000 blocks. + /// The block production rate (which may be slowed down because of finality lagging) should + /// be taken into account when choosing the `delay`. The GRANDPA voters based on the new + /// authority will start voting on top of `best_finalized_block_number` for new finalized + /// blocks. `best_finalized_block_number` should be the highest of the latest finalized + /// block of all validators of the new authority set. + /// /// Only callable by root. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::note_stalled())] pub fn note_stalled( origin: OriginFor, delay: T::BlockNumber, best_finalized_block_number: T::BlockNumber, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { ensure_root(origin)?; - Ok(Self::on_stalled(delay, best_finalized_block_number).into()) + Self::on_stalled(delay, best_finalized_block_number); + Ok(()) } } #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event { - /// New authority set has been applied. \[authority_set\] - NewAuthorities(AuthorityList), + /// New authority set has been applied. + NewAuthorities { authority_set: AuthorityList }, /// Current authority set has been paused. Paused, /// Current authority set has been resumed. Resumed, } - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// Attempt to signal GRANDPA pause when the authority set isn't live @@ -291,7 +301,8 @@ pub mod pallet { /// Pending change: (signaled at, scheduled change). #[pallet::storage] #[pallet::getter(fn pending_change)] - pub(super) type PendingChange = StorageValue<_, StoredPendingChange>; + pub(super) type PendingChange = + StorageValue<_, StoredPendingChange>; /// next block number where we can force a change. #[pallet::storage] @@ -317,18 +328,12 @@ pub mod pallet { #[pallet::getter(fn session_for_set)] pub(super) type SetIdSession = StorageMap<_, Twox64Concat, SetId, SessionIndex>; + #[cfg_attr(feature = "std", derive(Default))] #[pallet::genesis_config] pub struct GenesisConfig { pub authorities: AuthorityList, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { authorities: Default::default() } - } - } - #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { @@ -355,15 +360,21 @@ pub trait WeightInfo { fn note_stalled() -> Weight; } +/// Bounded version of `AuthorityList`, `Limit` being the bound +pub type BoundedAuthorityList = WeakBoundedVec<(AuthorityId, AuthorityWeight), Limit>; + /// A stored pending change. -#[derive(Encode, Decode, TypeInfo)] -pub struct StoredPendingChange { +/// `Limit` is the bound for `next_authorities` +#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] +#[codec(mel_bound(N: MaxEncodedLen, Limit: Get))] +#[scale_info(skip_type_params(Limit))] +pub struct StoredPendingChange { /// The block number this was scheduled at. pub scheduled_at: N, /// The delay in blocks until it will be applied. pub delay: N, - /// The next authority set. - pub next_authorities: AuthorityList, + /// The next authority set, weakly bounded in size by `Limit`. + pub next_authorities: BoundedAuthorityList, /// If defined it means the change was forced and the given block number /// indicates the median last finalized block when the change was signaled. pub forced: Option, @@ -372,7 +383,7 @@ pub struct StoredPendingChange { /// Current state of the GRANDPA authority set. State transitions must happen in /// the same order of states defined below, e.g. `Paused` implies a prior /// `PendingPause`. -#[derive(Decode, Encode, TypeInfo)] +#[derive(Decode, Encode, TypeInfo, MaxEncodedLen)] #[cfg_attr(test, derive(Debug, PartialEq))] pub enum StoredState { /// The current authority set is live, and GRANDPA is enabled. @@ -417,7 +428,7 @@ impl Pallet { Ok(()) } else { - Err(Error::::PauseFailed)? + Err(Error::::PauseFailed.into()) } } @@ -429,7 +440,7 @@ impl Pallet { Ok(()) } else { - Err(Error::::ResumeFailed)? + Err(Error::::ResumeFailed.into()) } } @@ -455,9 +466,9 @@ impl Pallet { if !>::exists() { let scheduled_at = >::block_number(); - if let Some(_) = forced { + if forced.is_some() { if Self::next_forced().map_or(false, |next| next > scheduled_at) { - Err(Error::::TooSoon)? + return Err(Error::::TooSoon.into()) } // only allow the next forced change when twice the window has passed since @@ -465,6 +476,14 @@ impl Pallet { >::put(scheduled_at + in_blocks * 2u32.into()); } + let next_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from( + next_authorities, + Some( + "Warning: The number of authorities given is too big. \ + A runtime configuration adjustment may be needed.", + ), + ); + >::put(StoredPendingChange { delay: in_blocks, scheduled_at, @@ -474,14 +493,14 @@ impl Pallet { Ok(()) } else { - Err(Error::::ChangePending)? + Err(Error::::ChangePending.into()) } } /// Deposit one of this module's logs. fn deposit_log(log: ConsensusLog) { - let log: DigestItem = DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode()); - >::deposit_log(log.into()); + let log = DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode()); + >::deposit_log(log); } // Perform module initialization, abstracted so that it can be called either through genesis @@ -530,14 +549,14 @@ impl Pallet { let previous_set_id_session_index = if set_id == 0 { None } else { - let session_index = Self::session_for_set(set_id - 1) - .ok_or_else(|| Error::::InvalidEquivocationProof)?; + let session_index = + Self::session_for_set(set_id - 1).ok_or(Error::::InvalidEquivocationProof)?; Some(session_index) }; let set_id_session_index = - Self::session_for_set(set_id).ok_or_else(|| Error::::InvalidEquivocationProof)?; + Self::session_for_set(set_id).ok_or(Error::::InvalidEquivocationProof)?; // check that the session id for the membership proof is within the // bounds of the set id reported in the equivocation. @@ -646,7 +665,7 @@ where SetIdSession::::insert(current_set_id, &session_index); } - fn on_disabled(i: usize) { + fn on_disabled(i: u32) { Self::deposit_log(ConsensusLog::OnDisabled(i as u64)) } } diff --git a/frame/grandpa/src/migrations.rs b/frame/grandpa/src/migrations.rs index 05c24e11b3939..7795afcd8034f 100644 --- a/frame/grandpa/src/migrations.rs +++ b/frame/grandpa/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/grandpa/src/migrations/v4.rs b/frame/grandpa/src/migrations/v4.rs index 094f276efef31..81dbd3bab4b67 100644 --- a/frame/grandpa/src/migrations/v4.rs +++ b/frame/grandpa/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +37,7 @@ pub fn migrate>(new_pallet_name: N) -> Weight { target: "runtime::afg", "New pallet name is equal to the old prefix. No migration needs to be done.", ); - return 0 + return Weight::zero() } let storage_version = StorageVersion::get::>(); log::info!( @@ -57,7 +57,7 @@ pub fn migrate>(new_pallet_name: N) -> Weight { ::BlockWeights::get().max_block } else { - 0 + Weight::zero() } } diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 26dda514516a3..1a97b1345fe5d 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,13 +22,14 @@ use crate::{self as pallet_grandpa, AuthorityId, AuthorityList, Config, ConsensusLog}; use ::grandpa as finality_grandpa; use codec::Encode; -use frame_election_provider_support::onchain; +use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, - traits::{GenesisBuild, KeyOwnerProofSystem, OnFinalize, OnInitialize}, + traits::{ + ConstU128, ConstU32, ConstU64, GenesisBuild, KeyOwnerProofSystem, OnFinalize, OnInitialize, + }, }; use pallet_session::historical as pallet_session_historical; -use pallet_staking::EraIndex; use sp_core::{crypto::KeyTypeId, H256}; use sp_finality_grandpa::{RoundNumber, SetId, GRANDPA_ENGINE_ID}; use sp_keyring::Ed25519Keyring; @@ -39,7 +40,7 @@ use sp_runtime::{ traits::{IdentityLookup, OpaqueKeys}, DigestItem, Perbill, }; -use sp_staking::SessionIndex; +use sp_staking::{EraIndex, SessionIndex}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -69,9 +70,8 @@ impl_opaque_keys! { } parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { @@ -79,17 +79,17 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -98,33 +98,32 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } impl frame_system::offchain::SendTransactionTypes for Test where - Call: From, + RuntimeCall: From, { - type OverarchingCall = Call; - type Extrinsic = TestXt; + type OverarchingCall = RuntimeCall; + type Extrinsic = TestXt; } parameter_types! { pub const Period: u64 = 1; pub const Offset: u64 = 0; - pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); } /// Custom `SessionHandler` since we use `TestSessionKeys` as `Keys`. impl pallet_session::Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ValidatorId = u64; type ValidatorIdOf = pallet_staking::StashOf; - type ShouldEndSession = pallet_session::PeriodicSessions; - type NextSessionRotation = pallet_session::PeriodicSessions; + type ShouldEndSession = pallet_session::PeriodicSessions, ConstU64<0>>; + type NextSessionRotation = pallet_session::PeriodicSessions, ConstU64<0>>; type SessionManager = pallet_session::historical::NoteHistoricalRoot; type SessionHandler = ::KeyTypeIdProviders; type Keys = TestSessionKeys; - type DisabledValidatorsThreshold = DisabledValidatorsThreshold; type WeightInfo = (); } @@ -133,41 +132,29 @@ impl pallet_session::historical::Config for Test { type FullIdentificationOf = pallet_staking::ExposureOf; } -parameter_types! { - pub const UncleGenerations: u64 = 0; -} - impl pallet_authorship::Config for Test { type FindAuthor = (); - type UncleGenerations = UncleGenerations; + type UncleGenerations = ConstU64<0>; type FilterUncle = (); type EventHandler = (); } -parameter_types! { - pub const ExistentialDeposit: u128 = 1; -} - impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u128; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; type AccountStore = System; type WeightInfo = (); } -parameter_types! { - pub const MinimumPeriod: u64 = 3; -} - impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<3>; type WeightInfo = (); } @@ -185,43 +172,53 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const SessionsPerEra: SessionIndex = 3; pub const BondingDuration: EraIndex = 3; - pub const SlashDeferDuration: EraIndex = 0; - pub const AttestationPeriod: u64 = 100; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const MaxNominatorRewardedPerValidator: u32 = 64; - pub const ElectionLookahead: u64 = 0; - pub const StakingUnsignedPriority: u64 = u64::MAX / 2; + pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); } -impl onchain::Config for Test { - type Accuracy = Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = ConstU32<100>; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; } impl pallet_staking::Config for Test { - const MAX_NOMINATIONS: u32 = 16; + type MaxNominations = ConstU32<16>; type RewardRemainder = (); type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; + type CurrencyBalance = ::Balance; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; - type SlashDeferDuration = SlashDeferDuration; + type SlashDeferDuration = (); type SlashCancelOrigin = frame_system::EnsureRoot; type SessionInterface = Self; type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type OnStakerSlash = (); + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } impl pallet_offences::Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type IdentificationTuple = pallet_session::historical::IdentificationTuple; type OnOffenceHandler = Staking; } @@ -232,8 +229,7 @@ parameter_types! { } impl Config for Test { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; type KeyOwnerProofSystem = Historical; @@ -249,9 +245,10 @@ impl Config for Test { super::EquivocationHandler; type WeightInfo = (); + type MaxAuthorities = ConstU32<100>; } -pub fn grandpa_log(log: ConsensusLog) -> DigestItem { +pub fn grandpa_log(log: ConsensusLog) -> DigestItem { DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode()) } @@ -334,7 +331,8 @@ pub fn start_session(session_index: SessionIndex) { System::parent_hash() }; - System::initialize(&(i as u64 + 1), &parent_hash, &Default::default(), Default::default()); + System::reset_events(); + System::initialize(&(i as u64 + 1), &parent_hash, &Default::default()); System::set_block_number((i + 1).into()); Timestamp::set_timestamp(System::block_number() * 6000); @@ -353,7 +351,8 @@ pub fn start_era(era_index: EraIndex) { } pub fn initialize_block(number: u64, parent_hash: H256) { - System::initialize(&number, &parent_hash, &Default::default(), Default::default()); + System::reset_events(); + System::initialize(&number, &parent_hash, &Default::default()); } pub fn generate_equivocation_proof( diff --git a/frame/grandpa/src/tests.rs b/frame/grandpa/src/tests.rs index 98f54f966fadc..626decd12821e 100644 --- a/frame/grandpa/src/tests.rs +++ b/frame/grandpa/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,8 +25,8 @@ use codec::Encode; use fg_primitives::ScheduledChange; use frame_support::{ assert_err, assert_noop, assert_ok, + dispatch::{GetDispatchInfo, Pays}, traits::{Currency, OnFinalize, OneSessionHandler}, - weights::{GetDispatchInfo, Pays}, }; use frame_system::{EventRecord, Phase}; use sp_core::H256; @@ -57,7 +57,10 @@ fn authorities_change_logged() { System::events(), vec![EventRecord { phase: Phase::Finalization, - event: Event::NewAuthorities(to_authorities(vec![(4, 1), (5, 1), (6, 1)])).into(), + event: Event::NewAuthorities { + authority_set: to_authorities(vec![(4, 1), (5, 1), (6, 1)]) + } + .into(), topics: vec![], },] ); @@ -93,7 +96,10 @@ fn authorities_change_logged_after_delay() { System::events(), vec![EventRecord { phase: Phase::Finalization, - event: Event::NewAuthorities(to_authorities(vec![(4, 1), (5, 1), (6, 1)])).into(), + event: Event::NewAuthorities { + authority_set: to_authorities(vec![(4, 1), (5, 1), (6, 1)]) + } + .into(), topics: vec![], },] ); @@ -353,7 +359,7 @@ fn report_equivocation_current_set_works() { // report the equivocation and the tx should be dispatched successfully assert_ok!(Grandpa::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ),); @@ -431,7 +437,7 @@ fn report_equivocation_old_set_works() { // report the equivocation using the key ownership proof generated on // the old set, the tx should be dispatched successfully assert_ok!(Grandpa::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ),); @@ -494,7 +500,7 @@ fn report_equivocation_invalid_set_id() { // the call for reporting the equivocation should error assert_err!( Grandpa::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ), @@ -535,7 +541,7 @@ fn report_equivocation_invalid_session() { // proof from the previous set, the session should be invalid. assert_err!( Grandpa::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ), @@ -580,7 +586,7 @@ fn report_equivocation_invalid_key_owner_proof() { // proof for a different key than the one in the equivocation proof. assert_err!( Grandpa::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), invalid_key_owner_proof, ), @@ -611,7 +617,7 @@ fn report_equivocation_invalid_equivocation_proof() { let assert_invalid_equivocation_proof = |equivocation_proof| { assert_err!( Grandpa::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof.clone(), ), @@ -717,7 +723,7 @@ fn report_equivocation_validate_unsigned_prevents_duplicates() { // we submit the report Grandpa::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ) @@ -817,7 +823,7 @@ fn report_equivocation_has_valid_weight() { .map(::WeightInfo::report_equivocation) .collect::>() .windows(2) - .all(|w| w[0] < w[1])); + .all(|w| w[0].ref_time() < w[1].ref_time())); } #[test] @@ -850,12 +856,12 @@ fn valid_equivocation_reports_dont_pay_fees() { .get_dispatch_info(); // it should have non-zero weight and the fee has to be paid. - assert!(info.weight > 0); + assert!(info.weight.any_gt(Weight::zero())); assert_eq!(info.pays_fee, Pays::Yes); // report the equivocation. let post_info = Grandpa::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof.clone()), key_owner_proof.clone(), ) @@ -869,7 +875,7 @@ fn valid_equivocation_reports_dont_pay_fees() { // report the equivocation again which is invalid now since it is // duplicate. let post_info = Grandpa::report_equivocation_unsigned( - Origin::none(), + RuntimeOrigin::none(), Box::new(equivocation_proof), key_owner_proof, ) diff --git a/frame/identity/Cargo.toml b/frame/identity/Cargo.toml index 598be25c5ef38..8c7655af6ab34 100644 --- a/frame/identity/Cargo.toml +++ b/frame/identity/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-identity" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME identity management pallet" readme = "README.md" @@ -13,34 +13,34 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -enumflags2 = { version = "0.6.2" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +enumflags2 = { version = "0.7.4" } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", "scale-info/std", - "sp-std/std", "sp-io/std", "sp-runtime/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", + "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/identity/src/benchmarking.rs b/frame/identity/src/benchmarking.rs index 8bda24ddc73e1..3584eb954b399 100644 --- a/frame/identity/src/benchmarking.rs +++ b/frame/identity/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,14 +22,17 @@ use super::*; use crate::Pallet as Identity; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -use frame_support::{ensure, traits::Get}; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_support::{ + ensure, + traits::{EnsureOrigin, Get}, +}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; const SEED: u32 = 0; -fn assert_last_event(generic_event: ::Event) { +fn assert_last_event(generic_event: ::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } @@ -37,13 +40,11 @@ fn assert_last_event(generic_event: ::Event) { fn add_registrars(r: u32) -> Result<(), &'static str> { for i in 0..r { let registrar: T::AccountId = account("registrar", i, SEED); + let registrar_lookup = T::Lookup::unlookup(registrar.clone()); let _ = T::Currency::make_free_balance_be(®istrar, BalanceOf::::max_value()); - Identity::::add_registrar(RawOrigin::Root.into(), registrar.clone())?; - Identity::::set_fee( - RawOrigin::Signed(registrar.clone()).into(), - i.into(), - 10u32.into(), - )?; + let registrar_origin = T::RegistrarOrigin::successful_origin(); + Identity::::add_registrar(registrar_origin, registrar_lookup)?; + Identity::::set_fee(RawOrigin::Signed(registrar.clone()).into(), i, 10u32.into())?; let fields = IdentityFields( IdentityField::Display | @@ -52,7 +53,7 @@ fn add_registrars(r: u32) -> Result<(), &'static str> { IdentityField::PgpFingerprint | IdentityField::Image | IdentityField::Twitter, ); - Identity::::set_fields(RawOrigin::Signed(registrar.clone()).into(), i.into(), fields)?; + Identity::::set_fields(RawOrigin::Signed(registrar.clone()).into(), i, fields)?; } assert_eq!(Registrars::::get().len(), r as usize); @@ -75,9 +76,11 @@ fn create_sub_accounts( } // Set identity so `set_subs` does not fail. - let _ = T::Currency::make_free_balance_be(&who, BalanceOf::::max_value()); - let info = create_identity_info::(1); - Identity::::set_identity(who_origin.clone().into(), Box::new(info))?; + if IdentityOf::::get(who).is_none() { + let _ = T::Currency::make_free_balance_be(who, BalanceOf::::max_value() / 2u32.into()); + let info = create_identity_info::(1); + Identity::::set_identity(who_origin.into(), Box::new(info))?; + } Ok(subs) } @@ -101,7 +104,7 @@ fn add_sub_accounts( fn create_identity_info(num_fields: u32) -> IdentityInfo { let data = Data::Raw(vec![0; 32].try_into().unwrap()); - let info = IdentityInfo { + IdentityInfo { additional: vec![(data.clone(), data.clone()); num_fields as usize].try_into().unwrap(), display: data.clone(), legal: data.clone(), @@ -110,50 +113,56 @@ fn create_identity_info(num_fields: u32) -> IdentityInfo add_registrars::(r)?; ensure!(Registrars::::get().len() as u32 == r, "Registrars not set up correctly."); - }: _(RawOrigin::Root, account("registrar", r + 1, SEED)) + let origin = T::RegistrarOrigin::successful_origin(); + let account = T::Lookup::unlookup(account("registrar", r + 1, SEED)); + }: _(origin, account) verify { ensure!(Registrars::::get().len() as u32 == r + 1, "Registrars not added."); } set_identity { let r in 1 .. T::MaxRegistrars::get() => add_registrars::(r)?; - let x in 1 .. T::MaxAdditionalFields::get(); + let x in 0 .. T::MaxAdditionalFields::get(); let caller = { // The target user let caller: T::AccountId = whitelisted_caller(); - let caller_lookup: ::Source = T::Lookup::unlookup(caller.clone()); - let caller_origin: ::Origin = RawOrigin::Signed(caller.clone()).into(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let caller_origin: ::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); // Add an initial identity let initial_info = create_identity_info::(1); - Identity::::set_identity(caller_origin.clone(), Box::new(initial_info))?; + Identity::::set_identity(caller_origin.clone(), Box::new(initial_info.clone()))?; // User requests judgement from all the registrars, and they approve for i in 0..r { + let registrar: T::AccountId = account("registrar", i, SEED); + let registrar_lookup = T::Lookup::unlookup(registrar.clone()); + let balance_to_use = T::Currency::minimum_balance() * 10u32.into(); + let _ = T::Currency::make_free_balance_be(®istrar, balance_to_use); + Identity::::request_judgement(caller_origin.clone(), i, 10u32.into())?; Identity::::provide_judgement( - RawOrigin::Signed(account("registrar", i, SEED)).into(), + RawOrigin::Signed(registrar).into(), i, caller_lookup.clone(), - Judgement::Reasonable + Judgement::Reasonable, + T::Hashing::hash_of(&initial_info), )?; } caller }; }: _(RawOrigin::Signed(caller.clone()), Box::new(create_identity_info::(x))) verify { - assert_last_event::(Event::::IdentitySet(caller).into()); + assert_last_event::(Event::::IdentitySet { who: caller }.into()); } // We need to split `set_subs` into two benchmarks to accurately isolate the potential @@ -162,7 +171,7 @@ benchmarks! { set_subs_new { let caller: T::AccountId = whitelisted_caller(); // Create a new subs vec with s sub accounts - let s in 1 .. T::MaxSubAccounts::get() => (); + let s in 0 .. T::MaxSubAccounts::get() => (); let subs = create_sub_accounts::(&caller, s)?; ensure!(SubsOf::::get(&caller).1.len() == 0, "Caller already has subs"); }: set_subs(RawOrigin::Signed(caller.clone()), subs) @@ -173,7 +182,7 @@ benchmarks! { set_subs_old { let caller: T::AccountId = whitelisted_caller(); // Give them p many previous sub accounts. - let p in 1 .. T::MaxSubAccounts::get() => { + let p in 0 .. T::MaxSubAccounts::get() => { let _ = add_sub_accounts::(&caller, p)?; }; // Remove all subs. @@ -189,32 +198,37 @@ benchmarks! { clear_identity { let caller: T::AccountId = whitelisted_caller(); - let caller_origin = ::Origin::from(RawOrigin::Signed(caller.clone())); + let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller.clone())); let caller_lookup = ::unlookup(caller.clone()); let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let r in 1 .. T::MaxRegistrars::get() => add_registrars::(r)?; - let s in 1 .. T::MaxSubAccounts::get() => { + let s in 0 .. T::MaxSubAccounts::get() => { // Give them s many sub accounts let caller: T::AccountId = whitelisted_caller(); let _ = add_sub_accounts::(&caller, s)?; }; - let x in 1 .. T::MaxAdditionalFields::get() => { - // Create their main identity with x additional fields - let info = create_identity_info::(x); - let caller: T::AccountId = whitelisted_caller(); - let caller_origin = ::Origin::from(RawOrigin::Signed(caller)); - Identity::::set_identity(caller_origin, Box::new(info))?; - }; + let x in 0 .. T::MaxAdditionalFields::get(); + + // Create their main identity with x additional fields + let info = create_identity_info::(x); + let caller: T::AccountId = whitelisted_caller(); + let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller.clone())); + Identity::::set_identity(caller_origin.clone(), Box::new(info.clone()))?; // User requests judgement from all the registrars, and they approve for i in 0..r { + let registrar: T::AccountId = account("registrar", i, SEED); + let balance_to_use = T::Currency::minimum_balance() * 10u32.into(); + let _ = T::Currency::make_free_balance_be(®istrar, balance_to_use); + Identity::::request_judgement(caller_origin.clone(), i, 10u32.into())?; Identity::::provide_judgement( - RawOrigin::Signed(account("registrar", i, SEED)).into(), + RawOrigin::Signed(registrar).into(), i, caller_lookup.clone(), - Judgement::Reasonable + Judgement::Reasonable, + T::Hashing::hash_of(&info), )?; } ensure!(IdentityOf::::contains_key(&caller), "Identity does not exist."); @@ -228,44 +242,46 @@ benchmarks! { let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let r in 1 .. T::MaxRegistrars::get() => add_registrars::(r)?; - let x in 1 .. T::MaxAdditionalFields::get() => { + let x in 0 .. T::MaxAdditionalFields::get() => { // Create their main identity with x additional fields let info = create_identity_info::(x); let caller: T::AccountId = whitelisted_caller(); - let caller_origin = ::Origin::from(RawOrigin::Signed(caller)); + let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller)); Identity::::set_identity(caller_origin, Box::new(info))?; }; }: _(RawOrigin::Signed(caller.clone()), r - 1, 10u32.into()) verify { - assert_last_event::(Event::::JudgementRequested(caller, r-1).into()); + assert_last_event::(Event::::JudgementRequested { who: caller, registrar_index: r-1 }.into()); } cancel_request { let caller: T::AccountId = whitelisted_caller(); - let caller_origin = ::Origin::from(RawOrigin::Signed(caller.clone())); + let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller.clone())); let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let r in 1 .. T::MaxRegistrars::get() => add_registrars::(r)?; - let x in 1 .. T::MaxAdditionalFields::get() => { + let x in 0 .. T::MaxAdditionalFields::get() => { // Create their main identity with x additional fields let info = create_identity_info::(x); let caller: T::AccountId = whitelisted_caller(); - let caller_origin = ::Origin::from(RawOrigin::Signed(caller)); + let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller)); Identity::::set_identity(caller_origin, Box::new(info))?; }; Identity::::request_judgement(caller_origin, r - 1, 10u32.into())?; }: _(RawOrigin::Signed(caller.clone()), r - 1) verify { - assert_last_event::(Event::::JudgementUnrequested(caller, r-1).into()); + assert_last_event::(Event::::JudgementUnrequested { who: caller, registrar_index: r-1 }.into()); } set_fee { let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; - Identity::::add_registrar(RawOrigin::Root.into(), caller.clone())?; + let registrar_origin = T::RegistrarOrigin::successful_origin(); + Identity::::add_registrar(registrar_origin, caller_lookup)?; let registrars = Registrars::::get(); ensure!(registrars[r as usize].as_ref().unwrap().fee == 0u32.into(), "Fee already set."); }: _(RawOrigin::Signed(caller), r, 100u32.into()) @@ -276,14 +292,17 @@ benchmarks! { set_account_id { let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; - Identity::::add_registrar(RawOrigin::Root.into(), caller.clone())?; + let registrar_origin = T::RegistrarOrigin::successful_origin(); + Identity::::add_registrar(registrar_origin, caller_lookup)?; let registrars = Registrars::::get(); - ensure!(registrars[r as usize].as_ref().unwrap().account == caller.clone(), "id not set."); - }: _(RawOrigin::Signed(caller), r, account("new", 0, SEED)) + ensure!(registrars[r as usize].as_ref().unwrap().account == caller, "id not set."); + let new_account = T::Lookup::unlookup(account("new", 0, SEED)); + }: _(RawOrigin::Signed(caller), r, new_account) verify { let registrars = Registrars::::get(); ensure!(registrars[r as usize].as_ref().unwrap().account == account("new", 0, SEED), "id not changed."); @@ -291,11 +310,13 @@ benchmarks! { set_fields { let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; - Identity::::add_registrar(RawOrigin::Root.into(), caller.clone())?; + let registrar_origin = T::RegistrarOrigin::successful_origin(); + Identity::::add_registrar(registrar_origin, caller_lookup)?; let fields = IdentityFields( IdentityField::Display | IdentityField::Legal | IdentityField::Web | IdentityField::Riot | IdentityField::Email | IdentityField::PgpFingerprint | IdentityField::Image | IdentityField::Twitter @@ -311,58 +332,67 @@ benchmarks! { provide_judgement { // The user let user: T::AccountId = account("user", r, SEED); - let user_origin = ::Origin::from(RawOrigin::Signed(user.clone())); + let user_origin = ::RuntimeOrigin::from(RawOrigin::Signed(user.clone())); let user_lookup = ::unlookup(user.clone()); let _ = T::Currency::make_free_balance_be(&user, BalanceOf::::max_value()); let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; - let x in 1 .. T::MaxAdditionalFields::get() => { - let info = create_identity_info::(x); - Identity::::set_identity(user_origin.clone(), Box::new(info))?; - }; + let x in 0 .. T::MaxAdditionalFields::get(); - Identity::::add_registrar(RawOrigin::Root.into(), caller.clone())?; - Identity::::request_judgement(user_origin.clone(), r, 10u32.into())?; - }: _(RawOrigin::Signed(caller), r, user_lookup, Judgement::Reasonable) + let info = create_identity_info::(x); + let info_hash = T::Hashing::hash_of(&info); + Identity::::set_identity(user_origin.clone(), Box::new(info))?; + + let registrar_origin = T::RegistrarOrigin::successful_origin(); + Identity::::add_registrar(registrar_origin, caller_lookup)?; + Identity::::request_judgement(user_origin, r, 10u32.into())?; + }: _(RawOrigin::Signed(caller), r, user_lookup, Judgement::Reasonable, info_hash) verify { - assert_last_event::(Event::::JudgementGiven(user, r).into()) + assert_last_event::(Event::::JudgementGiven { target: user, registrar_index: r }.into()) } kill_identity { let r in 1 .. T::MaxRegistrars::get() => add_registrars::(r)?; - let s in 1 .. T::MaxSubAccounts::get(); - let x in 1 .. T::MaxAdditionalFields::get(); + let s in 0 .. T::MaxSubAccounts::get(); + let x in 0 .. T::MaxAdditionalFields::get(); let target: T::AccountId = account("target", 0, SEED); - let target_origin: ::Origin = RawOrigin::Signed(target.clone()).into(); - let target_lookup: ::Source = T::Lookup::unlookup(target.clone()); + let target_origin: ::RuntimeOrigin = RawOrigin::Signed(target.clone()).into(); + let target_lookup = T::Lookup::unlookup(target.clone()); let _ = T::Currency::make_free_balance_be(&target, BalanceOf::::max_value()); let info = create_identity_info::(x); - Identity::::set_identity(target_origin.clone(), Box::new(info))?; + Identity::::set_identity(target_origin.clone(), Box::new(info.clone()))?; let _ = add_sub_accounts::(&target, s)?; // User requests judgement from all the registrars, and they approve for i in 0..r { + let registrar: T::AccountId = account("registrar", i, SEED); + let balance_to_use = T::Currency::minimum_balance() * 10u32.into(); + let _ = T::Currency::make_free_balance_be(®istrar, balance_to_use); + Identity::::request_judgement(target_origin.clone(), i, 10u32.into())?; Identity::::provide_judgement( - RawOrigin::Signed(account("registrar", i, SEED)).into(), + RawOrigin::Signed(registrar).into(), i, target_lookup.clone(), - Judgement::Reasonable + Judgement::Reasonable, + T::Hashing::hash_of(&info), )?; } ensure!(IdentityOf::::contains_key(&target), "Identity not set"); - }: _(RawOrigin::Root, target_lookup) + let origin = T::ForceOrigin::successful_origin(); + }: _(origin, target_lookup) verify { ensure!(!IdentityOf::::contains_key(&target), "Identity not removed"); } add_sub { - let s in 1 .. T::MaxSubAccounts::get() - 1; + let s in 0 .. T::MaxSubAccounts::get() - 1; let caller: T::AccountId = whitelisted_caller(); let _ = add_sub_accounts::(&caller, s)?; @@ -398,7 +428,7 @@ benchmarks! { } quit_sub { - let s in 1 .. T::MaxSubAccounts::get() - 1; + let s in 0 .. T::MaxSubAccounts::get() - 1; let caller: T::AccountId = whitelisted_caller(); let sup = account("super", 0, SEED); @@ -411,6 +441,5 @@ benchmarks! { ensure!(!SuperOf::::contains_key(&caller), "Sub not removed"); } + impl_benchmark_test_suite!(Identity, crate::tests::new_test_ext(), crate::tests::Test); } - -impl_benchmark_test_suite!(Identity, crate::tests::new_test_ext(), crate::tests::Test); diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index a91381f1edd8b..8eab2c67418a1 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -79,8 +79,8 @@ mod types; pub mod weights; use frame_support::traits::{BalanceStatus, Currency, OnUnbalanced, ReservableCurrency}; -use sp_runtime::traits::{AppendZerosInput, Saturating, StaticLookup, Zero}; -use sp_std::{convert::TryInto, prelude::*}; +use sp_runtime::traits::{AppendZerosInput, Hash, Saturating, StaticLookup, Zero}; +use sp_std::prelude::*; pub use weights::WeightInfo; pub use pallet::*; @@ -94,6 +94,7 @@ type BalanceOf = type NegativeImbalanceOf = <::Currency as Currency< ::AccountId, >>::NegativeImbalance; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] pub mod pallet { @@ -104,7 +105,7 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The currency trait. type Currency: ReservableCurrency; @@ -141,10 +142,10 @@ pub mod pallet { type Slashed: OnUnbalanced>; /// The origin which may forcibly set or remove a name. Root can always do this. - type ForceOrigin: EnsureOrigin; + type ForceOrigin: EnsureOrigin; /// The origin which may add or remove registrars. Root can always do this. - type RegistrarOrigin: EnsureOrigin; + type RegistrarOrigin: EnsureOrigin; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -152,7 +153,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::generate_storage_info] pub struct Pallet(_); /// Information that is pertinent to identify the entity behind an account. @@ -236,33 +236,36 @@ pub mod pallet { NotSub, /// Sub-account isn't owned by sender. NotOwned, + /// The provided judgement was for a different identity. + JudgementForDifferentIdentity, + /// Error that occurs when there is an issue paying for judgement. + JudgementPaymentFailed, } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// A name was set or reset (which will remove all judgements). \[who\] - IdentitySet(T::AccountId), - /// A name was cleared, and the given balance returned. \[who, deposit\] - IdentityCleared(T::AccountId, BalanceOf), - /// A name was removed and the given balance slashed. \[who, deposit\] - IdentityKilled(T::AccountId, BalanceOf), - /// A judgement was asked from a registrar. \[who, registrar_index\] - JudgementRequested(T::AccountId, RegistrarIndex), - /// A judgement request was retracted. \[who, registrar_index\] - JudgementUnrequested(T::AccountId, RegistrarIndex), - /// A judgement was given by a registrar. \[target, registrar_index\] - JudgementGiven(T::AccountId, RegistrarIndex), - /// A registrar was added. \[registrar_index\] - RegistrarAdded(RegistrarIndex), - /// A sub-identity was added to an identity and the deposit paid. \[sub, main, deposit\] - SubIdentityAdded(T::AccountId, T::AccountId, BalanceOf), + /// A name was set or reset (which will remove all judgements). + IdentitySet { who: T::AccountId }, + /// A name was cleared, and the given balance returned. + IdentityCleared { who: T::AccountId, deposit: BalanceOf }, + /// A name was removed and the given balance slashed. + IdentityKilled { who: T::AccountId, deposit: BalanceOf }, + /// A judgement was asked from a registrar. + JudgementRequested { who: T::AccountId, registrar_index: RegistrarIndex }, + /// A judgement request was retracted. + JudgementUnrequested { who: T::AccountId, registrar_index: RegistrarIndex }, + /// A judgement was given by a registrar. + JudgementGiven { target: T::AccountId, registrar_index: RegistrarIndex }, + /// A registrar was added. + RegistrarAdded { registrar_index: RegistrarIndex }, + /// A sub-identity was added to an identity and the deposit paid. + SubIdentityAdded { sub: T::AccountId, main: T::AccountId, deposit: BalanceOf }, /// A sub-identity was removed from an identity and the deposit freed. - /// \[sub, main, deposit\] - SubIdentityRemoved(T::AccountId, T::AccountId, BalanceOf), + SubIdentityRemoved { sub: T::AccountId, main: T::AccountId, deposit: BalanceOf }, /// A sub-identity was cleared, and the given deposit repatriated from the - /// main identity account to the sub-identity account. \[sub, main, deposit\] - SubIdentityRevoked(T::AccountId, T::AccountId, BalanceOf), + /// main identity account to the sub-identity account. + SubIdentityRevoked { sub: T::AccountId, main: T::AccountId, deposit: BalanceOf }, } #[pallet::call] @@ -281,12 +284,14 @@ pub mod pallet { /// - One storage mutation (codec `O(R)`). /// - One event. /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::add_registrar(T::MaxRegistrars::get()))] pub fn add_registrar( origin: OriginFor, - account: T::AccountId, + account: AccountIdLookupOf, ) -> DispatchResultWithPostInfo { T::RegistrarOrigin::ensure_origin(origin)?; + let account = T::Lookup::lookup(account)?; let (i, registrar_count) = >::try_mutate( |registrars| -> Result<(RegistrarIndex, usize), DispatchError> { @@ -301,7 +306,7 @@ pub mod pallet { }, )?; - Self::deposit_event(Event::RegistrarAdded(i)); + Self::deposit_event(Event::RegistrarAdded { registrar_index: i }); Ok(Some(T::WeightInfo::add_registrar(registrar_count as u32)).into()) } @@ -325,9 +330,10 @@ pub mod pallet { /// - One storage mutation (codec-read `O(X' + R)`, codec-write `O(X + R)`). /// - One event. /// # + #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::set_identity( - T::MaxRegistrars::get().into(), // R - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X ))] pub fn set_identity( origin: OriginFor, @@ -364,7 +370,7 @@ pub mod pallet { let judgements = id.judgements.len(); >::insert(&sender, id); - Self::deposit_event(Event::IdentitySet(sender)); + Self::deposit_event(Event::IdentitySet { who: sender }); Ok(Some(T::WeightInfo::set_identity( judgements as u32, // R @@ -400,6 +406,7 @@ pub mod pallet { // N storage items for N sub accounts. Right now the weight on this function // is a large overestimate due to the fact that it could potentially write // to 2 x T::MaxSubAccounts::get(). + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::set_subs_old(T::MaxSubAccounts::get()) // P: Assume max sub accounts removed. .saturating_add(T::WeightInfo::set_subs_new(subs.len() as u32)) // S: Assume all subs are new. )] @@ -418,7 +425,7 @@ pub mod pallet { let new_deposit = T::SubAccountDeposit::get() * >::from(subs.len() as u32); let not_other_sub = - subs.iter().filter_map(|i| SuperOf::::get(&i.0)).all(|i| &i.0 == &sender); + subs.iter().filter_map(|i| SuperOf::::get(&i.0)).all(|i| i.0 == sender); ensure!(not_other_sub, Error::::AlreadyClaimed); if old_deposit < new_deposit { @@ -471,10 +478,11 @@ pub mod pallet { /// - `2` storage reads and `S + 2` storage deletions. /// - One event. /// # + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::clear_identity( - T::MaxRegistrars::get().into(), // R - T::MaxSubAccounts::get().into(), // S - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxSubAccounts::get(), // S + T::MaxAdditionalFields::get(), // X ))] pub fn clear_identity(origin: OriginFor) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; @@ -486,10 +494,10 @@ pub mod pallet { >::remove(sub); } - let err_amount = T::Currency::unreserve(&sender, deposit.clone()); + let err_amount = T::Currency::unreserve(&sender, deposit); debug_assert!(err_amount.is_zero()); - Self::deposit_event(Event::IdentityCleared(sender, deposit)); + Self::deposit_event(Event::IdentityCleared { who: sender, deposit }); Ok(Some(T::WeightInfo::clear_identity( id.judgements.len() as u32, // R @@ -522,9 +530,10 @@ pub mod pallet { /// - Storage: 1 read `O(R)`, 1 mutate `O(X + R)`. /// - One event. /// # + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::request_judgement( - T::MaxRegistrars::get().into(), // R - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X ))] pub fn request_judgement( origin: OriginFor, @@ -544,7 +553,7 @@ pub mod pallet { match id.judgements.binary_search_by_key(®_index, |x| x.0) { Ok(i) => if id.judgements[i].1.is_sticky() { - Err(Error::::StickyJudgement)? + return Err(Error::::StickyJudgement.into()) } else { id.judgements[i] = item }, @@ -558,7 +567,10 @@ pub mod pallet { let extra_fields = id.info.additional.len(); >::insert(&sender, id); - Self::deposit_event(Event::JudgementRequested(sender, reg_index)); + Self::deposit_event(Event::JudgementRequested { + who: sender, + registrar_index: reg_index, + }); Ok(Some(T::WeightInfo::request_judgement(judgements as u32, extra_fields as u32)) .into()) @@ -581,9 +593,10 @@ pub mod pallet { /// - One storage mutation `O(R + X)`. /// - One event /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::cancel_request( - T::MaxRegistrars::get().into(), // R - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X ))] pub fn cancel_request( origin: OriginFor, @@ -599,7 +612,7 @@ pub mod pallet { let fee = if let Judgement::FeePaid(fee) = id.judgements.remove(pos).1 { fee } else { - Err(Error::::JudgementGiven)? + return Err(Error::::JudgementGiven.into()) }; let err_amount = T::Currency::unreserve(&sender, fee); @@ -608,7 +621,10 @@ pub mod pallet { let extra_fields = id.info.additional.len(); >::insert(&sender, id); - Self::deposit_event(Event::JudgementUnrequested(sender, reg_index)); + Self::deposit_event(Event::JudgementUnrequested { + who: sender, + registrar_index: reg_index, + }); Ok(Some(T::WeightInfo::cancel_request(judgements as u32, extra_fields as u32)).into()) } @@ -626,6 +642,7 @@ pub mod pallet { /// - One storage mutation `O(R)`. /// - Benchmark: 7.315 + R * 0.329 µs (min squares analysis) /// # + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::set_fee(T::MaxRegistrars::get()))] // R pub fn set_fee( origin: OriginFor, @@ -664,13 +681,15 @@ pub mod pallet { /// - One storage mutation `O(R)`. /// - Benchmark: 8.823 + R * 0.32 µs (min squares analysis) /// # + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::set_account_id(T::MaxRegistrars::get()))] // R pub fn set_account_id( origin: OriginFor, #[pallet::compact] index: RegistrarIndex, - new: T::AccountId, + new: AccountIdLookupOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; + let new = T::Lookup::lookup(new)?; let registrars = >::mutate(|rs| -> Result { rs.get_mut(index as usize) @@ -702,6 +721,7 @@ pub mod pallet { /// - One storage mutation `O(R)`. /// - Benchmark: 7.464 + R * 0.325 µs (min squares analysis) /// # + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::set_fields(T::MaxRegistrars::get()))] // R pub fn set_fields( origin: OriginFor, @@ -739,6 +759,7 @@ pub mod pallet { /// - `target`: the account whose identity the judgement is upon. This must be an account /// with a registered identity. /// - `judgement`: the judgement of the registrar of index `reg_index` about `target`. + /// - `identity`: The hash of the [`IdentityInfo`] for that the judgement is provided. /// /// Emits `JudgementGiven` if successful. /// @@ -749,15 +770,17 @@ pub mod pallet { /// - Storage: 1 read `O(R)`, 1 mutate `O(R + X)`. /// - One event. /// # + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::provide_judgement( - T::MaxRegistrars::get().into(), // R - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X ))] pub fn provide_judgement( origin: OriginFor, #[pallet::compact] reg_index: RegistrarIndex, - target: ::Source, + target: AccountIdLookupOf, judgement: Judgement>, + identity: T::Hash, ) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; let target = T::Lookup::lookup(target)?; @@ -765,20 +788,25 @@ pub mod pallet { >::get() .get(reg_index as usize) .and_then(Option::as_ref) - .and_then(|r| if r.account == sender { Some(r) } else { None }) + .filter(|r| r.account == sender) .ok_or(Error::::InvalidIndex)?; let mut id = >::get(&target).ok_or(Error::::InvalidTarget)?; + if T::Hashing::hash_of(&id.info) != identity { + return Err(Error::::JudgementForDifferentIdentity.into()) + } + let item = (reg_index, judgement); match id.judgements.binary_search_by_key(®_index, |x| x.0) { Ok(position) => { if let Judgement::FeePaid(fee) = id.judgements[position].1 { - let _ = T::Currency::repatriate_reserved( + T::Currency::repatriate_reserved( &target, &sender, fee, BalanceStatus::Free, - ); + ) + .map_err(|_| Error::::JudgementPaymentFailed)?; } id.judgements[position] = item }, @@ -791,7 +819,7 @@ pub mod pallet { let judgements = id.judgements.len(); let extra_fields = id.info.additional.len(); >::insert(&target, id); - Self::deposit_event(Event::JudgementGiven(target, reg_index)); + Self::deposit_event(Event::JudgementGiven { target, registrar_index: reg_index }); Ok(Some(T::WeightInfo::provide_judgement(judgements as u32, extra_fields as u32)) .into()) @@ -816,14 +844,15 @@ pub mod pallet { /// - `S + 2` storage mutations. /// - One event. /// # + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::kill_identity( - T::MaxRegistrars::get().into(), // R - T::MaxSubAccounts::get().into(), // S - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxSubAccounts::get(), // S + T::MaxAdditionalFields::get(), // X ))] pub fn kill_identity( origin: OriginFor, - target: ::Source, + target: AccountIdLookupOf, ) -> DispatchResultWithPostInfo { T::ForceOrigin::ensure_origin(origin)?; @@ -839,7 +868,7 @@ pub mod pallet { // Slash their deposit from them. T::Slashed::on_unbalanced(T::Currency::slash_reserved(&target, deposit).0); - Self::deposit_event(Event::IdentityKilled(target, deposit)); + Self::deposit_event(Event::IdentityKilled { who: target, deposit }); Ok(Some(T::WeightInfo::kill_identity( id.judgements.len() as u32, // R @@ -856,10 +885,11 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and the sender must have a registered /// sub identity of `sub`. + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::add_sub(T::MaxSubAccounts::get()))] pub fn add_sub( origin: OriginFor, - sub: ::Source, + sub: AccountIdLookupOf, data: Data, ) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -882,7 +912,7 @@ pub mod pallet { sub_ids.try_push(sub.clone()).expect("sub ids length checked above; qed"); *subs_deposit = subs_deposit.saturating_add(deposit); - Self::deposit_event(Event::SubIdentityAdded(sub, sender.clone(), deposit)); + Self::deposit_event(Event::SubIdentityAdded { sub, main: sender.clone(), deposit }); Ok(()) }) } @@ -891,10 +921,11 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and the sender must have a registered /// sub identity of `sub`. + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::rename_sub(T::MaxSubAccounts::get()))] pub fn rename_sub( origin: OriginFor, - sub: ::Source, + sub: AccountIdLookupOf, data: Data, ) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -912,11 +943,9 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and the sender must have a registered /// sub identity of `sub`. + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::remove_sub(T::MaxSubAccounts::get()))] - pub fn remove_sub( - origin: OriginFor, - sub: ::Source, - ) -> DispatchResult { + pub fn remove_sub(origin: OriginFor, sub: AccountIdLookupOf) -> DispatchResult { let sender = ensure_signed(origin)?; ensure!(IdentityOf::::contains_key(&sender), Error::::NoIdentity); let sub = T::Lookup::lookup(sub)?; @@ -929,7 +958,7 @@ pub mod pallet { *subs_deposit -= deposit; let err_amount = T::Currency::unreserve(&sender, deposit); debug_assert!(err_amount.is_zero()); - Self::deposit_event(Event::SubIdentityRemoved(sub, sender, deposit)); + Self::deposit_event(Event::SubIdentityRemoved { sub, main: sender, deposit }); }); Ok(()) } @@ -944,6 +973,7 @@ pub mod pallet { /// /// NOTE: This should not normally be used, but is provided in the case that the non- /// controller of an account is maliciously registered as a sub-account. + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::quit_sub(T::MaxSubAccounts::get()))] pub fn quit_sub(origin: OriginFor) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -954,7 +984,11 @@ pub mod pallet { *subs_deposit -= deposit; let _ = T::Currency::repatriate_reserved(&sup, &sender, deposit, BalanceStatus::Free); - Self::deposit_event(Event::SubIdentityRevoked(sender, sup.clone(), deposit)); + Self::deposit_event(Event::SubIdentityRevoked { + sub: sender, + main: sup.clone(), + deposit, + }); }); Ok(()) } @@ -970,4 +1004,10 @@ impl Pallet { .filter_map(|a| SuperOf::::get(&a).map(|x| (a, x.1))) .collect() } + + /// Check if the account has corresponding identity information by the identity field. + pub fn has_identity(who: &T::AccountId, fields: u64) -> bool { + IdentityOf::::get(who) + .map_or(false, |registration| (registration.info.fields().0.bits() & fields) == fields) + } } diff --git a/frame/identity/src/tests.rs b/frame/identity/src/tests.rs index c842b0e2f64be..6b0006971f0e6 100644 --- a/frame/identity/src/tests.rs +++ b/frame/identity/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,12 @@ use super::*; use crate as pallet_identity; use codec::{Decode, Encode}; -use frame_support::{assert_noop, assert_ok, ord_parameter_types, parameter_types, BoundedVec}; -use frame_system::{EnsureOneOf, EnsureRoot, EnsureSignedBy}; +use frame_support::{ + assert_noop, assert_ok, ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64, EitherOfDiverse}, + BoundedVec, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -45,25 +49,24 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; @@ -73,43 +76,40 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} + impl pallet_balances::Config for Test { type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } + parameter_types! { - pub const BasicDeposit: u64 = 10; - pub const FieldDeposit: u64 = 10; - pub const SubAccountDeposit: u64 = 10; - pub const MaxSubAccounts: u32 = 2; pub const MaxAdditionalFields: u32 = 2; pub const MaxRegistrars: u32 = 20; } + ord_parameter_types! { pub const One: u64 = 1; pub const Two: u64 = 2; } -type EnsureOneOrRoot = EnsureOneOf, EnsureSignedBy>; -type EnsureTwoOrRoot = EnsureOneOf, EnsureSignedBy>; +type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; +type EnsureTwoOrRoot = EitherOfDiverse, EnsureSignedBy>; impl pallet_identity::Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; type Slashed = (); - type BasicDeposit = BasicDeposit; - type FieldDeposit = FieldDeposit; - type SubAccountDeposit = SubAccountDeposit; - type MaxSubAccounts = MaxSubAccounts; + type BasicDeposit = ConstU64<10>; + type FieldDeposit = ConstU64<10>; + type SubAccountDeposit = ConstU64<10>; + type MaxSubAccounts = ConstU32<2>; type MaxAdditionalFields = MaxAdditionalFields; type MaxRegistrars = MaxRegistrars; type RegistrarOrigin = EnsureOneOrRoot; @@ -148,41 +148,44 @@ fn editing_subaccounts_should_work() { new_test_ext().execute_with(|| { let data = |x| Data::Raw(vec![x; 1].try_into().unwrap()); - assert_noop!(Identity::add_sub(Origin::signed(10), 20, data(1)), Error::::NoIdentity); + assert_noop!( + Identity::add_sub(RuntimeOrigin::signed(10), 20, data(1)), + Error::::NoIdentity + ); - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); // first sub account - assert_ok!(Identity::add_sub(Origin::signed(10), 1, data(1))); + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 1, data(1))); assert_eq!(SuperOf::::get(1), Some((10, data(1)))); assert_eq!(Balances::free_balance(10), 80); // second sub account - assert_ok!(Identity::add_sub(Origin::signed(10), 2, data(2))); + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 2, data(2))); assert_eq!(SuperOf::::get(1), Some((10, data(1)))); assert_eq!(SuperOf::::get(2), Some((10, data(2)))); assert_eq!(Balances::free_balance(10), 70); // third sub account is too many assert_noop!( - Identity::add_sub(Origin::signed(10), 3, data(3)), + Identity::add_sub(RuntimeOrigin::signed(10), 3, data(3)), Error::::TooManySubAccounts ); // rename first sub account - assert_ok!(Identity::rename_sub(Origin::signed(10), 1, data(11))); + assert_ok!(Identity::rename_sub(RuntimeOrigin::signed(10), 1, data(11))); assert_eq!(SuperOf::::get(1), Some((10, data(11)))); assert_eq!(SuperOf::::get(2), Some((10, data(2)))); assert_eq!(Balances::free_balance(10), 70); // remove first sub account - assert_ok!(Identity::remove_sub(Origin::signed(10), 1)); + assert_ok!(Identity::remove_sub(RuntimeOrigin::signed(10), 1)); assert_eq!(SuperOf::::get(1), None); assert_eq!(SuperOf::::get(2), Some((10, data(2)))); assert_eq!(Balances::free_balance(10), 80); // add third sub account - assert_ok!(Identity::add_sub(Origin::signed(10), 3, data(3))); + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 3, data(3))); assert_eq!(SuperOf::::get(1), None); assert_eq!(SuperOf::::get(2), Some((10, data(2)))); assert_eq!(SuperOf::::get(3), Some((10, data(3)))); @@ -195,27 +198,27 @@ fn resolving_subaccount_ownership_works() { new_test_ext().execute_with(|| { let data = |x| Data::Raw(vec![x; 1].try_into().unwrap()); - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); - assert_ok!(Identity::set_identity(Origin::signed(20), Box::new(twenty()))); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(20), Box::new(twenty()))); // 10 claims 1 as a subaccount - assert_ok!(Identity::add_sub(Origin::signed(10), 1, data(1))); + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 1, data(1))); assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::free_balance(10), 80); assert_eq!(Balances::reserved_balance(10), 20); // 20 cannot claim 1 now assert_noop!( - Identity::add_sub(Origin::signed(20), 1, data(1)), + Identity::add_sub(RuntimeOrigin::signed(20), 1, data(1)), Error::::AlreadyClaimed ); // 1 wants to be with 20 so it quits from 10 - assert_ok!(Identity::quit_sub(Origin::signed(1))); + assert_ok!(Identity::quit_sub(RuntimeOrigin::signed(1))); // 1 gets the 10 that 10 paid. assert_eq!(Balances::free_balance(1), 20); assert_eq!(Balances::free_balance(10), 80); assert_eq!(Balances::reserved_balance(10), 10); // 20 can claim 1 now - assert_ok!(Identity::add_sub(Origin::signed(20), 1, data(1))); + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(20), 1, data(1))); }); } @@ -232,10 +235,10 @@ fn trailing_zeros_decodes_into_default_data() { #[test] fn adding_registrar_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(Origin::signed(1), 3)); - assert_ok!(Identity::set_fee(Origin::signed(3), 0, 10)); + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); let fields = IdentityFields(IdentityField::Display | IdentityField::Legal); - assert_ok!(Identity::set_fields(Origin::signed(3), 0, fields)); + assert_ok!(Identity::set_fields(RuntimeOrigin::signed(3), 0, fields)); assert_eq!( Identity::registrars(), vec![Some(RegistrarInfo { account: 3, fee: 10, fields })] @@ -247,11 +250,11 @@ fn adding_registrar_should_work() { fn amount_of_registrars_is_limited() { new_test_ext().execute_with(|| { for i in 1..MaxRegistrars::get() + 1 { - assert_ok!(Identity::add_registrar(Origin::signed(1), i as u64)); + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), i as u64)); } let last_registrar = MaxRegistrars::get() as u64 + 1; assert_noop!( - Identity::add_registrar(Origin::signed(1), last_registrar), + Identity::add_registrar(RuntimeOrigin::signed(1), last_registrar), Error::::TooManyRegistrars ); }); @@ -260,18 +263,18 @@ fn amount_of_registrars_is_limited() { #[test] fn registration_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(Origin::signed(1), 3)); - assert_ok!(Identity::set_fee(Origin::signed(3), 0, 10)); + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); let mut three_fields = ten(); three_fields.additional.try_push(Default::default()).unwrap(); three_fields.additional.try_push(Default::default()).unwrap(); - assert_eq!(three_fields.additional.try_push(Default::default()), Err(())); - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); + assert!(three_fields.additional.try_push(Default::default()).is_err()); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); assert_eq!(Identity::identity(10).unwrap().info, ten()); assert_eq!(Balances::free_balance(10), 90); - assert_ok!(Identity::clear_identity(Origin::signed(10))); + assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(10))); assert_eq!(Balances::free_balance(10), 100); - assert_noop!(Identity::clear_identity(Origin::signed(10)), Error::::NotNamed); + assert_noop!(Identity::clear_identity(RuntimeOrigin::signed(10)), Error::::NotNamed); }); } @@ -279,27 +282,70 @@ fn registration_should_work() { fn uninvited_judgement_should_work() { new_test_ext().execute_with(|| { assert_noop!( - Identity::provide_judgement(Origin::signed(3), 0, 10, Judgement::Reasonable), + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + H256::random() + ), Error::::InvalidIndex ); - assert_ok!(Identity::add_registrar(Origin::signed(1), 3)); + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); assert_noop!( - Identity::provide_judgement(Origin::signed(3), 0, 10, Judgement::Reasonable), + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + H256::random() + ), Error::::InvalidTarget ); - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); assert_noop!( - Identity::provide_judgement(Origin::signed(10), 0, 10, Judgement::Reasonable), + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + H256::random() + ), + Error::::JudgementForDifferentIdentity + ); + + let identity_hash = BlakeTwo256::hash_of(&ten()); + + assert_noop!( + Identity::provide_judgement( + RuntimeOrigin::signed(10), + 0, + 10, + Judgement::Reasonable, + identity_hash + ), Error::::InvalidIndex ); assert_noop!( - Identity::provide_judgement(Origin::signed(3), 0, 10, Judgement::FeePaid(1)), + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::FeePaid(1), + identity_hash + ), Error::::InvalidJudgement ); - assert_ok!(Identity::provide_judgement(Origin::signed(3), 0, 10, Judgement::Reasonable)); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + identity_hash + )); assert_eq!(Identity::identity(10).unwrap().judgements, vec![(0, Judgement::Reasonable)]); }); } @@ -307,10 +353,16 @@ fn uninvited_judgement_should_work() { #[test] fn clearing_judgement_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(Origin::signed(1), 3)); - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); - assert_ok!(Identity::provide_judgement(Origin::signed(3), 0, 10, Judgement::Reasonable)); - assert_ok!(Identity::clear_identity(Origin::signed(10))); + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + BlakeTwo256::hash_of(&ten()) + )); + assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(10))); assert_eq!(Identity::identity(10), None); }); } @@ -318,12 +370,15 @@ fn clearing_judgement_should_work() { #[test] fn killing_slashing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); - assert_noop!(Identity::kill_identity(Origin::signed(1), 10), BadOrigin); - assert_ok!(Identity::kill_identity(Origin::signed(2), 10)); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); + assert_noop!(Identity::kill_identity(RuntimeOrigin::signed(1), 10), BadOrigin); + assert_ok!(Identity::kill_identity(RuntimeOrigin::signed(2), 10)); assert_eq!(Identity::identity(10), None); assert_eq!(Balances::free_balance(10), 90); - assert_noop!(Identity::kill_identity(Origin::signed(2), 10), Error::::NotNamed); + assert_noop!( + Identity::kill_identity(RuntimeOrigin::signed(2), 10), + Error::::NotNamed + ); }); } @@ -331,17 +386,20 @@ fn killing_slashing_should_work() { fn setting_subaccounts_should_work() { new_test_ext().execute_with(|| { let mut subs = vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))]; - assert_noop!(Identity::set_subs(Origin::signed(10), subs.clone()), Error::::NotFound); + assert_noop!( + Identity::set_subs(RuntimeOrigin::signed(10), subs.clone()), + Error::::NotFound + ); - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); - assert_ok!(Identity::set_subs(Origin::signed(10), subs.clone())); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), subs.clone())); assert_eq!(Balances::free_balance(10), 80); assert_eq!(Identity::subs_of(10), (10, vec![20].try_into().unwrap())); assert_eq!(Identity::super_of(20), Some((10, Data::Raw(vec![40; 1].try_into().unwrap())))); // push another item and re-set it. subs.push((30, Data::Raw(vec![50; 1].try_into().unwrap()))); - assert_ok!(Identity::set_subs(Origin::signed(10), subs.clone())); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), subs.clone())); assert_eq!(Balances::free_balance(10), 70); assert_eq!(Identity::subs_of(10), (20, vec![20, 30].try_into().unwrap())); assert_eq!(Identity::super_of(20), Some((10, Data::Raw(vec![40; 1].try_into().unwrap())))); @@ -349,7 +407,7 @@ fn setting_subaccounts_should_work() { // switch out one of the items and re-set. subs[0] = (40, Data::Raw(vec![60; 1].try_into().unwrap())); - assert_ok!(Identity::set_subs(Origin::signed(10), subs.clone())); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), subs.clone())); assert_eq!(Balances::free_balance(10), 70); // no change in the balance assert_eq!(Identity::subs_of(10), (20, vec![40, 30].try_into().unwrap())); assert_eq!(Identity::super_of(20), None); @@ -357,7 +415,7 @@ fn setting_subaccounts_should_work() { assert_eq!(Identity::super_of(40), Some((10, Data::Raw(vec![60; 1].try_into().unwrap())))); // clear - assert_ok!(Identity::set_subs(Origin::signed(10), vec![])); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), vec![])); assert_eq!(Balances::free_balance(10), 90); assert_eq!(Identity::subs_of(10), (0, BoundedVec::default())); assert_eq!(Identity::super_of(30), None); @@ -365,7 +423,7 @@ fn setting_subaccounts_should_work() { subs.push((20, Data::Raw(vec![40; 1].try_into().unwrap()))); assert_noop!( - Identity::set_subs(Origin::signed(10), subs.clone()), + Identity::set_subs(RuntimeOrigin::signed(10), subs.clone()), Error::::TooManySubAccounts ); }); @@ -374,12 +432,12 @@ fn setting_subaccounts_should_work() { #[test] fn clearing_account_should_remove_subaccounts_and_refund() { new_test_ext().execute_with(|| { - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); assert_ok!(Identity::set_subs( - Origin::signed(10), + RuntimeOrigin::signed(10), vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))] )); - assert_ok!(Identity::clear_identity(Origin::signed(10))); + assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(10))); assert_eq!(Balances::free_balance(10), 100); assert!(Identity::super_of(20).is_none()); }); @@ -388,12 +446,12 @@ fn clearing_account_should_remove_subaccounts_and_refund() { #[test] fn killing_account_should_remove_subaccounts_and_not_refund() { new_test_ext().execute_with(|| { - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); assert_ok!(Identity::set_subs( - Origin::signed(10), + RuntimeOrigin::signed(10), vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))] )); - assert_ok!(Identity::kill_identity(Origin::signed(2), 10)); + assert_ok!(Identity::kill_identity(RuntimeOrigin::signed(2), 10)); assert_eq!(Balances::free_balance(10), 80); assert!(Identity::super_of(20).is_none()); }); @@ -402,18 +460,30 @@ fn killing_account_should_remove_subaccounts_and_not_refund() { #[test] fn cancelling_requested_judgement_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(Origin::signed(1), 3)); - assert_ok!(Identity::set_fee(Origin::signed(3), 0, 10)); - assert_noop!(Identity::cancel_request(Origin::signed(10), 0), Error::::NoIdentity); - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); - assert_ok!(Identity::request_judgement(Origin::signed(10), 0, 10)); - assert_ok!(Identity::cancel_request(Origin::signed(10), 0)); + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + assert_noop!( + Identity::cancel_request(RuntimeOrigin::signed(10), 0), + Error::::NoIdentity + ); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10)); + assert_ok!(Identity::cancel_request(RuntimeOrigin::signed(10), 0)); assert_eq!(Balances::free_balance(10), 90); - assert_noop!(Identity::cancel_request(Origin::signed(10), 0), Error::::NotFound); + assert_noop!( + Identity::cancel_request(RuntimeOrigin::signed(10), 0), + Error::::NotFound + ); - assert_ok!(Identity::provide_judgement(Origin::signed(3), 0, 10, Judgement::Reasonable)); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + BlakeTwo256::hash_of(&ten()) + )); assert_noop!( - Identity::cancel_request(Origin::signed(10), 0), + Identity::cancel_request(RuntimeOrigin::signed(10), 0), Error::::JudgementGiven ); }); @@ -422,49 +492,86 @@ fn cancelling_requested_judgement_should_work() { #[test] fn requesting_judgement_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(Origin::signed(1), 3)); - assert_ok!(Identity::set_fee(Origin::signed(3), 0, 10)); - assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); assert_noop!( - Identity::request_judgement(Origin::signed(10), 0, 9), + Identity::request_judgement(RuntimeOrigin::signed(10), 0, 9), Error::::FeeChanged ); - assert_ok!(Identity::request_judgement(Origin::signed(10), 0, 10)); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10)); // 10 for the judgement request, 10 for the identity. assert_eq!(Balances::free_balance(10), 80); // Re-requesting won't work as we already paid. assert_noop!( - Identity::request_judgement(Origin::signed(10), 0, 10), + Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10), Error::::StickyJudgement ); - assert_ok!(Identity::provide_judgement(Origin::signed(3), 0, 10, Judgement::Erroneous)); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Erroneous, + BlakeTwo256::hash_of(&ten()) + )); // Registrar got their payment now. assert_eq!(Balances::free_balance(3), 20); // Re-requesting still won't work as it's erroneous. assert_noop!( - Identity::request_judgement(Origin::signed(10), 0, 10), + Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10), Error::::StickyJudgement ); // Requesting from a second registrar still works. - assert_ok!(Identity::add_registrar(Origin::signed(1), 4)); - assert_ok!(Identity::request_judgement(Origin::signed(10), 1, 10)); + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 4)); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 1, 10)); // Re-requesting after the judgement has been reduced works. - assert_ok!(Identity::provide_judgement(Origin::signed(3), 0, 10, Judgement::OutOfDate)); - assert_ok!(Identity::request_judgement(Origin::signed(10), 0, 10)); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::OutOfDate, + BlakeTwo256::hash_of(&ten()) + )); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10)); + }); +} + +#[test] +fn provide_judgement_should_return_judgement_payment_failed_error() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10)); + // 10 for the judgement request, 10 for the identity. + assert_eq!(Balances::free_balance(10), 80); + + // This forces judgement payment failed error + Balances::make_free_balance_be(&3, 0); + assert_noop!( + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Erroneous, + BlakeTwo256::hash_of(&ten()) + ), + Error::::JudgementPaymentFailed + ); }); } #[test] fn field_deposit_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(Origin::signed(1), 3)); - assert_ok!(Identity::set_fee(Origin::signed(3), 0, 10)); + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); assert_ok!(Identity::set_identity( - Origin::signed(10), + RuntimeOrigin::signed(10), Box::new(IdentityInfo { additional: vec![ ( @@ -488,15 +595,32 @@ fn field_deposit_should_work() { #[test] fn setting_account_id_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(Origin::signed(1), 3)); + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); // account 4 cannot change the first registrar's identity since it's owned by 3. assert_noop!( - Identity::set_account_id(Origin::signed(4), 0, 3), + Identity::set_account_id(RuntimeOrigin::signed(4), 0, 3), Error::::InvalidIndex ); // account 3 can, because that's the registrar's current account. - assert_ok!(Identity::set_account_id(Origin::signed(3), 0, 4)); + assert_ok!(Identity::set_account_id(RuntimeOrigin::signed(3), 0, 4)); // account 4 can now, because that's their new ID. - assert_ok!(Identity::set_account_id(Origin::signed(4), 0, 3)); + assert_ok!(Identity::set_account_id(RuntimeOrigin::signed(4), 0, 3)); + }); +} + +#[test] +fn test_has_identity() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); + assert!(Identity::has_identity(&10, IdentityField::Display as u64)); + assert!(Identity::has_identity(&10, IdentityField::Legal as u64)); + assert!(Identity::has_identity( + &10, + IdentityField::Display as u64 | IdentityField::Legal as u64 + )); + assert!(!Identity::has_identity( + &10, + IdentityField::Display as u64 | IdentityField::Legal as u64 | IdentityField::Web as u64 + )); }); } diff --git a/frame/identity/src/types.rs b/frame/identity/src/types.rs index ed6aeb18e96a1..b1f15da3b1117 100644 --- a/frame/identity/src/types.rs +++ b/frame/identity/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ use super::*; use codec::{Decode, Encode, MaxEncodedLen}; -use enumflags2::BitFlags; +use enumflags2::{bitflags, BitFlags}; use frame_support::{ traits::{ConstU32, Get}, BoundedVec, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, @@ -53,6 +53,12 @@ pub enum Data { ShaThree256([u8; 32]), } +impl Data { + pub fn is_none(&self) -> bool { + self == &Data::None + } +} + impl Decode for Data { fn decode(input: &mut I) -> sp_std::result::Result { let b = input.read_byte()?; @@ -211,27 +217,22 @@ impl bool { - match self { - Judgement::FeePaid(_) => true, - _ => false, - } + matches!(self, Judgement::FeePaid(_)) } /// Returns `true` if this judgement is one that should not be generally be replaced outside /// of specialized handlers. Examples include "malicious" judgements and deposit-holding /// judgements. pub(crate) fn is_sticky(&self) -> bool { - match self { - Judgement::FeePaid(_) | Judgement::Erroneous => true, - _ => false, - } + matches!(self, Judgement::FeePaid(_) | Judgement::Erroneous) } } /// The fields that we use to identify the owner of an account with. Each corresponds to a field /// in the `IdentityInfo` struct. +#[bitflags] #[repr(u64)] -#[derive(Clone, Copy, PartialEq, Eq, BitFlags, RuntimeDebug, TypeInfo)] +#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum IdentityField { Display = 0b0000000000000000000000000000000000000000000000000000000000000001, Legal = 0b0000000000000000000000000000000000000000000000000000000000000010, @@ -245,7 +246,7 @@ pub enum IdentityField { /// Wrapper type for `BitFlags` that implements `Codec`. #[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)] -pub struct IdentityFields(pub(crate) BitFlags); +pub struct IdentityFields(pub BitFlags); impl MaxEncodedLen for IdentityFields { fn max_encoded_len() -> usize { @@ -283,7 +284,7 @@ impl TypeInfo for IdentityFields { #[derive( CloneNoBound, Encode, Decode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] -#[codec(mel_bound(FieldLimit: Get))] +#[codec(mel_bound())] #[cfg_attr(test, derive(frame_support::DefaultNoBound))] #[scale_info(skip_type_params(FieldLimit))] pub struct IdentityInfo> { @@ -332,6 +333,37 @@ pub struct IdentityInfo> { pub twitter: Data, } +impl> IdentityInfo { + pub(crate) fn fields(&self) -> IdentityFields { + let mut res = >::empty(); + if !self.display.is_none() { + res.insert(IdentityField::Display); + } + if !self.legal.is_none() { + res.insert(IdentityField::Legal); + } + if !self.web.is_none() { + res.insert(IdentityField::Web); + } + if !self.riot.is_none() { + res.insert(IdentityField::Riot); + } + if !self.email.is_none() { + res.insert(IdentityField::Email); + } + if self.pgp_fingerprint.is_some() { + res.insert(IdentityField::PgpFingerprint); + } + if !self.image.is_none() { + res.insert(IdentityField::Image); + } + if !self.twitter.is_none() { + res.insert(IdentityField::Twitter); + } + IdentityFields(res) + } +} + /// Information concerning the identity of the controller of an account. /// /// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a @@ -339,11 +371,7 @@ pub struct IdentityInfo> { #[derive( CloneNoBound, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] -#[codec(mel_bound( - Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq + Zero + Add, - MaxJudgements: Get, - MaxAdditionalFields: Get, -))] +#[codec(mel_bound())] #[scale_info(skip_type_params(MaxJudgements, MaxAdditionalFields))] pub struct Registration< Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq, diff --git a/frame/identity/src/weights.rs b/frame/identity/src/weights.rs index 611909f326eab..1f2e8f98e988b 100644 --- a/frame/identity/src/weights.rs +++ b/frame/identity/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_identity //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/identity/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -67,335 +69,419 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn add_registrar(r: u32, ) -> Weight { - (22_152_000 as Weight) - // Standard Error: 6_000 - .saturating_add((339_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 20_269 nanoseconds. + Weight::from_ref_time(21_910_543 as u64) + // Standard Error: 4_604 + .saturating_add(Weight::from_ref_time(223_104 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. fn set_identity(r: u32, x: u32, ) -> Weight { - (53_017_000 as Weight) - // Standard Error: 14_000 - .saturating_add((279_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 1_000 - .saturating_add((1_081_000 as Weight).saturating_mul(x as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 41_872 nanoseconds. + Weight::from_ref_time(40_230_216 as u64) + // Standard Error: 2_342 + .saturating_add(Weight::from_ref_time(145_168 as u64).saturating_mul(r as u64)) + // Standard Error: 457 + .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity SuperOf (r:1 w:1) + // Storage: Identity SuperOf (r:2 w:2) + /// The range of component `s` is `[0, 100]`. fn set_subs_new(s: u32, ) -> Weight { - (44_693_000 as Weight) - // Standard Error: 1_000 - .saturating_add((6_631_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + // Minimum execution time: 12_024 nanoseconds. + Weight::from_ref_time(32_550_819 as u64) + // Standard Error: 5_057 + .saturating_add(Weight::from_ref_time(2_521_245 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(s as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity SuperOf (r:0 w:1) + // Storage: Identity SuperOf (r:0 w:2) + /// The range of component `p` is `[0, 100]`. fn set_subs_old(p: u32, ) -> Weight { - (42_017_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_193_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) + // Minimum execution time: 12_232 nanoseconds. + Weight::from_ref_time(34_009_761 as u64) + // Standard Error: 5_047 + .saturating_add(Weight::from_ref_time(1_113_100 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(p as u64))) } // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity IdentityOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + /// The range of component `x` is `[0, 100]`. fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { - (50_989_000 as Weight) - // Standard Error: 11_000 - .saturating_add((258_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 1_000 - .saturating_add((2_184_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 1_000 - .saturating_add((579_000 as Weight).saturating_mul(x as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + // Minimum execution time: 57_144 nanoseconds. + Weight::from_ref_time(41_559_247 as u64) + // Standard Error: 9_996 + .saturating_add(Weight::from_ref_time(146_770 as u64).saturating_mul(r as u64)) + // Standard Error: 1_952 + .saturating_add(Weight::from_ref_time(1_086_673 as u64).saturating_mul(s as u64)) + // Standard Error: 1_952 + .saturating_add(Weight::from_ref_time(162_481 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. fn request_judgement(r: u32, x: u32, ) -> Weight { - (55_562_000 as Weight) - // Standard Error: 5_000 - .saturating_add((317_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((1_137_000 as Weight).saturating_mul(x as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 44_726 nanoseconds. + Weight::from_ref_time(41_637_308 as u64) + // Standard Error: 1_907 + .saturating_add(Weight::from_ref_time(219_078 as u64).saturating_mul(r as u64)) + // Standard Error: 372 + .saturating_add(Weight::from_ref_time(309_888 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. fn cancel_request(r: u32, x: u32, ) -> Weight { - (51_744_000 as Weight) - // Standard Error: 6_000 - .saturating_add((192_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((1_131_000 as Weight).saturating_mul(x as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 39_719 nanoseconds. + Weight::from_ref_time(38_008_751 as u64) + // Standard Error: 2_394 + .saturating_add(Weight::from_ref_time(181_870 as u64).saturating_mul(r as u64)) + // Standard Error: 467 + .saturating_add(Weight::from_ref_time(314_990 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_fee(r: u32, ) -> Weight { - (9_472_000 as Weight) - // Standard Error: 3_000 - .saturating_add((321_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 10_634 nanoseconds. + Weight::from_ref_time(11_383_704 as u64) + // Standard Error: 2_250 + .saturating_add(Weight::from_ref_time(193_094 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_account_id(r: u32, ) -> Weight { - (9_705_000 as Weight) - // Standard Error: 3_000 - .saturating_add((312_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 10_840 nanoseconds. + Weight::from_ref_time(11_638_740 as u64) + // Standard Error: 1_985 + .saturating_add(Weight::from_ref_time(193_016 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_fields(r: u32, ) -> Weight { - (9_537_000 as Weight) - // Standard Error: 3_000 - .saturating_add((318_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 10_748 nanoseconds. + Weight::from_ref_time(11_346_901 as u64) + // Standard Error: 2_132 + .saturating_add(Weight::from_ref_time(196_630 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + /// The range of component `x` is `[0, 100]`. fn provide_judgement(r: u32, x: u32, ) -> Weight { - (36_298_000 as Weight) - // Standard Error: 5_000 - .saturating_add((284_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((1_141_000 as Weight).saturating_mul(x as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 33_682 nanoseconds. + Weight::from_ref_time(31_336_603 as u64) + // Standard Error: 3_056 + .saturating_add(Weight::from_ref_time(200_403 as u64).saturating_mul(r as u64)) + // Standard Error: 565 + .saturating_add(Weight::from_ref_time(525_142 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity IdentityOf (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) - fn kill_identity(r: u32, s: u32, _x: u32, ) -> Weight { - (63_238_000 as Weight) - // Standard Error: 10_000 - .saturating_add((246_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 1_000 - .saturating_add((2_184_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + /// The range of component `x` is `[0, 100]`. + fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight { + // Minimum execution time: 68_794 nanoseconds. + Weight::from_ref_time(52_114_486 as u64) + // Standard Error: 4_808 + .saturating_add(Weight::from_ref_time(153_462 as u64).saturating_mul(r as u64)) + // Standard Error: 939 + .saturating_add(Weight::from_ref_time(1_084_612 as u64).saturating_mul(s as u64)) + // Standard Error: 939 + .saturating_add(Weight::from_ref_time(170_112 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[0, 99]`. fn add_sub(s: u32, ) -> Weight { - (57_394_000 as Weight) - // Standard Error: 1_000 - .saturating_add((208_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 37_914 nanoseconds. + Weight::from_ref_time(43_488_083 as u64) + // Standard Error: 1_631 + .saturating_add(Weight::from_ref_time(118_845 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. fn rename_sub(s: u32, ) -> Weight { - (18_274_000 as Weight) - // Standard Error: 0 - .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 16_124 nanoseconds. + Weight::from_ref_time(18_580_462 as u64) + // Standard Error: 688 + .saturating_add(Weight::from_ref_time(67_220 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. fn remove_sub(s: u32, ) -> Weight { - (58_184_000 as Weight) - // Standard Error: 1_000 - .saturating_add((195_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 41_517 nanoseconds. + Weight::from_ref_time(45_123_530 as u64) + // Standard Error: 1_530 + .saturating_add(Weight::from_ref_time(105_429 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[0, 99]`. fn quit_sub(s: u32, ) -> Weight { - (36_304_000 as Weight) - // Standard Error: 1_000 - .saturating_add((191_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 30_171 nanoseconds. + Weight::from_ref_time(33_355_514 as u64) + // Standard Error: 1_286 + .saturating_add(Weight::from_ref_time(114_716 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn add_registrar(r: u32, ) -> Weight { - (22_152_000 as Weight) - // Standard Error: 6_000 - .saturating_add((339_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 20_269 nanoseconds. + Weight::from_ref_time(21_910_543 as u64) + // Standard Error: 4_604 + .saturating_add(Weight::from_ref_time(223_104 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. fn set_identity(r: u32, x: u32, ) -> Weight { - (53_017_000 as Weight) - // Standard Error: 14_000 - .saturating_add((279_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 1_000 - .saturating_add((1_081_000 as Weight).saturating_mul(x as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 41_872 nanoseconds. + Weight::from_ref_time(40_230_216 as u64) + // Standard Error: 2_342 + .saturating_add(Weight::from_ref_time(145_168 as u64).saturating_mul(r as u64)) + // Standard Error: 457 + .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity SuperOf (r:1 w:1) + // Storage: Identity SuperOf (r:2 w:2) + /// The range of component `s` is `[0, 100]`. fn set_subs_new(s: u32, ) -> Weight { - (44_693_000 as Weight) - // Standard Error: 1_000 - .saturating_add((6_631_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + // Minimum execution time: 12_024 nanoseconds. + Weight::from_ref_time(32_550_819 as u64) + // Standard Error: 5_057 + .saturating_add(Weight::from_ref_time(2_521_245 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(s as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity SuperOf (r:0 w:1) + // Storage: Identity SuperOf (r:0 w:2) + /// The range of component `p` is `[0, 100]`. fn set_subs_old(p: u32, ) -> Weight { - (42_017_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_193_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) + // Minimum execution time: 12_232 nanoseconds. + Weight::from_ref_time(34_009_761 as u64) + // Standard Error: 5_047 + .saturating_add(Weight::from_ref_time(1_113_100 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(p as u64))) } // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity IdentityOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + /// The range of component `x` is `[0, 100]`. fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { - (50_989_000 as Weight) - // Standard Error: 11_000 - .saturating_add((258_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 1_000 - .saturating_add((2_184_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 1_000 - .saturating_add((579_000 as Weight).saturating_mul(x as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + // Minimum execution time: 57_144 nanoseconds. + Weight::from_ref_time(41_559_247 as u64) + // Standard Error: 9_996 + .saturating_add(Weight::from_ref_time(146_770 as u64).saturating_mul(r as u64)) + // Standard Error: 1_952 + .saturating_add(Weight::from_ref_time(1_086_673 as u64).saturating_mul(s as u64)) + // Standard Error: 1_952 + .saturating_add(Weight::from_ref_time(162_481 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. fn request_judgement(r: u32, x: u32, ) -> Weight { - (55_562_000 as Weight) - // Standard Error: 5_000 - .saturating_add((317_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((1_137_000 as Weight).saturating_mul(x as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 44_726 nanoseconds. + Weight::from_ref_time(41_637_308 as u64) + // Standard Error: 1_907 + .saturating_add(Weight::from_ref_time(219_078 as u64).saturating_mul(r as u64)) + // Standard Error: 372 + .saturating_add(Weight::from_ref_time(309_888 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. fn cancel_request(r: u32, x: u32, ) -> Weight { - (51_744_000 as Weight) - // Standard Error: 6_000 - .saturating_add((192_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((1_131_000 as Weight).saturating_mul(x as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 39_719 nanoseconds. + Weight::from_ref_time(38_008_751 as u64) + // Standard Error: 2_394 + .saturating_add(Weight::from_ref_time(181_870 as u64).saturating_mul(r as u64)) + // Standard Error: 467 + .saturating_add(Weight::from_ref_time(314_990 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_fee(r: u32, ) -> Weight { - (9_472_000 as Weight) - // Standard Error: 3_000 - .saturating_add((321_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 10_634 nanoseconds. + Weight::from_ref_time(11_383_704 as u64) + // Standard Error: 2_250 + .saturating_add(Weight::from_ref_time(193_094 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_account_id(r: u32, ) -> Weight { - (9_705_000 as Weight) - // Standard Error: 3_000 - .saturating_add((312_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 10_840 nanoseconds. + Weight::from_ref_time(11_638_740 as u64) + // Standard Error: 1_985 + .saturating_add(Weight::from_ref_time(193_016 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_fields(r: u32, ) -> Weight { - (9_537_000 as Weight) - // Standard Error: 3_000 - .saturating_add((318_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 10_748 nanoseconds. + Weight::from_ref_time(11_346_901 as u64) + // Standard Error: 2_132 + .saturating_add(Weight::from_ref_time(196_630 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + /// The range of component `x` is `[0, 100]`. fn provide_judgement(r: u32, x: u32, ) -> Weight { - (36_298_000 as Weight) - // Standard Error: 5_000 - .saturating_add((284_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((1_141_000 as Weight).saturating_mul(x as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 33_682 nanoseconds. + Weight::from_ref_time(31_336_603 as u64) + // Standard Error: 3_056 + .saturating_add(Weight::from_ref_time(200_403 as u64).saturating_mul(r as u64)) + // Standard Error: 565 + .saturating_add(Weight::from_ref_time(525_142 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity IdentityOf (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) - fn kill_identity(r: u32, s: u32, _x: u32, ) -> Weight { - (63_238_000 as Weight) - // Standard Error: 10_000 - .saturating_add((246_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 1_000 - .saturating_add((2_184_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + /// The range of component `x` is `[0, 100]`. + fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight { + // Minimum execution time: 68_794 nanoseconds. + Weight::from_ref_time(52_114_486 as u64) + // Standard Error: 4_808 + .saturating_add(Weight::from_ref_time(153_462 as u64).saturating_mul(r as u64)) + // Standard Error: 939 + .saturating_add(Weight::from_ref_time(1_084_612 as u64).saturating_mul(s as u64)) + // Standard Error: 939 + .saturating_add(Weight::from_ref_time(170_112 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[0, 99]`. fn add_sub(s: u32, ) -> Weight { - (57_394_000 as Weight) - // Standard Error: 1_000 - .saturating_add((208_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 37_914 nanoseconds. + Weight::from_ref_time(43_488_083 as u64) + // Standard Error: 1_631 + .saturating_add(Weight::from_ref_time(118_845 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. fn rename_sub(s: u32, ) -> Weight { - (18_274_000 as Weight) - // Standard Error: 0 - .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 16_124 nanoseconds. + Weight::from_ref_time(18_580_462 as u64) + // Standard Error: 688 + .saturating_add(Weight::from_ref_time(67_220 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. fn remove_sub(s: u32, ) -> Weight { - (58_184_000 as Weight) - // Standard Error: 1_000 - .saturating_add((195_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 41_517 nanoseconds. + Weight::from_ref_time(45_123_530 as u64) + // Standard Error: 1_530 + .saturating_add(Weight::from_ref_time(105_429 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[0, 99]`. fn quit_sub(s: u32, ) -> Weight { - (36_304_000 as Weight) - // Standard Error: 1_000 - .saturating_add((191_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 30_171 nanoseconds. + Weight::from_ref_time(33_355_514 as u64) + // Standard Error: 1_286 + .saturating_add(Weight::from_ref_time(114_716 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } } diff --git a/frame/im-online/Cargo.toml b/frame/im-online/Cargo.toml index a1efd626c0690..c0058e9f2371d 100644 --- a/frame/im-online/Cargo.toml +++ b/frame/im-online/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-im-online" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME's I'm online pallet" readme = "README.md" @@ -13,20 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../../primitives/application-crypto" } -pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.14", default-features = false } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-session = { version = "4.0.0-dev", path = "../session" } @@ -34,18 +33,19 @@ pallet-session = { version = "4.0.0-dev", path = "../session" } [features] default = ["std"] std = [ - "sp-application-crypto/std", - "pallet-authorship/std", + "frame-benchmarking?/std", "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-authorship/std", "scale-info/std", + "sp-application-crypto/std", "sp-core/std", - "sp-std/std", "sp-io/std", "sp-runtime/std", "sp-staking/std", - "frame-support/std", - "frame-system/std", - "log/std", + "sp-std/std", ] -runtime-benchmarks = ["frame-benchmarking"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/im-online/README.md b/frame/im-online/README.md index 46b2268f18b12..be11e0c49dff3 100644 --- a/frame/im-online/README.md +++ b/frame/im-online/README.md @@ -26,21 +26,29 @@ It is submitted as an Unsigned Transaction via off-chain workers. ## Usage ```rust -use frame_support::{decl_module, dispatch}; -use frame_system::ensure_signed; use pallet_im_online::{self as im_online}; -pub trait Config: im_online::Config {} - -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - #[weight = 0] - pub fn is_online(origin, authority_index: u32) -> dispatch::DispatchResult { - let _sender = ensure_signed(origin)?; - let _is_online = >::is_online(authority_index); - Ok(()) - } - } +#[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: frame_system::Config + im_online::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn is_online(origin: OriginFor, authority_index: u32) -> DispatchResult { + let _sender = ensure_signed(origin)?; + let _is_online = >::is_online(authority_index); + Ok(()) + } + } } ``` diff --git a/frame/im-online/src/benchmarking.rs b/frame/im-online/src/benchmarking.rs index 1043a97f67def..edc21043c34de 100644 --- a/frame/im-online/src/benchmarking.rs +++ b/frame/im-online/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,8 @@ use super::*; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; -use frame_support::traits::UnfilteredDispatchable; +use frame_benchmarking::benchmarks; +use frame_support::{traits::UnfilteredDispatchable, WeakBoundedVec}; use frame_system::RawOrigin; use sp_core::{offchain::OpaqueMultiaddr, OpaquePeerId}; use sp_runtime::{ @@ -46,7 +46,9 @@ pub fn create_heartbeat( for _ in 0..k { keys.push(T::AuthorityId::generate_pair(None)); } - Keys::::put(keys.clone()); + let bounded_keys = WeakBoundedVec::<_, T::MaxKeys>::try_from(keys.clone()) + .map_err(|()| "More than the maximum number of keys provided")?; + Keys::::put(bounded_keys); let network_state = OpaqueNetworkState { peer_id: OpaquePeerId::default(), @@ -91,11 +93,13 @@ benchmarks! { let e in 1 .. MAX_EXTERNAL_ADDRESSES; let (input_heartbeat, signature) = create_heartbeat::(k, e)?; let call = Call::heartbeat { heartbeat: input_heartbeat, signature }; + let call_enc = call.encode(); }: { - ImOnline::::validate_unsigned(TransactionSource::InBlock, &call) - .map_err(<&str>::from)?; - call.dispatch_bypass_filter(RawOrigin::None.into())?; + ImOnline::::validate_unsigned(TransactionSource::InBlock, &call).map_err(<&str>::from)?; + as Decode>::decode(&mut &*call_enc) + .expect("call is encoded above, encoding must be correct") + .dispatch_bypass_filter(RawOrigin::None.into())?; } -} -impl_benchmark_test_suite!(ImOnline, crate::mock::new_test_ext(), crate::mock::Runtime); + impl_benchmark_test_suite!(ImOnline, crate::mock::new_test_ext(), crate::mock::Runtime); +} diff --git a/frame/im-online/src/lib.rs b/frame/im-online/src/lib.rs index ab4f7001574e5..f23e610a4874d 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,12 +17,12 @@ //! # I'm online Pallet //! -//! If the local node is a validator (i.e. contains an authority key), this module +//! If the local node is a validator (i.e. contains an authority key), this pallet //! gossips a heartbeat transaction with each new session. The heartbeat functions //! as a simple mechanism to signal that the node is online in the current era. //! //! Received heartbeats are tracked for one era and reset with each new era. The -//! module exposes two public functions to query if a heartbeat has been received +//! pallet exposes two public functions to query if a heartbeat has been received //! in the current era or session. //! //! The heartbeat is a signed transaction, which was signed using the session key @@ -43,16 +43,24 @@ //! ## Usage //! //! ``` -//! use frame_support::{decl_module, dispatch}; -//! use frame_system::ensure_signed; //! use pallet_im_online::{self as im_online}; //! -//! pub trait Config: im_online::Config {} +//! #[frame_support::pallet] +//! pub mod pallet { +//! use super::*; +//! use frame_support::pallet_prelude::*; +//! use frame_system::pallet_prelude::*; //! -//! decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { -//! #[weight = 0] -//! pub fn is_online(origin, authority_index: u32) -> dispatch::DispatchResult { +//! #[pallet::pallet] +//! pub struct Pallet(_); +//! +//! #[pallet::config] +//! pub trait Config: frame_system::Config + im_online::Config {} +//! +//! #[pallet::call] +//! impl Pallet { +//! #[pallet::weight(0)] +//! pub fn is_online(origin: OriginFor, authority_index: u32) -> DispatchResult { //! let _sender = ensure_signed(origin)?; //! let _is_online = >::is_online(authority_index); //! Ok(()) @@ -64,7 +72,7 @@ //! //! ## Dependencies //! -//! This module depends on the [Session module](../pallet_session/index.html). +//! This pallet depends on the [Session pallet](../pallet_session/index.html). // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -74,9 +82,13 @@ mod mock; mod tests; pub mod weights; -use codec::{Decode, Encode}; -use frame_support::traits::{ - EstimateNextSessionRotation, OneSessionHandler, ValidatorSet, ValidatorSetWithIdentification, +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + traits::{ + EstimateNextSessionRotation, Get, OneSessionHandler, ValidatorSet, + ValidatorSetWithIdentification, WrapperOpaque, + }, + BoundedSlice, WeakBoundedVec, }; use frame_system::offchain::{SendTransactionTypes, SubmitTransaction}; pub use pallet::*; @@ -92,7 +104,7 @@ use sp_staking::{ offence::{Kind, Offence, ReportOffence}, SessionIndex, }; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; pub use weights::WeightInfo; pub mod sr25519 { @@ -220,6 +232,64 @@ where pub validators_len: u32, } +/// A type that is the same as [`OpaqueNetworkState`] but with [`Vec`] replaced with +/// [`WeakBoundedVec`] where Limit is the respective size limit +/// `PeerIdEncodingLimit` represents the size limit of the encoding of `PeerId` +/// `MultiAddrEncodingLimit` represents the size limit of the encoding of `MultiAddr` +/// `AddressesLimit` represents the size limit of the vector of peers connected +#[derive(Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(PeerIdEncodingLimit, MultiAddrEncodingLimit, AddressesLimit))] +pub struct BoundedOpaqueNetworkState +where + PeerIdEncodingLimit: Get, + MultiAddrEncodingLimit: Get, + AddressesLimit: Get, +{ + /// PeerId of the local node in SCALE encoded. + pub peer_id: WeakBoundedVec, + /// List of addresses the node knows it can be reached as. + pub external_addresses: + WeakBoundedVec, AddressesLimit>, +} + +impl, MultiAddrEncodingLimit: Get, AddressesLimit: Get> + BoundedOpaqueNetworkState +{ + fn force_from(ons: &OpaqueNetworkState) -> Self { + let peer_id = WeakBoundedVec::<_, PeerIdEncodingLimit>::force_from( + ons.peer_id.0.clone(), + Some( + "Warning: The size of the encoding of PeerId \ + is bigger than expected. A runtime configuration \ + adjustment may be needed.", + ), + ); + + let external_addresses = WeakBoundedVec::<_, AddressesLimit>::force_from( + ons.external_addresses + .iter() + .map(|x| { + WeakBoundedVec::<_, MultiAddrEncodingLimit>::force_from( + x.0.clone(), + Some( + "Warning: The size of the encoding of MultiAddr \ + is bigger than expected. A runtime configuration \ + adjustment may be needed.", + ), + ) + }) + .collect(), + Some( + "Warning: The network has more peers than expected \ + A runtime configuration adjustment may be needed.", + ), + ); + + Self { peer_id, external_addresses } + } +} + /// A type for representing the validator id in a session. pub type ValidatorId = <::ValidatorSet as ValidatorSet< ::AccountId, @@ -239,15 +309,8 @@ type OffchainResult = Result::B #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, traits::Get, Parameter}; - use frame_system::{ensure_none, pallet_prelude::*}; - use sp_runtime::{ - traits::{MaybeSerializeDeserialize, Member}, - transaction_validity::{ - InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, - ValidTransaction, - }, - }; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -259,12 +322,22 @@ pub mod pallet { type AuthorityId: Member + Parameter + RuntimeAppPublic - + Default + Ord - + MaybeSerializeDeserialize; + + MaybeSerializeDeserialize + + MaxEncodedLen; + + /// The maximum number of keys that can be added. + type MaxKeys: Get; + + /// The maximum number of peers to be stored in `ReceivedHeartbeats` + type MaxPeerInHeartbeats: Get; + + /// The maximum size of the encoding of `PeerId` and `MultiAddr` that are coming + /// from the hearbeat + type MaxPeerDataEncodingSize: Get; /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// A type for retrieving the validators supposed to be online in a session. type ValidatorSet: ValidatorSetWithIdentification; @@ -299,12 +372,12 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// A new heartbeat was received from `AuthorityId` \[authority_id\] - HeartbeatReceived(T::AuthorityId), + /// A new heartbeat was received from `AuthorityId`. + HeartbeatReceived { authority_id: T::AuthorityId }, /// At the end of the session, no offence was committed. AllGood, - /// At the end of the session, at least one validator was found to be \[offline\]. - SomeOffline(Vec>), + /// At the end of the session, at least one validator was found to be offline. + SomeOffline { offline: Vec> }, } #[pallet::error] @@ -333,14 +406,27 @@ pub mod pallet { /// The current set of keys that may issue a heartbeat. #[pallet::storage] #[pallet::getter(fn keys)] - pub(crate) type Keys = StorageValue<_, Vec, ValueQuery>; + pub(crate) type Keys = + StorageValue<_, WeakBoundedVec, ValueQuery>; - /// For each session index, we keep a mapping of `AuthIndex` to - /// `offchain::OpaqueNetworkState`. + /// For each session index, we keep a mapping of `SessionIndex` and `AuthIndex` to + /// `WrapperOpaque`. #[pallet::storage] #[pallet::getter(fn received_heartbeats)] - pub(crate) type ReceivedHeartbeats = - StorageDoubleMap<_, Twox64Concat, SessionIndex, Twox64Concat, AuthIndex, Vec>; + pub(crate) type ReceivedHeartbeats = StorageDoubleMap< + _, + Twox64Concat, + SessionIndex, + Twox64Concat, + AuthIndex, + WrapperOpaque< + BoundedOpaqueNetworkState< + T::MaxPeerDataEncodingSize, + T::MaxPeerDataEncodingSize, + T::MaxPeerInHeartbeats, + >, + >, + >; /// For each session index, we keep a mapping of `ValidatorId` to the /// number of blocks authored by the given authority. @@ -388,6 +474,7 @@ pub mod pallet { /// # // NOTE: the weight includes the cost of validate_unsigned as it is part of the cost to // import block with such an extrinsic. + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::validate_unsigned_and_then_heartbeat( heartbeat.validators_len as u32, heartbeat.network_state.external_addresses.len() as u32, @@ -407,20 +494,24 @@ pub mod pallet { let keys = Keys::::get(); let public = keys.get(heartbeat.authority_index as usize); if let (false, Some(public)) = (exists, public) { - Self::deposit_event(Event::::HeartbeatReceived(public.clone())); + Self::deposit_event(Event::::HeartbeatReceived { authority_id: public.clone() }); - let network_state = heartbeat.network_state.encode(); + let network_state_bounded = BoundedOpaqueNetworkState::< + T::MaxPeerDataEncodingSize, + T::MaxPeerDataEncodingSize, + T::MaxPeerInHeartbeats, + >::force_from(&heartbeat.network_state); ReceivedHeartbeats::::insert( ¤t_session, &heartbeat.authority_index, - &network_state, + WrapperOpaque::from(network_state_bounded), ); Ok(()) } else if exists { - Err(Error::::DuplicatedHeartbeat)? + Err(Error::::DuplicatedHeartbeat.into()) } else { - Err(Error::::InvalidKey)? + Err(Error::::InvalidKey.into()) } } } @@ -483,7 +574,7 @@ pub mod pallet { // check signature (this is expensive so we do it last). let signature_valid = heartbeat.using_encoded(|encoded_heartbeat| { - authority_id.verify(&encoded_heartbeat, &signature) + authority_id.verify(&encoded_heartbeat, signature) }); if !signature_valid { @@ -739,13 +830,17 @@ impl Pallet { fn initialize_keys(keys: &[T::AuthorityId]) { if !keys.is_empty() { assert!(Keys::::get().is_empty(), "Keys are already initialized!"); - Keys::::put(keys); + let bounded_keys = >::try_from(keys) + .expect("More than the maximum number of keys provided"); + Keys::::put(bounded_keys); } } #[cfg(test)] fn set_keys(keys: Vec) { - Keys::::put(&keys) + let bounded_keys = WeakBoundedVec::<_, T::MaxKeys>::try_from(keys) + .expect("More than the maximum number of keys provided"); + Keys::::put(bounded_keys); } } @@ -776,7 +871,15 @@ impl OneSessionHandler for Pallet { >::put(block_number + half_session); // Remember who the authorities are for the new session. - Keys::::put(validators.map(|x| x.1).collect::>()); + let keys = validators.map(|x| x.1).collect::>(); + let bounded_keys = WeakBoundedVec::<_, T::MaxKeys>::force_from( + keys, + Some( + "Warning: The session has more keys than expected. \ + A runtime configuration adjustment may be needed.", + ), + ); + Keys::::put(bounded_keys); } fn on_before_session_ending() { @@ -798,13 +901,15 @@ impl OneSessionHandler for Pallet { // Remove all received heartbeats and number of authored blocks from the // current session, they have already been processed and won't be needed // anymore. + #[allow(deprecated)] ReceivedHeartbeats::::remove_prefix(&T::ValidatorSet::session_index(), None); + #[allow(deprecated)] AuthoredBlocks::::remove_prefix(&T::ValidatorSet::session_index(), None); if offenders.is_empty() { Self::deposit_event(Event::::AllGood); } else { - Self::deposit_event(Event::::SomeOffline(offenders.clone())); + Self::deposit_event(Event::::SomeOffline { offline: offenders.clone() }); let validator_set_count = keys.len() as u32; let offence = UnresponsivenessOffence { session_index, validator_set_count, offenders }; @@ -814,7 +919,7 @@ impl OneSessionHandler for Pallet { } } - fn on_disabled(_i: usize) { + fn on_disabled(_i: u32) { // ignore } } @@ -854,12 +959,12 @@ impl Offence for UnresponsivenessOffence { self.session_index } - fn slash_fraction(offenders: u32, validator_set_count: u32) -> Perbill { + 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% // when 13/30 are offline (around 5% when 1/3 are offline). - if let Some(threshold) = offenders.checked_sub(validator_set_count / 10 + 1) { - let x = Perbill::from_rational(3 * threshold, validator_set_count); + if let Some(threshold) = offenders.checked_sub(self.validator_set_count / 10 + 1) { + let x = Perbill::from_rational(3 * threshold, self.validator_set_count); x.saturating_mul(Perbill::from_percent(7)) } else { Perbill::default() diff --git a/frame/im-online/src/mock.rs b/frame/im-online/src/mock.rs index e4031b04271b9..5782e1a615b8e 100644 --- a/frame/im-online/src/mock.rs +++ b/frame/im-online/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,15 +19,17 @@ #![cfg(test)] -use std::cell::RefCell; - -use frame_support::{parameter_types, weights::Weight}; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, + weights::Weight, +}; use pallet_session::historical as pallet_session_historical; use sp_core::H256; use sp_runtime::{ testing::{Header, TestXt, UintAuthorityId}, traits::{BlakeTwo256, ConvertInto, IdentityLookup}, - Perbill, Permill, + Permill, }; use sp_staking::{ offence::{OffenceError, ReportOffence}, @@ -53,18 +55,18 @@ frame_support::construct_runtime!( } ); -thread_local! { - pub static VALIDATORS: RefCell>> = RefCell::new(Some(vec![ +parameter_types! { + pub static Validators: Option> = Some(vec![ 1, 2, 3, - ])); + ]); } pub struct TestSessionManager; impl pallet_session::SessionManager for TestSessionManager { fn new_session(_new_index: SessionIndex) -> Option> { - VALIDATORS.with(|l| l.borrow_mut().take()) + Validators::mutate(|l| l.take()) } fn end_session(_: SessionIndex) {} fn start_session(_: SessionIndex) {} @@ -72,10 +74,8 @@ impl pallet_session::SessionManager for TestSessionManager { impl pallet_session::historical::SessionManager for TestSessionManager { fn new_session(_new_index: SessionIndex) -> Option> { - VALIDATORS.with(|l| { - l.borrow_mut() - .take() - .map(|validators| validators.iter().map(|v| (*v, *v)).collect()) + Validators::mutate(|l| { + l.take().map(|validators| validators.iter().map(|v| (*v, *v)).collect()) }) } fn end_session(_: SessionIndex) {} @@ -83,19 +83,19 @@ impl pallet_session::historical::SessionManager for TestSessionManager } /// An extrinsic type used for tests. -pub type Extrinsic = TestXt; +pub type Extrinsic = TestXt; type IdentificationTuple = (u64, u64); type Offence = crate::UnresponsivenessOffence; -thread_local! { - pub static OFFENCES: RefCell, Offence)>> = RefCell::new(vec![]); +parameter_types! { + pub static Offences: Vec<(Vec, Offence)> = vec![]; } /// A mock offence report handler. pub struct OffenceHandler; impl ReportOffence for OffenceHandler { fn report_offence(reporters: Vec, offence: Offence) -> Result<(), OffenceError> { - OFFENCES.with(|l| l.borrow_mut().push((reporters, offence))); + Offences::mutate(|l| l.push((reporters, offence))); Ok(()) } @@ -106,13 +106,20 @@ impl ReportOffence for OffenceHandler { pub fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - t.into() + let mut result: sp_io::TestExternalities = t.into(); + // Set the default keys, otherwise session will discard the validator. + result.execute_with(|| { + for i in 1..=6 { + System::inc_providers(&i); + assert_eq!(Session::set_keys(RuntimeOrigin::signed(i), (i - 1).into(), vec![]), Ok(())); + } + }); + result } parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Runtime { @@ -120,17 +127,17 @@ impl frame_system::Config for Runtime { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -139,6 +146,7 @@ impl frame_system::Config for Runtime { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } parameter_types! { @@ -146,10 +154,6 @@ parameter_types! { pub const Offset: u64 = 0; } -parameter_types! { - pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33); -} - impl pallet_session::Config for Runtime { type ShouldEndSession = pallet_session::PeriodicSessions; type SessionManager = @@ -158,8 +162,7 @@ impl pallet_session::Config for Runtime { type ValidatorId = u64; type ValidatorIdOf = ConvertInto; type Keys = UintAuthorityId; - type Event = Event; - type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type RuntimeEvent = RuntimeEvent; type NextSessionRotation = pallet_session::PeriodicSessions; type WeightInfo = (); } @@ -169,23 +172,19 @@ impl pallet_session::historical::Config for Runtime { type FullIdentificationOf = ConvertInto; } -parameter_types! { - pub const UncleGenerations: u32 = 5; -} - impl pallet_authorship::Config for Runtime { type FindAuthor = (); - type UncleGenerations = UncleGenerations; + type UncleGenerations = ConstU64<5>; type FilterUncle = (); type EventHandler = ImOnline; } -thread_local! { - pub static MOCK_CURRENT_SESSION_PROGRESS: RefCell>> = RefCell::new(None); +parameter_types! { + pub static MockCurrentSessionProgress: Option> = None; } -thread_local! { - pub static MOCK_AVERAGE_SESSION_LENGTH: RefCell> = RefCell::new(None); +parameter_types! { + pub static MockAverageSessionLength: Option = None; } pub struct TestNextSessionRotation; @@ -193,7 +192,7 @@ pub struct TestNextSessionRotation; impl frame_support::traits::EstimateNextSessionRotation for TestNextSessionRotation { fn average_session_length() -> u64 { // take the mock result if any and return it - let mock = MOCK_AVERAGE_SESSION_LENGTH.with(|p| p.borrow_mut().take()); + let mock = MockAverageSessionLength::mutate(|p| p.take()); mock.unwrap_or(pallet_session::PeriodicSessions::::average_session_length()) } @@ -205,7 +204,7 @@ impl frame_support::traits::EstimateNextSessionRotation for TestNextSession ); // take the mock result if any and return it - let mock = MOCK_CURRENT_SESSION_PROGRESS.with(|p| p.borrow_mut().take()); + let mock = MockCurrentSessionProgress::mutate(|p| p.take()); (mock.unwrap_or(estimate), weight) } @@ -215,25 +214,24 @@ impl frame_support::traits::EstimateNextSessionRotation for TestNextSession } } -parameter_types! { - pub const UnsignedPriority: u64 = 1 << 20; -} - impl Config for Runtime { type AuthorityId = UintAuthorityId; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ValidatorSet = Historical; type NextSessionRotation = TestNextSessionRotation; type ReportUnresponsiveness = OffenceHandler; - type UnsignedPriority = UnsignedPriority; + type UnsignedPriority = ConstU64<{ 1 << 20 }>; type WeightInfo = (); + type MaxKeys = ConstU32<10_000>; + type MaxPeerInHeartbeats = ConstU32<10_000>; + type MaxPeerDataEncodingSize = ConstU32<1_000>; } impl frame_system::offchain::SendTransactionTypes for Runtime where - Call: From, + RuntimeCall: From, { - type OverarchingCall = Call; + type OverarchingCall = RuntimeCall; type Extrinsic = Extrinsic; } diff --git a/frame/im-online/src/tests.rs b/frame/im-online/src/tests.rs index bb2c4c7cae548..366119278d836 100644 --- a/frame/im-online/src/tests.rs +++ b/frame/im-online/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,22 +36,24 @@ use sp_runtime::{ #[test] fn test_unresponsiveness_slash_fraction() { + let dummy_offence = + UnresponsivenessOffence { session_index: 0, validator_set_count: 50, offenders: vec![()] }; // A single case of unresponsiveness is not slashed. - assert_eq!(UnresponsivenessOffence::<()>::slash_fraction(1, 50), Perbill::zero()); + assert_eq!(dummy_offence.slash_fraction(1), Perbill::zero()); assert_eq!( - UnresponsivenessOffence::<()>::slash_fraction(5, 50), + dummy_offence.slash_fraction(5), Perbill::zero(), // 0% ); assert_eq!( - UnresponsivenessOffence::<()>::slash_fraction(7, 50), + dummy_offence.slash_fraction(7), Perbill::from_parts(4200000), // 0.42% ); // One third offline should be punished around 5%. assert_eq!( - UnresponsivenessOffence::<()>::slash_fraction(17, 50), + dummy_offence.slash_fraction(17), Perbill::from_parts(46200000), // 4.62% ); } @@ -66,7 +68,7 @@ fn should_report_offline_validators() { advance_session(); // enact the change and buffer another one let validators = vec![1, 2, 3, 4, 5, 6]; - VALIDATORS.with(|l| *l.borrow_mut() = Some(validators.clone())); + Validators::mutate(|l| *l = Some(validators.clone())); advance_session(); // when @@ -74,7 +76,7 @@ fn should_report_offline_validators() { advance_session(); // then - let offences = OFFENCES.with(|l| l.replace(vec![])); + let offences = Offences::take(); assert_eq!( offences, vec![( @@ -94,7 +96,7 @@ fn should_report_offline_validators() { advance_session(); // then - let offences = OFFENCES.with(|l| l.replace(vec![])); + let offences = Offences::take(); assert_eq!( offences, vec![( @@ -139,7 +141,7 @@ fn heartbeat( "invalid validators len", e @ _ => <&'static str>::from(e), })?; - ImOnline::heartbeat(Origin::none(), heartbeat, signature) + ImOnline::heartbeat(RuntimeOrigin::none(), heartbeat, signature) } #[test] @@ -147,7 +149,7 @@ fn should_mark_online_validator_when_heartbeat_is_received() { new_test_ext().execute_with(|| { advance_session(); // given - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); + Validators::mutate(|l| *l = Some(vec![1, 2, 3, 4, 5, 6])); assert_eq!(Session::validators(), Vec::::new()); // enact the change and buffer another one advance_session(); @@ -182,7 +184,7 @@ fn late_heartbeat_and_invalid_keys_len_should_fail() { new_test_ext().execute_with(|| { advance_session(); // given - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); + Validators::mutate(|l| *l = Some(vec![1, 2, 3, 4, 5, 6])); assert_eq!(Session::validators(), Vec::::new()); // enact the change and buffer another one advance_session(); @@ -224,7 +226,7 @@ fn should_generate_heartbeats() { // buffer new validators Session::rotate_session(); // enact the change and buffer another one - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); + Validators::mutate(|l| *l = Some(vec![1, 2, 3, 4, 5, 6])); Session::rotate_session(); // when @@ -238,7 +240,8 @@ fn should_generate_heartbeats() { // check stuff about the transaction. let ex: Extrinsic = Decode::decode(&mut &*transaction).unwrap(); let heartbeat = match ex.call { - crate::mock::Call::ImOnline(crate::Call::heartbeat { heartbeat, .. }) => heartbeat, + crate::mock::RuntimeCall::ImOnline(crate::Call::heartbeat { heartbeat, .. }) => + heartbeat, e => panic!("Unexpected call: {:?}", e), }; @@ -260,7 +263,7 @@ fn should_cleanup_received_heartbeats_on_session_end() { new_test_ext().execute_with(|| { advance_session(); - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3])); + Validators::mutate(|l| *l = Some(vec![1, 2, 3])); assert_eq!(Session::validators(), Vec::::new()); // enact the change and buffer another one @@ -291,7 +294,7 @@ fn should_mark_online_validator_when_block_is_authored() { new_test_ext().execute_with(|| { advance_session(); // given - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); + Validators::mutate(|l| *l = Some(vec![1, 2, 3, 4, 5, 6])); assert_eq!(Session::validators(), Vec::::new()); // enact the change and buffer another one advance_session(); @@ -328,7 +331,7 @@ fn should_not_send_a_report_if_already_online() { ext.execute_with(|| { advance_session(); // given - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); + Validators::mutate(|l| *l = Some(vec![1, 2, 3, 4, 5, 6])); assert_eq!(Session::validators(), Vec::::new()); // enact the change and buffer another one advance_session(); @@ -341,7 +344,7 @@ fn should_not_send_a_report_if_already_online() { UintAuthorityId::set_all_keys(vec![1, 2, 3]); // we expect error, since the authority is already online. let mut res = ImOnline::send_heartbeats(4).unwrap(); - assert_eq!(res.next().unwrap().unwrap(), ()); + res.next().unwrap().unwrap(); assert_eq!(res.next().unwrap().unwrap_err(), OffchainErr::AlreadyOnline(1)); assert_eq!(res.next().unwrap().unwrap_err(), OffchainErr::AlreadyOnline(2)); assert_eq!(res.next(), None); @@ -353,7 +356,8 @@ fn should_not_send_a_report_if_already_online() { // check stuff about the transaction. let ex: Extrinsic = Decode::decode(&mut &*transaction).unwrap(); let heartbeat = match ex.call { - crate::mock::Call::ImOnline(crate::Call::heartbeat { heartbeat, .. }) => heartbeat, + crate::mock::RuntimeCall::ImOnline(crate::Call::heartbeat { heartbeat, .. }) => + heartbeat, e => panic!("Unexpected call: {:?}", e), }; @@ -391,12 +395,12 @@ fn should_handle_missing_progress_estimates() { Session::rotate_session(); // enact the change and buffer another one - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![0, 1, 2])); + Validators::mutate(|l| *l = Some(vec![0, 1, 2])); Session::rotate_session(); // we will return `None` on the next call to `estimate_current_session_progress` // and the offchain worker should fallback to checking `HeartbeatAfter` - MOCK_CURRENT_SESSION_PROGRESS.with(|p| *p.borrow_mut() = Some(None)); + MockCurrentSessionProgress::mutate(|p| *p = Some(None)); ImOnline::offchain_worker(block); assert_eq!(state.read().transactions.len(), 3); @@ -425,26 +429,25 @@ fn should_handle_non_linear_session_progress() { // mock the session length as being 10 blocks long, // enact the change and buffer another one - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![0, 1, 2])); + Validators::mutate(|l| *l = Some(vec![0, 1, 2])); // mock the session length has being 10 which should make us assume the fallback for half // session will be reached by block 5. - MOCK_AVERAGE_SESSION_LENGTH.with(|p| *p.borrow_mut() = Some(10)); + MockAverageSessionLength::mutate(|p| *p = Some(10)); Session::rotate_session(); // if we don't have valid results for the current session progres then // we'll fallback to `HeartbeatAfter` and only heartbeat on block 5. - MOCK_CURRENT_SESSION_PROGRESS.with(|p| *p.borrow_mut() = Some(None)); + MockCurrentSessionProgress::mutate(|p| *p = Some(None)); assert_eq!(ImOnline::send_heartbeats(2).err(), Some(OffchainErr::TooEarly)); - MOCK_CURRENT_SESSION_PROGRESS.with(|p| *p.borrow_mut() = Some(None)); + MockCurrentSessionProgress::mutate(|p| *p = Some(None)); assert!(ImOnline::send_heartbeats(5).ok().is_some()); // if we have a valid current session progress then we'll heartbeat as soon // as we're past 80% of the session regardless of the block number - MOCK_CURRENT_SESSION_PROGRESS - .with(|p| *p.borrow_mut() = Some(Some(Permill::from_percent(81)))); + MockCurrentSessionProgress::mutate(|p| *p = Some(Some(Permill::from_percent(81)))); assert!(ImOnline::send_heartbeats(2).ok().is_some()); }); @@ -462,8 +465,7 @@ fn test_does_not_heartbeat_early_in_the_session() { ext.execute_with(|| { // mock current session progress as being 5%. we only randomly start // heartbeating after 10% of the session has elapsed. - MOCK_CURRENT_SESSION_PROGRESS - .with(|p| *p.borrow_mut() = Some(Some(Permill::from_float(0.05)))); + MockCurrentSessionProgress::mutate(|p| *p = Some(Some(Permill::from_float(0.05)))); assert_eq!(ImOnline::send_heartbeats(2).err(), Some(OffchainErr::TooEarly)); }); } @@ -481,9 +483,8 @@ fn test_probability_of_heartbeating_increases_with_session_progress() { let set_test = |progress, random: f64| { // the average session length is 100 blocks, therefore the residual // probability of sending a heartbeat is 1% - MOCK_AVERAGE_SESSION_LENGTH.with(|p| *p.borrow_mut() = Some(100)); - MOCK_CURRENT_SESSION_PROGRESS - .with(|p| *p.borrow_mut() = Some(Some(Permill::from_float(progress)))); + MockAverageSessionLength::mutate(|p| *p = Some(100)); + MockCurrentSessionProgress::mutate(|p| *p = Some(Some(Permill::from_float(progress)))); let mut seed = [0u8; 32]; let encoded = ((random * Permill::ACCURACY as f64) as u32).encode(); diff --git a/frame/im-online/src/weights.rs b/frame/im-online/src/weights.rs index 1eadd63cc9d6c..f81db997c303d 100644 --- a/frame/im-online/src/weights.rs +++ b/frame/im-online/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_im_online //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/im-online/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -56,14 +58,17 @@ impl WeightInfo for SubstrateWeight { // Storage: ImOnline ReceivedHeartbeats (r:1 w:1) // Storage: ImOnline AuthoredBlocks (r:1 w:0) // Storage: ImOnline Keys (r:1 w:0) + /// The range of component `k` is `[1, 1000]`. + /// The range of component `e` is `[1, 100]`. fn validate_unsigned_and_then_heartbeat(k: u32, e: u32, ) -> Weight { - (93_400_000 as Weight) - // Standard Error: 0 - .saturating_add((144_000 as Weight).saturating_mul(k as Weight)) - // Standard Error: 0 - .saturating_add((335_000 as Weight).saturating_mul(e as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 101_380 nanoseconds. + Weight::from_ref_time(82_735_670 as u64) + // Standard Error: 121 + .saturating_add(Weight::from_ref_time(21_279 as u64).saturating_mul(k as u64)) + // Standard Error: 1_222 + .saturating_add(Weight::from_ref_time(296_343 as u64).saturating_mul(e as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } } @@ -74,13 +79,16 @@ impl WeightInfo for () { // Storage: ImOnline ReceivedHeartbeats (r:1 w:1) // Storage: ImOnline AuthoredBlocks (r:1 w:0) // Storage: ImOnline Keys (r:1 w:0) + /// The range of component `k` is `[1, 1000]`. + /// The range of component `e` is `[1, 100]`. fn validate_unsigned_and_then_heartbeat(k: u32, e: u32, ) -> Weight { - (93_400_000 as Weight) - // Standard Error: 0 - .saturating_add((144_000 as Weight).saturating_mul(k as Weight)) - // Standard Error: 0 - .saturating_add((335_000 as Weight).saturating_mul(e as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 101_380 nanoseconds. + Weight::from_ref_time(82_735_670 as u64) + // Standard Error: 121 + .saturating_add(Weight::from_ref_time(21_279 as u64).saturating_mul(k as u64)) + // Standard Error: 1_222 + .saturating_add(Weight::from_ref_time(296_343 as u64).saturating_mul(e as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } } diff --git a/frame/indices/Cargo.toml b/frame/indices/Cargo.toml index 17d04c43fa5d9..b3afa397c45f5 100644 --- a/frame/indices/Cargo.toml +++ b/frame/indices/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-indices" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME indices management pallet" readme = "README.md" @@ -13,17 +13,16 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-keyring = { version = "4.0.0-dev", optional = true, path = "../../primitives/keyring" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-keyring = { version = "7.0.0", optional = true, path = "../../primitives/keyring" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -31,18 +30,19 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] default = ["std"] std = [ - "sp-keyring", + "frame-benchmarking?/std", "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", "sp-core/std", - "sp-std/std", "sp-io/std", - "frame-support/std", + "sp-keyring", "sp-runtime/std", - "frame-system/std", + "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/indices/src/benchmarking.rs b/frame/indices/src/benchmarking.rs index ba0152008c41e..f462f22284d40 100644 --- a/frame/indices/src/benchmarking.rs +++ b/frame/indices/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; @@ -44,10 +44,11 @@ benchmarks! { let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let recipient: T::AccountId = account("recipient", 0, SEED); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); T::Currency::make_free_balance_be(&recipient, BalanceOf::::max_value()); // Claim the index Indices::::claim(RawOrigin::Signed(caller.clone()).into(), account_index)?; - }: _(RawOrigin::Signed(caller.clone()), recipient.clone(), account_index) + }: _(RawOrigin::Signed(caller.clone()), recipient_lookup, account_index) verify { assert_eq!(Accounts::::get(account_index).unwrap().0, recipient); } @@ -70,10 +71,11 @@ benchmarks! { let original: T::AccountId = account("original", 0, SEED); T::Currency::make_free_balance_be(&original, BalanceOf::::max_value()); let recipient: T::AccountId = account("recipient", 0, SEED); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); T::Currency::make_free_balance_be(&recipient, BalanceOf::::max_value()); // Claim the index Indices::::claim(RawOrigin::Signed(original).into(), account_index)?; - }: _(RawOrigin::Root, recipient.clone(), account_index, false) + }: _(RawOrigin::Root, recipient_lookup, account_index, false) verify { assert_eq!(Accounts::::get(account_index).unwrap().0, recipient); } @@ -91,6 +93,6 @@ benchmarks! { } // TODO in another PR: lookup and unlookup trait weights (not critical) -} -impl_benchmark_test_suite!(Indices, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!(Indices, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/indices/src/lib.rs b/frame/indices/src/lib.rs index 0901a89d41ad6..95d3cf4b2eed1 100644 --- a/frame/indices/src/lib.rs +++ b/frame/indices/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,6 +36,7 @@ pub use weights::WeightInfo; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; pub use pallet::*; @@ -56,7 +57,8 @@ pub mod pallet { + Codec + Default + AtLeast32Bit - + Copy; + + Copy + + MaxEncodedLen; /// The currency trait. type Currency: ReservableCurrency; @@ -66,7 +68,7 @@ pub mod pallet { type Deposit: Get>; /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -96,6 +98,7 @@ pub mod pallet { /// ------------------- /// - DB Weight: 1 Read/Write (Accounts) /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::claim())] pub fn claim(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { let who = ensure_signed(origin)?; @@ -105,7 +108,7 @@ pub mod pallet { *maybe_value = Some((who.clone(), T::Deposit::get(), false)); T::Currency::reserve(&who, T::Deposit::get()) })?; - Self::deposit_event(Event::IndexAssigned(who, index)); + Self::deposit_event(Event::IndexAssigned { who, index }); Ok(()) } @@ -129,24 +132,26 @@ pub mod pallet { /// - Reads: Indices Accounts, System Account (recipient) /// - Writes: Indices Accounts, System Account (recipient) /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, - new: T::AccountId, + new: AccountIdLookupOf, index: T::AccountIndex, ) -> DispatchResult { let who = ensure_signed(origin)?; + let new = T::Lookup::lookup(new)?; ensure!(who != new, Error::::NotTransfer); Accounts::::try_mutate(index, |maybe_value| -> DispatchResult { let (account, amount, perm) = maybe_value.take().ok_or(Error::::NotAssigned)?; ensure!(!perm, Error::::Permanent); - ensure!(&account == &who, Error::::NotOwner); + ensure!(account == who, Error::::NotOwner); let lost = T::Currency::repatriate_reserved(&who, &new, amount, Reserved)?; *maybe_value = Some((new.clone(), amount.saturating_sub(lost), false)); Ok(()) })?; - Self::deposit_event(Event::IndexAssigned(new, index)); + Self::deposit_event(Event::IndexAssigned { who: new, index }); Ok(()) } @@ -168,6 +173,7 @@ pub mod pallet { /// ------------------- /// - DB Weight: 1 Read/Write (Accounts) /// # + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::free())] pub fn free(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { let who = ensure_signed(origin)?; @@ -175,11 +181,11 @@ pub mod pallet { Accounts::::try_mutate(index, |maybe_value| -> DispatchResult { let (account, amount, perm) = maybe_value.take().ok_or(Error::::NotAssigned)?; ensure!(!perm, Error::::Permanent); - ensure!(&account == &who, Error::::NotOwner); + ensure!(account == who, Error::::NotOwner); T::Currency::unreserve(&who, amount); Ok(()) })?; - Self::deposit_event(Event::IndexFreed(index)); + Self::deposit_event(Event::IndexFreed { index }); Ok(()) } @@ -204,14 +210,16 @@ pub mod pallet { /// - Reads: Indices Accounts, System Account (original owner) /// - Writes: Indices Accounts, System Account (original owner) /// # + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::force_transfer())] pub fn force_transfer( origin: OriginFor, - new: T::AccountId, + new: AccountIdLookupOf, index: T::AccountIndex, freeze: bool, ) -> DispatchResult { ensure_root(origin)?; + let new = T::Lookup::lookup(new)?; Accounts::::mutate(index, |maybe_value| { if let Some((account, amount, _)) = maybe_value.take() { @@ -219,7 +227,7 @@ pub mod pallet { } *maybe_value = Some((new.clone(), Zero::zero(), freeze)); }); - Self::deposit_event(Event::IndexAssigned(new, index)); + Self::deposit_event(Event::IndexAssigned { who: new, index }); Ok(()) } @@ -241,6 +249,7 @@ pub mod pallet { /// ------------------- /// - DB Weight: 1 Read/Write (Accounts) /// # + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { let who = ensure_signed(origin)?; @@ -248,12 +257,12 @@ pub mod pallet { Accounts::::try_mutate(index, |maybe_value| -> DispatchResult { let (account, amount, perm) = maybe_value.take().ok_or(Error::::NotAssigned)?; ensure!(!perm, Error::::Permanent); - ensure!(&account == &who, Error::::NotOwner); + ensure!(account == who, Error::::NotOwner); T::Currency::slash_reserved(&who, amount); *maybe_value = Some((account, Zero::zero(), true)); Ok(()) })?; - Self::deposit_event(Event::IndexFrozen(index, who)); + Self::deposit_event(Event::IndexFrozen { index, who }); Ok(()) } } @@ -261,18 +270,14 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// A account index was assigned. \[index, who\] - IndexAssigned(T::AccountId, T::AccountIndex), - /// A account index has been freed up (unassigned). \[index\] - IndexFreed(T::AccountIndex), - /// A account index has been frozen to its current account ID. \[index, who\] - IndexFrozen(T::AccountIndex, T::AccountId), + /// A account index was assigned. + IndexAssigned { who: T::AccountId, index: T::AccountIndex }, + /// A account index has been freed up (unassigned). + IndexFreed { index: T::AccountIndex }, + /// A account index has been frozen to its current account ID. + IndexFrozen { index: T::AccountIndex, who: T::AccountId }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// The index was not already assigned. diff --git a/frame/indices/src/mock.rs b/frame/indices/src/mock.rs index f4c87016141b5..fd2e9fff16885 100644 --- a/frame/indices/src/mock.rs +++ b/frame/indices/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,10 @@ #![cfg(test)] use crate::{self as pallet_indices, Config}; -use frame_support::parameter_types; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; use sp_core::H256; use sp_runtime::testing::Header; @@ -40,9 +43,8 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { @@ -50,8 +52,8 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -59,8 +61,8 @@ impl frame_system::Config for Test { type AccountId = u64; type Lookup = Indices; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -69,10 +71,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 1; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { @@ -81,21 +80,17 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; type Balance = u64; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } -parameter_types! { - pub const Deposit: u64 = 1; -} - impl Config for Test { type AccountIndex = u64; type Currency = Balances; - type Deposit = Deposit; - type Event = Event; + type Deposit = ConstU64<1>; + type RuntimeEvent = RuntimeEvent; type WeightInfo = (); } diff --git a/frame/indices/src/tests.rs b/frame/indices/src/tests.rs index 37df20e9b9288..bed6cfffaa825 100644 --- a/frame/indices/src/tests.rs +++ b/frame/indices/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ use super::{mock::*, *}; use frame_support::{assert_noop, assert_ok}; use pallet_balances::Error as BalancesError; +use sp_runtime::MultiAddress::Id; #[test] fn claiming_should_work() { @@ -60,7 +61,7 @@ fn freezing_should_work() { assert_noop!(Indices::freeze(Some(1).into(), 0), Error::::Permanent); assert_noop!(Indices::free(Some(1).into(), 0), Error::::Permanent); - assert_noop!(Indices::transfer(Some(1).into(), 2, 0), Error::::Permanent); + assert_noop!(Indices::transfer(Some(1).into(), Id(2), 0), Error::::Permanent); }); } @@ -90,9 +91,9 @@ fn reclaim_index_on_accounts_should_work() { fn transfer_index_on_accounts_should_work() { new_test_ext().execute_with(|| { assert_ok!(Indices::claim(Some(1).into(), 0)); - assert_noop!(Indices::transfer(Some(1).into(), 2, 1), Error::::NotAssigned); - assert_noop!(Indices::transfer(Some(2).into(), 3, 0), Error::::NotOwner); - assert_ok!(Indices::transfer(Some(1).into(), 3, 0)); + assert_noop!(Indices::transfer(Some(1).into(), Id(2), 1), Error::::NotAssigned); + assert_noop!(Indices::transfer(Some(2).into(), Id(3), 0), Error::::NotOwner); + assert_ok!(Indices::transfer(Some(1).into(), Id(3), 0)); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Balances::reserved_balance(3), 1); assert_eq!(Indices::lookup_index(0), Some(3)); @@ -103,7 +104,7 @@ fn transfer_index_on_accounts_should_work() { fn force_transfer_index_on_preowned_should_work() { new_test_ext().execute_with(|| { assert_ok!(Indices::claim(Some(1).into(), 0)); - assert_ok!(Indices::force_transfer(Origin::root(), 3, 0, false)); + assert_ok!(Indices::force_transfer(RuntimeOrigin::root(), Id(3), 0, false)); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Balances::reserved_balance(3), 0); assert_eq!(Indices::lookup_index(0), Some(3)); @@ -113,7 +114,7 @@ fn force_transfer_index_on_preowned_should_work() { #[test] fn force_transfer_index_on_free_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Indices::force_transfer(Origin::root(), 3, 0, false)); + assert_ok!(Indices::force_transfer(RuntimeOrigin::root(), Id(3), 0, false)); assert_eq!(Balances::reserved_balance(3), 0); assert_eq!(Indices::lookup_index(0), Some(3)); }); diff --git a/frame/indices/src/weights.rs b/frame/indices/src/weights.rs index 97db589739534..7b974875cdf51 100644 --- a/frame/indices/src/weights.rs +++ b/frame/indices/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_indices //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/indices/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -57,35 +59,40 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Indices Accounts (r:1 w:1) fn claim() -> Weight { - (38_814_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 31_756 nanoseconds. + Weight::from_ref_time(32_252_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (47_274_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 38_686 nanoseconds. + Weight::from_ref_time(39_402_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Indices Accounts (r:1 w:1) fn free() -> Weight { - (39_692_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 33_752 nanoseconds. + Weight::from_ref_time(34_348_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (40_250_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 32_739 nanoseconds. + Weight::from_ref_time(33_151_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Indices Accounts (r:1 w:1) fn freeze() -> Weight { - (37_358_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 40_369 nanoseconds. + Weight::from_ref_time(40_982_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } } @@ -93,34 +100,39 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Indices Accounts (r:1 w:1) fn claim() -> Weight { - (38_814_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 31_756 nanoseconds. + Weight::from_ref_time(32_252_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (47_274_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 38_686 nanoseconds. + Weight::from_ref_time(39_402_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Indices Accounts (r:1 w:1) fn free() -> Weight { - (39_692_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 33_752 nanoseconds. + Weight::from_ref_time(34_348_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (40_250_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 32_739 nanoseconds. + Weight::from_ref_time(33_151_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Indices Accounts (r:1 w:1) fn freeze() -> Weight { - (37_358_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 40_369 nanoseconds. + Weight::from_ref_time(40_982_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } } diff --git a/frame/lottery/Cargo.toml b/frame/lottery/Cargo.toml index f14d65310cc70..9ac69d63eb983 100644 --- a/frame/lottery/Cargo.toml +++ b/frame/lottery/Cargo.toml @@ -2,47 +2,46 @@ name = "pallet-lottery" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME Participation Lottery Pallet" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] frame-support-test = { version = "3.0.0", path = "../support/test" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "codec/std", - "scale-info/std", - "sp-std/std", "frame-support/std", - "sp-runtime/std", "frame-system/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", - "frame-system/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/lottery/src/benchmarking.rs b/frame/lottery/src/benchmarking.rs index 3b7035c72deb0..fba722a07fabd 100644 --- a/frame/lottery/src/benchmarking.rs +++ b/frame/lottery/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,11 @@ use super::*; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -use frame_support::traits::{EnsureOrigin, OnInitialize, UnfilteredDispatchable}; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_support::{ + storage::bounded_vec::BoundedVec, + traits::{EnsureOrigin, OnInitialize}, +}; use frame_system::RawOrigin; use sp_runtime::traits::{Bounded, Zero}; @@ -55,12 +58,12 @@ benchmarks! { let set_code_index: CallIndex = Lottery::::call_to_index( &frame_system::Call::::set_code{ code: vec![] }.into() )?; - let already_called: (u32, Vec) = ( + let already_called: (u32, BoundedVec) = ( LotteryIndex::::get(), - vec![ + BoundedVec::::try_from(vec![ set_code_index; T::MaxCalls::get().saturating_sub(1) as usize - ], + ]).unwrap(), ); Participants::::insert(&caller, already_called); @@ -73,11 +76,9 @@ benchmarks! { set_calls { let n in 0 .. T::MaxCalls::get() as u32; let calls = vec![frame_system::Call::::remark { remark: vec![] }.into(); n as usize]; - - let call = Call::::set_calls { calls }; let origin = T::ManagerOrigin::successful_origin(); assert!(CallIndices::::get().is_empty()); - }: { call.dispatch_bypass_filter(origin)? } + }: _(origin, calls) verify { if !n.is_zero() { assert!(!CallIndices::::get().is_empty()); @@ -88,10 +89,8 @@ benchmarks! { let price = BalanceOf::::max_value(); let end = 10u32.into(); let payout = 5u32.into(); - - let call = Call::::start_lottery { price, length: end, delay: payout, repeat: true }; let origin = T::ManagerOrigin::successful_origin(); - }: { call.dispatch_bypass_filter(origin)? } + }: _(origin, price, end, payout, true) verify { assert!(crate::Lottery::::get().is_some()); } @@ -99,9 +98,8 @@ benchmarks! { stop_repeat { setup_lottery::(true)?; assert_eq!(crate::Lottery::::get().unwrap().repeat, true); - let call = Call::::stop_repeat {}; let origin = T::ManagerOrigin::successful_origin(); - }: { call.dispatch_bypass_filter(origin)? } + }: _(origin) verify { assert_eq!(crate::Lottery::::get().unwrap().repeat, false); } @@ -168,6 +166,6 @@ benchmarks! { assert_eq!(Lottery::::pot().1, 0u32.into()); assert!(!T::Currency::free_balance(&winner).is_zero()) } -} -impl_benchmark_test_suite!(Lottery, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!(Lottery, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/lottery/src/lib.rs b/frame/lottery/src/lib.rs index 260b4c2d76ae9..3255062e3fe7f 100644 --- a/frame/lottery/src/lib.rs +++ b/frame/lottery/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ //! users to make those calls on your network. An example of how this could be //! used is to set validator nominations as a valid lottery call. If the lottery //! is set to repeat every month, then users would be encouraged to re-nominate -//! validators every month. A user can ony purchase one ticket per valid call +//! validators every month. A user can only purchase one ticket per valid call //! per lottery. //! //! This pallet can be configured to use dynamically set calls or statically set @@ -58,6 +58,8 @@ use codec::{Decode, Encode}; use frame_support::{ dispatch::{DispatchResult, Dispatchable, GetDispatchInfo}, ensure, + pallet_prelude::MaxEncodedLen, + storage::bounded_vec::BoundedVec, traits::{Currency, ExistenceRequirement::KeepAlive, Get, Randomness, ReservableCurrency}, PalletId, RuntimeDebug, }; @@ -76,7 +78,9 @@ type BalanceOf = // We use this to uniquely match someone's incoming call with the calls configured for the lottery. type CallIndex = (u8, u8); -#[derive(Encode, Decode, Default, Eq, PartialEq, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + Encode, Decode, Default, Eq, PartialEq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] pub struct LotteryConfig { /// Price per entry. price: Balance, @@ -92,19 +96,19 @@ pub struct LotteryConfig { } pub trait ValidateCall { - fn validate_call(call: &::Call) -> bool; + fn validate_call(call: &::RuntimeCall) -> bool; } impl ValidateCall for () { - fn validate_call(_: &::Call) -> bool { + fn validate_call(_: &::RuntimeCall) -> bool { false } } impl ValidateCall for Pallet { - fn validate_call(call: &::Call) -> bool { + fn validate_call(call: &::RuntimeCall) -> bool { let valid_calls = CallIndices::::get(); - let call_index = match Self::call_to_index(&call) { + let call_index = match Self::call_to_index(call) { Ok(call_index) => call_index, Err(_) => return false, }; @@ -115,8 +119,8 @@ impl ValidateCall for Pallet { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, traits::EnsureOrigin, weights::Weight, Parameter}; - use frame_system::{ensure_signed, pallet_prelude::*}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -130,8 +134,8 @@ pub mod pallet { type PalletId: Get; /// A dispatchable call. - type Call: Parameter - + Dispatchable + type RuntimeCall: Parameter + + Dispatchable + GetDispatchInfo + From>; @@ -142,10 +146,10 @@ pub mod pallet { type Randomness: Randomness; /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The manager origin. - type ManagerOrigin: EnsureOrigin; + type ManagerOrigin: EnsureOrigin; /// The max number of calls available in a single lottery. #[pallet::constant] @@ -176,9 +180,9 @@ pub mod pallet { /// A new set of calls have been set! CallsUpdated, /// A winner has been chosen! - Winner(T::AccountId, BalanceOf), + Winner { winner: T::AccountId, lottery_balance: BalanceOf }, /// A ticket has been bought! - TicketBought(T::AccountId, CallIndex), + TicketBought { who: T::AccountId, call_index: CallIndex }, } #[pallet::error] @@ -209,8 +213,13 @@ pub mod pallet { /// Users who have purchased a ticket. (Lottery Index, Tickets Purchased) #[pallet::storage] - pub(crate) type Participants = - StorageMap<_, Twox64Concat, T::AccountId, (u32, Vec), ValueQuery>; + pub(crate) type Participants = StorageMap< + _, + Twox64Concat, + T::AccountId, + (u32, BoundedVec), + ValueQuery, + >; /// Total number of tickets sold. #[pallet::storage] @@ -226,7 +235,8 @@ pub mod pallet { /// The calls stored in this pallet to be used in an active lottery if configured /// by `Config::ValidateCall`. #[pallet::storage] - pub(crate) type CallIndices = StorageValue<_, Vec, ValueQuery>; + pub(crate) type CallIndices = + StorageValue<_, BoundedVec, ValueQuery>; #[pallet::hooks] impl Hooks> for Pallet { @@ -237,10 +247,8 @@ pub mod pallet { config.start.saturating_add(config.length).saturating_add(config.delay); if payout_block <= n { let (lottery_account, lottery_balance) = Self::pot(); - let ticket_count = TicketsCount::::get(); - let winning_number = Self::choose_winner(ticket_count); - let winner = Tickets::::get(winning_number).unwrap_or(lottery_account); + let winner = Self::choose_account().unwrap_or(lottery_account); // Not much we can do if this fails... let res = T::Currency::transfer( &Self::account_id(), @@ -250,7 +258,7 @@ pub mod pallet { ); debug_assert!(res.is_ok()); - Self::deposit_event(Event::::Winner(winner, lottery_balance)); + Self::deposit_event(Event::::Winner { winner, lottery_balance }); TicketsCount::::kill(); @@ -270,7 +278,7 @@ pub mod pallet { // but is not used if it is not relevant. } } - return T::DbWeight::get().reads(1) + T::DbWeight::get().reads(1) }) } } @@ -288,11 +296,15 @@ pub mod pallet { /// should listen for the `TicketBought` event. /// /// This extrinsic must be called by a signed origin. + #[pallet::call_index(0)] #[pallet::weight( T::WeightInfo::buy_ticket() .saturating_add(call.get_dispatch_info().weight) )] - pub fn buy_ticket(origin: OriginFor, call: Box<::Call>) -> DispatchResult { + pub fn buy_ticket( + origin: OriginFor, + call: Box<::RuntimeCall>, + ) -> DispatchResult { let caller = ensure_signed(origin.clone())?; call.clone().dispatch(origin).map_err(|e| e.error)?; @@ -306,8 +318,12 @@ pub mod pallet { /// provided by this pallet, which uses storage to determine the valid calls. /// /// This extrinsic must be called by the Manager origin. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::set_calls(calls.len() as u32))] - pub fn set_calls(origin: OriginFor, calls: Vec<::Call>) -> DispatchResult { + pub fn set_calls( + origin: OriginFor, + calls: Vec<::RuntimeCall>, + ) -> DispatchResult { T::ManagerOrigin::ensure_origin(origin)?; ensure!(calls.len() <= T::MaxCalls::get() as usize, Error::::TooManyCalls); if calls.is_empty() { @@ -330,6 +346,7 @@ pub mod pallet { /// * `length`: How long the lottery should run for starting at the current block. /// * `delay`: How long after the lottery end we should wait before picking a winner. /// * `repeat`: If the lottery should repeat when completed. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::start_lottery())] pub fn start_lottery( origin: OriginFor, @@ -362,6 +379,7 @@ pub mod pallet { /// The lottery will continue to run to completion. /// /// This extrinsic must be called by the `ManagerOrigin`. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::stop_repeat())] pub fn stop_repeat(origin: OriginFor) -> DispatchResult { T::ManagerOrigin::ensure_origin(origin)?; @@ -381,11 +399,11 @@ impl Pallet { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() + T::PalletId::get().into_account_truncating() } /// Return the pot account and amount of money in the pot. - // The existential deposit is not part of the pot so lottery account never gets deleted. + /// The existential deposit is not part of the pot so lottery account never gets deleted. fn pot() -> (T::AccountId, BalanceOf) { let account_id = Self::account_id(); let balance = @@ -394,27 +412,29 @@ impl Pallet { (account_id, balance) } - // Converts a vector of calls into a vector of call indices. - fn calls_to_indices(calls: &[::Call]) -> Result, DispatchError> { - let mut indices = Vec::with_capacity(calls.len()); + /// Converts a vector of calls into a vector of call indices. + fn calls_to_indices( + calls: &[::RuntimeCall], + ) -> Result, DispatchError> { + let mut indices = BoundedVec::::with_bounded_capacity(calls.len()); for c in calls.iter() { let index = Self::call_to_index(c)?; - indices.push(index) + indices.try_push(index).map_err(|_| Error::::TooManyCalls)?; } Ok(indices) } - // Convert a call to it's call index by encoding the call and taking the first two bytes. - fn call_to_index(call: &::Call) -> Result { + /// Convert a call to it's call index by encoding the call and taking the first two bytes. + fn call_to_index(call: &::RuntimeCall) -> Result { let encoded_call = call.encode(); if encoded_call.len() < 2 { - Err(Error::::EncodingFailed)? + return Err(Error::::EncodingFailed.into()) } - return Ok((encoded_call[0], encoded_call[1])) + Ok((encoded_call[0], encoded_call[1])) } - // Logic for buying a ticket. - fn do_buy_ticket(caller: &T::AccountId, call: &::Call) -> DispatchResult { + /// Logic for buying a ticket. + fn do_buy_ticket(caller: &T::AccountId, call: &::RuntimeCall) -> DispatchResult { // Check the call is valid lottery let config = Lottery::::get().ok_or(Error::::NotConfigured)?; let block_number = frame_system::Pallet::::block_number(); @@ -433,7 +453,7 @@ impl Pallet { let index = LotteryIndex::::get(); // If lottery index doesn't match, then reset participating calls and index. if *lottery_index != index { - *participating_calls = Vec::new(); + *participating_calls = Default::default(); *lottery_index = index; } else { // Check that user is not already participating under this call. @@ -442,23 +462,37 @@ impl Pallet { Error::::AlreadyParticipating ); } + participating_calls.try_push(call_index).map_err(|_| Error::::TooManyCalls)?; // Check user has enough funds and send it to the Lottery account. T::Currency::transfer(caller, &Self::account_id(), config.price, KeepAlive)?; // Create a new ticket. TicketsCount::::put(new_ticket_count); Tickets::::insert(ticket_count, caller.clone()); - participating_calls.push(call_index); Ok(()) }, )?; - Self::deposit_event(Event::::TicketBought(caller.clone(), call_index)); + Self::deposit_event(Event::::TicketBought { who: caller.clone(), call_index }); Ok(()) } - // Randomly choose a winner from among the total number of participants. - fn choose_winner(total: u32) -> u32 { + /// Randomly choose a winning ticket and return the account that purchased it. + /// The more tickets an account bought, the higher are its chances of winning. + /// Returns `None` if there is no winner. + fn choose_account() -> Option { + match Self::choose_ticket(TicketsCount::::get()) { + None => None, + Some(ticket) => Tickets::::get(ticket), + } + } + + /// Randomly choose a winning ticket from among the total number of tickets. + /// Returns `None` if there are no tickets. + fn choose_ticket(total: u32) -> Option { + if total == 0 { + return None + } let mut random_number = Self::generate_random_number(0); // Best effort attempt to remove bias from modulus operator. @@ -470,15 +504,15 @@ impl Pallet { random_number = Self::generate_random_number(i); } - random_number % total + Some(random_number % total) } - // Generate a random number from a given seed. - // Note that there is potential bias introduced by using modulus operator. - // You should call this function with different seed values until the random - // number lies within `u32::MAX - u32::MAX % n`. - // TODO: deal with randomness freshness - // https://github.com/paritytech/substrate/issues/8311 + /// Generate a random number from a given seed. + /// Note that there is potential bias introduced by using modulus operator. + /// You should call this function with different seed values until the random + /// number lies within `u32::MAX - u32::MAX % n`. + /// TODO: deal with randomness freshness + /// https://github.com/paritytech/substrate/issues/8311 fn generate_random_number(seed: u32) -> u32 { let (random_seed, _) = T::Randomness::random(&(T::PalletId::get(), seed).encode()); let random_number = ::decode(&mut random_seed.as_ref()) diff --git a/frame/lottery/src/mock.rs b/frame/lottery/src/mock.rs index d1f090aa26dcb..1977da5959d39 100644 --- a/frame/lottery/src/mock.rs +++ b/frame/lottery/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use crate as pallet_lottery; use frame_support::{ parameter_types, - traits::{OnFinalize, OnInitialize}, + traits::{ConstU32, ConstU64, OnFinalize, OnInitialize}, }; use frame_support_test::TestRandomness; use frame_system::EnsureRoot; @@ -49,9 +49,6 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaximumBlockWeight: u32 = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::one(); } @@ -60,17 +57,17 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type BlockNumber = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -79,10 +76,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 1; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { @@ -90,29 +84,27 @@ impl pallet_balances::Config for Test { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } parameter_types! { pub const LotteryPalletId: PalletId = PalletId(*b"py/lotto"); - pub const MaxCalls: u32 = 2; - pub const MaxGenerateRandom: u32 = 10; } impl Config for Test { type PalletId = LotteryPalletId; - type Call = Call; + type RuntimeCall = RuntimeCall; type Currency = Balances; type Randomness = TestRandomness; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ManagerOrigin = EnsureRoot; - type MaxCalls = MaxCalls; + type MaxCalls = ConstU32<2>; type ValidateCall = Lottery; - type MaxGenerateRandom = MaxGenerateRandom; + type MaxGenerateRandom = ConstU32<10>; type WeightInfo = (); } diff --git a/frame/lottery/src/tests.rs b/frame/lottery/src/tests.rs index 623beea4a6b5b..0eaf080564008 100644 --- a/frame/lottery/src/tests.rs +++ b/frame/lottery/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,10 @@ //! Tests for the module. use super::*; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok, assert_storage_noop}; use mock::{ - new_test_ext, run_to_block, Balances, BalancesCall, Call, Lottery, Origin, SystemCall, Test, + new_test_ext, run_to_block, Balances, BalancesCall, Lottery, RuntimeCall, RuntimeOrigin, + SystemCall, Test, }; use pallet_balances::Error as BalancesError; use sp_runtime::traits::BadOrigin; @@ -30,7 +31,7 @@ fn initial_state() { new_test_ext().execute_with(|| { assert_eq!(Balances::free_balance(Lottery::account_id()), 0); assert!(crate::Lottery::::get().is_none()); - assert_eq!(Participants::::get(&1), (0, vec![])); + assert_eq!(Participants::::get(&1), (0, Default::default())); assert_eq!(TicketsCount::::get(), 0); assert!(Tickets::::get(0).is_none()); }); @@ -43,20 +44,20 @@ fn basic_end_to_end_works() { let length = 20; let delay = 5; let calls = vec![ - Call::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), - Call::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), ]; // Set calls for the lottery - assert_ok!(Lottery::set_calls(Origin::root(), calls)); + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls)); // Start lottery, it repeats - assert_ok!(Lottery::start_lottery(Origin::root(), price, length, delay, true)); + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), price, length, delay, true)); assert!(crate::Lottery::::get().is_some()); assert_eq!(Balances::free_balance(&1), 100); - let call = Box::new(Call::Balances(BalancesCall::transfer { dest: 2, value: 20 })); - assert_ok!(Lottery::buy_ticket(Origin::signed(1), call.clone())); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 20 })); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone())); // 20 from the transfer, 10 from buying a ticket assert_eq!(Balances::free_balance(&1), 100 - 20 - 10); assert_eq!(Participants::::get(&1).1.len(), 1); @@ -65,14 +66,14 @@ fn basic_end_to_end_works() { assert_eq!(Tickets::::get(0), Some(1)); // More ticket purchases - assert_ok!(Lottery::buy_ticket(Origin::signed(2), call.clone())); - assert_ok!(Lottery::buy_ticket(Origin::signed(3), call.clone())); - assert_ok!(Lottery::buy_ticket(Origin::signed(4), call.clone())); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), call.clone())); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(3), call.clone())); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(4), call.clone())); assert_eq!(TicketsCount::::get(), 4); // Go to end run_to_block(20); - assert_ok!(Lottery::buy_ticket(Origin::signed(5), call.clone())); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(5), call.clone())); // Ticket isn't bought assert_eq!(TicketsCount::::get(), 4); @@ -90,36 +91,88 @@ fn basic_end_to_end_works() { }); } +/// Only the manager can stop the Lottery from repeating via `stop_repeat`. +#[test] +fn stop_repeat_works() { + new_test_ext().execute_with(|| { + let price = 10; + let length = 20; + let delay = 5; + + // Set no calls for the lottery. + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), vec![])); + // Start lottery, it repeats. + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), price, length, delay, true)); + + // Non-manager fails to `stop_repeat`. + assert_noop!(Lottery::stop_repeat(RuntimeOrigin::signed(1)), DispatchError::BadOrigin); + // Manager can `stop_repeat`, even twice. + assert_ok!(Lottery::stop_repeat(RuntimeOrigin::root())); + assert_ok!(Lottery::stop_repeat(RuntimeOrigin::root())); + + // Lottery still exists. + assert!(crate::Lottery::::get().is_some()); + // End and pick a winner. + run_to_block(length + delay); + + // Lottery stays dead and does not repeat. + assert!(crate::Lottery::::get().is_none()); + run_to_block(length + delay + 1); + assert!(crate::Lottery::::get().is_none()); + }); +} + #[test] fn set_calls_works() { new_test_ext().execute_with(|| { assert!(!CallIndices::::exists()); let calls = vec![ - Call::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), - Call::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), ]; - assert_ok!(Lottery::set_calls(Origin::root(), calls)); + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls)); assert!(CallIndices::::exists()); let too_many_calls = vec![ - Call::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), - Call::Balances(BalancesCall::transfer { dest: 0, value: 0 }), - Call::System(SystemCall::remark { remark: vec![] }), + RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::System(SystemCall::remark { remark: vec![] }), ]; assert_noop!( - Lottery::set_calls(Origin::root(), too_many_calls), + Lottery::set_calls(RuntimeOrigin::root(), too_many_calls), Error::::TooManyCalls, ); // Clear calls - assert_ok!(Lottery::set_calls(Origin::root(), vec![])); + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), vec![])); assert!(CallIndices::::get().is_empty()); }); } +#[test] +fn call_to_indices_works() { + new_test_ext().execute_with(|| { + let calls = vec![ + RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + ]; + let indices = Lottery::calls_to_indices(&calls).unwrap().into_inner(); + // Only comparing the length since it is otherwise dependant on the API + // of `BalancesCall`. + assert_eq!(indices.len(), calls.len()); + + let too_many_calls = vec![ + RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::System(SystemCall::remark { remark: vec![] }), + ]; + assert_noop!(Lottery::calls_to_indices(&too_many_calls), Error::::TooManyCalls); + }); +} + #[test] fn start_lottery_works() { new_test_ext().execute_with(|| { @@ -129,16 +182,16 @@ fn start_lottery_works() { // Setup ignores bad origin assert_noop!( - Lottery::start_lottery(Origin::signed(1), price, length, delay, false), + Lottery::start_lottery(RuntimeOrigin::signed(1), price, length, delay, false), BadOrigin, ); // All good - assert_ok!(Lottery::start_lottery(Origin::root(), price, length, delay, false)); + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), price, length, delay, false)); // Can't open another one if lottery is already present assert_noop!( - Lottery::start_lottery(Origin::root(), price, length, delay, false), + Lottery::start_lottery(RuntimeOrigin::root(), price, length, delay, false), Error::::InProgress, ); }); @@ -150,45 +203,49 @@ fn buy_ticket_works_as_simple_passthrough() { // as a simple passthrough to the real call. new_test_ext().execute_with(|| { // No lottery set up - let call = Box::new(Call::Balances(BalancesCall::transfer { dest: 2, value: 20 })); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 20 })); // This is just a basic transfer then - assert_ok!(Lottery::buy_ticket(Origin::signed(1), call.clone())); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone())); assert_eq!(Balances::free_balance(&1), 100 - 20); assert_eq!(TicketsCount::::get(), 0); // Lottery is set up, but too expensive to enter, so `do_buy_ticket` fails. let calls = vec![ - Call::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), - Call::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), ]; - assert_ok!(Lottery::set_calls(Origin::root(), calls)); + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls)); // Ticket price of 60 would kill the user's account - assert_ok!(Lottery::start_lottery(Origin::root(), 60, 10, 5, false)); - assert_ok!(Lottery::buy_ticket(Origin::signed(1), call.clone())); + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 60, 10, 5, false)); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone())); assert_eq!(Balances::free_balance(&1), 100 - 20 - 20); assert_eq!(TicketsCount::::get(), 0); // If call would fail, the whole thing still fails the same - let fail_call = Box::new(Call::Balances(BalancesCall::transfer { dest: 2, value: 1000 })); + let fail_call = + Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1000 })); assert_noop!( - Lottery::buy_ticket(Origin::signed(1), fail_call), + Lottery::buy_ticket(RuntimeOrigin::signed(1), fail_call), BalancesError::::InsufficientBalance, ); - let bad_origin_call = - Box::new(Call::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 })); - assert_noop!(Lottery::buy_ticket(Origin::signed(1), bad_origin_call), BadOrigin,); + let bad_origin_call = Box::new(RuntimeCall::Balances(BalancesCall::force_transfer { + source: 0, + dest: 0, + value: 0, + })); + assert_noop!(Lottery::buy_ticket(RuntimeOrigin::signed(1), bad_origin_call), BadOrigin,); // User can call other txs, but doesn't get a ticket let remark_call = - Box::new(Call::System(SystemCall::remark { remark: b"hello, world!".to_vec() })); - assert_ok!(Lottery::buy_ticket(Origin::signed(2), remark_call)); + Box::new(RuntimeCall::System(SystemCall::remark { remark: b"hello, world!".to_vec() })); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), remark_call)); assert_eq!(TicketsCount::::get(), 0); let successful_call = - Box::new(Call::Balances(BalancesCall::transfer { dest: 2, value: 1 })); - assert_ok!(Lottery::buy_ticket(Origin::signed(2), successful_call)); + Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1 })); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), successful_call)); assert_eq!(TicketsCount::::get(), 1); }); } @@ -198,47 +255,148 @@ fn buy_ticket_works() { new_test_ext().execute_with(|| { // Set calls for the lottery. let calls = vec![ - Call::System(SystemCall::remark { remark: vec![] }), - Call::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::System(SystemCall::remark { remark: vec![] }), + RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), ]; - assert_ok!(Lottery::set_calls(Origin::root(), calls)); + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls)); // Can't buy ticket before start - let call = Box::new(Call::Balances(BalancesCall::transfer { dest: 2, value: 1 })); - assert_ok!(Lottery::buy_ticket(Origin::signed(1), call.clone())); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1 })); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone())); assert_eq!(TicketsCount::::get(), 0); // Start lottery - assert_ok!(Lottery::start_lottery(Origin::root(), 1, 20, 5, false)); + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 20, 5, false)); // Go to start, buy ticket for transfer run_to_block(5); - assert_ok!(Lottery::buy_ticket(Origin::signed(1), call)); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call)); assert_eq!(TicketsCount::::get(), 1); // Can't buy another of the same ticket (even if call is slightly changed) - let call = Box::new(Call::Balances(BalancesCall::transfer { dest: 3, value: 30 })); - assert_ok!(Lottery::buy_ticket(Origin::signed(1), call)); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 3, value: 30 })); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call)); assert_eq!(TicketsCount::::get(), 1); // Buy ticket for remark - let call = Box::new(Call::System(SystemCall::remark { remark: b"hello, world!".to_vec() })); - assert_ok!(Lottery::buy_ticket(Origin::signed(1), call.clone())); + let call = + Box::new(RuntimeCall::System(SystemCall::remark { remark: b"hello, world!".to_vec() })); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone())); assert_eq!(TicketsCount::::get(), 2); // Go to end, can't buy tickets anymore run_to_block(20); - assert_ok!(Lottery::buy_ticket(Origin::signed(2), call.clone())); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), call.clone())); assert_eq!(TicketsCount::::get(), 2); // Go to payout, can't buy tickets when there is no lottery open run_to_block(25); - assert_ok!(Lottery::buy_ticket(Origin::signed(2), call.clone())); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), call.clone())); assert_eq!(TicketsCount::::get(), 0); assert_eq!(LotteryIndex::::get(), 1); }); } +/// Test that `do_buy_ticket` returns an `AlreadyParticipating` error. +/// Errors of `do_buy_ticket` are ignored by `buy_ticket`, therefore this white-box test. +#[test] +fn do_buy_ticket_already_participating() { + new_test_ext().execute_with(|| { + let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 10, 10, false)); + + // Buying once works. + assert_ok!(Lottery::do_buy_ticket(&1, &calls[0])); + // Buying the same ticket again fails. + assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), Error::::AlreadyParticipating); + }); +} + +/// `buy_ticket` is a storage noop when called with the same ticket again. +#[test] +fn buy_ticket_already_participating() { + new_test_ext().execute_with(|| { + let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 10, 10, false)); + + // Buying once works. + let call = Box::new(calls[0].clone()); + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone())); + + // Buying the same ticket again returns Ok, but changes nothing. + assert_storage_noop!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call).unwrap()); + + // Exactly one ticket exists. + assert_eq!(TicketsCount::::get(), 1); + }); +} + +/// `buy_ticket` is a storage noop when called with insufficient balance. +#[test] +fn buy_ticket_insufficient_balance() { + new_test_ext().execute_with(|| { + let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); + // Price set to 100. + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 100, 10, 10, false)); + let call = Box::new(calls[0].clone()); + + // Buying a ticket returns Ok, but changes nothing. + assert_storage_noop!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call).unwrap()); + assert!(TicketsCount::::get().is_zero()); + }); +} + +#[test] +fn do_buy_ticket_insufficient_balance() { + new_test_ext().execute_with(|| { + let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); + // Price set to 101. + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 101, 10, 10, false)); + + // Buying fails with InsufficientBalance. + assert_noop!( + Lottery::do_buy_ticket(&1, &calls[0]), + BalancesError::::InsufficientBalance + ); + assert!(TicketsCount::::get().is_zero()); + }); +} + +#[test] +fn do_buy_ticket_keep_alive() { + new_test_ext().execute_with(|| { + let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); + // Price set to 100. + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 100, 10, 10, false)); + + // Buying fails with KeepAlive. + assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), BalancesError::::KeepAlive); + assert!(TicketsCount::::get().is_zero()); + }); +} + +/// The lottery handles the case that no one participated. +#[test] +fn no_participants_works() { + new_test_ext().execute_with(|| { + let length = 20; + let delay = 5; + + // Set no calls for the lottery. + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), vec![])); + // Start lottery. + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 10, length, delay, false)); + + // End the lottery, no one wins. + run_to_block(length + delay); + }); +} + #[test] fn start_lottery_will_create_account() { new_test_ext().execute_with(|| { @@ -247,7 +405,30 @@ fn start_lottery_will_create_account() { let delay = 5; assert_eq!(Balances::total_balance(&Lottery::account_id()), 0); - assert_ok!(Lottery::start_lottery(Origin::root(), price, length, delay, false)); + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), price, length, delay, false)); assert_eq!(Balances::total_balance(&Lottery::account_id()), 1); }); } + +#[test] +fn choose_ticket_trivial_cases() { + new_test_ext().execute_with(|| { + assert!(Lottery::choose_ticket(0).is_none()); + assert_eq!(Lottery::choose_ticket(1).unwrap(), 0); + }); +} + +#[test] +fn choose_account_one_participant() { + new_test_ext().execute_with(|| { + let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); + assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 10, 10, 10, false)); + let call = Box::new(calls[0].clone()); + + // Buy one ticket with account 1. + assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call)); + // Account 1 is always the winner. + assert_eq!(Lottery::choose_account().unwrap(), 1); + }); +} diff --git a/frame/lottery/src/weights.rs b/frame/lottery/src/weights.rs index 5fbc61a32e577..e9ee528cc43b8 100644 --- a/frame/lottery/src/weights.rs +++ b/frame/lottery/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,24 +18,27 @@ //! Autogenerated weights for pallet_lottery //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark -// --chain=dev +// pallet // --steps=50 // --repeat=20 -// --pallet=pallet_lottery // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_lottery +// --chain=dev +// --header=./HEADER-APACHE2 // --output=./frame/lottery/src/weights.rs // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -64,30 +67,35 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Lottery Tickets (r:0 w:1) fn buy_ticket() -> Weight { - (70_034_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 52_479 nanoseconds. + Weight::from_ref_time(53_225_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Lottery CallIndices (r:0 w:1) + /// The range of component `n` is `[0, 10]`. fn set_calls(n: u32, ) -> Weight { - (15_243_000 as Weight) - // Standard Error: 8_000 - .saturating_add((312_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 14_433 nanoseconds. + Weight::from_ref_time(15_660_780) + // Standard Error: 5_894 + .saturating_add(Weight::from_ref_time(290_482).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Lottery Lottery (r:1 w:1) // Storage: Lottery LotteryIndex (r:1 w:1) // Storage: System Account (r:1 w:1) fn start_lottery() -> Weight { - (57_312_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 43_683 nanoseconds. + Weight::from_ref_time(44_580_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Lottery Lottery (r:1 w:1) fn stop_repeat() -> Weight { - (6_964_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 10_514 nanoseconds. + Weight::from_ref_time(10_821_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) // Storage: Lottery Lottery (r:1 w:1) @@ -95,9 +103,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Lottery TicketsCount (r:1 w:1) // Storage: Lottery Tickets (r:1 w:0) fn on_initialize_end() -> Weight { - (110_470_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 60_254 nanoseconds. + Weight::from_ref_time(61_924_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) // Storage: Lottery Lottery (r:1 w:1) @@ -106,9 +115,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Lottery Tickets (r:1 w:0) // Storage: Lottery LotteryIndex (r:1 w:1) fn on_initialize_repeat() -> Weight { - (114_794_000 as Weight) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + // Minimum execution time: 61_552 nanoseconds. + Weight::from_ref_time(62_152_000) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(5)) } } @@ -122,30 +132,35 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Lottery Tickets (r:0 w:1) fn buy_ticket() -> Weight { - (70_034_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 52_479 nanoseconds. + Weight::from_ref_time(53_225_000) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Lottery CallIndices (r:0 w:1) + /// The range of component `n` is `[0, 10]`. fn set_calls(n: u32, ) -> Weight { - (15_243_000 as Weight) - // Standard Error: 8_000 - .saturating_add((312_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 14_433 nanoseconds. + Weight::from_ref_time(15_660_780) + // Standard Error: 5_894 + .saturating_add(Weight::from_ref_time(290_482).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Lottery Lottery (r:1 w:1) // Storage: Lottery LotteryIndex (r:1 w:1) // Storage: System Account (r:1 w:1) fn start_lottery() -> Weight { - (57_312_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 43_683 nanoseconds. + Weight::from_ref_time(44_580_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Lottery Lottery (r:1 w:1) fn stop_repeat() -> Weight { - (6_964_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 10_514 nanoseconds. + Weight::from_ref_time(10_821_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) // Storage: Lottery Lottery (r:1 w:1) @@ -153,9 +168,10 @@ impl WeightInfo for () { // Storage: Lottery TicketsCount (r:1 w:1) // Storage: Lottery Tickets (r:1 w:0) fn on_initialize_end() -> Weight { - (110_470_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 60_254 nanoseconds. + Weight::from_ref_time(61_924_000) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) // Storage: Lottery Lottery (r:1 w:1) @@ -164,8 +180,9 @@ impl WeightInfo for () { // Storage: Lottery Tickets (r:1 w:0) // Storage: Lottery LotteryIndex (r:1 w:1) fn on_initialize_repeat() -> Weight { - (114_794_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + // Minimum execution time: 61_552 nanoseconds. + Weight::from_ref_time(62_152_000) + .saturating_add(RocksDbWeight::get().reads(7)) + .saturating_add(RocksDbWeight::get().writes(5)) } } diff --git a/frame/membership/Cargo.toml b/frame/membership/Cargo.toml index acc82f7678de6..b457c4c2911bd 100644 --- a/frame/membership/Cargo.toml +++ b/frame/membership/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-membership" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME membership management pallet" readme = "README.md" @@ -13,37 +13,35 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -log = { version = "0.4.0", default-features = false } - -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", "log/std", + "scale-info/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", - "frame-benchmarking/std", ] runtime-benchmarks = [ - "frame-benchmarking", - "sp-runtime/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/membership/README.md b/frame/membership/README.md index a769be497050e..3499a3f864e48 100644 --- a/frame/membership/README.md +++ b/frame/membership/README.md @@ -1,6 +1,6 @@ # Membership Module -Allows control of membership of a set of `AccountId`s, useful for managing membership of of a +Allows control of membership of a set of `AccountId`s, useful for managing membership of a collective. A prime member may be set. -License: Apache-2.0 \ No newline at end of file +License: Apache-2.0 diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index 7922d9efaf569..77b53aa72dad8 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,15 +17,17 @@ //! # Membership Module //! -//! Allows control of membership of a set of `AccountId`s, useful for managing membership of of a +//! Allows control of membership of a set of `AccountId`s, useful for managing membership of a //! collective. A prime member may be set // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{ - ChangeMembers, Contains, Get, InitializeMembers, SortedMembers, StorageVersion, +use frame_support::{ + traits::{ChangeMembers, Contains, Get, InitializeMembers, SortedMembers}, + BoundedVec, }; +use sp_runtime::traits::StaticLookup; use sp_std::prelude::*; pub mod migrations; @@ -34,6 +36,8 @@ pub mod weights; pub use pallet::*; pub use weights::WeightInfo; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -51,22 +55,23 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; /// Required origin for adding a member (though can always be Root). - type AddOrigin: EnsureOrigin; + type AddOrigin: EnsureOrigin; /// Required origin for removing a member (though can always be Root). - type RemoveOrigin: EnsureOrigin; + type RemoveOrigin: EnsureOrigin; /// Required origin for adding and removing a member in a single action. - type SwapOrigin: EnsureOrigin; + type SwapOrigin: EnsureOrigin; /// Required origin for resetting membership. - type ResetOrigin: EnsureOrigin; + type ResetOrigin: EnsureOrigin; /// Required origin for setting or resetting the prime member. - type PrimeOrigin: EnsureOrigin; + type PrimeOrigin: EnsureOrigin; /// The receiver of the signal for when the membership has been initialized. This happens /// pre-genesis and will usually be the same as `MembershipChanged`. If you need to do @@ -80,7 +85,7 @@ pub mod pallet { /// /// This is used for benchmarking. Re-run the benchmarks if this changes. /// - /// This is not enforced in the code; the membership size can exceed this limit. + /// This is enforced in the code; the membership size can not exceed this limit. type MaxMembers: Get; /// Weight information for extrinsics in this pallet. @@ -91,7 +96,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn members)] pub type Members, I: 'static = ()> = - StorageValue<_, Vec, ValueQuery>; + StorageValue<_, BoundedVec, ValueQuery>; /// The current prime member, if one exists. #[pallet::storage] @@ -100,14 +105,14 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { - pub members: Vec, + pub members: BoundedVec, pub phantom: PhantomData, } #[cfg(feature = "std")] impl, I: 'static> Default for GenesisConfig { fn default() -> Self { - Self { members: Vec::new(), phantom: Default::default() } + Self { members: Default::default(), phantom: Default::default() } } } @@ -143,19 +148,17 @@ pub mod pallet { /// One of the members' keys changed. KeyChanged, /// Phantom member, never used. - Dummy(PhantomData<(T::AccountId, >::Event)>), + Dummy { _phantom_data: PhantomData<(T::AccountId, >::RuntimeEvent)> }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// Already a member. AlreadyMember, /// Not a member. NotMember, + /// Too many members. + TooManyMembers, } #[pallet::call] @@ -163,15 +166,18 @@ pub mod pallet { /// Add a member `who` to the set. /// /// May only be called from `T::AddOrigin`. + #[pallet::call_index(0)] #[pallet::weight(50_000_000)] - pub fn add_member(origin: OriginFor, who: T::AccountId) -> DispatchResult { + pub fn add_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::AddOrigin::ensure_origin(origin)?; + let who = T::Lookup::lookup(who)?; let mut members = >::get(); let location = members.binary_search(&who).err().ok_or(Error::::AlreadyMember)?; - members.insert(location, who.clone()); + members + .try_insert(location, who.clone()) + .map_err(|_| Error::::TooManyMembers)?; - Self::maybe_warn_max_members(&members); >::put(&members); T::MembershipChanged::change_members_sorted(&[who], &[], &members[..]); @@ -183,15 +189,16 @@ pub mod pallet { /// Remove a member `who` from the set. /// /// May only be called from `T::RemoveOrigin`. + #[pallet::call_index(1)] #[pallet::weight(50_000_000)] - pub fn remove_member(origin: OriginFor, who: T::AccountId) -> DispatchResult { + pub fn remove_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::RemoveOrigin::ensure_origin(origin)?; + let who = T::Lookup::lookup(who)?; let mut members = >::get(); let location = members.binary_search(&who).ok().ok_or(Error::::NotMember)?; members.remove(location); - Self::maybe_warn_max_members(&members); >::put(&members); T::MembershipChanged::change_members_sorted(&[], &[who], &members[..]); @@ -206,13 +213,16 @@ pub mod pallet { /// May only be called from `T::SwapOrigin`. /// /// Prime membership is *not* passed from `remove` to `add`, if extant. + #[pallet::call_index(2)] #[pallet::weight(50_000_000)] pub fn swap_member( origin: OriginFor, - remove: T::AccountId, - add: T::AccountId, + remove: AccountIdLookupOf, + add: AccountIdLookupOf, ) -> DispatchResult { T::SwapOrigin::ensure_origin(origin)?; + let remove = T::Lookup::lookup(remove)?; + let add = T::Lookup::lookup(add)?; if remove == add { return Ok(()) @@ -224,7 +234,6 @@ pub mod pallet { members[location] = add.clone(); members.sort(); - Self::maybe_warn_max_members(&members); >::put(&members); T::MembershipChanged::change_members_sorted(&[add], &[remove], &members[..]); @@ -238,16 +247,17 @@ pub mod pallet { /// pass `members` pre-sorted. /// /// May only be called from `T::ResetOrigin`. + #[pallet::call_index(3)] #[pallet::weight(50_000_000)] pub fn reset_members(origin: OriginFor, members: Vec) -> DispatchResult { T::ResetOrigin::ensure_origin(origin)?; - let mut members = members; + let mut members: BoundedVec = + BoundedVec::try_from(members).map_err(|_| Error::::TooManyMembers)?; members.sort(); >::mutate(|m| { T::MembershipChanged::set_members_sorted(&members[..], m); Self::rejig_prime(&members); - Self::maybe_warn_max_members(&members); *m = members; }); @@ -260,9 +270,11 @@ pub mod pallet { /// May only be called from `Signed` origin of a current member. /// /// Prime membership is passed from the origin account to `new`, if extant. + #[pallet::call_index(4)] #[pallet::weight(50_000_000)] - pub fn change_key(origin: OriginFor, new: T::AccountId) -> DispatchResult { + pub fn change_key(origin: OriginFor, new: AccountIdLookupOf) -> DispatchResult { let remove = ensure_signed(origin)?; + let new = T::Lookup::lookup(new)?; if remove != new { let mut members = >::get(); @@ -272,7 +284,6 @@ pub mod pallet { members[location] = new.clone(); members.sort(); - Self::maybe_warn_max_members(&members); >::put(&members); T::MembershipChanged::change_members_sorted( @@ -294,9 +305,11 @@ pub mod pallet { /// Set the prime member. Must be a current member. /// /// May only be called from `T::PrimeOrigin`. + #[pallet::call_index(5)] #[pallet::weight(50_000_000)] - pub fn set_prime(origin: OriginFor, who: T::AccountId) -> DispatchResult { + pub fn set_prime(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::PrimeOrigin::ensure_origin(origin)?; + let who = T::Lookup::lookup(who)?; Self::members().binary_search(&who).ok().ok_or(Error::::NotMember)?; Prime::::put(&who); T::MembershipChanged::set_prime(Some(who)); @@ -306,6 +319,7 @@ pub mod pallet { /// Remove the prime member if it exists. /// /// May only be called from `T::PrimeOrigin`. + #[pallet::call_index(6)] #[pallet::weight(50_000_000)] pub fn clear_prime(origin: OriginFor) -> DispatchResult { T::PrimeOrigin::ensure_origin(origin)?; @@ -325,17 +339,6 @@ impl, I: 'static> Pallet { } } } - - fn maybe_warn_max_members(members: &[T::AccountId]) { - if members.len() as u32 > T::MaxMembers::get() { - log::error!( - target: "runtime::membership", - "maximum number of members used for weight is exceeded, weights can be underestimated [{} > {}].", - members.len(), - T::MaxMembers::get(), - ) - } - } } impl, I: 'static> Contains for Pallet { @@ -346,7 +349,7 @@ impl, I: 'static> Contains for Pallet { impl, I: 'static> SortedMembers for Pallet { fn sorted_members() -> Vec { - Self::members() + Self::members().to_vec() } fn count() -> usize { @@ -357,9 +360,7 @@ impl, I: 'static> SortedMembers for Pallet { #[cfg(feature = "runtime-benchmarks")] mod benchmark { use super::{Pallet as Membership, *}; - use frame_benchmarking::{ - account, benchmarks_instance_pallet, impl_benchmark_test_suite, whitelist, - }; + use frame_benchmarking::{account, benchmarks_instance_pallet, whitelist}; use frame_support::{assert_ok, traits::EnsureOrigin}; use frame_system::RawOrigin; @@ -371,7 +372,8 @@ mod benchmark { assert_ok!(>::reset_members(reset_origin, members.clone())); if let Some(prime) = prime.map(|i| members[i].clone()) { - assert_ok!(>::set_prime(prime_origin, prime)); + let prime_lookup = T::Lookup::unlookup(prime); + assert_ok!(>::set_prime(prime_origin, prime_lookup)); } else { assert_ok!(>::clear_prime(prime_origin)); } @@ -379,13 +381,14 @@ mod benchmark { benchmarks_instance_pallet! { add_member { - let m in 1 .. T::MaxMembers::get(); + let m in 1 .. (T::MaxMembers::get() - 1); let members = (0..m).map(|i| account("member", i, SEED)).collect::>(); - set_members::(members.clone(), None); + set_members::(members, None); let new_member = account::("add", m, SEED); + let new_member_lookup = T::Lookup::unlookup(new_member.clone()); }: { - assert_ok!(>::add_member(T::AddOrigin::successful_origin(), new_member.clone())); + assert_ok!(>::add_member(T::AddOrigin::successful_origin(), new_member_lookup)); } verify { assert!(>::get().contains(&new_member)); @@ -401,8 +404,9 @@ mod benchmark { set_members::(members.clone(), Some(members.len() - 1)); let to_remove = members.first().cloned().unwrap(); + let to_remove_lookup = T::Lookup::unlookup(to_remove.clone()); }: { - assert_ok!(>::remove_member(T::RemoveOrigin::successful_origin(), to_remove.clone())); + assert_ok!(>::remove_member(T::RemoveOrigin::successful_origin(), to_remove_lookup)); } verify { assert!(!>::get().contains(&to_remove)); // prime is rejigged @@ -417,12 +421,14 @@ mod benchmark { let members = (0..m).map(|i| account("member", i, SEED)).collect::>(); set_members::(members.clone(), Some(members.len() - 1)); let add = account::("member", m, SEED); + let add_lookup = T::Lookup::unlookup(add.clone()); let remove = members.first().cloned().unwrap(); + let remove_lookup = T::Lookup::unlookup(remove.clone()); }: { assert_ok!(>::swap_member( T::SwapOrigin::successful_origin(), - remove.clone(), - add.clone(), + remove_lookup, + add_lookup, )); } verify { assert!(!>::get().contains(&remove)); @@ -458,9 +464,10 @@ mod benchmark { set_members::(members.clone(), Some(members.len() - 1)); let add = account::("member", m, SEED); + let add_lookup = T::Lookup::unlookup(add.clone()); whitelist!(prime); }: { - assert_ok!(>::change_key(RawOrigin::Signed(prime.clone()).into(), add.clone())); + assert_ok!(>::change_key(RawOrigin::Signed(prime.clone()).into(), add_lookup)); } verify { assert!(!>::get().contains(&prime)); assert!(>::get().contains(&add)); @@ -473,9 +480,10 @@ mod benchmark { let m in 1 .. T::MaxMembers::get(); let members = (0..m).map(|i| account("member", i, SEED)).collect::>(); let prime = members.last().cloned().unwrap(); + let prime_lookup = T::Lookup::unlookup(prime.clone()); set_members::(members, None); }: { - assert_ok!(>::set_prime(T::PrimeOrigin::successful_origin(), prime)); + assert_ok!(>::set_prime(T::PrimeOrigin::successful_origin(), prime_lookup)); } verify { assert!(>::get().is_some()); assert!(::get_prime().is_some()); @@ -494,9 +502,9 @@ mod benchmark { assert!(::get_prime().is_none()); #[cfg(test)] crate::tests::clean(); } - } - impl_benchmark_test_suite!(Membership, crate::tests::new_bench_ext(), crate::tests::Test); + impl_benchmark_test_suite!(Membership, crate::tests::new_bench_ext(), crate::tests::Test); + } } #[cfg(test)] @@ -511,7 +519,8 @@ mod tests { }; use frame_support::{ - assert_noop, assert_ok, ord_parameter_types, parameter_types, traits::GenesisBuild, + assert_noop, assert_ok, bounded_vec, ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64, GenesisBuild, StorageVersion}, }; use frame_system::EnsureSignedBy; @@ -530,10 +539,8 @@ mod tests { ); parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaxMembers: u32 = 10; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); pub static Members: Vec = vec![]; pub static Prime: Option = None; } @@ -543,17 +550,17 @@ mod tests { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -562,6 +569,7 @@ mod tests { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } ord_parameter_types! { pub const One: u64 = 1; @@ -600,7 +608,7 @@ mod tests { } impl Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type AddOrigin = EnsureSignedBy; type RemoveOrigin = EnsureSignedBy; type SwapOrigin = EnsureSignedBy; @@ -608,7 +616,7 @@ mod tests { type PrimeOrigin = EnsureSignedBy; type MembershipInitialized = TestChangeMembers; type MembershipChanged = TestChangeMembers; - type MaxMembers = MaxMembers; + type MaxMembers = ConstU32<10>; type WeightInfo = (); } @@ -616,7 +624,7 @@ mod tests { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); // We use default for brevity, but you can configure as desired if needed. pallet_membership::GenesisConfig:: { - members: vec![10, 20, 30], + members: bounded_vec![10, 20, 30], ..Default::default() } .assimilate_storage(&mut t) @@ -646,13 +654,16 @@ mod tests { #[test] fn prime_member_works() { new_test_ext().execute_with(|| { - assert_noop!(Membership::set_prime(Origin::signed(4), 20), BadOrigin); - assert_noop!(Membership::set_prime(Origin::signed(5), 15), Error::::NotMember); - assert_ok!(Membership::set_prime(Origin::signed(5), 20)); + assert_noop!(Membership::set_prime(RuntimeOrigin::signed(4), 20), BadOrigin); + assert_noop!( + Membership::set_prime(RuntimeOrigin::signed(5), 15), + Error::::NotMember + ); + assert_ok!(Membership::set_prime(RuntimeOrigin::signed(5), 20)); assert_eq!(Membership::prime(), Some(20)); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); - assert_ok!(Membership::clear_prime(Origin::signed(5))); + assert_ok!(Membership::clear_prime(RuntimeOrigin::signed(5))); assert_eq!(Membership::prime(), None); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); }); @@ -661,29 +672,29 @@ mod tests { #[test] fn add_member_works() { new_test_ext().execute_with(|| { - assert_noop!(Membership::add_member(Origin::signed(5), 15), BadOrigin); + assert_noop!(Membership::add_member(RuntimeOrigin::signed(5), 15), BadOrigin); assert_noop!( - Membership::add_member(Origin::signed(1), 10), + Membership::add_member(RuntimeOrigin::signed(1), 10), Error::::AlreadyMember ); - assert_ok!(Membership::add_member(Origin::signed(1), 15)); + assert_ok!(Membership::add_member(RuntimeOrigin::signed(1), 15)); assert_eq!(Membership::members(), vec![10, 15, 20, 30]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); }); } #[test] fn remove_member_works() { new_test_ext().execute_with(|| { - assert_noop!(Membership::remove_member(Origin::signed(5), 20), BadOrigin); + assert_noop!(Membership::remove_member(RuntimeOrigin::signed(5), 20), BadOrigin); assert_noop!( - Membership::remove_member(Origin::signed(2), 15), + Membership::remove_member(RuntimeOrigin::signed(2), 15), Error::::NotMember ); - assert_ok!(Membership::set_prime(Origin::signed(5), 20)); - assert_ok!(Membership::remove_member(Origin::signed(2), 20)); + assert_ok!(Membership::set_prime(RuntimeOrigin::signed(5), 20)); + assert_ok!(Membership::remove_member(RuntimeOrigin::signed(2), 20)); assert_eq!(Membership::members(), vec![10, 30]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); assert_eq!(Membership::prime(), None); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); }); @@ -692,26 +703,26 @@ mod tests { #[test] fn swap_member_works() { new_test_ext().execute_with(|| { - assert_noop!(Membership::swap_member(Origin::signed(5), 10, 25), BadOrigin); + assert_noop!(Membership::swap_member(RuntimeOrigin::signed(5), 10, 25), BadOrigin); assert_noop!( - Membership::swap_member(Origin::signed(3), 15, 25), + Membership::swap_member(RuntimeOrigin::signed(3), 15, 25), Error::::NotMember ); assert_noop!( - Membership::swap_member(Origin::signed(3), 10, 30), + Membership::swap_member(RuntimeOrigin::signed(3), 10, 30), Error::::AlreadyMember ); - assert_ok!(Membership::set_prime(Origin::signed(5), 20)); - assert_ok!(Membership::swap_member(Origin::signed(3), 20, 20)); + assert_ok!(Membership::set_prime(RuntimeOrigin::signed(5), 20)); + assert_ok!(Membership::swap_member(RuntimeOrigin::signed(3), 20, 20)); assert_eq!(Membership::members(), vec![10, 20, 30]); assert_eq!(Membership::prime(), Some(20)); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); - assert_ok!(Membership::set_prime(Origin::signed(5), 10)); - assert_ok!(Membership::swap_member(Origin::signed(3), 10, 25)); + assert_ok!(Membership::set_prime(RuntimeOrigin::signed(5), 10)); + assert_ok!(Membership::swap_member(RuntimeOrigin::signed(3), 10, 25)); assert_eq!(Membership::members(), vec![20, 25, 30]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); assert_eq!(Membership::prime(), None); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); }); @@ -720,27 +731,27 @@ mod tests { #[test] fn swap_member_works_that_does_not_change_order() { new_test_ext().execute_with(|| { - assert_ok!(Membership::swap_member(Origin::signed(3), 10, 5)); + assert_ok!(Membership::swap_member(RuntimeOrigin::signed(3), 10, 5)); assert_eq!(Membership::members(), vec![5, 20, 30]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); }); } #[test] fn change_key_works() { new_test_ext().execute_with(|| { - assert_ok!(Membership::set_prime(Origin::signed(5), 10)); + assert_ok!(Membership::set_prime(RuntimeOrigin::signed(5), 10)); assert_noop!( - Membership::change_key(Origin::signed(3), 25), + Membership::change_key(RuntimeOrigin::signed(3), 25), Error::::NotMember ); assert_noop!( - Membership::change_key(Origin::signed(10), 20), + Membership::change_key(RuntimeOrigin::signed(10), 20), Error::::AlreadyMember ); - assert_ok!(Membership::change_key(Origin::signed(10), 40)); + assert_ok!(Membership::change_key(RuntimeOrigin::signed(10), 40)); assert_eq!(Membership::members(), vec![20, 30, 40]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); assert_eq!(Membership::prime(), Some(40)); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); }); @@ -749,27 +760,30 @@ mod tests { #[test] fn change_key_works_that_does_not_change_order() { new_test_ext().execute_with(|| { - assert_ok!(Membership::change_key(Origin::signed(10), 5)); + assert_ok!(Membership::change_key(RuntimeOrigin::signed(10), 5)); assert_eq!(Membership::members(), vec![5, 20, 30]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); }); } #[test] fn reset_members_works() { new_test_ext().execute_with(|| { - assert_ok!(Membership::set_prime(Origin::signed(5), 20)); - assert_noop!(Membership::reset_members(Origin::signed(1), vec![20, 40, 30]), BadOrigin); + assert_ok!(Membership::set_prime(RuntimeOrigin::signed(5), 20)); + assert_noop!( + Membership::reset_members(RuntimeOrigin::signed(1), bounded_vec![20, 40, 30]), + BadOrigin + ); - assert_ok!(Membership::reset_members(Origin::signed(4), vec![20, 40, 30])); + assert_ok!(Membership::reset_members(RuntimeOrigin::signed(4), vec![20, 40, 30])); assert_eq!(Membership::members(), vec![20, 30, 40]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); assert_eq!(Membership::prime(), Some(20)); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); - assert_ok!(Membership::reset_members(Origin::signed(4), vec![10, 40, 30])); + assert_ok!(Membership::reset_members(RuntimeOrigin::signed(4), vec![10, 40, 30])); assert_eq!(Membership::members(), vec![10, 30, 40]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); assert_eq!(Membership::prime(), None); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); }); @@ -779,7 +793,7 @@ mod tests { #[should_panic(expected = "Members cannot contain duplicate accounts.")] fn genesis_build_panics_with_duplicate_members() { pallet_membership::GenesisConfig:: { - members: vec![1, 2, 3, 1], + members: bounded_vec![1, 2, 3, 1], phantom: Default::default(), } .build_storage() @@ -790,9 +804,16 @@ mod tests { fn migration_v4() { new_test_ext().execute_with(|| { use frame_support::traits::PalletInfo; - let old_pallet_name = + let old_pallet_name = "OldMembership"; + let new_pallet_name = ::PalletInfo::name::().unwrap(); - let new_pallet_name = "NewMembership"; + + frame_support::storage::migration::move_pallet( + new_pallet_name.as_bytes(), + old_pallet_name.as_bytes(), + ); + + StorageVersion::new(0).put::(); crate::migrations::v4::pre_migrate::(old_pallet_name, new_pallet_name); crate::migrations::v4::migrate::(old_pallet_name, new_pallet_name); diff --git a/frame/membership/src/migrations/mod.rs b/frame/membership/src/migrations/mod.rs index 26d07a0cd5ac8..235d0f1c7cf15 100644 --- a/frame/membership/src/migrations/mod.rs +++ b/frame/membership/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/membership/src/migrations/v4.rs b/frame/membership/src/migrations/v4.rs index 9f4b15e468b38..5b8735aa2bac9 100644 --- a/frame/membership/src/migrations/v4.rs +++ b/frame/membership/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,8 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use sp_core::hexdisplay::HexDisplay; -use sp_io::{hashing::twox_128, storage}; +use sp_io::hashing::twox_128; use frame_support::{ traits::{ @@ -47,7 +46,7 @@ pub fn migrate::on_chain_storage_version(); @@ -72,7 +71,7 @@ pub fn migrate>(old_pallet_name: N, new_ let new_pallet_name = new_pallet_name.as_ref(); log_migration("pre-migration", old_pallet_name, new_pallet_name); - let old_pallet_prefix = twox_128(old_pallet_name.as_bytes()); - assert!(storage::next_key(&old_pallet_prefix) - .map_or(true, |next_key| next_key.starts_with(&old_pallet_prefix))); + if new_pallet_name == old_pallet_name { + return + } let new_pallet_prefix = twox_128(new_pallet_name.as_bytes()); - let storage_version_key = - [&new_pallet_prefix, &twox_128(STORAGE_VERSION_STORAGE_KEY_POSTFIX)[..]].concat(); - // ensure nothing is stored in the new prefix. - assert!( - storage::next_key(&new_pallet_prefix).map_or( - // either nothing is there - true, - // or we ensure that it has no common prefix with twox_128(new), - // or isn't the storage version that is already stored using the pallet name - |next_key| { - !next_key.starts_with(&new_pallet_prefix) || next_key == storage_version_key - }, - ), - "unexpected next_key({}) = {:?}", - new_pallet_name, - HexDisplay::from(&storage::next_key(&new_pallet_prefix).unwrap()), + let storage_version_key = twox_128(STORAGE_VERSION_STORAGE_KEY_POSTFIX); + + let mut new_pallet_prefix_iter = frame_support::storage::KeyPrefixIterator::new( + new_pallet_prefix.to_vec(), + new_pallet_prefix.to_vec(), + |key| Ok(key.to_vec()), ); + + // Ensure nothing except maybe the storage_version_key is stored in the new prefix. + assert!(new_pallet_prefix_iter.all(|key| key == storage_version_key)); + assert!(

::on_chain_storage_version() < 4); } @@ -119,26 +112,27 @@ pub fn post_migrate>(old_pallet_name: N, new let new_pallet_name = new_pallet_name.as_ref(); log_migration("post-migration", old_pallet_name, new_pallet_name); - let old_pallet_prefix = twox_128(old_pallet_name.as_bytes()); - #[cfg(test)] - { - let storage_version_key = - [&old_pallet_prefix, &twox_128(STORAGE_VERSION_STORAGE_KEY_POSTFIX)[..]].concat(); - assert!(storage::next_key(&old_pallet_prefix) - .map_or(true, |next_key| !next_key.starts_with(&old_pallet_prefix) || - next_key == storage_version_key)); - } - #[cfg(not(test))] - { - // Assert that nothing remains at the old prefix - assert!(storage::next_key(&old_pallet_prefix) - .map_or(true, |next_key| !next_key.starts_with(&old_pallet_prefix))); + if new_pallet_name == old_pallet_name { + return } + // Assert that nothing remains at the old prefix. + let old_pallet_prefix = twox_128(old_pallet_name.as_bytes()); + let old_pallet_prefix_iter = frame_support::storage::KeyPrefixIterator::new( + old_pallet_prefix.to_vec(), + old_pallet_prefix.to_vec(), + |_| Ok(()), + ); + assert_eq!(old_pallet_prefix_iter.count(), 0); + + // NOTE: storage_version_key is already in the new prefix. let new_pallet_prefix = twox_128(new_pallet_name.as_bytes()); - // Assert that the storages have been moved to the new prefix - assert!(storage::next_key(&new_pallet_prefix) - .map_or(true, |next_key| next_key.starts_with(&new_pallet_prefix))); + let new_pallet_prefix_iter = frame_support::storage::KeyPrefixIterator::new( + new_pallet_prefix.to_vec(), + new_pallet_prefix.to_vec(), + |_| Ok(()), + ); + assert!(new_pallet_prefix_iter.count() >= 1); assert_eq!(

::on_chain_storage_version(), 4); } diff --git a/frame/membership/src/weights.rs b/frame/membership/src/weights.rs index 81a1b073faac3..11574bc8fa399 100644 --- a/frame/membership/src/weights.rs +++ b/frame/membership/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_membership //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/membership/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -57,162 +59,190 @@ pub trait WeightInfo { /// Weights for pallet_membership using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Instance1Membership Members (r:1 w:1) - // Storage: Instance2Collective Proposals (r:1 w:0) - // Storage: Instance2Collective Members (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:1) + // Storage: TechnicalCommittee Proposals (r:1 w:0) + // Storage: TechnicalCommittee Members (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 99]`. fn add_member(m: u32, ) -> Weight { - (23_668_000 as Weight) - // Standard Error: 3_000 - .saturating_add((142_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 23_796 nanoseconds. + Weight::from_ref_time(24_829_996 as u64) + // Standard Error: 723 + .saturating_add(Weight::from_ref_time(48_467 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Instance1Membership Members (r:1 w:1) - // Storage: Instance2Collective Proposals (r:1 w:0) - // Storage: Instance1Membership Prime (r:1 w:0) - // Storage: Instance2Collective Members (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:1) + // Storage: TechnicalCommittee Proposals (r:1 w:0) + // Storage: TechnicalMembership Prime (r:1 w:0) + // Storage: TechnicalCommittee Members (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[2, 100]`. fn remove_member(m: u32, ) -> Weight { - (29_149_000 as Weight) - // Standard Error: 0 - .saturating_add((111_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 27_255 nanoseconds. + Weight::from_ref_time(28_234_490 as u64) + // Standard Error: 833 + .saturating_add(Weight::from_ref_time(34_894 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Instance1Membership Members (r:1 w:1) - // Storage: Instance2Collective Proposals (r:1 w:0) - // Storage: Instance1Membership Prime (r:1 w:0) - // Storage: Instance2Collective Members (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:1) + // Storage: TechnicalCommittee Proposals (r:1 w:0) + // Storage: TechnicalMembership Prime (r:1 w:0) + // Storage: TechnicalCommittee Members (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[2, 100]`. fn swap_member(m: u32, ) -> Weight { - (29_289_000 as Weight) - // Standard Error: 0 - .saturating_add((126_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 26_626 nanoseconds. + Weight::from_ref_time(27_989_042 as u64) + // Standard Error: 729 + .saturating_add(Weight::from_ref_time(51_567 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Instance1Membership Members (r:1 w:1) - // Storage: Instance2Collective Proposals (r:1 w:0) - // Storage: Instance1Membership Prime (r:1 w:0) - // Storage: Instance2Collective Members (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:1) + // Storage: TechnicalCommittee Proposals (r:1 w:0) + // Storage: TechnicalMembership Prime (r:1 w:0) + // Storage: TechnicalCommittee Members (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn reset_member(m: u32, ) -> Weight { - (30_178_000 as Weight) - // Standard Error: 1_000 - .saturating_add((286_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 25_412 nanoseconds. + Weight::from_ref_time(27_713_414 as u64) + // Standard Error: 883 + .saturating_add(Weight::from_ref_time(157_085 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Instance1Membership Members (r:1 w:1) - // Storage: Instance2Collective Proposals (r:1 w:0) - // Storage: Instance1Membership Prime (r:1 w:1) - // Storage: Instance2Collective Members (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:1) + // Storage: TechnicalCommittee Proposals (r:1 w:0) + // Storage: TechnicalMembership Prime (r:1 w:1) + // Storage: TechnicalCommittee Members (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn change_key(m: u32, ) -> Weight { - (31_049_000 as Weight) - // Standard Error: 0 - .saturating_add((121_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 27_122 nanoseconds. + Weight::from_ref_time(28_477_394 as u64) + // Standard Error: 801 + .saturating_add(Weight::from_ref_time(56_383 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } - // Storage: Instance1Membership Members (r:1 w:0) - // Storage: Instance1Membership Prime (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:0) + // Storage: TechnicalMembership Prime (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn set_prime(m: u32, ) -> Weight { - (8_006_000 as Weight) - // Standard Error: 0 - .saturating_add((89_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 9_368 nanoseconds. + Weight::from_ref_time(10_133_132 as u64) + // Standard Error: 366 + .saturating_add(Weight::from_ref_time(17_708 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: Instance1Membership Prime (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Prime (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn clear_prime(m: u32, ) -> Weight { - (3_452_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 5_546 nanoseconds. + Weight::from_ref_time(5_962_740 as u64) + // Standard Error: 186 + .saturating_add(Weight::from_ref_time(2_096 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Instance1Membership Members (r:1 w:1) - // Storage: Instance2Collective Proposals (r:1 w:0) - // Storage: Instance2Collective Members (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:1) + // Storage: TechnicalCommittee Proposals (r:1 w:0) + // Storage: TechnicalCommittee Members (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 99]`. fn add_member(m: u32, ) -> Weight { - (23_668_000 as Weight) - // Standard Error: 3_000 - .saturating_add((142_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 23_796 nanoseconds. + Weight::from_ref_time(24_829_996 as u64) + // Standard Error: 723 + .saturating_add(Weight::from_ref_time(48_467 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Instance1Membership Members (r:1 w:1) - // Storage: Instance2Collective Proposals (r:1 w:0) - // Storage: Instance1Membership Prime (r:1 w:0) - // Storage: Instance2Collective Members (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:1) + // Storage: TechnicalCommittee Proposals (r:1 w:0) + // Storage: TechnicalMembership Prime (r:1 w:0) + // Storage: TechnicalCommittee Members (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[2, 100]`. fn remove_member(m: u32, ) -> Weight { - (29_149_000 as Weight) - // Standard Error: 0 - .saturating_add((111_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 27_255 nanoseconds. + Weight::from_ref_time(28_234_490 as u64) + // Standard Error: 833 + .saturating_add(Weight::from_ref_time(34_894 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Instance1Membership Members (r:1 w:1) - // Storage: Instance2Collective Proposals (r:1 w:0) - // Storage: Instance1Membership Prime (r:1 w:0) - // Storage: Instance2Collective Members (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:1) + // Storage: TechnicalCommittee Proposals (r:1 w:0) + // Storage: TechnicalMembership Prime (r:1 w:0) + // Storage: TechnicalCommittee Members (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[2, 100]`. fn swap_member(m: u32, ) -> Weight { - (29_289_000 as Weight) - // Standard Error: 0 - .saturating_add((126_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 26_626 nanoseconds. + Weight::from_ref_time(27_989_042 as u64) + // Standard Error: 729 + .saturating_add(Weight::from_ref_time(51_567 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Instance1Membership Members (r:1 w:1) - // Storage: Instance2Collective Proposals (r:1 w:0) - // Storage: Instance1Membership Prime (r:1 w:0) - // Storage: Instance2Collective Members (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:1) + // Storage: TechnicalCommittee Proposals (r:1 w:0) + // Storage: TechnicalMembership Prime (r:1 w:0) + // Storage: TechnicalCommittee Members (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn reset_member(m: u32, ) -> Weight { - (30_178_000 as Weight) - // Standard Error: 1_000 - .saturating_add((286_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 25_412 nanoseconds. + Weight::from_ref_time(27_713_414 as u64) + // Standard Error: 883 + .saturating_add(Weight::from_ref_time(157_085 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Instance1Membership Members (r:1 w:1) - // Storage: Instance2Collective Proposals (r:1 w:0) - // Storage: Instance1Membership Prime (r:1 w:1) - // Storage: Instance2Collective Members (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:1) + // Storage: TechnicalCommittee Proposals (r:1 w:0) + // Storage: TechnicalMembership Prime (r:1 w:1) + // Storage: TechnicalCommittee Members (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn change_key(m: u32, ) -> Weight { - (31_049_000 as Weight) - // Standard Error: 0 - .saturating_add((121_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 27_122 nanoseconds. + Weight::from_ref_time(28_477_394 as u64) + // Standard Error: 801 + .saturating_add(Weight::from_ref_time(56_383 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } - // Storage: Instance1Membership Members (r:1 w:0) - // Storage: Instance1Membership Prime (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Members (r:1 w:0) + // Storage: TechnicalMembership Prime (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn set_prime(m: u32, ) -> Weight { - (8_006_000 as Weight) - // Standard Error: 0 - .saturating_add((89_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 9_368 nanoseconds. + Weight::from_ref_time(10_133_132 as u64) + // Standard Error: 366 + .saturating_add(Weight::from_ref_time(17_708 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: Instance1Membership Prime (r:0 w:1) - // Storage: Instance2Collective Prime (r:0 w:1) + // Storage: TechnicalMembership Prime (r:0 w:1) + // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn clear_prime(m: u32, ) -> Weight { - (3_452_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(m as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 5_546 nanoseconds. + Weight::from_ref_time(5_962_740 as u64) + // Standard Error: 186 + .saturating_add(Weight::from_ref_time(2_096 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } } diff --git a/frame/merkle-mountain-range/Cargo.toml b/frame/merkle-mountain-range/Cargo.toml index 02b4be182ef82..cf26cfb231c85 100644 --- a/frame/merkle-mountain-range/Cargo.toml +++ b/frame/merkle-mountain-range/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-mmr" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME Merkle Mountain Range pallet." @@ -12,39 +12,35 @@ description = "FRAME Merkle Mountain Range pallet." targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -mmr-lib = { package = "ckb-merkle-mountain-range", default-features = false, version = "0.3.1" } - -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } - -pallet-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "./primitives" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/merkle-mountain-range" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] +array-bytes = "4.1" env_logger = "0.9" -hex-literal = "0.3" +itertools = "0.10.3" [features] default = ["std"] std = [ "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", "scale-info/std", - "mmr-lib/std", "sp-core/std", "sp-io/std", + "sp-mmr-primitives/std", "sp-runtime/std", "sp-std/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", - "pallet-mmr-primitives/std", ] -runtime-benchmarks = ["frame-benchmarking"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/merkle-mountain-range/primitives/Cargo.toml b/frame/merkle-mountain-range/primitives/Cargo.toml deleted file mode 100644 index 07b2f8ae3a3a4..0000000000000 --- a/frame/merkle-mountain-range/primitives/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "pallet-mmr-primitives" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "FRAME Merkle Mountain Range primitives." - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -log = { version = "0.4.14", default-features = false } -serde = { version = "1.0.126", optional = true, features = ["derive"] } - -sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } - -frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } - -[dev-dependencies] -hex-literal = "0.3" - -[features] -default = ["std"] -std = [ - "codec/std", - "log/std", - "serde", - "sp-api/std", - "sp-core/std", - "sp-runtime/std", - "sp-std/std", - "frame-support/std", - "frame-system/std", -] diff --git a/frame/merkle-mountain-range/rpc/Cargo.toml b/frame/merkle-mountain-range/rpc/Cargo.toml deleted file mode 100644 index 5a0f114e50173..0000000000000 --- a/frame/merkle-mountain-range/rpc/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "pallet-mmr-rpc" -version = "3.0.0" -authors = ["Parity Technologies "] -edition = "2018" -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "Node-specific RPC methods for interaction with Merkle Mountain Range pallet." -publish = false - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -serde = { version = "1.0.126", features = ["derive"] } - -sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } - -pallet-mmr-primitives = { version = "4.0.0-dev", path = "../primitives" } - -[dev-dependencies] -serde_json = "1.0.68" diff --git a/frame/merkle-mountain-range/rpc/src/lib.rs b/frame/merkle-mountain-range/rpc/src/lib.rs deleted file mode 100644 index 4719893778f6a..0000000000000 --- a/frame/merkle-mountain-range/rpc/src/lib.rs +++ /dev/null @@ -1,208 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 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. - -#![warn(missing_docs)] - -//! Node-specific RPC methods for interaction with Merkle Mountain Range pallet. - -use std::sync::Arc; - -use codec::{Codec, Encode}; -use jsonrpc_core::{Error, ErrorCode, Result}; -use jsonrpc_derive::rpc; -use serde::{Deserialize, Serialize}; - -use pallet_mmr_primitives::{Error as MmrError, Proof}; -use sp_api::ProvideRuntimeApi; -use sp_blockchain::HeaderBackend; -use sp_core::Bytes; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; - -pub use pallet_mmr_primitives::MmrApi as MmrRuntimeApi; - -/// Retrieved MMR leaf and its proof. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct LeafProof { - /// Block hash the proof was generated for. - pub block_hash: BlockHash, - /// SCALE-encoded leaf data. - pub leaf: Bytes, - /// SCALE-encoded proof data. See [pallet_mmr_primitives::Proof]. - pub proof: Bytes, -} - -impl LeafProof { - /// Create new `LeafProof` from given concrete `leaf` and `proof`. - pub fn new(block_hash: BlockHash, leaf: Leaf, proof: Proof) -> Self - where - Leaf: Encode, - MmrHash: Encode, - { - Self { block_hash, leaf: Bytes(leaf.encode()), proof: Bytes(proof.encode()) } - } -} - -/// MMR RPC methods. -#[rpc] -pub trait MmrApi { - /// Generate MMR proof for given leaf index. - /// - /// This method calls into a runtime with MMR pallet included and attempts to generate - /// MMR proof for leaf at given `leaf_index`. - /// Optionally, a block hash at which the runtime should be queried can be specified. - /// - /// Returns the (full) leaf itself and a proof for this leaf (compact encoding, i.e. hash of - /// the leaf). Both parameters are SCALE-encoded. - #[rpc(name = "mmr_generateProof")] - fn generate_proof( - &self, - leaf_index: u64, - at: Option, - ) -> Result>; -} - -/// An implementation of MMR specific RPC methods. -pub struct Mmr { - client: Arc, - _marker: std::marker::PhantomData, -} - -impl Mmr { - /// Create new `Mmr` with the given reference to the client. - pub fn new(client: Arc) -> Self { - Self { client, _marker: Default::default() } - } -} - -impl MmrApi<::Hash> for Mmr -where - Block: BlockT, - C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, - C::Api: MmrRuntimeApi, - MmrHash: Codec + Send + Sync + 'static, -{ - fn generate_proof( - &self, - leaf_index: u64, - at: Option<::Hash>, - ) -> Result::Hash>> { - let api = self.client.runtime_api(); - let block_hash = at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash); - - let (leaf, proof) = api - .generate_proof_with_context( - &BlockId::hash(block_hash), - sp_core::ExecutionContext::OffchainCall(None), - leaf_index, - ) - .map_err(runtime_error_into_rpc_error)? - .map_err(mmr_error_into_rpc_error)?; - - Ok(LeafProof::new(block_hash, leaf, proof)) - } -} - -const RUNTIME_ERROR: i64 = 8000; -const MMR_ERROR: i64 = 8010; - -/// Converts a mmr-specific error into an RPC error. -fn mmr_error_into_rpc_error(err: MmrError) -> Error { - match err { - MmrError::LeafNotFound => Error { - code: ErrorCode::ServerError(MMR_ERROR + 1), - message: "Leaf was not found".into(), - data: Some(format!("{:?}", err).into()), - }, - MmrError::GenerateProof => Error { - code: ErrorCode::ServerError(MMR_ERROR + 2), - message: "Error while generating the proof".into(), - data: Some(format!("{:?}", err).into()), - }, - _ => Error { - code: ErrorCode::ServerError(MMR_ERROR), - message: "Unexpected MMR error".into(), - data: Some(format!("{:?}", err).into()), - }, - } -} - -/// Converts a runtime trap into an RPC error. -fn runtime_error_into_rpc_error(err: impl std::fmt::Debug) -> Error { - Error { - code: ErrorCode::ServerError(RUNTIME_ERROR), - message: "Runtime trapped".into(), - data: Some(format!("{:?}", err).into()), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_core::H256; - - #[test] - fn should_serialize_leaf_proof() { - // given - let leaf = vec![1_u8, 2, 3, 4]; - let proof = Proof { - leaf_index: 1, - leaf_count: 9, - items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], - }; - - let leaf_proof = LeafProof::new(H256::repeat_byte(0), leaf, proof); - - // when - let actual = serde_json::to_string(&leaf_proof).unwrap(); - - // then - assert_eq!( - actual, - r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaf":"0x1001020304","proof":"0x010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"# - ); - } - - #[test] - fn should_deserialize_leaf_proof() { - // given - let expected = LeafProof { - block_hash: H256::repeat_byte(0), - leaf: Bytes(vec![1_u8, 2, 3, 4].encode()), - proof: Bytes( - Proof { - leaf_index: 1, - leaf_count: 9, - items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], - } - .encode(), - ), - }; - - // when - let actual: LeafProof = serde_json::from_str(r#"{ - "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000", - "leaf":"0x1001020304", - "proof":"0x010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202" - }"#).unwrap(); - - // then - assert_eq!(actual, expected); - } -} diff --git a/frame/merkle-mountain-range/src/benchmarking.rs b/frame/merkle-mountain-range/src/benchmarking.rs index 2680b3d030067..d24364a55f9e6 100644 --- a/frame/merkle-mountain-range/src/benchmarking.rs +++ b/frame/merkle-mountain-range/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,17 +17,17 @@ //! Benchmarks for the MMR pallet. -#![cfg_attr(not(feature = "std"), no_std)] +#![cfg(feature = "runtime-benchmarks")] use crate::*; -use frame_benchmarking::{benchmarks_instance_pallet, impl_benchmark_test_suite}; +use frame_benchmarking::benchmarks_instance_pallet; use frame_support::traits::OnInitialize; benchmarks_instance_pallet! { on_initialize { let x in 1 .. 1_000; - let leaves = x as u64; + let leaves = x as NodeIndex; }: { for b in 0..leaves { Pallet::::on_initialize((b as u32).into()); @@ -35,6 +35,6 @@ benchmarks_instance_pallet! { } verify { assert_eq!(crate::NumberOfLeaves::::get(), leaves); } -} -impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::mock::Test); +} diff --git a/frame/merkle-mountain-range/src/default_weights.rs b/frame/merkle-mountain-range/src/default_weights.rs index 6308975ce7d22..e4f9750fbcba5 100644 --- a/frame/merkle-mountain-range/src/default_weights.rs +++ b/frame/merkle-mountain-range/src/default_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ //! This file was not auto-generated. use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_PER_NANOS}, + constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_NANOS}, Weight, }; @@ -28,9 +28,9 @@ impl crate::WeightInfo for () { // Reading the parent hash. let leaf_weight = DbWeight::get().reads(1); // Blake2 hash cost. - let hash_weight = 2 * WEIGHT_PER_NANOS; + let hash_weight = Weight::from_ref_time(2u64 * WEIGHT_REF_TIME_PER_NANOS); // No-op hook. - let hook_weight = 0; + let hook_weight = Weight::zero(); leaf_weight .saturating_add(hash_weight) diff --git a/frame/merkle-mountain-range/src/lib.rs b/frame/merkle-mountain-range/src/lib.rs index 01bf1b2254f09..46af84d218247 100644 --- a/frame/merkle-mountain-range/src/lib.rs +++ b/frame/merkle-mountain-range/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,9 +22,9 @@ //! Details on Merkle Mountain Ranges (MMRs) can be found here: //! //! -//! The MMR pallet constructs a MMR from leaf data obtained on every block from +//! The MMR pallet constructs an MMR from leaf data obtained on every block from //! `LeafDataProvider`. MMR nodes are stored both in: -//! - on-chain storage - hashes only; not full leaf content) +//! - on-chain storage - hashes only; not full leaf content; //! - off-chain storage - via Indexing API we push full leaf content (and all internal nodes as //! well) to the Off-chain DB, so that the data is available for Off-chain workers. //! Hashing used for MMR is configurable independently from the rest of the runtime (i.e. not using @@ -50,17 +50,26 @@ //! //! Secondary use case is to archive historical data, but still be able to retrieve them on-demand //! if needed. For instance if parent block hashes are stored in the MMR it's possible at any point -//! in time to provide a MMR proof about some past block hash, while this data can be safely pruned +//! in time to provide an MMR proof about some past block hash, while this data can be safely pruned //! from on-chain storage. //! //! NOTE This pallet is experimental and not proven to work in production. #![cfg_attr(not(feature = "std"), no_std)] -use codec::Encode; -use frame_support::weights::Weight; -use sp_runtime::traits; +use frame_support::{log, weights::Weight}; +use sp_mmr_primitives::utils; +use sp_runtime::{ + traits::{self, One, Saturating}, + SaturatedConversion, +}; +use sp_std::prelude::*; -#[cfg(any(feature = "runtime-benchmarks", test))] +pub use pallet::*; +pub use sp_mmr_primitives::{ + self as primitives, utils::NodesUtils, Error, LeafDataProvider, LeafIndex, NodeIndex, +}; + +#[cfg(feature = "runtime-benchmarks")] mod benchmarking; mod default_weights; mod mmr; @@ -69,13 +78,42 @@ mod mock; #[cfg(test)] mod tests; -pub use pallet::*; -pub use pallet_mmr_primitives as primitives; +/// The most common use case for MMRs is to store historical block hashes, +/// so that any point in time in the future we can receive a proof about some past +/// blocks without using excessive on-chain storage. +/// +/// Hence we implement the [LeafDataProvider] for [ParentNumberAndHash] which is a +/// crate-local wrapper over [frame_system::Pallet]. Since the current block hash +/// is not available (since the block is not finished yet), +/// we use the `parent_hash` here along with parent block number. +pub struct ParentNumberAndHash { + _phanthom: sp_std::marker::PhantomData, +} + +impl LeafDataProvider for ParentNumberAndHash { + type LeafData = (::BlockNumber, ::Hash); + + fn leaf_data() -> Self::LeafData { + ( + frame_system::Pallet::::block_number().saturating_sub(One::one()), + frame_system::Pallet::::parent_hash(), + ) + } +} pub trait WeightInfo { - fn on_initialize(peaks: u64) -> Weight; + fn on_initialize(peaks: NodeIndex) -> Weight; } +/// An MMR specific to the pallet. +type ModuleMmr = mmr::Mmr>; + +/// Leaf data. +type LeafOf = <>::LeafData as primitives::LeafDataProvider>::LeafData; + +/// Hashing used for the pallet. +pub(crate) type HashingOf = >::Hashing; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -92,12 +130,12 @@ pub mod pallet { /// Prefix for elements stored in the Off-chain DB via Indexing API. /// /// Each node of the MMR is inserted both on-chain and off-chain via Indexing API. - /// The former does not store full leaf content, just it's compact version (hash), + /// The former does not store full leaf content, just its compact version (hash), /// and some of the inner mmr nodes might be pruned from on-chain storage. /// The latter will contain all the entries in their full form. /// /// Each node is stored in the Off-chain DB under key derived from the - /// [`Self::INDEXING_PREFIX`] and it's in-tree index (MMR position). + /// [`Self::INDEXING_PREFIX`] and its in-tree index (MMR position). const INDEXING_PREFIX: &'static [u8]; /// A hasher type for MMR. @@ -125,7 +163,8 @@ pub mod pallet { + Default + codec::Codec + codec::EncodeLike - + scale_info::TypeInfo; + + scale_info::TypeInfo + + MaxEncodedLen; /// Data stored in the leaf nodes. /// @@ -137,6 +176,12 @@ pub mod pallet { /// /// Note that the leaf at each block MUST be unique. You may want to include a block hash or /// block number as an easiest way to ensure that. + /// Also note that the leaf added by each block is expected to only reference data coming + /// from ancestor blocks (leaves are saved offchain using `(pos, parent_hash)` key to be + /// fork-resistant, as such conflicts could only happen on 1-block deep forks, which means + /// two forks with identical line of ancestors compete to write the same offchain key, but + /// that's fine as long as leaves only contain data coming from ancestors - conflicting + /// writes are identical). type LeafData: primitives::LeafDataProvider; /// A hook to act on the new MMR root. @@ -160,7 +205,7 @@ pub mod pallet { /// Current size of the MMR (number of leaves). #[pallet::storage] #[pallet::getter(fn mmr_leaves)] - pub type NumberOfLeaves = StorageValue<_, u64, ValueQuery>; + pub type NumberOfLeaves = StorageValue<_, LeafIndex, ValueQuery>; /// Hashes of the nodes in the MMR. /// @@ -169,57 +214,60 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn mmr_peak)] pub type Nodes, I: 'static = ()> = - StorageMap<_, Identity, u64, >::Hash, OptionQuery>; + StorageMap<_, Identity, NodeIndex, >::Hash, OptionQuery>; #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(_n: T::BlockNumber) -> Weight { use primitives::LeafDataProvider; let leaves = Self::mmr_leaves(); - let peaks_before = mmr::utils::NodesUtils::new(leaves).number_of_peaks(); + let peaks_before = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); let data = T::LeafData::leaf_data(); + // append new leaf to MMR let mut mmr: ModuleMmr = mmr::Mmr::new(leaves); - mmr.push(data).expect("MMR push never fails."); - - // update the size - let (leaves, root) = mmr.finalize().expect("MMR finalize never fails."); + // MMR push never fails, but better safe than sorry. + if mmr.push(data).is_none() { + log::error!(target: "runtime::mmr", "MMR push failed"); + return T::WeightInfo::on_initialize(peaks_before) + } + // Update the size, `mmr.finalize()` should also never fail. + let (leaves, root) = match mmr.finalize() { + Ok((leaves, root)) => (leaves, root), + Err(e) => { + log::error!(target: "runtime::mmr", "MMR finalize failed: {:?}", e); + return T::WeightInfo::on_initialize(peaks_before) + }, + }; >::on_new_root(&root); >::put(leaves); >::put(root); - let peaks_after = mmr::utils::NodesUtils::new(leaves).number_of_peaks(); + let peaks_after = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); + T::WeightInfo::on_initialize(peaks_before.max(peaks_after)) } } } -/// A MMR specific to the pallet. -type ModuleMmr = mmr::Mmr>; - -/// Leaf data. -type LeafOf = <>::LeafData as primitives::LeafDataProvider>::LeafData; - -/// Hashing used for the pallet. -pub(crate) type HashingOf = >::Hashing; - -/// Stateless MMR proof verification. +/// Stateless MMR proof verification for batch of leaves. /// -/// This function can be used to verify received MMR proof (`proof`) -/// for given leaf data (`leaf`) against a known MMR root hash (`root`). -/// -/// The verification does not require any storage access. -pub fn verify_leaf_proof( +/// This function can be used to verify received MMR [primitives::Proof] (`proof`) +/// for given leaves set (`leaves`) against a known MMR root hash (`root`). +/// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the +/// same position in both the `leaves` vector and the `leaf_indices` vector contained in the +/// [primitives::Proof]. +pub fn verify_leaves_proof( root: H::Output, - leaf: mmr::Node, + leaves: Vec>, proof: primitives::Proof, ) -> Result<(), primitives::Error> where H: traits::Hash, L: primitives::FullLeaf, { - let is_valid = mmr::verify_leaf_proof::(root, leaf, proof)?; + let is_valid = mmr::verify_leaves_proof::(root, leaves, proof)?; if is_valid { Ok(()) } else { @@ -228,43 +276,114 @@ where } impl, I: 'static> Pallet { - fn offchain_key(pos: u64) -> sp_std::prelude::Vec { - (T::INDEXING_PREFIX, pos).encode() + /// Build offchain key from `parent_hash` of block that originally added node `pos` to MMR. + /// + /// This combination makes the offchain (key,value) entry resilient to chain forks. + fn node_temp_offchain_key( + pos: NodeIndex, + parent_hash: ::Hash, + ) -> sp_std::prelude::Vec { + NodesUtils::node_temp_offchain_key::<::Header>( + &T::INDEXING_PREFIX, + pos, + parent_hash, + ) } - /// Generate a MMR proof for the given `leaf_index`. + /// Build canonical offchain key for node `pos` in MMR. + /// + /// Used for nodes added by now finalized blocks. + /// Never read keys using `node_canon_offchain_key` unless you sure that + /// there's no `node_offchain_key` key in the storage. + fn node_canon_offchain_key(pos: NodeIndex) -> sp_std::prelude::Vec { + NodesUtils::node_canon_offchain_key(&T::INDEXING_PREFIX, pos) + } + + /// Provide the parent number for the block that added `leaf_index` to the MMR. + fn leaf_index_to_parent_block_num( + leaf_index: LeafIndex, + leaves_count: LeafIndex, + ) -> ::BlockNumber { + // leaves are zero-indexed and were added one per block since pallet activation, + // while block numbers are one-indexed, so block number that added `leaf_idx` is: + // `block_num = block_num_when_pallet_activated + leaf_idx + 1` + // `block_num = (current_block_num - leaves_count) + leaf_idx + 1` + // `parent_block_num = current_block_num - leaves_count + leaf_idx`. + >::block_number() + .saturating_sub(leaves_count.saturated_into()) + .saturating_add(leaf_index.saturated_into()) + } + + /// Convert a block number into a leaf index. + fn block_num_to_leaf_index(block_num: T::BlockNumber) -> Result + where + T: frame_system::Config, + { + let first_mmr_block = utils::first_mmr_block_num::( + >::block_number(), + Self::mmr_leaves(), + )?; + + utils::block_num_to_leaf_index::(block_num, first_mmr_block) + } + + /// Generate an MMR proof for the given `block_numbers`. + /// If `best_known_block_number = Some(n)`, this generates a historical proof for + /// the chain with head at height `n`. + /// Else it generates a proof for the MMR at the current block height. /// /// Note this method can only be used from an off-chain context /// (Offchain Worker or Runtime API call), since it requires /// all the leaves to be present. /// It may return an error or panic if used incorrectly. pub fn generate_proof( - leaf_index: u64, - ) -> Result<(LeafOf, primitives::Proof<>::Hash>), primitives::Error> { - let mmr: ModuleMmr = mmr::Mmr::new(Self::mmr_leaves()); - mmr.generate_proof(leaf_index) + block_numbers: Vec, + best_known_block_number: Option, + ) -> Result<(Vec>, primitives::Proof<>::Hash>), primitives::Error> { + // check whether best_known_block_number provided, else use current best block + let best_known_block_number = + best_known_block_number.unwrap_or_else(|| >::block_number()); + + let leaves_count = + Self::block_num_to_leaf_index(best_known_block_number)?.saturating_add(1); + + // we need to translate the block_numbers into leaf indices. + let leaf_indices = block_numbers + .iter() + .map(|block_num| -> Result { + Self::block_num_to_leaf_index(*block_num) + }) + .collect::, _>>()?; + + let mmr: ModuleMmr = mmr::Mmr::new(leaves_count); + mmr.generate_proof(leaf_indices) + } + + /// Return the on-chain MMR root hash. + pub fn mmr_root() -> >::Hash { + Self::mmr_root_hash() } - /// Verify MMR proof for given `leaf`. + /// Verify MMR proof for given `leaves`. /// /// This method is safe to use within the runtime code. /// It will return `Ok(())` if the proof is valid /// and an `Err(..)` if MMR is inconsistent (some leaves are missing) /// or the proof is invalid. - pub fn verify_leaf( - leaf: LeafOf, + pub fn verify_leaves( + leaves: Vec>, proof: primitives::Proof<>::Hash>, ) -> Result<(), primitives::Error> { if proof.leaf_count > Self::mmr_leaves() || proof.leaf_count == 0 || - proof.items.len() as u32 > mmr::utils::NodesUtils::new(proof.leaf_count).depth() + (proof.items.len().saturating_add(leaves.len())) as u64 > proof.leaf_count { return Err(primitives::Error::Verify .log_debug("The proof has incorrect number of leaves or proof items.")) } - let mmr: ModuleMmr = mmr::Mmr::new(proof.leaf_count); - let is_valid = mmr.verify_leaf_proof(leaf, proof)?; + let mmr: ModuleMmr = mmr::Mmr::new(proof.leaf_count); + let is_valid = mmr.verify_leaves_proof(leaves, proof)?; if is_valid { Ok(()) } else { diff --git a/frame/merkle-mountain-range/src/mmr/mmr.rs b/frame/merkle-mountain-range/src/mmr/mmr.rs index d5036e58f432e..cdb189a14b66d 100644 --- a/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,19 +18,21 @@ use crate::{ mmr::{ storage::{OffchainStorage, RuntimeStorage, Storage}, - utils::NodesUtils, Hasher, Node, NodeOf, }, - primitives::{self, Error}, + primitives::{self, Error, NodeIndex}, Config, HashingOf, }; -#[cfg(not(feature = "std"))] -use sp_std::vec; +use sp_mmr_primitives::{mmr_lib, utils::NodesUtils}; +use sp_std::prelude::*; -/// Stateless verification of the leaf proof. -pub fn verify_leaf_proof( +/// Stateless verification of the proof for a batch of leaves. +/// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the +/// same position in both the `leaves` vector and the `leaf_indices` vector contained in the +/// [primitives::Proof] +pub fn verify_leaves_proof( root: H::Output, - leaf: Node, + leaves: Vec>, proof: primitives::Proof, ) -> Result where @@ -38,17 +40,27 @@ where L: primitives::FullLeaf, { let size = NodesUtils::new(proof.leaf_count).size(); - let leaf_position = mmr_lib::leaf_index_to_pos(proof.leaf_index); + + if leaves.len() != proof.leaf_indices.len() { + return Err(Error::Verify.log_debug("Proof leaf_indices not same length with leaves")) + } + + let leaves_and_position_data = proof + .leaf_indices + .into_iter() + .map(|index| mmr_lib::leaf_index_to_pos(index)) + .zip(leaves.into_iter()) + .collect(); let p = mmr_lib::MerkleProof::, Hasher>::new( size, proof.items.into_iter().map(Node::Hash).collect(), ); - p.verify(Node::Hash(root), vec![(leaf_position, leaf)]) + p.verify(Node::Hash(root), leaves_and_position_data) .map_err(|e| Error::Verify.log_debug(e)) } -/// A wrapper around a MMR library to expose limited functionality. +/// A wrapper around an MMR library to expose limited functionality. /// /// Available functions depend on the storage kind ([Runtime](crate::mmr::storage::RuntimeStorage) /// vs [Off-chain](crate::mmr::storage::OffchainStorage)). @@ -60,7 +72,7 @@ where Storage: mmr_lib::MMRStore>, { mmr: mmr_lib::MMR, Hasher, L>, Storage>, - leaves: u64, + leaves: NodeIndex, } impl Mmr @@ -71,30 +83,43 @@ where Storage: mmr_lib::MMRStore>, { /// Create a pointer to an existing MMR with given number of leaves. - pub fn new(leaves: u64) -> Self { + pub fn new(leaves: NodeIndex) -> Self { let size = NodesUtils::new(leaves).size(); Self { mmr: mmr_lib::MMR::new(size, Default::default()), leaves } } - /// Verify proof of a single leaf. - pub fn verify_leaf_proof( + /// Verify proof for a set of leaves. + /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have + /// the same position in both the `leaves` vector and the `leaf_indices` vector contained in the + /// [primitives::Proof] + pub fn verify_leaves_proof( &self, - leaf: L, + leaves: Vec, proof: primitives::Proof<>::Hash>, ) -> Result { let p = mmr_lib::MerkleProof::, Hasher, L>>::new( self.mmr.mmr_size(), proof.items.into_iter().map(Node::Hash).collect(), ); - let position = mmr_lib::leaf_index_to_pos(proof.leaf_index); + + if leaves.len() != proof.leaf_indices.len() { + return Err(Error::Verify.log_debug("Proof leaf_indices not same length with leaves")) + } + + let leaves_positions_and_data = proof + .leaf_indices + .into_iter() + .map(|index| mmr_lib::leaf_index_to_pos(index)) + .zip(leaves.into_iter().map(|leaf| Node::Data(leaf))) + .collect(); let root = self.mmr.get_root().map_err(|e| Error::GetRoot.log_error(e))?; - p.verify(root, vec![(position, Node::Data(leaf))]) + p.verify(root, leaves_positions_and_data) .map_err(|e| Error::Verify.log_debug(e)) } /// Return the internal size of the MMR (number of nodes). #[cfg(test)] - pub fn size(&self) -> u64 { + pub fn size(&self) -> NodeIndex { self.mmr.mmr_size() } } @@ -109,7 +134,7 @@ where /// Push another item to the MMR. /// /// Returns element position (index) in the MMR. - pub fn push(&mut self, leaf: L) -> Option { + pub fn push(&mut self, leaf: L) -> Option { let position = self.mmr.push(Node::Data(leaf)).map_err(|e| Error::Push.log_error(e)).ok()?; @@ -120,7 +145,7 @@ where /// Commit the changes to underlying storage, return current number of leaves and /// calculate the new MMR's root hash. - pub fn finalize(self) -> Result<(u64, >::Hash), Error> { + pub fn finalize(self) -> Result<(NodeIndex, >::Hash), Error> { let root = self.mmr.get_root().map_err(|e| Error::GetRoot.log_error(e))?; self.mmr.commit().map_err(|e| Error::Commit.log_error(e))?; Ok((self.leaves, root.hash())) @@ -134,29 +159,36 @@ where I: 'static, L: primitives::FullLeaf + codec::Decode, { - /// Generate a proof for given leaf index. + /// Generate a proof for given leaf indices. /// /// Proof generation requires all the nodes (or their hashes) to be available in the storage. /// (i.e. you can't run the function in the pruned storage). pub fn generate_proof( &self, - leaf_index: u64, - ) -> Result<(L, primitives::Proof<>::Hash>), Error> { - let position = mmr_lib::leaf_index_to_pos(leaf_index); + leaf_indices: Vec, + ) -> Result<(Vec, primitives::Proof<>::Hash>), Error> { + let positions = leaf_indices + .iter() + .map(|index| mmr_lib::leaf_index_to_pos(*index)) + .collect::>(); let store = >::default(); - let leaf = match mmr_lib::MMRStore::get_elem(&store, position) { - Ok(Some(Node::Data(leaf))) => leaf, - e => return Err(Error::LeafNotFound.log_debug(e)), - }; + let leaves = positions + .iter() + .map(|pos| match mmr_lib::MMRStore::get_elem(&store, *pos) { + Ok(Some(Node::Data(leaf))) => Ok(leaf), + e => Err(Error::LeafNotFound.log_debug(e)), + }) + .collect::, Error>>()?; + let leaf_count = self.leaves; self.mmr - .gen_proof(vec![position]) + .gen_proof(positions) .map_err(|e| Error::GenerateProof.log_error(e)) .map(|p| primitives::Proof { - leaf_index, + leaf_indices, leaf_count, items: p.proof_items().iter().map(|x| x.hash()).collect(), }) - .map(|p| (leaf, p)) + .map(|p| (leaves, p)) } } diff --git a/frame/merkle-mountain-range/src/mmr/mod.rs b/frame/merkle-mountain-range/src/mmr/mod.rs index ec2dfe245bd41..51de294753151 100644 --- a/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/frame/merkle-mountain-range/src/mmr/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,18 +17,17 @@ mod mmr; pub mod storage; -pub mod utils; -use crate::primitives::FullLeaf; +use sp_mmr_primitives::{mmr_lib, DataOrHash, FullLeaf}; use sp_runtime::traits; -pub use self::mmr::{verify_leaf_proof, Mmr}; +pub use self::mmr::{verify_leaves_proof, Mmr}; /// Node type for runtime `T`. pub type NodeOf = Node<>::Hashing, L>; /// A node stored in the MMR. -pub type Node = crate::primitives::DataOrHash; +pub type Node = DataOrHash; /// Default Merging & Hashing behavior for MMR. pub struct Hasher(sp_std::marker::PhantomData<(H, L)>); @@ -36,10 +35,10 @@ pub struct Hasher(sp_std::marker::PhantomData<(H, L)>); impl mmr_lib::Merge for Hasher { type Item = Node; - fn merge(left: &Self::Item, right: &Self::Item) -> Self::Item { + fn merge(left: &Self::Item, right: &Self::Item) -> mmr_lib::Result { let mut concat = left.hash().as_ref().to_vec(); concat.extend_from_slice(right.hash().as_ref()); - Node::Hash(::hash(&concat)) + Ok(Node::Hash(::hash(&concat))) } } diff --git a/frame/merkle-mountain-range/src/mmr/storage.rs b/frame/merkle-mountain-range/src/mmr/storage.rs index 09e24017816ec..1f5d01f87e273 100644 --- a/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/frame/merkle-mountain-range/src/mmr/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,22 +15,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! A MMR storage implementations. +//! An MMR storage implementation. use codec::Encode; +use frame_support::log::{debug, trace}; +use sp_core::offchain::StorageKind; +use sp_io::offchain_index; +use sp_mmr_primitives::{mmr_lib, mmr_lib::helper, utils::NodesUtils}; +use sp_std::iter::Peekable; #[cfg(not(feature = "std"))] -use sp_std::prelude::Vec; +use sp_std::prelude::*; use crate::{ mmr::{Node, NodeOf}, - primitives, Config, Nodes, NumberOfLeaves, Pallet, + primitives::{self, NodeIndex}, + Config, Nodes, NumberOfLeaves, Pallet, }; /// A marker type for runtime-specific storage implementation. /// /// Allows appending new items to the MMR and proof verification. /// MMR nodes are appended to two different storages: -/// 1. We add nodes (leaves) hashes to the on-chain storge (see [crate::Nodes]). +/// 1. We add nodes (leaves) hashes to the on-chain storage (see [crate::Nodes]). /// 2. We add full leaves (and all inner nodes as well) into the `IndexingAPI` during block /// processing, so the values end up in the Offchain DB if indexing is enabled. pub struct RuntimeStorage; @@ -54,20 +60,54 @@ impl Default for Storage { } } +impl Storage +where + T: Config, + I: 'static, + L: primitives::FullLeaf, +{ +} + impl mmr_lib::MMRStore> for Storage where T: Config, I: 'static, L: primitives::FullLeaf + codec::Decode, { - fn get_elem(&self, pos: u64) -> mmr_lib::Result>> { - let key = Pallet::::offchain_key(pos); + fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result>> { + let leaves = NumberOfLeaves::::get(); + // Find out which leaf added node `pos` in the MMR. + let ancestor_leaf_idx = NodesUtils::leaf_index_that_added_node(pos); + + // We should only get here when trying to generate proofs. The client requests + // for proofs for finalized blocks, which should usually be already canonicalized, + // unless the MMR client gadget has a delay. + let key = Pallet::::node_canon_offchain_key(pos); + debug!( + target: "runtime::mmr::offchain", "offchain db get {}: leaf idx {:?}, canon key {:?}", + pos, ancestor_leaf_idx, key + ); + // Try to retrieve the element from Off-chain DB. + if let Some(elem) = sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) { + return Ok(codec::Decode::decode(&mut &*elem).ok()) + } + + // 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 temp_key = Pallet::::node_temp_offchain_key(pos, ancestor_parent_hash); + debug!( + target: "runtime::mmr::offchain", + "offchain db get {}: leaf idx {:?}, hash {:?}, temp key {:?}", + pos, ancestor_leaf_idx, ancestor_parent_hash, temp_key + ); // Retrieve the element from Off-chain DB. - Ok(sp_io::offchain::local_storage_get(sp_core::offchain::StorageKind::PERSISTENT, &key) + Ok(sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &temp_key) .and_then(|v| codec::Decode::decode(&mut &*v).ok())) } - fn append(&mut self, _: u64, _: Vec>) -> mmr_lib::Result<()> { + fn append(&mut self, _: NodeIndex, _: Vec>) -> mmr_lib::Result<()> { panic!("MMR must not be altered in the off-chain context.") } } @@ -78,32 +118,114 @@ where I: 'static, L: primitives::FullLeaf, { - fn get_elem(&self, pos: u64) -> mmr_lib::Result>> { + fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result>> { Ok(>::get(pos).map(Node::Hash)) } - fn append(&mut self, pos: u64, elems: Vec>) -> mmr_lib::Result<()> { - let mut leaves = crate::NumberOfLeaves::::get(); - let mut size = crate::mmr::utils::NodesUtils::new(leaves).size(); + fn append(&mut self, pos: NodeIndex, elems: Vec>) -> mmr_lib::Result<()> { + if elems.is_empty() { + return Ok(()) + } + + trace!( + target: "runtime::mmr", "elems: {:?}", + elems.iter().map(|elem| elem.hash()).collect::>() + ); + + let leaves = NumberOfLeaves::::get(); + let size = NodesUtils::new(leaves).size(); + if pos != size { return Err(mmr_lib::Error::InconsistentStore) } + let new_size = size + elems.len() as NodeIndex; + + // A sorted (ascending) iterator over peak indices to prune and persist. + let (peaks_to_prune, mut peaks_to_store) = peaks_to_prune_and_store(size, new_size); + + // Now we are going to iterate over elements to insert + // and keep track of the current `node_index` and `leaf_index`. + let mut leaf_index = leaves; + let mut node_index = size; + + // Use parent hash of block adding new nodes (this block) as extra identifier + // in offchain DB to avoid DB collisions and overwrites in case of forks. + let parent_hash = >::parent_hash(); for elem in elems { - // on-chain we only store the hash (even if it's a leaf) - >::insert(size, elem.hash()); - // Indexing API is used to store the full leaf content. - let key = Pallet::::offchain_key(size); - elem.using_encoded(|elem| sp_io::offchain_index::set(&key, elem)); - size += 1; + // On-chain we are going to only store new peaks. + if peaks_to_store.next_if_eq(&node_index).is_some() { + >::insert(node_index, elem.hash()); + } + // We are storing full node off-chain (using indexing API). + Self::store_to_offchain(node_index, parent_hash, &elem); + // Increase the indices. if let Node::Data(..) = elem { - leaves += 1; + leaf_index += 1; } + node_index += 1; } - NumberOfLeaves::::put(leaves); + // Update current number of leaves. + NumberOfLeaves::::put(leaf_index); + + // And remove all remaining items from `peaks_before` collection. + for pos in peaks_to_prune { + >::remove(pos); + } Ok(()) } } + +impl Storage +where + T: Config, + I: 'static, + L: primitives::FullLeaf, +{ + fn store_to_offchain( + pos: NodeIndex, + parent_hash: ::Hash, + node: &NodeOf, + ) { + let encoded_node = node.encode(); + // We store this leaf offchain keyed by `(parent_hash, node_index)` to make it + // fork-resistant. The MMR client gadget task will "canonicalize" it on the first + // finality notification that follows, when we are not worried about forks anymore. + let temp_key = Pallet::::node_temp_offchain_key(pos, parent_hash); + debug!( + target: "runtime::mmr::offchain", "offchain db set: pos {} parent_hash {:?} key {:?}", + pos, parent_hash, temp_key + ); + // Indexing API is used to store the full node content. + offchain_index::set(&temp_key, &encoded_node); + } +} + +fn peaks_to_prune_and_store( + old_size: NodeIndex, + new_size: NodeIndex, +) -> (impl Iterator, Peekable>) { + // A sorted (ascending) collection of peak indices before and after insertion. + // both collections may share a common prefix. + let peaks_before = if old_size == 0 { vec![] } else { helper::get_peaks(old_size) }; + let peaks_after = helper::get_peaks(new_size); + trace!(target: "runtime::mmr", "peaks_before: {:?}", peaks_before); + trace!(target: "runtime::mmr", "peaks_after: {:?}", peaks_after); + let mut peaks_before = peaks_before.into_iter().peekable(); + let mut peaks_after = peaks_after.into_iter().peekable(); + + // Consume a common prefix between `peaks_before` and `peaks_after`, + // since that's something we will not be touching anyway. + while peaks_before.peek() == peaks_after.peek() { + peaks_before.next(); + peaks_after.next(); + } + + // what's left in both collections is: + // 1. Old peaks to remove from storage + // 2. New peaks to persist in storage + (peaks_before, peaks_after) +} diff --git a/frame/merkle-mountain-range/src/mmr/utils.rs b/frame/merkle-mountain-range/src/mmr/utils.rs deleted file mode 100644 index 8fc725f11e72f..0000000000000 --- a/frame/merkle-mountain-range/src/mmr/utils.rs +++ /dev/null @@ -1,126 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 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. - -//! Merkle Mountain Range utilities. - -/// MMR nodes & size -related utilities. -pub struct NodesUtils { - no_of_leaves: u64, -} - -impl NodesUtils { - /// Create new instance of MMR nodes utilities for given number of leaves. - pub fn new(no_of_leaves: u64) -> Self { - Self { no_of_leaves } - } - - /// Calculate number of peaks in the MMR. - pub fn number_of_peaks(&self) -> u64 { - self.number_of_leaves().count_ones() as u64 - } - - /// Return the number of leaves in the MMR. - pub fn number_of_leaves(&self) -> u64 { - self.no_of_leaves - } - - /// Calculate the total size of MMR (number of nodes). - pub fn size(&self) -> u64 { - 2 * self.no_of_leaves - self.number_of_peaks() - } - - /// Calculate maximal depth of the MMR. - pub fn depth(&self) -> u32 { - if self.no_of_leaves == 0 { - return 0 - } - - 64 - self.no_of_leaves.next_power_of_two().leading_zeros() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn should_calculate_number_of_leaves_correctly() { - assert_eq!( - vec![0, 1, 2, 3, 4, 9, 15, 21] - .into_iter() - .map(|n| NodesUtils::new(n).depth()) - .collect::>(), - vec![0, 1, 2, 3, 3, 5, 5, 6] - ); - } - - #[test] - fn should_calculate_depth_correclty() { - assert_eq!( - vec![0, 1, 2, 3, 4, 9, 15, 21] - .into_iter() - .map(|n| NodesUtils::new(n).number_of_leaves()) - .collect::>(), - vec![0, 1, 2, 3, 4, 9, 15, 21] - ); - } - - #[test] - fn should_calculate_number_of_peaks_correctly() { - assert_eq!( - vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21] - .into_iter() - .map(|n| NodesUtils::new(n).number_of_peaks()) - .collect::>(), - vec![0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 3] - ); - } - - #[test] - fn should_calculate_the_size_correctly() { - let _ = env_logger::try_init(); - - let leaves = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21]; - let sizes = vec![0, 1, 3, 4, 7, 8, 10, 11, 15, 16, 18, 19, 22, 23, 25, 26, 39]; - assert_eq!( - leaves - .clone() - .into_iter() - .map(|n| NodesUtils::new(n).size()) - .collect::>(), - sizes.clone() - ); - - // size cross-check - let mut actual_sizes = vec![]; - for s in &leaves[1..] { - crate::tests::new_test_ext().execute_with(|| { - let mut mmr = crate::mmr::Mmr::< - crate::mmr::storage::RuntimeStorage, - crate::mock::Test, - _, - _, - >::new(0); - for i in 0..*s { - mmr.push(i); - } - actual_sizes.push(mmr.size()); - }) - } - assert_eq!(sizes[1..], actual_sizes[..]); - } -} diff --git a/frame/merkle-mountain-range/src/mock.rs b/frame/merkle-mountain-range/src/mock.rs index 3616a8d1d5242..3fd44275857c1 100644 --- a/frame/merkle-mountain-range/src/mock.rs +++ b/frame/merkle-mountain-range/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,14 +19,16 @@ use crate as pallet_mmr; use crate::*; use codec::{Decode, Encode}; -use frame_support::parameter_types; -use pallet_mmr_primitives::{Compact, LeafDataProvider}; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; use sp_core::H256; +use sp_mmr_primitives::{Compact, LeafDataProvider}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup, Keccak256}, }; -use sp_std::{cell::RefCell, prelude::*}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -42,13 +44,10 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub const BlockHashCount: u64 = 250; -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -56,8 +55,8 @@ impl frame_system::Config for Test { type AccountId = sp_core::sr25519::Public; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type DbWeight = (); type BlockWeights = (); type BlockLength = (); @@ -69,6 +68,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl Config for Test { @@ -76,7 +76,7 @@ impl Config for Test { type Hashing = Keccak256; type Hash = H256; - type LeafData = Compact, LeafData)>; + type LeafData = Compact, LeafData)>; type OnNewRoot = (); type WeightInfo = (); } @@ -93,14 +93,14 @@ impl LeafData { } } -thread_local! { - pub static LEAF_DATA: RefCell = RefCell::new(Default::default()); +parameter_types! { + pub static LeafDataTestValue: LeafData = Default::default(); } impl LeafDataProvider for LeafData { type LeafData = Self; fn leaf_data() -> Self::LeafData { - LEAF_DATA.with(|r| r.borrow().clone()) + LeafDataTestValue::get().clone() } } diff --git a/frame/merkle-mountain-range/src/tests.rs b/frame/merkle-mountain-range/src/tests.rs index 50512e9286951..b5f9a78ede010 100644 --- a/frame/merkle-mountain-range/src/tests.rs +++ b/frame/merkle-mountain-range/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,12 +17,12 @@ use crate::{mock::*, *}; -use frame_support::traits::OnInitialize; -use pallet_mmr_primitives::{Compact, Proof}; +use frame_support::traits::{Get, OnInitialize}; use sp_core::{ offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, H256, }; +use sp_mmr_primitives::{mmr_lib::helper, utils, Compact, Proof}; pub(crate) fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::default().build_storage::().unwrap().into() @@ -34,20 +34,21 @@ fn register_offchain_ext(ext: &mut sp_io::TestExternalities) { ext.register_extension(OffchainWorkerExt::new(offchain)); } -fn new_block() -> u64 { +fn new_block() -> Weight { let number = frame_system::Pallet::::block_number() + 1; let hash = H256::repeat_byte(number as u8); - LEAF_DATA.with(|r| r.borrow_mut().a = number); + LeafDataTestValue::mutate(|r| r.a = number); - frame_system::Pallet::::initialize( - &number, - &hash, - &Default::default(), - frame_system::InitKind::Full, - ); + frame_system::Pallet::::reset_events(); + frame_system::Pallet::::initialize(&number, &hash, &Default::default()); MMR::on_initialize(number) } +fn peaks_from_leaves_count(leaves_count: NodeIndex) -> Vec { + let size = utils::NodesUtils::new(leaves_count).size(); + helper::get_peaks(size) +} + pub(crate) fn hex(s: &str) -> H256 { s.parse().unwrap() } @@ -70,7 +71,7 @@ fn decode_node( } } -fn init_chain(blocks: usize) { +fn add_blocks(blocks: usize) { // given for _ in 0..blocks { new_block(); @@ -104,7 +105,7 @@ fn should_start_empty() { crate::RootHash::::get(), hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0") ); - assert!(weight != 0); + assert!(weight != Weight::zero()); }); } @@ -112,13 +113,34 @@ fn should_start_empty() { fn should_append_to_mmr_when_on_initialize_is_called() { let _ = env_logger::try_init(); let mut ext = new_test_ext(); - ext.execute_with(|| { + let (parent_b1, parent_b2) = ext.execute_with(|| { // when new_block(); + let parent_b1 = >::parent_hash(); + + // then + assert_eq!(crate::NumberOfLeaves::::get(), 1); + assert_eq!( + ( + crate::Nodes::::get(0), + crate::Nodes::::get(1), + crate::RootHash::::get(), + ), + ( + Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")), + None, + hex("0x4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0"), + ) + ); + + // when new_block(); + let parent_b2 = >::parent_hash(); // then assert_eq!(crate::NumberOfLeaves::::get(), 2); + let peaks = peaks_from_leaves_count(2); + assert_eq!(peaks, vec![2]); assert_eq!( ( crate::Nodes::::get(0), @@ -128,33 +150,42 @@ fn should_append_to_mmr_when_on_initialize_is_called() { crate::RootHash::::get(), ), ( - Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")), - Some(hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705")), + None, + None, Some(hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854")), None, hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"), ) ); - }); + (parent_b1, parent_b2) + }); // make sure the leaves end up in the offchain DB ext.persist_offchain_overlay(); + let offchain_db = ext.offchain_db(); + + let expected = Some(mmr::Node::Data(((0, H256::repeat_byte(1)), LeafData::new(1)))); assert_eq!( - offchain_db.get(&MMR::offchain_key(0)).map(decode_node), - Some(mmr::Node::Data(((0, H256::repeat_byte(1)), LeafData::new(1),))) + offchain_db.get(&MMR::node_temp_offchain_key(0, parent_b1)).map(decode_node), + expected ); + + let expected = Some(mmr::Node::Data(((1, H256::repeat_byte(2)), LeafData::new(2)))); assert_eq!( - offchain_db.get(&MMR::offchain_key(1)).map(decode_node), - Some(mmr::Node::Data(((1, H256::repeat_byte(2)), LeafData::new(2),))) + offchain_db.get(&MMR::node_temp_offchain_key(1, parent_b2)).map(decode_node), + expected ); + + let expected = Some(mmr::Node::Hash(hex( + "672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854", + ))); assert_eq!( - offchain_db.get(&MMR::offchain_key(2)).map(decode_node), - Some(mmr::Node::Hash(hex( - "672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854" - ))) + offchain_db.get(&MMR::node_temp_offchain_key(2, parent_b2)).map(decode_node), + expected ); - assert_eq!(offchain_db.get(&MMR::offchain_key(3)), None); + + assert_eq!(offchain_db.get(&MMR::node_temp_offchain_key(3, parent_b2)), None); } #[test] @@ -162,18 +193,25 @@ fn should_construct_larger_mmr_correctly() { let _ = env_logger::try_init(); new_test_ext().execute_with(|| { // when - init_chain(7); + add_blocks(7); // then assert_eq!(crate::NumberOfLeaves::::get(), 7); + let peaks = peaks_from_leaves_count(7); + assert_eq!(peaks, vec![6, 9, 10]); + for i in (0..=10).filter(|p| !peaks.contains(p)) { + assert!(crate::Nodes::::get(i).is_none()); + } assert_eq!( ( - crate::Nodes::::get(0), + crate::Nodes::::get(6), + crate::Nodes::::get(9), crate::Nodes::::get(10), crate::RootHash::::get(), ), ( - Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")), + Some(hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252")), + Some(hex("7e4316ae2ebf7c3b6821cb3a46ca8b7a4f9351a9b40fcf014bb0a4fd8e8f29da")), Some(hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c")), hex("e45e25259f7930626431347fa4dd9aae7ac83b4966126d425ca70ab343709d2c"), ) @@ -181,31 +219,71 @@ fn should_construct_larger_mmr_correctly() { }); } +#[test] +fn should_calculate_the_size_correctly() { + let _ = env_logger::try_init(); + + let leaves = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21]; + let sizes = vec![0, 1, 3, 4, 7, 8, 10, 11, 15, 16, 18, 19, 22, 23, 25, 26, 39]; + + // size cross-check + let mut actual_sizes = vec![]; + for s in &leaves[1..] { + new_test_ext().execute_with(|| { + let mut mmr = mmr::Mmr::::new(0); + for i in 0..*s { + mmr.push(i); + } + actual_sizes.push(mmr.size()); + }) + } + assert_eq!(sizes[1..], actual_sizes[..]); +} + #[test] fn should_generate_proofs_correctly() { let _ = env_logger::try_init(); let mut ext = new_test_ext(); // given - ext.execute_with(|| init_chain(7)); + let num_blocks: u64 = 7; + ext.execute_with(|| add_blocks(num_blocks as usize)); ext.persist_offchain_overlay(); // Try to generate proofs now. This requires the offchain extensions to be present // to retrieve full leaf data. register_offchain_ext(&mut ext); ext.execute_with(|| { - // when generate proofs for all leaves - let proofs = (0_u64..crate::NumberOfLeaves::::get()) + let best_block_number = frame_system::Pallet::::block_number(); + // when generate proofs for all leaves. + let proofs = (1_u64..=best_block_number) .into_iter() - .map(|leaf_index| crate::Pallet::::generate_proof(leaf_index).unwrap()) + .map(|block_num| crate::Pallet::::generate_proof(vec![block_num], None).unwrap()) + .collect::>(); + // when generate historical proofs for all leaves + let historical_proofs = (1_u64..best_block_number) + .into_iter() + .map(|block_num| { + let mut proofs = vec![]; + for historical_best_block in block_num..=num_blocks { + proofs.push( + crate::Pallet::::generate_proof( + vec![block_num], + Some(historical_best_block), + ) + .unwrap(), + ) + } + proofs + }) .collect::>(); // then assert_eq!( proofs[0], ( - Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),)), + vec![Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),))], Proof { - leaf_index: 0, + leaf_indices: vec![0], leaf_count: 7, items: vec![ hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"), @@ -215,12 +293,86 @@ fn should_generate_proofs_correctly() { } ) ); + assert_eq!( + historical_proofs[0][0], + ( + vec![Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),))], + Proof { leaf_indices: vec![0], leaf_count: 1, items: vec![] } + ) + ); + + // D + // / \ + // / \ + // A B C + // / \ / \ / \ + // 1 2 3 4 5 6 7 + // + // we're proving 3 => we need { 4, A, C++7 } + assert_eq!( + proofs[2], + ( + vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))], + Proof { + leaf_indices: vec![2], + leaf_count: 7, + items: vec![ + hex("1b14c1dc7d3e4def11acdf31be0584f4b85c3673f1ff72a3af467b69a3b0d9d0"), + hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"), + hex("dca421199bdcc55bb773c6b6967e8d16675de69062b52285ca63685241fdf626"), + ], + } + ) + ); + // A + // / \ + // 1 2 3 + // + // we're proving 3 => we need { A } + assert_eq!( + historical_proofs[2][0], + ( + vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))], + Proof { + leaf_indices: vec![2], + leaf_count: 3, + items: vec![hex( + "672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854" + )], + } + ) + ); + // D + // / \ + // / \ + // A B + // / \ / \ + // 1 2 3 4 5 + // we're proving 3 => we need { 4, A, 5 } + assert_eq!( + historical_proofs[2][2], + ( + vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))], + Proof { + leaf_indices: vec![2], + leaf_count: 5, + items: vec![ + hex("1b14c1dc7d3e4def11acdf31be0584f4b85c3673f1ff72a3af467b69a3b0d9d0"), + hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"), + hex("3b031d22e24f1126c8f7d2f394b663f9b960ed7abbedb7152e17ce16112656d0") + ], + } + ) + ); + assert_eq!(historical_proofs[2][4], proofs[2]); + assert_eq!( proofs[4], ( - Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),)), + // NOTE: the leaf index is equivalent to the block number(in this case 5) - 1 + vec![Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),))], Proof { - leaf_index: 4, + leaf_indices: vec![4], leaf_count: 7, items: vec![ hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"), @@ -230,12 +382,27 @@ fn should_generate_proofs_correctly() { } ) ); + assert_eq!( + historical_proofs[4][0], + ( + vec![Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),))], + Proof { + leaf_indices: vec![4], + leaf_count: 5, + items: vec![hex( + "ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252" + ),], + } + ) + ); + assert_eq!(historical_proofs[4][2], proofs[4]); + assert_eq!( proofs[6], ( - Compact::new(((6, H256::repeat_byte(7)).into(), LeafData::new(7).into(),)), + vec![Compact::new(((6, H256::repeat_byte(7)).into(), LeafData::new(7).into(),))], Proof { - leaf_index: 6, + leaf_indices: vec![6], leaf_count: 7, items: vec![ hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"), @@ -244,6 +411,60 @@ fn should_generate_proofs_correctly() { } ) ); + assert_eq!(historical_proofs[5][1], proofs[5]); + }); +} + +#[test] +fn should_generate_batch_proof_correctly() { + let _ = env_logger::try_init(); + let mut ext = new_test_ext(); + // given + ext.execute_with(|| add_blocks(7)); + ext.persist_offchain_overlay(); + + // Try to generate proofs now. This requires the offchain extensions to be present + // to retrieve full leaf data. + register_offchain_ext(&mut ext); + ext.execute_with(|| { + // when generate proofs for a batch of leaves + let (.., proof) = crate::Pallet::::generate_proof(vec![1, 5, 6], None).unwrap(); + // then + assert_eq!( + proof, + Proof { + // the leaf indices are equivalent to the above specified block numbers - 1. + leaf_indices: vec![0, 4, 5], + leaf_count: 7, + items: vec![ + hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"), + hex("cb24f4614ad5b2a5430344c99545b421d9af83c46fd632d70a332200884b4d46"), + hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c"), + ], + } + ); + + // when generate historical proofs for a batch of leaves + let (.., historical_proof) = + crate::Pallet::::generate_proof(vec![1, 5, 6], Some(6)).unwrap(); + // then + assert_eq!( + historical_proof, + Proof { + leaf_indices: vec![0, 4, 5], + leaf_count: 6, + items: vec![ + hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"), + hex("cb24f4614ad5b2a5430344c99545b421d9af83c46fd632d70a332200884b4d46"), + ], + } + ); + + // when generate historical proofs for a batch of leaves + let (.., historical_proof) = + crate::Pallet::::generate_proof(vec![1, 5, 6], None).unwrap(); + // then + assert_eq!(historical_proof, proof); }); } @@ -254,28 +475,129 @@ fn should_verify() { // Start off with chain initialisation and storing indexing data off-chain // (MMR Leafs) let mut ext = new_test_ext(); - ext.execute_with(|| init_chain(7)); + ext.execute_with(|| add_blocks(7)); ext.persist_offchain_overlay(); // Try to generate proof now. This requires the offchain extensions to be present // to retrieve full leaf data. register_offchain_ext(&mut ext); - let (leaf, proof5) = ext.execute_with(|| { + let (leaves, proof5) = ext.execute_with(|| { + // when + crate::Pallet::::generate_proof(vec![5], None).unwrap() + }); + let (simple_historical_leaves, simple_historical_proof5) = ext.execute_with(|| { // when - crate::Pallet::::generate_proof(5).unwrap() + crate::Pallet::::generate_proof(vec![5], Some(6)).unwrap() + }); + let (advanced_historical_leaves, advanced_historical_proof5) = ext.execute_with(|| { + // when + crate::Pallet::::generate_proof(vec![5], Some(7)).unwrap() }); - // Now to verify the proof, we really shouldn't require offchain storage or extension. - // Hence we initialize the storage once again, using different externalities and then - // verify. - let mut ext2 = new_test_ext(); - ext2.execute_with(|| { - init_chain(7); + ext.execute_with(|| { + add_blocks(7); // then - assert_eq!(crate::Pallet::::verify_leaf(leaf, proof5), Ok(())); + assert_eq!(crate::Pallet::::verify_leaves(leaves, proof5), Ok(())); + assert_eq!( + crate::Pallet::::verify_leaves( + simple_historical_leaves, + simple_historical_proof5 + ), + Ok(()) + ); + assert_eq!( + crate::Pallet::::verify_leaves( + advanced_historical_leaves, + advanced_historical_proof5 + ), + Ok(()) + ); }); } +#[test] +fn should_verify_batch_proofs() { + fn generate_and_verify_batch_proof( + ext: &mut sp_io::TestExternalities, + block_numbers: &Vec, + blocks_to_add: usize, + ) { + let (leaves, proof) = ext.execute_with(|| { + crate::Pallet::::generate_proof(block_numbers.to_vec(), None).unwrap() + }); + + let max_block_number = ext.execute_with(|| frame_system::Pallet::::block_number()); + let min_block_number = block_numbers.iter().max().unwrap(); + + // generate all possible historical proofs for the given blocks + let historical_proofs = (*min_block_number..=max_block_number) + .map(|best_block| { + ext.execute_with(|| { + crate::Pallet::::generate_proof(block_numbers.to_vec(), Some(best_block)) + .unwrap() + }) + }) + .collect::>(); + + ext.execute_with(|| { + add_blocks(blocks_to_add); + // then + assert_eq!(crate::Pallet::::verify_leaves(leaves, proof), Ok(())); + historical_proofs.iter().for_each(|(leaves, proof)| { + assert_eq!( + crate::Pallet::::verify_leaves(leaves.clone(), proof.clone()), + Ok(()) + ); + }); + }) + } + + let _ = env_logger::try_init(); + + use itertools::Itertools; + + let mut ext = new_test_ext(); + // require the offchain extensions to be present + // to retrieve full leaf data when generating proofs + register_offchain_ext(&mut ext); + + // verify that up to n=10, valid proofs are generated for all possible block number + // combinations. + for n in 1..=10 { + ext.execute_with(|| new_block()); + ext.persist_offchain_overlay(); + + // generate powerset (skipping empty set) of all possible block number combinations for mmr + // size n. + let blocks_set: Vec> = (1..=n).into_iter().powerset().skip(1).collect(); + + blocks_set.iter().for_each(|blocks_subset| { + generate_and_verify_batch_proof(&mut ext, &blocks_subset, 0); + ext.persist_offchain_overlay(); + }); + } + + // verify that up to n=15, valid proofs are generated for all possible 2-block number + // combinations. + for n in 11..=15 { + ext.execute_with(|| new_block()); + ext.persist_offchain_overlay(); + + // generate all possible 2-block number combinations for mmr size n. + let blocks_set: Vec> = (1..=n).into_iter().combinations(2).collect(); + + blocks_set.iter().for_each(|blocks_subset| { + generate_and_verify_batch_proof(&mut ext, &blocks_subset, 0); + ext.persist_offchain_overlay(); + }); + } + + generate_and_verify_batch_proof(&mut ext, &vec![8, 12], 20); + ext.execute_with(|| add_blocks(1000)); + ext.persist_offchain_overlay(); + generate_and_verify_batch_proof(&mut ext, &vec![8, 12, 100, 800], 100); +} + #[test] fn verification_should_be_stateless() { let _ = env_logger::try_init(); @@ -283,22 +605,96 @@ fn verification_should_be_stateless() { // Start off with chain initialisation and storing indexing data off-chain // (MMR Leafs) let mut ext = new_test_ext(); - ext.execute_with(|| init_chain(7)); + let (root_6, root_7) = ext.execute_with(|| { + add_blocks(6); + let root_6 = crate::Pallet::::mmr_root_hash(); + add_blocks(1); + let root_7 = crate::Pallet::::mmr_root_hash(); + (root_6, root_7) + }); ext.persist_offchain_overlay(); // Try to generate proof now. This requires the offchain extensions to be present // to retrieve full leaf data. register_offchain_ext(&mut ext); - let (leaf, proof5) = ext.execute_with(|| { + let (leaves, proof5) = ext.execute_with(|| { // when - crate::Pallet::::generate_proof(5).unwrap() + crate::Pallet::::generate_proof(vec![5], None).unwrap() + }); + let (_, historical_proof5) = ext.execute_with(|| { + // when + crate::Pallet::::generate_proof(vec![5], Some(6)).unwrap() }); - let root = ext.execute_with(|| crate::Pallet::::mmr_root_hash()); // Verify proof without relying on any on-chain data. - let leaf = crate::primitives::DataOrHash::Data(leaf); + let leaf = crate::primitives::DataOrHash::Data(leaves[0].clone()); assert_eq!( - crate::verify_leaf_proof::<::Hashing, _>(root, leaf, proof5), + crate::verify_leaves_proof::<::Hashing, _>( + root_7, + vec![leaf.clone()], + proof5 + ), + Ok(()) + ); + assert_eq!( + crate::verify_leaves_proof::<::Hashing, _>( + root_6, + vec![leaf], + historical_proof5 + ), + Ok(()) + ); +} + +#[test] +fn should_verify_batch_proof_statelessly() { + let _ = env_logger::try_init(); + + // Start off with chain initialisation and storing indexing data off-chain + // (MMR Leafs) + let mut ext = new_test_ext(); + let (root_6, root_7) = ext.execute_with(|| { + add_blocks(6); + let root_6 = crate::Pallet::::mmr_root_hash(); + add_blocks(1); + let root_7 = crate::Pallet::::mmr_root_hash(); + (root_6, root_7) + }); + ext.persist_offchain_overlay(); + + // Try to generate proof now. This requires the offchain extensions to be present + // to retrieve full leaf data. + register_offchain_ext(&mut ext); + let (leaves, proof) = ext.execute_with(|| { + // when + crate::Pallet::::generate_proof(vec![1, 4, 5], None).unwrap() + }); + let (historical_leaves, historical_proof) = ext.execute_with(|| { + // when + crate::Pallet::::generate_proof(vec![1, 4, 5], Some(6)).unwrap() + }); + + // Verify proof without relying on any on-chain data. + assert_eq!( + crate::verify_leaves_proof::<::Hashing, _>( + root_7, + leaves + .into_iter() + .map(|leaf| crate::primitives::DataOrHash::Data(leaf)) + .collect(), + proof + ), + Ok(()) + ); + assert_eq!( + crate::verify_leaves_proof::<::Hashing, _>( + root_6, + historical_leaves + .into_iter() + .map(|leaf| crate::primitives::DataOrHash::Data(leaf)) + .collect(), + historical_proof + ), Ok(()) ); } @@ -308,17 +704,88 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() { let _ = env_logger::try_init(); let mut ext = new_test_ext(); // given - ext.execute_with(|| init_chain(7)); + ext.execute_with(|| add_blocks(7)); ext.persist_offchain_overlay(); register_offchain_ext(&mut ext); ext.execute_with(|| { // when - let (leaf, proof5) = crate::Pallet::::generate_proof(5).unwrap(); + let (leaves, proof5) = crate::Pallet::::generate_proof(vec![5], None).unwrap(); new_block(); // then - assert_eq!(crate::Pallet::::verify_leaf(leaf, proof5), Ok(())); + assert_eq!(crate::Pallet::::verify_leaves(leaves, proof5), Ok(())); + }); +} + +#[test] +fn should_verify_canonicalized() { + use frame_support::traits::Hooks; + let _ = env_logger::try_init(); + + // How deep is our fork-aware storage (in terms of blocks/leaves, nodes will be more). + let block_hash_size: u64 = ::BlockHashCount::get(); + + // Start off with chain initialisation and storing indexing data off-chain. + // Create twice as many leaf entries than our fork-aware capacity, + // resulting in ~half of MMR storage to use canonical keys and the other half fork-aware keys. + // Verify that proofs can be generated (using leaves and nodes from full set) and verified. + let mut ext = new_test_ext(); + register_offchain_ext(&mut ext); + for blocknum in 0u32..(2 * block_hash_size).try_into().unwrap() { + ext.execute_with(|| { + new_block(); + as Hooks>::offchain_worker(blocknum.into()); + }); + ext.persist_offchain_overlay(); + } + + // Generate proofs for some blocks. + let (leaves, proofs) = + ext.execute_with(|| crate::Pallet::::generate_proof(vec![1, 4, 5, 7], None).unwrap()); + // Verify all previously generated proofs. + ext.execute_with(|| { + assert_eq!(crate::Pallet::::verify_leaves(leaves, proofs), Ok(())); + }); + + // Generate proofs for some new blocks. + let (leaves, proofs) = ext.execute_with(|| { + crate::Pallet::::generate_proof(vec![block_hash_size + 7], None).unwrap() + }); + // Add some more blocks then verify all previously generated proofs. + ext.execute_with(|| { + add_blocks(7); + assert_eq!(crate::Pallet::::verify_leaves(leaves, proofs), Ok(())); + }); +} + +#[test] +fn does_not_panic_when_generating_historical_proofs() { + let _ = env_logger::try_init(); + let mut ext = new_test_ext(); + + // given 7 blocks (7 MMR leaves) + ext.execute_with(|| add_blocks(7)); + ext.persist_offchain_overlay(); + + // Try to generate historical proof with invalid arguments. This requires the offchain + // extensions to be present to retrieve full leaf data. + register_offchain_ext(&mut ext); + ext.execute_with(|| { + // when leaf index is invalid + assert_eq!(crate::Pallet::::generate_proof(vec![10], None), Err(Error::LeafNotFound),); + + // when leaves count is invalid + assert_eq!( + crate::Pallet::::generate_proof(vec![3], Some(100)), + Err(Error::GenerateProof), + ); + + // when both leaf index and leaves count are invalid + assert_eq!( + crate::Pallet::::generate_proof(vec![10], Some(100)), + Err(Error::LeafNotFound), + ); }); } diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml new file mode 100644 index 0000000000000..47d114902f52c --- /dev/null +++ b/frame/message-queue/Cargo.toml @@ -0,0 +1,53 @@ +[package] +authors = ["Parity Technologies "] +edition = "2021" +name = "pallet-message-queue" +version = "7.0.0-dev" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet to queue and process messages" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +serde = { version = "1.0.137", optional = true, features = ["derive"] } +log = { version = "0.4.17", default-features = false } + +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } + +[dev-dependencies] +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +rand = "0.8.5" +rand_distr = "0.4.3" + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-arithmetic/std", + "sp-weights/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs new file mode 100644 index 0000000000000..9cd6b75e4d0ae --- /dev/null +++ b/frame/message-queue/src/benchmarking.rs @@ -0,0 +1,205 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 for the message queue pallet. + +#![cfg(feature = "runtime-benchmarks")] +#![allow(unused_assignments)] // Needed for `ready_ring_knit`. + +use super::{mock_helpers::*, Pallet as MessageQueue, *}; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::traits::Get; +use frame_system::RawOrigin; +use sp_std::prelude::*; + +benchmarks! { + where_clause { + where + // NOTE: We need to generate multiple origins, therefore Origin is `From`. The + // `PartialEq` is for asserting the outcome of the ring (un)knitting and *could* be + // removed if really necessary. + <::MessageProcessor as ProcessMessage>::Origin: From + PartialEq, + ::Size: From, + } + + // Worst case path of `ready_ring_knit`. + ready_ring_knit { + let mid: MessageOriginOf:: = 1.into(); + build_ring::(&[0.into(), mid.clone(), 2.into()]); + unknit::(&mid); + assert_ring::(&[0.into(), 2.into()]); + let mut neighbours = None; + }: { + neighbours = MessageQueue::::ready_ring_knit(&mid).ok(); + } verify { + // The neighbours needs to be modified manually. + BookStateFor::::mutate(&mid, |b| { b.ready_neighbours = neighbours }); + assert_ring::(&[0.into(), 2.into(), mid]); + } + + // Worst case path of `ready_ring_unknit`. + ready_ring_unknit { + build_ring::(&[0.into(), 1.into(), 2.into()]); + assert_ring::(&[0.into(), 1.into(), 2.into()]); + let o: MessageOriginOf:: = 0.into(); + let neighbours = BookStateFor::::get(&o).ready_neighbours.unwrap(); + }: { + MessageQueue::::ready_ring_unknit(&o, neighbours); + } verify { + assert_ring::(&[1.into(), 2.into()]); + } + + // `service_queues` without any queue processing. + service_queue_base { + }: { + MessageQueue::::service_queue(0.into(), &mut WeightMeter::max_limit(), Weight::MAX) + } + + // `service_page` without any message processing but with page completion. + service_page_base_completion { + let origin: MessageOriginOf = 0.into(); + let page = PageOf::::default(); + Pages::::insert(&origin, 0, &page); + let mut book_state = single_page_book::(); + let mut meter = WeightMeter::max_limit(); + let limit = Weight::MAX; + }: { + MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) + } + + // `service_page` without any message processing and without page completion. + service_page_base_no_completion { + let origin: MessageOriginOf = 0.into(); + let mut page = PageOf::::default(); + // Mock the storage such that `is_complete` returns `false` but `peek_first` returns `None`. + page.first = 1.into(); + page.remaining = 1.into(); + Pages::::insert(&origin, 0, &page); + let mut book_state = single_page_book::(); + let mut meter = WeightMeter::max_limit(); + let limit = Weight::MAX; + }: { + MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) + } + + // Processing a single message from a page. + service_page_item { + let msg = vec![1u8; MaxMessageLenOf::::get() as usize]; + let mut page = page::(&msg.clone()); + let mut book = book_for::(&page); + assert!(page.peek_first().is_some(), "There is one message"); + let mut weight = WeightMeter::max_limit(); + }: { + let status = MessageQueue::::service_page_item(&0u32.into(), 0, &mut book, &mut page, &mut weight, Weight::MAX); + assert_eq!(status, ItemExecutionStatus::Executed(true)); + } verify { + // Check that it was processed. + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&msg), origin: 0.into(), + weight_used: 1.into_weight(), success: true + }.into()); + let (_, processed, _) = page.peek_index(0).unwrap(); + assert!(processed); + assert_eq!(book.message_count, 0); + } + + // Worst case for calling `bump_service_head`. + bump_service_head { + setup_bump_service_head::(0.into(), 10.into()); + let mut weight = WeightMeter::max_limit(); + }: { + MessageQueue::::bump_service_head(&mut weight); + } verify { + assert_eq!(ServiceHead::::get().unwrap(), 10u32.into()); + assert_eq!(weight.consumed, T::WeightInfo::bump_service_head()); + } + + reap_page { + // Mock the storage to get a *cullable* but not *reapable* page. + let origin: MessageOriginOf = 0.into(); + let mut book = single_page_book::(); + let (page, msgs) = full_page::(); + + for p in 0 .. T::MaxStale::get() * T::MaxStale::get() { + if p == 0 { + Pages::::insert(&origin, p, &page); + } + book.end += 1; + book.count += 1; + book.message_count += msgs as u64; + book.size += page.remaining_size.into() as u64; + } + book.begin = book.end - T::MaxStale::get(); + BookStateFor::::insert(&origin, &book); + assert!(Pages::::contains_key(&origin, 0)); + + }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0) + verify { + assert_last_event::(Event::PageReaped{ origin: 0.into(), index: 0 }.into()); + assert!(!Pages::::contains_key(&origin, 0)); + } + + // Worst case for `execute_overweight` where the page is removed as completed. + // + // The worst case occurs when executing the last message in a page of which all are skipped since it is using `peek_index` which has linear complexities. + execute_overweight_page_removed { + let origin: MessageOriginOf = 0.into(); + let (mut page, msgs) = full_page::(); + // Skip all messages. + for _ in 1..msgs { + page.skip_first(true); + } + page.skip_first(false); + let book = book_for::(&page); + Pages::::insert(&origin, 0, &page); + BookStateFor::::insert(&origin, &book); + }: { + MessageQueue::::execute_overweight(RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX).unwrap() + } + verify { + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), + weight_used: Weight::from_parts(1, 1), success: true + }.into()); + assert!(!Pages::::contains_key(&origin, 0), "Page must be removed"); + } + + // Worst case for `execute_overweight` where the page is updated. + execute_overweight_page_updated { + let origin: MessageOriginOf = 0.into(); + let (mut page, msgs) = full_page::(); + // Skip all messages. + for _ in 0..msgs { + page.skip_first(false); + } + let book = book_for::(&page); + Pages::::insert(&origin, 0, &page); + BookStateFor::::insert(&origin, &book); + }: { + MessageQueue::::execute_overweight(RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX).unwrap() + } + verify { + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), + weight_used: Weight::from_parts(1, 1), success: true + }.into()); + assert!(Pages::::contains_key(&origin, 0), "Page must be updated"); + } + + impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::(), crate::integration_test::Test); +} diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs new file mode 100644 index 0000000000000..f4b1b7a125449 --- /dev/null +++ b/frame/message-queue/src/integration_test.rs @@ -0,0 +1,225 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Stress tests pallet-message-queue. Defines its own runtime config to use larger constants for +//! `HeapSize` and `MaxStale`. + +#![cfg(test)] + +use crate::{ + mock::{ + new_test_ext, CountingMessageProcessor, IntoWeight, MockedWeightInfo, NumMessagesProcessed, + }, + *, +}; + +use crate as pallet_message_queue; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand_distr::Pareto; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub const HeapSize: u32 = 32 * 1024; + pub const MaxStale: u32 = 32; + pub static ServiceWeight: Option = Some(Weight::from_parts(100, 100)); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = MockedWeightInfo; + type MessageProcessor = CountingMessageProcessor; + type Size = u32; + type QueueChangeHandler = (); + type HeapSize = HeapSize; + type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; +} + +/// Simulates heavy usage by enqueueing and processing large amounts of messages. +/// +/// Best to run with `-r`, `RUST_LOG=info` and `RUSTFLAGS='-Cdebug-assertions=y'`. +/// +/// # Example output +/// +/// ```pre +/// Enqueued 1189 messages across 176 queues. Payload 46.97 KiB +/// Processing 772 of 1189 messages +/// Enqueued 9270 messages across 1559 queues. Payload 131.85 KiB +/// Processing 6262 of 9687 messages +/// Enqueued 5025 messages across 1225 queues. Payload 100.23 KiB +/// Processing 1739 of 8450 messages +/// Enqueued 42061 messages across 6357 queues. Payload 536.29 KiB +/// Processing 11675 of 48772 messages +/// Enqueued 20253 messages across 2420 queues. Payload 288.34 KiB +/// Processing 28711 of 57350 messages +/// Processing all remaining 28639 messages +/// ``` +#[test] +#[ignore] // Only run in the CI. +fn stress_test_enqueue_and_service() { + let blocks = 20; + let max_queues = 10_000; + let max_messages_per_queue = 10_000; + let max_msg_len = MaxMessageLenOf::::get(); + let mut rng = StdRng::seed_from_u64(42); + + new_test_ext::().execute_with(|| { + let mut msgs_remaining = 0; + for _ in 0..blocks { + // Start by enqueuing a large number of messages. + let (enqueued, _) = + enqueue_messages(max_queues, max_messages_per_queue, max_msg_len, &mut rng); + msgs_remaining += enqueued; + + // Pick a fraction of all messages currently in queue and process them. + let processed = rng.gen_range(1..=msgs_remaining); + log::info!("Processing {} of all messages {}", processed, msgs_remaining); + process_messages(processed); // This also advances the block. + msgs_remaining -= processed; + } + log::info!("Processing all remaining {} messages", msgs_remaining); + process_messages(msgs_remaining); + post_conditions(); + }); +} + +/// Enqueue a random number of random messages into a random number of queues. +fn enqueue_messages( + max_queues: u32, + max_per_queue: u32, + max_msg_len: u32, + rng: &mut StdRng, +) -> (u32, usize) { + let num_queues = rng.gen_range(1..max_queues); + let mut num_messages = 0; + let mut total_msg_len = 0; + for origin in 0..num_queues { + let num_messages_per_queue = + (rng.sample(Pareto::new(1.0, 1.1).unwrap()) as u32).min(max_per_queue); + + for m in 0..num_messages_per_queue { + let mut message = format!("{}:{}", &origin, &m).into_bytes(); + let msg_len = (rng.sample(Pareto::new(1.0, 1.0).unwrap()) as u32) + .clamp(message.len() as u32, max_msg_len); + message.resize(msg_len as usize, 0); + MessageQueue::enqueue_message( + BoundedSlice::defensive_truncate_from(&message), + origin.into(), + ); + total_msg_len += msg_len; + } + num_messages += num_messages_per_queue; + } + log::info!( + "Enqueued {} messages across {} queues. Payload {:.2} KiB", + num_messages, + num_queues, + total_msg_len as f64 / 1024.0 + ); + (num_messages, total_msg_len as usize) +} + +/// Process the number of messages. +fn process_messages(num_msgs: u32) { + let weight = (num_msgs as u64).into_weight(); + ServiceWeight::set(Some(weight)); + let consumed = next_block(); + + assert_eq!(consumed, weight, "\n{}", MessageQueue::debug_info()); + assert_eq!(NumMessagesProcessed::take(), num_msgs as usize); +} + +/// Returns the weight consumed by `MessageQueue::on_initialize()`. +fn next_block() -> Weight { + MessageQueue::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + MessageQueue::on_initialize(System::block_number()) +} + +/// Assert that the pallet is in the expected post state. +fn post_conditions() { + // All queues are empty. + for (_, book) in BookStateFor::::iter() { + assert!(book.end >= book.begin); + assert_eq!(book.count, 0); + assert_eq!(book.size, 0); + assert_eq!(book.message_count, 0); + assert!(book.ready_neighbours.is_none()); + } + // No pages remain. + assert_eq!(Pages::::iter().count(), 0); + // Service head is gone. + assert!(ServiceHead::::get().is_none()); + // This still works fine. + assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero(), "Nothing left"); + next_block(); +} diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs new file mode 100644 index 0000000000000..8d9faebe0517f --- /dev/null +++ b/frame/message-queue/src/lib.rs @@ -0,0 +1,1311 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! # Generalized Message Queue Pallet +//! +//! Provides generalized message queuing and processing capabilities on a per-queue basis for +//! arbitrary use-cases. +//! +//! # Design Goals +//! +//! 1. Minimal assumptions about `Message`s and `MessageOrigin`s. Both should be MEL bounded blobs. +//! This ensures the generality and reusability of the pallet. +//! 2. Well known and tightly limited pre-dispatch PoV weights, especially for message execution. +//! This is paramount for the success of the pallet since message execution is done in +//! `on_initialize` which must _never_ under-estimate its PoV weight. It also needs a frugal PoV +//! footprint since PoV is scarce and this is (possibly) done in every block. This must also hold +//! in the presence of unpredictable message size distributions. +//! 3. Usable as XCMP, DMP and UMP message/dispatch queue - possibly through adapter types. +//! +//! # Design +//! +//! The pallet has means to enqueue, store and process messages. This is implemented by having +//! *queues* which store enqueued messages and can be *served* to process said messages. A queue is +//! identified by its origin in the `BookStateFor`. Each message has an origin which defines into +//! which queue it will be stored. Messages are stored by being appended to the last [`Page`] of a +//! book. Each book keeps track of its pages by indexing `Pages`. The `ReadyRing` contains all +//! queues which hold at least one unprocessed message and are thereby *ready* to be serviced. The +//! `ServiceHead` indicates which *ready* queue is the next to be serviced. +//! The pallet implements [`frame_support::traits::EnqueueMessage`], +//! [`frame_support::traits::ServiceQueues`] and has [`frame_support::traits::ProcessMessage`] and +//! [`OnQueueChanged`] hooks to communicate with the outside world. +//! +//! NOTE: The storage items are not linked since they are not public. +//! +//! **Message Execution** +//! +//! Executing a message is offloaded to the [`Config::MessageProcessor`] which contains the actual +//! logic of how to handle the message since they are blobs. A message can be temporarily or +//! permanently overweight. The pallet will perpetually try to execute a temporarily overweight +//! message. A permanently overweight message is skipped and must be executed manually. +//! +//! **Pagination** +//! +//! Queues are stored in a *paged* manner by splitting their messages into [`Page`]s. This results +//! in a lot of complexity when implementing the pallet but is completely necessary to archive the +//! second #[Design Goal](design-goals). The problem comes from the fact a message can *possibly* be +//! quite large, lets say 64KiB. This then results in a *MEL* of at least 64KiB which results in a +//! PoV of at least 64KiB. Now we have the assumption that most messages are much shorter than their +//! maximum allowed length. This would result in most messages having a pre-dispatch PoV size which +//! is much larger than their post-dispatch PoV size, possibly by a factor of thousand. Disregarding +//! this observation would cripple the processing power of the pallet since it cannot straighten out +//! this discrepancy at runtime. Conceptually, the implementation is packing as many messages into a +//! single bounded vec, as actually fit into the bounds. This reduces the wasted PoV. +//! +//! **Page Data Layout** +//! +//! A Page contains a heap which holds all its messages. The heap is built by concatenating +//! `(ItemHeader, Message)` pairs. The [`ItemHeader`] contains the length of the message which is +//! needed for retrieving it. This layout allows for constant access time of the next message and +//! linear access time for any message in the page. The header must remain minimal to reduce its PoV +//! impact. +//! +//! **Weight Metering** +//! +//! The pallet utilizes the [`sp_weights::WeightMeter`] to manually track its consumption to always +//! stay within the required limit. This implies that the message processor hook can calculate the +//! weight of a message without executing it. This restricts the possible use-cases but is necessary +//! since the pallet runs in `on_initialize` which has a hard weight limit. The weight meter is used +//! in a way that `can_accrue` and `check_accrue` are always used to check the remaining weight of +//! an operation before committing to it. The process of exiting due to insufficient weight is +//! termed "bailing". +//! +//! # Scenario: Message enqueuing +//! +//! A message `m` is enqueued for origin `o` into queue `Q[o]` through +//! [`frame_support::traits::EnqueueMessage::enqueue_message`]`(m, o)`. +//! +//! First the queue is either loaded if it exists or otherwise created with empty default values. +//! The message is then inserted to the queue by appended it into its last `Page` or by creating a +//! new `Page` just for `m` if it does not fit in there. The number of messages in the `Book` is +//! incremented. +//! +//! `Q[o]` is now *ready* which will eventually result in `m` being processed. +//! +//! # Scenario: Message processing +//! +//! The pallet runs each block in `on_initialize` or when being manually called through +//! [`frame_support::traits::ServiceQueues::service_queues`]. +//! +//! First it tries to "rotate" the `ReadyRing` by one through advancing the `ServiceHead` to the +//! next *ready* queue. It then starts to service this queue by servicing as many pages of it as +//! possible. Servicing a page means to execute as many message of it as possible. Each executed +//! message is marked as *processed* if the [`Config::MessageProcessor`] return Ok. An event +//! [`Event::Processed`] is emitted afterwards. It is possible that the weight limit of the pallet +//! will never allow a specific message to be executed. In this case it remains as unprocessed and +//! is skipped. This process stops if either there are no more messages in the queue or the +//! remaining weight became insufficient to service this queue. If there is enough weight it tries +//! to advance to the next *ready* queue and service it. This continues until there are no more +//! queues on which it can make progress or not enough weight to check that. +//! +//! # Scenario: Overweight execution +//! +//! A permanently over-weight message which was skipped by the message processing will never be +//! executed automatically through `on_initialize` nor by calling +//! [`frame_support::traits::ServiceQueues::service_queues`]. +//! +//! Manual intervention in the form of +//! [`frame_support::traits::ServiceQueues::execute_overweight`] is necessary. Overweight messages +//! emit an [`Event::OverweightEnqueued`] event which can be used to extract the arguments for +//! manual execution. This only works on permanently overweight messages. There is no guarantee that +//! this will work since the message could be part of a stale page and be reaped before execution +//! commences. +//! +//! # Terminology +//! +//! - `Message`: A blob of data into which the pallet has no introspection, defined as +//! [`BoundedSlice>`]. The message length is limited by [`MaxMessageLenOf`] +//! which is calculated from [`Config::HeapSize`] and [`ItemHeader::max_encoded_len()`]. +//! - `MessageOrigin`: A generic *origin* of a message, defined as [`MessageOriginOf`]. The +//! requirements for it are kept minimal to remain as generic as possible. The type is defined in +//! [`frame_support::traits::ProcessMessage::Origin`]. +//! - `Page`: An array of `Message`s, see [`Page`]. Can never be empty. +//! - `Book`: A list of `Page`s, see [`BookState`]. Can be empty. +//! - `Queue`: A `Book` together with an `MessageOrigin` which can be part of the `ReadyRing`. Can +//! be empty. +//! - `ReadyRing`: A double-linked list which contains all *ready* `Queue`s. It chains together the +//! queues via their `ready_neighbours` fields. A `Queue` is *ready* if it contains at least one +//! `Message` which can be processed. Can be empty. +//! - `ServiceHead`: A pointer into the `ReadyRing` to the next `Queue` to be serviced. +//! - (`un`)`processed`: A message is marked as *processed* after it was executed by the pallet. A +//! message which was either: not yet executed or could not be executed remains as `unprocessed` +//! which is the default state for a message after being enqueued. +//! - `knitting`/`unknitting`: The means of adding or removing a `Queue` from the `ReadyRing`. +//! - `MEL`: The Max Encoded Length of a type, see [`codec::MaxEncodedLen`]. +//! +//! # Properties +//! +//! **Liveness - Enqueueing** +//! +//! It is always possible to enqueue any message for any `MessageOrigin`. +//! +//! **Liveness - Processing** +//! +//! `on_initialize` always respects its finite weight-limit. +//! +//! **Progress - Enqueueing** +//! +//! An enqueued message immediately becomes *unprocessed* and thereby eligible for execution. +//! +//! **Progress - Processing** +//! +//! The pallet will execute at least one unprocessed message per block, if there is any. Ensuring +//! this property needs careful consideration of the concrete weights, since it is possible that the +//! weight limit of `on_initialize` never allows for the execution of even one message; trivially if +//! the limit is set to zero. `integrity_test` can be used to ensure that this property holds. +//! +//! **Fairness - Enqueuing** +//! +//! Enqueueing a message for a specific `MessageOrigin` does not influence the ability to enqueue a +//! message for the same of any other `MessageOrigin`; guaranteed by **Liveness - Enqueueing**. +//! +//! **Fairness - Processing** +//! +//! The average amount of weight available for message processing is the same for each queue if the +//! number of queues is constant. Creating a new queue must therefore be, possibly economically, +//! expensive. Currently this is archived by having one queue per para-chain/thread, which keeps the +//! number of queues within `O(n)` and should be "good enough". + +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +mod integration_test; +mod mock; +pub mod mock_helpers; +mod tests; +pub mod weights; + +use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use frame_support::{ + defensive, + pallet_prelude::*, + traits::{ + DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, + ProcessMessageError, ServiceQueues, + }, + BoundedSlice, CloneNoBound, DefaultNoBound, +}; +use frame_system::pallet_prelude::*; +pub use pallet::*; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use sp_runtime::{ + traits::{Hash, One, Zero}, + SaturatedConversion, Saturating, +}; +use sp_std::{fmt::Debug, ops::Deref, prelude::*, vec}; +use sp_weights::WeightMeter; +pub use weights::WeightInfo; + +/// Type for identifying a page. +type PageIndex = u32; + +/// Data encoded and prefixed to the encoded `MessageItem`. +#[derive(Encode, Decode, PartialEq, MaxEncodedLen, Debug)] +pub struct ItemHeader { + /// The length of this item, not including the size of this header. The next item of the page + /// follows immediately after the payload of this item. + payload_len: Size, + /// Whether this item has been processed. + is_processed: bool, +} + +/// A page of messages. Pages always contain at least one item. +#[derive( + CloneNoBound, Encode, Decode, RuntimeDebugNoBound, DefaultNoBound, TypeInfo, MaxEncodedLen, +)] +#[scale_info(skip_type_params(HeapSize))] +#[codec(mel_bound(Size: MaxEncodedLen))] +pub struct Page + Debug + Clone + Default, HeapSize: Get> { + /// Messages remaining to be processed; this includes overweight messages which have been + /// skipped. + remaining: Size, + /// The size of all remaining messages to be processed. + /// + /// Includes overweight messages outside of the `first` to `last` window. + remaining_size: Size, + /// The number of items before the `first` item in this page. + first_index: Size, + /// The heap-offset of the header of the first message item in this page which is ready for + /// processing. + first: Size, + /// The heap-offset of the header of the last message item in this page. + last: Size, + /// The heap. If `self.offset == self.heap.len()` then the page is empty and should be deleted. + heap: BoundedVec>, +} + +impl< + Size: BaseArithmetic + Unsigned + Copy + Into + Codec + MaxEncodedLen + Debug + Default, + HeapSize: Get, + > Page +{ + /// Create a [`Page`] from one unprocessed message. + fn from_message(message: BoundedSlice>) -> Self { + let payload_len = message.len(); + let data_len = ItemHeader::::max_encoded_len().saturating_add(payload_len); + let payload_len = payload_len.saturated_into(); + let header = ItemHeader:: { payload_len, is_processed: false }; + + let mut heap = Vec::with_capacity(data_len); + header.using_encoded(|h| heap.extend_from_slice(h)); + heap.extend_from_slice(message.deref()); + + Page { + remaining: One::one(), + remaining_size: payload_len, + first_index: Zero::zero(), + first: Zero::zero(), + last: Zero::zero(), + heap: BoundedVec::defensive_truncate_from(heap), + } + } + + /// Try to append one message to a page. + fn try_append_message( + &mut self, + message: BoundedSlice>, + ) -> Result<(), ()> { + let pos = self.heap.len(); + let payload_len = message.len(); + let data_len = ItemHeader::::max_encoded_len().saturating_add(payload_len); + let payload_len = payload_len.saturated_into(); + let header = ItemHeader:: { payload_len, is_processed: false }; + let heap_size: u32 = HeapSize::get().into(); + if (heap_size as usize).saturating_sub(self.heap.len()) < data_len { + // Can't fit. + return Err(()) + } + + let mut heap = sp_std::mem::take(&mut self.heap).into_inner(); + header.using_encoded(|h| heap.extend_from_slice(h)); + heap.extend_from_slice(message.deref()); + self.heap = BoundedVec::defensive_truncate_from(heap); + self.last = pos.saturated_into(); + self.remaining.saturating_inc(); + self.remaining_size.saturating_accrue(payload_len); + Ok(()) + } + + /// Returns the first message in the page without removing it. + /// + /// SAFETY: Does not panic even on corrupted storage. + fn peek_first(&self) -> Option>> { + if self.first > self.last { + return None + } + let f = (self.first.into() as usize).min(self.heap.len()); + let mut item_slice = &self.heap[f..]; + if let Ok(h) = ItemHeader::::decode(&mut item_slice) { + let payload_len = h.payload_len.into() as usize; + if payload_len <= item_slice.len() { + // impossible to truncate since is sliced up from `self.heap: BoundedVec` + return Some(BoundedSlice::defensive_truncate_from(&item_slice[..payload_len])) + } + } + defensive!("message-queue: heap corruption"); + None + } + + /// Point `first` at the next message, marking the first as processed if `is_processed` is true. + fn skip_first(&mut self, is_processed: bool) { + let f = (self.first.into() as usize).min(self.heap.len()); + if let Ok(mut h) = ItemHeader::decode(&mut &self.heap[f..]) { + if is_processed && !h.is_processed { + h.is_processed = true; + h.using_encoded(|d| self.heap[f..f + d.len()].copy_from_slice(d)); + self.remaining.saturating_dec(); + self.remaining_size.saturating_reduce(h.payload_len); + } + self.first + .saturating_accrue(ItemHeader::::max_encoded_len().saturated_into()); + self.first.saturating_accrue(h.payload_len); + self.first_index.saturating_inc(); + } + } + + /// Return the message with index `index` in the form of `(position, processed, message)`. + fn peek_index(&self, index: usize) -> Option<(usize, bool, &[u8])> { + let mut pos = 0; + let mut item_slice = &self.heap[..]; + let header_len: usize = ItemHeader::::max_encoded_len().saturated_into(); + for _ in 0..index { + let h = ItemHeader::::decode(&mut item_slice).ok()?; + let item_len = h.payload_len.into() as usize; + if item_slice.len() < item_len { + return None + } + item_slice = &item_slice[item_len..]; + pos.saturating_accrue(header_len.saturating_add(item_len)); + } + let h = ItemHeader::::decode(&mut item_slice).ok()?; + if item_slice.len() < h.payload_len.into() as usize { + return None + } + item_slice = &item_slice[..h.payload_len.into() as usize]; + Some((pos, h.is_processed, item_slice)) + } + + /// Set the `is_processed` flag for the item at `pos` to be `true` if not already and decrement + /// the `remaining` counter of the page. + /// + /// Does nothing if no [`ItemHeader`] could be decoded at the given position. + fn note_processed_at_pos(&mut self, pos: usize) { + if let Ok(mut h) = ItemHeader::::decode(&mut &self.heap[pos..]) { + if !h.is_processed { + h.is_processed = true; + h.using_encoded(|d| self.heap[pos..pos + d.len()].copy_from_slice(d)); + self.remaining.saturating_dec(); + self.remaining_size.saturating_reduce(h.payload_len); + } + } + } + + /// Returns whether the page is *complete* which means that no messages remain. + fn is_complete(&self) -> bool { + self.remaining.is_zero() + } +} + +/// A single link in the double-linked Ready Ring list. +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug, PartialEq)] +pub struct Neighbours { + /// The previous queue. + prev: MessageOrigin, + /// The next queue. + next: MessageOrigin, +} + +/// The state of a queue as represented by a book of its pages. +/// +/// Each queue has exactly one book which holds all of its pages. All pages of a book combined +/// contain all of the messages of its queue; hence the name *Book*. +/// Books can be chained together in a double-linked fashion through their `ready_neighbours` field. +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] +pub struct BookState { + /// The first page with some items to be processed in it. If this is `>= end`, then there are + /// no pages with items to be processing in them. + begin: PageIndex, + /// One more than the last page with some items to be processed in it. + end: PageIndex, + /// The number of pages stored at present. + /// + /// This might be larger than `end-begin`, because we keep pages with unprocessed overweight + /// messages outside of the end/begin window. + count: PageIndex, + /// If this book has any ready pages, then this will be `Some` with the previous and next + /// neighbours. This wraps around. + ready_neighbours: Option>, + /// The number of unprocessed messages stored at present. + message_count: u64, + /// The total size of all unprocessed messages stored at present. + size: u64, +} + +impl Default for BookState { + fn default() -> Self { + Self { begin: 0, end: 0, count: 0, ready_neighbours: None, message_count: 0, size: 0 } + } +} + +/// Handler code for when the items in a queue change. +pub trait OnQueueChanged { + /// Note that the queue `id` now has `item_count` items in it, taking up `items_size` bytes. + fn on_queue_changed(id: Id, items_count: u64, items_size: u64); +} + +impl OnQueueChanged for () { + fn on_queue_changed(_: Id, _: u64, _: u64) {} +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// The module configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Processor for a message. + /// + /// Must be set to [`mock_helpers::NoopMessageProcessor`] for benchmarking. + /// Other message processors that consumes exactly (1, 1) weight for any give message will + /// work as well. Otherwise the benchmarking will also measure the weight of the message + /// processor, which is not desired. + type MessageProcessor: ProcessMessage; + + /// Page/heap size type. + type Size: BaseArithmetic + + Unsigned + + Copy + + Into + + Member + + Encode + + Decode + + MaxEncodedLen + + TypeInfo + + Default; + + /// Code to be called when a message queue changes - either with items introduced or + /// removed. + type QueueChangeHandler: OnQueueChanged<::Origin>; + + /// The size of the page; this implies the maximum message size which can be sent. + /// + /// A good value depends on the expected message sizes, their weights, the weight that is + /// available for processing them and the maximal needed message size. The maximal message + /// size is slightly lower than this as defined by [`MaxMessageLenOf`]. + #[pallet::constant] + type HeapSize: Get; + + /// The maximum number of stale pages (i.e. of overweight messages) allowed before culling + /// can happen. Once there are more stale pages than this, then historical pages may be + /// dropped, even if they contain unprocessed overweight messages. + #[pallet::constant] + type MaxStale: Get; + + /// The amount of weight (if any) which should be provided to the message queue for + /// servicing enqueued items. + /// + /// This may be legitimately `None` in the case that you will call + /// `ServiceQueues::service_queues` manually. + #[pallet::constant] + type ServiceWeight: Get>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Message discarded due to an inability to decode the item. Usually caused by state + /// corruption. + Discarded { hash: T::Hash }, + /// Message discarded due to an error in the `MessageProcessor` (usually a format error). + ProcessingFailed { hash: T::Hash, origin: MessageOriginOf, error: ProcessMessageError }, + /// Message is processed. + Processed { hash: T::Hash, origin: MessageOriginOf, weight_used: Weight, success: bool }, + /// Message placed in overweight queue. + OverweightEnqueued { + hash: T::Hash, + origin: MessageOriginOf, + page_index: PageIndex, + message_index: T::Size, + }, + /// This page was reaped. + PageReaped { origin: MessageOriginOf, index: PageIndex }, + } + + #[pallet::error] + pub enum Error { + /// Page is not reapable because it has items remaining to be processed and is not old + /// enough. + NotReapable, + /// Page to be reaped does not exist. + NoPage, + /// The referenced message could not be found. + NoMessage, + /// The message was already processed and cannot be processed again. + AlreadyProcessed, + /// The message is queued for future execution. + Queued, + /// There is temporarily not enough weight to continue servicing messages. + InsufficientWeight, + } + + /// The index of the first and last (non-empty) pages. + #[pallet::storage] + pub(super) type BookStateFor = + StorageMap<_, Twox64Concat, MessageOriginOf, BookState>, ValueQuery>; + + /// The origin at which we should begin servicing. + #[pallet::storage] + pub(super) type ServiceHead = StorageValue<_, MessageOriginOf, OptionQuery>; + + /// The map of page indices to pages. + #[pallet::storage] + pub(super) type Pages = StorageDoubleMap< + _, + Twox64Concat, + MessageOriginOf, + Twox64Concat, + PageIndex, + Page, + OptionQuery, + >; + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + if let Some(weight_limit) = T::ServiceWeight::get() { + Self::service_queues(weight_limit) + } else { + Weight::zero() + } + } + + /// Check all assumptions about [`crate::Config`]. + fn integrity_test() { + assert!(!MaxMessageLenOf::::get().is_zero(), "HeapSize too low"); + } + } + + #[pallet::call] + impl Pallet { + /// Remove a page which has no more messages remaining to be processed or is stale. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::reap_page())] + pub fn reap_page( + origin: OriginFor, + message_origin: MessageOriginOf, + page_index: PageIndex, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + Self::do_reap_page(&message_origin, page_index) + } + + /// Execute an overweight message. + /// + /// - `origin`: Must be `Signed`. + /// - `message_origin`: The origin from which the message to be executed arrived. + /// - `page`: The page in the queue in which the message to be executed is sitting. + /// - `index`: The index into the queue of the message to be executed. + /// - `weight_limit`: The maximum amount of weight allowed to be consumed in the execution + /// of the message. + /// + /// Benchmark complexity considerations: O(index + weight_limit). + #[pallet::call_index(1)] + #[pallet::weight( + T::WeightInfo::execute_overweight_page_updated().max( + T::WeightInfo::execute_overweight_page_removed()).saturating_add(*weight_limit) + )] + pub fn execute_overweight( + origin: OriginFor, + message_origin: MessageOriginOf, + page: PageIndex, + index: T::Size, + weight_limit: Weight, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let actual_weight = + Self::do_execute_overweight(message_origin, page, index, weight_limit)?; + Ok(Some(actual_weight).into()) + } + } +} + +/// The status of a page after trying to execute its next message. +#[derive(PartialEq, Debug)] +enum PageExecutionStatus { + /// The execution bailed because there was not enough weight remaining. + Bailed, + /// No more messages could be loaded. This does _not_ imply `page.is_complete()`. + /// + /// The reasons for this status are: + /// - The end of the page is reached but there could still be skipped messages. + /// - The storage is corrupted. + NoMore, +} + +/// The status after trying to execute the next item of a [`Page`]. +#[derive(PartialEq, Debug)] +enum ItemExecutionStatus { + /// The execution bailed because there was not enough weight remaining. + Bailed, + /// The item was not found. + NoItem, + /// Whether the execution of an item resulted in it being processed. + /// + /// One reason for `false` would be permanently overweight. + Executed(bool), +} + +/// The status of an attempt to process a message. +#[derive(PartialEq)] +enum MessageExecutionStatus { + /// There is not enough weight remaining at present. + InsufficientWeight, + /// There will never be enough weight. + Overweight, + /// The message was processed successfully. + Processed, + /// The message was processed and resulted in a permanent error. + Unprocessable, +} + +impl Pallet { + /// Knit `origin` into the ready ring right at the end. + /// + /// Return the two ready ring neighbours of `origin`. + fn ready_ring_knit(origin: &MessageOriginOf) -> Result>, ()> { + if let Some(head) = ServiceHead::::get() { + let mut head_book_state = BookStateFor::::get(&head); + let mut head_neighbours = head_book_state.ready_neighbours.take().ok_or(())?; + let tail = head_neighbours.prev; + head_neighbours.prev = origin.clone(); + head_book_state.ready_neighbours = Some(head_neighbours); + BookStateFor::::insert(&head, head_book_state); + + let mut tail_book_state = BookStateFor::::get(&tail); + let mut tail_neighbours = tail_book_state.ready_neighbours.take().ok_or(())?; + tail_neighbours.next = origin.clone(); + tail_book_state.ready_neighbours = Some(tail_neighbours); + BookStateFor::::insert(&tail, tail_book_state); + + Ok(Neighbours { next: head, prev: tail }) + } else { + ServiceHead::::put(origin); + Ok(Neighbours { next: origin.clone(), prev: origin.clone() }) + } + } + + fn ready_ring_unknit(origin: &MessageOriginOf, neighbours: Neighbours>) { + if origin == &neighbours.next { + debug_assert!( + origin == &neighbours.prev, + "unknitting from single item ring; outgoing must be only item" + ); + // Service queue empty. + ServiceHead::::kill(); + } else { + BookStateFor::::mutate(&neighbours.next, |book_state| { + if let Some(ref mut n) = book_state.ready_neighbours { + n.prev = neighbours.prev.clone() + } + }); + BookStateFor::::mutate(&neighbours.prev, |book_state| { + if let Some(ref mut n) = book_state.ready_neighbours { + n.next = neighbours.next.clone() + } + }); + if let Some(head) = ServiceHead::::get() { + if &head == origin { + ServiceHead::::put(neighbours.next); + } + } else { + defensive!("`ServiceHead` must be some if there was a ready queue"); + } + } + } + + /// Tries to bump the current `ServiceHead` to the next ready queue. + /// + /// Returns the current head if it got be bumped and `None` otherwise. + fn bump_service_head(weight: &mut WeightMeter) -> Option> { + if !weight.check_accrue(T::WeightInfo::bump_service_head()) { + return None + } + + if let Some(head) = ServiceHead::::get() { + let mut head_book_state = BookStateFor::::get(&head); + if let Some(head_neighbours) = head_book_state.ready_neighbours.take() { + ServiceHead::::put(&head_neighbours.next); + Some(head) + } else { + None + } + } else { + None + } + } + + fn do_enqueue_message( + origin: &MessageOriginOf, + message: BoundedSlice>, + ) { + let mut book_state = BookStateFor::::get(origin); + book_state.message_count.saturating_inc(); + book_state + .size + // This should be payload size, but here the payload *is* the message. + .saturating_accrue(message.len() as u64); + + if book_state.end > book_state.begin { + debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); + // Already have a page in progress - attempt to append. + let last = book_state.end - 1; + let mut page = match Pages::::get(origin, last) { + Some(p) => p, + None => { + defensive!("Corruption: referenced page doesn't exist."); + return + }, + }; + if page.try_append_message::(message).is_ok() { + Pages::::insert(origin, last, &page); + BookStateFor::::insert(origin, book_state); + return + } + } else { + debug_assert!( + book_state.ready_neighbours.is_none(), + "Must not be in ready ring if not ready" + ); + // insert into ready queue. + match Self::ready_ring_knit(origin) { + Ok(neighbours) => book_state.ready_neighbours = Some(neighbours), + Err(()) => { + defensive!("Ring state invalid when knitting"); + }, + } + } + // No room on the page or no page - link in a new page. + book_state.end.saturating_inc(); + book_state.count.saturating_inc(); + let page = Page::from_message::(message); + Pages::::insert(origin, book_state.end - 1, page); + // NOTE: `T::QueueChangeHandler` is called by the caller. + BookStateFor::::insert(origin, book_state); + } + + /// Try to execute a single message that was marked as overweight. + /// + /// The `weight_limit` is the weight that can be consumed to execute the message. The base + /// weight of the function it self must be measured by the caller. + pub fn do_execute_overweight( + origin: MessageOriginOf, + page_index: PageIndex, + index: T::Size, + weight_limit: Weight, + ) -> Result> { + let mut book_state = BookStateFor::::get(&origin); + let mut page = Pages::::get(&origin, page_index).ok_or(Error::::NoPage)?; + let (pos, is_processed, payload) = + page.peek_index(index.into() as usize).ok_or(Error::::NoMessage)?; + let payload_len = payload.len() as u64; + ensure!( + page_index < book_state.begin || + (page_index == book_state.begin && pos < page.first.into() as usize), + Error::::Queued + ); + ensure!(!is_processed, Error::::AlreadyProcessed); + use MessageExecutionStatus::*; + let mut weight_counter = WeightMeter::from_limit(weight_limit); + match Self::process_message_payload( + origin.clone(), + page_index, + index, + payload, + &mut weight_counter, + Weight::MAX, + // ^^^ We never recognise it as permanently overweight, since that would result in an + // additional overweight event being deposited. + ) { + Overweight | InsufficientWeight => Err(Error::::InsufficientWeight), + Unprocessable | Processed => { + page.note_processed_at_pos(pos); + book_state.message_count.saturating_dec(); + book_state.size.saturating_reduce(payload_len); + let page_weight = if page.remaining.is_zero() { + debug_assert!( + page.remaining_size.is_zero(), + "no messages remaining; no space taken; qed" + ); + Pages::::remove(&origin, page_index); + debug_assert!(book_state.count >= 1, "page exists, so book must have pages"); + book_state.count.saturating_dec(); + T::WeightInfo::execute_overweight_page_removed() + // no need to consider .first or ready ring since processing an overweight page + // would not alter that state. + } else { + Pages::::insert(&origin, page_index, page); + T::WeightInfo::execute_overweight_page_updated() + }; + BookStateFor::::insert(&origin, &book_state); + T::QueueChangeHandler::on_queue_changed( + origin, + book_state.message_count, + book_state.size, + ); + Ok(weight_counter.consumed.saturating_add(page_weight)) + }, + } + } + + /// Remove a stale page or one which has no more messages remaining to be processed. + fn do_reap_page(origin: &MessageOriginOf, page_index: PageIndex) -> DispatchResult { + let mut book_state = BookStateFor::::get(origin); + // definitely not reapable if the page's index is no less than the `begin`ning of ready + // pages. + ensure!(page_index < book_state.begin, Error::::NotReapable); + + let page = Pages::::get(origin, page_index).ok_or(Error::::NoPage)?; + + // definitely reapable if the page has no messages in it. + let reapable = page.remaining.is_zero(); + + // also reapable if the page index has dropped below our watermark. + let cullable = || { + let total_pages = book_state.count; + let ready_pages = book_state.end.saturating_sub(book_state.begin).min(total_pages); + + // The number of stale pages - i.e. pages which contain unprocessed overweight messages. + // We would prefer to keep these around but will restrict how far into history they can + // extend if we notice that there's too many of them. + // + // We don't know *where* in history these pages are so we use a dynamic formula which + // reduces the historical time horizon as the stale pages pile up and increases it as + // they reduce. + let stale_pages = total_pages - ready_pages; + + // The maximum number of stale pages (i.e. of overweight messages) allowed before + // culling can happen at all. Once there are more stale pages than this, then historical + // pages may be dropped, even if they contain unprocessed overweight messages. + let max_stale = T::MaxStale::get(); + + // The amount beyond the maximum which are being used. If it's not beyond the maximum + // then we exit now since no culling is needed. + let overflow = match stale_pages.checked_sub(max_stale + 1) { + Some(x) => x + 1, + None => return false, + }; + + // The special formula which tells us how deep into index-history we will pages. As + // the overflow is greater (and thus the need to drop items from storage is more urgent) + // this is reduced, allowing a greater range of pages to be culled. + // With a minimum `overflow` (`1`), this returns `max_stale ** 2`, indicating we only + // cull beyond that number of indices deep into history. + // At this overflow increases, our depth reduces down to a limit of `max_stale`. We + // never want to reduce below this since this will certainly allow enough pages to be + // culled in order to bring `overflow` back to zero. + let backlog = (max_stale * max_stale / overflow).max(max_stale); + + let watermark = book_state.begin.saturating_sub(backlog); + page_index < watermark + }; + ensure!(reapable || cullable(), Error::::NotReapable); + + Pages::::remove(origin, page_index); + debug_assert!(book_state.count > 0, "reaping a page implies there are pages"); + book_state.count.saturating_dec(); + book_state.message_count.saturating_reduce(page.remaining.into() as u64); + book_state.size.saturating_reduce(page.remaining_size.into() as u64); + BookStateFor::::insert(origin, &book_state); + T::QueueChangeHandler::on_queue_changed( + origin.clone(), + book_state.message_count, + book_state.size, + ); + Self::deposit_event(Event::PageReaped { origin: origin.clone(), index: page_index }); + + Ok(()) + } + + /// Execute any messages remaining to be processed in the queue of `origin`, using up to + /// `weight_limit` to do so. Any messages which would take more than `overweight_limit` to + /// execute are deemed overweight and ignored. + fn service_queue( + origin: MessageOriginOf, + weight: &mut WeightMeter, + overweight_limit: Weight, + ) -> (bool, Option>) { + if !weight.check_accrue( + T::WeightInfo::service_queue_base().saturating_add(T::WeightInfo::ready_ring_unknit()), + ) { + return (false, None) + } + + let mut book_state = BookStateFor::::get(&origin); + let mut total_processed = 0; + + while book_state.end > book_state.begin { + let (processed, status) = + Self::service_page(&origin, &mut book_state, weight, overweight_limit); + total_processed.saturating_accrue(processed); + match status { + // Store the page progress and do not go to the next one. + PageExecutionStatus::Bailed => break, + // Go to the next page if this one is at the end. + PageExecutionStatus::NoMore => (), + }; + book_state.begin.saturating_inc(); + } + let next_ready = book_state.ready_neighbours.as_ref().map(|x| x.next.clone()); + if book_state.begin >= book_state.end && total_processed > 0 { + // No longer ready - unknit. + if let Some(neighbours) = book_state.ready_neighbours.take() { + Self::ready_ring_unknit(&origin, neighbours); + } else { + defensive!("Freshly processed queue must have been ready"); + } + } + BookStateFor::::insert(&origin, &book_state); + if total_processed > 0 { + T::QueueChangeHandler::on_queue_changed( + origin, + book_state.message_count, + book_state.size, + ); + } + (total_processed > 0, next_ready) + } + + /// Service as many messages of a page as possible. + /// + /// Returns how many messages were processed and the page's status. + fn service_page( + origin: &MessageOriginOf, + book_state: &mut BookStateOf, + weight: &mut WeightMeter, + overweight_limit: Weight, + ) -> (u32, PageExecutionStatus) { + use PageExecutionStatus::*; + if !weight.check_accrue( + T::WeightInfo::service_page_base_completion() + .max(T::WeightInfo::service_page_base_no_completion()), + ) { + return (0, Bailed) + } + + let page_index = book_state.begin; + let mut page = match Pages::::get(origin, page_index) { + Some(p) => p, + None => { + defensive!("message-queue: referenced page not found"); + return (0, NoMore) + }, + }; + + let mut total_processed = 0; + + // Execute as many messages as possible. + let status = loop { + use ItemExecutionStatus::*; + match Self::service_page_item( + origin, + page_index, + book_state, + &mut page, + weight, + overweight_limit, + ) { + Bailed => break PageExecutionStatus::Bailed, + NoItem => break PageExecutionStatus::NoMore, + // Keep going as long as we make progress... + Executed(true) => total_processed.saturating_inc(), + Executed(false) => (), + } + }; + + if page.is_complete() { + debug_assert!(status != Bailed, "we never bail if a page became complete"); + Pages::::remove(origin, page_index); + debug_assert!(book_state.count > 0, "completing a page implies there are pages"); + book_state.count.saturating_dec(); + } else { + Pages::::insert(origin, page_index, page); + } + (total_processed, status) + } + + /// Execute the next message of a page. + pub(crate) fn service_page_item( + origin: &MessageOriginOf, + page_index: PageIndex, + book_state: &mut BookStateOf, + page: &mut PageOf, + weight: &mut WeightMeter, + overweight_limit: Weight, + ) -> ItemExecutionStatus { + // This ugly pre-checking is needed for the invariant + // "we never bail if a page became complete". + if page.is_complete() { + return ItemExecutionStatus::NoItem + } + if !weight.check_accrue(T::WeightInfo::service_page_item()) { + return ItemExecutionStatus::Bailed + } + + let payload = &match page.peek_first() { + Some(m) => m, + None => return ItemExecutionStatus::NoItem, + }[..]; + + use MessageExecutionStatus::*; + let is_processed = match Self::process_message_payload( + origin.clone(), + page_index, + page.first_index, + payload.deref(), + weight, + overweight_limit, + ) { + InsufficientWeight => return ItemExecutionStatus::Bailed, + Processed | Unprocessable => true, + Overweight => false, + }; + + if is_processed { + book_state.message_count.saturating_dec(); + book_state.size.saturating_reduce(payload.len() as u64); + } + page.skip_first(is_processed); + ItemExecutionStatus::Executed(is_processed) + } + + /// Print the pages in each queue and the messages in each page. + /// + /// Processed messages are prefixed with a `*` and the current `begin`ning page with a `>`. + /// + /// # Example output + /// + /// ```text + /// queue Here: + /// page 0: [] + /// > page 1: [] + /// page 2: ["\0weight=4", "\0c", ] + /// page 3: ["\0bigbig 1", ] + /// page 4: ["\0bigbig 2", ] + /// page 5: ["\0bigbig 3", ] + /// ``` + #[cfg(feature = "std")] + pub fn debug_info() -> String { + let mut info = String::new(); + for (origin, book_state) in BookStateFor::::iter() { + let mut queue = format!("queue {:?}:\n", &origin); + let mut pages = Pages::::iter_prefix(&origin).collect::>(); + pages.sort_by(|(a, _), (b, _)| a.cmp(b)); + for (page_index, mut page) in pages.into_iter() { + let page_info = if book_state.begin == page_index { ">" } else { " " }; + let mut page_info = format!( + "{} page {} ({:?} first, {:?} last, {:?} remain): [ ", + page_info, page_index, page.first, page.last, page.remaining + ); + for i in 0..u32::MAX { + if let Some((_, processed, message)) = + page.peek_index(i.try_into().expect("std-only code")) + { + let msg = String::from_utf8_lossy(message.deref()); + if processed { + page_info.push('*'); + } + page_info.push_str(&format!("{:?}, ", msg)); + page.skip_first(true); + } else { + break + } + } + page_info.push_str("]\n"); + queue.push_str(&page_info); + } + info.push_str(&queue); + } + info + } + + /// Process a single message. + /// + /// The base weight of this function needs to be accounted for by the caller. `weight` is the + /// remaining weight to process the message. `overweight_limit` is the maximum weight that a + /// message can ever consume. Messages above this limit are marked as permanently overweight. + fn process_message_payload( + origin: MessageOriginOf, + page_index: PageIndex, + message_index: T::Size, + message: &[u8], + weight: &mut WeightMeter, + overweight_limit: Weight, + ) -> MessageExecutionStatus { + let hash = T::Hashing::hash(message); + use ProcessMessageError::Overweight; + match T::MessageProcessor::process_message(message, origin.clone(), weight.remaining()) { + Err(Overweight(w)) if w.any_gt(overweight_limit) => { + // Permanently overweight. + Self::deposit_event(Event::::OverweightEnqueued { + hash, + origin, + page_index, + message_index, + }); + MessageExecutionStatus::Overweight + }, + Err(Overweight(_)) => { + // Temporarily overweight - save progress and stop processing this + // queue. + MessageExecutionStatus::InsufficientWeight + }, + Err(error) => { + // Permanent error - drop + Self::deposit_event(Event::::ProcessingFailed { hash, origin, error }); + MessageExecutionStatus::Unprocessable + }, + Ok((success, weight_used)) => { + // Success + weight.defensive_saturating_accrue(weight_used); + let event = Event::::Processed { hash, origin, weight_used, success }; + Self::deposit_event(event); + MessageExecutionStatus::Processed + }, + } + } +} + +/// Provides a [`sp_core::Get`] to access the `MEL` of a [`codec::MaxEncodedLen`] type. +pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); +impl Get for MaxEncodedLenOf { + fn get() -> u32 { + T::max_encoded_len() as u32 + } +} + +/// Calculates the maximum message length and exposed it through the [`codec::MaxEncodedLen`] trait. +pub struct MaxMessageLen( + sp_std::marker::PhantomData<(Origin, Size, HeapSize)>, +); +impl, HeapSize: Get> Get + for MaxMessageLen +{ + fn get() -> u32 { + (HeapSize::get().into()).saturating_sub(ItemHeader::::max_encoded_len() as u32) + } +} + +/// The maximal message length. +pub type MaxMessageLenOf = + MaxMessageLen, ::Size, ::HeapSize>; +/// The maximal encoded origin length. +pub type MaxOriginLenOf = MaxEncodedLenOf>; +/// The `MessageOrigin` of this pallet. +pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; +/// The maximal heap size of a page. +pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; +/// The [`Page`] of this pallet. +pub type PageOf = Page<::Size, ::HeapSize>; +/// The [`BookState`] of this pallet. +pub type BookStateOf = BookState>; + +/// Converts a [`sp_core::Get`] with returns a type that can be cast into an `u32` into a `Get` +/// which returns an `u32`. +pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); +impl, O: Into> Get for IntoU32 { + fn get() -> u32 { + T::get().into() + } +} + +impl ServiceQueues for Pallet { + type OverweightMessageAddress = (MessageOriginOf, PageIndex, T::Size); + + fn service_queues(weight_limit: Weight) -> Weight { + // The maximum weight that processing a single message may take. + let overweight_limit = weight_limit; + let mut weight = WeightMeter::from_limit(weight_limit); + + let mut next = match Self::bump_service_head(&mut weight) { + Some(h) => h, + None => return weight.consumed, + }; + // The last queue that did not make any progress. + // The loop aborts as soon as it arrives at this queue again without making any progress + // on other queues in between. + let mut last_no_progress = None; + + loop { + let (progressed, n) = Self::service_queue(next.clone(), &mut weight, overweight_limit); + next = match n { + Some(n) => + if !progressed { + if last_no_progress == Some(n.clone()) { + break + } + if last_no_progress.is_none() { + last_no_progress = Some(next.clone()) + } + n + } else { + last_no_progress = None; + n + }, + None => break, + } + } + weight.consumed + } + + /// Execute a single overweight message. + /// + /// The weight limit must be enough for `execute_overweight` and the message execution itself. + fn execute_overweight( + weight_limit: Weight, + (message_origin, page, index): Self::OverweightMessageAddress, + ) -> Result { + let mut weight = WeightMeter::from_limit(weight_limit); + if !weight.check_accrue( + T::WeightInfo::execute_overweight_page_removed() + .max(T::WeightInfo::execute_overweight_page_updated()), + ) { + return Err(ExecuteOverweightError::InsufficientWeight) + } + + Pallet::::do_execute_overweight(message_origin, page, index, weight.remaining()).map_err( + |e| match e { + Error::::InsufficientWeight => ExecuteOverweightError::InsufficientWeight, + _ => ExecuteOverweightError::NotFound, + }, + ) + } +} + +impl EnqueueMessage> for Pallet { + type MaxMessageLen = + MaxMessageLen<::Origin, T::Size, T::HeapSize>; + + fn enqueue_message( + message: BoundedSlice, + origin: ::Origin, + ) { + Self::do_enqueue_message(&origin, message); + let book_state = BookStateFor::::get(&origin); + T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); + } + + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: ::Origin, + ) { + for message in messages { + Self::do_enqueue_message(&origin, message); + } + let book_state = BookStateFor::::get(&origin); + T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); + } + + fn sweep_queue(origin: MessageOriginOf) { + if !BookStateFor::::contains_key(&origin) { + return + } + let mut book_state = BookStateFor::::get(&origin); + book_state.begin = book_state.end; + if let Some(neighbours) = book_state.ready_neighbours.take() { + Self::ready_ring_unknit(&origin, neighbours); + } + BookStateFor::::insert(&origin, &book_state); + } + + fn footprint(origin: MessageOriginOf) -> Footprint { + let book_state = BookStateFor::::get(&origin); + Footprint { count: book_state.message_count, size: book_state.size } + } +} diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs new file mode 100644 index 0000000000000..7159840d1c01b --- /dev/null +++ b/frame/message-queue/src/mock.rs @@ -0,0 +1,315 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 helpers and runtime setup for the message queue pallet. + +#![cfg(test)] + +pub use super::mock_helpers::*; +use super::*; + +use crate as pallet_message_queue; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; +use sp_std::collections::btree_map::BTreeMap; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + } +); +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} +parameter_types! { + pub const HeapSize: u32 = 24; + pub const MaxStale: u32 = 2; + pub const ServiceWeight: Option = Some(Weight::from_parts(10, 10)); +} +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = MockedWeightInfo; + type MessageProcessor = RecordingMessageProcessor; + type Size = u32; + type QueueChangeHandler = RecordingQueueChangeHandler; + type HeapSize = HeapSize; + type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; +} + +/// Mocked `WeightInfo` impl with allows to set the weight per call. +pub struct MockedWeightInfo; + +parameter_types! { + /// Storage for `MockedWeightInfo`, do not use directly. + pub static WeightForCall: BTreeMap = Default::default(); +} + +/// Set the return value for a function from the `WeightInfo` trait. +impl MockedWeightInfo { + /// Set the weight of a specific weight function. + pub fn set_weight(call_name: &str, weight: Weight) { + let mut calls = WeightForCall::get(); + calls.insert(call_name.into(), weight); + WeightForCall::set(calls); + } +} + +impl crate::weights::WeightInfo for MockedWeightInfo { + fn reap_page() -> Weight { + WeightForCall::get().get("reap_page").copied().unwrap_or_default() + } + fn execute_overweight_page_updated() -> Weight { + WeightForCall::get() + .get("execute_overweight_page_updated") + .copied() + .unwrap_or_default() + } + fn execute_overweight_page_removed() -> Weight { + WeightForCall::get() + .get("execute_overweight_page_removed") + .copied() + .unwrap_or_default() + } + fn service_page_base_completion() -> Weight { + WeightForCall::get() + .get("service_page_base_completion") + .copied() + .unwrap_or_default() + } + fn service_page_base_no_completion() -> Weight { + WeightForCall::get() + .get("service_page_base_no_completion") + .copied() + .unwrap_or_default() + } + fn service_queue_base() -> Weight { + WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() + } + fn bump_service_head() -> Weight { + WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() + } + fn service_page_item() -> Weight { + WeightForCall::get().get("service_page_item").copied().unwrap_or_default() + } + fn ready_ring_knit() -> Weight { + WeightForCall::get().get("ready_ring_knit").copied().unwrap_or_default() + } + fn ready_ring_unknit() -> Weight { + WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() + } +} + +parameter_types! { + pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; +} + +/// A message processor which records all processed messages into [`MessagesProcessed`]. +pub struct RecordingMessageProcessor; +impl ProcessMessage for RecordingMessageProcessor { + /// The transport from where a message originates. + type Origin = MessageOrigin; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + /// + /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. + /// Errors if given the `weight_limit` is insufficient to process the message or if the message + /// is `badformat`, `corrupt` or `unsupported` with the respective error. + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + processing_message(message)?; + + let weight = if message.starts_with(&b"weight="[..]) { + let mut w: u64 = 0; + for &c in &message[7..] { + if (b'0'..=b'9').contains(&c) { + w = w * 10 + (c - b'0') as u64; + } else { + break + } + } + w + } else { + 1 + }; + let weight = Weight::from_parts(weight, weight); + + if weight.all_lte(weight_limit) { + let mut m = MessagesProcessed::get(); + m.push((message.to_vec(), origin)); + MessagesProcessed::set(m); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +/// Processed a mocked message. Messages that end with `badformat`, `corrupt` or `unsupported` will +/// fail with the respective error. +fn processing_message(msg: &[u8]) -> Result<(), ProcessMessageError> { + let msg = String::from_utf8_lossy(msg); + if msg.ends_with("badformat") { + Err(ProcessMessageError::BadFormat) + } else if msg.ends_with("corrupt") { + Err(ProcessMessageError::Corrupt) + } else if msg.ends_with("unsupported") { + Err(ProcessMessageError::Unsupported) + } else { + Ok(()) + } +} + +parameter_types! { + pub static NumMessagesProcessed: usize = 0; + pub static NumMessagesErrored: usize = 0; +} + +/// Similar to [`RecordingMessageProcessor`] but only counts the number of messages processed and +/// does always consume one weight per message. +/// +/// The [`RecordingMessageProcessor`] is a bit too slow for the integration tests. +pub struct CountingMessageProcessor; +impl ProcessMessage for CountingMessageProcessor { + type Origin = MessageOrigin; + + fn process_message( + message: &[u8], + _origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + if let Err(e) = processing_message(message) { + NumMessagesErrored::set(NumMessagesErrored::get() + 1); + return Err(e) + } + let weight = Weight::from_parts(1, 1); + + if weight.all_lte(weight_limit) { + NumMessagesProcessed::set(NumMessagesProcessed::get() + 1); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +parameter_types! { + /// Storage for `RecordingQueueChangeHandler`, do not use directly. + pub static QueueChanges: Vec<(MessageOrigin, u64, u64)> = vec![]; +} + +/// Records all queue changes into [`QueueChanges`]. +pub struct RecordingQueueChangeHandler; +impl OnQueueChanged for RecordingQueueChangeHandler { + fn on_queue_changed(id: MessageOrigin, items_count: u64, items_size: u64) { + QueueChanges::mutate(|cs| cs.push((id, items_count, items_size))); + } +} + +/// Create new test externalities. +/// +/// Is generic since it is used by the unit test, integration tests and benchmarks. +pub fn new_test_ext() -> sp_io::TestExternalities +where + ::BlockNumber: From, +{ + sp_tracing::try_init_simple(); + WeightForCall::take(); + QueueChanges::take(); + NumMessagesErrored::take(); + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| frame_system::Pallet::::set_block_number(1.into())); + ext +} + +/// Set the weight of a specific weight function. +pub fn set_weight(name: &str, w: Weight) { + MockedWeightInfo::set_weight::(name, w); +} + +/// Assert that exactly these pages are present. Assumes `Here` origin. +pub fn assert_pages(indices: &[u32]) { + assert_eq!(Pages::::iter().count(), indices.len()); + for i in indices { + assert!(Pages::::contains_key(MessageOrigin::Here, i)); + } +} + +/// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. +pub fn build_triple_ring() { + use MessageOrigin::*; + build_ring::(&[Here, There, Everywhere(0)]) +} + +/// Shim to get rid of the annoying `::` everywhere. +pub fn assert_ring(queues: &[MessageOrigin]) { + super::mock_helpers::assert_ring::(queues); +} + +pub fn knit(queue: &MessageOrigin) { + super::mock_helpers::knit::(queue); +} + +pub fn unknit(queue: &MessageOrigin) { + super::mock_helpers::unknit::(queue); +} diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs new file mode 100644 index 0000000000000..f12cf4cc41073 --- /dev/null +++ b/frame/message-queue/src/mock_helpers.rs @@ -0,0 +1,186 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Std setup helpers for testing and benchmarking. +//! +//! Cannot be put into mock.rs since benchmarks require no-std and mock.rs is std. + +use crate::*; +use frame_support::traits::Defensive; + +/// Converts `Self` into a `Weight` by using `Self` for all components. +pub trait IntoWeight { + fn into_weight(self) -> Weight; +} + +impl IntoWeight for u64 { + fn into_weight(self) -> Weight { + Weight::from_parts(self, self) + } +} + +/// Mocked message origin for testing. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] +pub enum MessageOrigin { + Here, + There, + Everywhere(u32), +} + +impl From for MessageOrigin { + fn from(i: u32) -> Self { + Self::Everywhere(i) + } +} + +/// Processes any message and consumes (1, 1) weight per message. +pub struct NoopMessageProcessor; +impl ProcessMessage for NoopMessageProcessor { + type Origin = MessageOrigin; + + fn process_message( + _message: &[u8], + _origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + let weight = Weight::from_parts(1, 1); + + if weight.all_lte(weight_limit) { + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +/// Create a message from the given data. +pub fn msg>(x: &'static str) -> BoundedSlice { + BoundedSlice::defensive_truncate_from(x.as_bytes()) +} + +pub fn vmsg(x: &'static str) -> Vec { + x.as_bytes().to_vec() +} + +/// Create a page from a single message. +pub fn page(msg: &[u8]) -> PageOf { + PageOf::::from_message::(msg.try_into().unwrap()) +} + +pub fn single_page_book() -> BookStateOf { + BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } +} + +pub fn empty_book() -> BookStateOf { + BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } +} + +/// Returns a full page of messages with their index as payload and the number of messages. +pub fn full_page() -> (PageOf, usize) { + let mut msgs = 0; + let mut page = PageOf::::default(); + for i in 0..u32::MAX { + let r = i.using_encoded(|d| page.try_append_message::(d.try_into().unwrap())); + if r.is_err() { + break + } else { + msgs += 1; + } + } + assert!(msgs > 0, "page must hold at least one message"); + (page, msgs) +} + +/// Returns a page filled with empty messages and the number of messages. +pub fn book_for(page: &PageOf) -> BookStateOf { + BookState { + count: 1, + begin: 0, + end: 1, + ready_neighbours: None, + message_count: page.remaining.into() as u64, + size: page.remaining_size.into() as u64, + } +} + +/// Assert the last event that was emitted. +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +pub fn assert_last_event(generic_event: ::RuntimeEvent) { + assert!( + !frame_system::Pallet::::block_number().is_zero(), + "The genesis block has n o events" + ); + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +/// Provide a setup for `bump_service_head`. +pub fn setup_bump_service_head( + current: <::MessageProcessor as ProcessMessage>::Origin, + next: <::MessageProcessor as ProcessMessage>::Origin, +) { + let mut book = single_page_book::(); + book.ready_neighbours = Some(Neighbours::> { prev: next.clone(), next }); + ServiceHead::::put(¤t); + BookStateFor::::insert(¤t, &book); +} + +/// Knit a queue into the ready-ring and write it back to storage. +pub fn knit(o: &<::MessageProcessor as ProcessMessage>::Origin) { + let mut b = BookStateFor::::get(o); + b.ready_neighbours = crate::Pallet::::ready_ring_knit(o).ok().defensive(); + BookStateFor::::insert(o, b); +} + +/// Unknit a queue into the ready-ring and write it back to storage. +pub fn unknit(o: &<::MessageProcessor as ProcessMessage>::Origin) { + let mut b = BookStateFor::::get(o); + crate::Pallet::::ready_ring_unknit(o, b.ready_neighbours.unwrap()); + b.ready_neighbours = None; + BookStateFor::::insert(o, b); +} + +/// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. +pub fn build_ring( + queues: &[<::MessageProcessor as ProcessMessage>::Origin], +) { + for queue in queues { + BookStateFor::::insert(queue, empty_book::()); + } + for queue in queues { + knit::(queue); + } + assert_ring::(queues); +} + +/// Check that the Ready Ring consists of `queues` in that exact order. +/// +/// Also check that all backlinks are valid and that the first element is the service head. +pub fn assert_ring( + queues: &[<::MessageProcessor as ProcessMessage>::Origin], +) { + for (i, origin) in queues.iter().enumerate() { + let book = BookStateFor::::get(origin); + assert_eq!( + book.ready_neighbours, + Some(Neighbours { + prev: queues[(i + queues.len() - 1) % queues.len()].clone(), + next: queues[(i + 1) % queues.len()].clone(), + }) + ); + } + assert_eq!(ServiceHead::::get(), queues.first().cloned()); +} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs new file mode 100644 index 0000000000000..103fb690ddba7 --- /dev/null +++ b/frame/message-queue/src/tests.rs @@ -0,0 +1,1092 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Tests for Message Queue Pallet. + +#![cfg(test)] + +use crate::{mock::*, *}; + +use frame_support::{assert_noop, assert_ok, assert_storage_noop, StorageNoopGuard}; +use rand::{rngs::StdRng, Rng, SeedableRng}; + +#[test] +fn mocked_weight_works() { + new_test_ext::().execute_with(|| { + assert!(::WeightInfo::service_queue_base().is_zero()); + }); + new_test_ext::().execute_with(|| { + set_weight("service_queue_base", Weight::MAX); + assert_eq!(::WeightInfo::service_queue_base(), Weight::MAX); + }); + // The externalities reset it. + new_test_ext::().execute_with(|| { + assert!(::WeightInfo::service_queue_base().is_zero()); + }); +} + +#[test] +fn enqueue_within_one_page_works() { + new_test_ext::().execute_with(|| { + use MessageOrigin::*; + MessageQueue::enqueue_message(msg("a"), Here); + MessageQueue::enqueue_message(msg("b"), Here); + MessageQueue::enqueue_message(msg("c"), Here); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(b"a".to_vec(), Here), (b"b".to_vec(), Here)]); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(b"c".to_vec(), Here)]); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); + assert!(MessagesProcessed::get().is_empty()); + + MessageQueue::enqueue_messages([msg("a"), msg("b"), msg("c")].into_iter(), There); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::take(), + vec![(b"a".to_vec(), There), (b"b".to_vec(), There),] + ); + + MessageQueue::enqueue_message(msg("d"), Everywhere(1)); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); + assert_eq!( + MessagesProcessed::take(), + vec![(b"c".to_vec(), There), (b"d".to_vec(), Everywhere(1))] + ); + }); +} + +#[test] +fn queue_priority_retains() { + new_test_ext::().execute_with(|| { + use MessageOrigin::*; + assert_ring(&[]); + MessageQueue::enqueue_message(msg("a"), Everywhere(1)); + assert_ring(&[Everywhere(1)]); + MessageQueue::enqueue_message(msg("b"), Everywhere(2)); + assert_ring(&[Everywhere(1), Everywhere(2)]); + MessageQueue::enqueue_message(msg("c"), Everywhere(3)); + assert_ring(&[Everywhere(1), Everywhere(2), Everywhere(3)]); + MessageQueue::enqueue_message(msg("d"), Everywhere(2)); + assert_ring(&[Everywhere(1), Everywhere(2), Everywhere(3)]); + // service head is 1, it will process a, leaving service head at 2. it also processes b but + // doees not empty queue 2, so service head will end at 2. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::take(), + vec![(vmsg("a"), Everywhere(1)), (vmsg("b"), Everywhere(2)),] + ); + assert_ring(&[Everywhere(2), Everywhere(3)]); + // service head is 2, so will process d first, then c. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::get(), + vec![(vmsg("d"), Everywhere(2)), (vmsg("c"), Everywhere(3)),] + ); + assert_ring(&[]); + }); +} + +#[test] +fn queue_priority_reset_once_serviced() { + new_test_ext::().execute_with(|| { + use MessageOrigin::*; + MessageQueue::enqueue_message(msg("a"), Everywhere(1)); + MessageQueue::enqueue_message(msg("b"), Everywhere(2)); + MessageQueue::enqueue_message(msg("c"), Everywhere(3)); + // service head is 1, it will process a, leaving service head at 2. it also processes b and + // empties queue 2, so service head will end at 3. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + MessageQueue::enqueue_message(msg("d"), Everywhere(2)); + // service head is 3, so will process c first, then d. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + + assert_eq!( + MessagesProcessed::get(), + vec![ + (vmsg("a"), Everywhere(1)), + (vmsg("b"), Everywhere(2)), + (vmsg("c"), Everywhere(3)), + (vmsg("d"), Everywhere(2)), + ] + ); + }); +} + +#[test] +fn service_queues_basic_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + MessageQueue::enqueue_messages(vec![msg("a"), msg("ab"), msg("abc")].into_iter(), Here); + MessageQueue::enqueue_messages(vec![msg("x"), msg("xy"), msg("xyz")].into_iter(), There); + assert_eq!(QueueChanges::take(), vec![(Here, 3, 6), (There, 3, 6)]); + + // Service one message from `Here`. + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 2, 5)]); + + // Service one message from `There`. + ServiceHead::::set(There.into()); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("x"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 2, 5)]); + + // Service the remaining from `Here`. + ServiceHead::::set(Here.into()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("ab"), Here), (vmsg("abc"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 0, 0)]); + + // Service all remaining messages. + assert_eq!(MessageQueue::service_queues(Weight::MAX), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("xy"), There), (vmsg("xyz"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 0, 0)]); + }); +} + +#[test] +fn service_queues_failing_messages_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + set_weight("service_page_item", 1.into_weight()); + MessageQueue::enqueue_message(msg("badformat"), Here); + MessageQueue::enqueue_message(msg("corrupt"), Here); + MessageQueue::enqueue_message(msg("unsupported"), Here); + // Starts with three pages. + assert_pages(&[0, 1, 2]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"badformat"), + origin: MessageOrigin::Here, + error: ProcessMessageError::BadFormat, + } + .into(), + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"corrupt"), + origin: MessageOrigin::Here, + error: ProcessMessageError::Corrupt, + } + .into(), + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"unsupported"), + origin: MessageOrigin::Here, + error: ProcessMessageError::Unsupported, + } + .into(), + ); + // All pages removed. + assert_pages(&[]); + }); +} + +#[test] +fn reap_page_permanent_overweight_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create 10 pages more than the stale limit. + let n = (MaxStale::get() + 10) as usize; + for _ in 0..n { + MessageQueue::enqueue_message(msg("weight=2"), Here); + } + assert_eq!(Pages::::iter().count(), n); + assert_eq!(QueueChanges::take().len(), n); + // Mark all pages as stale since their message is permanently overweight. + MessageQueue::service_queues(1.into_weight()); + + // Check that we can reap everything below the watermark. + let max_stale = MaxStale::get(); + for i in 0..n as u32 { + let b = BookStateFor::::get(Here); + let stale_pages = n as u32 - i; + let overflow = stale_pages.saturating_sub(max_stale + 1) + 1; + let backlog = (max_stale * max_stale / overflow).max(max_stale); + let watermark = b.begin.saturating_sub(backlog); + + if i >= watermark { + break + } + assert_ok!(MessageQueue::do_reap_page(&Here, i)); + assert_eq!(QueueChanges::take(), vec![(Here, b.message_count - 1, b.size - 8)]); + } + + // Cannot reap any more pages. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + assert!(QueueChanges::take().is_empty()); + } + }); +} + +#[test] +fn reaping_overweight_fails_properly() { + use MessageOrigin::*; + assert_eq!(MaxStale::get(), 2, "The stale limit is two"); + + new_test_ext::().execute_with(|| { + // page 0 + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("a"), Here); + // page 1 + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("b"), Here); + // page 2 + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("c"), Here); + // page 3 + MessageQueue::enqueue_message(msg("bigbig 1"), Here); + // page 4 + MessageQueue::enqueue_message(msg("bigbig 2"), Here); + // page 5 + MessageQueue::enqueue_message(msg("bigbig 3"), Here); + // Double-check that exactly these pages exist. + assert_pages(&[0, 1, 2, 3, 4, 5]); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here), (vmsg("b"), Here)]); + // 2 stale now. + + // Nothing reapable yet, because we haven't hit the stale limit. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + assert_pages(&[0, 1, 2, 3, 4, 5]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("c"), Here)]); + // 3 stale now: can take something 4 pages in history. + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 1"), Here)]); + + // Nothing reapable yet, because we haven't hit the stale limit. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + assert_pages(&[0, 1, 2, 4, 5]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 2"), Here)]); + assert_pages(&[0, 1, 2, 5]); + + // First is now reapable as it is too far behind the first ready page (5). + assert_ok!(MessageQueue::do_reap_page(&Here, 0)); + // Others not reapable yet, because we haven't hit the stale limit. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + assert_pages(&[1, 2, 5]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 3"), Here)]); + + assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); + assert_noop!(MessageQueue::do_reap_page(&Here, 3), Error::::NoPage); + assert_noop!(MessageQueue::do_reap_page(&Here, 4), Error::::NoPage); + // Still not reapable, since the number of stale pages is only 2. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + }); +} + +#[test] +fn service_queue_bails() { + // Not enough weight for `service_queue_base`. + new_test_ext::().execute_with(|| { + set_weight("service_queue_base", 2.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); + + assert_storage_noop!(MessageQueue::service_queue(0u32.into(), &mut meter, Weight::MAX)); + assert!(meter.consumed.is_zero()); + }); + // Not enough weight for `ready_ring_unknit`. + new_test_ext::().execute_with(|| { + set_weight("ready_ring_unknit", 2.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); + + assert_storage_noop!(MessageQueue::service_queue(0u32.into(), &mut meter, Weight::MAX)); + assert!(meter.consumed.is_zero()); + }); + // Not enough weight for `service_queue_base` and `ready_ring_unknit`. + new_test_ext::().execute_with(|| { + set_weight("service_queue_base", 2.into_weight()); + set_weight("ready_ring_unknit", 2.into_weight()); + + let mut meter = WeightMeter::from_limit(3.into_weight()); + assert_storage_noop!(MessageQueue::service_queue(0.into(), &mut meter, Weight::MAX)); + assert!(meter.consumed.is_zero()); + }); +} + +#[test] +fn service_page_works() { + use super::integration_test::Test; // Run with larger page size. + use MessageOrigin::*; + use PageExecutionStatus::*; + new_test_ext::().execute_with(|| { + set_weight("service_page_base_completion", 2.into_weight()); + set_weight("service_page_item", 3.into_weight()); + + let (page, mut msgs) = full_page::(); + assert!(msgs >= 10, "pre-condition: need at least 10 msgs per page"); + let mut book = book_for::(&page); + Pages::::insert(Here, 0, page); + + // Call it a few times each with a random weight limit. + let mut rng = rand::rngs::StdRng::seed_from_u64(42); + while msgs > 0 { + let process = rng.gen_range(0..=msgs); + msgs -= process; + + // Enough weight to process `process` messages. + let mut meter = WeightMeter::from_limit(((2 + (3 + 1) * process) as u64).into_weight()); + System::reset_events(); + let (processed, status) = + crate::Pallet::::service_page(&Here, &mut book, &mut meter, Weight::MAX); + assert_eq!(processed as usize, process); + assert_eq!(NumMessagesProcessed::take(), process); + assert_eq!(System::events().len(), process); + if msgs == 0 { + assert_eq!(status, NoMore); + } else { + assert_eq!(status, Bailed); + } + } + assert!(!Pages::::contains_key(Here, 0), "The page got removed"); + }); +} + +// `service_page` does nothing when called with an insufficient weight limit. +#[test] +fn service_page_bails() { + // Not enough weight for `service_page_base_completion`. + new_test_ext::().execute_with(|| { + set_weight("service_page_base_completion", 2.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); + + let (page, _) = full_page::(); + let mut book = book_for::(&page); + Pages::::insert(MessageOrigin::Here, 0, page); + + assert_storage_noop!(MessageQueue::service_page( + &MessageOrigin::Here, + &mut book, + &mut meter, + Weight::MAX + )); + assert!(meter.consumed.is_zero()); + }); + // Not enough weight for `service_page_base_no_completion`. + new_test_ext::().execute_with(|| { + set_weight("service_page_base_no_completion", 2.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); + + let (page, _) = full_page::(); + let mut book = book_for::(&page); + Pages::::insert(MessageOrigin::Here, 0, page); + + assert_storage_noop!(MessageQueue::service_page( + &MessageOrigin::Here, + &mut book, + &mut meter, + Weight::MAX + )); + assert!(meter.consumed.is_zero()); + }); +} + +#[test] +fn service_page_item_bails() { + new_test_ext::().execute_with(|| { + let _guard = StorageNoopGuard::default(); + let (mut page, _) = full_page::(); + let mut weight = WeightMeter::from_limit(10.into_weight()); + let overweight_limit = 10.into_weight(); + set_weight("service_page_item", 11.into_weight()); + + assert_eq!( + MessageQueue::service_page_item( + &MessageOrigin::Here, + 0, + &mut book_for::(&page), + &mut page, + &mut weight, + overweight_limit, + ), + ItemExecutionStatus::Bailed + ); + }); +} + +#[test] +fn bump_service_head_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create a ready ring with three queues. + BookStateFor::::insert(Here, empty_book::()); + knit(&Here); + BookStateFor::::insert(There, empty_book::()); + knit(&There); + BookStateFor::::insert(Everywhere(0), empty_book::()); + knit(&Everywhere(0)); + + // Bump 99 times. + for i in 0..99 { + let current = MessageQueue::bump_service_head(&mut WeightMeter::max_limit()).unwrap(); + assert_eq!(current, [Here, There, Everywhere(0)][i % 3]); + } + + // The ready ring is intact and the service head is still `Here`. + assert_ring(&[Here, There, Everywhere(0)]); + }); +} + +/// `bump_service_head` does nothing when called with an insufficient weight limit. +#[test] +fn bump_service_head_bails() { + new_test_ext::().execute_with(|| { + set_weight("bump_service_head", 2.into_weight()); + setup_bump_service_head::(0.into(), 10.into()); + + let _guard = StorageNoopGuard::default(); + let mut meter = WeightMeter::from_limit(1.into_weight()); + assert!(MessageQueue::bump_service_head(&mut meter).is_none()); + assert_eq!(meter.consumed, 0.into_weight()); + }); +} + +#[test] +fn bump_service_head_trivial_works() { + new_test_ext::().execute_with(|| { + set_weight("bump_service_head", 2.into_weight()); + let mut meter = WeightMeter::max_limit(); + + assert_eq!(MessageQueue::bump_service_head(&mut meter), None, "Cannot bump"); + assert_eq!(meter.consumed, 2.into_weight()); + + setup_bump_service_head::(0.into(), 1.into()); + + assert_eq!(MessageQueue::bump_service_head(&mut meter), Some(0.into())); + assert_eq!(ServiceHead::::get().unwrap(), 1.into(), "Bumped the head"); + assert_eq!(meter.consumed, 4.into_weight()); + + assert_eq!(MessageQueue::bump_service_head(&mut meter), None, "Cannot bump"); + assert_eq!(meter.consumed, 6.into_weight()); + }); +} + +#[test] +fn bump_service_head_no_head_noops() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create a ready ring with three queues. + BookStateFor::::insert(Here, empty_book::()); + knit(&Here); + BookStateFor::::insert(There, empty_book::()); + knit(&There); + BookStateFor::::insert(Everywhere(0), empty_book::()); + knit(&Everywhere(0)); + + // But remove the service head. + ServiceHead::::kill(); + + // Nothing happens. + assert_storage_noop!(MessageQueue::bump_service_head(&mut WeightMeter::max_limit())); + }); +} + +#[test] +fn service_page_item_consumes_correct_weight() { + new_test_ext::().execute_with(|| { + let mut page = page::(b"weight=3"); + let mut weight = WeightMeter::from_limit(10.into_weight()); + let overweight_limit = 0.into_weight(); + set_weight("service_page_item", 2.into_weight()); + + assert_eq!( + MessageQueue::service_page_item( + &MessageOrigin::Here, + 0, + &mut book_for::(&page), + &mut page, + &mut weight, + overweight_limit + ), + ItemExecutionStatus::Executed(true) + ); + assert_eq!(weight.consumed, 5.into_weight()); + }); +} + +/// `service_page_item` skips a permanently `Overweight` message and marks it as `unprocessed`. +#[test] +fn service_page_item_skips_perm_overweight_message() { + new_test_ext::().execute_with(|| { + let mut page = page::(b"TooMuch"); + let mut weight = WeightMeter::from_limit(2.into_weight()); + let overweight_limit = 0.into_weight(); + set_weight("service_page_item", 2.into_weight()); + + assert_eq!( + crate::Pallet::::service_page_item( + &MessageOrigin::Here, + 0, + &mut book_for::(&page), + &mut page, + &mut weight, + overweight_limit + ), + ItemExecutionStatus::Executed(false) + ); + assert_eq!(weight.consumed, 2.into_weight()); + assert_last_event::( + Event::OverweightEnqueued { + hash: ::Hashing::hash(b"TooMuch"), + origin: MessageOrigin::Here, + message_index: 0, + page_index: 0, + } + .into(), + ); + + // Check that the message was skipped. + let (pos, processed, payload) = page.peek_index(0).unwrap(); + assert_eq!(pos, 0); + assert!(!processed); + assert_eq!(payload, b"TooMuch".encode()); + }); +} + +#[test] +fn peek_index_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + // Fill a page with messages. + let (mut page, msgs) = full_page::(); + let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; + + for i in 0..msgs { + // Skip all even messages. + page.skip_first(i % 2 == 0); + // Peek each message and check that it is correct. + let (pos, processed, payload) = page.peek_index(i).unwrap(); + assert_eq!(pos, msg_enc_len * i); + assert_eq!(processed, i % 2 == 0); + // `full_page` uses the index as payload. + assert_eq!(payload, (i as u32).encode()); + } + }); +} + +#[test] +fn peek_first_and_skip_first_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + // Fill a page with messages. + let (mut page, msgs) = full_page::(); + + for i in 0..msgs { + let msg = page.peek_first().unwrap(); + // `full_page` uses the index as payload. + assert_eq!(msg.deref(), (i as u32).encode()); + page.skip_first(i % 2 == 0); // True of False should not matter here. + } + assert!(page.peek_first().is_none(), "Page must be at the end"); + + // Check that all messages were correctly marked as (un)processed. + for i in 0..msgs { + let (_, processed, _) = page.peek_index(i).unwrap(); + assert_eq!(processed, i % 2 == 0); + } + }); +} + +#[test] +fn note_processed_at_pos_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + let (mut page, msgs) = full_page::(); + + for i in 0..msgs { + let (pos, processed, _) = page.peek_index(i).unwrap(); + assert!(!processed); + assert_eq!(page.remaining as usize, msgs - i); + + page.note_processed_at_pos(pos); + + let (_, processed, _) = page.peek_index(i).unwrap(); + assert!(processed); + assert_eq!(page.remaining as usize, msgs - i - 1); + } + // `skip_first` still works fine. + for _ in 0..msgs { + page.peek_first().unwrap(); + page.skip_first(false); + } + assert!(page.peek_first().is_none()); + }); +} + +#[test] +fn note_processed_at_pos_idempotent() { + let (mut page, _) = full_page::(); + page.note_processed_at_pos(0); + + let original = page.clone(); + page.note_processed_at_pos(0); + assert_eq!(page.heap, original.heap); +} + +#[test] +fn is_complete_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + let (mut page, msgs) = full_page::(); + assert!(msgs > 3, "Boring"); + let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; + + assert!(!page.is_complete()); + for i in 0..msgs { + if i % 2 == 0 { + page.skip_first(false); + } else { + page.note_processed_at_pos(msg_enc_len * i); + } + } + // Not complete since `skip_first` was called with `false`. + assert!(!page.is_complete()); + for i in 0..msgs { + if i % 2 == 0 { + assert!(!page.is_complete()); + let (pos, _, _) = page.peek_index(i).unwrap(); + page.note_processed_at_pos(pos); + } + } + assert!(page.is_complete()); + assert_eq!(page.remaining_size, 0); + // Each message is marked as processed. + for i in 0..msgs { + let (_, processed, _) = page.peek_index(i).unwrap(); + assert!(processed); + } + }); +} + +#[test] +fn page_from_message_basic_works() { + assert!(MaxMessageLenOf::::get() > 0, "pre-condition unmet"); + let mut msg: BoundedVec> = Default::default(); + msg.bounded_resize(MaxMessageLenOf::::get() as usize, 123); + + let page = PageOf::::from_message::(msg.as_bounded_slice()); + assert_eq!(page.remaining, 1); + assert_eq!(page.remaining_size as usize, msg.len()); + assert!(page.first_index == 0 && page.first == 0 && page.last == 0); + + // Verify the content of the heap. + let mut heap = Vec::::new(); + let header = + ItemHeader::<::Size> { payload_len: msg.len() as u32, is_processed: false }; + heap.extend(header.encode()); + heap.extend(msg.deref()); + assert_eq!(page.heap, heap); +} + +#[test] +fn page_try_append_message_basic_works() { + use super::integration_test::Test; // Run with larger page size. + + let mut page = PageOf::::default(); + let mut msgs = 0; + // Append as many 4-byte message as possible. + for i in 0..u32::MAX { + let r = i.using_encoded(|i| page.try_append_message::(i.try_into().unwrap())); + if r.is_err() { + break + } else { + msgs += 1; + } + } + let expected_msgs = (::HeapSize::get()) / + (ItemHeader::<::Size>::max_encoded_len() as u32 + 4); + assert_eq!(expected_msgs, msgs, "Wrong number of messages"); + assert_eq!(page.remaining, msgs); + assert_eq!(page.remaining_size, msgs * 4); + + // Verify that the heap content is correct. + let mut heap = Vec::::new(); + for i in 0..msgs { + let header = ItemHeader::<::Size> { payload_len: 4, is_processed: false }; + heap.extend(header.encode()); + heap.extend(i.encode()); + } + assert_eq!(page.heap, heap); +} + +#[test] +fn page_try_append_message_max_msg_len_works_works() { + use super::integration_test::Test; // Run with larger page size. + + // We start off with an empty page. + let mut page = PageOf::::default(); + // … and append a message with maximum possible length. + let msg = vec![123u8; MaxMessageLenOf::::get() as usize]; + // … which works. + page.try_append_message::(BoundedSlice::defensive_truncate_from(&msg)) + .unwrap(); + // Now we cannot append *anything* since the heap is full. + page.try_append_message::(BoundedSlice::defensive_truncate_from(&[])) + .unwrap_err(); + assert_eq!(page.heap.len(), ::HeapSize::get() as usize); +} + +#[test] +fn page_try_append_message_with_remaining_size_works_works() { + use super::integration_test::Test; // Run with larger page size. + let header_size = ItemHeader::<::Size>::max_encoded_len(); + + // We start off with an empty page. + let mut page = PageOf::::default(); + let mut remaining = ::HeapSize::get() as usize; + let mut msgs = Vec::new(); + let mut rng = StdRng::seed_from_u64(42); + // Now we keep appending messages with different lengths. + while remaining >= header_size { + let take = rng.gen_range(0..=(remaining - header_size)); + let msg = vec![123u8; take]; + page.try_append_message::(BoundedSlice::defensive_truncate_from(&msg)) + .unwrap(); + remaining -= take + header_size; + msgs.push(msg); + } + // Cannot even fit a single header in there now. + assert!(remaining < header_size); + assert_eq!(::HeapSize::get() as usize - page.heap.len(), remaining); + assert_eq!(page.remaining as usize, msgs.len()); + assert_eq!( + page.remaining_size as usize, + msgs.iter().fold(0, |mut a, m| { + a += m.len(); + a + }) + ); + // Verify the heap content. + let mut heap = Vec::new(); + for msg in msgs.into_iter() { + let header = ItemHeader::<::Size> { + payload_len: msg.len() as u32, + is_processed: false, + }; + heap.extend(header.encode()); + heap.extend(msg); + } + assert_eq!(page.heap, heap); +} + +// `Page::from_message` does not panic when called with the maximum message and origin lengths. +#[test] +fn page_from_message_max_len_works() { + let max_msg_len: usize = MaxMessageLenOf::::get() as usize; + + let page = PageOf::::from_message::(vec![1; max_msg_len][..].try_into().unwrap()); + + assert_eq!(page.remaining, 1); +} + +#[test] +fn sweep_queue_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + build_triple_ring(); + + let book = BookStateFor::::get(Here); + assert!(book.begin != book.end); + // Removing the service head works + assert_eq!(ServiceHead::::get(), Some(Here)); + MessageQueue::sweep_queue(Here); + assert_ring(&[There, Everywhere(0)]); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(Here); + assert_eq!(book.begin, book.end); + + // Removing something that is not the service head works. + assert!(ServiceHead::::get() != Some(Everywhere(0))); + MessageQueue::sweep_queue(Everywhere(0)); + assert_ring(&[There]); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(Everywhere(0)); + assert_eq!(book.begin, book.end); + + MessageQueue::sweep_queue(There); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(There); + assert_eq!(book.begin, book.end); + assert_ring(&[]); + + // Sweeping a queue never calls OnQueueChanged. + assert!(QueueChanges::take().is_empty()); + }) +} + +/// Test that `sweep_queue` also works if the ReadyRing wraps around. +#[test] +fn sweep_queue_wraps_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + BookStateFor::::insert(Here, empty_book::()); + knit(&Here); + + MessageQueue::sweep_queue(Here); + let book = BookStateFor::::get(Here); + assert!(book.ready_neighbours.is_none()); + }); +} + +#[test] +fn sweep_queue_invalid_noops() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + assert_storage_noop!(MessageQueue::sweep_queue(Here)); + }); +} + +#[test] +fn footprint_works() { + new_test_ext::().execute_with(|| { + let origin = MessageOrigin::Here; + let (page, msgs) = full_page::(); + let book = book_for::(&page); + BookStateFor::::insert(origin, book); + + let info = MessageQueue::footprint(origin); + assert_eq!(info.count as usize, msgs); + assert_eq!(info.size, page.remaining_size as u64); + + // Sweeping a queue never calls OnQueueChanged. + assert!(QueueChanges::take().is_empty()); + }) +} + +/// The footprint of an invalid queue is the default footprint. +#[test] +fn footprint_invalid_works() { + new_test_ext::().execute_with(|| { + let origin = MessageOrigin::Here; + assert_eq!(MessageQueue::footprint(origin), Default::default()); + }) +} + +/// The footprint of a swept queue is still correct. +#[test] +fn footprint_on_swept_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + let mut book = empty_book::(); + book.message_count = 3; + book.size = 10; + BookStateFor::::insert(Here, &book); + knit(&Here); + + MessageQueue::sweep_queue(Here); + let fp = MessageQueue::footprint(Here); + assert_eq!(fp.count, 3); + assert_eq!(fp.size, 10); + }) +} + +#[test] +fn execute_overweight_works() { + new_test_ext::().execute_with(|| { + set_weight("bump_service_head", 1.into_weight()); + set_weight("service_queue_base", 1.into_weight()); + set_weight("service_page_base_completion", 1.into_weight()); + + // Enqueue a message + let origin = MessageOrigin::Here; + MessageQueue::enqueue_message(msg("weight=6"), origin); + // Load the current book + let book = BookStateFor::::get(origin); + assert_eq!(book.message_count, 1); + assert!(Pages::::contains_key(origin, 0)); + + // Mark the message as permanently overweight. + assert_eq!(MessageQueue::service_queues(4.into_weight()), 4.into_weight()); + assert_eq!(QueueChanges::take(), vec![(origin, 1, 8)]); + assert_last_event::( + Event::OverweightEnqueued { + hash: ::Hashing::hash(b"weight=6"), + origin: MessageOrigin::Here, + message_index: 0, + page_index: 0, + } + .into(), + ); + + // Now try to execute it with too few weight. + let consumed = + ::execute_overweight(5.into_weight(), (origin, 0, 0)); + assert_eq!(consumed, Err(ExecuteOverweightError::InsufficientWeight)); + + // Execute it with enough weight. + assert_eq!(Pages::::iter().count(), 1); + assert!(QueueChanges::take().is_empty()); + let consumed = + ::execute_overweight(7.into_weight(), (origin, 0, 0)) + .unwrap(); + assert_eq!(consumed, 6.into_weight()); + assert_eq!(QueueChanges::take(), vec![(origin, 0, 0)]); + // There is no message left in the book. + let book = BookStateFor::::get(origin); + assert_eq!(book.message_count, 0); + // And no more pages. + assert_eq!(Pages::::iter().count(), 0); + + // Doing it again with enough weight will error. + let consumed = + ::execute_overweight(70.into_weight(), (origin, 0, 0)); + assert_eq!(consumed, Err(ExecuteOverweightError::NotFound)); + assert!(QueueChanges::take().is_empty()); + assert!(!Pages::::contains_key(origin, 0), "Page is gone"); + }); +} + +/// Checks that (un)knitting the ready ring works with just one queue. +/// +/// This case is interesting since it wraps and a lot of `mutate` now operate on the same object. +#[test] +fn ready_ring_knit_basic_works() { + use MessageOrigin::*; + + new_test_ext::().execute_with(|| { + BookStateFor::::insert(Here, empty_book::()); + + for i in 0..10 { + if i % 2 == 0 { + knit(&Here); + assert_ring(&[Here]); + } else { + unknit(&Here); + assert_ring(&[]); + } + } + assert_ring(&[]); + }); +} + +#[test] +fn ready_ring_knit_and_unknit_works() { + use MessageOrigin::*; + + new_test_ext::().execute_with(|| { + // Place three queues into the storage. + BookStateFor::::insert(Here, empty_book::()); + BookStateFor::::insert(There, empty_book::()); + BookStateFor::::insert(Everywhere(0), empty_book::()); + + // Knit them into the ready ring. + assert_ring(&[]); + knit(&Here); + assert_ring(&[Here]); + knit(&There); + assert_ring(&[Here, There]); + knit(&Everywhere(0)); + assert_ring(&[Here, There, Everywhere(0)]); + + // Now unknit… + unknit(&Here); + assert_ring(&[There, Everywhere(0)]); + unknit(&There); + assert_ring(&[Everywhere(0)]); + unknit(&Everywhere(0)); + assert_ring(&[]); + }); +} + +#[test] +fn enqueue_message_works() { + use MessageOrigin::*; + let max_msg_per_page = ::HeapSize::get() as u64 / + (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); + + new_test_ext::().execute_with(|| { + // Enqueue messages which should fill three pages. + let n = max_msg_per_page * 3; + for i in 1..=n { + MessageQueue::enqueue_message(msg("a"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, i, i)], "OnQueueChanged not called"); + } + assert_eq!(Pages::::iter().count(), 3); + + // Enqueue one more onto page 4. + MessageQueue::enqueue_message(msg("abc"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); + assert_eq!(Pages::::iter().count(), 4); + + // Check the state. + assert_eq!(BookStateFor::::iter().count(), 1); + let book = BookStateFor::::get(Here); + assert_eq!(book.message_count, n + 1); + assert_eq!(book.size, n + 3); + assert_eq!((book.begin, book.end), (0, 4)); + assert_eq!(book.count as usize, Pages::::iter().count()); + }); +} + +#[test] +fn enqueue_messages_works() { + use MessageOrigin::*; + let max_msg_per_page = ::HeapSize::get() as u64 / + (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); + + new_test_ext::().execute_with(|| { + // Enqueue messages which should fill three pages. + let n = max_msg_per_page * 3; + let msgs = vec![msg("a"); n as usize]; + + // Now queue all messages at once. + MessageQueue::enqueue_messages(msgs.into_iter(), Here); + // The changed handler should only be called once. + assert_eq!(QueueChanges::take(), vec![(Here, n, n)], "OnQueueChanged not called"); + assert_eq!(Pages::::iter().count(), 3); + + // Enqueue one more onto page 4. + MessageQueue::enqueue_message(msg("abc"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); + assert_eq!(Pages::::iter().count(), 4); + + // Check the state. + assert_eq!(BookStateFor::::iter().count(), 1); + let book = BookStateFor::::get(Here); + assert_eq!(book.message_count, n + 1); + assert_eq!(book.size, n + 3); + assert_eq!((book.begin, book.end), (0, 4)); + assert_eq!(book.count as usize, Pages::::iter().count()); + }); +} diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs new file mode 100644 index 0000000000000..cd9268ffde224 --- /dev/null +++ b/frame/message-queue/src/weights.rs @@ -0,0 +1,216 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_message_queue +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/substrate +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_message_queue +// --chain=dev +// --header=./HEADER-APACHE2 +// --output=./frame/message-queue/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_message_queue. +pub trait WeightInfo { + fn ready_ring_knit() -> Weight; + fn ready_ring_unknit() -> Weight; + fn service_queue_base() -> Weight; + fn service_page_base_completion() -> Weight; + fn service_page_base_no_completion() -> Weight; + fn service_page_item() -> Weight; + fn bump_service_head() -> Weight; + fn reap_page() -> Weight; + fn execute_overweight_page_removed() -> Weight; + fn execute_overweight_page_updated() -> Weight; +} + +/// Weights for pallet_message_queue using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: MessageQueue ServiceHead (r:1 w:0) + // Storage: MessageQueue BookStateFor (r:2 w:2) + fn ready_ring_knit() -> Weight { + // Minimum execution time: 12_330 nanoseconds. + Weight::from_ref_time(12_711_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:2 w:2) + // Storage: MessageQueue ServiceHead (r:1 w:1) + fn ready_ring_unknit() -> Weight { + // Minimum execution time: 12_322 nanoseconds. + Weight::from_ref_time(12_560_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + fn service_queue_base() -> Weight { + // Minimum execution time: 4_652 nanoseconds. + Weight::from_ref_time(4_848_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: MessageQueue Pages (r:1 w:1) + fn service_page_base_completion() -> Weight { + // Minimum execution time: 7_115 nanoseconds. + Weight::from_ref_time(7_407_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: MessageQueue Pages (r:1 w:1) + fn service_page_base_no_completion() -> Weight { + // Minimum execution time: 6_974 nanoseconds. + Weight::from_ref_time(7_200_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn service_page_item() -> Weight { + // Minimum execution time: 79_657 nanoseconds. + Weight::from_ref_time(80_050_000) + } + // Storage: MessageQueue ServiceHead (r:1 w:1) + // Storage: MessageQueue BookStateFor (r:1 w:0) + fn bump_service_head() -> Weight { + // Minimum execution time: 7_598 nanoseconds. + Weight::from_ref_time(8_118_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn reap_page() -> Weight { + // Minimum execution time: 60_562 nanoseconds. + Weight::from_ref_time(61_430_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight_page_removed() -> Weight { + // Minimum execution time: 74_582 nanoseconds. + Weight::from_ref_time(75_445_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight_page_updated() -> Weight { + // Minimum execution time: 87_526 nanoseconds. + Weight::from_ref_time(88_055_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: MessageQueue ServiceHead (r:1 w:0) + // Storage: MessageQueue BookStateFor (r:2 w:2) + fn ready_ring_knit() -> Weight { + // Minimum execution time: 12_330 nanoseconds. + Weight::from_ref_time(12_711_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:2 w:2) + // Storage: MessageQueue ServiceHead (r:1 w:1) + fn ready_ring_unknit() -> Weight { + // Minimum execution time: 12_322 nanoseconds. + Weight::from_ref_time(12_560_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + fn service_queue_base() -> Weight { + // Minimum execution time: 4_652 nanoseconds. + Weight::from_ref_time(4_848_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: MessageQueue Pages (r:1 w:1) + fn service_page_base_completion() -> Weight { + // Minimum execution time: 7_115 nanoseconds. + Weight::from_ref_time(7_407_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: MessageQueue Pages (r:1 w:1) + fn service_page_base_no_completion() -> Weight { + // Minimum execution time: 6_974 nanoseconds. + Weight::from_ref_time(7_200_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + fn service_page_item() -> Weight { + // Minimum execution time: 79_657 nanoseconds. + Weight::from_ref_time(80_050_000) + } + // Storage: MessageQueue ServiceHead (r:1 w:1) + // Storage: MessageQueue BookStateFor (r:1 w:0) + fn bump_service_head() -> Weight { + // Minimum execution time: 7_598 nanoseconds. + Weight::from_ref_time(8_118_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn reap_page() -> Weight { + // Minimum execution time: 60_562 nanoseconds. + Weight::from_ref_time(61_430_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight_page_removed() -> Weight { + // Minimum execution time: 74_582 nanoseconds. + Weight::from_ref_time(75_445_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight_page_updated() -> Weight { + // Minimum execution time: 87_526 nanoseconds. + Weight::from_ref_time(88_055_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } +} diff --git a/frame/multisig/Cargo.toml b/frame/multisig/Cargo.toml index 177334d4ccf8d..4e9f4f5d832e6 100644 --- a/frame/multisig/Cargo.toml +++ b/frame/multisig/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-multisig" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME multi-signature dispatch pallet" readme = "README.md" @@ -13,33 +13,36 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +# third party +log = { version = "0.4.17", default-features = false } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "codec/std", - "scale-info/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", + "scale-info/std", "sp-io/std", - "sp-std/std" + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/multisig/src/benchmarking.rs b/frame/multisig/src/benchmarking.rs index 2e23dff156e07..d5faf9ae8ac1a 100644 --- a/frame/multisig/src/benchmarking.rs +++ b/frame/multisig/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use core::convert::TryInto; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; +use frame_benchmarking::{account, benchmarks}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; @@ -29,7 +28,10 @@ use crate::Pallet as Multisig; const SEED: u32 = 0; -fn setup_multi(s: u32, z: u32) -> Result<(Vec, Vec), &'static str> { +fn setup_multi( + s: u32, + z: u32, +) -> Result<(Vec, Box<::RuntimeCall>), &'static str> { let mut signatories: Vec = Vec::new(); for i in 0..s { let signatory = account("signatory", i, SEED); @@ -39,11 +41,10 @@ fn setup_multi(s: u32, z: u32) -> Result<(Vec, Vec) signatories.push(signatory); } signatories.sort(); - // Must first convert to outer call type. - let call: ::Call = + // Must first convert to runtime call type. + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![0; z as usize] }.into(); - let call_data = call.encode(); - return Ok((signatories, call_data)) + Ok((signatories, Box::new(call))) } benchmarks! { @@ -52,7 +53,7 @@ benchmarks! { let z in 0 .. 10_000; let max_signatories = T::MaxSignatories::get().into(); let (mut signatories, _) = setup_multi::(max_signatories, z)?; - let call: ::Call = frame_system::Call::::remark { + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![0; z as usize] }.into(); let call_hash = call.using_encoded(blake2_256); @@ -68,235 +69,146 @@ benchmarks! { as_multi_create { // Signatories, need at least 2 total people - let s in 2 .. T::MaxSignatories::get() as u32; + let s in 2 .. T::MaxSignatories::get(); // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; - let call_hash = blake2_256(&call); - let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); - let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; - // Whitelist caller account from further DB operations. - let caller_key = frame_system::Account::::hashed_key_for(&caller); - frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); - }: as_multi(RawOrigin::Signed(caller), s as u16, signatories, None, call, false, 0) - verify { - assert!(Multisigs::::contains_key(multi_account_id, call_hash)); - assert!(!Calls::::contains_key(call_hash)); - } - - as_multi_create_store { - // Signatories, need at least 2 total people - let s in 2 .. T::MaxSignatories::get() as u32; - // Transaction Length - let z in 0 .. 10_000; - let (mut signatories, call) = setup_multi::(s, z)?; - let call_hash = blake2_256(&call); + let call_hash = call.using_encoded(blake2_256); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); - }: as_multi(RawOrigin::Signed(caller), s as u16, signatories, None, call, true, 0) + }: as_multi(RawOrigin::Signed(caller), s as u16, signatories, None, call, Weight::zero()) verify { assert!(Multisigs::::contains_key(multi_account_id, call_hash)); - assert!(Calls::::contains_key(call_hash)); } as_multi_approve { // Signatories, need at least 3 people (so we don't complete the multisig) - let s in 3 .. T::MaxSignatories::get() as u32; + let s in 3 .. T::MaxSignatories::get(); // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; - let call_hash = blake2_256(&call); - let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); - let mut signatories2 = signatories.clone(); - let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; - // before the call, get the timepoint - let timepoint = Multisig::::timepoint(); - // Create the multi, storing for worst case - Multisig::::as_multi(RawOrigin::Signed(caller).into(), s as u16, signatories, None, call.clone(), true, 0)?; - assert!(Calls::::contains_key(call_hash)); - let caller2 = signatories2.remove(0); - // Whitelist caller account from further DB operations. - let caller_key = frame_system::Account::::hashed_key_for(&caller2); - frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); - }: as_multi(RawOrigin::Signed(caller2), s as u16, signatories2, Some(timepoint), call, false, 0) - verify { - let multisig = Multisigs::::get(multi_account_id, call_hash).ok_or("multisig not created")?; - assert_eq!(multisig.approvals.len(), 2); - } - - as_multi_approve_store { - // Signatories, need at least 3 people (so we don't complete the multisig) - let s in 3 .. T::MaxSignatories::get() as u32; - // Transaction Length - let z in 0 .. 10_000; - let (mut signatories, call) = setup_multi::(s, z)?; - let call_hash = blake2_256(&call); + let call_hash = call.using_encoded(blake2_256); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let mut signatories2 = signatories.clone(); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; // before the call, get the timepoint let timepoint = Multisig::::timepoint(); - // Create the multi, not storing - Multisig::::as_multi(RawOrigin::Signed(caller).into(), s as u16, signatories, None, call.clone(), false, 0)?; - assert!(!Calls::::contains_key(call_hash)); + // Create the multi + Multisig::::as_multi(RawOrigin::Signed(caller).into(), s as u16, signatories, None, call.clone(), Weight::zero())?; let caller2 = signatories2.remove(0); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller2); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); - }: as_multi(RawOrigin::Signed(caller2), s as u16, signatories2, Some(timepoint), call, true, 0) + }: as_multi(RawOrigin::Signed(caller2), s as u16, signatories2, Some(timepoint), call, Weight::zero()) verify { let multisig = Multisigs::::get(multi_account_id, call_hash).ok_or("multisig not created")?; assert_eq!(multisig.approvals.len(), 2); - assert!(Calls::::contains_key(call_hash)); } as_multi_complete { // Signatories, need at least 2 people - let s in 2 .. T::MaxSignatories::get() as u32; + let s in 2 .. T::MaxSignatories::get(); // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; - let call_hash = blake2_256(&call); + let call_hash = call.using_encoded(blake2_256); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let mut signatories2 = signatories.clone(); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; // before the call, get the timepoint let timepoint = Multisig::::timepoint(); - // Create the multi, storing it for worst case - Multisig::::as_multi(RawOrigin::Signed(caller).into(), s as u16, signatories, None, call.clone(), true, 0)?; + // Create the multi + Multisig::::as_multi(RawOrigin::Signed(caller).into(), s as u16, signatories, None, call.clone(), Weight::zero())?; // Everyone except the first person approves for i in 1 .. s - 1 { let mut signatories_loop = signatories2.clone(); let caller_loop = signatories_loop.remove(i as usize); let o = RawOrigin::Signed(caller_loop).into(); - Multisig::::as_multi(o, s as u16, signatories_loop, Some(timepoint), call.clone(), false, 0)?; + Multisig::::as_multi(o, s as u16, signatories_loop, Some(timepoint), call.clone(), Weight::zero())?; } let caller2 = signatories2.remove(0); assert!(Multisigs::::contains_key(&multi_account_id, call_hash)); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller2); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); - }: as_multi(RawOrigin::Signed(caller2), s as u16, signatories2, Some(timepoint), call, false, Weight::max_value()) + }: as_multi(RawOrigin::Signed(caller2), s as u16, signatories2, Some(timepoint), call, Weight::MAX) verify { assert!(!Multisigs::::contains_key(&multi_account_id, call_hash)); } approve_as_multi_create { // Signatories, need at least 2 people - let s in 2 .. T::MaxSignatories::get() as u32; + let s in 2 .. T::MaxSignatories::get(); // Transaction Length, not a component let z = 10_000; let (mut signatories, call) = setup_multi::(s, z)?; let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; - let call_hash = blake2_256(&call); + let call_hash = call.using_encoded(blake2_256); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); // Create the multi - }: approve_as_multi(RawOrigin::Signed(caller), s as u16, signatories, None, call_hash, 0) + }: approve_as_multi(RawOrigin::Signed(caller), s as u16, signatories, None, call_hash, Weight::zero()) verify { assert!(Multisigs::::contains_key(multi_account_id, call_hash)); } approve_as_multi_approve { // Signatories, need at least 2 people - let s in 2 .. T::MaxSignatories::get() as u32; + let s in 2 .. T::MaxSignatories::get(); // Transaction Length, not a component let z = 10_000; let (mut signatories, call) = setup_multi::(s, z)?; let mut signatories2 = signatories.clone(); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; - let call_hash = blake2_256(&call); + let call_hash = call.using_encoded(blake2_256); // before the call, get the timepoint let timepoint = Multisig::::timepoint(); // Create the multi Multisig::::as_multi( - RawOrigin::Signed(caller.clone()).into(), + RawOrigin::Signed(caller).into(), s as u16, signatories, None, - call.clone(), - false, - 0 + call, + Weight::zero() )?; let caller2 = signatories2.remove(0); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller2); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); - }: approve_as_multi(RawOrigin::Signed(caller2), s as u16, signatories2, Some(timepoint), call_hash, 0) + }: approve_as_multi(RawOrigin::Signed(caller2), s as u16, signatories2, Some(timepoint), call_hash, Weight::zero()) verify { let multisig = Multisigs::::get(multi_account_id, call_hash).ok_or("multisig not created")?; assert_eq!(multisig.approvals.len(), 2); } - approve_as_multi_complete { - // Signatories, need at least 2 people - let s in 2 .. T::MaxSignatories::get() as u32; - // Transaction Length, not a component - let z = 10_000; - let (mut signatories, call) = setup_multi::(s, z)?; - let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); - let mut signatories2 = signatories.clone(); - let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - let call_hash = blake2_256(&call); - // before the call, get the timepoint - let timepoint = Multisig::::timepoint(); - // Create the multi - Multisig::::as_multi(RawOrigin::Signed(caller).into(), s as u16, signatories, None, call.clone(), true, 0)?; - // Everyone except the first person approves - for i in 1 .. s - 1 { - let mut signatories_loop = signatories2.clone(); - let caller_loop = signatories_loop.remove(i as usize); - let o = RawOrigin::Signed(caller_loop).into(); - Multisig::::as_multi(o, s as u16, signatories_loop, Some(timepoint), call.clone(), false, 0)?; - } - let caller2 = signatories2.remove(0); - assert!(Multisigs::::contains_key(&multi_account_id, call_hash)); - // Whitelist caller account from further DB operations. - let caller_key = frame_system::Account::::hashed_key_for(&caller2); - frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); - }: approve_as_multi( - RawOrigin::Signed(caller2), - s as u16, - signatories2, - Some(timepoint), - call_hash, - Weight::max_value() - ) - verify { - assert!(!Multisigs::::contains_key(multi_account_id, call_hash)); - } - cancel_as_multi { // Signatories, need at least 2 people - let s in 2 .. T::MaxSignatories::get() as u32; + let s in 2 .. T::MaxSignatories::get(); // Transaction Length, not a component let z = 10_000; let (mut signatories, call) = setup_multi::(s, z)?; let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; - let call_hash = blake2_256(&call); + let call_hash = call.using_encoded(blake2_256); let timepoint = Multisig::::timepoint(); // Create the multi let o = RawOrigin::Signed(caller.clone()).into(); - Multisig::::as_multi(o, s as u16, signatories.clone(), None, call.clone(), true, 0)?; + Multisig::::as_multi(o, s as u16, signatories.clone(), None, call, Weight::zero())?; assert!(Multisigs::::contains_key(&multi_account_id, call_hash)); - assert!(Calls::::contains_key(call_hash)); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); }: _(RawOrigin::Signed(caller), s as u16, signatories, timepoint, call_hash) verify { assert!(!Multisigs::::contains_key(multi_account_id, call_hash)); - assert!(!Calls::::contains_key(call_hash)); } -} -impl_benchmark_test_suite!(Multisig, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(Multisig, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/frame/multisig/src/lib.rs b/frame/multisig/src/lib.rs index 43040ada45a98..076a289e06519 100644 --- a/frame/multisig/src/lib.rs +++ b/frame/multisig/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,24 +47,26 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +pub mod migrations; mod tests; pub mod weights; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::{ - DispatchErrorWithPostInfo, DispatchResult, DispatchResultWithPostInfo, PostDispatchInfo, + DispatchErrorWithPostInfo, DispatchResult, DispatchResultWithPostInfo, GetDispatchInfo, + PostDispatchInfo, }, ensure, traits::{Currency, Get, ReservableCurrency}, - weights::{GetDispatchInfo, Weight}, - RuntimeDebug, + weights::Weight, + BoundedVec, RuntimeDebug, }; use frame_system::{self as system, RawOrigin}; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::{ - traits::{Dispatchable, Zero}, + traits::{Dispatchable, TrailingZeroInput, Zero}, DispatchError, }; use sp_std::prelude::*; @@ -72,15 +74,29 @@ pub use weights::WeightInfo; pub use pallet::*; +/// The log target of this pallet. +pub const LOG_TARGET: &'static str = "runtime::multisig"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] ✍️ ", $patter), >::block_number() $(, $values)* + ) + }; +} + type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -/// Just a bunch of bytes, but they should decode to a valid `Call`. -pub type OpaqueCall = Vec; /// A global extrinsic index, formed as the extrinsic index within a block, together with that /// block's height. This allows a transaction in which a multisig operation of a particular /// composite was created to be uniquely identified. -#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] +#[derive( + Copy, Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] pub struct Timepoint { /// The height of the chain at the point in time. height: BlockNumber, @@ -89,8 +105,12 @@ pub struct Timepoint { } /// An open multisig operation. -#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct Multisig { +#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxApprovals))] +pub struct Multisig +where + MaxApprovals: Get, +{ /// The extrinsic when the multisig operation was opened. when: Timepoint, /// The amount held in reserve of the `depositor`, to be returned once the operation ends. @@ -98,13 +118,13 @@ pub struct Multisig { /// The account who opened it (i.e. the first to approve it). depositor: AccountId, /// The approvals achieved so far, including the depositor. Always sorted. - approvals: Vec, + approvals: BoundedVec, } type CallHash = [u8; 32]; -enum CallOrHash { - Call(OpaqueCall, bool), +enum CallOrHash { + Call(::RuntimeCall), Hash([u8; 32]), } @@ -117,11 +137,11 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The overarching call type. - type Call: Parameter - + Dispatchable + type RuntimeCall: Parameter + + Dispatchable + GetDispatchInfo + From>; @@ -145,14 +165,18 @@ pub mod pallet { /// The maximum amount of signatories allowed in the multisig. #[pallet::constant] - type MaxSignatories: Get; + type MaxSignatories: Get; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); /// The set of open multisig operations. @@ -163,13 +187,9 @@ pub mod pallet { T::AccountId, Blake2_128Concat, [u8; 32], - Multisig, T::AccountId>, + Multisig, T::AccountId, T::MaxSignatories>, >; - #[pallet::storage] - pub type Calls = - StorageMap<_, Identity, [u8; 32], (OpaqueCall, T::AccountId, BalanceOf)>; - #[pallet::error] pub enum Error { /// Threshold must be 2 or greater. @@ -205,21 +225,30 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// A new multisig operation has begun. \[approving, multisig, call_hash\] - NewMultisig(T::AccountId, T::AccountId, CallHash), + /// A new multisig operation has begun. + NewMultisig { approving: T::AccountId, multisig: T::AccountId, call_hash: CallHash }, /// A multisig operation has been approved by someone. - /// \[approving, timepoint, multisig, call_hash\] - MultisigApproval(T::AccountId, Timepoint, T::AccountId, CallHash), - /// A multisig operation has been executed. \[approving, timepoint, multisig, call_hash\] - MultisigExecuted( - T::AccountId, - Timepoint, - T::AccountId, - CallHash, - DispatchResult, - ), - /// A multisig operation has been cancelled. \[cancelling, timepoint, multisig, call_hash\] - MultisigCancelled(T::AccountId, Timepoint, T::AccountId, CallHash), + MultisigApproval { + approving: T::AccountId, + timepoint: Timepoint, + multisig: T::AccountId, + call_hash: CallHash, + }, + /// A multisig operation has been executed. + MultisigExecuted { + approving: T::AccountId, + timepoint: Timepoint, + multisig: T::AccountId, + call_hash: CallHash, + result: DispatchResult, + }, + /// A multisig operation has been cancelled. + MultisigCancelled { + cancelling: T::AccountId, + timepoint: Timepoint, + multisig: T::AccountId, + call_hash: CallHash, + }, } #[pallet::hooks] @@ -243,20 +272,21 @@ pub mod pallet { /// - DB Weight: None /// - Plus Call Weight /// # + #[pallet::call_index(0)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( T::WeightInfo::as_multi_threshold_1(call.using_encoded(|c| c.len() as u32)) - .saturating_add(dispatch_info.weight) // AccountData for inner call origin accountdata. - .saturating_add(T::DbWeight::get().reads_writes(1, 1)), + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(dispatch_info.weight), dispatch_info.class, ) })] pub fn as_multi_threshold_1( origin: OriginFor, other_signatories: Vec, - call: Box<::Call>, + call: Box<::RuntimeCall>, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let max_sigs = T::MaxSignatories::get() as usize; @@ -285,8 +315,7 @@ pub mod pallet { let weight_used = T::WeightInfo::as_multi_threshold_1(call_len as u32) .saturating_add(actual_weight); let post_info = Some(weight_used).into(); - let error = err.error.into(); - DispatchErrorWithPostInfo { post_info, error } + DispatchErrorWithPostInfo { post_info, error: err.error } }, None => err, }) @@ -333,16 +362,16 @@ pub mod pallet { /// taken for its lifetime of `DepositBase + threshold * DepositFactor`. /// ------------------------------- /// - DB Weight: - /// - Reads: Multisig Storage, [Caller Account], Calls (if `store_call`) - /// - Writes: Multisig Storage, [Caller Account], Calls (if `store_call`) + /// - Reads: Multisig Storage, [Caller Account] + /// - Writes: Multisig Storage, [Caller Account] /// - Plus Call Weight /// # + #[pallet::call_index(1)] #[pallet::weight({ let s = other_signatories.len() as u32; - let z = call.len() as u32; + let z = call.using_encoded(|d| d.len()) as u32; T::WeightInfo::as_multi_create(s, z) - .max(T::WeightInfo::as_multi_create_store(s, z)) .max(T::WeightInfo::as_multi_approve(s, z)) .max(T::WeightInfo::as_multi_complete(s, z)) .saturating_add(*max_weight) @@ -352,8 +381,7 @@ pub mod pallet { threshold: u16, other_signatories: Vec, maybe_timepoint: Option>, - call: OpaqueCall, - store_call: bool, + call: Box<::RuntimeCall>, max_weight: Weight, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -362,7 +390,7 @@ pub mod pallet { threshold, other_signatories, maybe_timepoint, - CallOrHash::Call(call, store_call), + CallOrHash::Call(*call), max_weight, ) } @@ -402,13 +430,13 @@ pub mod pallet { /// - Read: Multisig Storage, [Caller Account] /// - Write: Multisig Storage, [Caller Account] /// # + #[pallet::call_index(2)] #[pallet::weight({ let s = other_signatories.len() as u32; T::WeightInfo::approve_as_multi_create(s) - .max(T::WeightInfo::approve_as_multi_approve(s)) - .max(T::WeightInfo::approve_as_multi_complete(s)) - .saturating_add(*max_weight) + .max(T::WeightInfo::approve_as_multi_approve(s)) + .saturating_add(*max_weight) })] pub fn approve_as_multi( origin: OriginFor, @@ -452,9 +480,10 @@ pub mod pallet { /// - Storage: removes one item. /// ---------------------------------- /// - DB Weight: - /// - Read: Multisig Storage, [Caller Account], Refund Account, Calls - /// - Write: Multisig Storage, [Caller Account], Refund Account, Calls + /// - Read: Multisig Storage, [Caller Account], Refund Account + /// - Write: Multisig Storage, [Caller Account], Refund Account /// # + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::cancel_as_multi(other_signatories.len() as u32))] pub fn cancel_as_multi( origin: OriginFor, @@ -479,9 +508,13 @@ pub mod pallet { let err_amount = T::Currency::unreserve(&m.depositor, m.deposit); debug_assert!(err_amount.is_zero()); >::remove(&id, &call_hash); - Self::clear_call(&call_hash); - Self::deposit_event(Event::MultisigCancelled(who, timepoint, id, call_hash)); + Self::deposit_event(Event::MultisigCancelled { + cancelling: who, + timepoint, + multisig: id, + call_hash, + }); Ok(()) } } @@ -494,7 +527,8 @@ impl Pallet { /// NOTE: `who` must be sorted. If it is not, then you'll get the wrong answer. pub fn multi_account_id(who: &[T::AccountId], threshold: u16) -> T::AccountId { let entropy = (b"modlpy/utilisuba", who, threshold).using_encoded(blake2_256); - T::AccountId::decode(&mut &entropy[..]).unwrap_or_default() + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") } fn operate( @@ -502,7 +536,7 @@ impl Pallet { threshold: u16, other_signatories: Vec, maybe_timepoint: Option>, - call_or_hash: CallOrHash, + call_or_hash: CallOrHash, max_weight: Weight, ) -> DispatchResultWithPostInfo { ensure!(threshold >= 2, Error::::MinimumThreshold); @@ -515,13 +549,12 @@ impl Pallet { let id = Self::multi_account_id(&signatories, threshold); // Threshold > 1; this means it's a multi-step operation. We extract the `call_hash`. - let (call_hash, call_len, maybe_call, store) = match call_or_hash { - CallOrHash::Call(call, should_store) => { - let call_hash = blake2_256(&call); - let call_len = call.len(); - (call_hash, call_len, Some(call), should_store) + let (call_hash, call_len, maybe_call) = match call_or_hash { + CallOrHash::Call(call) => { + let (call_hash, call_len) = call.using_encoded(|d| (blake2_256(d), d.len())); + (call_hash, call_len, Some(call)) }, - CallOrHash::Hash(h) => (h, 0, None, false), + CallOrHash::Hash(h) => (h, 0, None), }; // Branch on whether the operation has already started or not. @@ -540,30 +573,26 @@ impl Pallet { } // We only bother fetching/decoding call if we know that we're ready to execute. - let maybe_approved_call = if approvals >= threshold { - Self::get_call(&call_hash, maybe_call.as_ref().map(|c| c.as_ref())) - } else { - None - }; - - if let Some((call, call_len)) = maybe_approved_call { + if let Some(call) = maybe_call.filter(|_| approvals >= threshold) { // verify weight - ensure!(call.get_dispatch_info().weight <= max_weight, Error::::MaxWeightTooLow); + ensure!( + call.get_dispatch_info().weight.all_lte(max_weight), + Error::::MaxWeightTooLow + ); // Clean up storage before executing call to avoid an possibility of reentrancy // attack. >::remove(&id, call_hash); - Self::clear_call(&call_hash); T::Currency::unreserve(&m.depositor, m.deposit); let result = call.dispatch(RawOrigin::Signed(id.clone()).into()); - Self::deposit_event(Event::MultisigExecuted( - who, + Self::deposit_event(Event::MultisigExecuted { + approving: who, timepoint, - id, + multisig: id, call_hash, - result.map(|_| ()).map_err(|e| e.error), - )); + result: result.map(|_| ()).map_err(|e| e.error), + }); Ok(get_result_weight(result) .map(|actual_weight| { T::WeightInfo::as_multi_complete( @@ -577,38 +606,26 @@ impl Pallet { // We cannot dispatch the call now; either it isn't available, or it is, but we // don't have threshold approvals even with our signature. - // Store the call if desired. - let stored = if let Some(data) = maybe_call.filter(|_| store) { - Self::store_call_and_reserve( - who.clone(), - &call_hash, - data, - BalanceOf::::zero(), - )?; - true - } else { - false - }; - if let Some(pos) = maybe_pos { // Record approval. - m.approvals.insert(pos, who.clone()); + m.approvals + .try_insert(pos, who.clone()) + .map_err(|_| Error::::TooManySignatories)?; >::insert(&id, call_hash, m); - Self::deposit_event(Event::MultisigApproval(who, timepoint, id, call_hash)); + Self::deposit_event(Event::MultisigApproval { + approving: who, + timepoint, + multisig: id, + call_hash, + }); } else { // If we already approved and didn't store the Call, then this was useless and // we report an error. - ensure!(stored, Error::::AlreadyApproved); + Err(Error::::AlreadyApproved)? } - let final_weight = if stored { - T::WeightInfo::as_multi_approve_store( - other_signatories_len as u32, - call_len as u32, - ) - } else { - T::WeightInfo::as_multi_approve(other_signatories_len as u32, call_len as u32) - }; + let final_weight = + T::WeightInfo::as_multi_approve(other_signatories_len as u32, call_len as u32); // Call is not made, so the actual weight does not include call Ok(Some(final_weight).into()) } @@ -619,14 +636,10 @@ impl Pallet { // Just start the operation by recording it in storage. let deposit = T::DepositBase::get() + T::DepositFactor::get() * threshold.into(); - // Store the call if desired. - let stored = if let Some(data) = maybe_call.filter(|_| store) { - Self::store_call_and_reserve(who.clone(), &call_hash, data, deposit)?; - true - } else { - T::Currency::reserve(&who, deposit)?; - false - }; + T::Currency::reserve(&who, deposit)?; + + let initial_approvals = + vec![who.clone()].try_into().map_err(|_| Error::::TooManySignatories)?; >::insert( &id, @@ -635,63 +648,18 @@ impl Pallet { when: Self::timepoint(), deposit, depositor: who.clone(), - approvals: vec![who.clone()], + approvals: initial_approvals, }, ); - Self::deposit_event(Event::NewMultisig(who, id, call_hash)); + Self::deposit_event(Event::NewMultisig { approving: who, multisig: id, call_hash }); - let final_weight = if stored { - T::WeightInfo::as_multi_create_store(other_signatories_len as u32, call_len as u32) - } else { - T::WeightInfo::as_multi_create(other_signatories_len as u32, call_len as u32) - }; + let final_weight = + T::WeightInfo::as_multi_create(other_signatories_len as u32, call_len as u32); // Call is not made, so the actual weight does not include call Ok(Some(final_weight).into()) } } - /// Place a call's encoded data in storage, reserving funds as appropriate. - /// - /// We store `data` here because storing `call` would result in needing another `.encode`. - /// - /// Returns a `bool` indicating whether the data did end up being stored. - fn store_call_and_reserve( - who: T::AccountId, - hash: &[u8; 32], - data: OpaqueCall, - other_deposit: BalanceOf, - ) -> DispatchResult { - ensure!(!Calls::::contains_key(hash), Error::::AlreadyStored); - let deposit = other_deposit + - T::DepositBase::get() + - T::DepositFactor::get() * BalanceOf::::from(((data.len() + 31) / 32) as u32); - T::Currency::reserve(&who, deposit)?; - Calls::::insert(&hash, (data, who, deposit)); - Ok(()) - } - - /// Attempt to decode and return the call, provided by the user or from storage. - fn get_call( - hash: &[u8; 32], - maybe_known: Option<&[u8]>, - ) -> Option<(::Call, usize)> { - maybe_known.map_or_else( - || { - Calls::::get(hash).and_then(|(data, ..)| { - Decode::decode(&mut &data[..]).ok().map(|d| (d, data.len())) - }) - }, - |data| Decode::decode(&mut &data[..]).ok().map(|d| (d, data.len())), - ) - } - - /// Attempt to remove a call from storage, returning any deposit on it to the owner. - fn clear_call(hash: &[u8; 32]) { - if let Some((_, who, deposit)) = Calls::::take(hash) { - T::Currency::unreserve(&who, deposit); - } - } - /// The current `Timepoint`. pub fn timepoint() -> Timepoint { Timepoint { diff --git a/frame/multisig/src/migrations.rs b/frame/multisig/src/migrations.rs new file mode 100644 index 0000000000000..5085297cde433 --- /dev/null +++ b/frame/multisig/src/migrations.rs @@ -0,0 +1,86 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +// Migrations for Multisig Pallet + +use super::*; +use frame_support::{ + dispatch::GetStorageVersion, + traits::{OnRuntimeUpgrade, WrapperKeepOpaque}, + Identity, +}; + +#[cfg(feature = "try-runtime")] +use frame_support::ensure; + +pub mod v1 { + use super::*; + + type OpaqueCall = WrapperKeepOpaque<::RuntimeCall>; + + #[frame_support::storage_alias] + type Calls = StorageMap< + Pallet, + Identity, + [u8; 32], + (OpaqueCall, ::AccountId, BalanceOf), + >; + + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + let onchain = Pallet::::on_chain_storage_version(); + + ensure!(onchain < 1, "this migration can be deleted"); + + log!(info, "Number of calls to refund and delete: {}", Calls::::iter().count()); + + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + if onchain > 0 { + log!(info, "MigrateToV1 should be removed"); + return T::DbWeight::get().reads(1) + } + + Calls::::drain().for_each(|(_call_hash, (_data, caller, deposit))| { + T::Currency::unreserve(&caller, deposit); + }); + + current.put::>(); + + ::BlockWeights::get().max_block + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + let onchain = Pallet::::on_chain_storage_version(); + ensure!(onchain < 2, "this migration needs to be removed"); + ensure!(onchain == 1, "this migration needs to be run"); + ensure!( + Calls::::iter().count() == 0, + "there are some dangling calls that need to be destroyed and refunded" + ); + Ok(()) + } + } +} diff --git a/frame/multisig/src/tests.rs b/frame/multisig/src/tests.rs index 3d311cf5d3dc8..206e566cf4cb6 100644 --- a/frame/multisig/src/tests.rs +++ b/frame/multisig/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,10 @@ use super::*; use crate as pallet_multisig; -use frame_support::{assert_noop, assert_ok, parameter_types, traits::Contains}; +use frame_support::{ + assert_noop, assert_ok, parameter_types, + traits::{ConstU32, ConstU64, Contains}, +}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -45,26 +48,25 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { type BaseCallFilter = TestBaseCallFilter; type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -73,44 +75,39 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } -parameter_types! { - pub const DepositBase: u64 = 1; - pub const DepositFactor: u64 = 1; - pub const MaxSignatories: u16 = 3; -} + pub struct TestBaseCallFilter; -impl Contains for TestBaseCallFilter { - fn contains(c: &Call) -> bool { +impl Contains for TestBaseCallFilter { + fn contains(c: &RuntimeCall) -> bool { match *c { - Call::Balances(_) => true, + RuntimeCall::Balances(_) => true, // Needed for benchmarking - Call::System(frame_system::Call::remark { .. }) => true, + RuntimeCall::System(frame_system::Call::remark { .. }) => true, _ => false, } } } impl Config for Test { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type Currency = Balances; - type DepositBase = DepositBase; - type DepositFactor = DepositFactor; - type MaxSignatories = MaxSignatories; + type DepositBase = ConstU64<1>; + type DepositFactor = ConstU64<1>; + type MaxSignatories = ConstU32<3>; type WeightInfo = (); } @@ -132,126 +129,41 @@ fn now() -> Timepoint { Multisig::timepoint() } -fn call_transfer(dest: u64, value: u64) -> Call { - Call::Balances(BalancesCall::transfer { dest, value }) +fn call_transfer(dest: u64, value: u64) -> Box { + Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest, value })) } #[test] fn multisig_deposit_is_taken_and_returned() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let call_weight = call.get_dispatch_info().weight; - let data = call.encode(); assert_ok!(Multisig::as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, vec![2, 3], None, - data.clone(), - false, - 0 + call.clone(), + Weight::zero() )); assert_eq!(Balances::free_balance(1), 2); assert_eq!(Balances::reserved_balance(1), 3); assert_ok!(Multisig::as_multi( - Origin::signed(2), - 2, - vec![1, 3], - Some(now()), - data, - false, - call_weight - )); - assert_eq!(Balances::free_balance(1), 5); - assert_eq!(Balances::reserved_balance(1), 0); - }); -} - -#[test] -fn multisig_deposit_is_taken_and_returned_with_call_storage() { - new_test_ext().execute_with(|| { - let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); - - let call = call_transfer(6, 15); - let call_weight = call.get_dispatch_info().weight; - let data = call.encode(); - let hash = blake2_256(&data); - assert_ok!(Multisig::as_multi(Origin::signed(1), 2, vec![2, 3], None, data, true, 0)); - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::reserved_balance(1), 5); - - assert_ok!(Multisig::approve_as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 2, vec![1, 3], Some(now()), - hash, - call_weight - )); - assert_eq!(Balances::free_balance(1), 5); - assert_eq!(Balances::reserved_balance(1), 0); - }); -} - -#[test] -fn multisig_deposit_is_taken_and_returned_with_alt_call_storage() { - new_test_ext().execute_with(|| { - let multi = Multisig::multi_account_id(&[1, 2, 3][..], 3); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); - - let call = call_transfer(6, 15); - let call_weight = call.get_dispatch_info().weight; - let data = call.encode(); - let hash = blake2_256(&data); - - assert_ok!(Multisig::approve_as_multi( - Origin::signed(1), - 3, - vec![2, 3], - None, - hash.clone(), - 0 - )); - assert_eq!(Balances::free_balance(1), 1); - assert_eq!(Balances::reserved_balance(1), 4); - - assert_ok!(Multisig::as_multi( - Origin::signed(2), - 3, - vec![1, 3], - Some(now()), - data, - true, - 0 - )); - assert_eq!(Balances::free_balance(2), 3); - assert_eq!(Balances::reserved_balance(2), 2); - assert_eq!(Balances::free_balance(1), 1); - assert_eq!(Balances::reserved_balance(1), 4); - - assert_ok!(Multisig::approve_as_multi( - Origin::signed(3), - 3, - vec![1, 2], - Some(now()), - hash, + call, call_weight )); assert_eq!(Balances::free_balance(1), 5); assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(Balances::free_balance(2), 5); - assert_eq!(Balances::reserved_balance(2), 0); }); } @@ -261,30 +173,24 @@ fn cancel_multisig_returns_deposit() { let call = call_transfer(6, 15).encode(); let hash = blake2_256(&call); assert_ok!(Multisig::approve_as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, vec![2, 3], None, - hash.clone(), - 0 + hash, + Weight::zero() )); assert_ok!(Multisig::approve_as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 3, vec![1, 3], Some(now()), - hash.clone(), - 0 + hash, + Weight::zero() )); assert_eq!(Balances::free_balance(1), 6); assert_eq!(Balances::reserved_balance(1), 4); - assert_ok!(Multisig::cancel_as_multi( - Origin::signed(1), - 3, - vec![2, 3], - now(), - hash.clone() - ),); + assert_ok!(Multisig::cancel_as_multi(RuntimeOrigin::signed(1), 3, vec![2, 3], now(), hash)); assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::reserved_balance(1), 0); }); @@ -294,41 +200,54 @@ fn cancel_multisig_returns_deposit() { fn timepoint_checking_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); - let call = call_transfer(6, 15).encode(); - let hash = blake2_256(&call); + let call = call_transfer(6, 15); + let hash = blake2_256(&call.encode()); assert_noop!( Multisig::approve_as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 2, vec![1, 3], Some(now()), - hash.clone(), - 0 + hash, + Weight::zero() ), Error::::UnexpectedTimepoint, ); - assert_ok!(Multisig::approve_as_multi(Origin::signed(1), 2, vec![2, 3], None, hash, 0)); + assert_ok!(Multisig::approve_as_multi( + RuntimeOrigin::signed(1), + 2, + vec![2, 3], + None, + hash, + Weight::zero() + )); assert_noop!( - Multisig::as_multi(Origin::signed(2), 2, vec![1, 3], None, call.clone(), false, 0), + Multisig::as_multi( + RuntimeOrigin::signed(2), + 2, + vec![1, 3], + None, + call.clone(), + Weight::zero() + ), Error::::NoTimepoint, ); let later = Timepoint { index: 1, ..now() }; assert_noop!( Multisig::as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 2, vec![1, 3], Some(later), - call.clone(), - false, - 0 + call, + Weight::zero() ), Error::::WrongTimepoint, ); @@ -336,54 +255,32 @@ fn timepoint_checking_works() { } #[test] -fn multisig_2_of_3_works_with_call_storing() { +fn multisig_2_of_3_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let call_weight = call.get_dispatch_info().weight; - let data = call.encode(); - let hash = blake2_256(&data); - assert_ok!(Multisig::as_multi(Origin::signed(1), 2, vec![2, 3], None, data, true, 0)); - assert_eq!(Balances::free_balance(6), 0); - + let hash = blake2_256(&call.encode()); assert_ok!(Multisig::approve_as_multi( - Origin::signed(2), + RuntimeOrigin::signed(1), 2, - vec![1, 3], - Some(now()), + vec![2, 3], + None, hash, - call_weight + Weight::zero() )); - assert_eq!(Balances::free_balance(6), 15); - }); -} - -#[test] -fn multisig_2_of_3_works() { - new_test_ext().execute_with(|| { - let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); - - let call = call_transfer(6, 15); - let call_weight = call.get_dispatch_info().weight; - let data = call.encode(); - let hash = blake2_256(&data); - assert_ok!(Multisig::approve_as_multi(Origin::signed(1), 2, vec![2, 3], None, hash, 0)); assert_eq!(Balances::free_balance(6), 0); assert_ok!(Multisig::as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 2, vec![1, 3], Some(now()), - data, - false, + call, call_weight )); assert_eq!(Balances::free_balance(6), 15); @@ -394,39 +291,37 @@ fn multisig_2_of_3_works() { fn multisig_3_of_3_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 3); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let call_weight = call.get_dispatch_info().weight; - let data = call.encode(); - let hash = blake2_256(&data); + let hash = blake2_256(&call.encode()); assert_ok!(Multisig::approve_as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, vec![2, 3], None, - hash.clone(), - 0 + hash, + Weight::zero() )); assert_ok!(Multisig::approve_as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 3, vec![1, 3], Some(now()), - hash.clone(), - 0 + hash, + Weight::zero() )); assert_eq!(Balances::free_balance(6), 0); assert_ok!(Multisig::as_multi( - Origin::signed(3), + RuntimeOrigin::signed(3), 3, vec![1, 2], Some(now()), - data, - false, + call, call_weight )); assert_eq!(Balances::free_balance(6), 15); @@ -439,92 +334,26 @@ fn cancel_multisig_works() { let call = call_transfer(6, 15).encode(); let hash = blake2_256(&call); assert_ok!(Multisig::approve_as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, vec![2, 3], None, - hash.clone(), - 0 - )); - assert_ok!(Multisig::approve_as_multi( - Origin::signed(2), - 3, - vec![1, 3], - Some(now()), - hash.clone(), - 0 + hash, + Weight::zero() )); - assert_noop!( - Multisig::cancel_as_multi(Origin::signed(2), 3, vec![1, 3], now(), hash.clone()), - Error::::NotOwner, - ); - assert_ok!(Multisig::cancel_as_multi( - Origin::signed(1), - 3, - vec![2, 3], - now(), - hash.clone() - ),); - }); -} - -#[test] -fn cancel_multisig_with_call_storage_works() { - new_test_ext().execute_with(|| { - let call = call_transfer(6, 15).encode(); - let hash = blake2_256(&call); - assert_ok!(Multisig::as_multi(Origin::signed(1), 3, vec![2, 3], None, call, true, 0)); - assert_eq!(Balances::free_balance(1), 4); assert_ok!(Multisig::approve_as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 3, vec![1, 3], Some(now()), - hash.clone(), - 0 + hash, + Weight::zero() )); assert_noop!( - Multisig::cancel_as_multi(Origin::signed(2), 3, vec![1, 3], now(), hash.clone()), + Multisig::cancel_as_multi(RuntimeOrigin::signed(2), 3, vec![1, 3], now(), hash), Error::::NotOwner, ); - assert_ok!(Multisig::cancel_as_multi( - Origin::signed(1), - 3, - vec![2, 3], - now(), - hash.clone() - ),); - assert_eq!(Balances::free_balance(1), 10); - }); -} - -#[test] -fn cancel_multisig_with_alt_call_storage_works() { - new_test_ext().execute_with(|| { - let call = call_transfer(6, 15).encode(); - let hash = blake2_256(&call); - assert_ok!(Multisig::approve_as_multi( - Origin::signed(1), - 3, - vec![2, 3], - None, - hash.clone(), - 0 - )); - assert_eq!(Balances::free_balance(1), 6); - assert_ok!(Multisig::as_multi( - Origin::signed(2), - 3, - vec![1, 3], - Some(now()), - call, - true, - 0 - )); - assert_eq!(Balances::free_balance(2), 8); - assert_ok!(Multisig::cancel_as_multi(Origin::signed(1), 3, vec![2, 3], now(), hash)); - assert_eq!(Balances::free_balance(1), 10); - assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Multisig::cancel_as_multi(RuntimeOrigin::signed(1), 3, vec![2, 3], now(), hash),); }); } @@ -532,31 +361,28 @@ fn cancel_multisig_with_alt_call_storage_works() { fn multisig_2_of_3_as_multi_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let call_weight = call.get_dispatch_info().weight; - let data = call.encode(); assert_ok!(Multisig::as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, vec![2, 3], None, - data.clone(), - false, - 0 + call.clone(), + Weight::zero() )); assert_eq!(Balances::free_balance(6), 0); assert_ok!(Multisig::as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 2, vec![1, 3], Some(now()), - data, - false, + call, call_weight )); assert_eq!(Balances::free_balance(6), 15); @@ -567,51 +393,45 @@ fn multisig_2_of_3_as_multi_works() { fn multisig_2_of_3_as_multi_with_many_calls_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); let call1 = call_transfer(6, 10); let call1_weight = call1.get_dispatch_info().weight; - let data1 = call1.encode(); let call2 = call_transfer(7, 5); let call2_weight = call2.get_dispatch_info().weight; - let data2 = call2.encode(); assert_ok!(Multisig::as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, vec![2, 3], None, - data1.clone(), - false, - 0 + call1.clone(), + Weight::zero() )); assert_ok!(Multisig::as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 2, vec![1, 3], None, - data2.clone(), - false, - 0 + call2.clone(), + Weight::zero() )); assert_ok!(Multisig::as_multi( - Origin::signed(3), + RuntimeOrigin::signed(3), 2, vec![1, 2], Some(now()), - data1, - false, + call1, call1_weight )); assert_ok!(Multisig::as_multi( - Origin::signed(3), + RuntimeOrigin::signed(3), 2, vec![1, 2], Some(now()), - data2, - false, + call2, call2_weight )); @@ -624,56 +444,58 @@ fn multisig_2_of_3_as_multi_with_many_calls_works() { fn multisig_2_of_3_cannot_reissue_same_call() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 10); let call_weight = call.get_dispatch_info().weight; - let data = call.encode(); - let hash = blake2_256(&data); + let hash = blake2_256(&call.encode()); assert_ok!(Multisig::as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, vec![2, 3], None, - data.clone(), - false, - 0 + call.clone(), + Weight::zero() )); assert_ok!(Multisig::as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 2, vec![1, 3], Some(now()), - data.clone(), - false, + call.clone(), call_weight )); assert_eq!(Balances::free_balance(multi), 5); assert_ok!(Multisig::as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, vec![2, 3], None, - data.clone(), - false, - 0 + call.clone(), + Weight::zero() )); assert_ok!(Multisig::as_multi( - Origin::signed(3), + RuntimeOrigin::signed(3), 2, vec![1, 2], Some(now()), - data.clone(), - false, + call.clone(), call_weight )); let err = DispatchError::from(BalancesError::::InsufficientBalance).stripped(); System::assert_last_event( - pallet_multisig::Event::MultisigExecuted(3, now(), multi, hash, Err(err)).into(), + pallet_multisig::Event::MultisigExecuted { + approving: 3, + timepoint: now(), + multisig: multi, + call_hash: hash, + result: Err(err), + } + .into(), ); }); } @@ -681,13 +503,27 @@ fn multisig_2_of_3_cannot_reissue_same_call() { #[test] fn minimum_threshold_check_works() { new_test_ext().execute_with(|| { - let call = call_transfer(6, 15).encode(); + let call = call_transfer(6, 15); assert_noop!( - Multisig::as_multi(Origin::signed(1), 0, vec![2], None, call.clone(), false, 0), + Multisig::as_multi( + RuntimeOrigin::signed(1), + 0, + vec![2], + None, + call.clone(), + Weight::zero() + ), Error::::MinimumThreshold, ); assert_noop!( - Multisig::as_multi(Origin::signed(1), 1, vec![2], None, call.clone(), false, 0), + Multisig::as_multi( + RuntimeOrigin::signed(1), + 1, + vec![2], + None, + call.clone(), + Weight::zero() + ), Error::::MinimumThreshold, ); }); @@ -696,9 +532,16 @@ fn minimum_threshold_check_works() { #[test] fn too_many_signatories_fails() { new_test_ext().execute_with(|| { - let call = call_transfer(6, 15).encode(); + let call = call_transfer(6, 15); assert_noop!( - Multisig::as_multi(Origin::signed(1), 2, vec![2, 3, 4], None, call.clone(), false, 0), + Multisig::as_multi( + RuntimeOrigin::signed(1), + 2, + vec![2, 3, 4], + None, + call.clone(), + Weight::zero() + ), Error::::TooManySignatories, ); }); @@ -710,40 +553,40 @@ fn duplicate_approvals_are_ignored() { let call = call_transfer(6, 15).encode(); let hash = blake2_256(&call); assert_ok!(Multisig::approve_as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, vec![2, 3], None, - hash.clone(), - 0 + hash, + Weight::zero() )); assert_noop!( Multisig::approve_as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, vec![2, 3], Some(now()), - hash.clone(), - 0 + hash, + Weight::zero() ), Error::::AlreadyApproved, ); assert_ok!(Multisig::approve_as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 2, vec![1, 3], Some(now()), - hash.clone(), - 0 + hash, + Weight::zero() )); assert_noop!( Multisig::approve_as_multi( - Origin::signed(3), + RuntimeOrigin::signed(3), 2, vec![1, 2], Some(now()), - hash.clone(), - 0 + hash, + Weight::zero() ), Error::::AlreadyApproved, ); @@ -754,22 +597,39 @@ fn duplicate_approvals_are_ignored() { fn multisig_1_of_3_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 1); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); - let call = call_transfer(6, 15).encode(); - let hash = blake2_256(&call); + let call = call_transfer(6, 15); + let hash = blake2_256(&call.encode()); assert_noop!( - Multisig::approve_as_multi(Origin::signed(1), 1, vec![2, 3], None, hash.clone(), 0), + Multisig::approve_as_multi( + RuntimeOrigin::signed(1), + 1, + vec![2, 3], + None, + hash, + Weight::zero() + ), Error::::MinimumThreshold, ); assert_noop!( - Multisig::as_multi(Origin::signed(1), 1, vec![2, 3], None, call.clone(), false, 0), + Multisig::as_multi( + RuntimeOrigin::signed(1), + 1, + vec![2, 3], + None, + call.clone(), + Weight::zero() + ), Error::::MinimumThreshold, ); - let boxed_call = Box::new(call_transfer(6, 15)); - assert_ok!(Multisig::as_multi_threshold_1(Origin::signed(1), vec![2, 3], boxed_call)); + assert_ok!(Multisig::as_multi_threshold_1( + RuntimeOrigin::signed(1), + vec![2, 3], + call_transfer(6, 15) + )); assert_eq!(Balances::free_balance(6), 15); }); @@ -778,10 +638,10 @@ fn multisig_1_of_3_works() { #[test] fn multisig_filters() { new_test_ext().execute_with(|| { - let call = Box::new(Call::System(frame_system::Call::set_code { code: vec![] })); + let call = Box::new(RuntimeCall::System(frame_system::Call::set_code { code: vec![] })); assert_noop!( - Multisig::as_multi_threshold_1(Origin::signed(1), vec![2], call.clone()), - DispatchError::BadOrigin, + Multisig::as_multi_threshold_1(RuntimeOrigin::signed(1), vec![2], call.clone()), + DispatchError::from(frame_system::Error::::CallFiltered), ); }); } @@ -790,25 +650,30 @@ fn multisig_filters() { fn weight_check_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); - let data = call.encode(); assert_ok!(Multisig::as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, vec![2, 3], None, - data.clone(), - false, - 0 + call.clone(), + Weight::zero() )); assert_eq!(Balances::free_balance(6), 0); assert_noop!( - Multisig::as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), data, false, 0), + Multisig::as_multi( + RuntimeOrigin::signed(2), + 2, + vec![1, 3], + Some(now()), + call, + Weight::zero() + ), Error::::MaxWeightTooLow, ); }); @@ -821,47 +686,45 @@ fn multisig_handles_no_preimage_after_all_approve() { // the call will go through. new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 3); - assert_ok!(Balances::transfer(Origin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(Origin::signed(3), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let call_weight = call.get_dispatch_info().weight; - let data = call.encode(); - let hash = blake2_256(&data); + let hash = blake2_256(&call.encode()); assert_ok!(Multisig::approve_as_multi( - Origin::signed(1), + RuntimeOrigin::signed(1), 3, vec![2, 3], None, - hash.clone(), - 0 + hash, + Weight::zero() )); assert_ok!(Multisig::approve_as_multi( - Origin::signed(2), + RuntimeOrigin::signed(2), 3, vec![1, 3], Some(now()), - hash.clone(), - 0 + hash, + Weight::zero() )); assert_ok!(Multisig::approve_as_multi( - Origin::signed(3), + RuntimeOrigin::signed(3), 3, vec![1, 2], Some(now()), - hash.clone(), - 0 + hash, + Weight::zero() )); assert_eq!(Balances::free_balance(6), 0); assert_ok!(Multisig::as_multi( - Origin::signed(3), + RuntimeOrigin::signed(3), 3, vec![1, 2], Some(now()), - data, - false, + call, call_weight )); assert_eq!(Balances::free_balance(6), 15); diff --git a/frame/multisig/src/weights.rs b/frame/multisig/src/weights.rs index 1bc72d251808f..1f435cb9f9087 100644 --- a/frame/multisig/src/weights.rs +++ b/frame/multisig/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_multisig //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/multisig/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -47,217 +49,176 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn as_multi_threshold_1(z: u32, ) -> Weight; fn as_multi_create(s: u32, z: u32, ) -> Weight; - fn as_multi_create_store(s: u32, z: u32, ) -> Weight; fn as_multi_approve(s: u32, z: u32, ) -> Weight; - fn as_multi_approve_store(s: u32, z: u32, ) -> Weight; fn as_multi_complete(s: u32, z: u32, ) -> Weight; fn approve_as_multi_create(s: u32, ) -> Weight; fn approve_as_multi_approve(s: u32, ) -> Weight; - fn approve_as_multi_complete(s: u32, ) -> Weight; fn cancel_as_multi(s: u32, ) -> Weight; } /// Weights for pallet_multisig using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// The range of component `z` is `[0, 10000]`. fn as_multi_threshold_1(z: u32, ) -> Weight { - (19_405_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) + // Minimum execution time: 20_447 nanoseconds. + Weight::from_ref_time(20_896_236 as u64) + // Standard Error: 2 + .saturating_add(Weight::from_ref_time(568 as u64).saturating_mul(z as u64)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { - (54_364_000 as Weight) - // Standard Error: 0 - .saturating_add((163_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:1) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - fn as_multi_create_store(s: u32, z: u32, ) -> Weight { - (59_545_000 as Weight) - // Standard Error: 0 - .saturating_add((168_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(z as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Multisig Multisigs (r:1 w:1) + // Minimum execution time: 54_987 nanoseconds. + Weight::from_ref_time(42_525_077 as u64) + // Standard Error: 562 + .saturating_add(Weight::from_ref_time(136_064 as u64).saturating_mul(s as u64)) + // Standard Error: 5 + .saturating_add(Weight::from_ref_time(1_508 as u64).saturating_mul(z as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Multisig Multisigs (r:1 w:1) + /// The range of component `s` is `[3, 100]`. + /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { - (32_721_000 as Weight) - // Standard Error: 0 - .saturating_add((176_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 42_573 nanoseconds. + Weight::from_ref_time(30_585_734 as u64) + // Standard Error: 637 + .saturating_add(Weight::from_ref_time(128_012 as u64).saturating_mul(s as u64)) + // Standard Error: 6 + .saturating_add(Weight::from_ref_time(1_507 as u64).saturating_mul(z as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:1) - fn as_multi_approve_store(s: u32, z: u32, ) -> Weight { - (56_596_000 as Weight) - // Standard Error: 1_000 - .saturating_add((183_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(z as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { - (72_391_000 as Weight) - // Standard Error: 1_000 - .saturating_add((268_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((4_000 as Weight).saturating_mul(z as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 57_143 nanoseconds. + Weight::from_ref_time(43_921_674 as u64) + // Standard Error: 704 + .saturating_add(Weight::from_ref_time(153_474 as u64).saturating_mul(s as u64)) + // Standard Error: 6 + .saturating_add(Weight::from_ref_time(1_536 as u64).saturating_mul(z as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { - (52_543_000 as Weight) - // Standard Error: 0 - .saturating_add((164_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 39_088 nanoseconds. + Weight::from_ref_time(41_258_697 as u64) + // Standard Error: 1_038 + .saturating_add(Weight::from_ref_time(126_040 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:0) + /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { - (30_764_000 as Weight) - // Standard Error: 0 - .saturating_add((180_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn approve_as_multi_complete(s: u32, ) -> Weight { - (113_631_000 as Weight) - // Standard Error: 3_000 - .saturating_add((283_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 26_872 nanoseconds. + Weight::from_ref_time(28_625_218 as u64) + // Standard Error: 793 + .saturating_add(Weight::from_ref_time(128_542 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:1) + /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { - (86_310_000 as Weight) - // Standard Error: 0 - .saturating_add((166_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 37_636 nanoseconds. + Weight::from_ref_time(39_614_705 as u64) + // Standard Error: 850 + .saturating_add(Weight::from_ref_time(136_222 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { + /// The range of component `z` is `[0, 10000]`. fn as_multi_threshold_1(z: u32, ) -> Weight { - (19_405_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) + // Minimum execution time: 20_447 nanoseconds. + Weight::from_ref_time(20_896_236 as u64) + // Standard Error: 2 + .saturating_add(Weight::from_ref_time(568 as u64).saturating_mul(z as u64)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { - (54_364_000 as Weight) - // Standard Error: 0 - .saturating_add((163_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:1) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - fn as_multi_create_store(s: u32, z: u32, ) -> Weight { - (59_545_000 as Weight) - // Standard Error: 0 - .saturating_add((168_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(z as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Multisig Multisigs (r:1 w:1) + // Minimum execution time: 54_987 nanoseconds. + Weight::from_ref_time(42_525_077 as u64) + // Standard Error: 562 + .saturating_add(Weight::from_ref_time(136_064 as u64).saturating_mul(s as u64)) + // Standard Error: 5 + .saturating_add(Weight::from_ref_time(1_508 as u64).saturating_mul(z as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Multisig Multisigs (r:1 w:1) + /// The range of component `s` is `[3, 100]`. + /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { - (32_721_000 as Weight) - // Standard Error: 0 - .saturating_add((176_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 42_573 nanoseconds. + Weight::from_ref_time(30_585_734 as u64) + // Standard Error: 637 + .saturating_add(Weight::from_ref_time(128_012 as u64).saturating_mul(s as u64)) + // Standard Error: 6 + .saturating_add(Weight::from_ref_time(1_507 as u64).saturating_mul(z as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:1) - fn as_multi_approve_store(s: u32, z: u32, ) -> Weight { - (56_596_000 as Weight) - // Standard Error: 1_000 - .saturating_add((183_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(z as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { - (72_391_000 as Weight) - // Standard Error: 1_000 - .saturating_add((268_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((4_000 as Weight).saturating_mul(z as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 57_143 nanoseconds. + Weight::from_ref_time(43_921_674 as u64) + // Standard Error: 704 + .saturating_add(Weight::from_ref_time(153_474 as u64).saturating_mul(s as u64)) + // Standard Error: 6 + .saturating_add(Weight::from_ref_time(1_536 as u64).saturating_mul(z as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { - (52_543_000 as Weight) - // Standard Error: 0 - .saturating_add((164_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 39_088 nanoseconds. + Weight::from_ref_time(41_258_697 as u64) + // Standard Error: 1_038 + .saturating_add(Weight::from_ref_time(126_040 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:0) + /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { - (30_764_000 as Weight) - // Standard Error: 0 - .saturating_add((180_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn approve_as_multi_complete(s: u32, ) -> Weight { - (113_631_000 as Weight) - // Standard Error: 3_000 - .saturating_add((283_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 26_872 nanoseconds. + Weight::from_ref_time(28_625_218 as u64) + // Standard Error: 793 + .saturating_add(Weight::from_ref_time(128_542 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Multisig Multisigs (r:1 w:1) - // Storage: Multisig Calls (r:1 w:1) + /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { - (86_310_000 as Weight) - // Standard Error: 0 - .saturating_add((166_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 37_636 nanoseconds. + Weight::from_ref_time(39_614_705 as u64) + // Standard Error: 850 + .saturating_add(Weight::from_ref_time(136_222 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } } diff --git a/frame/nicks/Cargo.toml b/frame/nicks/Cargo.toml index 431ee2c84157c..2390060c71698 100644 --- a/frame/nicks/Cargo.toml +++ b/frame/nicks/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-nicks" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for nick management" readme = "README.md" @@ -13,27 +13,27 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", - "sp-std/std", "sp-io/std", "sp-runtime/std", - "frame-support/std", - "frame-system/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/nicks/src/lib.rs b/frame/nicks/src/lib.rs index 16c7e2042dda0..79daeb9bdb9a8 100644 --- a/frame/nicks/src/lib.rs +++ b/frame/nicks/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,9 +35,6 @@ //! taken. //! * `clear_name` - Remove an account's associated name; the deposit is returned. //! * `kill_name` - Forcibly remove the associated name; the deposit is lost. -//! -//! [`Call`]: ./enum.Call.html -//! [`Config`]: ./trait.Config.html #![cfg_attr(not(feature = "std"), no_std)] @@ -46,26 +43,22 @@ pub use pallet::*; use sp_runtime::traits::{StaticLookup, Zero}; use sp_std::prelude::*; -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; -type NegativeImbalanceOf = <::Currency as Currency< - ::AccountId, ->>::NegativeImbalance; +type AccountIdOf = ::AccountId; +type BalanceOf = <::Currency as Currency>>::Balance; +type NegativeImbalanceOf = + <::Currency as Currency>>::NegativeImbalance; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{ - ensure, - pallet_prelude::*, - traits::{EnsureOrigin, Get}, - }; - use frame_system::{ensure_signed, pallet_prelude::*}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The currency trait. type Currency: ReservableCurrency; @@ -78,7 +71,7 @@ pub mod pallet { type Slashed: OnUnbalanced>; /// The origin which may forcibly set or remove a name. Root can always do this. - type ForceOrigin: EnsureOrigin; + type ForceOrigin: EnsureOrigin; /// The minimum length a name may be. #[pallet::constant] @@ -92,16 +85,16 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// A name was set. \[who\] - NameSet(T::AccountId), - /// A name was forcibly set. \[target\] - NameForced(T::AccountId), - /// A name was changed. \[who\] - NameChanged(T::AccountId), - /// A name was cleared, and the given balance returned. \[who, deposit\] - NameCleared(T::AccountId, BalanceOf), - /// A name was removed and the given balance slashed. \[target, deposit\] - NameKilled(T::AccountId, BalanceOf), + /// A name was set. + NameSet { who: T::AccountId }, + /// A name was forcibly set. + NameForced { target: T::AccountId }, + /// A name was changed. + NameChanged { who: T::AccountId }, + /// A name was cleared, and the given balance returned. + NameCleared { who: T::AccountId, deposit: BalanceOf }, + /// A name was removed and the given balance slashed. + NameKilled { target: T::AccountId, deposit: BalanceOf }, } /// Error for the nicks pallet. @@ -118,7 +111,7 @@ pub mod pallet { /// The lookup table for names. #[pallet::storage] pub(super) type NameOf = - StorageMap<_, Twox64Concat, T::AccountId, (Vec, BalanceOf)>; + StorageMap<_, Twox64Concat, T::AccountId, (BoundedVec, BalanceOf)>; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -142,24 +135,26 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(0)] #[pallet::weight(50_000_000)] pub fn set_name(origin: OriginFor, name: Vec) -> DispatchResult { let sender = ensure_signed(origin)?; - ensure!(name.len() >= T::MinLength::get() as usize, Error::::TooShort); - ensure!(name.len() <= T::MaxLength::get() as usize, Error::::TooLong); + let bounded_name: BoundedVec<_, _> = + name.try_into().map_err(|_| Error::::TooLong)?; + ensure!(bounded_name.len() >= T::MinLength::get() as usize, Error::::TooShort); let deposit = if let Some((_, deposit)) = >::get(&sender) { - Self::deposit_event(Event::::NameChanged(sender.clone())); + Self::deposit_event(Event::::NameChanged { who: sender.clone() }); deposit } else { let deposit = T::ReservationFee::get(); - T::Currency::reserve(&sender, deposit.clone())?; - Self::deposit_event(Event::::NameSet(sender.clone())); + T::Currency::reserve(&sender, deposit)?; + Self::deposit_event(Event::::NameSet { who: sender.clone() }); deposit }; - >::insert(&sender, (name, deposit)); + >::insert(&sender, (bounded_name, deposit)); Ok(()) } @@ -173,22 +168,23 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(1)] #[pallet::weight(70_000_000)] pub fn clear_name(origin: OriginFor) -> DispatchResult { let sender = ensure_signed(origin)?; let deposit = >::take(&sender).ok_or(Error::::Unnamed)?.1; - let err_amount = T::Currency::unreserve(&sender, deposit.clone()); + let err_amount = T::Currency::unreserve(&sender, deposit); debug_assert!(err_amount.is_zero()); - Self::deposit_event(Event::::NameCleared(sender, deposit)); + Self::deposit_event(Event::::NameCleared { who: sender, deposit }); Ok(()) } /// Remove an account's name and take charge of the deposit. /// - /// Fails if `who` has not been named. The deposit is dealt with through `T::Slashed` + /// Fails if `target` has not been named. The deposit is dealt with through `T::Slashed` /// imbalance handler. /// /// The dispatch origin for this call must match `T::ForceOrigin`. @@ -199,11 +195,9 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(2)] #[pallet::weight(70_000_000)] - pub fn kill_name( - origin: OriginFor, - target: ::Source, - ) -> DispatchResult { + pub fn kill_name(origin: OriginFor, target: AccountIdLookupOf) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; // Figure out who we're meant to be clearing. @@ -211,9 +205,9 @@ pub mod pallet { // Grab their deposit (and check that they have one). let deposit = >::take(&target).ok_or(Error::::Unnamed)?.1; // Slash their deposit from them. - T::Slashed::on_unbalanced(T::Currency::slash_reserved(&target, deposit.clone()).0); + T::Slashed::on_unbalanced(T::Currency::slash_reserved(&target, deposit).0); - Self::deposit_event(Event::::NameKilled(target, deposit)); + Self::deposit_event(Event::::NameKilled { target, deposit }); Ok(()) } @@ -229,19 +223,22 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(3)] #[pallet::weight(70_000_000)] pub fn force_name( origin: OriginFor, - target: ::Source, + target: AccountIdLookupOf, name: Vec, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; + let bounded_name: BoundedVec<_, _> = + name.try_into().map_err(|_| Error::::TooLong)?; let target = T::Lookup::lookup(target)?; let deposit = >::get(&target).map(|x| x.1).unwrap_or_else(Zero::zero); - >::insert(&target, (name, deposit)); + >::insert(&target, (bounded_name, deposit)); - Self::deposit_event(Event::::NameForced(target)); + Self::deposit_event(Event::::NameForced { target }); Ok(()) } } @@ -252,7 +249,10 @@ mod tests { use super::*; use crate as pallet_nicks; - use frame_support::{assert_noop, assert_ok, ord_parameter_types, parameter_types}; + use frame_support::{ + assert_noop, assert_ok, ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64}, + }; use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ @@ -269,33 +269,32 @@ mod tests { NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Nicks: pallet_nicks::{Pallet, Call, Storage, Event}, + System: frame_system, + Balances: pallet_balances, + Nicks: pallet_nicks, } ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -304,37 +303,32 @@ mod tests { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } - parameter_types! { - pub const ExistentialDeposit: u64 = 1; - } + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } - parameter_types! { - pub const ReservationFee: u64 = 2; - pub const MinLength: u32 = 3; - pub const MaxLength: u32 = 16; - } + ord_parameter_types! { pub const One: u64 = 1; } impl Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; - type ReservationFee = ReservationFee; + type ReservationFee = ConstU64<2>; type Slashed = (); type ForceOrigin = EnsureSignedBy; - type MinLength = MinLength; - type MaxLength = MaxLength; + type MinLength = ConstU32<3>; + type MaxLength = ConstU32<16>; } fn new_test_ext() -> sp_io::TestExternalities { @@ -348,9 +342,9 @@ mod tests { #[test] fn kill_name_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nicks::set_name(Origin::signed(2), b"Dave".to_vec())); + assert_ok!(Nicks::set_name(RuntimeOrigin::signed(2), b"Dave".to_vec())); assert_eq!(Balances::total_balance(&2), 10); - assert_ok!(Nicks::kill_name(Origin::signed(1), 2)); + assert_ok!(Nicks::kill_name(RuntimeOrigin::signed(1), 2)); assert_eq!(Balances::total_balance(&2), 8); assert_eq!(>::get(2), None); }); @@ -360,32 +354,42 @@ mod tests { fn force_name_should_work() { new_test_ext().execute_with(|| { assert_noop!( - Nicks::set_name(Origin::signed(2), b"Dr. David Brubeck, III".to_vec()), + Nicks::set_name(RuntimeOrigin::signed(2), b"Dr. David Brubeck, III".to_vec()), Error::::TooLong, ); - assert_ok!(Nicks::set_name(Origin::signed(2), b"Dave".to_vec())); + assert_ok!(Nicks::set_name(RuntimeOrigin::signed(2), b"Dave".to_vec())); assert_eq!(Balances::reserved_balance(2), 2); - assert_ok!(Nicks::force_name(Origin::signed(1), 2, b"Dr. David Brubeck, III".to_vec())); + assert_noop!( + Nicks::force_name(RuntimeOrigin::signed(1), 2, b"Dr. David Brubeck, III".to_vec()), + Error::::TooLong, + ); + assert_ok!(Nicks::force_name( + RuntimeOrigin::signed(1), + 2, + b"Dr. Brubeck, III".to_vec() + )); assert_eq!(Balances::reserved_balance(2), 2); - assert_eq!(>::get(2).unwrap(), (b"Dr. David Brubeck, III".to_vec(), 2)); + let (name, amount) = >::get(2).unwrap(); + assert_eq!(name, b"Dr. Brubeck, III".to_vec()); + assert_eq!(amount, 2); }); } #[test] fn normal_operation_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nicks::set_name(Origin::signed(1), b"Gav".to_vec())); + assert_ok!(Nicks::set_name(RuntimeOrigin::signed(1), b"Gav".to_vec())); assert_eq!(Balances::reserved_balance(1), 2); assert_eq!(Balances::free_balance(1), 8); assert_eq!(>::get(1).unwrap().0, b"Gav".to_vec()); - assert_ok!(Nicks::set_name(Origin::signed(1), b"Gavin".to_vec())); + assert_ok!(Nicks::set_name(RuntimeOrigin::signed(1), b"Gavin".to_vec())); assert_eq!(Balances::reserved_balance(1), 2); assert_eq!(Balances::free_balance(1), 8); assert_eq!(>::get(1).unwrap().0, b"Gavin".to_vec()); - assert_ok!(Nicks::clear_name(Origin::signed(1))); + assert_ok!(Nicks::clear_name(RuntimeOrigin::signed(1))); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Balances::free_balance(1), 10); }); @@ -394,24 +398,27 @@ mod tests { #[test] fn error_catching_should_work() { new_test_ext().execute_with(|| { - assert_noop!(Nicks::clear_name(Origin::signed(1)), Error::::Unnamed); + assert_noop!(Nicks::clear_name(RuntimeOrigin::signed(1)), Error::::Unnamed); assert_noop!( - Nicks::set_name(Origin::signed(3), b"Dave".to_vec()), + Nicks::set_name(RuntimeOrigin::signed(3), b"Dave".to_vec()), pallet_balances::Error::::InsufficientBalance ); assert_noop!( - Nicks::set_name(Origin::signed(1), b"Ga".to_vec()), + Nicks::set_name(RuntimeOrigin::signed(1), b"Ga".to_vec()), Error::::TooShort ); assert_noop!( - Nicks::set_name(Origin::signed(1), b"Gavin James Wood, Esquire".to_vec()), + Nicks::set_name(RuntimeOrigin::signed(1), b"Gavin James Wood, Esquire".to_vec()), Error::::TooLong ); - assert_ok!(Nicks::set_name(Origin::signed(1), b"Dave".to_vec())); - assert_noop!(Nicks::kill_name(Origin::signed(2), 1), BadOrigin); - assert_noop!(Nicks::force_name(Origin::signed(2), 1, b"Whatever".to_vec()), BadOrigin); + assert_ok!(Nicks::set_name(RuntimeOrigin::signed(1), b"Dave".to_vec())); + assert_noop!(Nicks::kill_name(RuntimeOrigin::signed(2), 1), BadOrigin); + assert_noop!( + Nicks::force_name(RuntimeOrigin::signed(2), 1, b"Whatever".to_vec()), + BadOrigin + ); }); } } diff --git a/frame/gilt/Cargo.toml b/frame/nis/Cargo.toml similarity index 56% rename from frame/gilt/Cargo.toml rename to frame/nis/Cargo.toml index c275b693d8f27..be12d97dd871d 100644 --- a/frame/gilt/Cargo.toml +++ b/frame/nis/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "pallet-gilt" +name = "pallet-nis" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for rewarding account freezing." readme = "README.md" @@ -13,34 +13,35 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../../primitives/arithmetic" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", - "sp-arithmetic/std", - "frame-benchmarking/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/gilt/README.md b/frame/nis/README.md similarity index 100% rename from frame/gilt/README.md rename to frame/nis/README.md diff --git a/frame/nis/src/benchmarking.rs b/frame/nis/src/benchmarking.rs new file mode 100644 index 0000000000000..606b1c484b1e8 --- /dev/null +++ b/frame/nis/src/benchmarking.rs @@ -0,0 +1,182 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 NIS Pallet + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_support::traits::{Currency, EnsureOrigin, Get}; +use frame_system::RawOrigin; +use sp_arithmetic::Perquintill; +use sp_runtime::{ + traits::{Bounded, One, Zero}, + DispatchError, PerThing, +}; +use sp_std::prelude::*; + +use crate::Pallet as Nis; + +const SEED: u32 = 0; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +fn fill_queues() -> Result<(), DispatchError> { + // filling queues involves filling the first queue entirely and placing a single item in all + // other queues. + + let queues = T::QueueCount::get(); + let bids = T::MaxQueueLen::get(); + + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be( + &caller, + T::MinBid::get() * BalanceOf::::from(queues + bids), + ); + + for _ in 0..bids { + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + } + for d in 1..queues { + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1 + d)?; + } + Ok(()) +} + +benchmarks! { + place_bid { + let l in 0..(T::MaxQueueLen::get() - 1); + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + for i in 0..l { + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + } + }: _(RawOrigin::Signed(caller.clone()), T::MinBid::get() * BalanceOf::::from(2u32), 1) + verify { + assert_eq!(QueueTotals::::get()[0], (l + 1, T::MinBid::get() * BalanceOf::::from(l + 2))); + } + + place_bid_max { + let caller: T::AccountId = whitelisted_caller(); + let origin = RawOrigin::Signed(caller.clone()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + for i in 0..T::MaxQueueLen::get() { + Nis::::place_bid(origin.clone().into(), T::MinBid::get(), 1)?; + } + }: place_bid(origin, T::MinBid::get() * BalanceOf::::from(2u32), 1) + verify { + assert_eq!(QueueTotals::::get()[0], ( + T::MaxQueueLen::get(), + T::MinBid::get() * BalanceOf::::from(T::MaxQueueLen::get() + 1), + )); + } + + retract_bid { + let l in 1..T::MaxQueueLen::get(); + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + for i in 0..l { + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + } + }: _(RawOrigin::Signed(caller.clone()), T::MinBid::get(), 1) + verify { + assert_eq!(QueueTotals::::get()[0], (l - 1, T::MinBid::get() * BalanceOf::::from(l - 1))); + } + + fund_deficit { + let origin = T::FundOrigin::successful_origin(); + let caller: T::AccountId = whitelisted_caller(); + let bid = T::MinBid::get().max(One::one()); + T::Currency::make_free_balance_be(&caller, bid); + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; + Nis::::process_queues(Perquintill::one(), 1, 1, &mut WeightCounter::unlimited()); + let original = T::Currency::free_balance(&Nis::::account_id()); + T::Currency::make_free_balance_be(&Nis::::account_id(), BalanceOf::::min_value()); + }: _(origin) + verify { + // Must fund at least 99.999% of the required amount. + let missing = Perquintill::from_rational( + T::Currency::free_balance(&Nis::::account_id()), original).left_from_one(); + assert!(missing <= Perquintill::one() / 100_000); + } + + thaw { + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::::from(3u32)); + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + Nis::::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited()); + Receipts::::mutate(0, |m_g| if let Some(ref mut g) = m_g { g.expiry = Zero::zero() }); + }: _(RawOrigin::Signed(caller.clone()), 0, None) + verify { + assert!(Receipts::::get(0).is_none()); + } + + process_queues { + fill_queues::()?; + }: { + Nis::::process_queues( + Perquintill::one(), + Zero::zero(), + u32::max_value(), + &mut WeightCounter::unlimited(), + ) + } + + process_queue { + let our_account = Nis::::account_id(); + let issuance = Nis::::issuance(); + let mut summary = Summary::::get(); + }: { + Nis::::process_queue( + 1u32, + 1u32.into(), + &our_account, + &issuance, + 0, + &mut Bounded::max_value(), + &mut (T::MaxQueueLen::get(), Bounded::max_value()), + &mut summary, + &mut WeightCounter::unlimited(), + ) + } + + process_bid { + let who = account::("bidder", 0, SEED); + let bid = Bid { + amount: T::MinBid::get(), + who, + }; + let our_account = Nis::::account_id(); + let issuance = Nis::::issuance(); + let mut summary = Summary::::get(); + }: { + Nis::::process_bid( + bid, + 2u32.into(), + &our_account, + &issuance, + &mut Bounded::max_value(), + &mut Bounded::max_value(), + &mut summary, + ) + } + + impl_benchmark_test_suite!(Nis, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/nis/src/lib.rs b/frame/nis/src/lib.rs new file mode 100644 index 0000000000000..dff64625a3654 --- /dev/null +++ b/frame/nis/src/lib.rs @@ -0,0 +1,940 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! # Non-Interactive Staking (NIS) Pallet +//! A pallet allowing accounts to auction for being frozen and receive open-ended +//! inflation-protection in return. +//! +//! ## Overview +//! +//! Lock up tokens, for at least as long as you offer, and be free from both inflation and +//! intermediate reward or exchange until the tokens become unlocked. +//! +//! ## Design +//! +//! Queues for each of `1..QueueCount` periods, given in blocks (`Period`). Queues are limited in +//! size to something sensible, `MaxQueueLen`. A secondary storage item with `QueueCount` x `u32` +//! elements with the number of items in each queue. +//! +//! Queues are split into two parts. The first part is a priority queue based on bid size. The +//! second part is just a FIFO (the size of the second part is set with `FifoQueueLen`). Items are +//! always prepended so that removal is always O(1) since removal often happens many times under a +//! single weighed function (`on_initialize`) yet placing bids only ever happens once per weighed +//! function (`place_bid`). If the queue has a priority portion, then it remains sorted in order of +//! bid size so that smaller bids fall off as it gets too large. +//! +//! Account may enqueue a balance with some number of `Period`s lock up, up to a maximum of +//! `QueueCount`. The balance gets reserved. There's a minimum of `MinBid` to avoid dust. +//! +//! Until your bid is consolidated and you receive a receipt, you can retract it instantly and the +//! funds are unreserved. +//! +//! There's a target proportion of effective total issuance (i.e. accounting for existing receipts) +//! which the pallet attempts to have frozen at any one time. It will likely be gradually increased +//! over time by governance. +//! +//! As the proportion of effective total issuance represented by outstanding receipts drops below +//! `FrozenFraction`, then bids are taken from queues and consolidated into receipts, with the queue +//! of the greatest period taking priority. If the item in the queue's locked amount is greater than +//! the amount remaining to achieve `FrozenFraction`, then it is split up into multiple bids and +//! becomes partially consolidated. +//! +//! With the consolidation of a bid, the bid amount is taken from the owner and a receipt is issued. +//! The receipt records the proportion of the bid compared to effective total issuance at the time +//! of consolidation. The receipt has two independent elements: a "main" non-fungible receipt and +//! a second set of fungible "counterpart" tokens. The accounting functionality of the latter must +//! be provided through the `Counterpart` trait item. The main non-fungible receipt may have its +//! owner transferred through the pallet's implementation of `nonfungible::Transfer`. +//! +//! A later `thaw` function may be called in order to reduce the recorded proportion or entirely +//! remove the receipt in return for the appropriate proportion of the effective total issuance. +//! This may happen no earlier than queue's period after the point at which the receipt was issued. +//! The call must be made by the owner of both the "main" non-fungible receipt and the appropriate +//! amount of counterpart tokens. +//! +//! `NoCounterpart` may be provided as an implementation for the counterpart token system in which +//! case they are completely disregarded from the thawing logic. +//! +//! ## Terms +//! +//! - *Effective total issuance*: The total issuance of balances in the system, including all claims +//! of all outstanding receipts but excluding `IgnoredIssuance`. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + traits::fungible::{Inspect as FungibleInspect, Mutate as FungibleMutate}, +}; +pub use pallet::*; +use sp_arithmetic::{traits::Unsigned, RationalArg}; +use sp_core::TypedGet; +use sp_runtime::{ + traits::{Convert, ConvertBack}, + Perquintill, +}; + +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +pub struct WithMaximumOf(sp_std::marker::PhantomData); +impl Convert for WithMaximumOf +where + A::Type: Clone + Unsigned + From, + u64: TryFrom, +{ + fn convert(a: Perquintill) -> A::Type { + a * A::get() + } +} +impl ConvertBack for WithMaximumOf +where + A::Type: RationalArg + From, + u64: TryFrom, + u128: TryFrom, +{ + fn convert_back(a: A::Type) -> Perquintill { + Perquintill::from_rational(a, A::get()) + } +} + +pub struct NoCounterpart(sp_std::marker::PhantomData); +impl FungibleInspect for NoCounterpart { + type Balance = u32; + fn total_issuance() -> u32 { + 0 + } + fn minimum_balance() -> u32 { + 0 + } + fn balance(_who: &T) -> u32 { + 0 + } + fn reducible_balance(_who: &T, _keep_alive: bool) -> u32 { + 0 + } + fn can_deposit( + _who: &T, + _amount: u32, + _mint: bool, + ) -> frame_support::traits::tokens::DepositConsequence { + frame_support::traits::tokens::DepositConsequence::Success + } + fn can_withdraw( + _who: &T, + _amount: u32, + ) -> frame_support::traits::tokens::WithdrawConsequence { + frame_support::traits::tokens::WithdrawConsequence::Success + } +} +impl FungibleMutate for NoCounterpart { + fn mint_into(_who: &T, _amount: u32) -> DispatchResult { + Ok(()) + } + fn burn_from(_who: &T, _amount: u32) -> Result { + Ok(0) + } +} +impl Convert for NoCounterpart { + fn convert(_: Perquintill) -> u32 { + 0 + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::{FungibleInspect, FungibleMutate}; + pub use crate::weights::WeightInfo; + use frame_support::{ + pallet_prelude::*, + traits::{ + nonfungible::{Inspect as NonfungibleInspect, Transfer as NonfungibleTransfer}, + Currency, Defensive, DefensiveSaturating, + ExistenceRequirement::AllowDeath, + OnUnbalanced, ReservableCurrency, + }, + PalletId, + }; + use frame_system::pallet_prelude::*; + use sp_arithmetic::{PerThing, Perquintill}; + use sp_runtime::{ + traits::{AccountIdConversion, Bounded, Convert, ConvertBack, Saturating, Zero}, + TokenError, + }; + use sp_std::prelude::*; + + type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + type PositiveImbalanceOf = <::Currency as Currency< + ::AccountId, + >>::PositiveImbalance; + type ReceiptRecordOf = ReceiptRecord< + ::AccountId, + ::BlockNumber, + >; + type IssuanceInfoOf = IssuanceInfo>; + type SummaryRecordOf = SummaryRecord<::BlockNumber>; + type BidOf = Bid, ::AccountId>; + type QueueTotalsTypeOf = BoundedVec<(u32, BalanceOf), ::QueueCount>; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Information on runtime weights. + type WeightInfo: WeightInfo; + + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The treasury's pallet id, used for deriving its sovereign account ID. + #[pallet::constant] + type PalletId: Get; + + /// Currency type that this works on. + type Currency: ReservableCurrency; + + /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to + /// `From`. + type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned + + codec::FullCodec + + Copy + + MaybeSerializeDeserialize + + sp_std::fmt::Debug + + Default + + From + + TypeInfo + + MaxEncodedLen; + + /// Origin required for auto-funding the deficit. + type FundOrigin: EnsureOrigin; + + /// The issuance to ignore. This is subtracted from the `Currency`'s `total_issuance` to get + /// the issuance with which we determine the thawed value of a given proportion. + type IgnoredIssuance: Get>; + + /// The accounting system for the fungible counterpart tokens. + type Counterpart: FungibleMutate; + + /// The system to convert an overall proportion of issuance into a number of fungible + /// counterpart tokens. + /// + /// In general it's best to use `WithMaximumOf`. + type CounterpartAmount: ConvertBack< + Perquintill, + >::Balance, + >; + + /// Unbalanced handler to account for funds created (in case of a higher total issuance over + /// freezing period). + type Deficit: OnUnbalanced>; + + /// The target sum of all receipts' proportions. + type Target: Get; + + /// Number of duration queues in total. This sets the maximum duration supported, which is + /// this value multiplied by `Period`. + #[pallet::constant] + type QueueCount: Get; + + /// Maximum number of items that may be in each duration queue. + /// + /// Must be larger than zero. + #[pallet::constant] + type MaxQueueLen: Get; + + /// Portion of the queue which is free from ordering and just a FIFO. + /// + /// Must be no greater than `MaxQueueLen`. + #[pallet::constant] + type FifoQueueLen: Get; + + /// The base period for the duration queues. This is the common multiple across all + /// supported freezing durations that can be bid upon. + #[pallet::constant] + type BasePeriod: Get; + + /// The minimum amount of funds that may be placed in a bid. Note that this + /// does not actually limit the amount which may be represented in a receipt since bids may + /// be split up by the system. + /// + /// It should be at least big enough to ensure that there is no possible storage spam attack + /// or queue-filling attack. + #[pallet::constant] + type MinBid: Get>; + + /// The minimum amount of funds which may intentionally be left remaining under a single + /// receipt. + #[pallet::constant] + type MinReceipt: Get; + + /// The number of blocks between consecutive attempts to dequeue bids and create receipts. + /// + /// A larger value results in fewer storage hits each block, but a slower period to get to + /// the target. + #[pallet::constant] + type IntakePeriod: Get; + + /// The maximum amount of bids that can consolidated into receipts in a single intake. A + /// larger value here means less of the block available for transactions should there be a + /// glut of bids. + #[pallet::constant] + type MaxIntakeWeight: Get; + + /// The maximum proportion which may be thawed and the period over which it is reset. + #[pallet::constant] + type ThawThrottle: Get<(Perquintill, Self::BlockNumber)>; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// A single bid, an item of a *queue* in `Queues`. + #[derive( + Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, + )] + pub struct Bid { + /// The amount bid. + pub amount: Balance, + /// The owner of the bid. + pub who: AccountId, + } + + /// Information representing a receipt. + #[derive( + Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, + )] + pub struct ReceiptRecord { + /// The proportion of the effective total issuance. + pub proportion: Perquintill, + /// The account to whom this receipt belongs. + pub who: AccountId, + /// The time after which this receipt can be thawed. + pub expiry: BlockNumber, + } + + /// An index for a receipt. + pub type ReceiptIndex = u32; + + /// Overall information package on the outstanding receipts. + /// + /// The way of determining the net issuance (i.e. after factoring in all maturing frozen funds) + /// is: + /// + /// `issuance - frozen + proportion * issuance` + /// + /// where `issuance = total_issuance - IgnoredIssuance` + #[derive( + Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, + )] + pub struct SummaryRecord { + /// The total proportion over all outstanding receipts. + pub proportion_owed: Perquintill, + /// The total number of receipts created so far. + pub index: ReceiptIndex, + /// The amount (as a proportion of ETI) which has been thawed in this period so far. + pub thawed: Perquintill, + /// The current thaw period's beginning. + pub last_period: BlockNumber, + } + + pub struct OnEmptyQueueTotals(sp_std::marker::PhantomData); + impl Get> for OnEmptyQueueTotals { + fn get() -> QueueTotalsTypeOf { + BoundedVec::truncate_from(vec![ + (0, Zero::zero()); + ::QueueCount::get() as usize + ]) + } + } + + /// The totals of items and balances within each queue. Saves a lot of storage reads in the + /// case of sparsely packed queues. + /// + /// The vector is indexed by duration in `Period`s, offset by one, so information on the queue + /// whose duration is one `Period` would be storage `0`. + #[pallet::storage] + pub type QueueTotals = + StorageValue<_, QueueTotalsTypeOf, ValueQuery, OnEmptyQueueTotals>; + + /// The queues of bids. Indexed by duration (in `Period`s). + #[pallet::storage] + pub type Queues = + StorageMap<_, Blake2_128Concat, u32, BoundedVec, T::MaxQueueLen>, ValueQuery>; + + /// Summary information over the general state. + #[pallet::storage] + pub type Summary = StorageValue<_, SummaryRecordOf, ValueQuery>; + + /// The currently outstanding receipts, indexed according to the order of creation. + #[pallet::storage] + pub type Receipts = + StorageMap<_, Blake2_128Concat, ReceiptIndex, ReceiptRecordOf, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A bid was successfully placed. + BidPlaced { who: T::AccountId, amount: BalanceOf, duration: u32 }, + /// A bid was successfully removed (before being accepted). + BidRetracted { who: T::AccountId, amount: BalanceOf, duration: u32 }, + /// A bid was dropped from a queue because of another, more substantial, bid was present. + BidDropped { who: T::AccountId, amount: BalanceOf, duration: u32 }, + /// A bid was accepted. The balance may not be released until expiry. + Issued { + /// The identity of the receipt. + index: ReceiptIndex, + /// The block number at which the receipt may be thawed. + expiry: T::BlockNumber, + /// The owner of the receipt. + who: T::AccountId, + /// The proportion of the effective total issuance which the receipt represents. + proportion: Perquintill, + /// The amount of funds which were debited from the owner. + amount: BalanceOf, + }, + /// An receipt has been (at least partially) thawed. + Thawed { + /// The identity of the receipt. + index: ReceiptIndex, + /// The owner. + who: T::AccountId, + /// The proportion of the effective total issuance by which the owner was debited. + proportion: Perquintill, + /// The amount by which the owner was credited. + amount: BalanceOf, + /// If `true` then the receipt is done. + dropped: bool, + }, + /// An automatic funding of the deficit was made. + Funded { deficit: BalanceOf }, + /// A receipt was transfered. + Transferred { from: T::AccountId, to: T::AccountId, index: ReceiptIndex }, + } + + #[pallet::error] + pub enum Error { + /// The duration of the bid is less than one. + DurationTooSmall, + /// The duration is the bid is greater than the number of queues. + DurationTooBig, + /// The amount of the bid is less than the minimum allowed. + AmountTooSmall, + /// The queue for the bid's duration is full and the amount bid is too low to get in + /// through replacing an existing bid. + BidTooLow, + /// Bond index is unknown. + Unknown, + /// Not the owner of the receipt. + NotOwner, + /// Bond not yet at expiry date. + NotExpired, + /// The given bid for retraction is not found. + NotFound, + /// The portion supplied is beyond the value of the receipt. + TooMuch, + /// Not enough funds are held to pay out. + Unfunded, + /// There are enough funds for what is required. + Funded, + /// The thaw throttle has been reached for this period. + Throttled, + /// The operation would result in a receipt worth an insignficant value. + MakesDust, + } + + pub(crate) struct WeightCounter { + pub(crate) used: Weight, + pub(crate) limit: Weight, + } + impl WeightCounter { + #[allow(dead_code)] + pub(crate) fn unlimited() -> Self { + WeightCounter { used: Weight::zero(), limit: Weight::max_value() } + } + fn check_accrue(&mut self, w: Weight) -> bool { + let test = self.used.saturating_add(w); + if test.any_gt(self.limit) { + false + } else { + self.used = test; + true + } + } + fn can_accrue(&mut self, w: Weight) -> bool { + self.used.saturating_add(w).all_lte(self.limit) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: T::BlockNumber) -> Weight { + let mut weight_counter = + WeightCounter { used: Weight::zero(), limit: T::MaxIntakeWeight::get() }; + if T::IntakePeriod::get().is_zero() || (n % T::IntakePeriod::get()).is_zero() { + if weight_counter.check_accrue(T::WeightInfo::process_queues()) { + Self::process_queues( + T::Target::get(), + T::QueueCount::get(), + u32::max_value(), + &mut weight_counter, + ) + } + } + weight_counter.used + } + + fn integrity_test() { + assert!(!T::IntakePeriod::get().is_zero()); + assert!(!T::MaxQueueLen::get().is_zero()); + } + } + + #[pallet::call] + impl Pallet { + /// Place a bid. + /// + /// Origin must be Signed, and account must have at least `amount` in free balance. + /// + /// - `amount`: The amount of the bid; these funds will be reserved, and if/when + /// consolidated, removed. Must be at least `MinBid`. + /// - `duration`: The number of periods before which the newly consolidated bid may be + /// thawed. Must be greater than 1 and no more than `QueueCount`. + /// + /// Complexities: + /// - `Queues[duration].len()` (just take max). + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::place_bid_max())] + pub fn place_bid( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + duration: u32, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(amount >= T::MinBid::get(), Error::::AmountTooSmall); + let queue_count = T::QueueCount::get() as usize; + let queue_index = duration.checked_sub(1).ok_or(Error::::DurationTooSmall)? as usize; + ensure!(queue_index < queue_count, Error::::DurationTooBig); + + let net = Queues::::try_mutate( + duration, + |q| -> Result<(u32, BalanceOf), DispatchError> { + let queue_full = q.len() == T::MaxQueueLen::get() as usize; + ensure!(!queue_full || q[0].amount < amount, Error::::BidTooLow); + T::Currency::reserve(&who, amount)?; + + // queue is + let mut bid = Bid { amount, who: who.clone() }; + let net = if queue_full { + sp_std::mem::swap(&mut q[0], &mut bid); + let _ = T::Currency::unreserve(&bid.who, bid.amount); + Self::deposit_event(Event::::BidDropped { + who: bid.who, + amount: bid.amount, + duration, + }); + (0, amount - bid.amount) + } else { + q.try_insert(0, bid).expect("verified queue was not full above. qed."); + (1, amount) + }; + + let sorted_item_count = q.len().saturating_sub(T::FifoQueueLen::get() as usize); + if sorted_item_count > 1 { + q[0..sorted_item_count].sort_by_key(|x| x.amount); + } + + Ok(net) + }, + )?; + QueueTotals::::mutate(|qs| { + qs.bounded_resize(queue_count, (0, Zero::zero())); + qs[queue_index].0 += net.0; + qs[queue_index].1.saturating_accrue(net.1); + }); + Self::deposit_event(Event::BidPlaced { who, amount, duration }); + + Ok(()) + } + + /// Retract a previously placed bid. + /// + /// Origin must be Signed, and the account should have previously issued a still-active bid + /// of `amount` for `duration`. + /// + /// - `amount`: The amount of the previous bid. + /// - `duration`: The duration of the previous bid. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::retract_bid(T::MaxQueueLen::get()))] + pub fn retract_bid( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + duration: u32, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let queue_count = T::QueueCount::get() as usize; + let queue_index = duration.checked_sub(1).ok_or(Error::::DurationTooSmall)? as usize; + ensure!(queue_index < queue_count, Error::::DurationTooBig); + + let bid = Bid { amount, who }; + let new_len = Queues::::try_mutate(duration, |q| -> Result { + let pos = q.iter().position(|i| i == &bid).ok_or(Error::::NotFound)?; + q.remove(pos); + Ok(q.len() as u32) + })?; + + QueueTotals::::mutate(|qs| { + qs.bounded_resize(queue_count, (0, Zero::zero())); + qs[queue_index].0 = new_len; + qs[queue_index].1.saturating_reduce(bid.amount); + }); + + T::Currency::unreserve(&bid.who, bid.amount); + Self::deposit_event(Event::BidRetracted { who: bid.who, amount: bid.amount, duration }); + + Ok(()) + } + + /// Ensure we have sufficient funding for all potential payouts. + /// + /// - `origin`: Must be accepted by `FundOrigin`. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::fund_deficit())] + pub fn fund_deficit(origin: OriginFor) -> DispatchResult { + T::FundOrigin::ensure_origin(origin)?; + let summary: SummaryRecordOf = Summary::::get(); + let our_account = Self::account_id(); + let issuance = Self::issuance_with(&our_account, &summary); + let deficit = issuance.required.saturating_sub(issuance.holdings); + ensure!(!deficit.is_zero(), Error::::Funded); + T::Deficit::on_unbalanced(T::Currency::deposit_creating(&our_account, deficit)); + Self::deposit_event(Event::::Funded { deficit }); + Ok(()) + } + + /// Reduce or remove an outstanding receipt, placing the according proportion of funds into + /// the account of the owner. + /// + /// - `origin`: Must be Signed and the account must be the owner of the receipt `index` as + /// well as any fungible counterpart. + /// - `index`: The index of the receipt. + /// - `portion`: If `Some`, then only the given portion of the receipt should be thawed. If + /// `None`, then all of it should be. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::thaw())] + pub fn thaw( + origin: OriginFor, + #[pallet::compact] index: ReceiptIndex, + portion: Option<>::Balance>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + // Look for `index` + let mut receipt: ReceiptRecordOf = + Receipts::::get(index).ok_or(Error::::Unknown)?; + // If found, check the owner is `who`. + ensure!(receipt.who == who, Error::::NotOwner); + let now = frame_system::Pallet::::block_number(); + ensure!(now >= receipt.expiry, Error::::NotExpired); + + let mut summary: SummaryRecordOf = Summary::::get(); + + let proportion = if let Some(counterpart) = portion { + let proportion = T::CounterpartAmount::convert_back(counterpart); + ensure!(proportion <= receipt.proportion, Error::::TooMuch); + let remaining = receipt.proportion.saturating_sub(proportion); + ensure!( + remaining.is_zero() || remaining >= T::MinReceipt::get(), + Error::::MakesDust + ); + proportion + } else { + receipt.proportion + }; + + let (throttle, throttle_period) = T::ThawThrottle::get(); + if now.saturating_sub(summary.last_period) >= throttle_period { + summary.thawed = Zero::zero(); + summary.last_period = now; + } + summary.thawed.saturating_accrue(proportion); + ensure!(summary.thawed <= throttle, Error::::Throttled); + + T::Counterpart::burn_from(&who, T::CounterpartAmount::convert(proportion))?; + + // Multiply the proportion it is by the total issued. + let our_account = Self::account_id(); + let effective_issuance = Self::issuance_with(&our_account, &summary).effective; + let amount = proportion * effective_issuance; + + receipt.proportion.saturating_reduce(proportion); + summary.proportion_owed.saturating_reduce(proportion); + + T::Currency::transfer(&our_account, &who, amount, AllowDeath) + .map_err(|_| Error::::Unfunded)?; + + let dropped = receipt.proportion.is_zero(); + if dropped { + Receipts::::remove(index); + } else { + Receipts::::insert(index, &receipt); + } + Summary::::put(&summary); + + Self::deposit_event(Event::Thawed { index, who, amount, proportion, dropped }); + + Ok(()) + } + } + + /// Issuance information returned by `issuance()`. + #[derive(RuntimeDebug)] + pub struct IssuanceInfo { + /// The balance held in reserve by this pallet instance. + pub holdings: Balance, + /// The (non-ignored) issuance in the system, not including this pallet's account. + pub other: Balance, + /// The effective total issuance, hypothetically if all outstanding receipts were thawed at + /// present. + pub effective: Balance, + /// The amount needed to be the pallet instance's account in case all outstanding receipts + /// were thawed at present. + pub required: Balance, + } + + impl NonfungibleInspect for Pallet { + type ItemId = ReceiptIndex; + + fn owner(item: &ReceiptIndex) -> Option { + Receipts::::get(item).map(|r| r.who) + } + + fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + let item = Receipts::::get(item)?; + match key { + b"proportion" => Some(item.proportion.encode()), + b"expiry" => Some(item.expiry.encode()), + _ => None, + } + } + } + + impl NonfungibleTransfer for Pallet { + fn transfer(index: &ReceiptIndex, destination: &T::AccountId) -> DispatchResult { + let mut item = Receipts::::get(index).ok_or(TokenError::UnknownAsset)?; + let from = item.who; + item.who = destination.clone(); + Receipts::::insert(&index, &item); + Pallet::::deposit_event(Event::::Transferred { + from, + to: item.who, + index: *index, + }); + Ok(()) + } + } + + impl Pallet { + /// The account ID of the reserves. + /// + /// This actually does computation. If you need to keep using it, then make sure you cache + /// the value and only call this once. + pub fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + + /// Returns information on the issuance within the system. + pub fn issuance() -> IssuanceInfo> { + Self::issuance_with(&Self::account_id(), &Summary::::get()) + } + + /// Returns information on the issuance within the system + /// + /// This function is equivalent to `issuance`, except that it accepts arguments rather than + /// queries state. If the arguments are already known, then this may be slightly more + /// performant. + /// + /// - `our_account`: The value returned by `Self::account_id()`. + /// - `summary`: The value returned by `Summary::::get()`. + pub fn issuance_with( + our_account: &T::AccountId, + summary: &SummaryRecordOf, + ) -> IssuanceInfo> { + let total_issuance = + T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get()); + let holdings = T::Currency::free_balance(our_account); + let other = total_issuance.saturating_sub(holdings); + let effective = + summary.proportion_owed.left_from_one().saturating_reciprocal_mul(other); + let required = summary.proportion_owed * effective; + IssuanceInfo { holdings, other, effective, required } + } + + /// Process some bids into receipts up to a `target` total of all receipts. + /// + /// Touch at most `max_queues`. + /// + /// Return the weight used. + pub(crate) fn process_queues( + target: Perquintill, + max_queues: u32, + max_bids: u32, + weight: &mut WeightCounter, + ) { + let mut summary: SummaryRecordOf = Summary::::get(); + if summary.proportion_owed >= target { + return + } + + let now = frame_system::Pallet::::block_number(); + let our_account = Self::account_id(); + let issuance: IssuanceInfoOf = Self::issuance_with(&our_account, &summary); + let mut remaining = target.saturating_sub(summary.proportion_owed) * issuance.effective; + + let mut queues_hit = 0; + let mut bids_hit = 0; + let mut totals = QueueTotals::::get(); + let queue_count = T::QueueCount::get(); + totals.bounded_resize(queue_count as usize, (0, Zero::zero())); + for duration in (1..=queue_count).rev() { + if totals[duration as usize - 1].0.is_zero() { + continue + } + if remaining.is_zero() || queues_hit >= max_queues + || !weight.check_accrue(T::WeightInfo::process_queue()) + // No point trying to process a queue if we can't process a single bid. + || !weight.can_accrue(T::WeightInfo::process_bid()) + { + break + } + + let b = Self::process_queue( + duration, + now, + &our_account, + &issuance, + max_bids.saturating_sub(bids_hit), + &mut remaining, + &mut totals[duration as usize - 1], + &mut summary, + weight, + ); + + bids_hit.saturating_accrue(b); + queues_hit.saturating_inc(); + } + QueueTotals::::put(&totals); + Summary::::put(&summary); + } + + pub(crate) fn process_queue( + duration: u32, + now: T::BlockNumber, + our_account: &T::AccountId, + issuance: &IssuanceInfo>, + max_bids: u32, + remaining: &mut BalanceOf, + queue_total: &mut (u32, BalanceOf), + summary: &mut SummaryRecordOf, + weight: &mut WeightCounter, + ) -> u32 { + let mut queue: BoundedVec, _> = Queues::::get(&duration); + let expiry = now.saturating_add(T::BasePeriod::get().saturating_mul(duration.into())); + let mut count = 0; + + while count < max_bids && + !queue.is_empty() && + !remaining.is_zero() && + weight.check_accrue(T::WeightInfo::process_bid()) + { + let bid = match queue.pop() { + Some(b) => b, + None => break, + }; + if let Some(bid) = Self::process_bid( + bid, + expiry, + our_account, + issuance, + remaining, + &mut queue_total.1, + summary, + ) { + queue.try_push(bid).expect("just popped, so there must be space. qed"); + // This should exit at the next iteration (though nothing will break if it + // doesn't). + } + count.saturating_inc(); + } + queue_total.0 = queue.len() as u32; + Queues::::insert(&duration, &queue); + count + } + + pub(crate) fn process_bid( + mut bid: BidOf, + expiry: T::BlockNumber, + our_account: &T::AccountId, + issuance: &IssuanceInfo>, + remaining: &mut BalanceOf, + queue_amount: &mut BalanceOf, + summary: &mut SummaryRecordOf, + ) -> Option> { + let result = if *remaining < bid.amount { + let overflow = bid.amount - *remaining; + bid.amount = *remaining; + Some(Bid { amount: overflow, who: bid.who.clone() }) + } else { + None + }; + let amount = bid.amount.saturating_sub(T::Currency::unreserve(&bid.who, bid.amount)); + if T::Currency::transfer(&bid.who, &our_account, amount, AllowDeath).is_err() { + return result + } + + // Can never overflow due to block above. + remaining.saturating_reduce(amount); + // Should never underflow since it should track the total of the + // bids exactly, but we'll be defensive. + queue_amount.defensive_saturating_reduce(amount); + + // Now to activate the bid... + let n = amount; + let d = issuance.effective; + let proportion = Perquintill::from_rational(n, d); + let who = bid.who; + let index = summary.index; + summary.proportion_owed.defensive_saturating_accrue(proportion); + summary.index += 1; + + let e = Event::Issued { index, expiry, who: who.clone(), amount, proportion }; + Self::deposit_event(e); + let receipt = ReceiptRecord { proportion, who: who.clone(), expiry }; + Receipts::::insert(index, receipt); + + // issue the fungible counterpart + let fung_eq = T::CounterpartAmount::convert(proportion); + let _ = T::Counterpart::mint_into(&who, fung_eq).defensive(); + result + } + } +} diff --git a/frame/gilt/src/mock.rs b/frame/nis/src/mock.rs similarity index 53% rename from frame/gilt/src/mock.rs rename to frame/nis/src/mock.rs index ac3f4df1b71dd..ebe073d683309 100644 --- a/frame/gilt/src/mock.rs +++ b/frame/nis/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,15 +15,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Test environment for Gilt pallet. +//! Test environment for NIS pallet. -use crate as pallet_gilt; +use crate::{self as pallet_nis, Perquintill, WithMaximumOf}; use frame_support::{ ord_parameter_types, parameter_types, - traits::{Currency, GenesisBuild, OnFinalize, OnInitialize}, + traits::{ConstU16, ConstU32, ConstU64, Currency, OnFinalize, OnInitialize, StorageMapShim}, + weights::Weight, + PalletId, }; -use sp_core::H256; +use pallet_balances::{Instance1, Instance2}; +use sp_core::{ConstU128, H256}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, @@ -39,23 +42,19 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Config, Storage, Event}, - Gilt: pallet_gilt::{Pallet, Call, Config, Storage, Event}, + System: frame_system, + Balances: pallet_balances::, + NisBalances: pallet_balances::, + Nis: pallet_nis, } ); -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -63,8 +62,8 @@ impl frame_system::Config for Test { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; @@ -72,19 +71,16 @@ impl frame_system::Config for Test { type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; + type SS58Prefix = ConstU16<42>; type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} - -impl pallet_balances::Config for Test { +impl pallet_balances::Config for Test { type Balance = u64; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = frame_support::traits::ConstU64<1>; type AccountStore = System; type WeightInfo = (); type MaxLocks = (); @@ -92,59 +88,79 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; } +impl pallet_balances::Config for Test { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = frame_support::traits::ConstU128<1>; + type AccountStore = StorageMapShim< + pallet_balances::Account, + frame_system::Provider, + u64, + pallet_balances::AccountData, + >; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + parameter_types! { pub IgnoredIssuance: u64 = Balances::total_balance(&0); // Account zero is ignored. - pub const QueueCount: u32 = 3; - pub const MaxQueueLen: u32 = 3; - pub const FifoQueueLen: u32 = 1; - pub const Period: u64 = 3; - pub const MinFreeze: u64 = 2; - pub const IntakePeriod: u64 = 2; - pub const MaxIntakeBids: u32 = 2; + pub const NisPalletId: PalletId = PalletId(*b"py/nis "); + pub static Target: Perquintill = Perquintill::zero(); + pub const MinReceipt: Perquintill = Perquintill::from_percent(1); + pub const ThawThrottle: (Perquintill, u64) = (Perquintill::from_percent(25), 5); + pub static MaxIntakeWeight: Weight = Weight::from_ref_time(2_000_000_000_000); } + ord_parameter_types! { pub const One: u64 = 1; } -impl pallet_gilt::Config for Test { - type Event = Event; +impl pallet_nis::Config for Test { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type PalletId = NisPalletId; type Currency = Balances; - type CurrencyBalance = ::Balance; - type AdminOrigin = frame_system::EnsureSignedBy; + type CurrencyBalance = >::Balance; + type FundOrigin = frame_system::EnsureSigned; type Deficit = (); - type Surplus = (); type IgnoredIssuance = IgnoredIssuance; - type QueueCount = QueueCount; - type MaxQueueLen = MaxQueueLen; - type FifoQueueLen = FifoQueueLen; - type Period = Period; - type MinFreeze = MinFreeze; - type IntakePeriod = IntakePeriod; - type MaxIntakeBids = MaxIntakeBids; - type WeightInfo = (); + type Counterpart = NisBalances; + type CounterpartAmount = WithMaximumOf>; + type Target = Target; + type QueueCount = ConstU32<3>; + type MaxQueueLen = ConstU32<3>; + type FifoQueueLen = ConstU32<1>; + type BasePeriod = ConstU64<3>; + type MinBid = ConstU64<2>; + type IntakePeriod = ConstU64<2>; + type MaxIntakeWeight = MaxIntakeWeight; + type MinReceipt = MinReceipt; + type ThawThrottle = ThawThrottle; } // This function basically just builds a genesis storage key/value store according to // our desired mockup. pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { + pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100)], } .assimilate_storage(&mut t) .unwrap(); - GenesisBuild::::assimilate_storage(&crate::GenesisConfig, &mut t).unwrap(); t.into() } pub fn run_to_block(n: u64) { while System::block_number() < n { - Gilt::on_finalize(System::block_number()); + Nis::on_finalize(System::block_number()); Balances::on_finalize(System::block_number()); System::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); Balances::on_initialize(System::block_number()); - Gilt::on_initialize(System::block_number()); + Nis::on_initialize(System::block_number()); } } diff --git a/frame/nis/src/tests.rs b/frame/nis/src/tests.rs new file mode 100644 index 0000000000000..f0c45cc80b0e5 --- /dev/null +++ b/frame/nis/src/tests.rs @@ -0,0 +1,654 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Tests for NIS pallet. + +use super::*; +use crate::{mock::*, Error}; +use frame_support::{ + assert_noop, assert_ok, + traits::{ + nonfungible::{Inspect, Transfer}, + Currency, + }, +}; +use pallet_balances::{Error as BalancesError, Instance1}; +use sp_arithmetic::Perquintill; +use sp_runtime::{Saturating, TokenError}; + +fn pot() -> u64 { + Balances::free_balance(&Nis::account_id()) +} + +fn enlarge(amount: u64, max_bids: u32) { + let summary: SummaryRecord = Summary::::get(); + let increase_in_proportion_owed = Perquintill::from_rational(amount, Nis::issuance().effective); + let target = summary.proportion_owed.saturating_add(increase_in_proportion_owed); + Nis::process_queues(target, u32::max_value(), max_bids, &mut WeightCounter::unlimited()); +} + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + + for q in 0..3 { + assert!(Queues::::get(q).is_empty()); + } + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::zero(), + index: 0, + last_period: 0, + thawed: Perquintill::zero() + } + ); + }); +} + +#[test] +fn place_bid_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_noop!(Nis::place_bid(RuntimeOrigin::signed(1), 1, 2), Error::::AmountTooSmall); + assert_noop!( + Nis::place_bid(RuntimeOrigin::signed(1), 101, 2), + BalancesError::::InsufficientBalance + ); + assert_noop!( + Nis::place_bid(RuntimeOrigin::signed(1), 10, 4), + Error::::DurationTooBig + ); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_eq!(Balances::reserved_balance(1), 10); + assert_eq!(Queues::::get(2), vec![Bid { amount: 10, who: 1 }]); + assert_eq!(QueueTotals::::get(), vec![(0, 0), (1, 10), (0, 0)]); + }); +} + +#[test] +fn place_bid_queuing_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 20, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 5, 2)); + assert_noop!(Nis::place_bid(RuntimeOrigin::signed(1), 5, 2), Error::::BidTooLow); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 15, 2)); + assert_eq!(Balances::reserved_balance(1), 45); + + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 25, 2)); + assert_eq!(Balances::reserved_balance(1), 60); + assert_noop!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2), Error::::BidTooLow); + assert_eq!( + Queues::::get(2), + vec![ + Bid { amount: 15, who: 1 }, + Bid { amount: 25, who: 1 }, + Bid { amount: 20, who: 1 }, + ] + ); + assert_eq!(QueueTotals::::get(), vec![(0, 0), (3, 60), (0, 0)]); + }); +} + +#[test] +fn place_bid_fails_when_queue_full() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(3), 10, 2)); + assert_noop!(Nis::place_bid(RuntimeOrigin::signed(4), 10, 2), Error::::BidTooLow); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(4), 10, 3)); + }); +} + +#[test] +fn multiple_place_bids_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 3)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 10, 2)); + + assert_eq!(Balances::reserved_balance(1), 40); + assert_eq!(Balances::reserved_balance(2), 10); + assert_eq!(Queues::::get(1), vec![Bid { amount: 10, who: 1 },]); + assert_eq!( + Queues::::get(2), + vec![ + Bid { amount: 10, who: 2 }, + Bid { amount: 10, who: 1 }, + Bid { amount: 10, who: 1 }, + ] + ); + assert_eq!(Queues::::get(3), vec![Bid { amount: 10, who: 1 },]); + assert_eq!(QueueTotals::::get(), vec![(1, 10), (3, 30), (1, 10)]); + }); +} + +#[test] +fn retract_single_item_queue_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 1)); + + assert_eq!(Balances::reserved_balance(1), 10); + assert_eq!(Queues::::get(1), vec![]); + assert_eq!(Queues::::get(2), vec![Bid { amount: 10, who: 1 }]); + assert_eq!(QueueTotals::::get(), vec![(0, 0), (1, 10), (0, 0)]); + }); +} + +#[test] +fn retract_with_other_and_duplicate_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 10, 2)); + + assert_ok!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_eq!(Balances::reserved_balance(1), 20); + assert_eq!(Balances::reserved_balance(2), 10); + assert_eq!(Queues::::get(1), vec![Bid { amount: 10, who: 1 },]); + assert_eq!( + Queues::::get(2), + vec![Bid { amount: 10, who: 2 }, Bid { amount: 10, who: 1 },] + ); + assert_eq!(QueueTotals::::get(), vec![(1, 10), (2, 20), (0, 0)]); + }); +} + +#[test] +fn retract_non_existent_item_fails() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 1), Error::::NotFound); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1)); + assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(1), 20, 1), Error::::NotFound); + assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 2), Error::::NotFound); + assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(2), 10, 1), Error::::NotFound); + }); +} + +#[test] +fn basic_enlarge_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 2)); + enlarge(40, 2); + + // Takes 2/2, then stopped because it reaches its max amount + assert_eq!(Balances::reserved_balance(1), 40); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(pot(), 40); + + assert_eq!(Queues::::get(1), vec![Bid { amount: 40, who: 1 }]); + assert_eq!(Queues::::get(2), vec![]); + assert_eq!(QueueTotals::::get(), vec![(1, 40), (0, 0), (0, 0)]); + + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(10), + index: 1, + last_period: 0, + thawed: Perquintill::zero() + } + ); + assert_eq!( + Receipts::::get(0).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 7 } + ); + }); +} + +#[test] +fn enlarge_respects_bids_limit() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(3), 40, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(4), 40, 3)); + enlarge(100, 2); + + // Should have taken 4/3 and 2/2, then stopped because it's only allowed 2. + assert_eq!(Queues::::get(1), vec![Bid { amount: 40, who: 1 }]); + assert_eq!(Queues::::get(2), vec![Bid { amount: 40, who: 3 }]); + assert_eq!(Queues::::get(3), vec![]); + assert_eq!(QueueTotals::::get(), vec![(1, 40), (1, 40), (0, 0)]); + + assert_eq!( + Receipts::::get(0).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 4, expiry: 10 } + ); + assert_eq!( + Receipts::::get(1).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 7 } + ); + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(20), + index: 2, + last_period: 0, + thawed: Perquintill::zero() + } + ); + }); +} + +#[test] +fn enlarge_respects_amount_limit_and_will_split() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 80, 1)); + enlarge(40, 2); + + // Takes 2/2, then stopped because it reaches its max amount + assert_eq!(Queues::::get(1), vec![Bid { amount: 40, who: 1 }]); + assert_eq!(QueueTotals::::get(), vec![(1, 40), (0, 0), (0, 0)]); + + assert_eq!( + Receipts::::get(0).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 1, expiry: 4 } + ); + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(10), + index: 1, + last_period: 0, + thawed: Perquintill::zero() + } + ); + }); +} + +#[test] +fn basic_thaw_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_eq!(Nis::issuance().effective, 400); + assert_eq!(Balances::free_balance(1), 60); + assert_eq!(Balances::reserved_balance(1), 40); + assert_eq!(pot(), 0); + + enlarge(40, 1); + assert_eq!(Nis::issuance().effective, 400); + assert_eq!(Balances::free_balance(1), 60); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(pot(), 40); + + run_to_block(3); + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::::NotExpired); + run_to_block(4); + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 1, None), Error::::Unknown); + assert_noop!(Nis::thaw(RuntimeOrigin::signed(2), 0, None), Error::::NotOwner); + + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + assert_eq!(NisBalances::free_balance(1), 0); + assert_eq!(Nis::typed_attribute::<_, Perquintill>(&0, b"proportion"), None); + assert_eq!(Nis::issuance().effective, 400); + assert_eq!(Balances::free_balance(1), 100); + assert_eq!(pot(), 0); + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::zero(), + index: 1, + last_period: 0, + thawed: Perquintill::from_percent(10) + } + ); + assert_eq!(Receipts::::get(0), None); + }); +} + +#[test] +fn partial_thaw_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 80, 1)); + enlarge(80, 1); + assert_eq!(pot(), 80); + + run_to_block(4); + assert_noop!( + Nis::thaw(RuntimeOrigin::signed(1), 0, Some(4_100_000)), + Error::::MakesDust + ); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, Some(1_050_000))); + + assert_eq!(NisBalances::free_balance(1), 3_150_000); + assert_eq!( + Nis::typed_attribute::<_, Perquintill>(&0, b"proportion"), + Some(Perquintill::from_rational(3_150_000u64, 21_000_000u64)), + ); + + assert_eq!(Nis::issuance().effective, 400); + assert_eq!(Balances::free_balance(1), 40); + assert_eq!(pot(), 60); + + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + + assert_eq!(Nis::issuance().effective, 400); + assert_eq!(Balances::free_balance(1), 100); + assert_eq!(pot(), 0); + + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::zero(), + index: 1, + last_period: 0, + thawed: Perquintill::from_percent(20) + } + ); + assert_eq!(Receipts::::get(0), None); + }); +} + +#[test] +fn thaw_respects_transfers() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + enlarge(40, 1); + run_to_block(4); + + assert_eq!(Nis::owner(&0), Some(1)); + assert_ok!(Nis::transfer(&0, &2)); + + // Transfering the receipt... + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::::NotOwner); + // ...can't be thawed due to missing counterpart + assert_noop!(Nis::thaw(RuntimeOrigin::signed(2), 0, None), TokenError::NoFunds); + + // Transfer the counterpart also... + assert_ok!(NisBalances::transfer(RuntimeOrigin::signed(1), 2, 2100000)); + // ...and thawing is possible. + assert_ok!(Nis::thaw(RuntimeOrigin::signed(2), 0, None)); + + assert_eq!(Balances::free_balance(2), 140); + assert_eq!(Balances::free_balance(1), 60); + }); +} + +#[test] +fn thaw_when_issuance_higher_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 100, 1)); + enlarge(100, 1); + + assert_eq!(NisBalances::free_balance(1), 5_250_000); // (25% of 21m) + + // Everybody else's balances goes up by 50% + Balances::make_free_balance_be(&2, 150); + Balances::make_free_balance_be(&3, 150); + Balances::make_free_balance_be(&4, 150); + + run_to_block(4); + + // Unfunded initially... + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::::Unfunded); + // ...so we fund. + assert_ok!(Nis::fund_deficit(RuntimeOrigin::signed(1))); + + // Transfer counterpart away... + assert_ok!(NisBalances::transfer(RuntimeOrigin::signed(1), 2, 250_000)); + // ...and it's not thawable. + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), TokenError::NoFunds); + + // Transfer counterpart back... + assert_ok!(NisBalances::transfer(RuntimeOrigin::signed(2), 1, 250_000)); + // ...and it is. + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + + assert_eq!(Balances::free_balance(1), 150); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn thaw_with_ignored_issuance_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + // Give account zero some balance. + Balances::make_free_balance_be(&0, 200); + + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 100, 1)); + enlarge(100, 1); + + // Account zero transfers 50 into everyone else's accounts. + assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 2, 50)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 3, 50)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 4, 50)); + + run_to_block(4); + // Unfunded initially... + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::::Unfunded); + // ...so we fund... + assert_ok!(Nis::fund_deficit(RuntimeOrigin::signed(1))); + // ...and then it's ok. + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + + // Account zero changes have been ignored. + assert_eq!(Balances::free_balance(1), 150); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn thaw_when_issuance_lower_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 100, 1)); + enlarge(100, 1); + + // Everybody else's balances goes down by 25% + Balances::make_free_balance_be(&2, 75); + Balances::make_free_balance_be(&3, 75); + Balances::make_free_balance_be(&4, 75); + + run_to_block(4); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + + assert_eq!(Balances::free_balance(1), 75); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn multiple_thaws_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 60, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 50, 1)); + enlarge(200, 3); + + // Double everyone's free balances. + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 200); + Balances::make_free_balance_be(&4, 200); + assert_ok!(Nis::fund_deficit(RuntimeOrigin::signed(1))); + + run_to_block(4); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 1, None)); + assert_noop!(Nis::thaw(RuntimeOrigin::signed(2), 2, None), Error::::Throttled); + run_to_block(5); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(2), 2, None)); + + assert_eq!(Balances::free_balance(1), 200); + assert_eq!(Balances::free_balance(2), 200); + }); +} + +#[test] +fn multiple_thaws_works_in_alternative_thaw_order() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 60, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 50, 1)); + enlarge(200, 3); + + // Double everyone's free balances. + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 200); + Balances::make_free_balance_be(&4, 200); + assert_ok!(Nis::fund_deficit(RuntimeOrigin::signed(1))); + + run_to_block(4); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(2), 2, None)); + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 1, None), Error::::Throttled); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + + run_to_block(5); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 1, None)); + + assert_eq!(Balances::free_balance(1), 200); + assert_eq!(Balances::free_balance(2), 200); + }); +} + +#[test] +fn enlargement_to_target_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let w = <() as WeightInfo>::process_queues() + + <() as WeightInfo>::process_queue() + + (<() as WeightInfo>::process_bid() * 2); + super::mock::MaxIntakeWeight::set(w); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 3)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(3), 40, 3)); + Target::set(Perquintill::from_percent(40)); + + run_to_block(3); + assert_eq!(Queues::::get(1), vec![Bid { amount: 40, who: 1 },]); + assert_eq!( + Queues::::get(2), + vec![Bid { amount: 40, who: 2 }, Bid { amount: 40, who: 1 },] + ); + assert_eq!( + Queues::::get(3), + vec![Bid { amount: 40, who: 3 }, Bid { amount: 40, who: 2 },] + ); + assert_eq!(QueueTotals::::get(), vec![(1, 40), (2, 80), (2, 80)]); + + run_to_block(4); + // Two new items should have been issued to 2 & 3 for 40 each & duration of 3. + assert_eq!( + Receipts::::get(0).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 13 } + ); + assert_eq!( + Receipts::::get(1).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 3, expiry: 13 } + ); + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(20), + index: 2, + last_period: 0, + thawed: Perquintill::zero(), + } + ); + + run_to_block(5); + // No change + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(20), + index: 2, + last_period: 0, + thawed: Perquintill::zero() + } + ); + + run_to_block(6); + // Two new items should have been issued to 1 & 2 for 40 each & duration of 2. + assert_eq!( + Receipts::::get(2).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 1, expiry: 12 } + ); + assert_eq!( + Receipts::::get(3).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 12 } + ); + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(40), + index: 4, + last_period: 0, + thawed: Perquintill::zero() + } + ); + + run_to_block(8); + // No change now. + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(40), + index: 4, + last_period: 0, + thawed: Perquintill::zero() + } + ); + + // Set target a bit higher to use up the remaining bid. + Target::set(Perquintill::from_percent(60)); + run_to_block(10); + + // Two new items should have been issued to 1 & 2 for 40 each & duration of 2. + assert_eq!( + Receipts::::get(4).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 1, expiry: 13 } + ); + + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(50), + index: 5, + last_period: 0, + thawed: Perquintill::zero() + } + ); + }); +} diff --git a/frame/nis/src/weights.rs b/frame/nis/src/weights.rs new file mode 100644 index 0000000000000..71577075ada91 --- /dev/null +++ b/frame/nis/src/weights.rs @@ -0,0 +1,201 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_nis +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_nis +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --template=./.maintain/frame-weight-template.hbs +// --output=./frame/nis/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_nis. +pub trait WeightInfo { + fn place_bid(l: u32, ) -> Weight; + fn place_bid_max() -> Weight; + fn retract_bid(l: u32, ) -> Weight; + fn thaw() -> Weight; + fn fund_deficit() -> Weight; + fn process_queues() -> Weight; + fn process_queue() -> Weight; + fn process_bid() -> Weight; +} + +/// Weights for pallet_nis using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn place_bid(l: u32, ) -> Weight { + // Minimum execution time: 42_332 nanoseconds. + Weight::from_ref_time(45_584_514 as u64) + // Standard Error: 129 + .saturating_add(Weight::from_ref_time(45_727 as u64).saturating_mul(l as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn place_bid_max() -> Weight { + // Minimum execution time: 85_866 nanoseconds. + Weight::from_ref_time(87_171_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn retract_bid(l: u32, ) -> Weight { + // Minimum execution time: 44_605 nanoseconds. + Weight::from_ref_time(46_850_108 as u64) + // Standard Error: 135 + .saturating_add(Weight::from_ref_time(34_178 as u64).saturating_mul(l as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nis Active (r:1 w:1) + // Storage: Nis ActiveTotal (r:1 w:1) + fn thaw() -> Weight { + // Minimum execution time: 55_143 nanoseconds. + Weight::from_ref_time(55_845_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nis Active (r:1 w:1) + // Storage: Nis ActiveTotal (r:1 w:1) + fn fund_deficit() -> Weight { + Weight::from_ref_time(47_753_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:0) + fn process_queues() -> Weight { + Weight::from_ref_time(1_663_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis Active (r:0 w:1) + fn process_queue() -> Weight { + Weight::from_ref_time(40_797_000 as u64) + // Standard Error: 1_000 + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis Active (r:0 w:1) + fn process_bid() -> Weight { + Weight::from_ref_time(14_944_000 as u64) + // Standard Error: 6_000 + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn place_bid(l: u32, ) -> Weight { + // Minimum execution time: 42_332 nanoseconds. + Weight::from_ref_time(45_584_514 as u64) + // Standard Error: 129 + .saturating_add(Weight::from_ref_time(45_727 as u64).saturating_mul(l as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn place_bid_max() -> Weight { + // Minimum execution time: 85_866 nanoseconds. + Weight::from_ref_time(87_171_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn retract_bid(l: u32, ) -> Weight { + // Minimum execution time: 44_605 nanoseconds. + Weight::from_ref_time(46_850_108 as u64) + // Standard Error: 135 + .saturating_add(Weight::from_ref_time(34_178 as u64).saturating_mul(l as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nis Active (r:1 w:1) + // Storage: Nis ActiveTotal (r:1 w:1) + fn thaw() -> Weight { + // Minimum execution time: 55_143 nanoseconds. + Weight::from_ref_time(55_845_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nis Active (r:1 w:1) + // Storage: Nis ActiveTotal (r:1 w:1) + fn fund_deficit() -> Weight { + Weight::from_ref_time(47_753_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:0) + fn process_queues() -> Weight { + Weight::from_ref_time(1_663_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis Active (r:0 w:1) + fn process_queue() -> Weight { + Weight::from_ref_time(40_797_000 as u64) + // Standard Error: 1_000 + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis Active (r:0 w:1) + fn process_bid() -> Weight { + Weight::from_ref_time(14_944_000 as u64) + // Standard Error: 6_000 + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } +} diff --git a/frame/node-authorization/Cargo.toml b/frame/node-authorization/Cargo.toml index 635e72e3a8b8a..fbf486644e15c 100644 --- a/frame/node-authorization/Cargo.toml +++ b/frame/node-authorization/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-node-authorization" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for node authorization" @@ -12,27 +12,27 @@ description = "FRAME pallet for node authorization" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -log = { version = "0.4.14", default-features = false } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-support/std", "frame-system/std", + "log/std", + "scale-info/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "log/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/node-authorization/src/lib.rs b/frame/node-authorization/src/lib.rs index 016f12d2eb838..543ba24500ebc 100644 --- a/frame/node-authorization/src/lib.rs +++ b/frame/node-authorization/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! # Node authorization pallet //! //! This pallet manages a configurable set of nodes for a permissioned network. -//! Each node is dentified by a PeerId (i.e. Vec). It provides two ways to +//! Each node is dentified by a PeerId (i.e. `Vec`). It provides two ways to //! authorize a node, //! //! - a set of well known nodes across different organizations in which the @@ -46,24 +46,28 @@ 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::*}; pub use weights::WeightInfo; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] pub struct Pallet(_); /// The module configuration trait #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The maximum number of well known nodes that are allowed to set #[pallet::constant] @@ -74,16 +78,16 @@ pub mod pallet { type MaxPeerIdLength: Get; /// The origin which can add a well known node. - type AddOrigin: EnsureOrigin; + type AddOrigin: EnsureOrigin; /// The origin which can remove a well known node. - type RemoveOrigin: EnsureOrigin; + type RemoveOrigin: EnsureOrigin; /// The origin which can swap the well known nodes. - type SwapOrigin: EnsureOrigin; + type SwapOrigin: EnsureOrigin; /// The origin which can reset the well known nodes. - type ResetOrigin: EnsureOrigin; + type ResetOrigin: EnsureOrigin; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -128,24 +132,24 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// The given well known node was added. - NodeAdded(PeerId, T::AccountId), + NodeAdded { peer_id: PeerId, who: T::AccountId }, /// The given well known node was removed. - NodeRemoved(PeerId), + NodeRemoved { peer_id: PeerId }, /// The given well known node was swapped; first item was removed, /// the latter was added. - NodeSwapped(PeerId, PeerId), + NodeSwapped { removed: PeerId, added: PeerId }, /// The given well known nodes were reset. - NodesReset(Vec<(PeerId, T::AccountId)>), + NodesReset { nodes: Vec<(PeerId, T::AccountId)> }, /// The given node was claimed by a user. - NodeClaimed(PeerId, T::AccountId), + NodeClaimed { peer_id: PeerId, who: T::AccountId }, /// The given claim was removed by its owner. - ClaimRemoved(PeerId, T::AccountId), + ClaimRemoved { peer_id: PeerId, who: T::AccountId }, /// The node was transferred to another account. - NodeTransferred(PeerId, T::AccountId), + NodeTransferred { peer_id: PeerId, target: T::AccountId }, /// The allowed connections were added to a node. - ConnectionsAdded(PeerId, Vec), + ConnectionsAdded { peer_id: PeerId, allowed_connections: Vec }, /// The allowed connections were removed from a node. - ConnectionsRemoved(PeerId, Vec), + ConnectionsRemoved { peer_id: PeerId, allowed_connections: Vec }, } #[pallet::error] @@ -206,13 +210,15 @@ pub mod pallet { /// May only be called from `T::AddOrigin`. /// /// - `node`: identifier of the node. + #[pallet::call_index(0)] #[pallet::weight((T::WeightInfo::add_well_known_node(), DispatchClass::Operational))] pub fn add_well_known_node( origin: OriginFor, node: PeerId, - owner: T::AccountId, + owner: AccountIdLookupOf, ) -> DispatchResult { T::AddOrigin::ensure_origin(origin)?; + let owner = T::Lookup::lookup(owner)?; ensure!(node.0.len() < T::MaxPeerIdLength::get() as usize, Error::::PeerIdTooLong); let mut nodes = WellKnownNodes::::get(); @@ -224,7 +230,7 @@ pub mod pallet { WellKnownNodes::::put(&nodes); >::insert(&node, &owner); - Self::deposit_event(Event::NodeAdded(node, owner)); + Self::deposit_event(Event::NodeAdded { peer_id: node, who: owner }); Ok(()) } @@ -234,6 +240,7 @@ pub mod pallet { /// May only be called from `T::RemoveOrigin`. /// /// - `node`: identifier of the node. + #[pallet::call_index(1)] #[pallet::weight((T::WeightInfo::remove_well_known_node(), DispatchClass::Operational))] pub fn remove_well_known_node(origin: OriginFor, node: PeerId) -> DispatchResult { T::RemoveOrigin::ensure_origin(origin)?; @@ -248,7 +255,7 @@ pub mod pallet { >::remove(&node); AdditionalConnections::::remove(&node); - Self::deposit_event(Event::NodeRemoved(node)); + Self::deposit_event(Event::NodeRemoved { peer_id: node }); Ok(()) } @@ -259,6 +266,7 @@ pub mod pallet { /// /// - `remove`: the node which will be moved out from the list. /// - `add`: the node which will be put in the list. + #[pallet::call_index(2)] #[pallet::weight((T::WeightInfo::swap_well_known_node(), DispatchClass::Operational))] pub fn swap_well_known_node( origin: OriginFor, @@ -284,7 +292,7 @@ pub mod pallet { Owners::::swap(&remove, &add); AdditionalConnections::::swap(&remove, &add); - Self::deposit_event(Event::NodeSwapped(remove, add)); + Self::deposit_event(Event::NodeSwapped { removed: remove, added: add }); Ok(()) } @@ -295,6 +303,7 @@ pub mod pallet { /// May only be called from `T::ResetOrigin`. /// /// - `nodes`: the new nodes for the allow list. + #[pallet::call_index(3)] #[pallet::weight((T::WeightInfo::reset_well_known_nodes(), DispatchClass::Operational))] pub fn reset_well_known_nodes( origin: OriginFor, @@ -305,7 +314,7 @@ pub mod pallet { Self::initialize_nodes(&nodes); - Self::deposit_event(Event::NodesReset(nodes)); + Self::deposit_event(Event::NodesReset { nodes }); Ok(()) } @@ -313,6 +322,7 @@ pub mod pallet { /// PeerId, so claim it right away! /// /// - `node`: identifier of the node. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::claim_node())] pub fn claim_node(origin: OriginFor, node: PeerId) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -321,7 +331,7 @@ pub mod pallet { ensure!(!Owners::::contains_key(&node), Error::::AlreadyClaimed); Owners::::insert(&node, &sender); - Self::deposit_event(Event::NodeClaimed(node, sender)); + Self::deposit_event(Event::NodeClaimed { peer_id: node, who: sender }); Ok(()) } @@ -330,6 +340,7 @@ pub mod pallet { /// needs to reach consensus among the network participants. /// /// - `node`: identifier of the node. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::remove_claim())] pub fn remove_claim(origin: OriginFor, node: PeerId) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -342,7 +353,7 @@ pub mod pallet { Owners::::remove(&node); AdditionalConnections::::remove(&node); - Self::deposit_event(Event::ClaimRemoved(node, sender)); + Self::deposit_event(Event::ClaimRemoved { peer_id: node, who: sender }); Ok(()) } @@ -350,13 +361,15 @@ pub mod pallet { /// /// - `node`: identifier of the node. /// - `owner`: new owner of the node. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::transfer_node())] pub fn transfer_node( origin: OriginFor, node: PeerId, - owner: T::AccountId, + owner: AccountIdLookupOf, ) -> DispatchResult { let sender = ensure_signed(origin)?; + let owner = T::Lookup::lookup(owner)?; ensure!(node.0.len() < T::MaxPeerIdLength::get() as usize, Error::::PeerIdTooLong); let pre_owner = Owners::::get(&node).ok_or(Error::::NotClaimed)?; @@ -364,7 +377,7 @@ pub mod pallet { Owners::::insert(&node, &owner); - Self::deposit_event(Event::NodeTransferred(node, owner)); + Self::deposit_event(Event::NodeTransferred { peer_id: node, target: owner }); Ok(()) } @@ -372,6 +385,7 @@ pub mod pallet { /// /// - `node`: identifier of the node. /// - `connections`: additonal nodes from which the connections are allowed. + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::add_connections())] pub fn add_connections( origin: OriginFor, @@ -395,7 +409,10 @@ pub mod pallet { AdditionalConnections::::insert(&node, nodes); - Self::deposit_event(Event::ConnectionsAdded(node, connections)); + Self::deposit_event(Event::ConnectionsAdded { + peer_id: node, + allowed_connections: connections, + }); Ok(()) } @@ -403,6 +420,7 @@ pub mod pallet { /// /// - `node`: identifier of the node. /// - `connections`: additonal nodes from which the connections are not allowed anymore. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::remove_connections())] pub fn remove_connections( origin: OriginFor, @@ -423,7 +441,10 @@ pub mod pallet { AdditionalConnections::::insert(&node, nodes); - Self::deposit_event(Event::ConnectionsRemoved(node, connections)); + Self::deposit_event(Event::ConnectionsRemoved { + peer_id: node, + allowed_connections: connections, + }); Ok(()) } } diff --git a/frame/node-authorization/src/mock.rs b/frame/node-authorization/src/mock.rs index 6c79f601c197d..fcf7ff0189332 100644 --- a/frame/node-authorization/src/mock.rs +++ b/frame/node-authorization/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,10 @@ use super::*; use crate as pallet_node_authorization; -use frame_support::{ord_parameter_types, parameter_types, traits::GenesisBuild}; +use frame_support::{ + ord_parameter_types, + traits::{ConstU32, ConstU64, GenesisBuild}, +}; use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ @@ -44,25 +47,22 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub const BlockHashCount: u64 = 250; -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type DbWeight = (); type BlockWeights = (); type BlockLength = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -71,6 +71,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } ord_parameter_types! { @@ -79,14 +80,11 @@ ord_parameter_types! { pub const Three: u64 = 3; pub const Four: u64 = 4; } -parameter_types! { - pub const MaxWellKnownNodes: u32 = 4; - pub const MaxPeerIdLength: u32 = 2; -} + impl Config for Test { - type Event = Event; - type MaxWellKnownNodes = MaxWellKnownNodes; - type MaxPeerIdLength = MaxPeerIdLength; + type RuntimeEvent = RuntimeEvent; + type MaxWellKnownNodes = ConstU32<4>; + type MaxPeerIdLength = ConstU32<2>; type AddOrigin = EnsureSignedBy; type RemoveOrigin = EnsureSignedBy; type SwapOrigin = EnsureSignedBy; diff --git a/frame/node-authorization/src/tests.rs b/frame/node-authorization/src/tests.rs index 530904fa73488..d9db09db4f46f 100644 --- a/frame/node-authorization/src/tests.rs +++ b/frame/node-authorization/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,19 +26,27 @@ use sp_runtime::traits::BadOrigin; fn add_well_known_node_works() { new_test_ext().execute_with(|| { assert_noop!( - NodeAuthorization::add_well_known_node(Origin::signed(2), test_node(15), 15), + NodeAuthorization::add_well_known_node(RuntimeOrigin::signed(2), test_node(15), 15), BadOrigin ); assert_noop!( - NodeAuthorization::add_well_known_node(Origin::signed(1), PeerId(vec![1, 2, 3]), 15), + NodeAuthorization::add_well_known_node( + RuntimeOrigin::signed(1), + PeerId(vec![1, 2, 3]), + 15 + ), Error::::PeerIdTooLong ); assert_noop!( - NodeAuthorization::add_well_known_node(Origin::signed(1), test_node(20), 20), + NodeAuthorization::add_well_known_node(RuntimeOrigin::signed(1), test_node(20), 20), Error::::AlreadyJoined ); - assert_ok!(NodeAuthorization::add_well_known_node(Origin::signed(1), test_node(15), 15)); + assert_ok!(NodeAuthorization::add_well_known_node( + RuntimeOrigin::signed(1), + test_node(15), + 15 + )); assert_eq!( WellKnownNodes::::get(), BTreeSet::from_iter(vec![test_node(10), test_node(15), test_node(20), test_node(30)]) @@ -49,7 +57,7 @@ fn add_well_known_node_works() { assert_eq!(Owners::::get(test_node(15)), Some(15)); assert_noop!( - NodeAuthorization::add_well_known_node(Origin::signed(1), test_node(25), 25), + NodeAuthorization::add_well_known_node(RuntimeOrigin::signed(1), test_node(25), 25), Error::::TooManyNodes ); }); @@ -59,15 +67,18 @@ fn add_well_known_node_works() { fn remove_well_known_node_works() { new_test_ext().execute_with(|| { assert_noop!( - NodeAuthorization::remove_well_known_node(Origin::signed(3), test_node(20)), + NodeAuthorization::remove_well_known_node(RuntimeOrigin::signed(3), test_node(20)), BadOrigin ); assert_noop!( - NodeAuthorization::remove_well_known_node(Origin::signed(2), PeerId(vec![1, 2, 3])), + NodeAuthorization::remove_well_known_node( + RuntimeOrigin::signed(2), + PeerId(vec![1, 2, 3]) + ), Error::::PeerIdTooLong ); assert_noop!( - NodeAuthorization::remove_well_known_node(Origin::signed(2), test_node(40)), + NodeAuthorization::remove_well_known_node(RuntimeOrigin::signed(2), test_node(40)), Error::::NotExist ); @@ -77,7 +88,10 @@ fn remove_well_known_node_works() { ); assert!(AdditionalConnections::::contains_key(test_node(20))); - assert_ok!(NodeAuthorization::remove_well_known_node(Origin::signed(2), test_node(20))); + assert_ok!(NodeAuthorization::remove_well_known_node( + RuntimeOrigin::signed(2), + test_node(20) + )); assert_eq!( WellKnownNodes::::get(), BTreeSet::from_iter(vec![test_node(10), test_node(30)]) @@ -91,12 +105,16 @@ fn remove_well_known_node_works() { fn swap_well_known_node_works() { new_test_ext().execute_with(|| { assert_noop!( - NodeAuthorization::swap_well_known_node(Origin::signed(4), test_node(20), test_node(5)), + NodeAuthorization::swap_well_known_node( + RuntimeOrigin::signed(4), + test_node(20), + test_node(5) + ), BadOrigin ); assert_noop!( NodeAuthorization::swap_well_known_node( - Origin::signed(3), + RuntimeOrigin::signed(3), PeerId(vec![1, 2, 3]), test_node(20) ), @@ -104,7 +122,7 @@ fn swap_well_known_node_works() { ); assert_noop!( NodeAuthorization::swap_well_known_node( - Origin::signed(3), + RuntimeOrigin::signed(3), test_node(20), PeerId(vec![1, 2, 3]) ), @@ -112,7 +130,7 @@ fn swap_well_known_node_works() { ); assert_ok!(NodeAuthorization::swap_well_known_node( - Origin::signed(3), + RuntimeOrigin::signed(3), test_node(20), test_node(20) )); @@ -122,12 +140,16 @@ fn swap_well_known_node_works() { ); assert_noop!( - NodeAuthorization::swap_well_known_node(Origin::signed(3), test_node(15), test_node(5)), + NodeAuthorization::swap_well_known_node( + RuntimeOrigin::signed(3), + test_node(15), + test_node(5) + ), Error::::NotExist ); assert_noop!( NodeAuthorization::swap_well_known_node( - Origin::signed(3), + RuntimeOrigin::signed(3), test_node(20), test_node(30) ), @@ -139,7 +161,7 @@ fn swap_well_known_node_works() { BTreeSet::from_iter(vec![test_node(15)]), ); assert_ok!(NodeAuthorization::swap_well_known_node( - Origin::signed(3), + RuntimeOrigin::signed(3), test_node(20), test_node(5) )); @@ -162,14 +184,14 @@ fn reset_well_known_nodes_works() { new_test_ext().execute_with(|| { assert_noop!( NodeAuthorization::reset_well_known_nodes( - Origin::signed(3), + RuntimeOrigin::signed(3), vec![(test_node(15), 15), (test_node(5), 5), (test_node(20), 20)] ), BadOrigin ); assert_noop!( NodeAuthorization::reset_well_known_nodes( - Origin::signed(4), + RuntimeOrigin::signed(4), vec![ (test_node(15), 15), (test_node(5), 5), @@ -181,7 +203,7 @@ fn reset_well_known_nodes_works() { ); assert_ok!(NodeAuthorization::reset_well_known_nodes( - Origin::signed(4), + RuntimeOrigin::signed(4), vec![(test_node(15), 15), (test_node(5), 5), (test_node(20), 20)] )); assert_eq!( @@ -198,15 +220,15 @@ fn reset_well_known_nodes_works() { fn claim_node_works() { new_test_ext().execute_with(|| { assert_noop!( - NodeAuthorization::claim_node(Origin::signed(1), PeerId(vec![1, 2, 3])), + NodeAuthorization::claim_node(RuntimeOrigin::signed(1), PeerId(vec![1, 2, 3])), Error::::PeerIdTooLong ); assert_noop!( - NodeAuthorization::claim_node(Origin::signed(1), test_node(20)), + NodeAuthorization::claim_node(RuntimeOrigin::signed(1), test_node(20)), Error::::AlreadyClaimed ); - assert_ok!(NodeAuthorization::claim_node(Origin::signed(15), test_node(15))); + assert_ok!(NodeAuthorization::claim_node(RuntimeOrigin::signed(15), test_node(15))); assert_eq!(Owners::::get(test_node(15)), Some(15)); }); } @@ -215,21 +237,21 @@ fn claim_node_works() { fn remove_claim_works() { new_test_ext().execute_with(|| { assert_noop!( - NodeAuthorization::remove_claim(Origin::signed(15), PeerId(vec![1, 2, 3])), + NodeAuthorization::remove_claim(RuntimeOrigin::signed(15), PeerId(vec![1, 2, 3])), Error::::PeerIdTooLong ); assert_noop!( - NodeAuthorization::remove_claim(Origin::signed(15), test_node(15)), + NodeAuthorization::remove_claim(RuntimeOrigin::signed(15), test_node(15)), Error::::NotClaimed ); assert_noop!( - NodeAuthorization::remove_claim(Origin::signed(15), test_node(20)), + NodeAuthorization::remove_claim(RuntimeOrigin::signed(15), test_node(20)), Error::::NotOwner ); assert_noop!( - NodeAuthorization::remove_claim(Origin::signed(20), test_node(20)), + NodeAuthorization::remove_claim(RuntimeOrigin::signed(20), test_node(20)), Error::::PermissionDenied ); @@ -238,7 +260,7 @@ fn remove_claim_works() { test_node(15), BTreeSet::from_iter(vec![test_node(20)]), ); - assert_ok!(NodeAuthorization::remove_claim(Origin::signed(15), test_node(15))); + assert_ok!(NodeAuthorization::remove_claim(RuntimeOrigin::signed(15), test_node(15))); assert!(!Owners::::contains_key(test_node(15))); assert!(!AdditionalConnections::::contains_key(test_node(15))); }); @@ -248,20 +270,20 @@ fn remove_claim_works() { fn transfer_node_works() { new_test_ext().execute_with(|| { assert_noop!( - NodeAuthorization::transfer_node(Origin::signed(15), PeerId(vec![1, 2, 3]), 10), + NodeAuthorization::transfer_node(RuntimeOrigin::signed(15), PeerId(vec![1, 2, 3]), 10), Error::::PeerIdTooLong ); assert_noop!( - NodeAuthorization::transfer_node(Origin::signed(15), test_node(15), 10), + NodeAuthorization::transfer_node(RuntimeOrigin::signed(15), test_node(15), 10), Error::::NotClaimed ); assert_noop!( - NodeAuthorization::transfer_node(Origin::signed(15), test_node(20), 10), + NodeAuthorization::transfer_node(RuntimeOrigin::signed(15), test_node(20), 10), Error::::NotOwner ); - assert_ok!(NodeAuthorization::transfer_node(Origin::signed(20), test_node(20), 15)); + assert_ok!(NodeAuthorization::transfer_node(RuntimeOrigin::signed(20), test_node(20), 15)); assert_eq!(Owners::::get(test_node(20)), Some(15)); }); } @@ -271,7 +293,7 @@ fn add_connections_works() { new_test_ext().execute_with(|| { assert_noop!( NodeAuthorization::add_connections( - Origin::signed(15), + RuntimeOrigin::signed(15), PeerId(vec![1, 2, 3]), vec![test_node(5)] ), @@ -279,7 +301,7 @@ fn add_connections_works() { ); assert_noop!( NodeAuthorization::add_connections( - Origin::signed(15), + RuntimeOrigin::signed(15), test_node(15), vec![test_node(5)] ), @@ -288,7 +310,7 @@ fn add_connections_works() { assert_noop!( NodeAuthorization::add_connections( - Origin::signed(15), + RuntimeOrigin::signed(15), test_node(20), vec![test_node(5)] ), @@ -296,7 +318,7 @@ fn add_connections_works() { ); assert_ok!(NodeAuthorization::add_connections( - Origin::signed(20), + RuntimeOrigin::signed(20), test_node(20), vec![test_node(15), test_node(5), test_node(25), test_node(20)] )); @@ -312,7 +334,7 @@ fn remove_connections_works() { new_test_ext().execute_with(|| { assert_noop!( NodeAuthorization::remove_connections( - Origin::signed(15), + RuntimeOrigin::signed(15), PeerId(vec![1, 2, 3]), vec![test_node(5)] ), @@ -320,7 +342,7 @@ fn remove_connections_works() { ); assert_noop!( NodeAuthorization::remove_connections( - Origin::signed(15), + RuntimeOrigin::signed(15), test_node(15), vec![test_node(5)] ), @@ -329,7 +351,7 @@ fn remove_connections_works() { assert_noop!( NodeAuthorization::remove_connections( - Origin::signed(15), + RuntimeOrigin::signed(15), test_node(20), vec![test_node(5)] ), @@ -341,7 +363,7 @@ fn remove_connections_works() { BTreeSet::from_iter(vec![test_node(5), test_node(15), test_node(25)]), ); assert_ok!(NodeAuthorization::remove_connections( - Origin::signed(20), + RuntimeOrigin::signed(20), test_node(20), vec![test_node(15), test_node(5)] )); diff --git a/frame/node-authorization/src/weights.rs b/frame/node-authorization/src/weights.rs index dbb7956cff967..63728c21b65c4 100644 --- a/frame/node-authorization/src/weights.rs +++ b/frame/node-authorization/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,13 +37,13 @@ pub trait WeightInfo { } impl WeightInfo for () { - fn add_well_known_node() -> Weight { 50_000_000 } - fn remove_well_known_node() -> Weight { 50_000_000 } - fn swap_well_known_node() -> Weight { 50_000_000 } - fn reset_well_known_nodes() -> Weight { 50_000_000 } - fn claim_node() -> Weight { 50_000_000 } - fn remove_claim() -> Weight { 50_000_000 } - fn transfer_node() -> Weight { 50_000_000 } - fn add_connections() -> Weight { 50_000_000 } - fn remove_connections() -> Weight { 50_000_000 } + fn add_well_known_node() -> Weight { Weight::from_ref_time(50_000_000) } + fn remove_well_known_node() -> Weight { Weight::from_ref_time(50_000_000) } + fn swap_well_known_node() -> Weight { Weight::from_ref_time(50_000_000) } + fn reset_well_known_nodes() -> Weight { Weight::from_ref_time(50_000_000) } + fn claim_node() -> Weight { Weight::from_ref_time(50_000_000) } + fn remove_claim() -> Weight { Weight::from_ref_time(50_000_000) } + fn transfer_node() -> Weight { Weight::from_ref_time(50_000_000) } + fn add_connections() -> Weight { Weight::from_ref_time(50_000_000) } + fn remove_connections() -> Weight { Weight::from_ref_time(50_000_000) } } diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml new file mode 100644 index 0000000000000..4894e3d97f19a --- /dev/null +++ b/frame/nomination-pools/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "pallet-nomination-pools" +version = "1.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME nomination pools pallet" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# parity +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# FRAME +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +log = { version = "0.4.0", default-features = false } + +# Optional: usef for testing and/or fuzzing +pallet-balances = { version = "4.0.0-dev", path = "../balances", optional = true } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing", optional = true } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } + +[features] +default = ["std"] +fuzzing = ["pallet-balances", "sp-tracing"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "sp-staking/std", + "sp-core/std", + "log/std", +] +runtime-benchmarks = [ + "sp-staking/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime" +] diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml new file mode 100644 index 0000000000000..be52d9777ac86 --- /dev/null +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "pallet-nomination-pools-benchmarking" +version = "1.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME nomination pools pallet benchmarking" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# parity +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# FRAME +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +pallet-bags-list = { version = "4.0.0-dev", default-features = false, path = "../../bags-list" } +pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../staking" } +pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../" } + +# Substrate Primitives +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime-interface" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-io = { optional = true, default-features = false, path = "../../../primitives/io" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } +pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } + +[features] +default = ["std"] + +std = [ + "frame-benchmarking/std", + "frame-election-provider-support/std", + "frame-support/std", + "frame-system/std", + "pallet-bags-list/std", + "pallet-staking/std", + "pallet-nomination-pools/std", + "sp-runtime/std", + "sp-runtime-interface/std", + "sp-io/std", + "sp-staking/std", + "sp-std/std", +] + +runtime-benchmarks = [ + "frame-election-provider-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "sp-staking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "pallet-staking/runtime-benchmarks", + "pallet-nomination-pools/runtime-benchmarks", + "pallet-bags-list/runtime-benchmarks", +] diff --git a/frame/nomination-pools/benchmarking/README.md b/frame/nomination-pools/benchmarking/README.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs new file mode 100644 index 0000000000000..9b063539152b7 --- /dev/null +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -0,0 +1,660 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +#![cfg(feature = "runtime-benchmarks")] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account, Vec}; +use frame_election_provider_support::SortedListProvider; +use frame_support::{assert_ok, ensure, traits::Get}; +use frame_system::RawOrigin as RuntimeOrigin; +use pallet_nomination_pools::{ + BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, MaxPoolMembers, + MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, + PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage, +}; +use sp_runtime::traits::{Bounded, StaticLookup, Zero}; +use sp_staking::{EraIndex, StakingInterface}; +// `frame_benchmarking::benchmarks!` macro needs this +use pallet_nomination_pools::Call; + +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::make_free_balance_be(&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, +) -> (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(); + + 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. + let i = CurrencyOf::::burn(CurrencyOf::::total_issuance()); + sp_std::mem::forget(i); + + // Create accounts with the origin weight + let (pool_creator1, pool_origin1) = create_pool_account::(USER_SEED + 1, origin_weight); + 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); + 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); + 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::::make_free_balance_be(&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::::free_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_reward { + 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()); + + // transfer exactly `extra` to the depositor of the src pool (1), + let reward_account1 = Pools::::create_reward_account(1); + assert!(extra >= CurrencyOf::::minimum_balance()); + CurrencyOf::::deposit_creating(&reward_account1, extra); + + }: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::Rewards) + verify { + assert!( + T::Staking::active_stake(&scenario.origin1).unwrap() >= + scenario.dest_weight + ); + } + + claim_payout { + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); + let ed = CurrencyOf::::minimum_balance(); + let (depositor, pool_account) = create_pool_account::(0, origin_weight); + let reward_account = Pools::::create_reward_account(1); + + // Send funds to the reward account of the pool + CurrencyOf::::make_free_balance_be(&reward_account, ed + origin_weight); + + // Sanity check + assert_eq!( + CurrencyOf::::free_balance(&depositor), + origin_weight + ); + + whitelist_account!(depositor); + }:_(RuntimeOrigin::Signed(depositor.clone())) + verify { + assert_eq!( + CurrencyOf::::free_balance(&depositor), + origin_weight * 2u32.into() + ); + assert_eq!( + CurrencyOf::::free_balance(&reward_account), + ed + Zero::zero() + ); + } + + 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); + + // 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::::free_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::::free_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); + + // 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::::free_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::::free_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); + 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::::free_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::::free_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::::make_free_balance_be(&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 { + points: min_create_bond, + state: PoolState::Open, + member_counter: 1, + roles: PoolRoles { + depositor: depositor.clone(), + root: Some(depositor.clone()), + nominator: Some(depositor.clone()), + state_toggler: Some(depositor.clone()), + }, + } + ); + assert_eq!( + T::Staking::active_stake(&Pools::::create_bonded_account(1)), + Ok(min_create_bond) + ); + } + + nominate { + let n in 1 .. T::MaxNominations::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); + + // 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 { + points: min_create_bond, + state: PoolState::Open, + member_counter: 1, + roles: PoolRoles { + depositor: depositor.clone(), + root: Some(depositor.clone()), + nominator: Some(depositor.clone()), + state_toggler: Some(depositor.clone()), + } + } + ); + 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); + BondedPools::::mutate(&1, |maybe_pool| { + // Force the pool into an invalid state + maybe_pool.as_mut().map(|mut 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()); + + // 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) + ) 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)); + } + + update_roles { + let first_id = pallet_nomination_pools::LastPoolId::::get() + 1; + let (root, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into()); + 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()), + state_toggler: Some(random.clone()), + root: Some(random), + }, + ) + } + + chill { + // Create a pool + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into()); + + // Nominate with the pool. + let validators: Vec<_> = (0..T::MaxNominations::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()); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::Runtime + ); +} diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs new file mode 100644 index 0000000000000..6959aa9783ee5 --- /dev/null +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -0,0 +1,201 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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::VoterBagsListInstance; +use frame_election_provider_support::VoteWeight; +use frame_support::{pallet_prelude::*, parameter_types, traits::ConstU64, PalletId}; +use sp_runtime::{ + traits::{Convert, IdentityLookup}, + FixedU128, +}; + +type AccountId = u128; +type AccountIndex = u32; +type BlockNumber = u64; +type Balance = u128; + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = AccountIndex; + type BlockNumber = BlockNumber; + type RuntimeCall = RuntimeCall; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = sp_runtime::testing::Header; + type RuntimeEvent = RuntimeEvent; + type 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>; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 10; +} +impl pallet_balances::Config for Runtime { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +pallet_staking_reward_curve::build! { + const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} +parameter_types! { + pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; +} +impl pallet_staking::Config for Runtime { + type MaxNominations = ConstU32<16>; + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type RewardRemainder = (); + type RuntimeEvent = RuntimeEvent; + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = ConstU32<3>; + type SessionInterface = (); + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = (); + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = VoterList; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type OnStakerSlash = Pools; + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); +} + +parameter_types! { + pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; +} + +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type BagThresholds = BagThresholds; + type ScoreProvider = Staking; + type Score = VoteWeight; +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub static PostUnbondingPoolsWindow: u32 = 10; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); + pub const MaxPointsToBalance: u8 = 10; +} + +impl pallet_nomination_pools::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type RewardCounter = FixedU128; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type Staking = Staking; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; + type PalletId = PoolsPalletId; + type MaxPointsToBalance = MaxPointsToBalance; +} + +impl crate::Config for Runtime {} + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, + VoterList: pallet_bags_list::::{Pallet, Call, Storage, Event}, + Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let _ = pallet_nomination_pools::GenesisConfig:: { + min_join_bond: 2, + min_create_bond: 2, + max_pools: Some(3), + max_members_per_pool: Some(3), + max_members: Some(3 * 3), + } + .assimilate_storage(&mut storage); + sp_io::TestExternalities::from(storage) +} diff --git a/frame/nomination-pools/fuzzer/Cargo.toml b/frame/nomination-pools/fuzzer/Cargo.toml new file mode 100644 index 0000000000000..7dde8733e3f60 --- /dev/null +++ b/frame/nomination-pools/fuzzer/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "pallet-nomination-pools-fuzzer" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Fuzzer for fixed point arithmetic primitives." +documentation = "https://docs.rs/sp-arithmetic-fuzzer" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +honggfuzz = "0.5.54" + +pallet-nomination-pools = { path = "..", features = ["fuzzing"] } + +frame-system = { path = "../../system" } +frame-support = { path = "../../support" } + +sp-runtime = { path = "../../../primitives/runtime" } +sp-io = { path = "../../../primitives/io" } +sp-tracing = { path = "../../../primitives/tracing" } + +rand = { version = "0.8.5", features = ["small_rng"] } +log = "0.4.17" + +[[bin]] +name = "call" +path = "src/call.rs" diff --git a/frame/nomination-pools/fuzzer/src/call.rs b/frame/nomination-pools/fuzzer/src/call.rs new file mode 100644 index 0000000000000..b07903609e8ab --- /dev/null +++ b/frame/nomination-pools/fuzzer/src/call.rs @@ -0,0 +1,353 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! # Running +//! Running this fuzzer can be done with `cargo hfuzz run call`. `honggfuzz` CLI +//! options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads. +//! +//! # Debugging a panic +//! Once a panic is found, it can be debugged with +//! `cargo hfuzz run-debug per_thing_rational hfuzz_workspace/call/*.fuzz`. + +use frame_support::{ + assert_ok, + traits::{Currency, GetCallName, UnfilteredDispatchable}, +}; +use honggfuzz::fuzz; +use pallet_nomination_pools::{ + log, + mock::*, + pallet as pools, + pallet::{BondedPools, Call as PoolsCall, Event as PoolsEvents, PoolMembers}, + BondExtra, BondedPool, LastPoolId, MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, + MinCreateBond, MinJoinBond, PoolId, +}; +use rand::{seq::SliceRandom, Rng}; +use sp_runtime::{assert_eq_error_rate, Perquintill}; + +const ERA: BlockNumber = 1000; +const MAX_ED_MULTIPLE: Balance = 10_000; +const MIN_ED_MULTIPLE: Balance = 10; + +// not quite elegant, just to make it available in random_signed_origin. +const REWARD_AGENT_ACCOUNT: AccountId = 42; + +/// Grab random accounts, either known ones, or new ones. +fn random_signed_origin(rng: &mut R) -> (RuntimeOrigin, AccountId) { + let count = PoolMembers::::count(); + if rng.gen::() && count > 0 { + // take an existing account. + let skip = rng.gen_range(0..count as usize); + + // this is tricky: the account might be our reward agent, which we never want to be + // randomly chosen here. Try another one, or, if it is only our agent, return a random + // one nonetheless. + let candidate = PoolMembers::::iter_keys().skip(skip).take(1).next().unwrap(); + let acc = + if candidate == REWARD_AGENT_ACCOUNT { rng.gen::() } else { candidate }; + + (RuntimeOrigin::signed(acc), acc) + } else { + // create a new account + let acc = rng.gen::(); + (RuntimeOrigin::signed(acc), acc) + } +} + +fn random_ed_multiple(rng: &mut R) -> Balance { + let multiple = rng.gen_range(MIN_ED_MULTIPLE..MAX_ED_MULTIPLE); + ExistentialDeposit::get() * multiple +} + +fn fund_account(rng: &mut R, account: &AccountId) { + let target_amount = random_ed_multiple(rng); + if let Some(top_up) = target_amount.checked_sub(Balances::free_balance(account)) { + let _ = Balances::deposit_creating(account, top_up); + } + assert!(Balances::free_balance(account) >= target_amount); +} + +fn random_existing_pool(mut rng: &mut R) -> Option { + BondedPools::::iter_keys().collect::>().choose(&mut rng).map(|x| *x) +} + +fn random_call(mut rng: &mut R) -> (pools::Call, RuntimeOrigin) { + let op = rng.gen::(); + let mut op_count = as GetCallName>::get_call_names().len(); + // Exclude set_state, set_metadata, set_configs, update_roles and chill. + op_count -= 5; + + match op % op_count { + 0 => { + // join + let pool_id = random_existing_pool(&mut rng).unwrap_or_default(); + let (origin, who) = random_signed_origin(&mut rng); + fund_account(&mut rng, &who); + let amount = random_ed_multiple(&mut rng); + (PoolsCall::::join { amount, pool_id }, origin) + }, + 1 => { + // bond_extra + let (origin, who) = random_signed_origin(&mut rng); + let extra = if rng.gen::() { + BondExtra::Rewards + } else { + fund_account(&mut rng, &who); + let amount = random_ed_multiple(&mut rng); + BondExtra::FreeBalance(amount) + }; + (PoolsCall::::bond_extra { extra }, origin) + }, + 2 => { + // claim_payout + let (origin, _) = random_signed_origin(&mut rng); + (PoolsCall::::claim_payout {}, origin) + }, + 3 => { + // unbond + let (origin, who) = random_signed_origin(&mut rng); + let amount = random_ed_multiple(&mut rng); + (PoolsCall::::unbond { member_account: who, unbonding_points: amount }, origin) + }, + 4 => { + // pool_withdraw_unbonded + let pool_id = random_existing_pool(&mut rng).unwrap_or_default(); + let (origin, _) = random_signed_origin(&mut rng); + (PoolsCall::::pool_withdraw_unbonded { pool_id, num_slashing_spans: 0 }, origin) + }, + 5 => { + // withdraw_unbonded + let (origin, who) = random_signed_origin(&mut rng); + ( + PoolsCall::::withdraw_unbonded { member_account: who, num_slashing_spans: 0 }, + origin, + ) + }, + 6 => { + // create + let (origin, who) = random_signed_origin(&mut rng); + let amount = random_ed_multiple(&mut rng); + fund_account(&mut rng, &who); + let root = who; + let state_toggler = who; + let nominator = who; + (PoolsCall::::create { amount, root, state_toggler, nominator }, origin) + }, + 7 => { + // nominate + let (origin, _) = random_signed_origin(&mut rng); + let pool_id = random_existing_pool(&mut rng).unwrap_or_default(); + let validators = Default::default(); + (PoolsCall::::nominate { pool_id, validators }, origin) + }, + _ => unreachable!(), + } +} + +#[derive(Default)] +struct RewardAgent { + who: AccountId, + pool_id: Option, + expected_reward: Balance, +} + +// TODO: inject some slashes into the game. +impl RewardAgent { + fn new(who: AccountId) -> Self { + Self { who, ..Default::default() } + } + + fn join(&mut self) { + if self.pool_id.is_some() { + return + } + let pool_id = LastPoolId::::get(); + let amount = 10 * ExistentialDeposit::get(); + let origin = RuntimeOrigin::signed(self.who); + let _ = Balances::deposit_creating(&self.who, 10 * amount); + self.pool_id = Some(pool_id); + log::info!(target: "reward-agent", "🤖 reward agent joining in {} with {}", pool_id, amount); + assert_ok!(PoolsCall::join:: { amount, pool_id }.dispatch_bypass_filter(origin)); + } + + fn claim_payout(&mut self) { + // 10 era later, we claim our payout. We expect our income to be roughly what we + // calculated. + if !PoolMembers::::contains_key(&self.who) { + log!(warn, "reward agent is not in the pool yet, cannot claim"); + return + } + let pre = Balances::free_balance(&42); + let origin = RuntimeOrigin::signed(42); + assert_ok!(PoolsCall::::claim_payout {}.dispatch_bypass_filter(origin)); + let post = Balances::free_balance(&42); + + let income = post - pre; + log::info!( + target: "reward-agent", "🤖 CLAIM: actual: {}, expected: {}", + income, + self.expected_reward, + ); + assert_eq_error_rate!(income, self.expected_reward, 10); + self.expected_reward = 0; + } +} + +fn main() { + let mut reward_agent = RewardAgent::new(REWARD_AGENT_ACCOUNT); + sp_tracing::try_init_simple(); + let mut ext = sp_io::TestExternalities::new_empty(); + let mut events_histogram = Vec::<(PoolsEvents, u32)>::default(); + let mut iteration = 0 as BlockNumber; + let mut ok = 0; + let mut err = 0; + + let dot: Balance = (10 as Balance).pow(10); + ExistentialDeposit::set(dot); + BondingDuration::set(8); + + ext.execute_with(|| { + MaxPoolMembers::::set(Some(10_000)); + MaxPoolMembersPerPool::::set(Some(1000)); + MaxPools::::set(Some(1_000)); + + MinCreateBond::::set(10 * ExistentialDeposit::get()); + MinJoinBond::::set(5 * ExistentialDeposit::get()); + System::set_block_number(1); + }); + + loop { + fuzz!(|seed: [u8; 32]| { + use ::rand::{rngs::SmallRng, SeedableRng}; + let mut rng = SmallRng::from_seed(seed); + + ext.execute_with(|| { + let (call, origin) = random_call(&mut rng); + let outcome = call.clone().dispatch_bypass_filter(origin.clone()); + iteration += 1; + match outcome { + Ok(_) => ok += 1, + Err(_) => err += 1, + }; + + log!( + trace, + "iteration {}, call {:?}, origin {:?}, outcome: {:?}, so far {} ok {} err", + iteration, + call, + origin, + outcome, + ok, + err, + ); + + // possibly join the reward_agent + if iteration > ERA / 2 && BondedPools::::count() > 0 { + reward_agent.join(); + } + // and possibly roughly every 4 era, trigger payout for the agent. Doing this more + // frequent is also harmless. + if rng.gen_range(0..(4 * ERA)) == 0 { + reward_agent.claim_payout(); + } + + // execute sanity checks at a fixed interval, possibly on every block. + if iteration % + (std::env::var("SANITY_CHECK_INTERVAL") + .ok() + .and_then(|x| x.parse::().ok())) + .unwrap_or(1) == 0 + { + log!(info, "running sanity checks at {}", iteration); + Pools::do_try_state(u8::MAX).unwrap(); + } + + // collect and reset events. + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let pallet_nomination_pools::mock::RuntimeEvent::Pools(inner) = e { + Some(inner) + } else { + None + } + }) + .for_each(|e| { + if let Some((_, c)) = events_histogram + .iter_mut() + .find(|(x, _)| std::mem::discriminant(x) == std::mem::discriminant(&e)) + { + *c += 1; + } else { + events_histogram.push((e, 1)) + } + }); + System::reset_events(); + + // trigger an era change, and check the status of the reward agent. + if iteration % ERA == 0 { + CurrentEra::mutate(|c| *c += 1); + BondedPools::::iter().for_each(|(id, _)| { + let amount = random_ed_multiple(&mut rng); + let _ = + Balances::deposit_creating(&Pools::create_reward_account(id), amount); + // if we just paid out the reward agent, let's calculate how much we expect + // our reward agent to have earned. + if reward_agent.pool_id.map_or(false, |mid| mid == id) { + let all_points = BondedPool::::get(id).map(|p| p.points).unwrap(); + let member_points = + PoolMembers::::get(reward_agent.who).map(|m| m.points).unwrap(); + let agent_share = Perquintill::from_rational(member_points, all_points); + log::info!( + target: "reward-agent", + "🤖 REWARD = amount = {:?}, ratio: {:?}, share {:?}", + amount, + agent_share, + agent_share * amount, + ); + reward_agent.expected_reward += agent_share * amount; + } + }); + + log!( + info, + "iteration {}, {} pools, {} members, {} ok {} err, events = {:?}", + iteration, + BondedPools::::count(), + PoolMembers::::count(), + ok, + err, + events_histogram + .iter() + .map(|(x, c)| ( + format!("{:?}", x) + .split(" ") + .map(|x| x.to_string()) + .collect::>() + .first() + .cloned() + .unwrap(), + c, + )) + .collect::>(), + ); + } + }) + }) + } +} diff --git a/frame/nomination-pools/runtime-api/Cargo.toml b/frame/nomination-pools/runtime-api/Cargo.toml new file mode 100644 index 0000000000000..cf72d795c9faa --- /dev/null +++ b/frame/nomination-pools/runtime-api/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "pallet-nomination-pools-runtime-api" +version = "1.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Runtime API for nomination-pools FRAME pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-api/std", + "sp-std/std", +] diff --git a/frame/nomination-pools/runtime-api/README.md b/frame/nomination-pools/runtime-api/README.md new file mode 100644 index 0000000000000..af90b31733b0b --- /dev/null +++ b/frame/nomination-pools/runtime-api/README.md @@ -0,0 +1,3 @@ +Runtime API definition for nomination-pools pallet. + +License: Apache-2.0 \ No newline at end of file diff --git a/frame/nomination-pools/runtime-api/src/lib.rs b/frame/nomination-pools/runtime-api/src/lib.rs new file mode 100644 index 0000000000000..aa3ca57ca5b8b --- /dev/null +++ b/frame/nomination-pools/runtime-api/src/lib.rs @@ -0,0 +1,33 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Runtime API definition for nomination-pools pallet. +//! Currently supports only one rpc endpoint. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; + +sp_api::decl_runtime_apis! { + /// Runtime api for accessing information about nomination pools. + pub trait NominationPoolsApi + where AccountId: Codec, Balance: Codec + { + /// Returns the pending rewards for the member that the AccountId was given for. + fn pending_rewards(member: AccountId) -> Balance; + } +} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs new file mode 100644 index 0000000000000..fd533ee3762b4 --- /dev/null +++ b/frame/nomination-pools/src/lib.rs @@ -0,0 +1,2600 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! # Nomination Pools for Staking Delegation +//! +//! A pallet that allows members to delegate their stake to nominating pools. A nomination pool acts +//! as nominator and nominates validators on the members behalf. +//! +//! # Index +//! +//! * [Key terms](#key-terms) +//! * [Usage](#usage) +//! * [Implementor's Guide](#implementors-guide) +//! * [Design](#design) +//! +//! ## Key Terms +//! +//! * pool id: A unique identifier of each pool. Set to u32. +//! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and +//! [`BondedPoolInner`]. +//! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and +//! [`RewardPools`]. +//! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See +//! [`SubPools`] and [`SubPoolsStorage`]. +//! * members: Accounts that are members of pools. See [`PoolMember`] and [`PoolMembers`]. +//! * roles: Administrative roles of each pool, capable of controlling nomination, and the state of +//! the pool. +//! * point: A unit of measure for a members portion of a pool's funds. Points initially have a +//! ratio of 1 (as set by `POINTS_TO_BALANCE_INIT_RATIO`) to balance, but as slashing happens, +//! this can change. +//! * kick: The act of a pool administrator forcibly ejecting a member. +//! * bonded account: A key-less account id derived from the pool id that acts as the bonded +//! account. This account registers itself as a nominator in the staking system, and follows +//! exactly the same rules and conditions as a normal staker. Its bond increases or decreases as +//! members join, it can `nominate` or `chill`, and might not even earn staking rewards if it is +//! not nominating proper validators. +//! * reward account: A similar key-less account, that is set as the `Payee` account for the bonded +//! account for all staking rewards. +//! +//! ## Usage +//! +//! ### Join +//! +//! An account can stake funds with a nomination pool by calling [`Call::join`]. +//! +//! ### Claim rewards +//! +//! After joining a pool, a member can claim rewards by calling [`Call::claim_payout`]. +//! +//! For design docs see the [reward pool](#reward-pool) section. +//! +//! ### Leave +//! +//! In order to leave, a member must take two steps. +//! +//! First, they must call [`Call::unbond`]. The unbond extrinsic will start the unbonding process by +//! unbonding all or a portion of the members funds. +//! +//! > A member can have up to [`Config::MaxUnbonding`] distinct active unbonding requests. +//! +//! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the member can +//! call [`Call::withdraw_unbonded`] to withdraw any funds that are free. +//! +//! For design docs see the [bonded pool](#bonded-pool) and [unbonding sub +//! pools](#unbonding-sub-pools) sections. +//! +//! ### Slashes +//! +//! Slashes are distributed evenly across the bonded pool and the unbonding pools from slash era+1 +//! through the slash apply era. Thus, any member who either +//! +//! 1. unbonded, or +//! 2. was actively bonded +// +//! in the aforementioned range of eras will be affected by the slash. A member is slashed pro-rata +//! based on its stake relative to the total slash amount. +//! +//! Slashing does not change any single member's balance. Instead, the slash will only reduce the +//! balance associated with a particular pool. But, we never change the total *points* of a pool +//! because of slashing. Therefore, when a slash happens, the ratio of points to balance changes in +//! a pool. In other words, the value of one point, which is initially 1-to-1 against a unit of +//! balance, is now less than one balance because of the slash. +//! +//! ### Administration +//! +//! A pool can be created with the [`Call::create`] call. Once created, the pools nominator or root +//! user must call [`Call::nominate`] to start nominating. [`Call::nominate`] can be called at +//! anytime to update validator selection. +//! +//! Similar to [`Call::nominate`], [`Call::chill`] will chill to pool in the staking system, and +//! [`Call::pool_withdraw_unbonded`] will withdraw any unbonding chunks of the pool bonded account. +//! The latter call is permissionless and can be called by anyone at any time. +//! +//! To help facilitate pool administration the pool has one of three states (see [`PoolState`]): +//! +//! * Open: Anyone can join the pool and no members can be permissionlessly removed. +//! * Blocked: No members can join and some admin roles can kick members. Kicking is not instant, +//! and follows the same process of `unbond` and then `withdraw_unbonded`. In other words, +//! administrators can permissionlessly unbond other members. +//! * Destroying: No members can join and all members can be permissionlessly removed with +//! [`Call::unbond`] and [`Call::withdraw_unbonded`]. Once a pool is in destroying state, it +//! cannot be reverted to another state. +//! +//! A pool has 4 administrative roles (see [`PoolRoles`]): +//! +//! * Depositor: creates the pool and is the initial member. They can only leave the pool once all +//! other members have left. Once they fully withdraw their funds, the pool is destroyed. +//! * Nominator: can select which validators the pool nominates. +//! * State-Toggler: can change the pools state and kick members if the pool is blocked. +//! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions +//! the nominator or state-toggler can. +//! +//! ### Dismantling +//! +//! As noted, a pool is destroyed once +//! +//! 1. First, all members need to fully unbond and withdraw. If the pool state is set to +//! `Destroying`, this can happen permissionlessly. +//! 2. The depositor itself fully unbonds and withdraws. +//! +//! > Note that at this point, based on the requirements of the staking system, the pool's bonded +//! > account's stake might not be able to ge below a certain threshold as a nominator. At this +//! > point, the pool should `chill` itself to allow the depositor to leave. See [`Call::chill`]. +//! +//! ## Implementor's Guide +//! +//! Some notes and common mistakes that wallets/apps wishing to implement this pallet should be +//! aware of: +//! +//! +//! ### Pool Members +//! +//! * In general, whenever a pool member changes their total point, the chain will automatically +//! claim all their pending rewards for them. This is not optional, and MUST happen for the reward +//! calculation to remain correct (see the documentation of `bond` as an example). So, make sure +//! you are warning your users about it. They might be surprised if they see that they bonded an +//! extra 100 DOTs, and now suddenly their 5.23 DOTs in pending reward is gone. It is not gone, it +//! has been paid out to you! +//! * Joining a pool implies transferring funds to the pool account. So it might be (based on which +//! wallet that you are using) that you no longer see the funds that are moved to the pool in your +//! “free balance” section. Make sure the user is aware of this, and not surprised by seeing this. +//! Also, the transfer that happens here is configured to to never accidentally destroy the sender +//! account. So to join a Pool, your sender account must remain alive with 1 DOT left in it. This +//! means, with 1 DOT as existential deposit, and 1 DOT as minimum to join a pool, you need at +//! least 2 DOT to join a pool. Consequently, if you are suggesting members to join a pool with +//! “Maximum possible value”, you must subtract 1 DOT to remain in the sender account to not +//! accidentally kill it. +//! * Points and balance are not the same! Any pool member, at any point in time, can have points in +//! either the bonded pool or any of the unbonding pools. The crucial fact is that in any of these +//! pools, the ratio of point to balance is different and might not be 1. Each pool starts with a +//! ratio of 1, but as time goes on, for reasons such as slashing, the ratio gets broken. Over +//! time, 100 points in a bonded pool can be worth 90 DOTs. Make sure you are either representing +//! points as points (not as DOTs), or even better, always display both: “You have x points in +//! pool y which is worth z DOTs”. See here and here for examples of how to calculate point to +//! balance ratio of each pool (it is almost trivial ;)) +//! +//! ### Pool Management +//! +//! * The pool will be seen from the perspective of the rest of the system as a single nominator. +//! Ergo, This nominator must always respect the `staking.minNominatorBond` limit. Similar to a +//! normal nominator, who has to first `chill` before fully unbonding, the pool must also do the +//! same. The pool’s bonded account will be fully unbonded only when the depositor wants to leave +//! and dismantle the pool. All that said, the message is: the depositor can only leave the chain +//! when they chill the pool first. +//! +//! ## Design +//! +//! _Notes_: this section uses pseudo code to explain general design and does not necessarily +//! reflect the exact implementation. Additionally, a working knowledge of `pallet-staking`'s api is +//! assumed. +//! +//! ### Goals +//! +//! * Maintain network security by upholding integrity of slashing events, sufficiently penalizing +//! members that where in the pool while it was backing a validator that got slashed. +//! * Maximize scalability in terms of member count. +//! +//! In order to maintain scalability, all operations are independent of the number of members. To do +//! this, delegation specific information is stored local to the member while the pool data +//! structures have bounded datum. +//! +//! ### Bonded pool +//! +//! A bonded pool nominates with its total balance, excluding that which has been withdrawn for +//! unbonding. The total points of a bonded pool are always equal to the sum of points of the +//! delegation members. A bonded pool tracks its points and reads its bonded balance. +//! +//! When a member joins a pool, `amount_transferred` is transferred from the members account to the +//! bonded pools account. Then the pool calls `staking::bond_extra(amount_transferred)` and issues +//! new points which are tracked by the member and added to the bonded pool's points. +//! +//! When the pool already has some balance, we want the value of a point before the transfer to +//! equal the value of a point after the transfer. So, when a member joins a bonded pool with a +//! given `amount_transferred`, we maintain the ratio of bonded balance to points such that: +//! +//! ```text +//! balance_after_transfer / points_after_transfer == balance_before_transfer / points_before_transfer; +//! ``` +//! +//! To achieve this, we issue points based on the following: +//! +//! ```text +//! points_issued = (points_before_transfer / balance_before_transfer) * amount_transferred; +//! ``` +//! +//! For new bonded pools we can set the points issued per balance arbitrarily. In this +//! implementation we use a 1 points to 1 balance ratio for pool creation (see +//! [`POINTS_TO_BALANCE_INIT_RATIO`]). +//! +//! **Relevant extrinsics:** +//! +//! * [`Call::create`] +//! * [`Call::join`] +//! +//! ### Reward pool +//! +//! When a pool is first bonded it sets up an deterministic, inaccessible account as its reward +//! destination. +//! +//! The reward pool is not really a pool anymore, as it does not track points anymore. Instead, it +//! tracks, a virtual value called `reward_counter`, among a few other values. +//! +//! See [this link](https://hackmd.io/PFGn6wI5TbCmBYoEA_f2Uw) for an in-depth explanation of the +//! reward pool mechanism. +//! +//! **Relevant extrinsics:** +//! +//! * [`Call::claim_payout`] +//! +//! ### Unbonding sub pools +//! +//! When a member unbonds, it's balance is unbonded in the bonded pool's account and tracked in +//! an unbonding pool associated with the active era. If no such pool exists, one is created. To +//! track which unbonding sub pool a member belongs too, a member tracks it's +//! `unbonding_era`. +//! +//! When a member initiates unbonding it's claim on the bonded pool +//! (`balance_to_unbond`) is computed as: +//! +//! ```text +//! balance_to_unbond = (bonded_pool.balance / bonded_pool.points) * member.points; +//! ``` +//! +//! If this is the first transfer into an unbonding pool arbitrary amount of points can be issued +//! per balance. In this implementation unbonding pools are initialized with a 1 point to 1 balance +//! ratio (see [`POINTS_TO_BALANCE_INIT_RATIO`]). Otherwise, the unbonding pools hold the same +//! points to balance ratio properties as the bonded pool, so member points in the +//! unbonding pool are issued based on +//! +//! ```text +//! new_points_issued = (points_before_transfer / balance_before_transfer) * balance_to_unbond; +//! ``` +//! +//! For scalability, a bound is maintained on the number of unbonding sub pools (see +//! [`TotalUnbondingPools`]). An unbonding pool is removed once its older than `current_era - +//! TotalUnbondingPools`. An unbonding pool is merged into the unbonded pool with +//! +//! ```text +//! unbounded_pool.balance = unbounded_pool.balance + unbonding_pool.balance; +//! unbounded_pool.points = unbounded_pool.points + unbonding_pool.points; +//! ``` +//! +//! This scheme "averages" out the points value in the unbonded pool. +//! +//! Once a members `unbonding_era` is older than `current_era - +//! [sp_staking::StakingInterface::bonding_duration]`, it can can cash it's points out of the +//! corresponding unbonding pool. If it's `unbonding_era` is older than `current_era - +//! TotalUnbondingPools`, it can cash it's points from the unbonded pool. +//! +//! **Relevant extrinsics:** +//! +//! * [`Call::unbond`] +//! * [`Call::withdraw_unbonded`] +//! +//! ### Slashing +//! +//! This section assumes that the slash computation is executed by +//! `pallet_staking::StakingLedger::slash`, which passes the information to this pallet via +//! [`sp_staking::OnStakerSlash::on_slash`]. +//! +//! Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool +//! while it was backing a validator that equivocated are punished. Without these measures a +//! member could unbond right after a validator equivocated with no consequences. +//! +//! This strategy is unfair to members who joined after the slash, because they get slashed as +//! well, but spares members who unbond. The latter is much more important for security: if a +//! pool's validators are attacking the network, their members need to unbond fast! Avoiding +//! slashes gives them an incentive to do that if validators get repeatedly slashed. +//! +//! To be fair to joiners, this implementation also need joining pools, which are actively staking, +//! in addition to the unbonding pools. For maintenance simplicity these are not implemented. +//! Related: +//! +//! **Relevant methods:** +//! +//! * [`Pallet::on_slash`] +//! +//! ### Limitations +//! +//! * PoolMembers cannot vote with their staked funds because they are transferred into the pools +//! account. In the future this can be overcome by allowing the members to vote with their bonded +//! funds via vote splitting. +//! * PoolMembers cannot quickly transfer to another pool if they do no like nominations, instead +//! they must wait for the unbonding duration. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; +use frame_support::{ + defensive, ensure, + pallet_prelude::{MaxEncodedLen, *}, + storage::bounded_btree_map::BoundedBTreeMap, + traits::{ + Currency, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, + ExistenceRequirement, Get, + }, + DefaultNoBound, +}; +use scale_info::TypeInfo; +use sp_core::U256; +use sp_runtime::{ + traits::{ + AccountIdConversion, CheckedAdd, CheckedSub, Convert, Saturating, StaticLookup, Zero, + }, + FixedPointNumber, +}; +use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; +use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; + +/// The log target of this pallet. +pub const LOG_TARGET: &'static str = "runtime::nomination-pools"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: $crate::LOG_TARGET, + concat!("[{:?}] 🏊‍♂️ ", $patter), >::block_number() $(, $values)* + ) + }; +} + +#[cfg(any(test, feature = "fuzzing"))] +pub mod mock; +#[cfg(test)] +mod tests; + +pub mod migration; +pub mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// The balance type used by the currency system. +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +/// Type used for unique identifier of each pool. +pub type PoolId = u32; + +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; + +/// Possible operations on the configuration values of this pallet. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq, Clone)] +pub enum ConfigOp { + /// Don't change. + Noop, + /// Set the given value. + Set(T), + /// Remove from storage. + Remove, +} + +/// The type of bonding that can happen to a pool. +enum BondType { + /// Someone is bonding into the pool upon creation. + Create, + /// Someone is adding more funds later to this pool. + Later, +} + +/// How to increase the bond of a member. +#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] +pub enum BondExtra { + /// Take from the free balance. + FreeBalance(Balance), + /// Take the entire amount from the accumulated rewards. + Rewards, +} + +/// The type of account being created. +#[derive(Encode, Decode)] +enum AccountType { + Bonded, + Reward, +} + +/// A member in a pool. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, CloneNoBound)] +#[cfg_attr(feature = "std", derive(frame_support::PartialEqNoBound, DefaultNoBound))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct PoolMember { + /// The identifier of the pool to which `who` belongs. + pub pool_id: PoolId, + /// The quantity of points this member has in the bonded pool or in a sub pool if + /// `Self::unbonding_era` is some. + pub points: BalanceOf, + /// The reward counter at the time of this member's last payout claim. + pub last_recorded_reward_counter: T::RewardCounter, + /// The eras in which this member is unbonding, mapped from era index to the number of + /// points scheduled to unbond in the given era. + pub unbonding_eras: BoundedBTreeMap, T::MaxUnbonding>, +} + +impl PoolMember { + /// The pending rewards of this member. + fn pending_rewards( + &self, + current_reward_counter: T::RewardCounter, + ) -> Result, Error> { + // accuracy note: Reward counters are `FixedU128` with base of 10^18. This value is being + // multiplied by a point. The worse case of a point is 10x the granularity of the balance + // (10x is the common configuration of `MaxPointsToBalance`). + // + // Assuming roughly the current issuance of polkadot (12,047,781,394,999,601,455, which is + // 1.2 * 10^9 * 10^10 = 1.2 * 10^19), the worse case point value is around 10^20. + // + // The final multiplication is: + // + // rc * 10^20 / 10^18 = rc * 100 + // + // the implementation of `multiply_by_rational_with_rounding` shows that it will only fail + // if the final division is not enough to fit in u128. In other words, if `rc * 100` is more + // than u128::max. Given that RC is interpreted as reward per unit of point, and unit of + // point is equal to balance (normally), and rewards are usually a proportion of the points + // in the pool, the likelihood of rc reaching near u128::MAX is near impossible. + + (current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter)) + .checked_mul_int(self.active_points()) + .ok_or(Error::::OverflowRisk) + } + + /// Active balance of the member. + /// + /// This is derived from the ratio of points in the pool to which the member belongs to. + /// Might return different values based on the pool state for the same member and points. + fn active_balance(&self) -> BalanceOf { + if let Some(pool) = BondedPool::::get(self.pool_id).defensive() { + pool.points_to_balance(self.points) + } else { + Zero::zero() + } + } + + /// Total points of this member, both active and unbonding. + fn total_points(&self) -> BalanceOf { + self.active_points().saturating_add(self.unbonding_points()) + } + + /// Active points of the member. + fn active_points(&self) -> BalanceOf { + self.points + } + + /// Inactive points of the member, waiting to be withdrawn. + fn unbonding_points(&self) -> BalanceOf { + self.unbonding_eras + .as_ref() + .iter() + .fold(BalanceOf::::zero(), |acc, (_, v)| acc.saturating_add(*v)) + } + + /// Try and unbond `points_dissolved` from self, and in return mint `points_issued` into the + /// corresponding `era`'s unlock schedule. + /// + /// In the absence of slashing, these two points are always the same. In the presence of + /// slashing, the value of points in different pools varies. + /// + /// Returns `Ok(())` and updates `unbonding_eras` and `points` if success, `Err(_)` otherwise. + fn try_unbond( + &mut self, + points_dissolved: BalanceOf, + points_issued: BalanceOf, + unbonding_era: EraIndex, + ) -> Result<(), Error> { + if let Some(new_points) = self.points.checked_sub(&points_dissolved) { + match self.unbonding_eras.get_mut(&unbonding_era) { + Some(already_unbonding_points) => + *already_unbonding_points = + already_unbonding_points.saturating_add(points_issued), + None => self + .unbonding_eras + .try_insert(unbonding_era, points_issued) + .map(|old| { + if old.is_some() { + defensive!("value checked to not exist in the map; qed"); + } + }) + .map_err(|_| Error::::MaxUnbondingLimit)?, + } + self.points = new_points; + Ok(()) + } else { + Err(Error::::MinimumBondNotMet) + } + } + + /// Withdraw any funds in [`Self::unbonding_eras`] who's deadline in reached and is fully + /// unlocked. + /// + /// Returns a a subset of [`Self::unbonding_eras`] that got withdrawn. + /// + /// Infallible, noop if no unbonding eras exist. + fn withdraw_unlocked( + &mut self, + current_era: EraIndex, + ) -> BoundedBTreeMap, T::MaxUnbonding> { + // NOTE: if only drain-filter was stable.. + let mut removed_points = + BoundedBTreeMap::, T::MaxUnbonding>::default(); + self.unbonding_eras.retain(|e, p| { + if *e > current_era { + true + } else { + removed_points + .try_insert(*e, *p) + .expect("source map is bounded, this is a subset, will be bounded; qed"); + false + } + }); + removed_points + } +} + +/// A pool's possible states. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, RuntimeDebugNoBound, Clone, Copy)] +pub enum PoolState { + /// The pool is open to be joined, and is working normally. + Open, + /// The pool is blocked. No one else can join. + Blocked, + /// The pool is in the process of being destroyed. + /// + /// All members can now be permissionlessly unbonded, and the pool can never go back to any + /// other state other than being dissolved. + Destroying, +} + +/// Pool administration roles. +/// +/// Any pool has a depositor, which can never change. But, all the other roles are optional, and +/// cannot exist. Note that if `root` is set to `None`, it basically means that the roles of this +/// pool can never change again (except via governance). +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone)] +pub struct PoolRoles { + /// Creates the pool and is the initial member. They can only leave the pool once all other + /// members have left. Once they fully leave, the pool is destroyed. + pub depositor: AccountId, + /// Can change the nominator, state-toggler, or itself and can perform any of the actions the + /// nominator or state-toggler can. + pub root: Option, + /// Can select which validators the pool nominates. + pub nominator: Option, + /// Can change the pools state and kick members if the pool is blocked. + pub state_toggler: Option, +} + +/// Pool permissions and state +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone)] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct BondedPoolInner { + /// Total points of all the members in the pool who are actively bonded. + pub points: BalanceOf, + /// The current state of the pool. + pub state: PoolState, + /// Count of members that belong to the pool. + pub member_counter: u32, + /// See [`PoolRoles`]. + pub roles: PoolRoles, +} + +/// A wrapper for bonded pools, with utility functions. +/// +/// The main purpose of this is to wrap a [`BondedPoolInner`], with the account + id of the pool, +/// for easier access. +#[derive(RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +pub struct BondedPool { + /// The identifier of the pool. + id: PoolId, + /// The inner fields. + inner: BondedPoolInner, +} + +impl sp_std::ops::Deref for BondedPool { + type Target = BondedPoolInner; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl sp_std::ops::DerefMut for BondedPool { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl BondedPool { + /// Create a new bonded pool with the given roles and identifier. + fn new(id: PoolId, roles: PoolRoles) -> Self { + Self { + id, + inner: BondedPoolInner { + roles, + state: PoolState::Open, + points: Zero::zero(), + member_counter: Zero::zero(), + }, + } + } + + /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. + pub fn get(id: PoolId) -> Option { + BondedPools::::try_get(id).ok().map(|inner| Self { id, inner }) + } + + /// Get the bonded account id of this pool. + fn bonded_account(&self) -> T::AccountId { + Pallet::::create_bonded_account(self.id) + } + + /// Get the reward account id of this pool. + fn reward_account(&self) -> T::AccountId { + Pallet::::create_reward_account(self.id) + } + + /// Consume self and put into storage. + fn put(self) { + BondedPools::::insert(self.id, BondedPoolInner { ..self.inner }); + } + + /// Consume self and remove from storage. + fn remove(self) { + BondedPools::::remove(self.id); + } + + /// Convert the given amount of balance to points given the current pool state. + /// + /// This is often used for bonding and issuing new funds into the pool. + fn balance_to_point(&self, new_funds: BalanceOf) -> BalanceOf { + let bonded_balance = + T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + Pallet::::balance_to_point(bonded_balance, self.points, new_funds) + } + + /// Convert the given number of points to balance given the current pool state. + /// + /// This is often used for unbonding. + fn points_to_balance(&self, points: BalanceOf) -> BalanceOf { + let bonded_balance = + T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + Pallet::::point_to_balance(bonded_balance, self.points, points) + } + + /// Issue points to [`Self`] for `new_funds`. + fn issue(&mut self, new_funds: BalanceOf) -> BalanceOf { + let points_to_issue = self.balance_to_point(new_funds); + self.points = self.points.saturating_add(points_to_issue); + points_to_issue + } + + /// Dissolve some points from the pool i.e. unbond the given amount of points from this pool. + /// This is the opposite of issuing some funds into the pool. + /// + /// Mutates self in place, but does not write anything to storage. + /// + /// Returns the equivalent balance amount that actually needs to get unbonded. + fn dissolve(&mut self, points: BalanceOf) -> BalanceOf { + // NOTE: do not optimize by removing `balance`. it must be computed before mutating + // `self.point`. + let balance = self.points_to_balance(points); + self.points = self.points.saturating_sub(points); + balance + } + + /// Increment the member counter. Ensures that the pool and system member limits are + /// respected. + fn try_inc_members(&mut self) -> Result<(), DispatchError> { + ensure!( + MaxPoolMembersPerPool::::get() + .map_or(true, |max_per_pool| self.member_counter < max_per_pool), + Error::::MaxPoolMembers + ); + ensure!( + MaxPoolMembers::::get().map_or(true, |max| PoolMembers::::count() < max), + Error::::MaxPoolMembers + ); + self.member_counter = self.member_counter.checked_add(1).ok_or(Error::::OverflowRisk)?; + Ok(()) + } + + /// Decrement the member counter. + fn dec_members(mut self) -> Self { + self.member_counter = self.member_counter.defensive_saturating_sub(1); + self + } + + /// The pools balance that is transferrable. + fn transferrable_balance(&self) -> BalanceOf { + let account = self.bonded_account(); + T::Currency::free_balance(&account) + .saturating_sub(T::Staking::active_stake(&account).unwrap_or_default()) + } + + fn is_root(&self, who: &T::AccountId) -> bool { + self.roles.root.as_ref().map_or(false, |root| root == who) + } + + fn is_state_toggler(&self, who: &T::AccountId) -> bool { + self.roles + .state_toggler + .as_ref() + .map_or(false, |state_toggler| state_toggler == who) + } + + fn can_update_roles(&self, who: &T::AccountId) -> bool { + self.is_root(who) + } + + fn can_nominate(&self, who: &T::AccountId) -> bool { + self.is_root(who) || + self.roles.nominator.as_ref().map_or(false, |nominator| nominator == who) + } + + fn can_kick(&self, who: &T::AccountId) -> bool { + self.state == PoolState::Blocked && (self.is_root(who) || self.is_state_toggler(who)) + } + + fn can_toggle_state(&self, who: &T::AccountId) -> bool { + (self.is_root(who) || self.is_state_toggler(who)) && !self.is_destroying() + } + + fn can_set_metadata(&self, who: &T::AccountId) -> bool { + self.is_root(who) || self.is_state_toggler(who) + } + + fn is_destroying(&self) -> bool { + matches!(self.state, PoolState::Destroying) + } + + fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf) -> bool { + // we need to ensure that `self.member_counter == 1` as well, because the depositor's + // initial `MinCreateBond` (or more) is what guarantees that the ledger of the pool does not + // get killed in the staking system, and that it does not fall below `MinimumNominatorBond`, + // which could prevent other non-depositor members from fully leaving. Thus, all members + // must withdraw, then depositor can unbond, and finally withdraw after waiting another + // cycle. + self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1 + } + + /// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the + /// pool is unrecoverable and should be in the destroying state. + fn ok_to_be_open(&self) -> Result<(), DispatchError> { + ensure!(!self.is_destroying(), Error::::CanNotChangeState); + + let bonded_balance = + T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); + + let points_to_balance_ratio_floor = self + .points + // We checked for zero above + .div(bonded_balance); + + let max_points_to_balance = T::MaxPointsToBalance::get(); + + // Pool points can inflate relative to balance, but only if the pool is slashed. + // If we cap the ratio of points:balance so one cannot join a pool that has been slashed + // by `max_points_to_balance`%, if not zero. + ensure!( + points_to_balance_ratio_floor < max_points_to_balance.into(), + Error::::OverflowRisk + ); + + // then we can be decently confident the bonding pool points will not overflow + // `BalanceOf`. Note that these are just heuristics. + + Ok(()) + } + + /// Check that the pool can accept a member with `new_funds`. + fn ok_to_join(&self) -> Result<(), DispatchError> { + ensure!(self.state == PoolState::Open, Error::::NotOpen); + self.ok_to_be_open()?; + Ok(()) + } + + fn ok_to_unbond_with( + &self, + caller: &T::AccountId, + target_account: &T::AccountId, + target_member: &PoolMember, + unbonding_points: BalanceOf, + ) -> Result<(), DispatchError> { + let is_permissioned = caller == target_account; + let is_depositor = *target_account == self.roles.depositor; + let is_full_unbond = unbonding_points == target_member.active_points(); + + let balance_after_unbond = { + let new_depositor_points = + target_member.active_points().saturating_sub(unbonding_points); + let mut target_member_after_unbond = (*target_member).clone(); + target_member_after_unbond.points = new_depositor_points; + target_member_after_unbond.active_balance() + }; + + // any partial unbonding is only ever allowed if this unbond is permissioned. + ensure!( + is_permissioned || is_full_unbond, + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // any unbond must comply with the balance condition: + ensure!( + is_full_unbond || + balance_after_unbond >= + if is_depositor { + Pallet::::depositor_min_bond() + } else { + MinJoinBond::::get() + }, + Error::::MinimumBondNotMet + ); + + // additional checks: + match (is_permissioned, is_depositor) { + (true, false) => (), + (true, true) => { + // permission depositor unbond: if destroying and pool is empty, always allowed, + // with no additional limits. + if self.is_destroying_and_only_depositor(target_member.active_points()) { + // everything good, let them unbond anything. + } else { + // depositor cannot fully unbond yet. + ensure!(!is_full_unbond, Error::::MinimumBondNotMet); + } + }, + (false, false) => { + // If the pool is blocked, then an admin with kicking permissions can remove a + // member. If the pool is being destroyed, anyone can remove a member + debug_assert!(is_full_unbond); + ensure!( + self.can_kick(caller) || self.is_destroying(), + Error::::NotKickerOrDestroying + ) + }, + (false, true) => { + // the depositor can simply not be unbonded permissionlessly, period. + return Err(Error::::DoesNotHavePermission.into()) + }, + }; + + Ok(()) + } + + /// # Returns + /// + /// * Ok(()) if [`Call::withdraw_unbonded`] can be called, `Err(DispatchError)` otherwise. + fn ok_to_withdraw_unbonded_with( + &self, + caller: &T::AccountId, + target_account: &T::AccountId, + ) -> Result<(), DispatchError> { + // This isn't a depositor + let is_permissioned = caller == target_account; + ensure!( + is_permissioned || self.can_kick(caller) || self.is_destroying(), + Error::::NotKickerOrDestroying + ); + Ok(()) + } + + /// Bond exactly `amount` from `who`'s funds into this pool. + /// + /// If the bond type is `Create`, `Staking::bond` is called, and `who` + /// is allowed to be killed. Otherwise, `Staking::bond_extra` is called and `who` + /// cannot be killed. + /// + /// Returns `Ok(points_issues)`, `Err` otherwise. + fn try_bond_funds( + &mut self, + who: &T::AccountId, + amount: BalanceOf, + ty: BondType, + ) -> Result, DispatchError> { + // Cache the value + let bonded_account = self.bonded_account(); + T::Currency::transfer( + &who, + &bonded_account, + amount, + match ty { + BondType::Create => ExistenceRequirement::AllowDeath, + BondType::Later => ExistenceRequirement::KeepAlive, + }, + )?; + // We must calculate the points issued *before* we bond who's funds, else points:balance + // ratio will be wrong. + let points_issued = self.issue(amount); + + match ty { + BondType::Create => T::Staking::bond(&bonded_account, amount, &self.reward_account())?, + // The pool should always be created in such a way its in a state to bond extra, but if + // the active balance is slashed below the minimum bonded or the account cannot be + // found, we exit early. + BondType::Later => T::Staking::bond_extra(&bonded_account, amount)?, + } + + Ok(points_issued) + } + + // Set the state of `self`, and deposit an event if the state changed. State should never be set + // directly in in order to ensure a state change event is always correctly deposited. + fn set_state(&mut self, state: PoolState) { + if self.state != state { + self.state = state; + Pallet::::deposit_event(Event::::StateChanged { + pool_id: self.id, + new_state: state, + }); + }; + } +} + +/// A reward pool. +/// +/// A reward pool is not so much a pool anymore, since it does not contain any shares or points. +/// Rather, simply to fit nicely next to bonded pool and unbonding pools in terms of terminology. In +/// reality, a reward pool is just a container for a few pool-dependent data related to the rewards. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq, DefaultNoBound))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct RewardPool { + /// The last recorded value of the reward counter. + /// + /// This is updated ONLY when the points in the bonded pool change, which means `join`, + /// `bond_extra` and `unbond`, all of which is done through `update_recorded`. + last_recorded_reward_counter: T::RewardCounter, + /// The last recorded total payouts of the reward pool. + /// + /// Payouts is essentially income of the pool. + /// + /// Update criteria is same as that of `last_recorded_reward_counter`. + last_recorded_total_payouts: BalanceOf, + /// Total amount that this pool has paid out so far to the members. + total_rewards_claimed: BalanceOf, +} + +impl RewardPool { + /// Getter for [`RewardPool::last_recorded_reward_counter`]. + pub(crate) fn last_recorded_reward_counter(&self) -> T::RewardCounter { + self.last_recorded_reward_counter + } + + /// Register some rewards that are claimed from the pool by the members. + fn register_claimed_reward(&mut self, reward: BalanceOf) { + self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward); + } + + /// Update the recorded values of the pool. + fn update_records(&mut self, id: PoolId, bonded_points: BalanceOf) -> Result<(), Error> { + let balance = Self::current_balance(id); + self.last_recorded_reward_counter = self.current_reward_counter(id, bonded_points)?; + self.last_recorded_total_payouts = balance + .checked_add(&self.total_rewards_claimed) + .ok_or(Error::::OverflowRisk)?; + Ok(()) + } + + /// Get the current reward counter, based on the given `bonded_points` being the state of the + /// bonded pool at this time. + fn current_reward_counter( + &self, + id: PoolId, + bonded_points: BalanceOf, + ) -> Result> { + let balance = Self::current_balance(id); + let payouts_since_last_record = balance + .saturating_add(self.total_rewards_claimed) + .saturating_sub(self.last_recorded_total_payouts); + + // * accuracy notes regarding the multiplication in `checked_from_rational`: + // `payouts_since_last_record` is a subset of the total_issuance at the very + // worse. `bonded_points` are similarly, in a non-slashed pool, have the same granularity as + // balance, and are thus below within the range of total_issuance. In the worse case + // scenario, for `saturating_from_rational`, we have: + // + // dot_total_issuance * 10^18 / `minJoinBond` + // + // assuming `MinJoinBond == ED` + // + // dot_total_issuance * 10^18 / 10^10 = dot_total_issuance * 10^8 + // + // which, with the current numbers, is a miniscule fraction of the u128 capacity. + // + // Thus, adding two values of type reward counter should be safe for ages in a chain like + // Polkadot. The important note here is that `reward_pool.last_recorded_reward_counter` only + // ever accumulates, but its semantics imply that it is less than total_issuance, when + // represented as `FixedU128`, which means it is less than `total_issuance * 10^18`. + // + // * accuracy notes regarding `checked_from_rational` collapsing to zero, meaning that no + // reward can be claimed: + // + // largest `bonded_points`, such that the reward counter is non-zero, with `FixedU128` + // will be when the payout is being computed. This essentially means `payout/bonded_points` + // needs to be more than 1/1^18. Thus, assuming that `bonded_points` will always be less + // than `10 * dot_total_issuance`, if the reward_counter is the smallest possible value, + // the value of the reward being calculated is: + // + // x / 10^20 = 1/ 10^18 + // + // x = 100 + // + // which is basically 10^-8 DOTs. See `smallest_claimable_reward` for an example of this. + T::RewardCounter::checked_from_rational(payouts_since_last_record, bonded_points) + .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r)) + .ok_or(Error::::OverflowRisk) + } + + /// Current free balance of the reward pool. + /// + /// This is sum of all the rewards that are claimable by pool members. + fn current_balance(id: PoolId) -> BalanceOf { + T::Currency::free_balance(&Pallet::::create_reward_account(id)) + .saturating_sub(T::Currency::minimum_balance()) + } +} + +/// An unbonding pool. This is always mapped with an era. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct UnbondPool { + /// The points in this pool. + points: BalanceOf, + /// The funds in the pool. + balance: BalanceOf, +} + +impl UnbondPool { + fn balance_to_point(&self, new_funds: BalanceOf) -> BalanceOf { + Pallet::::balance_to_point(self.balance, self.points, new_funds) + } + + fn point_to_balance(&self, points: BalanceOf) -> BalanceOf { + Pallet::::point_to_balance(self.balance, self.points, points) + } + + /// Issue the equivalent points of `new_funds` into self. + /// + /// Returns the actual amounts of points issued. + fn issue(&mut self, new_funds: BalanceOf) -> BalanceOf { + let new_points = self.balance_to_point(new_funds); + self.points = self.points.saturating_add(new_points); + self.balance = self.balance.saturating_add(new_funds); + new_points + } + + /// Dissolve some points from the unbonding pool, reducing the balance of the pool + /// proportionally. + /// + /// This is the opposite of `issue`. + /// + /// Returns the actual amount of `Balance` that was removed from the pool. + fn dissolve(&mut self, points: BalanceOf) -> BalanceOf { + let balance_to_unbond = self.point_to_balance(points); + self.points = self.points.saturating_sub(points); + self.balance = self.balance.saturating_sub(balance_to_unbond); + + balance_to_unbond + } +} + +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct SubPools { + /// A general, era agnostic pool of funds that have fully unbonded. The pools + /// of `Self::with_era` will lazily be merged into into this pool if they are + /// older then `current_era - TotalUnbondingPools`. + no_era: UnbondPool, + /// Map of era in which a pool becomes unbonded in => unbond pools. + with_era: BoundedBTreeMap, TotalUnbondingPools>, +} + +impl SubPools { + /// Merge the oldest `with_era` unbond pools into the `no_era` unbond pool. + /// + /// This is often used whilst getting the sub-pool from storage, thus it consumes and returns + /// `Self` for ergonomic purposes. + fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self { + // Ex: if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools + // 6..=10. Note that in the first few eras where `checked_sub` is `None`, we don't remove + // anything. + if let Some(newest_era_to_remove) = + current_era.checked_sub(T::PostUnbondingPoolsWindow::get()) + { + self.with_era.retain(|k, v| { + if *k > newest_era_to_remove { + // keep + true + } else { + // merge into the no-era pool + self.no_era.points = self.no_era.points.saturating_add(v.points); + self.no_era.balance = self.no_era.balance.saturating_add(v.balance); + false + } + }); + } + + self + } + + /// The sum of all unbonding balance, regardless of whether they are actually unlocked or not. + #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))] + fn sum_unbonding_balance(&self) -> BalanceOf { + self.no_era.balance.saturating_add( + self.with_era + .values() + .fold(BalanceOf::::zero(), |acc, pool| acc.saturating_add(pool.balance)), + ) + } +} + +/// The maximum amount of eras an unbonding pool can exist prior to being merged with the +/// `no_era` pool. This is guaranteed to at least be equal to the staking `UnbondingDuration`. For +/// improved UX [`Config::PostUnbondingPoolsWindow`] should be configured to a non-zero value. +pub struct TotalUnbondingPools(PhantomData); +impl Get for TotalUnbondingPools { + fn get() -> u32 { + // NOTE: this may be dangerous in the scenario bonding_duration gets decreased because + // we would no longer be able to decode `BoundedBTreeMap::, + // TotalUnbondingPools>`, which uses `TotalUnbondingPools` as the bound + T::Staking::bonding_duration() + T::PostUnbondingPoolsWindow::get() + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::traits::StorageVersion; + use frame_system::{ensure_signed, pallet_prelude::*}; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); + + #[pallet::pallet] + #[pallet::generate_store(pub(crate) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: weights::WeightInfo; + + /// The nominating balance. + type Currency: Currency; + + /// The type that is used for reward counter. + /// + /// The arithmetic of the reward counter might saturate based on the size of the + /// `Currency::Balance`. If this happens, operations fails. Nonetheless, this type should be + /// chosen such that this failure almost never happens, as if it happens, the pool basically + /// needs to be dismantled (or all pools migrated to a larger `RewardCounter` type, which is + /// a PITA to do). + /// + /// See the inline code docs of `Member::pending_rewards` and `RewardPool::update_recorded` + /// for example analysis. A [`sp_runtime::FixedU128`] should be fine for chains with balance + /// types similar to that of Polkadot and Kusama, in the absence of severe slashing (or + /// prevented via a reasonable `MaxPointsToBalance`), for many many years to come. + type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec; + + /// The nomination pool's pallet id. + #[pallet::constant] + type PalletId: Get; + + /// The maximum pool points-to-balance ratio that an `open` pool can have. + /// + /// This is important in the event slashing takes place and the pool's points-to-balance + /// ratio becomes disproportional. + /// + /// Moreover, this relates to the `RewardCounter` type as well, as the arithmetic operations + /// are a function of number of points, and by setting this value to e.g. 10, you ensure + /// that the total number of points in the system are at most 10 times the total_issuance of + /// the chain, in the absolute worse case. + /// + /// For a value of 10, the threshold would be a pool points-to-balance ratio of 10:1. + /// Such a scenario would also be the equivalent of the pool being 90% slashed. + #[pallet::constant] + type MaxPointsToBalance: Get; + + /// Infallible method for converting `Currency::Balance` to `U256`. + type BalanceToU256: Convert, U256>; + + /// Infallible method for converting `U256` to `Currency::Balance`. + type U256ToBalance: Convert>; + + /// The interface for nominating. + type Staking: StakingInterface, AccountId = Self::AccountId>; + + /// The amount of eras a `SubPools::with_era` pool can exist before it gets merged into the + /// `SubPools::no_era` pool. In other words, this is the amount of eras a member will be + /// able to withdraw from an unbonding pool which is guaranteed to have the correct ratio of + /// points to balance; once the `with_era` pool is merged into the `no_era` pool, the ratio + /// can become skewed due to some slashed ratio getting merged in at some point. + type PostUnbondingPoolsWindow: Get; + + /// The maximum length, in bytes, that a pools metadata maybe. + type MaxMetadataLen: Get; + + /// The maximum number of simultaneous unbonding chunks that can exist per member. + type MaxUnbonding: Get; + } + + /// Minimum amount to bond to join a pool. + #[pallet::storage] + pub type MinJoinBond = StorageValue<_, BalanceOf, ValueQuery>; + + /// Minimum bond required to create a pool. + /// + /// This is the amount that the depositor must put as their initial stake in the pool, as an + /// indication of "skin in the game". + /// + /// This is the value that will always exist in the staking ledger of the pool bonded account + /// while all other accounts leave. + #[pallet::storage] + pub type MinCreateBond = StorageValue<_, BalanceOf, ValueQuery>; + + /// Maximum number of nomination pools that can exist. If `None`, then an unbounded number of + /// pools can exist. + #[pallet::storage] + pub type MaxPools = StorageValue<_, u32, OptionQuery>; + + /// Maximum number of members that can exist in the system. If `None`, then the count + /// members are not bound on a system wide basis. + #[pallet::storage] + pub type MaxPoolMembers = StorageValue<_, u32, OptionQuery>; + + /// Maximum number of members that may belong to pool. If `None`, then the count of + /// members is not bound on a per pool basis. + #[pallet::storage] + pub type MaxPoolMembersPerPool = StorageValue<_, u32, OptionQuery>; + + /// Active members. + #[pallet::storage] + pub type PoolMembers = + CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember>; + + /// Storage for bonded pools. + // To get or insert a pool see [`BondedPool::get`] and [`BondedPool::put`] + #[pallet::storage] + pub type BondedPools = + CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner>; + + /// Reward pools. This is where there rewards for each pool accumulate. When a members payout + /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools account. + #[pallet::storage] + pub type RewardPools = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; + + /// Groups of unbonding pools. Each group of unbonding pools belongs to a bonded pool, + /// hence the name sub-pools. Keyed by the bonded pools account. + #[pallet::storage] + pub type SubPoolsStorage = CountedStorageMap<_, Twox64Concat, PoolId, SubPools>; + + /// Metadata for the pool. + #[pallet::storage] + pub type Metadata = + CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec, ValueQuery>; + + /// Ever increasing number of all pools created so far. + #[pallet::storage] + pub type LastPoolId = StorageValue<_, u32, ValueQuery>; + + /// A reverse lookup from the pool's account id to its id. + /// + /// This is only used for slashing. In all other instances, the pool id is used, and the + /// accounts are deterministically derived from it. + #[pallet::storage] + pub type ReversePoolIdLookup = + CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub min_join_bond: BalanceOf, + pub min_create_bond: BalanceOf, + pub max_pools: Option, + pub max_members_per_pool: Option, + pub max_members: Option, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { + min_join_bond: Zero::zero(), + min_create_bond: Zero::zero(), + max_pools: Some(16), + max_members_per_pool: Some(32), + max_members: Some(16 * 32), + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + MinJoinBond::::put(self.min_join_bond); + MinCreateBond::::put(self.min_create_bond); + if let Some(max_pools) = self.max_pools { + MaxPools::::put(max_pools); + } + if let Some(max_members_per_pool) = self.max_members_per_pool { + MaxPoolMembersPerPool::::put(max_members_per_pool); + } + if let Some(max_members) = self.max_members { + MaxPoolMembers::::put(max_members); + } + } + } + + /// Events of this pallet. + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// A pool has been created. + Created { depositor: T::AccountId, pool_id: PoolId }, + /// A member has became bonded in a pool. + Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf, joined: bool }, + /// A payout has been made to a member. + PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf }, + /// A member has unbonded from their pool. + /// + /// - `balance` is the corresponding balance of the number of points that has been + /// requested to be unbonded (the argument of the `unbond` transaction) from the bonded + /// pool. + /// - `points` is the number of points that are issued as a result of `balance` being + /// dissolved into the corresponding unbonding pool. + /// - `era` is the era in which the balance will be unbonded. + /// In the absence of slashing, these values will match. In the presence of slashing, the + /// number of points that are issued in the unbonding pool will be less than the amount + /// requested to be unbonded. + Unbonded { + member: T::AccountId, + pool_id: PoolId, + balance: BalanceOf, + points: BalanceOf, + era: EraIndex, + }, + /// A member has withdrawn from their pool. + /// + /// The given number of `points` have been dissolved in return of `balance`. + /// + /// Similar to `Unbonded` event, in the absence of slashing, the ratio of point to balance + /// will be 1. + Withdrawn { + member: T::AccountId, + pool_id: PoolId, + balance: BalanceOf, + points: BalanceOf, + }, + /// A pool has been destroyed. + Destroyed { pool_id: PoolId }, + /// The state of a pool has changed + StateChanged { pool_id: PoolId, new_state: PoolState }, + /// A member has been removed from a pool. + /// + /// The removal can be voluntary (withdrawn all unbonded funds) or involuntary (kicked). + MemberRemoved { pool_id: PoolId, member: T::AccountId }, + /// The roles of a pool have been updated to the given new roles. Note that the depositor + /// can never change. + RolesUpdated { + root: Option, + state_toggler: Option, + nominator: Option, + }, + /// The active balance of pool `pool_id` has been slashed to `balance`. + PoolSlashed { pool_id: PoolId, balance: BalanceOf }, + /// The unbond pool at `era` of pool `pool_id` has been slashed to `balance`. + UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf }, + } + + #[pallet::error] + #[cfg_attr(test, derive(PartialEq))] + pub enum Error { + /// A (bonded) pool id does not exist. + PoolNotFound, + /// An account is not a member. + PoolMemberNotFound, + /// A reward pool does not exist. In all cases this is a system logic error. + RewardPoolNotFound, + /// A sub pool does not exist. + SubPoolsNotFound, + /// An account is already delegating in another pool. An account may only belong to one + /// pool at a time. + AccountBelongsToOtherPool, + /// The member is fully unbonded (and thus cannot access the bonded and reward pool + /// anymore to, for example, collect rewards). + FullyUnbonding, + /// The member cannot unbond further chunks due to reaching the limit. + MaxUnbondingLimit, + /// None of the funds can be withdrawn yet because the bonding duration has not passed. + CannotWithdrawAny, + /// The amount does not meet the minimum bond to either join or create a pool. + /// + /// The depositor can never unbond to a value less than + /// `Pallet::depositor_min_bond`. The caller does not have nominating + /// permissions for the pool. Members can never unbond to a value below `MinJoinBond`. + MinimumBondNotMet, + /// The transaction could not be executed due to overflow risk for the pool. + OverflowRisk, + /// A pool must be in [`PoolState::Destroying`] in order for the depositor to unbond or for + /// other members to be permissionlessly unbonded. + NotDestroying, + /// The caller does not have nominating permissions for the pool. + NotNominator, + /// Either a) the caller cannot make a valid kick or b) the pool is not destroying. + NotKickerOrDestroying, + /// The pool is not open to join + NotOpen, + /// The system is maxed out on pools. + MaxPools, + /// Too many members in the pool or system. + MaxPoolMembers, + /// The pools state cannot be changed. + CanNotChangeState, + /// The caller does not have adequate permissions. + DoesNotHavePermission, + /// Metadata exceeds [`Config::MaxMetadataLen`] + MetadataExceedsMaxLen, + /// Some error occurred that should never happen. This should be reported to the + /// maintainers. + Defensive(DefensiveError), + /// Partial unbonding now allowed permissionlessly. + PartialUnbondNotAllowedPermissionlessly, + /// Pool id currently in use. + PoolIdInUse, + /// Pool id provided is not correct/usable. + InvalidPoolId, + } + + #[derive(Encode, Decode, PartialEq, TypeInfo, frame_support::PalletError, RuntimeDebug)] + pub enum DefensiveError { + /// There isn't enough space in the unbond pool. + NotEnoughSpaceInUnbondPool, + /// A (bonded) pool id does not exist. + PoolNotFound, + /// A reward pool does not exist. In all cases this is a system logic error. + RewardPoolNotFound, + /// A sub pool does not exist. + SubPoolsNotFound, + /// The bonded account should only be killed by the staking system when the depositor is + /// withdrawing + BondedStashKilledPrematurely, + } + + impl From for Error { + fn from(e: DefensiveError) -> Error { + Error::::Defensive(e) + } + } + + #[pallet::call] + impl Pallet { + /// Stake funds with a pool. The amount to bond is transferred from the member to the + /// pools account and immediately increases the pools bond. + /// + /// # Note + /// + /// * An account can only be a member of a single pool. + /// * An account cannot join the same pool multiple times. + /// * This call will *not* dust the member account, so the member must have at least + /// `existential deposit + amount` in their account. + /// * Only a pool with [`PoolState::Open`] can be joined + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::join())] + pub fn join( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + pool_id: PoolId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); + // If a member already exists that means they already belong to a pool + ensure!(!PoolMembers::::contains_key(&who), Error::::AccountBelongsToOtherPool); + + let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + bonded_pool.ok_to_join()?; + + let mut reward_pool = RewardPools::::get(pool_id) + .defensive_ok_or::>(DefensiveError::RewardPoolNotFound.into())?; + // IMPORTANT: reward pool records must be updated with the old points. + reward_pool.update_records(pool_id, bonded_pool.points)?; + + bonded_pool.try_inc_members()?; + let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?; + + PoolMembers::insert( + who.clone(), + PoolMember:: { + pool_id, + points: points_issued, + // we just updated `last_known_reward_counter` to the current one in + // `update_recorded`. + last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(), + unbonding_eras: Default::default(), + }, + ); + + Self::deposit_event(Event::::Bonded { + member: who, + pool_id, + bonded: amount, + joined: true, + }); + + bonded_pool.put(); + RewardPools::::insert(pool_id, reward_pool); + + Ok(()) + } + + /// Bond `extra` more funds from `origin` into the pool to which they already belong. + /// + /// Additional funds can come from either the free balance of the account, of from the + /// accumulated rewards, see [`BondExtra`]. + /// + /// Bonding extra funds implies an automatic payout of all pending rewards as well. + // NOTE: this transaction is implemented with the sole purpose of readability and + // correctness, not optimization. We read/write several storage items multiple times instead + // of just once, in the spirit reusing code. + #[pallet::call_index(1)] + #[pallet::weight( + T::WeightInfo::bond_extra_transfer() + .max(T::WeightInfo::bond_extra_reward()) + )] + pub fn bond_extra(origin: OriginFor, extra: BondExtra>) -> DispatchResult { + let who = ensure_signed(origin)?; + let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; + + // payout related stuff: we must claim the payouts, and updated recorded payout data + // before updating the bonded pool points, similar to that of `join` transaction. + reward_pool.update_records(bonded_pool.id, bonded_pool.points)?; + let claimed = + Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; + + let (points_issued, bonded) = match extra { + BondExtra::FreeBalance(amount) => + (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), + BondExtra::Rewards => + (bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed), + }; + + bonded_pool.ok_to_be_open()?; + member.points = + member.points.checked_add(&points_issued).ok_or(Error::::OverflowRisk)?; + + Self::deposit_event(Event::::Bonded { + member: who.clone(), + pool_id: member.pool_id, + bonded, + joined: false, + }); + Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); + + Ok(()) + } + + /// A bonded member can use this to claim their payout based on the rewards that the pool + /// has accumulated since their last claimed payout (OR since joining if this is there first + /// time claiming rewards). The payout will be transferred to the member's account. + /// + /// The member will earn rewards pro rata based on the members stake vs the sum of the + /// members in the pools stake. Rewards do not "expire". + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::claim_payout())] + pub fn claim_payout(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; + + let _ = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; + + Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); + Ok(()) + } + + /// Unbond up to `unbonding_points` of the `member_account`'s funds from the pool. It + /// implicitly collects the rewards one last time, since not doing so would mean some + /// rewards would be forfeited. + /// + /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any + /// account). + /// + /// # Conditions for a permissionless dispatch. + /// + /// * The pool is blocked and the caller is either the root or state-toggler. This is + /// refereed to as a kick. + /// * The pool is destroying and the member is not the depositor. + /// * The pool is destroying, the member is the depositor and no other members are in the + /// pool. + /// + /// ## Conditions for permissioned dispatch (i.e. the caller is also the + /// `member_account`): + /// + /// * The caller is not the depositor. + /// * The caller is the depositor, the pool is destroying and no other members are in the + /// pool. + /// + /// # Note + /// + /// If there are too many unlocking chunks to unbond with the pool account, + /// [`Call::pool_withdraw_unbonded`] can be called to try and minimize unlocking chunks. If + /// there are too many unlocking chunks, the result of this call will likely be the + /// `NoMoreChunks` error from the staking system. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::unbond())] + pub fn unbond( + origin: OriginFor, + member_account: AccountIdLookupOf, + #[pallet::compact] unbonding_points: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let member_account = T::Lookup::lookup(member_account)?; + let (mut member, mut bonded_pool, mut reward_pool) = + Self::get_member_with_pools(&member_account)?; + + bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?; + + // Claim the the payout prior to unbonding. Once the user is unbonding their points no + // longer exist in the bonded pool and thus they can no longer claim their payouts. It + // is not strictly necessary to claim the rewards, but we do it here for UX. + let _ = reward_pool.update_records(bonded_pool.id, bonded_pool.points)?; + let _ = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; + + let current_era = T::Staking::current_era(); + let unbond_era = T::Staking::bonding_duration().saturating_add(current_era); + + // Unbond in the actual underlying nominator. + let unbonding_balance = bonded_pool.dissolve(unbonding_points); + T::Staking::unbond(&bonded_pool.bonded_account(), unbonding_balance)?; + + // Note that we lazily create the unbonding pools here if they don't already exist + let mut sub_pools = SubPoolsStorage::::get(member.pool_id) + .unwrap_or_default() + .maybe_merge_pools(current_era); + + // Update the unbond pool associated with the current era with the unbonded funds. Note + // that we lazily create the unbond pool if it does not yet exist. + if !sub_pools.with_era.contains_key(&unbond_era) { + sub_pools + .with_era + .try_insert(unbond_era, UnbondPool::default()) + // The above call to `maybe_merge_pools` should ensure there is + // always enough space to insert. + .defensive_map_err::, _>(|_| { + DefensiveError::NotEnoughSpaceInUnbondPool.into() + })?; + } + + let points_unbonded = sub_pools + .with_era + .get_mut(&unbond_era) + // The above check ensures the pool exists. + .defensive_ok_or::>(DefensiveError::PoolNotFound.into())? + .issue(unbonding_balance); + + // Try and unbond in the member map. + member.try_unbond(unbonding_points, points_unbonded, unbond_era)?; + + Self::deposit_event(Event::::Unbonded { + member: member_account.clone(), + pool_id: member.pool_id, + points: points_unbonded, + balance: unbonding_balance, + era: unbond_era, + }); + + // Now that we know everything has worked write the items to storage. + SubPoolsStorage::insert(&member.pool_id, sub_pools); + Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool); + + Ok(()) + } + + /// Call `withdraw_unbonded` for the pools account. This call can be made by any account. + /// + /// This is useful if their are too many unlocking chunks to call `unbond`, and some + /// can be cleared by withdrawing. In the case there are too many unlocking chunks, the user + /// would probably see an error like `NoMoreChunks` emitted from the staking system when + /// they attempt to unbond. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))] + pub fn pool_withdraw_unbonded( + origin: OriginFor, + pool_id: PoolId, + num_slashing_spans: u32, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + let pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + // For now we only allow a pool to withdraw unbonded if its not destroying. If the pool + // is destroying then `withdraw_unbonded` can be used. + ensure!(pool.state != PoolState::Destroying, Error::::NotDestroying); + T::Staking::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?; + Ok(()) + } + + /// Withdraw unbonded funds from `member_account`. If no bonded funds can be unbonded, an + /// error is returned. + /// + /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any + /// account). + /// + /// # Conditions for a permissionless dispatch + /// + /// * The pool is in destroy mode and the target is not the depositor. + /// * The target is the depositor and they are the only member in the sub pools. + /// * The pool is blocked and the caller is either the root or state-toggler. + /// + /// # Conditions for permissioned dispatch + /// + /// * The caller is the target and they are not the depositor. + /// + /// # Note + /// + /// If the target is the depositor, the pool will be destroyed. + #[pallet::call_index(5)] + #[pallet::weight( + T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans) + )] + pub fn withdraw_unbonded( + origin: OriginFor, + member_account: AccountIdLookupOf, + num_slashing_spans: u32, + ) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + let member_account = T::Lookup::lookup(member_account)?; + let mut member = + PoolMembers::::get(&member_account).ok_or(Error::::PoolMemberNotFound)?; + let current_era = T::Staking::current_era(); + + let bonded_pool = BondedPool::::get(member.pool_id) + .defensive_ok_or::>(DefensiveError::PoolNotFound.into())?; + let mut sub_pools = + SubPoolsStorage::::get(member.pool_id).ok_or(Error::::SubPoolsNotFound)?; + + bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?; + + // NOTE: must do this after we have done the `ok_to_withdraw_unbonded_other_with` check. + let withdrawn_points = member.withdraw_unlocked(current_era); + ensure!(!withdrawn_points.is_empty(), Error::::CannotWithdrawAny); + + // Before calculating the `balance_to_unbond`, we call withdraw unbonded to ensure the + // `transferrable_balance` is correct. + let stash_killed = + T::Staking::withdraw_unbonded(bonded_pool.bonded_account(), num_slashing_spans)?; + + // defensive-only: the depositor puts enough funds into the stash so that it will only + // be destroyed when they are leaving. + ensure!( + !stash_killed || caller == bonded_pool.roles.depositor, + Error::::Defensive(DefensiveError::BondedStashKilledPrematurely) + ); + + let mut sum_unlocked_points: BalanceOf = Zero::zero(); + let balance_to_unbond = withdrawn_points + .iter() + .fold(BalanceOf::::zero(), |accumulator, (era, unlocked_points)| { + sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points); + if let Some(era_pool) = sub_pools.with_era.get_mut(&era) { + let balance_to_unbond = era_pool.dissolve(*unlocked_points); + if era_pool.points.is_zero() { + sub_pools.with_era.remove(&era); + } + accumulator.saturating_add(balance_to_unbond) + } else { + // A pool does not belong to this era, so it must have been merged to the + // era-less pool. + accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points)) + } + }) + // A call to this transaction may cause the pool's stash to get dusted. If this + // happens before the last member has withdrawn, then all subsequent withdraws will + // be 0. However the unbond pools do no get updated to reflect this. In the + // aforementioned scenario, this check ensures we don't try to withdraw funds that + // don't exist. This check is also defensive in cases where the unbond pool does not + // update its balance (e.g. a bug in the slashing hook.) We gracefully proceed in + // order to ensure members can leave the pool and it can be destroyed. + .min(bonded_pool.transferrable_balance()); + + T::Currency::transfer( + &bonded_pool.bonded_account(), + &member_account, + balance_to_unbond, + ExistenceRequirement::AllowDeath, + ) + .defensive()?; + + Self::deposit_event(Event::::Withdrawn { + member: member_account.clone(), + pool_id: member.pool_id, + points: sum_unlocked_points, + balance: balance_to_unbond, + }); + + let post_info_weight = if member.total_points().is_zero() { + // member being reaped. + PoolMembers::::remove(&member_account); + Self::deposit_event(Event::::MemberRemoved { + pool_id: member.pool_id, + member: member_account.clone(), + }); + + if member_account == bonded_pool.roles.depositor { + Pallet::::dissolve_pool(bonded_pool); + None + } else { + bonded_pool.dec_members().put(); + SubPoolsStorage::::insert(&member.pool_id, sub_pools); + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + } + } else { + // we certainly don't need to delete any pools, because no one is being removed. + SubPoolsStorage::::insert(&member.pool_id, sub_pools); + PoolMembers::::insert(&member_account, member); + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + }; + + Ok(post_info_weight.into()) + } + + /// Create a new delegation pool. + /// + /// # Arguments + /// + /// * `amount` - The amount of funds to delegate to the pool. This also acts of a sort of + /// deposit since the pools creator cannot fully unbond funds until the pool is being + /// destroyed. + /// * `index` - A disambiguation index for creating the account. Likely only useful when + /// creating multiple pools in the same extrinsic. + /// * `root` - The account to set as [`PoolRoles::root`]. + /// * `nominator` - The account to set as the [`PoolRoles::nominator`]. + /// * `state_toggler` - The account to set as the [`PoolRoles::state_toggler`]. + /// + /// # Note + /// + /// In addition to `amount`, the caller will transfer the existential deposit; so the caller + /// needs at have at least `amount + existential_deposit` transferrable. + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::create())] + pub fn create( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + root: AccountIdLookupOf, + nominator: AccountIdLookupOf, + state_toggler: AccountIdLookupOf, + ) -> DispatchResult { + let depositor = ensure_signed(origin)?; + + let pool_id = LastPoolId::::try_mutate::<_, Error, _>(|id| { + *id = id.checked_add(1).ok_or(Error::::OverflowRisk)?; + Ok(*id) + })?; + + Self::do_create(depositor, amount, root, nominator, state_toggler, pool_id) + } + + /// Create a new delegation pool with a previously used pool id + /// + /// # Arguments + /// + /// same as `create` with the inclusion of + /// * `pool_id` - `A valid PoolId. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::create())] + pub fn create_with_pool_id( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + root: AccountIdLookupOf, + nominator: AccountIdLookupOf, + state_toggler: AccountIdLookupOf, + pool_id: PoolId, + ) -> DispatchResult { + let depositor = ensure_signed(origin)?; + + ensure!(!BondedPools::::contains_key(pool_id), Error::::PoolIdInUse); + ensure!(pool_id < LastPoolId::::get(), Error::::InvalidPoolId); + + Self::do_create(depositor, amount, root, nominator, state_toggler, pool_id) + } + + /// Nominate on behalf of the pool. + /// + /// The dispatch origin of this call must be signed by the pool nominator or the pool + /// root role. + /// + /// This directly forward the call to the staking pallet, on behalf of the pool bonded + /// account. + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))] + pub fn nominate( + origin: OriginFor, + pool_id: PoolId, + validators: Vec, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); + T::Staking::nominate(&bonded_pool.bonded_account(), validators) + } + + /// Set a new state for the pool. + /// + /// If a pool is already in the `Destroying` state, then under no condition can its state + /// change again. + /// + /// The dispatch origin of this call must be either: + /// + /// 1. signed by the state toggler, or the root role of the pool, + /// 2. if the pool conditions to be open are NOT met (as described by `ok_to_be_open`), and + /// then the state of the pool can be permissionlessly changed to `Destroying`. + #[pallet::call_index(9)] + #[pallet::weight(T::WeightInfo::set_state())] + pub fn set_state( + origin: OriginFor, + pool_id: PoolId, + state: PoolState, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.state != PoolState::Destroying, Error::::CanNotChangeState); + + if bonded_pool.can_toggle_state(&who) { + bonded_pool.set_state(state); + } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying { + // If the pool has bad properties, then anyone can set it as destroying + bonded_pool.set_state(PoolState::Destroying); + } else { + Err(Error::::CanNotChangeState)?; + } + + bonded_pool.put(); + + Ok(()) + } + + /// Set a new metadata for the pool. + /// + /// The dispatch origin of this call must be signed by the state toggler, or the root role + /// of the pool. + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))] + pub fn set_metadata( + origin: OriginFor, + pool_id: PoolId, + metadata: Vec, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let metadata: BoundedVec<_, _> = + metadata.try_into().map_err(|_| Error::::MetadataExceedsMaxLen)?; + ensure!( + BondedPool::::get(pool_id) + .ok_or(Error::::PoolNotFound)? + .can_set_metadata(&who), + Error::::DoesNotHavePermission + ); + + Metadata::::mutate(pool_id, |pool_meta| *pool_meta = metadata); + + Ok(()) + } + + /// Update configurations for the nomination pools. The origin for this call must be + /// Root. + /// + /// # Arguments + /// + /// * `min_join_bond` - Set [`MinJoinBond`]. + /// * `min_create_bond` - Set [`MinCreateBond`]. + /// * `max_pools` - Set [`MaxPools`]. + /// * `max_members` - Set [`MaxPoolMembers`]. + /// * `max_members_per_pool` - Set [`MaxPoolMembersPerPool`]. + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::set_configs())] + pub fn set_configs( + origin: OriginFor, + min_join_bond: ConfigOp>, + min_create_bond: ConfigOp>, + max_pools: ConfigOp, + max_members: ConfigOp, + max_members_per_pool: ConfigOp, + ) -> DispatchResult { + ensure_root(origin)?; + + macro_rules! config_op_exp { + ($storage:ty, $op:ident) => { + match $op { + ConfigOp::Noop => (), + ConfigOp::Set(v) => <$storage>::put(v), + ConfigOp::Remove => <$storage>::kill(), + } + }; + } + + config_op_exp!(MinJoinBond::, min_join_bond); + config_op_exp!(MinCreateBond::, min_create_bond); + config_op_exp!(MaxPools::, max_pools); + config_op_exp!(MaxPoolMembers::, max_members); + config_op_exp!(MaxPoolMembersPerPool::, max_members_per_pool); + Ok(()) + } + + /// Update the roles of the pool. + /// + /// The root is the only entity that can change any of the roles, including itself, + /// excluding the depositor, who can never change. + /// + /// It emits an event, notifying UIs of the role change. This event is quite relevant to + /// most pool members and they should be informed of changes to pool roles. + #[pallet::call_index(12)] + #[pallet::weight(T::WeightInfo::update_roles())] + pub fn update_roles( + origin: OriginFor, + pool_id: PoolId, + new_root: ConfigOp, + new_nominator: ConfigOp, + new_state_toggler: ConfigOp, + ) -> DispatchResult { + let mut bonded_pool = match ensure_root(origin.clone()) { + Ok(()) => BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?, + Err(frame_support::error::BadOrigin) => { + let who = ensure_signed(origin)?; + let bonded_pool = + BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_update_roles(&who), Error::::DoesNotHavePermission); + bonded_pool + }, + }; + + match new_root { + ConfigOp::Noop => (), + ConfigOp::Remove => bonded_pool.roles.root = None, + ConfigOp::Set(v) => bonded_pool.roles.root = Some(v), + }; + match new_nominator { + ConfigOp::Noop => (), + ConfigOp::Remove => bonded_pool.roles.nominator = None, + ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v), + }; + match new_state_toggler { + ConfigOp::Noop => (), + ConfigOp::Remove => bonded_pool.roles.state_toggler = None, + ConfigOp::Set(v) => bonded_pool.roles.state_toggler = Some(v), + }; + + Self::deposit_event(Event::::RolesUpdated { + root: bonded_pool.roles.root.clone(), + nominator: bonded_pool.roles.nominator.clone(), + state_toggler: bonded_pool.roles.state_toggler.clone(), + }); + + bonded_pool.put(); + Ok(()) + } + + /// Chill on behalf of the pool. + /// + /// The dispatch origin of this call must be signed by the pool nominator or the pool + /// root role, same as [`Pallet::nominate`]. + /// + /// This directly forward the call to the staking pallet, on behalf of the pool bonded + /// account. + #[pallet::call_index(13)] + #[pallet::weight(T::WeightInfo::chill())] + pub fn chill(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); + T::Staking::chill(&bonded_pool.bonded_account()) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), &'static str> { + Self::do_try_state(u8::MAX) + } + + fn integrity_test() { + assert!( + T::MaxPointsToBalance::get() > 0, + "Minimum points to balance ratio must be greater than 0" + ); + assert!( + T::Staking::bonding_duration() < TotalUnbondingPools::::get(), + "There must be more unbonding pools then the bonding duration / + so a slash can be applied to relevant unboding pools. (We assume / + the bonding duration > slash deffer duration.", + ); + } + } +} + +impl Pallet { + /// Returns the pending rewards for the specified `member_account`. + /// + /// In the case of error, `None` is returned. + pub fn pending_rewards(member_account: T::AccountId) -> Option> { + if let Some(pool_member) = PoolMembers::::get(member_account) { + if let Some((reward_pool, bonded_pool)) = RewardPools::::get(pool_member.pool_id) + .zip(BondedPools::::get(pool_member.pool_id)) + { + let current_reward_counter = reward_pool + .current_reward_counter(pool_member.pool_id, bonded_pool.points) + .ok()?; + return pool_member.pending_rewards(current_reward_counter).ok() + } + } + + None + } + + /// The amount of bond that MUST REMAIN IN BONDED in ALL POOLS. + /// + /// It is the responsibility of the depositor to put these funds into the pool initially. Upon + /// unbond, they can never unbond to a value below this amount. + /// + /// It is essentially `max { MinNominatorBond, MinCreateBond, MinJoinBond }`, where the former + /// is coming from the staking pallet and the latter two are configured in this pallet. + pub fn depositor_min_bond() -> BalanceOf { + T::Staking::minimum_nominator_bond() + .max(MinCreateBond::::get()) + .max(MinJoinBond::::get()) + .max(T::Currency::minimum_balance()) + } + /// Remove everything related to the given bonded pool. + /// + /// Metadata and all of the sub-pools are also deleted. All accounts are dusted and the leftover + /// of the reward account is returned to the depositor. + pub fn dissolve_pool(bonded_pool: BondedPool) { + let reward_account = bonded_pool.reward_account(); + let bonded_account = bonded_pool.bonded_account(); + + ReversePoolIdLookup::::remove(&bonded_account); + RewardPools::::remove(bonded_pool.id); + SubPoolsStorage::::remove(bonded_pool.id); + + // Kill accounts from storage by making their balance go below ED. We assume that the + // accounts have no references that would prevent destruction once we get to this point. We + // don't work with the system pallet directly, but + // 1. we drain the reward account and kill it. This account should never have any extra + // consumers anyway. + // 2. the bonded account should become a 'killed stash' in the staking system, and all of + // its consumers removed. + debug_assert_eq!(frame_system::Pallet::::consumers(&reward_account), 0); + debug_assert_eq!(frame_system::Pallet::::consumers(&bonded_account), 0); + debug_assert_eq!( + T::Staking::total_stake(&bonded_account).unwrap_or_default(), + Zero::zero() + ); + + // This shouldn't fail, but if it does we don't really care + let reward_pool_remaining = T::Currency::free_balance(&reward_account); + let _ = T::Currency::transfer( + &reward_account, + &bonded_pool.roles.depositor, + reward_pool_remaining, + ExistenceRequirement::AllowDeath, + ); + + // NOTE: this is purely defensive. + T::Currency::make_free_balance_be(&reward_account, Zero::zero()); + T::Currency::make_free_balance_be(&bonded_pool.bonded_account(), Zero::zero()); + + Self::deposit_event(Event::::Destroyed { pool_id: bonded_pool.id }); + // Remove bonded pool metadata. + Metadata::::remove(bonded_pool.id); + + bonded_pool.remove(); + } + + /// Create the main, bonded account of a pool with the given id. + pub fn create_bonded_account(id: PoolId) -> T::AccountId { + T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id)) + } + + /// Create the reward account of a pool with the given id. + pub fn create_reward_account(id: PoolId) -> T::AccountId { + // NOTE: in order to have a distinction in the test account id type (u128), we put + // account_type first so it does not get truncated out. + T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id)) + } + + /// Get the member with their associated bonded and reward pool. + fn get_member_with_pools( + who: &T::AccountId, + ) -> Result<(PoolMember, BondedPool, RewardPool), Error> { + let member = PoolMembers::::get(&who).ok_or(Error::::PoolMemberNotFound)?; + let bonded_pool = + BondedPool::::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?; + let reward_pool = + RewardPools::::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?; + Ok((member, bonded_pool, reward_pool)) + } + + /// Persist the member with their associated bonded and reward pool into storage, consuming + /// all of them. + fn put_member_with_pools( + member_account: &T::AccountId, + member: PoolMember, + bonded_pool: BondedPool, + reward_pool: RewardPool, + ) { + bonded_pool.put(); + RewardPools::insert(member.pool_id, reward_pool); + PoolMembers::::insert(member_account, member); + } + + /// Calculate the equivalent point of `new_funds` in a pool with `current_balance` and + /// `current_points`. + fn balance_to_point( + current_balance: BalanceOf, + current_points: BalanceOf, + new_funds: BalanceOf, + ) -> BalanceOf { + let u256 = |x| T::BalanceToU256::convert(x); + let balance = |x| T::U256ToBalance::convert(x); + match (current_balance.is_zero(), current_points.is_zero()) { + (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, false) => { + // The pool was totally slashed. + // This is the equivalent of `(current_points / 1) * new_funds`. + new_funds.saturating_mul(current_points) + }, + (false, false) => { + // Equivalent to (current_points / current_balance) * new_funds + balance( + u256(current_points) + .saturating_mul(u256(new_funds)) + // We check for zero above + .div(u256(current_balance)), + ) + }, + } + } + + /// Calculate the equivalent balance of `points` in a pool with `current_balance` and + /// `current_points`. + fn point_to_balance( + current_balance: BalanceOf, + current_points: BalanceOf, + points: BalanceOf, + ) -> BalanceOf { + let u256 = |x| T::BalanceToU256::convert(x); + let balance = |x| T::U256ToBalance::convert(x); + if current_balance.is_zero() || current_points.is_zero() || points.is_zero() { + // There is nothing to unbond + return Zero::zero() + } + + // Equivalent of (current_balance / current_points) * points + balance(u256(current_balance).saturating_mul(u256(points))) + // We check for zero above + .div(current_points) + } + + /// If the member has some rewards, transfer a payout from the reward pool to the member. + // Emits events and potentially modifies pool state if any arithmetic saturates, but does + // not persist any of the mutable inputs to storage. + fn do_reward_payout( + member_account: &T::AccountId, + member: &mut PoolMember, + bonded_pool: &mut BondedPool, + reward_pool: &mut RewardPool, + ) -> Result, DispatchError> { + debug_assert_eq!(member.pool_id, bonded_pool.id); + + // a member who has no skin in the game anymore cannot claim any rewards. + ensure!(!member.active_points().is_zero(), Error::::FullyUnbonding); + + let current_reward_counter = + reward_pool.current_reward_counter(bonded_pool.id, bonded_pool.points)?; + let pending_rewards = member.pending_rewards(current_reward_counter)?; + + if pending_rewards.is_zero() { + return Ok(pending_rewards) + } + + // IFF the reward is non-zero alter the member and reward pool info. + member.last_recorded_reward_counter = current_reward_counter; + reward_pool.register_claimed_reward(pending_rewards); + + // Transfer payout to the member. + T::Currency::transfer( + &bonded_pool.reward_account(), + &member_account, + pending_rewards, + // defensive: the depositor has put existential deposit into the pool and it stays + // untouched, reward account shall not die. + ExistenceRequirement::AllowDeath, + )?; + + Self::deposit_event(Event::::PaidOut { + member: member_account.clone(), + pool_id: member.pool_id, + payout: pending_rewards, + }); + + Ok(pending_rewards) + } + + fn do_create( + who: T::AccountId, + amount: BalanceOf, + root: AccountIdLookupOf, + nominator: AccountIdLookupOf, + state_toggler: AccountIdLookupOf, + pool_id: PoolId, + ) -> DispatchResult { + let root = T::Lookup::lookup(root)?; + let nominator = T::Lookup::lookup(nominator)?; + let state_toggler = T::Lookup::lookup(state_toggler)?; + + ensure!(amount >= Pallet::::depositor_min_bond(), Error::::MinimumBondNotMet); + ensure!( + MaxPools::::get().map_or(true, |max_pools| BondedPools::::count() < max_pools), + Error::::MaxPools + ); + ensure!(!PoolMembers::::contains_key(&who), Error::::AccountBelongsToOtherPool); + let mut bonded_pool = BondedPool::::new( + pool_id, + PoolRoles { + root: Some(root), + nominator: Some(nominator), + state_toggler: Some(state_toggler), + depositor: who.clone(), + }, + ); + + bonded_pool.try_inc_members()?; + let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?; + + T::Currency::transfer( + &who, + &bonded_pool.reward_account(), + T::Currency::minimum_balance(), + ExistenceRequirement::AllowDeath, + )?; + + PoolMembers::::insert( + who.clone(), + PoolMember:: { + pool_id, + points, + last_recorded_reward_counter: Zero::zero(), + unbonding_eras: Default::default(), + }, + ); + RewardPools::::insert( + pool_id, + RewardPool:: { + last_recorded_reward_counter: Zero::zero(), + last_recorded_total_payouts: Zero::zero(), + total_rewards_claimed: Zero::zero(), + }, + ); + ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); + + Self::deposit_event(Event::::Created { depositor: who.clone(), pool_id }); + + Self::deposit_event(Event::::Bonded { + member: who, + pool_id, + bonded: amount, + joined: true, + }); + bonded_pool.put(); + + Ok(()) + } + + /// Ensure the correctness of the state of this pallet. + /// + /// This should be valid before or after each state transition of this pallet. + /// + /// ## Invariants: + /// + /// First, let's consider pools: + /// + /// * `BondedPools` and `RewardPools` must all have the EXACT SAME key-set. + /// * `SubPoolsStorage` must be a subset of the above superset. + /// * `Metadata` keys must be a subset of the above superset. + /// * the count of the above set must be less than `MaxPools`. + /// + /// Then, considering members as well: + /// + /// * each `BondedPool.member_counter` must be: + /// - correct (compared to actual count of member who have `.pool_id` this pool) + /// - less than `MaxPoolMembersPerPool`. + /// * each `member.pool_id` must correspond to an existing `BondedPool.id` (which implies the + /// existence of the reward pool as well). + /// * count of all members must be less than `MaxPoolMembers`. + /// + /// Then, considering unbonding members: + /// + /// for each pool: + /// * sum of the balance that's tracked in all unbonding pools must be the same as the + /// unbonded balance of the main account, as reported by the staking interface. + /// * sum of the balance that's tracked in all unbonding pools, plus the bonded balance of the + /// main account should be less than or qual to the total balance of the main account. + /// + /// ## Sanity check level + /// + /// To cater for tests that want to escape parts of these checks, this function is split into + /// multiple `level`s, where the higher the level, the more checks we performs. So, + /// `try_state(255)` is the strongest sanity check, and `0` performs no checks. + #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))] + pub fn do_try_state(level: u8) -> Result<(), &'static str> { + if level.is_zero() { + return Ok(()) + } + // note: while a bit wacky, since they have the same key, even collecting to vec should + // result in the same set of keys, in the same order. + let bonded_pools = BondedPools::::iter_keys().collect::>(); + let reward_pools = RewardPools::::iter_keys().collect::>(); + assert_eq!(bonded_pools, reward_pools); + + assert!(Metadata::::iter_keys().all(|k| bonded_pools.contains(&k))); + assert!(SubPoolsStorage::::iter_keys().all(|k| bonded_pools.contains(&k))); + + assert!(MaxPools::::get().map_or(true, |max| bonded_pools.len() <= (max as usize))); + + for id in reward_pools { + let account = Self::create_reward_account(id); + assert!( + T::Currency::free_balance(&account) >= T::Currency::minimum_balance(), + "reward pool of {id}: {:?} (ed = {:?})", + T::Currency::free_balance(&account), + T::Currency::minimum_balance() + ); + } + + let mut pools_members = BTreeMap::::new(); + let mut pools_members_pending_rewards = BTreeMap::>::new(); + let mut all_members = 0u32; + PoolMembers::::iter().for_each(|(_, d)| { + let bonded_pool = BondedPools::::get(d.pool_id).unwrap(); + assert!(!d.total_points().is_zero(), "no member should have zero points: {:?}", d); + *pools_members.entry(d.pool_id).or_default() += 1; + all_members += 1; + + let reward_pool = RewardPools::::get(d.pool_id).unwrap(); + if !bonded_pool.points.is_zero() { + let current_rc = + reward_pool.current_reward_counter(d.pool_id, bonded_pool.points).unwrap(); + *pools_members_pending_rewards.entry(d.pool_id).or_default() += + d.pending_rewards(current_rc).unwrap(); + } // else this pool has been heavily slashed and cannot have any rewards anymore. + }); + + RewardPools::::iter_keys().for_each(|id| { + // the sum of the pending rewards must be less than the leftover balance. Since the + // reward math rounds down, we might accumulate some dust here. + log!( + trace, + "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}", + id, + pools_members_pending_rewards.get(&id), + RewardPool::::current_balance(id) + ); + assert!( + RewardPool::::current_balance(id) >= + pools_members_pending_rewards.get(&id).map(|x| *x).unwrap_or_default() + ) + }); + + BondedPools::::iter().for_each(|(id, inner)| { + let bonded_pool = BondedPool { id, inner }; + assert_eq!( + pools_members.get(&id).map(|x| *x).unwrap_or_default(), + bonded_pool.member_counter + ); + assert!(MaxPoolMembersPerPool::::get() + .map_or(true, |max| bonded_pool.member_counter <= max)); + + let depositor = PoolMembers::::get(&bonded_pool.roles.depositor).unwrap(); + assert!( + bonded_pool.is_destroying_and_only_depositor(depositor.active_points()) || + depositor.active_points() >= MinCreateBond::::get(), + "depositor must always have MinCreateBond stake in the pool, except for when the \ + pool is being destroyed and the depositor is the last member", + ); + }); + assert!(MaxPoolMembers::::get().map_or(true, |max| all_members <= max)); + + if level <= 1 { + return Ok(()) + } + + for (pool_id, _pool) in BondedPools::::iter() { + let pool_account = Pallet::::create_bonded_account(pool_id); + let subs = SubPoolsStorage::::get(pool_id).unwrap_or_default(); + + let sum_unbonding_balance = subs.sum_unbonding_balance(); + let bonded_balance = T::Staking::active_stake(&pool_account).unwrap_or_default(); + let total_balance = T::Currency::total_balance(&pool_account); + + assert!( + total_balance >= bonded_balance + sum_unbonding_balance, + "faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}", + pool_id, + _pool, + total_balance, + bonded_balance, + sum_unbonding_balance + ); + } + + Ok(()) + } + + /// Fully unbond the shares of `member`, when executed from `origin`. + /// + /// This is useful for backwards compatibility with the majority of tests that only deal with + /// full unbonding, not partial unbonding. + #[cfg(any(feature = "runtime-benchmarks", test))] + pub fn fully_unbond( + origin: frame_system::pallet_prelude::OriginFor, + member: T::AccountId, + ) -> DispatchResult { + let points = PoolMembers::::get(&member).map(|d| d.active_points()).unwrap_or_default(); + let member_lookup = T::Lookup::unlookup(member); + Self::unbond(origin, member_lookup, points) + } +} + +impl OnStakerSlash> for Pallet { + fn on_slash( + pool_account: &T::AccountId, + // Bonded balance is always read directly from staking, therefore we don't need to update + // anything here. + slashed_bonded: BalanceOf, + slashed_unlocking: &BTreeMap>, + ) { + if let Some(pool_id) = ReversePoolIdLookup::::get(pool_account) { + let mut sub_pools = match SubPoolsStorage::::get(pool_id).defensive() { + Some(sub_pools) => sub_pools, + None => return, + }; + for (era, slashed_balance) in slashed_unlocking.iter() { + if let Some(pool) = sub_pools.with_era.get_mut(era) { + pool.balance = *slashed_balance; + Self::deposit_event(Event::::UnbondingPoolSlashed { + era: *era, + pool_id, + balance: *slashed_balance, + }); + } + } + + Self::deposit_event(Event::::PoolSlashed { pool_id, balance: slashed_bonded }); + SubPoolsStorage::::insert(pool_id, sub_pools); + } + } +} diff --git a/frame/nomination-pools/src/migration.rs b/frame/nomination-pools/src/migration.rs new file mode 100644 index 0000000000000..b73141c95f72c --- /dev/null +++ b/frame/nomination-pools/src/migration.rs @@ -0,0 +1,451 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::log; +use frame_support::traits::OnRuntimeUpgrade; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + +pub mod v1 { + use super::*; + + #[derive(Decode)] + pub struct OldPoolRoles { + pub depositor: AccountId, + pub root: AccountId, + pub nominator: AccountId, + pub state_toggler: AccountId, + } + + impl OldPoolRoles { + fn migrate_to_v1(self) -> PoolRoles { + PoolRoles { + depositor: self.depositor, + root: Some(self.root), + nominator: Some(self.nominator), + state_toggler: Some(self.state_toggler), + } + } + } + + #[derive(Decode)] + pub struct OldBondedPoolInner { + pub points: BalanceOf, + pub state: PoolState, + pub member_counter: u32, + pub roles: OldPoolRoles, + } + + impl OldBondedPoolInner { + fn migrate_to_v1(self) -> BondedPoolInner { + BondedPoolInner { + member_counter: self.member_counter, + points: self.points, + state: self.state, + roles: self.roles.migrate_to_v1(), + } + } + } + + /// Trivial migration which makes the roles of each pool optional. + /// + /// Note: The depositor is not optional since he can never change. + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log!( + info, + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + + if current == 1 && onchain == 0 { + // this is safe to execute on any runtime that has a bounded number of pools. + let mut translated = 0u64; + BondedPools::::translate::, _>(|_key, old_value| { + translated.saturating_inc(); + Some(old_value.migrate_to_v1()) + }); + + current.put::>(); + + log!(info, "Upgraded {} pools, storage to version {:?}", translated, current); + + T::DbWeight::get().reads_writes(translated + 1, translated + 1) + } else { + log!(info, "Migration did not executed. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), &'static str> { + // new version must be set. + assert_eq!(Pallet::::on_chain_storage_version(), 1); + Pallet::::try_state(frame_system::Pallet::::block_number())?; + Ok(()) + } + } +} + +pub mod v2 { + use super::*; + use sp_runtime::Perbill; + + #[test] + fn migration_assumption_is_correct() { + // this migrations cleans all the reward accounts to contain exactly ed, and all members + // having no claimable rewards. In this state, all fields of the `RewardPool` and + // `member.last_recorded_reward_counter` are all zero. + use crate::mock::*; + ExtBuilder::default().build_and_execute(|| { + let join = |x| { + Balances::make_free_balance_be(&x, Balances::minimum_balance() + 10); + frame_support::assert_ok!(Pools::join(RuntimeOrigin::signed(x), 10, 1)); + }; + + assert_eq!(BondedPool::::get(1).unwrap().points, 10); + assert_eq!( + RewardPools::::get(1).unwrap(), + RewardPool { ..Default::default() } + ); + assert_eq!( + PoolMembers::::get(10).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + + join(20); + assert_eq!(BondedPool::::get(1).unwrap().points, 20); + assert_eq!( + RewardPools::::get(1).unwrap(), + RewardPool { ..Default::default() } + ); + assert_eq!( + PoolMembers::::get(10).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + assert_eq!( + PoolMembers::::get(20).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + + join(30); + assert_eq!(BondedPool::::get(1).unwrap().points, 30); + assert_eq!( + RewardPools::::get(1).unwrap(), + RewardPool { ..Default::default() } + ); + assert_eq!( + PoolMembers::::get(10).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + assert_eq!( + PoolMembers::::get(20).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + assert_eq!( + PoolMembers::::get(30).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + }); + } + + #[derive(Decode)] + pub struct OldRewardPool { + pub balance: B, + pub total_earnings: B, + pub points: U256, + } + + #[derive(Decode)] + pub struct OldPoolMember { + pub pool_id: PoolId, + pub points: BalanceOf, + pub reward_pool_total_earnings: BalanceOf, + pub unbonding_eras: BoundedBTreeMap, T::MaxUnbonding>, + } + + /// Migrate the pool reward scheme to the new version, as per + /// . + pub struct MigrateToV2(sp_std::marker::PhantomData); + impl MigrateToV2 { + fn run(current: StorageVersion) -> Weight { + let mut reward_pools_translated = 0u64; + let mut members_translated = 0u64; + // just for logging. + let mut total_value_locked = BalanceOf::::zero(); + let mut total_points_locked = BalanceOf::::zero(); + + // store each member of the pool, with their active points. In the process, migrate + // their data as well. + let mut temp_members = BTreeMap::)>>::new(); + PoolMembers::::translate::, _>(|key, old_member| { + let id = old_member.pool_id; + temp_members.entry(id).or_default().push((key, old_member.points)); + + total_points_locked += old_member.points; + members_translated += 1; + Some(PoolMember:: { + last_recorded_reward_counter: Zero::zero(), + pool_id: old_member.pool_id, + points: old_member.points, + unbonding_eras: old_member.unbonding_eras, + }) + }); + + // translate all reward pools. In the process, do the last payout as well. + RewardPools::::translate::>, _>( + |id, _old_reward_pool| { + // each pool should have at least one member. + let members = match temp_members.get(&id) { + Some(x) => x, + None => { + log!(error, "pool {} has no member! deleting it..", id); + return None + }, + }; + let bonded_pool = match BondedPools::::get(id) { + Some(x) => x, + None => { + log!(error, "pool {} has no bonded pool! deleting it..", id); + return None + }, + }; + + let accumulated_reward = RewardPool::::current_balance(id); + let reward_account = Pallet::::create_reward_account(id); + let mut sum_paid_out = BalanceOf::::zero(); + + members + .into_iter() + .filter_map(|(who, points)| { + let bonded_pool = match BondedPool::::get(id) { + Some(x) => x, + None => { + log!(error, "pool {} for member {:?} does not exist!", id, who); + return None + }, + }; + + total_value_locked += bonded_pool.points_to_balance(*points); + let portion = Perbill::from_rational(*points, bonded_pool.points); + let last_claim = portion * accumulated_reward; + + log!( + debug, + "{:?} has {:?} ({:?}) of pool {} with total reward of {:?}", + who, + portion, + last_claim, + id, + accumulated_reward + ); + + if last_claim.is_zero() { + None + } else { + Some((who, last_claim)) + } + }) + .for_each(|(who, last_claim)| { + let outcome = T::Currency::transfer( + &reward_account, + &who, + last_claim, + ExistenceRequirement::KeepAlive, + ); + + if let Err(reason) = outcome { + log!(warn, "last reward claim failed due to {:?}", reason,); + } else { + sum_paid_out = sum_paid_out.saturating_add(last_claim); + } + + Pallet::::deposit_event(Event::::PaidOut { + member: who.clone(), + pool_id: id, + payout: last_claim, + }); + }); + + // this can only be because of rounding down, or because the person we + // wanted to pay their reward to could not accept it (dust). + let leftover = accumulated_reward.saturating_sub(sum_paid_out); + if !leftover.is_zero() { + // pay it all to depositor. + let o = T::Currency::transfer( + &reward_account, + &bonded_pool.roles.depositor, + leftover, + ExistenceRequirement::KeepAlive, + ); + log!(warn, "paying {:?} leftover to the depositor: {:?}", leftover, o); + } + + // finally, migrate the reward pool. + reward_pools_translated += 1; + + Some(RewardPool { + last_recorded_reward_counter: Zero::zero(), + last_recorded_total_payouts: Zero::zero(), + total_rewards_claimed: Zero::zero(), + }) + }, + ); + + log!( + info, + "Upgraded {} members, {} reward pools, TVL {:?} TPL {:?}, storage to version {:?}", + members_translated, + reward_pools_translated, + total_value_locked, + total_points_locked, + current + ); + current.put::>(); + + T::DbWeight::get().reads_writes(members_translated + 1, reward_pools_translated + 1) + } + } + + impl OnRuntimeUpgrade for MigrateToV2 { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log!( + info, + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + + if current == 2 && onchain == 1 { + Self::run(current) + } else { + log!(info, "MigrateToV2 did not executed. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + // all reward accounts must have more than ED. + RewardPools::::iter().for_each(|(id, _)| { + assert!( + T::Currency::free_balance(&Pallet::::create_reward_account(id)) >= + T::Currency::minimum_balance() + ) + }); + + Ok(Vec::new()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), &'static str> { + // new version must be set. + assert_eq!(Pallet::::on_chain_storage_version(), 2); + + // no reward or bonded pool has been skipped. + assert_eq!(RewardPools::::iter().count() as u32, RewardPools::::count()); + assert_eq!(BondedPools::::iter().count() as u32, BondedPools::::count()); + + // all reward pools must have exactly ED in them. This means no reward can be claimed, + // and that setting reward counters all over the board to zero will work henceforth. + RewardPools::::iter().for_each(|(id, _)| { + assert_eq!( + RewardPool::::current_balance(id), + Zero::zero(), + "reward pool({}) balance is {:?}", + id, + RewardPool::::current_balance(id) + ); + }); + + log!(info, "post upgrade hook for MigrateToV2 executed."); + Ok(()) + } + } +} + +pub mod v3 { + use super::*; + + /// This migration removes stale bonded-pool metadata, if any. + pub struct MigrateToV3(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV3 { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log!( + info, + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + + if current > onchain { + let mut metadata_iterated = 0u64; + let mut metadata_removed = 0u64; + Metadata::::iter_keys() + .filter(|id| { + metadata_iterated += 1; + !BondedPools::::contains_key(&id) + }) + .collect::>() + .into_iter() + .for_each(|id| { + metadata_removed += 1; + Metadata::::remove(&id); + }); + current.put::>(); + // metadata iterated + bonded pools read + a storage version read + let total_reads = metadata_iterated * 2 + 1; + // metadata removed + a storage version write + let total_writes = metadata_removed + 1; + T::DbWeight::get().reads_writes(total_reads, total_writes) + } else { + log!(info, "MigrateToV3 should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + ensure!( + Pallet::::current_storage_version() > Pallet::::on_chain_storage_version(), + "the on_chain version is equal or more than the current one" + ); + Ok(Vec::new()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), &'static str> { + ensure!( + Metadata::::iter_keys().all(|id| BondedPools::::contains_key(&id)), + "not all of the stale metadata has been removed" + ); + ensure!(Pallet::::on_chain_storage_version() == 3, "wrong storage version"); + Ok(()) + } + } +} diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs new file mode 100644 index 0000000000000..99d521df3241b --- /dev/null +++ b/frame/nomination-pools/src/mock.rs @@ -0,0 +1,409 @@ +use super::*; +use crate::{self as pools}; +use frame_support::{assert_ok, parameter_types, PalletId}; +use frame_system::RawOrigin; +use sp_runtime::FixedU128; +use sp_staking::Stake; + +pub type BlockNumber = u64; +pub type AccountId = u128; +pub type Balance = u128; +pub type RewardCounter = FixedU128; +// This sneaky little hack allows us to write code exactly as we would do in the pallet in the tests +// as well, e.g. `StorageItem::::get()`. +pub type T = Runtime; + +// Ext builder creates a pool with id 1. +pub fn default_bonded_account() -> AccountId { + Pools::create_bonded_account(1) +} + +// Ext builder creates a pool with id 1. +pub fn default_reward_account() -> AccountId { + Pools::create_reward_account(1) +} + +parameter_types! { + pub static MinJoinBondConfig: Balance = 2; + pub static CurrentEra: EraIndex = 0; + pub static BondingDuration: EraIndex = 3; + pub storage BondedBalanceMap: BTreeMap = Default::default(); + pub storage UnbondingBalanceMap: BTreeMap = Default::default(); + #[derive(Clone, PartialEq)] + pub static MaxUnbonding: u32 = 8; + pub static StakingMinBond: Balance = 10; + pub storage Nominations: Option> = None; +} + +pub struct StakingMock; +impl StakingMock { + pub(crate) fn set_bonded_balance(who: AccountId, bonded: Balance) { + let mut x = BondedBalanceMap::get(); + x.insert(who, bonded); + BondedBalanceMap::set(&x) + } +} + +impl sp_staking::StakingInterface for StakingMock { + type Balance = Balance; + type AccountId = AccountId; + + fn minimum_nominator_bond() -> Self::Balance { + StakingMinBond::get() + } + fn minimum_validator_bond() -> Self::Balance { + StakingMinBond::get() + } + + fn desired_validator_count() -> u32 { + unimplemented!("method currently not used in testing") + } + + fn current_era() -> EraIndex { + CurrentEra::get() + } + + fn bonding_duration() -> EraIndex { + BondingDuration::get() + } + + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { + let mut x = BondedBalanceMap::get(); + x.get_mut(who).map(|v| *v += extra); + BondedBalanceMap::set(&x); + Ok(()) + } + + fn unbond(who: &Self::AccountId, amount: Self::Balance) -> DispatchResult { + let mut x = BondedBalanceMap::get(); + *x.get_mut(who).unwrap() = x.get_mut(who).unwrap().saturating_sub(amount); + BondedBalanceMap::set(&x); + let mut y = UnbondingBalanceMap::get(); + *y.entry(*who).or_insert(Self::Balance::zero()) += amount; + UnbondingBalanceMap::set(&y); + Ok(()) + } + + fn chill(_: &Self::AccountId) -> sp_runtime::DispatchResult { + Ok(()) + } + + fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { + // Simulates removing unlocking chunks and only having the bonded balance locked + let mut x = UnbondingBalanceMap::get(); + x.remove(&who); + UnbondingBalanceMap::set(&x); + + Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty()) + } + + fn bond(stash: &Self::AccountId, value: Self::Balance, _: &Self::AccountId) -> DispatchResult { + StakingMock::set_bonded_balance(*stash, value); + Ok(()) + } + + fn nominate(_: &Self::AccountId, nominations: Vec) -> DispatchResult { + Nominations::set(&Some(nominations)); + Ok(()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn nominations(_: Self::AccountId) -> Option> { + Nominations::get() + } + + fn stash_by_ctrl(_controller: &Self::AccountId) -> Result { + unimplemented!("method currently not used in testing") + } + + fn stake(who: &Self::AccountId) -> Result, DispatchError> { + match ( + UnbondingBalanceMap::get().get(who).map(|v| *v), + BondedBalanceMap::get().get(who).map(|v| *v), + ) { + (None, None) => Err(DispatchError::Other("balance not found")), + (Some(v), None) => Ok(Stake { total: v, active: 0, stash: *who }), + (None, Some(v)) => Ok(Stake { total: v, active: v, stash: *who }), + (Some(a), Some(b)) => Ok(Stake { total: a + b, active: b, stash: *who }), + } + } + + fn election_ongoing() -> bool { + unimplemented!("method currently not used in testing") + } + + fn force_unstake(_who: Self::AccountId) -> sp_runtime::DispatchResult { + unimplemented!("method currently not used in testing") + } + + fn is_exposed_in_era(_who: &Self::AccountId, _era: &EraIndex) -> bool { + unimplemented!("method currently not used in testing") + } + + #[cfg(feature = "runtime-benchmarks")] + fn add_era_stakers( + _current_era: &EraIndex, + _stash: &Self::AccountId, + _exposures: Vec<(Self::AccountId, Self::Balance)>, + ) { + unimplemented!("method currently not used in testing") + } + + #[cfg(feature = "runtime-benchmarks")] + fn set_current_era(_era: EraIndex) { + unimplemented!("method currently not used in testing") + } +} + +impl frame_system::Config for Runtime { + type SS58Prefix = (); + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = BlockNumber; + type RuntimeCall = RuntimeCall; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = sp_runtime::testing::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = (); + type DbWeight = (); + type BlockLength = (); + type BlockWeights = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub static ExistentialDeposit: Balance = 5; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = frame_support::traits::ConstU32<1024>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub static PostUnbondingPoolsWindow: u32 = 2; + pub static MaxMetadataLen: u32 = 2; + pub static CheckLevel: u8 = 255; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); +} +impl pools::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type RewardCounter = RewardCounter; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type Staking = StakingMock; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type PalletId = PoolsPalletId; + type MaxMetadataLen = MaxMetadataLen; + type MaxUnbonding = MaxUnbonding; + type MaxPointsToBalance = frame_support::traits::ConstU8<10>; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Event, Config}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Pools: pools::{Pallet, Call, Storage, Event}, + } +); + +pub struct ExtBuilder { + members: Vec<(AccountId, Balance)>, + max_members: Option, + max_members_per_pool: Option, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { members: Default::default(), max_members: Some(4), max_members_per_pool: Some(3) } + } +} + +#[cfg_attr(feature = "fuzzing", allow(dead_code))] +impl ExtBuilder { + // Add members to pool 0. + pub fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self { + self.members = members; + self + } + + pub fn ed(self, ed: Balance) -> Self { + ExistentialDeposit::set(ed); + self + } + + pub fn min_bond(self, min: Balance) -> Self { + StakingMinBond::set(min); + self + } + + pub fn min_join_bond(self, min: Balance) -> Self { + MinJoinBondConfig::set(min); + self + } + + pub fn with_check(self, level: u8) -> Self { + CheckLevel::set(level); + self + } + + pub fn max_members(mut self, max: Option) -> Self { + self.max_members = max; + self + } + + pub fn max_members_per_pool(mut self, max: Option) -> Self { + self.max_members_per_pool = max; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = + frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let _ = crate::GenesisConfig:: { + min_join_bond: MinJoinBondConfig::get(), + min_create_bond: 2, + max_pools: Some(2), + max_members_per_pool: self.max_members_per_pool, + max_members: self.max_members, + } + .assimilate_storage(&mut storage); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // for events to be deposited. + frame_system::Pallet::::set_block_number(1); + + // make a pool + let amount_to_bond = Pools::depositor_min_bond(); + Balances::make_free_balance_be(&10, amount_to_bond * 5); + assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902)); + assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1])); + let last_pool = LastPoolId::::get(); + for (account_id, bonded) in self.members { + Balances::make_free_balance_be(&account_id, bonded * 2); + assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, last_pool)); + } + }); + + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + Pools::do_try_state(CheckLevel::get()).unwrap(); + }) + } +} + +pub fn unsafe_set_state(pool_id: PoolId, state: PoolState) { + BondedPools::::try_mutate(pool_id, |maybe_bonded_pool| { + maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { + bonded_pool.state = state; + }) + }) + .unwrap() +} + +parameter_types! { + storage PoolsEvents: u32 = 0; + storage BalancesEvents: u32 = 0; +} + +/// All events of this pallet. +pub fn pool_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = PoolsEvents::get(); + PoolsEvents::set(&(events.len() as u32)); + events.into_iter().skip(already_seen as usize).collect() +} + +/// All events of the `Balances` pallet. +pub fn balances_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Balances(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = BalancesEvents::get(); + BalancesEvents::set(&(events.len() as u32)); + events.into_iter().skip(already_seen as usize).collect() +} + +/// Same as `fully_unbond`, in permissioned setting. +pub fn fully_unbond_permissioned(member: AccountId) -> DispatchResult { + let points = PoolMembers::::get(&member) + .map(|d| d.active_points()) + .unwrap_or_default(); + Pools::unbond(RuntimeOrigin::signed(member), member, points) +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn u256_to_balance_convert_works() { + assert_eq!(U256ToBalance::convert(0u32.into()), Zero::zero()); + assert_eq!(U256ToBalance::convert(Balance::max_value().into()), Balance::max_value()) + } + + #[test] + #[should_panic] + fn u256_to_balance_convert_panics_correctly() { + U256ToBalance::convert(U256::from(Balance::max_value()).saturating_add(1u32.into())); + } + + #[test] + fn balance_to_u256_convert_works() { + assert_eq!(BalanceToU256::convert(0u32.into()), U256::zero()); + assert_eq!(BalanceToU256::convert(Balance::max_value()), Balance::max_value().into()) + } +} diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs new file mode 100644 index 0000000000000..7d5d418bbf2c8 --- /dev/null +++ b/frame/nomination-pools/src/tests.rs @@ -0,0 +1,5102 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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::{mock::*, Event}; +use frame_support::{assert_err, assert_noop, assert_ok, assert_storage_noop, bounded_btree_map}; +use pallet_balances::Event as BEvent; +use sp_runtime::traits::Dispatchable; + +macro_rules! unbonding_pools_with_era { + ($($k:expr => $v:expr),* $(,)?) => {{ + use sp_std::iter::{Iterator, IntoIterator}; + let not_bounded: BTreeMap<_, _> = Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*])); + BoundedBTreeMap::, TotalUnbondingPools>::try_from(not_bounded).unwrap() + }}; +} + +macro_rules! member_unbonding_eras { + ($( $any:tt )*) => {{ + let x: BoundedBTreeMap = bounded_btree_map!($( $any )*); + x + }}; +} + +pub const DEFAULT_ROLES: PoolRoles = + PoolRoles { depositor: 10, root: Some(900), nominator: Some(901), state_toggler: Some(902) }; + +#[test] +fn test_setup_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(BondedPools::::count(), 1); + assert_eq!(RewardPools::::count(), 1); + assert_eq!(SubPoolsStorage::::count(), 0); + assert_eq!(PoolMembers::::count(), 1); + assert_eq!(StakingMock::bonding_duration(), 3); + assert!(Metadata::::contains_key(1)); + + let last_pool = LastPoolId::::get(); + assert_eq!( + BondedPool::::get(last_pool).unwrap(), + BondedPool:: { + id: last_pool, + inner: BondedPoolInner { + state: PoolState::Open, + points: 10, + member_counter: 1, + roles: DEFAULT_ROLES + }, + } + ); + assert_eq!( + RewardPools::::get(last_pool).unwrap(), + RewardPool:: { + last_recorded_reward_counter: Zero::zero(), + last_recorded_total_payouts: 0, + total_rewards_claimed: 0 + } + ); + assert_eq!( + PoolMembers::::get(10).unwrap(), + PoolMember:: { pool_id: last_pool, points: 10, ..Default::default() } + ); + + let bonded_account = Pools::create_bonded_account(last_pool); + let reward_account = Pools::create_reward_account(last_pool); + + // the bonded_account should be bonded by the depositor's funds. + assert_eq!(StakingMock::active_stake(&bonded_account).unwrap(), 10); + assert_eq!(StakingMock::total_stake(&bonded_account).unwrap(), 10); + + // but not nominating yet. + assert!(Nominations::get().is_none()); + + // reward account should have an initial ED in it. + assert_eq!(Balances::free_balance(&reward_account), Balances::minimum_balance()); + }) +} + +mod bonded_pool { + use super::*; + #[test] + fn balance_to_point_works() { + ExtBuilder::default().build_and_execute(|| { + let mut bonded_pool = BondedPool:: { + id: 123123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + }; + + // 1 points : 1 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + assert_eq!(bonded_pool.balance_to_point(10), 10); + assert_eq!(bonded_pool.balance_to_point(0), 0); + + // 2 points : 1 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50); + assert_eq!(bonded_pool.balance_to_point(10), 20); + + // 1 points : 2 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 50; + assert_eq!(bonded_pool.balance_to_point(10), 5); + + // 100 points : 0 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + bonded_pool.points = 100; + assert_eq!(bonded_pool.balance_to_point(10), 100 * 10); + + // 0 points : 100 balance + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 0; + assert_eq!(bonded_pool.balance_to_point(10), 10); + + // 10 points : 3 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30); + bonded_pool.points = 100; + assert_eq!(bonded_pool.balance_to_point(10), 33); + + // 2 points : 3 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300); + bonded_pool.points = 200; + assert_eq!(bonded_pool.balance_to_point(10), 6); + + // 4 points : 9 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900); + bonded_pool.points = 400; + assert_eq!(bonded_pool.balance_to_point(90), 40); + }) + } + + #[test] + fn points_to_balance_works() { + ExtBuilder::default().build_and_execute(|| { + // 1 balance : 1 points ratio + let mut bonded_pool = BondedPool:: { + id: 123123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + }; + + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + assert_eq!(bonded_pool.points_to_balance(10), 10); + assert_eq!(bonded_pool.points_to_balance(0), 0); + + // 2 balance : 1 points ratio + bonded_pool.points = 50; + assert_eq!(bonded_pool.points_to_balance(10), 20); + + // 100 balance : 0 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 0; + assert_eq!(bonded_pool.points_to_balance(10), 0); + + // 0 balance : 100 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + bonded_pool.points = 100; + assert_eq!(bonded_pool.points_to_balance(10), 0); + + // 10 balance : 3 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 30; + assert_eq!(bonded_pool.points_to_balance(10), 33); + + // 2 balance : 3 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200); + bonded_pool.points = 300; + assert_eq!(bonded_pool.points_to_balance(10), 6); + + // 4 balance : 9 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400); + bonded_pool.points = 900; + assert_eq!(bonded_pool.points_to_balance(90), 40); + }) + } + + #[test] + fn ok_to_join_with_works() { + ExtBuilder::default().build_and_execute(|| { + let pool = BondedPool:: { + id: 123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + }; + + let max_points_to_balance: u128 = + <::MaxPointsToBalance as Get>::get().into(); + + // Simulate a 100% slashed pool + StakingMock::set_bonded_balance(pool.bonded_account(), 0); + assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); + + // Simulate a slashed pool at `MaxPointsToBalance` + 1 slashed pool + StakingMock::set_bonded_balance( + pool.bonded_account(), + max_points_to_balance.saturating_add(1).into(), + ); + assert_ok!(pool.ok_to_join()); + + // Simulate a slashed pool at `MaxPointsToBalance` + StakingMock::set_bonded_balance(pool.bonded_account(), max_points_to_balance); + assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); + + StakingMock::set_bonded_balance( + pool.bonded_account(), + Balance::MAX / max_points_to_balance, + ); + + // and a sanity check + StakingMock::set_bonded_balance( + pool.bonded_account(), + Balance::MAX / max_points_to_balance - 1, + ); + assert_ok!(pool.ok_to_join()); + }); + } +} + +mod reward_pool { + #[test] + fn current_balance_only_counts_balance_over_existential_deposit() { + use super::*; + + ExtBuilder::default().build_and_execute(|| { + let reward_account = Pools::create_reward_account(2); + + // Given + assert_eq!(Balances::free_balance(&reward_account), 0); + + // Then + assert_eq!(RewardPool::::current_balance(2), 0); + + // Given + Balances::make_free_balance_be(&reward_account, Balances::minimum_balance()); + + // Then + assert_eq!(RewardPool::::current_balance(2), 0); + + // Given + Balances::make_free_balance_be(&reward_account, Balances::minimum_balance() + 1); + + // Then + assert_eq!(RewardPool::::current_balance(2), 1); + }); + } +} + +mod unbond_pool { + use super::*; + + #[test] + fn points_to_issue_works() { + ExtBuilder::default().build_and_execute(|| { + // 1 points : 1 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; + assert_eq!(unbond_pool.balance_to_point(10), 10); + assert_eq!(unbond_pool.balance_to_point(0), 0); + + // 2 points : 1 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 50 }; + assert_eq!(unbond_pool.balance_to_point(10), 20); + + // 1 points : 2 balance ratio + let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; + assert_eq!(unbond_pool.balance_to_point(10), 5); + + // 100 points : 0 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 0 }; + assert_eq!(unbond_pool.balance_to_point(10), 100 * 10); + + // 0 points : 100 balance + let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; + assert_eq!(unbond_pool.balance_to_point(10), 10); + + // 10 points : 3 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 30 }; + assert_eq!(unbond_pool.balance_to_point(10), 33); + + // 2 points : 3 balance ratio + let unbond_pool = UnbondPool:: { points: 200, balance: 300 }; + assert_eq!(unbond_pool.balance_to_point(10), 6); + + // 4 points : 9 balance ratio + let unbond_pool = UnbondPool:: { points: 400, balance: 900 }; + assert_eq!(unbond_pool.balance_to_point(90), 40); + }) + } + + #[test] + fn balance_to_unbond_works() { + // 1 balance : 1 points ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; + assert_eq!(unbond_pool.point_to_balance(10), 10); + assert_eq!(unbond_pool.point_to_balance(0), 0); + + // 1 balance : 2 points ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 50 }; + assert_eq!(unbond_pool.point_to_balance(10), 5); + + // 2 balance : 1 points ratio + let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; + assert_eq!(unbond_pool.point_to_balance(10), 20); + + // 100 balance : 0 points ratio + let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; + assert_eq!(unbond_pool.point_to_balance(10), 0); + + // 0 balance : 100 points ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 0 }; + assert_eq!(unbond_pool.point_to_balance(10), 0); + + // 10 balance : 3 points ratio + let unbond_pool = UnbondPool:: { points: 30, balance: 100 }; + assert_eq!(unbond_pool.point_to_balance(10), 33); + + // 2 balance : 3 points ratio + let unbond_pool = UnbondPool:: { points: 300, balance: 200 }; + assert_eq!(unbond_pool.point_to_balance(10), 6); + + // 4 balance : 9 points ratio + let unbond_pool = UnbondPool:: { points: 900, balance: 400 }; + assert_eq!(unbond_pool.point_to_balance(90), 40); + } +} + +mod sub_pools { + use super::*; + + #[test] + fn maybe_merge_pools_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(TotalUnbondingPools::::get(), 5); + assert_eq!(BondingDuration::get(), 3); + assert_eq!(PostUnbondingPoolsWindow::get(), 2); + + // Given + let mut sub_pool_0 = SubPools:: { + no_era: UnbondPool::::default(), + with_era: unbonding_pools_with_era! { + 0 => UnbondPool:: { points: 10, balance: 10 }, + 1 => UnbondPool:: { points: 10, balance: 10 }, + 2 => UnbondPool:: { points: 20, balance: 20 }, + 3 => UnbondPool:: { points: 30, balance: 30 }, + 4 => UnbondPool:: { points: 40, balance: 40 }, + }, + }; + + // When `current_era < TotalUnbondingPools`, + let sub_pool_1 = sub_pool_0.clone().maybe_merge_pools(0); + + // Then it exits early without modifications + assert_eq!(sub_pool_1, sub_pool_0); + + // When `current_era == TotalUnbondingPools`, + let sub_pool_1 = sub_pool_1.maybe_merge_pools(1); + + // Then it exits early without modifications + assert_eq!(sub_pool_1, sub_pool_0); + + // When `current_era - TotalUnbondingPools == 0`, + let mut sub_pool_1 = sub_pool_1.maybe_merge_pools(2); + + // Then era 0 is merged into the `no_era` pool + sub_pool_0.no_era = sub_pool_0.with_era.remove(&0).unwrap(); + assert_eq!(sub_pool_1, sub_pool_0); + + // Given we have entries for era 1..=5 + sub_pool_1 + .with_era + .try_insert(5, UnbondPool:: { points: 50, balance: 50 }) + .unwrap(); + sub_pool_0 + .with_era + .try_insert(5, UnbondPool:: { points: 50, balance: 50 }) + .unwrap(); + + // When `current_era - TotalUnbondingPools == 1` + let sub_pool_2 = sub_pool_1.maybe_merge_pools(3); + let era_1_pool = sub_pool_0.with_era.remove(&1).unwrap(); + + // Then era 1 is merged into the `no_era` pool + sub_pool_0.no_era.points += era_1_pool.points; + sub_pool_0.no_era.balance += era_1_pool.balance; + assert_eq!(sub_pool_2, sub_pool_0); + + // When `current_era - TotalUnbondingPools == 5`, so all pools with era <= 4 are removed + let sub_pool_3 = sub_pool_2.maybe_merge_pools(7); + + // Then all eras <= 5 are merged into the `no_era` pool + for era in 2..=5 { + let to_merge = sub_pool_0.with_era.remove(&era).unwrap(); + sub_pool_0.no_era.points += to_merge.points; + sub_pool_0.no_era.balance += to_merge.balance; + } + assert_eq!(sub_pool_3, sub_pool_0); + }); + } +} + +mod join { + use super::*; + + #[test] + fn join_works() { + let bonded = |points, member_counter| BondedPool:: { + id: 1, + inner: BondedPoolInner { + state: PoolState::Open, + points, + member_counter, + roles: DEFAULT_ROLES, + }, + }; + ExtBuilder::default().with_check(0).build_and_execute(|| { + // Given + Balances::make_free_balance_be(&11, ExistentialDeposit::get() + 2); + assert!(!PoolMembers::::contains_key(&11)); + + // When + assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1)); + + // Then + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true }, + ] + ); + + assert_eq!( + PoolMembers::::get(&11).unwrap(), + PoolMember:: { pool_id: 1, points: 2, ..Default::default() } + ); + assert_eq!(BondedPool::::get(1).unwrap(), bonded(12, 2)); + + // Given + // The bonded balance is slashed in half + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 6); + + // And + Balances::make_free_balance_be(&12, ExistentialDeposit::get() + 12); + assert!(!PoolMembers::::contains_key(&12)); + + // When + assert_ok!(Pools::join(RuntimeOrigin::signed(12), 12, 1)); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::Bonded { member: 12, pool_id: 1, bonded: 12, joined: true }] + ); + + assert_eq!( + PoolMembers::::get(&12).unwrap(), + PoolMember:: { pool_id: 1, points: 24, ..Default::default() } + ); + assert_eq!(BondedPool::::get(1).unwrap(), bonded(12 + 24, 3)); + }); + } + + #[test] + fn join_errors_correctly() { + ExtBuilder::default().with_check(0).build_and_execute(|| { + // 10 is already part of the default pool created. + assert_eq!(PoolMembers::::get(&10).unwrap().pool_id, 1); + + assert_noop!( + Pools::join(RuntimeOrigin::signed(10), 420, 123), + Error::::AccountBelongsToOtherPool + ); + + assert_noop!( + Pools::join(RuntimeOrigin::signed(11), 420, 123), + Error::::PoolNotFound + ); + + // Force the pools bonded balance to 0, simulating a 100% slash + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 0); + assert_noop!( + Pools::join(RuntimeOrigin::signed(11), 420, 1), + Error::::OverflowRisk + ); + + // Given a mocked bonded pool + BondedPool:: { + id: 123, + inner: BondedPoolInner { + member_counter: 1, + state: PoolState::Open, + points: 100, + roles: DEFAULT_ROLES, + }, + } + .put(); + + // and reward pool + RewardPools::::insert(123, RewardPool:: { ..Default::default() }); + + // Force the points:balance ratio to `MaxPointsToBalance` (100/10) + let max_points_to_balance: u128 = + <::MaxPointsToBalance as Get>::get().into(); + + StakingMock::set_bonded_balance( + Pools::create_bonded_account(123), + max_points_to_balance, + ); + assert_noop!( + Pools::join(RuntimeOrigin::signed(11), 420, 123), + Error::::OverflowRisk + ); + + StakingMock::set_bonded_balance( + Pools::create_bonded_account(123), + Balance::MAX / max_points_to_balance, + ); + // Balance needs to be gt Balance::MAX / `MaxPointsToBalance` + assert_noop!( + Pools::join(RuntimeOrigin::signed(11), 5, 123), + pallet_balances::Error::::InsufficientBalance, + ); + + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance); + + // Cannot join a pool that isn't open + unsafe_set_state(123, PoolState::Blocked); + assert_noop!( + Pools::join(RuntimeOrigin::signed(11), max_points_to_balance, 123), + Error::::NotOpen + ); + + unsafe_set_state(123, PoolState::Destroying); + assert_noop!( + Pools::join(RuntimeOrigin::signed(11), max_points_to_balance, 123), + Error::::NotOpen + ); + + // Given + MinJoinBond::::put(100); + + // Then + assert_noop!( + Pools::join(RuntimeOrigin::signed(11), 99, 123), + Error::::MinimumBondNotMet + ); + }); + } + + #[test] + #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))] + #[cfg_attr(not(debug_assertions), should_panic)] + fn join_panics_when_reward_pool_not_found() { + ExtBuilder::default().build_and_execute(|| { + StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 100); + BondedPool:: { + id: 123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + } + .put(); + let _ = Pools::join(RuntimeOrigin::signed(11), 420, 123); + }); + } + + #[test] + fn join_max_member_limits_are_respected() { + ExtBuilder::default().build_and_execute(|| { + // Given + assert_eq!(MaxPoolMembersPerPool::::get(), Some(3)); + for i in 1..3 { + let account = i + 100; + Balances::make_free_balance_be(&account, 100 + Balances::minimum_balance()); + + assert_ok!(Pools::join(RuntimeOrigin::signed(account), 100, 1)); + } + + Balances::make_free_balance_be(&103, 100 + Balances::minimum_balance()); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 101, pool_id: 1, bonded: 100, joined: true }, + Event::Bonded { member: 102, pool_id: 1, bonded: 100, joined: true } + ] + ); + + assert_noop!( + Pools::join(RuntimeOrigin::signed(103), 100, 1), + Error::::MaxPoolMembers + ); + + // Given + assert_eq!(PoolMembers::::count(), 3); + assert_eq!(MaxPoolMembers::::get(), Some(4)); + + Balances::make_free_balance_be(&104, 100 + Balances::minimum_balance()); + assert_ok!(Pools::create(RuntimeOrigin::signed(104), 100, 104, 104, 104)); + + let pool_account = BondedPools::::iter() + .find(|(_, bonded_pool)| bonded_pool.roles.depositor == 104) + .map(|(pool_account, _)| pool_account) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 104, pool_id: 2 }, + Event::Bonded { member: 104, pool_id: 2, bonded: 100, joined: true } + ] + ); + + assert_noop!( + Pools::join(RuntimeOrigin::signed(103), 100, pool_account), + Error::::MaxPoolMembers + ); + }); + } +} + +mod claim_payout { + use super::*; + + fn del(points: Balance, last_recorded_reward_counter: u128) -> PoolMember { + PoolMember { + pool_id: 1, + points, + last_recorded_reward_counter: last_recorded_reward_counter.into(), + unbonding_eras: Default::default(), + } + } + + fn del_float(points: Balance, last_recorded_reward_counter: f64) -> PoolMember { + PoolMember { + pool_id: 1, + points, + last_recorded_reward_counter: RewardCounter::from_float(last_recorded_reward_counter), + unbonding_eras: Default::default(), + } + } + + fn rew( + last_recorded_reward_counter: u128, + last_recorded_total_payouts: Balance, + total_rewards_claimed: Balance, + ) -> RewardPool { + RewardPool { + last_recorded_reward_counter: last_recorded_reward_counter.into(), + last_recorded_total_payouts, + total_rewards_claimed, + } + } + + #[test] + fn claim_payout_works() { + ExtBuilder::default() + .add_members(vec![(40, 40), (50, 50)]) + .build_and_execute(|| { + // Given each member currently has a free balance of + Balances::make_free_balance_be(&10, 0); + Balances::make_free_balance_be(&40, 0); + Balances::make_free_balance_be(&50, 0); + let ed = Balances::minimum_balance(); + + // and the reward pool has earned 100 in rewards + assert_eq!(Balances::free_balance(default_reward_account()), ed); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100)); + + let _ = pool_events_since_last_call(); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 },] + ); + // last recorded reward counter at the time of this member's payout is 1 + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 1)); + // pool's 'last_recorded_reward_counter' and 'last_recorded_total_payouts' don't + // really change unless if someone bonds/unbonds. + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 10)); + assert_eq!(Balances::free_balance(&10), 10); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 90); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(40))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }] + ); + assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 1)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 50)); + assert_eq!(Balances::free_balance(&40), 40); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 50); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(50))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + ); + assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 1)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); + assert_eq!(Balances::free_balance(&50), 50); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); + + // Given the reward pool has some new rewards + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + ); + assert_eq!(PoolMembers::::get(10).unwrap(), del_float(10, 1.5)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 105)); + assert_eq!(Balances::free_balance(&10), 10 + 5); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 45); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(40))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }] + ); + assert_eq!(PoolMembers::::get(40).unwrap(), del_float(40, 1.5)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 125)); + assert_eq!(Balances::free_balance(&40), 40 + 20); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); + + // Given del 50 hasn't claimed and the reward pools has just earned 50 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(50))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + ); + assert_eq!(PoolMembers::::get(50).unwrap(), del_float(50, 2.0)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 175)); + assert_eq!(Balances::free_balance(&50), 50 + 50); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + ); + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 2)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 180)); + assert_eq!(Balances::free_balance(&10), 15 + 5); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20); + + // Given del 40 hasn't claimed and the reward pool has just earned 400 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }] + ); + + // We expect a payout of 40 + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 6)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 220)); + assert_eq!(Balances::free_balance(&10), 20 + 40); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 380); + + // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 2 }] + ); + assert_eq!(PoolMembers::::get(10).unwrap(), del_float(10, 6.2)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 222)); + assert_eq!(Balances::free_balance(&10), 60 + 2); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 398); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(40))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 188 }] + ); + assert_eq!(PoolMembers::::get(40).unwrap(), del_float(40, 6.2)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 410)); + assert_eq!(Balances::free_balance(&40), 60 + 188); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 210); + + // When + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(50))); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 210 }] + ); + assert_eq!(PoolMembers::::get(50).unwrap(), del_float(50, 6.2)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 620)); + assert_eq!(Balances::free_balance(&50), 100 + 210); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); + }); + } + + #[test] + fn reward_payout_errors_if_a_member_is_fully_unbonding() { + ExtBuilder::default().add_members(vec![(11, 11)]).build_and_execute(|| { + // fully unbond the member. + assert_ok!(fully_unbond_permissioned(11)); + + assert_noop!( + Pools::claim_payout(RuntimeOrigin::signed(11)), + Error::::FullyUnbonding + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 11, pool_id: 1, bonded: 11, joined: true }, + Event::Unbonded { member: 11, pool_id: 1, points: 11, balance: 11, era: 3 } + ] + ); + }); + } + + #[test] + fn do_reward_payout_works_with_a_pool_of_1() { + let del = |last_recorded_reward_counter| del_float(10, last_recorded_reward_counter); + + ExtBuilder::default().build_and_execute(|| { + let (mut member, mut bonded_pool, mut reward_pool) = + Pools::get_member_with_pools(&10).unwrap(); + let ed = Balances::minimum_balance(); + + let payout = + Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 0); + assert_eq!(member, del(0.0)); + assert_eq!(reward_pool, rew(0, 0, 0)); + + // Given the pool has earned some rewards for the first time + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 5)); + + // When + let payout = + Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 5 } + ] + ); + assert_eq!(payout, 5); + assert_eq!(reward_pool, rew(0, 0, 5)); + assert_eq!(member, del(0.5)); + + // Given the pool has earned rewards again + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10)); + + // When + let payout = + Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }] + ); + assert_eq!(payout, 10); + assert_eq!(reward_pool, rew(0, 0, 15)); + assert_eq!(member, del(1.5)); + + // Given the pool has earned no new rewards + Balances::make_free_balance_be(&default_reward_account(), ed + 0); + + // When + let payout = + Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(pool_events_since_last_call(), vec![]); + assert_eq!(payout, 0); + assert_eq!(reward_pool, rew(0, 0, 15)); + assert_eq!(member, del(1.5)); + }); + } + + #[test] + fn do_reward_payout_works_with_a_pool_of_3() { + ExtBuilder::default() + .add_members(vec![(40, 40), (50, 50)]) + .build_and_execute(|| { + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); + + let mut del_10 = PoolMembers::::get(10).unwrap(); + let mut del_40 = PoolMembers::::get(40).unwrap(); + let mut del_50 = PoolMembers::::get(50).unwrap(); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 50, pool_id: 1, bonded: 50, joined: true } + ] + ); + + // Given we have a total of 100 points split among the members + assert_eq!(del_50.points + del_40.points + del_10.points, 100); + assert_eq!(bonded_pool.points, 100); + + // and the reward pool has earned 100 in rewards + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100)); + + // When + let payout = + Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }] + ); + assert_eq!(payout, 10); + assert_eq!(del_10, del(10, 1)); + assert_eq!(reward_pool, rew(0, 0, 10)); + + // When + let payout = + Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }] + ); + assert_eq!(payout, 40); + assert_eq!(del_40, del(40, 1)); + assert_eq!(reward_pool, rew(0, 0, 50)); + + // When + let payout = + Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + ); + assert_eq!(payout, 50); + assert_eq!(del_50, del(50, 1)); + assert_eq!(reward_pool, rew(0, 0, 100)); + + // Given the reward pool has some new rewards + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + + // When + let payout = + Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + ); + assert_eq!(payout, 5); + assert_eq!(del_10, del_float(10, 1.5)); + assert_eq!(reward_pool, rew(0, 0, 105)); + + // When + let payout = + Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }] + ); + assert_eq!(payout, 20); + assert_eq!(del_40, del_float(40, 1.5)); + assert_eq!(reward_pool, rew(0, 0, 125)); + + // Given del_50 hasn't claimed and the reward pools has just earned 50 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + + // When + let payout = + Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + ); + assert_eq!(payout, 50); + assert_eq!(del_50, del_float(50, 2.0)); + assert_eq!(reward_pool, rew(0, 0, 175)); + + // When + let payout = + Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + ); + assert_eq!(payout, 5); + assert_eq!(del_10, del_float(10, 2.0)); + assert_eq!(reward_pool, rew(0, 0, 180)); + + // Given del_40 hasn't claimed and the reward pool has just earned 400 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); + + // When + let payout = + Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }] + ); + assert_eq!(payout, 40); + assert_eq!(del_10, del_float(10, 6.0)); + assert_eq!(reward_pool, rew(0, 0, 220)); + + // Given del_40 + del_50 haven't claimed and the reward pool has earned 20 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); + + // When + let payout = + Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 2); + assert_eq!(del_10, del_float(10, 6.2)); + assert_eq!(reward_pool, rew(0, 0, 222)); + + // When + let payout = + Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 188); // 20 (from the 50) + 160 (from the 400) + 8 (from the 20) + assert_eq!(del_40, del_float(40, 6.2)); + assert_eq!(reward_pool, rew(0, 0, 410)); + + // When + let payout = + Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 210); // 200 (from the 400) + 10 (from the 20) + assert_eq!(del_50, del_float(50, 6.2)); + assert_eq!(reward_pool, rew(0, 0, 620)); + }); + } + + #[test] + fn rewards_distribution_is_fair_basic() { + ExtBuilder::default().build_and_execute(|| { + // reward pool by 10. + Balances::mutate_account(&default_reward_account(), |f| f.free += 10).unwrap(); + + // 20 joins afterwards. + Balances::make_free_balance_be(&20, Balances::minimum_balance() + 10); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); + + // reward by another 20 + Balances::mutate_account(&default_reward_account(), |f| f.free += 20).unwrap(); + + // 10 should claim 10 + 10, 20 should claim 20 / 2. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + ] + ); + + // any upcoming rewards are shared equally. + Balances::mutate_account(&default_reward_account(), |f| f.free += 20).unwrap(); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + ] + ); + }); + } + + #[test] + fn rewards_distribution_is_fair_basic_with_fractions() { + // basically checks the case where the amount of rewards is less than the pool shares. for + // this, we have to rely on fixed point arithmetic. + ExtBuilder::default().build_and_execute(|| { + Balances::mutate_account(&default_reward_account(), |f| f.free += 3).unwrap(); + + Balances::make_free_balance_be(&20, Balances::minimum_balance() + 10); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 6).unwrap(); + + // 10 should claim 3, 20 should claim 3 + 3. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 3 + 3 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, + ] + ); + + // any upcoming rewards are shared equally. + Balances::mutate_account(&default_reward_account(), |f| f.free += 8).unwrap(); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 4 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 4 }, + ] + ); + + // uneven upcoming rewards are shared equally, rounded down. + Balances::mutate_account(&default_reward_account(), |f| f.free += 7).unwrap(); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 3 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, + ] + ); + }); + } + + #[test] + fn rewards_distribution_is_fair_3() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + + Balances::make_free_balance_be(&20, ed + 10); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); + + Balances::make_free_balance_be(&30, ed + 10); + assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1)); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + // 10 should claim 10, 20 should claim nothing. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 30 + 100 / 2 + 60 / 3 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 100 / 2 + 60 / 3 }, + Event::PaidOut { member: 30, pool_id: 1, payout: 60 / 3 }, + ] + ); + + // any upcoming rewards are shared equally. + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 30, pool_id: 1, payout: 10 }, + ] + ); + }); + } + + #[test] + fn pending_rewards_per_member_works() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + assert_eq!(Pools::pending_rewards(10), Some(0)); + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + assert_eq!(Pools::pending_rewards(10), Some(30)); + assert_eq!(Pools::pending_rewards(20), None); + + Balances::make_free_balance_be(&20, ed + 10); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); + + assert_eq!(Pools::pending_rewards(10), Some(30)); + assert_eq!(Pools::pending_rewards(20), Some(0)); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); + + assert_eq!(Pools::pending_rewards(10), Some(30 + 50)); + assert_eq!(Pools::pending_rewards(20), Some(50)); + assert_eq!(Pools::pending_rewards(30), None); + + Balances::make_free_balance_be(&30, ed + 10); + assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1)); + + assert_eq!(Pools::pending_rewards(10), Some(30 + 50)); + assert_eq!(Pools::pending_rewards(20), Some(50)); + assert_eq!(Pools::pending_rewards(30), Some(0)); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + assert_eq!(Pools::pending_rewards(10), Some(30 + 50 + 20)); + assert_eq!(Pools::pending_rewards(20), Some(50 + 20)); + assert_eq!(Pools::pending_rewards(30), Some(20)); + + // 10 should claim 10, 20 should claim nothing. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_eq!(Pools::pending_rewards(10), Some(0)); + assert_eq!(Pools::pending_rewards(20), Some(50 + 20)); + assert_eq!(Pools::pending_rewards(30), Some(20)); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + assert_eq!(Pools::pending_rewards(10), Some(0)); + assert_eq!(Pools::pending_rewards(20), Some(0)); + assert_eq!(Pools::pending_rewards(30), Some(20)); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); + assert_eq!(Pools::pending_rewards(10), Some(0)); + assert_eq!(Pools::pending_rewards(20), Some(0)); + assert_eq!(Pools::pending_rewards(30), Some(0)); + }); + } + + #[test] + fn rewards_distribution_is_fair_bond_extra() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::make_free_balance_be(&20, ed + 20); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1)); + Balances::make_free_balance_be(&30, ed + 20); + assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1)); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap(); + + // everyone claims. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 20 }, + Event::PaidOut { member: 30, pool_id: 1, payout: 10 } + ] + ); + + // 30 now bumps itself to be like 20. + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(30), BondExtra::FreeBalance(10))); + + // more rewards come in. + Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: false }, + Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 40 }, + Event::PaidOut { member: 30, pool_id: 1, payout: 40 } + ] + ); + }); + } + + #[test] + fn rewards_distribution_is_fair_unbond() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::make_free_balance_be(&20, ed + 20); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1)); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + + // everyone claims. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 20 } + ] + ); + + // 20 unbonds to be equal to 10 (10 points each). + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); + + // more rewards come in. + Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 }, + Event::PaidOut { member: 10, pool_id: 1, payout: 50 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 50 }, + ] + ); + }); + } + + #[test] + fn unclaimed_reward_is_safe() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::make_free_balance_be(&20, ed + 20); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1)); + Balances::make_free_balance_be(&30, ed + 20); + assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1)); + + // 10 gets 10, 20 gets 20, 30 gets 10 + Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap(); + + // some claim. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 20 } + ] + ); + + // 10 gets 20, 20 gets 40, 30 gets 20 + Balances::mutate_account(&default_reward_account(), |f| f.free += 80).unwrap(); + + // some claim. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 40 } + ] + ); + + // 10 gets 20, 20 gets 40, 30 gets 20 + Balances::mutate_account(&default_reward_account(), |f| f.free += 80).unwrap(); + + // some claim. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 40 } + ] + ); + + // now 30 claims all at once + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 30, pool_id: 1, payout: 10 + 20 + 20 }] + ); + }); + } + + #[test] + fn bond_extra_and_delayed_claim() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::make_free_balance_be(&20, ed + 200); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1)); + + // 10 gets 10, 20 gets 20, 30 gets 10 + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + + // some claim. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 10 } + ] + ); + + // 20 has not claimed yet, more reward comes + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + // and 20 bonds more -- they should not have more share of this reward. + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::FreeBalance(10))); + + // everyone claim. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + // 20 + 40, which means the extra amount they bonded did not impact us. + Event::PaidOut { member: 20, pool_id: 1, payout: 60 }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: false }, + Event::PaidOut { member: 10, pool_id: 1, payout: 20 } + ] + ); + + // but in the next round of rewards, the extra10 they bonded has an impact. + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + // everyone claim. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 15 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 45 } + ] + ); + }); + } + + #[test] + fn create_sets_recorded_data() { + ExtBuilder::default().build_and_execute(|| { + MaxPools::::set(None); + // pool 10 has already been created. + let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap(); + + assert_eq!(reward_pool_10.last_recorded_total_payouts, 0); + assert_eq!(reward_pool_10.total_rewards_claimed, 0); + assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into()); + + assert_eq!(member_10.last_recorded_reward_counter, 0.into()); + + // transfer some reward to pool 1. + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + // create pool 2 + Balances::make_free_balance_be(&20, 100); + assert_ok!(Pools::create(RuntimeOrigin::signed(20), 10, 20, 20, 20)); + + // has no impact -- initial + let (member_20, _, reward_pool_20) = Pools::get_member_with_pools(&20).unwrap(); + + assert_eq!(reward_pool_20.last_recorded_total_payouts, 0); + assert_eq!(reward_pool_20.total_rewards_claimed, 0); + assert_eq!(reward_pool_20.last_recorded_reward_counter, 0.into()); + + assert_eq!(member_20.last_recorded_reward_counter, 0.into()); + + // pre-fund the reward account of pool id 3 with some funds. + Balances::make_free_balance_be(&Pools::create_reward_account(3), 10); + + // create pool 3 + Balances::make_free_balance_be(&30, 100); + assert_ok!(Pools::create(RuntimeOrigin::signed(30), 10, 30, 30, 30)); + + // reward counter is still the same. + let (member_30, _, reward_pool_30) = Pools::get_member_with_pools(&30).unwrap(); + assert_eq!( + Balances::free_balance(&Pools::create_reward_account(3)), + 10 + Balances::minimum_balance() + ); + + assert_eq!(reward_pool_30.last_recorded_total_payouts, 0); + assert_eq!(reward_pool_30.total_rewards_claimed, 0); + assert_eq!(reward_pool_30.last_recorded_reward_counter, 0.into()); + + assert_eq!(member_30.last_recorded_reward_counter, 0.into()); + + // and 30 can claim the reward now. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Created { depositor: 20, pool_id: 2 }, + Event::Bonded { member: 20, pool_id: 2, bonded: 10, joined: true }, + Event::Created { depositor: 30, pool_id: 3 }, + Event::Bonded { member: 30, pool_id: 3, bonded: 10, joined: true }, + Event::PaidOut { member: 30, pool_id: 3, payout: 10 } + ] + ); + }) + } + + #[test] + fn join_updates_recorded_data() { + ExtBuilder::default().build_and_execute(|| { + MaxPoolMembers::::set(None); + MaxPoolMembersPerPool::::set(None); + let join = |x, y| { + Balances::make_free_balance_be(&x, y + Balances::minimum_balance()); + assert_ok!(Pools::join(RuntimeOrigin::signed(x), y, 1)); + }; + + { + let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap(); + + assert_eq!(reward_pool_10.last_recorded_total_payouts, 0); + assert_eq!(reward_pool_10.total_rewards_claimed, 0); + assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into()); + + assert_eq!(member_10.last_recorded_reward_counter, 0.into()); + } + + // someone joins without any rewards being issued. + { + join(20, 10); + let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap(); + // reward counter is 0 both before.. + assert_eq!(member.last_recorded_reward_counter, 0.into()); + assert_eq!(reward_pool.last_recorded_total_payouts, 0); + assert_eq!(reward_pool.last_recorded_reward_counter, 0.into()); + } + + // transfer some reward to pool 1. + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + { + join(30, 10); + let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap(); + assert_eq!(reward_pool.last_recorded_total_payouts, 60); + // explanation: we have a total of 20 points so far (excluding the 10 that just got + // bonded), and 60 unclaimed rewards. each share is then roughly worth of 3 units of + // rewards, thus reward counter is 3. member's reward counter is the same + assert_eq!(member.last_recorded_reward_counter, 3.into()); + assert_eq!(reward_pool.last_recorded_reward_counter, 3.into()); + } + + // someone else joins + { + join(40, 10); + let (member, _, reward_pool) = Pools::get_member_with_pools(&40).unwrap(); + // reward counter does not change since no rewards have came in. + assert_eq!(member.last_recorded_reward_counter, 3.into()); + assert_eq!(reward_pool.last_recorded_reward_counter, 3.into()); + assert_eq!(reward_pool.last_recorded_total_payouts, 60); + } + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 10, joined: true } + ] + ); + }) + } + + #[test] + fn bond_extra_updates_recorded_data() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + MaxPoolMembers::::set(None); + MaxPoolMembersPerPool::::set(None); + + // initial state of pool 1. + { + let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap(); + + assert_eq!(reward_pool_10.last_recorded_total_payouts, 0); + assert_eq!(reward_pool_10.total_rewards_claimed, 0); + assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into()); + + assert_eq!(member_10.last_recorded_reward_counter, 0.into()); + } + + Balances::make_free_balance_be(&10, 100); + Balances::make_free_balance_be(&20, 100); + + // 10 bonds extra without any rewards. + { + assert_ok!(Pools::bond_extra( + RuntimeOrigin::signed(10), + BondExtra::FreeBalance(10) + )); + let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap(); + assert_eq!(member.last_recorded_reward_counter, 0.into()); + assert_eq!(reward_pool.last_recorded_total_payouts, 0); + assert_eq!(reward_pool.last_recorded_reward_counter, 0.into()); + } + + // 10 bonds extra again with some rewards. This reward should be split equally between + // 10 and 20, as they both have equal points now. + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + + { + assert_ok!(Pools::bond_extra( + RuntimeOrigin::signed(10), + BondExtra::FreeBalance(10) + )); + let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap(); + // explanation: before bond_extra takes place, there is 40 points and 30 balance in + // the system, RewardCounter is therefore 7.5 + assert_eq!(member.last_recorded_reward_counter, RewardCounter::from_float(0.75)); + assert_eq!( + reward_pool.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + } + + // 20 bonds extra again, without further rewards. + { + assert_ok!(Pools::bond_extra( + RuntimeOrigin::signed(20), + BondExtra::FreeBalance(10) + )); + let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap(); + assert_eq!(member.last_recorded_reward_counter, RewardCounter::from_float(0.75)); + assert_eq!( + reward_pool.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + } + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, + Event::PaidOut { member: 10, pool_id: 1, payout: 15 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, + Event::PaidOut { member: 20, pool_id: 1, payout: 15 }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: false } + ] + ); + }) + } + + #[test] + fn bond_extra_pending_rewards_works() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + MaxPoolMembers::::set(None); + MaxPoolMembersPerPool::::set(None); + + // pool receives some rewards. + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + System::reset_events(); + + // 10 cashes it out, and bonds it. + { + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap(); + // there is 30 points and 30 reward points in the system RC is 1. + assert_eq!(member.last_recorded_reward_counter, 1.into()); + assert_eq!(reward_pool.total_rewards_claimed, 10); + // these two are not updated -- only updated when the points change. + assert_eq!(reward_pool.last_recorded_total_payouts, 0); + assert_eq!(reward_pool.last_recorded_reward_counter, 0.into()); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }] + ); + } + + // 20 re-bonds it. + { + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::Rewards)); + let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap(); + assert_eq!(member.last_recorded_reward_counter, 1.into()); + assert_eq!(reward_pool.total_rewards_claimed, 30); + // since points change, these two are updated. + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + assert_eq!(reward_pool.last_recorded_reward_counter, 1.into()); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 20, pool_id: 1, payout: 20 }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: false } + ] + ); + } + }) + } + + #[test] + fn unbond_updates_recorded_data() { + ExtBuilder::default() + .add_members(vec![(20, 20), (30, 20)]) + .build_and_execute(|| { + MaxPoolMembers::::set(None); + MaxPoolMembersPerPool::::set(None); + + // initial state of pool 1. + { + let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap(); + + assert_eq!(reward_pool.last_recorded_total_payouts, 0); + assert_eq!(reward_pool.total_rewards_claimed, 0); + assert_eq!(reward_pool.last_recorded_reward_counter, 0.into()); + + assert_eq!(member.last_recorded_reward_counter, 0.into()); + } + + // 20 unbonds without any rewards. + { + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); + let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap(); + assert_eq!(member.last_recorded_reward_counter, 0.into()); + assert_eq!(reward_pool.last_recorded_total_payouts, 0); + assert_eq!(reward_pool.last_recorded_reward_counter, 0.into()); + } + + // some rewards come in. + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + + // and 30 also unbonds half. + { + assert_ok!(Pools::unbond(RuntimeOrigin::signed(30), 30, 10)); + let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap(); + // 30 reward in the system, and 40 points before this unbond to collect it, + // RewardCounter is 3/4. + assert_eq!( + member.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + assert_eq!( + reward_pool.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + } + + // 30 unbonds again, not change this time. + { + assert_ok!(Pools::unbond(RuntimeOrigin::signed(30), 30, 5)); + let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap(); + assert_eq!( + member.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + assert_eq!( + reward_pool.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + } + + // 20 unbonds again, not change this time, just collecting their reward. + { + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5)); + let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap(); + assert_eq!( + member.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + assert_eq!( + reward_pool.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + } + + // trigger 10's reward as well to see all of the payouts. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Bonded { member: 30, pool_id: 1, bonded: 20, joined: true }, + Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 }, + Event::PaidOut { member: 30, pool_id: 1, payout: 15 }, + Event::Unbonded { member: 30, pool_id: 1, balance: 10, points: 10, era: 3 }, + Event::Unbonded { member: 30, pool_id: 1, balance: 5, points: 5, era: 3 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 7 }, + Event::Unbonded { member: 20, pool_id: 1, balance: 5, points: 5, era: 3 }, + Event::PaidOut { member: 10, pool_id: 1, payout: 7 } + ] + ); + }) + } + + #[test] + fn rewards_are_rounded_down_depositor_collects_them() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + // initial balance of 10. + + assert_eq!(Balances::free_balance(&10), 35); + assert_eq!( + Balances::free_balance(&default_reward_account()), + Balances::minimum_balance() + ); + + // some rewards come in. + Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap(); + + // everyone claims + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + // some dust (1) remains in the reward account. + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 13 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 26 } + ] + ); + + // start dismantling the pool. + assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying)); + assert_ok!(fully_unbond_permissioned(20)); + + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); + assert_ok!(fully_unbond_permissioned(10)); + + CurrentEra::set(6); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 3 }, + Event::Withdrawn { member: 20, pool_id: 1, balance: 20, points: 20 }, + Event::MemberRemoved { pool_id: 1, member: 20 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 6 }, + Event::Withdrawn { member: 10, pool_id: 1, balance: 10, points: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::Destroyed { pool_id: 1 } + ] + ); + + assert!(!Metadata::::contains_key(1)); + // original ed + ed put into reward account + reward + bond + dust. + assert_eq!(Balances::free_balance(&10), 35 + 5 + 13 + 10 + 1); + }) + } + + #[test] + fn claim_payout_large_numbers() { + let unit = 10u128.pow(12); // akin to KSM + ExistentialDeposit::set(unit); + StakingMinBond::set(unit * 1000); + + ExtBuilder::default() + .max_members(Some(4)) + .max_members_per_pool(Some(4)) + .add_members(vec![(20, 1500 * unit), (21, 2500 * unit), (22, 5000 * unit)]) + .build_and_execute(|| { + // some rewards come in. + assert_eq!(Balances::free_balance(&default_reward_account()), unit); + Balances::mutate_account(&default_reward_account(), |f| f.free += unit / 1000) + .unwrap(); + + // everyone claims + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(21))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(22))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 1000000000000000, + joined: true + }, + Event::Bonded { + member: 20, + pool_id: 1, + bonded: 1500000000000000, + joined: true + }, + Event::Bonded { + member: 21, + pool_id: 1, + bonded: 2500000000000000, + joined: true + }, + Event::Bonded { + member: 22, + pool_id: 1, + bonded: 5000000000000000, + joined: true + }, + Event::PaidOut { member: 10, pool_id: 1, payout: 100000000 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 150000000 }, + Event::PaidOut { member: 21, pool_id: 1, payout: 250000000 }, + Event::PaidOut { member: 22, pool_id: 1, payout: 500000000 } + ] + ); + }) + } +} + +mod unbond { + use super::*; + + #[test] + fn member_unbond_open() { + // depositor in pool, pool state open + // - member unbond above limit + // - member unbonds to 0 + // - member cannot unbond between within limit and 0 + ExtBuilder::default() + .min_join_bond(10) + .add_members(vec![(20, 20)]) + .build_and_execute(|| { + // can unbond to above limit + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 15); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 5); + + // cannot go to below 10: + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(20), 20, 10), + Error::::MinimumBondNotMet + ); + + // but can go to 0 + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 15)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 20); + }) + } + + #[test] + fn member_kicked() { + // depositor in pool, pool state blocked + // - member cannot be kicked to above limit + // - member cannot be kicked between within limit and 0 + // - member kicked to 0 + ExtBuilder::default() + .min_join_bond(10) + .add_members(vec![(20, 20)]) + .build_and_execute(|| { + unsafe_set_state(1, PoolState::Blocked); + let kicker = DEFAULT_ROLES.state_toggler.unwrap(); + + // cannot be kicked to above the limit. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(kicker), 20, 5), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // cannot go to below 10: + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(kicker), 20, 15), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // but they themselves can do an unbond + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 18); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 2); + + // can be kicked to 0. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(kicker), 20, 18)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 20); + }) + } + + #[test] + fn member_unbond_destroying() { + // depositor in pool, pool state destroying + // - member cannot be permissionlessly unbonded to above limit + // - member cannot be permissionlessly unbonded between within limit and 0 + // - member permissionlessly unbonded to 0 + ExtBuilder::default() + .min_join_bond(10) + .add_members(vec![(20, 20)]) + .build_and_execute(|| { + unsafe_set_state(1, PoolState::Destroying); + let random = 123; + + // cannot be kicked to above the limit. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(random), 20, 5), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // cannot go to below 10: + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(random), 20, 15), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // but they themselves can do an unbond + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 18); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 2); + + // but can go to 0 + assert_ok!(Pools::unbond(RuntimeOrigin::signed(random), 20, 18)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 20); + }) + } + + #[test] + fn depositor_unbond_open() { + // depositor in pool, pool state open + // - depositor unbonds to above limit + // - depositor cannot unbond to below limit or 0 + ExtBuilder::default().min_join_bond(10).build_and_execute(|| { + // give the depositor some extra funds. + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + + // can unbond to above the limit. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 15); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 5); + + // cannot go to below 10: + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 10), + Error::::MinimumBondNotMet + ); + + // cannot go to 0 either. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 15), + Error::::MinimumBondNotMet + ); + }) + } + + #[test] + fn depositor_kick() { + // depositor in pool, pool state blocked + // - depositor can never be kicked. + ExtBuilder::default().min_join_bond(10).build_and_execute(|| { + // give the depositor some extra funds. + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + + // set the stage + unsafe_set_state(1, PoolState::Blocked); + let kicker = DEFAULT_ROLES.state_toggler.unwrap(); + + // cannot be kicked to above limit. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(kicker), 10, 5), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // or below the limit + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(kicker), 10, 15), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // or 0. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(kicker), 10, 20), + Error::::DoesNotHavePermission + ); + + // they themselves cannot do it either + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 20), + Error::::MinimumBondNotMet + ); + }) + } + + #[test] + fn depositor_unbond_destroying_permissionless() { + // depositor can never be permissionlessly unbonded. + ExtBuilder::default().min_join_bond(10).build_and_execute(|| { + // give the depositor some extra funds. + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + + // set the stage + unsafe_set_state(1, PoolState::Destroying); + let random = 123; + + // cannot be kicked to above limit. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(random), 10, 5), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // or below the limit + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(random), 10, 15), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // or 0. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(random), 10, 20), + Error::::DoesNotHavePermission + ); + + // they themselves can do it in this case though. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20)); + }) + } + + #[test] + fn depositor_unbond_destroying_not_last_member() { + // deposit in pool, pool state destroying + // - depositor can never leave if there is another member in the pool. + ExtBuilder::default() + .min_join_bond(10) + .add_members(vec![(20, 20)]) + .build_and_execute(|| { + // give the depositor some extra funds. + assert_ok!(Pools::bond_extra( + RuntimeOrigin::signed(10), + BondExtra::FreeBalance(10) + )); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + + // set the stage + unsafe_set_state(1, PoolState::Destroying); + + // can go above the limit + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 15); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 5); + + // but not below the limit + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 10), + Error::::MinimumBondNotMet + ); + + // and certainly not zero + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 15), + Error::::MinimumBondNotMet + ); + }) + } + + #[test] + fn depositor_unbond_destroying_last_member() { + // deposit in pool, pool state destroying + // - depositor can unbond to above limit always. + // - depositor cannot unbond to below limit if last. + // - depositor can unbond to 0 if last and destroying. + ExtBuilder::default().min_join_bond(10).build_and_execute(|| { + // give the depositor some extra funds. + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + + // set the stage + unsafe_set_state(1, PoolState::Destroying); + + // can unbond to above the limit. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 15); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 5); + + // still cannot go to below limit + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 10), + Error::::MinimumBondNotMet + ); + + // can go to 0 too. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 15)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 20); + }) + } + + #[test] + fn unbond_of_1_works() { + ExtBuilder::default().build_and_execute(|| { + unsafe_set_state(1, PoolState::Destroying); + assert_ok!(fully_unbond_permissioned(10)); + + assert_eq!( + SubPoolsStorage::::get(1).unwrap().with_era, + unbonding_pools_with_era! { 0 + 3 => UnbondPool:: { points: 10, balance: 10 }} + ); + + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + state: PoolState::Destroying, + points: 0, + member_counter: 1, + roles: DEFAULT_ROLES, + } + } + ); + + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); + }); + } + + #[test] + fn unbond_of_3_works() { + ExtBuilder::default() + .add_members(vec![(40, 40), (550, 550)]) + .build_and_execute(|| { + let ed = Balances::minimum_balance(); + // Given a slash from 600 -> 100 + StakingMock::set_bonded_balance(default_bonded_account(), 100); + // and unclaimed rewards of 600. + Balances::make_free_balance_be(&default_reward_account(), ed + 600); + + // When + assert_ok!(fully_unbond_permissioned(40)); + + // Then + assert_eq!( + SubPoolsStorage::::get(1).unwrap().with_era, + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 6, balance: 6 }} + ); + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + state: PoolState::Open, + points: 560, + member_counter: 3, + roles: DEFAULT_ROLES, + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, + Event::PaidOut { member: 40, pool_id: 1, payout: 40 }, + Event::Unbonded { member: 40, pool_id: 1, points: 6, balance: 6, era: 3 } + ] + ); + + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94); + assert_eq!( + PoolMembers::::get(40).unwrap().unbonding_eras, + member_unbonding_eras!(0 + 3 => 6) + ); + assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding + + // When + unsafe_set_state(1, PoolState::Destroying); + assert_ok!(fully_unbond_permissioned(550)); + + // Then + assert_eq!( + SubPoolsStorage::::get(&1).unwrap().with_era, + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 98, balance: 98 }} + ); + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + state: PoolState::Destroying, + points: 10, + member_counter: 3, + roles: DEFAULT_ROLES + } + } + ); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 2); + assert_eq!( + PoolMembers::::get(550).unwrap().unbonding_eras, + member_unbonding_eras!(0 + 3 => 92) + ); + assert_eq!(Balances::free_balance(&550), 550 + 550); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 550, pool_id: 1, payout: 550 }, + Event::Unbonded { + member: 550, + pool_id: 1, + points: 92, + balance: 92, + era: 3 + } + ] + ); + + // When + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 40, 0)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 550, 0)); + assert_ok!(fully_unbond_permissioned(10)); + + // Then + assert_eq!( + SubPoolsStorage::::get(1).unwrap().with_era, + unbonding_pools_with_era! { 6 => UnbondPool { points: 2, balance: 2 }} + ); + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + state: PoolState::Destroying, + points: 0, + member_counter: 1, + roles: DEFAULT_ROLES + } + } + ); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); + + assert_eq!(Balances::free_balance(&550), 550 + 550 + 92); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 40, pool_id: 1, points: 6, balance: 6 }, + Event::MemberRemoved { pool_id: 1, member: 40 }, + Event::Withdrawn { member: 550, pool_id: 1, points: 92, balance: 92 }, + Event::MemberRemoved { pool_id: 1, member: 550 }, + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2, era: 6 } + ] + ); + }); + } + + #[test] + fn unbond_merges_older_pools() { + ExtBuilder::default().with_check(1).build_and_execute(|| { + // Given + assert_eq!(StakingMock::bonding_duration(), 3); + SubPoolsStorage::::insert( + 1, + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 0 + 3 => UnbondPool { balance: 10, points: 100 }, + 1 + 3 => UnbondPool { balance: 20, points: 20 }, + 2 + 3 => UnbondPool { balance: 101, points: 101} + }, + }, + ); + unsafe_set_state(1, PoolState::Destroying); + + // When + let current_era = 1 + TotalUnbondingPools::::get(); + CurrentEra::set(current_era); + + assert_ok!(fully_unbond_permissioned(10)); + + // Then + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: UnbondPool { balance: 10 + 20, points: 100 + 20 }, + with_era: unbonding_pools_with_era! { + 2 + 3 => UnbondPool { balance: 101, points: 101}, + current_era + 3 => UnbondPool { balance: 10, points: 10 }, + }, + }, + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 } + ] + ); + }); + } + + #[test] + fn unbond_kick_works() { + // Kick: the pool is blocked and the caller is either the root or state-toggler. + ExtBuilder::default() + .add_members(vec![(100, 100), (200, 200)]) + .build_and_execute(|| { + // Given + unsafe_set_state(1, PoolState::Blocked); + let bonded_pool = BondedPool::::get(1).unwrap(); + assert_eq!(bonded_pool.roles.root.unwrap(), 900); + assert_eq!(bonded_pool.roles.nominator.unwrap(), 901); + assert_eq!(bonded_pool.roles.state_toggler.unwrap(), 902); + + // When the nominator tries to kick, then its a noop + assert_noop!( + Pools::fully_unbond(RuntimeOrigin::signed(901), 100), + Error::::NotKickerOrDestroying + ); + + // When the root kicks then its ok + assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(900), 100)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, + Event::Unbonded { + member: 100, + pool_id: 1, + points: 100, + balance: 100, + era: 3 + }, + ] + ); + + // When the state toggler kicks then its ok + assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(902), 200)); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { + member: 200, + pool_id: 1, + points: 200, + balance: 200, + era: 3 + }] + ); + + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + roles: DEFAULT_ROLES, + state: PoolState::Blocked, + points: 10, // Only 10 points because 200 + 100 was unbonded + member_counter: 3, + } + } + ); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 0 + 3 => UnbondPool { points: 100 + 200, balance: 100 + 200 } + }, + } + ); + assert_eq!( + *UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(), + 100 + 200 + ); + }); + } + + #[test] + fn unbond_permissionless_works() { + // Scenarios where non-admin accounts can unbond others + ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { + // Given the pool is blocked + unsafe_set_state(1, PoolState::Blocked); + + // A permissionless unbond attempt errors + assert_noop!( + Pools::fully_unbond(RuntimeOrigin::signed(420), 100), + Error::::NotKickerOrDestroying + ); + + // permissionless unbond must be full + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(420), 100, 80), + Error::::PartialUnbondNotAllowedPermissionlessly, + ); + + // Given the pool is destroying + unsafe_set_state(1, PoolState::Destroying); + + // The depositor cannot be fully unbonded until they are the last member + assert_noop!( + Pools::fully_unbond(RuntimeOrigin::signed(10), 10), + Error::::MinimumBondNotMet, + ); + + // Any account can unbond a member that is not the depositor + assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(420), 100)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100, era: 3 } + ] + ); + + // still permissionless unbond must be full + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(420), 100, 80), + Error::::PartialUnbondNotAllowedPermissionlessly, + ); + + // Given the pool is blocked + unsafe_set_state(1, PoolState::Blocked); + + // The depositor cannot be unbonded + assert_noop!( + Pools::fully_unbond(RuntimeOrigin::signed(420), 10), + Error::::DoesNotHavePermission + ); + + // Given the pools is destroying + unsafe_set_state(1, PoolState::Destroying); + + // The depositor cannot be unbonded yet. + assert_noop!( + Pools::fully_unbond(RuntimeOrigin::signed(420), 10), + Error::::DoesNotHavePermission, + ); + + // but when everyone is unbonded it can.. + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 100, 0)); + + // still permissionless unbond must be full. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(420), 10, 5), + Error::::PartialUnbondNotAllowedPermissionlessly, + ); + + // depositor can never be unbonded permissionlessly . + assert_noop!( + Pools::fully_unbond(RuntimeOrigin::signed(420), 10), + Error::::DoesNotHavePermission + ); + // but depositor itself can do it. + assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(10), 10)); + + assert_eq!(BondedPools::::get(1).unwrap().points, 0); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 + 3 => UnbondPool { points: 10, balance: 10 } + } + } + ); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); + assert_eq!(*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(), 10); + }); + } + + #[test] + #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))] + #[cfg_attr(not(debug_assertions), should_panic)] + fn unbond_errors_correctly() { + ExtBuilder::default().build_and_execute(|| { + assert_noop!( + Pools::fully_unbond(RuntimeOrigin::signed(11), 11), + Error::::PoolMemberNotFound + ); + + // Add the member + let member = PoolMember { pool_id: 2, points: 10, ..Default::default() }; + PoolMembers::::insert(11, member); + + let _ = Pools::fully_unbond(RuntimeOrigin::signed(11), 11); + }); + } + + #[test] + #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))] + #[cfg_attr(not(debug_assertions), should_panic)] + fn unbond_panics_when_reward_pool_not_found() { + ExtBuilder::default().build_and_execute(|| { + let member = PoolMember { pool_id: 2, points: 10, ..Default::default() }; + PoolMembers::::insert(11, member); + BondedPool:: { + id: 1, + inner: BondedPoolInner { + state: PoolState::Open, + points: 10, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + } + .put(); + + let _ = Pools::fully_unbond(RuntimeOrigin::signed(11), 11); + }); + } + + #[test] + fn partial_unbond_era_tracking() { + ExtBuilder::default().ed(1).build_and_execute(|| { + // to make the depositor capable of withdrawing. + StakingMinBond::set(1); + MinCreateBond::::set(1); + MinJoinBond::::set(1); + assert_eq!(Pools::depositor_min_bond(), 1); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); + assert_eq!(PoolMembers::::get(10).unwrap().pool_id, 1); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!() + ); + assert_eq!(BondedPool::::get(1).unwrap().points, 10); + assert!(SubPoolsStorage::::get(1).is_none()); + assert_eq!(CurrentEra::get(), 0); + assert_eq!(BondingDuration::get(), 3); + + // so the depositor can leave, just keeps the test simpler. + unsafe_set_state(1, PoolState::Destroying); + + // when: casual unbond + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 1)); + + // then + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 9); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 1); + assert_eq!(BondedPool::::get(1).unwrap().points, 9); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 3 } + ] + ); + + // when: casual further unbond, same era. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5)); + + // then + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 4); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 6); + assert_eq!(BondedPool::::get(1).unwrap().points, 4); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5, era: 3 }] + ); + + // when: casual further unbond, next era. + CurrentEra::set(1); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 1)); + + // then + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 3); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 7); + assert_eq!(BondedPool::::get(1).unwrap().points, 3); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 4 }] + ); + + // when: unbonding more than our active: error + assert_noop!( + frame_support::storage::with_storage_layer(|| Pools::unbond( + RuntimeOrigin::signed(10), + 10, + 5 + )), + Error::::MinimumBondNotMet + ); + // instead: + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 3)); + + // then + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 10); + assert_eq!(BondedPool::::get(1).unwrap().points, 0); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 4) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 4, balance: 4 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3, era: 4 }] + ); + }); + } + + #[test] + fn partial_unbond_max_chunks() { + ExtBuilder::default().add_members(vec![(20, 20)]).ed(1).build_and_execute(|| { + MaxUnbonding::set(2); + + // given + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2)); + CurrentEra::set(1); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 3)); + assert_eq!( + PoolMembers::::get(20).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 2, 4 => 3) + ); + + // when + CurrentEra::set(2); + assert_noop!( + frame_support::storage::with_storage_layer(|| Pools::unbond( + RuntimeOrigin::signed(20), + 20, + 4 + )), + Error::::MaxUnbondingLimit + ); + + // when + MaxUnbonding::set(3); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 1)); + + assert_eq!( + PoolMembers::::get(20).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 2, 4 => 3, 5 => 1) + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Unbonded { member: 20, pool_id: 1, points: 2, balance: 2, era: 3 }, + Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3, era: 4 }, + Event::Unbonded { member: 20, pool_id: 1, points: 1, balance: 1, era: 5 } + ] + ); + }) + } + + // depositor can unbond only up to `MinCreateBond`. + #[test] + fn depositor_permissioned_partial_unbond() { + ExtBuilder::default().ed(1).build_and_execute(|| { + // given + StakingMinBond::set(5); + assert_eq!(Pools::depositor_min_bond(), 5); + + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); + + // can unbond a bit.. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 3)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 7); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 3); + + // but not less than 2 + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 6), + Error::::MinimumBondNotMet + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3, era: 3 } + ] + ); + }); + } + + #[test] + fn depositor_permissioned_partial_unbond_slashed() { + ExtBuilder::default().ed(1).build_and_execute(|| { + // given + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); + + // slash the default pool + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 5); + + // cannot unbond even 7, because the value of shares is now less. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 7), + Error::::MinimumBondNotMet + ); + }); + } + + #[test] + fn every_unbonding_triggers_payout() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + let initial_reward_account = Balances::free_balance(default_reward_account()); + assert_eq!(initial_reward_account, Balances::minimum_balance()); + assert_eq!(initial_reward_account, 5); + + Balances::make_free_balance_be( + &default_reward_account(), + 4 * Balances::minimum_balance(), + ); + + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2)); + assert_eq!( + pool_events_since_last_call(), + vec![ + // 2/3 of ed, which is 20's share. + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2, era: 3 } + ] + ); + + CurrentEra::set(1); + Balances::make_free_balance_be( + &default_reward_account(), + 4 * Balances::minimum_balance(), + ); + + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 3)); + assert_eq!( + pool_events_since_last_call(), + vec![ + // 2/3 of ed, which is 20's share. + Event::PaidOut { member: 20, pool_id: 1, payout: 6 }, + Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3, era: 4 } + ] + ); + + CurrentEra::set(2); + Balances::make_free_balance_be( + &default_reward_account(), + 4 * Balances::minimum_balance(), + ); + + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, + Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 5 } + ] + ); + + assert_eq!( + PoolMembers::::get(20).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 2, 4 => 3, 5 => 5) + ); + }); + } +} + +mod pool_withdraw_unbonded { + use super::*; + + #[test] + fn pool_withdraw_unbonded_works() { + ExtBuilder::default().build_and_execute(|| { + // Given 10 unbond'ed directly against the pool account + assert_ok!(StakingMock::unbond(&default_bonded_account(), 5)); + // and the pool account only has 10 balance + assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(5)); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(10)); + assert_eq!(Balances::free_balance(&default_bonded_account()), 10); + + // When + assert_ok!(Pools::pool_withdraw_unbonded(RuntimeOrigin::signed(10), 1, 0)); + + // Then there unbonding balance is no longer locked + assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(5)); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(5)); + assert_eq!(Balances::free_balance(&default_bonded_account()), 10); + }); + } +} + +mod withdraw_unbonded { + use super::*; + use frame_support::bounded_btree_map; + + #[test] + fn withdraw_unbonded_works_against_slashed_no_era_sub_pool() { + ExtBuilder::default() + .add_members(vec![(40, 40), (550, 550)]) + .build_and_execute(|| { + // reduce the noise a bit. + let _ = balances_events_since_last_call(); + + // Given + assert_eq!(StakingMock::bonding_duration(), 3); + assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(550), 550)); + assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(40), 40)); + assert_eq!(Balances::free_balance(&default_bonded_account()), 600); + + let mut current_era = 1; + CurrentEra::set(current_era); + + let mut sub_pools = SubPoolsStorage::::get(1).unwrap(); + let unbond_pool = sub_pools.with_era.get_mut(&3).unwrap(); + // Sanity check + assert_eq!(*unbond_pool, UnbondPool { points: 550 + 40, balance: 550 + 40 }); + + // Simulate a slash to the pool with_era(current_era), decreasing the balance by + // half + { + unbond_pool.balance /= 2; // 295 + SubPoolsStorage::::insert(1, sub_pools); + // Update the equivalent of the unbonding chunks for the `StakingMock` + let mut x = UnbondingBalanceMap::get(); + *x.get_mut(&default_bonded_account()).unwrap() /= 5; + UnbondingBalanceMap::set(&x); + Balances::make_free_balance_be( + &default_bonded_account(), + Balances::free_balance(&default_bonded_account()) / 2, // 300 + ); + StakingMock::set_bonded_balance( + default_bonded_account(), + StakingMock::active_stake(&default_bonded_account()).unwrap() / 2, + ); + }; + + // Advance the current_era to ensure all `with_era` pools will be merged into + // `no_era` pool + current_era += TotalUnbondingPools::::get(); + CurrentEra::set(current_era); + + // Simulate some other call to unbond that would merge `with_era` pools into + // `no_era` + let sub_pools = + SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era); + SubPoolsStorage::::insert(1, sub_pools); + + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: UnbondPool { points: 550 + 40, balance: 275 + 20 }, + with_era: Default::default() + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, + Event::Unbonded { + member: 550, + pool_id: 1, + points: 550, + balance: 550, + era: 3 + }, + Event::Unbonded { member: 40, pool_id: 1, points: 40, balance: 40, era: 3 }, + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::BalanceSet { + who: default_bonded_account(), + free: 300, + reserved: 0 + }] + ); + + // When + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0)); + + // Then + assert_eq!( + SubPoolsStorage::::get(1).unwrap().no_era, + UnbondPool { points: 40, balance: 20 } + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 550 }, + Event::MemberRemoved { pool_id: 1, member: 550 } + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 }] + ); + + // When + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0)); + + // Then + assert_eq!( + SubPoolsStorage::::get(1).unwrap().no_era, + UnbondPool { points: 0, balance: 0 } + ); + assert!(!PoolMembers::::contains_key(40)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 40 }, + Event::MemberRemoved { pool_id: 1, member: 40 } + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 }] + ); + + // now, finally, the depositor can take out its share. + unsafe_set_state(1, PoolState::Destroying); + assert_ok!(fully_unbond_permissioned(10)); + + current_era += 3; + CurrentEra::set(current_era); + + // when + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Unbonded { member: 10, pool_id: 1, balance: 5, points: 5, era: 9 }, + Event::Withdrawn { member: 10, pool_id: 1, balance: 5, points: 5 }, + Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::Destroyed { pool_id: 1 } + ] + ); + assert!(!Metadata::::contains_key(1)); + assert_eq!( + balances_events_since_last_call(), + vec![ + BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 }, + BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 } + ] + ); + }); + } + + #[test] + fn withdraw_unbonded_works_against_slashed_with_era_sub_pools() { + ExtBuilder::default() + .add_members(vec![(40, 40), (550, 550)]) + .build_and_execute(|| { + let _ = balances_events_since_last_call(); + + // Given + // current bond is 600, we slash it all to 300. + StakingMock::set_bonded_balance(default_bonded_account(), 300); + Balances::make_free_balance_be(&default_bonded_account(), 300); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(300)); + + assert_ok!(fully_unbond_permissioned(40)); + assert_ok!(fully_unbond_permissioned(550)); + + assert_eq!( + SubPoolsStorage::::get(&1).unwrap().with_era, + unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2 + 40 / 2, balance: 550 / 2 + 40 / 2 + }} + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, + Event::Unbonded { member: 40, pool_id: 1, balance: 20, points: 20, era: 3 }, + Event::Unbonded { + member: 550, + pool_id: 1, + balance: 275, + points: 275, + era: 3, + } + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::BalanceSet { + who: default_bonded_account(), + free: 300, + reserved: 0 + },] + ); + + CurrentEra::set(StakingMock::bonding_duration()); + + // When + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0)); + + // Then + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 20 }, + Event::MemberRemoved { pool_id: 1, member: 40 } + ] + ); + + assert_eq!( + SubPoolsStorage::::get(&1).unwrap().with_era, + unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2, balance: 550 / 2 }} + ); + + // When + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0)); + + // Then + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 275 }, + Event::MemberRemoved { pool_id: 1, member: 550 } + ] + ); + assert!(SubPoolsStorage::::get(&1).unwrap().with_era.is_empty()); + + // now, finally, the depositor can take out its share. + unsafe_set_state(1, PoolState::Destroying); + assert_ok!(fully_unbond_permissioned(10)); + + // because everyone else has left, the points + assert_eq!( + SubPoolsStorage::::get(&1).unwrap().with_era, + unbonding_pools_with_era! { 6 => UnbondPool { points: 5, balance: 5 }} + ); + + CurrentEra::set(CurrentEra::get() + 3); + + // set metadata to check that it's being removed on dissolve + assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1])); + assert!(Metadata::::contains_key(1)); + + // when + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); + + // then + assert_eq!(Balances::free_balance(&10), 10 + 35); + assert_eq!(Balances::free_balance(&default_bonded_account()), 0); + + // in this test 10 also gets a fair share of the slash, because the slash was + // applied to the bonded account. + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5, era: 6 }, + Event::Withdrawn { member: 10, pool_id: 1, points: 5, balance: 5 }, + Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::Destroyed { pool_id: 1 } + ] + ); + assert!(!Metadata::::contains_key(1)); + assert_eq!( + balances_events_since_last_call(), + vec![ + BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 }, + BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 } + ] + ); + }); + } + + #[test] + fn withdraw_unbonded_handles_faulty_sub_pool_accounting() { + ExtBuilder::default().build_and_execute(|| { + // Given + assert_eq!(Balances::minimum_balance(), 5); + assert_eq!(Balances::free_balance(&10), 35); + assert_eq!(Balances::free_balance(&default_bonded_account()), 10); + unsafe_set_state(1, PoolState::Destroying); + assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(10), 10)); + + // Simulate a slash that is not accounted for in the sub pools. + Balances::make_free_balance_be(&default_bonded_account(), 5); + assert_eq!( + SubPoolsStorage::::get(1).unwrap().with_era, + //------------------------------balance decrease is not account for + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 } } + ); + + CurrentEra::set(0 + 3); + + // When + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); + + // Then + assert_eq!(Balances::free_balance(10), 10 + 35); + assert_eq!(Balances::free_balance(&default_bonded_account()), 0); + }); + } + + #[test] + fn withdraw_unbonded_errors_correctly() { + ExtBuilder::default().with_check(0).build_and_execute(|| { + // Insert the sub-pool + let sub_pools = SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 }}, + }; + SubPoolsStorage::::insert(1, sub_pools.clone()); + + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0), + Error::::PoolMemberNotFound + ); + + let mut member = PoolMember { pool_id: 1, points: 10, ..Default::default() }; + PoolMembers::::insert(11, member.clone()); + + // Simulate calling `unbond` + member.unbonding_eras = member_unbonding_eras!(3 + 0 => 10); + PoolMembers::::insert(11, member.clone()); + + // We are still in the bonding duration + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0), + Error::::CannotWithdrawAny + ); + + // If we error the member does not get removed + assert_eq!(PoolMembers::::get(&11), Some(member)); + // and the sub pools do not get updated. + assert_eq!(SubPoolsStorage::::get(1).unwrap(), sub_pools) + }); + } + + #[test] + fn withdraw_unbonded_kick() { + ExtBuilder::default() + .add_members(vec![(100, 100), (200, 200)]) + .build_and_execute(|| { + // Given + assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(100), 100)); + assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(200), 200)); + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + points: 10, + state: PoolState::Open, + member_counter: 3, + roles: DEFAULT_ROLES + } + } + ); + CurrentEra::set(StakingMock::bonding_duration()); + + // Cannot kick when pool is open + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(902), 100, 0), + Error::::NotKickerOrDestroying + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, + Event::Unbonded { + member: 100, + pool_id: 1, + points: 100, + balance: 100, + era: 3 + }, + Event::Unbonded { + member: 200, + pool_id: 1, + points: 200, + balance: 200, + era: 3 + } + ] + ); + + // Given + unsafe_set_state(1, PoolState::Blocked); + + // Cannot kick as a nominator + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(901), 100, 0), + Error::::NotKickerOrDestroying + ); + + // Can kick as root + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(900), 100, 0)); + + // Can kick as state toggler + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(900), 200, 0)); + + assert_eq!(Balances::free_balance(100), 100 + 100); + assert_eq!(Balances::free_balance(200), 200 + 200); + assert!(!PoolMembers::::contains_key(100)); + assert!(!PoolMembers::::contains_key(200)); + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 }, + Event::MemberRemoved { pool_id: 1, member: 100 }, + Event::Withdrawn { member: 200, pool_id: 1, points: 200, balance: 200 }, + Event::MemberRemoved { pool_id: 1, member: 200 } + ] + ); + }); + } + + #[test] + fn withdraw_unbonded_destroying_permissionless() { + ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { + // Given + assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(100), 100)); + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + points: 10, + state: PoolState::Open, + member_counter: 2, + roles: DEFAULT_ROLES, + } + } + ); + CurrentEra::set(StakingMock::bonding_duration()); + assert_eq!(Balances::free_balance(100), 100); + + // Cannot permissionlessly withdraw + assert_noop!( + Pools::fully_unbond(RuntimeOrigin::signed(420), 100), + Error::::NotKickerOrDestroying + ); + + // Given + unsafe_set_state(1, PoolState::Destroying); + + // Can permissionlesly withdraw a member that is not the depositor + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(420), 100, 0)); + + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default(),); + assert_eq!(Balances::free_balance(100), 100 + 100); + assert!(!PoolMembers::::contains_key(100)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100, era: 3 }, + Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 }, + Event::MemberRemoved { pool_id: 1, member: 100 } + ] + ); + }); + } + + #[test] + fn partial_withdraw_unbonded_depositor() { + ExtBuilder::default().ed(1).build_and_execute(|| { + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); + unsafe_set_state(1, PoolState::Destroying); + + // given + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 6)); + CurrentEra::set(1); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 1)); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 13); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 7); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, + Event::Unbonded { member: 10, pool_id: 1, points: 6, balance: 6, era: 3 }, + Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 4 } + ] + ); + + // when + CurrentEra::set(2); + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0), + Error::::CannotWithdrawAny + ); + + // when + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); + + // then + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { member: 10, pool_id: 1, points: 6, balance: 6 }] + ); + + // when + CurrentEra::set(4); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); + + // then + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!() + ); + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { member: 10, pool_id: 1, points: 1, balance: 1 },] + ); + + // when repeating: + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0), + Error::::CannotWithdrawAny + ); + }); + } + + #[test] + fn partial_withdraw_unbonded_non_depositor() { + ExtBuilder::default().add_members(vec![(11, 10)]).build_and_execute(|| { + // given + assert_ok!(Pools::unbond(RuntimeOrigin::signed(11), 11, 6)); + CurrentEra::set(1); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(11), 11, 1)); + assert_eq!( + PoolMembers::::get(11).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!(PoolMembers::::get(11).unwrap().active_points(), 3); + assert_eq!(PoolMembers::::get(11).unwrap().unbonding_points(), 7); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 11, pool_id: 1, bonded: 10, joined: true }, + Event::Unbonded { member: 11, pool_id: 1, points: 6, balance: 6, era: 3 }, + Event::Unbonded { member: 11, pool_id: 1, points: 1, balance: 1, era: 4 } + ] + ); + + // when + CurrentEra::set(2); + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0), + Error::::CannotWithdrawAny + ); + + // when + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0)); + + // then + assert_eq!( + PoolMembers::::get(11).unwrap().unbonding_eras, + member_unbonding_eras!(4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { member: 11, pool_id: 1, points: 6, balance: 6 }] + ); + + // when + CurrentEra::set(4); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0)); + + // then + assert_eq!( + PoolMembers::::get(11).unwrap().unbonding_eras, + member_unbonding_eras!() + ); + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { member: 11, pool_id: 1, points: 1, balance: 1 }] + ); + + // when repeating: + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0), + Error::::CannotWithdrawAny + ); + }); + } + + #[test] + fn full_multi_step_withdrawing_non_depositor() { + ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { + // given + assert_ok!(Pools::unbond(RuntimeOrigin::signed(100), 100, 75)); + assert_eq!( + PoolMembers::::get(100).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 75) + ); + + // progress one era and unbond the leftover. + CurrentEra::set(1); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(100), 100, 25)); + assert_eq!( + PoolMembers::::get(100).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 75, 4 => 25) + ); + + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0), + Error::::CannotWithdrawAny + ); + + // now the 75 should be free. + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, points: 75, balance: 75, era: 3 }, + Event::Unbonded { member: 100, pool_id: 1, points: 25, balance: 25, era: 4 }, + Event::Withdrawn { member: 100, pool_id: 1, points: 75, balance: 75 }, + ] + ); + assert_eq!( + PoolMembers::::get(100).unwrap().unbonding_eras, + member_unbonding_eras!(4 => 25) + ); + + // the 25 should be free now, and the member removed. + CurrentEra::set(4); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 100, pool_id: 1, points: 25, balance: 25 }, + Event::MemberRemoved { pool_id: 1, member: 100 } + ] + ); + }) + } + + #[test] + fn out_of_sync_unbonding_chunks() { + // the unbonding_eras in pool member are always fixed to the era at which they are unlocked, + // but the actual unbonding pools get pruned and might get combined in the no_era pool. + // Pools are only merged when one unbonds, so we unbond a little bit on every era to + // simulate this. + ExtBuilder::default() + .add_members(vec![(20, 100), (30, 100)]) + .build_and_execute(|| { + System::reset_events(); + + // when + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(30), 30, 5)); + + // then member-local unbonding is pretty much in sync with the global pools. + assert_eq!( + PoolMembers::::get(20).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 5) + ); + assert_eq!( + PoolMembers::::get(30).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 5) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 10, balance: 10 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 3 }, + Event::Unbonded { member: 30, pool_id: 1, points: 5, balance: 5, era: 3 }, + ] + ); + + // when + CurrentEra::set(1); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5)); + + // then still member-local unbonding is pretty much in sync with the global pools. + assert_eq!( + PoolMembers::::get(20).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 5, 4 => 5) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 10, balance: 10 }, + 4 => UnbondPool { points: 5, balance: 5 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 4 }] + ); + + // when + CurrentEra::set(2); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5)); + + // then still member-local unbonding is pretty much in sync with the global pools. + assert_eq!( + PoolMembers::::get(20).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 5, 4 => 5, 5 => 5) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 10, balance: 10 }, + 4 => UnbondPool { points: 5, balance: 5 }, + 5 => UnbondPool { points: 5, balance: 5 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 5 }] + ); + + // when + CurrentEra::set(5); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5)); + + // then + assert_eq!( + PoolMembers::::get(20).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 5, 4 => 5, 5 => 5, 8 => 5) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + // era 3 is merged into no_era. + no_era: UnbondPool { points: 10, balance: 10 }, + with_era: unbonding_pools_with_era! { + 4 => UnbondPool { points: 5, balance: 5 }, + 5 => UnbondPool { points: 5, balance: 5 }, + 8 => UnbondPool { points: 5, balance: 5 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 8 }] + ); + + // now we start withdrawing unlocked bonds. + + // when + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); + // then + assert_eq!( + PoolMembers::::get(20).unwrap().unbonding_eras, + member_unbonding_eras!(8 => 5) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + // era 3 is merged into no_era. + no_era: UnbondPool { points: 5, balance: 5 }, + with_era: unbonding_pools_with_era! { + 8 => UnbondPool { points: 5, balance: 5 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { member: 20, pool_id: 1, points: 15, balance: 15 }] + ); + + // when + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(30), 30, 0)); + // then + assert_eq!( + PoolMembers::::get(30).unwrap().unbonding_eras, + member_unbonding_eras!() + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + // era 3 is merged into no_era. + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 8 => UnbondPool { points: 5, balance: 5 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { member: 30, pool_id: 1, points: 5, balance: 5 }] + ); + }) + } + + #[test] + fn full_multi_step_withdrawing_depositor() { + ExtBuilder::default().ed(1).build_and_execute(|| { + // depositor now has 20, they can unbond to 10. + assert_eq!(Pools::depositor_min_bond(), 10); + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); + + // now they can. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 7)); + + // progress one era and unbond the leftover. + CurrentEra::set(1); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 3)); + + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 7, 4 => 3) + ); + + // they can't unbond to a value below 10 other than 0.. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 5), + Error::::MinimumBondNotMet + ); + + // but not even full, because they pool is not yet destroying. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 10), + Error::::MinimumBondNotMet + ); + + // but now they can. + unsafe_set_state(1, PoolState::Destroying); + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 5), + Error::::MinimumBondNotMet + ); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10)); + + // now the 7 should be free. + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, + Event::Unbonded { member: 10, pool_id: 1, balance: 7, points: 7, era: 3 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 3, points: 3, era: 4 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 }, + Event::Withdrawn { member: 10, pool_id: 1, balance: 7, points: 7 } + ] + ); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(4 => 13) + ); + + // the 13 should be free now, and the member removed. + CurrentEra::set(4); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 10, pool_id: 1, points: 13, balance: 13 }, + Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::Destroyed { pool_id: 1 }, + ] + ); + assert!(!Metadata::::contains_key(1)); + }) + } +} + +mod create { + use super::*; + + #[test] + fn create_works() { + ExtBuilder::default().build_and_execute(|| { + // next pool id is 2. + let next_pool_stash = Pools::create_bonded_account(2); + let ed = Balances::minimum_balance(); + + assert!(!BondedPools::::contains_key(2)); + assert!(!RewardPools::::contains_key(2)); + assert!(!PoolMembers::::contains_key(11)); + assert_err!( + StakingMock::active_stake(&next_pool_stash), + DispatchError::Other("balance not found") + ); + + Balances::make_free_balance_be(&11, StakingMock::minimum_nominator_bond() + ed); + assert_ok!(Pools::create( + RuntimeOrigin::signed(11), + StakingMock::minimum_nominator_bond(), + 123, + 456, + 789 + )); + + assert_eq!(Balances::free_balance(&11), 0); + assert_eq!( + PoolMembers::::get(11).unwrap(), + PoolMember { + pool_id: 2, + points: StakingMock::minimum_nominator_bond(), + ..Default::default() + } + ); + assert_eq!( + BondedPool::::get(2).unwrap(), + BondedPool { + id: 2, + inner: BondedPoolInner { + points: StakingMock::minimum_nominator_bond(), + member_counter: 1, + state: PoolState::Open, + roles: PoolRoles { + depositor: 11, + root: Some(123), + nominator: Some(456), + state_toggler: Some(789) + } + } + } + ); + assert_eq!( + StakingMock::active_stake(&next_pool_stash).unwrap(), + StakingMock::minimum_nominator_bond() + ); + assert_eq!( + RewardPools::::get(2).unwrap(), + RewardPool { ..Default::default() } + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Created { depositor: 11, pool_id: 2 }, + Event::Bonded { member: 11, pool_id: 2, bonded: 10, joined: true } + ] + ); + }); + } + + #[test] + fn create_errors_correctly() { + ExtBuilder::default().with_check(0).build_and_execute(|| { + assert_noop!( + Pools::create(RuntimeOrigin::signed(10), 420, 123, 456, 789), + Error::::AccountBelongsToOtherPool + ); + + // Given + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(StakingMock::minimum_nominator_bond(), 10); + + // Then + assert_noop!( + Pools::create(RuntimeOrigin::signed(11), 9, 123, 456, 789), + Error::::MinimumBondNotMet + ); + + // Given + MinCreateBond::::put(20); + + // Then + assert_noop!( + Pools::create(RuntimeOrigin::signed(11), 19, 123, 456, 789), + Error::::MinimumBondNotMet + ); + + // Given + BondedPool:: { + id: 2, + inner: BondedPoolInner { + state: PoolState::Open, + points: 10, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + } + .put(); + assert_eq!(MaxPools::::get(), Some(2)); + assert_eq!(BondedPools::::count(), 2); + + // Then + assert_noop!( + Pools::create(RuntimeOrigin::signed(11), 20, 123, 456, 789), + Error::::MaxPools + ); + + // Given + assert_eq!(PoolMembers::::count(), 1); + MaxPools::::put(3); + MaxPoolMembers::::put(1); + Balances::make_free_balance_be(&11, 5 + 20); + + // Then + let create = RuntimeCall::Pools(crate::Call::::create { + amount: 20, + root: 11, + nominator: 11, + state_toggler: 11, + }); + assert_noop!( + create.dispatch(RuntimeOrigin::signed(11)), + Error::::MaxPoolMembers + ); + }); + } + + #[test] + fn create_with_pool_id_works() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::make_free_balance_be(&11, StakingMock::minimum_nominator_bond() + ed); + assert_ok!(Pools::create( + RuntimeOrigin::signed(11), + StakingMock::minimum_nominator_bond(), + 123, + 456, + 789 + )); + + assert_eq!(Balances::free_balance(&11), 0); + // delete the initial pool created, then pool_Id `1` will be free + + assert_noop!( + Pools::create_with_pool_id(RuntimeOrigin::signed(12), 20, 234, 654, 783, 1), + Error::::PoolIdInUse + ); + + assert_noop!( + Pools::create_with_pool_id(RuntimeOrigin::signed(12), 20, 234, 654, 783, 3), + Error::::InvalidPoolId + ); + + // start dismantling the pool. + assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying)); + assert_ok!(fully_unbond_permissioned(10)); + + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 10)); + + assert_ok!(Pools::create_with_pool_id(RuntimeOrigin::signed(10), 20, 234, 654, 783, 1)); + }); + } +} + +mod nominate { + use super::*; + + #[test] + fn nominate_works() { + ExtBuilder::default().build_and_execute(|| { + // Depositor can't nominate + assert_noop!( + Pools::nominate(RuntimeOrigin::signed(10), 1, vec![21]), + Error::::NotNominator + ); + + // State toggler can't nominate + assert_noop!( + Pools::nominate(RuntimeOrigin::signed(902), 1, vec![21]), + Error::::NotNominator + ); + + // Root can nominate + assert_ok!(Pools::nominate(RuntimeOrigin::signed(900), 1, vec![21])); + assert_eq!(Nominations::get().unwrap(), vec![21]); + + // Nominator can nominate + assert_ok!(Pools::nominate(RuntimeOrigin::signed(901), 1, vec![31])); + assert_eq!(Nominations::get().unwrap(), vec![31]); + + // Can't nominate for a pool that doesn't exist + assert_noop!( + Pools::nominate(RuntimeOrigin::signed(902), 123, vec![21]), + Error::::PoolNotFound + ); + }); + } +} + +mod set_state { + use super::*; + + #[test] + fn set_state_works() { + ExtBuilder::default().build_and_execute(|| { + // Given + assert_ok!(BondedPool::::get(1).unwrap().ok_to_be_open()); + + // Only the root and state toggler can change the state when the pool is ok to be open. + assert_noop!( + Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Blocked), + Error::::CanNotChangeState + ); + assert_noop!( + Pools::set_state(RuntimeOrigin::signed(901), 1, PoolState::Blocked), + Error::::CanNotChangeState + ); + + // Root can change state + assert_ok!(Pools::set_state(RuntimeOrigin::signed(900), 1, PoolState::Blocked)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::StateChanged { pool_id: 1, new_state: PoolState::Blocked } + ] + ); + + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Blocked); + + // State toggler can change state + assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying)); + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); + + // If the pool is destroying, then no one can set state + assert_noop!( + Pools::set_state(RuntimeOrigin::signed(900), 1, PoolState::Blocked), + Error::::CanNotChangeState + ); + assert_noop!( + Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Blocked), + Error::::CanNotChangeState + ); + + // If the pool is not ok to be open, then anyone can set it to destroying + + // Given + unsafe_set_state(1, PoolState::Open); + let mut bonded_pool = BondedPool::::get(1).unwrap(); + bonded_pool.points = 100; + bonded_pool.put(); + // When + assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying)); + // Then + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); + + // Given + Balances::make_free_balance_be(&default_bonded_account(), Balance::max_value() / 10); + unsafe_set_state(1, PoolState::Open); + // When + assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying)); + // Then + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); + + // If the pool is not ok to be open, it cannot be permissionlessly set to a state that + // isn't destroying + unsafe_set_state(1, PoolState::Open); + assert_noop!( + Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Blocked), + Error::::CanNotChangeState + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying } + ] + ); + }); + } +} + +mod set_metadata { + use super::*; + + #[test] + fn set_metadata_works() { + ExtBuilder::default().build_and_execute(|| { + // Root can set metadata + assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1])); + assert_eq!(Metadata::::get(1), vec![1, 1]); + + // State toggler can set metadata + assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(902), 1, vec![2, 2])); + assert_eq!(Metadata::::get(1), vec![2, 2]); + + // Depositor can't set metadata + assert_noop!( + Pools::set_metadata(RuntimeOrigin::signed(10), 1, vec![3, 3]), + Error::::DoesNotHavePermission + ); + + // Nominator can't set metadata + assert_noop!( + Pools::set_metadata(RuntimeOrigin::signed(901), 1, vec![3, 3]), + Error::::DoesNotHavePermission + ); + + // Metadata cannot be longer than `MaxMetadataLen` + assert_noop!( + Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1, 1]), + Error::::MetadataExceedsMaxLen + ); + }); + } +} + +mod set_configs { + use super::*; + + #[test] + fn set_configs_works() { + ExtBuilder::default().build_and_execute(|| { + // Setting works + assert_ok!(Pools::set_configs( + RuntimeOrigin::root(), + ConfigOp::Set(1 as Balance), + ConfigOp::Set(2 as Balance), + ConfigOp::Set(3u32), + ConfigOp::Set(4u32), + ConfigOp::Set(5u32), + )); + assert_eq!(MinJoinBond::::get(), 1); + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(MaxPools::::get(), Some(3)); + assert_eq!(MaxPoolMembers::::get(), Some(4)); + assert_eq!(MaxPoolMembersPerPool::::get(), Some(5)); + + // Noop does nothing + assert_storage_noop!(assert_ok!(Pools::set_configs( + RuntimeOrigin::root(), + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ))); + + // Removing works + assert_ok!(Pools::set_configs( + RuntimeOrigin::root(), + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + )); + assert_eq!(MinJoinBond::::get(), 0); + assert_eq!(MinCreateBond::::get(), 0); + assert_eq!(MaxPools::::get(), None); + assert_eq!(MaxPoolMembers::::get(), None); + assert_eq!(MaxPoolMembersPerPool::::get(), None); + }); + } +} + +mod bond_extra { + use super::*; + use crate::Event; + + #[test] + fn bond_extra_from_free_balance_creator() { + ExtBuilder::default().build_and_execute(|| { + // 10 is the owner and a member in pool 1, give them some more funds. + Balances::make_free_balance_be(&10, 100); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(BondedPools::::get(1).unwrap().points, 10); + assert_eq!(Balances::free_balance(10), 100); + + // when + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); + + // then + assert_eq!(Balances::free_balance(10), 90); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + assert_eq!(BondedPools::::get(1).unwrap().points, 20); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false } + ] + ); + + // when + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(20))); + + // then + assert_eq!(Balances::free_balance(10), 70); + assert_eq!(PoolMembers::::get(10).unwrap().points, 40); + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::Bonded { member: 10, pool_id: 1, bonded: 20, joined: false }] + ); + }) + } + + #[test] + fn bond_extra_from_rewards_creator() { + ExtBuilder::default().build_and_execute(|| { + // put some money in the reward account, all of which will belong to 10 as the only + // member of the pool. + Balances::make_free_balance_be(&default_reward_account(), 7); + // ... if which only 2 is claimable to make sure the reward account does not die. + let claimable_reward = 7 - ExistentialDeposit::get(); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(BondedPools::::get(1).unwrap().points, 10); + assert_eq!(Balances::free_balance(10), 35); + + // when + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards)); + + // then + assert_eq!(Balances::free_balance(10), 35); + assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + claimable_reward); + assert_eq!(BondedPools::::get(1).unwrap().points, 10 + claimable_reward); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: claimable_reward }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: claimable_reward, + joined: false + } + ] + ); + }) + } + + #[test] + fn bond_extra_from_rewards_joiner() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + // put some money in the reward account, all of which will belong to 10 as the only + // member of the pool. + Balances::make_free_balance_be(&default_reward_account(), 8); + // ... if which only 3 is claimable to make sure the reward account does not die. + let claimable_reward = 8 - ExistentialDeposit::get(); + // NOTE: easier to read of we use 3, so let's use the number instead of variable. + assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3"); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(PoolMembers::::get(20).unwrap().points, 20); + assert_eq!(BondedPools::::get(1).unwrap().points, 30); + assert_eq!(Balances::free_balance(10), 35); + assert_eq!(Balances::free_balance(20), 20); + + // when + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards)); + + // then + assert_eq!(Balances::free_balance(10), 35); + // 10's share of the reward is 1/3, since they gave 10/30 of the total shares. + assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + 1); + assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 1); + + // when + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::Rewards)); + + // then + assert_eq!(Balances::free_balance(20), 20); + // 20's share of the rewards is the other 2/3 of the rewards, since they have 20/30 of + // the shares + assert_eq!(PoolMembers::::get(20).unwrap().points, 20 + 2); + assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 3); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 1, joined: false }, + Event::PaidOut { member: 20, pool_id: 1, payout: 2 }, + Event::Bonded { member: 20, pool_id: 1, bonded: 2, joined: false } + ] + ); + }) + } +} + +mod update_roles { + use super::*; + + #[test] + fn update_roles_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!( + BondedPools::::get(1).unwrap().roles, + PoolRoles { + depositor: 10, + root: Some(900), + nominator: Some(901), + state_toggler: Some(902) + }, + ); + + // non-existent pools + assert_noop!( + Pools::update_roles( + RuntimeOrigin::signed(1), + 2, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + ), + Error::::PoolNotFound, + ); + + // depositor cannot change roles. + assert_noop!( + Pools::update_roles( + RuntimeOrigin::signed(1), + 1, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + ), + Error::::DoesNotHavePermission, + ); + + // nominator cannot change roles. + assert_noop!( + Pools::update_roles( + RuntimeOrigin::signed(901), + 1, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + ), + Error::::DoesNotHavePermission, + ); + // state-toggler + assert_noop!( + Pools::update_roles( + RuntimeOrigin::signed(902), + 1, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + ), + Error::::DoesNotHavePermission, + ); + + // but root can + assert_ok!(Pools::update_roles( + RuntimeOrigin::signed(900), + 1, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + )); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::RolesUpdated { + root: Some(5), + state_toggler: Some(7), + nominator: Some(6) + } + ] + ); + assert_eq!( + BondedPools::::get(1).unwrap().roles, + PoolRoles { + depositor: 10, + root: Some(5), + nominator: Some(6), + state_toggler: Some(7) + }, + ); + + // also root origin can + assert_ok!(Pools::update_roles( + RuntimeOrigin::root(), + 1, + ConfigOp::Set(1), + ConfigOp::Set(2), + ConfigOp::Set(3) + )); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::RolesUpdated { + root: Some(1), + state_toggler: Some(3), + nominator: Some(2) + }] + ); + assert_eq!( + BondedPools::::get(1).unwrap().roles, + PoolRoles { + depositor: 10, + root: Some(1), + nominator: Some(2), + state_toggler: Some(3) + }, + ); + + // Noop works + assert_ok!(Pools::update_roles( + RuntimeOrigin::root(), + 1, + ConfigOp::Set(11), + ConfigOp::Noop, + ConfigOp::Noop + )); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::RolesUpdated { + root: Some(11), + state_toggler: Some(3), + nominator: Some(2) + }] + ); + + assert_eq!( + BondedPools::::get(1).unwrap().roles, + PoolRoles { + depositor: 10, + root: Some(11), + nominator: Some(2), + state_toggler: Some(3) + }, + ); + + // Remove works + assert_ok!(Pools::update_roles( + RuntimeOrigin::root(), + 1, + ConfigOp::Set(69), + ConfigOp::Remove, + ConfigOp::Remove + )); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::RolesUpdated { root: Some(69), state_toggler: None, nominator: None }] + ); + + assert_eq!( + BondedPools::::get(1).unwrap().roles, + PoolRoles { depositor: 10, root: Some(69), nominator: None, state_toggler: None }, + ); + }) + } +} + +mod reward_counter_precision { + use sp_runtime::FixedU128; + + use super::*; + + const DOT: Balance = 10u128.pow(10u32); + const POLKADOT_TOTAL_ISSUANCE_GENESIS: Balance = DOT * 10u128.pow(9u32); + + const fn inflation(years: u128) -> u128 { + let mut i = 0; + let mut start = POLKADOT_TOTAL_ISSUANCE_GENESIS; + while i < years { + start = start + start / 10; + i += 1 + } + start + } + + fn default_pool_reward_counter() -> FixedU128 { + RewardPools::::get(1) + .unwrap() + .current_reward_counter(1, BondedPools::::get(1).unwrap().points) + .unwrap() + } + + fn pending_rewards(of: AccountId) -> Option> { + let member = PoolMembers::::get(of).unwrap(); + assert_eq!(member.pool_id, 1); + let rc = default_pool_reward_counter(); + member.pending_rewards(rc).ok() + } + + #[test] + fn smallest_claimable_reward() { + // create a pool that has all of the polkadot issuance in 50 years. + let pool_bond = inflation(50); + ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 1173908528796953165005, + joined: true, + } + ] + ); + + // the smallest reward that this pool can handle is + let expected_smallest_reward = inflation(50) / 10u128.pow(18); + + // tad bit less. cannot be paid out. + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += + expected_smallest_reward - 1)); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_eq!(pool_events_since_last_call(), vec![]); + // revert it. + + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= + expected_smallest_reward - 1)); + + // tad bit more. can be claimed. + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += + expected_smallest_reward + 1)); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 1173 }] + ); + }) + } + + #[test] + fn massive_reward_in_small_pool() { + let tiny_bond = 1000 * DOT; + ExtBuilder::default().ed(DOT).min_bond(tiny_bond).build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10000000000000, joined: true } + ] + ); + + Balances::make_free_balance_be(&20, tiny_bond); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), tiny_bond / 2, 1)); + + // Suddenly, add a shit ton of rewards. + assert_ok!( + Balances::mutate_account(&default_reward_account(), |a| a.free += inflation(1)) + ); + + // now claim. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Bonded { member: 20, pool_id: 1, bonded: 5000000000000, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 7333333333333333333 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 3666666666666666666 } + ] + ); + }) + } + + #[test] + fn reward_counter_calc_wont_fail_in_normal_polkadot_future() { + // create a pool that has roughly half of the polkadot issuance in 10 years. + let pool_bond = inflation(10) / 2; + ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 12_968_712_300_500_000_000, + joined: true, + } + ] + ); + + // in 10 years, the total claimed rewards are large values as well. assuming that a pool + // is earning all of the inflation per year (which is really unrealistic, but worse + // case), that will be: + let pool_total_earnings_10_years = inflation(10) - POLKADOT_TOTAL_ISSUANCE_GENESIS; + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += + pool_total_earnings_10_years)); + + // some whale now joins with the other half ot the total issuance. This will bloat all + // the calculation regarding current reward counter. + Balances::make_free_balance_be(&20, pool_bond * 2); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), pool_bond, 1)); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::Bonded { + member: 20, + pool_id: 1, + bonded: 12_968_712_300_500_000_000, + joined: true + }] + ); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 15937424600999999996 }] + ); + + // now let a small member join with 10 DOTs. + Balances::make_free_balance_be(&30, 20 * DOT); + assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10 * DOT, 1)); + + // and give a reasonably small reward to the pool. + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += DOT)); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Bonded { member: 30, pool_id: 1, bonded: 100000000000, joined: true }, + // quite small, but working fine. + Event::PaidOut { member: 30, pool_id: 1, payout: 38 } + ] + ); + }) + } + + #[test] + fn reward_counter_update_can_fail_if_pool_is_highly_slashed() { + // create a pool that has roughly half of the polkadot issuance in 10 years. + let pool_bond = inflation(10) / 2; + ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 12_968_712_300_500_000_000, + joined: true, + } + ] + ); + + // slash this pool by 99% of that. + StakingMock::set_bonded_balance(default_bonded_account(), DOT + pool_bond / 100); + + // some whale now joins with the other half ot the total issuance. This will trigger an + // overflow. This test is actually a bit too lenient because all the reward counters are + // set to zero. In other tests that we want to assert a scenario won't fail, we should + // also set the reward counters to some large value. + Balances::make_free_balance_be(&20, pool_bond * 2); + assert_err!( + Pools::join(RuntimeOrigin::signed(20), pool_bond, 1), + Error::::OverflowRisk + ); + }) + } + + #[test] + fn if_small_member_waits_long_enough_they_will_earn_rewards() { + // create a pool that has a quarter of the current polkadot issuance + ExtBuilder::default() + .ed(DOT) + .min_bond(POLKADOT_TOTAL_ISSUANCE_GENESIS / 4) + .build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 2500000000000000000, + joined: true, + } + ] + ); + + // and have a tiny fish join the pool as well.. + Balances::make_free_balance_be(&20, 20 * DOT); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1)); + + // earn some small rewards + assert_ok!( + Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) + ); + + // no point in claiming for 20 (nonetheless, it should be harmless) + assert!(pending_rewards(20).unwrap().is_zero()); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Bonded { + member: 20, + pool_id: 1, + bonded: 100000000000, + joined: true + }, + Event::PaidOut { member: 10, pool_id: 1, payout: 9999997 } + ] + ); + + // earn some small more, still nothing can be claimed for 20, but 10 claims their + // share. + assert_ok!( + Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) + ); + assert!(pending_rewards(20).unwrap().is_zero()); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10000000 }] + ); + + // earn some more rewards, this time 20 can also claim. + assert_ok!( + Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) + ); + assert_eq!(pending_rewards(20).unwrap(), 1); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 10000000 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 1 } + ] + ); + }); + } + + #[test] + fn zero_reward_claim_does_not_update_reward_counter() { + // create a pool that has a quarter of the current polkadot issuance + ExtBuilder::default() + .ed(DOT) + .min_bond(POLKADOT_TOTAL_ISSUANCE_GENESIS / 4) + .build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 2500000000000000000, + joined: true, + } + ] + ); + + // and have a tiny fish join the pool as well.. + Balances::make_free_balance_be(&20, 20 * DOT); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1)); + + // earn some small rewards + assert_ok!( + Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) + ); + + // if 20 claims now, their reward counter should stay the same, so that they have a + // chance of claiming this if they let it accumulate. Also see + // `if_small_member_waits_long_enough_they_will_earn_rewards` + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Bonded { + member: 20, + pool_id: 1, + bonded: 100000000000, + joined: true + }, + Event::PaidOut { member: 10, pool_id: 1, payout: 9999997 } + ] + ); + + let current_reward_counter = default_pool_reward_counter(); + // has been updated, because they actually claimed something. + assert_eq!( + PoolMembers::::get(10).unwrap().last_recorded_reward_counter, + current_reward_counter + ); + // has not be updated, even though the claim transaction went through okay. + assert_eq!( + PoolMembers::::get(20).unwrap().last_recorded_reward_counter, + Default::default() + ); + }); + } +} diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs new file mode 100644 index 0000000000000..1062b1749d417 --- /dev/null +++ b/frame/nomination-pools/src/weights.rs @@ -0,0 +1,558 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_nomination_pools +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_nomination_pools +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/nomination-pools/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_nomination_pools. +pub trait WeightInfo { + fn join() -> Weight; + fn bond_extra_transfer() -> Weight; + fn bond_extra_reward() -> Weight; + fn claim_payout() -> Weight; + fn unbond() -> Weight; + fn pool_withdraw_unbonded(s: u32, ) -> Weight; + fn withdraw_unbonded_update(s: u32, ) -> Weight; + fn withdraw_unbonded_kill(s: u32, ) -> Weight; + fn create() -> Weight; + fn nominate(n: u32, ) -> Weight; + fn set_state() -> Weight; + fn set_metadata(n: u32, ) -> Weight; + fn set_configs() -> Weight; + fn update_roles() -> Weight; + fn chill() -> Weight; +} + +/// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:2 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) + fn join() -> Weight { + // Minimum execution time: 159_948 nanoseconds. + Weight::from_ref_time(161_133_000 as u64) + .saturating_add(T::DbWeight::get().reads(17 as u64)) + .saturating_add(T::DbWeight::get().writes(12 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:3 w:2) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) + fn bond_extra_transfer() -> Weight { + // Minimum execution time: 155_517 nanoseconds. + Weight::from_ref_time(159_101_000 as u64) + .saturating_add(T::DbWeight::get().reads(14 as u64)) + .saturating_add(T::DbWeight::get().writes(12 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:3 w:3) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) + fn bond_extra_reward() -> Weight { + // Minimum execution time: 172_788 nanoseconds. + Weight::from_ref_time(174_212_000 as u64) + .saturating_add(T::DbWeight::get().reads(14 as u64)) + .saturating_add(T::DbWeight::get().writes(13 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn claim_payout() -> Weight { + // Minimum execution time: 64_560 nanoseconds. + Weight::from_ref_time(64_950_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: System Account (r:2 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + fn unbond() -> Weight { + // Minimum execution time: 161_398 nanoseconds. + Weight::from_ref_time(162_991_000 as u64) + .saturating_add(T::DbWeight::get().reads(18 as u64)) + .saturating_add(T::DbWeight::get().writes(13 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + /// The range of component `s` is `[0, 100]`. + fn pool_withdraw_unbonded(s: u32, ) -> Weight { + // Minimum execution time: 66_036 nanoseconds. + Weight::from_ref_time(67_183_304 as u64) + // Standard Error: 565 + .saturating_add(Weight::from_ref_time(57_830 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// The range of component `s` is `[0, 100]`. + fn withdraw_unbonded_update(s: u32, ) -> Weight { + // Minimum execution time: 111_156 nanoseconds. + Weight::from_ref_time(112_507_059 as u64) + // Standard Error: 655 + .saturating_add(Weight::from_ref_time(53_711 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + // Storage: NominationPools Metadata (r:1 w:1) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + /// The range of component `s` is `[0, 100]`. + fn withdraw_unbonded_kill(s: u32, ) -> Weight { + // Minimum execution time: 168_270 nanoseconds. + Weight::from_ref_time(170_059_380 as u64) + // Standard Error: 1_506 + .saturating_add(Weight::from_ref_time(1_258 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(20 as u64)) + .saturating_add(T::DbWeight::get().writes(17 as u64)) + } + // Storage: NominationPools LastPoolId (r:1 w:1) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: NominationPools MinCreateBond (r:1 w:0) + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools MaxPools (r:1 w:0) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: System Account (r:2 w:2) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + fn create() -> Weight { + // Minimum execution time: 146_153 nanoseconds. + Weight::from_ref_time(146_955_000 as u64) + .saturating_add(T::DbWeight::get().reads(21 as u64)) + .saturating_add(T::DbWeight::get().writes(15 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking MaxNominatorsCount (r:1 w:0) + // Storage: Staking Validators (r:2 w:0) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: VoterList ListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + /// The range of component `n` is `[1, 16]`. + fn nominate(n: u32, ) -> Weight { + // Minimum execution time: 71_380 nanoseconds. + Weight::from_ref_time(71_060_388 as u64) + // Standard Error: 2_587 + .saturating_add(Weight::from_ref_time(1_185_729 as u64).saturating_mul(n as u64)) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + fn set_state() -> Weight { + // Minimum execution time: 46_275 nanoseconds. + Weight::from_ref_time(46_689_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: NominationPools Metadata (r:1 w:1) + // Storage: NominationPools CounterForMetadata (r:1 w:1) + /// The range of component `n` is `[1, 256]`. + fn set_metadata(n: u32, ) -> Weight { + // Minimum execution time: 19_246 nanoseconds. + Weight::from_ref_time(20_415_018 as u64) + // Standard Error: 95 + .saturating_add(Weight::from_ref_time(2_040 as u64).saturating_mul(n as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: NominationPools MinJoinBond (r:0 w:1) + // Storage: NominationPools MaxPoolMembers (r:0 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:0 w:1) + // Storage: NominationPools MinCreateBond (r:0 w:1) + // Storage: NominationPools MaxPools (r:0 w:1) + fn set_configs() -> Weight { + // Minimum execution time: 9_231 nanoseconds. + Weight::from_ref_time(9_526_000 as u64) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:1) + fn update_roles() -> Weight { + // Minimum execution time: 31_246 nanoseconds. + Weight::from_ref_time(31_762_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) + fn chill() -> Weight { + // Minimum execution time: 73_812 nanoseconds. + Weight::from_ref_time(74_790_000 as u64) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:2 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) + fn join() -> Weight { + // Minimum execution time: 159_948 nanoseconds. + Weight::from_ref_time(161_133_000 as u64) + .saturating_add(RocksDbWeight::get().reads(17 as u64)) + .saturating_add(RocksDbWeight::get().writes(12 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:3 w:2) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) + fn bond_extra_transfer() -> Weight { + // Minimum execution time: 155_517 nanoseconds. + Weight::from_ref_time(159_101_000 as u64) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(12 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:3 w:3) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) + fn bond_extra_reward() -> Weight { + // Minimum execution time: 172_788 nanoseconds. + Weight::from_ref_time(174_212_000 as u64) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(13 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn claim_payout() -> Weight { + // Minimum execution time: 64_560 nanoseconds. + Weight::from_ref_time(64_950_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: System Account (r:2 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + fn unbond() -> Weight { + // Minimum execution time: 161_398 nanoseconds. + Weight::from_ref_time(162_991_000 as u64) + .saturating_add(RocksDbWeight::get().reads(18 as u64)) + .saturating_add(RocksDbWeight::get().writes(13 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + /// The range of component `s` is `[0, 100]`. + fn pool_withdraw_unbonded(s: u32, ) -> Weight { + // Minimum execution time: 66_036 nanoseconds. + Weight::from_ref_time(67_183_304 as u64) + // Standard Error: 565 + .saturating_add(Weight::from_ref_time(57_830 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// The range of component `s` is `[0, 100]`. + fn withdraw_unbonded_update(s: u32, ) -> Weight { + // Minimum execution time: 111_156 nanoseconds. + Weight::from_ref_time(112_507_059 as u64) + // Standard Error: 655 + .saturating_add(Weight::from_ref_time(53_711 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + // Storage: NominationPools Metadata (r:1 w:1) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + /// The range of component `s` is `[0, 100]`. + fn withdraw_unbonded_kill(s: u32, ) -> Weight { + // Minimum execution time: 168_270 nanoseconds. + Weight::from_ref_time(170_059_380 as u64) + // Standard Error: 1_506 + .saturating_add(Weight::from_ref_time(1_258 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(20 as u64)) + .saturating_add(RocksDbWeight::get().writes(17 as u64)) + } + // Storage: NominationPools LastPoolId (r:1 w:1) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: NominationPools MinCreateBond (r:1 w:0) + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools MaxPools (r:1 w:0) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: System Account (r:2 w:2) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + fn create() -> Weight { + // Minimum execution time: 146_153 nanoseconds. + Weight::from_ref_time(146_955_000 as u64) + .saturating_add(RocksDbWeight::get().reads(21 as u64)) + .saturating_add(RocksDbWeight::get().writes(15 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking MaxNominatorsCount (r:1 w:0) + // Storage: Staking Validators (r:2 w:0) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: VoterList ListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + /// The range of component `n` is `[1, 16]`. + fn nominate(n: u32, ) -> Weight { + // Minimum execution time: 71_380 nanoseconds. + Weight::from_ref_time(71_060_388 as u64) + // Standard Error: 2_587 + .saturating_add(Weight::from_ref_time(1_185_729 as u64).saturating_mul(n as u64)) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + fn set_state() -> Weight { + // Minimum execution time: 46_275 nanoseconds. + Weight::from_ref_time(46_689_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: NominationPools Metadata (r:1 w:1) + // Storage: NominationPools CounterForMetadata (r:1 w:1) + /// The range of component `n` is `[1, 256]`. + fn set_metadata(n: u32, ) -> Weight { + // Minimum execution time: 19_246 nanoseconds. + Weight::from_ref_time(20_415_018 as u64) + // Standard Error: 95 + .saturating_add(Weight::from_ref_time(2_040 as u64).saturating_mul(n as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: NominationPools MinJoinBond (r:0 w:1) + // Storage: NominationPools MaxPoolMembers (r:0 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:0 w:1) + // Storage: NominationPools MinCreateBond (r:0 w:1) + // Storage: NominationPools MaxPools (r:0 w:1) + fn set_configs() -> Weight { + // Minimum execution time: 9_231 nanoseconds. + Weight::from_ref_time(9_526_000 as u64) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:1) + fn update_roles() -> Weight { + // Minimum execution time: 31_246 nanoseconds. + Weight::from_ref_time(31_762_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) + fn chill() -> Weight { + // Minimum execution time: 73_812 nanoseconds. + Weight::from_ref_time(74_790_000 as u64) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } +} diff --git a/frame/nomination-pools/test-staking/Cargo.toml b/frame/nomination-pools/test-staking/Cargo.toml new file mode 100644 index 0000000000000..a45c7852d4151 --- /dev/null +++ b/frame/nomination-pools/test-staking/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "pallet-nomination-pools-test-staking" +version = "1.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME nomination pools pallet tests with the staking pallet" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dev-dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +scale-info = { version = "2.0.1", features = ["derive"] } + +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-std = { version = "5.0.0", path = "../../../primitives/std" } +sp-staking = { version = "4.0.0-dev", path = "../../../primitives/staking" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } + +frame-system = { version = "4.0.0-dev", path = "../../system" } +frame-support = { version = "4.0.0-dev", path = "../../support" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } + +pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } +pallet-balances = { version = "4.0.0-dev", path = "../../balances" } +pallet-staking = { version = "4.0.0-dev", path = "../../staking" } +pallet-bags-list = { version = "4.0.0-dev", path = "../../bags-list" } +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } +pallet-nomination-pools = { version = "1.0.0-dev", path = ".." } + +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +log = { version = "0.4.0" } diff --git a/frame/nomination-pools/test-staking/src/lib.rs b/frame/nomination-pools/test-staking/src/lib.rs new file mode 100644 index 0000000000000..00e0e40ce33b0 --- /dev/null +++ b/frame/nomination-pools/test-staking/src/lib.rs @@ -0,0 +1,693 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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)] + +mod mock; + +use frame_support::{assert_noop, assert_ok, bounded_btree_map, traits::Currency}; +use mock::*; +use pallet_nomination_pools::{ + BondedPools, Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember, PoolMembers, + PoolState, +}; +use pallet_staking::{CurrentEra, Event as StakingEvent, Payee, RewardDestination}; +use sp_runtime::traits::Zero; + +#[test] +fn pool_lifecycle_e2e() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::minimum_balance(), 5); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + // have the pool nominate. + assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + ] + ); + + // have two members join + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); + assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true }, + ] + ); + + // pool goes into destroying + assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying)); + + // depositor cannot unbond yet. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 50), + PoolsError::::MinimumBondNotMet, + ); + + // now the members want to unbond. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); + + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_eras.len(), 1); + assert_eq!(PoolMembers::::get(20).unwrap().points, 0); + assert_eq!(PoolMembers::::get(21).unwrap().unbonding_eras.len(), 1); + assert_eq!(PoolMembers::::get(21).unwrap().points, 0); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10, era: 3 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10, era: 3 }, + ] + ); + + // depositor cannot still unbond + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 50), + PoolsError::::MinimumBondNotMet, + ); + + for e in 1..BondingDuration::get() { + CurrentEra::::set(Some(e)); + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0), + PoolsError::::CannotWithdrawAny + ); + } + + // members are now unlocked. + CurrentEra::::set(Some(BondingDuration::get())); + + // depositor cannot still unbond + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 50), + PoolsError::::MinimumBondNotMet, + ); + + // but members can now withdraw. + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); + assert!(PoolMembers::::get(20).is_none()); + assert!(PoolMembers::::get(21).is_none()); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 21 }, + ] + ); + + // as soon as all members have left, the depositor can try to unbond, but since the + // min-nominator intention is set, they must chill first. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 50), + pallet_staking::Error::::InsufficientBond + ); + + assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Chilled { stash: POOL1_BONDED }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 }] + ); + + // waiting another bonding duration: + CurrentEra::::set(Some(BondingDuration::get() * 2)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1)); + + // pools is fully destroyed now. + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::Destroyed { pool_id: 1 } + ] + ); + }) +} + +#[test] +fn pool_slash_e2e() { + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + assert_eq!(Payee::::get(POOL1_BONDED), RewardDestination::Account(POOL1_REWARD)); + + // have two members join + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1)); + assert_ok!(Pools::join(RuntimeOrigin::signed(21), 20, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 } + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true }, + ] + ); + + // now let's progress a bit. + CurrentEra::::set(Some(1)); + + // 20 / 80 of the total funds are unlocked, and safe from any further slash. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 } + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 4 } + ] + ); + + CurrentEra::::set(Some(2)); + + // note: depositor cannot fully unbond at this point. + // these funds will still get slashed. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + ] + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 5 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 5 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 5 }, + ] + ); + + // At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and and + // another 30 are active and vulnerable to slash. Let's slash half of them. + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 30, + &mut Default::default(), + &mut Default::default(), + 2, // slash era 2, affects chunks at era 5 onwards. + ); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + // 30 has been slashed to 15 (15 slash) + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 }, + // 30 has been slashed to 15 (15 slash) + PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } + ] + ); + + CurrentEra::::set(Some(3)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); + + assert_eq!( + PoolMembers::::get(21).unwrap(), + PoolMember { + pool_id: 1, + points: 0, + last_recorded_reward_counter: Zero::zero(), + // the 10 points unlocked just now correspond to 5 points in the unbond pool. + unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5) + } + ); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5, era: 6 }] + ); + + // now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free. + CurrentEra::::set(Some(6)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + // 20 had unbonded 10 safely, and 10 got slashed by half. + PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + // 21 unbonded all of it after the slash + PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 21 } + ] + ); + assert_eq!( + staking_events_since_last_call(), + // a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 15 + 10 + 15 }] + ); + + // now, finally, we can unbond the depositor further than their current limit. + assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 } + ] + ); + + CurrentEra::::set(Some(9)); + assert_eq!( + PoolMembers::::get(10).unwrap(), + PoolMember { + pool_id: 1, + points: 0, + last_recorded_reward_counter: Zero::zero(), + unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10) + } + ); + // withdraw the depositor, they should lose 12 balance in total due to slash. + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::Destroyed { pool_id: 1 } + ] + ); + }); +} + +#[test] +fn pool_slash_proportional() { + // a typical example where 3 pool members unbond in era 99, 100, and 101, and a slash that + // happened in era 100 should only affect the latter two. + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + BondingDuration::set(28); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + // have two members join + let bond = 20; + assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); + assert_ok!(Pools::join(RuntimeOrigin::signed(21), bond, 1)); + assert_ok!(Pools::join(RuntimeOrigin::signed(22), bond, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: bond, joined: true }, + PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: bond, joined: true }, + ] + ); + + // now let's progress a lot. + CurrentEra::::set(Some(99)); + + // and unbond + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: 20, + pool_id: 1, + balance: bond, + points: bond, + era: 127 + }] + ); + + CurrentEra::::set(Some(100)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: 21, + pool_id: 1, + balance: bond, + points: bond, + era: 128 + }] + ); + + CurrentEra::::set(Some(101)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: 22, + pool_id: 1, + balance: bond, + points: bond, + era: 129 + }] + ); + + // Apply a slash that happened in era 100. This is typically applied with a delay. + // Of the total 100, 50 is slashed. + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 50, + &mut Default::default(), + &mut Default::default(), + 100, + ); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + // This era got slashed 12.5, which rounded up to 13. + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 128, balance: 7 }, + // This era got slashed 12 instead of 12.5 because an earlier chunk got 0.5 more + // slashed, and 12 is all the remaining slash + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 129, balance: 8 }, + // Bonded pool got slashed for 25, remaining 15 in it. + PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } + ] + ); + }); +} + +#[test] +fn pool_slash_non_proportional_only_bonded_pool() { + // A typical example where a pool member unbonds in era 99, and he can get away with a slash + // that happened in era 100, as long as the pool has enough active bond to cover the slash. If + // everything else in the slashing/staking system works, this should always be the case. + // Nonetheless, `ledger.slash` has been written such that it will slash greedily from any chunk + // if it runs out of chunks that it thinks should be affected by the slash. + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + BondingDuration::set(28); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + // have two members join + let bond = 20; + assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }] + ); + + // progress and unbond. + CurrentEra::::set(Some(99)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: 20, + pool_id: 1, + balance: bond, + points: bond, + era: 127 + }] + ); + + // slash for 30. This will be deducted only from the bonded pool. + CurrentEra::::set(Some(100)); + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 30, + &mut Default::default(), + &mut Default::default(), + 100, + ); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::PoolSlashed { pool_id: 1, balance: 10 }] + ); + }); +} + +#[test] +fn pool_slash_non_proportional_bonded_pool_and_chunks() { + // An uncommon example where even though some funds are unlocked such that they should not be + // affected by a slash, we still slash out of them. This should not happen at all. If a + // nomination has unbonded, from the next era onwards, their exposure will drop, so if an era + // happens in that era, then their share of that slash should naturally be less, such that only + // their active ledger stake is enough to compensate it. + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + BondingDuration::set(28); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + // have two members join + let bond = 20; + assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }] + ); + + // progress and unbond. + CurrentEra::::set(Some(99)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: 20, + pool_id: 1, + balance: bond, + points: bond, + era: 127 + }] + ); + + // slash 50. This will be deducted only from the bonded pool and one of the unbonding pools. + CurrentEra::::set(Some(100)); + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 50, + &mut Default::default(), + &mut Default::default(), + 100, + ); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + // out of 20, 10 was taken. + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 127, balance: 10 }, + // out of 40, all was taken. + PoolsEvent::PoolSlashed { pool_id: 1, balance: 0 } + ] + ); + }); +} diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs new file mode 100644 index 0000000000000..568dec7b3a340 --- /dev/null +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -0,0 +1,269 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 frame_election_provider_support::VoteWeight; +use frame_support::{ + assert_ok, + pallet_prelude::*, + parameter_types, + traits::{ConstU64, ConstU8}, + PalletId, +}; +use sp_runtime::{ + traits::{Convert, IdentityLookup}, + FixedU128, +}; + +type AccountId = u128; +type AccountIndex = u32; +type BlockNumber = u64; +type Balance = u128; + +pub(crate) type T = Runtime; + +pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128; +pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128; + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = AccountIndex; + type BlockNumber = BlockNumber; + type RuntimeCall = RuntimeCall; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = sp_runtime::testing::Header; + type RuntimeEvent = RuntimeEvent; + type 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>; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +parameter_types! { + pub static ExistentialDeposit: Balance = 5; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +pallet_staking_reward_curve::build! { + const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} + +parameter_types! { + pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; + pub static BondingDuration: u32 = 3; +} + +impl pallet_staking::Config for Runtime { + type MaxNominations = ConstU32<16>; + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type RewardRemainder = (); + type RuntimeEvent = RuntimeEvent; + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = BondingDuration; + type SessionInterface = (); + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = (); + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = VoterList; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type OnStakerSlash = Pools; + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); +} + +parameter_types! { + pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; +} + +type VoterBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type BagThresholds = BagThresholds; + type ScoreProvider = Staking; + type Score = VoteWeight; +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub const PostUnbondingPoolsWindow: u32 = 10; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); +} + +impl pallet_nomination_pools::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type RewardCounter = FixedU128; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type Staking = Staking; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; + type MaxPointsToBalance = ConstU8<10>; + type PalletId = PoolsPalletId; +} + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, + VoterList: pallet_bags_list::::{Pallet, Call, Storage, Event}, + Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let _ = pallet_nomination_pools::GenesisConfig:: { + min_join_bond: 2, + min_create_bond: 2, + max_pools: Some(3), + max_members_per_pool: Some(5), + max_members: Some(3 * 5), + } + .assimilate_storage(&mut storage) + .unwrap(); + + let _ = pallet_balances::GenesisConfig:: { + balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], + } + .assimilate_storage(&mut storage) + .unwrap(); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // for events to be deposited. + frame_system::Pallet::::set_block_number(1); + + // set some limit for nominations. + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + pallet_staking::ConfigOp::Set(10), // minimum nominator bond + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + )); + }); + + ext +} + +parameter_types! { + static ObservedEventsPools: usize = 0; + static ObservedEventsStaking: usize = 0; + static ObservedEventsBalances: usize = 0; +} + +pub(crate) fn pool_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEventsPools::get(); + ObservedEventsPools::set(events.len()); + events.into_iter().skip(already_seen).collect() +} + +pub(crate) fn staking_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEventsStaking::get(); + ObservedEventsStaking::set(events.len()); + events.into_iter().skip(already_seen).collect() +} diff --git a/frame/offences/Cargo.toml b/frame/offences/Cargo.toml index 8fdcbf46fa3e1..107a0489cd594 100644 --- a/frame/offences/Cargo.toml +++ b/frame/offences/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-offences" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME offences pallet" readme = "README.md" @@ -13,34 +13,34 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -serde = { version = "1.0.126", optional = true } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.14", default-features = false } +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "pallet-balances/std", "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", "scale-info/std", - "sp-std/std", "serde", "sp-runtime/std", "sp-staking/std", - "frame-support/std", - "frame-system/std", - "log/std", + "sp-std/std", ] runtime-benchmarks = [] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/offences/benchmarking/Cargo.toml b/frame/offences/benchmarking/Cargo.toml index b21e6cf9b7e13..e20aefd69ad4d 100644 --- a/frame/offences/benchmarking/Cargo.toml +++ b/frame/offences/benchmarking/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-offences-benchmarking" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME offences pallet benchmarking" readme = "README.md" @@ -13,37 +13,35 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-babe = { version = "4.0.0-dev", default-features = false, path = "../../babe" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } pallet-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../grandpa" } pallet-im-online = { version = "4.0.0-dev", default-features = false, path = "../../im-online" } -pallet-offences = { version = "4.0.0-dev", default-features = false, features = [ - "runtime-benchmarks", -], path = "../../offences" } +pallet-offences = { version = "4.0.0-dev", default-features = false, path = "../../offences" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../../session" } -pallet-staking = { version = "4.0.0-dev", default-features = false, features = [ - "runtime-benchmarks", -], path = "../../staking" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } +pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../staking" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } -frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-io = { version = "4.0.0-dev", path = "../../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } [features] default = ["std"] std = [ + "codec/std", "frame-benchmarking/std", + "frame-election-provider-support/std", "frame-support/std", "frame-system/std", "pallet-babe/std", @@ -53,10 +51,12 @@ std = [ "pallet-offences/std", "pallet-session/std", "pallet-staking/std", + "scale-info/std", "sp-runtime/std", "sp-staking/std", - "frame-election-provider-support/std", "sp-std/std", - "codec/std", - "scale-info/std", +] + +runtime-benchmarks = [ + "pallet-staking/runtime-benchmarks", ] diff --git a/frame/offences/benchmarking/src/lib.rs b/frame/offences/benchmarking/src/lib.rs index 35e3c1aec9403..e5ec2952f8114 100644 --- a/frame/offences/benchmarking/src/lib.rs +++ b/frame/offences/benchmarking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,14 +17,15 @@ //! Offences pallet benchmarking. +#![cfg(feature = "runtime-benchmarks")] #![cfg_attr(not(feature = "std"), no_std)] mod mock; use sp_std::{prelude::*, vec}; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; -use frame_support::traits::{Currency, ValidatorSet, ValidatorSetWithIdentification}; +use frame_benchmarking::{account, benchmarks}; +use frame_support::traits::{Currency, Get, ValidatorSet, ValidatorSetWithIdentification}; use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin}; use sp_runtime::{ @@ -101,15 +102,14 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' let controller: T::AccountId = account("controller", n, SEED); let controller_lookup: LookupSourceOf = T::Lookup::unlookup(controller.clone()); let reward_destination = RewardDestination::Staked; - let raw_amount = bond_amount::(); + let amount = bond_amount::(); // add twice as much balance to prevent the account from being killed. - let free_amount = raw_amount.saturating_mul(2u32.into()); + let free_amount = amount.saturating_mul(2u32.into()); T::Currency::make_free_balance_be(&stash, free_amount); - let amount: BalanceOf = raw_amount.into(); Staking::::bond( RawOrigin::Signed(stash.clone()).into(), controller_lookup.clone(), - amount.clone(), + amount, reward_destination.clone(), )?; @@ -127,12 +127,12 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' account("nominator controller", n * MAX_NOMINATORS + i, SEED); let nominator_controller_lookup: LookupSourceOf = T::Lookup::unlookup(nominator_controller.clone()); - T::Currency::make_free_balance_be(&nominator_stash, free_amount.into()); + T::Currency::make_free_balance_be(&nominator_stash, free_amount); Staking::::bond( RawOrigin::Signed(nominator_stash.clone()).into(), nominator_controller_lookup.clone(), - amount.clone(), + amount, reward_destination.clone(), )?; @@ -143,14 +143,13 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' )?; individual_exposures - .push(IndividualExposure { who: nominator_stash.clone(), value: amount.clone() }); + .push(IndividualExposure { who: nominator_stash.clone(), value: amount }); nominator_stashes.push(nominator_stash.clone()); } - let exposure = - Exposure { total: amount.clone() * n.into(), own: amount, others: individual_exposures }; + let exposure = Exposure { total: amount * n.into(), own: amount, others: individual_exposures }; let current_era = 0u32; - Staking::::add_era_stakers(current_era.into(), stash.clone().into(), exposure); + Staking::::add_era_stakers(current_era, stash.clone(), exposure); Ok(Offender { controller, stash, nominator_stashes }) } @@ -218,33 +217,55 @@ fn make_offenders_im_online( } #[cfg(test)] -fn check_events::Event>>(expected: I) { +fn check_events::RuntimeEvent>>(expected: I) { let events = System::::events() .into_iter() .map(|frame_system::EventRecord { event, .. }| event) .collect::>(); let expected = expected.collect::>(); - let lengths = (events.len(), expected.len()); - let length_mismatch = if lengths.0 != lengths.1 { - fn pretty(header: &str, ev: &[D]) { - println!("{}", header); - for (idx, ev) in ev.iter().enumerate() { - println!("\t[{:04}] {:?}", idx, ev); - } + + fn pretty(header: &str, ev: &[D], offset: usize) { + println!("{}", header); + for (idx, ev) in ev.iter().enumerate() { + println!("\t[{:04}] {:?}", idx + offset, ev); } - pretty("--Got:", &events); - pretty("--Expected:", &expected); - format!("Mismatching length. Got: {}, expected: {}", lengths.0, lengths.1) - } else { - Default::default() - }; + } + fn print_events(idx: usize, events: &[D], expected: &[D]) { + let window = 10; + let start = idx.saturating_sub(window / 2); + let end_got = (idx + window / 2).min(events.len()); + pretty("Got(window):", &events[start..end_got], start); + let end_expected = (idx + window / 2).min(expected.len()); + pretty("Expected(window):", &expected[start..end_expected], start); + println!("---------------"); + let start_got = events.len().saturating_sub(window); + pretty("Got(end):", &events[start_got..], start_got); + let start_expected = expected.len().saturating_sub(window); + pretty("Expected(end):", &expected[start_expected..], start_expected); + } + let events_copy = events.clone(); + let expected_copy = expected.clone(); for (idx, (a, b)) in events.into_iter().zip(expected).enumerate() { - assert_eq!(a, b, "Mismatch at: {}. {}", idx, length_mismatch); + if a != b { + print_events(idx, &events_copy, &expected_copy); + println!("Mismatch at: {}", idx); + println!(" Got: {:?}", b); + println!("Expected: {:?}", a); + if events_copy.len() != expected_copy.len() { + println!( + "Mismatching lengths. Got: {}, Expected: {}", + events_copy.len(), + expected_copy.len() + ) + } + panic!("Mismatching events."); + } } - if !length_mismatch.is_empty() { - panic!("{}", length_mismatch); + if events_copy.len() != expected_copy.len() { + print_events(0, &events_copy, &expected_copy); + panic!("Mismatching lengths. Got: {}, Expected: {}", events_copy.len(), expected_copy.len()) } } @@ -253,7 +274,7 @@ benchmarks! { let r in 1 .. MAX_REPORTERS; // we skip 1 offender, because in such case there is no slashing let o in 2 .. MAX_OFFENDERS; - let n in 0 .. MAX_NOMINATORS.min(::MAX_NOMINATIONS); + let n in 0 .. MAX_NOMINATORS.min(::MaxNominations::get()); // Make r reporters let mut reporters = vec![]; @@ -268,15 +289,13 @@ benchmarks! { let (offenders, raw_offenders) = make_offenders_im_online::(o, n)?; let keys = ImOnline::::keys(); let validator_set_count = keys.len() as u32; - - let slash_fraction = UnresponsivenessOffence::::slash_fraction( - offenders.len() as u32, validator_set_count, - ); + let offenders_count = offenders.len() as u32; let offence = UnresponsivenessOffence { session_index: 0, validator_set_count, offenders, }; + let slash_fraction = offence.slash_fraction(offenders_count); assert_eq!(System::::event_count(), 0); }: { let _ = ::ReportUnresponsiveness::report_offence( @@ -287,51 +306,83 @@ benchmarks! { verify { let bond_amount: u32 = UniqueSaturatedInto::::unique_saturated_into(bond_amount::()); let slash_amount = slash_fraction * bond_amount; - let reward_amount = slash_amount * (1 + n) / 2; + let reward_amount = slash_amount.saturating_mul(1 + n) / 2; + let reward = reward_amount / r; + let slash_report = |id| core::iter::once( + ::RuntimeEvent::from(StakingEvent::::SlashReported{ validator: id, fraction: slash_fraction, slash_era: 0}) + ); let slash = |id| core::iter::once( - ::Event::from(StakingEvent::::Slashed(id, BalanceOf::::from(slash_amount))) + ::RuntimeEvent::from(StakingEvent::::Slashed{ staker: id, amount: BalanceOf::::from(slash_amount) }) + ); + let balance_slash = |id| core::iter::once( + ::RuntimeEvent::from(pallet_balances::Event::::Slashed{ who: id, amount: slash_amount.into() }) ); let chill = |id| core::iter::once( - ::Event::from(StakingEvent::::Chilled(id)) + ::RuntimeEvent::from(StakingEvent::::Chilled{ stash: id }) ); - let mut slash_events = raw_offenders.into_iter() + let balance_deposit = |id, amount: u32| + ::RuntimeEvent::from(pallet_balances::Event::::Deposit{ who: id, amount: amount.into() }); + let mut first = true; + let slash_events = raw_offenders.into_iter() .flat_map(|offender| { - let nom_slashes = offender.nominator_stashes.into_iter().flat_map(|nom| slash(nom)); - chill(offender.stash.clone()) - .chain(slash(offender.stash)) - .chain(nom_slashes) + let nom_slashes = offender.nominator_stashes.into_iter().flat_map(|nom| { + balance_slash(nom.clone()).map(Into::into) + .chain(slash(nom).map(Into::into)) + }); + + let mut events = chill(offender.stash.clone()).map(Into::into) + .chain(slash_report(offender.stash.clone()).map(Into::into)) + .chain(balance_slash(offender.stash.clone()).map(Into::into)) + .chain(slash(offender.stash).map(Into::into)) + .chain(nom_slashes) + .collect::>(); + + // the first deposit creates endowed events, see `endowed_reward_events` + if first { + first = false; + let mut reward_events = reporters.clone().into_iter() + .flat_map(|reporter| vec![ + balance_deposit(reporter.clone(), reward).into(), + frame_system::Event::::NewAccount { account: reporter.clone() }.into(), + ::RuntimeEvent::from( + pallet_balances::Event::::Endowed{account: reporter, free_balance: reward.into()} + ).into(), + ]) + .collect::>(); + events.append(&mut reward_events); + events.into_iter() + } else { + let mut reward_events = reporters.clone().into_iter() + .map(|reporter| balance_deposit(reporter, reward).into()) + .collect::>(); + events.append(&mut reward_events); + events.into_iter() + } }) .collect::>(); - let reward_events = reporters.into_iter() - .flat_map(|reporter| vec![ - frame_system::Event::::NewAccount(reporter.clone()).into(), - ::Event::from( - pallet_balances::Event::::Endowed(reporter, (reward_amount / r).into()) - ).into() - ]); - - // Rewards are applied after first offender and it's nominators. - // We split after: offender slash + offender chill + nominator slashes. - let slash_rest = slash_events.split_off(2 + n as usize); - // make sure that all slashes have been applied + + #[cfg(test)] - check_events::( - std::iter::empty() - .chain(slash_events.into_iter().map(Into::into)) - .chain(reward_events) - .chain(slash_rest.into_iter().map(Into::into)) - .chain(std::iter::once(::Event::from( - pallet_offences::Event::Offence( - UnresponsivenessOffence::::ID, - 0_u32.to_le_bytes().to_vec(), - ) - ).into())) - ); + { + // In case of error it's useful to see the inputs + println!("Inputs: r: {}, o: {}, n: {}", r, o, n); + // make sure that all slashes have been applied + check_events::( + std::iter::empty() + .chain(slash_events.into_iter().map(Into::into)) + .chain(std::iter::once(::RuntimeEvent::from( + pallet_offences::Event::Offence{ + kind: UnresponsivenessOffence::::ID, + timeslot: 0_u32.to_le_bytes().to_vec(), + } + ).into())) + ); + } } report_offence_grandpa { - let n in 0 .. MAX_NOMINATORS.min(::MAX_NOMINATIONS); + let n in 0 .. MAX_NOMINATORS.min(::MaxNominations::get()); // for grandpa equivocation reports the number of reporters // and offenders is always 1 @@ -355,18 +406,20 @@ benchmarks! { } verify { // make sure that all slashes have been applied + #[cfg(test)] assert_eq!( System::::event_count(), 0 + 1 // offence - + 2 // reporter (reward + endowment) - + 1 // offenders slashed + + 3 // reporter (reward + endowment) + + 1 // offenders reported + + 2 // offenders slashed + 1 // offenders chilled - + n // nominators slashed + + 2 * n // nominators slashed ); } report_offence_babe { - let n in 0 .. MAX_NOMINATORS.min(::MAX_NOMINATIONS); + let n in 0 .. MAX_NOMINATORS.min(::MaxNominations::get()); // for babe equivocation reports the number of reporters // and offenders is always 1 @@ -390,15 +443,17 @@ benchmarks! { } verify { // make sure that all slashes have been applied + #[cfg(test)] assert_eq!( System::::event_count(), 0 + 1 // offence - + 2 // reporter (reward + endowment) - + 1 // offenders slashed + + 3 // reporter (reward + endowment) + + 1 // offenders reported + + 2 // offenders slashed + 1 // offenders chilled - + n // nominators slashed + + 2 * n // nominators slashed ); } -} -impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index c4fd88def0e33..de3a4eca6308d 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,12 @@ #![cfg(test)] use super::*; -use frame_election_provider_support::onchain; -use frame_support::{parameter_types, weights::constants::WEIGHT_PER_SECOND}; +use frame_election_provider_support::{onchain, SequentialPhragmen}; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, +}; use frame_system as system; use pallet_session::historical as pallet_session_historical; use sp_runtime::{ @@ -36,7 +40,9 @@ type Balance = u64; parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(2 * WEIGHT_PER_SECOND); + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX) + ); } impl frame_system::Config for Test { @@ -44,16 +50,16 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = AccountIndex; type BlockNumber = BlockNumber; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = sp_runtime::testing::Header; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); type PalletInfo = PalletInfo; @@ -63,29 +69,25 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: Balance = 10; -} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = Balance; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<10>; type AccountStore = System; type WeightInfo = (); } -parameter_types! { - pub const MinimumPeriod: u64 = 5; -} impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<5>; type WeightInfo = (); } impl pallet_session::historical::Config for Test { @@ -112,7 +114,7 @@ impl pallet_session::SessionHandler for TestSessionHandler { ) { } - fn on_disabled(_: usize) {} + fn on_disabled(_: u32) {} } parameter_types! { @@ -126,10 +128,9 @@ impl pallet_session::Config for Test { type ShouldEndSession = pallet_session::PeriodicSessions; type NextSessionRotation = pallet_session::PeriodicSessions; type SessionHandler = TestSessionHandler; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ValidatorId = AccountId; type ValidatorIdOf = pallet_staking::StashOf; - type DisabledValidatorsThreshold = (); type WeightInfo = (); } @@ -145,23 +146,29 @@ pallet_staking_reward_curve::build! { } parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; - pub const MaxNominatorRewardedPerValidator: u32 = 64; } -pub type Extrinsic = sp_runtime::testing::TestXt; +pub type Extrinsic = sp_runtime::testing::TestXt; -impl onchain::Config for Test { - type Accuracy = Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = ConstU32<100>; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; } impl pallet_staking::Config for Test { - const MAX_NOMINATIONS: u32 = 16; + type MaxNominations = ConstU32<16>; type Currency = Balances; + type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type RewardRemainder = (); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Slash = (); type Reward = (); type SessionsPerEra = (); @@ -171,40 +178,50 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type OnStakerSlash = (); + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } impl pallet_im_online::Config for Test { type AuthorityId = UintAuthorityId; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ValidatorSet = Historical; type NextSessionRotation = pallet_session::PeriodicSessions; type ReportUnresponsiveness = Offences; type UnsignedPriority = (); type WeightInfo = (); + type MaxKeys = ConstU32<10_000>; + type MaxPeerInHeartbeats = ConstU32<10_000>; + type MaxPeerDataEncodingSize = ConstU32<1_000>; } impl pallet_offences::Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type IdentificationTuple = pallet_session::historical::IdentificationTuple; type OnOffenceHandler = Staking; } impl frame_system::offchain::SendTransactionTypes for Test where - Call: From, + RuntimeCall: From, { type Extrinsic = Extrinsic; - type OverarchingCall = Call; + type OverarchingCall = RuntimeCall; } impl crate::Config for Test {} pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( pub enum Test where diff --git a/frame/offences/src/lib.rs b/frame/offences/src/lib.rs index 3392cd6e4a884..7858b02719c4c 100644 --- a/frame/offences/src/lib.rs +++ b/frame/offences/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -mod migration; +pub mod migration; mod mock; mod tests; @@ -43,45 +43,23 @@ type OpaqueTimeSlot = Vec; /// A type alias for a report identifier. type ReportIdOf = ::Hash; -pub trait WeightInfo { - fn report_offence_im_online(r: u32, o: u32, n: u32) -> Weight; - fn report_offence_grandpa(r: u32, n: u32) -> Weight; - fn report_offence_babe(r: u32, n: u32) -> Weight; - fn on_initialize(d: u32) -> Weight; -} - -impl WeightInfo for () { - fn report_offence_im_online(_r: u32, _o: u32, _n: u32) -> Weight { - 1_000_000_000 - } - fn report_offence_grandpa(_r: u32, _n: u32) -> Weight { - 1_000_000_000 - } - fn report_offence_babe(_r: u32, _n: u32) -> Weight { - 1_000_000_000 - } - fn on_initialize(_d: u32) -> Weight { - 1_000_000_000 - } -} - #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] pub struct Pallet(_); /// The pallet's config trait. #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From + IsType<::Event>; + type RuntimeEvent: From + IsType<::RuntimeEvent>; /// Full identification of the validator. - type IdentificationTuple: Parameter + Ord; + type IdentificationTuple: Parameter; /// A handler called for every offence report. type OnOffenceHandler: OnOffenceHandler; } @@ -130,14 +108,7 @@ pub mod pallet { /// There is an offence reported of the given `kind` happened at the `session_index` and /// (kind-specific) time slot. This event is not deposited for duplicate slashes. /// \[kind, timeslot\]. - Offence(Kind, OpaqueTimeSlot), - } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_runtime_upgrade() -> Weight { - migration::remove_deferred_storage::() - } + Offence { kind: Kind, timeslot: OpaqueTimeSlot }, } } @@ -149,7 +120,6 @@ where fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { let offenders = offence.offenders(); let time_slot = offence.time_slot(); - let validator_set_count = offence.validator_set_count(); // Go through all offenders in the offence report and find all offenders that were spotted // in unique reports. @@ -163,19 +133,19 @@ where let offenders_count = concurrent_offenders.len() as u32; // The amount new offenders are slashed - let new_fraction = O::slash_fraction(offenders_count, validator_set_count); + let new_fraction = offence.slash_fraction(offenders_count); - let slash_perbill: Vec<_> = - (0..concurrent_offenders.len()).map(|_| new_fraction.clone()).collect(); + let slash_perbill: Vec<_> = (0..concurrent_offenders.len()).map(|_| new_fraction).collect(); T::OnOffenceHandler::on_offence( &concurrent_offenders, &slash_perbill, offence.session_index(), + offence.disable_strategy(), ); // Deposit the event. - Self::deposit_event(Event::Offence(O::ID, time_slot.encode())); + Self::deposit_event(Event::Offence { kind: O::ID, timeslot: time_slot.encode() }); Ok(()) } @@ -230,7 +200,7 @@ impl Pallet { let concurrent_offenders = storage .concurrent_reports .iter() - .filter_map(|report_id| >::get(report_id)) + .filter_map(>::get) .collect::>(); storage.save(); diff --git a/frame/offences/src/migration.rs b/frame/offences/src/migration.rs index b6e32cbe69e26..e8fab1c0babc7 100644 --- a/frame/offences/src/migration.rs +++ b/frame/offences/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,10 +16,8 @@ // limitations under the License. use super::{Config, OffenceDetails, Perbill, SessionIndex}; -use frame_support::{ - generate_storage_alias, pallet_prelude::ValueQuery, traits::Get, weights::Weight, -}; -use sp_staking::offence::OnOffenceHandler; +use frame_support::{pallet_prelude::ValueQuery, storage_alias, traits::Get, weights::Weight}; +use sp_staking::offence::{DisableStrategy, OnOffenceHandler}; use sp_std::vec::Vec; /// Type of data stored as a deferred offence @@ -31,17 +29,21 @@ type DeferredOffenceOf = ( // Deferred reports that have been rejected by the offence handler and need to be submitted // at a later time. -generate_storage_alias!( - Offences, - DeferredOffences => Value>, ValueQuery> -); +#[storage_alias] +type DeferredOffences = + StorageValue, Vec>, ValueQuery>; pub fn remove_deferred_storage() -> Weight { let mut weight = T::DbWeight::get().reads_writes(1, 1); let deferred = >::take(); log::info!(target: "runtime::offences", "have {} deferred offences, applying.", deferred.len()); for (offences, perbill, session) in deferred.iter() { - let consumed = T::OnOffenceHandler::on_offence(&offences, &perbill, *session); + let consumed = T::OnOffenceHandler::on_offence( + offences, + perbill, + *session, + DisableStrategy::WhenSlashed, + ); weight = weight.saturating_add(consumed); } @@ -51,8 +53,7 @@ pub fn remove_deferred_storage() -> Weight { #[cfg(test)] mod test { use super::*; - use crate::mock::{new_test_ext, with_on_offence_fractions, Offences, Runtime as T}; - use frame_support::traits::OnRuntimeUpgrade; + use crate::mock::{new_test_ext, with_on_offence_fractions, Runtime as T}; use sp_runtime::Perbill; use sp_staking::offence::OffenceDetails; @@ -82,7 +83,7 @@ mod test { // when assert_eq!( - Offences::on_runtime_upgrade(), + remove_deferred_storage::(), ::DbWeight::get().reads_writes(1, 1), ); diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index 5e4c94944b6fd..8e4256ec3d3e6 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,8 +24,9 @@ use crate::Config; use codec::Encode; use frame_support::{ parameter_types, + traits::{ConstU32, ConstU64}, weights::{ - constants::{RocksDbWeight, WEIGHT_PER_SECOND}, + constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, Weight, }, }; @@ -36,16 +37,15 @@ use sp_runtime::{ Perbill, }; use sp_staking::{ - offence::{self, Kind, OffenceDetails}, + offence::{self, DisableStrategy, Kind, OffenceDetails}, SessionIndex, }; -use std::cell::RefCell; pub struct OnOffenceHandler; -thread_local! { - pub static ON_OFFENCE_PERBILL: RefCell> = RefCell::new(Default::default()); - pub static OFFENCE_WEIGHT: RefCell = RefCell::new(Default::default()); +parameter_types! { + pub static OnOffencePerbill: Vec = Default::default(); + pub static OffenceWeight: Weight = Default::default(); } impl offence::OnOffenceHandler @@ -55,17 +55,18 @@ impl offence::OnOffenceHandler _offenders: &[OffenceDetails], slash_fraction: &[Perbill], _offence_session: SessionIndex, + _disable_strategy: DisableStrategy, ) -> Weight { - ON_OFFENCE_PERBILL.with(|f| { - *f.borrow_mut() = slash_fraction.to_vec(); + OnOffencePerbill::mutate(|f| { + *f = slash_fraction.to_vec(); }); - OFFENCE_WEIGHT.with(|w| *w.borrow()) + OffenceWeight::get() } } pub fn with_on_offence_fractions) -> R>(f: F) -> R { - ON_OFFENCE_PERBILL.with(|fractions| f(&mut *fractions.borrow_mut())) + OnOffencePerbill::mutate(|fractions| f(fractions)) } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -83,26 +84,27 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(2 * WEIGHT_PER_SECOND); + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + ); } impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = RocksDbWeight; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -111,10 +113,11 @@ impl frame_system::Config for Runtime { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type IdentificationTuple = u64; type OnOffenceHandler = OnOffenceHandler; } @@ -166,8 +169,8 @@ impl offence::Offence for Offence { 1 } - fn slash_fraction(offenders_count: u32, validator_set_count: u32) -> Perbill { - Perbill::from_percent(5 + offenders_count * 100 / validator_set_count) + fn slash_fraction(&self, offenders_count: u32) -> Perbill { + Perbill::from_percent(5 + offenders_count * 100 / self.validator_set_count) } } diff --git a/frame/offences/src/tests.rs b/frame/offences/src/tests.rs index 18cfa9410a6c6..266e05debf050 100644 --- a/frame/offences/src/tests.rs +++ b/frame/offences/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,8 @@ use super::*; use crate::mock::{ - new_test_ext, offence_reports, report_id, with_on_offence_fractions, Event, Offence, Offences, - System, KIND, + new_test_ext, offence_reports, report_id, with_on_offence_fractions, Offence, Offences, + RuntimeEvent, System, KIND, }; use frame_system::{EventRecord, Phase}; use sp_runtime::Perbill; @@ -114,7 +114,10 @@ fn should_deposit_event() { System::events(), vec![EventRecord { phase: Phase::Initialization, - event: Event::Offences(crate::Event::Offence(KIND, time_slot.encode())), + event: RuntimeEvent::Offences(crate::Event::Offence { + kind: KIND, + timeslot: time_slot.encode() + }), topics: vec![], }] ); @@ -145,7 +148,10 @@ fn doesnt_deposit_event_for_dups() { System::events(), vec![EventRecord { phase: Phase::Initialization, - event: Event::Offences(crate::Event::Offence(KIND, time_slot.encode())), + event: RuntimeEvent::Offences(crate::Event::Offence { + kind: KIND, + timeslot: time_slot.encode() + }), topics: vec![], }] ); diff --git a/frame/preimage/Cargo.toml b/frame/preimage/Cargo.toml new file mode 100644 index 0000000000000..def39d61d5175 --- /dev/null +++ b/frame/preimage/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "pallet-preimage" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet for storing preimages of hashes" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "7.0.0", default-features = false, optional = true, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +log = { version = "0.4.17", default-features = false } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = [ + "frame-support/try-runtime", +] diff --git a/frame/preimage/src/benchmarking.rs b/frame/preimage/src/benchmarking.rs new file mode 100644 index 0000000000000..8a61d7d780bfd --- /dev/null +++ b/frame/preimage/src/benchmarking.rs @@ -0,0 +1,167 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Preimage pallet benchmarking. + +use super::*; +use frame_benchmarking::{account, benchmarks, whitelist_account}; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use sp_runtime::traits::Bounded; +use sp_std::{prelude::*, vec}; + +use crate::Pallet as Preimage; + +const SEED: u32 = 0; + +fn funded_account(name: &'static str, index: u32) -> T::AccountId { + let caller: T::AccountId = account(name, index, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + caller +} + +fn preimage_and_hash() -> (Vec, T::Hash) { + sized_preimage_and_hash::(MAX_SIZE) +} + +fn sized_preimage_and_hash(size: u32) -> (Vec, T::Hash) { + let mut preimage = vec![]; + preimage.resize(size as usize, 0); + let hash = ::Hashing::hash(&preimage[..]); + (preimage, hash) +} + +benchmarks! { + // Expensive note - will reserve. + note_preimage { + let s in 0 .. MAX_SIZE; + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let (preimage, hash) = sized_preimage_and_hash::(s); + }: _(RawOrigin::Signed(caller), preimage) + verify { + assert!(Preimage::::have_preimage(&hash)); + } + // Cheap note - will not reserve since it was requested. + note_requested_preimage { + let s in 0 .. MAX_SIZE; + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let (preimage, hash) = sized_preimage_and_hash::(s); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + }: note_preimage(RawOrigin::Signed(caller), preimage) + verify { + assert!(Preimage::::have_preimage(&hash)); + } + // Cheap note - will not reserve since it's the manager. + note_no_deposit_preimage { + let s in 0 .. MAX_SIZE; + let (preimage, hash) = sized_preimage_and_hash::(s); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + }: note_preimage(T::ManagerOrigin::successful_origin(), preimage) + verify { + assert!(Preimage::::have_preimage(&hash)); + } + + // Expensive unnote - will unreserve. + unnote_preimage { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + let (preimage, hash) = preimage_and_hash::(); + assert_ok!(Preimage::::note_preimage(RawOrigin::Signed(caller.clone()).into(), preimage)); + }: _(RawOrigin::Signed(caller), hash) + verify { + assert!(!Preimage::::have_preimage(&hash)); + } + // Cheap unnote - will not unreserve since there's no deposit held. + unnote_no_deposit_preimage { + let (preimage, hash) = preimage_and_hash::(); + assert_ok!(Preimage::::note_preimage(T::ManagerOrigin::successful_origin(), preimage)); + }: unnote_preimage(T::ManagerOrigin::successful_origin(), hash) + verify { + assert!(!Preimage::::have_preimage(&hash)); + } + + // Expensive request - will unreserve the noter's deposit. + request_preimage { + let (preimage, hash) = preimage_and_hash::(); + let noter = funded_account::("noter", 0); + whitelist_account!(noter); + assert_ok!(Preimage::::note_preimage(RawOrigin::Signed(noter.clone()).into(), preimage)); + }: _(T::ManagerOrigin::successful_origin(), hash) + verify { + let deposit = T::BaseDeposit::get() + T::ByteDeposit::get() * MAX_SIZE.into(); + let s = RequestStatus::Requested { deposit: Some((noter, deposit)), count: 1, len: Some(MAX_SIZE) }; + assert_eq!(StatusFor::::get(&hash), Some(s)); + } + // Cheap request - would unreserve the deposit but none was held. + request_no_deposit_preimage { + let (preimage, hash) = preimage_and_hash::(); + assert_ok!(Preimage::::note_preimage(T::ManagerOrigin::successful_origin(), preimage)); + }: request_preimage(T::ManagerOrigin::successful_origin(), hash) + verify { + let s = RequestStatus::Requested { deposit: None, count: 2, len: Some(MAX_SIZE) }; + assert_eq!(StatusFor::::get(&hash), Some(s)); + } + // Cheap request - the preimage is not yet noted, so deposit to unreserve. + request_unnoted_preimage { + let (_, hash) = preimage_and_hash::(); + }: request_preimage(T::ManagerOrigin::successful_origin(), hash) + verify { + let s = RequestStatus::Requested { deposit: None, count: 1, len: None }; + assert_eq!(StatusFor::::get(&hash), Some(s)); + } + // Cheap request - the preimage is already requested, so just a counter bump. + request_requested_preimage { + let (_, hash) = preimage_and_hash::(); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + }: request_preimage(T::ManagerOrigin::successful_origin(), hash) + verify { + let s = RequestStatus::Requested { deposit: None, count: 2, len: None }; + assert_eq!(StatusFor::::get(&hash), Some(s)); + } + + // Expensive unrequest - last reference and it's noted, so will destroy the preimage. + unrequest_preimage { + let (preimage, hash) = preimage_and_hash::(); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + assert_ok!(Preimage::::note_preimage(T::ManagerOrigin::successful_origin(), preimage)); + }: _(T::ManagerOrigin::successful_origin(), hash) + verify { + assert_eq!(StatusFor::::get(&hash), None); + } + // Cheap unrequest - last reference, but it's not noted. + unrequest_unnoted_preimage { + let (_, hash) = preimage_and_hash::(); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + }: unrequest_preimage(T::ManagerOrigin::successful_origin(), hash) + verify { + assert_eq!(StatusFor::::get(&hash), None); + } + // Cheap unrequest - not the last reference. + unrequest_multi_referenced_preimage { + let (_, hash) = preimage_and_hash::(); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + }: unrequest_preimage(T::ManagerOrigin::successful_origin(), hash) + verify { + let s = RequestStatus::Requested { deposit: None, count: 1, len: None }; + assert_eq!(StatusFor::::get(&hash), Some(s)); + } + + impl_benchmark_test_suite!(Preimage, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/preimage/src/lib.rs b/frame/preimage/src/lib.rs new file mode 100644 index 0000000000000..bf7d602057cac --- /dev/null +++ b/frame/preimage/src/lib.rs @@ -0,0 +1,460 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! # Preimage Pallet +//! +//! - [`Config`] +//! - [`Call`] +//! +//! ## Overview +//! +//! The Preimage pallet allows for the users and the runtime to store the preimage +//! of a hash on chain. This can be used by other pallets for storing and managing +//! large byte-blobs. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod migration; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +use sp_runtime::traits::{BadOrigin, Hash, Saturating}; +use sp_std::{borrow::Cow, prelude::*}; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + dispatch::Pays, + ensure, + pallet_prelude::Get, + traits::{ + Currency, Defensive, FetchResult, Hash as PreimageHash, PreimageProvider, + PreimageRecipient, QueryPreimage, ReservableCurrency, StorePreimage, + }, + BoundedSlice, BoundedVec, +}; +use scale_info::TypeInfo; +pub use weights::WeightInfo; + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +pub use pallet::*; + +/// A type to note whether a preimage is owned by a user or the system. +#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub enum RequestStatus { + /// The associated preimage has not yet been requested by the system. The given deposit (if + /// some) is being held until either it becomes requested or the user retracts the preimage. + Unrequested { deposit: (AccountId, Balance), len: u32 }, + /// There are a non-zero number of outstanding requests for this hash by this chain. If there + /// is a preimage registered, then `len` is `Some` and it may be removed iff this counter + /// becomes zero. + Requested { deposit: Option<(AccountId, Balance)>, count: u32, len: Option }, +} + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// Maximum size of preimage we can store is 4mb. +const MAX_SIZE: u32 = 4 * 1024 * 1024; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The Weight information for this pallet. + type WeightInfo: weights::WeightInfo; + + /// Currency type for this pallet. + type Currency: ReservableCurrency; + + /// An origin that can request a preimage be placed on-chain without a deposit or fee, or + /// manage existing preimages. + type ManagerOrigin: EnsureOrigin; + + /// The base deposit for placing a preimage on chain. + type BaseDeposit: Get>; + + /// The per-byte deposit for placing a preimage on chain. + type ByteDeposit: Get>; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A preimage has been noted. + Noted { hash: T::Hash }, + /// A preimage has been requested. + Requested { hash: T::Hash }, + /// A preimage has ben cleared. + Cleared { hash: T::Hash }, + } + + #[pallet::error] + pub enum Error { + /// Preimage is too large to store on-chain. + TooBig, + /// Preimage has already been noted on-chain. + AlreadyNoted, + /// The user is not authorized to perform this action. + NotAuthorized, + /// The preimage cannot be removed since it has not yet been noted. + NotNoted, + /// A preimage may not be removed when there are outstanding requests. + Requested, + /// The preimage request cannot be removed since no outstanding requests exist. + NotRequested, + } + + /// The request status of a given hash. + #[pallet::storage] + pub(super) type StatusFor = + StorageMap<_, Identity, T::Hash, RequestStatus>>; + + #[pallet::storage] + pub(super) type PreimageFor = + StorageMap<_, Identity, (T::Hash, u32), BoundedVec>>; + + #[pallet::call] + impl Pallet { + /// Register a preimage on-chain. + /// + /// If the preimage was previously requested, no fees or deposits are taken for providing + /// the preimage. Otherwise, a deposit is taken proportional to the size of the preimage. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::note_preimage(bytes.len() as u32))] + pub fn note_preimage(origin: OriginFor, bytes: Vec) -> DispatchResultWithPostInfo { + // We accept a signed origin which will pay a deposit, or a root origin where a deposit + // is not taken. + let maybe_sender = Self::ensure_signed_or_manager(origin)?; + let (system_requested, _) = Self::note_bytes(bytes.into(), maybe_sender.as_ref())?; + if system_requested || maybe_sender.is_none() { + Ok(Pays::No.into()) + } else { + Ok(().into()) + } + } + + /// Clear an unrequested preimage from the runtime storage. + /// + /// If `len` is provided, then it will be a much cheaper operation. + /// + /// - `hash`: The hash of the preimage to be removed from the store. + /// - `len`: The length of the preimage of `hash`. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::unnote_preimage())] + pub fn unnote_preimage(origin: OriginFor, hash: T::Hash) -> DispatchResult { + let maybe_sender = Self::ensure_signed_or_manager(origin)?; + Self::do_unnote_preimage(&hash, maybe_sender) + } + + /// Request a preimage be uploaded to the chain without paying any fees or deposits. + /// + /// If the preimage requests has already been provided on-chain, we unreserve any deposit + /// a user may have paid, and take the control of the preimage out of their hands. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::request_preimage())] + pub fn request_preimage(origin: OriginFor, hash: T::Hash) -> DispatchResult { + T::ManagerOrigin::ensure_origin(origin)?; + Self::do_request_preimage(&hash); + Ok(()) + } + + /// Clear a previously made request for a preimage. + /// + /// NOTE: THIS MUST NOT BE CALLED ON `hash` MORE TIMES THAN `request_preimage`. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::unrequest_preimage())] + pub fn unrequest_preimage(origin: OriginFor, hash: T::Hash) -> DispatchResult { + T::ManagerOrigin::ensure_origin(origin)?; + Self::do_unrequest_preimage(&hash) + } + } +} + +impl Pallet { + /// Ensure that the origin is either the `ManagerOrigin` or a signed origin. + fn ensure_signed_or_manager( + origin: T::RuntimeOrigin, + ) -> Result, BadOrigin> { + if T::ManagerOrigin::ensure_origin(origin.clone()).is_ok() { + return Ok(None) + } + let who = ensure_signed(origin)?; + Ok(Some(who)) + } + + /// Store some preimage on chain. + /// + /// If `maybe_depositor` is `None` then it is also requested. If `Some`, then it is not. + /// + /// We verify that the preimage is within the bounds of what the pallet supports. + /// + /// If the preimage was requested to be uploaded, then the user pays no deposits or tx fees. + fn note_bytes( + preimage: Cow<[u8]>, + maybe_depositor: Option<&T::AccountId>, + ) -> Result<(bool, T::Hash), DispatchError> { + let hash = T::Hashing::hash(&preimage); + let len = preimage.len() as u32; + ensure!(len <= MAX_SIZE, Error::::TooBig); + + // We take a deposit only if there is a provided depositor and the preimage was not + // previously requested. This also allows the tx to pay no fee. + let status = match (StatusFor::::get(hash), maybe_depositor) { + (Some(RequestStatus::Requested { count, deposit, .. }), _) => + RequestStatus::Requested { count, deposit, len: Some(len) }, + (Some(RequestStatus::Unrequested { .. }), Some(_)) => + return Err(Error::::AlreadyNoted.into()), + (Some(RequestStatus::Unrequested { len, deposit }), None) => + RequestStatus::Requested { deposit: Some(deposit), count: 1, len: Some(len) }, + (None, None) => RequestStatus::Requested { count: 1, len: Some(len), deposit: None }, + (None, Some(depositor)) => { + let length = preimage.len() as u32; + let deposit = T::BaseDeposit::get() + .saturating_add(T::ByteDeposit::get().saturating_mul(length.into())); + T::Currency::reserve(depositor, deposit)?; + RequestStatus::Unrequested { deposit: (depositor.clone(), deposit), len } + }, + }; + let was_requested = matches!(status, RequestStatus::Requested { .. }); + StatusFor::::insert(hash, status); + + let _ = Self::insert(&hash, preimage) + .defensive_proof("Unable to insert. Logic error in `note_bytes`?"); + + Self::deposit_event(Event::Noted { hash }); + + Ok((was_requested, hash)) + } + + // This function will add a hash to the list of requested preimages. + // + // If the preimage already exists before the request is made, the deposit for the preimage is + // returned to the user, and removed from their management. + fn do_request_preimage(hash: &T::Hash) { + let (count, len, deposit) = + StatusFor::::get(hash).map_or((1, None, None), |x| match x { + RequestStatus::Requested { mut count, len, deposit } => { + count.saturating_inc(); + (count, len, deposit) + }, + RequestStatus::Unrequested { deposit, len } => (1, Some(len), Some(deposit)), + }); + StatusFor::::insert(hash, RequestStatus::Requested { count, len, deposit }); + if count == 1 { + Self::deposit_event(Event::Requested { hash: *hash }); + } + } + + // Clear a preimage from the storage of the chain, returning any deposit that may be reserved. + // + // If `len` is provided, it will be a much cheaper operation. + // + // If `maybe_owner` is provided, we verify that it is the correct owner before clearing the + // data. + fn do_unnote_preimage( + hash: &T::Hash, + maybe_check_owner: Option, + ) -> DispatchResult { + match StatusFor::::get(hash).ok_or(Error::::NotNoted)? { + RequestStatus::Requested { deposit: Some((owner, deposit)), count, len } => { + ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::::NotAuthorized); + T::Currency::unreserve(&owner, deposit); + StatusFor::::insert( + hash, + RequestStatus::Requested { deposit: None, count, len }, + ); + Ok(()) + }, + RequestStatus::Requested { deposit: None, .. } => { + ensure!(maybe_check_owner.is_none(), Error::::NotAuthorized); + Self::do_unrequest_preimage(hash) + }, + RequestStatus::Unrequested { deposit: (owner, deposit), len } => { + ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::::NotAuthorized); + T::Currency::unreserve(&owner, deposit); + StatusFor::::remove(hash); + + Self::remove(hash, len); + Self::deposit_event(Event::Cleared { hash: *hash }); + Ok(()) + }, + } + } + + /// Clear a preimage request. + fn do_unrequest_preimage(hash: &T::Hash) -> DispatchResult { + match StatusFor::::get(hash).ok_or(Error::::NotRequested)? { + RequestStatus::Requested { mut count, len, deposit } if count > 1 => { + count.saturating_dec(); + StatusFor::::insert(hash, RequestStatus::Requested { count, len, deposit }); + }, + RequestStatus::Requested { count, len, deposit } => { + debug_assert!(count == 1, "preimage request counter at zero?"); + match (len, deposit) { + // Preimage was never noted. + (None, _) => StatusFor::::remove(hash), + // Preimage was noted without owner - just remove it. + (Some(len), None) => { + Self::remove(hash, len); + StatusFor::::remove(hash); + Self::deposit_event(Event::Cleared { hash: *hash }); + }, + // Preimage was noted with owner - move to unrequested so they can get refund. + (Some(len), Some(deposit)) => { + StatusFor::::insert(hash, RequestStatus::Unrequested { deposit, len }); + }, + } + }, + RequestStatus::Unrequested { .. } => return Err(Error::::NotRequested.into()), + } + Ok(()) + } + + fn insert(hash: &T::Hash, preimage: Cow<[u8]>) -> Result<(), ()> { + BoundedSlice::>::try_from(preimage.as_ref()) + .map_err(|_| ()) + .map(|s| PreimageFor::::insert((hash, s.len() as u32), s)) + } + + fn remove(hash: &T::Hash, len: u32) { + PreimageFor::::remove((hash, len)) + } + + fn have(hash: &T::Hash) -> bool { + Self::len(hash).is_some() + } + + fn len(hash: &T::Hash) -> Option { + use RequestStatus::*; + match StatusFor::::get(hash) { + Some(Requested { len: Some(len), .. }) | Some(Unrequested { len, .. }) => Some(len), + _ => None, + } + } + + fn fetch(hash: &T::Hash, len: Option) -> FetchResult { + let len = len.or_else(|| Self::len(hash)).ok_or(DispatchError::Unavailable)?; + PreimageFor::::get((hash, len)) + .map(|p| p.into_inner()) + .map(Into::into) + .ok_or(DispatchError::Unavailable) + } +} + +impl PreimageProvider for Pallet { + fn have_preimage(hash: &T::Hash) -> bool { + Self::have(hash) + } + + fn preimage_requested(hash: &T::Hash) -> bool { + matches!(StatusFor::::get(hash), Some(RequestStatus::Requested { .. })) + } + + fn get_preimage(hash: &T::Hash) -> Option> { + Self::fetch(hash, None).ok().map(Cow::into_owned) + } + + fn request_preimage(hash: &T::Hash) { + Self::do_request_preimage(hash) + } + + fn unrequest_preimage(hash: &T::Hash) { + let res = Self::do_unrequest_preimage(hash); + debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?"); + } +} + +impl PreimageRecipient for Pallet { + type MaxSize = ConstU32; // 2**22 + + fn note_preimage(bytes: BoundedVec) { + // We don't really care if this fails, since that's only the case if someone else has + // already noted it. + let _ = Self::note_bytes(bytes.into_inner().into(), None); + } + + fn unnote_preimage(hash: &T::Hash) { + // Should never fail if authorization check is skipped. + let res = Self::do_unrequest_preimage(hash); + debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?"); + } +} + +impl> QueryPreimage for Pallet { + fn len(hash: &T::Hash) -> Option { + Pallet::::len(hash) + } + + fn fetch(hash: &T::Hash, len: Option) -> FetchResult { + Pallet::::fetch(hash, len) + } + + fn is_requested(hash: &T::Hash) -> bool { + matches!(StatusFor::::get(hash), Some(RequestStatus::Requested { .. })) + } + + fn request(hash: &T::Hash) { + Self::do_request_preimage(hash) + } + + fn unrequest(hash: &T::Hash) { + let res = Self::do_unrequest_preimage(hash); + debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?"); + } +} + +impl> StorePreimage for Pallet { + const MAX_LENGTH: usize = MAX_SIZE as usize; + + fn note(bytes: Cow<[u8]>) -> Result { + // We don't really care if this fails, since that's only the case if someone else has + // already noted it. + let maybe_hash = Self::note_bytes(bytes, None).map(|(_, h)| h); + // Map to the correct trait error. + if maybe_hash == Err(DispatchError::from(Error::::TooBig)) { + Err(DispatchError::Exhausted) + } else { + maybe_hash + } + } + + fn unnote(hash: &T::Hash) { + // Should never fail if authorization check is skipped. + let res = Self::do_unnote_preimage(hash, None); + debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?"); + } +} diff --git a/frame/preimage/src/migration.rs b/frame/preimage/src/migration.rs new file mode 100644 index 0000000000000..a5d15c23c758a --- /dev/null +++ b/frame/preimage/src/migration.rs @@ -0,0 +1,263 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Storage migrations for the preimage pallet. + +use super::*; +use frame_support::{ + storage_alias, + traits::{ConstU32, OnRuntimeUpgrade}, +}; +use sp_std::collections::btree_map::BTreeMap; + +/// The log target. +const TARGET: &'static str = "runtime::preimage::migration::v1"; + +/// The original data layout of the preimage pallet without a specific version number. +mod v0 { + use super::*; + + #[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)] + pub enum RequestStatus { + Unrequested(Option<(AccountId, Balance)>), + Requested(u32), + } + + #[storage_alias] + pub type PreimageFor = StorageMap< + Pallet, + Identity, + ::Hash, + BoundedVec>, + >; + + #[storage_alias] + pub type StatusFor = StorageMap< + Pallet, + Identity, + ::Hash, + RequestStatus<::AccountId, BalanceOf>, + >; + + /// Returns the number of images or `None` if the storage is corrupted. + #[cfg(feature = "try-runtime")] + pub fn image_count() -> Option { + let images = v0::PreimageFor::::iter_values().count() as u32; + let status = v0::StatusFor::::iter_values().count() as u32; + + if images == status { + Some(images) + } else { + None + } + } +} + +pub mod v1 { + use super::*; + + /// Migration for moving preimage from V0 to V1 storage. + /// + /// Note: This needs to be run with the same hashing algorithm as before + /// since it is not re-hashing the preimages. + pub struct Migration(sp_std::marker::PhantomData); + + impl OnRuntimeUpgrade for Migration { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + assert_eq!(StorageVersion::get::>(), 0, "can only upgrade from version 0"); + + let images = v0::image_count::().expect("v0 storage corrupted"); + log::info!(target: TARGET, "Migrating {} images", &images); + Ok((images as u32).encode()) + } + + fn on_runtime_upgrade() -> Weight { + let mut weight = T::DbWeight::get().reads(1); + if StorageVersion::get::>() != 0 { + log::warn!( + target: TARGET, + "skipping MovePreimagesIntoBuckets: executed on wrong storage version.\ + Expected version 0" + ); + return weight + } + + let status = v0::StatusFor::::drain().collect::>(); + weight.saturating_accrue(T::DbWeight::get().reads(status.len() as u64)); + + let preimages = v0::PreimageFor::::drain().collect::>(); + weight.saturating_accrue(T::DbWeight::get().reads(preimages.len() as u64)); + + for (hash, status) in status.into_iter() { + let preimage = if let Some(preimage) = preimages.get(&hash) { + preimage + } else { + log::error!(target: TARGET, "preimage not found for hash {:?}", &hash); + continue + }; + let len = preimage.len() as u32; + if len > MAX_SIZE { + log::error!( + target: TARGET, + "preimage too large for hash {:?}, len: {}", + &hash, + len + ); + continue + } + + let status = match status { + v0::RequestStatus::Unrequested(deposit) => match deposit { + Some(deposit) => RequestStatus::Unrequested { deposit, len }, + // `None` depositor becomes system-requested. + None => + RequestStatus::Requested { deposit: None, count: 1, len: Some(len) }, + }, + v0::RequestStatus::Requested(count) if count == 0 => { + log::error!(target: TARGET, "preimage has counter of zero: {:?}", hash); + continue + }, + v0::RequestStatus::Requested(count) => + RequestStatus::Requested { deposit: None, count, len: Some(len) }, + }; + log::trace!(target: TARGET, "Moving preimage {:?} with len {}", hash, len); + + crate::StatusFor::::insert(hash, status); + crate::PreimageFor::::insert(&(hash, len), preimage); + + weight.saturating_accrue(T::DbWeight::get().writes(2)); + } + StorageVersion::new(1).put::>(); + + weight.saturating_add(T::DbWeight::get().writes(1)) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + let old_images: u32 = + Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed"); + let new_images = image_count::().expect("V1 storage corrupted"); + + if new_images != old_images { + log::error!( + target: TARGET, + "migrated {} images, expected {}", + new_images, + old_images + ); + } + assert_eq!(StorageVersion::get::>(), 1, "must upgrade"); + Ok(()) + } + } + + /// Returns the number of images or `None` if the storage is corrupted. + #[cfg(feature = "try-runtime")] + pub fn image_count() -> Option { + // Use iter_values() to ensure that the values are decodable. + let images = crate::PreimageFor::::iter_values().count() as u32; + let status = crate::StatusFor::::iter_values().count() as u32; + + if images == status { + Some(images) + } else { + None + } + } +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod test { + use super::*; + use crate::mock::{Test as T, *}; + + use frame_support::bounded_vec; + + #[test] + fn migration_works() { + new_test_ext().execute_with(|| { + assert_eq!(StorageVersion::get::>(), 0); + // Insert some preimages into the v0 storage: + + // Case 1: Unrequested without deposit + let (p, h) = preimage::(128); + v0::PreimageFor::::insert(h, p); + v0::StatusFor::::insert(h, v0::RequestStatus::Unrequested(None)); + // Case 2: Unrequested with deposit + let (p, h) = preimage::(1024); + v0::PreimageFor::::insert(h, p); + v0::StatusFor::::insert(h, v0::RequestStatus::Unrequested(Some((1, 1)))); + // Case 3: Requested by 0 (invalid) + let (p, h) = preimage::(8192); + v0::PreimageFor::::insert(h, p); + v0::StatusFor::::insert(h, v0::RequestStatus::Requested(0)); + // Case 4: Requested by 10 + let (p, h) = preimage::(65536); + v0::PreimageFor::::insert(h, p); + v0::StatusFor::::insert(h, v0::RequestStatus::Requested(10)); + + assert_eq!(v0::image_count::(), Some(4)); + assert_eq!(v1::image_count::(), None, "V1 storage should be corrupted"); + + let state = v1::Migration::::pre_upgrade().unwrap(); + let _w = v1::Migration::::on_runtime_upgrade(); + v1::Migration::::post_upgrade(state).unwrap(); + + // V0 and V1 share the same prefix, so `iter_values` still counts the same. + assert_eq!(v0::image_count::(), Some(3)); + assert_eq!(v1::image_count::(), Some(3)); // One gets skipped therefore 3. + assert_eq!(StorageVersion::get::>(), 1); + + // Case 1: Unrequested without deposit becomes system-requested + let (p, h) = preimage::(128); + assert_eq!(crate::PreimageFor::::get(&(h, 128)), Some(p)); + assert_eq!( + crate::StatusFor::::get(h), + Some(RequestStatus::Requested { deposit: None, count: 1, len: Some(128) }) + ); + // Case 2: Unrequested with deposit becomes unrequested + let (p, h) = preimage::(1024); + assert_eq!(crate::PreimageFor::::get(&(h, 1024)), Some(p)); + assert_eq!( + crate::StatusFor::::get(h), + Some(RequestStatus::Unrequested { deposit: (1, 1), len: 1024 }) + ); + // Case 3: Requested by 0 should be skipped + let (_, h) = preimage::(8192); + assert_eq!(crate::PreimageFor::::get(&(h, 8192)), None); + assert_eq!(crate::StatusFor::::get(h), None); + // Case 4: Requested by 10 becomes requested by 10 + let (p, h) = preimage::(65536); + assert_eq!(crate::PreimageFor::::get(&(h, 65536)), Some(p)); + assert_eq!( + crate::StatusFor::::get(h), + Some(RequestStatus::Requested { deposit: None, count: 10, len: Some(65536) }) + ); + }); + } + + /// Returns a preimage with a given size and its hash. + fn preimage( + len: usize, + ) -> (BoundedVec>, ::Hash) { + let p = bounded_vec![1; len]; + let h = ::Hashing::hash_of(&p); + (p, h) + } +} diff --git a/frame/preimage/src/mock.rs b/frame/preimage/src/mock.rs new file mode 100644 index 0000000000000..ce74ea65bd8aa --- /dev/null +++ b/frame/preimage/src/mock.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! # Scheduler test environment. + +use super::*; + +use crate as pallet_preimage; +use frame_support::{ + ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64, Everything}, + weights::constants::RocksDbWeight, +}; +use frame_system::EnsureSignedBy; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Preimage: pallet_preimage, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(2_000_000_000_000)); +} +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = RocksDbWeight; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<5>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; + pub const MaxScheduledPerBlock: u32 = 10; +} + +ord_parameter_types! { + pub const One: u64 = 1; +} + +impl Config for Test { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type ManagerOrigin = EnsureSignedBy; + type BaseDeposit = ConstU64<2>; + type ByteDeposit = ConstU64<1>; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let balances = pallet_balances::GenesisConfig:: { + balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], + }; + balances.assimilate_storage(&mut t).unwrap(); + t.into() +} + +pub fn hashed(data: impl AsRef<[u8]>) -> H256 { + BlakeTwo256::hash(data.as_ref()) +} diff --git a/frame/preimage/src/tests.rs b/frame/preimage/src/tests.rs new file mode 100644 index 0000000000000..f480b9c36b670 --- /dev/null +++ b/frame/preimage/src/tests.rs @@ -0,0 +1,490 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! # Scheduler tests. + +#![cfg(test)] + +use super::*; +use crate::mock::*; + +use frame_support::{ + assert_err, assert_noop, assert_ok, assert_storage_noop, bounded_vec, + traits::{Bounded, BoundedInline, Hash as PreimageHash}, + StorageNoopGuard, +}; +use pallet_balances::Error as BalancesError; +use sp_core::{blake2_256, H256}; + +/// Returns one `Inline`, `Lookup` and `Legacy` item each with different data and hash. +pub fn make_bounded_values() -> (Bounded>, Bounded>, Bounded>) { + let data: BoundedInline = bounded_vec![1]; + let inline = Bounded::>::Inline(data); + + let data = vec![1, 2]; + let hash: H256 = blake2_256(&data[..]).into(); + let len = data.len() as u32; + let lookup = Bounded::>::unrequested(hash, len); + + let data = vec![1, 2, 3]; + let hash: H256 = blake2_256(&data[..]).into(); + let legacy = Bounded::>::Legacy { hash, dummy: Default::default() }; + + (inline, lookup, legacy) +} + +#[test] +fn user_note_preimage_works() { + new_test_ext().execute_with(|| { + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1])); + assert_eq!(Balances::reserved_balance(2), 3); + assert_eq!(Balances::free_balance(2), 97); + + let h = hashed([1]); + assert!(Preimage::have_preimage(&h)); + assert_eq!(Preimage::get_preimage(&h), Some(vec![1])); + + assert_noop!( + Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]), + Error::::AlreadyNoted + ); + assert_noop!( + Preimage::note_preimage(RuntimeOrigin::signed(0), vec![2]), + BalancesError::::InsufficientBalance + ); + }); +} + +#[test] +fn manager_note_preimage_works() { + new_test_ext().execute_with(|| { + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![1])); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::free_balance(1), 100); + + let h = hashed([1]); + assert!(Preimage::have_preimage(&h)); + assert_eq!(Preimage::get_preimage(&h), Some(vec![1])); + + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![1])); + }); +} + +#[test] +fn user_unnote_preimage_works() { + new_test_ext().execute_with(|| { + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1])); + assert_noop!( + Preimage::unnote_preimage(RuntimeOrigin::signed(3), hashed([1])), + Error::::NotAuthorized + ); + assert_noop!( + Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([2])), + Error::::NotNoted + ); + + assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1]))); + assert_noop!( + Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1])), + Error::::NotNoted + ); + + let h = hashed([1]); + assert!(!Preimage::have_preimage(&h)); + assert_eq!(Preimage::get_preimage(&h), None); + }); +} + +#[test] +fn manager_unnote_preimage_works() { + new_test_ext().execute_with(|| { + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![1])); + assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_noop!( + Preimage::unnote_preimage(RuntimeOrigin::signed(1), hashed([1])), + Error::::NotNoted + ); + + let h = hashed([1]); + assert!(!Preimage::have_preimage(&h)); + assert_eq!(Preimage::get_preimage(&h), None); + }); +} + +#[test] +fn manager_unnote_user_preimage_works() { + new_test_ext().execute_with(|| { + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1])); + assert_noop!( + Preimage::unnote_preimage(RuntimeOrigin::signed(3), hashed([1])), + Error::::NotAuthorized + ); + assert_noop!( + Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([2])), + Error::::NotNoted + ); + + assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(1), hashed([1]))); + + let h = hashed([1]); + assert!(!Preimage::have_preimage(&h)); + assert_eq!(Preimage::get_preimage(&h), None); + }); +} + +#[test] +fn requested_then_noted_preimage_cannot_be_unnoted() { + new_test_ext().execute_with(|| { + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![1])); + assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(1), hashed([1]))); + // it's still here. + + let h = hashed([1]); + assert!(Preimage::have_preimage(&h)); + assert_eq!(Preimage::get_preimage(&h), Some(vec![1])); + + // now it's gone + assert_ok!(Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert!(!Preimage::have_preimage(&hashed([1]))); + }); +} + +#[test] +fn request_note_order_makes_no_difference() { + let one_way = new_test_ext().execute_with(|| { + assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1])); + ( + StatusFor::::iter().collect::>(), + PreimageFor::::iter().collect::>(), + ) + }); + new_test_ext().execute_with(|| { + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1])); + assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1]))); + let other_way = ( + StatusFor::::iter().collect::>(), + PreimageFor::::iter().collect::>(), + ); + assert_eq!(one_way, other_way); + }); +} + +#[test] +fn requested_then_user_noted_preimage_is_free() { + new_test_ext().execute_with(|| { + assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1])); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::free_balance(2), 100); + + let h = hashed([1]); + assert!(Preimage::have_preimage(&h)); + assert_eq!(Preimage::get_preimage(&h), Some(vec![1])); + }); +} + +#[test] +fn request_user_note_order_makes_no_difference() { + let one_way = new_test_ext().execute_with(|| { + assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1])); + ( + StatusFor::::iter().collect::>(), + PreimageFor::::iter().collect::>(), + ) + }); + new_test_ext().execute_with(|| { + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1])); + assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1]))); + let other_way = ( + StatusFor::::iter().collect::>(), + PreimageFor::::iter().collect::>(), + ); + assert_eq!(one_way, other_way); + }); +} + +#[test] +fn unrequest_preimage_works() { + new_test_ext().execute_with(|| { + assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1])); + assert_noop!( + Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([2])), + Error::::NotRequested + ); + + assert_ok!(Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert!(Preimage::have_preimage(&hashed([1]))); + + assert_ok!(Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_noop!( + Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([1])), + Error::::NotRequested + ); + }); +} + +#[test] +fn user_noted_then_requested_preimage_is_refunded_once_only() { + new_test_ext().execute_with(|| { + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1; 3])); + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1])); + assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_ok!(Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([1]))); + assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1]))); + // Still have reserve from `vec[1; 3]`. + assert_eq!(Balances::reserved_balance(2), 5); + assert_eq!(Balances::free_balance(2), 95); + }); +} + +#[test] +fn noted_preimage_use_correct_map() { + new_test_ext().execute_with(|| { + // Add one preimage per bucket... + for i in 0..7 { + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![0; 128 << (i * 2)])); + } + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![0; MAX_SIZE as usize])); + assert_eq!(PreimageFor::::iter().count(), 8); + + // All are present + assert_eq!(StatusFor::::iter().count(), 8); + + // Now start removing them again... + for i in 0..7 { + assert_ok!(Preimage::unnote_preimage( + RuntimeOrigin::signed(1), + hashed(vec![0; 128 << (i * 2)]) + )); + } + assert_eq!(PreimageFor::::iter().count(), 1); + assert_ok!(Preimage::unnote_preimage( + RuntimeOrigin::signed(1), + hashed(vec![0; MAX_SIZE as usize]) + )); + assert_eq!(PreimageFor::::iter().count(), 0); + + // All are gone + assert_eq!(StatusFor::::iter().count(), 0); + }); +} + +/// The `StorePreimage` and `QueryPreimage` traits work together. +#[test] +fn query_and_store_preimage_workflow() { + new_test_ext().execute_with(|| { + let _guard = StorageNoopGuard::default(); + let data: Vec = vec![1; 512]; + let encoded = data.encode(); + + // Bound an unbound value. + let bound = Preimage::bound(data.clone()).unwrap(); + let (len, hash) = (bound.len().unwrap(), bound.hash()); + + assert_eq!(hash, blake2_256(&encoded).into()); + assert_eq!(bound.len(), Some(len)); + assert!(bound.lookup_needed(), "Should not be Inlined"); + assert_eq!(bound.lookup_len(), Some(len)); + + // The value is requested and available. + assert!(Preimage::is_requested(&hash)); + assert!(::have(&bound)); + assert_eq!(Preimage::len(&hash), Some(len)); + + // It can be fetched with length. + assert_eq!(Preimage::fetch(&hash, Some(len)).unwrap(), encoded); + // ... and without length. + assert_eq!(Preimage::fetch(&hash, None).unwrap(), encoded); + // ... but not with wrong length. + assert_err!(Preimage::fetch(&hash, Some(0)), DispatchError::Unavailable); + + // It can be peeked and decoded correctly. + assert_eq!(Preimage::peek::>(&bound).unwrap(), (data.clone(), Some(len))); + // Request it two more times. + assert_eq!(Preimage::pick::>(hash, len), bound); + Preimage::request(&hash); + // It is requested thrice. + assert!(matches!( + StatusFor::::get(&hash).unwrap(), + RequestStatus::Requested { count: 3, .. } + )); + + // It can be realized and decoded correctly. + assert_eq!(Preimage::realize::>(&bound).unwrap(), (data.clone(), Some(len))); + assert!(matches!( + StatusFor::::get(&hash).unwrap(), + RequestStatus::Requested { count: 2, .. } + )); + // Dropping should unrequest. + Preimage::drop(&bound); + assert!(matches!( + StatusFor::::get(&hash).unwrap(), + RequestStatus::Requested { count: 1, .. } + )); + + // Is still available. + assert!(::have(&bound)); + // Manually unnote it. + Preimage::unnote(&hash); + // Is not available anymore. + assert!(!::have(&bound)); + assert_err!(Preimage::fetch(&hash, Some(len)), DispatchError::Unavailable); + // And not requested since the traits assume permissioned origin. + assert!(!Preimage::is_requested(&hash)); + + // No storage changes remain. Checked by `StorageNoopGuard`. + }); +} + +/// The request function behaves as expected. +#[test] +fn query_preimage_request_works() { + new_test_ext().execute_with(|| { + let _guard = StorageNoopGuard::default(); + let data: Vec = vec![1; 10]; + let hash: PreimageHash = blake2_256(&data[..]).into(); + + // Request the preimage. + ::request(&hash); + + // The preimage is requested with unknown length and cannot be fetched. + assert!(::is_requested(&hash)); + assert!(::len(&hash).is_none()); + assert_noop!(::fetch(&hash, None), DispatchError::Unavailable); + + // Request again. + ::request(&hash); + // The preimage is still requested. + assert!(::is_requested(&hash)); + assert!(::len(&hash).is_none()); + assert_noop!(::fetch(&hash, None), DispatchError::Unavailable); + // But there is only one entry in the map. + assert_eq!(StatusFor::::iter().count(), 1); + + // Un-request the preimage. + ::unrequest(&hash); + // It is still requested. + assert!(::is_requested(&hash)); + // Un-request twice. + ::unrequest(&hash); + // It is not requested anymore. + assert!(!::is_requested(&hash)); + // And there is no entry in the map. + assert_eq!(StatusFor::::iter().count(), 0); + }); +} + +/// The `QueryPreimage` functions can be used together with `Bounded` values. +#[test] +fn query_preimage_hold_and_drop_work() { + new_test_ext().execute_with(|| { + let _guard = StorageNoopGuard::default(); + let (inline, lookup, legacy) = make_bounded_values(); + + // `hold` does nothing for `Inline` values. + assert_storage_noop!(::hold(&inline)); + // `hold` requests `Lookup` values. + ::hold(&lookup); + assert!(::is_requested(&lookup.hash())); + // `hold` requests `Legacy` values. + ::hold(&legacy); + assert!(::is_requested(&legacy.hash())); + + // There are two values requested in total. + assert_eq!(StatusFor::::iter().count(), 2); + + // Cleanup by dropping both. + ::drop(&lookup); + assert!(!::is_requested(&lookup.hash())); + ::drop(&legacy); + assert!(!::is_requested(&legacy.hash())); + + // There are no values requested anymore. + assert_eq!(StatusFor::::iter().count(), 0); + }); +} + +/// The `StorePreimage` trait works as expected. +#[test] +fn store_preimage_basic_works() { + new_test_ext().execute_with(|| { + let _guard = StorageNoopGuard::default(); + let data: Vec = vec![1; 512]; // Too large to inline. + let encoded = Cow::from(data.encode()); + + // Bound the data. + let bound = ::bound(data.clone()).unwrap(); + // The preimage can be peeked. + assert_ok!(::peek(&bound)); + // Un-note the preimage. + ::unnote(&bound.hash()); + // The preimage cannot be peeked anymore. + assert_err!(::peek(&bound), DispatchError::Unavailable); + // Noting the wrong pre-image does not make it peek-able. + assert_ok!(::note(Cow::Borrowed(&data))); + assert_err!(::peek(&bound), DispatchError::Unavailable); + + // Manually note the preimage makes it peek-able again. + assert_ok!(::note(encoded.clone())); + // Noting again works. + assert_ok!(::note(encoded)); + assert_ok!(::peek(&bound)); + + // Cleanup. + ::unnote(&bound.hash()); + let data_hash = blake2_256(&data); + ::unnote(&data_hash.into()); + + // No storage changes remain. Checked by `StorageNoopGuard`. + }); +} + +#[test] +fn store_preimage_note_too_large_errors() { + new_test_ext().execute_with(|| { + // Works with `MAX_LENGTH`. + let len = ::MAX_LENGTH; + let data = vec![0u8; len]; + assert_ok!(::note(data.into())); + + // Errors with `MAX_LENGTH+1`. + let data = vec![0u8; len + 1]; + assert_err!(::note(data.into()), DispatchError::Exhausted); + }); +} + +#[test] +fn store_preimage_bound_too_large_errors() { + new_test_ext().execute_with(|| { + // Using `MAX_LENGTH` number of bytes in a vector does not work + // since SCALE prepends the length. + let len = ::MAX_LENGTH; + let data: Vec = vec![0; len]; + assert_err!(::bound(data.clone()), DispatchError::Exhausted); + + // Works with `MAX_LENGTH-4`. + let data: Vec = vec![0; len - 4]; + assert_ok!(::bound(data.clone())); + }); +} diff --git a/frame/preimage/src/weights.rs b/frame/preimage/src/weights.rs new file mode 100644 index 0000000000000..e73c986891ccd --- /dev/null +++ b/frame/preimage/src/weights.rs @@ -0,0 +1,269 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_preimage +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_preimage +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/preimage/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_preimage. +pub trait WeightInfo { + fn note_preimage(s: u32, ) -> Weight; + fn note_requested_preimage(s: u32, ) -> Weight; + fn note_no_deposit_preimage(s: u32, ) -> Weight; + fn unnote_preimage() -> Weight; + fn unnote_no_deposit_preimage() -> Weight; + fn request_preimage() -> Weight; + fn request_no_deposit_preimage() -> Weight; + fn request_unnoted_preimage() -> Weight; + fn request_requested_preimage() -> Weight; + fn unrequest_preimage() -> Weight; + fn unrequest_unnoted_preimage() -> Weight; + fn unrequest_multi_referenced_preimage() -> Weight; +} + +/// Weights for pallet_preimage using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + /// The range of component `s` is `[0, 4194304]`. + fn note_preimage(s: u32, ) -> Weight { + // Minimum execution time: 33_810 nanoseconds. + Weight::from_ref_time(34_299_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_703 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + /// The range of component `s` is `[0, 4194304]`. + fn note_requested_preimage(s: u32, ) -> Weight { + // Minimum execution time: 24_398 nanoseconds. + Weight::from_ref_time(24_839_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_702 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + /// The range of component `s` is `[0, 4194304]`. + fn note_no_deposit_preimage(s: u32, ) -> Weight { + // Minimum execution time: 22_235 nanoseconds. + Weight::from_ref_time(22_473_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_703 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + fn unnote_preimage() -> Weight { + // Minimum execution time: 43_241 nanoseconds. + Weight::from_ref_time(44_470_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + fn unnote_no_deposit_preimage() -> Weight { + // Minimum execution time: 29_529 nanoseconds. + Weight::from_ref_time(30_364_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn request_preimage() -> Weight { + // Minimum execution time: 28_914 nanoseconds. + Weight::from_ref_time(30_103_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn request_no_deposit_preimage() -> Weight { + // Minimum execution time: 14_479 nanoseconds. + Weight::from_ref_time(15_244_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn request_unnoted_preimage() -> Weight { + // Minimum execution time: 20_171 nanoseconds. + Weight::from_ref_time(20_806_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn request_requested_preimage() -> Weight { + // Minimum execution time: 9_756 nanoseconds. + Weight::from_ref_time(10_115_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + fn unrequest_preimage() -> Weight { + // Minimum execution time: 28_379 nanoseconds. + Weight::from_ref_time(29_778_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn unrequest_unnoted_preimage() -> Weight { + // Minimum execution time: 9_595 nanoseconds. + Weight::from_ref_time(9_888_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn unrequest_multi_referenced_preimage() -> Weight { + // Minimum execution time: 9_642 nanoseconds. + Weight::from_ref_time(9_985_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + /// The range of component `s` is `[0, 4194304]`. + fn note_preimage(s: u32, ) -> Weight { + // Minimum execution time: 33_810 nanoseconds. + Weight::from_ref_time(34_299_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_703 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + /// The range of component `s` is `[0, 4194304]`. + fn note_requested_preimage(s: u32, ) -> Weight { + // Minimum execution time: 24_398 nanoseconds. + Weight::from_ref_time(24_839_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_702 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + /// The range of component `s` is `[0, 4194304]`. + fn note_no_deposit_preimage(s: u32, ) -> Weight { + // Minimum execution time: 22_235 nanoseconds. + Weight::from_ref_time(22_473_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_703 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + fn unnote_preimage() -> Weight { + // Minimum execution time: 43_241 nanoseconds. + Weight::from_ref_time(44_470_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + fn unnote_no_deposit_preimage() -> Weight { + // Minimum execution time: 29_529 nanoseconds. + Weight::from_ref_time(30_364_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn request_preimage() -> Weight { + // Minimum execution time: 28_914 nanoseconds. + Weight::from_ref_time(30_103_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn request_no_deposit_preimage() -> Weight { + // Minimum execution time: 14_479 nanoseconds. + Weight::from_ref_time(15_244_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn request_unnoted_preimage() -> Weight { + // Minimum execution time: 20_171 nanoseconds. + Weight::from_ref_time(20_806_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn request_requested_preimage() -> Weight { + // Minimum execution time: 9_756 nanoseconds. + Weight::from_ref_time(10_115_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + fn unrequest_preimage() -> Weight { + // Minimum execution time: 28_379 nanoseconds. + Weight::from_ref_time(29_778_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn unrequest_unnoted_preimage() -> Weight { + // Minimum execution time: 9_595 nanoseconds. + Weight::from_ref_time(9_888_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Preimage StatusFor (r:1 w:1) + fn unrequest_multi_referenced_preimage() -> Weight { + // Minimum execution time: 9_642 nanoseconds. + Weight::from_ref_time(9_985_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/frame/proxy/Cargo.toml b/frame/proxy/Cargo.toml index 83db82990d105..1674e408668d2 100644 --- a/frame/proxy/Cargo.toml +++ b/frame/proxy/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-proxy" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME proxying pallet" readme = "README.md" @@ -13,34 +13,35 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["max-encoded-len"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-utility = { version = "4.0.0-dev", path = "../utility" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "codec/std", - "scale-info/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", - "sp-std/std", + "scale-info/std", "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/proxy/src/benchmarking.rs b/frame/proxy/src/benchmarking.rs index e66f6782c19e1..58c0cb73011df 100644 --- a/frame/proxy/src/benchmarking.rs +++ b/frame/proxy/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,23 +21,25 @@ use super::*; use crate::Pallet as Proxy; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; const SEED: u32 = 0; -fn assert_last_event(generic_event: ::Event) { +fn assert_last_event(generic_event: ::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } fn add_proxies(n: u32, maybe_who: Option) -> Result<(), &'static str> { - let caller = maybe_who.unwrap_or_else(|| whitelisted_caller()); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let caller = maybe_who.unwrap_or_else(whitelisted_caller); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); for i in 0..n { + let real = T::Lookup::unlookup(account("target", i, SEED)); + Proxy::::add_proxy( RawOrigin::Signed(caller.clone()).into(), - account("target", i, SEED), + real, T::ProxyType::default(), T::BlockNumber::zero(), )?; @@ -51,24 +53,26 @@ fn add_announcements( maybe_real: Option, ) -> Result<(), &'static str> { let caller = maybe_who.unwrap_or_else(|| account("caller", 0, SEED)); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); let real = if let Some(real) = maybe_real { real } else { let real = account("real", 0, SEED); - T::Currency::make_free_balance_be(&real, BalanceOf::::max_value()); + T::Currency::make_free_balance_be(&real, BalanceOf::::max_value() / 2u32.into()); Proxy::::add_proxy( RawOrigin::Signed(real.clone()).into(), - caller.clone(), + caller_lookup, T::ProxyType::default(), T::BlockNumber::zero(), )?; real }; + let real_lookup = T::Lookup::unlookup(real); for _ in 0..n { Proxy::::announce( RawOrigin::Signed(caller.clone()).into(), - real.clone(), + real_lookup.clone(), T::CallHasher::hash_of(&("add_announcement", n)), )?; } @@ -77,55 +81,59 @@ fn add_announcements( benchmarks! { proxy { - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; // In this case the caller is the "target" proxy let caller: T::AccountId = account("target", p - 1, SEED); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); - let call: ::Call = frame_system::Call::::remark { remark: vec![] }.into(); - }: _(RawOrigin::Signed(caller), real, Some(T::ProxyType::default()), Box::new(call)) + let real_lookup = T::Lookup::unlookup(real); + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + }: _(RawOrigin::Signed(caller), real_lookup, Some(T::ProxyType::default()), Box::new(call)) verify { - assert_last_event::(Event::ProxyExecuted(Ok(())).into()) + assert_last_event::(Event::ProxyExecuted { result: Ok(()) }.into()) } proxy_announced { let a in 0 .. T::MaxPending::get() - 1; - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; // In this case the caller is the "target" proxy - let caller: T::AccountId = account("anonymous", 0, SEED); + let caller: T::AccountId = account("pure", 0, SEED); let delegate: T::AccountId = account("target", p - 1, SEED); - T::Currency::make_free_balance_be(&delegate, BalanceOf::::max_value()); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + T::Currency::make_free_balance_be(&delegate, BalanceOf::::max_value() / 2u32.into()); // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); - let call: ::Call = frame_system::Call::::remark { remark: vec![] }.into(); + let real_lookup = T::Lookup::unlookup(real); + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); Proxy::::announce( RawOrigin::Signed(delegate.clone()).into(), - real.clone(), + real_lookup.clone(), T::CallHasher::hash_of(&call), )?; add_announcements::(a, Some(delegate.clone()), None)?; - }: _(RawOrigin::Signed(caller), delegate, real, Some(T::ProxyType::default()), Box::new(call)) + }: _(RawOrigin::Signed(caller), delegate_lookup, real_lookup, Some(T::ProxyType::default()), Box::new(call)) verify { - assert_last_event::(Event::ProxyExecuted(Ok(())).into()) + assert_last_event::(Event::ProxyExecuted { result: Ok(()) }.into()) } remove_announcement { let a in 0 .. T::MaxPending::get() - 1; - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; // In this case the caller is the "target" proxy let caller: T::AccountId = account("target", p - 1, SEED); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); - let call: ::Call = frame_system::Call::::remark { remark: vec![] }.into(); + let real_lookup = T::Lookup::unlookup(real); + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); Proxy::::announce( RawOrigin::Signed(caller.clone()).into(), - real.clone(), + real_lookup.clone(), T::CallHasher::hash_of(&call), )?; add_announcements::(a, Some(caller.clone()), None)?; - }: _(RawOrigin::Signed(caller.clone()), real, T::CallHasher::hash_of(&call)) + }: _(RawOrigin::Signed(caller.clone()), real_lookup, T::CallHasher::hash_of(&call)) verify { let (announcements, _) = Announcements::::get(&caller); assert_eq!(announcements.len() as u32, a); @@ -133,20 +141,22 @@ benchmarks! { reject_announcement { let a in 0 .. T::MaxPending::get() - 1; - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; // In this case the caller is the "target" proxy let caller: T::AccountId = account("target", p - 1, SEED); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); - let call: ::Call = frame_system::Call::::remark { remark: vec![] }.into(); + let real_lookup = T::Lookup::unlookup(real.clone()); + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); Proxy::::announce( RawOrigin::Signed(caller.clone()).into(), - real.clone(), + real_lookup, T::CallHasher::hash_of(&call), )?; add_announcements::(a, Some(caller.clone()), None)?; - }: _(RawOrigin::Signed(real), caller.clone(), T::CallHasher::hash_of(&call)) + }: _(RawOrigin::Signed(real), caller_lookup, T::CallHasher::hash_of(&call)) verify { let (announcements, _) = Announcements::::get(&caller); assert_eq!(announcements.len() as u32, a); @@ -154,26 +164,28 @@ benchmarks! { announce { let a in 0 .. T::MaxPending::get() - 1; - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; // In this case the caller is the "target" proxy let caller: T::AccountId = account("target", p - 1, SEED); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); + let real_lookup = T::Lookup::unlookup(real.clone()); add_announcements::(a, Some(caller.clone()), None)?; - let call: ::Call = frame_system::Call::::remark { remark: vec![] }.into(); + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); let call_hash = T::CallHasher::hash_of(&call); - }: _(RawOrigin::Signed(caller.clone()), real.clone(), call_hash) + }: _(RawOrigin::Signed(caller.clone()), real_lookup, call_hash) verify { - assert_last_event::(Event::Announced(real, caller, call_hash).into()); + assert_last_event::(Event::Announced { real, proxy: caller, call_hash }.into()); } add_proxy { - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); + let real = T::Lookup::unlookup(account("target", T::MaxProxies::get(), SEED)); }: _( RawOrigin::Signed(caller.clone()), - account("target", T::MaxProxies::get().into(), SEED), + real, T::ProxyType::default(), T::BlockNumber::zero() ) @@ -183,11 +195,12 @@ benchmarks! { } remove_proxy { - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); + let delegate = T::Lookup::unlookup(account("target", 0, SEED)); }: _( RawOrigin::Signed(caller.clone()), - account("target", 0, SEED), + delegate, T::ProxyType::default(), T::BlockNumber::zero() ) @@ -197,7 +210,7 @@ benchmarks! { } remove_proxies { - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); }: _(RawOrigin::Signed(caller.clone())) verify { @@ -205,8 +218,8 @@ benchmarks! { assert_eq!(proxies.len() as u32, 0); } - anonymous { - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + create_pure { + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); }: _( RawOrigin::Signed(caller.clone()), @@ -215,21 +228,22 @@ benchmarks! { 0 ) verify { - let anon_account = Pallet::::anonymous_account(&caller, &T::ProxyType::default(), 0, None); - assert_last_event::(Event::AnonymousCreated( - anon_account, - caller, - T::ProxyType::default(), - 0, - ).into()); + let pure_account = Pallet::::pure_account(&caller, &T::ProxyType::default(), 0, None); + assert_last_event::(Event::PureCreated { + pure: pure_account, + who: caller, + proxy_type: T::ProxyType::default(), + disambiguation_index: 0, + }.into()); } - kill_anonymous { - let p in 0 .. (T::MaxProxies::get() - 2).into(); + kill_pure { + let p in 0 .. (T::MaxProxies::get() - 2); let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - Pallet::::anonymous( + Pallet::::create_pure( RawOrigin::Signed(whitelisted_caller()).into(), T::ProxyType::default(), T::BlockNumber::zero(), @@ -237,14 +251,14 @@ benchmarks! { )?; let height = system::Pallet::::block_number(); let ext_index = system::Pallet::::extrinsic_index().unwrap_or(0); - let anon = Pallet::::anonymous_account(&caller, &T::ProxyType::default(), 0, None); + let pure_account = Pallet::::pure_account(&caller, &T::ProxyType::default(), 0, None); - add_proxies::(p, Some(anon.clone()))?; - ensure!(Proxies::::contains_key(&anon), "anon proxy not created"); - }: _(RawOrigin::Signed(anon.clone()), caller.clone(), T::ProxyType::default(), 0, height, ext_index) + add_proxies::(p, Some(pure_account.clone()))?; + ensure!(Proxies::::contains_key(&pure_account), "pure proxy not created"); + }: _(RawOrigin::Signed(pure_account.clone()), caller_lookup, T::ProxyType::default(), 0, height, ext_index) verify { - assert!(!Proxies::::contains_key(&anon)); + assert!(!Proxies::::contains_key(&pure_account)); } -} -impl_benchmark_test_suite!(Proxy, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(Proxy, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index b73101fa73486..d98534d16a21b 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,20 +35,19 @@ pub mod weights; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ - dispatch::DispatchError, + dispatch::{DispatchError, GetDispatchInfo}, ensure, traits::{Currency, Get, InstanceFilter, IsSubType, IsType, OriginTrait, ReservableCurrency}, - weights::GetDispatchInfo, RuntimeDebug, }; use frame_system::{self as system}; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::{ - traits::{Dispatchable, Hash, Saturating, Zero}, + traits::{Dispatchable, Hash, Saturating, StaticLookup, TrailingZeroInput, Zero}, DispatchResult, }; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; pub use weights::WeightInfo; pub use pallet::*; @@ -58,6 +57,8 @@ type CallHashOf = <::CallHasher as Hash>::Output; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + /// The parameters under which a particular account has a proxy relationship with some other /// account. #[derive( @@ -102,22 +103,21 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::generate_storage_info] pub struct Pallet(_); /// Configuration trait. #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The overarching call type. - type Call: Parameter - + Dispatchable + type RuntimeCall: Parameter + + Dispatchable + GetDispatchInfo + From> + IsSubType> - + IsType<::Call>; + + IsType<::RuntimeCall>; /// The currency mechanism. type Currency: ReservableCurrency; @@ -130,7 +130,7 @@ pub mod pallet { + Member + Ord + PartialOrd - + InstanceFilter<::Call> + + InstanceFilter<::RuntimeCall> + Default + MaxEncodedLen; @@ -191,25 +191,23 @@ pub mod pallet { /// - `real`: The account that the proxy will make a call on behalf of. /// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call. /// - `call`: The call to be made by the `real` account. - /// - /// # - /// Weight is a function of the number of proxies the user has (P). - /// # + #[pallet::call_index(0)] #[pallet::weight({ let di = call.get_dispatch_info(); - (T::WeightInfo::proxy(T::MaxProxies::get().into()) - .saturating_add(di.weight) + (T::WeightInfo::proxy(T::MaxProxies::get()) // AccountData for inner call origin accountdata. - .saturating_add(T::DbWeight::get().reads_writes(1, 1)), + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(di.weight), di.class) })] pub fn proxy( origin: OriginFor, - real: T::AccountId, + real: AccountIdLookupOf, force_proxy_type: Option, - call: Box<::Call>, + call: Box<::RuntimeCall>, ) -> DispatchResult { let who = ensure_signed(origin)?; + let real = T::Lookup::lookup(real)?; let def = Self::find_proxy(&real, &who, force_proxy_type)?; ensure!(def.delay.is_zero(), Error::::Unannounced); @@ -227,18 +225,16 @@ pub mod pallet { /// - `proxy_type`: The permissions allowed for this proxy account. /// - `delay`: The announcement period required of the initial proxy. Will generally be /// zero. - /// - /// # - /// Weight is a function of the number of proxies the user has (P). - /// # - #[pallet::weight(T::WeightInfo::add_proxy(T::MaxProxies::get().into()))] + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::add_proxy(T::MaxProxies::get()))] pub fn add_proxy( origin: OriginFor, - delegate: T::AccountId, + delegate: AccountIdLookupOf, proxy_type: T::ProxyType, delay: T::BlockNumber, ) -> DispatchResult { let who = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; Self::add_proxy_delegate(&who, delegate, proxy_type, delay) } @@ -249,18 +245,16 @@ pub mod pallet { /// Parameters: /// - `proxy`: The account that the `caller` would like to remove as a proxy. /// - `proxy_type`: The permissions currently enabled for the removed proxy account. - /// - /// # - /// Weight is a function of the number of proxies the user has (P). - /// # - #[pallet::weight(T::WeightInfo::remove_proxy(T::MaxProxies::get().into()))] + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::remove_proxy(T::MaxProxies::get()))] pub fn remove_proxy( origin: OriginFor, - delegate: T::AccountId, + delegate: AccountIdLookupOf, proxy_type: T::ProxyType, delay: T::BlockNumber, ) -> DispatchResult { let who = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; Self::remove_proxy_delegate(&who, delegate, proxy_type, delay) } @@ -268,13 +262,10 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_. /// - /// WARNING: This may be called on accounts created by `anonymous`, however if done, then + /// WARNING: This may be called on accounts created by `pure`, however if done, then /// the unreserved fees will be inaccessible. **All access to this account will be lost.** - /// - /// # - /// Weight is a function of the number of proxies the user has (P). - /// # - #[pallet::weight(T::WeightInfo::remove_proxies(T::MaxProxies::get().into()))] + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::remove_proxies(T::MaxProxies::get()))] pub fn remove_proxies(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let (_, old_deposit) = Proxies::::take(&who); @@ -301,13 +292,9 @@ pub mod pallet { /// same sender, with the same parameters. /// /// Fails if there are insufficient funds to pay for deposit. - /// - /// # - /// Weight is a function of the number of proxies the user has (P). - /// # - /// TODO: Might be over counting 1 read - #[pallet::weight(T::WeightInfo::anonymous(T::MaxProxies::get().into()))] - pub fn anonymous( + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::create_pure(T::MaxProxies::get()))] + pub fn create_pure( origin: OriginFor, proxy_type: T::ProxyType, delay: T::BlockNumber, @@ -315,8 +302,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - let anonymous = Self::anonymous_account(&who, &proxy_type, index, None); - ensure!(!Proxies::::contains_key(&anonymous), Error::::Duplicate); + let pure = Self::pure_account(&who, &proxy_type, index, None); + ensure!(!Proxies::::contains_key(&pure), Error::::Duplicate); let proxy_def = ProxyDefinition { delegate: who.clone(), proxy_type: proxy_type.clone(), delay }; @@ -326,45 +313,48 @@ pub mod pallet { let deposit = T::ProxyDepositBase::get() + T::ProxyDepositFactor::get(); T::Currency::reserve(&who, deposit)?; - Proxies::::insert(&anonymous, (bounded_proxies, deposit)); - Self::deposit_event(Event::AnonymousCreated(anonymous, who, proxy_type, index)); + Proxies::::insert(&pure, (bounded_proxies, deposit)); + Self::deposit_event(Event::PureCreated { + pure, + who, + proxy_type, + disambiguation_index: index, + }); Ok(()) } - /// Removes a previously spawned anonymous proxy. + /// Removes a previously spawned pure proxy. /// /// WARNING: **All access to this account will be lost.** Any funds held in it will be /// inaccessible. /// /// Requires a `Signed` origin, and the sender account must have been created by a call to - /// `anonymous` with corresponding parameters. - /// - /// - `spawner`: The account that originally called `anonymous` to create this account. - /// - `index`: The disambiguation index originally passed to `anonymous`. Probably `0`. - /// - `proxy_type`: The proxy type originally passed to `anonymous`. - /// - `height`: The height of the chain when the call to `anonymous` was processed. - /// - `ext_index`: The extrinsic index in which the call to `anonymous` was processed. - /// - /// Fails with `NoPermission` in case the caller is not a previously created anonymous - /// account whose `anonymous` call has corresponding parameters. - /// - /// # - /// Weight is a function of the number of proxies the user has (P). - /// # - #[pallet::weight(T::WeightInfo::kill_anonymous(T::MaxProxies::get().into()))] - pub fn kill_anonymous( + /// `pure` with corresponding parameters. + /// + /// - `spawner`: The account that originally called `pure` to create this account. + /// - `index`: The disambiguation index originally passed to `pure`. Probably `0`. + /// - `proxy_type`: The proxy type originally passed to `pure`. + /// - `height`: The height of the chain when the call to `pure` was processed. + /// - `ext_index`: The extrinsic index in which the call to `pure` was processed. + /// + /// Fails with `NoPermission` in case the caller is not a previously created pure + /// account whose `pure` call has corresponding parameters. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::kill_pure(T::MaxProxies::get()))] + pub fn kill_pure( origin: OriginFor, - spawner: T::AccountId, + spawner: AccountIdLookupOf, proxy_type: T::ProxyType, index: u16, #[pallet::compact] height: T::BlockNumber, #[pallet::compact] ext_index: u32, ) -> DispatchResult { let who = ensure_signed(origin)?; + let spawner = T::Lookup::lookup(spawner)?; let when = (height, ext_index); - let proxy = Self::anonymous_account(&spawner, &proxy_type, index, Some(when)); + let proxy = Self::pure_account(&spawner, &proxy_type, index, Some(when)); ensure!(proxy == who, Error::::NoPermission); let (_, deposit) = Proxies::::take(&who); @@ -388,28 +378,24 @@ pub mod pallet { /// Parameters: /// - `real`: The account that the proxy will make a call on behalf of. /// - `call_hash`: The hash of the call to be made by the `real` account. - /// - /// # - /// Weight is a function of: - /// - A: the number of announcements made. - /// - P: the number of proxies the user has. - /// # - #[pallet::weight(T::WeightInfo::announce(T::MaxPending::get(), T::MaxProxies::get().into()))] + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::announce(T::MaxPending::get(), T::MaxProxies::get()))] pub fn announce( origin: OriginFor, - real: T::AccountId, + real: AccountIdLookupOf, call_hash: CallHashOf, ) -> DispatchResult { let who = ensure_signed(origin)?; + let real = T::Lookup::lookup(real)?; Proxies::::get(&real) .0 .into_iter() - .find(|x| &x.delegate == &who) + .find(|x| x.delegate == who) .ok_or(Error::::NotProxy)?; let announcement = Announcement { real: real.clone(), - call_hash: call_hash.clone(), + call_hash, height: system::Pallet::::block_number(), }; @@ -427,7 +413,7 @@ pub mod pallet { }) .map(|d| *deposit = d) })?; - Self::deposit_event(Event::Announced(real, who, call_hash)); + Self::deposit_event(Event::Announced { real, proxy: who, call_hash }); Ok(()) } @@ -442,21 +428,18 @@ pub mod pallet { /// Parameters: /// - `real`: The account that the proxy will make a call on behalf of. /// - `call_hash`: The hash of the call to be made by the `real` account. - /// - /// # - /// Weight is a function of: - /// - A: the number of announcements made. - /// - P: the number of proxies the user has. - /// # - #[pallet::weight( - T::WeightInfo::remove_announcement(T::MaxPending::get(), T::MaxProxies::get().into()) - )] + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::remove_announcement( + T::MaxPending::get(), + T::MaxProxies::get() + ))] pub fn remove_announcement( origin: OriginFor, - real: T::AccountId, + real: AccountIdLookupOf, call_hash: CallHashOf, ) -> DispatchResult { let who = ensure_signed(origin)?; + let real = T::Lookup::lookup(real)?; Self::edit_announcements(&who, |ann| ann.real != real || ann.call_hash != call_hash)?; Ok(()) @@ -472,21 +455,18 @@ pub mod pallet { /// Parameters: /// - `delegate`: The account that previously announced the call. /// - `call_hash`: The hash of the call to be made. - /// - /// # - /// Weight is a function of: - /// - A: the number of announcements made. - /// - P: the number of proxies the user has. - /// # - #[pallet::weight( - T::WeightInfo::reject_announcement(T::MaxPending::get(), T::MaxProxies::get().into()) - )] + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::reject_announcement( + T::MaxPending::get(), + T::MaxProxies::get() + ))] pub fn reject_announcement( origin: OriginFor, - delegate: T::AccountId, + delegate: AccountIdLookupOf, call_hash: CallHashOf, ) -> DispatchResult { let who = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; Self::edit_announcements(&delegate, |ann| { ann.real != who || ann.call_hash != call_hash })?; @@ -505,28 +485,25 @@ pub mod pallet { /// - `real`: The account that the proxy will make a call on behalf of. /// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call. /// - `call`: The call to be made by the `real` account. - /// - /// # - /// Weight is a function of: - /// - A: the number of announcements made. - /// - P: the number of proxies the user has. - /// # + #[pallet::call_index(9)] #[pallet::weight({ let di = call.get_dispatch_info(); - (T::WeightInfo::proxy_announced(T::MaxPending::get(), T::MaxProxies::get().into()) - .saturating_add(di.weight) + (T::WeightInfo::proxy_announced(T::MaxPending::get(), T::MaxProxies::get()) // AccountData for inner call origin accountdata. - .saturating_add(T::DbWeight::get().reads_writes(1, 1)), + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(di.weight), di.class) })] pub fn proxy_announced( origin: OriginFor, - delegate: T::AccountId, - real: T::AccountId, + delegate: AccountIdLookupOf, + real: AccountIdLookupOf, force_proxy_type: Option, - call: Box<::Call>, + call: Box<::RuntimeCall>, ) -> DispatchResult { ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + let real = T::Lookup::lookup(real)?; let def = Self::find_proxy(&real, &delegate, force_proxy_type)?; let call_hash = T::CallHasher::hash_of(&call); @@ -547,22 +524,34 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// A proxy was executed correctly, with the given \[result\]. - ProxyExecuted(DispatchResult), - /// Anonymous account has been created by new proxy with given - /// disambiguation index and proxy type. \[anonymous, who, proxy_type, - /// disambiguation_index\] - AnonymousCreated(T::AccountId, T::AccountId, T::ProxyType, u16), - /// An announcement was placed to make a call in the future. \[real, proxy, call_hash\] - Announced(T::AccountId, T::AccountId, CallHashOf), - /// A proxy was added. \[delegator, delegatee, proxy_type, delay\] - ProxyAdded(T::AccountId, T::AccountId, T::ProxyType, T::BlockNumber), + /// A proxy was executed correctly, with the given. + ProxyExecuted { result: DispatchResult }, + /// A pure account has been created by new proxy with given + /// disambiguation index and proxy type. + PureCreated { + pure: T::AccountId, + who: T::AccountId, + proxy_type: T::ProxyType, + disambiguation_index: u16, + }, + /// An announcement was placed to make a call in the future. + Announced { real: T::AccountId, proxy: T::AccountId, call_hash: CallHashOf }, + /// A proxy was added. + ProxyAdded { + delegator: T::AccountId, + delegatee: T::AccountId, + proxy_type: T::ProxyType, + delay: T::BlockNumber, + }, + /// A proxy was removed. + ProxyRemoved { + delegator: T::AccountId, + delegatee: T::AccountId, + proxy_type: T::ProxyType, + delay: T::BlockNumber, + }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// There are too many proxies registered or too many announcements pending. @@ -614,7 +603,7 @@ pub mod pallet { } impl Pallet { - /// Calculate the address of an anonymous account. + /// Calculate the address of an pure account. /// /// - `who`: The spawner account. /// - `proxy_type`: The type of the proxy that the sender will be registered as over the @@ -623,9 +612,9 @@ impl Pallet { /// - `index`: A disambiguation index, in case this is called multiple times in the same /// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just /// want to use `0`. - /// - `maybe_when`: The block height and extrinsic index of when the anonymous account was + /// - `maybe_when`: The block height and extrinsic index of when the pure account was /// created. None to use current block height and extrinsic index. - pub fn anonymous_account( + pub fn pure_account( who: &T::AccountId, proxy_type: &T::ProxyType, index: u16, @@ -639,7 +628,8 @@ impl Pallet { }); let entropy = (b"modlpy/proxy____", who, height, ext_index, proxy_type, index) .using_encoded(blake2_256); - T::AccountId::decode(&mut &entropy[..]).unwrap_or_default() + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") } /// Register a proxy account for the delegator that is able to make calls on its behalf. @@ -672,12 +662,12 @@ impl Pallet { T::Currency::unreserve(delegator, *deposit - new_deposit); } *deposit = new_deposit; - Self::deposit_event(Event::::ProxyAdded( - delegator.clone(), + Self::deposit_event(Event::::ProxyAdded { + delegator: delegator.clone(), delegatee, proxy_type, delay, - )); + }); Ok(()) }) } @@ -698,7 +688,11 @@ impl Pallet { ) -> DispatchResult { Proxies::::try_mutate_exists(delegator, |x| { let (mut proxies, old_deposit) = x.take().ok_or(Error::::NotFound)?; - let proxy_def = ProxyDefinition { delegate: delegatee, proxy_type, delay }; + let proxy_def = ProxyDefinition { + delegate: delegatee.clone(), + proxy_type: proxy_type.clone(), + delay, + }; let i = proxies.binary_search(&proxy_def).ok().ok_or(Error::::NotFound)?; proxies.remove(i); let new_deposit = Self::deposit(proxies.len() as u32); @@ -710,6 +704,12 @@ impl Pallet { if !proxies.is_empty() { *x = Some((proxies, new_deposit)) } + Self::deposit_event(Event::::ProxyRemoved { + delegator: delegator.clone(), + delegatee, + proxy_type, + delay, + }); Ok(()) }) } @@ -732,9 +732,9 @@ impl Pallet { let new_deposit = if len == 0 { BalanceOf::::zero() } else { base + factor * (len as u32).into() }; if new_deposit > old_deposit { - T::Currency::reserve(&who, new_deposit - old_deposit)?; + T::Currency::reserve(who, new_deposit - old_deposit)?; } else if new_deposit < old_deposit { - T::Currency::unreserve(&who, old_deposit - new_deposit); + T::Currency::unreserve(who, old_deposit - new_deposit); } Ok(if len == 0 { None } else { Some(new_deposit) }) } @@ -777,29 +777,29 @@ impl Pallet { fn do_proxy( def: ProxyDefinition, real: T::AccountId, - call: ::Call, + call: ::RuntimeCall, ) { // This is a freshly authenticated new account, the origin restrictions doesn't apply. - let mut origin: T::Origin = frame_system::RawOrigin::Signed(real).into(); - origin.add_filter(move |c: &::Call| { - let c = ::Call::from_ref(c); + let mut origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(real).into(); + origin.add_filter(move |c: &::RuntimeCall| { + let c = ::RuntimeCall::from_ref(c); // We make sure the proxy call does access this pallet to change modify proxies. match c.is_sub_type() { // Proxy call cannot add or remove a proxy with more permissions than it already // has. Some(Call::add_proxy { ref proxy_type, .. }) | Some(Call::remove_proxy { ref proxy_type, .. }) - if !def.proxy_type.is_superset(&proxy_type) => + if !def.proxy_type.is_superset(proxy_type) => false, - // Proxy call cannot remove all proxies or kill anonymous proxies unless it has full + // Proxy call cannot remove all proxies or kill pure proxies unless it has full // permissions. - Some(Call::remove_proxies { .. }) | Some(Call::kill_anonymous { .. }) + Some(Call::remove_proxies { .. }) | Some(Call::kill_pure { .. }) if def.proxy_type != T::ProxyType::default() => false, _ => def.proxy_type.filter(c), } }); let e = call.dispatch(origin); - Self::deposit_event(Event::ProxyExecuted(e.map(|_| ()).map_err(|e| e.error))); + Self::deposit_event(Event::ProxyExecuted { result: e.map(|_| ()).map_err(|e| e.error) }); } } diff --git a/frame/proxy/src/tests.rs b/frame/proxy/src/tests.rs index d319ebb1a5ab0..17bc7bcb92ee1 100644 --- a/frame/proxy/src/tests.rs +++ b/frame/proxy/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,10 @@ use super::*; use crate as proxy; use codec::{Decode, Encode}; use frame_support::{ - assert_noop, assert_ok, dispatch::DispatchError, parameter_types, traits::Contains, + assert_noop, assert_ok, + dispatch::DispatchError, + parameter_types, + traits::{ConstU32, ConstU64, Contains}, RuntimeDebug, }; use sp_core::H256; @@ -50,26 +53,25 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -78,34 +80,27 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } impl pallet_utility::Config for Test { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; type WeightInfo = (); } -parameter_types! { - pub const ProxyDepositBase: u64 = 1; - pub const ProxyDepositFactor: u64 = 1; - pub const MaxProxies: u16 = 4; - pub const MaxPending: u32 = 2; - pub const AnnouncementDepositBase: u64 = 1; - pub const AnnouncementDepositFactor: u64 = 1; -} + #[derive( Copy, Clone, @@ -129,14 +124,14 @@ impl Default for ProxyType { Self::Any } } -impl InstanceFilter for ProxyType { - fn filter(&self, c: &Call) -> bool { +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { match self { ProxyType::Any => true, ProxyType::JustTransfer => { - matches!(c, Call::Balances(pallet_balances::Call::transfer { .. })) + matches!(c, RuntimeCall::Balances(pallet_balances::Call::transfer { .. })) }, - ProxyType::JustUtility => matches!(c, Call::Utility { .. }), + ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }), } } fn is_superset(&self, o: &Self) -> bool { @@ -144,29 +139,29 @@ impl InstanceFilter for ProxyType { } } pub struct BaseFilter; -impl Contains for BaseFilter { - fn contains(c: &Call) -> bool { +impl Contains for BaseFilter { + fn contains(c: &RuntimeCall) -> bool { match *c { // Remark is used as a no-op call in the benchmarking - Call::System(SystemCall::remark { .. }) => true, - Call::System(_) => false, + RuntimeCall::System(SystemCall::remark { .. }) => true, + RuntimeCall::System(_) => false, _ => true, } } } impl Config for Test { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type Currency = Balances; type ProxyType = ProxyType; - type ProxyDepositBase = ProxyDepositBase; - type ProxyDepositFactor = ProxyDepositFactor; - type MaxProxies = MaxProxies; + type ProxyDepositBase = ConstU64<1>; + type ProxyDepositFactor = ConstU64<1>; + type MaxProxies = ConstU32<4>; type WeightInfo = (); type CallHasher = BlakeTwo256; - type MaxPending = MaxPending; - type AnnouncementDepositBase = AnnouncementDepositBase; - type AnnouncementDepositFactor = AnnouncementDepositFactor; + type MaxPending = ConstU32<2>; + type AnnouncementDepositBase = ConstU64<1>; + type AnnouncementDepositFactor = ConstU64<1>; } use super::{Call as ProxyCall, Event as ProxyEvent}; @@ -174,6 +169,8 @@ use frame_system::Call as SystemCall; use pallet_balances::{Call as BalancesCall, Error as BalancesError, Event as BalancesEvent}; use pallet_utility::{Call as UtilityCall, Event as UtilityEvent}; +type SystemError = frame_system::Error; + pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); pallet_balances::GenesisConfig:: { @@ -186,7 +183,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext } -fn last_events(n: usize) -> Vec { +fn last_events(n: usize) -> Vec { system::Pallet::::events() .into_iter() .rev() @@ -196,23 +193,31 @@ fn last_events(n: usize) -> Vec { .collect() } -fn expect_events(e: Vec) { +fn expect_events(e: Vec) { assert_eq!(last_events(e.len()), e); } -fn call_transfer(dest: u64, value: u64) -> Call { - Call::Balances(BalancesCall::transfer { dest, value }) +fn call_transfer(dest: u64, value: u64) -> RuntimeCall { + RuntimeCall::Balances(BalancesCall::transfer { dest, value }) } #[test] fn announcement_works() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); - System::assert_last_event(ProxyEvent::ProxyAdded(1, 3, ProxyType::Any, 1).into()); - assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 3, ProxyType::Any, 1)); + System::assert_last_event( + ProxyEvent::ProxyAdded { + delegator: 1, + delegatee: 3, + proxy_type: ProxyType::Any, + delay: 1, + } + .into(), + ); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(2), 3, ProxyType::Any, 1)); assert_eq!(Balances::reserved_balance(3), 0); - assert_ok!(Proxy::announce(Origin::signed(3), 1, [1; 32].into())); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 1, [1; 32].into())); let announcements = Announcements::::get(3); assert_eq!( announcements.0, @@ -220,7 +225,7 @@ fn announcement_works() { ); assert_eq!(Balances::reserved_balance(3), announcements.1); - assert_ok!(Proxy::announce(Origin::signed(3), 2, [2; 32].into())); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 2, [2; 32].into())); let announcements = Announcements::::get(3); assert_eq!( announcements.0, @@ -231,20 +236,23 @@ fn announcement_works() { ); assert_eq!(Balances::reserved_balance(3), announcements.1); - assert_noop!(Proxy::announce(Origin::signed(3), 2, [3; 32].into()), Error::::TooMany); + assert_noop!( + Proxy::announce(RuntimeOrigin::signed(3), 2, [3; 32].into()), + Error::::TooMany + ); }); } #[test] fn remove_announcement_works() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); - assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); - assert_ok!(Proxy::announce(Origin::signed(3), 1, [1; 32].into())); - assert_ok!(Proxy::announce(Origin::signed(3), 2, [2; 32].into())); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(2), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 1, [1; 32].into())); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 2, [2; 32].into())); let e = Error::::NotFound; - assert_noop!(Proxy::remove_announcement(Origin::signed(3), 1, [0; 32].into()), e); - assert_ok!(Proxy::remove_announcement(Origin::signed(3), 1, [1; 32].into())); + assert_noop!(Proxy::remove_announcement(RuntimeOrigin::signed(3), 1, [0; 32].into()), e); + assert_ok!(Proxy::remove_announcement(RuntimeOrigin::signed(3), 1, [1; 32].into())); let announcements = Announcements::::get(3); assert_eq!( announcements.0, @@ -257,15 +265,15 @@ fn remove_announcement_works() { #[test] fn reject_announcement_works() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); - assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); - assert_ok!(Proxy::announce(Origin::signed(3), 1, [1; 32].into())); - assert_ok!(Proxy::announce(Origin::signed(3), 2, [2; 32].into())); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(2), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 1, [1; 32].into())); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 2, [2; 32].into())); let e = Error::::NotFound; - assert_noop!(Proxy::reject_announcement(Origin::signed(1), 3, [0; 32].into()), e); + assert_noop!(Proxy::reject_announcement(RuntimeOrigin::signed(1), 3, [0; 32].into()), e); let e = Error::::NotFound; - assert_noop!(Proxy::reject_announcement(Origin::signed(4), 3, [1; 32].into()), e); - assert_ok!(Proxy::reject_announcement(Origin::signed(1), 3, [1; 32].into())); + assert_noop!(Proxy::reject_announcement(RuntimeOrigin::signed(4), 3, [1; 32].into()), e); + assert_ok!(Proxy::reject_announcement(RuntimeOrigin::signed(1), 3, [1; 32].into())); let announcements = Announcements::::get(3); assert_eq!( announcements.0, @@ -278,41 +286,44 @@ fn reject_announcement_works() { #[test] fn announcer_must_be_proxy() { new_test_ext().execute_with(|| { - assert_noop!(Proxy::announce(Origin::signed(2), 1, H256::zero()), Error::::NotProxy); + assert_noop!( + Proxy::announce(RuntimeOrigin::signed(2), 1, H256::zero()), + Error::::NotProxy + ); }); } #[test] fn delayed_requires_pre_announcement() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 1)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::Any, 1)); let call = Box::new(call_transfer(6, 1)); let e = Error::::Unannounced; - assert_noop!(Proxy::proxy(Origin::signed(2), 1, None, call.clone()), e); + assert_noop!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call.clone()), e); let e = Error::::Unannounced; - assert_noop!(Proxy::proxy_announced(Origin::signed(0), 2, 1, None, call.clone()), e); + assert_noop!(Proxy::proxy_announced(RuntimeOrigin::signed(0), 2, 1, None, call.clone()), e); let call_hash = BlakeTwo256::hash_of(&call); - assert_ok!(Proxy::announce(Origin::signed(2), 1, call_hash)); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(2), 1, call_hash)); system::Pallet::::set_block_number(2); - assert_ok!(Proxy::proxy_announced(Origin::signed(0), 2, 1, None, call.clone())); + assert_ok!(Proxy::proxy_announced(RuntimeOrigin::signed(0), 2, 1, None, call.clone())); }); } #[test] fn proxy_announced_removes_announcement_and_returns_deposit() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); - assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(2), 3, ProxyType::Any, 1)); let call = Box::new(call_transfer(6, 1)); let call_hash = BlakeTwo256::hash_of(&call); - assert_ok!(Proxy::announce(Origin::signed(3), 1, call_hash)); - assert_ok!(Proxy::announce(Origin::signed(3), 2, call_hash)); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 1, call_hash)); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 2, call_hash)); // Too early to execute announced call let e = Error::::Unannounced; - assert_noop!(Proxy::proxy_announced(Origin::signed(0), 3, 1, None, call.clone()), e); + assert_noop!(Proxy::proxy_announced(RuntimeOrigin::signed(0), 3, 1, None, call.clone()), e); system::Pallet::::set_block_number(2); - assert_ok!(Proxy::proxy_announced(Origin::signed(0), 3, 1, None, call.clone())); + assert_ok!(Proxy::proxy_announced(RuntimeOrigin::signed(0), 3, 1, None, call.clone())); let announcements = Announcements::::get(3); assert_eq!(announcements.0, vec![Announcement { real: 2, call_hash, height: 1 }]); assert_eq!(Balances::reserved_balance(3), announcements.1); @@ -323,70 +334,91 @@ fn proxy_announced_removes_announcement_and_returns_deposit() { fn filtering_works() { new_test_ext().execute_with(|| { assert!(Balances::mutate_account(&1, |a| a.free = 1000).is_ok()); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 0)); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::JustTransfer, 0)); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::JustUtility, 0)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::Any, 0)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 3, ProxyType::JustTransfer, 0)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 4, ProxyType::JustUtility, 0)); let call = Box::new(call_transfer(6, 1)); - assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Ok(())).into()); - assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Ok(())).into()); - assert_ok!(Proxy::proxy(Origin::signed(4), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin)).into()); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call.clone())); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(3), 1, None, call.clone())); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(4), 1, None, call.clone())); + System::assert_last_event( + ProxyEvent::ProxyExecuted { result: Err(SystemError::CallFiltered.into()) }.into(), + ); let derivative_id = Utility::derivative_account_id(1, 0); assert!(Balances::mutate_account(&derivative_id, |a| a.free = 1000).is_ok()); let inner = Box::new(call_transfer(6, 1)); - let call = - Box::new(Call::Utility(UtilityCall::as_derivative { index: 0, call: inner.clone() })); - assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Ok(())).into()); - assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin)).into()); - assert_ok!(Proxy::proxy(Origin::signed(4), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin)).into()); - - let call = Box::new(Call::Utility(UtilityCall::batch { calls: vec![*inner] })); - assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); + let call = Box::new(RuntimeCall::Utility(UtilityCall::as_derivative { + index: 0, + call: inner.clone(), + })); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call.clone())); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(3), 1, None, call.clone())); + System::assert_last_event( + ProxyEvent::ProxyExecuted { result: Err(SystemError::CallFiltered.into()) }.into(), + ); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(4), 1, None, call.clone())); + System::assert_last_event( + ProxyEvent::ProxyExecuted { result: Err(SystemError::CallFiltered.into()) }.into(), + ); + + let call = Box::new(RuntimeCall::Utility(UtilityCall::batch { calls: vec![*inner] })); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call.clone())); expect_events(vec![ UtilityEvent::BatchCompleted.into(), - ProxyEvent::ProxyExecuted(Ok(())).into(), + ProxyEvent::ProxyExecuted { result: Ok(()) }.into(), ]); - assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin)).into()); - assert_ok!(Proxy::proxy(Origin::signed(4), 1, None, call.clone())); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(3), 1, None, call.clone())); + System::assert_last_event( + ProxyEvent::ProxyExecuted { result: Err(SystemError::CallFiltered.into()) }.into(), + ); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(4), 1, None, call.clone())); expect_events(vec![ - UtilityEvent::BatchInterrupted(0, DispatchError::BadOrigin).into(), - ProxyEvent::ProxyExecuted(Ok(())).into(), + UtilityEvent::BatchInterrupted { index: 0, error: SystemError::CallFiltered.into() } + .into(), + ProxyEvent::ProxyExecuted { result: Ok(()) }.into(), ]); - let inner = - Box::new(Call::Proxy(ProxyCall::new_call_variant_add_proxy(5, ProxyType::Any, 0))); - let call = Box::new(Call::Utility(UtilityCall::batch { calls: vec![*inner] })); - assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); + let inner = Box::new(RuntimeCall::Proxy(ProxyCall::new_call_variant_add_proxy( + 5, + ProxyType::Any, + 0, + ))); + let call = Box::new(RuntimeCall::Utility(UtilityCall::batch { calls: vec![*inner] })); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call.clone())); expect_events(vec![ UtilityEvent::BatchCompleted.into(), - ProxyEvent::ProxyExecuted(Ok(())).into(), + ProxyEvent::ProxyExecuted { result: Ok(()) }.into(), ]); - assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin)).into()); - assert_ok!(Proxy::proxy(Origin::signed(4), 1, None, call.clone())); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(3), 1, None, call.clone())); + System::assert_last_event( + ProxyEvent::ProxyExecuted { result: Err(SystemError::CallFiltered.into()) }.into(), + ); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(4), 1, None, call.clone())); expect_events(vec![ - UtilityEvent::BatchInterrupted(0, DispatchError::BadOrigin).into(), - ProxyEvent::ProxyExecuted(Ok(())).into(), + UtilityEvent::BatchInterrupted { index: 0, error: SystemError::CallFiltered.into() } + .into(), + ProxyEvent::ProxyExecuted { result: Ok(()) }.into(), ]); - let call = Box::new(Call::Proxy(ProxyCall::remove_proxies {})); - assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin)).into()); - assert_ok!(Proxy::proxy(Origin::signed(4), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin)).into()); - assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); + let call = Box::new(RuntimeCall::Proxy(ProxyCall::remove_proxies {})); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(3), 1, None, call.clone())); + System::assert_last_event( + ProxyEvent::ProxyExecuted { result: Err(SystemError::CallFiltered.into()) }.into(), + ); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(4), 1, None, call.clone())); + System::assert_last_event( + ProxyEvent::ProxyExecuted { result: Err(SystemError::CallFiltered.into()) }.into(), + ); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call.clone())); expect_events(vec![ - BalancesEvent::::Unreserved(1, 5).into(), - ProxyEvent::ProxyExecuted(Ok(())).into(), + BalancesEvent::::Unreserved { who: 1, amount: 5 }.into(), + ProxyEvent::ProxyExecuted { result: Ok(()) }.into(), ]); }); } @@ -394,36 +426,72 @@ fn filtering_works() { #[test] fn add_remove_proxies_works() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 0)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::Any, 0)); assert_noop!( - Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 0), + Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::Any, 0), Error::::Duplicate ); assert_eq!(Balances::reserved_balance(1), 2); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::JustTransfer, 0)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::JustTransfer, 0)); assert_eq!(Balances::reserved_balance(1), 3); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 0)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 3, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 4); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::JustUtility, 0)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 4, ProxyType::JustUtility, 0)); assert_eq!(Balances::reserved_balance(1), 5); assert_noop!( - Proxy::add_proxy(Origin::signed(1), 4, ProxyType::Any, 0), + Proxy::add_proxy(RuntimeOrigin::signed(1), 4, ProxyType::Any, 0), Error::::TooMany ); assert_noop!( - Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::JustTransfer, 0), + Proxy::remove_proxy(RuntimeOrigin::signed(1), 3, ProxyType::JustTransfer, 0), Error::::NotFound ); - assert_ok!(Proxy::remove_proxy(Origin::signed(1), 4, ProxyType::JustUtility, 0)); + assert_ok!(Proxy::remove_proxy(RuntimeOrigin::signed(1), 4, ProxyType::JustUtility, 0)); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 4, + proxy_type: ProxyType::JustUtility, + delay: 0, + } + .into(), + ); assert_eq!(Balances::reserved_balance(1), 4); - assert_ok!(Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::Any, 0)); + assert_ok!(Proxy::remove_proxy(RuntimeOrigin::signed(1), 3, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 3); - assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::Any, 0)); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 3, + proxy_type: ProxyType::Any, + delay: 0, + } + .into(), + ); + assert_ok!(Proxy::remove_proxy(RuntimeOrigin::signed(1), 2, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 2); - assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::JustTransfer, 0)); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 2, + proxy_type: ProxyType::Any, + delay: 0, + } + .into(), + ); + assert_ok!(Proxy::remove_proxy(RuntimeOrigin::signed(1), 2, ProxyType::JustTransfer, 0)); assert_eq!(Balances::reserved_balance(1), 0); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 2, + proxy_type: ProxyType::JustTransfer, + delay: 0, + } + .into(), + ); assert_noop!( - Proxy::add_proxy(Origin::signed(1), 1, ProxyType::Any, 0), + Proxy::add_proxy(RuntimeOrigin::signed(1), 1, ProxyType::Any, 0), Error::::NoSelfProxy ); }); @@ -432,10 +500,10 @@ fn add_remove_proxies_works() { #[test] fn cannot_add_proxy_without_balance() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(5), 3, ProxyType::Any, 0)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(5), 3, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(5), 2); assert_noop!( - Proxy::add_proxy(Origin::signed(5), 4, ProxyType::Any, 0), + Proxy::add_proxy(RuntimeOrigin::signed(5), 4, ProxyType::Any, 0), BalancesError::::InsufficientBalance ); }); @@ -444,86 +512,98 @@ fn cannot_add_proxy_without_balance() { #[test] fn proxying_works() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::JustTransfer, 0)); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 0)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::JustTransfer, 0)); + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 3, ProxyType::Any, 0)); let call = Box::new(call_transfer(6, 1)); assert_noop!( - Proxy::proxy(Origin::signed(4), 1, None, call.clone()), + Proxy::proxy(RuntimeOrigin::signed(4), 1, None, call.clone()), Error::::NotProxy ); assert_noop!( - Proxy::proxy(Origin::signed(2), 1, Some(ProxyType::Any), call.clone()), + Proxy::proxy(RuntimeOrigin::signed(2), 1, Some(ProxyType::Any), call.clone()), Error::::NotProxy ); - assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Ok(())).into()); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call.clone())); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); assert_eq!(Balances::free_balance(6), 1); - let call = Box::new(Call::System(SystemCall::set_code { code: vec![] })); - assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin)).into()); - - let call = - Box::new(Call::Balances(BalancesCall::transfer_keep_alive { dest: 6, value: 1 })); - assert_ok!(Call::Proxy(super::Call::new_call_variant_proxy(1, None, call.clone())) - .dispatch(Origin::signed(2))); - System::assert_last_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin)).into()); - assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); - System::assert_last_event(ProxyEvent::ProxyExecuted(Ok(())).into()); + let call = Box::new(RuntimeCall::System(SystemCall::set_code { code: vec![] })); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(3), 1, None, call.clone())); + System::assert_last_event( + ProxyEvent::ProxyExecuted { result: Err(SystemError::CallFiltered.into()) }.into(), + ); + + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_keep_alive { + dest: 6, + value: 1, + })); + assert_ok!(RuntimeCall::Proxy(super::Call::new_call_variant_proxy(1, None, call.clone())) + .dispatch(RuntimeOrigin::signed(2))); + System::assert_last_event( + ProxyEvent::ProxyExecuted { result: Err(SystemError::CallFiltered.into()) }.into(), + ); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(3), 1, None, call.clone())); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); assert_eq!(Balances::free_balance(6), 2); }); } #[test] -fn anonymous_works() { +fn pure_works() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0)); - let anon = Proxy::anonymous_account(&1, &ProxyType::Any, 0, None); + assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0)); + let anon = Proxy::pure_account(&1, &ProxyType::Any, 0, None); System::assert_last_event( - ProxyEvent::AnonymousCreated(anon.clone(), 1, ProxyType::Any, 0).into(), + ProxyEvent::PureCreated { + pure: anon, + who: 1, + proxy_type: ProxyType::Any, + disambiguation_index: 0, + } + .into(), ); - // other calls to anonymous allowed as long as they're not exactly the same. - assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::JustTransfer, 0, 0)); - assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 1)); - let anon2 = Proxy::anonymous_account(&2, &ProxyType::Any, 0, None); - assert_ok!(Proxy::anonymous(Origin::signed(2), ProxyType::Any, 0, 0)); + // other calls to pure allowed as long as they're not exactly the same. + assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::JustTransfer, 0, 0)); + assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 1)); + let anon2 = Proxy::pure_account(&2, &ProxyType::Any, 0, None); + assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(2), ProxyType::Any, 0, 0)); assert_noop!( - Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0), + Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0), Error::::Duplicate ); System::set_extrinsic_index(1); - assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0)); + assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0)); System::set_extrinsic_index(0); System::set_block_number(2); - assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0)); + assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0)); let call = Box::new(call_transfer(6, 1)); - assert_ok!(Balances::transfer(Origin::signed(3), anon, 5)); - assert_ok!(Proxy::proxy(Origin::signed(1), anon, None, call)); - System::assert_last_event(ProxyEvent::ProxyExecuted(Ok(())).into()); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), anon, 5)); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call)); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); assert_eq!(Balances::free_balance(6), 1); - let call = Box::new(Call::Proxy(ProxyCall::new_call_variant_kill_anonymous( + let call = Box::new(RuntimeCall::Proxy(ProxyCall::new_call_variant_kill_pure( 1, ProxyType::Any, 0, 1, 0, ))); - assert_ok!(Proxy::proxy(Origin::signed(2), anon2, None, call.clone())); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), anon2, None, call.clone())); let de = DispatchError::from(Error::::NoPermission).stripped(); - System::assert_last_event(ProxyEvent::ProxyExecuted(Err(de)).into()); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Err(de) }.into()); assert_noop!( - Proxy::kill_anonymous(Origin::signed(1), 1, ProxyType::Any, 0, 1, 0), + Proxy::kill_pure(RuntimeOrigin::signed(1), 1, ProxyType::Any, 0, 1, 0), Error::::NoPermission ); assert_eq!(Balances::free_balance(1), 0); - assert_ok!(Proxy::proxy(Origin::signed(1), anon, None, call.clone())); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call.clone())); assert_eq!(Balances::free_balance(1), 2); assert_noop!( - Proxy::proxy(Origin::signed(1), anon, None, call.clone()), + Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call.clone()), Error::::NotProxy ); }); diff --git a/frame/proxy/src/weights.rs b/frame/proxy/src/weights.rs index 41aa3034bece1..706810d3402ec 100644 --- a/frame/proxy/src/weights.rs +++ b/frame/proxy/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_proxy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/proxy/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -53,195 +55,251 @@ pub trait WeightInfo { fn add_proxy(p: u32, ) -> Weight; fn remove_proxy(p: u32, ) -> Weight; fn remove_proxies(p: u32, ) -> Weight; - fn anonymous(p: u32, ) -> Weight; - fn kill_anonymous(p: u32, ) -> Weight; + fn create_pure(p: u32, ) -> Weight; + fn kill_pure(p: u32, ) -> Weight; } /// Weights for pallet_proxy using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Proxy Proxies (r:1 w:0) + /// The range of component `p` is `[1, 31]`. fn proxy(p: u32, ) -> Weight { - (23_213_000 as Weight) - // Standard Error: 2_000 - .saturating_add((153_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) + // Minimum execution time: 24_285 nanoseconds. + Weight::from_ref_time(25_355_667 as u64) + // Standard Error: 1_468 + .saturating_add(Weight::from_ref_time(38_185 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn proxy_announced(a: u32, p: u32, ) -> Weight { - (53_286_000 as Weight) - // Standard Error: 2_000 - .saturating_add((549_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 2_000 - .saturating_add((138_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 44_948 nanoseconds. + Weight::from_ref_time(44_762_064 as u64) + // Standard Error: 1_778 + .saturating_add(Weight::from_ref_time(118_940 as u64).saturating_mul(a as u64)) + // Standard Error: 1_837 + .saturating_add(Weight::from_ref_time(51_232 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) - fn remove_announcement(a: u32, _p: u32, ) -> Weight { - (36_864_000 as Weight) - // Standard Error: 2_000 - .saturating_add((550_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn remove_announcement(a: u32, p: u32, ) -> Weight { + // Minimum execution time: 31_274 nanoseconds. + Weight::from_ref_time(32_219_165 as u64) + // Standard Error: 1_832 + .saturating_add(Weight::from_ref_time(132_454 as u64).saturating_mul(a as u64)) + // Standard Error: 1_893 + .saturating_add(Weight::from_ref_time(9_077 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) - fn reject_announcement(a: u32, _p: u32, ) -> Weight { - (36_755_000 as Weight) - // Standard Error: 1_000 - .saturating_add((550_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn reject_announcement(a: u32, p: u32, ) -> Weight { + // Minimum execution time: 31_219 nanoseconds. + Weight::from_ref_time(32_439_563 as u64) + // Standard Error: 1_829 + .saturating_add(Weight::from_ref_time(120_251 as u64).saturating_mul(a as u64)) + // Standard Error: 1_890 + .saturating_add(Weight::from_ref_time(8_689 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn announce(a: u32, p: u32, ) -> Weight { - (50_765_000 as Weight) - // Standard Error: 2_000 - .saturating_add((547_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 2_000 - .saturating_add((141_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 40_388 nanoseconds. + Weight::from_ref_time(40_718_245 as u64) + // Standard Error: 1_821 + .saturating_add(Weight::from_ref_time(129_674 as u64).saturating_mul(a as u64)) + // Standard Error: 1_882 + .saturating_add(Weight::from_ref_time(56_001 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn add_proxy(p: u32, ) -> Weight { - (35_556_000 as Weight) - // Standard Error: 3_000 - .saturating_add((211_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 33_997 nanoseconds. + Weight::from_ref_time(34_840_036 as u64) + // Standard Error: 1_659 + .saturating_add(Weight::from_ref_time(71_349 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn remove_proxy(p: u32, ) -> Weight { - (35_284_000 as Weight) - // Standard Error: 3_000 - .saturating_add((229_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 33_900 nanoseconds. + Weight::from_ref_time(35_069_110 as u64) + // Standard Error: 1_848 + .saturating_add(Weight::from_ref_time(82_380 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn remove_proxies(p: u32, ) -> Weight { - (34_449_000 as Weight) - // Standard Error: 2_000 - .saturating_add((146_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 29_627 nanoseconds. + Weight::from_ref_time(30_641_642 as u64) + // Standard Error: 1_495 + .saturating_add(Weight::from_ref_time(51_919 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: Proxy Proxies (r:1 w:1) - fn anonymous(p: u32, ) -> Weight { - (49_149_000 as Weight) - // Standard Error: 2_000 - .saturating_add((15_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + /// The range of component `p` is `[1, 31]`. + fn create_pure(p: u32, ) -> Weight { + // Minimum execution time: 37_761 nanoseconds. + Weight::from_ref_time(38_748_697 as u64) + // Standard Error: 1_594 + .saturating_add(Weight::from_ref_time(19_022 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Proxy Proxies (r:1 w:1) - fn kill_anonymous(p: u32, ) -> Weight { - (36_399_000 as Weight) - // Standard Error: 2_000 - .saturating_add((152_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + /// The range of component `p` is `[0, 30]`. + fn kill_pure(p: u32, ) -> Weight { + // Minimum execution time: 31_145 nanoseconds. + Weight::from_ref_time(31_933_568 as u64) + // Standard Error: 1_492 + .saturating_add(Weight::from_ref_time(50_250 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { // Storage: Proxy Proxies (r:1 w:0) + /// The range of component `p` is `[1, 31]`. fn proxy(p: u32, ) -> Weight { - (23_213_000 as Weight) - // Standard Error: 2_000 - .saturating_add((153_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + // Minimum execution time: 24_285 nanoseconds. + Weight::from_ref_time(25_355_667 as u64) + // Standard Error: 1_468 + .saturating_add(Weight::from_ref_time(38_185 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn proxy_announced(a: u32, p: u32, ) -> Weight { - (53_286_000 as Weight) - // Standard Error: 2_000 - .saturating_add((549_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 2_000 - .saturating_add((138_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 44_948 nanoseconds. + Weight::from_ref_time(44_762_064 as u64) + // Standard Error: 1_778 + .saturating_add(Weight::from_ref_time(118_940 as u64).saturating_mul(a as u64)) + // Standard Error: 1_837 + .saturating_add(Weight::from_ref_time(51_232 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) - fn remove_announcement(a: u32, _p: u32, ) -> Weight { - (36_864_000 as Weight) - // Standard Error: 2_000 - .saturating_add((550_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn remove_announcement(a: u32, p: u32, ) -> Weight { + // Minimum execution time: 31_274 nanoseconds. + Weight::from_ref_time(32_219_165 as u64) + // Standard Error: 1_832 + .saturating_add(Weight::from_ref_time(132_454 as u64).saturating_mul(a as u64)) + // Standard Error: 1_893 + .saturating_add(Weight::from_ref_time(9_077 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) - fn reject_announcement(a: u32, _p: u32, ) -> Weight { - (36_755_000 as Weight) - // Standard Error: 1_000 - .saturating_add((550_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn reject_announcement(a: u32, p: u32, ) -> Weight { + // Minimum execution time: 31_219 nanoseconds. + Weight::from_ref_time(32_439_563 as u64) + // Standard Error: 1_829 + .saturating_add(Weight::from_ref_time(120_251 as u64).saturating_mul(a as u64)) + // Standard Error: 1_890 + .saturating_add(Weight::from_ref_time(8_689 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn announce(a: u32, p: u32, ) -> Weight { - (50_765_000 as Weight) - // Standard Error: 2_000 - .saturating_add((547_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 2_000 - .saturating_add((141_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 40_388 nanoseconds. + Weight::from_ref_time(40_718_245 as u64) + // Standard Error: 1_821 + .saturating_add(Weight::from_ref_time(129_674 as u64).saturating_mul(a as u64)) + // Standard Error: 1_882 + .saturating_add(Weight::from_ref_time(56_001 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn add_proxy(p: u32, ) -> Weight { - (35_556_000 as Weight) - // Standard Error: 3_000 - .saturating_add((211_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 33_997 nanoseconds. + Weight::from_ref_time(34_840_036 as u64) + // Standard Error: 1_659 + .saturating_add(Weight::from_ref_time(71_349 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn remove_proxy(p: u32, ) -> Weight { - (35_284_000 as Weight) - // Standard Error: 3_000 - .saturating_add((229_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 33_900 nanoseconds. + Weight::from_ref_time(35_069_110 as u64) + // Standard Error: 1_848 + .saturating_add(Weight::from_ref_time(82_380 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn remove_proxies(p: u32, ) -> Weight { - (34_449_000 as Weight) - // Standard Error: 2_000 - .saturating_add((146_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 29_627 nanoseconds. + Weight::from_ref_time(30_641_642 as u64) + // Standard Error: 1_495 + .saturating_add(Weight::from_ref_time(51_919 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: Proxy Proxies (r:1 w:1) - fn anonymous(p: u32, ) -> Weight { - (49_149_000 as Weight) - // Standard Error: 2_000 - .saturating_add((15_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + /// The range of component `p` is `[1, 31]`. + fn create_pure(p: u32, ) -> Weight { + // Minimum execution time: 37_761 nanoseconds. + Weight::from_ref_time(38_748_697 as u64) + // Standard Error: 1_594 + .saturating_add(Weight::from_ref_time(19_022 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Proxy Proxies (r:1 w:1) - fn kill_anonymous(p: u32, ) -> Weight { - (36_399_000 as Weight) - // Standard Error: 2_000 - .saturating_add((152_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + /// The range of component `p` is `[0, 30]`. + fn kill_pure(p: u32, ) -> Weight { + // Minimum execution time: 31_145 nanoseconds. + Weight::from_ref_time(31_933_568 as u64) + // Standard Error: 1_492 + .saturating_add(Weight::from_ref_time(50_250 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } } diff --git a/frame/randomness-collective-flip/Cargo.toml b/frame/randomness-collective-flip/Cargo.toml index 5e8eb6b082879..5a20949e9f243 100644 --- a/frame/randomness-collective-flip/Cargo.toml +++ b/frame/randomness-collective-flip/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-randomness-collective-flip" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME randomness collective flip pallet" readme = "README.md" @@ -13,28 +13,27 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } safe-mix = { version = "1.0", default-features = false } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } - +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "safe-mix/std", "codec/std", + "frame-support/std", + "frame-system/std", + "safe-mix/std", "scale-info/std", "sp-runtime/std", "sp-std/std", - "frame-system/std", - "frame-support/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/randomness-collective-flip/README.md b/frame/randomness-collective-flip/README.md index 9885c734d9fad..0730d4abf7cf2 100644 --- a/frame/randomness-collective-flip/README.md +++ b/frame/randomness-collective-flip/README.md @@ -20,18 +20,28 @@ the system trait. ### Example - Get random seed for the current block ```rust -use frame_support::{decl_module, dispatch, traits::Randomness}; - -pub trait Config: frame_system::Config {} - -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - #[weight = 0] - pub fn random_module_example(origin) -> dispatch::DispatchResult { - let _random_value = >::random(&b"my context"[..]); - Ok(()) - } - } +use frame_support::traits::Randomness; + +#[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: frame_system::Config + pallet_randomness_collective_flip::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn random_module_example(origin: OriginFor) -> DispatchResult { + let _random_value = >::random(&b"my context"[..]); + Ok(()) + } + } } ``` diff --git a/frame/randomness-collective-flip/src/lib.rs b/frame/randomness-collective-flip/src/lib.rs index 1b1d5cb5cd823..be970ba2a8422 100644 --- a/frame/randomness-collective-flip/src/lib.rs +++ b/frame/randomness-collective-flip/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Randomness Module +//! # Randomness Pallet //! -//! The Randomness Collective Flip module provides a [`random`](./struct.Module.html#method.random) +//! The Randomness Collective Flip pallet provides a [`random`](./struct.Module.html#method.random) //! function that generates low-influence random values based on the block hashes from the previous //! `81` blocks. Low-influence randomness can be useful when defending against relatively weak //! adversaries. Using this pallet as a randomness source is advisable primarily in low-security @@ -31,7 +31,7 @@ //! //! ### Prerequisites //! -//! Import the Randomness Collective Flip module and derive your module's configuration trait from +//! Import the Randomness Collective Flip pallet and derive your pallet's configuration trait from //! the system trait. //! //! ### Example - Get random seed for the current block @@ -41,9 +41,9 @@ //! //! #[frame_support::pallet] //! pub mod pallet { +//! use super::*; //! use frame_support::pallet_prelude::*; //! use frame_system::pallet_prelude::*; -//! use super::*; //! //! #[pallet::pallet] //! #[pallet::generate_store(pub(super) trait Store)] @@ -69,9 +69,8 @@ use safe_mix::TripletMix; use codec::Encode; -use frame_support::traits::Randomness; +use frame_support::{pallet_prelude::Weight, traits::Randomness}; use sp_runtime::traits::{Hash, Saturating}; -use sp_std::{convert::TryInto, prelude::*}; const RANDOM_MATERIAL_LEN: u32 = 81; @@ -102,9 +101,7 @@ pub mod pallet { let parent_hash = >::parent_hash(); >::mutate(|ref mut values| { - if values.len() < RANDOM_MATERIAL_LEN as usize { - values.push(parent_hash) - } else { + if values.try_push(parent_hash).is_err() { let index = block_number_to_index::(block_number); values[index] = parent_hash; } @@ -119,7 +116,8 @@ pub mod pallet { /// the oldest hash. #[pallet::storage] #[pallet::getter(fn random_material)] - pub(super) type RandomMaterial = StorageValue<_, Vec, ValueQuery>; + pub(super) type RandomMaterial = + StorageValue<_, BoundedVec>, ValueQuery>; } impl Randomness for Pallet { @@ -169,7 +167,7 @@ mod tests { use frame_support::{ parameter_types, - traits::{OnInitialize, Randomness}, + traits::{ConstU32, ConstU64, OnInitialize, Randomness}, }; use frame_system::limits; @@ -188,9 +186,8 @@ mod tests { ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: limits::BlockWeights = limits::BlockWeights - ::simple_max(1024); + ::simple_max(Weight::from_ref_time(1024)); pub BlockLength: limits::BlockLength = limits::BlockLength ::max(2 * 1024); } @@ -200,17 +197,17 @@ mod tests { type BlockWeights = (); type BlockLength = BlockLength; type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -219,6 +216,7 @@ mod tests { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet_randomness_collective_flip::Config for Test {} @@ -239,7 +237,8 @@ mod tests { let mut parent_hash = System::parent_hash(); for i in 1..(blocks + 1) { - System::initialize(&i, &parent_hash, &Default::default(), frame_system::InitKind::Full); + System::reset_events(); + System::initialize(&i, &parent_hash, &Default::default()); CollectiveFlip::on_initialize(i); let header = System::finalize(); diff --git a/frame/ranked-collective/Cargo.toml b/frame/ranked-collective/Cargo.toml new file mode 100644 index 0000000000000..c5e79eb68f24d --- /dev/null +++ b/frame/ranked-collective/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "pallet-ranked-collective" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Ranked collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/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", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/ranked-collective/README.md b/frame/ranked-collective/README.md new file mode 100644 index 0000000000000..b5fe65ef34920 --- /dev/null +++ b/frame/ranked-collective/README.md @@ -0,0 +1,22 @@ +# Ranked collective system. + +This is a membership pallet providing a `Tally` implementation ready for use with polling +systems such as the Referenda pallet. Members each have a rank, with zero being the lowest. +There is no complexity limitation on either the number of members at a rank or the number of +ranks in the system thus allowing potentially public membership. A member of at least a given +rank can be selected at random in O(1) time, allowing for various games to constructed using +this as a primitive. Members may only be promoted and demoted by one rank at a time, however +all operations (save one) are O(1) in complexity. The only operation which is not O(1) is the +`remove_member` since they must be removed from all ranks from the present down to zero. + +Different ranks have different voting power, and are able to vote in different polls. In general +rank privileges are cumulative. Higher ranks are able to vote in any polls open to lower ranks. +Similarly, higher ranks always have at least as much voting power in any given poll as lower +ranks. + +Two `Config` trait items control these "rank privileges": `MinRankOfClass` and `VoteWeight`. +The first controls which ranks are allowed to vote on a particular class of poll. The second +controls the weight of a vote given the voters rank compared to the minimum rank of the poll. + +An origin control, `EnsureRank`, ensures that the origin is a member of the collective of at +least a particular rank. diff --git a/frame/ranked-collective/src/benchmarking.rs b/frame/ranked-collective/src/benchmarking.rs new file mode 100644 index 0000000000000..eb629b330abb2 --- /dev/null +++ b/frame/ranked-collective/src/benchmarking.rs @@ -0,0 +1,164 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Staking pallet benchmarking. + +use super::*; +#[allow(unused_imports)] +use crate::Pallet as RankedCollective; + +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; +use frame_support::{assert_ok, dispatch::UnfilteredDispatchable}; +use frame_system::RawOrigin as SystemOrigin; + +const SEED: u32 = 0; + +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn make_member, I: 'static>(rank: Rank) -> T::AccountId { + let who = account::("member", MemberCount::::get(0), SEED); + let who_lookup = T::Lookup::unlookup(who.clone()); + assert_ok!(Pallet::::add_member( + T::PromoteOrigin::successful_origin(), + who_lookup.clone() + )); + for _ in 0..rank { + assert_ok!(Pallet::::promote_member( + T::PromoteOrigin::successful_origin(), + who_lookup.clone() + )); + } + who +} + +benchmarks_instance_pallet! { + add_member { + let who = account::("member", 0, SEED); + let who_lookup = T::Lookup::unlookup(who.clone()); + let origin = T::PromoteOrigin::successful_origin(); + let call = Call::::add_member { who: who_lookup }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_eq!(MemberCount::::get(0), 1); + assert_last_event::(Event::MemberAdded { who }.into()); + } + + remove_member { + let r in 0 .. 10; + let rank = r as u16; + let first = make_member::(rank); + let who = make_member::(rank); + let who_lookup = T::Lookup::unlookup(who.clone()); + let last = make_member::(rank); + let last_index = (0..=rank).map(|r| IdToIndex::::get(r, &last).unwrap()).collect::>(); + let origin = T::DemoteOrigin::successful_origin(); + let call = Call::::remove_member { who: who_lookup, min_rank: rank }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + for r in 0..=rank { + assert_eq!(MemberCount::::get(r), 2); + assert_ne!(last_index[r as usize], IdToIndex::::get(r, &last).unwrap()); + } + assert_last_event::(Event::MemberRemoved { who, rank }.into()); + } + + promote_member { + let r in 0 .. 10; + let rank = r as u16; + let who = make_member::(rank); + let who_lookup = T::Lookup::unlookup(who.clone()); + let origin = T::PromoteOrigin::successful_origin(); + let call = Call::::promote_member { who: who_lookup }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_eq!(Members::::get(&who).unwrap().rank, rank + 1); + assert_last_event::(Event::RankChanged { who, rank: rank + 1 }.into()); + } + + demote_member { + let r in 0 .. 10; + let rank = r as u16; + let first = make_member::(rank); + let who = make_member::(rank); + let who_lookup = T::Lookup::unlookup(who.clone()); + let last = make_member::(rank); + let last_index = IdToIndex::::get(rank, &last).unwrap(); + let origin = T::DemoteOrigin::successful_origin(); + let call = Call::::demote_member { who: who_lookup }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_eq!(Members::::get(&who).map(|x| x.rank), rank.checked_sub(1)); + assert_eq!(MemberCount::::get(rank), 2); + assert_ne!(last_index, IdToIndex::::get(rank, &last).unwrap()); + assert_last_event::(match rank { + 0 => Event::MemberRemoved { who, rank: 0 }, + r => Event::RankChanged { who, rank: r - 1 }, + }.into()); + } + + vote { + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + assert_ok!(Pallet::::add_member(T::PromoteOrigin::successful_origin(), caller_lookup.clone())); + // Create a poll + let class = T::Polls::classes().into_iter().next().unwrap(); + let rank = T::MinRankOfClass::convert(class.clone()); + for _ in 0..rank { + assert_ok!(Pallet::::promote_member( + T::PromoteOrigin::successful_origin(), + caller_lookup.clone() + )); + } + + let poll = T::Polls::create_ongoing(class).expect("Must always be able to create a poll for rank 0"); + + // Vote once. + assert_ok!(Pallet::::vote(SystemOrigin::Signed(caller.clone()).into(), poll, true)); + }: _(SystemOrigin::Signed(caller.clone()), poll, false) + verify { + let tally = Tally::from_parts(0, 0, 1); + let ev = Event::Voted { who: caller, poll, vote: VoteRecord::Nay(1), tally }; + assert_last_event::(ev.into()); + } + + cleanup_poll { + let n in 0 .. 100; + + // Create a poll + let class = T::Polls::classes().into_iter().next().unwrap(); + let rank = T::MinRankOfClass::convert(class.clone()); + let poll = T::Polls::create_ongoing(class).expect("Must always be able to create a poll"); + + // Vote in the poll by each of `n` members + for i in 0..n { + let who = make_member::(rank); + assert_ok!(Pallet::::vote(SystemOrigin::Signed(who).into(), poll, true)); + } + + // End the poll. + T::Polls::end_ongoing(poll, false).expect("Must always be able to end a poll"); + + assert_eq!(Voting::::iter_prefix(poll).count(), n as usize); + }: _(SystemOrigin::Signed(whitelisted_caller()), poll, n) + verify { + assert_eq!(Voting::::iter().count(), 0); + } + + impl_benchmark_test_suite!(RankedCollective, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs new file mode 100644 index 0000000000000..b057a57508023 --- /dev/null +++ b/frame/ranked-collective/src/lib.rs @@ -0,0 +1,726 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Ranked collective system. +//! +//! This is a membership pallet providing a `Tally` implementation ready for use with polling +//! systems such as the Referenda pallet. Members each have a rank, with zero being the lowest. +//! There is no complexity limitation on either the number of members at a rank or the number of +//! ranks in the system thus allowing potentially public membership. A member of at least a given +//! rank can be selected at random in O(1) time, allowing for various games to be constructed using +//! this as a primitive. Members may only be promoted and demoted by one rank at a time, however +//! all operations (save one) are O(1) in complexity. The only operation which is not O(1) is the +//! `remove_member` since they must be removed from all ranks from the present down to zero. +//! +//! Different ranks have different voting power, and are able to vote in different polls. In general +//! rank privileges are cumulative. Higher ranks are able to vote in any polls open to lower ranks. +//! Similarly, higher ranks always have at least as much voting power in any given poll as lower +//! ranks. +//! +//! Two `Config` trait items control these "rank privileges": `MinRankOfClass` and `VoteWeight`. +//! The first controls which ranks are allowed to vote on a particular class of poll. The second +//! controls the weight of a vote given the voter's rank compared to the minimum rank of the poll. +//! +//! An origin control, `EnsureRank`, ensures that the origin is a member of the collective of at +//! least a particular rank. + +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "128"] + +use scale_info::TypeInfo; +use sp_arithmetic::traits::Saturating; +use sp_runtime::{ + traits::{Convert, StaticLookup}, + ArithmeticError::Overflow, + Perbill, RuntimeDebug, +}; +use sp_std::{marker::PhantomData, prelude::*}; + +use frame_support::{ + codec::{Decode, Encode, MaxEncodedLen}, + dispatch::{DispatchError, DispatchResultWithPostInfo, PostDispatchInfo}, + ensure, + traits::{EnsureOrigin, PollStatus, Polling, VoteTally}, + CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// A number of members. +pub type MemberIndex = u32; + +/// Member rank. +pub type Rank = u16; + +/// Votes. +pub type Votes = u32; + +/// Aggregated votes for an ongoing poll by members of the ranked collective. +#[derive( + CloneNoBound, + PartialEqNoBound, + EqNoBound, + RuntimeDebugNoBound, + TypeInfo, + Encode, + Decode, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(T, I, M))] +#[codec(mel_bound())] +pub struct Tally { + bare_ayes: MemberIndex, + ayes: Votes, + nays: Votes, + dummy: PhantomData<(T, I, M)>, +} + +impl, I: 'static, M: GetMaxVoters> Tally { + pub fn from_parts(bare_ayes: MemberIndex, ayes: Votes, nays: Votes) -> Self { + Tally { bare_ayes, ayes, nays, dummy: PhantomData } + } +} + +// Use (non-rank-weighted) ayes for calculating support. +// Allow only promotion/demotion by one rank only. +// Allow removal of member with rank zero only. +// This keeps everything O(1) while still allowing arbitrary number of ranks. + +// All functions of VoteTally now include the class as a param. + +pub type TallyOf = Tally>; +pub type PollIndexOf = <>::Polls as Polling>>::Index; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +impl, I: 'static, M: GetMaxVoters> VoteTally for Tally { + fn new(_: Rank) -> Self { + Self { bare_ayes: 0, ayes: 0, nays: 0, dummy: PhantomData } + } + fn ayes(&self, _: Rank) -> Votes { + self.bare_ayes + } + fn support(&self, class: Rank) -> Perbill { + Perbill::from_rational(self.bare_ayes, M::get_max_voters(class)) + } + fn approval(&self, _: Rank) -> Perbill { + Perbill::from_rational(self.ayes, 1.max(self.ayes + self.nays)) + } + #[cfg(feature = "runtime-benchmarks")] + fn unanimity(class: Rank) -> Self { + Self { + bare_ayes: M::get_max_voters(class), + ayes: M::get_max_voters(class), + nays: 0, + dummy: PhantomData, + } + } + #[cfg(feature = "runtime-benchmarks")] + fn rejection(class: Rank) -> Self { + Self { bare_ayes: 0, ayes: 0, nays: M::get_max_voters(class), dummy: PhantomData } + } + #[cfg(feature = "runtime-benchmarks")] + fn from_requirements(support: Perbill, approval: Perbill, class: Rank) -> Self { + let c = M::get_max_voters(class); + let ayes = support * c; + let nays = ((ayes as u64) * 1_000_000_000u64 / approval.deconstruct() as u64) as u32 - ayes; + Self { bare_ayes: ayes, ayes, nays, dummy: PhantomData } + } + + #[cfg(feature = "runtime-benchmarks")] + fn setup(class: Rank, granularity: Perbill) { + if M::get_max_voters(class) == 0 { + let max_voters = granularity.saturating_reciprocal_mul(1u32); + for i in 0..max_voters { + let who: T::AccountId = + frame_benchmarking::account("ranked_collective_benchmarking", i, 0); + crate::Pallet::::do_add_member_to_rank(who, class) + .expect("could not add members for benchmarks"); + } + assert_eq!(M::get_max_voters(class), max_voters); + } + } +} + +/// Record needed for every member. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct MemberRecord { + /// The rank of the member. + rank: Rank, +} + +/// Record needed for every vote. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum VoteRecord { + /// Vote was an aye with given vote weight. + Aye(Votes), + /// Vote was a nay with given vote weight. + Nay(Votes), +} + +impl From<(bool, Votes)> for VoteRecord { + fn from((aye, votes): (bool, Votes)) -> Self { + match aye { + true => VoteRecord::Aye(votes), + false => VoteRecord::Nay(votes), + } + } +} + +/// Vote-weight scheme where all voters get one vote regardless of rank. +pub struct Unit; +impl Convert for Unit { + fn convert(_: Rank) -> Votes { + 1 + } +} + +/// Vote-weight scheme where all voters get one vote plus an additional vote for every excess rank +/// they have. I.e.: +/// +/// - Each member with an excess rank of 0 gets 1 vote; +/// - ...with an excess rank of 1 gets 2 votes; +/// - ...with an excess rank of 2 gets 3 votes; +/// - ...with an excess rank of 3 gets 4 votes; +/// - ...with an excess rank of 4 gets 5 votes. +pub struct Linear; +impl Convert for Linear { + fn convert(r: Rank) -> Votes { + (r + 1) as Votes + } +} + +/// Vote-weight scheme where all voters get one vote plus additional votes for every excess rank +/// they have incrementing by one vote for each excess rank. I.e.: +/// +/// - Each member with an excess rank of 0 gets 1 vote; +/// - ...with an excess rank of 1 gets 3 votes; +/// - ...with an excess rank of 2 gets 6 votes; +/// - ...with an excess rank of 3 gets 10 votes; +/// - ...with an excess rank of 4 gets 15 votes. +pub struct Geometric; +impl Convert for Geometric { + fn convert(r: Rank) -> Votes { + let v = (r + 1) as Votes; + v * (v + 1) / 2 + } +} + +/// Trait for getting the maximum number of voters for a given rank. +pub trait GetMaxVoters { + /// Return the maximum number of voters for the rank `r`. + fn get_max_voters(r: Rank) -> MemberIndex; +} +impl, I: 'static> GetMaxVoters for Pallet { + fn get_max_voters(r: Rank) -> MemberIndex { + MemberCount::::get(r) + } +} + +/// Guard to ensure that the given origin is a member of the collective. The rank of the member is +/// the `Success` value. +pub struct EnsureRanked(PhantomData<(T, I)>); +impl, I: 'static, const MIN_RANK: u16> EnsureOrigin + for EnsureRanked +{ + type Success = Rank; + + fn try_origin(o: T::RuntimeOrigin) -> Result { + let who = frame_system::EnsureSigned::try_origin(o)?; + match Members::::get(&who) { + Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok(rank), + _ => Err(frame_system::RawOrigin::Signed(who).into()), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + let who = IndexToId::::get(MIN_RANK, 0).ok_or(())?; + Ok(frame_system::RawOrigin::Signed(who).into()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> T::RuntimeOrigin { + match Self::try_successful_origin() { + Ok(o) => o, + Err(()) => { + let who: T::AccountId = frame_benchmarking::whitelisted_caller(); + crate::Pallet::::do_add_member_to_rank(who.clone(), MIN_RANK) + .expect("failed to add ranked member"); + frame_system::RawOrigin::Signed(who).into() + }, + } + } +} + +/// Guard to ensure that the given origin is a member of the collective. The account ID of the +/// member is the `Success` value. +pub struct EnsureMember(PhantomData<(T, I)>); +impl, I: 'static, const MIN_RANK: u16> EnsureOrigin + for EnsureMember +{ + type Success = T::AccountId; + + fn try_origin(o: T::RuntimeOrigin) -> Result { + let who = frame_system::EnsureSigned::try_origin(o)?; + match Members::::get(&who) { + Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok(who), + _ => Err(frame_system::RawOrigin::Signed(who).into()), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + let who = IndexToId::::get(MIN_RANK, 0).ok_or(())?; + Ok(frame_system::RawOrigin::Signed(who).into()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> T::RuntimeOrigin { + match Self::try_successful_origin() { + Ok(o) => o, + Err(()) => { + let who: T::AccountId = frame_benchmarking::whitelisted_caller(); + crate::Pallet::::do_add_member_to_rank(who.clone(), MIN_RANK) + .expect("failed to add ranked member"); + frame_system::RawOrigin::Signed(who).into() + }, + } + } +} + +/// Guard to ensure that the given origin is a member of the collective. The pair of both the +/// account ID and the rank of the member is the `Success` value. +pub struct EnsureRankedMember(PhantomData<(T, I)>); +impl, I: 'static, const MIN_RANK: u16> EnsureOrigin + for EnsureRankedMember +{ + type Success = (T::AccountId, Rank); + + fn try_origin(o: T::RuntimeOrigin) -> Result { + let who = frame_system::EnsureSigned::try_origin(o)?; + match Members::::get(&who) { + Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok((who, rank)), + _ => Err(frame_system::RawOrigin::Signed(who).into()), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + let who = IndexToId::::get(MIN_RANK, 0).ok_or(())?; + Ok(frame_system::RawOrigin::Signed(who).into()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> T::RuntimeOrigin { + match Self::try_successful_origin() { + Ok(o) => o, + Err(()) => { + let who: T::AccountId = frame_benchmarking::whitelisted_caller(); + crate::Pallet::::do_add_member_to_rank(who.clone(), MIN_RANK) + .expect("failed to add ranked member"); + frame_system::RawOrigin::Signed(who).into() + }, + } + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{pallet_prelude::*, storage::KeyLenOf}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The runtime event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// The origin required to add or promote a mmember. The success value indicates the + /// maximum rank *to which* the promotion may be. + type PromoteOrigin: EnsureOrigin; + + /// The origin required to demote or remove a member. The success value indicates the + /// maximum rank *from which* the demotion/removal may be. + type DemoteOrigin: EnsureOrigin; + + /// The polling system used for our voting. + type Polls: Polling, Votes = Votes, Moment = Self::BlockNumber>; + + /// Convert the tally class into the minimum rank required to vote on the poll. If + /// `Polls::Class` is the same type as `Rank`, then `Identity` can be used here to mean + /// "a rank of at least the poll class". + type MinRankOfClass: Convert<>>::Class, Rank>; + + /// Convert a rank_delta into a number of votes the rank gets. + /// + /// Rank_delta is defined as the number of ranks above the minimum required to take part + /// in the poll. + type VoteWeight: Convert; + } + + /// The number of members in the collective who have at least the rank according to the index + /// of the vec. + #[pallet::storage] + pub type MemberCount, I: 'static = ()> = + StorageMap<_, Twox64Concat, Rank, MemberIndex, ValueQuery>; + + /// The current members of the collective. + #[pallet::storage] + pub type Members, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, MemberRecord>; + + /// The index of each ranks's member into the group of members who have at least that rank. + #[pallet::storage] + pub type IdToIndex, I: 'static = ()> = + StorageDoubleMap<_, Twox64Concat, Rank, Twox64Concat, T::AccountId, MemberIndex>; + + /// The members in the collective by index. All indices in the range `0..MemberCount` will + /// return `Some`, however a member's index is not guaranteed to remain unchanged over time. + #[pallet::storage] + pub type IndexToId, I: 'static = ()> = + StorageDoubleMap<_, Twox64Concat, Rank, Twox64Concat, MemberIndex, T::AccountId>; + + /// Votes on a given proposal, if it is ongoing. + #[pallet::storage] + pub type Voting, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + PollIndexOf, + Twox64Concat, + T::AccountId, + VoteRecord, + >; + + #[pallet::storage] + pub type VotingCleanup, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, PollIndexOf, BoundedVec>>>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// A member `who` has been added. + MemberAdded { who: T::AccountId }, + /// The member `who`se rank has been changed to the given `rank`. + RankChanged { who: T::AccountId, rank: Rank }, + /// The member `who` of given `rank` has been removed from the collective. + MemberRemoved { who: T::AccountId, rank: Rank }, + /// The member `who` has voted for the `poll` with the given `vote` leading to an updated + /// `tally`. + Voted { who: T::AccountId, poll: PollIndexOf, vote: VoteRecord, tally: TallyOf }, + } + + #[pallet::error] + pub enum Error { + /// Account is already a member. + AlreadyMember, + /// Account is not a member. + NotMember, + /// The given poll index is unknown or has closed. + NotPolling, + /// The given poll is still ongoing. + Ongoing, + /// There are no further records to be removed. + NoneRemaining, + /// Unexpected error in state. + Corruption, + /// The member's rank is too low to vote. + RankTooLow, + /// The information provided is incorrect. + InvalidWitness, + /// The origin is not sufficiently privileged to do the operation. + NoPermission, + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Introduce a new member. + /// + /// - `origin`: Must be the `AdminOrigin`. + /// - `who`: Account of non-member which will become a member. + /// - `rank`: The rank to give the new member. + /// + /// Weight: `O(1)` + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::add_member())] + pub fn add_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { + let _ = T::PromoteOrigin::ensure_origin(origin)?; + let who = T::Lookup::lookup(who)?; + Self::do_add_member(who) + } + + /// Increment the rank of an existing member by one. + /// + /// - `origin`: Must be the `AdminOrigin`. + /// - `who`: Account of existing member. + /// + /// Weight: `O(1)` + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::promote_member(0))] + pub fn promote_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { + let max_rank = T::PromoteOrigin::ensure_origin(origin)?; + let who = T::Lookup::lookup(who)?; + Self::do_promote_member(who, Some(max_rank)) + } + + /// Decrement the rank of an existing member by one. If the member is already at rank zero, + /// then they are removed entirely. + /// + /// - `origin`: Must be the `AdminOrigin`. + /// - `who`: Account of existing member of rank greater than zero. + /// + /// Weight: `O(1)`, less if the member's index is highest in its rank. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::demote_member(0))] + pub fn demote_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { + let max_rank = T::DemoteOrigin::ensure_origin(origin)?; + let who = T::Lookup::lookup(who)?; + let mut record = Self::ensure_member(&who)?; + let rank = record.rank; + ensure!(max_rank >= rank, Error::::NoPermission); + + Self::remove_from_rank(&who, rank)?; + let maybe_rank = rank.checked_sub(1); + match maybe_rank { + None => { + Members::::remove(&who); + Self::deposit_event(Event::MemberRemoved { who, rank: 0 }); + }, + Some(rank) => { + record.rank = rank; + Members::::insert(&who, &record); + Self::deposit_event(Event::RankChanged { who, rank }); + }, + } + Ok(()) + } + + /// Remove the member entirely. + /// + /// - `origin`: Must be the `AdminOrigin`. + /// - `who`: Account of existing member of rank greater than zero. + /// - `min_rank`: The rank of the member or greater. + /// + /// Weight: `O(min_rank)`. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::remove_member(*min_rank as u32))] + pub fn remove_member( + origin: OriginFor, + who: AccountIdLookupOf, + min_rank: Rank, + ) -> DispatchResultWithPostInfo { + let max_rank = T::DemoteOrigin::ensure_origin(origin)?; + let who = T::Lookup::lookup(who)?; + let MemberRecord { rank, .. } = Self::ensure_member(&who)?; + ensure!(min_rank >= rank, Error::::InvalidWitness); + ensure!(max_rank >= rank, Error::::NoPermission); + + for r in 0..=rank { + Self::remove_from_rank(&who, r)?; + } + Members::::remove(&who); + Self::deposit_event(Event::MemberRemoved { who, rank }); + Ok(PostDispatchInfo { + actual_weight: Some(T::WeightInfo::remove_member(rank as u32)), + pays_fee: Pays::Yes, + }) + } + + /// Add an aye or nay vote for the sender to the given proposal. + /// + /// - `origin`: Must be `Signed` by a member account. + /// - `poll`: Index of a poll which is ongoing. + /// - `aye`: `true` if the vote is to approve the proposal, `false` otherwise. + /// + /// Transaction fees are be waived if the member is voting on any particular proposal + /// for the first time and the call is successful. Subsequent vote changes will charge a + /// fee. + /// + /// Weight: `O(1)`, less if there was no previous vote on the poll by the member. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::vote())] + pub fn vote( + origin: OriginFor, + poll: PollIndexOf, + aye: bool, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let record = Self::ensure_member(&who)?; + use VoteRecord::*; + let mut pays = Pays::Yes; + + let (tally, vote) = T::Polls::try_access_poll( + poll, + |mut status| -> Result<(TallyOf, VoteRecord), DispatchError> { + match status { + PollStatus::None | PollStatus::Completed(..) => + Err(Error::::NotPolling)?, + PollStatus::Ongoing(ref mut tally, class) => { + match Voting::::get(&poll, &who) { + Some(Aye(votes)) => { + tally.bare_ayes.saturating_dec(); + tally.ayes.saturating_reduce(votes); + }, + Some(Nay(votes)) => tally.nays.saturating_reduce(votes), + None => pays = Pays::No, + } + let min_rank = T::MinRankOfClass::convert(class); + let votes = Self::rank_to_votes(record.rank, min_rank)?; + let vote = VoteRecord::from((aye, votes)); + match aye { + true => { + tally.bare_ayes.saturating_inc(); + tally.ayes.saturating_accrue(votes); + }, + false => tally.nays.saturating_accrue(votes), + } + Voting::::insert(&poll, &who, &vote); + Ok((tally.clone(), vote)) + }, + } + }, + )?; + Self::deposit_event(Event::Voted { who, poll, vote, tally }); + Ok(pays.into()) + } + + /// Remove votes from the given poll. It must have ended. + /// + /// - `origin`: Must be `Signed` by any account. + /// - `poll_index`: Index of a poll which is completed and for which votes continue to + /// exist. + /// - `max`: Maximum number of vote items from remove in this call. + /// + /// Transaction fees are waived if the operation is successful. + /// + /// Weight `O(max)` (less if there are fewer items to remove than `max`). + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::cleanup_poll(*max))] + pub fn cleanup_poll( + origin: OriginFor, + poll_index: PollIndexOf, + max: u32, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + ensure!(T::Polls::as_ongoing(poll_index).is_none(), Error::::Ongoing); + + let r = Voting::::clear_prefix( + poll_index, + max, + VotingCleanup::::take(poll_index).as_ref().map(|c| &c[..]), + ); + if r.unique == 0 { + // return Err(Error::::NoneRemaining) + return Ok(Pays::Yes.into()) + } + if let Some(cursor) = r.maybe_cursor { + VotingCleanup::::insert(poll_index, BoundedVec::truncate_from(cursor)); + } + Ok(PostDispatchInfo { + actual_weight: Some(T::WeightInfo::cleanup_poll(r.unique)), + pays_fee: Pays::No, + }) + } + } + + impl, I: 'static> Pallet { + fn ensure_member(who: &T::AccountId) -> Result { + Members::::get(who).ok_or(Error::::NotMember.into()) + } + + fn rank_to_votes(rank: Rank, min: Rank) -> Result { + let excess = rank.checked_sub(min).ok_or(Error::::RankTooLow)?; + Ok(T::VoteWeight::convert(excess)) + } + + fn remove_from_rank(who: &T::AccountId, rank: Rank) -> DispatchResult { + let last_index = MemberCount::::get(rank).saturating_sub(1); + let index = IdToIndex::::get(rank, &who).ok_or(Error::::Corruption)?; + if index != last_index { + let last = + IndexToId::::get(rank, last_index).ok_or(Error::::Corruption)?; + IdToIndex::::insert(rank, &last, index); + IndexToId::::insert(rank, index, &last); + } + MemberCount::::mutate(rank, |r| r.saturating_dec()); + Ok(()) + } + + /// Adds a member into the ranked collective at level 0. + /// + /// No origin checks are executed. + pub fn do_add_member(who: T::AccountId) -> DispatchResult { + ensure!(!Members::::contains_key(&who), Error::::AlreadyMember); + let index = MemberCount::::get(0); + let count = index.checked_add(1).ok_or(Overflow)?; + + Members::::insert(&who, MemberRecord { rank: 0 }); + IdToIndex::::insert(0, &who, index); + IndexToId::::insert(0, index, &who); + MemberCount::::insert(0, count); + Self::deposit_event(Event::MemberAdded { who }); + Ok(()) + } + + /// Promotes a member in the ranked collective into the next role. + /// + /// A `maybe_max_rank` may be provided to check that the member does not get promoted beyond + /// a certain rank. Is `None` is provided, then the rank will be incremented without checks. + pub fn do_promote_member( + who: T::AccountId, + maybe_max_rank: Option, + ) -> DispatchResult { + let record = Self::ensure_member(&who)?; + let rank = record.rank.checked_add(1).ok_or(Overflow)?; + if let Some(max_rank) = maybe_max_rank { + ensure!(max_rank >= rank, Error::::NoPermission); + } + let index = MemberCount::::get(rank); + MemberCount::::insert(rank, index.checked_add(1).ok_or(Overflow)?); + IdToIndex::::insert(rank, &who, index); + IndexToId::::insert(rank, index, &who); + Members::::insert(&who, MemberRecord { rank }); + Self::deposit_event(Event::RankChanged { who, rank }); + Ok(()) + } + + /// Add a member to the rank collective, and continue to promote them until a certain rank + /// is reached. + pub fn do_add_member_to_rank(who: T::AccountId, rank: Rank) -> DispatchResult { + Self::do_add_member(who.clone())?; + for _ in 0..rank { + Self::do_promote_member(who.clone(), None)?; + } + Ok(()) + } + } +} diff --git a/frame/ranked-collective/src/tests.rs b/frame/ranked-collective/src/tests.rs new file mode 100644 index 0000000000000..68bb79f3d07f7 --- /dev/null +++ b/frame/ranked-collective/src/tests.rs @@ -0,0 +1,506 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! The crate's tests. + +use std::collections::BTreeMap; + +use frame_support::{ + assert_noop, assert_ok, + error::BadOrigin, + pallet_prelude::Weight, + parameter_types, + traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, Identity, IdentityLookup, ReduceBy}, +}; + +use super::*; +use crate as pallet_ranked_collective; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Club: pallet_ranked_collective::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(1_000_000)); +} +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum TestPollState { + Ongoing(TallyOf, Rank), + Completed(u64, bool), +} +use TestPollState::*; + +parameter_types! { + pub static Polls: BTreeMap = vec![ + (1, Completed(1, true)), + (2, Completed(2, false)), + (3, Ongoing(Tally::from_parts(0, 0, 0), 1)), + ].into_iter().collect(); +} + +pub struct TestPolls; +impl Polling> for TestPolls { + type Index = u8; + type Votes = Votes; + type Moment = u64; + type Class = Rank; + fn classes() -> Vec { + vec![0, 1, 2] + } + fn as_ongoing(index: u8) -> Option<(TallyOf, Self::Class)> { + Polls::get().remove(&index).and_then(|x| { + if let TestPollState::Ongoing(t, c) = x { + Some((t, c)) + } else { + None + } + }) + } + fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut TallyOf, Self::Moment, Self::Class>) -> R, + ) -> R { + let mut polls = Polls::get(); + let entry = polls.get_mut(&index); + let r = match entry { + Some(Ongoing(ref mut tally_mut_ref, class)) => + f(PollStatus::Ongoing(tally_mut_ref, *class)), + Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), + None => f(PollStatus::None), + }; + Polls::set(polls); + r + } + fn try_access_poll( + index: Self::Index, + f: impl FnOnce( + PollStatus<&mut TallyOf, Self::Moment, Self::Class>, + ) -> Result, + ) -> Result { + let mut polls = Polls::get(); + let entry = polls.get_mut(&index); + let r = match entry { + Some(Ongoing(ref mut tally_mut_ref, class)) => + f(PollStatus::Ongoing(tally_mut_ref, *class)), + Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), + None => f(PollStatus::None), + }?; + Polls::set(polls); + Ok(r) + } + + #[cfg(feature = "runtime-benchmarks")] + fn create_ongoing(class: Self::Class) -> Result { + let mut polls = Polls::get(); + let i = polls.keys().rev().next().map_or(0, |x| x + 1); + polls.insert(i, Ongoing(Tally::new(class), class)); + Polls::set(polls); + Ok(i) + } + + #[cfg(feature = "runtime-benchmarks")] + fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> { + let mut polls = Polls::get(); + match polls.get(&index) { + Some(Ongoing(..)) => {}, + _ => return Err(()), + } + let now = frame_system::Pallet::::block_number(); + polls.insert(index, Completed(now, approved)); + Polls::set(polls); + Ok(()) + } +} + +impl Config for Test { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type PromoteOrigin = EitherOf< + // Root can promote arbitrarily. + frame_system::EnsureRootWithSuccess>, + // Members can promote up to the rank of 2 below them. + MapSuccess, ReduceBy>>, + >; + type DemoteOrigin = EitherOf< + // Root can demote arbitrarily. + frame_system::EnsureRootWithSuccess>, + // Members can demote up to the rank of 3 below them. + MapSuccess, ReduceBy>>, + >; + type Polls = TestPolls; + type MinRankOfClass = Identity; + type VoteWeight = Geometric; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); +} + +fn member_count(r: Rank) -> MemberIndex { + MemberCount::::get(r) +} + +#[allow(dead_code)] +fn run_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +fn tally(index: u8) -> TallyOf { + >>::as_ongoing(index).expect("No poll").0 +} + +#[test] +#[ignore] +#[should_panic(expected = "No poll")] +fn unknown_poll_should_panic() { + let _ = tally(0); +} + +#[test] +#[ignore] +#[should_panic(expected = "No poll")] +fn completed_poll_should_panic() { + let _ = tally(1); +} + +#[test] +fn basic_stuff() { + new_test_ext().execute_with(|| { + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + }); +} + +#[test] +fn member_lifecycle_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 0); + assert_eq!(member_count(1), 0); + }); +} + +#[test] +fn add_remove_works() { + new_test_ext().execute_with(|| { + assert_noop!(Club::add_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 1); + + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 0); + + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 1); + + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_eq!(member_count(0), 2); + + assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); + assert_eq!(member_count(0), 3); + + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 3)); + assert_eq!(member_count(0), 2); + + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 1); + + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 2)); + assert_eq!(member_count(0), 0); + }); +} + +#[test] +fn promote_demote_works() { + new_test_ext().execute_with(|| { + assert_noop!(Club::add_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 1); + assert_eq!(member_count(1), 0); + + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 0); + + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 1); + + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 2); + + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 1); + + assert_noop!(Club::demote_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 1); + assert_eq!(member_count(1), 1); + }); +} + +#[test] +fn promote_demote_by_rank_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + for _ in 0..7 { + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + } + + // #1 can add #2 and promote to rank 1 + assert_ok!(Club::add_member(RuntimeOrigin::signed(1), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); + // #2 as rank 1 cannot do anything privileged + assert_noop!(Club::add_member(RuntimeOrigin::signed(2), 3), BadOrigin); + + assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); + // #2 as rank 2 can add #3. + assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 3)); + + // #2 as rank 2 cannot promote #3 to rank 1 + assert_noop!( + Club::promote_member(RuntimeOrigin::signed(2), 3), + Error::::NoPermission + ); + + // #1 as rank 7 can promote #2 only up to rank 5 and once there cannot demote them. + assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); + assert_noop!( + Club::promote_member(RuntimeOrigin::signed(1), 2), + Error::::NoPermission + ); + assert_noop!(Club::demote_member(RuntimeOrigin::signed(1), 2), Error::::NoPermission); + + // #2 as rank 5 can promote #3 only up to rank 3 and once there cannot demote them. + assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 3)); + assert_noop!( + Club::promote_member(RuntimeOrigin::signed(2), 3), + Error::::NoPermission + ); + assert_noop!(Club::demote_member(RuntimeOrigin::signed(2), 3), Error::::NoPermission); + + // #2 can add #4 & #5 as rank 0 and #6 & #7 as rank 1. + assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 4)); + assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 5)); + assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 6)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 6)); + assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 7)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 7)); + + // #3 as rank 3 can demote/remove #4 & #5 but not #6 & #7 + assert_ok!(Club::demote_member(RuntimeOrigin::signed(3), 4)); + assert_ok!(Club::remove_member(RuntimeOrigin::signed(3), 5, 0)); + assert_noop!(Club::demote_member(RuntimeOrigin::signed(3), 6), Error::::NoPermission); + assert_noop!( + Club::remove_member(RuntimeOrigin::signed(3), 7, 1), + Error::::NoPermission + ); + + // #2 as rank 5 can demote/remove #6 & #7 + assert_ok!(Club::demote_member(RuntimeOrigin::signed(2), 6)); + assert_ok!(Club::remove_member(RuntimeOrigin::signed(2), 7, 1)); + }); +} + +#[test] +fn voting_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(RuntimeOrigin::root(), 0)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + + assert_noop!(Club::vote(RuntimeOrigin::signed(0), 3, true), Error::::RankTooLow); + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + + assert_ok!(Club::vote(RuntimeOrigin::signed(1), 3, true)); + assert_eq!(tally(3), Tally::from_parts(1, 1, 0)); + assert_ok!(Club::vote(RuntimeOrigin::signed(1), 3, false)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 1)); + + assert_ok!(Club::vote(RuntimeOrigin::signed(2), 3, true)); + assert_eq!(tally(3), Tally::from_parts(1, 3, 1)); + assert_ok!(Club::vote(RuntimeOrigin::signed(2), 3, false)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 4)); + + assert_ok!(Club::vote(RuntimeOrigin::signed(3), 3, true)); + assert_eq!(tally(3), Tally::from_parts(1, 6, 4)); + assert_ok!(Club::vote(RuntimeOrigin::signed(3), 3, false)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 10)); + }); +} + +#[test] +fn cleanup_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + + assert_ok!(Club::vote(RuntimeOrigin::signed(1), 3, true)); + assert_ok!(Club::vote(RuntimeOrigin::signed(2), 3, false)); + assert_ok!(Club::vote(RuntimeOrigin::signed(3), 3, true)); + + assert_noop!(Club::cleanup_poll(RuntimeOrigin::signed(4), 3, 10), Error::::Ongoing); + Polls::set( + vec![(1, Completed(1, true)), (2, Completed(2, false)), (3, Completed(3, true))] + .into_iter() + .collect(), + ); + assert_ok!(Club::cleanup_poll(RuntimeOrigin::signed(4), 3, 10)); + // NOTE: This will fail until #10016 is merged. + // assert_noop!(Club::cleanup_poll(RuntimeOrigin::signed(4), 3, 10), + // Error::::NoneRemaining); + }); +} + +#[test] +fn ensure_ranked_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + + use frame_support::traits::OriginTrait; + type Rank1 = EnsureRanked; + type Rank2 = EnsureRanked; + type Rank3 = EnsureRanked; + type Rank4 = EnsureRanked; + assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(1)).unwrap(), 1); + assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(2)).unwrap(), 2); + assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); + assert_eq!( + Rank2::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), + 1 + ); + assert_eq!(Rank2::try_origin(RuntimeOrigin::signed(2)).unwrap(), 2); + assert_eq!(Rank2::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); + assert_eq!( + Rank3::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), + 1 + ); + assert_eq!( + Rank3::try_origin(RuntimeOrigin::signed(2)).unwrap_err().as_signed().unwrap(), + 2 + ); + assert_eq!(Rank3::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); + assert_eq!( + Rank4::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), + 1 + ); + assert_eq!( + Rank4::try_origin(RuntimeOrigin::signed(2)).unwrap_err().as_signed().unwrap(), + 2 + ); + assert_eq!( + Rank4::try_origin(RuntimeOrigin::signed(3)).unwrap_err().as_signed().unwrap(), + 3 + ); + }); +} + +#[test] +fn do_add_member_to_rank_works() { + new_test_ext().execute_with(|| { + let max_rank = 9u16; + assert_ok!(Club::do_add_member_to_rank(69, max_rank / 2)); + assert_ok!(Club::do_add_member_to_rank(1337, max_rank)); + for i in 0..=max_rank { + if i <= max_rank / 2 { + assert_eq!(member_count(i), 2); + } else { + assert_eq!(member_count(i), 1); + } + } + assert_eq!(member_count(max_rank + 1), 0); + }) +} diff --git a/frame/ranked-collective/src/weights.rs b/frame/ranked-collective/src/weights.rs new file mode 100644 index 0000000000000..c054d200452e8 --- /dev/null +++ b/frame/ranked-collective/src/weights.rs @@ -0,0 +1,211 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_ranked_collective +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_ranked_collective +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/ranked-collective/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_ranked_collective. +pub trait WeightInfo { + fn add_member() -> Weight; + fn remove_member(r: u32, ) -> Weight; + fn promote_member(r: u32, ) -> Weight; + fn demote_member(r: u32, ) -> Weight; + fn vote() -> Weight; + fn cleanup_poll(n: u32, ) -> Weight; +} + +/// Weights for pallet_ranked_collective using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IndexToId (r:0 w:1) + // Storage: RankedCollective IdToIndex (r:0 w:1) + fn add_member() -> Weight { + // Minimum execution time: 24_344 nanoseconds. + Weight::from_ref_time(24_856_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IdToIndex (r:1 w:1) + // Storage: RankedCollective IndexToId (r:1 w:1) + /// The range of component `r` is `[0, 10]`. + fn remove_member(r: u32, ) -> Weight { + // Minimum execution time: 36_881 nanoseconds. + Weight::from_ref_time(39_284_238 as u64) + // Standard Error: 16_355 + .saturating_add(Weight::from_ref_time(11_385_424 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(r as u64))) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IndexToId (r:0 w:1) + // Storage: RankedCollective IdToIndex (r:0 w:1) + /// The range of component `r` is `[0, 10]`. + fn promote_member(r: u32, ) -> Weight { + // Minimum execution time: 27_444 nanoseconds. + Weight::from_ref_time(28_576_394 as u64) + // Standard Error: 4_818 + .saturating_add(Weight::from_ref_time(519_056 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IdToIndex (r:1 w:1) + // Storage: RankedCollective IndexToId (r:1 w:1) + /// The range of component `r` is `[0, 10]`. + fn demote_member(r: u32, ) -> Weight { + // Minimum execution time: 36_539 nanoseconds. + Weight::from_ref_time(39_339_893 as u64) + // Standard Error: 16_526 + .saturating_add(Weight::from_ref_time(807_457 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: RankedCollective Members (r:1 w:0) + // Storage: RankedPolls ReferendumInfoFor (r:1 w:1) + // Storage: RankedCollective Voting (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn vote() -> Weight { + // Minimum execution time: 50_548 nanoseconds. + Weight::from_ref_time(51_276_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: RankedPolls ReferendumInfoFor (r:1 w:0) + // Storage: RankedCollective VotingCleanup (r:1 w:0) + // Storage: RankedCollective Voting (r:0 w:2) + /// The range of component `n` is `[0, 100]`. + fn cleanup_poll(n: u32, ) -> Weight { + // Minimum execution time: 16_222 nanoseconds. + Weight::from_ref_time(22_982_955 as u64) + // Standard Error: 3_863 + .saturating_add(Weight::from_ref_time(1_074_054 as u64).saturating_mul(n as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IndexToId (r:0 w:1) + // Storage: RankedCollective IdToIndex (r:0 w:1) + fn add_member() -> Weight { + // Minimum execution time: 24_344 nanoseconds. + Weight::from_ref_time(24_856_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IdToIndex (r:1 w:1) + // Storage: RankedCollective IndexToId (r:1 w:1) + /// The range of component `r` is `[0, 10]`. + fn remove_member(r: u32, ) -> Weight { + // Minimum execution time: 36_881 nanoseconds. + Weight::from_ref_time(39_284_238 as u64) + // Standard Error: 16_355 + .saturating_add(Weight::from_ref_time(11_385_424 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(r as u64))) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IndexToId (r:0 w:1) + // Storage: RankedCollective IdToIndex (r:0 w:1) + /// The range of component `r` is `[0, 10]`. + fn promote_member(r: u32, ) -> Weight { + // Minimum execution time: 27_444 nanoseconds. + Weight::from_ref_time(28_576_394 as u64) + // Standard Error: 4_818 + .saturating_add(Weight::from_ref_time(519_056 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IdToIndex (r:1 w:1) + // Storage: RankedCollective IndexToId (r:1 w:1) + /// The range of component `r` is `[0, 10]`. + fn demote_member(r: u32, ) -> Weight { + // Minimum execution time: 36_539 nanoseconds. + Weight::from_ref_time(39_339_893 as u64) + // Standard Error: 16_526 + .saturating_add(Weight::from_ref_time(807_457 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: RankedCollective Members (r:1 w:0) + // Storage: RankedPolls ReferendumInfoFor (r:1 w:1) + // Storage: RankedCollective Voting (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn vote() -> Weight { + // Minimum execution time: 50_548 nanoseconds. + Weight::from_ref_time(51_276_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: RankedPolls ReferendumInfoFor (r:1 w:0) + // Storage: RankedCollective VotingCleanup (r:1 w:0) + // Storage: RankedCollective Voting (r:0 w:2) + /// The range of component `n` is `[0, 100]`. + fn cleanup_poll(n: u32, ) -> Weight { + // Minimum execution time: 16_222 nanoseconds. + Weight::from_ref_time(22_982_955 as u64) + // Standard Error: 3_863 + .saturating_add(Weight::from_ref_time(1_074_054 as u64).saturating_mul(n as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + } +} diff --git a/frame/recovery/Cargo.toml b/frame/recovery/Cargo.toml index 40a89e9b59f89..cdcebbec161bc 100644 --- a/frame/recovery/Cargo.toml +++ b/frame/recovery/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-recovery" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME account recovery pallet" readme = "README.md" @@ -13,27 +13,35 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] +runtime-benchmarks = [ + 'frame-benchmarking', + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] std = [ "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", "scale-info/std", - "sp-std/std", "sp-io/std", "sp-runtime/std", - "frame-support/std", - "frame-system/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/recovery/src/benchmarking.rs b/frame/recovery/src/benchmarking.rs new file mode 100644 index 0000000000000..870543d9bd290 --- /dev/null +++ b/frame/recovery/src/benchmarking.rs @@ -0,0 +1,381 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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(feature = "runtime-benchmarks")] + +use super::*; + +use crate::Pallet; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_support::traits::{Currency, Get}; +use frame_system::RawOrigin; +use sp_runtime::traits::Bounded; + +const SEED: u32 = 0; +const DEFAULT_DELAY: u32 = 0; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn get_total_deposit( + bounded_friends: &FriendsOf, +) -> Option<<::Currency as Currency<::AccountId>>::Balance> +{ + let friend_deposit = T::FriendDepositFactor::get() + .checked_mul(&bounded_friends.len().saturated_into()) + .unwrap(); + + T::ConfigDepositBase::get().checked_add(&friend_deposit) +} + +fn generate_friends(num: u32) -> Vec<::AccountId> { + // Create friends + let mut friends = (0..num).map(|x| account("friend", x, SEED)).collect::>(); + // Sort + friends.sort(); + + for friend in 0..friends.len() { + // Top up accounts of friends + T::Currency::make_free_balance_be( + &friends.get(friend).unwrap(), + BalanceOf::::max_value(), + ); + } + + friends +} + +fn add_caller_and_generate_friends( + caller: T::AccountId, + num: u32, +) -> Vec<::AccountId> { + // Create friends + let mut friends = generate_friends::(num - 1); + + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + friends.push(caller); + + // Sort + friends.sort(); + + friends +} + +fn insert_recovery_account(caller: &T::AccountId, account: &T::AccountId) { + T::Currency::make_free_balance_be(&account, BalanceOf::::max_value()); + + let n = T::MaxFriends::get(); + + let friends = generate_friends::(n); + + let bounded_friends: FriendsOf = friends.try_into().unwrap(); + + // Get deposit for recovery + let total_deposit = get_total_deposit::(&bounded_friends).unwrap(); + + let recovery_config = RecoveryConfig { + delay_period: DEFAULT_DELAY.into(), + deposit: total_deposit, + friends: bounded_friends, + threshold: n as u16, + }; + + // Reserve deposit for recovery + T::Currency::reserve(&caller, total_deposit).unwrap(); + + >::insert(&account, recovery_config); +} + +benchmarks! { + as_recovered { + let caller: T::AccountId = whitelisted_caller(); + let recovered_account: T::AccountId = account("recovered_account", 0, SEED); + let recovered_account_lookup = T::Lookup::unlookup(recovered_account.clone()); + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + + Proxy::::insert(&caller, &recovered_account); + }: _( + RawOrigin::Signed(caller), + recovered_account_lookup, + Box::new(call) + ) + + set_recovered { + let lost: T::AccountId = whitelisted_caller(); + let lost_lookup = T::Lookup::unlookup(lost.clone()); + let rescuer: T::AccountId = whitelisted_caller(); + let rescuer_lookup = T::Lookup::unlookup(rescuer.clone()); + }: _( + RawOrigin::Root, + lost_lookup, + rescuer_lookup + ) verify { + assert_last_event::( + Event::AccountRecovered { + lost_account: lost, + rescuer_account: rescuer, + }.into() + ); + } + + create_recovery { + let n in 1 .. T::MaxFriends::get(); + + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Create friends + let friends = generate_friends::(n); + }: _( + RawOrigin::Signed(caller.clone()), + friends, + n as u16, + DEFAULT_DELAY.into() + ) verify { + assert_last_event::(Event::RecoveryCreated { account: caller }.into()); + } + + initiate_recovery { + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + let lost_account: T::AccountId = account("lost_account", 0, SEED); + let lost_account_lookup = T::Lookup::unlookup(lost_account.clone()); + + insert_recovery_account::(&caller, &lost_account); + }: _( + RawOrigin::Signed(caller.clone()), + lost_account_lookup + ) verify { + assert_last_event::( + Event::RecoveryInitiated { + lost_account: lost_account, + rescuer_account: caller, + }.into() + ); + } + + vouch_recovery { + let n in 1 .. T::MaxFriends::get(); + + let caller: T::AccountId = whitelisted_caller(); + let lost_account: T::AccountId = account("lost_account", 0, SEED); + let lost_account_lookup = T::Lookup::unlookup(lost_account.clone()); + let rescuer_account: T::AccountId = account("rescuer_account", 0, SEED); + let rescuer_account_lookup = T::Lookup::unlookup(rescuer_account.clone()); + + + // Create friends + let friends = add_caller_and_generate_friends::(caller.clone(), n); + let bounded_friends: FriendsOf = friends.try_into().unwrap(); + + // Get deposit for recovery + let total_deposit = get_total_deposit::(&bounded_friends).unwrap(); + + let recovery_config = RecoveryConfig { + delay_period: DEFAULT_DELAY.into(), + deposit: total_deposit.clone(), + friends: bounded_friends.clone(), + threshold: n as u16, + }; + + // Create the recovery config storage item + >::insert(&lost_account, recovery_config.clone()); + + // Reserve deposit for recovery + T::Currency::reserve(&caller, total_deposit).unwrap(); + + // Create an active recovery status + let recovery_status = ActiveRecovery { + created: DEFAULT_DELAY.into(), + deposit: total_deposit, + friends: generate_friends::(n - 1).try_into().unwrap(), + }; + + // Create the active recovery storage item + >::insert(&lost_account, &rescuer_account, recovery_status); + + }: _( + RawOrigin::Signed(caller.clone()), + lost_account_lookup, + rescuer_account_lookup + ) verify { + assert_last_event::( + Event::RecoveryVouched { + lost_account: lost_account, + rescuer_account: rescuer_account, + sender: caller, + }.into() + ); + } + + claim_recovery { + let n in 1 .. T::MaxFriends::get(); + + let caller: T::AccountId = whitelisted_caller(); + let lost_account: T::AccountId = account("lost_account", 0, SEED); + let lost_account_lookup = T::Lookup::unlookup(lost_account.clone()); + + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Create friends + let friends = generate_friends::(n); + let bounded_friends: FriendsOf = friends.try_into().unwrap(); + + // Get deposit for recovery + let total_deposit = get_total_deposit::(&bounded_friends).unwrap(); + + let recovery_config = RecoveryConfig { + delay_period: 0u32.into(), + deposit: total_deposit.clone(), + friends: bounded_friends.clone(), + threshold: n as u16, + }; + + // Create the recovery config storage item + >::insert(&lost_account, recovery_config.clone()); + + // Reserve deposit for recovery + T::Currency::reserve(&caller, total_deposit).unwrap(); + + // Create an active recovery status + let recovery_status = ActiveRecovery { + created: 0u32.into(), + deposit: total_deposit, + friends: bounded_friends.clone(), + }; + + // Create the active recovery storage item + >::insert(&lost_account, &caller, recovery_status); + }: _( + RawOrigin::Signed(caller.clone()), + lost_account_lookup + ) verify { + assert_last_event::( + Event::AccountRecovered { + lost_account: lost_account, + rescuer_account: caller, + }.into() + ); + } + + close_recovery { + let caller: T::AccountId = whitelisted_caller(); + let rescuer_account: T::AccountId = account("rescuer_account", 0, SEED); + let rescuer_account_lookup = T::Lookup::unlookup(rescuer_account.clone()); + + let n in 1 .. T::MaxFriends::get(); + + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + T::Currency::make_free_balance_be(&rescuer_account, BalanceOf::::max_value()); + + // Create friends + let friends = generate_friends::(n); + let bounded_friends: FriendsOf = friends.try_into().unwrap(); + + // Get deposit for recovery + let total_deposit = get_total_deposit::(&bounded_friends).unwrap(); + + let recovery_config = RecoveryConfig { + delay_period: DEFAULT_DELAY.into(), + deposit: total_deposit.clone(), + friends: bounded_friends.clone(), + threshold: n as u16, + }; + + // Create the recovery config storage item + >::insert(&caller, recovery_config.clone()); + + // Reserve deposit for recovery + T::Currency::reserve(&caller, total_deposit).unwrap(); + + // Create an active recovery status + let recovery_status = ActiveRecovery { + created: DEFAULT_DELAY.into(), + deposit: total_deposit, + friends: bounded_friends.clone(), + }; + + // Create the active recovery storage item + >::insert(&caller, &rescuer_account, recovery_status); + }: _( + RawOrigin::Signed(caller.clone()), + rescuer_account_lookup + ) verify { + assert_last_event::( + Event::RecoveryClosed { + lost_account: caller, + rescuer_account: rescuer_account, + }.into() + ); + } + + remove_recovery { + let n in 1 .. T::MaxFriends::get(); + + let caller: T::AccountId = whitelisted_caller(); + + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Create friends + let friends = generate_friends::(n); + let bounded_friends: FriendsOf = friends.try_into().unwrap(); + + // Get deposit for recovery + let total_deposit = get_total_deposit::(&bounded_friends).unwrap(); + + let recovery_config = RecoveryConfig { + delay_period: DEFAULT_DELAY.into(), + deposit: total_deposit.clone(), + friends: bounded_friends.clone(), + threshold: n as u16, + }; + + // Create the recovery config storage item + >::insert(&caller, recovery_config); + + // Reserve deposit for recovery + T::Currency::reserve(&caller, total_deposit).unwrap(); + }: _( + RawOrigin::Signed(caller.clone()) + ) verify { + assert_last_event::( + Event::RecoveryRemoved { + lost_account: caller + }.into() + ); + } + + cancel_recovered { + let caller: T::AccountId = whitelisted_caller(); + let account: T::AccountId = account("account", 0, SEED); + let account_lookup = T::Lookup::unlookup(account.clone()); + + frame_system::Pallet::::inc_providers(&caller); + + frame_system::Pallet::::inc_consumers(&caller)?; + + Proxy::::insert(&caller, &account); + }: _( + RawOrigin::Signed(caller), + account_lookup + ) + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/recovery/src/lib.rs b/frame/recovery/src/lib.rs index 797581788077b..9c57ca79d2e47 100644 --- a/frame/recovery/src/lib.rs +++ b/frame/recovery/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -154,43 +154,50 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_runtime::traits::{CheckedAdd, CheckedMul, Dispatchable, SaturatedConversion}; +use sp_runtime::traits::{CheckedAdd, CheckedMul, Dispatchable, SaturatedConversion, StaticLookup}; use sp_std::prelude::*; use frame_support::{ - dispatch::PostDispatchInfo, + dispatch::{GetDispatchInfo, PostDispatchInfo}, traits::{BalanceStatus, Currency, ReservableCurrency}, - weights::GetDispatchInfo, - RuntimeDebug, + BoundedVec, RuntimeDebug, }; pub use pallet::*; +pub use weights::WeightInfo; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +pub mod weights; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +type FriendsOf = BoundedVec<::AccountId, ::MaxFriends>; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + /// An active recovery process. -#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct ActiveRecovery { +#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ActiveRecovery { /// The block number when the recovery process started. created: BlockNumber, /// The amount held in reserve of the `depositor`, /// To be returned once this recovery process is closed. deposit: Balance, /// The friends which have vouched so far. Always sorted. - friends: Vec, + friends: Friends, } /// Configuration for recovering an account. -#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct RecoveryConfig { +#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct RecoveryConfig { /// The minimum number of blocks since the start of the recovery process before the account /// can be recovered. delay_period: BlockNumber, @@ -198,7 +205,7 @@ pub struct RecoveryConfig { /// to be returned once this configuration is removed. deposit: Balance, /// The list of friends which can help recover an account. Always sorted. - friends: Vec, + friends: Friends, /// The number of approving friends needed to recover an account. threshold: u16, } @@ -206,8 +213,8 @@ pub struct RecoveryConfig { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{ensure, pallet_prelude::*, traits::Get, Parameter}; - use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; use sp_runtime::ArithmeticError; #[pallet::pallet] @@ -218,12 +225,16 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; /// The overarching call type. - type Call: Parameter - + Dispatchable - + GetDispatchInfo; + type RuntimeCall: Parameter + + Dispatchable + + GetDispatchInfo + + From>; /// The currency mechanism. type Currency: ReservableCurrency; @@ -244,8 +255,13 @@ pub mod pallet { type FriendDepositFactor: Get>; /// The maximum amount of friends allowed in a recovery configuration. + /// + /// NOTE: The threshold programmed in this Pallet uses u16, so it does + /// not really make sense to have a limit here greater than u16::MAX. + /// But also, that is a lot more than you should probably set this value + /// to anyway... #[pallet::constant] - type MaxFriends: Get; + type MaxFriends: Get; /// The base amount of currency needed to reserve for starting a recovery. /// @@ -262,22 +278,22 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// A recovery process has been set up for an \[account\]. - RecoveryCreated(T::AccountId), + /// A recovery process has been set up for an account. + RecoveryCreated { account: T::AccountId }, /// A recovery process has been initiated for lost account by rescuer account. - /// \[lost, rescuer\] - RecoveryInitiated(T::AccountId, T::AccountId), + RecoveryInitiated { lost_account: T::AccountId, rescuer_account: T::AccountId }, /// A recovery process for lost account by rescuer account has been vouched for by sender. - /// \[lost, rescuer, sender\] - RecoveryVouched(T::AccountId, T::AccountId, T::AccountId), + RecoveryVouched { + lost_account: T::AccountId, + rescuer_account: T::AccountId, + sender: T::AccountId, + }, /// A recovery process for lost account by rescuer account has been closed. - /// \[lost, rescuer\] - RecoveryClosed(T::AccountId, T::AccountId), + RecoveryClosed { lost_account: T::AccountId, rescuer_account: T::AccountId }, /// Lost account has been successfully recovered by rescuer account. - /// \[lost, rescuer\] - AccountRecovered(T::AccountId, T::AccountId), - /// A recovery process has been removed for an \[account\]. - RecoveryRemoved(T::AccountId), + AccountRecovered { lost_account: T::AccountId, rescuer_account: T::AccountId }, + /// A recovery process has been removed for an account. + RecoveryRemoved { lost_account: T::AccountId }, } #[pallet::error] @@ -323,7 +339,7 @@ pub mod pallet { _, Twox64Concat, T::AccountId, - RecoveryConfig, T::AccountId>, + RecoveryConfig, FriendsOf>, >; /// Active recovery attempts. @@ -338,7 +354,7 @@ pub mod pallet { T::AccountId, Twox64Concat, T::AccountId, - ActiveRecovery, T::AccountId>, + ActiveRecovery, FriendsOf>, >; /// The list of allowed proxy accounts. @@ -358,30 +374,23 @@ pub mod pallet { /// Parameters: /// - `account`: The recovered account you want to make a call on-behalf-of. /// - `call`: The call you want to make with the recovered account. - /// - /// # - /// - The weight of the `call` + 10,000. - /// - One storage lookup to check account is recovered by `who`. O(1) - /// # + #[pallet::call_index(0)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( - dispatch_info.weight - .saturating_add(10_000) - // AccountData for inner call origin accountdata. - .saturating_add(T::DbWeight::get().reads_writes(1, 1)), + T::WeightInfo::as_recovered().saturating_add(dispatch_info.weight), dispatch_info.class, - ) - })] + )})] pub fn as_recovered( origin: OriginFor, - account: T::AccountId, - call: Box<::Call>, + account: AccountIdLookupOf, + call: Box<::RuntimeCall>, ) -> DispatchResult { let who = ensure_signed(origin)?; + let account = T::Lookup::lookup(account)?; // Check `who` is allowed to make a call on behalf of `account` let target = Self::proxy(&who).ok_or(Error::::NotAllowed)?; - ensure!(&target == &account, Error::::NotAllowed); + ensure!(target == account, Error::::NotAllowed); call.dispatch(frame_system::RawOrigin::Signed(account).into()) .map(|_| ()) .map_err(|e| e.error) @@ -395,21 +404,22 @@ pub mod pallet { /// Parameters: /// - `lost`: The "lost account" to be recovered. /// - `rescuer`: The "rescuer account" which can call as the lost account. - /// - /// # - /// - One storage write O(1) - /// - One event - /// # - #[pallet::weight(30_000_000)] + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::set_recovered())] pub fn set_recovered( origin: OriginFor, - lost: T::AccountId, - rescuer: T::AccountId, + lost: AccountIdLookupOf, + rescuer: AccountIdLookupOf, ) -> DispatchResult { ensure_root(origin)?; + let lost = T::Lookup::lookup(lost)?; + let rescuer = T::Lookup::lookup(rescuer)?; // Create the recovery storage item. >::insert(&rescuer, &lost); - Self::deposit_event(Event::::AccountRecovered(lost, rescuer)); + Self::deposit_event(Event::::AccountRecovered { + lost_account: lost, + rescuer_account: rescuer, + }); Ok(()) } @@ -429,18 +439,8 @@ pub mod pallet { /// friends. /// - `delay_period`: The number of blocks after a recovery attempt is initialized that /// needs to pass before the account can be recovered. - /// - /// # - /// - Key: F (len of friends) - /// - One storage read to check that account is not already recoverable. O(1). - /// - A check that the friends list is sorted and unique. O(F) - /// - One currency reserve operation. O(X) - /// - One storage write. O(1). Codec O(F). - /// - One event. - /// - /// Total Complexity: O(F + X) - /// # - #[pallet::weight(100_000_000)] + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::create_recovery(friends.len() as u32))] pub fn create_recovery( origin: OriginFor, friends: Vec, @@ -454,12 +454,12 @@ pub mod pallet { ensure!(threshold >= 1, Error::::ZeroThreshold); ensure!(!friends.is_empty(), Error::::NotEnoughFriends); ensure!(threshold as usize <= friends.len(), Error::::NotEnoughFriends); - let max_friends = T::MaxFriends::get() as usize; - ensure!(friends.len() <= max_friends, Error::::MaxFriends); - ensure!(Self::is_sorted_and_unique(&friends), Error::::NotSorted); + let bounded_friends: FriendsOf = + friends.try_into().map_err(|_| Error::::MaxFriends)?; + ensure!(Self::is_sorted_and_unique(&bounded_friends), Error::::NotSorted); // Total deposit is base fee + number of friends * factor fee let friend_deposit = T::FriendDepositFactor::get() - .checked_mul(&friends.len().saturated_into()) + .checked_mul(&bounded_friends.len().saturated_into()) .ok_or(ArithmeticError::Overflow)?; let total_deposit = T::ConfigDepositBase::get() .checked_add(&friend_deposit) @@ -467,12 +467,16 @@ pub mod pallet { // Reserve the deposit T::Currency::reserve(&who, total_deposit)?; // Create the recovery configuration - let recovery_config = - RecoveryConfig { delay_period, deposit: total_deposit, friends, threshold }; + let recovery_config = RecoveryConfig { + delay_period, + deposit: total_deposit, + friends: bounded_friends, + threshold, + }; // Create the recovery configuration storage item >::insert(&who, recovery_config); - Self::deposit_event(Event::::RecoveryCreated(who)); + Self::deposit_event(Event::::RecoveryCreated { account: who }); Ok(()) } @@ -487,20 +491,14 @@ pub mod pallet { /// Parameters: /// - `account`: The lost account that you want to recover. This account needs to be /// recoverable (i.e. have a recovery configuration). - /// - /// # - /// - One storage read to check that account is recoverable. O(F) - /// - One storage read to check that this recovery process hasn't already started. O(1) - /// - One currency reserve operation. O(X) - /// - One storage read to get the current block number. O(1) - /// - One storage write. O(1). - /// - One event. - /// - /// Total Complexity: O(F + X) - /// # - #[pallet::weight(100_000_000)] - pub fn initiate_recovery(origin: OriginFor, account: T::AccountId) -> DispatchResult { + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::initiate_recovery())] + pub fn initiate_recovery( + origin: OriginFor, + account: AccountIdLookupOf, + ) -> DispatchResult { let who = ensure_signed(origin)?; + let account = T::Lookup::lookup(account)?; // Check that the account is recoverable ensure!(>::contains_key(&account), Error::::NotRecoverable); // Check that the recovery process has not already been started @@ -515,11 +513,14 @@ pub mod pallet { let recovery_status = ActiveRecovery { created: >::block_number(), deposit: recovery_deposit, - friends: vec![], + friends: Default::default(), }; // Create the active recovery storage item >::insert(&account, &who, recovery_status); - Self::deposit_event(Event::::RecoveryInitiated(account, who)); + Self::deposit_event(Event::::RecoveryInitiated { + lost_account: account, + rescuer_account: who, + }); Ok(()) } @@ -535,25 +536,16 @@ pub mod pallet { /// /// The combination of these two parameters must point to an active recovery /// process. - /// - /// # - /// Key: F (len of friends in config), V (len of vouching friends) - /// - One storage read to get the recovery configuration. O(1), Codec O(F) - /// - One storage read to get the active recovery process. O(1), Codec O(V) - /// - One binary search to confirm caller is a friend. O(logF) - /// - One binary search to confirm caller has not already vouched. O(logV) - /// - One storage write. O(1), Codec O(V). - /// - One event. - /// - /// Total Complexity: O(F + logF + V + logV) - /// # - #[pallet::weight(100_000_000)] + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::vouch_recovery(T::MaxFriends::get()))] pub fn vouch_recovery( origin: OriginFor, - lost: T::AccountId, - rescuer: T::AccountId, + lost: AccountIdLookupOf, + rescuer: AccountIdLookupOf, ) -> DispatchResult { let who = ensure_signed(origin)?; + let lost = T::Lookup::lookup(lost)?; + let rescuer = T::Lookup::lookup(rescuer)?; // Get the recovery configuration for the lost account. let recovery_config = Self::recovery_config(&lost).ok_or(Error::::NotRecoverable)?; // Get the active recovery process for the rescuer. @@ -563,12 +555,19 @@ pub mod pallet { ensure!(Self::is_friend(&recovery_config.friends, &who), Error::::NotFriend); // Either insert the vouch, or return an error that the user already vouched. match active_recovery.friends.binary_search(&who) { - Ok(_pos) => Err(Error::::AlreadyVouched)?, - Err(pos) => active_recovery.friends.insert(pos, who.clone()), + Ok(_pos) => return Err(Error::::AlreadyVouched.into()), + Err(pos) => active_recovery + .friends + .try_insert(pos, who.clone()) + .map_err(|_| Error::::MaxFriends)?, } // Update storage with the latest details >::insert(&lost, &rescuer, active_recovery); - Self::deposit_event(Event::::RecoveryVouched(lost, rescuer, who)); + Self::deposit_event(Event::::RecoveryVouched { + lost_account: lost, + rescuer_account: rescuer, + sender: who, + }); Ok(()) } @@ -581,20 +580,14 @@ pub mod pallet { /// Parameters: /// - `account`: The lost account that you want to claim has been successfully recovered by /// you. - /// - /// # - /// Key: F (len of friends in config), V (len of vouching friends) - /// - One storage read to get the recovery configuration. O(1), Codec O(F) - /// - One storage read to get the active recovery process. O(1), Codec O(V) - /// - One storage read to get the current block number. O(1) - /// - One storage write. O(1), Codec O(V). - /// - One event. - /// - /// Total Complexity: O(F + V) - /// # - #[pallet::weight(100_000_000)] - pub fn claim_recovery(origin: OriginFor, account: T::AccountId) -> DispatchResult { + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::claim_recovery(T::MaxFriends::get()))] + pub fn claim_recovery( + origin: OriginFor, + account: AccountIdLookupOf, + ) -> DispatchResult { let who = ensure_signed(origin)?; + let account = T::Lookup::lookup(account)?; // Get the recovery configuration for the lost account let recovery_config = Self::recovery_config(&account).ok_or(Error::::NotRecoverable)?; @@ -617,7 +610,10 @@ pub mod pallet { frame_system::Pallet::::inc_consumers(&who).map_err(|_| Error::::BadState)?; // Create the recovery storage item Proxy::::insert(&who, &account); - Self::deposit_event(Event::::AccountRecovered(account, who)); + Self::deposit_event(Event::::AccountRecovered { + lost_account: account, + rescuer_account: who, + }); Ok(()) } @@ -632,18 +628,14 @@ pub mod pallet { /// /// Parameters: /// - `rescuer`: The account trying to rescue this recoverable account. - /// - /// # - /// Key: V (len of vouching friends) - /// - One storage read/remove to get the active recovery process. O(1), Codec O(V) - /// - One balance call to repatriate reserved. O(X) - /// - One event. - /// - /// Total Complexity: O(V + X) - /// # - #[pallet::weight(30_000_000)] - pub fn close_recovery(origin: OriginFor, rescuer: T::AccountId) -> DispatchResult { + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::close_recovery(T::MaxFriends::get()))] + pub fn close_recovery( + origin: OriginFor, + rescuer: AccountIdLookupOf, + ) -> DispatchResult { let who = ensure_signed(origin)?; + let rescuer = T::Lookup::lookup(rescuer)?; // Take the active recovery process started by the rescuer for this account. let active_recovery = >::take(&who, &rescuer).ok_or(Error::::NotStarted)?; @@ -656,7 +648,10 @@ pub mod pallet { BalanceStatus::Free, ); debug_assert!(res.is_ok()); - Self::deposit_event(Event::::RecoveryClosed(who, rescuer)); + Self::deposit_event(Event::::RecoveryClosed { + lost_account: who, + rescuer_account: rescuer, + }); Ok(()) } @@ -671,17 +666,8 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and must be a /// recoverable account (i.e. has a recovery configuration). - /// - /// # - /// Key: F (len of friends) - /// - One storage read to get the prefix iterator for active recoveries. O(1) - /// - One storage read/remove to get the recovery configuration. O(1), Codec O(F) - /// - One balance call to unreserved. O(X) - /// - One event. - /// - /// Total Complexity: O(F + X) - /// # - #[pallet::weight(30_000_000)] + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::remove_recovery(T::MaxFriends::get()))] pub fn remove_recovery(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; // Check there are no active recoveries @@ -692,7 +678,7 @@ pub mod pallet { // Unreserve the initial deposit for the recovery configuration. T::Currency::unreserve(&who, recovery_config.deposit); - Self::deposit_event(Event::::RecoveryRemoved(who)); + Self::deposit_event(Event::::RecoveryRemoved { lost_account: who }); Ok(()) } @@ -703,16 +689,18 @@ pub mod pallet { /// /// Parameters: /// - `account`: The recovered account you are able to call on-behalf-of. - /// - /// # - /// - One storage mutation to check account is recovered by `who`. O(1) - /// # - #[pallet::weight(30_000_000)] - pub fn cancel_recovered(origin: OriginFor, account: T::AccountId) -> DispatchResult { + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::cancel_recovered())] + pub fn cancel_recovered( + origin: OriginFor, + account: AccountIdLookupOf, + ) -> DispatchResult { let who = ensure_signed(origin)?; + let account = T::Lookup::lookup(account)?; // Check `who` is allowed to make a call on behalf of `account` ensure!(Self::proxy(&who) == Some(account), Error::::NotAllowed); Proxy::::remove(&who); + frame_system::Pallet::::dec_consumers(&who); Ok(()) } diff --git a/frame/recovery/src/mock.rs b/frame/recovery/src/mock.rs index f6d4a6b159431..2a29390fdd20f 100644 --- a/frame/recovery/src/mock.rs +++ b/frame/recovery/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use super::*; use crate as recovery; use frame_support::{ parameter_types, - traits::{OnFinalize, OnInitialize}, + traits::{ConstU32, ConstU64, OnFinalize, OnInitialize}, }; use sp_core::H256; use sp_runtime::{ @@ -46,9 +46,8 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { @@ -56,8 +55,8 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -65,8 +64,8 @@ impl frame_system::Config for Test { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -75,6 +74,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } parameter_types! { @@ -87,7 +87,7 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; type Balance = u128; type DustRemoval = (); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); @@ -96,13 +96,15 @@ impl pallet_balances::Config for Test { parameter_types! { pub const ConfigDepositBase: u64 = 10; pub const FriendDepositFactor: u64 = 1; - pub const MaxFriends: u16 = 3; pub const RecoveryDeposit: u64 = 10; + // Large number of friends for benchmarking. + pub const MaxFriends: u32 = 128; } impl Config for Test { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type RuntimeCall = RuntimeCall; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; diff --git a/frame/recovery/src/tests.rs b/frame/recovery/src/tests.rs index fe971319bc97c..b037a8110147d 100644 --- a/frame/recovery/src/tests.rs +++ b/frame/recovery/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,10 @@ //! Tests for the module. use super::*; -use frame_support::{assert_noop, assert_ok, traits::Currency}; +use frame_support::{assert_noop, assert_ok, bounded_vec, traits::Currency}; use mock::{ - new_test_ext, run_to_block, Balances, BalancesCall, Call, Origin, Recovery, RecoveryCall, Test, + new_test_ext, run_to_block, Balances, BalancesCall, MaxFriends, Recovery, RecoveryCall, + RuntimeCall, RuntimeOrigin, Test, }; use sp_runtime::traits::BadOrigin; @@ -40,12 +41,12 @@ fn basic_setup_works() { fn set_recovered_works() { new_test_ext().execute_with(|| { // Not accessible by a normal user - assert_noop!(Recovery::set_recovered(Origin::signed(1), 5, 1), BadOrigin); + assert_noop!(Recovery::set_recovered(RuntimeOrigin::signed(1), 5, 1), BadOrigin); // Root can set a recovered account though - assert_ok!(Recovery::set_recovered(Origin::root(), 5, 1)); + assert_ok!(Recovery::set_recovered(RuntimeOrigin::root(), 5, 1)); // Account 1 should now be able to make a call through account 5 - let call = Box::new(Call::Balances(BalancesCall::transfer { dest: 1, value: 100 })); - assert_ok!(Recovery::as_recovered(Origin::signed(1), 5, call)); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 1, value: 100 })); + assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call)); // Account 1 has successfully drained the funds from account 5 assert_eq!(Balances::free_balance(1), 200); assert_eq!(Balances::free_balance(5), 0); @@ -59,38 +60,46 @@ fn recovery_life_cycle_works() { let threshold = 3; let delay_period = 10; // Account 5 sets up a recovery configuration on their account - assert_ok!(Recovery::create_recovery(Origin::signed(5), friends, threshold, delay_period)); + assert_ok!(Recovery::create_recovery( + RuntimeOrigin::signed(5), + friends, + threshold, + delay_period + )); // Some time has passed, and the user lost their keys! run_to_block(10); // Using account 1, the user begins the recovery process to recover the lost account - assert_ok!(Recovery::initiate_recovery(Origin::signed(1), 5)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Off chain, the user contacts their friends and asks them to vouch for the recovery // attempt - assert_ok!(Recovery::vouch_recovery(Origin::signed(2), 5, 1)); - assert_ok!(Recovery::vouch_recovery(Origin::signed(3), 5, 1)); - assert_ok!(Recovery::vouch_recovery(Origin::signed(4), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1)); // We met the threshold, lets try to recover the account...? - assert_noop!(Recovery::claim_recovery(Origin::signed(1), 5), Error::::DelayPeriod); + assert_noop!( + Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), + Error::::DelayPeriod + ); // We need to wait at least the delay_period number of blocks before we can recover run_to_block(20); - assert_ok!(Recovery::claim_recovery(Origin::signed(1), 5)); + assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(1), 5)); // Account 1 can use account 5 to close the active recovery process, claiming the deposited // funds used to initiate the recovery process into account 5. - let call = Box::new(Call::Recovery(RecoveryCall::close_recovery { rescuer: 1 })); - assert_ok!(Recovery::as_recovered(Origin::signed(1), 5, call)); + let call = Box::new(RuntimeCall::Recovery(RecoveryCall::close_recovery { rescuer: 1 })); + assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call)); // Account 1 can then use account 5 to remove the recovery configuration, claiming the // deposited funds used to create the recovery configuration into account 5. - let call = Box::new(Call::Recovery(RecoveryCall::remove_recovery {})); - assert_ok!(Recovery::as_recovered(Origin::signed(1), 5, call)); + let call = Box::new(RuntimeCall::Recovery(RecoveryCall::remove_recovery {})); + assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call)); // Account 1 should now be able to make a call through account 5 to get all of their funds assert_eq!(Balances::free_balance(5), 110); - let call = Box::new(Call::Balances(BalancesCall::transfer { dest: 1, value: 110 })); - assert_ok!(Recovery::as_recovered(Origin::signed(1), 5, call)); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 1, value: 110 })); + assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call)); // All funds have been fully recovered! assert_eq!(Balances::free_balance(1), 200); assert_eq!(Balances::free_balance(5), 0); // Remove the proxy link. - assert_ok!(Recovery::cancel_recovered(Origin::signed(1), 5)); + assert_ok!(Recovery::cancel_recovered(RuntimeOrigin::signed(1), 5)); // All storage items are removed from the module assert!(!>::contains_key(&5, &1)); @@ -106,35 +115,52 @@ fn malicious_recovery_fails() { let threshold = 3; let delay_period = 10; // Account 5 sets up a recovery configuration on their account - assert_ok!(Recovery::create_recovery(Origin::signed(5), friends, threshold, delay_period)); + assert_ok!(Recovery::create_recovery( + RuntimeOrigin::signed(5), + friends, + threshold, + delay_period + )); // Some time has passed, and account 1 wants to try and attack this account! run_to_block(10); // Using account 1, the malicious user begins the recovery process on account 5 - assert_ok!(Recovery::initiate_recovery(Origin::signed(1), 5)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Off chain, the user **tricks** their friends and asks them to vouch for the recovery - assert_ok!(Recovery::vouch_recovery(Origin::signed(2), 5, 1)); // shame on you - assert_ok!(Recovery::vouch_recovery(Origin::signed(3), 5, 1)); // shame on you - assert_ok!(Recovery::vouch_recovery(Origin::signed(4), 5, 1)); // shame on you - // We met the threshold, lets try to recover the account...? - assert_noop!(Recovery::claim_recovery(Origin::signed(1), 5), Error::::DelayPeriod); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1)); + // shame on you + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); + // shame on you + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1)); + // shame on you + // We met the threshold, lets try to recover the account...? + assert_noop!( + Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), + Error::::DelayPeriod + ); // Account 1 needs to wait... run_to_block(19); // One more block to wait! - assert_noop!(Recovery::claim_recovery(Origin::signed(1), 5), Error::::DelayPeriod); + assert_noop!( + Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), + Error::::DelayPeriod + ); // Account 5 checks their account every `delay_period` and notices the malicious attack! // Account 5 can close the recovery process before account 1 can claim it - assert_ok!(Recovery::close_recovery(Origin::signed(5), 1)); + assert_ok!(Recovery::close_recovery(RuntimeOrigin::signed(5), 1)); // By doing so, account 5 has now claimed the deposit originally reserved by account 1 assert_eq!(Balances::total_balance(&1), 90); // Thanks for the free money! assert_eq!(Balances::total_balance(&5), 110); // The recovery process has been closed, so account 1 can't make the claim run_to_block(20); - assert_noop!(Recovery::claim_recovery(Origin::signed(1), 5), Error::::NotStarted); + assert_noop!( + Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), + Error::::NotStarted + ); // Account 5 can remove their recovery config and pick some better friends - assert_ok!(Recovery::remove_recovery(Origin::signed(5))); + assert_ok!(Recovery::remove_recovery(RuntimeOrigin::signed(5))); assert_ok!(Recovery::create_recovery( - Origin::signed(5), + RuntimeOrigin::signed(5), vec![22, 33, 44], threshold, delay_period @@ -147,38 +173,43 @@ fn create_recovery_handles_basic_errors() { new_test_ext().execute_with(|| { // No friends assert_noop!( - Recovery::create_recovery(Origin::signed(5), vec![], 1, 0), + Recovery::create_recovery(RuntimeOrigin::signed(5), vec![], 1, 0), Error::::NotEnoughFriends ); // Zero threshold assert_noop!( - Recovery::create_recovery(Origin::signed(5), vec![2], 0, 0), + Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2], 0, 0), Error::::ZeroThreshold ); // Threshold greater than friends length assert_noop!( - Recovery::create_recovery(Origin::signed(5), vec![2, 3, 4], 4, 0), + Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 4, 0), Error::::NotEnoughFriends ); // Too many friends assert_noop!( - Recovery::create_recovery(Origin::signed(5), vec![1, 2, 3, 4], 4, 0), + Recovery::create_recovery( + RuntimeOrigin::signed(5), + vec![1; (MaxFriends::get() + 1) as usize], + 1, + 0 + ), Error::::MaxFriends ); // Unsorted friends assert_noop!( - Recovery::create_recovery(Origin::signed(5), vec![3, 2, 4], 3, 0), + Recovery::create_recovery(RuntimeOrigin::signed(5), vec![3, 2, 4], 3, 0), Error::::NotSorted ); // Duplicate friends assert_noop!( - Recovery::create_recovery(Origin::signed(5), vec![2, 2, 4], 3, 0), + Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 2, 4], 3, 0), Error::::NotSorted ); // Already configured - assert_ok!(Recovery::create_recovery(Origin::signed(5), vec![2, 3, 4], 3, 10)); + assert_ok!(Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 3, 10)); assert_noop!( - Recovery::create_recovery(Origin::signed(5), vec![2, 3, 4], 3, 10), + Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 3, 10), Error::::AlreadyRecoverable ); }); @@ -192,7 +223,7 @@ fn create_recovery_works() { let delay_period = 10; // Account 5 sets up a recovery configuration on their account assert_ok!(Recovery::create_recovery( - Origin::signed(5), + RuntimeOrigin::signed(5), friends.clone(), threshold, delay_period @@ -201,8 +232,12 @@ fn create_recovery_works() { // Base 10 + 1 per friends = 13 total reserved assert_eq!(Balances::reserved_balance(5), 13); // Recovery configuration is correctly stored - let recovery_config = - RecoveryConfig { delay_period, deposit: 13, friends: friends.clone(), threshold }; + let recovery_config = RecoveryConfig { + delay_period, + deposit: 13, + friends: friends.try_into().unwrap(), + threshold, + }; assert_eq!(Recovery::recovery_config(5), Some(recovery_config)); }); } @@ -212,7 +247,7 @@ fn initiate_recovery_handles_basic_errors() { new_test_ext().execute_with(|| { // No recovery process set up for the account assert_noop!( - Recovery::initiate_recovery(Origin::signed(1), 5), + Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5), Error::::NotRecoverable ); // Create a recovery process for next test @@ -220,15 +255,15 @@ fn initiate_recovery_handles_basic_errors() { let threshold = 3; let delay_period = 10; assert_ok!(Recovery::create_recovery( - Origin::signed(5), + RuntimeOrigin::signed(5), friends.clone(), threshold, delay_period )); // Same user cannot recover same account twice - assert_ok!(Recovery::initiate_recovery(Origin::signed(1), 5)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); assert_noop!( - Recovery::initiate_recovery(Origin::signed(1), 5), + Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5), Error::::AlreadyStarted ); // No double deposit @@ -244,20 +279,21 @@ fn initiate_recovery_works() { let threshold = 3; let delay_period = 10; assert_ok!(Recovery::create_recovery( - Origin::signed(5), + RuntimeOrigin::signed(5), friends.clone(), threshold, delay_period )); // Recovery can be initiated - assert_ok!(Recovery::initiate_recovery(Origin::signed(1), 5)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Deposit is reserved assert_eq!(Balances::reserved_balance(1), 10); // Recovery status object is created correctly - let recovery_status = ActiveRecovery { created: 0, deposit: 10, friends: vec![] }; + let recovery_status = + ActiveRecovery { created: 0, deposit: 10, friends: Default::default() }; assert_eq!(>::get(&5, &1), Some(recovery_status)); // Multiple users can attempt to recover the same account - assert_ok!(Recovery::initiate_recovery(Origin::signed(2), 5)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(2), 5)); }); } @@ -266,7 +302,7 @@ fn vouch_recovery_handles_basic_errors() { new_test_ext().execute_with(|| { // Cannot vouch for non-recoverable account assert_noop!( - Recovery::vouch_recovery(Origin::signed(2), 5, 1), + Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1), Error::::NotRecoverable ); // Create a recovery process for next tests @@ -274,21 +310,27 @@ fn vouch_recovery_handles_basic_errors() { let threshold = 3; let delay_period = 10; assert_ok!(Recovery::create_recovery( - Origin::signed(5), + RuntimeOrigin::signed(5), friends.clone(), threshold, delay_period )); // Cannot vouch a recovery process that has not started - assert_noop!(Recovery::vouch_recovery(Origin::signed(2), 5, 1), Error::::NotStarted); + assert_noop!( + Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1), + Error::::NotStarted + ); // Initiate a recovery process - assert_ok!(Recovery::initiate_recovery(Origin::signed(1), 5)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Cannot vouch if you are not a friend - assert_noop!(Recovery::vouch_recovery(Origin::signed(22), 5, 1), Error::::NotFriend); + assert_noop!( + Recovery::vouch_recovery(RuntimeOrigin::signed(22), 5, 1), + Error::::NotFriend + ); // Cannot vouch twice - assert_ok!(Recovery::vouch_recovery(Origin::signed(2), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1)); assert_noop!( - Recovery::vouch_recovery(Origin::signed(2), 5, 1), + Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1), Error::::AlreadyVouched ); }); @@ -302,19 +344,20 @@ fn vouch_recovery_works() { let threshold = 3; let delay_period = 10; assert_ok!(Recovery::create_recovery( - Origin::signed(5), + RuntimeOrigin::signed(5), friends.clone(), threshold, delay_period )); - assert_ok!(Recovery::initiate_recovery(Origin::signed(1), 5)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Vouching works - assert_ok!(Recovery::vouch_recovery(Origin::signed(2), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1)); // Handles out of order vouches - assert_ok!(Recovery::vouch_recovery(Origin::signed(4), 5, 1)); - assert_ok!(Recovery::vouch_recovery(Origin::signed(3), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); // Final recovery status object is updated correctly - let recovery_status = ActiveRecovery { created: 0, deposit: 10, friends: vec![2, 3, 4] }; + let recovery_status = + ActiveRecovery { created: 0, deposit: 10, friends: bounded_vec![2, 3, 4] }; assert_eq!(>::get(&5, &1), Some(recovery_status)); }); } @@ -323,28 +366,40 @@ fn vouch_recovery_works() { fn claim_recovery_handles_basic_errors() { new_test_ext().execute_with(|| { // Cannot claim a non-recoverable account - assert_noop!(Recovery::claim_recovery(Origin::signed(1), 5), Error::::NotRecoverable); + assert_noop!( + Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), + Error::::NotRecoverable + ); // Create a recovery process for the test let friends = vec![2, 3, 4]; let threshold = 3; let delay_period = 10; assert_ok!(Recovery::create_recovery( - Origin::signed(5), + RuntimeOrigin::signed(5), friends.clone(), threshold, delay_period )); // Cannot claim an account which has not started the recovery process - assert_noop!(Recovery::claim_recovery(Origin::signed(1), 5), Error::::NotStarted); - assert_ok!(Recovery::initiate_recovery(Origin::signed(1), 5)); + assert_noop!( + Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), + Error::::NotStarted + ); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Cannot claim an account which has not passed the delay period - assert_noop!(Recovery::claim_recovery(Origin::signed(1), 5), Error::::DelayPeriod); + assert_noop!( + Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), + Error::::DelayPeriod + ); run_to_block(11); // Cannot claim an account which has not passed the threshold number of votes - assert_ok!(Recovery::vouch_recovery(Origin::signed(2), 5, 1)); - assert_ok!(Recovery::vouch_recovery(Origin::signed(3), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); // Only 2/3 is not good enough - assert_noop!(Recovery::claim_recovery(Origin::signed(1), 5), Error::::Threshold); + assert_noop!( + Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), + Error::::Threshold + ); }); } @@ -356,32 +411,32 @@ fn claim_recovery_works() { let threshold = 3; let delay_period = 10; assert_ok!(Recovery::create_recovery( - Origin::signed(5), + RuntimeOrigin::signed(5), friends.clone(), threshold, delay_period )); - assert_ok!(Recovery::initiate_recovery(Origin::signed(1), 5)); - assert_ok!(Recovery::vouch_recovery(Origin::signed(2), 5, 1)); - assert_ok!(Recovery::vouch_recovery(Origin::signed(3), 5, 1)); - assert_ok!(Recovery::vouch_recovery(Origin::signed(4), 5, 1)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1)); run_to_block(11); // Account can be recovered. - assert_ok!(Recovery::claim_recovery(Origin::signed(1), 5)); + assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(1), 5)); // Recovered storage item is correctly created assert_eq!(>::get(&1), Some(5)); // Account could be re-recovered in the case that the recoverer account also gets lost. - assert_ok!(Recovery::initiate_recovery(Origin::signed(4), 5)); - assert_ok!(Recovery::vouch_recovery(Origin::signed(2), 5, 4)); - assert_ok!(Recovery::vouch_recovery(Origin::signed(3), 5, 4)); - assert_ok!(Recovery::vouch_recovery(Origin::signed(4), 5, 4)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(4), 5)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 4)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 4)); + assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 4)); run_to_block(21); // Account is re-recovered. - assert_ok!(Recovery::claim_recovery(Origin::signed(4), 5)); + assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(4), 5)); // Recovered storage item is correctly updated assert_eq!(>::get(&4), Some(5)); }); @@ -391,7 +446,10 @@ fn claim_recovery_works() { fn close_recovery_handles_basic_errors() { new_test_ext().execute_with(|| { // Cannot close a non-active recovery - assert_noop!(Recovery::close_recovery(Origin::signed(5), 1), Error::::NotStarted); + assert_noop!( + Recovery::close_recovery(RuntimeOrigin::signed(5), 1), + Error::::NotStarted + ); }); } @@ -399,26 +457,35 @@ fn close_recovery_handles_basic_errors() { fn remove_recovery_works() { new_test_ext().execute_with(|| { // Cannot remove an unrecoverable account - assert_noop!(Recovery::remove_recovery(Origin::signed(5)), Error::::NotRecoverable); + assert_noop!( + Recovery::remove_recovery(RuntimeOrigin::signed(5)), + Error::::NotRecoverable + ); // Create and initiate a recovery process for the test let friends = vec![2, 3, 4]; let threshold = 3; let delay_period = 10; assert_ok!(Recovery::create_recovery( - Origin::signed(5), + RuntimeOrigin::signed(5), friends.clone(), threshold, delay_period )); - assert_ok!(Recovery::initiate_recovery(Origin::signed(1), 5)); - assert_ok!(Recovery::initiate_recovery(Origin::signed(2), 5)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); + assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(2), 5)); // Cannot remove a recovery when there are active recoveries. - assert_noop!(Recovery::remove_recovery(Origin::signed(5)), Error::::StillActive); - assert_ok!(Recovery::close_recovery(Origin::signed(5), 1)); + assert_noop!( + Recovery::remove_recovery(RuntimeOrigin::signed(5)), + Error::::StillActive + ); + assert_ok!(Recovery::close_recovery(RuntimeOrigin::signed(5), 1)); // Still need to remove one more! - assert_noop!(Recovery::remove_recovery(Origin::signed(5)), Error::::StillActive); - assert_ok!(Recovery::close_recovery(Origin::signed(5), 2)); + assert_noop!( + Recovery::remove_recovery(RuntimeOrigin::signed(5)), + Error::::StillActive + ); + assert_ok!(Recovery::close_recovery(RuntimeOrigin::signed(5), 2)); // Finally removed - assert_ok!(Recovery::remove_recovery(Origin::signed(5))); + assert_ok!(Recovery::remove_recovery(RuntimeOrigin::signed(5))); }); } diff --git a/frame/recovery/src/weights.rs b/frame/recovery/src/weights.rs new file mode 100644 index 0000000000000..39a8d09a38261 --- /dev/null +++ b/frame/recovery/src/weights.rs @@ -0,0 +1,232 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_recovery +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_recovery +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/recovery/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_recovery. +pub trait WeightInfo { + fn as_recovered() -> Weight; + fn set_recovered() -> Weight; + fn create_recovery(n: u32, ) -> Weight; + fn initiate_recovery() -> Weight; + fn vouch_recovery(n: u32, ) -> Weight; + fn claim_recovery(n: u32, ) -> Weight; + fn close_recovery(n: u32, ) -> Weight; + fn remove_recovery(n: u32, ) -> Weight; + fn cancel_recovered() -> Weight; +} + +/// Weights for pallet_recovery using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Recovery Proxy (r:1 w:0) + fn as_recovered() -> Weight { + // Minimum execution time: 10_672 nanoseconds. + Weight::from_ref_time(10_946_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + } + // Storage: Recovery Proxy (r:0 w:1) + fn set_recovered() -> Weight { + // Minimum execution time: 17_092 nanoseconds. + Weight::from_ref_time(17_660_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Recovery Recoverable (r:1 w:1) + /// The range of component `n` is `[1, 9]`. + fn create_recovery(n: u32, ) -> Weight { + // Minimum execution time: 32_800 nanoseconds. + Weight::from_ref_time(33_769_078 as u64) + // Standard Error: 4_075 + .saturating_add(Weight::from_ref_time(252_382 as u64).saturating_mul(n as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:1) + fn initiate_recovery() -> Weight { + // Minimum execution time: 39_224 nanoseconds. + Weight::from_ref_time(39_663_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:1) + /// The range of component `n` is `[1, 9]`. + fn vouch_recovery(n: u32, ) -> Weight { + // Minimum execution time: 27_158 nanoseconds. + Weight::from_ref_time(28_130_506 as u64) + // Standard Error: 4_523 + .saturating_add(Weight::from_ref_time(321_436 as u64).saturating_mul(n as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:0) + // Storage: Recovery Proxy (r:1 w:1) + /// The range of component `n` is `[1, 9]`. + fn claim_recovery(n: u32, ) -> Weight { + // Minimum execution time: 36_269 nanoseconds. + Weight::from_ref_time(36_966_173 as u64) + // Standard Error: 5_016 + .saturating_add(Weight::from_ref_time(223_069 as u64).saturating_mul(n as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Recovery ActiveRecoveries (r:1 w:1) + // Storage: System Account (r:1 w:1) + /// The range of component `n` is `[1, 9]`. + fn close_recovery(n: u32, ) -> Weight { + // Minimum execution time: 40_213 nanoseconds. + Weight::from_ref_time(41_140_968 as u64) + // Standard Error: 3_822 + .saturating_add(Weight::from_ref_time(163_217 as u64).saturating_mul(n as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Recovery ActiveRecoveries (r:1 w:0) + // Storage: Recovery Recoverable (r:1 w:1) + /// The range of component `n` is `[1, 9]`. + fn remove_recovery(n: u32, ) -> Weight { + // Minimum execution time: 38_740 nanoseconds. + Weight::from_ref_time(39_710_400 as u64) + // Standard Error: 5_554 + .saturating_add(Weight::from_ref_time(224_200 as u64).saturating_mul(n as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Recovery Proxy (r:1 w:1) + fn cancel_recovered() -> Weight { + // Minimum execution time: 20_316 nanoseconds. + Weight::from_ref_time(20_912_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Recovery Proxy (r:1 w:0) + fn as_recovered() -> Weight { + // Minimum execution time: 10_672 nanoseconds. + Weight::from_ref_time(10_946_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + } + // Storage: Recovery Proxy (r:0 w:1) + fn set_recovered() -> Weight { + // Minimum execution time: 17_092 nanoseconds. + Weight::from_ref_time(17_660_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Recovery Recoverable (r:1 w:1) + /// The range of component `n` is `[1, 9]`. + fn create_recovery(n: u32, ) -> Weight { + // Minimum execution time: 32_800 nanoseconds. + Weight::from_ref_time(33_769_078 as u64) + // Standard Error: 4_075 + .saturating_add(Weight::from_ref_time(252_382 as u64).saturating_mul(n as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:1) + fn initiate_recovery() -> Weight { + // Minimum execution time: 39_224 nanoseconds. + Weight::from_ref_time(39_663_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:1) + /// The range of component `n` is `[1, 9]`. + fn vouch_recovery(n: u32, ) -> Weight { + // Minimum execution time: 27_158 nanoseconds. + Weight::from_ref_time(28_130_506 as u64) + // Standard Error: 4_523 + .saturating_add(Weight::from_ref_time(321_436 as u64).saturating_mul(n as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:0) + // Storage: Recovery Proxy (r:1 w:1) + /// The range of component `n` is `[1, 9]`. + fn claim_recovery(n: u32, ) -> Weight { + // Minimum execution time: 36_269 nanoseconds. + Weight::from_ref_time(36_966_173 as u64) + // Standard Error: 5_016 + .saturating_add(Weight::from_ref_time(223_069 as u64).saturating_mul(n as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Recovery ActiveRecoveries (r:1 w:1) + // Storage: System Account (r:1 w:1) + /// The range of component `n` is `[1, 9]`. + fn close_recovery(n: u32, ) -> Weight { + // Minimum execution time: 40_213 nanoseconds. + Weight::from_ref_time(41_140_968 as u64) + // Standard Error: 3_822 + .saturating_add(Weight::from_ref_time(163_217 as u64).saturating_mul(n as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Recovery ActiveRecoveries (r:1 w:0) + // Storage: Recovery Recoverable (r:1 w:1) + /// The range of component `n` is `[1, 9]`. + fn remove_recovery(n: u32, ) -> Weight { + // Minimum execution time: 38_740 nanoseconds. + Weight::from_ref_time(39_710_400 as u64) + // Standard Error: 5_554 + .saturating_add(Weight::from_ref_time(224_200 as u64).saturating_mul(n as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Recovery Proxy (r:1 w:1) + fn cancel_recovered() -> Weight { + // Minimum execution time: 20_316 nanoseconds. + Weight::from_ref_time(20_912_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml new file mode 100644 index 0000000000000..02894e1499d93 --- /dev/null +++ b/frame/referenda/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "pallet-referenda" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet for inclusive on-chain decisions" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +assert_matches = { version = "1.5", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.3", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +log = { version = "0.4.17", default-features = false } + +[dev-dependencies] +assert_matches = { version = "1.5" } +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } +pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "sp-runtime/std", + "sp-arithmetic/std", + "frame-system/std", + "scale-info/std", + "serde", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "assert_matches", + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/referenda/README.md b/frame/referenda/README.md new file mode 100644 index 0000000000000..85031a0113033 --- /dev/null +++ b/frame/referenda/README.md @@ -0,0 +1,8 @@ +# Referenda Pallet + +- [`assembly::Config`](https://docs.rs/pallet-assembly/latest/pallet_assembly/trait.Config.html) +- [`Call`](https://docs.rs/pallet-assembly/latest/pallet_assembly/enum.Call.html) + +## Overview + +The Assembly pallet handles the administration of general stakeholder voting. diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs new file mode 100644 index 0000000000000..e57b5f9859e5b --- /dev/null +++ b/frame/referenda/src/benchmarking.rs @@ -0,0 +1,558 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2021 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. + +//! Democracy pallet benchmarking. + +use super::*; +use crate::Pallet as Referenda; +use assert_matches::assert_matches; +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelist_account}; +use frame_support::{ + assert_ok, + dispatch::UnfilteredDispatchable, + traits::{Bounded, Currency, EnsureOrigin}, +}; +use frame_system::RawOrigin; +use sp_runtime::traits::Bounded as ArithBounded; + +const SEED: u32 = 0; + +#[allow(dead_code)] +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn funded_account, I: 'static>(name: &'static str, index: u32) -> T::AccountId { + let caller: T::AccountId = account(name, index, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + caller +} + +fn dummy_call, I: 'static>() -> Bounded<>::RuntimeCall> { + let inner = frame_system::Call::remark { remark: vec![] }; + let call = >::RuntimeCall::from(inner); + T::Preimages::bound(call).unwrap() +} + +fn create_referendum, I: 'static>() -> (T::RuntimeOrigin, ReferendumIndex) { + let origin: T::RuntimeOrigin = T::SubmitOrigin::successful_origin(); + if let Ok(caller) = frame_system::ensure_signed(origin.clone()) { + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + whitelist_account!(caller); + } + + let proposal_origin = Box::new(RawOrigin::Root.into()); + let proposal = dummy_call::(); + let enactment_moment = DispatchTime::After(0u32.into()); + let call = crate::Call::::submit { proposal_origin, proposal, enactment_moment }; + assert_ok!(call.dispatch_bypass_filter(origin.clone())); + let index = ReferendumCount::::get() - 1; + (origin, index) +} + +fn place_deposit, I: 'static>(index: ReferendumIndex) { + let caller = funded_account::("caller", 0); + whitelist_account!(caller); + assert_ok!(Referenda::::place_decision_deposit(RawOrigin::Signed(caller).into(), index)); +} + +fn nudge, I: 'static>(index: ReferendumIndex) { + assert_ok!(Referenda::::nudge_referendum(RawOrigin::Root.into(), index)); +} + +fn fill_queue, I: 'static>( + index: ReferendumIndex, + spaces: u32, + pass_after: u32, +) -> Vec { + // First, create enough other referendums to fill the track. + let mut others = vec![]; + for _ in 0..info::(index).max_deciding { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + others.push(index); + } + + // We will also need enough referenda which are queued and passing, we want `MaxQueued - 1` + // in order to force the maximum amount of work to insert ours into the queue. + for _ in spaces..T::MaxQueued::get() { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + make_passing_after::(index, Perbill::from_percent(pass_after)); + others.push(index); + } + + // Skip to when they can start being decided. + skip_prepare_period::(index); + + // Manually nudge the other referenda first to ensure that they begin. + others.iter().for_each(|&i| nudge::(i)); + + others +} + +fn info, I: 'static>(index: ReferendumIndex) -> &'static TrackInfoOf { + let status = Referenda::::ensure_ongoing(index).unwrap(); + T::Tracks::info(status.track).expect("Id value returned from T::Tracks") +} + +fn make_passing_after, I: 'static>(index: ReferendumIndex, period_portion: Perbill) { + // We add an extra 1 percent to handle any perbill rounding errors which may cause + // a proposal to not actually pass. + let support = info::(index) + .min_support + .threshold(period_portion) + .saturating_add(Perbill::from_percent(1)); + let approval = info::(index) + .min_approval + .threshold(period_portion) + .saturating_add(Perbill::from_percent(1)); + Referenda::::access_poll(index, |status| { + if let PollStatus::Ongoing(tally, class) = status { + T::Tally::setup(class, Perbill::from_rational(1u32, 1000u32)); + *tally = T::Tally::from_requirements(support, approval, class); + } + }); +} + +fn make_passing, I: 'static>(index: ReferendumIndex) { + Referenda::::access_poll(index, |status| { + if let PollStatus::Ongoing(tally, class) = status { + T::Tally::setup(class, Perbill::from_rational(1u32, 1000u32)); + *tally = T::Tally::unanimity(class); + } + }); +} + +fn make_failing, I: 'static>(index: ReferendumIndex) { + Referenda::::access_poll(index, |status| { + if let PollStatus::Ongoing(tally, class) = status { + T::Tally::setup(class, Perbill::from_rational(1u32, 1000u32)); + *tally = T::Tally::rejection(class); + } + }); +} + +fn skip_prepare_period, I: 'static>(index: ReferendumIndex) { + let status = Referenda::::ensure_ongoing(index).unwrap(); + let prepare_period_over = status.submitted + info::(index).prepare_period; + frame_system::Pallet::::set_block_number(prepare_period_over); +} + +fn skip_decision_period, I: 'static>(index: ReferendumIndex) { + let status = Referenda::::ensure_ongoing(index).unwrap(); + let decision_period_over = status.deciding.unwrap().since + info::(index).decision_period; + frame_system::Pallet::::set_block_number(decision_period_over); +} + +fn skip_confirm_period, I: 'static>(index: ReferendumIndex) { + let status = Referenda::::ensure_ongoing(index).unwrap(); + let confirm_period_over = status.deciding.unwrap().confirming.unwrap(); + frame_system::Pallet::::set_block_number(confirm_period_over); +} + +fn skip_timeout_period, I: 'static>(index: ReferendumIndex) { + let status = Referenda::::ensure_ongoing(index).unwrap(); + let timeout_period_over = status.submitted + T::UndecidingTimeout::get(); + frame_system::Pallet::::set_block_number(timeout_period_over); +} + +fn alarm_time, I: 'static>(index: ReferendumIndex) -> T::BlockNumber { + let status = Referenda::::ensure_ongoing(index).unwrap(); + status.alarm.unwrap().0 +} + +fn is_confirming, I: 'static>(index: ReferendumIndex) -> bool { + let status = Referenda::::ensure_ongoing(index).unwrap(); + matches!( + status, + ReferendumStatus { deciding: Some(DecidingStatus { confirming: Some(_), .. }), .. } + ) +} + +fn is_not_confirming, I: 'static>(index: ReferendumIndex) -> bool { + let status = Referenda::::ensure_ongoing(index).unwrap(); + matches!( + status, + ReferendumStatus { deciding: Some(DecidingStatus { confirming: None, .. }), .. } + ) +} + +benchmarks_instance_pallet! { + submit { + let origin: T::RuntimeOrigin = T::SubmitOrigin::successful_origin(); + if let Ok(caller) = frame_system::ensure_signed(origin.clone()) { + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + whitelist_account!(caller); + } + }: _( + origin, + Box::new(RawOrigin::Root.into()), + dummy_call::(), + DispatchTime::After(0u32.into()) + ) verify { + let index = ReferendumCount::::get().checked_sub(1).unwrap(); + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Ongoing(_))); + } + + place_decision_deposit_preparing { + let (origin, index) = create_referendum::(); + }: place_decision_deposit(origin, index) + verify { + assert!(Referenda::::ensure_ongoing(index).unwrap().decision_deposit.is_some()); + } + + place_decision_deposit_queued { + let (origin, index) = create_referendum::(); + fill_queue::(index, 1, 90); + }: place_decision_deposit(origin, index) + verify { + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert!(TrackQueue::::get(&track).contains(&(index, 0u32.into()))); + } + + place_decision_deposit_not_queued { + let (origin, index) = create_referendum::(); + fill_queue::(index, 0, 90); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); + }: place_decision_deposit(origin, index) + verify { + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); + } + + place_decision_deposit_passing { + let (origin, index) = create_referendum::(); + skip_prepare_period::(index); + make_passing::(index); + }: place_decision_deposit(origin, index) + verify { + assert!(is_confirming::(index)); + } + + place_decision_deposit_failing { + let (origin, index) = create_referendum::(); + skip_prepare_period::(index); + }: place_decision_deposit(origin, index) + verify { + assert!(is_not_confirming::(index)); + } + + refund_decision_deposit { + let (origin, index) = create_referendum::(); + place_deposit::(index); + assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); + }: _(origin, index) + verify { + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, _, None))); + } + + refund_submission_deposit { + let (origin, index) = create_referendum::(); + let caller = frame_system::ensure_signed(origin.clone()).unwrap(); + let balance = T::Currency::free_balance(&caller); + assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, Some(_), _))); + }: _(origin, index) + verify { + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, None, _))); + let new_balance = T::Currency::free_balance(&caller); + // the deposit is zero or make sure it was unreserved. + assert!(T::SubmissionDeposit::get().is_zero() || new_balance > balance); + } + + cancel { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + }: _(T::CancelOrigin::successful_origin(), index) + verify { + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(..))); + } + + kill { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + }: _(T::KillOrigin::successful_origin(), index) + verify { + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Killed(..))); + } + + one_fewer_deciding_queue_empty { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + nudge::(index); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); + assert_eq!(DecidingCount::::get(&track), 1); + }: one_fewer_deciding(RawOrigin::Root, track) + verify { + assert_eq!(DecidingCount::::get(&track), 0); + } + + one_fewer_deciding_failing { + let (_origin, index) = create_referendum::(); + // No spaces free in the queue. + let queued = fill_queue::(index, 0, 90); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), queued[0])); + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + let deciding_count = DecidingCount::::get(&track); + }: one_fewer_deciding(RawOrigin::Root, track) + verify { + assert_eq!(DecidingCount::::get(&track), deciding_count); + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); + assert!(queued.into_iter().skip(1).all(|i| Referenda::::ensure_ongoing(i) + .unwrap() + .deciding + .map_or(true, |d| d.confirming.is_none()) + )); + } + + one_fewer_deciding_passing { + let (_origin, index) = create_referendum::(); + // No spaces free in the queue. + let queued = fill_queue::(index, 0, 0); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), queued[0])); + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + let deciding_count = DecidingCount::::get(&track); + }: one_fewer_deciding(RawOrigin::Root, track) + verify { + assert_eq!(DecidingCount::::get(&track), deciding_count); + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); + assert!(queued.into_iter().skip(1).filter(|i| Referenda::::ensure_ongoing(*i) + .unwrap() + .deciding + .map_or(false, |d| d.confirming.is_some()) + ).count() == 1); + } + + nudge_referendum_requeued_insertion { + // First create our referendum and place the deposit. It will be failing. + let (_origin, index) = create_referendum::(); + place_deposit::(index); + fill_queue::(index, 0, 90); + + // Now nudge ours, with the track now full and the queue full of referenda with votes, + // ours will not be in the queue. + nudge::(index); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); + + // Now alter the voting, so that ours goes into pole-position and shifts others down. + make_passing::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let t = TrackQueue::::get(&track); + assert_eq!(t.len() as u32, T::MaxQueued::get()); + assert_eq!(t[t.len() - 1].0, index); + } + + nudge_referendum_requeued_slide { + // First create our referendum and place the deposit. It will be failing. + let (_origin, index) = create_referendum::(); + place_deposit::(index); + fill_queue::(index, 1, 90); + + // Now nudge ours, with the track now full, ours will be queued, but with no votes, it + // will have the worst position. + nudge::(index); + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); + + // Now alter the voting, so that ours leap-frogs all into the best position. + make_passing::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let t = TrackQueue::::get(&track); + assert_eq!(t.len() as u32, T::MaxQueued::get()); + assert_eq!(t[t.len() - 1].0, index); + } + + nudge_referendum_queued { + // NOTE: worst possible queue situation is with a queue full of passing refs with one slot + // free and this failing. It would result in `QUEUE_SIZE - 1` items being shifted for the + // insertion at the beginning. + + // First create our referendum and place the deposit. It will be failing. + let (_origin, index) = create_referendum::(); + place_deposit::(index); + fill_queue::(index, 1, 0); + + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); + assert!(TrackQueue::::get(&track).into_iter().all(|(_, v)| v > 0u32.into())); + + // Then nudge ours, with the track now full, ours will be queued. + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); + } + + nudge_referendum_not_queued { + // First create our referendum and place the deposit. It will be failing. + let (_origin, index) = create_referendum::(); + place_deposit::(index); + fill_queue::(index, 0, 0); + + let track = Referenda::::ensure_ongoing(index).unwrap().track; + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert!(TrackQueue::::get(&track).into_iter().all(|(_, v)| v > 0u32.into())); + + // Then nudge ours, with the track now full, ours will be queued. + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); + assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); + } + + nudge_referendum_no_deposit { + let (_origin, index) = create_referendum::(); + skip_prepare_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let status = Referenda::::ensure_ongoing(index).unwrap(); + assert_matches!(status, ReferendumStatus { deciding: None, .. }); + } + + nudge_referendum_preparing { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let status = Referenda::::ensure_ongoing(index).unwrap(); + assert_matches!(status, ReferendumStatus { deciding: None, .. }); + } + + nudge_referendum_timed_out { + let (_origin, index) = create_referendum::(); + skip_timeout_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let info = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(info, ReferendumInfo::TimedOut(..)); + } + + nudge_referendum_begin_deciding_failing { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert!(is_not_confirming::(index)); + } + + nudge_referendum_begin_deciding_passing { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + make_passing::(index); + skip_prepare_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert!(is_confirming::(index)); + } + + nudge_referendum_begin_confirming { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + nudge::(index); + assert!(!is_confirming::(index)); + make_passing::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert!(is_confirming::(index)); + } + + nudge_referendum_end_confirming { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + make_passing::(index); + nudge::(index); + assert!(is_confirming::(index)); + make_failing::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert!(!is_confirming::(index)); + } + + nudge_referendum_continue_not_confirming { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + nudge::(index); + assert!(!is_confirming::(index)); + let old_alarm = alarm_time::(index); + make_passing_after::(index, Perbill::from_percent(50)); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert_ne!(old_alarm, alarm_time::(index)); + assert!(!is_confirming::(index)); + } + + nudge_referendum_continue_confirming { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + make_passing::(index); + skip_prepare_period::(index); + nudge::(index); + assert!(is_confirming::(index)); + let old_alarm = alarm_time::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + assert!(is_confirming::(index)); + } + + nudge_referendum_approved { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + make_passing::(index); + nudge::(index); + skip_confirm_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let info = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(info, ReferendumInfo::Approved(..)); + } + + nudge_referendum_rejected { + let (_origin, index) = create_referendum::(); + place_deposit::(index); + skip_prepare_period::(index); + make_failing::(index); + nudge::(index); + skip_decision_period::(index); + }: nudge_referendum(RawOrigin::Root, index) + verify { + let info = ReferendumInfoFor::::get(index).unwrap(); + assert_matches!(info, ReferendumInfo::Rejected(..)); + } + + impl_benchmark_test_suite!( + Referenda, + crate::mock::new_test_ext(), + crate::mock::Test + ); +} diff --git a/frame/referenda/src/branch.rs b/frame/referenda/src/branch.rs new file mode 100644 index 0000000000000..d3744979fc547 --- /dev/null +++ b/frame/referenda/src/branch.rs @@ -0,0 +1,180 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Helpers for managing the different weights in various algorithmic branches. + +use super::Config; +use crate::weights::WeightInfo; +use frame_support::weights::Weight; + +/// Branches within the `begin_deciding` function. +pub enum BeginDecidingBranch { + Passing, + Failing, +} + +/// Branches within the `service_referendum` function. +pub enum ServiceBranch { + Fail, + NoDeposit, + Preparing, + Queued, + NotQueued, + RequeuedInsertion, + RequeuedSlide, + BeginDecidingPassing, + BeginDecidingFailing, + BeginConfirming, + ContinueConfirming, + EndConfirming, + ContinueNotConfirming, + Approved, + Rejected, + TimedOut, +} + +impl From for ServiceBranch { + fn from(x: BeginDecidingBranch) -> Self { + use BeginDecidingBranch::*; + use ServiceBranch::*; + match x { + Passing => BeginDecidingPassing, + Failing => BeginDecidingFailing, + } + } +} + +impl ServiceBranch { + /// Return the weight of the `nudge` function when it takes the branch denoted by `self`. + pub fn weight_of_nudge, I: 'static>(self) -> frame_support::weights::Weight { + use ServiceBranch::*; + match self { + NoDeposit => T::WeightInfo::nudge_referendum_no_deposit(), + Preparing => T::WeightInfo::nudge_referendum_preparing(), + Queued => T::WeightInfo::nudge_referendum_queued(), + NotQueued => T::WeightInfo::nudge_referendum_not_queued(), + RequeuedInsertion => T::WeightInfo::nudge_referendum_requeued_insertion(), + RequeuedSlide => T::WeightInfo::nudge_referendum_requeued_slide(), + BeginDecidingPassing => T::WeightInfo::nudge_referendum_begin_deciding_passing(), + BeginDecidingFailing => T::WeightInfo::nudge_referendum_begin_deciding_failing(), + BeginConfirming => T::WeightInfo::nudge_referendum_begin_confirming(), + ContinueConfirming => T::WeightInfo::nudge_referendum_continue_confirming(), + EndConfirming => T::WeightInfo::nudge_referendum_end_confirming(), + ContinueNotConfirming => T::WeightInfo::nudge_referendum_continue_not_confirming(), + Approved => T::WeightInfo::nudge_referendum_approved(), + Rejected => T::WeightInfo::nudge_referendum_rejected(), + TimedOut | Fail => T::WeightInfo::nudge_referendum_timed_out(), + } + } + + /// Return the maximum possible weight of the `nudge` function. + pub fn max_weight_of_nudge, I: 'static>() -> frame_support::weights::Weight { + Weight::zero() + .max(T::WeightInfo::nudge_referendum_no_deposit()) + .max(T::WeightInfo::nudge_referendum_preparing()) + .max(T::WeightInfo::nudge_referendum_queued()) + .max(T::WeightInfo::nudge_referendum_not_queued()) + .max(T::WeightInfo::nudge_referendum_requeued_insertion()) + .max(T::WeightInfo::nudge_referendum_requeued_slide()) + .max(T::WeightInfo::nudge_referendum_begin_deciding_passing()) + .max(T::WeightInfo::nudge_referendum_begin_deciding_failing()) + .max(T::WeightInfo::nudge_referendum_begin_confirming()) + .max(T::WeightInfo::nudge_referendum_continue_confirming()) + .max(T::WeightInfo::nudge_referendum_end_confirming()) + .max(T::WeightInfo::nudge_referendum_continue_not_confirming()) + .max(T::WeightInfo::nudge_referendum_approved()) + .max(T::WeightInfo::nudge_referendum_rejected()) + .max(T::WeightInfo::nudge_referendum_timed_out()) + } + + /// Return the weight of the `place_decision_deposit` function when it takes the branch denoted + /// by `self`. + pub fn weight_of_deposit, I: 'static>( + self, + ) -> Option { + use ServiceBranch::*; + let ref_time_weight = match self { + Preparing => T::WeightInfo::place_decision_deposit_preparing(), + Queued => T::WeightInfo::place_decision_deposit_queued(), + NotQueued => T::WeightInfo::place_decision_deposit_not_queued(), + BeginDecidingPassing => T::WeightInfo::place_decision_deposit_passing(), + BeginDecidingFailing => T::WeightInfo::place_decision_deposit_failing(), + BeginConfirming | + ContinueConfirming | + EndConfirming | + ContinueNotConfirming | + Approved | + Rejected | + RequeuedInsertion | + RequeuedSlide | + TimedOut | + Fail | + NoDeposit => return None, + }; + + Some(ref_time_weight) + } + + /// Return the maximum possible weight of the `place_decision_deposit` function. + pub fn max_weight_of_deposit, I: 'static>() -> frame_support::weights::Weight { + Weight::zero() + .max(T::WeightInfo::place_decision_deposit_preparing()) + .max(T::WeightInfo::place_decision_deposit_queued()) + .max(T::WeightInfo::place_decision_deposit_not_queued()) + .max(T::WeightInfo::place_decision_deposit_passing()) + .max(T::WeightInfo::place_decision_deposit_failing()) + } +} + +/// Branches that the `one_fewer_deciding` function may take. +pub enum OneFewerDecidingBranch { + QueueEmpty, + BeginDecidingPassing, + BeginDecidingFailing, +} + +impl From for OneFewerDecidingBranch { + fn from(x: BeginDecidingBranch) -> Self { + use BeginDecidingBranch::*; + use OneFewerDecidingBranch::*; + match x { + Passing => BeginDecidingPassing, + Failing => BeginDecidingFailing, + } + } +} + +impl OneFewerDecidingBranch { + /// Return the weight of the `one_fewer_deciding` function when it takes the branch denoted + /// by `self`. + pub fn weight, I: 'static>(self) -> frame_support::weights::Weight { + use OneFewerDecidingBranch::*; + match self { + QueueEmpty => T::WeightInfo::one_fewer_deciding_queue_empty(), + BeginDecidingPassing => T::WeightInfo::one_fewer_deciding_passing(), + BeginDecidingFailing => T::WeightInfo::one_fewer_deciding_failing(), + } + } + + /// Return the maximum possible weight of the `one_fewer_deciding` function. + pub fn max_weight, I: 'static>() -> frame_support::weights::Weight { + Weight::zero() + .max(T::WeightInfo::one_fewer_deciding_queue_empty()) + .max(T::WeightInfo::one_fewer_deciding_passing()) + .max(T::WeightInfo::one_fewer_deciding_failing()) + } +} diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs new file mode 100644 index 0000000000000..0b846faf88558 --- /dev/null +++ b/frame/referenda/src/lib.rs @@ -0,0 +1,1207 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! # Referenda Pallet +//! +//! ## Overview +//! +//! A pallet for executing referenda. No voting logic is present here, and the `Polling` and +//! `PollStatus` traits are used to allow the voting logic (likely in a pallet) to be utilized. +//! +//! A referendum is a vote on whether a proposal should be dispatched from a particular origin. The +//! origin is used to determine which one of several _tracks_ that a referendum happens under. +//! Tracks each have their own configuration which governs the voting process and parameters. +//! +//! A referendum's lifecycle has three main stages: Preparation, deciding and conclusion. +//! Referenda are considered "ongoing" immediately after submission until their eventual +//! conclusion, and votes may be cast throughout. +//! +//! In order to progress from preparating to being decided, three things must be in place: +//! - There must have been a *Decision Deposit* placed, an amount determined by the track. Anyone +//! may place this deposit. +//! - A period must have elapsed since submission of the referendum. This period is known as the +//! *Preparation Period* and is determined by the track. +//! - The track must not already be at capacity with referendum being decided. The maximum number of +//! referenda which may be being decided simultaneously is determined by the track. +//! +//! In order to become concluded, one of three things must happen: +//! - The referendum should remain in an unbroken _Passing_ state for a period of time. This +//! is known as the _Confirmation Period_ and is determined by the track. A referendum is considered +//! _Passing_ when there is a sufficiently high support and approval, given the amount of time it +//! has been being decided. Generally the threshold for what counts as being "sufficiently high" +//! will reduce over time. The curves setting these thresholds are determined by the track. In this +//! case, the referendum is considered _Approved_ and the proposal is scheduled for dispatch. +//! - The referendum reaches the end of its deciding phase outside not _Passing_. It ends in +//! rejection and the proposal is not dispatched. +//! - The referendum is cancelled. +//! +//! A general time-out is also in place and referenda which exist in preparation for too long may +//! conclude without ever entering into a deciding stage. +//! +//! Once a referendum is concluded, the decision deposit may be refunded. +//! +//! ## Terms +//! - *Support*: The number of aye-votes, pre-conviction, as a proportion of the total number of +//! pre-conviction votes able to be cast in the population. +//! +//! - [`Config`] +//! - [`Call`] + +#![recursion_limit = "256"] +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Codec, Encode}; +use frame_support::{ + ensure, + traits::{ + schedule::{ + v3::{Anon as ScheduleAnon, Named as ScheduleNamed}, + DispatchTime, + }, + Currency, LockIdentifier, OnUnbalanced, OriginTrait, PollStatus, Polling, QueryPreimage, + ReservableCurrency, StorePreimage, VoteTally, + }, + BoundedVec, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, Dispatchable, One, Saturating, Zero}, + DispatchError, Perbill, +}; +use sp_std::{fmt::Debug, prelude::*}; + +mod branch; +pub mod migration; +mod types; +pub mod weights; + +use self::branch::{BeginDecidingBranch, OneFewerDecidingBranch, ServiceBranch}; +pub use self::{ + pallet::*, + types::{ + BalanceOf, BoundedCallOf, CallOf, Curve, DecidingStatus, DecidingStatusOf, Deposit, + InsertSorted, NegativeImbalanceOf, PalletsOriginOf, ReferendumIndex, ReferendumInfo, + ReferendumInfoOf, ReferendumStatus, ReferendumStatusOf, ScheduleAddressOf, TallyOf, + TrackIdOf, TrackInfo, TrackInfoOf, TracksInfo, VotesOf, + }, + weights::WeightInfo, +}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +pub use frame_support::traits::Get; +pub use sp_std::vec::Vec; + +#[macro_export] +macro_rules! impl_tracksinfo_get { + ($tracksinfo:ty, $balance:ty, $blocknumber:ty) => { + impl + $crate::Get< + $crate::Vec<( + <$tracksinfo as $crate::TracksInfo<$balance, $blocknumber>>::Id, + $crate::TrackInfo<$balance, $blocknumber>, + )>, + > for $tracksinfo + { + fn get() -> $crate::Vec<( + <$tracksinfo as $crate::TracksInfo<$balance, $blocknumber>>::Id, + $crate::TrackInfo<$balance, $blocknumber>, + )> { + <$tracksinfo as $crate::TracksInfo<$balance, $blocknumber>>::tracks().to_vec() + } + } + }; +} + +const ASSEMBLY_ID: LockIdentifier = *b"assembly"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + Sized { + // System level stuff. + type RuntimeCall: Parameter + + Dispatchable + + From> + + IsType<::RuntimeCall> + + From>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + /// The Scheduler. + type Scheduler: ScheduleAnon, PalletsOriginOf> + + ScheduleNamed, PalletsOriginOf>; + /// Currency type for this pallet. + type Currency: ReservableCurrency; + // Origins and unbalances. + /// Origin from which proposals may be submitted. + type SubmitOrigin: EnsureOrigin; + /// Origin from which any vote may be cancelled. + type CancelOrigin: EnsureOrigin; + /// Origin from which any vote may be killed. + type KillOrigin: EnsureOrigin; + /// Handler for the unbalanced reduction when slashing a preimage deposit. + type Slash: OnUnbalanced>; + /// The counting type for votes. Usually just balance. + type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member + MaxEncodedLen; + /// The tallying type. + type Tally: VoteTally> + + Clone + + Codec + + Eq + + Debug + + TypeInfo + + MaxEncodedLen; + + // Constants + /// The minimum amount to be used as a deposit for a public referendum proposal. + #[pallet::constant] + type SubmissionDeposit: Get>; + + /// Maximum size of the referendum queue for a single track. + #[pallet::constant] + type MaxQueued: Get; + + /// The number of blocks after submission that a referendum must begin being decided by. + /// Once this passes, then anyone may cancel the referendum. + #[pallet::constant] + type UndecidingTimeout: Get; + + /// Quantization level for the referendum wakeup scheduler. A higher number will result in + /// fewer storage reads/writes needed for smaller voters, but also result in delays to the + /// automatic referendum status changes. Explicit servicing instructions are unaffected. + #[pallet::constant] + type AlarmInterval: Get; + + // The other stuff. + /// Information concerning the different referendum tracks. + #[pallet::constant] + type Tracks: Get< + Vec<( + , Self::BlockNumber>>::Id, + TrackInfo, Self::BlockNumber>, + )>, + > + TracksInfo< + BalanceOf, + Self::BlockNumber, + RuntimeOrigin = ::PalletsOrigin, + >; + + /// The preimage provider. + type Preimages: QueryPreimage + StorePreimage; + } + + /// The next free referendum index, aka the number of referenda started so far. + #[pallet::storage] + pub type ReferendumCount = StorageValue<_, ReferendumIndex, ValueQuery>; + + /// Information concerning any given referendum. + #[pallet::storage] + pub type ReferendumInfoFor, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf>; + + /// The sorted list of referenda ready to be decided but not yet being decided, ordered by + /// conviction-weighted approvals. + /// + /// This should be empty if `DecidingCount` is less than `TrackInfo::max_deciding`. + #[pallet::storage] + pub type TrackQueue, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + TrackIdOf, + BoundedVec<(ReferendumIndex, T::Votes), T::MaxQueued>, + ValueQuery, + >; + + /// The number of referenda being decided currently. + #[pallet::storage] + pub type DecidingCount, I: 'static = ()> = + StorageMap<_, Twox64Concat, TrackIdOf, u32, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// A referendum has been submitted. + Submitted { + /// Index of the referendum. + index: ReferendumIndex, + /// The track (and by extension proposal dispatch origin) of this referendum. + track: TrackIdOf, + /// The proposal for the referendum. + proposal: BoundedCallOf, + }, + /// The decision deposit has been placed. + DecisionDepositPlaced { + /// Index of the referendum. + index: ReferendumIndex, + /// The account who placed the deposit. + who: T::AccountId, + /// The amount placed by the account. + amount: BalanceOf, + }, + /// The decision deposit has been refunded. + DecisionDepositRefunded { + /// Index of the referendum. + index: ReferendumIndex, + /// The account who placed the deposit. + who: T::AccountId, + /// The amount placed by the account. + amount: BalanceOf, + }, + /// A deposit has been slashaed. + DepositSlashed { + /// The account who placed the deposit. + who: T::AccountId, + /// The amount placed by the account. + amount: BalanceOf, + }, + /// A referendum has moved into the deciding phase. + DecisionStarted { + /// Index of the referendum. + index: ReferendumIndex, + /// The track (and by extension proposal dispatch origin) of this referendum. + track: TrackIdOf, + /// The proposal for the referendum. + proposal: BoundedCallOf, + /// The current tally of votes in this referendum. + tally: T::Tally, + }, + ConfirmStarted { + /// Index of the referendum. + index: ReferendumIndex, + }, + ConfirmAborted { + /// Index of the referendum. + index: ReferendumIndex, + }, + /// A referendum has ended its confirmation phase and is ready for approval. + Confirmed { + /// Index of the referendum. + index: ReferendumIndex, + /// The final tally of votes in this referendum. + tally: T::Tally, + }, + /// A referendum has been approved and its proposal has been scheduled. + Approved { + /// Index of the referendum. + index: ReferendumIndex, + }, + /// A proposal has been rejected by referendum. + Rejected { + /// Index of the referendum. + index: ReferendumIndex, + /// The final tally of votes in this referendum. + tally: T::Tally, + }, + /// A referendum has been timed out without being decided. + TimedOut { + /// Index of the referendum. + index: ReferendumIndex, + /// The final tally of votes in this referendum. + tally: T::Tally, + }, + /// A referendum has been cancelled. + Cancelled { + /// Index of the referendum. + index: ReferendumIndex, + /// The final tally of votes in this referendum. + tally: T::Tally, + }, + /// A referendum has been killed. + Killed { + /// Index of the referendum. + index: ReferendumIndex, + /// The final tally of votes in this referendum. + tally: T::Tally, + }, + /// The submission deposit has been refunded. + SubmissionDepositRefunded { + /// Index of the referendum. + index: ReferendumIndex, + /// The account who placed the deposit. + who: T::AccountId, + /// The amount placed by the account. + amount: BalanceOf, + }, + } + + #[pallet::error] + pub enum Error { + /// Referendum is not ongoing. + NotOngoing, + /// Referendum's decision deposit is already paid. + HasDeposit, + /// The track identifier given was invalid. + BadTrack, + /// There are already a full complement of referenda in progress for this track. + Full, + /// The queue of the track is empty. + QueueEmpty, + /// The referendum index provided is invalid in this context. + BadReferendum, + /// There was nothing to do in the advancement. + NothingToDo, + /// No track exists for the proposal origin. + NoTrack, + /// Any deposit cannot be refunded until after the decision is over. + Unfinished, + /// The deposit refunder is not the depositor. + NoPermission, + /// The deposit cannot be refunded since none was made. + NoDeposit, + /// The referendum status is invalid for this operation. + BadStatus, + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Propose a referendum on a privileged action. + /// + /// - `origin`: must be `SubmitOrigin` and the account must have `SubmissionDeposit` funds + /// available. + /// - `proposal_origin`: The origin from which the proposal should be executed. + /// - `proposal`: The proposal. + /// - `enactment_moment`: The moment that the proposal should be enacted. + /// + /// Emits `Submitted`. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::submit())] + pub fn submit( + origin: OriginFor, + proposal_origin: Box>, + proposal: BoundedCallOf, + enactment_moment: DispatchTime, + ) -> DispatchResult { + let who = T::SubmitOrigin::ensure_origin(origin)?; + + let track = + T::Tracks::track_for(&proposal_origin).map_err(|_| Error::::NoTrack)?; + let submission_deposit = Self::take_deposit(who, T::SubmissionDeposit::get())?; + let index = ReferendumCount::::mutate(|x| { + let r = *x; + *x += 1; + r + }); + let now = frame_system::Pallet::::block_number(); + let nudge_call = + T::Preimages::bound(CallOf::::from(Call::nudge_referendum { index }))?; + let status = ReferendumStatus { + track, + origin: *proposal_origin, + proposal: proposal.clone(), + enactment: enactment_moment, + submitted: now, + submission_deposit, + decision_deposit: None, + deciding: None, + tally: TallyOf::::new(track), + in_queue: false, + alarm: Self::set_alarm(nudge_call, now.saturating_add(T::UndecidingTimeout::get())), + }; + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + + Self::deposit_event(Event::::Submitted { index, track, proposal }); + Ok(()) + } + + /// Post the Decision Deposit for a referendum. + /// + /// - `origin`: must be `Signed` and the account must have funds available for the + /// referendum's track's Decision Deposit. + /// - `index`: The index of the submitted referendum whose Decision Deposit is yet to be + /// posted. + /// + /// Emits `DecisionDepositPlaced`. + #[pallet::call_index(1)] + #[pallet::weight(ServiceBranch::max_weight_of_deposit::())] + pub fn place_decision_deposit( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let mut status = Self::ensure_ongoing(index)?; + ensure!(status.decision_deposit.is_none(), Error::::HasDeposit); + let track = Self::track(status.track).ok_or(Error::::NoTrack)?; + status.decision_deposit = + Some(Self::take_deposit(who.clone(), track.decision_deposit)?); + let now = frame_system::Pallet::::block_number(); + let (info, _, branch) = Self::service_referendum(now, index, status); + ReferendumInfoFor::::insert(index, info); + let e = + Event::::DecisionDepositPlaced { index, who, amount: track.decision_deposit }; + Self::deposit_event(e); + Ok(branch.weight_of_deposit::().into()) + } + + /// Refund the Decision Deposit for a closed referendum back to the depositor. + /// + /// - `origin`: must be `Signed` or `Root`. + /// - `index`: The index of a closed referendum whose Decision Deposit has not yet been + /// refunded. + /// + /// Emits `DecisionDepositRefunded`. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::refund_decision_deposit())] + pub fn refund_decision_deposit( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResult { + ensure_signed_or_root(origin)?; + let mut info = + ReferendumInfoFor::::get(index).ok_or(Error::::BadReferendum)?; + let deposit = info + .take_decision_deposit() + .map_err(|_| Error::::Unfinished)? + .ok_or(Error::::NoDeposit)?; + Self::refund_deposit(Some(deposit.clone())); + ReferendumInfoFor::::insert(index, info); + let e = Event::::DecisionDepositRefunded { + index, + who: deposit.who, + amount: deposit.amount, + }; + Self::deposit_event(e); + Ok(()) + } + + /// Cancel an ongoing referendum. + /// + /// - `origin`: must be the `CancelOrigin`. + /// - `index`: The index of the referendum to be cancelled. + /// + /// Emits `Cancelled`. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::cancel())] + pub fn cancel(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { + T::CancelOrigin::ensure_origin(origin)?; + let status = Self::ensure_ongoing(index)?; + if let Some((_, last_alarm)) = status.alarm { + let _ = T::Scheduler::cancel(last_alarm); + } + Self::note_one_fewer_deciding(status.track); + Self::deposit_event(Event::::Cancelled { index, tally: status.tally }); + let info = ReferendumInfo::Cancelled( + frame_system::Pallet::::block_number(), + Some(status.submission_deposit), + status.decision_deposit, + ); + ReferendumInfoFor::::insert(index, info); + Ok(()) + } + + /// Cancel an ongoing referendum and slash the deposits. + /// + /// - `origin`: must be the `KillOrigin`. + /// - `index`: The index of the referendum to be cancelled. + /// + /// Emits `Killed` and `DepositSlashed`. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::kill())] + pub fn kill(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { + T::KillOrigin::ensure_origin(origin)?; + let status = Self::ensure_ongoing(index)?; + if let Some((_, last_alarm)) = status.alarm { + let _ = T::Scheduler::cancel(last_alarm); + } + Self::note_one_fewer_deciding(status.track); + Self::deposit_event(Event::::Killed { index, tally: status.tally }); + Self::slash_deposit(Some(status.submission_deposit.clone())); + Self::slash_deposit(status.decision_deposit.clone()); + let info = ReferendumInfo::Killed(frame_system::Pallet::::block_number()); + ReferendumInfoFor::::insert(index, info); + Ok(()) + } + + /// Advance a referendum onto its next logical state. Only used internally. + /// + /// - `origin`: must be `Root`. + /// - `index`: the referendum to be advanced. + #[pallet::call_index(5)] + #[pallet::weight(ServiceBranch::max_weight_of_nudge::())] + pub fn nudge_referendum( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + let now = frame_system::Pallet::::block_number(); + let mut status = Self::ensure_ongoing(index)?; + // This is our wake-up, so we can disregard the alarm. + status.alarm = None; + let (info, dirty, branch) = Self::service_referendum(now, index, status); + if dirty { + ReferendumInfoFor::::insert(index, info); + } + Ok(Some(branch.weight_of_nudge::()).into()) + } + + /// Advance a track onto its next logical state. Only used internally. + /// + /// - `origin`: must be `Root`. + /// - `track`: the track to be advanced. + /// + /// Action item for when there is now one fewer referendum in the deciding phase and the + /// `DecidingCount` is not yet updated. This means that we should either: + /// - begin deciding another referendum (and leave `DecidingCount` alone); or + /// - decrement `DecidingCount`. + #[pallet::call_index(6)] + #[pallet::weight(OneFewerDecidingBranch::max_weight::())] + pub fn one_fewer_deciding( + origin: OriginFor, + track: TrackIdOf, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + let track_info = T::Tracks::info(track).ok_or(Error::::BadTrack)?; + let mut track_queue = TrackQueue::::get(track); + let branch = + if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { + let now = frame_system::Pallet::::block_number(); + let (maybe_alarm, branch) = + Self::begin_deciding(&mut status, index, now, track_info); + if let Some(set_alarm) = maybe_alarm { + Self::ensure_alarm_at(&mut status, index, set_alarm); + } + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + TrackQueue::::insert(track, track_queue); + branch.into() + } else { + DecidingCount::::mutate(track, |x| x.saturating_dec()); + OneFewerDecidingBranch::QueueEmpty + }; + Ok(Some(branch.weight::()).into()) + } + + /// Refund the Submission Deposit for a closed referendum back to the depositor. + /// + /// - `origin`: must be `Signed` or `Root`. + /// - `index`: The index of a closed referendum whose Submission Deposit has not yet been + /// refunded. + /// + /// Emits `SubmissionDepositRefunded`. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::refund_submission_deposit())] + pub fn refund_submission_deposit( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResult { + ensure_signed_or_root(origin)?; + let mut info = + ReferendumInfoFor::::get(index).ok_or(Error::::BadReferendum)?; + let deposit = info + .take_submission_deposit() + .map_err(|_| Error::::BadStatus)? + .ok_or(Error::::NoDeposit)?; + Self::refund_deposit(Some(deposit.clone())); + ReferendumInfoFor::::insert(index, info); + let e = Event::::SubmissionDepositRefunded { + index, + who: deposit.who, + amount: deposit.amount, + }; + Self::deposit_event(e); + Ok(()) + } + } +} + +impl, I: 'static> Polling for Pallet { + type Index = ReferendumIndex; + type Votes = VotesOf; + type Moment = T::BlockNumber; + type Class = TrackIdOf; + + fn classes() -> Vec { + T::Tracks::tracks().iter().map(|x| x.0).collect() + } + + fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>) -> R, + ) -> R { + match ReferendumInfoFor::::get(index) { + Some(ReferendumInfo::Ongoing(mut status)) => { + let result = f(PollStatus::Ongoing(&mut status.tally, status.track)); + let now = frame_system::Pallet::::block_number(); + Self::ensure_alarm_at(&mut status, index, now + One::one()); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + result + }, + Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), + Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)), + _ => f(PollStatus::None), + } + } + + fn try_access_poll( + index: Self::Index, + f: impl FnOnce( + PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>, + ) -> Result, + ) -> Result { + match ReferendumInfoFor::::get(index) { + Some(ReferendumInfo::Ongoing(mut status)) => { + let result = f(PollStatus::Ongoing(&mut status.tally, status.track))?; + let now = frame_system::Pallet::::block_number(); + Self::ensure_alarm_at(&mut status, index, now + One::one()); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + Ok(result) + }, + Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), + Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)), + _ => f(PollStatus::None), + } + } + + fn as_ongoing(index: Self::Index) -> Option<(T::Tally, TrackIdOf)> { + Self::ensure_ongoing(index).ok().map(|x| (x.tally, x.track)) + } + + #[cfg(feature = "runtime-benchmarks")] + fn create_ongoing(class: Self::Class) -> Result { + let index = ReferendumCount::::mutate(|x| { + let r = *x; + *x += 1; + r + }); + let now = frame_system::Pallet::::block_number(); + let dummy_account_id = + codec::Decode::decode(&mut sp_runtime::traits::TrailingZeroInput::new(&b"dummy"[..])) + .expect("infinite length input; no invalid inputs for type; qed"); + let mut status = ReferendumStatusOf:: { + track: class, + origin: frame_support::dispatch::RawOrigin::Root.into(), + proposal: T::Preimages::bound(CallOf::::from(Call::nudge_referendum { index })) + .map_err(|_| ())?, + enactment: DispatchTime::After(Zero::zero()), + submitted: now, + submission_deposit: Deposit { who: dummy_account_id, amount: Zero::zero() }, + decision_deposit: None, + deciding: None, + tally: TallyOf::::new(class), + in_queue: false, + alarm: None, + }; + Self::ensure_alarm_at(&mut status, index, sp_runtime::traits::Bounded::max_value()); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + Ok(index) + } + + #[cfg(feature = "runtime-benchmarks")] + fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> { + let mut status = Self::ensure_ongoing(index).map_err(|_| ())?; + Self::ensure_no_alarm(&mut status); + Self::note_one_fewer_deciding(status.track); + let now = frame_system::Pallet::::block_number(); + let info = if approved { + ReferendumInfo::Approved(now, Some(status.submission_deposit), status.decision_deposit) + } else { + ReferendumInfo::Rejected(now, Some(status.submission_deposit), status.decision_deposit) + }; + ReferendumInfoFor::::insert(index, info); + Ok(()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn max_ongoing() -> (Self::Class, u32) { + let r = T::Tracks::tracks() + .iter() + .max_by_key(|(_, info)| info.max_deciding) + .expect("Always one class"); + (r.0, r.1.max_deciding) + } +} + +impl, I: 'static> Pallet { + /// Check that referendum `index` is in the `Ongoing` state and return the `ReferendumStatus` + /// value, or `Err` otherwise. + pub fn ensure_ongoing( + index: ReferendumIndex, + ) -> Result, DispatchError> { + match ReferendumInfoFor::::get(index) { + Some(ReferendumInfo::Ongoing(status)) => Ok(status), + _ => Err(Error::::NotOngoing.into()), + } + } + + /// Returns whether the referendum is passing. + /// Referendum must be ongoing and its track must exist. + pub fn is_referendum_passing(ref_index: ReferendumIndex) -> Result { + let info = ReferendumInfoFor::::get(ref_index).ok_or(Error::::BadReferendum)?; + match info { + ReferendumInfo::Ongoing(status) => { + let track = Self::track(status.track).ok_or(Error::::NoTrack)?; + let elapsed = if let Some(deciding) = status.deciding { + frame_system::Pallet::::block_number().saturating_sub(deciding.since) + } else { + Zero::zero() + }; + Ok(Self::is_passing( + &status.tally, + elapsed, + track.decision_period, + &track.min_support, + &track.min_approval, + status.track, + )) + }, + _ => Err(Error::::NotOngoing.into()), + } + } + + // Enqueue a proposal from a referendum which has presumably passed. + fn schedule_enactment( + index: ReferendumIndex, + track: &TrackInfoOf, + desired: DispatchTime, + origin: PalletsOriginOf, + call: BoundedCallOf, + ) { + let now = frame_system::Pallet::::block_number(); + let earliest_allowed = now.saturating_add(track.min_enactment_period); + let desired = desired.evaluate(now); + let ok = T::Scheduler::schedule_named( + (ASSEMBLY_ID, "enactment", index).using_encoded(sp_io::hashing::blake2_256), + DispatchTime::At(desired.max(earliest_allowed)), + None, + 63, + origin, + call, + ) + .is_ok(); + debug_assert!(ok, "LOGIC ERROR: bake_referendum/schedule_named failed"); + } + + /// Set an alarm to dispatch `call` at block number `when`. + fn set_alarm( + call: BoundedCallOf, + when: T::BlockNumber, + ) -> Option<(T::BlockNumber, ScheduleAddressOf)> { + let alarm_interval = T::AlarmInterval::get().max(One::one()); + // Alarm must go off no earlier than `when`. + // This rounds `when` upwards to the next multiple of `alarm_interval`. + let when = (when.saturating_add(alarm_interval.saturating_sub(One::one())) / + alarm_interval) + .saturating_mul(alarm_interval); + let maybe_result = T::Scheduler::schedule( + DispatchTime::At(when), + None, + 128u8, + frame_system::RawOrigin::Root.into(), + call, + ) + .ok() + .map(|x| (when, x)); + debug_assert!( + maybe_result.is_some(), + "Unable to schedule a new alarm at #{:?} (now: #{:?})?!", + when, + frame_system::Pallet::::block_number() + ); + maybe_result + } + + /// Mutate a referendum's `status` into the correct deciding state. + /// + /// - `now` is the current block number. + /// - `track` is the track info for the referendum. + /// + /// This will properly set up the `confirming` item. + fn begin_deciding( + status: &mut ReferendumStatusOf, + index: ReferendumIndex, + now: T::BlockNumber, + track: &TrackInfoOf, + ) -> (Option, BeginDecidingBranch) { + let is_passing = Self::is_passing( + &status.tally, + Zero::zero(), + track.decision_period, + &track.min_support, + &track.min_approval, + status.track, + ); + status.in_queue = false; + Self::deposit_event(Event::::DecisionStarted { + index, + tally: status.tally.clone(), + proposal: status.proposal.clone(), + track: status.track, + }); + let confirming = if is_passing { + Self::deposit_event(Event::::ConfirmStarted { index }); + Some(now.saturating_add(track.confirm_period)) + } else { + None + }; + let deciding_status = DecidingStatus { since: now, confirming }; + let alarm = Self::decision_time(&deciding_status, &status.tally, status.track, track) + .max(now.saturating_add(One::one())); + status.deciding = Some(deciding_status); + let branch = + if is_passing { BeginDecidingBranch::Passing } else { BeginDecidingBranch::Failing }; + (Some(alarm), branch) + } + + /// If it returns `Some`, deciding has begun and it needs waking at the given block number. The + /// second item is the flag for whether it is confirming or not. + /// + /// If `None`, then it is queued and should be nudged automatically as the queue gets drained. + fn ready_for_deciding( + now: T::BlockNumber, + track: &TrackInfoOf, + index: ReferendumIndex, + status: &mut ReferendumStatusOf, + ) -> (Option, ServiceBranch) { + let deciding_count = DecidingCount::::get(status.track); + if deciding_count < track.max_deciding { + // Begin deciding. + DecidingCount::::insert(status.track, deciding_count.saturating_add(1)); + let r = Self::begin_deciding(status, index, now, track); + (r.0, r.1.into()) + } else { + // Add to queue. + let item = (index, status.tally.ayes(status.track)); + status.in_queue = true; + TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)); + (None, ServiceBranch::Queued) + } + } + + /// Grab the index and status for the referendum which is the highest priority of those for the + /// given track which are ready for being decided. + fn next_for_deciding( + track_queue: &mut BoundedVec<(u32, VotesOf), T::MaxQueued>, + ) -> Option<(ReferendumIndex, ReferendumStatusOf)> { + loop { + let (index, _) = track_queue.pop()?; + match Self::ensure_ongoing(index) { + Ok(s) => return Some((index, s)), + Err(_) => {}, // referendum already timedout or was cancelled. + } + } + } + + /// Schedule a call to `one_fewer_deciding` function via the dispatchable + /// `defer_one_fewer_deciding`. We could theoretically call it immediately (and it would be + /// overall more efficient), however the weights become rather less easy to measure. + fn note_one_fewer_deciding(track: TrackIdOf) { + // Set an alarm call for the next block to nudge the track along. + let now = frame_system::Pallet::::block_number(); + let next_block = now + One::one(); + let call = match T::Preimages::bound(CallOf::::from(Call::one_fewer_deciding { + track, + })) { + Ok(c) => c, + Err(_) => { + debug_assert!(false, "Unable to create a bounded call from `one_fewer_deciding`??",); + return + }, + }; + Self::set_alarm(call, next_block); + } + + /// Ensure that a `service_referendum` alarm happens for the referendum `index` at `alarm`. + /// + /// This will do nothing if the alarm is already set. + /// + /// Returns `false` if nothing changed. + fn ensure_alarm_at( + status: &mut ReferendumStatusOf, + index: ReferendumIndex, + alarm: T::BlockNumber, + ) -> bool { + if status.alarm.as_ref().map_or(true, |&(when, _)| when != alarm) { + // Either no alarm or one that was different + Self::ensure_no_alarm(status); + let call = + match T::Preimages::bound(CallOf::::from(Call::nudge_referendum { index })) { + Ok(c) => c, + Err(_) => { + debug_assert!( + false, + "Unable to create a bounded call from `nudge_referendum`??", + ); + return false + }, + }; + status.alarm = Self::set_alarm(call, alarm); + true + } else { + false + } + } + + /// Advance the state of a referendum, which comes down to: + /// - If it's ready to be decided, start deciding; + /// - If it's not ready to be decided and non-deciding timeout has passed, fail; + /// - If it's ongoing and passing, ensure confirming; if at end of confirmation period, pass. + /// - If it's ongoing and not passing, stop confirning; if it has reached end time, fail. + /// + /// Weight will be a bit different depending on what it does, but it's designed so as not to + /// differ dramatically, especially if `MaxQueue` is kept small. In particular _there are no + /// balance operations in here_. + /// + /// In terms of storage, every call to it is expected to access: + /// - The scheduler, either to insert, remove or alter an entry; + /// - `TrackQueue`, which should be a `BoundedVec` with a low limit (8-16); + /// - `DecidingCount`. + /// + /// Both of the two storage items will only have as many items as there are different tracks, + /// perhaps around 10 and should be whitelisted. + /// + /// The heaviest branch is likely to be when a proposal is placed into, or moved within, the + /// `TrackQueue`. Basically this happens when a referendum is in the deciding queue and receives + /// a vote, or when it moves into the deciding queue. + fn service_referendum( + now: T::BlockNumber, + index: ReferendumIndex, + mut status: ReferendumStatusOf, + ) -> (ReferendumInfoOf, bool, ServiceBranch) { + let mut dirty = false; + // Should it begin being decided? + let track = match Self::track(status.track) { + Some(x) => x, + None => return (ReferendumInfo::Ongoing(status), false, ServiceBranch::Fail), + }; + let timeout = status.submitted + T::UndecidingTimeout::get(); + // Default the alarm to the submission timeout. + let mut alarm = timeout; + let branch; + match &mut status.deciding { + None => { + // Are we already queued for deciding? + if status.in_queue { + // Does our position in the queue need updating? + let ayes = status.tally.ayes(status.track); + let mut queue = TrackQueue::::get(status.track); + let maybe_old_pos = queue.iter().position(|(x, _)| *x == index); + let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x); + branch = if maybe_old_pos.is_none() && new_pos > 0 { + // Just insert. + let _ = queue.force_insert_keep_right(new_pos, (index, ayes)); + ServiceBranch::RequeuedInsertion + } else if let Some(old_pos) = maybe_old_pos { + // We were in the queue - slide into the correct position. + queue[old_pos].1 = ayes; + queue.slide(old_pos, new_pos); + ServiceBranch::RequeuedSlide + } else { + ServiceBranch::NotQueued + }; + TrackQueue::::insert(status.track, queue); + } else { + // Are we ready for deciding? + branch = if status.decision_deposit.is_some() { + let prepare_end = status.submitted.saturating_add(track.prepare_period); + if now >= prepare_end { + let (maybe_alarm, branch) = + Self::ready_for_deciding(now, track, index, &mut status); + if let Some(set_alarm) = maybe_alarm { + alarm = alarm.min(set_alarm); + } + dirty = true; + branch + } else { + alarm = alarm.min(prepare_end); + ServiceBranch::Preparing + } + } else { + ServiceBranch::NoDeposit + } + } + // If we didn't move into being decided, then check the timeout. + if status.deciding.is_none() && now >= timeout { + // Too long without being decided - end it. + Self::ensure_no_alarm(&mut status); + Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); + return ( + ReferendumInfo::TimedOut( + now, + Some(status.submission_deposit), + status.decision_deposit, + ), + true, + ServiceBranch::TimedOut, + ) + } + }, + Some(deciding) => { + let is_passing = Self::is_passing( + &status.tally, + now.saturating_sub(deciding.since), + track.decision_period, + &track.min_support, + &track.min_approval, + status.track, + ); + branch = if is_passing { + match deciding.confirming { + Some(t) if now >= t => { + // Passed! + Self::ensure_no_alarm(&mut status); + Self::note_one_fewer_deciding(status.track); + let (desired, call) = (status.enactment, status.proposal); + Self::schedule_enactment(index, track, desired, status.origin, call); + Self::deposit_event(Event::::Confirmed { + index, + tally: status.tally, + }); + return ( + ReferendumInfo::Approved( + now, + Some(status.submission_deposit), + status.decision_deposit, + ), + true, + ServiceBranch::Approved, + ) + }, + Some(_) => ServiceBranch::ContinueConfirming, + None => { + // Start confirming + dirty = true; + deciding.confirming = Some(now.saturating_add(track.confirm_period)); + Self::deposit_event(Event::::ConfirmStarted { index }); + ServiceBranch::BeginConfirming + }, + } + } else { + if now >= deciding.since.saturating_add(track.decision_period) { + // Failed! + Self::ensure_no_alarm(&mut status); + Self::note_one_fewer_deciding(status.track); + Self::deposit_event(Event::::Rejected { index, tally: status.tally }); + return ( + ReferendumInfo::Rejected( + now, + Some(status.submission_deposit), + status.decision_deposit, + ), + true, + ServiceBranch::Rejected, + ) + } + if deciding.confirming.is_some() { + // Stop confirming + dirty = true; + deciding.confirming = None; + Self::deposit_event(Event::::ConfirmAborted { index }); + ServiceBranch::EndConfirming + } else { + ServiceBranch::ContinueNotConfirming + } + }; + alarm = Self::decision_time(deciding, &status.tally, status.track, track); + }, + } + + let dirty_alarm = Self::ensure_alarm_at(&mut status, index, alarm); + (ReferendumInfo::Ongoing(status), dirty_alarm || dirty, branch) + } + + /// Determine the point at which a referendum will be accepted, move into confirmation with the + /// given `tally` or end with rejection (whichever happens sooner). + fn decision_time( + deciding: &DecidingStatusOf, + tally: &T::Tally, + track_id: TrackIdOf, + track: &TrackInfoOf, + ) -> T::BlockNumber { + deciding.confirming.unwrap_or_else(|| { + // Set alarm to the point where the current voting would make it pass. + let approval = tally.approval(track_id); + let support = tally.support(track_id); + let until_approval = track.min_approval.delay(approval); + let until_support = track.min_support.delay(support); + let offset = until_support.max(until_approval); + deciding.since.saturating_add(offset * track.decision_period) + }) + } + + /// Cancel the alarm in `status`, if one exists. + fn ensure_no_alarm(status: &mut ReferendumStatusOf) { + if let Some((_, last_alarm)) = status.alarm.take() { + // Incorrect alarm - cancel it. + let _ = T::Scheduler::cancel(last_alarm); + } + } + + /// Reserve a deposit and return the `Deposit` instance. + fn take_deposit( + who: T::AccountId, + amount: BalanceOf, + ) -> Result>, DispatchError> { + T::Currency::reserve(&who, amount)?; + Ok(Deposit { who, amount }) + } + + /// Return a deposit, if `Some`. + fn refund_deposit(deposit: Option>>) { + if let Some(Deposit { who, amount }) = deposit { + T::Currency::unreserve(&who, amount); + } + } + + /// Slash a deposit, if `Some`. + fn slash_deposit(deposit: Option>>) { + if let Some(Deposit { who, amount }) = deposit { + T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); + Self::deposit_event(Event::::DepositSlashed { who, amount }); + } + } + + /// Get the track info value for the track `id`. + fn track(id: TrackIdOf) -> Option<&'static TrackInfoOf> { + let tracks = T::Tracks::tracks(); + let index = tracks.binary_search_by_key(&id, |x| x.0).unwrap_or_else(|x| x); + Some(&tracks[index].1) + } + + /// Determine whether the given `tally` would result in a referendum passing at `elapsed` blocks + /// into a total decision `period`, given the two curves for `support_needed` and + /// `approval_needed`. + fn is_passing( + tally: &T::Tally, + elapsed: T::BlockNumber, + period: T::BlockNumber, + support_needed: &Curve, + approval_needed: &Curve, + id: TrackIdOf, + ) -> bool { + let x = Perbill::from_rational(elapsed.min(period), period); + support_needed.passing(x, tally.support(id)) && + approval_needed.passing(x, tally.approval(id)) + } +} diff --git a/frame/referenda/src/migration.rs b/frame/referenda/src/migration.rs new file mode 100644 index 0000000000000..e495090c13754 --- /dev/null +++ b/frame/referenda/src/migration.rs @@ -0,0 +1,232 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Storage migrations for the referenda pallet. + +use super::*; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; +use log; + +/// Initial version of storage types. +pub mod v0 { + use super::*; + // ReferendumStatus and its dependency types referenced from the latest version while staying + // unchanged. [`super::test::referendum_status_v0()`] checks its immutability between v0 and + // latest version. + #[cfg(test)] + pub(super) use super::{ReferendumStatus, ReferendumStatusOf}; + + pub type ReferendumInfoOf = ReferendumInfo< + TrackIdOf, + PalletsOriginOf, + ::BlockNumber, + BoundedCallOf, + BalanceOf, + TallyOf, + ::AccountId, + ScheduleAddressOf, + >; + + /// Info regarding a referendum, present or past. + #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub enum ReferendumInfo< + TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, + Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + > { + /// Referendum has been submitted and is being voted on. + Ongoing( + ReferendumStatus< + TrackId, + RuntimeOrigin, + Moment, + Call, + Balance, + Tally, + AccountId, + ScheduleAddress, + >, + ), + /// Referendum finished with approval. Submission deposit is held. + Approved(Moment, Deposit, Option>), + /// Referendum finished with rejection. Submission deposit is held. + Rejected(Moment, Deposit, Option>), + /// Referendum finished with cancellation. Submission deposit is held. + Cancelled(Moment, Deposit, Option>), + /// Referendum finished and was never decided. Submission deposit is held. + TimedOut(Moment, Deposit, Option>), + /// Referendum finished with a kill. + Killed(Moment), + } + + #[storage_alias] + pub type ReferendumInfoFor, I: 'static> = + StorageMap, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf>; +} + +pub mod v1 { + use super::*; + + /// The log target. + const TARGET: &'static str = "runtime::democracy::migration::v1"; + + /// Transforms a submission deposit of ReferendumInfo(Approved|Rejected|Cancelled|TimedOut) to + /// optional value, making it refundable. + pub struct MigrateV0ToV1(PhantomData<(T, I)>); + impl, I: 'static> OnRuntimeUpgrade for MigrateV0ToV1 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + let onchain_version = Pallet::::on_chain_storage_version(); + assert_eq!(onchain_version, 0, "migration from version 0 to 1."); + let referendum_count = v0::ReferendumInfoFor::::iter().count(); + log::info!( + target: TARGET, + "pre-upgrade state contains '{}' referendums.", + referendum_count + ); + Ok((referendum_count as u32).encode()) + } + + fn on_runtime_upgrade() -> Weight { + let current_version = Pallet::::current_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); + let mut weight = T::DbWeight::get().reads(1); + log::info!( + target: TARGET, + "running migration with current storage version {:?} / onchain {:?}.", + current_version, + onchain_version + ); + if onchain_version != 0 { + log::warn!(target: TARGET, "skipping migration from v0 to v1."); + return weight + } + v0::ReferendumInfoFor::::iter().for_each(|(key, value)| { + let maybe_new_value = match value { + v0::ReferendumInfo::Ongoing(_) | v0::ReferendumInfo::Killed(_) => None, + v0::ReferendumInfo::Approved(e, s, d) => + Some(ReferendumInfo::Approved(e, Some(s), d)), + v0::ReferendumInfo::Rejected(e, s, d) => + Some(ReferendumInfo::Rejected(e, Some(s), d)), + v0::ReferendumInfo::Cancelled(e, s, d) => + Some(ReferendumInfo::Cancelled(e, Some(s), d)), + v0::ReferendumInfo::TimedOut(e, s, d) => + Some(ReferendumInfo::TimedOut(e, Some(s), d)), + }; + if let Some(new_value) = maybe_new_value { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + log::info!(target: TARGET, "migrating referendum #{:?}", &key); + ReferendumInfoFor::::insert(key, new_value); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + }); + StorageVersion::new(1).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + weight + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + let onchain_version = Pallet::::on_chain_storage_version(); + assert_eq!(onchain_version, 1, "must upgrade from version 0 to 1."); + let pre_referendum_count: u32 = Decode::decode(&mut &state[..]) + .expect("failed to decode the state from pre-upgrade."); + let post_referendum_count = ReferendumInfoFor::::iter().count() as u32; + assert_eq!( + post_referendum_count, pre_referendum_count, + "must migrate all referendums." + ); + log::info!(target: TARGET, "migrated all referendums."); + Ok(()) + } + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::mock::{Test as T, *}; + use core::str::FromStr; + + // create referendum status v0. + fn create_status_v0() -> v0::ReferendumStatusOf { + let origin: OriginCaller = frame_system::RawOrigin::Root.into(); + let track = >::Tracks::track_for(&origin).unwrap(); + v0::ReferendumStatusOf:: { + track, + in_queue: true, + origin, + proposal: set_balance_proposal_bounded(1), + enactment: DispatchTime::At(1), + tally: TallyOf::::new(track), + submission_deposit: Deposit { who: 1, amount: 10 }, + submitted: 1, + decision_deposit: None, + alarm: None, + deciding: None, + } + } + + #[test] + pub fn referendum_status_v0() { + // make sure the bytes of the encoded referendum v0 is decodable. + let ongoing_encoded = sp_core::Bytes::from_str("0x00000000013001012a000000000000000400000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap(); + let ongoing_dec = v0::ReferendumInfoOf::::decode(&mut &*ongoing_encoded).unwrap(); + let ongoing = v0::ReferendumInfoOf::::Ongoing(create_status_v0()); + assert_eq!(ongoing, ongoing_dec); + } + + #[test] + fn migration_v0_to_v1_works() { + new_test_ext().execute_with(|| { + // create and insert into the storage an ongoing referendum v0. + let status_v0 = create_status_v0(); + let ongoing_v0 = v0::ReferendumInfoOf::::Ongoing(status_v0.clone()); + v0::ReferendumInfoFor::::insert(2, ongoing_v0); + // create and insert into the storage an approved referendum v0. + let approved_v0 = v0::ReferendumInfoOf::::Approved( + 123, + Deposit { who: 1, amount: 10 }, + Some(Deposit { who: 2, amount: 20 }), + ); + v0::ReferendumInfoFor::::insert(5, approved_v0); + // run migration from v0 to v1. + v1::MigrateV0ToV1::::on_runtime_upgrade(); + // fetch and assert migrated into v1 the ongoing referendum. + let ongoing_v1 = ReferendumInfoFor::::get(2).unwrap(); + // referendum status schema is the same for v0 and v1. + assert_eq!(ReferendumInfoOf::::Ongoing(status_v0), ongoing_v1); + // fetch and assert migrated into v1 the approved referendum. + let approved_v1 = ReferendumInfoFor::::get(5).unwrap(); + assert_eq!( + approved_v1, + ReferendumInfoOf::::Approved( + 123, + Some(Deposit { who: 1, amount: 10 }), + Some(Deposit { who: 2, amount: 20 }) + ) + ); + }); + } +} diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs new file mode 100644 index 0000000000000..c98fbf9a676b1 --- /dev/null +++ b/frame/referenda/src/mock.rs @@ -0,0 +1,474 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! The crate's tests. + +use super::*; +use crate as pallet_referenda; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + assert_ok, ord_parameter_types, parameter_types, + traits::{ + ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, OriginTrait, Polling, + SortedMembers, + }, + weights::Weight, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + DispatchResult, Perbill, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Preimage: pallet_preimage, + Scheduler: pallet_scheduler, + Referenda: pallet_referenda, + } +); + +// Test that a fitlered call can be dispatched. +pub struct BaseFilter; +impl Contains for BaseFilter { + fn contains(call: &RuntimeCall) -> bool { + !matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. })) + } +} + +parameter_types! { + pub MaxWeight: Weight = Weight::from_ref_time(2_000_000_000_000); + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(MaxWeight::get()); +} +impl frame_system::Config for Test { + type BaseCallFilter = BaseFilter; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} +impl pallet_preimage::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type ManagerOrigin = EnsureRoot; + type BaseDeposit = (); + type ByteDeposit = (); +} +impl pallet_scheduler::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type MaximumWeight = MaxWeight; + type ScheduleOrigin = EnsureRoot; + type MaxScheduledPerBlock = ConstU32<100>; + type WeightInfo = (); + type OriginPrivilegeCmp = EqualPrivilegeOnly; + type Preimages = Preimage; +} +impl pallet_balances::Config for Test { + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type MaxLocks = ConstU32<10>; + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); +} +parameter_types! { + pub static AlarmInterval: u64 = 1; +} +ord_parameter_types! { + pub const One: u64 = 1; + pub const Two: u64 = 2; + pub const Three: u64 = 3; + pub const Four: u64 = 4; + pub const Five: u64 = 5; + pub const Six: u64 = 6; +} +pub struct OneToFive; +impl SortedMembers for OneToFive { + fn sorted_members() -> Vec { + vec![1, 2, 3, 4, 5] + } + #[cfg(feature = "runtime-benchmarks")] + fn add(_m: &u64) {} +} + +pub struct TestTracksInfo; +impl TracksInfo for TestTracksInfo { + type Id = u8; + type RuntimeOrigin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, TrackInfo)] { + static DATA: [(u8, TrackInfo); 2] = [ + ( + 0u8, + TrackInfo { + name: "root", + max_deciding: 1, + decision_deposit: 10, + prepare_period: 4, + decision_period: 4, + confirm_period: 2, + min_enactment_period: 4, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(100), + }, + }, + ), + ( + 1u8, + TrackInfo { + name: "none", + max_deciding: 3, + decision_deposit: 1, + prepare_period: 2, + decision_period: 2, + confirm_period: 1, + min_enactment_period: 2, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(95), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(90), + ceil: Perbill::from_percent(100), + }, + }, + ), + ]; + &DATA[..] + } + fn track_for(id: &Self::RuntimeOrigin) -> Result { + if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) { + match system_origin { + frame_system::RawOrigin::Root => Ok(0), + frame_system::RawOrigin::None => Ok(1), + _ => Err(()), + } + } else { + Err(()) + } + } +} +impl_tracksinfo_get!(TestTracksInfo, u64, u64); + +impl Config for Test { + type WeightInfo = (); + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; + type Currency = pallet_balances::Pallet; + type SubmitOrigin = frame_system::EnsureSigned; + type CancelOrigin = EnsureSignedBy; + type KillOrigin = EnsureRoot; + type Slash = (); + type Votes = u32; + type Tally = Tally; + type SubmissionDeposit = ConstU64<2>; + type MaxQueued = ConstU32<3>; + type UndecidingTimeout = ConstU64<20>; + type AlarmInterval = AlarmInterval; + type Tracks = TestTracksInfo; + type Preimages = Preimage; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)]; + pallet_balances::GenesisConfig:: { balances } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +/// Execute the function two times, with `true` and with `false`. +#[allow(dead_code)] +pub fn new_test_ext_execute_with_cond(execute: impl FnOnce(bool) -> () + Clone) { + new_test_ext().execute_with(|| (execute.clone())(false)); + new_test_ext().execute_with(|| execute(true)); +} + +#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, MaxEncodedLen)] +pub struct Tally { + pub ayes: u32, + pub nays: u32, +} + +impl VoteTally for Tally { + fn new(_: Class) -> Self { + Self { ayes: 0, nays: 0 } + } + + fn ayes(&self, _: Class) -> u32 { + self.ayes + } + + fn support(&self, _: Class) -> Perbill { + Perbill::from_percent(self.ayes) + } + + fn approval(&self, _: Class) -> Perbill { + if self.ayes + self.nays > 0 { + Perbill::from_rational(self.ayes, self.ayes + self.nays) + } else { + Perbill::zero() + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn unanimity(_: Class) -> Self { + Self { ayes: 100, nays: 0 } + } + + #[cfg(feature = "runtime-benchmarks")] + fn rejection(_: Class) -> Self { + Self { ayes: 0, nays: 100 } + } + + #[cfg(feature = "runtime-benchmarks")] + fn from_requirements(support: Perbill, approval: Perbill, _: Class) -> Self { + let ayes = support.mul_ceil(100u32); + let nays = ((ayes as u64) * 1_000_000_000u64 / approval.deconstruct() as u64) as u32 - ayes; + Self { ayes, nays } + } + + #[cfg(feature = "runtime-benchmarks")] + fn setup(_: Class, _: Perbill) {} +} + +pub fn set_balance_proposal(value: u64) -> Vec { + RuntimeCall::Balances(pallet_balances::Call::set_balance { + who: 42, + new_free: value, + new_reserved: 0, + }) + .encode() +} + +pub fn set_balance_proposal_bounded(value: u64) -> BoundedCallOf { + let c = RuntimeCall::Balances(pallet_balances::Call::set_balance { + who: 42, + new_free: value, + new_reserved: 0, + }); + ::bound(c).unwrap() +} + +#[allow(dead_code)] +pub fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { + Referenda::submit( + RuntimeOrigin::signed(who), + Box::new(frame_system::RawOrigin::Root.into()), + set_balance_proposal_bounded(value), + DispatchTime::After(delay), + ) +} + +pub fn next_block() { + System::set_block_number(System::block_number() + 1); + Scheduler::on_initialize(System::block_number()); +} + +pub fn run_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +#[allow(dead_code)] +pub fn begin_referendum() -> ReferendumIndex { + System::set_block_number(0); + assert_ok!(propose_set_balance(1, 2, 1)); + run_to(2); + 0 +} + +#[allow(dead_code)] +pub fn tally(r: ReferendumIndex) -> Tally { + Referenda::ensure_ongoing(r).unwrap().tally +} + +pub fn set_tally(index: ReferendumIndex, ayes: u32, nays: u32) { + >::access_poll(index, |status| { + let tally = status.ensure_ongoing().unwrap().0; + tally.ayes = ayes; + tally.nays = nays; + }); +} + +pub fn waiting_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Ongoing(ReferendumStatus { submitted, deciding: None, .. }) => submitted, + _ => panic!("Not waiting"), + } +} + +pub fn deciding_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Ongoing(ReferendumStatus { + deciding: Some(DecidingStatus { since, .. }), + .. + }) => since, + _ => panic!("Not deciding"), + } +} + +pub fn deciding_and_failing_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Ongoing(ReferendumStatus { + deciding: Some(DecidingStatus { since, confirming: None, .. }), + .. + }) => since, + _ => panic!("Not deciding"), + } +} + +pub fn confirming_until(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Ongoing(ReferendumStatus { + deciding: Some(DecidingStatus { confirming: Some(until), .. }), + .. + }) => until, + _ => panic!("Not confirming"), + } +} + +pub fn approved_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Approved(since, ..) => since, + _ => panic!("Not approved"), + } +} + +pub fn rejected_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Rejected(since, ..) => since, + _ => panic!("Not rejected"), + } +} + +pub fn cancelled_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Cancelled(since, ..) => since, + _ => panic!("Not cancelled"), + } +} + +pub fn killed_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::Killed(since, ..) => since, + _ => panic!("Not killed"), + } +} + +pub fn timed_out_since(i: ReferendumIndex) -> u64 { + match ReferendumInfoFor::::get(i).unwrap() { + ReferendumInfo::TimedOut(since, ..) => since, + _ => panic!("Not timed out"), + } +} + +fn is_deciding(i: ReferendumIndex) -> bool { + matches!( + ReferendumInfoFor::::get(i), + Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. })) + ) +} + +#[derive(Clone, Copy)] +pub enum RefState { + Failing, + Passing, + Confirming { immediate: bool }, +} + +impl RefState { + pub fn create(self) -> ReferendumIndex { + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(frame_support::dispatch::RawOrigin::Root.into()), + set_balance_proposal_bounded(1), + DispatchTime::At(10), + )); + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0)); + if matches!(self, RefState::Confirming { immediate: true }) { + set_tally(0, 100, 0); + } + let index = ReferendumCount::::get() - 1; + while !is_deciding(index) { + run_to(System::block_number() + 1); + } + if matches!(self, RefState::Confirming { immediate: false }) { + set_tally(0, 100, 0); + run_to(System::block_number() + 1); + } + if matches!(self, RefState::Confirming { .. }) { + assert_eq!(confirming_until(index), System::block_number() + 2); + } + if matches!(self, RefState::Passing) { + set_tally(0, 100, 99); + run_to(System::block_number() + 1); + } + index + } +} diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs new file mode 100644 index 0000000000000..c109fafe332e2 --- /dev/null +++ b/frame/referenda/src/tests.rs @@ -0,0 +1,580 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! The crate's tests. + +use super::*; +use crate::mock::{RefState::*, *}; +use assert_matches::assert_matches; +use codec::Decode; +use frame_support::{ + assert_noop, assert_ok, + dispatch::{DispatchError::BadOrigin, RawOrigin}, + traits::Contains, +}; +use pallet_balances::Error as BalancesError; + +#[test] +fn params_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(ReferendumCount::::get(), 0); + assert_eq!(Balances::free_balance(42), 0); + assert_eq!(Balances::total_issuance(), 600); + }); +} + +#[test] +fn basic_happy_path_works() { + new_test_ext().execute_with(|| { + // #1: submit + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + set_balance_proposal_bounded(1), + DispatchTime::At(10), + )); + assert_eq!(Balances::reserved_balance(&1), 2); + assert_eq!(ReferendumCount::::get(), 1); + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0)); + run_to(4); + assert_eq!(DecidingCount::::get(0), 0); + run_to(5); + // #5: 4 blocks after submit - vote should now be deciding. + assert_eq!(DecidingCount::::get(0), 1); + run_to(6); + // #6: Lots of ayes. Should now be confirming. + set_tally(0, 100, 0); + run_to(7); + assert_eq!(confirming_until(0), 9); + run_to(9); + // #8: Should be confirmed & ended. + assert_eq!(approved_since(0), 9); + assert_ok!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(2), 0)); + run_to(12); + // #9: Should not yet be enacted. + assert_eq!(Balances::free_balance(&42), 0); + run_to(13); + // #10: Proposal should be executed. + assert_eq!(Balances::free_balance(&42), 1); + }); +} + +#[test] +fn insta_confirm_then_kill_works() { + new_test_ext().execute_with(|| { + let r = Confirming { immediate: true }.create(); + run_to(6); + assert_ok!(Referenda::kill(RuntimeOrigin::root(), r)); + assert_eq!(killed_since(r), 6); + }); +} + +#[test] +fn confirm_then_reconfirm_with_elapsed_trigger_works() { + new_test_ext().execute_with(|| { + let r = Confirming { immediate: false }.create(); + assert_eq!(confirming_until(r), 8); + run_to(7); + set_tally(r, 100, 99); + run_to(8); + assert_eq!(deciding_and_failing_since(r), 5); + run_to(11); + assert_eq!(approved_since(r), 11); + }); +} + +#[test] +fn instaconfirm_then_reconfirm_with_elapsed_trigger_works() { + new_test_ext().execute_with(|| { + let r = Confirming { immediate: true }.create(); + run_to(6); + assert_eq!(confirming_until(r), 7); + set_tally(r, 100, 99); + run_to(7); + assert_eq!(deciding_and_failing_since(r), 5); + run_to(11); + assert_eq!(approved_since(r), 11); + }); +} + +#[test] +fn instaconfirm_then_reconfirm_with_voting_trigger_works() { + new_test_ext().execute_with(|| { + let r = Confirming { immediate: true }.create(); + run_to(6); + assert_eq!(confirming_until(r), 7); + set_tally(r, 100, 99); + run_to(7); + assert_eq!(deciding_and_failing_since(r), 5); + run_to(8); + set_tally(r, 100, 0); + run_to(9); + assert_eq!(confirming_until(r), 11); + run_to(11); + assert_eq!(approved_since(r), 11); + }); +} + +#[test] +fn voting_should_extend_for_late_confirmation() { + new_test_ext().execute_with(|| { + let r = Passing.create(); + run_to(10); + assert_eq!(confirming_until(r), 11); + run_to(11); + assert_eq!(approved_since(r), 11); + }); +} + +#[test] +fn should_instafail_during_extension_confirmation() { + new_test_ext().execute_with(|| { + let r = Passing.create(); + run_to(10); + assert_eq!(confirming_until(r), 11); + // Should insta-fail since it's now past the normal voting time. + set_tally(r, 100, 101); + run_to(11); + assert_eq!(rejected_since(r), 11); + }); +} + +#[test] +fn confirming_then_fail_works() { + new_test_ext().execute_with(|| { + let r = Failing.create(); + // Normally ends at 5 + 4 (voting period) = 9. + assert_eq!(deciding_and_failing_since(r), 5); + set_tally(r, 100, 0); + run_to(6); + assert_eq!(confirming_until(r), 8); + set_tally(r, 100, 101); + run_to(9); + assert_eq!(rejected_since(r), 9); + }); +} + +#[test] +fn queueing_works() { + new_test_ext().execute_with(|| { + // Submit a proposal into a track with a queue len of 1. + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(5), + Box::new(RawOrigin::Root.into()), + set_balance_proposal_bounded(0), + DispatchTime::After(0), + )); + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(5), 0)); + + run_to(2); + + // Submit 3 more proposals into the same queue. + for i in 1..=4 { + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(i), + Box::new(RawOrigin::Root.into()), + set_balance_proposal_bounded(i), + DispatchTime::After(0), + )); + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(i), i as u32)); + // TODO: decision deposit after some initial votes with a non-highest voted coming + // first. + } + assert_eq!(ReferendumCount::::get(), 5); + + run_to(5); + // One should be being decided. + assert_eq!(DecidingCount::::get(0), 1); + assert_eq!(deciding_and_failing_since(0), 5); + for i in 1..=4 { + assert_eq!(waiting_since(i), 2); + } + + // Vote to set order. + set_tally(1, 1, 10); + set_tally(2, 2, 20); + set_tally(3, 3, 30); + set_tally(4, 100, 0); + println!("Agenda #6: {:?}", pallet_scheduler::Agenda::::get(6)); + run_to(6); + println!("{:?}", Vec::<_>::from(TrackQueue::::get(0))); + + // Cancel the first. + assert_ok!(Referenda::cancel(RuntimeOrigin::signed(4), 0)); + assert_eq!(cancelled_since(0), 6); + + // The other with the most approvals (#4) should be being decided. + run_to(7); + assert_eq!(DecidingCount::::get(0), 1); + assert_eq!(deciding_since(4), 7); + assert_eq!(confirming_until(4), 9); + + // Vote on the remaining two to change order. + println!("Set tally #1"); + set_tally(1, 30, 31); + println!("{:?}", Vec::<_>::from(TrackQueue::::get(0))); + println!("Set tally #2"); + set_tally(2, 20, 20); + println!("{:?}", Vec::<_>::from(TrackQueue::::get(0))); + + // Let confirmation period end. + run_to(9); + + // #4 should have been confirmed. + assert_eq!(approved_since(4), 9); + + // On to the next block to select the new referendum + run_to(10); + // #1 (the one with the most approvals) should now be being decided. + assert_eq!(deciding_since(1), 10); + + // Let it end unsuccessfully. + run_to(14); + assert_eq!(rejected_since(1), 14); + + // Service queue. + run_to(15); + // #2 should now be being decided. It will (barely) pass. + assert_eq!(deciding_and_failing_since(2), 15); + + // #2 moves into confirming at the last moment with a 50% approval. + run_to(19); + assert_eq!(confirming_until(2), 21); + + // #2 gets approved. + run_to(21); + assert_eq!(approved_since(2), 21); + + // The final one has since timed out. + run_to(22); + assert_eq!(timed_out_since(3), 22); + }); +} + +#[test] +fn alarm_interval_works() { + new_test_ext().execute_with(|| { + let call = + ::Preimages::bound(CallOf::::from(Call::nudge_referendum { + index: 0, + })) + .unwrap(); + for n in 0..10 { + let interval = n * n; + let now = 100 * (interval + 1); + System::set_block_number(now); + AlarmInterval::set(interval); + let when = now + 1; + let (actual, _) = Referenda::set_alarm(call.clone(), when).unwrap(); + assert!(actual >= when); + assert!(actual - interval <= when); + } + }); +} + +#[test] +fn auto_timeout_should_happen_with_nothing_but_submit() { + new_test_ext().execute_with(|| { + // #1: submit + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + set_balance_proposal_bounded(1), + DispatchTime::At(20), + )); + run_to(20); + assert_matches!(ReferendumInfoFor::::get(0), Some(ReferendumInfo::Ongoing(..))); + run_to(21); + // #11: Timed out - ended. + assert_matches!( + ReferendumInfoFor::::get(0), + Some(ReferendumInfo::TimedOut(21, _, None)) + ); + }); +} + +#[test] +fn tracks_are_distinguished() { + new_test_ext().execute_with(|| { + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + set_balance_proposal_bounded(1), + DispatchTime::At(10), + )); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(2), + Box::new(RawOrigin::None.into()), + set_balance_proposal_bounded(2), + DispatchTime::At(20), + )); + + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(3), 0)); + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(4), 1)); + + let mut i = ReferendumInfoFor::::iter().collect::>(); + i.sort_by_key(|x| x.0); + assert_eq!( + i, + vec![ + ( + 0, + ReferendumInfo::Ongoing(ReferendumStatus { + track: 0, + origin: OriginCaller::system(RawOrigin::Root), + proposal: set_balance_proposal_bounded(1), + enactment: DispatchTime::At(10), + submitted: 1, + submission_deposit: Deposit { who: 1, amount: 2 }, + decision_deposit: Some(Deposit { who: 3, amount: 10 }), + deciding: None, + tally: Tally { ayes: 0, nays: 0 }, + in_queue: false, + alarm: Some((5, (5, 0))), + }) + ), + ( + 1, + ReferendumInfo::Ongoing(ReferendumStatus { + track: 1, + origin: OriginCaller::system(RawOrigin::None), + proposal: set_balance_proposal_bounded(2), + enactment: DispatchTime::At(20), + submitted: 1, + submission_deposit: Deposit { who: 2, amount: 2 }, + decision_deposit: Some(Deposit { who: 4, amount: 1 }), + deciding: None, + tally: Tally { ayes: 0, nays: 0 }, + in_queue: false, + alarm: Some((3, (3, 0))), + }) + ), + ] + ); + }); +} + +#[test] +fn submit_errors_work() { + new_test_ext().execute_with(|| { + let h = set_balance_proposal_bounded(1); + // No track for Signed origins. + assert_noop!( + Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Signed(2).into()), + h.clone(), + DispatchTime::At(10), + ), + Error::::NoTrack + ); + + // No funds for deposit + assert_noop!( + Referenda::submit( + RuntimeOrigin::signed(10), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + ), + BalancesError::::InsufficientBalance + ); + }); +} + +#[test] +fn decision_deposit_errors_work() { + new_test_ext().execute_with(|| { + let e = Error::::NotOngoing; + assert_noop!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0), e); + + let h = set_balance_proposal_bounded(1); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + )); + let e = BalancesError::::InsufficientBalance; + assert_noop!(Referenda::place_decision_deposit(RuntimeOrigin::signed(10), 0), e); + + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0)); + let e = Error::::HasDeposit; + assert_noop!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0), e); + }); +} + +#[test] +fn refund_deposit_works() { + new_test_ext().execute_with(|| { + let e = Error::::BadReferendum; + assert_noop!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(1), 0), e); + + let h = set_balance_proposal_bounded(1); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + )); + let e = Error::::NoDeposit; + assert_noop!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(2), 0), e); + + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0)); + let e = Error::::Unfinished; + assert_noop!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(3), 0), e); + + run_to(11); + assert_ok!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(3), 0)); + }); +} + +#[test] +fn refund_submission_deposit_works() { + new_test_ext().execute_with(|| { + // refund of non existing referendum fails. + let e = Error::::BadReferendum; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(1), 0), e); + // create a referendum. + let h = set_balance_proposal_bounded(1); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h.clone(), + DispatchTime::At(10), + )); + // refund of an ongoing referendum fails. + let e = Error::::BadStatus; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(3), 0), e); + // cancel referendum. + assert_ok!(Referenda::cancel(RuntimeOrigin::signed(4), 0)); + // refund of canceled referendum works. + assert_ok!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(3), 0)); + // fails if already refunded. + let e = Error::::NoDeposit; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(2), 0), e); + // create second referendum. + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + )); + // refund of a killed referendum fails. + assert_ok!(Referenda::kill(RuntimeOrigin::root(), 1)); + let e = Error::::NoDeposit; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(2), 0), e); + }); +} + +#[test] +fn cancel_works() { + new_test_ext().execute_with(|| { + let h = set_balance_proposal_bounded(1); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + )); + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0)); + + run_to(8); + assert_ok!(Referenda::cancel(RuntimeOrigin::signed(4), 0)); + assert_ok!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(3), 0)); + assert_eq!(cancelled_since(0), 8); + }); +} + +#[test] +fn cancel_errors_works() { + new_test_ext().execute_with(|| { + let h = set_balance_proposal_bounded(1); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + )); + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0)); + assert_noop!(Referenda::cancel(RuntimeOrigin::signed(1), 0), BadOrigin); + + run_to(11); + assert_noop!(Referenda::cancel(RuntimeOrigin::signed(4), 0), Error::::NotOngoing); + }); +} + +#[test] +fn kill_works() { + new_test_ext().execute_with(|| { + let h = set_balance_proposal_bounded(1); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + )); + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0)); + + run_to(8); + assert_ok!(Referenda::kill(RuntimeOrigin::root(), 0)); + let e = Error::::NoDeposit; + assert_noop!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(3), 0), e); + assert_eq!(killed_since(0), 8); + }); +} + +#[test] +fn kill_errors_works() { + new_test_ext().execute_with(|| { + let h = set_balance_proposal_bounded(1); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + )); + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0)); + assert_noop!(Referenda::kill(RuntimeOrigin::signed(4), 0), BadOrigin); + + run_to(11); + assert_noop!(Referenda::kill(RuntimeOrigin::root(), 0), Error::::NotOngoing); + }); +} + +#[test] +fn set_balance_proposal_is_correctly_filtered_out() { + for i in 0..10 { + let call = crate::mock::RuntimeCall::decode(&mut &set_balance_proposal(i)[..]).unwrap(); + assert!(!::BaseCallFilter::contains(&call)); + } +} + +#[test] +fn curve_handles_all_inputs() { + let test_curve = Curve::LinearDecreasing { + length: Perbill::one(), + floor: Perbill::zero(), + ceil: Perbill::from_percent(100), + }; + + let delay = test_curve.delay(Perbill::zero()); + assert_eq!(delay, Perbill::one()); + + let threshold = test_curve.threshold(Perbill::one()); + assert_eq!(threshold, Perbill::zero()); +} diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs new file mode 100644 index 0000000000000..0763a71a95ba3 --- /dev/null +++ b/frame/referenda/src/types.rs @@ -0,0 +1,675 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Miscellaneous additional datatypes. + +use super::*; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use frame_support::{ + traits::{schedule::v3::Anon, Bounded}, + Parameter, +}; +use scale_info::TypeInfo; +use sp_arithmetic::{Rounding::*, SignedRounding::*}; +use sp_runtime::{FixedI64, PerThing, RuntimeDebug}; +use sp_std::fmt::Debug; + +pub type BalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +pub type NegativeImbalanceOf = <>::Currency as Currency< + ::AccountId, +>>::NegativeImbalance; +pub type CallOf = >::RuntimeCall; +pub type BoundedCallOf = Bounded<>::RuntimeCall>; +pub type VotesOf = >::Votes; +pub type TallyOf = >::Tally; +pub type PalletsOriginOf = + <::RuntimeOrigin as OriginTrait>::PalletsOrigin; +pub type ReferendumInfoOf = ReferendumInfo< + TrackIdOf, + PalletsOriginOf, + ::BlockNumber, + BoundedCallOf, + BalanceOf, + TallyOf, + ::AccountId, + ScheduleAddressOf, +>; +pub type ReferendumStatusOf = ReferendumStatus< + TrackIdOf, + PalletsOriginOf, + ::BlockNumber, + BoundedCallOf, + BalanceOf, + TallyOf, + ::AccountId, + ScheduleAddressOf, +>; +pub type DecidingStatusOf = DecidingStatus<::BlockNumber>; +pub type TrackInfoOf = + TrackInfo, ::BlockNumber>; +pub type TrackIdOf = <>::Tracks as TracksInfo< + BalanceOf, + ::BlockNumber, +>>::Id; +pub type ScheduleAddressOf = <>::Scheduler as Anon< + ::BlockNumber, + CallOf, + PalletsOriginOf, +>>::Address; + +/// A referendum index. +pub type ReferendumIndex = u32; + +pub trait InsertSorted { + /// Inserts an item into a sorted series. + /// + /// Returns `true` if it was inserted, `false` if it would belong beyond the bound of the + /// series. + fn insert_sorted_by_key K, K: PartialOrd + Ord>( + &mut self, + t: T, + f: F, + ) -> bool; +} +impl> InsertSorted for BoundedVec { + fn insert_sorted_by_key K, K: PartialOrd + Ord>( + &mut self, + t: T, + mut f: F, + ) -> bool { + let index = self.binary_search_by_key::(&f(&t), f).unwrap_or_else(|x| x); + self.force_insert_keep_right(index, t).is_ok() + } +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct DecidingStatus { + /// When this referendum began being "decided". If confirming, then the + /// end will actually be delayed until the end of the confirmation period. + pub since: BlockNumber, + /// If `Some`, then the referendum has entered confirmation stage and will end at + /// the block number as long as it doesn't lose its approval in the meantime. + pub confirming: Option, +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct Deposit { + pub who: AccountId, + pub amount: Balance, +} + +#[derive(Clone, Encode, TypeInfo)] +pub struct TrackInfo { + /// Name of this track. + pub name: &'static str, + /// A limit for the number of referenda on this track that can be being decided at once. + /// For Root origin this should generally be just one. + pub max_deciding: u32, + /// Amount that must be placed on deposit before a decision can be made. + pub decision_deposit: Balance, + /// Amount of time this must be submitted for before a decision can be made. + pub prepare_period: Moment, + /// Amount of time that a decision may take to be approved prior to cancellation. + pub decision_period: Moment, + /// Amount of time that the approval criteria must hold before it can be approved. + pub confirm_period: Moment, + /// Minimum amount of time that an approved proposal must be in the dispatch queue. + pub min_enactment_period: Moment, + /// Minimum aye votes as percentage of overall conviction-weighted votes needed for + /// approval as a function of time into decision period. + pub min_approval: Curve, + /// Minimum pre-conviction aye-votes ("support") as percentage of overall population that is + /// needed for approval as a function of time into decision period. + pub min_support: Curve, +} + +/// Information on the voting tracks. +pub trait TracksInfo { + /// The identifier for a track. + type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync + 'static + MaxEncodedLen; + + /// The origin type from which a track is implied. + type RuntimeOrigin; + + /// Return the array of known tracks and their information. + fn tracks() -> &'static [(Self::Id, TrackInfo)]; + + /// Determine the voting track for the given `origin`. + fn track_for(origin: &Self::RuntimeOrigin) -> Result; + + /// Return the track info for track `id`, by default this just looks it up in `Self::tracks()`. + fn info(id: Self::Id) -> Option<&'static TrackInfo> { + Self::tracks().iter().find(|x| x.0 == id).map(|x| &x.1) + } +} + +/// Info regarding an ongoing referendum. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ReferendumStatus< + TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Moment: Parameter + Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, + Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, +> { + /// The track of this referendum. + pub track: TrackId, + /// The origin for this referendum. + pub origin: RuntimeOrigin, + /// The hash of the proposal up for referendum. + pub proposal: Call, + /// The time the proposal should be scheduled for enactment. + pub enactment: DispatchTime, + /// The time of submission. Once `UndecidingTimeout` passes, it may be closed by anyone if + /// `deciding` is `None`. + pub submitted: Moment, + /// The deposit reserved for the submission of this referendum. + pub submission_deposit: Deposit, + /// The deposit reserved for this referendum to be decided. + pub decision_deposit: Option>, + /// The status of a decision being made. If `None`, it has not entered the deciding period. + pub deciding: Option>, + /// The current tally of votes in this referendum. + pub tally: Tally, + /// Whether we have been placed in the queue for being decided or not. + pub in_queue: bool, + /// The next scheduled wake-up, if `Some`. + pub alarm: Option<(Moment, ScheduleAddress)>, +} + +/// Info regarding a referendum, present or past. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum ReferendumInfo< + TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, + Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, +> { + /// Referendum has been submitted and is being voted on. + Ongoing( + ReferendumStatus< + TrackId, + RuntimeOrigin, + Moment, + Call, + Balance, + Tally, + AccountId, + ScheduleAddress, + >, + ), + /// Referendum finished with approval. Submission deposit is held. + Approved(Moment, Option>, Option>), + /// Referendum finished with rejection. Submission deposit is held. + Rejected(Moment, Option>, Option>), + /// Referendum finished with cancellation. Submission deposit is held. + Cancelled(Moment, Option>, Option>), + /// Referendum finished and was never decided. Submission deposit is held. + TimedOut(Moment, Option>, Option>), + /// Referendum finished with a kill. + Killed(Moment), +} + +impl< + TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Moment: Parameter + Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, + Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + > ReferendumInfo +{ + /// Take the Decision Deposit from `self`, if there is one. Returns an `Err` if `self` is not + /// in a valid state for the Decision Deposit to be refunded. + pub fn take_decision_deposit(&mut self) -> Result>, ()> { + use ReferendumInfo::*; + match self { + Ongoing(x) if x.decision_deposit.is_none() => Ok(None), + // Cannot refund deposit if Ongoing as this breaks assumptions. + Ongoing(_) => Err(()), + Approved(_, _, d) | Rejected(_, _, d) | TimedOut(_, _, d) | Cancelled(_, _, d) => + Ok(d.take()), + Killed(_) => Ok(None), + } + } + + /// Take the Submission Deposit from `self`, if there is one and it's in a valid state to be + /// taken. Returns an `Err` if `self` is not in a valid state for the Submission Deposit to be + /// refunded. + pub fn take_submission_deposit(&mut self) -> Result>, ()> { + use ReferendumInfo::*; + match self { + // Can only refund deposit if it's appoved or cancelled. + Approved(_, s, _) | Cancelled(_, s, _) => Ok(s.take()), + // Cannot refund deposit if Ongoing as this breaks assumptions. + Ongoing(..) | Rejected(..) | TimedOut(..) | Killed(..) => Err(()), + } + } +} + +/// Type for describing a curve over the 2-dimensional space of axes between 0-1, as represented +/// by `(Perbill, Perbill)`. +#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(not(feature = "std"), derive(RuntimeDebug))] +pub enum Curve { + /// Linear curve starting at `(0, ceil)`, proceeding linearly to `(length, floor)`, then + /// remaining at `floor` until the end of the period. + LinearDecreasing { length: Perbill, floor: Perbill, ceil: Perbill }, + /// Stepped curve, beginning at `(0, begin)`, then remaining constant for `period`, at which + /// point it steps down to `(period, begin - step)`. It then remains constant for another + /// `period` before stepping down to `(period * 2, begin - step * 2)`. This pattern continues + /// but the `y` component has a lower limit of `end`. + SteppedDecreasing { begin: Perbill, end: Perbill, step: Perbill, period: Perbill }, + /// A recipocal (`K/(x+S)-T`) curve: `factor` is `K` and `x_offset` is `S`, `y_offset` is `T`. + Reciprocal { factor: FixedI64, x_offset: FixedI64, y_offset: FixedI64 }, +} + +/// Calculate the quadratic solution for the given curve. +/// +/// WARNING: This is a `const` function designed for convenient use at build time and +/// will panic on overflow. Ensure that any inputs are sensible. +const fn pos_quad_solution(a: FixedI64, b: FixedI64, c: FixedI64) -> FixedI64 { + const TWO: FixedI64 = FixedI64::from_u32(2); + const FOUR: FixedI64 = FixedI64::from_u32(4); + b.neg().add(b.mul(b).sub(FOUR.mul(a).mul(c)).sqrt()).div(TWO.mul(a)) +} + +impl Curve { + /// Create a `Curve::Linear` instance from a high-level description. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn make_linear(length: u128, period: u128, floor: FixedI64, ceil: FixedI64) -> Curve { + let length = FixedI64::from_rational(length, period).into_perbill(); + let floor = floor.into_perbill(); + let ceil = ceil.into_perbill(); + Curve::LinearDecreasing { length, floor, ceil } + } + + /// Create a `Curve::Reciprocal` instance from a high-level description. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn make_reciprocal( + delay: u128, + period: u128, + level: FixedI64, + floor: FixedI64, + ceil: FixedI64, + ) -> Curve { + let delay = FixedI64::from_rational(delay, period).into_perbill(); + let mut bounds = ( + ( + FixedI64::from_u32(0), + Self::reciprocal_from_parts(FixedI64::from_u32(0), floor, ceil), + FixedI64::from_inner(i64::max_value()), + ), + ( + FixedI64::from_u32(1), + Self::reciprocal_from_parts(FixedI64::from_u32(1), floor, ceil), + FixedI64::from_inner(i64::max_value()), + ), + ); + const TWO: FixedI64 = FixedI64::from_u32(2); + while (bounds.1).0.sub((bounds.0).0).into_inner() > 1 { + let factor = (bounds.0).0.add((bounds.1).0).div(TWO); + let curve = Self::reciprocal_from_parts(factor, floor, ceil); + let curve_level = FixedI64::from_perbill(curve.const_threshold(delay)); + if curve_level.into_inner() > level.into_inner() { + bounds = (bounds.0, (factor, curve, curve_level.sub(level))); + } else { + bounds = ((factor, curve, level.sub(curve_level)), bounds.1); + } + } + if (bounds.0).2.into_inner() < (bounds.1).2.into_inner() { + (bounds.0).1 + } else { + (bounds.1).1 + } + } + + /// Create a `Curve::Reciprocal` instance from basic parameters. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + const fn reciprocal_from_parts(factor: FixedI64, floor: FixedI64, ceil: FixedI64) -> Self { + let delta = ceil.sub(floor); + let x_offset = pos_quad_solution(delta, delta, factor.neg()); + let y_offset = floor.sub(factor.div(FixedI64::from_u32(1).add(x_offset))); + Curve::Reciprocal { factor, x_offset, y_offset } + } + + /// Print some info on the curve. + #[cfg(feature = "std")] + pub fn info(&self, days: u32, name: impl std::fmt::Display) { + let hours = days * 24; + println!("Curve {} := {:?}:", name, self); + println!(" t + 0h: {:?}", self.threshold(Perbill::zero())); + println!(" t + 1h: {:?}", self.threshold(Perbill::from_rational(1, hours))); + println!(" t + 2h: {:?}", self.threshold(Perbill::from_rational(2, hours))); + println!(" t + 3h: {:?}", self.threshold(Perbill::from_rational(3, hours))); + println!(" t + 6h: {:?}", self.threshold(Perbill::from_rational(6, hours))); + println!(" t + 12h: {:?}", self.threshold(Perbill::from_rational(12, hours))); + println!(" t + 24h: {:?}", self.threshold(Perbill::from_rational(24, hours))); + let mut l = 0; + for &(n, d) in [(1, 12), (1, 8), (1, 4), (1, 2), (3, 4), (1, 1)].iter() { + let t = days * n / d; + if t != l { + println!(" t + {}d: {:?}", t, self.threshold(Perbill::from_rational(t, days))); + l = t; + } + } + let t = |p: Perbill| -> std::string::String { + if p.is_one() { + "never".into() + } else { + let minutes = p * (hours * 60); + if minutes < 60 { + format!("{} minutes", minutes) + } else if minutes < 8 * 60 && minutes % 60 != 0 { + format!("{} hours {} minutes", minutes / 60, minutes % 60) + } else if minutes < 72 * 60 { + format!("{} hours", minutes / 60) + } else if minutes / 60 % 24 == 0 { + format!("{} days", minutes / 60 / 24) + } else { + format!("{} days {} hours", minutes / 60 / 24, minutes / 60 % 24) + } + } + }; + if self.delay(Perbill::from_percent(49)) < Perbill::one() { + println!(" 30% threshold: {}", t(self.delay(Perbill::from_percent(30)))); + println!(" 10% threshold: {}", t(self.delay(Perbill::from_percent(10)))); + println!(" 3% threshold: {}", t(self.delay(Perbill::from_percent(3)))); + println!(" 1% threshold: {}", t(self.delay(Perbill::from_percent(1)))); + println!(" 0.1% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 1_000)))); + println!(" 0.01% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 10_000)))); + } else { + println!( + " 99.9% threshold: {}", + t(self.delay(Perbill::from_rational(999u32, 1_000))) + ); + println!(" 99% threshold: {}", t(self.delay(Perbill::from_percent(99)))); + println!(" 95% threshold: {}", t(self.delay(Perbill::from_percent(95)))); + println!(" 90% threshold: {}", t(self.delay(Perbill::from_percent(90)))); + println!(" 75% threshold: {}", t(self.delay(Perbill::from_percent(75)))); + println!(" 60% threshold: {}", t(self.delay(Perbill::from_percent(60)))); + } + } + + /// Determine the `y` value for the given `x` value. + pub fn threshold(&self, x: Perbill) -> Perbill { + match self { + Self::LinearDecreasing { length, floor, ceil } => + *ceil - (x.min(*length).saturating_div(*length, Down) * (*ceil - *floor)), + Self::SteppedDecreasing { begin, end, step, period } => + (*begin - (step.int_mul(x.int_div(*period))).min(*begin)).max(*end), + Self::Reciprocal { factor, x_offset, y_offset } => factor + .checked_rounding_div(FixedI64::from(x) + *x_offset, Low) + .map(|yp| (yp + *y_offset).into_clamped_perthing()) + .unwrap_or_else(Perbill::one), + } + } + + /// Determine the `y` value for the given `x` value. + /// + /// This is a partial implementation designed only for use in const functions. + const fn const_threshold(&self, x: Perbill) -> Perbill { + match self { + Self::Reciprocal { factor, x_offset, y_offset } => { + match factor.checked_rounding_div(FixedI64::from_perbill(x).add(*x_offset), Low) { + Some(yp) => (yp.add(*y_offset)).into_perbill(), + None => Perbill::one(), + } + }, + _ => panic!("const_threshold cannot be used on this curve"), + } + } + + /// Determine the smallest `x` value such that `passing` returns `true` when passed along with + /// the given `y` value. + /// + /// If `passing` never returns `true` for any value of `x` when paired with `y`, then + /// `Perbill::one` may be returned. + /// + /// ```nocompile + /// let c = Curve::LinearDecreasing { begin: Perbill::one(), delta: Perbill::one() }; + /// // ^^^ Can be any curve. + /// let y = Perbill::from_percent(50); + /// // ^^^ Can be any value. + /// let x = c.delay(y); + /// assert!(c.passing(x, y)); + /// ``` + pub fn delay(&self, y: Perbill) -> Perbill { + match self { + Self::LinearDecreasing { length, floor, ceil } => + if y < *floor { + Perbill::one() + } else if y > *ceil { + Perbill::zero() + } else { + (*ceil - y).saturating_div(*ceil - *floor, Up).saturating_mul(*length) + }, + Self::SteppedDecreasing { begin, end, step, period } => + if y < *end { + Perbill::one() + } else { + period.int_mul((*begin - y.min(*begin) + step.less_epsilon()).int_div(*step)) + }, + Self::Reciprocal { factor, x_offset, y_offset } => { + let y = FixedI64::from(y); + let maybe_term = factor.checked_rounding_div(y - *y_offset, High); + maybe_term + .and_then(|term| (term - *x_offset).try_into_perthing().ok()) + .unwrap_or_else(Perbill::one) + }, + } + } + + /// Return `true` iff the `y` value is greater than the curve at the `x`. + pub fn passing(&self, x: Perbill, y: Perbill) -> bool { + y >= self.threshold(x) + } +} + +#[cfg(feature = "std")] +impl Debug for Curve { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + match self { + Self::LinearDecreasing { length, floor, ceil } => { + write!( + f, + "Linear[(0%, {:?}) -> ({:?}, {:?}) -> (100%, {:?})]", + ceil, length, floor, floor, + ) + }, + Self::SteppedDecreasing { begin, end, step, period } => { + write!( + f, + "Stepped[(0%, {:?}) -> (100%, {:?}) by ({:?}, {:?})]", + begin, end, period, step, + ) + }, + Self::Reciprocal { factor, x_offset, y_offset } => { + write!( + f, + "Reciprocal[factor of {:?}, x_offset of {:?}, y_offset of {:?}]", + factor, x_offset, y_offset, + ) + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::traits::ConstU32; + use sp_runtime::PerThing; + + const fn percent(x: u128) -> FixedI64 { + FixedI64::from_rational(x, 100) + } + + const TIP_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100)); + const TIP_SUP: Curve = Curve::make_reciprocal(1, 28, percent(4), percent(0), percent(50)); + const ROOT_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100)); + const ROOT_SUP: Curve = Curve::make_linear(28, 28, percent(0), percent(50)); + const WHITE_APP: Curve = + Curve::make_reciprocal(16, 28 * 24, percent(96), percent(50), percent(100)); + const WHITE_SUP: Curve = Curve::make_reciprocal(1, 28, percent(20), percent(10), percent(50)); + const SMALL_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100)); + const SMALL_SUP: Curve = Curve::make_reciprocal(8, 28, percent(1), percent(0), percent(50)); + const MID_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100)); + const MID_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50)); + const BIG_APP: Curve = Curve::make_linear(23, 28, percent(50), percent(100)); + const BIG_SUP: Curve = Curve::make_reciprocal(16, 28, percent(1), percent(0), percent(50)); + const HUGE_APP: Curve = Curve::make_linear(28, 28, percent(50), percent(100)); + const HUGE_SUP: Curve = Curve::make_reciprocal(20, 28, percent(1), percent(0), percent(50)); + const PARAM_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100)); + const PARAM_SUP: Curve = Curve::make_reciprocal(7, 28, percent(10), percent(0), percent(50)); + const ADMIN_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100)); + const ADMIN_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50)); + + // TODO: ceil for linear. + + #[test] + #[should_panic] + fn check_curves() { + TIP_APP.info(28u32, "Tip Approval"); + TIP_SUP.info(28u32, "Tip Support"); + ROOT_APP.info(28u32, "Root Approval"); + ROOT_SUP.info(28u32, "Root Support"); + WHITE_APP.info(28u32, "Whitelist Approval"); + WHITE_SUP.info(28u32, "Whitelist Support"); + SMALL_APP.info(28u32, "Small Spend Approval"); + SMALL_SUP.info(28u32, "Small Spend Support"); + MID_APP.info(28u32, "Mid Spend Approval"); + MID_SUP.info(28u32, "Mid Spend Support"); + BIG_APP.info(28u32, "Big Spend Approval"); + BIG_SUP.info(28u32, "Big Spend Support"); + HUGE_APP.info(28u32, "Huge Spend Approval"); + HUGE_SUP.info(28u32, "Huge Spend Support"); + PARAM_APP.info(28u32, "Mid-tier Parameter Change Approval"); + PARAM_SUP.info(28u32, "Mid-tier Parameter Change Support"); + ADMIN_APP.info(28u32, "Admin (e.g. Cancel Slash) Approval"); + ADMIN_SUP.info(28u32, "Admin (e.g. Cancel Slash) Support"); + assert!(false); + } + + #[test] + fn insert_sorted_works() { + let mut b: BoundedVec> = vec![20, 30, 40].try_into().unwrap(); + assert!(b.insert_sorted_by_key(10, |&x| x)); + assert_eq!(&b[..], &[10, 20, 30, 40][..]); + + assert!(b.insert_sorted_by_key(60, |&x| x)); + assert_eq!(&b[..], &[10, 20, 30, 40, 60][..]); + + assert!(b.insert_sorted_by_key(50, |&x| x)); + assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]); + + assert!(!b.insert_sorted_by_key(9, |&x| x)); + assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]); + + assert!(b.insert_sorted_by_key(11, |&x| x)); + assert_eq!(&b[..], &[11, 20, 30, 40, 50, 60][..]); + + assert!(b.insert_sorted_by_key(21, |&x| x)); + assert_eq!(&b[..], &[20, 21, 30, 40, 50, 60][..]); + + assert!(b.insert_sorted_by_key(61, |&x| x)); + assert_eq!(&b[..], &[21, 30, 40, 50, 60, 61][..]); + + assert!(b.insert_sorted_by_key(51, |&x| x)); + assert_eq!(&b[..], &[30, 40, 50, 51, 60, 61][..]); + } + + #[test] + fn translated_reciprocal_works() { + let c: Curve = Curve::Reciprocal { + factor: FixedI64::from_float(0.03125), + x_offset: FixedI64::from_float(0.0363306838226), + y_offset: FixedI64::from_float(0.139845532427), + }; + c.info(28u32, "Test"); + + for i in 0..9_696_969u32 { + let query = Perbill::from_rational(i, 9_696_969); + // Determine the nearest point in time when the query will be above threshold. + let delay_needed = c.delay(query); + // Ensure that it actually does pass at that time, or that it will never pass. + assert!(delay_needed.is_one() || c.passing(delay_needed, query)); + } + } + + #[test] + fn stepped_decreasing_works() { + fn pc(x: u32) -> Perbill { + Perbill::from_percent(x) + } + + let c = + Curve::SteppedDecreasing { begin: pc(80), end: pc(30), step: pc(10), period: pc(15) }; + + for i in 0..9_696_969u32 { + let query = Perbill::from_rational(i, 9_696_969); + // Determine the nearest point in time when the query will be above threshold. + let delay_needed = c.delay(query); + // Ensure that it actually does pass at that time, or that it will never pass. + assert!(delay_needed.is_one() || c.passing(delay_needed, query)); + } + + assert_eq!(c.threshold(pc(0)), pc(80)); + assert_eq!(c.threshold(pc(15).less_epsilon()), pc(80)); + assert_eq!(c.threshold(pc(15)), pc(70)); + assert_eq!(c.threshold(pc(30).less_epsilon()), pc(70)); + assert_eq!(c.threshold(pc(30)), pc(60)); + assert_eq!(c.threshold(pc(45).less_epsilon()), pc(60)); + assert_eq!(c.threshold(pc(45)), pc(50)); + assert_eq!(c.threshold(pc(60).less_epsilon()), pc(50)); + assert_eq!(c.threshold(pc(60)), pc(40)); + assert_eq!(c.threshold(pc(75).less_epsilon()), pc(40)); + assert_eq!(c.threshold(pc(75)), pc(30)); + assert_eq!(c.threshold(pc(100)), pc(30)); + + assert_eq!(c.delay(pc(100)), pc(0)); + assert_eq!(c.delay(pc(80)), pc(0)); + assert_eq!(c.delay(pc(80).less_epsilon()), pc(15)); + assert_eq!(c.delay(pc(70)), pc(15)); + assert_eq!(c.delay(pc(70).less_epsilon()), pc(30)); + assert_eq!(c.delay(pc(60)), pc(30)); + assert_eq!(c.delay(pc(60).less_epsilon()), pc(45)); + assert_eq!(c.delay(pc(50)), pc(45)); + assert_eq!(c.delay(pc(50).less_epsilon()), pc(60)); + assert_eq!(c.delay(pc(40)), pc(60)); + assert_eq!(c.delay(pc(40).less_epsilon()), pc(75)); + assert_eq!(c.delay(pc(30)), pc(75)); + assert_eq!(c.delay(pc(30).less_epsilon()), pc(100)); + assert_eq!(c.delay(pc(0)), pc(100)); + } +} diff --git a/frame/referenda/src/weights.rs b/frame/referenda/src/weights.rs new file mode 100644 index 0000000000000..f0eae517af743 --- /dev/null +++ b/frame/referenda/src/weights.rs @@ -0,0 +1,554 @@ +// 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_referenda +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-27, STEPS: `20`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `cob`, CPU: `` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/substrate +// benchmark +// pallet +// --chain=dev +// --steps=20 +// --repeat=1 +// --pallet=pallet-referenda +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/referenda/src/._weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_referenda. +pub trait WeightInfo { + fn submit() -> Weight; + fn place_decision_deposit_preparing() -> Weight; + fn place_decision_deposit_queued() -> Weight; + fn place_decision_deposit_not_queued() -> Weight; + fn place_decision_deposit_passing() -> Weight; + fn place_decision_deposit_failing() -> Weight; + fn refund_decision_deposit() -> Weight; + fn refund_submission_deposit() -> Weight; + fn cancel() -> Weight; + fn kill() -> Weight; + fn one_fewer_deciding_queue_empty() -> Weight; + fn one_fewer_deciding_failing() -> Weight; + fn one_fewer_deciding_passing() -> Weight; + fn nudge_referendum_requeued_insertion() -> Weight; + fn nudge_referendum_requeued_slide() -> Weight; + fn nudge_referendum_queued() -> Weight; + fn nudge_referendum_not_queued() -> Weight; + fn nudge_referendum_no_deposit() -> Weight; + fn nudge_referendum_preparing() -> Weight; + fn nudge_referendum_timed_out() -> Weight; + fn nudge_referendum_begin_deciding_failing() -> Weight; + fn nudge_referendum_begin_deciding_passing() -> Weight; + fn nudge_referendum_begin_confirming() -> Weight; + fn nudge_referendum_end_confirming() -> Weight; + fn nudge_referendum_continue_not_confirming() -> Weight; + fn nudge_referendum_continue_confirming() -> Weight; + fn nudge_referendum_approved() -> Weight; + fn nudge_referendum_rejected() -> Weight; +} + +/// Weights for pallet_referenda using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Referenda ReferendumCount (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:0 w:1) + fn submit() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(29_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn place_decision_deposit_preparing() -> Weight { + // Minimum execution time: 35_000 nanoseconds. + Weight::from_ref_time(35_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:0) + // Storage: Referenda TrackQueue (r:1 w:1) + fn place_decision_deposit_queued() -> Weight { + // Minimum execution time: 40_000 nanoseconds. + Weight::from_ref_time(40_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:0) + // Storage: Referenda TrackQueue (r:1 w:1) + fn place_decision_deposit_not_queued() -> Weight { + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(39_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn place_decision_deposit_passing() -> Weight { + // Minimum execution time: 43_000 nanoseconds. + Weight::from_ref_time(43_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn place_decision_deposit_failing() -> Weight { + // Minimum execution time: 84_000 nanoseconds. + Weight::from_ref_time(84_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + fn refund_decision_deposit() -> Weight { + // Minimum execution time: 25_000 nanoseconds. + Weight::from_ref_time(25_000_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + fn refund_submission_deposit() -> Weight { + // Minimum execution time: 25_000 nanoseconds. + Weight::from_ref_time(25_000_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn cancel() -> Weight { + // Minimum execution time: 26_000 nanoseconds. + Weight::from_ref_time(26_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn kill() -> Weight { + // Minimum execution time: 47_000 nanoseconds. + Weight::from_ref_time(47_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Referenda TrackQueue (r:1 w:0) + // Storage: Referenda DecidingCount (r:1 w:1) + fn one_fewer_deciding_queue_empty() -> Weight { + // Minimum execution time: 8_000 nanoseconds. + Weight::from_ref_time(8_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn one_fewer_deciding_failing() -> Weight { + // Minimum execution time: 88_000 nanoseconds. + Weight::from_ref_time(88_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn one_fewer_deciding_passing() -> Weight { + // Minimum execution time: 75_000 nanoseconds. + Weight::from_ref_time(75_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_requeued_insertion() -> Weight { + // Minimum execution time: 72_000 nanoseconds. + Weight::from_ref_time(72_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_requeued_slide() -> Weight { + // Minimum execution time: 56_000 nanoseconds. + Weight::from_ref_time(56_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:0) + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_queued() -> Weight { + // Minimum execution time: 55_000 nanoseconds. + Weight::from_ref_time(55_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:0) + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_not_queued() -> Weight { + // Minimum execution time: 60_000 nanoseconds. + Weight::from_ref_time(60_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_no_deposit() -> Weight { + // Minimum execution time: 22_000 nanoseconds. + Weight::from_ref_time(22_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_preparing() -> Weight { + // Minimum execution time: 21_000 nanoseconds. + Weight::from_ref_time(21_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + fn nudge_referendum_timed_out() -> Weight { + // Minimum execution time: 17_000 nanoseconds. + Weight::from_ref_time(17_000_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_begin_deciding_failing() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(29_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_begin_deciding_passing() -> Weight { + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(39_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_begin_confirming() -> Weight { + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(31_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_end_confirming() -> Weight { + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_continue_not_confirming() -> Weight { + // Minimum execution time: 28_000 nanoseconds. + Weight::from_ref_time(28_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_continue_confirming() -> Weight { + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + // Storage: Scheduler Lookup (r:1 w:1) + fn nudge_referendum_approved() -> Weight { + // Minimum execution time: 45_000 nanoseconds. + Weight::from_ref_time(45_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_rejected() -> Weight { + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Referenda ReferendumCount (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:0 w:1) + fn submit() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(29_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn place_decision_deposit_preparing() -> Weight { + // Minimum execution time: 35_000 nanoseconds. + Weight::from_ref_time(35_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:0) + // Storage: Referenda TrackQueue (r:1 w:1) + fn place_decision_deposit_queued() -> Weight { + // Minimum execution time: 40_000 nanoseconds. + Weight::from_ref_time(40_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:0) + // Storage: Referenda TrackQueue (r:1 w:1) + fn place_decision_deposit_not_queued() -> Weight { + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(39_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn place_decision_deposit_passing() -> Weight { + // Minimum execution time: 43_000 nanoseconds. + Weight::from_ref_time(43_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn place_decision_deposit_failing() -> Weight { + // Minimum execution time: 84_000 nanoseconds. + Weight::from_ref_time(84_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + fn refund_decision_deposit() -> Weight { + // Minimum execution time: 25_000 nanoseconds. + Weight::from_ref_time(25_000_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + fn refund_submission_deposit() -> Weight { + // Minimum execution time: 25_000 nanoseconds. + Weight::from_ref_time(25_000_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn cancel() -> Weight { + // Minimum execution time: 26_000 nanoseconds. + Weight::from_ref_time(26_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn kill() -> Weight { + // Minimum execution time: 47_000 nanoseconds. + Weight::from_ref_time(47_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Referenda TrackQueue (r:1 w:0) + // Storage: Referenda DecidingCount (r:1 w:1) + fn one_fewer_deciding_queue_empty() -> Weight { + // Minimum execution time: 8_000 nanoseconds. + Weight::from_ref_time(8_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn one_fewer_deciding_failing() -> Weight { + // Minimum execution time: 88_000 nanoseconds. + Weight::from_ref_time(88_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn one_fewer_deciding_passing() -> Weight { + // Minimum execution time: 75_000 nanoseconds. + Weight::from_ref_time(75_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_requeued_insertion() -> Weight { + // Minimum execution time: 72_000 nanoseconds. + Weight::from_ref_time(72_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_requeued_slide() -> Weight { + // Minimum execution time: 56_000 nanoseconds. + Weight::from_ref_time(56_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:0) + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_queued() -> Weight { + // Minimum execution time: 55_000 nanoseconds. + Weight::from_ref_time(55_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:0) + // Storage: Referenda TrackQueue (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_not_queued() -> Weight { + // Minimum execution time: 60_000 nanoseconds. + Weight::from_ref_time(60_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_no_deposit() -> Weight { + // Minimum execution time: 22_000 nanoseconds. + Weight::from_ref_time(22_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_preparing() -> Weight { + // Minimum execution time: 21_000 nanoseconds. + Weight::from_ref_time(21_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + fn nudge_referendum_timed_out() -> Weight { + // Minimum execution time: 17_000 nanoseconds. + Weight::from_ref_time(17_000_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_begin_deciding_failing() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(29_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Referenda DecidingCount (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_begin_deciding_passing() -> Weight { + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(39_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_begin_confirming() -> Weight { + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(31_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_end_confirming() -> Weight { + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_continue_not_confirming() -> Weight { + // Minimum execution time: 28_000 nanoseconds. + Weight::from_ref_time(28_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_continue_confirming() -> Weight { + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + // Storage: Scheduler Lookup (r:1 w:1) + fn nudge_referendum_approved() -> Weight { + // Minimum execution time: 45_000 nanoseconds. + Weight::from_ref_time(45_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn nudge_referendum_rejected() -> Weight { + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } +} diff --git a/frame/remark/Cargo.toml b/frame/remark/Cargo.toml new file mode 100644 index 0000000000000..a827d165f8389 --- /dev/null +++ b/frame/remark/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "pallet-remark" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Remark storage pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } + +[features] +default = ["std"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +std = [ + "frame-benchmarking?/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = [ "frame-support/try-runtime" ] diff --git a/frame/remark/README.md b/frame/remark/README.md new file mode 100644 index 0000000000000..f2341d6a0eaec --- /dev/null +++ b/frame/remark/README.md @@ -0,0 +1,6 @@ +# Remark Storage Pallet + +Allows storing arbitrary data off chain. + + +License: Apache-2.0 diff --git a/frame/remark/src/benchmarking.rs b/frame/remark/src/benchmarking.rs new file mode 100644 index 0000000000000..c0db8d5d3d59b --- /dev/null +++ b/frame/remark/src/benchmarking.rs @@ -0,0 +1,47 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 remarks pallet + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_system::{EventRecord, Pallet as System, RawOrigin}; +use sp_std::*; + +#[cfg(test)] +use crate::Pallet as Remark; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = System::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +benchmarks! { + store { + let l in 1 .. 1024*1024; + let caller: T::AccountId = whitelisted_caller(); + }: _(RawOrigin::Signed(caller.clone()), vec![0u8; l as usize]) + verify { + assert_last_event::(Event::Stored { sender: caller, content_hash: sp_io::hashing::blake2_256(&vec![0u8; l as usize]).into() }.into()); + } + + impl_benchmark_test_suite!(Remark, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/remark/src/lib.rs b/frame/remark/src/lib.rs new file mode 100644 index 0000000000000..80fe393c20f4a --- /dev/null +++ b/frame/remark/src/lib.rs @@ -0,0 +1,85 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Remark storage pallet. Indexes remarks and stores them off chain. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +pub mod weights; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +use sp_std::prelude::*; + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; +pub use weights::WeightInfo; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// Attempting to store empty data. + Empty, + /// Attempted to call `store` outside of block execution. + BadContext, + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + /// Index and store data off chain. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::store(remark.len() as u32))] + pub fn store(origin: OriginFor, remark: Vec) -> DispatchResultWithPostInfo { + ensure!(!remark.is_empty(), Error::::Empty); + let sender = ensure_signed(origin)?; + let content_hash = sp_io::hashing::blake2_256(&remark); + let extrinsic_index = >::extrinsic_index() + .ok_or_else(|| Error::::BadContext)?; + sp_io::transaction_index::index(extrinsic_index, remark.len() as u32, content_hash); + Self::deposit_event(Event::Stored { sender, content_hash: content_hash.into() }); + Ok(().into()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Stored data off chain. + Stored { sender: T::AccountId, content_hash: sp_core::H256 }, + } +} diff --git a/frame/remark/src/mock.rs b/frame/remark/src/mock.rs new file mode 100644 index 0000000000000..22467796cf37b --- /dev/null +++ b/frame/remark/src/mock.rs @@ -0,0 +1,79 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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 remarks pallet. + +use crate as pallet_remark; +use frame_support::traits::{ConstU16, ConstU32, ConstU64}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +pub type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Remark: pallet_remark::{ Pallet, Call, Event }, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_remark::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = GenesisConfig { system: Default::default() }.build_storage().unwrap(); + t.into() +} diff --git a/frame/remark/src/tests.rs b/frame/remark/src/tests.rs new file mode 100644 index 0000000000000..2278e3817b48a --- /dev/null +++ b/frame/remark/src/tests.rs @@ -0,0 +1,58 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Tests for remarks pallet. + +use super::{Error, Event, Pallet as Remark}; +use crate::mock::*; +use frame_support::{assert_noop, assert_ok}; +use frame_system::RawOrigin; + +#[test] +fn generates_event() { + new_test_ext().execute_with(|| { + let caller = 1; + let data = vec![0u8; 100]; + System::set_block_number(System::block_number() + 1); //otherwise event won't be registered. + assert_ok!(Remark::::store(RawOrigin::Signed(caller).into(), data.clone(),)); + let events = System::events(); + // this one we create as we expect it + let system_event: ::RuntimeEvent = Event::Stored { + content_hash: sp_io::hashing::blake2_256(&data).into(), + sender: caller, + } + .into(); + // this one we actually go into the system pallet and get the last event + // because we know its there from block +1 + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); + }); +} + +#[test] +fn does_not_store_empty() { + new_test_ext().execute_with(|| { + let caller = 1; + let data = vec![]; + System::set_block_number(System::block_number() + 1); //otherwise event won't be registered. + assert_noop!( + Remark::::store(RawOrigin::Signed(caller).into(), data.clone(),), + Error::::Empty + ); + assert!(System::events().is_empty()); + }); +} diff --git a/frame/remark/src/weights.rs b/frame/remark/src/weights.rs new file mode 100644 index 0000000000000..0d739657c852b --- /dev/null +++ b/frame/remark/src/weights.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_remark +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_remark +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/remark/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_remark. +pub trait WeightInfo { + fn store(l: u32, ) -> Weight; +} + +/// Weights for pallet_remark using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + /// The range of component `l` is `[1, 1048576]`. + fn store(l: u32, ) -> Weight { + // Minimum execution time: 17_017 nanoseconds. + Weight::from_ref_time(8_269_935 as u64) + // Standard Error: 1 + .saturating_add(Weight::from_ref_time(1_407 as u64).saturating_mul(l as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + /// The range of component `l` is `[1, 1048576]`. + fn store(l: u32, ) -> Weight { + // Minimum execution time: 17_017 nanoseconds. + Weight::from_ref_time(8_269_935 as u64) + // Standard Error: 1 + .saturating_add(Weight::from_ref_time(1_407 as u64).saturating_mul(l as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + } +} diff --git a/frame/root-offences/Cargo.toml b/frame/root-offences/Cargo.toml new file mode 100644 index 0000000000000..76eb832c88e1b --- /dev/null +++ b/frame/root-offences/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "pallet-root-offences" +version = "1.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME root offences pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = "../../frame/session", default-features = false } +pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../frame/staking" } + +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } + +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } + +[features] +runtime-benchmarks = [] +try-runtime = ["frame-support/try-runtime"] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "pallet-session/std", + "pallet-staking/std", + "scale-info/std", + "sp-runtime/std", +] diff --git a/frame/root-offences/README.md b/frame/root-offences/README.md new file mode 100644 index 0000000000000..c582158721816 --- /dev/null +++ b/frame/root-offences/README.md @@ -0,0 +1,5 @@ +# Root Offences Pallet + +Pallet that allows the root to create an offence. + +NOTE: This pallet should only be used for testing purposes. \ No newline at end of file diff --git a/frame/root-offences/src/lib.rs b/frame/root-offences/src/lib.rs new file mode 100644 index 0000000000000..ed039f46becc8 --- /dev/null +++ b/frame/root-offences/src/lib.rs @@ -0,0 +1,132 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! # Root Offences Pallet +//! Pallet that allows the root to create an offence. +//! +//! NOTE: This pallet should be used for testing purposes. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +use pallet_session::historical::IdentificationTuple; +use pallet_staking::{BalanceOf, Exposure, ExposureOf, Pallet as Staking}; +use sp_runtime::Perbill; +use sp_staking::offence::{DisableStrategy, OnOffenceHandler}; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: + frame_system::Config + + pallet_staking::Config + + pallet_session::Config::AccountId> + + pallet_session::historical::Config< + FullIdentification = Exposure< + ::AccountId, + BalanceOf, + >, + FullIdentificationOf = ExposureOf, + > + { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// An offence was created by root. + OffenceCreated { offenders: Vec<(T::AccountId, Perbill)> }, + } + + #[pallet::error] + pub enum Error { + /// Failed to get the active era from the staking pallet. + FailedToGetActiveEra, + } + + type OffenceDetails = sp_staking::offence::OffenceDetails< + ::AccountId, + IdentificationTuple, + >; + + #[pallet::call] + impl Pallet { + /// Allows the `root`, for example sudo to create an offence. + #[pallet::call_index(0)] + #[pallet::weight(T::DbWeight::get().reads(2))] + pub fn create_offence( + origin: OriginFor, + offenders: Vec<(T::AccountId, Perbill)>, + ) -> DispatchResult { + ensure_root(origin)?; + + let slash_fraction = + offenders.clone().into_iter().map(|(_, fraction)| fraction).collect::>(); + let offence_details = Self::get_offence_details(offenders.clone())?; + + Self::submit_offence(&offence_details, &slash_fraction); + Self::deposit_event(Event::OffenceCreated { offenders }); + Ok(()) + } + } + + impl Pallet { + /// Returns a vector of offenders that are going to be slashed. + fn get_offence_details( + offenders: Vec<(T::AccountId, Perbill)>, + ) -> Result>, DispatchError> { + let now = Staking::::active_era() + .map(|e| e.index) + .ok_or(Error::::FailedToGetActiveEra)?; + + Ok(offenders + .clone() + .into_iter() + .map(|(o, _)| OffenceDetails:: { + offender: (o.clone(), Staking::::eras_stakers(now, o)), + reporters: vec![], + }) + .collect()) + } + + /// Submits the offence by calling the `on_offence` function. + fn submit_offence(offenders: &[OffenceDetails], slash_fraction: &[Perbill]) { + let session_index = as frame_support::traits::ValidatorSet>::session_index(); + + as OnOffenceHandler< + T::AccountId, + IdentificationTuple, + Weight, + >>::on_offence(&offenders, &slash_fraction, session_index, DisableStrategy::WhenSlashed); + } + } +} diff --git a/frame/root-offences/src/mock.rs b/frame/root-offences/src/mock.rs new file mode 100644 index 0000000000000..65bfcad4b26fc --- /dev/null +++ b/frame/root-offences/src/mock.rs @@ -0,0 +1,359 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 as root_offences; + +use frame_election_provider_support::{onchain, SequentialPhragmen}; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64, GenesisBuild, Hooks, OneSessionHandler}, +}; +use pallet_staking::StakerStatus; +use sp_core::H256; +use sp_runtime::{ + curve::PiecewiseLinear, + testing::{Header, UintAuthorityId}, + traits::{BlakeTwo256, IdentityLookup, Zero}, +}; +use sp_staking::{EraIndex, SessionIndex}; +use sp_std::collections::btree_map::BTreeMap; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +type AccountId = u64; +type Balance = u64; +type BlockNumber = u64; + +pub const INIT_TIMESTAMP: u64 = 30_000; +pub const BLOCK_TIME: u64 = 1000; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, + Session: pallet_session::{Pallet, Call, Storage, Event, Config}, + RootOffences: root_offences::{Pallet, Call, Storage, Event}, + Historical: pallet_session::historical::{Pallet, Storage}, + } +); + +/// Another session handler struct to test on_disabled. +pub struct OtherSessionHandler; +impl OneSessionHandler for OtherSessionHandler { + type Key = UintAuthorityId; + + fn on_genesis_session<'a, I: 'a>(_: I) + where + I: Iterator, + AccountId: 'a, + { + } + + fn on_new_session<'a, I: 'a>(_: bool, _: I, _: I) + where + I: Iterator, + AccountId: 'a, + { + } + + fn on_disabled(_validator_index: u32) {} +} + +impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { + type Public = UintAuthorityId; +} + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); +} + +pallet_staking_reward_curve::build! { + const REWARD_CURVE: PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000u64, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} + +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; + type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = ConstU32<100>; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; +} + +pub struct OnStakerSlashMock(core::marker::PhantomData); +impl sp_staking::OnStakerSlash for OnStakerSlashMock { + fn on_slash( + _pool_account: &AccountId, + slashed_bonded: Balance, + slashed_chunks: &BTreeMap, + ) { + LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone())); + } +} + +parameter_types! { + pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; + pub static Offset: BlockNumber = 0; + pub const Period: BlockNumber = 1; + pub static SessionsPerEra: SessionIndex = 3; + 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 { + type MaxNominations = ConstU32<16>; + type Currency = Balances; + type CurrencyBalance = ::Balance; + type UnixTime = Timestamp; + type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type RewardRemainder = (); + type RuntimeEvent = RuntimeEvent; + type Slash = (); + type Reward = (); + type SessionsPerEra = SessionsPerEra; + type SlashDeferDuration = SlashDeferDuration; + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = BondingDuration; + type SessionInterface = Self; + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = Session; + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = OffendingValidatorsThreshold; + type ElectionProvider = onchain::OnChainExecution; + type GenesisElectionProvider = Self::ElectionProvider; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type OnStakerSlash = OnStakerSlashMock; + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); +} + +impl pallet_session::historical::Config for Test { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; +} + +sp_runtime::impl_opaque_keys! { + pub struct SessionKeys { + pub other: OtherSessionHandler, + } +} + +impl pallet_session::Config for Test { + type SessionManager = pallet_session::historical::NoteHistoricalRoot; + type Keys = SessionKeys; + type ShouldEndSession = pallet_session::PeriodicSessions; + type SessionHandler = (OtherSessionHandler,); + type RuntimeEvent = RuntimeEvent; + type ValidatorId = AccountId; + type ValidatorIdOf = pallet_staking::StashOf; + type NextSessionRotation = pallet_session::PeriodicSessions; + type WeightInfo = (); +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; +} + +pub struct ExtBuilder { + validator_count: u32, + minimum_validator_count: u32, + invulnerables: Vec, + balance_factor: Balance, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + validator_count: 2, + minimum_validator_count: 0, + invulnerables: vec![], + balance_factor: 1, + } + } +} + +impl ExtBuilder { + fn build(self) -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + //controllers + (10, self.balance_factor * 50), + (20, self.balance_factor * 50), + (30, self.balance_factor * 50), + (40, self.balance_factor * 50), + // stashes + (11, self.balance_factor * 1000), + (21, self.balance_factor * 1000), + (31, self.balance_factor * 500), + (41, self.balance_factor * 1000), + ], + } + .assimilate_storage(&mut storage) + .unwrap(); + + let stakers = vec![ + // (stash, ctrl, stake, status) + // these two will be elected in the default test where we elect 2. + (11, 10, 1000, StakerStatus::::Validator), + (21, 20, 1000, StakerStatus::::Validator), + // a loser validator + (31, 30, 500, StakerStatus::::Validator), + // an idle validator + (41, 40, 1000, StakerStatus::::Idle), + ]; + + let _ = pallet_staking::GenesisConfig:: { + stakers: stakers.clone(), + ..Default::default() + }; + + let _ = pallet_staking::GenesisConfig:: { + stakers: stakers.clone(), + validator_count: self.validator_count, + minimum_validator_count: self.minimum_validator_count, + invulnerables: self.invulnerables, + slash_reward_fraction: Perbill::from_percent(10), + ..Default::default() + } + .assimilate_storage(&mut storage); + + let _ = pallet_session::GenesisConfig:: { + keys: stakers + .into_iter() + .map(|(id, ..)| (id, id, SessionKeys { other: id.into() })) + .collect(), + } + .assimilate_storage(&mut storage); + + storage.into() + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + let mut ext = self.build(); + ext.execute_with(test); + } +} + +/// Progresses from the current block number (whatever that may be) to the `P * session_index + 1`. +pub(crate) fn start_session(session_index: SessionIndex) { + let end: u64 = if Offset::get().is_zero() { + (session_index as u64) * Period::get() + } else { + Offset::get() + (session_index.saturating_sub(1) as u64) * Period::get() + }; + run_to_block(end); + // session must have progressed properly. + assert_eq!( + Session::current_index(), + session_index, + "current session index = {}, expected = {}", + Session::current_index(), + session_index, + ); +} + +/// 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 +/// a block import/propose process where we first initialize the block, then execute some stuff (not +/// in the function), and then finalize the block. +pub(crate) fn run_to_block(n: BlockNumber) { + Staking::on_finalize(System::block_number()); + for b in (System::block_number() + 1)..=n { + System::set_block_number(b); + Session::on_initialize(b); + >::on_initialize(b); + Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); + if b != n { + Staking::on_finalize(System::block_number()); + } + } +} + +pub(crate) fn active_era() -> EraIndex { + Staking::active_era().unwrap().index +} diff --git a/frame/root-offences/src/tests.rs b/frame/root-offences/src/tests.rs new file mode 100644 index 0000000000000..a8b7d0a6d6aca --- /dev/null +++ b/frame/root-offences/src/tests.rs @@ -0,0 +1,94 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 frame_support::{assert_err, assert_ok}; +use mock::{active_era, start_session, Balances, ExtBuilder, RootOffences, RuntimeOrigin, System}; + +#[test] +fn create_offence_fails_given_signed_origin() { + use sp_runtime::traits::BadOrigin; + ExtBuilder::default().build_and_execute(|| { + let offenders = (&[]).to_vec(); + assert_err!(RootOffences::create_offence(RuntimeOrigin::signed(1), offenders), BadOrigin); + }) +} + +#[test] +fn create_offence_works_given_root_origin() { + ExtBuilder::default().build_and_execute(|| { + start_session(1); + + assert_eq!(active_era(), 0); + + assert_eq!(Balances::free_balance(11), 1000); + + let offenders = [(11, Perbill::from_percent(50))].to_vec(); + assert_ok!(RootOffences::create_offence(RuntimeOrigin::root(), offenders.clone())); + + System::assert_last_event(Event::OffenceCreated { offenders }.into()); + // the slash should be applied right away. + assert_eq!(Balances::free_balance(11), 500); + + // the other validator should keep his balance, because we only created + // an offences for the first validator. + assert_eq!(Balances::free_balance(21), 1000); + }) +} + +#[test] +fn create_offence_wont_slash_non_active_validators() { + ExtBuilder::default().build_and_execute(|| { + start_session(1); + + assert_eq!(active_era(), 0); + + // 31 is not an active validator. + assert_eq!(Balances::free_balance(31), 500); + + let offenders = [(31, Perbill::from_percent(20)), (11, Perbill::from_percent(20))].to_vec(); + assert_ok!(RootOffences::create_offence(RuntimeOrigin::root(), offenders.clone())); + + System::assert_last_event(Event::OffenceCreated { offenders }.into()); + + // so 31 didn't get slashed. + assert_eq!(Balances::free_balance(31), 500); + + // but 11 is an active validator so he got slashed. + assert_eq!(Balances::free_balance(11), 800); + }) +} + +#[test] +fn create_offence_wont_slash_idle() { + ExtBuilder::default().build_and_execute(|| { + start_session(1); + + assert_eq!(active_era(), 0); + + // 41 is idle. + assert_eq!(Balances::free_balance(41), 1000); + + let offenders = [(41, Perbill::from_percent(50))].to_vec(); + assert_ok!(RootOffences::create_offence(RuntimeOrigin::root(), offenders.clone())); + + System::assert_last_event(Event::OffenceCreated { offenders }.into()); + + // 41 didn't get slashed. + assert_eq!(Balances::free_balance(41), 1000); + }) +} diff --git a/frame/root-testing/Cargo.toml b/frame/root-testing/Cargo.toml new file mode 100644 index 0000000000000..4d3f70c5d0d9f --- /dev/null +++ b/frame/root-testing/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "pallet-root-testing" +version = "1.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME root testing pallet" +readme = "README.md" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +[features] +try-runtime = ["frame-support/try-runtime"] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-runtime/std", +] diff --git a/frame/root-testing/README.md b/frame/root-testing/README.md new file mode 100644 index 0000000000000..637430445a22f --- /dev/null +++ b/frame/root-testing/README.md @@ -0,0 +1,5 @@ +# Root Testing Pallet + +Pallet that contains extrinsics that can be usefull in testing. + +NOTE: This pallet should only be used for testing purposes and should not be used in production runtimes! \ No newline at end of file diff --git a/frame/root-testing/src/lib.rs b/frame/root-testing/src/lib.rs new file mode 100644 index 0000000000000..da67904967853 --- /dev/null +++ b/frame/root-testing/src/lib.rs @@ -0,0 +1,55 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! # Root Testing Pallet +//! +//! Pallet that contains extrinsics that can be usefull in testing. +//! +//! NOTE: This pallet should only be used for testing purposes and should not be used in production +//! runtimes! + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::dispatch::DispatchResult; +use sp_runtime::Perbill; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + /// A dispatch that will fill the block weight up to the given ratio. + #[pallet::call_index(0)] + #[pallet::weight(*_ratio * T::BlockWeights::get().max_block)] + pub fn fill_block(origin: OriginFor, _ratio: Perbill) -> DispatchResult { + ensure_root(origin)?; + Ok(()) + } + } +} diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index 62b21fe04c9df..25ac602681cc0 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -2,45 +2,47 @@ name = "pallet-scheduler" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" -license = "Unlicense" -homepage = "https://substrate.dev" +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -description = "FRAME example pallet" +description = "FRAME Scheduler pallet" readme = "README.md" [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -log = { version = "0.4.14", default-features = false } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core", default-features = false } +pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } [features] default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] std = [ "codec/std", - "scale-info/std", - "sp-runtime/std", - "frame-benchmarking/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "log/std", + "scale-info/std", "sp-io/std", + "sp-runtime/std", "sp-std/std", - "log/std", -] -runtime-benchmarks = [ - "frame-benchmarking", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", + "sp-weights/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/scheduler/src/benchmarking.rs b/frame/scheduler/src/benchmarking.rs index 2c164eaede229..e621c913b2386 100644 --- a/frame/scheduler/src/benchmarking.rs +++ b/frame/scheduler/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,48 +17,213 @@ //! Scheduler pallet benchmarking. -#![cfg(feature = "runtime-benchmarks")] - use super::*; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; -use frame_support::{ensure, traits::OnInitialize}; +use frame_benchmarking::{account, benchmarks}; +use frame_support::{ + ensure, + traits::{schedule::Priority, BoundedInline}, +}; use frame_system::RawOrigin; use sp_std::{prelude::*, vec}; use crate::Pallet as Scheduler; -use frame_system::Pallet as System; +use frame_system::Call as SystemCall; + +const SEED: u32 = 0; const BLOCK_NUMBER: u32 = 2; -// Add `n` named items to the schedule +type SystemOrigin = ::RuntimeOrigin; + +/// Add `n` items to the schedule. +/// +/// For `resolved`: +/// - ` +/// - `None`: aborted (hash without preimage) +/// - `Some(true)`: hash resolves into call if possible, plain call otherwise +/// - `Some(false)`: plain call fn fill_schedule(when: T::BlockNumber, n: u32) -> Result<(), &'static str> { - // Essentially a no-op call. - let call = frame_system::Call::set_storage { items: vec![] }; + let t = DispatchTime::At(when); + let origin: ::PalletsOrigin = frame_system::RawOrigin::Root.into(); for i in 0..n { - // Named schedule is strictly heavier than anonymous - Scheduler::::do_schedule_named( - i.encode(), - DispatchTime::At(when), - // Add periodicity - Some((T::BlockNumber::one(), 100)), - // HARD_DEADLINE priority means it gets executed no matter what - 0, - frame_system::RawOrigin::Root.into(), - call.clone().into(), - )?; + let call = make_call::(None); + let period = Some(((i + 100).into(), 100)); + let name = u32_to_name(i); + Scheduler::::do_schedule_named(name, t, period, 0, origin.clone(), call)?; } ensure!(Agenda::::get(when).len() == n as usize, "didn't fill schedule"); Ok(()) } +fn u32_to_name(i: u32) -> TaskName { + i.using_encoded(blake2_256) +} + +fn make_task( + periodic: bool, + named: bool, + signed: bool, + maybe_lookup_len: Option, + priority: Priority, +) -> ScheduledOf { + let call = make_call::(maybe_lookup_len); + let maybe_periodic = match periodic { + true => Some((100u32.into(), 100)), + false => None, + }; + let maybe_id = match named { + true => Some(u32_to_name(0)), + false => None, + }; + let origin = make_origin::(signed); + Scheduled { maybe_id, priority, call, maybe_periodic, origin, _phantom: PhantomData } +} + +fn bounded(len: u32) -> Option::RuntimeCall>> { + let call = + <::RuntimeCall>::from(SystemCall::remark { remark: vec![0; len as usize] }); + T::Preimages::bound(call).ok() +} + +fn make_call(maybe_lookup_len: Option) -> Bounded<::RuntimeCall> { + let bound = BoundedInline::bound() as u32; + let mut len = match maybe_lookup_len { + Some(len) => len.min(T::Preimages::MAX_LENGTH as u32 - 2).max(bound) - 3, + None => bound.saturating_sub(4), + }; + + loop { + let c = match bounded::(len) { + Some(x) => x, + None => { + len -= 1; + continue + }, + }; + if c.lookup_needed() == maybe_lookup_len.is_some() { + break c + } + if maybe_lookup_len.is_some() { + len += 1; + } else { + if len > 0 { + len -= 1; + } else { + break c + } + } + } +} + +fn make_origin(signed: bool) -> ::PalletsOrigin { + match signed { + true => frame_system::RawOrigin::Signed(account("origin", 0, SEED)).into(), + false => frame_system::RawOrigin::Root.into(), + } +} + benchmarks! { - schedule { + // `service_agendas` when no work is done. + service_agendas_base { + let now = T::BlockNumber::from(BLOCK_NUMBER); + IncompleteSince::::put(now - One::one()); + }: { + Scheduler::::service_agendas(&mut WeightMeter::max_limit(), now, 0); + } verify { + assert_eq!(IncompleteSince::::get(), Some(now - One::one())); + } + + // `service_agenda` when no work is done. + service_agenda_base { + let now = BLOCK_NUMBER.into(); let s in 0 .. T::MaxScheduledPerBlock::get(); + fill_schedule::(now, s)?; + let mut executed = 0; + }: { + Scheduler::::service_agenda(&mut WeightMeter::max_limit(), &mut executed, now, now, 0); + } verify { + assert_eq!(executed, 0); + } + + // `service_task` when the task is a non-periodic, non-named, non-fetched call which is not + // dispatched (e.g. due to being overweight). + service_task_base { + let now = BLOCK_NUMBER.into(); + let task = make_task::(false, false, false, None, 0); + // prevent any tasks from actually being executed as we only want the surrounding weight. + let mut counter = WeightMeter::from_limit(Weight::zero()); + }: { + let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); + } verify { + //assert_eq!(result, Ok(())); + } + + // `service_task` when the task is a non-periodic, non-named, fetched call (with a known + // preimage length) and which is not dispatched (e.g. due to being overweight). + service_task_fetched { + let s in (BoundedInline::bound() as u32) .. (T::Preimages::MAX_LENGTH as u32); + let now = BLOCK_NUMBER.into(); + let task = make_task::(false, false, false, Some(s), 0); + // prevent any tasks from actually being executed as we only want the surrounding weight. + let mut counter = WeightMeter::from_limit(Weight::zero()); + }: { + let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); + } verify { + } + + // `service_task` when the task is a non-periodic, named, non-fetched call which is not + // dispatched (e.g. due to being overweight). + service_task_named { + let now = BLOCK_NUMBER.into(); + let task = make_task::(false, true, false, None, 0); + // prevent any tasks from actually being executed as we only want the surrounding weight. + let mut counter = WeightMeter::from_limit(Weight::zero()); + }: { + let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); + } verify { + } + + // `service_task` when the task is a periodic, non-named, non-fetched call which is not + // dispatched (e.g. due to being overweight). + service_task_periodic { + let now = BLOCK_NUMBER.into(); + let task = make_task::(true, false, false, None, 0); + // prevent any tasks from actually being executed as we only want the surrounding weight. + let mut counter = WeightMeter::from_limit(Weight::zero()); + }: { + let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); + } verify { + } + + // `execute_dispatch` when the origin is `Signed`, not counting the dispatable's weight. + execute_dispatch_signed { + let mut counter = WeightMeter::max_limit(); + let origin = make_origin::(true); + let call = T::Preimages::realize(&make_call::(None)).unwrap().0; + }: { + assert!(Scheduler::::execute_dispatch(&mut counter, origin, call).is_ok()); + } + verify { + } + + // `execute_dispatch` when the origin is not `Signed`, not counting the dispatable's weight. + execute_dispatch_unsigned { + let mut counter = WeightMeter::max_limit(); + let origin = make_origin::(false); + let call = T::Preimages::realize(&make_call::(None)).unwrap().0; + }: { + assert!(Scheduler::::execute_dispatch(&mut counter, origin, call).is_ok()); + } + verify { + } + + schedule { + let s in 0 .. (T::MaxScheduledPerBlock::get() - 1); let when = BLOCK_NUMBER.into(); let periodic = Some((T::BlockNumber::one(), 100)); let priority = 0; // Essentially a no-op call. - let call = Box::new(frame_system::Call::set_storage { items: vec![] }.into()); + let call = Box::new(SystemCall::set_storage { items: vec![] }.into()); fill_schedule::(when, s)?; }: _(RawOrigin::Root, when, periodic, priority, call) @@ -75,10 +240,11 @@ benchmarks! { fill_schedule::(when, s)?; assert_eq!(Agenda::::get(when).len(), s as usize); - }: _(RawOrigin::Root, when, 0) + let schedule_origin = T::ScheduleOrigin::successful_origin(); + }: _>(schedule_origin, when, 0) verify { ensure!( - Lookup::::get(0.encode()).is_none(), + Lookup::::get(u32_to_name(0)).is_none(), "didn't remove from lookup" ); // Removed schedule is NONE @@ -89,13 +255,13 @@ benchmarks! { } schedule_named { - let s in 0 .. T::MaxScheduledPerBlock::get(); - let id = s.encode(); + let s in 0 .. (T::MaxScheduledPerBlock::get() - 1); + let id = u32_to_name(s); let when = BLOCK_NUMBER.into(); let periodic = Some((T::BlockNumber::one(), 100)); let priority = 0; // Essentially a no-op call. - let call = Box::new(frame_system::Call::set_storage { items: vec![] }.into()); + let call = Box::new(SystemCall::set_storage { items: vec![] }.into()); fill_schedule::(when, s)?; }: _(RawOrigin::Root, id, when, periodic, priority, call) @@ -111,10 +277,10 @@ benchmarks! { let when = BLOCK_NUMBER.into(); fill_schedule::(when, s)?; - }: _(RawOrigin::Root, 0.encode()) + }: _(RawOrigin::Root, u32_to_name(0)) verify { ensure!( - Lookup::::get(0.encode()).is_none(), + Lookup::::get(u32_to_name(0)).is_none(), "didn't remove from lookup" ); // Removed schedule is NONE @@ -124,21 +290,5 @@ benchmarks! { ); } - // TODO [#7141]: Make this more complex and flexible so it can be used in automation. - #[extra] - on_initialize { - let s in 0 .. T::MaxScheduledPerBlock::get(); - let when = BLOCK_NUMBER.into(); - fill_schedule::(when, s)?; - }: { Scheduler::::on_initialize(BLOCK_NUMBER.into()); } - verify { - assert_eq!(System::::event_count(), s); - // Next block should have all the schedules again - ensure!( - Agenda::::get(when + T::BlockNumber::one()).len() == s as usize, - "didn't append schedule" - ); - } + impl_benchmark_test_suite!(Scheduler, crate::mock::new_test_ext(), crate::mock::Test); } - -impl_benchmark_test_suite!(Scheduler, crate::tests::new_test_ext(), crate::tests::Test); diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index ca9e15812a76d..d6a66c5e2cb2c 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,26 +50,38 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "runtime-benchmarks")] mod benchmarking; +pub mod migration; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; pub mod weights; -use codec::{Codec, Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ - dispatch::{DispatchError, DispatchResult, Dispatchable, Parameter}, + dispatch::{ + DispatchError, DispatchResult, Dispatchable, GetDispatchInfo, Parameter, RawOrigin, + }, + ensure, traits::{ - schedule::{self, DispatchTime}, - EnsureOrigin, Get, IsType, OriginTrait, + schedule::{self, DispatchTime, MaybeHashed}, + Bounded, CallerTrait, EnsureOrigin, Get, Hash as PreimageHash, IsType, OriginTrait, + PalletInfoAccess, PrivilegeCmp, QueryPreimage, StorageVersion, StorePreimage, }, - weights::{GetDispatchInfo, Weight}, + weights::{Weight, WeightMeter}, }; -use frame_system::{self as system, ensure_signed}; -pub use pallet::*; +use frame_system::{self as system}; use scale_info::TypeInfo; +use sp_io::hashing::blake2_256; use sp_runtime::{ traits::{BadOrigin, One, Saturating, Zero}, - RuntimeDebug, + BoundedVec, RuntimeDebug, }; -use sp_std::{borrow::Borrow, marker::PhantomData, prelude::*}; +use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*}; + +pub use pallet::*; pub use weights::WeightInfo; /// Just a simple index for naming period tasks. @@ -77,6 +89,9 @@ pub type PeriodicIndex = u32; /// The location of a scheduled task that can be used to remove it. pub type TaskAddress = (BlockNumber, u32); +pub type CallOrHashOf = + MaybeHashed<::RuntimeCall, ::Hash>; + #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))] #[derive(Clone, RuntimeDebug, Encode, Decode)] struct ScheduledV1 { @@ -88,118 +103,171 @@ struct ScheduledV1 { /// Information regarding an item to be executed in the future. #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))] -#[derive(Clone, RuntimeDebug, Encode, Decode, TypeInfo)] -pub struct ScheduledV2 { +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct Scheduled { /// The unique identity for this task, if there is one. - maybe_id: Option>, + maybe_id: Option, /// This task's priority. priority: schedule::Priority, /// The call to be dispatched. call: Call, /// If the call is periodic, then this points to the information concerning that. maybe_periodic: Option>, - /// The origin to dispatch the call. + /// The origin with which to dispatch the call. origin: PalletsOrigin, _phantom: PhantomData, } -/// The current version of Scheduled struct. -pub type Scheduled = - ScheduledV2; - -// A value placed in storage that represents the current version of the Scheduler storage. -// This value is used by the `on_runtime_upgrade` logic to determine whether we run -// storage migration logic. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] -enum Releases { - V1, - V2, -} - -impl Default for Releases { - fn default() -> Self { - Releases::V1 +use crate::{Scheduled as ScheduledV3, Scheduled as ScheduledV2}; + +pub type ScheduledV2Of = ScheduledV2< + Vec, + ::RuntimeCall, + ::BlockNumber, + ::PalletsOrigin, + ::AccountId, +>; + +pub type ScheduledV3Of = ScheduledV3< + Vec, + CallOrHashOf, + ::BlockNumber, + ::PalletsOrigin, + ::AccountId, +>; + +pub type ScheduledOf = Scheduled< + TaskName, + Bounded<::RuntimeCall>, + ::BlockNumber, + ::PalletsOrigin, + ::AccountId, +>; + +pub(crate) trait MarginalWeightInfo: WeightInfo { + fn service_task(maybe_lookup_len: Option, named: bool, periodic: bool) -> Weight { + let base = Self::service_task_base(); + let mut total = match maybe_lookup_len { + None => base, + Some(l) => Self::service_task_fetched(l as u32), + }; + if named { + total.saturating_accrue(Self::service_task_named().saturating_sub(base)); + } + if periodic { + total.saturating_accrue(Self::service_task_periodic().saturating_sub(base)); + } + total } } +impl MarginalWeightInfo for T {} #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{dispatch::PostDispatchInfo, pallet_prelude::*}; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); /// `system::Config` should always be included in our implied traits. #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The aggregated origin which the dispatch will take. - type Origin: OriginTrait + type RuntimeOrigin: OriginTrait + From - + IsType<::Origin>; + + IsType<::RuntimeOrigin>; /// The caller origin, overarching type of all pallets origins. - type PalletsOrigin: From> + Codec + Clone + Eq + TypeInfo; + type PalletsOrigin: From> + + CallerTrait + + MaxEncodedLen; /// The aggregated call type. - type Call: Parameter - + Dispatchable::Origin> - + GetDispatchInfo + type RuntimeCall: Parameter + + Dispatchable< + RuntimeOrigin = ::RuntimeOrigin, + PostInfo = PostDispatchInfo, + > + GetDispatchInfo + From>; - /// The maximum weight that may be scheduled per block for any dispatchables of less - /// priority than `schedule::HARD_DEADLINE`. + /// The maximum weight that may be scheduled per block for any dispatchables. #[pallet::constant] type MaximumWeight: Get; /// Required origin to schedule or cancel calls. - type ScheduleOrigin: EnsureOrigin<::Origin>; + type ScheduleOrigin: EnsureOrigin<::RuntimeOrigin>; + + /// Compare the privileges of origins. + /// + /// This will be used when canceling a task, to ensure that the origin that tries + /// to cancel has greater or equal privileges as the origin that created the scheduled task. + /// + /// For simplicity the [`EqualPrivilegeOnly`](frame_support::traits::EqualPrivilegeOnly) can + /// be used. This will only check if two given origins are equal. + type OriginPrivilegeCmp: PrivilegeCmp; /// The maximum number of scheduled calls in the queue for a single block. - /// Not strictly enforced, but used for weight estimation. #[pallet::constant] type MaxScheduledPerBlock: Get; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// The preimage provider with which we look up call hashes to get the call. + type Preimages: QueryPreimage + StorePreimage; } + #[pallet::storage] + pub type IncompleteSince = StorageValue<_, T::BlockNumber>; + /// Items to be executed, indexed by the block number that they should be executed on. #[pallet::storage] pub type Agenda = StorageMap< _, Twox64Concat, T::BlockNumber, - Vec::Call, T::BlockNumber, T::PalletsOrigin, T::AccountId>>>, + BoundedVec>, T::MaxScheduledPerBlock>, ValueQuery, >; - /// Lookup from identity to the block number and index of the task. - #[pallet::storage] - pub(crate) type Lookup = - StorageMap<_, Twox64Concat, Vec, TaskAddress>; - - /// Storage version of the pallet. + /// Lookup from a name to the block number and index of the task. /// - /// New networks start with last version. + /// For v3 -> v4 the previously unbounded identities are Blake2-256 hashed to form the v4 + /// identities. #[pallet::storage] - pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; + pub(crate) type Lookup = + StorageMap<_, Twox64Concat, TaskName, TaskAddress>; /// Events type. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Scheduled some task. \[when, index\] - Scheduled(T::BlockNumber, u32), - /// Canceled some task. \[when, index\] - Canceled(T::BlockNumber, u32), - /// Dispatched some task. \[task, id, result\] - Dispatched(TaskAddress, Option>, DispatchResult), + /// Scheduled some task. + Scheduled { when: T::BlockNumber, index: u32 }, + /// Canceled some task. + Canceled { when: T::BlockNumber, index: u32 }, + /// Dispatched some task. + Dispatched { + task: TaskAddress, + id: Option, + result: DispatchResult, + }, + /// The call for the provided hash was not found so the task has been aborted. + CallUnavailable { task: TaskAddress, id: Option }, + /// The given task was unable to be renewed since the agenda is full at that block. + PeriodicFailed { task: TaskAddress, id: Option }, + /// The given task can never be executed since it is overweight. + PermanentlyOverweight { task: TaskAddress, id: Option }, } #[pallet::error] @@ -212,230 +280,84 @@ pub mod pallet { TargetBlockNumberInPast, /// Reschedule failed because it does not change scheduled time. RescheduleNoChange, - } - - #[pallet::genesis_config] - pub struct GenesisConfig; - - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self - } - } - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - StorageVersion::::put(Releases::V2); - } + /// Attempt to use a non-named function on a named task. + Named, } #[pallet::hooks] impl Hooks> for Pallet { /// Execute the scheduled calls - /// - /// # - /// - S = Number of already scheduled calls - /// - N = Named scheduled calls - /// - P = Periodic Calls - /// - Base Weight: 9.243 + 23.45 * S µs - /// - DB Weight: - /// - Read: Agenda + Lookup * N + Agenda(Future) * P - /// - Write: Agenda + Lookup * N + Agenda(future) * P - /// # fn on_initialize(now: T::BlockNumber) -> Weight { - let limit = T::MaximumWeight::get(); - let mut queued = Agenda::::take(now) - .into_iter() - .enumerate() - .filter_map(|(index, s)| s.map(|inner| (index as u32, inner))) - .collect::>(); - if queued.len() as u32 > T::MaxScheduledPerBlock::get() { - log::warn!( - target: "runtime::scheduler", - "Warning: This block has more items queued in Scheduler than \ - expected from the runtime configuration. An update might be needed." - ); - } - queued.sort_by_key(|(_, s)| s.priority); - let base_weight: Weight = T::DbWeight::get().reads_writes(1, 2); // Agenda + Agenda(next) - let mut total_weight: Weight = 0; - queued - .into_iter() - .enumerate() - .scan(base_weight, |cumulative_weight, (order, (index, s))| { - *cumulative_weight = - cumulative_weight.saturating_add(s.call.get_dispatch_info().weight); - - let origin = - <::Origin as From>::from(s.origin.clone()) - .into(); - - if ensure_signed(origin).is_ok() { - // AccountData for inner call origin accountdata. - *cumulative_weight = - cumulative_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - } - - if s.maybe_id.is_some() { - // Remove/Modify Lookup - *cumulative_weight = - cumulative_weight.saturating_add(T::DbWeight::get().writes(1)); - } - if s.maybe_periodic.is_some() { - // Read/Write Agenda for future block - *cumulative_weight = - cumulative_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - } - - Some((order, index, *cumulative_weight, s)) - }) - .filter_map(|(order, index, cumulative_weight, mut s)| { - // We allow a scheduled call if any is true: - // - It's priority is `HARD_DEADLINE` - // - It does not push the weight past the limit. - // - It is the first item in the schedule - if s.priority <= schedule::HARD_DEADLINE || - cumulative_weight <= limit || - order == 0 - { - let r = s.call.clone().dispatch(s.origin.clone().into()); - let maybe_id = s.maybe_id.clone(); - if let &Some((period, count)) = &s.maybe_periodic { - if count > 1 { - s.maybe_periodic = Some((period, count - 1)); - } else { - s.maybe_periodic = None; - } - let next = now + period; - // If scheduled is named, place it's information in `Lookup` - if let Some(ref id) = s.maybe_id { - let next_index = Agenda::::decode_len(now + period).unwrap_or(0); - Lookup::::insert(id, (next, next_index as u32)); - } - Agenda::::append(next, Some(s)); - } else { - if let Some(ref id) = s.maybe_id { - Lookup::::remove(id); - } - } - Self::deposit_event(Event::Dispatched( - (now, index), - maybe_id, - r.map(|_| ()).map_err(|e| e.error), - )); - total_weight = cumulative_weight; - None - } else { - Some(Some(s)) - } - }) - .for_each(|unused| { - let next = now + One::one(); - Agenda::::append(next, unused); - }); - - total_weight + let mut weight_counter = WeightMeter::from_limit(T::MaximumWeight::get()); + Self::service_agendas(&mut weight_counter, now, u32::max_value()); + weight_counter.consumed } } #[pallet::call] impl Pallet { /// Anonymously schedule a task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 22.29 + .126 * S µs - /// - DB Weight: - /// - Read: Agenda - /// - Write: Agenda - /// - Will use base weight of 25 which should be good for up to 30 scheduled calls - /// # + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] pub fn schedule( origin: OriginFor, when: T::BlockNumber, maybe_periodic: Option>, priority: schedule::Priority, - call: Box<::Call>, + call: Box<::RuntimeCall>, ) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); + let origin = ::RuntimeOrigin::from(origin); Self::do_schedule( DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), - *call, + T::Preimages::bound(*call)?, )?; Ok(()) } /// Cancel an anonymously scheduled task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 22.15 + 2.869 * S µs - /// - DB Weight: - /// - Read: Agenda - /// - Write: Agenda, Lookup - /// - Will use base weight of 100 which should be good for up to 30 scheduled calls - /// # + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))] pub fn cancel(origin: OriginFor, when: T::BlockNumber, index: u32) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); + let origin = ::RuntimeOrigin::from(origin); Self::do_cancel(Some(origin.caller().clone()), (when, index))?; Ok(()) } /// Schedule a named task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 29.6 + .159 * S µs - /// - DB Weight: - /// - Read: Agenda, Lookup - /// - Write: Agenda, Lookup - /// - Will use base weight of 35 which should be good for more than 30 scheduled calls - /// # + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] pub fn schedule_named( origin: OriginFor, - id: Vec, + id: TaskName, when: T::BlockNumber, maybe_periodic: Option>, priority: schedule::Priority, - call: Box<::Call>, + call: Box<::RuntimeCall>, ) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); + let origin = ::RuntimeOrigin::from(origin); Self::do_schedule_named( id, DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), - *call, + T::Preimages::bound(*call)?, )?; Ok(()) } /// Cancel a named scheduled task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 24.91 + 2.907 * S µs - /// - DB Weight: - /// - Read: Agenda, Lookup - /// - Write: Agenda, Lookup - /// - Will use base weight of 100 which should be good for up to 30 scheduled calls - /// # + #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))] - pub fn cancel_named(origin: OriginFor, id: Vec) -> DispatchResult { + pub fn cancel_named(origin: OriginFor, id: TaskName) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); + let origin = ::RuntimeOrigin::from(origin); Self::do_cancel_named(Some(origin.caller().clone()), id)?; Ok(()) } @@ -445,22 +367,23 @@ pub mod pallet { /// # /// Same as [`schedule`]. /// # + #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] pub fn schedule_after( origin: OriginFor, after: T::BlockNumber, maybe_periodic: Option>, priority: schedule::Priority, - call: Box<::Call>, + call: Box<::RuntimeCall>, ) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); + let origin = ::RuntimeOrigin::from(origin); Self::do_schedule( DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), - *call, + T::Preimages::bound(*call)?, )?; Ok(()) } @@ -470,71 +393,296 @@ pub mod pallet { /// # /// Same as [`schedule_named`](Self::schedule_named). /// # + #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] pub fn schedule_named_after( origin: OriginFor, - id: Vec, + id: TaskName, after: T::BlockNumber, maybe_periodic: Option>, priority: schedule::Priority, - call: Box<::Call>, + call: Box<::RuntimeCall>, ) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); + let origin = ::RuntimeOrigin::from(origin); Self::do_schedule_named( id, DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), - *call, + T::Preimages::bound(*call)?, )?; Ok(()) } } } -impl Pallet { - /// Migrate storage format from V1 to V2. - /// Return true if migration is performed. - pub fn migrate_v1_to_t2() -> bool { - if StorageVersion::::get() == Releases::V1 { - StorageVersion::::put(Releases::V2); - - Agenda::::translate::< - Vec::Call, T::BlockNumber>>>, - _, - >(|_, agenda| { - Some( - agenda - .into_iter() - .map(|schedule| { - schedule.map(|schedule| ScheduledV2 { - maybe_id: schedule.maybe_id, +impl> Pallet { + /// Migrate storage format from V1 to V4. + /// + /// Returns the weight consumed by this migration. + pub fn migrate_v1_to_v4() -> Weight { + use migration::v1 as old; + let mut weight = T::DbWeight::get().reads_writes(1, 1); + + // Delete all undecodable values. + // `StorageMap::translate` is not enough since it just skips them and leaves the keys in. + let keys = old::Agenda::::iter_keys().collect::>(); + for key in keys { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if let Err(_) = old::Agenda::::try_get(&key) { + weight.saturating_accrue(T::DbWeight::get().writes(1)); + old::Agenda::::remove(&key); + log::warn!("Deleted undecodable agenda"); + } + } + + Agenda::::translate::< + Vec::RuntimeCall, T::BlockNumber>>>, + _, + >(|_, agenda| { + Some(BoundedVec::truncate_from( + agenda + .into_iter() + .map(|schedule| { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + schedule.and_then(|schedule| { + if let Some(id) = schedule.maybe_id.as_ref() { + let name = blake2_256(id); + if let Some(item) = old::Lookup::::take(id) { + Lookup::::insert(name, item); + } + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + + let call = T::Preimages::bound(schedule.call).ok()?; + + if call.lookup_needed() { + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); + } + + Some(Scheduled { + maybe_id: schedule.maybe_id.map(|x| blake2_256(&x[..])), priority: schedule.priority, - call: schedule.call, + call, maybe_periodic: schedule.maybe_periodic, origin: system::RawOrigin::Root.into(), _phantom: Default::default(), }) }) - .collect::>(), - ) - }); + }) + .collect::>(), + )) + }); - true - } else { - false + #[allow(deprecated)] + frame_support::storage::migration::remove_storage_prefix( + Self::name().as_bytes(), + b"StorageVersion", + &[], + ); + + StorageVersion::new(4).put::(); + + weight + T::DbWeight::get().writes(2) + } + + /// Migrate storage format from V2 to V4. + /// + /// Returns the weight consumed by this migration. + pub fn migrate_v2_to_v4() -> Weight { + use migration::v2 as old; + let mut weight = T::DbWeight::get().reads_writes(1, 1); + + // Delete all undecodable values. + // `StorageMap::translate` is not enough since it just skips them and leaves the keys in. + let keys = old::Agenda::::iter_keys().collect::>(); + for key in keys { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if let Err(_) = old::Agenda::::try_get(&key) { + weight.saturating_accrue(T::DbWeight::get().writes(1)); + old::Agenda::::remove(&key); + log::warn!("Deleted undecodable agenda"); + } + } + + Agenda::::translate::>>, _>(|_, agenda| { + Some(BoundedVec::truncate_from( + agenda + .into_iter() + .map(|schedule| { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + schedule.and_then(|schedule| { + if let Some(id) = schedule.maybe_id.as_ref() { + let name = blake2_256(id); + if let Some(item) = old::Lookup::::take(id) { + Lookup::::insert(name, item); + } + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + + let call = T::Preimages::bound(schedule.call).ok()?; + if call.lookup_needed() { + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); + } + + Some(Scheduled { + maybe_id: schedule.maybe_id.map(|x| blake2_256(&x[..])), + priority: schedule.priority, + call, + maybe_periodic: schedule.maybe_periodic, + origin: schedule.origin, + _phantom: Default::default(), + }) + }) + }) + .collect::>(), + )) + }); + + #[allow(deprecated)] + frame_support::storage::migration::remove_storage_prefix( + Self::name().as_bytes(), + b"StorageVersion", + &[], + ); + + StorageVersion::new(4).put::(); + + weight + T::DbWeight::get().writes(2) + } + + /// Migrate storage format from V3 to V4. + /// + /// Returns the weight consumed by this migration. + #[allow(deprecated)] + pub fn migrate_v3_to_v4() -> Weight { + use migration::v3 as old; + let mut weight = T::DbWeight::get().reads_writes(2, 1); + + // Delete all undecodable values. + // `StorageMap::translate` is not enough since it just skips them and leaves the keys in. + let blocks = old::Agenda::::iter_keys().collect::>(); + for block in blocks { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if let Err(_) = old::Agenda::::try_get(&block) { + weight.saturating_accrue(T::DbWeight::get().writes(1)); + old::Agenda::::remove(&block); + log::warn!("Deleted undecodable agenda of block: {:?}", block); + } } + + Agenda::::translate::>>, _>(|block, agenda| { + log::info!("Migrating agenda of block: {:?}", &block); + Some(BoundedVec::truncate_from( + agenda + .into_iter() + .map(|schedule| { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + schedule + .and_then(|schedule| { + if let Some(id) = schedule.maybe_id.as_ref() { + let name = blake2_256(id); + if let Some(item) = old::Lookup::::take(id) { + Lookup::::insert(name, item); + log::info!("Migrated name for id: {:?}", id); + } else { + log::error!("No name in Lookup for id: {:?}", &id); + } + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } else { + log::info!("Schedule is unnamed"); + } + + let call = match schedule.call { + MaybeHashed::Hash(h) => { + let bounded = Bounded::from_legacy_hash(h); + // Check that the call can be decoded in the new runtime. + if let Err(err) = T::Preimages::peek::< + ::RuntimeCall, + >(&bounded) + { + log::error!( + "Dropping undecodable call {}: {:?}", + &h, + &err + ); + return None + } + weight.saturating_accrue(T::DbWeight::get().reads(1)); + log::info!("Migrated call by hash, hash: {:?}", h); + bounded + }, + MaybeHashed::Value(v) => { + let call = T::Preimages::bound(v) + .map_err(|e| { + log::error!("Could not bound Call: {:?}", e) + }) + .ok()?; + if call.lookup_needed() { + weight.saturating_accrue( + T::DbWeight::get().reads_writes(0, 1), + ); + } + log::info!( + "Migrated call by value, hash: {:?}", + call.hash() + ); + call + }, + }; + + Some(Scheduled { + maybe_id: schedule.maybe_id.map(|x| blake2_256(&x[..])), + priority: schedule.priority, + call, + maybe_periodic: schedule.maybe_periodic, + origin: schedule.origin, + _phantom: Default::default(), + }) + }) + .or_else(|| { + log::info!("Schedule in agenda for block {:?} is empty - nothing to do here.", &block); + None + }) + }) + .collect::>(), + )) + }); + + #[allow(deprecated)] + frame_support::storage::migration::remove_storage_prefix( + Self::name().as_bytes(), + b"StorageVersion", + &[], + ); + + StorageVersion::new(4).put::(); + + weight + T::DbWeight::get().writes(2) } +} +impl Pallet { /// Helper to migrate scheduler when the pallet origin type has changed. pub fn migrate_origin + codec::Decode>() { Agenda::::translate::< - Vec::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, + Vec< + Option< + Scheduled< + TaskName, + Bounded<::RuntimeCall>, + T::BlockNumber, + OldOrigin, + T::AccountId, + >, + >, + >, _, >(|_, agenda| { - Some( + Some(BoundedVec::truncate_from( agenda .into_iter() .map(|schedule| { @@ -548,7 +696,7 @@ impl Pallet { }) }) .collect::>(), - ) + )) }); } @@ -569,12 +717,47 @@ impl Pallet { Ok(when) } + fn place_task( + when: T::BlockNumber, + what: ScheduledOf, + ) -> Result, (DispatchError, ScheduledOf)> { + let maybe_name = what.maybe_id; + let index = Self::push_to_agenda(when, what)?; + let address = (when, index); + if let Some(name) = maybe_name { + Lookup::::insert(name, address) + } + Self::deposit_event(Event::Scheduled { when: address.0, index: address.1 }); + Ok(address) + } + + fn push_to_agenda( + when: T::BlockNumber, + what: ScheduledOf, + ) -> Result)> { + let mut agenda = Agenda::::get(when); + let index = if (agenda.len() as u32) < T::MaxScheduledPerBlock::get() { + // will always succeed due to the above check. + let _ = agenda.try_push(Some(what)); + agenda.len() as u32 - 1 + } else { + if let Some(hole_index) = agenda.iter().position(|i| i.is_none()) { + agenda[hole_index] = Some(what); + hole_index as u32 + } else { + return Err((DispatchError::Exhausted, what)) + } + }; + Agenda::::insert(when, agenda); + Ok(index) + } + fn do_schedule( when: DispatchTime, maybe_periodic: Option>, priority: schedule::Priority, origin: T::PalletsOrigin, - call: ::Call, + call: Bounded<::RuntimeCall>, ) -> Result, DispatchError> { let when = Self::resolve_time(when)?; @@ -583,26 +766,15 @@ impl Pallet { .filter(|p| p.1 > 1 && !p.0.is_zero()) // Remove one from the number of repetitions since we will schedule one now. .map(|(p, c)| (p, c - 1)); - let s = Some(Scheduled { + let task = Scheduled { maybe_id: None, priority, call, maybe_periodic, origin, - _phantom: PhantomData::::default(), - }); - Agenda::::append(when, s); - let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; - if index > T::MaxScheduledPerBlock::get() { - log::warn!( - target: "runtime::scheduler", - "Warning: There are more items queued in the Scheduler than \ - expected from the runtime configuration. An update might be needed.", - ); - } - Self::deposit_event(Event::Scheduled(when, index)); - - Ok((when, index)) + _phantom: PhantomData, + }; + Self::place_task(when, task).map_err(|x| x.0) } fn do_cancel( @@ -612,9 +784,12 @@ impl Pallet { let scheduled = Agenda::::try_mutate(when, |agenda| { agenda.get_mut(index as usize).map_or( Ok(None), - |s| -> Result>, DispatchError> { + |s| -> Result>, DispatchError> { if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { - if *o != s.origin { + if matches!( + T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin), + Some(Ordering::Less) | None + ) { return Err(BadOrigin.into()) } }; @@ -623,13 +798,14 @@ impl Pallet { ) })?; if let Some(s) = scheduled { + T::Preimages::drop(&s.call); if let Some(id) = s.maybe_id { Lookup::::remove(id); } - Self::deposit_event(Event::Canceled(when, index)); + Self::deposit_event(Event::Canceled { when, index }); Ok(()) } else { - Err(Error::::NotFound)? + return Err(Error::::NotFound.into()) } } @@ -643,31 +819,27 @@ impl Pallet { return Err(Error::::RescheduleNoChange.into()) } - Agenda::::try_mutate(when, |agenda| -> DispatchResult { + let task = Agenda::::try_mutate(when, |agenda| { let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; - let task = task.take().ok_or(Error::::NotFound)?; - Agenda::::append(new_time, Some(task)); - Ok(()) + ensure!(!matches!(task, Some(Scheduled { maybe_id: Some(_), .. })), Error::::Named); + task.take().ok_or(Error::::NotFound) })?; + Self::deposit_event(Event::Canceled { when, index }); - let new_index = Agenda::::decode_len(new_time).unwrap_or(1) as u32 - 1; - Self::deposit_event(Event::Canceled(when, index)); - Self::deposit_event(Event::Scheduled(new_time, new_index)); - - Ok((new_time, new_index)) + Self::place_task(new_time, task).map_err(|x| x.0) } fn do_schedule_named( - id: Vec, + id: TaskName, when: DispatchTime, maybe_periodic: Option>, priority: schedule::Priority, origin: T::PalletsOrigin, - call: ::Call, + call: Bounded<::RuntimeCall>, ) -> Result, DispatchError> { // ensure id it is unique if Lookup::::contains_key(&id) { - return Err(Error::::FailedToSchedule)? + return Err(Error::::FailedToSchedule.into()) } let when = Self::resolve_time(when)?; @@ -678,100 +850,297 @@ impl Pallet { // Remove one from the number of repetitions since we will schedule one now. .map(|(p, c)| (p, c - 1)); - let s = Scheduled { - maybe_id: Some(id.clone()), + let task = Scheduled { + maybe_id: Some(id), priority, call, maybe_periodic, origin, _phantom: Default::default(), }; - Agenda::::append(when, Some(s)); - let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; - if index > T::MaxScheduledPerBlock::get() { - log::warn!( - target: "runtime::scheduler", - "Warning: There are more items queued in the Scheduler than \ - expected from the runtime configuration. An update might be needed.", - ); - } - let address = (when, index); - Lookup::::insert(&id, &address); - Self::deposit_event(Event::Scheduled(when, index)); - - Ok(address) + Self::place_task(when, task).map_err(|x| x.0) } - fn do_cancel_named(origin: Option, id: Vec) -> DispatchResult { + fn do_cancel_named(origin: Option, id: TaskName) -> DispatchResult { Lookup::::try_mutate_exists(id, |lookup| -> DispatchResult { if let Some((when, index)) = lookup.take() { let i = index as usize; Agenda::::try_mutate(when, |agenda| -> DispatchResult { if let Some(s) = agenda.get_mut(i) { if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { - if *o != s.origin { + if matches!( + T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin), + Some(Ordering::Less) | None + ) { return Err(BadOrigin.into()) } + T::Preimages::drop(&s.call); } *s = None; } Ok(()) })?; - Self::deposit_event(Event::Canceled(when, index)); + Self::deposit_event(Event::Canceled { when, index }); Ok(()) } else { - Err(Error::::NotFound)? + return Err(Error::::NotFound.into()) } }) } fn do_reschedule_named( - id: Vec, + id: TaskName, new_time: DispatchTime, ) -> Result, DispatchError> { let new_time = Self::resolve_time(new_time)?; - Lookup::::try_mutate_exists( - id, - |lookup| -> Result, DispatchError> { - let (when, index) = lookup.ok_or(Error::::NotFound)?; + let lookup = Lookup::::get(id); + let (when, index) = lookup.ok_or(Error::::NotFound)?; - if new_time == when { - return Err(Error::::RescheduleNoChange.into()) - } + if new_time == when { + return Err(Error::::RescheduleNoChange.into()) + } - Agenda::::try_mutate(when, |agenda| -> DispatchResult { - let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; - let task = task.take().ok_or(Error::::NotFound)?; - Agenda::::append(new_time, Some(task)); + let task = Agenda::::try_mutate(when, |agenda| { + let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; + task.take().ok_or(Error::::NotFound) + })?; + Self::deposit_event(Event::Canceled { when, index }); + Self::place_task(new_time, task).map_err(|x| x.0) + } +} - Ok(()) - })?; +enum ServiceTaskError { + /// Could not be executed due to missing preimage. + Unavailable, + /// Could not be executed due to weight limitations. + Overweight, +} +use ServiceTaskError::*; + +impl Pallet { + /// Service up to `max` agendas queue starting from earliest incompletely executed agenda. + fn service_agendas(weight: &mut WeightMeter, now: T::BlockNumber, max: u32) { + if !weight.check_accrue(T::WeightInfo::service_agendas_base()) { + return + } + + let mut incomplete_since = now + One::one(); + let mut when = IncompleteSince::::take().unwrap_or(now); + let mut executed = 0; + + let max_items = T::MaxScheduledPerBlock::get(); + let mut count_down = max; + let service_agenda_base_weight = T::WeightInfo::service_agenda_base(max_items); + while count_down > 0 && when <= now && weight.can_accrue(service_agenda_base_weight) { + if !Self::service_agenda(weight, &mut executed, now, when, u32::max_value()) { + incomplete_since = incomplete_since.min(when); + } + when.saturating_inc(); + count_down.saturating_dec(); + } + incomplete_since = incomplete_since.min(when); + if incomplete_since <= now { + IncompleteSince::::put(incomplete_since); + } + } + + /// Returns `true` if the agenda was fully completed, `false` if it should be revisited at a + /// later block. + fn service_agenda( + weight: &mut WeightMeter, + executed: &mut u32, + now: T::BlockNumber, + when: T::BlockNumber, + max: u32, + ) -> bool { + let mut agenda = Agenda::::get(when); + let mut ordered = agenda + .iter() + .enumerate() + .filter_map(|(index, maybe_item)| { + maybe_item.as_ref().map(|item| (index as u32, item.priority)) + }) + .collect::>(); + ordered.sort_by_key(|k| k.1); + let within_limit = + weight.check_accrue(T::WeightInfo::service_agenda_base(ordered.len() as u32)); + debug_assert!(within_limit, "weight limit should have been checked in advance"); + + // Items which we know can be executed and have postponed for execution in a later block. + let mut postponed = (ordered.len() as u32).saturating_sub(max); + // Items which we don't know can ever be executed. + let mut dropped = 0; + + for (agenda_index, _) in ordered.into_iter().take(max as usize) { + let task = match agenda[agenda_index as usize].take() { + None => continue, + Some(t) => t, + }; + let base_weight = T::WeightInfo::service_task( + task.call.lookup_len().map(|x| x as usize), + task.maybe_id.is_some(), + task.maybe_periodic.is_some(), + ); + if !weight.can_accrue(base_weight) { + postponed += 1; + break + } + let result = Self::service_task(weight, now, when, agenda_index, *executed == 0, task); + agenda[agenda_index as usize] = match result { + Err((Unavailable, slot)) => { + dropped += 1; + slot + }, + Err((Overweight, slot)) => { + postponed += 1; + slot + }, + Ok(()) => { + *executed += 1; + None + }, + }; + } + if postponed > 0 || dropped > 0 { + Agenda::::insert(when, agenda); + } else { + Agenda::::remove(when); + } + postponed == 0 + } - let new_index = Agenda::::decode_len(new_time).unwrap_or(1) as u32 - 1; - Self::deposit_event(Event::Canceled(when, index)); - Self::deposit_event(Event::Scheduled(new_time, new_index)); + /// Service (i.e. execute) the given task, being careful not to overflow the `weight` counter. + /// + /// This involves: + /// - removing and potentially replacing the `Lookup` entry for the task. + /// - realizing the task's call which can include a preimage lookup. + /// - Rescheduling the task for execution in a later agenda if periodic. + fn service_task( + weight: &mut WeightMeter, + now: T::BlockNumber, + when: T::BlockNumber, + agenda_index: u32, + is_first: bool, + mut task: ScheduledOf, + ) -> Result<(), (ServiceTaskError, Option>)> { + if let Some(ref id) = task.maybe_id { + Lookup::::remove(id); + } - *lookup = Some((new_time, new_index)); + let (call, lookup_len) = match T::Preimages::peek(&task.call) { + Ok(c) => c, + Err(_) => return Err((Unavailable, Some(task))), + }; - Ok((new_time, new_index)) + weight.check_accrue(T::WeightInfo::service_task( + lookup_len.map(|x| x as usize), + task.maybe_id.is_some(), + task.maybe_periodic.is_some(), + )); + + match Self::execute_dispatch(weight, task.origin.clone(), call) { + Err(Unavailable) => { + debug_assert!(false, "Checked to exist with `peek`"); + Self::deposit_event(Event::CallUnavailable { + task: (when, agenda_index), + id: task.maybe_id, + }); + Err((Unavailable, Some(task))) + }, + Err(Overweight) if is_first => { + T::Preimages::drop(&task.call); + Self::deposit_event(Event::PermanentlyOverweight { + task: (when, agenda_index), + id: task.maybe_id, + }); + Err((Unavailable, Some(task))) + }, + Err(Overweight) => Err((Overweight, Some(task))), + Ok(result) => { + Self::deposit_event(Event::Dispatched { + task: (when, agenda_index), + id: task.maybe_id, + result, + }); + if let &Some((period, count)) = &task.maybe_periodic { + if count > 1 { + task.maybe_periodic = Some((period, count - 1)); + } else { + task.maybe_periodic = None; + } + let wake = now.saturating_add(period); + match Self::place_task(wake, task) { + Ok(_) => {}, + Err((_, task)) => { + // TODO: Leave task in storage somewhere for it to be rescheduled + // manually. + T::Preimages::drop(&task.call); + Self::deposit_event(Event::PeriodicFailed { + task: (when, agenda_index), + id: task.maybe_id, + }); + }, + } + } else { + T::Preimages::drop(&task.call); + } + Ok(()) }, - ) + } + } + + /// Make a dispatch to the given `call` from the given `origin`, ensuring that the `weight` + /// counter does not exceed its limit and that it is counted accurately (e.g. accounted using + /// post info if available). + /// + /// NOTE: Only the weight for this function will be counted (origin lookup, dispatch and the + /// call itself). + fn execute_dispatch( + weight: &mut WeightMeter, + origin: T::PalletsOrigin, + call: ::RuntimeCall, + ) -> Result { + let base_weight = match origin.as_system_ref() { + Some(&RawOrigin::Signed(_)) => T::WeightInfo::execute_dispatch_signed(), + _ => T::WeightInfo::execute_dispatch_unsigned(), + }; + let call_weight = call.get_dispatch_info().weight; + // We only allow a scheduled call if it cannot push the weight past the limit. + let max_weight = base_weight.saturating_add(call_weight); + + if !weight.can_accrue(max_weight) { + return Err(Overweight) + } + + let dispatch_origin = origin.into(); + let (maybe_actual_call_weight, result) = match call.dispatch(dispatch_origin) { + Ok(post_info) => (post_info.actual_weight, Ok(())), + Err(error_and_info) => + (error_and_info.post_info.actual_weight, Err(error_and_info.error)), + }; + let call_weight = maybe_actual_call_weight.unwrap_or(call_weight); + weight.check_accrue(base_weight); + weight.check_accrue(call_weight); + Ok(result) } } -impl schedule::Anon::Call, T::PalletsOrigin> - for Pallet +impl> + schedule::v2::Anon::RuntimeCall, T::PalletsOrigin> for Pallet { type Address = TaskAddress; + type Hash = T::Hash; fn schedule( when: DispatchTime, maybe_periodic: Option>, priority: schedule::Priority, origin: T::PalletsOrigin, - call: ::Call, + call: CallOrHashOf, ) -> Result { + let call = call.as_value().ok_or(DispatchError::CannotLookup)?; + let call = T::Preimages::bound(call)?.transmute(); Self::do_schedule(when, maybe_periodic, priority, origin, call) } @@ -791,10 +1160,11 @@ impl schedule::Anon::Call, T::PalletsOr } } -impl schedule::Named::Call, T::PalletsOrigin> - for Pallet +impl> + schedule::v2::Named::RuntimeCall, T::PalletsOrigin> for Pallet { type Address = TaskAddress; + type Hash = T::Hash; fn schedule_named( id: Vec, @@ -802,1042 +1172,110 @@ impl schedule::Named::Call, T::PalletsO maybe_periodic: Option>, priority: schedule::Priority, origin: T::PalletsOrigin, - call: ::Call, + call: CallOrHashOf, ) -> Result { - Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call).map_err(|_| ()) + let call = call.as_value().ok_or(())?; + let call = T::Preimages::bound(call).map_err(|_| ())?.transmute(); + let name = blake2_256(&id[..]); + Self::do_schedule_named(name, when, maybe_periodic, priority, origin, call).map_err(|_| ()) } fn cancel_named(id: Vec) -> Result<(), ()> { - Self::do_cancel_named(None, id).map_err(|_| ()) + let name = blake2_256(&id[..]); + Self::do_cancel_named(None, name).map_err(|_| ()) } fn reschedule_named( id: Vec, when: DispatchTime, ) -> Result { - Self::do_reschedule_named(id, when) + let name = blake2_256(&id[..]); + Self::do_reschedule_named(name, when) } fn next_dispatch_time(id: Vec) -> Result { - Lookup::::get(id) + let name = blake2_256(&id[..]); + Lookup::::get(name) .and_then(|(when, index)| Agenda::::get(when).get(index as usize).map(|_| when)) .ok_or(()) } } -#[cfg(test)] -mod tests { - use super::*; - - use crate as scheduler; - use frame_support::{ - assert_err, assert_noop, assert_ok, ord_parameter_types, parameter_types, - traits::{Contains, OnFinalize, OnInitialize}, - weights::constants::RocksDbWeight, - Hashable, - }; - use frame_system::{EnsureOneOf, EnsureRoot, EnsureSignedBy}; - use sp_core::H256; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - Perbill, - }; - use substrate_test_utils::assert_eq_uvec; - - // Logger module to track execution. - #[frame_support::pallet] - pub mod logger { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - use std::cell::RefCell; - - thread_local! { - static LOG: RefCell> = RefCell::new(Vec::new()); - } - pub fn log() -> Vec<(OriginCaller, u32)> { - LOG.with(|log| log.borrow().clone()) - } - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(PhantomData); - - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::config] - pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - Logged(u32, Weight), - } - - #[pallet::call] - impl Pallet - where - ::Origin: OriginTrait, - { - #[pallet::weight(*weight)] - pub fn log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { - Self::deposit_event(Event::Logged(i, weight)); - LOG.with(|log| { - log.borrow_mut().push((origin.caller().clone(), i)); - }); - Ok(()) - } - - #[pallet::weight(*weight)] - pub fn log_without_filter( - origin: OriginFor, - i: u32, - weight: Weight, - ) -> DispatchResult { - Self::deposit_event(Event::Logged(i, weight)); - LOG.with(|log| { - log.borrow_mut().push((origin.caller().clone(), i)); - }); - Ok(()) - } - } - } - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlock; - - frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Logger: logger::{Pallet, Call, Event}, - Scheduler: scheduler::{Pallet, Call, Storage, Event}, - } - ); - - // Scheduler must dispatch with root and no filter, this tests base filter is indeed not used. - pub struct BaseFilter; - impl Contains for BaseFilter { - fn contains(call: &Call) -> bool { - !matches!(call, Call::Logger(LoggerCall::log { .. })) - } - } - - parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(2_000_000_000_000); - } - impl system::Config for Test { - type BaseCallFilter = BaseFilter; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = RocksDbWeight; - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - } - impl logger::Config for Test { - type Event = Event; - } - parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; - pub const MaxScheduledPerBlock: u32 = 10; - } - ord_parameter_types! { - pub const One: u64 = 1; - } - - impl Config for Test { - type Event = Event; - type Origin = Origin; - type PalletsOrigin = OriginCaller; - type Call = Call; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureOneOf, EnsureSignedBy>; - type MaxScheduledPerBlock = MaxScheduledPerBlock; - type WeightInfo = (); - } - - pub type LoggerCall = logger::Call; - - pub fn new_test_ext() -> sp_io::TestExternalities { - let t = system::GenesisConfig::default().build_storage::().unwrap(); - t.into() - } - - fn run_to_block(n: u64) { - while System::block_number() < n { - Scheduler::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - Scheduler::on_initialize(System::block_number()); - } - } - - fn root() -> OriginCaller { - system::RawOrigin::Root.into() - } - - #[test] - fn basic_scheduling_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(LoggerCall::log { i: 42, weight: 1000 }); - assert!(!::BaseCallFilter::contains(&call)); - assert_ok!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call)); - run_to_block(3); - assert!(logger::log().is_empty()); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn schedule_after_works() { - new_test_ext().execute_with(|| { - run_to_block(2); - let call = Call::Logger(LoggerCall::log { i: 42, weight: 1000 }); - assert!(!::BaseCallFilter::contains(&call)); - // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6 - assert_ok!(Scheduler::do_schedule(DispatchTime::After(3), None, 127, root(), call)); - run_to_block(5); - assert!(logger::log().is_empty()); - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn schedule_after_zero_works() { - new_test_ext().execute_with(|| { - run_to_block(2); - let call = Call::Logger(LoggerCall::log { i: 42, weight: 1000 }); - assert!(!::BaseCallFilter::contains(&call)); - assert_ok!(Scheduler::do_schedule(DispatchTime::After(0), None, 127, root(), call)); - // Will trigger on the next block. - run_to_block(3); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn periodic_scheduling_works() { - new_test_ext().execute_with(|| { - // at #4, every 3 blocks, 3 times. - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - Some((3, 3)), - 127, - root(), - Call::Logger(logger::Call::log { i: 42, weight: 1000 }) - )); - run_to_block(3); - assert!(logger::log().is_empty()); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(7); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(10); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - }); - } - - #[test] - fn reschedule_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(LoggerCall::log { i: 42, weight: 1000 }); - assert!(!::BaseCallFilter::contains(&call)); - assert_eq!( - Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call).unwrap(), - (4, 0) - ); - - run_to_block(3); - assert!(logger::log().is_empty()); - - assert_eq!(Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), (6, 0)); - - assert_noop!( - Scheduler::do_reschedule((6, 0), DispatchTime::At(6)), - Error::::RescheduleNoChange - ); - - run_to_block(4); - assert!(logger::log().is_empty()); - - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn reschedule_named_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(LoggerCall::log { i: 42, weight: 1000 }); - assert!(!::BaseCallFilter::contains(&call)); - assert_eq!( - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - None, - 127, - root(), - call - ) - .unwrap(), - (4, 0) - ); - - run_to_block(3); - assert!(logger::log().is_empty()); - - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), - (6, 0) - ); - - assert_noop!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)), - Error::::RescheduleNoChange - ); - - run_to_block(4); - assert!(logger::log().is_empty()); - - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn reschedule_named_perodic_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(LoggerCall::log { i: 42, weight: 1000 }); - assert!(!::BaseCallFilter::contains(&call)); - assert_eq!( - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - Some((3, 3)), - 127, - root(), - call - ) - .unwrap(), - (4, 0) - ); - - run_to_block(3); - assert!(logger::log().is_empty()); - - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(5)).unwrap(), - (5, 0) - ); - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), - (6, 0) - ); - - run_to_block(5); - assert!(logger::log().is_empty()); - - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(10)).unwrap(), - (10, 0) - ); - - run_to_block(9); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - run_to_block(10); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - - run_to_block(13); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - }); - } - - #[test] - fn cancel_named_scheduling_works_with_normal_cancel() { - new_test_ext().execute_with(|| { - // at #4. - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log { i: 69, weight: 1000 }), - ) - .unwrap(); - let i = Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log { i: 42, weight: 1000 }), - ) - .unwrap(); - run_to_block(3); - assert!(logger::log().is_empty()); - assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); - assert_ok!(Scheduler::do_cancel(None, i)); - run_to_block(100); - assert!(logger::log().is_empty()); - }); - } - - #[test] - fn cancel_named_periodic_scheduling_works() { - new_test_ext().execute_with(|| { - // at #4, every 3 blocks, 3 times. - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - Some((3, 3)), - 127, - root(), - Call::Logger(LoggerCall::log { i: 42, weight: 1000 }), - ) - .unwrap(); - // same id results in error. - assert!(Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log { i: 69, weight: 1000 }) - ) - .is_err()); - // different id is ok. - Scheduler::do_schedule_named( - 2u32.encode(), - DispatchTime::At(8), - None, - 127, - root(), - Call::Logger(LoggerCall::log { i: 69, weight: 1000 }), - ) - .unwrap(); - run_to_block(3); - assert!(logger::log().is_empty()); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); - assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); - }); - } - - #[test] - fn scheduler_respects_weight_limits() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log { i: 42, weight: MaximumSchedulerWeight::get() / 2 }) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log { i: 69, weight: MaximumSchedulerWeight::get() / 2 }) - )); - // 69 and 42 do not fit together - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(5); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); - }); - } - - #[test] - fn scheduler_respects_hard_deadlines_more() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 0, - root(), - Call::Logger(LoggerCall::log { i: 42, weight: MaximumSchedulerWeight::get() / 2 }) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 0, - root(), - Call::Logger(LoggerCall::log { i: 69, weight: MaximumSchedulerWeight::get() / 2 }) - )); - // With base weights, 69 and 42 should not fit together, but do because of hard - // deadlines - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); - }); - } +impl schedule::v3::Anon::RuntimeCall, T::PalletsOrigin> + for Pallet +{ + type Address = TaskAddress; - #[test] - fn scheduler_respects_priority_ordering() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 1, - root(), - Call::Logger(LoggerCall::log { i: 42, weight: MaximumSchedulerWeight::get() / 2 }) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 0, - root(), - Call::Logger(LoggerCall::log { i: 69, weight: MaximumSchedulerWeight::get() / 2 }) - )); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); - }); + fn schedule( + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: Bounded<::RuntimeCall>, + ) -> Result { + Self::do_schedule(when, maybe_periodic, priority, origin, call) } - #[test] - fn scheduler_respects_priority_ordering_with_soft_deadlines() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 255, - root(), - Call::Logger(LoggerCall::log { i: 42, weight: MaximumSchedulerWeight::get() / 3 }) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log { i: 69, weight: MaximumSchedulerWeight::get() / 2 }) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 126, - root(), - Call::Logger(LoggerCall::log { - i: 2600, - weight: MaximumSchedulerWeight::get() / 2 - }) - )); - - // 2600 does not fit with 69 or 42, but has higher priority, so will go through - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 2600u32)]); - // 69 and 42 fit together - run_to_block(5); - assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]); - }); + fn cancel((when, index): Self::Address) -> Result<(), DispatchError> { + Self::do_cancel(None, (when, index)).map_err(map_err_to_v3_err::) } - #[test] - fn on_initialize_weight_is_correct() { - new_test_ext().execute_with(|| { - let base_weight: Weight = - ::DbWeight::get().reads_writes(1, 2); - let base_multiplier = 0; - let named_multiplier = ::DbWeight::get().writes(1); - let periodic_multiplier = - ::DbWeight::get().reads_writes(1, 1); - - // Named - assert_ok!(Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(1), - None, - 255, - root(), - Call::Logger(LoggerCall::log { i: 3, weight: MaximumSchedulerWeight::get() / 3 }) - )); - // Anon Periodic - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(1), - Some((1000, 3)), - 128, - root(), - Call::Logger(LoggerCall::log { i: 42, weight: MaximumSchedulerWeight::get() / 3 }) - )); - // Anon - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(1), - None, - 127, - root(), - Call::Logger(LoggerCall::log { i: 69, weight: MaximumSchedulerWeight::get() / 2 }) - )); - // Named Periodic - assert_ok!(Scheduler::do_schedule_named( - 2u32.encode(), - DispatchTime::At(1), - Some((1000, 3)), - 126, - root(), - Call::Logger(LoggerCall::log { - i: 2600, - weight: MaximumSchedulerWeight::get() / 2 - }) - )); - - // Will include the named periodic only - let actual_weight = Scheduler::on_initialize(1); - let call_weight = MaximumSchedulerWeight::get() / 2; - assert_eq!( - actual_weight, - call_weight + - base_weight + base_multiplier + - named_multiplier + periodic_multiplier - ); - assert_eq!(logger::log(), vec![(root(), 2600u32)]); - - // Will include anon and anon periodic - let actual_weight = Scheduler::on_initialize(2); - let call_weight = MaximumSchedulerWeight::get() / 2 + MaximumSchedulerWeight::get() / 3; - assert_eq!( - actual_weight, - call_weight + base_weight + base_multiplier * 2 + periodic_multiplier - ); - assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]); - - // Will include named only - let actual_weight = Scheduler::on_initialize(3); - let call_weight = MaximumSchedulerWeight::get() / 3; - assert_eq!( - actual_weight, - call_weight + base_weight + base_multiplier + named_multiplier - ); - assert_eq!( - logger::log(), - vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32), (root(), 3u32)] - ); - - // Will contain none - let actual_weight = Scheduler::on_initialize(4); - assert_eq!(actual_weight, 0); - }); + fn reschedule( + address: Self::Address, + when: DispatchTime, + ) -> Result { + Self::do_reschedule(address, when).map_err(map_err_to_v3_err::) } - #[test] - fn root_calls_works() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(LoggerCall::log { i: 69, weight: 1000 })); - let call2 = Box::new(Call::Logger(LoggerCall::log { i: 42, weight: 1000 })); - assert_ok!(Scheduler::schedule_named( - Origin::root(), - 1u32.encode(), - 4, - None, - 127, - call - )); - assert_ok!(Scheduler::schedule(Origin::root(), 4, None, 127, call2)); - run_to_block(3); - // Scheduled calls are in the agenda. - assert_eq!(Agenda::::get(4).len(), 2); - assert!(logger::log().is_empty()); - assert_ok!(Scheduler::cancel_named(Origin::root(), 1u32.encode())); - assert_ok!(Scheduler::cancel(Origin::root(), 4, 1)); - // Scheduled calls are made NONE, so should not effect state - run_to_block(100); - assert!(logger::log().is_empty()); - }); + fn next_dispatch_time((when, index): Self::Address) -> Result { + Agenda::::get(when) + .get(index as usize) + .ok_or(DispatchError::Unavailable) + .map(|_| when) } +} - #[test] - fn fails_to_schedule_task_in_the_past() { - new_test_ext().execute_with(|| { - run_to_block(3); - - let call = Box::new(Call::Logger(LoggerCall::log { i: 69, weight: 1000 })); - let call2 = Box::new(Call::Logger(LoggerCall::log { i: 42, weight: 1000 })); - - assert_err!( - Scheduler::schedule_named(Origin::root(), 1u32.encode(), 2, None, 127, call), - Error::::TargetBlockNumberInPast, - ); - - assert_err!( - Scheduler::schedule(Origin::root(), 2, None, 127, call2.clone()), - Error::::TargetBlockNumberInPast, - ); +use schedule::v3::TaskName; - assert_err!( - Scheduler::schedule(Origin::root(), 3, None, 127, call2), - Error::::TargetBlockNumberInPast, - ); - }); - } +impl schedule::v3::Named::RuntimeCall, T::PalletsOrigin> + for Pallet +{ + type Address = TaskAddress; - #[test] - fn should_use_orign() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(LoggerCall::log { i: 69, weight: 1000 })); - let call2 = Box::new(Call::Logger(LoggerCall::log { i: 42, weight: 1000 })); - assert_ok!(Scheduler::schedule_named( - system::RawOrigin::Signed(1).into(), - 1u32.encode(), - 4, - None, - 127, - call - )); - assert_ok!(Scheduler::schedule( - system::RawOrigin::Signed(1).into(), - 4, - None, - 127, - call2 - )); - run_to_block(3); - // Scheduled calls are in the agenda. - assert_eq!(Agenda::::get(4).len(), 2); - assert!(logger::log().is_empty()); - assert_ok!(Scheduler::cancel_named(system::RawOrigin::Signed(1).into(), 1u32.encode())); - assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1)); - // Scheduled calls are made NONE, so should not effect state - run_to_block(100); - assert!(logger::log().is_empty()); - }); + fn schedule_named( + id: TaskName, + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: Bounded<::RuntimeCall>, + ) -> Result { + Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call) } - #[test] - fn should_check_orign() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(LoggerCall::log { i: 69, weight: 1000 })); - let call2 = Box::new(Call::Logger(LoggerCall::log { i: 42, weight: 1000 })); - assert_noop!( - Scheduler::schedule_named( - system::RawOrigin::Signed(2).into(), - 1u32.encode(), - 4, - None, - 127, - call - ), - BadOrigin - ); - assert_noop!( - Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, 127, call2), - BadOrigin - ); - }); + fn cancel_named(id: TaskName) -> Result<(), DispatchError> { + Self::do_cancel_named(None, id).map_err(map_err_to_v3_err::) } - #[test] - fn should_check_orign_for_cancel() { - new_test_ext().execute_with(|| { - let call = - Box::new(Call::Logger(LoggerCall::log_without_filter { i: 69, weight: 1000 })); - let call2 = - Box::new(Call::Logger(LoggerCall::log_without_filter { i: 42, weight: 1000 })); - assert_ok!(Scheduler::schedule_named( - system::RawOrigin::Signed(1).into(), - 1u32.encode(), - 4, - None, - 127, - call - )); - assert_ok!(Scheduler::schedule( - system::RawOrigin::Signed(1).into(), - 4, - None, - 127, - call2 - )); - run_to_block(3); - // Scheduled calls are in the agenda. - assert_eq!(Agenda::::get(4).len(), 2); - assert!(logger::log().is_empty()); - assert_noop!( - Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), 1u32.encode()), - BadOrigin - ); - assert_noop!(Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), BadOrigin); - assert_noop!( - Scheduler::cancel_named(system::RawOrigin::Root.into(), 1u32.encode()), - BadOrigin - ); - assert_noop!(Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), BadOrigin); - run_to_block(5); - assert_eq!( - logger::log(), - vec![ - (system::RawOrigin::Signed(1).into(), 69u32), - (system::RawOrigin::Signed(1).into(), 42u32) - ] - ); - }); + fn reschedule_named( + id: TaskName, + when: DispatchTime, + ) -> Result { + Self::do_reschedule_named(id, when).map_err(map_err_to_v3_err::) } - #[test] - fn migration_to_v2_works() { - new_test_ext().execute_with(|| { - for i in 0..3u64 { - let k = i.twox_64_concat(); - let old = vec![ - Some(ScheduledV1 { - maybe_id: None, - priority: i as u8 + 10, - call: Call::Logger(LoggerCall::log { i: 96, weight: 100 }), - maybe_periodic: None, - }), - None, - Some(ScheduledV1 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log { i: 69, weight: 1000 }), - maybe_periodic: Some((456u64, 10)), - }), - ]; - frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); - } - - assert_eq!(StorageVersion::::get(), Releases::V1); - - assert!(Scheduler::migrate_v1_to_t2()); - - assert_eq_uvec!( - Agenda::::iter().collect::>(), - vec![ - ( - 0, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 10, - call: Call::Logger(LoggerCall::log { i: 96, weight: 100 }), - maybe_periodic: None, - origin: root(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log { i: 69, weight: 1000 }), - maybe_periodic: Some((456u64, 10)), - origin: root(), - _phantom: PhantomData::::default(), - }), - ] - ), - ( - 1, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 11, - call: Call::Logger(LoggerCall::log { i: 96, weight: 100 }), - maybe_periodic: None, - origin: root(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log { i: 69, weight: 1000 }), - maybe_periodic: Some((456u64, 10)), - origin: root(), - _phantom: PhantomData::::default(), - }), - ] - ), - ( - 2, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 12, - call: Call::Logger(LoggerCall::log { i: 96, weight: 100 }), - maybe_periodic: None, - origin: root(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log { i: 69, weight: 1000 }), - maybe_periodic: Some((456u64, 10)), - origin: root(), - _phantom: PhantomData::::default(), - }), - ] - ) - ] - ); - - assert_eq!(StorageVersion::::get(), Releases::V2); - }); + fn next_dispatch_time(id: TaskName) -> Result { + Lookup::::get(id) + .and_then(|(when, index)| Agenda::::get(when).get(index as usize).map(|_| when)) + .ok_or(DispatchError::Unavailable) } +} - #[test] - fn test_migrate_origin() { - new_test_ext().execute_with(|| { - for i in 0..3u64 { - let k = i.twox_64_concat(); - let old: Vec>> = vec![ - Some(Scheduled { - maybe_id: None, - priority: i as u8 + 10, - call: Call::Logger(LoggerCall::log { i: 96, weight: 100 }), - origin: 3u32, - maybe_periodic: None, - _phantom: Default::default(), - }), - None, - Some(Scheduled { - maybe_id: Some(b"test".to_vec()), - priority: 123, - origin: 2u32, - call: Call::Logger(LoggerCall::log { i: 69, weight: 1000 }), - maybe_periodic: Some((456u64, 10)), - _phantom: Default::default(), - }), - ]; - frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); - } - - impl Into for u32 { - fn into(self) -> OriginCaller { - match self { - 3u32 => system::RawOrigin::Root.into(), - 2u32 => system::RawOrigin::None.into(), - _ => unreachable!("test make no use of it"), - } - } - } - - Scheduler::migrate_origin::(); - - assert_eq_uvec!( - Agenda::::iter().collect::>(), - vec![ - ( - 0, - vec![ - Some(ScheduledV2::<_, _, OriginCaller, u64> { - maybe_id: None, - priority: 10, - call: Call::Logger(LoggerCall::log { i: 96, weight: 100 }), - maybe_periodic: None, - origin: system::RawOrigin::Root.into(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log { i: 69, weight: 1000 }), - maybe_periodic: Some((456u64, 10)), - origin: system::RawOrigin::None.into(), - _phantom: PhantomData::::default(), - }), - ] - ), - ( - 1, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 11, - call: Call::Logger(LoggerCall::log { i: 96, weight: 100 }), - maybe_periodic: None, - origin: system::RawOrigin::Root.into(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log { i: 69, weight: 1000 }), - maybe_periodic: Some((456u64, 10)), - origin: system::RawOrigin::None.into(), - _phantom: PhantomData::::default(), - }), - ] - ), - ( - 2, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 12, - call: Call::Logger(LoggerCall::log { i: 96, weight: 100 }), - maybe_periodic: None, - origin: system::RawOrigin::Root.into(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log { i: 69, weight: 1000 }), - maybe_periodic: Some((456u64, 10)), - origin: system::RawOrigin::None.into(), - _phantom: PhantomData::::default(), - }), - ] - ) - ] - ); - }); +/// Maps a pallet error to an `schedule::v3` error. +fn map_err_to_v3_err(err: DispatchError) -> DispatchError { + if err == DispatchError::from(Error::::NotFound) { + DispatchError::Unavailable + } else { + err } } diff --git a/frame/scheduler/src/migration.rs b/frame/scheduler/src/migration.rs new file mode 100644 index 0000000000000..6769d20023196 --- /dev/null +++ b/frame/scheduler/src/migration.rs @@ -0,0 +1,402 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Migrations for the scheduler pallet. + +use super::*; +use frame_support::traits::OnRuntimeUpgrade; + +/// The log target. +const TARGET: &'static str = "runtime::scheduler::migration"; + +pub mod v1 { + use super::*; + use frame_support::pallet_prelude::*; + + #[frame_support::storage_alias] + pub(crate) type Agenda = StorageMap< + Pallet, + Twox64Concat, + ::BlockNumber, + Vec< + Option< + ScheduledV1<::RuntimeCall, ::BlockNumber>, + >, + >, + ValueQuery, + >; + + #[frame_support::storage_alias] + pub(crate) type Lookup = StorageMap< + Pallet, + Twox64Concat, + Vec, + TaskAddress<::BlockNumber>, + >; +} + +pub mod v2 { + use super::*; + use frame_support::pallet_prelude::*; + + #[frame_support::storage_alias] + pub(crate) type Agenda = StorageMap< + Pallet, + Twox64Concat, + ::BlockNumber, + Vec>>, + ValueQuery, + >; + + #[frame_support::storage_alias] + pub(crate) type Lookup = StorageMap< + Pallet, + Twox64Concat, + Vec, + TaskAddress<::BlockNumber>, + >; +} + +pub mod v3 { + use super::*; + use frame_support::pallet_prelude::*; + + #[frame_support::storage_alias] + pub(crate) type Agenda = StorageMap< + Pallet, + Twox64Concat, + ::BlockNumber, + Vec>>, + ValueQuery, + >; + + #[frame_support::storage_alias] + pub(crate) type Lookup = StorageMap< + Pallet, + Twox64Concat, + Vec, + TaskAddress<::BlockNumber>, + >; + + /// Migrate the scheduler pallet from V3 to V4. + pub struct MigrateToV4(sp_std::marker::PhantomData); + + impl> OnRuntimeUpgrade for MigrateToV4 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + assert_eq!(StorageVersion::get::>(), 3, "Can only upgrade from version 3"); + + let agendas = Agenda::::iter_keys().count() as u32; + let decodable_agendas = Agenda::::iter_values().count() as u32; + if agendas != decodable_agendas { + // This is not necessarily an error, but can happen when there are Calls + // in an Agenda that are not valid anymore with the new runtime. + log::error!( + target: TARGET, + "Can only decode {} of {} agendas - others will be dropped", + decodable_agendas, + agendas + ); + } + log::info!(target: TARGET, "Trying to migrate {} agendas...", decodable_agendas); + + // Check that no agenda overflows `MaxScheduledPerBlock`. + let max_scheduled_per_block = T::MaxScheduledPerBlock::get() as usize; + for (block_number, agenda) in Agenda::::iter() { + if agenda.iter().cloned().filter_map(|s| s).count() > max_scheduled_per_block { + log::error!( + target: TARGET, + "Would truncate agenda of block {:?} from {} items to {} items.", + block_number, + agenda.len(), + max_scheduled_per_block, + ); + return Err("Agenda would overflow `MaxScheduledPerBlock`.") + } + } + // Check that bounding the calls will not overflow `MAX_LENGTH`. + let max_length = T::Preimages::MAX_LENGTH as usize; + for (block_number, agenda) in Agenda::::iter() { + for schedule in agenda.iter().cloned().filter_map(|s| s) { + match schedule.call { + frame_support::traits::schedule::MaybeHashed::Value(call) => { + let l = call.using_encoded(|c| c.len()); + if l > max_length { + log::error!( + target: TARGET, + "Call in agenda of block {:?} is too large: {} byte", + block_number, + l, + ); + return Err("Call is too large.") + } + }, + _ => (), + } + } + } + + Ok((decodable_agendas as u32).encode()) + } + + fn on_runtime_upgrade() -> Weight { + let version = StorageVersion::get::>(); + if version != 3 { + log::warn!( + target: TARGET, + "skipping v3 to v4 migration: executed on wrong storage version.\ + Expected version 3, found {:?}", + version, + ); + return T::DbWeight::get().reads(1) + } + + crate::Pallet::::migrate_v3_to_v4() + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + assert_eq!(StorageVersion::get::>(), 4, "Must upgrade"); + + // Check that everything decoded fine. + for k in crate::Agenda::::iter_keys() { + assert!(crate::Agenda::::try_get(k).is_ok(), "Cannot decode V4 Agenda"); + } + + let old_agendas: u32 = + Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed"); + let new_agendas = crate::Agenda::::iter_keys().count() as u32; + if old_agendas != new_agendas { + // This is not necessarily an error, but can happen when there are Calls + // in an Agenda that are not valid anymore in the new runtime. + log::error!( + target: TARGET, + "Did not migrate all Agendas. Previous {}, Now {}", + old_agendas, + new_agendas, + ); + } else { + log::info!(target: TARGET, "Migrated {} agendas.", new_agendas); + } + + Ok(()) + } + } +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod test { + use super::*; + use crate::mock::*; + use frame_support::Hashable; + use sp_std::borrow::Cow; + use substrate_test_utils::assert_eq_uvec; + + #[test] + #[allow(deprecated)] + fn migration_v3_to_v4_works() { + new_test_ext().execute_with(|| { + // Assume that we are at V3. + StorageVersion::new(3).put::(); + + // Call that will be bounded to a `Lookup`. + let large_call = + RuntimeCall::System(frame_system::Call::remark { remark: vec![0; 1024] }); + // Call that can be inlined. + let small_call = + RuntimeCall::System(frame_system::Call::remark { remark: vec![0; 10] }); + // Call that is already hashed and can will be converted to `Legacy`. + let hashed_call = + RuntimeCall::System(frame_system::Call::remark { remark: vec![0; 2048] }); + let bound_hashed_call = Preimage::bound(hashed_call.clone()).unwrap(); + assert!(bound_hashed_call.lookup_needed()); + // A Call by hash that will fail to decode becomes `None`. + let trash_data = vec![255u8; 1024]; + let undecodable_hash = Preimage::note(Cow::Borrowed(&trash_data)).unwrap(); + + for i in 0..2u64 { + let k = i.twox_64_concat(); + let old = vec![ + Some(ScheduledV3Of:: { + maybe_id: None, + priority: i as u8 + 10, + call: small_call.clone().into(), + maybe_periodic: None, // 1 + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV3Of:: { + maybe_id: Some(vec![i as u8; 32]), + priority: 123, + call: large_call.clone().into(), + maybe_periodic: Some((4u64, 20)), + origin: signed(i), + _phantom: PhantomData::::default(), + }), + Some(ScheduledV3Of:: { + maybe_id: Some(vec![255 - i as u8; 320]), + priority: 123, + call: MaybeHashed::Hash(bound_hashed_call.hash()), + maybe_periodic: Some((8u64, 10)), + origin: signed(i), + _phantom: PhantomData::::default(), + }), + Some(ScheduledV3Of:: { + maybe_id: Some(vec![i as u8; 320]), + priority: 123, + call: MaybeHashed::Hash(undecodable_hash.clone()), + maybe_periodic: Some((4u64, 20)), + origin: root(), + _phantom: PhantomData::::default(), + }), + ]; + frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); + } + + let state = v3::MigrateToV4::::pre_upgrade().unwrap(); + let _w = v3::MigrateToV4::::on_runtime_upgrade(); + v3::MigrateToV4::::post_upgrade(state).unwrap(); + + let mut x = Agenda::::iter().map(|x| (x.0, x.1.into_inner())).collect::>(); + x.sort_by_key(|x| x.0); + + let bound_large_call = Preimage::bound(large_call).unwrap(); + assert!(bound_large_call.lookup_needed()); + let bound_small_call = Preimage::bound(small_call).unwrap(); + assert!(!bound_small_call.lookup_needed()); + + let expected = vec![ + ( + 0, + vec![ + Some(ScheduledOf:: { + maybe_id: None, + priority: 10, + call: bound_small_call.clone(), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledOf:: { + maybe_id: Some(blake2_256(&[0u8; 32])), + priority: 123, + call: bound_large_call.clone(), + maybe_periodic: Some((4u64, 20)), + origin: signed(0), + _phantom: PhantomData::::default(), + }), + Some(ScheduledOf:: { + maybe_id: Some(blake2_256(&[255u8; 320])), + priority: 123, + call: Bounded::from_legacy_hash(bound_hashed_call.hash()), + maybe_periodic: Some((8u64, 10)), + origin: signed(0), + _phantom: PhantomData::::default(), + }), + None, + ], + ), + ( + 1, + vec![ + Some(ScheduledOf:: { + maybe_id: None, + priority: 11, + call: bound_small_call.clone(), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledOf:: { + maybe_id: Some(blake2_256(&[1u8; 32])), + priority: 123, + call: bound_large_call.clone(), + maybe_periodic: Some((4u64, 20)), + origin: signed(1), + _phantom: PhantomData::::default(), + }), + Some(ScheduledOf:: { + maybe_id: Some(blake2_256(&[254u8; 320])), + priority: 123, + call: Bounded::from_legacy_hash(bound_hashed_call.hash()), + maybe_periodic: Some((8u64, 10)), + origin: signed(1), + _phantom: PhantomData::::default(), + }), + None, + ], + ), + ]; + for (outer, (i, j)) in x.iter().zip(expected.iter()).enumerate() { + assert_eq!(i.0, j.0); + for (inner, (x, y)) in i.1.iter().zip(j.1.iter()).enumerate() { + assert_eq!(x, y, "at index: outer {} inner {}", outer, inner); + } + } + assert_eq_uvec!(x, expected); + + assert_eq!(StorageVersion::get::(), 4); + }); + } + + #[test] + #[allow(deprecated)] + fn migration_v3_to_v4_too_large_calls_are_ignored() { + new_test_ext().execute_with(|| { + // Assume that we are at V3. + StorageVersion::new(3).put::(); + + let too_large_call = RuntimeCall::System(frame_system::Call::remark { + remark: vec![0; ::Preimages::MAX_LENGTH + 1], + }); + + let i = 0u64; + let k = i.twox_64_concat(); + let old = vec![Some(ScheduledV3Of:: { + maybe_id: None, + priority: 1, + call: too_large_call.clone().into(), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + })]; + frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); + + // The pre_upgrade hook fails: + let err = v3::MigrateToV4::::pre_upgrade().unwrap_err(); + assert!(err.contains("Call is too large")); + // But the migration itself works: + let _w = v3::MigrateToV4::::on_runtime_upgrade(); + + let mut x = Agenda::::iter().map(|x| (x.0, x.1.into_inner())).collect::>(); + x.sort_by_key(|x| x.0); + // The call becomes `None`. + let expected = vec![(0, vec![None])]; + assert_eq_uvec!(x, expected); + + assert_eq!(StorageVersion::get::(), 4); + }); + } + + fn signed(i: u64) -> OriginCaller { + system::RawOrigin::Signed(i).into() + } +} diff --git a/frame/scheduler/src/mock.rs b/frame/scheduler/src/mock.rs new file mode 100644 index 0000000000000..0aaac56667dcb --- /dev/null +++ b/frame/scheduler/src/mock.rs @@ -0,0 +1,243 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! # Scheduler test environment. + +use super::*; + +use crate as scheduler; +use frame_support::{ + ord_parameter_types, parameter_types, + traits::{ + ConstU32, ConstU64, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize, + }, + weights::constants::RocksDbWeight, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +// Logger module to track execution. +#[frame_support::pallet] +pub mod logger { + use super::{OriginCaller, OriginTrait}; + use frame_support::{pallet_prelude::*, parameter_types}; + use frame_system::pallet_prelude::*; + + parameter_types! { + static Log: Vec<(OriginCaller, u32)> = Vec::new(); + } + pub fn log() -> Vec<(OriginCaller, u32)> { + Log::get().clone() + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Logged(u32, Weight), + } + + #[pallet::call] + impl Pallet + where + ::RuntimeOrigin: OriginTrait, + { + #[pallet::call_index(0)] + #[pallet::weight(*weight)] + pub fn log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { + Self::deposit_event(Event::Logged(i, weight)); + Log::mutate(|log| { + log.push((origin.caller().clone(), i)); + }); + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(*weight)] + pub fn log_without_filter(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { + Self::deposit_event(Event::Logged(i, weight)); + Log::mutate(|log| { + log.push((origin.caller().clone(), i)); + }); + Ok(()) + } + } +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Logger: logger::{Pallet, Call, Event}, + Scheduler: scheduler::{Pallet, Call, Storage, Event}, + Preimage: pallet_preimage::{Pallet, Call, Storage, Event}, + } +); + +// Scheduler must dispatch with root and no filter, this tests base filter is indeed not used. +pub struct BaseFilter; +impl Contains for BaseFilter { + fn contains(call: &RuntimeCall) -> bool { + !matches!(call, RuntimeCall::Logger(LoggerCall::log { .. })) + } +} + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max( + Weight::from_ref_time(2_000_000_000_000).set_proof_size(u64::MAX), + ); +} +impl system::Config for Test { + type BaseCallFilter = BaseFilter; + type BlockWeights = BlockWeights; + type BlockLength = (); + type DbWeight = RocksDbWeight; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} +impl logger::Config for Test { + type RuntimeEvent = RuntimeEvent; +} +ord_parameter_types! { + pub const One: u64 = 1; +} + +impl pallet_preimage::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = (); + type ManagerOrigin = EnsureRoot; + type BaseDeposit = (); + type ByteDeposit = (); +} + +pub struct TestWeightInfo; +impl WeightInfo for TestWeightInfo { + fn service_agendas_base() -> Weight { + Weight::from_ref_time(0b0000_0001) + } + fn service_agenda_base(i: u32) -> Weight { + Weight::from_ref_time((i << 8) as u64 + 0b0000_0010) + } + fn service_task_base() -> Weight { + Weight::from_ref_time(0b0000_0100) + } + fn service_task_periodic() -> Weight { + Weight::from_ref_time(0b0000_1100) + } + fn service_task_named() -> Weight { + Weight::from_ref_time(0b0001_0100) + } + fn service_task_fetched(s: u32) -> Weight { + Weight::from_ref_time((s << 8) as u64 + 0b0010_0100) + } + fn execute_dispatch_signed() -> Weight { + Weight::from_ref_time(0b0100_0000) + } + fn execute_dispatch_unsigned() -> Weight { + Weight::from_ref_time(0b1000_0000) + } + fn schedule(_s: u32) -> Weight { + Weight::from_ref_time(50) + } + fn cancel(_s: u32) -> Weight { + Weight::from_ref_time(50) + } + fn schedule_named(_s: u32) -> Weight { + Weight::from_ref_time(50) + } + fn cancel_named(_s: u32) -> Weight { + Weight::from_ref_time(50) + } +} +parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * + BlockWeights::get().max_block; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EitherOfDiverse, EnsureSignedBy>; + type MaxScheduledPerBlock = ConstU32<10>; + type WeightInfo = TestWeightInfo; + type OriginPrivilegeCmp = EqualPrivilegeOnly; + type Preimages = Preimage; +} + +pub type LoggerCall = logger::Call; + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::default().build_storage::().unwrap(); + t.into() +} + +pub fn run_to_block(n: u64) { + while System::block_number() < n { + Scheduler::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + Scheduler::on_initialize(System::block_number()); + } +} + +pub fn root() -> OriginCaller { + system::RawOrigin::Root.into() +} diff --git a/frame/scheduler/src/tests.rs b/frame/scheduler/src/tests.rs new file mode 100644 index 0000000000000..033d787946709 --- /dev/null +++ b/frame/scheduler/src/tests.rs @@ -0,0 +1,1769 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! # Scheduler tests. + +use super::*; +use crate::mock::{ + logger, new_test_ext, root, run_to_block, LoggerCall, RuntimeCall, Scheduler, Test, *, +}; +use frame_support::{ + assert_err, assert_noop, assert_ok, + traits::{Contains, GetStorageVersion, OnInitialize, QueryPreimage, StorePreimage}, + Hashable, +}; +use sp_runtime::traits::Hash; +use substrate_test_utils::assert_eq_uvec; + +#[test] +fn basic_scheduling_works() { + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + assert!(!::BaseCallFilter::contains(&call)); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap() + )); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn scheduling_with_preimages_works() { + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let hash = ::Hashing::hash_of(&call); + let len = call.using_encoded(|x| x.len()) as u32; + let hashed = Preimage::pick(hash, len); + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(0), call.encode())); + assert_ok!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), hashed)); + assert!(Preimage::is_requested(&hash)); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert!(!Preimage::len(&hash).is_some()); + assert!(!Preimage::is_requested(&hash)); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn schedule_after_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + assert!(!::BaseCallFilter::contains(&call)); + // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6 + assert_ok!(Scheduler::do_schedule( + DispatchTime::After(3), + None, + 127, + root(), + Preimage::bound(call).unwrap() + )); + run_to_block(5); + assert!(logger::log().is_empty()); + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn schedule_after_zero_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + assert!(!::BaseCallFilter::contains(&call)); + assert_ok!(Scheduler::do_schedule( + DispatchTime::After(0), + None, + 127, + root(), + Preimage::bound(call).unwrap() + )); + // Will trigger on the next block. + run_to_block(3); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::log { + i: 42, + weight: Weight::from_ref_time(10) + })) + .unwrap() + )); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(7); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(9); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); + }); +} + +#[test] +fn reschedule_works() { + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + assert!(!::BaseCallFilter::contains(&call)); + assert_eq!( + Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap() + ) + .unwrap(), + (4, 0) + ); + + run_to_block(3); + assert!(logger::log().is_empty()); + + assert_eq!(Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), (6, 0)); + + assert_noop!( + Scheduler::do_reschedule((6, 0), DispatchTime::At(6)), + Error::::RescheduleNoChange + ); + + run_to_block(4); + assert!(logger::log().is_empty()); + + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn reschedule_named_works() { + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + assert!(!::BaseCallFilter::contains(&call)); + assert_eq!( + Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(), + (4, 0) + ); + + run_to_block(3); + assert!(logger::log().is_empty()); + + assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)).unwrap(), (6, 0)); + + assert_noop!( + Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)), + Error::::RescheduleNoChange + ); + + run_to_block(4); + assert!(logger::log().is_empty()); + + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn reschedule_named_perodic_works() { + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + assert!(!::BaseCallFilter::contains(&call)); + assert_eq!( + Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(), + (4, 0) + ); + + run_to_block(3); + assert!(logger::log().is_empty()); + + assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(5)).unwrap(), (5, 0)); + assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)).unwrap(), (6, 0)); + + run_to_block(5); + assert!(logger::log().is_empty()); + + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + assert_eq!( + Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(10)).unwrap(), + (10, 0) + ); + + run_to_block(9); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + + run_to_block(13); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); + + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); + }); +} + +#[test] +fn cancel_named_scheduling_works_with_normal_cancel() { + new_test_ext().execute_with(|| { + // at #4. + Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + ) + .unwrap(); + let i = Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + ) + .unwrap(); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); + assert_ok!(Scheduler::do_cancel(None, i)); + run_to_block(100); + assert!(logger::log().is_empty()); + }); +} + +#[test] +fn cancel_named_periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + ) + .unwrap(); + // same id results in error. + assert!(Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10) + })) + .unwrap(), + ) + .is_err()); + // different id is ok. + Scheduler::do_schedule_named( + [2u8; 32], + DispatchTime::At(8), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + ) + .unwrap(); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(6); + assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); + }); +} + +#[test] +fn scheduler_respects_weight_limits() { + let max_weight: Weight = ::MaximumWeight::get(); + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 3 * 2 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { i: 69, weight: max_weight / 3 * 2 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + // 69 and 42 do not fit together + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(5); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); + }); +} + +/// Permanently overweight calls are not deleted but also not executed. +#[test] +fn scheduler_does_not_delete_permanently_overweight_call() { + let max_weight: Weight = ::MaximumWeight::get(); + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + // Never executes. + run_to_block(100); + assert_eq!(logger::log(), vec![]); + + // Assert the `PermanentlyOverweight` event. + assert_eq!( + System::events().last().unwrap().event, + crate::Event::PermanentlyOverweight { task: (4, 0), id: None }.into(), + ); + // The call is still in the agenda. + assert!(Agenda::::get(4)[0].is_some()); + }); +} + +#[test] +fn scheduler_handles_periodic_failure() { + let max_weight: Weight = ::MaximumWeight::get(); + let max_per_block = ::MaxScheduledPerBlock::get(); + + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: (max_weight / 3) * 2 }); + let bound = Preimage::bound(call).unwrap(); + + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + Some((4, u32::MAX)), + 127, + root(), + bound.clone(), + )); + // Executes 5 times till block 20. + run_to_block(20); + assert_eq!(logger::log().len(), 5); + + // Block 28 will already be full. + for _ in 0..max_per_block { + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(28), + None, + 120, + root(), + bound.clone(), + )); + } + + // Going to block 24 will emit a `PeriodicFailed` event. + run_to_block(24); + assert_eq!(logger::log().len(), 6); + + assert_eq!( + System::events().last().unwrap().event, + crate::Event::PeriodicFailed { task: (24, 0), id: None }.into(), + ); + }); +} + +#[test] +fn scheduler_handles_periodic_unavailable_preimage() { + let max_weight: Weight = ::MaximumWeight::get(); + + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: (max_weight / 3) * 2 }); + let hash = ::Hashing::hash_of(&call); + let len = call.using_encoded(|x| x.len()) as u32; + let bound = Preimage::pick(hash, len); + assert_ok!(Preimage::note(call.encode().into())); + + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + Some((4, u32::MAX)), + 127, + root(), + bound.clone(), + )); + // Executes 1 times till block 4. + run_to_block(4); + assert_eq!(logger::log().len(), 1); + + // Unnote the preimage. + Preimage::unnote(&hash); + + // Does not ever execute again. + run_to_block(100); + assert_eq!(logger::log().len(), 1); + + // The preimage is not requested anymore. + assert!(!Preimage::is_requested(&hash)); + }); +} + +#[test] +fn scheduler_respects_priority_ordering() { + let max_weight: Weight = ::MaximumWeight::get(); + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 3 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 1, + root(), + Preimage::bound(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { i: 69, weight: max_weight / 3 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 0, + root(), + Preimage::bound(call).unwrap(), + )); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); + }); +} + +#[test] +fn scheduler_respects_priority_ordering_with_soft_deadlines() { + new_test_ext().execute_with(|| { + let max_weight: Weight = ::MaximumWeight::get(); + let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 5 * 2 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 255, + root(), + Preimage::bound(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { i: 69, weight: max_weight / 5 * 2 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { i: 2600, weight: max_weight / 5 * 4 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 126, + root(), + Preimage::bound(call).unwrap(), + )); + + // 2600 does not fit with 69 or 42, but has higher priority, so will go through + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 2600u32)]); + // 69 and 42 fit together + run_to_block(5); + assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]); + }); +} + +#[test] +fn on_initialize_weight_is_correct() { + new_test_ext().execute_with(|| { + let call_weight = Weight::from_ref_time(25); + + // Named + let call = RuntimeCall::Logger(LoggerCall::log { + i: 3, + weight: call_weight + Weight::from_ref_time(1), + }); + assert_ok!(Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(3), + None, + 255, + root(), + Preimage::bound(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: call_weight + Weight::from_ref_time(2), + }); + // Anon Periodic + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(2), + Some((1000, 3)), + 128, + root(), + Preimage::bound(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: call_weight + Weight::from_ref_time(3), + }); + // Anon + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(2), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + // Named Periodic + let call = RuntimeCall::Logger(LoggerCall::log { + i: 2600, + weight: call_weight + Weight::from_ref_time(4), + }); + assert_ok!(Scheduler::do_schedule_named( + [2u8; 32], + DispatchTime::At(1), + Some((1000, 3)), + 126, + root(), + Preimage::bound(call).unwrap(), + )); + + // Will include the named periodic only + assert_eq!( + Scheduler::on_initialize(1), + TestWeightInfo::service_agendas_base() + + TestWeightInfo::service_agenda_base(1) + + ::service_task(None, true, true) + + TestWeightInfo::execute_dispatch_unsigned() + + call_weight + Weight::from_ref_time(4) + ); + assert_eq!(IncompleteSince::::get(), None); + assert_eq!(logger::log(), vec![(root(), 2600u32)]); + + // Will include anon and anon periodic + assert_eq!( + Scheduler::on_initialize(2), + TestWeightInfo::service_agendas_base() + + TestWeightInfo::service_agenda_base(2) + + ::service_task(None, false, true) + + TestWeightInfo::execute_dispatch_unsigned() + + call_weight + Weight::from_ref_time(3) + + ::service_task(None, false, false) + + TestWeightInfo::execute_dispatch_unsigned() + + call_weight + Weight::from_ref_time(2) + ); + assert_eq!(IncompleteSince::::get(), None); + assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]); + + // Will include named only + assert_eq!( + Scheduler::on_initialize(3), + TestWeightInfo::service_agendas_base() + + TestWeightInfo::service_agenda_base(1) + + ::service_task(None, true, false) + + TestWeightInfo::execute_dispatch_unsigned() + + call_weight + Weight::from_ref_time(1) + ); + assert_eq!(IncompleteSince::::get(), None); + assert_eq!( + logger::log(), + vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32), (root(), 3u32)] + ); + + // Will contain none + let actual_weight = Scheduler::on_initialize(4); + assert_eq!( + actual_weight, + TestWeightInfo::service_agendas_base() + TestWeightInfo::service_agenda_base(0) + ); + }); +} + +#[test] +fn root_calls_works() { + new_test_ext().execute_with(|| { + let call = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })); + let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })); + assert_ok!( + Scheduler::schedule_named(RuntimeOrigin::root(), [1u8; 32], 4, None, 127, call,) + ); + assert_ok!(Scheduler::schedule(RuntimeOrigin::root(), 4, None, 127, call2)); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::cancel_named(RuntimeOrigin::root(), [1u8; 32])); + assert_ok!(Scheduler::cancel(RuntimeOrigin::root(), 4, 1)); + // Scheduled calls are made NONE, so should not effect state + run_to_block(100); + assert!(logger::log().is_empty()); + }); +} + +#[test] +fn fails_to_schedule_task_in_the_past() { + new_test_ext().execute_with(|| { + run_to_block(3); + + let call1 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })); + let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })); + let call3 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })); + + assert_noop!( + Scheduler::schedule_named(RuntimeOrigin::root(), [1u8; 32], 2, None, 127, call1), + Error::::TargetBlockNumberInPast, + ); + + assert_noop!( + Scheduler::schedule(RuntimeOrigin::root(), 2, None, 127, call2), + Error::::TargetBlockNumberInPast, + ); + + assert_noop!( + Scheduler::schedule(RuntimeOrigin::root(), 3, None, 127, call3), + Error::::TargetBlockNumberInPast, + ); + }); +} + +#[test] +fn should_use_origin() { + new_test_ext().execute_with(|| { + let call = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })); + let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })); + assert_ok!(Scheduler::schedule_named( + system::RawOrigin::Signed(1).into(), + [1u8; 32], + 4, + None, + 127, + call, + )); + assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2,)); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::cancel_named(system::RawOrigin::Signed(1).into(), [1u8; 32])); + assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1)); + // Scheduled calls are made NONE, so should not effect state + run_to_block(100); + assert!(logger::log().is_empty()); + }); +} + +#[test] +fn should_check_origin() { + new_test_ext().execute_with(|| { + let call = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })); + let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })); + assert_noop!( + Scheduler::schedule_named( + system::RawOrigin::Signed(2).into(), + [1u8; 32], + 4, + None, + 127, + call + ), + BadOrigin + ); + assert_noop!( + Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, 127, call2), + BadOrigin + ); + }); +} + +#[test] +fn should_check_origin_for_cancel() { + new_test_ext().execute_with(|| { + let call = Box::new(RuntimeCall::Logger(LoggerCall::log_without_filter { + i: 69, + weight: Weight::from_ref_time(10), + })); + let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log_without_filter { + i: 42, + weight: Weight::from_ref_time(10), + })); + assert_ok!(Scheduler::schedule_named( + system::RawOrigin::Signed(1).into(), + [1u8; 32], + 4, + None, + 127, + call, + )); + assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2,)); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_noop!( + Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), [1u8; 32]), + BadOrigin + ); + assert_noop!(Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), BadOrigin); + assert_noop!(Scheduler::cancel_named(system::RawOrigin::Root.into(), [1u8; 32]), BadOrigin); + assert_noop!(Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), BadOrigin); + run_to_block(5); + assert_eq!( + logger::log(), + vec![ + (system::RawOrigin::Signed(1).into(), 69u32), + (system::RawOrigin::Signed(1).into(), 42u32) + ] + ); + }); +} + +#[test] +fn migration_to_v4_works() { + new_test_ext().execute_with(|| { + for i in 0..3u64 { + let k = i.twox_64_concat(); + let old = vec![ + Some(ScheduledV1 { + maybe_id: None, + priority: i as u8 + 10, + call: RuntimeCall::Logger(LoggerCall::log { + i: 96, + weight: Weight::from_ref_time(100), + }), + maybe_periodic: None, + }), + None, + Some(ScheduledV1 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + }), + maybe_periodic: Some((456u64, 10)), + }), + ]; + frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); + } + + Scheduler::migrate_v1_to_v4(); + + let mut x = Agenda::::iter().map(|x| (x.0, x.1.into_inner())).collect::>(); + x.sort_by_key(|x| x.0); + let expected = vec![ + ( + 0, + vec![ + Some(ScheduledOf:: { + maybe_id: None, + priority: 10, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 96, + weight: Weight::from_ref_time(100), + })) + .unwrap(), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledOf:: { + maybe_id: Some(blake2_256(&b"test"[..])), + priority: 123, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + maybe_periodic: Some((456u64, 10)), + origin: root(), + _phantom: PhantomData::::default(), + }), + ], + ), + ( + 1, + vec![ + Some(ScheduledOf:: { + maybe_id: None, + priority: 11, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 96, + weight: Weight::from_ref_time(100), + })) + .unwrap(), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledOf:: { + maybe_id: Some(blake2_256(&b"test"[..])), + priority: 123, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + maybe_periodic: Some((456u64, 10)), + origin: root(), + _phantom: PhantomData::::default(), + }), + ], + ), + ( + 2, + vec![ + Some(ScheduledOf:: { + maybe_id: None, + priority: 12, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 96, + weight: Weight::from_ref_time(100), + })) + .unwrap(), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledOf:: { + maybe_id: Some(blake2_256(&b"test"[..])), + priority: 123, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + maybe_periodic: Some((456u64, 10)), + origin: root(), + _phantom: PhantomData::::default(), + }), + ], + ), + ]; + for (i, j) in x.iter().zip(expected.iter()) { + assert_eq!(i.0, j.0); + for (x, y) in i.1.iter().zip(j.1.iter()) { + assert_eq!(x, y); + } + } + assert_eq_uvec!(x, expected); + + assert_eq!(Scheduler::current_storage_version(), 3); + }); +} + +#[test] +fn test_migrate_origin() { + new_test_ext().execute_with(|| { + for i in 0..3u64 { + let k = i.twox_64_concat(); + let old: Vec, u64, u32, u64>>> = vec![ + Some(Scheduled { + maybe_id: None, + priority: i as u8 + 10, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 96, + weight: Weight::from_ref_time(100), + })) + .unwrap(), + origin: 3u32, + maybe_periodic: None, + _phantom: Default::default(), + }), + None, + Some(Scheduled { + maybe_id: Some(blake2_256(&b"test"[..])), + priority: 123, + origin: 2u32, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + maybe_periodic: Some((456u64, 10)), + _phantom: Default::default(), + }), + ]; + frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); + } + + impl Into for u32 { + fn into(self) -> OriginCaller { + match self { + 3u32 => system::RawOrigin::Root.into(), + 2u32 => system::RawOrigin::None.into(), + _ => unreachable!("test make no use of it"), + } + } + } + + Scheduler::migrate_origin::(); + + assert_eq_uvec!( + Agenda::::iter().map(|x| (x.0, x.1.into_inner())).collect::>(), + vec![ + ( + 0, + vec![ + Some(ScheduledOf:: { + maybe_id: None, + priority: 10, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 96, + weight: Weight::from_ref_time(100) + })) + .unwrap(), + maybe_periodic: None, + origin: system::RawOrigin::Root.into(), + _phantom: PhantomData::::default(), + }), + None, + Some(Scheduled { + maybe_id: Some(blake2_256(&b"test"[..])), + priority: 123, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10) + })) + .unwrap(), + maybe_periodic: Some((456u64, 10)), + origin: system::RawOrigin::None.into(), + _phantom: PhantomData::::default(), + }), + ] + ), + ( + 1, + vec![ + Some(Scheduled { + maybe_id: None, + priority: 11, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 96, + weight: Weight::from_ref_time(100) + })) + .unwrap(), + maybe_periodic: None, + origin: system::RawOrigin::Root.into(), + _phantom: PhantomData::::default(), + }), + None, + Some(Scheduled { + maybe_id: Some(blake2_256(&b"test"[..])), + priority: 123, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10) + })) + .unwrap(), + maybe_periodic: Some((456u64, 10)), + origin: system::RawOrigin::None.into(), + _phantom: PhantomData::::default(), + }), + ] + ), + ( + 2, + vec![ + Some(Scheduled { + maybe_id: None, + priority: 12, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 96, + weight: Weight::from_ref_time(100) + })) + .unwrap(), + maybe_periodic: None, + origin: system::RawOrigin::Root.into(), + _phantom: PhantomData::::default(), + }), + None, + Some(Scheduled { + maybe_id: Some(blake2_256(&b"test"[..])), + priority: 123, + call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10) + })) + .unwrap(), + maybe_periodic: Some((456u64, 10)), + origin: system::RawOrigin::None.into(), + _phantom: PhantomData::::default(), + }), + ] + ) + ] + ); + }); +} + +#[test] +fn postponed_named_task_cannot_be_rescheduled() { + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(1000) }); + let hash = ::Hashing::hash_of(&call); + let len = call.using_encoded(|x| x.len()) as u32; + let hashed = Preimage::pick(hash, len); + let name: [u8; 32] = hash.as_ref().try_into().unwrap(); + + let address = Scheduler::do_schedule_named( + name, + DispatchTime::At(4), + None, + 127, + root(), + hashed.clone(), + ) + .unwrap(); + assert!(Preimage::is_requested(&hash)); + assert!(Lookup::::contains_key(name)); + + // Run to a very large block. + run_to_block(10); + // It was not executed. + assert!(logger::log().is_empty()); + assert!(Preimage::is_requested(&hash)); + // Postponing removes the lookup. + assert!(!Lookup::::contains_key(name)); + + // The agenda still contains the call. + let agenda = Agenda::::iter().collect::>(); + assert_eq!(agenda.len(), 1); + assert_eq!( + agenda[0].1, + vec![Some(Scheduled { + maybe_id: Some(name), + priority: 127, + call: hashed, + maybe_periodic: None, + origin: root().into(), + _phantom: Default::default(), + })] + ); + + // Finally add the preimage. + assert_ok!(Preimage::note(call.encode().into())); + run_to_block(1000); + // It did not execute. + assert!(logger::log().is_empty()); + assert!(Preimage::is_requested(&hash)); + + // Manually re-schedule the call by name does not work. + assert_err!( + Scheduler::do_reschedule_named(name, DispatchTime::At(1001)), + Error::::NotFound + ); + // Manually re-scheduling the call by address errors. + assert_err!( + Scheduler::do_reschedule(address, DispatchTime::At(1001)), + Error::::Named + ); + }); +} + +/// Using the scheduler as `v3::Anon` works. +#[test] +fn scheduler_v3_anon_basic_works() { + use frame_support::traits::schedule::v3::Anon; + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + + // Schedule a call. + let _address = >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(); + + run_to_block(3); + // Did not execute till block 3. + assert!(logger::log().is_empty()); + // Executes in block 4. + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // ... but not again. + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn scheduler_v3_anon_cancel_works() { + use frame_support::traits::schedule::v3::Anon; + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let bound = Preimage::bound(call).unwrap(); + + // Schedule a call. + let address = >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + bound.clone(), + ) + .unwrap(); + // Cancel the call. + assert_ok!(>::cancel(address)); + // It did not get executed. + run_to_block(100); + assert!(logger::log().is_empty()); + // Cannot cancel again. + assert_err!(>::cancel(address), DispatchError::Unavailable); + }); +} + +#[test] +fn scheduler_v3_anon_reschedule_works() { + use frame_support::traits::schedule::v3::Anon; + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + + // Schedule a call. + let address = >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(); + + run_to_block(3); + // Did not execute till block 3. + assert!(logger::log().is_empty()); + + // Cannot re-schedule into the same block. + assert_noop!( + >::reschedule(address, DispatchTime::At(4)), + Error::::RescheduleNoChange + ); + // Cannot re-schedule into the past. + assert_noop!( + >::reschedule(address, DispatchTime::At(3)), + Error::::TargetBlockNumberInPast + ); + // Re-schedule to block 5. + assert_ok!(>::reschedule(address, DispatchTime::At(5))); + // Scheduled for block 5. + run_to_block(4); + assert!(logger::log().is_empty()); + run_to_block(5); + // Does execute in block 5. + assert_eq!(logger::log(), vec![(root(), 42)]); + // Cannot re-schedule executed task. + assert_noop!( + >::reschedule(address, DispatchTime::At(10)), + DispatchError::Unavailable + ); + }); +} + +/// Cancelling a call and then scheduling a second call for the same +/// block results in different addresses. +#[test] +fn scheduler_v3_anon_schedule_does_not_resuse_addr() { + use frame_support::traits::schedule::v3::Anon; + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + + // Schedule both calls. + let addr_1 = >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call.clone()).unwrap(), + ) + .unwrap(); + // Cancel the call. + assert_ok!(>::cancel(addr_1)); + let addr_2 = >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(); + + // Should not re-use the address. + assert!(addr_1 != addr_2); + }); +} + +#[test] +fn scheduler_v3_anon_next_schedule_time_works() { + use frame_support::traits::schedule::v3::Anon; + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let bound = Preimage::bound(call).unwrap(); + + // Schedule a call. + let address = >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + bound.clone(), + ) + .unwrap(); + + run_to_block(3); + // Did not execute till block 3. + assert!(logger::log().is_empty()); + + // Scheduled for block 4. + assert_eq!(>::next_dispatch_time(address), Ok(4)); + // Block 4 executes it. + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42)]); + + // It has no dispatch time anymore. + assert_noop!( + >::next_dispatch_time(address), + DispatchError::Unavailable + ); + }); +} + +/// Re-scheduling a task changes its next dispatch time. +#[test] +fn scheduler_v3_anon_reschedule_and_next_schedule_time_work() { + use frame_support::traits::schedule::v3::Anon; + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let bound = Preimage::bound(call).unwrap(); + + // Schedule a call. + let old_address = >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + bound.clone(), + ) + .unwrap(); + + run_to_block(3); + // Did not execute till block 3. + assert!(logger::log().is_empty()); + + // Scheduled for block 4. + assert_eq!(>::next_dispatch_time(old_address), Ok(4)); + // Re-schedule to block 5. + let address = + >::reschedule(old_address, DispatchTime::At(5)).unwrap(); + assert!(address != old_address); + // Scheduled for block 5. + assert_eq!(>::next_dispatch_time(address), Ok(5)); + + // Block 4 does nothing. + run_to_block(4); + assert!(logger::log().is_empty()); + // Block 5 executes it. + run_to_block(5); + assert_eq!(logger::log(), vec![(root(), 42)]); + }); +} + +#[test] +fn scheduler_v3_anon_schedule_agenda_overflows() { + use frame_support::traits::schedule::v3::Anon; + let max: u32 = ::MaxScheduledPerBlock::get(); + + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let bound = Preimage::bound(call).unwrap(); + + // Schedule the maximal number allowed per block. + for _ in 0..max { + >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + bound.clone(), + ) + .unwrap(); + } + + // One more time and it errors. + assert_noop!( + >::schedule(DispatchTime::At(4), None, 127, root(), bound,), + DispatchError::Exhausted + ); + + run_to_block(4); + // All scheduled calls are executed. + assert_eq!(logger::log().len() as u32, max); + }); +} + +/// Cancelling and scheduling does not overflow the agenda but fills holes. +#[test] +fn scheduler_v3_anon_cancel_and_schedule_fills_holes() { + use frame_support::traits::schedule::v3::Anon; + let max: u32 = ::MaxScheduledPerBlock::get(); + assert!(max > 3, "This test only makes sense for MaxScheduledPerBlock > 3"); + + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let bound = Preimage::bound(call).unwrap(); + let mut addrs = Vec::<_>::default(); + + // Schedule the maximal number allowed per block. + for _ in 0..max { + addrs.push( + >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + bound.clone(), + ) + .unwrap(), + ); + } + // Cancel three of them. + for addr in addrs.into_iter().take(3) { + >::cancel(addr).unwrap(); + } + // Schedule three new ones. + for i in 0..3 { + let (_block, index) = >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + bound.clone(), + ) + .unwrap(); + assert_eq!(i, index); + } + + run_to_block(4); + // Maximum number of calls are executed. + assert_eq!(logger::log().len() as u32, max); + }); +} + +/// Re-scheduling does not overflow the agenda but fills holes. +#[test] +fn scheduler_v3_anon_reschedule_fills_holes() { + use frame_support::traits::schedule::v3::Anon; + let max: u32 = ::MaxScheduledPerBlock::get(); + assert!(max > 3, "pre-condition: This test only makes sense for MaxScheduledPerBlock > 3"); + + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let bound = Preimage::bound(call).unwrap(); + let mut addrs = Vec::<_>::default(); + + // Schedule the maximal number allowed per block. + for _ in 0..max { + addrs.push( + >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + bound.clone(), + ) + .unwrap(), + ); + } + let mut new_addrs = Vec::<_>::default(); + // Reversed last three elements of block 4. + let last_three = addrs.into_iter().rev().take(3).collect::>(); + // Re-schedule three of them to block 5. + for addr in last_three.iter().cloned() { + new_addrs + .push(>::reschedule(addr, DispatchTime::At(5)).unwrap()); + } + // Re-scheduling them back into block 3 should result in the same addrs. + for (old, want) in new_addrs.into_iter().zip(last_three.into_iter().rev()) { + let new = >::reschedule(old, DispatchTime::At(4)).unwrap(); + assert_eq!(new, want); + } + + run_to_block(4); + // Maximum number of calls are executed. + assert_eq!(logger::log().len() as u32, max); + }); +} + +/// Re-scheduling into the same block produces a different address +/// if there is still space in the agenda. +#[test] +fn scheduler_v3_anon_reschedule_does_not_resuse_addr_if_agenda_not_full() { + use frame_support::traits::schedule::v3::Anon; + let max: u32 = ::MaxScheduledPerBlock::get(); + assert!(max > 1, "This test only makes sense for MaxScheduledPerBlock > 1"); + + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + + // Schedule both calls. + let addr_1 = >::schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call.clone()).unwrap(), + ) + .unwrap(); + // Cancel the call. + assert_ok!(>::cancel(addr_1)); + let addr_2 = >::schedule( + DispatchTime::At(5), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(); + // Re-schedule `call` to block 4. + let addr_3 = >::reschedule(addr_2, DispatchTime::At(4)).unwrap(); + + // Should not re-use the address. + assert!(addr_1 != addr_3); + }); +} + +/// The scheduler can be used as `v3::Named` trait. +#[test] +fn scheduler_v3_named_basic_works() { + use frame_support::traits::schedule::v3::Named; + + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let name = [1u8; 32]; + + // Schedule a call. + let _address = >::schedule_named( + name, + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(); + + run_to_block(3); + // Did not execute till block 3. + assert!(logger::log().is_empty()); + // Executes in block 4. + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // ... but not again. + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +/// A named task can be cancelled by its name. +#[test] +fn scheduler_v3_named_cancel_named_works() { + use frame_support::traits::schedule::v3::Named; + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let bound = Preimage::bound(call).unwrap(); + let name = [1u8; 32]; + + // Schedule a call. + >::schedule_named( + name, + DispatchTime::At(4), + None, + 127, + root(), + bound.clone(), + ) + .unwrap(); + // Cancel the call by name. + assert_ok!(>::cancel_named(name)); + // It did not get executed. + run_to_block(100); + assert!(logger::log().is_empty()); + // Cannot cancel again. + assert_noop!(>::cancel_named(name), DispatchError::Unavailable); + }); +} + +/// A named task can also be cancelled by its address. +#[test] +fn scheduler_v3_named_cancel_without_name_works() { + use frame_support::traits::schedule::v3::{Anon, Named}; + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let bound = Preimage::bound(call).unwrap(); + let name = [1u8; 32]; + + // Schedule a call. + let address = >::schedule_named( + name, + DispatchTime::At(4), + None, + 127, + root(), + bound.clone(), + ) + .unwrap(); + // Cancel the call by address. + assert_ok!(>::cancel(address)); + // It did not get executed. + run_to_block(100); + assert!(logger::log().is_empty()); + // Cannot cancel again. + assert_err!(>::cancel(address), DispatchError::Unavailable); + }); +} + +/// A named task can be re-scheduled by its name but not by its address. +#[test] +fn scheduler_v3_named_reschedule_named_works() { + use frame_support::traits::schedule::v3::{Anon, Named}; + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let name = [1u8; 32]; + + // Schedule a call. + let address = >::schedule_named( + name, + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(); + + run_to_block(3); + // Did not execute till block 3. + assert!(logger::log().is_empty()); + + // Cannot re-schedule by address. + assert_noop!( + >::reschedule(address, DispatchTime::At(10)), + Error::::Named, + ); + // Cannot re-schedule into the same block. + assert_noop!( + >::reschedule_named(name, DispatchTime::At(4)), + Error::::RescheduleNoChange + ); + // Cannot re-schedule into the past. + assert_noop!( + >::reschedule_named(name, DispatchTime::At(3)), + Error::::TargetBlockNumberInPast + ); + // Re-schedule to block 5. + assert_ok!(>::reschedule_named(name, DispatchTime::At(5))); + // Scheduled for block 5. + run_to_block(4); + assert!(logger::log().is_empty()); + run_to_block(5); + // Does execute in block 5. + assert_eq!(logger::log(), vec![(root(), 42)]); + // Cannot re-schedule executed task. + assert_noop!( + >::reschedule_named(name, DispatchTime::At(10)), + DispatchError::Unavailable + ); + // Also not by address. + assert_noop!( + >::reschedule(address, DispatchTime::At(10)), + DispatchError::Unavailable + ); + }); +} + +#[test] +fn scheduler_v3_named_next_schedule_time_works() { + use frame_support::traits::schedule::v3::{Anon, Named}; + new_test_ext().execute_with(|| { + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let bound = Preimage::bound(call).unwrap(); + let name = [1u8; 32]; + + // Schedule a call. + let address = >::schedule_named( + name, + DispatchTime::At(4), + None, + 127, + root(), + bound.clone(), + ) + .unwrap(); + + run_to_block(3); + // Did not execute till block 3. + assert!(logger::log().is_empty()); + + // Scheduled for block 4. + assert_eq!(>::next_dispatch_time(name), Ok(4)); + // Also works by address. + assert_eq!(>::next_dispatch_time(address), Ok(4)); + // Block 4 executes it. + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42)]); + + // It has no dispatch time anymore. + assert_noop!( + >::next_dispatch_time(name), + DispatchError::Unavailable + ); + // Also not by address. + assert_noop!( + >::next_dispatch_time(address), + DispatchError::Unavailable + ); + }); +} diff --git a/frame/scheduler/src/weights.rs b/frame/scheduler/src/weights.rs index d83aefdc453af..5b86e7a143e7a 100644 --- a/frame/scheduler/src/weights.rs +++ b/frame/scheduler/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_scheduler //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/scheduler/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -45,6 +47,14 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_scheduler. pub trait WeightInfo { + fn service_agendas_base() -> Weight; + fn service_agenda_base(s: u32, ) -> Weight; + fn service_task_base() -> Weight; + fn service_task_fetched(s: u32, ) -> Weight; + fn service_task_named() -> Weight; + fn service_task_periodic() -> Weight; + fn execute_dispatch_signed() -> Weight; + fn execute_dispatch_unsigned() -> Weight; fn schedule(s: u32, ) -> Weight; fn cancel(s: u32, ) -> Weight; fn schedule_named(s: u32, ) -> Weight; @@ -54,78 +64,194 @@ pub trait WeightInfo { /// Weights for pallet_scheduler using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + // Storage: Scheduler IncompleteSince (r:1 w:1) + fn service_agendas_base() -> Weight { + // Minimum execution time: 5_131 nanoseconds. + Weight::from_ref_time(5_286_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 512]`. + fn service_agenda_base(s: u32, ) -> Weight { + // Minimum execution time: 4_111 nanoseconds. + Weight::from_ref_time(8_763_440 as u64) + // Standard Error: 783 + .saturating_add(Weight::from_ref_time(372_339 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn service_task_base() -> Weight { + // Minimum execution time: 10_880 nanoseconds. + Weight::from_ref_time(11_194_000 as u64) + } + // Storage: Preimage PreimageFor (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + /// The range of component `s` is `[128, 4194304]`. + fn service_task_fetched(s: u32, ) -> Weight { + // Minimum execution time: 25_347 nanoseconds. + Weight::from_ref_time(25_717_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_128 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Scheduler Lookup (r:0 w:1) + fn service_task_named() -> Weight { + // Minimum execution time: 12_894 nanoseconds. + Weight::from_ref_time(13_108_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn service_task_periodic() -> Weight { + // Minimum execution time: 10_667 nanoseconds. + Weight::from_ref_time(10_908_000 as u64) + } + fn execute_dispatch_signed() -> Weight { + // Minimum execution time: 4_124 nanoseconds. + Weight::from_ref_time(4_680_000 as u64) + } + fn execute_dispatch_unsigned() -> Weight { + // Minimum execution time: 4_156 nanoseconds. + Weight::from_ref_time(4_361_000 as u64) + } // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 511]`. fn schedule(s: u32, ) -> Weight { - (24_730_000 as Weight) - // Standard Error: 1_000 - .saturating_add((77_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 20_504 nanoseconds. + Weight::from_ref_time(27_066_818 as u64) + // Standard Error: 1_114 + .saturating_add(Weight::from_ref_time(372_897 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Scheduler Agenda (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) + /// The range of component `s` is `[1, 512]`. fn cancel(s: u32, ) -> Weight { - (23_272_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_261_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 21_686 nanoseconds. + Weight::from_ref_time(25_696_496 as u64) + // Standard Error: 1_261 + .saturating_add(Weight::from_ref_time(362_498 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 511]`. fn schedule_named(s: u32, ) -> Weight { - (30_971_000 as Weight) - // Standard Error: 1_000 - .saturating_add((96_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 23_084 nanoseconds. + Weight::from_ref_time(31_255_518 as u64) + // Standard Error: 1_258 + .saturating_add(Weight::from_ref_time(382_534 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[1, 512]`. fn cancel_named(s: u32, ) -> Weight { - (25_778_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_270_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 23_862 nanoseconds. + Weight::from_ref_time(28_591_336 as u64) + // Standard Error: 742 + .saturating_add(Weight::from_ref_time(369_305 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { + // Storage: Scheduler IncompleteSince (r:1 w:1) + fn service_agendas_base() -> Weight { + // Minimum execution time: 5_131 nanoseconds. + Weight::from_ref_time(5_286_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 512]`. + fn service_agenda_base(s: u32, ) -> Weight { + // Minimum execution time: 4_111 nanoseconds. + Weight::from_ref_time(8_763_440 as u64) + // Standard Error: 783 + .saturating_add(Weight::from_ref_time(372_339 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn service_task_base() -> Weight { + // Minimum execution time: 10_880 nanoseconds. + Weight::from_ref_time(11_194_000 as u64) + } + // Storage: Preimage PreimageFor (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + /// The range of component `s` is `[128, 4194304]`. + fn service_task_fetched(s: u32, ) -> Weight { + // Minimum execution time: 25_347 nanoseconds. + Weight::from_ref_time(25_717_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_128 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Scheduler Lookup (r:0 w:1) + fn service_task_named() -> Weight { + // Minimum execution time: 12_894 nanoseconds. + Weight::from_ref_time(13_108_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn service_task_periodic() -> Weight { + // Minimum execution time: 10_667 nanoseconds. + Weight::from_ref_time(10_908_000 as u64) + } + fn execute_dispatch_signed() -> Weight { + // Minimum execution time: 4_124 nanoseconds. + Weight::from_ref_time(4_680_000 as u64) + } + fn execute_dispatch_unsigned() -> Weight { + // Minimum execution time: 4_156 nanoseconds. + Weight::from_ref_time(4_361_000 as u64) + } // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 511]`. fn schedule(s: u32, ) -> Weight { - (24_730_000 as Weight) - // Standard Error: 1_000 - .saturating_add((77_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 20_504 nanoseconds. + Weight::from_ref_time(27_066_818 as u64) + // Standard Error: 1_114 + .saturating_add(Weight::from_ref_time(372_897 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Scheduler Agenda (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) + /// The range of component `s` is `[1, 512]`. fn cancel(s: u32, ) -> Weight { - (23_272_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_261_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 21_686 nanoseconds. + Weight::from_ref_time(25_696_496 as u64) + // Standard Error: 1_261 + .saturating_add(Weight::from_ref_time(362_498 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 511]`. fn schedule_named(s: u32, ) -> Weight { - (30_971_000 as Weight) - // Standard Error: 1_000 - .saturating_add((96_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 23_084 nanoseconds. + Weight::from_ref_time(31_255_518 as u64) + // Standard Error: 1_258 + .saturating_add(Weight::from_ref_time(382_534 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[1, 512]`. fn cancel_named(s: u32, ) -> Weight { - (25_778_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_270_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 23_862 nanoseconds. + Weight::from_ref_time(28_591_336 as u64) + // Standard Error: 742 + .saturating_add(Weight::from_ref_time(369_305 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } } diff --git a/frame/scored-pool/Cargo.toml b/frame/scored-pool/Cargo.toml index 9d5f156c175d5..a1e8dc453df6c 100644 --- a/frame/scored-pool/Cargo.toml +++ b/frame/scored-pool/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-scored-pool" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for scored pools" readme = "README.md" @@ -13,27 +13,27 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/scored-pool/README.md b/frame/scored-pool/README.md index bf20124edf52e..455bae24e7951 100644 --- a/frame/scored-pool/README.md +++ b/frame/scored-pool/README.md @@ -37,26 +37,33 @@ by the next highest scoring candidate in the pool, if available. ## Usage ```rust -use frame_support::{decl_module, dispatch}; -use frame_system::ensure_signed; use pallet_scored_pool::{self as scored_pool}; -pub trait Config: scored_pool::Config {} - -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - #[weight = 0] - pub fn candidate(origin) -> dispatch::DispatchResult { - let who = ensure_signed(origin)?; - - let _ = >::submit_candidacy( - T::Origin::from(Some(who.clone()).into()) - ); - Ok(()) - } - } +#[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: frame_system::Config + scored_pool::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn candidate(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + + let _ = >::submit_candidacy( + T::RuntimeOrigin::from(Some(who.clone()).into()) + ); + Ok(()) + } + } } - ``` ## Dependencies diff --git a/frame/scored-pool/src/lib.rs b/frame/scored-pool/src/lib.rs index a5cdb6274f995..5db9c6506d770 100644 --- a/frame/scored-pool/src/lib.rs +++ b/frame/scored-pool/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -54,20 +54,28 @@ //! ## Usage //! //! ``` -//! use frame_support::{decl_module, dispatch}; -//! use frame_system::ensure_signed; //! use pallet_scored_pool::{self as scored_pool}; //! -//! pub trait Config: scored_pool::Config {} +//! #[frame_support::pallet] +//! pub mod pallet { +//! use super::*; +//! use frame_support::pallet_prelude::*; +//! use frame_system::pallet_prelude::*; //! -//! decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { -//! #[weight = 0] -//! pub fn candidate(origin) -> dispatch::DispatchResult { +//! #[pallet::pallet] +//! pub struct Pallet(_); +//! +//! #[pallet::config] +//! pub trait Config: frame_system::Config + scored_pool::Config {} +//! +//! #[pallet::call] +//! impl Pallet { +//! #[pallet::weight(0)] +//! pub fn candidate(origin: OriginFor) -> DispatchResult { //! let who = ensure_signed(origin)?; //! //! let _ = >::submit_candidacy( -//! T::Origin::from(Some(who.clone()).into()) +//! T::RuntimeOrigin::from(Some(who.clone()).into()) //! ); //! Ok(()) //! } @@ -90,10 +98,11 @@ mod mock; #[cfg(test)] mod tests; -use codec::FullCodec; +use codec::{FullCodec, MaxEncodedLen}; use frame_support::{ ensure, traits::{ChangeMembers, Currency, Get, InitializeMembers, ReservableCurrency}, + BoundedVec, }; pub use pallet::*; use sp_runtime::traits::{AtLeast32Bit, StaticLookup, Zero}; @@ -101,7 +110,13 @@ use sp_std::{fmt::Debug, prelude::*}; type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; -type PoolT = Vec<(::AccountId, Option<>::Score>)>; +type PoolT = BoundedVec< + (::AccountId, Option<>::Score>), + >::MaximumMembers, +>; +type MembersT = + BoundedVec<::AccountId, >::MaximumMembers>; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; /// The enum is supplied when refreshing the members set. /// Depending on the enum variant the corresponding associated @@ -116,9 +131,8 @@ enum ChangeReceiver { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, traits::EnsureOrigin, weights::Weight}; - use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; - use sp_runtime::traits::MaybeSerializeDeserialize; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -129,6 +143,10 @@ pub mod pallet { /// The currency used for deposits. type Currency: Currency + ReservableCurrency; + /// Maximum members length allowed. + #[pallet::constant] + type MaximumMembers: Get; + /// The score attributed to a member or candidate. type Score: AtLeast32Bit + Clone @@ -137,10 +155,12 @@ pub mod pallet { + FullCodec + MaybeSerializeDeserialize + Debug - + scale_info::TypeInfo; + + scale_info::TypeInfo + + MaxEncodedLen; /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; // The deposit which is reserved from candidates if they want to // start a candidacy. The deposit gets returned when the candidacy is @@ -163,13 +183,13 @@ pub mod pallet { type MembershipChanged: ChangeMembers; /// Allows a configurable origin type to set a score to a candidate in the pool. - type ScoreOrigin: EnsureOrigin; + type ScoreOrigin: EnsureOrigin; /// Required origin for removing a member (though can always be Root). /// Configurable origin which enables removing an entity. If the entity /// is part of the `Members` it is immediately replaced by the next /// highest scoring candidate, if available. - type KickOrigin: EnsureOrigin; + type KickOrigin: EnsureOrigin; } #[pallet::event] @@ -198,9 +218,11 @@ pub mod pallet { InvalidIndex, /// Index does not match requested account. WrongAccountIndex, + /// Number of members exceeds `MaximumMembers`. + TooManyMembers, } - /// The current pool of candidates, stored as an ordered Vec + /// The current pool of candidates, stored as an ordered Bounded Vec /// (ordered descending by score, `None` last, highest first). #[pallet::storage] #[pallet::getter(fn pool)] @@ -220,7 +242,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn members)] pub(crate) type Members, I: 'static = ()> = - StorageValue<_, Vec, ValueQuery>; + StorageValue<_, MembersT, ValueQuery>; /// Size of the `Members` set. #[pallet::storage] @@ -248,16 +270,16 @@ pub mod pallet { // reserve balance for each candidate in the pool. // panicking here is ok, since this just happens one time, pre-genesis. pool.iter().for_each(|(who, _)| { - T::Currency::reserve(&who, T::CandidateDeposit::get()) + T::Currency::reserve(who, T::CandidateDeposit::get()) .expect("balance too low to create candidacy"); >::insert(who, true); }); // Sorts the `Pool` by score in a descending order. Entities which - // have a score of `None` are sorted to the beginning of the vec. + // have a score of `None` are sorted to the end of the bounded vec. pool.sort_by_key(|(_, maybe_score)| Reverse(maybe_score.unwrap_or_default())); - - >::put(self.member_count); + >::update_member_count(self.member_count) + .expect("Number of allowed members exceeded"); >::put(&pool); >::refresh_members(pool, ChangeReceiver::MembershipInitialized); } @@ -272,7 +294,7 @@ pub mod pallet { let pool = >::get(); >::refresh_members(pool, ChangeReceiver::MembershipChanged); } - 0 + Weight::zero() } } @@ -289,6 +311,7 @@ pub mod pallet { /// /// The `index` parameter of this function must be set to /// the index of the transactor in the `Pool`. + #[pallet::call_index(0)] #[pallet::weight(0)] pub fn submit_candidacy(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -299,7 +322,8 @@ pub mod pallet { // can be inserted as last element in pool, since entities with // `None` are always sorted to the end. - >::append((who.clone(), Option::<>::Score>::None)); + >::try_append((who.clone(), Option::<>::Score>::None)) + .map_err(|_| Error::::TooManyMembers)?; >::insert(&who, true); @@ -317,6 +341,7 @@ pub mod pallet { /// /// The `index` parameter of this function must be set to /// the index of the transactor in the `Pool`. + #[pallet::call_index(1)] #[pallet::weight(0)] pub fn withdraw_candidacy(origin: OriginFor, index: u32) -> DispatchResult { let who = ensure_signed(origin)?; @@ -335,10 +360,11 @@ pub mod pallet { /// /// The `index` parameter of this function must be set to /// the index of `dest` in the `Pool`. + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn kick( origin: OriginFor, - dest: ::Source, + dest: AccountIdLookupOf, index: u32, ) -> DispatchResult { T::KickOrigin::ensure_origin(origin)?; @@ -359,10 +385,11 @@ pub mod pallet { /// /// The `index` parameter of this function must be set to /// the index of the `dest` in the `Pool`. + #[pallet::call_index(3)] #[pallet::weight(0)] pub fn score( origin: OriginFor, - dest: ::Source, + dest: AccountIdLookupOf, index: u32, score: T::Score, ) -> DispatchResult { @@ -379,13 +406,13 @@ pub mod pallet { // if there is already an element with `score`, we insert // right before that. if not, the search returns a location // where we can insert while maintaining order. - let item = (who, Some(score.clone())); + let item = (who, Some(score)); let location = pool .binary_search_by_key(&Reverse(score), |(_, maybe_score)| { Reverse(maybe_score.unwrap_or_default()) }) .unwrap_or_else(|l| l); - pool.insert(location, item); + pool.try_insert(location, item).map_err(|_| Error::::TooManyMembers)?; >::put(&pool); Self::deposit_event(Event::::CandidateScored); @@ -398,11 +425,11 @@ pub mod pallet { /// (this happens each `Period`). /// /// May only be called from root. + #[pallet::call_index(4)] #[pallet::weight(0)] pub fn change_member_count(origin: OriginFor, count: u32) -> DispatchResult { ensure_root(origin)?; - MemberCount::::put(&count); - Ok(()) + Self::update_member_count(count).map_err(Into::into) } } } @@ -415,23 +442,28 @@ impl, I: 'static> Pallet { /// type function to invoke at the end of the method. fn refresh_members(pool: PoolT, notify: ChangeReceiver) { let count = MemberCount::::get(); + let old_members = >::get(); - let mut new_members: Vec = pool + let new_members: Vec = pool .into_iter() .filter(|(_, score)| score.is_some()) .take(count as usize) .map(|(account_id, _)| account_id) .collect(); - new_members.sort(); - let old_members = >::get(); - >::put(&new_members); + // It's safe to truncate_from at this point since MemberCount + // is verified that it does not exceed the MaximumMembers value + let mut new_members_bounded: MembersT = BoundedVec::truncate_from(new_members); + + new_members_bounded.sort(); + + >::put(&new_members_bounded); match notify { ChangeReceiver::MembershipInitialized => - T::MembershipInitialized::initialize_members(&new_members), + T::MembershipInitialized::initialize_members(&new_members_bounded), ChangeReceiver::MembershipChanged => - T::MembershipChanged::set_members_sorted(&new_members[..], &old_members[..]), + T::MembershipChanged::set_members_sorted(&new_members_bounded[..], &old_members[..]), } } @@ -477,4 +509,11 @@ impl, I: 'static> Pallet { Ok(()) } + + /// Make sure the new member count value does not exceed the MaximumMembers + fn update_member_count(new_member_count: u32) -> Result<(), Error> { + ensure!(new_member_count <= T::MaximumMembers::get(), Error::::TooManyMembers); + >::put(new_member_count); + Ok(()) + } } diff --git a/frame/scored-pool/src/mock.rs b/frame/scored-pool/src/mock.rs index 5c5425ae2bdd8..d6f653b32ad2d 100644 --- a/frame/scored-pool/src/mock.rs +++ b/frame/scored-pool/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,19 +20,21 @@ use super::*; use crate as pallet_scored_pool; -use frame_support::{ord_parameter_types, parameter_types, traits::GenesisBuild}; +use frame_support::{ + bounded_vec, construct_runtime, ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64, GenesisBuild}, +}; use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; -use std::cell::RefCell; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test where Block = Block, NodeBlock = Block, @@ -46,11 +48,8 @@ frame_support::construct_runtime!( parameter_types! { pub const CandidateDeposit: u64 = 25; - pub const Period: u64 = 4; - pub const BlockHashCount: u64 = 250; - pub const ExistentialDeposit: u64 = 1; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } ord_parameter_types! { pub const KickOrigin: u64 = 2; @@ -62,17 +61,17 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -81,6 +80,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { @@ -88,21 +88,21 @@ impl pallet_balances::Config for Test { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } -thread_local! { - pub static MEMBERS: RefCell> = RefCell::new(vec![]); +parameter_types! { + pub static MembersTestValue: BoundedVec> = bounded_vec![0,10]; } pub struct TestChangeMembers; impl ChangeMembers for TestChangeMembers { fn change_members_sorted(incoming: &[u64], outgoing: &[u64], new: &[u64]) { - let mut old_plus_incoming = MEMBERS.with(|m| m.borrow().to_vec()); + let mut old_plus_incoming = MembersTestValue::get().into_inner(); old_plus_incoming.extend_from_slice(incoming); old_plus_incoming.sort(); @@ -112,47 +112,47 @@ impl ChangeMembers for TestChangeMembers { assert_eq!(old_plus_incoming, new_plus_outgoing); - MEMBERS.with(|m| *m.borrow_mut() = new.to_vec()); + MembersTestValue::set(>>::truncate_from(new.to_vec())); } } impl InitializeMembers for TestChangeMembers { fn initialize_members(new_members: &[u64]) { - MEMBERS.with(|m| *m.borrow_mut() = new_members.to_vec()); + MembersTestValue::set(>>::truncate_from( + new_members.to_vec(), + )); } } impl Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type KickOrigin = EnsureSignedBy; type MembershipInitialized = TestChangeMembers; type MembershipChanged = TestChangeMembers; type Currency = Balances; type CandidateDeposit = CandidateDeposit; - type Period = Period; + type Period = ConstU64<4>; type Score = u64; type ScoreOrigin = EnsureSignedBy; + type MaximumMembers = ConstU32<10>; } pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { - balances: vec![ - (5, 500_000), - (10, 500_000), - (15, 500_000), - (20, 500_000), - (31, 500_000), - (40, 500_000), - (99, 1), - ], + let mut balances = vec![]; + for i in 1..31 { + balances.push((i, 500_000)); } - .assimilate_storage(&mut t) - .unwrap(); + balances.push((31, 500_000)); + balances.push((40, 500_000)); + balances.push((99, 1)); + + pallet_balances::GenesisConfig:: { balances } + .assimilate_storage(&mut t) + .unwrap(); pallet_scored_pool::GenesisConfig:: { - pool: vec![(5, None), (10, Some(1)), (20, Some(2)), (31, Some(2)), (40, Some(3))], + pool: bounded_vec![(10, Some(1)), (20, Some(2)), (31, Some(2)), (40, Some(3)), (5, None)], member_count: 2, - ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/frame/scored-pool/src/tests.rs b/frame/scored-pool/src/tests.rs index 0503e308e76a5..8f4daff47cc44 100644 --- a/frame/scored-pool/src/tests.rs +++ b/frame/scored-pool/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,7 @@ fn query_membership_works() { assert_eq!(ScoredPool::members(), vec![20, 40]); assert_eq!(Balances::reserved_balance(31), CandidateDeposit::get()); assert_eq!(Balances::reserved_balance(40), CandidateDeposit::get()); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), vec![20, 40]); + assert_eq!(MembersTestValue::get().clone(), vec![20, 40]); }); } @@ -41,11 +41,11 @@ fn query_membership_works() { fn submit_candidacy_must_not_work() { new_test_ext().execute_with(|| { assert_noop!( - ScoredPool::submit_candidacy(Origin::signed(99)), + ScoredPool::submit_candidacy(RuntimeOrigin::signed(99)), pallet_balances::Error::::InsufficientBalance, ); assert_noop!( - ScoredPool::submit_candidacy(Origin::signed(40)), + ScoredPool::submit_candidacy(RuntimeOrigin::signed(40)), Error::::AlreadyInPool ); }); @@ -58,7 +58,7 @@ fn submit_candidacy_works() { let who = 15; // when - assert_ok!(ScoredPool::submit_candidacy(Origin::signed(who))); + assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who))); assert_eq!(fetch_from_pool(15), Some((who, None))); // then @@ -72,11 +72,11 @@ fn scoring_works() { // given let who = 15; let score = 99; - assert_ok!(ScoredPool::submit_candidacy(Origin::signed(who))); + assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who))); // when let index = find_in_pool(who).expect("entity must be in pool") as u32; - assert_ok!(ScoredPool::score(Origin::signed(ScoreOrigin::get()), who, index, score)); + assert_ok!(ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), who, index, score)); // then assert_eq!(fetch_from_pool(who), Some((who, Some(score)))); @@ -93,7 +93,7 @@ fn scoring_same_element_with_same_score_works() { let score = 2; // when - assert_ok!(ScoredPool::score(Origin::signed(ScoreOrigin::get()), who, index, score)); + assert_ok!(ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), who, index, score)); // then assert_eq!(fetch_from_pool(who), Some((who, Some(score)))); @@ -109,7 +109,7 @@ fn kicking_works_only_for_authorized() { new_test_ext().execute_with(|| { let who = 40; let index = find_in_pool(who).expect("entity must be in pool") as u32; - assert_noop!(ScoredPool::kick(Origin::signed(99), who, index), BadOrigin); + assert_noop!(ScoredPool::kick(RuntimeOrigin::signed(99), who, index), BadOrigin); }); } @@ -123,12 +123,12 @@ fn kicking_works() { // when let index = find_in_pool(who).expect("entity must be in pool") as u32; - assert_ok!(ScoredPool::kick(Origin::signed(KickOrigin::get()), who, index)); + assert_ok!(ScoredPool::kick(RuntimeOrigin::signed(KickOrigin::get()), who, index)); // then assert_eq!(find_in_pool(who), None); assert_eq!(ScoredPool::members(), vec![20, 31]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), ScoredPool::members()); + assert_eq!(MembersTestValue::get().clone(), ScoredPool::members()); assert_eq!(Balances::reserved_balance(who), 0); // deposit must have been returned }); } @@ -138,21 +138,21 @@ fn unscored_entities_must_not_be_used_for_filling_members() { new_test_ext().execute_with(|| { // given // we submit a candidacy, score will be `None` - assert_ok!(ScoredPool::submit_candidacy(Origin::signed(15))); + assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(15))); // when // we remove every scored member ScoredPool::pool().into_iter().for_each(|(who, score)| { if let Some(_) = score { let index = find_in_pool(who).expect("entity must be in pool") as u32; - assert_ok!(ScoredPool::kick(Origin::signed(KickOrigin::get()), who, index)); + assert_ok!(ScoredPool::kick(RuntimeOrigin::signed(KickOrigin::get()), who, index)); } }); // then // the `None` candidates should not have been filled in assert!(ScoredPool::members().is_empty()); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), ScoredPool::members()); + assert_eq!(MembersTestValue::get().clone(), ScoredPool::members()); }); } @@ -161,16 +161,16 @@ fn refreshing_works() { new_test_ext().execute_with(|| { // given let who = 15; - assert_ok!(ScoredPool::submit_candidacy(Origin::signed(who))); + assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who))); let index = find_in_pool(who).expect("entity must be in pool") as u32; - assert_ok!(ScoredPool::score(Origin::signed(ScoreOrigin::get()), who, index, 99)); + assert_ok!(ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), who, index, 99)); // when ScoredPool::refresh_members(ScoredPool::pool(), ChangeReceiver::MembershipChanged); // then assert_eq!(ScoredPool::members(), vec![15, 40]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), ScoredPool::members()); + assert_eq!(MembersTestValue::get().clone(), ScoredPool::members()); }); } @@ -179,9 +179,9 @@ fn refreshing_happens_every_period() { new_test_ext().execute_with(|| { // given System::set_block_number(1); - assert_ok!(ScoredPool::submit_candidacy(Origin::signed(15))); + assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(15))); let index = find_in_pool(15).expect("entity must be in pool") as u32; - assert_ok!(ScoredPool::score(Origin::signed(ScoreOrigin::get()), 15, index, 99)); + assert_ok!(ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), 15, index, 99)); assert_eq!(ScoredPool::members(), vec![20, 40]); // when @@ -190,7 +190,7 @@ fn refreshing_happens_every_period() { // then assert_eq!(ScoredPool::members(), vec![15, 40]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), ScoredPool::members()); + assert_eq!(MembersTestValue::get().clone(), ScoredPool::members()); }); } @@ -200,7 +200,7 @@ fn withdraw_candidacy_must_only_work_for_members() { let who = 77; let index = 0; assert_noop!( - ScoredPool::withdraw_candidacy(Origin::signed(who), index), + ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), index), Error::::WrongAccountIndex ); }); @@ -212,15 +212,15 @@ fn oob_index_should_abort() { let who = 40; let oob_index = ScoredPool::pool().len() as u32; assert_noop!( - ScoredPool::withdraw_candidacy(Origin::signed(who), oob_index), + ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), oob_index), Error::::InvalidIndex ); assert_noop!( - ScoredPool::score(Origin::signed(ScoreOrigin::get()), who, oob_index, 99), + ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), who, oob_index, 99), Error::::InvalidIndex ); assert_noop!( - ScoredPool::kick(Origin::signed(KickOrigin::get()), who, oob_index), + ScoredPool::kick(RuntimeOrigin::signed(KickOrigin::get()), who, oob_index), Error::::InvalidIndex ); }); @@ -232,15 +232,15 @@ fn index_mismatches_should_abort() { let who = 40; let index = 3; assert_noop!( - ScoredPool::withdraw_candidacy(Origin::signed(who), index), + ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), index), Error::::WrongAccountIndex ); assert_noop!( - ScoredPool::score(Origin::signed(ScoreOrigin::get()), who, index, 99), + ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), who, index, 99), Error::::WrongAccountIndex ); assert_noop!( - ScoredPool::kick(Origin::signed(KickOrigin::get()), who, index), + ScoredPool::kick(RuntimeOrigin::signed(KickOrigin::get()), who, index), Error::::WrongAccountIndex ); }); @@ -254,7 +254,7 @@ fn withdraw_unscored_candidacy_must_work() { // when let index = find_in_pool(who).expect("entity must be in pool") as u32; - assert_ok!(ScoredPool::withdraw_candidacy(Origin::signed(who), index)); + assert_ok!(ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), index)); // then assert_eq!(fetch_from_pool(5), None); @@ -270,7 +270,7 @@ fn withdraw_scored_candidacy_must_work() { // when let index = find_in_pool(who).expect("entity must be in pool") as u32; - assert_ok!(ScoredPool::withdraw_candidacy(Origin::signed(who), index)); + assert_ok!(ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), index)); // then assert_eq!(fetch_from_pool(who), None); @@ -286,14 +286,36 @@ fn candidacy_resubmitting_works() { let who = 15; // when - assert_ok!(ScoredPool::submit_candidacy(Origin::signed(who))); + assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who))); assert_eq!(ScoredPool::candidate_exists(who), true); let index = find_in_pool(who).expect("entity must be in pool") as u32; - assert_ok!(ScoredPool::withdraw_candidacy(Origin::signed(who), index)); + assert_ok!(ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), index)); assert_eq!(ScoredPool::candidate_exists(who), false); - assert_ok!(ScoredPool::submit_candidacy(Origin::signed(who))); + assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who))); // then assert_eq!(ScoredPool::candidate_exists(who), true); }); } + +#[test] +fn pool_candidates_exceeded() { + new_test_ext().execute_with(|| { + for i in [1, 2, 3, 4, 6] { + let who = i as u64; + assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who))); + let index = find_in_pool(who).expect("entity must be in pool") as u32; + assert_ok!(ScoredPool::score( + RuntimeOrigin::signed(ScoreOrigin::get()), + who, + index, + 99 + )); + } + + assert_noop!( + ScoredPool::submit_candidacy(RuntimeOrigin::signed(8)), + Error::::TooManyMembers + ); + }); +} diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index 8f07de2e7a6db..57b519e81e59b 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-session" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME sessions pallet" readme = "README.md" @@ -13,38 +13,37 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ - "derive", -] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +impl-trait-for-tuples = "0.2.2" +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } -sp-trie = { version = "4.0.0-dev", optional = true, default-features = false, path = "../../primitives/trie" } -log = { version = "0.4.0", default-features = false } -impl-trait-for-tuples = "0.2.1" +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-trie = { version = "7.0.0", default-features = false, optional = true, path = "../../primitives/trie" } [features] -default = ["std", "historical"] +default = ["historical", "std"] historical = ["sp-trie"] std = [ "codec/std", - "scale-info/std", - "sp-std/std", - "sp-io/std", "frame-support/std", + "frame-system/std", + "log/std", + "pallet-timestamp/std", + "scale-info/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", "sp-session/std", "sp-staking/std", - "pallet-timestamp/std", + "sp-std/std", "sp-trie/std", - "log/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/session/README.md b/frame/session/README.md index c47b5610de09c..09132470d4433 100644 --- a/frame/session/README.md +++ b/frame/session/README.md @@ -1,11 +1,11 @@ -# Session Module +# Session Pallet The Session module allows validators to manage their session keys, provides a function for changing the session length, and handles session rotation. - [`session::Trait`](https://docs.rs/pallet-session/latest/pallet_session/trait.Config.html) - [`Call`](https://docs.rs/pallet-session/latest/pallet_session/enum.Call.html) -- [`Module`](https://docs.rs/pallet-session/latest/pallet_session/struct.Module.html) +- [`Pallet`](https://docs.rs/pallet-session/latest/pallet_session/struct.Pallet.html) ## Overview @@ -72,11 +72,11 @@ The [Staking pallet](https://docs.rs/pallet-staking/latest/pallet_staking/) uses use pallet_session as session; fn validators() -> Vec<::ValidatorId> { - >::validators() + >::validators() } ``` -## Related Modules +## Related Pallets - [Staking](https://docs.rs/pallet-staking/latest/pallet_staking/) diff --git a/frame/session/benchmarking/Cargo.toml b/frame/session/benchmarking/Cargo.toml index cc242085bf5e4..90d6d95c07f4f 100644 --- a/frame/session/benchmarking/Cargo.toml +++ b/frame/session/benchmarking/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-session-benchmarking" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME sessions pallet benchmarking" readme = "README.md" @@ -13,39 +13,40 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +rand = { version = "0.7.2", default-features = false } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } -pallet-staking = { version = "4.0.0-dev", default-features = false, features = [ - "runtime-benchmarks", -], path = "../../staking" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../../session" } -rand = { version = "0.7.2", default-features = false } +pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../staking" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", features = [ - "derive", -] } -scale-info = "1.0" -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +scale-info = "2.1.1" +frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } +pallet-balances = { version = "4.0.0-dev", path = "../../balances" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } -sp-io = { version = "4.0.0-dev", path = "../../../primitives/io" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } -pallet-balances = { version = "4.0.0-dev", path = "../../balances" } -frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } [features] default = ["std"] std = [ - "sp-std/std", - "sp-session/std", - "sp-runtime/std", - "frame-system/std", "frame-benchmarking/std", "frame-support/std", - "pallet-staking/std", + "frame-system/std", "pallet-session/std", + "pallet-staking/std", + "rand/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", +] + +runtime-benchmarks = [ + "pallet-staking/runtime-benchmarks", ] diff --git a/frame/session/benchmarking/src/lib.rs b/frame/session/benchmarking/src/lib.rs index 8b84145c1acfd..9e478ada53cf2 100644 --- a/frame/session/benchmarking/src/lib.rs +++ b/frame/session/benchmarking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,28 +18,29 @@ //! Benchmarks for the Session Pallet. // This is separated into its own crate due to cyclic dependency issues. +#![cfg(feature = "runtime-benchmarks")] #![cfg_attr(not(feature = "std"), no_std)] mod mock; +use sp_runtime::traits::{One, StaticLookup, TrailingZeroInput}; use sp_std::{prelude::*, vec}; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; +use frame_benchmarking::benchmarks; use frame_support::{ codec::Decode, - traits::{KeyOwnerProofSystem, OnInitialize}, + traits::{Get, KeyOwnerProofSystem, OnInitialize}, }; use frame_system::RawOrigin; -use pallet_session::{historical::Module as Historical, Module as Session, *}; +use pallet_session::{historical::Pallet as Historical, Pallet as Session, *}; use pallet_staking::{ benchmarking::create_validator_with_nominators, testing_utils::create_validators, RewardDestination, }; -use sp_runtime::traits::{One, StaticLookup}; const MAX_VALIDATORS: u32 = 1000; -pub struct Pallet(pallet_session::Module); +pub struct Pallet(pallet_session::Pallet); pub trait Config: pallet_session::Config + pallet_session::historical::Config + pallet_staking::Config { @@ -47,21 +48,22 @@ pub trait Config: impl OnInitialize for Pallet { fn on_initialize(n: T::BlockNumber) -> frame_support::weights::Weight { - pallet_session::Module::::on_initialize(n) + pallet_session::Pallet::::on_initialize(n) } } benchmarks! { set_keys { - let n = ::MAX_NOMINATIONS; + let n = ::MaxNominations::get(); let (v_stash, _) = create_validator_with_nominators::( n, - ::MAX_NOMINATIONS, + ::MaxNominations::get(), false, RewardDestination::Staked, )?; let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; - let keys = T::Keys::default(); + + 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); @@ -69,15 +71,15 @@ benchmarks! { }: _(RawOrigin::Signed(v_controller), keys, proof) purge_keys { - let n = ::MAX_NOMINATIONS; + let n = ::MaxNominations::get(); let (v_stash, _) = create_validator_with_nominators::( n, - ::MAX_NOMINATIONS, + ::MaxNominations::get(), false, RewardDestination::Staked )?; let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; - let keys = T::Keys::default(); + 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. @@ -115,6 +117,8 @@ benchmarks! { 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 @@ -161,5 +165,3 @@ fn check_membership_proof_setup( (key, Historical::::prove(key).unwrap()) } - -impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test, extra = false); diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index c685db2bb2524..2db7eb385111c 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,8 +19,11 @@ #![cfg(test)] -use frame_election_provider_support::onchain; -use frame_support::parameter_types; +use frame_election_provider_support::{onchain, SequentialPhragmen}; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; use sp_runtime::traits::IdentityLookup; type AccountId = u64; @@ -49,16 +52,16 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = AccountIndex; type BlockNumber = BlockNumber; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = sp_runtime::testing::Header; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); type PalletInfo = PalletInfo; @@ -68,29 +71,25 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: Balance = 10; -} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = Balance; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<10>; type AccountStore = System; type WeightInfo = (); } -parameter_types! { - pub const MinimumPeriod: u64 = 5; -} impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<5>; type WeightInfo = (); } impl pallet_session::historical::Config for Test { @@ -117,7 +116,7 @@ impl pallet_session::SessionHandler for TestSessionHandler { ) { } - fn on_disabled(_: usize) {} + fn on_disabled(_: u32) {} } impl pallet_session::Config for Test { @@ -126,10 +125,9 @@ impl pallet_session::Config for Test { type ShouldEndSession = pallet_session::PeriodicSessions<(), ()>; type NextSessionRotation = pallet_session::PeriodicSessions<(), ()>; type SessionHandler = TestSessionHandler; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ValidatorId = AccountId; type ValidatorIdOf = pallet_staking::StashOf; - type DisabledValidatorsThreshold = (); type WeightInfo = (); } pallet_staking_reward_curve::build! { @@ -144,32 +142,27 @@ pallet_staking_reward_curve::build! { } parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; - pub const MaxNominatorRewardedPerValidator: u32 = 64; - pub const UnsignedPriority: u64 = 1 << 20; } -pub type Extrinsic = sp_runtime::testing::TestXt; - -impl frame_system::offchain::SendTransactionTypes for Test -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = Extrinsic; -} - -impl onchain::Config for Test { - type Accuracy = sp_runtime::Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = ConstU32<100>; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; } impl pallet_staking::Config for Test { - const MAX_NOMINATIONS: u32 = 16; + type MaxNominations = ConstU32<16>; type Currency = Balances; + type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type RewardRemainder = (); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Slash = (); type Reward = (); type SessionsPerEra = (); @@ -179,9 +172,16 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; + type OnStakerSlash = (); + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/session/src/historical/mod.rs b/frame/session/src/historical/mod.rs index c9b13e3c7f262..45b4ba3c0a799 100644 --- a/frame/session/src/historical/mod.rs +++ b/frame/session/src/historical/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,62 +26,74 @@ //! These roots and proofs of inclusion can be generated at any time during the current session. //! Afterwards, the proofs can be fed to a consensus module when reporting misbehavior. -use super::{Module as SessionModule, SessionIndex}; +pub mod offchain; +pub mod onchain; +mod shared; + use codec::{Decode, Encode}; -use frame_support::{ - decl_module, decl_storage, print, - traits::{ValidatorSet, ValidatorSetWithIdentification}, - Parameter, -}; use sp_runtime::{ traits::{Convert, OpaqueKeys}, KeyTypeId, }; use sp_session::{MembershipProof, ValidatorCount}; +use sp_staking::SessionIndex; use sp_std::prelude::*; use sp_trie::{ - trie_types::{TrieDB, TrieDBMut}, - MemoryDB, Recorder, Trie, TrieMut, EMPTY_PREFIX, + trie_types::{TrieDBBuilder, TrieDBMutBuilderV0}, + LayoutV0, MemoryDB, Recorder, Trie, TrieMut, EMPTY_PREFIX, }; -pub mod offchain; -pub mod onchain; -mod shared; +use frame_support::{ + print, + traits::{KeyOwnerProofSystem, ValidatorSet, ValidatorSetWithIdentification}, + Parameter, +}; -/// Config necessary for the historical module. -pub trait Config: super::Config { - /// Full identification of the validator. - type FullIdentification: Parameter; - - /// A conversion from validator ID to full identification. - /// - /// This should contain any references to economic actors associated with the - /// validator, since they may be outdated by the time this is queried from a - /// historical trie. - /// - /// It must return the identification for the current session index. - type FullIdentificationOf: Convert>; -} +use crate::{self as pallet_session, Pallet as Session}; + +pub use pallet::*; -decl_storage! { - trait Store for Module as Session { - /// Mapping from historical session indices to session-data root hash and validator count. - HistoricalSessions get(fn historical_root): - map hasher(twox_64_concat) SessionIndex => Option<(T::Hash, ValidatorCount)>; - /// The range of historical sessions we store. [first, last) - StoredRange: Option<(SessionIndex, SessionIndex)>; - /// Deprecated. - CachedObsolete: - map hasher(twox_64_concat) SessionIndex - => Option>; +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + /// Config necessary for the historical pallet. + #[pallet::config] + pub trait Config: pallet_session::Config + frame_system::Config { + /// Full identification of the validator. + type FullIdentification: Parameter; + + /// A conversion from validator ID to full identification. + /// + /// This should contain any references to economic actors associated with the + /// validator, since they may be outdated by the time this is queried from a + /// historical trie. + /// + /// It must return the identification for the current session index. + type FullIdentificationOf: Convert>; } -} -decl_module! { - pub struct Module for enum Call where origin: T::Origin {} + /// Mapping from historical session indices to session-data root hash and validator count. + #[pallet::storage] + #[pallet::getter(fn historical_root)] + pub type HistoricalSessions = + StorageMap<_, Twox64Concat, SessionIndex, (T::Hash, ValidatorCount), OptionQuery>; + + /// The range of historical sessions we store. [first, last) + #[pallet::storage] + pub type StoredRange = StorageValue<_, (SessionIndex, SessionIndex), OptionQuery>; } -impl Module { +impl Pallet { /// Prune historical stored session roots up to (but not including) /// `up_to`. pub fn prune_up_to(up_to: SessionIndex) { @@ -109,20 +121,20 @@ impl Module { } } -impl ValidatorSet for Module { +impl ValidatorSet for Pallet { type ValidatorId = T::ValidatorId; type ValidatorIdOf = T::ValidatorIdOf; fn session_index() -> sp_staking::SessionIndex { - super::Module::::current_index() + super::Pallet::::current_index() } fn validators() -> Vec { - super::Module::::validators() + super::Pallet::::validators() } } -impl ValidatorSetWithIdentification for Module { +impl ValidatorSetWithIdentification for Pallet { type Identification = T::FullIdentification; type IdentificationOf = T::FullIdentificationOf; } @@ -130,7 +142,7 @@ impl ValidatorSetWithIdentification for Module { /// Specialization of the crate-level `SessionManager` which returns the set of full identification /// when creating a new session. pub trait SessionManager: - crate::SessionManager + pallet_session::SessionManager { /// If there was a validator set change, its returns the set of new validators along with their /// full identifications. @@ -150,7 +162,7 @@ pub struct NoteHistoricalRoot(sp_std::marker::PhantomData<(T, I)>); impl> NoteHistoricalRoot { fn do_new_session(new_index: SessionIndex, is_genesis: bool) -> Option> { - StoredRange::mutate(|range| { + >::mutate(|range| { range.get_or_insert_with(|| (new_index, new_index)).1 = new_index + 1; }); @@ -183,7 +195,7 @@ impl> NoteHi } } -impl crate::SessionManager for NoteHistoricalRoot +impl pallet_session::SessionManager for NoteHistoricalRoot where I: SessionManager, { @@ -207,7 +219,7 @@ where /// A tuple of the validator's ID and their full identification. pub type IdentificationTuple = - (::ValidatorId, ::FullIdentification); + (::ValidatorId, ::FullIdentification); /// A trie instance for checking and generating proofs. pub struct ProvingTrie { @@ -224,10 +236,10 @@ impl ProvingTrie { let mut root = Default::default(); { - let mut trie = TrieDBMut::new(&mut db, &mut root); + let mut trie = TrieDBMutBuilderV0::new(&mut db, &mut root).build(); for (i, (validator, full_id)) in validators.into_iter().enumerate() { let i = i as u32; - let keys = match >::load_keys(&validator) { + let keys = match >::load_keys(&validator) { None => continue, Some(k) => k, }; @@ -266,19 +278,20 @@ impl ProvingTrie { /// Prove the full verification data for a given key and key ID. pub fn prove(&self, key_id: KeyTypeId, key_data: &[u8]) -> Option>> { - let trie = TrieDB::new(&self.db, &self.root).ok()?; - let mut recorder = Recorder::new(); - let val_idx = (key_id, key_data).using_encoded(|s| { - trie.get_with(s, &mut recorder) - .ok()? - .and_then(|raw| u32::decode(&mut &*raw).ok()) - })?; - - val_idx.using_encoded(|s| { - trie.get_with(s, &mut recorder) - .ok()? - .and_then(|raw| >::decode(&mut &*raw).ok()) - })?; + let mut recorder = Recorder::>::new(); + { + let trie = + TrieDBBuilder::new(&self.db, &self.root).with_recorder(&mut recorder).build(); + let val_idx = (key_id, key_data).using_encoded(|s| { + trie.get(s).ok()?.and_then(|raw| u32::decode(&mut &*raw).ok()) + })?; + + val_idx.using_encoded(|s| { + trie.get(s) + .ok()? + .and_then(|raw| >::decode(&mut &*raw).ok()) + })?; + } Some(recorder.drain().into_iter().map(|r| r.data).collect()) } @@ -291,7 +304,7 @@ impl ProvingTrie { // Check a proof contained within the current memory-db. Returns `None` if the // nodes within the current `MemoryDB` are insufficient to query the item. fn query(&self, key_id: KeyTypeId, key_data: &[u8]) -> Option> { - let trie = TrieDB::new(&self.db, &self.root).ok()?; + let trie = TrieDBBuilder::new(&self.db, &self.root).build(); let val_idx = (key_id, key_data) .using_encoded(|s| trie.get(s)) .ok()? @@ -304,15 +317,13 @@ impl ProvingTrie { } } -impl> frame_support::traits::KeyOwnerProofSystem<(KeyTypeId, D)> - for Module -{ +impl> KeyOwnerProofSystem<(KeyTypeId, D)> for Pallet { type Proof = MembershipProof; type IdentificationTuple = IdentificationTuple; fn prove(key: (KeyTypeId, D)) -> Option { - let session = >::current_index(); - let validators = >::validators() + let session = >::current_index(); + let validators = >::validators() .into_iter() .filter_map(|validator| { T::FullIdentificationOf::convert(validator.clone()) @@ -335,10 +346,10 @@ impl> frame_support::traits::KeyOwnerProofSystem<(KeyT fn check_proof(key: (KeyTypeId, D), proof: Self::Proof) -> Option> { let (id, data) = key; - if proof.session == >::current_index() { - >::key_owner(id, data.as_ref()).and_then(|owner| { + if proof.session == >::current_index() { + >::key_owner(id, data.as_ref()).and_then(|owner| { T::FullIdentificationOf::convert(owner.clone()).and_then(move |id| { - let count = >::validators().len() as ValidatorCount; + let count = >::validators().len() as ValidatorCount; if count != proof.validator_count { return None @@ -364,27 +375,33 @@ impl> frame_support::traits::KeyOwnerProofSystem<(KeyT pub(crate) mod tests { use super::*; use crate::mock::{ - force_new_session, set_next_validators, Session, System, Test, NEXT_VALIDATORS, + force_new_session, set_next_validators, NextValidators, Session, System, Test, }; + + use sp_runtime::{key_types::DUMMY, testing::UintAuthorityId}; + use frame_support::{ - traits::{KeyOwnerProofSystem, OnInitialize}, + traits::{GenesisBuild, KeyOwnerProofSystem, OnInitialize}, BasicExternalities, }; - use sp_runtime::{key_types::DUMMY, testing::UintAuthorityId}; - type Historical = Module; + type Historical = Pallet; pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let keys: Vec<_> = NEXT_VALIDATORS.with(|l| { - l.borrow().iter().cloned().map(|i| (i, i, UintAuthorityId(i).into())).collect() - }); + let keys: Vec<_> = NextValidators::get() + .iter() + .cloned() + .map(|i| (i, i, UintAuthorityId(i).into())) + .collect(); BasicExternalities::execute_with_storage(&mut t, || { for (ref k, ..) in &keys { frame_system::Pallet::::inc_providers(k); } }); - crate::GenesisConfig:: { keys }.assimilate_storage(&mut t).unwrap(); + pallet_session::GenesisConfig:: { keys } + .assimilate_storage(&mut t) + .unwrap(); sp_io::TestExternalities::new(t) } @@ -434,27 +451,27 @@ pub(crate) mod tests { Session::on_initialize(i); } - assert_eq!(StoredRange::get(), Some((0, 100))); + assert_eq!(>::get(), Some((0, 100))); for i in 0..100 { assert!(Historical::historical_root(i).is_some()) } Historical::prune_up_to(10); - assert_eq!(StoredRange::get(), Some((10, 100))); + assert_eq!(>::get(), Some((10, 100))); Historical::prune_up_to(9); - assert_eq!(StoredRange::get(), Some((10, 100))); + assert_eq!(>::get(), Some((10, 100))); for i in 10..100 { assert!(Historical::historical_root(i).is_some()) } Historical::prune_up_to(99); - assert_eq!(StoredRange::get(), Some((99, 100))); + assert_eq!(>::get(), Some((99, 100))); Historical::prune_up_to(100); - assert_eq!(StoredRange::get(), None); + assert_eq!(>::get(), None); for i in 99..199u64 { set_next_validators(vec![i]); @@ -464,14 +481,14 @@ pub(crate) mod tests { Session::on_initialize(i); } - assert_eq!(StoredRange::get(), Some((100, 200))); + assert_eq!(>::get(), Some((100, 200))); for i in 100..200 { assert!(Historical::historical_root(i).is_some()) } Historical::prune_up_to(9999); - assert_eq!(StoredRange::get(), None); + assert_eq!(>::get(), None); for i in 100..200 { assert!(Historical::historical_root(i).is_none()) diff --git a/frame/session/src/historical/offchain.rs b/frame/session/src/historical/offchain.rs index 8583c2bb439be..ececb8af5ad58 100644 --- a/frame/session/src/historical/offchain.rs +++ b/frame/session/src/historical/offchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,15 +30,11 @@ use sp_runtime::{ KeyTypeId, }; use sp_session::MembershipProof; - -use super::{ - super::{Pallet as SessionModule, SessionIndex}, - Config, IdentificationTuple, ProvingTrie, -}; - -use super::shared; use sp_std::prelude::*; +use super::{shared, Config, IdentificationTuple, ProvingTrie}; +use crate::{Pallet as SessionModule, SessionIndex}; + /// A set of validators, which was used for a fixed session index. struct ValidatorSet { validator_set: Vec>, @@ -142,33 +138,37 @@ pub fn keep_newest(n_to_keep: usize) { #[cfg(test)] mod tests { - use super::{ - super::{onchain, Module}, - *, - }; - use crate::mock::{ - force_new_session, set_next_validators, Session, System, Test, NEXT_VALIDATORS, + use super::*; + use crate::{ + historical::{onchain, Pallet}, + mock::{force_new_session, set_next_validators, NextValidators, Session, System, Test}, }; + use codec::Encode; - use frame_support::traits::{KeyOwnerProofSystem, OnInitialize}; use sp_core::{ crypto::key_types::DUMMY, offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt, StorageKind}, }; - - use frame_support::BasicExternalities; use sp_runtime::testing::UintAuthorityId; - type Historical = Module; + use frame_support::{ + traits::{GenesisBuild, KeyOwnerProofSystem, OnInitialize}, + BasicExternalities, + }; + + type Historical = Pallet; pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default() .build_storage::() .expect("Failed to create test externalities."); - let keys: Vec<_> = NEXT_VALIDATORS.with(|l| { - l.borrow().iter().cloned().map(|i| (i, i, UintAuthorityId(i).into())).collect() - }); + let keys: Vec<_> = NextValidators::get() + .iter() + .cloned() + .map(|i| (i, i, UintAuthorityId(i).into())) + .collect(); + BasicExternalities::execute_with_storage(&mut t, || { for (ref k, ..) in &keys { frame_system::Pallet::::inc_providers(k); diff --git a/frame/session/src/historical/onchain.rs b/frame/session/src/historical/onchain.rs index 514e343f4e0f6..c7160e2fcf531 100644 --- a/frame/session/src/historical/onchain.rs +++ b/frame/session/src/historical/onchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,15 +19,11 @@ use codec::Encode; use sp_runtime::traits::Convert; - -use super::{ - super::{Config as SessionConfig, Pallet as SessionModule, SessionIndex}, - Config as HistoricalConfig, -}; - -use super::shared; use sp_std::prelude::*; +use super::{shared, Config as HistoricalConfig}; +use crate::{Config as SessionConfig, Pallet as SessionModule, SessionIndex}; + /// Store the validator-set associated to the `session_index` to the off-chain database. /// /// Further processing is then done [`off-chain side`](super::offchain). diff --git a/frame/session/src/historical/shared.rs b/frame/session/src/historical/shared.rs index e801aa80eef4c..faa91f7919860 100644 --- a/frame/session/src/historical/shared.rs +++ b/frame/session/src/historical/shared.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,8 @@ //! Shared logic between on-chain and off-chain components used for slashing using an off-chain //! worker. -use super::SessionIndex; use codec::Encode; +use sp_staking::SessionIndex; use sp_std::prelude::*; pub(super) const PREFIX: &[u8] = b"session_historical"; @@ -27,13 +27,10 @@ pub(super) const LAST_PRUNE: &[u8] = b"session_historical_last_prune"; /// Derive the key used to store the list of validators pub(super) fn derive_key>(prefix: P, session_index: SessionIndex) -> Vec { - let prefix: &[u8] = prefix.as_ref(); session_index.using_encoded(|encoded_session_index| { - prefix - .into_iter() - .chain(b"/".into_iter()) - .chain(encoded_session_index.into_iter()) - .copied() - .collect::>() + let mut key = prefix.as_ref().to_owned(); + key.push(b'/'); + key.extend_from_slice(encoded_session_index); + key }) } diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index 3f5d853d4fa21..4e2caf5e0874e 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,14 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Session Module +//! # Session Pallet //! -//! The Session module allows validators to manage their session keys, provides a function for +//! The Session pallet allows validators to manage their session keys, provides a function for //! changing the session length, and handles session rotation. //! //! - [`Config`] //! - [`Call`] -//! - [`Module`] +//! - [`Pallet`] //! //! ## Overview //! @@ -95,12 +95,12 @@ //! use pallet_session as session; //! //! fn validators() -> Vec<::ValidatorId> { -//! >::validators() +//! >::validators() //! } //! # fn main(){} //! ``` //! -//! ## Related Modules +//! ## Related Pallets //! //! - [Staking](../pallet_staking/index.html) @@ -108,28 +108,27 @@ #[cfg(feature = "historical")] pub mod historical; +pub mod migrations; #[cfg(test)] mod mock; #[cfg(test)] mod tests; pub mod weights; -use codec::Decode; use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, - dispatch::{self, DispatchError, DispatchResult}, + codec::{Decode, MaxEncodedLen}, + dispatch::{DispatchError, DispatchResult}, ensure, traits::{ EstimateNextNewSession, EstimateNextSessionRotation, FindAuthor, Get, OneSessionHandler, ValidatorRegistration, ValidatorSet, }, weights::Weight, - ConsensusEngineId, Parameter, + Parameter, }; -use frame_system::ensure_signed; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Convert, Member, One, OpaqueKeys, Zero}, - KeyTypeId, Perbill, Permill, RuntimeAppPublic, + ConsensusEngineId, KeyTypeId, Permill, RuntimeAppPublic, }; use sp_staking::SessionIndex; use sp_std::{ @@ -137,6 +136,8 @@ use sp_std::{ ops::{Rem, Sub}, prelude::*, }; + +pub use pallet::*; pub use weights::WeightInfo; /// Decides whether the session should be ended. @@ -183,7 +184,7 @@ impl< // (0% is never returned). let progress = if now >= offset { let current = (now - offset) % period.clone() + One::one(); - Some(Permill::from_rational(current.clone(), period.clone())) + Some(Permill::from_rational(current, period)) } else { Some(Permill::from_rational(now + One::one(), offset)) }; @@ -228,7 +229,7 @@ pub trait SessionManager { /// /// Even if the validator-set is the same as before, if any underlying economic conditions have /// changed (i.e. stake-weights), the new validator set must be returned. This is necessary for - /// consensus engines making use of the session module to issue a validator-set change so + /// consensus engines making use of the session pallet to issue a validator-set change so /// misbehavior can be provably associated with the new economic conditions as opposed to the /// old. The returned validator set, if any, will not be applied until `new_index`. `new_index` /// is strictly greater than from previous call. @@ -280,7 +281,7 @@ pub trait SessionHandler { fn on_genesis_session(validators: &[(ValidatorId, Ks)]); /// Session set has changed; act appropriately. Note that this can be called - /// before initialization of your module. + /// before initialization of your pallet. /// /// `changed` is true whenever any of the session keys or underlying economic /// identities or weightings behind those keys has changed. @@ -297,7 +298,7 @@ pub trait SessionHandler { fn on_before_session_ending() {} /// A validator got disabled. Act accordingly until a new session begins. - fn on_disabled(validator_index: usize); + fn on_disabled(validator_index: u32); } #[impl_trait_for_tuples::impl_for_tuples(1, 30)] @@ -311,8 +312,10 @@ impl SessionHandler for Tuple { for_tuples!( #( let our_keys: Box> = Box::new(validators.iter() - .map(|k| (&k.0, k.1.get::(::ID) - .unwrap_or_default()))); + .filter_map(|k| + k.1.get::(::ID).map(|k1| (&k.0, k1)) + ) + ); Tuple::on_genesis_session(our_keys); )* @@ -327,11 +330,13 @@ impl SessionHandler for Tuple { for_tuples!( #( let our_keys: Box> = Box::new(validators.iter() - .map(|k| (&k.0, k.1.get::(::ID) - .unwrap_or_default()))); + .filter_map(|k| + k.1.get::(::ID).map(|k1| (&k.0, k1)) + )); let queued_keys: Box> = Box::new(queued_validators.iter() - .map(|k| (&k.0, k.1.get::(::ID) - .unwrap_or_default()))); + .filter_map(|k| + k.1.get::(::ID).map(|k1| (&k.0, k1)) + )); Tuple::on_new_session(changed, our_keys, queued_keys); )* ) @@ -341,7 +346,7 @@ impl SessionHandler for Tuple { for_tuples!( #( Tuple::on_before_session_ending(); )* ) } - fn on_disabled(i: usize) { + fn on_disabled(i: u32) { for_tuples!( #( Tuple::on_disabled(i); )* ) } } @@ -353,89 +358,85 @@ impl SessionHandler for TestSessionHandler { fn on_genesis_session(_: &[(AId, Ks)]) {} fn on_new_session(_: bool, _: &[(AId, Ks)], _: &[(AId, Ks)]) {} fn on_before_session_ending() {} - fn on_disabled(_: usize) {} + fn on_disabled(_: u32) {} } -impl ValidatorRegistration for Module { - fn is_registered(id: &T::ValidatorId) -> bool { - Self::load_keys(id).is_some() - } -} - -pub trait Config: frame_system::Config { - /// The overarching event type. - type Event: From + Into<::Event>; - - /// A stable ID for a validator. - type ValidatorId: Member + Parameter; - - /// A conversion from account ID to validator ID. - /// - /// Its cost must be at most one storage read. - type ValidatorIdOf: Convert>; - - /// Indicator for when to end the session. - type ShouldEndSession: ShouldEndSession; - - /// Something that can predict the next session rotation. This should typically come from the - /// same logical unit that provides [`ShouldEndSession`], yet, it gives a best effort estimate. - /// It is helpful to implement [`EstimateNextNewSession`]. - type NextSessionRotation: EstimateNextSessionRotation; - - /// Handler for managing new session. - type SessionManager: SessionManager; - - /// Handler when a session has changed. - type SessionHandler: SessionHandler; - - /// The keys. - type Keys: OpaqueKeys + Member + Parameter + Default; - - /// The fraction of validators set that is safe to be disabled. - /// - /// After the threshold is reached `disabled` method starts to return true, - /// which in combination with `pallet_staking` forces a new era. - type DisabledValidatorsThreshold: Get; +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From + IsType<::RuntimeEvent>; + + /// A stable ID for a validator. + type ValidatorId: Member + + Parameter + + MaybeSerializeDeserialize + + MaxEncodedLen + + TryFrom; + + /// A conversion from account ID to validator ID. + /// + /// Its cost must be at most one storage read. + type ValidatorIdOf: Convert>; - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; -} + /// Indicator for when to end the session. + type ShouldEndSession: ShouldEndSession; -decl_storage! { - trait Store for Module as Session { - /// The current set of validators. - Validators get(fn validators): Vec; + /// Something that can predict the next session rotation. This should typically come from + /// the same logical unit that provides [`ShouldEndSession`], yet, it gives a best effort + /// estimate. It is helpful to implement [`EstimateNextNewSession`]. + type NextSessionRotation: EstimateNextSessionRotation; - /// Current index of the session. - CurrentIndex get(fn current_index): SessionIndex; + /// Handler for managing new session. + type SessionManager: SessionManager; - /// True if the underlying economic identities or weighting behind the validators - /// has changed in the queued validator set. - QueuedChanged: bool; + /// Handler when a session has changed. + type SessionHandler: SessionHandler; - /// The queued keys for the next session. When the next session begins, these keys - /// will be used to determine the validator's session keys. - QueuedKeys get(fn queued_keys): Vec<(T::ValidatorId, T::Keys)>; + /// The keys. + type Keys: OpaqueKeys + Member + Parameter + MaybeSerializeDeserialize; - /// Indices of disabled validators. - /// - /// The set is cleared when `on_session_ending` returns a new set of identities. - DisabledValidators get(fn disabled_validators): Vec; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } - /// The next session keys for a validator. - NextKeys: map hasher(twox_64_concat) T::ValidatorId => Option; + #[pallet::genesis_config] + pub struct GenesisConfig { + pub keys: Vec<(T::AccountId, T::ValidatorId, T::Keys)>, + } - /// The owner of a key. The key is the `KeyTypeId` + the encoded key. - KeyOwner: map hasher(twox_64_concat) (KeyTypeId, Vec) => Option; + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { keys: Default::default() } + } } - add_extra_genesis { - config(keys): Vec<(T::AccountId, T::ValidatorId, T::Keys)>; - build(|config: &GenesisConfig| { + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { if T::SessionHandler::KEY_TYPE_IDS.len() != T::Keys::key_ids().len() { panic!("Number of keys in session handler and session keys does not match"); } - T::SessionHandler::KEY_TYPE_IDS.iter().zip(T::Keys::key_ids()).enumerate() + T::SessionHandler::KEY_TYPE_IDS + .iter() + .zip(T::Keys::key_ids()) + .enumerate() .for_each(|(i, (sk, kk))| { if sk != kk { panic!( @@ -445,10 +446,10 @@ decl_storage! { } }); - for (account, val, keys) in config.keys.iter().cloned() { - >::inner_set_keys(&val, keys) + for (account, val, keys) in self.keys.iter().cloned() { + >::inner_set_keys(&val, keys) .expect("genesis config must not contain duplicates; qed"); - if frame_system::Pallet::::inc_consumers(&account).is_err() { + if frame_system::Pallet::::inc_consumers_without_limit(&account).is_err() { // This will leak a provider reference, however it only happens once (at // genesis) so it's really not a big deal and we assume that the user wants to // do this since it's the only way a non-endowed account can contain a session @@ -457,49 +458,98 @@ decl_storage! { } } - let initial_validators_0 = T::SessionManager::new_session_genesis(0) - .unwrap_or_else(|| { - frame_support::print("No initial validator provided by `SessionManager`, use \ - session config keys to generate initial validator set."); - config.keys.iter().map(|x| x.1.clone()).collect() + let initial_validators_0 = + T::SessionManager::new_session_genesis(0).unwrap_or_else(|| { + frame_support::print( + "No initial validator provided by `SessionManager`, use \ + session config keys to generate initial validator set.", + ); + self.keys.iter().map(|x| x.1.clone()).collect() }); - assert!(!initial_validators_0.is_empty(), "Empty validator set for session 0 in genesis block!"); + assert!( + !initial_validators_0.is_empty(), + "Empty validator set for session 0 in genesis block!" + ); let initial_validators_1 = T::SessionManager::new_session_genesis(1) .unwrap_or_else(|| initial_validators_0.clone()); - assert!(!initial_validators_1.is_empty(), "Empty validator set for session 1 in genesis block!"); + assert!( + !initial_validators_1.is_empty(), + "Empty validator set for session 1 in genesis block!" + ); let queued_keys: Vec<_> = initial_validators_1 .iter() .cloned() - .map(|v| ( - v.clone(), - >::load_keys(&v).unwrap_or_default(), - )) + .map(|v| { + ( + v.clone(), + Pallet::::load_keys(&v).expect("Validator in session 1 missing keys!"), + ) + }) .collect(); // Tell everyone about the genesis session keys T::SessionHandler::on_genesis_session::(&queued_keys); - >::put(initial_validators_0); + Validators::::put(initial_validators_0); >::put(queued_keys); T::SessionManager::start_session(0); - }); + } } -} -decl_event!( + /// The current set of validators. + #[pallet::storage] + #[pallet::getter(fn validators)] + pub type Validators = StorageValue<_, Vec, ValueQuery>; + + /// Current index of the session. + #[pallet::storage] + #[pallet::getter(fn current_index)] + pub type CurrentIndex = StorageValue<_, SessionIndex, ValueQuery>; + + /// True if the underlying economic identities or weighting behind the validators + /// has changed in the queued validator set. + #[pallet::storage] + pub type QueuedChanged = StorageValue<_, bool, ValueQuery>; + + /// The queued keys for the next session. When the next session begins, these keys + /// will be used to determine the validator's session keys. + #[pallet::storage] + #[pallet::getter(fn queued_keys)] + pub type QueuedKeys = StorageValue<_, Vec<(T::ValidatorId, T::Keys)>, ValueQuery>; + + /// Indices of disabled validators. + /// + /// The vec is always kept sorted so that we can find whether a given validator is + /// disabled using binary search. It gets cleared when `on_session_ending` returns + /// a new set of identities. + #[pallet::storage] + #[pallet::getter(fn disabled_validators)] + pub type DisabledValidators = StorageValue<_, Vec, ValueQuery>; + + /// The next session keys for a validator. + #[pallet::storage] + pub type NextKeys = + StorageMap<_, Twox64Concat, T::ValidatorId, T::Keys, OptionQuery>; + + /// The owner of a key. The key is the `KeyTypeId` + the encoded key. + #[pallet::storage] + pub type KeyOwner = + StorageMap<_, Twox64Concat, (KeyTypeId, Vec), T::ValidatorId, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// New session has happened. Note that the argument is the \[session_index\], not the + /// New session has happened. Note that the argument is the session index, not the /// block number as the type might suggest. - NewSession(SessionIndex), + NewSession { session_index: SessionIndex }, } -); -decl_error! { - /// Error for the session module. - pub enum Error for Module { + /// Error for the session pallet. + #[pallet::error] + pub enum Error { /// Invalid ownership proof. InvalidProof, /// No associated validator ID for account. @@ -511,14 +561,26 @@ decl_error! { /// Key setting account is not live, so it's impossible to associate keys. NoAccount, } -} -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - type Error = Error; - - fn deposit_event() = default; + #[pallet::hooks] + impl Hooks> for Pallet { + /// Called when a block is initialized. Will rotate session if it is the last + /// block of the current session. + fn on_initialize(n: T::BlockNumber) -> Weight { + if T::ShouldEndSession::should_end_session(n) { + Self::rotate_session(); + T::BlockWeights::get().max_block + } else { + // NOTE: the non-database part of the weight for `should_end_session(n)` is + // included as weight for empty block, the database part is expected to be in + // cache. + Weight::zero() + } + } + } + #[pallet::call] + impl Pallet { /// Sets the session key(s) of the function caller to `keys`. /// Allows an account to set its session key prior to becoming a validator. /// This doesn't take effect until the next session. @@ -526,67 +588,58 @@ decl_module! { /// The dispatch origin of this function must be signed. /// /// # - /// - Complexity: `O(1)` - /// Actual cost depends on the number of length of `T::Keys::key_ids()` which is fixed. + /// - Complexity: `O(1)`. Actual cost depends on the number of length of + /// `T::Keys::key_ids()` which is fixed. /// - DbReads: `origin account`, `T::ValidatorIdOf`, `NextKeys` /// - DbWrites: `origin account`, `NextKeys` /// - DbReads per key id: `KeyOwner` /// - DbWrites per key id: `KeyOwner` /// # - #[weight = T::WeightInfo::set_keys()] - pub fn set_keys(origin, keys: T::Keys, proof: Vec) -> dispatch::DispatchResult { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::set_keys())] + pub fn set_keys(origin: OriginFor, keys: T::Keys, proof: Vec) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(keys.ownership_proof_is_valid(&proof), Error::::InvalidProof); Self::do_set_keys(&who, keys)?; - Ok(()) } /// Removes any session key(s) of the function caller. + /// /// This doesn't take effect until the next session. /// - /// The dispatch origin of this function must be signed. + /// The dispatch origin of this function must be Signed and the account must be either be + /// convertible to a validator ID using the chain's typical addressing system (this usually + /// means being a controller account) or directly convertible into a validator ID (which + /// usually means being a stash account). /// /// # - /// - Complexity: `O(1)` in number of key types. - /// Actual cost depends on the number of length of `T::Keys::key_ids()` which is fixed. + /// - Complexity: `O(1)` in number of key types. Actual cost depends on the number of length + /// of `T::Keys::key_ids()` which is fixed. /// - DbReads: `T::ValidatorIdOf`, `NextKeys`, `origin account` /// - DbWrites: `NextKeys`, `origin account` /// - DbWrites per key id: `KeyOwner` /// # - #[weight = T::WeightInfo::purge_keys()] - pub fn purge_keys(origin) { + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::purge_keys())] + pub fn purge_keys(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; Self::do_purge_keys(&who)?; - } - - /// Called when a block is initialized. Will rotate session if it is the last - /// block of the current session. - fn on_initialize(n: T::BlockNumber) -> Weight { - if T::ShouldEndSession::should_end_session(n) { - Self::rotate_session(); - T::BlockWeights::get().max_block - } else { - // NOTE: the non-database part of the weight for `should_end_session(n)` is - // included as weight for empty block, the database part is expected to be in - // cache. - 0 - } + Ok(()) } } } -impl Module { +impl Pallet { /// Move on to next session. Register new validator set and session keys. Changes to the /// validator set have a session of delay to take effect. This allows for equivocation /// punishment after a fork. pub fn rotate_session() { - let session_index = CurrentIndex::get(); + let session_index = >::get(); log::trace!(target: "runtime::session", "rotating session {:?}", session_index); - let changed = QueuedChanged::get(); + let changed = >::get(); // Inform the session handlers that a session is going to end. T::SessionHandler::on_before_session_ending(); @@ -596,16 +649,16 @@ impl Module { let session_keys = >::get(); let validators = session_keys.iter().map(|(validator, _)| validator.clone()).collect::>(); - >::put(&validators); + Validators::::put(&validators); if changed { // reset disabled validators - DisabledValidators::take(); + >::take(); } // Increment session index. let session_index = session_index + 1; - CurrentIndex::put(session_index); + >::put(session_index); T::SessionManager::start_session(session_index); @@ -618,7 +671,7 @@ impl Module { // same as before, as underlying economic conditions may have changed. (validators, true) } else { - (>::get(), false) + (Validators::::get(), false) }; // Queue next session keys. @@ -638,16 +691,15 @@ impl Module { if let Some(&(_, ref old_keys)) = now_session_keys.next() { if old_keys != keys { changed = true; - return } } }; let queued_amalgamated = next_validators .into_iter() - .map(|a| { - let k = Self::load_keys(&a).unwrap_or_default(); + .filter_map(|a| { + let k = Self::load_keys(&a)?; check_next_changed(&k); - (a, k) + Some((a, k)) }) .collect::>(); @@ -655,51 +707,43 @@ impl Module { }; >::put(queued_amalgamated.clone()); - QueuedChanged::put(next_changed); + >::put(next_changed); // Record that this happened. - Self::deposit_event(Event::NewSession(session_index)); + Self::deposit_event(Event::NewSession { session_index }); // Tell everyone about the new session keys. T::SessionHandler::on_new_session::(changed, &session_keys, &queued_amalgamated); } - /// Disable the validator of index `i`. - /// - /// Returns `true` if this causes a `DisabledValidatorsThreshold` of validators - /// to be already disabled. - pub fn disable_index(i: usize) -> bool { - let (fire_event, threshold_reached) = DisabledValidators::mutate(|disabled| { - let i = i as u32; + /// Disable the validator of index `i`, returns `false` if the validator was already disabled. + pub fn disable_index(i: u32) -> bool { + if i >= Validators::::decode_len().unwrap_or(0) as u32 { + return false + } + + >::mutate(|disabled| { if let Err(index) = disabled.binary_search(&i) { - let count = >::decode_len().unwrap_or(0) as u32; - let threshold = T::DisabledValidatorsThreshold::get() * count; disabled.insert(index, i); - (true, disabled.len() as u32 > threshold) - } else { - (false, false) + T::SessionHandler::on_disabled(i); + return true } - }); - - if fire_event { - T::SessionHandler::on_disabled(i); - } - threshold_reached + false + }) } - /// Disable the validator identified by `c`. (If using with the staking module, + /// Disable the validator identified by `c`. (If using with the staking pallet, /// this would be their *stash* account.) /// - /// Returns `Ok(true)` if more than `DisabledValidatorsThreshold` validators in current - /// session is already disabled. - /// If used with the staking module it allows to force a new era in such case. - pub fn disable(c: &T::ValidatorId) -> sp_std::result::Result { + /// Returns `false` either if the validator could not be found or it was already + /// disabled. + pub fn disable(c: &T::ValidatorId) -> bool { Self::validators() .iter() .position(|i| i == c) - .map(Self::disable_index) - .ok_or(()) + .map(|i| Self::disable_index(i as u32)) + .unwrap_or(false) } /// Upgrade the key type from some old type to a new type. Supports adding @@ -711,7 +755,7 @@ impl Module { /// /// Care should be taken that the raw versions of the /// added keys are unique for every `ValidatorId, KeyTypeId` combination. - /// This is an invariant that the session module typically maintains internally. + /// This is an invariant that the session pallet typically maintains internally. /// /// As the actual values of the keys are typically not known at runtime upgrade, /// it's recommended to initialize the keys to a (unique) dummy value with the expectation @@ -756,14 +800,14 @@ impl Module { /// /// This ensures that the reference counter in system is incremented appropriately and as such /// must accept an account ID, rather than a validator ID. - fn do_set_keys(account: &T::AccountId, keys: T::Keys) -> dispatch::DispatchResult { + fn do_set_keys(account: &T::AccountId, keys: T::Keys) -> DispatchResult { let who = T::ValidatorIdOf::convert(account.clone()) .ok_or(Error::::NoAssociatedValidatorId)?; - ensure!(frame_system::Pallet::::can_inc_consumer(&account), Error::::NoAccount); + ensure!(frame_system::Pallet::::can_inc_consumer(account), Error::::NoAccount); let old_keys = Self::inner_set_keys(&who, keys)?; if old_keys.is_none() { - let assertion = frame_system::Pallet::::inc_consumers(&account).is_ok(); + let assertion = frame_system::Pallet::::inc_consumers(account).is_ok(); debug_assert!(assertion, "can_inc_consumer() returned true; no change since; qed"); } @@ -812,6 +856,10 @@ impl Module { fn do_purge_keys(account: &T::AccountId) -> DispatchResult { let who = T::ValidatorIdOf::convert(account.clone()) + // `purge_keys` may not have a controller-stash pair any more. If so then we expect the + // stash account to be passed in directly and convert that to a `ValidatorId` using the + // `TryFrom` trait if supported. + .or_else(|| T::ValidatorId::try_from(account.clone()).ok()) .ok_or(Error::::NoAssociatedValidatorId)?; let old_keys = Self::take_keys(&who).ok_or(Error::::NoKeys)?; @@ -819,7 +867,7 @@ impl Module { let key_data = old_keys.get_raw(*id); Self::clear_key_owner(*id, key_data); } - frame_system::Pallet::::dec_consumers(&account); + frame_system::Pallet::::dec_consumers(account); Ok(()) } @@ -850,16 +898,40 @@ impl Module { } } -impl ValidatorSet for Module { +impl ValidatorRegistration for Pallet { + fn is_registered(id: &T::ValidatorId) -> bool { + Self::load_keys(id).is_some() + } +} + +impl ValidatorSet for Pallet { type ValidatorId = T::ValidatorId; type ValidatorIdOf = T::ValidatorIdOf; fn session_index() -> sp_staking::SessionIndex { - Module::::current_index() + Pallet::::current_index() } fn validators() -> Vec { - Module::::validators() + Pallet::::validators() + } +} + +impl EstimateNextNewSession for Pallet { + fn average_session_length() -> T::BlockNumber { + T::NextSessionRotation::average_session_length() + } + + /// This session pallet always calls new_session and next_session at the same time, hence we + /// do a simple proxy and pass the function to next rotation. + fn estimate_next_new_session(now: T::BlockNumber) -> (Option, Weight) { + T::NextSessionRotation::estimate_next_session_rotation(now) + } +} + +impl frame_support::traits::DisabledValidators for Pallet { + fn is_disabled(index: u32) -> bool { + >::disabled_validators().binary_search(&index).is_ok() } } @@ -877,25 +949,7 @@ impl> FindAuthor { let i = Inner::find_author(digests)?; - let validators = >::validators(); - validators.get(i as usize).map(|k| k.clone()) - } -} - -impl EstimateNextNewSession for Module { - fn average_session_length() -> T::BlockNumber { - T::NextSessionRotation::average_session_length() - } - - /// This session module always calls new_session and next_session at the same time, hence we - /// do a simple proxy and pass the function to next rotation. - fn estimate_next_new_session(now: T::BlockNumber) -> (Option, Weight) { - T::NextSessionRotation::estimate_next_session_rotation(now) - } -} - -impl frame_support::traits::DisabledValidators for Module { - fn is_disabled(index: u32) -> bool { - >::disabled_validators().binary_search(&index).is_ok() + let validators = >::validators(); + validators.get(i as usize).cloned() } } diff --git a/frame/session/src/migrations/mod.rs b/frame/session/src/migrations/mod.rs new file mode 100644 index 0000000000000..5981e87b677cc --- /dev/null +++ b/frame/session/src/migrations/mod.rs @@ -0,0 +1,24 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +/// Version 1. +/// +/// In version 0 session historical pallet uses `Session` for storage module prefix. +/// In version 1 it uses its name as configured in `construct_runtime`. +/// This migration moves session historical pallet storages from old prefix to new prefix. +#[cfg(feature = "historical")] +pub mod v1; diff --git a/frame/session/src/migrations/v1.rs b/frame/session/src/migrations/v1.rs new file mode 100644 index 0000000000000..c0dce422fe8b5 --- /dev/null +++ b/frame/session/src/migrations/v1.rs @@ -0,0 +1,194 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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 sp_io::hashing::twox_128; +use sp_std::str; + +use frame_support::{ + storage::{generator::StorageValue, StoragePrefixedMap}, + traits::{ + Get, GetStorageVersion, PalletInfoAccess, StorageVersion, + STORAGE_VERSION_STORAGE_KEY_POSTFIX, + }, + weights::Weight, +}; + +use crate::historical as pallet_session_historical; + +const OLD_PREFIX: &str = "Session"; + +/// Migrate the entire storage of this pallet to a new prefix. +/// +/// This new prefix must be the same as the one set in construct_runtime. +/// +/// The migration will look into the storage version in order not to trigger a migration on an up +/// to date storage. Thus the on chain storage version must be less than 1 in order to trigger the +/// migration. +pub fn migrate( +) -> Weight { + let new_pallet_name =

::name(); + + if new_pallet_name == OLD_PREFIX { + log::info!( + target: "runtime::session_historical", + "New pallet name is equal to the old prefix. No migration needs to be done.", + ); + return Weight::zero() + } + + let on_chain_storage_version =

::on_chain_storage_version(); + log::info!( + target: "runtime::session_historical", + "Running migration to v1 for session_historical with storage version {:?}", + on_chain_storage_version, + ); + + if on_chain_storage_version < 1 { + let storage_prefix = pallet_session_historical::HistoricalSessions::::storage_prefix(); + frame_support::storage::migration::move_storage_from_pallet( + storage_prefix, + OLD_PREFIX.as_bytes(), + new_pallet_name.as_bytes(), + ); + log_migration("migration", storage_prefix, OLD_PREFIX, new_pallet_name); + + let storage_prefix = pallet_session_historical::StoredRange::::storage_prefix(); + frame_support::storage::migration::move_storage_from_pallet( + storage_prefix, + OLD_PREFIX.as_bytes(), + new_pallet_name.as_bytes(), + ); + log_migration("migration", storage_prefix, OLD_PREFIX, new_pallet_name); + + StorageVersion::new(1).put::

(); + ::BlockWeights::get().max_block + } else { + log::warn!( + target: "runtime::session_historical", + "Attempted to apply migration to v1 but failed because storage version is {:?}", + on_chain_storage_version, + ); + Weight::zero() + } +} + +/// Some checks prior to migration. This can be linked to +/// `frame_support::traits::OnRuntimeUpgrade::pre_upgrade` for further testing. +/// +/// Panics if anything goes wrong. +pub fn pre_migrate< + T: pallet_session_historical::Config, + P: GetStorageVersion + PalletInfoAccess, +>() { + let new_pallet_name =

::name(); + + let storage_prefix_historical_sessions = + pallet_session_historical::HistoricalSessions::::storage_prefix(); + let storage_prefix_stored_range = pallet_session_historical::StoredRange::::storage_prefix(); + + log_migration("pre-migration", storage_prefix_historical_sessions, OLD_PREFIX, new_pallet_name); + log_migration("pre-migration", storage_prefix_stored_range, OLD_PREFIX, new_pallet_name); + + if new_pallet_name == OLD_PREFIX { + return + } + + let new_pallet_prefix = twox_128(new_pallet_name.as_bytes()); + let storage_version_key = twox_128(STORAGE_VERSION_STORAGE_KEY_POSTFIX); + + let mut new_pallet_prefix_iter = frame_support::storage::KeyPrefixIterator::new( + new_pallet_prefix.to_vec(), + new_pallet_prefix.to_vec(), + |key| Ok(key.to_vec()), + ); + + // Ensure nothing except the storage_version_key is stored in the new prefix. + assert!(new_pallet_prefix_iter.all(|key| key == storage_version_key)); + + assert!(

::on_chain_storage_version() < 1); +} + +/// Some checks for after migration. This can be linked to +/// `frame_support::traits::OnRuntimeUpgrade::post_upgrade` for further testing. +/// +/// Panics if anything goes wrong. +pub fn post_migrate< + T: pallet_session_historical::Config, + P: GetStorageVersion + PalletInfoAccess, +>() { + let new_pallet_name =

::name(); + + let storage_prefix_historical_sessions = + pallet_session_historical::HistoricalSessions::::storage_prefix(); + let storage_prefix_stored_range = pallet_session_historical::StoredRange::::storage_prefix(); + + log_migration( + "post-migration", + storage_prefix_historical_sessions, + OLD_PREFIX, + new_pallet_name, + ); + log_migration("post-migration", storage_prefix_stored_range, OLD_PREFIX, new_pallet_name); + + if new_pallet_name == OLD_PREFIX { + return + } + + // Assert that no `HistoricalSessions` and `StoredRange` storages remains at the old prefix. + let old_pallet_prefix = twox_128(OLD_PREFIX.as_bytes()); + let old_historical_sessions_key = + [&old_pallet_prefix, &twox_128(storage_prefix_historical_sessions)[..]].concat(); + let old_historical_sessions_key_iter = frame_support::storage::KeyPrefixIterator::new( + old_historical_sessions_key.to_vec(), + old_historical_sessions_key.to_vec(), + |_| Ok(()), + ); + assert_eq!(old_historical_sessions_key_iter.count(), 0); + + let old_stored_range_key = + [&old_pallet_prefix, &twox_128(storage_prefix_stored_range)[..]].concat(); + let old_stored_range_key_iter = frame_support::storage::KeyPrefixIterator::new( + old_stored_range_key.to_vec(), + old_stored_range_key.to_vec(), + |_| Ok(()), + ); + assert_eq!(old_stored_range_key_iter.count(), 0); + + // Assert that the `HistoricalSessions` and `StoredRange` storages (if they exist) have been + // moved to the new prefix. + // NOTE: storage_version_key is already in the new prefix. + let new_pallet_prefix = twox_128(new_pallet_name.as_bytes()); + let new_pallet_prefix_iter = frame_support::storage::KeyPrefixIterator::new( + new_pallet_prefix.to_vec(), + new_pallet_prefix.to_vec(), + |_| Ok(()), + ); + assert!(new_pallet_prefix_iter.count() >= 1); + + assert_eq!(

::on_chain_storage_version(), 1); +} + +fn log_migration(stage: &str, storage_prefix: &[u8], old_pallet_name: &str, new_pallet_name: &str) { + log::info!( + target: "runtime::session_historical", + "{} prefix of storage '{}': '{}' ==> '{}'", + stage, + str::from_utf8(storage_prefix).unwrap_or(""), + old_pallet_name, + new_pallet_name, + ); +} diff --git a/frame/session/src/mock.rs b/frame/session/src/mock.rs index 449acaff5305d..aa13eacba9564 100644 --- a/frame/session/src/mock.rs +++ b/frame/session/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,16 +21,22 @@ use super::*; use crate as pallet_session; #[cfg(feature = "historical")] use crate::historical as pallet_session_historical; -use frame_support::{parameter_types, BasicExternalities}; + +use std::collections::BTreeMap; + use sp_core::{crypto::key_types::DUMMY, H256}; use sp_runtime::{ impl_opaque_keys, testing::{Header, UintAuthorityId}, - traits::{BlakeTwo256, ConvertInto, IdentityLookup}, - Perbill, + traits::{BlakeTwo256, IdentityLookup}, }; use sp_staking::SessionIndex; -use std::cell::RefCell; + +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64, GenesisBuild}, + BasicExternalities, +}; impl_opaque_keys! { pub struct MockSessionKeys { @@ -97,28 +103,29 @@ frame_support::construct_runtime!( } ); -thread_local! { - pub static VALIDATORS: RefCell> = RefCell::new(vec![1, 2, 3]); - pub static NEXT_VALIDATORS: RefCell> = RefCell::new(vec![1, 2, 3]); - pub static AUTHORITIES: RefCell> = - RefCell::new(vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - pub static FORCE_SESSION_END: RefCell = RefCell::new(false); - pub static SESSION_LENGTH: RefCell = RefCell::new(2); - pub static SESSION_CHANGED: RefCell = RefCell::new(false); - pub static TEST_SESSION_CHANGED: RefCell = RefCell::new(false); - pub static DISABLED: RefCell = RefCell::new(false); +parameter_types! { + pub static Validators: Vec = vec![1, 2, 3]; + pub static NextValidators: Vec = vec![1, 2, 3]; + pub static Authorities: Vec = + vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]; + pub static ForceSessionEnd: bool = false; + pub static SessionLength: u64 = 2; + pub static SessionChanged: bool = false; + pub static TestSessionChanged: bool = false; + pub static Disabled: bool = false; // Stores if `on_before_session_end` was called - pub static BEFORE_SESSION_END_CALLED: RefCell = RefCell::new(false); + pub static BeforeSessionEndCalled: bool = false; + pub static ValidatorAccounts: BTreeMap = BTreeMap::new(); } pub struct TestShouldEndSession; impl ShouldEndSession for TestShouldEndSession { fn should_end_session(now: u64) -> bool { - let l = SESSION_LENGTH.with(|l| *l.borrow()); + let l = SessionLength::get(); now % l == 0 || - FORCE_SESSION_END.with(|l| { - let r = *l.borrow(); - *l.borrow_mut() = false; + ForceSessionEnd::mutate(|l| { + let r = *l; + *l = false; r }) } @@ -133,19 +140,19 @@ impl SessionHandler for TestSessionHandler { validators: &[(u64, T)], _queued_validators: &[(u64, T)], ) { - SESSION_CHANGED.with(|l| *l.borrow_mut() = changed); - AUTHORITIES.with(|l| { - *l.borrow_mut() = validators + SessionChanged::mutate(|l| *l = changed); + Authorities::mutate(|l| { + *l = validators .iter() .map(|(_, id)| id.get::(DUMMY).unwrap_or_default()) .collect() }); } - fn on_disabled(_validator_index: usize) { - DISABLED.with(|l| *l.borrow_mut() = true) + fn on_disabled(_validator_index: u32) { + Disabled::mutate(|l| *l = true) } fn on_before_session_ending() { - BEFORE_SESSION_END_CALLED.with(|b| *b.borrow_mut() = true); + BeforeSessionEndCalled::mutate(|b| *b = true); } } @@ -154,16 +161,15 @@ impl SessionManager for TestSessionManager { fn end_session(_: SessionIndex) {} fn start_session(_: SessionIndex) {} fn new_session(_: SessionIndex) -> Option> { - if !TEST_SESSION_CHANGED.with(|l| *l.borrow()) { - VALIDATORS.with(|v| { - let mut v = v.borrow_mut(); - *v = NEXT_VALIDATORS.with(|l| l.borrow().clone()); + if !TestSessionChanged::get() { + Validators::mutate(|v| { + *v = NextValidators::get().clone(); Some(v.clone()) }) - } else if DISABLED.with(|l| std::mem::replace(&mut *l.borrow_mut(), false)) { + } else if Disabled::mutate(|l| std::mem::replace(&mut *l, false)) { // If there was a disabled validator, underlying conditions have changed // so we return `Some`. - Some(VALIDATORS.with(|v| v.borrow().clone())) + Some(Validators::get().clone()) } else { None } @@ -181,37 +187,40 @@ impl crate::historical::SessionManager for TestSessionManager { } pub fn authorities() -> Vec { - AUTHORITIES.with(|l| l.borrow().to_vec()) + Authorities::get().to_vec() } pub fn force_new_session() { - FORCE_SESSION_END.with(|l| *l.borrow_mut() = true) + ForceSessionEnd::mutate(|l| *l = true) } pub fn set_session_length(x: u64) { - SESSION_LENGTH.with(|l| *l.borrow_mut() = x) + SessionLength::mutate(|l| *l = x) } pub fn session_changed() -> bool { - SESSION_CHANGED.with(|l| *l.borrow()) + SessionChanged::get() } pub fn set_next_validators(next: Vec) { - NEXT_VALIDATORS.with(|v| *v.borrow_mut() = next); + NextValidators::mutate(|v| *v = next); } pub fn before_session_end_called() -> bool { - BEFORE_SESSION_END_CALLED.with(|b| *b.borrow()) + BeforeSessionEndCalled::get() } pub fn reset_before_session_end_called() { - BEFORE_SESSION_END_CALLED.with(|b| *b.borrow_mut() = false); + BeforeSessionEndCalled::mutate(|b| *b = false); } pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let keys: Vec<_> = NEXT_VALIDATORS - .with(|l| l.borrow().iter().cloned().map(|i| (i, i, UintAuthorityId(i).into())).collect()); + let keys: Vec<_> = NextValidators::get() + .iter() + .cloned() + .map(|i| (i, i, UintAuthorityId(i).into())) + .collect(); BasicExternalities::execute_with_storage(&mut t, || { for (ref k, ..) in &keys { frame_system::Pallet::::inc_providers(k); @@ -223,14 +232,15 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_session::GenesisConfig:: { keys } .assimilate_storage(&mut t) .unwrap(); + + let v = NextValidators::get().iter().map(|&i| (i, i)).collect(); + ValidatorAccounts::mutate(|m| *m = v); sp_io::TestExternalities::new(t) } parameter_types! { - pub const MinimumPeriod: u64 = 5; - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { @@ -238,17 +248,17 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -257,17 +267,26 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<5>; type WeightInfo = (); } -parameter_types! { - pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33); +pub struct TestValidatorIdOf; +impl TestValidatorIdOf { + pub fn set(v: BTreeMap) { + ValidatorAccounts::mutate(|m| *m = v); + } +} +impl Convert> for TestValidatorIdOf { + fn convert(x: u64) -> Option { + ValidatorAccounts::get().get(&x).cloned() + } } impl Config for Test { @@ -278,10 +297,9 @@ impl Config for Test { type SessionManager = TestSessionManager; type SessionHandler = TestSessionHandler; type ValidatorId = u64; - type ValidatorIdOf = ConvertInto; + type ValidatorIdOf = TestValidatorIdOf; type Keys = MockSessionKeys; - type Event = Event; - type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type RuntimeEvent = RuntimeEvent; type NextSessionRotation = (); type WeightInfo = (); } diff --git a/frame/session/src/tests.rs b/frame/session/src/tests.rs index 23e1c6a993427..43809cc3a9de0 100644 --- a/frame/session/src/tests.rs +++ b/frame/session/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,19 +18,24 @@ // Tests for the Session Pallet use super::*; -use codec::Decode; -use frame_support::{assert_noop, assert_ok, traits::OnInitialize}; -use mock::{ +use crate::mock::{ authorities, before_session_end_called, force_new_session, new_test_ext, reset_before_session_end_called, session_changed, set_next_validators, set_session_length, - Origin, PreUpgradeMockSessionKeys, Session, System, Test, SESSION_CHANGED, - TEST_SESSION_CHANGED, + PreUpgradeMockSessionKeys, RuntimeOrigin, Session, SessionChanged, System, Test, + TestSessionChanged, TestValidatorIdOf, }; + +use codec::Decode; use sp_core::crypto::key_types::DUMMY; use sp_runtime::testing::UintAuthorityId; +use frame_support::{ + assert_noop, assert_ok, + traits::{ConstU64, OnInitialize}, +}; + fn initialize_block(block: u64) { - SESSION_CHANGED.with(|l| *l.borrow_mut() = false); + SessionChanged::mutate(|l| *l = false); System::set_block_number(block); Session::on_initialize(block); } @@ -62,7 +67,7 @@ fn keys_cleared_on_kill() { assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), Some(1)); assert!(System::is_provider_required(&1)); - assert_ok!(Session::purge_keys(Origin::signed(1))); + assert_ok!(Session::purge_keys(RuntimeOrigin::signed(1))); assert!(!System::is_provider_required(&1)); assert_eq!(Session::load_keys(&1), None); @@ -70,11 +75,35 @@ fn keys_cleared_on_kill() { }) } +#[test] +fn purge_keys_works_for_stash_id() { + let mut ext = new_test_ext(); + ext.execute_with(|| { + assert_eq!(Session::validators(), vec![1, 2, 3]); + TestValidatorIdOf::set(vec![(10, 1), (20, 2), (3, 3)].into_iter().collect()); + assert_eq!(Session::load_keys(&1), Some(UintAuthorityId(1).into())); + assert_eq!(Session::load_keys(&2), Some(UintAuthorityId(2).into())); + + let id = DUMMY; + assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), Some(1)); + + assert_ok!(Session::purge_keys(RuntimeOrigin::signed(10))); + assert_ok!(Session::purge_keys(RuntimeOrigin::signed(2))); + + assert_eq!(Session::load_keys(&10), None); + assert_eq!(Session::load_keys(&20), None); + assert_eq!(Session::key_owner(id, UintAuthorityId(10).get_raw(id)), None); + assert_eq!(Session::key_owner(id, UintAuthorityId(20).get_raw(id)), None); + }) +} + #[test] fn authorities_should_track_validators() { reset_before_session_end_called(); new_test_ext().execute_with(|| { + TestValidatorIdOf::set(vec![(1, 1), (2, 2), (3, 3), (4, 4)].into_iter().collect()); + set_next_validators(vec![1, 2]); force_new_session(); initialize_block(1); @@ -99,7 +128,7 @@ fn authorities_should_track_validators() { reset_before_session_end_called(); set_next_validators(vec![1, 2, 4]); - assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(4).into(), vec![])); + assert_ok!(Session::set_keys(RuntimeOrigin::signed(4), UintAuthorityId(4).into(), vec![])); force_new_session(); initialize_block(3); assert_eq!( @@ -165,7 +194,7 @@ fn session_change_should_work() { // Block 3: Set new key for validator 2; no visible change. initialize_block(3); - assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5).into(), vec![])); + assert_ok!(Session::set_keys(RuntimeOrigin::signed(2), UintAuthorityId(5).into(), vec![])); assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); // Block 4: Session rollover; no visible change. @@ -185,16 +214,18 @@ fn session_change_should_work() { #[test] fn duplicates_are_not_allowed() { new_test_ext().execute_with(|| { + TestValidatorIdOf::set(vec![(1, 1), (2, 2), (3, 3), (4, 4)].into_iter().collect()); + System::set_block_number(1); Session::on_initialize(1); assert_noop!( - Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]), + Session::set_keys(RuntimeOrigin::signed(4), UintAuthorityId(1).into(), vec![]), Error::::DuplicatedKey, ); - assert_ok!(Session::set_keys(Origin::signed(1), UintAuthorityId(10).into(), vec![])); + assert_ok!(Session::set_keys(RuntimeOrigin::signed(1), UintAuthorityId(10).into(), vec![])); // is fine now that 1 has migrated off. - assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![])); + assert_ok!(Session::set_keys(RuntimeOrigin::signed(4), UintAuthorityId(1).into(), vec![])); }); } @@ -203,7 +234,8 @@ fn session_changed_flag_works() { reset_before_session_end_called(); new_test_ext().execute_with(|| { - TEST_SESSION_CHANGED.with(|l| *l.borrow_mut() = true); + TestValidatorIdOf::set(vec![(1, 1), (2, 2), (3, 3), (69, 69)].into_iter().collect()); + TestSessionChanged::mutate(|l| *l = true); force_new_session(); initialize_block(1); @@ -236,7 +268,7 @@ fn session_changed_flag_works() { assert!(before_session_end_called()); reset_before_session_end_called(); - assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5).into(), vec![])); + assert_ok!(Session::set_keys(RuntimeOrigin::signed(2), UintAuthorityId(5).into(), vec![])); force_new_session(); initialize_block(6); assert!(!session_changed()); @@ -244,7 +276,11 @@ fn session_changed_flag_works() { reset_before_session_end_called(); // changing the keys of a validator leads to change. - assert_ok!(Session::set_keys(Origin::signed(69), UintAuthorityId(69).into(), vec![])); + assert_ok!(Session::set_keys( + RuntimeOrigin::signed(69), + UintAuthorityId(69).into(), + vec![] + )); force_new_session(); initialize_block(7); assert!(session_changed()); @@ -262,12 +298,7 @@ fn session_changed_flag_works() { #[test] fn periodic_session_works() { - frame_support::parameter_types! { - const Period: u64 = 10; - const Offset: u64 = 3; - } - - type P = PeriodicSessions; + type P = PeriodicSessions, ConstU64<3>>; // make sure that offset phase behaves correctly for i in 0u64..3 { @@ -328,7 +359,7 @@ fn session_keys_generate_output_works_as_set_keys_input() { new_test_ext().execute_with(|| { let new_keys = mock::MockSessionKeys::generate(None); assert_ok!(Session::set_keys( - Origin::signed(2), + RuntimeOrigin::signed(2), ::Keys::decode(&mut &new_keys[..]).expect("Decode keys"), vec![], )); @@ -336,7 +367,7 @@ fn session_keys_generate_output_works_as_set_keys_input() { } #[test] -fn return_true_if_more_than_third_is_disabled() { +fn disable_index_returns_false_if_already_disabled() { new_test_ext().execute_with(|| { set_next_validators(vec![1, 2, 3, 4, 5, 6, 7]); force_new_session(); @@ -345,10 +376,9 @@ fn return_true_if_more_than_third_is_disabled() { force_new_session(); initialize_block(2); + assert_eq!(Session::disable_index(0), true); assert_eq!(Session::disable_index(0), false); - assert_eq!(Session::disable_index(1), false); - assert_eq!(Session::disable_index(2), true); - assert_eq!(Session::disable_index(3), true); + assert_eq!(Session::disable_index(1), true); }); } @@ -358,8 +388,8 @@ fn upgrade_keys() { use sp_core::crypto::key_types::DUMMY; // This test assumes certain mocks. - assert_eq!(mock::NEXT_VALIDATORS.with(|l| l.borrow().clone()), vec![1, 2, 3]); - assert_eq!(mock::VALIDATORS.with(|l| l.borrow().clone()), vec![1, 2, 3]); + assert_eq!(mock::NextValidators::get().clone(), vec![1, 2, 3]); + assert_eq!(mock::Validators::get().clone(), vec![1, 2, 3]); new_test_ext().execute_with(|| { let pre_one = PreUpgradeMockSessionKeys { a: [1u8; 32], b: [1u8; 64] }; @@ -425,3 +455,30 @@ fn upgrade_keys() { } }) } + +#[cfg(feature = "historical")] +#[test] +fn test_migration_v1() { + use crate::{ + historical::{HistoricalSessions, StoredRange}, + mock::Historical, + }; + use frame_support::traits::{PalletInfoAccess, StorageVersion}; + + new_test_ext().execute_with(|| { + assert!(>::iter_values().count() > 0); + assert!(>::exists()); + + let old_pallet = "Session"; + let new_pallet = ::name(); + frame_support::storage::migration::move_pallet( + new_pallet.as_bytes(), + old_pallet.as_bytes(), + ); + StorageVersion::new(0).put::(); + + crate::migrations::v1::pre_migrate::(); + crate::migrations::v1::migrate::(); + crate::migrations::v1::post_migrate::(); + }); +} diff --git a/frame/session/src/weights.rs b/frame/session/src/weights.rs index 64e7ac19ea7a0..d29413a33dd17 100644 --- a/frame/session/src/weights.rs +++ b/frame/session/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_session //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/session/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -56,17 +58,19 @@ impl WeightInfo for SubstrateWeight { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:4 w:4) fn set_keys() -> Weight { - (64_427_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + // Minimum execution time: 59_046 nanoseconds. + Weight::from_ref_time(59_934_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:0 w:4) fn purge_keys() -> Weight { - (42_497_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + // Minimum execution time: 48_872 nanoseconds. + Weight::from_ref_time(49_666_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) } } @@ -76,16 +80,18 @@ impl WeightInfo for () { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:4 w:4) fn set_keys() -> Weight { - (64_427_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + // Minimum execution time: 59_046 nanoseconds. + Weight::from_ref_time(59_934_000 as u64) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:0 w:4) fn purge_keys() -> Weight { - (42_497_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + // Minimum execution time: 48_872 nanoseconds. + Weight::from_ref_time(49_666_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) } } diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index 942b2844195f2..40b78c8922299 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-society" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME society pallet" readme = "README.md" @@ -13,33 +13,33 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +rand_chacha = { version = "0.2", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -rand_chacha = { version = "0.2", default-features = false } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-io ={ version = "4.0.0-dev", path = "../../primitives/io" } frame-support-test = { version = "3.0.0", path = "../support/test" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", + "rand_chacha/std", "scale-info/std", "sp-runtime/std", - "rand_chacha/std", "sp-std/std", - "frame-support/std", - "frame-system/std", ] runtime-benchmarks = [ - "sp-runtime/runtime-benchmarks", "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index c6d63eed20ac0..0edf00ff80f6e 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,14 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Society Module +//! # Society Pallet //! //! - [`Config`] //! - [`Call`] //! //! ## Overview //! -//! The Society module is an economic game which incentivizes users to participate +//! The Society pallet is an economic game which incentivizes users to participate //! and maintain a membership society. //! //! ### User Types @@ -77,7 +77,7 @@ //! #### Society Treasury //! //! The membership society is independently funded by a treasury managed by this -//! module. Some subset of this treasury is placed in a Society Pot, which is used +//! pallet. Some subset of this treasury is placed in a Society Pot, which is used //! to determine the number of accepted bids. //! //! #### Rate of Growth @@ -132,7 +132,7 @@ //! the society. A vouching bid can additionally request some portion of that reward as a tip //! to the voucher for vouching for the prospective candidate. //! -//! Every rotation period, Bids are ordered by reward amount, and the module +//! Every rotation period, Bids are ordered by reward amount, and the pallet //! selects as many bids the Society Pot can support for that period. //! //! These selected bids become candidates and move on to the Candidate phase. @@ -251,19 +251,15 @@ mod mock; #[cfg(test)] mod tests; -use codec::{Decode, Encode}; use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, - dispatch::DispatchResult, - ensure, + pallet_prelude::*, traits::{ BalanceStatus, ChangeMembers, Currency, EnsureOrigin, ExistenceRequirement::AllowDeath, - Get, Imbalance, OnUnbalanced, Randomness, ReservableCurrency, + Imbalance, OnUnbalanced, Randomness, ReservableCurrency, }, - weights::Weight, PalletId, }; -use frame_system::{self as system, ensure_root, ensure_signed}; +use frame_system::pallet_prelude::*; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaChaRng, @@ -278,61 +274,14 @@ use sp_runtime::{ }; use sp_std::prelude::*; +pub use pallet::*; + type BalanceOf = - <>::Currency as Currency<::AccountId>>::Balance; -type NegativeImbalanceOf = <::Currency as Currency< + <>::Currency as Currency<::AccountId>>::Balance; +type NegativeImbalanceOf = <>::Currency as Currency< ::AccountId, >>::NegativeImbalance; - -/// The module's configuration trait. -pub trait Config: system::Config { - /// The overarching event type. - type Event: From> + Into<::Event>; - - /// The societies's module id - type PalletId: Get; - - /// The currency type used for bidding. - type Currency: ReservableCurrency; - - /// Something that provides randomness in the runtime. - type Randomness: Randomness; - - /// The minimum amount of a deposit required for a bid to be made. - type CandidateDeposit: Get>; - - /// The amount of the unpaid reward that gets deducted in the case that either a skeptic - /// doesn't vote or someone votes in the wrong way. - type WrongSideDeduction: Get>; - - /// The number of times a member may vote the wrong way (or not at all, when they are a skeptic) - /// before they become suspended. - type MaxStrikes: Get; - - /// The amount of incentive paid within each period. Doesn't include VoterTip. - type PeriodSpend: Get>; - - /// The receiver of the signal for when the members have changed. - type MembershipChanged: ChangeMembers; - - /// The number of blocks between candidate/membership rotation periods. - type RotationPeriod: Get; - - /// The maximum duration of the payout lock. - type MaxLockDuration: Get; - - /// The origin that is allowed to call `found`. - type FounderSetOrigin: EnsureOrigin; - - /// The origin that is allowed to make suspension judgements. - type SuspensionJudgementOrigin: EnsureOrigin; - - /// The number of blocks between membership challenges. - type ChallengePeriod: Get; - - /// The maximum number of candidates that we accept per round. - type MaxCandidateIntake: Get; -} +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; /// A vote by a member on a candidate application. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] @@ -409,116 +358,325 @@ impl BidKind { if a == v { Ok(()) } else { - Err("incorrect identity")? + Err("incorrect identity".into()) } } else { - Err("not vouched")? + Err("not vouched".into()) } } } -// This module's storage items. -decl_storage! { - trait Store for Module, I: Instance=DefaultInstance> as Society { - /// The first member. - pub Founder get(fn founder) build(|config: &GenesisConfig| config.members.first().cloned()): - Option; +#[frame_support::pallet] +pub mod pallet { + use super::*; - /// A hash of the rules of this society concerning membership. Can only be set once and - /// only by the founder. - pub Rules get(fn rules): Option; + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); - /// The current set of candidates; bidders that are attempting to become members. - pub Candidates get(fn candidates): Vec>>; + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; - /// The set of suspended candidates. - pub SuspendedCandidates get(fn suspended_candidate): - map hasher(twox_64_concat) T::AccountId - => Option<(BalanceOf, BidKind>)>; + /// The societies's pallet id + #[pallet::constant] + type PalletId: Get; - /// Amount of our account balance that is specifically for the next round's bid(s). - pub Pot get(fn pot) config(): BalanceOf; + /// The currency type used for bidding. + type Currency: ReservableCurrency; - /// The most primary from the most recently approved members. - pub Head get(fn head) build(|config: &GenesisConfig| config.members.first().cloned()): - Option; + /// Something that provides randomness in the runtime. + type Randomness: Randomness; - /// The current set of members, ordered. - pub Members get(fn members) build(|config: &GenesisConfig| { - let mut m = config.members.clone(); - m.sort(); - m - }): Vec; + /// The minimum amount of a deposit required for a bid to be made. + #[pallet::constant] + type CandidateDeposit: Get>; - /// The set of suspended members. - pub SuspendedMembers get(fn suspended_member): map hasher(twox_64_concat) T::AccountId => bool; + /// The amount of the unpaid reward that gets deducted in the case that either a skeptic + /// doesn't vote or someone votes in the wrong way. + #[pallet::constant] + type WrongSideDeduction: Get>; + + /// The number of times a member may vote the wrong way (or not at all, when they are a + /// skeptic) before they become suspended. + #[pallet::constant] + type MaxStrikes: Get; - /// The current bids, stored ordered by the value of the bid. - Bids: Vec>>; + /// The amount of incentive paid within each period. Doesn't include VoterTip. + #[pallet::constant] + type PeriodSpend: Get>; - /// Members currently vouching or banned from vouching again - Vouching get(fn vouching): map hasher(twox_64_concat) T::AccountId => Option; + /// The receiver of the signal for when the members have changed. + type MembershipChanged: ChangeMembers; - /// Pending payouts; ordered by block number, with the amount that should be paid out. - Payouts: map hasher(twox_64_concat) T::AccountId => Vec<(T::BlockNumber, BalanceOf)>; + /// The number of blocks between candidate/membership rotation periods. + #[pallet::constant] + type RotationPeriod: Get; - /// The ongoing number of losing votes cast by the member. - Strikes: map hasher(twox_64_concat) T::AccountId => StrikeCount; + /// The maximum duration of the payout lock. + #[pallet::constant] + type MaxLockDuration: Get; - /// Double map from Candidate -> Voter -> (Maybe) Vote. - Votes: double_map - hasher(twox_64_concat) T::AccountId, - hasher(twox_64_concat) T::AccountId - => Option; + /// The origin that is allowed to call `found`. + type FounderSetOrigin: EnsureOrigin; - /// The defending member currently being challenged. - Defender get(fn defender): Option; + /// The origin that is allowed to make suspension judgements. + type SuspensionJudgementOrigin: EnsureOrigin; - /// Votes for the defender. - DefenderVotes: map hasher(twox_64_concat) T::AccountId => Option; + /// The number of blocks between membership challenges. + #[pallet::constant] + type ChallengePeriod: Get; - /// The max number of members for the society at one time. - MaxMembers get(fn max_members) config(): u32; + /// The maximum number of candidates that we accept per round. + #[pallet::constant] + type MaxCandidateIntake: Get; } - add_extra_genesis { - config(members): Vec; + + #[pallet::error] + pub enum Error { + /// An incorrect position was provided. + BadPosition, + /// User is not a member. + NotMember, + /// User is already a member. + AlreadyMember, + /// User is suspended. + Suspended, + /// User is not suspended. + NotSuspended, + /// Nothing to payout. + NoPayout, + /// Society already founded. + AlreadyFounded, + /// Not enough in pot to accept candidate. + InsufficientPot, + /// Member is already vouching or banned from vouching again. + AlreadyVouching, + /// Member is not vouching. + NotVouching, + /// Cannot remove the head of the chain. + Head, + /// Cannot remove the founder. + Founder, + /// User has already made a bid. + AlreadyBid, + /// User is already a candidate. + AlreadyCandidate, + /// User is not a candidate. + NotCandidate, + /// Too many members in the society. + MaxMembers, + /// The caller is not the founder. + NotFounder, + /// The caller is not the head. + NotHead, } -} -// The module's dispatchable functions. -decl_module! { - /// The module declaration. - pub struct Module, I: Instance=DefaultInstance> for enum Call where origin: T::Origin { - type Error = Error; - /// The minimum amount of a deposit required for a bid to be made. - const CandidateDeposit: BalanceOf = T::CandidateDeposit::get(); + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// The society is founded by the given identity. + Founded { founder: T::AccountId }, + /// A membership bid just happened. The given account is the candidate's ID and their offer + /// is the second. + Bid { candidate_id: T::AccountId, offer: BalanceOf }, + /// A membership bid just happened by vouching. The given account is the candidate's ID and + /// their offer is the second. The vouching party is the third. + Vouch { candidate_id: T::AccountId, offer: BalanceOf, vouching: T::AccountId }, + /// A candidate was dropped (due to an excess of bids in the system). + AutoUnbid { candidate: T::AccountId }, + /// A candidate was dropped (by their request). + Unbid { candidate: T::AccountId }, + /// A candidate was dropped (by request of who vouched for them). + Unvouch { candidate: T::AccountId }, + /// A group of candidates have been inducted. The batch's primary is the first value, the + /// batch in full is the second. + Inducted { primary: T::AccountId, candidates: Vec }, + /// A suspended member has been judged. + SuspendedMemberJudgement { who: T::AccountId, judged: bool }, + /// A candidate has been suspended + CandidateSuspended { candidate: T::AccountId }, + /// A member has been suspended + MemberSuspended { member: T::AccountId }, + /// A member has been challenged + Challenged { member: T::AccountId }, + /// A vote has been placed + Vote { candidate: T::AccountId, voter: T::AccountId, vote: bool }, + /// A vote has been placed for a defending member + DefenderVote { voter: T::AccountId, vote: bool }, + /// A new \[max\] member count has been set + NewMaxMembers { max: u32 }, + /// Society is unfounded. + Unfounded { founder: T::AccountId }, + /// Some funds were deposited into the society account. + Deposit { value: BalanceOf }, + } - /// The amount of the unpaid reward that gets deducted in the case that either a skeptic - /// doesn't vote or someone votes in the wrong way. - const WrongSideDeduction: BalanceOf = T::WrongSideDeduction::get(); + /// The first member. + #[pallet::storage] + #[pallet::getter(fn founder)] + pub type Founder, I: 'static = ()> = StorageValue<_, T::AccountId>; + + /// A hash of the rules of this society concerning membership. Can only be set once and + /// only by the founder. + #[pallet::storage] + #[pallet::getter(fn rules)] + pub type Rules, I: 'static = ()> = StorageValue<_, T::Hash>; + + /// The current set of candidates; bidders that are attempting to become members. + #[pallet::storage] + #[pallet::getter(fn candidates)] + pub type Candidates, I: 'static = ()> = + StorageValue<_, Vec>>, ValueQuery>; + + /// The set of suspended candidates. + #[pallet::storage] + #[pallet::getter(fn suspended_candidate)] + pub type SuspendedCandidates, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + T::AccountId, + (BalanceOf, BidKind>), + >; + + /// Amount of our account balance that is specifically for the next round's bid(s). + #[pallet::storage] + #[pallet::getter(fn pot)] + pub type Pot, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; + + /// The most primary from the most recently approved members. + #[pallet::storage] + #[pallet::getter(fn head)] + pub type Head, I: 'static = ()> = StorageValue<_, T::AccountId>; + + /// The current set of members, ordered. + #[pallet::storage] + #[pallet::getter(fn members)] + pub type Members, I: 'static = ()> = + StorageValue<_, Vec, ValueQuery>; + + /// The set of suspended members. + #[pallet::storage] + #[pallet::getter(fn suspended_member)] + pub type SuspendedMembers, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, bool, ValueQuery>; + + /// The current bids, stored ordered by the value of the bid. + #[pallet::storage] + pub(super) type Bids, I: 'static = ()> = + StorageValue<_, Vec>>, ValueQuery>; + + /// Members currently vouching or banned from vouching again + #[pallet::storage] + #[pallet::getter(fn vouching)] + pub(super) type Vouching, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, VouchingStatus>; + + /// Pending payouts; ordered by block number, with the amount that should be paid out. + #[pallet::storage] + pub(super) type Payouts, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + T::AccountId, + Vec<(T::BlockNumber, BalanceOf)>, + ValueQuery, + >; + + /// The ongoing number of losing votes cast by the member. + #[pallet::storage] + pub(super) type Strikes, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, StrikeCount, ValueQuery>; + + /// Double map from Candidate -> Voter -> (Maybe) Vote. + #[pallet::storage] + pub(super) type Votes, I: 'static = ()> = + StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, T::AccountId, Vote>; + + /// The defending member currently being challenged. + #[pallet::storage] + #[pallet::getter(fn defender)] + pub(super) type Defender, I: 'static = ()> = StorageValue<_, T::AccountId>; + + /// Votes for the defender. + #[pallet::storage] + pub(super) type DefenderVotes, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, Vote>; + + /// The max number of members for the society at one time. + #[pallet::storage] + #[pallet::getter(fn max_members)] + pub(super) type MaxMembers, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + fn on_initialize(n: T::BlockNumber) -> Weight { + let mut members = vec![]; - /// The number of times a member may vote the wrong way (or not at all, when they are a skeptic) - /// before they become suspended. - const MaxStrikes: u32 = T::MaxStrikes::get(); + let mut weight = Weight::zero(); + let weights = T::BlockWeights::get(); - /// The amount of incentive paid within each period. Doesn't include VoterTip. - const PeriodSpend: BalanceOf = T::PeriodSpend::get(); + // Run a candidate/membership rotation + if (n % T::RotationPeriod::get()).is_zero() { + members = >::get(); + Self::rotate_period(&mut members); - /// The number of blocks between candidate/membership rotation periods. - const RotationPeriod: T::BlockNumber = T::RotationPeriod::get(); + weight += weights.max_block / 20; + } - /// The number of blocks between membership challenges. - const ChallengePeriod: T::BlockNumber = T::ChallengePeriod::get(); + // Run a challenge rotation + if (n % T::ChallengePeriod::get()).is_zero() { + // Only read members if not already read. + if members.is_empty() { + members = >::get(); + } + Self::rotate_challenge(&mut members); + + weight += weights.max_block / 20; + } - /// The societies's module id - const PalletId: PalletId = T::PalletId::get(); + weight + } + } - /// Maximum candidate intake per round. - const MaxCandidateIntake: u32 = T::MaxCandidateIntake::get(); + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + pub pot: BalanceOf, + pub members: Vec, + pub max_members: u32, + } - // Used for handling module events. - fn deposit_event() = default; + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + pot: Default::default(), + members: Default::default(), + max_members: Default::default(), + } + } + } + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + Pot::::put(self.pot); + MaxMembers::::put(self.max_members); + let first_member = self.members.first(); + if let Some(member) = first_member { + Founder::::put(member.clone()); + Head::::put(member.clone()); + }; + let mut m = self.members.clone(); + m.sort(); + Members::::put(m); + } + } + + #[pallet::call] + impl, I: 'static> Pallet { /// A user outside of the society can make a bid for entry. /// /// Payment: `CandidateDeposit` will be reserved for making a bid. It is returned @@ -538,12 +696,13 @@ decl_module! { /// - One storage read to retrieve all current candidates. O(C) /// - One storage read to retrieve all members. O(M) /// - Storage Writes: - /// - One storage mutate to add a new bid to the vector O(B) (TODO: possible optimization w/ read) + /// - One storage mutate to add a new bid to the vector O(B) (TODO: possible optimization + /// w/ read) /// - Up to one storage removal if bid.len() > MAX_BID_COUNT. O(1) /// - Notable Computation: /// - O(B + C + log M) search to check user is not already a part of society. /// - O(log B) search to insert the new bid sorted. - /// - External Module Operations: + /// - External Pallet Operations: /// - One balance reserve operation. O(X) /// - Up to one balance unreserve operation if bids.len() > MAX_BID_COUNT. /// - Events: @@ -552,8 +711,9 @@ decl_module! { /// /// Total Complexity: O(M + B + C + logM + logB + X) /// # - #[weight = T::BlockWeights::get().max_block / 10] - pub fn bid(origin, value: BalanceOf) -> DispatchResult { + #[pallet::call_index(0)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn bid(origin: OriginFor, value: BalanceOf) -> DispatchResult { let who = ensure_signed(origin)?; ensure!(!>::contains_key(&who), Error::::Suspended); ensure!(!>::contains_key(&who), Error::::Suspended); @@ -562,13 +722,13 @@ decl_module! { let candidates = >::get(); ensure!(!Self::is_candidate(&candidates, &who), Error::::AlreadyCandidate); let members = >::get(); - ensure!(!Self::is_member(&members ,&who), Error::::AlreadyMember); + ensure!(!Self::is_member(&members, &who), Error::::AlreadyMember); let deposit = T::CandidateDeposit::get(); T::Currency::reserve(&who, deposit)?; - Self::put_bid(bids, &who, value.clone(), BidKind::Deposit(deposit)); - Self::deposit_event(RawEvent::Bid(who, value)); + Self::put_bid(bids, &who, value, BidKind::Deposit(deposit)); + Self::deposit_event(Event::::Bid { candidate_id: who, offer: value }); Ok(()) } @@ -591,12 +751,13 @@ decl_module! { /// /// Total Complexity: O(B + X) /// # - #[weight = T::BlockWeights::get().max_block / 10] - pub fn unbid(origin, pos: u32) -> DispatchResult { + #[pallet::call_index(1)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn unbid(origin: OriginFor, pos: u32) -> DispatchResult { let who = ensure_signed(origin)?; let pos = pos as usize; - >::mutate(|b| + >::mutate(|b| { if pos < b.len() && b[pos].who == who { // Either unreserve the deposit or free up the vouching member. // In neither case can we do much if the action isn't completable, but there's @@ -605,17 +766,17 @@ decl_module! { BidKind::Deposit(deposit) => { let err_amount = T::Currency::unreserve(&who, deposit); debug_assert!(err_amount.is_zero()); - } + }, BidKind::Vouch(voucher, _) => { >::remove(&voucher); - } + }, } - Self::deposit_event(RawEvent::Unbid(who)); + Self::deposit_event(Event::::Unbid { candidate: who }); Ok(()) } else { - Err(Error::::BadPosition)? + Err(Error::::BadPosition.into()) } - ) + }) } /// As a member, vouch for someone to join society by placing a bid on their behalf. @@ -647,13 +808,14 @@ decl_module! { /// - One storage read to retrieve all current candidates. O(C) /// - Storage Writes: /// - One storage write to insert vouching status to the member. O(1) - /// - One storage mutate to add a new bid to the vector O(B) (TODO: possible optimization w/ read) + /// - One storage mutate to add a new bid to the vector O(B) (TODO: possible optimization + /// w/ read) /// - Up to one storage removal if bid.len() > MAX_BID_COUNT. O(1) /// - Notable Computation: /// - O(log M) search to check sender is a member. /// - O(B + C + log M) search to check user is not already a part of society. /// - O(log B) search to insert the new bid sorted. - /// - External Module Operations: + /// - External Pallet Operations: /// - One balance reserve operation. O(X) /// - Up to one balance unreserve operation if bids.len() > MAX_BID_COUNT. /// - Events: @@ -662,9 +824,16 @@ decl_module! { /// /// Total Complexity: O(M + B + C + logM + logB + X) /// # - #[weight = T::BlockWeights::get().max_block / 10] - pub fn vouch(origin, who: T::AccountId, value: BalanceOf, tip: BalanceOf) -> DispatchResult { + #[pallet::call_index(2)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn vouch( + origin: OriginFor, + who: AccountIdLookupOf, + value: BalanceOf, + tip: BalanceOf, + ) -> DispatchResult { let voucher = ensure_signed(origin)?; + let who = T::Lookup::lookup(who)?; // Check user is not suspended. ensure!(!>::contains_key(&who), Error::::Suspended); ensure!(!>::contains_key(&who), Error::::Suspended); @@ -681,8 +850,12 @@ decl_module! { ensure!(!>::contains_key(&voucher), Error::::AlreadyVouching); >::insert(&voucher, VouchingStatus::Vouching); - Self::put_bid(bids, &who, value.clone(), BidKind::Vouch(voucher.clone(), tip)); - Self::deposit_event(RawEvent::Vouch(who, value, voucher)); + Self::put_bid(bids, &who, value, BidKind::Vouch(voucher.clone(), tip)); + Self::deposit_event(Event::::Vouch { + candidate_id: who, + offer: value, + vouching: voucher, + }); Ok(()) } @@ -703,23 +876,27 @@ decl_module! { /// /// Total Complexity: O(B) /// # - #[weight = T::BlockWeights::get().max_block / 10] - pub fn unvouch(origin, pos: u32) -> DispatchResult { + #[pallet::call_index(3)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn unvouch(origin: OriginFor, pos: u32) -> DispatchResult { let voucher = ensure_signed(origin)?; - ensure!(Self::vouching(&voucher) == Some(VouchingStatus::Vouching), Error::::NotVouching); + ensure!( + Self::vouching(&voucher) == Some(VouchingStatus::Vouching), + Error::::NotVouching + ); let pos = pos as usize; - >::mutate(|b| + >::mutate(|b| { if pos < b.len() { b[pos].kind.check_voucher(&voucher)?; >::remove(&voucher); let who = b.remove(pos).who; - Self::deposit_event(RawEvent::Unvouch(who)); + Self::deposit_event(Event::::Unvouch { candidate: who }); Ok(()) } else { - Err(Error::::BadPosition)? + Err(Error::::BadPosition.into()) } - ) + }) } /// As a member, vote on a candidate. @@ -728,8 +905,8 @@ decl_module! { /// /// Parameters: /// - `candidate`: The candidate that the member would like to bid on. - /// - `approve`: A boolean which says if the candidate should be - /// approved (`true`) or rejected (`false`). + /// - `approve`: A boolean which says if the candidate should be approved (`true`) or + /// rejected (`false`). /// /// # /// Key: C (len of candidates), M (len of members) @@ -741,8 +918,13 @@ decl_module! { /// /// Total Complexity: O(M + logM + C) /// # - #[weight = T::BlockWeights::get().max_block / 10] - pub fn vote(origin, candidate: ::Source, approve: bool) { + #[pallet::call_index(4)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn vote( + origin: OriginFor, + candidate: AccountIdLookupOf, + approve: bool, + ) -> DispatchResult { let voter = ensure_signed(origin)?; let candidate = T::Lookup::lookup(candidate)?; let candidates = >::get(); @@ -753,7 +935,8 @@ decl_module! { let vote = if approve { Vote::Approve } else { Vote::Reject }; >::insert(&candidate, &voter, vote); - Self::deposit_event(RawEvent::Vote(candidate, voter, approve)); + Self::deposit_event(Event::::Vote { candidate, voter, vote: approve }); + Ok(()) } /// As a member, vote on the defender. @@ -772,8 +955,9 @@ decl_module! { /// /// Total Complexity: O(M + logM) /// # - #[weight = T::BlockWeights::get().max_block / 10] - pub fn defender_vote(origin, approve: bool) { + #[pallet::call_index(5)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResult { let voter = ensure_signed(origin)?; let members = >::get(); ensure!(Self::is_member(&members, &voter), Error::::NotMember); @@ -781,12 +965,14 @@ decl_module! { let vote = if approve { Vote::Approve } else { Vote::Reject }; >::insert(&voter, vote); - Self::deposit_event(RawEvent::DefenderVote(voter, approve)); + Self::deposit_event(Event::::DefenderVote { voter, vote: approve }); + Ok(()) } /// Transfer the first matured payout for the sender and remove it from the records. /// - /// NOTE: This extrinsic needs to be called multiple times to claim multiple matured payouts. + /// NOTE: This extrinsic needs to be called multiple times to claim multiple matured + /// payouts. /// /// Payment: The member will receive a payment equal to their first matured /// payout to their free balance. @@ -804,8 +990,9 @@ decl_module! { /// /// Total Complexity: O(M + logM + P + X) /// # - #[weight = T::BlockWeights::get().max_block / 10] - pub fn payout(origin) { + #[pallet::call_index(6)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let members = >::get(); @@ -813,7 +1000,7 @@ decl_module! { let mut payouts = >::get(&who); if let Some((when, amount)) = payouts.first() { - if when <= &>::block_number() { + if when <= &>::block_number() { T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?; payouts.remove(0); if payouts.is_empty() { @@ -824,13 +1011,13 @@ decl_module! { return Ok(()) } } - Err(Error::::NoPayout)? + Err(Error::::NoPayout.into()) } /// Found the society. /// /// This is done as a discrete action in order to allow for the - /// module to be included into a running chain and can only be done once. + /// pallet to be included into a running chain and can only be done once. /// /// The dispatch origin for this call must be from the _FounderSetOrigin_. /// @@ -846,18 +1033,26 @@ decl_module! { /// /// Total Complexity: O(1) /// # - #[weight = T::BlockWeights::get().max_block / 10] - fn found(origin, founder: T::AccountId, max_members: u32, rules: Vec) { + #[pallet::call_index(7)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn found( + origin: OriginFor, + founder: AccountIdLookupOf, + max_members: u32, + rules: Vec, + ) -> DispatchResult { T::FounderSetOrigin::ensure_origin(origin)?; + let founder = T::Lookup::lookup(founder)?; ensure!(!>::exists(), Error::::AlreadyFounded); ensure!(max_members > 1, Error::::MaxMembers); // This should never fail in the context of this function... - >::put(max_members); + >::put(max_members); Self::add_member(&founder)?; >::put(&founder); >::put(&founder); Rules::::put(T::Hashing::hash(&rules)); - Self::deposit_event(RawEvent::Founded(founder)); + Self::deposit_event(Event::::Founded { founder }); + Ok(()) } /// Annul the founding of the society. @@ -873,8 +1068,9 @@ decl_module! { /// /// Total Complexity: O(1) /// # - #[weight = T::BlockWeights::get().max_block / 10] - fn unfound(origin) { + #[pallet::call_index(8)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn unfound(origin: OriginFor) -> DispatchResult { let founder = ensure_signed(origin)?; ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); ensure!(Head::::get() == Some(founder.clone()), Error::::NotHead); @@ -884,8 +1080,10 @@ decl_module! { Founder::::kill(); Rules::::kill(); Candidates::::kill(); + #[allow(deprecated)] SuspendedCandidates::::remove_all(None); - Self::deposit_event(RawEvent::Unfounded(founder)); + Self::deposit_event(Event::::Unfounded { founder }); + Ok(()) } /// Allow suspension judgement origin to make judgement on a suspended member. @@ -900,13 +1098,14 @@ decl_module! { /// /// Parameters: /// - `who` - The suspended member to be judged. - /// - `forgive` - A boolean representing whether the suspension judgement origin - /// forgives (`true`) or rejects (`false`) a suspended member. + /// - `forgive` - A boolean representing whether the suspension judgement origin forgives + /// (`true`) or rejects (`false`) a suspended member. /// /// # /// Key: B (len of bids), M (len of members) /// - One storage read to check `who` is a suspended member. O(1) - /// - Up to one storage write O(M) with O(log M) binary search to add a member back to society. + /// - Up to one storage write O(M) with O(log M) binary search to add a member back to + /// society. /// - Up to 3 storage removals O(1) to clean up a removed member. /// - Up to one storage write O(B) with O(B) search to remove vouched bid from bids. /// - Up to one additional event if unvouch takes place. @@ -915,9 +1114,15 @@ decl_module! { /// /// Total Complexity: O(M + logM + B) /// # - #[weight = T::BlockWeights::get().max_block / 10] - fn judge_suspended_member(origin, who: T::AccountId, forgive: bool) { + #[pallet::call_index(9)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn judge_suspended_member( + origin: OriginFor, + who: AccountIdLookupOf, + forgive: bool, + ) -> DispatchResult { T::SuspensionJudgementOrigin::ensure_origin(origin)?; + let who = T::Lookup::lookup(who)?; ensure!(>::contains_key(&who), Error::::NotSuspended); if forgive { @@ -936,14 +1141,15 @@ decl_module! { if let Some(pos) = bids.iter().position(|b| b.kind.check_voucher(&who).is_ok()) { // Remove the bid, and emit an event let vouched = bids.remove(pos).who; - Self::deposit_event(RawEvent::Unvouch(vouched)); + Self::deposit_event(Event::::Unvouch { candidate: vouched }); } ); } } >::remove(&who); - Self::deposit_event(RawEvent::SuspendedMemberJudgement(who, forgive)); + Self::deposit_event(Event::::SuspendedMemberJudgement { who, judged: forgive }); + Ok(()) } /// Allow suspended judgement origin to make judgement on a suspended candidate. @@ -986,9 +1192,15 @@ decl_module! { /// /// Total Complexity: O(M + logM + B + X) /// # - #[weight = T::BlockWeights::get().max_block / 10] - fn judge_suspended_candidate(origin, who: T::AccountId, judgement: Judgement) { + #[pallet::call_index(10)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn judge_suspended_candidate( + origin: OriginFor, + who: AccountIdLookupOf, + judgement: Judgement, + ) -> DispatchResult { T::SuspensionJudgementOrigin::ensure_origin(origin)?; + let who = T::Lookup::lookup(who)?; if let Some((value, kind)) = >::get(&who) { match judgement { Judgement::Approve => { @@ -1001,36 +1213,43 @@ decl_module! { // Reduce next pot by payout >::put(pot - value); // Add payout for new candidate - let maturity = >::block_number() - + Self::lock_duration(Self::members().len() as u32); + let maturity = >::block_number() + + Self::lock_duration(Self::members().len() as u32); Self::pay_accepted_candidate(&who, value, kind, maturity); - } + }, Judgement::Reject => { // Founder has rejected this candidate match kind { BidKind::Deposit(deposit) => { // Slash deposit and move it to the society account - let res = T::Currency::repatriate_reserved(&who, &Self::account_id(), deposit, BalanceStatus::Free); + let res = T::Currency::repatriate_reserved( + &who, + &Self::account_id(), + deposit, + BalanceStatus::Free, + ); debug_assert!(res.is_ok()); - } + }, BidKind::Vouch(voucher, _) => { // Ban the voucher from vouching again >::insert(&voucher, VouchingStatus::Banned); - } + }, } - } + }, Judgement::Rebid => { - // Founder has taken no judgement, and candidate is placed back into the pool. + // Founder has taken no judgement, and candidate is placed back into the + // pool. let bids = >::get(); Self::put_bid(bids, &who, value, kind); - } + }, } // Remove suspended candidate >::remove(who); } else { - Err(Error::::NotSuspended)? + return Err(Error::::NotSuspended.into()) } + Ok(()) } /// Allows root origin to change the maximum number of members in society. @@ -1047,145 +1266,33 @@ decl_module! { /// /// Total Complexity: O(1) /// # - #[weight = T::BlockWeights::get().max_block / 10] - fn set_max_members(origin, max: u32) { + #[pallet::call_index(11)] + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn set_max_members(origin: OriginFor, max: u32) -> DispatchResult { ensure_root(origin)?; ensure!(max > 1, Error::::MaxMembers); - MaxMembers::::put(max); - Self::deposit_event(RawEvent::NewMaxMembers(max)); - } - - fn on_initialize(n: T::BlockNumber) -> Weight { - let mut members = vec![]; - - let mut weight = 0; - let weights = T::BlockWeights::get(); - - // Run a candidate/membership rotation - if (n % T::RotationPeriod::get()).is_zero() { - members = >::get(); - Self::rotate_period(&mut members); - - weight += weights.max_block / 20; - } - - // Run a challenge rotation - if (n % T::ChallengePeriod::get()).is_zero() { - // Only read members if not already read. - if members.is_empty() { - members = >::get(); - } - Self::rotate_challenge(&mut members); - - weight += weights.max_block / 20; - } - - weight + MaxMembers::::put(max); + Self::deposit_event(Event::::NewMaxMembers { max }); + Ok(()) } } } -decl_error! { - /// Errors for this module. - pub enum Error for Module, I: Instance> { - /// An incorrect position was provided. - BadPosition, - /// User is not a member. - NotMember, - /// User is already a member. - AlreadyMember, - /// User is suspended. - Suspended, - /// User is not suspended. - NotSuspended, - /// Nothing to payout. - NoPayout, - /// Society already founded. - AlreadyFounded, - /// Not enough in pot to accept candidate. - InsufficientPot, - /// Member is already vouching or banned from vouching again. - AlreadyVouching, - /// Member is not vouching. - NotVouching, - /// Cannot remove the head of the chain. - Head, - /// Cannot remove the founder. - Founder, - /// User has already made a bid. - AlreadyBid, - /// User is already a candidate. - AlreadyCandidate, - /// User is not a candidate. - NotCandidate, - /// Too many members in the society. - MaxMembers, - /// The caller is not the founder. - NotFounder, - /// The caller is not the head. - NotHead, - } -} - -decl_event! { - /// Events for this module. - pub enum Event where - AccountId = ::AccountId, - Balance = BalanceOf - { - /// The society is founded by the given identity. \[founder\] - Founded(AccountId), - /// A membership bid just happened. The given account is the candidate's ID and their offer - /// is the second. \[candidate_id, offer\] - Bid(AccountId, Balance), - /// A membership bid just happened by vouching. The given account is the candidate's ID and - /// their offer is the second. The vouching party is the third. \[candidate_id, offer, vouching\] - Vouch(AccountId, Balance, AccountId), - /// A \[candidate\] was dropped (due to an excess of bids in the system). - AutoUnbid(AccountId), - /// A \[candidate\] was dropped (by their request). - Unbid(AccountId), - /// A \[candidate\] was dropped (by request of who vouched for them). - Unvouch(AccountId), - /// A group of candidates have been inducted. The batch's primary is the first value, the - /// batch in full is the second. \[primary, candidates\] - Inducted(AccountId, Vec), - /// A suspended member has been judged. \[who, judged\] - SuspendedMemberJudgement(AccountId, bool), - /// A \[candidate\] has been suspended - CandidateSuspended(AccountId), - /// A \[member\] has been suspended - MemberSuspended(AccountId), - /// A \[member\] has been challenged - Challenged(AccountId), - /// A vote has been placed \[candidate, voter, vote\] - Vote(AccountId, AccountId, bool), - /// A vote has been placed for a defending member \[voter, vote\] - DefenderVote(AccountId, bool), - /// A new \[max\] member count has been set - NewMaxMembers(u32), - /// Society is unfounded. \[founder\] - Unfounded(AccountId), - /// Some funds were deposited into the society account. \[value\] - Deposit(Balance), - } -} - /// Simple ensure origin struct to filter for the founder account. pub struct EnsureFounder(sp_std::marker::PhantomData); -impl EnsureOrigin for EnsureFounder { +impl EnsureOrigin for EnsureFounder { type Success = T::AccountId; - fn try_origin(o: T::Origin) -> Result { + fn try_origin(o: T::RuntimeOrigin) -> Result { o.into().and_then(|o| match (o, Founder::::get()) { - (system::RawOrigin::Signed(ref who), Some(ref f)) if who == f => Ok(who.clone()), - (r, _) => Err(T::Origin::from(r)), + (frame_system::RawOrigin::Signed(ref who), Some(ref f)) if who == f => Ok(who.clone()), + (r, _) => Err(T::RuntimeOrigin::from(r)), }) } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> T::Origin { - let founder = Founder::::get().expect("society founder should exist"); - T::Origin::from(system::RawOrigin::Signed(founder)) + fn try_successful_origin() -> Result { + let founder = Founder::::get().ok_or(())?; + Ok(T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(founder))) } } @@ -1199,11 +1306,11 @@ fn pick_item<'a, R: RngCore, T>(rng: &mut R, items: &'a [T]) -> Option<&'a T> { } /// Pick a new PRN, in the range [0, `max`] (inclusive). -fn pick_usize<'a, R: RngCore>(rng: &mut R, max: usize) -> usize { +fn pick_usize(rng: &mut R, max: usize) -> usize { (rng.next_u32() % (max as u32 + 1)) as usize } -impl, I: Instance> Module { +impl, I: 'static> Pallet { /// Puts a bid into storage ordered by smallest to largest value. /// Allows a maximum of 1000 bids in queue, removing largest value people first. fn put_bid( @@ -1225,9 +1332,9 @@ impl, I: Instance> Module { // Skip ahead to the suggested position .skip(pos) // Keep skipping ahead until the position changes - .skip_while(|(_, x)| x.value <= bids[pos].value) // Get the element when things changed - .next(); + .find(|(_, x)| x.value > bids[pos].value); + // If the element is not at the end of the list, insert the new element // in the spot. if let Some((p, _)) = different_bid { @@ -1251,7 +1358,7 @@ impl, I: Instance> Module { >::remove(&voucher); }, } - Self::deposit_event(RawEvent::AutoUnbid(popped)); + Self::deposit_event(Event::::AutoUnbid { candidate: popped }); } >::put(bids); @@ -1260,7 +1367,7 @@ impl, I: Instance> Module { /// Check a user is a bid. fn is_bid(bids: &Vec>>, who: &T::AccountId) -> bool { // Bids are ordered by `value`, so we cannot binary search for a user. - bids.iter().find(|bid| bid.who == *who).is_some() + bids.iter().any(|bid| bid.who == *who) } /// Check a user is a candidate. @@ -1281,7 +1388,7 @@ impl, I: Instance> Module { /// Can fail when `MaxMember` limit is reached, but has no side-effects. fn add_member(who: &T::AccountId) -> DispatchResult { let mut members = >::get(); - ensure!(members.len() < MaxMembers::::get() as usize, Error::::MaxMembers); + ensure!(members.len() < MaxMembers::::get() as usize, Error::::MaxMembers); match members.binary_search(who) { // Add the new member Err(i) => { @@ -1304,8 +1411,8 @@ impl, I: Instance> Module { ensure!(Self::founder() != Some(m.clone()), Error::::Founder); let mut members = >::get(); - match members.binary_search(&m) { - Err(_) => Err(Error::::NotMember)?, + match members.binary_search(m) { + Err(_) => Err(Error::::NotMember.into()), Ok(i) => { members.remove(i); T::MembershipChanged::change_members_sorted(&[], &[m.clone()], &members[..]); @@ -1338,8 +1445,8 @@ impl, I: Instance> Module { // out of society. members.reserve(candidates.len()); - let maturity = - >::block_number() + Self::lock_duration(members.len() as u32); + let maturity = >::block_number() + + Self::lock_duration(members.len() as u32); let mut rewardees = Vec::new(); let mut total_approvals = 0; @@ -1416,13 +1523,14 @@ impl, I: Instance> Module { } else { // Suspend Candidate >::insert(&candidate, (value, kind)); - Self::deposit_event(RawEvent::CandidateSuspended(candidate)); + Self::deposit_event(Event::::CandidateSuspended { candidate }); None } }) .collect::>(); // Clean up all votes. + #[allow(deprecated)] >::remove_all(None); // Reward one of the voters who voted the right way. @@ -1484,8 +1592,8 @@ impl, I: Instance> Module { >::put(&members[..]); >::put(&primary); - T::MembershipChanged::change_members_sorted(&accounts, &[], &members); - Self::deposit_event(RawEvent::Inducted(primary, accounts)); + T::MembershipChanged::change_members_sorted(&accounts, &[], members); + Self::deposit_event(Event::::Inducted { primary, candidates: accounts }); } // Bump the pot by at most PeriodSpend, but less if there's not very much left in our @@ -1517,7 +1625,7 @@ impl, I: Instance> Module { if !payouts.is_empty() { let mut dropped = 0; for (_, amount) in payouts.iter_mut() { - if let Some(new_rest) = rest.checked_sub(&amount) { + if let Some(new_rest) = rest.checked_sub(amount) { // not yet totally slashed after this one; drop it completely. rest = new_rest; dropped += 1; @@ -1547,10 +1655,10 @@ impl, I: Instance> Module { /// Suspend a user, removing them from the member list. fn suspend_member(who: &T::AccountId) { - if Self::remove_member(&who).is_ok() { + if Self::remove_member(who).is_ok() { >::insert(who, true); >::remove(who); - Self::deposit_event(RawEvent::MemberSuspended(who.clone())); + Self::deposit_event(Event::::MemberSuspended { member: who.clone() }); } } @@ -1595,12 +1703,10 @@ impl, I: Instance> Module { let mut approval_count = 0; let mut rejection_count = 0; // Tallies total number of approve and reject votes for the defender. - members.iter().filter_map(|m| >::take(m)).for_each( - |v| match v { - Vote::Approve => approval_count += 1, - _ => rejection_count += 1, - }, - ); + members.iter().filter_map(>::take).for_each(|v| match v { + Vote::Approve => approval_count += 1, + _ => rejection_count += 1, + }); if approval_count <= rejection_count { // User has failed the challenge @@ -1609,6 +1715,7 @@ impl, I: Instance> Module { } // Clean up all votes. + #[allow(deprecated)] >::remove_all(None); } @@ -1628,7 +1735,7 @@ impl, I: Instance> Module { let chosen = pick_item(&mut rng, &members[1..members.len() - 1]) .expect("exited if members empty; qed"); >::put(&chosen); - Self::deposit_event(RawEvent::Challenged(chosen.clone())); + Self::deposit_event(Event::::Challenged { member: chosen.clone() }); } else { >::kill(); } @@ -1640,7 +1747,7 @@ impl, I: Instance> Module { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() + T::PalletId::get().into_account_truncating() } /// The account ID of the payouts pot. This is where payouts are made from. @@ -1648,7 +1755,7 @@ impl, I: Instance> Module { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn payouts() -> T::AccountId { - T::PalletId::get().into_sub_account(b"payouts") + T::PalletId::get().into_sub_account_truncating(b"payouts") } /// Return the duration of the lock, in blocks, with the given number of members. @@ -1668,7 +1775,7 @@ impl, I: Instance> Module { members_len: usize, pot: BalanceOf, ) -> Vec>> { - let max_members = MaxMembers::::get() as usize; + let max_members = MaxMembers::::get() as usize; let mut max_selections: usize = (T::MaxCandidateIntake::get() as usize).min(max_members.saturating_sub(members_len)); @@ -1725,13 +1832,13 @@ impl, I: Instance> Module { } } -impl OnUnbalanced> for Module { - fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { +impl, I: 'static> OnUnbalanced> for Pallet { + fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { let numeric_amount = amount.peek(); // Must resolve into existing but better to be safe. let _ = T::Currency::resolve_creating(&Self::account_id(), amount); - Self::deposit_event(RawEvent::Deposit(numeric_amount)); + Self::deposit_event(Event::::Deposit { value: numeric_amount }); } } diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 38c2586323135..0b1b93aeae761 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use crate as pallet_society; use frame_support::{ ord_parameter_types, parameter_types, - traits::{OnFinalize, OnInitialize}, + traits::{ConstU32, ConstU64}, }; use frame_support_test::TestRandomness; use frame_system::EnsureSignedBy; @@ -48,19 +48,9 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const CandidateDeposit: u64 = 25; - pub const WrongSideDeduction: u64 = 2; - pub const MaxStrikes: u32 = 2; - pub const RotationPeriod: u64 = 4; - pub const PeriodSpend: u64 = 1000; - pub const MaxLockDuration: u64 = 100; - pub const ChallengePeriod: u64 = 8; - pub const BlockHashCount: u64 = 250; - pub const ExistentialDeposit: u64 = 1; - pub const MaxCandidateIntake: u32 = 10; pub const SocietyPalletId: PalletId = PalletId(*b"py/socie"); pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } ord_parameter_types! { @@ -73,17 +63,17 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u128; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type OnNewAccount = (); @@ -92,6 +82,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { @@ -99,28 +90,28 @@ impl pallet_balances::Config for Test { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } impl Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = pallet_balances::Pallet; type Randomness = TestRandomness; - type CandidateDeposit = CandidateDeposit; - type WrongSideDeduction = WrongSideDeduction; - type MaxStrikes = MaxStrikes; - type PeriodSpend = PeriodSpend; + type CandidateDeposit = ConstU64<25>; + type WrongSideDeduction = ConstU64<2>; + type MaxStrikes = ConstU32<2>; + type PeriodSpend = ConstU64<1000>; type MembershipChanged = (); - type RotationPeriod = RotationPeriod; - type MaxLockDuration = MaxLockDuration; + type RotationPeriod = ConstU64<4>; + type MaxLockDuration = ConstU64<100>; type FounderSetOrigin = EnsureSignedBy; type SuspensionJudgementOrigin = EnsureSignedBy; - type ChallengePeriod = ChallengePeriod; - type MaxCandidateIntake = MaxCandidateIntake; + type ChallengePeriod = ConstU64<8>; + type MaxCandidateIntake = ConstU32<10>; type PalletId = SocietyPalletId; } diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 9f8e32dea5088..864735aa10cca 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,9 +33,9 @@ fn founding_works() { assert_eq!(Society::pot(), 0); // Account 1 is set as the founder origin // Account 5 cannot start a society - assert_noop!(Society::found(Origin::signed(5), 20, 100, vec![]), BadOrigin); + assert_noop!(Society::found(RuntimeOrigin::signed(5), 20, 100, vec![]), BadOrigin); // Account 1 can start a society, where 10 is the founding member - assert_ok!(Society::found(Origin::signed(1), 10, 100, b"be cool".to_vec())); + assert_ok!(Society::found(RuntimeOrigin::signed(1), 10, 100, b"be cool".to_vec())); // Society members only include 10 assert_eq!(Society::members(), vec![10]); // 10 is the head of the society @@ -51,7 +51,7 @@ fn founding_works() { assert_eq!(Society::pot(), 1000); // Cannot start another society assert_noop!( - Society::found(Origin::signed(1), 20, 100, vec![]), + Society::found(RuntimeOrigin::signed(1), 20, 100, vec![]), Error::::AlreadyFounded ); }); @@ -61,22 +61,22 @@ fn founding_works() { fn unfounding_works() { EnvBuilder::new().with_max_members(0).with_members(vec![]).execute(|| { // Account 1 sets the founder... - assert_ok!(Society::found(Origin::signed(1), 10, 100, vec![])); + assert_ok!(Society::found(RuntimeOrigin::signed(1), 10, 100, vec![])); // Account 2 cannot unfound it as it's not the founder. - assert_noop!(Society::unfound(Origin::signed(2)), Error::::NotFounder); + assert_noop!(Society::unfound(RuntimeOrigin::signed(2)), Error::::NotFounder); // Account 10 can, though. - assert_ok!(Society::unfound(Origin::signed(10))); + assert_ok!(Society::unfound(RuntimeOrigin::signed(10))); // 1 sets the founder to 20 this time - assert_ok!(Society::found(Origin::signed(1), 20, 100, vec![])); + assert_ok!(Society::found(RuntimeOrigin::signed(1), 20, 100, vec![])); // Bring in a new member... - assert_ok!(Society::bid(Origin::signed(10), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(10), 0)); run_to_block(4); - assert_ok!(Society::vote(Origin::signed(20), 10, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(20), 10, true)); run_to_block(8); // Unfounding won't work now, even though it's from 20. - assert_noop!(Society::unfound(Origin::signed(20)), Error::::NotHead); + assert_noop!(Society::unfound(RuntimeOrigin::signed(20)), Error::::NotHead); }); } @@ -85,7 +85,7 @@ fn basic_new_member_works() { EnvBuilder::new().execute(|| { assert_eq!(Balances::free_balance(20), 50); // Bid causes Candidate Deposit to be reserved. - assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(20), 0)); assert_eq!(Balances::free_balance(20), 25); assert_eq!(Balances::reserved_balance(20), 25); // Rotate period every 4 blocks @@ -93,7 +93,7 @@ fn basic_new_member_works() { // 20 is now a candidate assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); // 10 (a member) can vote for the candidate - assert_ok!(Society::vote(Origin::signed(10), 20, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, true)); // Rotate period every 4 blocks run_to_block(8); // 20 is now a member of the society @@ -108,10 +108,10 @@ fn basic_new_member_works() { fn bidding_works() { EnvBuilder::new().execute(|| { // Users make bids of various amounts - assert_ok!(Society::bid(Origin::signed(60), 1900)); - assert_ok!(Society::bid(Origin::signed(50), 500)); - assert_ok!(Society::bid(Origin::signed(40), 400)); - assert_ok!(Society::bid(Origin::signed(30), 300)); + assert_ok!(Society::bid(RuntimeOrigin::signed(60), 1900)); + assert_ok!(Society::bid(RuntimeOrigin::signed(50), 500)); + assert_ok!(Society::bid(RuntimeOrigin::signed(40), 400)); + assert_ok!(Society::bid(RuntimeOrigin::signed(30), 300)); // Rotate period run_to_block(4); // Pot is 1000 after "PeriodSpend" @@ -126,8 +126,8 @@ fn bidding_works() { ] ); // A member votes for these candidates to join the society - assert_ok!(Society::vote(Origin::signed(10), 30, true)); - assert_ok!(Society::vote(Origin::signed(10), 40, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 30, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 40, true)); run_to_block(8); // Candidates become members after a period rotation assert_eq!(Society::members(), vec![10, 30, 40]); @@ -137,7 +137,7 @@ fn bidding_works() { // Left over from the original bids is 50 who satisfies the condition of bid less than pot. assert_eq!(Society::candidates(), vec![create_bid(500, 50, BidKind::Deposit(25))]); // 40, now a member, can vote for 50 - assert_ok!(Society::vote(Origin::signed(40), 50, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(40), 50, true)); run_to_block(12); // 50 is now a member assert_eq!(Society::members(), vec![10, 30, 40, 50]); @@ -146,8 +146,8 @@ fn bidding_works() { assert_eq!(Balances::free_balance(Society::account_id()), 8_800); // No more candidates satisfy the requirements assert_eq!(Society::candidates(), vec![]); - assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around - // Next period + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(10), true)); // Keep defender around + // Next period run_to_block(16); // Same members assert_eq!(Society::members(), vec![10, 30, 40, 50]); @@ -158,7 +158,7 @@ fn bidding_works() { // Candidate 60 now qualifies based on the increased pot size. assert_eq!(Society::candidates(), vec![create_bid(1900, 60, BidKind::Deposit(25))]); // Candidate 60 is voted in. - assert_ok!(Society::vote(Origin::signed(50), 60, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(50), 60, true)); run_to_block(20); // 60 joins as a member assert_eq!(Society::members(), vec![10, 30, 40, 50, 60]); @@ -172,15 +172,15 @@ fn bidding_works() { fn unbidding_works() { EnvBuilder::new().execute(|| { // 20 and 30 make bids - assert_ok!(Society::bid(Origin::signed(20), 1000)); - assert_ok!(Society::bid(Origin::signed(30), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(20), 1000)); + assert_ok!(Society::bid(RuntimeOrigin::signed(30), 0)); // Balances are reserved assert_eq!(Balances::free_balance(30), 25); assert_eq!(Balances::reserved_balance(30), 25); // Must know right position to unbid + cannot unbid someone else - assert_noop!(Society::unbid(Origin::signed(30), 1), Error::::BadPosition); + assert_noop!(Society::unbid(RuntimeOrigin::signed(30), 1), Error::::BadPosition); // Can unbid themselves with the right position - assert_ok!(Society::unbid(Origin::signed(30), 0)); + assert_ok!(Society::unbid(RuntimeOrigin::signed(30), 0)); // Balance is returned assert_eq!(Balances::free_balance(30), 50); assert_eq!(Balances::reserved_balance(30), 0); @@ -195,15 +195,15 @@ fn payout_works() { EnvBuilder::new().execute(|| { // Original balance of 50 assert_eq!(Balances::free_balance(20), 50); - assert_ok!(Society::bid(Origin::signed(20), 1000)); + assert_ok!(Society::bid(RuntimeOrigin::signed(20), 1000)); run_to_block(4); - assert_ok!(Society::vote(Origin::signed(10), 20, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, true)); run_to_block(8); // payout not ready - assert_noop!(Society::payout(Origin::signed(20)), Error::::NoPayout); + assert_noop!(Society::payout(RuntimeOrigin::signed(20)), Error::::NoPayout); run_to_block(9); // payout should be here - assert_ok!(Society::payout(Origin::signed(20))); + assert_ok!(Society::payout(RuntimeOrigin::signed(20))); assert_eq!(Balances::free_balance(20), 1050); }); } @@ -212,7 +212,7 @@ fn payout_works() { fn basic_new_member_skeptic_works() { EnvBuilder::new().execute(|| { assert_eq!(Strikes::::get(10), 0); - assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(20), 0)); run_to_block(4); assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); run_to_block(8); @@ -227,14 +227,14 @@ fn basic_new_member_reject_works() { // Starting Balance assert_eq!(Balances::free_balance(20), 50); // 20 makes a bid - assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(20), 0)); assert_eq!(Balances::free_balance(20), 25); assert_eq!(Balances::reserved_balance(20), 25); // Rotation Period run_to_block(4); assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); // We say no - assert_ok!(Society::vote(Origin::signed(10), 20, false)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, false)); run_to_block(8); // User is not added as member assert_eq!(Society::members(), vec![10]); @@ -248,19 +248,19 @@ fn basic_new_member_reject_works() { fn slash_payout_works() { EnvBuilder::new().execute(|| { assert_eq!(Balances::free_balance(20), 50); - assert_ok!(Society::bid(Origin::signed(20), 1000)); + assert_ok!(Society::bid(RuntimeOrigin::signed(20), 1000)); run_to_block(4); - assert_ok!(Society::vote(Origin::signed(10), 20, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, true)); run_to_block(8); // payout in queue assert_eq!(Payouts::::get(20), vec![(9, 1000)]); - assert_noop!(Society::payout(Origin::signed(20)), Error::::NoPayout); + assert_noop!(Society::payout(RuntimeOrigin::signed(20)), Error::::NoPayout); // slash payout assert_eq!(Society::slash_payout(&20, 500), 500); assert_eq!(Payouts::::get(20), vec![(9, 500)]); run_to_block(9); // payout should be here, but 500 less - assert_ok!(Society::payout(Origin::signed(20))); + assert_ok!(Society::payout(RuntimeOrigin::signed(20))); assert_eq!(Balances::free_balance(20), 550); }); } @@ -295,10 +295,10 @@ fn suspended_member_life_cycle_works() { assert_eq!(>::get(20), false); // Let's suspend account 20 by giving them 2 strikes by not voting - assert_ok!(Society::bid(Origin::signed(30), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(30), 0)); run_to_block(8); assert_eq!(Strikes::::get(20), 1); - assert_ok!(Society::bid(Origin::signed(40), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(40), 0)); run_to_block(16); // Strike 2 is accumulated, and 20 is suspended :( @@ -307,14 +307,17 @@ fn suspended_member_life_cycle_works() { // Suspended members cannot get payout Society::bump_payout(&20, 10, 100); - assert_noop!(Society::payout(Origin::signed(20)), Error::::NotMember); + assert_noop!(Society::payout(RuntimeOrigin::signed(20)), Error::::NotMember); // Normal people cannot make judgement - assert_noop!(Society::judge_suspended_member(Origin::signed(20), 20, true), BadOrigin); + assert_noop!( + Society::judge_suspended_member(RuntimeOrigin::signed(20), 20, true), + BadOrigin + ); // Suspension judgment origin can judge thee // Suspension judgement origin forgives the suspended member - assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, true)); + assert_ok!(Society::judge_suspended_member(RuntimeOrigin::signed(2), 20, true)); assert_eq!(>::get(20), false); assert_eq!(>::get(), vec![10, 20]); @@ -322,7 +325,7 @@ fn suspended_member_life_cycle_works() { Society::suspend_member(&20); assert_eq!(>::get(20), true); // Suspension judgement origin does not forgive the suspended member - assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); + assert_ok!(Society::judge_suspended_member(RuntimeOrigin::signed(2), 20, false)); // Cleaned up assert_eq!(>::get(20), false); assert_eq!(>::get(), vec![10]); @@ -337,14 +340,14 @@ fn suspended_candidate_rejected_works() { assert_eq!(Balances::free_balance(20), 50); assert_eq!(Balances::free_balance(Society::account_id()), 10000); // 20 makes a bid - assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(20), 0)); assert_eq!(Balances::free_balance(20), 25); assert_eq!(Balances::reserved_balance(20), 25); // Rotation Period run_to_block(4); assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); // We say no - assert_ok!(Society::vote(Origin::signed(10), 20, false)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, false)); run_to_block(8); // User is not added as member assert_eq!(Society::members(), vec![10]); @@ -354,18 +357,22 @@ fn suspended_candidate_rejected_works() { // Normal user cannot make judgement on suspended candidate assert_noop!( - Society::judge_suspended_candidate(Origin::signed(20), 20, Judgement::Approve), + Society::judge_suspended_candidate(RuntimeOrigin::signed(20), 20, Judgement::Approve), BadOrigin ); // Suspension judgement origin makes no direct judgement - assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), 20, Judgement::Rebid)); + assert_ok!(Society::judge_suspended_candidate( + RuntimeOrigin::signed(2), + 20, + Judgement::Rebid + )); // They are placed back in bid pool, repeat suspension process // Rotation Period run_to_block(12); assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); // We say no - assert_ok!(Society::vote(Origin::signed(10), 20, false)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, false)); run_to_block(16); // User is not added as member assert_eq!(Society::members(), vec![10]); @@ -374,7 +381,11 @@ fn suspended_candidate_rejected_works() { assert_eq!(Society::suspended_candidate(20).is_some(), true); // Suspension judgement origin rejects the candidate - assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), 20, Judgement::Reject)); + assert_ok!(Society::judge_suspended_candidate( + RuntimeOrigin::signed(2), + 20, + Judgement::Reject + )); // User is slashed assert_eq!(Balances::free_balance(20), 25); assert_eq!(Balances::reserved_balance(20), 0); @@ -392,13 +403,16 @@ fn vouch_works() { // 10 is the only member assert_eq!(Society::members(), vec![10]); // A non-member cannot vouch - assert_noop!(Society::vouch(Origin::signed(1), 20, 1000, 100), Error::::NotMember); + assert_noop!( + Society::vouch(RuntimeOrigin::signed(1), 20, 1000, 100), + Error::::NotMember + ); // A member can though - assert_ok!(Society::vouch(Origin::signed(10), 20, 1000, 100)); + assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 20, 1000, 100)); assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); // A member cannot vouch twice at the same time assert_noop!( - Society::vouch(Origin::signed(10), 30, 100, 0), + Society::vouch(RuntimeOrigin::signed(10), 30, 100, 0), Error::::AlreadyVouching ); // Vouching creates the right kind of bid @@ -407,7 +421,7 @@ fn vouch_works() { run_to_block(4); assert_eq!(Society::candidates(), vec![create_bid(1000, 20, BidKind::Vouch(10, 100))]); // Vote yes - assert_ok!(Society::vote(Origin::signed(10), 20, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, true)); // Vouched user can win run_to_block(8); assert_eq!(Society::members(), vec![10, 20]); @@ -426,14 +440,14 @@ fn voucher_cannot_win_more_than_bid() { // 10 is the only member assert_eq!(Society::members(), vec![10]); // 10 vouches, but asks for more than the bid - assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 1000)); + assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 20, 100, 1000)); // Vouching creates the right kind of bid assert_eq!(>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]); // Vouched user can become candidate run_to_block(4); assert_eq!(Society::candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]); // Vote yes - assert_ok!(Society::vote(Origin::signed(10), 20, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, true)); // Vouched user can win run_to_block(8); assert_eq!(Society::members(), vec![10, 20]); @@ -450,25 +464,25 @@ fn unvouch_works() { // 10 is the only member assert_eq!(Society::members(), vec![10]); // 10 vouches for 20 - assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); + assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 20, 100, 0)); // 20 has a bid assert_eq!(>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); // 10 is vouched assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); // To unvouch, you must know the right bid position - assert_noop!(Society::unvouch(Origin::signed(10), 2), Error::::BadPosition); + assert_noop!(Society::unvouch(RuntimeOrigin::signed(10), 2), Error::::BadPosition); // 10 can unvouch with the right position - assert_ok!(Society::unvouch(Origin::signed(10), 0)); + assert_ok!(Society::unvouch(RuntimeOrigin::signed(10), 0)); // 20 no longer has a bid assert_eq!(>::get(), vec![]); // 10 is no longer vouching assert_eq!(>::get(10), None); // Cannot unvouch after they become candidate - assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); + assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 20, 100, 0)); run_to_block(4); assert_eq!(Society::candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); - assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::::BadPosition); + assert_noop!(Society::unvouch(RuntimeOrigin::signed(10), 0), Error::::BadPosition); // 10 is still vouching until candidate is approved or rejected assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); run_to_block(8); @@ -478,18 +492,22 @@ fn unvouch_works() { // User is stuck vouching until judgement origin resolves suspended candidate assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); // Judge denies candidate - assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), 20, Judgement::Reject)); + assert_ok!(Society::judge_suspended_candidate( + RuntimeOrigin::signed(2), + 20, + Judgement::Reject + )); // 10 is banned from vouching assert_eq!(>::get(10), Some(VouchingStatus::Banned)); assert_eq!(Society::members(), vec![10]); // 10 cannot vouch again assert_noop!( - Society::vouch(Origin::signed(10), 30, 100, 0), + Society::vouch(RuntimeOrigin::signed(10), 30, 100, 0), Error::::AlreadyVouching ); // 10 cannot unvouch either, so they are banned forever. - assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::::NotVouching); + assert_noop!(Society::unvouch(RuntimeOrigin::signed(10), 0), Error::::NotVouching); }); } @@ -499,13 +517,13 @@ fn unbid_vouch_works() { // 10 is the only member assert_eq!(Society::members(), vec![10]); // 10 vouches for 20 - assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); + assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 20, 100, 0)); // 20 has a bid assert_eq!(>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); // 10 is vouched assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); // 20 doesn't want to be a member and can unbid themselves. - assert_ok!(Society::unbid(Origin::signed(20), 0)); + assert_ok!(Society::unbid(RuntimeOrigin::signed(20), 0)); // Everything is cleaned up assert_eq!(>::get(10), None); assert_eq!(>::get(), vec![]); @@ -520,22 +538,22 @@ fn founder_and_head_cannot_be_removed() { assert_eq!(Society::founder(), Some(10)); assert_eq!(Society::head(), Some(10)); // 10 can still accumulate strikes - assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(20), 0)); run_to_block(8); assert_eq!(Strikes::::get(10), 1); - assert_ok!(Society::bid(Origin::signed(30), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(30), 0)); run_to_block(16); assert_eq!(Strikes::::get(10), 2); // Awkwardly they can obtain more than MAX_STRIKES... - assert_ok!(Society::bid(Origin::signed(40), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(40), 0)); run_to_block(24); assert_eq!(Strikes::::get(10), 3); // Replace the head - assert_ok!(Society::bid(Origin::signed(50), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(50), 0)); run_to_block(28); - assert_ok!(Society::vote(Origin::signed(10), 50, true)); - assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 50, true)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(10), true)); // Keep defender around run_to_block(32); assert_eq!(Society::members(), vec![10, 50]); assert_eq!(Society::head(), Some(50)); @@ -543,29 +561,29 @@ fn founder_and_head_cannot_be_removed() { assert_eq!(Society::founder(), Some(10)); // 50 can still accumulate strikes - assert_ok!(Society::bid(Origin::signed(60), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(60), 0)); run_to_block(40); assert_eq!(Strikes::::get(50), 1); - assert_ok!(Society::bid(Origin::signed(70), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(70), 0)); run_to_block(48); assert_eq!(Strikes::::get(50), 2); // Replace the head - assert_ok!(Society::bid(Origin::signed(80), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(80), 0)); run_to_block(52); - assert_ok!(Society::vote(Origin::signed(10), 80, true)); - assert_ok!(Society::vote(Origin::signed(50), 80, true)); - assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 80, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(50), 80, true)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(10), true)); // Keep defender around run_to_block(56); assert_eq!(Society::members(), vec![10, 50, 80]); assert_eq!(Society::head(), Some(80)); assert_eq!(Society::founder(), Some(10)); // 50 can now be suspended for strikes - assert_ok!(Society::bid(Origin::signed(90), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(90), 0)); run_to_block(60); // The candidate is rejected, so voting approve will give a strike - assert_ok!(Society::vote(Origin::signed(50), 90, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(50), 90, true)); run_to_block(64); assert_eq!(Strikes::::get(50), 0); assert_eq!(>::get(50), true); @@ -592,19 +610,22 @@ fn challenges_work() { run_to_block(8); assert_eq!(Society::defender(), Some(30)); // They can always free vote for themselves - assert_ok!(Society::defender_vote(Origin::signed(30), true)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(30), true)); // If no one else votes, nothing happens run_to_block(16); assert_eq!(Society::members(), vec![10, 20, 30, 40]); // New challenge period assert_eq!(Society::defender(), Some(30)); // Non-member cannot challenge - assert_noop!(Society::defender_vote(Origin::signed(1), true), Error::::NotMember); + assert_noop!( + Society::defender_vote(RuntimeOrigin::signed(1), true), + Error::::NotMember + ); // 3 people say accept, 1 reject - assert_ok!(Society::defender_vote(Origin::signed(10), true)); - assert_ok!(Society::defender_vote(Origin::signed(20), true)); - assert_ok!(Society::defender_vote(Origin::signed(30), true)); - assert_ok!(Society::defender_vote(Origin::signed(40), false)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(10), true)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(20), true)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(30), true)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(40), false)); run_to_block(24); // 20 survives assert_eq!(Society::members(), vec![10, 20, 30, 40]); @@ -616,10 +637,10 @@ fn challenges_work() { // One more time assert_eq!(Society::defender(), Some(30)); // 2 people say accept, 2 reject - assert_ok!(Society::defender_vote(Origin::signed(10), true)); - assert_ok!(Society::defender_vote(Origin::signed(20), true)); - assert_ok!(Society::defender_vote(Origin::signed(30), false)); - assert_ok!(Society::defender_vote(Origin::signed(40), false)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(10), true)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(20), true)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(30), false)); + assert_ok!(Society::defender_vote(RuntimeOrigin::signed(40), false)); run_to_block(32); // 20 is suspended assert_eq!(Society::members(), vec![10, 20, 40]); @@ -653,12 +674,12 @@ fn bad_vote_slash_works() { assert_eq!(>::get(30), vec![(5, 100)]); assert_eq!(>::get(40), vec![(5, 100)]); // Create a new bid - assert_ok!(Society::bid(Origin::signed(50), 1000)); + assert_ok!(Society::bid(RuntimeOrigin::signed(50), 1000)); run_to_block(4); - assert_ok!(Society::vote(Origin::signed(10), 50, false)); - assert_ok!(Society::vote(Origin::signed(20), 50, true)); - assert_ok!(Society::vote(Origin::signed(30), 50, false)); - assert_ok!(Society::vote(Origin::signed(40), 50, false)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 50, false)); + assert_ok!(Society::vote(RuntimeOrigin::signed(20), 50, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(30), 50, false)); + assert_ok!(Society::vote(RuntimeOrigin::signed(40), 50, false)); run_to_block(8); // Wrong voter gained a strike assert_eq!(>::get(10), 0); @@ -677,15 +698,15 @@ fn bad_vote_slash_works() { fn user_cannot_bid_twice() { EnvBuilder::new().execute(|| { // Cannot bid twice - assert_ok!(Society::bid(Origin::signed(20), 100)); - assert_noop!(Society::bid(Origin::signed(20), 100), Error::::AlreadyBid); + assert_ok!(Society::bid(RuntimeOrigin::signed(20), 100)); + assert_noop!(Society::bid(RuntimeOrigin::signed(20), 100), Error::::AlreadyBid); // Cannot bid when vouched - assert_ok!(Society::vouch(Origin::signed(10), 30, 100, 100)); - assert_noop!(Society::bid(Origin::signed(30), 100), Error::::AlreadyBid); + assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 30, 100, 100)); + assert_noop!(Society::bid(RuntimeOrigin::signed(30), 100), Error::::AlreadyBid); // Cannot vouch when already bid assert_ok!(Society::add_member(&50)); assert_noop!( - Society::vouch(Origin::signed(50), 20, 100, 100), + Society::vouch(RuntimeOrigin::signed(50), 20, 100, 100), Error::::AlreadyBid ); }); @@ -697,7 +718,7 @@ fn vouching_handles_removed_member_with_bid() { // Add a member assert_ok!(Society::add_member(&20)); // Have that member vouch for a user - assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); + assert_ok!(Society::vouch(RuntimeOrigin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching assert_eq!(>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); @@ -708,7 +729,7 @@ fn vouching_handles_removed_member_with_bid() { assert_eq!(>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Remove member - assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); + assert_ok!(Society::judge_suspended_member(RuntimeOrigin::signed(2), 20, false)); // Bid is removed, vouching status is removed assert_eq!(>::get(), vec![]); assert_eq!(>::get(20), None); @@ -721,7 +742,7 @@ fn vouching_handles_removed_member_with_candidate() { // Add a member assert_ok!(Society::add_member(&20)); // Have that member vouch for a user - assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); + assert_ok!(Society::vouch(RuntimeOrigin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching assert_eq!(>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); @@ -735,12 +756,12 @@ fn vouching_handles_removed_member_with_candidate() { assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Remove member - assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); + assert_ok!(Society::judge_suspended_member(RuntimeOrigin::signed(2), 20, false)); // Vouching status is removed, but candidate is still in the queue assert_eq!(>::get(20), None); assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); // Candidate wins - assert_ok!(Society::vote(Origin::signed(10), 30, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 30, true)); run_to_block(8); assert_eq!(Society::members(), vec![10, 30]); // Payout does not go to removed member @@ -753,16 +774,19 @@ fn vouching_handles_removed_member_with_candidate() { fn votes_are_working() { EnvBuilder::new().execute(|| { // Users make bids of various amounts - assert_ok!(Society::bid(Origin::signed(50), 500)); - assert_ok!(Society::bid(Origin::signed(40), 400)); - assert_ok!(Society::bid(Origin::signed(30), 300)); + assert_ok!(Society::bid(RuntimeOrigin::signed(50), 500)); + assert_ok!(Society::bid(RuntimeOrigin::signed(40), 400)); + assert_ok!(Society::bid(RuntimeOrigin::signed(30), 300)); // Rotate period run_to_block(4); // A member votes for these candidates to join the society - assert_ok!(Society::vote(Origin::signed(10), 30, true)); - assert_ok!(Society::vote(Origin::signed(10), 40, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 30, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 40, true)); // You cannot vote for a non-candidate - assert_noop!(Society::vote(Origin::signed(10), 50, true), Error::::NotCandidate); + assert_noop!( + Society::vote(RuntimeOrigin::signed(10), 50, true), + Error::::NotCandidate + ); // Votes are stored assert_eq!(>::get(30, 10), Some(Vote::Approve)); assert_eq!(>::get(40, 10), Some(Vote::Approve)); @@ -784,7 +808,7 @@ fn max_limits_work() { for i in (100..1110).rev() { // Give them some funds let _ = Balances::make_free_balance_be(&(i as u128), 1000); - assert_ok!(Society::bid(Origin::signed(i as u128), i)); + assert_ok!(Society::bid(RuntimeOrigin::signed(i as u128), i)); } let bids = >::get(); // Length is 1000 @@ -810,7 +834,7 @@ fn max_limits_work() { // Fill up members with suspended candidates from the first rotation for i in 100..104 { assert_ok!(Society::judge_suspended_candidate( - Origin::signed(2), + RuntimeOrigin::signed(2), i, Judgement::Approve )); @@ -821,9 +845,9 @@ fn max_limits_work() { // However, a fringe scenario allows for in-progress candidates to increase the membership // pool, but it has no real after-effects. for i in Society::members().iter() { - assert_ok!(Society::vote(Origin::signed(*i), 110, true)); - assert_ok!(Society::vote(Origin::signed(*i), 111, true)); - assert_ok!(Society::vote(Origin::signed(*i), 112, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(*i), 110, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(*i), 111, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(*i), 112, true)); } // Rotate period run_to_block(12); @@ -832,7 +856,7 @@ fn max_limits_work() { // No candidates because full assert_eq!(Society::candidates().len(), 0); // Increase member limit - assert_ok!(Society::set_max_members(Origin::root(), 200)); + assert_ok!(Society::set_max_members(RuntimeOrigin::root(), 200)); // Rotate period run_to_block(16); // Candidates are back! @@ -847,11 +871,11 @@ fn zero_bid_works() { // * That zero bid is placed as head when accepted. EnvBuilder::new().execute(|| { // Users make bids of various amounts - assert_ok!(Society::bid(Origin::signed(60), 400)); - assert_ok!(Society::bid(Origin::signed(50), 300)); - assert_ok!(Society::bid(Origin::signed(30), 0)); - assert_ok!(Society::bid(Origin::signed(20), 0)); - assert_ok!(Society::bid(Origin::signed(40), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(60), 400)); + assert_ok!(Society::bid(RuntimeOrigin::signed(50), 300)); + assert_ok!(Society::bid(RuntimeOrigin::signed(30), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(20), 0)); + assert_ok!(Society::bid(RuntimeOrigin::signed(40), 0)); // Rotate period run_to_block(4); @@ -872,9 +896,9 @@ fn zero_bid_works() { vec![create_bid(0, 20, BidKind::Deposit(25)), create_bid(0, 40, BidKind::Deposit(25)),] ); // A member votes for these candidates to join the society - assert_ok!(Society::vote(Origin::signed(10), 30, true)); - assert_ok!(Society::vote(Origin::signed(10), 50, true)); - assert_ok!(Society::vote(Origin::signed(10), 60, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 30, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 50, true)); + assert_ok!(Society::vote(RuntimeOrigin::signed(10), 60, true)); run_to_block(8); // Candidates become members after a period rotation assert_eq!(Society::members(), vec![10, 30, 50, 60]); @@ -892,7 +916,7 @@ fn bids_ordered_correctly() { for j in 0..5 { // Give them some funds let _ = Balances::make_free_balance_be(&(100 + (i * 5 + j) as u128), 1000); - assert_ok!(Society::bid(Origin::signed(100 + (i * 5 + j) as u128), j)); + assert_ok!(Society::bid(RuntimeOrigin::signed(100 + (i * 5 + j) as u128), j)); } } diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index aba19ba56357a..3ad63ad94a08a 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-staking" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet staking" readme = "README.md" @@ -13,14 +13,14 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.126", optional = true } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +serde = { version = "1.0.136", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } @@ -28,20 +28,23 @@ pallet-session = { version = "4.0.0-dev", default-features = false, features = [ "historical", ], path = "../session" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../../primitives/application-crypto" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.17", default-features = false } -# Optional imports for benchmarking +# optional dependencies for cargo features frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } rand_chacha = { version = "0.2", default-features = false, optional = true } +pallet-bags-list = { default-features = false, optional = true, path = "../bags-list" } [dev-dependencies] -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } +pallet-bags-list = { version = "4.0.0-dev", path = "../bags-list" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } @@ -50,6 +53,7 @@ rand_chacha = { version = "0.2" } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "serde", "codec/std", "scale-info/std", @@ -59,6 +63,7 @@ std = [ "sp-runtime/std", "sp-staking/std", "pallet-session/std", + "pallet-bags-list/std", "frame-system/std", "pallet-authorship/std", "sp-application-crypto/std", @@ -66,8 +71,14 @@ std = [ "frame-election-provider-support/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-election-provider-support/runtime-benchmarks", "rand_chacha", + "sp-staking/runtime-benchmarks", + "pallet-bags-list/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] +fuzz = [ + "pallet-bags-list/fuzz", + "frame-election-provider-support/fuzz", +] diff --git a/frame/staking/README.md b/frame/staking/README.md index 072353b1a586c..bbd5bd18f6e81 100644 --- a/frame/staking/README.md +++ b/frame/staking/README.md @@ -133,19 +133,27 @@ The Staking module contains many public storage items and (im)mutable functions. ### Example: Rewarding a validator by id. ```rust -use frame_support::{decl_module, dispatch}; -use frame_system::ensure_signed; use pallet_staking::{self as staking}; -pub trait Config: staking::Config {} +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; -decl_module! { - pub struct Module for enum Call where origin: T::Origin { + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + staking::Config {} + + #[pallet::call] + impl Pallet { /// Reward a validator. - #[weight = 0] - pub fn reward_myself(origin) -> dispatch::DispatchResult { + #[pallet::weight(0)] + pub fn reward_myself(origin: OriginFor) -> DispatchResult { let reported = ensure_signed(origin)?; - >::reward_by_ids(vec![(reported, 10)]); + >::reward_by_ids(vec![(reported, 10)]); Ok(()) } } diff --git a/frame/staking/fuzzer/src/mock.rs b/frame/staking/fuzzer/src/mock.rs deleted file mode 100644 index 921e0d3b48d7d..0000000000000 --- a/frame/staking/fuzzer/src/mock.rs +++ /dev/null @@ -1,202 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 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. - -//! Mock file for staking fuzzing. - -use frame_support::parameter_types; - -type AccountId = u64; -type AccountIndex = u32; -type BlockNumber = u64; -type Balance = u64; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Staking: pallet_staking::{Pallet, Call, Config, Storage, Event, ValidateUnsigned}, - Indices: pallet_indices::{Pallet, Call, Storage, Config, Event}, - Session: pallet_session::{Pallet, Call, Storage, Event, Config}, - } -); - -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Origin = Origin; - type Index = AccountIndex; - type BlockNumber = BlockNumber; - type Call = Call; - type Hash = sp_core::H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = Indices; - type Header = sp_runtime::testing::Header; - type Event = Event; - type BlockHashCount = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} -parameter_types! { - pub const ExistentialDeposit: Balance = 10; -} -impl pallet_balances::Config for Test { - type MaxLocks = (); - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; - type Balance = Balance; - type Event = Event; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); -} -impl pallet_indices::Config for Test { - type AccountIndex = AccountIndex; - type Event = Event; - type Currency = Balances; - type Deposit = (); - type WeightInfo = (); -} -parameter_types! { - pub const MinimumPeriod: u64 = 5; -} -impl pallet_timestamp::Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} -impl pallet_session::historical::Config for Test { - type FullIdentification = pallet_staking::Exposure; - type FullIdentificationOf = pallet_staking::ExposureOf; -} - -sp_runtime::impl_opaque_keys! { - pub struct SessionKeys { - pub foo: sp_runtime::testing::UintAuthorityId, - } -} - -pub struct TestSessionHandler; -impl pallet_session::SessionHandler for TestSessionHandler { - const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[]; - - fn on_genesis_session(_validators: &[(AccountId, Ks)]) {} - - fn on_new_session( - _: bool, - _: &[(AccountId, Ks)], - _: &[(AccountId, Ks)], - ) {} - - fn on_disabled(_: usize) {} -} - -impl pallet_session::Config for Test { - type SessionManager = pallet_session::historical::NoteHistoricalRoot; - type Keys = SessionKeys; - type ShouldEndSession = pallet_session::PeriodicSessions<(), ()>; - type NextSessionRotation = pallet_session::PeriodicSessions<(), ()>; - type SessionHandler = TestSessionHandler; - type Event = Event; - type ValidatorId = AccountId; - type ValidatorIdOf = pallet_staking::StashOf; - type DisabledValidatorsThreshold = (); - type WeightInfo = (); -} -pallet_staking_reward_curve::build! { - const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( - min_inflation: 0_025_000, - max_inflation: 0_100_000, - ideal_stake: 0_500_000, - falloff: 0_050_000, - max_piece_count: 40, - test_precision: 0_005_000, - ); -} -parameter_types! { - pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; - pub const MaxNominatorRewardedPerValidator: u32 = 64; - pub const MaxIterations: u32 = 20; -} - -pub type Extrinsic = sp_runtime::testing::TestXt; - -impl frame_system::offchain::SendTransactionTypes for Test -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = Extrinsic; -} - -pub struct MockElectionProvider; -impl frame_election_provider_support::ElectionProvider - for MockElectionProvider -{ - type Error = (); - type DataProvider = pallet_staking::Module; - - fn elect() -> Result< - (sp_npos_elections::Supports, frame_support::weights::Weight), - Self::Error - > { - Err(()) - } -} - -impl pallet_staking::Config for Test { - type Currency = Balances; - type UnixTime = pallet_timestamp::Pallet; - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; - type RewardRemainder = (); - type Event = Event; - type Slash = (); - type Reward = (); - type SessionsPerEra = (); - type SlashDeferDuration = (); - type SlashCancelOrigin = frame_system::EnsureRoot; - type BondingDuration = (); - type SessionInterface = Self; - type EraPayout = pallet_staking::ConvertCurve; - type NextNewSession = Session; - type ElectionLookahead = (); - type Call = Call; - type MaxIterations = MaxIterations; - type MinSolutionScoreBump = (); - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; - type UnsignedPriority = (); - type OffchainSolutionWeightLimit = (); - type WeightInfo = (); - type ElectionProvider = MockElectionProvider; -} diff --git a/frame/staking/reward-curve/Cargo.toml b/frame/staking/reward-curve/Cargo.toml index 4cbc2473cb526..c761517ea6829 100644 --- a/frame/staking/reward-curve/Cargo.toml +++ b/frame/staking/reward-curve/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-staking-reward-curve" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Reward Curve for FRAME staking pallet" @@ -15,10 +15,10 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "1.0.58", features = ["full", "visit"] } -quote = "1.0.3" -proc-macro2 = "1.0.29" -proc-macro-crate = "1.0.0" +proc-macro-crate = "1.1.3" +proc-macro2 = "1.0.37" +quote = "1.0.10" +syn = { version = "1.0.98", features = ["full", "visit"] } [dev-dependencies] -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/frame/staking/reward-curve/src/lib.rs b/frame/staking/reward-curve/src/lib.rs index 06e35d11350e0..e1ea8aa7b15d5 100644 --- a/frame/staking/reward-curve/src/lib.rs +++ b/frame/staking/reward-curve/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,12 +24,11 @@ use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use proc_macro_crate::{crate_name, FoundCrate}; use quote::{quote, ToTokens}; -use std::convert::TryInto; use syn::parse::{Parse, ParseStream}; /// Accepts a number of expressions to create a instance of PiecewiseLinear which represents the /// NPoS curve (as detailed -/// [here](https://research.web3.foundation/en/latest/polkadot/Token%20Economics.html#inflation-model)) +/// [here](https://research.web3.foundation/en/latest/polkadot/overview/2-token-economics.html#inflation-model)) /// for those parameters. Parameters are: /// - `min_inflation`: the minimal amount to be rewarded between validators, expressed as a fraction /// of total issuance. Known as `I_0` in the literature. Expressed in millionth, must be between 0 diff --git a/frame/staking/reward-curve/src/log.rs b/frame/staking/reward-curve/src/log.rs index c196aaaa31a93..248a1e3c36a6e 100644 --- a/frame/staking/reward-curve/src/log.rs +++ b/frame/staking/reward-curve/src/log.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - /// Simple u32 power of 2 function - simply uses a bit shift macro_rules! pow2 { ($n:expr) => { diff --git a/frame/staking/reward-curve/tests/test.rs b/frame/staking/reward-curve/tests/test.rs index fda7df145d0f3..aa19b1782453d 100644 --- a/frame/staking/reward-curve/tests/test.rs +++ b/frame/staking/reward-curve/tests/test.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/staking/reward-fn/Cargo.toml b/frame/staking/reward-fn/Cargo.toml index 076e05bf2a61e..0fb034a17202b 100644 --- a/frame/staking/reward-fn/Cargo.toml +++ b/frame/staking/reward-fn/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-staking-reward-fn" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Reward function for FRAME staking pallet" @@ -14,12 +14,12 @@ targets = ["x86_64-unknown-linux-gnu"] [lib] [dependencies] -sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/arithmetic" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.17", default-features = false } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../../primitives/arithmetic" } [features] default = ["std"] std = [ - "sp-arithmetic/std", "log/std", + "sp-arithmetic/std", ] diff --git a/frame/staking/reward-fn/src/lib.rs b/frame/staking/reward-fn/src/lib.rs index dd5e629b3984c..cc9919c28cce3 100644 --- a/frame/staking/reward-fn/src/lib.rs +++ b/frame/staking/reward-fn/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ //! Useful function for inflation for nominated proof of stake. -use core::convert::TryFrom; use sp_arithmetic::{ biguint::BigUint, traits::{SaturatedConversion, Zero}, @@ -138,20 +137,18 @@ fn compute_taylor_serie_part(p: &INPoSParam) -> BigUint { if taylor_sum_positive == last_taylor_term_positive { taylor_sum = taylor_sum.add(&last_taylor_term); + } else if taylor_sum >= last_taylor_term { + taylor_sum = taylor_sum + .sub(&last_taylor_term) + // NOTE: Should never happen as checked above + .unwrap_or_else(|e| e); } else { - if taylor_sum >= last_taylor_term { - taylor_sum = taylor_sum - .sub(&last_taylor_term) - // NOTE: Should never happen as checked above - .unwrap_or_else(|e| e); - } else { - taylor_sum_positive = !taylor_sum_positive; - taylor_sum = last_taylor_term - .clone() - .sub(&taylor_sum) - // NOTE: Should never happen as checked above - .unwrap_or_else(|e| e); - } + taylor_sum_positive = !taylor_sum_positive; + taylor_sum = last_taylor_term + .clone() + .sub(&taylor_sum) + // NOTE: Should never happen as checked above + .unwrap_or_else(|e| e); } } @@ -218,10 +215,10 @@ fn div_by_stripped(mut a: BigUint, b: &BigUint) -> BigUint { return new_a .div(b, false) .map(|res| res.0) - .unwrap_or_else(|| BigUint::zero()) + .unwrap_or_else(BigUint::zero) .div_unit(100_000) .div_unit(100_000) } - a.div(b, false).map(|res| res.0).unwrap_or_else(|| BigUint::zero()) + a.div(b, false).map(|res| res.0).unwrap_or_else(BigUint::zero) } diff --git a/frame/staking/reward-fn/tests/test.rs b/frame/staking/reward-fn/tests/test.rs index dc5b661c4098d..a79137716fd28 100644 --- a/frame/staking/reward-fn/tests/test.rs +++ b/frame/staking/reward-fn/tests/test.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index bdc3d81f3c29b..8409b5413f992 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,15 +18,18 @@ //! Staking pallet benchmarking. use super::*; -use crate::Pallet as Staking; +use crate::{ConfigOp, Pallet as Staking}; use testing_utils::*; +use codec::Decode; +use frame_election_provider_support::SortedListProvider; use frame_support::{ + dispatch::UnfilteredDispatchable, pallet_prelude::*, - traits::{Currency, Get, Imbalance}, + traits::{Currency, CurrencyToVote, Get, Imbalance}, }; use sp_runtime::{ - traits::{StaticLookup, Zero}, + traits::{Bounded, One, StaticLookup, TrailingZeroInput, Zero}, Perbill, Percent, }; use sp_staking::SessionIndex; @@ -36,17 +39,17 @@ pub use frame_benchmarking::{ account, benchmarks, impl_benchmark_test_suite, whitelist_account, whitelisted_caller, }; use frame_system::RawOrigin; -use sp_runtime::traits::{Bounded, One}; const SEED: u32 = 0; const MAX_SPANS: u32 = 100; -const MAX_VALIDATORS: u32 = 1000; -const MAX_NOMINATORS: u32 = 1000; const MAX_SLASHES: u32 = 1000; +type MaxValidators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxValidators; +type MaxNominators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxNominators; + // Add slashing spans to a user account. Not relevant for actual use, only to benchmark // read and write operations. -fn add_slashing_spans(who: &T::AccountId, spans: u32) { +pub fn add_slashing_spans(who: &T::AccountId, spans: u32) { if spans == 0 { return } @@ -80,11 +83,12 @@ pub fn create_validator_with_nominators( let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; Staking::::validate(RawOrigin::Signed(v_controller).into(), validator_prefs)?; - let stash_lookup: ::Source = T::Lookup::unlookup(v_stash.clone()); + let stash_lookup = T::Lookup::unlookup(v_stash.clone()); points_total += 10; points_individual.push((v_stash.clone(), 10)); + let original_nominator_count = Nominators::::count(); let mut nominators = Vec::new(); // Give the validator n nominators, but keep total users in the system the same. @@ -110,6 +114,8 @@ pub fn create_validator_with_nominators( assert_eq!(new_validators.len(), 1); assert_eq!(new_validators[0], v_stash, "Our validator was not selected!"); + assert_ne!(Validators::::count(), 0); + assert_eq!(Nominators::::count(), original_nominator_count + nominators.len() as u32); // Give Era Points let reward = EraRewardPoints:: { @@ -129,13 +135,90 @@ pub fn create_validator_with_nominators( Ok((v_stash, nominators)) } +struct ListScenario { + /// Stash that is expected to be moved. + origin_stash1: T::AccountId, + /// Controller of the Stash that is expected to be moved. + origin_controller1: T::AccountId, + dest_weight: BalanceOf, +} + +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. + /// + /// NOTE: while this scenario specifically targets a worst case for the bags-list, it should + /// also elicit a worst case for other known `VoterList` implementations; although + /// this may not be true against unknown `VoterList` implementations. + fn new(origin_weight: BalanceOf, is_increase: bool) -> Result { + ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); + + // burn the entire issuance. + let i = T::Currency::burn(T::Currency::total_issuance()); + sp_std::mem::forget(i); + + // create accounts with the origin weight + + let (origin_stash1, origin_controller1) = create_stash_controller_with_balance::( + USER_SEED + 2, + origin_weight, + Default::default(), + )?; + Staking::::nominate( + RawOrigin::Signed(origin_controller1.clone()).into(), + // NOTE: these don't really need to be validators. + vec![T::Lookup::unlookup(account("random_validator", 0, SEED))], + )?; + + let (_origin_stash2, origin_controller2) = create_stash_controller_with_balance::( + USER_SEED + 3, + origin_weight, + Default::default(), + )?; + Staking::::nominate( + RawOrigin::Signed(origin_controller2).into(), + vec![T::Lookup::unlookup(account("random_validator", 0, SEED))], + )?; + + // find a destination weight that will trigger the worst case scenario + let dest_weight_as_vote = + T::VoterList::score_update_worst_case(&origin_stash1, is_increase); + + let total_issuance = T::Currency::total_issuance(); + + let dest_weight = + T::CurrencyToVote::to_currency(dest_weight_as_vote as u128, total_issuance); + + // create an account with the worst case destination weight + let (_dest_stash1, dest_controller1) = create_stash_controller_with_balance::( + USER_SEED + 1, + dest_weight, + Default::default(), + )?; + Staking::::nominate( + RawOrigin::Signed(dest_controller1).into(), + vec![T::Lookup::unlookup(account("random_validator", 0, SEED))], + )?; + + Ok(ListScenario { origin_stash1, origin_controller1, dest_weight }) + } +} + const USER_SEED: u32 = 999666; benchmarks! { bond { let stash = create_funded_user::("stash", USER_SEED, 100); let controller = create_funded_user::("controller", USER_SEED, 100); - let controller_lookup: ::Source = T::Lookup::unlookup(controller.clone()); + let controller_lookup = T::Lookup::unlookup(controller.clone()); let reward_destination = RewardDestination::Staked; let amount = T::Currency::minimum_balance() * 10u32.into(); whitelist_account!(stash); @@ -146,10 +229,25 @@ benchmarks! { } bond_extra { - let (stash, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; - let max_additional = T::Currency::minimum_balance() * 10u32.into(); - let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; - let original_bonded: BalanceOf = ledger.active; + // clean up any existing state. + clear_validators_and_nominators::(); + + let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + + // setup the worst case list scenario. + + // the weight the nominator will start at. + let scenario = ListScenario::::new(origin_weight, true)?; + + let max_additional = scenario.dest_weight - origin_weight; + + let stash = scenario.origin_stash1.clone(); + let controller = scenario.origin_controller1; + let original_bonded: BalanceOf + = Ledger::::get(&controller).map(|l| l.active).ok_or("ledger not created after")?; + + T::Currency::deposit_into_existing(&stash, max_additional).unwrap(); + whitelist_account!(stash); }: _(RawOrigin::Signed(stash), max_additional) verify { @@ -159,10 +257,24 @@ benchmarks! { } unbond { - let (_, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; - let amount = T::Currency::minimum_balance() * 10u32.into(); + // clean up any existing state. + clear_validators_and_nominators::(); + + // setup the worst case list scenario. + let total_issuance = T::Currency::total_issuance(); + // 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 = BalanceOf::::try_from(952_994_955_240_703u128) + .map_err(|_| "balance expected to be a u128") + .unwrap(); + let scenario = ListScenario::::new(origin_weight, false)?; + + let stash = scenario.origin_stash1.clone(); + let controller = scenario.origin_controller1.clone(); + let amount = origin_weight - scenario.dest_weight; let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_bonded: BalanceOf = ledger.active; + whitelist_account!(controller); }: _(RawOrigin::Signed(controller.clone()), amount) verify { @@ -194,46 +306,66 @@ benchmarks! { withdraw_unbonded_kill { // Slashing Spans let s in 0 .. MAX_SPANS; - let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; - add_slashing_spans::(&stash, s); - let amount = T::Currency::minimum_balance() * 10u32.into(); - Staking::::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?; + // clean up any existing state. + clear_validators_and_nominators::(); + + let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + + // setup a worst case list scenario. Note that we don't care about the setup of the + // destination position because we are doing a removal from the list but no insert. + let scenario = ListScenario::::new(origin_weight, true)?; + let controller = scenario.origin_controller1.clone(); + let stash = scenario.origin_stash1; + assert!(T::VoterList::contains(&stash)); + + let ed = T::Currency::minimum_balance(); + let mut ledger = Ledger::::get(&controller).unwrap(); + ledger.active = ed - One::one(); + Ledger::::insert(&controller, ledger); CurrentEra::::put(EraIndex::max_value()); - let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; - let original_total: BalanceOf = ledger.total; + whitelist_account!(controller); }: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s) verify { assert!(!Ledger::::contains_key(controller)); + assert!(!T::VoterList::contains(&stash)); } validate { - let (stash, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; + let (stash, controller) = create_stash_controller::( + T::MaxNominations::get() - 1, + 100, + Default::default(), + )?; + // because it is chilled. + assert!(!T::VoterList::contains(&stash)); + let prefs = ValidatorPrefs::default(); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), prefs) verify { - assert!(Validators::::contains_key(stash)); + assert!(Validators::::contains_key(&stash)); + assert!(T::VoterList::contains(&stash)); } kick { // scenario: we want to kick `k` nominators from nominating us (we are a validator). // we'll assume that `k` is under 128 for the purposes of determining the slope. - // each nominator should have `T::MAX_NOMINATIONS` validators nominated, and our validator + // each nominator should have `T::MaxNominations::get()` validators nominated, and our validator // should be somewhere in there. let k in 1 .. 128; - // these are the other validators; there are `T::MAX_NOMINATIONS - 1` of them, so - // there are a total of `T::MAX_NOMINATIONS` validators in the system. - let rest_of_validators = create_validators::(T::MAX_NOMINATIONS - 1, 100)?; + // these are the other validators; there are `T::MaxNominations::get() - 1` of them, so + // there are a total of `T::MaxNominations::get()` validators in the system. + let rest_of_validators = create_validators_with_seed::(T::MaxNominations::get() - 1, 100, 415)?; // this is the validator that will be kicking. let (stash, controller) = create_stash_controller::( - T::MAX_NOMINATIONS - 1, + T::MaxNominations::get() - 1, 100, Default::default(), )?; - let stash_lookup: ::Source = T::Lookup::unlookup(stash.clone()); + let stash_lookup = T::Lookup::unlookup(stash.clone()); // they start validating. Staking::::validate(RawOrigin::Signed(controller.clone()).into(), Default::default())?; @@ -244,7 +376,7 @@ benchmarks! { for i in 0 .. k { // create a nominator stash. let (n_stash, n_controller) = create_stash_controller::( - T::MAX_NOMINATIONS + i, + T::MaxNominations::get() + i, 100, Default::default(), )?; @@ -279,21 +411,53 @@ benchmarks! { } } - // Worst case scenario, T::MAX_NOMINATIONS + // Worst case scenario, T::MaxNominations::get() nominate { - let n in 1 .. T::MAX_NOMINATIONS; - let (stash, controller) = create_stash_controller::(n + 1, 100, Default::default())?; - let validators = create_validators::(n, 100)?; + let n in 1 .. T::MaxNominations::get(); + + // clean up any existing state. + clear_validators_and_nominators::(); + + let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + + // setup a worst case list scenario. Note we don't care about the destination position, because + // we are just doing an insert into the origin position. + let scenario = ListScenario::::new(origin_weight, true)?; + let (stash, controller) = create_stash_controller_with_balance::( + SEED + T::MaxNominations::get() + 1, // make sure the account does not conflict with others + origin_weight, + Default::default(), + ).unwrap(); + + assert!(!Nominators::::contains_key(&stash)); + assert!(!T::VoterList::contains(&stash)); + + let validators = create_validators::(n, 100).unwrap(); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), validators) verify { - assert!(Nominators::::contains_key(stash)); + assert!(Nominators::::contains_key(&stash)); + assert!(T::VoterList::contains(&stash)) } chill { - let (_, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; + // clean up any existing state. + clear_validators_and_nominators::(); + + let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + + // setup a worst case list scenario. Note that we don't care about the setup of the + // destination position because we are doing a removal from the list but no insert. + let scenario = ListScenario::::new(origin_weight, true)?; + let controller = scenario.origin_controller1.clone(); + let stash = scenario.origin_stash1; + assert!(T::VoterList::contains(&stash)); + whitelist_account!(controller); }: _(RawOrigin::Signed(controller)) + verify { + assert!(!T::VoterList::contains(&stash)); + } set_payee { let (stash, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; @@ -315,7 +479,7 @@ benchmarks! { } set_validator_count { - let validator_count = MAX_VALIDATORS; + let validator_count = MaxValidators::::get(); }: _(RawOrigin::Root, validator_count) verify { assert_eq!(ValidatorCount::::get(), validator_count); @@ -332,7 +496,7 @@ benchmarks! { // Worst case scenario, the list of invulnerables is very long. set_invulnerables { - let v in 0 .. MAX_VALIDATORS; + let v in 0 .. MaxValidators::::get(); let mut invulnerables = Vec::new(); for i in 0 .. v { invulnerables.push(account("invulnerable", i, SEED)); @@ -345,19 +509,32 @@ benchmarks! { force_unstake { // Slashing Spans let s in 0 .. MAX_SPANS; - let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; + // Clean up any existing state. + clear_validators_and_nominators::(); + + let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + + // setup a worst case list scenario. Note that we don't care about the setup of the + // destination position because we are doing a removal from the list but no insert. + let scenario = ListScenario::::new(origin_weight, true)?; + let controller = scenario.origin_controller1.clone(); + let stash = scenario.origin_stash1; + assert!(T::VoterList::contains(&stash)); add_slashing_spans::(&stash, s); - }: _(RawOrigin::Root, stash, s) + + }: _(RawOrigin::Root, stash.clone(), s) verify { assert!(!Ledger::::contains_key(&controller)); + assert!(!T::VoterList::contains(&stash)); } cancel_deferred_slash { let s in 1 .. MAX_SLASHES; let mut unapplied_slashes = Vec::new(); let era = EraIndex::one(); + let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap(); for _ in 0 .. MAX_SLASHES { - unapplied_slashes.push(UnappliedSlash::>::default()); + unapplied_slashes.push(UnappliedSlash::>::default_from(dummy())); } UnappliedSlashes::::insert(era, &unapplied_slashes); @@ -368,7 +545,7 @@ benchmarks! { } payout_stakers_dead_controller { - let n in 1 .. T::MaxNominatorRewardedPerValidator::get() as u32; + let n in 0 .. T::MaxNominatorRewardedPerValidator::get() as u32; let (validator, nominators) = create_validator_with_nominators::( n, T::MaxNominatorRewardedPerValidator::get() as u32, @@ -384,10 +561,10 @@ benchmarks! { let validator_controller = >::get(&validator).unwrap(); let balance_before = T::Currency::free_balance(&validator_controller); for (_, controller) in &nominators { - let balance = T::Currency::free_balance(&controller); + let balance = T::Currency::free_balance(controller); ensure!(balance.is_zero(), "Controller has balance, but should be dead."); } - }: payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era) + }: payout_stakers(RawOrigin::Signed(caller), validator, current_era) verify { let balance_after = T::Currency::free_balance(&validator_controller); ensure!( @@ -395,13 +572,13 @@ benchmarks! { "Balance of validator controller should have increased after payout.", ); for (_, controller) in &nominators { - let balance = T::Currency::free_balance(&controller); + let balance = T::Currency::free_balance(controller); ensure!(!balance.is_zero(), "Payout not given to controller."); } } payout_stakers_alive_staked { - let n in 1 .. T::MaxNominatorRewardedPerValidator::get() as u32; + let n in 0 .. T::MaxNominatorRewardedPerValidator::get() as u32; let (validator, nominators) = create_validator_with_nominators::( n, T::MaxNominatorRewardedPerValidator::get() as u32, @@ -417,7 +594,7 @@ benchmarks! { let balance_before = T::Currency::free_balance(&validator); let mut nominator_balances_before = Vec::new(); for (stash, _) in &nominators { - let balance = T::Currency::free_balance(&stash); + let balance = T::Currency::free_balance(stash); nominator_balances_before.push(balance); } }: payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era) @@ -428,7 +605,7 @@ benchmarks! { "Balance of validator stash should have increased after payout.", ); for ((stash, _), balance_before) in nominators.iter().zip(nominator_balances_before.iter()) { - let balance_after = T::Currency::free_balance(&stash); + let balance_after = T::Currency::free_balance(stash); ensure!( balance_before < &balance_after, "Balance of nominator stash should have increased after payout.", @@ -437,69 +614,94 @@ benchmarks! { } rebond { - let l in 1 .. MAX_UNLOCKING_CHUNKS as u32; - let (_, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; - let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); + let l in 1 .. T::MaxUnlockingChunks::get() as u32; + + // clean up any existing state. + clear_validators_and_nominators::(); + + let origin_weight = MinNominatorBond::::get() + .max(T::Currency::minimum_balance()) + // we use 100 to play friendly with the list threshold values in the mock + .max(100u32.into()); + + // setup a worst case list scenario. + let scenario = ListScenario::::new(origin_weight, true)?; + let dest_weight = scenario.dest_weight; + + // rebond an amount that will give the user dest_weight + let rebond_amount = dest_weight - origin_weight; + + // spread that amount to rebond across `l` unlocking chunks, + let value = rebond_amount / l.into(); + // if `value` is zero, we need a greater delta between dest <=> origin weight + assert_ne!(value, Zero::zero()); + // so the sum of unlocking chunks puts voter into the dest bag. + assert!(value * l.into() + origin_weight > origin_weight); + assert!(value * l.into() + origin_weight <= dest_weight); let unlock_chunk = UnlockChunk::> { - value: 1u32.into(), + value, era: EraIndex::zero(), }; + + let stash = scenario.origin_stash1.clone(); + let controller = scenario.origin_controller1; + let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); + for _ in 0 .. l { - staking_ledger.unlocking.push(unlock_chunk.clone()) + staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap() } Ledger::::insert(controller.clone(), staking_ledger.clone()); let original_bonded: BalanceOf = staking_ledger.active; + whitelist_account!(controller); - }: _(RawOrigin::Signed(controller.clone()), (l + 100).into()) + }: _(RawOrigin::Signed(controller.clone()), rebond_amount) verify { let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; let new_bonded: BalanceOf = ledger.active; assert!(original_bonded < new_bonded); } - set_history_depth { - let e in 1 .. 100; - HistoryDepth::::put(e); - CurrentEra::::put(e); - for i in 0 .. e { - >::insert(i, T::AccountId::default(), Exposure::>::default()); - >::insert(i, T::AccountId::default(), Exposure::>::default()); - >::insert(i, T::AccountId::default(), ValidatorPrefs::default()); - >::insert(i, BalanceOf::::one()); - >::insert(i, EraRewardPoints::::default()); - >::insert(i, BalanceOf::::one()); - ErasStartSessionIndex::::insert(i, i); - } - }: _(RawOrigin::Root, EraIndex::zero(), u32::MAX) - verify { - assert_eq!(HistoryDepth::::get(), 0); - } - reap_stash { let s in 1 .. MAX_SPANS; - let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; - Staking::::validate(RawOrigin::Signed(controller.clone()).into(), ValidatorPrefs::default())?; + // clean up any existing state. + clear_validators_and_nominators::(); + + let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + + // setup a worst case list scenario. Note that we don't care about the setup of the + // destination position because we are doing a removal from the list but no insert. + let scenario = ListScenario::::new(origin_weight, true)?; + let controller = scenario.origin_controller1.clone(); + let stash = scenario.origin_stash1; + add_slashing_spans::(&stash, s); - T::Currency::make_free_balance_be(&stash, T::Currency::minimum_balance()); - whitelist_account!(controller); + let l = StakingLedger { + stash: stash.clone(), + active: T::Currency::minimum_balance() - One::one(), + total: T::Currency::minimum_balance() - One::one(), + unlocking: Default::default(), + claimed_rewards: Default::default(), + }; + Ledger::::insert(&controller, l); assert!(Bonded::::contains_key(&stash)); - assert!(Validators::::contains_key(&stash)); + assert!(T::VoterList::contains(&stash)); + whitelist_account!(controller); }: _(RawOrigin::Signed(controller), stash.clone(), s) verify { assert!(!Bonded::::contains_key(&stash)); - assert!(!Validators::::contains_key(&stash)); + assert!(!T::VoterList::contains(&stash)); } new_era { let v in 1 .. 10; - let n in 1 .. 100; + let n in 0 .. 100; create_validators_with_nominators_for_era::( v, n, - ::MAX_NOMINATIONS as usize, + ::MaxNominations::get() as usize, false, None, )?; @@ -513,11 +715,11 @@ benchmarks! { #[extra] payout_all { let v in 1 .. 10; - let n in 1 .. 100; + let n in 0 .. 100; create_validators_with_nominators_for_era::( v, n, - ::MAX_NOMINATIONS as usize, + ::MaxNominations::get() as usize, false, None, )?; @@ -549,15 +751,21 @@ benchmarks! { >::insert(current_era, total_payout); let caller: T::AccountId = whitelisted_caller(); + let origin = RawOrigin::Signed(caller); + let calls: Vec<_> = payout_calls_arg.iter().map(|arg| + Call::::payout_stakers { validator_stash: arg.0.clone(), era: arg.1 }.encode() + ).collect(); }: { - for arg in payout_calls_arg { - >::payout_stakers(RawOrigin::Signed(caller.clone()).into(), arg.0, arg.1)?; + for call in calls { + as Decode>::decode(&mut &*call) + .expect("call is encoded above, encoding must be correct") + .dispatch_bypass_filter(origin.clone().into())?; } } #[extra] do_slash { - let l in 1 .. MAX_UNLOCKING_CHUNKS as u32; + let l in 1 .. T::MaxUnlockingChunks::get() as u32; let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); let unlock_chunk = UnlockChunk::> { @@ -565,7 +773,7 @@ benchmarks! { era: EraIndex::zero(), }; for _ in 0 .. l { - staking_ledger.unlocking.push(unlock_chunk.clone()) + staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap(); } Ledger::::insert(controller, staking_ledger); let slash_amount = T::Currency::minimum_balance() * 10u32.into(); @@ -575,7 +783,8 @@ benchmarks! { &stash, slash_amount, &mut BalanceOf::::zero(), - &mut NegativeImbalanceOf::::zero() + &mut NegativeImbalanceOf::::zero(), + EraIndex::zero() ); } verify { let balance_after = T::Currency::free_balance(&stash); @@ -583,89 +792,159 @@ benchmarks! { } get_npos_voters { - // number of validator intention. - let v in (MAX_VALIDATORS / 2) .. MAX_VALIDATORS; - // number of nominator intention. - let n in (MAX_NOMINATORS / 2) .. MAX_NOMINATORS; - // total number of slashing spans. Assigned to validators randomly. - let s in 1 .. 20; - - let validators = create_validators_with_nominators_for_era::(v, n, T::MAX_NOMINATIONS as usize, false, None)? - .into_iter() - .map(|v| T::Lookup::lookup(v).unwrap()) - .collect::>(); - - (0..s).for_each(|index| { - add_slashing_spans::(&validators[index as usize], 10); - }); + // number of validator intention. we will iterate all of them. + let v in (MaxValidators::::get() / 2) .. MaxValidators::::get(); + // number of nominator intention. we will iterate all of them. + let n in (MaxNominators::::get() / 2) .. MaxNominators::::get(); + + let validators = create_validators_with_nominators_for_era::( + v, n, T::MaxNominations::get() as usize, false, None + )? + .into_iter() + .map(|v| T::Lookup::lookup(v).unwrap()) + .collect::>(); + + assert_eq!(Validators::::count(), v); + assert_eq!(Nominators::::count(), n); + + let num_voters = (v + n) as usize; }: { - let voters = >::get_npos_voters(); - assert_eq!(voters.len() as u32, v + n); + let voters = >::get_npos_voters(None); + assert_eq!(voters.len(), num_voters); } get_npos_targets { // number of validator intention. - let v in (MAX_VALIDATORS / 2) .. MAX_VALIDATORS; + let v in (MaxValidators::::get() / 2) .. MaxValidators::::get(); // number of nominator intention. - let n = MAX_NOMINATORS; + let n = MaxNominators::::get(); - let _ = create_validators_with_nominators_for_era::(v, n, T::MAX_NOMINATIONS as usize, false, None)?; + let _ = create_validators_with_nominators_for_era::( + v, n, T::MaxNominations::get() as usize, false, None + )?; }: { - let targets = >::get_npos_targets(); + let targets = >::get_npos_targets(None); assert_eq!(targets.len() as u32, v); } - set_staking_limits { - // This function always does the same thing... just write to 4 storage items. - }: _( + set_staking_configs_all_set { + }: set_staking_configs( RawOrigin::Root, - BalanceOf::::max_value(), - BalanceOf::::max_value(), - Some(u32::MAX), - Some(u32::MAX), - Some(Percent::max_value()) + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(Percent::max_value()), + ConfigOp::Set(Perbill::max_value()) ) verify { assert_eq!(MinNominatorBond::::get(), BalanceOf::::max_value()); assert_eq!(MinValidatorBond::::get(), BalanceOf::::max_value()); assert_eq!(MaxNominatorsCount::::get(), Some(u32::MAX)); assert_eq!(MaxValidatorsCount::::get(), Some(u32::MAX)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(100))); + assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); + } + + set_staking_configs_all_remove { + }: set_staking_configs( + RawOrigin::Root, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove + ) verify { + assert!(!MinNominatorBond::::exists()); + assert!(!MinValidatorBond::::exists()); + assert!(!MaxNominatorsCount::::exists()); + assert!(!MaxValidatorsCount::::exists()); + assert!(!ChillThreshold::::exists()); + assert!(!MinCommission::::exists()); } chill_other { - let (_, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; - Staking::::validate(RawOrigin::Signed(controller.clone()).into(), ValidatorPrefs::default())?; - Staking::::set_staking_limits( + // clean up any existing state. + clear_validators_and_nominators::(); + + let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + + // setup a worst case list scenario. Note that we don't care about the setup of the + // destination position because we are doing a removal from the list but no insert. + let scenario = ListScenario::::new(origin_weight, true)?; + let controller = scenario.origin_controller1.clone(); + let stash = scenario.origin_stash1; + assert!(T::VoterList::contains(&stash)); + + Staking::::set_staking_configs( RawOrigin::Root.into(), - BalanceOf::::max_value(), - BalanceOf::::max_value(), - Some(0), - Some(0), - Some(Percent::from_percent(0)) + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(0), + ConfigOp::Set(0), + ConfigOp::Set(Percent::from_percent(0)), + ConfigOp::Set(Zero::zero()), )?; + let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), controller.clone()) + }: _(RawOrigin::Signed(caller), controller) verify { - assert!(!Validators::::contains_key(controller)); + assert!(!T::VoterList::contains(&stash)); } + + force_apply_min_commission { + // Clean up any existing state + clear_validators_and_nominators::(); + + // Create a validator with a commission of 50% + let (stash, controller) = + create_stash_controller::(1, 1, RewardDestination::Staked)?; + let validator_prefs = + ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; + Staking::::validate(RawOrigin::Signed(controller).into(), validator_prefs)?; + + // Sanity check + assert_eq!( + Validators::::get(&stash), + ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() } + ); + + // Set the min commission to 75% + MinCommission::::set(Perbill::from_percent(75)); + let caller = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), stash.clone()) + verify { + // The validators commission has been bumped to 75% + assert_eq!( + Validators::::get(&stash), + ValidatorPrefs { commission: Perbill::from_percent(75), ..Default::default() } + ); + } + + impl_benchmark_test_suite!( + Staking, + crate::mock::ExtBuilder::default().has_stakers(true), + crate::mock::Test, + exec_name = build_and_execute + ); } #[cfg(test)] mod tests { use super::*; - use crate::mock::{Balances, ExtBuilder, Origin, Staking, Test}; + use crate::mock::{Balances, ExtBuilder, RuntimeOrigin, Staking, Test}; use frame_support::assert_ok; #[test] fn create_validators_with_nominators_for_era_works() { - ExtBuilder::default().has_stakers(true).build_and_execute(|| { + ExtBuilder::default().build_and_execute(|| { let v = 10; let n = 100; create_validators_with_nominators_for_era::( v, n, - ::MAX_NOMINATIONS as usize, + ::MaxNominations::get() as usize, false, None, ) @@ -674,6 +953,9 @@ mod tests { let count_validators = Validators::::iter().count(); let count_nominators = Nominators::::iter().count(); + assert_eq!(count_validators, Validators::::count() as usize); + assert_eq!(count_nominators, Nominators::::count() as usize); + assert_eq!(count_validators, v as usize); assert_eq!(count_nominators, n as usize); }); @@ -681,12 +963,12 @@ mod tests { #[test] fn create_validator_with_nominators_works() { - ExtBuilder::default().has_stakers(true).build_and_execute(|| { + ExtBuilder::default().build_and_execute(|| { let n = 10; let (validator_stash, nominators) = create_validator_with_nominators::( n, - ::MaxNominatorRewardedPerValidator::get() as u32, + <::MaxNominatorRewardedPerValidator as Get<_>>::get(), false, RewardDestination::Staked, ) @@ -697,7 +979,11 @@ mod tests { let current_era = CurrentEra::::get().unwrap(); let original_free_balance = Balances::free_balance(&validator_stash); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), validator_stash, current_era)); + assert_ok!(Staking::payout_stakers( + RuntimeOrigin::signed(1337), + validator_stash, + current_era + )); let new_free_balance = Balances::free_balance(&validator_stash); assert!(original_free_balance < new_free_balance); @@ -706,12 +992,12 @@ mod tests { #[test] fn add_slashing_spans_works() { - ExtBuilder::default().has_stakers(true).build_and_execute(|| { + ExtBuilder::default().build_and_execute(|| { let n = 10; let (validator_stash, _nominators) = create_validator_with_nominators::( n, - ::MaxNominatorRewardedPerValidator::get() as u32, + <::MaxNominatorRewardedPerValidator as Get<_>>::get(), false, RewardDestination::Staked, ) @@ -738,7 +1024,7 @@ mod tests { #[test] fn test_payout_all() { - ExtBuilder::default().has_stakers(true).build_and_execute(|| { + ExtBuilder::default().build_and_execute(|| { let v = 10; let n = 100; @@ -759,10 +1045,3 @@ mod tests { }); } } - -impl_benchmark_test_suite!( - Staking, - crate::mock::ExtBuilder::default().has_stakers(true), - crate::mock::Test, - exec_name = build_and_execute -); diff --git a/frame/staking/src/inflation.rs b/frame/staking/src/inflation.rs index 8e44a8c5482e5..c7519683c75d1 100644 --- a/frame/staking/src/inflation.rs +++ b/frame/staking/src/inflation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 31b35acdd99aa..0f5b8e0123ab6 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -100,6 +100,13 @@ //! //! An account can become a nominator via the [`nominate`](Call::nominate) call. //! +//! #### Voting +//! +//! Staking is closely related to elections; actual validators are chosen from among all potential +//! validators via election by the potential validators and nominators. To reduce use of the phrase +//! "potential validators and nominators", we often use the term **voters**, who are simply +//! the union of potential validators and nominators. +//! //! #### Rewards and Slash //! //! The **reward and slashing** procedure is the core of the Staking pallet, attempting to _embrace @@ -150,17 +157,25 @@ //! ### Example: Rewarding a validator by id. //! //! ``` -//! use frame_support::{decl_module, dispatch}; -//! use frame_system::ensure_signed; //! use pallet_staking::{self as staking}; //! -//! pub trait Config: staking::Config {} +//! #[frame_support::pallet] +//! pub mod pallet { +//! use super::*; +//! use frame_support::pallet_prelude::*; +//! use frame_system::pallet_prelude::*; +//! +//! #[pallet::pallet] +//! pub struct Pallet(_); //! -//! decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { +//! #[pallet::config] +//! pub trait Config: frame_system::Config + staking::Config {} +//! +//! #[pallet::call] +//! impl Pallet { //! /// Reward a validator. -//! #[weight = 0] -//! pub fn reward_myself(origin) -> dispatch::DispatchResult { +//! #[pallet::weight(0)] +//! pub fn reward_myself(origin: OriginFor) -> DispatchResult { //! let reported = ensure_signed(origin)?; //! >::reward_by_ids(vec![(reported, 10)]); //! Ok(()) @@ -206,16 +221,16 @@ //! //! The validator and its nominator split their reward as following: //! -//! The validator can declare an amount, named -//! [`commission`](ValidatorPrefs::commission), that does not get shared -//! with the nominators at each reward payout through its -//! [`ValidatorPrefs`]. This value gets deducted from the total reward -//! that is paid to the validator and its nominators. The remaining portion is split among the -//! validator and all of the nominators that nominated the validator, proportional to the value -//! staked behind this validator (_i.e._ dividing the -//! [`own`](Exposure::own) or -//! [`others`](Exposure::others) by -//! [`total`](Exposure::total) in [`Exposure`]). +//! The validator can declare an amount, named [`commission`](ValidatorPrefs::commission), that does +//! not get shared with the nominators at each reward payout through its [`ValidatorPrefs`]. This +//! value gets deducted from the total reward that is paid to the validator and its nominators. The +//! remaining portion is split pro rata among the validator and the top +//! [`Config::MaxNominatorRewardedPerValidator`] nominators that nominated the validator, +//! proportional to the value staked behind the validator (_i.e._ dividing the +//! [`own`](Exposure::own) or [`others`](Exposure::others) by [`total`](Exposure::total) in +//! [`Exposure`]). Note that the pro rata division of rewards uses the total exposure behind the +//! validator, *not* just the exposure of the validator and the top +//! [`Config::MaxNominatorRewardedPerValidator`] nominators. //! //! All entities who receive a reward have the option to choose their reward destination through the //! [`Payee`] storage item (see @@ -264,15 +279,16 @@ //! - [Session](../pallet_session/index.html): Used to manage sessions. Also, a list of new //! validators is stored in the Session pallet's `Validators` at the end of each era. -#![recursion_limit = "128"] #![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "256"] #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; -#[cfg(test)] -mod mock; #[cfg(any(feature = "runtime-benchmarks", test))] pub mod testing_utils; + +#[cfg(test)] +pub(crate) mod mock; #[cfg(test)] mod tests; @@ -283,27 +299,28 @@ pub mod weights; mod pallet; -use codec::{Decode, Encode, HasCompact}; +use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use frame_support::{ - traits::{Currency, Get}, + traits::{Currency, Defensive, Get}, weights::Weight, + BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_runtime::{ curve::PiecewiseLinear, - traits::{AtLeast32BitUnsigned, Convert, Saturating, Zero}, - Perbill, RuntimeDebug, + traits::{AtLeast32BitUnsigned, Convert, Saturating, StaticLookup, Zero}, + Perbill, Perquintill, Rounding, RuntimeDebug, }; use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, - SessionIndex, + EraIndex, SessionIndex, }; -use sp_std::{collections::btree_map::BTreeMap, convert::From, prelude::*}; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; pub use weights::WeightInfo; pub use pallet::{pallet::*, *}; -pub(crate) const LOG_TARGET: &'static str = "runtime::staking"; +pub(crate) const LOG_TARGET: &str = "runtime::staking"; // syntactic sugar for logging. #[macro_export] @@ -316,15 +333,15 @@ macro_rules! log { }; } -/// Counter for the number of eras that have passed. -pub type EraIndex = u32; +/// Maximum number of winners (aka. active validators), as defined in the election provider of this +/// pallet. +pub type MaxWinnersOf = <::ElectionProvider as frame_election_provider_support::ElectionProviderBase>::MaxWinners; /// Counter for the number of "reward" points earned by a given validator. pub type RewardPoint = u32; /// The balance type of this pallet. -pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; +pub type BalanceOf = ::CurrencyBalance; type PositiveImbalanceOf = <::Currency as Currency< ::AccountId, @@ -333,8 +350,10 @@ type NegativeImbalanceOf = <::Currency as Currency< ::AccountId, >>::NegativeImbalance; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + /// Information regarding the active era (era in used in session). -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct ActiveEraInfo { /// Index of era. pub index: EraIndex, @@ -348,17 +367,23 @@ pub struct ActiveEraInfo { /// Reward points of an era. Used to split era total payout between validators. /// /// This points will be used to reward validators and their respective nominators. -#[derive(PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct EraRewardPoints { /// Total number of points. Equals the sum of reward points for each validator. - total: RewardPoint, + pub total: RewardPoint, /// The reward points earned by a given validator. - individual: BTreeMap, + pub individual: BTreeMap, +} + +impl Default for EraRewardPoints { + fn default() -> Self { + EraRewardPoints { total: Default::default(), individual: BTreeMap::new() } + } } /// Indicates the initial status of the staker. #[derive(RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Clone))] pub enum StakerStatus { /// Chilling. Idle, @@ -369,7 +394,7 @@ pub enum StakerStatus { } /// A destination account for payment. -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum RewardDestination { /// Pay into the stash account, increasing the amount at stake accordingly. Staked, @@ -390,7 +415,7 @@ impl Default for RewardDestination { } /// Preference of what happens regarding validation. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Default, MaxEncodedLen)] pub struct ValidatorPrefs { /// Reward that validator takes up-front; only the rest is split between themselves and /// nominators. @@ -402,15 +427,9 @@ pub struct ValidatorPrefs { pub blocked: bool, } -impl Default for ValidatorPrefs { - fn default() -> Self { - ValidatorPrefs { commission: Default::default(), blocked: false } - } -} - /// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct UnlockChunk { +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct UnlockChunk { /// Amount of funds to be unlocked. #[codec(compact)] value: Balance, @@ -420,34 +439,54 @@ pub struct UnlockChunk { } /// The ledger of a (bonded) stash. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct StakingLedger { +#[derive( + PartialEqNoBound, + EqNoBound, + CloneNoBound, + Encode, + Decode, + RuntimeDebugNoBound, + TypeInfo, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(T))] +pub struct StakingLedger { /// The stash account whose balance is actually locked and at stake. - pub stash: AccountId, + pub stash: T::AccountId, /// The total amount of the stash's balance that we are currently accounting for. /// It's just `active` plus all the `unlocking` balances. #[codec(compact)] - pub total: Balance, + pub total: BalanceOf, /// The total amount of the stash's balance that will be at stake in any forthcoming /// rounds. #[codec(compact)] - pub active: Balance, - /// Any balance that is becoming free, which may eventually be transferred out - /// of the stash (assuming it doesn't get slashed first). - pub unlocking: Vec>, + pub active: BalanceOf, + /// Any balance that is becoming free, which may eventually be transferred out of the stash + /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first + /// in, first out queue where the new (higher value) eras get pushed on the back. + pub unlocking: BoundedVec>, T::MaxUnlockingChunks>, /// List of eras for which the stakers behind a validator have claimed rewards. Only updated /// for validators. - pub claimed_rewards: Vec, + pub claimed_rewards: BoundedVec, } -impl - StakingLedger -{ +impl StakingLedger { + /// Initializes the default object using the given `validator`. + pub fn default_from(stash: T::AccountId) -> Self { + Self { + stash, + total: Zero::zero(), + active: Zero::zero(), + unlocking: Default::default(), + claimed_rewards: Default::default(), + } + } + /// Remove entries from `unlocking` that are sufficiently old and reduce the /// total by the sum of their balances. fn consolidate_unlocked(self, current_era: EraIndex) -> Self { let mut total = self.total; - let unlocking = self + let unlocking: BoundedVec<_, _> = self .unlocking .into_iter() .filter(|chunk| { @@ -458,7 +497,11 @@ impl false } }) - .collect(); + .collect::>() + .try_into() + .expect( + "filtering items from a bounded vec always leaves length less than bounds. qed", + ); Self { stash: self.stash, @@ -470,8 +513,10 @@ impl } /// Re-bond funds that were scheduled for unlocking. - fn rebond(mut self, value: Balance) -> Self { - let mut unlocking_balance: Balance = Zero::zero(); + /// + /// Returns the updated ledger, and the amount actually rebonded. + fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { + let mut unlocking_balance = BalanceOf::::zero(); while let Some(last) = self.unlocking.last_mut() { if unlocking_balance + last.value <= value { @@ -491,67 +536,162 @@ impl } } - self + (self, unlocking_balance) } -} -impl StakingLedger -where - Balance: AtLeast32BitUnsigned + Saturating + Copy, -{ - /// Slash the validator for a given amount of balance. This can grow the value - /// of the slash in the case that the validator has less than `minimum_balance` - /// active funds. Returns the amount of funds actually slashed. + /// Slash the staker for a given amount of balance. /// - /// Slashes from `active` funds first, and then `unlocking`, starting with the - /// chunks that are closest to unlocking. - fn slash(&mut self, mut value: Balance, minimum_balance: Balance) -> Balance { - let pre_total = self.total; - let total = &mut self.total; - let active = &mut self.active; - - let slash_out_of = - |total_remaining: &mut Balance, target: &mut Balance, value: &mut Balance| { - let mut slash_from_target = (*value).min(*target); - - if !slash_from_target.is_zero() { - *target -= slash_from_target; - - // Don't leave a dust balance in the staking system. - if *target <= minimum_balance { - slash_from_target += *target; - *value += sp_std::mem::replace(target, Zero::zero()); - } - - *total_remaining = total_remaining.saturating_sub(slash_from_target); - *value -= slash_from_target; - } - }; + /// This implements a proportional slashing system, whereby we set our preference to slash as + /// such: + /// + /// - If any unlocking chunks exist that are scheduled to be unlocked at `slash_era + + /// bonding_duration` and onwards, the slash is divided equally between the active ledger and + /// the unlocking chunks. + /// - If no such chunks exist, then only the active balance is slashed. + /// + /// Note that the above is only a *preference*. If for any reason the active ledger, with or + /// without some portion of the unlocking chunks that are more justified to be slashed are not + /// enough, then the slashing will continue and will consume as much of the active and unlocking + /// chunks as needed. + /// + /// This will never slash more than the given amount. If any of the chunks become dusted, the + /// last chunk is slashed slightly less to compensate. Returns the amount of funds actually + /// slashed. + /// + /// `slash_era` is the era in which the slash (which is being enacted now) actually happened. + /// + /// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash was + /// applied. + pub fn slash( + &mut self, + slash_amount: BalanceOf, + minimum_balance: BalanceOf, + slash_era: EraIndex, + ) -> BalanceOf { + if slash_amount.is_zero() { + return Zero::zero() + } - slash_out_of(total, active, &mut value); + use sp_runtime::PerThing as _; + use sp_staking::OnStakerSlash as _; + let mut remaining_slash = slash_amount; + let pre_slash_total = self.total; + + // for a `slash_era = x`, any chunk that is scheduled to be unlocked at era `x + 28` + // (assuming 28 is the bonding duration) onwards should be slashed. + let slashable_chunks_start = slash_era + T::BondingDuration::get(); + + // `Some(ratio)` if this is proportional, with `ratio`, `None` otherwise. In both cases, we + // slash first the active chunk, and then `slash_chunks_priority`. + let (maybe_proportional, slash_chunks_priority) = { + if let Some(first_slashable_index) = + self.unlocking.iter().position(|c| c.era >= slashable_chunks_start) + { + // If there exists a chunk who's after the first_slashable_start, then this is a + // proportional slash, because we want to slash active and these chunks + // proportionally. + + // The indices of the first chunk after the slash up through the most recent chunk. + // (The most recent chunk is at greatest from this era) + let affected_indices = first_slashable_index..self.unlocking.len(); + let unbonding_affected_balance = + affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { + if let Some(chunk) = self.unlocking.get(i).defensive() { + sum.saturating_add(chunk.value) + } else { + sum + } + }); + let affected_balance = self.active.saturating_add(unbonding_affected_balance); + let ratio = Perquintill::from_rational_with_rounding( + slash_amount, + affected_balance, + Rounding::Up, + ) + .unwrap_or_else(|_| Perquintill::one()); + ( + Some(ratio), + affected_indices.chain((0..first_slashable_index).rev()).collect::>(), + ) + } else { + // We just slash from the last chunk to the most recent one, if need be. + (None, (0..self.unlocking.len()).rev().collect::>()) + } + }; + + // Helper to update `target` and the ledgers total after accounting for slashing `target`. + log!( + debug, + "slashing {:?} for era {:?} out of {:?}, priority: {:?}, proportional = {:?}", + slash_amount, + slash_era, + self, + slash_chunks_priority, + maybe_proportional, + ); - let i = self - .unlocking - .iter_mut() - .map(|chunk| { - slash_out_of(total, &mut chunk.value, &mut value); - chunk.value - }) - .take_while(|value| value.is_zero()) // Take all fully-consumed chunks out. - .count(); + let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { + let mut slash_from_target = if let Some(ratio) = maybe_proportional { + ratio.mul_ceil(*target) + } else { + *slash_remaining + } + // this is the total that that the slash target has. We can't slash more than + // this anyhow! + .min(*target) + // this is the total amount that we would have wanted to slash + // non-proportionally, a proportional slash should never exceed this either! + .min(*slash_remaining); + + // slash out from *target exactly `slash_from_target`. + *target = *target - slash_from_target; + if *target < minimum_balance { + // Slash the rest of the target if it's dust. This might cause the last chunk to be + // slightly under-slashed, by at most `MaxUnlockingChunks * ED`, which is not a big + // deal. + slash_from_target = + sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) + } + + self.total = self.total.saturating_sub(slash_from_target); + *slash_remaining = slash_remaining.saturating_sub(slash_from_target); + }; + + // If this is *not* a proportional slash, the active will always wiped to 0. + slash_out_of(&mut self.active, &mut remaining_slash); + + let mut slashed_unlocking = BTreeMap::<_, _>::new(); + for i in slash_chunks_priority { + if remaining_slash.is_zero() { + break + } + + if let Some(chunk) = self.unlocking.get_mut(i).defensive() { + slash_out_of(&mut chunk.value, &mut remaining_slash); + // write the new slashed value of this chunk to the map. + slashed_unlocking.insert(chunk.era, chunk.value); + } else { + break + } + } - // Kill all drained chunks. - let _ = self.unlocking.drain(..i); + // clean unlocking chunks that are set to zero. + self.unlocking.retain(|c| !c.value.is_zero()); - pre_total.saturating_sub(*total) + T::OnStakerSlash::on_slash(&self.stash, self.active, &slashed_unlocking); + pre_slash_total.saturating_sub(self.total) } } /// A record of the nominations made by a specific account. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct Nominations { +#[derive( + PartialEqNoBound, EqNoBound, Clone, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen, +)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T))] +pub struct Nominations { /// The targets of nomination. - pub targets: Vec, + pub targets: BoundedVec, /// The era the nominations were submitted. /// /// Except for initial nominations which are considered submitted at era 0. @@ -574,9 +714,7 @@ pub struct IndividualExposure { } /// A snapshot of the stake backing a single validator in the system. -#[derive( - PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, RuntimeDebug, TypeInfo, -)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct Exposure { /// The total balance backing this validator. #[codec(compact)] @@ -588,9 +726,15 @@ pub struct Exposure { pub others: Vec>, } +impl Default for Exposure { + fn default() -> Self { + Self { total: Default::default(), own: Default::default(), others: vec![] } + } +} + /// A pending slash record. The value of the slash has been computed but not applied yet, /// rather deferred for several eras. -#[derive(Encode, Decode, Default, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] pub struct UnappliedSlash { /// The stash ID of the offending validator. validator: AccountId, @@ -604,16 +748,26 @@ pub struct UnappliedSlash { payout: Balance, } +impl UnappliedSlash { + /// Initializes the default object using the given `validator`. + pub fn default_from(validator: AccountId) -> Self { + Self { + validator, + own: Zero::zero(), + others: vec![], + reporters: vec![], + payout: Zero::zero(), + } + } +} + /// Means for interacting with a specialized version of the `session` trait. /// /// This is needed because `Staking` sets the `ValidatorIdOf` of the `pallet_session::Config` -pub trait SessionInterface: frame_system::Config { - /// Disable a given validator by stash ID. - /// - /// Returns `true` if new era should be forced at the end of this session. - /// This allows preventing a situation where there is too many validators - /// disabled and block production stalls. - fn disable_validator(validator: &AccountId) -> Result; +pub trait SessionInterface { + /// Disable the validator at the given index, returns `false` if the validator was already + /// disabled or the index is out of bounds. + fn disable_validator(validator_index: u32) -> bool; /// Get the validators from session. fn validators() -> Vec; /// Prune historical session tries up to but not including the given index. @@ -634,8 +788,8 @@ where Option<::AccountId>, >, { - fn disable_validator(validator: &::AccountId) -> Result { - >::disable(validator) + fn disable_validator(validator_index: u32) -> bool { + >::disable_index(validator_index) } fn validators() -> Vec<::AccountId> { @@ -647,6 +801,18 @@ where } } +impl SessionInterface for () { + fn disable_validator(_: u32) -> bool { + true + } + fn validators() -> Vec { + Vec::new() + } + fn prune_historical_up_to(_: SessionIndex) { + () + } +} + /// Handler for determining how much of a balance should be paid out on the current era. pub trait EraPayout { /// Determine the payout for this era. @@ -682,7 +848,7 @@ impl (Balance, Balance) { let (validator_payout, max_payout) = inflation::compute_total_payout( - &T::get(), + T::get(), total_staked, total_issuance, // Duration of era; more than u64::MAX is rewarded as u64::MAX. @@ -694,7 +860,7 @@ impl VoterBagsList + V12_0_0, // remove `HistoryDepth`. } impl Default for Releases { fn default() -> Self { - Releases::V7_0_0 + Releases::V11_0_0 } } @@ -781,7 +952,9 @@ where if bonded_eras.first().filter(|(_, start)| offence_session >= *start).is_some() { R::report_offence(reporters, offence) } else { - >::deposit_event(Event::::OldSlashingReportDiscarded(offence_session)); + >::deposit_event(Event::::OldSlashingReportDiscarded { + session_index: offence_session, + }); Ok(()) } } @@ -790,3 +963,23 @@ where R::is_known_offence(offenders, time_slot) } } + +/// Configurations of the benchmarking of the pallet. +pub trait BenchmarkingConfig { + /// The maximum number of validators to use. + type MaxValidators: Get; + /// The maximum number of nominators to use. + type MaxNominators: Get; +} + +/// A mock benchmarking config for pallet-staking. +/// +/// Should only be used for testing. +#[cfg(feature = "std")] +pub struct TestBenchmarkingConfig; + +#[cfg(feature = "std")] +impl BenchmarkingConfig for TestBenchmarkingConfig { + type MaxValidators = frame_support::traits::ConstU32<100>; + type MaxNominators = frame_support::traits::ConstU32<100>; +} diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index d7fa2afc63082..f2ccb4f8b096f 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,337 @@ //! Storage migrations for the Staking pallet. use super::*; +use frame_election_provider_support::SortedListProvider; +use frame_support::traits::OnRuntimeUpgrade; + +pub mod v12 { + use super::*; + use frame_support::{pallet_prelude::ValueQuery, storage_alias}; + + #[storage_alias] + type HistoryDepth = StorageValue, u32, ValueQuery>; + + /// Clean up `HistoryDepth` from storage. + /// + /// We will be depending on the configurable value of `HistoryDepth` post + /// this release. + pub struct MigrateToV12(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV12 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + frame_support::ensure!( + StorageVersion::::get() == Releases::V11_0_0, + "Expected v11 before upgrading to v12" + ); + + if HistoryDepth::::exists() { + frame_support::ensure!( + T::HistoryDepth::get() == HistoryDepth::::get(), + "Provided value of HistoryDepth should be same as the existing storage value" + ); + } else { + log::info!("No HistoryDepth in storage; nothing to remove"); + } + + Ok(Default::default()) + } + + fn on_runtime_upgrade() -> frame_support::weights::Weight { + if StorageVersion::::get() == Releases::V11_0_0 { + HistoryDepth::::kill(); + StorageVersion::::put(Releases::V12_0_0); + + log!(info, "v12 applied successfully"); + T::DbWeight::get().reads_writes(1, 2) + } else { + log!(warn, "Skipping v12, should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V12_0_0, + "v12 not applied" + ); + Ok(()) + } + } +} + +pub mod v11 { + use super::*; + use frame_support::{ + storage::migration::move_pallet, + traits::{GetStorageVersion, PalletInfoAccess}, + }; + #[cfg(feature = "try-runtime")] + use sp_io::hashing::twox_128; + + pub struct MigrateToV11(sp_std::marker::PhantomData<(T, P, N)>); + impl> OnRuntimeUpgrade + for MigrateToV11 + { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V10_0_0, + "must upgrade linearly" + ); + let old_pallet_prefix = twox_128(N::get().as_bytes()); + + frame_support::ensure!( + sp_io::storage::next_key(&old_pallet_prefix).is_some(), + "no data for the old pallet name has been detected" + ); + + Ok(Default::default()) + } + + /// Migrate the entire storage of this pallet to a new prefix. + /// + /// This new prefix must be the same as the one set in construct_runtime. For safety, use + /// `PalletInfo` to get it, as: + /// `::PalletInfo::name::`. + /// + /// The migration will look into the storage version in order to avoid triggering a + /// migration on an up to date storage. + fn on_runtime_upgrade() -> Weight { + let old_pallet_name = N::get(); + let new_pallet_name =

::name(); + + if StorageVersion::::get() == Releases::V10_0_0 { + // bump version anyway, even if we don't need to move the prefix + StorageVersion::::put(Releases::V11_0_0); + if new_pallet_name == old_pallet_name { + log!( + warn, + "new bags-list name is equal to the old one, only bumping the version" + ); + return T::DbWeight::get().reads(1).saturating_add(T::DbWeight::get().writes(1)) + } + + move_pallet(old_pallet_name.as_bytes(), new_pallet_name.as_bytes()); + ::BlockWeights::get().max_block + } else { + log!(warn, "v11::migrate should be removed."); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V11_0_0, + "wrong version after the upgrade" + ); + + let old_pallet_name = N::get(); + let new_pallet_name =

::name(); + + // skip storage prefix checks for the same pallet names + if new_pallet_name == old_pallet_name { + return Ok(()) + } + + let old_pallet_prefix = twox_128(N::get().as_bytes()); + frame_support::ensure!( + sp_io::storage::next_key(&old_pallet_prefix).is_none(), + "old pallet data hasn't been removed" + ); + + let new_pallet_name =

::name(); + let new_pallet_prefix = twox_128(new_pallet_name.as_bytes()); + frame_support::ensure!( + sp_io::storage::next_key(&new_pallet_prefix).is_some(), + "new pallet data hasn't been created" + ); + + Ok(()) + } + } +} + +pub mod v10 { + use super::*; + use frame_support::storage_alias; + + #[storage_alias] + type EarliestUnappliedSlash = StorageValue, EraIndex>; + + /// Apply any pending slashes that where queued. + /// + /// That means we might slash someone a bit too early, but we will definitely + /// won't forget to slash them. The cap of 512 is somewhat randomly taken to + /// prevent us from iterating over an arbitrary large number of keys `on_runtime_upgrade`. + pub struct MigrateToV10(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV10 { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + if StorageVersion::::get() == Releases::V9_0_0 { + let pending_slashes = as Store>::UnappliedSlashes::iter().take(512); + for (era, slashes) in pending_slashes { + for slash in slashes { + // in the old slashing scheme, the slash era was the key at which we read + // from `UnappliedSlashes`. + log!(warn, "prematurely applying a slash ({:?}) for era {:?}", slash, era); + slashing::apply_slash::(slash, era); + } + } + + EarliestUnappliedSlash::::kill(); + StorageVersion::::put(Releases::V10_0_0); + + log!(info, "MigrateToV10 executed successfully"); + T::DbWeight::get().reads_writes(1, 1) + } else { + log!(warn, "MigrateToV10 should be removed."); + T::DbWeight::get().reads(1) + } + } + } +} + +pub mod v9 { + use super::*; + #[cfg(feature = "try-runtime")] + use frame_support::codec::{Decode, Encode}; + #[cfg(feature = "try-runtime")] + use sp_std::vec::Vec; + + /// Migration implementation that injects all validators into sorted list. + /// + /// This is only useful for chains that started their `VoterList` just based on nominators. + pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { + fn on_runtime_upgrade() -> Weight { + if StorageVersion::::get() == Releases::V8_0_0 { + let prev_count = T::VoterList::count(); + let weight_of_cached = Pallet::::weight_of_fn(); + for (v, _) in Validators::::iter() { + let weight = weight_of_cached(&v); + let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { + log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) + }); + } + + log!( + info, + "injected a total of {} new voters, prev count: {} next count: {}, updating to version 9", + Validators::::count(), + prev_count, + T::VoterList::count(), + ); + + StorageVersion::::put(crate::Releases::V9_0_0); + T::BlockWeights::get().max_block + } else { + log!( + warn, + "InjectValidatorsIntoVoterList being executed on the wrong storage \ + version, expected Releases::V8_0_0" + ); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V8_0_0, + "must upgrade linearly" + ); + + let prev_count = T::VoterList::count(); + Ok(prev_count.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev_count: Vec) -> Result<(), &'static str> { + let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( + "the state parameter should be something that was generated by pre_upgrade", + ); + let post_count = T::VoterList::count(); + let validators = Validators::::count(); + assert!(post_count == prev_count + validators); + + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V9_0_0, + "must upgrade " + ); + Ok(()) + } + } +} + +pub mod v8 { + use crate::{Config, Nominators, Pallet, StorageVersion, Weight}; + use frame_election_provider_support::SortedListProvider; + use frame_support::traits::Get; + + #[cfg(feature = "try-runtime")] + pub fn pre_migrate() -> Result<(), &'static str> { + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V7_0_0, + "must upgrade linearly" + ); + + crate::log!(info, "👜 staking bags-list migration passes PRE migrate checks ✅",); + Ok(()) + } + + /// Migration to sorted `VoterList`. + pub fn migrate() -> Weight { + if StorageVersion::::get() == crate::Releases::V7_0_0 { + crate::log!(info, "migrating staking to Releases::V8_0_0"); + + let migrated = T::VoterList::unsafe_regenerate( + Nominators::::iter().map(|(id, _)| id), + Pallet::::weight_of_fn(), + ); + debug_assert_eq!(T::VoterList::try_state(), Ok(())); + + StorageVersion::::put(crate::Releases::V8_0_0); + crate::log!( + info, + "👜 completed staking migration to Releases::V8_0_0 with {} voters migrated", + migrated, + ); + + T::BlockWeights::get().max_block + } else { + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + pub fn post_migrate() -> Result<(), &'static str> { + T::VoterList::try_state().map_err(|_| "VoterList is not in a sane state.")?; + crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",); + Ok(()) + } +} pub mod v7 { use super::*; + use frame_support::storage_alias; + + #[storage_alias] + type CounterForValidators = StorageValue, u32>; + #[storage_alias] + type CounterForNominators = StorageValue, u32>; pub fn pre_migrate() -> Result<(), &'static str> { - assert!(CounterForValidators::::get().is_zero(), "CounterForValidators already set."); - assert!(CounterForNominators::::get().is_zero(), "CounterForNominators already set."); + assert!( + CounterForValidators::::get().unwrap().is_zero(), + "CounterForValidators already set." + ); + assert!( + CounterForNominators::::get().unwrap().is_zero(), + "CounterForNominators already set." + ); + assert!(Validators::::count().is_zero(), "Validators already set."); + assert!(Nominators::::count().is_zero(), "Nominators already set."); assert!(StorageVersion::::get() == Releases::V6_0_0); Ok(()) } @@ -45,26 +369,35 @@ pub mod v7 { pub mod v6 { use super::*; - use frame_support::{generate_storage_alias, traits::Get, weights::Weight}; + use frame_support::{storage_alias, traits::Get, weights::Weight}; // NOTE: value type doesn't matter, we just set it to () here. - generate_storage_alias!(Staking, SnapshotValidators => Value<()>); - generate_storage_alias!(Staking, SnapshotNominators => Value<()>); - generate_storage_alias!(Staking, QueuedElected => Value<()>); - generate_storage_alias!(Staking, QueuedScore => Value<()>); - generate_storage_alias!(Staking, EraElectionStatus => Value<()>); - generate_storage_alias!(Staking, IsCurrentSessionFinal => Value<()>); + #[storage_alias] + type SnapshotValidators = StorageValue, ()>; + #[storage_alias] + type SnapshotNominators = StorageValue, ()>; + #[storage_alias] + type QueuedElected = StorageValue, ()>; + #[storage_alias] + type QueuedScore = StorageValue, ()>; + #[storage_alias] + type EraElectionStatus = StorageValue, ()>; + #[storage_alias] + type IsCurrentSessionFinal = StorageValue, ()>; /// check to execute prior to migration. pub fn pre_migrate() -> Result<(), &'static str> { // these may or may not exist. - log!(info, "SnapshotValidators.exits()? {:?}", SnapshotValidators::exists()); - log!(info, "SnapshotNominators.exits()? {:?}", SnapshotNominators::exists()); - log!(info, "QueuedElected.exits()? {:?}", QueuedElected::exists()); - log!(info, "QueuedScore.exits()? {:?}", QueuedScore::exists()); + log!(info, "SnapshotValidators.exits()? {:?}", SnapshotValidators::::exists()); + log!(info, "SnapshotNominators.exits()? {:?}", SnapshotNominators::::exists()); + log!(info, "QueuedElected.exits()? {:?}", QueuedElected::::exists()); + log!(info, "QueuedScore.exits()? {:?}", QueuedScore::::exists()); // these must exist. - assert!(IsCurrentSessionFinal::exists(), "IsCurrentSessionFinal storage item not found!"); - assert!(EraElectionStatus::exists(), "EraElectionStatus storage item not found!"); + assert!( + IsCurrentSessionFinal::::exists(), + "IsCurrentSessionFinal storage item not found!" + ); + assert!(EraElectionStatus::::exists(), "EraElectionStatus storage item not found!"); Ok(()) } @@ -72,12 +405,12 @@ pub mod v6 { pub fn migrate() -> Weight { log!(info, "Migrating staking to Releases::V6_0_0"); - SnapshotValidators::kill(); - SnapshotNominators::kill(); - QueuedElected::kill(); - QueuedScore::kill(); - EraElectionStatus::kill(); - IsCurrentSessionFinal::kill(); + SnapshotValidators::::kill(); + SnapshotNominators::::kill(); + QueuedElected::::kill(); + QueuedScore::::kill(); + EraElectionStatus::::kill(); + IsCurrentSessionFinal::::kill(); StorageVersion::::put(Releases::V6_0_0); log!(info, "Done."); diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 0357fa05cb1dd..d3affda05277a 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,14 +17,13 @@ //! Test utilities -use crate as staking; -use crate::*; -use frame_election_provider_support::onchain; +use crate::{self as pallet_staking, *}; +use frame_election_provider_support::{onchain, SequentialPhragmen, VoteWeight}; use frame_support::{ assert_ok, parameter_types, traits::{ - Currency, FindAuthor, GenesisBuild, Get, Hooks, Imbalance, OnInitialize, OnUnbalanced, - OneSessionHandler, + ConstU32, ConstU64, Currency, FindAuthor, GenesisBuild, Get, Hooks, Imbalance, + OnUnbalanced, OneSessionHandler, }, weights::constants::RocksDbWeight, }; @@ -32,11 +31,10 @@ use sp_core::H256; use sp_io; use sp_runtime::{ curve::PiecewiseLinear, - testing::{Header, TestXt, UintAuthorityId}, + testing::{Header, UintAuthorityId}, traits::{IdentityLookup, Zero}, }; -use sp_staking::offence::{OffenceDetails, OnOffenceHandler}; -use std::{cell::RefCell, collections::HashSet}; +use sp_staking::offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}; pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; @@ -47,10 +45,6 @@ pub(crate) type AccountIndex = u64; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; -thread_local! { - static SESSION: RefCell<(Vec, HashSet)> = RefCell::new(Default::default()); -} - /// Another session handler struct to test on_disabled. pub struct OtherSessionHandler; impl OneSessionHandler for OtherSessionHandler { @@ -63,23 +57,14 @@ impl OneSessionHandler for OtherSessionHandler { { } - fn on_new_session<'a, I: 'a>(_: bool, validators: I, _: I) + fn on_new_session<'a, I: 'a>(_: bool, _: I, _: I) where I: Iterator, AccountId: 'a, { - SESSION.with(|x| { - *x.borrow_mut() = (validators.map(|x| x.0.clone()).collect(), HashSet::new()) - }); } - fn on_disabled(validator_index: usize) { - SESSION.with(|d| { - let mut d = d.borrow_mut(); - let value = d.0[validator_index]; - d.1.insert(value); - }) - } + fn on_disabled(_validator_index: u32) {} } impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { @@ -88,7 +73,12 @@ impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { pub fn is_disabled(controller: AccountId) -> bool { let stash = Staking::ledger(&controller).unwrap().stash; - SESSION.with(|d| d.borrow().1.contains(&stash)) + let validator_index = match Session::validators().iter().position(|v| *v == stash) { + Some(index) => index as u32, + None => return false, + }; + + Session::disabled_validators().contains(&validator_index) } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -104,8 +94,10 @@ frame_support::construct_runtime!( Authorship: pallet_authorship::{Pallet, Call, Storage, Inherent}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Staking: staking::{Pallet, Call, Config, Storage, Event}, + Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, + Historical: pallet_session::historical::{Pallet, Storage}, + VoterBagsList: pallet_bags_list::::{Pallet, Call, Storage, Event}, } ); @@ -121,12 +113,10 @@ impl FindAuthor for Author11 { } parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - frame_support::weights::constants::WEIGHT_PER_SECOND * 2 + Weight::from_parts(frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND * 2, u64::MAX), ); - pub const MaxLocks: u32 = 1024; pub static SessionsPerEra: SessionIndex = 3; pub static ExistentialDeposit: Balance = 1; pub static SlashDeferDuration: EraIndex = 0; @@ -139,17 +129,17 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = RocksDbWeight; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = AccountIndex; type BlockNumber = BlockNumber; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -158,22 +148,20 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { - type MaxLocks = MaxLocks; + type MaxLocks = frame_support::traits::ConstU32<1024>; type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = Balance; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); } -parameter_types! { - pub const UncleGenerations: u64 = 0; - pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(25); -} + sp_runtime::impl_opaque_keys! { pub struct SessionKeys { pub other: OtherSessionHandler, @@ -184,10 +172,9 @@ impl pallet_session::Config for Test { type Keys = SessionKeys; type ShouldEndSession = pallet_session::PeriodicSessions; type SessionHandler = (OtherSessionHandler,); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ValidatorId = AccountId; type ValidatorIdOf = crate::StashOf; - type DisabledValidatorsThreshold = DisabledValidatorsThreshold; type NextSessionRotation = pallet_session::PeriodicSessions; type WeightInfo = (); } @@ -198,19 +185,18 @@ impl pallet_session::historical::Config for Test { } impl pallet_authorship::Config for Test { type FindAuthor = Author11; - type UncleGenerations = UncleGenerations; + type UncleGenerations = ConstU64<0>; type FilterUncle = (); type EventHandler = Pallet; } -parameter_types! { - pub const MinimumPeriod: u64 = 5; -} + impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<5>; type WeightInfo = (); } + pallet_staking_reward_curve::build! { const I_NPOS: PiecewiseLinear<'static> = curve!( min_inflation: 0_025_000, @@ -224,38 +210,86 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const BondingDuration: EraIndex = 3; pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS; - pub const MaxNominatorRewardedPerValidator: u32 = 64; + pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(75); } -thread_local! { - pub static REWARD_REMAINDER_UNBALANCED: RefCell = RefCell::new(0); +parameter_types! { + pub static RewardRemainderUnbalanced: u128 = 0; } pub struct RewardRemainderMock; impl OnUnbalanced> for RewardRemainderMock { fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { - REWARD_REMAINDER_UNBALANCED.with(|v| { - *v.borrow_mut() += amount.peek(); + RewardRemainderUnbalanced::mutate(|v| { + *v += amount.peek(); }); drop(amount); } } -impl onchain::Config for Test { - type Accuracy = Perbill; +const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = + [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; + +parameter_types! { + pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; + pub static MaxNominations: u32 = 16; + pub static HistoryDepth: u32 = 80; + pub static MaxUnlockingChunks: u32 = 32; + pub static RewardOnUnbalanceWasCalled: bool = false; + pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); + pub static MaxWinners: u32 = 100; +} + +type VoterBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + // Staking is the source of truth for voter bags list, since they are not kept up to date. + type ScoreProvider = Staking; + type BagThresholds = BagThresholds; + type Score = VoteWeight; +} + +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = MaxWinners; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; +} + +pub struct MockReward {} +impl OnUnbalanced> for MockReward { + fn on_unbalanced(_: PositiveImbalanceOf) { + RewardOnUnbalanceWasCalled::set(true); + } +} + +pub struct OnStakerSlashMock(core::marker::PhantomData); +impl sp_staking::OnStakerSlash for OnStakerSlashMock { + fn on_slash( + _pool_account: &AccountId, + slashed_bonded: Balance, + slashed_chunks: &BTreeMap, + ) { + LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone())); + } } -impl Config for Test { - const MAX_NOMINATIONS: u32 = 16; +impl crate::pallet::pallet::Config for Test { + type MaxNominations = MaxNominations; type Currency = Balances; + type CurrencyBalance = ::Balance; type UnixTime = Timestamp; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type RewardRemainder = RewardRemainderMock; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Slash = (); - type Reward = (); + type Reward = MockReward; type SessionsPerEra = SessionsPerEra; type SlashDeferDuration = SlashDeferDuration; type SlashCancelOrigin = frame_system::EnsureRoot; @@ -263,23 +297,22 @@ impl Config for Test { type SessionInterface = Self; type EraPayout = ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = OffendingValidatorsThreshold; + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; + // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. + type VoterList = VoterBagsList; + type TargetList = UseValidatorsMap; + type MaxUnlockingChunks = MaxUnlockingChunks; + type HistoryDepth = HistoryDepth; + type OnStakerSlash = OnStakerSlashMock; + type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); } -impl frame_system::offchain::SendTransactionTypes for Test -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = Extrinsic; -} - -pub type Extrinsic = TestXt; pub(crate) type StakingCall = crate::Call; -pub(crate) type TestRuntimeCall = ::Call; +pub(crate) type TestCall = ::RuntimeCall; pub struct ExtBuilder { nominate: bool, @@ -288,7 +321,7 @@ pub struct ExtBuilder { invulnerables: Vec, has_stakers: bool, initialize_first_session: bool, - min_nominator_bond: Balance, + pub min_nominator_bond: Balance, min_validator_bond: Balance, balance_factor: Balance, status: BTreeMap>, @@ -469,8 +502,8 @@ impl ExtBuilder { stakers.extend(self.stakers) } - let _ = staking::GenesisConfig:: { - stakers, + let _ = pallet_staking::GenesisConfig:: { + stakers: stakers.clone(), validator_count: self.validator_count, minimum_validator_count: self.minimum_validator_count, invulnerables: self.invulnerables, @@ -483,22 +516,21 @@ impl ExtBuilder { let _ = pallet_session::GenesisConfig:: { keys: if self.has_stakers { - // genesis election will overwrite this, no worries. - Default::default() + // set the keys for the first session. + stakers + .into_iter() + .map(|(id, ..)| (id, id, SessionKeys { other: id.into() })) + .collect() } else { // set some dummy validators in genesis. (0..self.validator_count as u64) - .map(|x| (x, x, SessionKeys { other: UintAuthorityId(x as u64) })) + .map(|id| (id, id, SessionKeys { other: id.into() })) .collect() }, } .assimilate_storage(&mut storage); let mut ext = sp_io::TestExternalities::from(storage); - ext.execute_with(|| { - let validators = Session::validators(); - SESSION.with(|x| *x.borrow_mut() = (validators.clone(), HashSet::new())); - }); if self.initialize_first_session { // We consider all test to start after timestamp is initialized This must be ensured by @@ -515,106 +547,13 @@ impl ExtBuilder { ext } pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + sp_tracing::try_init_simple(); let mut ext = self.build(); ext.execute_with(test); - ext.execute_with(post_conditions); - } -} - -fn post_conditions() { - check_nominators(); - check_exposures(); - check_ledgers(); - check_count(); -} - -fn check_count() { - let nominator_count = Nominators::::iter().count() as u32; - let validator_count = Validators::::iter().count() as u32; - assert_eq!(nominator_count, CounterForNominators::::get()); - assert_eq!(validator_count, CounterForValidators::::get()); -} - -fn check_ledgers() { - // check the ledger of all stakers. - Bonded::::iter().for_each(|(_, ctrl)| assert_ledger_consistent(ctrl)) -} - -fn check_exposures() { - // a check per validator to ensure the exposure struct is always sane. - let era = active_era(); - ErasStakers::::iter_prefix_values(era).for_each(|expo| { - assert_eq!( - expo.total as u128, - expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::(), - "wrong total exposure.", - ); - }) -} - -fn check_nominators() { - // a check per nominator to ensure their entire stake is correctly distributed. Will only kick- - // in if the nomination was submitted before the current era. - let era = active_era(); - >::iter() - .filter_map( - |(nominator, nomination)| { - if nomination.submitted_in > era { - Some(nominator) - } else { - None - } - }, - ) - .for_each(|nominator| { - // must be bonded. - assert_is_stash(nominator); - let mut sum = 0; - Session::validators() - .iter() - .map(|v| Staking::eras_stakers(era, v)) - .for_each(|e| { - let individual = - e.others.iter().filter(|e| e.who == nominator).collect::>(); - let len = individual.len(); - match len { - 0 => { /* not supporting this validator at all. */ }, - 1 => sum += individual[0].value, - _ => panic!("nominator cannot back a validator more than once."), - }; - }); - - let nominator_stake = Staking::slashable_balance_of(&nominator); - // a nominator cannot over-spend. - assert!( - nominator_stake >= sum, - "failed: Nominator({}) stake({}) >= sum divided({})", - nominator, - nominator_stake, - sum, - ); - - let diff = nominator_stake - sum; - assert!(diff < 100); + ext.execute_with(|| { + Staking::do_try_state(System::block_number()).unwrap(); }); -} - -fn assert_is_stash(acc: AccountId) { - assert!(Staking::bonded(&acc).is_some(), "Not a stash."); -} - -fn assert_ledger_consistent(ctrl: AccountId) { - // ensures ledger.total == ledger.active + sum(ledger.unlocking). - let ledger = Staking::ledger(ctrl).expect("Not a controller."); - let real_total: Balance = ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value); - assert_eq!(real_total, ledger.total); - assert!( - ledger.active >= Balances::minimum_balance() || ledger.active == 0, - "{}: active ledger amount ({}) must be greater than ED {}", - ctrl, - ledger.active, - Balances::minimum_balance() - ); + } } pub(crate) fn active_era() -> EraIndex { @@ -625,11 +564,25 @@ pub(crate) fn current_era() -> EraIndex { Staking::current_era().unwrap() } -pub(crate) fn bond_validator(stash: AccountId, ctrl: AccountId, val: Balance) { +pub(crate) fn bond(stash: AccountId, ctrl: AccountId, val: Balance) { let _ = Balances::make_free_balance_be(&stash, val); let _ = Balances::make_free_balance_be(&ctrl, val); - assert_ok!(Staking::bond(Origin::signed(stash), ctrl, val, RewardDestination::Controller)); - assert_ok!(Staking::validate(Origin::signed(ctrl), ValidatorPrefs::default())); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(stash), + ctrl, + val, + RewardDestination::Controller + )); +} + +pub(crate) fn bond_validator(stash: AccountId, ctrl: AccountId, val: Balance) { + bond(stash, ctrl, val); + assert_ok!(Staking::validate(RuntimeOrigin::signed(ctrl), ValidatorPrefs::default())); + assert_ok!(Session::set_keys( + RuntimeOrigin::signed(ctrl), + SessionKeys { other: ctrl.into() }, + vec![] + )); } pub(crate) fn bond_nominator( @@ -638,10 +591,8 @@ pub(crate) fn bond_nominator( val: Balance, target: Vec, ) { - let _ = Balances::make_free_balance_be(&stash, val); - let _ = Balances::make_free_balance_be(&ctrl, val); - assert_ok!(Staking::bond(Origin::signed(stash), ctrl, val, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(ctrl), target)); + bond(stash, ctrl, val); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(ctrl), target)); } /// Progress to the given block, triggering session and era changes as we progress. @@ -755,11 +706,12 @@ 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); + let _ = Staking::on_offence(offenders, slash_fraction, start_session, disable_strategy); return } else if bonded_era > era { break @@ -771,6 +723,7 @@ 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); @@ -785,13 +738,13 @@ pub(crate) fn on_offence_now( slash_fraction: &[Perbill], ) { let now = Staking::active_era().unwrap().index; - on_offence_in_era(offenders, slash_fraction, now) + on_offence_in_era(offenders, slash_fraction, now, DisableStrategy::WhenSlashed) } pub(crate) fn add_slash(who: &AccountId) { on_offence_now( &[OffenceDetails { - offender: (who.clone(), Staking::eras_stakers(active_era(), who.clone())), + offender: (*who, Staking::eras_stakers(active_era(), *who)), reporters: vec![], }], &[Perbill::from_percent(10)], @@ -809,7 +762,7 @@ pub(crate) fn make_all_reward_payment(era: EraIndex) { // reward validators for validator_controller in validators_with_reward.iter().filter_map(Staking::bonded) { let ledger = >::get(&validator_controller).unwrap(); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), ledger.stash, era)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), ledger.stash, era)); } } @@ -833,14 +786,28 @@ macro_rules! assert_session_era { }; } -pub(crate) fn staking_events() -> Vec> { +pub(crate) fn staking_events() -> Vec> { System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let Event::Staking(inner) = e { Some(inner) } else { None }) + .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) .collect() } +parameter_types! { + static StakingEventsIndex: usize = 0; +} + +pub(crate) fn staking_events_since_last_call() -> Vec> { + let all: Vec<_> = System::events() + .into_iter() + .filter_map(|r| if let RuntimeEvent::Staking(inner) = r.event { Some(inner) } else { None }) + .collect(); + let seen = StakingEventsIndex::get(); + StakingEventsIndex::set(all.len()); + all.into_iter().skip(seen).collect() +} + pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { (Balances::free_balance(who), Balances::reserved_balance(who)) } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index fecd493eea022..6729a2ca32ecc 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,34 +17,47 @@ //! Implementations for the Staking FRAME Pallet. -use frame_election_provider_support::{data_provider, ElectionProvider, Supports, VoteWeight}; +use frame_election_provider_support::{ + data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ScoreProvider, + SortedListProvider, VoteWeight, VoterOf, +}; use frame_support::{ + dispatch::WithPostDispatchInfo, pallet_prelude::*, traits::{ - Currency, CurrencyToVote, EstimateNextNewSession, Get, Imbalance, LockableCurrency, - OnUnbalanced, UnixTime, WithdrawReasons, + Currency, CurrencyToVote, Defensive, DefensiveResult, EstimateNextNewSession, Get, + Imbalance, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, WithdrawReasons, }, - weights::{Weight, WithPostDispatchInfo}, + weights::Weight, }; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::historical; use sp_runtime::{ - traits::{Bounded, Convert, SaturatedConversion, Saturating, Zero}, + traits::{Bounded, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}, Perbill, }; use sp_staking::{ - offence::{OffenceDetails, OnOffenceHandler}, - SessionIndex, + offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, + EraIndex, SessionIndex, Stake, StakingInterface, }; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +use sp_std::prelude::*; use crate::{ - log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraIndex, EraPayout, Exposure, - ExposureOf, Forcing, IndividualExposure, Nominations, PositiveImbalanceOf, RewardDestination, + log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, Exposure, ExposureOf, + Forcing, IndividualExposure, MaxWinnersOf, Nominations, PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs, }; use super::{pallet::*, STAKING_ID}; +/// The maximum number of iterations that we do whilst iterating over `T::VoterList` in +/// `get_npos_voters`. +/// +/// In most cases, if we want n items, we iterate exactly n times. In rare cases, if a voter is +/// invalid (for any reason) the iteration continues. With this constant, we iterate at most 2 * n +/// times and then give up. +const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; + impl Pallet { /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { @@ -64,7 +77,7 @@ impl Pallet { /// /// This prevents call sites from repeatedly requesting `total_issuance` from backend. But it is /// important to be only used while the total issuance is not changing. - pub fn slashable_balance_of_fn() -> Box VoteWeight> { + pub fn weight_of_fn() -> Box VoteWeight> { // NOTE: changing this to unboxed `impl Fn(..)` return type and the pallet will still // compile, while some types in mock fail to resolve. let issuance = T::Currency::total_issuance(); @@ -73,6 +86,12 @@ impl Pallet { }) } + /// Same as `weight_of_fn`, but made for one time use. + pub fn weight_of(who: &T::AccountId) -> VoteWeight { + let issuance = T::Currency::total_issuance(); + Self::slashable_balance_of_vote_weight(who, issuance) + } + pub(super) fn do_payout_stakers( validator_stash: T::AccountId, era: EraIndex, @@ -82,7 +101,7 @@ impl Pallet { Error::::InvalidEraToReward .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) })?; - let history_depth = Self::history_depth(); + let history_depth = T::HistoryDepth::get(); ensure!( era <= current_era && era >= current_era.saturating_sub(history_depth), Error::::InvalidEraToReward @@ -104,10 +123,18 @@ impl Pallet { ledger .claimed_rewards .retain(|&x| x >= current_era.saturating_sub(history_depth)); + match ledger.claimed_rewards.binary_search(&era) { - Ok(_) => Err(Error::::AlreadyClaimed - .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))?, - Err(pos) => ledger.claimed_rewards.insert(pos, era), + Ok(_) => + return Err(Error::::AlreadyClaimed + .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))), + Err(pos) => ledger + .claimed_rewards + .try_insert(pos, era) + // Since we retain era entries in `claimed_rewards` only upto + // `HistoryDepth`, following bound is always expected to be + // satisfied. + .defensive_map_err(|_| Error::::BoundNotMet)?, } let exposure = >::get(&era, &ledger.stash); @@ -128,8 +155,8 @@ impl Pallet { let validator_reward_points = era_reward_points .individual .get(&ledger.stash) - .map(|points| *points) - .unwrap_or_else(|| Zero::zero()); + .copied() + .unwrap_or_else(Zero::zero); // Nothing to do if they have no reward points. if validator_reward_points.is_zero() { @@ -154,13 +181,21 @@ impl Pallet { let validator_exposure_part = Perbill::from_rational(exposure.own, exposure.total); let validator_staking_payout = validator_exposure_part * validator_leftover_payout; - Self::deposit_event(Event::::PayoutStarted(era, ledger.stash.clone())); + Self::deposit_event(Event::::PayoutStarted { + era_index: era, + validator_stash: ledger.stash.clone(), + }); + let mut total_imbalance = PositiveImbalanceOf::::zero(); // We can now make total validator payout: if let Some(imbalance) = Self::make_payout(&ledger.stash, validator_staking_payout + validator_commission_payout) { - Self::deposit_event(Event::::Rewarded(ledger.stash, imbalance.peek())); + Self::deposit_event(Event::::Rewarded { + stash: ledger.stash, + amount: imbalance.peek(), + }); + total_imbalance.subsume(imbalance); } // Track the number of payout ops to nominators. Note: @@ -179,11 +214,14 @@ impl Pallet { if let Some(imbalance) = Self::make_payout(&nominator.who, nominator_reward) { // Note: this logic does not count payouts for `RewardDestination::None`. nominator_payout_count += 1; - let e = Event::::Rewarded(nominator.who.clone(), imbalance.peek()); + let e = + Event::::Rewarded { stash: nominator.who.clone(), amount: imbalance.peek() }; Self::deposit_event(e); + total_imbalance.subsume(imbalance); } } + T::Reward::on_unbalanced(total_imbalance); debug_assert!(nominator_payout_count <= T::MaxNominatorRewardedPerValidator::get()); Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into()) } @@ -191,10 +229,7 @@ impl Pallet { /// Update the ledger for a controller. /// /// This will also update the stash lock. - pub(crate) fn update_ledger( - controller: &T::AccountId, - ledger: &StakingLedger>, - ) { + pub(crate) fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger) { T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); } @@ -204,7 +239,7 @@ impl Pallet { let chilled_as_validator = Self::do_remove_validator(stash); let chilled_as_nominator = Self::do_remove_nominator(stash); if chilled_as_validator || chilled_as_nominator { - Self::deposit_event(Event::::Chilled(stash.clone())); + Self::deposit_event(Event::::Chilled { stash: stash.clone() }); } } @@ -214,7 +249,7 @@ impl Pallet { let dest = Self::payee(stash); match dest { RewardDestination::Controller => Self::bonded(stash) - .and_then(|controller| Some(T::Currency::deposit_creating(&controller, amount))), + .map(|controller| T::Currency::deposit_creating(&controller, amount)), RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(), RewardDestination::Staked => Self::bonded(stash) .and_then(|c| Self::ledger(&c).map(|l| (c, l))) @@ -232,7 +267,10 @@ impl Pallet { } /// Plan a new session potentially trigger a new era. - fn new_session(session_index: SessionIndex, is_genesis: bool) -> Option> { + fn new_session( + session_index: SessionIndex, + is_genesis: bool, + ) -> Option>> { if let Some(current_era) = Self::current_era() { // Initial era has been set. let current_era_start_session_index = Self::eras_start_session_index(current_era) @@ -241,8 +279,7 @@ impl Pallet { 0 }); - let era_length = - session_index.checked_sub(current_era_start_session_index).unwrap_or(0); // Must never happen. + let era_length = session_index.saturating_sub(current_era_start_session_index); // Must never happen. match ForceEra::::get() { // Will be set to `NotForcing` again if a new era has been triggered. @@ -292,6 +329,13 @@ impl Pallet { Self::start_era(start_session); } } + + // disable all offending validators that have been disabled for the whole era + for (index, disabled) in >::get() { + if disabled { + T::SessionInterface::disable_validator(index); + } + } } /// End a session potentially ending an era. @@ -307,6 +351,7 @@ impl Pallet { } } + /// Start a new era. It does: /// /// * Increment `active_era.index`, /// * reset `active_era.start`, @@ -357,13 +402,21 @@ impl Pallet { let era_duration = (now_as_millis_u64 - active_era_start).saturated_into::(); let staked = Self::eras_total_stake(&active_era.index); let issuance = T::Currency::total_issuance(); - let (validator_payout, rest) = T::EraPayout::era_payout(staked, issuance, era_duration); + let (validator_payout, remainder) = + T::EraPayout::era_payout(staked, issuance, era_duration); - Self::deposit_event(Event::::EraPaid(active_era.index, validator_payout, rest)); + Self::deposit_event(Event::::EraPaid { + era_index: active_era.index, + validator_payout, + remainder, + }); // Set ending era reward. >::insert(&active_era.index, validator_payout); - T::RewardRemainder::on_unbalanced(T::Currency::issue(rest)); + T::RewardRemainder::on_unbalanced(T::Currency::issue(remainder)); + + // Clear offending validators. + >::kill(); } } @@ -377,8 +430,11 @@ impl Pallet { /// Returns the new validator set. pub fn trigger_new_era( start_session_index: SessionIndex, - exposures: Vec<(T::AccountId, Exposure>)>, - ) -> Vec { + exposures: BoundedVec< + (T::AccountId, Exposure>), + MaxWinnersOf, + >, + ) -> BoundedVec> { // Increment or set current era. let new_planned_era = CurrentEra::::mutate(|s| { *s = Some(s.map(|s| s + 1).unwrap_or(0)); @@ -387,7 +443,7 @@ impl Pallet { ErasStartSessionIndex::::insert(&new_planned_era, &start_session_index); // Clean old era information. - if let Some(old_era) = new_planned_era.checked_sub(Self::history_depth() + 1) { + if let Some(old_era) = new_planned_era.checked_sub(T::HistoryDepth::get() + 1) { Self::clear_era_information(old_era); } @@ -404,19 +460,26 @@ impl Pallet { pub(crate) fn try_trigger_new_era( start_session_index: SessionIndex, is_genesis: bool, - ) -> Option> { - let election_result = if is_genesis { - T::GenesisElectionProvider::elect().map_err(|e| { + ) -> Option>> { + let election_result: BoundedVec<_, MaxWinnersOf> = if is_genesis { + let result = ::elect().map_err(|e| { log!(warn, "genesis election provider failed due to {:?}", e); Self::deposit_event(Event::StakingElectionFailed); - }) + }); + + result + .ok()? + .into_inner() + .try_into() + // both bounds checked in integrity test to be equal + .defensive_unwrap_or_default() } else { - T::ElectionProvider::elect().map_err(|e| { + let result = ::elect().map_err(|e| { log!(warn, "election provider failed due to {:?}", e); Self::deposit_event(Event::StakingElectionFailed); - }) - } - .ok()?; + }); + result.ok()? + }; let exposures = Self::collect_exposures(election_result); if (exposures.len() as u32) < Self::minimum_validator_count().max(1) { @@ -453,10 +516,19 @@ impl Pallet { /// /// Store staking information for the new planned era pub fn store_stakers_info( - exposures: Vec<(T::AccountId, Exposure>)>, + exposures: BoundedVec< + (T::AccountId, Exposure>), + MaxWinnersOf, + >, new_planned_era: EraIndex, - ) -> Vec { - let elected_stashes = exposures.iter().cloned().map(|(x, _)| x).collect::>(); + ) -> BoundedVec> { + let elected_stashes: BoundedVec<_, MaxWinnersOf> = exposures + .iter() + .cloned() + .map(|(x, _)| x) + .collect::>() + .try_into() + .expect("since we only map through exposures, size of elected_stashes is always same as exposures; qed"); // Populate stakers, exposures, and the snapshot of validator prefs. let mut total_stake: BalanceOf = Zero::zero(); @@ -494,11 +566,11 @@ impl Pallet { elected_stashes } - /// Consume a set of [`Supports`] from [`sp_npos_elections`] and collect them into a + /// Consume a set of [`BoundedSupports`] from [`sp_npos_elections`] and collect them into a /// [`Exposure`]. fn collect_exposures( - supports: Supports, - ) -> Vec<(T::AccountId, Exposure>)> { + supports: BoundedSupportsOf, + ) -> BoundedVec<(T::AccountId, Exposure>), MaxWinnersOf> { let total_issuance = T::Currency::total_issuance(); let to_currency = |e: frame_election_provider_support::ExtendedBalance| { T::CurrencyToVote::to_currency(e, total_issuance) @@ -527,7 +599,8 @@ impl Pallet { let exposure = Exposure { own, others, total }; (validator, exposure) }) - .collect::)>>() + .try_collect() + .expect("we only map through support vector which cannot change the size; qed") } /// Remove all associated data of a stash account from the staking system. @@ -556,8 +629,11 @@ impl Pallet { /// Clear all era information for given era. pub(crate) fn clear_era_information(era_index: EraIndex) { + #[allow(deprecated)] >::remove_prefix(era_index, None); + #[allow(deprecated)] >::remove_prefix(era_index, None); + #[allow(deprecated)] >::remove_prefix(era_index, None); >::remove(era_index); >::remove(era_index); @@ -567,20 +643,17 @@ impl Pallet { /// Apply previously-unapplied slashes on the beginning of a new era, after a delay. fn apply_unapplied_slashes(active_era: EraIndex) { - let slash_defer_duration = T::SlashDeferDuration::get(); - ::EarliestUnappliedSlash::mutate(|earliest| { - if let Some(ref mut earliest) = earliest { - let keep_from = active_era.saturating_sub(slash_defer_duration); - for era in (*earliest)..keep_from { - let era_slashes = ::UnappliedSlashes::take(&era); - for slash in era_slashes { - slashing::apply_slash::(slash); - } - } - - *earliest = (*earliest).max(keep_from) - } - }) + let era_slashes = ::UnappliedSlashes::take(&active_era); + log!( + debug, + "found {} slashes scheduled to be executed in era {:?}", + era_slashes.len(), + active_era, + ); + for slash in era_slashes { + let slash_era = active_era.saturating_sub(T::SlashDeferDuration::get()); + slashing::apply_slash::(slash, slash_era); + } } /// Add reward points to validators using their stash account ID. @@ -616,10 +689,10 @@ impl Pallet { #[cfg(feature = "runtime-benchmarks")] pub fn add_era_stakers( current_era: EraIndex, - controller: T::AccountId, + stash: T::AccountId, exposure: Exposure>, ) { - >::insert(¤t_era, &controller, &exposure); + >::insert(¤t_era, &stash, &exposure); } #[cfg(feature = "runtime-benchmarks")] @@ -629,53 +702,80 @@ impl Pallet { /// Get all of the voters that are eligible for the npos election. /// - /// This will use all on-chain nominators, and all the validators will inject a self vote. + /// `maybe_max_len` can imposes a cap on the number of voters returned; /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - /// - /// ### Slashing - /// - /// All nominations that have been submitted before the last non-zero slash of the validator are - /// auto-chilled. - pub fn get_npos_voters() -> Vec<(T::AccountId, VoteWeight, Vec)> { - let weight_of = Self::slashable_balance_of_fn(); - let mut all_voters = Vec::new(); - - let mut validator_count = 0u32; - for (validator, _) in >::iter() { - // Append self vote. - let self_vote = (validator.clone(), weight_of(&validator), vec![validator.clone()]); - all_voters.push(self_vote); - validator_count.saturating_inc(); - } + pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { + let max_allowed_len = { + let all_voter_count = T::VoterList::count() as usize; + maybe_max_len.unwrap_or(all_voter_count).min(all_voter_count) + }; - // Collect all slashing spans into a BTreeMap for further queries. - let slashing_spans = >::iter().collect::>(); + let mut all_voters = Vec::<_>::with_capacity(max_allowed_len); - let mut nominator_count = 0u32; - for (nominator, nominations) in Nominators::::iter() { - let Nominations { submitted_in, mut targets, suppressed: _ } = nominations; + // cache a few things. + let weight_of = Self::weight_of_fn(); - // Filter out nomination targets which were nominated before the most recent - // slashing span. - targets.retain(|stash| { - slashing_spans - .get(stash) - .map_or(true, |spans| submitted_in >= spans.last_nonzero_slash()) - }); + let mut voters_seen = 0u32; + let mut validators_taken = 0u32; + let mut nominators_taken = 0u32; + + let mut sorted_voters = T::VoterList::iter(); + while all_voters.len() < max_allowed_len && + voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32) + { + let voter = match sorted_voters.next() { + Some(voter) => { + voters_seen.saturating_inc(); + voter + }, + None => break, + }; - if !targets.is_empty() { - let vote_weight = weight_of(&nominator); - all_voters.push((nominator, vote_weight, targets)); - nominator_count.saturating_inc(); + if let Some(Nominations { targets, .. }) = >::get(&voter) { + if !targets.is_empty() { + all_voters.push((voter.clone(), weight_of(&voter), targets)); + nominators_taken.saturating_inc(); + } else { + // Technically should never happen, but not much we can do about it. + } + } else if Validators::::contains_key(&voter) { + // if this voter is a validator: + let self_vote = ( + voter.clone(), + weight_of(&voter), + vec![voter.clone()] + .try_into() + .expect("`MaxVotesPerVoter` must be greater than or equal to 1"), + ); + all_voters.push(self_vote); + validators_taken.saturating_inc(); + } else { + // this can only happen if: 1. there a bug in the bags-list (or whatever is the + // sorted list) logic and the state of the two pallets is no longer compatible, or + // because the nominators is not decodable since they have more nomination than + // `T::MaxNominations`. The latter can rarely happen, and is not really an emergency + // or bug if it does. + log!( + warn, + "DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now", + voter + ); } } - Self::register_weight(T::WeightInfo::get_npos_voters( - validator_count, - nominator_count, - slashing_spans.len() as u32, - )); + // all_voters should have not re-allocated. + debug_assert!(all_voters.capacity() == max_allowed_len); + + Self::register_weight(T::WeightInfo::get_npos_voters(validators_taken, nominators_taken)); + + log!( + info, + "generated {} npos voters, {} from validators and {} nominators", + all_voters.len(), + validators_taken, + nominators_taken + ); all_voters } @@ -683,68 +783,124 @@ impl Pallet { /// Get the targets for an upcoming npos election. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_targets() -> Vec { - let mut validator_count = 0u32; - let targets = Validators::::iter() - .map(|(v, _)| { - validator_count.saturating_inc(); - v - }) - .collect::>(); + pub fn get_npos_targets(maybe_max_len: Option) -> Vec { + let max_allowed_len = maybe_max_len.unwrap_or_else(|| T::TargetList::count() as usize); + let mut all_targets = Vec::::with_capacity(max_allowed_len); + let mut targets_seen = 0; + + let mut targets_iter = T::TargetList::iter(); + while all_targets.len() < max_allowed_len && + targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32) + { + let target = match targets_iter.next() { + Some(target) => { + targets_seen.saturating_inc(); + target + }, + None => break, + }; + + if Validators::::contains_key(&target) { + all_targets.push(target); + } + } - Self::register_weight(T::WeightInfo::get_npos_targets(validator_count)); + Self::register_weight(T::WeightInfo::get_npos_targets(all_targets.len() as u32)); + log!(info, "generated {} npos targets", all_targets.len()); - targets + all_targets } /// This function will add a nominator to the `Nominators` storage map, - /// and keep track of the `CounterForNominators`. + /// and `VoterList`. /// /// If the nominator already exists, their nominations will be updated. - pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { + /// + /// NOTE: you must ALWAYS use this function to add nominator or update their targets. Any access + /// to `Nominators` or `VoterList` outside of this function is almost certainly + /// wrong. + pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { if !Nominators::::contains_key(who) { - CounterForNominators::::mutate(|x| x.saturating_inc()) + // maybe update sorted list. + let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) + .defensive_unwrap_or_default(); } Nominators::::insert(who, nominations); + + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::VoterList::count() + ); } /// This function will remove a nominator from the `Nominators` storage map, - /// and keep track of the `CounterForNominators`. + /// and `VoterList`. /// /// Returns true if `who` was removed from `Nominators`, otherwise false. + /// + /// NOTE: you must ALWAYS use this function to remove a nominator from the system. Any access to + /// `Nominators` or `VoterList` outside of this function is almost certainly + /// wrong. pub fn do_remove_nominator(who: &T::AccountId) -> bool { - if Nominators::::contains_key(who) { + let outcome = if Nominators::::contains_key(who) { Nominators::::remove(who); - CounterForNominators::::mutate(|x| x.saturating_dec()); + let _ = T::VoterList::on_remove(who).defensive(); true } else { false - } + }; + + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::VoterList::count() + ); + + outcome } - /// This function will add a validator to the `Validators` storage map, - /// and keep track of the `CounterForValidators`. + /// This function will add a validator to the `Validators` storage map. /// /// If the validator already exists, their preferences will be updated. + /// + /// NOTE: you must ALWAYS use this function to add a validator to the system. Any access to + /// `Validators` or `VoterList` outside of this function is almost certainly + /// wrong. pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { if !Validators::::contains_key(who) { - CounterForValidators::::mutate(|x| x.saturating_inc()) + // maybe update sorted list. + let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) + .defensive_unwrap_or_default(); } Validators::::insert(who, prefs); + + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::VoterList::count() + ); } - /// This function will remove a validator from the `Validators` storage map, - /// and keep track of the `CounterForValidators`. + /// This function will remove a validator from the `Validators` storage map. /// /// Returns true if `who` was removed from `Validators`, otherwise false. + /// + /// NOTE: you must ALWAYS use this function to remove a validator from the system. Any access to + /// `Validators` or `VoterList` outside of this function is almost certainly + /// wrong. pub fn do_remove_validator(who: &T::AccountId) -> bool { - if Validators::::contains_key(who) { + let outcome = if Validators::::contains_key(who) { Validators::::remove(who); - CounterForValidators::::mutate(|x| x.saturating_dec()); + let _ = T::VoterList::on_remove(who).defensive(); true } else { false - } + }; + + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::VoterList::count() + ); + + outcome } /// Register some amount of weight directly with the system pallet. @@ -758,46 +914,33 @@ impl Pallet { } } -impl frame_election_provider_support::ElectionDataProvider - for Pallet -{ - const MAXIMUM_VOTES_PER_VOTER: u32 = T::MAX_NOMINATIONS; +impl ElectionDataProvider for Pallet { + type AccountId = T::AccountId; + type BlockNumber = BlockNumberFor; + type MaxVotesPerVoter = T::MaxNominations; + fn desired_targets() -> data_provider::Result { Self::register_weight(T::DbWeight::get().reads(1)); Ok(Self::validator_count()) } - fn voters( - maybe_max_len: Option, - ) -> data_provider::Result)>> { - let nominator_count = CounterForNominators::::get(); - let validator_count = CounterForValidators::::get(); - - let voter_count = nominator_count.saturating_add(validator_count) as usize; - debug_assert!(>::iter().count() as u32 == CounterForNominators::::get()); - debug_assert!(>::iter().count() as u32 == CounterForValidators::::get()); - - // register the extra 2 reads - Self::register_weight(T::DbWeight::get().reads(2)); - - if maybe_max_len.map_or(false, |max_len| voter_count > max_len) { - return Err("Voter snapshot too big") - } + fn electing_voters(maybe_max_len: Option) -> data_provider::Result>> { + // This can never fail -- if `maybe_max_len` is `Some(_)` we handle it. + let voters = Self::get_npos_voters(maybe_max_len); + debug_assert!(maybe_max_len.map_or(true, |max| voters.len() <= max)); - Ok(Self::get_npos_voters()) + Ok(voters) } - fn targets(maybe_max_len: Option) -> data_provider::Result> { - let target_count = CounterForValidators::::get() as usize; + fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { + let target_count = T::TargetList::count(); - // register the extra 1 read - Self::register_weight(T::DbWeight::get().reads(1)); - - if maybe_max_len.map_or(false, |max_len| target_count > max_len) { + // We can't handle this case yet -- return an error. + if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) { return Err("Target snapshot too big") } - Ok(Self::get_npos_targets()) + Ok(Self::get_npos_targets(None)) } fn next_election_prediction(now: T::BlockNumber) -> T::BlockNumber { @@ -834,8 +977,11 @@ impl frame_election_provider_support::ElectionDataProvider) { - use sp_std::convert::TryFrom; + fn add_voter( + voter: T::AccountId, + weight: VoteWeight, + targets: BoundedVec, + ) { let stake = >::try_from(weight).unwrap_or_else(|_| { panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.") }); @@ -846,10 +992,11 @@ impl frame_election_provider_support::ElectionDataProvider frame_election_provider_support::ElectionDataProvider frame_election_provider_support::ElectionDataProvider>::remove_all(None); + #[allow(deprecated)] >::remove_all(None); - >::remove_all(None); - >::remove_all(None); + #[allow(deprecated)] + >::remove_all(); + #[allow(deprecated)] + >::remove_all(); + + T::VoterList::unsafe_clear(); } #[cfg(feature = "runtime-benchmarks")] fn put_snapshot( - voters: Vec<(T::AccountId, VoteWeight, Vec)>, + voters: Vec>, targets: Vec, target_stake: Option, ) { - use sp_std::convert::TryFrom; targets.into_iter().for_each(|v| { let stake: BalanceOf = target_stake .and_then(|w| >::try_from(w).ok()) @@ -899,8 +1051,8 @@ impl frame_election_provider_support::ElectionDataProvider frame_election_provider_support::ElectionDataProvider pallet_session::SessionManager for Pallet { fn new_session(new_index: SessionIndex) -> Option> { log!(trace, "planning new session {}", new_index); CurrentPlannedSession::::put(new_index); - Self::new_session(new_index, false) + Self::new_session(new_index, false).map(|v| v.into_inner()) } fn new_session_genesis(new_index: SessionIndex) -> Option> { log!(trace, "planning new session {} at genesis", new_index); CurrentPlannedSession::::put(new_index); - Self::new_session(new_index, true) + Self::new_session(new_index, true).map(|v| v.into_inner()) } fn start_session(start_index: SessionIndex) { log!(trace, "starting session {}", start_index); @@ -1016,8 +1168,13 @@ where fn note_author(author: T::AccountId) { Self::reward_by_ids(vec![(author, 20)]) } - fn note_uncle(author: T::AccountId, _age: T::BlockNumber) { - Self::reward_by_ids(vec![(>::author(), 2), (author, 1)]) + fn note_uncle(uncle_author: T::AccountId, _age: T::BlockNumber) { + // defensive-only: block author must exist. + if let Some(block_author) = >::author() { + Self::reward_by_ids(vec![(block_author, 2), (uncle_author, 1)]) + } else { + crate::log!(warn, "block author not set, this should never happen"); + } } } @@ -1045,9 +1202,10 @@ where >], slash_fraction: &[Perbill], slash_session: SessionIndex, + disable_strategy: DisableStrategy, ) -> Weight { let reward_proportion = SlashRewardFraction::::get(); - let mut consumed_weight: Weight = 0; + let mut consumed_weight = Weight::from_ref_time(0); let mut add_db_reads_writes = |reads, writes| { consumed_weight += T::DbWeight::get().reads_writes(reads, writes); }; @@ -1079,18 +1237,13 @@ where add_db_reads_writes(1, 0); // Reverse because it's more likely to find reports from recent eras. - match eras.iter().rev().filter(|&&(_, ref sesh)| sesh <= &slash_session).next() { + match eras.iter().rev().find(|&&(_, ref sesh)| sesh <= &slash_session) { Some(&(ref slash_era, _)) => *slash_era, // Before bonding period. defensive - should be filtered out. None => return consumed_weight, } }; - ::EarliestUnappliedSlash::mutate(|earliest| { - if earliest.is_none() { - *earliest = Some(active_era) - } - }); add_db_reads_writes(1, 1); let slash_defer_duration = T::SlashDeferDuration::get(); @@ -1114,6 +1267,13 @@ where window_start, now: active_era, reward_proportion, + disable_strategy, + }); + + Self::deposit_event(Event::::SlashReported { + validator: stash.clone(), + fraction: *slash_fraction, + slash_era, }); if let Some(mut unapplied) = unapplied { @@ -1128,7 +1288,7 @@ where unapplied.reporters = details.reporters.clone(); if slash_defer_duration == 0 { // Apply right away. - slashing::apply_slash::(unapplied); + slashing::apply_slash::(unapplied, slash_era); { let slash_cost = (6, 5); let reward_cost = (2, 2); @@ -1139,9 +1299,18 @@ where } } else { // Defer to end of some `slash_defer_duration` from now. - ::UnappliedSlashes::mutate(active_era, move |for_later| { - for_later.push(unapplied) - }); + log!( + debug, + "deferring slash of {:?}% happened in {:?} (reported in {:?}) to {:?}", + slash_fraction, + slash_era, + active_era, + slash_era + slash_defer_duration + 1, + ); + ::UnappliedSlashes::mutate( + slash_era.saturating_add(slash_defer_duration).saturating_add(One::one()), + move |for_later| for_later.push(unapplied), + ); add_db_reads_writes(1, 1); } } else { @@ -1152,3 +1321,412 @@ where consumed_weight } } + +impl ScoreProvider for Pallet { + type Score = VoteWeight; + + fn score(who: &T::AccountId) -> Self::Score { + Self::weight_of(who) + } + + #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz"))] + fn set_score_of(who: &T::AccountId, weight: Self::Score) { + // this will clearly results in an inconsistent state, but it should not matter for a + // benchmark. + let active: BalanceOf = weight.try_into().map_err(|_| ()).unwrap(); + let mut ledger = match Self::ledger(who) { + None => StakingLedger::default_from(who.clone()), + Some(l) => l, + }; + ledger.active = active; + + >::insert(who, ledger); + >::insert(who, who); + + // also, we play a trick to make sure that a issuance based-`CurrencyToVote` behaves well: + // This will make sure that total issuance is zero, thus the currency to vote will be a 1-1 + // conversion. + let imbalance = T::Currency::burn(T::Currency::total_issuance()); + // kinda ugly, but gets the job done. The fact that this works here is a HUGE exception. + // Don't try this pattern in other places. + sp_std::mem::forget(imbalance); + } +} + +/// A simple sorted list implementation that does not require any additional pallets. Note, this +/// does not provide validators in sorted order. If you desire nominators in a sorted order take +/// a look at [`pallet-bags-list`]. +pub struct UseValidatorsMap(sp_std::marker::PhantomData); +impl SortedListProvider for UseValidatorsMap { + type Score = BalanceOf; + type Error = (); + + /// Returns iterator over voter list, which can have `take` called on it. + fn iter() -> Box> { + Box::new(Validators::::iter().map(|(v, _)| v)) + } + fn iter_from( + start: &T::AccountId, + ) -> Result>, Self::Error> { + if Validators::::contains_key(start) { + let start_key = Validators::::hashed_key_for(start); + Ok(Box::new(Validators::::iter_from(start_key).map(|(n, _)| n))) + } else { + Err(()) + } + } + fn count() -> u32 { + Validators::::count() + } + fn contains(id: &T::AccountId) -> bool { + Validators::::contains_key(id) + } + fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { + // nothing to do on insert. + Ok(()) + } + fn get_score(id: &T::AccountId) -> Result { + Ok(Pallet::::weight_of(id).into()) + } + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { + // nothing to do on update. + Ok(()) + } + fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> { + // nothing to do on remove. + Ok(()) + } + fn unsafe_regenerate( + _: impl IntoIterator, + _: Box Self::Score>, + ) -> u32 { + // nothing to do upon regenerate. + 0 + } + fn try_state() -> Result<(), &'static str> { + Ok(()) + } + fn unsafe_clear() { + #[allow(deprecated)] + Validators::::remove_all(); + } + + #[cfg(feature = "runtime-benchmarks")] + fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score { + unimplemented!() + } +} + +/// A simple voter list implementation that does not require any additional pallets. Note, this +/// does not provided nominators in sorted ordered. If you desire nominators in a sorted order take +/// a look at [`pallet-bags-list]. +pub struct UseNominatorsAndValidatorsMap(sp_std::marker::PhantomData); +impl SortedListProvider for UseNominatorsAndValidatorsMap { + type Error = (); + type Score = VoteWeight; + + fn iter() -> Box> { + Box::new( + Validators::::iter() + .map(|(v, _)| v) + .chain(Nominators::::iter().map(|(n, _)| n)), + ) + } + fn iter_from( + start: &T::AccountId, + ) -> Result>, Self::Error> { + if Validators::::contains_key(start) { + let start_key = Validators::::hashed_key_for(start); + Ok(Box::new( + Validators::::iter_from(start_key) + .map(|(n, _)| n) + .chain(Nominators::::iter().map(|(x, _)| x)), + )) + } else if Nominators::::contains_key(start) { + let start_key = Nominators::::hashed_key_for(start); + Ok(Box::new(Nominators::::iter_from(start_key).map(|(n, _)| n))) + } else { + Err(()) + } + } + fn count() -> u32 { + Nominators::::count().saturating_add(Validators::::count()) + } + fn contains(id: &T::AccountId) -> bool { + Nominators::::contains_key(id) || Validators::::contains_key(id) + } + fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { + // nothing to do on insert. + Ok(()) + } + fn get_score(id: &T::AccountId) -> Result { + Ok(Pallet::::weight_of(id)) + } + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { + // nothing to do on update. + Ok(()) + } + fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> { + // nothing to do on remove. + Ok(()) + } + fn unsafe_regenerate( + _: impl IntoIterator, + _: Box Self::Score>, + ) -> u32 { + // nothing to do upon regenerate. + 0 + } + fn try_state() -> Result<(), &'static str> { + Ok(()) + } + + fn unsafe_clear() { + // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a + // condition of SortedListProvider::unsafe_clear. + #[allow(deprecated)] + Nominators::::remove_all(); + #[allow(deprecated)] + Validators::::remove_all(); + } + + #[cfg(feature = "runtime-benchmarks")] + fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score { + unimplemented!() + } +} + +// NOTE: in this entire impl block, the assumption is that `who` is a stash account. +impl StakingInterface for Pallet { + type AccountId = T::AccountId; + type Balance = BalanceOf; + + fn minimum_nominator_bond() -> Self::Balance { + MinNominatorBond::::get() + } + + fn minimum_validator_bond() -> Self::Balance { + MinValidatorBond::::get() + } + + fn desired_validator_count() -> u32 { + ValidatorCount::::get() + } + + fn election_ongoing() -> bool { + T::ElectionProvider::ongoing() + } + + fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult { + let num_slashing_spans = Self::slashing_spans(&who).iter().count() as u32; + Self::force_unstake(RawOrigin::Root.into(), who.clone(), num_slashing_spans) + } + + fn stash_by_ctrl(controller: &Self::AccountId) -> Result { + Self::ledger(controller) + .map(|l| l.stash) + .ok_or(Error::::NotController.into()) + } + + fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { + ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { + validator == *who || exposures.others.iter().any(|i| i.who == *who) + }) + } + + fn bonding_duration() -> EraIndex { + T::BondingDuration::get() + } + + fn current_era() -> EraIndex { + Self::current_era().unwrap_or(Zero::zero()) + } + + fn stake(who: &Self::AccountId) -> Result, DispatchError> { + Self::bonded(who) + .and_then(|c| Self::ledger(c)) + .map(|l| Stake { stash: l.stash, total: l.total, active: l.active }) + .ok_or(Error::::NotStash.into()) + } + + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { + Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra) + } + + fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult { + let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; + Self::unbond(RawOrigin::Signed(ctrl).into(), value) + } + + 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. + let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; + Self::chill(RawOrigin::Signed(ctrl).into()) + } + + fn withdraw_unbonded( + who: Self::AccountId, + num_slashing_spans: u32, + ) -> Result { + let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; + Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), num_slashing_spans) + .map(|_| !Ledger::::contains_key(&ctrl)) + .map_err(|with_post| with_post.error) + } + + fn bond( + who: &Self::AccountId, + value: Self::Balance, + payee: &Self::AccountId, + ) -> DispatchResult { + Self::bond( + RawOrigin::Signed(who.clone()).into(), + T::Lookup::unlookup(who.clone()), + value, + RewardDestination::Account(payee.clone()), + ) + } + + fn nominate(who: &Self::AccountId, targets: Vec) -> DispatchResult { + let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; + let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); + Self::nominate(RawOrigin::Signed(ctrl).into(), targets) + } + + #[cfg(feature = "runtime-benchmarks")] + fn nominations(who: Self::AccountId) -> Option> { + Nominators::::get(who).map(|n| n.targets.into_inner()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn add_era_stakers( + current_era: &EraIndex, + stash: &T::AccountId, + exposures: Vec<(Self::AccountId, Self::Balance)>, + ) { + let others = exposures + .iter() + .map(|(who, value)| IndividualExposure { who: who.clone(), value: value.clone() }) + .collect::>(); + let exposure = Exposure { total: Default::default(), own: Default::default(), others }; + Self::add_era_stakers(current_era.clone(), stash.clone(), exposure) + } + + #[cfg(feature = "runtime-benchmarks")] + fn set_current_era(era: EraIndex) { + CurrentEra::::put(era); + } +} + +#[cfg(any(test, feature = "try-runtime"))] +impl Pallet { + pub(crate) fn do_try_state(_: BlockNumberFor) -> Result<(), &'static str> { + ensure!( + T::VoterList::iter() + .all(|x| >::contains_key(&x) || >::contains_key(&x)), + "VoterList contains non-nominators" + ); + T::VoterList::try_state()?; + Self::check_nominators()?; + Self::check_exposures()?; + Self::check_ledgers()?; + Self::check_count() + } + + fn check_count() -> Result<(), &'static str> { + ensure!( + ::VoterList::count() == + Nominators::::count() + Validators::::count(), + "wrong external count" + ); + + ensure!( + ValidatorCount::::get() <= + ::MaxWinners::get(), + "validator count exceeded election max winners" + ); + Ok(()) + } + + fn check_ledgers() -> Result<(), &'static str> { + Bonded::::iter() + .map(|(_, ctrl)| Self::ensure_ledger_consistent(ctrl)) + .collect::>() + } + + fn check_exposures() -> Result<(), &'static str> { + // a check per validator to ensure the exposure struct is always sane. + let era = Self::active_era().unwrap().index; + ErasStakers::::iter_prefix_values(era) + .map(|expo| { + ensure!( + expo.total == + expo.own + + expo.others + .iter() + .map(|e| e.value) + .fold(Zero::zero(), |acc, x| acc + x), + "wrong total exposure.", + ); + Ok(()) + }) + .collect::>() + } + + fn check_nominators() -> Result<(), &'static str> { + // a check per nominator to ensure their entire stake is correctly distributed. Will only + // kick-in if the nomination was submitted before the current era. + let era = Self::active_era().unwrap().index; + >::iter() + .filter_map( + |(nominator, nomination)| { + if nomination.submitted_in > era { + Some(nominator) + } else { + None + } + }, + ) + .map(|nominator| { + // must be bonded. + Self::ensure_is_stash(&nominator)?; + let mut sum = BalanceOf::::zero(); + T::SessionInterface::validators() + .iter() + .map(|v| Self::eras_stakers(era, v)) + .map(|e| { + let individual = + e.others.iter().filter(|e| e.who == nominator).collect::>(); + let len = individual.len(); + match len { + 0 => { /* not supporting this validator at all. */ }, + 1 => sum += individual[0].value, + _ => return Err("nominator cannot back a validator more than once."), + }; + Ok(()) + }) + .collect::>() + }) + .collect::>() + } + + fn ensure_is_stash(who: &T::AccountId) -> Result<(), &'static str> { + ensure!(Self::bonded(who).is_some(), "Not a stash."); + Ok(()) + } + + fn ensure_ledger_consistent(ctrl: T::AccountId) -> Result<(), &'static str> { + // ensures ledger.total == ledger.active + sum(ledger.unlocking). + let ledger = Self::ledger(ctrl.clone()).ok_or("Not a controller.")?; + let real_total: BalanceOf = + ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value); + ensure!(real_total == ledger.total, "ledger.total corrupt"); + + if !(ledger.active >= T::Currency::minimum_balance() || ledger.active.is_zero()) { + log!(warn, "ledger.active less than ED: {:?}, {:?}", ctrl, ledger) + } + + Ok(()) + } +} diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index d99cd89f3b06c..fda455ca3c166 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,49 +17,83 @@ //! Staking FRAME Pallet. +use frame_election_provider_support::{ + ElectionProvider, ElectionProviderBase, SortedListProvider, VoteWeight, +}; use frame_support::{ + dispatch::Codec, pallet_prelude::*, traits::{ - Currency, CurrencyToVote, EnsureOrigin, EstimateNextNewSession, Get, LockIdentifier, - LockableCurrency, OnUnbalanced, UnixTime, + Currency, CurrencyToVote, Defensive, DefensiveResult, DefensiveSaturating, EnsureOrigin, + EstimateNextNewSession, Get, LockIdentifier, LockableCurrency, OnUnbalanced, TryCollect, + UnixTime, }, weights::Weight, + BoundedVec, }; -use frame_system::{ensure_root, ensure_signed, offchain::SendTransactionTypes, pallet_prelude::*}; +use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; use sp_runtime::{ traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, - DispatchError, Perbill, Percent, + ArithmeticError, Perbill, Percent, }; -use sp_staking::SessionIndex; -use sp_std::{convert::From, prelude::*, result}; +use sp_staking::{EraIndex, SessionIndex}; +use sp_std::prelude::*; mod impls; pub use impls::*; use crate::{ - migrations, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraIndex, EraPayout, + slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, Releases, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, }; -pub const MAX_UNLOCKING_CHUNKS: usize = 32; const STAKING_ID: LockIdentifier = *b"staking "; #[frame_support::pallet] pub mod pallet { + use frame_election_provider_support::ElectionDataProvider; + + use crate::BenchmarkingConfig; + use super::*; #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] pub struct Pallet(_); + /// Possible operations on the configuration values of this pallet. + #[derive(TypeInfo, Debug, Clone, Encode, Decode, PartialEq)] + pub enum ConfigOp { + /// Don't change. + Noop, + /// Set the given value. + Set(T), + /// Remove from storage. + Remove, + } + #[pallet::config] - pub trait Config: frame_system::Config + SendTransactionTypes> { + pub trait Config: frame_system::Config { /// The staking balance. - type Currency: LockableCurrency; - + type Currency: LockableCurrency< + Self::AccountId, + Moment = Self::BlockNumber, + Balance = Self::CurrencyBalance, + >; + /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to + /// `From`. + type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned + + codec::FullCodec + + Copy + + MaybeSerializeDeserialize + + sp_std::fmt::Debug + + Default + + From + + TypeInfo + + MaxEncodedLen; /// Time used for computing era duration. /// /// It is guaranteed to start being called from the first `on_finalize`. Thus value at @@ -68,40 +102,67 @@ pub mod pallet { /// Convert a balance into a number used for election calculation. This must fit into a /// `u64` but is allowed to be sensibly lossy. The `u64` is used to communicate with the - /// [`sp_npos_elections`] crate which accepts u64 numbers and does operations in 128. + /// [`frame_election_provider_support`] crate which accepts u64 numbers and does operations + /// in 128. /// Consequently, the backward convert is used convert the u128s from sp-elections back to a /// [`BalanceOf`]. type CurrencyToVote: CurrencyToVote>; /// Something that provides the election functionality. - type ElectionProvider: frame_election_provider_support::ElectionProvider< - Self::AccountId, - Self::BlockNumber, + type ElectionProvider: ElectionProvider< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, // we only accept an election provider that has staking as data provider. DataProvider = Pallet, >; /// Something that provides the election functionality at genesis. - type GenesisElectionProvider: frame_election_provider_support::ElectionProvider< - Self::AccountId, - Self::BlockNumber, + type GenesisElectionProvider: ElectionProvider< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, DataProvider = Pallet, >; /// Maximum number of nominations per nominator. - const MAX_NOMINATIONS: u32; + #[pallet::constant] + type MaxNominations: Get; + + /// Number of eras to keep in history. + /// + /// Following information is kept for eras in `[current_era - + /// HistoryDepth, current_era]`: `ErasStakers`, `ErasStakersClipped`, + /// `ErasValidatorPrefs`, `ErasValidatorReward`, `ErasRewardPoints`, + /// `ErasTotalStake`, `ErasStartSessionIndex`, + /// `StakingLedger.claimed_rewards`. + /// + /// Must be more than the number of eras delayed by session. + /// I.e. active era must always be in history. I.e. `active_era > + /// current_era - history_depth` must be guaranteed. + /// + /// If migrating an existing pallet from storage value to config value, + /// this should be set to same value or greater as in storage. + /// + /// Note: `HistoryDepth` is used as the upper bound for the `BoundedVec` + /// item `StakingLedger.claimed_rewards`. Setting this value lower than + /// the existing value can lead to inconsistencies in the + /// `StakingLedger` and will need to be handled properly in a migration. + /// The test `reducing_history_depth_abrupt` shows this effect. + #[pallet::constant] + type HistoryDepth: Get; /// Tokens have been minted and are unused for validator-reward. /// See [Era payout](./index.html#era-payout). type RewardRemainder: OnUnbalanced>; /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Handler for the unbalanced reduction when slashing a staker. type Slash: OnUnbalanced>; /// Handler for the unbalanced increment when rewarding a staker. + /// NOTE: in most cases, the implementation of `OnUnbalanced` should modify the total + /// issuance. type Reward: OnUnbalanced>; /// Number of sessions per era. @@ -120,7 +181,7 @@ pub mod pallet { type SlashDeferDuration: Get; /// The origin which can cancel a deferred slash. Root can always do this. - type SlashCancelOrigin: EnsureOrigin; + type SlashCancelOrigin: EnsureOrigin; /// Interface for interacting with a session pallet. type SessionInterface: SessionInterface; @@ -140,36 +201,70 @@ pub mod pallet { #[pallet::constant] type MaxNominatorRewardedPerValidator: Get; - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; - } + /// 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; - #[pallet::extra_constants] - impl Pallet { - // TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed. - #[allow(non_snake_case)] - fn MaxNominations() -> u32 { - T::MAX_NOMINATIONS - } - } + /// Something that provides a best-effort sorted list of voters aka electing nominators, + /// used for NPoS election. + /// + /// The changes to nominators are reported to this. Moreover, each validator's self-vote is + /// also reported as one independent vote. + /// + /// To keep the load off the chain as much as possible, changes made to the staked amount + /// via rewards and slashes are not reported and thus need to be manually fixed by the + /// staker. In case of `bags-list`, this always means using `rebag` and `putInFrontOf`. + /// + /// Invariant: what comes out of this list will always be a nominator. + type VoterList: SortedListProvider; - #[pallet::type_value] - pub(crate) fn HistoryDepthOnEmpty() -> u32 { - 84u32 - } + /// WIP: This is a noop as of now, the actual business logic that's described below is going + /// to be introduced in a follow-up PR. + /// + /// Something that provides a best-effort sorted list of targets aka electable validators, + /// used for NPoS election. + /// + /// The changes to the approval stake of each validator are reported to this. This means any + /// change to: + /// 1. The stake of any validator or nominator. + /// 2. The targets of any nominator + /// 3. The role of any staker (e.g. validator -> chilled, nominator -> validator, etc) + /// + /// Unlike `VoterList`, the values in this list are always kept up to date with reward and + /// slash as well, and thus represent the accurate approval stake of all account being + /// nominated by nominators. + /// + /// Note that while at the time of nomination, all targets are checked to be real + /// validators, they can chill at any point, and their approval stakes will still be + /// recorded. This implies that what comes out of iterating this list MIGHT NOT BE AN ACTIVE + /// VALIDATOR. + type TargetList: SortedListProvider>; - /// Number of eras to keep in history. - /// - /// Information is kept for eras in `[current_era - history_depth; current_era]`. - /// - /// Must be more than the number of eras delayed by session otherwise. I.e. active era must - /// always be in history. I.e. `active_era > current_era - history_depth` must be - /// guaranteed. - #[pallet::storage] - #[pallet::getter(fn history_depth)] - pub(crate) type HistoryDepth = StorageValue<_, u32, ValueQuery, HistoryDepthOnEmpty>; + /// The maximum number of `unlocking` chunks a [`StakingLedger`] can + /// have. Effectively determines how many unique eras a staker may be + /// unbonding in. + /// + /// Note: `MaxUnlockingChunks` is used as the upper bound for the + /// `BoundedVec` item `StakingLedger.unlocking`. Setting this value + /// lower than the existing value can lead to inconsistencies in the + /// `StakingLedger` and will need to be handled properly in a runtime + /// migration. The test `reducing_max_unlocking_chunks_abrupt` shows + /// this effect. + #[pallet::constant] + type MaxUnlockingChunks: Get; + + /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless + /// other pallets exist that are affected by slashing per-staker. + type OnStakerSlash: sp_staking::OnStakerSlash>; + + /// Some parameters of the benchmarking. + type BenchmarkingConfig: BenchmarkingConfig; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } - /// The ideal number of staking participants. + /// The ideal number of active validators. #[pallet::storage] #[pallet::getter(fn validator_count)] pub type ValidatorCount = StorageValue<_, u32, ValueQuery>; @@ -184,6 +279,7 @@ pub mod pallet { /// invulnerables) and restricted to testnets. #[pallet::storage] #[pallet::getter(fn invulnerables)] + #[pallet::unbounded] pub type Invulnerables = StorageValue<_, Vec, ValueQuery>; /// Map from all locked "stash" accounts to the controller account. @@ -199,11 +295,16 @@ pub mod pallet { #[pallet::storage] pub type MinValidatorBond = StorageValue<_, BalanceOf, ValueQuery>; + /// The minimum amount of commission that validators can set. + /// + /// If set to `0`, no limit exists. + #[pallet::storage] + pub type MinCommission = StorageValue<_, Perbill, ValueQuery>; + /// Map from all (unlocked) "controller" accounts to the info regarding the staking. #[pallet::storage] #[pallet::getter(fn ledger)] - pub type Ledger = - StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>>; + pub type Ledger = StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>; /// Where the reward payment should be made. Keyed by stash. #[pallet::storage] @@ -212,16 +313,10 @@ pub mod pallet { StorageMap<_, Twox64Concat, T::AccountId, RewardDestination, ValueQuery>; /// The map from (wannabe) validator stash key to the preferences of that validator. - /// - /// When updating this storage item, you must also update the `CounterForValidators`. #[pallet::storage] #[pallet::getter(fn validators)] pub type Validators = - StorageMap<_, Twox64Concat, T::AccountId, ValidatorPrefs, ValueQuery>; - - /// A tracker to keep count of the number of items in the `Validators` map. - #[pallet::storage] - pub type CounterForValidators = StorageValue<_, u32, ValueQuery>; + CountedStorageMap<_, Twox64Concat, T::AccountId, ValidatorPrefs, ValueQuery>; /// The maximum validator count before we stop allowing new validators to join. /// @@ -229,17 +324,26 @@ pub mod pallet { #[pallet::storage] pub type MaxValidatorsCount = StorageValue<_, u32, OptionQuery>; - /// The map from nominator stash key to the set of stash keys of all validators to nominate. + /// The map from nominator stash key to their nomination preferences, namely the validators that + /// they wish to support. + /// + /// Note that the keys of this storage map might become non-decodable in case the + /// [`Config::MaxNominations`] configuration is decreased. In this rare case, these nominators + /// are still existent in storage, their key is correct and retrievable (i.e. `contains_key` + /// indicates that they exist), but their value cannot be decoded. Therefore, the non-decodable + /// nominators will effectively not-exist, until they re-submit their preferences such that it + /// is within the bounds of the newly set `Config::MaxNominations`. + /// + /// This implies that `::iter_keys().count()` and `::iter().count()` might return different + /// values for this map. Moreover, the main `::count()` is aligned with the former, namely the + /// number of keys that exist. /// - /// When updating this storage item, you must also update the `CounterForNominators`. + /// Lastly, if any of the nominators become non-decodable, they can be chilled immediately via + /// [`Call::chill_other`] dispatchable by anyone. #[pallet::storage] #[pallet::getter(fn nominators)] pub type Nominators = - StorageMap<_, Twox64Concat, T::AccountId, Nominations>; - - /// A tracker to keep count of the number of items in the `Nominators` map. - #[pallet::storage] - pub type CounterForNominators = StorageValue<_, u32, ValueQuery>; + CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations>; /// The maximum nominator count before we stop allowing new validators to join. /// @@ -279,6 +383,7 @@ pub mod pallet { /// If stakers hasn't been set or has been removed then empty exposure is returned. #[pallet::storage] #[pallet::getter(fn eras_stakers)] + #[pallet::unbounded] pub type ErasStakers = StorageDoubleMap< _, Twox64Concat, @@ -301,6 +406,7 @@ pub mod pallet { /// Is it removed after `HISTORY_DEPTH` eras. /// If stakers hasn't been set or has been removed then empty exposure is returned. #[pallet::storage] + #[pallet::unbounded] #[pallet::getter(fn eras_stakers_clipped)] pub type ErasStakersClipped = StorageDoubleMap< _, @@ -340,6 +446,7 @@ pub mod pallet { /// Rewards for the last `HISTORY_DEPTH` eras. /// If reward hasn't been set or has been removed then 0 reward is returned. #[pallet::storage] + #[pallet::unbounded] #[pallet::getter(fn eras_reward_points)] pub type ErasRewardPoints = StorageMap<_, Twox64Concat, EraIndex, EraRewardPoints, ValueQuery>; @@ -371,6 +478,7 @@ pub mod pallet { /// All unapplied slashes that are queued for later. #[pallet::storage] + #[pallet::unbounded] pub type UnappliedSlashes = StorageMap< _, Twox64Concat, @@ -384,6 +492,7 @@ pub mod pallet { /// Must contains information for eras for the range: /// `[active_era - bounding_duration; active_era]` #[pallet::storage] + #[pallet::unbounded] pub(crate) type BondedEras = StorageValue<_, Vec<(EraIndex, SessionIndex)>, ValueQuery>; @@ -406,7 +515,9 @@ pub mod pallet { /// Slashing spans for stash accounts. #[pallet::storage] - pub(crate) type SlashingSpans = + #[pallet::getter(fn slashing_spans)] + #[pallet::unbounded] + pub type SlashingSpans = StorageMap<_, Twox64Concat, T::AccountId, slashing::SlashingSpans>; /// Records information about the maximum slash of a stash within a slashing span, @@ -420,10 +531,6 @@ pub mod pallet { ValueQuery, >; - /// The earliest era for which we have a pending, unapplied slash. - #[pallet::storage] - pub(crate) type EarliestUnappliedSlash = StorageValue<_, EraIndex>; - /// The last planned session scheduled by the session pallet. /// /// This is basically in sync with the call to [`pallet_session::SessionManager::new_session`]. @@ -431,6 +538,20 @@ 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. + /// + /// 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. + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn offending_validators)] + pub type OffendingValidators = StorageValue<_, Vec<(u32, bool)>, ValueQuery>; + /// True if network has been upgraded to this version. /// Storage version of the pallet. /// @@ -446,7 +567,6 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig { - pub history_depth: u32, pub validator_count: u32, pub minimum_validator_count: u32, pub invulnerables: Vec, @@ -457,13 +577,14 @@ pub mod pallet { Vec<(T::AccountId, T::AccountId, BalanceOf, crate::StakerStatus)>, pub min_nominator_bond: BalanceOf, pub min_validator_bond: BalanceOf, + pub max_validator_count: Option, + pub max_nominator_count: Option, } #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { GenesisConfig { - history_depth: 84u32, validator_count: Default::default(), minimum_validator_count: Default::default(), invulnerables: Default::default(), @@ -473,6 +594,8 @@ pub mod pallet { stakers: Default::default(), min_nominator_bond: Default::default(), min_validator_bond: Default::default(), + max_validator_count: None, + max_nominator_count: None, } } } @@ -480,7 +603,6 @@ pub mod pallet { #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { - HistoryDepth::::put(self.history_depth); ValidatorCount::::put(self.validator_count); MinimumValidatorCount::::put(self.minimum_validator_count); Invulnerables::::put(&self.invulnerables); @@ -490,30 +612,54 @@ pub mod pallet { StorageVersion::::put(Releases::V7_0_0); MinNominatorBond::::put(self.min_nominator_bond); MinValidatorBond::::put(self.min_validator_bond); + if let Some(x) = self.max_validator_count { + MaxValidatorsCount::::put(x); + } + if let Some(x) = self.max_nominator_count { + MaxNominatorsCount::::put(x); + } for &(ref stash, ref controller, balance, ref status) in &self.stakers { + crate::log!( + trace, + "inserting genesis staker: {:?} => {:?} => {:?}", + stash, + balance, + status + ); assert!( - T::Currency::free_balance(&stash) >= balance, + T::Currency::free_balance(stash) >= balance, "Stash does not have enough balance to bond." ); frame_support::assert_ok!(>::bond( - T::Origin::from(Some(stash.clone()).into()), + T::RuntimeOrigin::from(Some(stash.clone()).into()), T::Lookup::unlookup(controller.clone()), balance, RewardDestination::Staked, )); frame_support::assert_ok!(match status { crate::StakerStatus::Validator => >::validate( - T::Origin::from(Some(controller.clone()).into()), + T::RuntimeOrigin::from(Some(controller.clone()).into()), Default::default(), ), crate::StakerStatus::Nominator(votes) => >::nominate( - T::Origin::from(Some(controller.clone()).into()), + T::RuntimeOrigin::from(Some(controller.clone()).into()), votes.iter().map(|l| T::Lookup::unlookup(l.clone())).collect(), ), _ => Ok(()), }); + assert!( + ValidatorCount::::get() <= + ::MaxWinners::get() + ); } + + // all voters are reported to the `VoterList`. + assert_eq!( + T::VoterList::count(), + Nominators::::count() + Validators::::count(), + "not all genesis stakers were inserted into sorted list provider, something is wrong." + ); } } @@ -522,37 +668,39 @@ pub mod pallet { pub enum Event { /// The era payout has been set; the first balance is the validator-payout; the second is /// the remainder from the maximum amount of reward. - /// \[era_index, validator_payout, remainder\] - EraPaid(EraIndex, BalanceOf, BalanceOf), - /// The nominator has been rewarded by this amount. \[stash, amount\] - Rewarded(T::AccountId, BalanceOf), - /// One validator (and its nominators) has been slashed by the given amount. - /// \[validator, amount\] - Slashed(T::AccountId, BalanceOf), + EraPaid { era_index: EraIndex, validator_payout: BalanceOf, remainder: BalanceOf }, + /// The nominator has been rewarded by this amount. + Rewarded { stash: T::AccountId, amount: BalanceOf }, + /// A staker (validator or nominator) has been slashed by the given amount. + Slashed { staker: T::AccountId, amount: BalanceOf }, + /// A slash for the given validator, for the given percentage of their stake, at the given + /// era as been reported. + SlashReported { validator: T::AccountId, fraction: Perbill, slash_era: EraIndex }, /// An old slashing report from a prior era was discarded because it could - /// not be processed. \[session_index\] - OldSlashingReportDiscarded(SessionIndex), + /// not be processed. + OldSlashingReportDiscarded { session_index: SessionIndex }, /// A new set of stakers was elected. StakersElected, /// An account has bonded this amount. \[stash, amount\] /// /// NOTE: This event is only emitted when funds are bonded via a dispatchable. Notably, /// it will not be emitted for staking rewards when they are added to stake. - Bonded(T::AccountId, BalanceOf), - /// An account has unbonded this amount. \[stash, amount\] - Unbonded(T::AccountId, BalanceOf), + Bonded { stash: T::AccountId, amount: BalanceOf }, + /// An account has unbonded this amount. + Unbonded { stash: T::AccountId, amount: BalanceOf }, /// An account has called `withdraw_unbonded` and removed unbonding chunks worth `Balance` - /// from the unlocking queue. \[stash, amount\] - Withdrawn(T::AccountId, BalanceOf), - /// A nominator has been kicked from a validator. \[nominator, stash\] - Kicked(T::AccountId, T::AccountId), + /// from the unlocking queue. + Withdrawn { stash: T::AccountId, amount: BalanceOf }, + /// A nominator has been kicked from a validator. + Kicked { nominator: T::AccountId, stash: T::AccountId }, /// The election failed. No new era is planned. StakingElectionFailed, /// An account has stopped participating as either a validator or nominator. - /// \[stash\] - Chilled(T::AccountId), - /// The stakers' rewards are getting paid. \[era_index, validator_stash\] - PayoutStarted(EraIndex, T::AccountId), + Chilled { stash: T::AccountId }, + /// The stakers' rewards are getting paid. + PayoutStarted { era_index: EraIndex, validator_stash: T::AccountId }, + /// A validator has set their preferences. + ValidatorPrefsSet { stash: T::AccountId, prefs: ValidatorPrefs }, } #[pallet::error] @@ -571,7 +719,9 @@ pub mod pallet { DuplicateIndex, /// Slash record index out of bounds. InvalidSlashIndex, - /// Can not bond with value less than minimum required. + /// Cannot have a validator or nominator role, with value less than the minimum defined by + /// governance (see `MinValidatorBond` and `MinNominatorBond`). If unbonding is the + /// intention, `chill` first to remove one's role as validator/nominator. InsufficientBond, /// Can not schedule more unlock chunks. NoMoreChunks, @@ -602,30 +752,17 @@ pub mod pallet { /// There are too many nominators in the system. Governance needs to adjust the staking /// settings to keep things safe for the runtime. TooManyNominators, - /// There are too many validators in the system. Governance needs to adjust the staking - /// settings to keep things safe for the runtime. + /// There are too many validator candidates in the system. Governance needs to adjust the + /// staking settings to keep things safe for the runtime. TooManyValidators, + /// Commission is too low. Must be at least `MinCommission`. + CommissionTooLow, + /// Some bound is not met. + BoundNotMet, } #[pallet::hooks] impl Hooks> for Pallet { - fn on_runtime_upgrade() -> Weight { - if StorageVersion::::get() == Releases::V6_0_0 { - migrations::v7::migrate::() - } else { - T::DbWeight::get().reads(1) - } - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { - if StorageVersion::::get() == Releases::V6_0_0 { - migrations::v7::pre_migrate::() - } else { - Ok(()) - } - } - fn on_initialize(_now: BlockNumberFor) -> Weight { // just return the weight of the on_finalize. T::DbWeight::get().reads(1) @@ -646,6 +783,20 @@ pub mod pallet { } fn integrity_test() { + // ensure that we funnel the correct value to the `DataProvider::MaxVotesPerVoter`; + assert_eq!( + T::MaxNominations::get(), + ::MaxVotesPerVoter::get() + ); + // and that MaxNominations is always greater than 1, since we count on this. + assert!(!T::MaxNominations::get().is_zero()); + + // ensure election results are always bounded with the same value + assert!( + ::MaxWinners::get() == + ::MaxWinners::get() + ); + sp_std::if_std! { sp_io::TestExternalities::new_empty().execute_with(|| assert!( @@ -657,6 +808,11 @@ pub mod pallet { ); } } + + #[cfg(feature = "try-runtime")] + fn try_state(n: BlockNumberFor) -> Result<(), &'static str> { + Self::do_try_state(n) + } } #[pallet::call] @@ -678,28 +834,29 @@ pub mod pallet { /// unless the `origin` falls below _existential deposit_ and gets removed as dust. /// ------------------ /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::bond())] pub fn bond( origin: OriginFor, - controller: ::Source, + controller: AccountIdLookupOf, #[pallet::compact] value: BalanceOf, payee: RewardDestination, ) -> DispatchResult { let stash = ensure_signed(origin)?; if >::contains_key(&stash) { - Err(Error::::AlreadyBonded)? + return Err(Error::::AlreadyBonded.into()) } let controller = T::Lookup::lookup(controller)?; if >::contains_key(&controller) { - Err(Error::::AlreadyPaired)? + return Err(Error::::AlreadyPaired.into()) } // Reject a bond which is considered to be _dust_. if value < T::Currency::minimum_balance() { - Err(Error::::InsufficientBond)? + return Err(Error::::InsufficientBond.into()) } frame_system::Pallet::::inc_consumers(&stash).map_err(|_| Error::::BadState)?; @@ -710,18 +867,23 @@ pub mod pallet { >::insert(&stash, payee); let current_era = CurrentEra::::get().unwrap_or(0); - let history_depth = Self::history_depth(); + let history_depth = T::HistoryDepth::get(); let last_reward_era = current_era.saturating_sub(history_depth); let stash_balance = T::Currency::free_balance(&stash); let value = value.min(stash_balance); - Self::deposit_event(Event::::Bonded(stash.clone(), value)); + Self::deposit_event(Event::::Bonded { stash: stash.clone(), amount: value }); let item = StakingLedger { stash, total: value, active: value, - unlocking: vec![], - claimed_rewards: (last_reward_era..current_era).collect(), + unlocking: Default::default(), + claimed_rewards: (last_reward_era..current_era) + .try_collect() + // Since last_reward_era is calculated as `current_era - + // HistoryDepth`, following bound is always expected to be + // satisfied. + .defensive_map_err(|_| Error::::BoundNotMet)?, }; Self::update_ledger(&controller, &item); Ok(()) @@ -742,6 +904,7 @@ pub mod pallet { /// - Independent of the arguments. Insignificant complexity. /// - O(1). /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::bond_extra())] pub fn bond_extra( origin: OriginFor, @@ -763,8 +926,15 @@ pub mod pallet { Error::::InsufficientBond ); - Self::deposit_event(Event::::Bonded(stash, extra)); + // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); + // 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(&ledger.stash)).defensive(); + } + + Self::deposit_event(Event::::Bonded { stash, amount: extra }); } Ok(()) } @@ -778,7 +948,7 @@ pub mod pallet { /// Once the unlock period is done, you can call `withdraw_unbonded` to actually move /// the funds out of management ready for transfer. /// - /// No more than a limited number of unlocking chunks (see `MAX_UNLOCKING_CHUNKS`) + /// No more than a limited number of unlocking chunks (see `MaxUnlockingChunks`) /// can co-exists at the same time. In that case, [`Call::withdraw_unbonded`] need /// to be called first to remove some of the chunks (if possible). /// @@ -788,6 +958,7 @@ pub mod pallet { /// Emits `Unbonded`. /// /// See also [`Call::withdraw_unbonded`]. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::unbond())] pub fn unbond( origin: OriginFor, @@ -795,7 +966,10 @@ pub mod pallet { ) -> DispatchResult { let controller = ensure_signed(origin)?; let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; - ensure!(ledger.unlocking.len() < MAX_UNLOCKING_CHUNKS, Error::::NoMoreChunks,); + ensure!( + ledger.unlocking.len() < T::MaxUnlockingChunks::get() as usize, + Error::::NoMoreChunks, + ); let mut value = value.min(ledger.active); @@ -822,9 +996,29 @@ pub mod pallet { // Note: in case there is no current era it is fine to bond one era more. let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get(); - ledger.unlocking.push(UnlockChunk { value, era }); + if let Some(mut chunk) = + ledger.unlocking.last_mut().filter(|chunk| chunk.era == era) + { + // To keep the chunk count down, we only keep one chunk per era. Since + // `unlocking` is a FiFo queue, if a chunk exists for `era` we know that it will + // be the last one. + chunk.value = chunk.value.defensive_saturating_add(value) + } else { + ledger + .unlocking + .try_push(UnlockChunk { value, era }) + .map_err(|_| Error::::NoMoreChunks)?; + }; + // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - Self::deposit_event(Event::::Unbonded(ledger.stash, value)); + + // update this staker in the sorted list, if they exist in it. + if T::VoterList::contains(&ledger.stash) { + let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) + .defensive(); + } + + Self::deposit_event(Event::::Unbonded { stash: ledger.stash, amount: value }); } Ok(()) } @@ -844,6 +1038,7 @@ pub mod pallet { /// Complexity O(S) where S is the number of slashing spans to remove /// NOTE: Weight annotation is the kill scenario, we refund otherwise. /// # + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans))] pub fn withdraw_unbonded( origin: OriginFor, @@ -880,7 +1075,7 @@ pub mod pallet { if ledger.total < old_total { // Already checked that this won't overflow by entry condition. let value = old_total - ledger.total; - Self::deposit_event(Event::::Withdrawn(stash, value)); + Self::deposit_event(Event::::Withdrawn { stash, amount: value }); } Ok(post_info_weight.into()) @@ -891,14 +1086,19 @@ pub mod pallet { /// Effects will be felt at the beginning of the next era. /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::validate())] pub fn validate(origin: OriginFor, prefs: ValidatorPrefs) -> DispatchResult { let controller = ensure_signed(origin)?; let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + ensure!(ledger.active >= MinValidatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; + // ensure their commission is correct. + ensure!(prefs.commission >= MinCommission::::get(), Error::::CommissionTooLow); + // Only check limits if they are not already a validator. if !Validators::::contains_key(stash) { // If this error is reached, we need to adjust the `MinValidatorBond` and start @@ -906,14 +1106,16 @@ pub mod pallet { // the runtime. if let Some(max_validators) = MaxValidatorsCount::::get() { ensure!( - CounterForValidators::::get() < max_validators, + Validators::::count() < max_validators, Error::::TooManyValidators ); } } Self::do_remove_nominator(stash); - Self::do_add_validator(stash, prefs); + Self::do_add_validator(stash, prefs.clone()); + Self::deposit_event(Event::::ValidatorPrefsSet { stash: ledger.stash, prefs }); + Ok(()) } @@ -925,13 +1127,14 @@ pub mod pallet { /// /// # /// - The transaction's complexity is proportional to the size of `targets` (N) - /// which is capped at CompactAssignments::LIMIT (MAX_NOMINATIONS). + /// which is capped at CompactAssignments::LIMIT (T::MaxNominations). /// - Both the reads and writes follow a similar pattern. /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::nominate(targets.len() as u32))] pub fn nominate( origin: OriginFor, - targets: Vec<::Source>, + targets: Vec>, ) -> DispatchResult { let controller = ensure_signed(origin)?; @@ -946,18 +1149,18 @@ pub mod pallet { // the runtime. if let Some(max_nominators) = MaxNominatorsCount::::get() { ensure!( - CounterForNominators::::get() < max_nominators, + Nominators::::count() < max_nominators, Error::::TooManyNominators ); } } ensure!(!targets.is_empty(), Error::::EmptyTargets); - ensure!(targets.len() <= T::MAX_NOMINATIONS as usize, Error::::TooManyTargets); + ensure!(targets.len() <= T::MaxNominations::get() as usize, Error::::TooManyTargets); - let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets); + let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets.into_inner()); - let targets = targets + let targets: BoundedVec<_, _> = targets .into_iter() .map(|t| T::Lookup::lookup(t).map_err(DispatchError::from)) .map(|n| { @@ -969,11 +1172,13 @@ pub mod pallet { } }) }) - .collect::, _>>()?; + .collect::, _>>()? + .try_into() + .map_err(|_| Error::::TooManyNominators)?; let nominations = Nominations { targets, - // Initial nominations are considered submitted at era 0. See `Nominations` doc + // Initial nominations are considered submitted at era 0. See `Nominations` doc. submitted_in: Self::current_era().unwrap_or(0), suppressed: false, }; @@ -994,6 +1199,7 @@ pub mod pallet { /// - Contains one read. /// - Writes are limited to the `origin` account key. /// # + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::chill())] pub fn chill(origin: OriginFor) -> DispatchResult { let controller = ensure_signed(origin)?; @@ -1004,7 +1210,7 @@ pub mod pallet { /// (Re-)set the payment target for a controller. /// - /// Effects will be felt at the beginning of the next era. + /// Effects will be felt instantly (as soon as this function is completed successfully). /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// @@ -1018,6 +1224,7 @@ pub mod pallet { /// - Read: Ledger /// - Write: Payee /// # + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::set_payee())] pub fn set_payee( origin: OriginFor, @@ -1032,7 +1239,7 @@ pub mod pallet { /// (Re-)set the controller of a stash. /// - /// Effects will be felt at the beginning of the next era. + /// Effects will be felt instantly (as soon as this function is completed successfully). /// /// The dispatch origin for this call must be _Signed_ by the stash, not the controller. /// @@ -1046,16 +1253,17 @@ pub mod pallet { /// - Read: Bonded, Ledger New Controller, Ledger Old Controller /// - Write: Bonded, Ledger New Controller, Ledger Old Controller /// # + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::set_controller())] pub fn set_controller( origin: OriginFor, - controller: ::Source, + controller: AccountIdLookupOf, ) -> DispatchResult { let stash = ensure_signed(origin)?; let old_controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; let controller = T::Lookup::lookup(controller)?; if >::contains_key(&controller) { - Err(Error::::AlreadyPaired)? + return Err(Error::::AlreadyPaired.into()) } if controller != old_controller { >::insert(&stash, &controller); @@ -1074,44 +1282,70 @@ pub mod pallet { /// Weight: O(1) /// Write: Validator Count /// # + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn set_validator_count( origin: OriginFor, #[pallet::compact] new: u32, ) -> DispatchResult { ensure_root(origin)?; + // ensure new validator count does not exceed maximum winners + // support by election provider. + ensure!( + new <= ::MaxWinners::get(), + Error::::TooManyValidators + ); ValidatorCount::::put(new); Ok(()) } - /// Increments the ideal number of validators. + /// Increments the ideal number of validators upto maximum of + /// `ElectionProviderBase::MaxWinners`. /// /// The dispatch origin must be Root. /// /// # /// Same as [`Self::set_validator_count`]. /// # + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn increase_validator_count( origin: OriginFor, #[pallet::compact] additional: u32, ) -> DispatchResult { ensure_root(origin)?; - ValidatorCount::::mutate(|n| *n += additional); + let old = ValidatorCount::::get(); + let new = old.checked_add(additional).ok_or(ArithmeticError::Overflow)?; + ensure!( + new <= ::MaxWinners::get(), + Error::::TooManyValidators + ); + + ValidatorCount::::put(new); Ok(()) } - /// Scale up the ideal number of validators by a factor. + /// Scale up the ideal number of validators by a factor upto maximum of + /// `ElectionProviderBase::MaxWinners`. /// /// The dispatch origin must be Root. /// /// # /// Same as [`Self::set_validator_count`]. /// # + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn scale_validator_count(origin: OriginFor, factor: Percent) -> DispatchResult { ensure_root(origin)?; - ValidatorCount::::mutate(|n| *n += factor * *n); + let old = ValidatorCount::::get(); + let new = old.checked_add(factor.mul_floor(old)).ok_or(ArithmeticError::Overflow)?; + + ensure!( + new <= ::MaxWinners::get(), + Error::::TooManyValidators + ); + + ValidatorCount::::put(new); Ok(()) } @@ -1130,6 +1364,7 @@ pub mod pallet { /// - Weight: O(1) /// - Write: ForceEra /// # + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::force_no_eras())] pub fn force_no_eras(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -1153,6 +1388,7 @@ pub mod pallet { /// - Weight: O(1) /// - Write ForceEra /// # + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::force_new_era())] pub fn force_new_era(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -1163,11 +1399,7 @@ pub mod pallet { /// Set the validators who cannot be slashed (if any). /// /// The dispatch origin must be Root. - /// - /// # - /// - O(V) - /// - Write: Invulnerables - /// # + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::set_invulnerables(invulnerables.len() as u32))] pub fn set_invulnerables( origin: OriginFor, @@ -1181,13 +1413,7 @@ pub mod pallet { /// Force a current staker to become completely unstaked, immediately. /// /// The dispatch origin must be Root. - /// - /// # - /// O(S) where S is the number of slashing spans to be removed - /// Reads: Bonded, Slashing Spans, Account, Locks - /// Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators, - /// Account, Locks Writes Each: SpanSlash * S - /// # + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::force_unstake(*num_slashing_spans))] pub fn force_unstake( origin: OriginFor, @@ -1213,11 +1439,7 @@ pub mod pallet { /// The election process starts multiple blocks before the end of the era. /// If this is called just before a new era is triggered, the election process may not /// have enough blocks to get a result. - /// - /// # - /// - Weight: O(1) - /// - Write: ForceEra - /// # + #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::force_new_era_always())] pub fn force_new_era_always(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -1230,14 +1452,7 @@ pub mod pallet { /// Can be called by the `T::SlashCancelOrigin`. /// /// Parameters: era and indices of the slashes for that era to kill. - /// - /// # - /// Complexity: O(U + S) - /// with U unapplied slashes weighted with U=1000 - /// and S is the number of slash indices to be canceled. - /// - Read: Unapplied Slashes - /// - Write: Unapplied Slashes - /// # + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::cancel_deferred_slash(slash_indices.len() as u32))] pub fn cancel_deferred_slash( origin: OriginFor, @@ -1283,6 +1498,7 @@ pub mod pallet { /// NOTE: weights are assuming that payouts are made to alive stash account (Staked). /// Paying even a dead controller is cheaper weight-wise. We don't do any refunds here. /// # + #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::payout_stakers_alive_staked( T::MaxNominatorRewardedPerValidator::get() ))] @@ -1301,10 +1517,11 @@ pub mod pallet { /// /// # /// - Time complexity: O(L), where L is unlocking chunks - /// - Bounded by `MAX_UNLOCKING_CHUNKS`. + /// - Bounded by `MaxUnlockingChunks`. /// - Storage changes: Can't increase storage, only decrease it. /// # - #[pallet::weight(T::WeightInfo::rebond(MAX_UNLOCKING_CHUNKS as u32))] + #[pallet::call_index(19)] + #[pallet::weight(T::WeightInfo::rebond(T::MaxUnlockingChunks::get() as u32))] pub fn rebond( origin: OriginFor, #[pallet::compact] value: BalanceOf, @@ -1314,12 +1531,21 @@ pub mod pallet { ensure!(!ledger.unlocking.is_empty(), Error::::NoUnlockChunk); let initial_unlocking = ledger.unlocking.len() as u32; - let ledger = ledger.rebond(value); + let (ledger, rebonded_value) = ledger.rebond(value); // Last check: the new active amount of ledger must be more than ED. ensure!(ledger.active >= T::Currency::minimum_balance(), Error::::InsufficientBond); - Self::deposit_event(Event::::Bonded(ledger.stash.clone(), value)); + Self::deposit_event(Event::::Bonded { + stash: ledger.stash.clone(), + amount: rebonded_value, + }); + + // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); + if T::VoterList::contains(&ledger.stash) { + let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) + .defensive(); + } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed .saturating_add(initial_unlocking) @@ -1327,75 +1553,38 @@ pub mod pallet { Ok(Some(T::WeightInfo::rebond(removed_chunks)).into()) } - /// Set `HistoryDepth` value. This function will delete any history information - /// when `HistoryDepth` is reduced. - /// - /// Parameters: - /// - `new_history_depth`: The new history depth you would like to set. - /// - `era_items_deleted`: The number of items that will be deleted by this dispatch. This - /// should report all the storage items that will be deleted by clearing old era history. - /// Needed to report an accurate weight for the dispatch. Trusted by `Root` to report an - /// accurate number. - /// - /// Origin must be root. + /// Remove all data structures concerning a staker/stash once it is at a state where it can + /// be considered `dust` in the staking system. The requirements are: /// - /// # - /// - E: Number of history depths removed, i.e. 10 -> 7 = 3 - /// - Weight: O(E) - /// - DB Weight: - /// - Reads: Current Era, History Depth - /// - Writes: History Depth - /// - Clear Prefix Each: Era Stakers, EraStakersClipped, ErasValidatorPrefs - /// - Writes Each: ErasValidatorReward, ErasRewardPoints, ErasTotalStake, - /// ErasStartSessionIndex - /// # - #[pallet::weight(T::WeightInfo::set_history_depth(*_era_items_deleted))] - pub fn set_history_depth( - origin: OriginFor, - #[pallet::compact] new_history_depth: EraIndex, - #[pallet::compact] _era_items_deleted: u32, - ) -> DispatchResult { - ensure_root(origin)?; - if let Some(current_era) = Self::current_era() { - HistoryDepth::::mutate(|history_depth| { - let last_kept = current_era.checked_sub(*history_depth).unwrap_or(0); - let new_last_kept = current_era.checked_sub(new_history_depth).unwrap_or(0); - for era_index in last_kept..new_last_kept { - Self::clear_era_information(era_index); - } - *history_depth = new_history_depth - }) - } - Ok(()) - } - - /// Remove all data structure concerning a staker/stash once its balance is at the minimum. - /// This is essentially equivalent to `withdraw_unbonded` except it can be called by anyone - /// and the target `stash` must have no funds left beyond the ED. + /// 1. the `total_balance` of the stash is below existential deposit. + /// 2. or, the `ledger.total` of the stash is below existential deposit. /// - /// This can be called from any origin. + /// The former can happen in cases like a slash; the latter when a fully unbonded account + /// is still receiving staking rewards in `RewardDestination::Staked`. /// - /// - `stash`: The stash account to reap. Its balance must be zero. + /// It can be called by anyone, as long as `stash` meets the above requirements. /// - /// # - /// Complexity: O(S) where S is the number of slashing spans on the account. - /// DB Weight: - /// - Reads: Stash Account, Bonded, Slashing Spans, Locks - /// - Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators, - /// Stash Account, Locks - /// - Writes Each: SpanSlash * S - /// # + /// Refunds the transaction fees upon successful execution. + #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::reap_stash(*num_slashing_spans))] pub fn reap_stash( - _origin: OriginFor, + origin: OriginFor, stash: T::AccountId, num_slashing_spans: u32, - ) -> DispatchResult { - let at_minimum = T::Currency::total_balance(&stash) == T::Currency::minimum_balance(); - ensure!(at_minimum, Error::::FundedTarget); + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + + let ed = T::Currency::minimum_balance(); + let reapable = T::Currency::total_balance(&stash) < ed || + Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) + .map(|l| l.total) + .unwrap_or_default() < ed; + ensure!(reapable, Error::::FundedTarget); + Self::kill_stash(&stash, num_slashing_spans)?; T::Currency::remove_lock(STAKING_ID, &stash); - Ok(()) + + Ok(Pays::No.into()) } /// Remove the given nominations from the calling validator. @@ -1409,11 +1598,9 @@ pub mod pallet { /// /// Note: Making this call only makes sense if you first set the validator preferences to /// block any further nominations. + #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::kick(who.len() as u32))] - pub fn kick( - origin: OriginFor, - who: Vec<::Source>, - ) -> DispatchResult { + pub fn kick(origin: OriginFor, who: Vec>) -> DispatchResult { let controller = ensure_signed(origin)?; let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let stash = &ledger.stash; @@ -1428,10 +1615,10 @@ pub mod pallet { if let Some(ref mut nom) = maybe_nom { if let Some(pos) = nom.targets.iter().position(|v| v == stash) { nom.targets.swap_remove(pos); - Self::deposit_event(Event::::Kicked( - nom_stash.clone(), - stash.clone(), - )); + Self::deposit_event(Event::::Kicked { + nominator: nom_stash.clone(), + stash: stash.clone(), + }); } } }); @@ -1440,7 +1627,7 @@ pub mod pallet { Ok(()) } - /// Update the various staking limits this pallet. + /// Update the various staking configurations . /// /// * `min_nominator_bond`: The minimum active bond needed to be a nominator. /// * `min_validator_bond`: The minimum active bond needed to be a validator. @@ -1448,26 +1635,49 @@ pub mod pallet { /// set to `None`, no limit is enforced. /// * `max_validator_count`: The max number of users who can be a validator at once. When /// set to `None`, no limit is enforced. + /// * `chill_threshold`: The ratio of `max_nominator_count` or `max_validator_count` which + /// should be filled in order for the `chill_other` transaction to work. + /// * `min_commission`: The minimum amount of commission that each validators must maintain. + /// This is checked only upon calling `validate`. Existing validators are not affected. /// - /// Origin must be Root to call this function. + /// RuntimeOrigin must be Root to call this function. /// /// NOTE: Existing nominators and validators will not be affected by this update. /// to kick people under the new limits, `chill_other` should be called. - #[pallet::weight(T::WeightInfo::set_staking_limits())] - pub fn set_staking_limits( + // We assume the worst case for this call is either: all items are set or all items are + // removed. + #[pallet::call_index(22)] + #[pallet::weight( + T::WeightInfo::set_staking_configs_all_set() + .max(T::WeightInfo::set_staking_configs_all_remove()) + )] + pub fn set_staking_configs( origin: OriginFor, - min_nominator_bond: BalanceOf, - min_validator_bond: BalanceOf, - max_nominator_count: Option, - max_validator_count: Option, - threshold: Option, + min_nominator_bond: ConfigOp>, + min_validator_bond: ConfigOp>, + max_nominator_count: ConfigOp, + max_validator_count: ConfigOp, + chill_threshold: ConfigOp, + min_commission: ConfigOp, ) -> DispatchResult { ensure_root(origin)?; - MinNominatorBond::::set(min_nominator_bond); - MinValidatorBond::::set(min_validator_bond); - MaxNominatorsCount::::set(max_nominator_count); - MaxValidatorsCount::::set(max_validator_count); - ChillThreshold::::set(threshold); + + macro_rules! config_op_exp { + ($storage:ty, $op:ident) => { + match $op { + ConfigOp::Noop => (), + ConfigOp::Set(v) => <$storage>::put(v), + ConfigOp::Remove => <$storage>::kill(), + } + }; + } + + config_op_exp!(MinNominatorBond, min_nominator_bond); + config_op_exp!(MinValidatorBond, min_validator_bond); + config_op_exp!(MaxNominatorsCount, max_nominator_count); + config_op_exp!(MaxValidatorsCount, max_validator_count); + config_op_exp!(ChillThreshold, chill_threshold); + config_op_exp!(MinCommission, min_commission); Ok(()) } @@ -1482,6 +1692,11 @@ pub mod pallet { /// /// If the caller is different than the controller being targeted, the following conditions /// must be met: + /// + /// * `controller` must belong to a nominator who has become non-decodable, + /// + /// Or: + /// /// * A `ChillThreshold` must be set and checked which defines how close to the max /// nominators or validators we must reach before users can start chilling one-another. /// * A `MaxNominatorCount` and `MaxValidatorCount` must be set which is used to determine @@ -1492,8 +1707,7 @@ pub mod pallet { /// /// This can be helpful if bond requirements are updated, and we need to remove old users /// who do not satisfy these requirements. - // TODO: Maybe we can deprecate `chill` in the future. - // https://github.com/paritytech/substrate/issues/9111 + #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::chill_other())] pub fn chill_other(origin: OriginFor, controller: T::AccountId) -> DispatchResult { // Anyone can call this function. @@ -1502,6 +1716,11 @@ pub mod pallet { let stash = ledger.stash; // In order for one user to chill another user, the following conditions must be met: + // + // * `controller` belongs to a nominator who has become non-decodable, + // + // Or + // // * A `ChillThreshold` is set which defines how close to the max nominators or // validators we must reach before users can start chilling one-another. // * A `MaxNominatorCount` and `MaxValidatorCount` which is used to determine how close @@ -1511,12 +1730,18 @@ pub mod pallet { // threshold bond required. // // Otherwise, if caller is the same as the controller, this is just like `chill`. + + if Nominators::::contains_key(&stash) && Nominators::::get(&stash).is_none() { + Self::chill_stash(&stash); + return Ok(()) + } + if caller != controller { let threshold = ChillThreshold::::get().ok_or(Error::::CannotChillOther)?; let min_active_bond = if Nominators::::contains_key(&stash) { let max_nominator_count = MaxNominatorsCount::::get().ok_or(Error::::CannotChillOther)?; - let current_nominator_count = CounterForNominators::::get(); + let current_nominator_count = Nominators::::count(); ensure!( threshold * max_nominator_count < current_nominator_count, Error::::CannotChillOther @@ -1525,7 +1750,7 @@ pub mod pallet { } else if Validators::::contains_key(&stash) { let max_validator_count = MaxValidatorsCount::::get().ok_or(Error::::CannotChillOther)?; - let current_validator_count = CounterForValidators::::get(); + let current_validator_count = Validators::::count(); ensure!( threshold * max_validator_count < current_validator_count, Error::::CannotChillOther @@ -1541,6 +1766,29 @@ pub mod pallet { Self::chill_stash(&stash); Ok(()) } + + /// Force a validator to have at least the minimum commission. This will not affect a + /// validator who already has a commission greater than or equal to the minimum. Any account + /// can call this. + #[pallet::call_index(24)] + #[pallet::weight(T::WeightInfo::force_apply_min_commission())] + pub fn force_apply_min_commission( + origin: OriginFor, + validator_stash: T::AccountId, + ) -> DispatchResult { + ensure_signed(origin)?; + let min_commission = MinCommission::::get(); + Validators::::try_mutate_exists(validator_stash, |maybe_prefs| { + maybe_prefs + .as_mut() + .map(|prefs| { + (prefs.commission < min_commission) + .then(|| prefs.commission = min_commission) + }) + .ok_or(Error::::NotStash) + })?; + Ok(()) + } } } diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 15ca85b4d046f..aeea0a1a58c63 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,22 +47,23 @@ //! has multiple misbehaviors. However, accounting for such cases is necessary //! to deter a class of "rage-quit" attacks. //! -//! Based on research at +//! Based on research at use crate::{ - BalanceOf, Config, EraIndex, Error, Exposure, NegativeImbalanceOf, Pallet, Perbill, - SessionInterface, Store, UnappliedSlash, + BalanceOf, Config, Error, Exposure, NegativeImbalanceOf, Pallet, Perbill, SessionInterface, + Store, UnappliedSlash, }; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ ensure, - traits::{Currency, Imbalance, OnUnbalanced}, + traits::{Currency, Defensive, Get, Imbalance, OnUnbalanced}, }; use scale_info::TypeInfo; use sp_runtime::{ traits::{Saturating, Zero}, DispatchResult, RuntimeDebug, }; +use sp_staking::{offence::DisableStrategy, EraIndex}; use sp_std::vec::Vec; /// The proportion of the slashing reward to be paid out on the first slashing detection. @@ -181,7 +182,7 @@ impl SlashingSpans { } /// A slashing-span record for a particular stash. -#[derive(Encode, Decode, Default, TypeInfo)] +#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)] pub(crate) struct SpanRecord { slashed: Balance, paid_out: Balance, @@ -190,7 +191,7 @@ pub(crate) struct SpanRecord { impl SpanRecord { /// The value of stash balance slashed in this span. #[cfg(test)] - pub(crate) fn amount_slashed(&self) -> &Balance { + pub(crate) fn amount(&self) -> &Balance { &self.slashed } } @@ -213,6 +214,8 @@ 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 @@ -224,29 +227,30 @@ pub(crate) struct SlashParams<'a, T: 'a + Config> { pub(crate) fn compute_slash( params: SlashParams, ) -> Option>> { - let SlashParams { stash, slash, exposure, slash_era, window_start, now, reward_proportion } = - params.clone(); - let mut reward_payout = Zero::zero(); let mut val_slashed = Zero::zero(); // is the slash amount here a maximum for the era? - let own_slash = slash * exposure.own; - if slash * exposure.total == Zero::zero() { + let own_slash = params.slash * params.exposure.own; + if params.slash * params.exposure.total == Zero::zero() { // kick out the validator even if they won't be slashed, // as long as the misbehavior is from their most recent slashing span. kick_out_if_recent::(params); return None } - let (prior_slash_p, _era_slash) = - as Store>::ValidatorSlashInEra::get(&slash_era, stash) - .unwrap_or((Perbill::zero(), Zero::zero())); + let prior_slash_p = + as Store>::ValidatorSlashInEra::get(¶ms.slash_era, params.stash) + .map_or(Zero::zero(), |(prior_slash_proportion, _)| prior_slash_proportion); // compare slash proportions rather than slash values to avoid issues due to rounding // error. - if slash.deconstruct() > prior_slash_p.deconstruct() { - as Store>::ValidatorSlashInEra::insert(&slash_era, stash, &(slash, own_slash)); + if params.slash.deconstruct() > prior_slash_p.deconstruct() { + as Store>::ValidatorSlashInEra::insert( + ¶ms.slash_era, + params.stash, + &(params.slash, own_slash), + ); } else { // we slash based on the max in era - this new event is not the max, // so neither the validator or any nominators will need an update. @@ -261,14 +265,14 @@ pub(crate) fn compute_slash( // apply slash to validator. { let mut spans = fetch_spans::( - stash, - window_start, + params.stash, + params.window_start, &mut reward_payout, &mut val_slashed, - reward_proportion, + params.reward_proportion, ); - let target_span = spans.compare_and_update_span_slash(slash_era, own_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 @@ -276,22 +280,19 @@ pub(crate) fn compute_slash( // chill the validator - it misbehaved in the current span and should // not continue in the next election. also end the slashing span. - spans.end_span(now); - >::chill_stash(stash); - - // make sure to disable validator till the end of this session - if T::SessionInterface::disable_validator(stash).unwrap_or(false) { - // force a new era, to select a new validator set - >::ensure_new_era() - } + 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); + let mut nominators_slashed = Vec::new(); - reward_payout += slash_nominators::(params, prior_slash_p, &mut nominators_slashed); + reward_payout += slash_nominators::(params.clone(), prior_slash_p, &mut nominators_slashed); Some(UnappliedSlash { - validator: stash.clone(), + validator: params.stash.clone(), own: val_slashed, others: nominators_slashed, reporters: Vec::new(), @@ -316,13 +317,52 @@ fn kick_out_if_recent(params: SlashParams) { if spans.era_span(params.slash_era).map(|s| s.index) == Some(spans.span_index()) { 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 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) { + as Store>::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; - // make sure to disable validator till the end of this session - if T::SessionInterface::disable_validator(params.stash).unwrap_or(false) { - // force a new era, to select a new validator set - >::ensure_new_era() + 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); + } + }, } - } + }); } /// Slash nominators. Accepts general parameters and the prior slash percentage of the validator. @@ -333,13 +373,10 @@ fn slash_nominators( prior_slash_p: Perbill, nominators_slashed: &mut Vec<(T::AccountId, BalanceOf)>, ) -> BalanceOf { - let SlashParams { stash: _, slash, exposure, slash_era, window_start, now, reward_proportion } = - params; - let mut reward_payout = Zero::zero(); - nominators_slashed.reserve(exposure.others.len()); - for nominator in &exposure.others { + nominators_slashed.reserve(params.exposure.others.len()); + for nominator in ¶ms.exposure.others { let stash = &nominator.who; let mut nom_slashed = Zero::zero(); @@ -347,15 +384,14 @@ fn slash_nominators( // had a new max slash for the era. let era_slash = { let own_slash_prior = prior_slash_p * nominator.value; - let own_slash_by_validator = slash * nominator.value; + let own_slash_by_validator = params.slash * nominator.value; let own_slash_difference = own_slash_by_validator.saturating_sub(own_slash_prior); - let mut era_slash = as Store>::NominatorSlashInEra::get(&slash_era, stash) - .unwrap_or_else(|| Zero::zero()); - + let mut era_slash = + as Store>::NominatorSlashInEra::get(¶ms.slash_era, stash) + .unwrap_or_else(Zero::zero); era_slash += own_slash_difference; - - as Store>::NominatorSlashInEra::insert(&slash_era, stash, &era_slash); + as Store>::NominatorSlashInEra::insert(¶ms.slash_era, stash, &era_slash); era_slash }; @@ -364,21 +400,19 @@ fn slash_nominators( { let mut spans = fetch_spans::( stash, - window_start, + params.window_start, &mut reward_payout, &mut nom_slashed, - reward_proportion, + params.reward_proportion, ); - let target_span = spans.compare_and_update_span_slash(slash_era, era_slash); + let target_span = spans.compare_and_update_span_slash(params.slash_era, era_slash); if target_span == Some(spans.span_index()) { - // End the span, but don't chill the nominator. its nomination - // on this validator will be ignored in the future. - spans.end_span(now); + // end the span, but don't chill the nominator. + spans.end_span(params.now); } } - nominators_slashed.push((stash.clone(), nom_slashed)); } @@ -519,7 +553,9 @@ impl<'a, T: 'a + Config> Drop for InspectingSpans<'a, T> { /// Clear slashing metadata for an obsolete era. pub(crate) fn clear_era_metadata(obsolete_era: EraIndex) { + #[allow(deprecated)] as Store>::ValidatorSlashInEra::remove_prefix(&obsolete_era, None); + #[allow(deprecated)] as Store>::NominatorSlashInEra::remove_prefix(&obsolete_era, None); } @@ -560,9 +596,10 @@ pub fn do_slash( value: BalanceOf, reward_payout: &mut BalanceOf, slashed_imbalance: &mut NegativeImbalanceOf, + slash_era: EraIndex, ) { - let controller = match >::bonded(stash) { - None => return, // defensive: should always exist. + let controller = match >::bonded(stash).defensive() { + None => return, Some(c) => c, }; @@ -571,7 +608,7 @@ pub fn do_slash( None => return, // nothing to do. }; - let value = ledger.slash(value, T::Currency::minimum_balance()); + let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era); if !value.is_zero() { let (imbalance, missing) = T::Currency::slash(stash, value); @@ -585,12 +622,18 @@ pub fn do_slash( >::update_ledger(&controller, &ledger); // trigger the event - >::deposit_event(super::Event::::Slashed(stash.clone(), value)); + >::deposit_event(super::Event::::Slashed { + staker: stash.clone(), + amount: value, + }); } } /// Apply a previously-unapplied slash. -pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash>) { +pub(crate) fn apply_slash( + unapplied_slash: UnappliedSlash>, + slash_era: EraIndex, +) { let mut slashed_imbalance = NegativeImbalanceOf::::zero(); let mut reward_payout = unapplied_slash.payout; @@ -599,10 +642,17 @@ pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash(&nominator, nominator_slash, &mut reward_payout, &mut slashed_imbalance); + do_slash::( + nominator, + nominator_slash, + &mut reward_payout, + &mut slashed_imbalance, + slash_era, + ); } pay_reporters::(reward_payout, slashed_imbalance, &unapplied_slash.reporters); diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 795c066d09bb3..0e0ac76523471 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,6 +27,7 @@ use rand_chacha::{ }; use sp_io::hashing::blake2_256; +use frame_election_provider_support::SortedListProvider; use frame_support::{pallet_prelude::*, traits::Currency}; use sp_runtime::{traits::StaticLookup, Perbill}; use sp_std::prelude::*; @@ -35,10 +36,15 @@ const SEED: u32 = 0; /// This function removes all validators and nominators from storage. pub fn clear_validators_and_nominators() { - Validators::::remove_all(None); - CounterForValidators::::kill(); - Nominators::::remove_all(None); - CounterForNominators::::kill(); + #[allow(deprecated)] + Validators::::remove_all(); + + // whenever we touch nominators counter we should update `T::VoterList` as well. + #[allow(deprecated)] + Nominators::::remove_all(); + + // NOTE: safe to call outside block production + T::VoterList::unsafe_clear(); } /// Grab a funded user. @@ -49,9 +55,18 @@ pub fn create_funded_user( ) -> T::AccountId { let user = account(string, n, SEED); let balance = T::Currency::minimum_balance() * balance_factor.into(); - T::Currency::make_free_balance_be(&user, balance); - // ensure T::CurrencyToVote will work correctly. - T::Currency::issue(balance); + let _ = T::Currency::make_free_balance_be(&user, balance); + user +} + +/// Grab a funded user with max Balance. +pub fn create_funded_user_with_balance( + string: &'static str, + n: u32, + balance: BalanceOf, +) -> T::AccountId { + let user = account(string, n, SEED); + let _ = T::Currency::make_free_balance_be(&user, balance); user } @@ -63,8 +78,7 @@ pub fn create_stash_controller( ) -> Result<(T::AccountId, T::AccountId), &'static str> { let stash = create_funded_user::("stash", n, balance_factor); let controller = create_funded_user::("controller", n, balance_factor); - let controller_lookup: ::Source = - T::Lookup::unlookup(controller.clone()); + let controller_lookup = T::Lookup::unlookup(controller.clone()); let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); Staking::::bond( RawOrigin::Signed(stash.clone()).into(), @@ -72,7 +86,26 @@ pub fn create_stash_controller( amount, destination, )?; - return Ok((stash, controller)) + Ok((stash, controller)) +} + +/// Create a stash and controller pair with fixed balance. +pub fn create_stash_controller_with_balance( + n: u32, + balance: crate::BalanceOf, + destination: RewardDestination, +) -> Result<(T::AccountId, T::AccountId), &'static str> { + let stash = create_funded_user_with_balance::("stash", n, balance); + let controller = create_funded_user_with_balance::("controller", n, balance); + let controller_lookup = T::Lookup::unlookup(controller.clone()); + + Staking::::bond( + RawOrigin::Signed(stash.clone()).into(), + controller_lookup, + balance, + destination, + )?; + Ok((stash, controller)) } /// Create a stash and controller pair, where the controller is dead, and payouts go to controller. @@ -85,8 +118,7 @@ pub fn create_stash_and_dead_controller( let stash = create_funded_user::("stash", n, balance_factor); // controller has no funds let controller = create_funded_user::("controller", n, 0); - let controller_lookup: ::Source = - T::Lookup::unlookup(controller.clone()); + let controller_lookup = T::Lookup::unlookup(controller.clone()); let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); Staking::::bond( RawOrigin::Signed(stash.clone()).into(), @@ -94,22 +126,31 @@ pub fn create_stash_and_dead_controller( amount, destination, )?; - return Ok((stash, controller)) + Ok((stash, controller)) } /// create `max` validators. pub fn create_validators( max: u32, balance_factor: u32, -) -> Result::Source>, &'static str> { - let mut validators: Vec<::Source> = Vec::with_capacity(max as usize); +) -> Result>, &'static str> { + create_validators_with_seed::(max, balance_factor, 0) +} + +/// create `max` validators, with a seed to help unintentional prevent account collisions. +pub fn create_validators_with_seed( + max: u32, + balance_factor: u32, + seed: u32, +) -> Result>, &'static str> { + let mut validators: Vec> = Vec::with_capacity(max as usize); for i in 0..max { let (stash, controller) = - create_stash_controller::(i, balance_factor, RewardDestination::Staked)?; + create_stash_controller::(i + seed, balance_factor, RewardDestination::Staked)?; let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; Staking::::validate(RawOrigin::Signed(controller).into(), validator_prefs)?; - let stash_lookup: ::Source = T::Lookup::unlookup(stash); + let stash_lookup = T::Lookup::unlookup(stash); validators.push(stash_lookup); } Ok(validators) @@ -136,11 +177,10 @@ pub fn create_validators_with_nominators_for_era( edge_per_nominator: usize, randomize_stake: bool, to_nominate: Option, -) -> Result::Source>, &'static str> { +) -> Result>, &'static str> { clear_validators_and_nominators::(); - let mut validators_stash: Vec<::Source> = - Vec::with_capacity(validators as usize); + let mut validators_stash: Vec> = Vec::with_capacity(validators as usize); let mut rng = ChaChaRng::from_seed(SEED.using_encoded(blake2_256)); // Create validators @@ -151,8 +191,7 @@ pub fn create_validators_with_nominators_for_era( let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; Staking::::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?; - let stash_lookup: ::Source = - T::Lookup::unlookup(v_stash.clone()); + let stash_lookup = T::Lookup::unlookup(v_stash.clone()); validators_stash.push(stash_lookup.clone()); } @@ -167,7 +206,7 @@ pub fn create_validators_with_nominators_for_era( // Have them randomly validate let mut available_validators = validator_chosen.clone(); - let mut selected_validators: Vec<::Source> = + let mut selected_validators: Vec> = Vec::with_capacity(edge_per_nominator); for _ in 0..validators.min(edge_per_nominator as u32) { diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 97dfaa39c84a9..3e0a62f53d886 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,29 +17,78 @@ //! Tests for the module. -use super::{Event, *}; -use frame_election_provider_support::{ElectionProvider, Support}; +use super::{ConfigOp, Event, *}; +use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; use frame_support::{ - assert_noop, assert_ok, - dispatch::WithPostDispatchInfo, + assert_noop, assert_ok, assert_storage_noop, bounded_vec, + dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, pallet_prelude::*, - traits::{Currency, Get, OnInitialize, ReservableCurrency}, - weights::{extract_actual_weight, GetDispatchInfo}, + traits::{Currency, Get, ReservableCurrency}, }; use mock::*; use pallet_balances::Error as BalancesError; use sp_runtime::{ assert_eq_error_rate, traits::{BadOrigin, Dispatchable}, - Perbill, Percent, + Perbill, Percent, Rounding, }; use sp_staking::{ - offence::{OffenceDetails, OnOffenceHandler}, + offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, SessionIndex, }; use sp_std::prelude::*; use substrate_test_utils::assert_eq_uvec; +#[test] +fn set_staking_configs_works() { + ExtBuilder::default().build_and_execute(|| { + // setting works + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Set(1_500), + ConfigOp::Set(2_000), + ConfigOp::Set(10), + ConfigOp::Set(20), + ConfigOp::Set(Percent::from_percent(75)), + ConfigOp::Set(Zero::zero()) + )); + assert_eq!(MinNominatorBond::::get(), 1_500); + assert_eq!(MinValidatorBond::::get(), 2_000); + assert_eq!(MaxNominatorsCount::::get(), Some(10)); + assert_eq!(MaxValidatorsCount::::get(), Some(20)); + assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(75))); + assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); + + // noop does nothing + assert_storage_noop!(assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop + ))); + + // removing works + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove + )); + assert_eq!(MinNominatorBond::::get(), 0); + assert_eq!(MinValidatorBond::::get(), 0); + assert_eq!(MaxNominatorsCount::::get(), None); + assert_eq!(MaxValidatorsCount::::get(), None); + assert_eq!(ChillThreshold::::get(), None); + assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); + }); +} + #[test] fn force_unstake_works() { ExtBuilder::default().build_and_execute(|| { @@ -49,22 +98,22 @@ fn force_unstake_works() { add_slash(&11); // Cant transfer assert_noop!( - Balances::transfer(Origin::signed(11), 1, 10), + Balances::transfer(RuntimeOrigin::signed(11), 1, 10), BalancesError::::LiquidityRestrictions ); // Force unstake requires root. - assert_noop!(Staking::force_unstake(Origin::signed(11), 11, 2), BadOrigin); + assert_noop!(Staking::force_unstake(RuntimeOrigin::signed(11), 11, 2), BadOrigin); // Force unstake needs correct number of slashing spans (for weight calculation) assert_noop!( - Staking::force_unstake(Origin::root(), 11, 0), + Staking::force_unstake(RuntimeOrigin::root(), 11, 0), Error::::IncorrectSlashingSpans ); // We now force them to unstake - assert_ok!(Staking::force_unstake(Origin::root(), 11, 2)); + assert_ok!(Staking::force_unstake(RuntimeOrigin::root(), 11, 2)); // No longer bonded. assert_eq!(Staking::bonded(&11), None); // Transfer works. - assert_ok!(Balances::transfer(Origin::signed(11), 1, 10)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(11), 1, 10)); }); } @@ -99,14 +148,14 @@ fn basic_setup_works() { // Account 10 controls the stash from account 11, which is 100 * balance_factor units assert_eq!( - Staking::ledger(&10), - Some(StakingLedger { + Staking::ledger(&10).unwrap(), + StakingLedger { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![] - }) + unlocking: Default::default(), + claimed_rewards: bounded_vec![], + } ); // Account 20 controls the stash from account 21, which is 200 * balance_factor units assert_eq!( @@ -115,8 +164,8 @@ fn basic_setup_works() { stash: 21, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); // Account 1 does not control any stash @@ -138,8 +187,8 @@ fn basic_setup_works() { stash: 101, total: 500, active: 500, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); @@ -186,19 +235,19 @@ fn change_controller_works() { assert_eq!(Staking::bonded(&11), Some(10)); // 10 can control 11 who is initially a validator. - assert_ok!(Staking::chill(Origin::signed(10))); + assert_ok!(Staking::chill(RuntimeOrigin::signed(10))); // change controller - assert_ok!(Staking::set_controller(Origin::signed(11), 5)); + assert_ok!(Staking::set_controller(RuntimeOrigin::signed(11), 5)); assert_eq!(Staking::bonded(&11), Some(5)); mock::start_active_era(1); // 10 is no longer in control. assert_noop!( - Staking::validate(Origin::signed(10), ValidatorPrefs::default()), + Staking::validate(RuntimeOrigin::signed(10), ValidatorPrefs::default()), Error::::NotController, ); - assert_ok!(Staking::validate(Origin::signed(5), ValidatorPrefs::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(5), ValidatorPrefs::default())); }) } @@ -217,16 +266,17 @@ fn rewards_should_work() { Payee::::insert(21, RewardDestination::Controller); Payee::::insert(101, RewardDestination::Controller); - >::reward_by_ids(vec![(11, 50)]); - >::reward_by_ids(vec![(11, 50)]); + Pallet::::reward_by_ids(vec![(11, 50)]); + Pallet::::reward_by_ids(vec![(11, 50)]); // This is the second validator of the current elected set. - >::reward_by_ids(vec![(21, 50)]); + Pallet::::reward_by_ids(vec![(21, 50)]); // Compute total payout now for whole duration of the session. let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); let maximum_payout = maximum_payout_for_duration(reward_time_per_era()); start_session(1); + assert_eq_uvec!(Session::validators(), vec![11, 21]); assert_eq!(Balances::total_balance(&10), init_balance_10); assert_eq!(Balances::total_balance(&11), init_balance_11); @@ -234,7 +284,6 @@ fn rewards_should_work() { assert_eq!(Balances::total_balance(&21), init_balance_21); assert_eq!(Balances::total_balance(&100), init_balance_100); assert_eq!(Balances::total_balance(&101), init_balance_101); - assert_eq_uvec!(Session::validators(), vec![11, 21]); assert_eq!( Staking::eras_reward_points(active_era()), EraRewardPoints { @@ -251,13 +300,14 @@ fn rewards_should_work() { start_session(3); assert_eq!(active_era(), 1); - assert_eq!( - mock::REWARD_REMAINDER_UNBALANCED.with(|v| *v.borrow()), - maximum_payout - total_payout_0, - ); + assert_eq!(mock::RewardRemainderUnbalanced::get(), maximum_payout - total_payout_0,); assert_eq!( *mock::staking_events().last().unwrap(), - Event::EraPaid(0, total_payout_0, maximum_payout - total_payout_0) + Event::EraPaid { + era_index: 0, + validator_payout: total_payout_0, + remainder: maximum_payout - total_payout_0 + } ); mock::make_all_reward_payment(0); @@ -283,19 +333,23 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); assert_eq_uvec!(Session::validators(), vec![11, 21]); - >::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(11, 1)]); // Compute total payout now for whole duration as other parameter won't change let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); mock::start_active_era(2); assert_eq!( - mock::REWARD_REMAINDER_UNBALANCED.with(|v| *v.borrow()), + mock::RewardRemainderUnbalanced::get(), maximum_payout * 2 - total_payout_0 - total_payout_1, ); assert_eq!( *mock::staking_events().last().unwrap(), - Event::EraPaid(1, total_payout_1, maximum_payout - total_payout_1) + Event::EraPaid { + era_index: 1, + validator_payout: total_payout_1, + remainder: maximum_payout - total_payout_1 + } ); mock::make_all_reward_payment(1); @@ -336,8 +390,13 @@ fn staking_should_work() { // --- Block 2: start_session(2); // add a new candidate for being a validator. account 3 controlled by 4. - assert_ok!(Staking::bond(Origin::signed(3), 4, 1500, RewardDestination::Controller)); - assert_ok!(Staking::validate(Origin::signed(4), ValidatorPrefs::default())); + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 1500, RewardDestination::Controller)); + assert_ok!(Staking::validate(RuntimeOrigin::signed(4), ValidatorPrefs::default())); + assert_ok!(Session::set_keys( + RuntimeOrigin::signed(4), + SessionKeys { other: 4.into() }, + vec![] + )); // No effects will be seen so far. assert_eq_uvec!(validator_controllers(), vec![20, 10]); @@ -361,7 +420,7 @@ fn staking_should_work() { assert_eq_uvec!(validator_controllers(), vec![20, 4]); // --- Block 6: Unstake 4 as a validator, freeing up the balance stashed in 3 // 4 will chill - Staking::chill(Origin::signed(4)).unwrap(); + Staking::chill(RuntimeOrigin::signed(4)).unwrap(); // --- Block 7: nothing. 4 is still there. start_session(7); @@ -381,8 +440,8 @@ fn staking_should_work() { stash: 3, total: 1500, active: 1500, - unlocking: vec![], - claimed_rewards: vec![0], + unlocking: Default::default(), + claimed_rewards: bounded_vec![0], }) ); // e.g. it cannot reserve more than 500 that it has free from the total 2000 @@ -400,20 +459,20 @@ fn blocking_and_kicking_works() { .build_and_execute(|| { // block validator 10/11 assert_ok!(Staking::validate( - Origin::signed(10), + RuntimeOrigin::signed(10), ValidatorPrefs { blocked: true, ..Default::default() } )); // attempt to nominate from 100/101... - assert_ok!(Staking::nominate(Origin::signed(100), vec![11])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(100), vec![11])); // should have worked since we're already nominated them assert_eq!(Nominators::::get(&101).unwrap().targets, vec![11]); // kick the nominator - assert_ok!(Staking::kick(Origin::signed(10), vec![101])); + assert_ok!(Staking::kick(RuntimeOrigin::signed(10), vec![101])); // should have been kicked now assert!(Nominators::::get(&101).unwrap().targets.is_empty()); // attempt to nominate from 100/101... assert_noop!( - Staking::nominate(Origin::signed(100), vec![11]), + Staking::nominate(RuntimeOrigin::signed(100), vec![11]), Error::::BadTarget ); }); @@ -459,7 +518,7 @@ fn no_candidate_emergency_condition() { ::MinimumValidatorCount::put(10); // try to chill - let res = Staking::chill(Origin::signed(10)); + let res = Staking::chill(RuntimeOrigin::signed(10)); assert_ok!(res); let current_era = CurrentEra::::get(); @@ -495,14 +554,26 @@ fn nominating_and_rewards_should_work() { assert_eq_uvec!(validator_controllers(), vec![40, 20]); // re-validate with 11 and 31. - assert_ok!(Staking::validate(Origin::signed(10), Default::default())); - assert_ok!(Staking::validate(Origin::signed(30), Default::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(10), Default::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(30), Default::default())); // Set payee to controller. - assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); - assert_ok!(Staking::set_payee(Origin::signed(20), RewardDestination::Controller)); - assert_ok!(Staking::set_payee(Origin::signed(30), RewardDestination::Controller)); - assert_ok!(Staking::set_payee(Origin::signed(40), RewardDestination::Controller)); + assert_ok!(Staking::set_payee( + RuntimeOrigin::signed(10), + RewardDestination::Controller + )); + assert_ok!(Staking::set_payee( + RuntimeOrigin::signed(20), + RewardDestination::Controller + )); + assert_ok!(Staking::set_payee( + RuntimeOrigin::signed(30), + RewardDestination::Controller + )); + assert_ok!(Staking::set_payee( + RuntimeOrigin::signed(40), + RewardDestination::Controller + )); // give the man some money let initial_balance = 1000; @@ -511,16 +582,26 @@ fn nominating_and_rewards_should_work() { } // bond two account pairs and state interest in nomination. - assert_ok!(Staking::bond(Origin::signed(1), 2, 1000, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(2), vec![11, 21, 31])); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(1), + 2, + 1000, + RewardDestination::Controller + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11, 21, 31])); - assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(4), vec![11, 21, 41])); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(3), + 4, + 1000, + RewardDestination::Controller + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![11, 21, 41])); // the total reward for era 0 let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); - >::reward_by_ids(vec![(41, 1)]); - >::reward_by_ids(vec![(21, 1)]); + Pallet::::reward_by_ids(vec![(41, 1)]); + Pallet::::reward_by_ids(vec![(21, 1)]); mock::start_active_era(1); @@ -542,8 +623,8 @@ fn nominating_and_rewards_should_work() { total: 1000 + 800, own: 1000, others: vec![ - IndividualExposure { who: 3, value: 400 }, IndividualExposure { who: 1, value: 400 }, + IndividualExposure { who: 3, value: 400 }, ] }, ); @@ -553,16 +634,16 @@ fn nominating_and_rewards_should_work() { total: 1000 + 1200, own: 1000, others: vec![ - IndividualExposure { who: 3, value: 600 }, IndividualExposure { who: 1, value: 600 }, + IndividualExposure { who: 3, value: 600 }, ] }, ); // the total reward for era 1 let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); - >::reward_by_ids(vec![(21, 2)]); - >::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(21, 2)]); + Pallet::::reward_by_ids(vec![(11, 1)]); mock::start_active_era(2); @@ -667,20 +748,28 @@ fn double_staking_should_fail() { let arbitrary_value = 5; // 2 = controller, 1 stashed => ok assert_ok!(Staking::bond( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, arbitrary_value, RewardDestination::default() )); // 4 = not used so far, 1 stashed => not allowed. assert_noop!( - Staking::bond(Origin::signed(1), 4, arbitrary_value, RewardDestination::default()), + Staking::bond( + RuntimeOrigin::signed(1), + 4, + arbitrary_value, + RewardDestination::default() + ), Error::::AlreadyBonded, ); // 1 = stashed => attempting to nominate should fail. - assert_noop!(Staking::nominate(Origin::signed(1), vec![1]), Error::::NotController); + assert_noop!( + Staking::nominate(RuntimeOrigin::signed(1), vec![1]), + Error::::NotController + ); // 2 = controller => nominating should work. - assert_ok!(Staking::nominate(Origin::signed(2), vec![1])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![1])); }); } @@ -693,14 +782,19 @@ fn double_controlling_should_fail() { let arbitrary_value = 5; // 2 = controller, 1 stashed => ok assert_ok!(Staking::bond( - Origin::signed(1), + RuntimeOrigin::signed(1), 2, arbitrary_value, RewardDestination::default(), )); // 2 = controller, 3 stashed (Note that 2 is reused.) => no-op assert_noop!( - Staking::bond(Origin::signed(3), 2, arbitrary_value, RewardDestination::default()), + Staking::bond( + RuntimeOrigin::signed(3), + 2, + arbitrary_value, + RewardDestination::default() + ), Error::::AlreadyPaired, ); }); @@ -866,14 +960,14 @@ fn cannot_transfer_staked_balance() { assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000); // Confirm account 11 cannot transfer as a result assert_noop!( - Balances::transfer(Origin::signed(11), 20, 1), + Balances::transfer(RuntimeOrigin::signed(11), 20, 1), BalancesError::::LiquidityRestrictions ); // Give account 11 extra free balance let _ = Balances::make_free_balance_be(&11, 10000); // Confirm that account 11 can now transfer some balance - assert_ok!(Balances::transfer(Origin::signed(11), 20, 1)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(11), 20, 1)); }); } @@ -891,10 +985,10 @@ fn cannot_transfer_staked_balance_2() { assert_eq!(Staking::eras_stakers(active_era(), 21).total, 1000); // Confirm account 21 can transfer at most 1000 assert_noop!( - Balances::transfer(Origin::signed(21), 20, 1001), + Balances::transfer(RuntimeOrigin::signed(21), 20, 1001), BalancesError::::LiquidityRestrictions ); - assert_ok!(Balances::transfer(Origin::signed(21), 20, 1000)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(21), 20, 1000)); }); } @@ -935,14 +1029,14 @@ fn reward_destination_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); // Compute total payout now for whole duration as other parameter won't change let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); - >::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(11, 1)]); mock::start_active_era(1); mock::make_all_reward_payment(0); @@ -958,8 +1052,8 @@ fn reward_destination_works() { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, - unlocking: vec![], - claimed_rewards: vec![0], + unlocking: Default::default(), + claimed_rewards: bounded_vec![0], }) ); @@ -968,7 +1062,7 @@ fn reward_destination_works() { // Compute total payout now for whole duration as other parameter won't change let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); - >::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(11, 1)]); mock::start_active_era(2); mock::make_all_reward_payment(1); @@ -986,8 +1080,8 @@ fn reward_destination_works() { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, - unlocking: vec![], - claimed_rewards: vec![0, 1], + unlocking: Default::default(), + claimed_rewards: bounded_vec![0, 1], }) ); @@ -999,7 +1093,7 @@ fn reward_destination_works() { // Compute total payout now for whole duration as other parameter won't change let total_payout_2 = current_total_payout_for_duration(reward_time_per_era()); - >::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(11, 1)]); mock::start_active_era(3); mock::make_all_reward_payment(2); @@ -1015,8 +1109,8 @@ fn reward_destination_works() { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, - unlocking: vec![], - claimed_rewards: vec![0, 1, 2], + unlocking: Default::default(), + claimed_rewards: bounded_vec![0, 1, 2], }) ); // Check that amount in staked account is NOT increased. @@ -1031,10 +1125,7 @@ fn validator_payment_prefs_work() { // This test will focus on validator payment. ExtBuilder::default().build_and_execute(|| { let commission = Perbill::from_percent(40); - >::insert( - &11, - ValidatorPrefs { commission: commission.clone(), ..Default::default() }, - ); + >::insert(&11, ValidatorPrefs { commission, ..Default::default() }); // Reward controller so staked ratio doesn't change. >::insert(&11, RewardDestination::Controller); @@ -1049,7 +1140,7 @@ fn validator_payment_prefs_work() { // Compute total payout now for whole duration as other parameter won't change let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); let exposure_1 = Staking::eras_stakers(active_era(), 11); - >::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(11, 1)]); mock::start_active_era(2); mock::make_all_reward_payment(1); @@ -1080,8 +1171,8 @@ fn bond_extra_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); @@ -1089,7 +1180,7 @@ fn bond_extra_works() { let _ = Balances::make_free_balance_be(&11, 1000000); // Call the bond_extra function from controller, add only 100 - assert_ok!(Staking::bond_extra(Origin::signed(11), 100)); + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 100)); // There should be 100 more `total` and `active` in the ledger assert_eq!( Staking::ledger(&10), @@ -1097,13 +1188,13 @@ fn bond_extra_works() { stash: 11, total: 1000 + 100, active: 1000 + 100, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); // Call the bond_extra function with a large number, should handle it - assert_ok!(Staking::bond_extra(Origin::signed(11), Balance::max_value())); + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), Balance::max_value())); // The full amount of the funds should now be in the total and active assert_eq!( Staking::ledger(&10), @@ -1111,8 +1202,8 @@ fn bond_extra_works() { stash: 11, total: 1000000, active: 1000000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); }); @@ -1128,7 +1219,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // * Once the unbonding period is done, it can actually take the funds out of the stash. ExtBuilder::default().nominate(false).build_and_execute(|| { // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(RuntimeOrigin::signed(10), RewardDestination::Controller)); // Give account 11 some large free balance greater than total let _ = Balances::make_free_balance_be(&11, 1000000); @@ -1149,8 +1240,8 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); assert_eq!( @@ -1159,7 +1250,7 @@ fn bond_extra_and_withdraw_unbonded_works() { ); // deposit the extra 100 units - Staking::bond_extra(Origin::signed(11), 100).unwrap(); + Staking::bond_extra(RuntimeOrigin::signed(11), 100).unwrap(); assert_eq!( Staking::ledger(&10), @@ -1167,8 +1258,8 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 1000 + 100, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); // Exposure is a snapshot! only updated after the next era update. @@ -1188,8 +1279,8 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 1000 + 100, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); // Exposure is now updated. @@ -1199,28 +1290,28 @@ fn bond_extra_and_withdraw_unbonded_works() { ); // Unbond almost all of the funds in stash. - Staking::unbond(Origin::signed(10), 1000).unwrap(); + Staking::unbond(RuntimeOrigin::signed(10), 1000).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 100, active: 100, - unlocking: vec![UnlockChunk { value: 1000, era: 2 + 3 }], - claimed_rewards: vec![] + unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], + claimed_rewards: bounded_vec![], }), ); // Attempting to free the balances now will fail. 2 eras need to pass. - assert_ok!(Staking::withdraw_unbonded(Origin::signed(10), 0)); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0)); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 100, active: 100, - unlocking: vec![UnlockChunk { value: 1000, era: 2 + 3 }], - claimed_rewards: vec![] + unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], + claimed_rewards: bounded_vec![], }), ); @@ -1228,22 +1319,22 @@ fn bond_extra_and_withdraw_unbonded_works() { mock::start_active_era(3); // nothing yet - assert_ok!(Staking::withdraw_unbonded(Origin::signed(10), 0)); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0)); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 100, active: 100, - unlocking: vec![UnlockChunk { value: 1000, era: 2 + 3 }], - claimed_rewards: vec![] + unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], + claimed_rewards: bounded_vec![], }), ); // trigger next era. mock::start_active_era(5); - assert_ok!(Staking::withdraw_unbonded(Origin::signed(10), 0)); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0)); // Now the value is free and the staking ledger is updated. assert_eq!( Staking::ledger(&10), @@ -1251,8 +1342,8 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 100, active: 100, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }), ); }) @@ -1261,26 +1352,39 @@ fn bond_extra_and_withdraw_unbonded_works() { #[test] fn too_many_unbond_calls_should_not_work() { ExtBuilder::default().build_and_execute(|| { - // locked at era 0 until 3 - for _ in 0..MAX_UNLOCKING_CHUNKS - 1 { - assert_ok!(Staking::unbond(Origin::signed(10), 1)); + let mut current_era = 0; + // locked at era MaxUnlockingChunks - 1 until 3 + + for i in 0..<::MaxUnlockingChunks as Get>::get() - 1 { + // There is only 1 chunk per era, so we need to be in a new era to create a chunk. + current_era = i as u32; + mock::start_active_era(current_era); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); } - mock::start_active_era(1); + current_era += 1; + mock::start_active_era(current_era); - // locked at era 1 until 4 - assert_ok!(Staking::unbond(Origin::signed(10), 1)); + // This chunk is locked at `current_era` through `current_era + 2` (because BondingDuration + // == 3). + assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); + assert_eq!( + Staking::ledger(&10).unwrap().unlocking.len(), + <::MaxUnlockingChunks as Get>::get() as usize + ); // can't do more. - assert_noop!(Staking::unbond(Origin::signed(10), 1), Error::::NoMoreChunks); + assert_noop!(Staking::unbond(RuntimeOrigin::signed(10), 1), Error::::NoMoreChunks); - mock::start_active_era(3); + current_era += 2; + mock::start_active_era(current_era); - assert_noop!(Staking::unbond(Origin::signed(10), 1), Error::::NoMoreChunks); - // free up. - assert_ok!(Staking::withdraw_unbonded(Origin::signed(10), 0)); + assert_noop!(Staking::unbond(RuntimeOrigin::signed(10), 1), Error::::NoMoreChunks); + // free up everything except the most recently added chunk. + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0)); + assert_eq!(Staking::ledger(&10).unwrap().unlocking.len(), 1); // Can add again. - assert_ok!(Staking::unbond(Origin::signed(10), 1)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); assert_eq!(Staking::ledger(&10).unwrap().unlocking.len(), 2); }) } @@ -1294,7 +1398,7 @@ fn rebond_works() { // * it can re-bond a portion of the funds scheduled to unlock. ExtBuilder::default().nominate(false).build_and_execute(|| { // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(RuntimeOrigin::signed(10), RewardDestination::Controller)); // Give account 11 some large free balance greater than total let _ = Balances::make_free_balance_be(&11, 1000000); @@ -1309,8 +1413,8 @@ fn rebond_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); @@ -1318,105 +1422,98 @@ fn rebond_works() { assert_eq!(active_era(), 2); // Try to rebond some funds. We get an error since no fund is unbonded. - assert_noop!(Staking::rebond(Origin::signed(10), 500), Error::::NoUnlockChunk); + assert_noop!(Staking::rebond(RuntimeOrigin::signed(10), 500), Error::::NoUnlockChunk); // Unbond almost all of the funds in stash. - Staking::unbond(Origin::signed(10), 900).unwrap(); + Staking::unbond(RuntimeOrigin::signed(10), 900).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 100, - unlocking: vec![UnlockChunk { value: 900, era: 2 + 3 }], - claimed_rewards: vec![], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 2 + 3 }], + claimed_rewards: bounded_vec![], }) ); // Re-bond all the funds unbonded. - Staking::rebond(Origin::signed(10), 900).unwrap(); + Staking::rebond(RuntimeOrigin::signed(10), 900).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); // Unbond almost all of the funds in stash. - Staking::unbond(Origin::signed(10), 900).unwrap(); + Staking::unbond(RuntimeOrigin::signed(10), 900).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 100, - unlocking: vec![UnlockChunk { value: 900, era: 5 }], - claimed_rewards: vec![], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], + claimed_rewards: bounded_vec![], }) ); // Re-bond part of the funds unbonded. - Staking::rebond(Origin::signed(10), 500).unwrap(); + Staking::rebond(RuntimeOrigin::signed(10), 500).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 600, - unlocking: vec![UnlockChunk { value: 400, era: 5 }], - claimed_rewards: vec![], + unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], + claimed_rewards: bounded_vec![], }) ); // Re-bond the remainder of the funds unbonded. - Staking::rebond(Origin::signed(10), 500).unwrap(); + Staking::rebond(RuntimeOrigin::signed(10), 500).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); // Unbond parts of the funds in stash. - Staking::unbond(Origin::signed(10), 300).unwrap(); - Staking::unbond(Origin::signed(10), 300).unwrap(); - Staking::unbond(Origin::signed(10), 300).unwrap(); + Staking::unbond(RuntimeOrigin::signed(10), 300).unwrap(); + Staking::unbond(RuntimeOrigin::signed(10), 300).unwrap(); + Staking::unbond(RuntimeOrigin::signed(10), 300).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 100, - unlocking: vec![ - UnlockChunk { value: 300, era: 5 }, - UnlockChunk { value: 300, era: 5 }, - UnlockChunk { value: 300, era: 5 }, - ], - claimed_rewards: vec![], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], + claimed_rewards: bounded_vec![], }) ); // Re-bond part of the funds unbonded. - Staking::rebond(Origin::signed(10), 500).unwrap(); + Staking::rebond(RuntimeOrigin::signed(10), 500).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 600, - unlocking: vec![ - UnlockChunk { value: 300, era: 5 }, - UnlockChunk { value: 100, era: 5 }, - ], - claimed_rewards: vec![], + unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], + claimed_rewards: bounded_vec![], }) ); }) @@ -1427,7 +1524,7 @@ fn rebond_is_fifo() { // Rebond should proceed by reversing the most recent bond operations. ExtBuilder::default().nominate(false).build_and_execute(|| { // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(RuntimeOrigin::signed(10), RewardDestination::Controller)); // Give account 11 some large free balance greater than total let _ = Balances::make_free_balance_be(&11, 1000000); @@ -1442,81 +1539,140 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); mock::start_active_era(2); // Unbond some of the funds in stash. - Staking::unbond(Origin::signed(10), 400).unwrap(); + Staking::unbond(RuntimeOrigin::signed(10), 400).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 600, - unlocking: vec![UnlockChunk { value: 400, era: 2 + 3 },], - claimed_rewards: vec![], + unlocking: bounded_vec![UnlockChunk { value: 400, era: 2 + 3 }], + claimed_rewards: bounded_vec![], }) ); mock::start_active_era(3); // Unbond more of the funds in stash. - Staking::unbond(Origin::signed(10), 300).unwrap(); + Staking::unbond(RuntimeOrigin::signed(10), 300).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 300, - unlocking: vec![ + unlocking: bounded_vec![ UnlockChunk { value: 400, era: 2 + 3 }, UnlockChunk { value: 300, era: 3 + 3 }, ], - claimed_rewards: vec![], + claimed_rewards: bounded_vec![], }) ); mock::start_active_era(4); // Unbond yet more of the funds in stash. - Staking::unbond(Origin::signed(10), 200).unwrap(); + Staking::unbond(RuntimeOrigin::signed(10), 200).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 100, - unlocking: vec![ + unlocking: bounded_vec![ UnlockChunk { value: 400, era: 2 + 3 }, UnlockChunk { value: 300, era: 3 + 3 }, UnlockChunk { value: 200, era: 4 + 3 }, ], - claimed_rewards: vec![], + claimed_rewards: bounded_vec![], }) ); // Re-bond half of the unbonding funds. - Staking::rebond(Origin::signed(10), 400).unwrap(); + Staking::rebond(RuntimeOrigin::signed(10), 400).unwrap(); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 500, - unlocking: vec![ + unlocking: bounded_vec![ UnlockChunk { value: 400, era: 2 + 3 }, UnlockChunk { value: 100, era: 3 + 3 }, ], - claimed_rewards: vec![], + claimed_rewards: bounded_vec![], }) ); }) } +#[test] +fn rebond_emits_right_value_in_event() { + // When a user calls rebond with more than can be rebonded, things succeed, + // and the rebond event emits the actual value rebonded. + ExtBuilder::default().nominate(false).build_and_execute(|| { + // Set payee to controller. avoids confusion + assert_ok!(Staking::set_payee(RuntimeOrigin::signed(10), RewardDestination::Controller)); + + // Give account 11 some large free balance greater than total + let _ = Balances::make_free_balance_be(&11, 1000000); + + // confirm that 10 is a normal validator and gets paid at the end of the era. + mock::start_active_era(1); + + // Unbond almost all of the funds in stash. + Staking::unbond(RuntimeOrigin::signed(10), 900).unwrap(); + assert_eq!( + Staking::ledger(&10), + Some(StakingLedger { + stash: 11, + total: 1000, + active: 100, + unlocking: bounded_vec![UnlockChunk { value: 900, era: 1 + 3 }], + claimed_rewards: bounded_vec![], + }) + ); + + // Re-bond less than the total + Staking::rebond(RuntimeOrigin::signed(10), 100).unwrap(); + assert_eq!( + Staking::ledger(&10), + Some(StakingLedger { + stash: 11, + total: 1000, + active: 200, + unlocking: bounded_vec![UnlockChunk { value: 800, era: 1 + 3 }], + claimed_rewards: bounded_vec![], + }) + ); + // Event emitted should be correct + assert_eq!(*staking_events().last().unwrap(), Event::Bonded { stash: 11, amount: 100 }); + + // Re-bond way more than available + Staking::rebond(RuntimeOrigin::signed(10), 100_000).unwrap(); + assert_eq!( + Staking::ledger(&10), + Some(StakingLedger { + stash: 11, + total: 1000, + active: 1000, + unlocking: Default::default(), + claimed_rewards: bounded_vec![], + }) + ); + // Event emitted should be correct, only 800 + assert_eq!(*staking_events().last().unwrap(), Event::Bonded { stash: 11, amount: 800 }); + }); +} + #[test] fn reward_to_stake_works() { ExtBuilder::default() @@ -1544,15 +1700,15 @@ fn reward_to_stake_works() { stash: 21, total: 69, active: 69, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }, ); // Compute total payout now for whole duration as other parameter won't change let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); - >::reward_by_ids(vec![(11, 1)]); - >::reward_by_ids(vec![(21, 1)]); + Pallet::::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(21, 1)]); // New era --> rewards are paid --> stakes are changed mock::start_active_era(1); @@ -1574,115 +1730,52 @@ fn reward_to_stake_works() { } #[test] -fn on_free_balance_zero_stash_removes_validator() { - // Tests that validator storage items are cleaned up when stash is empty - // Tests that storage items are untouched when controller is empty +fn reap_stash_works() { ExtBuilder::default() .existential_deposit(10) .balance_factor(10) .build_and_execute(|| { - // Check the balance of the validator account + // given assert_eq!(Balances::free_balance(10), 10); - // Check the balance of the stash account - assert_eq!(Balances::free_balance(11), 10 * 1000); - // Check these two accounts are bonded - assert_eq!(Staking::bonded(&11), Some(10)); - - // Set payee information - assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Stash)); - - // Check storage items that should be cleaned up - assert!(>::contains_key(&10)); - assert!(>::contains_key(&11)); - assert!(>::contains_key(&11)); - assert!(>::contains_key(&11)); - - // Reduce free_balance of controller to 0 - let _ = Balances::slash(&10, Balance::max_value()); - - // Check the balance of the stash account has not been touched assert_eq!(Balances::free_balance(11), 10 * 1000); - // Check these two accounts are still bonded assert_eq!(Staking::bonded(&11), Some(10)); - // Check storage items have not changed assert!(>::contains_key(&10)); assert!(>::contains_key(&11)); assert!(>::contains_key(&11)); assert!(>::contains_key(&11)); - // Reduce free_balance of stash to 0 - let _ = Balances::slash(&11, Balance::max_value()); - // Check total balance of stash - assert_eq!(Balances::total_balance(&11), 10); - - // Reap the stash - assert_ok!(Staking::reap_stash(Origin::none(), 11, 0)); - - // Check storage items do not exist - assert!(!>::contains_key(&10)); - assert!(!>::contains_key(&11)); - assert!(!>::contains_key(&11)); - assert!(!>::contains_key(&11)); - assert!(!>::contains_key(&11)); - }); -} - -#[test] -fn on_free_balance_zero_stash_removes_nominator() { - // Tests that nominator storage items are cleaned up when stash is empty - // Tests that storage items are untouched when controller is empty - ExtBuilder::default() - .existential_deposit(10) - .balance_factor(10) - .build_and_execute(|| { - // Make 10 a nominator - assert_ok!(Staking::nominate(Origin::signed(10), vec![20])); - // Check that account 10 is a nominator - assert!(>::contains_key(11)); - // Check the balance of the nominator account - assert_eq!(Balances::free_balance(10), 10); - // Check the balance of the stash account - assert_eq!(Balances::free_balance(11), 10_000); - - // Set payee information - assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Stash)); - - // Check storage items that should be cleaned up - assert!(>::contains_key(&10)); - assert!(>::contains_key(&11)); - assert!(>::contains_key(&11)); - assert!(>::contains_key(&11)); - - // Reduce free_balance of controller to 0 - let _ = Balances::slash(&10, Balance::max_value()); - // Check total balance of account 10 - assert_eq!(Balances::total_balance(&10), 0); - - // Check the balance of the stash account has not been touched - assert_eq!(Balances::free_balance(11), 10_000); - // Check these two accounts are still bonded - assert_eq!(Staking::bonded(&11), Some(10)); - - // Check storage items have not changed - assert!(>::contains_key(&10)); - assert!(>::contains_key(&11)); - assert!(>::contains_key(&11)); - assert!(>::contains_key(&11)); + // stash is not reapable + assert_noop!( + Staking::reap_stash(RuntimeOrigin::signed(20), 11, 0), + Error::::FundedTarget + ); + // controller or any other account is not reapable + assert_noop!( + Staking::reap_stash(RuntimeOrigin::signed(20), 10, 0), + Error::::NotStash + ); - // Reduce free_balance of stash to 0 - let _ = Balances::slash(&11, Balance::max_value()); - // Check total balance of stash - assert_eq!(Balances::total_balance(&11), 10); + // no easy way to cause an account to go below ED, we tweak their staking ledger + // instead. + Ledger::::insert( + 10, + StakingLedger { + stash: 11, + total: 5, + active: 5, + unlocking: Default::default(), + claimed_rewards: bounded_vec![], + }, + ); - // Reap the stash - assert_ok!(Staking::reap_stash(Origin::none(), 11, 0)); + // reap-able + assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(20), 11, 0)); - // Check storage items do not exist + // then assert!(!>::contains_key(&10)); assert!(!>::contains_key(&11)); assert!(!>::contains_key(&11)); - assert!(!>::contains_key(&11)); assert!(!>::contains_key(&11)); }); } @@ -1694,7 +1787,10 @@ fn switching_roles() { ExtBuilder::default().nominate(false).build_and_execute(|| { // Reset reward destination for i in &[10, 20] { - assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); + assert_ok!(Staking::set_payee( + RuntimeOrigin::signed(*i), + RewardDestination::Controller + )); } assert_eq_uvec!(validator_controllers(), vec![20, 10]); @@ -1705,15 +1801,20 @@ fn switching_roles() { } // add 2 nominators - assert_ok!(Staking::bond(Origin::signed(1), 2, 2000, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(2), vec![11, 5])); + assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 2, 2000, RewardDestination::Controller)); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11, 5])); - assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(4), vec![21, 1])); + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 500, RewardDestination::Controller)); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![21, 1])); // add a new validator candidate - assert_ok!(Staking::bond(Origin::signed(5), 6, 1000, RewardDestination::Controller)); - assert_ok!(Staking::validate(Origin::signed(6), ValidatorPrefs::default())); + assert_ok!(Staking::bond(RuntimeOrigin::signed(5), 6, 1000, RewardDestination::Controller)); + assert_ok!(Staking::validate(RuntimeOrigin::signed(6), ValidatorPrefs::default())); + assert_ok!(Session::set_keys( + RuntimeOrigin::signed(6), + SessionKeys { other: 6.into() }, + vec![] + )); mock::start_active_era(1); @@ -1721,7 +1822,12 @@ fn switching_roles() { assert_eq_uvec!(validator_controllers(), vec![6, 10]); // 2 decides to be a validator. Consequences: - assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(2), ValidatorPrefs::default())); + assert_ok!(Session::set_keys( + RuntimeOrigin::signed(2), + SessionKeys { other: 2.into() }, + vec![] + )); // new stakes: // 10: 1000 self vote // 20: 1000 self vote + 250 vote @@ -1773,23 +1879,28 @@ fn bond_with_no_staked_value() { .build_and_execute(|| { // Can't bond with 1 assert_noop!( - Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller), + Staking::bond(RuntimeOrigin::signed(1), 2, 1, RewardDestination::Controller), Error::::InsufficientBond, ); // bonded with absolute minimum value possible. - assert_ok!(Staking::bond(Origin::signed(1), 2, 5, RewardDestination::Controller)); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(1), + 2, + 5, + RewardDestination::Controller + )); assert_eq!(Balances::locks(&1)[0].amount, 5); // unbonding even 1 will cause all to be unbonded. - assert_ok!(Staking::unbond(Origin::signed(2), 1)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(2), 1)); assert_eq!( Staking::ledger(2), Some(StakingLedger { stash: 1, active: 0, total: 5, - unlocking: vec![UnlockChunk { value: 5, era: 3 }], - claimed_rewards: vec![], + unlocking: bounded_vec![UnlockChunk { value: 5, era: 3 }], + claimed_rewards: bounded_vec![], }) ); @@ -1797,14 +1908,14 @@ fn bond_with_no_staked_value() { mock::start_active_era(2); // not yet removed. - assert_ok!(Staking::withdraw_unbonded(Origin::signed(2), 0)); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(2), 0)); assert!(Staking::ledger(2).is_some()); assert_eq!(Balances::locks(&1)[0].amount, 5); mock::start_active_era(3); // poof. Account 1 is removed from the staking system. - assert_ok!(Staking::withdraw_unbonded(Origin::signed(2), 0)); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(2), 0)); assert!(Staking::ledger(2).is_none()); assert_eq!(Balances::locks(&1).len(), 0); }); @@ -1818,14 +1929,27 @@ fn bond_with_little_staked_value_bounded() { .minimum_validator_count(1) .build_and_execute(|| { // setup - assert_ok!(Staking::chill(Origin::signed(30))); - assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::chill(RuntimeOrigin::signed(30))); + assert_ok!(Staking::set_payee( + RuntimeOrigin::signed(10), + RewardDestination::Controller + )); let init_balance_2 = Balances::free_balance(&2); let init_balance_10 = Balances::free_balance(&10); // Stingy validator. - assert_ok!(Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller)); - assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default())); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(1), + 2, + 1, + RewardDestination::Controller + )); + assert_ok!(Staking::validate(RuntimeOrigin::signed(2), ValidatorPrefs::default())); + assert_ok!(Session::set_keys( + RuntimeOrigin::signed(2), + SessionKeys { other: 2.into() }, + vec![] + )); // 1 era worth of reward. BUT, we set the timestamp after on_initialize, so outdated by // one block. @@ -1895,11 +2019,21 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { let _ = Balances::make_free_balance_be(i, initial_balance); } - assert_ok!(Staking::bond(Origin::signed(1), 2, 1000, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(2), vec![11, 11, 11, 21, 31])); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(1), + 2, + 1000, + RewardDestination::Controller + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11, 11, 11, 21, 31])); - assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(4), vec![21, 31])); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(3), + 4, + 1000, + RewardDestination::Controller + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![21, 31])); // winners should be 21 and 31. Otherwise this election is taking duplicates into // account. @@ -1907,8 +2041,8 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { assert_eq!( supports, vec![ - (21, Support { total: 1800, voters: vec![(21, 1000), (3, 400), (1, 400)] }), - (31, Support { total: 2200, voters: vec![(31, 1000), (3, 600), (1, 600)] }) + (21, Support { total: 1800, voters: vec![(21, 1000), (1, 400), (3, 400)] }), + (31, Support { total: 2200, voters: vec![(31, 1000), (1, 600), (3, 600)] }) ], ); }); @@ -1940,11 +2074,21 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { let _ = Balances::make_free_balance_be(i, initial_balance); } - assert_ok!(Staking::bond(Origin::signed(1), 2, 1000, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(2), vec![11, 11, 11, 21])); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(1), + 2, + 1000, + RewardDestination::Controller + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11, 11, 11, 21])); - assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(4), vec![21])); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(3), + 4, + 1000, + RewardDestination::Controller + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![21])); // winners should be 21 and 11. let supports = ::ElectionProvider::elect().unwrap(); @@ -1952,7 +2096,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { supports, vec![ (11, Support { total: 1500, voters: vec![(11, 1000), (1, 500)] }), - (21, Support { total: 2500, voters: vec![(21, 1000), (3, 1000), (1, 500)] }) + (21, Support { total: 2500, voters: vec![(21, 1000), (1, 500), (3, 1000)] }) ], ); }); @@ -1976,8 +2120,8 @@ fn phragmen_should_not_overflow() { // This is the maximum value that we can have as the outcome of CurrencyToVote. type Votes = u64; - let _ = Staking::chill(Origin::signed(10)); - let _ = Staking::chill(Origin::signed(20)); + let _ = Staking::chill(RuntimeOrigin::signed(10)); + let _ = Staking::chill(RuntimeOrigin::signed(20)); bond_validator(3, 2, Votes::max_value() as Balance); bond_validator(5, 4, Votes::max_value() as Balance); @@ -2018,16 +2162,16 @@ fn reward_validator_slashing_validator_does_not_overflow() { ErasStakers::::insert(0, 11, &exposure); ErasStakersClipped::::insert(0, 11, exposure); ErasValidatorReward::::insert(0, stake); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 0)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 0)); assert_eq!(Balances::total_balance(&11), stake * 2); // Set staker let _ = Balances::make_free_balance_be(&11, stake); let _ = Balances::make_free_balance_be(&2, stake); - // only slashes out of bonded stake are applied. without this line, - // it is 0. - Staking::bond(Origin::signed(2), 20000, stake - 1, RewardDestination::default()).unwrap(); + // only slashes out of bonded stake are applied. without this line, it is 0. + Staking::bond(RuntimeOrigin::signed(2), 20000, stake - 1, RewardDestination::default()) + .unwrap(); // Override exposure of 11 ErasStakers::::insert( 0, @@ -2058,12 +2202,12 @@ fn reward_from_authorship_event_handler_works() { ExtBuilder::default().build_and_execute(|| { use pallet_authorship::EventHandler; - assert_eq!(>::author(), 11); + assert_eq!(>::author(), Some(11)); - >::note_author(11); - >::note_uncle(21, 1); + Pallet::::note_author(11); + Pallet::::note_uncle(21, 1); // Rewarding the same two times works. - >::note_uncle(11, 1); + Pallet::::note_uncle(11, 1); // Not mandatory but must be coherent with rewards assert_eq_uvec!(Session::validators(), vec![11, 21]); @@ -2086,9 +2230,9 @@ fn add_reward_points_fns_works() { // Not mandatory but must be coherent with rewards assert_eq_uvec!(Session::validators(), vec![21, 11]); - >::reward_by_ids(vec![(21, 1), (11, 1), (11, 1)]); + Pallet::::reward_by_ids(vec![(21, 1), (11, 1), (11, 1)]); - >::reward_by_ids(vec![(21, 1), (11, 1), (11, 1)]); + Pallet::::reward_by_ids(vec![(21, 1), (11, 1), (11, 1)]); assert_eq!( ErasRewardPoints::::get(active_era()), @@ -2103,7 +2247,7 @@ fn unbonded_balance_is_not_slashable() { // total amount staked is slashable. assert_eq!(Staking::slashable_balance_of(&11), 1000); - assert_ok!(Staking::unbond(Origin::signed(10), 800)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 800)); // only the active portion. assert_eq!(Staking::slashable_balance_of(&11), 200); @@ -2159,7 +2303,7 @@ fn offence_forces_new_era() { #[test] fn offence_ensures_new_era_without_clobbering() { ExtBuilder::default().build_and_execute(|| { - assert_ok!(Staking::force_new_era_always(Origin::root())); + assert_ok!(Staking::force_new_era_always(RuntimeOrigin::root())); assert_eq!(Staking::force_era(), Forcing::ForceAlways); on_offence_now( @@ -2240,7 +2384,7 @@ fn slash_in_old_span_does_not_deselect() { mock::start_active_era(2); - Staking::validate(Origin::signed(10), Default::default()).unwrap(); + Staking::validate(RuntimeOrigin::signed(10), Default::default()).unwrap(); assert_eq!(Staking::force_era(), Forcing::NotForcing); assert!(>::contains_key(11)); assert!(!Session::validators().contains(&11)); @@ -2257,12 +2401,14 @@ fn slash_in_old_span_does_not_deselect() { }], &[Perbill::from_percent(0)], 1, + DisableStrategy::WhenSlashed, ); - // not forcing for zero-slash and previous span. - assert_eq!(Staking::force_era(), Forcing::NotForcing); - assert!(>::contains_key(11)); - assert!(Session::validators().contains(&11)); + // the validator doesn't get chilled again + assert!(::Validators::iter().any(|(stash, _)| stash == 11)); + + // but we are still forcing a new era + assert_eq!(Staking::force_era(), Forcing::ForceNew); on_offence_in_era( &[OffenceDetails { @@ -2272,12 +2418,16 @@ fn slash_in_old_span_does_not_deselect() { // NOTE: A 100% slash here would clean up the account, causing de-registration. &[Perbill::from_percent(95)], 1, + DisableStrategy::WhenSlashed, ); - // or non-zero. - assert_eq!(Staking::force_era(), Forcing::NotForcing); - assert!(>::contains_key(11)); - assert!(Session::validators().contains(&11)); + // the validator doesn't get chilled again + assert!(::Validators::iter().any(|(stash, _)| stash == 11)); + + // but it's disabled + assert!(is_disabled(10)); + // and we are still forcing a new era + assert_eq!(Staking::force_era(), Forcing::ForceNew); }); } @@ -2472,7 +2622,7 @@ fn garbage_collection_after_slashing() { assert_eq!(Balances::free_balance(11), 2000 - 200); assert!(::SlashingSpans::get(&11).is_some()); - assert_eq!(::SpanSlash::get(&(11, 0)).amount_slashed(), &200); + assert_eq!(::SpanSlash::get(&(11, 0)).amount(), &200); on_offence_now( &[OffenceDetails { @@ -2493,13 +2643,13 @@ fn garbage_collection_after_slashing() { // reap_stash respects num_slashing_spans so that weight is accurate assert_noop!( - Staking::reap_stash(Origin::none(), 11, 0), + Staking::reap_stash(RuntimeOrigin::signed(20), 11, 0), Error::::IncorrectSlashingSpans ); - assert_ok!(Staking::reap_stash(Origin::none(), 11, 2)); + assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(20), 11, 2)); assert!(::SlashingSpans::get(&11).is_none()); - assert_eq!(::SpanSlash::get(&(11, 0)).amount_slashed(), &0); + assert_eq!(::SpanSlash::get(&(11, 0)).amount(), &0); }) } @@ -2565,6 +2715,7 @@ fn slashing_nominators_by_span_max() { }], &[Perbill::from_percent(10)], 2, + DisableStrategy::WhenSlashed, ); assert_eq!(Balances::free_balance(11), 900); @@ -2591,6 +2742,7 @@ fn slashing_nominators_by_span_max() { }], &[Perbill::from_percent(30)], 3, + DisableStrategy::WhenSlashed, ); // 11 was not further slashed, but 21 and 101 were. @@ -2612,6 +2764,7 @@ fn slashing_nominators_by_span_max() { }], &[Perbill::from_percent(20)], 2, + DisableStrategy::WhenSlashed, ); // 11 was further slashed, but 21 and 101 were not. @@ -2656,7 +2809,7 @@ fn slashes_are_summed_across_spans() { assert_eq!(Balances::free_balance(21), 1900); // 21 has been force-chilled. re-signal intent to validate. - Staking::validate(Origin::signed(20), Default::default()).unwrap(); + Staking::validate(RuntimeOrigin::signed(20), Default::default()).unwrap(); mock::start_active_era(4); @@ -2692,6 +2845,8 @@ fn deferred_slashes_are_deferred() { assert_eq!(Balances::free_balance(101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; + System::reset_events(); + on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), @@ -2700,6 +2855,9 @@ fn deferred_slashes_are_deferred() { &[Perbill::from_percent(10)], ); + // nominations are not removed regardless of the deferring. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); @@ -2719,134 +2877,311 @@ fn deferred_slashes_are_deferred() { assert_eq!(Balances::free_balance(11), 900); assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); + + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::Chilled { stash: 11 }, + Event::SlashReported { validator: 11, slash_era: 1, .. }, + .., + Event::Slashed { staker: 11, amount: 100 }, + Event::Slashed { staker: 101, amount: 12 } + ] + )); }) } #[test] -fn remove_deferred() { +fn retroactive_deferred_slashes_two_eras_before() { ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { + assert_eq!(BondingDuration::get(), 3); + mock::start_active_era(1); + let exposure_11_at_era1 = Staking::eras_stakers(active_era(), 11); - assert_eq!(Balances::free_balance(11), 1000); + mock::start_active_era(3); - let exposure = Staking::eras_stakers(active_era(), 11); - assert_eq!(Balances::free_balance(101), 2000); - let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], + System::reset_events(); + on_offence_in_era( + &[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, ); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - mock::start_active_era(2); + mock::start_active_era(4); - on_offence_in_era( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(15)], - 1, - ); + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::Chilled { stash: 11 }, + Event::SlashReported { validator: 11, slash_era: 1, .. }, + .., + Event::Slashed { staker: 11, amount: 100 }, + Event::Slashed { staker: 101, amount: 12 } + ] + )); + }) +} - // fails if empty - assert_noop!( - Staking::cancel_deferred_slash(Origin::root(), 1, vec![]), - Error::::EmptyTargets - ); +#[test] +fn retroactive_deferred_slashes_one_before() { + ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { + assert_eq!(BondingDuration::get(), 3); - assert_ok!(Staking::cancel_deferred_slash(Origin::root(), 1, vec![0])); + mock::start_active_era(1); + let exposure_11_at_era1 = Staking::eras_stakers(active_era(), 11); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + // unbond at slash era. + mock::start_active_era(2); + assert_ok!(Staking::chill(RuntimeOrigin::signed(10))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 100)); mock::start_active_era(3); + System::reset_events(); + on_offence_in_era( + &[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, + ); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - // at the start of era 4, slashes from era 1 are processed, - // after being deferred for at least 2 full eras. mock::start_active_era(4); - // the first slash for 10% was cancelled, so no effect. - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(Staking::ledger(10).unwrap().total, 1000); + // slash happens after the next line. mock::start_active_era(5); + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::SlashReported { validator: 11, slash_era: 2, .. }, + .., + Event::Slashed { staker: 11, amount: 100 }, + Event::Slashed { staker: 101, amount: 12 } + ] + )); - let slash_10 = Perbill::from_percent(10); - let slash_15 = Perbill::from_percent(15); - let initial_slash = slash_10 * nominated_value; - - let total_slash = slash_15 * nominated_value; - let actual_slash = total_slash - initial_slash; - - // 5% slash (15 - 10) processed now. - assert_eq!(Balances::free_balance(11), 950); - assert_eq!(Balances::free_balance(101), 2000 - actual_slash); + // their ledger has already been slashed. + assert_eq!(Staking::ledger(10).unwrap().total, 900); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1000)); + assert_eq!(Staking::ledger(10).unwrap().total, 900); }) } #[test] -fn remove_multi_deferred() { +fn staker_cannot_bail_deferred_slash() { + // as long as SlashDeferDuration is less than BondingDuration, this should not be possible. ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { mock::start_active_era(1); assert_eq!(Balances::free_balance(11), 1000); - - 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(10)], - ); + let exposure = Staking::eras_stakers(active_era(), 11); + let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; on_offence_now( &[OffenceDetails { - offender: (21, Staking::eras_stakers(active_era(), 21)), + offender: (11, Staking::eras_stakers(active_era(), 11)), reporters: vec![], }], &[Perbill::from_percent(10)], ); - on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - ); + // now we chill + assert_ok!(Staking::chill(RuntimeOrigin::signed(100))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(100), 500)); - on_offence_now( - &[OffenceDetails { offender: (42, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - ); + assert_eq!(Staking::current_era().unwrap(), 1); + assert_eq!(active_era(), 1); - on_offence_now( - &[OffenceDetails { offender: (69, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], + assert_eq!( + Ledger::::get(100).unwrap(), + StakingLedger { + active: 0, + total: 500, + stash: 101, + claimed_rewards: bounded_vec![], + unlocking: bounded_vec![UnlockChunk { era: 4u32, value: 500 }], + } ); - assert_eq!(::UnappliedSlashes::get(&1).len(), 5); + // no slash yet. + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); - // fails if list is not sorted + // no slash yet. + mock::start_active_era(2); + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(Staking::current_era().unwrap(), 2); + assert_eq!(active_era(), 2); + + // no slash yet. + mock::start_active_era(3); + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(Staking::current_era().unwrap(), 3); + assert_eq!(active_era(), 3); + + // and cannot yet unbond: + assert_storage_noop!(assert!( + Staking::withdraw_unbonded(RuntimeOrigin::signed(100), 0).is_ok() + )); + assert_eq!( + Ledger::::get(100).unwrap().unlocking.into_inner(), + vec![UnlockChunk { era: 4u32, value: 500 as Balance }], + ); + + // at the start of era 4, slashes from era 1 are processed, + // after being deferred for at least 2 full eras. + mock::start_active_era(4); + + assert_eq!(Balances::free_balance(11), 900); + assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); + + // and the leftover of the funds can now be unbonded. + }) +} + +#[test] +fn remove_deferred() { + ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { + mock::start_active_era(1); + + assert_eq!(Balances::free_balance(11), 1000); + + let exposure = Staking::eras_stakers(active_era(), 11); + assert_eq!(Balances::free_balance(101), 2000); + let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; + + // deferred to start of era 4. + on_offence_now( + &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], + &[Perbill::from_percent(10)], + ); + + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); + + mock::start_active_era(2); + + // reported later, but deferred to start of era 4 as well. + System::reset_events(); + on_offence_in_era( + &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], + &[Perbill::from_percent(15)], + 1, + DisableStrategy::WhenSlashed, + ); + + // fails if empty + assert_noop!( + Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![]), + Error::::EmptyTargets + ); + + // cancel one of them. + assert_ok!(Staking::cancel_deferred_slash(RuntimeOrigin::root(), 4, vec![0])); + + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); + + mock::start_active_era(3); + + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); + + // at the start of era 4, slashes from era 1 are processed, + // after being deferred for at least 2 full eras. + mock::start_active_era(4); + + // the first slash for 10% was cancelled, but the 15% one not. + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::SlashReported { validator: 11, slash_era: 1, .. }, + .., + Event::Slashed { staker: 11, amount: 50 }, + Event::Slashed { staker: 101, amount: 7 } + ] + )); + + let slash_10 = Perbill::from_percent(10); + let slash_15 = Perbill::from_percent(15); + let initial_slash = slash_10 * nominated_value; + + let total_slash = slash_15 * nominated_value; + let actual_slash = total_slash - initial_slash; + + // 5% slash (15 - 10) processed now. + assert_eq!(Balances::free_balance(11), 950); + assert_eq!(Balances::free_balance(101), 2000 - actual_slash); + }) +} + +#[test] +fn remove_multi_deferred() { + ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { + mock::start_active_era(1); + + assert_eq!(Balances::free_balance(11), 1000); + + 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(10)], + ); + + on_offence_now( + &[OffenceDetails { + offender: (21, Staking::eras_stakers(active_era(), 21)), + reporters: vec![], + }], + &[Perbill::from_percent(10)], + ); + + on_offence_now( + &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], + &[Perbill::from_percent(25)], + ); + + on_offence_now( + &[OffenceDetails { offender: (42, exposure.clone()), reporters: vec![] }], + &[Perbill::from_percent(25)], + ); + + on_offence_now( + &[OffenceDetails { offender: (69, exposure.clone()), reporters: vec![] }], + &[Perbill::from_percent(25)], + ); + + assert_eq!(::UnappliedSlashes::get(&4).len(), 5); + + // fails if list is not sorted assert_noop!( - Staking::cancel_deferred_slash(Origin::root(), 1, vec![2, 0, 4]), + Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![2, 0, 4]), Error::::NotSortedAndUnique ); // fails if list is not unique assert_noop!( - Staking::cancel_deferred_slash(Origin::root(), 1, vec![0, 2, 2]), + Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![0, 2, 2]), Error::::NotSortedAndUnique ); // fails if bad index assert_noop!( - Staking::cancel_deferred_slash(Origin::root(), 1, vec![1, 2, 3, 4, 5]), + Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![1, 2, 3, 4, 5]), Error::::InvalidSlashIndex ); - assert_ok!(Staking::cancel_deferred_slash(Origin::root(), 1, vec![0, 2, 4])); + assert_ok!(Staking::cancel_deferred_slash(RuntimeOrigin::root(), 4, vec![0, 2, 4])); - let slashes = ::UnappliedSlashes::get(&1); + let slashes = ::UnappliedSlashes::get(&4); assert_eq!(slashes.len(), 2); assert_eq!(slashes[0].validator, 21); assert_eq!(slashes[1].validator, 42); @@ -2863,6 +3198,9 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); + // 100 has approval for 11 as of now + assert!(Staking::nominators(101).unwrap().targets.contains(&11)); + // 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); @@ -2875,39 +3213,268 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid &[Perbill::from_percent(10)], ); + 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::SlashReported { + validator: 11, + fraction: Perbill::from_percent(10), + slash_era: 1 + }, + Event::Slashed { staker: 11, amount: 100 }, + Event::Slashed { staker: 101, amount: 12 }, + ] + ); + // 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); - // This is the best way to check that the validator was chilled; `get` will - // return default value. - for (stash, _) in ::Validators::iter() { - assert!(stash != 11); - } - - let nominations = ::Nominators::get(&101).unwrap(); - - // and make sure that the vote will be ignored even if the validator - // re-registers. - let last_slash = ::SlashingSpans::get(&11).unwrap().last_nonzero_slash(); - assert!(nominations.submitted_in < last_slash); + // check that validator was chilled. + assert!(::Validators::iter().all(|(stash, _)| stash != 11)); // actually re-bond the slashed validator - assert_ok!(Staking::validate(Origin::signed(10), Default::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(10), Default::default())); mock::start_active_era(2); let exposure_11 = Staking::eras_stakers(active_era(), &11); let exposure_21 = Staking::eras_stakers(active_era(), &21); - // 10 is re-elected, but without the support of 100 - assert_eq!(exposure_11.total, 900); + // 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]); + + 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()], + ); + + // 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)], + ); + + // 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::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 } + ] + ); - // 20 is re-elected, with the (almost) entire support of 100 - assert_eq!(exposure_21.total, 1000 + 500 - nominator_slash_amount_11); + // the offence for validator 10 wasn't slashable so it wasn't disabled + assert!(!is_disabled(10)); + // whereas validator 20 gets disabled + assert!(is_disabled(20)); }); } +#[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]); + + 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; + + // offence with no slash associated, BUT disabling + on_offence_in_era( + &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[Perbill::zero()], + now, + DisableStrategy::Always, + ); + + // 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, + ); + + // 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::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 } + ] + ); + + // the offence for validator 10 was explicitly disabled + assert!(is_disabled(10)); + // whereas validator 20 is explicitly not disabled + assert!(!is_disabled(20)); + }); +} + +#[test] +fn offence_threshold_triggers_new_era() { + ExtBuilder::default() + .validator_count(4) + .set_status(41, StakerStatus::Validator) + .build_and_execute(|| { + mock::start_active_era(1); + assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41]); + + assert_eq!( + ::OffendingValidatorsThreshold::get(), + Perbill::from_percent(75), + ); + + // we have 4 validators and an offending validator threshold of 75%, + // once the third validator commits an offence a new era should 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); + let exposure_31 = Staking::eras_stakers(Staking::active_era().unwrap().index, &31); + + on_offence_now( + &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[Perbill::zero()], + ); + + assert_eq!(ForceEra::::get(), Forcing::NotForcing); + + on_offence_now( + &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], + &[Perbill::zero()], + ); + + assert_eq!(ForceEra::::get(), Forcing::NotForcing); + + on_offence_now( + &[OffenceDetails { offender: (31, exposure_31.clone()), reporters: vec![] }], + &[Perbill::zero()], + ); + + assert_eq!(ForceEra::::get(), Forcing::ForceNew); + }); +} + +#[test] +fn disabled_validators_are_kept_disabled_for_whole_era() { + ExtBuilder::default() + .validator_count(4) + .set_status(41, StakerStatus::Validator) + .build_and_execute(|| { + mock::start_active_era(1); + assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41]); + 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)], + ); + + // nominations are not updated. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + + // validator 10 should not be disabled since the offence wasn't slashable + assert!(!is_disabled(10)); + // validator 20 gets disabled since it got slashed + assert!(is_disabled(20)); + + advance_session(); + + // disabled validators should carry-on through all sessions in the era + assert!(!is_disabled(10)); + assert!(is_disabled(20)); + + // validator 10 should now get disabled + on_offence_now( + &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[Perbill::from_percent(25)], + ); + + // nominations are not updated. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + + advance_session(); + + // and both are disabled in the last session of the era + assert!(is_disabled(10)); + assert!(is_disabled(20)); + + mock::start_active_era(2); + + // when a new era starts disabled validators get cleared + assert!(!is_disabled(10)); + assert!(!is_disabled(20)); + }); +} + #[test] fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { // should check that: @@ -2916,7 +3483,7 @@ fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { // * double claim of one era fails ExtBuilder::default().nominate(true).build_and_execute(|| { // Consumed weight for all payout_stakers dispatches that fail - let err_weight = weights::SubstrateWeight::::payout_stakers_alive_staked(0); + let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); let init_balance_10 = Balances::total_balance(&10); let init_balance_100 = Balances::total_balance(&100); @@ -2928,13 +3495,13 @@ fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { Payee::::insert(11, RewardDestination::Controller); Payee::::insert(101, RewardDestination::Controller); - >::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(11, 1)]); // Compute total payout now for whole duration as other parameter won't change let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); mock::start_active_era(1); - >::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(11, 1)]); // Change total issuance in order to modify total payout let _ = Balances::deposit_creating(&999, 1_000_000_000); // Compute total payout now for whole duration as other parameter won't change @@ -2943,7 +3510,7 @@ fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { mock::start_active_era(2); - >::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(11, 1)]); // Change total issuance in order to modify total payout let _ = Balances::deposit_creating(&999, 1_000_000_000); // Compute total payout now for whole duration as other parameter won't change @@ -2951,7 +3518,7 @@ fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { assert!(total_payout_2 != total_payout_0); assert!(total_payout_2 != total_payout_1); - mock::start_active_era(Staking::history_depth() + 1); + mock::start_active_era(HistoryDepth::get() + 1); let active_era = active_era(); @@ -2959,21 +3526,21 @@ fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { let current_era = Staking::current_era().unwrap(); // Last kept is 1: - assert!(current_era - Staking::history_depth() == 1); + assert!(current_era - HistoryDepth::get() == 1); assert_noop!( - Staking::payout_stakers(Origin::signed(1337), 11, 0), + Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 0), // Fail: Era out of history Error::::InvalidEraToReward.with_weight(err_weight) ); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 1)); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 2)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 2)); assert_noop!( - Staking::payout_stakers(Origin::signed(1337), 11, 2), + Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 2), // Fail: Double claim Error::::AlreadyClaimed.with_weight(err_weight) ); assert_noop!( - Staking::payout_stakers(Origin::signed(1337), 11, active_era), + Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, active_era), // Fail: Era not finished yet Error::::InvalidEraToReward.with_weight(err_weight) ); @@ -3010,18 +3577,10 @@ fn zero_slash_keeps_nominators() { assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); - // This is the best way to check that the validator was chilled; `get` will - // return default value. - for (stash, _) in ::Validators::iter() { - assert!(stash != 11); - } - - let nominations = ::Nominators::get(&101).unwrap(); - - // and make sure that the vote will not be ignored, because the slash was - // zero. - let last_slash = ::SlashingSpans::get(&11).unwrap().last_nonzero_slash(); - assert!(nominations.submitted_in >= last_slash); + // 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]); }); } @@ -3088,22 +3647,22 @@ fn six_session_delay() { #[test] fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward() { ExtBuilder::default().build_and_execute(|| { - for i in 0..=::MaxNominatorRewardedPerValidator::get() { + for i in 0..=<::MaxNominatorRewardedPerValidator as Get<_>>::get() { let stash = 10_000 + i as AccountId; let controller = 20_000 + i as AccountId; let balance = 10_000 + i as Balance; Balances::make_free_balance_be(&stash, balance); assert_ok!(Staking::bond( - Origin::signed(stash), + RuntimeOrigin::signed(stash), controller, balance, RewardDestination::Stash )); - assert_ok!(Staking::nominate(Origin::signed(controller), vec![11])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(controller), vec![11])); } mock::start_active_era(1); - >::reward_by_ids(vec![(11, 1)]); + Pallet::::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. let _ = current_total_payout_for_duration(reward_time_per_era()); @@ -3111,7 +3670,7 @@ fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward( mock::make_all_reward_payment(1); // Assert only nominators from 1 to Max are rewarded - for i in 0..=::MaxNominatorRewardedPerValidator::get() { + for i in 0..=<::MaxNominatorRewardedPerValidator as Get<_>>::get() { let stash = 10_000 + i as AccountId; let balance = 10_000 + i as Balance; if stash == 10_000 { @@ -3123,47 +3682,49 @@ fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward( }); } -#[test] -fn set_history_depth_works() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(10); - Staking::set_history_depth(Origin::root(), 20, 0).unwrap(); - assert!(::ErasTotalStake::contains_key(10 - 4)); - assert!(::ErasTotalStake::contains_key(10 - 5)); - Staking::set_history_depth(Origin::root(), 4, 0).unwrap(); - assert!(::ErasTotalStake::contains_key(10 - 4)); - assert!(!::ErasTotalStake::contains_key(10 - 5)); - Staking::set_history_depth(Origin::root(), 3, 0).unwrap(); - assert!(!::ErasTotalStake::contains_key(10 - 4)); - assert!(!::ErasTotalStake::contains_key(10 - 5)); - Staking::set_history_depth(Origin::root(), 8, 0).unwrap(); - assert!(!::ErasTotalStake::contains_key(10 - 4)); - assert!(!::ErasTotalStake::contains_key(10 - 5)); - }); -} - #[test] fn test_payout_stakers() { - // Here we will test validator can set `max_nominators_payout` and it works. - // We also test that `payout_extra_nominators` works. + // Test that payout_stakers work in general, including that only the top + // `T::MaxNominatorRewardedPerValidator` nominators are rewarded. ExtBuilder::default().has_stakers(false).build_and_execute(|| { let balance = 1000; + // Track the exposure of the validator and all nominators. + let mut total_exposure = balance; + // Track the exposure of the validator and the nominators that will get paid out. + let mut payout_exposure = balance; // Create a validator: bond_validator(11, 10, balance); // Default(64) + assert_eq!(Validators::::count(), 1); // Create nominators, targeting stash of validators for i in 0..100 { - bond_nominator(1000 + i, 100 + i, balance + i as Balance, vec![11]); + let bond_amount = balance + i as Balance; + bond_nominator(1000 + i, 100 + i, bond_amount, vec![11]); + total_exposure += bond_amount; + if i >= 36 { + payout_exposure += bond_amount; + }; } + let payout_exposure_part = Perbill::from_rational(payout_exposure, total_exposure); mock::start_active_era(1); Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. - let _ = current_total_payout_for_duration(reward_time_per_era()); + let payout = current_total_payout_for_duration(reward_time_per_era()); + let actual_paid_out = payout_exposure_part * payout; mock::start_active_era(2); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 1)); + + let pre_payout_total_issuance = Balances::total_issuance(); + RewardOnUnbalanceWasCalled::set(false); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); + assert_eq_error_rate!( + Balances::total_issuance(), + pre_payout_total_issuance + actual_paid_out, + 1 + ); + assert!(RewardOnUnbalanceWasCalled::get()); // Top 64 nominators of validator 11 automatically paid out, including the validator // Validator payout goes to controller. @@ -3183,8 +3744,8 @@ fn test_payout_stakers() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![1] + unlocking: Default::default(), + claimed_rewards: bounded_vec![1] }) ); @@ -3192,10 +3753,19 @@ fn test_payout_stakers() { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. - let _ = current_total_payout_for_duration(reward_time_per_era()); + let payout = current_total_payout_for_duration(reward_time_per_era()); + let actual_paid_out = payout_exposure_part * payout; + let pre_payout_total_issuance = Balances::total_issuance(); mock::start_active_era(i); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, i - 1)); + RewardOnUnbalanceWasCalled::set(false); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, i - 1)); + assert_eq_error_rate!( + Balances::total_issuance(), + pre_payout_total_issuance + actual_paid_out, + 1 + ); + assert!(RewardOnUnbalanceWasCalled::get()); } // We track rewards in `claimed_rewards` vec @@ -3205,12 +3775,16 @@ fn test_payout_stakers() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: (1..=14).collect() + unlocking: Default::default(), + claimed_rewards: (1..=14).collect::>().try_into().unwrap() }) ); - for i in 16..100 { + let last_era = 99; + let history_depth = HistoryDepth::get(); + let expected_last_reward_era = last_era - 1; + let expected_start_reward_era = last_era - history_depth; + for i in 16..=last_era { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. let _ = current_total_payout_for_duration(reward_time_per_era()); @@ -3218,31 +3792,45 @@ fn test_payout_stakers() { } // We clean it up as history passes - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 15)); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 98)); + assert_ok!(Staking::payout_stakers( + RuntimeOrigin::signed(1337), + 11, + expected_start_reward_era + )); + assert_ok!(Staking::payout_stakers( + RuntimeOrigin::signed(1337), + 11, + expected_last_reward_era + )); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![15, 98] + unlocking: Default::default(), + claimed_rewards: bounded_vec![expected_start_reward_era, expected_last_reward_era] }) ); // Out of order claims works. - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 69)); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 23)); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 42)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 69)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 23)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 42)); assert_eq!( Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![15, 23, 42, 69, 98] + unlocking: Default::default(), + claimed_rewards: bounded_vec![ + expected_start_reward_era, + 23, + 42, + 69, + expected_last_reward_era + ] }) ); }); @@ -3253,7 +3841,7 @@ fn payout_stakers_handles_basic_errors() { // Here we will test payouts handle all errors. ExtBuilder::default().has_stakers(false).build_and_execute(|| { // Consumed weight for all payout_stakers dispatches that fail - let err_weight = weights::SubstrateWeight::::payout_stakers_alive_staked(0); + let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); // Same setup as the test above let balance = 1000; @@ -3274,41 +3862,56 @@ fn payout_stakers_handles_basic_errors() { // Wrong Era, too big assert_noop!( - Staking::payout_stakers(Origin::signed(1337), 11, 2), + Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 2), Error::::InvalidEraToReward.with_weight(err_weight) ); // Wrong Staker assert_noop!( - Staking::payout_stakers(Origin::signed(1337), 10, 1), + Staking::payout_stakers(RuntimeOrigin::signed(1337), 10, 1), Error::::NotStash.with_weight(err_weight) ); - for i in 3..100 { + let last_era = 99; + for i in 3..=last_era { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. let _ = current_total_payout_for_duration(reward_time_per_era()); mock::start_active_era(i); } - // We are at era 99, with history depth of 84 - // We should be able to payout era 15 through 98 (84 total eras), but not 14 or 99. + + let history_depth = HistoryDepth::get(); + let expected_last_reward_era = last_era - 1; + let expected_start_reward_era = last_era - history_depth; + + // We are at era last_era=99. Given history_depth=80, we should be able + // to payout era starting from expected_start_reward_era=19 through + // expected_last_reward_era=98 (80 total eras), but not 18 or 99. assert_noop!( - Staking::payout_stakers(Origin::signed(1337), 11, 14), + Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, expected_start_reward_era - 1), Error::::InvalidEraToReward.with_weight(err_weight) ); assert_noop!( - Staking::payout_stakers(Origin::signed(1337), 11, 99), + Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, expected_last_reward_era + 1), Error::::InvalidEraToReward.with_weight(err_weight) ); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 15)); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 98)); + assert_ok!(Staking::payout_stakers( + RuntimeOrigin::signed(1337), + 11, + expected_start_reward_era + )); + assert_ok!(Staking::payout_stakers( + RuntimeOrigin::signed(1337), + 11, + expected_last_reward_era + )); // Can't claim again assert_noop!( - Staking::payout_stakers(Origin::signed(1337), 11, 15), + Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, expected_start_reward_era), Error::::AlreadyClaimed.with_weight(err_weight) ); assert_noop!( - Staking::payout_stakers(Origin::signed(1337), 11, 98), + Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, expected_last_reward_era), Error::::AlreadyClaimed.with_weight(err_weight) ); }); @@ -3319,7 +3922,8 @@ fn payout_stakers_handles_weight_refund() { // Note: this test relies on the assumption that `payout_stakers_alive_staked` is solely used by // `payout_stakers` to calculate the weight of each payout op. ExtBuilder::default().has_stakers(false).build_and_execute(|| { - let max_nom_rewarded = ::MaxNominatorRewardedPerValidator::get(); + let max_nom_rewarded = + <::MaxNominatorRewardedPerValidator as Get<_>>::get(); // Make sure the configured value is meaningful for our use. assert!(max_nom_rewarded >= 4); let half_max_nom_rewarded = max_nom_rewarded / 2; @@ -3332,9 +3936,9 @@ fn payout_stakers_handles_weight_refund() { let half_max_nom_rewarded_weight = ::WeightInfo::payout_stakers_alive_staked(half_max_nom_rewarded); let zero_nom_payouts_weight = ::WeightInfo::payout_stakers_alive_staked(0); - assert!(zero_nom_payouts_weight > 0); - assert!(half_max_nom_rewarded_weight > zero_nom_payouts_weight); - assert!(max_nom_rewarded_weight > half_max_nom_rewarded_weight); + assert!(zero_nom_payouts_weight.any_gt(Weight::zero())); + assert!(half_max_nom_rewarded_weight.any_gt(zero_nom_payouts_weight)); + assert!(max_nom_rewarded_weight.any_gt(half_max_nom_rewarded_weight)); let balance = 1000; bond_validator(11, 10, balance); @@ -3355,10 +3959,9 @@ fn payout_stakers_handles_weight_refund() { start_active_era(2); // Collect payouts when there are no nominators - let call = - TestRuntimeCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 1 }); + let call = TestCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 1 }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(20)); + let result = call.dispatch(RuntimeOrigin::signed(20)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), zero_nom_payouts_weight); @@ -3369,10 +3972,9 @@ fn payout_stakers_handles_weight_refund() { start_active_era(3); // Collect payouts for an era where the validator did not receive any points. - let call = - TestRuntimeCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 2 }); + let call = TestCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 2 }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(20)); + let result = call.dispatch(RuntimeOrigin::signed(20)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), zero_nom_payouts_weight); @@ -3383,10 +3985,9 @@ fn payout_stakers_handles_weight_refund() { start_active_era(4); // Collect payouts when the validator has `half_max_nom_rewarded` nominators. - let call = - TestRuntimeCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 3 }); + let call = TestCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 3 }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(20)); + let result = call.dispatch(RuntimeOrigin::signed(20)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), half_max_nom_rewarded_weight); @@ -3407,18 +4008,16 @@ fn payout_stakers_handles_weight_refund() { start_active_era(6); // Collect payouts when the validator had `half_max_nom_rewarded` nominators. - let call = - TestRuntimeCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 5 }); + let call = TestCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 5 }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(20)); + let result = call.dispatch(RuntimeOrigin::signed(20)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), max_nom_rewarded_weight); // Try and collect payouts for an era that has already been collected. - let call = - TestRuntimeCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 5 }); + let call = TestCall::Staking(StakingCall::payout_stakers { validator_stash: 11, era: 5 }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(20)); + let result = call.dispatch(RuntimeOrigin::signed(20)); assert!(result.is_err()); // When there is an error the consumed weight == weight when there are 0 nominator payouts. assert_eq!(extract_actual_weight(&result, &info), zero_nom_payouts_weight); @@ -3436,8 +4035,8 @@ fn bond_during_era_correctly_populates_claimed_rewards() { stash: 9, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: bounded_vec![], }) ); mock::start_active_era(5); @@ -3448,11 +4047,15 @@ fn bond_during_era_correctly_populates_claimed_rewards() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: (0..5).collect(), + unlocking: Default::default(), + claimed_rewards: (0..5).collect::>().try_into().unwrap(), }) ); - mock::start_active_era(99); + + // make sure only era upto history depth is stored + let current_era = 99; + let last_reward_era = 99 - HistoryDepth::get(); + mock::start_active_era(current_era); bond_validator(13, 12, 1000); assert_eq!( Staking::ledger(&12), @@ -3460,8 +4063,11 @@ fn bond_during_era_correctly_populates_claimed_rewards() { stash: 13, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: (15..99).collect(), + unlocking: Default::default(), + claimed_rewards: (last_reward_era..current_era) + .collect::>() + .try_into() + .unwrap(), }) ); }); @@ -3471,42 +4077,68 @@ fn bond_during_era_correctly_populates_claimed_rewards() { fn offences_weight_calculated_correctly() { ExtBuilder::default().nominate(true).build_and_execute(|| { // On offence with zero offenders: 4 Reads, 1 Write - let zero_offence_weight = ::DbWeight::get().reads_writes(4, 1); - assert_eq!(Staking::on_offence(&[], &[Perbill::from_percent(50)], 0), zero_offence_weight); + let zero_offence_weight = + ::DbWeight::get().reads_writes(4, 1); + assert_eq!( + Staking::on_offence(&[], &[Perbill::from_percent(50)], 0, DisableStrategy::WhenSlashed), + zero_offence_weight + ); // On Offence with N offenders, Unapplied: 4 Reads, 1 Write + 4 Reads, 5 Writes - let n_offence_unapplied_weight = ::DbWeight::get().reads_writes(4, 1) - + ::DbWeight::get().reads_writes(4, 5); - - let offenders: Vec::AccountId, pallet_session::historical::IdentificationTuple>> - = (1..10).map(|i| - OffenceDetails { - offender: (i, Staking::eras_stakers(active_era(), i)), - reporters: vec![], - } - ).collect(); - assert_eq!(Staking::on_offence(&offenders, &[Perbill::from_percent(50)], 0), n_offence_unapplied_weight); + let n_offence_unapplied_weight = ::DbWeight::get() + .reads_writes(4, 1) + + ::DbWeight::get().reads_writes(4, 5); + + let offenders: Vec< + OffenceDetails< + ::AccountId, + pallet_session::historical::IdentificationTuple, + >, + > = (1..10) + .map(|i| OffenceDetails { + offender: (i, Staking::eras_stakers(active_era(), i)), + reporters: vec![], + }) + .collect(); + assert_eq!( + Staking::on_offence( + &offenders, + &[Perbill::from_percent(50)], + 0, + DisableStrategy::WhenSlashed + ), + n_offence_unapplied_weight + ); // On Offence with one offenders, Applied - let one_offender = [ - OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![1], - }, - ]; + let one_offender = [OffenceDetails { + offender: (11, Staking::eras_stakers(active_era(), 11)), + reporters: vec![1], + }]; let n = 1; // Number of offenders let rw = 3 + 3 * n; // rw reads and writes - let one_offence_unapplied_weight = ::DbWeight::get().reads_writes(4, 1) - + ::DbWeight::get().reads_writes(rw, rw) + let one_offence_unapplied_weight = + ::DbWeight::get().reads_writes(4, 1) + + + ::DbWeight::get().reads_writes(rw, rw) // One `slash_cost` + ::DbWeight::get().reads_writes(6, 5) // `slash_cost` * nominators (1) + ::DbWeight::get().reads_writes(6, 5) // `reward_cost` * reporters (1) - + ::DbWeight::get().reads_writes(2, 2); + + ::DbWeight::get().reads_writes(2, 2) + ; - assert_eq!(Staking::on_offence(&one_offender, &[Perbill::from_percent(50)], 0), one_offence_unapplied_weight); + assert_eq!( + Staking::on_offence( + &one_offender, + &[Perbill::from_percent(50)], + 0, + DisableStrategy::WhenSlashed{} + ), + one_offence_unapplied_weight + ); }); } @@ -3521,7 +4153,7 @@ fn payout_creates_controller() { bond_nominator(1234, 1337, 100, vec![11]); // kill controller - assert_ok!(Balances::transfer(Origin::signed(1337), 1234, 100)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1337), 1234, 100)); assert_eq!(Balances::free_balance(1337), 0); mock::start_active_era(1); @@ -3529,7 +4161,7 @@ fn payout_creates_controller() { // compute and ensure the reward amount is greater than zero. let _ = current_total_payout_for_duration(reward_time_per_era()); mock::start_active_era(2); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 1)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); // Controller is created assert!(Balances::free_balance(1337) > 0); @@ -3547,7 +4179,7 @@ fn payout_to_any_account_works() { bond_nominator(1234, 1337, 100, vec![11]); // Update payout location - assert_ok!(Staking::set_payee(Origin::signed(1337), RewardDestination::Account(42))); + assert_ok!(Staking::set_payee(RuntimeOrigin::signed(1337), RewardDestination::Account(42))); // Reward Destination account doesn't exist assert_eq!(Balances::free_balance(42), 0); @@ -3557,7 +4189,7 @@ fn payout_to_any_account_works() { // compute and ensure the reward amount is greater than zero. let _ = current_total_payout_for_duration(reward_time_per_era()); mock::start_active_era(2); - assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 1)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); // Payment is successful assert!(Balances::free_balance(42) > 0); @@ -3679,27 +4311,30 @@ fn cannot_rebond_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 10 * 1000, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: Default::default(), + claimed_rewards: bounded_vec![], } ); // unbond all of it. must be chilled first. - assert_ok!(Staking::chill(Origin::signed(20))); - assert_ok!(Staking::unbond(Origin::signed(20), 10 * 1000)); + assert_ok!(Staking::chill(RuntimeOrigin::signed(20))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(20), 10 * 1000)); assert_eq!( Staking::ledger(&20).unwrap(), StakingLedger { stash: 21, total: 10 * 1000, active: 0, - unlocking: vec![UnlockChunk { value: 10 * 1000, era: 3 }], - claimed_rewards: vec![] + unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, era: 3 }], + claimed_rewards: bounded_vec![], } ); // now bond a wee bit more - assert_noop!(Staking::rebond(Origin::signed(20), 5), Error::::InsufficientBond); + assert_noop!( + Staking::rebond(RuntimeOrigin::signed(20), 5), + Error::::InsufficientBond + ); }) } @@ -3716,28 +4351,28 @@ fn cannot_bond_extra_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 10 * 1000, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: Default::default(), + claimed_rewards: bounded_vec![], } ); // unbond all of it. must be chilled first. - assert_ok!(Staking::chill(Origin::signed(20))); - assert_ok!(Staking::unbond(Origin::signed(20), 10 * 1000)); + assert_ok!(Staking::chill(RuntimeOrigin::signed(20))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(20), 10 * 1000)); assert_eq!( Staking::ledger(&20).unwrap(), StakingLedger { stash: 21, total: 10 * 1000, active: 0, - unlocking: vec![UnlockChunk { value: 10 * 1000, era: 3 }], - claimed_rewards: vec![] + unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, era: 3 }], + claimed_rewards: bounded_vec![], } ); // now bond a wee bit more assert_noop!( - Staking::bond_extra(Origin::signed(21), 5), + Staking::bond_extra(RuntimeOrigin::signed(21), 5), Error::::InsufficientBond, ); }) @@ -3757,15 +4392,15 @@ fn do_not_die_when_active_is_ed() { stash: 21, total: 1000 * ed, active: 1000 * ed, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: Default::default(), + claimed_rewards: bounded_vec![], } ); // when unbond all of it except ed. - assert_ok!(Staking::unbond(Origin::signed(20), 999 * ed)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(20), 999 * ed)); start_active_era(3); - assert_ok!(Staking::withdraw_unbonded(Origin::signed(20), 100)); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(20), 100)); // then assert_eq!( @@ -3774,8 +4409,8 @@ fn do_not_die_when_active_is_ed() { stash: 21, total: ed, active: ed, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: Default::default(), + claimed_rewards: bounded_vec![], } ); }) @@ -3785,7 +4420,7 @@ fn do_not_die_when_active_is_ed() { fn on_finalize_weight_is_nonzero() { ExtBuilder::default().build_and_execute(|| { let on_finalize_weight = ::DbWeight::get().reads(1); - assert!(>::on_initialize(1) >= on_finalize_weight); + assert!(>::on_initialize(1).all_gte(on_finalize_weight)); }) } @@ -3796,9 +4431,10 @@ mod election_data_provider { #[test] fn targets_2sec_block() { let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators) < - 2 * frame_support::weights::constants::WEIGHT_PER_SECOND - { + while ::WeightInfo::get_npos_targets(validators).all_lt(Weight::from_parts( + 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + u64::MAX, + )) { validators += 1; } @@ -3810,13 +4446,14 @@ mod election_data_provider { // we assume a network only wants up to 1000 validators in most cases, thus having 2000 // candidates is as high as it gets. let validators = 2000; - // we assume the worse case: each validator also has a slashing span. - let slashing_spans = validators; let mut nominators = 1000; - while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) < - 2 * frame_support::weights::constants::WEIGHT_PER_SECOND - { + while ::WeightInfo::get_npos_voters(validators, nominators).all_lt( + Weight::from_parts( + 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + u64::MAX, + ), + ) { nominators += 1; } @@ -3829,63 +4466,96 @@ mod election_data_provider { #[test] fn voters_include_self_vote() { ExtBuilder::default().nominate(false).build_and_execute(|| { - assert!(>::iter().map(|(x, _)| x).all(|v| Staking::voters(None) - .unwrap() - .into_iter() - .find(|(w, _, t)| { v == *w && t[0] == *w }) - .is_some())) + assert!(>::iter().map(|(x, _)| x).all(|v| Staking::electing_voters( + None + ) + .unwrap() + .into_iter() + .any(|(w, _, t)| { v == w && t[0] == w }))) }) } #[test] - fn voters_exclude_slashed() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - assert_eq!( - >::voters(None) - .unwrap() - .iter() - .find(|x| x.0 == 101) - .unwrap() - .2, - vec![11, 21] - ); - - start_active_era(1); - add_slash(&11); - - // 11 is gone. - start_active_era(2); - assert_eq!( - >::voters(None) - .unwrap() - .iter() - .find(|x| x.0 == 101) - .unwrap() - .2, - vec![21] - ); - - // resubmit and it is back - assert_ok!(Staking::nominate(Origin::signed(100), vec![11, 21])); - assert_eq!( - >::voters(None) - .unwrap() - .iter() - .find(|x| x.0 == 101) - .unwrap() - .2, - vec![11, 21] - ); - }) + fn respects_snapshot_len_limits() { + ExtBuilder::default() + .set_status(41, StakerStatus::Validator) + .build_and_execute(|| { + // sum of all nominators who'd be voters (1), plus the self-votes (4). + assert_eq!(::VoterList::count(), 5); + + // if limits is less.. + assert_eq!(Staking::electing_voters(Some(1)).unwrap().len(), 1); + + // if limit is equal.. + assert_eq!(Staking::electing_voters(Some(5)).unwrap().len(), 5); + + // if limit is more. + assert_eq!(Staking::electing_voters(Some(55)).unwrap().len(), 5); + + // if target limit is more.. + assert_eq!(Staking::electable_targets(Some(6)).unwrap().len(), 4); + assert_eq!(Staking::electable_targets(Some(4)).unwrap().len(), 4); + + // if target limit is less, then we return an error. + assert_eq!( + Staking::electable_targets(Some(1)).unwrap_err(), + "Target snapshot too big" + ); + }); } + // Tests the criteria that in `ElectionDataProvider::voters` function, we try to get at most + // `maybe_max_len` voters, and if some of them end up being skipped, we iterate at most `2 * + // maybe_max_len`. #[test] - fn respects_len_limits() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(Staking::voters(Some(1)).unwrap_err(), "Voter snapshot too big"); - assert_eq!(Staking::targets(Some(1)).unwrap_err(), "Target snapshot too big"); - }); + fn only_iterates_max_2_times_max_allowed_len() { + ExtBuilder::default() + .nominate(false) + // the best way to invalidate a bunch of nominators is to have them nominate a lot of + // ppl, but then lower the MaxNomination limit. + .add_staker( + 61, + 60, + 2_000, + StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), + ) + .add_staker( + 71, + 70, + 2_000, + StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), + ) + .add_staker( + 81, + 80, + 2_000, + StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), + ) + .build_and_execute(|| { + // all voters ordered by stake, + assert_eq!( + ::VoterList::iter().collect::>(), + vec![61, 71, 81, 11, 21, 31] + ); + + MaxNominations::set(2); + + // we want 2 voters now, and in maximum we allow 4 iterations. This is what happens: + // 61 is pruned; + // 71 is pruned; + // 81 is pruned; + // 11 is taken; + // we finish since the 2x limit is reached. + assert_eq!( + Staking::electing_voters(Some(2)) + .unwrap() + .iter() + .map(|(stash, _, _)| stash) + .copied() + .collect::>(), + vec![11], + ); + }); } #[test] @@ -3914,13 +4584,13 @@ mod election_data_provider { assert_eq!(staking_events().len(), 3); assert_eq!(*staking_events().last().unwrap(), Event::StakersElected); - Staking::force_no_eras(Origin::root()).unwrap(); + Staking::force_no_eras(RuntimeOrigin::root()).unwrap(); assert_eq!(Staking::next_election_prediction(System::block_number()), u64::MAX); - Staking::force_new_era_always(Origin::root()).unwrap(); + Staking::force_new_era_always(RuntimeOrigin::root()).unwrap(); assert_eq!(Staking::next_election_prediction(System::block_number()), 45 + 5); - Staking::force_new_era(Origin::root()).unwrap(); + Staking::force_new_era(RuntimeOrigin::root()).unwrap(); assert_eq!(Staking::next_election_prediction(System::block_number()), 45 + 5); // Do a fail election @@ -3952,7 +4622,11 @@ fn count_check_works() { Validators::::insert(987654321, ValidatorPrefs::default()); Nominators::::insert( 987654321, - Nominations { targets: vec![], submitted_in: Default::default(), suppressed: false }, + Nominations { + targets: Default::default(), + submitted_in: Default::default(), + suppressed: false, + }, ); }) } @@ -3966,40 +4640,51 @@ fn min_bond_checks_work() { .min_validator_bond(1_500) .build_and_execute(|| { // 500 is not enough for any role - assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Controller)); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(3), + 4, + 500, + RewardDestination::Controller + )); assert_noop!( - Staking::nominate(Origin::signed(4), vec![1]), + Staking::nominate(RuntimeOrigin::signed(4), vec![1]), Error::::InsufficientBond ); assert_noop!( - Staking::validate(Origin::signed(4), ValidatorPrefs::default()), + Staking::validate(RuntimeOrigin::signed(4), ValidatorPrefs::default()), Error::::InsufficientBond, ); // 1000 is enough for nominator - assert_ok!(Staking::bond_extra(Origin::signed(3), 500)); - assert_ok!(Staking::nominate(Origin::signed(4), vec![1])); + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(3), 500)); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![1])); assert_noop!( - Staking::validate(Origin::signed(4), ValidatorPrefs::default()), + Staking::validate(RuntimeOrigin::signed(4), ValidatorPrefs::default()), Error::::InsufficientBond, ); // 1500 is enough for validator - assert_ok!(Staking::bond_extra(Origin::signed(3), 500)); - assert_ok!(Staking::nominate(Origin::signed(4), vec![1])); - assert_ok!(Staking::validate(Origin::signed(4), ValidatorPrefs::default())); + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(3), 500)); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![1])); + assert_ok!(Staking::validate(RuntimeOrigin::signed(4), ValidatorPrefs::default())); // Can't unbond anything as validator - assert_noop!(Staking::unbond(Origin::signed(4), 500), Error::::InsufficientBond); + assert_noop!( + Staking::unbond(RuntimeOrigin::signed(4), 500), + Error::::InsufficientBond + ); // Once they are a nominator, they can unbond 500 - assert_ok!(Staking::nominate(Origin::signed(4), vec![1])); - assert_ok!(Staking::unbond(Origin::signed(4), 500)); - assert_noop!(Staking::unbond(Origin::signed(4), 500), Error::::InsufficientBond); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![1])); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(4), 500)); + assert_noop!( + Staking::unbond(RuntimeOrigin::signed(4), 500), + Error::::InsufficientBond + ); // Once they are chilled they can unbond everything - assert_ok!(Staking::chill(Origin::signed(4))); - assert_ok!(Staking::unbond(Origin::signed(4), 1000)); + assert_ok!(Staking::chill(RuntimeOrigin::signed(4))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(4), 1000)); }) } @@ -4011,8 +4696,8 @@ fn chill_other_works() { .min_nominator_bond(1_000) .min_validator_bond(1_500) .build_and_execute(|| { - let initial_validators = CounterForValidators::::get(); - let initial_nominators = CounterForNominators::::get(); + let initial_validators = Validators::::count(); + let initial_nominators = Nominators::::count(); for i in 0..15 { let a = 4 * i; let b = 4 * i + 1; @@ -4025,21 +4710,21 @@ fn chill_other_works() { // Nominator assert_ok!(Staking::bond( - Origin::signed(a), + RuntimeOrigin::signed(a), b, 1000, RewardDestination::Controller )); - assert_ok!(Staking::nominate(Origin::signed(b), vec![1])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(b), vec![1])); // Validator assert_ok!(Staking::bond( - Origin::signed(c), + RuntimeOrigin::signed(c), d, 1500, RewardDestination::Controller )); - assert_ok!(Staking::validate(Origin::signed(d), ValidatorPrefs::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(d), ValidatorPrefs::default())); } // To chill other users, we need to: @@ -4052,119 +4737,131 @@ fn chill_other_works() { // Can't chill these users assert_noop!( - Staking::chill_other(Origin::signed(1337), 1), + Staking::chill_other(RuntimeOrigin::signed(1337), 1), Error::::CannotChillOther ); assert_noop!( - Staking::chill_other(Origin::signed(1337), 3), + Staking::chill_other(RuntimeOrigin::signed(1337), 3), Error::::CannotChillOther ); // Change the minimum bond... but no limits. - assert_ok!(Staking::set_staking_limits(Origin::root(), 1_500, 2_000, None, None, None)); + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Set(1_500), + ConfigOp::Set(2_000), + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove + )); // Still can't chill these users assert_noop!( - Staking::chill_other(Origin::signed(1337), 1), + Staking::chill_other(RuntimeOrigin::signed(1337), 1), Error::::CannotChillOther ); assert_noop!( - Staking::chill_other(Origin::signed(1337), 3), + Staking::chill_other(RuntimeOrigin::signed(1337), 3), Error::::CannotChillOther ); // Add limits, but no threshold - assert_ok!(Staking::set_staking_limits( - Origin::root(), - 1_500, - 2_000, - Some(10), - Some(10), - None + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Set(10), + ConfigOp::Set(10), + ConfigOp::Noop, + ConfigOp::Noop )); // Still can't chill these users assert_noop!( - Staking::chill_other(Origin::signed(1337), 1), + Staking::chill_other(RuntimeOrigin::signed(1337), 1), Error::::CannotChillOther ); assert_noop!( - Staking::chill_other(Origin::signed(1337), 3), + Staking::chill_other(RuntimeOrigin::signed(1337), 3), Error::::CannotChillOther ); // Add threshold, but no limits - assert_ok!(Staking::set_staking_limits( - Origin::root(), - 1_500, - 2_000, - None, - None, - Some(Percent::from_percent(0)) + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Noop, + ConfigOp::Noop )); // Still can't chill these users assert_noop!( - Staking::chill_other(Origin::signed(1337), 1), + Staking::chill_other(RuntimeOrigin::signed(1337), 1), Error::::CannotChillOther ); assert_noop!( - Staking::chill_other(Origin::signed(1337), 3), + Staking::chill_other(RuntimeOrigin::signed(1337), 3), Error::::CannotChillOther ); // Add threshold and limits - assert_ok!(Staking::set_staking_limits( - Origin::root(), - 1_500, - 2_000, - Some(10), - Some(10), - Some(Percent::from_percent(75)) + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Set(10), + ConfigOp::Set(10), + ConfigOp::Set(Percent::from_percent(75)), + ConfigOp::Noop )); // 16 people total because tests start with 2 active one - assert_eq!(CounterForNominators::::get(), 15 + initial_nominators); - assert_eq!(CounterForValidators::::get(), 15 + initial_validators); + assert_eq!(Nominators::::count(), 15 + initial_nominators); + assert_eq!(Validators::::count(), 15 + initial_validators); // Users can now be chilled down to 7 people, so we try to remove 9 of them (starting // with 16) for i in 6..15 { let b = 4 * i + 1; let d = 4 * i + 3; - assert_ok!(Staking::chill_other(Origin::signed(1337), b)); - assert_ok!(Staking::chill_other(Origin::signed(1337), d)); + assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), b)); + assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), d)); } // chill a nominator. Limit is not reached, not chill-able - assert_eq!(CounterForNominators::::get(), 7); + assert_eq!(Nominators::::count(), 7); assert_noop!( - Staking::chill_other(Origin::signed(1337), 1), + Staking::chill_other(RuntimeOrigin::signed(1337), 1), Error::::CannotChillOther ); // chill a validator. Limit is reached, chill-able. - assert_eq!(CounterForValidators::::get(), 9); - assert_ok!(Staking::chill_other(Origin::signed(1337), 3)); + assert_eq!(Validators::::count(), 9); + assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), 3)); }) } #[test] fn capped_stakers_works() { ExtBuilder::default().build_and_execute(|| { - let validator_count = CounterForValidators::::get(); + let validator_count = Validators::::count(); assert_eq!(validator_count, 3); - let nominator_count = CounterForNominators::::get(); + let nominator_count = Nominators::::count(); assert_eq!(nominator_count, 1); // Change the maximums let max = 10; - assert_ok!(Staking::set_staking_limits( - Origin::root(), - 10, - 10, - Some(max), - Some(max), - Some(Percent::from_percent(0)) + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Set(10), + ConfigOp::Set(10), + ConfigOp::Set(max), + ConfigOp::Set(max), + ConfigOp::Remove, + ConfigOp::Remove, )); // can create `max - validator_count` validators @@ -4176,7 +4873,10 @@ fn capped_stakers_works() { RewardDestination::Controller, ) .unwrap(); - assert_ok!(Staking::validate(Origin::signed(controller), ValidatorPrefs::default())); + assert_ok!(Staking::validate( + RuntimeOrigin::signed(controller), + ValidatorPrefs::default() + )); some_existing_validator = controller; } @@ -4189,7 +4889,7 @@ fn capped_stakers_works() { .unwrap(); assert_noop!( - Staking::validate(Origin::signed(last_validator), ValidatorPrefs::default()), + Staking::validate(RuntimeOrigin::signed(last_validator), ValidatorPrefs::default()), Error::::TooManyValidators, ); @@ -4202,7 +4902,7 @@ fn capped_stakers_works() { RewardDestination::Controller, ) .unwrap(); - assert_ok!(Staking::nominate(Origin::signed(controller), vec![1])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(controller), vec![1])); some_existing_nominator = controller; } @@ -4214,21 +4914,756 @@ fn capped_stakers_works() { ) .unwrap(); assert_noop!( - Staking::nominate(Origin::signed(last_nominator), vec![1]), + Staking::nominate(RuntimeOrigin::signed(last_nominator), vec![1]), Error::::TooManyNominators ); // Re-nominate works fine - assert_ok!(Staking::nominate(Origin::signed(some_existing_nominator), vec![1])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(some_existing_nominator), vec![1])); // Re-validate works fine assert_ok!(Staking::validate( - Origin::signed(some_existing_validator), + RuntimeOrigin::signed(some_existing_validator), ValidatorPrefs::default() )); // No problem when we set to `None` again - assert_ok!(Staking::set_staking_limits(Origin::root(), 10, 10, None, None, None)); - assert_ok!(Staking::nominate(Origin::signed(last_nominator), vec![1])); - assert_ok!(Staking::validate(Origin::signed(last_validator), ValidatorPrefs::default())); + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Noop, + ConfigOp::Noop, + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(last_nominator), vec![1])); + assert_ok!(Staking::validate( + RuntimeOrigin::signed(last_validator), + ValidatorPrefs::default() + )); + }) +} + +#[test] +fn min_commission_works() { + ExtBuilder::default().build_and_execute(|| { + // account 10 controls the stash from account 11 + assert_ok!(Staking::validate( + RuntimeOrigin::signed(10), + ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } + )); + + // event emitted should be correct + assert_eq!( + *staking_events().last().unwrap(), + Event::ValidatorPrefsSet { + stash: 11, + prefs: ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } + } + ); + + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Set(Perbill::from_percent(10)), + )); + + // can't make it less than 10 now + assert_noop!( + Staking::validate( + RuntimeOrigin::signed(10), + ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } + ), + Error::::CommissionTooLow + ); + + // can only change to higher. + assert_ok!(Staking::validate( + RuntimeOrigin::signed(10), + ValidatorPrefs { commission: Perbill::from_percent(10), blocked: false } + )); + + assert_ok!(Staking::validate( + RuntimeOrigin::signed(10), + ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false } + )); + }) +} + +#[test] +fn change_of_max_nominations() { + use frame_election_provider_support::ElectionDataProvider; + ExtBuilder::default() + .add_staker(60, 61, 10, StakerStatus::Nominator(vec![1])) + .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) + .balance_factor(10) + .build_and_execute(|| { + // pre-condition + assert_eq!(MaxNominations::get(), 16); + + assert_eq!( + Nominators::::iter() + .map(|(k, n)| (k, n.targets.len())) + .collect::>(), + vec![(70, 3), (101, 2), (60, 1)] + ); + // 3 validators and 3 nominators + assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); + + // abrupt change from 16 to 4, everyone should be fine. + MaxNominations::set(4); + + assert_eq!( + Nominators::::iter() + .map(|(k, n)| (k, n.targets.len())) + .collect::>(), + vec![(70, 3), (101, 2), (60, 1)] + ); + assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); + + // abrupt change from 4 to 3, everyone should be fine. + MaxNominations::set(3); + + assert_eq!( + Nominators::::iter() + .map(|(k, n)| (k, n.targets.len())) + .collect::>(), + vec![(70, 3), (101, 2), (60, 1)] + ); + assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); + + // abrupt change from 3 to 2, this should cause some nominators to be non-decodable, and + // thus non-existent unless if they update. + MaxNominations::set(2); + + assert_eq!( + Nominators::::iter() + .map(|(k, n)| (k, n.targets.len())) + .collect::>(), + vec![(101, 2), (60, 1)] + ); + // 70 is still in storage.. + assert!(Nominators::::contains_key(70)); + // but its value cannot be decoded and default is returned. + assert!(Nominators::::get(70).is_none()); + + assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 2); + assert!(Nominators::::contains_key(101)); + + // abrupt change from 2 to 1, this should cause some nominators to be non-decodable, and + // thus non-existent unless if they update. + MaxNominations::set(1); + + assert_eq!( + Nominators::::iter() + .map(|(k, n)| (k, n.targets.len())) + .collect::>(), + vec![(60, 1)] + ); + assert!(Nominators::::contains_key(70)); + assert!(Nominators::::contains_key(60)); + assert!(Nominators::::get(70).is_none()); + assert!(Nominators::::get(60).is_some()); + assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 1); + + // now one of them can revive themselves by re-nominating to a proper value. + assert_ok!(Staking::nominate(RuntimeOrigin::signed(71), vec![1])); + assert_eq!( + Nominators::::iter() + .map(|(k, n)| (k, n.targets.len())) + .collect::>(), + vec![(70, 1), (60, 1)] + ); + + // or they can be chilled by any account. + assert!(Nominators::::contains_key(101)); + assert!(Nominators::::get(101).is_none()); + assert_ok!(Staking::chill_other(RuntimeOrigin::signed(70), 100)); + assert!(!Nominators::::contains_key(101)); + assert!(Nominators::::get(101).is_none()); + }) +} + +mod sorted_list_provider { + use super::*; + use frame_election_provider_support::SortedListProvider; + + #[test] + fn re_nominate_does_not_change_counters_or_list() { + ExtBuilder::default().nominate(true).build_and_execute(|| { + // given + let pre_insert_voter_count = + (Nominators::::count() + Validators::::count()) as u32; + assert_eq!(::VoterList::count(), pre_insert_voter_count); + + assert_eq!( + ::VoterList::iter().collect::>(), + vec![11, 21, 31, 101] + ); + + // when account 101 renominates + assert_ok!(Staking::nominate(RuntimeOrigin::signed(100), vec![41])); + + // then counts don't change + assert_eq!(::VoterList::count(), pre_insert_voter_count); + // and the list is the same + assert_eq!( + ::VoterList::iter().collect::>(), + vec![11, 21, 31, 101] + ); + }); + } + + #[test] + fn re_validate_does_not_change_counters_or_list() { + ExtBuilder::default().nominate(false).build_and_execute(|| { + // given + let pre_insert_voter_count = + (Nominators::::count() + Validators::::count()) as u32; + assert_eq!(::VoterList::count(), pre_insert_voter_count); + + assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); + + // when account 11 re-validates + assert_ok!(Staking::validate(RuntimeOrigin::signed(10), Default::default())); + + // then counts don't change + assert_eq!(::VoterList::count(), pre_insert_voter_count); + // and the list is the same + assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); + }); + } +} + +#[test] +fn force_apply_min_commission_works() { + let prefs = |c| ValidatorPrefs { commission: Perbill::from_percent(c), blocked: false }; + let validators = || Validators::::iter().collect::>(); + ExtBuilder::default().build_and_execute(|| { + assert_ok!(Staking::validate(RuntimeOrigin::signed(30), prefs(10))); + assert_ok!(Staking::validate(RuntimeOrigin::signed(20), prefs(5))); + + // Given + assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]); + MinCommission::::set(Perbill::from_percent(5)); + + // When applying to a commission greater than min + assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 31)); + // Then the commission is not changed + assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]); + + // When applying to a commission that is equal to min + assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 21)); + // Then the commission is not changed + assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]); + + // When applying to a commission that is less than the min + assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 11)); + // Then the commission is bumped to the min + assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(5))]); + + // When applying commission to a validator that doesn't exist then storage is not altered + assert_noop!( + Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 420), + Error::::NotStash + ); + }); +} + +#[test] +fn proportional_slash_stop_slashing_if_remaining_zero() { + let c = |era, value| UnlockChunk:: { era, value }; + // Given + let mut ledger = StakingLedger:: { + stash: 123, + total: 40, + active: 20, + // we have some chunks, but they are not affected. + unlocking: bounded_vec![c(1, 10), c(2, 10)], + claimed_rewards: bounded_vec![], + }; + + assert_eq!(BondingDuration::get(), 3); + + // should not slash more than the amount requested, by accidentally slashing the first chunk. + assert_eq!(ledger.slash(18, 1, 0), 18); +} + +#[test] +fn proportional_ledger_slash_works() { + let c = |era, value| UnlockChunk:: { era, value }; + // Given + let mut ledger = StakingLedger:: { + stash: 123, + total: 10, + active: 10, + unlocking: bounded_vec![], + claimed_rewards: bounded_vec![], + }; + assert_eq!(BondingDuration::get(), 3); + + // When we slash a ledger with no unlocking chunks + assert_eq!(ledger.slash(5, 1, 0), 5); + // Then + assert_eq!(ledger.total, 5); + assert_eq!(ledger.active, 5); + assert_eq!(LedgerSlashPerEra::get().0, 5); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // When we slash a ledger with no unlocking chunks and the slash amount is greater then the + // total + assert_eq!(ledger.slash(11, 1, 0), 5); + // Then + assert_eq!(ledger.total, 0); + assert_eq!(ledger.active, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // Given + ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)]; + ledger.total = 2 * 10; + ledger.active = 0; + // When all the chunks overlap with the slash eras + assert_eq!(ledger.slash(20, 0, 0), 20); + // Then + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.total = 4 * 100; + ledger.active = 0; + // When the first 2 chunks don't overlap with the affected range of unlock eras. + assert_eq!(ledger.slash(140, 0, 3), 140); + // Then + assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]); + assert_eq!(ledger.total, 4 * 100 - 140); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.total = 4 * 100; + ledger.active = 0; + // When the first 2 chunks don't overlap with the affected range of unlock eras. + assert_eq!(ledger.slash(15, 0, 3), 15); + // Then + assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 100 - 8), c(7, 100 - 7)]); + assert_eq!(ledger.total, 4 * 100 - 15); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 92), (7, 93)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + // 900 + ledger.total = 40 + 10 + 100 + 250 + 500; + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(900 / 2, 0, 0), 450); + // Then + assert_eq!(ledger.active, 500 / 2); + assert_eq!(ledger.unlocking, vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]); + assert_eq!(ledger.total, 900 / 2); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)]) + ); + + // slash 1/4th with not chunk. + ledger.unlocking = bounded_vec![]; + ledger.active = 500; + ledger.total = 500; + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4); + // Then + assert_eq!(ledger.active, 3 * 500 / 4); + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, ledger.active); + assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // Given we have the same as above, + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + assert_eq!(ledger.total, 900); + // When we have a higher min balance + assert_eq!( + ledger.slash( + 900 / 2, + 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to + * get swept */ + 0 + ), + 450 + ); + assert_eq!(ledger.active, 500 / 2); + // the last chunk was not slashed 50% like all the rest, because some other earlier chunks got + // dusted. + assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]); + assert_eq!(ledger.total, 900 / 2); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)]) + ); + + // Given + // slash order --------------------NA--------2----------0----------1---- + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + assert_eq!( + ledger.slash( + 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 + 0, + 3 /* slash era 6 first, so the affected parts are era 6, era 7 and + * ledge.active. This will cause the affected to go to zero, and then we will + * start slashing older chunks */ + ), + 500 + 250 + 10 + 100 / 2 + ); + // Then + assert_eq!(ledger.active, 0); + assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]); + assert_eq!(ledger.total, 90); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)])); + + // Given + // iteration order------------------NA---------2----------0----------1---- + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.active = 100; + ledger.total = 5 * 100; + // When + assert_eq!( + ledger.slash( + 351, // active + era 6 + era 7 + era 5 / 2 + 1 + 50, // min balance - everything slashed below 50 will get dusted + 3 /* slash era 3+3 first, so the affected parts are era 6, era 7 and + * ledge.active. This will cause the affected to go to zero, and then we will + * start slashing older chunks */ + ), + 400 + ); + // Then + assert_eq!(ledger.active, 0); + assert_eq!(ledger.unlocking, vec![c(4, 100)]); + assert_eq!(ledger.total, 100); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)])); + + // Tests for saturating arithmetic + + // Given + let slash = u64::MAX as Balance * 2; + // The value of the other parts of ledger that will get slashed + let value = slash - (10 * 4); + + ledger.active = 10; + ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; + ledger.total = value + 40; + // When + let slash_amount = ledger.slash(slash, 0, 0); + assert_eq_error_rate!(slash_amount, slash, 5); + // Then + assert_eq!(ledger.active, 0); // slash of 9 + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)])); + + // Given + use sp_runtime::PerThing as _; + let slash = u64::MAX as Balance * 2; + let value = u64::MAX as Balance * 2; + let unit = 100; + // slash * value that will saturate + assert!(slash.checked_mul(value).is_none()); + // but slash * unit won't. + assert!(slash.checked_mul(unit).is_some()); + ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)]; + //--------------------------------------note value^^^ + ledger.active = unit; + ledger.total = unit * 4 + value; + // When + assert_eq!(ledger.slash(slash, 0, 0), slash - 5); + // Then + // The amount slashed out of `unit` + let affected_balance = value + unit * 4; + let ratio = + Perquintill::from_rational_with_rounding(slash, affected_balance, Rounding::Up).unwrap(); + // `unit` after the slash is applied + let unit_slashed = { + let unit_slash = ratio.mul_ceil(unit); + unit - unit_slash + }; + let value_slashed = { + let value_slash = ratio.mul_ceil(value); + value - value_slash + }; + assert_eq!(ledger.active, unit_slashed); + assert_eq!(ledger.unlocking, vec![c(5, value_slashed)]); + assert_eq!(ledger.total, value_slashed); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 0)]) + ); +} + +#[test] +fn pre_bonding_era_cannot_be_claimed() { + // Verifies initial conditions of mock + ExtBuilder::default().nominate(false).build_and_execute(|| { + let history_depth = HistoryDepth::get(); + // jump to some era above history_depth + let mut current_era = history_depth + 10; + let last_reward_era = current_era - 1; + let start_reward_era = current_era - history_depth; + + // put some money in stash=3 and controller=4. + for i in 3..5 { + let _ = Balances::make_free_balance_be(&i, 2000); + } + + mock::start_active_era(current_era); + + // add a new candidate for being a validator. account 3 controlled by 4. + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 1500, RewardDestination::Controller)); + + let claimed_rewards: BoundedVec<_, _> = + (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); + assert_eq!( + Staking::ledger(&4).unwrap(), + StakingLedger { + stash: 3, + total: 1500, + active: 1500, + unlocking: Default::default(), + claimed_rewards, + } + ); + + // start next era + current_era = current_era + 1; + mock::start_active_era(current_era); + + // claiming reward for last era in which validator was active works + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 1)); + + // consumed weight for all payout_stakers dispatches that fail + let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); + // cannot claim rewards for an era before bonding occured as it is + // already marked as claimed. + assert_noop!( + Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 2), + Error::::AlreadyClaimed.with_weight(err_weight) + ); + + // decoding will fail now since Staking Ledger is in corrupt state + HistoryDepth::set(history_depth - 1); + assert_eq!(Staking::ledger(&4), None); + + // make sure stakers still cannot claim rewards that they are not meant to + assert_noop!( + Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 2), + Error::::NotController + ); + + // fix the corrupted state for post conditions check + HistoryDepth::set(history_depth); + }); +} + +#[test] +fn reducing_history_depth_abrupt() { + // Verifies initial conditions of mock + ExtBuilder::default().nominate(false).build_and_execute(|| { + let original_history_depth = HistoryDepth::get(); + let mut current_era = original_history_depth + 10; + let last_reward_era = current_era - 1; + let start_reward_era = current_era - original_history_depth; + + // put some money in (stash, controller)=(3,4),(5,6). + for i in 3..7 { + let _ = Balances::make_free_balance_be(&i, 2000); + } + + // start current era + mock::start_active_era(current_era); + + // add a new candidate for being a staker. account 3 controlled by 4. + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 1500, RewardDestination::Controller)); + + // all previous era before the bonding action should be marked as + // claimed. + let claimed_rewards: BoundedVec<_, _> = + (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); + assert_eq!( + Staking::ledger(&4).unwrap(), + StakingLedger { + stash: 3, + total: 1500, + active: 1500, + unlocking: Default::default(), + claimed_rewards, + } + ); + + // next era + current_era = current_era + 1; + mock::start_active_era(current_era); + + // claiming reward for last era in which validator was active works + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 1)); + + // next era + current_era = current_era + 1; + mock::start_active_era(current_era); + + // history_depth reduced without migration + let history_depth = original_history_depth - 1; + HistoryDepth::set(history_depth); + // claiming reward does not work anymore + assert_noop!( + Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 1), + Error::::NotController + ); + + // new stakers can still bond + assert_ok!(Staking::bond(RuntimeOrigin::signed(5), 6, 1200, RewardDestination::Controller)); + + // new staking ledgers created will be bounded by the current history depth + let last_reward_era = current_era - 1; + let start_reward_era = current_era - history_depth; + let claimed_rewards: BoundedVec<_, _> = + (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); + assert_eq!( + Staking::ledger(&6).unwrap(), + StakingLedger { + stash: 5, + total: 1200, + active: 1200, + unlocking: Default::default(), + claimed_rewards, + } + ); + + // fix the corrupted state for post conditions check + HistoryDepth::set(original_history_depth); + }); +} + +#[test] +fn reducing_max_unlocking_chunks_abrupt() { + // Concern is on validators only + // By Default 11, 10 are stash and ctrl and 21,20 + ExtBuilder::default().build_and_execute(|| { + // given a staker at era=10 and MaxUnlockChunks set to 2 + MaxUnlockingChunks::set(2); + start_active_era(10); + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 300, RewardDestination::Staked)); + assert!(matches!(Staking::ledger(4), Some(_))); + + // when staker unbonds + assert_ok!(Staking::unbond(RuntimeOrigin::signed(4), 20)); + + // then an unlocking chunk is added at `current_era + bonding_duration` + // => 10 + 3 = 13 + let expected_unlocking: BoundedVec, MaxUnlockingChunks> = + bounded_vec![UnlockChunk { value: 20 as Balance, era: 13 as EraIndex }]; + assert!(matches!(Staking::ledger(4), + Some(StakingLedger { + unlocking, + .. + }) if unlocking==expected_unlocking)); + + // when staker unbonds at next era + start_active_era(11); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(4), 50)); + // then another unlock chunk is added + let expected_unlocking: BoundedVec, MaxUnlockingChunks> = + bounded_vec![UnlockChunk { value: 20, era: 13 }, UnlockChunk { value: 50, era: 14 }]; + assert!(matches!(Staking::ledger(4), + Some(StakingLedger { + unlocking, + .. + }) if unlocking==expected_unlocking)); + + // when staker unbonds further + start_active_era(12); + // then further unbonding not possible + assert_noop!(Staking::unbond(RuntimeOrigin::signed(4), 20), Error::::NoMoreChunks); + + // when max unlocking chunks is reduced abruptly to a low value + MaxUnlockingChunks::set(1); + // then unbond, rebond ops are blocked with ledger in corrupt state + assert_noop!(Staking::unbond(RuntimeOrigin::signed(4), 20), Error::::NotController); + assert_noop!(Staking::rebond(RuntimeOrigin::signed(4), 100), Error::::NotController); + + // reset the ledger corruption + MaxUnlockingChunks::set(2); + }) +} + +#[test] +fn cannot_set_unsupported_validator_count() { + ExtBuilder::default().build_and_execute(|| { + MaxWinners::set(50); + // set validator count works + assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 30)); + assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 50)); + // setting validator count above 100 does not work + assert_noop!( + Staking::set_validator_count(RuntimeOrigin::root(), 51), + Error::::TooManyValidators, + ); + }) +} + +#[test] +fn increase_validator_count_errors() { + ExtBuilder::default().build_and_execute(|| { + MaxWinners::set(50); + assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 40)); + + // increase works + assert_ok!(Staking::increase_validator_count(RuntimeOrigin::root(), 6)); + assert_eq!(ValidatorCount::::get(), 46); + + // errors + assert_noop!( + Staking::increase_validator_count(RuntimeOrigin::root(), 5), + Error::::TooManyValidators, + ); + }) +} + +#[test] +fn scale_validator_count_errors() { + ExtBuilder::default().build_and_execute(|| { + MaxWinners::set(50); + assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 20)); + + // scale value works + assert_ok!(Staking::scale_validator_count( + RuntimeOrigin::root(), + Percent::from_percent(200) + )); + assert_eq!(ValidatorCount::::get(), 40); + + // errors + assert_noop!( + Staking::scale_validator_count(RuntimeOrigin::root(), Percent::from_percent(126)), + Error::::TooManyValidators, + ); }) } diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index 0bcf179e29339..21fc3d6f077bc 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,24 +18,27 @@ //! Autogenerated weights for pallet_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-18, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-12-12, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark -// --chain=dev +// pallet // --steps=50 // --repeat=20 -// --pallet=pallet_staking // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_staking +// --chain=dev +// --header=./HEADER-APACHE2 // --output=./frame/staking/src/weights.rs // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -66,13 +69,14 @@ pub trait WeightInfo { fn payout_stakers_dead_controller(n: u32, ) -> Weight; fn payout_stakers_alive_staked(n: u32, ) -> Weight; fn rebond(l: u32, ) -> Weight; - fn set_history_depth(e: u32, ) -> Weight; fn reap_stash(s: u32, ) -> Weight; fn new_era(v: u32, n: u32, ) -> Weight; - fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight; + fn get_npos_voters(v: u32, n: u32, ) -> Weight; fn get_npos_targets(v: u32, ) -> Weight; - fn set_staking_limits() -> Weight; + fn set_staking_configs_all_set() -> Weight; + fn set_staking_configs_all_remove() -> Weight; fn chill_other() -> Weight; + fn force_apply_min_commission() -> Weight; } /// Weights for pallet_staking using the Substrate node and recommended hardware. @@ -81,82 +85,101 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - (73_523_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 56_034 nanoseconds. + Weight::from_ref_time(56_646_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Staking Bonded (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn bond_extra() -> Weight { - (58_129_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 94_354 nanoseconds. + Weight::from_ref_time(95_318_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(7)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking Nominators (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: Staking Bonded (r:1 w:0) + // Storage: VoterList ListBags (r:2 w:2) fn unbond() -> Weight { - (61_542_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 99_960 nanoseconds. + Weight::from_ref_time(101_022_000) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(8)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - (53_160_000 as Weight) - // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 45_819 nanoseconds. + Weight::from_ref_time(48_073_614) + // Standard Error: 1_410 + .saturating_add(Weight::from_ref_time(62_881).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking Bonded (r:1 w:1) // Storage: Staking SlashingSpans (r:1 w:0) // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) - // Storage: Staking SpanSlash (r:0 w:2) - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - (85_826_000 as Weight) - // Standard Error: 2_000 - .saturating_add((2_453_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(8 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + /// The range of component `s` is `[0, 100]`. + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + // Minimum execution time: 86_035 nanoseconds. + Weight::from_ref_time(89_561_735) + .saturating_add(T::DbWeight::get().reads(13)) + .saturating_add(T::DbWeight::get().writes(11)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinValidatorBond (r:1 w:0) + // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) // Storage: Staking MaxValidatorsCount (r:1 w:0) // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: VoterList ListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - (34_936_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 68_748 nanoseconds. + Weight::from_ref_time(69_285_000) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(5)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) + /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - (23_493_000 as Weight) - // Standard Error: 17_000 - .saturating_add((16_632_000 as Weight).saturating_mul(k as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(k as Weight))) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) + // Minimum execution time: 41_641 nanoseconds. + Weight::from_ref_time(48_919_231) + // Standard Error: 11_548 + .saturating_add(Weight::from_ref_time(6_901_201).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinNominatorBond (r:1 w:0) @@ -164,246 +187,302 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:1 w:0) // Storage: Staking Validators (r:2 w:0) // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) + /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - (41_733_000 as Weight) - // Standard Error: 11_000 - .saturating_add((5_840_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 75_097 nanoseconds. + Weight::from_ref_time(74_052_497) + // Standard Error: 6_784 + .saturating_add(Weight::from_ref_time(2_842_146).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - (17_901_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) + // Minimum execution time: 67_307 nanoseconds. + Weight::from_ref_time(67_838_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - (13_760_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 18_831 nanoseconds. + Weight::from_ref_time(19_047_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - (28_388_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 27_534 nanoseconds. + Weight::from_ref_time(27_806_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - (2_537_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_211 nanoseconds. + Weight::from_ref_time(5_372_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - (2_749_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_382 nanoseconds. + Weight::from_ref_time(5_654_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - (2_834_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_618 nanoseconds. + Weight::from_ref_time(5_714_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - (2_800_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_589 nanoseconds. + Weight::from_ref_time(5_776_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Invulnerables (r:0 w:1) + /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - (3_429_000 as Weight) - // Standard Error: 0 - .saturating_add((56_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_541 nanoseconds. + Weight::from_ref_time(6_479_253) + // Standard Error: 49 + .saturating_add(Weight::from_ref_time(10_125).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking SlashingSpans (r:1 w:0) // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: Staking Ledger (r:0 w:1) // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:2) + /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - (61_799_000 as Weight) - // Standard Error: 2_000 - .saturating_add((2_451_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + // Minimum execution time: 81_041 nanoseconds. + Weight::from_ref_time(88_526_481) + // Standard Error: 11_494 + .saturating_add(Weight::from_ref_time(1_095_933).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: Staking UnappliedSlashes (r:1 w:1) + /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - (3_383_988_000 as Weight) - // Standard Error: 223_000 - .saturating_add((19_981_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 92_308 nanoseconds. + Weight::from_ref_time(900_351_007) + // Standard Error: 59_145 + .saturating_add(Weight::from_ref_time(4_944_988).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Staking ErasValidatorReward (r:1 w:0) - // Storage: Staking Bonded (r:2 w:0) + // Storage: Staking Bonded (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking ErasStakersClipped (r:1 w:0) // Storage: Staking ErasRewardPoints (r:1 w:0) // Storage: Staking ErasValidatorPrefs (r:1 w:0) - // Storage: Staking Payee (r:2 w:0) - // Storage: System Account (r:2 w:2) + // Storage: Staking Payee (r:1 w:0) + // Storage: System Account (r:1 w:1) + /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - (124_714_000 as Weight) - // Standard Error: 23_000 - .saturating_add((47_575_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(10 as Weight)) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) + // Minimum execution time: 131_855 nanoseconds. + Weight::from_ref_time(197_412_779) + // Standard Error: 21_283 + .saturating_add(Weight::from_ref_time(22_093_758).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Staking ErasValidatorReward (r:1 w:0) - // Storage: Staking Bonded (r:2 w:0) - // Storage: Staking Ledger (r:2 w:2) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) // Storage: Staking ErasStakersClipped (r:1 w:0) // Storage: Staking ErasRewardPoints (r:1 w:0) // Storage: Staking ErasValidatorPrefs (r:1 w:0) - // Storage: Staking Payee (r:2 w:0) - // Storage: System Account (r:2 w:2) - // Storage: Balances Locks (r:2 w:2) + // Storage: Staking Payee (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - (160_203_000 as Weight) - // Standard Error: 24_000 - .saturating_add((61_321_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(11 as Weight)) - .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(n as Weight))) + // Minimum execution time: 163_118 nanoseconds. + Weight::from_ref_time(229_356_697) + // Standard Error: 30_740 + .saturating_add(Weight::from_ref_time(31_575_360).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) } // Storage: Staking Ledger (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: Staking Bonded (r:1 w:0) + // Storage: VoterList ListBags (r:2 w:2) + /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - (49_593_000 as Weight) - // Standard Error: 3_000 - .saturating_add((78_000 as Weight).saturating_mul(l as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking HistoryDepth (r:1 w:1) - // Storage: Staking ErasStakersClipped (r:0 w:2) - // Storage: Staking ErasValidatorPrefs (r:0 w:2) - // Storage: Staking ErasValidatorReward (r:0 w:1) - // Storage: Staking ErasRewardPoints (r:0 w:1) - // Storage: Staking ErasStakers (r:0 w:2) - // Storage: Staking ErasTotalStake (r:0 w:1) - // Storage: Staking ErasStartSessionIndex (r:0 w:1) - fn set_history_depth(e: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 71_000 - .saturating_add((35_237_000 as Weight).saturating_mul(e as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) - .saturating_add(T::DbWeight::get().writes((7 as Weight).saturating_mul(e as Weight))) + // Minimum execution time: 94_048 nanoseconds. + Weight::from_ref_time(95_784_236) + // Standard Error: 2_313 + .saturating_add(Weight::from_ref_time(52_798).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(8)) } // Storage: System Account (r:1 w:1) // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) // Storage: Staking SlashingSpans (r:1 w:1) - // Storage: Staking Validators (r:1 w:1) - // Storage: Staking CounterForValidators (r:1 w:1) - // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Ledger (r:0 w:1) // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:1) + /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - (72_484_000 as Weight) - // Standard Error: 2_000 - .saturating_add((2_452_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(8 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) - } - // Storage: Staking CounterForNominators (r:1 w:0) - // Storage: Staking CounterForValidators (r:1 w:0) + // Minimum execution time: 93_342 nanoseconds. + Weight::from_ref_time(95_756_184) + // Standard Error: 2_067 + .saturating_add(Weight::from_ref_time(1_090_785).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + } + // Storage: VoterList CounterForListNodes (r:1 w:0) + // Storage: VoterList ListBags (r:200 w:0) + // Storage: VoterList ListNodes (r:101 w:0) + // Storage: Staking Nominators (r:101 w:0) // Storage: Staking Validators (r:2 w:0) // Storage: Staking Bonded (r:101 w:0) // Storage: Staking Ledger (r:101 w:0) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Nominators (r:101 w:0) + // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking ValidatorCount (r:1 w:0) // Storage: Staking MinimumValidatorCount (r:1 w:0) // Storage: Staking CurrentEra (r:1 w:1) - // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Staking ErasStakersClipped (r:0 w:1) // Storage: Staking ErasValidatorPrefs (r:0 w:1) // Storage: Staking ErasStakers (r:0 w:1) // Storage: Staking ErasTotalStake (r:0 w:1) // Storage: Staking ErasStartSessionIndex (r:0 w:1) + /// The range of component `v` is `[1, 10]`. + /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 856_000 - .saturating_add((305_057_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 43_000 - .saturating_add((47_890_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(9 as Weight)) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) - } - // Storage: Staking Validators (r:501 w:0) + // Minimum execution time: 506_874 nanoseconds. + Weight::from_ref_time(507_798_000) + // Standard Error: 1_802_261 + .saturating_add(Weight::from_ref_time(59_874_736).saturating_mul(v.into())) + // Standard Error: 179_585 + .saturating_add(Weight::from_ref_time(13_668_574).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(206)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(v.into()))) + } + // Storage: VoterList CounterForListNodes (r:1 w:0) + // Storage: VoterList ListBags (r:200 w:0) + // Storage: VoterList ListNodes (r:1500 w:0) + // Storage: Staking Nominators (r:1500 w:0) + // Storage: Staking Validators (r:500 w:0) // Storage: Staking Bonded (r:1500 w:0) // Storage: Staking Ledger (r:1500 w:0) - // Storage: Staking SlashingSpans (r:21 w:0) - // Storage: Staking Nominators (r:1001 w:0) - fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 98_000 - .saturating_add((25_610_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 98_000 - .saturating_add((28_064_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 3_346_000 - .saturating_add((18_123_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) + /// The range of component `v` is `[500, 1000]`. + /// The range of component `n` is `[500, 1000]`. + fn get_npos_voters(v: u32, n: u32, ) -> Weight { + // Minimum execution time: 24_634_585 nanoseconds. + Weight::from_ref_time(24_718_377_000) + // Standard Error: 324_839 + .saturating_add(Weight::from_ref_time(3_654_508).saturating_mul(v.into())) + // Standard Error: 324_839 + .saturating_add(Weight::from_ref_time(2_927_535).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(201)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) } + // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:501 w:0) + /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - (30_422_000 as Weight) - // Standard Error: 33_000 - .saturating_add((11_252_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) + // Minimum execution time: 4_805_490 nanoseconds. + Weight::from_ref_time(118_475_494) + // Standard Error: 26_332 + .saturating_add(Weight::from_ref_time(9_635_188).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) + } + // Storage: Staking MinCommission (r:0 w:1) + // Storage: Staking MinValidatorBond (r:0 w:1) + // Storage: Staking MaxValidatorsCount (r:0 w:1) + // Storage: Staking ChillThreshold (r:0 w:1) + // Storage: Staking MaxNominatorsCount (r:0 w:1) + // Storage: Staking MinNominatorBond (r:0 w:1) + fn set_staking_configs_all_set() -> Weight { + // Minimum execution time: 10_816 nanoseconds. + Weight::from_ref_time(11_242_000) + .saturating_add(T::DbWeight::get().writes(6)) } + // Storage: Staking MinCommission (r:0 w:1) // Storage: Staking MinValidatorBond (r:0 w:1) // Storage: Staking MaxValidatorsCount (r:0 w:1) // Storage: Staking ChillThreshold (r:0 w:1) // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) - fn set_staking_limits() -> Weight { - (6_486_000 as Weight) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + fn set_staking_configs_all_remove() -> Weight { + // Minimum execution time: 9_581 nanoseconds. + Weight::from_ref_time(10_383_000) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) // Storage: Staking ChillThreshold (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) - // Storage: Staking Validators (r:1 w:1) - // Storage: Staking MaxValidatorsCount (r:1 w:0) - // Storage: Staking CounterForValidators (r:1 w:1) - // Storage: Staking MinValidatorBond (r:1 w:0) + // Storage: Staking MaxNominatorsCount (r:1 w:0) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - (58_222_000 as Weight) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 83_669 nanoseconds. + Weight::from_ref_time(84_772_000) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(6)) + } + // Storage: Staking MinCommission (r:1 w:0) + // Storage: Staking Validators (r:1 w:1) + fn force_apply_min_commission() -> Weight { + // Minimum execution time: 20_553 nanoseconds. + Weight::from_ref_time(20_933_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } } @@ -412,82 +491,101 @@ impl WeightInfo for () { // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - (73_523_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 56_034 nanoseconds. + Weight::from_ref_time(56_646_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Staking Bonded (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn bond_extra() -> Weight { - (58_129_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 94_354 nanoseconds. + Weight::from_ref_time(95_318_000) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(7)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking Nominators (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: Staking Bonded (r:1 w:0) + // Storage: VoterList ListBags (r:2 w:2) fn unbond() -> Weight { - (61_542_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 99_960 nanoseconds. + Weight::from_ref_time(101_022_000) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().writes(8)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - (53_160_000 as Weight) - // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 45_819 nanoseconds. + Weight::from_ref_time(48_073_614) + // Standard Error: 1_410 + .saturating_add(Weight::from_ref_time(62_881).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking Bonded (r:1 w:1) // Storage: Staking SlashingSpans (r:1 w:0) // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) - // Storage: Staking SpanSlash (r:0 w:2) - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - (85_826_000 as Weight) - // Standard Error: 2_000 - .saturating_add((2_453_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(8 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + /// The range of component `s` is `[0, 100]`. + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + // Minimum execution time: 86_035 nanoseconds. + Weight::from_ref_time(89_561_735) + .saturating_add(RocksDbWeight::get().reads(13)) + .saturating_add(RocksDbWeight::get().writes(11)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinValidatorBond (r:1 w:0) + // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) // Storage: Staking MaxValidatorsCount (r:1 w:0) // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: VoterList ListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - (34_936_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 68_748 nanoseconds. + Weight::from_ref_time(69_285_000) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(5)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) + /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - (23_493_000 as Weight) - // Standard Error: 17_000 - .saturating_add((16_632_000 as Weight).saturating_mul(k as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(k as Weight))) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) + // Minimum execution time: 41_641 nanoseconds. + Weight::from_ref_time(48_919_231) + // Standard Error: 11_548 + .saturating_add(Weight::from_ref_time(6_901_201).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinNominatorBond (r:1 w:0) @@ -495,245 +593,301 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:1 w:0) // Storage: Staking Validators (r:2 w:0) // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) + /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - (41_733_000 as Weight) - // Standard Error: 11_000 - .saturating_add((5_840_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 75_097 nanoseconds. + Weight::from_ref_time(74_052_497) + // Standard Error: 6_784 + .saturating_add(Weight::from_ref_time(2_842_146).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - (17_901_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + // Minimum execution time: 67_307 nanoseconds. + Weight::from_ref_time(67_838_000) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - (13_760_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 18_831 nanoseconds. + Weight::from_ref_time(19_047_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - (28_388_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 27_534 nanoseconds. + Weight::from_ref_time(27_806_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - (2_537_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_211 nanoseconds. + Weight::from_ref_time(5_372_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - (2_749_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_382 nanoseconds. + Weight::from_ref_time(5_654_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - (2_834_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_618 nanoseconds. + Weight::from_ref_time(5_714_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - (2_800_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_589 nanoseconds. + Weight::from_ref_time(5_776_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Invulnerables (r:0 w:1) + /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - (3_429_000 as Weight) - // Standard Error: 0 - .saturating_add((56_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 5_541 nanoseconds. + Weight::from_ref_time(6_479_253) + // Standard Error: 49 + .saturating_add(Weight::from_ref_time(10_125).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking SlashingSpans (r:1 w:0) // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: Staking Ledger (r:0 w:1) // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:2) + /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - (61_799_000 as Weight) - // Standard Error: 2_000 - .saturating_add((2_451_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + // Minimum execution time: 81_041 nanoseconds. + Weight::from_ref_time(88_526_481) + // Standard Error: 11_494 + .saturating_add(Weight::from_ref_time(1_095_933).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(12)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: Staking UnappliedSlashes (r:1 w:1) + /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - (3_383_988_000 as Weight) - // Standard Error: 223_000 - .saturating_add((19_981_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 92_308 nanoseconds. + Weight::from_ref_time(900_351_007) + // Standard Error: 59_145 + .saturating_add(Weight::from_ref_time(4_944_988).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Staking ErasValidatorReward (r:1 w:0) - // Storage: Staking Bonded (r:2 w:0) + // Storage: Staking Bonded (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking ErasStakersClipped (r:1 w:0) // Storage: Staking ErasRewardPoints (r:1 w:0) // Storage: Staking ErasValidatorPrefs (r:1 w:0) - // Storage: Staking Payee (r:2 w:0) - // Storage: System Account (r:2 w:2) + // Storage: Staking Payee (r:1 w:0) + // Storage: System Account (r:1 w:1) + /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - (124_714_000 as Weight) - // Standard Error: 23_000 - .saturating_add((47_575_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(10 as Weight)) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) + // Minimum execution time: 131_855 nanoseconds. + Weight::from_ref_time(197_412_779) + // Standard Error: 21_283 + .saturating_add(Weight::from_ref_time(22_093_758).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(2)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) } // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Staking ErasValidatorReward (r:1 w:0) - // Storage: Staking Bonded (r:2 w:0) - // Storage: Staking Ledger (r:2 w:2) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) // Storage: Staking ErasStakersClipped (r:1 w:0) // Storage: Staking ErasRewardPoints (r:1 w:0) // Storage: Staking ErasValidatorPrefs (r:1 w:0) - // Storage: Staking Payee (r:2 w:0) - // Storage: System Account (r:2 w:2) - // Storage: Balances Locks (r:2 w:2) + // Storage: Staking Payee (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - (160_203_000 as Weight) - // Standard Error: 24_000 - .saturating_add((61_321_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(11 as Weight)) - .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(n as Weight))) + // Minimum execution time: 163_118 nanoseconds. + Weight::from_ref_time(229_356_697) + // Standard Error: 30_740 + .saturating_add(Weight::from_ref_time(31_575_360).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(10)) + .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) } // Storage: Staking Ledger (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: Staking Bonded (r:1 w:0) + // Storage: VoterList ListBags (r:2 w:2) + /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - (49_593_000 as Weight) - // Standard Error: 3_000 - .saturating_add((78_000 as Weight).saturating_mul(l as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - } - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking HistoryDepth (r:1 w:1) - // Storage: Staking ErasStakersClipped (r:0 w:2) - // Storage: Staking ErasValidatorPrefs (r:0 w:2) - // Storage: Staking ErasValidatorReward (r:0 w:1) - // Storage: Staking ErasRewardPoints (r:0 w:1) - // Storage: Staking ErasStakers (r:0 w:2) - // Storage: Staking ErasTotalStake (r:0 w:1) - // Storage: Staking ErasStartSessionIndex (r:0 w:1) - fn set_history_depth(e: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 71_000 - .saturating_add((35_237_000 as Weight).saturating_mul(e as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes((7 as Weight).saturating_mul(e as Weight))) + // Minimum execution time: 94_048 nanoseconds. + Weight::from_ref_time(95_784_236) + // Standard Error: 2_313 + .saturating_add(Weight::from_ref_time(52_798).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(9)) + .saturating_add(RocksDbWeight::get().writes(8)) } // Storage: System Account (r:1 w:1) // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) // Storage: Staking SlashingSpans (r:1 w:1) - // Storage: Staking Validators (r:1 w:1) - // Storage: Staking CounterForValidators (r:1 w:1) - // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Ledger (r:0 w:1) // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:1) + /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - (72_484_000 as Weight) - // Standard Error: 2_000 - .saturating_add((2_452_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(8 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) - } - // Storage: Staking CounterForNominators (r:1 w:0) - // Storage: Staking CounterForValidators (r:1 w:0) + // Minimum execution time: 93_342 nanoseconds. + Weight::from_ref_time(95_756_184) + // Standard Error: 2_067 + .saturating_add(Weight::from_ref_time(1_090_785).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().writes(12)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) + } + // Storage: VoterList CounterForListNodes (r:1 w:0) + // Storage: VoterList ListBags (r:200 w:0) + // Storage: VoterList ListNodes (r:101 w:0) + // Storage: Staking Nominators (r:101 w:0) // Storage: Staking Validators (r:2 w:0) // Storage: Staking Bonded (r:101 w:0) // Storage: Staking Ledger (r:101 w:0) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Nominators (r:101 w:0) + // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking ValidatorCount (r:1 w:0) // Storage: Staking MinimumValidatorCount (r:1 w:0) // Storage: Staking CurrentEra (r:1 w:1) - // Storage: Staking HistoryDepth (r:1 w:0) // Storage: Staking ErasStakersClipped (r:0 w:1) // Storage: Staking ErasValidatorPrefs (r:0 w:1) // Storage: Staking ErasStakers (r:0 w:1) // Storage: Staking ErasTotalStake (r:0 w:1) // Storage: Staking ErasStartSessionIndex (r:0 w:1) + /// The range of component `v` is `[1, 10]`. + /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 856_000 - .saturating_add((305_057_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 43_000 - .saturating_add((47_890_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(9 as Weight)) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) - } - // Storage: Staking Validators (r:501 w:0) + // Minimum execution time: 506_874 nanoseconds. + Weight::from_ref_time(507_798_000) + // Standard Error: 1_802_261 + .saturating_add(Weight::from_ref_time(59_874_736).saturating_mul(v.into())) + // Standard Error: 179_585 + .saturating_add(Weight::from_ref_time(13_668_574).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(206)) + .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(v.into()))) + } + // Storage: VoterList CounterForListNodes (r:1 w:0) + // Storage: VoterList ListBags (r:200 w:0) + // Storage: VoterList ListNodes (r:1500 w:0) + // Storage: Staking Nominators (r:1500 w:0) + // Storage: Staking Validators (r:500 w:0) // Storage: Staking Bonded (r:1500 w:0) // Storage: Staking Ledger (r:1500 w:0) - // Storage: Staking SlashingSpans (r:21 w:0) - // Storage: Staking Nominators (r:1001 w:0) - fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 98_000 - .saturating_add((25_610_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 98_000 - .saturating_add((28_064_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 3_346_000 - .saturating_add((18_123_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) + /// The range of component `v` is `[500, 1000]`. + /// The range of component `n` is `[500, 1000]`. + fn get_npos_voters(v: u32, n: u32, ) -> Weight { + // Minimum execution time: 24_634_585 nanoseconds. + Weight::from_ref_time(24_718_377_000) + // Standard Error: 324_839 + .saturating_add(Weight::from_ref_time(3_654_508).saturating_mul(v.into())) + // Standard Error: 324_839 + .saturating_add(Weight::from_ref_time(2_927_535).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(201)) + .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) } + // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:501 w:0) + /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - (30_422_000 as Weight) - // Standard Error: 33_000 - .saturating_add((11_252_000 as Weight).saturating_mul(v as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) + // Minimum execution time: 4_805_490 nanoseconds. + Weight::from_ref_time(118_475_494) + // Standard Error: 26_332 + .saturating_add(Weight::from_ref_time(9_635_188).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) + } + // Storage: Staking MinCommission (r:0 w:1) + // Storage: Staking MinValidatorBond (r:0 w:1) + // Storage: Staking MaxValidatorsCount (r:0 w:1) + // Storage: Staking ChillThreshold (r:0 w:1) + // Storage: Staking MaxNominatorsCount (r:0 w:1) + // Storage: Staking MinNominatorBond (r:0 w:1) + fn set_staking_configs_all_set() -> Weight { + // Minimum execution time: 10_816 nanoseconds. + Weight::from_ref_time(11_242_000) + .saturating_add(RocksDbWeight::get().writes(6)) } + // Storage: Staking MinCommission (r:0 w:1) // Storage: Staking MinValidatorBond (r:0 w:1) // Storage: Staking MaxValidatorsCount (r:0 w:1) // Storage: Staking ChillThreshold (r:0 w:1) // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) - fn set_staking_limits() -> Weight { - (6_486_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + fn set_staking_configs_all_remove() -> Weight { + // Minimum execution time: 9_581 nanoseconds. + Weight::from_ref_time(10_383_000) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) // Storage: Staking ChillThreshold (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) - // Storage: Staking Validators (r:1 w:1) - // Storage: Staking MaxValidatorsCount (r:1 w:0) - // Storage: Staking CounterForValidators (r:1 w:1) - // Storage: Staking MinValidatorBond (r:1 w:0) + // Storage: Staking MaxNominatorsCount (r:1 w:0) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: VoterList ListNodes (r:2 w:2) + // Storage: VoterList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - (58_222_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 83_669 nanoseconds. + Weight::from_ref_time(84_772_000) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(6)) + } + // Storage: Staking MinCommission (r:1 w:0) + // Storage: Staking Validators (r:1 w:1) + fn force_apply_min_commission() -> Weight { + // Minimum execution time: 20_553 nanoseconds. + Weight::from_ref_time(20_933_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } } diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml new file mode 100644 index 0000000000000..90c8f426d0e10 --- /dev/null +++ b/frame/state-trie-migration/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "pallet-state-trie-migration" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet migration of trie" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.133", optional = true } +thousands = { version = "0.2.0", optional = true } +zstd = { version = "0.11.2", default-features = false, optional = true } +frame-benchmarking = { default-features = false, optional = true, path = "../benchmarking" } +frame-support = { default-features = false, path = "../support" } +frame-system = { default-features = false, path = "../system" } +remote-externalities = { optional = true, path = "../../utils/frame/remote-externalities", package = "frame-remote-externalities" } +sp-core = { default-features = false, path = "../../primitives/core" } +sp-io = { default-features = false, path = "../../primitives/io" } +sp-runtime = { default-features = false, path = "../../primitives/runtime" } +sp-std = { default-features = false, path = "../../primitives/std" } +substrate-state-trie-migration-rpc = { optional = true, path = "../../utils/frame/rpc/state-trie-migration-rpc" } + +[dev-dependencies] +parking_lot = "0.12.1" +tokio = { version = "1.22.0", features = ["macros"] } +pallet-balances = { path = "../balances" } +sp-tracing = { path = "../../primitives/tracing" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = ["frame-benchmarking"] +try-runtime = ["frame-support/try-runtime"] +remote-test = [ "remote-externalities", "serde", "std", "substrate-state-trie-migration-rpc", "thousands", "zstd" ] diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs new file mode 100644 index 0000000000000..823ea08a0b573 --- /dev/null +++ b/frame/state-trie-migration/src/lib.rs @@ -0,0 +1,1778 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! # Pallet State Trie Migration +//! +//! Reads and writes all keys and values in the entire state in a systematic way. This is useful for +//! upgrading a chain to [`sp-core::StateVersion::V1`], where all keys need to be touched. +//! +//! ## Migration Types +//! +//! This pallet provides 2 ways to do this, each of which is suited for a particular use-case, and +//! can be enabled independently. +//! +//! ### Auto migration +//! +//! This system will try and migrate all keys by continuously using `on_initialize`. It is only +//! sensible for a relay chain or a solo chain, where going slightly over weight is not a problem. +//! It can be configured so that the migration takes at most `n` items and tries to not go over `x` +//! bytes, but the latter is not guaranteed. +//! +//! For example, if a chain contains keys of 1 byte size, the `on_initialize` could read up to `x - +//! 1` bytes from `n` different keys, while the next key is suddenly `:code:`, and there is no way +//! to bail out of this. +//! +//! ### Signed migration +//! +//! As a backup, the migration process can be set in motion via signed transactions that basically +//! say in advance how many items and how many bytes they will consume, and pay for it as well. This +//! can be a good safe alternative, if the former system is not desirable. +//! +//! The (minor) caveat of this approach is that we cannot know in advance how many bytes reading a +//! certain number of keys will incur. To overcome this, the runtime needs to configure this pallet +//! with a `SignedDepositPerItem`. This is the per-item deposit that the origin of the signed +//! migration transactions need to have in their account (on top of the normal fee) and if the size +//! witness data that they claim is incorrect, this deposit is slashed. +//! +//! --- +//! +//! Initially, this pallet does not contain any auto migration. They must be manually enabled by the +//! `ControlOrigin`. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; +pub mod weights; + +const LOG_TARGET: &str = "runtime::state-trie-migration"; + +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 🤖 ", $patter), frame_system::Pallet::::block_number() $(, $values)* + ) + }; +} + +#[frame_support::pallet] +pub mod pallet { + + pub use crate::weights::WeightInfo; + + use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo}, + ensure, + pallet_prelude::*, + traits::{Currency, Get}, + }; + use frame_system::{self, pallet_prelude::*}; + use sp_core::{ + hexdisplay::HexDisplay, storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX, + }; + use sp_runtime::{ + self, + traits::{Saturating, Zero}, + }; + use sp_std::{ops::Deref, prelude::*}; + + pub(crate) type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + + /// The progress of either the top or child keys. + #[derive( + CloneNoBound, + Encode, + Decode, + scale_info::TypeInfo, + PartialEqNoBound, + EqNoBound, + MaxEncodedLen, + )] + #[scale_info(skip_type_params(MaxKeyLen))] + #[codec(mel_bound())] + pub enum Progress> { + /// Yet to begin. + ToStart, + /// Ongoing, with the last key given. + LastKey(BoundedVec), + /// All done. + Complete, + } + + /// Convenience type for easier usage of [`Progress`]. + pub type ProgressOf = Progress<::MaxKeyLen>; + + /// A migration task stored in state. + /// + /// It tracks the last top and child keys read. + #[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, MaxEncodedLen)] + #[codec(mel_bound(T: Config))] + #[scale_info(skip_type_params(T))] + pub struct MigrationTask { + /// The current top trie migration progress. + pub(crate) progress_top: ProgressOf, + /// The current child trie migration progress. + /// + /// If `ToStart`, no further top keys are processed until the child key migration is + /// `Complete`. + pub(crate) progress_child: ProgressOf, + + /// Dynamic counter for the number of items that we have processed in this execution from + /// the top trie. + /// + /// It is not written to storage. + #[codec(skip)] + pub(crate) dyn_top_items: u32, + /// Dynamic counter for the number of items that we have processed in this execution from + /// any child trie. + /// + /// It is not written to storage. + #[codec(skip)] + pub(crate) dyn_child_items: u32, + + /// Dynamic counter for for the byte size of items that we have processed in this + /// execution. + /// + /// It is not written to storage. + #[codec(skip)] + pub(crate) dyn_size: u32, + + /// The total size of the migration, over all executions. + /// + /// This only kept around for bookkeeping and debugging. + pub(crate) size: u32, + /// The total count of top keys in the migration, over all executions. + /// + /// This only kept around for bookkeeping and debugging. + pub(crate) top_items: u32, + /// The total count of child keys in the migration, over all executions. + /// + /// This only kept around for bookkeeping and debugging. + pub(crate) child_items: u32, + + #[codec(skip)] + pub(crate) _ph: sp_std::marker::PhantomData, + } + + impl> sp_std::fmt::Debug for Progress { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + match self { + Progress::ToStart => f.write_str("To start"), + Progress::LastKey(key) => write!(f, "Last: {:?}", HexDisplay::from(key.deref())), + Progress::Complete => f.write_str("Complete"), + } + } + } + + impl sp_std::fmt::Debug for MigrationTask { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + f.debug_struct("MigrationTask") + .field("top", &self.progress_top) + .field("child", &self.progress_child) + .field("dyn_top_items", &self.dyn_top_items) + .field("dyn_child_items", &self.dyn_child_items) + .field("dyn_size", &self.dyn_size) + .field("size", &self.size) + .field("top_items", &self.top_items) + .field("child_items", &self.child_items) + .finish() + } + } + + impl Default for MigrationTask { + fn default() -> Self { + Self { + progress_top: Progress::ToStart, + progress_child: Progress::ToStart, + dyn_child_items: Default::default(), + dyn_top_items: Default::default(), + dyn_size: Default::default(), + _ph: Default::default(), + size: Default::default(), + top_items: Default::default(), + child_items: Default::default(), + } + } + } + + impl MigrationTask { + /// Return true if the task is finished. + pub(crate) fn finished(&self) -> bool { + matches!(self.progress_top, Progress::Complete) + } + + /// Check if there's any work left, or if we have exhausted the limits already. + fn exhausted(&self, limits: MigrationLimits) -> bool { + self.dyn_total_items() >= limits.item || self.dyn_size >= limits.size + } + + /// get the total number of keys affected by the current task. + pub(crate) fn dyn_total_items(&self) -> u32 { + self.dyn_child_items.saturating_add(self.dyn_top_items) + } + + /// Migrate keys until either of the given limits are exhausted, or if no more top keys + /// exist. + /// + /// Note that this can return after the **first** migration tick that causes exhaustion, + /// specifically in the case of the `size` constrain. The reason for this is that before + /// reading a key, we simply cannot know how many bytes it is. In other words, this should + /// not be used in any environment where resources are strictly bounded (e.g. a parachain), + /// but it is acceptable otherwise (relay chain, offchain workers). + pub fn migrate_until_exhaustion( + &mut self, + limits: MigrationLimits, + ) -> Result<(), Error> { + log!(debug, "running migrations on top of {:?} until {:?}", self, limits); + + if limits.item.is_zero() || limits.size.is_zero() { + // handle this minor edge case, else we would call `migrate_tick` at least once. + log!(warn, "limits are zero. stopping"); + return Ok(()) + } + + while !self.exhausted(limits) && !self.finished() { + if let Err(e) = self.migrate_tick() { + log!(error, "migrate_until_exhaustion failed: {:?}", e); + return Err(e) + } + } + + // accumulate dynamic data into the storage items. + self.size = self.size.saturating_add(self.dyn_size); + self.child_items = self.child_items.saturating_add(self.dyn_child_items); + self.top_items = self.top_items.saturating_add(self.dyn_top_items); + log!(debug, "finished with {:?}", self); + Ok(()) + } + + /// Migrate AT MOST ONE KEY. This can be either a top or a child key. + /// + /// This function is *the* core of this entire pallet. + fn migrate_tick(&mut self) -> Result<(), Error> { + match (&self.progress_top, &self.progress_child) { + (Progress::ToStart, _) => self.migrate_top(), + (Progress::LastKey(_), Progress::LastKey(_)) => { + // we're in the middle of doing work on a child tree. + self.migrate_child() + }, + (Progress::LastKey(top_key), Progress::ToStart) => { + // 3. this is the root of a child key, and we are finishing all child-keys (and + // should call `migrate_top`). + + // NOTE: this block is written intentionally to verbosely for easy of + // verification. + if !top_key.starts_with(DEFAULT_CHILD_STORAGE_KEY_PREFIX) { + // we continue the top key migrations. + // continue the top key migration + self.migrate_top() + } else { + // this is the root of a child key, and we start processing child keys (and + // should call `migrate_child`). + self.migrate_child() + } + }, + (Progress::LastKey(_), Progress::Complete) => { + // we're done with migrating a child-root. + self.migrate_top()?; + self.progress_child = Progress::ToStart; + Ok(()) + }, + (Progress::Complete, _) => { + // nada + Ok(()) + }, + } + } + + /// Migrate the current child key, setting it to its new value, if one exists. + /// + /// It updates the dynamic counters. + fn migrate_child(&mut self) -> Result<(), Error> { + use sp_io::default_child_storage as child_io; + let (maybe_current_child, child_root) = match (&self.progress_child, &self.progress_top) + { + (Progress::LastKey(last_child), Progress::LastKey(last_top)) => { + let child_root = Pallet::::transform_child_key_or_halt(last_top); + let maybe_current_child: Option> = + if let Some(next) = child_io::next_key(child_root, last_child) { + Some(next.try_into().map_err(|_| Error::::KeyTooLong)?) + } else { + None + }; + + (maybe_current_child, child_root) + }, + (Progress::ToStart, Progress::LastKey(last_top)) => { + let child_root = Pallet::::transform_child_key_or_halt(last_top); + // Start with the empty key as first key. + (Some(Default::default()), child_root) + }, + _ => { + // defensive: there must be an ongoing top migration. + frame_support::defensive!("cannot migrate child key."); + return Ok(()) + }, + }; + + if let Some(current_child) = maybe_current_child.as_ref() { + let added_size = if let Some(data) = child_io::get(child_root, current_child) { + child_io::set(child_root, current_child, &data); + data.len() as u32 + } else { + Zero::zero() + }; + self.dyn_size = self.dyn_size.saturating_add(added_size); + self.dyn_child_items.saturating_inc(); + } + + log!(trace, "migrated a child key, next_child_key: {:?}", maybe_current_child); + self.progress_child = match maybe_current_child { + Some(last_child) => Progress::LastKey(last_child), + None => Progress::Complete, + }; + Ok(()) + } + + /// Migrate the current top key, setting it to its new value, if one exists. + /// + /// It updates the dynamic counters. + fn migrate_top(&mut self) -> Result<(), Error> { + let maybe_current_top = match &self.progress_top { + Progress::LastKey(last_top) => { + let maybe_top: Option> = + if let Some(next) = sp_io::storage::next_key(last_top) { + Some(next.try_into().map_err(|_| Error::::KeyTooLong)?) + } else { + None + }; + maybe_top + }, + // Start with the empty key as first key. + Progress::ToStart => Some(Default::default()), + Progress::Complete => { + // defensive: there must be an ongoing top migration. + frame_support::defensive!("cannot migrate top key."); + return Ok(()) + }, + }; + + if let Some(current_top) = maybe_current_top.as_ref() { + let added_size = if let Some(data) = sp_io::storage::get(current_top) { + sp_io::storage::set(current_top, &data); + data.len() as u32 + } else { + Zero::zero() + }; + self.dyn_size = self.dyn_size.saturating_add(added_size); + self.dyn_top_items.saturating_inc(); + } + + log!(trace, "migrated a top key, next_top_key = {:?}", maybe_current_top); + self.progress_top = match maybe_current_top { + Some(last_top) => Progress::LastKey(last_top), + None => Progress::Complete, + }; + Ok(()) + } + } + + /// The limits of a migration. + #[derive( + Clone, + Copy, + Encode, + Decode, + scale_info::TypeInfo, + Default, + Debug, + PartialEq, + Eq, + MaxEncodedLen, + )] + pub struct MigrationLimits { + /// The byte size limit. + pub size: u32, + /// The number of keys limit. + pub item: u32, + } + + /// How a migration was computed. + #[derive(Clone, Copy, Encode, Decode, scale_info::TypeInfo, Debug, PartialEq, Eq)] + pub enum MigrationCompute { + /// A signed origin triggered the migration. + Signed, + /// An automatic task triggered the migration. + Auto, + } + + /// Inner events of this pallet. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Given number of `(top, child)` keys were migrated respectively, with the given + /// `compute`. + Migrated { top: u32, child: u32, compute: MigrationCompute }, + /// Some account got slashed by the given amount. + Slashed { who: T::AccountId, amount: BalanceOf }, + /// The auto migration task finished. + AutoMigrationFinished, + /// Migration got halted due to an error or miss-configuration. + Halted { error: Error }, + } + + /// The outer Pallet struct. + #[pallet::pallet] + #[pallet::generate_store(pub(crate) trait Store)] + pub struct Pallet(_); + + /// Configurations of this pallet. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Origin that can control the configurations of this pallet. + type ControlOrigin: frame_support::traits::EnsureOrigin; + + /// Filter on which origin that trigger the manual migrations. + type SignedFilter: EnsureOrigin; + + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The currency provider type. + type Currency: Currency; + + /// Maximal number of bytes that a key can have. + /// + /// FRAME itself does not limit the key length. + /// The concrete value must therefore depend on your storage usage. + /// A [`frame_support::storage::StorageNMap`] for example can have an arbitrary number of + /// keys which are then hashed and concatenated, resulting in arbitrarily long keys. + /// + /// Use the *state migration RPC* to retrieve the length of the longest key in your + /// storage: + /// + /// The migration will halt with a `Halted` event if this value is too small. + /// Since there is no real penalty from over-estimating, it is advised to use a large + /// value. The default is 512 byte. + /// + /// Some key lengths for reference: + /// - [`frame_support::storage::StorageValue`]: 32 byte + /// - [`frame_support::storage::StorageMap`]: 64 byte + /// - [`frame_support::storage::StorageDoubleMap`]: 96 byte + /// + /// For more info see + /// + + #[pallet::constant] + type MaxKeyLen: Get; + + /// The amount of deposit collected per item in advance, for signed migrations. + /// + /// This should reflect the average storage value size in the worse case. + type SignedDepositPerItem: Get>; + + /// The base value of [`Config::SignedDepositPerItem`]. + /// + /// Final deposit is `items * SignedDepositPerItem + SignedDepositBase`. + type SignedDepositBase: Get>; + + /// The weight information of this pallet. + type WeightInfo: WeightInfo; + } + + /// Migration progress. + /// + /// This stores the snapshot of the last migrated keys. It can be set into motion and move + /// forward by any of the means provided by this pallet. + #[pallet::storage] + #[pallet::getter(fn migration_process)] + pub type MigrationProcess = StorageValue<_, MigrationTask, ValueQuery>; + + /// The limits that are imposed on automatic migrations. + /// + /// If set to None, then no automatic migration happens. + #[pallet::storage] + #[pallet::getter(fn auto_limits)] + pub type AutoLimits = StorageValue<_, Option, ValueQuery>; + + /// The maximum limits that the signed migration could use. + /// + /// If not set, no signed submission is allowed. + #[pallet::storage] + #[pallet::getter(fn signed_migration_max_limits)] + pub type SignedMigrationMaxLimits = StorageValue<_, MigrationLimits, OptionQuery>; + + #[pallet::error] + #[derive(Clone, PartialEq)] + pub enum Error { + /// Max signed limits not respected. + MaxSignedLimits, + /// A key was longer than the configured maximum. + /// + /// This means that the migration halted at the current [`Progress`] and + /// can be resumed with a larger [`crate::Config::MaxKeyLen`] value. + /// Retrying with the same [`crate::Config::MaxKeyLen`] value will not work. + /// The value should only be increased to avoid a storage migration for the currently + /// stored [`crate::Progress::LastKey`]. + KeyTooLong, + /// submitter does not have enough funds. + NotEnoughFunds, + /// Bad witness data provided. + BadWitness, + /// Signed migration is not allowed because the maximum limit is not set yet. + SignedMigrationNotAllowed, + /// Bad child root provided. + BadChildRoot, + } + + #[pallet::call] + impl Pallet { + /// Control the automatic migration. + /// + /// The dispatch origin of this call must be [`Config::ControlOrigin`]. + #[pallet::call_index(0)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] + pub fn control_auto_migration( + origin: OriginFor, + maybe_config: Option, + ) -> DispatchResult { + T::ControlOrigin::ensure_origin(origin)?; + AutoLimits::::put(maybe_config); + Ok(()) + } + + /// Continue the migration for the given `limits`. + /// + /// The dispatch origin of this call can be any signed account. + /// + /// This transaction has NO MONETARY INCENTIVES. calling it will not reward anyone. Albeit, + /// Upon successful execution, the transaction fee is returned. + /// + /// The (potentially over-estimated) of the byte length of all the data read must be + /// provided for up-front fee-payment and weighing. In essence, the caller is guaranteeing + /// that executing the current `MigrationTask` with the given `limits` will not exceed + /// `real_size_upper` bytes of read data. + /// + /// The `witness_task` is merely a helper to prevent the caller from being slashed or + /// generally trigger a migration that they do not intend. This parameter is just a message + /// from caller, saying that they believed `witness_task` was the last state of the + /// migration, and they only wish for their transaction to do anything, if this assumption + /// holds. In case `witness_task` does not match, the transaction fails. + /// + /// Based on the documentation of [`MigrationTask::migrate_until_exhaustion`], the + /// recommended way of doing this is to pass a `limit` that only bounds `count`, as the + /// `size` limit can always be overwritten. + #[pallet::call_index(1)] + #[pallet::weight( + // the migration process + Pallet::::dynamic_weight(limits.item, * real_size_upper) + // rest of the operations, like deposit etc. + + T::WeightInfo::continue_migrate() + )] + pub fn continue_migrate( + origin: OriginFor, + limits: MigrationLimits, + real_size_upper: u32, + witness_task: MigrationTask, + ) -> DispatchResultWithPostInfo { + let who = T::SignedFilter::ensure_origin(origin)?; + + let max_limits = + Self::signed_migration_max_limits().ok_or(Error::::SignedMigrationNotAllowed)?; + ensure!( + limits.size <= max_limits.size && limits.item <= max_limits.item, + Error::::MaxSignedLimits, + ); + + // ensure they can pay more than the fee. + let deposit = T::SignedDepositPerItem::get().saturating_mul(limits.item.into()); + ensure!(T::Currency::can_slash(&who, deposit), Error::::NotEnoughFunds); + + let mut task = Self::migration_process(); + ensure!( + task == witness_task, + DispatchErrorWithPostInfo { + error: Error::::BadWitness.into(), + post_info: PostDispatchInfo { + actual_weight: Some(T::WeightInfo::continue_migrate_wrong_witness()), + pays_fee: Pays::Yes + } + } + ); + let migration = task.migrate_until_exhaustion(limits); + + // ensure that the migration witness data was correct. + if real_size_upper < task.dyn_size { + // let the imbalance burn. + let (_imbalance, _remainder) = T::Currency::slash(&who, deposit); + Self::deposit_event(Event::::Slashed { who, amount: deposit }); + debug_assert!(_remainder.is_zero()); + return Ok(().into()) + } + + Self::deposit_event(Event::::Migrated { + top: task.dyn_top_items, + child: task.dyn_child_items, + compute: MigrationCompute::Signed, + }); + + // refund and correct the weight. + let actual_weight = Some( + Pallet::::dynamic_weight(limits.item, task.dyn_size) + .saturating_add(T::WeightInfo::continue_migrate()), + ); + + MigrationProcess::::put(task); + let post_info = PostDispatchInfo { actual_weight, pays_fee: Pays::No }; + if let Err(error) = migration { + Self::halt(error); + } + Ok(post_info) + } + + /// Migrate the list of top keys by iterating each of them one by one. + /// + /// This does not affect the global migration process tracker ([`MigrationProcess`]), and + /// should only be used in case any keys are leftover due to a bug. + #[pallet::call_index(2)] + #[pallet::weight( + T::WeightInfo::migrate_custom_top_success() + .max(T::WeightInfo::migrate_custom_top_fail()) + .saturating_add( + Pallet::::dynamic_weight(keys.len() as u32, *witness_size) + ) + )] + pub fn migrate_custom_top( + origin: OriginFor, + keys: Vec>, + witness_size: u32, + ) -> DispatchResultWithPostInfo { + let who = T::SignedFilter::ensure_origin(origin)?; + + // ensure they can pay more than the fee. + let deposit = T::SignedDepositBase::get().saturating_add( + T::SignedDepositPerItem::get().saturating_mul((keys.len() as u32).into()), + ); + ensure!(T::Currency::can_slash(&who, deposit), "not enough funds"); + + let mut dyn_size = 0u32; + for key in &keys { + if let Some(data) = sp_io::storage::get(key) { + dyn_size = dyn_size.saturating_add(data.len() as u32); + sp_io::storage::set(key, &data); + } + } + + if dyn_size > witness_size { + let (_imbalance, _remainder) = T::Currency::slash(&who, deposit); + Self::deposit_event(Event::::Slashed { who, amount: deposit }); + debug_assert!(_remainder.is_zero()); + Ok(().into()) + } else { + Self::deposit_event(Event::::Migrated { + top: keys.len() as u32, + child: 0, + compute: MigrationCompute::Signed, + }); + Ok(PostDispatchInfo { + actual_weight: Some( + T::WeightInfo::migrate_custom_top_success().saturating_add( + Pallet::::dynamic_weight(keys.len() as u32, dyn_size), + ), + ), + pays_fee: Pays::Yes, + }) + } + } + + /// Migrate the list of child keys by iterating each of them one by one. + /// + /// All of the given child keys must be present under one `child_root`. + /// + /// This does not affect the global migration process tracker ([`MigrationProcess`]), and + /// should only be used in case any keys are leftover due to a bug. + #[pallet::call_index(3)] + #[pallet::weight( + T::WeightInfo::migrate_custom_child_success() + .max(T::WeightInfo::migrate_custom_child_fail()) + .saturating_add( + Pallet::::dynamic_weight(child_keys.len() as u32, *total_size) + ) + )] + pub fn migrate_custom_child( + origin: OriginFor, + root: Vec, + child_keys: Vec>, + total_size: u32, + ) -> DispatchResultWithPostInfo { + use sp_io::default_child_storage as child_io; + let who = T::SignedFilter::ensure_origin(origin)?; + + // ensure they can pay more than the fee. + let deposit = T::SignedDepositBase::get().saturating_add( + T::SignedDepositPerItem::get().saturating_mul((child_keys.len() as u32).into()), + ); + sp_std::if_std! { + println!("+ {:?} / {:?} / {:?}", who, deposit, T::Currency::free_balance(&who)); + } + ensure!(T::Currency::can_slash(&who, deposit), "not enough funds"); + + let mut dyn_size = 0u32; + let transformed_child_key = Self::transform_child_key(&root).ok_or("bad child key")?; + for child_key in &child_keys { + if let Some(data) = child_io::get(transformed_child_key, child_key) { + dyn_size = dyn_size.saturating_add(data.len() as u32); + child_io::set(transformed_child_key, child_key, &data); + } + } + + if dyn_size != total_size { + let (_imbalance, _remainder) = T::Currency::slash(&who, deposit); + debug_assert!(_remainder.is_zero()); + Self::deposit_event(Event::::Slashed { who, amount: deposit }); + Ok(PostDispatchInfo { + actual_weight: Some(T::WeightInfo::migrate_custom_child_fail()), + pays_fee: Pays::Yes, + }) + } else { + Self::deposit_event(Event::::Migrated { + top: 0, + child: child_keys.len() as u32, + compute: MigrationCompute::Signed, + }); + Ok(PostDispatchInfo { + actual_weight: Some( + T::WeightInfo::migrate_custom_child_success().saturating_add( + Pallet::::dynamic_weight(child_keys.len() as u32, total_size), + ), + ), + pays_fee: Pays::Yes, + }) + } + } + + /// Set the maximum limit of the signed migration. + #[pallet::call_index(4)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] + pub fn set_signed_max_limits( + origin: OriginFor, + limits: MigrationLimits, + ) -> DispatchResult { + let _ = T::ControlOrigin::ensure_origin(origin)?; + SignedMigrationMaxLimits::::put(limits); + Ok(()) + } + + /// Forcefully set the progress the running migration. + /// + /// This is only useful in one case: the next key to migrate is too big to be migrated with + /// a signed account, in a parachain context, and we simply want to skip it. A reasonable + /// example of this would be `:code:`, which is both very expensive to migrate, and commonly + /// used, so probably it is already migrated. + /// + /// In case you mess things up, you can also, in principle, use this to reset the migration + /// process. + #[pallet::call_index(5)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] + pub fn force_set_progress( + origin: OriginFor, + progress_top: ProgressOf, + progress_child: ProgressOf, + ) -> DispatchResult { + let _ = T::ControlOrigin::ensure_origin(origin)?; + MigrationProcess::::mutate(|task| { + task.progress_top = progress_top; + task.progress_child = progress_child; + }); + Ok(()) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_: BlockNumberFor) -> Weight { + if let Some(limits) = Self::auto_limits() { + let mut task = Self::migration_process(); + if let Err(e) = task.migrate_until_exhaustion(limits) { + Self::halt(e); + } + let weight = Self::dynamic_weight(task.dyn_total_items(), task.dyn_size); + + log!( + info, + "migrated {} top keys, {} child keys, and a total of {} bytes.", + task.dyn_top_items, + task.dyn_child_items, + task.dyn_size, + ); + + if task.finished() { + Self::deposit_event(Event::::AutoMigrationFinished); + AutoLimits::::kill(); + } else { + Self::deposit_event(Event::::Migrated { + top: task.dyn_top_items, + child: task.dyn_child_items, + compute: MigrationCompute::Auto, + }); + } + + MigrationProcess::::put(task); + + weight + } else { + T::DbWeight::get().reads(1) + } + } + } + + impl Pallet { + /// The real weight of a migration of the given number of `items` with total `size`. + fn dynamic_weight(items: u32, size: u32) -> frame_support::pallet_prelude::Weight { + let items = items as u64; + ::DbWeight::get() + .reads_writes(1, 1) + .saturating_mul(items) + // we assume that the read/write per-byte weight is the same for child and top tree. + .saturating_add(T::WeightInfo::process_top_key(size)) + } + + /// Put a stop to all ongoing migrations and logs an error. + fn halt(error: Error) { + log!(error, "migration halted due to: {:?}", error); + AutoLimits::::kill(); + Self::deposit_event(Event::::Halted { error }); + } + + /// Convert a child root key, aka. "Child-bearing top key" into the proper format. + fn transform_child_key(root: &Vec) -> Option<&[u8]> { + use sp_core::storage::{ChildType, PrefixedStorageKey}; + match ChildType::from_prefixed_key(PrefixedStorageKey::new_ref(root)) { + Some((ChildType::ParentKeyId, root)) => Some(root), + _ => None, + } + } + + /// Same as [`child_io_key`], and it halts the auto/unsigned migrations if a bad child root + /// is used. + /// + /// This should be used when we are sure that `root` is a correct default child root. + fn transform_child_key_or_halt(root: &Vec) -> &[u8] { + let key = Self::transform_child_key(root); + if key.is_none() { + Self::halt(Error::::BadChildRoot); + } + key.unwrap_or_default() + } + + /// Convert a child root to be in the default child-tree. + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub(crate) fn childify(root: &'static str) -> Vec { + let mut string = DEFAULT_CHILD_STORAGE_KEY_PREFIX.to_vec(); + string.extend_from_slice(root.as_ref()); + string + } + } +} + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarks { + use super::{pallet::Pallet as StateTrieMigration, *}; + use frame_support::traits::{Currency, Get}; + use sp_runtime::traits::Saturating; + use sp_std::prelude::*; + + // The size of the key seemingly makes no difference in the read/write time, so we make it + // constant. + const KEY: &[u8] = b"key"; + + frame_benchmarking::benchmarks! { + continue_migrate { + // note that this benchmark should migrate nothing, as we only want the overhead weight + // of the bookkeeping, and the migration cost itself is noted via the `dynamic_weight` + // function. + let null = MigrationLimits::default(); + let caller = frame_benchmarking::whitelisted_caller(); + // Allow signed migrations. + SignedMigrationMaxLimits::::put(MigrationLimits { size: 1024, item: 5 }); + }: _(frame_system::RawOrigin::Signed(caller), null, 0, StateTrieMigration::::migration_process()) + verify { + assert_eq!(StateTrieMigration::::migration_process(), Default::default()) + } + + continue_migrate_wrong_witness { + let null = MigrationLimits::default(); + let caller = frame_benchmarking::whitelisted_caller(); + let bad_witness = MigrationTask { progress_top: Progress::LastKey(vec![1u8].try_into().unwrap()), ..Default::default() }; + }: { + assert!( + StateTrieMigration::::continue_migrate( + frame_system::RawOrigin::Signed(caller).into(), + null, + 0, + bad_witness, + ) + .is_err() + ) + } + verify { + assert_eq!(StateTrieMigration::::migration_process(), Default::default()) + } + + migrate_custom_top_success { + let null = MigrationLimits::default(); + let caller = frame_benchmarking::whitelisted_caller(); + let deposit = T::SignedDepositBase::get().saturating_add( + T::SignedDepositPerItem::get().saturating_mul(1u32.into()), + ); + let stash = T::Currency::minimum_balance() * BalanceOf::::from(1000u32) + deposit; + T::Currency::make_free_balance_be(&caller, stash); + }: migrate_custom_top(frame_system::RawOrigin::Signed(caller.clone()), Default::default(), 0) + verify { + assert_eq!(StateTrieMigration::::migration_process(), Default::default()); + assert_eq!(T::Currency::free_balance(&caller), stash) + } + + migrate_custom_top_fail { + let null = MigrationLimits::default(); + let caller = frame_benchmarking::whitelisted_caller(); + let deposit = T::SignedDepositBase::get().saturating_add( + T::SignedDepositPerItem::get().saturating_mul(1u32.into()), + ); + let stash = T::Currency::minimum_balance() * BalanceOf::::from(1000u32) + deposit; + T::Currency::make_free_balance_be(&caller, stash); + // for tests, we need to make sure there is _something_ in storage that is being + // migrated. + sp_io::storage::set(b"foo", vec![1u8;33].as_ref()); + }: { + assert!( + StateTrieMigration::::migrate_custom_top( + frame_system::RawOrigin::Signed(caller.clone()).into(), + vec![b"foo".to_vec()], + 1, + ).is_ok() + ); + + frame_system::Pallet::::assert_last_event( + ::RuntimeEvent::from(crate::Event::Slashed { + who: caller.clone(), + amount: T::SignedDepositBase::get() + .saturating_add(T::SignedDepositPerItem::get().saturating_mul(1u32.into())), + }).into(), + ); + } + verify { + assert_eq!(StateTrieMigration::::migration_process(), Default::default()); + // must have gotten slashed + assert!(T::Currency::free_balance(&caller) < stash) + } + + migrate_custom_child_success { + let caller = frame_benchmarking::whitelisted_caller(); + let deposit = T::SignedDepositBase::get().saturating_add( + T::SignedDepositPerItem::get().saturating_mul(1u32.into()), + ); + let stash = T::Currency::minimum_balance() * BalanceOf::::from(1000u32) + deposit; + T::Currency::make_free_balance_be(&caller, stash); + }: migrate_custom_child( + frame_system::RawOrigin::Signed(caller.clone()), + StateTrieMigration::::childify(Default::default()), + Default::default(), + 0 + ) + verify { + assert_eq!(StateTrieMigration::::migration_process(), Default::default()); + assert_eq!(T::Currency::free_balance(&caller), stash); + } + + migrate_custom_child_fail { + let caller = frame_benchmarking::whitelisted_caller(); + let deposit = T::SignedDepositBase::get().saturating_add( + T::SignedDepositPerItem::get().saturating_mul(1u32.into()), + ); + let stash = T::Currency::minimum_balance() * BalanceOf::::from(1000u32) + deposit; + T::Currency::make_free_balance_be(&caller, stash); + // for tests, we need to make sure there is _something_ in storage that is being + // migrated. + sp_io::default_child_storage::set(b"top", b"foo", vec![1u8;33].as_ref()); + }: { + assert!( + StateTrieMigration::::migrate_custom_child( + frame_system::RawOrigin::Signed(caller.clone()).into(), + StateTrieMigration::::childify("top"), + vec![b"foo".to_vec()], + 1, + ).is_ok() + ) + } + verify { + assert_eq!(StateTrieMigration::::migration_process(), Default::default()); + // must have gotten slashed + assert!(T::Currency::free_balance(&caller) < stash) + } + + process_top_key { + let v in 1 .. (4 * 1024 * 1024); + + let value = sp_std::vec![1u8; v as usize]; + sp_io::storage::set(KEY, &value); + }: { + let data = sp_io::storage::get(KEY).unwrap(); + sp_io::storage::set(KEY, &data); + let _next = sp_io::storage::next_key(KEY); + assert_eq!(data, value); + } + + impl_benchmark_test_suite!( + StateTrieMigration, + crate::mock::new_test_ext(sp_runtime::StateVersion::V0, true, None, None), + crate::mock::Test + ); + } +} + +#[cfg(test)] +mod mock { + use super::*; + use crate as pallet_state_trie_migration; + use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64, Hooks}, + weights::Weight, + }; + use frame_system::{EnsureRoot, EnsureSigned}; + use sp_core::{ + storage::{ChildInfo, StateVersion}, + H256, + }; + use sp_runtime::{ + traits::{BlakeTwo256, Header as _, IdentityLookup}, + StorageChild, + }; + + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + type Block = frame_system::mocking::MockBlock; + + // Configure a mock runtime to test the pallet. + frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Config, Storage, Event}, + StateTrieMigration: pallet_state_trie_migration::{Pallet, Call, Storage, Event}, + } + ); + + parameter_types! { + pub const SS58Prefix: u8 = 42; + } + + impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u32; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = sp_runtime::generic::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU32<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; + } + + parameter_types! { + pub const SignedDepositPerItem: u64 = 1; + pub const SignedDepositBase: u64 = 5; + pub const MigrationMaxKeyLen: u32 = 512; + } + + impl pallet_balances::Config for Test { + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); + } + + /// Test only Weights for state migration. + pub struct StateMigrationTestWeight; + + impl WeightInfo for StateMigrationTestWeight { + fn process_top_key(_: u32) -> Weight { + Weight::from_ref_time(1000000) + } + fn continue_migrate() -> Weight { + Weight::from_ref_time(1000000) + } + fn continue_migrate_wrong_witness() -> Weight { + Weight::from_ref_time(1000000) + } + fn migrate_custom_top_fail() -> Weight { + Weight::from_ref_time(1000000) + } + fn migrate_custom_top_success() -> Weight { + Weight::from_ref_time(1000000) + } + fn migrate_custom_child_fail() -> Weight { + Weight::from_ref_time(1000000) + } + fn migrate_custom_child_success() -> Weight { + Weight::from_ref_time(1000000) + } + } + + impl pallet_state_trie_migration::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ControlOrigin = EnsureRoot; + type Currency = Balances; + type MaxKeyLen = MigrationMaxKeyLen; + type SignedDepositPerItem = SignedDepositPerItem; + type SignedDepositBase = SignedDepositBase; + type SignedFilter = EnsureSigned; + type WeightInfo = StateMigrationTestWeight; + } + + pub fn new_test_ext( + version: StateVersion, + with_pallets: bool, + custom_keys: Option, Vec)>>, + custom_child: Option, Vec, Vec)>>, + ) -> sp_io::TestExternalities { + let minimum_size = sp_core::storage::TRIE_VALUE_NODE_THRESHOLD as usize + 1; + let mut custom_storage = sp_core::storage::Storage { + top: vec![ + (b"key1".to_vec(), vec![1u8; minimum_size + 1]), // 6b657931 + (b"key2".to_vec(), vec![1u8; minimum_size + 2]), // 6b657931 + (b"key3".to_vec(), vec![1u8; minimum_size + 3]), // 6b657931 + (b"key4".to_vec(), vec![1u8; minimum_size + 4]), // 6b657931 + (b"key5".to_vec(), vec![1u8; minimum_size + 5]), // 6b657932 + (b"key6".to_vec(), vec![1u8; minimum_size + 6]), // 6b657934 + (b"key7".to_vec(), vec![1u8; minimum_size + 7]), // 6b657934 + (b"key8".to_vec(), vec![1u8; minimum_size + 8]), // 6b657934 + (b"key9".to_vec(), vec![1u8; minimum_size + 9]), // 6b657934 + (b"CODE".to_vec(), vec![1u8; minimum_size + 100]), // 434f4445 + ] + .into_iter() + .chain(custom_keys.unwrap_or_default()) + .collect(), + children_default: vec![ + ( + b"chk1".to_vec(), // 63686b31 + StorageChild { + data: vec![ + (b"key1".to_vec(), vec![1u8; 55]), + (b"key2".to_vec(), vec![2u8; 66]), + ] + .into_iter() + .collect(), + child_info: ChildInfo::new_default(b"chk1"), + }, + ), + ( + b"chk2".to_vec(), + StorageChild { + data: vec![ + (b"key1".to_vec(), vec![1u8; 54]), + (b"key2".to_vec(), vec![2u8; 64]), + ] + .into_iter() + .collect(), + child_info: ChildInfo::new_default(b"chk2"), + }, + ), + ] + .into_iter() + .chain( + custom_child + .unwrap_or_default() + .into_iter() + .map(|(r, k, v)| { + ( + r.clone(), + StorageChild { + data: vec![(k, v)].into_iter().collect(), + child_info: ChildInfo::new_default(&r), + }, + ) + }) + .collect::>(), + ) + .collect(), + }; + + if with_pallets { + frame_system::GenesisConfig::default() + .assimilate_storage::(&mut custom_storage) + .unwrap(); + pallet_balances::GenesisConfig:: { balances: vec![(1, 1000)] } + .assimilate_storage(&mut custom_storage) + .unwrap(); + } + + sp_tracing::try_init_simple(); + (custom_storage, version).into() + } + + pub(crate) fn run_to_block(n: u32) -> (H256, Weight) { + let mut root = Default::default(); + let mut weight_sum = Weight::zero(); + log::trace!(target: LOG_TARGET, "running from {:?} to {:?}", System::block_number(), n); + while System::block_number() < n { + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + + weight_sum += StateTrieMigration::on_initialize(System::block_number()); + + root = *System::finalize().state_root(); + System::on_finalize(System::block_number()); + } + (root, weight_sum) + } +} + +#[cfg(test)] +mod test { + use super::{mock::*, *}; + use frame_support::{bounded_vec, dispatch::*}; + use sp_runtime::{traits::Bounded, StateVersion}; + + #[test] + fn fails_if_no_migration() { + let mut ext = new_test_ext(StateVersion::V0, false, None, None); + let root1 = ext.execute_with(|| run_to_block(30).0); + + let mut ext2 = new_test_ext(StateVersion::V1, false, None, None); + let root2 = ext2.execute_with(|| run_to_block(30).0); + + // these two roots should not be the same. + assert_ne!(root1, root2); + } + + #[test] + fn halts_if_top_key_too_long() { + let bad_key = vec![1u8; MigrationMaxKeyLen::get() as usize + 1]; + let bad_top_keys = vec![(bad_key.clone(), vec![])]; + + new_test_ext(StateVersion::V0, true, Some(bad_top_keys), None).execute_with(|| { + System::set_block_number(1); + assert_eq!(MigrationProcess::::get(), Default::default()); + + // Allow signed migrations. + SignedMigrationMaxLimits::::put(MigrationLimits { size: 1 << 20, item: 50 }); + + // fails if the top key is too long. + frame_support::assert_ok!(StateTrieMigration::continue_migrate( + RuntimeOrigin::signed(1), + MigrationLimits { item: 50, size: 1 << 20 }, + Bounded::max_value(), + MigrationProcess::::get() + ),); + // The auto migration halted. + System::assert_last_event( + crate::Event::Halted { error: Error::::KeyTooLong }.into(), + ); + // Limits are killed. + assert!(AutoLimits::::get().is_none()); + + // Calling `migrate_until_exhaustion` also fails. + let mut task = StateTrieMigration::migration_process(); + let result = task.migrate_until_exhaustion( + StateTrieMigration::signed_migration_max_limits().unwrap(), + ); + assert!(result.is_err()); + }); + } + + #[test] + fn halts_if_child_key_too_long() { + let bad_key = vec![1u8; MigrationMaxKeyLen::get() as usize + 1]; + let bad_child_keys = vec![(bad_key.clone(), vec![], vec![])]; + + new_test_ext(StateVersion::V0, true, None, Some(bad_child_keys)).execute_with(|| { + System::set_block_number(1); + assert_eq!(MigrationProcess::::get(), Default::default()); + + // Allow signed migrations. + SignedMigrationMaxLimits::::put(MigrationLimits { size: 1 << 20, item: 50 }); + + // fails if the top key is too long. + frame_support::assert_ok!(StateTrieMigration::continue_migrate( + RuntimeOrigin::signed(1), + MigrationLimits { item: 50, size: 1 << 20 }, + Bounded::max_value(), + MigrationProcess::::get() + )); + // The auto migration halted. + System::assert_last_event( + crate::Event::Halted { error: Error::::KeyTooLong }.into(), + ); + // Limits are killed. + assert!(AutoLimits::::get().is_none()); + + // Calling `migrate_until_exhaustion` also fails. + let mut task = StateTrieMigration::migration_process(); + let result = task.migrate_until_exhaustion( + StateTrieMigration::signed_migration_max_limits().unwrap(), + ); + assert!(result.is_err()); + }); + } + + #[test] + fn detects_value_in_empty_top_key() { + let limit = MigrationLimits { item: 1, size: 1000 }; + let initial_keys = Some(vec![(vec![], vec![66u8; 77])]); + let mut ext = new_test_ext(StateVersion::V0, false, initial_keys.clone(), None); + + let root_upgraded = ext.execute_with(|| { + AutoLimits::::put(Some(limit)); + let root = run_to_block(30).0; + + // eventually everything is over. + assert!(StateTrieMigration::migration_process().finished()); + root + }); + + let mut ext2 = new_test_ext(StateVersion::V1, false, initial_keys, None); + let root = ext2.execute_with(|| { + AutoLimits::::put(Some(limit)); + run_to_block(30).0 + }); + + assert_eq!(root, root_upgraded); + } + + #[test] + fn detects_value_in_first_child_key() { + let limit = MigrationLimits { item: 1, size: 1000 }; + let initial_child = Some(vec![(b"chk1".to_vec(), vec![], vec![66u8; 77])]); + let mut ext = new_test_ext(StateVersion::V0, false, None, initial_child.clone()); + + let root_upgraded = ext.execute_with(|| { + AutoLimits::::put(Some(limit)); + let root = run_to_block(30).0; + + // eventually everything is over. + assert!(StateTrieMigration::migration_process().finished()); + root + }); + + let mut ext2 = new_test_ext(StateVersion::V1, false, None, initial_child); + let root = ext2.execute_with(|| { + AutoLimits::::put(Some(limit)); + run_to_block(30).0 + }); + + assert_eq!(root, root_upgraded); + } + + #[test] + fn auto_migrate_works() { + let run_with_limits = |limit, from, until| { + let mut ext = new_test_ext(StateVersion::V0, false, None, None); + let root_upgraded = ext.execute_with(|| { + assert_eq!(AutoLimits::::get(), None); + assert_eq!(MigrationProcess::::get(), Default::default()); + + // nothing happens if we don't set the limits. + let _ = run_to_block(from); + assert_eq!(MigrationProcess::::get(), Default::default()); + + // this should allow 1 item per block to be migrated. + AutoLimits::::put(Some(limit)); + + let root = run_to_block(until).0; + + // eventually everything is over. + assert!(matches!( + StateTrieMigration::migration_process(), + MigrationTask { progress_top: Progress::Complete, .. } + )); + root + }); + + let mut ext2 = new_test_ext(StateVersion::V1, false, None, None); + let root = ext2.execute_with(|| { + // update ex2 to contain the new items + let _ = run_to_block(from); + AutoLimits::::put(Some(limit)); + run_to_block(until).0 + }); + assert_eq!(root, root_upgraded); + }; + + // single item + run_with_limits(MigrationLimits { item: 1, size: 1000 }, 10, 100); + // multi-item + run_with_limits(MigrationLimits { item: 5, size: 1000 }, 10, 100); + // multi-item, based on size. Note that largest value is 100 bytes. + run_with_limits(MigrationLimits { item: 1000, size: 128 }, 10, 100); + // unbounded + run_with_limits( + MigrationLimits { item: Bounded::max_value(), size: Bounded::max_value() }, + 10, + 100, + ); + } + + #[test] + fn signed_migrate_works() { + new_test_ext(StateVersion::V0, true, None, None).execute_with(|| { + assert_eq!(MigrationProcess::::get(), Default::default()); + + // Allow signed migrations. + SignedMigrationMaxLimits::::put(MigrationLimits { size: 1024, item: 5 }); + + // can't submit if limit is too high. + frame_support::assert_err!( + StateTrieMigration::continue_migrate( + RuntimeOrigin::signed(1), + MigrationLimits { item: 5, size: sp_runtime::traits::Bounded::max_value() }, + Bounded::max_value(), + MigrationProcess::::get() + ), + Error::::MaxSignedLimits, + ); + + // can't submit if poor. + frame_support::assert_err!( + StateTrieMigration::continue_migrate( + RuntimeOrigin::signed(2), + MigrationLimits { item: 5, size: 100 }, + 100, + MigrationProcess::::get() + ), + Error::::NotEnoughFunds, + ); + + // can't submit with bad witness. + frame_support::assert_err_ignore_postinfo!( + StateTrieMigration::continue_migrate( + RuntimeOrigin::signed(1), + MigrationLimits { item: 5, size: 100 }, + 100, + MigrationTask { + progress_top: Progress::LastKey(bounded_vec![1u8]), + ..Default::default() + } + ), + Error::::BadWitness, + ); + + // migrate all keys in a series of submissions + while !MigrationProcess::::get().finished() { + // first we compute the task to get the accurate consumption. + let mut task = StateTrieMigration::migration_process(); + let result = task.migrate_until_exhaustion( + StateTrieMigration::signed_migration_max_limits().unwrap(), + ); + assert!(result.is_ok()); + + frame_support::assert_ok!(StateTrieMigration::continue_migrate( + RuntimeOrigin::signed(1), + StateTrieMigration::signed_migration_max_limits().unwrap(), + task.dyn_size, + MigrationProcess::::get() + )); + + // no funds should remain reserved. + assert_eq!(Balances::reserved_balance(&1), 0); + + // and the task should be updated + assert!(matches!( + StateTrieMigration::migration_process(), + MigrationTask { size: x, .. } if x > 0, + )); + } + }); + } + + #[test] + fn custom_migrate_top_works() { + let correct_witness = 3 + sp_core::storage::TRIE_VALUE_NODE_THRESHOLD * 3 + 1 + 2 + 3; + new_test_ext(StateVersion::V0, true, None, None).execute_with(|| { + frame_support::assert_ok!(StateTrieMigration::migrate_custom_top( + RuntimeOrigin::signed(1), + vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()], + correct_witness, + )); + + // no funds should remain reserved. + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Balances::free_balance(&1), 1000); + }); + + new_test_ext(StateVersion::V0, true, None, None).execute_with(|| { + // works if the witness is an overestimate + frame_support::assert_ok!(StateTrieMigration::migrate_custom_top( + RuntimeOrigin::signed(1), + vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()], + correct_witness + 99, + )); + + // no funds should remain reserved. + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Balances::free_balance(&1), 1000); + }); + + new_test_ext(StateVersion::V0, true, None, None).execute_with(|| { + assert_eq!(Balances::free_balance(&1), 1000); + + // note that we don't expect this to be a noop -- we do slash. + frame_support::assert_ok!(StateTrieMigration::migrate_custom_top( + RuntimeOrigin::signed(1), + vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()], + correct_witness - 1, + ),); + + // no funds should remain reserved. + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!( + Balances::free_balance(&1), + 1000 - (3 * SignedDepositPerItem::get() + SignedDepositBase::get()) + ); + }); + } + + #[test] + fn custom_migrate_child_works() { + new_test_ext(StateVersion::V0, true, None, None).execute_with(|| { + frame_support::assert_ok!(StateTrieMigration::migrate_custom_child( + RuntimeOrigin::signed(1), + StateTrieMigration::childify("chk1"), + vec![b"key1".to_vec(), b"key2".to_vec()], + 55 + 66, + )); + + // no funds should remain reserved. + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Balances::free_balance(&1), 1000); + }); + + new_test_ext(StateVersion::V0, true, None, None).execute_with(|| { + assert_eq!(Balances::free_balance(&1), 1000); + + // note that we don't expect this to be a noop -- we do slash. + frame_support::assert_ok!(StateTrieMigration::migrate_custom_child( + RuntimeOrigin::signed(1), + StateTrieMigration::childify("chk1"), + vec![b"key1".to_vec(), b"key2".to_vec()], + 999999, // wrong witness + )); + + // no funds should remain reserved. + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!( + Balances::free_balance(&1), + 1000 - (2 * SignedDepositPerItem::get() + SignedDepositBase::get()) + ); + }); + } +} + +/// Exported set of tests to be called against different runtimes. +#[cfg(feature = "remote-test")] +pub(crate) mod remote_tests { + use crate::{AutoLimits, MigrationLimits, Pallet as StateTrieMigration, LOG_TARGET}; + use codec::Encode; + use frame_support::{ + traits::{Get, Hooks}, + weights::Weight, + }; + use frame_system::Pallet as System; + use remote_externalities::Mode; + use sp_core::H256; + use sp_runtime::traits::{Block as BlockT, HashFor, Header as _, One, Zero}; + use thousands::Separable; + + #[allow(dead_code)] + fn run_to_block>( + n: ::BlockNumber, + ) -> (H256, Weight) { + let mut root = Default::default(); + let mut weight_sum = Weight::zero(); + while System::::block_number() < n { + System::::set_block_number(System::::block_number() + One::one()); + System::::on_initialize(System::::block_number()); + + weight_sum += + StateTrieMigration::::on_initialize(System::::block_number()); + + root = System::::finalize().state_root().clone(); + System::::on_finalize(System::::block_number()); + } + (root, weight_sum) + } + + /// Run the entire migration, against the given `Runtime`, until completion. + /// + /// This will print some very useful statistics, make sure [`crate::LOG_TARGET`] is enabled. + #[allow(dead_code)] + pub(crate) async fn run_with_limits(limits: MigrationLimits, mode: Mode) + where + Runtime: crate::Config, + Block: BlockT, + Block::Header: serde::de::DeserializeOwned, + { + let mut ext = remote_externalities::Builder::::new() + .mode(mode) + .state_version(sp_core::storage::StateVersion::V0) + .build() + .await + .unwrap(); + + let mut now = ext.execute_with(|| { + AutoLimits::::put(Some(limits)); + // requires the block number type in our tests to be same as with mainnet, u32. + frame_system::Pallet::::block_number() + }); + + let mut duration: ::BlockNumber = Zero::zero(); + // set the version to 1, as if the upgrade happened. + ext.state_version = sp_core::storage::StateVersion::V1; + + let status = + substrate_state_trie_migration_rpc::migration_status(&ext.as_backend()).unwrap(); + assert!( + status.top_remaining_to_migrate > 0, + "no node needs migrating, this probably means that state was initialized with `StateVersion::V1`", + ); + + log::info!( + target: LOG_TARGET, + "initial check: top_left: {}, child_left: {}, total_top {}, total_child {}", + status.top_remaining_to_migrate.separate_with_commas(), + status.child_remaining_to_migrate.separate_with_commas(), + status.total_top.separate_with_commas(), + status.total_child.separate_with_commas(), + ); + + loop { + let last_state_root = ext.backend.root().clone(); + let ((finished, weight), proof) = ext.execute_and_prove(|| { + let weight = run_to_block::(now + One::one()).1; + if StateTrieMigration::::migration_process().finished() { + return (true, weight) + } + duration += One::one(); + now += One::one(); + (false, weight) + }); + + let compact_proof = + proof.clone().into_compact_proof::>(last_state_root).unwrap(); + log::info!( + target: LOG_TARGET, + "proceeded to #{}, weight: [{} / {}], proof: [{} / {} / {}]", + now, + weight.separate_with_commas(), + ::BlockWeights::get() + .max_block + .separate_with_commas(), + proof.encoded_size().separate_with_commas(), + compact_proof.encoded_size().separate_with_commas(), + zstd::stream::encode_all(&compact_proof.encode()[..], 0) + .unwrap() + .len() + .separate_with_commas(), + ); + ext.commit_all().unwrap(); + + if finished { + break + } + } + + ext.execute_with(|| { + log::info!( + target: LOG_TARGET, + "finished on_initialize migration in {} block, final state of the task: {:?}", + duration, + StateTrieMigration::::migration_process(), + ) + }); + + let status = + substrate_state_trie_migration_rpc::migration_status(&ext.as_backend()).unwrap(); + assert_eq!(status.top_remaining_to_migrate, 0); + assert_eq!(status.child_remaining_to_migrate, 0); + } +} + +#[cfg(all(test, feature = "remote-test"))] +mod remote_tests_local { + use super::{ + mock::{RuntimeCall as MockCall, *}, + remote_tests::run_with_limits, + *, + }; + use remote_externalities::{Mode, OfflineConfig, OnlineConfig, SnapshotConfig}; + use sp_runtime::traits::Bounded; + use std::env::var as env_var; + + // we only use the hash type from this, so using the mock should be fine. + type Extrinsic = sp_runtime::testing::TestXt; + type Block = sp_runtime::testing::Block; + + #[tokio::test] + async fn on_initialize_migration() { + let snap: SnapshotConfig = env_var("SNAP").expect("Need SNAP env var").into(); + let ws_api = env_var("WS_API").expect("Need WS_API env var").into(); + + sp_tracing::try_init_simple(); + let mode = Mode::OfflineOrElseOnline( + OfflineConfig { state_snapshot: snap.clone() }, + OnlineConfig { transport: ws_api, state_snapshot: Some(snap), ..Default::default() }, + ); + + // item being the bottleneck + run_with_limits::( + MigrationLimits { item: 8 * 1024, size: 128 * 1024 * 1024 }, + mode.clone(), + ) + .await; + // size being the bottleneck + run_with_limits::( + MigrationLimits { item: Bounded::max_value(), size: 64 * 1024 }, + mode, + ) + .await; + } +} diff --git a/frame/state-trie-migration/src/weights.rs b/frame/state-trie-migration/src/weights.rs new file mode 100644 index 0000000000000..7414bb9038fdd --- /dev/null +++ b/frame/state-trie-migration/src/weights.rs @@ -0,0 +1,158 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_state_trie_migration +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_state_trie_migration +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/state-trie-migration/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_state_trie_migration. +pub trait WeightInfo { + fn continue_migrate() -> Weight; + fn continue_migrate_wrong_witness() -> Weight; + fn migrate_custom_top_success() -> Weight; + fn migrate_custom_top_fail() -> Weight; + fn migrate_custom_child_success() -> Weight; + fn migrate_custom_child_fail() -> Weight; + fn process_top_key(v: u32, ) -> Weight; +} + +/// Weights for pallet_state_trie_migration using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) + // Storage: StateTrieMigration MigrationProcess (r:1 w:1) + fn continue_migrate() -> Weight { + // Minimum execution time: 23_874 nanoseconds. + Weight::from_ref_time(24_127_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) + fn continue_migrate_wrong_witness() -> Weight { + // Minimum execution time: 6_119 nanoseconds. + Weight::from_ref_time(6_325_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + } + fn migrate_custom_top_success() -> Weight { + // Minimum execution time: 20_365 nanoseconds. + Weight::from_ref_time(20_790_000 as u64) + } + // Storage: unknown [0x666f6f] (r:1 w:1) + fn migrate_custom_top_fail() -> Weight { + // Minimum execution time: 38_979 nanoseconds. + Weight::from_ref_time(40_271_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn migrate_custom_child_success() -> Weight { + // Minimum execution time: 21_217 nanoseconds. + Weight::from_ref_time(21_526_000 as u64) + } + // Storage: unknown [0x666f6f] (r:1 w:1) + fn migrate_custom_child_fail() -> Weight { + // Minimum execution time: 43_853 nanoseconds. + Weight::from_ref_time(44_693_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: unknown [0x6b6579] (r:1 w:1) + /// The range of component `v` is `[1, 4194304]`. + fn process_top_key(v: u32, ) -> Weight { + // Minimum execution time: 5_575 nanoseconds. + Weight::from_ref_time(5_719_000 as u64) + // Standard Error: 3 + .saturating_add(Weight::from_ref_time(1_404 as u64).saturating_mul(v as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) + // Storage: StateTrieMigration MigrationProcess (r:1 w:1) + fn continue_migrate() -> Weight { + // Minimum execution time: 23_874 nanoseconds. + Weight::from_ref_time(24_127_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) + fn continue_migrate_wrong_witness() -> Weight { + // Minimum execution time: 6_119 nanoseconds. + Weight::from_ref_time(6_325_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + } + fn migrate_custom_top_success() -> Weight { + // Minimum execution time: 20_365 nanoseconds. + Weight::from_ref_time(20_790_000 as u64) + } + // Storage: unknown [0x666f6f] (r:1 w:1) + fn migrate_custom_top_fail() -> Weight { + // Minimum execution time: 38_979 nanoseconds. + Weight::from_ref_time(40_271_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn migrate_custom_child_success() -> Weight { + // Minimum execution time: 21_217 nanoseconds. + Weight::from_ref_time(21_526_000 as u64) + } + // Storage: unknown [0x666f6f] (r:1 w:1) + fn migrate_custom_child_fail() -> Weight { + // Minimum execution time: 43_853 nanoseconds. + Weight::from_ref_time(44_693_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: unknown [0x6b6579] (r:1 w:1) + /// The range of component `v` is `[1, 4194304]`. + fn process_top_key(v: u32, ) -> Weight { + // Minimum execution time: 5_575 nanoseconds. + Weight::from_ref_time(5_719_000 as u64) + // Standard Error: 3 + .saturating_add(Weight::from_ref_time(1_404 as u64).saturating_mul(v as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/frame/sudo/Cargo.toml b/frame/sudo/Cargo.toml index baacb66d5c751..b0e38b0139c11 100644 --- a/frame/sudo/Cargo.toml +++ b/frame/sudo/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-sudo" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for sudo" readme = "README.md" @@ -13,26 +13,26 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", - "sp-std/std", "sp-io/std", "sp-runtime/std", - "frame-support/std", - "frame-system/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/sudo/README.md b/frame/sudo/README.md index ac7de01615f3f..7342832d2d7a7 100644 --- a/frame/sudo/README.md +++ b/frame/sudo/README.md @@ -35,15 +35,22 @@ Learn more about privileged functions and `Root` origin in the [`Origin`] type d This is an example of a module that exposes a privileged function: ```rust -use frame_support::{decl_module, dispatch}; -use frame_system::ensure_root; - -pub trait Config: frame_system::Config {} - -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - #[weight = 0] - pub fn privileged_function(origin) -> dispatch::DispatchResult { +#[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: frame_system::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn privileged_function(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; // do something... @@ -65,6 +72,6 @@ You need to set an initial superuser account as the sudo `key`. [`Call`]: ./enum.Call.html [`Config`]: ./trait.Config.html -[`Origin`]: https://docs.substrate.dev/docs/substrate-types +[`Origin`]: https://docs.substrate.io/main-docs/build/origins/ License: Apache-2.0 diff --git a/frame/sudo/src/extension.rs b/frame/sudo/src/extension.rs new file mode 100644 index 0000000000000..068fa2ed928d5 --- /dev/null +++ b/frame/sudo/src/extension.rs @@ -0,0 +1,107 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::{Config, Pallet}; +use codec::{Decode, Encode}; +use frame_support::{dispatch::DispatchInfo, ensure}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, SignedExtension}, + transaction_validity::{ + InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError, + UnknownTransaction, ValidTransaction, + }, +}; +use sp_std::{fmt, marker::PhantomData}; + +/// Ensure that signed transactions are only valid if they are signed by sudo account. +/// +/// In the initial phase of a chain without any tokens you can not prevent accounts from sending +/// transactions. +/// These transactions would enter the transaction pool as the succeed the validation, but would +/// fail on applying them as they are not allowed/disabled/whatever. This would be some huge dos +/// vector to any kind of chain. This extension solves the dos vector by preventing any kind of +/// transaction entering the pool as long as it is not signed by the sudo account. +#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckOnlySudoAccount(PhantomData); + +impl Default for CheckOnlySudoAccount { + fn default() -> Self { + Self(Default::default()) + } +} + +impl fmt::Debug for CheckOnlySudoAccount { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "CheckOnlySudoAccount") + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result { + Ok(()) + } +} + +impl CheckOnlySudoAccount { + /// Creates new `SignedExtension` to check sudo key. + pub fn new() -> Self { + Self::default() + } +} + +impl SignedExtension for CheckOnlySudoAccount +where + ::RuntimeCall: Dispatchable, +{ + const IDENTIFIER: &'static str = "CheckOnlySudoAccount"; + type AccountId = T::AccountId; + type Call = ::RuntimeCall; + type AdditionalSigned = (); + type Pre = (); + + fn additional_signed(&self) -> Result { + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + _call: &Self::Call, + info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + let sudo_key: T::AccountId = >::key().ok_or(UnknownTransaction::CannotLookup)?; + ensure!(*who == sudo_key, InvalidTransaction::BadSigner); + + Ok(ValidTransaction { + priority: info.weight.ref_time() as TransactionPriority, + ..Default::default() + }) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } +} diff --git a/frame/sudo/src/lib.rs b/frame/sudo/src/lib.rs index bab93ffcee162..0867f24b1691e 100644 --- a/frame/sudo/src/lib.rs +++ b/frame/sudo/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,34 +52,40 @@ //! This is an example of a pallet that exposes a privileged function: //! //! ``` -//! //! #[frame_support::pallet] -//! pub mod logger { +//! pub mod pallet { +//! use super::*; //! use frame_support::pallet_prelude::*; //! use frame_system::pallet_prelude::*; -//! use super::*; -//! -//! #[pallet::config] -//! pub trait Config: frame_system::Config {} //! //! #[pallet::pallet] //! pub struct Pallet(PhantomData); //! +//! #[pallet::config] +//! pub trait Config: frame_system::Config {} +//! //! #[pallet::call] //! impl Pallet { //! #[pallet::weight(0)] -//! pub fn privileged_function(origin: OriginFor) -> DispatchResultWithPostInfo { +//! pub fn privileged_function(origin: OriginFor) -> DispatchResult { //! ensure_root(origin)?; //! //! // do something... //! -//! Ok(().into()) +//! Ok(()) //! } //! } //! } //! # fn main() {} //! ``` //! +//! ### Signed Extension +//! +//! The Sudo pallet defines the following extension: +//! +//! - [`CheckOnlySudoAccount`]: Ensures that the signed transactions are only valid if they are +//! signed by sudo account. +//! //! ## Genesis Config //! //! The Sudo pallet depends on the [`GenesisConfig`]. @@ -89,22 +95,26 @@ //! //! * [Democracy](../pallet_democracy/index.html) //! -//! [`Origin`]: https://docs.substrate.dev/docs/substrate-types +//! [`Origin`]: https://docs.substrate.io/main-docs/build/origins/ #![cfg_attr(not(feature = "std"), no_std)] use sp_runtime::{traits::StaticLookup, DispatchResult}; use sp_std::prelude::*; -use frame_support::{traits::UnfilteredDispatchable, weights::GetDispatchInfo}; +use frame_support::{dispatch::GetDispatchInfo, traits::UnfilteredDispatchable}; +mod extension; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +pub use extension::CheckOnlySudoAccount; pub use pallet::*; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + #[frame_support::pallet] pub mod pallet { use super::{DispatchResult, *}; @@ -114,15 +124,16 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// A sudo-able call. - type Call: Parameter + UnfilteredDispatchable + GetDispatchInfo; + type RuntimeCall: Parameter + + UnfilteredDispatchable + + GetDispatchInfo; } #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::generate_storage_info] pub struct Pallet(PhantomData); #[pallet::call] @@ -137,20 +148,21 @@ pub mod pallet { /// - One DB write (event). /// - Weight of derivative `call` execution + 10,000. /// # + #[pallet::call_index(0)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); - (dispatch_info.weight.saturating_add(10_000), dispatch_info.class) + (dispatch_info.weight, dispatch_info.class) })] pub fn sudo( origin: OriginFor, - call: Box<::Call>, + call: Box<::RuntimeCall>, ) -> DispatchResultWithPostInfo { // This is a public call, so we ensure that the origin is some signed account. let sender = ensure_signed(origin)?; - ensure!(sender == Self::key(), Error::::RequireSudo); + ensure!(Self::key().map_or(false, |k| sender == k), Error::::RequireSudo); let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); - Self::deposit_event(Event::Sudid(res.map(|_| ()).map_err(|e| e.error))); + Self::deposit_event(Event::Sudid { sudo_result: res.map(|_| ()).map_err(|e| e.error) }); // Sudo user does not pay a fee. Ok(Pays::No.into()) } @@ -165,18 +177,19 @@ pub mod pallet { /// - O(1). /// - The weight of this call is defined by the caller. /// # + #[pallet::call_index(1)] #[pallet::weight((*_weight, call.get_dispatch_info().class))] pub fn sudo_unchecked_weight( origin: OriginFor, - call: Box<::Call>, + call: Box<::RuntimeCall>, _weight: Weight, ) -> DispatchResultWithPostInfo { // This is a public call, so we ensure that the origin is some signed account. let sender = ensure_signed(origin)?; - ensure!(sender == Self::key(), Error::::RequireSudo); + ensure!(Self::key().map_or(false, |k| sender == k), Error::::RequireSudo); let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); - Self::deposit_event(Event::Sudid(res.map(|_| ()).map_err(|e| e.error))); + Self::deposit_event(Event::Sudid { sudo_result: res.map(|_| ()).map_err(|e| e.error) }); // Sudo user does not pay a fee. Ok(Pays::No.into()) } @@ -191,18 +204,19 @@ pub mod pallet { /// - Limited storage reads. /// - One DB change. /// # + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn set_key( origin: OriginFor, - new: ::Source, + new: AccountIdLookupOf, ) -> DispatchResultWithPostInfo { // This is a public call, so we ensure that the origin is some signed account. let sender = ensure_signed(origin)?; - ensure!(sender == Self::key(), Error::::RequireSudo); + ensure!(Self::key().map_or(false, |k| sender == k), Error::::RequireSudo); let new = T::Lookup::lookup(new)?; - Self::deposit_event(Event::KeyChanged(Self::key())); - >::put(new); + Self::deposit_event(Event::KeyChanged { old_sudoer: Key::::get() }); + Key::::put(&new); // Sudo user does not pay a fee. Ok(Pays::No.into()) } @@ -218,11 +232,11 @@ pub mod pallet { /// - One DB write (event). /// - Weight of derivative `call` execution + 10,000. /// # + #[pallet::call_index(3)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( dispatch_info.weight - .saturating_add(10_000) // AccountData for inner call origin accountdata. .saturating_add(T::DbWeight::get().reads_writes(1, 1)), dispatch_info.class, @@ -230,18 +244,20 @@ pub mod pallet { })] pub fn sudo_as( origin: OriginFor, - who: ::Source, - call: Box<::Call>, + who: AccountIdLookupOf, + call: Box<::RuntimeCall>, ) -> DispatchResultWithPostInfo { // This is a public call, so we ensure that the origin is some signed account. let sender = ensure_signed(origin)?; - ensure!(sender == Self::key(), Error::::RequireSudo); + ensure!(Self::key().map_or(false, |k| sender == k), Error::::RequireSudo); let who = T::Lookup::lookup(who)?; let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Signed(who).into()); - Self::deposit_event(Event::SudoAsDone(res.map(|_| ()).map_err(|e| e.error))); + Self::deposit_event(Event::SudoAsDone { + sudo_result: res.map(|_| ()).map_err(|e| e.error), + }); // Sudo user does not pay a fee. Ok(Pays::No.into()) } @@ -251,11 +267,11 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// A sudo just took place. \[result\] - Sudid(DispatchResult), - /// The \[sudoer\] just switched identity; the old key is supplied. - KeyChanged(T::AccountId), + Sudid { sudo_result: DispatchResult }, + /// The \[sudoer\] just switched identity; the old key is supplied if one existed. + KeyChanged { old_sudoer: Option }, /// A sudo just took place. \[result\] - SudoAsDone(DispatchResult), + SudoAsDone { sudo_result: DispatchResult }, } #[pallet::error] @@ -268,25 +284,27 @@ pub mod pallet { /// The `AccountId` of the sudo key. #[pallet::storage] #[pallet::getter(fn key)] - pub(super) type Key = StorageValue<_, T::AccountId, ValueQuery>; + pub(super) type Key = StorageValue<_, T::AccountId, OptionQuery>; #[pallet::genesis_config] pub struct GenesisConfig { /// The `AccountId` of the sudo key. - pub key: T::AccountId, + pub key: Option, } #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { - Self { key: Default::default() } + Self { key: None } } } #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { - >::put(&self.key); + if let Some(ref key) = self.key { + Key::::put(key); + } } } } diff --git a/frame/sudo/src/mock.rs b/frame/sudo/src/mock.rs index dad17384d5603..639e81ceaa308 100644 --- a/frame/sudo/src/mock.rs +++ b/frame/sudo/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,8 @@ use super::*; use crate as sudo; use frame_support::{ parameter_types, - traits::{Contains, GenesisBuild}, + traits::{ConstU32, ConstU64, Contains, GenesisBuild}, + weights::Weight, }; use frame_system::limits; use sp_core::H256; @@ -34,13 +35,12 @@ use sp_runtime::{ // Logger module to track execution. #[frame_support::pallet] pub mod logger { - use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; } #[pallet::pallet] @@ -49,6 +49,7 @@ pub mod logger { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(*weight)] pub fn privileged_i32_log( origin: OriginFor, @@ -57,11 +58,12 @@ pub mod logger { ) -> DispatchResultWithPostInfo { // Ensure that the `origin` is `Root`. ensure_root(origin)?; - >::append(i); - Self::deposit_event(Event::AppendI32(i, weight)); + >::try_append(i).map_err(|_| "could not append")?; + Self::deposit_event(Event::AppendI32 { value: i, weight }); Ok(().into()) } + #[pallet::call_index(1)] #[pallet::weight(*weight)] pub fn non_privileged_log( origin: OriginFor, @@ -70,9 +72,9 @@ pub mod logger { ) -> DispatchResultWithPostInfo { // Ensure that the `origin` is some signed account. let sender = ensure_signed(origin)?; - >::append(i); - >::append(sender.clone()); - Self::deposit_event(Event::AppendI32AndAccount(sender, i, weight)); + >::try_append(i).map_err(|_| "could not append")?; + >::try_append(sender.clone()).map_err(|_| "could not append")?; + Self::deposit_event(Event::AppendI32AndAccount { sender, value: i, weight }); Ok(().into()) } } @@ -80,17 +82,18 @@ pub mod logger { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - AppendI32(i32, Weight), - AppendI32AndAccount(T::AccountId, i32, Weight), + AppendI32 { value: i32, weight: Weight }, + AppendI32AndAccount { sender: T::AccountId, value: i32, weight: Weight }, } #[pallet::storage] #[pallet::getter(fn account_log)] - pub(super) type AccountLog = StorageValue<_, Vec, ValueQuery>; + pub(super) type AccountLog = + StorageValue<_, BoundedVec>, ValueQuery>; #[pallet::storage] #[pallet::getter(fn i32_log)] - pub(super) type I32Log = StorageValue<_, Vec, ValueQuery>; + pub(super) type I32Log = StorageValue<_, BoundedVec>, ValueQuery>; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -109,13 +112,12 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: limits::BlockWeights = limits::BlockWeights::simple_max(1024); + pub BlockWeights: limits::BlockWeights = limits::BlockWeights::simple_max(Weight::from_ref_time(1024)); } pub struct BlockEverything; -impl Contains for BlockEverything { - fn contains(_: &Call) -> bool { +impl Contains for BlockEverything { + fn contains(_: &RuntimeCall) -> bool { false } } @@ -125,8 +127,8 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -134,8 +136,8 @@ impl frame_system::Config for Test { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -144,17 +146,18 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } // Implement the logger module's `Config` on the Test runtime. impl logger::Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; } // Implement the sudo module's `Config` on the Test runtime. impl Config for Test { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; } // New types for dispatchable functions. @@ -164,7 +167,7 @@ pub type LoggerCall = logger::Call; // Build test environment by setting the root `key` for the Genesis. pub fn new_test_ext(root_key: u64) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - sudo::GenesisConfig:: { key: root_key } + sudo::GenesisConfig:: { key: Some(root_key) } .assimilate_storage(&mut t) .unwrap(); t.into() diff --git a/frame/sudo/src/tests.rs b/frame/sudo/src/tests.rs index 2eb558e9471c4..ae8f198736004 100644 --- a/frame/sudo/src/tests.rs +++ b/frame/sudo/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,17 +18,17 @@ //! Tests for the module. use super::*; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok, weights::Weight}; use mock::{ - new_test_ext, Call, Event as TestEvent, Logger, LoggerCall, Origin, Sudo, SudoCall, System, - Test, + new_test_ext, Logger, LoggerCall, RuntimeCall, RuntimeEvent as TestEvent, RuntimeOrigin, Sudo, + SudoCall, System, Test, }; #[test] fn test_setup_works() { // Environment setup, logger storage, and sudo `key` retrieval should work as expected. new_test_ext(1).execute_with(|| { - assert_eq!(Sudo::key(), 1u64); + assert_eq!(Sudo::key(), Some(1u64)); assert!(Logger::i32_log().is_empty()); assert!(Logger::account_log().is_empty()); }); @@ -39,13 +39,19 @@ fn sudo_basics() { // Configure a default test environment and set the root `key` to 1. new_test_ext(1).execute_with(|| { // A privileged function should work when `sudo` is passed the root `key` as `origin`. - let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log { i: 42, weight: 1_000 })); - assert_ok!(Sudo::sudo(Origin::signed(1), call)); + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_ref_time(1_000), + })); + assert_ok!(Sudo::sudo(RuntimeOrigin::signed(1), call)); assert_eq!(Logger::i32_log(), vec![42i32]); // A privileged function should not work when `sudo` is passed a non-root `key` as `origin`. - let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log { i: 42, weight: 1_000 })); - assert_noop!(Sudo::sudo(Origin::signed(2), call), Error::::RequireSudo); + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_ref_time(1_000), + })); + assert_noop!(Sudo::sudo(RuntimeOrigin::signed(2), call), Error::::RequireSudo); }); } @@ -56,9 +62,12 @@ fn sudo_emits_events_correctly() { System::set_block_number(1); // Should emit event to indicate success when called with the root `key` and `call` is `Ok`. - let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log { i: 42, weight: 1 })); - assert_ok!(Sudo::sudo(Origin::signed(1), call)); - System::assert_has_event(TestEvent::Sudo(Event::Sudid(Ok(())))); + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_ref_time(1), + })); + assert_ok!(Sudo::sudo(RuntimeOrigin::signed(1), call)); + System::assert_has_event(TestEvent::Sudo(Event::Sudid { sudo_result: Ok(()) })); }) } @@ -66,24 +75,42 @@ fn sudo_emits_events_correctly() { fn sudo_unchecked_weight_basics() { new_test_ext(1).execute_with(|| { // A privileged function should work when `sudo` is passed the root `key` as origin. - let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log { i: 42, weight: 1_000 })); - assert_ok!(Sudo::sudo_unchecked_weight(Origin::signed(1), call, 1_000)); + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_ref_time(1_000), + })); + assert_ok!(Sudo::sudo_unchecked_weight( + RuntimeOrigin::signed(1), + call, + Weight::from_ref_time(1_000) + )); assert_eq!(Logger::i32_log(), vec![42i32]); // A privileged function should not work when called with a non-root `key`. - let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log { i: 42, weight: 1_000 })); + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_ref_time(1_000), + })); assert_noop!( - Sudo::sudo_unchecked_weight(Origin::signed(2), call, 1_000), + Sudo::sudo_unchecked_weight( + RuntimeOrigin::signed(2), + call, + Weight::from_ref_time(1_000) + ), Error::::RequireSudo, ); // `I32Log` is unchanged after unsuccessful call. assert_eq!(Logger::i32_log(), vec![42i32]); // Controls the dispatched weight. - let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log { i: 42, weight: 1 })); - let sudo_unchecked_weight_call = SudoCall::sudo_unchecked_weight { call, weight: 1_000 }; + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_ref_time(1), + })); + let sudo_unchecked_weight_call = + SudoCall::sudo_unchecked_weight { call, weight: Weight::from_ref_time(1_000) }; let info = sudo_unchecked_weight_call.get_dispatch_info(); - assert_eq!(info.weight, 1_000); + assert_eq!(info.weight, Weight::from_ref_time(1_000)); }); } @@ -94,9 +121,16 @@ fn sudo_unchecked_weight_emits_events_correctly() { System::set_block_number(1); // Should emit event to indicate success when called with the root `key` and `call` is `Ok`. - let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log { i: 42, weight: 1 })); - assert_ok!(Sudo::sudo_unchecked_weight(Origin::signed(1), call, 1_000)); - System::assert_has_event(TestEvent::Sudo(Event::Sudid(Ok(())))); + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_ref_time(1), + })); + assert_ok!(Sudo::sudo_unchecked_weight( + RuntimeOrigin::signed(1), + call, + Weight::from_ref_time(1_000) + )); + System::assert_has_event(TestEvent::Sudo(Event::Sudid { sudo_result: Ok(()) })); }) } @@ -104,14 +138,14 @@ fn sudo_unchecked_weight_emits_events_correctly() { fn set_key_basics() { new_test_ext(1).execute_with(|| { // A root `key` can change the root `key` - assert_ok!(Sudo::set_key(Origin::signed(1), 2)); - assert_eq!(Sudo::key(), 2u64); + assert_ok!(Sudo::set_key(RuntimeOrigin::signed(1), 2)); + assert_eq!(Sudo::key(), Some(2u64)); }); new_test_ext(1).execute_with(|| { // A non-root `key` will trigger a `RequireSudo` error and a non-root `key` cannot change // the root `key`. - assert_noop!(Sudo::set_key(Origin::signed(2), 3), Error::::RequireSudo); + assert_noop!(Sudo::set_key(RuntimeOrigin::signed(2), 3), Error::::RequireSudo); }); } @@ -122,11 +156,11 @@ fn set_key_emits_events_correctly() { System::set_block_number(1); // A root `key` can change the root `key`. - assert_ok!(Sudo::set_key(Origin::signed(1), 2)); - System::assert_has_event(TestEvent::Sudo(Event::KeyChanged(1))); + assert_ok!(Sudo::set_key(RuntimeOrigin::signed(1), 2)); + System::assert_has_event(TestEvent::Sudo(Event::KeyChanged { old_sudoer: Some(1) })); // Double check. - assert_ok!(Sudo::set_key(Origin::signed(2), 4)); - System::assert_has_event(TestEvent::Sudo(Event::KeyChanged(2))); + assert_ok!(Sudo::set_key(RuntimeOrigin::signed(2), 4)); + System::assert_has_event(TestEvent::Sudo(Event::KeyChanged { old_sudoer: Some(2) })); }); } @@ -134,18 +168,27 @@ fn set_key_emits_events_correctly() { fn sudo_as_basics() { new_test_ext(1).execute_with(|| { // A privileged function will not work when passed to `sudo_as`. - let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log { i: 42, weight: 1_000 })); - assert_ok!(Sudo::sudo_as(Origin::signed(1), 2, call)); + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_ref_time(1_000), + })); + assert_ok!(Sudo::sudo_as(RuntimeOrigin::signed(1), 2, call)); assert!(Logger::i32_log().is_empty()); assert!(Logger::account_log().is_empty()); // A non-privileged function should not work when called with a non-root `key`. - let call = Box::new(Call::Logger(LoggerCall::non_privileged_log { i: 42, weight: 1 })); - assert_noop!(Sudo::sudo_as(Origin::signed(3), 2, call), Error::::RequireSudo); + let call = Box::new(RuntimeCall::Logger(LoggerCall::non_privileged_log { + i: 42, + weight: Weight::from_ref_time(1), + })); + assert_noop!(Sudo::sudo_as(RuntimeOrigin::signed(3), 2, call), Error::::RequireSudo); // A non-privileged function will work when passed to `sudo_as` with the root `key`. - let call = Box::new(Call::Logger(LoggerCall::non_privileged_log { i: 42, weight: 1 })); - assert_ok!(Sudo::sudo_as(Origin::signed(1), 2, call)); + let call = Box::new(RuntimeCall::Logger(LoggerCall::non_privileged_log { + i: 42, + weight: Weight::from_ref_time(1), + })); + assert_ok!(Sudo::sudo_as(RuntimeOrigin::signed(1), 2, call)); assert_eq!(Logger::i32_log(), vec![42i32]); // The correct user makes the call within `sudo_as`. assert_eq!(Logger::account_log(), vec![2]); @@ -159,8 +202,11 @@ fn sudo_as_emits_events_correctly() { System::set_block_number(1); // A non-privileged function will work when passed to `sudo_as` with the root `key`. - let call = Box::new(Call::Logger(LoggerCall::non_privileged_log { i: 42, weight: 1 })); - assert_ok!(Sudo::sudo_as(Origin::signed(1), 2, call)); - System::assert_has_event(TestEvent::Sudo(Event::SudoAsDone(Ok(())))); + let call = Box::new(RuntimeCall::Logger(LoggerCall::non_privileged_log { + i: 42, + weight: Weight::from_ref_time(1), + })); + assert_ok!(Sudo::sudo_as(RuntimeOrigin::signed(1), 2, call)); + System::assert_has_event(TestEvent::Sudo(Event::SudoAsDone { sudo_result: Ok(()) })); }); } diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index b09ed65a114dc..4945b5ab915f9 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -2,9 +2,9 @@ name = "frame-support" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Support code for the runtime." readme = "README.md" @@ -13,38 +13,46 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.126", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -frame-metadata = { version = "14.0.0", default-features = false, features = ["v14"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-tracing = { version = "4.0.0-dev", default-features = false, path = "../../primitives/tracing" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../../primitives/arithmetic" } +serde = { version = "1.0.136", optional = true, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-metadata = { version = "15.0.0", default-features = false, features = ["v14"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-tracing = { version = "6.0.0", default-features = false, path = "../../primitives/tracing" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } +tt-call = "1.0.8" frame-support-procedural = { version = "4.0.0-dev", default-features = false, path = "./procedural" } paste = "1.0" once_cell = { version = "1", default-features = false, optional = true } -sp-state-machine = { version = "0.10.0-dev", optional = true, path = "../../primitives/state-machine" } +sp-state-machine = { version = "0.13.0", default-features = false, optional = true, path = "../../primitives/state-machine" } bitflags = "1.3" -impl-trait-for-tuples = "0.2.1" -smallvec = "1.4.1" -log = { version = "0.4.14", default-features = false } +impl-trait-for-tuples = "0.2.2" +smallvec = "1.8.0" +log = { version = "0.4.17", default-features = false } +sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } +k256 = { version = "0.11.5", default-features = false, features = ["ecdsa"] } [dev-dependencies] +serde_json = "1.0.85" assert_matches = "1.3.0" -pretty_assertions = "0.6.1" +pretty_assertions = "1.2.1" frame-system = { version = "4.0.0-dev", path = "../system" } -parity-util-mem = { version = "0.10.0", default-features = false, features = ["primitive-types"] } [features] default = ["std"] std = [ + "sp-core/std", + "k256/std", "once_cell", "serde", + "sp-api/std", "sp-io/std", "codec/std", "scale-info/std", @@ -55,9 +63,20 @@ std = [ "frame-metadata/std", "sp-inherents/std", "sp-staking/std", - "sp-state-machine", + "sp-state-machine/std", + "sp-weights/std", "frame-support-procedural/std", "log/std", ] runtime-benchmarks = [] try-runtime = [] +# By default some types have documentation, `no-metadata-docs` allows to reduce the documentation +# in the metadata. +no-metadata-docs = ["frame-support-procedural/no-metadata-docs"] +# By default some types have documentation, `full-metadata-docs` allows to add documentation to +# more types in the metadata. +full-metadata-docs = ["scale-info/docs"] +# Generate impl-trait for tuples with the given number of tuples. Will be needed as the number of +# pallets in a runtime grows. Does increase the compile time! +tuples-96 = [] +tuples-128 = [] diff --git a/frame/support/procedural/Cargo.toml b/frame/support/procedural/Cargo.toml index e1ff6dcf39b7e..06b8056aff982 100644 --- a/frame/support/procedural/Cargo.toml +++ b/frame/support/procedural/Cargo.toml @@ -2,9 +2,9 @@ name = "frame-support-procedural" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Proc macro of Support code for the runtime." @@ -15,12 +15,15 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -frame-support-procedural-tools = { version = "4.0.0-dev", path = "./tools" } -proc-macro2 = "1.0.29" -quote = "1.0.3" Inflector = "0.11.4" -syn = { version = "1.0.58", features = ["full"] } +cfg-expr = "0.10.3" +itertools = "0.10.3" +proc-macro2 = "1.0.37" +quote = "1.0.10" +syn = { version = "1.0.98", features = ["full"] } +frame-support-procedural-tools = { version = "4.0.0-dev", path = "./tools" } [features] default = ["std"] std = [] +no-metadata-docs = [] diff --git a/frame/support/procedural/src/clone_no_bound.rs b/frame/support/procedural/src/clone_no_bound.rs index 747900fd023f6..bd2741c0d47ab 100644 --- a/frame/support/procedural/src/clone_no_bound.rs +++ b/frame/support/procedural/src/clone_no_bound.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/construct_runtime/expand/call.rs b/frame/support/procedural/src/construct_runtime/expand/call.rs index 2532a680e21be..8f07448f5785e 100644 --- a/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,10 +18,12 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::Ident; pub fn expand_outer_dispatch( runtime: &Ident, + system_pallet: &Pallet, pallet_decls: &[Pallet], scrate: &TokenStream, ) -> TokenStream { @@ -29,6 +31,8 @@ pub fn expand_outer_dispatch( let mut variant_patterns = Vec::new(); let mut query_call_part_macros = Vec::new(); let mut pallet_names = Vec::new(); + let mut pallet_attrs = Vec::new(); + let system_path = &system_pallet.path; let pallets_with_call = pallet_decls.iter().filter(|decl| decl.exists_part("Call")); @@ -36,12 +40,24 @@ pub fn expand_outer_dispatch( let name = &pallet_declaration.name; let path = &pallet_declaration.path; let index = pallet_declaration.index; + let attr = + pallet_declaration.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); - variant_defs.extend( - quote!(#[codec(index = #index)] #name( #scrate::dispatch::CallableCallFor<#name, #runtime> ),), - ); - variant_patterns.push(quote!(Call::#name(call))); + variant_defs.extend(quote! { + #attr + #[codec(index = #index)] + #name( #scrate::dispatch::CallableCallFor<#name, #runtime> ), + }); + variant_patterns.push(quote!(RuntimeCall::#name(call))); pallet_names.push(name); + pallet_attrs.push(attr); query_call_part_macros.push(quote! { #path::__substrate_call_check::is_call_part_defined!(#name); }); @@ -57,21 +73,65 @@ pub fn expand_outer_dispatch( #scrate::scale_info::TypeInfo, #scrate::RuntimeDebug, )] - pub enum Call { + pub enum RuntimeCall { #variant_defs } - impl #scrate::dispatch::GetDispatchInfo for Call { + #[cfg(test)] + impl RuntimeCall { + /// Return a list of the module names together with their size in memory. + pub const fn sizes() -> &'static [( &'static str, usize )] { + use #scrate::dispatch::Callable; + use core::mem::size_of; + &[#( + #pallet_attrs + ( + stringify!(#pallet_names), + size_of::< <#pallet_names as Callable<#runtime>>::RuntimeCall >(), + ), + )*] + } + + /// Panics with diagnostic information if the size is greater than the given `limit`. + pub fn assert_size_under(limit: usize) { + let size = core::mem::size_of::(); + let call_oversize = size > limit; + if call_oversize { + println!("Size of `Call` is {} bytes (provided limit is {} bytes)", size, limit); + let mut sizes = Self::sizes().to_vec(); + sizes.sort_by_key(|x| -(x.1 as isize)); + for (i, &(name, size)) in sizes.iter().enumerate().take(5) { + println!("Offender #{}: {} at {} bytes", i + 1, name, size); + } + if let Some((_, next_size)) = sizes.get(5) { + println!("{} others of size {} bytes or less", sizes.len() - 5, next_size); + } + panic!( + "Size of `Call` is more than limit; use `Box` on complex parameter types to reduce the + size of `Call`. + If the limit is too strong, maybe consider providing a higher limit." + ); + } + } + } + impl #scrate::dispatch::GetDispatchInfo for RuntimeCall { fn get_dispatch_info(&self) -> #scrate::dispatch::DispatchInfo { match self { - #( #variant_patterns => call.get_dispatch_info(), )* + #( + #pallet_attrs + #variant_patterns => call.get_dispatch_info(), + )* } } } - impl #scrate::dispatch::GetCallMetadata for Call { + // Deprecated, but will warn when used + #[allow(deprecated)] + impl #scrate::weights::GetDispatchInfo for RuntimeCall {} + impl #scrate::dispatch::GetCallMetadata for RuntimeCall { fn get_call_metadata(&self) -> #scrate::dispatch::CallMetadata { use #scrate::dispatch::GetCallName; match self { #( + #pallet_attrs #variant_patterns => { let function_name = call.get_call_name(); let pallet_name = stringify!(#pallet_names); @@ -83,6 +143,7 @@ pub fn expand_outer_dispatch( fn get_module_names() -> &'static [&'static str] { &[#( + #pallet_attrs stringify!(#pallet_names), )*] } @@ -91,32 +152,36 @@ pub fn expand_outer_dispatch( use #scrate::dispatch::{Callable, GetCallName}; match module { #( + #pallet_attrs stringify!(#pallet_names) => - <<#pallet_names as Callable<#runtime>>::Call + <<#pallet_names as Callable<#runtime>>::RuntimeCall as GetCallName>::get_call_names(), )* _ => unreachable!(), } } } - impl #scrate::dispatch::Dispatchable for Call { - type Origin = Origin; - type Config = Call; - type Info = #scrate::weights::DispatchInfo; - type PostInfo = #scrate::weights::PostDispatchInfo; - fn dispatch(self, origin: Origin) -> #scrate::dispatch::DispatchResultWithPostInfo { - if !::filter_call(&origin, &self) { - return #scrate::sp_std::result::Result::Err(#scrate::dispatch::DispatchError::BadOrigin.into()); + impl #scrate::dispatch::Dispatchable for RuntimeCall { + type RuntimeOrigin = RuntimeOrigin; + type Config = RuntimeCall; + type Info = #scrate::dispatch::DispatchInfo; + type PostInfo = #scrate::dispatch::PostDispatchInfo; + fn dispatch(self, origin: RuntimeOrigin) -> #scrate::dispatch::DispatchResultWithPostInfo { + if !::filter_call(&origin, &self) { + return #scrate::sp_std::result::Result::Err( + #system_path::Error::<#runtime>::CallFiltered.into() + ); } #scrate::traits::UnfilteredDispatchable::dispatch_bypass_filter(self, origin) } } - impl #scrate::traits::UnfilteredDispatchable for Call { - type Origin = Origin; - fn dispatch_bypass_filter(self, origin: Origin) -> #scrate::dispatch::DispatchResultWithPostInfo { + impl #scrate::traits::UnfilteredDispatchable for RuntimeCall { + type RuntimeOrigin = RuntimeOrigin; + fn dispatch_bypass_filter(self, origin: RuntimeOrigin) -> #scrate::dispatch::DispatchResultWithPostInfo { match self { #( + #pallet_attrs #variant_patterns => #scrate::traits::UnfilteredDispatchable::dispatch_bypass_filter(call, origin), )* @@ -125,7 +190,8 @@ pub fn expand_outer_dispatch( } #( - impl #scrate::traits::IsSubType<#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> for Call { + #pallet_attrs + impl #scrate::traits::IsSubType<#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> for RuntimeCall { #[allow(unreachable_patterns)] fn is_sub_type(&self) -> Option<&#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> { match self { @@ -136,7 +202,8 @@ pub fn expand_outer_dispatch( } } - impl From<#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> for Call { + #pallet_attrs + impl From<#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> for RuntimeCall { fn from(call: #scrate::dispatch::CallableCallFor<#pallet_names, #runtime>) -> Self { #variant_patterns } diff --git a/frame/support/procedural/src/construct_runtime/expand/config.rs b/frame/support/procedural/src/construct_runtime/expand/config.rs index 5e1b9d94700e6..9b731a5825a3c 100644 --- a/frame/support/procedural/src/construct_runtime/expand/config.rs +++ b/frame/support/procedural/src/construct_runtime/expand/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,7 @@ use crate::construct_runtime::Pallet; use inflector::Inflector; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; +use std::str::FromStr; use syn::Ident; pub fn expand_outer_config( @@ -40,15 +41,19 @@ pub fn expand_outer_config( let field_name = &Ident::new(&pallet_name.to_string().to_snake_case(), decl.name.span()); let part_is_generic = !pallet_entry.generics.params.is_empty(); + let attr = &decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); - types.extend(expand_config_types(runtime, decl, &config, part_is_generic)); - fields.extend(quote!(pub #field_name: #config,)); - build_storage_calls.extend(expand_config_build_storage_call( - scrate, - runtime, - decl, - &field_name, - )); + types.extend(expand_config_types(attr, runtime, decl, &config, part_is_generic)); + fields.extend(quote!(#attr pub #field_name: #config,)); + build_storage_calls + .extend(expand_config_build_storage_call(scrate, attr, runtime, decl, field_name)); query_genesis_config_part_macros.push(quote! { #path::__substrate_genesis_config_check::is_genesis_config_defined!(#pallet_name); #[cfg(feature = "std")] @@ -92,6 +97,7 @@ pub fn expand_outer_config( } fn expand_config_types( + attr: &TokenStream, runtime: &Ident, decl: &Pallet, config: &Ident, @@ -101,14 +107,17 @@ fn expand_config_types( match (decl.instance.as_ref(), part_is_generic) { (Some(inst), true) => quote! { + #attr #[cfg(any(feature = "std", test))] pub type #config = #path::GenesisConfig<#runtime, #path::#inst>; }, (None, true) => quote! { + #attr #[cfg(any(feature = "std", test))] pub type #config = #path::GenesisConfig<#runtime>; }, (_, false) => quote! { + #attr #[cfg(any(feature = "std", test))] pub type #config = #path::GenesisConfig; }, @@ -117,6 +126,7 @@ fn expand_config_types( fn expand_config_build_storage_call( scrate: &TokenStream, + attr: &TokenStream, runtime: &Ident, decl: &Pallet, field_name: &Ident, @@ -129,6 +139,7 @@ fn expand_config_build_storage_call( }; quote! { + #attr #scrate::sp_runtime::BuildModuleGenesisStorage:: <#runtime, #instance>::build_module_genesis_storage(&self.#field_name, storage)?; } diff --git a/frame/support/procedural/src/construct_runtime/expand/event.rs b/frame/support/procedural/src/construct_runtime/expand/event.rs index 798646bf27334..b11fcef1bfd53 100644 --- a/frame/support/procedural/src/construct_runtime/expand/event.rs +++ b/frame/support/procedural/src/construct_runtime/expand/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::{Generics, Ident}; pub fn expand_outer_event( @@ -79,7 +80,7 @@ pub fn expand_outer_event( #scrate::RuntimeDebug, )] #[allow(non_camel_case_types)] - pub enum Event { + pub enum RuntimeEvent { #event_variants } @@ -97,19 +98,35 @@ fn expand_event_variant( let path = &pallet.path; let variant_name = &pallet.name; let part_is_generic = !generics.params.is_empty(); + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); match instance { - Some(inst) if part_is_generic => { - quote!(#[codec(index = #index)] #variant_name(#path::Event<#runtime, #path::#inst>),) + Some(inst) if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Event<#runtime, #path::#inst>), }, - Some(inst) => { - quote!(#[codec(index = #index)] #variant_name(#path::Event<#path::#inst>),) + Some(inst) => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Event<#path::#inst>), }, - None if part_is_generic => { - quote!(#[codec(index = #index)] #variant_name(#path::Event<#runtime>),) + None if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Event<#runtime>), }, - None => { - quote!(#[codec(index = #index)] #variant_name(#path::Event),) + None => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Event), }, } } @@ -120,14 +137,24 @@ fn expand_event_conversion( pallet_event: &TokenStream, ) -> TokenStream { let variant_name = &pallet.name; + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); quote! { - impl From<#pallet_event> for Event { + #attr + impl From<#pallet_event> for RuntimeEvent { fn from(x: #pallet_event) -> Self { - Event::#variant_name(x) + RuntimeEvent::#variant_name(x) } } - impl #scrate::sp_std::convert::TryInto<#pallet_event> for Event { + #attr + impl TryInto<#pallet_event> for RuntimeEvent { type Error = (); fn try_into(self) -> #scrate::sp_std::result::Result<#pallet_event, Self::Error> { diff --git a/frame/support/procedural/src/construct_runtime/expand/inherent.rs b/frame/support/procedural/src/construct_runtime/expand/inherent.rs index fd30416782687..599b34ba87241 100644 --- a/frame/support/procedural/src/construct_runtime/expand/inherent.rs +++ b/frame/support/procedural/src/construct_runtime/expand/inherent.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::{Ident, TypePath}; pub fn expand_outer_inherent( @@ -28,14 +29,24 @@ pub fn expand_outer_inherent( scrate: &TokenStream, ) -> TokenStream { let mut pallet_names = Vec::new(); + let mut pallet_attrs = Vec::new(); let mut query_inherent_part_macros = Vec::new(); for pallet_decl in pallet_decls { if pallet_decl.exists_part("Inherent") { let name = &pallet_decl.name; let path = &pallet_decl.path; + let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); pallet_names.push(name); + pallet_attrs.push(attr); query_inherent_part_macros.push(quote! { #path::__substrate_inherent_check::is_inherent_part_defined!(#name); }); @@ -60,6 +71,7 @@ pub fn expand_outer_inherent( let mut inherents = Vec::new(); #( + #pallet_attrs if let Some(inherent) = #pallet_names::create_inherent(self) { let inherent = <#unchecked_extrinsic as #scrate::inherent::Extrinsic>::new( inherent.into(), @@ -90,22 +102,25 @@ pub fn expand_outer_inherent( let mut is_inherent = false; - #({ - let call = <#unchecked_extrinsic as ExtrinsicCall>::call(xt); - if let Some(call) = IsSubType::<_>::is_sub_type(call) { - if #pallet_names::is_inherent(call) { - is_inherent = true; - if let Err(e) = #pallet_names::check_inherent(call, self) { - result.put_error( - #pallet_names::INHERENT_IDENTIFIER, &e - ).expect("There is only one fatal error; qed"); - if e.is_fatal_error() { - return result; + #( + #pallet_attrs + { + let call = <#unchecked_extrinsic as ExtrinsicCall>::call(xt); + if let Some(call) = IsSubType::<_>::is_sub_type(call) { + if #pallet_names::is_inherent(call) { + is_inherent = true; + if let Err(e) = #pallet_names::check_inherent(call, self) { + result.put_error( + #pallet_names::INHERENT_IDENTIFIER, &e + ).expect("There is only one fatal error; qed"); + if e.is_fatal_error() { + return result; + } } } } } - })* + )* // Inherents are before any other extrinsics. // No module marked it as inherent thus it is not. @@ -115,6 +130,7 @@ pub fn expand_outer_inherent( } #( + #pallet_attrs match #pallet_names::is_inherent_required(self) { Ok(Some(e)) => { let found = block.extrinsics().iter().any(|xt| { @@ -177,14 +193,17 @@ pub fn expand_outer_inherent( false } else { let mut is_inherent = false; - #({ - let call = <#unchecked_extrinsic as ExtrinsicCall>::call(xt); - if let Some(call) = IsSubType::<_>::is_sub_type(call) { - if #pallet_names::is_inherent(&call) { - is_inherent = true; + #( + #pallet_attrs + { + let call = <#unchecked_extrinsic as ExtrinsicCall>::call(xt); + if let Some(call) = IsSubType::<_>::is_sub_type(call) { + if #pallet_names::is_inherent(&call) { + is_inherent = true; + } } } - })* + )* is_inherent }; diff --git a/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/frame/support/procedural/src/construct_runtime/expand/metadata.rs index c8445e0bbc255..ec90a0d30f98b 100644 --- a/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::{Ident, TypePath}; pub fn expand_runtime_metadata( @@ -47,8 +48,17 @@ pub fn expand_runtime_metadata( let event = expand_pallet_metadata_events(&filtered_names, runtime, scrate, decl); let constants = expand_pallet_metadata_constants(runtime, decl); let errors = expand_pallet_metadata_errors(runtime, decl); + let attr = decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); quote! { + #attr #scrate::metadata::PalletMetadata { name: stringify!(#name), index: #index, diff --git a/frame/support/procedural/src/construct_runtime/expand/mod.rs b/frame/support/procedural/src/construct_runtime/expand/mod.rs index cf8b5eef8d105..6c92d2c3444ec 100644 --- a/frame/support/procedural/src/construct_runtime/expand/mod.rs +++ b/frame/support/procedural/src/construct_runtime/expand/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/construct_runtime/expand/origin.rs b/frame/support/procedural/src/construct_runtime/expand/origin.rs index a65ad78527ff7..1551d85ea4c96 100644 --- a/frame/support/procedural/src/construct_runtime/expand/origin.rs +++ b/frame/support/procedural/src/construct_runtime/expand/origin.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,23 +18,15 @@ use crate::construct_runtime::{Pallet, SYSTEM_PALLET_NAME}; use proc_macro2::TokenStream; use quote::quote; -use syn::{token, Generics, Ident}; +use std::str::FromStr; +use syn::{Generics, Ident}; pub fn expand_outer_origin( runtime: &Ident, + system_pallet: &Pallet, pallets: &[Pallet], - pallets_token: token::Brace, scrate: &TokenStream, ) -> syn::Result { - let system_pallet = - pallets.iter().find(|decl| decl.name == SYSTEM_PALLET_NAME).ok_or_else(|| { - syn::Error::new( - pallets_token.span, - "`System` pallet declaration is missing. \ - Please add this line: `System: frame_system::{Pallet, Call, Storage, Config, Event},`", - ) - })?; - let mut caller_variants = TokenStream::new(); let mut pallet_conversions = TokenStream::new(); let mut query_origin_part_macros = Vec::new(); @@ -77,21 +69,47 @@ pub fn expand_outer_origin( } let system_path = &system_pallet.path; + let system_index = system_pallet.index; + let system_path_name = system_path.module_name(); + + let doc_string = get_intra_doc_string( + "Origin is always created with the base filter configured in", + &system_path_name, + ); + + let doc_string_none_origin = + get_intra_doc_string("Create with system none origin and", &system_path_name); + + let doc_string_root_origin = + get_intra_doc_string("Create with system root origin and", &system_path_name); + + let doc_string_signed_origin = + get_intra_doc_string("Create with system signed origin and", &system_path_name); + + let doc_string_runtime_origin = + get_intra_doc_string("Convert to runtime origin, using as filter:", &system_path_name); + + let doc_string_runtime_origin_with_caller = get_intra_doc_string( + "Convert to runtime origin with caller being system signed or none and use filter", + &system_path_name, + ); + Ok(quote! { #( #query_origin_part_macros )* - // WARNING: All instance must hold the filter `frame_system::Config::BaseCallFilter`, except - // when caller is system Root. One can use `OriginTrait::reset_filter` to do so. + /// The runtime origin type representing the origin of a call. + /// + #[doc = #doc_string] #[derive(Clone)] - pub struct Origin { + pub struct RuntimeOrigin { caller: OriginCaller, - filter: #scrate::sp_std::rc::Rc::Call) -> bool>>, + filter: #scrate::sp_std::rc::Rc::RuntimeCall) -> bool>>, } #[cfg(not(feature = "std"))] - impl #scrate::sp_std::fmt::Debug for Origin { + impl #scrate::sp_std::fmt::Debug for RuntimeOrigin { fn fmt( &self, fmt: &mut #scrate::sp_std::fmt::Formatter, @@ -101,7 +119,7 @@ pub fn expand_outer_origin( } #[cfg(feature = "std")] - impl #scrate::sp_std::fmt::Debug for Origin { + impl #scrate::sp_std::fmt::Debug for RuntimeOrigin { fn fmt( &self, fmt: &mut #scrate::sp_std::fmt::Formatter, @@ -113,8 +131,8 @@ pub fn expand_outer_origin( } } - impl #scrate::traits::OriginTrait for Origin { - type Call = <#runtime as #system_path::Config>::Call; + impl #scrate::traits::OriginTrait for RuntimeOrigin { + type Call = <#runtime as #system_path::Config>::RuntimeCall; type PalletsOrigin = OriginCaller; type AccountId = <#runtime as #system_path::Config>::AccountId; @@ -129,7 +147,7 @@ pub fn expand_outer_origin( fn reset_filter(&mut self) { let filter = < <#runtime as #system_path::Config>::BaseCallFilter - as #scrate::traits::Contains<<#runtime as #system_path::Config>::Call> + as #scrate::traits::Contains<<#runtime as #system_path::Config>::RuntimeCall> >::contains; self.filter = #scrate::sp_std::rc::Rc::new(Box::new(filter)); @@ -140,13 +158,21 @@ pub fn expand_outer_origin( } fn filter_call(&self, call: &Self::Call) -> bool { - (self.filter)(call) + match self.caller { + // Root bypasses all filters + OriginCaller::system(#system_path::Origin::<#runtime>::Root) => true, + _ => (self.filter)(call), + } } fn caller(&self) -> &Self::PalletsOrigin { &self.caller } + fn into_caller(self) -> Self::PalletsOrigin { + self.caller + } + fn try_with_caller( mut self, f: impl FnOnce(Self::PalletsOrigin) -> Result, @@ -157,23 +183,22 @@ pub fn expand_outer_origin( } } - /// Create with system none origin and `frame-system::Config::BaseCallFilter`. fn none() -> Self { #system_path::RawOrigin::None.into() } - /// Create with system root origin and no filter. + fn root() -> Self { #system_path::RawOrigin::Root.into() } - /// Create with system signed origin and `frame-system::Config::BaseCallFilter`. - fn signed(by: <#runtime as #system_path::Config>::AccountId) -> Self { + + fn signed(by: Self::AccountId) -> Self { #system_path::RawOrigin::Signed(by).into() } } #[derive( Clone, PartialEq, Eq, #scrate::RuntimeDebug, #scrate::codec::Encode, - #scrate::codec::Decode, #scrate::scale_info::TypeInfo, + #scrate::codec::Decode, #scrate::scale_info::TypeInfo, #scrate::codec::MaxEncodedLen, )] #[allow(non_camel_case_types)] pub enum OriginCaller { @@ -186,18 +211,20 @@ pub fn expand_outer_origin( // For backwards compatibility and ease of accessing these functions. #[allow(dead_code)] - impl Origin { - /// Create with system none origin and `frame-system::Config::BaseCallFilter`. + impl RuntimeOrigin { + #[doc = #doc_string_none_origin] pub fn none() -> Self { - ::none() + ::none() } - /// Create with system root origin and no filter. + + #[doc = #doc_string_root_origin] pub fn root() -> Self { - ::root() + ::root() } - /// Create with system signed origin and `frame-system::Config::BaseCallFilter`. + + #[doc = #doc_string_signed_origin] pub fn signed(by: <#runtime as #system_path::Config>::AccountId) -> Self { - ::signed(by) + ::signed(by) } } @@ -207,7 +234,22 @@ pub fn expand_outer_origin( } } - impl #scrate::sp_std::convert::TryFrom for #system_path::Origin<#runtime> { + impl #scrate::traits::CallerTrait<<#runtime as #system_path::Config>::AccountId> for OriginCaller { + fn into_system(self) -> Option<#system_path::RawOrigin<<#runtime as #system_path::Config>::AccountId>> { + match self { + OriginCaller::system(x) => Some(x), + _ => None, + } + } + fn as_system_ref(&self) -> Option<&#system_path::RawOrigin<<#runtime as #system_path::Config>::AccountId>> { + match &self { + OriginCaller::system(o) => Some(o), + _ => None, + } + } + } + + impl TryFrom for #system_path::Origin<#runtime> { type Error = OriginCaller; fn try_from(x: OriginCaller) -> #scrate::sp_std::result::Result<#system_path::Origin<#runtime>, OriginCaller> @@ -220,35 +262,31 @@ pub fn expand_outer_origin( } } - impl From<#system_path::Origin<#runtime>> for Origin { - /// Convert to runtime origin: - /// * root origin is built with no filter - /// * others use `frame-system::Config::BaseCallFilter` + impl From<#system_path::Origin<#runtime>> for RuntimeOrigin { + + #[doc = #doc_string_runtime_origin] fn from(x: #system_path::Origin<#runtime>) -> Self { let o: OriginCaller = x.into(); o.into() } } - impl From for Origin { + impl From for RuntimeOrigin { fn from(x: OriginCaller) -> Self { - let mut o = Origin { + let mut o = RuntimeOrigin { caller: x, filter: #scrate::sp_std::rc::Rc::new(Box::new(|_| true)), }; - // Root has no filter - if !matches!(o.caller, OriginCaller::system(#system_path::Origin::<#runtime>::Root)) { - #scrate::traits::OriginTrait::reset_filter(&mut o); - } + #scrate::traits::OriginTrait::reset_filter(&mut o); o } } - impl From for #scrate::sp_std::result::Result<#system_path::Origin<#runtime>, Origin> { + impl From for #scrate::sp_std::result::Result<#system_path::Origin<#runtime>, RuntimeOrigin> { /// NOTE: converting to pallet origin loses the origin filter information. - fn from(val: Origin) -> Self { + fn from(val: RuntimeOrigin) -> Self { if let OriginCaller::system(l) = val.caller { Ok(l) } else { @@ -256,9 +294,8 @@ pub fn expand_outer_origin( } } } - impl From::AccountId>> for Origin { - /// Convert to runtime origin with caller being system signed or none and use filter - /// `frame-system::Config::BaseCallFilter`. + impl From::AccountId>> for RuntimeOrigin { + #[doc = #doc_string_runtime_origin_with_caller] fn from(x: Option<<#runtime as #system_path::Config>::AccountId>) -> Self { <#system_path::Origin<#runtime>>::from(x).into() } @@ -278,19 +315,35 @@ fn expand_origin_caller_variant( let part_is_generic = !generics.params.is_empty(); let variant_name = &pallet.name; let path = &pallet.path; + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); match instance { - Some(inst) if part_is_generic => { - quote!(#[codec(index = #index)] #variant_name(#path::Origin<#runtime, #path::#inst>),) + Some(inst) if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Origin<#runtime, #path::#inst>), }, - Some(inst) => { - quote!(#[codec(index = #index)] #variant_name(#path::Origin<#path::#inst>),) + Some(inst) => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Origin<#path::#inst>), }, - None if part_is_generic => { - quote!(#[codec(index = #index)] #variant_name(#path::Origin<#runtime>),) + None if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Origin<#runtime>), }, - None => { - quote!(#[codec(index = #index)] #variant_name(#path::Origin),) + None => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Origin), }, } } @@ -313,24 +366,37 @@ fn expand_origin_pallet_conversions( None => quote!(#path::Origin), }; + let doc_string = get_intra_doc_string(" Convert to runtime origin using", &path.module_name()); + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); + quote! { + #attr impl From<#pallet_origin> for OriginCaller { fn from(x: #pallet_origin) -> Self { OriginCaller::#variant_name(x) } } - impl From<#pallet_origin> for Origin { - /// Convert to runtime origin using `frame-system::Config::BaseCallFilter`. + #attr + impl From<#pallet_origin> for RuntimeOrigin { + #[doc = #doc_string] fn from(x: #pallet_origin) -> Self { let x: OriginCaller = x.into(); x.into() } } - impl From for #scrate::sp_std::result::Result<#pallet_origin, Origin> { + #attr + impl From for #scrate::sp_std::result::Result<#pallet_origin, RuntimeOrigin> { /// NOTE: converting to pallet origin loses the origin filter information. - fn from(val: Origin) -> Self { + fn from(val: RuntimeOrigin) -> Self { if let OriginCaller::#variant_name(l) = val.caller { Ok(l) } else { @@ -339,7 +405,8 @@ fn expand_origin_pallet_conversions( } } - impl #scrate::sp_std::convert::TryFrom for #pallet_origin { + #attr + impl TryFrom for #pallet_origin { type Error = OriginCaller; fn try_from( x: OriginCaller, @@ -353,3 +420,8 @@ fn expand_origin_pallet_conversions( } } } + +// Get the actual documentation using the doc information and system path name +fn get_intra_doc_string(doc_info: &str, system_path_name: &String) -> String { + format!(" {} [`{}::Config::BaseCallFilter`].", doc_info, system_path_name) +} diff --git a/frame/support/procedural/src/construct_runtime/expand/unsigned.rs b/frame/support/procedural/src/construct_runtime/expand/unsigned.rs index d51792dd4a8d5..1d528779423a7 100644 --- a/frame/support/procedural/src/construct_runtime/expand/unsigned.rs +++ b/frame/support/procedural/src/construct_runtime/expand/unsigned.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::Ident; pub fn expand_outer_validate_unsigned( @@ -26,14 +27,24 @@ pub fn expand_outer_validate_unsigned( scrate: &TokenStream, ) -> TokenStream { let mut pallet_names = Vec::new(); + let mut pallet_attrs = Vec::new(); let mut query_validate_unsigned_part_macros = Vec::new(); for pallet_decl in pallet_decls { if pallet_decl.exists_part("ValidateUnsigned") { let name = &pallet_decl.name; let path = &pallet_decl.path; + let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); pallet_names.push(name); + pallet_attrs.push(attr); query_validate_unsigned_part_macros.push(quote! { #path::__substrate_validate_unsigned_check::is_validate_unsigned_part_defined!(#name); }); @@ -44,12 +55,15 @@ pub fn expand_outer_validate_unsigned( #( #query_validate_unsigned_part_macros )* impl #scrate::unsigned::ValidateUnsigned for #runtime { - type Call = Call; + type Call = RuntimeCall; fn pre_dispatch(call: &Self::Call) -> Result<(), #scrate::unsigned::TransactionValidityError> { #[allow(unreachable_patterns)] match call { - #( Call::#pallet_names(inner_call) => #pallet_names::pre_dispatch(inner_call), )* + #( + #pallet_attrs + RuntimeCall::#pallet_names(inner_call) => #pallet_names::pre_dispatch(inner_call), + )* // pre-dispatch should not stop inherent extrinsics, validation should prevent // including arbitrary (non-inherent) extrinsics to blocks. _ => Ok(()), @@ -63,7 +77,10 @@ pub fn expand_outer_validate_unsigned( ) -> #scrate::unsigned::TransactionValidity { #[allow(unreachable_patterns)] match call { - #( Call::#pallet_names(inner_call) => #pallet_names::validate_unsigned(source, inner_call), )* + #( + #pallet_attrs + RuntimeCall::#pallet_names(inner_call) => #pallet_names::validate_unsigned(source, inner_call), + )* _ => #scrate::unsigned::UnknownTransaction::NoUnsignedValidator.into(), } } diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 8aacd8f0aa810..9e22037a6782e 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,132 +15,258 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Implementation of `construct_runtime`. +//! +//! `construct_runtime` implementation is recursive and can generate code which will call itself in +//! order to get all the pallet parts for each pallet. +//! +//! Pallets define their parts (`Call`, `Storage`, ..) either explicitly with the syntax +//! `::{Call, ...}` or implicitly. +//! +//! In case a pallet defines its parts implicitly, then the pallet must provide the +//! `tt_default_parts` macro. `construct_rutime` will generate some code which utilizes `tt_call` +//! to call the `tt_default_parts` macro of the pallet. `tt_default_parts` will then return the +//! default pallet parts as input tokens to the `match_and_replace` macro, which ultimately +//! generates a call to `construct_runtime` again, this time with all the pallet parts explicitly +//! defined. +//! +//! E.g. +//! ```ignore +//! construct_runtime!( +//! //... +//! { +//! System: frame_system = 0, // Implicit definition of parts +//! Balances: pallet_balances = 1, // Implicit definition of parts +//! } +//! ); +//! ``` +//! This call has some implicit pallet parts, thus it will expand to: +//! ```ignore +//! frame_support::tt_call! { +//! macro = [{ pallet_balances::tt_default_parts }] +//! ~~> frame_support::match_and_insert! { +//! target = [{ +//! frame_support::tt_call! { +//! macro = [{ frame_system::tt_default_parts }] +//! ~~> frame_support::match_and_insert! { +//! target = [{ +//! construct_runtime!( +//! //... +//! { +//! System: frame_system = 0, +//! Balances: pallet_balances = 1, +//! } +//! ); +//! }] +//! pattern = [{ System: frame_system }] +//! } +//! } +//! }] +//! pattern = [{ Balances: pallet_balances }] +//! } +//! } +//! ``` +//! `tt_default_parts` must be defined. It returns the pallet parts inside some tokens, and +//! then `tt_call` will pipe the returned pallet parts into the input of `match_and_insert`. +//! Thus `match_and_insert` will initially receive the following inputs: +//! ```ignore +//! frame_support::match_and_insert! { +//! target = [{ +//! frame_support::match_and_insert! { +//! target = [{ +//! construct_runtime!( +//! //... +//! { +//! System: frame_system = 0, +//! Balances: pallet_balances = 1, +//! } +//! ) +//! }] +//! pattern = [{ System: frame_system }] +//! tokens = [{ ::{Pallet, Call} }] +//! } +//! }] +//! pattern = [{ Balances: pallet_balances }] +//! tokens = [{ ::{Pallet, Call} }] +//! } +//! ``` +//! After dealing with `pallet_balances`, the inner `match_and_insert` will expand to: +//! ```ignore +//! frame_support::match_and_insert! { +//! target = [{ +//! construct_runtime!( +//! //... +//! { +//! System: frame_system = 0, // Implicit definition of parts +//! Balances: pallet_balances::{Pallet, Call} = 1, // Explicit definition of parts +//! } +//! ) +//! }] +//! pattern = [{ System: frame_system }] +//! tokens = [{ ::{Pallet, Call} }] +//! } +//! ``` +//! Which will then finally expand to the following: +//! ```ignore +//! construct_runtime!( +//! //... +//! { +//! System: frame_system::{Pallet, Call}, +//! Balances: pallet_balances::{Pallet, Call}, +//! } +//! ) +//! ``` +//! This call has no implicit pallet parts, thus it will expand to the runtime construction: +//! ```ignore +//! pub struct Runtime { ... } +//! pub struct Call { ... } +//! impl Call ... +//! pub enum Origin { ... } +//! ... +//! ``` +//! +//! Visualizing the entire flow of `construct_runtime!`, it would look like the following: +//! +//! ```ignore +//! +--------------------+ +---------------------+ +-------------------+ +//! | | | (defined in pallet) | | | +//! | construct_runtime! | --> | tt_default_parts! | --> | match_and_insert! | +//! | w/ no pallet parts | | | | | +//! +--------------------+ +---------------------+ +-------------------+ +//! +//! +--------------------+ +//! | | +//! --> | construct_runtime! | +//! | w/ pallet parts | +//! +--------------------+ +//! ``` + mod expand; mod parse; +use cfg_expr::Predicate; use frame_support_procedural_tools::{ - generate_crate_access, generate_hidden_includes, syn_ext as ext, + generate_crate_access, generate_crate_access_2018, generate_hidden_includes, +}; +use itertools::Itertools; +use parse::{ + ExplicitRuntimeDeclaration, ImplicitRuntimeDeclaration, Pallet, RuntimeDeclaration, + WhereSection, }; -use parse::{PalletDeclaration, PalletPart, PalletPath, RuntimeDefinition, WhereSection}; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use std::collections::HashMap; +use std::{collections::HashSet, str::FromStr}; use syn::{Ident, Result}; /// The fixed name of the system pallet. const SYSTEM_PALLET_NAME: &str = "System"; -/// The complete definition of a pallet with the resulting fixed index. -#[derive(Debug, Clone)] -pub struct Pallet { - pub name: Ident, - pub index: u8, - pub path: PalletPath, - pub instance: Option, - pub pallet_parts: Vec, -} +/// Implementation of `construct_runtime` macro. Either expand to some code which will call +/// `construct_runtime` again, or expand to the final runtime definition. +pub fn construct_runtime(input: TokenStream) -> TokenStream { + let input_copy = input.clone(); + let definition = syn::parse_macro_input!(input as RuntimeDeclaration); -impl Pallet { - /// Get resolved pallet parts - fn pallet_parts(&self) -> &[PalletPart] { - &self.pallet_parts - } + let res = match definition { + RuntimeDeclaration::Implicit(implicit_def) => + construct_runtime_intermediary_expansion(input_copy.into(), implicit_def), + RuntimeDeclaration::Explicit(explicit_decl) => + construct_runtime_final_expansion(explicit_decl), + }; - /// Find matching parts - fn find_part(&self, name: &str) -> Option<&PalletPart> { - self.pallet_parts.iter().find(|part| part.name() == name) - } - - /// Return whether pallet contains part - fn exists_part(&self, name: &str) -> bool { - self.find_part(name).is_some() - } + res.unwrap_or_else(|e| e.to_compile_error()).into() } -/// Convert from the parsed pallet to their final information. -/// Assign index to each pallet using same rules as rust for fieldless enum. -/// I.e. implicit are assigned number incrementedly from last explicit or 0. -fn complete_pallets(decl: impl Iterator) -> syn::Result> { - let mut indices = HashMap::new(); - let mut last_index: Option = None; - let mut names = HashMap::new(); - - decl.map(|pallet| { - let final_index = match pallet.index { - Some(i) => i, - None => last_index.map_or(Some(0), |i| i.checked_add(1)).ok_or_else(|| { - let msg = "Pallet index doesn't fit into u8, index is 256"; - syn::Error::new(pallet.name.span(), msg) - })?, - }; - - last_index = Some(final_index); - - if let Some(used_pallet) = indices.insert(final_index, pallet.name.clone()) { - let msg = format!( - "Pallet indices are conflicting: Both pallets {} and {} are at index {}", - used_pallet, pallet.name, final_index, - ); - let mut err = syn::Error::new(used_pallet.span(), &msg); - err.combine(syn::Error::new(pallet.name.span(), msg)); - return Err(err) - } - - if let Some(used_pallet) = names.insert(pallet.name.clone(), pallet.name.span()) { - let msg = "Two pallets with the same name!"; - - let mut err = syn::Error::new(used_pallet, &msg); - err.combine(syn::Error::new(pallet.name.span(), &msg)); - return Err(err) - } +/// When some pallet have implicit parts definition then the macro will expand into a macro call to +/// `construct_runtime_args` of each pallets, see root documentation. +fn construct_runtime_intermediary_expansion( + input: TokenStream2, + definition: ImplicitRuntimeDeclaration, +) -> Result { + let frame_support = generate_crate_access_2018("frame-support")?; + let mut expansion = quote::quote!( + #frame_support::construct_runtime! { #input } + ); + for pallet in definition.pallets.iter().filter(|pallet| pallet.pallet_parts.is_none()) { + let pallet_path = &pallet.path; + let pallet_name = &pallet.name; + let pallet_instance = pallet.instance.as_ref().map(|instance| quote::quote!(::<#instance>)); + expansion = quote::quote!( + #frame_support::tt_call! { + macro = [{ #pallet_path::tt_default_parts }] + frame_support = [{ #frame_support }] + ~~> #frame_support::match_and_insert! { + target = [{ #expansion }] + pattern = [{ #pallet_name: #pallet_path #pallet_instance }] + } + } + ); + } - Ok(Pallet { - name: pallet.name, - index: final_index, - path: pallet.path, - instance: pallet.instance, - pallet_parts: pallet.pallet_parts, - }) - }) - .collect() + Ok(expansion) } -pub fn construct_runtime(input: TokenStream) -> TokenStream { - let definition = syn::parse_macro_input!(input as RuntimeDefinition); - construct_runtime_parsed(definition) - .unwrap_or_else(|e| e.to_compile_error()) - .into() -} - -fn construct_runtime_parsed(definition: RuntimeDefinition) -> Result { - let RuntimeDefinition { +/// All pallets have explicit definition of parts, this will expand to the runtime declaration. +fn construct_runtime_final_expansion( + definition: ExplicitRuntimeDeclaration, +) -> Result { + let ExplicitRuntimeDeclaration { name, - where_section: WhereSection { block, node_block, unchecked_extrinsic, .. }, - pallets: - ext::Braces { content: ext::Punctuated { inner: pallets, .. }, token: pallets_token }, - .. + where_section: WhereSection { block, node_block, unchecked_extrinsic }, + pallets, + pallets_token, } = definition; - let pallets = complete_pallets(pallets.into_iter())?; + let system_pallet = + pallets.iter().find(|decl| decl.name == SYSTEM_PALLET_NAME).ok_or_else(|| { + syn::Error::new( + pallets_token.span, + "`System` pallet declaration is missing. \ + Please add this line: `System: frame_system::{Pallet, Call, Storage, Config, Event},`", + ) + })?; + if !system_pallet.cfg_pattern.is_empty() { + return Err(syn::Error::new( + system_pallet.name.span(), + "`System` pallet declaration is feature gated, please remove any `#[cfg]` attributes", + )) + } + + let features = pallets + .iter() + .filter_map(|decl| { + (!decl.cfg_pattern.is_empty()).then(|| { + decl.cfg_pattern.iter().flat_map(|attr| { + attr.predicates().filter_map(|pred| match pred { + Predicate::Feature(feat) => Some(feat), + Predicate::Test => Some("test"), + _ => None, + }) + }) + }) + }) + .flatten() + .collect::>(); let hidden_crate_name = "construct_runtime"; - let scrate = generate_crate_access(&hidden_crate_name, "frame-support"); - let scrate_decl = generate_hidden_includes(&hidden_crate_name, "frame-support"); + let scrate = generate_crate_access(hidden_crate_name, "frame-support"); + let scrate_decl = generate_hidden_includes(hidden_crate_name, "frame-support"); let outer_event = expand::expand_outer_event(&name, &pallets, &scrate)?; - let outer_origin = expand::expand_outer_origin(&name, &pallets, pallets_token, &scrate)?; - let all_pallets = decl_all_pallets(&name, pallets.iter()); - let pallet_to_index = decl_pallet_runtime_setup(&pallets, &scrate); + let outer_origin = expand::expand_outer_origin(&name, system_pallet, &pallets, &scrate)?; + let all_pallets = decl_all_pallets(&name, pallets.iter(), &features); + let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate); - let dispatch = expand::expand_outer_dispatch(&name, &pallets, &scrate); + let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate); let metadata = expand::expand_runtime_metadata(&name, &pallets, &scrate, &unchecked_extrinsic); let outer_config = expand::expand_outer_config(&name, &pallets, &scrate); let inherent = expand::expand_outer_inherent(&name, &block, &unchecked_extrinsic, &pallets, &scrate); let validate_unsigned = expand::expand_outer_validate_unsigned(&name, &pallets, &scrate); let integrity_test = decl_integrity_test(&scrate); + let static_assertions = decl_static_assertions(&name, &pallets, &scrate); let res = quote!( #scrate_decl @@ -182,6 +308,8 @@ fn construct_runtime_parsed(definition: RuntimeDefinition) -> Result Result( runtime: &'a Ident, pallet_declarations: impl Iterator, + features: &HashSet<&str>, ) -> TokenStream2 { let mut types = TokenStream2::new(); - let mut names = Vec::new(); + + // Every feature set to the pallet names that should be included by this feature set. + let mut features_to_names = features + .iter() + .map(|f| *f) + .powerset() + .map(|feat| (HashSet::from_iter(feat), Vec::new())) + .collect::, Vec<_>)>>(); + for pallet_declaration in pallet_declarations { let type_name = &pallet_declaration.name; let pallet = &pallet_declaration.path; let mut generics = vec![quote!(#runtime)]; generics.extend(pallet_declaration.instance.iter().map(|name| quote!(#pallet::#name))); + let mut attrs = Vec::new(); + for cfg in &pallet_declaration.cfg_pattern { + let feat = format!("#[cfg({})]\n", cfg.original()); + attrs.extend(TokenStream2::from_str(&feat).expect("was parsed successfully; qed")); + } let type_decl = quote!( + #(#attrs)* pub type #type_name = #pallet::Pallet <#(#generics),*>; ); types.extend(type_decl); - names.push(&pallet_declaration.name); + + if pallet_declaration.cfg_pattern.is_empty() { + for (_, names) in features_to_names.iter_mut() { + names.push(&pallet_declaration.name); + } + } else { + for (feature_set, names) in &mut features_to_names { + // Rust tidbit: if we have multiple `#[cfg]` feature on the same item, then the + // predicates listed in all `#[cfg]` attributes are effectively joined by `and()`, + // meaning that all of them must match in order to activate the item + let is_feature_active = pallet_declaration.cfg_pattern.iter().all(|expr| { + expr.eval(|pred| match pred { + Predicate::Feature(f) => feature_set.contains(f), + Predicate::Test => feature_set.contains(&"test"), + _ => false, + }) + }); + + if is_feature_active { + names.push(&pallet_declaration.name); + } + } + } } - // Make nested tuple structure like (((Babe, Consensus), Grandpa), ...) - // But ignore the system pallet. - let all_pallets = names - .iter() - .filter(|n| **n != SYSTEM_PALLET_NAME) - .fold(TokenStream2::default(), |combined, name| quote!((#name, #combined))); - let all_pallets_with_system = names + // All possible features. This will be used below for the empty feature set. + let mut all_features = features_to_names .iter() - .fold(TokenStream2::default(), |combined, name| quote!((#name, #combined))); + .flat_map(|f| f.0.iter().cloned()) + .collect::>(); + let attribute_to_names = features_to_names + .into_iter() + .map(|(mut features, names)| { + // If this is the empty feature set, it needs to be changed to negate all available + // features. So, we ensure that there is some type declared when all features are not + // enabled. + if features.is_empty() { + let test_cfg = all_features.remove("test").then_some(quote!(test)).into_iter(); + let features = all_features.iter(); + let attr = quote!(#[cfg(all( #(not(#test_cfg)),* #(not(feature = #features)),* ))]); + + (attr, names) + } else { + let test_cfg = features.remove("test").then_some(quote!(test)).into_iter(); + let disabled_features = all_features.difference(&features); + let features = features.iter(); + let attr = quote!(#[cfg(all( #(#test_cfg,)* #(feature = #features,)* #(not(feature = #disabled_features)),* ))]); + + (attr, names) + } + }) + .collect::>(); + + let all_pallets_without_system = attribute_to_names.iter().map(|(attr, names)| { + let names = names.iter().filter(|n| **n != SYSTEM_PALLET_NAME); + quote! { + #attr + /// All pallets included in the runtime as a nested tuple of types. + /// Excludes the System pallet. + pub type AllPalletsWithoutSystem = ( #(#names,)* ); + } + }); + + let all_pallets_with_system = attribute_to_names.iter().map(|(attr, names)| { + quote! { + #attr + /// All pallets included in the runtime as a nested tuple of types. + pub type AllPalletsWithSystem = ( #(#names,)* ); + } + }); + + let all_pallets_without_system_reversed = attribute_to_names.iter().map(|(attr, names)| { + let names = names.iter().filter(|n| **n != SYSTEM_PALLET_NAME).rev(); + quote! { + #attr + /// All pallets included in the runtime as a nested tuple of types in reversed order. + /// Excludes the System pallet. + #[deprecated(note = "Using reverse pallet orders is deprecated. use only \ + `AllPalletsWithSystem or AllPalletsWithoutSystem`")] + pub type AllPalletsWithoutSystemReversed = ( #(#names,)* ); + } + }); + + let all_pallets_with_system_reversed = attribute_to_names.iter().map(|(attr, names)| { + let names = names.iter().rev(); + quote! { + #attr + /// All pallets included in the runtime as a nested tuple of types in reversed order. + #[deprecated(note = "Using reverse pallet orders is deprecated. use only \ + `AllPalletsWithSystem or AllPalletsWithoutSystem`")] + pub type AllPalletsWithSystemReversed = ( #(#names,)* ); + } + }); + + let all_pallets_reversed_with_system_first = attribute_to_names.iter().map(|(attr, names)| { + let system = quote::format_ident!("{}", SYSTEM_PALLET_NAME); + let names = std::iter::once(&system) + .chain(names.iter().rev().filter(|n| **n != SYSTEM_PALLET_NAME).cloned()); + quote! { + #attr + /// All pallets included in the runtime as a nested tuple of types in reversed order. + /// With the system pallet first. + #[deprecated(note = "Using reverse pallet orders is deprecated. use only \ + `AllPalletsWithSystem or AllPalletsWithoutSystem`")] + pub type AllPalletsReversedWithSystemFirst = ( #(#names,)* ); + } + }); quote!( #types + /// All pallets included in the runtime as a nested tuple of types. - /// Excludes the System pallet. - pub type AllPallets = ( #all_pallets ); - /// All pallets included in the runtime as a nested tuple of types. - pub type AllPalletsWithSystem = ( #all_pallets_with_system ); - - /// All modules included in the runtime as a nested tuple of types. - /// Excludes the System pallet. - #[deprecated(note = "use `AllPallets` instead")] - #[allow(dead_code)] - pub type AllModules = ( #all_pallets ); - /// All modules included in the runtime as a nested tuple of types. - #[deprecated(note = "use `AllPalletsWithSystem` instead")] - #[allow(dead_code)] - pub type AllModulesWithSystem = ( #all_pallets_with_system ); + #[deprecated(note = "The type definition has changed from representing all pallets \ + excluding system, in reversed order to become the representation of all pallets \ + including system pallet in regular order. For this reason it is encouraged to use \ + explicitly one of `AllPalletsWithSystem`, `AllPalletsWithoutSystem`, \ + `AllPalletsWithSystemReversed`, `AllPalletsWithoutSystemReversed`. \ + Note that the type `frame_executive::Executive` expects one of `AllPalletsWithSystem` \ + , `AllPalletsWithSystemReversed`, `AllPalletsReversedWithSystemFirst`. More details in \ + https://github.com/paritytech/substrate/pull/10043")] + pub type AllPallets = AllPalletsWithSystem; + + #( #all_pallets_with_system )* + + #( #all_pallets_without_system )* + + #( #all_pallets_with_system_reversed )* + + #( #all_pallets_without_system_reversed )* + + #( #all_pallets_reversed_with_system_first )* ) } fn decl_pallet_runtime_setup( + runtime: &Ident, pallet_declarations: &[Pallet], scrate: &TokenStream2, ) -> TokenStream2 { - let names = pallet_declarations.iter().map(|d| &d.name); - let names2 = pallet_declarations.iter().map(|d| &d.name); + let names = pallet_declarations.iter().map(|d| &d.name).collect::>(); let name_strings = pallet_declarations.iter().map(|d| d.name.to_string()); + let module_names = pallet_declarations.iter().map(|d| d.path.module_name()); let indices = pallet_declarations.iter().map(|pallet| pallet.index as usize); + let pallet_structs = pallet_declarations + .iter() + .map(|pallet| { + let path = &pallet.path; + match pallet.instance.as_ref() { + Some(inst) => quote!(#path::Pallet<#runtime, #path::#inst>), + None => quote!(#path::Pallet<#runtime>), + } + }) + .collect::>(); + let pallet_attrs = pallet_declarations + .iter() + .map(|pallet| { + pallet.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| { + let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }) + }) + .collect::>(); quote!( /// Provides an implementation of `PalletInfo` to provide information @@ -253,6 +521,7 @@ fn decl_pallet_runtime_setup( fn index() -> Option { let type_id = #scrate::sp_std::any::TypeId::of::

(); #( + #pallet_attrs if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { return Some(#indices) } @@ -264,13 +533,40 @@ fn decl_pallet_runtime_setup( fn name() -> Option<&'static str> { let type_id = #scrate::sp_std::any::TypeId::of::

(); #( - if type_id == #scrate::sp_std::any::TypeId::of::<#names2>() { + #pallet_attrs + if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { return Some(#name_strings) } )* None } + + fn module_name() -> Option<&'static str> { + let type_id = #scrate::sp_std::any::TypeId::of::

(); + #( + #pallet_attrs + if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { + return Some(#module_names) + } + )* + + None + } + + fn crate_version() -> Option<#scrate::traits::CrateVersion> { + let type_id = #scrate::sp_std::any::TypeId::of::

(); + #( + #pallet_attrs + if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { + return Some( + <#pallet_structs as #scrate::traits::PalletInfoAccess>::crate_version() + ) + } + )* + + None + } } ) } @@ -288,3 +584,34 @@ fn decl_integrity_test(scrate: &TokenStream2) -> TokenStream2 { } ) } + +fn decl_static_assertions( + runtime: &Ident, + pallet_decls: &[Pallet], + scrate: &TokenStream2, +) -> TokenStream2 { + let error_encoded_size_check = pallet_decls.iter().map(|decl| { + let path = &decl.path; + let assert_message = format!( + "The maximum encoded size of the error type in the `{}` pallet exceeds \ + `MAX_MODULE_ERROR_ENCODED_SIZE`", + decl.name, + ); + + quote! { + #scrate::tt_call! { + macro = [{ #path::tt_error_token }] + frame_support = [{ #scrate }] + ~~> #scrate::assert_error_encoded_size! { + path = [{ #path }] + runtime = [{ #runtime }] + assert_message = [{ #assert_message }] + } + } + } + }); + + quote! { + #(#error_encoded_size_check)* + } +} diff --git a/frame/support/procedural/src/construct_runtime/parse.rs b/frame/support/procedural/src/construct_runtime/parse.rs index 6f2fd82e73f4b..7a5acf43b92b0 100644 --- a/frame/support/procedural/src/construct_runtime/parse.rs +++ b/frame/support/procedural/src/construct_runtime/parse.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,13 @@ use frame_support_procedural_tools::syn_ext as ext; use proc_macro2::{Span, TokenStream}; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, - token, Error, Ident, Path, PathArguments, PathSegment, Result, Token, + token, Attribute, Error, Ident, Path, Result, Token, }; mod keyword { @@ -38,26 +38,70 @@ mod keyword { syn::custom_keyword!(Origin); syn::custom_keyword!(Inherent); syn::custom_keyword!(ValidateUnsigned); + syn::custom_keyword!(exclude_parts); + syn::custom_keyword!(use_parts); } +/// Declaration of a runtime. +/// +/// Pallet declare their part either explicitly or implicitly (using no part declaration) +/// If all pallet have explicit parts then the runtime declaration is explicit, otherwise it is +/// implicit. +#[derive(Debug)] +pub enum RuntimeDeclaration { + Implicit(ImplicitRuntimeDeclaration), + Explicit(ExplicitRuntimeDeclaration), +} + +/// Declaration of a runtime with some pallet with implicit declaration of parts. #[derive(Debug)] -pub struct RuntimeDefinition { - pub visibility_token: Token![pub], - pub enum_token: Token![enum], +pub struct ImplicitRuntimeDeclaration { pub name: Ident, pub where_section: WhereSection, - pub pallets: ext::Braces>, + pub pallets: Vec, } -impl Parse for RuntimeDefinition { +/// Declaration of a runtime with all pallet having explicit declaration of parts. +#[derive(Debug)] +pub struct ExplicitRuntimeDeclaration { + pub name: Ident, + pub where_section: WhereSection, + pub pallets: Vec, + pub pallets_token: token::Brace, +} + +impl Parse for RuntimeDeclaration { fn parse(input: ParseStream) -> Result { - Ok(Self { - visibility_token: input.parse()?, - enum_token: input.parse()?, - name: input.parse()?, - where_section: input.parse()?, - pallets: input.parse()?, - }) + input.parse::()?; + + // Support either `enum` or `struct`. + if input.peek(Token![struct]) { + input.parse::()?; + } else { + input.parse::()?; + } + + let name = input.parse::()?; + let where_section = input.parse()?; + let pallets = + input.parse::>>()?; + let pallets_token = pallets.token; + + match convert_pallets(pallets.content.inner.into_iter().collect())? { + PalletsConversion::Implicit(pallets) => + Ok(RuntimeDeclaration::Implicit(ImplicitRuntimeDeclaration { + name, + where_section, + pallets, + })), + PalletsConversion::Explicit(pallets) => + Ok(RuntimeDeclaration::Explicit(ExplicitRuntimeDeclaration { + name, + where_section, + pallets, + pallets_token, + })), + } } } @@ -136,88 +180,157 @@ impl Parse for WhereDefinition { } } +/// The declaration of a pallet. #[derive(Debug, Clone)] pub struct PalletDeclaration { + /// The name of the pallet, e.g.`System` in `System: frame_system`. pub name: Ident, - /// Optional fixed index (e.g. `MyPallet ... = 3,`) + /// Optional attributes tagged right above a pallet declaration. + pub attrs: Vec, + /// Optional fixed index, e.g. `MyPallet ... = 3,`. pub index: Option, + /// The path of the pallet, e.g. `frame_system` in `System: frame_system`. pub path: PalletPath, + /// The instance of the pallet, e.g. `Instance1` in `Council: pallet_collective::`. pub instance: Option, - pub pallet_parts: Vec, + /// The declared pallet parts, + /// e.g. `Some([Pallet, Call])` for `System: system::{Pallet, Call}` + /// or `None` for `System: system`. + pub pallet_parts: Option>, + /// The specified parts, either use_parts or exclude_parts. + pub specified_parts: SpecifiedParts, +} + +/// The possible declaration of pallet parts to use. +#[derive(Debug, Clone)] +pub enum SpecifiedParts { + /// Use all the pallet parts except those specified. + Exclude(Vec), + /// Use only the specified pallet parts. + Use(Vec), + /// Use the all the pallet parts. + All, } impl Parse for PalletDeclaration { fn parse(input: ParseStream) -> Result { + let attrs = input.call(Attribute::parse_outer)?; + let name = input.parse()?; let _: Token![:] = input.parse()?; let path = input.parse()?; - let instance = if input.peek(Token![<]) { + + // Parse for instance. + let instance = if input.peek(Token![::]) && input.peek3(Token![<]) { + let _: Token![::] = input.parse()?; let _: Token![<] = input.parse()?; let res = Some(input.parse()?); let _: Token![>] = input.parse()?; - let _: Token![::] = input.parse()?; res + } else if !(input.peek(Token![::]) && input.peek3(token::Brace)) && + !input.peek(keyword::exclude_parts) && + !input.peek(keyword::use_parts) && + !input.peek(Token![=]) && + !input.peek(Token![,]) && + !input.is_empty() + { + return Err(input.error( + "Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,`", + )); } else { None }; - let pallet_parts = parse_pallet_parts(input)?; + // Parse for explicit parts + let pallet_parts = if input.peek(Token![::]) && input.peek3(token::Brace) { + let _: Token![::] = input.parse()?; + Some(parse_pallet_parts(input)?) + } else if !input.peek(keyword::exclude_parts) && + !input.peek(keyword::use_parts) && + !input.peek(Token![=]) && + !input.peek(Token![,]) && + !input.is_empty() + { + return Err(input.error( + "Unexpected tokens, expected one of `::{`, `exclude_parts`, `use_parts`, `=`, `,`", + )) + } else { + None + }; + // Parse for specified parts + let specified_parts = if input.peek(keyword::exclude_parts) { + let _: keyword::exclude_parts = input.parse()?; + SpecifiedParts::Exclude(parse_pallet_parts_no_generic(input)?) + } else if input.peek(keyword::use_parts) { + let _: keyword::use_parts = input.parse()?; + SpecifiedParts::Use(parse_pallet_parts_no_generic(input)?) + } else if !input.peek(Token![=]) && !input.peek(Token![,]) && !input.is_empty() { + return Err(input.error("Unexpected tokens, expected one of `exclude_parts`, `=`, `,`")) + } else { + SpecifiedParts::All + }; + + // Parse for pallet index let index = if input.peek(Token![=]) { input.parse::()?; let index = input.parse::()?; let index = index.base10_parse::()?; Some(index) + } else if !input.peek(Token![,]) && !input.is_empty() { + return Err(input.error("Unexpected tokens, expected one of `=`, `,`")) } else { None }; - let parsed = Self { name, path, instance, pallet_parts, index }; - - Ok(parsed) + Ok(Self { attrs, name, path, instance, pallet_parts, specified_parts, index }) } } /// A struct representing a path to a pallet. `PalletPath` is almost identical to the standard /// Rust path with a few restrictions: /// - No leading colons allowed -/// - Path segments can only consist of identifers; angle-bracketed or parenthesized segments will -/// result in a parsing error (except when specifying instances) +/// - Path segments can only consist of identifers separated by colons #[derive(Debug, Clone)] pub struct PalletPath { pub inner: Path, } +impl PalletPath { + pub fn module_name(&self) -> String { + self.inner.segments.iter().fold(String::new(), |mut acc, segment| { + if !acc.is_empty() { + acc.push_str("::"); + } + acc.push_str(&segment.ident.to_string()); + acc + }) + } +} + impl Parse for PalletPath { fn parse(input: ParseStream) -> Result { - let mut lookahead = input.lookahead1(); - let mut segments = Punctuated::new(); + let mut res = + PalletPath { inner: Path { leading_colon: None, segments: Punctuated::new() } }; + let lookahead = input.lookahead1(); if lookahead.peek(Token![crate]) || lookahead.peek(Token![self]) || lookahead.peek(Token![super]) || lookahead.peek(Ident) { let ident = input.call(Ident::parse_any)?; - segments.push(PathSegment { ident, arguments: PathArguments::None }); - let _: Token![::] = input.parse()?; - lookahead = input.lookahead1(); + res.inner.segments.push(ident.into()); } else { return Err(lookahead.error()) } - while lookahead.peek(Ident) { - let ident = input.parse()?; - segments.push(PathSegment { ident, arguments: PathArguments::None }); - let _: Token![::] = input.parse()?; - lookahead = input.lookahead1(); - } - - if !lookahead.peek(token::Brace) && !lookahead.peek(Token![<]) { - return Err(lookahead.error()) + while input.peek(Token![::]) && input.peek3(Ident) { + input.parse::()?; + let ident = input.parse::()?; + res.inner.segments.push(ident.into()); } - - Ok(Self { inner: Path { leading_colon: None, segments } }) + Ok(res) } } @@ -379,3 +492,197 @@ fn remove_kind( Err(input.error(msg)) } } + +/// The declaration of a part without its generics +#[derive(Debug, Clone)] +pub struct PalletPartNoGeneric { + keyword: PalletPartKeyword, +} + +impl Parse for PalletPartNoGeneric { + fn parse(input: ParseStream) -> Result { + Ok(Self { keyword: input.parse()? }) + } +} + +/// Parse [`PalletPartNoGeneric`]'s from a braces enclosed list that is split by commas, e.g. +/// +/// `{ Call, Event }` +fn parse_pallet_parts_no_generic(input: ParseStream) -> Result> { + let pallet_parts: ext::Braces> = + input.parse()?; + + let mut resolved = HashSet::new(); + for part in pallet_parts.content.inner.iter() { + if !resolved.insert(part.keyword.name()) { + let msg = format!( + "`{}` was already declared before. Please remove the duplicate declaration", + part.keyword.name(), + ); + return Err(Error::new(part.keyword.span(), msg)) + } + } + + Ok(pallet_parts.content.inner.into_iter().collect()) +} + +/// The final definition of a pallet with the resulting fixed index and explicit parts. +#[derive(Debug, Clone)] +pub struct Pallet { + /// The name of the pallet, e.g.`System` in `System: frame_system`. + pub name: Ident, + /// Either automatically infered, or defined (e.g. `MyPallet ... = 3,`). + pub index: u8, + /// The path of the pallet, e.g. `frame_system` in `System: frame_system`. + pub path: PalletPath, + /// The instance of the pallet, e.g. `Instance1` in `Council: pallet_collective::`. + pub instance: Option, + /// The pallet parts to use for the pallet. + pub pallet_parts: Vec, + /// Expressions specified inside of a #[cfg] attribute. + pub cfg_pattern: Vec, +} + +impl Pallet { + /// Get resolved pallet parts + pub fn pallet_parts(&self) -> &[PalletPart] { + &self.pallet_parts + } + + /// Find matching parts + pub fn find_part(&self, name: &str) -> Option<&PalletPart> { + self.pallet_parts.iter().find(|part| part.name() == name) + } + + /// Return whether pallet contains part + pub fn exists_part(&self, name: &str) -> bool { + self.find_part(name).is_some() + } +} + +/// Result of a conversion of a declaration of pallets. +enum PalletsConversion { + Implicit(Vec), + Explicit(Vec), +} + +/// Convert from the parsed pallet declaration to their final information. +/// +/// Check if all pallet have explicit declaration of their parts, if so then assign index to each +/// pallet using same rules as rust for fieldless enum. I.e. implicit are assigned number +/// incrementedly from last explicit or 0. +fn convert_pallets(pallets: Vec) -> syn::Result { + if pallets.iter().any(|pallet| pallet.pallet_parts.is_none()) { + return Ok(PalletsConversion::Implicit(pallets)) + } + + let mut indices = HashMap::new(); + let mut last_index: Option = None; + let mut names = HashMap::new(); + + let pallets = pallets + .into_iter() + .map(|pallet| { + let final_index = match pallet.index { + Some(i) => i, + None => last_index.map_or(Some(0), |i| i.checked_add(1)).ok_or_else(|| { + let msg = "Pallet index doesn't fit into u8, index is 256"; + syn::Error::new(pallet.name.span(), msg) + })?, + }; + + last_index = Some(final_index); + + if let Some(used_pallet) = indices.insert(final_index, pallet.name.clone()) { + let msg = format!( + "Pallet indices are conflicting: Both pallets {} and {} are at index {}", + used_pallet, pallet.name, final_index, + ); + let mut err = syn::Error::new(used_pallet.span(), &msg); + err.combine(syn::Error::new(pallet.name.span(), msg)); + return Err(err) + } + + if let Some(used_pallet) = names.insert(pallet.name.clone(), pallet.name.span()) { + let msg = "Two pallets with the same name!"; + + let mut err = syn::Error::new(used_pallet, &msg); + err.combine(syn::Error::new(pallet.name.span(), &msg)); + return Err(err) + } + + let mut pallet_parts = pallet.pallet_parts.expect("Checked above"); + + let available_parts = + pallet_parts.iter().map(|part| part.keyword.name()).collect::>(); + + // Check parts are correctly specified + match &pallet.specified_parts { + SpecifiedParts::Exclude(parts) | SpecifiedParts::Use(parts) => + for part in parts { + if !available_parts.contains(part.keyword.name()) { + let msg = format!( + "Invalid pallet part specified, the pallet `{}` doesn't have the \ + `{}` part. Available parts are: {}.", + pallet.name, + part.keyword.name(), + pallet_parts.iter().fold(String::new(), |fold, part| { + if fold.is_empty() { + format!("`{}`", part.keyword.name()) + } else { + format!("{}, `{}`", fold, part.keyword.name()) + } + }) + ); + return Err(syn::Error::new(part.keyword.span(), msg)) + } + }, + SpecifiedParts::All => (), + } + + // Set only specified parts. + match pallet.specified_parts { + SpecifiedParts::Exclude(excluded_parts) => pallet_parts.retain(|part| { + !excluded_parts + .iter() + .any(|excluded_part| excluded_part.keyword.name() == part.keyword.name()) + }), + SpecifiedParts::Use(used_parts) => pallet_parts.retain(|part| { + used_parts.iter().any(|use_part| use_part.keyword.name() == part.keyword.name()) + }), + SpecifiedParts::All => (), + } + + let cfg_pattern = pallet + .attrs + .iter() + .map(|attr| { + if attr.path.segments.len() != 1 || attr.path.segments[0].ident != "cfg" { + let msg = "Unsupported attribute, only #[cfg] is supported on pallet \ + declarations in `construct_runtime`"; + return Err(syn::Error::new(attr.span(), msg)) + } + + attr.parse_args_with(|input: syn::parse::ParseStream| { + // Required, otherwise the parse stream doesn't advance and will result in + // an error. + let input = input.parse::()?; + cfg_expr::Expression::parse(&input.to_string()) + .map_err(|e| syn::Error::new(attr.span(), e.to_string())) + }) + }) + .collect::>>()?; + + Ok(Pallet { + name: pallet.name, + index: final_index, + path: pallet.path, + instance: pallet.instance, + cfg_pattern, + pallet_parts, + }) + }) + .collect::>>()?; + + Ok(PalletsConversion::Explicit(pallets)) +} diff --git a/frame/support/procedural/src/crate_version.rs b/frame/support/procedural/src/crate_version.rs new file mode 100644 index 0000000000000..e2be6dd889db4 --- /dev/null +++ b/frame/support/procedural/src/crate_version.rs @@ -0,0 +1,54 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Implementation of macros related to crate versioning. + +use super::get_cargo_env_var; +use frame_support_procedural_tools::generate_crate_access_2018; +use proc_macro2::{Span, TokenStream}; +use syn::{Error, Result}; + +/// Create an error that will be shown by rustc at the call site of the macro. +fn create_error(message: &str) -> Error { + Error::new(Span::call_site(), message) +} + +/// Implementation of the `crate_to_crate_version!` macro. +pub fn crate_to_crate_version(input: proc_macro::TokenStream) -> Result { + if !input.is_empty() { + return Err(create_error("No arguments expected!")) + } + + let major_version = get_cargo_env_var::("CARGO_PKG_VERSION_MAJOR") + .map_err(|_| create_error("Major version needs to fit into `u16`"))?; + + let minor_version = get_cargo_env_var::("CARGO_PKG_VERSION_MINOR") + .map_err(|_| create_error("Minor version needs to fit into `u8`"))?; + + let patch_version = get_cargo_env_var::("CARGO_PKG_VERSION_PATCH") + .map_err(|_| create_error("Patch version needs to fit into `u8`"))?; + + let crate_ = generate_crate_access_2018("frame-support")?; + + Ok(quote::quote! { + #crate_::traits::CrateVersion { + major: #major_version, + minor: #minor_version, + patch: #patch_version, + } + }) +} diff --git a/frame/support/procedural/src/debug_no_bound.rs b/frame/support/procedural/src/debug_no_bound.rs index acfd8d0cabc8a..56168edb87e83 100644 --- a/frame/support/procedural/src/debug_no_bound.rs +++ b/frame/support/procedural/src/debug_no_bound.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/default_no_bound.rs b/frame/support/procedural/src/default_no_bound.rs index 38d6e19b1732f..b174a49e91741 100644 --- a/frame/support/procedural/src/default_no_bound.rs +++ b/frame/support/procedural/src/default_no_bound.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,82 +15,142 @@ // See the License for the specific language governing permissions and // limitations under the License. -use syn::spanned::Spanned; +use proc_macro2::Span; +use quote::{quote, quote_spanned}; +use syn::{spanned::Spanned, Data, DeriveInput, Fields}; -/// Derive Clone but do not bound any generic. +/// Derive Default but do not bound any generic. pub fn derive_default_no_bound(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input: syn::DeriveInput = match syn::parse(input) { - Ok(input) => input, - Err(e) => return e.to_compile_error().into(), - }; + let input = syn::parse_macro_input!(input as DeriveInput); let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let impl_ = match input.data { - syn::Data::Struct(struct_) => match struct_.fields { - syn::Fields::Named(named) => { - let fields = named.named.iter().map(|i| &i.ident).map(|i| { - quote::quote_spanned!(i.span() => - #i: core::default::Default::default() - ) + Data::Struct(struct_) => match struct_.fields { + Fields::Named(named) => { + let fields = named.named.iter().map(|field| &field.ident).map(|ident| { + quote_spanned! {ident.span() => + #ident: core::default::Default::default() + } }); - quote::quote!( Self { #( #fields, )* } ) + quote!(Self { #( #fields, )* }) }, - syn::Fields::Unnamed(unnamed) => { - let fields = - unnamed.unnamed.iter().enumerate().map(|(i, _)| syn::Index::from(i)).map(|i| { - quote::quote_spanned!(i.span() => - core::default::Default::default() - ) - }); - - quote::quote!( Self ( #( #fields, )* ) ) + Fields::Unnamed(unnamed) => { + let fields = unnamed.unnamed.iter().map(|field| { + quote_spanned! {field.span()=> + core::default::Default::default() + } + }); + + quote!(Self( #( #fields, )* )) }, - syn::Fields::Unit => { - quote::quote!(Self) + Fields::Unit => { + quote!(Self) }, }, - syn::Data::Enum(enum_) => - if let Some(first_variant) = enum_.variants.first() { - let variant_ident = &first_variant.ident; - match &first_variant.fields { - syn::Fields::Named(named) => { - let fields = named.named.iter().map(|i| &i.ident).map(|i| { - quote::quote_spanned!(i.span() => - #i: core::default::Default::default() - ) - }); - - quote::quote!( #name :: #ty_generics :: #variant_ident { #( #fields, )* } ) - }, - syn::Fields::Unnamed(unnamed) => { - let fields = unnamed - .unnamed - .iter() - .enumerate() - .map(|(i, _)| syn::Index::from(i)) - .map(|i| { - quote::quote_spanned!(i.span() => + Data::Enum(enum_) => { + if enum_.variants.is_empty() { + return syn::Error::new_spanned(name, "cannot derive Default for an empty enum") + .to_compile_error() + .into() + } + + // all #[default] attrs with the variant they're on; i.e. a var + let default_variants = enum_ + .variants + .into_iter() + .filter(|variant| variant.attrs.iter().any(|attr| attr.path.is_ident("default"))) + .collect::>(); + + match &*default_variants { + [] => { + return syn::Error::new( + name.clone().span(), + // writing this as a regular string breaks rustfmt for some reason + r#"no default declared, make a variant default by placing `#[default]` above it"#, + ) + .into_compile_error() + .into() + }, + // only one variant with the #[default] attribute set + [default_variant] => { + let variant_attrs = default_variant + .attrs + .iter() + .filter(|a| a.path.is_ident("default")) + .collect::>(); + + // check that there is only one #[default] attribute on the variant + if let [first_attr, second_attr, additional_attrs @ ..] = &*variant_attrs { + let mut err = + syn::Error::new(Span::call_site(), "multiple `#[default]` attributes"); + + err.combine(syn::Error::new_spanned(first_attr, "`#[default]` used here")); + + err.extend([second_attr].into_iter().chain(additional_attrs).map( + |variant| { + syn::Error::new_spanned(variant, "`#[default]` used again here") + }, + )); + + return err.into_compile_error().into() + } + + let variant_ident = &default_variant.ident; + + let fully_qualified_variant_path = quote!(Self::#variant_ident); + + match &default_variant.fields { + Fields::Named(named) => { + let fields = + named.named.iter().map(|field| &field.ident).map(|ident| { + quote_spanned! {ident.span()=> + #ident: core::default::Default::default() + } + }); + + quote!(#fully_qualified_variant_path { #( #fields, )* }) + }, + Fields::Unnamed(unnamed) => { + let fields = unnamed.unnamed.iter().map(|field| { + quote_spanned! {field.span()=> core::default::Default::default() - ) + } }); - quote::quote!( #name :: #ty_generics :: #variant_ident ( #( #fields, )* ) ) - }, - syn::Fields::Unit => quote::quote!( #name :: #ty_generics :: #variant_ident ), - } - } else { - quote::quote!(Self) - }, - syn::Data::Union(_) => { - let msg = "Union type not supported by `derive(CloneNoBound)`"; - return syn::Error::new(input.span(), msg).to_compile_error().into() + quote!(#fully_qualified_variant_path( #( #fields, )* )) + }, + Fields::Unit => fully_qualified_variant_path, + } + }, + [first, additional @ ..] => { + let mut err = syn::Error::new(Span::call_site(), "multiple declared defaults"); + + err.combine(syn::Error::new_spanned(first, "first default")); + + err.extend( + additional + .into_iter() + .map(|variant| syn::Error::new_spanned(variant, "additional default")), + ); + + return err.into_compile_error().into() + }, + } }, + Data::Union(union_) => + return syn::Error::new_spanned( + union_.union_token, + "Union type not supported by `derive(DefaultNoBound)`", + ) + .to_compile_error() + .into(), }; - quote::quote!( + quote!( const _: () = { impl #impl_generics core::default::Default for #name #ty_generics #where_clause { fn default() -> Self { diff --git a/frame/support/procedural/src/key_prefix.rs b/frame/support/procedural/src/key_prefix.rs index 3f424e8b8b8dd..05582f1297eed 100644 --- a/frame/support/procedural/src/key_prefix.rs +++ b/frame/support/procedural/src/key_prefix.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index a8ac022c35c6b..41dbc4ee9592c 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,17 +21,23 @@ mod clone_no_bound; mod construct_runtime; +mod crate_version; mod debug_no_bound; mod default_no_bound; mod dummy_part_checker; mod key_prefix; +mod match_and_insert; mod pallet; +mod pallet_error; mod partial_eq_no_bound; mod storage; +mod storage_alias; mod transactional; +mod tt_macro; use proc_macro::TokenStream; -use std::cell::RefCell; +use quote::quote; +use std::{cell::RefCell, str::FromStr}; pub(crate) use storage::INHERENT_INSTANCE_NAME; thread_local! { @@ -39,9 +45,9 @@ thread_local! { static COUNTER: RefCell = RefCell::new(Counter(0)); } -/// Counter to generate a relatively unique identifier for macros querying for the existence of -/// pallet parts. This is necessary because declarative macros gets hoisted to the crate root, -/// which shares the namespace with other pallets containing the very same query macros. +/// Counter to generate a relatively unique identifier for macros. This is necessary because +/// declarative macros gets hoisted to the crate root, which shares the namespace with other pallets +/// containing the very same macros. struct Counter(u64); impl Counter { @@ -52,6 +58,16 @@ impl Counter { } } +/// Get the value from the given environment variable set by cargo. +/// +/// The value is parsed into the requested destination type. +fn get_cargo_env_var(version_env: &str) -> std::result::Result { + let version = std::env::var(version_env) + .unwrap_or_else(|_| panic!("`{}` is always set by cargo; qed", version_env)); + + T::from_str(&version).map_err(drop) +} + /// Declares strongly-typed wrappers around codec-compatible types in storage. /// /// ## Example @@ -286,52 +302,91 @@ pub fn decl_storage(input: TokenStream) -> TokenStream { /// /// # Example: /// -/// ```nocompile +/// ```ignore /// construct_runtime!( /// pub enum Runtime where /// Block = Block, /// NodeBlock = node::Block, /// UncheckedExtrinsic = UncheckedExtrinsic /// { -/// System: system::{Pallet, Call, Event, Config} = 0, -/// Test: test::{Pallet, Call} = 1, -/// Test2: test_with_long_module::{Pallet, Event}, +/// System: frame_system::{Pallet, Call, Event, Config} = 0, +/// Test: path::to::test::{Pallet, Call} = 1, /// -/// // Pallets with instances -/// Test3_Instance1: test3::::{Pallet, Call, Storage, Event, Config, Origin}, -/// Test3_DefaultInstance: test3::{Pallet, Call, Storage, Event, Config, Origin} = 4, +/// // Pallets with instances. +/// Test2_Instance1: test2::::{Pallet, Call, Storage, Event, Config, Origin}, +/// Test2_DefaultInstance: test2::{Pallet, Call, Storage, Event, Config, Origin} = 4, +/// +/// // Pallets declared with `pallet` attribute macro: no need to define the parts +/// Test3_Instance1: test3::, +/// Test3_DefaultInstance: test3, +/// +/// // with `exclude_parts` keyword some part can be excluded. +/// Test4_Instance1: test4:: exclude_parts { Call, Origin }, +/// Test4_DefaultInstance: test4 exclude_parts { Storage }, +/// +/// // with `use_parts` keyword, a subset of the pallet parts can be specified. +/// Test4_Instance1: test4:: use_parts { Pallet, Call}, +/// Test4_DefaultInstance: test4 use_parts { Pallet }, /// } /// ) /// ``` /// -/// The identifier `System` is the name of the pallet and the lower case identifier `system` is the -/// name of the Rust module/crate for this Substrate pallet. The identifiers between the braces are -/// the pallet parts provided by the pallet. It is important to list these parts here to export -/// them correctly in the metadata or to make the pallet usable in the runtime. +/// Each pallet is declared as such: +/// * `Identifier`: name given to the pallet that uniquely identifies it. /// -/// We provide support for the following module parts in a pallet: +/// * `:`: colon separator /// -/// - `Pallet` - Required for all pallets -/// - `Call` - If the pallet has callable functions -/// - `Storage` - If the pallet uses storage -/// - `Event` or `Event` (if the event is generic) - If the pallet emits events -/// - `Origin` or `Origin` (if the origin is generic) - If the pallet has instanciable origins -/// - `Config` or `Config` (if the config is generic) - If the pallet builds the genesis storage -/// with `GenesisConfig` -/// - `Inherent` - If the pallet provides/can check inherents. -/// - `ValidateUnsigned` - If the pallet validates unsigned extrinsics. +/// * `path::to::pallet`: identifiers separated by colons which declare the path to a pallet +/// definition. /// -/// `= $n` is an optional part allowing to define at which index the pallet variants in -/// `OriginCaller`, `Call` and `Event` are encoded, and to define the ModuleToIndex value. +/// * `::` optional: specify the instance of the pallet to use. If not specified it will +/// use the default instance (or the only instance in case of non-instantiable pallets). /// -/// if `= $n` is not given, then index is resolved same as fieldless enum in Rust -/// (i.e. incrementedly from previous index): -/// ```nocompile -/// pallet1 .. = 2, -/// pallet2 .., // Here pallet2 is given index 3 -/// pallet3 .. = 0, -/// pallet4 .., // Here pallet4 is given index 1 -/// ``` +/// * `::{ Part1, Part2, .. }` optional if pallet declared with `frame_support::pallet`: Comma +/// separated parts declared with their generic. If a pallet is declared with +/// `frame_support::pallet` macro then the parts can be automatically derived if not explicitly +/// provided. We provide support for the following module parts in a pallet: +/// +/// - `Pallet` - Required for all pallets +/// - `Call` - If the pallet has callable functions +/// - `Storage` - If the pallet uses storage +/// - `Event` or `Event` (if the event is generic) - If the pallet emits events +/// - `Origin` or `Origin` (if the origin is generic) - If the pallet has instanciable origins +/// - `Config` or `Config` (if the config is generic) - If the pallet builds the genesis +/// storage with `GenesisConfig` +/// - `Inherent` - If the pallet provides/can check inherents. +/// - `ValidateUnsigned` - If the pallet validates unsigned extrinsics. +/// +/// It is important to list these parts here to export them correctly in the metadata or to make +/// the pallet usable in the runtime. +/// +/// * `exclude_parts { Part1, Part2 }` optional: comma separated parts without generics. I.e. one of +/// `Pallet`, `Call`, `Storage`, `Event`, `Origin`, `Config`, `Inherent`, `ValidateUnsigned`. It +/// is incompatible with `use_parts`. This specifies the part to exclude. In order to select +/// subset of the pallet parts. +/// +/// For example excluding the part `Call` can be useful if the runtime doesn't want to make the +/// pallet calls available. +/// +/// * `use_parts { Part1, Part2 }` optional: comma separated parts without generics. I.e. one of +/// `Pallet`, `Call`, `Storage`, `Event`, `Origin`, `Config`, `Inherent`, `ValidateUnsigned`. It +/// is incompatible with `exclude_parts`. This specifies the part to use. In order to select a +/// subset of the pallet parts. +/// +/// For example not using the part `Call` can be useful if the runtime doesn't want to make the +/// pallet calls available. +/// +/// * `= $n` optional: number to define at which index the pallet variants in `OriginCaller`, `Call` +/// and `Event` are encoded, and to define the ModuleToIndex value. +/// +/// if `= $n` is not given, then index is resolved in the same way as fieldless enum in Rust +/// (i.e. incrementedly from previous index): +/// ```nocompile +/// pallet1 .. = 2, +/// pallet2 .., // Here pallet2 is given index 3 +/// pallet3 .. = 0, +/// pallet4 .., // Here pallet4 is given index 1 +/// ``` /// /// # Note /// @@ -341,14 +396,84 @@ pub fn decl_storage(input: TokenStream) -> TokenStream { /// /// # Type definitions /// -/// * The macro generates a type alias for each pallet to their `Module` (or `Pallet`). E.g. `type -/// System = frame_system::Pallet` +/// * The macro generates a type alias for each pallet to their `Pallet`. E.g. `type System = +/// frame_system::Pallet` #[proc_macro] pub fn construct_runtime(input: TokenStream) -> TokenStream { construct_runtime::construct_runtime(input) } -/// Macro to define a pallet. Docs are at `frame_support::pallet`. +/// The pallet struct placeholder `#[pallet::pallet]` is mandatory and allows you to specify +/// pallet information. +/// +/// The struct must be defined as follows: +/// ```ignore +/// #[pallet::pallet] +/// pub struct Pallet(_); +/// ``` +/// I.e. a regular struct definition named `Pallet`, with generic T and no where clause. +/// +/// ## Macro expansion: +/// +/// The macro adds this attribute to the struct definition: +/// ```ignore +/// #[derive( +/// frame_support::CloneNoBound, +/// frame_support::EqNoBound, +/// frame_support::PartialEqNoBound, +/// frame_support::RuntimeDebugNoBound, +/// )] +/// ``` +/// and replaces the type `_` with `PhantomData`. It also implements on the pallet: +/// * `GetStorageVersion` +/// * `OnGenesis`: contains some logic to write the pallet version into storage. +/// * `PalletErrorTypeInfo`: provides the type information for the pallet error, if defined. +/// +/// It declares `type Module` type alias for `Pallet`, used by `construct_runtime`. +/// +/// It implements `PalletInfoAccess` on `Pallet` to ease access to pallet information given by +/// `frame_support::traits::PalletInfo`. (The implementation uses the associated type +/// `frame_system::Config::PalletInfo`). +/// +/// It implements `StorageInfoTrait` on `Pallet` which give information about all storages. +/// +/// If the attribute `generate_store` is set then the macro creates the trait `Store` and +/// implements it on `Pallet`. +/// +/// If the attribute `set_storage_max_encoded_len` is set then the macro calls +/// `StorageInfoTrait` for each storage in the implementation of `StorageInfoTrait` for the +/// pallet. Otherwise it implements `StorageInfoTrait` for the pallet using the +/// `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. +/// * All storages are marked as unbounded, meaning you do not need to implement `MaxEncodedLen` on +/// storage types. This is equivalent to specifying `#[pallet::unbounded]` on all storage type +/// definitions. +/// +/// 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. +/// +///

+/// +/// See `frame_support::pallet` docs for more info. #[proc_macro_attribute] pub fn pallet(attr: TokenStream, item: TokenStream) -> TokenStream { pallet::pallet(attr, item) @@ -379,6 +504,12 @@ pub fn transactional(attr: TokenStream, input: TokenStream) -> TokenStream { transactional::transactional(attr, input).unwrap_or_else(|e| e.to_compile_error().into()) } +#[proc_macro_attribute] +pub fn require_transactional(attr: TokenStream, input: TokenStream) -> TokenStream { + transactional::require_transactional(attr, input) + .unwrap_or_else(|e| e.to_compile_error().into()) +} + /// Derive [`Clone`] but do not bound any generic. Docs are at `frame_support::CloneNoBound`. #[proc_macro_derive(CloneNoBound)] pub fn derive_clone_no_bound(input: TokenStream) -> TokenStream { @@ -451,15 +582,16 @@ pub fn derive_eq_no_bound(input: TokenStream) -> TokenStream { } /// derive `Default` but do no bound any generic. Docs are at `frame_support::DefaultNoBound`. -#[proc_macro_derive(DefaultNoBound)] +#[proc_macro_derive(DefaultNoBound, attributes(default))] pub fn derive_default_no_bound(input: TokenStream) -> TokenStream { default_no_bound::derive_default_no_bound(input) } -#[proc_macro_attribute] -pub fn require_transactional(attr: TokenStream, input: TokenStream) -> TokenStream { - transactional::require_transactional(attr, input) - .unwrap_or_else(|e| e.to_compile_error().into()) +#[proc_macro] +pub fn crate_to_crate_version(input: TokenStream) -> TokenStream { + crate_version::crate_to_crate_version(input) + .unwrap_or_else(|e| e.to_compile_error()) + .into() } /// The number of module instances supported by the runtime, starting at index 1, @@ -480,3 +612,680 @@ pub fn impl_key_prefix_for_tuples(input: TokenStream) -> TokenStream { pub fn __generate_dummy_part_checker(input: TokenStream) -> TokenStream { dummy_part_checker::generate_dummy_part_checker(input) } + +/// Macro that inserts some tokens after the first match of some pattern. +/// +/// # Example: +/// +/// ```nocompile +/// match_and_insert!( +/// target = [{ Some content with { at some point match pattern } other match pattern are ignored }] +/// pattern = [{ match pattern }] // the match pattern cannot contain any group: `[]`, `()`, `{}` +/// // can relax this constraint, but will require modifying the match logic in code +/// tokens = [{ expansion tokens }] // content inside braces can be anything including groups +/// ); +/// ``` +/// +/// will generate: +/// +/// ```nocompile +/// Some content with { at some point match pattern expansion tokens } other match patterns are +/// ignored +/// ``` +#[proc_macro] +pub fn match_and_insert(input: TokenStream) -> TokenStream { + match_and_insert::match_and_insert(input) +} + +#[proc_macro_derive(PalletError, attributes(codec))] +pub fn derive_pallet_error(input: TokenStream) -> TokenStream { + pallet_error::derive_pallet_error(input) +} + +/// Internal macro used by `frame_support` to create tt-call-compliant macros +#[proc_macro] +pub fn __create_tt_macro(input: TokenStream) -> TokenStream { + tt_macro::create_tt_return_macro(input) +} + +#[proc_macro_attribute] +pub fn storage_alias(_: TokenStream, input: TokenStream) -> TokenStream { + storage_alias::storage_alias(input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} + +/// Used internally to decorate pallet attribute macro stubs when they are erroneously used +/// outside of a pallet module +fn pallet_macro_stub() -> TokenStream { + quote!(compile_error!( + "This attribute can only be used from within a pallet module marked with `#[frame_support::pallet]`" + )) + .into() +} + +/// The mandatory attribute `#[pallet::config]` defines the configurable options for the pallet. +/// +/// Item must be defined as: +/// +/// ```ignore +/// #[pallet::config] +/// pub trait Config: frame_system::Config + $optionally_some_other_supertraits +/// $optional_where_clause +/// { +/// ... +/// } +/// ``` +/// +/// I.e. a regular trait definition named `Config`, with the supertrait +/// `frame_system::pallet::Config`, and optionally other supertraits and a where clause. +/// (Specifying other supertraits here is known as [tight +/// coupling](https://docs.substrate.io/reference/how-to-guides/pallet-design/use-tight-coupling/)) +/// +/// The associated type `RuntimeEvent` is reserved. If defined, it must have the bounds +/// `From` and `IsType<::RuntimeEvent>`. +/// +/// [`pallet::event`](`macro@event`) must be present if `RuntimeEvent` exists as a config item +/// in your `#[pallet::config]`. +#[proc_macro_attribute] +pub fn config(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::constant]` attribute can be used to add an associated type trait bounded by `Get` +/// from [`pallet::config`](`macro@config`) into metadata, e.g.: +/// +/// ```ignore +/// #[pallet::config] +/// pub trait Config: frame_system::Config { +/// #[pallet::constant] +/// type Foo: Get; +/// } +/// ``` +#[proc_macro_attribute] +pub fn constant(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// To bypass the `frame_system::Config` supertrait check, use the attribute +/// `pallet::disable_frame_system_supertrait_check`, e.g.: +/// +/// ```ignore +/// #[pallet::config] +/// #[pallet::disable_frame_system_supertrait_check] +/// pub trait Config: pallet_timestamp::Config {} +/// ``` +/// +/// NOTE: Bypassing the `frame_system::Config` supertrait check is typically desirable when you +/// want to write an alternative to the `frame_system` pallet. +#[proc_macro_attribute] +pub fn disable_frame_system_supertrait_check(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// To generate a `Store` trait associating all storages, annotate your `Pallet` struct with +/// the attribute `#[pallet::generate_store($vis trait Store)]`, e.g.: +/// +/// ```ignore +/// #[pallet::pallet] +/// #[pallet::generate_store(pub(super) trait Store)] +/// pub struct Pallet(_); +/// ``` +/// More precisely, the `Store` trait contains an associated type for each storage. It is +/// implemented for `Pallet` allowing access to the storage from pallet struct. +/// +/// Thus when defining a storage named `Foo`, it can later be accessed from `Pallet` using +/// `::Foo`. +/// +/// NOTE: this attribute is only valid when applied _directly_ to your `Pallet` struct +/// definition. +#[proc_macro_attribute] +pub fn generate_store(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// To generate the full storage info (used for PoV calculation) use the attribute +/// `#[pallet::generate_storage_info]`, e.g.: +/// +/// ```ignore +/// #[pallet::pallet] +/// #[pallet::generate_storage_info] +/// pub struct Pallet(_); +/// ``` +/// +/// This requires all storage items to implement the trait `StorageInfoTrait`, thus all keys +/// and value types must be bound by `MaxEncodedLen`. Individual storages can opt-out from this +/// constraint by using [`#[pallet::unbounded]`](`macro@unbounded`) (see +/// [`#[pallet::storage]`](`macro@storage`) for more info). +#[proc_macro_attribute] +pub fn generate_storage_info(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// Because the `pallet::pallet` macro implements `GetStorageVersion`, the current storage +/// version needs to be communicated to the macro. This can be done by using the +/// `pallet::storage_version` attribute: +/// +/// ```ignore +/// const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); +/// +/// #[pallet::pallet] +/// #[pallet::storage_version(STORAGE_VERSION)] +/// pub struct Pallet(_); +/// ``` +/// +/// If not present, the current storage version is set to the default value. +#[proc_macro_attribute] +pub fn storage_version(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::hooks]` attribute allows you to specify a `Hooks` implementation for +/// `Pallet` that specifies pallet-specific logic. +/// +/// The item the attribute attaches to must be defined as follows: +/// ```ignore +/// #[pallet::hooks] +/// impl Hooks> for Pallet $optional_where_clause { +/// ... +/// } +/// ``` +/// I.e. a regular trait implementation with generic bound: `T: Config`, for the trait +/// `Hooks>` (they are defined in preludes), for the type `Pallet` and +/// with an optional where clause. +/// +/// If no `#[pallet::hooks]` exists, then the following default implementation is +/// automatically generated: +/// ```ignore +/// #[pallet::hooks] +/// impl Hooks> for Pallet {} +/// ``` +/// +/// ## Macro expansion +/// +/// The macro implements the traits `OnInitialize`, `OnIdle`, `OnFinalize`, `OnRuntimeUpgrade`, +/// `OffchainWorker`, and `IntegrityTest` using the provided `Hooks` implementation. +/// +/// NOTE: `OnRuntimeUpgrade` is implemented with `Hooks::on_runtime_upgrade` and some +/// additional logic. E.g. logic to write the pallet version into storage. +/// +/// NOTE: The macro also adds some tracing logic when implementing the above traits. The +/// following hooks emit traces: `on_initialize`, `on_finalize` and `on_runtime_upgrade`. +#[proc_macro_attribute] +pub fn hooks(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// Each dispatchable needs to define a weight with `#[pallet::weight($expr)]` attribute, the +/// first argument must be `origin: OriginFor`. +#[proc_macro_attribute] +pub fn weight(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// Compact encoding for arguments can be achieved via `#[pallet::compact]`. The function must +/// return a `DispatchResultWithPostInfo` or `DispatchResult`. +#[proc_macro_attribute] +pub fn compact(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// Each dispatchable may also be annotated with the `#[pallet::call_index($idx)]` attribute, +/// which explicitly defines the codec index for the dispatchable function in the `Call` enum. +/// +/// All call indexes start from 0, until it encounters a dispatchable function with a defined +/// call index. The dispatchable function that lexically follows the function with a defined +/// call index will have that call index, but incremented by 1, e.g. if there are 3 +/// dispatchable functions `fn foo`, `fn bar` and `fn qux` in that order, and only `fn bar` +/// has a call index of 10, then `fn qux` will have an index of 11, instead of 1. +/// +/// All arguments must implement [`Debug`], [`PartialEq`], [`Eq`], `Decode`, `Encode`, and +/// [`Clone`]. For ease of use, bound by the trait `frame_support::pallet_prelude::Member`. +/// +/// If no `#[pallet::call]` exists, then a default implementation corresponding to the +/// following code is automatically generated: +/// +/// ```ignore +/// #[pallet::call] +/// impl Pallet {} +/// ``` +/// +/// **WARNING**: modifying dispatchables, changing their order, removing some, etc., must be +/// done with care. Indeed this will change the outer runtime call type (which is an enum with +/// one variant per pallet), this outer runtime call can be stored on-chain (e.g. in +/// `pallet-scheduler`). Thus migration might be needed. To mitigate against some of this, the +/// `#[pallet::call_index($idx)]` attribute can be used to fix the order of the dispatchable so +/// that the `Call` enum encoding does not change after modification. As a general rule of +/// thumb, it is therefore adventageous to always add new calls to the end so you can maintain +/// the existing order of calls. +/// +/// ### Macro expansion +/// +/// The macro creates an enum `Call` with one variant per dispatchable. This enum implements: +/// [`Clone`], [`Eq`], [`PartialEq`], [`Debug`] (with stripped implementation in `not("std")`), +/// `Encode`, `Decode`, `GetDispatchInfo`, `GetCallName`, and `UnfilteredDispatchable`. +/// +/// The macro implements the `Callable` trait on `Pallet` and a function `call_functions` +/// which returns the dispatchable metadata. +#[proc_macro_attribute] +pub fn call_index(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// Allows you to define some extra constants to be added into constant metadata. +/// +/// Item must be defined as: +/// +/// ```ignore +/// #[pallet::extra_constants] +/// impl Pallet where $optional_where_clause { +/// /// $some_doc +/// $vis fn $fn_name() -> $some_return_type { +/// ... +/// } +/// ... +/// } +/// ``` +/// I.e. a regular rust `impl` block with some optional where clause and functions with 0 args, +/// 0 generics, and some return type. +/// +/// ## Macro expansion +/// +/// The macro add some extra constants to pallet constant metadata. +#[proc_macro_attribute] +pub fn extra_constants(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::error]` attribute allows you to define an error enum that will be returned +/// from the dispatchable when an error occurs. The information for this error type is then +/// stored in metadata. +/// +/// Item must be defined as: +/// +/// ```ignore +/// #[pallet::error] +/// pub enum Error { +/// /// $some_optional_doc +/// $SomeFieldLessVariant, +/// /// $some_more_optional_doc +/// $SomeVariantWithOneField(FieldType), +/// ... +/// } +/// ``` +/// I.e. a regular enum named `Error`, with generic `T` and fieldless or multiple-field +/// variants. +/// +/// Any field type in the enum variants must implement `TypeInfo` in order to be properly used +/// in the metadata, and its encoded size should be as small as possible, preferably 1 byte in +/// size in order to reduce storage size. The error enum itself has an absolute maximum encoded +/// size specified by `MAX_MODULE_ERROR_ENCODED_SIZE`. +/// +/// (1 byte can still be 256 different errors. The more specific the error, the easier it is to +/// diagnose problems and give a better experience to the user. Don't skimp on having lots of +/// individual error conditions.) +/// +/// Field types in enum variants must also implement `PalletError`, otherwise the pallet will +/// fail to compile. Rust primitive types have already implemented the `PalletError` trait +/// along with some commonly used stdlib types such as [`Option`] and `PhantomData`, and hence +/// in most use cases, a manual implementation is not necessary and is discouraged. +/// +/// The generic `T` must not bound anything and a `where` clause is not allowed. That said, +/// bounds and/or a where clause should not needed for any use-case. +/// +/// ## Macro expansion +/// +/// The macro implements the [`Debug`] trait and functions `as_u8` using variant position, and +/// `as_str` using variant doc. +/// +/// The macro also implements `From>` for `&'static str` and `From>` for +/// `DispatchError`. +#[proc_macro_attribute] +pub fn error(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::event]` attribute allows you to define pallet events. Pallet events are +/// stored under the `system` / `events` key when the block is applied (and then replaced when +/// the next block writes it's events). +/// +/// The Event enum must be defined as follows: +/// +/// ```ignore +/// #[pallet::event] +/// #[pallet::generate_deposit($visibility fn deposit_event)] // Optional +/// pub enum Event<$some_generic> $optional_where_clause { +/// /// Some doc +/// $SomeName($SomeType, $YetanotherType, ...), +/// ... +/// } +/// ``` +/// +/// I.e. an enum (with named or unnamed fields variant), named `Event`, with generic: none or +/// `T` or `T: Config`, and optional w here clause. +/// +/// Each field must implement [`Clone`], [`Eq`], [`PartialEq`], `Encode`, `Decode`, and +/// [`Debug`] (on std only). For ease of use, bound by the trait `Member`, available in +/// `frame_support::pallet_prelude`. +#[proc_macro_attribute] +pub fn event(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The attribute `#[pallet::generate_deposit($visibility fn deposit_event)]` generates a +/// helper function on `Pallet` that handles deposit events. +/// +/// NOTE: For instantiable pallets, the event must be generic over `T` and `I`. +/// +/// ## Macro expansion +/// +/// The macro will add on enum `Event` the attributes: +/// * `#[derive(frame_support::CloneNoBound)]` +/// * `#[derive(frame_support::EqNoBound)]` +/// * `#[derive(frame_support::PartialEqNoBound)]` +/// * `#[derive(frame_support::RuntimeDebugNoBound)]` +/// * `#[derive(codec::Encode)]` +/// * `#[derive(codec::Decode)]` +/// +/// The macro implements `From>` for (). +/// +/// The macro implements a metadata function on `Event` returning the `EventMetadata`. +/// +/// If `#[pallet::generate_deposit]` is present then the macro implements `fn deposit_event` on +/// `Pallet`. +#[proc_macro_attribute] +pub fn generate_deposit(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::storage]` attribute lets you define some abstract storage inside of runtime +/// storage and also set its metadata. This attribute can be used multiple times. +/// +/// Item should be defined as: +/// +/// ```ignore +/// #[pallet::storage] +/// #[pallet::getter(fn $getter_name)] // optional +/// $vis type $StorageName<$some_generic> $optional_where_clause +/// = $StorageType<$generic_name = $some_generics, $other_name = $some_other, ...>; +/// ``` +/// +/// or with unnamed generic: +/// +/// ```ignore +/// #[pallet::storage] +/// #[pallet::getter(fn $getter_name)] // optional +/// $vis type $StorageName<$some_generic> $optional_where_clause +/// = $StorageType<_, $some_generics, ...>; +/// ``` +/// +/// I.e. it must be a type alias, with generics: `T` or `T: Config`. The aliased type must be +/// one of `StorageValue`, `StorageMap` or `StorageDoubleMap`. The generic arguments of the +/// storage type can be given in two manners: named and unnamed. For named generic arguments, +/// the name for each argument should match the name defined for it on the storage struct: +/// * `StorageValue` expects `Value` and optionally `QueryKind` and `OnEmpty`, +/// * `StorageMap` expects `Hasher`, `Key`, `Value` and optionally `QueryKind` and `OnEmpty`, +/// * `CountedStorageMap` expects `Hasher`, `Key`, `Value` and optionally `QueryKind` and `OnEmpty`, +/// * `StorageDoubleMap` expects `Hasher1`, `Key1`, `Hasher2`, `Key2`, `Value` and optionally +/// `QueryKind` and `OnEmpty`. +/// +/// For unnamed generic arguments: Their first generic must be `_` as it is replaced by the +/// macro and other generic must declared as a normal generic type declaration. +/// +/// The `Prefix` generic written by the macro is generated using +/// `PalletInfo::name::>()` and the name of the storage type. E.g. if runtime names +/// the pallet "MyExample" then the storage `type Foo = ...` should use the prefix: +/// `Twox128(b"MyExample") ++ Twox128(b"Foo")`. +/// +/// For the `CountedStorageMap` variant, the `Prefix` also implements +/// `CountedStorageMapInstance`. It also associates a `CounterPrefix`, which is implemented the +/// same as above, but the storage prefix is prepend with `"CounterFor"`. E.g. if runtime names +/// the pallet "MyExample" then the storage `type Foo = CountedStorageaMap<...>` will store +/// its counter at the prefix: `Twox128(b"MyExample") ++ Twox128(b"CounterForFoo")`. +/// +/// E.g: +/// +/// ```ignore +/// #[pallet::storage] +/// pub(super) type MyStorage = StorageMap; +/// ``` +/// +/// In this case the final prefix used by the map is `Twox128(b"MyExample") ++ +/// Twox128(b"OtherName")`. +#[proc_macro_attribute] +pub fn storage(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The optional attribute `#[pallet::getter(fn $my_getter_fn_name)]` allows you to define a +/// getter function on `Pallet`. +/// +/// Also see [`pallet::storage`](`macro@storage`) +#[proc_macro_attribute] +pub fn getter(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The optional attribute `#[pallet::storage_prefix = "SomeName"]` allows you to define the +/// storage prefix to use. This is helpful if you wish to rename the storage field but don't +/// want to perform a migration. +/// +/// E.g: +/// +/// ```ignore +/// #[pallet::storage] +/// #[pallet::storage_prefix = "foo"] +/// #[pallet::getter(fn my_storage)] +/// pub(super) type MyStorage = StorageMap; +/// ``` +/// +/// or +/// +/// ```ignore +/// #[pallet::storage] +/// #[pallet::getter(fn my_storage)] +/// pub(super) type MyStorage = StorageMap<_, Blake2_128Concat, u32, u32>; +/// ``` +#[proc_macro_attribute] +pub fn storage_prefix(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The optional attribute `#[pallet::unbounded]` declares the storage as unbounded. When +/// implementating the storage info (when `#[pallet::generate_storage_info]` is specified on +/// the pallet struct placeholder), the size of the storage will be declared as unbounded. This +/// can be useful for storage which can never go into PoV (Proof of Validity). +#[proc_macro_attribute] +pub fn unbounded(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The optional attribute `#[pallet::whitelist_storage]` will declare the +/// storage as whitelisted from benchmarking. Doing so will exclude reads of +/// that value's storage key from counting towards weight calculations during +/// benchmarking. +/// +/// This attribute should only be attached to storages that are known to be +/// read/used in every block. This will result in a more accurate benchmarking weight. +/// +/// ### Example +/// ```ignore +/// #[pallet::storage] +/// #[pallet::whitelist_storage] +/// pub(super) type Number = StorageValue<_, T::BlockNumber, ValueQuery>; +/// ``` +/// +/// NOTE: As with all `pallet::*` attributes, this one _must_ be written as +/// `#[pallet::whitelist_storage]` and can only be placed inside a `pallet` module in order for +/// it to work properly. +#[proc_macro_attribute] +pub fn whitelist_storage(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::type_value]` attribute lets you define a struct implementing the `Get` trait +/// to ease the use of storage types. This attribute is meant to be used alongside +/// [`#[pallet::storage]`](`macro@storage`) to define a storage's default value. This attribute +/// can be used multiple times. +/// +/// Item must be defined as: +/// +/// ```ignore +/// #[pallet::type_value] +/// fn $MyDefaultName<$some_generic>() -> $default_type $optional_where_clause { $expr } +/// ``` +/// +/// I.e.: a function definition with generics none or `T: Config` and a returned type. +/// +/// E.g.: +/// +/// ```ignore +/// #[pallet::type_value] +/// fn MyDefault() -> T::Balance { 3.into() } +/// ``` +/// +/// ## Macro expansion +/// +/// The macro renames the function to some internal name, generates a struct with the original +/// name of the function and its generic, and implements `Get<$ReturnType>` by calling the user +/// defined function. +#[proc_macro_attribute] +pub fn type_value(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::genesis_config]` attribute allows you to define the genesis configuration +/// for the pallet. +/// +/// Item is defined as either an enum or a struct. It needs to be public and implement the +/// trait `GenesisBuild` with [`#[pallet::genesis_build]`](`macro@genesis_build`). The type +/// generics are constrained to be either none, or `T` or `T: Config`. +/// +/// E.g: +/// +/// ```ignore +/// #[pallet::genesis_config] +/// pub struct GenesisConfig { +/// _myfield: BalanceOf, +/// } +/// ``` +#[proc_macro_attribute] +pub fn genesis_config(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::genesis_build]` attribute allows you to define how `genesis_configuration` +/// is built. This takes as input the `GenesisConfig` type (as `self`) and constructs the pallet's +/// initial state. +/// +/// The impl must be defined as: +/// +/// ```ignore +/// #[pallet::genesis_build] +/// impl GenesisBuild for GenesisConfig<$maybe_generics> { +/// fn build(&self) { $expr } +/// } +/// ``` +/// +/// I.e. a trait implementation with generic `T: Config`, of trait `GenesisBuild` on +/// type `GenesisConfig` with generics none or `T`. +/// +/// E.g.: +/// +/// ```ignore +/// #[pallet::genesis_build] +/// impl GenesisBuild for GenesisConfig { +/// fn build(&self) {} +/// } +/// ``` +/// +/// ## Macro expansion +/// +/// The macro will add the following attribute: +/// * `#[cfg(feature = "std")]` +/// +/// The macro will implement `sp_runtime::BuildModuleGenesisStorage` using `()` as a second +/// generic for non-instantiable pallets. +#[proc_macro_attribute] +pub fn genesis_build(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::inherent]` attribute allows the pallet to provide some +/// [inherent](https://docs.substrate.io/fundamentals/transaction-types/#inherent-transactions). +/// An inherent is some piece of data that is inserted by a block authoring node at block +/// creation time and can either be accepted or rejected by validators based on whether the +/// data falls within an acceptable range. +/// +/// The most common inherent is the `timestamp` that is inserted into every block. Since there +/// is no way to validate timestamps, validators simply check that the timestamp reported by +/// the block authoring node falls within an acceptable range. +/// +/// Item must be defined as: +/// +/// ```ignore +/// #[pallet::inherent] +/// impl ProvideInherent for Pallet { +/// // ... regular trait implementation +/// } +/// ``` +/// +/// I.e. a trait implementation with bound `T: Config`, of trait `ProvideInherent` for type +/// `Pallet`, and some optional where clause. +/// +/// ## Macro expansion +/// +/// The macro currently makes no use of this information, but it might use this information in +/// the future to give information directly to `construct_runtime`. +#[proc_macro_attribute] +pub fn inherent(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::validate_unsigned]` attribute allows the pallet to validate some unsigned +/// transaction: +/// +/// Item must be defined as: +/// +/// ```ignore +/// #[pallet::validate_unsigned] +/// impl ValidateUnsigned for Pallet { +/// // ... regular trait implementation +/// } +/// ``` +/// +/// I.e. a trait implementation with bound `T: Config`, of trait `ValidateUnsigned` for type +/// `Pallet`, and some optional where clause. +/// +/// NOTE: There is also the `sp_runtime::traits::SignedExtension` trait that can be used to add +/// some specific logic for transaction validation. +/// +/// ## Macro expansion +/// +/// The macro currently makes no use of this information, but it might use this information in +/// the future to give information directly to `construct_runtime`. +#[proc_macro_attribute] +pub fn validate_unsigned(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// The `#[pallet::origin]` attribute allows you to define some origin for the pallet. +/// +/// Item must be either a type alias, an enum, or a struct. It needs to be public. +/// +/// E.g.: +/// +/// ```ignore +/// #[pallet::origin] +/// pub struct Origin(PhantomData<(T)>); +/// ``` +/// +/// **WARNING**: modifying origin changes the outer runtime origin. This outer runtime origin +/// can be stored on-chain (e.g. in `pallet-scheduler`), thus any change must be done with care +/// as it might require some migration. +/// +/// NOTE: for instantiable pallets, the origin must be generic over `T` and `I`. +#[proc_macro_attribute] +pub fn origin(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} diff --git a/frame/support/procedural/src/match_and_insert.rs b/frame/support/procedural/src/match_and_insert.rs new file mode 100644 index 0000000000000..79d1da7549c1d --- /dev/null +++ b/frame/support/procedural/src/match_and_insert.rs @@ -0,0 +1,159 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Implementation of the `match_and_insert` macro. + +use proc_macro2::{Group, Span, TokenStream, TokenTree}; +use std::iter::once; +use syn::spanned::Spanned; + +mod keyword { + syn::custom_keyword!(target); + syn::custom_keyword!(pattern); + syn::custom_keyword!(tokens); +} + +pub fn match_and_insert(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let MatchAndInsertDef { pattern, tokens, target } = + syn::parse_macro_input!(input as MatchAndInsertDef); + + match expand_in_stream(&pattern, &mut Some(tokens), target) { + Ok(stream) => stream.into(), + Err(err) => err.to_compile_error().into(), + } +} + +struct MatchAndInsertDef { + // Token stream to search and insert tokens into. + target: TokenStream, + // Pattern to match against, this is ensured to have no TokenTree::Group nor TokenTree::Literal + // (i.e. contains only Punct or Ident), and not being empty. + pattern: Vec, + // Token stream to insert after the match pattern. + tokens: TokenStream, +} + +impl syn::parse::Parse for MatchAndInsertDef { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut target; + let _ = input.parse::()?; + let _ = input.parse::()?; + let _replace_with_bracket: syn::token::Bracket = syn::bracketed!(target in input); + let _replace_with_brace: syn::token::Brace = syn::braced!(target in target); + let target = target.parse()?; + + let mut pattern; + let _ = input.parse::()?; + let _ = input.parse::()?; + let _replace_with_bracket: syn::token::Bracket = syn::bracketed!(pattern in input); + let _replace_with_brace: syn::token::Brace = syn::braced!(pattern in pattern); + let pattern = pattern.parse::()?.into_iter().collect::>(); + + if let Some(t) = pattern.iter().find(|t| matches!(t, TokenTree::Group(_))) { + return Err(syn::Error::new(t.span(), "Unexpected group token tree")) + } + if let Some(t) = pattern.iter().find(|t| matches!(t, TokenTree::Literal(_))) { + return Err(syn::Error::new(t.span(), "Unexpected literal token tree")) + } + + if pattern.is_empty() { + return Err(syn::Error::new(Span::call_site(), "empty match pattern is invalid")) + } + + let mut tokens; + let _ = input.parse::()?; + let _ = input.parse::()?; + let _replace_with_bracket: syn::token::Bracket = syn::bracketed!(tokens in input); + let _replace_with_brace: syn::token::Brace = syn::braced!(tokens in tokens); + let tokens = tokens.parse()?; + + Ok(Self { tokens, pattern, target }) + } +} + +// Insert `tokens` after the first matching `pattern`. +// `tokens` must be some (Option is used for internal simplification). +// `pattern` must not be empty and should only contain Ident or Punct. +fn expand_in_stream( + pattern: &[TokenTree], + tokens: &mut Option, + stream: TokenStream, +) -> syn::Result { + assert!( + tokens.is_some(), + "`tokens` must be some, Option is used because `tokens` is used only once" + ); + assert!( + !pattern.is_empty(), + "`pattern` must not be empty, otherwise there is nothing to match against" + ); + + let stream_span = stream.span(); + let mut stream = stream.into_iter(); + let mut extended = TokenStream::new(); + let mut match_cursor = 0; + + while let Some(token) = stream.next() { + match token { + TokenTree::Group(group) => { + match_cursor = 0; + let group_stream = group.stream(); + match expand_in_stream(pattern, tokens, group_stream) { + Ok(s) => { + extended.extend(once(TokenTree::Group(Group::new(group.delimiter(), s)))); + extended.extend(stream); + return Ok(extended) + }, + Err(_) => { + extended.extend(once(TokenTree::Group(group))); + }, + } + }, + other => { + advance_match_cursor(&other, pattern, &mut match_cursor); + + extended.extend(once(other)); + + if match_cursor == pattern.len() { + extended + .extend(once(tokens.take().expect("tokens is used to replace only once"))); + extended.extend(stream); + return Ok(extended) + } + }, + } + } + // if we reach this point, it means the stream is empty and we haven't found a matching pattern + let msg = format!("Cannot find pattern `{:?}` in given token stream", pattern); + Err(syn::Error::new(stream_span, msg)) +} + +fn advance_match_cursor(other: &TokenTree, pattern: &[TokenTree], match_cursor: &mut usize) { + use TokenTree::{Ident, Punct}; + + let does_match_other_pattern = match (other, &pattern[*match_cursor]) { + (Ident(i1), Ident(i2)) => i1 == i2, + (Punct(p1), Punct(p2)) => p1.as_char() == p2.as_char(), + _ => false, + }; + + if does_match_other_pattern { + *match_cursor += 1; + } else { + *match_cursor = 0; + } +} diff --git a/frame/support/procedural/src/pallet/expand/call.rs b/frame/support/procedural/src/pallet/expand/call.rs index 8f7bcdccaf22d..6b166e6726d38 100644 --- a/frame/support/procedural/src/pallet/expand/call.rs +++ b/frame/support/procedural/src/pallet/expand/call.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +16,7 @@ // limitations under the License. use crate::{pallet::Def, COUNTER}; +use quote::ToTokens; use syn::spanned::Spanned; /// @@ -31,7 +32,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { (span, where_clause, methods, docs) }, - None => (def.item.span(), None, Vec::new(), Vec::new()), + None => (def.item.span(), def.config.where_clause.clone(), Vec::new(), Vec::new()), }; let frame_support = &def.frame_support; let frame_system = &def.frame_system; @@ -42,6 +43,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { let pallet_ident = &def.pallet_struct.pallet; let fn_name = methods.iter().map(|method| &method.name).collect::>(); + let call_index = methods.iter().map(|method| method.call_index).collect::>(); let new_call_variant_fn_name = fn_name .iter() .map(|fn_name| quote::format_ident!("new_call_variant_{}", fn_name)) @@ -68,7 +70,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { .args .iter() .map(|(_, name, _)| { - syn::Ident::new(&name.to_string().trim_start_matches('_'), name.span()) + syn::Ident::new(name.to_string().trim_start_matches('_'), name.span()) }) .collect::>() }) @@ -137,6 +139,44 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { let count = COUNTER.with(|counter| counter.borrow_mut().inc()); let macro_ident = syn::Ident::new(&format!("__is_call_part_defined_{}", count), span); + let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; + + // Wrap all calls inside of storage layers + if let Some(syn::Item::Impl(item_impl)) = def + .call + .as_ref() + .map(|c| &mut def.item.content.as_mut().expect("Checked by def parser").1[c.index]) + { + item_impl.items.iter_mut().for_each(|i| { + if let syn::ImplItem::Method(method) = i { + let block = &method.block; + method.block = syn::parse_quote! {{ + // We execute all dispatchable in a new storage layer, allowing them + // to return an error at any point, and undoing any storage changes. + #frame_support::storage::with_storage_layer(|| #block) + }}; + } + }); + } + + // Extracts #[allow] attributes, necessary so that we don't run into compiler warnings + let maybe_allow_attrs = methods + .iter() + .map(|method| { + method + .attrs + .iter() + .find(|attr| { + if let Ok(syn::Meta::List(syn::MetaList { path, .. })) = attr.parse_meta() { + path.segments.last().map(|seg| seg.ident == "allow").unwrap_or(false) + } else { + false + } + }) + .map_or(proc_macro2::TokenStream::new(), |attr| attr.to_token_stream()) + }) + .collect::>(); + quote::quote_spanned!(span => #[doc(hidden)] pub mod __substrate_call_check { @@ -164,7 +204,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { )] #[codec(encode_bound())] #[codec(decode_bound())] - #[scale_info(skip_type_params(#type_use_gen), capture_docs = "always")] + #[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)] #[allow(non_camel_case_types)] pub enum #call_ident<#type_decl_bounded_gen> #where_clause { #[doc(hidden)] @@ -175,8 +215,12 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { ), #( #( #[doc = #fn_doc] )* + #[codec(index = #call_index)] #fn_name { - #( #args_compact_attr #args_name_stripped: #args_type ),* + #( + #[allow(missing_docs)] + #args_compact_attr #args_name_stripped: #args_type + ),* }, )* } @@ -230,6 +274,10 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { } } + // Deprecated, but will warn when used + #[allow(deprecated)] + impl<#type_impl_gen> #frame_support::weights::GetDispatchInfo for #call_ident<#type_use_gen> #where_clause {} + impl<#type_impl_gen> #frame_support::dispatch::GetCallName for #call_ident<#type_use_gen> #where_clause { @@ -249,10 +297,10 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { for #call_ident<#type_use_gen> #where_clause { - type Origin = #frame_system::pallet_prelude::OriginFor; + type RuntimeOrigin = #frame_system::pallet_prelude::OriginFor; fn dispatch_bypass_filter( self, - origin: Self::Origin + origin: Self::RuntimeOrigin ) -> #frame_support::dispatch::DispatchResultWithPostInfo { match self { #( @@ -260,6 +308,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #frame_support::sp_tracing::enter_span!( #frame_support::sp_tracing::trace_span!(stringify!(#fn_name)) ); + #maybe_allow_attrs <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) .map(Into::into).map_err(Into::into) }, @@ -275,7 +324,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { impl<#type_impl_gen> #frame_support::dispatch::Callable for #pallet_ident<#type_use_gen> #where_clause { - type Call = #call_ident<#type_use_gen>; + type RuntimeCall = #call_ident<#type_use_gen>; } impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clause { diff --git a/frame/support/procedural/src/pallet/expand/config.rs b/frame/support/procedural/src/pallet/expand/config.rs index dad26ccad6dc1..8ad34361d684a 100644 --- a/frame/support/procedural/src/pallet/expand/config.rs +++ b/frame/support/procedural/src/pallet/expand/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/expand/constants.rs b/frame/support/procedural/src/pallet/expand/constants.rs index 7cc245e8089df..1f9526f9cebe0 100644 --- a/frame/support/procedural/src/pallet/expand/constants.rs +++ b/frame/support/procedural/src/pallet/expand/constants.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,8 @@ struct ConstDef { pub doc: Vec, /// default_byte implementation pub default_byte_impl: proc_macro2::TokenStream, + /// Constant name for Metadata (optional) + pub metadata_name: Option, } /// @@ -35,6 +37,7 @@ pub fn expand_constants(def: &mut Def) -> proc_macro2::TokenStream { let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site()); let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site()); let pallet_ident = &def.pallet_struct.pallet; + let trait_use_gen = &def.trait_use_generics(proc_macro2::Span::call_site()); let mut where_clauses = vec![&def.config.where_clause]; where_clauses.extend(def.extra_constants.iter().map(|d| &d.where_clause)); @@ -49,9 +52,11 @@ pub fn expand_constants(def: &mut Def) -> proc_macro2::TokenStream { type_: const_.type_.clone(), doc: const_.doc.clone(), default_byte_impl: quote::quote!( - let value = >::get(); + let value = <::#ident as + #frame_support::traits::Get<#const_type>>::get(); #frame_support::codec::Encode::encode(&value) ), + metadata_name: None, } }); @@ -66,14 +71,17 @@ pub fn expand_constants(def: &mut Def) -> proc_macro2::TokenStream { let value = >::#ident(); #frame_support::codec::Encode::encode(&value) ), + metadata_name: const_.metadata_name.clone(), } }); let consts = config_consts.chain(extra_consts).map(|const_| { let const_type = &const_.type_; - let ident = &const_.ident; - let ident_str = format!("{}", ident); - let doc = const_.doc.clone().into_iter(); + let ident_str = format!("{}", const_.metadata_name.unwrap_or(const_.ident)); + + let no_docs = vec![]; + let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &const_.doc }; + let default_byte_impl = &const_.default_byte_impl; quote::quote!({ diff --git a/frame/support/procedural/src/pallet/expand/error.rs b/frame/support/procedural/src/pallet/expand/error.rs index 7a058bb32c922..5a8487b09de5c 100644 --- a/frame/support/procedural/src/pallet/expand/error.rs +++ b/frame/support/procedural/src/pallet/expand/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,20 +15,48 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::pallet::Def; +use crate::{ + pallet::{parse::error::VariantField, Def}, + COUNTER, +}; use frame_support_procedural_tools::get_doc_literals; +use syn::spanned::Spanned; /// /// * impl various trait on Error pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { - let error = if let Some(error) = &def.error { error } else { return Default::default() }; + let count = COUNTER.with(|counter| counter.borrow_mut().inc()); + let error_token_unique_id = + syn::Ident::new(&format!("__tt_error_token_{}", count), def.item.span()); - let error_ident = &error.error; let frame_support = &def.frame_support; let frame_system = &def.frame_system; + let config_where_clause = &def.config.where_clause; + + let error = if let Some(error) = &def.error { + error + } else { + return quote::quote! { + #[macro_export] + #[doc(hidden)] + macro_rules! #error_token_unique_id { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support::)*tt_return! { + $caller + } + }; + } + + pub use #error_token_unique_id as tt_error_token; + } + }; + + let error_ident = &error.error; let type_impl_gen = &def.type_impl_generics(error.attr_span); let type_use_gen = &def.type_use_generics(error.attr_span); - let config_where_clause = &def.config.where_clause; let phantom_variant: syn::Variant = syn::parse_quote!( #[doc(hidden)] @@ -39,13 +67,19 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { ) ); - let as_u8_matches = error.variants.iter().enumerate().map( - |(i, (variant, _))| quote::quote_spanned!(error.attr_span => Self::#variant => #i as u8,), - ); - - let as_str_matches = error.variants.iter().map(|(variant, _)| { - let variant_str = format!("{}", variant); - quote::quote_spanned!(error.attr_span => Self::#variant => #variant_str,) + let as_str_matches = error.variants.iter().map(|(variant, field_ty, _)| { + let variant_str = variant.to_string(); + match field_ty { + Some(VariantField { is_named: true }) => { + quote::quote_spanned!(error.attr_span => Self::#variant { .. } => #variant_str,) + }, + Some(VariantField { is_named: false }) => { + quote::quote_spanned!(error.attr_span => Self::#variant(..) => #variant_str,) + }, + None => { + quote::quote_spanned!(error.attr_span => Self::#variant => #variant_str,) + }, + } }); let error_item = { @@ -58,18 +92,26 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { }; error_item.variants.insert(0, phantom_variant); + + let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; + // derive TypeInfo for error metadata - error_item - .attrs - .push(syn::parse_quote!( #[derive(#frame_support::scale_info::TypeInfo)] )); + error_item.attrs.push(syn::parse_quote! { + #[derive( + #frame_support::codec::Encode, + #frame_support::codec::Decode, + #frame_support::scale_info::TypeInfo, + #frame_support::PalletError, + )] + }); error_item.attrs.push(syn::parse_quote!( - #[scale_info(skip_type_params(#type_use_gen), capture_docs = "always")] + #[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)] )); if get_doc_literals(&error_item.attrs).is_empty() { error_item.attrs.push(syn::parse_quote!( #[doc = r" - Custom [dispatch errors](https://substrate.dev/docs/en/knowledgebase/runtime/errors) + Custom [dispatch errors](https://docs.substrate.io/main-docs/build/events-errors/) of this pallet. "] )); @@ -87,13 +129,7 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { } impl<#type_impl_gen> #error_ident<#type_use_gen> #config_where_clause { - pub fn as_u8(&self) -> u8 { - match &self { - Self::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), - #( #as_u8_matches )* - } - } - + #[doc(hidden)] pub fn as_str(&self) -> &'static str { match &self { Self::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), @@ -115,18 +151,37 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { #config_where_clause { fn from(err: #error_ident<#type_use_gen>) -> Self { + use #frame_support::codec::Encode; let index = < ::PalletInfo as #frame_support::traits::PalletInfo >::index::>() .expect("Every active module has an index in the runtime; qed") as u8; + let mut encoded = err.encode(); + encoded.resize(#frame_support::MAX_MODULE_ERROR_ENCODED_SIZE, 0); - #frame_support::sp_runtime::DispatchError::Module { + #frame_support::sp_runtime::DispatchError::Module(#frame_support::sp_runtime::ModuleError { index, - error: err.as_u8(), + error: TryInto::try_into(encoded).expect("encoded error is resized to be equal to the maximum encoded error size; qed"), message: Some(err.as_str()), - } + }) } } + + #[macro_export] + #[doc(hidden)] + macro_rules! #error_token_unique_id { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support::)*tt_return! { + $caller + error = [{ #error_ident }] + } + }; + } + + pub use #error_token_unique_id as tt_error_token; ) } diff --git a/frame/support/procedural/src/pallet/expand/event.rs b/frame/support/procedural/src/pallet/expand/event.rs index ebd2d7aeabaff..abed680eb245e 100644 --- a/frame/support/procedural/src/pallet/expand/event.rs +++ b/frame/support/procedural/src/pallet/expand/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -61,7 +61,8 @@ pub fn expand_event(def: &mut Def) -> proc_macro2::TokenStream { let event_where_clause = &event.where_clause; // NOTE: actually event where clause must be a subset of config where clause because of - // `type Event: From>`. But we merge either way for potential better error message + // `type RuntimeEvent: From>`. But we merge either way for potential better error + // message let completed_where_clause = super::merge_where_clauses(&[&event.where_clause, &def.config.where_clause]); @@ -98,7 +99,7 @@ pub fn expand_event(def: &mut Def) -> proc_macro2::TokenStream { if get_doc_literals(&event_item.attrs).is_empty() { event_item.attrs.push(syn::parse_quote!( #[doc = r" - The [event](https://substrate.dev/docs/en/knowledgebase/runtime/events) emitted + The [event](https://docs.substrate.io/main-docs/build/events-errors/) emitted by this pallet. "] )); @@ -117,9 +118,11 @@ pub fn expand_event(def: &mut Def) -> proc_macro2::TokenStream { )] )); - // skip requirement for type params to implement `TypeInfo`, and require docs capture + let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; + + // skip requirement for type params to implement `TypeInfo`, and set docs capture event_item.attrs.push(syn::parse_quote!( - #[scale_info(skip_type_params(#event_use_gen), capture_docs = "always")] + #[scale_info(skip_type_params(#event_use_gen), capture_docs = #capture_docs)] )); let deposit_event = if let Some(deposit_event) = &event.deposit_event { @@ -134,13 +137,13 @@ pub fn expand_event(def: &mut Def) -> proc_macro2::TokenStream { impl<#type_impl_gen> Pallet<#type_use_gen> #completed_where_clause { #fn_vis fn deposit_event(event: Event<#event_use_gen>) { let event = < - ::Event as + ::RuntimeEvent as From> >::from(event); let event = < - ::Event as - Into<::Event> + ::RuntimeEvent as + Into<::RuntimeEvent> >::into(event); <#frame_system::Pallet>::deposit_event(event) diff --git a/frame/support/procedural/src/pallet/expand/genesis_build.rs b/frame/support/procedural/src/pallet/expand/genesis_build.rs index 06acaf324254c..d19476779011b 100644 --- a/frame/support/procedural/src/pallet/expand/genesis_build.rs +++ b/frame/support/procedural/src/pallet/expand/genesis_build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ use crate::pallet::Def; /// /// * implement the trait `sp_runtime::BuildModuleGenesisStorage` -/// * add #[cfg(features = "std")] to GenesisBuild implementation. +/// * add #[cfg(feature = "std")] to GenesisBuild implementation. pub fn expand_genesis_build(def: &mut Def) -> proc_macro2::TokenStream { let genesis_config = if let Some(genesis_config) = &def.genesis_config { genesis_config diff --git a/frame/support/procedural/src/pallet/expand/genesis_config.rs b/frame/support/procedural/src/pallet/expand/genesis_config.rs index 4bbba2c05908e..739e85e0d1ced 100644 --- a/frame/support/procedural/src/pallet/expand/genesis_config.rs +++ b/frame/support/procedural/src/pallet/expand/genesis_config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -85,11 +85,11 @@ pub fn expand_genesis_config(def: &mut Def) -> proc_macro2::TokenStream { syn::Item::Enum(syn::ItemEnum { attrs, .. }) | syn::Item::Struct(syn::ItemStruct { attrs, .. }) | syn::Item::Type(syn::ItemType { attrs, .. }) => { - if get_doc_literals(&attrs).is_empty() { + if get_doc_literals(attrs).is_empty() { attrs.push(syn::parse_quote!( #[doc = r" Can be used to configure the - [genesis state](https://substrate.dev/docs/en/knowledgebase/integrate/chain-spec#the-genesis-state) + [genesis state](https://docs.substrate.io/v3/runtime/chain-specs#the-genesis-state) of this pallet. "] )); diff --git a/frame/support/procedural/src/pallet/expand/hooks.rs b/frame/support/procedural/src/pallet/expand/hooks.rs index e0b7e3669da43..d8d009cf3c940 100644 --- a/frame/support/procedural/src/pallet/expand/hooks.rs +++ b/frame/support/procedural/src/pallet/expand/hooks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ use crate::pallet::Def; -/// /// * implement the individual traits using the Hooks trait pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { let (where_clause, span, has_runtime_upgrade) = match def.hooks.as_ref() { @@ -27,7 +26,7 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { let has_runtime_upgrade = hooks.has_runtime_upgrade; (where_clause, span, has_runtime_upgrade) }, - None => (None, def.pallet_struct.attr_span, false), + None => (def.config.where_clause.clone(), def.pallet_struct.attr_span, false), }; let frame_support = &def.frame_support; @@ -59,6 +58,19 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { } }; + let log_try_state = quote::quote! { + let pallet_name = < + ::PalletInfo + as + #frame_support::traits::PalletInfo + >::name::().expect("Every active pallet has a name in the runtime; qed"); + #frame_support::log::debug!( + target: #frame_support::LOG_TARGET, + "🩺 try-state pallet {:?}", + pallet_name, + ); + }; + let hooks_impl = if def.hooks.is_none() { let frame_system = &def.frame_system; quote::quote! { @@ -148,7 +160,7 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { + fn pre_upgrade() -> Result<#frame_support::sp_std::vec::Vec, &'static str> { < Self as @@ -157,12 +169,12 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { } #[cfg(feature = "try-runtime")] - fn post_upgrade() -> Result<(), &'static str> { + fn post_upgrade(state: #frame_support::sp_std::vec::Vec) -> Result<(), &'static str> { < Self as #frame_support::traits::Hooks<::BlockNumber> - >::post_upgrade() + >::post_upgrade(state) } } @@ -191,5 +203,23 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { >::integrity_test() } } + + #[cfg(feature = "try-runtime")] + impl<#type_impl_gen> + #frame_support::traits::TryState<::BlockNumber> + for #pallet_ident<#type_use_gen> #where_clause + { + fn try_state( + n: ::BlockNumber, + _s: #frame_support::traits::TryStateSelect + ) -> Result<(), &'static str> { + #log_try_state + < + Self as #frame_support::traits::Hooks< + ::BlockNumber + > + >::try_state(n) + } + } ) } diff --git a/frame/support/procedural/src/pallet/expand/inherent.rs b/frame/support/procedural/src/pallet/expand/inherent.rs index 185211ecd4df2..71d95f958fcd2 100644 --- a/frame/support/procedural/src/pallet/expand/inherent.rs +++ b/frame/support/procedural/src/pallet/expand/inherent.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/expand/instances.rs b/frame/support/procedural/src/pallet/expand/instances.rs index 2ecb5ec481ac4..52dc887635503 100644 --- a/frame/support/procedural/src/pallet/expand/instances.rs +++ b/frame/support/procedural/src/pallet/expand/instances.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/expand/mod.rs b/frame/support/procedural/src/pallet/expand/mod.rs index 1c8883977c765..c33d2386700b2 100644 --- a/frame/support/procedural/src/pallet/expand/mod.rs +++ b/frame/support/procedural/src/pallet/expand/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,6 +29,7 @@ mod origin; mod pallet_struct; mod storage; mod store_trait; +mod tt_default_parts; mod type_value; mod validate_unsigned; @@ -67,14 +68,15 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { let type_values = type_value::expand_type_values(&mut def); let origins = origin::expand_origins(&mut def); let validate_unsigned = validate_unsigned::expand_validate_unsigned(&mut def); + let tt_default_parts = tt_default_parts::expand_tt_default_parts(&mut def); if get_doc_literals(&def.item.attrs).is_empty() { def.item.attrs.push(syn::parse_quote!( #[doc = r" The module that hosts all the - [FRAME](https://substrate.dev/docs/en/knowledgebase/runtime/frame) + [FRAME](https://docs.substrate.io/main-docs/build/events-errors/) types needed to add this pallet to a - [runtime](https://substrate.dev/docs/en/knowledgebase/runtime/). + runtime. "] )); } @@ -96,6 +98,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { #type_values #origins #validate_unsigned + #tt_default_parts ); def.item diff --git a/frame/support/procedural/src/pallet/expand/origin.rs b/frame/support/procedural/src/pallet/expand/origin.rs index 987512f69a02b..721fc781a907c 100644 --- a/frame/support/procedural/src/pallet/expand/origin.rs +++ b/frame/support/procedural/src/pallet/expand/origin.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/expand/pallet_struct.rs b/frame/support/procedural/src/pallet/expand/pallet_struct.rs index a217742fec55d..e5941a33fee13 100644 --- a/frame/support/procedural/src/pallet/expand/pallet_struct.rs +++ b/frame/support/procedural/src/pallet/expand/pallet_struct.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,7 +62,7 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { if get_doc_literals(&pallet_item.attrs).is_empty() { pallet_item.attrs.push(syn::parse_quote!( #[doc = r" - The [pallet](https://substrate.dev/docs/en/knowledgebase/runtime/pallets) implementing + The [pallet](https://docs.substrate.io/reference/frame-pallets/#pallets) implementing the on-chain logic. "] )); @@ -81,6 +81,7 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { let error_ident = &error_def.error; quote::quote_spanned!(def.pallet_struct.attr_span => impl<#type_impl_gen> #pallet_ident<#type_use_gen> #config_where_clause { + #[doc(hidden)] pub fn error_metadata() -> Option<#frame_support::metadata::PalletErrorMetadata> { Some(#frame_support::metadata::PalletErrorMetadata { ty: #frame_support::scale_info::meta_type::<#error_ident<#type_use_gen>>() @@ -91,6 +92,7 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { } else { quote::quote_spanned!(def.pallet_struct.attr_span => impl<#type_impl_gen> #pallet_ident<#type_use_gen> #config_where_clause { + #[doc(hidden)] pub fn error_metadata() -> Option<#frame_support::metadata::PalletErrorMetadata> { None } @@ -98,28 +100,39 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { ) }; - // Depending on the flag `generate_storage_info` we use partial or full storage info from - // storage. - let (storage_info_span, storage_info_trait, storage_info_method) = - if let Some(span) = def.pallet_struct.generate_storage_info { - ( - span, - quote::quote_spanned!(span => StorageInfoTrait), - quote::quote_spanned!(span => storage_info), - ) - } else { - let span = def.pallet_struct.attr_span; - ( - span, - quote::quote_spanned!(span => PartialStorageInfoTrait), - quote::quote_spanned!(span => partial_storage_info), - ) - }; + let storage_info_span = + def.pallet_struct.without_storage_info.unwrap_or(def.pallet_struct.attr_span); let storage_names = &def.storages.iter().map(|storage| &storage.ident).collect::>(); let storage_cfg_attrs = &def.storages.iter().map(|storage| &storage.cfg_attrs).collect::>(); + // Depending on the flag `without_storage_info` and the storage attribute `unbounded`, we use + // partial or full storage info from storage. + let storage_info_traits = &def + .storages + .iter() + .map(|storage| { + if storage.unbounded || def.pallet_struct.without_storage_info.is_some() { + quote::quote_spanned!(storage_info_span => PartialStorageInfoTrait) + } else { + quote::quote_spanned!(storage_info_span => StorageInfoTrait) + } + }) + .collect::>(); + + let storage_info_methods = &def + .storages + .iter() + .map(|storage| { + if storage.unbounded || def.pallet_struct.without_storage_info.is_some() { + quote::quote_spanned!(storage_info_span => partial_storage_info) + } else { + quote::quote_spanned!(storage_info_span => storage_info) + } + }) + .collect::>(); + let storage_info = quote::quote_spanned!(storage_info_span => impl<#type_impl_gen> #frame_support::traits::StorageInfoTrait for #pallet_ident<#type_use_gen> @@ -136,8 +149,8 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { { let mut storage_info = < #storage_names<#type_use_gen> - as #frame_support::traits::#storage_info_trait - >::#storage_info_method(); + as #frame_support::traits::#storage_info_traits + >::#storage_info_methods(); res.append(&mut storage_info); } )* @@ -153,6 +166,24 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { quote::quote! { #frame_support::traits::StorageVersion::default() } }; + let whitelisted_storage_idents: Vec = def + .storages + .iter() + .filter_map(|s| s.whitelisted.then_some(s.ident.clone())) + .collect(); + + let whitelisted_storage_keys_impl = quote::quote![ + use #frame_support::traits::{StorageInfoTrait, TrackedStorageKey, WhitelistedStorageKeys}; + impl<#type_impl_gen> WhitelistedStorageKeys for #pallet_ident<#type_use_gen> #storages_where_clauses { + fn whitelisted_storage_keys() -> #frame_support::sp_std::vec::Vec { + use #frame_support::sp_std::vec; + vec![#( + TrackedStorageKey::new(#whitelisted_storage_idents::<#type_use_gen>::hashed_key().to_vec()) + ),*] + } + } + ]; + quote::quote_spanned!(def.pallet_struct.attr_span => #pallet_error_metadata @@ -208,8 +239,38 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { .expect("Pallet is part of the runtime because pallet `Config` trait is \ implemented by the runtime") } + + fn module_name() -> &'static str { + < + ::PalletInfo as #frame_support::traits::PalletInfo + >::module_name::() + .expect("Pallet is part of the runtime because pallet `Config` trait is \ + implemented by the runtime") + } + + fn crate_version() -> #frame_support::traits::CrateVersion { + #frame_support::crate_to_crate_version!() + } + } + + impl<#type_impl_gen> #frame_support::traits::PalletsInfoAccess + for #pallet_ident<#type_use_gen> + #config_where_clause + { + fn count() -> usize { 1 } + fn infos() -> #frame_support::sp_std::vec::Vec<#frame_support::traits::PalletInfoData> { + use #frame_support::traits::PalletInfoAccess; + let item = #frame_support::traits::PalletInfoData { + index: Self::index(), + name: Self::name(), + module_name: Self::module_name(), + crate_version: Self::crate_version(), + }; + #frame_support::sp_std::vec![item] + } } #storage_info + #whitelisted_storage_keys_impl ) } diff --git a/frame/support/procedural/src/pallet/expand/storage.rs b/frame/support/procedural/src/pallet/expand/storage.rs index a4f030722f1c1..181f35b545496 100644 --- a/frame/support/procedural/src/pallet/expand/storage.rs +++ b/frame/support/procedural/src/pallet/expand/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,9 @@ use crate::pallet::{ parse::storage::{Metadata, QueryKind, StorageDef, StorageGenerics}, Def, }; -use std::collections::HashMap; +use quote::ToTokens; +use std::{collections::HashMap, ops::IndexMut}; +use syn::spanned::Spanned; /// Generate the prefix_ident related to the storage. /// prefix_ident is used for the prefix struct to be given to storage as first generic param. @@ -74,9 +76,7 @@ fn check_prefix_duplicates( ), ); - if let Some(other_dup_err) = - used_prefixes.insert(counter_prefix.clone(), counter_dup_err.clone()) - { + if let Some(other_dup_err) = used_prefixes.insert(counter_prefix, counter_dup_err.clone()) { let mut err = counter_dup_err; err.combine(other_dup_err); return Err(err) @@ -86,12 +86,28 @@ fn check_prefix_duplicates( Ok(()) } +pub struct ResultOnEmptyStructMetadata { + /// The Rust ident that is going to be used as the name of the OnEmpty struct. + pub name: syn::Ident, + /// The path to the error type being returned by the ResultQuery. + pub error_path: syn::Path, + /// The visibility of the OnEmpty struct. + pub visibility: syn::Visibility, + /// The type of the storage item. + pub value_ty: syn::Type, + /// The name of the pallet error enum variant that is going to be returned. + pub variant_name: syn::Ident, + /// The span used to report compilation errors about the OnEmpty struct. + pub span: proc_macro2::Span, +} + /// /// * if generics are unnamed: replace the first generic `_` by the generated prefix structure /// * if generics are named: reorder the generic, remove their name, and add the missing ones. /// * Add `#[allow(type_alias_bounds)]` -pub fn process_generics(def: &mut Def) -> syn::Result<()> { +pub fn process_generics(def: &mut Def) -> syn::Result> { let frame_support = &def.frame_support; + let mut on_empty_struct_metadata = Vec::new(); for storage_def in def.storages.iter_mut() { let item = &mut def.item.content.as_mut().expect("Checked by def").1[storage_def.index]; @@ -113,7 +129,7 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> { _ => unreachable!("Checked by def"), }; - let prefix_ident = prefix_ident(&storage_def); + let prefix_ident = prefix_ident(storage_def); let type_use_gen = if def.config.has_instance { quote::quote_spanned!(storage_def.attr_span => T, I) } else { @@ -122,27 +138,72 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> { let default_query_kind: syn::Type = syn::parse_quote!(#frame_support::storage::types::OptionQuery); - let default_on_empty: syn::Type = syn::parse_quote!(#frame_support::traits::GetDefault); + let mut default_on_empty = |value_ty: syn::Type| -> syn::Type { + if let Some(QueryKind::ResultQuery(error_path, variant_name)) = + storage_def.query_kind.as_ref() + { + let on_empty_ident = + quote::format_ident!("__Frame_Internal_Get{}Result", storage_def.ident); + on_empty_struct_metadata.push(ResultOnEmptyStructMetadata { + name: on_empty_ident.clone(), + visibility: storage_def.vis.clone(), + value_ty, + error_path: error_path.clone(), + variant_name: variant_name.clone(), + span: storage_def.attr_span, + }); + return syn::parse_quote!(#on_empty_ident) + } + syn::parse_quote!(#frame_support::traits::GetDefault) + }; let default_max_values: syn::Type = syn::parse_quote!(#frame_support::traits::GetDefault); + let set_result_query_type_parameter = |query_type: &mut syn::Type| -> syn::Result<()> { + if let Some(QueryKind::ResultQuery(error_path, _)) = storage_def.query_kind.as_ref() { + if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = + query_type + { + if let Some(seg) = segments.last_mut() { + if let syn::PathArguments::AngleBracketed( + syn::AngleBracketedGenericArguments { args, .. }, + ) = &mut seg.arguments + { + args.clear(); + args.push(syn::GenericArgument::Type(syn::parse_quote!(#error_path))); + } + } + } else { + let msg = format!( + "Invalid pallet::storage, unexpected type for query, expected ResultQuery \ + with 1 type parameter, found `{}`", + query_type.to_token_stream().to_string() + ); + return Err(syn::Error::new(query_type.span(), msg)) + } + } + Ok(()) + }; + if let Some(named_generics) = storage_def.named_generics.clone() { args.args.clear(); args.args.push(syn::parse_quote!( #prefix_ident<#type_use_gen> )); match named_generics { StorageGenerics::Value { value, query_kind, on_empty } => { - args.args.push(syn::GenericArgument::Type(value)); - let query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + args.args.push(syn::GenericArgument::Type(value.clone())); + let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + set_result_query_type_parameter(&mut query_kind)?; args.args.push(syn::GenericArgument::Type(query_kind)); - let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone()); + let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value)); args.args.push(syn::GenericArgument::Type(on_empty)); }, StorageGenerics::Map { hasher, key, value, query_kind, on_empty, max_values } => { args.args.push(syn::GenericArgument::Type(hasher)); args.args.push(syn::GenericArgument::Type(key)); - args.args.push(syn::GenericArgument::Type(value)); - let query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + args.args.push(syn::GenericArgument::Type(value.clone())); + let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + set_result_query_type_parameter(&mut query_kind)?; args.args.push(syn::GenericArgument::Type(query_kind)); - let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone()); + let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value)); args.args.push(syn::GenericArgument::Type(on_empty)); let max_values = max_values.unwrap_or_else(|| default_max_values.clone()); args.args.push(syn::GenericArgument::Type(max_values)); @@ -157,10 +218,11 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> { } => { args.args.push(syn::GenericArgument::Type(hasher)); args.args.push(syn::GenericArgument::Type(key)); - args.args.push(syn::GenericArgument::Type(value)); - let query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + args.args.push(syn::GenericArgument::Type(value.clone())); + let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + set_result_query_type_parameter(&mut query_kind)?; args.args.push(syn::GenericArgument::Type(query_kind)); - let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone()); + let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value)); args.args.push(syn::GenericArgument::Type(on_empty)); let max_values = max_values.unwrap_or_else(|| default_max_values.clone()); args.args.push(syn::GenericArgument::Type(max_values)); @@ -179,20 +241,22 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> { args.args.push(syn::GenericArgument::Type(key1)); args.args.push(syn::GenericArgument::Type(hasher2)); args.args.push(syn::GenericArgument::Type(key2)); - args.args.push(syn::GenericArgument::Type(value)); - let query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + args.args.push(syn::GenericArgument::Type(value.clone())); + let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + set_result_query_type_parameter(&mut query_kind)?; args.args.push(syn::GenericArgument::Type(query_kind)); - let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone()); + let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value)); args.args.push(syn::GenericArgument::Type(on_empty)); let max_values = max_values.unwrap_or_else(|| default_max_values.clone()); args.args.push(syn::GenericArgument::Type(max_values)); }, StorageGenerics::NMap { keygen, value, query_kind, on_empty, max_values } => { args.args.push(syn::GenericArgument::Type(keygen)); - args.args.push(syn::GenericArgument::Type(value)); - let query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + args.args.push(syn::GenericArgument::Type(value.clone())); + let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + set_result_query_type_parameter(&mut query_kind)?; args.args.push(syn::GenericArgument::Type(query_kind)); - let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone()); + let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value)); args.args.push(syn::GenericArgument::Type(on_empty)); let max_values = max_values.unwrap_or_else(|| default_max_values.clone()); args.args.push(syn::GenericArgument::Type(max_values)); @@ -200,10 +264,40 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> { } } else { args.args[0] = syn::parse_quote!( #prefix_ident<#type_use_gen> ); + + let (value_idx, query_idx, on_empty_idx) = match storage_def.metadata { + Metadata::Value { .. } => (1, 2, 3), + Metadata::NMap { .. } => (2, 3, 4), + Metadata::Map { .. } | Metadata::CountedMap { .. } => (3, 4, 5), + Metadata::DoubleMap { .. } => (5, 6, 7), + }; + + if query_idx < args.args.len() { + if let syn::GenericArgument::Type(query_kind) = args.args.index_mut(query_idx) { + set_result_query_type_parameter(query_kind)?; + } + } else if let Some(QueryKind::ResultQuery(error_path, _)) = + storage_def.query_kind.as_ref() + { + args.args.push(syn::GenericArgument::Type(syn::parse_quote!(#error_path))) + } + + // Here, we only need to check if OnEmpty is *not* specified, and if so, then we have to + // generate a default OnEmpty struct for it. + if on_empty_idx >= args.args.len() && + matches!(storage_def.query_kind.as_ref(), Some(QueryKind::ResultQuery(_, _))) + { + let value_ty = match args.args[value_idx].clone() { + syn::GenericArgument::Type(ty) => ty, + _ => unreachable!(), + }; + let on_empty = default_on_empty(value_ty); + args.args.push(syn::GenericArgument::Type(on_empty)); + } } } - Ok(()) + Ok(on_empty_struct_metadata) } /// @@ -214,9 +308,10 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> { /// * Add `#[allow(type_alias_bounds)]` on storages type alias /// * generate metadatas pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { - if let Err(e) = process_generics(def) { - return e.into_compile_error().into() - } + let on_empty_struct_metadata = match process_generics(def) { + Ok(idents) => idents, + Err(e) => return e.into_compile_error(), + }; // Check for duplicate prefixes let mut prefix_set = HashMap::new(); @@ -234,7 +329,8 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { let pallet_ident = &def.pallet_struct.pallet; let entries_builder = def.storages.iter().map(|storage| { - let docs = &storage.docs; + let no_docs = vec![]; + let docs = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &storage.docs }; let ident = &storage.ident; let gen = &def.type_use_generics(storage.attr_span); @@ -278,6 +374,10 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => Option<#value> ), + QueryKind::ResultQuery(error_path, _) => + quote::quote_spanned!(storage.attr_span => + Result<#value, #error_path> + ), QueryKind::ValueQuery => quote::quote!(#value), }; quote::quote_spanned!(storage.attr_span => @@ -297,6 +397,10 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => Option<#value> ), + QueryKind::ResultQuery(error_path, _) => + quote::quote_spanned!(storage.attr_span => + Result<#value, #error_path> + ), QueryKind::ValueQuery => quote::quote!(#value), }; quote::quote_spanned!(storage.attr_span => @@ -318,6 +422,10 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => Option<#value> ), + QueryKind::ResultQuery(error_path, _) => + quote::quote_spanned!(storage.attr_span => + Result<#value, #error_path> + ), QueryKind::ValueQuery => quote::quote!(#value), }; quote::quote_spanned!(storage.attr_span => @@ -339,6 +447,10 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => Option<#value> ), + QueryKind::ResultQuery(error_path, _) => + quote::quote_spanned!(storage.attr_span => + Result<#value, #error_path> + ), QueryKind::ValueQuery => quote::quote!(#value), }; quote::quote_spanned!(storage.attr_span => @@ -362,6 +474,10 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => Option<#value> ), + QueryKind::ResultQuery(error_path, _) => + quote::quote_spanned!(storage.attr_span => + Result<#value, #error_path> + ), QueryKind::ValueQuery => quote::quote!(#value), }; quote::quote_spanned!(storage.attr_span => @@ -392,7 +508,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { let prefix_structs = def.storages.iter().map(|storage_def| { let type_impl_gen = &def.type_impl_generics(storage_def.attr_span); let type_use_gen = &def.type_use_generics(storage_def.attr_span); - let prefix_struct_ident = prefix_ident(&storage_def); + let prefix_struct_ident = prefix_ident(storage_def); let prefix_struct_vis = &storage_def.vis; let prefix_struct_const = storage_def.prefix(); let config_where_clause = &def.config.where_clause; @@ -405,6 +521,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { quote::quote_spanned!(storage_def.attr_span => #(#cfg_attrs)* + #[doc(hidden)] #prefix_struct_vis struct #counter_prefix_struct_ident<#type_use_gen>( core::marker::PhantomData<(#type_use_gen,)> ); @@ -438,6 +555,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { #maybe_counter #(#cfg_attrs)* + #[doc(hidden)] #prefix_struct_vis struct #prefix_struct_ident<#type_use_gen>( core::marker::PhantomData<(#type_use_gen,)> ); @@ -458,6 +576,61 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { ) }); + let on_empty_structs = on_empty_struct_metadata.into_iter().map(|metadata| { + use crate::pallet::parse::GenericKind; + use syn::{GenericArgument, Path, PathArguments, PathSegment, Type, TypePath}; + + let ResultOnEmptyStructMetadata { + name, + visibility, + value_ty, + error_path, + variant_name, + span, + } = metadata; + + let generic_kind = match error_path.segments.last() { + Some(PathSegment { arguments: PathArguments::AngleBracketed(args), .. }) => { + let (has_config, has_instance) = + args.args.iter().fold((false, false), |(has_config, has_instance), arg| { + match arg { + GenericArgument::Type(Type::Path(TypePath { + path: Path { segments, .. }, + .. + })) => { + let maybe_config = + segments.first().map_or(false, |seg| seg.ident == "T"); + let maybe_instance = + segments.first().map_or(false, |seg| seg.ident == "I"); + + (has_config || maybe_config, has_instance || maybe_instance) + }, + _ => (has_config, has_instance), + } + }); + GenericKind::from_gens(has_config, has_instance).unwrap_or(GenericKind::None) + }, + _ => GenericKind::None, + }; + let type_impl_gen = generic_kind.type_impl_gen(proc_macro2::Span::call_site()); + let config_where_clause = &def.config.where_clause; + + quote::quote_spanned!(span => + #[doc(hidden)] + #[allow(non_camel_case_types)] + #visibility struct #name; + + impl<#type_impl_gen> #frame_support::traits::Get> + for #name + #config_where_clause + { + fn get() -> Result<#value_ty, #error_path> { + Err(<#error_path>::#variant_name) + } + } + ) + }); + let mut where_clauses = vec![&def.config.where_clause]; where_clauses.extend(def.storages.iter().map(|storage| &storage.where_clause)); let completed_where_clause = super::merge_where_clauses(&where_clauses); @@ -488,5 +661,6 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { #( #getters )* #( #prefix_structs )* + #( #on_empty_structs )* ) } diff --git a/frame/support/procedural/src/pallet/expand/store_trait.rs b/frame/support/procedural/src/pallet/expand/store_trait.rs index 36cc08b732fe5..4fb4f46143a14 100644 --- a/frame/support/procedural/src/pallet/expand/store_trait.rs +++ b/frame/support/procedural/src/pallet/expand/store_trait.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs new file mode 100644 index 0000000000000..173dcc0e2ac3f --- /dev/null +++ b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs @@ -0,0 +1,82 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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::{pallet::Def, COUNTER}; +use syn::spanned::Spanned; + +/// Generate the `tt_default_parts` macro. +pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { + let count = COUNTER.with(|counter| counter.borrow_mut().inc()); + let default_parts_unique_id = + syn::Ident::new(&format!("__tt_default_parts_{}", count), def.item.span()); + + let call_part = def.call.as_ref().map(|_| quote::quote!(Call,)); + + let storage_part = (!def.storages.is_empty()).then(|| quote::quote!(Storage,)); + + let event_part = def.event.as_ref().map(|event| { + let gen = event.gen_kind.is_generic().then(|| quote::quote!( )); + quote::quote!( Event #gen , ) + }); + + let origin_part = def.origin.as_ref().map(|origin| { + let gen = origin.is_generic.then(|| quote::quote!( )); + quote::quote!( Origin #gen , ) + }); + + let config_part = def.genesis_config.as_ref().map(|genesis_config| { + let gen = genesis_config.gen_kind.is_generic().then(|| quote::quote!( )); + quote::quote!( Config #gen , ) + }); + + let inherent_part = def.inherent.as_ref().map(|_| quote::quote!(Inherent,)); + + let validate_unsigned_part = + def.validate_unsigned.as_ref().map(|_| quote::quote!(ValidateUnsigned,)); + + quote::quote!( + // This macro follows the conventions as laid out by the `tt-call` crate. It does not + // accept any arguments and simply returns the pallet parts, separated by commas, then + // wrapped inside of braces and finally prepended with double colons, to the caller inside + // of a key named `tokens`. + // + // We need to accept a frame_support argument here, because this macro gets expanded on the + // crate that called the `construct_runtime!` macro, and said crate may have renamed + // frame-support, and so we need to pass in the frame-support path that said crate + // recognizes. + #[macro_export] + #[doc(hidden)] + macro_rules! #default_parts_unique_id { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support)*::tt_return! { + $caller + tokens = [{ + ::{ + Pallet, #call_part #storage_part #event_part #origin_part #config_part + #inherent_part #validate_unsigned_part + } + }] + } + }; + } + + pub use #default_parts_unique_id as tt_default_parts; + ) +} diff --git a/frame/support/procedural/src/pallet/expand/type_value.rs b/frame/support/procedural/src/pallet/expand/type_value.rs index 535a187773807..abe52459a791d 100644 --- a/frame/support/procedural/src/pallet/expand/type_value.rs +++ b/frame/support/procedural/src/pallet/expand/type_value.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,7 +59,10 @@ pub fn expand_type_values(def: &mut Def) -> proc_macro2::TokenStream { (Default::default(), Default::default()) }; + let docs = &type_value.docs; + expand.extend(quote::quote_spanned!(type_value.attr_span => + #( #[doc = #docs] )* #vis struct #ident<#struct_use_gen>(core::marker::PhantomData<((), #struct_use_gen)>); impl<#struct_impl_gen> #frame_support::traits::Get<#type_> for #ident<#struct_use_gen> #where_clause diff --git a/frame/support/procedural/src/pallet/expand/validate_unsigned.rs b/frame/support/procedural/src/pallet/expand/validate_unsigned.rs index 5f30d712e9a51..b49ba2b5b02ea 100644 --- a/frame/support/procedural/src/pallet/expand/validate_unsigned.rs +++ b/frame/support/procedural/src/pallet/expand/validate_unsigned.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/mod.rs b/frame/support/procedural/src/pallet/mod.rs index 93797906d04d9..3f85be81c1f7d 100644 --- a/frame/support/procedural/src/pallet/mod.rs +++ b/frame/support/procedural/src/pallet/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,20 +31,30 @@ mod parse; pub use parse::Def; use syn::spanned::Spanned; +mod keyword { + syn::custom_keyword!(dev_mode); +} + pub fn pallet( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { + let mut dev_mode = false; if !attr.is_empty() { - let msg = - "Invalid pallet macro call: expected no attributes, e.g. macro call must be just \ - `#[frame_support::pallet]` or `#[pallet]`"; - let span = proc_macro2::TokenStream::from(attr).span(); - return syn::Error::new(span, msg).to_compile_error().into() + if let Ok(_) = syn::parse::(attr.clone()) { + dev_mode = true; + } else { + let msg = "Invalid pallet macro call: unexpected attribute. Macro call must be \ + bare, such as `#[frame_support::pallet]` or `#[pallet]`, or must specify the \ + `dev_mode` attribute, such as `#[frame_support::pallet(dev_mode)]` or \ + #[pallet(dev_mode)]."; + let span = proc_macro2::TokenStream::from(attr).span(); + return syn::Error::new(span, msg).to_compile_error().into() + } } let item = syn::parse_macro_input!(item as syn::ItemMod); - match parse::Def::try_from(item) { + match parse::Def::try_from(item, dev_mode) { Ok(def) => expand::expand(def).into(), Err(e) => e.to_compile_error().into(), } diff --git a/frame/support/procedural/src/pallet/parse/call.rs b/frame/support/procedural/src/pallet/parse/call.rs index 0563568f33311..fbca9a52c767c 100644 --- a/frame/support/procedural/src/pallet/parse/call.rs +++ b/frame/support/procedural/src/pallet/parse/call.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ use super::helper; use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; +use std::collections::HashMap; use syn::spanned::Spanned; /// List of additional token to be used for parsing. @@ -25,6 +26,7 @@ mod keyword { syn::custom_keyword!(Call); syn::custom_keyword!(OriginFor); syn::custom_keyword!(weight); + syn::custom_keyword!(call_index); syn::custom_keyword!(compact); syn::custom_keyword!(T); syn::custom_keyword!(pallet); @@ -46,8 +48,8 @@ pub struct CallDef { pub docs: Vec, } -#[derive(Clone)] /// Definition of dispatchable typically: `#[weight...] fn foo(origin .., param1: ...) -> ..` +#[derive(Clone)] pub struct CallVariantDef { /// Function name. pub name: syn::Ident, @@ -55,14 +57,19 @@ pub struct CallVariantDef { pub args: Vec<(bool, syn::Ident, Box)>, /// Weight formula. pub weight: syn::Expr, + /// Call index of the dispatchable. + pub call_index: u8, /// Docs, used for metadata. pub docs: Vec, + /// Attributes annotated at the top of the dispatchable function. + pub attrs: Vec, } /// Attributes for functions in call impl block. -/// Parse for `#[pallet::weight(expr)]` -pub struct FunctionAttr { - weight: syn::Expr, +/// Parse for `#[pallet::weight(expr)]` or `#[pallet::call_index(expr)] +pub enum FunctionAttr { + CallIndex(u8), + Weight(syn::Expr), } impl syn::parse::Parse for FunctionAttr { @@ -72,11 +79,22 @@ impl syn::parse::Parse for FunctionAttr { syn::bracketed!(content in input); content.parse::()?; content.parse::()?; - content.parse::()?; - let weight_content; - syn::parenthesized!(weight_content in content); - Ok(FunctionAttr { weight: weight_content.parse::()? }) + let lookahead = content.lookahead1(); + if lookahead.peek(keyword::weight) { + content.parse::()?; + let weight_content; + syn::parenthesized!(weight_content in content); + Ok(FunctionAttr::Weight(weight_content.parse::()?)) + } else if lookahead.peek(keyword::call_index) { + content.parse::()?; + let call_index_content; + syn::parenthesized!(call_index_content in content); + let index = call_index_content.parse::()?; + Ok(FunctionAttr::CallIndex(index.base10_parse()?)) + } else { + Err(lookahead.error()) + } } } @@ -126,26 +144,30 @@ impl CallDef { attr_span: proc_macro2::Span, index: usize, item: &mut syn::Item, + dev_mode: bool, ) -> syn::Result { - let item = if let syn::Item::Impl(item) = item { + let item_impl = if let syn::Item::Impl(item) = item { item } else { return Err(syn::Error::new(item.span(), "Invalid pallet::call, expected item impl")) }; - let mut instances = vec![]; - instances.push(helper::check_impl_gen(&item.generics, item.impl_token.span())?); - instances.push(helper::check_pallet_struct_usage(&item.self_ty)?); + let instances = vec![ + helper::check_impl_gen(&item_impl.generics, item_impl.impl_token.span())?, + helper::check_pallet_struct_usage(&item_impl.self_ty)?, + ]; - if let Some((_, _, for_)) = item.trait_ { + if let Some((_, _, for_)) = item_impl.trait_ { let msg = "Invalid pallet::call, expected no trait ident as in \ `impl<..> Pallet<..> { .. }`"; return Err(syn::Error::new(for_.span(), msg)) } let mut methods = vec![]; - for impl_item in &mut item.items { - if let syn::ImplItem::Method(method) = impl_item { + let mut indices = HashMap::new(); + let mut last_index: Option = None; + for item in &mut item_impl.items { + if let syn::ImplItem::Method(method) = item { if !matches!(method.vis, syn::Visibility::Public(_)) { let msg = "Invalid pallet::call, dispatchable function must be public: \ `pub fn`"; @@ -169,7 +191,7 @@ impl CallDef { return Err(syn::Error::new(method.sig.span(), msg)) }, Some(syn::FnArg::Typed(arg)) => { - check_dispatchable_first_arg_type(&*arg.ty)?; + check_dispatchable_first_arg_type(&arg.ty)?; }, } @@ -181,18 +203,66 @@ impl CallDef { return Err(syn::Error::new(method.sig.span(), msg)) } - let mut call_var_attrs: Vec = - helper::take_item_pallet_attrs(&mut method.attrs)?; + let (mut weight_attrs, mut call_idx_attrs): (Vec, Vec) = + helper::take_item_pallet_attrs(&mut method.attrs)?.into_iter().partition( + |attr| { + if let FunctionAttr::Weight(_) = attr { + true + } else { + false + } + }, + ); + + if weight_attrs.is_empty() && dev_mode { + // inject a default O(1) weight when dev mode is enabled and no weight has + // been specified on the call + let empty_weight: syn::Expr = syn::parse(quote::quote!(0).into()) + .expect("we are parsing a quoted string; qed"); + weight_attrs.push(FunctionAttr::Weight(empty_weight)); + } - if call_var_attrs.len() != 1 { - let msg = if call_var_attrs.is_empty() { + if weight_attrs.len() != 1 { + let msg = if weight_attrs.is_empty() { "Invalid pallet::call, requires weight attribute i.e. `#[pallet::weight($expr)]`" } else { "Invalid pallet::call, too many weight attributes given" }; return Err(syn::Error::new(method.sig.span(), msg)) } - let weight = call_var_attrs.pop().unwrap().weight; + let weight = match weight_attrs.pop().unwrap() { + FunctionAttr::Weight(w) => w, + _ => unreachable!("checked during creation of the let binding"), + }; + + if call_idx_attrs.len() > 1 { + let msg = "Invalid pallet::call, too many call_index attributes given"; + return Err(syn::Error::new(method.sig.span(), msg)) + } + let call_index = call_idx_attrs.pop().map(|attr| match attr { + FunctionAttr::CallIndex(idx) => idx, + _ => unreachable!("checked during creation of the let binding"), + }); + + let final_index = match call_index { + Some(i) => i, + None => + last_index.map_or(Some(0), |idx| idx.checked_add(1)).ok_or_else(|| { + let msg = "Call index doesn't fit into u8, index is 256"; + syn::Error::new(method.sig.span(), msg) + })?, + }; + last_index = Some(final_index); + + if let Some(used_fn) = indices.insert(final_index, method.sig.ident.clone()) { + let msg = format!( + "Call indices are conflicting: Both functions {} and {} are at index {}", + used_fn, method.sig.ident, final_index, + ); + let mut err = syn::Error::new(used_fn.span(), &msg); + err.combine(syn::Error::new(method.sig.ident.span(), msg)); + return Err(err) + } let mut args = vec![]; for arg in method.sig.inputs.iter_mut().skip(1) { @@ -222,10 +292,17 @@ impl CallDef { let docs = get_doc_literals(&method.attrs); - methods.push(CallVariantDef { name: method.sig.ident.clone(), weight, args, docs }); + methods.push(CallVariantDef { + name: method.sig.ident.clone(), + weight, + call_index: final_index, + args, + docs, + attrs: method.attrs.clone(), + }); } else { let msg = "Invalid pallet::call, only method accepted"; - return Err(syn::Error::new(impl_item.span(), msg)) + return Err(syn::Error::new(item.span(), msg)) } } @@ -234,8 +311,8 @@ impl CallDef { attr_span, instances, methods, - where_clause: item.generics.where_clause.clone(), - docs: get_doc_literals(&item.attrs), + where_clause: item_impl.generics.where_clause.clone(), + docs: get_doc_literals(&item_impl.attrs), }) } } diff --git a/frame/support/procedural/src/pallet/parse/config.rs b/frame/support/procedural/src/pallet/parse/config.rs index 712c20ffc7b4c..0f3aa69b170ce 100644 --- a/frame/support/procedural/src/pallet/parse/config.rs +++ b/frame/support/procedural/src/pallet/parse/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,6 @@ // limitations under the License. use super::helper; -use core::convert::TryFrom; use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; use syn::spanned::Spanned; @@ -29,6 +28,7 @@ mod keyword { syn::custom_keyword!(I); syn::custom_keyword!(config); syn::custom_keyword!(IsType); + syn::custom_keyword!(RuntimeEvent); syn::custom_keyword!(Event); syn::custom_keyword!(constant); syn::custom_keyword!(frame_system); @@ -43,8 +43,9 @@ pub struct ConfigDef { pub has_instance: bool, /// Const associated type. pub consts_metadata: Vec, - /// Whether the trait has the associated type `Event`, note that those bounds are checked: - /// * `IsType::Event` + /// Whether the trait has the associated type `Event`, note that those bounds are + /// checked: + /// * `IsType::RuntimeEvent` /// * `From` or `From>` or `From>` pub has_event_type: bool, /// The where clause on trait definition but modified so `Self` is `T`. @@ -160,7 +161,7 @@ impl syn::parse::Parse for ConfigBoundParse { } } -/// Parse for `IsType<::Event>` and retrieve `$ident` +/// Parse for `IsType<::RuntimeEvent>` and retrieve `$ident` pub struct IsTypeBoundEventParse(syn::Ident); impl syn::parse::Parse for IsTypeBoundEventParse { @@ -175,7 +176,7 @@ impl syn::parse::Parse for IsTypeBoundEventParse { input.parse::()?; input.parse::]>()?; input.parse::()?; - input.parse::()?; + input.parse::()?; input.parse::]>()?; Ok(Self(ident)) @@ -213,7 +214,7 @@ impl syn::parse::Parse for FromEventParse { } } -/// Check if trait_item is `type Event`, if so checks its bounds are those expected. +/// Check if trait_item is `type RuntimeEvent`, if so checks its bounds are those expected. /// (Event type is reserved type) fn check_event_type( frame_system: &syn::Ident, @@ -221,10 +222,10 @@ fn check_event_type( trait_has_instance: bool, ) -> syn::Result { if let syn::TraitItem::Type(type_) = trait_item { - if type_.ident == "Event" { + if type_.ident == "RuntimeEvent" { // Check event has no generics if !type_.generics.params.is_empty() || type_.generics.where_clause.is_some() { - let msg = "Invalid `type Event`, associated type `Event` is reserved and must have\ + let msg = "Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must have\ no generics nor where_clause"; return Err(syn::Error::new(trait_item.span(), msg)) } @@ -237,8 +238,8 @@ fn check_event_type( if !has_is_type_bound { let msg = format!( - "Invalid `type Event`, associated type `Event` is reserved and must \ - bound: `IsType<::Event>`", + "Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must \ + bound: `IsType<::RuntimeEvent>`", frame_system, ); return Err(syn::Error::new(type_.span(), msg)) @@ -252,14 +253,14 @@ fn check_event_type( let from_event_bound = if let Some(b) = from_event_bound { b } else { - let msg = "Invalid `type Event`, associated type `Event` is reserved and must \ + let msg = "Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must \ bound: `From` or `From>` or `From>`"; return Err(syn::Error::new(type_.span(), msg)) }; if from_event_bound.is_generic && (from_event_bound.has_instance != trait_has_instance) { - let msg = "Invalid `type Event`, associated type `Event` bounds inconsistent \ + let msg = "Invalid `type RuntimeEvent`, associated type `RuntimeEvent` bounds inconsistent \ `From`. Config and generic Event must be both with instance or \ without instance"; return Err(syn::Error::new(type_.span(), msg)) @@ -374,9 +375,10 @@ impl ConfigDef { let found = if item.supertraits.is_empty() { "none".to_string() } else { - let mut found = item.supertraits.iter().fold(String::new(), |acc, s| { - format!("{}`{}`, ", acc, quote::quote!(#s).to_string()) - }); + let mut found = item + .supertraits + .iter() + .fold(String::new(), |acc, s| format!("{}`{}`, ", acc, quote::quote!(#s))); found.pop(); found.pop(); found diff --git a/frame/support/procedural/src/pallet/parse/error.rs b/frame/support/procedural/src/pallet/parse/error.rs index 9c9a95105c53c..c6ce9b37c75a2 100644 --- a/frame/support/procedural/src/pallet/parse/error.rs +++ b/frame/support/procedural/src/pallet/parse/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,20 +18,26 @@ use super::helper; use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; -use syn::spanned::Spanned; +use syn::{spanned::Spanned, Fields}; /// List of additional token to be used for parsing. mod keyword { syn::custom_keyword!(Error); } +/// Records information about the error enum variants. +pub struct VariantField { + /// Whether or not the field is named, i.e. whether it is a tuple variant or struct variant. + pub is_named: bool, +} + /// This checks error declaration as a enum declaration with only variants without fields nor /// discriminant. pub struct ErrorDef { /// The index of error item in pallet module. pub index: usize, - /// Variants ident and doc literals (ordered as declaration order) - pub variants: Vec<(syn::Ident, Vec)>, + /// Variants ident, optional field and doc literals (ordered as declaration order) + pub variants: Vec<(syn::Ident, Option, Vec)>, /// A set of usage of instance, must be check for consistency with trait. pub instances: Vec, /// The keyword error used (contains span). @@ -56,8 +62,8 @@ impl ErrorDef { return Err(syn::Error::new(item.span(), msg)) } - let mut instances = vec![]; - instances.push(helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?); + let instances = + vec![helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?]; if item.generics.where_clause.is_some() { let msg = "Invalid pallet::error, where clause is not allowed on pallet error item"; @@ -70,18 +76,19 @@ impl ErrorDef { .variants .iter() .map(|variant| { - if !matches!(variant.fields, syn::Fields::Unit) { - let msg = "Invalid pallet::error, unexpected fields, must be `Unit`"; - return Err(syn::Error::new(variant.fields.span(), msg)) - } + let field_ty = match &variant.fields { + Fields::Unit => None, + Fields::Named(_) => Some(VariantField { is_named: true }), + Fields::Unnamed(_) => Some(VariantField { is_named: false }), + }; if variant.discriminant.is_some() { - let msg = "Invalid pallet::error, unexpected discriminant, discriminant \ + let msg = "Invalid pallet::error, unexpected discriminant, discriminants \ are not supported"; let span = variant.discriminant.as_ref().unwrap().0.span(); return Err(syn::Error::new(span, msg)) } - Ok((variant.ident.clone(), get_doc_literals(&variant.attrs))) + Ok((variant.ident.clone(), field_ty, get_doc_literals(&variant.attrs))) }) .collect::>()?; diff --git a/frame/support/procedural/src/pallet/parse/event.rs b/frame/support/procedural/src/pallet/parse/event.rs index 33de4aca8b599..e046cacac88e8 100644 --- a/frame/support/procedural/src/pallet/parse/event.rs +++ b/frame/support/procedural/src/pallet/parse/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/parse/extra_constants.rs b/frame/support/procedural/src/pallet/parse/extra_constants.rs index c1324df6c22f1..d8622da08461b 100644 --- a/frame/support/procedural/src/pallet/parse/extra_constants.rs +++ b/frame/support/procedural/src/pallet/parse/extra_constants.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,7 @@ mod keyword { syn::custom_keyword!(compact); syn::custom_keyword!(T); syn::custom_keyword!(pallet); + syn::custom_keyword!(constant_name); } /// Definition of extra constants typically `impl Pallet { ... }` @@ -50,6 +51,29 @@ pub struct ExtraConstantDef { pub type_: syn::Type, /// The doc associated pub doc: Vec, + /// Optional MetaData Name + pub metadata_name: Option, +} + +/// Attributes for functions in extra_constants impl block. +/// Parse for `#[pallet::constant_name(ConstantName)]` +pub struct ExtraConstAttr { + metadata_name: syn::Ident, +} + +impl syn::parse::Parse for ExtraConstAttr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + input.parse::()?; + let content; + syn::bracketed!(content in input); + content.parse::()?; + content.parse::()?; + content.parse::()?; + + let metadata_name; + syn::parenthesized!(metadata_name in content); + Ok(ExtraConstAttr { metadata_name: metadata_name.parse::()? }) + } } impl ExtraConstantsDef { @@ -57,12 +81,16 @@ impl ExtraConstantsDef { let item = if let syn::Item::Impl(item) = item { item } else { - return Err(syn::Error::new(item.span(), "Invalid pallet::call, expected item impl")) + return Err(syn::Error::new( + item.span(), + "Invalid pallet::extra_constants, expected item impl", + )) }; - let mut instances = vec![]; - instances.push(helper::check_impl_gen(&item.generics, item.impl_token.span())?); - instances.push(helper::check_pallet_struct_usage(&item.self_ty)?); + let instances = vec![ + helper::check_impl_gen(&item.generics, item.impl_token.span())?, + helper::check_pallet_struct_usage(&item.self_ty)?, + ]; if let Some((_, _, for_)) = item.trait_ { let msg = "Invalid pallet::call, expected no trait ident as in \ @@ -102,10 +130,23 @@ impl ExtraConstantsDef { syn::ReturnType::Type(_, type_) => *type_.clone(), }; + // parse metadata_name + let mut extra_constant_attrs: Vec = + helper::take_item_pallet_attrs(method)?; + + if extra_constant_attrs.len() > 1 { + let msg = + "Invalid attribute in pallet::constant_name, only one attribute is expected"; + return Err(syn::Error::new(extra_constant_attrs[1].metadata_name.span(), msg)) + } + + let metadata_name = extra_constant_attrs.pop().map(|attr| attr.metadata_name); + extra_constants.push(ExtraConstantDef { ident: method.sig.ident.clone(), type_, doc: get_doc_literals(&method.attrs), + metadata_name, }); } diff --git a/frame/support/procedural/src/pallet/parse/genesis_build.rs b/frame/support/procedural/src/pallet/parse/genesis_build.rs index 82e297b4e26e8..9815b8d2203c4 100644 --- a/frame/support/procedural/src/pallet/parse/genesis_build.rs +++ b/frame/support/procedural/src/pallet/parse/genesis_build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,8 +53,7 @@ impl GenesisBuildDef { })? .1; - let mut instances = vec![]; - instances.push(helper::check_genesis_builder_usage(&item_trait)?); + let instances = vec![helper::check_genesis_builder_usage(item_trait)?]; Ok(Self { attr_span, index, instances, where_clause: item.generics.where_clause.clone() }) } diff --git a/frame/support/procedural/src/pallet/parse/genesis_config.rs b/frame/support/procedural/src/pallet/parse/genesis_config.rs index a0cf7de1a846b..45e765c018aae 100644 --- a/frame/support/procedural/src/pallet/parse/genesis_config.rs +++ b/frame/support/procedural/src/pallet/parse/genesis_config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,7 +49,7 @@ impl GenesisConfigDef { let mut instances = vec![]; // NOTE: GenesisConfig is not allowed to be only generic on I because it is not supported // by construct_runtime. - if let Some(u) = helper::check_type_def_optional_gen(&generics, ident.span())? { + if let Some(u) = helper::check_type_def_optional_gen(generics, ident.span())? { instances.push(u); } diff --git a/frame/support/procedural/src/pallet/parse/helper.rs b/frame/support/procedural/src/pallet/parse/helper.rs index 2590e86b58b0e..8244079173581 100644 --- a/frame/support/procedural/src/pallet/parse/helper.rs +++ b/frame/support/procedural/src/pallet/parse/helper.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -139,6 +139,12 @@ impl MutItemAttrs for syn::ItemMod { } } +impl MutItemAttrs for syn::ImplItemMethod { + fn mut_item_attrs(&mut self) -> Option<&mut Vec> { + Some(&mut self.attrs) + } +} + /// Parse for `()` struct Unit; impl syn::parse::Parse for Unit { diff --git a/frame/support/procedural/src/pallet/parse/hooks.rs b/frame/support/procedural/src/pallet/parse/hooks.rs index 1dd86498f22d5..2dc8f4da47c5f 100644 --- a/frame/support/procedural/src/pallet/parse/hooks.rs +++ b/frame/support/procedural/src/pallet/parse/hooks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,9 +45,10 @@ impl HooksDef { return Err(syn::Error::new(item.span(), msg)) }; - let mut instances = vec![]; - instances.push(helper::check_pallet_struct_usage(&item.self_ty)?); - instances.push(helper::check_impl_gen(&item.generics, item.impl_token.span())?); + let instances = vec![ + helper::check_pallet_struct_usage(&item.self_ty)?, + helper::check_impl_gen(&item.generics, item.impl_token.span())?, + ]; let item_trait = &item .trait_ diff --git a/frame/support/procedural/src/pallet/parse/inherent.rs b/frame/support/procedural/src/pallet/parse/inherent.rs index de5ad8f795db5..a485eed4c40d9 100644 --- a/frame/support/procedural/src/pallet/parse/inherent.rs +++ b/frame/support/procedural/src/pallet/parse/inherent.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,9 +50,10 @@ impl InherentDef { return Err(syn::Error::new(item.span(), msg)) } - let mut instances = vec![]; - instances.push(helper::check_pallet_struct_usage(&item.self_ty)?); - instances.push(helper::check_impl_gen(&item.generics, item.impl_token.span())?); + let instances = vec![ + helper::check_pallet_struct_usage(&item.self_ty)?, + helper::check_impl_gen(&item.generics, item.impl_token.span())?, + ]; Ok(InherentDef { index, instances }) } diff --git a/frame/support/procedural/src/pallet/parse/mod.rs b/frame/support/procedural/src/pallet/parse/mod.rs index 96d4776e805bc..f91159248281c 100644 --- a/frame/support/procedural/src/pallet/parse/mod.rs +++ b/frame/support/procedural/src/pallet/parse/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,10 +59,11 @@ pub struct Def { pub type_values: Vec, pub frame_system: syn::Ident, pub frame_support: syn::Ident, + pub dev_mode: bool, } impl Def { - pub fn try_from(mut item: syn::ItemMod) -> syn::Result { + pub fn try_from(mut item: syn::ItemMod, dev_mode: bool) -> syn::Result { let frame_system = generate_crate_access_2018("frame-system")?; let frame_support = generate_crate_access_2018("frame-support")?; @@ -105,11 +106,11 @@ impl Def { let m = hooks::HooksDef::try_from(span, index, item)?; hooks = Some(m); }, - Some(PalletAttr::Call(span)) if call.is_none() => - call = Some(call::CallDef::try_from(span, index, item)?), + Some(PalletAttr::RuntimeCall(span)) if call.is_none() => + call = Some(call::CallDef::try_from(span, index, item, dev_mode)?), Some(PalletAttr::Error(span)) if error.is_none() => error = Some(error::ErrorDef::try_from(span, index, item)?), - Some(PalletAttr::Event(span)) if event.is_none() => + Some(PalletAttr::RuntimeEvent(span)) if event.is_none() => event = Some(event::EventDef::try_from(span, index, item)?), Some(PalletAttr::GenesisConfig(_)) if genesis_config.is_none() => { let g = genesis_config::GenesisConfigDef::try_from(index, item)?; @@ -119,12 +120,12 @@ impl Def { let g = genesis_build::GenesisBuildDef::try_from(span, index, item)?; genesis_build = Some(g); }, - Some(PalletAttr::Origin(_)) if origin.is_none() => + Some(PalletAttr::RuntimeOrigin(_)) if origin.is_none() => origin = Some(origin::OriginDef::try_from(index, item)?), Some(PalletAttr::Inherent(_)) if inherent.is_none() => inherent = Some(inherent::InherentDef::try_from(index, item)?), Some(PalletAttr::Storage(span)) => - storages.push(storage::StorageDef::try_from(span, index, item)?), + storages.push(storage::StorageDef::try_from(span, index, item, dev_mode)?), Some(PalletAttr::ValidateUnsigned(_)) if validate_unsigned.is_none() => { let v = validate_unsigned::ValidateUnsignedDef::try_from(index, item)?; validate_unsigned = Some(v); @@ -173,6 +174,7 @@ impl Def { type_values, frame_system, frame_support, + dev_mode, }; def.check_instance_usage()?; @@ -182,19 +184,19 @@ impl Def { } /// Check that usage of trait `Event` is consistent with the definition, i.e. it is declared - /// and trait defines type Event, or not declared and no trait associated type. + /// and trait defines type RuntimeEvent, or not declared and no trait associated type. fn check_event_usage(&self) -> syn::Result<()> { match (self.config.has_event_type, self.event.is_some()) { (true, false) => { - let msg = "Invalid usage of Event, `Config` contains associated type `Event`, \ + let msg = "Invalid usage of RuntimeEvent, `Config` contains associated type `RuntimeEvent`, \ but enum `Event` is not declared (i.e. no use of `#[pallet::event]`). \ - Note that type `Event` in trait is reserved to work alongside pallet event."; + Note that type `RuntimeEvent` in trait is reserved to work alongside pallet event."; Err(syn::Error::new(proc_macro2::Span::call_site(), msg)) }, (false, true) => { - let msg = "Invalid usage of Event, `Config` contains no associated type \ - `Event`, but enum `Event` is declared (in use of `#[pallet::event]`). \ - An Event associated type must be declare on trait `Config`."; + let msg = "Invalid usage of RuntimeEvent, `Config` contains no associated type \ + `RuntimeEvent`, but enum `Event` is declared (in use of `#[pallet::event]`). \ + An RuntimeEvent associated type must be declare on trait `Config`."; Err(syn::Error::new(proc_macro2::Span::call_site(), msg)) }, _ => Ok(()), @@ -391,10 +393,10 @@ enum PalletAttr { Config(proc_macro2::Span), Pallet(proc_macro2::Span), Hooks(proc_macro2::Span), - Call(proc_macro2::Span), + RuntimeCall(proc_macro2::Span), Error(proc_macro2::Span), - Event(proc_macro2::Span), - Origin(proc_macro2::Span), + RuntimeEvent(proc_macro2::Span), + RuntimeOrigin(proc_macro2::Span), Inherent(proc_macro2::Span), Storage(proc_macro2::Span), GenesisConfig(proc_macro2::Span), @@ -410,10 +412,10 @@ impl PalletAttr { Self::Config(span) => *span, Self::Pallet(span) => *span, Self::Hooks(span) => *span, - Self::Call(span) => *span, + Self::RuntimeCall(span) => *span, Self::Error(span) => *span, - Self::Event(span) => *span, - Self::Origin(span) => *span, + Self::RuntimeEvent(span) => *span, + Self::RuntimeOrigin(span) => *span, Self::Inherent(span) => *span, Self::Storage(span) => *span, Self::GenesisConfig(span) => *span, @@ -441,13 +443,13 @@ impl syn::parse::Parse for PalletAttr { } else if lookahead.peek(keyword::hooks) { Ok(PalletAttr::Hooks(content.parse::()?.span())) } else if lookahead.peek(keyword::call) { - Ok(PalletAttr::Call(content.parse::()?.span())) + Ok(PalletAttr::RuntimeCall(content.parse::()?.span())) } else if lookahead.peek(keyword::error) { Ok(PalletAttr::Error(content.parse::()?.span())) } else if lookahead.peek(keyword::event) { - Ok(PalletAttr::Event(content.parse::()?.span())) + Ok(PalletAttr::RuntimeEvent(content.parse::()?.span())) } else if lookahead.peek(keyword::origin) { - Ok(PalletAttr::Origin(content.parse::()?.span())) + Ok(PalletAttr::RuntimeOrigin(content.parse::()?.span())) } else if lookahead.peek(keyword::inherent) { Ok(PalletAttr::Inherent(content.parse::()?.span())) } else if lookahead.peek(keyword::storage) { diff --git a/frame/support/procedural/src/pallet/parse/origin.rs b/frame/support/procedural/src/pallet/parse/origin.rs index c4e1197ac511c..89929e3e8dbfc 100644 --- a/frame/support/procedural/src/pallet/parse/origin.rs +++ b/frame/support/procedural/src/pallet/parse/origin.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,7 +50,7 @@ impl OriginDef { let is_generic = !generics.params.is_empty(); let mut instances = vec![]; - if let Some(u) = helper::check_type_def_optional_gen(&generics, item.span())? { + if let Some(u) = helper::check_type_def_optional_gen(generics, item.span())? { instances.push(u); } else { // construct_runtime only allow generic event for instantiable pallet. diff --git a/frame/support/procedural/src/pallet/parse/pallet_struct.rs b/frame/support/procedural/src/pallet/parse/pallet_struct.rs index 278f46e13818e..a96c310b6f1ca 100644 --- a/frame/support/procedural/src/pallet/parse/pallet_struct.rs +++ b/frame/support/procedural/src/pallet/parse/pallet_struct.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ mod keyword { syn::custom_keyword!(pallet); syn::custom_keyword!(Pallet); syn::custom_keyword!(generate_store); - syn::custom_keyword!(generate_storage_info); + syn::custom_keyword!(without_storage_info); syn::custom_keyword!(storage_version); syn::custom_keyword!(Store); } @@ -43,18 +43,18 @@ pub struct PalletStructDef { pub attr_span: proc_macro2::Span, /// Whether to specify the storages max encoded len when implementing `StorageInfoTrait`. /// Contains the span of the attribute. - pub generate_storage_info: Option, + pub without_storage_info: Option, /// The current storage version of the pallet. pub storage_version: Option, } /// Parse for one variant of: /// * `#[pallet::generate_store($vis trait Store)]` -/// * `#[pallet::generate_storage_info]` +/// * `#[pallet::without_storage_info]` /// * `#[pallet::storage_version(STORAGE_VERSION)]` pub enum PalletStructAttr { GenerateStore { span: proc_macro2::Span, vis: syn::Visibility, keyword: keyword::Store }, - GenerateStorageInfoTrait(proc_macro2::Span), + WithoutStorageInfoTrait(proc_macro2::Span), StorageVersion { storage_version: syn::Path, span: proc_macro2::Span }, } @@ -62,7 +62,7 @@ impl PalletStructAttr { fn span(&self) -> proc_macro2::Span { match self { Self::GenerateStore { span, .. } => *span, - Self::GenerateStorageInfoTrait(span) => *span, + Self::WithoutStorageInfoTrait(span) => *span, Self::StorageVersion { span, .. } => *span, } } @@ -86,9 +86,9 @@ impl syn::parse::Parse for PalletStructAttr { generate_content.parse::()?; let keyword = generate_content.parse::()?; Ok(Self::GenerateStore { vis, keyword, span }) - } else if lookahead.peek(keyword::generate_storage_info) { - let span = content.parse::()?.span(); - Ok(Self::GenerateStorageInfoTrait(span)) + } else if lookahead.peek(keyword::without_storage_info) { + let span = content.parse::()?.span(); + Ok(Self::WithoutStorageInfoTrait(span)) } else if lookahead.peek(keyword::storage_version) { let span = content.parse::()?.span(); @@ -117,7 +117,7 @@ impl PalletStructDef { }; let mut store = None; - let mut generate_storage_info = None; + let mut without_storage_info = None; let mut storage_version_found = None; let struct_attrs: Vec = helper::take_item_pallet_attrs(&mut item.attrs)?; @@ -126,16 +126,16 @@ impl PalletStructDef { PalletStructAttr::GenerateStore { vis, keyword, .. } if store.is_none() => { store = Some((vis, keyword)); }, - PalletStructAttr::GenerateStorageInfoTrait(span) - if generate_storage_info.is_none() => + PalletStructAttr::WithoutStorageInfoTrait(span) + if without_storage_info.is_none() => { - generate_storage_info = Some(span); - } + without_storage_info = Some(span); + }, PalletStructAttr::StorageVersion { storage_version, .. } if storage_version_found.is_none() => { storage_version_found = Some(storage_version); - } + }, attr => { let msg = "Unexpected duplicated attribute"; return Err(syn::Error::new(attr.span(), msg)) @@ -155,8 +155,8 @@ impl PalletStructDef { return Err(syn::Error::new(item.generics.where_clause.span(), msg)) } - let mut instances = vec![]; - instances.push(helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?); + let instances = + vec![helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?]; Ok(Self { index, @@ -164,7 +164,7 @@ impl PalletStructDef { pallet, store, attr_span, - generate_storage_info, + without_storage_info, storage_version: storage_version_found, }) } diff --git a/frame/support/procedural/src/pallet/parse/storage.rs b/frame/support/procedural/src/pallet/parse/storage.rs index 8075daacb6f44..8b551ab31d6c3 100644 --- a/frame/support/procedural/src/pallet/parse/storage.rs +++ b/frame/support/procedural/src/pallet/parse/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,22 +27,32 @@ mod keyword { syn::custom_keyword!(pallet); syn::custom_keyword!(getter); syn::custom_keyword!(storage_prefix); + syn::custom_keyword!(unbounded); + syn::custom_keyword!(whitelist_storage); syn::custom_keyword!(OptionQuery); + syn::custom_keyword!(ResultQuery); syn::custom_keyword!(ValueQuery); } /// Parse for one of the following: /// * `#[pallet::getter(fn dummy)]` /// * `#[pallet::storage_prefix = "CustomName"]` +/// * `#[pallet::unbounded]` +/// * `#[pallet::whitelist_storage] pub enum PalletStorageAttr { Getter(syn::Ident, proc_macro2::Span), StorageName(syn::LitStr, proc_macro2::Span), + Unbounded(proc_macro2::Span), + WhitelistStorage(proc_macro2::Span), } impl PalletStorageAttr { fn attr_span(&self) -> proc_macro2::Span { match self { - Self::Getter(_, span) | Self::StorageName(_, span) => *span, + Self::Getter(_, span) | + Self::StorageName(_, span) | + Self::Unbounded(span) | + Self::WhitelistStorage(span) => *span, } } } @@ -76,12 +86,51 @@ impl syn::parse::Parse for PalletStorageAttr { })?; Ok(Self::StorageName(renamed_prefix, attr_span)) + } else if lookahead.peek(keyword::unbounded) { + content.parse::()?; + + Ok(Self::Unbounded(attr_span)) + } else if lookahead.peek(keyword::whitelist_storage) { + content.parse::()?; + Ok(Self::WhitelistStorage(attr_span)) } else { Err(lookahead.error()) } } } +struct PalletStorageAttrInfo { + getter: Option, + rename_as: Option, + unbounded: bool, + whitelisted: bool, +} + +impl PalletStorageAttrInfo { + fn from_attrs(attrs: Vec) -> syn::Result { + let mut getter = None; + let mut rename_as = None; + let mut unbounded = false; + let mut whitelisted = false; + for attr in attrs { + match attr { + PalletStorageAttr::Getter(ident, ..) if getter.is_none() => getter = Some(ident), + PalletStorageAttr::StorageName(name, ..) if rename_as.is_none() => + rename_as = Some(name), + PalletStorageAttr::Unbounded(..) if !unbounded => unbounded = true, + PalletStorageAttr::WhitelistStorage(..) if !whitelisted => whitelisted = true, + attr => + return Err(syn::Error::new( + attr.attr_span(), + "Invalid attribute: Duplicate attribute", + )), + } + } + + Ok(PalletStorageAttrInfo { getter, rename_as, unbounded, whitelisted }) + } +} + /// The value and key types used by storages. Needed to expand metadata. pub enum Metadata { Value { value: syn::Type }, @@ -93,6 +142,7 @@ pub enum Metadata { pub enum QueryKind { OptionQuery, + ResultQuery(syn::Path, syn::Ident), ValueQuery, } @@ -117,7 +167,7 @@ pub struct StorageDef { /// Optional expression that evaluates to a type that can be used as StoragePrefix instead of /// ident. pub rename_as: Option, - /// Whereas the querytype of the storage is OptionQuery or ValueQuery. + /// Whereas the querytype of the storage is OptionQuery, ResultQuery or ValueQuery. /// Note that this is best effort as it can't be determined when QueryKind is generic, and /// result can be false if user do some unexpected type alias. pub query_kind: Option, @@ -131,6 +181,10 @@ pub struct StorageDef { /// generics of the storage. /// If generics are not named, this is none. pub named_generics: Option, + /// If the value stored in this storage is unbounded. + pub unbounded: bool, + /// Whether or not reads to this storage key will be ignored by benchmarking + pub whitelisted: bool, } /// The parsed generic from the @@ -234,7 +288,7 @@ fn check_generics( ); e.pop(); e.pop(); - e.push_str("."); + e.push('.'); e }; @@ -501,8 +555,8 @@ fn process_generics( found => { let msg = format!( "Invalid pallet::storage, expected ident: `StorageValue` or \ - `StorageMap` or `StorageDoubleMap` or `StorageNMap` in order to expand metadata, \ - found `{}`.", + `StorageMap` or `CountedStorageMap` or `StorageDoubleMap` or `StorageNMap` \ + in order to expand metadata, found `{}`.", found, ); return Err(syn::Error::new(segment.ident.span(), msg)) @@ -512,7 +566,7 @@ fn process_generics( let args_span = segment.arguments.span(); let args = match &segment.arguments { - syn::PathArguments::AngleBracketed(args) if args.args.len() != 0 => args, + syn::PathArguments::AngleBracketed(args) if !args.args.is_empty() => args, _ => { let msg = "Invalid pallet::storage, invalid number of generic generic arguments, \ expect more that 0 generic arguments."; @@ -608,19 +662,23 @@ impl StorageDef { self.rename_as .as_ref() .map(syn::LitStr::value) - .unwrap_or(self.ident.to_string()) + .unwrap_or_else(|| self.ident.to_string()) } /// Return either the span of the ident or the span of the literal in the /// #[storage_prefix] attribute pub fn prefix_span(&self) -> proc_macro2::Span { - self.rename_as.as_ref().map(syn::LitStr::span).unwrap_or(self.ident.span()) + self.rename_as + .as_ref() + .map(syn::LitStr::span) + .unwrap_or_else(|| self.ident.span()) } pub fn try_from( attr_span: proc_macro2::Span, index: usize, item: &mut syn::Item, + dev_mode: bool, ) -> syn::Result { let item = if let syn::Item::Type(item) = item { item @@ -629,30 +687,14 @@ impl StorageDef { }; let attrs: Vec = helper::take_item_pallet_attrs(&mut item.attrs)?; - let (mut getters, mut names) = attrs - .into_iter() - .partition::, _>(|attr| matches!(attr, PalletStorageAttr::Getter(..))); - if getters.len() > 1 { - let msg = "Invalid pallet::storage, multiple argument pallet::getter found"; - return Err(syn::Error::new(getters[1].attr_span(), msg)) - } - if names.len() > 1 { - let msg = "Invalid pallet::storage, multiple argument pallet::storage_prefix found"; - return Err(syn::Error::new(names[1].attr_span(), msg)) - } - let getter = getters.pop().map(|attr| match attr { - PalletStorageAttr::Getter(ident, _) => ident, - _ => unreachable!(), - }); - let rename_as = names.pop().map(|attr| match attr { - PalletStorageAttr::StorageName(lit, _) => lit, - _ => unreachable!(), - }); + let PalletStorageAttrInfo { getter, rename_as, mut unbounded, whitelisted } = + PalletStorageAttrInfo::from_attrs(attrs)?; + // set all storages to be unbounded if dev_mode is enabled + unbounded |= dev_mode; let cfg_attrs = helper::get_item_cfg_attrs(&item.attrs); - let mut instances = vec![]; - instances.push(helper::check_type_def_gen(&item.generics, item.ident.span())?); + let instances = vec![helper::check_type_def_gen(&item.generics, item.ident.span())?]; let where_clause = item.generics.where_clause.clone(); let docs = get_doc_literals(&item.attrs); @@ -672,22 +714,106 @@ impl StorageDef { let (named_generics, metadata, query_kind) = process_generics(&typ.path.segments[0])?; let query_kind = query_kind - .map(|query_kind| match query_kind { - syn::Type::Path(path) - if path.path.segments.last().map_or(false, |s| s.ident == "OptionQuery") => - Some(QueryKind::OptionQuery), - syn::Type::Path(path) - if path.path.segments.last().map_or(false, |s| s.ident == "ValueQuery") => - Some(QueryKind::ValueQuery), - _ => None, + .map(|query_kind| { + use syn::{ + AngleBracketedGenericArguments, GenericArgument, Path, PathArguments, Type, + TypePath, + }; + + let result_query = match query_kind { + Type::Path(path) + if path + .path + .segments + .last() + .map_or(false, |s| s.ident == "OptionQuery") => + return Ok(Some(QueryKind::OptionQuery)), + Type::Path(TypePath { path: Path { segments, .. }, .. }) + if segments.last().map_or(false, |s| s.ident == "ResultQuery") => + segments + .last() + .expect("segments is checked to have the last value; qed") + .clone(), + Type::Path(path) + if path.path.segments.last().map_or(false, |s| s.ident == "ValueQuery") => + return Ok(Some(QueryKind::ValueQuery)), + _ => return Ok(None), + }; + + let error_type = match result_query.arguments { + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + args, .. + }) => { + if args.len() != 1 { + let msg = format!( + "Invalid pallet::storage, unexpected number of generic arguments \ + for ResultQuery, expected 1 type argument, found {}", + args.len(), + ); + return Err(syn::Error::new(args.span(), msg)) + } + + args[0].clone() + }, + args => { + let msg = format!( + "Invalid pallet::storage, unexpected generic args for ResultQuery, \ + expected angle-bracketed arguments, found `{}`", + args.to_token_stream().to_string() + ); + return Err(syn::Error::new(args.span(), msg)) + }, + }; + + match error_type { + GenericArgument::Type(Type::Path(TypePath { + path: Path { segments: err_variant, leading_colon }, + .. + })) => { + if err_variant.len() < 2 { + let msg = format!( + "Invalid pallet::storage, unexpected number of path segments for \ + the generics in ResultQuery, expected a path with at least 2 \ + segments, found {}", + err_variant.len(), + ); + return Err(syn::Error::new(err_variant.span(), msg)) + } + let mut error = err_variant.clone(); + let err_variant = error + .pop() + .expect("Checked to have at least 2; qed") + .into_value() + .ident; + + // Necessary here to eliminate the last double colon + let last = + error.pop().expect("Checked to have at least 2; qed").into_value(); + error.push_value(last); + + Ok(Some(QueryKind::ResultQuery( + syn::Path { leading_colon, segments: error }, + err_variant, + ))) + }, + gen_arg => { + let msg = format!( + "Invalid pallet::storage, unexpected generic argument kind, expected a \ + type path to a `PalletError` enum variant, found `{}`", + gen_arg.to_token_stream().to_string(), + ); + Err(syn::Error::new(gen_arg.span(), msg)) + }, + } }) - .unwrap_or(Some(QueryKind::OptionQuery)); // This value must match the default generic. + .transpose()? + .unwrap_or(Some(QueryKind::OptionQuery)); - if query_kind.is_none() && getter.is_some() { + if let (None, Some(getter)) = (query_kind.as_ref(), getter.as_ref()) { let msg = "Invalid pallet::storage, cannot generate getter because QueryKind is not \ - identifiable. QueryKind must be `OptionQuery`, `ValueQuery`, or default one to be \ - identifiable."; - return Err(syn::Error::new(getter.unwrap().span(), msg)) + identifiable. QueryKind must be `OptionQuery`, `ResultQuery`, `ValueQuery`, or default \ + one to be identifiable."; + return Err(syn::Error::new(getter.span(), msg)) } Ok(StorageDef { @@ -704,6 +830,8 @@ impl StorageDef { where_clause, cfg_attrs, named_generics, + unbounded, + whitelisted, }) } } diff --git a/frame/support/procedural/src/pallet/parse/type_value.rs b/frame/support/procedural/src/pallet/parse/type_value.rs index 7b9d57472db4b..a3d004cd8a532 100644 --- a/frame/support/procedural/src/pallet/parse/type_value.rs +++ b/frame/support/procedural/src/pallet/parse/type_value.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,6 +38,8 @@ pub struct TypeValueDef { pub where_clause: Option, /// The span of the pallet::type_value attribute. pub attr_span: proc_macro2::Span, + /// Docs on the item. + pub docs: Vec, } impl TypeValueDef { @@ -53,9 +55,18 @@ impl TypeValueDef { return Err(syn::Error::new(item.span(), msg)) }; - if !item.attrs.is_empty() { - let msg = "Invalid pallet::type_value, unexpected attribute"; - return Err(syn::Error::new(item.attrs[0].span(), msg)) + let mut docs = vec![]; + for attr in &item.attrs { + if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() { + if meta.path.get_ident().map_or(false, |ident| ident == "doc") { + docs.push(meta.lit); + continue + } + } + + let msg = "Invalid pallet::type_value, unexpected attribute, only doc attribute are \ + allowed"; + return Err(syn::Error::new(attr.span(), msg)) } if let Some(span) = item @@ -106,6 +117,7 @@ impl TypeValueDef { type_, instances, where_clause, + docs, }) } } diff --git a/frame/support/procedural/src/pallet/parse/validate_unsigned.rs b/frame/support/procedural/src/pallet/parse/validate_unsigned.rs index 87e2a326f1862..18d5a2dc4443f 100644 --- a/frame/support/procedural/src/pallet/parse/validate_unsigned.rs +++ b/frame/support/procedural/src/pallet/parse/validate_unsigned.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,9 +52,10 @@ impl ValidateUnsignedDef { return Err(syn::Error::new(item.span(), msg)) } - let mut instances = vec![]; - instances.push(helper::check_pallet_struct_usage(&item.self_ty)?); - instances.push(helper::check_impl_gen(&item.generics, item.impl_token.span())?); + let instances = vec![ + helper::check_pallet_struct_usage(&item.self_ty)?, + helper::check_impl_gen(&item.generics, item.impl_token.span())?, + ]; Ok(ValidateUnsignedDef { index, instances }) } diff --git a/frame/support/procedural/src/pallet_error.rs b/frame/support/procedural/src/pallet_error.rs new file mode 100644 index 0000000000000..216168131e43d --- /dev/null +++ b/frame/support/procedural/src/pallet_error.rs @@ -0,0 +1,197 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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 frame_support_procedural_tools::generate_crate_access_2018; +use quote::ToTokens; +use std::str::FromStr; + +// Derive `PalletError` +pub fn derive_pallet_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let syn::DeriveInput { ident: name, generics, data, .. } = match syn::parse(input) { + Ok(input) => input, + Err(e) => return e.to_compile_error().into(), + }; + + let frame_support = match generate_crate_access_2018("frame-support") { + Ok(c) => c, + Err(e) => return e.into_compile_error().into(), + }; + let frame_support = &frame_support; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let max_encoded_size = match data { + syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields { + syn::Fields::Named(syn::FieldsNamed { named: fields, .. }) | + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: fields, .. }) => { + let maybe_field_tys = fields + .iter() + .map(|f| generate_field_types(f, &frame_support)) + .collect::>>(); + let field_tys = match maybe_field_tys { + Ok(tys) => tys.into_iter().flatten(), + Err(e) => return e.into_compile_error().into(), + }; + quote::quote! { + 0_usize + #( + .saturating_add(< + #field_tys as #frame_support::traits::PalletError + >::MAX_ENCODED_SIZE) + )* + } + }, + syn::Fields::Unit => quote::quote!(0), + }, + syn::Data::Enum(syn::DataEnum { variants, .. }) => { + let field_tys = variants + .iter() + .map(|variant| generate_variant_field_types(variant, &frame_support)) + .collect::>>, syn::Error>>(); + + let field_tys = match field_tys { + Ok(tys) => tys.into_iter().flatten().collect::>(), + Err(e) => return e.to_compile_error().into(), + }; + + // We start with `1`, because the discriminant of an enum is stored as u8 + if field_tys.is_empty() { + quote::quote!(1) + } else { + let variant_sizes = field_tys.into_iter().map(|variant_field_tys| { + quote::quote! { + 1_usize + #(.saturating_add(< + #variant_field_tys as #frame_support::traits::PalletError + >::MAX_ENCODED_SIZE))* + } + }); + + quote::quote! {{ + let mut size = 1_usize; + let mut tmp = 0_usize; + #( + tmp = #variant_sizes; + size = if tmp > size { tmp } else { size }; + tmp = 0_usize; + )* + size + }} + } + }, + syn::Data::Union(syn::DataUnion { union_token, .. }) => { + let msg = "Cannot derive `PalletError` for union; please implement it directly"; + return syn::Error::new(union_token.span, msg).into_compile_error().into() + }, + }; + + quote::quote!( + const _: () = { + impl #impl_generics #frame_support::traits::PalletError + for #name #ty_generics #where_clause + { + const MAX_ENCODED_SIZE: usize = #max_encoded_size; + } + }; + ) + .into() +} + +fn generate_field_types( + field: &syn::Field, + scrate: &syn::Ident, +) -> syn::Result> { + let attrs = &field.attrs; + + for attr in attrs { + if attr.path.is_ident("codec") { + match attr.parse_meta()? { + syn::Meta::List(ref meta_list) if meta_list.nested.len() == 1 => { + match meta_list + .nested + .first() + .expect("Just checked that there is one item; qed") + { + syn::NestedMeta::Meta(syn::Meta::Path(path)) + if path.get_ident().map_or(false, |i| i == "skip") => + return Ok(None), + + syn::NestedMeta::Meta(syn::Meta::Path(path)) + if path.get_ident().map_or(false, |i| i == "compact") => + { + let field_ty = &field.ty; + return Ok(Some(quote::quote!(#scrate::codec::Compact<#field_ty>))) + }, + + syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { + path, + lit: syn::Lit::Str(lit_str), + .. + })) if path.get_ident().map_or(false, |i| i == "encoded_as") => { + let ty = proc_macro2::TokenStream::from_str(&lit_str.value())?; + return Ok(Some(ty)) + }, + + _ => (), + } + }, + _ => (), + } + } + } + + Ok(Some(field.ty.to_token_stream())) +} + +fn generate_variant_field_types( + variant: &syn::Variant, + scrate: &syn::Ident, +) -> syn::Result>> { + let attrs = &variant.attrs; + + for attr in attrs { + if attr.path.is_ident("codec") { + match attr.parse_meta()? { + syn::Meta::List(ref meta_list) if meta_list.nested.len() == 1 => { + match meta_list + .nested + .first() + .expect("Just checked that there is one item; qed") + { + syn::NestedMeta::Meta(syn::Meta::Path(path)) + if path.get_ident().map_or(false, |i| i == "skip") => + return Ok(None), + + _ => (), + } + }, + _ => (), + } + } + } + + match &variant.fields { + syn::Fields::Named(syn::FieldsNamed { named: fields, .. }) | + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: fields, .. }) => { + let field_tys = fields + .iter() + .map(|field| generate_field_types(field, scrate)) + .collect::>>()?; + Ok(Some(field_tys.into_iter().flatten().collect())) + }, + syn::Fields::Unit => Ok(None), + } +} diff --git a/frame/support/procedural/src/partial_eq_no_bound.rs b/frame/support/procedural/src/partial_eq_no_bound.rs index 3dbabf3f5d39a..31930c0c3dae3 100644 --- a/frame/support/procedural/src/partial_eq_no_bound.rs +++ b/frame/support/procedural/src/partial_eq_no_bound.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/genesis_config/builder_def.rs b/frame/support/procedural/src/storage/genesis_config/builder_def.rs index 001cea0f2b788..29e2560bc16e4 100644 --- a/frame/support/procedural/src/storage/genesis_config/builder_def.rs +++ b/frame/support/procedural/src/storage/genesis_config/builder_def.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,7 +49,7 @@ impl BuilderDef { let mut data = None; if let Some(builder) = &line.build { - is_generic |= ext::expr_contains_ident(&builder, &def.module_runtime_generic); + is_generic |= ext::expr_contains_ident(builder, &def.module_runtime_generic); is_generic |= line.is_generic; data = Some(match &line.storage_type { @@ -138,7 +138,7 @@ impl BuilderDef { } if let Some(builder) = def.extra_genesis_build.as_ref() { - is_generic |= ext::expr_contains_ident(&builder, &def.module_runtime_generic); + is_generic |= ext::expr_contains_ident(builder, &def.module_runtime_generic); blocks.push(quote_spanned! { builder.span() => let extra_genesis_builder: fn(&Self) = #builder; diff --git a/frame/support/procedural/src/storage/genesis_config/genesis_config_def.rs b/frame/support/procedural/src/storage/genesis_config/genesis_config_def.rs index fbdaab06b4895..d24e50096f25e 100644 --- a/frame/support/procedural/src/storage/genesis_config/genesis_config_def.rs +++ b/frame/support/procedural/src/storage/genesis_config/genesis_config_def.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/genesis_config/mod.rs b/frame/support/procedural/src/storage/genesis_config/mod.rs index d2d1afb017736..d4348ee19171c 100644 --- a/frame/support/procedural/src/storage/genesis_config/mod.rs +++ b/frame/support/procedural/src/storage/genesis_config/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -75,12 +75,12 @@ fn decl_genesis_config_and_impl_default( #[serde(deny_unknown_fields)] #[serde(crate = #serde_crate)] #serde_bug_bound - pub struct GenesisConfig#genesis_struct_decl #genesis_where_clause { + pub struct GenesisConfig #genesis_struct_decl #genesis_where_clause { #( #config_fields )* } #[cfg(feature = "std")] - impl#genesis_impl Default for GenesisConfig#genesis_struct #genesis_where_clause { + impl #genesis_impl Default for GenesisConfig #genesis_struct #genesis_where_clause { fn default() -> Self { GenesisConfig { #( #config_field_defaults )* @@ -137,7 +137,7 @@ fn impl_build_storage( quote! { #[cfg(feature = "std")] - impl#genesis_impl GenesisConfig#genesis_struct #genesis_where_clause { + impl #genesis_impl GenesisConfig #genesis_struct #genesis_where_clause { /// Build the storage for this module. pub fn build_storage #fn_generic (&self) -> std::result::Result< #scrate::sp_runtime::Storage, @@ -161,7 +161,7 @@ fn impl_build_storage( } #[cfg(feature = "std")] - impl#build_storage_impl #build_storage_impl_trait for GenesisConfig#genesis_struct + impl #build_storage_impl #build_storage_impl_trait for GenesisConfig #genesis_struct #where_clause { fn build_module_genesis_storage( diff --git a/frame/support/procedural/src/storage/getters.rs b/frame/support/procedural/src/storage/getters.rs index 988e6fa096243..9fe3734e6889c 100644 --- a/frame/support/procedural/src/storage/getters.rs +++ b/frame/support/procedural/src/storage/getters.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -92,7 +92,7 @@ pub fn impl_getters(def: &DeclStorageDefExt) -> TokenStream { let where_clause = &def.where_clause; quote!( - impl#module_impl #module_struct #where_clause { + impl #module_impl #module_struct #where_clause { #getters } ) diff --git a/frame/support/procedural/src/storage/instance_trait.rs b/frame/support/procedural/src/storage/instance_trait.rs index 00a73d6fbd6e7..e9df02e3dc132 100644 --- a/frame/support/procedural/src/storage/instance_trait.rs +++ b/frame/support/procedural/src/storage/instance_trait.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -116,7 +116,7 @@ fn create_and_impl_instance_struct( let instance_trait = quote!( #scrate::traits::Instance ); let instance_struct = &instance_def.instance_struct; - let prefix = format!("{}{}", instance_def.prefix, def.crate_name.to_string()); + let prefix = format!("{}{}", instance_def.prefix, def.crate_name); let doc = &instance_def.doc; let index = instance_def.index; diff --git a/frame/support/procedural/src/storage/metadata.rs b/frame/support/procedural/src/storage/metadata.rs index a90e5051c5b2e..d1879d1509f8d 100644 --- a/frame/support/procedural/src/storage/metadata.rs +++ b/frame/support/procedural/src/storage/metadata.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -207,7 +207,7 @@ pub fn impl_metadata(def: &DeclStorageDefExt) -> TokenStream { quote!( #default_byte_getter_struct_defs - impl#module_impl #module_struct #where_clause { + impl #module_impl #module_struct #where_clause { #[doc(hidden)] pub fn storage_metadata() -> #scrate::metadata::PalletStorageMetadata { #store_metadata diff --git a/frame/support/procedural/src/storage/mod.rs b/frame/support/procedural/src/storage/mod.rs index 27964d7012a28..e8e2d7529cb3f 100644 --- a/frame/support/procedural/src/storage/mod.rs +++ b/frame/support/procedural/src/storage/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ pub(crate) use instance_trait::INHERENT_INSTANCE_NAME; use frame_support_procedural_tools::{ generate_crate_access, generate_hidden_includes, syn_ext as ext, }; + use quote::quote; /// All information contained in input of decl_storage @@ -259,7 +260,7 @@ impl StorageLineDefExt { ) -> Self { let is_generic = match &storage_def.storage_type { StorageLineTypeDef::Simple(value) => - ext::type_contains_ident(&value, &def.module_runtime_generic), + ext::type_contains_ident(value, &def.module_runtime_generic), StorageLineTypeDef::Map(map) => ext::type_contains_ident(&map.key, &def.module_runtime_generic) || ext::type_contains_ident(&map.value, &def.module_runtime_generic), diff --git a/frame/support/procedural/src/storage/parse.rs b/frame/support/procedural/src/storage/parse.rs index 3a11846181a8f..54026b7d78b19 100644 --- a/frame/support/procedural/src/storage/parse.rs +++ b/frame/support/procedural/src/storage/parse.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/storage_info.rs b/frame/support/procedural/src/storage/storage_info.rs index 844896409f851..77515fa739b2b 100644 --- a/frame/support/procedural/src/storage/storage_info.rs +++ b/frame/support/procedural/src/storage/storage_info.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -48,7 +48,7 @@ pub fn impl_storage_info(def: &DeclStorageDefExt) -> TokenStream { let where_clause = &def.where_clause; quote!( - impl#module_impl #scrate::traits::StorageInfoTrait for #module_struct #where_clause { + impl #module_impl #scrate::traits::StorageInfoTrait for #module_struct #where_clause { fn storage_info() -> #scrate::sp_std::vec::Vec<#scrate::traits::StorageInfo> { let mut res = #scrate::sp_std::vec![]; #res_append_storage diff --git a/frame/support/procedural/src/storage/storage_struct.rs b/frame/support/procedural/src/storage/storage_struct.rs index b318225681c1d..649a41bab5ece 100644 --- a/frame/support/procedural/src/storage/storage_struct.rs +++ b/frame/support/procedural/src/storage/storage_struct.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/store_trait.rs b/frame/support/procedural/src/storage/store_trait.rs index 7dde92cf9a75d..daf933b3b770c 100644 --- a/frame/support/procedural/src/storage/store_trait.rs +++ b/frame/support/procedural/src/storage/store_trait.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -48,7 +48,7 @@ pub fn decl_and_impl(def: &DeclStorageDefExt) -> TokenStream { #visibility trait #store_trait { #decl_store_items } - impl#module_impl #store_trait for #module_struct #where_clause { + impl #module_impl #store_trait for #module_struct #where_clause { #impl_store_items } ) diff --git a/frame/support/procedural/src/storage_alias.rs b/frame/support/procedural/src/storage_alias.rs new file mode 100644 index 0000000000000..e0df0123595b9 --- /dev/null +++ b/frame/support/procedural/src/storage_alias.rs @@ -0,0 +1,570 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Implementation of the `storage_alias` attribute macro. + +use frame_support_procedural_tools::generate_crate_access_2018; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{ + ext::IdentExt, + parenthesized, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token, Attribute, Error, Ident, Result, Token, Type, TypeParam, Visibility, WhereClause, +}; + +/// Represents a path that only consists of [`Ident`] separated by `::`. +struct SimplePath { + leading_colon: Option, + segments: Punctuated, +} + +impl SimplePath { + /// Returns the [`Ident`] of this path. + /// + /// It only returns `Some(_)` if there is exactly one element and no leading colon. + fn get_ident(&self) -> Option<&Ident> { + if self.segments.len() != 1 || self.leading_colon.is_some() { + None + } else { + self.segments.first() + } + } +} + +impl Parse for SimplePath { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + leading_colon: if input.peek(Token![::]) { Some(input.parse()?) } else { None }, + segments: Punctuated::parse_separated_nonempty_with(input, |p| Ident::parse_any(p))?, + }) + } +} + +impl ToTokens for SimplePath { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.leading_colon.to_tokens(tokens); + self.segments.to_tokens(tokens); + } +} + +/// Represents generics which only support [`Ident`] separated by commas as you would pass it to a +/// type. +struct TypeGenerics { + lt_token: Token![<], + params: Punctuated, + gt_token: Token![>], +} + +impl TypeGenerics { + /// Returns the generics for types declarations etc. + fn iter(&self) -> impl Iterator { + self.params.iter() + } +} + +impl Parse for TypeGenerics { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + lt_token: input.parse()?, + params: Punctuated::parse_separated_nonempty(input)?, + gt_token: input.parse()?, + }) + } +} + +impl ToTokens for TypeGenerics { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.lt_token.to_tokens(tokens); + self.params.to_tokens(tokens); + self.gt_token.to_tokens(tokens); + } +} + +/// Represents generics which only support [`TypeParam`] separated by commas. +struct SimpleGenerics { + lt_token: Token![<], + params: Punctuated, + gt_token: Token![>], +} + +impl SimpleGenerics { + /// Returns the generics for types declarations etc. + fn type_generics(&self) -> impl Iterator { + self.params.iter().map(|p| &p.ident) + } + + /// Returns the generics for the `impl` block. + fn impl_generics(&self) -> impl Iterator { + self.params.iter() + } +} + +impl Parse for SimpleGenerics { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + lt_token: input.parse()?, + params: Punctuated::parse_separated_nonempty(input)?, + gt_token: input.parse()?, + }) + } +} + +impl ToTokens for SimpleGenerics { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.lt_token.to_tokens(tokens); + self.params.to_tokens(tokens); + self.gt_token.to_tokens(tokens); + } +} + +mod storage_types { + syn::custom_keyword!(StorageValue); + syn::custom_keyword!(StorageMap); + syn::custom_keyword!(StorageDoubleMap); + syn::custom_keyword!(StorageNMap); +} + +/// The supported storage types +enum StorageType { + Value { + _kw: storage_types::StorageValue, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, + Map { + _kw: storage_types::StorageMap, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _hasher_comma: Token![,], + hasher_ty: Type, + _key_comma: Token![,], + key_ty: Type, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, + DoubleMap { + _kw: storage_types::StorageDoubleMap, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _hasher1_comma: Token![,], + hasher1_ty: Type, + _key1_comma: Token![,], + key1_ty: Type, + _hasher2_comma: Token![,], + hasher2_ty: Type, + _key2_comma: Token![,], + key2_ty: Type, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, + NMap { + _kw: storage_types::StorageNMap, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _paren_comma: Token![,], + _paren_token: token::Paren, + key_types: Punctuated, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, +} + +impl StorageType { + /// Generate the actual type declaration. + fn generate_type_declaration( + &self, + crate_: &Ident, + storage_instance: &StorageInstance, + storage_name: &Ident, + storage_generics: Option<&SimpleGenerics>, + visibility: &Visibility, + attributes: &[Attribute], + ) -> TokenStream { + let storage_instance = &storage_instance.name; + let attributes = attributes.iter(); + let storage_generics = storage_generics.map(|g| { + let generics = g.type_generics(); + + quote!( < #( #generics ),* > ) + }); + + match self { + Self::Value { value_ty, query_type, prefix_generics, .. } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageValue< + #storage_instance #prefix_generics, + #value_ty + #query_type + >; + } + }, + Self::Map { value_ty, query_type, hasher_ty, key_ty, prefix_generics, .. } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageMap< + #storage_instance #prefix_generics, + #hasher_ty, + #key_ty, + #value_ty + #query_type + >; + } + }, + Self::DoubleMap { + value_ty, + query_type, + hasher1_ty, + key1_ty, + hasher2_ty, + key2_ty, + prefix_generics, + .. + } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageDoubleMap< + #storage_instance #prefix_generics, + #hasher1_ty, + #key1_ty, + #hasher2_ty, + #key2_ty, + #value_ty + #query_type + >; + } + }, + Self::NMap { value_ty, query_type, key_types, prefix_generics, .. } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + let key_types = key_types.iter(); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageNMap< + #storage_instance #prefix_generics, + ( #( #key_types ),* ), + #value_ty + #query_type + >; + } + }, + } + } + + /// The prefix for this storage type. + fn prefix(&self) -> &SimplePath { + match self { + Self::Value { prefix, .. } | + Self::Map { prefix, .. } | + Self::NMap { prefix, .. } | + Self::DoubleMap { prefix, .. } => prefix, + } + } + + /// The prefix generics for this storage type. + fn prefix_generics(&self) -> Option<&TypeGenerics> { + match self { + Self::Value { prefix_generics, .. } | + Self::Map { prefix_generics, .. } | + Self::NMap { prefix_generics, .. } | + Self::DoubleMap { prefix_generics, .. } => prefix_generics.as_ref(), + } + } +} + +impl Parse for StorageType { + fn parse(input: ParseStream<'_>) -> Result { + let lookahead = input.lookahead1(); + + let parse_query_type = |input: ParseStream<'_>| -> Result> { + if input.peek(Token![,]) && !input.peek2(Token![>]) { + Ok(Some((input.parse()?, input.parse()?))) + } else { + Ok(None) + } + }; + + let parse_pallet_generics = |input: ParseStream<'_>| -> Result> { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![<]) { + Ok(Some(input.parse()?)) + } else if lookahead.peek(Token![,]) { + Ok(None) + } else { + Err(lookahead.error()) + } + }; + + if lookahead.peek(storage_types::StorageValue) { + Ok(Self::Value { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else if lookahead.peek(storage_types::StorageMap) { + Ok(Self::Map { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _hasher_comma: input.parse()?, + hasher_ty: input.parse()?, + _key_comma: input.parse()?, + key_ty: input.parse()?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else if lookahead.peek(storage_types::StorageDoubleMap) { + Ok(Self::DoubleMap { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _hasher1_comma: input.parse()?, + hasher1_ty: input.parse()?, + _key1_comma: input.parse()?, + key1_ty: input.parse()?, + _hasher2_comma: input.parse()?, + hasher2_ty: input.parse()?, + _key2_comma: input.parse()?, + key2_ty: input.parse()?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else if lookahead.peek(storage_types::StorageNMap) { + let content; + Ok(Self::NMap { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _paren_comma: input.parse()?, + _paren_token: parenthesized!(content in input), + key_types: Punctuated::parse_terminated(&content)?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else { + Err(lookahead.error()) + } + } +} + +/// The input expected by this macro. +struct Input { + attributes: Vec, + visibility: Visibility, + _type: Token![type], + storage_name: Ident, + storage_generics: Option, + where_clause: Option, + _equal: Token![=], + storage_type: StorageType, + _semicolon: Token![;], +} + +impl Parse for Input { + fn parse(input: ParseStream<'_>) -> Result { + let attributes = input.call(Attribute::parse_outer)?; + let visibility = input.parse()?; + let _type = input.parse()?; + let storage_name = input.parse()?; + + let lookahead = input.lookahead1(); + let storage_generics = if lookahead.peek(Token![<]) { + Some(input.parse()?) + } else if lookahead.peek(Token![=]) { + None + } else { + return Err(lookahead.error()) + }; + + let lookahead = input.lookahead1(); + let where_clause = if lookahead.peek(Token![where]) { + Some(input.parse()?) + } else if lookahead.peek(Token![=]) { + None + } else { + return Err(lookahead.error()) + }; + + let _equal = input.parse()?; + + let storage_type = input.parse()?; + + let _semicolon = input.parse()?; + + Ok(Self { + attributes, + visibility, + _type, + storage_name, + storage_generics, + _equal, + storage_type, + where_clause, + _semicolon, + }) + } +} + +/// Implementation of the `storage_alias` attribute macro. +pub fn storage_alias(input: TokenStream) -> Result { + let input = syn::parse2::(input)?; + let crate_ = generate_crate_access_2018("frame-support")?; + + let storage_instance = generate_storage_instance( + &crate_, + &input.storage_name, + input.storage_generics.as_ref(), + input.where_clause.as_ref(), + input.storage_type.prefix(), + input.storage_type.prefix_generics(), + &input.visibility, + )?; + + let definition = input.storage_type.generate_type_declaration( + &crate_, + &storage_instance, + &input.storage_name, + input.storage_generics.as_ref(), + &input.visibility, + &input.attributes, + ); + + let storage_instance_code = storage_instance.code; + + Ok(quote! { + #storage_instance_code + + #definition + }) +} + +/// The storage instance to use for the storage alias. +struct StorageInstance { + name: Ident, + code: TokenStream, +} + +/// Generate the [`StorageInstance`] for the storage alias. +fn generate_storage_instance( + crate_: &Ident, + storage_name: &Ident, + storage_generics: Option<&SimpleGenerics>, + storage_where_clause: Option<&WhereClause>, + prefix: &SimplePath, + prefix_generics: Option<&TypeGenerics>, + visibility: &Visibility, +) -> Result { + if let Some(ident) = prefix.get_ident().filter(|i| *i == "_") { + return Err(Error::new(ident.span(), "`_` is not allowed as prefix by `storage_alias`.")) + } + + let (pallet_prefix, impl_generics, type_generics) = + if let Some((prefix_generics, storage_generics)) = + prefix_generics.and_then(|p| storage_generics.map(|s| (p, s))) + { + let type_generics = prefix_generics.iter(); + let type_generics2 = prefix_generics.iter(); + let impl_generics = storage_generics + .impl_generics() + .filter(|g| prefix_generics.params.iter().any(|pg| *pg == g.ident)); + + ( + quote! { + <#prefix < #( #type_generics2 ),* > as #crate_::traits::PalletInfoAccess>::name() + }, + quote!( #( #impl_generics ),* ), + quote!( #( #type_generics ),* ), + ) + } else if let Some(prefix) = prefix.get_ident() { + let prefix_str = prefix.to_string(); + + (quote!(#prefix_str), quote!(), quote!()) + } else { + return Err(Error::new_spanned( + prefix, + "If there are no generics, the prefix is only allowed to be an identifier.", + )) + }; + + let where_clause = storage_where_clause.map(|w| quote!(#w)).unwrap_or_default(); + + let name = Ident::new(&format!("{}_Storage_Instance", storage_name), Span::call_site()); + let storage_name_str = storage_name.to_string(); + + // Implement `StorageInstance` trait. + let code = quote! { + #visibility struct #name< #impl_generics >( + #crate_::sp_std::marker::PhantomData<(#type_generics)> + ) #where_clause; + + impl<#impl_generics> #crate_::traits::StorageInstance + for #name< #type_generics > #where_clause + { + fn pallet_prefix() -> &'static str { + #pallet_prefix + } + + const STORAGE_PREFIX: &'static str = #storage_name_str; + } + }; + + Ok(StorageInstance { name, code }) +} diff --git a/frame/support/procedural/src/transactional.rs b/frame/support/procedural/src/transactional.rs index 403f1cd02bac7..eb77a320a509f 100644 --- a/frame/support/procedural/src/transactional.rs +++ b/frame/support/procedural/src/transactional.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,7 +49,9 @@ pub fn require_transactional(_attr: TokenStream, input: TokenStream) -> Result, +} + +impl syn::parse::Parse for CreateTtReturnMacroDef { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let name = input.parse()?; + let _ = input.parse::()?; + + let mut args = Vec::new(); + while !input.is_empty() { + let mut value; + let key: Ident = input.parse()?; + let _ = input.parse::()?; + let _: syn::token::Bracket = syn::bracketed!(value in input); + let _: syn::token::Brace = syn::braced!(value in value); + let value: TokenStream = value.parse()?; + + args.push((key, value)) + } + + Ok(Self { name, args }) + } +} + +/// A proc macro that accepts a name and any number of key-value pairs, to be used to create a +/// declarative macro that follows tt-call conventions and simply calls [`tt_call::tt_return`], +/// accepting an optional `frame-support` argument and returning the key-value pairs that were +/// supplied to the proc macro. +/// +/// # Example +/// ```ignore +/// __create_tt_macro! { +/// my_tt_macro, +/// foo = [{ bar }] +/// } +/// +/// // Creates the following declarative macro: +/// +/// macro_rules! my_tt_macro { +/// { +/// $caller:tt +/// $(frame_support = [{ $($frame_support:ident)::* }])? +/// } => { +/// frame_support::tt_return! { +/// $caller +/// foo = [{ bar }] +/// } +/// } +/// } +/// ``` +pub fn create_tt_return_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let CreateTtReturnMacroDef { name, args } = + syn::parse_macro_input!(input as CreateTtReturnMacroDef); + + let frame_support = match generate_crate_access_2018("frame-support") { + Ok(i) => i, + Err(e) => return e.into_compile_error().into(), + }; + let (keys, values): (Vec<_>, Vec<_>) = args.into_iter().unzip(); + let count = COUNTER.with(|counter| counter.borrow_mut().inc()); + let unique_name = format_ident!("{}_{}", name, count); + + let decl_macro = quote::quote! { + #[macro_export] + #[doc(hidden)] + macro_rules! #unique_name { + { + $caller:tt + $(frame_support = [{ $($frame_support:ident)::* }])? + } => { + #frame_support::tt_return! { + $caller + #( + #keys = [{ #values }] + )* + } + } + } + + pub use #unique_name as #name; + }; + + decl_macro.into() +} diff --git a/frame/support/procedural/tools/Cargo.toml b/frame/support/procedural/tools/Cargo.toml index ee59f53287efa..755483f7db9aa 100644 --- a/frame/support/procedural/tools/Cargo.toml +++ b/frame/support/procedural/tools/Cargo.toml @@ -2,9 +2,9 @@ name = "frame-support-procedural-tools" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Proc macro helpers for procedural macros" @@ -12,8 +12,8 @@ description = "Proc macro helpers for procedural macros" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +proc-macro-crate = "1.1.3" +proc-macro2 = "1.0.37" +quote = "1.0.10" +syn = { version = "1.0.98", features = ["full", "visit", "extra-traits"] } frame-support-procedural-tools-derive = { version = "3.0.0", path = "./derive" } -proc-macro2 = "1.0.29" -quote = "1.0.3" -syn = { version = "1.0.58", features = ["full", "visit", "extra-traits"] } -proc-macro-crate = "1.0.0" diff --git a/frame/support/procedural/tools/derive/Cargo.toml b/frame/support/procedural/tools/derive/Cargo.toml index 12ec6a69f3967..a688ee13a7d17 100644 --- a/frame/support/procedural/tools/derive/Cargo.toml +++ b/frame/support/procedural/tools/derive/Cargo.toml @@ -2,9 +2,9 @@ name = "frame-support-procedural-tools-derive" version = "3.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Use to derive parsing for parsing struct." @@ -15,6 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro2 = "1.0.29" -quote = { version = "1.0.3", features = ["proc-macro"] } -syn = { version = "1.0.58", features = ["proc-macro" ,"full", "extra-traits", "parsing"] } +proc-macro2 = "1.0.37" +quote = { version = "1.0.10", features = ["proc-macro"] } +syn = { version = "1.0.98", features = ["proc-macro", "full", "extra-traits", "parsing"] } diff --git a/frame/support/procedural/tools/derive/src/lib.rs b/frame/support/procedural/tools/derive/src/lib.rs index 7922105895608..392a438342017 100644 --- a/frame/support/procedural/tools/derive/src/lib.rs +++ b/frame/support/procedural/tools/derive/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -131,7 +131,7 @@ fn derive_totokens_enum(input: syn::ItemEnum) -> TokenStream { }; let field = fields_idents(v.fields.iter().map(Clone::clone)); quote! { - #ident::#v_ident#fields_build => { + #ident::#v_ident #fields_build => { #( #field.to_tokens(tokens); )* diff --git a/frame/support/procedural/tools/src/lib.rs b/frame/support/procedural/tools/src/lib.rs index d7aba4c7cbf1c..3abde59574119 100644 --- a/frame/support/procedural/tools/src/lib.rs +++ b/frame/support/procedural/tools/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/tools/src/syn_ext.rs b/frame/support/procedural/tools/src/syn_ext.rs index a9e9ef573985f..1a2d7c1d372ad 100644 --- a/frame/support/procedural/tools/src/syn_ext.rs +++ b/frame/support/procedural/tools/src/syn_ext.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,7 +47,8 @@ macro_rules! groups_impl { impl Parse for $name

(); + if type_id == sp_std::any::TypeId::of::() { + return Some("tests") + } + + None + } + fn crate_version() -> Option { + let type_id = sp_std::any::TypeId::of::

(); + if type_id == sp_std::any::TypeId::of::() { + return Some(frame_support::crate_to_crate_version!()) + } + None } } + #[derive( + TypeInfo, crate::RuntimeDebug, Eq, PartialEq, Clone, Encode, Decode, MaxEncodedLen, + )] pub struct OuterOrigin; + impl From::AccountId>> for OuterOrigin { + fn from(_: RawOrigin<::AccountId>) -> Self { + unimplemented!("Not required in tests!") + } + } + + impl CallerTrait<::AccountId> for OuterOrigin { + fn into_system(self) -> Option::AccountId>> { + unimplemented!("Not required in tests!") + } + + fn as_system_ref(&self) -> Option<&RawOrigin<::AccountId>> { + unimplemented!("Not required in tests!") + } + } + impl crate::traits::OriginTrait for OuterOrigin { - type Call = ::Call; + type Call = ::RuntimeCall; type PalletsOrigin = OuterOrigin; type AccountId = ::AccountId; @@ -2662,6 +3337,10 @@ mod tests { unimplemented!("Not required in tests!") } + fn into_caller(self) -> Self::PalletsOrigin { + unimplemented!("Not required in tests!") + } + fn try_with_caller( self, _f: impl FnOnce(Self::PalletsOrigin) -> Result, @@ -2678,12 +3357,18 @@ mod tests { fn signed(_by: ::AccountId) -> Self { unimplemented!("Not required in tests!") } + fn as_signed(self) -> Option { + unimplemented!("Not required in tests!") + } + fn as_system_ref(&self) -> Option<&RawOrigin> { + unimplemented!("Not required in tests!") + } } impl system::Config for TraitImpl { - type Origin = OuterOrigin; + type RuntimeOrigin = OuterOrigin; type AccountId = u32; - type Call = (); + type RuntimeCall = (); type BaseCallFilter = frame_support::traits::Everything; type BlockNumber = u32; type PalletInfo = Self; @@ -2734,24 +3419,30 @@ mod tests { #[test] fn on_initialize_should_work_2() { - assert_eq!( as OnInitialize>::on_initialize(10), 7); + assert_eq!( + as OnInitialize>::on_initialize(10), + Weight::from_ref_time(7) + ); } #[test] #[should_panic(expected = "on_idle")] fn on_idle_should_work_1() { - as OnIdle>::on_idle(42, 9); + as OnIdle>::on_idle(42, Weight::from_ref_time(9)); } #[test] #[should_panic(expected = "on_idle")] fn on_idle_should_work_2() { - as OnIdle>::on_idle(9, 42); + as OnIdle>::on_idle(9, Weight::from_ref_time(42)); } #[test] fn on_idle_should_work_3() { - assert_eq!( as OnIdle>::on_idle(10, 11), 7); + assert_eq!( + as OnIdle>::on_idle(10, Weight::from_ref_time(11)), + Weight::from_ref_time(7) + ); } #[test] @@ -2763,7 +3454,10 @@ mod tests { #[test] fn on_runtime_upgrade_should_work() { sp_io::TestExternalities::default().execute_with(|| { - assert_eq!( as OnRuntimeUpgrade>::on_runtime_upgrade(), 10) + assert_eq!( + as OnRuntimeUpgrade>::on_runtime_upgrade(), + Weight::from_ref_time(10) + ) }); } @@ -2772,12 +3466,20 @@ mod tests { // operational. assert_eq!( Call::::operational {}.get_dispatch_info(), - DispatchInfo { weight: 5, class: DispatchClass::Operational, pays_fee: Pays::Yes }, + DispatchInfo { + weight: Weight::from_ref_time(5), + class: DispatchClass::Operational, + pays_fee: Pays::Yes + }, ); // custom basic assert_eq!( Call::::aux_3 {}.get_dispatch_info(), - DispatchInfo { weight: 3, class: DispatchClass::Normal, pays_fee: Pays::Yes }, + DispatchInfo { + weight: Weight::from_ref_time(3), + class: DispatchClass::Normal, + pays_fee: Pays::Yes + }, ); } @@ -2807,3 +3509,187 @@ mod tests { Call::::new_call_variant_aux_0(); } } + +#[cfg(test)] +// Do not complain about unused `dispatch` and `dispatch_aux`. +#[allow(dead_code)] +mod weight_tests { + use super::*; + use sp_core::{parameter_types, Get}; + use sp_weights::RuntimeDbWeight; + + pub trait Config: 'static { + type RuntimeOrigin; + type Balance; + type BlockNumber; + type DbWeight: Get; + type PalletInfo: crate::traits::PalletInfo; + } + + pub struct TraitImpl {} + + parameter_types! { + pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 100, + write: 1000, + }; + } + + impl Config for TraitImpl { + type RuntimeOrigin = u32; + type BlockNumber = u32; + type Balance = u32; + type DbWeight = DbWeight; + type PalletInfo = crate::tests::PanicPalletInfo; + } + + decl_module! { + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { + // no arguments, fixed weight + #[weight = 1000] + fn f00(_origin) { unimplemented!(); } + + #[weight = (1000, DispatchClass::Mandatory)] + fn f01(_origin) { unimplemented!(); } + + #[weight = (1000, Pays::No)] + fn f02(_origin) { unimplemented!(); } + + #[weight = (1000, DispatchClass::Operational, Pays::No)] + fn f03(_origin) { unimplemented!(); } + + // weight = a x 10 + b + #[weight = ((_a * 10 + _eb * 1) as u64, DispatchClass::Normal, Pays::Yes)] + fn f11(_origin, _a: u32, _eb: u32) { unimplemented!(); } + + #[weight = (0, DispatchClass::Operational, Pays::Yes)] + fn f12(_origin, _a: u32, _eb: u32) { unimplemented!(); } + + #[weight = T::DbWeight::get().reads(3) + T::DbWeight::get().writes(2) + Weight::from_ref_time(10_000)] + fn f20(_origin) { unimplemented!(); } + + #[weight = T::DbWeight::get().reads_writes(6, 5) + Weight::from_ref_time(40_000)] + fn f21(_origin) { unimplemented!(); } + + } + } + + #[test] + fn weights_are_correct() { + // #[weight = 1000] + let info = Call::::f00 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_ref_time(1000)); + assert_eq!(info.class, DispatchClass::Normal); + assert_eq!(info.pays_fee, Pays::Yes); + + // #[weight = (1000, DispatchClass::Mandatory)] + let info = Call::::f01 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_ref_time(1000)); + assert_eq!(info.class, DispatchClass::Mandatory); + assert_eq!(info.pays_fee, Pays::Yes); + + // #[weight = (1000, Pays::No)] + let info = Call::::f02 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_ref_time(1000)); + assert_eq!(info.class, DispatchClass::Normal); + assert_eq!(info.pays_fee, Pays::No); + + // #[weight = (1000, DispatchClass::Operational, Pays::No)] + let info = Call::::f03 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_ref_time(1000)); + assert_eq!(info.class, DispatchClass::Operational); + assert_eq!(info.pays_fee, Pays::No); + + // #[weight = ((_a * 10 + _eb * 1) as Weight, DispatchClass::Normal, Pays::Yes)] + let info = Call::::f11 { _a: 13, _eb: 20 }.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_ref_time(150)); // 13*10 + 20 + assert_eq!(info.class, DispatchClass::Normal); + assert_eq!(info.pays_fee, Pays::Yes); + + // #[weight = (0, DispatchClass::Operational, Pays::Yes)] + let info = Call::::f12 { _a: 10, _eb: 20 }.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_ref_time(0)); + assert_eq!(info.class, DispatchClass::Operational); + assert_eq!(info.pays_fee, Pays::Yes); + + // #[weight = T::DbWeight::get().reads(3) + T::DbWeight::get().writes(2) + 10_000] + let info = Call::::f20 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_ref_time(12300)); // 100*3 + 1000*2 + 10_1000 + assert_eq!(info.class, DispatchClass::Normal); + assert_eq!(info.pays_fee, Pays::Yes); + + // #[weight = T::DbWeight::get().reads_writes(6, 5) + 40_000] + let info = Call::::f21 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_ref_time(45600)); // 100*6 + 1000*5 + 40_1000 + assert_eq!(info.class, DispatchClass::Normal); + assert_eq!(info.pays_fee, Pays::Yes); + } + + #[test] + fn extract_actual_weight_works() { + let pre = DispatchInfo { weight: Weight::from_ref_time(1000), ..Default::default() }; + assert_eq!(extract_actual_weight(&Ok(Some(7).into()), &pre), Weight::from_ref_time(7)); + assert_eq!( + extract_actual_weight(&Ok(Some(1000).into()), &pre), + Weight::from_ref_time(1000) + ); + assert_eq!( + extract_actual_weight( + &Err(DispatchError::BadOrigin.with_weight(Weight::from_ref_time(9))), + &pre + ), + Weight::from_ref_time(9) + ); + } + + #[test] + fn extract_actual_weight_caps_at_pre_weight() { + let pre = DispatchInfo { weight: Weight::from_ref_time(1000), ..Default::default() }; + assert_eq!( + extract_actual_weight(&Ok(Some(1250).into()), &pre), + Weight::from_ref_time(1000) + ); + assert_eq!( + extract_actual_weight( + &Err(DispatchError::BadOrigin.with_weight(Weight::from_ref_time(1300))), + &pre + ), + Weight::from_ref_time(1000), + ); + } + + #[test] + fn extract_actual_pays_fee_works() { + let pre = DispatchInfo { weight: Weight::from_ref_time(1000), ..Default::default() }; + assert_eq!(extract_actual_pays_fee(&Ok(Some(7).into()), &pre), Pays::Yes); + assert_eq!(extract_actual_pays_fee(&Ok(Some(1000).into()), &pre), Pays::Yes); + assert_eq!(extract_actual_pays_fee(&Ok((Some(1000), Pays::Yes).into()), &pre), Pays::Yes); + assert_eq!(extract_actual_pays_fee(&Ok((Some(1000), Pays::No).into()), &pre), Pays::No); + assert_eq!( + extract_actual_pays_fee( + &Err(DispatchError::BadOrigin.with_weight(Weight::from_ref_time(9))), + &pre + ), + Pays::Yes + ); + assert_eq!( + extract_actual_pays_fee( + &Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { actual_weight: None, pays_fee: Pays::No }, + error: DispatchError::BadOrigin, + }), + &pre + ), + Pays::No + ); + + let pre = DispatchInfo { + weight: Weight::from_ref_time(1000), + pays_fee: Pays::No, + ..Default::default() + }; + assert_eq!(extract_actual_pays_fee(&Ok(Some(7).into()), &pre), Pays::No); + assert_eq!(extract_actual_pays_fee(&Ok(Some(1000).into()), &pre), Pays::No); + assert_eq!(extract_actual_pays_fee(&Ok((Some(1000), Pays::Yes).into()), &pre), Pays::No); + } +} diff --git a/frame/support/src/error.rs b/frame/support/src/error.rs index 836428c6bc7db..337bd75895c2c 100644 --- a/frame/support/src/error.rs +++ b/frame/support/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,7 +51,7 @@ pub use sp_runtime::traits::{BadOrigin, LookupError}; /// // exported in the metadata. /// /// decl_module! { -/// pub struct Module for enum Call where origin: T::Origin { +/// pub struct Module for enum Call where origin: T::RuntimeOrigin { /// type Error = MyError; /// /// #[weight = 0] @@ -85,7 +85,12 @@ macro_rules! decl_error { } ) => { $(#[$attr])* - #[derive($crate::scale_info::TypeInfo)] + #[derive( + $crate::codec::Encode, + $crate::codec::Decode, + $crate::scale_info::TypeInfo, + $crate::PalletError, + )] #[scale_info(skip_type_params($generic $(, $inst_generic)?), capture_docs = "always")] pub enum $error<$generic: $trait $(, $inst_generic: $instance)?> $( where $( $where_ty: $where_bound ),* )? @@ -114,17 +119,6 @@ macro_rules! decl_error { impl<$generic: $trait $(, $inst_generic: $instance)?> $error<$generic $(, $inst_generic)?> $( where $( $where_ty: $where_bound ),* )? { - fn as_u8(&self) -> u8 { - $crate::decl_error! { - @GENERATE_AS_U8 - self - $error - {} - 0, - $( $name ),* - } - } - fn as_str(&self) -> &'static str { match self { Self::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), @@ -149,47 +143,19 @@ macro_rules! decl_error { $( where $( $where_ty: $where_bound ),* )? { fn from(err: $error<$generic $(, $inst_generic)?>) -> Self { + use $crate::codec::Encode; let index = <$generic::PalletInfo as $crate::traits::PalletInfo> ::index::<$module<$generic $(, $inst_generic)?>>() .expect("Every active module has an index in the runtime; qed") as u8; + let mut error = err.encode(); + error.resize($crate::MAX_MODULE_ERROR_ENCODED_SIZE, 0); - $crate::sp_runtime::DispatchError::Module { + $crate::sp_runtime::DispatchError::Module($crate::sp_runtime::ModuleError { index, - error: err.as_u8(), + error: TryInto::try_into(error).expect("encoded error is resized to be equal to the maximum encoded error size; qed"), message: Some(err.as_str()), - } - } - } - }; - (@GENERATE_AS_U8 - $self:ident - $error:ident - { $( $generated:tt )* } - $index:expr, - $name:ident - $( , $rest:ident )* - ) => { - $crate::decl_error! { - @GENERATE_AS_U8 - $self - $error - { - $( $generated )* - $error::$name => $index, + }) } - $index + 1, - $( $rest ),* } }; - (@GENERATE_AS_U8 - $self:ident - $error:ident - { $( $generated:tt )* } - $index:expr, - ) => { - match $self { - $error::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), - $( $generated )* - } - } } diff --git a/frame/support/src/event.rs b/frame/support/src/event.rs index 3d042a3122db8..dc45f2bf69d94 100644 --- a/frame/support/src/event.rs +++ b/frame/support/src/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/hash.rs b/frame/support/src/hash.rs index f943bcf323090..9bdad6d9d59de 100644 --- a/frame/support/src/hash.rs +++ b/frame/support/src/hash.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -101,7 +101,7 @@ impl StorageHasher for Twox64Concat { const METADATA: metadata::StorageHasher = metadata::StorageHasher::Twox64Concat; type Output = Vec; fn hash(x: &[u8]) -> Vec { - twox_64(x).iter().chain(x.into_iter()).cloned().collect::>() + twox_64(x).iter().chain(x.iter()).cloned().collect::>() } fn max_len() -> usize { K::max_encoded_len().saturating_add(8) @@ -123,7 +123,7 @@ impl StorageHasher for Blake2_128Concat { const METADATA: metadata::StorageHasher = metadata::StorageHasher::Blake2_128Concat; type Output = Vec; fn hash(x: &[u8]) -> Vec { - blake2_128(x).iter().chain(x.into_iter()).cloned().collect::>() + blake2_128(x).iter().chain(x.iter()).cloned().collect::>() } fn max_len() -> usize { K::max_encoded_len().saturating_add(16) diff --git a/frame/support/src/inherent.rs b/frame/support/src/inherent.rs index 2125f3e7f50a7..0aa6b9d3f75a9 100644 --- a/frame/support/src/inherent.rs +++ b/frame/support/src/inherent.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,9 +24,12 @@ pub use sp_inherents::{ CheckInherentsResult, InherentData, InherentIdentifier, IsFatalError, MakeFatalError, }; -/// A pallet that provides or verifies an inherent extrinsic. +/// A pallet that provides or verifies an inherent extrinsic will implement this trait. /// -/// The pallet may provide the inherent, verify an inherent, or both provide and verify. +/// The pallet may provide an inherent, verify an inherent, or both provide and verify. +/// +/// Briefly, inherent extrinsics ("inherents") are extrinsics that are added to a block by the block +/// producer. See [`sp_inherents`] for more documentation on inherents. pub trait ProvideInherent { /// The call type of the pallet. type Call; @@ -36,6 +39,12 @@ pub trait ProvideInherent { const INHERENT_IDENTIFIER: self::InherentIdentifier; /// Create an inherent out of the given `InherentData`. + /// + /// NOTE: All checks necessary to ensure that the inherent is correct and that can be done in + /// the runtime should happen in the returned `Call`. + /// E.g. if this provides the timestamp, the call will check that the given timestamp is + /// increasing the old timestamp by more than a minimum and it will also check that the + /// timestamp hasn't already been set in the current block. fn create_inherent(data: &InherentData) -> Option; /// Determines whether this inherent is required in this block. @@ -44,15 +53,17 @@ pub trait ProvideInherent { /// implementation returns this. /// /// - `Ok(Some(e))` indicates that this inherent is required in this block. `construct_runtime!` - /// will call this function from in its implementation of `fn check_extrinsics`. + /// will call this function in its implementation of `fn check_extrinsics`. /// If the inherent is not present, it will return `e`. /// /// - `Err(_)` indicates that this function failed and further operations should be aborted. /// - /// NOTE: If inherent is required then the runtime asserts that the block contains at least + /// NOTE: If the inherent is required then the runtime asserts that the block contains at least /// one inherent for which: /// * type is [`Self::Call`], /// * [`Self::is_inherent`] returns true. + /// + /// NOTE: This is currently only checked by block producers, not all full nodes. fn is_inherent_required(_: &InherentData) -> Result, Self::Error> { Ok(None) } @@ -64,21 +75,27 @@ pub trait ProvideInherent { /// included in the block by its author. Whereas the second parameter represents the inherent /// data that the verifying node calculates. /// - /// NOTE: A block can contains multiple inherent. + /// This is intended to allow for checks that cannot be done within the runtime such as, e.g., + /// the timestamp. + /// + /// # Warning + /// + /// This check is not guaranteed to be run by all full nodes and cannot be relied upon for + /// ensuring that the block is correct. fn check_inherent(_: &Self::Call, _: &InherentData) -> Result<(), Self::Error> { Ok(()) } /// Return whether the call is an inherent call. /// - /// NOTE: Signed extrinsics are not inherent, but signed extrinsic with the given call variant - /// can be dispatched. + /// NOTE: Signed extrinsics are not inherents, but a signed extrinsic with the given call + /// variant can be dispatched. /// /// # Warning /// - /// In FRAME, inherent are enforced to be before other extrinsics, for this reason, + /// In FRAME, inherents are enforced to be executed before other extrinsics. For this reason, /// pallets with unsigned transactions **must ensure** that no unsigned transaction call /// is an inherent call, when implementing `ValidateUnsigned::validate_unsigned`. - /// Otherwise block producer can produce invalid blocks by including them after non inherent. + /// Otherwise block producers can produce invalid blocks by including them after non inherents. fn is_inherent(call: &Self::Call) -> bool; } diff --git a/frame/support/src/instances.rs b/frame/support/src/instances.rs index 9908d16076a08..81139f8671818 100644 --- a/frame/support/src/instances.rs +++ b/frame/support/src/instances.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 459698707366d..efecbb75f9c62 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +16,16 @@ // limitations under the License. //! Support code for the runtime. +//! +//! ## Note on Tuple Traits +//! +//! Many of the traits defined in [`traits`] have auto-implementations on tuples as well. Usually, +//! the tuple is a function of number of pallets in the runtime. By default, the traits are +//! implemented for tuples of up to 64 items. +// +// If you have more pallets in your runtime, or for any other reason need more, enabled `tuples-96` +// or the `tuples-128` complication flag. Note that these features *will increase* the compilation +// of this crate. #![cfg_attr(not(feature = "std"), no_std)] @@ -42,14 +52,21 @@ pub use scale_info; pub use serde; pub use sp_core::Void; #[doc(hidden)] +pub use sp_core_hashing_proc_macro; +#[doc(hidden)] pub use sp_io::{self, storage::root as storage_root}; +#[cfg(feature = "std")] #[doc(hidden)] -pub use sp_runtime::RuntimeDebug; +pub use sp_runtime::{bounded_btree_map, bounded_vec}; +#[doc(hidden)] +pub use sp_runtime::{RuntimeDebug, StateVersion}; #[cfg(feature = "std")] #[doc(hidden)] pub use sp_state_machine::BasicExternalities; #[doc(hidden)] pub use sp_std; +#[doc(hidden)] +pub use tt_call::*; #[macro_use] pub mod dispatch; @@ -60,6 +77,7 @@ pub mod event; pub mod inherent; #[macro_use] pub mod error; +pub mod crypto; pub mod instances; pub mod migrations; pub mod traits; @@ -75,6 +93,8 @@ pub mod unsigned { }; } +#[cfg(any(feature = "std", feature = "runtime-benchmarks", feature = "try-runtime", test))] +pub use self::storage::storage_noop_guard::StorageNoopGuard; pub use self::{ dispatch::{Callable, Parameter}, hash::{ @@ -82,6 +102,8 @@ pub use self::{ StorageHasher, Twox128, Twox256, Twox64Concat, }, storage::{ + bounded_btree_map::BoundedBTreeMap, + bounded_btree_set::BoundedBTreeSet, bounded_vec::{BoundedSlice, BoundedVec}, migration, weak_bounded_vec::WeakBoundedVec, @@ -89,17 +111,19 @@ pub use self::{ StorageMap, StorageNMap, StoragePrefixedMap, StorageValue, }, }; -pub use sp_runtime::{self, print, traits::Printable, ConsensusEngineId}; +pub use sp_runtime::{ + self, print, traits::Printable, ConsensusEngineId, MAX_MODULE_ERROR_ENCODED_SIZE, +}; use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::TypeId; /// A unified log target for support operations. -pub const LOG_TARGET: &'static str = "runtime::frame-support"; +pub const LOG_TARGET: &str = "runtime::frame-support"; /// A type that cannot be instantiated. -#[derive(Debug, PartialEq, Eq, Clone, TypeInfo)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum Never {} /// A pallet identifier. These are per pallet and should be stored in a registry somewhere. @@ -117,178 +141,74 @@ impl TypeId for PalletId { /// Useful for creating a *storage-like* struct for test and migrations. /// /// ``` -/// # use frame_support::generate_storage_alias; +/// # use frame_support::storage_alias; /// use frame_support::codec; /// use frame_support::Twox64Concat; /// // generate a storage value with type u32. -/// generate_storage_alias!(Prefix, StorageName => Value); +/// #[storage_alias] +/// type StorageName = StorageValue; /// /// // generate a double map from `(u32, u32)` (with hashers `Twox64Concat` for each key) /// // to `Vec` -/// generate_storage_alias!( -/// OtherPrefix, OtherStorageName => DoubleMap< -/// (u32, Twox64Concat), -/// (u32, Twox64Concat), -/// Vec -/// > -/// ); +/// #[storage_alias] +/// type OtherStorageName = StorageDoubleMap< +/// OtherPrefix, +/// Twox64Concat, +/// u32, +/// Twox64Concat, +/// u32, +/// Vec, +/// >; /// /// // optionally specify the query type /// use frame_support::pallet_prelude::{ValueQuery, OptionQuery}; -/// generate_storage_alias!(Prefix, ValueName => Value); -/// generate_storage_alias!( -/// Prefix, SomeStorageName => DoubleMap< -/// (u32, Twox64Concat), -/// (u32, Twox64Concat), -/// Vec, -/// ValueQuery -/// > -/// ); +/// #[storage_alias] +/// type ValueName = StorageValue; +/// #[storage_alias] +/// type SomeStorageName = StorageMap< +/// Prefix, +/// Twox64Concat, +/// u32, +/// Vec, +/// ValueQuery, +/// >; /// /// // generate a map from `Config::AccountId` (with hasher `Twox64Concat`) to `Vec` /// trait Config { type AccountId: codec::FullCodec; } -/// generate_storage_alias!( -/// Prefix, GenericStorage => Map<(T::AccountId, Twox64Concat), Vec> -/// ); +/// #[storage_alias] +/// type GenericStorage = StorageMap::AccountId, Vec>; +/// +/// // It also supports NMap +/// use frame_support::storage::types::Key as NMapKey; +/// +/// #[storage_alias] +/// type SomeNMap = StorageNMap, NMapKey), Vec>; +/// +/// // Using pallet name as prefix. +/// // +/// // When the first generic argument is taking generic arguments it is expected to be a pallet. +/// // The prefix will then be the pallet name as configured in the runtime through +/// // `construct_runtime!`. +/// +/// # struct Pallet(std::marker::PhantomData<(T, I)>); +/// # impl frame_support::traits::PalletInfoAccess for Pallet { +/// # fn index() -> usize { 0 } +/// # fn name() -> &'static str { "pallet" } +/// # fn module_name() -> &'static str { "module" } +/// # fn crate_version() -> frame_support::traits::CrateVersion { unimplemented!() } +/// # } +/// +/// #[storage_alias] +/// type SomeValue = StorageValue, u64>; +/// +/// // Pallet with instance +/// +/// #[storage_alias] +/// type SomeValue2 = StorageValue, u64>; +/// /// # fn main() {} /// ``` -#[macro_export] -macro_rules! generate_storage_alias { - // without generic for $name. - ($pallet:ident, $name:ident => Map<($key:ty, $hasher:ty), $value:ty $(, $querytype:ty)?>) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageMap< - [<$name Instance>], - $hasher, - $key, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident - => DoubleMap<($key1:ty, $hasher1:ty), ($key2:ty, $hasher2:ty), $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageDoubleMap< - [<$name Instance>], - $hasher1, - $key1, - $hasher2, - $key2, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident - => NMap, $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageNMap< - [<$name Instance>], - ( - $( $crate::storage::types::Key<$hasher, $key>, )+ - ), - $value, - $( $querytype )? - >; - } - }; - ($pallet:ident, $name:ident => Value<$value:ty $(, $querytype:ty)?>) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageValue< - [<$name Instance>], - $value, - $( $querytype )? - >; - } - }; - // with generic for $name. - ( - $pallet:ident, - $name:ident<$t:ident : $bounds:tt> - => Map<($key:ty, $hasher:ty), $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageMap< - [<$name Instance>], - $key, - $hasher, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident<$t:ident : $bounds:tt> - => DoubleMap<($key1:ty, $hasher1:ty), ($key2:ty, $hasher2:ty), $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageDoubleMap< - [<$name Instance>], - $key1, - $hasher1, - $key2, - $hasher2, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident<$t:ident : $bounds:tt> - => NMap<$(($key:ty, $hasher:ty),)+ $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageNMap< - [<$name Instance>], - ( - $( $crate::storage::types::Key<$hasher, $key>, )+ - ), - $value, - $( $querytype )? - >; - } - }; - ($pallet:ident, $name:ident<$t:ident : $bounds:tt> => Value<$value:ty $(, $querytype:ty)?>) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageValue< - [<$name Instance>], - $value, - $( $querytype )? - >; - } - }; - // helper used in all arms. - (@GENERATE_INSTANCE_STRUCT $pallet:ident, $name:ident) => { - $crate::paste::paste! { - struct [<$name Instance>]; - impl $crate::traits::StorageInstance for [<$name Instance>] { - fn pallet_prefix() -> &'static str { stringify!($pallet) } - const STORAGE_PREFIX: &'static str = stringify!($name); - } - } - }; -} +pub use frame_support_procedural::storage_alias; /// Create new implementations of the [`Get`](crate::traits::Get) trait. /// @@ -363,78 +283,95 @@ macro_rules! generate_storage_alias { macro_rules! parameter_types { ( $( #[ $attr:meta ] )* - $vis:vis const $name:ident: $type:ty = $value:expr; + $vis:vis const $name:ident $(< $($ty_params:ident),* >)?: $type:ty = $value:expr; $( $rest:tt )* ) => ( $( #[ $attr ] )* - $vis struct $name; - $crate::parameter_types!(IMPL_CONST $name , $type , $value); + $vis struct $name $( + < $($ty_params),* >( $($crate::sp_std::marker::PhantomData<$ty_params>),* ) + )?; + $crate::parameter_types!(IMPL_CONST $name , $type , $value $( $(, $ty_params)* )?); $crate::parameter_types!( $( $rest )* ); ); ( $( #[ $attr:meta ] )* - $vis:vis $name:ident: $type:ty = $value:expr; + $vis:vis $name:ident $(< $($ty_params:ident),* >)?: $type:ty = $value:expr; $( $rest:tt )* ) => ( $( #[ $attr ] )* - $vis struct $name; - $crate::parameter_types!(IMPL $name, $type, $value); + $vis struct $name $( + < $($ty_params),* >( $($crate::sp_std::marker::PhantomData<$ty_params>),* ) + )?; + $crate::parameter_types!(IMPL $name, $type, $value $( $(, $ty_params)* )?); $crate::parameter_types!( $( $rest )* ); ); ( $( #[ $attr:meta ] )* - $vis:vis storage $name:ident: $type:ty = $value:expr; + $vis:vis storage $name:ident $(< $($ty_params:ident),* >)?: $type:ty = $value:expr; $( $rest:tt )* ) => ( $( #[ $attr ] )* - $vis struct $name; - $crate::parameter_types!(IMPL_STORAGE $name, $type, $value); + $vis struct $name $( + < $($ty_params),* >( $($crate::sp_std::marker::PhantomData<$ty_params>),* ) + )?; + $crate::parameter_types!(IMPL_STORAGE $name, $type, $value $( $(, $ty_params)* )?); $crate::parameter_types!( $( $rest )* ); ); () => (); - (IMPL_CONST $name:ident, $type:ty, $value:expr) => { - impl $name { + (IMPL_CONST $name:ident, $type:ty, $value:expr $(, $ty_params:ident)*) => { + impl< $($ty_params),* > $name< $($ty_params),* > { /// Returns the value of this parameter type. pub const fn get() -> $type { $value } } - impl> $crate::traits::Get for $name { - fn get() -> I { - I::from(Self::get()) + impl<_I: From<$type> $(, $ty_params)*> $crate::traits::Get<_I> for $name< $($ty_params),* > { + fn get() -> _I { + _I::from(Self::get()) + } + } + + impl< $($ty_params),* > $crate::traits::TypedGet for $name< $($ty_params),* > { + type Type = $type; + fn get() -> $type { + Self::get() } } }; - (IMPL $name:ident, $type:ty, $value:expr) => { - impl $name { + (IMPL $name:ident, $type:ty, $value:expr $(, $ty_params:ident)*) => { + impl< $($ty_params),* > $name< $($ty_params),* > { /// Returns the value of this parameter type. pub fn get() -> $type { $value } } - impl> $crate::traits::Get for $name { - fn get() -> I { - I::from(Self::get()) + impl<_I: From<$type>, $(, $ty_params)*> $crate::traits::Get<_I> for $name< $($ty_params),* > { + fn get() -> _I { + _I::from(Self::get()) + } + } + + impl< $($ty_params),* > $crate::traits::TypedGet for $name< $($ty_params),* > { + type Type = $type; + fn get() -> $type { + Self::get() } } }; - (IMPL_STORAGE $name:ident, $type:ty, $value:expr) => { - impl $name { + (IMPL_STORAGE $name:ident, $type:ty, $value:expr $(, $ty_params:ident)*) => { + #[allow(unused)] + impl< $($ty_params),* > $name< $($ty_params),* > { /// Returns the key for this parameter type. - #[allow(unused)] pub fn key() -> [u8; 16] { - $crate::sp_io::hashing::twox_128( - concat!(":", stringify!($name), ":").as_bytes() - ) + $crate::sp_core_hashing_proc_macro::twox_128!(b":", $name, b":") } /// Set the value of this parameter type in the storage. /// /// This needs to be executed in an externalities provided /// environment. - #[allow(unused)] pub fn set(value: &$type) { $crate::storage::unhashed::put(&Self::key(), value); } @@ -449,9 +386,16 @@ macro_rules! parameter_types { } } - impl> $crate::traits::Get for $name { - fn get() -> I { - I::from(Self::get()) + impl<_I: From<$type> $(, $ty_params)*> $crate::traits::Get<_I> for $name< $($ty_params),* > { + fn get() -> _I { + _I::from(Self::get()) + } + } + + impl< $($ty_params),* > $crate::traits::TypedGet for $name< $($ty_params),* > { + type Type = $type; + fn get() -> $type { + Self::get() } } }; @@ -501,6 +445,23 @@ macro_rules! parameter_types_impl_thread_local { pub fn set(t: $type) { [<$name:snake:upper>].with(|v| *v.borrow_mut() = t); } + + /// Mutate the internal value in place. + #[allow(unused)] + pub fn mutate R>(mutate: F) -> R{ + let mut current = Self::get(); + let result = mutate(&mut current); + Self::set(current); + result + } + + /// Get current value and replace with initial value of the parameter type. + #[allow(unused)] + pub fn take() -> $type { + let current = Self::get(); + Self::set($value); + current + } } )* } @@ -573,11 +534,12 @@ pub fn debug(data: &impl sp_std::fmt::Debug) { #[doc(inline)] pub use frame_support_procedural::{ - construct_runtime, decl_storage, transactional, RuntimeDebugNoBound, + construct_runtime, decl_storage, match_and_insert, transactional, PalletError, + RuntimeDebugNoBound, }; #[doc(hidden)] -pub use frame_support_procedural::__generate_dummy_part_checker; +pub use frame_support_procedural::{__create_tt_macro, __generate_dummy_part_checker}; /// Derive [`Clone`] but do not bound any generic. /// @@ -659,14 +621,23 @@ pub use frame_support_procedural::DebugNoBound; /// # use frame_support::DefaultNoBound; /// # use core::default::Default; /// trait Config { -/// type C: Default; +/// type C: Default; /// } /// /// // Foo implements [`Default`] because `C` bounds [`Default`]. /// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Default`]. /// #[derive(DefaultNoBound)] /// struct Foo { -/// c: T::C, +/// c: T::C, +/// } +/// +/// // Also works with enums, by specifying the default with #[default]: +/// #[derive(DefaultNoBound)] +/// enum Bar { +/// // Bar will implement Default as long as all of the types within Baz also implement default. +/// #[default] +/// Baz(T::C), +/// Quxx, /// } /// ``` pub use frame_support_procedural::DefaultNoBound; @@ -702,6 +673,21 @@ pub use frame_support_procedural::DefaultNoBound; /// ``` pub use frame_support_procedural::require_transactional; +/// Convert the current crate version into a [`CrateVersion`](crate::traits::CrateVersion). +/// +/// It uses the `CARGO_PKG_VERSION_MAJOR`, `CARGO_PKG_VERSION_MINOR` and +/// `CARGO_PKG_VERSION_PATCH` environment variables to fetch the crate version. +/// This means that the [`CrateVersion`](crate::traits::CrateVersion) +/// object will correspond to the version of the crate the macro is called in! +/// +/// # Example +/// +/// ``` +/// # use frame_support::{traits::CrateVersion, crate_to_crate_version}; +/// const Version: CrateVersion = crate_to_crate_version!(); +/// ``` +pub use frame_support_procedural::crate_to_crate_version; + /// Return Err of the expression: `return Err($expression);`. /// /// Used as `fail!(expression)`. @@ -734,9 +720,9 @@ macro_rules! assert_noop { $x:expr, $y:expr $(,)? ) => { - let h = $crate::storage_root(); + let h = $crate::storage_root($crate::StateVersion::V1); $crate::assert_err!($x, $y); - assert_eq!(h, $crate::storage_root()); + assert_eq!(h, $crate::storage_root($crate::StateVersion::V1), "storage has been mutated"); }; } @@ -749,9 +735,9 @@ macro_rules! assert_storage_noop { ( $x:expr ) => { - let h = $crate::storage_root(); + let h = $crate::storage_root($crate::StateVersion::V1); $x; - assert_eq!(h, $crate::storage_root()); + assert_eq!(h, $crate::storage_root($crate::StateVersion::V1)); }; } @@ -767,7 +753,7 @@ macro_rules! assert_err { /// Assert an expression returns an error specified. /// -/// This can be used on`DispatchResultWithPostInfo` when the post info should +/// This can be used on `DispatchResultWithPostInfo` when the post info should /// be ignored. #[macro_export] macro_rules! assert_err_ignore_postinfo { @@ -782,7 +768,7 @@ macro_rules! assert_err_with_weight { ($call:expr, $err:expr, $weight:expr $(,)? ) => { if let Err(dispatch_err_with_post) = $call { $crate::assert_err!($call.map(|_| ()).map_err(|e| e.error), $err); - assert_eq!(dispatch_err_with_post.post_info.actual_weight, $weight.into()); + assert_eq!(dispatch_err_with_post.post_info.actual_weight, $weight); } else { panic!("expected Err(_), got Ok(_).") } @@ -807,6 +793,32 @@ macro_rules! assert_ok { }; } +/// Assert that the maximum encoding size does not exceed the value defined in +/// [`MAX_MODULE_ERROR_ENCODED_SIZE`] during compilation. +/// +/// This macro is intended to be used in conjunction with `tt_call!`. +#[macro_export] +macro_rules! assert_error_encoded_size { + { + path = [{ $($path:ident)::+ }] + runtime = [{ $runtime:ident }] + assert_message = [{ $assert_message:literal }] + error = [{ $error:ident }] + } => { + const _: () = assert!( + < + $($path::)+$error<$runtime> as $crate::traits::PalletError + >::MAX_ENCODED_SIZE <= $crate::MAX_MODULE_ERROR_ENCODED_SIZE, + $assert_message + ); + }; + { + path = [{ $($path:ident)::+ }] + runtime = [{ $runtime:ident }] + assert_message = [{ $assert_message:literal }] + } => {}; +} + #[cfg(feature = "std")] #[doc(hidden)] pub use serde::{Deserialize, Serialize}; @@ -819,7 +831,8 @@ pub mod tests { StorageHasher, }; use codec::{Codec, EncodeLike}; - use sp_io::TestExternalities; + use frame_support::traits::CrateVersion; + use sp_io::{MultiRemovalResults, TestExternalities}; use sp_std::result; /// A PalletInfo implementation which just panics. @@ -832,11 +845,17 @@ pub mod tests { fn name() -> Option<&'static str> { unimplemented!("PanicPalletInfo mustn't be triggered by tests"); } + fn module_name() -> Option<&'static str> { + unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + } + fn crate_version() -> Option { + unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + } } pub trait Config: 'static { type BlockNumber: Codec + EncodeLike + Default + TypeInfo; - type Origin; + type RuntimeOrigin; type PalletInfo: crate::traits::PalletInfo; type DbWeight: crate::traits::Get; } @@ -847,9 +866,10 @@ pub mod tests { use super::Config; decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=self {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} } } + use self::module::Module; decl_storage! { @@ -875,9 +895,10 @@ pub mod tests { } struct Test; + impl Config for Test { type BlockNumber = u32; - type Origin = u32; + type RuntimeOrigin = u32; type PalletInfo = PanicPalletInfo; type DbWeight = (); } @@ -891,6 +912,7 @@ pub mod tests { trait Sorted { fn sorted(self) -> Self; } + impl Sorted for Vec { fn sorted(mut self) -> Self { self.sort(); @@ -898,6 +920,32 @@ pub mod tests { } } + #[test] + fn storage_alias_works() { + new_test_ext().execute_with(|| { + #[crate::storage_alias] + type GenericData2 = StorageMap< + Test, + Blake2_128Concat, + ::BlockNumber, + ::BlockNumber, + >; + + assert_eq!(Module::::generic_data2(5), None); + GenericData2::::insert(5, 5); + assert_eq!(Module::::generic_data2(5), Some(5)); + + /// Some random docs that ensure that docs are accepted + #[crate::storage_alias] + pub type GenericData = StorageMap< + Test2, + Blake2_128Concat, + ::BlockNumber, + ::BlockNumber, + >; + }); + } + #[test] fn map_issue_3318() { new_test_ext().execute_with(|| { @@ -1047,16 +1095,61 @@ pub mod tests { }); } + #[test] + fn double_map_basic_insert_remove_remove_prefix_with_commit_should_work() { + let key1 = 17u32; + let key2 = 18u32; + type DoubleMap = DataDM; + let mut e = new_test_ext(); + e.execute_with(|| { + // initialized during genesis + assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); + + // get / insert / take + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + DoubleMap::insert(&key1, &key2, &4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 4u64); + assert_eq!(DoubleMap::take(&key1, &key2), 4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // mutate + DoubleMap::mutate(&key1, &key2, |val| *val = 15); + assert_eq!(DoubleMap::get(&key1, &key2), 15u64); + + // remove + DoubleMap::remove(&key1, &key2); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // remove prefix + DoubleMap::insert(&key1, &key2, &4u64); + DoubleMap::insert(&key1, &(key2 + 1), &4u64); + DoubleMap::insert(&(key1 + 1), &key2, &4u64); + DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); + }); + e.commit_all().unwrap(); + e.execute_with(|| { + assert!(matches!( + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 2, unique: 2, loops: 2 } + )); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &key2), 4u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &(key2 + 1)), 4u64); + }); + } + #[test] fn double_map_basic_insert_remove_remove_prefix_should_work() { new_test_ext().execute_with(|| { + let key1 = 17u32; + let key2 = 18u32; type DoubleMap = DataDM; + // initialized during genesis assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); // get / insert / take - let key1 = 17u32; - let key2 = 18u32; assert_eq!(DoubleMap::get(&key1, &key2), 0u64); DoubleMap::insert(&key1, &key2, &4u64); assert_eq!(DoubleMap::get(&key1, &key2), 4u64); @@ -1064,9 +1157,7 @@ pub mod tests { assert_eq!(DoubleMap::get(&key1, &key2), 0u64); // mutate - DoubleMap::mutate(&key1, &key2, |val| { - *val = 15; - }); + DoubleMap::mutate(&key1, &key2, |val| *val = 15); assert_eq!(DoubleMap::get(&key1, &key2), 15u64); // remove @@ -1078,9 +1169,18 @@ pub mod tests { DoubleMap::insert(&key1, &(key2 + 1), &4u64); DoubleMap::insert(&(key1 + 1), &key2, &4u64); DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); + // all in overlay assert!(matches!( - DoubleMap::remove_prefix(&key1, None), - sp_io::KillStorageResult::AllRemoved(0), // all in overlay + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } + )); + // Note this is the incorrect answer (for now), since we are using v2 of + // `clear_prefix`. + // When we switch to v3, then this will become: + // MultiRemovalResults:: { maybe_cursor: None, backend: 0, unique: 2, loops: 2 }, + assert!(matches!( + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } )); assert_eq!(DoubleMap::get(&key1, &key2), 0u64); assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); @@ -1286,26 +1386,30 @@ pub mod pallet_prelude { #[cfg(feature = "std")] pub use crate::traits::GenesisBuild; pub use crate::{ - dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Parameter}, + dispatch::{ + DispatchClass, DispatchError, DispatchResult, DispatchResultWithPostInfo, Parameter, + Pays, + }, ensure, inherent::{InherentData, InherentIdentifier, ProvideInherent}, storage, storage::{ bounded_vec::BoundedVec, types::{ - CountedStorageMap, Key as NMapKey, OptionQuery, StorageDoubleMap, StorageMap, - StorageNMap, StorageValue, ValueQuery, + CountedStorageMap, Key as NMapKey, OptionQuery, ResultQuery, StorageDoubleMap, + StorageMap, StorageNMap, StorageValue, ValueQuery, }, }, traits::{ ConstU32, EnsureOrigin, Get, GetDefault, GetStorageVersion, Hooks, IsType, - PalletInfoAccess, StorageInfoTrait, + PalletInfoAccess, StorageInfoTrait, StorageVersion, TypedGet, }, - weights::{DispatchClass, Pays, Weight}, Blake2_128, Blake2_128Concat, Blake2_256, CloneNoBound, DebugNoBound, EqNoBound, Identity, PartialEqNoBound, RuntimeDebug, RuntimeDebugNoBound, Twox128, Twox256, Twox64Concat, }; pub use codec::{Decode, Encode, MaxEncodedLen}; + pub use frame_support::pallet_macros::*; + pub use scale_info::TypeInfo; pub use sp_runtime::{ traits::{MaybeSerializeDeserialize, Member, ValidateUnsigned}, transaction_validity::{ @@ -1313,42 +1417,169 @@ pub mod pallet_prelude { TransactionTag, TransactionValidity, TransactionValidityError, UnknownTransaction, ValidTransaction, }, + MAX_MODULE_ERROR_ENCODED_SIZE, }; pub use sp_std::marker::PhantomData; + pub use sp_weights::Weight; } -/// `pallet` attribute macro allows to define a pallet to be used in `construct_runtime!`. +/// The `pallet` attribute macro defines a pallet that can be used with +/// [`construct_runtime!`]. It must be attached to a module named `pallet` as follows: /// -/// It is define by a module item: /// ```ignore /// #[pallet] /// pub mod pallet { -/// ... +/// ... /// } /// ``` /// -/// Inside the module the macro will parse item with the attribute: `#[pallet::*]`, some -/// attributes are mandatory, some other optional. +/// Note that various types can be automatically imported using +/// [`frame_support::pallet_prelude`] and `frame_system::pallet_prelude`: /// -/// The attribute are explained with the syntax of non instantiable pallets, to see how pallet -/// with instance work see below example. -/// -/// Note various type can be automatically imported using pallet_prelude in frame_support and -/// frame_system: /// ```ignore /// #[pallet] /// pub mod pallet { -/// use frame_support::pallet_prelude::*; -/// use frame_system::pallet_prelude::*; -/// ... +/// use frame_support::pallet_prelude::*; +/// use frame_system::pallet_prelude::*; +/// ... /// } /// ``` /// -/// # Config trait: `#[pallet::config]` mandatory +/// # pallet::* Attributes +/// +/// The `pallet` macro will parse any items within your `pallet` module that are annotated with +/// `#[pallet::*]` attributes. Some of these attributes are mandatory and some are optional, +/// and they can attach to different types of items within your pallet depending on the +/// attribute in question. The full list of `#[pallet::*]` attributes is shown below in the +/// order in which they are mentioned in this document: +/// +/// * [`pallet::pallet`](#pallet-struct-placeholder-palletpallet-mandatory) +/// * [`pallet::config`](#config-trait-palletconfig-mandatory) +/// * [`pallet::constant`](#palletconstant) +/// * [`pallet::disable_frame_system_supertrait_check`](#disable_supertrait_check) +/// * [`pallet::generate_store($vis trait Store)`](#palletgenerate_storevis-trait-store) +/// * [`pallet::generate_storage_info`](#palletgenerate_storage_info) +/// * [`pallet::storage_version`](#palletstorage_version) +/// * [`pallet::hooks`](#hooks-pallethooks-optional) +/// * [`pallet::call`](#call-palletcall-optional) +/// * [`pallet::weight($expr)`](#palletweightexpr) +/// * [`pallet::compact`](#palletcompact-some_arg-some_type) +/// * [`pallet::call_index($idx)`](#palletcall_indexidx) +/// * [`pallet::extra_constants`](#extra-constants-palletextra_constants-optional) +/// * [`pallet::error`](#error-palleterror-optional) +/// * [`pallet::event`](#event-palletevent-optional) +/// * [`pallet::generate_deposit($visibility fn +/// deposit_event)`](#palletgenerate_depositvisibility-fn-deposit_event) +/// * [`pallet::storage`](#storage-palletstorage-optional) +/// * [`pallet::getter(fn $my_getter_fn_name)`](#palletgetterfn-my_getter_fn_name-optional) +/// * [`pallet::storage_prefix = "SomeName"`](#palletstorage_prefix--somename-optional) +/// * [`pallet::unbounded`](#palletunbounded-optional) +/// * [`pallet::whitelist_storage`](#palletwhitelist_storage-optional) +/// * [`cfg(..)`](#cfg-for-storage) (on storage items) +/// * [`pallet::type_value`](#type-value-pallettype_value-optional) +/// * [`pallet::genesis_config`](#genesis-config-palletgenesis_config-optional) +/// * [`pallet::genesis_build`](#genesis-build-palletgenesis_build-optional) +/// * [`pallet::inherent`](#inherent-palletinherent-optional) +/// * [`pallet::validate_unsigned`](#validate-unsigned-palletvalidate_unsigned-optional) +/// * [`pallet::origin`](#origin-palletorigin-optional) +/// +/// Note that at compile-time, the `#[pallet]` macro will analyze and expand all of these +/// attributes, ultimately removing their AST nodes before they can be parsed as real +/// attribute macro calls. This means that technically we do not need attribute macro +/// definitions for any of these attributes, however, for consistency and discoverability +/// reasons, we still maintain stub attribute macro definitions for all of these attributes in +/// the [`pallet_macros`] module which is automatically included in all pallets as part of the +/// pallet prelude. The actual "work" for all of these attribute macros can be found in the +/// macro expansion for `#[pallet]`. +/// +/// Also note that in this document, pallet attributes are explained using the syntax of +/// non-instantiable pallets. For an example of an instantiable pallet, see [this +/// example](#example-of-an-instantiable-pallet). +/// +/// # Dev Mode (`#[pallet(dev_mode)]`) +/// +/// Specifying the argument `dev_mode` on the `#[pallet]` or `#[frame_support::pallet]` +/// attribute attached to your pallet module 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. +/// * All storages are marked as unbounded, meaning you do not need to implement +/// `MaxEncodedLen` on storage types. This is equivalent to specifying `#[pallet::unbounded]` +/// on all storage type definitions. +/// +/// 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. +/// +///

+/// +/// # Pallet struct placeholder: `#[pallet::pallet]` (mandatory) +/// +/// The pallet struct placeholder `#[pallet::pallet]` is mandatory and allows you to specify +/// pallet information. +/// +/// The struct must be defined as follows: +/// ```ignore +/// #[pallet::pallet] +/// pub struct Pallet(_); +/// ``` +/// I.e. a regular struct definition named `Pallet`, with generic T and no where clause. /// -/// The trait defining generics of the pallet. +/// ## Macro expansion: +/// +/// The macro adds this attribute to the struct definition: +/// ```ignore +/// #[derive( +/// frame_support::CloneNoBound, +/// frame_support::EqNoBound, +/// frame_support::PartialEqNoBound, +/// frame_support::RuntimeDebugNoBound, +/// )] +/// ``` +/// and replaces the type `_` with `PhantomData`. It also implements on the pallet: +/// * [`GetStorageVersion`](`traits::GetStorageVersion`) +/// * [`OnGenesis`](`traits::OnGenesis`): contains some logic to write the pallet version into +/// storage. +/// * `PalletErrorTypeInfo`: provides the type information for the pallet error, if defined. +/// +/// It declares `type Module` type alias for `Pallet`, used by `construct_runtime`. +/// +/// It implements [`PalletInfoAccess`](`traits::PalletInfoAccess') on `Pallet` to ease access +/// to pallet information given by [`frame_support::traits::PalletInfo`]. (The implementation +/// uses the associated type `frame_system::Config::PalletInfo`). +/// +/// It implements [`StorageInfoTrait`](`traits::StorageInfoTrait`) on `Pallet` which give +/// information about all storages. +/// +/// If the attribute `generate_store` is set then the macro creates the trait `Store` and +/// implements it on `Pallet`. +/// +/// If the attribute `set_storage_max_encoded_len` is set then the macro calls +/// [`StorageInfoTrait`](`traits::StorageInfoTrait`) for each storage in the implementation of +/// [`StorageInfoTrait`](`traits::StorageInfoTrait`) for the pallet. Otherwise it implements +/// [`StorageInfoTrait`](`traits::StorageInfoTrait`) for the pallet using the +/// [`PartialStorageInfoTrait`](`traits::PartialStorageInfoTrait`) implementation of storages. +/// +/// # Config trait: `#[pallet::config]` (mandatory) +/// +/// The mandatory attribute `#[pallet::config]` defines the configurable options for the +/// pallet. +/// +/// Item must be defined as: /// -/// Item must be defined as /// ```ignore /// #[pallet::config] /// pub trait Config: frame_system::Config + $optionally_some_other_supertraits @@ -1357,72 +1588,103 @@ pub mod pallet_prelude { /// ... /// } /// ``` -/// I.e. a regular trait definition named `Config`, with supertrait `frame_system::Config`, -/// optionally other supertrait and where clause. /// -/// The associated type `Event` is reserved, if defined it must bounds `From` and -/// `IsType<::Event>`, see `#[pallet::event]` for more -/// information. +/// I.e. a regular trait definition named `Config`, with the supertrait +/// `frame_system::pallet::Config`, and optionally other supertraits and a where clause. +/// (Specifying other supertraits here is known as [tight +/// coupling](https://docs.substrate.io/reference/how-to-guides/pallet-design/use-tight-coupling/)) +/// +/// The associated type `RuntimeEvent` is reserved. If defined, it must have the bounds +/// `From` and `IsType<::RuntimeEvent>`. +/// +/// [`pallet::event`](`frame_support::pallet_macros::event`) must be present if `RuntimeEvent` +/// exists as a config item in your `#[pallet::config]`. +/// +/// Also see [`pallet::config`](`frame_support::pallet_macros::config`) +/// +/// ## `pallet::constant` +/// +/// The `#[pallet::constant]` attribute can be used to add an associated type trait bounded by +/// [`Get`](crate::traits::Get) from [`pallet::config`](#palletconfig) into metadata, e.g.: /// -/// To put `Get` associated type into metadatas, use the attribute `#[pallet::constant]`, e.g.: /// ```ignore /// #[pallet::config] /// pub trait Config: frame_system::Config { -/// #[pallet::constant] -/// type Foo: Get; +/// #[pallet::constant] +/// type Foo: Get; /// } /// ``` /// +/// Also see [`pallet::constant`](`frame_support::pallet_macros::constant`) +/// +/// ## `pallet::disable_frame_system_supertrait_check` +/// +/// /// To bypass the `frame_system::Config` supertrait check, use the attribute -/// `#[pallet::disable_frame_system_supertrait_check]`, e.g.: +/// `pallet::disable_frame_system_supertrait_check`, e.g.: +/// /// ```ignore /// #[pallet::config] /// #[pallet::disable_frame_system_supertrait_check] /// pub trait Config: pallet_timestamp::Config {} /// ``` /// -/// ### Macro expansion: +/// NOTE: Bypassing the `frame_system::Config` supertrait check is typically desirable when you +/// want to write an alternative to the `frame_system` pallet. /// -/// The macro expand pallet constant metadata with the information given by -/// `#[pallet::constant]`. +/// Also see +/// [`pallet::disable_frame_system_supertrait_check`](`frame_support::pallet_macros::disable_frame_system_supertrait_check`) /// -/// # Pallet struct placeholder: `#[pallet::pallet]` mandatory +/// ## Macro expansion: /// -/// The placeholder struct, on which is implemented pallet informations. +/// The macro expands pallet constant metadata with the information given by +/// `#[pallet::constant]`. /// -/// Item must be defined as followed: -/// ```ignore -/// #[pallet::pallet] -/// pub struct Pallet(_); -/// ``` -/// I.e. a regular struct definition named `Pallet`, with generic T and no where clause. +/// # `pallet::generate_store($vis trait Store)` +/// +/// To generate a `Store` trait associating all storages, annotate your `Pallet` struct with +/// the attribute `#[pallet::generate_store($vis trait Store)]`, e.g.: /// -/// To generate a `Store` trait associating all storages, use the attribute -/// `#[pallet::generate_store($vis trait Store)]`, e.g.: /// ```ignore /// #[pallet::pallet] /// #[pallet::generate_store(pub(super) trait Store)] /// pub struct Pallet(_); /// ``` -/// More precisely the store trait contains an associated type for each storage. It is -/// implemented for `Pallet` allowing to access the storage from pallet struct. +/// More precisely, the `Store` trait contains an associated type for each storage. It is +/// implemented for `Pallet` allowing access to the storage from pallet struct. /// /// Thus when defining a storage named `Foo`, it can later be accessed from `Pallet` using /// `::Foo`. /// +/// NOTE: this attribute is only valid when applied _directly_ to your `Pallet` struct +/// definition. +/// +/// Also see [`pallet::generate_store`](`frame_support::pallet_macros::generate_store`). +/// +/// # `pallet::generate_storage_info` +/// /// To generate the full storage info (used for PoV calculation) use the attribute -/// `#[pallet::set_storage_max_encoded_len]`, e.g.: +/// `#[pallet::generate_storage_info]`, e.g.: +/// /// ```ignore /// #[pallet::pallet] -/// #[pallet::set_storage_max_encoded_len] +/// #[pallet::generate_storage_info] /// pub struct Pallet(_); /// ``` /// -/// This require all storage to implement the trait [`traits::StorageInfoTrait`], thus all keys -/// and value types must bound [`pallet_prelude::MaxEncodedLen`]. +/// This requires all storage items to implement the trait [`traits::StorageInfoTrait`], thus +/// all keys and value types must be bound by [`pallet_prelude::MaxEncodedLen`]. Individual +/// storages can opt-out from this constraint by using `#[pallet::unbounded]` (see +/// `#[pallet::storage]` for more info). +/// +/// Also see [`pallet::generate_storage_info`](`frame_support::pallet_macros::generate_storage_info`) /// -/// As the macro implements [`traits::GetStorageVersion`], the current storage version needs to -/// be communicated to the macro. This can be done by using the `storage_version` attribute: +/// # `pallet::storage_version` +/// +/// Because the [`pallet::pallet`](#pallet-struct-placeholder-palletpallet-mandatory) macro +/// implements [`traits::GetStorageVersion`], the current storage version needs to be +/// communicated to the macro. This can be done by using the `pallet::storage_version` +/// attribute: /// /// ```ignore /// const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); @@ -1434,75 +1696,34 @@ pub mod pallet_prelude { /// /// If not present, the current storage version is set to the default value. /// -/// ### Macro expansion: +/// Also see [`pallet::storage_version`](`frame_support::pallet_macros::storage_version`) /// -/// The macro add this attribute to the struct definition: -/// ```ignore -/// #[derive( -/// frame_support::CloneNoBound, -/// frame_support::EqNoBound, -/// frame_support::PartialEqNoBound, -/// frame_support::RuntimeDebugNoBound, -/// )] -/// ``` -/// and replace the type `_` by `PhantomData`. +/// # Hooks: `#[pallet::hooks]` (optional) /// -/// It implements on pallet: -/// * [`traits::GetStorageVersion`] -/// * [`traits::OnGenesis`]: contains some logic to write pallet version into storage. -/// * `PalletErrorTypeInfo`: provides the type information for the pallet error, if defined. +/// The `pallet::hooks` attribute allows you to specify a `Hooks` implementation for `Pallet` +/// that specifies pallet-specific logic. /// -/// It declare `type Module` type alias for `Pallet`, used by [`construct_runtime`]. -/// -/// It implements [`traits::PalletInfoAccess`] on `Pallet` to ease access to pallet -/// informations given by [`frame_support::traits::PalletInfo`]. -/// (The implementation use the associated type `frame_system::Config::PalletInfo`). -/// -/// It implements [`traits::StorageInfoTrait`] on `Pallet` which give information about all -/// storages. -/// -/// If the attribute generate_store is set then the macro creates the trait `Store` and -/// implements it on `Pallet`. -/// -/// If the attribute set_storage_max_encoded_len is set then the macro call -/// [`traits::StorageInfoTrait`] for each storage in the implementation of -/// [`traits::StorageInfoTrait`] for the pallet. -/// Otherwise it implements [`traits::StorageInfoTrait`] for the pallet using the -/// [`traits::PartialStorageInfoTrait`] implementation of storages. -/// -/// # Hooks: `#[pallet::hooks]` optional -/// -/// Implementation of `Hooks` on `Pallet` allowing to define some specific pallet logic. -/// -/// Item must be defined as +/// The item the attribute attaches to must be defined as follows: /// ```ignore /// #[pallet::hooks] /// impl Hooks> for Pallet $optional_where_clause { +/// ... /// } /// ``` /// I.e. a regular trait implementation with generic bound: `T: Config`, for the trait -/// `Hooks>` (they are defined in preludes), for the type `Pallet` -/// and with an optional where clause. +/// `Hooks>` (they are defined in preludes), for the type `Pallet` and +/// with an optional where clause. /// -/// If no `#[pallet::hooks]` exists, then a default implementation corresponding to the -/// following code is automatically generated: +/// If no `#[pallet::hooks]` exists, then the following default implementation is +/// automatically generated: /// ```ignore /// #[pallet::hooks] /// impl Hooks> for Pallet {} /// ``` /// -/// ### Macro expansion: -/// -/// The macro implements the traits `OnInitialize`, `OnIdle`, `OnFinalize`, `OnRuntimeUpgrade`, -/// `OffchainWorker`, `IntegrityTest` using `Hooks` implementation. +/// Also see [`pallet::hooks`](`frame_support::pallet_macros::hooks`) /// -/// NOTE: OnRuntimeUpgrade is implemented with `Hooks::on_runtime_upgrade` and some additional -/// logic. E.g. logic to write pallet version into storage. -/// -/// NOTE: The macro also adds some tracing logic when implementing the above traits. The -/// following hooks emit traces: `on_initialize`, `on_finalize` and `on_runtime_upgrade`. -/// -/// # Call: `#[pallet::call]` optional +/// # Call: `#[pallet::call]` (optional) /// /// Implementation of pallet dispatchables. /// @@ -1524,42 +1745,50 @@ pub mod pallet_prelude { /// } /// ``` /// I.e. a regular type implementation, with generic `T: Config`, on type `Pallet`, with -/// optional where clause. +/// an optional where clause. /// -/// Each dispatchable needs to define a weight with `#[pallet::weight($expr)]` attribute, -/// the first argument must be `origin: OriginFor`, compact encoding for argument can be -/// used using `#[pallet::compact]`, function must return `DispatchResultWithPostInfo` or -/// `DispatchResult`. +/// ## `#[pallet::weight($expr)]` /// -/// All arguments must implement `Debug`, `PartialEq`, `Eq`, `Decode`, `Encode`, `Clone`. For -/// ease of use, bound the trait `Member` available in frame_support::pallet_prelude. +/// Each dispatchable needs to define a weight with `#[pallet::weight($expr)]` attribute, the +/// first argument must be `origin: OriginFor`. /// -/// If no `#[pallet::call]` exists, then a default implementation corresponding to the -/// following code is automatically generated: -/// ```ignore -/// #[pallet::call] -/// impl Pallet {} -/// ``` +/// Also see [`pallet::weight`](`frame_support::pallet_macros::weight`) +/// +/// ### `#[pallet::compact] $some_arg: $some_type` +/// +/// Compact encoding for arguments can be achieved via `#[pallet::compact]`. The function must +/// return a `DispatchResultWithPostInfo` or `DispatchResult`. +/// +/// Also see [`pallet::compact`](`frame_support::pallet_macros::compact`) /// -/// **WARNING**: modifying dispatchables, changing their order, removing some must be done with -/// care. Indeed this will change the outer runtime call type (which is an enum with one -/// variant per pallet), this outer runtime call can be stored on-chain (e.g. in -/// pallet-scheduler). Thus migration might be needed. +/// ## `#[pallet::call_index($idx)]` /// -/// ### Macro expansion +/// Each dispatchable may also be annotated with the `#[pallet::call_index($idx)]` attribute, +/// which explicitly defines the codec index for the dispatchable function in the `Call` enum. /// -/// The macro create an enum `Call` with one variant per dispatchable. This enum implements: -/// `Clone`, `Eq`, `PartialEq`, `Debug` (with stripped implementation in `not("std")`), -/// `Encode`, `Decode`, `GetDispatchInfo`, `GetCallName`, `UnfilteredDispatchable`. +/// All call indexes start from 0, until it encounters a dispatchable function with a defined +/// call index. The dispatchable function that lexically follows the function with a defined +/// call index will have that call index, but incremented by 1, e.g. if there are 3 +/// dispatchable functions `fn foo`, `fn bar` and `fn qux` in that order, and only `fn bar` +/// has a call index of 10, then `fn qux` will have an index of 11, instead of 1. /// -/// The macro implement on `Pallet`, the `Callable` trait and a function `call_functions` which -/// returns the dispatchable metadatas. +/// **WARNING**: modifying dispatchables, changing their order, removing some, etc., must be +/// done with care. Indeed this will change the outer runtime call type (which is an enum with +/// one variant per pallet), this outer runtime call can be stored on-chain (e.g. in +/// `pallet-scheduler`). Thus migration might be needed. To mitigate against some of this, the +/// `#[pallet::call_index($idx)]` attribute can be used to fix the order of the dispatchable so +/// that the `Call` enum encoding does not change after modification. As a general rule of +/// thumb, it is therefore adventageous to always add new calls to the end so you can maintain +/// the existing order of calls. /// -/// # Extra constants: `#[pallet::extra_constants]` optional +/// Also see [`pallet::call_index`](`frame_support::pallet_macros::call_index`) /// -/// Allow to define some extra constants to put into constant metadata. +/// # Extra constants: `#[pallet::extra_constants]` (optional) +/// +/// Allows you to define some extra constants to be added into constant metadata. /// /// Item must be defined as: +/// /// ```ignore /// #[pallet::extra_constants] /// impl Pallet where $optional_where_clause { @@ -1570,45 +1799,63 @@ pub mod pallet_prelude { /// ... /// } /// ``` -/// I.e. a regular rust implement block with some optional where clause and functions with 0 -/// args, 0 generics, and some return type. +/// I.e. a regular rust `impl` block with some optional where clause and functions with 0 args, +/// 0 generics, and some return type. +/// +/// ## Macro expansion /// -/// ### Macro expansion +/// The macro add some extra constants to pallet constant metadata. /// -/// The macro add some extra constant to pallet constant metadata. +/// Also see: [`pallet::extra_constants`](`frame_support::pallet_macros::extra_constants`) /// -/// # Error: `#[pallet::error]` optional +/// # Error: `#[pallet::error]` (optional) /// -/// Allow to define an error type to be return from dispatchable on error. -/// This error type informations are put into metadata. +/// The `#[pallet::error]` attribute allows you to define an error enum that will be returned +/// from the dispatchable when an error occurs. The information for this error type is then +/// stored in metadata. /// /// Item must be defined as: +/// /// ```ignore /// #[pallet::error] /// pub enum Error { /// /// $some_optional_doc /// $SomeFieldLessVariant, +/// /// $some_more_optional_doc +/// $SomeVariantWithOneField(FieldType), /// ... /// } /// ``` -/// I.e. a regular rust enum named `Error`, with generic `T` and fieldless variants. -/// The generic `T` mustn't bound anything and where clause is not allowed. But bounds and -/// where clause shouldn't be needed for any usecase. +/// I.e. a regular enum named `Error`, with generic `T` and fieldless or multiple-field +/// variants. +/// +/// Any field type in the enum variants must implement [`scale_info::TypeInfo`] in order to be +/// properly used in the metadata, and its encoded size should be as small as possible, +/// preferably 1 byte in size in order to reduce storage size. The error enum itself has an +/// absolute maximum encoded size specified by [`MAX_MODULE_ERROR_ENCODED_SIZE`]. /// -/// ### Macro expansion +/// (1 byte can still be 256 different errors. The more specific the error, the easier it is to +/// diagnose problems and give a better experience to the user. Don't skimp on having lots of +/// individual error conditions.) /// -/// The macro implements `Debug` trait and functions `as_u8` using variant position, and -/// `as_str` using variant doc. +/// Field types in enum variants must also implement [`PalletError`](traits::PalletError), +/// otherwise the pallet will fail to compile. Rust primitive types have already implemented +/// the [`PalletError`](traits::PalletError) trait along with some commonly used stdlib types +/// such as [`Option`] and [`PhantomData`](`frame_support::dispatch::marker::PhantomData`), and +/// hence in most use cases, a manual implementation is not necessary and is discouraged. /// -/// The macro implements `From>` for `&'static str`. -/// The macro implements `From>` for `DispatchError`. +/// The generic `T` must not bound anything and a `where` clause is not allowed. That said, +/// bounds and/or a where clause should not needed for any use-case. /// -/// # Event: `#[pallet::event]` optional +/// Also see: [`pallet::error`](`frame_support::pallet_macros::error`) /// -/// Allow to define pallet events, pallet events are stored in the block when they deposited -/// (and removed in next block). +/// # Event: `#[pallet::event]` (optional) +/// +/// Allows you to define pallet events. Pallet events are stored under the `system` / `events` +/// key when the block is applied (and then replaced when the next block writes it's events). +/// +/// The Event enum must be defined as follows: /// -/// Item is defined as: /// ```ignore /// #[pallet::event] /// #[pallet::generate_deposit($visibility fn deposit_event)] // Optional @@ -1618,112 +1865,148 @@ pub mod pallet_prelude { /// ... /// } /// ``` -/// I.e. an enum (with named or unnamed fields variant), named Event, with generic: none or `T` -/// or `T: Config`, and optional where clause. /// -/// Each field must implement `Clone`, `Eq`, `PartialEq`, `Encode`, `Decode`, and `Debug` (on -/// std only). -/// For ease of use, bound the trait `Member` available in frame_support::pallet_prelude. +/// I.e. an enum (with named or unnamed fields variant), named `Event`, with generic: none or +/// `T` or `T: Config`, and optional w here clause. /// -/// The attribute `#[pallet::generate_deposit($visibility fn deposit_event)]` generate a helper -/// function on `Pallet` to deposit event. +/// Each field must implement [`Clone`], [`Eq`], [`PartialEq`], [`Encode`], [`Decode`], and +/// [`Debug`] (on std only). For ease of use, bound by the trait +/// [`Member`](`frame_support::pallet_prelude::Member`), available in +/// frame_support::pallet_prelude. /// -/// NOTE: For instantiable pallet, event must be generic over T and I. +/// Also see [`pallet::event`](`frame_support::pallet_macros::event`) /// -/// ### Macro expansion: +/// ## `#[pallet::generate_deposit($visibility fn deposit_event)]` /// -/// Macro will add on enum `Event` the attributes: -/// * `#[derive(frame_support::CloneNoBound)]`, -/// * `#[derive(frame_support::EqNoBound)]`, -/// * `#[derive(frame_support::PartialEqNoBound)]`, -/// * `#[derive(codec::Encode)]`, -/// * `#[derive(codec::Decode)]`, -/// * `#[derive(frame_support::RuntimeDebugNoBound)]` +/// The attribute `#[pallet::generate_deposit($visibility fn deposit_event)]` generates a +/// helper function on `Pallet` that handles deposit events. /// -/// Macro implements `From>` for (). +/// NOTE: For instantiable pallets, the event must be generic over `T` and `I`. /// -/// Macro implements metadata function on `Event` returning the `EventMetadata`. +/// Also see [`pallet::generate_deposit`](`frame_support::pallet_macros::generate_deposit`) /// -/// If `#[pallet::generate_deposit]` then macro implement `fn deposit_event` on `Pallet`. +/// # Storage: `#[pallet::storage]` (optional) /// -/// # Storage: `#[pallet::storage]` optional +/// The `#[pallet::storage]` attribute lets you define some abstract storage inside of runtime +/// storage and also set its metadata. This attribute can be used multiple times. /// -/// Allow to define some abstract storage inside runtime storage and also set its metadata. -/// This attribute can be used multiple times. +/// Item should be defined as: /// -/// Item is defined as: /// ```ignore /// #[pallet::storage] /// #[pallet::getter(fn $getter_name)] // optional /// $vis type $StorageName<$some_generic> $optional_where_clause /// = $StorageType<$generic_name = $some_generics, $other_name = $some_other, ...>; /// ``` -/// or with unnamed generic +/// +/// or with unnamed generic: +/// /// ```ignore /// #[pallet::storage] /// #[pallet::getter(fn $getter_name)] // optional /// $vis type $StorageName<$some_generic> $optional_where_clause /// = $StorageType<_, $some_generics, ...>; /// ``` -/// I.e. it must be a type alias, with generics: `T` or `T: Config`, aliased type must be one -/// of `StorageValue`, `StorageMap` or `StorageDoubleMap` (defined in frame_support). -/// The generic arguments of the storage type can be given in two manner: named and unnamed. -/// For named generic argument: the name for each argument is the one as define on the storage -/// struct: -/// * [`pallet_prelude::StorageValue`] expect `Value` and optionally `QueryKind` and `OnEmpty`, -/// * [`pallet_prelude::StorageMap`] expect `Hasher`, `Key`, `Value` and optionally `QueryKind` -/// and `OnEmpty`, -/// * [`pallet_prelude::CountedStorageMap`] expect `Hasher`, `Key`, `Value` and optionally -/// `QueryKind` and `OnEmpty`, -/// * [`pallet_prelude::StorageDoubleMap`] expect `Hasher1`, `Key1`, `Hasher2`, `Key2`, `Value` -/// and optionally `QueryKind` and `OnEmpty`. -/// -/// For unnamed generic argument: Their first generic must be `_` as it is replaced by the -/// macro and other generic must declared as a normal declaration of type generic in rust. /// -/// The Prefix generic written by the macro is generated using -/// `PalletInfo::name::>()` and the name of the storage type. -/// E.g. if runtime names the pallet "MyExample" then the storage `type Foo = ...` use the -/// prefix: `Twox128(b"MyExample") ++ Twox128(b"Foo")`. -/// -/// For the `CountedStorageMap` variant, the Prefix also implements -/// `CountedStorageMapInstance`. It associate a `CounterPrefix`, which is implemented same as -/// above, but the storage prefix is prepend with `"CounterFor"`. -/// E.g. if runtime names the pallet "MyExample" then the storage -/// `type Foo = CountedStorageaMap<...>` will store its counter at the prefix: -/// `Twox128(b"MyExample") ++ Twox128(b"CounterForFoo")`. +/// I.e. it must be a type alias, with generics: `T` or `T: Config`. The aliased type must be +/// one of [`StorageValue`](`pallet_prelude::StorageValue`), +/// [`StorageMap`](`pallet_prelude::StorageMap`) or +/// [`StorageDoubleMap`](`pallet_prelude::StorageDoubleMap`). The generic arguments of the +/// storage type can be given in two manners: named and unnamed. For named generic arguments, +/// the name for each argument should match the name defined for it on the storage struct: +/// * [`StorageValue`](`pallet_prelude::StorageValue`) expects `Value` and optionally +/// `QueryKind` and `OnEmpty`, +/// * [`StorageMap`](`pallet_prelude::StorageMap`) expects `Hasher`, `Key`, `Value` and +/// optionally `QueryKind` and `OnEmpty`, +/// * [`CountedStorageMap`](`pallet_prelude::CountedStorageMap`) expects `Hasher`, `Key`, +/// `Value` and optionally `QueryKind` and `OnEmpty`, +/// * [`StorageDoubleMap`](`pallet_prelude::StorageDoubleMap`) expects `Hasher1`, `Key1`, +/// `Hasher2`, `Key2`, `Value` and optionally `QueryKind` and `OnEmpty`. +/// +/// For unnamed generic arguments: Their first generic must be `_` as it is replaced by the +/// macro and other generic must declared as a normal generic type declaration. +/// +/// The `Prefix` generic written by the macro is generated using +/// `PalletInfo::name::>()` and the name of the storage type. E.g. if runtime names +/// the pallet "MyExample" then the storage `type Foo = ...` should use the prefix: +/// `Twox128(b"MyExample") ++ Twox128(b"Foo")`. +/// +/// For the [`CountedStorageMap`](`pallet_prelude::CountedStorageMap`) variant, the `Prefix` +/// also implements +/// [`CountedStorageMapInstance`](`frame_support::storage::types::CountedStorageMapInstance`). +/// It also associates a [`CounterPrefix`](`pallet_prelude::CounterPrefix'), which is +/// implemented the same as above, but the storage prefix is prepend with `"CounterFor"`. E.g. +/// if runtime names the pallet "MyExample" then the storage `type Foo = +/// CountedStorageaMap<...>` will store its counter at the prefix: `Twox128(b"MyExample") ++ +/// Twox128(b"CounterForFoo")`. /// /// E.g: +/// /// ```ignore /// #[pallet::storage] /// pub(super) type MyStorage = StorageMap; /// ``` -/// In this case the final prefix used by the map is -/// `Twox128(b"MyExample") ++ Twox128(b"OtherName")`. /// -/// The optional attribute `#[pallet::getter(fn $my_getter_fn_name)]` allows to define a +/// In this case the final prefix used by the map is `Twox128(b"MyExample") ++ +/// Twox128(b"OtherName")`. +/// +/// Also see [`pallet::storage`](`frame_support::pallet_macros::storage`) +/// +/// ## `#[pallet::getter(fn $my_getter_fn_name)]` (optional) +/// +/// The optional attribute `#[pallet::getter(fn $my_getter_fn_name)]` allows you to define a /// getter function on `Pallet`. /// -/// The optional attribute `#[pallet::storage_prefix = "SomeName"]` allow to define the storage -/// prefix to use, see how `Prefix` generic is implemented above. +/// Also see [`pallet::getter`](`frame_support::pallet_macros::getter`) +/// +/// ## `#[pallet::storage_prefix = "SomeName"]` (optional) +/// +/// The optional attribute `#[pallet::storage_prefix = "SomeName"]` allows you to define the +/// storage prefix to use, see how `Prefix` generic is implemented above. This is helpful if +/// you wish to rename the storage field but don't want to perform a migration. /// /// E.g: +/// /// ```ignore /// #[pallet::storage] /// #[pallet::storage_prefix = "foo"] /// #[pallet::getter(fn my_storage)] /// pub(super) type MyStorage = StorageMap; /// ``` +/// /// or +/// /// ```ignore /// #[pallet::storage] /// #[pallet::getter(fn my_storage)] /// pub(super) type MyStorage = StorageMap<_, Blake2_128Concat, u32, u32>; /// ``` /// +/// Also see [`pallet::storage_prefix`](`frame_support::pallet_macros::storage_prefix`) +/// +/// ## `#[pallet::unbounded]` (optional) +/// +/// The optional attribute `#[pallet::unbounded]` declares the storage as unbounded. When +/// implementating the storage info (when `#[pallet::generate_storage_info]` is specified on +/// the pallet struct placeholder), the size of the storage will be declared as unbounded. This +/// can be useful for storage which can never go into PoV (Proof of Validity). +/// +/// Also see [`pallet::unbounded`](`frame_support::pallet_macros::unbounded`) +/// +/// ## `#[pallet::whitelist_storage]` (optional) +/// +/// The optional attribute `#[pallet::whitelist_storage]` will declare the storage as +/// whitelisted from benchmarking. +/// +/// See +/// [`pallet::whitelist_storage`](frame_support::pallet_macros::whitelist_storage) +/// for more info. +/// +/// ## `#[cfg(..)]` (for storage) /// The optional attributes `#[cfg(..)]` allow conditional compilation for the storage. /// /// E.g: +/// /// ```ignore /// #[cfg(feature = "my-feature")] /// #[pallet::storage] @@ -1733,6 +2016,24 @@ pub mod pallet_prelude { /// All the `cfg` attributes are automatically copied to the items generated for the storage, /// i.e. the getter, storage prefix, and the metadata element etc. /// +/// Any type placed as the `QueryKind` parameter must implement +/// [`frame_support::storage::types::QueryKindTrait`]. There are 3 implementations of this +/// trait by default: +/// +/// 1. [`OptionQuery`](`frame_support::storage::types::OptionQuery`), the default `QueryKind` +/// used when this type parameter is omitted. Specifying this as the `QueryKind` would cause +/// storage map APIs that return a `QueryKind` to instead return an [`Option`], returning +/// `Some` when a value does exist under a specified storage key, and `None` otherwise. +/// 2. [`ValueQuery`](`frame_support::storage::types::ValueQuery`) causes storage map APIs that +/// return a `QueryKind` to instead return the value type. In cases where a value does not +/// exist under a specified storage key, the `OnEmpty` type parameter on `QueryKindTrait` is +/// used to return an appropriate value. +/// 3. [`ResultQuery`](`frame_support::storage::types::ResultQuery`) causes storage map APIs +/// that return a `QueryKind` to instead return a `Result`, with `T` being the value +/// type and `E` being the pallet error type specified by the `#[pallet::error]` attribute. +/// In cases where a value does not exist under a specified storage key, an `Err` with the +/// specified pallet error variant is returned. +/// /// NOTE: If the `QueryKind` generic parameter is still generic at this stage or is using some /// type alias then the generation of the getter might fail. In this case the getter can be /// implemented manually. @@ -1741,60 +2042,62 @@ pub mod pallet_prelude { /// usable at all). We use [`StorageHasher::METADATA`] for the metadata of the hasher of the /// storage item. Thus generic hasher is supported. /// -/// ### Macro expansion +/// ## Macro expansion /// /// For each storage item the macro generates a struct named /// `_GeneratedPrefixForStorage$NameOfStorage`, and implements /// [`StorageInstance`](traits::StorageInstance) on it using the pallet and storage name. It -/// then uses it as the first generic of the aliased type. -/// For `CountedStorageMap`, `CountedStorageMapInstance` is implemented, and another similar -/// struct is generated. +/// then uses it as the first generic of the aliased type. For +/// [`CountedStorageMap`](`pallet_prelude::CountedStorageMap`), +/// [`CountedStorageMapInstance`](`frame_support::storage::types::CountedStorageMapInstance`) +/// is implemented, and another similar struct is generated. /// -/// For named generic, the macro will reorder the generics, and remove the names. +/// For a named generic, the macro will reorder the generics, and remove the names. /// -/// The macro implements the function `storage_metadata` on `Pallet` implementing the metadata -/// for all storage items based on their kind: +/// The macro implements the function `storage_metadata` on the `Pallet` implementing the +/// metadata for all storage items based on their kind: /// * for a storage value, the type of the value is copied into the metadata /// * for a storage map, the type of the values and the key's type is copied into the metadata -/// * for a storage double map, the type of the values, and the types of key1 and key2 are +/// * for a storage double map, the type of the values, and the types of `key1` and `key2` are /// copied into the metadata. /// -/// # Type value: `#[pallet::type_value]` optional +/// # Type value: `#[pallet::type_value]` (optional) /// -/// Helper to define a struct implementing `Get` trait. To ease use of storage types. -/// This attribute can be used multiple time. +/// The `#[pallet::type_value]` attribute lets you define a struct implementing the +/// [`Get`](crate::traits::Get) trait to ease use of storage types. This attribute is meant to +/// be used alongside [`#[pallet::storage]`](#storage-palletstorage-optional) to define a +/// storage's default value. This attribute can be used multiple times. +/// +/// Item must be defined as: /// -/// Item is defined as /// ```ignore /// #[pallet::type_value] /// fn $MyDefaultName<$some_generic>() -> $default_type $optional_where_clause { $expr } /// ``` +/// /// I.e.: a function definition with generics none or `T: Config` and a returned type. /// /// E.g.: +/// /// ```ignore /// #[pallet::type_value] /// fn MyDefault() -> T::Balance { 3.into() } /// ``` /// -/// NOTE: This attribute is meant to be used alongside `#[pallet::storage]` to defined some -/// specific default value in storage. +/// Also see [`pallet::type_value`](`frame_support::pallet_macros::type_value`) /// -/// ### Macro expansion +/// # Genesis config: `#[pallet::genesis_config]` (optional) /// -/// Macro renames the function to some internal name, generate a struct with the original name -/// of the function and its generic, and implement `Get<$ReturnType>` by calling the user -/// defined function. +/// The `#[pallet::genesis_config]` attribute allows you to define the genesis configuration +/// for the pallet. /// -/// # Genesis config: `#[pallet::genesis_config]` optional -/// -/// Allow to define the genesis configuration of the pallet. -/// -/// Item is defined as either an enum or a struct. -/// It needs to be public and implement trait GenesisBuild with `#[pallet::genesis_build]`. -/// The type generics is constrained to be either none, or `T` or `T: Config`. +/// Item is defined as either an enum or a struct. It needs to be public and implement the +/// trait [`GenesisBuild`](`traits::GenesisBuild`) with +/// [`#[pallet::genesis_build]`](#genesis-build-palletgenesis_build-optional). The type +/// generics are constrained to be either none, or `T` or `T: Config`. /// /// E.g: +/// /// ```ignore /// #[pallet::genesis_config] /// pub struct GenesisConfig { @@ -1802,31 +2105,28 @@ pub mod pallet_prelude { /// } /// ``` /// -/// ### Macro expansion +/// Also see [`pallet::genesis_config`](`frame_support::pallet_macros::genesis_config`) /// -/// Macro will add the following attribute on it: -/// * `#[cfg(feature = "std")]` -/// * `#[derive(Serialize, Deserialize)]` -/// * `#[serde(rename_all = "camelCase")]` -/// * `#[serde(deny_unknown_fields)]` -/// * `#[serde(bound(serialize = ""))]` -/// * `#[serde(bound(deserialize = ""))]` +/// # Genesis build: `#[pallet::genesis_build]` (optional) /// -/// # Genesis build: `#[pallet::genesis_build]` optional +/// The `#[pallet::genesis_build]` attribute allows you to define how `genesis_configuration` +/// is built. This takes as input the `GenesisConfig` type (as `self`) and constructs the +/// pallet's initial state. /// -/// Allow to define how genesis_configuration is built. +/// The impl must be defined as: /// -/// Item is defined as /// ```ignore /// #[pallet::genesis_build] /// impl GenesisBuild for GenesisConfig<$maybe_generics> { /// fn build(&self) { $expr } /// } /// ``` -/// I.e. a rust trait implementation with generic `T: Config`, of trait `GenesisBuild` on +/// +/// I.e. a trait implementation with generic `T: Config`, of trait `GenesisBuild` on /// type `GenesisConfig` with generics none or `T`. /// /// E.g.: +/// /// ```ignore /// #[pallet::genesis_build] /// impl GenesisBuild for GenesisConfig { @@ -1834,87 +2134,93 @@ pub mod pallet_prelude { /// } /// ``` /// -/// ### Macro expansion +/// Also see [`pallet::genesis_build`](`frame_support::pallet_macros::genesis_build`) /// -/// Macro will add the following attribute on it: -/// * `#[cfg(feature = "std")]` +/// # Inherent: `#[pallet::inherent]` (optional) /// -/// Macro will implement `sp_runtime::BuildModuleGenesisStorage` using `()` as second generic -/// for non-instantiable pallets. +/// The `#[pallet::inherent]` attribute allows the pallet to provide some +/// [inherent](https://docs.substrate.io/fundamentals/transaction-types/#inherent-transactions). +/// An inherent is some piece of data that is inserted by a block authoring node at block +/// creation time and can either be accepted or rejected by validators based on whether the +/// data falls within an acceptable range. /// -/// # Inherent: `#[pallet::inherent]` optional +/// The most common inherent is the `timestamp` that is inserted into every block. Since there +/// is no way to validate timestamps, validators simply check that the timestamp reported by +/// the block authoring node falls within an acceptable range. /// -/// Allow the pallet to provide some inherent: +/// Item must be defined as: /// -/// Item is defined as: /// ```ignore /// #[pallet::inherent] /// impl ProvideInherent for Pallet { /// // ... regular trait implementation /// } /// ``` -/// I.e. a trait implementation with bound `T: Config`, of trait `ProvideInherent` for type -/// `Pallet`, and some optional where clause. /// -/// ### Macro expansion +/// I.e. a trait implementation with bound `T: Config`, of trait +/// [`ProvideInherent`](`pallet_prelude::ProvideInherent`) for type `Pallet`, and some +/// optional where clause. +/// +/// Also see [`pallet::inherent`](`frame_support::pallet_macros::inherent`) /// -/// Macro make currently no use of this information, but it might use this information in the -/// future to give information directly to construct_runtime. +/// # Validate unsigned: `#[pallet::validate_unsigned]` (optional) /// -/// # Validate unsigned: `#[pallet::validate_unsigned]` optional +/// The `#[pallet::validate_unsigned]` attribute allows the pallet to validate some unsigned +/// transaction: /// -/// Allow the pallet to validate some unsigned transaction: +/// Item must be defined as: /// -/// Item is defined as: /// ```ignore /// #[pallet::validate_unsigned] /// impl ValidateUnsigned for Pallet { /// // ... regular trait implementation /// } /// ``` -/// I.e. a trait implementation with bound `T: Config`, of trait `ValidateUnsigned` for type -/// `Pallet`, and some optional where clause. /// -/// NOTE: There is also `sp_runtime::traits::SignedExtension` that can be used to add some -/// specific logic for transaction validation. +/// I.e. a trait implementation with bound `T: Config`, of trait +/// [`ValidateUnsigned`](`pallet_prelude::ValidateUnsigned`) for type `Pallet`, and some +/// optional where clause. /// -/// ### Macro expansion +/// NOTE: There is also the [`sp_runtime::traits::SignedExtension`] trait that can be used to +/// add some specific logic for transaction validation. /// -/// Macro make currently no use of this information, but it might use this information in the -/// future to give information directly to construct_runtime. +/// Also see [`pallet::validate_unsigned`](`frame_support::pallet_macros::validate_unsigned`) /// -/// # Origin: `#[pallet::origin]` optional +/// # Origin: `#[pallet::origin]` (optional) /// -/// Allow to define some origin for the pallet. +/// The `#[pallet::origin]` attribute allows you to define some origin for the pallet. /// -/// Item must be either a type alias or an enum or a struct. It needs to be public. +/// Item must be either a type alias, an enum, or a struct. It needs to be public. /// /// E.g.: +/// /// ```ignore /// #[pallet::origin] /// pub struct Origin(PhantomData<(T)>); /// ``` /// /// **WARNING**: modifying origin changes the outer runtime origin. This outer runtime origin -/// can be stored on-chain (e.g. in pallet-scheduler), thus any change must be done with care +/// can be stored on-chain (e.g. in `pallet-scheduler`), thus any change must be done with care /// as it might require some migration. /// -/// NOTE: for instantiable pallet, origin must be generic over T and I. +/// NOTE: for instantiable pallets, the origin must be generic over `T` and `I`. /// -/// # General notes on instantiable pallet +/// Also see [`pallet::origin`](`frame_support::pallet_macros::origin`) /// -/// An instantiable pallet is one where Config is generic, i.e. `Config`. This allow runtime -/// to implement multiple instance of the pallet, by using different type for the generic. -/// This is the sole purpose of the generic `I`. -/// But because `PalletInfo` requires `Pallet` placeholder to be static it is important to -/// bound `'static` whenever `PalletInfo` can be used. -/// And in order to have instantiable pallet usable as a regular pallet without instance, it is -/// important to bound `= ()` on every types. +/// # General notes on instantiable pallets /// -/// Thus impl bound look like `impl, I: 'static>`, and types look like +/// An instantiable pallet is one where Config is generic, i.e. `Config`. This allows +/// runtime to implement multiple instances of the pallet, by using different types for the +/// generic. This is the sole purpose of the generic `I`, but because +/// [`PalletInfo`](`traits::PalletInfo`) requires the `Pallet` placeholder to be static, it is +/// important to bound by `'static` whenever [`PalletInfo`](`traits::PalletInfo`) can be used. +/// Additionally, in order to make an instantiable pallet usable as a regular pallet without an +/// instance, it is important to bound by `= ()` on every type. +/// +/// Thus impl bound looks like `impl, I: 'static>`, and types look like /// `SomeType` or `SomeType, I: 'static = ()>`. /// -/// # Example for pallet without instance. +/// # Example of a non-instantiable pallet /// /// ``` /// pub use pallet::*; // reexport in crate namespace for `construct_runtime!` @@ -1935,8 +2241,8 @@ pub mod pallet_prelude { /// pub trait Config: frame_system::Config { /// #[pallet::constant] // put the constant in metadata /// type MyGetParam: Get; -/// type Balance: Parameter + From; -/// type Event: From> + IsType<::Event>; +/// type Balance: Parameter + MaxEncodedLen + From; +/// type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// } /// /// // Define some additional constant to put into the constant metadata. @@ -2108,7 +2414,7 @@ pub mod pallet_prelude { /// } /// ``` /// -/// # Example for pallet with instance. +/// # Example of an instantiable pallet /// /// ``` /// pub use pallet::*; @@ -2124,8 +2430,8 @@ pub mod pallet_prelude { /// pub trait Config: frame_system::Config { /// #[pallet::constant] /// type MyGetParam: Get; -/// type Balance: Parameter + From; -/// type Event: From> + IsType<::Event>; +/// type Balance: Parameter + MaxEncodedLen + From; +/// type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// } /// /// #[pallet::extra_constants] @@ -2239,28 +2545,28 @@ pub mod pallet_prelude { /// } /// ``` /// -/// ## Upgrade guidelines: +/// # Upgrade guidelines /// /// 1. Export the metadata of the pallet for later checks /// - run your node with the pallet active /// - query the metadata using the `state_getMetadata` RPC and curl, or use `subsee -p /// > meta.json` -/// 2. generate the template upgrade for the pallet provided by decl_storage -/// with environment variable `PRINT_PALLET_UPGRADE`: -/// `PRINT_PALLET_UPGRADE=1 cargo check -p my_pallet` This template can be -/// used as information it contains all information for storages, genesis -/// config and genesis build. -/// 3. reorganize pallet to have trait `Config`, `decl_*` macros, `ValidateUnsigned`, -/// `ProvideInherent`, `Origin` all together in one file. Suggested order: -/// * Config, -/// * decl_module, -/// * decl_event, -/// * decl_error, -/// * decl_storage, -/// * origin, -/// * validate_unsigned, -/// * provide_inherent, -/// so far it should compile and all be correct. +/// 2. Generate the template upgrade for the pallet provided by `decl_storage` with the +/// environment variable `PRINT_PALLET_UPGRADE`: `PRINT_PALLET_UPGRADE=1 cargo check -p +/// my_pallet`. This template can be used as it contains all information for storages, +/// genesis config and genesis build. +/// 3. Reorganize the pallet to have the trait `Config`, `decl_*` macros, +/// [`ValidateUnsigned`](`pallet_prelude::ValidateUnsigned`), +/// [`ProvideInherent`](`pallet_prelude::ProvideInherent`), and Origin` all together in one +/// file. Suggested order: +/// * `Config`, +/// * `decl_module`, +/// * `decl_event`, +/// * `decl_error`, +/// * `decl_storage`, +/// * `origin`, +/// * `validate_unsigned`, +/// * `provide_inherent`, so far it should compile and all be correct. /// 4. start writing the new pallet module /// ```ignore /// pub use pallet::*; @@ -2280,16 +2586,17 @@ pub mod pallet_prelude { /// } /// ``` /// 5. **migrate Config**: move trait into the module with -/// * all const in decl_module to `#[pallet::constant]` -/// * add bound `IsType<::Event>` to `type Event` +/// * all const in `decl_module` to [`#[pallet::constant]`](#palletconstant) +/// * add the bound `IsType<::RuntimeEvent>` to `type +/// RuntimeEvent` /// 7. **migrate decl_module**: write: /// ```ignore /// #[pallet::hooks] /// impl Hooks for Pallet { /// } /// ``` -/// and write inside -/// `on_initialize`, `on_finalize`, `on_runtime_upgrade`, `offchain_worker`, `integrity_test`. +/// and write inside `on_initialize`, `on_finalize`, `on_runtime_upgrade`, +/// `offchain_worker`, and `integrity_test`. /// /// then write: /// ```ignore @@ -2297,25 +2604,26 @@ pub mod pallet_prelude { /// impl Pallet { /// } /// ``` -/// and write inside all the calls in decl_module with a few changes in the signature: -/// - origin must now be written completely, e.g. `origin: OriginFor` -/// - result type must be `DispatchResultWithPostInfo`, you need to write it and also you -/// might -/// need to put `Ok(().into())` at the end or the function. -/// - `#[compact]` must now be written `#[pallet::compact]` -/// - `#[weight = ..]` must now be written `#[pallet::weight(..)]` -/// -/// 7. **migrate event**: -/// rewrite as a simple enum under with the attribute `#[pallet::event]`, -/// use `#[pallet::generate_deposit($vis fn deposit_event)]` to generate deposit_event, -/// 8. **migrate error**: rewrite it with attribute `#[pallet::error]`. -/// 9. **migrate storage**: -/// decl_storage provide an upgrade template (see 3.). All storages, genesis config, genesis -/// build and default implementation of genesis config can be taken from it directly. -/// -/// Otherwise here is the manual process: -/// -/// first migrate the genesis logic. write: +/// and write inside all the calls in `decl_module` with a few changes in the signature: +/// - origin must now be written completely, e.g. `origin: OriginFor` +/// - result type must be `DispatchResultWithPostInfo`, you need to write it and also you +/// might need to put `Ok(().into())` at the end or the function. +/// - `#[compact]` must now be written +/// [`#[pallet::compact]`](#palletcompact-some_arg-some_type) +/// - `#[weight = ..]` must now be written [`#[pallet::weight(..)]`](#palletweightexpr) +/// +/// 7. **migrate event**: rewrite as a simple enum with the attribute +/// [`#[pallet::event]`](#event-palletevent-optional), use [`#[pallet::generate_deposit($vis +/// fn deposit_event)]`](#event-palletevent-optional) to generate `deposit_event`, +/// 8. **migrate error**: rewrite it with attribute +/// [`#[pallet::error]`](#error-palleterror-optional). +/// 9. **migrate storage**: `decl_storage` provide an upgrade template (see 3.). All storages, +/// genesis config, genesis build and default implementation of genesis config can be +/// taken from it directly. +/// +/// Otherwise here is the manual process: +/// +/// first migrate the genesis logic. write: /// ```ignore /// #[pallet::genesis_config] /// struct GenesisConfig { @@ -2333,79 +2641,85 @@ pub mod pallet_prelude { /// } /// } /// ``` -/// for each storages, if it contains config(..) then add a fields, and make its default to the -/// value in `= ..;` or the type default if none, if it contains no build then also add the -/// logic to build the value. -/// for each storages if it contains build(..) then add the logic to genesis_build. -/// -/// NOTE: in decl_storage: is executed first the individual config and build and at the end the -/// add_extra_genesis build -/// -/// Once this is done you can migrate storage individually, a few notes: -/// - for private storage use `pub(crate) type ` or `pub(super) type` or nothing, -/// - for storage with `get(fn ..)` use `#[pallet::getter(fn ...)]` -/// - for storage with value being `Option<$something>` make generic `Value` being -/// `$something` -/// and generic `QueryKind` being `OptionQuery` (note: this is default). Otherwise make -/// `Value` the complete value type and `QueryKind` being `ValueQuery`. -/// - for storage with default value: `= $expr;` provide some specific OnEmpty generic. To do -/// so -/// use of `#[pallet::type_value]` to generate the wanted struct to put. -/// example: `MyStorage: u32 = 3u32` would be written: -/// ```ignore +/// for each storage, if it contains `config(..)` then add fields, and make it default to +/// the value in `= ..;` or the type default if none, if it contains no build then also add +/// the logic to build the value. for each storage if it contains `build(..)` then add the +/// logic to `genesis_build`. +/// +/// NOTE: within `decl_storage`: the individual config is executed first, followed by the +/// build and finally the `add_extra_genesis` build. +/// +/// Once this is done you can migrate storages individually, a few notes: +/// - for private storage use `pub(crate) type ` or `pub(super) type` or nothing, +/// - for storages with `get(fn ..)` use [`#[pallet::getter(fn +/// ...)]`](#palletgetterfn-my_getter_fn_name-optional) +/// - for storages with value being `Option<$something>` make generic `Value` being +/// `$something` and generic `QueryKind` being `OptionQuery` (note: this is default). +/// Otherwise make `Value` the complete value type and `QueryKind` being `ValueQuery`. +/// - for storages with default value: `= $expr;` provide some specific `OnEmpty` generic. +/// To do so use of `#[pallet::type_value]` to generate the wanted struct to put. +/// example: `MyStorage: u32 = 3u32` would be written: +/// +/// ```ignore /// #[pallet::type_value] fn MyStorageOnEmpty() -> u32 { 3u32 } /// #[pallet::storage] /// pub(super) type MyStorage = StorageValue<_, u32, ValueQuery, MyStorageOnEmpty>; /// ``` /// -/// NOTE: `decl_storage` also generates functions `assimilate_storage` and `build_storage` -/// directly on GenesisConfig, those are sometimes used in tests. In order not to break they -/// can be implemented manually, one can implement those functions by calling `GenesisBuild` -/// implementation. -/// -/// 10. **migrate origin**: move the origin to the pallet module under `#[pallet::origin]` -/// 11. **migrate validate_unsigned**: move the `ValidateUnsigned` implementation to the pallet -/// module under `#[pallet::validate_unsigned]` -/// 12. **migrate provide_inherent**: move the `ProvideInherent` implementation to the pallet -/// module under `#[pallet::inherent]` +/// NOTE: `decl_storage` also generates the functions `assimilate_storage` and +/// `build_storage` directly on `GenesisConfig`, and these are sometimes used in tests. +/// In order not to break they can be implemented manually, one can implement those +/// functions by calling the `GenesisBuild` implementation. +/// 10. **migrate origin**: move the origin to the pallet module to be under a +/// [`#[pallet::origin]`](#origin-palletorigin-optional) attribute +/// 11. **migrate validate_unsigned**: move the +/// [`ValidateUnsigned`](`pallet_prelude::ValidateUnsigned`) implementation to the pallet +/// module under a +/// [`#[pallet::validate_unsigned]`](#validate-unsigned-palletvalidate_unsigned-optional) +/// attribute +/// 12. **migrate provide_inherent**: move the +/// [`ProvideInherent`](`pallet_prelude::ProvideInherent`) implementation to the pallet +/// module under a [`#[pallet::inherent]`](#inherent-palletinherent-optional) attribute /// 13. rename the usage of `Module` to `Pallet` inside the crate. -/// 14. migration is done, now double check migration with the checking migration guidelines. +/// 14. migration is done, now double check the migration with the checking migration +/// guidelines shown below. /// -/// ## Checking upgrade guidelines: +/// # Checking upgrade guidelines: /// /// * compare metadata. Use [subsee](https://github.com/ascjones/subsee) to fetch the metadata -/// and do a diff of the resulting json before and after migration. This checks for: -/// * call, names, signature, docs -/// * event names, docs -/// * error names, docs -/// * storage names, hasher, prefixes, default value -/// * error , error, constant, +/// and do a diff of the resulting json before and after migration. This checks for: +/// * call, names, signature, docs +/// * event names, docs +/// * error names, docs +/// * storage names, hasher, prefixes, default value +/// * error, error, constant /// * manually check that: -/// * `Origin` is moved inside the macro under `#[pallet::origin]` if it exists -/// * `ValidateUnsigned` is moved inside the macro under `#[pallet::validate_unsigned)]` if it -/// exists -/// * `ProvideInherent` is moved inside macro under `#[pallet::inherent)]` if it exists -/// * `on_initialize`/`on_finalize`/`on_runtime_upgrade`/`offchain_worker` are moved to -/// `Hooks` -/// implementation -/// * storages with `config(..)` are converted to `GenesisConfig` field, and their default is -/// `= $expr;` if the storage have default value -/// * storages with `build($expr)` or `config(..)` are built in `GenesisBuild::build` -/// * `add_extra_genesis` fields are converted to `GenesisConfig` field with their correct -/// default if specified -/// * `add_extra_genesis` build is written into `GenesisBuild::build` +/// * `Origin` was moved inside the macro under +/// [`#[pallet::origin]`](#origin-palletorigin-optional) if it exists +/// * [`ValidateUnsigned`](`pallet_prelude::ValidateUnsigned`) was moved inside the macro +/// under +/// [`#[pallet::validate_unsigned)]`](#validate-unsigned-palletvalidate_unsigned-optional) +/// if it exists +/// * [`ProvideInherent`](`pallet_prelude::ProvideInherent`) was moved inside the macro +/// under [`#[pallet::inherent)]`](#inherent-palletinherent-optional) if it exists +/// * `on_initialize` / `on_finalize` / `on_runtime_upgrade` / `offchain_worker` were moved +/// to the `Hooks` implementation +/// * storages with `config(..)` were converted to `GenesisConfig` field, and their default +/// is `= $expr;` if the storage has a default value +/// * storages with `build($expr)` or `config(..)` were built in `GenesisBuild::build` +/// * `add_extra_genesis` fields were converted to `GenesisConfig` field with their correct +/// default if specified +/// * `add_extra_genesis` build was written into `GenesisBuild::build` /// * storage items defined with [`pallet`] use the name of the pallet provided by -/// [`traits::PalletInfo::name`] as `pallet_prefix` (in `decl_storage`, storage items used the -/// `pallet_prefix` given as input of `decl_storage` with the syntax `as Example`). -/// Thus a runtime using the pallet must be careful with this change. -/// To handle this change: -/// * either ensure that the name of the pallet given to `construct_runtime!` is the same -/// as the name the pallet was giving to `decl_storage`, -/// * or do a storage migration from the old prefix used to the new prefix used. -/// -/// NOTE: The prefixes used by storage items are in the metadata. Thus, ensuring the metadata -/// hasn't changed does ensure that the `pallet_prefix`s used by the storage items haven't -/// changed. +/// [`traits::PalletInfo::name`] as `pallet_prefix` (in `decl_storage`, storage items used +/// the `pallet_prefix` given as input of `decl_storage` with the syntax `as Example`). Thus +/// a runtime using the pallet must be careful with this change. To handle this change: +/// * either ensure that the name of the pallet given to `construct_runtime!` is the same +/// as the name the pallet was giving to `decl_storage`, +/// * or do a storage migration from the old prefix used to the new prefix used. +/// +/// NOTE: The prefixes used by storage items are in metadata. Thus, ensuring the metadata +/// hasn't changed ensures that the `pallet_prefix`s used by the storage items haven't changed. /// /// # Notes when macro fails to show proper error message spans: /// @@ -2420,3 +2734,13 @@ pub mod pallet_prelude { /// ``` /// * use the newest nightly possible. pub use frame_support_procedural::pallet; + +/// Contains macro stubs for all of the pallet:: macros +pub mod pallet_macros { + pub use frame_support_procedural::{ + call_index, compact, config, constant, disable_frame_system_supertrait_check, error, event, + extra_constants, generate_deposit, generate_storage_info, generate_store, genesis_build, + genesis_config, getter, hooks, inherent, origin, storage, storage_prefix, storage_version, + type_value, unbounded, validate_unsigned, weight, whitelist_storage, + }; +} diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index dc3402440fdd4..63679fd56d667 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,7 @@ use crate::{ traits::{GetStorageVersion, PalletInfoAccess}, weights::{RuntimeDbWeight, Weight}, }; +use impl_trait_for_tuples::impl_for_tuples; /// Trait used by [`migrate_from_pallet_version_to_storage_version`] to do the actual migration. pub trait PalletVersionToStorageVersionHelper { @@ -42,10 +43,12 @@ impl PalletVersionToStorageVersionHelpe } } -#[impl_trait_for_tuples::impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] impl PalletVersionToStorageVersionHelper for T { fn migrate(db_weight: &RuntimeDbWeight) -> Weight { - let mut weight: Weight = 0; + let mut weight = Weight::zero(); for_tuples!( #( weight = weight.saturating_add(T::migrate(db_weight)); )* ); @@ -58,9 +61,9 @@ impl PalletVersionToStorageVersionHelper for T { /// /// This will remove all `PalletVersion's` from the state and insert the current storage version. pub fn migrate_from_pallet_version_to_storage_version< - AllPallets: PalletVersionToStorageVersionHelper, + Pallets: PalletVersionToStorageVersionHelper, >( db_weight: &RuntimeDbWeight, ) -> Weight { - AllPallets::migrate(db_weight) + Pallets::migrate(db_weight) } diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index d0c0aa7c4f155..d567faa38fdc1 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,309 +17,28 @@ //! Traits, types and structs to support a bounded BTreeMap. -use crate::{storage::StorageDecodeLength, traits::Get}; -use codec::{Decode, Encode, MaxEncodedLen}; -use sp_std::{ - borrow::Borrow, collections::btree_map::BTreeMap, convert::TryFrom, fmt, marker::PhantomData, - ops::Deref, -}; - -/// A bounded map based on a B-Tree. -/// -/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing -/// the amount of work performed in a search. See [`BTreeMap`] for more details. -/// -/// Unlike a standard `BTreeMap`, there is an enforced upper limit to the number of items in the -/// map. All internal operations ensure this bound is respected. -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct BoundedBTreeMap(BTreeMap, PhantomData); - -impl Decode for BoundedBTreeMap -where - K: Decode + Ord, - V: Decode, - S: Get, -{ - fn decode(input: &mut I) -> Result { - let inner = BTreeMap::::decode(input)?; - if inner.len() > S::get() as usize { - return Err("BoundedBTreeMap exceeds its limit".into()) - } - Ok(Self(inner, PhantomData)) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - BTreeMap::::skip(input) - } -} - -impl BoundedBTreeMap -where - S: Get, -{ - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } -} - -impl BoundedBTreeMap -where - K: Ord, - S: Get, -{ - /// Create a new `BoundedBTreeMap`. - /// - /// Does not allocate. - pub fn new() -> Self { - BoundedBTreeMap(BTreeMap::new(), PhantomData) - } - - /// Consume self, and return the inner `BTreeMap`. - /// - /// This is useful when a mutating API of the inner type is desired, and closure-based mutation - /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. - pub fn into_inner(self) -> BTreeMap { - debug_assert!(self.0.len() <= Self::bound()); - self.0 - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut BTreeMap)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - // Clears the map, removing all elements. - pub fn clear(&mut self) { - self.0.clear() - } - - /// Return a mutable reference to the value corresponding to the key. - /// - /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed - /// form _must_ match the ordering on the key type. - pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> - where - K: Borrow, - Q: Ord + ?Sized, - { - self.0.get_mut(key) - } - - /// Exactly the same semantics as [`BTreeMap::insert`], but returns an `Err` (and is a noop) if - /// the new length of the map exceeds `S`. - /// - /// In the `Err` case, returns the inserted pair so it can be further used without cloning. - pub fn try_insert(&mut self, key: K, value: V) -> Result, (K, V)> { - if self.len() < Self::bound() || self.0.contains_key(&key) { - Ok(self.0.insert(key, value)) - } else { - Err((key, value)) - } - } - - /// Remove a key from the map, returning the value at the key if the key was previously in the - /// map. - /// - /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed - /// form _must_ match the ordering on the key type. - pub fn remove(&mut self, key: &Q) -> Option - where - K: Borrow, - Q: Ord + ?Sized, - { - self.0.remove(key) - } - - /// Remove a key from the map, returning the value at the key if the key was previously in the - /// map. - /// - /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed - /// form _must_ match the ordering on the key type. - pub fn remove_entry(&mut self, key: &Q) -> Option<(K, V)> - where - K: Borrow, - Q: Ord + ?Sized, - { - self.0.remove_entry(key) - } -} - -impl Default for BoundedBTreeMap -where - K: Ord, - S: Get, -{ - fn default() -> Self { - Self::new() - } -} - -impl Clone for BoundedBTreeMap -where - BTreeMap: Clone, -{ - fn clone(&self) -> Self { - BoundedBTreeMap(self.0.clone(), PhantomData) - } -} - -#[cfg(feature = "std")] -impl fmt::Debug for BoundedBTreeMap -where - BTreeMap: fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("BoundedBTreeMap").field(&self.0).field(&Self::bound()).finish() - } -} - -impl PartialEq for BoundedBTreeMap -where - BTreeMap: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Eq for BoundedBTreeMap where BTreeMap: Eq {} - -impl PartialEq> for BoundedBTreeMap -where - BTreeMap: PartialEq, -{ - fn eq(&self, other: &BTreeMap) -> bool { - self.0 == *other - } -} - -impl PartialOrd for BoundedBTreeMap -where - BTreeMap: PartialOrd, -{ - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for BoundedBTreeMap -where - BTreeMap: Ord, -{ - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl IntoIterator for BoundedBTreeMap { - type Item = (K, V); - type IntoIter = sp_std::collections::btree_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl MaxEncodedLen for BoundedBTreeMap -where - K: MaxEncodedLen, - V: MaxEncodedLen, - S: Get, -{ - fn max_encoded_len() -> usize { - Self::bound() - .saturating_mul(K::max_encoded_len().saturating_add(V::max_encoded_len())) - .saturating_add(codec::Compact(S::get()).encoded_size()) - } -} - -impl Deref for BoundedBTreeMap -where - K: Ord, -{ - type Target = BTreeMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef> for BoundedBTreeMap -where - K: Ord, -{ - fn as_ref(&self) -> &BTreeMap { - &self.0 - } -} - -impl From> for BTreeMap -where - K: Ord, -{ - fn from(map: BoundedBTreeMap) -> Self { - map.0 - } -} - -impl TryFrom> for BoundedBTreeMap -where - K: Ord, - S: Get, -{ - type Error = (); - - fn try_from(value: BTreeMap) -> Result { - (value.len() <= Self::bound()) - .then(move || BoundedBTreeMap(value, PhantomData)) - .ok_or(()) - } -} - -impl codec::DecodeLength for BoundedBTreeMap { - fn len(self_encoded: &[u8]) -> Result { - // `BoundedBTreeMap` is stored just a `BTreeMap`, which is stored as a - // `Compact` with its length followed by an iteration of its items. We can just use - // the underlying implementation. - as codec::DecodeLength>::len(self_encoded) - } -} +use crate::storage::StorageDecodeLength; +pub use sp_runtime::BoundedBTreeMap; impl StorageDecodeLength for BoundedBTreeMap {} -impl codec::EncodeLike> for BoundedBTreeMap where - BTreeMap: Encode -{ -} - #[cfg(test)] pub mod test { use super::*; use crate::Twox128; + use frame_support::traits::{ConstU32, Get}; use sp_io::TestExternalities; - use sp_std::convert::TryInto; + use sp_std::collections::btree_map::BTreeMap; - crate::parameter_types! { - pub const Seven: u32 = 7; - pub const Four: u32 = 4; - } + #[crate::storage_alias] + type Foo = StorageValue>>; - crate::generate_storage_alias! { Prefix, Foo => Value> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), BoundedBTreeMap> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedBTreeMap> - } + #[crate::storage_alias] + type FooMap = StorageMap>>; + + #[crate::storage_alias] + type FooDoubleMap = + StorageDoubleMap>>; fn map_from_keys(keys: &[K]) -> BTreeMap where @@ -339,13 +58,13 @@ pub mod test { #[test] fn decode_len_works() { TestExternalities::default().execute_with(|| { - let bounded = boundedmap_from_keys::(&[1, 2, 3]); + let bounded = boundedmap_from_keys::>(&[1, 2, 3]); Foo::put(bounded); assert_eq!(Foo::decode_len().unwrap(), 3); }); TestExternalities::default().execute_with(|| { - let bounded = boundedmap_from_keys::(&[1, 2, 3]); + let bounded = boundedmap_from_keys::>(&[1, 2, 3]); FooMap::insert(1, bounded); assert_eq!(FooMap::decode_len(1).unwrap(), 3); assert!(FooMap::decode_len(0).is_none()); @@ -353,7 +72,7 @@ pub mod test { }); TestExternalities::default().execute_with(|| { - let bounded = boundedmap_from_keys::(&[1, 2, 3]); + let bounded = boundedmap_from_keys::>(&[1, 2, 3]); FooDoubleMap::insert(1, 1, bounded); assert_eq!(FooDoubleMap::decode_len(1, 1).unwrap(), 3); assert!(FooDoubleMap::decode_len(2, 1).is_none()); @@ -361,100 +80,4 @@ pub mod test { assert!(FooDoubleMap::decode_len(2, 2).is_none()); }); } - - #[test] - fn try_insert_works() { - let mut bounded = boundedmap_from_keys::(&[1, 2, 3]); - bounded.try_insert(0, ()).unwrap(); - assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); - - assert!(bounded.try_insert(9, ()).is_err()); - assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); - } - - #[test] - fn deref_coercion_works() { - let bounded = boundedmap_from_keys::(&[1, 2, 3]); - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded = boundedmap_from_keys::(&[1, 2, 3, 4, 5, 6]); - let bounded = bounded - .try_mutate(|v| { - v.insert(7, ()); - }) - .unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded - .try_mutate(|v| { - v.insert(8, ()); - }) - .is_none()); - } - - #[test] - fn btree_map_eq_works() { - let bounded = boundedmap_from_keys::(&[1, 2, 3, 4, 5, 6]); - assert_eq!(bounded, map_from_keys(&[1, 2, 3, 4, 5, 6])); - } - - #[test] - fn too_big_fail_to_decode() { - let v: Vec<(u32, u32)> = vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]; - assert_eq!( - BoundedBTreeMap::::decode(&mut &v.encode()[..]), - Err("BoundedBTreeMap exceeds its limit".into()), - ); - } - - #[test] - fn unequal_eq_impl_insert_works() { - // given a struct with a strange notion of equality - #[derive(Debug)] - struct Unequal(u32, bool); - - impl PartialEq for Unequal { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - impl Eq for Unequal {} - - impl Ord for Unequal { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } - } - - impl PartialOrd for Unequal { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - let mut map = BoundedBTreeMap::::new(); - - // when the set is full - - for i in 0..4 { - map.try_insert(Unequal(i, false), i).unwrap(); - } - - // can't insert a new distinct member - map.try_insert(Unequal(5, false), 5).unwrap_err(); - - // but _can_ insert a distinct member which compares equal, though per the documentation, - // neither the set length nor the actual member are changed, but the value is - map.try_insert(Unequal(0, true), 6).unwrap(); - assert_eq!(map.len(), 4); - let (zero_key, zero_value) = map.get_key_value(&Unequal(0, true)).unwrap(); - assert_eq!(zero_key.0, 0); - assert_eq!(zero_key.1, false); - assert_eq!(*zero_value, 6); - } } diff --git a/frame/support/src/storage/bounded_btree_set.rs b/frame/support/src/storage/bounded_btree_set.rs index 182884e655dd2..9ed129e67c46c 100644 --- a/frame/support/src/storage/bounded_btree_set.rs +++ b/frame/support/src/storage/bounded_btree_set.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,315 +17,54 @@ //! Traits, types and structs to support a bounded `BTreeSet`. -use crate::{storage::StorageDecodeLength, traits::Get}; -use codec::{Decode, Encode, MaxEncodedLen}; -use sp_std::{ - borrow::Borrow, collections::btree_set::BTreeSet, convert::TryFrom, fmt, marker::PhantomData, - ops::Deref, -}; - -/// A bounded set based on a B-Tree. -/// -/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing -/// the amount of work performed in a search. See [`BTreeSet`] for more details. -/// -/// Unlike a standard `BTreeSet`, there is an enforced upper limit to the number of items in the -/// set. All internal operations ensure this bound is respected. -#[derive(Encode)] -pub struct BoundedBTreeSet(BTreeSet, PhantomData); - -impl Decode for BoundedBTreeSet -where - T: Decode + Ord, - S: Get, -{ - fn decode(input: &mut I) -> Result { - let inner = BTreeSet::::decode(input)?; - if inner.len() > S::get() as usize { - return Err("BoundedBTreeSet exceeds its limit".into()) - } - Ok(Self(inner, PhantomData)) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - BTreeSet::::skip(input) - } -} - -impl BoundedBTreeSet -where - S: Get, -{ - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } -} - -impl BoundedBTreeSet -where - T: Ord, - S: Get, -{ - /// Create a new `BoundedBTreeSet`. - /// - /// Does not allocate. - pub fn new() -> Self { - BoundedBTreeSet(BTreeSet::new(), PhantomData) - } - - /// Consume self, and return the inner `BTreeSet`. - /// - /// This is useful when a mutating API of the inner type is desired, and closure-based mutation - /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. - pub fn into_inner(self) -> BTreeSet { - debug_assert!(self.0.len() <= Self::bound()); - self.0 - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut BTreeSet)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - // Clears the set, removing all elements. - pub fn clear(&mut self) { - self.0.clear() - } - - /// Exactly the same semantics as [`BTreeSet::insert`], but returns an `Err` (and is a noop) if - /// the new length of the set exceeds `S`. - /// - /// In the `Err` case, returns the inserted item so it can be further used without cloning. - pub fn try_insert(&mut self, item: T) -> Result { - if self.len() < Self::bound() || self.0.contains(&item) { - Ok(self.0.insert(item)) - } else { - Err(item) - } - } - - /// Remove an item from the set, returning whether it was previously in the set. - /// - /// The item may be any borrowed form of the set's item type, but the ordering on the borrowed - /// form _must_ match the ordering on the item type. - pub fn remove(&mut self, item: &Q) -> bool - where - T: Borrow, - Q: Ord + ?Sized, - { - self.0.remove(item) - } - - /// Removes and returns the value in the set, if any, that is equal to the given one. - /// - /// The value may be any borrowed form of the set's value type, but the ordering on the borrowed - /// form _must_ match the ordering on the value type. - pub fn take(&mut self, value: &Q) -> Option - where - T: Borrow + Ord, - Q: Ord + ?Sized, - { - self.0.take(value) - } -} - -impl Default for BoundedBTreeSet -where - T: Ord, - S: Get, -{ - fn default() -> Self { - Self::new() - } -} - -impl Clone for BoundedBTreeSet -where - BTreeSet: Clone, -{ - fn clone(&self) -> Self { - BoundedBTreeSet(self.0.clone(), PhantomData) - } -} - -#[cfg(feature = "std")] -impl fmt::Debug for BoundedBTreeSet -where - BTreeSet: fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("BoundedBTreeSet").field(&self.0).field(&Self::bound()).finish() - } -} - -impl PartialEq for BoundedBTreeSet -where - BTreeSet: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Eq for BoundedBTreeSet where BTreeSet: Eq {} - -impl PartialEq> for BoundedBTreeSet -where - BTreeSet: PartialEq, -{ - fn eq(&self, other: &BTreeSet) -> bool { - self.0 == *other - } -} - -impl PartialOrd for BoundedBTreeSet -where - BTreeSet: PartialOrd, -{ - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for BoundedBTreeSet -where - BTreeSet: Ord, -{ - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl IntoIterator for BoundedBTreeSet { - type Item = T; - type IntoIter = sp_std::collections::btree_set::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl MaxEncodedLen for BoundedBTreeSet -where - T: MaxEncodedLen, - S: Get, -{ - fn max_encoded_len() -> usize { - Self::bound() - .saturating_mul(T::max_encoded_len()) - .saturating_add(codec::Compact(S::get()).encoded_size()) - } -} - -impl Deref for BoundedBTreeSet -where - T: Ord, -{ - type Target = BTreeSet; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef> for BoundedBTreeSet -where - T: Ord, -{ - fn as_ref(&self) -> &BTreeSet { - &self.0 - } -} - -impl From> for BTreeSet -where - T: Ord, -{ - fn from(set: BoundedBTreeSet) -> Self { - set.0 - } -} - -impl TryFrom> for BoundedBTreeSet -where - T: Ord, - S: Get, -{ - type Error = (); - - fn try_from(value: BTreeSet) -> Result { - (value.len() <= Self::bound()) - .then(move || BoundedBTreeSet(value, PhantomData)) - .ok_or(()) - } -} - -impl codec::DecodeLength for BoundedBTreeSet { - fn len(self_encoded: &[u8]) -> Result { - // `BoundedBTreeSet` is stored just a `BTreeSet`, which is stored as a - // `Compact` with its length followed by an iteration of its items. We can just use - // the underlying implementation. - as codec::DecodeLength>::len(self_encoded) - } -} +use crate::storage::StorageDecodeLength; +pub use sp_runtime::BoundedBTreeSet; impl StorageDecodeLength for BoundedBTreeSet {} -impl codec::EncodeLike> for BoundedBTreeSet where BTreeSet: Encode {} - #[cfg(test)] pub mod test { use super::*; use crate::Twox128; + use frame_support::traits::{ConstU32, Get}; use sp_io::TestExternalities; - use sp_std::convert::TryInto; + use sp_std::collections::btree_set::BTreeSet; - crate::parameter_types! { - pub const Seven: u32 = 7; - pub const Four: u32 = 4; - } + #[crate::storage_alias] + type Foo = StorageValue>>; - crate::generate_storage_alias! { Prefix, Foo => Value> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), BoundedBTreeSet> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedBTreeSet> - } + #[crate::storage_alias] + type FooMap = StorageMap>>; + + #[crate::storage_alias] + type FooDoubleMap = + StorageDoubleMap>>; - fn map_from_keys(keys: &[T]) -> BTreeSet + fn set_from_keys(keys: &[T]) -> BTreeSet where T: Ord + Copy, { keys.iter().copied().collect() } - fn boundedmap_from_keys(keys: &[T]) -> BoundedBTreeSet + fn boundedset_from_keys(keys: &[T]) -> BoundedBTreeSet where T: Ord + Copy, S: Get, { - map_from_keys(keys).try_into().unwrap() + set_from_keys(keys).try_into().unwrap() } #[test] fn decode_len_works() { TestExternalities::default().execute_with(|| { - let bounded = boundedmap_from_keys::(&[1, 2, 3]); + let bounded = boundedset_from_keys::>(&[1, 2, 3]); Foo::put(bounded); assert_eq!(Foo::decode_len().unwrap(), 3); }); TestExternalities::default().execute_with(|| { - let bounded = boundedmap_from_keys::(&[1, 2, 3]); + let bounded = boundedset_from_keys::>(&[1, 2, 3]); FooMap::insert(1, bounded); assert_eq!(FooMap::decode_len(1).unwrap(), 3); assert!(FooMap::decode_len(0).is_none()); @@ -333,7 +72,7 @@ pub mod test { }); TestExternalities::default().execute_with(|| { - let bounded = boundedmap_from_keys::(&[1, 2, 3]); + let bounded = boundedset_from_keys::>(&[1, 2, 3]); FooDoubleMap::insert(1, 1, bounded); assert_eq!(FooDoubleMap::decode_len(1, 1).unwrap(), 3); assert!(FooDoubleMap::decode_len(2, 1).is_none()); @@ -341,99 +80,4 @@ pub mod test { assert!(FooDoubleMap::decode_len(2, 2).is_none()); }); } - - #[test] - fn try_insert_works() { - let mut bounded = boundedmap_from_keys::(&[1, 2, 3]); - bounded.try_insert(0).unwrap(); - assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); - - assert!(bounded.try_insert(9).is_err()); - assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); - } - - #[test] - fn deref_coercion_works() { - let bounded = boundedmap_from_keys::(&[1, 2, 3]); - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded = boundedmap_from_keys::(&[1, 2, 3, 4, 5, 6]); - let bounded = bounded - .try_mutate(|v| { - v.insert(7); - }) - .unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded - .try_mutate(|v| { - v.insert(8); - }) - .is_none()); - } - - #[test] - fn btree_map_eq_works() { - let bounded = boundedmap_from_keys::(&[1, 2, 3, 4, 5, 6]); - assert_eq!(bounded, map_from_keys(&[1, 2, 3, 4, 5, 6])); - } - - #[test] - fn too_big_fail_to_decode() { - let v: Vec = vec![1, 2, 3, 4, 5]; - assert_eq!( - BoundedBTreeSet::::decode(&mut &v.encode()[..]), - Err("BoundedBTreeSet exceeds its limit".into()), - ); - } - - #[test] - fn unequal_eq_impl_insert_works() { - // given a struct with a strange notion of equality - #[derive(Debug)] - struct Unequal(u32, bool); - - impl PartialEq for Unequal { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - impl Eq for Unequal {} - - impl Ord for Unequal { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } - } - - impl PartialOrd for Unequal { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - let mut set = BoundedBTreeSet::::new(); - - // when the set is full - - for i in 0..4 { - set.try_insert(Unequal(i, false)).unwrap(); - } - - // can't insert a new distinct member - set.try_insert(Unequal(5, false)).unwrap_err(); - - // but _can_ insert a distinct member which compares equal, though per the documentation, - // neither the set length nor the actual member are changed - set.try_insert(Unequal(0, true)).unwrap(); - assert_eq!(set.len(), 4); - let zero_item = set.get(&Unequal(0, true)).unwrap(); - assert_eq!(zero_item.0, 0); - assert_eq!(zero_item.1, false); - } } diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index b45c294f8d4a4..1fa01b44ae6c4 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,304 +21,8 @@ use crate::{ storage::{StorageDecodeLength, StorageTryAppend}, traits::Get, - WeakBoundedVec, }; -use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -use core::{ - ops::{Deref, Index, IndexMut}, - slice::SliceIndex, -}; -use sp_std::{convert::TryFrom, fmt, marker::PhantomData, prelude::*}; - -/// A bounded vector. -/// -/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once -/// put into storage as a raw value, map or double-map. -/// -/// As the name suggests, the length of the queue is always bounded. All internal operations ensure -/// this bound is respected. -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct BoundedVec(Vec, PhantomData); - -/// A bounded slice. -/// -/// Similar to a `BoundedVec`, but not owned and cannot be decoded. -#[derive(Encode)] -pub struct BoundedSlice<'a, T, S>(&'a [T], PhantomData); - -// `BoundedSlice`s encode to something which will always decode into a `BoundedVec`, -// `WeakBoundedVec`, or a `Vec`. -impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} -impl<'a, T: Encode + Decode, S: Get> EncodeLike> - for BoundedSlice<'a, T, S> -{ -} -impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} - -impl<'a, T, S: Get> TryFrom<&'a [T]> for BoundedSlice<'a, T, S> { - type Error = (); - fn try_from(t: &'a [T]) -> Result { - if t.len() < S::get() as usize { - Ok(BoundedSlice(t, PhantomData)) - } else { - Err(()) - } - } -} - -impl<'a, T, S> From> for &'a [T] { - fn from(t: BoundedSlice<'a, T, S>) -> Self { - t.0 - } -} - -impl> Decode for BoundedVec { - fn decode(input: &mut I) -> Result { - let inner = Vec::::decode(input)?; - if inner.len() > S::get() as usize { - return Err("BoundedVec exceeds its limit".into()) - } - Ok(Self(inner, PhantomData)) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - Vec::::skip(input) - } -} - -// `BoundedVec`s encode to something which will always decode as a `Vec`. -impl> EncodeLike> for BoundedVec {} - -impl BoundedVec { - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: Vec) -> Self { - Self(t, Default::default()) - } - - /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an - /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can - /// be used. - /// - /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which - /// is not provided by the wrapper `BoundedVec`. - pub fn into_inner(self) -> Vec { - self.0 - } - - /// Exactly the same semantics as [`Vec::remove`]. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { - self.0.remove(index) - } - - /// Exactly the same semantics as [`Vec::swap_remove`]. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn swap_remove(&mut self, index: usize) -> T { - self.0.swap_remove(index) - } - - /// Exactly the same semantics as [`Vec::retain`]. - pub fn retain bool>(&mut self, f: F) { - self.0.retain(f) - } - - /// Exactly the same semantics as [`Vec::get_mut`]. - pub fn get_mut>( - &mut self, - index: I, - ) -> Option<&mut >::Output> { - self.0.get_mut(index) - } -} - -impl> From> for Vec { - fn from(x: BoundedVec) -> Vec { - x.0 - } -} - -impl> BoundedVec { - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if `index > len`. - pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), ()> { - if self.len() < Self::bound() { - self.0.insert(index, element); - Ok(()) - } else { - Err(()) - } - } - - /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if the new capacity exceeds isize::MAX bytes. - pub fn try_push(&mut self, element: T) -> Result<(), ()> { - if self.len() < Self::bound() { - self.0.push(element); - Ok(()) - } else { - Err(()) - } - } -} - -impl Default for BoundedVec { - fn default() -> Self { - // the bound cannot be below 0, which is satisfied by an empty vector - Self::unchecked_from(Vec::default()) - } -} - -#[cfg(feature = "std")] -impl fmt::Debug for BoundedVec -where - T: fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("BoundedVec").field(&self.0).field(&Self::bound()).finish() - } -} - -impl Clone for BoundedVec -where - T: Clone, -{ - fn clone(&self) -> Self { - // bound is retained - Self::unchecked_from(self.0.clone()) - } -} - -impl> TryFrom> for BoundedVec { - type Error = (); - fn try_from(t: Vec) -> Result { - if t.len() <= Self::bound() { - // explicit check just above - Ok(Self::unchecked_from(t)) - } else { - Err(()) - } - } -} - -// It is okay to give a non-mutable reference of the inner vec to anyone. -impl AsRef> for BoundedVec { - fn as_ref(&self) -> &Vec { - &self.0 - } -} - -impl AsRef<[T]> for BoundedVec { - fn as_ref(&self) -> &[T] { - &self.0 - } -} - -impl AsMut<[T]> for BoundedVec { - fn as_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} - -// will allow for immutable all operations of `Vec` on `BoundedVec`. -impl Deref for BoundedVec { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. -impl Index for BoundedVec -where - I: SliceIndex<[T]>, -{ - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - self.0.index(index) - } -} - -impl IndexMut for BoundedVec -where - I: SliceIndex<[T]>, -{ - #[inline] - fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.0.index_mut(index) - } -} - -impl sp_std::iter::IntoIterator for BoundedVec { - type Item = T; - type IntoIter = sp_std::vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl codec::DecodeLength for BoundedVec { - fn len(self_encoded: &[u8]) -> Result { - // `BoundedVec` stored just a `Vec`, thus the length is at the beginning in - // `Compact` form, and same implementation as `Vec` can be used. - as codec::DecodeLength>::len(self_encoded) - } -} - -// NOTE: we could also implement this as: -// impl, S2: Get> PartialEq> for BoundedVec -// to allow comparison of bounded vectors with different bounds. -impl PartialEq for BoundedVec -where - T: PartialEq, -{ - fn eq(&self, rhs: &Self) -> bool { - self.0 == rhs.0 - } -} - -impl> PartialEq> for BoundedVec { - fn eq(&self, other: &Vec) -> bool { - &self.0 == other - } -} - -impl Eq for BoundedVec where T: Eq {} +pub use sp_runtime::{BoundedSlice, BoundedVec}; impl StorageDecodeLength for BoundedVec {} @@ -328,56 +32,32 @@ impl> StorageTryAppend for BoundedVec { } } -impl MaxEncodedLen for BoundedVec -where - T: MaxEncodedLen, - S: Get, - BoundedVec: Encode, -{ - fn max_encoded_len() -> usize { - // BoundedVec encodes like Vec which encodes like [T], which is a compact u32 - // plus each item in the slice: - // https://substrate.dev/rustdocs/v3.0.0/src/parity_scale_codec/codec.rs.html#798-808 - codec::Compact(S::get()) - .encoded_size() - .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) - } -} - #[cfg(test)] pub mod test { use super::*; - use crate::Twox128; + use crate::{bounded_vec, traits::ConstU32, Twox128}; use sp_io::TestExternalities; - use sp_std::convert::TryInto; - crate::parameter_types! { - pub const Seven: u32 = 7; - pub const Four: u32 = 4; - } + #[crate::storage_alias] + type Foo = StorageValue>>; - crate::generate_storage_alias! { Prefix, Foo => Value> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), BoundedVec> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedVec> - } + #[crate::storage_alias] + type FooMap = StorageMap>>; - #[test] - fn try_append_is_correct() { - assert_eq!(BoundedVec::::bound(), 7); - } + #[crate::storage_alias] + type FooDoubleMap = + StorageDoubleMap>>; #[test] fn decode_len_works() { TestExternalities::default().execute_with(|| { - let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + let bounded: BoundedVec> = bounded_vec![1, 2, 3]; Foo::put(bounded); assert_eq!(Foo::decode_len().unwrap(), 3); }); TestExternalities::default().execute_with(|| { - let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + let bounded: BoundedVec> = bounded_vec![1, 2, 3]; FooMap::insert(1, bounded); assert_eq!(FooMap::decode_len(1).unwrap(), 3); assert!(FooMap::decode_len(0).is_none()); @@ -385,7 +65,7 @@ pub mod test { }); TestExternalities::default().execute_with(|| { - let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + let bounded: BoundedVec> = bounded_vec![1, 2, 3]; FooDoubleMap::insert(1, 1, bounded); assert_eq!(FooDoubleMap::decode_len(1, 1).unwrap(), 3); assert!(FooDoubleMap::decode_len(2, 1).is_none()); @@ -393,68 +73,4 @@ pub mod test { assert!(FooDoubleMap::decode_len(2, 2).is_none()); }); } - - #[test] - fn try_insert_works() { - let mut bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); - bounded.try_insert(1, 0).unwrap(); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - - assert!(bounded.try_insert(0, 9).is_err()); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - } - - #[test] - #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] - fn try_inert_panics_if_oob() { - let mut bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); - bounded.try_insert(9, 0).unwrap(); - } - - #[test] - fn try_push_works() { - let mut bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); - bounded.try_push(0).unwrap(); - assert_eq!(*bounded, vec![1, 2, 3, 0]); - - assert!(bounded.try_push(9).is_err()); - } - - #[test] - fn deref_coercion_works() { - let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded: BoundedVec = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded.try_mutate(|v| v.push(8)).is_none()); - } - - #[test] - fn slice_indexing_works() { - let bounded: BoundedVec = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - assert_eq!(&bounded[0..=2], &[1, 2, 3]); - } - - #[test] - fn vec_eq_works() { - let bounded: BoundedVec = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); - } - - #[test] - fn too_big_vec_fail_to_decode() { - let v: Vec = vec![1, 2, 3, 4, 5]; - assert_eq!( - BoundedVec::::decode(&mut &v.encode()[..]), - Err("BoundedVec exceeds its limit".into()), - ); - } } diff --git a/frame/support/src/storage/child.rs b/frame/support/src/storage/child.rs index 4b237aaa561fd..397daaa82a677 100644 --- a/frame/support/src/storage/child.rs +++ b/frame/support/src/storage/child.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +21,10 @@ // NOTE: could replace unhashed by having only one kind of storage (top trie being the child info // of null length parent storage key). -pub use crate::sp_io::KillStorageResult; +pub use crate::sp_io::{KillStorageResult, MultiRemovalResults}; use crate::sp_std::prelude::*; use codec::{Codec, Decode, Encode}; -pub use sp_core::storage::{ChildInfo, ChildType}; +pub use sp_core::storage::{ChildInfo, ChildType, StateVersion}; /// Return the value of the item in storage under `key`, or `None` if there is no explicit entry. pub fn get(child_info: &ChildInfo, key: &[u8]) -> Option { @@ -34,8 +34,9 @@ pub fn get(child_info: &ChildInfo, key: &[u8]) -> Option { sp_io::default_child_storage::get(storage_key, key).and_then(|v| { Decode::decode(&mut &v[..]).map(Some).unwrap_or_else(|_| { // TODO #3700: error should be handleable. - crate::runtime_print!( - "ERROR: Corrupted state in child trie at {:?}/{:?}", + log::error!( + target: "runtime::storage", + "Corrupted state in child trie at {:?}/{:?}", storage_key, key, ); @@ -49,7 +50,7 @@ pub fn get(child_info: &ChildInfo, key: &[u8]) -> Option { /// Return the value of the item in storage under `key`, or the type's default if there is no /// explicit entry. pub fn get_or_default(child_info: &ChildInfo, key: &[u8]) -> T { - get(child_info, key).unwrap_or_else(Default::default) + get(child_info, key).unwrap_or_default() } /// Return the value of the item in storage under `key`, or `default_value` if there is no @@ -89,7 +90,7 @@ pub fn take(child_info: &ChildInfo, key: &[u8]) -> Option /// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage, /// the default for its type. pub fn take_or_default(child_info: &ChildInfo, key: &[u8]) -> T { - take(child_info, key).unwrap_or_else(Default::default) + take(child_info, key).unwrap_or_default() } /// Return the value of the item in storage under `key`, or `default_value` if there is no @@ -112,8 +113,7 @@ pub fn take_or_else T>( pub fn exists(child_info: &ChildInfo, key: &[u8]) -> bool { match child_info.child_type() { ChildType::ParentKeyId => - sp_io::default_child_storage::read(child_info.storage_key(), key, &mut [0; 0][..], 0) - .is_some(), + sp_io::default_child_storage::exists(child_info.storage_key(), key), } } @@ -136,6 +136,7 @@ pub fn exists(child_info: &ChildInfo, key: &[u8]) -> bool { /// not make much sense because it is not cumulative when called inside the same block. /// Use this function to distribute the deletion of a single child trie across multiple /// blocks. +#[deprecated = "Use `clear_storage` instead"] pub fn kill_storage(child_info: &ChildInfo, limit: Option) -> KillStorageResult { match child_info.child_type() { ChildType::ParentKeyId => @@ -143,6 +144,58 @@ pub fn kill_storage(child_info: &ChildInfo, limit: Option) -> KillStorageRe } } +/// Partially clear the child storage of each key-value pair. +/// +/// # Limit +/// +/// A *limit* should always be provided through `maybe_limit`. This is one fewer than the +/// maximum number of backend iterations which may be done by this operation and as such +/// represents the maximum number of backend deletions which may happen. A *limit* of zero +/// implies that no keys will be deleted, though there may be a single iteration done. +/// +/// The limit can be used to partially delete storage items in case it is too large or costly +/// to delete all in a single operation. +/// +/// # Cursor +/// +/// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be +/// passed once (in the initial call) for any attempt to clear storage. In general, subsequent calls +/// operating on the same prefix should pass `Some` and this value should be equal to the +/// previous call result's `maybe_cursor` field. The only exception to this is when you can +/// guarantee that the subsequent call is in a new block; in this case the previous call's result +/// cursor need not be passed in an a `None` may be passed instead. This exception may be useful +/// then making this call solely from a block-hook such as `on_initialize`. +/// +/// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once the +/// resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. +/// +/// NOTE: After the initial call for any given child storage, it is important that no keys further +/// keys are inserted. If so, then they may or may not be deleted by subsequent calls. +/// +/// # Note +/// +/// Please note that keys which are residing in the overlay for the child are deleted without +/// counting towards the `limit`. +pub fn clear_storage( + child_info: &ChildInfo, + maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, +) -> MultiRemovalResults { + // TODO: Once the network has upgraded to include the new host functions, this code can be + // enabled. + // sp_io::default_child_storage::storage_kill(prefix, maybe_limit, maybe_cursor) + let r = match child_info.child_type() { + ChildType::ParentKeyId => + sp_io::default_child_storage::storage_kill(child_info.storage_key(), maybe_limit), + }; + use sp_io::KillStorageResult::*; + let (maybe_cursor, backend) = match r { + AllRemoved(db) => (None, db), + SomeRemaining(db) => (Some(child_info.storage_key().to_vec()), db), + }; + MultiRemovalResults { maybe_cursor, backend, unique: backend, loops: backend } +} + /// Ensure `key` has no explicit entry in storage. pub fn kill(child_info: &ChildInfo, key: &[u8]) { match child_info.child_type() { @@ -168,9 +221,10 @@ pub fn put_raw(child_info: &ChildInfo, key: &[u8], value: &[u8]) { } /// Calculate current child root value. -pub fn root(child_info: &ChildInfo) -> Vec { +pub fn root(child_info: &ChildInfo, version: StateVersion) -> Vec { match child_info.child_type() { - ChildType::ParentKeyId => sp_io::default_child_storage::root(child_info.storage_key()), + ChildType::ParentKeyId => + sp_io::default_child_storage::root(child_info.storage_key(), version), } } diff --git a/frame/support/src/storage/generator/double_map.rs b/frame/support/src/storage/generator/double_map.rs index 636a10feb1ab3..c95dcee9d7e5c 100644 --- a/frame/support/src/storage/generator/double_map.rs +++ b/frame/support/src/storage/generator/double_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -151,6 +151,13 @@ where unhashed::get(&Self::storage_double_map_final_key(k1, k2)).ok_or(()) } + fn set, KArg2: EncodeLike>(k1: KArg1, k2: KArg2, q: Self::Query) { + match G::from_query_to_optional_value(q) { + Some(v) => Self::insert(k1, k2, v), + None => Self::remove(k1, k2), + } + } + fn take(k1: KArg1, k2: KArg2) -> Self::Query where KArg1: EncodeLike, @@ -202,11 +209,28 @@ where unhashed::kill(&Self::storage_double_map_final_key(k1, k2)) } - fn remove_prefix(k1: KArg1, limit: Option) -> sp_io::KillStorageResult + fn remove_prefix(k1: KArg1, maybe_limit: Option) -> sp_io::KillStorageResult where KArg1: EncodeLike, { - unhashed::kill_prefix(Self::storage_double_map_final_key1(k1).as_ref(), limit) + unhashed::clear_prefix(Self::storage_double_map_final_key1(k1).as_ref(), maybe_limit, None) + .into() + } + + fn clear_prefix( + k1: KArg1, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + KArg1: EncodeLike, + { + unhashed::clear_prefix( + Self::storage_double_map_final_key1(k1).as_ref(), + Some(limit), + maybe_cursor, + ) + .into() } fn iter_prefix_values(k1: KArg1) -> storage::PrefixIterator @@ -461,7 +485,7 @@ where }, }; - let mut key2_material = G::Hasher2::reverse(&key_material); + let mut key2_material = G::Hasher2::reverse(key_material); let key2 = match K2::decode(&mut key2_material) { Ok(key2) => key2, Err(_) => { @@ -488,14 +512,14 @@ mod test_iterators { use codec::{Decode, Encode}; pub trait Config: 'static { - type Origin; + type RuntimeOrigin; type BlockNumber; type PalletInfo: crate::traits::PalletInfo; type DbWeight: crate::traits::Get; } crate::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=self {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} } #[derive(PartialEq, Eq, Clone, Encode, Decode)] @@ -525,10 +549,8 @@ mod test_iterators { fn double_map_iter_from() { sp_io::TestExternalities::default().execute_with(|| { use crate::hash::Identity; - crate::generate_storage_alias!( - MyModule, - MyDoubleMap => DoubleMap<(u64, Identity), (u64, Identity), u64> - ); + #[crate::storage_alias] + type MyDoubleMap = StorageDoubleMap; MyDoubleMap::insert(1, 10, 100); MyDoubleMap::insert(1, 21, 201); diff --git a/frame/support/src/storage/generator/map.rs b/frame/support/src/storage/generator/map.rs index 1a4225173c4ae..f6c8eaa270bb3 100644 --- a/frame/support/src/storage/generator/map.rs +++ b/frame/support/src/storage/generator/map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -245,6 +245,13 @@ impl> storage::StorageMap unhashed::get(Self::storage_map_final_key(key).as_ref()).ok_or(()) } + fn set>(key: KeyArg, q: Self::Query) { + match G::from_query_to_optional_value(q) { + Some(v) => Self::insert(key, v), + None => Self::remove(key), + } + } + fn insert, ValArg: EncodeLike>(key: KeyArg, val: ValArg) { unhashed::put(Self::storage_map_final_key(key).as_ref(), &val) } @@ -347,14 +354,14 @@ mod test_iterators { use codec::{Decode, Encode}; pub trait Config: 'static { - type Origin; + type RuntimeOrigin; type BlockNumber; type PalletInfo: crate::traits::PalletInfo; type DbWeight: crate::traits::Get; } crate::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=self {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} } #[derive(PartialEq, Eq, Clone, Encode, Decode)] @@ -384,7 +391,8 @@ mod test_iterators { fn map_iter_from() { sp_io::TestExternalities::default().execute_with(|| { use crate::hash::Identity; - crate::generate_storage_alias!(MyModule, MyMap => Map<(u64, Identity), u64>); + #[crate::storage_alias] + type MyMap = StorageMap; MyMap::insert(1, 10); MyMap::insert(2, 20); diff --git a/frame/support/src/storage/generator/mod.rs b/frame/support/src/storage/generator/mod.rs index 576bada2e262c..334e9b9e24e86 100644 --- a/frame/support/src/storage/generator/mod.rs +++ b/frame/support/src/storage/generator/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,21 +47,21 @@ mod tests { struct Runtime; pub trait Config: 'static { - type Origin; + type RuntimeOrigin; type BlockNumber; type PalletInfo: crate::traits::PalletInfo; type DbWeight: crate::traits::Get; } impl Config for Runtime { - type Origin = u32; + type RuntimeOrigin = u32; type BlockNumber = u32; type PalletInfo = crate::tests::PanicPalletInfo; type DbWeight = (); } decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=self {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} } crate::decl_storage! { diff --git a/frame/support/src/storage/generator/nmap.rs b/frame/support/src/storage/generator/nmap.rs index 4845673d3d8c2..79f3d72044e28 100755 --- a/frame/support/src/storage/generator/nmap.rs +++ b/frame/support/src/storage/generator/nmap.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -138,6 +138,13 @@ where unhashed::get(&Self::storage_n_map_final_key::(key)).ok_or(()) } + fn set + TupleToEncodedIter>(key: KArg, q: Self::Query) { + match G::from_query_to_optional_value(q) { + Some(v) => Self::insert(key, v), + None => Self::remove(key), + } + } + fn take + TupleToEncodedIter>(key: KArg) -> Self::Query { let final_key = Self::storage_n_map_final_key::(key); @@ -183,7 +190,22 @@ where where K: HasKeyPrefix, { - unhashed::kill_prefix(&Self::storage_n_map_partial_key(partial_key), limit) + unhashed::clear_prefix(&Self::storage_n_map_partial_key(partial_key), limit, None).into() + } + + fn clear_prefix( + partial_key: KP, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + K: HasKeyPrefix, + { + unhashed::clear_prefix( + &Self::storage_n_map_partial_key(partial_key), + Some(limit), + maybe_cursor, + ) } fn iter_prefix_values(partial_key: KP) -> PrefixIterator @@ -438,14 +460,14 @@ mod test_iterators { use codec::{Decode, Encode}; pub trait Config: 'static { - type Origin; + type RuntimeOrigin; type BlockNumber; type PalletInfo: crate::traits::PalletInfo; type DbWeight: crate::traits::Get; } crate::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=self {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} } #[derive(PartialEq, Eq, Clone, Encode, Decode)] @@ -475,10 +497,12 @@ mod test_iterators { fn n_map_iter_from() { sp_io::TestExternalities::default().execute_with(|| { use crate::{hash::Identity, storage::Key as NMapKey}; - crate::generate_storage_alias!( + #[crate::storage_alias] + type MyNMap = StorageNMap< MyModule, - MyNMap => NMap, u64> - ); + (NMapKey, NMapKey, NMapKey), + u64, + >; MyNMap::insert((1, 1, 1), 11); MyNMap::insert((1, 1, 2), 21); @@ -518,11 +542,15 @@ mod test_iterators { let key_hash = NMap::hashed_key_for((1, 2)); { - crate::generate_storage_alias!(Test, NMap => DoubleMap< - (u16, crate::Blake2_128Concat), - (u32, crate::Twox64Concat), - u64 - >); + #[crate::storage_alias] + type NMap = StorageDoubleMap< + Test, + crate::Blake2_128Concat, + u16, + crate::Twox64Concat, + u32, + u64, + >; let value = NMap::get(1, 2).unwrap(); assert_eq!(value, 50); diff --git a/frame/support/src/storage/generator/value.rs b/frame/support/src/storage/generator/value.rs index 3486eaa005c06..55b3487b1324c 100644 --- a/frame/support/src/storage/generator/value.rs +++ b/frame/support/src/storage/generator/value.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/storage/hashed.rs b/frame/support/src/storage/hashed.rs index 241caff809b3d..19c9d73be868d 100644 --- a/frame/support/src/storage/hashed.rs +++ b/frame/support/src/storage/hashed.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::get(&hash(key).as_ref()) + unhashed::get(hash(key).as_ref()) } /// Return the value of the item in storage under `key`, or the type's default if there is no @@ -39,7 +39,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::get_or_default(&hash(key).as_ref()) + unhashed::get_or_default(hash(key).as_ref()) } /// Return the value of the item in storage under `key`, or `default_value` if there is no @@ -50,7 +50,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::get_or(&hash(key).as_ref(), default_value) + unhashed::get_or(hash(key).as_ref(), default_value) } /// Return the value of the item in storage under `key`, or `default_value()` if there is no @@ -62,7 +62,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::get_or_else(&hash(key).as_ref(), default_value) + unhashed::get_or_else(hash(key).as_ref(), default_value) } /// Put `value` in storage under `key`. @@ -72,7 +72,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::put(&hash(key).as_ref(), value) + unhashed::put(hash(key).as_ref(), value) } /// Remove `key` from storage, returning its value if it had an explicit entry or `None` otherwise. @@ -82,7 +82,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::take(&hash(key).as_ref()) + unhashed::take(hash(key).as_ref()) } /// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage, @@ -93,7 +93,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::take_or_default(&hash(key).as_ref()) + unhashed::take_or_default(hash(key).as_ref()) } /// Return the value of the item in storage under `key`, or `default_value` if there is no @@ -104,7 +104,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::take_or(&hash(key).as_ref(), default_value) + unhashed::take_or(hash(key).as_ref(), default_value) } /// Return the value of the item in storage under `key`, or `default_value()` if there is no @@ -116,7 +116,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::take_or_else(&hash(key).as_ref(), default_value) + unhashed::take_or_else(hash(key).as_ref(), default_value) } /// Check to see if `key` has an explicit entry in storage. @@ -125,7 +125,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::exists(&hash(key).as_ref()) + unhashed::exists(hash(key).as_ref()) } /// Ensure `key` has no explicit entry in storage. @@ -134,7 +134,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::kill(&hash(key).as_ref()) + unhashed::kill(hash(key).as_ref()) } /// Get a Vec of bytes from storage. @@ -143,7 +143,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::get_raw(&hash(key).as_ref()) + unhashed::get_raw(hash(key).as_ref()) } /// Put a raw byte slice into storage. @@ -152,5 +152,5 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::put_raw(&hash(key).as_ref(), value) + unhashed::put_raw(hash(key).as_ref(), value) } diff --git a/frame/support/src/storage/migration.rs b/frame/support/src/storage/migration.rs index 59422a282aab5..67001fc4e1f42 100644 --- a/frame/support/src/storage/migration.rs +++ b/frame/support/src/storage/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -181,8 +181,8 @@ pub fn storage_iter_with_suffix( prefix.extend_from_slice(&storage_prefix); prefix.extend_from_slice(suffix); let previous_key = prefix.clone(); - let closure = |raw_key_without_prefix: &[u8], raw_value: &[u8]| { - let value = T::decode(&mut &raw_value[..])?; + let closure = |raw_key_without_prefix: &[u8], mut raw_value: &[u8]| { + let value = T::decode(&mut raw_value)?; Ok((raw_key_without_prefix.to_vec(), value)) }; @@ -213,10 +213,10 @@ pub fn storage_key_iter_with_suffix< prefix.extend_from_slice(&storage_prefix); prefix.extend_from_slice(suffix); let previous_key = prefix.clone(); - let closure = |raw_key_without_prefix: &[u8], raw_value: &[u8]| { + let closure = |raw_key_without_prefix: &[u8], mut raw_value: &[u8]| { let mut key_material = H::reverse(raw_key_without_prefix); let key = K::decode(&mut key_material)?; - let value = T::decode(&mut &raw_value[..])?; + let value = T::decode(&mut raw_value)?; Ok((key, value)) }; PrefixIterator { prefix, previous_key, drain: false, closure, phantom: Default::default() } @@ -256,12 +256,43 @@ pub fn put_storage_value(module: &[u8], item: &[u8], hash: &[u8], val /// Remove all items under a storage prefix by the `module`, the map's `item` name and the key /// `hash`. +#[deprecated = "Use `clear_storage_prefix` instead"] pub fn remove_storage_prefix(module: &[u8], item: &[u8], hash: &[u8]) { let mut key = vec![0u8; 32 + hash.len()]; let storage_prefix = storage_prefix(module, item); key[0..32].copy_from_slice(&storage_prefix); key[32..].copy_from_slice(hash); - frame_support::storage::unhashed::kill_prefix(&key, None); + let _ = frame_support::storage::unhashed::clear_prefix(&key, None, None); +} + +/// Attempt to remove all values under a storage prefix by the `module`, the map's `item` name and +/// the key `hash`. +/// +/// All values in the client overlay will be deleted, if `maybe_limit` is `Some` then up to +/// that number of values are deleted from the client backend by seeking and reading that number of +/// storage values plus one. If `maybe_limit` is `None` then all values in the client backend are +/// deleted. This is potentially unsafe since it's an unbounded operation. +/// +/// ## Cursors +/// +/// The `maybe_cursor` parameter should be `None` for the first call to initial removal. +/// If the resultant `maybe_cursor` is `Some`, then another call is required to complete the +/// removal operation. This value must be passed in as the subsequent call's `maybe_cursor` +/// parameter. If the resultant `maybe_cursor` is `None`, then the operation is complete and no +/// items remain in storage provided that no items were added between the first calls and the +/// final call. +pub fn clear_storage_prefix( + module: &[u8], + item: &[u8], + hash: &[u8], + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, +) -> sp_io::MultiRemovalResults { + let mut key = vec![0u8; 32 + hash.len()]; + let storage_prefix = storage_prefix(module, item); + key[0..32].copy_from_slice(&storage_prefix); + key[32..].copy_from_slice(hash); + frame_support::storage::unhashed::clear_prefix(&key, maybe_limit, maybe_cursor) } /// Take a particular item in storage by the `module`, the map's `item` name and the key `hash`. diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 35552e08fef1e..333f4382557b1 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ //! Stuff to do with the runtime's storage. -pub use self::types::StorageEntryMetadataBuilder; use crate::{ hash::{ReversibleStorageHasher, StorageHasher}, storage::types::{ @@ -28,8 +27,15 @@ use crate::{ use codec::{Decode, Encode, EncodeLike, FullCodec, FullEncode}; use sp_core::storage::ChildInfo; use sp_runtime::generic::{Digest, DigestItem}; +use sp_std::{collections::btree_set::BTreeSet, marker::PhantomData, prelude::*}; + +pub use self::{ + transactional::{ + in_storage_layer, with_storage_layer, with_transaction, with_transaction_unchecked, + }, + types::StorageEntryMetadataBuilder, +}; pub use sp_runtime::TransactionOutcome; -use sp_std::prelude::*; pub use types::Key; pub mod bounded_btree_map; @@ -40,87 +46,15 @@ pub mod child; pub mod generator; pub mod hashed; pub mod migration; +pub mod storage_noop_guard; +pub mod transactional; pub mod types; pub mod unhashed; pub mod weak_bounded_vec; -#[cfg(all(feature = "std", any(test, debug_assertions)))] -mod debug_helper { - use std::cell::RefCell; - - thread_local! { - static TRANSACTION_LEVEL: RefCell = RefCell::new(0); - } - - pub fn require_transaction() { - let level = TRANSACTION_LEVEL.with(|v| *v.borrow()); - if level == 0 { - panic!("Require transaction not called within with_transaction"); - } - } - - pub struct TransactionLevelGuard; - - impl Drop for TransactionLevelGuard { - fn drop(&mut self) { - TRANSACTION_LEVEL.with(|v| *v.borrow_mut() -= 1); - } - } - - /// Increments the transaction level. - /// - /// Returns a guard that when dropped decrements the transaction level automatically. - pub fn inc_transaction_level() -> TransactionLevelGuard { - TRANSACTION_LEVEL.with(|v| { - let mut val = v.borrow_mut(); - *val += 1; - if *val > 10 { - log::warn!( - "Detected with_transaction with nest level {}. Nested usage of with_transaction is not recommended.", - *val - ); - } - }); - - TransactionLevelGuard - } -} - -/// Assert this method is called within a storage transaction. -/// This will **panic** if is not called within a storage transaction. -/// -/// This assertion is enabled for native execution and when `debug_assertions` are enabled. -pub fn require_transaction() { - #[cfg(all(feature = "std", any(test, debug_assertions)))] - debug_helper::require_transaction(); -} - -/// Execute the supplied function in a new storage transaction. -/// -/// All changes to storage performed by the supplied function are discarded if the returned -/// outcome is `TransactionOutcome::Rollback`. -/// -/// Transactions can be nested to any depth. Commits happen to the parent transaction. -pub fn with_transaction(f: impl FnOnce() -> TransactionOutcome) -> R { - use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction}; - use TransactionOutcome::*; - - start_transaction(); - - #[cfg(all(feature = "std", any(test, debug_assertions)))] - let _guard = debug_helper::inc_transaction_level(); - - match f() { - Commit(res) => { - commit_transaction(); - res - }, - Rollback(res) => { - rollback_transaction(); - res - }, - } -} +/// Utility type for converting a storage map into a `Get` impl which returns the maximum +/// key size. +pub struct KeyLenOf(PhantomData); /// A trait for working with macro-generated storage values under the substrate storage API. /// @@ -234,6 +168,9 @@ pub trait StorageMap { /// Load the value associated with the given key from the map. fn get>(key: KeyArg) -> Self::Query; + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + fn set>(key: KeyArg, query: Self::Query); + /// Try to get the value for the given key from the map. /// /// Returns `Ok` if it exists, `Err` if not. @@ -266,6 +203,8 @@ pub trait StorageMap { ) -> R; /// Mutate the item, only if an `Ok` value is returned. Deletes the item if mutated to a `None`. + /// `f` will always be called with an option representing if the storage item exists (`Some`) + /// or if the storage item does not exist (`None`), independent of the `QueryType`. fn try_mutate_exists, R, E, F: FnOnce(&mut Option) -> Result>( key: KeyArg, f: F, @@ -515,9 +454,6 @@ pub trait IterableStorageNMap: StorageN /// An implementation of a map with a two keys. /// -/// It provides an important ability to efficiently remove all entries -/// that have a common first key. -/// /// Details on implementation can be found at [`generator::StorageDoubleMap`]. pub trait StorageDoubleMap { /// The type that get/take returns. @@ -549,6 +485,9 @@ pub trait StorageDoubleMap { KArg1: EncodeLike, KArg2: EncodeLike; + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + fn set, KArg2: EncodeLike>(k1: KArg1, k2: KArg2, query: Self::Query); + /// Take a value from storage, removing it afterwards. fn take(k1: KArg1, k2: KArg2) -> Self::Query where @@ -576,11 +515,46 @@ pub trait StorageDoubleMap { KArg1: EncodeLike, KArg2: EncodeLike; - /// Remove all values under the first key. + /// Remove all values under the first key `k1` in the overlay and up to `limit` in the + /// backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear_prefix` instead"] fn remove_prefix(k1: KArg1, limit: Option) -> sp_io::KillStorageResult where KArg1: ?Sized + EncodeLike; + /// Remove all values under the first key `k1` in the overlay and up to `maybe_limit` in the + /// backend. + /// + /// All values in the client overlay will be deleted, if `maybe_limit` is `Some` then up to + /// that number of values are deleted from the client backend, otherwise all values in the + /// client backend are deleted. + /// + /// ## Cursors + /// + /// The `maybe_cursor` parameter should be `None` for the first call to initial removal. + /// If the resultant `maybe_cursor` is `Some`, then another call is required to complete the + /// removal operation. This value must be passed in as the subsequent call's `maybe_cursor` + /// parameter. If the resultant `maybe_cursor` is `None`, then the operation is complete and no + /// items remain in storage provided that no items were added between the first calls and the + /// final call. + fn clear_prefix( + k1: KArg1, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + KArg1: ?Sized + EncodeLike; + /// Iterate over values that share the first key. fn iter_prefix_values(k1: KArg1) -> PrefixIterator where @@ -608,6 +582,8 @@ pub trait StorageDoubleMap { F: FnOnce(&mut Option) -> R; /// Mutate the item, only if an `Ok` value is returned. Deletes the item if mutated to a `None`. + /// `f` will always be called with an option representing if the storage item exists (`Some`) + /// or if the storage item does not exist (`None`), independent of the `QueryType`. fn try_mutate_exists(k1: KArg1, k2: KArg2, f: F) -> Result where KArg1: EncodeLike, @@ -688,6 +664,9 @@ pub trait StorageNMap { /// Returns `Ok` if it exists, `Err` if not. fn try_get + TupleToEncodedIter>(key: KArg) -> Result; + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + fn set + TupleToEncodedIter>(key: KArg, query: Self::Query); + /// Swap the values of two keys. fn swap(key1: KArg1, key2: KArg2) where @@ -704,11 +683,54 @@ pub trait StorageNMap { /// Remove the value under a key. fn remove + TupleToEncodedIter>(key: KArg); - /// Remove all values under the partial prefix key. + /// Remove all values starting with `partial_key` in the overlay and up to `limit` in the + /// backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear_prefix` instead"] fn remove_prefix(partial_key: KP, limit: Option) -> sp_io::KillStorageResult where K: HasKeyPrefix; + /// Attempt to remove items from the map matching a `partial_key` prefix. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map which match the `partial key`. If so, then the map may not be + /// empty when the resultant `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must be provided in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map and `partial_key`. Subsequent + /// calls operating on the same map/`partial_key` should always pass `Some`, and this should be + /// equal to the previous call result's `maybe_cursor` field. + fn clear_prefix( + partial_key: KP, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + K: HasKeyPrefix; + /// Iterate over values that share the partial prefix key. fn iter_prefix_values(partial_key: KP) -> PrefixIterator where @@ -735,6 +757,8 @@ pub trait StorageNMap { F: FnOnce(&mut Option) -> R; /// Mutate the item, only if an `Ok` value is returned. Deletes the item if mutated to a `None`. + /// `f` will always be called with an option representing if the storage item exists (`Some`) + /// or if the storage item does not exist (`None`), independent of the `QueryType`. fn try_mutate_exists(key: KArg, f: F) -> Result where KArg: EncodeLikeTuple + TupleToEncodedIter, @@ -802,6 +826,19 @@ pub struct PrefixIterator { phantom: core::marker::PhantomData, } +impl PrefixIterator { + /// Converts to the same iterator but with the different 'OnRemoval' type + pub fn convert_on_removal(self) -> PrefixIterator { + PrefixIterator:: { + prefix: self.prefix, + previous_key: self.previous_key, + drain: self.drain, + closure: self.closure, + phantom: Default::default(), + } + } +} + /// Trait for specialising on removal logic of [`PrefixIterator`]. pub trait PrefixIteratorOnRemoval { /// This function is called whenever a key/value is removed. @@ -1018,8 +1055,8 @@ impl ChildTriePrefixIterator<(Vec, T)> { pub fn with_prefix(child_info: &ChildInfo, prefix: &[u8]) -> Self { let prefix = prefix.to_vec(); let previous_key = prefix.clone(); - let closure = |raw_key_without_prefix: &[u8], raw_value: &[u8]| { - let value = T::decode(&mut &raw_value[..])?; + let closure = |raw_key_without_prefix: &[u8], mut raw_value: &[u8]| { + let value = T::decode(&mut raw_value)?; Ok((raw_key_without_prefix.to_vec(), value)) }; @@ -1045,10 +1082,10 @@ impl ChildTriePrefixIterator<(K, T)> { ) -> Self { let prefix = prefix.to_vec(); let previous_key = prefix.clone(); - let closure = |raw_key_without_prefix: &[u8], raw_value: &[u8]| { + let closure = |raw_key_without_prefix: &[u8], mut raw_value: &[u8]| { let mut key_material = H::reverse(raw_key_without_prefix); let key = K::decode(&mut key_material)?; - let value = T::decode(&mut &raw_value[..])?; + let value = T::decode(&mut raw_value)?; Ok((key, value)) }; @@ -1073,7 +1110,7 @@ impl Iterator for ChildTriePrefixIterator { Some(self.previous_key.clone()) } else { sp_io::default_child_storage::next_key( - &self.child_info.storage_key(), + self.child_info.storage_key(), &self.previous_key, ) .filter(|n| n.starts_with(&self.prefix)) @@ -1133,9 +1170,47 @@ pub trait StoragePrefixedMap { crate::storage::storage_prefix(Self::module_prefix(), Self::storage_prefix()) } - /// Remove all value of the storage. + /// Remove all values in the overlay and up to `limit` in the backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear` instead"] fn remove_all(limit: Option) -> sp_io::KillStorageResult { - sp_io::storage::clear_prefix(&Self::final_prefix(), limit) + unhashed::clear_prefix(&Self::final_prefix(), limit, None).into() + } + + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> sp_io::MultiRemovalResults { + unhashed::clear_prefix(&Self::final_prefix(), Some(limit), maybe_cursor) } /// Iter over all value of the storage. @@ -1193,7 +1268,7 @@ pub trait StoragePrefixedMap { pub trait StorageAppend: private::Sealed {} /// Marker trait that will be implemented for types that support to decode their length in an -/// effificent way. It is expected that the length is at the beginning of the encoded object +/// efficient way. It is expected that the length is at the beginning of the encoded object /// and that the length is a `Compact`. /// /// This trait is sealed. @@ -1223,11 +1298,12 @@ mod private { pub trait Sealed {} impl Sealed for Vec {} - impl Sealed for Digest {} + impl Sealed for Digest {} impl Sealed for BoundedVec {} impl Sealed for WeakBoundedVec {} impl Sealed for bounded_btree_map::BoundedBTreeMap {} impl Sealed for bounded_btree_set::BoundedBTreeSet {} + impl Sealed for BTreeSet {} macro_rules! impl_sealed_for_tuple { ($($elem:ident),+) => { @@ -1260,10 +1336,13 @@ mod private { impl StorageAppend for Vec {} impl StorageDecodeLength for Vec {} +impl StorageAppend for BTreeSet {} +impl StorageDecodeLength for BTreeSet {} + /// We abuse the fact that SCALE does not put any marker into the encoding, i.e. we only encode the /// internal vec and we can append to this vec. We have a test that ensures that if the `Digest` /// format ever changes, we need to remove this here. -impl StorageAppend> for Digest {} +impl StorageAppend for Digest {} /// Marker trait that is implemented for types that support the `storage::append` api with a limit /// on the number of element. @@ -1400,7 +1479,7 @@ mod test { use super::*; use crate::{assert_ok, hash::Identity, Twox128}; use bounded_vec::BoundedVec; - use core::convert::{TryFrom, TryInto}; + use frame_support::traits::ConstU32; use generator::StorageValue as _; use sp_core::hashing::twox_128; use sp_io::TestExternalities; @@ -1450,7 +1529,7 @@ mod test { assert_eq!(MyStorage::iter_values().collect::>(), vec![1, 2, 3, 4]); // test removal - MyStorage::remove_all(None); + let _ = MyStorage::clear(u32::max_value(), None); assert!(MyStorage::iter_values().collect::>().is_empty()); // test migration @@ -1460,7 +1539,7 @@ mod test { assert!(MyStorage::iter_values().collect::>().is_empty()); MyStorage::translate_values(|v: u32| Some(v as u64)); assert_eq!(MyStorage::iter_values().collect::>(), vec![1, 2]); - MyStorage::remove_all(None); + let _ = MyStorage::clear(u32::max_value(), None); // test migration 2 unhashed::put(&[&k[..], &vec![1][..]].concat(), &1u128); @@ -1472,7 +1551,7 @@ mod test { assert_eq!(MyStorage::iter_values().collect::>(), vec![1, 2, 3]); MyStorage::translate_values(|v: u128| Some(v as u64)); assert_eq!(MyStorage::iter_values().collect::>(), vec![1, 2, 3]); - MyStorage::remove_all(None); + let _ = MyStorage::clear(u32::max_value(), None); // test that other values are not modified. assert_eq!(unhashed::get(&key_before[..]), Some(32u64)); @@ -1485,8 +1564,8 @@ mod test { fn digest_storage_append_works_as_expected() { TestExternalities::default().execute_with(|| { struct Storage; - impl generator::StorageValue> for Storage { - type Query = Digest; + impl generator::StorageValue for Storage { + type Query = Digest; fn module_prefix() -> &'static [u8] { b"MyModule" @@ -1496,50 +1575,24 @@ mod test { b"Storage" } - fn from_optional_value_to_query(v: Option>) -> Self::Query { + fn from_optional_value_to_query(v: Option) -> Self::Query { v.unwrap() } - fn from_query_to_optional_value(v: Self::Query) -> Option> { + fn from_query_to_optional_value(v: Self::Query) -> Option { Some(v) } } - Storage::append(DigestItem::ChangesTrieRoot(1)); Storage::append(DigestItem::Other(Vec::new())); let value = unhashed::get_raw(&Storage::storage_value_final_key()).unwrap(); - let expected = Digest { - logs: vec![DigestItem::ChangesTrieRoot(1), DigestItem::Other(Vec::new())], - }; + let expected = Digest { logs: vec![DigestItem::Other(Vec::new())] }; assert_eq!(Digest::decode(&mut &value[..]).unwrap(), expected); }); } - #[test] - #[should_panic(expected = "Require transaction not called within with_transaction")] - fn require_transaction_should_panic() { - TestExternalities::default().execute_with(|| { - require_transaction(); - }); - } - - #[test] - fn require_transaction_should_not_panic_in_with_transaction() { - TestExternalities::default().execute_with(|| { - with_transaction(|| { - require_transaction(); - TransactionOutcome::Commit(()) - }); - - with_transaction(|| { - require_transaction(); - TransactionOutcome::Rollback(()) - }); - }); - } - #[test] fn key_prefix_iterator_works() { TestExternalities::default().execute_with(|| { @@ -1596,10 +1649,8 @@ mod test { fn prefix_iterator_pagination_works() { TestExternalities::default().execute_with(|| { use crate::{hash::Identity, storage::generator::map::StorageMap}; - crate::generate_storage_alias! { - MyModule, - MyStorageMap => Map<(u64, Identity), u64> - } + #[crate::storage_alias] + type MyStorageMap = StorageMap; MyStorageMap::insert(1, 10); MyStorageMap::insert(2, 20); @@ -1714,22 +1765,18 @@ mod test { }); } - crate::parameter_types! { - pub const Seven: u32 = 7; - pub const Four: u32 = 4; - } - - crate::generate_storage_alias! { Prefix, Foo => Value> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), BoundedVec> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedVec> - } + #[crate::storage_alias] + type Foo = StorageValue>>; + #[crate::storage_alias] + type FooMap = StorageMap>>; + #[crate::storage_alias] + type FooDoubleMap = + StorageDoubleMap>>; #[test] fn try_append_works() { TestExternalities::default().execute_with(|| { - let bounded: WeakBoundedVec = vec![1, 2, 3].try_into().unwrap(); + let bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); Foo::put(bounded); assert_ok!(Foo::try_append(4)); assert_ok!(Foo::try_append(5)); @@ -1740,7 +1787,7 @@ mod test { }); TestExternalities::default().execute_with(|| { - let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + let bounded: BoundedVec> = vec![1, 2, 3].try_into().unwrap(); FooMap::insert(1, bounded); assert_ok!(FooMap::try_append(1, 4)); @@ -1755,17 +1802,17 @@ mod test { assert_ok!(FooMap::try_append(2, 4)); assert_eq!( FooMap::get(2).unwrap(), - BoundedVec::::try_from(vec![4]).unwrap(), + BoundedVec::>::try_from(vec![4]).unwrap(), ); assert_ok!(FooMap::try_append(2, 5)); assert_eq!( FooMap::get(2).unwrap(), - BoundedVec::::try_from(vec![4, 5]).unwrap(), + BoundedVec::>::try_from(vec![4, 5]).unwrap(), ); }); TestExternalities::default().execute_with(|| { - let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + let bounded: BoundedVec> = vec![1, 2, 3].try_into().unwrap(); FooDoubleMap::insert(1, 1, bounded); assert_ok!(FooDoubleMap::try_append(1, 1, 4)); @@ -1780,13 +1827,31 @@ mod test { assert_ok!(FooDoubleMap::try_append(2, 1, 4)); assert_eq!( FooDoubleMap::get(2, 1).unwrap(), - BoundedVec::::try_from(vec![4]).unwrap(), + BoundedVec::>::try_from(vec![4]).unwrap(), ); assert_ok!(FooDoubleMap::try_append(2, 1, 5)); assert_eq!( FooDoubleMap::get(2, 1).unwrap(), - BoundedVec::::try_from(vec![4, 5]).unwrap(), + BoundedVec::>::try_from(vec![4, 5]).unwrap(), ); }); } + + #[crate::storage_alias] + type FooSet = StorageValue>; + + #[test] + fn btree_set_append_and_decode_len_works() { + TestExternalities::default().execute_with(|| { + let btree = BTreeSet::from([1, 2, 3]); + FooSet::put(btree); + + FooSet::append(4); + FooSet::append(5); + FooSet::append(6); + FooSet::append(7); + + assert_eq!(FooSet::decode_len().unwrap(), 7); + }); + } } diff --git a/frame/support/src/storage/storage_noop_guard.rs b/frame/support/src/storage/storage_noop_guard.rs new file mode 100644 index 0000000000000..7186c3eaf467a --- /dev/null +++ b/frame/support/src/storage/storage_noop_guard.rs @@ -0,0 +1,114 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +// Feature gated since it can panic. +#![cfg(any(feature = "std", feature = "runtime-benchmarks", feature = "try-runtime", test))] + +//! Contains the [`crate::StorageNoopGuard`] for conveniently asserting +//! that no storage mutation has been made by a whole code block. + +/// Asserts that no storage changes took place between con- and destruction of [`Self`]. +/// +/// This is easier than wrapping the whole code-block inside a `assert_storage_noop!`. +/// +/// # Example +/// +/// ```should_panic +/// use frame_support::{StorageNoopGuard, storage::unhashed::put}; +/// +/// sp_io::TestExternalities::default().execute_with(|| { +/// let _guard = frame_support::StorageNoopGuard::default(); +/// put(b"key", b"value"); +/// // Panics since there are storage changes. +/// }); +/// ``` +#[must_use] +pub struct StorageNoopGuard(sp_std::vec::Vec); + +impl Default for StorageNoopGuard { + fn default() -> Self { + Self(frame_support::storage_root(frame_support::StateVersion::V1)) + } +} + +impl Drop for StorageNoopGuard { + fn drop(&mut self) { + // No need to double panic, eg. inside a test assertion failure. + if sp_std::thread::panicking() { + return + } + assert_eq!( + frame_support::storage_root(frame_support::StateVersion::V1), + self.0, + "StorageNoopGuard detected wrongful storage changes.", + ); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_io::TestExternalities; + + #[test] + #[should_panic(expected = "StorageNoopGuard detected wrongful storage changes.")] + fn storage_noop_guard_panics_on_changed() { + TestExternalities::default().execute_with(|| { + let _guard = StorageNoopGuard::default(); + frame_support::storage::unhashed::put(b"key", b"value"); + }); + } + + #[test] + fn storage_noop_guard_works_on_unchanged() { + TestExternalities::default().execute_with(|| { + let _guard = StorageNoopGuard::default(); + frame_support::storage::unhashed::put(b"key", b"value"); + frame_support::storage::unhashed::kill(b"key"); + }); + } + + #[test] + #[should_panic(expected = "StorageNoopGuard detected wrongful storage changes.")] + fn storage_noop_guard_panics_on_early_drop() { + TestExternalities::default().execute_with(|| { + let guard = StorageNoopGuard::default(); + frame_support::storage::unhashed::put(b"key", b"value"); + sp_std::mem::drop(guard); + frame_support::storage::unhashed::kill(b"key"); + }); + } + + #[test] + fn storage_noop_guard_works_on_changed_forget() { + TestExternalities::default().execute_with(|| { + let guard = StorageNoopGuard::default(); + frame_support::storage::unhashed::put(b"key", b"value"); + sp_std::mem::forget(guard); + }); + } + + #[test] + #[should_panic(expected = "Something else")] + fn storage_noop_guard_does_not_double_panic() { + TestExternalities::default().execute_with(|| { + let _guard = StorageNoopGuard::default(); + frame_support::storage::unhashed::put(b"key", b"value"); + panic!("Something else"); + }); + } +} diff --git a/frame/support/src/storage/transactional.rs b/frame/support/src/storage/transactional.rs new file mode 100644 index 0000000000000..909d5909ed8bd --- /dev/null +++ b/frame/support/src/storage/transactional.rs @@ -0,0 +1,301 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Provides functionality around the transaction storage. +//! +//! Transactional storage provides functionality to run an entire code block +//! in a storage transaction. This means that either the entire changes to the +//! storage are committed or everything is thrown away. This simplifies the +//! writing of functionality that may bail at any point of operation. Otherwise +//! you would need to first verify all storage accesses and then do the storage +//! modifications. +//! +//! [`with_transaction`] provides a way to run a given closure in a transactional context. + +use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction}; +use sp_runtime::{DispatchError, TransactionOutcome, TransactionalError}; + +/// The type that is being used to store the current number of active layers. +pub type Layer = u32; +/// The key that is holds the current number of active layers. +pub const TRANSACTION_LEVEL_KEY: &[u8] = b":transaction_level:"; +/// The maximum number of nested layers. +pub const TRANSACTIONAL_LIMIT: Layer = 255; + +/// Returns the current number of nested transactional layers. +fn get_transaction_level() -> Layer { + crate::storage::unhashed::get_or_default::(TRANSACTION_LEVEL_KEY) +} + +/// Set the current number of nested transactional layers. +fn set_transaction_level(level: Layer) { + crate::storage::unhashed::put::(TRANSACTION_LEVEL_KEY, &level); +} + +/// Kill the transactional layers storage. +fn kill_transaction_level() { + crate::storage::unhashed::kill(TRANSACTION_LEVEL_KEY); +} + +/// Increments the transaction level. Returns an error if levels go past the limit. +/// +/// Returns a guard that when dropped decrements the transaction level automatically. +fn inc_transaction_level() -> Result { + let existing_levels = get_transaction_level(); + if existing_levels >= TRANSACTIONAL_LIMIT { + return Err(()) + } + // Cannot overflow because of check above. + set_transaction_level(existing_levels + 1); + Ok(StorageLayerGuard) +} + +fn dec_transaction_level() { + let existing_levels = get_transaction_level(); + if existing_levels == 0 { + log::warn!( + "We are underflowing with calculating transactional levels. Not great, but let's not panic...", + ); + } else if existing_levels == 1 { + // Don't leave any trace of this storage item. + kill_transaction_level(); + } else { + // Cannot underflow because of checks above. + set_transaction_level(existing_levels - 1); + } +} + +struct StorageLayerGuard; + +impl Drop for StorageLayerGuard { + fn drop(&mut self) { + dec_transaction_level() + } +} + +/// Check if the current call is within a transactional layer. +pub fn is_transactional() -> bool { + get_transaction_level() > 0 +} + +/// Execute the supplied function in a new storage transaction. +/// +/// All changes to storage performed by the supplied function are discarded if the returned +/// outcome is `TransactionOutcome::Rollback`. +/// +/// Transactions can be nested up to `TRANSACTIONAL_LIMIT` times; more than that will result in an +/// error. +/// +/// Commits happen to the parent transaction. +pub fn with_transaction(f: F) -> Result +where + E: From, + F: FnOnce() -> TransactionOutcome>, +{ + // This needs to happen before `start_transaction` below. + // Otherwise we may rollback the increase, then decrease as the guard goes out of scope + // and then end in some bad state. + let _guard = inc_transaction_level().map_err(|()| TransactionalError::LimitReached.into())?; + + start_transaction(); + + match f() { + TransactionOutcome::Commit(res) => { + commit_transaction(); + res + }, + TransactionOutcome::Rollback(res) => { + rollback_transaction(); + res + }, + } +} + +/// Same as [`with_transaction`] but without a limit check on nested transactional layers. +/// +/// This is mostly for backwards compatibility before there was a transactional layer limit. +/// It is recommended to only use [`with_transaction`] to avoid users from generating too many +/// transactional layers. +pub fn with_transaction_unchecked(f: F) -> R +where + F: FnOnce() -> TransactionOutcome, +{ + // This needs to happen before `start_transaction` below. + // Otherwise we may rollback the increase, then decrease as the guard goes out of scope + // and then end in some bad state. + let maybe_guard = inc_transaction_level(); + + if maybe_guard.is_err() { + log::warn!( + "The transactional layer limit has been reached, and new transactional layers are being + spawned with `with_transaction_unchecked`. This could be caused by someone trying to + attack your chain, and you should investigate usage of `with_transaction_unchecked` and + potentially migrate to `with_transaction`, which enforces a transactional limit.", + ); + } + + start_transaction(); + + match f() { + TransactionOutcome::Commit(res) => { + commit_transaction(); + res + }, + TransactionOutcome::Rollback(res) => { + rollback_transaction(); + res + }, + } +} + +/// Execute the supplied function, adding a new storage layer. +/// +/// This is the same as `with_transaction`, but assuming that any function returning an `Err` should +/// rollback, and any function returning `Ok` should commit. This provides a cleaner API to the +/// developer who wants this behavior. +pub fn with_storage_layer(f: F) -> Result +where + E: From, + F: FnOnce() -> Result, +{ + with_transaction(|| { + let r = f(); + if r.is_ok() { + TransactionOutcome::Commit(r) + } else { + TransactionOutcome::Rollback(r) + } + }) +} + +/// Execute the supplied function, ensuring we are at least in one storage layer. +/// +/// If we are already in a storage layer, we just execute the provided closure. +/// If we are not, we execute the closure within a [`with_storage_layer`]. +pub fn in_storage_layer(f: F) -> Result +where + E: From, + F: FnOnce() -> Result, +{ + if is_transactional() { + f() + } else { + with_storage_layer(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{assert_noop, assert_ok}; + use sp_io::TestExternalities; + use sp_runtime::DispatchResult; + + #[test] + fn is_transactional_should_return_false() { + TestExternalities::default().execute_with(|| { + assert!(!is_transactional()); + }); + } + + #[test] + fn is_transactional_should_not_error_in_with_transaction() { + TestExternalities::default().execute_with(|| { + assert_ok!(with_transaction(|| -> TransactionOutcome { + assert!(is_transactional()); + TransactionOutcome::Commit(Ok(())) + })); + + assert_noop!( + with_transaction(|| -> TransactionOutcome { + assert!(is_transactional()); + TransactionOutcome::Rollback(Err("revert".into())) + }), + "revert" + ); + }); + } + + fn recursive_transactional(num: u32) -> DispatchResult { + if num == 0 { + return Ok(()) + } + + with_transaction(|| -> TransactionOutcome { + let res = recursive_transactional(num - 1); + TransactionOutcome::Commit(res) + }) + } + + #[test] + fn transaction_limit_should_work() { + TestExternalities::default().execute_with(|| { + assert_eq!(get_transaction_level(), 0); + + assert_ok!(with_transaction(|| -> TransactionOutcome { + assert_eq!(get_transaction_level(), 1); + TransactionOutcome::Commit(Ok(())) + })); + + assert_ok!(with_transaction(|| -> TransactionOutcome { + assert_eq!(get_transaction_level(), 1); + let res = with_transaction(|| -> TransactionOutcome { + assert_eq!(get_transaction_level(), 2); + TransactionOutcome::Commit(Ok(())) + }); + TransactionOutcome::Commit(res) + })); + + assert_ok!(recursive_transactional(255)); + assert_noop!( + recursive_transactional(256), + sp_runtime::TransactionalError::LimitReached + ); + + assert_eq!(get_transaction_level(), 0); + }); + } + + #[test] + fn in_storage_layer_works() { + TestExternalities::default().execute_with(|| { + assert_eq!(get_transaction_level(), 0); + + let res = in_storage_layer(|| -> DispatchResult { + assert_eq!(get_transaction_level(), 1); + in_storage_layer(|| -> DispatchResult { + // We are still in the same layer :) + assert_eq!(get_transaction_level(), 1); + Ok(()) + }) + }); + + assert_ok!(res); + + let res = in_storage_layer(|| -> DispatchResult { + assert_eq!(get_transaction_level(), 1); + in_storage_layer(|| -> DispatchResult { + // We are still in the same layer :) + assert_eq!(get_transaction_level(), 1); + Err("epic fail".into()) + }) + }); + + assert_noop!(res, "epic fail"); + }); + } +} diff --git a/frame/support/src/storage/types/counted_map.rs b/frame/support/src/storage/types/counted_map.rs index 0860a4ed541c6..8c19434767f49 100644 --- a/frame/support/src/storage/types/counted_map.rs +++ b/frame/support/src/storage/types/counted_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,6 +31,7 @@ use crate::{ Never, }; use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen, Ref}; +use sp_io::MultiRemovalResults; use sp_runtime::traits::Saturating; use sp_std::prelude::*; @@ -98,6 +99,17 @@ where OnEmpty: Get + 'static, MaxValues: Get>, { + /// The key used to store the counter of the map. + pub fn counter_storage_final_key() -> [u8; 32] { + CounterFor::::hashed_key() + } + + /// The prefix used to generate the key of the map. + pub fn map_storage_final_prefix() -> Vec { + use crate::storage::generator::StorageMap; + ::Map::prefix_hash() + } + /// Get the storage key used to fetch a value corresponding to a specific key. pub fn hashed_key_for>(key: KeyArg) -> Vec { ::Map::hashed_key_for(key) @@ -120,16 +132,18 @@ where ::Map::try_get(key) } + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + pub fn set>(key: KeyArg, q: QueryKind::Query) { + ::Map::set(key, q) + } + /// Swap the values of two keys. pub fn swap, KeyArg2: EncodeLike>(key1: KeyArg1, key2: KeyArg2) { ::Map::swap(key1, key2) } /// Store a value to be associated with the given key from the map. - pub fn insert + Clone, ValArg: EncodeLike>( - key: KeyArg, - val: ValArg, - ) { + pub fn insert, ValArg: EncodeLike>(key: KeyArg, val: ValArg) { if !::Map::contains_key(Ref::from(&key)) { CounterFor::::mutate(|value| value.saturating_inc()); } @@ -137,7 +151,7 @@ where } /// Remove the value under a key. - pub fn remove + Clone>(key: KeyArg) { + pub fn remove>(key: KeyArg) { if ::Map::contains_key(Ref::from(&key)) { CounterFor::::mutate(|value| value.saturating_dec()); } @@ -145,7 +159,7 @@ where } /// Mutate the value under a key. - pub fn mutate + Clone, R, F: FnOnce(&mut QueryKind::Query) -> R>( + pub fn mutate, R, F: FnOnce(&mut QueryKind::Query) -> R>( key: KeyArg, f: F, ) -> R { @@ -156,7 +170,7 @@ where /// Mutate the item, only if an `Ok` value is returned. pub fn try_mutate(key: KeyArg, f: F) -> Result where - KeyArg: EncodeLike + Clone, + KeyArg: EncodeLike, F: FnOnce(&mut QueryKind::Query) -> Result, { Self::try_mutate_exists(key, |option_value_ref| { @@ -170,7 +184,7 @@ where } /// Mutate the value under a key. Deletes the item if mutated to a `None`. - pub fn mutate_exists + Clone, R, F: FnOnce(&mut Option) -> R>( + pub fn mutate_exists, R, F: FnOnce(&mut Option) -> R>( key: KeyArg, f: F, ) -> R { @@ -179,9 +193,11 @@ where } /// Mutate the item, only if an `Ok` value is returned. Deletes the item if mutated to a `None`. + /// `f` will always be called with an option representing if the storage item exists (`Some`) + /// or if the storage item does not exist (`None`), independent of the `QueryType`. pub fn try_mutate_exists(key: KeyArg, f: F) -> Result where - KeyArg: EncodeLike + Clone, + KeyArg: EncodeLike, F: FnOnce(&mut Option) -> Result, { ::Map::try_mutate_exists(key, |option_value| { @@ -203,9 +219,8 @@ where } /// Take the value under a key. - pub fn take + Clone>(key: KeyArg) -> QueryKind::Query { - let removed_value = - ::Map::mutate_exists(key, |value| core::mem::replace(value, None)); + pub fn take>(key: KeyArg) -> QueryKind::Query { + let removed_value = ::Map::mutate_exists(key, |value| value.take()); if removed_value.is_some() { CounterFor::::mutate(|value| value.saturating_dec()); } @@ -222,7 +237,7 @@ where /// `[item]`. Any default value set for the storage item will be ignored on overwrite. pub fn append(key: EncodeLikeKey, item: EncodeLikeItem) where - EncodeLikeKey: EncodeLike + Clone, + EncodeLikeKey: EncodeLike, Item: Encode, EncodeLikeItem: EncodeLike, Value: StorageAppend, @@ -261,24 +276,51 @@ where ::Map::migrate_key::(key) } - /// Remove all value of the storage. + /// Remove all values in the map. + #[deprecated = "Use `clear` instead"] pub fn remove_all() { - CounterFor::::set(0u32); + #[allow(deprecated)] ::Map::remove_all(None); + CounterFor::::kill(); + } + + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + pub fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> MultiRemovalResults { + let result = ::Map::clear(limit, maybe_cursor); + match result.maybe_cursor { + None => CounterFor::::kill(), + Some(_) => CounterFor::::mutate(|x| x.saturating_reduce(result.unique)), + } + result } /// Iter over all value of the storage. /// /// NOTE: If a value failed to decode because storage is corrupted then it is skipped. pub fn iter_values() -> crate::storage::PrefixIterator> { - let map_iterator = ::Map::iter_values(); - crate::storage::PrefixIterator { - prefix: map_iterator.prefix, - previous_key: map_iterator.previous_key, - drain: map_iterator.drain, - closure: map_iterator.closure, - phantom: Default::default(), - } + ::Map::iter_values().convert_on_removal() } /// Translate the values of all elements by a function `f`, in the map in no particular order. @@ -310,7 +352,7 @@ where /// Is only available if `Value` of the storage implements [`StorageTryAppend`]. pub fn try_append(key: KArg, item: EncodeLikeItem) -> Result<(), ()> where - KArg: EncodeLike + Clone, + KArg: EncodeLike, Item: Encode, EncodeLikeItem: EncodeLike, Value: StorageTryAppend, @@ -360,28 +402,14 @@ where /// /// If you alter the map while doing this, you'll get undefined results. pub fn iter() -> crate::storage::PrefixIterator<(Key, Value), OnRemovalCounterUpdate> { - let map_iterator = ::Map::iter(); - crate::storage::PrefixIterator { - prefix: map_iterator.prefix, - previous_key: map_iterator.previous_key, - drain: map_iterator.drain, - closure: map_iterator.closure, - phantom: Default::default(), - } + ::Map::iter().convert_on_removal() } /// Remove all elements from the map and iterate through them in no particular order. /// /// If you add elements to the map while doing this, you'll get undefined results. pub fn drain() -> crate::storage::PrefixIterator<(Key, Value), OnRemovalCounterUpdate> { - let map_iterator = ::Map::drain(); - crate::storage::PrefixIterator { - prefix: map_iterator.prefix, - previous_key: map_iterator.previous_key, - drain: map_iterator.drain, - closure: map_iterator.closure, - phantom: Default::default(), - } + ::Map::drain().convert_on_removal() } /// Translate the values of all elements by a function `f`, in the map in no particular order. @@ -398,6 +426,23 @@ where res }) } + + /// Enumerate all elements in the counted map after a specified `starting_raw_key` in no + /// particular order. + /// + /// If you alter the map while doing this, you'll get undefined results. + pub fn iter_from( + starting_raw_key: Vec, + ) -> crate::storage::PrefixIterator<(Key, Value), OnRemovalCounterUpdate> { + ::Map::iter_from(starting_raw_key).convert_on_removal() + } + + /// Enumerate all keys in the counted map. + /// + /// If you alter the map while doing this, you'll get undefined results. + pub fn iter_keys() -> crate::storage::KeyPrefixIterator { + ::Map::iter_keys() + } } impl StorageEntryMetadataBuilder @@ -414,7 +459,11 @@ where fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { ::Map::build_metadata(docs, entries); CounterFor::::build_metadata( - vec![&"Counter for the related counted storage map"], + if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + vec!["Counter for the related counted storage map"] + }, entries, ); } @@ -676,7 +725,7 @@ mod test { assert_eq!(A::count(), 2); // Remove all. - A::remove_all(); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::count(), 0); assert_eq!(A::initialize_counter(), 0); @@ -907,7 +956,7 @@ mod test { assert_eq!(B::count(), 2); // Remove all. - B::remove_all(); + let _ = B::clear(u32::max_value(), None); assert_eq!(B::count(), 0); assert_eq!(B::initialize_counter(), 0); @@ -1008,6 +1057,25 @@ mod test { }) } + #[test] + fn test_iter_from() { + type A = CountedStorageMap; + TestExternalities::default().execute_with(|| { + A::insert(1, 1); + A::insert(2, 2); + A::insert(3, 3); + A::insert(4, 4); + + // no prefix is same as normal iter. + assert_eq!(A::iter_from(vec![]).collect::>(), A::iter().collect::>()); + + let iter_all = A::iter().collect::>(); + let (before, after) = iter_all.split_at(2); + let last_key = before.last().map(|(k, _)| k).unwrap(); + assert_eq!(A::iter_from(A::hashed_key_for(last_key)).collect::>(), after); + }) + } + #[test] fn test_metadata() { type A = CountedStorageMap; @@ -1032,7 +1100,11 @@ mod test { modifier: StorageEntryModifier::Default, ty: StorageEntryType::Plain(scale_info::meta_type::()), default: vec![0, 0, 0, 0], - docs: vec!["Counter for the related counted storage map"], + docs: if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + vec!["Counter for the related counted storage map"] + }, }, ] ); diff --git a/frame/support/src/storage/types/double_map.rs b/frame/support/src/storage/types/double_map.rs index b9af4a621b92a..9ba4cf052e82a 100644 --- a/frame/support/src/storage/types/double_map.rs +++ b/frame/support/src/storage/types/double_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,9 +22,10 @@ use crate::{ metadata::{StorageEntryMetadata, StorageEntryType}, storage::{ types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder}, - StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend, + KeyLenOf, StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend, }, traits::{Get, GetDefault, StorageInfo, StorageInstance}, + StorageHasher, Twox128, }; use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::SaturatedConversion; @@ -70,6 +71,35 @@ pub struct StorageDoubleMap< )>, ); +impl Get + for KeyLenOf< + StorageDoubleMap< + Prefix, + Hasher1, + Key1, + Hasher2, + Key2, + Value, + QueryKind, + OnEmpty, + MaxValues, + >, + > where + Prefix: StorageInstance, + Hasher1: crate::hash::StorageHasher, + Hasher2: crate::hash::StorageHasher, + Key1: MaxEncodedLen, + Key2: MaxEncodedLen, +{ + fn get() -> u32 { + // The `max_len` of both key hashes plus the pallet prefix and storage prefix (which both + // are hashed with `Twox128`). + let z = + Hasher1::max_len::() + Hasher2::max_len::() + Twox128::max_len::<()>() * 2; + z as u32 + } +} + impl crate::storage::generator::StorageDoubleMap for StorageDoubleMap @@ -174,6 +204,15 @@ where >::try_get(k1, k2) } + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + pub fn set, KArg2: EncodeLike>( + k1: KArg1, + k2: KArg2, + q: QueryKind::Query, + ) { + >::set(k1, k2, q) + } + /// Take a value from storage, removing it afterwards. pub fn take(k1: KArg1, k2: KArg2) -> QueryKind::Query where @@ -217,14 +256,65 @@ where >::remove(k1, k2) } - /// Remove all values under the first key. + /// Remove all values under `k1` in the overlay and up to `limit` in the + /// backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear_prefix` instead"] pub fn remove_prefix(k1: KArg1, limit: Option) -> sp_io::KillStorageResult where KArg1: ?Sized + EncodeLike, { + #[allow(deprecated)] >::remove_prefix(k1, limit) } + /// Attempt to remove items from the map matching a `first_key` prefix. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map which match the `first_key`. If so, then the map may not be + /// empty when the resultant `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map and `first_key`. Subsequent + /// calls operating on the same map/`first_key` should always pass `Some`, and this should be + /// equal to the previous call result's `maybe_cursor` field. + pub fn clear_prefix( + first_key: KArg1, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + KArg1: ?Sized + EncodeLike, + { + >::clear_prefix( + first_key, + limit, + maybe_cursor, + ) + } + /// Iterate over values that share the first key. pub fn iter_prefix_values(k1: KArg1) -> crate::storage::PrefixIterator where @@ -264,6 +354,8 @@ where } /// Mutate the item, only if an `Ok` value is returned. Deletes the item if mutated to a `None`. + /// `f` will always be called with an option representing if the storage item exists (`Some`) + /// or if the storage item does not exist (`None`), independent of the `QueryType`. pub fn try_mutate_exists(k1: KArg1, k2: KArg2, f: F) -> Result where KArg1: EncodeLike, @@ -335,11 +427,50 @@ where >(key1, key2) } - /// Remove all value of the storage. + /// Remove all values in the overlay and up to `limit` in the backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear` instead"] pub fn remove_all(limit: Option) -> sp_io::KillStorageResult { + #[allow(deprecated)] >::remove_all(limit) } + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen.A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + pub fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> sp_io::MultiRemovalResults { + >::clear(limit, maybe_cursor) + } + /// Iter over all value of the storage. /// /// NOTE: If a value failed to decode because storage is corrupted then it is skipped. @@ -526,6 +657,8 @@ where MaxValues: Get>, { fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { + let docs = if cfg!(feature = "no-metadata-docs") { vec![] } else { docs }; + let entry = StorageEntryMetadata { name: Prefix::STORAGE_PREFIX, modifier: QueryKind::METADATA, @@ -623,6 +756,16 @@ mod test { } } + #[test] + fn keylenof_works() { + // Works with Blake2_128Concat and Twox64Concat. + type A = StorageDoubleMap; + let size = 16 * 2 // Two Twox128 + + 16 + 8 // Blake2_128Concat = hash + key + + 8 + 4; // Twox64Concat = hash + key + assert_eq!(KeyLenOf::::get(), size); + } + #[test] fn test() { type A = @@ -743,7 +886,7 @@ mod test { A::insert(3, 30, 10); A::insert(4, 40, 10); - A::remove_all(None); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::contains_key(3, 30), false); assert_eq!(A::contains_key(4, 40), false); @@ -804,7 +947,7 @@ mod test { ] ); - WithLen::remove_all(None); + let _ = WithLen::clear(u32::max_value(), None); assert_eq!(WithLen::decode_len(3, 30), None); WithLen::append(0, 100, 10); assert_eq!(WithLen::decode_len(0, 100), Some(1)); @@ -818,7 +961,7 @@ mod test { assert_eq!(A::iter_prefix_values(4).collect::>(), vec![13, 14]); assert_eq!(A::iter_prefix(4).collect::>(), vec![(40, 13), (41, 14)]); - A::remove_prefix(3, None); + let _ = A::clear_prefix(3, u32::max_value(), None); assert_eq!(A::iter_prefix(3).collect::>(), vec![]); assert_eq!(A::iter_prefix(4).collect::>(), vec![(40, 13), (41, 14)]); diff --git a/frame/support/src/storage/types/key.rs b/frame/support/src/storage/types/key.rs index da265fd6e6c87..182ddbedd9b8e 100755 --- a/frame/support/src/storage/types/key.rs +++ b/frame/support/src/storage/types/key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index 45340f9015eaa..0f89e2378a55d 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,9 +22,10 @@ use crate::{ metadata::{StorageEntryMetadata, StorageEntryType}, storage::{ types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder}, - StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend, + KeyLenOf, StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend, }, traits::{Get, GetDefault, StorageInfo, StorageInstance}, + StorageHasher, Twox128, }; use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::SaturatedConversion; @@ -53,6 +54,21 @@ pub struct StorageMap< MaxValues = GetDefault, >(core::marker::PhantomData<(Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues)>); +impl Get + for KeyLenOf> +where + Prefix: StorageInstance, + Hasher: crate::hash::StorageHasher, + Key: FullCodec + MaxEncodedLen, +{ + fn get() -> u32 { + // The `max_len` of the key hash plus the pallet prefix and storage prefix (which both are + // hashed with `Twox128`). + let z = Hasher::max_len::() + Twox128::max_len::<()>() * 2; + z as u32 + } +} + impl crate::storage::generator::StorageMap for StorageMap @@ -138,6 +154,11 @@ where >::swap(key1, key2) } + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + pub fn set>(key: KeyArg, q: QueryKind::Query) { + >::set(key, q) + } + /// Store a value to be associated with the given key from the map. pub fn insert, ValArg: EncodeLike>(key: KeyArg, val: ValArg) { >::insert(key, val) @@ -174,6 +195,8 @@ where } /// Mutate the item, only if an `Ok` value is returned. Deletes the item if mutated to a `None`. + /// `f` will always be called with an option representing if the storage item exists (`Some`) + /// or if the storage item does not exist (`None`), independent of the `QueryType`. pub fn try_mutate_exists(key: KeyArg, f: F) -> Result where KeyArg: EncodeLike, @@ -234,11 +257,50 @@ where >::migrate_key::(key) } - /// Remove all value of the storage. + /// Remove all values of the storage in the overlay and up to `limit` in the backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear` instead"] pub fn remove_all(limit: Option) -> sp_io::KillStorageResult { + #[allow(deprecated)] >::remove_all(limit) } + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + pub fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> sp_io::MultiRemovalResults { + >::clear(limit, maybe_cursor) + } + /// Iter over all value of the storage. /// /// NOTE: If a value failed to decode because storage is corrupted then it is skipped. @@ -348,6 +410,8 @@ where MaxValues: Get>, { fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { + let docs = if cfg!(feature = "no-metadata-docs") { vec![] } else { docs }; + let entry = StorageEntryMetadata { name: Prefix::STORAGE_PREFIX, modifier: QueryKind::METADATA, @@ -439,6 +503,27 @@ mod test { } } + #[test] + fn keylenof_works() { + // Works with Blake2_128Concat. + type A = StorageMap; + let size = 16 * 2 // Two Twox128 + + 16 + 4; // Blake2_128Concat = hash + key + assert_eq!(KeyLenOf::::get(), size); + + // Works with Blake2_256. + type B = StorageMap; + let size = 16 * 2 // Two Twox128 + + 32; // Blake2_256 + assert_eq!(KeyLenOf::::get(), size); + + // Works with Twox64Concat. + type C = StorageMap; + let size = 16 * 2 // Two Twox128 + + 8 + 4; // Twox64Concat = hash + key + assert_eq!(KeyLenOf::::get(), size); + } + #[test] fn test() { type A = StorageMap; @@ -549,7 +634,7 @@ mod test { A::insert(3, 10); A::insert(4, 10); - A::remove_all(None); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::contains_key(3), false); assert_eq!(A::contains_key(4), false); @@ -604,7 +689,7 @@ mod test { ] ); - WithLen::remove_all(None); + let _ = WithLen::clear(u32::max_value(), None); assert_eq!(WithLen::decode_len(3), None); WithLen::append(0, 10); assert_eq!(WithLen::decode_len(0), Some(1)); diff --git a/frame/support/src/storage/types/mod.rs b/frame/support/src/storage/types/mod.rs index bcab996f68323..f87da5de12274 100644 --- a/frame/support/src/storage/types/mod.rs +++ b/frame/support/src/storage/types/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,9 +42,11 @@ pub use value::StorageValue; /// Trait implementing how the storage optional value is converted into the queried type. /// /// It is implemented by: -/// * `OptionQuery` which convert an optional value to an optional value, user when querying storage -/// will get an optional value. -/// * `ValueQuery` which convert an optional value to a value, user when querying storage will get a +/// * `OptionQuery` which converts an optional value to an optional value, used when querying +/// storage returns an optional value. +/// * `ResultQuery` which converts an optional value to a result value, used when querying storage +/// returns a result value. +/// * `ValueQuery` which converts an optional value to a value, used when querying storage returns a /// value. pub trait QueryKindTrait { /// Metadata for the storage kind. @@ -85,6 +87,30 @@ where } } +/// Implement QueryKindTrait with query being `Result` +pub struct ResultQuery(sp_std::marker::PhantomData); +impl QueryKindTrait for ResultQuery +where + Value: FullCodec + 'static, + Error: FullCodec + 'static, + OnEmpty: crate::traits::Get>, +{ + const METADATA: StorageEntryModifier = StorageEntryModifier::Optional; + + type Query = Result; + + fn from_optional_value_to_query(v: Option) -> Self::Query { + match v { + Some(v) => Ok(v), + None => OnEmpty::get(), + } + } + + fn from_query_to_optional_value(v: Self::Query) -> Option { + v.ok() + } +} + /// Implement QueryKindTrait with query being `Value` pub struct ValueQuery; impl QueryKindTrait for ValueQuery diff --git a/frame/support/src/storage/types/nmap.rs b/frame/support/src/storage/types/nmap.rs index 96d6f383ae117..dcbdac761fe15 100755 --- a/frame/support/src/storage/types/nmap.rs +++ b/frame/support/src/storage/types/nmap.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -142,6 +142,14 @@ where >::try_get(key) } + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + pub fn set + TupleToEncodedIter>( + key: KArg, + query: QueryKind::Query, + ) { + >::set(key, query) + } + /// Take a value from storage, removing it afterwards. pub fn take + TupleToEncodedIter>( key: KArg, @@ -173,14 +181,65 @@ where >::remove(key) } - /// Remove all values under the first key. + /// Remove all values starting with `partial_key` in the overlay and up to `limit` in the + /// backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear_prefix` instead"] pub fn remove_prefix(partial_key: KP, limit: Option) -> sp_io::KillStorageResult where Key: HasKeyPrefix, { + #[allow(deprecated)] >::remove_prefix(partial_key, limit) } + /// Attempt to remove items from the map matching a `partial_key` prefix. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map which match the `partial key`. If so, then the map may not be + /// empty when the resultant `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must be provided in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map and `partial_key`. Subsequent + /// calls operating on the same map/`partial_key` should always pass `Some`, and this should be + /// equal to the previous call result's `maybe_cursor` field. + pub fn clear_prefix( + partial_key: KP, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + Key: HasKeyPrefix, + { + >::clear_prefix( + partial_key, + limit, + maybe_cursor, + ) + } + /// Iterate over values that share the first key. pub fn iter_prefix_values(partial_key: KP) -> PrefixIterator where @@ -217,6 +276,8 @@ where } /// Mutate the item, only if an `Ok` value is returned. Deletes the item if mutated to a `None`. + /// `f` will always be called with an option representing if the storage item exists (`Some`) + /// or if the storage item does not exist (`None`), independent of the `QueryType`. pub fn try_mutate_exists(key: KArg, f: F) -> Result where KArg: EncodeLikeTuple + TupleToEncodedIter, @@ -275,9 +336,48 @@ where >::migrate_keys::<_>(key, hash_fns) } - /// Remove all value of the storage. + /// Remove all values in the overlay and up to `limit` in the backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear` instead"] pub fn remove_all(limit: Option) -> sp_io::KillStorageResult { - >::remove_all(limit) + #[allow(deprecated)] + >::remove_all(limit).into() + } + + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + pub fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> sp_io::MultiRemovalResults { + >::clear(limit, maybe_cursor) } /// Iter over all value of the storage. @@ -451,12 +551,14 @@ where MaxValues: Get>, { fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { + let docs = if cfg!(feature = "no-metadata-docs") { vec![] } else { docs }; + let entry = StorageEntryMetadata { name: Prefix::STORAGE_PREFIX, modifier: QueryKind::METADATA, ty: StorageEntryType::Map { key: scale_info::meta_type::(), - hashers: Key::HASHER_METADATA.iter().cloned().collect(), + hashers: Key::HASHER_METADATA.to_vec(), value: scale_info::meta_type::(), }, default: OnEmpty::get().encode(), @@ -519,7 +621,7 @@ mod test { use crate::{ hash::{StorageHasher as _, *}, metadata::{StorageEntryModifier, StorageHasher}, - storage::types::{Key, ValueQuery}, + storage::types::{Key as NMapKey, ValueQuery}, }; use sp_io::{hashing::twox_128, TestExternalities}; @@ -540,12 +642,12 @@ mod test { #[test] fn test_1_key() { - type A = StorageNMap, u32, OptionQuery>; + type A = StorageNMap, u32, OptionQuery>; type AValueQueryWithAnOnEmpty = - StorageNMap, u32, ValueQuery, ADefault>; - type B = StorageNMap, u32, ValueQuery>; - type C = StorageNMap, u8, ValueQuery>; - type WithLen = StorageNMap, Vec>; + StorageNMap, u32, ValueQuery, ADefault>; + type B = StorageNMap, u32, ValueQuery>; + type C = StorageNMap, u8, ValueQuery>; + type WithLen = StorageNMap, Vec>; TestExternalities::default().execute_with(|| { let mut k: Vec = vec![]; @@ -564,16 +666,14 @@ mod test { assert_eq!(AValueQueryWithAnOnEmpty::get((3,)), 10); { - crate::generate_storage_alias!(test, Foo => NMap< - Key<(u16, Blake2_128Concat)>, - u32 - >); + #[crate::storage_alias] + type Foo = StorageNMap), u32>; assert_eq!(Foo::contains_key((3,)), true); assert_eq!(Foo::get((3,)), Some(10)); } - A::swap::, _, _>((3,), (2,)); + A::swap::, _, _>((3,), (2,)); assert_eq!(A::contains_key((3,)), false); assert_eq!(A::contains_key((2,)), true); assert_eq!(A::get((3,)), None); @@ -661,7 +761,7 @@ mod test { A::insert((3,), 10); A::insert((4,), 10); - A::remove_all(None); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::contains_key((3,)), false); assert_eq!(A::contains_key((4,)), false); @@ -716,7 +816,7 @@ mod test { ] ); - WithLen::remove_all(None); + let _ = WithLen::clear(u32::max_value(), None); assert_eq!(WithLen::decode_len((3,)), None); WithLen::append((0,), 10); assert_eq!(WithLen::decode_len((0,)), Some(1)); @@ -727,26 +827,30 @@ mod test { fn test_2_keys() { type A = StorageNMap< Prefix, - (Key, Key), + (NMapKey, NMapKey), u32, OptionQuery, >; type AValueQueryWithAnOnEmpty = StorageNMap< Prefix, - (Key, Key), + (NMapKey, NMapKey), u32, ValueQuery, ADefault, >; - type B = StorageNMap, Key), u32, ValueQuery>; + type B = + StorageNMap, NMapKey), u32, ValueQuery>; type C = StorageNMap< Prefix, - (Key, Key), + (NMapKey, NMapKey), u8, ValueQuery, >; - type WithLen = - StorageNMap, Key), Vec>; + type WithLen = StorageNMap< + Prefix, + (NMapKey, NMapKey), + Vec, + >; TestExternalities::default().execute_with(|| { let mut k: Vec = vec![]; @@ -765,7 +869,10 @@ mod test { assert_eq!(A::get((3, 30)), Some(10)); assert_eq!(AValueQueryWithAnOnEmpty::get((3, 30)), 10); - A::swap::<(Key, Key), _, _>((3, 30), (2, 20)); + A::swap::<(NMapKey, NMapKey), _, _>( + (3, 30), + (2, 20), + ); assert_eq!(A::contains_key((3, 30)), false); assert_eq!(A::contains_key((2, 20)), true); assert_eq!(A::get((3, 30)), None); @@ -854,7 +961,7 @@ mod test { A::insert((3, 30), 10); A::insert((4, 40), 10); - A::remove_all(None); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::contains_key((3, 30)), false); assert_eq!(A::contains_key((4, 40)), false); @@ -915,7 +1022,7 @@ mod test { ] ); - WithLen::remove_all(None); + let _ = WithLen::clear(u32::max_value(), None); assert_eq!(WithLen::decode_len((3, 30)), None); WithLen::append((0, 100), 10); assert_eq!(WithLen::decode_len((0, 100)), Some(1)); @@ -933,32 +1040,48 @@ mod test { fn test_3_keys() { type A = StorageNMap< Prefix, - (Key, Key, Key), + ( + NMapKey, + NMapKey, + NMapKey, + ), u32, OptionQuery, >; type AValueQueryWithAnOnEmpty = StorageNMap< Prefix, - (Key, Key, Key), + ( + NMapKey, + NMapKey, + NMapKey, + ), u32, ValueQuery, ADefault, >; type B = StorageNMap< Prefix, - (Key, Key, Key), + (NMapKey, NMapKey, NMapKey), u32, ValueQuery, >; type C = StorageNMap< Prefix, - (Key, Key, Key), + ( + NMapKey, + NMapKey, + NMapKey, + ), u8, ValueQuery, >; type WithLen = StorageNMap< Prefix, - (Key, Key, Key), + ( + NMapKey, + NMapKey, + NMapKey, + ), Vec, >; @@ -981,7 +1104,11 @@ mod test { assert_eq!(AValueQueryWithAnOnEmpty::get((1, 10, 100)), 30); A::swap::< - (Key, Key, Key), + ( + NMapKey, + NMapKey, + NMapKey, + ), _, _, >((1, 10, 100), (2, 20, 200)); @@ -1070,7 +1197,7 @@ mod test { A::insert((3, 30, 300), 10); A::insert((4, 40, 400), 10); - A::remove_all(None); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::contains_key((3, 30, 300)), false); assert_eq!(A::contains_key((4, 40, 400)), false); @@ -1138,7 +1265,7 @@ mod test { ] ); - WithLen::remove_all(None); + let _ = WithLen::clear(u32::max_value(), None); assert_eq!(WithLen::decode_len((3, 30, 300)), None); WithLen::append((0, 100, 1000), 10); assert_eq!(WithLen::decode_len((0, 100, 1000)), Some(1)); diff --git a/frame/support/src/storage/types/value.rs b/frame/support/src/storage/types/value.rs index c5e7173bd0af7..f145e9fb30414 100644 --- a/frame/support/src/storage/types/value.rs +++ b/frame/support/src/storage/types/value.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -210,6 +210,8 @@ where OnEmpty: crate::traits::Get + 'static, { fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { + let docs = if cfg!(feature = "no-metadata-docs") { vec![] } else { docs }; + let entry = StorageEntryMetadata { name: Prefix::STORAGE_PREFIX, modifier: QueryKind::METADATA, diff --git a/frame/support/src/storage/unhashed.rs b/frame/support/src/storage/unhashed.rs index f700771b2d5cc..089230bc9bfdf 100644 --- a/frame/support/src/storage/unhashed.rs +++ b/frame/support/src/storage/unhashed.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,11 @@ pub fn get(key: &[u8]) -> Option { sp_io::storage::get(key).and_then(|val| { Decode::decode(&mut &val[..]).map(Some).unwrap_or_else(|_| { // TODO #3700: error should be handleable. - crate::runtime_print!("ERROR: Corrupted state at {:?}", key); + log::error!( + target: "runtime::storage", + "Corrupted state at {:?}", + key, + ); None }) }) @@ -34,7 +38,7 @@ pub fn get(key: &[u8]) -> Option { /// Return the value of the item in storage under `key`, or the type's default if there is no /// explicit entry. pub fn get_or_default(key: &[u8]) -> T { - get(key).unwrap_or_else(Default::default) + get(key).unwrap_or_default() } /// Return the value of the item in storage under `key`, or `default_value` if there is no @@ -66,7 +70,7 @@ pub fn take(key: &[u8]) -> Option { /// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage, /// the default for its type. pub fn take_or_default(key: &[u8]) -> T { - take(key).unwrap_or_else(Default::default) + take(key).unwrap_or_default() } /// Return the value of the item in storage under `key`, or `default_value` if there is no @@ -92,13 +96,66 @@ pub fn kill(key: &[u8]) { } /// Ensure keys with the given `prefix` have no entries in storage. +#[deprecated = "Use `clear_prefix` instead"] pub fn kill_prefix(prefix: &[u8], limit: Option) -> sp_io::KillStorageResult { + // TODO: Once the network has upgraded to include the new host functions, this code can be + // enabled. + // clear_prefix(prefix, limit).into() sp_io::storage::clear_prefix(prefix, limit) } +/// Partially clear the storage of all keys under a common `prefix`. +/// +/// # Limit +/// +/// A *limit* should always be provided through `maybe_limit`. This is one fewer than the +/// maximum number of backend iterations which may be done by this operation and as such +/// represents the maximum number of backend deletions which may happen. A *limit* of zero +/// implies that no keys will be deleted, though there may be a single iteration done. +/// +/// The limit can be used to partially delete storage items in case it is too large or costly +/// to delete all in a single operation. +/// +/// # Cursor +/// +/// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be +/// passed once (in the initial call) for any attempt to clear storage. In general, subsequent calls +/// operating on the same prefix should pass `Some` and this value should be equal to the +/// previous call result's `maybe_cursor` field. The only exception to this is when you can +/// guarantee that the subsequent call is in a new block; in this case the previous call's result +/// cursor need not be passed in an a `None` may be passed instead. This exception may be useful +/// then making this call solely from a block-hook such as `on_initialize`. +/// +/// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once the +/// resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. +/// +/// NOTE: After the initial call for any given child storage, it is important that no keys further +/// keys are inserted. If so, then they may or may not be deleted by subsequent calls. +/// +/// # Note +/// +/// Please note that keys which are residing in the overlay for the child are deleted without +/// counting towards the `limit`. +pub fn clear_prefix( + prefix: &[u8], + maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, +) -> sp_io::MultiRemovalResults { + // TODO: Once the network has upgraded to include the new host functions, this code can be + // enabled. + // sp_io::storage::clear_prefix(prefix, maybe_limit, maybe_cursor) + use sp_io::{KillStorageResult::*, MultiRemovalResults}; + #[allow(deprecated)] + let (maybe_cursor, i) = match kill_prefix(prefix, maybe_limit) { + AllRemoved(i) => (None, i), + SomeRemaining(i) => (Some(prefix.to_vec()), i), + }; + MultiRemovalResults { maybe_cursor, backend: i, unique: i, loops: i } +} + /// Get a Vec of bytes from storage. pub fn get_raw(key: &[u8]) -> Option> { - sp_io::storage::get(key) + sp_io::storage::get(key).map(|value| value.to_vec()) } /// Put a raw byte slice into storage. diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index 9c30c45c3e2e1..72ba8d775a1d3 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,273 +22,7 @@ use crate::{ storage::{StorageDecodeLength, StorageTryAppend}, traits::Get, }; -use codec::{Decode, Encode, MaxEncodedLen}; -use core::{ - ops::{Deref, Index, IndexMut}, - slice::SliceIndex, -}; -use sp_std::{convert::TryFrom, fmt, marker::PhantomData, prelude::*}; - -/// A weakly bounded vector. -/// -/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once -/// put into storage as a raw value, map or double-map. -/// -/// The length of the vec is not strictly bounded. Decoding a vec with more element that the bound -/// is accepted, and some method allow to bypass the restriction with warnings. -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct WeakBoundedVec(Vec, PhantomData); - -impl> Decode for WeakBoundedVec { - fn decode(input: &mut I) -> Result { - let inner = Vec::::decode(input)?; - Ok(Self::force_from(inner, Some("decode"))) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - Vec::::skip(input) - } -} - -impl WeakBoundedVec { - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: Vec) -> Self { - Self(t, Default::default()) - } - - /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an - /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can - /// be used. - /// - /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which - /// is not provided by the wrapper `WeakBoundedVec`. - pub fn into_inner(self) -> Vec { - self.0 - } - - /// Exactly the same semantics as [`Vec::remove`]. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { - self.0.remove(index) - } - - /// Exactly the same semantics as [`Vec::swap_remove`]. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn swap_remove(&mut self, index: usize) -> T { - self.0.swap_remove(index) - } - - /// Exactly the same semantics as [`Vec::retain`]. - pub fn retain bool>(&mut self, f: F) { - self.0.retain(f) - } - - /// Exactly the same semantics as [`Vec::get_mut`]. - pub fn get_mut>( - &mut self, - index: I, - ) -> Option<&mut >::Output> { - self.0.get_mut(index) - } -} - -impl> WeakBoundedVec { - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } - - /// Create `Self` from `t` without any checks. Logs warnings if the bound is not being - /// respected. The additional scope can be used to indicate where a potential overflow is - /// happening. - pub fn force_from(t: Vec, scope: Option<&'static str>) -> Self { - if t.len() > Self::bound() { - log::warn!( - target: crate::LOG_TARGET, - "length of a bounded vector in scope {} is not respected.", - scope.unwrap_or("UNKNOWN"), - ); - } - - Self::unchecked_from(t) - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if `index > len`. - pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), ()> { - if self.len() < Self::bound() { - self.0.insert(index, element); - Ok(()) - } else { - Err(()) - } - } - - /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if the new capacity exceeds isize::MAX bytes. - pub fn try_push(&mut self, element: T) -> Result<(), ()> { - if self.len() < Self::bound() { - self.0.push(element); - Ok(()) - } else { - Err(()) - } - } -} - -impl Default for WeakBoundedVec { - fn default() -> Self { - // the bound cannot be below 0, which is satisfied by an empty vector - Self::unchecked_from(Vec::default()) - } -} - -#[cfg(feature = "std")] -impl fmt::Debug for WeakBoundedVec -where - T: fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("WeakBoundedVec").field(&self.0).field(&Self::bound()).finish() - } -} - -impl Clone for WeakBoundedVec -where - T: Clone, -{ - fn clone(&self) -> Self { - // bound is retained - Self::unchecked_from(self.0.clone()) - } -} - -impl> TryFrom> for WeakBoundedVec { - type Error = (); - fn try_from(t: Vec) -> Result { - if t.len() <= Self::bound() { - // explicit check just above - Ok(Self::unchecked_from(t)) - } else { - Err(()) - } - } -} - -// It is okay to give a non-mutable reference of the inner vec to anyone. -impl AsRef> for WeakBoundedVec { - fn as_ref(&self) -> &Vec { - &self.0 - } -} - -impl AsRef<[T]> for WeakBoundedVec { - fn as_ref(&self) -> &[T] { - &self.0 - } -} - -impl AsMut<[T]> for WeakBoundedVec { - fn as_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} - -// will allow for immutable all operations of `Vec` on `WeakBoundedVec`. -impl Deref for WeakBoundedVec { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. -impl Index for WeakBoundedVec -where - I: SliceIndex<[T]>, -{ - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - self.0.index(index) - } -} - -impl IndexMut for WeakBoundedVec -where - I: SliceIndex<[T]>, -{ - #[inline] - fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.0.index_mut(index) - } -} - -impl sp_std::iter::IntoIterator for WeakBoundedVec { - type Item = T; - type IntoIter = sp_std::vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl codec::DecodeLength for WeakBoundedVec { - fn len(self_encoded: &[u8]) -> Result { - // `WeakBoundedVec` stored just a `Vec`, thus the length is at the beginning in - // `Compact` form, and same implementation as `Vec` can be used. - as codec::DecodeLength>::len(self_encoded) - } -} - -// NOTE: we could also implement this as: -// impl, S2: Get> PartialEq> for WeakBoundedVec to allow comparison of bounded vectors with different bounds. -impl PartialEq for WeakBoundedVec -where - T: PartialEq, -{ - fn eq(&self, rhs: &Self) -> bool { - self.0 == rhs.0 - } -} - -impl> PartialEq> for WeakBoundedVec { - fn eq(&self, other: &Vec) -> bool { - &self.0 == other - } -} - -impl Eq for WeakBoundedVec where T: Eq {} +pub use sp_runtime::WeakBoundedVec; impl StorageDecodeLength for WeakBoundedVec {} @@ -298,56 +32,31 @@ impl> StorageTryAppend for WeakBoundedVec { } } -impl MaxEncodedLen for WeakBoundedVec -where - T: MaxEncodedLen, - S: Get, - WeakBoundedVec: Encode, -{ - fn max_encoded_len() -> usize { - // WeakBoundedVec encodes like Vec which encodes like [T], which is a compact u32 - // plus each item in the slice: - // https://substrate.dev/rustdocs/v3.0.0/src/parity_scale_codec/codec.rs.html#798-808 - codec::Compact(S::get()) - .encoded_size() - .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) - } -} - #[cfg(test)] pub mod test { use super::*; use crate::Twox128; + use frame_support::traits::ConstU32; use sp_io::TestExternalities; - use sp_std::convert::TryInto; - - crate::parameter_types! { - pub const Seven: u32 = 7; - pub const Four: u32 = 4; - } - - crate::generate_storage_alias! { Prefix, Foo => Value> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), WeakBoundedVec> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), WeakBoundedVec> - } - #[test] - fn try_append_is_correct() { - assert_eq!(WeakBoundedVec::::bound(), 7); - } + #[crate::storage_alias] + type Foo = StorageValue>>; + #[crate::storage_alias] + type FooMap = StorageMap>>; + #[crate::storage_alias] + type FooDoubleMap = + StorageDoubleMap>>; #[test] fn decode_len_works() { TestExternalities::default().execute_with(|| { - let bounded: WeakBoundedVec = vec![1, 2, 3].try_into().unwrap(); + let bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); Foo::put(bounded); assert_eq!(Foo::decode_len().unwrap(), 3); }); TestExternalities::default().execute_with(|| { - let bounded: WeakBoundedVec = vec![1, 2, 3].try_into().unwrap(); + let bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); FooMap::insert(1, bounded); assert_eq!(FooMap::decode_len(1).unwrap(), 3); assert!(FooMap::decode_len(0).is_none()); @@ -355,7 +64,7 @@ pub mod test { }); TestExternalities::default().execute_with(|| { - let bounded: WeakBoundedVec = vec![1, 2, 3].try_into().unwrap(); + let bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); FooDoubleMap::insert(1, 1, bounded); assert_eq!(FooDoubleMap::decode_len(1, 1).unwrap(), 3); assert!(FooDoubleMap::decode_len(2, 1).is_none()); @@ -363,66 +72,4 @@ pub mod test { assert!(FooDoubleMap::decode_len(2, 2).is_none()); }); } - - #[test] - fn try_insert_works() { - let mut bounded: WeakBoundedVec = vec![1, 2, 3].try_into().unwrap(); - bounded.try_insert(1, 0).unwrap(); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - - assert!(bounded.try_insert(0, 9).is_err()); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - } - - #[test] - #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] - fn try_inert_panics_if_oob() { - let mut bounded: WeakBoundedVec = vec![1, 2, 3].try_into().unwrap(); - bounded.try_insert(9, 0).unwrap(); - } - - #[test] - fn try_push_works() { - let mut bounded: WeakBoundedVec = vec![1, 2, 3].try_into().unwrap(); - bounded.try_push(0).unwrap(); - assert_eq!(*bounded, vec![1, 2, 3, 0]); - - assert!(bounded.try_push(9).is_err()); - } - - #[test] - fn deref_coercion_works() { - let bounded: WeakBoundedVec = vec![1, 2, 3].try_into().unwrap(); - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded: WeakBoundedVec = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded.try_mutate(|v| v.push(8)).is_none()); - } - - #[test] - fn slice_indexing_works() { - let bounded: WeakBoundedVec = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - assert_eq!(&bounded[0..=2], &[1, 2, 3]); - } - - #[test] - fn vec_eq_works() { - let bounded: WeakBoundedVec = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); - } - - #[test] - fn too_big_succeed_to_decode() { - let v: Vec = vec![1, 2, 3, 4, 5]; - let w = WeakBoundedVec::::decode(&mut &v.encode()[..]).unwrap(); - assert_eq!(v, *w); - } } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index efb5559ed0622..63c86c1f68459 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,20 +22,21 @@ pub mod tokens; pub use tokens::{ currency::{ - Currency, LockIdentifier, LockableCurrency, NamedReservableCurrency, ReservableCurrency, - VestingSchedule, + ActiveIssuanceOf, Currency, LockIdentifier, LockableCurrency, NamedReservableCurrency, + ReservableCurrency, TotalIssuanceOf, VestingSchedule, }, fungible, fungibles, imbalance::{Imbalance, OnUnbalanced, SignedImbalance}, - BalanceStatus, ExistenceRequirement, WithdrawReasons, + nonfungible, nonfungibles, BalanceStatus, ExistenceRequirement, Locker, WithdrawReasons, }; mod members; #[allow(deprecated)] pub use members::{AllowAll, DenyAll, Filter}; pub use members::{ - AsContains, ChangeMembers, Contains, ContainsLengthBound, Everything, InitializeMembers, - IsInVec, Nothing, SortedMembers, + AsContains, ChangeMembers, Contains, ContainsLengthBound, ContainsPair, Everything, + EverythingBut, FromContainsPair, InitializeMembers, InsideBoth, IsInVec, Nothing, + SortedMembers, TheseExcept, }; mod validation; @@ -45,15 +46,26 @@ pub use validation::{ ValidatorSetWithIdentification, VerifySeal, }; +mod error; +pub use error::PalletError; + mod filter; -pub use filter::{ClearFilterGuard, FilterStack, FilterStackGuard, InstanceFilter, IntegrityTest}; +pub use filter::{ClearFilterGuard, FilterStack, FilterStackGuard, InstanceFilter}; mod misc; pub use misc::{ - Backing, ConstU32, EnsureInherentsAreFirst, EstimateCallFee, ExecuteBlock, ExtrinsicCall, Get, - GetBacking, GetDefault, HandleLifetime, IsSubType, IsType, Len, OffchainWorker, - OnKilledAccount, OnNewAccount, SameOrOther, Time, TryDrop, UnixTime, + defensive_prelude::{self, *}, + Backing, ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, + ConstU32, ConstU64, ConstU8, DefensiveMax, DefensiveMin, DefensiveSaturating, + DefensiveTruncateFrom, EnsureInherentsAreFirst, EqualPrivilegeOnly, EstimateCallFee, + ExecuteBlock, ExtrinsicCall, Get, GetBacking, GetDefault, HandleLifetime, IsSubType, IsType, + Len, OffchainWorker, OnKilledAccount, OnNewAccount, PrivilegeCmp, SameOrOther, Time, + TryCollect, TryDrop, TypedGet, UnixTime, WrapperKeepOpaque, WrapperOpaque, }; +#[allow(deprecated)] +pub use misc::{PreimageProvider, PreimageRecipient}; +#[doc(hidden)] +pub use misc::{DEFENSIVE_OP_INTERNAL_ERROR, DEFENSIVE_OP_PUBLIC_ERROR}; mod stored_map; pub use stored_map::{StorageMapShim, StoredMap}; @@ -62,27 +74,51 @@ pub use randomness::Randomness; mod metadata; pub use metadata::{ - CallMetadata, GetCallMetadata, GetCallName, GetStorageVersion, PalletInfo, PalletInfoAccess, - StorageVersion, STORAGE_VERSION_STORAGE_KEY_POSTFIX, + CallMetadata, CrateVersion, GetCallMetadata, GetCallName, GetStorageVersion, PalletInfo, + PalletInfoAccess, PalletInfoData, PalletsInfoAccess, StorageVersion, + STORAGE_VERSION_STORAGE_KEY_POSTFIX, }; mod hooks; #[cfg(feature = "std")] pub use hooks::GenesisBuild; pub use hooks::{ - Hooks, OnFinalize, OnGenesis, OnIdle, OnInitialize, OnRuntimeUpgrade, OnTimestampSet, + Hooks, IntegrityTest, OnFinalize, OnGenesis, OnIdle, OnInitialize, OnRuntimeUpgrade, + OnTimestampSet, }; -#[cfg(feature = "try-runtime")] -pub use hooks::{OnRuntimeUpgradeHelpersExt, ON_RUNTIME_UPGRADE_PREFIX}; pub mod schedule; mod storage; pub use storage::{ Instance, PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, StorageInstance, + TrackedStorageKey, WhitelistedStorageKeys, }; mod dispatch; -pub use dispatch::{EnsureOrigin, OriginTrait, UnfilteredDispatchable}; +#[allow(deprecated)] +pub use dispatch::EnsureOneOf; +pub use dispatch::{ + AsEnsureOriginWithArg, CallerTrait, EitherOf, EitherOfDiverse, EnsureOrigin, + EnsureOriginEqualOrHigherPrivilege, EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, + OriginTrait, TryMapSuccess, UnfilteredDispatchable, +}; mod voting; -pub use voting::{CurrencyToVote, SaturatingCurrencyToVote, U128CurrencyToVote}; +pub use voting::{ + ClassCountOf, CurrencyToVote, PollStatus, Polling, SaturatingCurrencyToVote, + U128CurrencyToVote, VoteTally, +}; + +mod preimages; +pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, StorePreimage}; + +mod messages; +pub use messages::{ + EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, ProcessMessageError, + ServiceQueues, +}; + +#[cfg(feature = "try-runtime")] +mod try_runtime; +#[cfg(feature = "try-runtime")] +pub use try_runtime::{Select as TryStateSelect, TryState}; diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index f82628ede18cc..36ddf5b507c0c 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,25 +17,297 @@ //! Traits for dealing with dispatching calls and the origin from which they are dispatched. -use crate::dispatch::DispatchResultWithPostInfo; -use sp_runtime::traits::BadOrigin; +use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; +use codec::MaxEncodedLen; +use sp_runtime::{ + traits::{BadOrigin, Get, Member, Morph, TryMorph}, + Either, +}; +use sp_std::{cmp::Ordering, marker::PhantomData}; + +use super::misc; /// Some sort of check on the origin is performed by this object. pub trait EnsureOrigin { /// A return type. type Success; + /// Perform the origin check. fn ensure_origin(o: OuterOrigin) -> Result { Self::try_origin(o).map_err(|_| BadOrigin) } + /// Perform the origin check. fn try_origin(o: OuterOrigin) -> Result; + /// Returns an outer origin capable of passing `try_origin` check. + /// + /// NOTE: This should generally *NOT* be reimplemented. Instead implement + /// `try_successful_origin`. + /// + /// ** Should be used for benchmarking only!!! ** + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> OuterOrigin { + Self::try_successful_origin().expect("No origin exists which can satisfy the guard") + } + + /// Attept to get an outer origin capable of passing `try_origin` check. May return `Err` if it + /// is impossible. Default implementation just uses `successful_origin()`. + /// + /// ** Should be used for benchmarking only!!! ** + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(Self::successful_origin()) + } +} + +/// [`EnsureOrigin`] implementation that always fails. +pub struct NeverEnsureOrigin(sp_std::marker::PhantomData); +impl EnsureOrigin for NeverEnsureOrigin { + type Success = Success; + fn try_origin(o: OO) -> Result { + Err(o) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Err(()) + } +} + +/// [`EnsureOrigin`] implementation that checks that an origin has equal or higher privilege +/// compared to the expected `Origin`. +/// +/// It will take the shortcut of comparing the incoming origin with the expected `Origin` and if +/// both are the same the origin is accepted. +/// +/// # Example +/// +/// ```rust +/// # use frame_support::traits::{EnsureOriginEqualOrHigherPrivilege, PrivilegeCmp, EnsureOrigin as _}; +/// # use sp_runtime::traits::{parameter_types, Get}; +/// # use sp_std::cmp::Ordering; +/// +/// #[derive(Eq, PartialEq, Debug)] +/// pub enum Origin { +/// Root, +/// SomethingBelowRoot, +/// NormalUser, +/// } +/// +/// struct OriginPrivilegeCmp; +/// +/// impl PrivilegeCmp for OriginPrivilegeCmp { +/// fn cmp_privilege(left: &Origin, right: &Origin) -> Option { +/// match (left, right) { +/// (Origin::Root, Origin::Root) => Some(Ordering::Equal), +/// (Origin::Root, _) => Some(Ordering::Greater), +/// (Origin::SomethingBelowRoot, Origin::SomethingBelowRoot) => Some(Ordering::Equal), +/// (Origin::SomethingBelowRoot, Origin::Root) => Some(Ordering::Less), +/// (Origin::SomethingBelowRoot, Origin::NormalUser) => Some(Ordering::Greater), +/// (Origin::NormalUser, Origin::NormalUser) => Some(Ordering::Equal), +/// (Origin::NormalUser, _) => Some(Ordering::Less), +/// } +/// } +/// } +/// +/// parameter_types! { +/// pub const ExpectedOrigin: Origin = Origin::SomethingBelowRoot; +/// } +/// +/// type EnsureOrigin = EnsureOriginEqualOrHigherPrivilege; +/// +/// // `Root` has an higher privilege as our expected origin. +/// assert!(EnsureOrigin::ensure_origin(Origin::Root).is_ok()); +/// // `SomethingBelowRoot` is exactly the expected origin. +/// assert!(EnsureOrigin::ensure_origin(Origin::SomethingBelowRoot).is_ok()); +/// // The `NormalUser` origin is not allowed. +/// assert!(EnsureOrigin::ensure_origin(Origin::NormalUser).is_err()); +/// ``` +pub struct EnsureOriginEqualOrHigherPrivilege( + sp_std::marker::PhantomData<(Origin, PrivilegeCmp)>, +); + +impl EnsureOrigin + for EnsureOriginEqualOrHigherPrivilege +where + Origin: Get, + OuterOrigin: Eq, + PrivilegeCmp: misc::PrivilegeCmp, +{ + type Success = (); + + fn try_origin(o: OuterOrigin) -> Result { + let expected_origin = Origin::get(); + + // If this is the expected origin, it has the same privilege. + if o == expected_origin { + return Ok(()) + } + + let cmp = PrivilegeCmp::cmp_privilege(&o, &expected_origin); + + match cmp { + Some(Ordering::Equal) | Some(Ordering::Greater) => Ok(()), + None | Some(Ordering::Less) => Err(o), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(Origin::get()) + } +} + +/// Some sort of check on the origin is performed by this object. +pub trait EnsureOriginWithArg { + /// A return type. + type Success; + + /// Perform the origin check. + fn ensure_origin(o: OuterOrigin, a: &Argument) -> Result { + Self::try_origin(o, a).map_err(|_| BadOrigin) + } + + /// Perform the origin check, returning the origin value if unsuccessful. This allows chaining. + fn try_origin(o: OuterOrigin, a: &Argument) -> Result; + + /// Returns an outer origin capable of passing `try_origin` check. + /// + /// NOTE: This should generally *NOT* be reimplemented. Instead implement + /// `try_successful_origin`. + /// + /// ** Should be used for benchmarking only!!! ** + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin(a: &Argument) -> OuterOrigin { + Self::try_successful_origin(a).expect("No origin exists which can satisfy the guard") + } + + /// Attept to get an outer origin capable of passing `try_origin` check. May return `Err` if it + /// is impossible. Default implementation just uses `successful_origin()`. + /// + /// ** Should be used for benchmarking only!!! ** + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &Argument) -> Result { + Ok(Self::successful_origin(a)) + } +} + +pub struct AsEnsureOriginWithArg(sp_std::marker::PhantomData); +impl> + EnsureOriginWithArg for AsEnsureOriginWithArg +{ + /// A return type. + type Success = EO::Success; + + /// Perform the origin check. + fn ensure_origin(o: OuterOrigin, _: &Argument) -> Result { + EO::ensure_origin(o) + } + + /// Perform the origin check, returning the origin value if unsuccessful. This allows chaining. + fn try_origin(o: OuterOrigin, _: &Argument) -> Result { + EO::try_origin(o) + } + /// Returns an outer origin capable of passing `try_origin` check. /// /// ** Should be used for benchmarking only!!! ** #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> OuterOrigin; + fn try_successful_origin(_: &Argument) -> Result { + EO::try_successful_origin() + } +} + +/// A derivative `EnsureOrigin` implementation. It mutates the `Success` result of an `Original` +/// implementation with a given `Mutator`. +pub struct MapSuccess(PhantomData<(Original, Mutator)>); +impl, Mutator: Morph> EnsureOrigin + for MapSuccess +{ + type Success = Mutator::Outcome; + fn try_origin(o: O) -> Result { + Ok(Mutator::morph(Original::try_origin(o)?)) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Original::try_successful_origin() + } +} + +/// A derivative `EnsureOrigin` implementation. It mutates the `Success` result of an `Original` +/// implementation with a given `Mutator`, allowing the possibility of an error to be returned +/// from the mutator. +/// +/// NOTE: This is strictly worse performance than `MapSuccess` since it clones the original origin +/// value. If possible, use `MapSuccess` instead. +pub struct TryMapSuccess(PhantomData<(Orig, Mutator)>); +impl, Mutator: TryMorph> EnsureOrigin + for TryMapSuccess +{ + type Success = Mutator::Outcome; + fn try_origin(o: O) -> Result { + let orig = o.clone(); + Mutator::try_morph(Original::try_origin(o)?).map_err(|()| orig) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Original::try_successful_origin() + } +} + +/// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L` +/// and `R`, with them combined using an `Either` type. +/// +/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. +/// +/// Successful origin is derived from the left side. +pub struct EitherOfDiverse(sp_std::marker::PhantomData<(L, R)>); +impl, R: EnsureOrigin> + EnsureOrigin for EitherOfDiverse +{ + type Success = Either; + fn try_origin(o: OuterOrigin) -> Result { + L::try_origin(o) + .map_or_else(|o| R::try_origin(o).map(Either::Right), |o| Ok(Either::Left(o))) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + L::try_successful_origin().or_else(|()| R::try_successful_origin()) + } +} + +/// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L` +/// and `R`, with them combined using an `Either` type. +/// +/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. +/// +/// Successful origin is derived from the left side. +#[deprecated = "Use `EitherOfDiverse` instead"] +pub type EnsureOneOf = EitherOfDiverse; + +/// "OR gate" implementation of `EnsureOrigin`, `Success` type for both `L` and `R` must +/// be equal. +/// +/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. +/// +/// Successful origin is derived from the left side. +pub struct EitherOf(sp_std::marker::PhantomData<(L, R)>); +impl< + OuterOrigin, + L: EnsureOrigin, + R: EnsureOrigin, + > EnsureOrigin for EitherOf +{ + type Success = L::Success; + fn try_origin(o: OuterOrigin) -> Result { + L::try_origin(o).or_else(|o| R::try_origin(o)) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + L::try_successful_origin().or_else(|()| R::try_successful_origin()) + } } /// Type that can be dispatched with an origin but without checking the origin filter. @@ -43,20 +315,32 @@ pub trait EnsureOrigin { /// Implemented for pallet dispatchable type by `decl_module` and for runtime dispatchable by /// `construct_runtime`. pub trait UnfilteredDispatchable { - /// The origin type of the runtime, (i.e. `frame_system::Config::Origin`). - type Origin; + /// The origin type of the runtime, (i.e. `frame_system::Config::RuntimeOrigin`). + type RuntimeOrigin; /// Dispatch this call but do not check the filter in origin. - fn dispatch_bypass_filter(self, origin: Self::Origin) -> DispatchResultWithPostInfo; + fn dispatch_bypass_filter(self, origin: Self::RuntimeOrigin) -> DispatchResultWithPostInfo; } -/// Methods available on `frame_system::Config::Origin`. +/// The trait implemented by the overarching enumeration of the different pallets' origins. +/// Unlike `OriginTrait` impls, this does not include any kind of dispatch/call filter. Also, this +/// trait is more flexible in terms of how it can be used: it is a `Parameter` and `Member`, so it +/// can be used as dispatchable parameters as well as in storage items. +pub trait CallerTrait: Parameter + Member + From> { + /// Extract the signer from the message if it is a `Signed` origin. + fn into_system(self) -> Option>; + + /// Extract a reference to the system-level `RawOrigin` if it is that. + fn as_system_ref(&self) -> Option<&RawOrigin>; +} + +/// Methods available on `frame_system::Config::RuntimeOrigin`. pub trait OriginTrait: Sized { /// Runtime call type, as in `frame_system::Config::Call` type Call; /// The caller origin, overarching type of all pallets origins. - type PalletsOrigin; + type PalletsOrigin: Into + CallerTrait + MaxEncodedLen; /// The AccountId used across the system. type AccountId; @@ -70,24 +354,122 @@ pub trait OriginTrait: Sized { /// Replace the caller with caller from the other origin fn set_caller_from(&mut self, other: impl Into); - /// Filter the call, if false then call is filtered out. + /// Filter the call if caller is not root, if false is returned then the call must be filtered + /// out. + /// + /// For root origin caller, the filters are bypassed and true is returned. fn filter_call(&self, call: &Self::Call) -> bool; - /// Get the caller. + /// Get a reference to the caller (`CallerTrait` impl). fn caller(&self) -> &Self::PalletsOrigin; + /// Consume `self` and return the caller. + fn into_caller(self) -> Self::PalletsOrigin; + /// Do something with the caller, consuming self but returning it if the caller was unused. fn try_with_caller( self, f: impl FnOnce(Self::PalletsOrigin) -> Result, ) -> Result; - /// Create with system none origin and `frame-system::Config::BaseCallFilter`. + /// Create with system none origin and `frame_system::Config::BaseCallFilter`. fn none() -> Self; - /// Create with system root origin and no filter. + /// Create with system root origin and `frame_system::Config::BaseCallFilter`. fn root() -> Self; - /// Create with system signed origin and `frame-system::Config::BaseCallFilter`. + /// Create with system signed origin and `frame_system::Config::BaseCallFilter`. fn signed(by: Self::AccountId) -> Self; + + /// Extract the signer from the message if it is a `Signed` origin. + fn as_signed(self) -> Option { + self.into_caller().into_system().and_then(|s| { + if let RawOrigin::Signed(who) = s { + Some(who) + } else { + None + } + }) + } + + /// Extract a reference to the sytsem origin, if that's what the caller is. + fn as_system_ref(&self) -> Option<&RawOrigin> { + self.caller().as_system_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::{ConstBool, ConstU8, TypedGet}; + use std::marker::PhantomData; + + struct EnsureSuccess(PhantomData); + struct EnsureFail(PhantomData); + + impl EnsureOrigin<()> for EnsureSuccess { + type Success = V::Type; + fn try_origin(_: ()) -> Result { + Ok(V::get()) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result<(), ()> { + Ok(()) + } + } + + impl EnsureOrigin<()> for EnsureFail { + type Success = T; + fn try_origin(_: ()) -> Result { + Err(()) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result<(), ()> { + Err(()) + } + } + + #[test] + fn either_of_diverse_works() { + assert_eq!( + EitherOfDiverse::< + EnsureSuccess>, + EnsureSuccess>, + >::try_origin(()).unwrap().left(), + Some(true) + ); + assert_eq!( + EitherOfDiverse::>, EnsureFail>::try_origin(()) + .unwrap() + .left(), + Some(true) + ); + assert_eq!( + EitherOfDiverse::, EnsureSuccess>>::try_origin(()) + .unwrap() + .right(), + Some(0u8) + ); + assert!(EitherOfDiverse::, EnsureFail>::try_origin(()).is_err()); + } + + #[test] + fn either_of_works() { + assert_eq!( + EitherOf::< + EnsureSuccess>, + EnsureSuccess>, + >::try_origin(()).unwrap(), + true + ); + assert_eq!( + EitherOf::>, EnsureFail>::try_origin(()).unwrap(), + true + ); + assert_eq!( + EitherOf::, EnsureSuccess>>::try_origin(()).unwrap(), + false + ); + assert!(EitherOf::, EnsureFail>::try_origin(()).is_err()); + } } diff --git a/frame/support/src/traits/error.rs b/frame/support/src/traits/error.rs new file mode 100644 index 0000000000000..8e26891669e65 --- /dev/null +++ b/frame/support/src/traits/error.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Traits for describing and constraining pallet error types. +use codec::{Compact, Decode, Encode}; +use sp_std::marker::PhantomData; + +/// Trait indicating that the implementing type is going to be included as a field in a variant of +/// the `#[pallet::error]` enum type. +/// +/// ## Notes +/// +/// The pallet error enum has a maximum encoded size as defined by +/// [`frame_support::MAX_MODULE_ERROR_ENCODED_SIZE`]. If the pallet error type exceeds this size +/// limit, a static assertion during compilation will fail. The compilation error will be in the +/// format of `error[E0080]: evaluation of constant value failed` due to the usage of +/// const assertions. +pub trait PalletError: Encode + Decode { + /// The maximum encoded size for the implementing type. + /// + /// This will be used to check whether the pallet error type is less than or equal to + /// [`frame_support::MAX_MODULE_ERROR_ENCODED_SIZE`], and if it is, a compilation error will be + /// thrown. + const MAX_ENCODED_SIZE: usize; +} + +macro_rules! impl_for_types { + (size: $size:expr, $($typ:ty),+) => { + $( + impl PalletError for $typ { + const MAX_ENCODED_SIZE: usize = $size; + } + )+ + }; +} + +impl_for_types!(size: 0, (), crate::Never); +impl_for_types!(size: 1, u8, i8, bool); +impl_for_types!(size: 2, u16, i16, Compact); +impl_for_types!(size: 4, u32, i32, Compact); +impl_for_types!(size: 5, Compact); +impl_for_types!(size: 8, u64, i64); +impl_for_types!(size: 9, Compact); +// Contains a u64 for secs and u32 for nanos, hence 12 bytes +impl_for_types!(size: 12, core::time::Duration); +impl_for_types!(size: 16, u128, i128); +impl_for_types!(size: 17, Compact); + +impl PalletError for PhantomData { + const MAX_ENCODED_SIZE: usize = 0; +} + +impl PalletError for core::ops::Range { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_mul(2); +} + +impl PalletError for [T; N] { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_mul(N); +} + +impl PalletError for Option { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_add(1); +} + +impl PalletError for Result { + const MAX_ENCODED_SIZE: usize = if T::MAX_ENCODED_SIZE > E::MAX_ENCODED_SIZE { + T::MAX_ENCODED_SIZE + } else { + E::MAX_ENCODED_SIZE + } + .saturating_add(1); +} + +#[impl_trait_for_tuples::impl_for_tuples(1, 18)] +impl PalletError for Tuple { + const MAX_ENCODED_SIZE: usize = { + let mut size = 0_usize; + for_tuples!( #(size = size.saturating_add(Tuple::MAX_ENCODED_SIZE);)* ); + size + }; +} diff --git a/frame/support/src/traits/filter.rs b/frame/support/src/traits/filter.rs index c67ffc3c3a11e..cdd82a3124e63 100644 --- a/frame/support/src/traits/filter.rs +++ b/frame/support/src/traits/filter.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -180,17 +180,6 @@ macro_rules! impl_filter_stack { } } -/// Type that provide some integrity tests. -/// -/// This implemented for modules by `decl_module`. -#[impl_trait_for_tuples::impl_for_tuples(30)] -pub trait IntegrityTest { - /// Run integrity test. - /// - /// The test is not executed in a externalities provided environment. - fn integrity_test() {} -} - #[cfg(test)] pub mod test_impl_filter_stack { use super::*; diff --git a/frame/support/src/traits/hooks.rs b/frame/support/src/traits/hooks.rs index adba88e5acbf3..3415682c0b382 100644 --- a/frame/support/src/traits/hooks.rs +++ b/frame/support/src/traits/hooks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,9 +17,13 @@ //! Traits for hooking tasks to events in a blockchain's lifecycle. +use crate::weights::Weight; use impl_trait_for_tuples::impl_for_tuples; -use sp_arithmetic::traits::Saturating; -use sp_runtime::traits::{AtLeast32BitUnsigned, MaybeSerializeDeserialize}; +use sp_runtime::traits::AtLeast32BitUnsigned; +use sp_std::prelude::*; + +#[cfg(all(feature = "try-runtime", test))] +use codec::{Decode, Encode}; /// The block initialization trait. /// @@ -33,15 +37,17 @@ pub trait OnInitialize { /// NOTE: This function is called BEFORE ANY extrinsic in a block is applied, /// including inherent extrinsics. Hence for instance, if you runtime includes /// `pallet_timestamp`, the `timestamp` is not yet up to date at this point. - fn on_initialize(_n: BlockNumber) -> crate::weights::Weight { - 0 + fn on_initialize(_n: BlockNumber) -> Weight { + Weight::zero() } } -#[impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] impl OnInitialize for Tuple { - fn on_initialize(n: BlockNumber) -> crate::weights::Weight { - let mut weight = 0; + fn on_initialize(n: BlockNumber) -> Weight { + let mut weight = Weight::zero(); for_tuples!( #( weight = weight.saturating_add(Tuple::on_initialize(n.clone())); )* ); weight } @@ -50,7 +56,9 @@ impl OnInitialize for Tuple { /// The block finalization trait. /// /// Implementing this lets you express what should happen for your pallet when the block is ending. -#[impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] pub trait OnFinalize { /// The block is being finalized. Implement to have something happen. /// @@ -71,30 +79,27 @@ pub trait OnIdle { /// /// NOTE: This function is called AFTER ALL extrinsics - including inherent extrinsics - /// in a block are applied but before `on_finalize` is executed. - fn on_idle( - _n: BlockNumber, - _remaining_weight: crate::weights::Weight, - ) -> crate::weights::Weight { - 0 + fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { + Weight::zero() } } -#[impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] impl OnIdle for Tuple { - fn on_idle(n: BlockNumber, remaining_weight: crate::weights::Weight) -> crate::weights::Weight { - let on_idle_functions: &[fn( - BlockNumber, - crate::weights::Weight, - ) -> crate::weights::Weight] = &[for_tuples!( #( Tuple::on_idle ),* )]; - let mut weight = 0; + fn on_idle(n: BlockNumber, remaining_weight: Weight) -> Weight { + let on_idle_functions: &[fn(BlockNumber, Weight) -> Weight] = + &[for_tuples!( #( Tuple::on_idle ),* )]; + let mut weight = Weight::zero(); let len = on_idle_functions.len(); let start_index = n % (len as u32).into(); let start_index = start_index.try_into().ok().expect( "`start_index % len` always fits into `usize`, because `len` can be in maximum `usize::MAX`; qed" ); - for on_idle in on_idle_functions.iter().cycle().skip(start_index).take(len) { + for on_idle_fn in on_idle_functions.iter().cycle().skip(start_index).take(len) { let adjusted_remaining_weight = remaining_weight.saturating_sub(weight); - weight = weight.saturating_add(on_idle(n, adjusted_remaining_weight)); + weight = weight.saturating_add(on_idle_fn(n, adjusted_remaining_weight)); } weight } @@ -105,53 +110,14 @@ impl OnIdle for Tuple { /// Implementing this trait for a pallet let's you express operations that should /// happen at genesis. It will be called in an externalities provided environment and /// will see the genesis state after all pallets have written their genesis state. -#[impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] pub trait OnGenesis { /// Something that should happen at genesis. fn on_genesis() {} } -/// Prefix to be used (optionally) for implementing [`OnRuntimeUpgradeHelpersExt::storage_key`]. -#[cfg(feature = "try-runtime")] -pub const ON_RUNTIME_UPGRADE_PREFIX: &[u8] = b"__ON_RUNTIME_UPGRADE__"; - -/// Some helper functions for [`OnRuntimeUpgrade`] during `try-runtime` testing. -#[cfg(feature = "try-runtime")] -pub trait OnRuntimeUpgradeHelpersExt { - /// Generate a storage key unique to this runtime upgrade. - /// - /// This can be used to communicate data from pre-upgrade to post-upgrade state and check - /// them. See [`Self::set_temp_storage`] and [`Self::get_temp_storage`]. - #[cfg(feature = "try-runtime")] - fn storage_key(ident: &str) -> [u8; 32] { - crate::storage::storage_prefix(ON_RUNTIME_UPGRADE_PREFIX, ident.as_bytes()) - } - - /// Get temporary storage data written by [`Self::set_temp_storage`]. - /// - /// Returns `None` if either the data is unavailable or un-decodable. - /// - /// A `at` storage identifier must be provided to indicate where the storage is being read from. - #[cfg(feature = "try-runtime")] - fn get_temp_storage(at: &str) -> Option { - sp_io::storage::get(&Self::storage_key(at)) - .and_then(|bytes| codec::Decode::decode(&mut &*bytes).ok()) - } - - /// Write some temporary data to a specific storage that can be read (potentially in - /// post-upgrade hook) via [`Self::get_temp_storage`]. - /// - /// A `at` storage identifier must be provided to indicate where the storage is being written - /// to. - #[cfg(feature = "try-runtime")] - fn set_temp_storage(data: T, at: &str) { - sp_io::storage::set(&Self::storage_key(at), &data.encode()); - } -} - -#[cfg(feature = "try-runtime")] -impl OnRuntimeUpgradeHelpersExt for U {} - /// The runtime upgrade trait. /// /// Implementing this lets you express what should happen when the runtime upgrades, @@ -166,50 +132,100 @@ pub trait OnRuntimeUpgrade { /// block local data are not accessible. /// /// Return the non-negotiable weight consumed for runtime upgrade. - fn on_runtime_upgrade() -> crate::weights::Weight { - 0 + fn on_runtime_upgrade() -> Weight { + Weight::zero() } /// Execute some pre-checks prior to a runtime upgrade. /// + /// Return a `Vec` that can contain arbitrary encoded data (usually some pre-upgrade state), + /// which will be passed to `post_upgrade` after upgrading for post-check. An empty vector + /// should be returned if there is no such need. + /// /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { - Ok(()) + fn pre_upgrade() -> Result, &'static str> { + Ok(Vec::new()) } /// Execute some post-checks after a runtime upgrade. /// + /// The `state` parameter is the `Vec` returned by `pre_upgrade` before upgrading, which + /// can be used for post-check. NOTE: if `pre_upgrade` is not implemented an empty vector will + /// be passed in, in such case `post_upgrade` should ignore it. + /// /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. #[cfg(feature = "try-runtime")] - fn post_upgrade() -> Result<(), &'static str> { + fn post_upgrade(_state: Vec) -> Result<(), &'static str> { Ok(()) } } -#[impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] impl OnRuntimeUpgrade for Tuple { - fn on_runtime_upgrade() -> crate::weights::Weight { - let mut weight = 0; + #[cfg(not(feature = "try-runtime"))] + fn on_runtime_upgrade() -> Weight { + let mut weight = Weight::zero(); for_tuples!( #( weight = weight.saturating_add(Tuple::on_runtime_upgrade()); )* ); weight } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { - let mut result = Ok(()); - for_tuples!( #( result = result.and(Tuple::pre_upgrade()); )* ); - result + /// We are executing pre- and post-checks sequentially in order to be able to test several + /// consecutive migrations for the same pallet without errors. Therefore pre and post upgrade + /// hooks for tuples are a noop. + fn on_runtime_upgrade() -> Weight { + use scale_info::prelude::format; + + let mut weight = Weight::zero(); + // migration index in the tuple, start with 1 for better readability + let mut i = 1; + for_tuples!( #( + let _guard = frame_support::StorageNoopGuard::default(); + // we want to panic if any checks fail right here right now. + let state = Tuple::pre_upgrade().expect(&format!("PreUpgrade failed for migration #{}", i)); + drop(_guard); + + weight = weight.saturating_add(Tuple::on_runtime_upgrade()); + + let _guard = frame_support::StorageNoopGuard::default(); + // we want to panic if any checks fail right here right now. + Tuple::post_upgrade(state).expect(&format!("PostUpgrade failed for migration #{}", i)); + drop(_guard); + + i += 1; + )* ); + weight } #[cfg(feature = "try-runtime")] - fn post_upgrade() -> Result<(), &'static str> { - let mut result = Ok(()); - for_tuples!( #( result = result.and(Tuple::post_upgrade()); )* ); - result + /// noop + fn pre_upgrade() -> Result, &'static str> { + Ok(Vec::new()) + } + + #[cfg(feature = "try-runtime")] + /// noop + fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + Ok(()) } } +/// Type that provide some integrity tests. +/// +/// This implemented for modules by `decl_module`. +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] +pub trait IntegrityTest { + /// Run integrity test. + /// + /// The test is not executed in a externalities provided environment. + fn integrity_test() {} +} + /// The pallet hooks trait. Implementing this lets you express some logic to execute. pub trait Hooks { /// The block is being finalized. Implement to have something happen. @@ -220,18 +236,15 @@ pub trait Hooks { /// Will not fire if the remaining weight is 0. /// Return the weight used, the hook will subtract it from current weight used /// and pass the result to the next `on_idle` hook if it exists. - fn on_idle( - _n: BlockNumber, - _remaining_weight: crate::weights::Weight, - ) -> crate::weights::Weight { - 0 + fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { + Weight::zero() } /// The block is being initialized. Implement to have something happen. /// /// Return the non-negotiable weight consumed in the block. - fn on_initialize(_n: BlockNumber) -> crate::weights::Weight { - 0 + fn on_initialize(_n: BlockNumber) -> Weight { + Weight::zero() } /// Perform a module upgrade. @@ -244,27 +257,49 @@ pub trait Hooks { /// # Warning /// /// This function will be called before we initialized any runtime state, aka `on_initialize` - /// wasn't called yet. So, information like the block number and any other - /// block local data are not accessible. + /// wasn't called yet. So, information like the block number and any other block local data are + /// not accessible. /// /// Return the non-negotiable weight consumed for runtime upgrade. - fn on_runtime_upgrade() -> crate::weights::Weight { - 0 + /// + /// While this function can be freely implemented, using `on_runtime_upgrade` from inside the + /// pallet is discouraged and might get deprecated in the future. Alternatively, export the same + /// logic as a free-function from your pallet, and pass it to `type Executive` from the + /// top-level runtime. + fn on_runtime_upgrade() -> Weight { + Weight::zero() + } + + /// Execute the sanity checks of this pallet, per block. + /// + /// It should focus on certain checks to ensure that the state is sensible. This is never + /// executed in a consensus code-path, therefore it can consume as much weight as it needs. + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumber) -> Result<(), &'static str> { + Ok(()) } /// Execute some pre-checks prior to a runtime upgrade. /// + /// Return a `Vec` that can contain arbitrary encoded data (usually some pre-upgrade state), + /// which will be passed to `post_upgrade` after upgrading for post-check. An empty vector + /// should be returned if there is no such need. + /// /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { - Ok(()) + fn pre_upgrade() -> Result, &'static str> { + Ok(Vec::new()) } /// Execute some post-checks after a runtime upgrade. /// + /// The `state` parameter is the `Vec` returned by `pre_upgrade` before upgrading, which + /// can be used for post-check. NOTE: if `pre_upgrade` is not implemented an empty vector will + /// be passed in, in such case `post_upgrade` should ignore it. + /// /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. #[cfg(feature = "try-runtime")] - fn post_upgrade() -> Result<(), &'static str> { + fn post_upgrade(_state: Vec) -> Result<(), &'static str> { Ok(()) } @@ -294,7 +329,7 @@ pub trait Hooks { /// A trait to define the build function of a genesis config, T and I are placeholder for pallet /// trait and pallet instance. #[cfg(feature = "std")] -pub trait GenesisBuild: Default + MaybeSerializeDeserialize { +pub trait GenesisBuild: Default + sp_runtime::traits::MaybeSerializeDeserialize { /// The build function is called within an externalities allowing storage APIs. /// Thus one can write to storage using regular pallet storages. fn build(&self); @@ -316,7 +351,9 @@ pub trait GenesisBuild: Default + MaybeSerializeDeserialize { } /// A trait which is called when the timestamp is set in the runtime. -#[impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] pub trait OnTimestampSet { /// Called when the timestamp is set. fn on_timestamp_set(moment: Moment); @@ -328,20 +365,24 @@ mod tests { #[test] fn on_initialize_and_on_runtime_upgrade_weight_merge_works() { + use sp_io::TestExternalities; struct Test; + impl OnInitialize for Test { - fn on_initialize(_n: u8) -> crate::weights::Weight { - 10 + fn on_initialize(_n: u8) -> Weight { + Weight::from_ref_time(10) } } impl OnRuntimeUpgrade for Test { - fn on_runtime_upgrade() -> crate::weights::Weight { - 20 + fn on_runtime_upgrade() -> Weight { + Weight::from_ref_time(20) } } - assert_eq!(<(Test, Test)>::on_initialize(0), 20); - assert_eq!(<(Test, Test)>::on_runtime_upgrade(), 40); + TestExternalities::default().execute_with(|| { + assert_eq!(<(Test, Test)>::on_initialize(0), Weight::from_ref_time(20)); + assert_eq!(<(Test, Test)>::on_runtime_upgrade(), Weight::from_ref_time(40)); + }); } #[test] @@ -353,50 +394,156 @@ mod tests { struct Test3; type TestTuple = (Test1, Test2, Test3); impl OnIdle for Test1 { - fn on_idle(_n: u32, _weight: crate::weights::Weight) -> crate::weights::Weight { + fn on_idle(_n: u32, _weight: Weight) -> Weight { unsafe { ON_IDLE_INVOCATION_ORDER.push("Test1"); } - 0 + Weight::zero() } } impl OnIdle for Test2 { - fn on_idle(_n: u32, _weight: crate::weights::Weight) -> crate::weights::Weight { + fn on_idle(_n: u32, _weight: Weight) -> Weight { unsafe { ON_IDLE_INVOCATION_ORDER.push("Test2"); } - 0 + Weight::zero() } } impl OnIdle for Test3 { - fn on_idle(_n: u32, _weight: crate::weights::Weight) -> crate::weights::Weight { + fn on_idle(_n: u32, _weight: Weight) -> Weight { unsafe { ON_IDLE_INVOCATION_ORDER.push("Test3"); } - 0 + Weight::zero() } } unsafe { - TestTuple::on_idle(0, 0); + TestTuple::on_idle(0, Weight::zero()); assert_eq!(ON_IDLE_INVOCATION_ORDER, ["Test1", "Test2", "Test3"].to_vec()); ON_IDLE_INVOCATION_ORDER.clear(); - TestTuple::on_idle(1, 0); + TestTuple::on_idle(1, Weight::zero()); assert_eq!(ON_IDLE_INVOCATION_ORDER, ["Test2", "Test3", "Test1"].to_vec()); ON_IDLE_INVOCATION_ORDER.clear(); - TestTuple::on_idle(2, 0); + TestTuple::on_idle(2, Weight::zero()); assert_eq!(ON_IDLE_INVOCATION_ORDER, ["Test3", "Test1", "Test2"].to_vec()); ON_IDLE_INVOCATION_ORDER.clear(); - TestTuple::on_idle(3, 0); + TestTuple::on_idle(3, Weight::zero()); assert_eq!(ON_IDLE_INVOCATION_ORDER, ["Test1", "Test2", "Test3"].to_vec()); ON_IDLE_INVOCATION_ORDER.clear(); - TestTuple::on_idle(4, 0); + TestTuple::on_idle(4, Weight::zero()); assert_eq!(ON_IDLE_INVOCATION_ORDER, ["Test2", "Test3", "Test1"].to_vec()); ON_IDLE_INVOCATION_ORDER.clear(); } } + + #[cfg(feature = "try-runtime")] + #[test] + #[allow(dead_code)] + fn on_runtime_upgrade_tuple() { + use frame_support::parameter_types; + use sp_io::TestExternalities; + + struct Test1; + struct Test2; + struct Test3; + + parameter_types! { + static Test1Assertions: u8 = 0; + static Test2Assertions: u8 = 0; + static Test3Assertions: u8 = 0; + static EnableSequentialTest: bool = false; + static SequentialAssertions: u8 = 0; + } + + impl OnRuntimeUpgrade for Test1 { + fn pre_upgrade() -> Result, &'static str> { + Ok("Test1".encode()) + } + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + let s: String = Decode::decode(&mut state.as_slice()).unwrap(); + Test1Assertions::mutate(|val| *val += 1); + if EnableSequentialTest::get() { + SequentialAssertions::mutate(|val| *val += 1); + } + assert_eq!(s, "Test1"); + Ok(()) + } + } + + impl OnRuntimeUpgrade for Test2 { + fn pre_upgrade() -> Result, &'static str> { + Ok(100u32.encode()) + } + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + let s: u32 = Decode::decode(&mut state.as_slice()).unwrap(); + Test2Assertions::mutate(|val| *val += 1); + if EnableSequentialTest::get() { + assert_eq!(SequentialAssertions::get(), 1); + SequentialAssertions::mutate(|val| *val += 1); + } + assert_eq!(s, 100); + Ok(()) + } + } + + impl OnRuntimeUpgrade for Test3 { + fn pre_upgrade() -> Result, &'static str> { + Ok(true.encode()) + } + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + let s: bool = Decode::decode(&mut state.as_slice()).unwrap(); + Test3Assertions::mutate(|val| *val += 1); + if EnableSequentialTest::get() { + assert_eq!(SequentialAssertions::get(), 2); + SequentialAssertions::mutate(|val| *val += 1); + } + assert_eq!(s, true); + Ok(()) + } + } + + TestExternalities::default().execute_with(|| { + type TestEmpty = (); + let origin_state = ::pre_upgrade().unwrap(); + assert!(origin_state.is_empty()); + ::post_upgrade(origin_state).unwrap(); + + type Test1Tuple = (Test1,); + let origin_state = ::pre_upgrade().unwrap(); + assert!(origin_state.is_empty()); + ::post_upgrade(origin_state).unwrap(); + assert_eq!(Test1Assertions::get(), 0); + ::on_runtime_upgrade(); + assert_eq!(Test1Assertions::take(), 1); + + type Test321 = (Test3, Test2, Test1); + ::on_runtime_upgrade(); + assert_eq!(Test1Assertions::take(), 1); + assert_eq!(Test2Assertions::take(), 1); + assert_eq!(Test3Assertions::take(), 1); + + // enable sequential tests + EnableSequentialTest::mutate(|val| *val = true); + + type Test123 = (Test1, Test2, Test3); + ::on_runtime_upgrade(); + assert_eq!(Test1Assertions::take(), 1); + assert_eq!(Test2Assertions::take(), 1); + assert_eq!(Test3Assertions::take(), 1); + + // reset assertions + SequentialAssertions::take(); + + type TestNested123 = (Test1, (Test2, Test3)); + ::on_runtime_upgrade(); + assert_eq!(Test1Assertions::take(), 1); + assert_eq!(Test2Assertions::take(), 1); + assert_eq!(Test3Assertions::take(), 1); + }); + } } diff --git a/frame/support/src/traits/members.rs b/frame/support/src/traits/members.rs index a59869c2fc9a3..daf2d3aa6517d 100644 --- a/frame/support/src/traits/members.rs +++ b/frame/support/src/traits/members.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,7 @@ //! Traits for dealing with the idea of membership. +use impl_trait_for_tuples::impl_for_tuples; use sp_std::{marker::PhantomData, prelude::*}; /// A trait for querying whether a type can be said to "contain" a value. @@ -25,6 +26,44 @@ pub trait Contains { fn contains(t: &T) -> bool; } +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] +impl Contains for Tuple { + fn contains(t: &T) -> bool { + for_tuples!( #( + if Tuple::contains(t) { return true } + )* ); + false + } +} + +/// A trait for querying whether a type can be said to "contain" a pair-value. +pub trait ContainsPair { + /// Return `true` if this "contains" the pair-value `(a, b)`. + fn contains(a: &A, b: &B) -> bool; +} + +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] +impl ContainsPair for Tuple { + fn contains(a: &A, b: &B) -> bool { + for_tuples!( #( + if Tuple::contains(a, b) { return true } + )* ); + false + } +} + +/// Converter `struct` to use a `ContainsPair` implementation for a `Contains` bound. +pub struct FromContainsPair(PhantomData); +impl> Contains<(A, B)> for FromContainsPair { + fn contains((ref a, ref b): &(A, B)) -> bool { + CP::contains(a, b) + } +} + /// A [`Contains`] implementation that contains every value. pub enum Everything {} impl Contains for Everything { @@ -32,6 +71,11 @@ impl Contains for Everything { true } } +impl ContainsPair for Everything { + fn contains(_: &A, _: &B) -> bool { + true + } +} /// A [`Contains`] implementation that contains no value. pub enum Nothing {} @@ -40,43 +84,112 @@ impl Contains for Nothing { false } } +impl ContainsPair for Nothing { + fn contains(_: &A, _: &B) -> bool { + false + } +} -#[deprecated = "Use `Everything` instead"] -pub type AllowAll = Everything; -#[deprecated = "Use `Nothing` instead"] -pub type DenyAll = Nothing; -#[deprecated = "Use `Contains` instead"] -pub trait Filter { - fn filter(t: &T) -> bool; +/// A [`Contains`] implementation that contains everything except the values in `Exclude`. +pub struct EverythingBut(PhantomData); +impl> Contains for EverythingBut { + fn contains(t: &T) -> bool { + !Exclude::contains(t) + } } -#[allow(deprecated)] -impl> Filter for C { - fn filter(t: &T) -> bool { - Self::contains(t) +impl> ContainsPair for EverythingBut { + fn contains(a: &A, b: &B) -> bool { + !Exclude::contains(a, b) } } -#[impl_trait_for_tuples::impl_for_tuples(1, 30)] -impl Contains for Tuple { +/// A [`Contains`] implementation that contains all members of `These` excepting any members in +/// `Except`. +pub struct TheseExcept(PhantomData<(These, Except)>); +impl, Except: Contains> Contains for TheseExcept { fn contains(t: &T) -> bool { - for_tuples!( #( - if Tuple::contains(t) { return true } - )* ); - false + These::contains(t) && !Except::contains(t) + } +} +impl, Except: ContainsPair> ContainsPair + for TheseExcept +{ + fn contains(a: &A, b: &B) -> bool { + These::contains(a, b) && !Except::contains(a, b) + } +} + +/// A [`Contains`] implementation which contains all members of `These` which are also members of +/// `Those`. +pub struct InsideBoth(PhantomData<(These, Those)>); +impl, Those: Contains> Contains for InsideBoth { + fn contains(t: &T) -> bool { + These::contains(t) && Those::contains(t) + } +} +impl, Those: ContainsPair> ContainsPair + for InsideBoth +{ + fn contains(a: &A, b: &B) -> bool { + These::contains(a, b) && Those::contains(a, b) } } /// Create a type which implements the `Contains` trait for a particular type with syntax similar /// to `matches!`. #[macro_export] -macro_rules! match_type { - ( pub type $n:ident: impl Contains<$t:ty> = { $phead:pat $( | $ptail:pat )* } ; ) => { +macro_rules! match_types { + ( + pub type $n:ident: impl Contains<$t:ty> = { + $phead:pat_param $( | $ptail:pat )* + }; + $( $rest:tt )* + ) => { pub struct $n; impl $crate::traits::Contains<$t> for $n { fn contains(l: &$t) -> bool { matches!(l, $phead $( | $ptail )* ) } } + $crate::match_types!( $( $rest )* ); + }; + ( + pub type $n:ident: impl ContainsPair<$a:ty, $b:ty> = { + $phead:pat_param $( | $ptail:pat )* + }; + $( $rest:tt )* + ) => { + pub struct $n; + impl $crate::traits::ContainsPair<$a, $b> for $n { + fn contains(a: &$a, b: &$b) -> bool { + matches!((a, b), $phead $( | $ptail )* ) + } + } + $crate::match_types!( $( $rest )* ); + }; + () => {} +} + +/// Create a type which implements the `Contains` trait for a particular type with syntax similar +/// to `matches!`. +#[macro_export] +#[deprecated = "Use `match_types!` instead"] +macro_rules! match_type { + ($( $x:tt )*) => { $crate::match_types!( $( $x )* ); } +} + +#[deprecated = "Use `Everything` instead"] +pub type AllowAll = Everything; +#[deprecated = "Use `Nothing` instead"] +pub type DenyAll = Nothing; +#[deprecated = "Use `Contains` instead"] +pub trait Filter { + fn filter(t: &T) -> bool; +} +#[allow(deprecated)] +impl> Filter for C { + fn filter(t: &T) -> bool { + Self::contains(t) } } @@ -84,12 +197,12 @@ macro_rules! match_type { mod tests { use super::*; - match_type! { + match_types! { pub type OneOrTenToTwenty: impl Contains = { 1 | 10..=20 }; } #[test] - fn match_type_works() { + fn match_types_works() { for i in 0..=255 { assert_eq!(OneOrTenToTwenty::contains(&i), i == 1 || i >= 10 && i <= 20); } @@ -191,7 +304,7 @@ pub trait ChangeMembers { /// This resets any previous value of prime. fn set_members_sorted(new_members: &[AccountId], old_members: &[AccountId]) { let (incoming, outgoing) = Self::compute_members_diff_sorted(new_members, old_members); - Self::change_members_sorted(&incoming[..], &outgoing[..], &new_members); + Self::change_members_sorted(&incoming[..], &outgoing[..], new_members); } /// Compute diff between new and old members; they **must already be sorted**. diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs new file mode 100644 index 0000000000000..9b86c421ad9e0 --- /dev/null +++ b/frame/support/src/traits/messages.rs @@ -0,0 +1,202 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Traits for managing message queuing and handling. + +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::{ConstU32, Get, TypedGet}; +use sp_runtime::{traits::Convert, BoundedSlice, RuntimeDebug}; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; +use sp_weights::Weight; + +/// Errors that can happen when attempting to process a message with +/// [`ProcessMessage::process_message()`]. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, RuntimeDebug)] +pub enum ProcessMessageError { + /// The message data format is unknown (e.g. unrecognised header) + BadFormat, + /// The message data is bad (e.g. decoding returns an error). + Corrupt, + /// The message format is unsupported (e.g. old XCM version). + Unsupported, + /// Message processing was not attempted because it was not certain that the weight limit + /// would be respected. The parameter gives the maximum weight which the message could take + /// to process. + Overweight(Weight), +} + +/// Can process messages from a specific origin. +pub trait ProcessMessage { + /// The transport from where a message originates. + type Origin: FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + Debug; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError>; +} + +/// Errors that can happen when attempting to execute an overweight message with +/// [`ServiceQueues::execute_overweight()`]. +#[derive(Eq, PartialEq, RuntimeDebug)] +pub enum ExecuteOverweightError { + /// The referenced message was not found. + NotFound, + /// The available weight was insufficient to execute the message. + InsufficientWeight, +} + +/// Can service queues and execute overweight messages. +pub trait ServiceQueues { + /// Addresses a specific overweight message. + type OverweightMessageAddress; + + /// Service all message queues in some fair manner. + /// + /// - `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`. + fn service_queues(weight_limit: Weight) -> Weight; + + /// Executes a message that could not be executed by [`Self::service_queues()`] because it was + /// temporarily overweight. + fn execute_overweight( + _weight_limit: Weight, + _address: Self::OverweightMessageAddress, + ) -> Result { + Err(ExecuteOverweightError::NotFound) + } +} + +/// The resource footprint of a queue. +#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] +pub struct Footprint { + pub count: u64, + pub size: u64, +} + +/// Can enqueue messages for multiple origins. +pub trait EnqueueMessage { + /// The maximal length any enqueued message may have. + type MaxMessageLen: Get; + + /// Enqueue a single `message` from a specific `origin`. + fn enqueue_message(message: BoundedSlice, origin: Origin); + + /// Enqueue multiple `messages` from a specific `origin`. + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: Origin, + ); + + /// Any remaining unprocessed messages should happen only lazily, not proactively. + fn sweep_queue(origin: Origin); + + /// Return the state footprint of the given queue. + fn footprint(origin: Origin) -> Footprint; +} + +impl EnqueueMessage for () { + type MaxMessageLen = ConstU32<0>; + fn enqueue_message(_: BoundedSlice, _: Origin) {} + fn enqueue_messages<'a>( + _: impl Iterator>, + _: Origin, + ) { + } + fn sweep_queue(_: Origin) {} + fn footprint(_: Origin) -> Footprint { + Footprint::default() + } +} + +/// Transform the origin of an [`EnqueueMessage`] via `C::convert`. +pub struct TransformOrigin(PhantomData<(E, O, N, C)>); +impl, O: MaxEncodedLen, N: MaxEncodedLen, C: Convert> EnqueueMessage + for TransformOrigin +{ + type MaxMessageLen = E::MaxMessageLen; + + fn enqueue_message(message: BoundedSlice, origin: N) { + E::enqueue_message(message, C::convert(origin)); + } + + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: N, + ) { + E::enqueue_messages(messages, C::convert(origin)); + } + + fn sweep_queue(origin: N) { + E::sweep_queue(C::convert(origin)); + } + + fn footprint(origin: N) -> Footprint { + E::footprint(C::convert(origin)) + } +} + +/// Handles incoming messages for a single origin. +pub trait HandleMessage { + /// The maximal length any enqueued message may have. + type MaxMessageLen: Get; + + /// Enqueue a single `message` with an implied origin. + fn handle_message(message: BoundedSlice); + + /// Enqueue multiple `messages` from an implied origin. + fn handle_messages<'a>( + messages: impl Iterator>, + ); + + /// Any remaining unprocessed messages should happen only lazily, not proactively. + fn sweep_queue(); + + /// Return the state footprint of the queue. + fn footprint() -> Footprint; +} + +/// Adapter type to transform an [`EnqueueMessage`] with an origin into a [`HandleMessage`] impl. +pub struct EnqueueWithOrigin(PhantomData<(E, O)>); +impl, O: TypedGet> HandleMessage for EnqueueWithOrigin +where + O::Type: MaxEncodedLen, +{ + type MaxMessageLen = E::MaxMessageLen; + + fn handle_message(message: BoundedSlice) { + E::enqueue_message(message, O::get()); + } + + fn handle_messages<'a>( + messages: impl Iterator>, + ) { + E::enqueue_messages(messages, O::get()); + } + + fn sweep_queue() { + E::sweep_queue(O::get()); + } + + fn footprint() -> Footprint { + E::footprint(O::get()) + } +} diff --git a/frame/support/src/traits/metadata.rs b/frame/support/src/traits/metadata.rs index e877f29e0a137..42f2d759a597d 100644 --- a/frame/support/src/traits/metadata.rs +++ b/frame/support/src/traits/metadata.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,11 @@ //! Traits for managing information attached to pallets and their constituents. use codec::{Decode, Encode}; +use impl_trait_for_tuples::impl_for_tuples; use sp_runtime::RuntimeDebug; +use sp_std::prelude::*; -/// Provides information about the pallet setup in the runtime. +/// Provides information about the pallet itself and its setup in the runtime. /// /// An implementor should be able to provide information about each pallet that /// is configured in `construct_runtime!`. @@ -29,16 +31,63 @@ pub trait PalletInfo { fn index() -> Option; /// Convert the given pallet `P` into its name as configured in the runtime. fn name() -> Option<&'static str>; + /// Convert the given pallet `P` into its Rust module name as used in `construct_runtime!`. + fn module_name() -> Option<&'static str>; + /// Convert the given pallet `P` into its containing crate version. + fn crate_version() -> Option; } -/// Provides information about the pallet setup in the runtime. +/// Information regarding an instance of a pallet. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug)] +pub struct PalletInfoData { + /// Index of the pallet as configured in the runtime. + pub index: usize, + /// Name of the pallet as configured in the runtime. + pub name: &'static str, + /// Name of the Rust module containing the pallet. + pub module_name: &'static str, + /// Version of the crate containing the pallet. + pub crate_version: CrateVersion, +} + +/// Provides information about the pallet itself and its setup in the runtime. /// -/// Access the information provided by [`PalletInfo`] for a specific pallet. +/// Declare some information and access the information provided by [`PalletInfo`] for a specific +/// pallet. pub trait PalletInfoAccess { /// Index of the pallet as configured in the runtime. fn index() -> usize; /// Name of the pallet as configured in the runtime. fn name() -> &'static str; + /// Name of the Rust module containing the pallet. + fn module_name() -> &'static str; + /// Version of the crate containing the pallet. + fn crate_version() -> CrateVersion; +} + +/// Provide information about a bunch of pallets. +pub trait PalletsInfoAccess { + /// The number of pallets' information that this type represents. + /// + /// You probably don't want this function but `infos()` instead. + fn count() -> usize { + // for backwards compatibility with XCM-3, Mark as deprecated. + Self::infos().len() + } + + /// All of the pallets' information that this type represents. + fn infos() -> Vec; +} + +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] +impl PalletsInfoAccess for Tuple { + fn infos() -> Vec { + let mut res = vec![]; + for_tuples!( #( res.extend(Tuple::infos()); )* ); + res + } } /// The function and pallet name of the Call. @@ -68,6 +117,37 @@ pub trait GetCallMetadata { fn get_call_metadata(&self) -> CallMetadata; } +/// The version of a crate. +#[derive(Debug, Eq, PartialEq, Encode, Decode, Clone, Copy, Default)] +pub struct CrateVersion { + /// The major version of the crate. + pub major: u16, + /// The minor version of the crate. + pub minor: u8, + /// The patch version of the crate. + pub patch: u8, +} + +impl CrateVersion { + pub const fn new(major: u16, minor: u8, patch: u8) -> Self { + Self { major, minor, patch } + } +} + +impl sp_std::cmp::Ord for CrateVersion { + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.major + .cmp(&other.major) + .then_with(|| self.minor.cmp(&other.minor).then_with(|| self.patch.cmp(&other.patch))) + } +} + +impl sp_std::cmp::PartialOrd for CrateVersion { + fn partial_cmp(&self, other: &Self) -> Option { + Some(::cmp(self, other)) + } +} + /// The storage key postfix that is used to store the [`StorageVersion`] per pallet. /// /// The full storage key is built by using: @@ -78,7 +158,7 @@ pub const STORAGE_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__STORAGE_VERSION__:"; /// /// Each storage version of a pallet is stored in the state under a fixed key. See /// [`STORAGE_VERSION_STORAGE_KEY_POSTFIX`] for how this key is built. -#[derive(RuntimeDebug, Eq, PartialEq, Encode, Decode, Ord, Clone, Copy, PartialOrd, Default)] +#[derive(Debug, Eq, PartialEq, Encode, Decode, Ord, Clone, Copy, PartialOrd, Default)] pub struct StorageVersion(u16); impl StorageVersion { @@ -166,6 +246,37 @@ pub trait GetStorageVersion { mod tests { use super::*; + struct Pallet1; + impl PalletInfoAccess for Pallet1 { + fn index() -> usize { + 1 + } + fn name() -> &'static str { + "Pallet1" + } + fn module_name() -> &'static str { + "pallet1" + } + fn crate_version() -> CrateVersion { + CrateVersion::new(1, 0, 0) + } + } + struct Pallet2; + impl PalletInfoAccess for Pallet2 { + fn index() -> usize { + 2 + } + fn name() -> &'static str { + "Pallet2" + } + fn module_name() -> &'static str { + "pallet2" + } + fn crate_version() -> CrateVersion { + CrateVersion::new(1, 0, 0) + } + } + #[test] fn check_storage_version_ordering() { let version = StorageVersion::new(1); diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 1776e1ba320ea..33e48faa7ef29 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,57 +18,557 @@ //! Smaller traits used in FRAME which don't need their own file. use crate::dispatch::Parameter; +use codec::{CompactLen, Decode, DecodeLimit, Encode, EncodeLike, Input, MaxEncodedLen}; +use impl_trait_for_tuples::impl_for_tuples; +use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; +use sp_arithmetic::traits::{CheckedAdd, CheckedMul, CheckedSub, One, Saturating}; +use sp_core::bounded::bounded_vec::TruncateFrom; +#[doc(hidden)] +pub use sp_runtime::traits::{ + ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, ConstU32, + ConstU64, ConstU8, Get, GetDefault, TryCollect, TypedGet, +}; use sp_runtime::{traits::Block as BlockT, DispatchError}; +use sp_std::{cmp::Ordering, prelude::*}; -/// Anything that can have a `::len()` method. -pub trait Len { - /// Return the length of data type. - fn len(&self) -> usize; +#[doc(hidden)] +pub const DEFENSIVE_OP_PUBLIC_ERROR: &str = "a defensive failure has been triggered; please report the block number at https://github.com/paritytech/substrate/issues"; +#[doc(hidden)] +pub const DEFENSIVE_OP_INTERNAL_ERROR: &str = "Defensive failure has been triggered!"; + +/// Generic function to mark an execution path as ONLY defensive. +/// +/// Similar to mark a match arm or `if/else` branch as `unreachable!`. +#[macro_export] +macro_rules! defensive { + () => { + frame_support::log::error!( + target: "runtime", + "{}", + $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR + ); + debug_assert!(false, "{}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR); + }; + ($error:tt) => { + frame_support::log::error!( + target: "runtime", + "{}: {:?}", + $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR, + $error + ); + debug_assert!(false, "{}: {:?}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR, $error); + }; + ($error:tt, $proof:tt) => { + frame_support::log::error!( + target: "runtime", + "{}: {:?}: {:?}", + $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR, + $error, + $proof, + ); + debug_assert!(false, "{}: {:?}: {:?}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR, $error, $proof); + } } -impl Len for T +/// Prelude module for all defensive traits to be imported at once. +pub mod defensive_prelude { + pub use super::{Defensive, DefensiveOption, DefensiveResult}; +} + +/// A trait to handle errors and options when you are really sure that a condition must hold, but +/// not brave enough to `expect` on it, or a default fallback value makes more sense. +/// +/// This trait mostly focuses on methods that eventually unwrap the inner value. See +/// [`DefensiveResult`] and [`DefensiveOption`] for methods that specifically apply to the +/// respective types. +/// +/// Each function in this trait will have two side effects, aside from behaving exactly as the name +/// would suggest: +/// +/// 1. It panics on `#[debug_assertions]`, so if the infallible code is reached in any of the tests, +/// you realize. +/// 2. It will log an error using the runtime logging system. This might help you detect such bugs +/// in production as well. Note that the log message, as of now, are not super expressive. Your +/// best shot of fully diagnosing the error would be to infer the block number of which the log +/// message was emitted, then re-execute that block using `check-block` or `try-runtime` +/// subcommands in substrate client. +pub trait Defensive { + /// Exactly the same as `unwrap_or`, but it does the defensive warnings explained in the trait + /// docs. + fn defensive_unwrap_or(self, other: T) -> T; + + /// Exactly the same as `unwrap_or_else`, but it does the defensive warnings explained in the + /// trait docs. + fn defensive_unwrap_or_else T>(self, f: F) -> T; + + /// Exactly the same as `unwrap_or_default`, but it does the defensive warnings explained in the + /// trait docs. + fn defensive_unwrap_or_default(self) -> T + where + T: Default; + + /// Does not alter the inner value at all, but it will log warnings if the inner value is `None` + /// or `Err`. + /// + /// In some ways, this is like `.defensive_map(|x| x)`. + /// + /// This is useful as: + /// ```nocompile + /// if let Some(inner) = maybe_value().defensive() { + /// .. + /// } + /// ``` + fn defensive(self) -> Self; + + /// Same as [`Defensive::defensive`], but it takes a proof as input, and displays it if the + /// defensive operation has been triggered. + fn defensive_proof(self, proof: &'static str) -> Self; +} + +/// Subset of methods similar to [`Defensive`] that can only work for a `Result`. +pub trait DefensiveResult { + /// Defensively map the error into another return type, but you are really sure that this + /// conversion should never be needed. + fn defensive_map_err F>(self, o: O) -> Result; + + /// Defensively map and unpack the value to something else (`U`), or call the default callback + /// if `Err`, which should never happen. + fn defensive_map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U; + + /// Defensively transform this result into an option, discarding the `Err` variant if it + /// happens, which should never happen. + fn defensive_ok(self) -> Option; + + /// Exactly the same as `map`, but it prints the appropriate warnings if the value being mapped + /// is `Err`. + fn defensive_map U>(self, f: F) -> Result; +} + +/// Subset of methods similar to [`Defensive`] that can only work for a `Option`. +pub trait DefensiveOption { + /// Potentially map and unpack the value to something else (`U`), or call the default callback + /// if `None`, which should never happen. + fn defensive_map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U; + + /// Defensively transform this option to a result, mapping `None` to the return value of an + /// error closure. + fn defensive_ok_or_else E>(self, err: F) -> Result; + + /// Defensively transform this option to a result, mapping `None` to a default value. + fn defensive_ok_or(self, err: E) -> Result; + + /// Exactly the same as `map`, but it prints the appropriate warnings if the value being mapped + /// is `None`. + fn defensive_map U>(self, f: F) -> Option; +} + +impl Defensive for Option { + fn defensive_unwrap_or(self, or: T) -> T { + match self { + Some(inner) => inner, + None => { + defensive!(); + or + }, + } + } + + fn defensive_unwrap_or_else T>(self, f: F) -> T { + match self { + Some(inner) => inner, + None => { + defensive!(); + f() + }, + } + } + + fn defensive_unwrap_or_default(self) -> T + where + T: Default, + { + match self { + Some(inner) => inner, + None => { + defensive!(); + Default::default() + }, + } + } + + fn defensive(self) -> Self { + match self { + Some(inner) => Some(inner), + None => { + defensive!(); + None + }, + } + } + + fn defensive_proof(self, proof: &'static str) -> Self { + if self.is_none() { + defensive!(proof); + } + self + } +} + +impl Defensive for Result { + fn defensive_unwrap_or(self, or: T) -> T { + match self { + Ok(inner) => inner, + Err(e) => { + defensive!(e); + or + }, + } + } + + fn defensive_unwrap_or_else T>(self, f: F) -> T { + match self { + Ok(inner) => inner, + Err(e) => { + defensive!(e); + f() + }, + } + } + + fn defensive_unwrap_or_default(self) -> T + where + T: Default, + { + match self { + Ok(inner) => inner, + Err(e) => { + defensive!(e); + Default::default() + }, + } + } + + fn defensive(self) -> Self { + match self { + Ok(inner) => Ok(inner), + Err(e) => { + defensive!(e); + Err(e) + }, + } + } + + fn defensive_proof(self, proof: &'static str) -> Self { + match self { + Ok(inner) => Ok(inner), + Err(e) => { + defensive!(e, proof); + Err(e) + }, + } + } +} + +impl DefensiveResult for Result { + fn defensive_map_err F>(self, o: O) -> Result { + self.map_err(|e| { + defensive!(e); + o(e) + }) + } + + fn defensive_map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U { + self.map_or_else( + |e| { + defensive!(e); + default(e) + }, + f, + ) + } + + fn defensive_ok(self) -> Option { + match self { + Ok(inner) => Some(inner), + Err(e) => { + defensive!(e); + None + }, + } + } + + fn defensive_map U>(self, f: F) -> Result { + match self { + Ok(inner) => Ok(f(inner)), + Err(e) => { + defensive!(e); + Err(e) + }, + } + } +} + +impl DefensiveOption for Option { + fn defensive_map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U { + self.map_or_else( + || { + defensive!(); + default() + }, + f, + ) + } + + fn defensive_ok_or_else E>(self, err: F) -> Result { + self.ok_or_else(|| { + let err_value = err(); + defensive!(err_value); + err_value + }) + } + + fn defensive_ok_or(self, err: E) -> Result { + self.ok_or_else(|| { + defensive!(err); + err + }) + } + + fn defensive_map U>(self, f: F) -> Option { + match self { + Some(inner) => Some(f(inner)), + None => { + defensive!(); + None + }, + } + } +} + +/// A variant of [`Defensive`] with the same rationale, for the arithmetic operations where in +/// case an infallible operation fails, it saturates. +pub trait DefensiveSaturating { + /// Return `self` plus `other` defensively. + fn defensive_saturating_add(self, other: Self) -> Self; + /// Return `self` minus `other` defensively. + fn defensive_saturating_sub(self, other: Self) -> Self; + /// Return the product of `self` and `other` defensively. + fn defensive_saturating_mul(self, other: Self) -> Self; + /// Increase `self` by `other` defensively. + fn defensive_saturating_accrue(&mut self, other: Self); + /// Reduce `self` by `other` defensively. + fn defensive_saturating_reduce(&mut self, other: Self); + /// Increment `self` by one defensively. + fn defensive_saturating_inc(&mut self); + /// Decrement `self` by one defensively. + fn defensive_saturating_dec(&mut self); +} + +// NOTE: A bit unfortunate, since T has to be bound by all the traits needed. Could make it +// `DefensiveSaturating` to mitigate. +impl DefensiveSaturating for T { + fn defensive_saturating_add(self, other: Self) -> Self { + self.checked_add(&other).defensive_unwrap_or_else(|| self.saturating_add(other)) + } + fn defensive_saturating_sub(self, other: Self) -> Self { + self.checked_sub(&other).defensive_unwrap_or_else(|| self.saturating_sub(other)) + } + fn defensive_saturating_mul(self, other: Self) -> Self { + self.checked_mul(&other).defensive_unwrap_or_else(|| self.saturating_mul(other)) + } + fn defensive_saturating_accrue(&mut self, other: Self) { + // Use `replace` here since `take` would require `T: Default`. + *self = sp_std::mem::replace(self, One::one()).defensive_saturating_add(other); + } + fn defensive_saturating_reduce(&mut self, other: Self) { + // Use `replace` here since `take` would require `T: Default`. + *self = sp_std::mem::replace(self, One::one()).defensive_saturating_sub(other); + } + fn defensive_saturating_inc(&mut self) { + self.defensive_saturating_accrue(One::one()); + } + fn defensive_saturating_dec(&mut self) { + self.defensive_saturating_reduce(One::one()); + } +} + +/// Construct an object by defensively truncating an input if the `TryFrom` conversion fails. +pub trait DefensiveTruncateFrom { + /// Use `TryFrom` first and defensively fall back to truncating otherwise. + /// + /// # Example + /// + /// ``` + /// use frame_support::{BoundedVec, traits::DefensiveTruncateFrom}; + /// use sp_runtime::traits::ConstU32; + /// + /// let unbound = vec![1, 2]; + /// let bound = BoundedVec::>::defensive_truncate_from(unbound); + /// + /// assert_eq!(bound, vec![1, 2]); + /// ``` + fn defensive_truncate_from(unbound: T) -> Self; +} + +impl DefensiveTruncateFrom for T where - ::IntoIter: ExactSizeIterator, + // NOTE: We use the fact that `BoundedVec` and + // `BoundedSlice` use `Self` as error type. We could also + // require a `Clone` bound and use `unbound.clone()` in the + // error case. + T: TruncateFrom + TryFrom, { - fn len(&self) -> usize { - self.clone().into_iter().len() + fn defensive_truncate_from(unbound: U) -> Self { + unbound.try_into().map_or_else( + |err| { + defensive!("DefensiveTruncateFrom truncating"); + T::truncate_from(err) + }, + |bound| bound, + ) } } -/// A trait for querying a single value from a type. +/// Defensively calculates the minimum of two values. /// -/// It is not required that the value is constant. -pub trait Get { - /// Return the current value. - fn get() -> T; +/// Can be used in contexts where we assume the receiver value to be (strictly) smaller. +pub trait DefensiveMin { + /// Returns the minimum and defensively checks that `self` is not larger than `other`. + /// + /// # Example + /// + /// ``` + /// use frame_support::traits::DefensiveMin; + /// // min(3, 4) is 3. + /// assert_eq!(3, 3_u32.defensive_min(4_u32)); + /// // min(4, 4) is 4. + /// assert_eq!(4, 4_u32.defensive_min(4_u32)); + /// ``` + /// + /// ```should_panic + /// use frame_support::traits::DefensiveMin; + /// // min(4, 3) panics. + /// 4_u32.defensive_min(3_u32); + /// ``` + fn defensive_min(self, other: T) -> Self; + + /// Returns the minimum and defensively checks that `self` is smaller than `other`. + /// + /// # Example + /// + /// ``` + /// use frame_support::traits::DefensiveMin; + /// // min(3, 4) is 3. + /// assert_eq!(3, 3_u32.defensive_strict_min(4_u32)); + /// ``` + /// + /// ```should_panic + /// use frame_support::traits::DefensiveMin; + /// // min(4, 4) panics. + /// 4_u32.defensive_strict_min(4_u32); + /// ``` + fn defensive_strict_min(self, other: T) -> Self; } -impl Get for () { - fn get() -> T { - T::default() +impl DefensiveMin for T +where + T: sp_std::cmp::PartialOrd, +{ + fn defensive_min(self, other: T) -> Self { + if self <= other { + self + } else { + defensive!("DefensiveMin"); + other + } + } + + fn defensive_strict_min(self, other: T) -> Self { + if self < other { + self + } else { + defensive!("DefensiveMin strict"); + other + } } } -/// Implement Get by returning Default for any type that implements Default. -pub struct GetDefault; -impl Get for GetDefault { - fn get() -> T { - T::default() - } +/// Defensively calculates the maximum of two values. +/// +/// Can be used in contexts where we assume the receiver value to be (strictly) larger. +pub trait DefensiveMax { + /// Returns the maximum and defensively asserts that `other` is not larger than `self`. + /// + /// # Example + /// + /// ``` + /// use frame_support::traits::DefensiveMax; + /// // max(4, 3) is 4. + /// assert_eq!(4, 4_u32.defensive_max(3_u32)); + /// // max(4, 4) is 4. + /// assert_eq!(4, 4_u32.defensive_max(4_u32)); + /// ``` + /// + /// ```should_panic + /// use frame_support::traits::DefensiveMax; + /// // max(4, 5) panics. + /// 4_u32.defensive_max(5_u32); + /// ``` + fn defensive_max(self, other: T) -> Self; + + /// Returns the maximum and defensively asserts that `other` is smaller than `self`. + /// + /// # Example + /// + /// ``` + /// use frame_support::traits::DefensiveMax; + /// // y(4, 3) is 4. + /// assert_eq!(4, 4_u32.defensive_strict_max(3_u32)); + /// ``` + /// + /// ```should_panic + /// use frame_support::traits::DefensiveMax; + /// // max(4, 4) panics. + /// 4_u32.defensive_strict_max(4_u32); + /// ``` + fn defensive_strict_max(self, other: T) -> Self; } -/// Implement `Get` and `Get>` using the given const. -pub struct ConstU32; +impl DefensiveMax for T +where + T: sp_std::cmp::PartialOrd, +{ + fn defensive_max(self, other: T) -> Self { + if self >= other { + self + } else { + defensive!("DefensiveMax"); + other + } + } -impl Get for ConstU32 { - fn get() -> u32 { - T + fn defensive_strict_max(self, other: T) -> Self { + if self > other { + self + } else { + defensive!("DefensiveMax strict"); + other + } } } -impl Get> for ConstU32 { - fn get() -> Option { - Some(T) +/// Anything that can have a `::len()` method. +pub trait Len { + /// Return the length of data type. + fn len(&self) -> usize; +} + +impl Len for T +where + ::IntoIter: ExactSizeIterator, +{ + fn len(&self) -> usize { + self.clone().into_iter().len() } } @@ -78,6 +578,12 @@ pub trait TryDrop: Sized { fn try_drop(self) -> Result<(), Self>; } +impl TryDrop for () { + fn try_drop(self) -> Result<(), Self> { + Ok(()) + } +} + /// Return type used when we need to return one of two items, each of the opposite direction or /// sign, with one (`Same`) being of the same type as the `self` or primary argument of the function /// that returned it. @@ -151,14 +657,18 @@ impl SameOrOther { } /// Handler for when a new account has been created. -#[impl_trait_for_tuples::impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] pub trait OnNewAccount { /// A new account `who` has been registered. fn on_new_account(who: &AccountId); } /// The account with the given id was reaped. -#[impl_trait_for_tuples::impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] pub trait OnKilledAccount { /// The account with the given id was reaped. fn on_killed_account(who: &AccountId); @@ -180,7 +690,7 @@ pub trait HandleLifetime { impl HandleLifetime for () {} pub trait Time { - type Moment: sp_arithmetic::traits::AtLeast32Bit + Parameter + Default + Copy; + type Moment: sp_arithmetic::traits::AtLeast32Bit + Parameter + Default + Copy + MaxEncodedLen; fn now() -> Self::Moment; } @@ -286,6 +796,26 @@ pub trait ExecuteBlock { fn execute_block(block: Block); } +/// Something that can compare privileges of two origins. +pub trait PrivilegeCmp { + /// Compare the `left` to the `right` origin. + /// + /// The returned ordering should be from the pov of the `left` origin. + /// + /// Should return `None` when it can not compare the given origins. + fn cmp_privilege(left: &Origin, right: &Origin) -> Option; +} + +/// Implementation of [`PrivilegeCmp`] that only checks for equal origins. +/// +/// This means it will either return [`Ordering::Equal`] or `None`. +pub struct EqualPrivilegeOnly; +impl PrivilegeCmp for EqualPrivilegeOnly { + fn cmp_privilege(left: &Origin, right: &Origin) -> Option { + (left == right).then(|| Ordering::Equal) + } +} + /// Off-chain computation trait. /// /// Implementing this trait on a module allows you to perform long-running tasks @@ -296,7 +826,9 @@ pub trait ExecuteBlock { /// but cannot preform any alterations. More specifically alterations are /// not forbidden, but they are not persisted in any way after the worker /// has finished. -#[impl_trait_for_tuples::impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] pub trait OffchainWorker { /// This function is being called after every block import (when fully synced). /// @@ -367,13 +899,498 @@ pub trait EstimateCallFee { /// /// The dispatch info and the length is deduced from the call. The post info can optionally be /// provided. - fn estimate_call_fee(call: &Call, post_info: crate::weights::PostDispatchInfo) -> Balance; + fn estimate_call_fee(call: &Call, post_info: crate::dispatch::PostDispatchInfo) -> Balance; } // Useful for building mocks. #[cfg(feature = "std")] impl, const T: u32> EstimateCallFee for ConstU32 { - fn estimate_call_fee(_: &Call, _: crate::weights::PostDispatchInfo) -> Balance { + fn estimate_call_fee(_: &Call, _: crate::dispatch::PostDispatchInfo) -> Balance { T.into() } } + +/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec`. +/// +/// The encoding is the encoding of `T` prepended with the compact encoding of its size in bytes. +/// Thus the encoded value can be decoded as a `Vec`. +#[derive(Debug, Eq, PartialEq, Default, Clone)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct WrapperOpaque(pub T); + +impl EncodeLike for WrapperOpaque {} +impl EncodeLike> for WrapperOpaque {} + +impl Encode for WrapperOpaque { + fn size_hint(&self) -> usize { + self.0.size_hint().saturating_add(>::max_encoded_len()) + } + + fn encode_to(&self, dest: &mut O) { + self.0.encode().encode_to(dest); + } + + fn encode(&self) -> Vec { + self.0.encode().encode() + } + + fn using_encoded R>(&self, f: F) -> R { + self.0.encode().using_encoded(f) + } +} + +impl Decode for WrapperOpaque { + fn decode(input: &mut I) -> Result { + Ok(Self(T::decode_all_with_depth_limit( + sp_api::MAX_EXTRINSIC_DEPTH, + &mut &>::decode(input)?[..], + )?)) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + >::skip(input) + } +} + +impl From for WrapperOpaque { + fn from(t: T) -> Self { + Self(t) + } +} + +impl MaxEncodedLen for WrapperOpaque { + fn max_encoded_len() -> usize { + let t_max_len = T::max_encoded_len(); + + // See scale encoding: https://docs.substrate.io/reference/scale-codec/ + if t_max_len < 64 { + t_max_len + 1 + } else if t_max_len < 2usize.pow(14) { + t_max_len + 2 + } else if t_max_len < 2usize.pow(30) { + t_max_len + 4 + } else { + >::max_encoded_len().saturating_add(T::max_encoded_len()) + } + } +} + +impl TypeInfo for WrapperOpaque { + type Identity = Self; + fn type_info() -> Type { + Type::builder() + .path(Path::new("WrapperOpaque", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite( + Fields::unnamed() + .field(|f| f.compact::()) + .field(|f| f.ty::().type_name("T")), + ) + } +} + +/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec`. +/// +/// This type is similar to [`WrapperOpaque`], but it differs in the way it stores the type `T`. +/// While [`WrapperOpaque`] stores the decoded type, the [`WrapperKeepOpaque`] stores the type only +/// in its opaque format, aka as a `Vec`. To access the real type `T` [`Self::try_decode`] needs +/// to be used. +#[derive(Debug, Eq, PartialEq, Default, Clone)] +pub struct WrapperKeepOpaque { + data: Vec, + _phantom: sp_std::marker::PhantomData, +} + +impl WrapperKeepOpaque { + /// Try to decode the wrapped type from the inner `data`. + /// + /// Returns `None` if the decoding failed. + pub fn try_decode(&self) -> Option { + T::decode_all_with_depth_limit(sp_api::MAX_EXTRINSIC_DEPTH, &mut &self.data[..]).ok() + } + + /// Returns the length of the encoded `T`. + pub fn encoded_len(&self) -> usize { + self.data.len() + } + + /// Returns the encoded data. + pub fn encoded(&self) -> &[u8] { + &self.data + } + + /// Create from the given encoded `data`. + pub fn from_encoded(data: Vec) -> Self { + Self { data, _phantom: sp_std::marker::PhantomData } + } +} + +impl EncodeLike for WrapperKeepOpaque {} +impl EncodeLike> for WrapperKeepOpaque {} + +impl Encode for WrapperKeepOpaque { + fn size_hint(&self) -> usize { + self.data.len() + codec::Compact::::compact_len(&(self.data.len() as u32)) + } + + fn encode_to(&self, dest: &mut O) { + self.data.encode_to(dest); + } + + fn encode(&self) -> Vec { + self.data.encode() + } + + fn using_encoded R>(&self, f: F) -> R { + self.data.using_encoded(f) + } +} + +impl Decode for WrapperKeepOpaque { + fn decode(input: &mut I) -> Result { + Ok(Self { data: Vec::::decode(input)?, _phantom: sp_std::marker::PhantomData }) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + >::skip(input) + } +} + +impl MaxEncodedLen for WrapperKeepOpaque { + fn max_encoded_len() -> usize { + WrapperOpaque::::max_encoded_len() + } +} + +impl TypeInfo for WrapperKeepOpaque { + type Identity = Self; + fn type_info() -> Type { + Type::builder() + .path(Path::new("WrapperKeepOpaque", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite( + Fields::unnamed() + .field(|f| f.compact::()) + .field(|f| f.ty::().type_name("T")), + ) + } +} + +/// A interface for looking up preimages from their hash on chain. +pub trait PreimageProvider { + /// Returns whether a preimage exists for a given hash. + /// + /// A value of `true` implies that `get_preimage` is `Some`. + fn have_preimage(hash: &Hash) -> bool; + + /// Returns the preimage for a given hash. + fn get_preimage(hash: &Hash) -> Option>; + + /// Returns whether a preimage request exists for a given hash. + fn preimage_requested(hash: &Hash) -> bool; + + /// Request that someone report a preimage. Providers use this to optimise the economics for + /// preimage reporting. + fn request_preimage(hash: &Hash); + + /// Cancel a previous preimage request. + fn unrequest_preimage(hash: &Hash); +} + +impl PreimageProvider for () { + fn have_preimage(_: &Hash) -> bool { + false + } + fn get_preimage(_: &Hash) -> Option> { + None + } + fn preimage_requested(_: &Hash) -> bool { + false + } + fn request_preimage(_: &Hash) {} + fn unrequest_preimage(_: &Hash) {} +} + +/// A interface for managing preimages to hashes on chain. +/// +/// Note that this API does not assume any underlying user is calling, and thus +/// does not handle any preimage ownership or fees. Other system level logic that +/// uses this API should implement that on their own side. +pub trait PreimageRecipient: PreimageProvider { + /// Maximum size of a preimage. + type MaxSize: Get; + + /// Store the bytes of a preimage on chain infallible due to the bounded type. + fn note_preimage(bytes: crate::BoundedVec); + + /// Clear a previously noted preimage. This is infallible and should be treated more like a + /// hint - if it was not previously noted or if it is now requested, then this will not do + /// anything. + fn unnote_preimage(hash: &Hash); +} + +impl PreimageRecipient for () { + type MaxSize = (); + fn note_preimage(_: crate::BoundedVec) {} + fn unnote_preimage(_: &Hash) {} +} + +#[cfg(test)] +mod test { + use super::*; + use sp_core::bounded::{BoundedSlice, BoundedVec}; + use sp_std::marker::PhantomData; + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_saturating_accrue_works() { + let mut v = 1_u32; + v.defensive_saturating_accrue(2); + assert_eq!(v, 3); + v.defensive_saturating_accrue(u32::MAX); + assert_eq!(v, u32::MAX); + v.defensive_saturating_accrue(1); + assert_eq!(v, u32::MAX); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Defensive")] + fn defensive_saturating_accrue_panics() { + let mut v = u32::MAX; + v.defensive_saturating_accrue(1); // defensive failure + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_saturating_reduce_works() { + let mut v = u32::MAX; + v.defensive_saturating_reduce(3); + assert_eq!(v, u32::MAX - 3); + v.defensive_saturating_reduce(u32::MAX); + assert_eq!(v, 0); + v.defensive_saturating_reduce(1); + assert_eq!(v, 0); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Defensive")] + fn defensive_saturating_reduce_panics() { + let mut v = 0_u32; + v.defensive_saturating_reduce(1); // defensive failure + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_saturating_inc_works() { + let mut v = 0_u32; + for i in 1..10 { + v.defensive_saturating_inc(); + assert_eq!(v, i); + } + v += u32::MAX - 10; + v.defensive_saturating_inc(); + assert_eq!(v, u32::MAX); + v.defensive_saturating_inc(); + assert_eq!(v, u32::MAX); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Defensive")] + fn defensive_saturating_inc_panics() { + let mut v = u32::MAX; + v.defensive_saturating_inc(); // defensive failure + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_saturating_dec_works() { + let mut v = u32::MAX; + for i in 1..10 { + v.defensive_saturating_dec(); + assert_eq!(v, u32::MAX - i); + } + v -= u32::MAX - 10; + v.defensive_saturating_dec(); + assert_eq!(v, 0); + v.defensive_saturating_dec(); + assert_eq!(v, 0); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Defensive")] + fn defensive_saturating_dec_panics() { + let mut v = 0_u32; + v.defensive_saturating_dec(); // defensive failure + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_truncating_from_vec_defensive_works() { + let unbound = vec![1u32, 2]; + let bound = BoundedVec::>::defensive_truncate_from(unbound); + assert_eq!(bound, vec![1u32]); + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_truncating_from_slice_defensive_works() { + let unbound = &[1u32, 2]; + let bound = BoundedSlice::>::defensive_truncate_from(unbound); + assert_eq!(bound, &[1u32][..]); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic( + expected = "Defensive failure has been triggered!: \"DefensiveTruncateFrom truncating\"" + )] + fn defensive_truncating_from_vec_defensive_panics() { + let unbound = vec![1u32, 2]; + let _ = BoundedVec::>::defensive_truncate_from(unbound); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic( + expected = "Defensive failure has been triggered!: \"DefensiveTruncateFrom truncating\"" + )] + fn defensive_truncating_from_slice_defensive_panics() { + let unbound = &[1u32, 2]; + let _ = BoundedSlice::>::defensive_truncate_from(unbound); + } + + #[test] + fn defensive_truncate_from_vec_works() { + let unbound = vec![1u32, 2, 3]; + let bound = BoundedVec::>::defensive_truncate_from(unbound.clone()); + assert_eq!(bound, unbound); + } + + #[test] + fn defensive_truncate_from_slice_works() { + let unbound = [1u32, 2, 3]; + let bound = BoundedSlice::>::defensive_truncate_from(&unbound); + assert_eq!(bound, &unbound[..]); + } + + #[derive(Encode, Decode)] + enum NestedType { + Nested(Box), + Done, + } + + #[test] + fn test_opaque_wrapper_decode_limit() { + let limit = sp_api::MAX_EXTRINSIC_DEPTH as usize; + let mut ok_bytes = vec![0u8; limit]; + ok_bytes.push(1u8); + let mut err_bytes = vec![0u8; limit + 1]; + err_bytes.push(1u8); + assert!(>::decode(&mut &ok_bytes.encode()[..]).is_ok()); + assert!(>::decode(&mut &err_bytes.encode()[..]).is_err()); + + let ok_keep_opaque = WrapperKeepOpaque { data: ok_bytes, _phantom: PhantomData }; + let err_keep_opaque = WrapperKeepOpaque { data: err_bytes, _phantom: PhantomData }; + + assert!(>::try_decode(&ok_keep_opaque).is_some()); + assert!(>::try_decode(&err_keep_opaque).is_none()); + } + + #[test] + fn test_opaque_wrapper() { + let encoded = WrapperOpaque(3u32).encode(); + assert_eq!(encoded, [codec::Compact(4u32).encode(), 3u32.to_le_bytes().to_vec()].concat()); + let vec_u8 = >::decode(&mut &encoded[..]).unwrap(); + let decoded_from_vec_u8 = u32::decode(&mut &vec_u8[..]).unwrap(); + assert_eq!(decoded_from_vec_u8, 3u32); + let decoded = >::decode(&mut &encoded[..]).unwrap(); + assert_eq!(decoded.0, 3u32); + + assert_eq!(>::max_encoded_len(), 63 + 1); + assert_eq!( + >::max_encoded_len(), + WrapperOpaque([0u8; 63]).encode().len() + ); + + assert_eq!(>::max_encoded_len(), 64 + 2); + assert_eq!( + >::max_encoded_len(), + WrapperOpaque([0u8; 64]).encode().len() + ); + + assert_eq!( + >::max_encoded_len(), + 2usize.pow(14) - 1 + 2 + ); + assert_eq!(>::max_encoded_len(), 2usize.pow(14) + 4); + + let data = 4u64; + // Ensure that we check that the `Vec` is consumed completly on decode. + assert!(WrapperOpaque::::decode(&mut &data.encode().encode()[..]).is_err()); + } + + #[test] + fn test_keep_opaque_wrapper() { + let data = 3u32.encode().encode(); + + let keep_opaque = WrapperKeepOpaque::::decode(&mut &data[..]).unwrap(); + keep_opaque.try_decode().unwrap(); + + let data = WrapperOpaque(50u32).encode(); + let decoded = WrapperKeepOpaque::::decode(&mut &data[..]).unwrap(); + let data = decoded.encode(); + WrapperOpaque::::decode(&mut &data[..]).unwrap(); + } + + #[test] + fn defensive_min_works() { + assert_eq!(10, 10_u32.defensive_min(11_u32)); + assert_eq!(10, 10_u32.defensive_min(10_u32)); + } + + #[test] + #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMin\"")] + fn defensive_min_panics() { + 10_u32.defensive_min(9_u32); + } + + #[test] + fn defensive_strict_min_works() { + assert_eq!(10, 10_u32.defensive_strict_min(11_u32)); + assert_eq!(9, 9_u32.defensive_strict_min(10_u32)); + } + + #[test] + #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMin strict\"")] + fn defensive_strict_min_panics() { + 9_u32.defensive_strict_min(9_u32); + } + + #[test] + fn defensive_max_works() { + assert_eq!(11, 11_u32.defensive_max(10_u32)); + assert_eq!(10, 10_u32.defensive_max(10_u32)); + } + + #[test] + #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMax\"")] + fn defensive_max_panics() { + 9_u32.defensive_max(10_u32); + } + + #[test] + fn defensive_strict_max_works() { + assert_eq!(11, 11_u32.defensive_strict_max(10_u32)); + assert_eq!(10, 10_u32.defensive_strict_max(9_u32)); + } + + #[test] + #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMax strict\"")] + fn defensive_strict_max_panics() { + 9_u32.defensive_strict_max(9_u32); + } +} diff --git a/frame/support/src/traits/preimages.rs b/frame/support/src/traits/preimages.rs new file mode 100644 index 0000000000000..594532ba96903 --- /dev/null +++ b/frame/support/src/traits/preimages.rs @@ -0,0 +1,317 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Stuff for dealing with 32-byte hashed preimages. + +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use sp_core::{RuntimeDebug, H256}; +use sp_io::hashing::blake2_256; +use sp_runtime::{traits::ConstU32, DispatchError}; +use sp_std::borrow::Cow; + +pub type Hash = H256; +pub type BoundedInline = crate::BoundedVec>; + +#[derive( + Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, scale_info::TypeInfo, RuntimeDebug, +)] +#[codec(mel_bound())] +pub enum Bounded { + /// A Blake2 256 hash with no preimage length. We + /// do not support creation of this except for transitioning from legacy state. + /// In the future we will make this a pure `Dummy` item storing only the final `dummy` field. + Legacy { hash: Hash, dummy: sp_std::marker::PhantomData }, + /// A an bounded `Call`. Its encoding must be at most 128 bytes. + Inline(BoundedInline), + /// A Blake2-256 hash of the call together with an upper limit for its size. + Lookup { hash: Hash, len: u32 }, +} + +impl Bounded { + /// Casts the wrapped type into something that encodes alike. + /// + /// # Examples + /// ``` + /// use frame_support::traits::Bounded; + /// + /// // Transmute from `String` to `&str`. + /// let x: Bounded = Bounded::Inline(Default::default()); + /// let _: Bounded<&str> = x.transmute(); + /// ``` + pub fn transmute(self) -> Bounded + where + T: Encode + EncodeLike, + { + use Bounded::*; + match self { + Legacy { hash, .. } => Legacy { hash, dummy: sp_std::marker::PhantomData }, + Inline(x) => Inline(x), + Lookup { hash, len } => Lookup { hash, len }, + } + } + + /// Returns the hash of the preimage. + /// + /// The hash is re-calculated every time if the preimage is inlined. + pub fn hash(&self) -> H256 { + use Bounded::*; + match self { + Legacy { hash, .. } => *hash, + Inline(x) => blake2_256(x.as_ref()).into(), + Lookup { hash, .. } => *hash, + } + } +} + +// The maximum we expect a single legacy hash lookup to be. +const MAX_LEGACY_LEN: u32 = 1_000_000; + +impl Bounded { + /// Returns the length of the preimage or `None` if the length is unknown. + pub fn len(&self) -> Option { + match self { + Self::Legacy { .. } => None, + Self::Inline(i) => Some(i.len() as u32), + Self::Lookup { len, .. } => Some(*len), + } + } + + /// Returns whether the image will require a lookup to be peeked. + pub fn lookup_needed(&self) -> bool { + match self { + Self::Inline(..) => false, + Self::Legacy { .. } | Self::Lookup { .. } => true, + } + } + + /// The maximum length of the lookup that is needed to peek `Self`. + pub fn lookup_len(&self) -> Option { + match self { + Self::Inline(..) => None, + Self::Legacy { .. } => Some(MAX_LEGACY_LEN), + Self::Lookup { len, .. } => Some(*len), + } + } + + /// Constructs a `Lookup` bounded item. + pub fn unrequested(hash: Hash, len: u32) -> Self { + Self::Lookup { hash, len } + } + + /// Constructs a `Legacy` bounded item. + #[deprecated = "This API is only for transitioning to Scheduler v3 API"] + pub fn from_legacy_hash(hash: impl Into) -> Self { + Self::Legacy { hash: hash.into(), dummy: sp_std::marker::PhantomData } + } +} + +pub type FetchResult = Result, DispatchError>; + +/// A interface for looking up preimages from their hash on chain. +pub trait QueryPreimage { + /// Returns whether a preimage exists for a given hash and if so its length. + fn len(hash: &Hash) -> Option; + + /// Returns the preimage for a given hash. If given, `len` must be the size of the preimage. + fn fetch(hash: &Hash, len: Option) -> FetchResult; + + /// Returns whether a preimage request exists for a given hash. + fn is_requested(hash: &Hash) -> bool; + + /// Request that someone report a preimage. Providers use this to optimise the economics for + /// preimage reporting. + fn request(hash: &Hash); + + /// Cancel a previous preimage request. + fn unrequest(hash: &Hash); + + /// Request that the data required for decoding the given `bounded` value is made available. + fn hold(bounded: &Bounded) { + use Bounded::*; + match bounded { + Inline(..) => {}, + Legacy { hash, .. } | Lookup { hash, .. } => Self::request(hash), + } + } + + /// No longer request that the data required for decoding the given `bounded` value is made + /// available. + fn drop(bounded: &Bounded) { + use Bounded::*; + match bounded { + Inline(..) => {}, + Legacy { hash, .. } | Lookup { hash, .. } => Self::unrequest(hash), + } + } + + /// Check to see if all data required for the given `bounded` value is available for its + /// decoding. + fn have(bounded: &Bounded) -> bool { + use Bounded::*; + match bounded { + Inline(..) => true, + Legacy { hash, .. } | Lookup { hash, .. } => Self::len(hash).is_some(), + } + } + + /// Create a `Bounded` instance based on the `hash` and `len` of the encoded value. This may not + /// be `peek`-able or `realize`-able. + fn pick(hash: Hash, len: u32) -> Bounded { + Self::request(&hash); + Bounded::Lookup { hash, len } + } + + /// Convert the given `bounded` instance back into its original instance, also returning the + /// exact size of its encoded form if it needed to be looked-up from a stored preimage). + /// + /// NOTE: This does not remove any data needed for realization. If you will no longer use the + /// `bounded`, call `realize` instead or call `drop` afterwards. + fn peek(bounded: &Bounded) -> Result<(T, Option), DispatchError> { + use Bounded::*; + match bounded { + Inline(data) => T::decode(&mut &data[..]).ok().map(|x| (x, None)), + Lookup { hash, len } => { + let data = Self::fetch(hash, Some(*len))?; + T::decode(&mut &data[..]).ok().map(|x| (x, Some(data.len() as u32))) + }, + Legacy { hash, .. } => { + let data = Self::fetch(hash, None)?; + T::decode(&mut &data[..]).ok().map(|x| (x, Some(data.len() as u32))) + }, + } + .ok_or(DispatchError::Corruption) + } + + /// Convert the given `bounded` value back into its original instance. If successful, + /// `drop` any data backing it. This will not break the realisability of independently + /// created instances of `Bounded` which happen to have identical data. + fn realize(bounded: &Bounded) -> Result<(T, Option), DispatchError> { + let r = Self::peek(bounded)?; + Self::drop(bounded); + Ok(r) + } +} + +/// A interface for managing preimages to hashes on chain. +/// +/// Note that this API does not assume any underlying user is calling, and thus +/// does not handle any preimage ownership or fees. Other system level logic that +/// uses this API should implement that on their own side. +pub trait StorePreimage: QueryPreimage { + /// The maximum length of preimage we can store. + /// + /// This is the maximum length of the *encoded* value that can be passed to `bound`. + const MAX_LENGTH: usize; + + /// Request and attempt to store the bytes of a preimage on chain. + /// + /// May return `DispatchError::Exhausted` if the preimage is just too big. + fn note(bytes: Cow<[u8]>) -> Result; + + /// Attempt to clear a previously noted preimage. Exactly the same as `unrequest` but is + /// provided for symmetry. + fn unnote(hash: &Hash) { + Self::unrequest(hash) + } + + /// Convert an otherwise unbounded or large value into a type ready for placing in storage. The + /// result is a type whose `MaxEncodedLen` is 131 bytes. + /// + /// NOTE: Once this API is used, you should use either `drop` or `realize`. + fn bound(t: T) -> Result, DispatchError> { + let data = t.encode(); + let len = data.len() as u32; + Ok(match BoundedInline::try_from(data) { + Ok(bounded) => Bounded::Inline(bounded), + Err(unbounded) => Bounded::Lookup { hash: Self::note(unbounded.into())?, len }, + }) + } +} + +impl QueryPreimage for () { + fn len(_: &Hash) -> Option { + None + } + fn fetch(_: &Hash, _: Option) -> FetchResult { + Err(DispatchError::Unavailable) + } + fn is_requested(_: &Hash) -> bool { + false + } + fn request(_: &Hash) {} + fn unrequest(_: &Hash) {} +} + +impl StorePreimage for () { + const MAX_LENGTH: usize = 0; + fn note(_: Cow<[u8]>) -> Result { + Err(DispatchError::Exhausted) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{bounded_vec, BoundedVec}; + + #[test] + fn bounded_size_is_correct() { + assert_eq!(> as MaxEncodedLen>::max_encoded_len(), 131); + } + + #[test] + fn bounded_basic_works() { + let data: BoundedVec = bounded_vec![b'a', b'b', b'c']; + let len = data.len() as u32; + let hash = blake2_256(&data).into(); + + // Inline works + { + let bound: Bounded> = Bounded::Inline(data.clone()); + assert_eq!(bound.hash(), hash); + assert_eq!(bound.len(), Some(len)); + assert!(!bound.lookup_needed()); + assert_eq!(bound.lookup_len(), None); + } + // Legacy works + { + let bound: Bounded> = Bounded::Legacy { hash, dummy: Default::default() }; + assert_eq!(bound.hash(), hash); + assert_eq!(bound.len(), None); + assert!(bound.lookup_needed()); + assert_eq!(bound.lookup_len(), Some(1_000_000)); + } + // Lookup works + { + let bound: Bounded> = Bounded::Lookup { hash, len: data.len() as u32 }; + assert_eq!(bound.hash(), hash); + assert_eq!(bound.len(), Some(len)); + assert!(bound.lookup_needed()); + assert_eq!(bound.lookup_len(), Some(len)); + } + } + + #[test] + fn bounded_transmuting_works() { + let data: BoundedVec = bounded_vec![b'a', b'b', b'c']; + + // Transmute a `String` into a `&str`. + let x: Bounded = Bounded::Inline(data.clone()); + let y: Bounded<&str> = x.transmute(); + assert_eq!(y, Bounded::Inline(data)); + } +} diff --git a/frame/support/src/traits/randomness.rs b/frame/support/src/traits/randomness.rs index 865893f99b393..d68b95b1dfcf5 100644 --- a/frame/support/src/traits/randomness.rs +++ b/frame/support/src/traits/randomness.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/schedule.rs b/frame/support/src/traits/schedule.rs index a4a4f9c03ab12..b8e6a7f807904 100644 --- a/frame/support/src/traits/schedule.rs +++ b/frame/support/src/traits/schedule.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,9 +17,12 @@ //! Traits and associated utilities for scheduling dispatchables in FRAME. -use codec::{Codec, Decode, Encode, EncodeLike}; -use sp_runtime::{DispatchError, RuntimeDebug}; -use sp_std::{fmt::Debug, prelude::*}; +#[allow(deprecated)] +use super::PreimageProvider; +use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Saturating, DispatchError, RuntimeDebug}; +use sp_std::{fmt::Debug, prelude::*, result::Result}; /// Information relating to the period of a scheduled task. First item is the length of the /// period and the second is the number of times it should be executed in total before the task @@ -31,7 +34,7 @@ pub type Period = (BlockNumber, u32); pub type Priority = u8; /// The dispatch time of a scheduled task. -#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug)] +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum DispatchTime { /// At specified block. At(BlockNumber), @@ -39,6 +42,15 @@ pub enum DispatchTime { After(BlockNumber), } +impl DispatchTime { + pub fn evaluate(&self, since: BlockNumber) -> BlockNumber { + match &self { + Self::At(m) => *m, + Self::After(m) => m.saturating_add(since), + } + } +} + /// The highest priority. We invert the value so that normal sorting will place the highest /// priority at the beginning of the list. pub const HIGHEST_PRIORITY: Priority = 0; @@ -48,86 +60,416 @@ pub const HARD_DEADLINE: Priority = 63; /// The lowest priority. Most stuff should be around here. pub const LOWEST_PRIORITY: Priority = 255; -/// A type that can be used as a scheduler. -pub trait Anon { - /// An address which can be used for removing a scheduled task. - type Address: Codec + Clone + Eq + EncodeLike + Debug; - - /// Schedule a dispatch to happen at the beginning of some block in the future. - /// - /// This is not named. - fn schedule( - when: DispatchTime, - maybe_periodic: Option>, - priority: Priority, - origin: Origin, - call: Call, - ) -> Result; - - /// Cancel a scheduled task. If periodic, then it will cancel all further instances of that, - /// also. - /// - /// Will return an error if the `address` is invalid. - /// - /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. - /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. - /// - /// NOTE2: This will not work to cancel periodic tasks after their initial execution. For - /// that, you must name the task explicitly using the `Named` trait. - fn cancel(address: Self::Address) -> Result<(), ()>; - - /// Reschedule a task. For one-off tasks, this dispatch is guaranteed to succeed - /// only if it is executed *before* the currently scheduled block. For periodic tasks, - /// this dispatch is guaranteed to succeed only before the *initial* execution; for - /// others, use `reschedule_named`. - /// - /// Will return an error if the `address` is invalid. - fn reschedule( - address: Self::Address, - when: DispatchTime, - ) -> Result; - - /// Return the next dispatch time for a given task. - /// - /// Will return an error if the `address` is invalid. - fn next_dispatch_time(address: Self::Address) -> Result; +/// Type representing an encodable value or the hash of the encoding of such a value. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum MaybeHashed { + /// The value itself. + Value(T), + /// The hash of the encoded value which this value represents. + Hash(Hash), +} + +impl From for MaybeHashed { + fn from(t: T) -> Self { + MaybeHashed::Value(t) + } +} + +/// Error type for `MaybeHashed::lookup`. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum LookupError { + /// A call of this hash was not known. + Unknown, + /// The preimage for this hash was known but could not be decoded into a `Call`. + BadFormat, +} + +impl MaybeHashed { + pub fn as_value(&self) -> Option<&T> { + match &self { + Self::Value(c) => Some(c), + Self::Hash(_) => None, + } + } + + pub fn as_hash(&self) -> Option<&H> { + match &self { + Self::Value(_) => None, + Self::Hash(h) => Some(h), + } + } + + pub fn ensure_requested>(&self) { + match &self { + Self::Value(_) => (), + Self::Hash(hash) => P::request_preimage(hash), + } + } + + pub fn ensure_unrequested>(&self) { + match &self { + Self::Value(_) => (), + Self::Hash(hash) => P::unrequest_preimage(hash), + } + } + + pub fn resolved>(self) -> (Self, Option) { + match self { + Self::Value(c) => (Self::Value(c), None), + Self::Hash(h) => { + let data = match P::get_preimage(&h) { + Some(p) => p, + None => return (Self::Hash(h), None), + }; + match T::decode(&mut &data[..]) { + Ok(c) => (Self::Value(c), Some(h)), + Err(_) => (Self::Hash(h), None), + } + }, + } + } +} + +// TODO: deprecate +pub mod v1 { + use super::*; + + /// A type that can be used as a scheduler. + pub trait Anon { + /// An address which can be used for removing a scheduled task. + type Address: Codec + Clone + Eq + EncodeLike + Debug + TypeInfo + MaxEncodedLen; + + /// Schedule a dispatch to happen at the beginning of some block in the future. + /// + /// This is not named. + fn schedule( + when: DispatchTime, + maybe_periodic: Option>, + priority: Priority, + origin: RuntimeOrigin, + call: Call, + ) -> Result; + + /// Cancel a scheduled task. If periodic, then it will cancel all further instances of that, + /// also. + /// + /// Will return an error if the `address` is invalid. + /// + /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. + /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. + /// + /// NOTE2: This will not work to cancel periodic tasks after their initial execution. For + /// that, you must name the task explicitly using the `Named` trait. + fn cancel(address: Self::Address) -> Result<(), ()>; + + /// Reschedule a task. For one-off tasks, this dispatch is guaranteed to succeed + /// only if it is executed *before* the currently scheduled block. For periodic tasks, + /// this dispatch is guaranteed to succeed only before the *initial* execution; for + /// others, use `reschedule_named`. + /// + /// Will return an error if the `address` is invalid. + fn reschedule( + address: Self::Address, + when: DispatchTime, + ) -> Result; + + /// Return the next dispatch time for a given task. + /// + /// Will return an error if the `address` is invalid. + fn next_dispatch_time(address: Self::Address) -> Result; + } + + /// A type that can be used as a scheduler. + pub trait Named { + /// An address which can be used for removing a scheduled task. + type Address: Codec + Clone + Eq + EncodeLike + sp_std::fmt::Debug + MaxEncodedLen; + + /// Schedule a dispatch to happen at the beginning of some block in the future. + /// + /// - `id`: The identity of the task. This must be unique and will return an error if not. + fn schedule_named( + id: Vec, + when: DispatchTime, + maybe_periodic: Option>, + priority: Priority, + origin: RuntimeOrigin, + call: Call, + ) -> Result; + + /// Cancel a scheduled, named task. If periodic, then it will cancel all further instances + /// of that, also. + /// + /// Will return an error if the `id` is invalid. + /// + /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. + /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. + fn cancel_named(id: Vec) -> Result<(), ()>; + + /// Reschedule a task. For one-off tasks, this dispatch is guaranteed to succeed + /// only if it is executed *before* the currently scheduled block. + fn reschedule_named( + id: Vec, + when: DispatchTime, + ) -> Result; + + /// Return the next dispatch time for a given task. + /// + /// Will return an error if the `id` is invalid. + fn next_dispatch_time(id: Vec) -> Result; + } + + impl Anon for T + where + T: v2::Anon, + { + type Address = T::Address; + + fn schedule( + when: DispatchTime, + maybe_periodic: Option>, + priority: Priority, + origin: RuntimeOrigin, + call: Call, + ) -> Result { + let c = MaybeHashed::::Value(call); + T::schedule(when, maybe_periodic, priority, origin, c) + } + + fn cancel(address: Self::Address) -> Result<(), ()> { + T::cancel(address) + } + + fn reschedule( + address: Self::Address, + when: DispatchTime, + ) -> Result { + T::reschedule(address, when) + } + + fn next_dispatch_time(address: Self::Address) -> Result { + T::next_dispatch_time(address) + } + } + + impl Named for T + where + T: v2::Named, + { + type Address = T::Address; + + fn schedule_named( + id: Vec, + when: DispatchTime, + maybe_periodic: Option>, + priority: Priority, + origin: RuntimeOrigin, + call: Call, + ) -> Result { + let c = MaybeHashed::::Value(call); + T::schedule_named(id, when, maybe_periodic, priority, origin, c) + } + + fn cancel_named(id: Vec) -> Result<(), ()> { + T::cancel_named(id) + } + + fn reschedule_named( + id: Vec, + when: DispatchTime, + ) -> Result { + T::reschedule_named(id, when) + } + + fn next_dispatch_time(id: Vec) -> Result { + T::next_dispatch_time(id) + } + } } -/// A type that can be used as a scheduler. -pub trait Named { - /// An address which can be used for removing a scheduled task. - type Address: Codec + Clone + Eq + EncodeLike + sp_std::fmt::Debug; - - /// Schedule a dispatch to happen at the beginning of some block in the future. - /// - /// - `id`: The identity of the task. This must be unique and will return an error if not. - fn schedule_named( - id: Vec, - when: DispatchTime, - maybe_periodic: Option>, - priority: Priority, - origin: Origin, - call: Call, - ) -> Result; - - /// Cancel a scheduled, named task. If periodic, then it will cancel all further instances - /// of that, also. - /// - /// Will return an error if the `id` is invalid. - /// - /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. - /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. - fn cancel_named(id: Vec) -> Result<(), ()>; - - /// Reschedule a task. For one-off tasks, this dispatch is guaranteed to succeed - /// only if it is executed *before* the currently scheduled block. - fn reschedule_named( - id: Vec, - when: DispatchTime, - ) -> Result; - - /// Return the next dispatch time for a given task. - /// - /// Will return an error if the `id` is invalid. - fn next_dispatch_time(id: Vec) -> Result; +// TODO: deprecate +pub mod v2 { + use super::*; + + /// A type that can be used as a scheduler. + pub trait Anon { + /// An address which can be used for removing a scheduled task. + type Address: Codec + Clone + Eq + EncodeLike + Debug + TypeInfo + MaxEncodedLen; + /// A means of expressing a call by the hash of its encoded data. + type Hash; + + /// Schedule a dispatch to happen at the beginning of some block in the future. + /// + /// This is not named. + fn schedule( + when: DispatchTime, + maybe_periodic: Option>, + priority: Priority, + origin: RuntimeOrigin, + call: MaybeHashed, + ) -> Result; + + /// Cancel a scheduled task. If periodic, then it will cancel all further instances of that, + /// also. + /// + /// Will return an error if the `address` is invalid. + /// + /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. + /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. + /// + /// NOTE2: This will not work to cancel periodic tasks after their initial execution. For + /// that, you must name the task explicitly using the `Named` trait. + fn cancel(address: Self::Address) -> Result<(), ()>; + + /// Reschedule a task. For one-off tasks, this dispatch is guaranteed to succeed + /// only if it is executed *before* the currently scheduled block. For periodic tasks, + /// this dispatch is guaranteed to succeed only before the *initial* execution; for + /// others, use `reschedule_named`. + /// + /// Will return an error if the `address` is invalid. + fn reschedule( + address: Self::Address, + when: DispatchTime, + ) -> Result; + + /// Return the next dispatch time for a given task. + /// + /// Will return an error if the `address` is invalid. + fn next_dispatch_time(address: Self::Address) -> Result; + } + + /// A type that can be used as a scheduler. + pub trait Named { + /// An address which can be used for removing a scheduled task. + type Address: Codec + Clone + Eq + EncodeLike + sp_std::fmt::Debug + MaxEncodedLen; + /// A means of expressing a call by the hash of its encoded data. + type Hash; + + /// Schedule a dispatch to happen at the beginning of some block in the future. + /// + /// - `id`: The identity of the task. This must be unique and will return an error if not. + fn schedule_named( + id: Vec, + when: DispatchTime, + maybe_periodic: Option>, + priority: Priority, + origin: RuntimeOrigin, + call: MaybeHashed, + ) -> Result; + + /// Cancel a scheduled, named task. If periodic, then it will cancel all further instances + /// of that, also. + /// + /// Will return an error if the `id` is invalid. + /// + /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. + /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. + fn cancel_named(id: Vec) -> Result<(), ()>; + + /// Reschedule a task. For one-off tasks, this dispatch is guaranteed to succeed + /// only if it is executed *before* the currently scheduled block. + fn reschedule_named( + id: Vec, + when: DispatchTime, + ) -> Result; + + /// Return the next dispatch time for a given task. + /// + /// Will return an error if the `id` is invalid. + fn next_dispatch_time(id: Vec) -> Result; + } } + +pub mod v3 { + use super::*; + use crate::traits::Bounded; + + /// A type that can be used as a scheduler. + pub trait Anon { + /// An address which can be used for removing a scheduled task. + type Address: Codec + MaxEncodedLen + Clone + Eq + EncodeLike + Debug + TypeInfo; + + /// Schedule a dispatch to happen at the beginning of some block in the future. + /// + /// This is not named. + fn schedule( + when: DispatchTime, + maybe_periodic: Option>, + priority: Priority, + origin: Origin, + call: Bounded, + ) -> Result; + + /// Cancel a scheduled task. If periodic, then it will cancel all further instances of that, + /// also. + /// + /// Will return an `Unavailable` error if the `address` is invalid. + /// + /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. + /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. + /// + /// NOTE2: This will not work to cancel periodic tasks after their initial execution. For + /// that, you must name the task explicitly using the `Named` trait. + fn cancel(address: Self::Address) -> Result<(), DispatchError>; + + /// Reschedule a task. For one-off tasks, this dispatch is guaranteed to succeed + /// only if it is executed *before* the currently scheduled block. For periodic tasks, + /// this dispatch is guaranteed to succeed only before the *initial* execution; for + /// others, use `reschedule_named`. + /// + /// Will return an `Unavailable` error if the `address` is invalid. + fn reschedule( + address: Self::Address, + when: DispatchTime, + ) -> Result; + + /// Return the next dispatch time for a given task. + /// + /// Will return an `Unavailable` error if the `address` is invalid. + fn next_dispatch_time(address: Self::Address) -> Result; + } + + pub type TaskName = [u8; 32]; + + /// A type that can be used as a scheduler. + pub trait Named { + /// An address which can be used for removing a scheduled task. + type Address: Codec + MaxEncodedLen + Clone + Eq + EncodeLike + sp_std::fmt::Debug; + + /// Schedule a dispatch to happen at the beginning of some block in the future. + /// + /// - `id`: The identity of the task. This must be unique and will return an error if not. + fn schedule_named( + id: TaskName, + when: DispatchTime, + maybe_periodic: Option>, + priority: Priority, + origin: Origin, + call: Bounded, + ) -> Result; + + /// Cancel a scheduled, named task. If periodic, then it will cancel all further instances + /// of that, also. + /// + /// Will return an `Unavailable` error if the `id` is invalid. + /// + /// NOTE: This guaranteed to work only *before* the point that it is due to be executed. + /// If it ends up being delayed beyond the point of execution, then it cannot be cancelled. + fn cancel_named(id: TaskName) -> Result<(), DispatchError>; + + /// Reschedule a task. For one-off tasks, this dispatch is guaranteed to succeed + /// only if it is executed *before* the currently scheduled block. + /// + /// Will return an `Unavailable` error if the `id` is invalid. + fn reschedule_named( + id: TaskName, + when: DispatchTime, + ) -> Result; + + /// Return the next dispatch time for a given task. + /// + /// Will return an `Unavailable` error if the `id` is invalid. + fn next_dispatch_time(id: TaskName) -> Result; + } +} + +pub use v1::*; diff --git a/frame/support/src/traits/storage.rs b/frame/support/src/traits/storage.rs index 9a88a3ed44046..24653d1899836 100644 --- a/frame/support/src/traits/storage.rs +++ b/frame/support/src/traits/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,9 @@ //! Traits for encoding data related to pallet's storage items. +use crate::sp_std::collections::btree_set::BTreeSet; +use impl_trait_for_tuples::impl_for_tuples; +pub use sp_core::storage::TrackedStorageKey; use sp_std::prelude::*; /// An instance of a pallet in the storage. @@ -71,7 +74,9 @@ pub trait StorageInfoTrait { fn storage_info() -> Vec; } -#[impl_trait_for_tuples::impl_for_tuples(30)] +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] impl StorageInfoTrait for Tuple { fn storage_info() -> Vec { let mut res = vec![]; @@ -87,3 +92,29 @@ impl StorageInfoTrait for Tuple { pub trait PartialStorageInfoTrait { fn partial_storage_info() -> Vec; } + +/// Allows a pallet to specify storage keys to whitelist during benchmarking. +/// This means those keys will be excluded from the benchmarking performance +/// calculation. +pub trait WhitelistedStorageKeys { + /// Returns a [`Vec`] indicating the storage keys that + /// should be whitelisted during benchmarking. This means that those keys + /// will be excluded from the benchmarking performance calculation. + fn whitelisted_storage_keys() -> Vec; +} + +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] +impl WhitelistedStorageKeys for Tuple { + fn whitelisted_storage_keys() -> Vec { + // de-duplicate the storage keys + let mut combined_keys: BTreeSet = BTreeSet::new(); + for_tuples!( #( + for storage_key in Tuple::whitelisted_storage_keys() { + combined_keys.insert(storage_key); + } + )* ); + combined_keys.into_iter().collect::>() + } +} diff --git a/frame/support/src/traits/stored_map.rs b/frame/support/src/traits/stored_map.rs index 715a5211be430..2aae88096ec74 100644 --- a/frame/support/src/traits/stored_map.rs +++ b/frame/support/src/traits/stored_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,9 @@ pub trait StoredMap { fn get(k: &K) -> T; /// Maybe mutate the item only if an `Ok` value is returned from `f`. Do nothing if an `Err` is - /// returned. It is removed or reset to default value if it has been mutated to `None` + /// returned. It is removed or reset to default value if it has been mutated to `None`. + /// `f` will always be called with an option representing if the storage item exists (`Some`) + /// or if the storage item does not exist (`None`), independent of the `QueryType`. fn try_mutate_exists>( k: &K, f: impl FnOnce(&mut Option) -> Result, @@ -99,7 +101,7 @@ impl< } fn remove(k: &K) -> Result<(), DispatchError> { if S::contains_key(&k) { - L::killed(&k)?; + L::killed(k)?; S::remove(k); } Ok(()) diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index aca62bcad65c7..77eb83adfbfb0 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,6 @@ pub mod nonfungible; pub mod nonfungibles; pub use imbalance::Imbalance; pub use misc::{ - BalanceConversion, BalanceStatus, DepositConsequence, ExistenceRequirement, - WithdrawConsequence, WithdrawReasons, + AssetId, Balance, BalanceConversion, BalanceStatus, DepositConsequence, ExistenceRequirement, + Locker, WithdrawConsequence, WithdrawReasons, }; diff --git a/frame/support/src/traits/tokens/currency.rs b/frame/support/src/traits/tokens/currency.rs index bf078658477f5..48247b6021798 100644 --- a/frame/support/src/traits/tokens/currency.rs +++ b/frame/support/src/traits/tokens/currency.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,12 @@ use super::{ imbalance::{Imbalance, SignedImbalance}, misc::{Balance, ExistenceRequirement, WithdrawReasons}, }; -use crate::dispatch::{DispatchError, DispatchResult}; +use crate::{ + dispatch::{DispatchError, DispatchResult}, + traits::Get, +}; use codec::MaxEncodedLen; -use sp_runtime::traits::MaybeSerializeDeserialize; +use sp_runtime::{traits::MaybeSerializeDeserialize, FixedPointOperand}; use sp_std::fmt::Debug; mod reservable; @@ -34,7 +37,7 @@ pub use lockable::{LockIdentifier, LockableCurrency, VestingSchedule}; /// Abstraction over a fungible assets system. pub trait Currency { /// The balance of an account. - type Balance: Balance + MaybeSerializeDeserialize + Debug + MaxEncodedLen; + type Balance: Balance + MaybeSerializeDeserialize + Debug + MaxEncodedLen + FixedPointOperand; /// The opaque token type for an imbalance. This is returned by unbalanced operations /// and must be dealt with. It may be dropped but cannot be cloned. @@ -56,6 +59,18 @@ pub trait Currency { /// The total amount of issuance in the system. fn total_issuance() -> Self::Balance; + /// The total amount of issuance in the system excluding those which are controlled by the + /// system. + fn active_issuance() -> Self::Balance { + Self::total_issuance() + } + + /// Reduce the active issuance by some amount. + fn deactivate(_: Self::Balance) {} + + /// Increase the active issuance by some amount, up to the outstanding amount reduced. + fn reactivate(_: Self::Balance) {} + /// The minimum balance any single account may have. This is equivalent to the `Balances` /// module's `ExistentialDeposit`. fn minimum_balance() -> Self::Balance; @@ -80,7 +95,7 @@ pub trait Currency { /// This is just the same as burning and issuing the same amount and has no effect on the /// total issuance. fn pair(amount: Self::Balance) -> (Self::PositiveImbalance, Self::NegativeImbalance) { - (Self::burn(amount.clone()), Self::issue(amount)) + (Self::burn(amount), Self::issue(amount)) } /// The 'free' balance of a given account. @@ -199,3 +214,109 @@ pub trait Currency { balance: Self::Balance, ) -> SignedImbalance; } + +/// A non-const `Get` implementation parameterised by a `Currency` impl which provides the result +/// of `total_issuance`. +pub struct TotalIssuanceOf, A>(sp_std::marker::PhantomData<(C, A)>); +impl, A> Get for TotalIssuanceOf { + fn get() -> C::Balance { + C::total_issuance() + } +} + +/// A non-const `Get` implementation parameterised by a `Currency` impl which provides the result +/// of `active_issuance`. +pub struct ActiveIssuanceOf, A>(sp_std::marker::PhantomData<(C, A)>); +impl, A> Get for ActiveIssuanceOf { + fn get() -> C::Balance { + C::active_issuance() + } +} + +#[cfg(feature = "std")] +impl Currency for () { + type Balance = u32; + type PositiveImbalance = (); + type NegativeImbalance = (); + fn total_balance(_: &AccountId) -> Self::Balance { + 0 + } + fn can_slash(_: &AccountId, _: Self::Balance) -> bool { + true + } + fn total_issuance() -> Self::Balance { + 0 + } + fn minimum_balance() -> Self::Balance { + 0 + } + fn burn(_: Self::Balance) -> Self::PositiveImbalance { + () + } + fn issue(_: Self::Balance) -> Self::NegativeImbalance { + () + } + fn pair(_: Self::Balance) -> (Self::PositiveImbalance, Self::NegativeImbalance) { + ((), ()) + } + fn free_balance(_: &AccountId) -> Self::Balance { + 0 + } + fn ensure_can_withdraw( + _: &AccountId, + _: Self::Balance, + _: WithdrawReasons, + _: Self::Balance, + ) -> DispatchResult { + Ok(()) + } + fn transfer( + _: &AccountId, + _: &AccountId, + _: Self::Balance, + _: ExistenceRequirement, + ) -> DispatchResult { + Ok(()) + } + fn slash(_: &AccountId, _: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { + ((), 0) + } + fn deposit_into_existing( + _: &AccountId, + _: Self::Balance, + ) -> Result { + Ok(()) + } + fn resolve_into_existing( + _: &AccountId, + _: Self::NegativeImbalance, + ) -> Result<(), Self::NegativeImbalance> { + Ok(()) + } + fn deposit_creating(_: &AccountId, _: Self::Balance) -> Self::PositiveImbalance { + () + } + fn resolve_creating(_: &AccountId, _: Self::NegativeImbalance) {} + fn withdraw( + _: &AccountId, + _: Self::Balance, + _: WithdrawReasons, + _: ExistenceRequirement, + ) -> Result { + Ok(()) + } + fn settle( + _: &AccountId, + _: Self::PositiveImbalance, + _: WithdrawReasons, + _: ExistenceRequirement, + ) -> Result<(), Self::PositiveImbalance> { + Ok(()) + } + fn make_free_balance_be( + _: &AccountId, + _: Self::Balance, + ) -> SignedImbalance { + SignedImbalance::Positive(()) + } +} diff --git a/frame/support/src/traits/tokens/currency/lockable.rs b/frame/support/src/traits/tokens/currency/lockable.rs index 26463864a6471..a10edd6e3e874 100644 --- a/frame/support/src/traits/tokens/currency/lockable.rs +++ b/frame/support/src/traits/tokens/currency/lockable.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/currency/reservable.rs b/frame/support/src/traits/tokens/currency/reservable.rs index 0ca7a93dc7f69..53f6764c3a1ac 100644 --- a/frame/support/src/traits/tokens/currency/reservable.rs +++ b/frame/support/src/traits/tokens/currency/reservable.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,14 @@ //! The reservable currency trait. +use scale_info::TypeInfo; +use sp_core::Get; + use super::{super::misc::BalanceStatus, Currency}; -use crate::dispatch::{DispatchError, DispatchResult}; +use crate::{ + dispatch::{DispatchError, DispatchResult}, + traits::{ExistenceRequirement, SignedImbalance, WithdrawReasons}, +}; /// A currency where funds can be reserved from the user. pub trait ReservableCurrency: Currency { @@ -81,10 +87,37 @@ pub trait ReservableCurrency: Currency { ) -> Result; } +#[cfg(feature = "std")] +impl ReservableCurrency for () { + fn can_reserve(_: &AccountId, _: Self::Balance) -> bool { + true + } + fn slash_reserved(_: &AccountId, _: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { + ((), 0) + } + fn reserved_balance(_: &AccountId) -> Self::Balance { + 0 + } + fn reserve(_: &AccountId, _: Self::Balance) -> DispatchResult { + Ok(()) + } + fn unreserve(_: &AccountId, _: Self::Balance) -> Self::Balance { + 0 + } + fn repatriate_reserved( + _: &AccountId, + _: &AccountId, + _: Self::Balance, + _: BalanceStatus, + ) -> Result { + Ok(0) + } +} + pub trait NamedReservableCurrency: ReservableCurrency { /// An identifier for a reserve. Used for disambiguating different reserves so that /// they can be individually replaced or removed. - type ReserveIdentifier; + type ReserveIdentifier: codec::Encode + TypeInfo + 'static; /// Deducts up to `value` from reserved balance of `who`. This function cannot fail. /// @@ -209,3 +242,144 @@ pub trait NamedReservableCurrency: ReservableCurrency { Self::repatriate_reserved_named(id, slashed, beneficiary, value, status).map(|_| ()) } } + +/// Adapter to allow a `NamedReservableCurrency` to be passed as regular `ReservableCurrency` +/// together with an `Id`. +/// +/// All "anonymous" operations are then implemented as their named counterparts with the given `Id`. +pub struct WithName( + sp_std::marker::PhantomData<(NamedReservable, Id, AccountId)>, +); +impl< + NamedReservable: NamedReservableCurrency, + Id: Get, + AccountId, + > Currency for WithName +{ + type Balance = >::Balance; + type PositiveImbalance = >::PositiveImbalance; + type NegativeImbalance = >::NegativeImbalance; + + fn total_balance(who: &AccountId) -> Self::Balance { + NamedReservable::total_balance(who) + } + fn can_slash(who: &AccountId, value: Self::Balance) -> bool { + NamedReservable::can_slash(who, value) + } + fn total_issuance() -> Self::Balance { + NamedReservable::total_issuance() + } + fn minimum_balance() -> Self::Balance { + NamedReservable::minimum_balance() + } + fn burn(amount: Self::Balance) -> Self::PositiveImbalance { + NamedReservable::burn(amount) + } + fn issue(amount: Self::Balance) -> Self::NegativeImbalance { + NamedReservable::issue(amount) + } + fn pair(amount: Self::Balance) -> (Self::PositiveImbalance, Self::NegativeImbalance) { + NamedReservable::pair(amount) + } + fn free_balance(who: &AccountId) -> Self::Balance { + NamedReservable::free_balance(who) + } + fn ensure_can_withdraw( + who: &AccountId, + amount: Self::Balance, + reasons: WithdrawReasons, + new_balance: Self::Balance, + ) -> DispatchResult { + NamedReservable::ensure_can_withdraw(who, amount, reasons, new_balance) + } + + fn transfer( + source: &AccountId, + dest: &AccountId, + value: Self::Balance, + existence_requirement: ExistenceRequirement, + ) -> DispatchResult { + NamedReservable::transfer(source, dest, value, existence_requirement) + } + fn slash(who: &AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { + NamedReservable::slash(who, value) + } + fn deposit_into_existing( + who: &AccountId, + value: Self::Balance, + ) -> Result { + NamedReservable::deposit_into_existing(who, value) + } + fn resolve_into_existing( + who: &AccountId, + value: Self::NegativeImbalance, + ) -> Result<(), Self::NegativeImbalance> { + NamedReservable::resolve_into_existing(who, value) + } + fn deposit_creating(who: &AccountId, value: Self::Balance) -> Self::PositiveImbalance { + NamedReservable::deposit_creating(who, value) + } + fn resolve_creating(who: &AccountId, value: Self::NegativeImbalance) { + NamedReservable::resolve_creating(who, value) + } + fn withdraw( + who: &AccountId, + value: Self::Balance, + reasons: WithdrawReasons, + liveness: ExistenceRequirement, + ) -> Result { + NamedReservable::withdraw(who, value, reasons, liveness) + } + fn settle( + who: &AccountId, + value: Self::PositiveImbalance, + reasons: WithdrawReasons, + liveness: ExistenceRequirement, + ) -> Result<(), Self::PositiveImbalance> { + NamedReservable::settle(who, value, reasons, liveness) + } + fn make_free_balance_be( + who: &AccountId, + balance: Self::Balance, + ) -> SignedImbalance { + NamedReservable::make_free_balance_be(who, balance) + } +} +impl< + NamedReservable: NamedReservableCurrency, + Id: Get, + AccountId, + > ReservableCurrency for WithName +{ + fn can_reserve(who: &AccountId, value: Self::Balance) -> bool { + NamedReservable::can_reserve(who, value) + } + + fn slash_reserved( + who: &AccountId, + value: Self::Balance, + ) -> (Self::NegativeImbalance, Self::Balance) { + NamedReservable::slash_reserved_named(&Id::get(), who, value) + } + + fn reserved_balance(who: &AccountId) -> Self::Balance { + NamedReservable::reserved_balance_named(&Id::get(), who) + } + + fn reserve(who: &AccountId, value: Self::Balance) -> DispatchResult { + NamedReservable::reserve_named(&Id::get(), who, value) + } + + fn unreserve(who: &AccountId, value: Self::Balance) -> Self::Balance { + NamedReservable::unreserve_named(&Id::get(), who, value) + } + + fn repatriate_reserved( + slashed: &AccountId, + beneficiary: &AccountId, + value: Self::Balance, + status: BalanceStatus, + ) -> Result { + NamedReservable::repatriate_reserved_named(&Id::get(), slashed, beneficiary, value, status) + } +} diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs index b033236d447bb..05e109b870ec0 100644 --- a/frame/support/src/traits/tokens/fungible.rs +++ b/frame/support/src/traits/tokens/fungible.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,6 +40,12 @@ pub trait Inspect { /// The total amount of issuance in the system. fn total_issuance() -> Self::Balance; + /// The total amount of issuance in the system excluding those which are controlled by the + /// system. + fn active_issuance() -> Self::Balance { + Self::total_issuance() + } + /// The minimum balance any single account may have. fn minimum_balance() -> Self::Balance; @@ -50,7 +56,11 @@ pub trait Inspect { fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance; /// Returns `true` if the balance of `who` may be increased by `amount`. - fn can_deposit(who: &AccountId, amount: Self::Balance) -> DepositConsequence; + /// + /// - `who`: The account of which the balance should be increased by `amount`. + /// - `amount`: How much should the balance be increased? + /// - `mint`: Will `amount` be minted to deposit it into `account`? + fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence; /// Returns `Failed` if the balance of `who` may not be decreased by `amount`, otherwise /// the consequence. @@ -73,7 +83,7 @@ pub trait Mutate: Inspect { /// is returned and nothing is changed. If successful, the amount of tokens reduced is returned. /// /// The default implementation just uses `withdraw` along with `reducible_balance` to ensure - /// that is doesn't fail. + /// that it doesn't fail. fn slash(who: &AccountId, amount: Self::Balance) -> Result { Self::burn_from(who, Self::reducible_balance(who, false).min(amount)) } @@ -86,7 +96,9 @@ pub trait Mutate: Inspect { amount: Self::Balance, ) -> Result { let extra = Self::can_withdraw(&source, amount).into_result()?; - Self::can_deposit(&dest, amount.saturating_add(extra)).into_result()?; + // As we first burn and then mint, we don't need to check if `mint` fits into the supply. + // If we can withdraw/burn it, we can also mint it again. + Self::can_deposit(dest, amount.saturating_add(extra), false).into_result()?; let actual = Self::burn_from(source, amount)?; debug_assert!( actual == amount.saturating_add(extra), @@ -114,6 +126,12 @@ pub trait Transfer: Inspect { amount: Self::Balance, keep_alive: bool, ) -> Result; + + /// Reduce the active issuance by some amount. + fn deactivate(_: Self::Balance) {} + + /// Increase the active issuance by some amount, up to the outstanding amount reduced. + fn reactivate(_: Self::Balance) {} } /// Trait for inspecting a fungible asset which can be reserved. @@ -207,6 +225,9 @@ impl< fn total_issuance() -> Self::Balance { >::total_issuance(A::get()) } + fn active_issuance() -> Self::Balance { + >::active_issuance(A::get()) + } fn minimum_balance() -> Self::Balance { >::minimum_balance(A::get()) } @@ -216,8 +237,8 @@ impl< fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance { >::reducible_balance(A::get(), who, keep_alive) } - fn can_deposit(who: &AccountId, amount: Self::Balance) -> DepositConsequence { - >::can_deposit(A::get(), who, amount) + fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence { + >::can_deposit(A::get(), who, amount, mint) } fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence { >::can_withdraw(A::get(), who, amount) @@ -252,6 +273,12 @@ impl< ) -> Result { >::transfer(A::get(), source, dest, amount, keep_alive) } + fn deactivate(amount: Self::Balance) { + >::deactivate(A::get(), amount) + } + fn reactivate(amount: Self::Balance) { + >::reactivate(A::get(), amount) + } } impl< diff --git a/frame/support/src/traits/tokens/fungible/balanced.rs b/frame/support/src/traits/tokens/fungible/balanced.rs index 7b33a595a1b55..0e75ccc22d050 100644 --- a/frame/support/src/traits/tokens/fungible/balanced.rs +++ b/frame/support/src/traits/tokens/fungible/balanced.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -164,8 +164,8 @@ pub trait Unbalanced: Inspect { amount: Self::Balance, ) -> Result { let old_balance = Self::balance(who); - let (mut new_balance, mut amount) = if old_balance < amount { - Err(TokenError::NoFunds)? + let (mut new_balance, mut amount) = if Self::reducible_balance(who, false) < amount { + return Err(TokenError::NoFunds.into()) } else { (old_balance - amount, amount) }; @@ -186,8 +186,9 @@ pub trait Unbalanced: Inspect { /// Return the imbalance by which the account was reduced. fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance { let old_balance = Self::balance(who); - let (mut new_balance, mut amount) = if old_balance < amount { - (Zero::zero(), old_balance) + let old_free_balance = Self::reducible_balance(who, false); + let (mut new_balance, mut amount) = if old_free_balance < amount { + (old_balance.saturating_sub(old_free_balance), old_free_balance) } else { (old_balance - amount, amount) }; @@ -225,7 +226,7 @@ pub trait Unbalanced: Inspect { let old_balance = Self::balance(who); let new_balance = old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?; if new_balance < Self::minimum_balance() { - Err(TokenError::BelowMinimum)? + return Err(TokenError::BelowMinimum.into()) } if old_balance != new_balance { Self::set_balance(who, new_balance)?; diff --git a/frame/support/src/traits/tokens/fungible/imbalance.rs b/frame/support/src/traits/tokens/fungible/imbalance.rs index 362e0c126d996..ca911cf12d44c 100644 --- a/frame/support/src/traits/tokens/fungible/imbalance.rs +++ b/frame/support/src/traits/tokens/fungible/imbalance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index 3f5a1c75860c2..a29cb974fe450 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,11 +23,17 @@ use super::{ }; use crate::dispatch::{DispatchError, DispatchResult}; use sp_runtime::traits::Saturating; +use sp_std::vec::Vec; +pub mod approvals; mod balanced; +pub mod enumerable; +pub use enumerable::InspectEnumerable; +pub mod metadata; pub use balanced::{Balanced, Unbalanced}; mod imbalance; pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance}; +pub mod roles; /// Trait for providing balance-inspection access to a set of named fungible assets. pub trait Inspect { @@ -40,6 +46,12 @@ pub trait Inspect { /// The total amount of issuance in the system. fn total_issuance(asset: Self::AssetId) -> Self::Balance; + /// The total amount of issuance in the system excluding those which are controlled by the + /// system. + fn active_issuance(asset: Self::AssetId) -> Self::Balance { + Self::total_issuance(asset) + } + /// The minimum balance any single account may have. fn minimum_balance(asset: Self::AssetId) -> Self::Balance; @@ -50,10 +62,16 @@ pub trait Inspect { fn reducible_balance(asset: Self::AssetId, who: &AccountId, keep_alive: bool) -> Self::Balance; /// Returns `true` if the `asset` balance of `who` may be increased by `amount`. + /// + /// - `asset`: The asset that should be deposited. + /// - `who`: The account of which the balance should be increased by `amount`. + /// - `amount`: How much should the balance be increased? + /// - `mint`: Will `amount` be minted to deposit it into `account`? fn can_deposit( asset: Self::AssetId, who: &AccountId, amount: Self::Balance, + mint: bool, ) -> DepositConsequence; /// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise @@ -63,6 +81,21 @@ pub trait Inspect { who: &AccountId, amount: Self::Balance, ) -> WithdrawConsequence; + + /// Returns `true` if an `asset` exists. + fn asset_exists(asset: Self::AssetId) -> bool; +} + +/// Trait for reading metadata from a fungible asset. +pub trait InspectMetadata: Inspect { + /// Return the name of an asset. + fn name(asset: &Self::AssetId) -> Vec; + + /// Return the symbol of an asset. + fn symbol(asset: &Self::AssetId) -> Vec; + + /// Return the decimals of an asset. + fn decimals(asset: &Self::AssetId) -> u8; } /// Trait for providing a set of named fungible assets which can be created and destroyed. @@ -122,7 +155,9 @@ pub trait Mutate: Inspect { amount: Self::Balance, ) -> Result { let extra = Self::can_withdraw(asset, &source, amount).into_result()?; - Self::can_deposit(asset, &dest, amount.saturating_add(extra)).into_result()?; + // As we first burn and then mint, we don't need to check if `mint` fits into the supply. + // If we can withdraw/burn it, we can also mint it again. + Self::can_deposit(asset, dest, amount.saturating_add(extra), false).into_result()?; let actual = Self::burn_from(asset, source, amount)?; debug_assert!( actual == amount.saturating_add(extra), @@ -151,6 +186,12 @@ pub trait Transfer: Inspect { amount: Self::Balance, keep_alive: bool, ) -> Result; + + /// Reduce the active issuance by some amount. + fn deactivate(_: Self::AssetId, _: Self::Balance) {} + + /// Increase the active issuance by some amount, up to the outstanding amount reduced. + fn reactivate(_: Self::AssetId, _: Self::Balance) {} } /// Trait for inspecting a set of named fungible assets which can be placed on hold. @@ -227,3 +268,65 @@ impl + MutateHold> BalancedHold>::slash(asset, who, actual) } } + +/// Trait for providing the ability to create new fungible assets. +pub trait Create: Inspect { + /// Create a new fungible asset. + fn create( + id: Self::AssetId, + admin: AccountId, + is_sufficient: bool, + min_balance: Self::Balance, + ) -> DispatchResult; +} + +/// Trait for providing the ability to destroy existing fungible assets. +pub trait Destroy: Inspect { + /// Start the destruction an existing fungible asset. + /// * `id`: The `AssetId` to be destroyed. successfully. + /// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy + /// command. If not provided, no authorization checks will be performed before destroying + /// asset. + fn start_destroy(id: Self::AssetId, maybe_check_owner: Option) -> DispatchResult; + + /// Destroy all accounts associated with a given asset. + /// `destroy_accounts` should only be called after `start_destroy` has been called, and the + /// asset is in a `Destroying` state + /// + /// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset. + /// * `max_items`: The maximum number of accounts to be destroyed for a given call of the + /// function. This value should be small enough to allow the operation fit into a logical + /// block. + /// + /// Response: + /// * u32: Total number of approvals which were actually destroyed + /// + /// Due to weight restrictions, this function may need to be called multiple + /// times to fully destroy all approvals. It will destroy `max_items` approvals at a + /// time. + fn destroy_accounts(id: Self::AssetId, max_items: u32) -> Result; + /// Destroy all approvals associated with a given asset up to the `max_items` + /// `destroy_approvals` should only be called after `start_destroy` has been called, and the + /// asset is in a `Destroying` state + /// + /// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset. + /// * `max_items`: The maximum number of accounts to be destroyed for a given call of the + /// function. This value should be small enough to allow the operation fit into a logical + /// block. + /// + /// Response: + /// * u32: Total number of approvals which were actually destroyed + /// + /// Due to weight restrictions, this function may need to be called multiple + /// times to fully destroy all approvals. It will destroy `max_items` approvals at a + /// time. + fn destroy_approvals(id: Self::AssetId, max_items: u32) -> Result; + + /// Complete destroying asset and unreserve currency. + /// `finish_destroy` should only be called after `start_destroy` has been called, and the + /// asset is in a `Destroying` state. All accounts or approvals should be destroyed before + /// hand. + /// + /// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset. + fn finish_destroy(id: Self::AssetId) -> DispatchResult; +} diff --git a/frame/support/src/traits/tokens/fungibles/approvals.rs b/frame/support/src/traits/tokens/fungibles/approvals.rs new file mode 100644 index 0000000000000..48929955d9497 --- /dev/null +++ b/frame/support/src/traits/tokens/fungibles/approvals.rs @@ -0,0 +1,43 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Inspect and Mutate traits for Asset approvals + +use crate::dispatch::DispatchResult; +pub trait Inspect: super::Inspect { + // Check the amount approved by an owner to be spent by a delegate + fn allowance(asset: Self::AssetId, owner: &AccountId, delegate: &AccountId) -> Self::Balance; +} + +pub trait Mutate: Inspect { + // Approve a delegate account to spend an amount of tokens owned by an owner + fn approve( + asset: Self::AssetId, + owner: &AccountId, + delegate: &AccountId, + amount: Self::Balance, + ) -> DispatchResult; + + // Transfer from a delegate account an amount approved by the owner of the asset + fn transfer_from( + asset: Self::AssetId, + owner: &AccountId, + delegate: &AccountId, + dest: &AccountId, + amount: Self::Balance, + ) -> DispatchResult; +} diff --git a/frame/support/src/traits/tokens/fungibles/balanced.rs b/frame/support/src/traits/tokens/fungibles/balanced.rs index 40a65305b87da..9e50ff834a874 100644 --- a/frame/support/src/traits/tokens/fungibles/balanced.rs +++ b/frame/support/src/traits/tokens/fungibles/balanced.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -163,7 +163,7 @@ pub trait Balanced: Inspect { /// **WARNING** /// Do not use this directly unless you want trouble, since it allows you to alter account balances /// without keeping the issuance up to date. It has no safeguards against accidentally creating -/// token imbalances in your system leading to accidental imflation or deflation. It's really just +/// token imbalances in your system leading to accidental inflation or deflation. It's really just /// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to /// use. pub trait Unbalanced: Inspect { @@ -185,8 +185,8 @@ pub trait Unbalanced: Inspect { amount: Self::Balance, ) -> Result { let old_balance = Self::balance(asset, who); - let (mut new_balance, mut amount) = if old_balance < amount { - Err(TokenError::NoFunds)? + let (mut new_balance, mut amount) = if Self::reducible_balance(asset, who, false) < amount { + return Err(TokenError::NoFunds.into()) } else { (old_balance - amount, amount) }; @@ -211,8 +211,9 @@ pub trait Unbalanced: Inspect { amount: Self::Balance, ) -> Self::Balance { let old_balance = Self::balance(asset, who); - let (mut new_balance, mut amount) = if old_balance < amount { - (Zero::zero(), old_balance) + let old_free_balance = Self::reducible_balance(asset, who, false); + let (mut new_balance, mut amount) = if old_free_balance < amount { + (old_balance.saturating_sub(old_free_balance), old_free_balance) } else { (old_balance - amount, amount) }; @@ -251,7 +252,7 @@ pub trait Unbalanced: Inspect { let old_balance = Self::balance(asset, who); let new_balance = old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?; if new_balance < Self::minimum_balance(asset) { - Err(TokenError::BelowMinimum)? + return Err(TokenError::BelowMinimum.into()) } if old_balance != new_balance { Self::set_balance(asset, who, new_balance)?; diff --git a/frame/support/src/traits/tokens/fungibles/enumerable.rs b/frame/support/src/traits/tokens/fungibles/enumerable.rs new file mode 100644 index 0000000000000..151d15b3684a5 --- /dev/null +++ b/frame/support/src/traits/tokens/fungibles/enumerable.rs @@ -0,0 +1,26 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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::traits::fungibles::Inspect; + +/// Interface for enumerating assets in existence or owned by a given account. +pub trait InspectEnumerable: Inspect { + type AssetsIterator; + + /// Returns an iterator of the collections in existence. + fn asset_ids() -> Self::AssetsIterator; +} diff --git a/frame/support/src/traits/tokens/fungibles/imbalance.rs b/frame/support/src/traits/tokens/fungibles/imbalance.rs index c44c471646485..61bd4a43064e6 100644 --- a/frame/support/src/traits/tokens/fungibles/imbalance.rs +++ b/frame/support/src/traits/tokens/fungibles/imbalance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/fungibles/metadata.rs b/frame/support/src/traits/tokens/fungibles/metadata.rs new file mode 100644 index 0000000000000..b736ab1489f58 --- /dev/null +++ b/frame/support/src/traits/tokens/fungibles/metadata.rs @@ -0,0 +1,41 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Inspect and Mutate traits for Asset metadata + +use crate::dispatch::DispatchResult; +use sp_std::vec::Vec; + +pub trait Inspect: super::Inspect { + // Get name for an AssetId. + fn name(asset: Self::AssetId) -> Vec; + // Get symbol for an AssetId. + fn symbol(asset: Self::AssetId) -> Vec; + // Get decimals for an AssetId. + fn decimals(asset: Self::AssetId) -> u8; +} + +pub trait Mutate: Inspect { + // Set name, symbol and decimals for a given assetId. + fn set( + asset: Self::AssetId, + from: &AccountId, + name: Vec, + symbol: Vec, + decimals: u8, + ) -> DispatchResult; +} diff --git a/frame/support/src/traits/tokens/fungibles/roles.rs b/frame/support/src/traits/tokens/fungibles/roles.rs new file mode 100644 index 0000000000000..18fd1cc801210 --- /dev/null +++ b/frame/support/src/traits/tokens/fungibles/roles.rs @@ -0,0 +1,29 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Inspect traits for Asset roles + +pub trait Inspect: super::Inspect { + // Get owner for an AssetId. + fn owner(asset: Self::AssetId) -> Option; + // Get issuer for an AssetId. + fn issuer(asset: Self::AssetId) -> Option; + // Get admin for an AssetId. + fn admin(asset: Self::AssetId) -> Option; + // Get freezer for an AssetId. + fn freezer(asset: Self::AssetId) -> Option; +} diff --git a/frame/support/src/traits/tokens/imbalance.rs b/frame/support/src/traits/tokens/imbalance.rs index 0f7b38a65efc8..d721beb41494c 100644 --- a/frame/support/src/traits/tokens/imbalance.rs +++ b/frame/support/src/traits/tokens/imbalance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -177,3 +177,55 @@ pub trait Imbalance: Sized + TryDrop + Default { /// The raw value of self. fn peek(&self) -> Balance; } + +#[cfg(feature = "std")] +impl Imbalance for () { + type Opposite = (); + fn zero() -> Self { + () + } + fn drop_zero(self) -> Result<(), Self> { + Ok(()) + } + fn split(self, _: Balance) -> (Self, Self) { + ((), ()) + } + fn ration(self, _: u32, _: u32) -> (Self, Self) + where + Balance: From + Saturating + Div, + { + ((), ()) + } + fn split_merge(self, _: Balance, _: (Self, Self)) -> (Self, Self) { + ((), ()) + } + fn ration_merge(self, _: u32, _: u32, _: (Self, Self)) -> (Self, Self) + where + Balance: From + Saturating + Div, + { + ((), ()) + } + fn split_merge_into(self, _: Balance, _: &mut (Self, Self)) {} + fn ration_merge_into(self, _: u32, _: u32, _: &mut (Self, Self)) + where + Balance: From + Saturating + Div, + { + } + fn merge(self, _: Self) -> Self { + () + } + fn merge_into(self, _: &mut Self) {} + fn maybe_merge(self, _: Option) -> Self { + () + } + fn subsume(&mut self, _: Self) {} + fn maybe_subsume(&mut self, _: Option) { + () + } + fn offset(self, _: Self::Opposite) -> SameOrOther { + SameOrOther::None + } + fn peek(&self) -> Balance { + Default::default() + } +} diff --git a/frame/support/src/traits/tokens/imbalance/on_unbalanced.rs b/frame/support/src/traits/tokens/imbalance/on_unbalanced.rs index bc7df0e2acf33..0125254cefc85 100644 --- a/frame/support/src/traits/tokens/imbalance/on_unbalanced.rs +++ b/frame/support/src/traits/tokens/imbalance/on_unbalanced.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/imbalance/signed_imbalance.rs b/frame/support/src/traits/tokens/imbalance/signed_imbalance.rs index 3e76d069f50e7..f969a4363405a 100644 --- a/frame/support/src/traits/tokens/imbalance/signed_imbalance.rs +++ b/frame/support/src/traits/tokens/imbalance/signed_imbalance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/imbalance/split_two_ways.rs b/frame/support/src/traits/tokens/imbalance/split_two_ways.rs index 882b43c2e914c..b963895af0de5 100644 --- a/frame/support/src/traits/tokens/imbalance/split_two_ways.rs +++ b/frame/support/src/traits/tokens/imbalance/split_two_ways.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,27 +18,26 @@ //! Means for splitting an imbalance into two and hanlding them differently. use super::super::imbalance::{Imbalance, OnUnbalanced}; -use sp_core::u32_trait::Value as U32; use sp_runtime::traits::Saturating; use sp_std::{marker::PhantomData, ops::Div}; /// Split an unbalanced amount two ways between a common divisor. -pub struct SplitTwoWays( - PhantomData<(Balance, Imbalance, Part1, Target1, Part2, Target2)>, +pub struct SplitTwoWays( + PhantomData<(Balance, Imbalance, Target1, Target2)>, ); impl< Balance: From + Saturating + Div, I: Imbalance, - Part1: U32, Target1: OnUnbalanced, - Part2: U32, Target2: OnUnbalanced, - > OnUnbalanced for SplitTwoWays + const PART1: u32, + const PART2: u32, + > OnUnbalanced for SplitTwoWays { fn on_nonzero_unbalanced(amount: I) { - let total: u32 = Part1::VALUE + Part2::VALUE; - let amount1 = amount.peek().saturating_mul(Part1::VALUE.into()) / total.into(); + let total: u32 = PART1 + PART2; + let amount1 = amount.peek().saturating_mul(PART1.into()) / total.into(); let (imb1, imb2) = amount.split(amount1); Target1::on_unbalanced(imb1); Target2::on_unbalanced(imb2); diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index 214c28708a196..294d0e89c8b9e 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,14 +17,14 @@ //! Miscellaneous types. -use codec::{Decode, Encode, FullCodec}; +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_core::RuntimeDebug; use sp_runtime::{ArithmeticError, DispatchError, TokenError}; use sp_std::fmt::Debug; /// One of a number of consequences of withdrawing a fungible from an account. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] pub enum WithdrawConsequence { /// Withdraw could not happen since the amount to be withdrawn is less than the total funds in /// the account. @@ -69,7 +69,7 @@ impl WithdrawConsequence { } /// One of a number of consequences of withdrawing a fungible from an account. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] pub enum DepositConsequence { /// Deposit couldn't happen due to the amount being too low. This is usually because the /// account doesn't yet exist and the deposit wouldn't bring it to at least the minimum needed @@ -104,7 +104,7 @@ impl DepositConsequence { } /// Simple boolean for whether an account needs to be kept in existence. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] pub enum ExistenceRequirement { /// Operation must not result in the account going out of existence. /// @@ -116,7 +116,9 @@ pub enum ExistenceRequirement { } /// Status of funds. -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] pub enum BalanceStatus { /// Funds are free, as corresponding to `free` item in Balances. Free, @@ -126,7 +128,7 @@ pub enum BalanceStatus { bitflags::bitflags! { /// Reasons for moving funds out of an account. - #[derive(Encode, Decode)] + #[derive(Encode, Decode, MaxEncodedLen)] pub struct WithdrawReasons: u8 { /// In order to pay for (system) transaction costs. const TRANSACTION_PAYMENT = 0b00000001; @@ -161,16 +163,29 @@ impl WithdrawReasons { } /// Simple amalgamation trait to collect together properties for an AssetId under one roof. -pub trait AssetId: FullCodec + Copy + Eq + PartialEq + Debug {} -impl AssetId for T {} +pub trait AssetId: + FullCodec + Copy + Eq + PartialEq + Debug + scale_info::TypeInfo + MaxEncodedLen +{ +} +impl AssetId + for T +{ +} /// Simple amalgamation trait to collect together properties for a Balance under one roof. pub trait Balance: - AtLeast32BitUnsigned + FullCodec + Copy + Default + Debug + scale_info::TypeInfo + AtLeast32BitUnsigned + FullCodec + Copy + Default + Debug + scale_info::TypeInfo + MaxEncodedLen { } -impl Balance - for T +impl< + T: AtLeast32BitUnsigned + + FullCodec + + Copy + + Default + + Debug + + scale_info::TypeInfo + + MaxEncodedLen, + > Balance for T { } @@ -179,3 +194,19 @@ pub trait BalanceConversion { type Error; fn to_asset_balance(balance: InBalance, asset_id: AssetId) -> Result; } + +/// Trait to handle asset locking mechanism to ensure interactions with the asset can be implemented +/// downstream to extend logic of Uniques current functionality. +pub trait Locker { + /// Check if the asset should be locked and prevent interactions with the asset from executing. + fn is_locked(collection: CollectionId, item: ItemId) -> bool; +} + +impl Locker for () { + // Default will be false if not implemented downstream. + // Note: The logic check in this function must be constant time and consistent for benchmarks + // to work. + fn is_locked(_collection: CollectionId, _item: ItemId) -> bool { + false + } +} diff --git a/frame/support/src/traits/tokens/nonfungible.rs b/frame/support/src/traits/tokens/nonfungible.rs index 821884f6e3905..46ca573131127 100644 --- a/frame/support/src/traits/tokens/nonfungible.rs +++ b/frame/support/src/traits/tokens/nonfungible.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Traits for dealing with a single non-fungible asset class. +//! Traits for dealing with a single non-fungible collection of items. //! -//! This assumes a single level namespace identified by `Inspect::InstanceId`, and could +//! This assumes a single level namespace identified by `Inspect::ItemId`, and could //! reasonably be implemented by pallets which wants to expose a single collection of NFT-like //! objects. //! @@ -30,164 +30,174 @@ use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; -/// Trait for providing an interface to a read-only NFT-like set of asset instances. +/// Trait for providing an interface to a read-only NFT-like set of items. pub trait Inspect { - /// Type for identifying an asset instance. - type InstanceId; + /// Type for identifying an item. + type ItemId; - /// Returns the owner of asset `instance`, or `None` if the asset doesn't exist or has no + /// Returns the owner of `item`, or `None` if the item doesn't exist or has no /// owner. - fn owner(instance: &Self::InstanceId) -> Option; + fn owner(item: &Self::ItemId) -> Option; - /// Returns the attribute value of `instance` corresponding to `key`. + /// Returns the attribute value of `item` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. - fn attribute(_instance: &Self::InstanceId, _key: &[u8]) -> Option> { + fn attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { None } - /// Returns the strongly-typed attribute value of `instance` corresponding to `key`. + /// Returns the strongly-typed attribute value of `item` corresponding to `key`. /// /// By default this just attempts to use `attribute`. - fn typed_attribute(instance: &Self::InstanceId, key: &K) -> Option { - key.using_encoded(|d| Self::attribute(instance, d)) + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + key.using_encoded(|d| Self::attribute(item, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } - /// Returns `true` if the asset `instance` may be transferred. + /// Returns `true` if the `item` may be transferred. /// - /// Default implementation is that all assets are transferable. - fn can_transfer(_instance: &Self::InstanceId) -> bool { + /// Default implementation is that all items are transferable. + fn can_transfer(_item: &Self::ItemId) -> bool { true } } -/// Interface for enumerating assets in existence or owned by a given account over a collection +/// Interface for enumerating items in existence or owned by a given account over a collection /// of NFTs. pub trait InspectEnumerable: Inspect { - /// Returns an iterator of the instances of an asset `class` in existence. - fn instances() -> Box>; + /// The iterator type for [`Self::items`]. + type ItemsIterator: Iterator; + /// The iterator type for [`Self::owned`]. + type OwnedIterator: Iterator; - /// Returns an iterator of the asset instances of all classes owned by `who`. - fn owned(who: &AccountId) -> Box>; + /// Returns an iterator of the items within a `collection` in existence. + fn items() -> Self::ItemsIterator; + + /// Returns an iterator of the items of all collections owned by `who`. + fn owned(who: &AccountId) -> Self::OwnedIterator; } -/// Trait for providing an interface for NFT-like assets which may be minted, burned and/or have +/// Trait for providing an interface for NFT-like items which may be minted, burned and/or have /// attributes set on them. pub trait Mutate: Inspect { - /// Mint some asset `instance` to be owned by `who`. + /// Mint some `item` to be owned by `who`. /// /// By default, this is not a supported operation. - fn mint_into(_instance: &Self::InstanceId, _who: &AccountId) -> DispatchResult { + fn mint_into(_item: &Self::ItemId, _who: &AccountId) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Burn some asset `instance`. + /// Burn some `item`. /// /// By default, this is not a supported operation. - fn burn_from(_instance: &Self::InstanceId) -> DispatchResult { + fn burn(_item: &Self::ItemId, _maybe_check_owner: Option<&AccountId>) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Set attribute `value` of asset `instance`'s `key`. + /// Set attribute `value` of `item`'s `key`. /// /// By default, this is not a supported operation. - fn set_attribute(_instance: &Self::InstanceId, _key: &[u8], _value: &[u8]) -> DispatchResult { + fn set_attribute(_item: &Self::ItemId, _key: &[u8], _value: &[u8]) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Attempt to set the strongly-typed attribute `value` of `instance`'s `key`. + /// Attempt to set the strongly-typed attribute `value` of `item`'s `key`. /// /// By default this just attempts to use `set_attribute`. fn set_typed_attribute( - instance: &Self::InstanceId, + item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(instance, k, v))) + key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(item, k, v))) } } -/// Trait for providing a non-fungible set of assets which can only be transferred. +/// Trait for providing a non-fungible set of items which can only be transferred. pub trait Transfer: Inspect { - /// Transfer asset `instance` into `destination` account. - fn transfer(instance: &Self::InstanceId, destination: &AccountId) -> DispatchResult; + /// Transfer `item` into `destination` account. + fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult; } /// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying /// a single item. pub struct ItemOf< F: nonfungibles::Inspect, - A: Get<>::ClassId>, + A: Get<>::CollectionId>, AccountId, >(sp_std::marker::PhantomData<(F, A, AccountId)>); impl< F: nonfungibles::Inspect, - A: Get<>::ClassId>, + A: Get<>::CollectionId>, AccountId, > Inspect for ItemOf { - type InstanceId = >::InstanceId; - fn owner(instance: &Self::InstanceId) -> Option { - >::owner(&A::get(), instance) + type ItemId = >::ItemId; + fn owner(item: &Self::ItemId) -> Option { + >::owner(&A::get(), item) } - fn attribute(instance: &Self::InstanceId, key: &[u8]) -> Option> { - >::attribute(&A::get(), instance, key) + fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + >::attribute(&A::get(), item, key) } - fn typed_attribute(instance: &Self::InstanceId, key: &K) -> Option { - >::typed_attribute(&A::get(), instance, key) + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + >::typed_attribute(&A::get(), item, key) } - fn can_transfer(instance: &Self::InstanceId) -> bool { - >::can_transfer(&A::get(), instance) + fn can_transfer(item: &Self::ItemId) -> bool { + >::can_transfer(&A::get(), item) } } impl< F: nonfungibles::InspectEnumerable, - A: Get<>::ClassId>, + A: Get<>::CollectionId>, AccountId, > InspectEnumerable for ItemOf { - fn instances() -> Box> { - >::instances(&A::get()) + type ItemsIterator = >::ItemsIterator; + type OwnedIterator = + >::OwnedInCollectionIterator; + + fn items() -> Self::ItemsIterator { + >::items(&A::get()) } - fn owned(who: &AccountId) -> Box> { - >::owned_in_class(&A::get(), who) + + fn owned(who: &AccountId) -> Self::OwnedIterator { + >::owned_in_collection(&A::get(), who) } } impl< F: nonfungibles::Mutate, - A: Get<>::ClassId>, + A: Get<>::CollectionId>, AccountId, > Mutate for ItemOf { - fn mint_into(instance: &Self::InstanceId, who: &AccountId) -> DispatchResult { - >::mint_into(&A::get(), instance, who) + fn mint_into(item: &Self::ItemId, who: &AccountId) -> DispatchResult { + >::mint_into(&A::get(), item, who) } - fn burn_from(instance: &Self::InstanceId) -> DispatchResult { - >::burn_from(&A::get(), instance) + fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { + >::burn(&A::get(), item, maybe_check_owner) } - fn set_attribute(instance: &Self::InstanceId, key: &[u8], value: &[u8]) -> DispatchResult { - >::set_attribute(&A::get(), instance, key, value) + fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult { + >::set_attribute(&A::get(), item, key, value) } fn set_typed_attribute( - instance: &Self::InstanceId, + item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - >::set_typed_attribute(&A::get(), instance, key, value) + >::set_typed_attribute(&A::get(), item, key, value) } } impl< F: nonfungibles::Transfer, - A: Get<>::ClassId>, + A: Get<>::CollectionId>, AccountId, > Transfer for ItemOf { - fn transfer(instance: &Self::InstanceId, destination: &AccountId) -> DispatchResult { - >::transfer(&A::get(), instance, destination) + fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult { + >::transfer(&A::get(), item, destination) } } diff --git a/frame/support/src/traits/tokens/nonfungibles.rs b/frame/support/src/traits/tokens/nonfungibles.rs index 452ee2212d62a..ac007b5a67f1d 100644 --- a/frame/support/src/traits/tokens/nonfungibles.rs +++ b/frame/support/src/traits/tokens/nonfungibles.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Traits for dealing with multiple collections of non-fungible assets. +//! Traits for dealing with multiple collections of non-fungible items. //! -//! This assumes a dual-level namespace identified by `Inspect::InstanceId`, and could +//! This assumes a dual-level namespace identified by `Inspect::ItemId`, and could //! reasonably be implemented by pallets which want to expose multiple independent collections of //! NFT-like objects. //! @@ -27,172 +27,224 @@ //! Implementations of these traits may be converted to implementations of corresponding //! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter. -use crate::dispatch::DispatchResult; +use crate::dispatch::{DispatchError, DispatchResult}; use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; -/// Trait for providing an interface to many read-only NFT-like sets of asset instances. +/// Trait for providing an interface to many read-only NFT-like sets of items. pub trait Inspect { - /// Type for identifying an asset instance. - type InstanceId; + /// Type for identifying an item. + type ItemId; - /// Type for identifying an asset class (an identifier for an independent collection of asset - /// instances). - type ClassId; + /// Type for identifying a collection (an identifier for an independent collection of + /// items). + type CollectionId; - /// Returns the owner of asset `instance` of `class`, or `None` if the asset doesn't exist (or - /// somehow has no owner). - fn owner(class: &Self::ClassId, instance: &Self::InstanceId) -> Option; + /// Returns the owner of `item` of `collection`, or `None` if the item doesn't exist + /// (or somehow has no owner). + fn owner(collection: &Self::CollectionId, item: &Self::ItemId) -> Option; - /// Returns the owner of the asset `class`, if there is one. For many NFTs this may not make - /// any sense, so users of this API should not be surprised to find an asset class results in - /// `None` here. - fn class_owner(_class: &Self::ClassId) -> Option { + /// Returns the owner of the `collection`, if there is one. For many NFTs this may not + /// make any sense, so users of this API should not be surprised to find a collection + /// results in `None` here. + fn collection_owner(_collection: &Self::CollectionId) -> Option { None } - /// Returns the attribute value of `instance` of `class` corresponding to `key`. + /// Returns the attribute value of `item` of `collection` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. fn attribute( - _class: &Self::ClassId, - _instance: &Self::InstanceId, + _collection: &Self::CollectionId, + _item: &Self::ItemId, _key: &[u8], ) -> Option> { None } - /// Returns the strongly-typed attribute value of `instance` of `class` corresponding to `key`. + /// Returns the strongly-typed attribute value of `item` of `collection` corresponding to + /// `key`. /// /// By default this just attempts to use `attribute`. fn typed_attribute( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, key: &K, ) -> Option { - key.using_encoded(|d| Self::attribute(class, instance, d)) + key.using_encoded(|d| Self::attribute(collection, item, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } - /// Returns the attribute value of `class` corresponding to `key`. + /// Returns the attribute value of `collection` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. - fn class_attribute(_class: &Self::ClassId, _key: &[u8]) -> Option> { + fn collection_attribute(_collection: &Self::CollectionId, _key: &[u8]) -> Option> { None } - /// Returns the strongly-typed attribute value of `class` corresponding to `key`. + /// Returns the strongly-typed attribute value of `collection` corresponding to `key`. /// - /// By default this just attempts to use `class_attribute`. - fn typed_class_attribute(class: &Self::ClassId, key: &K) -> Option { - key.using_encoded(|d| Self::class_attribute(class, d)) + /// By default this just attempts to use `collection_attribute`. + fn typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::collection_attribute(collection, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } - /// Returns `true` if the asset `instance` of `class` may be transferred. + /// Returns `true` if the `item` of `collection` may be transferred. /// - /// Default implementation is that all assets are transferable. - fn can_transfer(_class: &Self::ClassId, _instance: &Self::InstanceId) -> bool { + /// Default implementation is that all items are transferable. + fn can_transfer(_collection: &Self::CollectionId, _item: &Self::ItemId) -> bool { true } } -/// Interface for enumerating assets in existence or owned by a given account over many collections +/// Interface for enumerating items in existence or owned by a given account over many collections /// of NFTs. pub trait InspectEnumerable: Inspect { - /// Returns an iterator of the asset classes in existence. - fn classes() -> Box>; - - /// Returns an iterator of the instances of an asset `class` in existence. - fn instances(class: &Self::ClassId) -> Box>; - - /// Returns an iterator of the asset instances of all classes owned by `who`. - fn owned(who: &AccountId) -> Box>; - - /// Returns an iterator of the asset instances of `class` owned by `who`. - fn owned_in_class( - class: &Self::ClassId, + /// The iterator type for [`Self::collections`]. + type CollectionsIterator: Iterator; + /// The iterator type for [`Self::items`]. + type ItemsIterator: Iterator; + /// The iterator type for [`Self::owned`]. + type OwnedIterator: Iterator; + /// The iterator type for [`Self::owned_in_collection`]. + type OwnedInCollectionIterator: Iterator; + + /// Returns an iterator of the collections in existence. + fn collections() -> Self::CollectionsIterator; + + /// Returns an iterator of the items of a `collection` in existence. + fn items(collection: &Self::CollectionId) -> Self::ItemsIterator; + + /// Returns an iterator of the items of all collections owned by `who`. + fn owned(who: &AccountId) -> Self::OwnedIterator; + + /// Returns an iterator of the items of `collection` owned by `who`. + fn owned_in_collection( + collection: &Self::CollectionId, who: &AccountId, - ) -> Box>; + ) -> Self::OwnedInCollectionIterator; } -/// Trait for providing the ability to create classes of nonfungible assets. +/// Trait for providing the ability to create collections of nonfungible items. pub trait Create: Inspect { - /// Create a `class` of nonfungible assets to be owned by `who` and managed by `admin`. - fn create_class(class: &Self::ClassId, who: &AccountId, admin: &AccountId) -> DispatchResult; + /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. + fn create_collection( + collection: &Self::CollectionId, + who: &AccountId, + admin: &AccountId, + ) -> DispatchResult; } -/// Trait for providing an interface for multiple classes of NFT-like assets which may be minted, -/// burned and/or have attributes set on them. +/// Trait for providing the ability to destroy collections of nonfungible items. +pub trait Destroy: Inspect { + /// The witness data needed to destroy an item. + type DestroyWitness; + + /// Provide the appropriate witness data needed to destroy an item. + fn get_destroy_witness(collection: &Self::CollectionId) -> Option; + + /// Destroy an existing fungible item. + /// * `collection`: The `CollectionId` to be destroyed. + /// * `witness`: Any witness data that needs to be provided to complete the operation + /// successfully. + /// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy + /// command. If not provided, we will not do any authorization checks before destroying the + /// item. + /// + /// If successful, this function will return the actual witness data from the destroyed item. + /// This may be different than the witness data provided, and can be used to refund weight. + fn destroy( + collection: Self::CollectionId, + witness: Self::DestroyWitness, + maybe_check_owner: Option, + ) -> Result; +} + +/// Trait for providing an interface for multiple collections of NFT-like items which may be +/// minted, burned and/or have attributes set on them. pub trait Mutate: Inspect { - /// Mint some asset `instance` of `class` to be owned by `who`. + /// Mint some `item` of `collection` to be owned by `who`. /// /// By default, this is not a supported operation. fn mint_into( - _class: &Self::ClassId, - _instance: &Self::InstanceId, + _collection: &Self::CollectionId, + _item: &Self::ItemId, _who: &AccountId, ) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Burn some asset `instance` of `class`. + /// Burn some `item` of `collection`. /// /// By default, this is not a supported operation. - fn burn_from(_class: &Self::ClassId, _instance: &Self::InstanceId) -> DispatchResult { + fn burn( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _maybe_check_owner: Option<&AccountId>, + ) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Set attribute `value` of asset `instance` of `class`'s `key`. + /// Set attribute `value` of `item` of `collection`'s `key`. /// /// By default, this is not a supported operation. fn set_attribute( - _class: &Self::ClassId, - _instance: &Self::InstanceId, + _collection: &Self::CollectionId, + _item: &Self::ItemId, _key: &[u8], _value: &[u8], ) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Attempt to set the strongly-typed attribute `value` of `instance` of `class`'s `key`. + /// Attempt to set the strongly-typed attribute `value` of `item` of `collection`'s `key`. /// /// By default this just attempts to use `set_attribute`. fn set_typed_attribute( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(class, instance, k, v))) + key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(collection, item, k, v))) } - /// Set attribute `value` of asset `class`'s `key`. + /// Set attribute `value` of `collection`'s `key`. /// /// By default, this is not a supported operation. - fn set_class_attribute(_class: &Self::ClassId, _key: &[u8], _value: &[u8]) -> DispatchResult { + fn set_collection_attribute( + _collection: &Self::CollectionId, + _key: &[u8], + _value: &[u8], + ) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Attempt to set the strongly-typed attribute `value` of `class`'s `key`. + /// Attempt to set the strongly-typed attribute `value` of `collection`'s `key`. /// /// By default this just attempts to use `set_attribute`. - fn set_typed_class_attribute( - class: &Self::ClassId, + fn set_typed_collection_attribute( + collection: &Self::CollectionId, key: &K, value: &V, ) -> DispatchResult { - key.using_encoded(|k| value.using_encoded(|v| Self::set_class_attribute(class, k, v))) + key.using_encoded(|k| { + value.using_encoded(|v| Self::set_collection_attribute(collection, k, v)) + }) } } -/// Trait for providing a non-fungible sets of assets which can only be transferred. +/// Trait for providing a non-fungible sets of items which can only be transferred. pub trait Transfer: Inspect { - /// Transfer asset `instance` of `class` into `destination` account. + /// Transfer `item` of `collection` into `destination` account. fn transfer( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, destination: &AccountId, ) -> DispatchResult; } diff --git a/frame/support/src/traits/try_runtime.rs b/frame/support/src/traits/try_runtime.rs new file mode 100644 index 0000000000000..640bb566a65af --- /dev/null +++ b/frame/support/src/traits/try_runtime.rs @@ -0,0 +1,138 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Try-runtime specific traits and types. + +use impl_trait_for_tuples::impl_for_tuples; +use sp_arithmetic::traits::AtLeast32BitUnsigned; +use sp_std::prelude::*; + +// Which state tests to execute. +#[derive(codec::Encode, codec::Decode, Clone)] +pub enum Select { + /// None of them. + None, + /// All of them. + All, + /// Run a fixed number of them in a round robin manner. + RoundRobin(u32), + /// Run only pallets who's name matches the given list. + /// + /// Pallet names are obtained from [`super::PalletInfoAccess`]. + Only(Vec>), +} + +impl Default for Select { + fn default() -> Self { + Select::None + } +} + +impl sp_std::fmt::Debug for Select { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + match self { + Select::RoundRobin(x) => write!(f, "RoundRobin({})", x), + Select::Only(x) => write!( + f, + "Only({:?})", + x.iter() + .map(|x| sp_std::str::from_utf8(x).unwrap_or("")) + .collect::>(), + ), + Select::All => write!(f, "All"), + Select::None => write!(f, "None"), + } + } +} + +#[cfg(feature = "std")] +impl sp_std::str::FromStr for Select { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s { + "all" | "All" => Ok(Select::All), + "none" | "None" => Ok(Select::None), + _ => + if s.starts_with("rr-") { + let count = s + .split_once('-') + .and_then(|(_, count)| count.parse::().ok()) + .ok_or("failed to parse count")?; + Ok(Select::RoundRobin(count)) + } else { + let pallets = s.split(',').map(|x| x.as_bytes().to_vec()).collect::>(); + Ok(Select::Only(pallets)) + }, + } + } +} + +/// Execute some checks to ensure the internal state of a pallet is consistent. +/// +/// Usually, these checks should check all of the invariants that are expected to be held on all of +/// the storage items of your pallet. +pub trait TryState { + /// Execute the state checks. + fn try_state(_: BlockNumber, _: Select) -> Result<(), &'static str>; +} + +#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] +#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] +#[cfg_attr(all(feature = "tuples-128"), impl_for_tuples(128))] +impl TryState + for Tuple +{ + for_tuples!( where #( Tuple: crate::traits::PalletInfoAccess )* ); + fn try_state(n: BlockNumber, targets: Select) -> Result<(), &'static str> { + match targets { + Select::None => Ok(()), + Select::All => { + let mut result = Ok(()); + for_tuples!( #( result = result.and(Tuple::try_state(n.clone(), targets.clone())); )* ); + result + }, + Select::RoundRobin(len) => { + let functions: &[fn(BlockNumber, Select) -> Result<(), &'static str>] = + &[for_tuples!(#( Tuple::try_state ),*)]; + let skip = n.clone() % (functions.len() as u32).into(); + let skip: u32 = + skip.try_into().unwrap_or_else(|_| sp_runtime::traits::Bounded::max_value()); + let mut result = Ok(()); + for try_state_fn in functions.iter().cycle().skip(skip as usize).take(len as usize) + { + result = result.and(try_state_fn(n.clone(), targets.clone())); + } + result + }, + Select::Only(ref pallet_names) => { + let try_state_fns: &[( + &'static str, + fn(BlockNumber, Select) -> Result<(), &'static str>, + )] = &[for_tuples!( + #( (::name(), Tuple::try_state) ),* + )]; + let mut result = Ok(()); + for (name, try_state_fn) in try_state_fns { + if pallet_names.iter().any(|n| n == name.as_bytes()) { + result = result.and(try_state_fn(n.clone(), targets.clone())); + } + } + result + }, + } + } +} diff --git a/frame/support/src/traits/validation.rs b/frame/support/src/traits/validation.rs index f4107ef6e2b02..135dd927acbb2 100644 --- a/frame/support/src/traits/validation.rs +++ b/frame/support/src/traits/validation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Traits for dealing with validation and validators. use crate::{dispatch::Parameter, weights::Weight}; -use codec::{Codec, Decode}; +use codec::{Codec, Decode, MaxEncodedLen}; use sp_runtime::{ traits::{Convert, Zero}, BoundToRuntimeAppPublic, ConsensusEngineId, Permill, RuntimeAppPublic, @@ -31,7 +31,7 @@ use sp_std::prelude::*; /// Something that can give information about the current validator set. pub trait ValidatorSet { /// Type for representing validator id in a session. - type ValidatorId: Parameter; + type ValidatorId: Parameter + MaxEncodedLen; /// A type for converting `AccountId` to `ValidatorId`. type ValidatorIdOf: Convert>; @@ -77,7 +77,7 @@ pub trait VerifySeal { /// A session handler for specific key type. pub trait OneSessionHandler: BoundToRuntimeAppPublic { /// The key type expected. - type Key: Decode + Default + RuntimeAppPublic; + type Key: Decode + RuntimeAppPublic; /// The given validator set will be used for the genesis session. /// It is guaranteed that the given validator set will also be used @@ -109,7 +109,7 @@ pub trait OneSessionHandler: BoundToRuntimeAppPublic { fn on_before_session_ending() {} /// A validator got disabled. Act accordingly until a new session begins. - fn on_disabled(_validator_index: usize); + fn on_disabled(_validator_index: u32); } /// Something that can estimate at which block the next session rotation will happen (i.e. a new diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 62c6217ad59bc..49ae3163d0cd1 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,14 @@ //! Traits and associated data structures concerned with voting, and moving between tokens and //! votes. -use sp_arithmetic::traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}; +use crate::dispatch::{DispatchError, Parameter}; +use codec::{HasCompact, MaxEncodedLen}; +use sp_arithmetic::{ + traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}, + Perbill, +}; +use sp_runtime::traits::Member; +use sp_std::prelude::*; /// A trait similar to `Convert` to convert values from `B` an abstract balance type /// into u64 and back from u128. (This conversion is used in election and other places where complex @@ -87,3 +94,96 @@ impl + UniqueSaturatedFrom> CurrencyToVote B::unique_saturated_from(value) } } + +pub trait VoteTally { + fn new(_: Class) -> Self; + fn ayes(&self, class: Class) -> Votes; + fn support(&self, class: Class) -> Perbill; + fn approval(&self, class: Class) -> Perbill; + #[cfg(feature = "runtime-benchmarks")] + fn unanimity(class: Class) -> Self; + #[cfg(feature = "runtime-benchmarks")] + fn rejection(class: Class) -> Self; + #[cfg(feature = "runtime-benchmarks")] + fn from_requirements(support: Perbill, approval: Perbill, class: Class) -> Self; + #[cfg(feature = "runtime-benchmarks")] + /// A function that should be called before any use of the `runtime-benchmarks` gated functions + /// of the `VoteTally` trait. + /// + /// Should be used to set up any needed state in a Pallet which implements `VoteTally` so that + /// benchmarks that execute will complete successfully. `class` can be used to set up a + /// particular class of voters, and `granularity` is used to determine the weight of one vote + /// relative to total unanimity. + /// + /// For example, in the case where there are a number of unique voters, and each voter has equal + /// voting weight, a granularity of `Perbill::from_rational(1, 1000)` should create `1_000` + /// users. + fn setup(class: Class, granularity: Perbill); +} +pub enum PollStatus { + None, + Ongoing(Tally, Class), + Completed(Moment, bool), +} + +impl PollStatus { + pub fn ensure_ongoing(self) -> Option<(Tally, Class)> { + match self { + Self::Ongoing(t, c) => Some((t, c)), + _ => None, + } + } +} + +pub struct ClassCountOf(sp_std::marker::PhantomData<(P, T)>); +impl> sp_runtime::traits::Get for ClassCountOf { + fn get() -> u32 { + P::classes().len() as u32 + } +} + +pub trait Polling { + type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact + MaxEncodedLen; + type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact + MaxEncodedLen; + type Class: Parameter + Member + Ord + PartialOrd + MaxEncodedLen; + type Moment; + + /// Provides a vec of values that `T` may take. + fn classes() -> Vec; + + /// `Some` if the referendum `index` can be voted on, along with the tally and class of + /// referendum. + /// + /// Don't use this if you might mutate - use `try_access_poll` instead. + fn as_ongoing(index: Self::Index) -> Option<(Tally, Self::Class)>; + + fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut Tally, Self::Moment, Self::Class>) -> R, + ) -> R; + + fn try_access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut Tally, Self::Moment, Self::Class>) -> Result, + ) -> Result; + + /// Create an ongoing majority-carries poll of given class lasting given period for the purpose + /// of benchmarking. + /// + /// May return `Err` if it is impossible. + #[cfg(feature = "runtime-benchmarks")] + fn create_ongoing(class: Self::Class) -> Result; + + /// End the given ongoing poll and return the result. + /// + /// Returns `Err` if `index` is not an ongoing poll. + #[cfg(feature = "runtime-benchmarks")] + fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()>; + + /// The maximum amount of ongoing polls within any single class. By default it practically + /// unlimited (`u32::max_value()`). + #[cfg(feature = "runtime-benchmarks")] + fn max_ongoing() -> (Self::Class, u32) { + (Self::classes().into_iter().next().expect("Always one class"), u32::max_value()) + } +} diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index 115470a9bf034..9ff49b97bf21f 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,110 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Primitives for transaction weighting. -//! -//! Every dispatchable function is responsible for providing `#[weight = $x]` attribute. In this -//! snipped, `$x` can be any user provided struct that implements the following traits: -//! -//! - [`WeighData`]: the weight amount. -//! - [`ClassifyDispatch`]: class of the dispatch. -//! - [`PaysFee`]: whether this weight should be translated to fee and deducted upon dispatch. -//! -//! Substrate then bundles the output information of the three traits into [`DispatchInfo`] struct -//! and provides it by implementing the [`GetDispatchInfo`] for all `Call` both inner and outer call -//! types. -//! -//! Substrate provides two pre-defined ways to annotate weight: -//! -//! ### 1. Fixed values -//! -//! This can only be used when all 3 traits can be resolved statically. You have 3 degrees of -//! configuration: -//! -//! 1. Define only weight, **in which case `ClassifyDispatch` will be `Normal` and `PaysFee` will be -//! `Yes`**. -//! -//! ``` -//! # use frame_system::Config; -//! frame_support::decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { -//! #[weight = 1000] -//! fn dispatching(origin) { unimplemented!() } -//! } -//! } -//! # fn main() {} -//! ``` -//! -//! 2.1 Define weight and class, **in which case `PaysFee` would be `Yes`**. -//! -//! ``` -//! # use frame_system::Config; -//! # use frame_support::weights::DispatchClass; -//! frame_support::decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { -//! #[weight = (1000, DispatchClass::Operational)] -//! fn dispatching(origin) { unimplemented!() } -//! } -//! } -//! # fn main() {} -//! ``` -//! -//! 2.2 Define weight and `PaysFee`, **in which case `ClassifyDispatch` would be `Normal`**. -//! -//! ``` -//! # use frame_system::Config; -//! # use frame_support::weights::Pays; -//! frame_support::decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { -//! #[weight = (1000, Pays::No)] -//! fn dispatching(origin) { unimplemented!() } -//! } -//! } -//! # fn main() {} -//! ``` -//! -//! 3. Define all 3 parameters. -//! -//! ``` -//! # use frame_system::Config; -//! # use frame_support::weights::{DispatchClass, Pays}; -//! frame_support::decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { -//! #[weight = (1000, DispatchClass::Operational, Pays::No)] -//! fn dispatching(origin) { unimplemented!() } -//! } -//! } -//! # fn main() {} -//! ``` -//! -//! ### 2. Define weights as a function of input arguments using `FunctionOf` tuple struct. -//! -//! This struct works in a similar manner as above. 3 items must be provided and each can be either -//! a fixed value or a function/closure with the same parameters list as the dispatchable function -//! itself, wrapper in a tuple. -//! -//! Using this only makes sense if you want to use a function for at least one of the elements. If -//! all 3 are static values, providing a raw tuple is easier. -//! -//! ``` -//! # use frame_system::Config; -//! # use frame_support::weights::{DispatchClass, FunctionOf, Pays}; -//! frame_support::decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { -//! #[weight = FunctionOf( -//! // weight, function. -//! |args: (&u32, &u64)| *args.0 as u64 + args.1, -//! // class, fixed. -//! DispatchClass::Operational, -//! // pays fee, function. -//! |args: (&u32, &u64)| if *args.0 > 1000 { Pays::Yes } else { Pays::No }, -//! )] -//! fn dispatching(origin, a: u32, b: u64) { unimplemented!() } -//! } -//! } -//! # fn main() {} -//! ``` -//! FRAME assumes a weight of `1_000_000_000_000` equals 1 second of compute on a standard machine. +//! Re-exports `sp-weights` public API, and contains benchmarked weight constants specific to +//! FRAME. //! //! Latest machine specification used to benchmark are: //! - Digital Ocean: ubuntu-s-2vcpu-4gb-ams3-01 @@ -127,888 +25,91 @@ //! - Ubuntu 19.10 (GNU/Linux 5.3.0-18-generic x86_64) //! - rustc 1.42.0 (b8cedc004 2020-03-09) -use crate::dispatch::{DispatchError, DispatchErrorWithPostInfo, DispatchResultWithPostInfo}; -use codec::{Decode, Encode}; -use scale_info::TypeInfo; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; -use smallvec::{smallvec, SmallVec}; -use sp_arithmetic::{ - traits::{BaseArithmetic, Saturating, Unsigned}, - Perbill, -}; -use sp_runtime::{ - generic::{CheckedExtrinsic, UncheckedExtrinsic}, - traits::{SaturatedConversion, SignedExtension}, - RuntimeDebug, -}; +mod block_weights; +mod extrinsic_weights; +mod paritydb_weights; +mod rocksdb_weights; -/// Re-export priority as type -pub use sp_runtime::transaction_validity::TransactionPriority; - -/// Numeric range of a transaction weight. -pub type Weight = u64; +use crate::dispatch; +pub use sp_weights::*; /// These constants are specific to FRAME, and the current implementation of its various components. /// For example: FRAME System, FRAME Executive, our FRAME support libraries, etc... pub mod constants { - use super::{RuntimeDbWeight, Weight}; - use crate::parameter_types; - - pub const WEIGHT_PER_SECOND: Weight = 1_000_000_000_000; - pub const WEIGHT_PER_MILLIS: Weight = WEIGHT_PER_SECOND / 1000; // 1_000_000_000 - pub const WEIGHT_PER_MICROS: Weight = WEIGHT_PER_MILLIS / 1000; // 1_000_000 - pub const WEIGHT_PER_NANOS: Weight = WEIGHT_PER_MICROS / 1000; // 1_000 - - parameter_types! { - /// Importing a block with 0 txs takes ~5 ms - pub const BlockExecutionWeight: Weight = 5 * WEIGHT_PER_MILLIS; - /// Executing 10,000 System remarks (no-op) txs takes ~1.26 seconds -> ~125 µs per tx - pub const ExtrinsicBaseWeight: Weight = 125 * WEIGHT_PER_MICROS; - /// By default, Substrate uses RocksDB, so this will be the weight used throughout - /// the runtime. - pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 25 * WEIGHT_PER_MICROS, // ~25 µs @ 200,000 items - write: 100 * WEIGHT_PER_MICROS, // ~100 µs @ 200,000 items - }; - /// ParityDB can be enabled with a feature flag, but is still experimental. These weights - /// are available for brave runtime engineers who may want to try this out as default. - pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 8 * WEIGHT_PER_MICROS, // ~8 µs @ 200,000 items - write: 50 * WEIGHT_PER_MICROS, // ~50 µs @ 200,000 items - }; - } -} - -/// Means of weighing some particular kind of data (`T`). -pub trait WeighData { - /// Weigh the data `T` given by `target`. When implementing this for a dispatchable, `T` will be - /// a tuple of all arguments given to the function (except origin). - fn weigh_data(&self, target: T) -> Weight; -} - -/// Means of classifying a dispatchable function. -pub trait ClassifyDispatch { - /// Classify the dispatch function based on input data `target` of type `T`. When implementing - /// this for a dispatchable, `T` will be a tuple of all arguments given to the function (except - /// origin). - fn classify_dispatch(&self, target: T) -> DispatchClass; -} - -/// Indicates if dispatch function should pay fees or not. -/// If set to `Pays::No`, the block resource limits are applied, yet no fee is deducted. -pub trait PaysFee { - fn pays_fee(&self, _target: T) -> Pays; -} - -/// Explicit enum to denote if a transaction pays fee or not. -#[derive(Clone, Copy, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)] -pub enum Pays { - /// Transactor will pay related fees. - Yes, - /// Transactor will NOT pay related fees. - No, -} - -impl Default for Pays { - fn default() -> Self { - Self::Yes - } -} - -/// A generalized group of dispatch types. -/// -/// NOTE whenever upgrading the enum make sure to also update -/// [DispatchClass::all] and [DispatchClass::non_mandatory] helper functions. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)] -pub enum DispatchClass { - /// A normal dispatch. - Normal, - /// An operational dispatch. - Operational, - /// A mandatory dispatch. These kinds of dispatch are always included regardless of their - /// weight, therefore it is critical that they are separately validated to ensure that a - /// malicious validator cannot craft a valid but impossibly heavy block. Usually this just - /// means ensuring that the extrinsic can only be included once and that it is always very - /// light. - /// - /// Do *NOT* use it for extrinsics that can be heavy. - /// - /// The only real use case for this is inherent extrinsics that are required to execute in a - /// block for the block to be valid, and it solves the issue in the case that the block - /// initialization is sufficiently heavy to mean that those inherents do not fit into the - /// block. Essentially, we assume that in these exceptional circumstances, it is better to - /// allow an overweight block to be created than to not allow any block at all to be created. - Mandatory, -} - -impl Default for DispatchClass { - fn default() -> Self { - Self::Normal - } -} - -impl DispatchClass { - /// Returns an array containing all dispatch classes. - pub fn all() -> &'static [DispatchClass] { - &[DispatchClass::Normal, DispatchClass::Operational, DispatchClass::Mandatory] - } - - /// Returns an array of all dispatch classes except `Mandatory`. - pub fn non_mandatory() -> &'static [DispatchClass] { - &[DispatchClass::Normal, DispatchClass::Operational] - } -} - -/// A trait that represents one or many values of given type. -/// -/// Useful to accept as parameter type to let the caller pass either a single value directly -/// or an iterator. -pub trait OneOrMany { - /// The iterator type. - type Iter: Iterator; - /// Convert this item into an iterator. - fn into_iter(self) -> Self::Iter; -} - -impl OneOrMany for DispatchClass { - type Iter = sp_std::iter::Once; - fn into_iter(self) -> Self::Iter { - sp_std::iter::once(self) - } -} - -impl<'a> OneOrMany for &'a [DispatchClass] { - type Iter = sp_std::iter::Cloned>; - fn into_iter(self) -> Self::Iter { - self.iter().cloned() - } -} - -/// Primitives related to priority management of Frame. -pub mod priority { - /// The starting point of all Operational transactions. 3/4 of u64::MAX. - pub const LIMIT: u64 = 13_835_058_055_282_163_711_u64; - - /// Wrapper for priority of different dispatch classes. - /// - /// This only makes sure that any value created for the operational dispatch class is - /// incremented by [`LIMIT`]. - pub enum FrameTransactionPriority { - Normal(u64), - Operational(u64), - } - - impl From for u64 { - fn from(priority: FrameTransactionPriority) -> Self { - match priority { - FrameTransactionPriority::Normal(inner) => inner, - FrameTransactionPriority::Operational(inner) => inner.saturating_add(LIMIT), - } - } - } -} - -/// A bundle of static information collected from the `#[weight = $x]` attributes. -#[derive(Clone, Copy, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] -pub struct DispatchInfo { - /// Weight of this transaction. - pub weight: Weight, - /// Class of this transaction. - pub class: DispatchClass, - /// Does this transaction pay fees. - pub pays_fee: Pays, -} - -/// A `Dispatchable` function (aka transaction) that can carry some static information along with -/// it, using the `#[weight]` attribute. -pub trait GetDispatchInfo { - /// Return a `DispatchInfo`, containing relevant information of this dispatch. - /// - /// This is done independently of its encoded size. - fn get_dispatch_info(&self) -> DispatchInfo; -} - -impl GetDispatchInfo for () { - fn get_dispatch_info(&self) -> DispatchInfo { - DispatchInfo::default() - } -} - -/// Weight information that is only available post dispatch. -/// NOTE: This can only be used to reduce the weight or fee, not increase it. -#[derive(Clone, Copy, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] -pub struct PostDispatchInfo { - /// Actual weight consumed by a call or `None` which stands for the worst case static weight. - pub actual_weight: Option, - /// Whether this transaction should pay fees when all is said and done. - pub pays_fee: Pays, -} - -impl PostDispatchInfo { - /// Calculate how much (if any) weight was not used by the `Dispatchable`. - pub fn calc_unspent(&self, info: &DispatchInfo) -> Weight { - info.weight - self.calc_actual_weight(info) - } - - /// Calculate how much weight was actually spent by the `Dispatchable`. - pub fn calc_actual_weight(&self, info: &DispatchInfo) -> Weight { - if let Some(actual_weight) = self.actual_weight { - actual_weight.min(info.weight) - } else { - info.weight - } - } - - /// Determine if user should actually pay fees at the end of the dispatch. - pub fn pays_fee(&self, info: &DispatchInfo) -> Pays { - // If they originally were not paying fees, or the post dispatch info - // says they should not pay fees, then they don't pay fees. - // This is because the pre dispatch information must contain the - // worst case for weight and fees paid. - if info.pays_fee == Pays::No || self.pays_fee == Pays::No { - Pays::No - } else { - // Otherwise they pay. - Pays::Yes - } - } -} - -/// Extract the actual weight from a dispatch result if any or fall back to the default weight. -pub fn extract_actual_weight(result: &DispatchResultWithPostInfo, info: &DispatchInfo) -> Weight { - match result { - Ok(post_info) => &post_info, - Err(err) => &err.post_info, - } - .calc_actual_weight(info) -} - -impl From<(Option, Pays)> for PostDispatchInfo { - fn from(post_weight_info: (Option, Pays)) -> Self { - let (actual_weight, pays_fee) = post_weight_info; - Self { actual_weight, pays_fee } - } -} - -impl From for PostDispatchInfo { - fn from(pays_fee: Pays) -> Self { - Self { actual_weight: None, pays_fee } - } -} - -impl From> for PostDispatchInfo { - fn from(actual_weight: Option) -> Self { - Self { actual_weight, pays_fee: Default::default() } - } -} - -impl From<()> for PostDispatchInfo { - fn from(_: ()) -> Self { - Self { actual_weight: None, pays_fee: Default::default() } - } -} - -impl sp_runtime::traits::Printable for PostDispatchInfo { - fn print(&self) { - "actual_weight=".print(); - match self.actual_weight { - Some(weight) => weight.print(), - None => "max-weight".print(), - }; - "pays_fee=".print(); - match self.pays_fee { - Pays::Yes => "Yes".print(), - Pays::No => "No".print(), - } - } -} - -/// Allows easy conversion from `DispatchError` to `DispatchErrorWithPostInfo` for dispatchables -/// that want to return a custom a posterior weight on error. -pub trait WithPostDispatchInfo { - /// Call this on your modules custom errors type in order to return a custom weight on error. - /// - /// # Example - /// - /// ```ignore - /// let who = ensure_signed(origin).map_err(|e| e.with_weight(100))?; - /// ensure!(who == me, Error::::NotMe.with_weight(200_000)); - /// ``` - fn with_weight(self, actual_weight: Weight) -> DispatchErrorWithPostInfo; -} - -impl WithPostDispatchInfo for T -where - T: Into, -{ - fn with_weight(self, actual_weight: Weight) -> DispatchErrorWithPostInfo { - DispatchErrorWithPostInfo { - post_info: PostDispatchInfo { - actual_weight: Some(actual_weight), - pays_fee: Default::default(), - }, - error: self.into(), - } - } -} - -impl WeighData for Weight { - fn weigh_data(&self, _: T) -> Weight { - return *self - } -} - -impl ClassifyDispatch for Weight { - fn classify_dispatch(&self, _: T) -> DispatchClass { - DispatchClass::Normal - } -} - -impl PaysFee for Weight { - fn pays_fee(&self, _: T) -> Pays { - Pays::Yes - } -} - -impl WeighData for (Weight, DispatchClass, Pays) { - fn weigh_data(&self, _: T) -> Weight { - return self.0 - } -} - -impl ClassifyDispatch for (Weight, DispatchClass, Pays) { - fn classify_dispatch(&self, _: T) -> DispatchClass { - self.1 - } -} - -impl PaysFee for (Weight, DispatchClass, Pays) { - fn pays_fee(&self, _: T) -> Pays { - self.2 - } -} - -impl WeighData for (Weight, DispatchClass) { - fn weigh_data(&self, _: T) -> Weight { - return self.0 - } -} - -impl ClassifyDispatch for (Weight, DispatchClass) { - fn classify_dispatch(&self, _: T) -> DispatchClass { - self.1 - } -} - -impl PaysFee for (Weight, DispatchClass) { - fn pays_fee(&self, _: T) -> Pays { - Pays::Yes - } -} - -impl WeighData for (Weight, Pays) { - fn weigh_data(&self, _: T) -> Weight { - return self.0 - } -} - -impl ClassifyDispatch for (Weight, Pays) { - fn classify_dispatch(&self, _: T) -> DispatchClass { - DispatchClass::Normal - } -} - -impl PaysFee for (Weight, Pays) { - fn pays_fee(&self, _: T) -> Pays { - self.1 - } -} - -/// A struct to represent a weight which is a function of the input arguments. The given items have -/// the following types: -/// -/// - `WD`: a raw `Weight` value or a closure that returns a `Weight` with the same argument list as -/// the dispatched, wrapped in a tuple. -/// - `CD`: a raw `DispatchClass` value or a closure that returns a `DispatchClass` with the same -/// argument list as the dispatched, wrapped in a tuple. -/// - `PF`: a `Pays` variant for whether this dispatch pays fee or not or a closure that returns a -/// `Pays` variant with the same argument list as the dispatched, wrapped in a tuple. -#[deprecated = "Function arguments are available directly inside the annotation now."] -pub struct FunctionOf(pub WD, pub CD, pub PF); - -// `WeighData` as a raw value -#[allow(deprecated)] -impl WeighData for FunctionOf { - fn weigh_data(&self, _: Args) -> Weight { - self.0 - } -} - -// `WeighData` as a closure -#[allow(deprecated)] -impl WeighData for FunctionOf -where - WD: Fn(Args) -> Weight, -{ - fn weigh_data(&self, args: Args) -> Weight { - (self.0)(args) - } -} - -// `ClassifyDispatch` as a raw value -#[allow(deprecated)] -impl ClassifyDispatch for FunctionOf { - fn classify_dispatch(&self, _: Args) -> DispatchClass { - self.1 - } -} - -// `ClassifyDispatch` as a raw value -#[allow(deprecated)] -impl ClassifyDispatch for FunctionOf -where - CD: Fn(Args) -> DispatchClass, -{ - fn classify_dispatch(&self, args: Args) -> DispatchClass { - (self.1)(args) - } -} - -// `PaysFee` as a raw value -#[allow(deprecated)] -impl PaysFee for FunctionOf { - fn pays_fee(&self, _: Args) -> Pays { - self.2 - } -} - -// `PaysFee` as a closure -#[allow(deprecated)] -impl PaysFee for FunctionOf -where - PF: Fn(Args) -> Pays, -{ - fn pays_fee(&self, args: Args) -> Pays { - (self.2)(args) - } -} - -/// Implementation for unchecked extrinsic. -impl GetDispatchInfo - for UncheckedExtrinsic -where - Call: GetDispatchInfo, - Extra: SignedExtension, -{ - fn get_dispatch_info(&self) -> DispatchInfo { - self.function.get_dispatch_info() - } -} - -/// Implementation for checked extrinsic. -impl GetDispatchInfo for CheckedExtrinsic -where - Call: GetDispatchInfo, -{ - fn get_dispatch_info(&self) -> DispatchInfo { - self.function.get_dispatch_info() - } -} - -/// Implementation for test extrinsic. -#[cfg(feature = "std")] -impl GetDispatchInfo for sp_runtime::testing::TestXt { - fn get_dispatch_info(&self) -> DispatchInfo { - // for testing: weight == size. - DispatchInfo { weight: self.encode().len() as _, pays_fee: Pays::Yes, ..Default::default() } - } -} - -/// The weight of database operations that the runtime can invoke. -#[derive(Clone, Copy, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] -pub struct RuntimeDbWeight { - pub read: Weight, - pub write: Weight, -} - -impl RuntimeDbWeight { - pub fn reads(self, r: Weight) -> Weight { - self.read.saturating_mul(r) - } - - pub fn writes(self, w: Weight) -> Weight { - self.write.saturating_mul(w) - } - - pub fn reads_writes(self, r: Weight, w: Weight) -> Weight { - let read_weight = self.read.saturating_mul(r); - let write_weight = self.write.saturating_mul(w); - read_weight.saturating_add(write_weight) - } -} - -/// One coefficient and its position in the `WeightToFeePolynomial`. -/// -/// One term of polynomial is calculated as: -/// -/// ```ignore -/// coeff_integer * x^(degree) + coeff_frac * x^(degree) -/// ``` -/// -/// The `negative` value encodes whether the term is added or substracted from the -/// overall polynomial result. -#[derive(Clone, Encode, Decode, TypeInfo)] -pub struct WeightToFeeCoefficient { - /// The integral part of the coefficient. - pub coeff_integer: Balance, - /// The fractional part of the coefficient. - pub coeff_frac: Perbill, - /// True iff the coefficient should be interpreted as negative. - pub negative: bool, - /// Degree/exponent of the term. - pub degree: u8, -} - -/// A list of coefficients that represent one polynomial. -pub type WeightToFeeCoefficients = SmallVec<[WeightToFeeCoefficient; 4]>; - -/// A trait that describes the weight to fee calculation as polynomial. -/// -/// An implementor should only implement the `polynomial` function. -pub trait WeightToFeePolynomial { - /// The type that is returned as result from polynomial evaluation. - type Balance: BaseArithmetic + From + Copy + Unsigned; - - /// Returns a polynomial that describes the weight to fee conversion. - /// - /// This is the only function that should be manually implemented. Please note - /// that all calculation is done in the probably unsigned `Balance` type. This means - /// that the order of coefficients is important as putting the negative coefficients - /// first will most likely saturate the result to zero mid evaluation. - fn polynomial() -> WeightToFeeCoefficients; - - /// Calculates the fee from the passed `weight` according to the `polynomial`. - /// - /// This should not be overriden in most circumstances. Calculation is done in the - /// `Balance` type and never overflows. All evaluation is saturating. - fn calc(weight: &Weight) -> Self::Balance { - Self::polynomial() - .iter() - .fold(Self::Balance::saturated_from(0u32), |mut acc, args| { - let w = Self::Balance::saturated_from(*weight).saturating_pow(args.degree.into()); - - // The sum could get negative. Therefore we only sum with the accumulator. - // The Perbill Mul implementation is non overflowing. - let frac = args.coeff_frac * w; - let integer = args.coeff_integer.saturating_mul(w); - - if args.negative { - acc = acc.saturating_sub(frac); - acc = acc.saturating_sub(integer); - } else { - acc = acc.saturating_add(frac); - acc = acc.saturating_add(integer); - } - - acc - }) - } -} - -/// Implementor of `WeightToFeePolynomial` that maps one unit of weight to one unit of fee. -pub struct IdentityFee(sp_std::marker::PhantomData); - -impl WeightToFeePolynomial for IdentityFee -where - T: BaseArithmetic + From + Copy + Unsigned, -{ - type Balance = T; - - fn polynomial() -> WeightToFeeCoefficients { - smallvec!(WeightToFeeCoefficient { - coeff_integer: 1u32.into(), - coeff_frac: Perbill::zero(), - negative: false, - degree: 1, - }) - } -} - -/// A struct holding value for each `DispatchClass`. -#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] -pub struct PerDispatchClass { - /// Value for `Normal` extrinsics. - normal: T, - /// Value for `Operational` extrinsics. - operational: T, - /// Value for `Mandatory` extrinsics. - mandatory: T, -} - -impl PerDispatchClass { - /// Create new `PerDispatchClass` with the same value for every class. - pub fn new(val: impl Fn(DispatchClass) -> T) -> Self { - Self { - normal: val(DispatchClass::Normal), - operational: val(DispatchClass::Operational), - mandatory: val(DispatchClass::Mandatory), - } - } - - /// Get a mutable reference to current value of given class. - pub fn get_mut(&mut self, class: DispatchClass) -> &mut T { - match class { - DispatchClass::Operational => &mut self.operational, - DispatchClass::Normal => &mut self.normal, - DispatchClass::Mandatory => &mut self.mandatory, - } - } - - /// Get current value for given class. - pub fn get(&self, class: DispatchClass) -> &T { - match class { - DispatchClass::Normal => &self.normal, - DispatchClass::Operational => &self.operational, - DispatchClass::Mandatory => &self.mandatory, - } - } -} - -impl PerDispatchClass { - /// Set the value of given class. - pub fn set(&mut self, new: T, class: impl OneOrMany) { - for class in class.into_iter() { - *self.get_mut(class) = new.clone(); - } - } -} - -impl PerDispatchClass { - /// Returns the total weight consumed by all extrinsics in the block. - pub fn total(&self) -> Weight { - let mut sum = 0; - for class in DispatchClass::all() { - sum = sum.saturating_add(*self.get(*class)); - } - sum - } - - /// Add some weight of a specific dispatch class, saturating at the numeric bounds of `Weight`. - pub fn add(&mut self, weight: Weight, class: DispatchClass) { - let value = self.get_mut(class); - *value = value.saturating_add(weight); - } - - /// Try to add some weight of a specific dispatch class, returning Err(()) if overflow would - /// occur. - pub fn checked_add(&mut self, weight: Weight, class: DispatchClass) -> Result<(), ()> { - let value = self.get_mut(class); - *value = value.checked_add(weight).ok_or(())?; - Ok(()) - } - - /// Subtract some weight of a specific dispatch class, saturating at the numeric bounds of - /// `Weight`. - pub fn sub(&mut self, weight: Weight, class: DispatchClass) { - let value = self.get_mut(class); - *value = value.saturating_sub(weight); - } -} - -#[cfg(test)] -#[allow(dead_code)] -mod tests { - use super::*; - use crate::{decl_module, parameter_types, traits::Get}; - - pub trait Config: 'static { - type Origin; - type Balance; - type BlockNumber; - type DbWeight: Get; - type PalletInfo: crate::traits::PalletInfo; - } - - pub struct TraitImpl {} - - parameter_types! { - pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 100, - write: 1000, - }; - } - - impl Config for TraitImpl { - type Origin = u32; - type BlockNumber = u32; - type Balance = u32; - type DbWeight = DbWeight; - type PalletInfo = crate::tests::PanicPalletInfo; - } - - decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=self { - // no arguments, fixed weight - #[weight = 1000] - fn f00(_origin) { unimplemented!(); } - - #[weight = (1000, DispatchClass::Mandatory)] - fn f01(_origin) { unimplemented!(); } - - #[weight = (1000, Pays::No)] - fn f02(_origin) { unimplemented!(); } - - #[weight = (1000, DispatchClass::Operational, Pays::No)] - fn f03(_origin) { unimplemented!(); } - - // weight = a x 10 + b - #[weight = ((_a * 10 + _eb * 1) as Weight, DispatchClass::Normal, Pays::Yes)] - fn f11(_origin, _a: u32, _eb: u32) { unimplemented!(); } - - #[weight = (0, DispatchClass::Operational, Pays::Yes)] - fn f12(_origin, _a: u32, _eb: u32) { unimplemented!(); } - - #[weight = T::DbWeight::get().reads(3) + T::DbWeight::get().writes(2) + 10_000] - fn f20(_origin) { unimplemented!(); } - - #[weight = T::DbWeight::get().reads_writes(6, 5) + 40_000] - fn f21(_origin) { unimplemented!(); } - - } - } - - #[test] - fn weights_are_correct() { - // #[weight = 1000] - let info = Call::::f00 {}.get_dispatch_info(); - assert_eq!(info.weight, 1000); - assert_eq!(info.class, DispatchClass::Normal); - assert_eq!(info.pays_fee, Pays::Yes); - - // #[weight = (1000, DispatchClass::Mandatory)] - let info = Call::::f01 {}.get_dispatch_info(); - assert_eq!(info.weight, 1000); - assert_eq!(info.class, DispatchClass::Mandatory); - assert_eq!(info.pays_fee, Pays::Yes); - - // #[weight = (1000, Pays::No)] - let info = Call::::f02 {}.get_dispatch_info(); - assert_eq!(info.weight, 1000); - assert_eq!(info.class, DispatchClass::Normal); - assert_eq!(info.pays_fee, Pays::No); - - // #[weight = (1000, DispatchClass::Operational, Pays::No)] - let info = Call::::f03 {}.get_dispatch_info(); - assert_eq!(info.weight, 1000); - assert_eq!(info.class, DispatchClass::Operational); - assert_eq!(info.pays_fee, Pays::No); - - // #[weight = ((_a * 10 + _eb * 1) as Weight, DispatchClass::Normal, Pays::Yes)] - let info = Call::::f11 { _a: 13, _eb: 20 }.get_dispatch_info(); - assert_eq!(info.weight, 150); // 13*10 + 20 - assert_eq!(info.class, DispatchClass::Normal); - assert_eq!(info.pays_fee, Pays::Yes); - - // #[weight = (0, DispatchClass::Operational, Pays::Yes)] - let info = Call::::f12 { _a: 10, _eb: 20 }.get_dispatch_info(); - assert_eq!(info.weight, 0); - assert_eq!(info.class, DispatchClass::Operational); - assert_eq!(info.pays_fee, Pays::Yes); - - // #[weight = T::DbWeight::get().reads(3) + T::DbWeight::get().writes(2) + 10_000] - let info = Call::::f20 {}.get_dispatch_info(); - assert_eq!(info.weight, 12300); // 100*3 + 1000*2 + 10_1000 - assert_eq!(info.class, DispatchClass::Normal); - assert_eq!(info.pays_fee, Pays::Yes); - - // #[weight = T::DbWeight::get().reads_writes(6, 5) + 40_000] - let info = Call::::f21 {}.get_dispatch_info(); - assert_eq!(info.weight, 45600); // 100*6 + 1000*5 + 40_1000 - assert_eq!(info.class, DispatchClass::Normal); - assert_eq!(info.pays_fee, Pays::Yes); - } - - #[test] - fn extract_actual_weight_works() { - let pre = DispatchInfo { weight: 1000, ..Default::default() }; - assert_eq!(extract_actual_weight(&Ok(Some(7).into()), &pre), 7); - assert_eq!(extract_actual_weight(&Ok(Some(1000).into()), &pre), 1000); - assert_eq!(extract_actual_weight(&Err(DispatchError::BadOrigin.with_weight(9)), &pre), 9); - } - - #[test] - fn extract_actual_weight_caps_at_pre_weight() { - let pre = DispatchInfo { weight: 1000, ..Default::default() }; - assert_eq!(extract_actual_weight(&Ok(Some(1250).into()), &pre), 1000); - assert_eq!( - extract_actual_weight(&Err(DispatchError::BadOrigin.with_weight(1300)), &pre), - 1000 - ); - } - - type Balance = u64; - - // 0.5x^3 + 2.333x^2 + 7x - 10_000 - struct Poly; - impl WeightToFeePolynomial for Poly { - type Balance = Balance; - - fn polynomial() -> WeightToFeeCoefficients { - smallvec![ - WeightToFeeCoefficient { - coeff_integer: 0, - coeff_frac: Perbill::from_float(0.5), - negative: false, - degree: 3 - }, - WeightToFeeCoefficient { - coeff_integer: 2, - coeff_frac: Perbill::from_rational(1u32, 3u32), - negative: false, - degree: 2 - }, - WeightToFeeCoefficient { - coeff_integer: 7, - coeff_frac: Perbill::zero(), - negative: false, - degree: 1 - }, - WeightToFeeCoefficient { - coeff_integer: 10_000, - coeff_frac: Perbill::zero(), - negative: true, - degree: 0 - }, - ] - } - } - - #[test] - fn polynomial_works() { - // 100^3/2=500000 100^2*(2+1/3)=23333 700 -10000 - assert_eq!(Poly::calc(&100), 514033); - // 10123^3/2=518677865433 10123^2*(2+1/3)=239108634 70861 -10000 - assert_eq!(Poly::calc(&10_123), 518917034928); - } - - #[test] - fn polynomial_does_not_underflow() { - assert_eq!(Poly::calc(&0), 0); - assert_eq!(Poly::calc(&10), 0); - } - - #[test] - fn polynomial_does_not_overflow() { - assert_eq!(Poly::calc(&Weight::max_value()), Balance::max_value() - 10_000); - } - - #[test] - fn identity_fee_works() { - assert_eq!(IdentityFee::::calc(&0), 0); - assert_eq!(IdentityFee::::calc(&50), 50); - assert_eq!(IdentityFee::::calc(&Weight::max_value()), Balance::max_value()); + pub use sp_weights::constants::*; + + // Expose the Block and Extrinsic base weights. + pub use super::{block_weights::BlockExecutionWeight, extrinsic_weights::ExtrinsicBaseWeight}; + + // Expose the DB weights. + pub use super::{ + paritydb_weights::constants::ParityDbWeight, rocksdb_weights::constants::RocksDbWeight, + }; +} + +#[deprecated = "Function has moved to `frame_support::dispatch`"] +pub fn extract_actual_pays_fee( + res: &dispatch::DispatchResultWithPostInfo, + info: &dispatch::DispatchInfo, +) -> dispatch::Pays { + dispatch::extract_actual_pays_fee(res, info) +} +#[deprecated = "Function has moved to `frame_support::dispatch`"] +pub fn extract_actual_weight( + res: &dispatch::DispatchResultWithPostInfo, + info: &dispatch::DispatchInfo, +) -> Weight { + dispatch::extract_actual_weight(res, info) +} +#[deprecated = "Trait has moved to `frame_support::dispatch`"] +pub trait ClassifyDispatch: dispatch::ClassifyDispatch { + fn classify_dispatch(&self, target: T) -> dispatch::DispatchClass { + >::classify_dispatch(self, target) + } +} +#[deprecated = "Enum has moved to `frame_support::dispatch`"] +pub type DispatchClass = dispatch::DispatchClass; +#[deprecated = "Struct has moved to `frame_support::dispatch`"] +pub type DispatchInfo = dispatch::DispatchInfo; +#[deprecated = "Trait has moved to `frame_support::dispatch`"] +pub trait GetDispatchInfo: dispatch::GetDispatchInfo { + fn get_dispatch_info(&self) -> dispatch::DispatchInfo { + ::get_dispatch_info(self) + } +} +#[deprecated = "Trait has moved to `frame_support::dispatch`"] +pub trait OneOrMany: dispatch::OneOrMany { + fn into_iter(self) -> Self::Iter + where + Self: Sized, + { + >::into_iter(self) + } +} +#[deprecated = "Enum has moved to `frame_support::dispatch`"] +pub type Pays = dispatch::Pays; +#[deprecated = "Trait has moved to `frame_support::dispatch`"] +pub trait PaysFee: dispatch::PaysFee { + fn pays_fee(&self, target: T) -> dispatch::Pays { + >::pays_fee(self, target) + } +} +#[deprecated = "Struct has moved to `frame_support::dispatch`"] +pub type PerDispatchClass = dispatch::PerDispatchClass; +#[deprecated = "Struct has moved to `frame_support::dispatch`"] +pub type PostDispatchInfo = dispatch::PostDispatchInfo; +#[deprecated = "Trait has moved to `frame_support::dispatch`"] +pub trait WeighData: dispatch::WeighData { + fn weigh_data(&self, target: T) -> Weight { + >::weigh_data(self, target) + } +} +#[deprecated = "Trait has moved to `frame_support::dispatch`"] +pub trait WithPostDispatchInfo: dispatch::WithPostDispatchInfo { + fn with_weight(self, actual_weight: Weight) -> dispatch::DispatchErrorWithPostInfo + where + Self: Sized, + { + ::with_weight(self, actual_weight) } } diff --git a/frame/support/src/weights/block_weights.rs b/frame/support/src/weights/block_weights.rs new file mode 100644 index 0000000000000..b68c1fb508b01 --- /dev/null +++ b/frame/support/src/weights/block_weights.rs @@ -0,0 +1,82 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07 (Y/M/D) +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! +//! SHORT-NAME: `block`, LONG-NAME: `BlockExecution`, RUNTIME: `Development` +//! WARMUPS: `10`, REPEAT: `100` +//! WEIGHT-PATH: `./frame/support/src/weights/` +//! WEIGHT-METRIC: `Average`, WEIGHT-MUL: `1.0`, WEIGHT-ADD: `0` + +// Executed Command: +// ./target/production/substrate +// benchmark +// overhead +// --chain=dev +// --execution=wasm +// --wasm-execution=compiled +// --weight-path=./frame/support/src/weights/ +// --header=./HEADER-APACHE2 +// --warmup=10 +// --repeat=100 + +use sp_core::parameter_types; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; + +parameter_types! { + /// Time to execute an empty block. + /// Calculated by multiplying the *Average* with `1.0` and adding `0`. + /// + /// Stats nanoseconds: + /// Min, Max: 351_000, 392_617 + /// Average: 358_523 + /// Median: 359_836 + /// Std-Dev: 6698.67 + /// + /// Percentiles nanoseconds: + /// 99th: 390_723 + /// 95th: 365_799 + /// 75th: 361_582 + pub const BlockExecutionWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(358_523)); +} + +#[cfg(test)] +mod test_weights { + use sp_weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::BlockExecutionWeight::get(); + + // At least 100 µs. + assert!( + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 100 µs." + ); + // At most 50 ms. + assert!( + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 50 ms." + ); + } +} diff --git a/frame/support/src/weights/extrinsic_weights.rs b/frame/support/src/weights/extrinsic_weights.rs new file mode 100644 index 0000000000000..ced1fb91621f6 --- /dev/null +++ b/frame/support/src/weights/extrinsic_weights.rs @@ -0,0 +1,82 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07 (Y/M/D) +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! +//! SHORT-NAME: `extrinsic`, LONG-NAME: `ExtrinsicBase`, RUNTIME: `Development` +//! WARMUPS: `10`, REPEAT: `100` +//! WEIGHT-PATH: `./frame/support/src/weights/` +//! WEIGHT-METRIC: `Average`, WEIGHT-MUL: `1.0`, WEIGHT-ADD: `0` + +// Executed Command: +// ./target/production/substrate +// benchmark +// overhead +// --chain=dev +// --execution=wasm +// --wasm-execution=compiled +// --weight-path=./frame/support/src/weights/ +// --header=./HEADER-APACHE2 +// --warmup=10 +// --repeat=100 + +use sp_core::parameter_types; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; + +parameter_types! { + /// Time to execute a NO-OP extrinsic, for example `System::remark`. + /// Calculated by multiplying the *Average* with `1.0` and adding `0`. + /// + /// Stats nanoseconds: + /// Min, Max: 98_722, 101_420 + /// Average: 98_974 + /// Median: 98_951 + /// Std-Dev: 271.62 + /// + /// Percentiles nanoseconds: + /// 99th: 99_202 + /// 95th: 99_163 + /// 75th: 99_030 + pub const ExtrinsicBaseWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(98_974)); +} + +#[cfg(test)] +mod test_weights { + use sp_weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::ExtrinsicBaseWeight::get(); + + // At least 10 µs. + assert!( + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 10 µs." + ); + // At most 1 ms. + assert!( + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 1 ms." + ); + } +} diff --git a/frame/support/src/weights/paritydb_weights.rs b/frame/support/src/weights/paritydb_weights.rs new file mode 100644 index 0000000000000..6fd1112ee2947 --- /dev/null +++ b/frame/support/src/weights/paritydb_weights.rs @@ -0,0 +1,62 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 mod constants { + use frame_support::weights::constants; + use sp_core::parameter_types; + use sp_weights::RuntimeDbWeight; + + parameter_types! { + /// ParityDB can be enabled with a feature flag, but is still experimental. These weights + /// are available for brave runtime engineers who may want to try this out as default. + pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 8_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 50_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::ParityDbWeight as W; + use sp_weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/frame/support/src/weights/rocksdb_weights.rs b/frame/support/src/weights/rocksdb_weights.rs new file mode 100644 index 0000000000000..b18b387de9957 --- /dev/null +++ b/frame/support/src/weights/rocksdb_weights.rs @@ -0,0 +1,62 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 mod constants { + use frame_support::weights::constants; + use sp_core::parameter_types; + use sp_weights::RuntimeDbWeight; + + parameter_types! { + /// By default, Substrate uses RocksDB, so this will be the weight used throughout + /// the runtime. + pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 25_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 100_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::RocksDbWeight as W; + use sp_weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/frame/support/test/Cargo.toml b/frame/support/test/Cargo.toml index e12880871e5c2..0ac3d90f59e07 100644 --- a/frame/support/test/Cargo.toml +++ b/frame/support/test/Cargo.toml @@ -2,30 +2,30 @@ name = "frame-support-test" version = "3.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" publish = false -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.126", default-features = false, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/arithmetic" } -sp-io = { version = "4.0.0-dev", path = "../../../primitives/io", default-features = false } -sp-state-machine = { version = "0.10.0-dev", optional = true, path = "../../../primitives/state-machine" } +serde = { version = "1.0.136", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../../primitives/arithmetic" } +sp-io = { version = "7.0.0", path = "../../../primitives/io", default-features = false } +sp-state-machine = { version = "0.13.0", optional = true, path = "../../../primitives/state-machine" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } -sp-version = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/version" } -trybuild = "1.0.43" -pretty_assertions = "0.6.1" -rustversion = "1.0.0" +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } +trybuild = { version = "1.0.60", features = [ "diff" ] } +pretty_assertions = "1.2.1" +rustversion = "1.0.6" frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } # The "std" feature for this pallet is never activated on purpose, in order to test construct_runtime error message test-pallet = { package = "frame-support-test-pallet", default-features = false, path = "pallet" } @@ -36,15 +36,22 @@ std = [ "serde/std", "codec/std", "scale-info/std", - "sp-io/std", "frame-support/std", "frame-system/std", "sp-core/std", "sp-std/std", + "sp-io/std", "sp-runtime/std", "sp-state-machine", + "sp-arithmetic/std", + "sp-version/std", ] try-runtime = ["frame-support/try-runtime"] -# WARNING: CI only execute pallet test with this feature, -# if the feature intended to be used outside, CI and this message need to be updated. -conditional-storage = [] +# WARNING: +# Only CI runs with this feature enabled. This feature is for testing stuff related to the FRAME macros +# in conjunction with rust features. +frame-feature-testing = [] +frame-feature-testing-2 = [] +# Disable ui tests +disable-ui-tests = [] +no-metadata-docs = ["frame-support/no-metadata-docs"] diff --git a/frame/support/test/compile_pass/Cargo.toml b/frame/support/test/compile_pass/Cargo.toml new file mode 100644 index 0000000000000..ea22a735b3698 --- /dev/null +++ b/frame/support/test/compile_pass/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "frame-support-test-compile-pass" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +publish = false +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../system" } +sp-core = { version = "7.0.0", default-features = false, path = "../../../../primitives/core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../../primitives/runtime" } +sp-version = { version = "5.0.0", default-features = false, path = "../../../../primitives/version" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-version/std", +] diff --git a/frame/support/test/compile_pass/src/lib.rs b/frame/support/test/compile_pass/src/lib.rs new file mode 100644 index 0000000000000..b46f6c48a6d99 --- /dev/null +++ b/frame/support/test/compile_pass/src/lib.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] +//! This crate tests that `construct_runtime!` expands the pallet parts +//! correctly even when frame-support is renamed in Cargo.toml + +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU16, ConstU32, ConstU64}, +}; +use sp_core::{sr25519, H256}; +use sp_runtime::{ + create_runtime_str, generic, + traits::{BlakeTwo256, IdentityLookup, Verify}, +}; +use sp_version::RuntimeVersion; + +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("frame-support-test-compile-pass"), + impl_name: create_runtime_str!("substrate-frame-support-test-compile-pass-runtime"), + authoring_version: 0, + spec_version: 0, + impl_version: 0, + apis: sp_version::create_apis_vec!([]), + transaction_version: 0, + state_version: 0, +}; + +pub type Signature = sr25519::Signature; +pub type AccountId = ::Signer; +pub type BlockNumber = u64; +pub type Index = u64; + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type Index = u128; + type Hash = H256; + type Hashing = BlakeTwo256; + type Header = Header; + type Lookup = IdentityLookup; + type BlockHashCount = ConstU64<2400>; + type Version = Version; + type AccountData = (); + type RuntimeOrigin = RuntimeOrigin; + type BlockNumber = BlockNumber; + type AccountId = AccountId; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type RuntimeCall = RuntimeCall; + type DbWeight = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<0>; +} + +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system, + } +); diff --git a/frame/support/test/pallet/Cargo.toml b/frame/support/test/pallet/Cargo.toml index 35eb4f34acae1..bf5febeb45441 100644 --- a/frame/support/test/pallet/Cargo.toml +++ b/frame/support/test/pallet/Cargo.toml @@ -2,18 +2,18 @@ name = "frame-support-test-pallet" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" publish = false -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../system" } @@ -21,7 +21,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../../ default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-support/std", "frame-system/std", + "scale-info/std", ] diff --git a/frame/support/test/pallet/src/lib.rs b/frame/support/test/pallet/src/lib.rs index f9f94b06a0a5a..37678e056f3e1 100644 --- a/frame/support/test/pallet/src/lib.rs +++ b/frame/support/test/pallet/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,12 @@ // 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. + +//! Testing pallet macro + +// Ensure docs are propagated properly by the macros. +#![warn(missing_docs)] + pub use pallet::*; #[frame_support::pallet] @@ -29,18 +35,22 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config {} + /// I'm the documentation + #[pallet::storage] + pub type Value = StorageValue; + #[pallet::genesis_config] + #[cfg_attr(feature = "std", derive(Default))] pub struct GenesisConfig {} - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self {} - } - } - #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) {} } + + #[pallet::error] + pub enum Error { + /// Something failed + Test, + } } diff --git a/frame/support/test/src/lib.rs b/frame/support/test/src/lib.rs index 52c0a6270d47f..0ceeed42ff982 100644 --- a/frame/support/test/src/lib.rs +++ b/frame/support/test/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,7 @@ /// The configuration trait pub trait Config: 'static { /// The runtime origin type. - type Origin: codec::Codec + codec::EncodeLike + Default + scale_info::TypeInfo; + type RuntimeOrigin: codec::Codec + codec::EncodeLike + Default + scale_info::TypeInfo; /// The block number type. type BlockNumber: codec::Codec + codec::EncodeLike + Default + scale_info::TypeInfo; /// The information about the pallet setup in the runtime. @@ -36,7 +36,7 @@ pub trait Config: 'static { frame_support::decl_module! { /// Some test module - pub struct Module for enum Call where origin: T::Origin, system=self {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} } /// A PalletInfo implementation which just panics. @@ -49,6 +49,12 @@ impl frame_support::traits::PalletInfo for PanicPalletInfo { fn name() -> Option<&'static str> { unimplemented!("PanicPalletInfo mustn't be triggered by tests"); } + fn module_name() -> Option<&'static str> { + unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + } + fn crate_version() -> Option { + unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + } } /// Provides an implementation of [`frame_support::traits::Randomness`] that should only be used in diff --git a/frame/support/test/src/pallet_version.rs b/frame/support/test/src/pallet_version.rs index bdea3859d65c6..096289116c419 100644 --- a/frame/support/test/src/pallet_version.rs +++ b/frame/support/test/src/pallet_version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/test/tests/construct_runtime.rs b/frame/support/test/tests/construct_runtime.rs index 062993fe10fbb..b1ace12936241 100644 --- a/frame/support/test/tests/construct_runtime.rs +++ b/frame/support/test/tests/construct_runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,22 +21,25 @@ #![recursion_limit = "128"] -use frame_support::traits::PalletInfo as _; +use codec::MaxEncodedLen; +use frame_support::{ + parameter_types, + traits::{CrateVersion, PalletInfo as _}, +}; use scale_info::TypeInfo; use sp_core::{sr25519, H256}; use sp_runtime::{ generic, traits::{BlakeTwo256, Verify}, - DispatchError, + DispatchError, ModuleError, }; -use sp_std::cell::RefCell; mod system; pub trait Currency {} -thread_local! { - pub static INTEGRITY_TEST_EXEC: RefCell = RefCell::new(0); +parameter_types! { + pub static IntegrityTestExec: u32 = 0; } mod module1 { @@ -46,7 +49,7 @@ mod module1 { frame_support::decl_module! { pub struct Module, I: Instance = DefaultInstance> for enum Call - where origin: ::Origin, system=system + where origin: ::RuntimeOrigin, system=system { #[weight = 0] pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { @@ -55,7 +58,9 @@ mod module1 { } } - #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] pub struct Origin(pub core::marker::PhantomData<(T, I)>); frame_support::decl_event! { @@ -84,7 +89,7 @@ mod module2 { frame_support::decl_module! { pub struct Module for enum Call - where origin: ::Origin, system=system + where origin: ::RuntimeOrigin, system=system { #[weight = 0] pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { @@ -92,12 +97,14 @@ mod module2 { } fn integrity_test() { - INTEGRITY_TEST_EXEC.with(|i| *i.borrow_mut() += 1); + IntegrityTestExec::mutate(|i| *i += 1); } } } - #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] pub struct Origin; frame_support::decl_event! { @@ -127,7 +134,7 @@ mod nested { frame_support::decl_module! { pub struct Module for enum Call - where origin: ::Origin, system=system + where origin: ::RuntimeOrigin, system=system { #[weight = 0] pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { @@ -135,12 +142,14 @@ mod nested { } fn integrity_test() { - INTEGRITY_TEST_EXEC.with(|i| *i.borrow_mut() += 1); + IntegrityTestExec::mutate(|i| *i += 1); } } } - #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] pub struct Origin; frame_support::decl_event! { @@ -171,7 +180,7 @@ pub mod module3 { frame_support::decl_module! { pub struct Module for enum Call - where origin: ::Origin, system=system + where origin: ::RuntimeOrigin, system=system { #[weight = 0] pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { @@ -191,12 +200,14 @@ pub mod module3 { } #[weight = 3] fn aux_4(_origin) -> frame_support::dispatch::DispatchResult { unreachable!() } - #[weight = (5, frame_support::weights::DispatchClass::Operational)] + #[weight = (5, frame_support::dispatch::DispatchClass::Operational)] fn operational(_origin) { unreachable!() } } } - #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] pub struct Origin(pub core::marker::PhantomData); frame_support::decl_event! { @@ -229,15 +240,19 @@ pub type AccountId = ::Signer; pub type BlockNumber = u64; pub type Index = u64; +fn test_pub() -> AccountId { + AccountId::from_raw([0; 32]) +} + impl system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type Hash = H256; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type BlockNumber = BlockNumber; type AccountId = AccountId; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type PalletInfo = PalletInfo; - type Call = Call; + type RuntimeCall = RuntimeCall; type DbWeight = (); } @@ -265,136 +280,106 @@ frame_support::construct_runtime!( pub type Header = generic::Header; pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; - -mod origin_test { - use super::{module3, nested, system, Block, UncheckedExtrinsic}; - use frame_support::traits::{Contains, OriginTrait}; - - impl nested::module3::Config for RuntimeOriginTest {} - impl module3::Config for RuntimeOriginTest {} - - pub struct BaseCallFilter; - impl Contains for BaseCallFilter { - fn contains(c: &Call) -> bool { - match c { - Call::NestedModule3(_) => true, - _ => false, - } - } - } - - impl system::Config for RuntimeOriginTest { - type BaseCallFilter = BaseCallFilter; - type Hash = super::H256; - type Origin = Origin; - type BlockNumber = super::BlockNumber; - type AccountId = u32; - type Event = Event; - type PalletInfo = PalletInfo; - type Call = Call; - type DbWeight = (); - } - - frame_support::construct_runtime!( - pub enum RuntimeOriginTest where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: system::{Pallet, Event, Origin}, - NestedModule3: nested::module3::{Pallet, Origin, Call}, - Module3: module3::{Pallet, Origin, Call}, - } - ); - - #[test] - fn origin_default_filter() { - let accepted_call = nested::module3::Call::fail {}.into(); - let rejected_call = module3::Call::fail {}.into(); - - assert_eq!(Origin::root().filter_call(&accepted_call), true); - assert_eq!(Origin::root().filter_call(&rejected_call), true); - assert_eq!(Origin::none().filter_call(&accepted_call), true); - assert_eq!(Origin::none().filter_call(&rejected_call), false); - assert_eq!(Origin::signed(0).filter_call(&accepted_call), true); - assert_eq!(Origin::signed(0).filter_call(&rejected_call), false); - assert_eq!(Origin::from(Some(0)).filter_call(&accepted_call), true); - assert_eq!(Origin::from(Some(0)).filter_call(&rejected_call), false); - assert_eq!(Origin::from(None).filter_call(&accepted_call), true); - assert_eq!(Origin::from(None).filter_call(&rejected_call), false); - assert_eq!(Origin::from(super::nested::module3::Origin).filter_call(&accepted_call), true); - assert_eq!(Origin::from(super::nested::module3::Origin).filter_call(&rejected_call), false); - - let mut origin = Origin::from(Some(0)); - - origin.add_filter(|c| matches!(c, Call::Module3(_))); - assert_eq!(origin.filter_call(&accepted_call), false); - assert_eq!(origin.filter_call(&rejected_call), false); - - origin.set_caller_from(Origin::root()); - assert!(matches!(origin.caller, OriginCaller::system(super::system::RawOrigin::Root))); - assert_eq!(origin.filter_call(&accepted_call), false); - assert_eq!(origin.filter_call(&rejected_call), false); - - origin.reset_filter(); - assert_eq!(origin.filter_call(&accepted_call), true); - assert_eq!(origin.filter_call(&rejected_call), false); - } -} +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; #[test] fn check_modules_error_type() { - assert_eq!( - Module1_1::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 31, error: 0, message: Some("Something") }), - ); - assert_eq!( - Module2::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 32, error: 0, message: Some("Something") }), - ); - assert_eq!( - Module1_2::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 33, error: 0, message: Some("Something") }), - ); - assert_eq!( - NestedModule3::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 34, error: 0, message: Some("Something") }), - ); - assert_eq!( - Module1_3::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 6, error: 0, message: Some("Something") }), - ); - assert_eq!( - Module1_4::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 3, error: 0, message: Some("Something") }), - ); - assert_eq!( - Module1_5::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 4, error: 0, message: Some("Something") }), - ); - assert_eq!( - Module1_6::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 1, error: 0, message: Some("Something") }), - ); - assert_eq!( - Module1_7::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 2, error: 0, message: Some("Something") }), - ); - assert_eq!( - Module1_8::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 12, error: 0, message: Some("Something") }), - ); - assert_eq!( - Module1_9::fail(system::Origin::::Root.into()), - Err(DispatchError::Module { index: 13, error: 0, message: Some("Something") }), - ); + sp_io::TestExternalities::default().execute_with(|| { + assert_eq!( + Module1_1::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 31, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module2::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 32, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_2::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 33, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + NestedModule3::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 34, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_3::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 6, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_4::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 3, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_5::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 4, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_6::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 1, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_7::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 2, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_8::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 12, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_9::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 13, + error: [0; 4], + message: Some("Something") + })), + ); + }) } #[test] fn integrity_test_works() { __construct_runtime_integrity_test::runtime_integrity_tests(); - assert_eq!(INTEGRITY_TEST_EXEC.with(|i| *i.borrow()), 2); + assert_eq!(IntegrityTestExec::get(), 2); } #[test] @@ -437,53 +422,53 @@ fn event_codec() { use codec::Encode; let event = system::Event::::ExtrinsicSuccess; - assert_eq!(Event::from(event).encode()[0], 30); + assert_eq!(RuntimeEvent::from(event).encode()[0], 30); - let event = module1::Event::::A(Default::default()); - assert_eq!(Event::from(event).encode()[0], 31); + let event = module1::Event::::A(test_pub()); + assert_eq!(RuntimeEvent::from(event).encode()[0], 31); let event = module2::Event::A; - assert_eq!(Event::from(event).encode()[0], 32); + assert_eq!(RuntimeEvent::from(event).encode()[0], 32); - let event = module1::Event::::A(Default::default()); - assert_eq!(Event::from(event).encode()[0], 33); + let event = module1::Event::::A(test_pub()); + assert_eq!(RuntimeEvent::from(event).encode()[0], 33); let event = nested::module3::Event::A; - assert_eq!(Event::from(event).encode()[0], 34); + assert_eq!(RuntimeEvent::from(event).encode()[0], 34); let event = module3::Event::A; - assert_eq!(Event::from(event).encode()[0], 35); + assert_eq!(RuntimeEvent::from(event).encode()[0], 35); - let event = module1::Event::::A(Default::default()); - assert_eq!(Event::from(event).encode()[0], 4); + let event = module1::Event::::A(test_pub()); + assert_eq!(RuntimeEvent::from(event).encode()[0], 4); - let event = module1::Event::::A(Default::default()); - assert_eq!(Event::from(event).encode()[0], 1); + let event = module1::Event::::A(test_pub()); + assert_eq!(RuntimeEvent::from(event).encode()[0], 1); - let event = module1::Event::::A(Default::default()); - assert_eq!(Event::from(event).encode()[0], 2); + let event = module1::Event::::A(test_pub()); + assert_eq!(RuntimeEvent::from(event).encode()[0], 2); - let event = module1::Event::::A(Default::default()); - assert_eq!(Event::from(event).encode()[0], 12); + let event = module1::Event::::A(test_pub()); + assert_eq!(RuntimeEvent::from(event).encode()[0], 12); - let event = module1::Event::::A(Default::default()); - assert_eq!(Event::from(event).encode()[0], 13); + let event = module1::Event::::A(test_pub()); + assert_eq!(RuntimeEvent::from(event).encode()[0], 13); } #[test] fn call_codec() { use codec::Encode; - assert_eq!(Call::System(system::Call::noop {}).encode()[0], 30); - assert_eq!(Call::Module1_1(module1::Call::fail {}).encode()[0], 31); - assert_eq!(Call::Module2(module2::Call::fail {}).encode()[0], 32); - assert_eq!(Call::Module1_2(module1::Call::fail {}).encode()[0], 33); - assert_eq!(Call::NestedModule3(nested::module3::Call::fail {}).encode()[0], 34); - assert_eq!(Call::Module3(module3::Call::fail {}).encode()[0], 35); - assert_eq!(Call::Module1_4(module1::Call::fail {}).encode()[0], 3); - assert_eq!(Call::Module1_6(module1::Call::fail {}).encode()[0], 1); - assert_eq!(Call::Module1_7(module1::Call::fail {}).encode()[0], 2); - assert_eq!(Call::Module1_8(module1::Call::fail {}).encode()[0], 12); - assert_eq!(Call::Module1_9(module1::Call::fail {}).encode()[0], 13); + assert_eq!(RuntimeCall::System(system::Call::noop {}).encode()[0], 30); + assert_eq!(RuntimeCall::Module1_1(module1::Call::fail {}).encode()[0], 31); + assert_eq!(RuntimeCall::Module2(module2::Call::fail {}).encode()[0], 32); + assert_eq!(RuntimeCall::Module1_2(module1::Call::fail {}).encode()[0], 33); + assert_eq!(RuntimeCall::NestedModule3(nested::module3::Call::fail {}).encode()[0], 34); + assert_eq!(RuntimeCall::Module3(module3::Call::fail {}).encode()[0], 35); + assert_eq!(RuntimeCall::Module1_4(module1::Call::fail {}).encode()[0], 3); + assert_eq!(RuntimeCall::Module1_6(module1::Call::fail {}).encode()[0], 1); + assert_eq!(RuntimeCall::Module1_7(module1::Call::fail {}).encode()[0], 2); + assert_eq!(RuntimeCall::Module1_8(module1::Call::fail {}).encode()[0], 12); + assert_eq!(RuntimeCall::Module1_9(module1::Call::fail {}).encode()[0], 13); } #[test] @@ -519,18 +504,26 @@ fn call_encode_is_correct_and_decode_works() { #[test] fn call_weight_should_attach_to_call_enum() { use frame_support::{ - dispatch::{DispatchInfo, GetDispatchInfo}, - weights::{DispatchClass, Pays}, + dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}, + weights::Weight, }; // operational. assert_eq!( module3::Call::::operational {}.get_dispatch_info(), - DispatchInfo { weight: 5, class: DispatchClass::Operational, pays_fee: Pays::Yes }, + DispatchInfo { + weight: Weight::from_ref_time(5), + class: DispatchClass::Operational, + pays_fee: Pays::Yes + }, ); // custom basic assert_eq!( module3::Call::::aux_4 {}.get_dispatch_info(), - DispatchInfo { weight: 3, class: DispatchClass::Normal, pays_fee: Pays::Yes }, + DispatchInfo { + weight: Weight::from_ref_time(3), + class: DispatchClass::Normal, + pays_fee: Pays::Yes + }, ); } @@ -544,7 +537,7 @@ fn call_name() { #[test] fn call_metadata() { use frame_support::dispatch::{CallMetadata, GetCallMetadata}; - let call = Call::Module3(module3::Call::::aux_4 {}); + let call = RuntimeCall::Module3(module3::Call::::aux_4 {}); let metadata = call.get_call_metadata(); let expected = CallMetadata { function_name: "aux_4".into(), pallet_name: "Module3".into() }; assert_eq!(metadata, expected); @@ -560,7 +553,7 @@ fn get_call_names() { #[test] fn get_module_names() { use frame_support::dispatch::GetCallMetadata; - let module_names = Call::get_module_names(); + let module_names = RuntimeCall::get_module_names(); assert_eq!( [ "System", @@ -582,13 +575,13 @@ fn get_module_names() { #[test] fn call_subtype_conversion() { use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; - let call = Call::Module3(module3::Call::::fail {}); + let call = RuntimeCall::Module3(module3::Call::::fail {}); let subcall: Option<&CallableCallFor> = call.is_sub_type(); let subcall_none: Option<&CallableCallFor> = call.is_sub_type(); assert_eq!(Some(&module3::Call::::fail {}), subcall); assert_eq!(None, subcall_none); - let from = Call::from(subcall.unwrap().clone()); + let from = RuntimeCall::from(subcall.unwrap().clone()); assert_eq!(from, call); } @@ -739,40 +732,66 @@ fn test_metadata() { fn pallet_in_runtime_is_correct() { assert_eq!(PalletInfo::index::().unwrap(), 30); assert_eq!(PalletInfo::name::().unwrap(), "System"); + assert_eq!(PalletInfo::module_name::().unwrap(), "system"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 31); assert_eq!(PalletInfo::name::().unwrap(), "Module1_1"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 32); assert_eq!(PalletInfo::name::().unwrap(), "Module2"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module2"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 33); assert_eq!(PalletInfo::name::().unwrap(), "Module1_2"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 34); assert_eq!(PalletInfo::name::().unwrap(), "NestedModule3"); + assert_eq!(PalletInfo::module_name::().unwrap(), "nested::module3"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 35); assert_eq!(PalletInfo::name::().unwrap(), "Module3"); + assert_eq!(PalletInfo::module_name::().unwrap(), "self::module3"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 6); assert_eq!(PalletInfo::name::().unwrap(), "Module1_3"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 3); assert_eq!(PalletInfo::name::().unwrap(), "Module1_4"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 4); assert_eq!(PalletInfo::name::().unwrap(), "Module1_5"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 1); assert_eq!(PalletInfo::name::().unwrap(), "Module1_6"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 2); assert_eq!(PalletInfo::name::().unwrap(), "Module1_7"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 12); assert_eq!(PalletInfo::name::().unwrap(), "Module1_8"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 13); assert_eq!(PalletInfo::name::().unwrap(), "Module1_9"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); } diff --git a/frame/support/test/tests/construct_runtime_ui.rs b/frame/support/test/tests/construct_runtime_ui.rs index a55e800628582..42fd87ca95c0e 100644 --- a/frame/support/test/tests/construct_runtime_ui.rs +++ b/frame/support/test/tests/construct_runtime_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,13 +15,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; - #[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] #[test] fn ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. - env::set_var("SKIP_WASM_BUILD", "1"); + std::env::set_var("SKIP_WASM_BUILD", "1"); let t = trybuild::TestCases::new(); t.compile_fail("tests/construct_runtime_ui/*.rs"); diff --git a/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs new file mode 100644 index 0000000000000..7a074db9986a2 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs @@ -0,0 +1,33 @@ +use frame_support::construct_runtime; +use sp_runtime::{generic, traits::BlakeTwo256}; +use sp_core::sr25519; + +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); +} + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u64; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +impl pallet::Config for Runtime {} + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet exclude_parts { Pallet } use_parts { Pallet }, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr new file mode 100644 index 0000000000000..5f1fccd43c549 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr @@ -0,0 +1,19 @@ +error: Unexpected tokens, expected one of `=`, `,` + --> tests/construct_runtime_ui/both_use_and_excluded_parts.rs:29:43 + | +29 | Pallet: pallet exclude_parts { Pallet } use_parts { Pallet }, + | ^^^^^^^^^ + +error[E0412]: cannot find type `RuntimeCall` in this scope + --> tests/construct_runtime_ui/both_use_and_excluded_parts.rs:18:64 + | +18 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | - ^^^^^^^^^^^ not found in this scope + | | + | help: you might be missing a type parameter: `` + +error[E0412]: cannot find type `Runtime` in this scope + --> tests/construct_runtime_ui/both_use_and_excluded_parts.rs:20:25 + | +20 | impl pallet::Config for Runtime {} + | ^^^^^^^ not found in this scope diff --git a/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs b/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs new file mode 100644 index 0000000000000..6d21c2a6e170a --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs @@ -0,0 +1,13 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + UncheckedExtrinsic = UncheckedExtrinsic, + Block = Block, + NodeBlock = Block, + { + System: frame_system exclude_parts { Call, Call }, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.stderr b/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.stderr new file mode 100644 index 0000000000000..75de56076528b --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.stderr @@ -0,0 +1,5 @@ +error: `Call` was already declared before. Please remove the duplicate declaration + --> $DIR/duplicate_exclude.rs:9:46 + | +9 | System: frame_system exclude_parts { Call, Call }, + | ^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs b/frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs new file mode 100644 index 0000000000000..16cbf1e82cf89 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs @@ -0,0 +1,13 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + UncheckedExtrinsic = UncheckedExtrinsic, + Block = Block, + NodeBlock = Block, + { + System: frame_system exclude_part { Call }, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_missspell.stderr b/frame/support/test/tests/construct_runtime_ui/exclude_missspell.stderr new file mode 100644 index 0000000000000..82e6aa6c8e308 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/exclude_missspell.stderr @@ -0,0 +1,5 @@ +error: Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,` + --> $DIR/exclude_missspell.rs:9:24 + | +9 | System: frame_system exclude_part { Call }, + | ^^^^^^^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs new file mode 100644 index 0000000000000..302b5af7ecd26 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs @@ -0,0 +1,38 @@ +use frame_support::construct_runtime; +use sp_runtime::{generic, traits::BlakeTwo256}; +use sp_core::sr25519; + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + type Foo = StorageValue; +} + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u64; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +impl pallet::Config for Runtime {} + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet exclude_parts { Call }, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr new file mode 100644 index 0000000000000..c623ecfbf4cdf --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr @@ -0,0 +1,19 @@ +error: Invalid pallet part specified, the pallet `Pallet` doesn't have the `Call` part. Available parts are: `Pallet`, `Storage`. + --> tests/construct_runtime_ui/exclude_undefined_part.rs:34:34 + | +34 | Pallet: pallet exclude_parts { Call }, + | ^^^^ + +error[E0412]: cannot find type `RuntimeCall` in this scope + --> tests/construct_runtime_ui/exclude_undefined_part.rs:23:64 + | +23 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | - ^^^^^^^^^^^ not found in this scope + | | + | help: you might be missing a type parameter: `` + +error[E0412]: cannot find type `Runtime` in this scope + --> tests/construct_runtime_ui/exclude_undefined_part.rs:25:25 + | +25 | impl pallet::Config for Runtime {} + | ^^^^^^^ not found in this scope diff --git a/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.rs b/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.rs new file mode 100644 index 0000000000000..79b5632babd95 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.rs @@ -0,0 +1,14 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + #[cfg(test)] + System: frame_system::{Pallet, Call, Storage, Config, Event}, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.stderr b/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.stderr new file mode 100644 index 0000000000000..a86a839615aa0 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.stderr @@ -0,0 +1,5 @@ +error: `System` pallet declaration is feature gated, please remove any `#[cfg]` attributes + --> tests/construct_runtime_ui/feature_gated_system_pallet.rs:10:3 + | +10 | System: frame_system::{Pallet, Call, Storage, Config, Event}, + | ^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.rs b/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.rs new file mode 100644 index 0000000000000..a1d39fa76ea85 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.rs @@ -0,0 +1,15 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet}, + #[cfg(feature = 1)] + Balance: balances::{Config, Call}, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.stderr new file mode 100644 index 0000000000000..68366a3410bf1 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.stderr @@ -0,0 +1,6 @@ +error: feature = 1 + ^ expected one of ``, `all`, `any`, `not` here + --> tests/construct_runtime_ui/invalid_meta_literal.rs:10:3 + | +10 | #[cfg(feature = 1)] + | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr index 50505b9130cbe..db96b8749ca11 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr @@ -1,5 +1,5 @@ -error: expected one of: identifier, curly braces, `<` - --> $DIR/invalid_module_details.rs:9:19 +error: Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,` + --> $DIR/invalid_module_details.rs:9:17 | 9 | system: System::(), - | ^^ + | ^^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr index 3b967f96d7b4e..6025de82bd206 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr @@ -1,4 +1,4 @@ -error: expected `::` +error: Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,` --> $DIR/invalid_token_after_module.rs:9:18 | 9 | system: System ? diff --git a/frame/support/test/tests/construct_runtime_ui/missing_where_block.stderr b/frame/support/test/tests/construct_runtime_ui/missing_where_block.stderr index 4af672a2610b6..c6baf8fc24d07 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_where_block.stderr +++ b/frame/support/test/tests/construct_runtime_ui/missing_where_block.stderr @@ -1,5 +1,5 @@ error: expected `where` - --> $DIR/missing_where_block.rs:4:19 + --> tests/construct_runtime_ui/missing_where_block.rs:4:19 | 4 | pub enum Runtime {} - | ^^ + | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/missing_where_param.stderr b/frame/support/test/tests/construct_runtime_ui/missing_where_param.stderr index ac7313523c0c4..fb7e38b53dcd6 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_where_param.stderr +++ b/frame/support/test/tests/construct_runtime_ui/missing_where_param.stderr @@ -1,5 +1,5 @@ error: Missing associated type for `UncheckedExtrinsic`. Add `UncheckedExtrinsic` = ... to where section. - --> $DIR/missing_where_param.rs:7:2 + --> tests/construct_runtime_ui/missing_where_param.rs:7:2 | 7 | {} - | ^^ + | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.rs b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.rs index 89774eb8a7702..2ca9676406579 100644 --- a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.rs +++ b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.rs @@ -3,20 +3,47 @@ use sp_runtime::{generic, traits::BlakeTwo256}; use sp_core::sr25519; pub type Signature = sr25519::Signature; -pub type BlockNumber = u64; +pub type BlockNumber = u32; pub type Header = generic::Header; pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; impl test_pallet::Config for Runtime {} +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + construct_runtime! { pub enum Runtime where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, Pallet: test_pallet::{Pallet, Config}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr index 5bc831f58988b..d35565fb933ac 100644 --- a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr +++ b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr @@ -1,88 +1,46 @@ error: `Pallet` does not have the std feature enabled, this will cause the `test_pallet::GenesisConfig` type to be undefined. - --> $DIR/no_std_genesis_config.rs:13:1 + --> tests/construct_runtime_ui/no_std_genesis_config.rs:40:1 | -13 | / construct_runtime! { -14 | | pub enum Runtime where -15 | | Block = Block, -16 | | NodeBlock = Block, +40 | / construct_runtime! { +41 | | pub enum Runtime where +42 | | Block = Block, +43 | | NodeBlock = Block, ... | -21 | | } -22 | | } +48 | | } +49 | | } | |_^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/no_std_genesis_config.rs:19:11 - | -19 | System: system::{Pallet, Call, Storage, Config, Event}, - | ^^^^^^ use of undeclared crate or module `system` - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/no_std_genesis_config.rs:13:1 - | -13 | / construct_runtime! { -14 | | pub enum Runtime where -15 | | Block = Block, -16 | | NodeBlock = Block, -... | -21 | | } -22 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum - | -1 | use frame_system::RawOrigin; - | - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/no_std_genesis_config.rs:13:1 - | -13 | / construct_runtime! { -14 | | pub enum Runtime where -15 | | Block = Block, -16 | | NodeBlock = Block, -... | -21 | | } -22 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items - | -1 | use frame_support_test::Pallet; - | -1 | use frame_system::Pallet; - | -1 | use test_pallet::Pallet; - | + = note: this error originates in the macro `test_pallet::__substrate_genesis_config_check::is_std_enabled_for_genesis` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0412]: cannot find type `GenesisConfig` in crate `test_pallet` - --> $DIR/no_std_genesis_config.rs:13:1 + --> tests/construct_runtime_ui/no_std_genesis_config.rs:40:1 | -13 | / construct_runtime! { -14 | | pub enum Runtime where -15 | | Block = Block, -16 | | NodeBlock = Block, +40 | / construct_runtime! { +41 | | pub enum Runtime where +42 | | Block = Block, +43 | | NodeBlock = Block, ... | -21 | | } -22 | | } +48 | | } +49 | | } | |_^ not found in `test_pallet` | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider importing this struct | 1 | use frame_system::GenesisConfig; | -error[E0277]: the trait bound `Runtime: frame_system::pallet::Config` is not satisfied - --> $DIR/no_std_genesis_config.rs:11:6 +error[E0283]: type annotations needed + --> tests/construct_runtime_ui/no_std_genesis_config.rs:40:1 | -11 | impl test_pallet::Config for Runtime {} - | ^^^^^^^^^^^^^^^^^^^ the trait `frame_system::pallet::Config` is not implemented for `Runtime` - | - ::: $WORKSPACE/frame/support/test/pallet/src/lib.rs +40 | / construct_runtime! { +41 | | pub enum Runtime where +42 | | Block = Block, +43 | | NodeBlock = Block, +... | +48 | | } +49 | | } + | |_^ cannot infer type | - | pub trait Config: frame_system::Config {} - | -------------------- required by this bound in `Config` + = note: cannot satisfy `_: std::default::Default` + = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.rs b/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.rs new file mode 100644 index 0000000000000..5691549c20f34 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.rs @@ -0,0 +1,26 @@ +use frame_support::construct_runtime; + +mod pallet_old { + pub trait Config: frame_system::Config {} + + decl_storage! { + trait Store for Module as Example {} + } + + decl_module! { + pub struct Module for enum Call where origin: T::RuntimeOrigin {} + } + +} +construct_runtime! { + pub enum Runtime where + UncheckedExtrinsic = UncheckedExtrinsic, + Block = Block, + NodeBlock = Block, + { + System: frame_system, + OldPallet: pallet_old, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.stderr b/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.stderr new file mode 100644 index 0000000000000..f8ec07e00106f --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.stderr @@ -0,0 +1,31 @@ +error[E0433]: failed to resolve: could not find `tt_default_parts` in `pallet_old` + --> $DIR/old_unsupported_pallet_decl.rs:15:1 + | +15 | / construct_runtime! { +16 | | pub enum Runtime where +17 | | UncheckedExtrinsic = UncheckedExtrinsic, +18 | | Block = Block, +... | +23 | | } +24 | | } + | |_^ could not find `tt_default_parts` in `pallet_old` + | + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot find macro `decl_storage` in this scope + --> $DIR/old_unsupported_pallet_decl.rs:6:2 + | +6 | decl_storage! { + | ^^^^^^^^^^^^ + | + = note: consider importing this macro: + frame_support::decl_storage + +error: cannot find macro `decl_module` in this scope + --> $DIR/old_unsupported_pallet_decl.rs:10:2 + | +10 | decl_module! { + | ^^^^^^^^^^^ + | + = note: consider importing this macro: + frame_support::decl_module diff --git a/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs new file mode 100644 index 0000000000000..b7ccadb5e3e58 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs @@ -0,0 +1,85 @@ +use frame_support::construct_runtime; +use sp_runtime::{generic, traits::BlakeTwo256}; +use sp_core::sr25519; + +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::error] + pub enum Error { + MyError(crate::Nested1), + } +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested1 { + Nested2(Nested2) +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested2 { + Nested3(Nested3) +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested3 { + Nested4(Nested4) +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested4 { + Num(u8) +} + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u32; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +impl pallet::Config for Runtime {} + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet::{Pallet}, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr new file mode 100644 index 0000000000000..99a543eef7a8a --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr @@ -0,0 +1,13 @@ +error[E0080]: evaluation of constant value failed + --> tests/construct_runtime_ui/pallet_error_too_large.rs:74:1 + | +74 | / construct_runtime! { +75 | | pub enum Runtime where +76 | | Block = Block, +77 | | NodeBlock = Block, +... | +82 | | } +83 | | } + | |_^ 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:74: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) diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_call_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_call_part.rs index c5b9fcca1f318..abe5c4cfeb343 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_call_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_call_part.rs @@ -12,20 +12,47 @@ mod pallet { } pub type Signature = sr25519::Signature; -pub type BlockNumber = u64; +pub type BlockNumber = u32; pub type Header = generic::Header; pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; impl pallet::Config for Runtime {} +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + construct_runtime! { pub enum Runtime where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet::{Pallet, Call}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr index 8781fe0df201a..6baf01e866f64 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr @@ -1,73 +1,16 @@ error: `Pallet` does not have #[pallet::call] defined, perhaps you should remove `Call` from construct_runtime? - --> $DIR/undefined_call_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_call_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } +57 | | } +58 | | } | |_- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_call_part.rs:28:11 - | -28 | System: system::{Pallet, Call, Storage, Config, Event}, - | ^^^^^^ use of undeclared crate or module `system` - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_call_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, -... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum - | -1 | use frame_system::RawOrigin; - | - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_call_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, -... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items - | -1 | use crate::pallet::Pallet; - | -1 | use frame_support_test::Pallet; - | -1 | use frame_system::Pallet; - | -1 | use test_pallet::Pallet; - | - -error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_call_part.rs:20:6 - | -8 | pub trait Config: frame_system::Config {} - | -------------------- required by this bound in `pallet::Config` -... -20 | impl pallet::Config for Runtime {} - | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` + = 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) diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs index 6aec45f240c90..9a6ac5c6251fb 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs @@ -12,20 +12,47 @@ mod pallet { } pub type Signature = sr25519::Signature; -pub type BlockNumber = u64; +pub type BlockNumber = u32; pub type Header = generic::Header; pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; impl pallet::Config for Runtime {} +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + construct_runtime! { pub enum Runtime where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet::{Pallet, Event}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr index fa837698aa642..ff8ecf3041bf6 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr @@ -1,111 +1,34 @@ error: `Pallet` does not have #[pallet::event] defined, perhaps you should remove `Event` from construct_runtime? - --> $DIR/undefined_event_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_event_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } +57 | | } +58 | | } | |_- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_event_part.rs:28:11 - | -28 | System: system::{Pallet, Call, Storage, Config, Event}, - | ^^^^^^ use of undeclared crate or module `system` + = note: this error originates in the macro `pallet::__substrate_event_check::is_event_part_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0412]: cannot find type `Event` in module `pallet` - --> $DIR/undefined_event_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_event_part.rs:49:1 | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } +57 | | } +58 | | } | |_^ not found in `pallet` | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider importing this enum | 1 | use frame_system::Event; | - -error[E0412]: cannot find type `Event` in module `pallet` - --> $DIR/undefined_event_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, -... | -30 | | } -31 | | } - | |_^ not found in `pallet` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items - | -1 | use crate::Event; - | -1 | use frame_system::Event; - | - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_event_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, -... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum - | -1 | use frame_system::RawOrigin; - | - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_event_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, -... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items - | -1 | use crate::pallet::Pallet; - | -1 | use frame_support_test::Pallet; - | -1 | use frame_system::Pallet; - | -1 | use test_pallet::Pallet; - | - -error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_event_part.rs:20:6 - | -8 | pub trait Config: frame_system::Config {} - | -------------------- required by this bound in `pallet::Config` -... -20 | impl pallet::Config for Runtime {} - | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs index 5e08fd96fa1ad..4facf85e280c0 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs @@ -12,20 +12,47 @@ mod pallet { } pub type Signature = sr25519::Signature; -pub type BlockNumber = u64; +pub type BlockNumber = u32; pub type Header = generic::Header; pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; impl pallet::Config for Runtime {} +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + construct_runtime! { pub enum Runtime where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet::{Pallet, Config}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr index 699f66a414ed2..046369e1112b0 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr @@ -1,91 +1,49 @@ error: `Pallet` does not have #[pallet::genesis_config] defined, perhaps you should remove `Config` from construct_runtime? - --> $DIR/undefined_genesis_config_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } +57 | | } +58 | | } | |_- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_genesis_config_part.rs:28:17 - | -28 | System: system::{Pallet, Call, Storage, Config, Event}, - | ^^^^^^ use of undeclared crate or module `system` - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_genesis_config_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, -... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum - | -1 | use frame_system::RawOrigin; - | - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_genesis_config_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, -... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items - | -1 | use crate::pallet::Pallet; - | -1 | use frame_support_test::Pallet; - | -1 | use frame_system::Pallet; - | -1 | use test_pallet::Pallet; - | + = note: this error originates in the macro `pallet::__substrate_genesis_config_check::is_genesis_config_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0412]: cannot find type `GenesisConfig` in module `pallet` - --> $DIR/undefined_genesis_config_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:49:1 | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } +57 | | } +58 | | } | |_^ not found in `pallet` | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider importing this struct | 1 | use frame_system::GenesisConfig; | -error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_genesis_config_part.rs:20:6 +error[E0283]: type annotations needed + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:49:1 | -8 | pub trait Config: frame_system::Config {} - | -------------------- required by this bound in `pallet::Config` -... -20 | impl pallet::Config for Runtime {} - | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, +... | +57 | | } +58 | | } + | |_^ cannot infer type + | + = note: cannot satisfy `_: std::default::Default` + = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs index 06c36a30f5506..322fa2c297285 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs @@ -12,20 +12,47 @@ mod pallet { } pub type Signature = sr25519::Signature; -pub type BlockNumber = u64; +pub type BlockNumber = u32; pub type Header = generic::Header; pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; impl pallet::Config for Runtime {} +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + construct_runtime! { pub enum Runtime where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet::{Pallet, Inherent}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr index 88ff9ee910937..74af0c264cd5e 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr @@ -1,73 +1,116 @@ error: `Pallet` does not have #[pallet::inherent] defined, perhaps you should remove `Inherent` from construct_runtime? - --> $DIR/undefined_inherent_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } +57 | | } +58 | | } | |_- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `pallet::__substrate_inherent_check::is_inherent_part_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_inherent_part.rs:28:11 +error[E0599]: no function or associated item named `create_inherent` found for struct `pallet::Pallet` in the current scope + --> tests/construct_runtime_ui/undefined_inherent_part.rs:49:1 | -28 | System: system::{Pallet, Call, Storage, Config, Event}, - | ^^^^^^ use of undeclared crate or module `system` - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_inherent_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +11 | pub struct Pallet(_); + | -------------------- function or associated item `create_inherent` not found for this struct +... +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum - | -1 | use frame_system::RawOrigin; +57 | | } +58 | | } + | |_^ function or associated item not found in `pallet::Pallet` | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `create_inherent`, perhaps you need to implement it: + candidate #1: `ProvideInherent` + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_inherent_part.rs:22:1 +error[E0599]: no function or associated item named `is_inherent` found for struct `pallet::Pallet` in the current scope + --> tests/construct_runtime_ui/undefined_inherent_part.rs:49:1 | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +11 | pub struct Pallet(_); + | -------------------- function or associated item `is_inherent` not found for this struct +... +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } - | |_^ not found in `system` +57 | | } +58 | | } + | |_^ function or associated item not found in `pallet::Pallet` | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items - | -1 | use crate::pallet::Pallet; + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `is_inherent`, perhaps you need to implement it: + candidate #1: `ProvideInherent` + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: no function or associated item named `check_inherent` found for struct `pallet::Pallet` in the current scope + --> tests/construct_runtime_ui/undefined_inherent_part.rs:49:1 | -1 | use frame_support_test::Pallet; +11 | pub struct Pallet(_); + | -------------------- function or associated item `check_inherent` not found for this struct +... +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, +... | +57 | | } +58 | | } + | |_^ function or associated item not found in `pallet::Pallet` | -1 | use frame_system::Pallet; + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `check_inherent`, perhaps you need to implement it: + candidate #1: `ProvideInherent` + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: no associated item named `INHERENT_IDENTIFIER` found for struct `pallet::Pallet` in the current scope + --> tests/construct_runtime_ui/undefined_inherent_part.rs:49:1 | -1 | use test_pallet::Pallet; +11 | pub struct Pallet(_); + | -------------------- associated item `INHERENT_IDENTIFIER` not found for this struct +... +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, +... | +57 | | } +58 | | } + | |_^ associated item not found in `pallet::Pallet` | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `INHERENT_IDENTIFIER`, perhaps you need to implement it: + candidate #1: `ProvideInherent` + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_inherent_part.rs:20:6 +error[E0599]: no function or associated item named `is_inherent_required` found for struct `pallet::Pallet` in the current scope + --> tests/construct_runtime_ui/undefined_inherent_part.rs:49:1 | -8 | pub trait Config: frame_system::Config {} - | -------------------- required by this bound in `pallet::Config` +11 | pub struct Pallet(_); + | -------------------- function or associated item `is_inherent_required` not found for this struct ... -20 | impl pallet::Config for Runtime {} - | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, +... | +57 | | } +58 | | } + | |_^ function or associated item not found in `pallet::Pallet` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `is_inherent_required`, perhaps you need to implement it: + candidate #1: `ProvideInherent` + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs index bec5c27ec0346..55cd5b545d6ba 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs @@ -12,20 +12,47 @@ mod pallet { } pub type Signature = sr25519::Signature; -pub type BlockNumber = u64; +pub type BlockNumber = u32; pub type Header = generic::Header; pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; impl pallet::Config for Runtime {} +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + construct_runtime! { pub enum Runtime where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet::{Pallet, Origin}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr index 3b3aa75c1ea08..4907053b12877 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr @@ -1,111 +1,52 @@ error: `Pallet` does not have #[pallet::origin] defined, perhaps you should remove `Origin` from construct_runtime? - --> $DIR/undefined_origin_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_origin_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } +57 | | } +58 | | } | |_- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_origin_part.rs:28:11 - | -28 | System: system::{Pallet, Call, Storage, Config, Event}, - | ^^^^^^ use of undeclared crate or module `system` - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_origin_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, -... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum - | -1 | use frame_system::RawOrigin; - | + = note: this error originates in the macro `pallet::__substrate_origin_check::is_origin_part_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0412]: cannot find type `Origin` in module `pallet` - --> $DIR/undefined_origin_part.rs:22:1 + --> tests/construct_runtime_ui/undefined_origin_part.rs:49:1 | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } +57 | | } +58 | | } | |_^ not found in `pallet` | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider importing this type alias | 1 | use frame_system::Origin; | -error[E0412]: cannot find type `Origin` in module `pallet` - --> $DIR/undefined_origin_part.rs:22:1 +error[E0282]: type annotations needed + --> tests/construct_runtime_ui/undefined_origin_part.rs:49:1 | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } - | |_^ not found in `pallet` +57 | | } +58 | | } + | |_^ cannot infer type of the type parameter `AccountId` declared on the enum `RawOrigin` | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items - | -1 | use crate::Origin; - | -1 | use frame_system::Origin; + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider specifying the generic argument | - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_origin_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, -... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items - | -1 | use crate::pallet::Pallet; - | -1 | use frame_support_test::Pallet; - | -1 | use frame_system::Pallet; - | -1 | use test_pallet::Pallet; - | - -error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_origin_part.rs:20:6 - | -8 | pub trait Config: frame_system::Config {} - | -------------------- required by this bound in `pallet::Config` -... -20 | impl pallet::Config for Runtime {} - | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` +58 | }:: + | +++++++++++++ diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs index 816f52b91cccb..0cf305a7dc055 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs @@ -12,20 +12,47 @@ mod pallet { } pub type Signature = sr25519::Signature; -pub type BlockNumber = u64; +pub type BlockNumber = u32; pub type Header = generic::Header; pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; impl pallet::Config for Runtime {} +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + construct_runtime! { pub enum Runtime where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet::{Pallet, ValidateUnsigned}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr index ac12c56d5c279..6f0b13c58933e 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr @@ -1,73 +1,72 @@ error: `Pallet` does not have #[pallet::validate_unsigned] defined, perhaps you should remove `ValidateUnsigned` from construct_runtime? - --> $DIR/undefined_validate_unsigned_part.rs:5:1 + --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:5:1 | 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } +57 | | } +58 | | } | |_- in this macro invocation | - = note: this error originates in a macro (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 `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_validate_unsigned_part.rs:28:11 +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:56:3 | -28 | System: system::{Pallet, Call, Storage, Config, Event}, - | ^^^^^^ use of undeclared crate or module `system` - -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_validate_unsigned_part.rs:22:1 - | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum - | -1 | use frame_system::RawOrigin; - | +56 | | Pallet: pallet::{Pallet, ValidateUnsigned}, + | | ^^^^^^ variant or associated item not found in `RuntimeCall` +57 | | } +58 | | } + | |_- variant or associated item `Pallet` not found for this enum -error[E0433]: failed to resolve: use of undeclared crate or module `system` - --> $DIR/undefined_validate_unsigned_part.rs:22:1 +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:49:1 | -22 | / construct_runtime! { -23 | | pub enum Runtime where -24 | | Block = Block, -25 | | NodeBlock = Block, +11 | pub struct Pallet(_); + | -------------------- function or associated item `pre_dispatch` not found for this struct +... +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, ... | -30 | | } -31 | | } - | |_^ not found in `system` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items - | -1 | use crate::pallet::Pallet; - | -1 | use frame_support_test::Pallet; - | -1 | use frame_system::Pallet; - | -1 | use test_pallet::Pallet; +57 | | } +58 | | } + | |_^ function or associated item not found in `pallet::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 `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: frame_system::Config` is not satisfied - --> $DIR/undefined_validate_unsigned_part.rs:20:6 +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:49:1 | -8 | pub trait Config: frame_system::Config {} - | -------------------- required by this bound in `pallet::Config` +11 | pub struct Pallet(_); + | -------------------- function or associated item `validate_unsigned` not found for this struct ... -20 | impl pallet::Config for Runtime {} - | ^^^^^^^^^^^^^^ the trait `frame_system::Config` is not implemented for `Runtime` +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, +... | +57 | | } +58 | | } + | |_^ function or associated item not found in `pallet::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 `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.rs b/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.rs new file mode 100644 index 0000000000000..b93adf9a780a7 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.rs @@ -0,0 +1,15 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet}, + #[cfg(feature(test))] + Balance: balances::{Config, Call}, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.stderr b/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.stderr new file mode 100644 index 0000000000000..98d99a0d34997 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.stderr @@ -0,0 +1,6 @@ +error: feature(test) + ^ expected one of `=`, `,`, `)` here + --> tests/construct_runtime_ui/unsupported_meta_structure.rs:10:3 + | +10 | #[cfg(feature(test))] + | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.rs b/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.rs new file mode 100644 index 0000000000000..3ec8b9db1d435 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.rs @@ -0,0 +1,15 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet}, + #[attr] + Balance: balances::{Config, Call}, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.stderr b/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.stderr new file mode 100644 index 0000000000000..fceb2b8a99db8 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.stderr @@ -0,0 +1,5 @@ +error: Unsupported attribute, only #[cfg] is supported on pallet declarations in `construct_runtime` + --> tests/construct_runtime_ui/unsupported_pallet_attr.rs:10:3 + | +10 | #[attr] + | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs new file mode 100644 index 0000000000000..c74e29bc05469 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs @@ -0,0 +1,38 @@ +use frame_support::construct_runtime; +use sp_runtime::{generic, traits::BlakeTwo256}; +use sp_core::sr25519; + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + type Foo = StorageValue; +} + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u64; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +impl pallet::Config for Runtime {} + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet use_parts { Call }, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr new file mode 100644 index 0000000000000..e289c75fb008a --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr @@ -0,0 +1,19 @@ +error: Invalid pallet part specified, the pallet `Pallet` doesn't have the `Call` part. Available parts are: `Pallet`, `Storage`. + --> tests/construct_runtime_ui/use_undefined_part.rs:34:30 + | +34 | Pallet: pallet use_parts { Call }, + | ^^^^ + +error[E0412]: cannot find type `RuntimeCall` in this scope + --> tests/construct_runtime_ui/use_undefined_part.rs:23:64 + | +23 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | - ^^^^^^^^^^^ not found in this scope + | | + | help: you might be missing a type parameter: `` + +error[E0412]: cannot find type `Runtime` in this scope + --> tests/construct_runtime_ui/use_undefined_part.rs:25:25 + | +25 | impl pallet::Config for Runtime {} + | ^^^^^^^ not found in this scope diff --git a/frame/support/test/tests/decl_module_ui.rs b/frame/support/test/tests/decl_module_ui.rs index 2c097bb6e1332..292451335e7ea 100644 --- a/frame/support/test/tests/decl_module_ui.rs +++ b/frame/support/test/tests/decl_module_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,14 @@ // limitations under the License. #[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] #[test] fn decl_module_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs index cc7c1ff219d8b..0f662b96e2b13 100644 --- a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs +++ b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs @@ -1,5 +1,5 @@ frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=self { + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { fn integrity_test() {} fn integrity_test() {} diff --git a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr index 3bf5f58b43a39..4212707599d41 100644 --- a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr +++ b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr @@ -1,8 +1,8 @@ error: `integrity_test` can only be passed once as input. - --> $DIR/reserved_keyword_two_times_integrity_test.rs:1:1 + --> tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs:1:1 | 1 | / frame_support::decl_module! { -2 | | pub struct Module for enum Call where origin: T::Origin, system=self { +2 | | pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { 3 | | fn integrity_test() {} 4 | | 5 | | fn integrity_test() {} @@ -10,16 +10,10 @@ error: `integrity_test` can only be passed once as input. 7 | | } | |_^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::decl_module` which comes from the expansion of the macro `frame_support::decl_module` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0601]: `main` function not found in crate `$CRATE` - --> $DIR/reserved_keyword_two_times_integrity_test.rs:1:1 + --> tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs:7:2 | -1 | / frame_support::decl_module! { -2 | | pub struct Module for enum Call where origin: T::Origin, system=self { -3 | | fn integrity_test() {} -4 | | -5 | | fn integrity_test() {} -6 | | } -7 | | } - | |_^ consider adding a `main` function to `$DIR/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs` +7 | } + | ^ consider adding a `main` function to `$DIR/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs` diff --git a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.rs b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.rs index ddde7c72c1cc5..ea0746b1c501c 100644 --- a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.rs +++ b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.rs @@ -1,5 +1,5 @@ frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=self { + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { fn on_initialize() -> Weight { 0 } @@ -9,3 +9,5 @@ frame_support::decl_module! { } } } + +fn main() {} diff --git a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.stderr b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.stderr index 2911d7ded8a23..94bde853e4cc8 100644 --- a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.stderr +++ b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.stderr @@ -1,8 +1,8 @@ error: `on_initialize` can only be passed once as input. - --> $DIR/reserved_keyword_two_times_on_initialize.rs:1:1 + --> tests/decl_module_ui/reserved_keyword_two_times_on_initialize.rs:1:1 | 1 | / frame_support::decl_module! { -2 | | pub struct Module for enum Call where origin: T::Origin, system=self { +2 | | pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { 3 | | fn on_initialize() -> Weight { 4 | | 0 ... | @@ -10,16 +10,4 @@ error: `on_initialize` can only be passed once as input. 11 | | } | |_^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0601]: `main` function not found in crate `$CRATE` - --> $DIR/reserved_keyword_two_times_on_initialize.rs:1:1 - | -1 | / frame_support::decl_module! { -2 | | pub struct Module for enum Call where origin: T::Origin, system=self { -3 | | fn on_initialize() -> Weight { -4 | | 0 -... | -10 | | } -11 | | } - | |_^ consider adding a `main` function to `$DIR/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.rs` + = note: this error originates in the macro `$crate::decl_module` which comes from the expansion of the macro `frame_support::decl_module` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/decl_storage.rs b/frame/support/test/tests/decl_storage.rs index 347a3130daa79..a4e420eb454b6 100644 --- a/frame/support/test/tests/decl_storage.rs +++ b/frame/support/test/tests/decl_storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,7 @@ mod tests { use sp_io::TestExternalities; frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} } pub trait Config: frame_support_test::Config { @@ -94,7 +94,7 @@ mod tests { struct TraitImpl {} impl frame_support_test::Config for TraitImpl { - type Origin = u32; + type RuntimeOrigin = u32; type BlockNumber = u32; type PalletInfo = frame_support_test::PanicPalletInfo; type DbWeight = (); @@ -617,7 +617,7 @@ mod test2 { pub trait Config: frame_support_test::Config {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} } type PairOf = (T, T); @@ -639,7 +639,7 @@ mod test2 { struct TraitImpl {} impl frame_support_test::Config for TraitImpl { - type Origin = u32; + type RuntimeOrigin = u32; type BlockNumber = u32; type PalletInfo = frame_support_test::PanicPalletInfo; type DbWeight = (); @@ -695,7 +695,7 @@ mod test3 { pub trait Config: frame_support_test::Config {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} } frame_support::decl_storage! { trait Store for Module as Test { @@ -708,7 +708,7 @@ mod test3 { struct TraitImpl {} impl frame_support_test::Config for TraitImpl { - type Origin = u32; + type RuntimeOrigin = u32; type BlockNumber = u32; type PalletInfo = frame_support_test::PanicPalletInfo; type DbWeight = (); @@ -726,7 +726,7 @@ mod test_append_and_len { pub trait Config: frame_support_test::Config {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} } #[derive(PartialEq, Eq, Clone, Encode, Decode, scale_info::TypeInfo)] @@ -753,7 +753,7 @@ mod test_append_and_len { struct Test {} impl frame_support_test::Config for Test { - type Origin = u32; + type RuntimeOrigin = u32; type BlockNumber = u32; type PalletInfo = frame_support_test::PanicPalletInfo; type DbWeight = (); @@ -797,7 +797,7 @@ mod test_append_and_len { TestExternalities::default().execute_with(|| { let key = JustVec::hashed_key(); // Set it to some invalid value. - frame_support::storage::unhashed::put_raw(&key, &*b"1"); + frame_support::storage::unhashed::put_raw(&key, b"1"); assert!(JustVec::get().is_empty()); assert_eq!(frame_support::storage::unhashed::get_raw(&key), Some(b"1".to_vec())); diff --git a/frame/support/test/tests/decl_storage_ui.rs b/frame/support/test/tests/decl_storage_ui.rs index 99d2da87aca28..34dfea8601ab9 100644 --- a/frame/support/test/tests/decl_storage_ui.rs +++ b/frame/support/test/tests/decl_storage_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,14 @@ // limitations under the License. #[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] #[test] fn decl_storage_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/frame/support/test/tests/decl_storage_ui/config_duplicate.rs b/frame/support/test/tests/decl_storage_ui/config_duplicate.rs index 17f80c8c84755..af74823f64ca6 100644 --- a/frame/support/test/tests/decl_storage_ui/config_duplicate.rs +++ b/frame/support/test/tests/decl_storage_ui/config_duplicate.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ pub trait Config: frame_support_test::Config {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} } frame_support::decl_storage!{ diff --git a/frame/support/test/tests/decl_storage_ui/config_get_duplicate.rs b/frame/support/test/tests/decl_storage_ui/config_get_duplicate.rs index fec6aeb64cec4..8805effd9e725 100644 --- a/frame/support/test/tests/decl_storage_ui/config_get_duplicate.rs +++ b/frame/support/test/tests/decl_storage_ui/config_get_duplicate.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ pub trait Config: frame_support_test::Config {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} } frame_support::decl_storage!{ diff --git a/frame/support/test/tests/decl_storage_ui/get_duplicate.rs b/frame/support/test/tests/decl_storage_ui/get_duplicate.rs index 13c57a638bb18..5e05887110537 100644 --- a/frame/support/test/tests/decl_storage_ui/get_duplicate.rs +++ b/frame/support/test/tests/decl_storage_ui/get_duplicate.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ pub trait Config: frame_support_test::Config {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} } frame_support::decl_storage!{ diff --git a/frame/support/test/tests/derive_no_bound.rs b/frame/support/test/tests/derive_no_bound.rs index 1827844664fa7..f891b3a2d2db8 100644 --- a/frame/support/test/tests/derive_no_bound.rs +++ b/frame/support/test/tests/derive_no_bound.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -110,10 +110,33 @@ fn test_struct_unnamed() { assert!(b != a_1); } +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +struct StructNoGenerics { + field1: u32, + field2: u64, +} + +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +enum EnumNoGenerics { + #[default] + VariantUnnamed(u32, u64), + VariantNamed { + a: u32, + b: u64, + }, + VariantUnit, +} + #[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] enum Enum { + #[default] VariantUnnamed(u32, u64, T::C, core::marker::PhantomData<(U, V)>), - VariantNamed { a: u32, b: u64, c: T::C, phantom: core::marker::PhantomData<(U, V)> }, + VariantNamed { + a: u32, + b: u64, + c: T::C, + phantom: core::marker::PhantomData<(U, V)>, + }, VariantUnit, VariantUnit2, } @@ -121,7 +144,12 @@ enum Enum { // enum that will have a named default. #[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] enum Enum2 { - VariantNamed { a: u32, b: u64, c: T::C }, + #[default] + VariantNamed { + a: u32, + b: u64, + c: T::C, + }, VariantUnnamed(u32, u64, T::C), VariantUnit, VariantUnit2, @@ -130,8 +158,13 @@ enum Enum2 { // enum that will have a unit default. #[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] enum Enum3 { + #[default] VariantUnit, - VariantNamed { a: u32, b: u64, c: T::C }, + VariantNamed { + a: u32, + b: u64, + c: T::C, + }, VariantUnnamed(u32, u64, T::C), VariantUnit2, } diff --git a/frame/support/test/tests/derive_no_bound_ui.rs b/frame/support/test/tests/derive_no_bound_ui.rs index 434671e19b105..d714e1113625a 100644 --- a/frame/support/test/tests/derive_no_bound_ui.rs +++ b/frame/support/test/tests/derive_no_bound_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,14 @@ // limitations under the License. #[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] #[test] fn derive_no_bound_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/frame/support/test/tests/derive_no_bound_ui/clone.stderr b/frame/support/test/tests/derive_no_bound_ui/clone.stderr index 4b253ad12451b..7744586e56bf4 100644 --- a/frame/support/test/tests/derive_no_bound_ui/clone.stderr +++ b/frame/support/test/tests/derive_no_bound_ui/clone.stderr @@ -1,7 +1,5 @@ error[E0277]: the trait bound `::C: Clone` is not satisfied - --> $DIR/clone.rs:7:2 + --> tests/derive_no_bound_ui/clone.rs:7:2 | 7 | c: T::C, | ^ the trait `Clone` is not implemented for `::C` - | - = note: required by `clone` diff --git a/frame/support/test/tests/derive_no_bound_ui/debug.stderr b/frame/support/test/tests/derive_no_bound_ui/debug.stderr index 7580cab2ea0b3..acc7f80b37663 100644 --- a/frame/support/test/tests/derive_no_bound_ui/debug.stderr +++ b/frame/support/test/tests/derive_no_bound_ui/debug.stderr @@ -1,8 +1,8 @@ error[E0277]: `::C` doesn't implement `std::fmt::Debug` - --> $DIR/debug.rs:7:2 + --> tests/derive_no_bound_ui/debug.rs:7:2 | 7 | c: T::C, | ^ `::C` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | = help: the trait `std::fmt::Debug` is not implemented for `::C` - = note: required for the cast to the object type `dyn std::fmt::Debug` + = note: required for the cast from `::C` to the object type `dyn std::fmt::Debug` diff --git a/frame/support/test/tests/derive_no_bound_ui/default.stderr b/frame/support/test/tests/derive_no_bound_ui/default.stderr index d58b5e9185268..d56dd438f2a7f 100644 --- a/frame/support/test/tests/derive_no_bound_ui/default.stderr +++ b/frame/support/test/tests/derive_no_bound_ui/default.stderr @@ -1,7 +1,5 @@ error[E0277]: the trait bound `::C: std::default::Default` is not satisfied - --> $DIR/default.rs:7:2 + --> tests/derive_no_bound_ui/default.rs:7:2 | 7 | c: T::C, | ^ the trait `std::default::Default` is not implemented for `::C` - | - = note: required by `std::default::Default::default` diff --git a/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.rs b/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.rs new file mode 100644 index 0000000000000..51b6137c00755 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.rs @@ -0,0 +1,4 @@ +#[derive(frame_support::DefaultNoBound)] +enum Empty {} + +fn main() {} diff --git a/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.stderr b/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.stderr new file mode 100644 index 0000000000000..9c93b515adce5 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.stderr @@ -0,0 +1,5 @@ +error: cannot derive Default for an empty enum + --> tests/derive_no_bound_ui/default_empty_enum.rs:2:6 + | +2 | enum Empty {} + | ^^^^^ diff --git a/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.rs b/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.rs new file mode 100644 index 0000000000000..185df01fe2b84 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.rs @@ -0,0 +1,11 @@ +trait Config { + type C; +} + +#[derive(frame_support::DefaultNoBound)] +enum Foo { + Bar(T::C), + Baz, +} + +fn main() {} diff --git a/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.stderr b/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.stderr new file mode 100644 index 0000000000000..12e0023671587 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.stderr @@ -0,0 +1,5 @@ +error: no default declared, make a variant default by placing `#[default]` above it + --> tests/derive_no_bound_ui/default_no_attribute.rs:6:6 + | +6 | enum Foo { + | ^^^ diff --git a/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.rs b/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.rs new file mode 100644 index 0000000000000..c3d175da6c056 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.rs @@ -0,0 +1,13 @@ +trait Config { + type C; +} + +#[derive(frame_support::DefaultNoBound)] +enum Foo { + #[default] + Bar(T::C), + #[default] + Baz, +} + +fn main() {} diff --git a/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.stderr b/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.stderr new file mode 100644 index 0000000000000..5430ef142c5c8 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.stderr @@ -0,0 +1,21 @@ +error: multiple declared defaults + --> tests/derive_no_bound_ui/default_too_many_attributes.rs:5:10 + | +5 | #[derive(frame_support::DefaultNoBound)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the derive macro `frame_support::DefaultNoBound` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: first default + --> tests/derive_no_bound_ui/default_too_many_attributes.rs:7:2 + | +7 | / #[default] +8 | | Bar(T::C), + | |_____________^ + +error: additional default + --> tests/derive_no_bound_ui/default_too_many_attributes.rs:9:2 + | +9 | / #[default] +10 | | Baz, + | |_______^ diff --git a/frame/support/test/tests/derive_no_bound_ui/default_union.rs b/frame/support/test/tests/derive_no_bound_ui/default_union.rs new file mode 100644 index 0000000000000..5822cda1aa64d --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_union.rs @@ -0,0 +1,7 @@ +#[derive(frame_support::DefaultNoBound)] +union Foo { + field1: u32, + field2: (), +} + +fn main() {} diff --git a/frame/support/test/tests/derive_no_bound_ui/default_union.stderr b/frame/support/test/tests/derive_no_bound_ui/default_union.stderr new file mode 100644 index 0000000000000..1e01e1baaf8ac --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_union.stderr @@ -0,0 +1,5 @@ +error: Union type not supported by `derive(DefaultNoBound)` + --> tests/derive_no_bound_ui/default_union.rs:2:1 + | +2 | union Foo { + | ^^^^^ diff --git a/frame/support/test/tests/derive_no_bound_ui/eq.stderr b/frame/support/test/tests/derive_no_bound_ui/eq.stderr index fce13d6f17f06..97ac29f96902d 100644 --- a/frame/support/test/tests/derive_no_bound_ui/eq.stderr +++ b/frame/support/test/tests/derive_no_bound_ui/eq.stderr @@ -1,12 +1,12 @@ error[E0277]: can't compare `Foo` with `Foo` - --> $DIR/eq.rs:6:8 + --> tests/derive_no_bound_ui/eq.rs:6:8 | 6 | struct Foo { | ^^^ no implementation for `Foo == Foo` | - ::: $RUST/core/src/cmp.rs + = help: the trait `PartialEq` is not implemented for `Foo` +note: required by a bound in `std::cmp::Eq` + --> $RUST/core/src/cmp.rs | | pub trait Eq: PartialEq { - | --------------- required by this bound in `std::cmp::Eq` - | - = help: the trait `PartialEq` is not implemented for `Foo` + | ^^^^^^^^^^^^^^^ required by this bound in `std::cmp::Eq` diff --git a/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr b/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr index 64f844e547be0..1c230db376a49 100644 --- a/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr +++ b/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr @@ -1,7 +1,5 @@ error[E0369]: binary operation `==` cannot be applied to type `::C` - --> $DIR/partial_eq.rs:7:2 + --> tests/derive_no_bound_ui/partial_eq.rs:7:2 | 7 | c: T::C, | ^ - | - = note: the trait `std::cmp::PartialEq` is not implemented for `::C` diff --git a/frame/support/test/tests/final_keys.rs b/frame/support/test/tests/final_keys.rs index e89f961d893f5..a7ad88c801001 100644 --- a/frame/support/test/tests/final_keys.rs +++ b/frame/support/test/tests/final_keys.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ mod no_instance { pub trait Config: frame_support_test::Config {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} } frame_support::decl_storage! { @@ -53,7 +53,7 @@ mod instance { frame_support::decl_module! { pub struct Module, I: Instance = DefaultInstance> - for enum Call where origin: T::Origin, system=frame_support_test {} + for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} } frame_support::decl_storage! { diff --git a/frame/support/test/tests/genesisconfig.rs b/frame/support/test/tests/genesisconfig.rs index d488e8bfbfaff..f0094b3188ae1 100644 --- a/frame/support/test/tests/genesisconfig.rs +++ b/frame/support/test/tests/genesisconfig.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ pub trait Config: frame_support_test::Config {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} } frame_support::decl_storage! { @@ -31,7 +31,7 @@ struct Test; impl frame_support_test::Config for Test { type BlockNumber = u32; - type Origin = (); + type RuntimeOrigin = (); type PalletInfo = frame_support_test::PanicPalletInfo; type DbWeight = (); } @@ -40,5 +40,5 @@ impl Config for Test {} #[test] fn init_genesis_config() { - GenesisConfig:: { t: Default::default() }; + GenesisConfig::::default(); } diff --git a/frame/support/test/tests/instance.rs b/frame/support/test/tests/instance.rs index 809edae14f80c..043959b67ee6e 100644 --- a/frame/support/test/tests/instance.rs +++ b/frame/support/test/tests/instance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,15 +17,14 @@ #![recursion_limit = "128"] -use codec::{Codec, Decode, Encode, EncodeLike}; +use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; use frame_support::{ inherent::{InherentData, InherentIdentifier, MakeFatalError, ProvideInherent}, metadata::{ PalletStorageMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher, }, - parameter_types, - traits::Get, + traits::{ConstU32, Get}, Parameter, StorageDoubleMap, StorageMap, StorageValue, }; use scale_info::TypeInfo; @@ -51,15 +50,15 @@ mod module1 { where ::BlockNumber: From, { - type Event: From> + Into<::Event>; - type Origin: From>; + type RuntimeEvent: From> + Into<::RuntimeEvent>; + type RuntimeOrigin: From>; type SomeParameter: Get; type GenericType: Default + Clone + Codec + EncodeLike + TypeInfo; } frame_support::decl_module! { pub struct Module, I: Instance> for enum Call where - origin: ::Origin, + origin: ::RuntimeOrigin, system = system, T::BlockNumber: From { @@ -109,7 +108,9 @@ mod module1 { } } - #[derive(PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, TypeInfo)] + #[derive( + PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen, + )] pub enum Origin, I> where T::BlockNumber: From, @@ -153,15 +154,15 @@ mod module2 { pub trait Config: system::Config { type Amount: Parameter + Default; - type Event: From> + Into<::Event>; - type Origin: From>; + type RuntimeEvent: From> + Into<::RuntimeEvent>; + type RuntimeOrigin: From>; } impl, I: Instance> Currency for Module {} frame_support::decl_module! { pub struct Module, I: Instance=DefaultInstance> for enum Call where - origin: ::Origin, + origin: ::RuntimeOrigin, system = system { fn deposit_event() = default; @@ -182,7 +183,9 @@ mod module2 { } } - #[derive(PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, TypeInfo)] + #[derive( + PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen, + )] pub enum Origin, I = DefaultInstance> { Members(u32), _Phantom(std::marker::PhantomData<(T, I)>), @@ -225,45 +228,41 @@ mod module3 { } frame_support::decl_module! { - pub struct Module for enum Call where origin: ::Origin, system=system {} + pub struct Module for enum Call where origin: ::RuntimeOrigin, system=system {} } } -parameter_types! { - pub const SomeValue: u32 = 100; -} - impl module1::Config for Runtime { - type Event = Event; - type Origin = Origin; - type SomeParameter = SomeValue; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type SomeParameter = ConstU32<100>; type GenericType = u32; } impl module1::Config for Runtime { - type Event = Event; - type Origin = Origin; - type SomeParameter = SomeValue; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type SomeParameter = ConstU32<100>; type GenericType = u32; } impl module2::Config for Runtime { type Amount = u16; - type Event = Event; - type Origin = Origin; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; } impl module2::Config for Runtime { type Amount = u32; - type Event = Event; - type Origin = Origin; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; } impl module2::Config for Runtime { type Amount = u32; - type Event = Event; - type Origin = Origin; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; } impl module2::Config for Runtime { type Amount = u64; - type Event = Event; - type Origin = Origin; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; } impl module3::Config for Runtime { type Currency = Module2_2; @@ -278,12 +277,12 @@ pub type Index = u64; impl system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type Hash = H256; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type BlockNumber = BlockNumber; type AccountId = AccountId; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type PalletInfo = PalletInfo; - type Call = Call; + type RuntimeCall = RuntimeCall; type DbWeight = (); } @@ -316,7 +315,7 @@ frame_support::construct_runtime!( pub type Header = generic::Header; pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; fn new_test_ext() -> sp_io::TestExternalities { GenesisConfig { diff --git a/frame/support/test/tests/issue2219.rs b/frame/support/test/tests/issue2219.rs index 68ad2a50a21bc..5d24d54165c1f 100644 --- a/frame/support/test/tests/issue2219.rs +++ b/frame/support/test/tests/issue2219.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -86,7 +86,7 @@ mod module { pub trait Config: system::Config + TypeInfo {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=system {} + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=system {} } #[derive(Encode, Decode, Copy, Clone, Serialize, Deserialize)] @@ -155,17 +155,17 @@ pub type BlockNumber = u64; pub type Index = u64; pub type Header = generic::Header; pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; impl system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type Hash = H256; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type BlockNumber = BlockNumber; type AccountId = AccountId; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type PalletInfo = PalletInfo; - type Call = Call; + type RuntimeCall = RuntimeCall; type DbWeight = (); } @@ -184,7 +184,9 @@ frame_support::construct_runtime!( #[test] fn create_genesis_config() { - GenesisConfig { + let config = GenesisConfig { module: module::GenesisConfig { request_life_time: 0, enable_storage_role: true }, }; + assert_eq!(config.module.request_life_time, 0); + assert!(config.module.enable_storage_role); } diff --git a/frame/support/test/tests/origin.rs b/frame/support/test/tests/origin.rs new file mode 100644 index 0000000000000..f794cd8be1ce0 --- /dev/null +++ b/frame/support/test/tests/origin.rs @@ -0,0 +1,220 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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. + +//! RuntimeOrigin tests for construct_runtime macro + +#![recursion_limit = "128"] + +use codec::MaxEncodedLen; +use frame_support::traits::{Contains, OriginTrait}; +use scale_info::TypeInfo; +use sp_core::{sr25519, H256}; +use sp_runtime::{generic, traits::BlakeTwo256}; + +mod system; + +mod nested { + use super::*; + + pub mod module { + + use super::*; + + pub trait Config: system::Config {} + + frame_support::decl_module! { + pub struct Module for enum Call + where origin: ::RuntimeOrigin, system=system + { + #[weight = 0] + pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { + Err(Error::::Something.into()) + } + } + } + + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] + pub struct Origin; + + frame_support::decl_event! { + pub enum Event { + A, + } + } + + frame_support::decl_error! { + pub enum Error for Module { + Something + } + } + + frame_support::decl_storage! { + trait Store for Module as Module {} + add_extra_genesis { + build(|_config| {}) + } + } + } +} + +pub mod module { + use super::*; + + pub trait Config: system::Config {} + + frame_support::decl_module! { + pub struct Module for enum Call + where origin: ::RuntimeOrigin, system=system + { + #[weight = 0] + pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { + Err(Error::::Something.into()) + } + #[weight = 0] + pub fn aux_1(_origin, #[compact] _data: u32) -> frame_support::dispatch::DispatchResult { + unreachable!() + } + #[weight = 0] + pub fn aux_2(_origin, _data: i32, #[compact] _data2: u32) -> frame_support::dispatch::DispatchResult { + unreachable!() + } + #[weight = 0] + fn aux_3(_origin, _data: i32, _data2: String) -> frame_support::dispatch::DispatchResult { + unreachable!() + } + #[weight = 3] + fn aux_4(_origin) -> frame_support::dispatch::DispatchResult { unreachable!() } + #[weight = (5, frame_support::dispatch::DispatchClass::Operational)] + fn operational(_origin) { unreachable!() } + } + } + + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] + pub struct Origin(pub core::marker::PhantomData); + + frame_support::decl_event! { + pub enum Event { + A, + } + } + + frame_support::decl_error! { + pub enum Error for Module { + Something + } + } + + frame_support::decl_storage! { + trait Store for Module as Module {} + add_extra_genesis { + build(|_config| {}) + } + } +} + +impl nested::module::Config for RuntimeOriginTest {} +impl module::Config for RuntimeOriginTest {} + +pub struct BaseCallFilter; +impl Contains for BaseCallFilter { + fn contains(c: &RuntimeCall) -> bool { + match c { + RuntimeCall::NestedModule(_) => true, + _ => false, + } + } +} + +impl system::Config for RuntimeOriginTest { + type BaseCallFilter = BaseCallFilter; + type Hash = H256; + type RuntimeOrigin = RuntimeOrigin; + type BlockNumber = BlockNumber; + type AccountId = u32; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type RuntimeCall = RuntimeCall; + type DbWeight = (); +} + +frame_support::construct_runtime!( + pub enum RuntimeOriginTest where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet, Event, Origin}, + NestedModule: nested::module::{Pallet, Origin, Call}, + Module: module::{Pallet, Origin, Call}, + } +); + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u64; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +#[test] +fn origin_default_filter() { + let accepted_call = nested::module::Call::fail {}.into(); + let rejected_call = module::Call::fail {}.into(); + + assert_eq!(RuntimeOrigin::root().filter_call(&accepted_call), true); + assert_eq!(RuntimeOrigin::root().filter_call(&rejected_call), true); + assert_eq!(RuntimeOrigin::none().filter_call(&accepted_call), true); + assert_eq!(RuntimeOrigin::none().filter_call(&rejected_call), false); + assert_eq!(RuntimeOrigin::signed(0).filter_call(&accepted_call), true); + assert_eq!(RuntimeOrigin::signed(0).filter_call(&rejected_call), false); + assert_eq!(RuntimeOrigin::from(Some(0)).filter_call(&accepted_call), true); + assert_eq!(RuntimeOrigin::from(Some(0)).filter_call(&rejected_call), false); + assert_eq!(RuntimeOrigin::from(None).filter_call(&accepted_call), true); + assert_eq!(RuntimeOrigin::from(None).filter_call(&rejected_call), false); + assert_eq!(RuntimeOrigin::from(nested::module::Origin).filter_call(&accepted_call), true); + assert_eq!(RuntimeOrigin::from(nested::module::Origin).filter_call(&rejected_call), false); + + let mut origin = RuntimeOrigin::from(Some(0)); + origin.add_filter(|c| matches!(c, RuntimeCall::Module(_))); + assert_eq!(origin.filter_call(&accepted_call), false); + assert_eq!(origin.filter_call(&rejected_call), false); + + // Now test for root origin and filters: + let mut origin = RuntimeOrigin::from(Some(0)); + origin.set_caller_from(RuntimeOrigin::root()); + assert!(matches!(origin.caller, OriginCaller::system(system::RawOrigin::Root))); + + // Root origin bypass all filter. + assert_eq!(origin.filter_call(&accepted_call), true); + assert_eq!(origin.filter_call(&rejected_call), true); + + origin.set_caller_from(RuntimeOrigin::from(Some(0))); + + // Back to another signed origin, the filtered are now effective again + assert_eq!(origin.filter_call(&accepted_call), true); + assert_eq!(origin.filter_call(&rejected_call), false); + + origin.set_caller_from(RuntimeOrigin::root()); + origin.reset_filter(); + + // Root origin bypass all filter, even when they are reset. + assert_eq!(origin.filter_call(&accepted_call), true); + assert_eq!(origin.filter_call(&rejected_call), true); +} diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index 6a9a18ea48d4b..c0376d5aa450f 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,20 +16,23 @@ // limitations under the License. use frame_support::{ - dispatch::{Parameter, UnfilteredDispatchable}, + dispatch::{ + DispatchClass, DispatchInfo, GetDispatchInfo, Parameter, Pays, UnfilteredDispatchable, + }, + pallet_prelude::ValueQuery, storage::unhashed, traits::{ - GetCallName, GetStorageVersion, OnFinalize, OnGenesis, OnInitialize, OnRuntimeUpgrade, - PalletInfoAccess, StorageVersion, + ConstU32, GetCallName, GetStorageVersion, OnFinalize, OnGenesis, OnInitialize, + OnRuntimeUpgrade, PalletError, PalletInfoAccess, StorageVersion, }, - weights::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays, RuntimeDbWeight}, + weights::{RuntimeDbWeight, Weight}, }; use scale_info::{meta_type, TypeInfo}; use sp_io::{ hashing::{blake2_128, twox_128, twox_64}, TestExternalities, }; -use sp_runtime::DispatchError; +use sp_runtime::{DispatchError, ModuleError}; pub struct SomeType1; impl From for u64 { @@ -96,13 +99,9 @@ impl SomeAssociation2 for u64 { #[frame_support::pallet] pub mod pallet { - use super::{ - SomeAssociation1, SomeAssociation2, SomeType1, SomeType2, SomeType3, SomeType4, SomeType5, - SomeType6, SomeType7, StorageVersion, - }; + use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use scale_info::TypeInfo; type BalanceOf = ::Balance; @@ -128,7 +127,7 @@ pub mod pallet { type Balance: Parameter + Default + TypeInfo; - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; } #[pallet::extra_constants] @@ -146,11 +145,16 @@ pub mod pallet { fn some_extra_extra() -> T::AccountId { SomeType1.into() } + + /// Some doc + #[pallet::constant_name(SomeExtraRename)] + fn some_extra_rename() -> T::AccountId { + SomeType1.into() + } } #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] - #[pallet::generate_storage_info] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -160,25 +164,25 @@ pub mod pallet { T::AccountId: From + From + SomeAssociation1, { fn on_initialize(_: BlockNumberFor) -> Weight { - T::AccountId::from(SomeType1); // Test for where clause - T::AccountId::from(SomeType2); // Test for where clause + let _ = T::AccountId::from(SomeType1); // Test for where clause + let _ = T::AccountId::from(SomeType2); // Test for where clause Self::deposit_event(Event::Something(10)); - 10 + Weight::from_ref_time(10) } fn on_finalize(_: BlockNumberFor) { - T::AccountId::from(SomeType1); // Test for where clause - T::AccountId::from(SomeType2); // Test for where clause + let _ = T::AccountId::from(SomeType1); // Test for where clause + let _ = T::AccountId::from(SomeType2); // Test for where clause Self::deposit_event(Event::Something(20)); } fn on_runtime_upgrade() -> Weight { - T::AccountId::from(SomeType1); // Test for where clause - T::AccountId::from(SomeType2); // Test for where clause + let _ = T::AccountId::from(SomeType1); // Test for where clause + let _ = T::AccountId::from(SomeType2); // Test for where clause Self::deposit_event(Event::Something(30)); - 30 + Weight::from_ref_time(30) } fn integrity_test() { - T::AccountId::from(SomeType1); // Test for where clause - T::AccountId::from(SomeType2); // Test for where clause + let _ = T::AccountId::from(SomeType1); // Test for where clause + let _ = T::AccountId::from(SomeType2); // Test for where clause } } @@ -188,23 +192,24 @@ pub mod pallet { T::AccountId: From + From + SomeAssociation1, { /// Doc comment put in metadata - #[pallet::weight(Weight::from(*_foo))] + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_ref_time(*_foo as u64))] pub fn foo( origin: OriginFor, #[pallet::compact] _foo: u32, _bar: u32, ) -> DispatchResultWithPostInfo { - T::AccountId::from(SomeType1); // Test for where clause - T::AccountId::from(SomeType3); // Test for where clause + let _ = T::AccountId::from(SomeType1); // Test for where clause + let _ = T::AccountId::from(SomeType3); // Test for where clause let _ = origin; Self::deposit_event(Event::Something(3)); Ok(().into()) } /// Doc comment put in metadata + #[pallet::call_index(1)] #[pallet::weight(1)] - #[frame_support::transactional] - pub fn foo_transactional( + pub fn foo_storage_layer( _origin: OriginFor, #[pallet::compact] foo: u32, ) -> DispatchResultWithPostInfo { @@ -217,6 +222,7 @@ pub mod pallet { } // Test for DispatchResult return type + #[pallet::call_index(2)] #[pallet::weight(1)] pub fn foo_no_post_info(_origin: OriginFor) -> DispatchResult { Ok(()) @@ -224,9 +230,15 @@ pub mod pallet { } #[pallet::error] + #[derive(PartialEq, Eq)] pub enum Error { /// doc comment put into metadata InsufficientProposersBalance, + NonExistentStorageValue, + Code(u8), + #[codec(skip)] + Skipped(u128), + CompactU8(#[codec(compact)] u8), } #[pallet::event] @@ -256,12 +268,13 @@ pub mod pallet { #[pallet::storage_prefix = "Value2"] pub type RenamedValue = StorageValue; + /// Test some doc #[pallet::type_value] pub fn MyDefault() -> u16 where T::AccountId: From + From + SomeAssociation1, { - T::AccountId::from(SomeType7); // Test where clause works + let _ = T::AccountId::from(SomeType7); // Test where clause works 4u16 } @@ -275,6 +288,10 @@ pub mod pallet { pub type Map2 = StorageMap>; + #[pallet::storage] + pub type Map3 = + StorageMap<_, Blake2_128Concat, u32, u64, ResultQuery::NonExistentStorageValue>>; + #[pallet::storage] pub type DoubleMap = StorageDoubleMap<_, Blake2_128Concat, u8, Twox64Concat, u16, u32>; @@ -288,6 +305,17 @@ pub mod pallet { MaxValues = ConstU32<5>, >; + #[pallet::storage] + pub type DoubleMap3 = StorageDoubleMap< + _, + Blake2_128Concat, + u32, + Twox64Concat, + u64, + u128, + ResultQuery::NonExistentStorageValue>, + >; + #[pallet::storage] #[pallet::getter(fn nmap)] pub type NMap = StorageNMap<_, storage::Key, u32>; @@ -300,24 +328,33 @@ pub mod pallet { MaxValues = ConstU32<11>, >; + #[pallet::storage] + #[pallet::getter(fn nmap3)] + pub type NMap3 = StorageNMap< + _, + (NMapKey, NMapKey), + u128, + ResultQuery::NonExistentStorageValue>, + >; + #[pallet::storage] #[pallet::getter(fn conditional_value)] - #[cfg(feature = "conditional-storage")] + #[cfg(feature = "frame-feature-testing")] pub type ConditionalValue = StorageValue<_, u32>; - #[cfg(feature = "conditional-storage")] + #[cfg(feature = "frame-feature-testing")] #[pallet::storage] #[pallet::getter(fn conditional_map)] pub type ConditionalMap = StorageMap<_, Twox64Concat, u16, u32, OptionQuery, GetDefault, ConstU32<12>>; - #[cfg(feature = "conditional-storage")] + #[cfg(feature = "frame-feature-testing")] #[pallet::storage] #[pallet::getter(fn conditional_double_map)] pub type ConditionalDoubleMap = StorageDoubleMap<_, Blake2_128Concat, u8, Twox64Concat, u16, u32>; - #[cfg(feature = "conditional-storage")] + #[cfg(feature = "frame-feature-testing")] #[pallet::storage] #[pallet::getter(fn conditional_nmap)] pub type ConditionalNMap = @@ -329,6 +366,10 @@ pub mod pallet { pub type SomeCountedStorageMap = CountedStorageMap; + #[pallet::storage] + #[pallet::unbounded] + pub type Unbounded = StorageValue>; + #[pallet::genesis_config] #[derive(Default)] pub struct GenesisConfig { @@ -341,14 +382,21 @@ pub mod pallet { T::AccountId: From + SomeAssociation1 + From, { fn build(&self) { - T::AccountId::from(SomeType1); // Test for where clause - T::AccountId::from(SomeType4); // Test for where clause + let _ = T::AccountId::from(SomeType1); // Test for where clause + let _ = T::AccountId::from(SomeType4); // Test for where clause } } #[pallet::origin] #[derive( - EqNoBound, RuntimeDebugNoBound, CloneNoBound, PartialEqNoBound, Encode, Decode, TypeInfo, + EqNoBound, + RuntimeDebugNoBound, + CloneNoBound, + PartialEqNoBound, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, )] pub struct Origin(PhantomData); @@ -359,9 +407,9 @@ pub mod pallet { { type Call = Call; fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - T::AccountId::from(SomeType1); // Test for where clause - T::AccountId::from(SomeType5); // Test for where clause - if matches!(call, Call::foo_transactional { .. }) { + let _ = T::AccountId::from(SomeType1); // Test for where clause + let _ = T::AccountId::from(SomeType5); // Test for where clause + if matches!(call, Call::foo_storage_layer { .. }) { return Ok(ValidTransaction::default()) } Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) @@ -379,8 +427,8 @@ pub mod pallet { const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; fn create_inherent(_data: &InherentData) -> Option { - T::AccountId::from(SomeType1); // Test for where clause - T::AccountId::from(SomeType6); // Test for where clause + let _ = T::AccountId::from(SomeType1); // Test for where clause + let _ = T::AccountId::from(SomeType6); // Test for where clause Some(Call::foo_no_post_info {}) } @@ -422,7 +470,7 @@ pub mod pallet { } // Test that a pallet with non generic event and generic genesis_config is correctly handled -// and that a pallet without the attribute generate_storage_info is correctly handled. +// and that a pallet with the attribute without_storage_info is correctly handled. #[frame_support::pallet] pub mod pallet2 { use super::{SomeAssociation1, SomeType1}; @@ -434,17 +482,30 @@ pub mod pallet2 { where ::AccountId: From + SomeAssociation1, { - type Event: From + IsType<::Event>; + type RuntimeEvent: From + IsType<::RuntimeEvent>; } #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] + #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::hooks] - impl Hooks> for Pallet where - T::AccountId: From + SomeAssociation1 + impl Hooks> for Pallet + where + T::AccountId: From + SomeAssociation1, { + fn on_initialize(_: BlockNumberFor) -> Weight { + Self::deposit_event(Event::Something(11)); + Weight::zero() + } + fn on_finalize(_: BlockNumberFor) { + Self::deposit_event(Event::Something(21)); + } + fn on_runtime_upgrade() -> Weight { + Self::deposit_event(Event::Something(31)); + Weight::zero() + } } #[pallet::call] @@ -458,6 +519,7 @@ pub mod pallet2 { CountedStorageMap; #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] pub enum Event { /// Something Something(u32), @@ -493,32 +555,59 @@ pub mod pallet2 { #[frame_support::pallet] pub mod pallet3 { #[pallet::config] - pub trait Config: frame_system::Config {} + pub trait Config: + frame_system::Config::RuntimeOrigin> + { + type RuntimeOrigin; + } + + #[pallet::pallet] + pub struct Pallet(_); +} + +#[frame_support::pallet] +pub mod pallet4 { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet {} +} + +/// Test that the supertrait check works when we pass some parameter to the `frame_system::Config`. +#[frame_support::pallet] +pub mod pallet5 { + #[pallet::config] + pub trait Config: + frame_system::Config::RuntimeOrigin> + { + type RuntimeOrigin; + } #[pallet::pallet] pub struct Pallet(_); } frame_support::parameter_types!( - pub const MyGetParam: u32 = 10; - pub const MyGetParam2: u32 = 11; pub const MyGetParam3: u32 = 12; - pub const BlockHashCount: u32 = 250; ); impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u32; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU32<250>; type BlockWeights = (); type BlockLength = (); type DbWeight = (); @@ -530,22 +619,35 @@ impl frame_system::Config for Runtime { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet::Config for Runtime { - type Event = Event; - type MyGetParam = MyGetParam; - type MyGetParam2 = MyGetParam2; + type RuntimeEvent = RuntimeEvent; + type MyGetParam = ConstU32<10>; + type MyGetParam2 = ConstU32<11>; type MyGetParam3 = MyGetParam3; type Balance = u64; } impl pallet2::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; +} + +impl pallet4::Config for Runtime {} + +#[cfg(feature = "frame-feature-testing")] +impl pallet3::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; +} + +#[cfg(feature = "frame-feature-testing-2")] +impl pallet5::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; } pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( pub enum Runtime where @@ -553,24 +655,38 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: frame_system::{Pallet, Call, Event}, - Example: pallet::{Pallet, Call, Event, Config, Storage, Inherent, Origin, ValidateUnsigned}, - Example2: pallet2::{Pallet, Call, Event, Config, Storage}, + // Exclude part `Storage` in order not to check its metadata in tests. + System: frame_system exclude_parts { Pallet, Storage }, + Example: pallet, + Example2: pallet2 exclude_parts { Call }, + #[cfg(feature = "frame-feature-testing")] + Example3: pallet3, + Example4: pallet4 use_parts { Call }, + + #[cfg(feature = "frame-feature-testing-2")] + Example5: pallet5, } ); +// Test that the part `RuntimeCall` is excluded from Example2 and included in Example4. +fn _ensure_call_is_correctly_excluded_and_included(call: RuntimeCall) { + match call { + RuntimeCall::System(_) | RuntimeCall::Example(_) | RuntimeCall::Example4(_) => (), + } +} + #[test] fn transactional_works() { TestExternalities::default().execute_with(|| { frame_system::Pallet::::set_block_number(1); - pallet::Call::::foo_transactional { foo: 0 } + pallet::Call::::foo_storage_layer { foo: 0 } .dispatch_bypass_filter(None.into()) .err() .unwrap(); assert!(frame_system::Pallet::::events().is_empty()); - pallet::Call::::foo_transactional { foo: 1 } + pallet::Call::::foo_storage_layer { foo: 1 } .dispatch_bypass_filter(None.into()) .unwrap(); assert_eq!( @@ -578,7 +694,7 @@ fn transactional_works() { .iter() .map(|e| &e.event) .collect::>(), - vec![&Event::Example(pallet::Event::Something(0))], + vec![&RuntimeEvent::Example(pallet::Event::Something(0))], ); }) } @@ -588,12 +704,16 @@ fn call_expand() { let call_foo = pallet::Call::::foo { foo: 3, bar: 0 }; assert_eq!( call_foo.get_dispatch_info(), - DispatchInfo { weight: 3, class: DispatchClass::Normal, pays_fee: Pays::Yes } + DispatchInfo { + weight: frame_support::weights::Weight::from_ref_time(3), + class: DispatchClass::Normal, + pays_fee: Pays::Yes + } ); assert_eq!(call_foo.get_call_name(), "foo"); assert_eq!( pallet::Call::::get_call_names(), - &["foo", "foo_transactional", "foo_no_post_info"], + &["foo", "foo_storage_layer", "foo_no_post_info"], ); } @@ -609,8 +729,13 @@ fn error_expand() { ); assert_eq!( DispatchError::from(pallet::Error::::InsufficientProposersBalance), - DispatchError::Module { index: 1, error: 0, message: Some("InsufficientProposersBalance") }, + DispatchError::Module(ModuleError { + index: 1, + error: [0, 0, 0, 0], + message: Some("InsufficientProposersBalance") + }), ); + assert_eq!( as PalletError>::MAX_ENCODED_SIZE, 3); } #[test] @@ -634,7 +759,7 @@ fn inherent_expand() { let inherents = InherentData::new().create_extrinsics(); let expected = vec![UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_no_post_info {}), + function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: None, }]; assert_eq!(expected, inherents); @@ -649,11 +774,11 @@ fn inherent_expand() { ), vec![ UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_no_post_info {}), + function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: None, }, UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo { foo: 1, bar: 0 }), + function: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 0 }), signature: None, }, ], @@ -671,11 +796,11 @@ fn inherent_expand() { ), vec![ UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_no_post_info {}), + function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: None, }, UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo { foo: 0, bar: 0 }), + function: RuntimeCall::Example(pallet::Call::foo { foo: 0, bar: 0 }), signature: None, }, ], @@ -692,7 +817,7 @@ fn inherent_expand() { Digest::default(), ), vec![UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_transactional { foo: 0 }), + function: RuntimeCall::Example(pallet::Call::foo_storage_layer { foo: 0 }), signature: None, }], ); @@ -710,7 +835,7 @@ fn inherent_expand() { Digest::default(), ), vec![UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_no_post_info {}), + function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: Some((1, (), ())), }], ); @@ -729,11 +854,11 @@ fn inherent_expand() { ), vec![ UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo { foo: 1, bar: 1 }), + function: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 1 }), signature: None, }, UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_transactional { foo: 0 }), + function: RuntimeCall::Example(pallet::Call::foo_storage_layer { foo: 0 }), signature: None, }, ], @@ -751,15 +876,15 @@ fn inherent_expand() { ), vec![ UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo { foo: 1, bar: 1 }), + function: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 1 }), signature: None, }, UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_transactional { foo: 0 }), + function: RuntimeCall::Example(pallet::Call::foo_storage_layer { foo: 0 }), signature: None, }, UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_no_post_info {}), + function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: None, }, ], @@ -777,15 +902,15 @@ fn inherent_expand() { ), vec![ UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo { foo: 1, bar: 1 }), + function: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 1 }), signature: None, }, UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo { foo: 1, bar: 0 }), + function: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 0 }), signature: Some((1, (), ())), }, UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_no_post_info {}), + function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: None, }, ], @@ -805,7 +930,7 @@ fn validate_unsigned_expand() { let validity = pallet::Pallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(); assert_eq!(validity, TransactionValidityError::Invalid(InvalidTransaction::Call)); - let call = pallet::Call::::foo_transactional { foo: 0 }; + let call = pallet::Call::::foo_storage_layer { foo: 0 }; let validity = pallet::Pallet::validate_unsigned(TransactionSource::External, &call).unwrap(); assert_eq!(validity, ValidTransaction::default()); @@ -829,14 +954,14 @@ fn pallet_expand_deposit_event() { .unwrap(); assert_eq!( frame_system::Pallet::::events()[0].event, - Event::Example(pallet::Event::Something(3)), + RuntimeEvent::Example(pallet::Event::Something(3)), ); }) } #[test] fn pallet_new_call_variant() { - Call::Example(pallet::Call::new_call_variant_foo(3, 4)); + pallet::Call::::new_call_variant_foo(3, 4); } #[test] @@ -876,6 +1001,16 @@ fn storage_expand() { assert_eq!(unhashed::get::(&k), Some(2u32)); assert_eq!(&k[..32], &>::final_prefix()); + pallet::Map3::::insert(1, 2); + let mut k = [twox_128(b"Example"), twox_128(b"Map3")].concat(); + k.extend(1u32.using_encoded(blake2_128_concat)); + assert_eq!(unhashed::get::(&k), Some(2u64)); + assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!( + pallet::Map3::::get(2), + Err(pallet::Error::::NonExistentStorageValue), + ); + pallet::DoubleMap::::insert(&1, &2, &3); let mut k = [twox_128(b"Example"), twox_128(b"DoubleMap")].concat(); k.extend(1u8.using_encoded(blake2_128_concat)); @@ -890,6 +1025,17 @@ fn storage_expand() { assert_eq!(unhashed::get::(&k), Some(3u64)); assert_eq!(&k[..32], &>::final_prefix()); + pallet::DoubleMap3::::insert(&1, &2, &3); + let mut k = [twox_128(b"Example"), twox_128(b"DoubleMap3")].concat(); + k.extend(1u32.using_encoded(blake2_128_concat)); + k.extend(2u64.using_encoded(twox_64_concat)); + assert_eq!(unhashed::get::(&k), Some(3u128)); + assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!( + pallet::DoubleMap3::::get(2, 3), + Err(pallet::Error::::NonExistentStorageValue), + ); + pallet::NMap::::insert((&1,), &3); let mut k = [twox_128(b"Example"), twox_128(b"NMap")].concat(); k.extend(1u8.using_encoded(blake2_128_concat)); @@ -903,7 +1049,18 @@ fn storage_expand() { assert_eq!(unhashed::get::(&k), Some(3u64)); assert_eq!(&k[..32], &>::final_prefix()); - #[cfg(feature = "conditional-storage")] + pallet::NMap3::::insert((&1, &2), &3); + let mut k = [twox_128(b"Example"), twox_128(b"NMap3")].concat(); + k.extend(1u8.using_encoded(blake2_128_concat)); + k.extend(2u16.using_encoded(twox_64_concat)); + assert_eq!(unhashed::get::(&k), Some(3u128)); + assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!( + pallet::NMap3::::get((2, 3)), + Err(pallet::Error::::NonExistentStorageValue), + ); + + #[cfg(feature = "frame-feature-testing")] { pallet::ConditionalValue::::put(1); pallet::ConditionalMap::::insert(1, 2); @@ -917,6 +1074,10 @@ fn storage_expand() { assert_eq!(unhashed::get::(&k), Some(2u32)); let k = [twox_128(b"Example"), twox_128(b"CounterForRenamedCountedMap")].concat(); assert_eq!(unhashed::get::(&k), Some(1u32)); + + pallet::Unbounded::::put(vec![1, 2]); + let k = [twox_128(b"Example"), twox_128(b"Unbounded")].concat(); + assert_eq!(unhashed::get::>(&k), Some(vec![1, 2])); }) } @@ -925,22 +1086,80 @@ fn pallet_hooks_expand() { TestExternalities::default().execute_with(|| { frame_system::Pallet::::set_block_number(1); - assert_eq!(AllPallets::on_initialize(1), 10); - AllPallets::on_finalize(1); + assert_eq!(AllPalletsWithoutSystem::on_initialize(1), Weight::from_ref_time(10)); + AllPalletsWithoutSystem::on_finalize(1); - assert_eq!(AllPallets::on_runtime_upgrade(), 30); + assert_eq!(AllPalletsWithoutSystem::on_runtime_upgrade(), Weight::from_ref_time(30)); assert_eq!( frame_system::Pallet::::events()[0].event, - Event::Example(pallet::Event::Something(10)), + RuntimeEvent::Example(pallet::Event::Something(10)), ); assert_eq!( frame_system::Pallet::::events()[1].event, - Event::Example(pallet::Event::Something(20)), + RuntimeEvent::Example2(pallet2::Event::Something(11)), ); assert_eq!( frame_system::Pallet::::events()[2].event, - Event::Example(pallet::Event::Something(30)), + RuntimeEvent::Example(pallet::Event::Something(20)), + ); + assert_eq!( + frame_system::Pallet::::events()[3].event, + RuntimeEvent::Example2(pallet2::Event::Something(21)), + ); + assert_eq!( + frame_system::Pallet::::events()[4].event, + RuntimeEvent::Example(pallet::Event::Something(30)), + ); + assert_eq!( + frame_system::Pallet::::events()[5].event, + RuntimeEvent::Example2(pallet2::Event::Something(31)), + ); + }) +} + +#[test] +fn all_pallets_type_reversed_order_is_correct() { + TestExternalities::default().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + + #[allow(deprecated)] + { + assert_eq!( + AllPalletsWithoutSystemReversed::on_initialize(1), + Weight::from_ref_time(10) + ); + AllPalletsWithoutSystemReversed::on_finalize(1); + + assert_eq!( + AllPalletsWithoutSystemReversed::on_runtime_upgrade(), + Weight::from_ref_time(30) + ); + } + + assert_eq!( + frame_system::Pallet::::events()[0].event, + RuntimeEvent::Example2(pallet2::Event::Something(11)), + ); + assert_eq!( + frame_system::Pallet::::events()[1].event, + RuntimeEvent::Example(pallet::Event::Something(10)), + ); + assert_eq!( + frame_system::Pallet::::events()[2].event, + RuntimeEvent::Example2(pallet2::Event::Something(21)), + ); + assert_eq!( + frame_system::Pallet::::events()[3].event, + RuntimeEvent::Example(pallet::Event::Something(20)), + ); + assert_eq!( + frame_system::Pallet::::events()[4].event, + RuntimeEvent::Example2(pallet2::Event::Something(31)), + ); + assert_eq!( + frame_system::Pallet::::events()[5].event, + RuntimeEvent::Example(pallet::Event::Something(30)), ); }) } @@ -981,8 +1200,16 @@ fn migrate_from_pallet_version_to_storage_version() { AllPalletsWithSystem, >(&db_weight); - // 3 pallets, 2 writes and every write costs 5 weight. - assert_eq!(3 * 2 * 5, weight); + let mut pallet_num = 4; + if cfg!(feature = "frame-feature-testing") { + pallet_num += 1; + }; + if cfg!(feature = "frame-feature-testing-2") { + pallet_num += 1; + }; + + // `pallet_num` pallets, 2 writes and every write costs 5 weight. + assert_eq!(Weight::from_ref_time(pallet_num * 2 * 5), weight); // All pallet versions should be removed assert!(sp_io::storage::get(&pallet_version_key(Example::name())).is_none()); @@ -999,53 +1226,15 @@ fn migrate_from_pallet_version_to_storage_version() { fn metadata() { use frame_support::metadata::*; + fn maybe_docs(doc: Vec<&'static str>) -> Vec<&'static str> { + if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + doc + } + } + let pallets = vec![ - PalletMetadata { - index: 0, - name: "System", - storage: None, - calls: Some(meta_type::>().into()), - event: Some(meta_type::>().into()), - constants: vec![ - PalletConstantMetadata { - name: "BlockWeights", - ty: meta_type::(), - value: vec![], - docs: vec![], - }, - PalletConstantMetadata { - name: "BlockLength", - ty: meta_type::(), - value: vec![], - docs: vec![], - }, - PalletConstantMetadata { - name: "BlockHashCount", - ty: meta_type::(), - value: vec![], - docs: vec![], - }, - PalletConstantMetadata { - name: "DbWeight", - ty: meta_type::(), - value: vec![], - docs: vec![], - }, - PalletConstantMetadata { - name: "Version", - ty: meta_type::(), - value: vec![], - docs: vec![], - }, - PalletConstantMetadata { - name: "SS58Prefix", - ty: meta_type::(), - value: vec![], - docs: vec![], - }, - ], - error: Some(meta_type::>().into()), - }, PalletMetadata { index: 1, name: "Example", @@ -1095,6 +1284,17 @@ fn metadata() { default: vec![0], docs: vec![], }, + StorageEntryMetadata { + name: "Map3", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Map { + key: meta_type::(), + value: meta_type::(), + hashers: vec![StorageHasher::Blake2_128Concat], + }, + default: vec![1, 1], + docs: vec![], + }, StorageEntryMetadata { name: "DoubleMap", modifier: StorageEntryModifier::Optional, @@ -1123,6 +1323,20 @@ fn metadata() { default: vec![0], docs: vec![], }, + StorageEntryMetadata { + name: "DoubleMap3", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Map { + value: meta_type::(), + key: meta_type::<(u32, u64)>(), + hashers: vec![ + StorageHasher::Blake2_128Concat, + StorageHasher::Twox64Concat, + ], + }, + default: vec![1, 1], + docs: vec![], + }, StorageEntryMetadata { name: "NMap", modifier: StorageEntryModifier::Optional, @@ -1148,7 +1362,21 @@ fn metadata() { default: vec![0], docs: vec![], }, - #[cfg(feature = "conditional-storage")] + StorageEntryMetadata { + name: "NMap3", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Map { + key: meta_type::<(u8, u16)>(), + hashers: vec![ + StorageHasher::Blake2_128Concat, + StorageHasher::Twox64Concat, + ], + value: meta_type::(), + }, + default: vec![1, 1], + docs: vec![], + }, + #[cfg(feature = "frame-feature-testing")] StorageEntryMetadata { name: "ConditionalValue", modifier: StorageEntryModifier::Optional, @@ -1156,7 +1384,7 @@ fn metadata() { default: vec![0], docs: vec![], }, - #[cfg(feature = "conditional-storage")] + #[cfg(feature = "frame-feature-testing")] StorageEntryMetadata { name: "ConditionalMap", modifier: StorageEntryModifier::Optional, @@ -1168,7 +1396,7 @@ fn metadata() { default: vec![0], docs: vec![], }, - #[cfg(feature = "conditional-storage")] + #[cfg(feature = "frame-feature-testing")] StorageEntryMetadata { name: "ConditionalDoubleMap", modifier: StorageEntryModifier::Optional, @@ -1183,7 +1411,7 @@ fn metadata() { default: vec![0], docs: vec![], }, - #[cfg(feature = "conditional-storage")] + #[cfg(feature = "frame-feature-testing")] StorageEntryMetadata { name: "ConditionalNMap", modifier: StorageEntryModifier::Optional, @@ -1214,7 +1442,14 @@ fn metadata() { modifier: StorageEntryModifier::Default, ty: StorageEntryType::Plain(meta_type::()), default: vec![0, 0, 0, 0], - docs: vec!["Counter for the related counted storage map"], + docs: maybe_docs(vec!["Counter for the related counted storage map"]), + }, + StorageEntryMetadata { + name: "Unbounded", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Plain(meta_type::>()), + default: vec![0], + docs: vec![], }, ], }), @@ -1225,13 +1460,13 @@ fn metadata() { name: "MyGetParam", ty: meta_type::(), value: vec![10, 0, 0, 0], - docs: vec![" Some comment", " Some comment"], + docs: maybe_docs(vec![" Some comment", " Some comment"]), }, PalletConstantMetadata { name: "MyGetParam2", ty: meta_type::(), value: vec![11, 0, 0, 0], - docs: vec![" Some comment", " Some comment"], + docs: maybe_docs(vec![" Some comment", " Some comment"]), }, PalletConstantMetadata { name: "MyGetParam3", @@ -1243,171 +1478,38 @@ fn metadata() { name: "some_extra", ty: meta_type::(), value: vec![100, 0, 0, 0, 0, 0, 0, 0], - docs: vec![" Some doc", " Some doc"], + docs: maybe_docs(vec![" Some doc", " Some doc"]), }, PalletConstantMetadata { name: "some_extra_extra", ty: meta_type::(), value: vec![0, 0, 0, 0, 0, 0, 0, 0], - docs: vec![" Some doc"], + docs: maybe_docs(vec![" Some doc"]), + }, + PalletConstantMetadata { + name: "SomeExtraRename", + ty: meta_type::(), + value: vec![0, 0, 0, 0, 0, 0, 0, 0], + docs: maybe_docs(vec![" Some doc"]), }, ], error: Some(PalletErrorMetadata { ty: meta_type::>() }), }, PalletMetadata { - index: 1, - name: "Example", + index: 2, + name: "Example2", storage: Some(PalletStorageMetadata { - prefix: "Example", + prefix: "Example2", entries: vec![ StorageEntryMetadata { - name: "ValueWhereClause", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(meta_type::()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "Value", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(meta_type::()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "Value2", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(meta_type::()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "Map", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - key: meta_type::(), - value: meta_type::(), - hashers: vec![StorageHasher::Blake2_128Concat], - }, - default: vec![4, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "Map2", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - key: meta_type::(), - value: meta_type::(), - hashers: vec![StorageHasher::Twox64Concat], - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "DoubleMap", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - value: meta_type::(), - key: meta_type::<(u8, u16)>(), - hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Twox64Concat, - ], - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "DoubleMap2", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - value: meta_type::(), - key: meta_type::<(u16, u32)>(), - hashers: vec![ - StorageHasher::Twox64Concat, - StorageHasher::Blake2_128Concat, - ], - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "NMap", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - key: meta_type::(), - hashers: vec![StorageHasher::Blake2_128Concat], - value: meta_type::(), - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "NMap2", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - key: meta_type::<(u16, u32)>(), - hashers: vec![ - StorageHasher::Twox64Concat, - StorageHasher::Blake2_128Concat, - ], - value: meta_type::(), - }, - default: vec![0], - docs: vec![], - }, - #[cfg(feature = "conditional-storage")] - StorageEntryMetadata { - name: "ConditionalValue", + name: "SomeValue", modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(meta_type::()), + ty: StorageEntryType::Plain(meta_type::>()), default: vec![0], docs: vec![], }, - #[cfg(feature = "conditional-storage")] StorageEntryMetadata { - name: "ConditionalMap", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - key: meta_type::(), - value: meta_type::(), - hashers: vec![StorageHasher::Twox64Concat], - }, - default: vec![0], - docs: vec![], - }, - #[cfg(feature = "conditional-storage")] - StorageEntryMetadata { - name: "ConditionalDoubleMap", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - value: meta_type::(), - key: meta_type::<(u8, u16)>(), - hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Twox64Concat, - ], - }, - default: vec![0], - docs: vec![], - }, - #[cfg(feature = "conditional-storage")] - StorageEntryMetadata { - name: "ConditionalNMap", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - key: meta_type::<(u8, u16)>(), - hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Twox64Concat, - ], - value: meta_type::(), - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "RenamedCountedMap", + name: "SomeCountedStorageMap", modifier: StorageEntryModifier::Optional, ty: StorageEntryType::Map { hashers: vec![StorageHasher::Twox64Concat], @@ -1418,70 +1520,51 @@ fn metadata() { docs: vec![], }, StorageEntryMetadata { - name: "CounterForRenamedCountedMap", + name: "CounterForSomeCountedStorageMap", modifier: StorageEntryModifier::Default, ty: StorageEntryType::Plain(meta_type::()), default: vec![0, 0, 0, 0], - docs: vec!["Counter for the related counted storage map"], + docs: maybe_docs(vec!["Counter for the related counted storage map"]), }, ], }), - calls: Some(meta_type::>().into()), - event: Some(meta_type::>().into()), - constants: vec![ - PalletConstantMetadata { - name: "MyGetParam", - ty: meta_type::(), - value: vec![10, 0, 0, 0], - docs: vec![" Some comment", " Some comment"], - }, - PalletConstantMetadata { - name: "MyGetParam2", - ty: meta_type::(), - value: vec![11, 0, 0, 0], - docs: vec![" Some comment", " Some comment"], - }, - PalletConstantMetadata { - name: "MyGetParam3", - ty: meta_type::(), - value: vec![12, 0, 0, 0, 0, 0, 0, 0], - docs: vec![], - }, - PalletConstantMetadata { - name: "some_extra", - ty: meta_type::(), - value: vec![100, 0, 0, 0, 0, 0, 0, 0], - docs: vec![" Some doc", " Some doc"], - }, - PalletConstantMetadata { - name: "some_extra_extra", - ty: meta_type::(), - value: vec![0, 0, 0, 0, 0, 0, 0, 0], - docs: vec![" Some doc"], - }, - ], - error: Some(PalletErrorMetadata { ty: meta_type::>() }), + calls: None, + event: Some(PalletEventMetadata { ty: meta_type::() }), + constants: vec![], + error: None, }, + #[cfg(feature = "frame-feature-testing")] PalletMetadata { - index: 2, - name: "Example2", - storage: Some(PalletStorageMetadata { - prefix: "Example2", - entries: vec![StorageEntryMetadata { - name: "SomeValue", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(meta_type::>()), - default: vec![0], - docs: vec![], - }], - }), - calls: Some(meta_type::>().into()), - event: Some(PalletEventMetadata { ty: meta_type::() }), + index: 3, + name: "Example3", + storage: None, + calls: None, + event: None, + constants: vec![], + error: None, + }, + #[cfg(feature = "frame-feature-testing-2")] + PalletMetadata { + index: 5, + name: "Example5", + storage: None, + calls: None, + event: None, constants: vec![], error: None, }, ]; + let empty_doc = pallets[0].event.as_ref().unwrap().ty.type_info().docs().is_empty() && + pallets[0].error.as_ref().unwrap().ty.type_info().docs().is_empty() && + pallets[0].calls.as_ref().unwrap().ty.type_info().docs().is_empty(); + + if cfg!(feature = "no-metadata-docs") { + assert!(empty_doc) + } else { + assert!(!empty_doc) + } + let extrinsic = ExtrinsicMetadata { ty: meta_type::(), version: 4, @@ -1504,7 +1587,7 @@ fn metadata() { _ => panic!("metadata has been bumped, test needs to be updated"), }; - pretty_assertions::assert_eq!(actual_metadata.pallets[1], expected_metadata.pallets[1]); + pretty_assertions::assert_eq!(actual_metadata.pallets, expected_metadata.pallets); } #[test] @@ -1525,6 +1608,8 @@ fn test_storage_info() { traits::{StorageInfo, StorageInfoTrait}, }; + // Storage max size is calculated by adding up all the hasher size, the key type size and the + // value type size assert_eq!( Example::storage_info(), vec![ @@ -1554,44 +1639,65 @@ fn test_storage_info() { storage_name: b"Map".to_vec(), prefix: prefix(b"Example", b"Map").to_vec(), max_values: None, - max_size: Some(3 + 16), + max_size: Some(16 + 1 + 2), }, StorageInfo { pallet_name: b"Example".to_vec(), storage_name: b"Map2".to_vec(), prefix: prefix(b"Example", b"Map2").to_vec(), max_values: Some(3), - max_size: Some(6 + 8), + max_size: Some(8 + 2 + 4), + }, + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"Map3".to_vec(), + prefix: prefix(b"Example", b"Map3").to_vec(), + max_values: None, + max_size: Some(16 + 4 + 8), }, StorageInfo { pallet_name: b"Example".to_vec(), storage_name: b"DoubleMap".to_vec(), prefix: prefix(b"Example", b"DoubleMap").to_vec(), max_values: None, - max_size: Some(7 + 16 + 8), + max_size: Some(16 + 1 + 8 + 2 + 4), }, StorageInfo { pallet_name: b"Example".to_vec(), storage_name: b"DoubleMap2".to_vec(), prefix: prefix(b"Example", b"DoubleMap2").to_vec(), max_values: Some(5), - max_size: Some(14 + 8 + 16), + max_size: Some(8 + 2 + 16 + 4 + 8), + }, + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"DoubleMap3".to_vec(), + prefix: prefix(b"Example", b"DoubleMap3").to_vec(), + max_values: None, + max_size: Some(16 + 4 + 8 + 8 + 16), }, StorageInfo { pallet_name: b"Example".to_vec(), storage_name: b"NMap".to_vec(), prefix: prefix(b"Example", b"NMap").to_vec(), max_values: None, - max_size: Some(5 + 16), + max_size: Some(16 + 1 + 4), }, StorageInfo { pallet_name: b"Example".to_vec(), storage_name: b"NMap2".to_vec(), prefix: prefix(b"Example", b"NMap2").to_vec(), max_values: Some(11), - max_size: Some(14 + 8 + 16), + max_size: Some(8 + 2 + 16 + 4 + 8), }, - #[cfg(feature = "conditional-storage")] + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"NMap3".to_vec(), + prefix: prefix(b"Example", b"NMap3").to_vec(), + max_values: None, + max_size: Some(16 + 1 + 8 + 2 + 16), + }, + #[cfg(feature = "frame-feature-testing")] { StorageInfo { pallet_name: b"Example".to_vec(), @@ -1601,34 +1707,34 @@ fn test_storage_info() { max_size: Some(4), } }, - #[cfg(feature = "conditional-storage")] + #[cfg(feature = "frame-feature-testing")] { StorageInfo { pallet_name: b"Example".to_vec(), storage_name: b"ConditionalMap".to_vec(), prefix: prefix(b"Example", b"ConditionalMap").to_vec(), max_values: Some(12), - max_size: Some(6 + 8), + max_size: Some(8 + 2 + 4), } }, - #[cfg(feature = "conditional-storage")] + #[cfg(feature = "frame-feature-testing")] { StorageInfo { pallet_name: b"Example".to_vec(), storage_name: b"ConditionalDoubleMap".to_vec(), prefix: prefix(b"Example", b"ConditionalDoubleMap").to_vec(), max_values: None, - max_size: Some(7 + 16 + 8), + max_size: Some(16 + 1 + 8 + 2 + 4), } }, - #[cfg(feature = "conditional-storage")] + #[cfg(feature = "frame-feature-testing")] { StorageInfo { pallet_name: b"Example".to_vec(), storage_name: b"ConditionalNMap".to_vec(), prefix: prefix(b"Example", b"ConditionalNMap").to_vec(), max_values: None, - max_size: Some(7 + 16 + 8), + max_size: Some(16 + 1 + 8 + 2 + 4), } }, StorageInfo { @@ -1636,7 +1742,7 @@ fn test_storage_info() { storage_name: b"RenamedCountedMap".to_vec(), prefix: prefix(b"Example", b"RenamedCountedMap").to_vec(), max_values: None, - max_size: Some(1 + 4 + 8), + max_size: Some(8 + 1 + 4), }, StorageInfo { pallet_name: b"Example".to_vec(), @@ -1645,6 +1751,13 @@ fn test_storage_info() { max_values: Some(1), max_size: Some(4), }, + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"Unbounded".to_vec(), + prefix: prefix(b"Example", b"Unbounded").to_vec(), + max_values: Some(1), + max_size: None, + }, ], ); @@ -1675,3 +1788,132 @@ fn test_storage_info() { ], ); } + +#[test] +fn assert_type_all_pallets_reversed_with_system_first_is_correct() { + // Just ensure the 2 types are same. + #[allow(deprecated)] + fn _a(_t: AllPalletsReversedWithSystemFirst) {} + #[cfg(all(not(feature = "frame-feature-testing"), not(feature = "frame-feature-testing-2")))] + fn _b(t: (System, Example4, Example2, Example)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", not(feature = "frame-feature-testing-2")))] + fn _b(t: (System, Example4, Example3, Example2, Example)) { + _a(t) + } + + #[cfg(all(not(feature = "frame-feature-testing"), feature = "frame-feature-testing-2"))] + fn _b(t: (System, Example5, Example4, Example2, Example)) { + _a(t) + } + + #[cfg(all(feature = "frame-feature-testing", feature = "frame-feature-testing-2"))] + fn _b(t: (System, Example5, Example4, Example3, Example2, Example)) { + _a(t) + } +} + +#[test] +fn assert_type_all_pallets_with_system_is_correct() { + // Just ensure the 2 types are same. + fn _a(_t: AllPalletsWithSystem) {} + #[cfg(all(not(feature = "frame-feature-testing"), not(feature = "frame-feature-testing-2")))] + fn _b(t: (System, Example, Example2, Example4)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", not(feature = "frame-feature-testing-2")))] + fn _b(t: (System, Example, Example2, Example3, Example4)) { + _a(t) + } + #[cfg(all(not(feature = "frame-feature-testing"), feature = "frame-feature-testing-2"))] + fn _b(t: (System, Example, Example2, Example4, Example5)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", feature = "frame-feature-testing-2"))] + fn _b(t: (System, Example, Example2, Example3, Example4, Example5)) { + _a(t) + } +} + +#[test] +fn assert_type_all_pallets_without_system_is_correct() { + // Just ensure the 2 types are same. + fn _a(_t: AllPalletsWithoutSystem) {} + #[cfg(all(not(feature = "frame-feature-testing"), not(feature = "frame-feature-testing-2")))] + fn _b(t: (Example, Example2, Example4)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", not(feature = "frame-feature-testing-2")))] + fn _b(t: (Example, Example2, Example3, Example4)) { + _a(t) + } + #[cfg(all(not(feature = "frame-feature-testing"), feature = "frame-feature-testing-2"))] + fn _b(t: (Example, Example2, Example4, Example5)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", feature = "frame-feature-testing-2"))] + fn _b(t: (Example, Example2, Example3, Example4, Example5)) { + _a(t) + } +} + +#[test] +fn assert_type_all_pallets_with_system_reversed_is_correct() { + // Just ensure the 2 types are same. + #[allow(deprecated)] + fn _a(_t: AllPalletsWithSystemReversed) {} + #[cfg(all(not(feature = "frame-feature-testing"), not(feature = "frame-feature-testing-2")))] + fn _b(t: (Example4, Example2, Example, System)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", not(feature = "frame-feature-testing-2")))] + fn _b(t: (Example4, Example3, Example2, Example, System)) { + _a(t) + } + #[cfg(all(not(feature = "frame-feature-testing"), feature = "frame-feature-testing-2"))] + fn _b(t: (Example5, Example4, Example2, Example, System)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", feature = "frame-feature-testing-2"))] + fn _b(t: (Example5, Example4, Example3, Example2, Example, System)) { + _a(t) + } +} + +#[test] +fn assert_type_all_pallets_without_system_reversed_is_correct() { + // Just ensure the 2 types are same. + #[allow(deprecated)] + fn _a(_t: AllPalletsWithoutSystemReversed) {} + #[cfg(all(not(feature = "frame-feature-testing"), not(feature = "frame-feature-testing-2")))] + fn _b(t: (Example4, Example2, Example)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", not(feature = "frame-feature-testing-2")))] + fn _b(t: (Example4, Example3, Example2, Example)) { + _a(t) + } + #[cfg(all(not(feature = "frame-feature-testing"), feature = "frame-feature-testing-2"))] + fn _b(t: (Example5, Example4, Example2, Example)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", feature = "frame-feature-testing-2"))] + fn _b(t: (Example5, Example4, Example3, Example2, Example)) { + _a(t) + } +} + +#[test] +fn test_storage_alias() { + #[frame_support::storage_alias] + type Value + where + ::AccountId: From + SomeAssociation1, + = StorageValue, u32, ValueQuery>; + + TestExternalities::default().execute_with(|| { + pallet::Value::::put(10); + assert_eq!(10, Value::::get()); + }) +} diff --git a/frame/support/test/tests/pallet_compatibility.rs b/frame/support/test/tests/pallet_compatibility.rs index 4523063252ab9..300fb9a40cf4e 100644 --- a/frame/support/test/tests/pallet_compatibility.rs +++ b/frame/support/test/tests/pallet_compatibility.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Old macros don't support the flag `no-metadata-docs` so the result differs when the feature is +// activated. +#![cfg(not(feature = "no-metadata-docs"))] + +use frame_support::traits::{ConstU32, ConstU64}; + pub trait SomeAssociation { type A: frame_support::dispatch::Parameter + Default; } @@ -34,10 +40,10 @@ mod pallet_old { type Balance: Parameter + codec::HasCompact + From - + Into + + Into + Default + SomeAssociation; - type Event: From> + Into<::Event>; + type RuntimeEvent: From> + Into<::RuntimeEvent>; } decl_storage! { @@ -64,12 +70,12 @@ mod pallet_old { ); decl_module! { - pub struct Module for enum Call where origin: T::Origin { + pub struct Module for enum Call where origin: T::RuntimeOrigin { type Error = Error; fn deposit_event() = default; const SomeConst: T::Balance = T::SomeConst::get(); - #[weight = >::into(new_value.clone())] + #[weight = >::into(new_value.clone())] fn set_dummy(origin, #[compact] new_value: T::Balance) { ensure_root(origin)?; @@ -79,7 +85,7 @@ mod pallet_old { fn on_initialize(_n: T::BlockNumber) -> Weight { >::put(T::Balance::from(10)); - 10 + Weight::from_ref_time(10) } fn on_finalize(_n: T::BlockNumber) { @@ -107,24 +113,25 @@ pub mod pallet { type Balance: Parameter + codec::HasCompact + From - + Into + + Into + Default + MaybeSerializeDeserialize + SomeAssociation + scale_info::StaticTypeInfo; #[pallet::constant] type SomeConst: Get; - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; } #[pallet::pallet] + #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::hooks] impl Hooks for Pallet { fn on_initialize(_n: T::BlockNumber) -> Weight { >::put(T::Balance::from(10)); - 10 + Weight::from_ref_time(10) } fn on_finalize(_n: T::BlockNumber) { @@ -134,7 +141,8 @@ pub mod pallet { #[pallet::call] impl Pallet { - #[pallet::weight(>::into(new_value.clone()))] + #[pallet::call_index(0)] + #[pallet::weight(>::into(new_value.clone()))] pub fn set_dummy( origin: OriginFor, #[pallet::compact] new_value: T::Balance, @@ -217,24 +225,19 @@ pub mod pallet { } } -frame_support::parameter_types!( - pub const SomeConst: u64 = 10; - pub const BlockHashCount: u32 = 250; -); - impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u32; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU32<250>; type BlockWeights = (); type BlockLength = (); type DbWeight = (); @@ -246,21 +249,22 @@ impl frame_system::Config for Runtime { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet::Config for Runtime { - type Event = Event; - type SomeConst = SomeConst; + type RuntimeEvent = RuntimeEvent; + type SomeConst = ConstU64<10>; type Balance = u64; } impl pallet_old::Config for Runtime { - type Event = Event; - type SomeConst = SomeConst; + type RuntimeEvent = RuntimeEvent; + type SomeConst = ConstU64<10>; type Balance = u64; } pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( pub enum Runtime where diff --git a/frame/support/test/tests/pallet_compatibility_instance.rs b/frame/support/test/tests/pallet_compatibility_instance.rs index 768b9f28d35f3..79370d911b943 100644 --- a/frame/support/test/tests/pallet_compatibility_instance.rs +++ b/frame/support/test/tests/pallet_compatibility_instance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Old macros don't support the flag `no-metadata-docs` so the result differs when the feature is +// activated. +#![cfg(not(feature = "no-metadata-docs"))] + +use frame_support::traits::{ConstU32, ConstU64}; + mod pallet_old { use frame_support::{ decl_error, decl_event, decl_module, decl_storage, traits::Get, weights::Weight, Parameter, @@ -23,8 +29,8 @@ mod pallet_old { pub trait Config: frame_system::Config { type SomeConst: Get; - type Balance: Parameter + codec::HasCompact + From + Into + Default; - type Event: From> + Into<::Event>; + type Balance: Parameter + codec::HasCompact + From + Into + Default; + type RuntimeEvent: From> + Into<::RuntimeEvent>; } decl_storage! { @@ -50,13 +56,13 @@ mod pallet_old { decl_module! { pub struct Module, I: Instance = DefaultInstance> for enum Call - where origin: T::Origin + where origin: T::RuntimeOrigin { type Error = Error; fn deposit_event() = default; const SomeConst: T::Balance = T::SomeConst::get(); - #[weight = >::into(new_value.clone())] + #[weight = >::into(new_value.clone())] fn set_dummy(origin, #[compact] new_value: T::Balance) { ensure_root(origin)?; @@ -66,7 +72,7 @@ mod pallet_old { fn on_initialize(_n: T::BlockNumber) -> Weight { >::put(T::Balance::from(10)); - 10 + Weight::from_ref_time(10) } fn on_finalize(_n: T::BlockNumber) { @@ -93,23 +99,25 @@ pub mod pallet { type Balance: Parameter + codec::HasCompact + From - + Into + + Into + Default + MaybeSerializeDeserialize + scale_info::StaticTypeInfo; #[pallet::constant] type SomeConst: Get; - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; } #[pallet::pallet] + #[pallet::without_storage_info] pub struct Pallet(PhantomData<(T, I)>); #[pallet::hooks] impl, I: 'static> Hooks for Pallet { fn on_initialize(_n: T::BlockNumber) -> Weight { >::put(T::Balance::from(10)); - 10 + Weight::from_ref_time(10) } fn on_finalize(_n: T::BlockNumber) { @@ -119,7 +127,8 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { - #[pallet::weight(>::into(new_value.clone()))] + #[pallet::call_index(0)] + #[pallet::weight(>::into(new_value.clone()))] pub fn set_dummy( origin: OriginFor, #[pallet::compact] new_value: T::Balance, @@ -197,27 +206,22 @@ pub mod pallet { } } -frame_support::parameter_types!( - pub const SomeConst: u64 = 10; - pub const BlockHashCount: u32 = 250; -); - impl frame_system::Config for Runtime { type BlockWeights = (); type BlockLength = (); type DbWeight = (); type BaseCallFilter = frame_support::traits::Everything; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u32; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU32<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -226,41 +230,42 @@ impl frame_system::Config for Runtime { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet::Config for Runtime { - type Event = Event; - type SomeConst = SomeConst; + type RuntimeEvent = RuntimeEvent; + type SomeConst = ConstU64<10>; type Balance = u64; } impl pallet::Config for Runtime { - type Event = Event; - type SomeConst = SomeConst; + type RuntimeEvent = RuntimeEvent; + type SomeConst = ConstU64<10>; type Balance = u64; } impl pallet::Config for Runtime { - type Event = Event; - type SomeConst = SomeConst; + type RuntimeEvent = RuntimeEvent; + type SomeConst = ConstU64<10>; type Balance = u64; } impl pallet_old::Config for Runtime { - type Event = Event; - type SomeConst = SomeConst; + type RuntimeEvent = RuntimeEvent; + type SomeConst = ConstU64<10>; type Balance = u64; } impl pallet_old::Config for Runtime { - type Event = Event; - type SomeConst = SomeConst; + type RuntimeEvent = RuntimeEvent; + type SomeConst = ConstU64<10>; type Balance = u64; } impl pallet_old::Config for Runtime { - type Event = Event; - type SomeConst = SomeConst; + type RuntimeEvent = RuntimeEvent; + type SomeConst = ConstU64<10>; type Balance = u64; } pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( pub enum Runtime where diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index 34586e8414216..d8ad13ceda1dd 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,20 +16,21 @@ // limitations under the License. use frame_support::{ - dispatch::UnfilteredDispatchable, + dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays, UnfilteredDispatchable}, + pallet_prelude::ValueQuery, storage::unhashed, - traits::{GetCallName, OnFinalize, OnGenesis, OnInitialize, OnRuntimeUpgrade}, - weights::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}, + traits::{ConstU32, GetCallName, OnFinalize, OnGenesis, OnInitialize, OnRuntimeUpgrade}, }; use sp_io::{ hashing::{blake2_128, twox_128, twox_64}, TestExternalities, }; -use sp_runtime::DispatchError; +use sp_runtime::{DispatchError, ModuleError}; #[frame_support::pallet] pub mod pallet { - use frame_support::{pallet_prelude::*, scale_info}; + use codec::MaxEncodedLen; + use frame_support::{pallet_prelude::*, parameter_types, scale_info}; use frame_system::pallet_prelude::*; use sp_std::any::TypeId; @@ -40,7 +41,8 @@ pub mod pallet { #[pallet::constant] type MyGetParam: Get; type Balance: Parameter + Default + scale_info::StaticTypeInfo; - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; } #[pallet::pallet] @@ -52,10 +54,10 @@ pub mod pallet { fn on_initialize(_: BlockNumberFor) -> Weight { if TypeId::of::() == TypeId::of::<()>() { Self::deposit_event(Event::Something(10)); - 10 + Weight::from_ref_time(10) } else { Self::deposit_event(Event::Something(11)); - 11 + Weight::from_ref_time(11) } } fn on_finalize(_: BlockNumberFor) { @@ -68,10 +70,10 @@ pub mod pallet { fn on_runtime_upgrade() -> Weight { if TypeId::of::() == TypeId::of::<()>() { Self::deposit_event(Event::Something(30)); - 30 + Weight::from_ref_time(30) } else { Self::deposit_event(Event::Something(31)); - 31 + Weight::from_ref_time(31) } } fn integrity_test() {} @@ -80,7 +82,8 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { /// Doc comment put in metadata - #[pallet::weight(Weight::from(*_foo))] + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_ref_time(*_foo as u64))] pub fn foo( origin: OriginFor, #[pallet::compact] _foo: u32, @@ -91,9 +94,9 @@ pub mod pallet { } /// Doc comment put in metadata + #[pallet::call_index(1)] #[pallet::weight(1)] - #[frame_support::transactional] - pub fn foo_transactional( + pub fn foo_storage_layer( origin: OriginFor, #[pallet::compact] _foo: u32, ) -> DispatchResultWithPostInfo { @@ -103,9 +106,11 @@ pub mod pallet { } #[pallet::error] + #[derive(PartialEq, Eq)] pub enum Error { /// doc comment put into metadata InsufficientProposersBalance, + NonExistentStorageValue, } #[pallet::event] @@ -127,6 +132,20 @@ pub mod pallet { #[pallet::storage] pub type Map2 = StorageMap<_, Twox64Concat, u16, u32>; + parameter_types! { + pub const Map3Default: Result> = Ok(1337); + } + + #[pallet::storage] + pub type Map3 = StorageMap< + _, + Blake2_128Concat, + u32, + u64, + ResultQuery::NonExistentStorageValue>, + Map3Default, + >; + #[pallet::storage] pub type DoubleMap = StorageDoubleMap<_, Blake2_128Concat, u8, Twox64Concat, u16, u32>; @@ -135,6 +154,17 @@ pub mod pallet { pub type DoubleMap2 = StorageDoubleMap<_, Twox64Concat, u16, Blake2_128Concat, u32, u64>; + #[pallet::storage] + pub type DoubleMap3 = StorageDoubleMap< + _, + Blake2_128Concat, + u32, + Twox64Concat, + u64, + u128, + ResultQuery::NonExistentStorageValue>, + >; + #[pallet::storage] #[pallet::getter(fn nmap)] pub type NMap = StorageNMap<_, storage::Key, u32>; @@ -144,6 +174,15 @@ pub mod pallet { pub type NMap2 = StorageNMap<_, (storage::Key, storage::Key), u64>; + #[pallet::storage] + #[pallet::getter(fn nmap3)] + pub type NMap3 = StorageNMap< + _, + (NMapKey, NMapKey), + u128, + ResultQuery::NonExistentStorageValue>, + >; + #[pallet::genesis_config] #[derive(Default)] pub struct GenesisConfig { @@ -164,6 +203,7 @@ pub mod pallet { Encode, Decode, scale_info::TypeInfo, + MaxEncodedLen, )] #[scale_info(skip_type_params(T, I))] pub struct Origin(PhantomData<(T, I)>); @@ -215,7 +255,8 @@ pub mod pallet2 { #[pallet::config] pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; } #[pallet::pallet] @@ -245,24 +286,19 @@ pub mod pallet2 { } } -frame_support::parameter_types!( - pub const MyGetParam: u32 = 10; - pub const BlockHashCount: u32 = 250; -); - impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u32; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU32<250>; type BlockWeights = (); type BlockLength = (); type DbWeight = (); @@ -274,27 +310,28 @@ impl frame_system::Config for Runtime { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet::Config for Runtime { - type Event = Event; - type MyGetParam = MyGetParam; + type RuntimeEvent = RuntimeEvent; + type MyGetParam = ConstU32<10>; type Balance = u64; } impl pallet::Config for Runtime { - type Event = Event; - type MyGetParam = MyGetParam; + type RuntimeEvent = RuntimeEvent; + type MyGetParam = ConstU32<10>; type Balance = u64; } impl pallet2::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; } impl pallet2::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; } pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( pub enum Runtime where @@ -302,35 +339,44 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: frame_system::{Pallet, Call, Event}, - Example: pallet::{Pallet, Call, Event, Config, Storage, Inherent, Origin, ValidateUnsigned}, - Instance1Example: pallet::::{ - Pallet, Call, Event, Config, Storage, Inherent, Origin, ValidateUnsigned - }, - Example2: pallet2::{Pallet, Event, Config, Storage}, - Instance1Example2: pallet2::::{Pallet, Event, Config, Storage}, + // Exclude part `Storage` in order not to check its metadata in tests. + System: frame_system exclude_parts { Storage }, + Example: pallet, + Instance1Example: pallet::, + Example2: pallet2, + Instance1Example2: pallet2::, } ); +use frame_support::weights::Weight; + #[test] fn call_expand() { let call_foo = pallet::Call::::foo { foo: 3 }; assert_eq!( call_foo.get_dispatch_info(), - DispatchInfo { weight: 3, class: DispatchClass::Normal, pays_fee: Pays::Yes } + DispatchInfo { + weight: Weight::from_ref_time(3), + class: DispatchClass::Normal, + pays_fee: Pays::Yes + } ); assert_eq!(call_foo.get_call_name(), "foo"); - assert_eq!(pallet::Call::::get_call_names(), &["foo", "foo_transactional"]); + assert_eq!(pallet::Call::::get_call_names(), &["foo", "foo_storage_layer"]); let call_foo = pallet::Call::::foo { foo: 3 }; assert_eq!( call_foo.get_dispatch_info(), - DispatchInfo { weight: 3, class: DispatchClass::Normal, pays_fee: Pays::Yes } + DispatchInfo { + weight: Weight::from_ref_time(3), + class: DispatchClass::Normal, + pays_fee: Pays::Yes + } ); assert_eq!(call_foo.get_call_name(), "foo"); assert_eq!( pallet::Call::::get_call_names(), - &["foo", "foo_transactional"], + &["foo", "foo_storage_layer"], ); } @@ -346,7 +392,11 @@ fn error_expand() { ); assert_eq!( DispatchError::from(pallet::Error::::InsufficientProposersBalance), - DispatchError::Module { index: 1, error: 0, message: Some("InsufficientProposersBalance") }, + DispatchError::Module(ModuleError { + index: 1, + error: [0; 4], + message: Some("InsufficientProposersBalance") + }), ); assert_eq!( @@ -363,7 +413,11 @@ fn error_expand() { DispatchError::from( pallet::Error::::InsufficientProposersBalance ), - DispatchError::Module { index: 2, error: 0, message: Some("InsufficientProposersBalance") }, + DispatchError::Module(ModuleError { + index: 2, + error: [0; 4], + message: Some("InsufficientProposersBalance") + }), ); } @@ -382,7 +436,7 @@ fn pallet_expand_deposit_event() { .unwrap(); assert_eq!( frame_system::Pallet::::events()[0].event, - Event::Example(pallet::Event::Something(3)), + RuntimeEvent::Example(pallet::Event::Something(3)), ); }); @@ -393,7 +447,7 @@ fn pallet_expand_deposit_event() { .unwrap(); assert_eq!( frame_system::Pallet::::events()[0].event, - Event::Instance1Example(pallet::Event::Something(3)), + RuntimeEvent::Instance1Example(pallet::Event::Something(3)), ); }); } @@ -431,6 +485,13 @@ fn storage_expand() { assert_eq!(unhashed::get::(&k), Some(2u32)); assert_eq!(&k[..32], &>::final_prefix()); + >::insert(1, 2); + let mut k = [twox_128(b"Example"), twox_128(b"Map3")].concat(); + k.extend(1u32.using_encoded(blake2_128_concat)); + assert_eq!(unhashed::get::(&k), Some(2u64)); + assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(>::get(2), Ok(1337)); + >::insert(&1, &2, &3); let mut k = [twox_128(b"Example"), twox_128(b"DoubleMap")].concat(); k.extend(1u8.using_encoded(blake2_128_concat)); @@ -445,6 +506,17 @@ fn storage_expand() { assert_eq!(unhashed::get::(&k), Some(3u64)); assert_eq!(&k[..32], &>::final_prefix()); + >::insert(&1, &2, &3); + let mut k = [twox_128(b"Example"), twox_128(b"DoubleMap3")].concat(); + k.extend(1u32.using_encoded(blake2_128_concat)); + k.extend(2u64.using_encoded(twox_64_concat)); + assert_eq!(unhashed::get::(&k), Some(3u128)); + assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!( + >::get(2, 3), + Err(pallet::Error::::NonExistentStorageValue), + ); + >::insert((&1,), &3); let mut k = [twox_128(b"Example"), twox_128(b"NMap")].concat(); k.extend(1u8.using_encoded(blake2_128_concat)); @@ -457,6 +529,17 @@ fn storage_expand() { k.extend(2u32.using_encoded(blake2_128_concat)); assert_eq!(unhashed::get::(&k), Some(3u64)); assert_eq!(&k[..32], &>::final_prefix()); + + >::insert((&1, &2), &3); + let mut k = [twox_128(b"Example"), twox_128(b"NMap3")].concat(); + k.extend(1u8.using_encoded(blake2_128_concat)); + k.extend(2u16.using_encoded(twox_64_concat)); + assert_eq!(unhashed::get::(&k), Some(3u128)); + assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!( + >::get((2, 3)), + Err(pallet::Error::::NonExistentStorageValue), + ); }); TestExternalities::default().execute_with(|| { @@ -476,6 +559,13 @@ fn storage_expand() { assert_eq!(unhashed::get::(&k), Some(2u32)); assert_eq!(&k[..32], &>::final_prefix()); + >::insert(1, 2); + let mut k = [twox_128(b"Instance1Example"), twox_128(b"Map3")].concat(); + k.extend(1u32.using_encoded(blake2_128_concat)); + assert_eq!(unhashed::get::(&k), Some(2u64)); + assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(>::get(2), Ok(1337)); + >::insert(&1, &2, &3); let mut k = [twox_128(b"Instance1Example"), twox_128(b"DoubleMap")].concat(); k.extend(1u8.using_encoded(blake2_128_concat)); @@ -490,6 +580,17 @@ fn storage_expand() { assert_eq!(unhashed::get::(&k), Some(3u64)); assert_eq!(&k[..32], &>::final_prefix()); + >::insert(&1, &2, &3); + let mut k = [twox_128(b"Instance1Example"), twox_128(b"DoubleMap3")].concat(); + k.extend(1u32.using_encoded(blake2_128_concat)); + k.extend(2u64.using_encoded(twox_64_concat)); + assert_eq!(unhashed::get::(&k), Some(3u128)); + assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!( + >::get(2, 3), + Err(pallet::Error::::NonExistentStorageValue), + ); + >::insert((&1,), &3); let mut k = [twox_128(b"Instance1Example"), twox_128(b"NMap")].concat(); k.extend(1u8.using_encoded(blake2_128_concat)); @@ -502,43 +603,95 @@ fn storage_expand() { k.extend(2u32.using_encoded(blake2_128_concat)); assert_eq!(unhashed::get::(&k), Some(3u64)); assert_eq!(&k[..32], &>::final_prefix()); + + >::insert((&1, &2), &3); + let mut k = [twox_128(b"Instance1Example"), twox_128(b"NMap3")].concat(); + k.extend(1u8.using_encoded(blake2_128_concat)); + k.extend(2u16.using_encoded(twox_64_concat)); + assert_eq!(unhashed::get::(&k), Some(3u128)); + assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!( + >::get((2, 3)), + Err(pallet::Error::::NonExistentStorageValue), + ); }); } +#[test] +fn pallet_metadata_expands() { + use frame_support::traits::{CrateVersion, PalletInfoData, PalletsInfoAccess}; + let mut infos = AllPalletsWithSystem::infos(); + infos.sort_by_key(|x| x.index); + assert_eq!( + infos, + vec![ + PalletInfoData { + index: 0, + name: "System", + module_name: "frame_system", + crate_version: CrateVersion { major: 4, minor: 0, patch: 0 }, + }, + PalletInfoData { + index: 1, + name: "Example", + module_name: "pallet", + crate_version: CrateVersion { major: 3, minor: 0, patch: 0 }, + }, + PalletInfoData { + index: 2, + name: "Instance1Example", + module_name: "pallet", + crate_version: CrateVersion { major: 3, minor: 0, patch: 0 }, + }, + PalletInfoData { + index: 3, + name: "Example2", + module_name: "pallet2", + crate_version: CrateVersion { major: 3, minor: 0, patch: 0 }, + }, + PalletInfoData { + index: 4, + name: "Instance1Example2", + module_name: "pallet2", + crate_version: CrateVersion { major: 3, minor: 0, patch: 0 }, + }, + ] + ); +} + #[test] fn pallet_hooks_expand() { TestExternalities::default().execute_with(|| { frame_system::Pallet::::set_block_number(1); - assert_eq!(AllPallets::on_initialize(1), 21); - AllPallets::on_finalize(1); + assert_eq!(AllPalletsWithoutSystem::on_initialize(1), Weight::from_ref_time(21)); + AllPalletsWithoutSystem::on_finalize(1); - assert_eq!(AllPallets::on_runtime_upgrade(), 61); + assert_eq!(AllPalletsWithoutSystem::on_runtime_upgrade(), Weight::from_ref_time(61)); - // The order is indeed reversed due to https://github.com/paritytech/substrate/issues/6280 assert_eq!( frame_system::Pallet::::events()[0].event, - Event::Instance1Example(pallet::Event::Something(11)), + RuntimeEvent::Example(pallet::Event::Something(10)), ); assert_eq!( frame_system::Pallet::::events()[1].event, - Event::Example(pallet::Event::Something(10)), + RuntimeEvent::Instance1Example(pallet::Event::Something(11)), ); assert_eq!( frame_system::Pallet::::events()[2].event, - Event::Instance1Example(pallet::Event::Something(21)), + RuntimeEvent::Example(pallet::Event::Something(20)), ); assert_eq!( frame_system::Pallet::::events()[3].event, - Event::Example(pallet::Event::Something(20)), + RuntimeEvent::Instance1Example(pallet::Event::Something(21)), ); assert_eq!( frame_system::Pallet::::events()[4].event, - Event::Instance1Example(pallet::Event::Something(31)), + RuntimeEvent::Example(pallet::Event::Something(30)), ); assert_eq!( frame_system::Pallet::::events()[5].event, - Event::Example(pallet::Event::Something(30)), + RuntimeEvent::Instance1Example(pallet::Event::Something(31)), ); }) } @@ -559,7 +712,7 @@ fn metadata() { let system_pallet_metadata = PalletMetadata { index: 0, name: "System", - storage: None, + storage: None, // The storage metadatas have been excluded. calls: Some(scale_info::meta_type::>().into()), event: Some(PalletEventMetadata { ty: scale_info::meta_type::>(), @@ -642,6 +795,17 @@ fn metadata() { default: vec![0], docs: vec![], }, + StorageEntryMetadata { + name: "Map3", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Map { + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + hashers: vec![StorageHasher::Blake2_128Concat], + }, + default: vec![0, 57, 5, 0, 0, 0, 0, 0, 0], + docs: vec![], + }, StorageEntryMetadata { name: "DoubleMap", modifier: StorageEntryModifier::Optional, @@ -664,6 +828,17 @@ fn metadata() { default: vec![0], docs: vec![], }, + StorageEntryMetadata { + name: "DoubleMap3", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Map { + value: scale_info::meta_type::(), + key: scale_info::meta_type::<(u32, u64)>(), + hashers: vec![StorageHasher::Blake2_128Concat, StorageHasher::Twox64Concat], + }, + default: vec![1, 1], + docs: vec![], + }, StorageEntryMetadata { name: "NMap", modifier: StorageEntryModifier::Optional, @@ -686,6 +861,17 @@ fn metadata() { default: vec![0], docs: vec![], }, + StorageEntryMetadata { + name: "NMap3", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Map { + key: scale_info::meta_type::<(u8, u16)>(), + hashers: vec![StorageHasher::Blake2_128Concat, StorageHasher::Twox64Concat], + value: scale_info::meta_type::(), + }, + default: vec![1, 1], + docs: vec![], + }, ], }), calls: Some(scale_info::meta_type::>().into()), @@ -777,3 +963,15 @@ fn test_pallet_info_access() { assert_eq!(::index(), 3); assert_eq!(::index(), 4); } + +#[test] +fn test_storage_alias() { + #[frame_support::storage_alias] + type Value, I: 'static> = + StorageValue, u32, ValueQuery>; + + TestExternalities::default().execute_with(|| { + pallet::Value::::put(10); + assert_eq!(10, Value::::get()); + }) +} diff --git a/frame/support/test/tests/pallet_ui.rs b/frame/support/test/tests/pallet_ui.rs index e5f4a54dfb000..2db1d3cb0543a 100644 --- a/frame/support/test/tests/pallet_ui.rs +++ b/frame/support/test/tests/pallet_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,14 @@ // limitations under the License. #[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] #[test] fn pallet_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/frame/support/test/tests/pallet_ui/attr_non_empty.stderr b/frame/support/test/tests/pallet_ui/attr_non_empty.stderr index 144af5a17ea5c..9eac5de35db80 100644 --- a/frame/support/test/tests/pallet_ui/attr_non_empty.stderr +++ b/frame/support/test/tests/pallet_ui/attr_non_empty.stderr @@ -1,5 +1,5 @@ -error: Invalid pallet macro call: expected no attributes, e.g. macro call must be just `#[frame_support::pallet]` or `#[pallet]` - --> $DIR/attr_non_empty.rs:1:26 +error: Invalid pallet macro call: unexpected attribute. Macro call must be bare, such as `#[frame_support::pallet]` or `#[pallet]`, or must specify the `dev_mode` attribute, such as `#[frame_support::pallet(dev_mode)]` or #[pallet(dev_mode)]. + --> tests/pallet_ui/attr_non_empty.rs:1:26 | 1 | #[frame_support::pallet [foo]] | ^^^ diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr index d1b040c16091f..62d8649f8af49 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr @@ -1,28 +1,21 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` - --> $DIR/call_argument_invalid_bound.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 | 20 | 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` - = note: required because of the requirements on the impl of `std::fmt::Debug` for `&::Bar` - = note: required for the cast to the object type `dyn std::fmt::Debug` + = note: required for `&::Bar` to implement `std::fmt::Debug` + = note: required for the cast from `&::Bar` to the object type `dyn std::fmt::Debug` error[E0277]: the trait bound `::Bar: Clone` is not satisfied - --> $DIR/call_argument_invalid_bound.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 | 20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^ the trait `Clone` is not implemented for `::Bar` - | - = note: required by `clone` error[E0369]: binary operation `==` cannot be applied to type `&::Bar` - --> $DIR/call_argument_invalid_bound.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 | 20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^ - | -help: consider further restricting this bound - | -17 | #[pallet::call + std::cmp::PartialEq] - | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr index 84d4863672957..f486c071631ea 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr @@ -1,54 +1,46 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` - --> $DIR/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 | 20 | 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` - = note: required because of the requirements on the impl of `std::fmt::Debug` for `&::Bar` - = note: required for the cast to the object type `dyn std::fmt::Debug` + = note: required for `&::Bar` to implement `std::fmt::Debug` + = note: required for the cast from `&::Bar` to the object type `dyn std::fmt::Debug` error[E0277]: the trait bound `::Bar: Clone` is not satisfied - --> $DIR/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 | 20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^ the trait `Clone` is not implemented for `::Bar` - | - = note: required by `clone` error[E0369]: binary operation `==` cannot be applied to type `&::Bar` - --> $DIR/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 | 20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^ - | -help: consider further restricting this bound - | -17 | #[pallet::call + std::cmp::PartialEq] - | ^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is not satisfied - --> $DIR/call_argument_invalid_bound_2.rs:20:36 - | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `WrapperTypeEncode` is not implemented for `::Bar` - | - ::: $CARGO/parity-scale-codec-2.2.0/src/codec.rs - | - | fn encode_to(&self, dest: &mut T) { - | ------ required by this bound in `encode_to` - | - = note: required because of the requirements on the impl of `Encode` for `::Bar` + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + | +1 | / #[frame_support::pallet] +2 | | mod pallet { +3 | | use frame_support::pallet_prelude::{Hooks, DispatchResultWithPostInfo}; +4 | | use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; +... | +16 | | +17 | | #[pallet::call] + | |__________________- required by a bound introduced by this call +... +20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^ the trait `WrapperTypeEncode` is not implemented for `::Bar` + | + = note: required for `::Bar` to implement `Encode` error[E0277]: the trait bound `::Bar: WrapperTypeDecode` is not satisfied - --> $DIR/call_argument_invalid_bound_2.rs:20:36 - | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar` - | - ::: $CARGO/parity-scale-codec-2.2.0/src/codec.rs - | - | fn decode(input: &mut I) -> Result; - | ----- required by this bound in `pallet::_::_parity_scale_codec::Decode::decode` - | - = note: required because of the requirements on the impl of `Decode` for `::Bar` + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:17:12 + | +17 | #[pallet::call] + | ^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar` + | + = note: required for `::Bar` to implement `Decode` diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs index e7f99d7ca4f2d..1cdfb369feadb 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs @@ -1,8 +1,8 @@ #[frame_support::pallet] mod pallet { - use frame_support::pallet_prelude::{Hooks, DispatchResultWithPostInfo}; + use codec::{Decode, Encode}; + use frame_support::pallet_prelude::{DispatchResultWithPostInfo, Hooks}; use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; - use codec::{Encode, Decode}; #[pallet::config] pub trait Config: frame_system::Config {} @@ -13,7 +13,7 @@ mod pallet { #[pallet::hooks] impl Hooks> for Pallet {} - #[derive(Encode, Decode, scale_info::TypeInfo)] + #[derive(Encode, Decode, scale_info::TypeInfo, PartialEq, Clone)] struct Bar; #[pallet::call] @@ -25,5 +25,4 @@ mod pallet { } } -fn main() { -} +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr index 73513907e85f3..6e51bf2dbf862 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr @@ -1,26 +1,14 @@ error[E0277]: `Bar` doesn't implement `std::fmt::Debug` - --> $DIR/call_argument_invalid_bound_3.rs:22:36 + --> tests/pallet_ui/call_argument_invalid_bound_3.rs:22:36 | 22 | pub fn foo(origin: OriginFor, bar: Bar) -> DispatchResultWithPostInfo { | ^^^ `Bar` cannot be formatted using `{:?}` | = help: the trait `std::fmt::Debug` is not implemented for `Bar` - = note: add `#[derive(Debug)]` or manually implement `std::fmt::Debug` - = note: required because of the requirements on the impl of `std::fmt::Debug` for `&Bar` - = note: required for the cast to the object type `dyn std::fmt::Debug` - -error[E0277]: the trait bound `Bar: Clone` is not satisfied - --> $DIR/call_argument_invalid_bound_3.rs:22:36 + = 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 the object type `dyn std::fmt::Debug` +help: consider annotating `Bar` with `#[derive(Debug)]` | -22 | pub fn foo(origin: OriginFor, bar: Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `Clone` is not implemented for `Bar` - | - = note: required by `clone` - -error[E0369]: binary operation `==` cannot be applied to type `&Bar` - --> $DIR/call_argument_invalid_bound_3.rs:22:36 - | -22 | pub fn foo(origin: OriginFor, bar: Bar) -> DispatchResultWithPostInfo { - | ^^^ +17 | #[derive(Debug)] | - = note: an implementation of `std::cmp::PartialEq` might be missing for `&Bar` diff --git a/frame/support/test/tests/pallet_ui/call_conflicting_indices.rs b/frame/support/test/tests/pallet_ui/call_conflicting_indices.rs new file mode 100644 index 0000000000000..395766c7cd331 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_conflicting_indices.rs @@ -0,0 +1,24 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResultWithPostInfo; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + #[pallet::call_index(10)] + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + + #[pallet::weight(0)] + #[pallet::call_index(10)] + pub fn bar(origin: OriginFor) -> DispatchResultWithPostInfo {} + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_conflicting_indices.stderr b/frame/support/test/tests/pallet_ui/call_conflicting_indices.stderr new file mode 100644 index 0000000000000..5d0c90609c2a1 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_conflicting_indices.stderr @@ -0,0 +1,11 @@ +error: Call indices are conflicting: Both functions foo and bar are at index 10 + --> tests/pallet_ui/call_conflicting_indices.rs:15:10 + | +15 | pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + | ^^^ + +error: Call indices are conflicting: Both functions foo and bar are at index 10 + --> tests/pallet_ui/call_conflicting_indices.rs:19:10 + | +19 | pub fn bar(origin: OriginFor) -> DispatchResultWithPostInfo {} + | ^^^ diff --git a/frame/support/test/tests/pallet_ui/call_invalid_attr.rs b/frame/support/test/tests/pallet_ui/call_invalid_attr.rs new file mode 100644 index 0000000000000..118d3c92f360d --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_invalid_attr.rs @@ -0,0 +1,20 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::weird_attr] + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_invalid_attr.stderr b/frame/support/test/tests/pallet_ui/call_invalid_attr.stderr new file mode 100644 index 0000000000000..3f680203a262f --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_invalid_attr.stderr @@ -0,0 +1,5 @@ +error: expected `weight` or `call_index` + --> tests/pallet_ui/call_invalid_attr.rs:14:13 + | +14 | #[pallet::weird_attr] + | ^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/call_invalid_index.rs b/frame/support/test/tests/pallet_ui/call_invalid_index.rs new file mode 100644 index 0000000000000..0b40691ca43a3 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_invalid_index.rs @@ -0,0 +1,21 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + #[pallet::call_index(256)] + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_invalid_index.stderr b/frame/support/test/tests/pallet_ui/call_invalid_index.stderr new file mode 100644 index 0000000000000..1e07a4974bf69 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_invalid_index.stderr @@ -0,0 +1,5 @@ +error: number too large to fit in target type + --> tests/pallet_ui/call_invalid_index.rs:15:24 + | +15 | #[pallet::call_index(256)] + | ^^^ diff --git a/frame/support/test/tests/pallet_ui/call_multiple_call_index.rs b/frame/support/test/tests/pallet_ui/call_multiple_call_index.rs new file mode 100644 index 0000000000000..5753ecd076c80 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_multiple_call_index.rs @@ -0,0 +1,22 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + #[pallet::call_index(1)] + #[pallet::call_index(2)] + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_multiple_call_index.stderr b/frame/support/test/tests/pallet_ui/call_multiple_call_index.stderr new file mode 100644 index 0000000000000..ba22b012745c1 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_multiple_call_index.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::call, too many call_index attributes given + --> tests/pallet_ui/call_multiple_call_index.rs:17:7 + | +17 | pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + | ^^ diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg.rs b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.rs new file mode 100644 index 0000000000000..f044ae6d7878f --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.rs @@ -0,0 +1,33 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + // The struct on which we build all of our Pallet logic. + #[pallet::pallet] + pub struct Pallet(_); + + // Your Pallet's configuration trait, representing custom external types and interfaces. + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::storage] + type MyStorage = StorageValue<_, Vec>; + + // Your Pallet's callable functions. + #[pallet::call] + impl Pallet { + pub fn my_call(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + } + + // Your Pallet's internal functions. + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr new file mode 100644 index 0000000000000..fac7fd77df9ae --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr @@ -0,0 +1,11 @@ +error: Invalid pallet::call, requires weight attribute i.e. `#[pallet::weight($expr)]` + --> tests/pallet_ui/dev_mode_without_arg.rs:24:7 + | +24 | pub fn my_call(_origin: OriginFor) -> DispatchResult { + | ^^ + +error[E0432]: unresolved import `pallet` + --> tests/pallet_ui/dev_mode_without_arg.rs:3:9 + | +3 | pub use pallet::*; + | ^^^^^^ help: a similar path exists: `test_pallet::pallet` diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs new file mode 100644 index 0000000000000..f6efcc3fc3d72 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs @@ -0,0 +1,34 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + // The struct on which we build all of our Pallet logic. + #[pallet::pallet] + pub struct Pallet(_); + + // Your Pallet's configuration trait, representing custom external types and interfaces. + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::storage] + type MyStorage = StorageValue<_, Vec>; + + // Your Pallet's callable functions. + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn my_call(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + } + + // Your Pallet's internal functions. + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr new file mode 100644 index 0000000000000..a5ec31a9bb4e7 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -0,0 +1,17 @@ +error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied + --> tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs:11:12 + | +11 | #[pallet::pallet] + | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Vec` + | + = help: the following other types implement trait `MaxEncodedLen`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and 78 others + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.rs b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.rs new file mode 100644 index 0000000000000..254d65866774f --- /dev/null +++ b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.rs @@ -0,0 +1,19 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::error] + pub enum Error { + CustomError(crate::MyError), + } +} + +#[derive(scale_info::TypeInfo, codec::Encode, codec::Decode)] +enum MyError {} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr new file mode 100644 index 0000000000000..7edb55a62d401 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr @@ -0,0 +1,17 @@ +error[E0277]: the trait bound `MyError: PalletError` is not satisfied + --> tests/pallet_ui/error_does_not_derive_pallet_error.rs:1:1 + | +1 | #[frame_support::pallet] + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PalletError` is not implemented for `MyError` + | + = help: the following other types implement trait `PalletError`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and 36 others + = note: this error originates in the derive macro `frame_support::PalletError` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/pallet_ui/error_no_fieldless.stderr b/frame/support/test/tests/pallet_ui/error_no_fieldless.stderr deleted file mode 100644 index 1d69fbeff9aac..0000000000000 --- a/frame/support/test/tests/pallet_ui/error_no_fieldless.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Invalid pallet::error, unexpected fields, must be `Unit` - --> $DIR/error_no_fieldless.rs:20:5 - | -20 | U8(u8), - | ^^^^ diff --git a/frame/support/test/tests/pallet_ui/event_field_not_member.rs b/frame/support/test/tests/pallet_ui/event_field_not_member.rs index 0ecde4c130878..2b45a971788fb 100644 --- a/frame/support/test/tests/pallet_ui/event_field_not_member.rs +++ b/frame/support/test/tests/pallet_ui/event_field_not_member.rs @@ -6,7 +6,7 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type Bar; - type Event: IsType<::Event> + From>; + type RuntimeEvent: IsType<::RuntimeEvent> + From>; } #[pallet::pallet] diff --git a/frame/support/test/tests/pallet_ui/event_field_not_member.stderr b/frame/support/test/tests/pallet_ui/event_field_not_member.stderr index d48012a6c952d..1161f4a190231 100644 --- a/frame/support/test/tests/pallet_ui/event_field_not_member.stderr +++ b/frame/support/test/tests/pallet_ui/event_field_not_member.stderr @@ -1,28 +1,21 @@ error[E0277]: the trait bound `::Bar: Clone` is not satisfied - --> $DIR/event_field_not_member.rs:23:7 + --> tests/pallet_ui/event_field_not_member.rs:23:7 | 23 | B { b: T::Bar }, | ^ the trait `Clone` is not implemented for `::Bar` - | - = note: required by `clone` error[E0369]: binary operation `==` cannot be applied to type `&::Bar` - --> $DIR/event_field_not_member.rs:23:7 + --> tests/pallet_ui/event_field_not_member.rs:23:7 | 23 | B { b: T::Bar }, | ^ - | -help: consider further restricting this bound - | -22 | pub enum Event { - | ^^^^^^^^^^^^^^^^^^^^^ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` - --> $DIR/event_field_not_member.rs:23:7 + --> tests/pallet_ui/event_field_not_member.rs:23:7 | 23 | 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` - = note: required because of the requirements on the impl of `std::fmt::Debug` for `&::Bar` - = note: required for the cast to the object type `dyn std::fmt::Debug` + = note: required for `&::Bar` to implement `std::fmt::Debug` + = note: required for the cast from `&::Bar` to the object type `dyn std::fmt::Debug` diff --git a/frame/support/test/tests/pallet_ui/event_not_in_trait.stderr b/frame/support/test/tests/pallet_ui/event_not_in_trait.stderr index dd96c700ce7e5..2eda72eb5f72f 100644 --- a/frame/support/test/tests/pallet_ui/event_not_in_trait.stderr +++ b/frame/support/test/tests/pallet_ui/event_not_in_trait.stderr @@ -1,7 +1,7 @@ -error: Invalid usage of Event, `Config` contains no associated type `Event`, but enum `Event` is declared (in use of `#[pallet::event]`). An Event associated type must be declare on trait `Config`. +error: Invalid usage of RuntimeEvent, `Config` contains no associated type `RuntimeEvent`, but enum `Event` is declared (in use of `#[pallet::event]`). An RuntimeEvent associated type must be declare on trait `Config`. --> $DIR/event_not_in_trait.rs:1:1 | 1 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/pallet_ui/event_type_invalid_bound.rs b/frame/support/test/tests/pallet_ui/event_type_invalid_bound.rs index fa3bf04d3530d..a02cc9b9de883 100644 --- a/frame/support/test/tests/pallet_ui/event_type_invalid_bound.rs +++ b/frame/support/test/tests/pallet_ui/event_type_invalid_bound.rs @@ -6,7 +6,7 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type Bar; - type Event; + type RuntimeEvent; } #[pallet::pallet] diff --git a/frame/support/test/tests/pallet_ui/event_type_invalid_bound.stderr b/frame/support/test/tests/pallet_ui/event_type_invalid_bound.stderr index 1f58a37576d0d..d54149d719a3b 100644 --- a/frame/support/test/tests/pallet_ui/event_type_invalid_bound.stderr +++ b/frame/support/test/tests/pallet_ui/event_type_invalid_bound.stderr @@ -1,5 +1,5 @@ -error: Invalid `type Event`, associated type `Event` is reserved and must bound: `IsType<::Event>` +error: Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must bound: `IsType<::RuntimeEvent>` --> $DIR/event_type_invalid_bound.rs:9:3 | -9 | type Event; +9 | type RuntimeEvent; | ^^^^ diff --git a/frame/support/test/tests/pallet_ui/event_type_invalid_bound_2.rs b/frame/support/test/tests/pallet_ui/event_type_invalid_bound_2.rs index 564a539b89f57..99df89d67278c 100644 --- a/frame/support/test/tests/pallet_ui/event_type_invalid_bound_2.rs +++ b/frame/support/test/tests/pallet_ui/event_type_invalid_bound_2.rs @@ -6,7 +6,7 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type Bar; - type Event: IsType<::Event>; + type RuntimeEvent: IsType<::RuntimeEvent>; } #[pallet::pallet] diff --git a/frame/support/test/tests/pallet_ui/event_type_invalid_bound_2.stderr b/frame/support/test/tests/pallet_ui/event_type_invalid_bound_2.stderr index 8b8946f3b25eb..ea8b2ff000ceb 100644 --- a/frame/support/test/tests/pallet_ui/event_type_invalid_bound_2.stderr +++ b/frame/support/test/tests/pallet_ui/event_type_invalid_bound_2.stderr @@ -1,5 +1,5 @@ -error: Invalid `type Event`, associated type `Event` is reserved and must bound: `From` or `From>` or `From>` +error: Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must bound: `From` or `From>` or `From>` --> $DIR/event_type_invalid_bound_2.rs:9:3 | -9 | type Event: IsType<::Event>; +9 | type RuntimeEvent: IsType<::RuntimeEvent>; | ^^^^ diff --git a/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr b/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr index 4bc3cfdcbf9b7..80903928585b4 100644 --- a/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr +++ b/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr @@ -1,10 +1,15 @@ error[E0277]: the trait bound `pallet::GenesisConfig: std::default::Default` is not satisfied - --> $DIR/genesis_default_not_satisfied.rs:22:18 + --> tests/pallet_ui/genesis_default_not_satisfied.rs:22:18 | 22 | impl GenesisBuild for GenesisConfig {} | ^^^^^^^^^^^^^^^ the trait `std::default::Default` is not implemented for `pallet::GenesisConfig` | - ::: $WORKSPACE/frame/support/src/traits/hooks.rs +note: required by a bound in `GenesisBuild` + --> $WORKSPACE/frame/support/src/traits/hooks.rs + | + | pub trait GenesisBuild: Default + sp_runtime::traits::MaybeSerializeDeserialize { + | ^^^^^^^ required by this bound in `GenesisBuild` +help: consider annotating `pallet::GenesisConfig` with `#[derive(Default)]` + | +19 | #[derive(Default)] | - | pub trait GenesisBuild: Default + MaybeSerializeDeserialize { - | ------- required by this bound in `GenesisBuild` diff --git a/frame/support/test/tests/pallet_ui/genesis_invalid_generic.stderr b/frame/support/test/tests/pallet_ui/genesis_invalid_generic.stderr index f451f7b16aee5..f57b4a61c80c5 100644 --- a/frame/support/test/tests/pallet_ui/genesis_invalid_generic.stderr +++ b/frame/support/test/tests/pallet_ui/genesis_invalid_generic.stderr @@ -10,4 +10,4 @@ error: expected `<` 1 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr b/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr index 3d7303fafdcf5..ff52a094d6f8d 100644 --- a/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr +++ b/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr @@ -1,15 +1,15 @@ error[E0107]: missing generics for trait `Hooks` - --> $DIR/hooks_invalid_item.rs:12:18 + --> tests/pallet_ui/hooks_invalid_item.rs:12:18 | 12 | impl Hooks for Pallet {} - | ^^^^^ expected 1 type argument + | ^^^^^ expected 1 generic argument | -note: trait defined here, with 1 type parameter: `BlockNumber` - --> $DIR/hooks.rs:214:11 +note: trait defined here, with 1 generic parameter: `BlockNumber` + --> $WORKSPACE/frame/support/src/traits/hooks.rs | -214 | pub trait Hooks { + | pub trait Hooks { | ^^^^^ ----------- -help: use angle brackets to add missing type argument +help: add missing generic argument | 12 | impl Hooks for Pallet {} - | ^^^^^^^^^^^^^ + | ~~~~~~~~~~~~~~~~~~ diff --git a/frame/support/test/tests/pallet_ui/pallet_invalid_arg.rs b/frame/support/test/tests/pallet_ui/pallet_invalid_arg.rs new file mode 100644 index 0000000000000..1fc42f6511cfa --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_invalid_arg.rs @@ -0,0 +1,4 @@ +#[frame_support::pallet(foo)] +pub mod pallet {} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/pallet_invalid_arg.stderr b/frame/support/test/tests/pallet_ui/pallet_invalid_arg.stderr new file mode 100644 index 0000000000000..234dc07f2ece3 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_invalid_arg.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet macro call: unexpected attribute. Macro call must be bare, such as `#[frame_support::pallet]` or `#[pallet]`, or must specify the `dev_mode` attribute, such as `#[frame_support::pallet(dev_mode)]` or #[pallet(dev_mode)]. + --> tests/pallet_ui/pallet_invalid_arg.rs:1:25 + | +1 | #[frame_support::pallet(foo)] + | ^^^ diff --git a/frame/support/test/tests/pallet_ui/pass/dev_mode_valid.rs b/frame/support/test/tests/pallet_ui/pass/dev_mode_valid.rs new file mode 100644 index 0000000000000..97e0d585d0ce9 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/dev_mode_valid.rs @@ -0,0 +1,35 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + // The struct on which we build all of our Pallet logic. + #[pallet::pallet] + pub struct Pallet(_); + + // Your Pallet's configuration trait, representing custom external types and interfaces. + #[pallet::config] + pub trait Config: frame_system::Config {} + + // The MEL requirement for bounded pallets is skipped by `dev_mode`. + #[pallet::storage] + type MyStorage = StorageValue<_, Vec>; + + // Your Pallet's callable functions. + #[pallet::call] + impl Pallet { + // No need to define a `weight` attribute here because of `dev_mode`. + pub fn my_call(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + } + + // Your Pallet's internal functions. + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/pass/error_nested_types.rs b/frame/support/test/tests/pallet_ui/pass/error_nested_types.rs new file mode 100644 index 0000000000000..1b6f584af23b9 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/error_nested_types.rs @@ -0,0 +1,41 @@ +use codec::{Decode, Encode}; +use frame_support::PalletError; + +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::error] + pub enum Error { + CustomError(crate::MyError), + } +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub enum MyError { + Foo, + Bar, + Baz(NestedError), + Struct(MyStruct), + Wrapper(Wrapper), +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub enum NestedError { + Quux +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub struct MyStruct { + field: u8, +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub struct Wrapper(bool); + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs index 30b6d651f3b89..fe4682c401fa0 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs @@ -7,6 +7,7 @@ mod pallet { pub trait Config: frame_system::Config {} #[pallet::pallet] + #[pallet::without_storage_info] pub struct Pallet(core::marker::PhantomData); #[pallet::hooks] diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 239de4dba949b..999d8585c221a 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -1,77 +1,130 @@ +error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:10:12 + | +10 | #[pallet::without_storage_info] + | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes + = note: required for `Bar` to implement `Decode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` + +error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:10:12 + | +10 | #[pallet::without_storage_info] + | ^^^^^^^^^^^^^^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` + | + = help: the following other types implement trait `EncodeLike`: + <&&T as EncodeLike> + <&T as EncodeLike> + <&T as EncodeLike> + <&[(K, V)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[T] as EncodeLike>> + and 279 others + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` + +error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:10:12 + | +10 | #[pallet::without_storage_info] + | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | + = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc + Box + Cow<'a, T> + Rc + Vec + bytes::bytes::Bytes + and 3 others + = note: required for `Bar` to implement `Encode` + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` + error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen.rs:20:12 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 | -20 | #[pallet::storage] +21 | #[pallet::storage] | ^^^^^^^ the trait `TypeInfo` is not implemented for `Bar` | - = note: required because of the requirements on the impl of `StaticTypeInfo` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `build_metadata` + = help: the following other types implement trait `TypeInfo`: + &T + &mut T + () + (A, B) + (A, B, C) + (A, B, C, D) + (A, B, C, D, E) + (A, B, C, D, E, F) + and 162 others + = note: required for `Bar` to implement `StaticTypeInfo` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen.rs:20:12 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 | -20 | #[pallet::storage] +21 | #[pallet::storage] | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` | - = note: required because of the requirements on the impl of `Decode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `build_metadata` + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes + = note: required for `Bar` to implement `Decode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen.rs:20:12 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 | -20 | #[pallet::storage] +21 | #[pallet::storage] | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` | - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `build_metadata` + = help: the following other types implement trait `EncodeLike`: + <&&T as EncodeLike> + <&T as EncodeLike> + <&T as EncodeLike> + <&[(K, V)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[T] as EncodeLike>> + and 279 others + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen.rs:20:12 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 | -20 | #[pallet::storage] +21 | #[pallet::storage] | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` | - = note: required because of the requirements on the impl of `Encode` for `Bar` - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `build_metadata` - -error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen.rs:9:12 - | -9 | #[pallet::pallet] - | ^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `Decode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `partial_storage_info` - -error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen.rs:9:12 - | -9 | #[pallet::pallet] - | ^^^^^^ the trait `EncodeLike` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `partial_storage_info` - -error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen.rs:9:12 - | -9 | #[pallet::pallet] - | ^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `Encode` for `Bar` - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `partial_storage_info` + = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc + Box + Cow<'a, T> + Rc + Vec + bytes::bytes::Bytes + and 3 others + = note: required for `Bar` to implement `Encode` + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs index ddb19121660da..82512a89fb15a 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs @@ -7,6 +7,7 @@ mod pallet { pub trait Config: frame_system::Config {} #[pallet::pallet] + #[pallet::without_storage_info] pub struct Pallet(core::marker::PhantomData); #[pallet::hooks] diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index a5bf32a0ef2d2..e2870ffb9e86f 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -1,77 +1,130 @@ +error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:10:12 + | +10 | #[pallet::without_storage_info] + | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes + = note: required for `Bar` to implement `Decode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` + +error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:10:12 + | +10 | #[pallet::without_storage_info] + | ^^^^^^^^^^^^^^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` + | + = help: the following other types implement trait `EncodeLike`: + <&&T as EncodeLike> + <&T as EncodeLike> + <&T as EncodeLike> + <&[(K, V)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[T] as EncodeLike>> + and 279 others + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` + +error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:10:12 + | +10 | #[pallet::without_storage_info] + | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | + = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc + Box + Cow<'a, T> + Rc + Vec + bytes::bytes::Bytes + and 3 others + = note: required for `Bar` to implement `Encode` + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` + error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:20:12 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 | -20 | #[pallet::storage] +21 | #[pallet::storage] | ^^^^^^^ the trait `TypeInfo` is not implemented for `Bar` | - = note: required because of the requirements on the impl of `StaticTypeInfo` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `build_metadata` + = help: the following other types implement trait `TypeInfo`: + &T + &mut T + () + (A, B) + (A, B, C) + (A, B, C, D) + (A, B, C, D, E) + (A, B, C, D, E, F) + and 162 others + = note: required for `Bar` to implement `StaticTypeInfo` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:20:12 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 | -20 | #[pallet::storage] +21 | #[pallet::storage] | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` | - = note: required because of the requirements on the impl of `Decode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `build_metadata` + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes + = note: required for `Bar` to implement `Decode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:20:12 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 | -20 | #[pallet::storage] +21 | #[pallet::storage] | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` | - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `build_metadata` + = help: the following other types implement trait `EncodeLike`: + <&&T as EncodeLike> + <&T as EncodeLike> + <&T as EncodeLike> + <&[(K, V)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[T] as EncodeLike>> + and 279 others + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:20:12 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 | -20 | #[pallet::storage] +21 | #[pallet::storage] | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` | - = note: required because of the requirements on the impl of `Encode` for `Bar` - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `build_metadata` - -error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:9:12 - | -9 | #[pallet::pallet] - | ^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `Decode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `partial_storage_info` - -error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:9:12 - | -9 | #[pallet::pallet] - | ^^^^^^ the trait `EncodeLike` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `partial_storage_info` - -error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied - --> $DIR/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:9:12 - | -9 | #[pallet::pallet] - | ^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `Encode` for `Bar` - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `partial_storage_info` + = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc + Box + Cow<'a, T> + Rc + Vec + bytes::bytes::Bytes + and 3 others + = note: required for `Bar` to implement `Encode` + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.rs b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.rs index 76e3566100640..4d43e3a17a9ec 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.rs +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.rs @@ -7,7 +7,6 @@ mod pallet { pub trait Config: frame_system::Config {} #[pallet::pallet] - #[pallet::generate_storage_info] pub struct Pallet(core::marker::PhantomData); #[pallet::hooks] diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index ad415911bc933..cce9fa70b3da5 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -1,8 +1,17 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied - --> $DIR/storage_info_unsatisfied.rs:10:12 - | -10 | #[pallet::generate_storage_info] - | ^^^^^^^^^^^^^^^^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` - = note: required by `storage_info` + --> tests/pallet_ui/storage_info_unsatisfied.rs:9:12 + | +9 | #[pallet::pallet] + | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` + | + = help: the following other types implement trait `MaxEncodedLen`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and 78 others + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.rs b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.rs index c5d773d716116..dd10bc0723fe1 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.rs +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.rs @@ -1,13 +1,15 @@ #[frame_support::pallet] mod pallet { - use frame_support::pallet_prelude::{Hooks, StorageNMap, Twox64Concat, NMapKey}; + use frame_support::{ + pallet_prelude::{Hooks, Twox64Concat}, + storage::types::{StorageNMap, Key}, + }; use frame_system::pallet_prelude::BlockNumberFor; #[pallet::config] pub trait Config: frame_system::Config {} #[pallet::pallet] - #[pallet::generate_storage_info] pub struct Pallet(core::marker::PhantomData); #[pallet::hooks] @@ -20,8 +22,7 @@ mod pallet { struct Bar; #[pallet::storage] - type Foo = StorageNMap<_, NMapKey, u32>; + type Foo = StorageNMap<_, Key, u32>; } -fn main() { -} +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 6c92423c6a7fe..877485dda2084 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -1,9 +1,18 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied - --> $DIR/storage_info_unsatisfied_nmap.rs:10:12 + --> tests/pallet_ui/storage_info_unsatisfied_nmap.rs:12:12 | -10 | #[pallet::generate_storage_info] - | ^^^^^^^^^^^^^^^^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` +12 | #[pallet::pallet] + | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` | - = note: required because of the requirements on the impl of `KeyGeneratorMaxEncodedLen` for `Key` - = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` - = note: required by `storage_info` + = help: the following other types implement trait `MaxEncodedLen`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and 78 others + = note: required for `Key` to implement `KeyGeneratorMaxEncodedLen` + = note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_invalid_attribute.stderr b/frame/support/test/tests/pallet_ui/storage_invalid_attribute.stderr index bf93d99cf56bd..80c6526bbf888 100644 --- a/frame/support/test/tests/pallet_ui/storage_invalid_attribute.stderr +++ b/frame/support/test/tests/pallet_ui/storage_invalid_attribute.stderr @@ -1,4 +1,4 @@ -error: expected `getter` or `storage_prefix` +error: expected one of: `getter`, `storage_prefix`, `unbounded`, `whitelist_storage` --> $DIR/storage_invalid_attribute.rs:16:12 | 16 | #[pallet::generate_store(pub trait Store)] diff --git a/frame/support/test/tests/pallet_ui/storage_multiple_getters.stderr b/frame/support/test/tests/pallet_ui/storage_multiple_getters.stderr index 188eed3cb0d17..40f57f16e0df5 100644 --- a/frame/support/test/tests/pallet_ui/storage_multiple_getters.stderr +++ b/frame/support/test/tests/pallet_ui/storage_multiple_getters.stderr @@ -1,4 +1,4 @@ -error: Invalid pallet::storage, multiple argument pallet::getter found +error: Invalid attribute: Duplicate attribute --> $DIR/storage_multiple_getters.rs:20:3 | 20 | #[pallet::getter(fn foo_error)] diff --git a/frame/support/test/tests/pallet_ui/storage_multiple_renames.stderr b/frame/support/test/tests/pallet_ui/storage_multiple_renames.stderr index 9288d131d95af..52cb7e85adf21 100644 --- a/frame/support/test/tests/pallet_ui/storage_multiple_renames.stderr +++ b/frame/support/test/tests/pallet_ui/storage_multiple_renames.stderr @@ -1,4 +1,4 @@ -error: Invalid pallet::storage, multiple argument pallet::storage_prefix found +error: Invalid attribute: Duplicate attribute --> $DIR/storage_multiple_renames.rs:20:3 | 20 | #[pallet::storage_prefix = "Baz"] diff --git a/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr b/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr index 4fd59183282d0..223e9cfa3e9f8 100644 --- a/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr +++ b/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr @@ -1,4 +1,4 @@ -error: Invalid pallet::storage, expected ident: `StorageValue` or `StorageMap` or `StorageDoubleMap` or `StorageNMap` in order to expand metadata, found `u8`. +error: Invalid pallet::storage, expected ident: `StorageValue` or `StorageMap` or `CountedStorageMap` or `StorageDoubleMap` or `StorageNMap` in order to expand metadata, found `u8`. --> $DIR/storage_not_storage_type.rs:19:16 | 19 | type Foo = u8; diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.rs b/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.rs new file mode 100644 index 0000000000000..a051cc087db58 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.rs @@ -0,0 +1,21 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::error] + pub enum Error { + NonExistentValue, + } + + #[pallet::storage] + type Foo = StorageValue<_, u8, ResultQuery>; +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr b/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr new file mode 100644 index 0000000000000..98265462bbdfb --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr @@ -0,0 +1,15 @@ +error[E0107]: missing generics for enum `pallet::Error` + --> tests/pallet_ui/storage_result_query_missing_generics.rs:17:56 + | +17 | type Foo = StorageValue<_, u8, ResultQuery>; + | ^^^^^ expected 1 generic argument + | +note: enum defined here, with 1 generic parameter: `T` + --> tests/pallet_ui/storage_result_query_missing_generics.rs:12:11 + | +12 | pub enum Error { + | ^^^^^ - +help: add missing generic argument + | +17 | type Foo = StorageValue<_, u8, ResultQuery::NonExistentValue>>; + | ~~~~~~~~ diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.rs b/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.rs new file mode 100644 index 0000000000000..9e0da4b62128d --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.rs @@ -0,0 +1,23 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::error] + pub enum Error { + NonExistentValue, + SomeOtherError, + } + + #[pallet::storage] + type Foo = StorageValue<_, u8, ResultQuery::NonExistentValue, SomeOtherError>>; +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.stderr b/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.stderr new file mode 100644 index 0000000000000..4be2a36eb89e1 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::storage, unexpected number of generic arguments for ResultQuery, expected 1 type argument, found 2 + --> tests/pallet_ui/storage_result_query_multiple_type_args.rs:19:56 + | +19 | type Foo = StorageValue<_, u8, ResultQuery::NonExistentValue, SomeOtherError>>; + | ^^^^^ diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.rs b/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.rs new file mode 100644 index 0000000000000..102a2261f8333 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.rs @@ -0,0 +1,16 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::storage] + type Foo = StorageValue<_, u8, ResultQuery>; +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.stderr b/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.stderr new file mode 100644 index 0000000000000..77a7972a5b5cf --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::storage, unexpected number of path segments for the generics in ResultQuery, expected a path with at least 2 segments, found 1 + --> tests/pallet_ui/storage_result_query_no_defined_pallet_error.rs:12:56 + | +12 | type Foo = StorageValue<_, u8, ResultQuery>; + | ^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/error_no_fieldless.rs b/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.rs similarity index 61% rename from frame/support/test/tests/pallet_ui/error_no_fieldless.rs rename to frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.rs index c9d444d6f90dd..f30dc3b6a3cc7 100644 --- a/frame/support/test/tests/pallet_ui/error_no_fieldless.rs +++ b/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.rs @@ -1,6 +1,6 @@ #[frame_support::pallet] mod pallet { - use frame_support::pallet_prelude::Hooks; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::BlockNumberFor; #[pallet::config] @@ -9,16 +9,13 @@ mod pallet { #[pallet::pallet] pub struct Pallet(core::marker::PhantomData); - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::call] - impl Pallet {} - #[pallet::error] pub enum Error { - U8(u8), + NonExistentValue, } + + #[pallet::storage] + type Foo = StorageValue<_, u8, ResultQuery(NonExistentValue)>; } fn main() { diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr b/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr new file mode 100644 index 0000000000000..caffd846f272a --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::storage, unexpected generic args for ResultQuery, expected angle-bracketed arguments, found `(NonExistentValue)` + --> tests/pallet_ui/storage_result_query_parenthesized_generics.rs:18:55 + | +18 | type Foo = StorageValue<_, u8, ResultQuery(NonExistentValue)>; + | ^^^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.rs b/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.rs new file mode 100644 index 0000000000000..a5065398b3970 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.rs @@ -0,0 +1,22 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::error] + pub enum Error { + NonExistentValue, + } + + #[pallet::storage] + type Foo = StorageValue<_, u8, ResultQuery<'static>>; +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.stderr b/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.stderr new file mode 100644 index 0000000000000..9f333ae28e6aa --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::storage, unexpected generic argument kind, expected a type path to a `PalletError` enum variant, found `'static` + --> tests/pallet_ui/storage_result_query_wrong_generic_kind.rs:18:56 + | +18 | type Foo = StorageValue<_, u8, ResultQuery<'static>>; + | ^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/type_value_forgotten_where_clause.stderr b/frame/support/test/tests/pallet_ui/type_value_forgotten_where_clause.stderr index 85d7342b253d4..6288dcd534b60 100644 --- a/frame/support/test/tests/pallet_ui/type_value_forgotten_where_clause.stderr +++ b/frame/support/test/tests/pallet_ui/type_value_forgotten_where_clause.stderr @@ -1,47 +1,53 @@ error[E0277]: the trait bound `::AccountId: From` is not satisfied --> $DIR/type_value_forgotten_where_clause.rs:24:34 | -7 | pub trait Config: frame_system::Config - | ------ required by a bound in this -8 | where ::AccountId: From - | --------- required by this bound in `pallet::Config` -... 24 | #[pallet::type_value] fn Foo() -> u32 { 3u32 } | ^^^^^^ the trait `From` is not implemented for `::AccountId` | +note: required by a bound in `pallet::Config` + --> $DIR/type_value_forgotten_where_clause.rs:8:51 + | +7 | pub trait Config: frame_system::Config + | ------ required by a bound in this +8 | where ::AccountId: From + | ^^^^^^^^^ required by this bound in `pallet::Config` help: consider further restricting the associated type | 24 | #[pallet::type_value] fn Foo() -> u32 where ::AccountId: From { 3u32 } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ error[E0277]: the trait bound `::AccountId: From` is not satisfied --> $DIR/type_value_forgotten_where_clause.rs:24:12 | -7 | pub trait Config: frame_system::Config - | ------ required by a bound in this -8 | where ::AccountId: From - | --------- required by this bound in `pallet::Config` -... 24 | #[pallet::type_value] fn Foo() -> u32 { 3u32 } | ^^^^^^^^^^ the trait `From` is not implemented for `::AccountId` | +note: required by a bound in `pallet::Config` + --> $DIR/type_value_forgotten_where_clause.rs:8:51 + | +7 | pub trait Config: frame_system::Config + | ------ required by a bound in this +8 | where ::AccountId: From + | ^^^^^^^^^ required by this bound in `pallet::Config` help: consider further restricting the associated type | 24 | #[pallet::type_value where ::AccountId: From] fn Foo() -> u32 { 3u32 } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ error[E0277]: the trait bound `::AccountId: From` is not satisfied --> $DIR/type_value_forgotten_where_clause.rs:24:12 | -7 | pub trait Config: frame_system::Config - | ------ required by a bound in this -8 | where ::AccountId: From - | --------- required by this bound in `pallet::Config` -... 24 | #[pallet::type_value] fn Foo() -> u32 { 3u32 } | ^^^^^^^^^^ the trait `From` is not implemented for `::AccountId` | +note: required by a bound in `pallet::Config` + --> $DIR/type_value_forgotten_where_clause.rs:8:51 + | +7 | pub trait Config: frame_system::Config + | ------ required by a bound in this +8 | where ::AccountId: From + | ^^^^^^^^^ required by this bound in `pallet::Config` help: consider further restricting the associated type | 24 | #[pallet::type_value] fn Foo() -> u32 where ::AccountId: From { 3u32 } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/frame/support/test/tests/pallet_with_name_trait_is_valid.rs b/frame/support/test/tests/pallet_with_name_trait_is_valid.rs index 1c47d13a619f2..0066420566fe8 100644 --- a/frame/support/test/tests/pallet_with_name_trait_is_valid.rs +++ b/frame/support/test/tests/pallet_with_name_trait_is_valid.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ pub trait Trait: frame_system::Config { type Balance: frame_support::dispatch::Parameter; /// The overarching event type. - type Event: From> + Into<::Event>; + type RuntimeEvent: From> + Into<::RuntimeEvent>; } frame_support::decl_storage! { @@ -43,7 +43,7 @@ frame_support::decl_error!( ); frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin { + pub struct Module for enum Call where origin: T::RuntimeOrigin { fn deposit_event() = default; type Error = Error; const Foo: u32 = u32::MAX; @@ -54,7 +54,7 @@ frame_support::decl_module! { } fn on_initialize(_n: T::BlockNumber) -> frame_support::weights::Weight { - 0 + frame_support::weights::Weight::zero() } } } @@ -97,7 +97,7 @@ impl frame_support::inherent::ProvideInherent for Module { mod tests { use crate as pallet_test; - use frame_support::parameter_types; + use frame_support::traits::ConstU64; type SignedExtra = ( frame_system::CheckEra, @@ -108,7 +108,7 @@ mod tests { type TestHeader = sp_runtime::generic::Header; type TestUncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic< ::AccountId, - ::Call, + ::RuntimeCall, (), SignedExtra, >; @@ -124,23 +124,19 @@ mod tests { } ); - parameter_types! { - pub const BlockHashCount: u64 = 250; - } - impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = sp_core::H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; type Header = TestHeader; - type Event = (); - type BlockHashCount = BlockHashCount; + type RuntimeEvent = (); + type BlockHashCount = ConstU64<250>; type DbWeight = (); type BlockWeights = (); type BlockLength = (); @@ -152,10 +148,11 @@ mod tests { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_test::Trait for Runtime { type Balance = u32; - type Event = (); + type RuntimeEvent = (); } } diff --git a/frame/support/test/tests/reserved_keyword/on_initialize.rs b/frame/support/test/tests/reserved_keyword/on_initialize.rs deleted file mode 100644 index 72d53abfb1034..0000000000000 --- a/frame/support/test/tests/reserved_keyword/on_initialize.rs +++ /dev/null @@ -1,30 +0,0 @@ -macro_rules! reserved { - ($($reserved:ident)*) => { - $( - mod $reserved { - pub use frame_support::dispatch; - - pub trait Config: frame_support_test::Config {} - - pub mod system { - use frame_support::dispatch; - - pub fn ensure_root(_: R) -> dispatch::DispatchResult { - Ok(()) - } - } - - frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test { - #[weight = 0] - fn $reserved(_origin) -> dispatch::DispatchResult { unreachable!() } - } - } - } - )* - } -} - -reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); - -fn main() {} diff --git a/frame/support/test/tests/reserved_keyword/on_initialize.stderr b/frame/support/test/tests/reserved_keyword/on_initialize.stderr deleted file mode 100644 index 3df392dee9005..0000000000000 --- a/frame/support/test/tests/reserved_keyword/on_initialize.stderr +++ /dev/null @@ -1,39 +0,0 @@ -error: Invalid call fn name: `on_finalize`, name is reserved and doesn't match expected signature, please refer to `decl_module!` documentation to see the appropriate usage, or rename it to an unreserved keyword. - --> $DIR/on_initialize.rs:28:1 - | -28 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error: Invalid call fn name: `on_initialize`, name is reserved and doesn't match expected signature, please refer to `decl_module!` documentation to see the appropriate usage, or rename it to an unreserved keyword. - --> $DIR/on_initialize.rs:28:1 - | -28 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error: Invalid call fn name: `on_runtime_upgrade`, name is reserved and doesn't match expected signature, please refer to `decl_module!` documentation to see the appropriate usage, or rename it to an unreserved keyword. - --> $DIR/on_initialize.rs:28:1 - | -28 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error: Invalid call fn name: `offchain_worker`, name is reserved and doesn't match expected signature, please refer to `decl_module!` documentation to see the appropriate usage, or rename it to an unreserved keyword. - --> $DIR/on_initialize.rs:28:1 - | -28 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error: Invalid call fn name: `deposit_event`, name is reserved and doesn't match expected signature, please refer to `decl_module!` documentation to see the appropriate usage, or rename it to an unreserved keyword. - --> $DIR/on_initialize.rs:28:1 - | -28 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/reserved_keyword.rs b/frame/support/test/tests/storage_alias_ui.rs similarity index 74% rename from frame/support/test/tests/reserved_keyword.rs rename to frame/support/test/tests/storage_alias_ui.rs index d29b0477c3836..d45d071578dab 100644 --- a/frame/support/test/tests/reserved_keyword.rs +++ b/frame/support/test/tests/storage_alias_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,11 +16,17 @@ // limitations under the License. #[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] #[test] -fn reserved_keyword() { +fn storage_alias_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); let t = trybuild::TestCases::new(); - t.compile_fail("tests/reserved_keyword/*.rs"); + t.compile_fail("tests/storage_alias_ui/*.rs"); } diff --git a/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.rs b/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.rs new file mode 100644 index 0000000000000..4ed9d5adfec77 --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.rs @@ -0,0 +1,4 @@ +#[frame_support::storage_alias] +type Ident = StorageValue; + +fn main() {} diff --git a/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.stderr b/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.stderr new file mode 100644 index 0000000000000..726efed400715 --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.stderr @@ -0,0 +1,5 @@ +error: If there are no generics, the prefix is only allowed to be an identifier. + --> tests/storage_alias_ui/checks_for_valid_storage_type.rs:2:27 + | +2 | type Ident = StorageValue; + | ^^^^^^^^^^^^ diff --git a/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.rs b/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.rs new file mode 100644 index 0000000000000..59d8004bbe620 --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.rs @@ -0,0 +1,4 @@ +#[frame_support::storage_alias] +type Ident = CustomStorage; + +fn main() {} diff --git a/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr b/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr new file mode 100644 index 0000000000000..3aa517ecfa314 --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr @@ -0,0 +1,5 @@ +error: expected one of: `StorageValue`, `StorageMap`, `StorageDoubleMap`, `StorageNMap` + --> tests/storage_alias_ui/forbid_underscore_as_prefix.rs:2:14 + | +2 | type Ident = CustomStorage; + | ^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.rs b/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.rs new file mode 100644 index 0000000000000..79328268dc925 --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.rs @@ -0,0 +1,4 @@ +#[frame_support::storage_alias] +type NoUnderscore = StorageValue<_, u32>; + +fn main() {} diff --git a/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.stderr b/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.stderr new file mode 100644 index 0000000000000..abb7bf2518f4f --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.stderr @@ -0,0 +1,5 @@ +error: `_` is not allowed as prefix by `storage_alias`. + --> tests/storage_alias_ui/prefix_must_be_an_ident.rs:2:34 + | +2 | type NoUnderscore = StorageValue<_, u32>; + | ^ diff --git a/frame/support/test/tests/storage_layers.rs b/frame/support/test/tests/storage_layers.rs new file mode 100644 index 0000000000000..cff81c0bea2ed --- /dev/null +++ b/frame/support/test/tests/storage_layers.rs @@ -0,0 +1,286 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 frame_support::{ + assert_noop, assert_ok, dispatch::DispatchResult, pallet_prelude::ConstU32, + storage::with_storage_layer, +}; +use pallet::*; +use sp_io::TestExternalities; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{ensure, pallet_prelude::*}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::storage] + pub type Value = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + pub type Map = StorageMap<_, Blake2_128Concat, u32, u32, ValueQuery>; + + #[pallet::error] + pub enum Error { + Revert, + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(1)] + pub fn set_value(_origin: OriginFor, value: u32) -> DispatchResult { + Value::::put(value); + ensure!(value != 1, Error::::Revert); + Ok(()) + } + } +} + +pub mod decl_pallet { + pub trait Config: frame_system::Config {} + + frame_support::decl_module! { + pub struct Module for enum Call where origin: T::RuntimeOrigin { + #[weight = 0] + pub fn set_value(_origin, value: u32) { + DeclValue::put(value); + frame_support::ensure!(value != 1, "Revert!"); + } + } + } + + frame_support::decl_storage! { + trait Store for Module as StorageTransactions { + pub DeclValue: u32; + } + } +} + +pub type BlockNumber = u64; +pub type Index = u64; +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +impl frame_system::Config for Runtime { + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU32<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet::Config for Runtime {} + +impl decl_pallet::Config for Runtime {} + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system, + MyPallet: pallet, + DeclPallet: decl_pallet::{Call, Storage}, + } +); + +#[test] +fn storage_layer_basic_commit() { + TestExternalities::default().execute_with(|| { + assert_eq!(Value::::get(), 0); + assert!(!Map::::contains_key(0)); + + assert_ok!(with_storage_layer(|| -> DispatchResult { + Value::::set(99); + Map::::insert(0, 99); + assert_eq!(Value::::get(), 99); + assert_eq!(Map::::get(0), 99); + Ok(()) + })); + + assert_eq!(Value::::get(), 99); + assert_eq!(Map::::get(0), 99); + }); +} + +#[test] +fn storage_layer_basic_rollback() { + TestExternalities::default().execute_with(|| { + assert_eq!(Value::::get(), 0); + assert_eq!(Map::::get(0), 0); + + assert_noop!( + with_storage_layer(|| -> DispatchResult { + Value::::set(99); + Map::::insert(0, 99); + assert_eq!(Value::::get(), 99); + assert_eq!(Map::::get(0), 99); + Err("revert".into()) + }), + "revert" + ); + + assert_eq!(Value::::get(), 0); + assert_eq!(Map::::get(0), 0); + }); +} + +#[test] +fn storage_layer_rollback_then_commit() { + TestExternalities::default().execute_with(|| { + Value::::set(1); + Map::::insert(1, 1); + + assert_ok!(with_storage_layer(|| -> DispatchResult { + Value::::set(2); + Map::::insert(1, 2); + Map::::insert(2, 2); + + assert_noop!( + with_storage_layer(|| -> DispatchResult { + Value::::set(3); + Map::::insert(1, 3); + Map::::insert(2, 3); + Map::::insert(3, 3); + + assert_eq!(Value::::get(), 3); + assert_eq!(Map::::get(1), 3); + assert_eq!(Map::::get(2), 3); + assert_eq!(Map::::get(3), 3); + + Err("revert".into()) + }), + "revert" + ); + + assert_eq!(Value::::get(), 2); + assert_eq!(Map::::get(1), 2); + assert_eq!(Map::::get(2), 2); + assert_eq!(Map::::get(3), 0); + + Ok(()) + })); + + assert_eq!(Value::::get(), 2); + assert_eq!(Map::::get(1), 2); + assert_eq!(Map::::get(2), 2); + assert_eq!(Map::::get(3), 0); + }); +} + +#[test] +fn storage_layer_commit_then_rollback() { + TestExternalities::default().execute_with(|| { + Value::::set(1); + Map::::insert(1, 1); + + assert_noop!( + with_storage_layer(|| -> DispatchResult { + Value::::set(2); + Map::::insert(1, 2); + Map::::insert(2, 2); + + assert_ok!(with_storage_layer(|| -> DispatchResult { + Value::::set(3); + Map::::insert(1, 3); + Map::::insert(2, 3); + Map::::insert(3, 3); + + assert_eq!(Value::::get(), 3); + assert_eq!(Map::::get(1), 3); + assert_eq!(Map::::get(2), 3); + assert_eq!(Map::::get(3), 3); + + Ok(()) + })); + + assert_eq!(Value::::get(), 3); + assert_eq!(Map::::get(1), 3); + assert_eq!(Map::::get(2), 3); + assert_eq!(Map::::get(3), 3); + + Err("revert".into()) + }), + "revert" + ); + + assert_eq!(Value::::get(), 1); + assert_eq!(Map::::get(1), 1); + assert_eq!(Map::::get(2), 0); + assert_eq!(Map::::get(3), 0); + }); +} + +#[test] +fn storage_layer_in_pallet_call() { + TestExternalities::default().execute_with(|| { + use sp_runtime::traits::Dispatchable; + let call1 = RuntimeCall::MyPallet(pallet::Call::set_value { value: 2 }); + assert_ok!(call1.dispatch(RuntimeOrigin::signed(0))); + assert_eq!(Value::::get(), 2); + + let call2 = RuntimeCall::MyPallet(pallet::Call::set_value { value: 1 }); + assert_noop!(call2.dispatch(RuntimeOrigin::signed(0)), Error::::Revert); + }); +} + +#[test] +fn storage_layer_in_decl_pallet_call() { + TestExternalities::default().execute_with(|| { + use frame_support::StorageValue; + use sp_runtime::traits::Dispatchable; + + let call1 = RuntimeCall::DeclPallet(decl_pallet::Call::set_value { value: 2 }); + assert_ok!(call1.dispatch(RuntimeOrigin::signed(0))); + assert_eq!(decl_pallet::DeclValue::get(), 2); + + let call2 = RuntimeCall::DeclPallet(decl_pallet::Call::set_value { value: 1 }); + assert_noop!(call2.dispatch(RuntimeOrigin::signed(0)), "Revert!"); + // Calling the function directly also works with storage layers. + assert_noop!( + decl_pallet::Module::::set_value(RuntimeOrigin::signed(1), 1), + "Revert!" + ); + }); +} diff --git a/frame/support/test/tests/storage_transaction.rs b/frame/support/test/tests/storage_transaction.rs index 4e97a87377b17..6fedd75019e37 100644 --- a/frame/support/test/tests/storage_transaction.rs +++ b/frame/support/test/tests/storage_transaction.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,19 +15,23 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Disable warnings for #\[transactional\] being deprecated. +#![allow(deprecated)] + use frame_support::{ - assert_noop, assert_ok, + assert_noop, assert_ok, assert_storage_noop, dispatch::{DispatchError, DispatchResult}, storage::{with_transaction, TransactionOutcome::*}, transactional, StorageMap, StorageValue, }; use sp_io::TestExternalities; +use sp_runtime::TransactionOutcome; use sp_std::result; pub trait Config: frame_support_test::Config {} frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=frame_support_test { + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test { #[weight = 0] #[transactional] fn value_commits(_origin, v: u32) { @@ -53,7 +57,7 @@ frame_support::decl_storage! { struct Runtime; impl frame_support_test::Config for Runtime { - type Origin = u32; + type RuntimeOrigin = u32; type BlockNumber = u32; type PalletInfo = frame_support_test::PanicPalletInfo; type DbWeight = (); @@ -67,13 +71,13 @@ fn storage_transaction_basic_commit() { assert_eq!(Value::get(), 0); assert!(!Map::contains_key("val0")); - with_transaction(|| { + assert_ok!(with_transaction(|| -> TransactionOutcome { Value::set(99); Map::insert("val0", 99); assert_eq!(Value::get(), 99); assert_eq!(Map::get("val0"), 99); - Commit(()) - }); + Commit(Ok(())) + })); assert_eq!(Value::get(), 99); assert_eq!(Map::get("val0"), 99); @@ -86,13 +90,26 @@ fn storage_transaction_basic_rollback() { assert_eq!(Value::get(), 0); assert_eq!(Map::get("val0"), 0); - with_transaction(|| { - Value::set(99); - Map::insert("val0", 99); - assert_eq!(Value::get(), 99); - assert_eq!(Map::get("val0"), 99); - Rollback(()) - }); + assert_noop!( + with_transaction(|| -> TransactionOutcome { + Value::set(99); + Map::insert("val0", 99); + assert_eq!(Value::get(), 99); + assert_eq!(Map::get("val0"), 99); + Rollback(Err("revert".into())) + }), + "revert" + ); + + assert_storage_noop!(assert_ok!(with_transaction( + || -> TransactionOutcome { + Value::set(99); + Map::insert("val0", 99); + assert_eq!(Value::get(), 99); + assert_eq!(Map::get("val0"), 99); + Rollback(Ok(())) + } + ))); assert_eq!(Value::get(), 0); assert_eq!(Map::get("val0"), 0); @@ -105,32 +122,35 @@ fn storage_transaction_rollback_then_commit() { Value::set(1); Map::insert("val1", 1); - with_transaction(|| { + assert_ok!(with_transaction(|| -> TransactionOutcome { Value::set(2); Map::insert("val1", 2); Map::insert("val2", 2); - with_transaction(|| { - Value::set(3); - Map::insert("val1", 3); - Map::insert("val2", 3); - Map::insert("val3", 3); + assert_noop!( + with_transaction(|| -> TransactionOutcome { + Value::set(3); + Map::insert("val1", 3); + Map::insert("val2", 3); + Map::insert("val3", 3); - assert_eq!(Value::get(), 3); - assert_eq!(Map::get("val1"), 3); - assert_eq!(Map::get("val2"), 3); - assert_eq!(Map::get("val3"), 3); + assert_eq!(Value::get(), 3); + assert_eq!(Map::get("val1"), 3); + assert_eq!(Map::get("val2"), 3); + assert_eq!(Map::get("val3"), 3); - Rollback(()) - }); + Rollback(Err("revert".into())) + }), + "revert" + ); assert_eq!(Value::get(), 2); assert_eq!(Map::get("val1"), 2); assert_eq!(Map::get("val2"), 2); assert_eq!(Map::get("val3"), 0); - Commit(()) - }); + Commit(Ok(())) + })); assert_eq!(Value::get(), 2); assert_eq!(Map::get("val1"), 2); @@ -145,32 +165,35 @@ fn storage_transaction_commit_then_rollback() { Value::set(1); Map::insert("val1", 1); - with_transaction(|| { - Value::set(2); - Map::insert("val1", 2); - Map::insert("val2", 2); + assert_noop!( + with_transaction(|| -> TransactionOutcome { + Value::set(2); + Map::insert("val1", 2); + Map::insert("val2", 2); - with_transaction(|| { - Value::set(3); - Map::insert("val1", 3); - Map::insert("val2", 3); - Map::insert("val3", 3); + assert_ok!(with_transaction(|| -> TransactionOutcome { + Value::set(3); + Map::insert("val1", 3); + Map::insert("val2", 3); + Map::insert("val3", 3); + + assert_eq!(Value::get(), 3); + assert_eq!(Map::get("val1"), 3); + assert_eq!(Map::get("val2"), 3); + assert_eq!(Map::get("val3"), 3); + + Commit(Ok(())) + })); assert_eq!(Value::get(), 3); assert_eq!(Map::get("val1"), 3); assert_eq!(Map::get("val2"), 3); assert_eq!(Map::get("val3"), 3); - Commit(()) - }); - - assert_eq!(Value::get(), 3); - assert_eq!(Map::get("val1"), 3); - assert_eq!(Map::get("val2"), 3); - assert_eq!(Map::get("val3"), 3); - - Rollback(()) - }); + Rollback(Err("revert".into())) + }), + "revert" + ); assert_eq!(Value::get(), 1); assert_eq!(Map::get("val1"), 1); diff --git a/frame/support/test/tests/system.rs b/frame/support/test/tests/system.rs index 4acc248d25f20..eff41242917a0 100644 --- a/frame/support/test/tests/system.rs +++ b/frame/support/test/tests/system.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,28 +22,28 @@ use frame_support::{ }; pub trait Config: 'static + Eq + Clone { - type Origin: Into, Self::Origin>> + type RuntimeOrigin: Into, Self::RuntimeOrigin>> + From>; - type BaseCallFilter: frame_support::traits::Contains; + type BaseCallFilter: frame_support::traits::Contains; type BlockNumber: Decode + Encode + EncodeLike + Clone + Default + scale_info::TypeInfo; type Hash; type AccountId: Encode + EncodeLike + Decode + scale_info::TypeInfo; - type Call; - type Event: From>; + type RuntimeCall; + type RuntimeEvent: From>; type PalletInfo: frame_support::traits::PalletInfo; type DbWeight: Get; } frame_support::decl_module! { - pub struct Module for enum Call where origin: T::Origin, system=self { + pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { #[weight = 0] fn noop(_origin) {} } } impl Module { - pub fn deposit_event(_event: impl Into) {} + pub fn deposit_event(_event: impl Into) {} } frame_support::decl_event!( @@ -63,27 +63,13 @@ frame_support::decl_error! { TestError, /// Error documentation /// with multiple lines - AnotherError - } -} - -/// Origin for the system module. -#[derive(PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, scale_info::TypeInfo)] -pub enum RawOrigin { - Root, - Signed(AccountId), - None, -} - -impl From> for RawOrigin { - fn from(s: Option) -> RawOrigin { - match s { - Some(who) => RawOrigin::Signed(who), - None => RawOrigin::None, - } + AnotherError, + // Required by construct_runtime + CallFiltered, } } +pub use frame_support::dispatch::RawOrigin; pub type Origin = RawOrigin<::AccountId>; #[allow(dead_code)] diff --git a/frame/system/Cargo.toml b/frame/system/Cargo.toml index 389730107b439..55c9b5bda54fa 100644 --- a/frame/system/Cargo.toml +++ b/frame/system/Cargo.toml @@ -2,9 +2,9 @@ name = "frame-system" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME system module" readme = "README.md" @@ -13,39 +13,41 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.126", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io", default-features = false } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-version = { version = "4.0.0-dev", default-features = false, path = "../../primitives/version" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -log = { version = "0.4.14", default-features = false } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-version = { version = "5.0.0", default-features = false, path = "../../primitives/version" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } [dev-dependencies] criterion = "0.3.3" -sp-externalities = { version = "0.10.0-dev", path = "../../primitives/externalities" } +sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] default = ["std"] std = [ - "serde", "codec/std", + "frame-support/std", + "log/std", "scale-info/std", + "serde", "sp-core/std", - "sp-std/std", "sp-io/std", - "frame-support/std", "sp-runtime/std", + "sp-std/std", "sp-version/std", - "log/std", + "sp-weights/std", ] runtime-benchmarks = [ - "sp-runtime/runtime-benchmarks", "frame-support/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/system/README.md b/frame/system/README.md index 6766c3d73f4de..c22b41e42d798 100644 --- a/frame/system/README.md +++ b/frame/system/README.md @@ -54,21 +54,28 @@ Import the System module and derive your module's configuration trait from the s ### Example - Get extrinsic count and parent hash for the current block ```rust -use frame_support::{decl_module, dispatch}; -use frame_system::{self as system, ensure_signed}; - -pub trait Config: system::Config {} - -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - #[weight = 0] - pub fn system_module_example(origin) -> dispatch::DispatchResult { - let _sender = ensure_signed(origin)?; - let _extrinsic_count = >::extrinsic_count(); - let _parent_hash = >::parent_hash(); - Ok(()) - } - } +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn system_module_example(origin: OriginFor) -> DispatchResult { + let _sender = ensure_signed(origin)?; + let _extrinsic_count = >::extrinsic_count(); + let _parent_hash = >::parent_hash(); + Ok(()) + } + } } ``` diff --git a/frame/system/benches/bench.rs b/frame/system/benches/bench.rs index 97c19c5e8159a..0881b81eaca7d 100644 --- a/frame/system/benches/bench.rs +++ b/frame/system/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,10 @@ // limitations under the License. use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use frame_support::{decl_event, decl_module}; -use frame_system as system; +use frame_support::{ + traits::{ConstU32, ConstU64}, + weights::Weight, +}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -25,24 +27,24 @@ use sp_runtime::{ Perbill, }; +#[frame_support::pallet] mod module { - use super::*; + use frame_support::pallet_prelude::*; - pub trait Config: system::Config { - type Event: From + Into<::Event>; - } + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); - decl_module! { - pub struct Module for enum Call where origin: T::Origin { - pub fn deposit_event() = default; - } + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From + IsType<::RuntimeEvent>; } - decl_event!( - pub enum Event { - Complex(Vec, u32, u16, u128), - } - ); + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Complex(Vec, u32, u16, u128), + } } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -55,37 +57,36 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - Module: module::{Pallet, Call, Event}, + Module: module::{Pallet, Event}, } ); frame_support::parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::with_sensible_defaults( - 4 * 1024 * 1024, Perbill::from_percent(75), + Weight::from_ref_time(4 * 1024 * 1024), Perbill::from_percent(75), ); pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength::max_with_normal_ratio( 4 * 1024 * 1024, Perbill::from_percent(75), ); } -impl system::Config for Runtime { +impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = BlockLength; type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -94,21 +95,25 @@ impl system::Config for Runtime { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl module::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; } fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::default().build_storage::().unwrap().into() + frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into() } fn deposit_events(n: usize) { let mut t = new_test_ext(); t.execute_with(|| { for _ in 0..n { - module::Module::::deposit_event(module::Event::Complex( + module::Pallet::::deposit_event(module::Event::Complex( vec![1, 2, 3], 2, 3, diff --git a/frame/system/benchmarking/Cargo.toml b/frame/system/benchmarking/Cargo.toml index 29bcccfd7d830..30b299ea6a56e 100644 --- a/frame/system/benchmarking/Cargo.toml +++ b/frame/system/benchmarking/Cargo.toml @@ -2,9 +2,9 @@ name = "frame-system-benchmarking" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME System benchmarking" readme = "README.md" @@ -13,27 +13,33 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] -sp-io = { version = "4.0.0-dev", path = "../../../primitives/io" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-runtime/std", - "sp-std/std", "frame-benchmarking/std", - "frame-system/std", "frame-support/std", + "frame-system/std", + "scale-info/std", "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", ] diff --git a/frame/system/benchmarking/src/lib.rs b/frame/system/benchmarking/src/lib.rs index beb61829bce37..0f7603fe1dd9f 100644 --- a/frame/system/benchmarking/src/lib.rs +++ b/frame/system/benchmarking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,10 +20,10 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::Encode; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -use frame_support::{storage, traits::Get, weights::DispatchClass}; -use frame_system::{Call, DigestItemOf, Pallet as System, RawOrigin}; -use sp_core::{storage::well_known_keys, ChangesTrieConfiguration}; +use frame_benchmarking::{benchmarks, whitelisted_caller}; +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}; @@ -62,26 +62,9 @@ benchmarks! { assert_eq!(current_code.len(), 4_000_000 as usize); } - set_changes_trie_config { - let d = 1000; - - let digest_item = DigestItemOf::::Other(vec![]); - - for i in 0 .. d { - System::::deposit_log(digest_item.clone()); - } - let changes_trie_config = ChangesTrieConfiguration { - digest_interval: d, - digest_levels: d, - }; - }: _(RawOrigin::Root, Some(changes_trie_config)) - verify { - assert_eq!(System::::digest().logs.len(), (d + 1) as usize) - } - #[skip_meta] set_storage { - let i in 1 .. 1000; + let i in 0 .. 1000; // Set up i items to add let mut items = Vec::new(); @@ -89,57 +72,70 @@ benchmarks! { 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(); }: _(RawOrigin::Root, items) verify { - let last_hash = (i, i - 1).using_encoded(T::Hashing::hash); - let value = storage::unhashed::get_raw(last_hash.as_ref()).ok_or("No value stored")?; - assert_eq!(value, last_hash.as_ref().to_vec()); + // 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); + } } #[skip_meta] kill_storage { - let i in 1 .. 1000; + let i in 0 .. 1000; // Add i items to storage - let mut items = Vec::new(); + 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); } - // We will verify this value is removed - let last_hash = (i, i - 1).using_encoded(T::Hashing::hash); - let value = storage::unhashed::get_raw(last_hash.as_ref()).ok_or("No value stored")?; - assert_eq!(value, last_hash.as_ref().to_vec()); + // 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(); }: _(RawOrigin::Root, items) verify { - assert_eq!(storage::unhashed::get_raw(last_hash.as_ref()), None); + // Verify that they're not in the storage anymore. + for item in items_to_verify { + assert!(storage::unhashed::get_raw(&item).is_none()); + } } #[skip_meta] kill_prefix { - let p in 1 .. 1000; + let p in 0 .. 1000; 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); } - // We will verify this value is removed - let last_hash = (p, p - 1).using_encoded(T::Hashing::hash).as_ref().to_vec(); - let last_key = [&prefix[..], &last_hash[..]].concat(); - let value = storage::unhashed::get_raw(&last_key).ok_or("No value stored")?; - assert_eq!(value, last_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); + } }: _(RawOrigin::Root, prefix, p) verify { - assert_eq!(storage::unhashed::get_raw(&last_key), None); + // Verify that they're not in the storage anymore. + for item in items { + assert!(storage::unhashed::get_raw(&item).is_none()); + } } -} -impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/system/benchmarking/src/mock.rs b/frame/system/benchmarking/src/mock.rs index d828fb22ff5ff..a7f28ca30fe87 100644 --- a/frame/system/benchmarking/src/mock.rs +++ b/frame/system/benchmarking/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,16 +43,16 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = AccountIndex; type BlockNumber = BlockNumber; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = sp_runtime::testing::Header; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); type PalletInfo = PalletInfo; @@ -62,6 +62,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } impl crate::Config for Test {} diff --git a/frame/system/rpc/runtime-api/Cargo.toml b/frame/system/rpc/runtime-api/Cargo.toml index fce29612b4d8c..63d76d731e26e 100644 --- a/frame/system/rpc/runtime-api/Cargo.toml +++ b/frame/system/rpc/runtime-api/Cargo.toml @@ -2,9 +2,9 @@ name = "frame-system-rpc-runtime-api" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Runtime API definition required by System RPC extensions." readme = "README.md" @@ -13,12 +13,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } [features] default = ["std"] std = [ - "sp-api/std", "codec/std", + "sp-api/std", ] diff --git a/frame/system/rpc/runtime-api/src/lib.rs b/frame/system/rpc/runtime-api/src/lib.rs index 319883c36d748..6e01bdae2d150 100644 --- a/frame/system/rpc/runtime-api/src/lib.rs +++ b/frame/system/rpc/runtime-api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/system/src/extensions/check_genesis.rs b/frame/system/src/extensions/check_genesis.rs index 6f409d5d3d4ad..f5811f306cfe3 100644 --- a/frame/system/src/extensions/check_genesis.rs +++ b/frame/system/src/extensions/check_genesis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,11 +19,16 @@ use crate::{Config, Pallet}; use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{SignedExtension, Zero}, + traits::{DispatchInfoOf, SignedExtension, Zero}, transaction_validity::TransactionValidityError, }; /// Genesis hash check to provide replay protection between different networks. +/// +/// # Transaction Validity +/// +/// Note that while a transaction with invalid `genesis_hash` will fail to be decoded, +/// the extension does not affect any other fields of `TransactionValidity` directly. #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct CheckGenesis(sp_std::marker::PhantomData); @@ -49,7 +54,7 @@ impl CheckGenesis { impl SignedExtension for CheckGenesis { type AccountId = T::AccountId; - type Call = ::Call; + type Call = ::RuntimeCall; type AdditionalSigned = T::Hash; type Pre = (); const IDENTIFIER: &'static str = "CheckGenesis"; @@ -57,4 +62,14 @@ impl SignedExtension for CheckGenesis { fn additional_signed(&self) -> Result { Ok(>::block_hash(T::BlockNumber::zero())) } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } } diff --git a/frame/system/src/extensions/check_mortality.rs b/frame/system/src/extensions/check_mortality.rs index 69cca765efea9..635ab4ef1d9a9 100644 --- a/frame/system/src/extensions/check_mortality.rs +++ b/frame/system/src/extensions/check_mortality.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,9 +27,13 @@ use sp_runtime::{ }; /// Check for transaction mortality. +/// +/// # Transaction Validity +/// +/// The extension affects `longevity` of the transaction according to the [`Era`] definition. #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] -pub struct CheckMortality(Era, sp_std::marker::PhantomData); +pub struct CheckMortality(pub Era, sp_std::marker::PhantomData); impl CheckMortality { /// utility constructor. Used only in client/factory code. @@ -52,7 +56,7 @@ impl sp_std::fmt::Debug for CheckMortality { impl SignedExtension for CheckMortality { type AccountId = T::AccountId; - type Call = T::Call; + type Call = T::RuntimeCall; type AdditionalSigned = T::Hash; type Pre = (); const IDENTIFIER: &'static str = "CheckMortality"; @@ -81,13 +85,26 @@ impl SignedExtension for CheckMortality { Ok(>::block_hash(n)) } } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } } #[cfg(test)] mod tests { use super::*; use crate::mock::{new_test_ext, System, Test, CALL}; - use frame_support::weights::{DispatchClass, DispatchInfo, Pays}; + use frame_support::{ + dispatch::{DispatchClass, DispatchInfo, Pays}, + weights::Weight, + }; use sp_core::H256; #[test] @@ -112,8 +129,11 @@ mod tests { #[test] fn signed_ext_check_era_should_change_longevity() { new_test_ext().execute_with(|| { - let normal = - DispatchInfo { weight: 100, class: DispatchClass::Normal, pays_fee: Pays::Yes }; + let normal = DispatchInfo { + weight: Weight::from_ref_time(100), + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }; let len = 0_usize; let ext = ( crate::CheckWeight::::new(), diff --git a/frame/system/src/extensions/check_non_zero_sender.rs b/frame/system/src/extensions/check_non_zero_sender.rs new file mode 100644 index 0000000000000..036f70c2fdd48 --- /dev/null +++ b/frame/system/src/extensions/check_non_zero_sender.rs @@ -0,0 +1,110 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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::Config; +use codec::{Decode, Encode}; +use frame_support::dispatch::DispatchInfo; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, SignedExtension}, + transaction_validity::{ + InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction, + }, +}; +use sp_std::{marker::PhantomData, prelude::*}; + +/// Check to ensure that the sender is not the zero address. +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckNonZeroSender(PhantomData); + +impl sp_std::fmt::Debug for CheckNonZeroSender { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "CheckNonZeroSender") + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl CheckNonZeroSender { + /// Create new `SignedExtension` to check runtime version. + pub fn new() -> Self { + Self(sp_std::marker::PhantomData) + } +} + +impl SignedExtension for CheckNonZeroSender +where + T::RuntimeCall: Dispatchable, +{ + type AccountId = T::AccountId; + type Call = T::RuntimeCall; + type AdditionalSigned = (); + type Pre = (); + const IDENTIFIER: &'static str = "CheckNonZeroSender"; + + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + Ok(()) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } + + fn validate( + &self, + who: &Self::AccountId, + _call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + if who.using_encoded(|d| d.iter().all(|x| *x == 0)) { + return Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner)) + } + Ok(ValidTransaction::default()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{new_test_ext, Test, CALL}; + use frame_support::{assert_noop, assert_ok}; + + #[test] + fn zero_account_ban_works() { + new_test_ext().execute_with(|| { + let info = DispatchInfo::default(); + let len = 0_usize; + assert_noop!( + CheckNonZeroSender::::new().validate(&0, CALL, &info, len), + InvalidTransaction::BadSigner + ); + assert_ok!(CheckNonZeroSender::::new().validate(&1, CALL, &info, len)); + }) + } +} diff --git a/frame/system/src/extensions/check_nonce.rs b/frame/system/src/extensions/check_nonce.rs index 081a0efa3db71..1616a2d8a119e 100644 --- a/frame/system/src/extensions/check_nonce.rs +++ b/frame/system/src/extensions/check_nonce.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ use crate::Config; use codec::{Decode, Encode}; -use frame_support::weights::DispatchInfo; +use frame_support::dispatch::DispatchInfo; use scale_info::TypeInfo; use sp_runtime::{ traits::{DispatchInfoOf, Dispatchable, One, SignedExtension}, @@ -30,11 +30,14 @@ use sp_std::vec; /// Nonce check and increment to give replay protection for transactions. /// -/// Note that this does not set any priority by default. Make sure that AT LEAST one of the signed -/// extension sets some kind of priority upon validating transactions. +/// # Transaction Validity +/// +/// This extension affects `requires` and `provides` tags of validity, but DOES NOT +/// set the `priority` field. Make sure that AT LEAST one of the signed extension sets +/// some kind of priority upon validating transactions. #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] -pub struct CheckNonce(#[codec(compact)] T::Index); +pub struct CheckNonce(#[codec(compact)] pub T::Index); impl CheckNonce { /// utility constructor. Used only in client/factory code. @@ -57,10 +60,10 @@ impl sp_std::fmt::Debug for CheckNonce { impl SignedExtension for CheckNonce where - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { type AccountId = T::AccountId; - type Call = T::Call; + type Call = T::RuntimeCall; type AdditionalSigned = (); type Pre = (); const IDENTIFIER: &'static str = "CheckNonce"; diff --git a/frame/system/src/extensions/check_spec_version.rs b/frame/system/src/extensions/check_spec_version.rs index 0217aefae6b9d..ef5f40402692c 100644 --- a/frame/system/src/extensions/check_spec_version.rs +++ b/frame/system/src/extensions/check_spec_version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,17 @@ use crate::{Config, Pallet}; use codec::{Decode, Encode}; use scale_info::TypeInfo; -use sp_runtime::{traits::SignedExtension, transaction_validity::TransactionValidityError}; +use sp_runtime::{ + traits::{DispatchInfoOf, SignedExtension}, + transaction_validity::TransactionValidityError, +}; /// Ensure the runtime version registered in the transaction is the same as at present. +/// +/// # Transaction Validity +/// +/// The transaction with incorrect `spec_version` are considered invalid. The validity +/// is not affected in any other way. #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct CheckSpecVersion(sp_std::marker::PhantomData); @@ -46,7 +54,7 @@ impl CheckSpecVersion { impl SignedExtension for CheckSpecVersion { type AccountId = T::AccountId; - type Call = ::Call; + type Call = ::RuntimeCall; type AdditionalSigned = u32; type Pre = (); const IDENTIFIER: &'static str = "CheckSpecVersion"; @@ -54,4 +62,14 @@ impl SignedExtension for CheckSpecVersion { fn additional_signed(&self) -> Result { Ok(>::runtime_version().spec_version) } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } } diff --git a/frame/system/src/extensions/check_tx_version.rs b/frame/system/src/extensions/check_tx_version.rs index 9418d3ff5d937..be0b8fe2354aa 100644 --- a/frame/system/src/extensions/check_tx_version.rs +++ b/frame/system/src/extensions/check_tx_version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,17 @@ use crate::{Config, Pallet}; use codec::{Decode, Encode}; use scale_info::TypeInfo; -use sp_runtime::{traits::SignedExtension, transaction_validity::TransactionValidityError}; +use sp_runtime::{ + traits::{DispatchInfoOf, SignedExtension}, + transaction_validity::TransactionValidityError, +}; /// Ensure the transaction version registered in the transaction is the same as at present. +/// +/// # Transaction Validity +/// +/// The transaction with incorrect `transaction_version` are considered invalid. The validity +/// is not affected in any other way. #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct CheckTxVersion(sp_std::marker::PhantomData); @@ -46,7 +54,7 @@ impl CheckTxVersion { impl SignedExtension for CheckTxVersion { type AccountId = T::AccountId; - type Call = ::Call; + type Call = ::RuntimeCall; type AdditionalSigned = u32; type Pre = (); const IDENTIFIER: &'static str = "CheckTxVersion"; @@ -54,4 +62,13 @@ impl SignedExtension for CheckTxVersion { fn additional_signed(&self) -> Result { Ok(>::runtime_version().transaction_version) } + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } } diff --git a/frame/system/src/extensions/check_weight.rs b/frame/system/src/extensions/check_weight.rs index 92dc7382fa2d5..757b2197bc238 100644 --- a/frame/system/src/extensions/check_weight.rs +++ b/frame/system/src/extensions/check_weight.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,36 +18,40 @@ use crate::{limits::BlockWeights, Config, Pallet}; use codec::{Decode, Encode}; use frame_support::{ + dispatch::{DispatchInfo, PostDispatchInfo}, traits::Get, - weights::{priority::FrameTransactionPriority, DispatchClass, DispatchInfo, PostDispatchInfo}, }; use scale_info::TypeInfo; use sp_runtime::{ traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension}, - transaction_validity::{ - InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError, - ValidTransaction, - }, + transaction_validity::{InvalidTransaction, TransactionValidity, TransactionValidityError}, DispatchResult, }; +use sp_weights::Weight; /// Block resource (weight) limit check. +/// +/// # Transaction Validity +/// +/// This extension does not influence any fields of `TransactionValidity` in case the +/// transaction is valid. #[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct CheckWeight(sp_std::marker::PhantomData); impl CheckWeight where - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { /// Checks if the current extrinsic does not exceed the maximum weight a single extrinsic /// with given `DispatchClass` can have. fn check_extrinsic_weight( - info: &DispatchInfoOf, + info: &DispatchInfoOf, ) -> Result<(), TransactionValidityError> { let max = T::BlockWeights::get().get(info.class).max_extrinsic; match max { - Some(max) if info.weight > max => Err(InvalidTransaction::ExhaustsResources.into()), + Some(max) if info.weight.any_gt(max) => + Err(InvalidTransaction::ExhaustsResources.into()), _ => Ok(()), } } @@ -56,18 +60,18 @@ where /// /// Upon successes, it returns the new block weight as a `Result`. fn check_block_weight( - info: &DispatchInfoOf, + info: &DispatchInfoOf, ) -> Result { let maximum_weight = T::BlockWeights::get(); let all_weight = Pallet::::block_weight(); - calculate_consumed_weight::(maximum_weight, all_weight, info) + calculate_consumed_weight::(maximum_weight, all_weight, info) } /// Checks if the current extrinsic can fit into the block with respect to block length limits. /// /// Upon successes, it returns the new block length as a `Result`. fn check_block_length( - info: &DispatchInfoOf, + info: &DispatchInfoOf, len: usize, ) -> Result { let length_limit = T::BlockLength::get(); @@ -81,23 +85,6 @@ where } } - /// Get the priority of an extrinsic denoted by `info`. - /// - /// Operational transaction will be given a fixed initial amount to be fairly distinguished from - /// the normal ones. - fn get_priority(info: &DispatchInfoOf) -> TransactionPriority { - match info.class { - // Normal transaction. - DispatchClass::Normal => FrameTransactionPriority::Normal(info.weight.into()).into(), - // Don't use up the whole priority space, to allow things like `tip` to be taken into - // account as well. - DispatchClass::Operational => - FrameTransactionPriority::Operational(info.weight.into()).into(), - // Mandatory extrinsics are only for inherents; never transactions. - DispatchClass::Mandatory => TransactionPriority::min_value(), - } - } - /// Creates new `SignedExtension` to check weight of the extrinsic. pub fn new() -> Self { Self(Default::default()) @@ -107,7 +94,7 @@ where /// /// It checks and notes the new weight and length. pub fn do_pre_dispatch( - info: &DispatchInfoOf, + info: &DispatchInfoOf, len: usize, ) -> Result<(), TransactionValidityError> { let next_len = Self::check_block_length(info, len)?; @@ -122,7 +109,7 @@ where /// Do the validate checks. This can be applied to both signed and unsigned. /// /// It only checks that the block weight and length limit will not exceed. - pub fn do_validate(info: &DispatchInfoOf, len: usize) -> TransactionValidity { + pub fn do_validate(info: &DispatchInfoOf, len: usize) -> TransactionValidity { // ignore the next length. If they return `Ok`, then it is below the limit. let _ = Self::check_block_length(info, len)?; // during validation we skip block limit check. Since the `validate_transaction` @@ -130,7 +117,7 @@ where // consumption from causing false negatives. Self::check_extrinsic_weight(info)?; - Ok(ValidTransaction { priority: Self::get_priority(info), ..Default::default() }) + Ok(Default::default()) } } @@ -159,7 +146,8 @@ where // Check if we don't exceed per-class allowance match limit_per_class.max_total { - Some(max) if per_class > max => return Err(InvalidTransaction::ExhaustsResources.into()), + Some(max) if per_class.any_gt(max) => + return Err(InvalidTransaction::ExhaustsResources.into()), // There is no `max_total` limit (`None`), // or we are below the limit. _ => {}, @@ -167,10 +155,10 @@ where // In cases total block weight is exceeded, we need to fall back // to `reserved` pool if there is any. - if all_weight.total() > maximum_weight.max_block { + if all_weight.total().any_gt(maximum_weight.max_block) { match limit_per_class.reserved { // We are over the limit in reserved pool. - Some(reserved) if per_class > reserved => + Some(reserved) if per_class.any_gt(reserved) => return Err(InvalidTransaction::ExhaustsResources.into()), // There is either no limit in reserved pool (`None`), // or we are below the limit. @@ -183,10 +171,10 @@ where impl SignedExtension for CheckWeight where - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { type AccountId = T::AccountId; - type Call = T::Call; + type Call = T::RuntimeCall; type AdditionalSigned = (); type Pre = (); const IDENTIFIER: &'static str = "CheckWeight"; @@ -202,9 +190,6 @@ where info: &DispatchInfoOf, len: usize, ) -> Result<(), TransactionValidityError> { - if info.class == DispatchClass::Mandatory { - Err(InvalidTransaction::MandatoryDispatch)? - } Self::do_pre_dispatch(info, len) } @@ -215,9 +200,6 @@ where info: &DispatchInfoOf, len: usize, ) -> TransactionValidity { - if info.class == DispatchClass::Mandatory { - Err(InvalidTransaction::MandatoryDispatch)? - } Self::do_validate(info, len) } @@ -238,22 +220,14 @@ where } fn post_dispatch( - _pre: Self::Pre, + _pre: Option, info: &DispatchInfoOf, post_info: &PostDispatchInfoOf, _len: usize, - result: &DispatchResult, + _result: &DispatchResult, ) -> Result<(), TransactionValidityError> { - // Since mandatory dispatched do not get validated for being overweight, we are sensitive - // to them actually being useful. Block producers are thus not allowed to include mandatory - // extrinsics that result in error. - if let (DispatchClass::Mandatory, Err(e)) = (info.class, result) { - log::error!(target: "runtime::system", "Bad mandatory: {:?}", e); - Err(InvalidTransaction::BadMandatory)? - } - let unspent = post_info.calc_unspent(info); - if unspent > 0 { + if unspent.any_gt(Weight::zero()) { crate::BlockWeight::::mutate(|current_weight| { current_weight.sub(unspent, info.class); }) @@ -280,12 +254,9 @@ mod tests { use super::*; use crate::{ mock::{new_test_ext, System, Test, CALL}, - AllExtrinsicsLen, BlockWeight, - }; - use frame_support::{ - assert_err, assert_ok, - weights::{Pays, Weight}, + AllExtrinsicsLen, BlockWeight, DispatchClass, }; + use frame_support::{assert_err, assert_ok, dispatch::Pays, weights::Weight}; use sp_std::marker::PhantomData; fn block_weights() -> crate::limits::BlockWeights { @@ -312,7 +283,7 @@ mod tests { fn check(call: impl FnOnce(&DispatchInfo, usize)) { new_test_ext().execute_with(|| { let max = DispatchInfo { - weight: Weight::max_value(), + weight: Weight::MAX, class: DispatchClass::Mandatory, ..Default::default() }; @@ -324,8 +295,8 @@ mod tests { check(|max, len| { assert_ok!(CheckWeight::::do_pre_dispatch(max, len)); - assert_eq!(System::block_weight().total(), Weight::max_value()); - assert!(System::block_weight().total() > block_weight_limit()); + assert_eq!(System::block_weight().total(), Weight::MAX); + assert!(System::block_weight().total().ref_time() > block_weight_limit().ref_time()); }); check(|max, len| { assert_ok!(CheckWeight::::do_validate(max, len)); @@ -336,7 +307,8 @@ mod tests { fn normal_extrinsic_limited_by_maximum_extrinsic_weight() { new_test_ext().execute_with(|| { let max = DispatchInfo { - weight: block_weights().get(DispatchClass::Normal).max_extrinsic.unwrap() + 1, + weight: block_weights().get(DispatchClass::Normal).max_extrinsic.unwrap() + + Weight::from_ref_time(1), class: DispatchClass::Normal, ..Default::default() }; @@ -356,25 +328,19 @@ mod tests { .get(DispatchClass::Operational) .max_total .unwrap_or_else(|| weights.max_block); - let base_weight = weights.get(DispatchClass::Normal).base_extrinsic; + let base_weight = weights.get(DispatchClass::Operational).base_extrinsic; let weight = operational_limit - base_weight; let okay = DispatchInfo { weight, class: DispatchClass::Operational, ..Default::default() }; let max = DispatchInfo { - weight: weight + 1, + weight: weight + Weight::from_ref_time(1), class: DispatchClass::Operational, ..Default::default() }; let len = 0_usize; - assert_eq!( - CheckWeight::::do_validate(&okay, len), - Ok(ValidTransaction { - priority: CheckWeight::::get_priority(&okay), - ..Default::default() - }) - ); + assert_eq!(CheckWeight::::do_validate(&okay, len), Ok(Default::default())); assert_err!( CheckWeight::::do_validate(&max, len), InvalidTransaction::ExhaustsResources @@ -385,9 +351,9 @@ mod tests { #[test] fn register_extra_weight_unchecked_doesnt_care_about_limits() { new_test_ext().execute_with(|| { - System::register_extra_weight_unchecked(Weight::max_value(), DispatchClass::Normal); - assert_eq!(System::block_weight().total(), Weight::max_value()); - assert!(System::block_weight().total() > block_weight_limit()); + System::register_extra_weight_unchecked(Weight::MAX, DispatchClass::Normal); + assert_eq!(System::block_weight().total(), Weight::MAX); + assert!(System::block_weight().total().ref_time() > block_weight_limit().ref_time()); }); } @@ -398,10 +364,11 @@ mod tests { // Max normal is 768 (75%) // 10 is taken for block execution weight // So normal extrinsic can be 758 weight (-5 for base extrinsic weight) - // And Operational can be 256 to produce a full block (-5 for base) - let max_normal = DispatchInfo { weight: 753, ..Default::default() }; + // And Operational can be 246 to produce a full block (-10 for base) + let max_normal = + DispatchInfo { weight: Weight::from_ref_time(753), ..Default::default() }; let rest_operational = DispatchInfo { - weight: 251, + weight: Weight::from_ref_time(246), class: DispatchClass::Operational, ..Default::default() }; @@ -409,10 +376,10 @@ mod tests { let len = 0_usize; assert_ok!(CheckWeight::::do_pre_dispatch(&max_normal, len)); - assert_eq!(System::block_weight().total(), 768); + assert_eq!(System::block_weight().total(), Weight::from_ref_time(768)); assert_ok!(CheckWeight::::do_pre_dispatch(&rest_operational, len)); - assert_eq!(block_weight_limit(), 1024); - assert_eq!(System::block_weight().total(), block_weight_limit()); + assert_eq!(block_weight_limit(), Weight::from_ref_time(1024).set_proof_size(u64::MAX)); + assert_eq!(System::block_weight().total(), block_weight_limit().set_proof_size(0)); // Checking single extrinsic should not take current block weight into account. assert_eq!(CheckWeight::::check_extrinsic_weight(&rest_operational), Ok(())); }); @@ -422,9 +389,10 @@ mod tests { fn dispatch_order_does_not_effect_weight_logic() { new_test_ext().execute_with(|| { // We switch the order of `full_block_with_normal_and_operational` - let max_normal = DispatchInfo { weight: 753, ..Default::default() }; + let max_normal = + DispatchInfo { weight: Weight::from_ref_time(753), ..Default::default() }; let rest_operational = DispatchInfo { - weight: 251, + weight: Weight::from_ref_time(246), class: DispatchClass::Operational, ..Default::default() }; @@ -432,11 +400,11 @@ mod tests { let len = 0_usize; assert_ok!(CheckWeight::::do_pre_dispatch(&rest_operational, len)); - // Extra 15 here from block execution + base extrinsic weight - assert_eq!(System::block_weight().total(), 266); + // Extra 20 here from block execution + base extrinsic weight + assert_eq!(System::block_weight().total(), Weight::from_ref_time(266)); assert_ok!(CheckWeight::::do_pre_dispatch(&max_normal, len)); - assert_eq!(block_weight_limit(), 1024); - assert_eq!(System::block_weight().total(), block_weight_limit()); + assert_eq!(block_weight_limit(), Weight::from_ref_time(1024).set_proof_size(u64::MAX)); + assert_eq!(System::block_weight().total(), block_weight_limit().set_proof_size(0)); }); } @@ -444,11 +412,14 @@ mod tests { fn operational_works_on_full_block() { new_test_ext().execute_with(|| { // An on_initialize takes up the whole block! (Every time!) - System::register_extra_weight_unchecked(Weight::max_value(), DispatchClass::Mandatory); - let dispatch_normal = - DispatchInfo { weight: 251, class: DispatchClass::Normal, ..Default::default() }; + System::register_extra_weight_unchecked(Weight::MAX, DispatchClass::Mandatory); + let dispatch_normal = DispatchInfo { + weight: Weight::from_ref_time(251), + class: DispatchClass::Normal, + ..Default::default() + }; let dispatch_operational = DispatchInfo { - weight: 251, + weight: Weight::from_ref_time(246), class: DispatchClass::Operational, ..Default::default() }; @@ -474,9 +445,9 @@ mod tests { #[test] fn signed_ext_check_weight_works_operational_tx() { new_test_ext().execute_with(|| { - let normal = DispatchInfo { weight: 100, ..Default::default() }; + let normal = DispatchInfo { weight: Weight::from_ref_time(100), ..Default::default() }; let op = DispatchInfo { - weight: 100, + weight: Weight::from_ref_time(100), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -506,35 +477,11 @@ mod tests { }) } - #[test] - fn signed_ext_check_weight_works() { - new_test_ext().execute_with(|| { - let normal = - DispatchInfo { weight: 100, class: DispatchClass::Normal, pays_fee: Pays::Yes }; - let op = DispatchInfo { - weight: 100, - class: DispatchClass::Operational, - pays_fee: Pays::Yes, - }; - let len = 0_usize; - - let priority = CheckWeight::(PhantomData) - .validate(&1, CALL, &normal, len) - .unwrap() - .priority; - assert_eq!(priority, 100); - - let priority = - CheckWeight::(PhantomData).validate(&1, CALL, &op, len).unwrap().priority; - assert_eq!(priority, frame_support::weights::priority::LIMIT + 100); - }) - } - #[test] fn signed_ext_check_weight_block_size_works() { new_test_ext().execute_with(|| { let normal = DispatchInfo::default(); - let normal_limit = normal_weight_limit() as usize; + let normal_limit = normal_weight_limit().ref_time() as usize; let reset_check_weight = |tx, s, f| { AllExtrinsicsLen::::put(0); let r = CheckWeight::(PhantomData).pre_dispatch(&1, CALL, tx, s); @@ -550,8 +497,11 @@ mod tests { reset_check_weight(&normal, normal_limit + 1, true); // Operational ones don't have this limit. - let op = - DispatchInfo { weight: 0, class: DispatchClass::Operational, pays_fee: Pays::Yes }; + let op = DispatchInfo { + weight: Weight::zero(), + class: DispatchClass::Operational, + pays_fee: Pays::Yes, + }; reset_check_weight(&op, normal_limit, false); reset_check_weight(&op, normal_limit + 100, false); reset_check_weight(&op, 1024, false); @@ -563,12 +513,14 @@ mod tests { fn signed_ext_check_weight_works_normal_tx() { new_test_ext().execute_with(|| { let normal_limit = normal_weight_limit(); - let small = DispatchInfo { weight: 100, ..Default::default() }; + let small = DispatchInfo { weight: Weight::from_ref_time(100), ..Default::default() }; let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; let medium = DispatchInfo { weight: normal_limit - base_extrinsic, ..Default::default() }; - let big = - DispatchInfo { weight: normal_limit - base_extrinsic + 1, ..Default::default() }; + let big = DispatchInfo { + weight: normal_limit - base_extrinsic + Weight::from_ref_time(1), + ..Default::default() + }; let len = 0_usize; let reset_check_weight = |i, f, s| { @@ -583,9 +535,9 @@ mod tests { } }; - reset_check_weight(&small, false, 0); - reset_check_weight(&medium, false, 0); - reset_check_weight(&big, true, 1); + reset_check_weight(&small, false, Weight::zero()); + reset_check_weight(&medium, false, Weight::zero()); + reset_check_weight(&big, true, Weight::from_ref_time(1)); }) } @@ -593,49 +545,76 @@ mod tests { fn signed_ext_check_weight_refund_works() { new_test_ext().execute_with(|| { // This is half of the max block weight - let info = DispatchInfo { weight: 512, ..Default::default() }; - let post_info = - PostDispatchInfo { actual_weight: Some(128), pays_fee: Default::default() }; + let info = DispatchInfo { weight: Weight::from_ref_time(512), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_ref_time(128)), + pays_fee: Default::default(), + }; let len = 0_usize; let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; // We allow 75% for normal transaction, so we put 25% - extrinsic base weight BlockWeight::::mutate(|current_weight| { - current_weight.set(0, DispatchClass::Mandatory); - current_weight.set(256 - base_extrinsic, DispatchClass::Normal); + current_weight.set(Weight::zero(), DispatchClass::Mandatory); + current_weight + .set(Weight::from_ref_time(256) - base_extrinsic, DispatchClass::Normal); }); let pre = CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap(); - assert_eq!(BlockWeight::::get().total(), info.weight + 256); + assert_eq!( + BlockWeight::::get().total(), + info.weight + Weight::from_ref_time(256) + ); - assert_ok!(CheckWeight::::post_dispatch(pre, &info, &post_info, len, &Ok(()))); - assert_eq!(BlockWeight::::get().total(), post_info.actual_weight.unwrap() + 256); + assert_ok!(CheckWeight::::post_dispatch( + Some(pre), + &info, + &post_info, + len, + &Ok(()) + )); + assert_eq!( + BlockWeight::::get().total(), + post_info.actual_weight.unwrap() + Weight::from_ref_time(256) + ); }) } #[test] fn signed_ext_check_weight_actual_weight_higher_than_max_is_capped() { new_test_ext().execute_with(|| { - let info = DispatchInfo { weight: 512, ..Default::default() }; - let post_info = - PostDispatchInfo { actual_weight: Some(700), pays_fee: Default::default() }; + let info = DispatchInfo { weight: Weight::from_ref_time(512), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_ref_time(700)), + pays_fee: Default::default(), + }; let len = 0_usize; BlockWeight::::mutate(|current_weight| { - current_weight.set(0, DispatchClass::Mandatory); - current_weight.set(128, DispatchClass::Normal); + current_weight.set(Weight::zero(), DispatchClass::Mandatory); + current_weight.set(Weight::from_ref_time(128), DispatchClass::Normal); }); let pre = CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap(); assert_eq!( BlockWeight::::get().total(), - info.weight + 128 + block_weights().get(DispatchClass::Normal).base_extrinsic, + info.weight + + Weight::from_ref_time(128) + + block_weights().get(DispatchClass::Normal).base_extrinsic, ); - assert_ok!(CheckWeight::::post_dispatch(pre, &info, &post_info, len, &Ok(()))); + assert_ok!(CheckWeight::::post_dispatch( + Some(pre), + &info, + &post_info, + len, + &Ok(()) + )); assert_eq!( BlockWeight::::get().total(), - info.weight + 128 + block_weights().get(DispatchClass::Normal).base_extrinsic, + info.weight + + Weight::from_ref_time(128) + + block_weights().get(DispatchClass::Normal).base_extrinsic, ); }) } @@ -644,7 +623,7 @@ mod tests { fn zero_weight_extrinsic_still_has_base_weight() { new_test_ext().execute_with(|| { let weights = block_weights(); - let free = DispatchInfo { weight: 0, ..Default::default() }; + let free = DispatchInfo { weight: Weight::zero(), ..Default::default() }; let len = 0_usize; // Initial weight from `weights.base_block` @@ -663,9 +642,10 @@ mod tests { // Max block is 1024 // Max normal is 768 (75%) // Max mandatory is unlimited - let max_normal = DispatchInfo { weight: 753, ..Default::default() }; + let max_normal = + DispatchInfo { weight: Weight::from_ref_time(753), ..Default::default() }; let mandatory = DispatchInfo { - weight: 1019, + weight: Weight::from_ref_time(1019), class: DispatchClass::Mandatory, ..Default::default() }; @@ -673,10 +653,10 @@ mod tests { let len = 0_usize; assert_ok!(CheckWeight::::do_pre_dispatch(&max_normal, len)); - assert_eq!(System::block_weight().total(), 768); + assert_eq!(System::block_weight().total(), Weight::from_ref_time(768)); assert_ok!(CheckWeight::::do_pre_dispatch(&mandatory, len)); - assert_eq!(block_weight_limit(), 1024); - assert_eq!(System::block_weight().total(), 1024 + 768); + assert_eq!(block_weight_limit(), Weight::from_ref_time(1024).set_proof_size(u64::MAX)); + assert_eq!(System::block_weight().total(), Weight::from_ref_time(1024 + 768)); assert_eq!(CheckWeight::::check_extrinsic_weight(&mandatory), Ok(())); }); } @@ -685,39 +665,45 @@ mod tests { fn no_max_total_should_still_be_limited_by_max_block() { // given let maximum_weight = BlockWeights::builder() - .base_block(0) + .base_block(Weight::zero()) .for_class(DispatchClass::non_mandatory(), |w| { - w.base_extrinsic = 0; - w.max_total = Some(20); + w.base_extrinsic = Weight::zero(); + w.max_total = Some(Weight::from_ref_time(20).set_proof_size(u64::MAX)); }) .for_class(DispatchClass::Mandatory, |w| { - w.base_extrinsic = 0; - w.reserved = Some(5); + w.base_extrinsic = Weight::zero(); + w.reserved = Some(Weight::from_ref_time(5).set_proof_size(u64::MAX)); w.max_total = None; }) .build_or_panic(); let all_weight = crate::ConsumedWeight::new(|class| match class { - DispatchClass::Normal => 10, - DispatchClass::Operational => 10, - DispatchClass::Mandatory => 0, + DispatchClass::Normal => Weight::from_ref_time(10), + DispatchClass::Operational => Weight::from_ref_time(10), + DispatchClass::Mandatory => Weight::zero(), }); - assert_eq!(maximum_weight.max_block, all_weight.total()); + assert_eq!(maximum_weight.max_block, all_weight.total().set_proof_size(u64::MAX)); // fits into reserved - let mandatory1 = - DispatchInfo { weight: 5, class: DispatchClass::Mandatory, ..Default::default() }; + let mandatory1 = DispatchInfo { + weight: Weight::from_ref_time(5), + class: DispatchClass::Mandatory, + ..Default::default() + }; // does not fit into reserved and the block is full. - let mandatory2 = - DispatchInfo { weight: 6, class: DispatchClass::Mandatory, ..Default::default() }; + let mandatory2 = DispatchInfo { + weight: Weight::from_ref_time(6), + class: DispatchClass::Mandatory, + ..Default::default() + }; // when - assert_ok!(calculate_consumed_weight::<::Call>( + assert_ok!(calculate_consumed_weight::<::RuntimeCall>( maximum_weight.clone(), all_weight.clone(), &mandatory1 )); assert_err!( - calculate_consumed_weight::<::Call>( + calculate_consumed_weight::<::RuntimeCall>( maximum_weight, all_weight, &mandatory2 diff --git a/frame/system/src/extensions/mod.rs b/frame/system/src/extensions/mod.rs index 0af9722e475d1..9ba73c5f4d4e5 100644 --- a/frame/system/src/extensions/mod.rs +++ b/frame/system/src/extensions/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,7 @@ pub mod check_genesis; pub mod check_mortality; +pub mod check_non_zero_sender; pub mod check_nonce; pub mod check_spec_version; pub mod check_tx_version; diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 2e7f26eef16f4..b41083538a325 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -66,14 +66,16 @@ #[cfg(feature = "std")] use serde::Serialize; +#[cfg(feature = "runtime-benchmarks")] +use sp_runtime::traits::TrailingZeroInput; use sp_runtime::{ generic, traits::{ self, AtLeast32Bit, AtLeast32BitUnsigned, BadOrigin, BlockNumberProvider, Bounded, - CheckEqual, Dispatchable, Hash, Lookup, LookupError, MaybeDisplay, MaybeMallocSizeOf, + CheckEqual, Dispatchable, Hash, Lookup, LookupError, MaybeDisplay, MaybeSerializeDeserialize, Member, One, Saturating, SimpleBitOps, StaticLookup, Zero, }, - DispatchError, Either, Perbill, RuntimeDebug, + DispatchError, RuntimeDebug, }; #[cfg(any(feature = "std", test))] use sp_std::map; @@ -82,20 +84,20 @@ use sp_version::RuntimeVersion; use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen}; use frame_support::{ - dispatch::{DispatchResult, DispatchResultWithPostInfo}, + dispatch::{ + extract_actual_pays_fee, extract_actual_weight, DispatchClass, DispatchInfo, + DispatchResult, DispatchResultWithPostInfo, PerDispatchClass, + }, storage, traits::{ - Contains, EnsureOrigin, Get, HandleLifetime, OnKilledAccount, OnNewAccount, OriginTrait, - PalletInfo, SortedMembers, StoredMap, - }, - weights::{ - extract_actual_weight, DispatchClass, DispatchInfo, PerDispatchClass, RuntimeDbWeight, - Weight, + ConstU32, Contains, EnsureOrigin, Get, HandleLifetime, OnKilledAccount, OnNewAccount, + OriginTrait, PalletInfo, SortedMembers, StoredMap, TypedGet, }, Parameter, }; use scale_info::TypeInfo; -use sp_core::{storage::well_known_keys, ChangesTrieConfiguration}; +use sp_core::storage::well_known_keys; +use sp_weights::{RuntimeDbWeight, Weight}; #[cfg(feature = "std")] use frame_support::traits::GenesisBuild; @@ -114,23 +116,33 @@ pub mod mocking; mod tests; pub mod weights; +pub mod migrations; + pub use extensions::{ - check_genesis::CheckGenesis, check_mortality::CheckMortality, check_nonce::CheckNonce, + check_genesis::CheckGenesis, check_mortality::CheckMortality, + check_non_zero_sender::CheckNonZeroSender, check_nonce::CheckNonce, check_spec_version::CheckSpecVersion, check_tx_version::CheckTxVersion, check_weight::CheckWeight, }; // Backward compatible re-export. pub use extensions::check_mortality::CheckMortality as CheckEra; +pub use frame_support::dispatch::RawOrigin; pub use weights::WeightInfo; /// Compute the trie root of a list of extrinsics. +/// +/// The merkle proof is using the same trie as runtime state with +/// `state_version` 0. pub fn extrinsics_root(extrinsics: &[E]) -> H::Output { extrinsics_data_root::(extrinsics.iter().map(codec::Encode::encode).collect()) } /// Compute the trie root of a list of extrinsics. +/// +/// The merkle proof is using the same trie as runtime state with +/// `state_version` 0. pub fn extrinsics_data_root(xts: Vec>) -> H::Output { - H::ordered_trie_root(xts) + H::ordered_trie_root(xts, sp_core::storage::StateVersion::V0) } /// An object to track the currently used extrinsic weight in a block. @@ -151,6 +163,36 @@ impl SetCode for () { } } +/// Numeric limits over the ability to add a consumer ref using `inc_consumers`. +pub trait ConsumerLimits { + /// The number of consumers over which `inc_consumers` will cease to work. + fn max_consumers() -> RefCount; + /// The maximum number of additional consumers expected to be over be added at once using + /// `inc_consumers_without_limit`. + /// + /// Note: This is not enforced and it's up to the chain's author to ensure this reflects the + /// actual situation. + fn max_overflow() -> RefCount; +} + +impl ConsumerLimits for ConstU32 { + fn max_consumers() -> RefCount { + Z + } + fn max_overflow() -> RefCount { + Z + } +} + +impl, MaxOverflow: Get> ConsumerLimits for (MaxNormal, MaxOverflow) { + fn max_consumers() -> RefCount { + MaxNormal::get() + } + fn max_overflow() -> RefCount { + MaxOverflow::get() + } +} + #[frame_support::pallet] pub mod pallet { use crate::{self as frame_system, pallet_prelude::*, *}; @@ -162,7 +204,7 @@ pub mod pallet { pub trait Config: 'static + Eq + Clone { /// The basic call filter to use in Origin. All origins are built with this filter as base, /// except Root. - type BaseCallFilter: Contains; + type BaseCallFilter: Contains; /// Block & extrinsics weights: base values and limits. #[pallet::constant] @@ -172,14 +214,17 @@ pub mod pallet { #[pallet::constant] type BlockLength: Get; - /// The `Origin` type used by dispatchable calls. - type Origin: Into, Self::Origin>> + /// The `RuntimeOrigin` type used by dispatchable calls. + type RuntimeOrigin: Into, Self::RuntimeOrigin>> + From> + Clone - + OriginTrait; + + OriginTrait; - /// The aggregated `Call` type. - type Call: Dispatchable + Debug; + /// The aggregated `RuntimeCall` type. + type RuntimeCall: Parameter + + Dispatchable + + Debug + + From>; /// Account index (aka nonce) type. This stores the number of previous transactions /// associated with a sender account. @@ -190,7 +235,8 @@ pub mod pallet { + Default + MaybeDisplay + AtLeast32Bit - + Copy; + + Copy + + MaxEncodedLen; /// The block number type used by the runtime. type BlockNumber: Parameter @@ -204,7 +250,6 @@ pub mod pallet { + Copy + sp_std::hash::Hash + sp_std::str::FromStr - + MaybeMallocSizeOf + MaxEncodedLen + TypeInfo; @@ -222,7 +267,6 @@ pub mod pallet { + sp_std::hash::Hash + AsRef<[u8]> + AsMut<[u8]> - + MaybeMallocSizeOf + MaxEncodedLen; /// The hashing system (algorithm) being used in the runtime (e.g. Blake2). @@ -235,7 +279,6 @@ pub mod pallet { + Debug + MaybeDisplay + Ord - + Default + MaxEncodedLen; /// Converting trait to take a source type and convert to `AccountId`. @@ -250,11 +293,11 @@ pub mod pallet { type Header: Parameter + traits::Header; /// The aggregated event type of the runtime. - type Event: Parameter + type RuntimeEvent: Parameter + Member + From> + Debug - + IsType<::Event>; + + IsType<::RuntimeEvent>; /// Maximum number of block number to block hash mappings to keep (oldest pruned first). #[pallet::constant] @@ -278,7 +321,7 @@ pub mod pallet { /// Data to be associated with an account (other than nonce/transaction counter, which this /// pallet does regardless). - type AccountData: Member + FullCodec + Clone + Default + TypeInfo; + type AccountData: Member + FullCodec + Clone + Default + TypeInfo + MaxEncodedLen; /// Handler for when a new account has just been created. type OnNewAccount: OnNewAccount; @@ -290,7 +333,7 @@ pub mod pallet { type SystemWeightInfo: WeightInfo; - /// The designated SS85 prefix of this chain. + /// The designated SS58 prefix of this chain. /// /// This replaces the "ss58Format" property declared in the chain spec. Reason is /// that the runtime should know about the prefix in order to make use of it as @@ -301,10 +344,14 @@ pub mod pallet { /// What to do if the runtime wants to change the code to something new. /// /// The default (`()`) implementation is responsible for setting the correct storage - /// entry and emitting corresponding event and log item. (see [`update_code_in_storage`]). + /// entry and emitting corresponding event and log item. (see + /// [`Pallet::update_code_in_storage`]). /// It's unlikely that this needs to be customized, unless you are writing a parachain using /// `Cumulus`, where the actual code change is deferred. type OnSetCode: SetCode; + + /// The maximum number of consumers allowed on a single account. + type MaxConsumers: ConsumerLimits; } #[pallet::pallet] @@ -313,15 +360,6 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { - fn on_runtime_upgrade() -> frame_support::weights::Weight { - if !UpgradedToTripleRefCount::::get() { - UpgradedToTripleRefCount::::put(true); - migrations::migrate_to_triple_ref_count::() - } else { - 0 - } - } - fn integrity_test() { T::BlockWeights::get().validate().expect("The weights are invalid."); } @@ -329,35 +367,20 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// A dispatch that will fill the block weight up to the given ratio. - // TODO: This should only be available for testing, rather than in general usage, but - // that's not possible at present (since it's within the pallet macro). - #[pallet::weight(*_ratio * T::BlockWeights::get().max_block)] - pub fn fill_block(origin: OriginFor, _ratio: Perbill) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - Ok(().into()) - } - /// Make some on-chain remark. /// /// # /// - `O(1)` /// # + #[pallet::call_index(0)] #[pallet::weight(T::SystemWeightInfo::remark(_remark.len() as u32))] pub fn remark(origin: OriginFor, _remark: Vec) -> DispatchResultWithPostInfo { - ensure_signed(origin)?; + ensure_signed_or_root(origin)?; Ok(().into()) } /// Set the number of pages in the WebAssembly environment's heap. - /// - /// # - /// - `O(1)` - /// - 1 storage write. - /// - Base Weight: 1.405 µs - /// - 1 write to HEAP_PAGES - /// - 1 digest item - /// # + #[pallet::call_index(1)] #[pallet::weight((T::SystemWeightInfo::set_heap_pages(), DispatchClass::Operational))] pub fn set_heap_pages(origin: OriginFor, pages: u64) -> DispatchResultWithPostInfo { ensure_root(origin)?; @@ -378,6 +401,7 @@ pub mod pallet { /// The weight of this function is dependent on the runtime, but generally this is very /// expensive. We will treat this as a full block. /// # + #[pallet::call_index(2)] #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] pub fn set_code(origin: OriginFor, code: Vec) -> DispatchResultWithPostInfo { ensure_root(origin)?; @@ -395,6 +419,7 @@ pub mod pallet { /// - 1 event. /// The weight of this function is dependent on the runtime. We will treat this as a full /// block. # + #[pallet::call_index(3)] #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] pub fn set_code_without_checks( origin: OriginFor, @@ -405,45 +430,8 @@ pub mod pallet { Ok(().into()) } - /// Set the new changes trie configuration. - /// - /// # - /// - `O(1)` - /// - 1 storage write or delete (codec `O(1)`). - /// - 1 call to `deposit_log`: Uses `append` API, so O(1) - /// - Base Weight: 7.218 µs - /// - DB Weight: - /// - Writes: Changes Trie, System Digest - /// # - #[pallet::weight((T::SystemWeightInfo::set_changes_trie_config(), DispatchClass::Operational))] - pub fn set_changes_trie_config( - origin: OriginFor, - changes_trie_config: Option, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - match changes_trie_config.clone() { - Some(changes_trie_config) => storage::unhashed::put_raw( - well_known_keys::CHANGES_TRIE_CONFIG, - &changes_trie_config.encode(), - ), - None => storage::unhashed::kill(well_known_keys::CHANGES_TRIE_CONFIG), - } - - let log = generic::DigestItem::ChangesTrieSignal( - generic::ChangesTrieSignal::NewConfiguration(changes_trie_config), - ); - Self::deposit_log(log.into()); - Ok(().into()) - } - /// Set some items of storage. - /// - /// # - /// - `O(I)` where `I` length of `items` - /// - `I` storage writes (`O(1)`). - /// - Base Weight: 0.568 * i µs - /// - Writes: Number of items - /// # + #[pallet::call_index(4)] #[pallet::weight(( T::SystemWeightInfo::set_storage(items.len() as u32), DispatchClass::Operational, @@ -460,13 +448,7 @@ pub mod pallet { } /// Kill some items from storage. - /// - /// # - /// - `O(IK)` where `I` length of `keys` and `K` length of one key - /// - `I` storage deletions. - /// - Base Weight: .378 * i µs - /// - Writes: Number of items - /// # + #[pallet::call_index(5)] #[pallet::weight(( T::SystemWeightInfo::kill_storage(keys.len() as u32), DispatchClass::Operational, @@ -474,7 +456,7 @@ pub mod pallet { pub fn kill_storage(origin: OriginFor, keys: Vec) -> DispatchResultWithPostInfo { ensure_root(origin)?; for key in &keys { - storage::unhashed::kill(&key); + storage::unhashed::kill(key); } Ok(().into()) } @@ -483,13 +465,7 @@ pub mod pallet { /// /// **NOTE:** We rely on the Root origin to provide us the number of subkeys under /// the prefix we are removing to accurately calculate the weight of this function. - /// - /// # - /// - `O(P)` where `P` amount of keys with prefix `prefix` - /// - `P` storage deletions. - /// - Base Weight: 0.834 * P µs - /// - Writes: Number of subkeys + 1 - /// # + #[pallet::call_index(6)] #[pallet::weight(( T::SystemWeightInfo::kill_prefix(_subkeys.saturating_add(1)), DispatchClass::Operational, @@ -500,16 +476,12 @@ pub mod pallet { _subkeys: u32, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; - storage::unhashed::kill_prefix(&prefix, None); + let _ = storage::unhashed::clear_prefix(&prefix, None, None); Ok(().into()) } /// Make some on-chain remark and emit event. - /// - /// # - /// - `O(b)` where b is the length of the remark. - /// - 1 event. - /// # + #[pallet::call_index(7)] #[pallet::weight(T::SystemWeightInfo::remark_with_event(remark.len() as u32))] pub fn remark_with_event( origin: OriginFor, @@ -517,7 +489,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let hash = T::Hashing::hash(&remark[..]); - Self::deposit_event(Event::Remarked(who, hash)); + Self::deposit_event(Event::Remarked { sender: who, hash }); Ok(().into()) } } @@ -525,24 +497,20 @@ pub mod pallet { /// Event for the System pallet. #[pallet::event] pub enum Event { - /// An extrinsic completed successfully. \[info\] - ExtrinsicSuccess(DispatchInfo), - /// An extrinsic failed. \[error, info\] - ExtrinsicFailed(DispatchError, DispatchInfo), + /// An extrinsic completed successfully. + ExtrinsicSuccess { dispatch_info: DispatchInfo }, + /// An extrinsic failed. + ExtrinsicFailed { dispatch_error: DispatchError, dispatch_info: DispatchInfo }, /// `:code` was updated. CodeUpdated, - /// A new \[account\] was created. - NewAccount(T::AccountId), - /// An \[account\] was reaped. - KilledAccount(T::AccountId), - /// On on-chain remark happened. \[origin, remark_hash\] - Remarked(T::AccountId, T::Hash), + /// A new account was created. + NewAccount { account: T::AccountId }, + /// An account was reaped. + KilledAccount { account: T::AccountId }, + /// On on-chain remark happened. + Remarked { sender: T::AccountId, hash: T::Hash }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - /// Error for the System pallet #[pallet::error] pub enum Error { @@ -560,6 +528,8 @@ pub mod pallet { NonDefaultComposite, /// There is a non-zero reference count preventing the account from being purged. NonZeroRefCount, + /// The origin filter prevent the call to be dispatched. + CallFiltered, } /// Exposed trait-generic origin type. @@ -583,6 +553,7 @@ pub mod pallet { /// The current weight for the block. #[pallet::storage] + #[pallet::whitelist_storage] #[pallet::getter(fn block_weight)] pub(super) type BlockWeight = StorageValue<_, ConsumedWeight, ValueQuery>; @@ -599,11 +570,13 @@ pub mod pallet { /// Extrinsics data for the current block (maps an extrinsic's index to its data). #[pallet::storage] #[pallet::getter(fn extrinsic_data)] + #[pallet::unbounded] pub(super) type ExtrinsicData = StorageMap<_, Twox64Concat, u32, Vec, ValueQuery>; /// The current block number being processed. Set by `execute_block`. #[pallet::storage] + #[pallet::whitelist_storage] #[pallet::getter(fn block_number)] pub(super) type Number = StorageValue<_, T::BlockNumber, ValueQuery>; @@ -614,19 +587,26 @@ pub mod pallet { /// Digest of the current block, also part of the block header. #[pallet::storage] + #[pallet::unbounded] #[pallet::getter(fn digest)] - pub(super) type Digest = StorageValue<_, DigestOf, ValueQuery>; + pub(super) type Digest = StorageValue<_, generic::Digest, ValueQuery>; /// Events deposited for the current block. /// - /// NOTE: This storage item is explicitly unbounded since it is never intended to be read - /// from within the runtime. + /// NOTE: The item is unbound and should therefore never be read on chain. + /// It could otherwise inflate the PoV size of a block. + /// + /// Events have a large in-memory size. Box the events to not go out-of-memory + /// just in case someone still reads them from within the runtime. #[pallet::storage] + #[pallet::whitelist_storage] + #[pallet::unbounded] pub(super) type Events = - StorageValue<_, Vec>, ValueQuery>; + StorageValue<_, Vec>>, ValueQuery>; /// The number of events in the `Events` list. #[pallet::storage] + #[pallet::whitelist_storage] #[pallet::getter(fn event_count)] pub(super) type EventCount = StorageValue<_, EventIndex, ValueQuery>; @@ -641,12 +621,14 @@ pub mod pallet { /// the `EventIndex` then in case if the topic has the same contents on the next block /// no notification will be triggered thus the event might be lost. #[pallet::storage] + #[pallet::unbounded] #[pallet::getter(fn event_topics)] pub(super) type EventTopics = StorageMap<_, Blake2_128Concat, T::Hash, Vec<(T::BlockNumber, EventIndex)>, ValueQuery>; /// Stores the `spec_version` and `spec_name` of when the last runtime upgrade happened. #[pallet::storage] + #[pallet::unbounded] pub type LastRuntimeUpgrade = StorageValue<_, LastRuntimeUpgradeInfo>; /// True if we have upgraded so that `type RefCount` is `u32`. False (default) if not. @@ -660,22 +642,16 @@ pub mod pallet { /// The execution phase of the block. #[pallet::storage] + #[pallet::whitelist_storage] pub(super) type ExecutionPhase = StorageValue<_, Phase>; + #[cfg_attr(feature = "std", derive(Default))] #[pallet::genesis_config] pub struct GenesisConfig { - pub changes_trie_config: Option, #[serde(with = "sp_core::bytes")] pub code: Vec, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { changes_trie_config: Default::default(), code: Default::default() } - } - } - #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { @@ -687,56 +663,10 @@ pub mod pallet { sp_io::storage::set(well_known_keys::CODE, &self.code); sp_io::storage::set(well_known_keys::EXTRINSIC_INDEX, &0u32.encode()); - if let Some(ref changes_trie_config) = self.changes_trie_config { - sp_io::storage::set( - well_known_keys::CHANGES_TRIE_CONFIG, - &changes_trie_config.encode(), - ); - } } } } -pub mod migrations { - use super::*; - - #[allow(dead_code)] - /// Migrate from unique `u8` reference counting to triple `u32` reference counting. - pub fn migrate_all() -> frame_support::weights::Weight { - Account::::translate::<(T::Index, u8, T::AccountData), _>(|_key, (nonce, rc, data)| { - Some(AccountInfo { - nonce, - consumers: rc as RefCount, - providers: 1, - sufficients: 0, - data, - }) - }); - T::BlockWeights::get().max_block - } - - #[allow(dead_code)] - /// Migrate from unique `u32` reference counting to triple `u32` reference counting. - pub fn migrate_to_dual_ref_count() -> frame_support::weights::Weight { - Account::::translate::<(T::Index, RefCount, T::AccountData), _>( - |_key, (nonce, consumers, data)| { - Some(AccountInfo { nonce, consumers, providers: 1, sufficients: 0, data }) - }, - ); - T::BlockWeights::get().max_block - } - - /// Migrate from dual `u32` reference counting to triple `u32` reference counting. - pub fn migrate_to_triple_ref_count() -> frame_support::weights::Weight { - Account::::translate::<(T::Index, RefCount, RefCount, T::AccountData), _>( - |_key, (nonce, consumers, providers, data)| { - Some(AccountInfo { nonce, consumers, providers, sufficients: 0, data }) - }, - ); - T::BlockWeights::get().max_block - } -} - #[cfg(feature = "std")] impl GenesisConfig { /// Direct implementation of `GenesisBuild::build_storage`. @@ -757,14 +687,11 @@ impl GenesisConfig { } } -pub type DigestOf = generic::Digest<::Hash>; -pub type DigestItemOf = generic::DigestItem<::Hash>; - pub type Key = Vec; pub type KeyValue = (Vec, Vec); /// A phase of a block's execution. -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(Serialize, PartialEq, Eq, Clone))] pub enum Phase { /// Applying an extrinsic. @@ -793,28 +720,6 @@ pub struct EventRecord { pub topics: Vec, } -/// Origin for the System pallet. -#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo)] -pub enum RawOrigin { - /// The system itself ordained this dispatch to happen: this is the highest privilege level. - Root, - /// It is signed by some public key and we provide the `AccountId`. - Signed(AccountId), - /// It is signed by nobody, can be either: - /// * included and agreed upon by the validators anyway, - /// * or unsigned transaction validated by a pallet. - None, -} - -impl From> for RawOrigin { - fn from(s: Option) -> RawOrigin { - match s { - Some(who) => RawOrigin::Signed(who), - None => RawOrigin::None, - } - } -} - // Create a Hash with 69 for each byte, // only used to build genesis config. #[cfg(feature = "std")] @@ -834,7 +739,7 @@ type EventIndex = u32; pub type RefCount = u32; /// Information of an account. -#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] +#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct AccountInfo { /// The number of transactions this account has sent. pub nonce: Index, @@ -876,6 +781,7 @@ impl From for LastRuntimeUpgradeInfo { } } +/// Ensure the origin is Root. pub struct EnsureRoot(sp_std::marker::PhantomData); impl, O>> + From>, AccountId> EnsureOrigin for EnsureRoot @@ -889,13 +795,62 @@ impl, O>> + From>, Acco } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Root) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::Root)) } } +/// Ensure the origin is Root and return the provided `Success` value. +pub struct EnsureRootWithSuccess( + sp_std::marker::PhantomData<(AccountId, Success)>, +); +impl< + O: Into, O>> + From>, + AccountId, + Success: TypedGet, + > EnsureOrigin for EnsureRootWithSuccess +{ + type Success = Success::Type; + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + RawOrigin::Root => Ok(Success::get()), + r => Err(O::from(r)), + }) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::Root)) + } +} + +/// Ensure the origin is provided `Ensure` origin and return the provided `Success` value. +pub struct EnsureWithSuccess( + sp_std::marker::PhantomData<(Ensure, AccountId, Success)>, +); + +impl< + O: Into, O>> + From>, + Ensure: EnsureOrigin, + AccountId, + Success: TypedGet, + > EnsureOrigin for EnsureWithSuccess +{ + type Success = Success::Type; + + fn try_origin(o: O) -> Result { + Ensure::try_origin(o).map(|_| Success::get()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ensure::try_successful_origin() + } +} + +/// Ensure the origin is any `Signed` origin. pub struct EnsureSigned(sp_std::marker::PhantomData); -impl, O>> + From>, AccountId: Default> +impl, O>> + From>, AccountId: Decode> EnsureOrigin for EnsureSigned { type Success = AccountId; @@ -907,16 +862,19 @@ impl, O>> + From>, Acco } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Signed(Default::default())) + fn try_successful_origin() -> Result { + let zero_account_id = + AccountId::decode(&mut TrailingZeroInput::zeroes()).map_err(|_| ())?; + Ok(O::from(RawOrigin::Signed(zero_account_id))) } } +/// Ensure the origin is `Signed` origin from the given `AccountId`. pub struct EnsureSignedBy(sp_std::marker::PhantomData<(Who, AccountId)>); impl< O: Into, O>> + From>, Who: SortedMembers, - AccountId: PartialEq + Clone + Ord + Default, + AccountId: PartialEq + Clone + Ord + Decode, > EnsureOrigin for EnsureSignedBy { type Success = AccountId; @@ -928,16 +886,19 @@ impl< } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { + fn try_successful_origin() -> Result { + let zero_account_id = + AccountId::decode(&mut TrailingZeroInput::zeroes()).map_err(|_| ())?; let members = Who::sorted_members(); let first_member = match members.get(0) { Some(account) => account.clone(), - None => Default::default(), + None => zero_account_id, }; - O::from(RawOrigin::Signed(first_member.clone())) + Ok(O::from(RawOrigin::Signed(first_member))) } } +/// Ensure the origin is `None`. i.e. unsigned transaction. pub struct EnsureNone(sp_std::marker::PhantomData); impl, O>> + From>, AccountId> EnsureOrigin for EnsureNone @@ -951,11 +912,12 @@ impl, O>> + From>, Acco } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::None) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::None)) } } +/// Always fail. pub struct EnsureNever(sp_std::marker::PhantomData); impl EnsureOrigin for EnsureNever { type Success = T; @@ -964,31 +926,8 @@ impl EnsureOrigin for EnsureNever { } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - unimplemented!() - } -} - -/// The "OR gate" implementation of `EnsureOrigin`. -/// -/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. -pub struct EnsureOneOf(sp_std::marker::PhantomData<(AccountId, L, R)>); -impl< - AccountId, - O: Into, O>> + From>, - L: EnsureOrigin, - R: EnsureOrigin, - > EnsureOrigin for EnsureOneOf -{ - type Success = Either; - fn try_origin(o: O) -> Result { - L::try_origin(o) - .map_or_else(|o| R::try_origin(o).map(|o| Either::Right(o)), |o| Ok(Either::Left(o))) - } - - #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - L::successful_origin() + fn try_successful_origin() -> Result { + Err(()) } } @@ -1004,6 +943,22 @@ where } } +/// Ensure that the origin `o` represents either a signed extrinsic (i.e. transaction) or the root. +/// Returns `Ok` with the account that signed the extrinsic, `None` if it was root, or an `Err` +/// otherwise. +pub fn ensure_signed_or_root( + o: OuterOrigin, +) -> Result, BadOrigin> +where + OuterOrigin: Into, OuterOrigin>>, +{ + match o.into() { + Ok(RawOrigin::Root) => Ok(None), + Ok(RawOrigin::Signed(t)) => Ok(Some(t)), + _ => Err(BadOrigin), + } +} + /// Ensure that the origin `o` represents the root. Returns `Ok` or an `Err` otherwise. pub fn ensure_root(o: OuterOrigin) -> Result<(), BadOrigin> where @@ -1026,27 +981,6 @@ where } } -/// A type of block initialization to perform. -pub enum InitKind { - /// Leave inspectable storage entries in state. - /// - /// i.e. `Events` are not being reset. - /// Should only be used for off-chain calls, - /// regular block execution should clear those. - Inspection, - - /// Reset also inspectable storage entries. - /// - /// This should be used for regular block execution. - Full, -} - -impl Default for InitKind { - fn default() -> Self { - InitKind::Full - } -} - /// Reference status; can be either referenced or unreferenced. #[derive(RuntimeDebug)] pub enum RefStatus { @@ -1239,8 +1173,27 @@ impl Pallet { /// Increment the reference counter on an account. /// - /// The account `who`'s `providers` must be non-zero or this will return an error. + /// The account `who`'s `providers` must be non-zero and the current number of consumers must + /// be less than `MaxConsumers::max_consumers()` or this will return an error. pub fn inc_consumers(who: &T::AccountId) -> Result<(), DispatchError> { + Account::::try_mutate(who, |a| { + if a.providers > 0 { + if a.consumers < T::MaxConsumers::max_consumers() { + a.consumers = a.consumers.saturating_add(1); + Ok(()) + } else { + Err(DispatchError::TooManyConsumers) + } + } else { + Err(DispatchError::NoProviders) + } + }) + } + + /// Increment the reference counter on an account, ignoring the `MaxConsumers` limits. + /// + /// The account `who`'s `providers` must be non-zero or this will return an error. + pub fn inc_consumers_without_limit(who: &T::AccountId) -> Result<(), DispatchError> { Account::::try_mutate(who, |a| { if a.providers > 0 { a.consumers = a.consumers.saturating_add(1); @@ -1284,11 +1237,12 @@ impl Pallet { /// True if the account has at least one provider reference. pub fn can_inc_consumer(who: &T::AccountId) -> bool { - Account::::get(who).providers > 0 + let a = Account::::get(who); + a.providers > 0 && a.consumers < T::MaxConsumers::max_consumers() } /// Deposits an event into this block's event record. - pub fn deposit_event(event: impl Into) { + pub fn deposit_event(event: impl Into) { Self::deposit_event_indexed(&[], event.into()); } @@ -1297,7 +1251,7 @@ impl Pallet { /// /// This will update storage entries that correspond to the specified topics. /// It is expected that light-clients could subscribe to this topics. - pub fn deposit_event_indexed(topics: &[T::Hash], event: T::Event) { + pub fn deposit_event_indexed(topics: &[T::Hash], event: T::RuntimeEvent) { let block_number = Self::block_number(); // Don't populate events on genesis. if block_number.is_zero() { @@ -1305,8 +1259,7 @@ impl Pallet { } let phase = ExecutionPhase::::get().unwrap_or_default(); - let event = - EventRecord { phase, event, topics: topics.iter().cloned().collect::>() }; + let event = EventRecord { phase, event, topics: topics.to_vec() }; // Index of the to be added event. let event_idx = { @@ -1321,7 +1274,7 @@ impl Pallet { old_event_count }; - Events::::append(&event); + Events::::append(event); for topic in topics { >::append(topic, &(block_number, event_idx)); @@ -1364,12 +1317,7 @@ impl Pallet { } /// Start the execution of a particular block. - pub fn initialize( - number: &T::BlockNumber, - parent_hash: &T::Hash, - digest: &DigestOf, - kind: InitKind, - ) { + pub fn initialize(number: &T::BlockNumber, parent_hash: &T::Hash, digest: &generic::Digest) { // populate environment ExecutionPhase::::put(Phase::Initialization); storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &0u32); @@ -1380,18 +1328,46 @@ impl Pallet { // Remove previous block data from storage BlockWeight::::kill(); - - // Kill inspectable storage entries in state when `InitKind::Full`. - if let InitKind::Full = kind { - >::kill(); - EventCount::::kill(); - >::remove_all(None); - } } /// Remove temporary "environment" entries in storage, compute the storage root and return the /// resulting header for this block. pub fn finalize() -> T::Header { + log::debug!( + target: "runtime::system", + "[{:?}] {} extrinsics, length: {} (normal {}%, op: {}%, mandatory {}%) / normal weight:\ + {} ({}%) op weight {} ({}%) / mandatory weight {} ({}%)", + Self::block_number(), + Self::extrinsic_index().unwrap_or_default(), + Self::all_extrinsics_len(), + sp_runtime::Percent::from_rational( + Self::all_extrinsics_len(), + *T::BlockLength::get().max.get(DispatchClass::Normal) + ).deconstruct(), + sp_runtime::Percent::from_rational( + Self::all_extrinsics_len(), + *T::BlockLength::get().max.get(DispatchClass::Operational) + ).deconstruct(), + sp_runtime::Percent::from_rational( + Self::all_extrinsics_len(), + *T::BlockLength::get().max.get(DispatchClass::Mandatory) + ).deconstruct(), + Self::block_weight().get(DispatchClass::Normal), + sp_runtime::Percent::from_rational( + Self::block_weight().get(DispatchClass::Normal).ref_time(), + T::BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap_or(Bounded::max_value()).ref_time() + ).deconstruct(), + Self::block_weight().get(DispatchClass::Operational), + sp_runtime::Percent::from_rational( + Self::block_weight().get(DispatchClass::Operational).ref_time(), + T::BlockWeights::get().get(DispatchClass::Operational).max_total.unwrap_or(Bounded::max_value()).ref_time() + ).deconstruct(), + Self::block_weight().get(DispatchClass::Mandatory), + sp_runtime::Percent::from_rational( + Self::block_weight().get(DispatchClass::Mandatory).ref_time(), + T::BlockWeights::get().get(DispatchClass::Mandatory).max_total.unwrap_or(Bounded::max_value()).ref_time() + ).deconstruct(), + ); ExecutionPhase::::kill(); AllExtrinsicsLen::::kill(); @@ -1407,7 +1383,7 @@ impl Pallet { // stay to be inspected by the client and will be cleared by `Self::initialize`. let number = >::get(); let parent_hash = >::get(); - let mut digest = >::get(); + let digest = >::get(); let extrinsics = (0..ExtrinsicCount::::take().unwrap_or_default()) .map(ExtrinsicData::::take) @@ -1423,19 +1399,9 @@ impl Pallet { >::remove(to_remove); } - let storage_root = T::Hash::decode(&mut &sp_io::storage::root()[..]) + let version = T::Version::get().state_version(); + let storage_root = T::Hash::decode(&mut &sp_io::storage::root(version)[..]) .expect("Node is configured to use the same hash; qed"); - let storage_changes_root = sp_io::storage::changes_root(&parent_hash.encode()); - - // we can't compute changes trie root earlier && put it to the Digest - // because it will include all currently existing temporaries. - if let Some(storage_changes_root) = storage_changes_root { - let item = generic::DigestItem::ChangesTrieRoot( - T::Hash::decode(&mut &storage_changes_root[..]) - .expect("Node is configured to use the same hash; qed"), - ); - digest.push(item); - } ::new( number, @@ -1452,7 +1418,7 @@ impl Pallet { /// - `O(1)` /// - 1 storage write (codec `O(1)`) /// # - pub fn deposit_log(item: DigestItemOf) { + pub fn deposit_log(item: generic::DigestItem) { >::append(item); } @@ -1475,15 +1441,17 @@ impl Pallet { /// impact on the PoV size of a block. Users should use alternative and well bounded storage /// items for any behavior like this. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] - pub fn events() -> Vec> { - Self::read_events_no_consensus() + pub fn events() -> Vec> { + // Dereferencing the events here is fine since we are not in the + // memory-restricted runtime. + Self::read_events_no_consensus().into_iter().map(|e| *e).collect() } /// Get the current events deposited by the runtime. /// /// Should only be called if you know what you are doing and outside of the runtime block /// execution else it can have a large impact on the PoV size of a block. - pub fn read_events_no_consensus() -> Vec> { + pub fn read_events_no_consensus() -> Vec>> { Events::::get() } @@ -1516,24 +1484,25 @@ impl Pallet { AllExtrinsicsLen::::put(len as u32); } - /// Reset events. Can be used as an alternative to - /// `initialize` for tests that don't need to bother with the other environment entries. - #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + /// Reset events. + /// + /// This needs to be used in prior calling [`initialize`](Self::initialize) for each new block + /// to clear events from previous block. pub fn reset_events() { >::kill(); EventCount::::kill(); - >::remove_all(None); + let _ = >::clear(u32::max_value(), None); } /// Assert the given `event` exists. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] - pub fn assert_has_event(event: T::Event) { + pub fn assert_has_event(event: T::RuntimeEvent) { assert!(Self::events().iter().any(|record| record.event == event)) } /// Assert the last event equal to the given `event`. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] - pub fn assert_last_event(event: T::Event) { + pub fn assert_last_event(event: T::RuntimeEvent) { assert_eq!(Self::events().last().expect("events expected").event, event); } @@ -1561,10 +1530,17 @@ impl Pallet { } /// To be called immediately after an extrinsic has been applied. + /// + /// Emits an `ExtrinsicSuccess` or `ExtrinsicFailed` event depending on the outcome. + /// The emitted event contains the post-dispatch corrected weight including + /// the base-weight for its dispatch class. pub fn note_applied_extrinsic(r: &DispatchResultWithPostInfo, mut info: DispatchInfo) { - info.weight = extract_actual_weight(r, &info); + info.weight = extract_actual_weight(r, &info) + .saturating_add(T::BlockWeights::get().get(info.class).base_extrinsic); + info.pays_fee = extract_actual_pays_fee(r, &info); + Self::deposit_event(match r { - Ok(_) => Event::ExtrinsicSuccess(info), + Ok(_) => Event::ExtrinsicSuccess { dispatch_info: info }, Err(err) => { log::trace!( target: "runtime::system", @@ -1572,7 +1548,7 @@ impl Pallet { Self::block_number(), err, ); - Event::ExtrinsicFailed(err.error, info) + Event::ExtrinsicFailed { dispatch_error: err.error, dispatch_info: info } }, }); @@ -1600,13 +1576,13 @@ impl Pallet { /// An account is being created. pub fn on_created_account(who: T::AccountId, _a: &mut AccountInfo) { T::OnNewAccount::on_new_account(&who); - Self::deposit_event(Event::NewAccount(who)); + Self::deposit_event(Event::NewAccount { account: who }); } /// Do anything that needs to be done after an account has been killed. fn on_killed_account(who: T::AccountId) { T::OnKilledAccount::on_killed_account(&who); - Self::deposit_event(Event::KilledAccount(who)); + Self::deposit_event(Event::KilledAccount { account: who }); } /// Determine whether or not it is possible to update the code. @@ -1616,16 +1592,16 @@ impl Pallet { /// of the old and new runtime has the same spec name and that the spec version is increasing. pub fn can_set_code(code: &[u8]) -> Result<(), sp_runtime::DispatchError> { let current_version = T::Version::get(); - let new_version = sp_io::misc::runtime_version(&code) + let new_version = sp_io::misc::runtime_version(code) .and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok()) - .ok_or_else(|| Error::::FailedToExtractRuntimeVersion)?; + .ok_or(Error::::FailedToExtractRuntimeVersion)?; if new_version.spec_name != current_version.spec_name { - Err(Error::::InvalidSpecName)? + return Err(Error::::InvalidSpecName.into()) } if new_version.spec_version <= current_version.spec_version { - Err(Error::::SpecVersionNeedsToIncrease)? + return Err(Error::::SpecVersionNeedsToIncrease.into()) } Ok(()) @@ -1749,10 +1725,10 @@ impl Lookup for ChainContext { /// Prelude to be used alongside pallet macro, for ease of use. pub mod pallet_prelude { - pub use crate::{ensure_none, ensure_root, ensure_signed}; + pub use crate::{ensure_none, ensure_root, ensure_signed, ensure_signed_or_root}; /// Type alias for the `Origin` associated type of system config. - pub type OriginFor = ::Origin; + pub type OriginFor = ::RuntimeOrigin; /// Type alias for the `BlockNumber` associated type of system config. pub type BlockNumberFor = ::BlockNumber; diff --git a/frame/system/src/limits.rs b/frame/system/src/limits.rs index 687fb6f3dd367..54d27c5b9e86d 100644 --- a/frame/system/src/limits.rs +++ b/frame/system/src/limits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,9 +25,12 @@ //! `DispatchClass`. This module contains configuration object for both resources, //! which should be passed to `frame_system` configuration when runtime is being set up. -use frame_support::weights::{constants, DispatchClass, OneOrMany, PerDispatchClass, Weight}; +use frame_support::{ + dispatch::{DispatchClass, OneOrMany, PerDispatchClass}, + weights::{constants, Weight}, +}; use scale_info::TypeInfo; -use sp_runtime::{Perbill, RuntimeDebug}; +use sp_runtime::{traits::Bounded, Perbill, RuntimeDebug}; /// Block length limit configuration. #[derive(RuntimeDebug, Clone, codec::Encode, codec::Decode, TypeInfo)] @@ -204,7 +207,10 @@ pub struct BlockWeights { impl Default for BlockWeights { fn default() -> Self { - Self::with_sensible_defaults(1 * constants::WEIGHT_PER_SECOND, DEFAULT_NORMAL_RATIO) + Self::with_sensible_defaults( + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + DEFAULT_NORMAL_RATIO, + ) } } @@ -217,7 +223,7 @@ impl BlockWeights { /// Verifies correctness of this `BlockWeights` object. pub fn validate(self) -> ValidationResult { fn or_max(w: Option) -> Weight { - w.unwrap_or_else(|| Weight::max_value()) + w.unwrap_or_else(Weight::max_value) } let mut error = ValidationErrors::default(); @@ -229,15 +235,18 @@ impl BlockWeights { // Make sure that if total is set it's greater than base_block && // base_for_class error_assert!( - (max_for_class > self.base_block && max_for_class > base_for_class) - || max_for_class == 0, + (max_for_class.all_gt(self.base_block) && max_for_class.all_gt(base_for_class)) + || max_for_class == Weight::zero(), &mut error, "[{:?}] {:?} (total) has to be greater than {:?} (base block) & {:?} (base extrinsic)", class, max_for_class, self.base_block, base_for_class, ); // Max extrinsic can't be greater than max_for_class. error_assert!( - weights.max_extrinsic.unwrap_or(0) <= max_for_class.saturating_sub(base_for_class), + weights + .max_extrinsic + .unwrap_or(Weight::zero()) + .all_lte(max_for_class.saturating_sub(base_for_class)), &mut error, "[{:?}] {:?} (max_extrinsic) can't be greater than {:?} (max for class)", class, @@ -246,14 +255,14 @@ impl BlockWeights { ); // Max extrinsic should not be 0 error_assert!( - weights.max_extrinsic.unwrap_or_else(|| Weight::max_value()) > 0, + weights.max_extrinsic.unwrap_or_else(Weight::max_value).all_gt(Weight::zero()), &mut error, "[{:?}] {:?} (max_extrinsic) must not be 0. Check base cost and average initialization cost.", class, weights.max_extrinsic, ); // Make sure that if reserved is set it's greater than base_for_class. error_assert!( - reserved > base_for_class || reserved == 0, + reserved.all_gt(base_for_class) || reserved == Weight::zero(), &mut error, "[{:?}] {:?} (reserved) has to be greater than {:?} (base extrinsic) if set", class, @@ -262,7 +271,7 @@ impl BlockWeights { ); // Make sure max block is greater than max_total if it's set. error_assert!( - self.max_block >= weights.max_total.unwrap_or(0), + self.max_block.all_gte(weights.max_total.unwrap_or(Weight::zero())), &mut error, "[{:?}] {:?} (max block) has to be greater than {:?} (max for class)", class, @@ -271,7 +280,7 @@ impl BlockWeights { ); // Make sure we can fit at least one extrinsic. error_assert!( - self.max_block > base_for_class + self.base_block, + self.max_block.all_gt(base_for_class + self.base_block), &mut error, "[{:?}] {:?} (max block) must fit at least one extrinsic {:?} (base weight)", class, @@ -294,9 +303,9 @@ impl BlockWeights { /// is not suitable for production deployments. pub fn simple_max(block_weight: Weight) -> Self { Self::builder() - .base_block(0) + .base_block(Weight::zero()) .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = 0; + weights.base_extrinsic = Weight::zero(); }) .for_class(DispatchClass::non_mandatory(), |weights| { weights.max_total = block_weight.into(); @@ -333,9 +342,10 @@ impl BlockWeights { BlockWeightsBuilder { weights: BlockWeights { base_block: constants::BlockExecutionWeight::get(), - max_block: 0, + max_block: Weight::zero(), per_class: PerDispatchClass::new(|class| { - let initial = if class == DispatchClass::Mandatory { None } else { Some(0) }; + let initial = + if class == DispatchClass::Mandatory { None } else { Some(Weight::zero()) }; WeightsPerClass { base_extrinsic: constants::ExtrinsicBaseWeight::get(), max_extrinsic: None, @@ -369,7 +379,7 @@ impl BlockWeightsBuilder { /// /// This is to make sure that extrinsics don't stay forever in the pool, /// because they could seamingly fit the block (since they are below `max_block`), - /// but the cost of calling `on_initialize` alway prevents them from being included. + /// but the cost of calling `on_initialize` always prevents them from being included. pub fn avg_block_initialization(mut self, init_cost: Perbill) -> Self { self.init_cost = Some(init_cost); self @@ -398,7 +408,7 @@ impl BlockWeightsBuilder { // compute max block size. for class in DispatchClass::all() { weights.max_block = match weights.per_class.get(*class).max_total { - Some(max) if max > weights.max_block => max, + Some(max) => max.max(weights.max_block), _ => weights.max_block, }; } diff --git a/frame/system/src/migrations/mod.rs b/frame/system/src/migrations/mod.rs new file mode 100644 index 0000000000000..15746d7376ac5 --- /dev/null +++ b/frame/system/src/migrations/mod.rs @@ -0,0 +1,121 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Migrate the reference counting state. + +use crate::{Config, Pallet}; +use codec::{Decode, Encode, FullCodec}; +use frame_support::{ + pallet_prelude::ValueQuery, traits::PalletInfoAccess, weights::Weight, Blake2_128Concat, + RuntimeDebug, +}; +use sp_std::prelude::*; + +/// Type used to encode the number of references an account has. +type RefCount = u32; + +/// Information of an account. +#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode)] +struct AccountInfo { + nonce: Index, + consumers: RefCount, + providers: RefCount, + sufficients: RefCount, + data: AccountData, +} + +/// Trait to implement to give information about types used for migration +pub trait V2ToV3 { + /// The system pallet. + type Pallet: 'static + PalletInfoAccess; + + /// System config account id + type AccountId: 'static + FullCodec; + + /// System config index + type Index: 'static + FullCodec + Copy; + + /// System config account data + type AccountData: 'static + FullCodec; +} + +#[frame_support::storage_alias] +type UpgradedToU32RefCount = StorageValue, bool, ValueQuery>; + +#[frame_support::storage_alias] +type UpgradedToTripleRefCount = StorageValue, bool, ValueQuery>; + +#[frame_support::storage_alias] +type Account = StorageMap< + Pallet, + Blake2_128Concat, + ::AccountId, + AccountInfo<::Index, ::AccountData>, +>; + +/// Migrate from unique `u8` reference counting to triple `u32` reference counting. +pub fn migrate_from_single_u8_to_triple_ref_count() -> Weight { + let mut translated: usize = 0; + >::translate::<(V::Index, u8, V::AccountData), _>(|_key, (nonce, rc, data)| { + translated += 1; + Some(AccountInfo { nonce, consumers: rc as RefCount, providers: 1, sufficients: 0, data }) + }); + log::info!( + target: "runtime::system", + "Applied migration from single u8 to triple reference counting to {:?} elements.", + translated + ); + >::put(true); + >::put(true); + Weight::MAX +} + +/// Migrate from unique `u32` reference counting to triple `u32` reference counting. +pub fn migrate_from_single_to_triple_ref_count() -> Weight { + let mut translated: usize = 0; + >::translate::<(V::Index, RefCount, V::AccountData), _>( + |_key, (nonce, consumers, data)| { + translated += 1; + Some(AccountInfo { nonce, consumers, providers: 1, sufficients: 0, data }) + }, + ); + log::info!( + target: "runtime::system", + "Applied migration from single to triple reference counting to {:?} elements.", + translated + ); + >::put(true); + Weight::MAX +} + +/// Migrate from dual `u32` reference counting to triple `u32` reference counting. +pub fn migrate_from_dual_to_triple_ref_count() -> Weight { + let mut translated: usize = 0; + >::translate::<(V::Index, RefCount, RefCount, V::AccountData), _>( + |_key, (nonce, consumers, providers, data)| { + translated += 1; + Some(AccountInfo { nonce, consumers, providers, sufficients: 0, data }) + }, + ); + log::info!( + target: "runtime::system", + "Applied migration from dual to triple reference counting to {:?} elements.", + translated + ); + >::put(true); + Weight::MAX +} diff --git a/frame/system/src/mock.rs b/frame/system/src/mock.rs index 9dd35691cab84..fb230f66a94f7 100644 --- a/frame/system/src/mock.rs +++ b/frame/system/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,14 +16,16 @@ // limitations under the License. use crate::{self as frame_system, *}; -use frame_support::parameter_types; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + BuildStorage, Perbill, }; -use sp_std::cell::RefCell; type UncheckedExtrinsic = mocking::MockUncheckedExtrinsic; type Block = mocking::MockBlock; @@ -39,10 +41,9 @@ frame_support::construct_runtime!( ); const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); -const MAX_BLOCK_WEIGHT: Weight = 1024; +const MAX_BLOCK_WEIGHT: Weight = Weight::from_ref_time(1024).set_proof_size(u64::MAX); parameter_types! { - pub const BlockHashCount: u64 = 10; pub Version: RuntimeVersion = RuntimeVersion { spec_name: sp_version::create_runtime_str!("test"), impl_name: sp_version::create_runtime_str!("system-test"), @@ -51,20 +52,22 @@ parameter_types! { impl_version: 1, apis: sp_version::create_apis_vec!([]), transaction_version: 1, + state_version: 1, }; pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 10, write: 100, }; pub RuntimeBlockWeights: limits::BlockWeights = limits::BlockWeights::builder() - .base_block(10) + .base_block(Weight::from_ref_time(10)) .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = 5; + weights.base_extrinsic = Weight::from_ref_time(5); }) .for_class(DispatchClass::Normal, |weights| { weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAX_BLOCK_WEIGHT); }) .for_class(DispatchClass::Operational, |weights| { + weights.base_extrinsic = Weight::from_ref_time(10); weights.max_total = Some(MAX_BLOCK_WEIGHT); weights.reserved = Some( MAX_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAX_BLOCK_WEIGHT @@ -76,14 +79,14 @@ parameter_types! { limits::BlockLength::max_with_normal_ratio(1024, NORMAL_DISPATCH_RATIO); } -thread_local! { - pub static KILLED: RefCell> = RefCell::new(vec![]); +parameter_types! { + pub static Killed: Vec = vec![]; } pub struct RecordKilled; impl OnKilledAccount for RecordKilled { fn on_killed_account(who: &u64) { - KILLED.with(|r| r.borrow_mut().push(*who)) + Killed::mutate(|r| r.push(*who)) } } @@ -91,8 +94,8 @@ impl Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -100,8 +103,8 @@ impl Config for Test { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<10>; type DbWeight = DbWeight; type Version = Version; type PalletInfo = PalletInfo; @@ -111,13 +114,14 @@ impl Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } pub type SysEvent = frame_system::Event; /// A simple call, which one doesn't matter. -pub const CALL: &::Call = - &Call::System(frame_system::Call::set_heap_pages { pages: 0u64 }); +pub const CALL: &::RuntimeCall = + &RuntimeCall::System(frame_system::Call::set_heap_pages { pages: 0u64 }); /// Create new externalities for `System` module tests. pub fn new_test_ext() -> sp_io::TestExternalities { diff --git a/frame/system/src/mocking.rs b/frame/system/src/mocking.rs index 7e6026b726186..d8cfcb9baf268 100644 --- a/frame/system/src/mocking.rs +++ b/frame/system/src/mocking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use sp_runtime::generic; /// An unchecked extrinsic type to be used in tests. pub type MockUncheckedExtrinsic = generic::UncheckedExtrinsic< ::AccountId, - ::Call, + ::RuntimeCall, Signature, Extra, >; diff --git a/frame/system/src/offchain.rs b/frame/system/src/offchain.rs index ed758a2556b77..99a4c1541d30f 100644 --- a/frame/system/src/offchain.rs +++ b/frame/system/src/offchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,11 +62,7 @@ use sp_runtime::{ app_crypto::RuntimeAppPublic, traits::{Extrinsic as ExtrinsicT, IdentifyAccount, One}, }; -use sp_std::{ - collections::btree_set::BTreeSet, - convert::{TryFrom, TryInto}, - prelude::{Box, Vec}, -}; +use sp_std::{collections::btree_set::BTreeSet, prelude::*}; /// Marker struct used to flag using all supported keys to sign a payload. pub struct ForAll {} @@ -92,7 +88,7 @@ where call: >::OverarchingCall, signature: Option<::SignaturePayload>, ) -> Result<(), ()> { - let xt = T::Extrinsic::new(call.into(), signature).ok_or(())?; + let xt = T::Extrinsic::new(call, signature).ok_or(())?; sp_io::offchain::submit_transaction(xt.encode()) } @@ -167,7 +163,7 @@ impl, X> Signer keystore_accounts.map(|account| account.public).collect(); Box::new( - keys.into_iter() + keys.iter() .enumerate() .map(|(index, key)| { let account_id = key.clone().into_account(); @@ -621,7 +617,7 @@ pub trait SignedPayload: Encode { #[cfg(test)] mod tests { use super::*; - use crate::mock::{Call, Test as TestRuntime, CALL}; + use crate::mock::{RuntimeCall, Test as TestRuntime, CALL}; use codec::Decode; use sp_core::offchain::{testing, TransactionPoolExt}; use sp_runtime::testing::{TestSignature, TestXt, UintAuthorityId}; @@ -631,11 +627,11 @@ mod tests { type Signature = TestSignature; } - type Extrinsic = TestXt; + type Extrinsic = TestXt; - impl SendTransactionTypes for TestRuntime { + impl SendTransactionTypes for TestRuntime { type Extrinsic = Extrinsic; - type OverarchingCall = Call; + type OverarchingCall = RuntimeCall; } #[derive(codec::Encode, codec::Decode)] diff --git a/frame/system/src/tests.rs b/frame/system/src/tests.rs index a4dd3403f2c3a..c42131c450228 100644 --- a/frame/system/src/tests.rs +++ b/frame/system/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,9 +17,10 @@ use crate::*; use frame_support::{ - assert_noop, assert_ok, dispatch::PostDispatchInfo, weights::WithPostDispatchInfo, + assert_noop, assert_ok, + dispatch::{Pays, PostDispatchInfo, WithPostDispatchInfo}, }; -use mock::{Origin, *}; +use mock::{RuntimeOrigin, *}; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, Header}, @@ -28,8 +29,8 @@ use sp_runtime::{ #[test] fn origin_works() { - let o = Origin::from(RawOrigin::::Signed(1u64)); - let x: Result, Origin> = o.into(); + let o = RuntimeOrigin::from(RawOrigin::::Signed(1u64)); + let x: Result, RuntimeOrigin> = o.into(); assert_eq!(x.unwrap(), RawOrigin::::Signed(1u64)); } @@ -53,9 +54,9 @@ fn stored_map_works() { System::dec_consumers(&0); assert!(!System::is_provider_required(&0)); - assert!(KILLED.with(|r| r.borrow().is_empty())); + assert!(Killed::get().is_empty()); assert_ok!(System::remove(&0)); - assert_eq!(KILLED.with(|r| r.borrow().clone()), vec![0u64]); + assert_eq!(Killed::get(), vec![0u64]); }); } @@ -154,7 +155,8 @@ fn provider_required_to_support_consumer() { #[test] fn deposit_event_should_work() { new_test_ext().execute_with(|| { - System::initialize(&1, &[0u8; 32].into(), &Default::default(), InitKind::Full); + System::reset_events(); + System::initialize(&1, &[0u8; 32].into(), &Default::default()); System::note_finished_extrinsics(); System::deposit_event(SysEvent::CodeUpdated); System::finalize(); @@ -167,45 +169,53 @@ fn deposit_event_should_work() { }] ); - System::initialize(&2, &[0u8; 32].into(), &Default::default(), InitKind::Full); - System::deposit_event(SysEvent::NewAccount(32)); + let normal_base = ::BlockWeights::get() + .get(DispatchClass::Normal) + .base_extrinsic; + + System::reset_events(); + System::initialize(&2, &[0u8; 32].into(), &Default::default()); + System::deposit_event(SysEvent::NewAccount { account: 32 }); System::note_finished_initialize(); - System::deposit_event(SysEvent::KilledAccount(42)); + System::deposit_event(SysEvent::KilledAccount { account: 42 }); System::note_applied_extrinsic(&Ok(().into()), Default::default()); System::note_applied_extrinsic(&Err(DispatchError::BadOrigin.into()), Default::default()); System::note_finished_extrinsics(); - System::deposit_event(SysEvent::NewAccount(3)); + System::deposit_event(SysEvent::NewAccount { account: 3 }); System::finalize(); assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, - event: SysEvent::NewAccount(32).into(), + event: SysEvent::NewAccount { account: 32 }.into(), topics: vec![], }, EventRecord { phase: Phase::ApplyExtrinsic(0), - event: SysEvent::KilledAccount(42).into(), + event: SysEvent::KilledAccount { account: 42 }.into(), topics: vec![] }, EventRecord { phase: Phase::ApplyExtrinsic(0), - event: SysEvent::ExtrinsicSuccess(Default::default()).into(), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { weight: normal_base, ..Default::default() } + } + .into(), topics: vec![] }, EventRecord { phase: Phase::ApplyExtrinsic(1), - event: SysEvent::ExtrinsicFailed( - DispatchError::BadOrigin.into(), - Default::default() - ) + event: SysEvent::ExtrinsicFailed { + dispatch_error: DispatchError::BadOrigin.into(), + dispatch_info: DispatchInfo { weight: normal_base, ..Default::default() } + } .into(), topics: vec![] }, EventRecord { phase: Phase::Finalization, - event: SysEvent::NewAccount(3).into(), + event: SysEvent::NewAccount { account: 3 }.into(), topics: vec![] }, ] @@ -214,12 +224,16 @@ fn deposit_event_should_work() { } #[test] -fn deposit_event_uses_actual_weight() { +fn deposit_event_uses_actual_weight_and_pays_fee() { new_test_ext().execute_with(|| { - System::initialize(&1, &[0u8; 32].into(), &Default::default(), InitKind::Full); + System::reset_events(); + System::initialize(&1, &[0u8; 32].into(), &Default::default()); System::note_finished_initialize(); - let pre_info = DispatchInfo { weight: 1000, ..Default::default() }; + let normal_base = ::BlockWeights::get() + .get(DispatchClass::Normal) + .base_extrinsic; + let pre_info = DispatchInfo { weight: Weight::from_ref_time(1000), ..Default::default() }; System::note_applied_extrinsic(&Ok(Some(300).into()), pre_info); System::note_applied_extrinsic(&Ok(Some(1000).into()), pre_info); System::note_applied_extrinsic( @@ -227,49 +241,204 @@ fn deposit_event_uses_actual_weight() { &Ok(Some(1200).into()), pre_info, ); - System::note_applied_extrinsic(&Err(DispatchError::BadOrigin.with_weight(999)), pre_info); + System::note_applied_extrinsic(&Ok((Some(2_500_000), Pays::Yes).into()), pre_info); + System::note_applied_extrinsic(&Ok(Pays::No.into()), pre_info); + System::note_applied_extrinsic(&Ok((Some(2_500_000), Pays::No).into()), pre_info); + System::note_applied_extrinsic(&Ok((Some(500), Pays::No).into()), pre_info); + System::note_applied_extrinsic( + &Err(DispatchError::BadOrigin.with_weight(Weight::from_ref_time(999))), + pre_info, + ); - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: SysEvent::ExtrinsicSuccess(DispatchInfo { - weight: 300, - ..Default::default() - },) - .into(), - topics: vec![] - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: SysEvent::ExtrinsicSuccess(DispatchInfo { - weight: 1000, - ..Default::default() - },) - .into(), - topics: vec![] - }, - EventRecord { - phase: Phase::ApplyExtrinsic(2), - event: SysEvent::ExtrinsicSuccess(DispatchInfo { - weight: 1000, - ..Default::default() - },) - .into(), - topics: vec![] + System::note_applied_extrinsic( + &Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes }, + error: DispatchError::BadOrigin, + }), + pre_info, + ); + System::note_applied_extrinsic( + &Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(Weight::from_ref_time(800)), + pays_fee: Pays::Yes, }, - EventRecord { - phase: Phase::ApplyExtrinsic(3), - event: SysEvent::ExtrinsicFailed( - DispatchError::BadOrigin.into(), - DispatchInfo { weight: 999, ..Default::default() }, - ) - .into(), - topics: vec![] + error: DispatchError::BadOrigin, + }), + pre_info, + ); + System::note_applied_extrinsic( + &Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(Weight::from_ref_time(800)), + pays_fee: Pays::No, }, - ] + error: DispatchError::BadOrigin, + }), + pre_info, ); + // Also works for operational. + let operational_base = ::BlockWeights::get() + .get(DispatchClass::Operational) + .base_extrinsic; + assert!(normal_base != operational_base, "Test pre-condition violated"); + let pre_info = DispatchInfo { + weight: Weight::from_ref_time(1000), + class: DispatchClass::Operational, + ..Default::default() + }; + System::note_applied_extrinsic(&Ok(Some(300).into()), pre_info); + + let got = System::events(); + let want = vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(300).saturating_add(normal_base), + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(1000).saturating_add(normal_base), + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(2), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(1000).saturating_add(normal_base), + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(3), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(1000).saturating_add(normal_base), + pays_fee: Pays::Yes, + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(4), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(1000).saturating_add(normal_base), + pays_fee: Pays::No, + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(5), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(1000).saturating_add(normal_base), + pays_fee: Pays::No, + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(6), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(500).saturating_add(normal_base), + pays_fee: Pays::No, + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(7), + event: SysEvent::ExtrinsicFailed { + dispatch_error: DispatchError::BadOrigin.into(), + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(999).saturating_add(normal_base), + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(8), + event: SysEvent::ExtrinsicFailed { + dispatch_error: DispatchError::BadOrigin.into(), + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(1000).saturating_add(normal_base), + pays_fee: Pays::Yes, + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(9), + event: SysEvent::ExtrinsicFailed { + dispatch_error: DispatchError::BadOrigin.into(), + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(800).saturating_add(normal_base), + pays_fee: Pays::Yes, + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(10), + event: SysEvent::ExtrinsicFailed { + dispatch_error: DispatchError::BadOrigin.into(), + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(800).saturating_add(normal_base), + pays_fee: Pays::No, + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(11), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: Weight::from_ref_time(300).saturating_add(operational_base), + class: DispatchClass::Operational, + ..Default::default() + }, + } + .into(), + topics: vec![], + }, + ]; + for (i, event) in want.into_iter().enumerate() { + assert_eq!(got[i], event, "Event mismatch at index {}", i); + } }); } @@ -278,15 +447,16 @@ fn deposit_event_topics() { new_test_ext().execute_with(|| { const BLOCK_NUMBER: u64 = 1; - System::initialize(&BLOCK_NUMBER, &[0u8; 32].into(), &Default::default(), InitKind::Full); + System::reset_events(); + System::initialize(&BLOCK_NUMBER, &[0u8; 32].into(), &Default::default()); System::note_finished_extrinsics(); let topics = vec![H256::repeat_byte(1), H256::repeat_byte(2), H256::repeat_byte(3)]; // We deposit a few events with different sets of topics. - System::deposit_event_indexed(&topics[0..3], SysEvent::NewAccount(1).into()); - System::deposit_event_indexed(&topics[0..1], SysEvent::NewAccount(2).into()); - System::deposit_event_indexed(&topics[1..2], SysEvent::NewAccount(3).into()); + System::deposit_event_indexed(&topics[0..3], SysEvent::NewAccount { account: 1 }.into()); + System::deposit_event_indexed(&topics[0..1], SysEvent::NewAccount { account: 2 }.into()); + System::deposit_event_indexed(&topics[1..2], SysEvent::NewAccount { account: 3 }.into()); System::finalize(); @@ -296,17 +466,17 @@ fn deposit_event_topics() { vec![ EventRecord { phase: Phase::Finalization, - event: SysEvent::NewAccount(1).into(), + event: SysEvent::NewAccount { account: 1 }.into(), topics: topics[0..3].to_vec(), }, EventRecord { phase: Phase::Finalization, - event: SysEvent::NewAccount(2).into(), + event: SysEvent::NewAccount { account: 2 }.into(), topics: topics[0..1].to_vec(), }, EventRecord { phase: Phase::Finalization, - event: SysEvent::NewAccount(3).into(), + event: SysEvent::NewAccount { account: 3 }.into(), topics: topics[1..2].to_vec(), } ] @@ -336,7 +506,8 @@ fn prunes_block_hash_mappings() { new_test_ext().execute_with(|| { // simulate import of 15 blocks for n in 1..=15 { - System::initialize(&n, &[n as u8 - 1; 32].into(), &Default::default(), InitKind::Full); + System::reset_events(); + System::initialize(&n, &[n as u8 - 1; 32].into(), &Default::default()); System::finalize(); } @@ -464,21 +635,11 @@ fn events_not_emitted_during_genesis() { }); } -#[test] -fn ensure_one_of_works() { - fn ensure_root_or_signed(o: RawOrigin) -> Result, Origin> { - EnsureOneOf::, EnsureSigned>::try_origin(o.into()) - } - - assert_eq!(ensure_root_or_signed(RawOrigin::Root).unwrap(), Either::Left(())); - assert_eq!(ensure_root_or_signed(RawOrigin::Signed(0)).unwrap(), Either::Right(0)); - assert!(ensure_root_or_signed(RawOrigin::None).is_err()); -} - #[test] fn extrinsics_root_is_calculated_correctly() { new_test_ext().execute_with(|| { - System::initialize(&1, &[0u8; 32].into(), &Default::default(), InitKind::Full); + System::reset_events(); + System::initialize(&1, &[0u8; 32].into(), &Default::default()); System::note_finished_initialize(); System::note_extrinsic(vec![1]); System::note_applied_extrinsic(&Ok(().into()), Default::default()); @@ -495,8 +656,32 @@ fn extrinsics_root_is_calculated_correctly() { #[test] fn runtime_updated_digest_emitted_when_heap_pages_changed() { new_test_ext().execute_with(|| { - System::initialize(&1, &[0u8; 32].into(), &Default::default(), InitKind::Full); + System::reset_events(); + System::initialize(&1, &[0u8; 32].into(), &Default::default()); System::set_heap_pages(RawOrigin::Root.into(), 5).unwrap(); assert_runtime_updated_digest(1); }); } + +#[test] +fn ensure_signed_stuff_works() { + struct Members; + impl SortedMembers for Members { + fn sorted_members() -> Vec { + (0..10).collect() + } + } + + let signed_origin = RuntimeOrigin::signed(0u64); + assert_ok!(EnsureSigned::try_origin(signed_origin.clone())); + assert_ok!(EnsureSignedBy::::try_origin(signed_origin)); + + #[cfg(feature = "runtime-benchmarks")] + { + let successful_origin: RuntimeOrigin = EnsureSigned::successful_origin(); + assert_ok!(EnsureSigned::try_origin(successful_origin)); + + let successful_origin: RuntimeOrigin = EnsureSignedBy::::successful_origin(); + assert_ok!(EnsureSignedBy::::try_origin(successful_origin)); + } +} diff --git a/frame/system/src/weights.rs b/frame/system/src/weights.rs index 281d26375c81b..696a6a09b8f80 100644 --- a/frame/system/src/weights.rs +++ b/frame/system/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for frame_system //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/system/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,7 +50,6 @@ pub trait WeightInfo { fn remark(b: u32, ) -> Weight; fn remark_with_event(b: u32, ) -> Weight; fn set_heap_pages() -> Weight; - fn set_changes_trie_config() -> Weight; fn set_storage(i: u32, ) -> Weight; fn kill_storage(i: u32, ) -> Weight; fn kill_prefix(p: u32, ) -> Weight; @@ -57,94 +58,106 @@ pub trait WeightInfo { /// Weights for frame_system using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// The range of component `b` is `[0, 3932160]`. fn remark(b: u32, ) -> Weight { - (574_000 as Weight) + // Minimum execution time: 3_951 nanoseconds. + Weight::from_ref_time(1_307_232 as u64) // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(Weight::from_ref_time(363 as u64).saturating_mul(b as u64)) } + /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { - (0 as Weight) + // Minimum execution time: 14_880 nanoseconds. + Weight::from_ref_time(15_173_000 as u64) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(Weight::from_ref_time(1_424 as u64).saturating_mul(b as u64)) } + // Storage: System Digest (r:1 w:1) // Storage: unknown [0x3a686561707061676573] (r:0 w:1) fn set_heap_pages() -> Weight { - (1_891_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - // Storage: System Digest (r:1 w:1) - // Storage: unknown [0x3a6368616e6765735f74726965] (r:0 w:1) - fn set_changes_trie_config() -> Weight { - (7_370_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 9_819 nanoseconds. + Weight::from_ref_time(10_513_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[0, 1000]`. fn set_storage(i: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 0 - .saturating_add((848_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + // Minimum execution time: 4_038 nanoseconds. + Weight::from_ref_time(4_098_000 as u64) + // Standard Error: 710 + .saturating_add(Weight::from_ref_time(620_813 as u64).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[0, 1000]`. fn kill_storage(i: u32, ) -> Weight { - (308_000 as Weight) - // Standard Error: 0 - .saturating_add((559_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + // Minimum execution time: 3_972 nanoseconds. + Weight::from_ref_time(4_082_000 as u64) + // Standard Error: 884 + .saturating_add(Weight::from_ref_time(536_923 as u64).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `p` is `[0, 1000]`. fn kill_prefix(p: u32, ) -> Weight { - (7_616_000 as Weight) - // Standard Error: 1_000 - .saturating_add((783_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) + // Minimum execution time: 5_703 nanoseconds. + Weight::from_ref_time(5_763_000 as u64) + // Standard Error: 1_248 + .saturating_add(Weight::from_ref_time(1_126_062 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(p as u64))) } } // For backwards compatibility and tests impl WeightInfo for () { + /// The range of component `b` is `[0, 3932160]`. fn remark(b: u32, ) -> Weight { - (574_000 as Weight) + // Minimum execution time: 3_951 nanoseconds. + Weight::from_ref_time(1_307_232 as u64) // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(Weight::from_ref_time(363 as u64).saturating_mul(b as u64)) } + /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { - (0 as Weight) + // Minimum execution time: 14_880 nanoseconds. + Weight::from_ref_time(15_173_000 as u64) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(Weight::from_ref_time(1_424 as u64).saturating_mul(b as u64)) } + // Storage: System Digest (r:1 w:1) // Storage: unknown [0x3a686561707061676573] (r:0 w:1) fn set_heap_pages() -> Weight { - (1_891_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - // Storage: System Digest (r:1 w:1) - // Storage: unknown [0x3a6368616e6765735f74726965] (r:0 w:1) - fn set_changes_trie_config() -> Weight { - (7_370_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 9_819 nanoseconds. + Weight::from_ref_time(10_513_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[0, 1000]`. fn set_storage(i: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 0 - .saturating_add((848_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + // Minimum execution time: 4_038 nanoseconds. + Weight::from_ref_time(4_098_000 as u64) + // Standard Error: 710 + .saturating_add(Weight::from_ref_time(620_813 as u64).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[0, 1000]`. fn kill_storage(i: u32, ) -> Weight { - (308_000 as Weight) - // Standard Error: 0 - .saturating_add((559_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + // Minimum execution time: 3_972 nanoseconds. + Weight::from_ref_time(4_082_000 as u64) + // Standard Error: 884 + .saturating_add(Weight::from_ref_time(536_923 as u64).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `p` is `[0, 1000]`. fn kill_prefix(p: u32, ) -> Weight { - (7_616_000 as Weight) - // Standard Error: 1_000 - .saturating_add((783_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) + // Minimum execution time: 5_703 nanoseconds. + Weight::from_ref_time(5_763_000 as u64) + // Standard Error: 1_248 + .saturating_add(Weight::from_ref_time(1_126_062 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(p as u64))) } } diff --git a/frame/timestamp/Cargo.toml b/frame/timestamp/Cargo.toml index 1c95c4782b5c4..df63ed0d72b8e 100644 --- a/frame/timestamp/Cargo.toml +++ b/frame/timestamp/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-timestamp" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME Timestamp Module" documentation = "https://docs.rs/pallet-timestamp" @@ -13,37 +13,37 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io", optional = true } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } +sp-io = { version = "7.0.0", default-features = false, optional = true, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../primitives/timestamp" } -log = { version = "0.4.14", default-features = false } [dev-dependencies] -sp-io ={ version = "4.0.0-dev", path = "../../primitives/io" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "sp-inherents/std", + "sp-io?/std", "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", - "frame-benchmarking/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "sp-timestamp/std", "log/std", + "scale-info/std", + "sp-inherents/std", + "sp-runtime/std", + "sp-std/std", + "sp-timestamp/std", ] -runtime-benchmarks = ["frame-benchmarking", "sp-io"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "sp-io"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/timestamp/README.md b/frame/timestamp/README.md index 5f8388b04f829..1546377ee6743 100644 --- a/frame/timestamp/README.md +++ b/frame/timestamp/README.md @@ -45,20 +45,29 @@ trait from the timestamp trait. ### Get current timestamp ```rust -use frame_support::{decl_module, dispatch}; -use frame_system::ensure_signed; - -pub trait Config: timestamp::Config {} - -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - #[weight = 0] - pub fn get_time(origin) -> dispatch::DispatchResult { - let _sender = ensure_signed(origin)?; - let _now = >::get(); - Ok(()) - } - } +use pallet_timestamp::{self as timestamp}; + +#[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: frame_system::Config + timestamp::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn get_time(origin: OriginFor) -> DispatchResult { + let _sender = ensure_signed(origin)?; + let _now = >::get(); + Ok(()) + } + } } ``` diff --git a/frame/timestamp/src/benchmarking.rs b/frame/timestamp/src/benchmarking.rs index 97ddd4cddd63f..8974eb95692af 100644 --- a/frame/timestamp/src/benchmarking.rs +++ b/frame/timestamp/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, TrackedStorageKey}; +use frame_benchmarking::{benchmarks, TrackedStorageKey}; use frame_support::{ensure, traits::OnFinalize}; use frame_system::RawOrigin; @@ -55,6 +55,6 @@ benchmarks! { verify { ensure!(!DidUpdate::::exists(), "Time was not removed."); } -} -impl_benchmark_test_suite!(Timestamp, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(Timestamp, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/timestamp/src/lib.rs b/frame/timestamp/src/lib.rs index 153606bedbacf..e859474c2cb9e 100644 --- a/frame/timestamp/src/lib.rs +++ b/frame/timestamp/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -64,18 +64,26 @@ //! ### Get current timestamp //! //! ``` -//! use frame_support::{decl_module, dispatch}; -//! # use pallet_timestamp as timestamp; -//! use frame_system::ensure_signed; +//! use pallet_timestamp::{self as timestamp}; //! -//! pub trait Config: timestamp::Config {} +//! #[frame_support::pallet] +//! pub mod pallet { +//! use super::*; +//! use frame_support::pallet_prelude::*; +//! use frame_system::pallet_prelude::*; //! -//! decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { -//! #[weight = 0] -//! pub fn get_time(origin) -> dispatch::DispatchResult { +//! #[pallet::pallet] +//! pub struct Pallet(_); +//! +//! #[pallet::config] +//! pub trait Config: frame_system::Config + timestamp::Config {} +//! +//! #[pallet::call] +//! impl Pallet { +//! #[pallet::weight(0)] +//! pub fn get_time(origin: OriginFor) -> DispatchResult { //! let _sender = ensure_signed(origin)?; -//! let _now = >::get(); +//! let _now = >::get(); //! Ok(()) //! } //! } @@ -95,6 +103,10 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; pub mod weights; use frame_support::traits::{OnTimestampSet, Time, UnixTime}; @@ -140,7 +152,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::generate_storage_info] pub struct Pallet(PhantomData); /// Current time for the current block. @@ -187,6 +198,7 @@ pub mod pallet { /// `on_finalize`) /// - 1 event handler `on_timestamp_set`. Must be `O(1)`. /// # + #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::set(), DispatchClass::Mandatory @@ -222,7 +234,7 @@ pub mod pallet { let data = (*inherent_data).saturated_into::(); let next_time = cmp::max(data, Self::now() + T::MinimumPeriod::get()); - Some(Call::set { now: next_time.into() }) + Some(Call::set { now: next_time }) } fn check_inherent( @@ -233,7 +245,7 @@ pub mod pallet { sp_timestamp::Timestamp::new(30 * 1000); let t: u64 = match call { - Call::set { ref now } => now.clone().saturated_into::(), + Call::set { ref now } => (*now).saturated_into::(), _ => return Ok(()), }; @@ -271,6 +283,8 @@ impl Pallet { #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] pub fn set_timestamp(now: T::Moment) { Now::::put(now); + DidUpdate::::put(true); + >::on_timestamp_set(now); } } @@ -302,106 +316,3 @@ impl UnixTime for Pallet { core::time::Duration::from_millis(now.saturated_into::()) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate as pallet_timestamp; - - use frame_support::{assert_ok, parameter_types}; - use sp_core::H256; - use sp_io::TestExternalities; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - }; - - pub fn new_test_ext() -> TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - TestExternalities::new(t) - } - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlock; - - frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - } - ); - - parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); - } - impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Call = Call; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - } - parameter_types! { - pub const MinimumPeriod: u64 = 5; - } - impl Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); - } - - #[test] - fn timestamp_works() { - new_test_ext().execute_with(|| { - Timestamp::set_timestamp(42); - assert_ok!(Timestamp::set(Origin::none(), 69)); - assert_eq!(Timestamp::now(), 69); - }); - } - - #[test] - #[should_panic(expected = "Timestamp must be updated only once in the block")] - fn double_timestamp_should_fail() { - new_test_ext().execute_with(|| { - Timestamp::set_timestamp(42); - assert_ok!(Timestamp::set(Origin::none(), 69)); - let _ = Timestamp::set(Origin::none(), 70); - }); - } - - #[test] - #[should_panic( - expected = "Timestamp must increment by at least between sequential blocks" - )] - fn block_period_minimum_enforced() { - new_test_ext().execute_with(|| { - Timestamp::set_timestamp(42); - let _ = Timestamp::set(Origin::none(), 46); - }); - } -} diff --git a/frame/timestamp/src/mock.rs b/frame/timestamp/src/mock.rs new file mode 100644 index 0000000000000..2208510f24fe5 --- /dev/null +++ b/frame/timestamp/src/mock.rs @@ -0,0 +1,110 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Tests Utilities. + +use super::*; +use crate as pallet_timestamp; + +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_io::TestExternalities; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +type Moment = u64; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub static CapturedMoment: Option = None; +} + +pub struct MockOnTimestampSet; +impl OnTimestampSet for MockOnTimestampSet { + fn on_timestamp_set(moment: Moment) { + CapturedMoment::mutate(|x| *x = Some(moment)); + } +} + +impl Config for Test { + type Moment = Moment; + type OnTimestampSet = MockOnTimestampSet; + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +pub(crate) fn clear_captured_moment() { + CapturedMoment::mutate(|x| *x = None); +} + +pub(crate) fn get_captured_moment() -> Option { + CapturedMoment::get() +} + +pub(crate) fn new_test_ext() -> TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + clear_captured_moment(); + TestExternalities::new(t) +} diff --git a/frame/timestamp/src/tests.rs b/frame/timestamp/src/tests.rs new file mode 100644 index 0000000000000..6a76fbc4820e6 --- /dev/null +++ b/frame/timestamp/src/tests.rs @@ -0,0 +1,51 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Tests for the Timestamp module. + +use crate::mock::*; +use frame_support::assert_ok; + +#[test] +fn timestamp_works() { + new_test_ext().execute_with(|| { + crate::Now::::put(46); + assert_ok!(Timestamp::set(RuntimeOrigin::none(), 69)); + assert_eq!(Timestamp::now(), 69); + assert_eq!(Some(69), get_captured_moment()); + }); +} + +#[test] +#[should_panic(expected = "Timestamp must be updated only once in the block")] +fn double_timestamp_should_fail() { + new_test_ext().execute_with(|| { + Timestamp::set_timestamp(42); + assert_ok!(Timestamp::set(RuntimeOrigin::none(), 69)); + }); +} + +#[test] +#[should_panic( + expected = "Timestamp must increment by at least between sequential blocks" +)] +fn block_period_minimum_enforced() { + new_test_ext().execute_with(|| { + crate::Now::::put(44); + let _ = Timestamp::set(RuntimeOrigin::none(), 46); + }); +} diff --git a/frame/timestamp/src/weights.rs b/frame/timestamp/src/weights.rs index b4e7370ee7616..52123920977da 100644 --- a/frame/timestamp/src/weights.rs +++ b/frame/timestamp/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_timestamp //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/timestamp/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -55,12 +57,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:1) // Storage: Babe CurrentSlot (r:1 w:0) fn set() -> Weight { - (10_391_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 11_331 nanoseconds. + Weight::from_ref_time(11_584_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } fn on_finalize() -> Weight { - (4_843_000 as Weight) + // Minimum execution time: 5_280 nanoseconds. + Weight::from_ref_time(5_412_000 as u64) } } @@ -69,11 +73,13 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:1) // Storage: Babe CurrentSlot (r:1 w:0) fn set() -> Weight { - (10_391_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 11_331 nanoseconds. + Weight::from_ref_time(11_584_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } fn on_finalize() -> Weight { - (4_843_000 as Weight) + // Minimum execution time: 5_280 nanoseconds. + Weight::from_ref_time(5_412_000 as u64) } } diff --git a/frame/tips/Cargo.toml b/frame/tips/Cargo.toml index 8ca395e1c5416..7d0576ec28cce 100644 --- a/frame/tips/Cargo.toml +++ b/frame/tips/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-tips" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet to manage tips" readme = "README.md" @@ -13,44 +13,41 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.126", features = ["derive"], optional = true } - -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } - +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-storage = { version = "4.0.0-dev", path = "../../primitives/storage" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-storage = { version = "7.0.0", path = "../../primitives/storage" } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "codec/std", + "frame-support/std", + "frame-system/std", "log/std", + "pallet-treasury/std", "scale-info/std", "serde", - "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", - "pallet-treasury/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/tips/src/benchmarking.rs b/frame/tips/src/benchmarking.rs index 5e08121855210..312424e5799ec 100644 --- a/frame/tips/src/benchmarking.rs +++ b/frame/tips/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ #![cfg(feature = "runtime-benchmarks")] -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; use frame_support::ensure; use frame_system::RawOrigin; use sp_runtime::traits::Saturating; @@ -30,7 +30,7 @@ use crate::Pallet as TipsMod; const SEED: u32 = 0; // Create the pre-requisite information needed to create a `report_awesome`. -fn setup_awesome(length: u32) -> (T::AccountId, Vec, T::AccountId) { +fn setup_awesome, I: 'static>(length: u32) -> (T::AccountId, Vec, T::AccountId) { let caller = whitelisted_caller(); let value = T::TipReportDepositBase::get() + T::DataDepositPerByte::get() * length.into() + @@ -42,10 +42,10 @@ fn setup_awesome(length: u32) -> (T::AccountId, Vec, T::AccountId } // Create the pre-requisite information needed to call `tip_new`. -fn setup_tip( +fn setup_tip, I: 'static>( r: u32, t: u32, -) -> Result<(T::AccountId, Vec, T::AccountId, BalanceOf), &'static str> { +) -> Result<(T::AccountId, Vec, T::AccountId, BalanceOf), &'static str> { let tippers_count = T::Tippers::count(); for i in 0..t { @@ -64,13 +64,17 @@ fn setup_tip( // Create `t` new tips for the tip proposal with `hash`. // This function automatically makes the tip able to close. -fn create_tips(t: u32, hash: T::Hash, value: BalanceOf) -> Result<(), &'static str> { +fn create_tips, I: 'static>( + t: u32, + hash: T::Hash, + value: BalanceOf, +) -> Result<(), &'static str> { for i in 0..t { let caller = account("member", i, SEED); ensure!(T::Tippers::contains(&caller), "caller is not a tipper"); - TipsMod::::tip(RawOrigin::Signed(caller).into(), hash, value)?; + TipsMod::::tip(RawOrigin::Signed(caller).into(), hash, value)?; } - Tips::::mutate(hash, |maybe_tip| { + Tips::::mutate(hash, |maybe_tip| { if let Some(open_tip) = maybe_tip { open_tip.closes = Some(T::BlockNumber::zero()); } @@ -78,31 +82,30 @@ fn create_tips(t: u32, hash: T::Hash, value: BalanceOf) -> Result< Ok(()) } -fn setup_pot_account() { - let pot_account = TipsMod::::account_id(); +fn setup_pot_account, I: 'static>() { + let pot_account = TipsMod::::account_id(); let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000u32.into()); let _ = T::Currency::make_free_balance_be(&pot_account, value); } -const MAX_BYTES: u32 = 16384; -const MAX_TIPPERS: u32 = 100; - -benchmarks! { +benchmarks_instance_pallet! { report_awesome { - let r in 0 .. MAX_BYTES; - let (caller, reason, awesome_person) = setup_awesome::(r); + let r in 0 .. T::MaximumReasonLength::get(); + let (caller, reason, awesome_person) = setup_awesome::(r); + let awesome_person_lookup = T::Lookup::unlookup(awesome_person); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); - }: _(RawOrigin::Signed(caller), reason, awesome_person) + }: _(RawOrigin::Signed(caller), reason, awesome_person_lookup) retract_tip { - let r = MAX_BYTES; - let (caller, reason, awesome_person) = setup_awesome::(r); - TipsMod::::report_awesome( + let r = T::MaximumReasonLength::get(); + let (caller, reason, awesome_person) = setup_awesome::(r); + let awesome_person_lookup = T::Lookup::unlookup(awesome_person.clone()); + TipsMod::::report_awesome( RawOrigin::Signed(caller.clone()).into(), reason.clone(), - awesome_person.clone() + awesome_person_lookup )?; let reason_hash = T::Hashing::hash(&reason[..]); let hash = T::Hashing::hash_of(&(&reason_hash, &awesome_person)); @@ -112,29 +115,31 @@ benchmarks! { }: _(RawOrigin::Signed(caller), hash) tip_new { - let r in 0 .. MAX_BYTES; - let t in 1 .. MAX_TIPPERS; + let r in 0 .. T::MaximumReasonLength::get(); + let t in 1 .. T::Tippers::max_len() as u32; - let (caller, reason, beneficiary, value) = setup_tip::(r, t)?; + let (caller, reason, beneficiary, value) = setup_tip::(r, t)?; + let beneficiary_lookup = T::Lookup::unlookup(beneficiary); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); - }: _(RawOrigin::Signed(caller), reason, beneficiary, value) + }: _(RawOrigin::Signed(caller), reason, beneficiary_lookup, value) tip { - let t in 1 .. MAX_TIPPERS; - let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let t in 1 .. T::Tippers::max_len() as u32; + let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let beneficiary_lookup = T::Lookup::unlookup(beneficiary.clone()); let value = T::Currency::minimum_balance().saturating_mul(100u32.into()); - TipsMod::::tip_new( + TipsMod::::tip_new( RawOrigin::Signed(member).into(), reason.clone(), - beneficiary.clone(), + beneficiary_lookup, value )?; let reason_hash = T::Hashing::hash(&reason[..]); let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary)); - ensure!(Tips::::contains_key(hash), "tip does not exist"); - create_tips::(t - 1, hash.clone(), value)?; + ensure!(Tips::::contains_key(hash), "tip does not exist"); + create_tips::(t - 1, hash, value)?; let caller = account("member", t - 1, SEED); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); @@ -142,27 +147,28 @@ benchmarks! { }: _(RawOrigin::Signed(caller), hash, value) close_tip { - let t in 1 .. MAX_TIPPERS; + let t in 1 .. T::Tippers::max_len() as u32; // Make sure pot is funded - setup_pot_account::(); + setup_pot_account::(); // Set up a new tip proposal - let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let beneficiary_lookup = T::Lookup::unlookup(beneficiary.clone()); let value = T::Currency::minimum_balance().saturating_mul(100u32.into()); - TipsMod::::tip_new( + TipsMod::::tip_new( RawOrigin::Signed(member).into(), reason.clone(), - beneficiary.clone(), + beneficiary_lookup, value )?; // Create a bunch of tips let reason_hash = T::Hashing::hash(&reason[..]); let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary)); - ensure!(Tips::::contains_key(hash), "tip does not exist"); + ensure!(Tips::::contains_key(hash), "tip does not exist"); - create_tips::(t, hash.clone(), value)?; + create_tips::(t, hash, value)?; let caller = account("caller", t, SEED); // Whitelist caller account from further DB operations. @@ -171,25 +177,27 @@ benchmarks! { }: _(RawOrigin::Signed(caller), hash) slash_tip { - let t in 1 .. MAX_TIPPERS; + let t in 1 .. T::Tippers::max_len() as u32; // Make sure pot is funded - setup_pot_account::(); + setup_pot_account::(); // Set up a new tip proposal - let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let beneficiary_lookup = T::Lookup::unlookup(beneficiary.clone()); let value = T::Currency::minimum_balance().saturating_mul(100u32.into()); - TipsMod::::tip_new( + TipsMod::::tip_new( RawOrigin::Signed(member).into(), reason.clone(), - beneficiary.clone(), + beneficiary_lookup, value )?; let reason_hash = T::Hashing::hash(&reason[..]); let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary)); - ensure!(Tips::::contains_key(hash), "tip does not exist"); - }: _(RawOrigin::Root, hash) -} + ensure!(Tips::::contains_key(hash), "tip does not exist"); + let reject_origin = T::RejectOrigin::successful_origin(); + }: _(reject_origin, hash) -impl_benchmark_test_suite!(TipsMod, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(TipsMod, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index f4a4edb7b3999..dd9ebc9813233 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -61,7 +61,7 @@ pub mod migrations; pub mod weights; use sp_runtime::{ - traits::{AccountIdConversion, BadOrigin, Hash, Zero}, + traits::{AccountIdConversion, BadOrigin, Hash, StaticLookup, TrailingZeroInput, Zero}, Percent, RuntimeDebug, }; use sp_std::prelude::*; @@ -70,7 +70,7 @@ use codec::{Decode, Encode}; use frame_support::{ traits::{ ContainsLengthBound, Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, Get, - OnUnbalanced, ReservableCurrency, SortedMembers, StorageVersion, + OnUnbalanced, ReservableCurrency, SortedMembers, }, Parameter, }; @@ -78,8 +78,9 @@ use frame_support::{ pub use pallet::*; pub use weights::WeightInfo; -pub type BalanceOf = pallet_treasury::BalanceOf; -pub type NegativeImbalanceOf = pallet_treasury::NegativeImbalanceOf; +pub type BalanceOf = pallet_treasury::BalanceOf; +pub type NegativeImbalanceOf = pallet_treasury::NegativeImbalanceOf; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; /// An open tipping "motion". Retains all details of a tip including information on the finder /// and the members who have voted. @@ -120,20 +121,24 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] - pub struct Pallet(_); + #[pallet::without_storage_info] + pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + pallet_treasury::Config { + pub trait Config: frame_system::Config + pallet_treasury::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; /// Maximum acceptable reason length. + /// + /// Benchmarks depend on this value, be sure to update weights file when changing this value #[pallet::constant] type MaximumReasonLength: Get; /// The amount held on deposit per byte within the tip report reason or bounty description. #[pallet::constant] - type DataDepositPerByte: Get>; + type DataDepositPerByte: Get>; /// The period for which a tip remains open after is has achieved threshold tippers. #[pallet::constant] @@ -145,12 +150,13 @@ pub mod pallet { /// The amount held on deposit for placing a tip report. #[pallet::constant] - type TipReportDepositBase: Get>; + type TipReportDepositBase: Get>; /// Origin from which tippers must come. /// /// `ContainsLengthBound::max_len` must be cost free (i.e. no storage read or heavy - /// operation). + /// operation). Benchmarks depend on the value of `ContainsLengthBound::max_len` be sure to + /// update weights file when altering this method. type Tippers: SortedMembers + ContainsLengthBound; /// Weight information for extrinsics in this pallet. @@ -162,11 +168,11 @@ pub mod pallet { /// guaranteed to be a secure hash. #[pallet::storage] #[pallet::getter(fn tips)] - pub type Tips = StorageMap< + pub type Tips, I: 'static = ()> = StorageMap< _, Twox64Concat, T::Hash, - OpenTip, T::BlockNumber, T::Hash>, + OpenTip, T::BlockNumber, T::Hash>, OptionQuery, >; @@ -174,29 +180,26 @@ pub mod pallet { /// insecure enumerable hash since the key is guaranteed to be the result of a secure hash. #[pallet::storage] #[pallet::getter(fn reasons)] - pub type Reasons = StorageMap<_, Identity, T::Hash, Vec, OptionQuery>; + pub type Reasons, I: 'static = ()> = + StorageMap<_, Identity, T::Hash, Vec, OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A new tip suggestion has been opened. \[tip_hash\] - NewTip(T::Hash), - /// A tip suggestion has reached threshold and is closing. \[tip_hash\] - TipClosing(T::Hash), - /// A tip suggestion has been closed. \[tip_hash, who, payout\] - TipClosed(T::Hash, T::AccountId, BalanceOf), - /// A tip suggestion has been retracted. \[tip_hash\] - TipRetracted(T::Hash), - /// A tip suggestion has been slashed. \[tip_hash, finder, deposit\] - TipSlashed(T::Hash, T::AccountId, BalanceOf), + pub enum Event, I: 'static = ()> { + /// A new tip suggestion has been opened. + NewTip { tip_hash: T::Hash }, + /// A tip suggestion has reached threshold and is closing. + TipClosing { tip_hash: T::Hash }, + /// A tip suggestion has been closed. + TipClosed { tip_hash: T::Hash, who: T::AccountId, payout: BalanceOf }, + /// A tip suggestion has been retracted. + TipRetracted { tip_hash: T::Hash }, + /// A tip suggestion has been slashed. + TipSlashed { tip_hash: T::Hash, finder: T::AccountId, deposit: BalanceOf }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] - pub enum Error { + pub enum Error { /// The reason given is just too big. ReasonTooBig, /// The tip was already found/started. @@ -212,7 +215,7 @@ pub mod pallet { } #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { /// Report something `reason` that deserves a tip and claim any eventual the finder's fee. /// /// The dispatch origin for this call must be _Signed_. @@ -232,29 +235,31 @@ pub mod pallet { /// - DbReads: `Reasons`, `Tips` /// - DbWrites: `Reasons`, `Tips` /// # - #[pallet::weight(::WeightInfo::report_awesome(reason.len() as u32))] + #[pallet::call_index(0)] + #[pallet::weight(>::WeightInfo::report_awesome(reason.len() as u32))] pub fn report_awesome( origin: OriginFor, reason: Vec, - who: T::AccountId, + who: AccountIdLookupOf, ) -> DispatchResult { let finder = ensure_signed(origin)?; + let who = T::Lookup::lookup(who)?; ensure!( reason.len() <= T::MaximumReasonLength::get() as usize, - Error::::ReasonTooBig + Error::::ReasonTooBig ); let reason_hash = T::Hashing::hash(&reason[..]); - ensure!(!Reasons::::contains_key(&reason_hash), Error::::AlreadyKnown); + ensure!(!Reasons::::contains_key(&reason_hash), Error::::AlreadyKnown); let hash = T::Hashing::hash_of(&(&reason_hash, &who)); - ensure!(!Tips::::contains_key(&hash), Error::::AlreadyKnown); + ensure!(!Tips::::contains_key(&hash), Error::::AlreadyKnown); let deposit = T::TipReportDepositBase::get() + T::DataDepositPerByte::get() * (reason.len() as u32).into(); T::Currency::reserve(&finder, deposit)?; - Reasons::::insert(&reason_hash, &reason); + Reasons::::insert(&reason_hash, &reason); let tip = OpenTip { reason: reason_hash, who, @@ -264,8 +269,8 @@ pub mod pallet { tips: vec![], finders_fee: true, }; - Tips::::insert(&hash, tip); - Self::deposit_event(Event::NewTip(hash)); + Tips::::insert(&hash, tip); + Self::deposit_event(Event::NewTip { tip_hash: hash }); Ok(()) } @@ -288,19 +293,20 @@ pub mod pallet { /// - DbReads: `Tips`, `origin account` /// - DbWrites: `Reasons`, `Tips`, `origin account` /// # - #[pallet::weight(::WeightInfo::retract_tip())] + #[pallet::call_index(1)] + #[pallet::weight(>::WeightInfo::retract_tip())] pub fn retract_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { let who = ensure_signed(origin)?; - let tip = Tips::::get(&hash).ok_or(Error::::UnknownTip)?; - ensure!(tip.finder == who, Error::::NotFinder); + let tip = Tips::::get(&hash).ok_or(Error::::UnknownTip)?; + ensure!(tip.finder == who, Error::::NotFinder); - Reasons::::remove(&tip.reason); - Tips::::remove(&hash); + Reasons::::remove(&tip.reason); + Tips::::remove(&hash); if !tip.deposit.is_zero() { let err_amount = T::Currency::unreserve(&who, tip.deposit); debug_assert!(err_amount.is_zero()); } - Self::deposit_event(Event::TipRetracted(hash)); + Self::deposit_event(Event::TipRetracted { tip_hash: hash }); Ok(()) } @@ -326,21 +332,23 @@ pub mod pallet { /// - DbReads: `Tippers`, `Reasons` /// - DbWrites: `Reasons`, `Tips` /// # - #[pallet::weight(::WeightInfo::tip_new(reason.len() as u32, T::Tippers::max_len() as u32))] + #[pallet::call_index(2)] + #[pallet::weight(>::WeightInfo::tip_new(reason.len() as u32, T::Tippers::max_len() as u32))] pub fn tip_new( origin: OriginFor, reason: Vec, - who: T::AccountId, - #[pallet::compact] tip_value: BalanceOf, + who: AccountIdLookupOf, + #[pallet::compact] tip_value: BalanceOf, ) -> DispatchResult { let tipper = ensure_signed(origin)?; + let who = T::Lookup::lookup(who)?; ensure!(T::Tippers::contains(&tipper), BadOrigin); let reason_hash = T::Hashing::hash(&reason[..]); - ensure!(!Reasons::::contains_key(&reason_hash), Error::::AlreadyKnown); + ensure!(!Reasons::::contains_key(&reason_hash), Error::::AlreadyKnown); let hash = T::Hashing::hash_of(&(&reason_hash, &who)); - Reasons::::insert(&reason_hash, &reason); - Self::deposit_event(Event::NewTip(hash.clone())); + Reasons::::insert(&reason_hash, &reason); + Self::deposit_event(Event::NewTip { tip_hash: hash }); let tips = vec![(tipper.clone(), tip_value)]; let tip = OpenTip { reason: reason_hash, @@ -351,7 +359,7 @@ pub mod pallet { tips, finders_fee: false, }; - Tips::::insert(&hash, tip); + Tips::::insert(&hash, tip); Ok(()) } @@ -379,20 +387,21 @@ pub mod pallet { /// - DbReads: `Tippers`, `Tips` /// - DbWrites: `Tips` /// # - #[pallet::weight(::WeightInfo::tip(T::Tippers::max_len() as u32))] + #[pallet::call_index(3)] + #[pallet::weight(>::WeightInfo::tip(T::Tippers::max_len() as u32))] pub fn tip( origin: OriginFor, hash: T::Hash, - #[pallet::compact] tip_value: BalanceOf, + #[pallet::compact] tip_value: BalanceOf, ) -> DispatchResult { let tipper = ensure_signed(origin)?; ensure!(T::Tippers::contains(&tipper), BadOrigin); - let mut tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; + let mut tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; if Self::insert_tip_and_check_closing(&mut tip, tipper, tip_value) { - Self::deposit_event(Event::TipClosing(hash.clone())); + Self::deposit_event(Event::TipClosing { tip_hash: hash }); } - Tips::::insert(&hash, tip); + Tips::::insert(&hash, tip); Ok(()) } @@ -412,16 +421,17 @@ pub mod pallet { /// - DbReads: `Tips`, `Tippers`, `tip finder` /// - DbWrites: `Reasons`, `Tips`, `Tippers`, `tip finder` /// # - #[pallet::weight(::WeightInfo::close_tip(T::Tippers::max_len() as u32))] + #[pallet::call_index(4)] + #[pallet::weight(>::WeightInfo::close_tip(T::Tippers::max_len() as u32))] pub fn close_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { ensure_signed(origin)?; - let tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; - let n = tip.closes.as_ref().ok_or(Error::::StillOpen)?; - ensure!(frame_system::Pallet::::block_number() >= *n, Error::::Premature); + let tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; + let n = tip.closes.as_ref().ok_or(Error::::StillOpen)?; + ensure!(frame_system::Pallet::::block_number() >= *n, Error::::Premature); // closed. - Reasons::::remove(&tip.reason); - Tips::::remove(hash); + Reasons::::remove(&tip.reason); + Tips::::remove(hash); Self::payout_tip(hash, tip); Ok(()) } @@ -438,24 +448,29 @@ pub mod pallet { /// `T` is charged as upper bound given by `ContainsLengthBound`. /// The actual cost depends on the implementation of `T::Tippers`. /// # - #[pallet::weight(::WeightInfo::slash_tip(T::Tippers::max_len() as u32))] + #[pallet::call_index(5)] + #[pallet::weight(>::WeightInfo::slash_tip(T::Tippers::max_len() as u32))] pub fn slash_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { T::RejectOrigin::ensure_origin(origin)?; - let tip = Tips::::take(hash).ok_or(Error::::UnknownTip)?; + let tip = Tips::::take(hash).ok_or(Error::::UnknownTip)?; if !tip.deposit.is_zero() { let imbalance = T::Currency::slash_reserved(&tip.finder, tip.deposit).0; T::OnSlash::on_unbalanced(imbalance); } - Reasons::::remove(&tip.reason); - Self::deposit_event(Event::TipSlashed(hash, tip.finder, tip.deposit)); + Reasons::::remove(&tip.reason); + Self::deposit_event(Event::TipSlashed { + tip_hash: hash, + finder: tip.finder, + deposit: tip.deposit, + }); Ok(()) } } } -impl Pallet { +impl, I: 'static> Pallet { // Add public immutables and private mutables. /// The account ID of the treasury pot. @@ -463,7 +478,7 @@ impl Pallet { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() + T::PalletId::get().into_account_truncating() } /// Given a mutable reference to an `OpenTip`, insert the tip into it and check whether it @@ -471,9 +486,9 @@ impl Pallet { /// /// `O(T)` and one storage access. fn insert_tip_and_check_closing( - tip: &mut OpenTip, T::BlockNumber, T::Hash>, + tip: &mut OpenTip, T::BlockNumber, T::Hash>, tipper: T::AccountId, - tip_value: BalanceOf, + tip_value: BalanceOf, ) -> bool { match tip.tips.binary_search_by_key(&&tipper, |x| &x.0) { Ok(pos) => tip.tips[pos] = (tipper, tip_value), @@ -490,7 +505,7 @@ impl Pallet { } /// Remove any non-members of `Tippers` from a `tips` vector. `O(T)`. - fn retain_active_tips(tips: &mut Vec<(T::AccountId, BalanceOf)>) { + fn retain_active_tips(tips: &mut Vec<(T::AccountId, BalanceOf)>) { let members = T::Tippers::sorted_members(); let mut members_iter = members.iter(); let mut member = members_iter.next(); @@ -516,14 +531,14 @@ impl Pallet { /// Plus `O(T)` (`T` is Tippers length). fn payout_tip( hash: T::Hash, - tip: OpenTip, T::BlockNumber, T::Hash>, + tip: OpenTip, T::BlockNumber, T::Hash>, ) { let mut tips = tip.tips; Self::retain_active_tips(&mut tips); tips.sort_by_key(|i| i.1); let treasury = Self::account_id(); - let max_payout = pallet_treasury::Pallet::::pot(); + let max_payout = pallet_treasury::Pallet::::pot(); let mut payout = tips[tips.len() / 2].1.min(max_payout); if !tip.deposit.is_zero() { @@ -544,7 +559,7 @@ impl Pallet { // same as above: best-effort only. let res = T::Currency::transfer(&treasury, &tip.who, payout, KeepAlive); debug_assert!(res.is_ok()); - Self::deposit_event(Event::TipClosed(hash, tip.who, payout)); + Self::deposit_event(Event::TipClosed { tip_hash: hash, who: tip.who, payout }); } pub fn migrate_retract_tip_for_tip_new(module: &[u8], item: &[u8]) { @@ -573,16 +588,19 @@ impl Pallet { use frame_support::{migration::storage_key_iter, Twox64Concat}; + let zero_account = T::AccountId::decode(&mut TrailingZeroInput::new(&[][..])) + .expect("infinite input; qed"); + for (hash, old_tip) in storage_key_iter::< T::Hash, - OldOpenTip, T::BlockNumber, T::Hash>, + OldOpenTip, T::BlockNumber, T::Hash>, Twox64Concat, >(module, item) .drain() { let (finder, deposit, finders_fee) = match old_tip.finder { Some((finder, deposit)) => (finder, deposit, true), - None => (T::AccountId::default(), Zero::zero(), false), + None => (zero_account.clone(), Zero::zero(), false), }; let new_tip = OpenTip { reason: old_tip.reason, @@ -593,7 +611,7 @@ impl Pallet { tips: old_tip.tips, finders_fee, }; - Tips::::insert(hash, new_tip) + Tips::::insert(hash, new_tip) } } } diff --git a/frame/tips/src/migrations/mod.rs b/frame/tips/src/migrations/mod.rs index 81139120da1c8..719bb2f86fddd 100644 --- a/frame/tips/src/migrations/mod.rs +++ b/frame/tips/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/tips/src/migrations/v4.rs b/frame/tips/src/migrations/v4.rs index 69df1d08d2c8a..5e10fa7dd2c6d 100644 --- a/frame/tips/src/migrations/v4.rs +++ b/frame/tips/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,7 +49,7 @@ pub fn migrate::on_chain_storage_version(); @@ -84,7 +84,7 @@ pub fn migrate}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, + Treasury1: pallet_treasury::::{Pallet, Call, Storage, Config, Event}, Tips: pallet_tips::{Pallet, Call, Storage, Event}, + Tips1: pallet_tips::::{Pallet, Call, Storage, Event}, } ); parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaximumBlockWeight: Weight = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::one(); } impl frame_system::Config for Test { @@ -64,17 +65,17 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -83,33 +84,31 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } -thread_local! { - static TEN_TO_FOURTEEN: RefCell> = RefCell::new(vec![10,11,12,13,14]); +parameter_types! { + static TenToFourteenTestValue: Vec = vec![10,11,12,13,14]; } pub struct TenToFourteen; impl SortedMembers for TenToFourteen { fn sorted_members() -> Vec { - TEN_TO_FOURTEEN.with(|v| v.borrow().clone()) + TenToFourteenTestValue::get().clone() } #[cfg(feature = "runtime-benchmarks")] fn add(new: &u128) { - TEN_TO_FOURTEEN.with(|v| { - let mut members = v.borrow_mut(); + TenToFourteenTestValue::mutate(|members| { members.push(*new); members.sort(); }) @@ -117,7 +116,7 @@ impl SortedMembers for TenToFourteen { } impl ContainsLengthBound for TenToFourteen { fn max_len() -> usize { - TEN_TO_FOURTEEN.with(|v| v.borrow().len()) + TenToFourteenTestValue::get().len() } fn min_len() -> usize { 0 @@ -125,63 +124,92 @@ impl ContainsLengthBound for TenToFourteen { } parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); - pub const ProposalBondMinimum: u64 = 1; - pub const SpendPeriod: u64 = 2; pub const Burn: Permill = Permill::from_percent(50); - pub const DataDepositPerByte: u64 = 1; pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); - pub const MaximumReasonLength: u32 = 16384; - pub const MaxApprovals: u32 = 100; + pub const TreasuryPalletId2: PalletId = PalletId(*b"py/trsr2"); } impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId; type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; - type Event = Event; + type RuntimeEvent = RuntimeEvent; + type OnSlash = (); + type ProposalBond = ProposalBond; + type ProposalBondMinimum = ConstU64<1>; + type ProposalBondMaximum = (); + type SpendPeriod = ConstU64<2>; + type Burn = Burn; + type BurnDestination = (); // Just gets burned. + type WeightInfo = (); + type SpendFunds = (); + type MaxApprovals = ConstU32<100>; + type SpendOrigin = frame_support::traits::NeverEnsureOrigin; +} + +impl pallet_treasury::Config for Test { + type PalletId = TreasuryPalletId2; + type Currency = pallet_balances::Pallet; + type ApproveOrigin = frame_system::EnsureRoot; + type RejectOrigin = frame_system::EnsureRoot; + type RuntimeEvent = RuntimeEvent; type OnSlash = (); type ProposalBond = ProposalBond; - type ProposalBondMinimum = ProposalBondMinimum; - type SpendPeriod = SpendPeriod; + type ProposalBondMinimum = ConstU64<1>; + type ProposalBondMaximum = (); + type SpendPeriod = ConstU64<2>; type Burn = Burn; type BurnDestination = (); // Just gets burned. type WeightInfo = (); type SpendFunds = (); - type MaxApprovals = MaxApprovals; + type MaxApprovals = ConstU32<100>; + type SpendOrigin = frame_support::traits::NeverEnsureOrigin; } + parameter_types! { - pub const TipCountdown: u64 = 1; pub const TipFindersFee: Percent = Percent::from_percent(20); - pub const TipReportDepositBase: u64 = 1; } impl Config for Test { - type MaximumReasonLength = MaximumReasonLength; + type MaximumReasonLength = ConstU32<16384>; + type Tippers = TenToFourteen; + type TipCountdown = ConstU64<1>; + type TipFindersFee = TipFindersFee; + type TipReportDepositBase = ConstU64<1>; + type DataDepositPerByte = ConstU64<1>; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +impl Config for Test { + type MaximumReasonLength = ConstU32<16384>; type Tippers = TenToFourteen; - type TipCountdown = TipCountdown; + type TipCountdown = ConstU64<1>; type TipFindersFee = TipFindersFee; - type TipReportDepositBase = TipReportDepositBase; - type DataDepositPerByte = DataDepositPerByte; - type Event = Event; + type TipReportDepositBase = ConstU64<1>; + type DataDepositPerByte = ConstU64<1>; + type RuntimeEvent = RuntimeEvent; type WeightInfo = (); } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { - // Total issuance will be 200 with treasury account initialized at ED. - balances: vec![(0, 100), (1, 98), (2, 1)], + let mut ext: sp_io::TestExternalities = GenesisConfig { + system: frame_system::GenesisConfig::default(), + balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, + treasury: Default::default(), + treasury_1: Default::default(), } - .assimilate_storage(&mut t) - .unwrap(); - GenesisBuild::::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap(); - t.into() + .build_storage() + .unwrap() + .into(); + ext.execute_with(|| System::set_block_number(1)); + ext } fn last_event() -> TipEvent { System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let Event::Tips(inner) = e { Some(inner) } else { None }) + .filter_map(|e| if let RuntimeEvent::Tips(inner) = e { Some(inner) } else { None }) .last() .unwrap() } @@ -202,9 +230,9 @@ fn tip_hash() -> H256 { fn tip_new_cannot_be_used_twice() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Tips::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); + assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.dot".to_vec(), 3, 10)); assert_noop!( - Tips::tip_new(Origin::signed(11), b"awesome.dot".to_vec(), 3, 10), + Tips::tip_new(RuntimeOrigin::signed(11), b"awesome.dot".to_vec(), 3, 10), Error::::AlreadyKnown ); }); @@ -214,23 +242,23 @@ fn tip_new_cannot_be_used_twice() { fn report_awesome_and_tip_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Tips::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.dot".to_vec(), 3)); assert_eq!(Balances::reserved_balance(0), 12); assert_eq!(Balances::free_balance(0), 88); // other reports don't count. assert_noop!( - Tips::report_awesome(Origin::signed(1), b"awesome.dot".to_vec(), 3), + Tips::report_awesome(RuntimeOrigin::signed(1), b"awesome.dot".to_vec(), 3), Error::::AlreadyKnown ); let h = tip_hash(); - assert_ok!(Tips::tip(Origin::signed(10), h.clone(), 10)); - assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10)); - assert_noop!(Tips::tip(Origin::signed(9), h.clone(), 10), BadOrigin); + assert_ok!(Tips::tip(RuntimeOrigin::signed(10), h, 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10)); + assert_noop!(Tips::tip(RuntimeOrigin::signed(9), h, 10), BadOrigin); System::set_block_number(2); - assert_ok!(Tips::close_tip(Origin::signed(100), h.into())); + assert_ok!(Tips::close_tip(RuntimeOrigin::signed(100), h.into())); assert_eq!(Balances::reserved_balance(0), 0); assert_eq!(Balances::free_balance(0), 102); assert_eq!(Balances::free_balance(3), 8); @@ -241,15 +269,15 @@ fn report_awesome_and_tip_works() { fn report_awesome_from_beneficiary_and_tip_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Tips::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 0)); + assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.dot".to_vec(), 0)); assert_eq!(Balances::reserved_balance(0), 12); assert_eq!(Balances::free_balance(0), 88); let h = BlakeTwo256::hash_of(&(BlakeTwo256::hash(b"awesome.dot"), 0u128)); - assert_ok!(Tips::tip(Origin::signed(10), h.clone(), 10)); - assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(10), h, 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10)); System::set_block_number(2); - assert_ok!(Tips::close_tip(Origin::signed(100), h.into())); + assert_ok!(Tips::close_tip(RuntimeOrigin::signed(100), h.into())); assert_eq!(Balances::reserved_balance(0), 0); assert_eq!(Balances::free_balance(0), 110); }); @@ -263,30 +291,33 @@ fn close_tip_works() { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); - assert_ok!(Tips::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); + assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.dot".to_vec(), 3, 10)); let h = tip_hash(); - assert_eq!(last_event(), TipEvent::NewTip(h)); + assert_eq!(last_event(), TipEvent::NewTip { tip_hash: h }); - assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10)); - assert_noop!(Tips::close_tip(Origin::signed(0), h.into()), Error::::StillOpen); + assert_noop!(Tips::close_tip(RuntimeOrigin::signed(0), h.into()), Error::::StillOpen); - assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10)); - assert_eq!(last_event(), TipEvent::TipClosing(h)); + assert_eq!(last_event(), TipEvent::TipClosing { tip_hash: h }); - assert_noop!(Tips::close_tip(Origin::signed(0), h.into()), Error::::Premature); + assert_noop!(Tips::close_tip(RuntimeOrigin::signed(0), h.into()), Error::::Premature); System::set_block_number(2); - assert_noop!(Tips::close_tip(Origin::none(), h.into()), BadOrigin); - assert_ok!(Tips::close_tip(Origin::signed(0), h.into())); + assert_noop!(Tips::close_tip(RuntimeOrigin::none(), h.into()), BadOrigin); + assert_ok!(Tips::close_tip(RuntimeOrigin::signed(0), h.into())); assert_eq!(Balances::free_balance(3), 10); - assert_eq!(last_event(), TipEvent::TipClosed(h, 3, 10)); + assert_eq!(last_event(), TipEvent::TipClosed { tip_hash: h, who: 3, payout: 10 }); - assert_noop!(Tips::close_tip(Origin::signed(100), h.into()), Error::::UnknownTip); + assert_noop!( + Tips::close_tip(RuntimeOrigin::signed(100), h.into()), + Error::::UnknownTip + ); }); } @@ -300,20 +331,20 @@ fn slash_tip_works() { assert_eq!(Balances::reserved_balance(0), 0); assert_eq!(Balances::free_balance(0), 100); - assert_ok!(Tips::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.dot".to_vec(), 3)); assert_eq!(Balances::reserved_balance(0), 12); assert_eq!(Balances::free_balance(0), 88); let h = tip_hash(); - assert_eq!(last_event(), TipEvent::NewTip(h)); + assert_eq!(last_event(), TipEvent::NewTip { tip_hash: h }); // can't remove from any origin - assert_noop!(Tips::slash_tip(Origin::signed(0), h.clone()), BadOrigin); + assert_noop!(Tips::slash_tip(RuntimeOrigin::signed(0), h), BadOrigin); // can remove from root. - assert_ok!(Tips::slash_tip(Origin::root(), h.clone())); - assert_eq!(last_event(), TipEvent::TipSlashed(h, 0, 12)); + assert_ok!(Tips::slash_tip(RuntimeOrigin::root(), h)); + assert_eq!(last_event(), TipEvent::TipSlashed { tip_hash: h, finder: 0, deposit: 12 }); // tipper slashed assert_eq!(Balances::reserved_balance(0), 0); @@ -326,26 +357,32 @@ fn retract_tip_works() { new_test_ext().execute_with(|| { // with report awesome Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Tips::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.dot".to_vec(), 3)); let h = tip_hash(); - assert_ok!(Tips::tip(Origin::signed(10), h.clone(), 10)); - assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10)); - assert_noop!(Tips::retract_tip(Origin::signed(10), h.clone()), Error::::NotFinder); - assert_ok!(Tips::retract_tip(Origin::signed(0), h.clone())); + assert_ok!(Tips::tip(RuntimeOrigin::signed(10), h, 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10)); + assert_noop!(Tips::retract_tip(RuntimeOrigin::signed(10), h), Error::::NotFinder); + assert_ok!(Tips::retract_tip(RuntimeOrigin::signed(0), h)); System::set_block_number(2); - assert_noop!(Tips::close_tip(Origin::signed(0), h.into()), Error::::UnknownTip); + assert_noop!( + Tips::close_tip(RuntimeOrigin::signed(0), h.into()), + Error::::UnknownTip + ); // with tip new Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Tips::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); + assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.dot".to_vec(), 3, 10)); let h = tip_hash(); - assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10)); - assert_noop!(Tips::retract_tip(Origin::signed(0), h.clone()), Error::::NotFinder); - assert_ok!(Tips::retract_tip(Origin::signed(10), h.clone())); + assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10)); + assert_noop!(Tips::retract_tip(RuntimeOrigin::signed(0), h), Error::::NotFinder); + assert_ok!(Tips::retract_tip(RuntimeOrigin::signed(10), h)); System::set_block_number(2); - assert_noop!(Tips::close_tip(Origin::signed(10), h.into()), Error::::UnknownTip); + assert_noop!( + Tips::close_tip(RuntimeOrigin::signed(10), h.into()), + Error::::UnknownTip + ); }); } @@ -353,12 +390,12 @@ fn retract_tip_works() { fn tip_median_calculation_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Tips::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 0)); + assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.dot".to_vec(), 3, 0)); let h = tip_hash(); - assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 1000000)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 1000000)); System::set_block_number(2); - assert_ok!(Tips::close_tip(Origin::signed(0), h.into())); + assert_ok!(Tips::close_tip(RuntimeOrigin::signed(0), h.into())); assert_eq!(Balances::free_balance(3), 10); }); } @@ -367,17 +404,17 @@ fn tip_median_calculation_works() { fn tip_changing_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Tips::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10000)); + assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.dot".to_vec(), 3, 10000)); let h = tip_hash(); - assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10000)); - assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10000)); - assert_ok!(Tips::tip(Origin::signed(13), h.clone(), 0)); - assert_ok!(Tips::tip(Origin::signed(14), h.clone(), 0)); - assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 1000)); - assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 100)); - assert_ok!(Tips::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10000)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10000)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(13), h, 0)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(14), h, 0)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 1000)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 100)); + assert_ok!(Tips::tip(RuntimeOrigin::signed(10), h, 10)); System::set_block_number(2); - assert_ok!(Tips::close_tip(Origin::signed(0), h.into())); + assert_ok!(Tips::close_tip(RuntimeOrigin::signed(0), h.into())); assert_eq!(Balances::free_balance(3), 10); }); } @@ -545,3 +582,36 @@ fn genesis_funding_works() { assert_eq!(Treasury::pot(), initial_funding - Balances::minimum_balance()); }); } + +#[test] +fn report_awesome_and_tip_works_second_instance() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&Treasury1::account_id(), 201); + assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); + assert_eq!(Balances::free_balance(&Treasury1::account_id()), 201); + + assert_ok!(Tips1::report_awesome(RuntimeOrigin::signed(0), b"awesome.dot".to_vec(), 3)); + // duplicate report in tips1 reports don't count. + assert_noop!( + Tips1::report_awesome(RuntimeOrigin::signed(1), b"awesome.dot".to_vec(), 3), + Error::::AlreadyKnown + ); + // but tips is separate + assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.dot".to_vec(), 3)); + + let h = tip_hash(); + assert_ok!(Tips1::tip(RuntimeOrigin::signed(10), h, 10)); + assert_ok!(Tips1::tip(RuntimeOrigin::signed(11), h, 10)); + assert_ok!(Tips1::tip(RuntimeOrigin::signed(12), h, 10)); + assert_noop!(Tips1::tip(RuntimeOrigin::signed(9), h, 10), BadOrigin); + + System::set_block_number(2); + + assert_ok!(Tips1::close_tip(RuntimeOrigin::signed(100), h.into())); + // Treasury 1 unchanged + assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); + // Treasury 2 gave the funds + assert_eq!(Balances::free_balance(&Treasury1::account_id()), 191); + }); +} diff --git a/frame/tips/src/weights.rs b/frame/tips/src/weights.rs index 3376afb066170..1aa3fd8fa2eb7 100644 --- a/frame/tips/src/weights.rs +++ b/frame/tips/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_tips //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/tips/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -56,122 +58,146 @@ pub trait WeightInfo { /// Weights for pallet_tips using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Treasury Reasons (r:1 w:1) - // Storage: Treasury Tips (r:1 w:1) + // Storage: Tips Reasons (r:1 w:1) + // Storage: Tips Tips (r:1 w:1) + /// The range of component `r` is `[0, 300]`. fn report_awesome(r: u32, ) -> Weight { - (50_921_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 35_458 nanoseconds. + Weight::from_ref_time(36_920_009 as u64) + // Standard Error: 252 + .saturating_add(Weight::from_ref_time(1_835 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: Treasury Tips (r:1 w:1) - // Storage: Treasury Reasons (r:0 w:1) + // Storage: Tips Tips (r:1 w:1) + // Storage: Tips Reasons (r:0 w:1) fn retract_tip() -> Weight { - (46_352_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 34_322 nanoseconds. + Weight::from_ref_time(35_292_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Elections Members (r:1 w:0) - // Storage: Treasury Reasons (r:1 w:1) - // Storage: Treasury Tips (r:0 w:1) + // Storage: Tips Reasons (r:1 w:1) + // Storage: Tips Tips (r:0 w:1) + /// The range of component `r` is `[0, 300]`. + /// The range of component `t` is `[1, 13]`. fn tip_new(r: u32, t: u32, ) -> Weight { - (33_338_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((115_000 as Weight).saturating_mul(t as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 26_691 nanoseconds. + Weight::from_ref_time(27_313_497 as u64) + // Standard Error: 141 + .saturating_add(Weight::from_ref_time(818 as u64).saturating_mul(r as u64)) + // Standard Error: 3_352 + .saturating_add(Weight::from_ref_time(108_557 as u64).saturating_mul(t as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Elections Members (r:1 w:0) - // Storage: Treasury Tips (r:1 w:1) + // Storage: Tips Tips (r:1 w:1) + /// The range of component `t` is `[1, 13]`. fn tip(t: u32, ) -> Weight { - (22_702_000 as Weight) - // Standard Error: 0 - .saturating_add((538_000 as Weight).saturating_mul(t as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 17_464 nanoseconds. + Weight::from_ref_time(17_621_090 as u64) + // Standard Error: 3_702 + .saturating_add(Weight::from_ref_time(269_919 as u64).saturating_mul(t as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Treasury Tips (r:1 w:1) + // Storage: Tips Tips (r:1 w:1) // Storage: Elections Members (r:1 w:0) // Storage: System Account (r:1 w:1) - // Storage: Treasury Reasons (r:0 w:1) + // Storage: Tips Reasons (r:0 w:1) + /// The range of component `t` is `[1, 13]`. fn close_tip(t: u32, ) -> Weight { - (84_094_000 as Weight) - // Standard Error: 0 - .saturating_add((283_000 as Weight).saturating_mul(t as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 52_221 nanoseconds. + Weight::from_ref_time(53_168_303 as u64) + // Standard Error: 6_591 + .saturating_add(Weight::from_ref_time(243_706 as u64).saturating_mul(t as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Treasury Tips (r:1 w:1) - // Storage: Treasury Reasons (r:0 w:1) + // Storage: Tips Tips (r:1 w:1) + // Storage: Tips Reasons (r:0 w:1) + /// The range of component `t` is `[1, 13]`. fn slash_tip(t: u32, ) -> Weight { - (24_891_000 as Weight) - // Standard Error: 0 - .saturating_add((6_000 as Weight).saturating_mul(t as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 22_911 nanoseconds. + Weight::from_ref_time(23_750_488 as u64) + // Standard Error: 2_561 + .saturating_add(Weight::from_ref_time(12_282 as u64).saturating_mul(t as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Treasury Reasons (r:1 w:1) - // Storage: Treasury Tips (r:1 w:1) + // Storage: Tips Reasons (r:1 w:1) + // Storage: Tips Tips (r:1 w:1) + /// The range of component `r` is `[0, 300]`. fn report_awesome(r: u32, ) -> Weight { - (50_921_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 35_458 nanoseconds. + Weight::from_ref_time(36_920_009 as u64) + // Standard Error: 252 + .saturating_add(Weight::from_ref_time(1_835 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: Treasury Tips (r:1 w:1) - // Storage: Treasury Reasons (r:0 w:1) + // Storage: Tips Tips (r:1 w:1) + // Storage: Tips Reasons (r:0 w:1) fn retract_tip() -> Weight { - (46_352_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 34_322 nanoseconds. + Weight::from_ref_time(35_292_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Elections Members (r:1 w:0) - // Storage: Treasury Reasons (r:1 w:1) - // Storage: Treasury Tips (r:0 w:1) + // Storage: Tips Reasons (r:1 w:1) + // Storage: Tips Tips (r:0 w:1) + /// The range of component `r` is `[0, 300]`. + /// The range of component `t` is `[1, 13]`. fn tip_new(r: u32, t: u32, ) -> Weight { - (33_338_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((115_000 as Weight).saturating_mul(t as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 26_691 nanoseconds. + Weight::from_ref_time(27_313_497 as u64) + // Standard Error: 141 + .saturating_add(Weight::from_ref_time(818 as u64).saturating_mul(r as u64)) + // Standard Error: 3_352 + .saturating_add(Weight::from_ref_time(108_557 as u64).saturating_mul(t as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Elections Members (r:1 w:0) - // Storage: Treasury Tips (r:1 w:1) + // Storage: Tips Tips (r:1 w:1) + /// The range of component `t` is `[1, 13]`. fn tip(t: u32, ) -> Weight { - (22_702_000 as Weight) - // Standard Error: 0 - .saturating_add((538_000 as Weight).saturating_mul(t as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 17_464 nanoseconds. + Weight::from_ref_time(17_621_090 as u64) + // Standard Error: 3_702 + .saturating_add(Weight::from_ref_time(269_919 as u64).saturating_mul(t as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Treasury Tips (r:1 w:1) + // Storage: Tips Tips (r:1 w:1) // Storage: Elections Members (r:1 w:0) // Storage: System Account (r:1 w:1) - // Storage: Treasury Reasons (r:0 w:1) + // Storage: Tips Reasons (r:0 w:1) + /// The range of component `t` is `[1, 13]`. fn close_tip(t: u32, ) -> Weight { - (84_094_000 as Weight) - // Standard Error: 0 - .saturating_add((283_000 as Weight).saturating_mul(t as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 52_221 nanoseconds. + Weight::from_ref_time(53_168_303 as u64) + // Standard Error: 6_591 + .saturating_add(Weight::from_ref_time(243_706 as u64).saturating_mul(t as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Treasury Tips (r:1 w:1) - // Storage: Treasury Reasons (r:0 w:1) + // Storage: Tips Tips (r:1 w:1) + // Storage: Tips Reasons (r:0 w:1) + /// The range of component `t` is `[1, 13]`. fn slash_tip(t: u32, ) -> Weight { - (24_891_000 as Weight) - // Standard Error: 0 - .saturating_add((6_000 as Weight).saturating_mul(t as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 22_911 nanoseconds. + Weight::from_ref_time(23_750_488 as u64) + // Standard Error: 2_561 + .saturating_add(Weight::from_ref_time(12_282 as u64).saturating_mul(t as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } } diff --git a/frame/transaction-payment/Cargo.toml b/frame/transaction-payment/Cargo.toml index 546939692bbaf..a2f77b6cf2279 100644 --- a/frame/transaction-payment/Cargo.toml +++ b/frame/transaction-payment/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-transaction-payment" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet to manage transaction payments" readme = "README.md" @@ -13,36 +13,33 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.126", optional = true } -smallvec = "1.4.1" - -sp-core = { version = "4.0.0-dev", path = "../../primitives/core", default-features = false } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io", default-features = false } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } - +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -serde_json = "1.0.68" +serde_json = "1.0.85" pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] default = ["std"] std = [ - "serde", "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", + "serde", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/transaction-payment/asset-tx-payment/Cargo.toml b/frame/transaction-payment/asset-tx-payment/Cargo.toml new file mode 100644 index 0000000000000..b192c4e9cd96e --- /dev/null +++ b/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "pallet-asset-tx-payment" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "pallet to manage transaction payments in assets" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# Substrate dependencies +sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } + +# optional dependencies for cargo features +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = ".." } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true } +pallet-assets = { default-features = false, optional = true, path = "../../assets" } + +# Other dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } + +[dev-dependencies] +serde_json = "1.0.85" + +sp-storage = { version = "7.0.0", default-features = false, path = "../../../primitives/storage" } + +pallet-assets = { version = "4.0.0-dev", path = "../../assets" } +pallet-authorship = { version = "4.0.0-dev", path = "../../authorship" } +pallet-balances = { version = "4.0.0-dev", path = "../../balances" } + + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "codec/std", + "sp-std/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "sp-io/std", + "sp-core/std", + "pallet-transaction-payment/std", + "frame-benchmarking?/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/transaction-payment/asset-tx-payment/README.md b/frame/transaction-payment/asset-tx-payment/README.md new file mode 100644 index 0000000000000..fc860347d85fa --- /dev/null +++ b/frame/transaction-payment/asset-tx-payment/README.md @@ -0,0 +1,21 @@ +# pallet-asset-tx-payment + +## Asset Transaction Payment Pallet + +This pallet allows runtimes that include it to pay for transactions in assets other than the +native token of the chain. + +### Overview +It does this by extending transactions to include an optional `AssetId` that specifies the asset +to be used for payment (defaulting to the native token on `None`). It expects an +[`OnChargeAssetTransaction`] implementation analogously to [`pallet-transaction-payment`]. The +included [`FungiblesAdapter`] (implementing [`OnChargeAssetTransaction`]) determines the fee +amount by converting the fee calculated by [`pallet-transaction-payment`] into the desired +asset. + +### Integration +This pallet wraps FRAME's transaction payment pallet and functions as a replacement. This means +you should include both pallets in your `construct_runtime` macro, but only include this +pallet's [`SignedExtension`] ([`ChargeAssetTxPayment`]). + +License: Apache-2.0 diff --git a/frame/transaction-payment/asset-tx-payment/src/lib.rs b/frame/transaction-payment/asset-tx-payment/src/lib.rs new file mode 100644 index 0000000000000..43cc1efa0858e --- /dev/null +++ b/frame/transaction-payment/asset-tx-payment/src/lib.rs @@ -0,0 +1,312 @@ +// Copyright (C) 2021-2022 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 Transaction Payment Pallet +//! +//! This pallet allows runtimes that include it to pay for transactions in assets other than the +//! main token of the chain. +//! +//! ## Overview + +//! It does this by extending transactions to include an optional `AssetId` that specifies the asset +//! to be used for payment (defaulting to the native token on `None`). It expects an +//! [`OnChargeAssetTransaction`] implementation analogously to [`pallet-transaction-payment`]. The +//! included [`FungiblesAdapter`] (implementing [`OnChargeAssetTransaction`]) determines the fee +//! amount by converting the fee calculated by [`pallet-transaction-payment`] into the desired +//! asset. +//! +//! ## Integration + +//! This pallet wraps FRAME's transaction payment pallet and functions as a replacement. This means +//! you should include both pallets in your `construct_runtime` macro, but only include this +//! pallet's [`SignedExtension`] ([`ChargeAssetTxPayment`]). + +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_std::prelude::*; + +use codec::{Decode, Encode}; +use frame_support::{ + dispatch::{DispatchInfo, DispatchResult, PostDispatchInfo}, + traits::{ + tokens::{ + fungibles::{Balanced, CreditOf, Inspect}, + WithdrawConsequence, + }, + IsType, + }, + DefaultNoBound, +}; +use pallet_transaction_payment::OnChargeTransaction; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension, Zero}, + transaction_validity::{ + InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction, + }, + FixedPointOperand, +}; + +#[cfg(test)] +mod tests; + +mod payment; +pub use payment::*; + +// Type aliases used for interaction with `OnChargeTransaction`. +pub(crate) type OnChargeTransactionOf = + ::OnChargeTransaction; +// Balance type alias. +pub(crate) type BalanceOf = as OnChargeTransaction>::Balance; +// Liquity info type alias. +pub(crate) type LiquidityInfoOf = + as OnChargeTransaction>::LiquidityInfo; + +// Type alias used for interaction with fungibles (assets). +// Balance type alias. +pub(crate) type AssetBalanceOf = + <::Fungibles as Inspect<::AccountId>>::Balance; +/// Asset id type alias. +pub(crate) type AssetIdOf = + <::Fungibles as Inspect<::AccountId>>::AssetId; + +// Type aliases used for interaction with `OnChargeAssetTransaction`. +// Balance type alias. +pub(crate) type ChargeAssetBalanceOf = + <::OnChargeAssetTransaction as OnChargeAssetTransaction>::Balance; +// Asset id type alias. +pub(crate) type ChargeAssetIdOf = + <::OnChargeAssetTransaction as OnChargeAssetTransaction>::AssetId; +// Liquity info type alias. +pub(crate) type ChargeAssetLiquidityOf = + <::OnChargeAssetTransaction as OnChargeAssetTransaction>::LiquidityInfo; + +/// Used to pass the initial payment info from pre- to post-dispatch. +#[derive(Encode, Decode, DefaultNoBound, TypeInfo)] +pub enum InitialPayment { + /// No initial fee was payed. + #[default] + Nothing, + /// The initial fee was payed in the native currency. + Native(LiquidityInfoOf), + /// The initial fee was payed in an asset. + Asset(CreditOf), +} + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_transaction_payment::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The fungibles instance used to pay for transactions in assets. + type Fungibles: Balanced; + /// The actual transaction charging logic that charges the fees. + type OnChargeAssetTransaction: OnChargeAssetTransaction; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A transaction fee `actual_fee`, of which `tip` was added to the minimum inclusion fee, + /// has been paid by `who` in an asset `asset_id`. + AssetTxFeePaid { + who: T::AccountId, + actual_fee: BalanceOf, + tip: BalanceOf, + asset_id: Option>, + }, + } +} + +/// Require the transactor pay for themselves and maybe include a tip to gain additional priority +/// in the queue. Allows paying via both `Currency` as well as `fungibles::Balanced`. +/// +/// Wraps the transaction logic in [`pallet_transaction_payment`] and extends it with assets. +/// An asset id of `None` falls back to the underlying transaction payment via the native currency. +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct ChargeAssetTxPayment { + #[codec(compact)] + tip: BalanceOf, + asset_id: Option>, +} + +impl ChargeAssetTxPayment +where + T::RuntimeCall: Dispatchable, + AssetBalanceOf: Send + Sync + FixedPointOperand, + BalanceOf: Send + Sync + FixedPointOperand + IsType>, + ChargeAssetIdOf: Send + Sync, + CreditOf: IsType>, +{ + /// Utility constructor. Used only in client/factory code. + pub fn from(tip: BalanceOf, asset_id: Option>) -> Self { + Self { tip, asset_id } + } + + /// Fee withdrawal logic that dispatches to either `OnChargeAssetTransaction` or + /// `OnChargeTransaction`. + fn withdraw_fee( + &self, + who: &T::AccountId, + call: &T::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> Result<(BalanceOf, InitialPayment), TransactionValidityError> { + let fee = pallet_transaction_payment::Pallet::::compute_fee(len as u32, info, self.tip); + debug_assert!(self.tip <= fee, "tip should be included in the computed fee"); + if fee.is_zero() { + Ok((fee, InitialPayment::Nothing)) + } else if let Some(asset_id) = self.asset_id { + T::OnChargeAssetTransaction::withdraw_fee( + who, + call, + info, + asset_id, + fee.into(), + self.tip.into(), + ) + .map(|i| (fee, InitialPayment::Asset(i.into()))) + } else { + as OnChargeTransaction>::withdraw_fee( + who, call, info, fee, self.tip, + ) + .map(|i| (fee, InitialPayment::Native(i))) + .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() }) + } + } +} + +impl sp_std::fmt::Debug for ChargeAssetTxPayment { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "ChargeAssetTxPayment<{:?}, {:?}>", self.tip, self.asset_id.encode()) + } + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl SignedExtension for ChargeAssetTxPayment +where + T::RuntimeCall: Dispatchable, + AssetBalanceOf: Send + Sync + FixedPointOperand, + BalanceOf: Send + Sync + From + FixedPointOperand + IsType>, + ChargeAssetIdOf: Send + Sync, + CreditOf: IsType>, +{ + const IDENTIFIER: &'static str = "ChargeAssetTxPayment"; + type AccountId = T::AccountId; + type Call = T::RuntimeCall; + type AdditionalSigned = (); + type Pre = ( + // tip + BalanceOf, + // who paid the fee + Self::AccountId, + // imbalance resulting from withdrawing the fee + InitialPayment, + // asset_id for the transaction payment + Option>, + ); + + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> TransactionValidity { + use pallet_transaction_payment::ChargeTransactionPayment; + let (fee, _) = self.withdraw_fee(who, call, info, len)?; + let priority = ChargeTransactionPayment::::get_priority(info, len, self.tip, fee); + Ok(ValidTransaction { priority, ..Default::default() }) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + let (_fee, initial_payment) = self.withdraw_fee(who, call, info, len)?; + Ok((self.tip, who.clone(), initial_payment, self.asset_id)) + } + + fn post_dispatch( + pre: Option, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + if let Some((tip, who, initial_payment, asset_id)) = pre { + match initial_payment { + InitialPayment::Native(already_withdrawn) => { + pallet_transaction_payment::ChargeTransactionPayment::::post_dispatch( + Some((tip, who, already_withdrawn)), + info, + post_info, + len, + result, + )?; + }, + InitialPayment::Asset(already_withdrawn) => { + let actual_fee = pallet_transaction_payment::Pallet::::compute_actual_fee( + len as u32, info, post_info, tip, + ); + T::OnChargeAssetTransaction::correct_and_deposit_fee( + &who, + info, + post_info, + actual_fee.into(), + tip.into(), + already_withdrawn.into(), + )?; + Pallet::::deposit_event(Event::::AssetTxFeePaid { + who, + actual_fee, + tip, + asset_id, + }); + }, + InitialPayment::Nothing => { + // `actual_fee` should be zero here for any signed extrinsic. It would be + // non-zero here in case of unsigned extrinsics as they don't pay fees but + // `compute_actual_fee` is not aware of them. In both cases it's fine to just + // move ahead without adjusting the fee, though, so we do nothing. + debug_assert!(tip.is_zero(), "tip should be zero if initial fee was zero."); + }, + } + } + + Ok(()) + } +} diff --git a/frame/transaction-payment/asset-tx-payment/src/payment.rs b/frame/transaction-payment/asset-tx-payment/src/payment.rs new file mode 100644 index 0000000000000..80ff4e40dcffa --- /dev/null +++ b/frame/transaction-payment/asset-tx-payment/src/payment.rs @@ -0,0 +1,165 @@ +// Copyright (C) 2021-2022 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. + +///! Traits and default implementation for paying transaction fees in assets. +use super::*; +use crate::Config; + +use codec::FullCodec; +use frame_support::{ + traits::{ + fungibles::{Balanced, CreditOf, Inspect}, + tokens::BalanceConversion, + }, + unsigned::TransactionValidityError, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{ + AtLeast32BitUnsigned, DispatchInfoOf, MaybeSerializeDeserialize, One, PostDispatchInfoOf, + }, + transaction_validity::InvalidTransaction, +}; +use sp_std::{fmt::Debug, marker::PhantomData}; + +/// Handle withdrawing, refunding and depositing of transaction fees. +pub trait OnChargeAssetTransaction { + /// The underlying integer type in which fees are calculated. + type Balance: AtLeast32BitUnsigned + + FullCodec + + Copy + + MaybeSerializeDeserialize + + Debug + + Default + + TypeInfo; + /// The type used to identify the assets used for transaction payment. + type AssetId: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo; + /// The type used to store the intermediate values between pre- and post-dispatch. + type LiquidityInfo; + + /// Before the transaction is executed the payment of the transaction fees needs to be secured. + /// + /// Note: The `fee` already includes the `tip`. + fn withdraw_fee( + who: &T::AccountId, + call: &T::RuntimeCall, + dispatch_info: &DispatchInfoOf, + asset_id: Self::AssetId, + fee: Self::Balance, + tip: Self::Balance, + ) -> Result; + + /// After the transaction was executed the actual fee can be calculated. + /// This function should refund any overpaid fees and optionally deposit + /// the corrected amount. + /// + /// Note: The `fee` already includes the `tip`. + fn correct_and_deposit_fee( + who: &T::AccountId, + dispatch_info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + corrected_fee: Self::Balance, + tip: Self::Balance, + already_withdrawn: Self::LiquidityInfo, + ) -> Result<(), TransactionValidityError>; +} + +/// Allows specifying what to do with the withdrawn asset fees. +pub trait HandleCredit> { + /// Implement to determine what to do with the withdrawn asset fees. + /// Default for `CreditOf` from the assets pallet is to burn and + /// decrease total issuance. + fn handle_credit(credit: CreditOf); +} + +/// Default implementation that just drops the credit according to the `OnDrop` in the underlying +/// imbalance type. +impl> HandleCredit for () { + fn handle_credit(_credit: CreditOf) {} +} + +/// Implements the asset transaction for a balance to asset converter (implementing +/// [`BalanceConversion`]) and a credit handler (implementing [`HandleCredit`]). +/// +/// The credit handler is given the complete fee in terms of the asset used for the transaction. +pub struct FungiblesAdapter(PhantomData<(CON, HC)>); + +/// Default implementation for a runtime instantiating this pallet, a balance to asset converter and +/// a credit handler. +impl OnChargeAssetTransaction for FungiblesAdapter +where + T: Config, + CON: BalanceConversion, AssetIdOf, AssetBalanceOf>, + HC: HandleCredit, + AssetIdOf: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo, +{ + type Balance = BalanceOf; + type AssetId = AssetIdOf; + type LiquidityInfo = CreditOf; + + /// Withdraw the predicted fee from the transaction origin. + /// + /// Note: The `fee` already includes the `tip`. + fn withdraw_fee( + who: &T::AccountId, + _call: &T::RuntimeCall, + _info: &DispatchInfoOf, + asset_id: Self::AssetId, + fee: Self::Balance, + _tip: Self::Balance, + ) -> Result { + // We don't know the precision of the underlying asset. Because the converted fee could be + // less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum + // fee. + let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() }; + let converted_fee = CON::to_asset_balance(fee, asset_id) + .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))? + .max(min_converted_fee); + let can_withdraw = + >::can_withdraw(asset_id, who, converted_fee); + if !matches!(can_withdraw, WithdrawConsequence::Success) { + return Err(InvalidTransaction::Payment.into()) + } + >::withdraw(asset_id, who, converted_fee) + .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment)) + } + + /// Hand the fee and the tip over to the `[HandleCredit]` implementation. + /// Since the predicted fee might have been too high, parts of the fee may be refunded. + /// + /// Note: The `corrected_fee` already includes the `tip`. + fn correct_and_deposit_fee( + who: &T::AccountId, + _dispatch_info: &DispatchInfoOf, + _post_info: &PostDispatchInfoOf, + corrected_fee: Self::Balance, + _tip: Self::Balance, + paid: Self::LiquidityInfo, + ) -> Result<(), TransactionValidityError> { + let min_converted_fee = if corrected_fee.is_zero() { Zero::zero() } else { One::one() }; + // Convert the corrected fee into the asset used for payment. + let converted_fee = CON::to_asset_balance(corrected_fee, paid.asset()) + .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })? + .max(min_converted_fee); + // Calculate how much refund we should return. + let (final_fee, refund) = paid.split(converted_fee); + // Refund to the account that paid the fees. If this fails, the account might have dropped + // below the existential balance. In that case we don't refund anything. + let _ = >::resolve(who, refund); + // Handle the final fee, e.g. by transferring to the block author or burning. + HC::handle_credit(final_fee); + Ok(()) + } +} diff --git a/frame/transaction-payment/asset-tx-payment/src/tests.rs b/frame/transaction-payment/asset-tx-payment/src/tests.rs new file mode 100644 index 0000000000000..02e15654f3eed --- /dev/null +++ b/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -0,0 +1,748 @@ +// Copyright (C) 2021-2022 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 as pallet_asset_tx_payment; + +use codec; +use frame_support::{ + assert_ok, + dispatch::{DispatchClass, DispatchInfo, PostDispatchInfo}, + pallet_prelude::*, + parameter_types, + traits::{fungibles::Mutate, AsEnsureOriginWithArg, ConstU32, ConstU64, ConstU8, FindAuthor}, + weights::{Weight, WeightToFee as WeightToFeeT}, + ConsensusEngineId, +}; +use frame_system as system; +use frame_system::EnsureRoot; +use pallet_balances::Call as BalancesCall; +use pallet_transaction_payment::CurrencyAdapter; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, ConvertInto, IdentityLookup, SaturatedConversion, StaticLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +type Balance = u64; +type AccountId = u64; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, + Assets: pallet_assets::{Pallet, Call, Storage, Event}, + Authorship: pallet_authorship::{Pallet, Call, Storage}, + AssetTxPayment: pallet_asset_tx_payment::{Pallet, Event}, + } +); + +const CALL: &::RuntimeCall = + &RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 69 }); + +parameter_types! { + static ExtrinsicBaseWeight: Weight = Weight::zero(); +} + +pub struct BlockWeights; +impl Get for BlockWeights { + fn get() -> frame_system::limits::BlockWeights { + frame_system::limits::BlockWeights::builder() + .base_block(Weight::zero()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get().into(); + }) + .for_class(DispatchClass::non_mandatory(), |weights| { + weights.max_total = Weight::from_ref_time(1024).set_proof_size(u64::MAX).into(); + }) + .build_or_panic() + } +} + +parameter_types! { + pub static WeightToFee: u64 = 1; + pub static TransactionByteFee: u64 = 1; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = BlockWeights; + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + 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! { + pub const ExistentialDeposit: u64 = 10; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<10>; + type AccountStore = System; + type MaxLocks = (); + type WeightInfo = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; +} + +impl WeightToFeeT for WeightToFee { + type Balance = u64; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(weight.ref_time()) + .saturating_mul(WEIGHT_TO_FEE.with(|v| *v.borrow())) + } +} + +impl WeightToFeeT for TransactionByteFee { + type Balance = u64; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(weight.ref_time()) + .saturating_mul(TRANSACTION_BYTE_FEE.with(|v| *v.borrow())) + } +} + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = CurrencyAdapter; + type WeightToFee = WeightToFee; + type LengthToFee = TransactionByteFee; + type FeeMultiplierUpdate = (); + type OperationalFeeMultiplier = ConstU8<5>; +} + +type AssetId = u32; + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetId; + type AssetIdParameter = codec::Compact; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = ConstU64<2>; + type AssetAccountDeposit = ConstU64<2>; + type MetadataDepositBase = ConstU64<0>; + type MetadataDepositPerByte = ConstU64<0>; + type ApprovalDeposit = ConstU64<0>; + type StringLimit = ConstU32<20>; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type RemoveItemsLimit = ConstU32<1000>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +pub struct HardcodedAuthor; +const BLOCK_AUTHOR: AccountId = 1234; +impl FindAuthor for HardcodedAuthor { + fn find_author<'a, I>(_: I) -> Option + where + I: 'a + IntoIterator, + { + Some(BLOCK_AUTHOR) + } +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = HardcodedAuthor; + type UncleGenerations = (); + type FilterUncle = (); + type EventHandler = (); +} + +pub struct CreditToBlockAuthor; +impl HandleCredit for CreditToBlockAuthor { + fn handle_credit(credit: CreditOf) { + if let Some(author) = pallet_authorship::Pallet::::author() { + // What to do in case paying the author fails (e.g. because `fee < min_balance`) + // default: drop the result which will trigger the `OnDrop` of the imbalance. + let _ = >::resolve(&author, credit); + } + } +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Fungibles = Assets; + type OnChargeAssetTransaction = FungiblesAdapter< + pallet_assets::BalanceToAssetBalance, + CreditToBlockAuthor, + >; +} + +pub struct ExtBuilder { + balance_factor: u64, + base_weight: Weight, + byte_fee: u64, + weight_to_fee: u64, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balance_factor: 1, + base_weight: Weight::from_ref_time(0), + byte_fee: 1, + weight_to_fee: 1, + } + } +} + +impl ExtBuilder { + pub fn base_weight(mut self, base_weight: Weight) -> Self { + self.base_weight = base_weight; + self + } + pub fn balance_factor(mut self, factor: u64) -> Self { + self.balance_factor = factor; + self + } + fn set_constants(&self) { + ExtrinsicBaseWeight::mutate(|v| *v = self.base_weight); + TRANSACTION_BYTE_FEE.with(|v| *v.borrow_mut() = self.byte_fee); + WEIGHT_TO_FEE.with(|v| *v.borrow_mut() = self.weight_to_fee); + } + pub fn build(self) -> sp_io::TestExternalities { + self.set_constants(); + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: if self.balance_factor > 0 { + vec![ + (1, 10 * self.balance_factor), + (2, 20 * self.balance_factor), + (3, 30 * self.balance_factor), + (4, 40 * self.balance_factor), + (5, 50 * self.balance_factor), + (6, 60 * self.balance_factor), + ] + } else { + vec![] + }, + } + .assimilate_storage(&mut t) + .unwrap(); + t.into() + } +} + +/// create a transaction info struct from weight. Handy to avoid building the whole struct. +pub fn info_from_weight(w: Weight) -> DispatchInfo { + // pays_fee: Pays::Yes -- class: DispatchClass::Normal + DispatchInfo { weight: w, ..Default::default() } +} + +fn post_info_from_weight(w: Weight) -> PostDispatchInfo { + PostDispatchInfo { actual_weight: Some(w), pays_fee: Default::default() } +} + +fn info_from_pays(p: Pays) -> DispatchInfo { + DispatchInfo { pays_fee: p, ..Default::default() } +} + +fn post_info_from_pays(p: Pays) -> PostDispatchInfo { + PostDispatchInfo { actual_weight: None, pays_fee: p } +} + +fn default_post_info() -> PostDispatchInfo { + PostDispatchInfo { actual_weight: None, pays_fee: Default::default() } +} + +#[test] +fn transaction_payment_in_native_possible() { + let balance_factor = 100; + ExtBuilder::default() + .balance_factor(balance_factor) + .base_weight(Weight::from_ref_time(5)) + .build() + .execute_with(|| { + let len = 10; + let pre = ChargeAssetTxPayment::::from(0, None) + .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_ref_time(5)), len) + .unwrap(); + let initial_balance = 10 * balance_factor; + assert_eq!(Balances::free_balance(1), initial_balance - 5 - 5 - 10); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(Weight::from_ref_time(5)), + &default_post_info(), + len, + &Ok(()) + )); + assert_eq!(Balances::free_balance(1), initial_balance - 5 - 5 - 10); + + let pre = ChargeAssetTxPayment::::from(5 /* tipped */, None) + .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_ref_time(100)), len) + .unwrap(); + let initial_balance_for_2 = 20 * balance_factor; + assert_eq!(Balances::free_balance(2), initial_balance_for_2 - 5 - 10 - 100 - 5); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(Weight::from_ref_time(100)), + &post_info_from_weight(Weight::from_ref_time(50)), + len, + &Ok(()) + )); + assert_eq!(Balances::free_balance(2), initial_balance_for_2 - 5 - 10 - 50 - 5); + }); +} + +#[test] +fn transaction_payment_in_asset_possible() { + let base_weight = 5; + let balance_factor = 100; + ExtBuilder::default() + .balance_factor(balance_factor) + .base_weight(Weight::from_ref_time(base_weight)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 2; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 1; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 100; + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + let weight = 5; + let len = 10; + // we convert the from weight to fee based on the ratio between asset min balance and + // existential deposit + let fee = (base_weight + weight + len as u64) * min_balance / ExistentialDeposit::get(); + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .unwrap(); + // assert that native balance is not used + assert_eq!(Balances::free_balance(caller), 10 * balance_factor); + // check that fee was charged in the given asset + assert_eq!(Assets::balance(asset_id, caller), balance - fee); + assert_eq!(Assets::balance(asset_id, BLOCK_AUTHOR), 0); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(Weight::from_ref_time(weight)), + &default_post_info(), + len, + &Ok(()) + )); + 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); + }); +} + +#[test] +fn transaction_payment_without_fee() { + let base_weight = 5; + let balance_factor = 100; + ExtBuilder::default() + .balance_factor(balance_factor) + .base_weight(Weight::from_ref_time(base_weight)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 2; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 1; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 100; + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + let weight = 5; + let len = 10; + // we convert the from weight to fee based on the ratio between asset min balance and + // existential deposit + let fee = (base_weight + weight + len as u64) * min_balance / ExistentialDeposit::get(); + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .unwrap(); + // assert that native balance is not used + assert_eq!(Balances::free_balance(caller), 10 * balance_factor); + // check that fee was charged in the given asset + assert_eq!(Assets::balance(asset_id, caller), balance - fee); + assert_eq!(Assets::balance(asset_id, BLOCK_AUTHOR), 0); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(Weight::from_ref_time(weight)), + &post_info_from_pays(Pays::No), + len, + &Ok(()) + )); + // caller should be refunded + assert_eq!(Assets::balance(asset_id, caller), balance); + // check that the block author did not get rewarded + assert_eq!(Assets::balance(asset_id, BLOCK_AUTHOR), 0); + }); +} + +#[test] +fn asset_transaction_payment_with_tip_and_refund() { + let base_weight = 5; + ExtBuilder::default() + .balance_factor(100) + .base_weight(Weight::from_ref_time(base_weight)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 2; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 2; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 1000; + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + let weight = 100; + let tip = 5; + let len = 10; + // we convert the from weight to fee based on the ratio between asset min balance and + // existential deposit + let fee_with_tip = + (base_weight + weight + len as u64 + tip) * min_balance / ExistentialDeposit::get(); + let pre = ChargeAssetTxPayment::::from(tip, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .unwrap(); + assert_eq!(Assets::balance(asset_id, caller), balance - fee_with_tip); + + let final_weight = 50; + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(Weight::from_ref_time(weight)), + &post_info_from_weight(Weight::from_ref_time(final_weight)), + len, + &Ok(()) + )); + let final_fee = + 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); + }); +} + +#[test] +fn payment_from_account_with_only_assets() { + let base_weight = 5; + ExtBuilder::default() + .balance_factor(100) + .base_weight(Weight::from_ref_time(base_weight)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 2; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 333; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 100; + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + // assert that native balance is not necessary + assert_eq!(Balances::free_balance(caller), 0); + let weight = 5; + let len = 10; + // we convert the from weight to fee based on the ratio between asset min balance and + // existential deposit + let fee = (base_weight + weight + len as u64) * min_balance / ExistentialDeposit::get(); + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .unwrap(); + assert_eq!(Balances::free_balance(caller), 0); + // check that fee was charged in the given asset + assert_eq!(Assets::balance(asset_id, caller), balance - fee); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(Weight::from_ref_time(weight)), + &default_post_info(), + len, + &Ok(()) + )); + assert_eq!(Assets::balance(asset_id, caller), balance - fee); + assert_eq!(Balances::free_balance(caller), 0); + }); +} + +#[test] +fn payment_only_with_existing_sufficient_asset() { + let base_weight = 5; + ExtBuilder::default() + .balance_factor(100) + .base_weight(Weight::from_ref_time(base_weight)) + .build() + .execute_with(|| { + let asset_id = 1; + let caller = 1; + let weight = 5; + let len = 10; + // pre_dispatch fails for non-existent asset + assert!(ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .is_err()); + + // create the non-sufficient asset + let min_balance = 2; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + false, /* is_sufficient */ + min_balance + )); + // pre_dispatch fails for non-sufficient asset + assert!(ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .is_err()); + }); +} + +#[test] +fn converted_fee_is_never_zero_if_input_fee_is_not() { + let base_weight = 1; + ExtBuilder::default() + .balance_factor(100) + .base_weight(Weight::from_ref_time(base_weight)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 1; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 333; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 100; + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + let weight = 1; + let len = 1; + // we convert the from weight to fee based on the ratio between asset min balance and + // existential deposit + let fee = (base_weight + weight + len as u64) * min_balance / ExistentialDeposit::get(); + // naive fee calculation would round down to zero + assert_eq!(fee, 0); + { + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_pays(Pays::No), len) + .unwrap(); + // `Pays::No` still implies no fees + assert_eq!(Assets::balance(asset_id, caller), balance); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_pays(Pays::No), + &post_info_from_pays(Pays::No), + len, + &Ok(()) + )); + assert_eq!(Assets::balance(asset_id, caller), balance); + } + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .unwrap(); + // check that at least one coin was charged in the given asset + assert_eq!(Assets::balance(asset_id, caller), balance - 1); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(Weight::from_ref_time(weight)), + &default_post_info(), + len, + &Ok(()) + )); + assert_eq!(Assets::balance(asset_id, caller), balance - 1); + }); +} + +#[test] +fn post_dispatch_fee_is_zero_if_pre_dispatch_fee_is_zero() { + let base_weight = 1; + ExtBuilder::default() + .balance_factor(100) + .base_weight(Weight::from_ref_time(base_weight)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 100; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 333; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 100; + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + let weight = 1; + let len = 1; + // we convert the from weight to fee based on the ratio between asset min balance and + // existential deposit + let fee = (base_weight + weight + len as u64) * min_balance / ExistentialDeposit::get(); + // calculated fee is greater than 0 + assert!(fee > 0); + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_pays(Pays::No), len) + .unwrap(); + // `Pays::No` implies no pre-dispatch fees + assert_eq!(Assets::balance(asset_id, caller), balance); + let (_tip, _who, initial_payment, _asset_id) = ⪯ + let not_paying = match initial_payment { + &InitialPayment::Nothing => true, + _ => false, + }; + assert!(not_paying, "initial payment should be Nothing if we pass Pays::No"); + + // `Pays::Yes` on post-dispatch does not mean we pay (we never charge more than the + // initial fee) + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_pays(Pays::No), + &post_info_from_pays(Pays::Yes), + len, + &Ok(()) + )); + assert_eq!(Assets::balance(asset_id, caller), balance); + }); +} + +#[test] +fn post_dispatch_fee_is_zero_if_unsigned_pre_dispatch_fee_is_zero() { + let base_weight = 1; + ExtBuilder::default() + .balance_factor(100) + .base_weight(Weight::from_ref_time(base_weight)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 100; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 333; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 100; + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + let weight = 1; + let len = 1; + ChargeAssetTxPayment::::pre_dispatch_unsigned( + CALL, + &info_from_weight(Weight::from_ref_time(weight)), + len, + ) + .unwrap(); + + assert_eq!(Assets::balance(asset_id, caller), balance); + + // `Pays::Yes` on post-dispatch does not mean we pay (we never charge more than the + // initial fee) + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + None, + &info_from_weight(Weight::from_ref_time(weight)), + &post_info_from_pays(Pays::Yes), + len, + &Ok(()) + )); + assert_eq!(Assets::balance(asset_id, caller), balance); + }); +} diff --git a/frame/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml index 3858c41a38763..b77143201ffd4 100644 --- a/frame/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-transaction-payment-rpc" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "RPC interface for the transaction payment pallet." readme = "README.md" @@ -13,14 +13,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" - +codec = { package = "parity-scale-codec", version = "3.0.0" } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-rpc = { version = "4.0.0-dev", path = "../../../primitives/rpc" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-rpc = { version = "6.0.0", path = "../../../primitives/rpc" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-weights = { version = "4.0.0", path = "../../../primitives/weights" } diff --git a/frame/transaction-payment/rpc/runtime-api/Cargo.toml b/frame/transaction-payment/rpc/runtime-api/Cargo.toml index 2f78f2439c604..86753526fef47 100644 --- a/frame/transaction-payment/rpc/runtime-api/Cargo.toml +++ b/frame/transaction-payment/rpc/runtime-api/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-transaction-payment-rpc-runtime-api" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "RPC runtime API for transaction payment FRAME pallet" readme = "README.md" @@ -13,16 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../transaction-payment" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../../primitives/runtime" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../../../primitives/weights" } [features] default = ["std"] std = [ "codec/std", + "pallet-transaction-payment/std", "sp-api/std", "sp-runtime/std", - "pallet-transaction-payment/std", + "sp-weights/std", ] diff --git a/frame/transaction-payment/rpc/runtime-api/src/lib.rs b/frame/transaction-payment/rpc/runtime-api/src/lib.rs index 696550d3ef040..10fd2a9e61fc1 100644 --- a/frame/transaction-payment/rpc/runtime-api/src/lib.rs +++ b/frame/transaction-payment/rpc/runtime-api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,10 +25,26 @@ use sp_runtime::traits::MaybeDisplay; pub use pallet_transaction_payment::{FeeDetails, InclusionFee, RuntimeDispatchInfo}; sp_api::decl_runtime_apis! { + #[api_version(2)] pub trait TransactionPaymentApi where Balance: Codec + MaybeDisplay, { + #[changed_in(2)] + fn query_info(uxt: Block::Extrinsic, len: u32) -> RuntimeDispatchInfo; fn query_info(uxt: Block::Extrinsic, len: u32) -> RuntimeDispatchInfo; fn query_fee_details(uxt: Block::Extrinsic, len: u32) -> FeeDetails; } + + #[api_version(2)] + pub trait TransactionPaymentCallApi + where + Balance: Codec + MaybeDisplay, + Call: Codec, + { + /// Query information of a dispatch class, weight, and fee of a given encoded `Call`. + fn query_call_info(call: Call, len: u32) -> RuntimeDispatchInfo; + + /// Query fee details of a given encoded `Call`. + fn query_call_fee_details(call: Call, len: u32) -> FeeDetails; + } } diff --git a/frame/transaction-payment/rpc/src/lib.rs b/frame/transaction-payment/rpc/src/lib.rs index 945156d12a6a4..19007d37963ec 100644 --- a/frame/transaction-payment/rpc/src/lib.rs +++ b/frame/transaction-payment/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,16 @@ //! RPC interface for the transaction payment pallet. -pub use self::gen_client::Client as TransactionPaymentClient; +use std::{convert::TryInto, sync::Arc}; + use codec::{Codec, Decode}; -use jsonrpc_core::{Error as RpcError, ErrorCode, Result}; -use jsonrpc_derive::rpc; -pub use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi as TransactionPaymentRuntimeApi; +use jsonrpsee::{ + core::{Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorCode, ErrorObject}, +}; use pallet_transaction_payment_rpc_runtime_api::{FeeDetails, InclusionFee, RuntimeDispatchInfo}; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_core::Bytes; use sp_rpc::number::NumberOrHex; @@ -31,28 +34,31 @@ use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, MaybeDisplay}, }; -use std::{convert::TryInto, sync::Arc}; -#[rpc] +pub use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi as TransactionPaymentRuntimeApi; + +#[rpc(client, server)] pub trait TransactionPaymentApi { - #[rpc(name = "payment_queryInfo")] - fn query_info(&self, encoded_xt: Bytes, at: Option) -> Result; - #[rpc(name = "payment_queryFeeDetails")] + #[method(name = "payment_queryInfo")] + fn query_info(&self, encoded_xt: Bytes, at: Option) -> RpcResult; + + #[method(name = "payment_queryFeeDetails")] fn query_fee_details( &self, encoded_xt: Bytes, at: Option, - ) -> Result>; + ) -> RpcResult>; } -/// A struct that implements the [`TransactionPaymentApi`]. +/// Provides RPC methods to query a dispatchable's class, weight and fee. pub struct TransactionPayment { + /// Shared reference to the client. client: Arc, _marker: std::marker::PhantomData

, } impl TransactionPayment { - /// Create new `TransactionPayment` with the given reference to the client. + /// Creates a new instance of the TransactionPayment Rpc helper. pub fn new(client: Arc) -> Self { Self { client, _marker: Default::default() } } @@ -66,8 +72,8 @@ pub enum Error { RuntimeError, } -impl From for i64 { - fn from(e: Error) -> i64 { +impl From for i32 { + fn from(e: Error) -> i32 { match e { Error::RuntimeError => 1, Error::DecodeError => 2, @@ -75,66 +81,103 @@ impl From for i64 { } } -impl TransactionPaymentApi<::Hash, RuntimeDispatchInfo> - for TransactionPayment +impl + TransactionPaymentApiServer< + ::Hash, + RuntimeDispatchInfo, + > for TransactionPayment where Block: BlockT, - C: 'static + ProvideRuntimeApi + HeaderBackend, + C: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, C::Api: TransactionPaymentRuntimeApi, - Balance: Codec + MaybeDisplay + Copy + TryInto, + Balance: Codec + MaybeDisplay + Copy + TryInto + Send + Sync + 'static, { fn query_info( &self, encoded_xt: Bytes, - at: Option<::Hash>, - ) -> Result> { + at: Option, + ) -> RpcResult> { let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); let encoded_len = encoded_xt.len() as u32; - let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::DecodeError.into()), - message: "Unable to query dispatch info.".into(), - data: Some(format!("{:?}", e).into()), + let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::DecodeError.into(), + "Unable to query dispatch info.", + Some(format!("{:?}", e)), + )) })?; - api.query_info(&at, uxt, encoded_len).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to query dispatch info.".into(), - data: Some(format!("{:?}", e).into()), - }) + + fn map_err(error: impl ToString, desc: &'static str) -> CallError { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + desc, + Some(error.to_string()), + )) + } + + let api_version = api + .api_version::>(&at) + .map_err(|e| map_err(e, "Failed to get transaction payment runtime api version"))? + .ok_or_else(|| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Transaction payment runtime api wasn't found in the runtime", + None::, + )) + })?; + + if api_version < 2 { + #[allow(deprecated)] + api.query_info_before_version_2(&at, uxt, encoded_len) + .map_err(|e| map_err(e, "Unable to query dispatch info.").into()) + } else { + let res = api + .query_info(&at, uxt, encoded_len) + .map_err(|e| map_err(e, "Unable to query dispatch info."))?; + + Ok(RuntimeDispatchInfo { + weight: sp_weights::OldWeight(res.weight.ref_time()), + class: res.class, + partial_fee: res.partial_fee, + }) + } } fn query_fee_details( &self, encoded_xt: Bytes, - at: Option<::Hash>, - ) -> Result> { + at: Option, + ) -> RpcResult> { let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); let encoded_len = encoded_xt.len() as u32; - let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::DecodeError.into()), - message: "Unable to query fee details.".into(), - data: Some(format!("{:?}", e).into()), + let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::DecodeError.into(), + "Unable to query fee details.", + Some(format!("{:?}", e)), + )) })?; - let fee_details = api.query_fee_details(&at, uxt, encoded_len).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to query fee details.".into(), - data: Some(format!("{:?}", e).into()), + let fee_details = api.query_fee_details(&at, uxt, encoded_len).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query fee details.", + Some(e.to_string()), + )) })?; let try_into_rpc_balance = |value: Balance| { - value.try_into().map_err(|_| RpcError { - code: ErrorCode::InvalidParams, - message: format!("{} doesn't fit in NumberOrHex representation", value), - data: None, + value.try_into().map_err(|_| { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InvalidParams.code(), + format!("{} doesn't fit in NumberOrHex representation", value), + None::<()>, + ))) }) }; diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index e3a3bccc3d39a..ce747fa6bd85c 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,13 +47,13 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ traits::{ - Convert, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SaturatedConversion, Saturating, - SignedExtension, Zero, + Convert, DispatchInfoOf, Dispatchable, One, PostDispatchInfoOf, SaturatedConversion, + Saturating, SignedExtension, Zero, }, transaction_validity::{ TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransaction, @@ -63,12 +63,11 @@ use sp_runtime::{ use sp_std::prelude::*; use frame_support::{ - dispatch::DispatchResult, - traits::{EstimateCallFee, Get}, - weights::{ - DispatchClass, DispatchInfo, GetDispatchInfo, Pays, PostDispatchInfo, Weight, - WeightToFeeCoefficient, WeightToFeePolynomial, + dispatch::{ + DispatchClass, DispatchInfo, DispatchResult, GetDispatchInfo, Pays, PostDispatchInfo, }, + traits::{EstimateCallFee, Get}, + weights::{Weight, WeightToFee}, }; mod payment; @@ -127,13 +126,15 @@ type BalanceOf = <::OnChargeTransaction as OnChargeTransaction -pub struct TargetedFeeAdjustment(sp_std::marker::PhantomData<(T, S, V, M)>); +/// +pub struct TargetedFeeAdjustment(sp_std::marker::PhantomData<(T, S, V, M, X)>); /// Something that can convert the current multiplier to the next one. pub trait MultiplierUpdate: Convert { - /// Minimum multiplier + /// Minimum multiplier. Any outcome of the `convert` function should be at least this. fn min() -> Multiplier; + /// Maximum multiplier. Any outcome of the `convert` function should be less or equal this. + fn max() -> Multiplier; /// Target block saturation level fn target() -> Perquintill; /// Variability factor @@ -144,6 +145,9 @@ impl MultiplierUpdate for () { fn min() -> Multiplier { Default::default() } + fn max() -> Multiplier { + ::max_value() + } fn target() -> Perquintill { Default::default() } @@ -152,16 +156,20 @@ impl MultiplierUpdate for () { } } -impl MultiplierUpdate for TargetedFeeAdjustment +impl MultiplierUpdate for TargetedFeeAdjustment where T: frame_system::Config, S: Get, V: Get, M: Get, + X: Get, { fn min() -> Multiplier { M::get() } + fn max() -> Multiplier { + X::get() + } fn target() -> Perquintill { S::get() } @@ -170,29 +178,33 @@ where } } -impl Convert for TargetedFeeAdjustment +impl Convert for TargetedFeeAdjustment where T: frame_system::Config, S: Get, V: Get, M: Get, + X: Get, { fn convert(previous: Multiplier) -> Multiplier { // Defensive only. The multiplier in storage should always be at most positive. Nonetheless // we recover here in case of errors, because any value below this would be stale and can // never change. let min_multiplier = M::get(); + let max_multiplier = X::get(); let previous = previous.max(min_multiplier); let weights = T::BlockWeights::get(); // the computed ratio is only among the normal class. - let normal_max_weight = weights - .get(DispatchClass::Normal) - .max_total - .unwrap_or_else(|| weights.max_block); + let normal_max_weight = + weights.get(DispatchClass::Normal).max_total.unwrap_or(weights.max_block); let current_block_weight = >::block_weight(); let normal_block_weight = - *current_block_weight.get(DispatchClass::Normal).min(&normal_max_weight); + current_block_weight.get(DispatchClass::Normal).min(normal_max_weight); + + // TODO: Handle all weight dimensions + let normal_max_weight = normal_max_weight.ref_time(); + let normal_block_weight = normal_block_weight.ref_time(); let s = S::get(); let v = V::get(); @@ -216,17 +228,44 @@ where if positive { let excess = first_term.saturating_add(second_term).saturating_mul(previous); - previous.saturating_add(excess).max(min_multiplier) + previous.saturating_add(excess).clamp(min_multiplier, max_multiplier) } else { // Defensive-only: first_term > second_term. Safe subtraction. let negative = first_term.saturating_sub(second_term).saturating_mul(previous); - previous.saturating_sub(negative).max(min_multiplier) + previous.saturating_sub(negative).clamp(min_multiplier, max_multiplier) } } } +/// A struct to make the fee multiplier a constant +pub struct ConstFeeMultiplier>(sp_std::marker::PhantomData); + +impl> MultiplierUpdate for ConstFeeMultiplier { + fn min() -> Multiplier { + M::get() + } + fn max() -> Multiplier { + M::get() + } + fn target() -> Perquintill { + Default::default() + } + fn variability() -> Multiplier { + Default::default() + } +} + +impl Convert for ConstFeeMultiplier +where + M: Get, +{ + fn convert(_previous: Multiplier) -> Multiplier { + Self::min() + } +} + /// Storage releases of the pallet. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] enum Releases { /// Original version of the pallet. V1Ancient, @@ -240,6 +279,10 @@ impl Default for Releases { } } +/// Default value for NextFeeMultiplier. This is used in genesis and is also used in +/// NextFeeMultiplierOnEmpty() to provide a value when none exists in storage. +const MULTIPLIER_DEFAULT_VALUE: Multiplier = Multiplier::from_u32(1); + #[frame_support::pallet] pub mod pallet { use super::*; @@ -252,6 +295,9 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Handler for withdrawing, refunding and depositing the transaction fee. /// Transaction fees are withdrawn before the transaction is executed. /// After the transaction was executed the transaction weight can be @@ -260,30 +306,43 @@ pub mod pallet { /// might be refunded. In the end the fees can be deposited. type OnChargeTransaction: OnChargeTransaction; - /// The fee to be paid for making a transaction; the per-byte portion. + /// A fee mulitplier for `Operational` extrinsics to compute "virtual tip" to boost their + /// `priority` + /// + /// This value is multipled by the `final_fee` to obtain a "virtual tip" that is later + /// added to a tip component in regular `priority` calculations. + /// It means that a `Normal` transaction can front-run a similarly-sized `Operational` + /// extrinsic (with no tip), by including a tip value greater than the virtual tip. + /// + /// ```rust,ignore + /// // For `Normal` + /// let priority = priority_calc(tip); + /// + /// // For `Operational` + /// let virtual_tip = (inclusion_fee + tip) * OperationalFeeMultiplier; + /// let priority = priority_calc(tip + virtual_tip); + /// ``` + /// + /// Note that since we use `final_fee` the multiplier applies also to the regular `tip` + /// sent with the transaction. So, not only does the transaction get a priority bump based + /// on the `inclusion_fee`, but we also amplify the impact of tips applied to `Operational` + /// transactions. #[pallet::constant] - type TransactionByteFee: Get>; + type OperationalFeeMultiplier: Get; /// Convert a weight value into a deductible fee based on the currency type. - type WeightToFee: WeightToFeePolynomial>; + type WeightToFee: WeightToFee>; + + /// Convert a length value into a deductible fee based on the currency type. + type LengthToFee: WeightToFee>; /// Update the multiplier of the next block, based on the previous block's weight. type FeeMultiplierUpdate: MultiplierUpdate; } - #[pallet::extra_constants] - impl Pallet { - // TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed. - #[allow(non_snake_case)] - /// The polynomial that is applied in order to derive fee from weight. - fn WeightToFee() -> Vec>> { - T::WeightToFee::polynomial().to_vec() - } - } - #[pallet::type_value] pub fn NextFeeMultiplierOnEmpty() -> Multiplier { - Multiplier::saturating_from_integer(1) + MULTIPLIER_DEFAULT_VALUE } #[pallet::storage] @@ -295,12 +354,14 @@ pub mod pallet { pub(super) type StorageVersion = StorageValue<_, Releases, ValueQuery>; #[pallet::genesis_config] - pub struct GenesisConfig; + pub struct GenesisConfig { + pub multiplier: Multiplier, + } #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { - Self + Self { multiplier: MULTIPLIER_DEFAULT_VALUE } } } @@ -308,9 +369,18 @@ pub mod pallet { impl GenesisBuild for GenesisConfig { fn build(&self) { StorageVersion::::put(Releases::V2); + NextFeeMultiplier::::put(self.multiplier); } } + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A transaction fee `actual_fee`, of which `tip` was added to the minimum inclusion fee, + /// has been paid by `who`. + TransactionFeePaid { who: T::AccountId, actual_fee: BalanceOf, tip: BalanceOf }, + } + #[pallet::hooks] impl Hooks> for Pallet { fn on_finalize(_: T::BlockNumber) { @@ -323,35 +393,37 @@ pub mod pallet { // given weight == u64, we build multipliers from `diff` of two weight values, which can // at most be maximum block weight. Make sure that this can fit in a multiplier without // loss. - use sp_std::convert::TryInto; assert!( ::max_value() >= - Multiplier::checked_from_integer( - T::BlockWeights::get().max_block.try_into().unwrap() + Multiplier::checked_from_integer::( + T::BlockWeights::get().max_block.ref_time().try_into().unwrap() ) .unwrap(), ); - // This is the minimum value of the multiplier. Make sure that if we collapse to this - // value, we can recover with a reasonable amount of traffic. For this test we assert - // that if we collapse to minimum, the trend will be positive with a weight value - // which is 1% more than the target. - let min_value = T::FeeMultiplierUpdate::min(); - let mut target = T::FeeMultiplierUpdate::target() * + let target = T::FeeMultiplierUpdate::target() * T::BlockWeights::get().get(DispatchClass::Normal).max_total.expect( "Setting `max_total` for `Normal` dispatch class is not compatible with \ `transaction-payment` pallet.", ); // add 1 percent; let addition = target / 100; - if addition == 0 { - // this is most likely because in a test setup we set everything to (). + if addition == Weight::zero() { + // this is most likely because in a test setup we set everything to () + // or to `ConstFeeMultiplier`. return } - target += addition; #[cfg(any(feature = "std", test))] sp_io::TestExternalities::new_empty().execute_with(|| { + // This is the minimum value of the multiplier. Make sure that if we collapse to + // this value, we can recover with a reasonable amount of traffic. For this test we + // assert that if we collapse to minimum, the trend will be positive with a weight + // value which is 1% more than the target. + let min_value = T::FeeMultiplierUpdate::min(); + + let target = target + addition; + >::set_block_consumed_resources(target, 0); let next = T::FeeMultiplierUpdate::convert(min_value); assert!( @@ -377,12 +449,12 @@ where /// /// All dispatchables must be annotated with weight and will have some fee info. This function /// always returns. - pub fn query_info( + pub fn query_info( unchecked_extrinsic: Extrinsic, len: u32, ) -> RuntimeDispatchInfo> where - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { // NOTE: we can actually make it understand `ChargeTransactionPayment`, but would be some // hassle for sure. We have to make it aware of the index of `ChargeTransactionPayment` in @@ -391,28 +463,72 @@ where // a very very little potential gain in the future. let dispatch_info = ::get_dispatch_info(&unchecked_extrinsic); - let partial_fee = Self::compute_fee(len, &dispatch_info, 0u32.into()); + let partial_fee = if unchecked_extrinsic.is_signed().unwrap_or(false) { + Self::compute_fee(len, &dispatch_info, 0u32.into()) + } else { + // Unsigned extrinsics have no partial fee. + 0u32.into() + }; + let DispatchInfo { weight, class, .. } = dispatch_info; RuntimeDispatchInfo { weight, class, partial_fee } } /// Query the detailed fee of a given `call`. - pub fn query_fee_details( + pub fn query_fee_details( unchecked_extrinsic: Extrinsic, len: u32, ) -> FeeDetails> where - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { let dispatch_info = ::get_dispatch_info(&unchecked_extrinsic); - Self::compute_fee_details(len, &dispatch_info, 0u32.into()) + + let tip = 0u32.into(); + + if unchecked_extrinsic.is_signed().unwrap_or(false) { + Self::compute_fee_details(len, &dispatch_info, tip) + } else { + // Unsigned extrinsics have no inclusion fee. + FeeDetails { inclusion_fee: None, tip } + } + } + + /// Query information of a dispatch class, weight, and fee of a given encoded `Call`. + pub fn query_call_info(call: T::RuntimeCall, len: u32) -> RuntimeDispatchInfo> + where + T::RuntimeCall: Dispatchable + GetDispatchInfo, + { + let dispatch_info = ::get_dispatch_info(&call); + let DispatchInfo { weight, class, .. } = dispatch_info; + + RuntimeDispatchInfo { + weight, + class, + partial_fee: Self::compute_fee(len, &dispatch_info, 0u32.into()), + } + } + + /// Query fee details of a given encoded `Call`. + pub fn query_call_fee_details(call: T::RuntimeCall, len: u32) -> FeeDetails> + where + T::RuntimeCall: Dispatchable + GetDispatchInfo, + { + let dispatch_info = ::get_dispatch_info(&call); + let tip = 0u32.into(); + + Self::compute_fee_details(len, &dispatch_info, tip) } /// Compute the final fee value for a particular transaction. - pub fn compute_fee(len: u32, info: &DispatchInfoOf, tip: BalanceOf) -> BalanceOf + pub fn compute_fee( + len: u32, + info: &DispatchInfoOf, + tip: BalanceOf, + ) -> BalanceOf where - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { Self::compute_fee_details(len, info, tip).final_fee() } @@ -420,11 +536,11 @@ where /// Compute the fee details for a particular transaction. pub fn compute_fee_details( len: u32, - info: &DispatchInfoOf, + info: &DispatchInfoOf, tip: BalanceOf, ) -> FeeDetails> where - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { Self::compute_fee_raw(len, info.weight, tip, info.pays_fee, info.class) } @@ -435,12 +551,12 @@ where /// weight is used for the weight fee calculation. pub fn compute_actual_fee( len: u32, - info: &DispatchInfoOf, - post_info: &PostDispatchInfoOf, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, tip: BalanceOf, ) -> BalanceOf where - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { Self::compute_actual_fee_details(len, info, post_info, tip).final_fee() } @@ -448,12 +564,12 @@ where /// Compute the actual post dispatch fee details for a particular transaction. pub fn compute_actual_fee_details( len: u32, - info: &DispatchInfoOf, - post_info: &PostDispatchInfoOf, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, tip: BalanceOf, ) -> FeeDetails> where - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { Self::compute_fee_raw( len, @@ -472,25 +588,18 @@ where class: DispatchClass, ) -> FeeDetails> { if pays_fee == Pays::Yes { - let len = >::from(len); - let per_byte = T::TransactionByteFee::get(); - - // length fee. this is not adjusted. - let fixed_len_fee = per_byte.saturating_mul(len); - // the adjustable part of the fee. let unadjusted_weight_fee = Self::weight_to_fee(weight); let multiplier = Self::next_fee_multiplier(); // final adjusted weight fee. let adjusted_weight_fee = multiplier.saturating_mul_int(unadjusted_weight_fee); + // length fee. this is adjusted via `LengthToFee`. + let len_fee = Self::length_to_fee(len); + let base_fee = Self::weight_to_fee(T::BlockWeights::get().get(class).base_extrinsic); FeeDetails { - inclusion_fee: Some(InclusionFee { - base_fee, - len_fee: fixed_len_fee, - adjusted_weight_fee, - }), + inclusion_fee: Some(InclusionFee { base_fee, len_fee, adjusted_weight_fee }), tip, } } else { @@ -498,11 +607,15 @@ where } } + fn length_to_fee(length: u32) -> BalanceOf { + T::LengthToFee::weight_to_fee(&Weight::from_ref_time(length as u64)) + } + fn weight_to_fee(weight: Weight) -> BalanceOf { // cap the weight to the maximum defined in runtime, otherwise it will be the // `Bounded` maximum of its data type, which is not desired. let capped_weight = weight.min(T::BlockWeights::get().max_block); - T::WeightToFee::calc(&capped_weight) + T::WeightToFee::weight_to_fee(&capped_weight) } } @@ -523,13 +636,21 @@ where /// Require the transactor pay for themselves and maybe include a tip to gain additional priority /// in the queue. +/// +/// # Transaction Validity +/// +/// This extension sets the `priority` field of `TransactionValidity` depending on the amount +/// of tip being paid per weight unit. +/// +/// Operational transactions will receive an additional priority bump, so that they are normally +/// considered before regular transactions. #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct ChargeTransactionPayment(#[codec(compact)] BalanceOf); impl ChargeTransactionPayment where - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, BalanceOf: Send + Sync + FixedPointOperand, { /// utility constructor. Used only in client/factory code. @@ -537,7 +658,7 @@ where Self(fee) } - /// Returns the tip as being choosen by the transaction sender. + /// Returns the tip as being chosen by the transaction sender. pub fn tip(&self) -> BalanceOf { self.0 } @@ -545,8 +666,8 @@ where fn withdraw_fee( &self, who: &T::AccountId, - call: &T::Call, - info: &DispatchInfoOf, + call: &T::RuntimeCall, + info: &DispatchInfoOf, len: usize, ) -> Result< ( @@ -564,27 +685,77 @@ where .map(|i| (fee, i)) } - /// Get an appropriate priority for a transaction with the given length and info. + /// Get an appropriate priority for a transaction with the given `DispatchInfo`, encoded length + /// and user-included tip. /// - /// This will try and optimise the `fee/weight` `fee/length`, whichever is consuming more of the - /// maximum corresponding limit. + /// The priority is based on the amount of `tip` the user is willing to pay per unit of either + /// `weight` or `length`, depending which one is more limiting. For `Operational` extrinsics + /// we add a "virtual tip" to the calculations. /// - /// For example, if a transaction consumed 1/4th of the block length and half of the weight, its - /// final priority is `fee * min(2, 4) = fee * 2`. If it consumed `1/4th` of the block length - /// and the entire block weight `(1/1)`, its priority is `fee * min(1, 4) = fee * 1`. This means - /// that the transaction which consumes more resources (either length or weight) with the same - /// `fee` ends up having lower priority. - fn get_priority( + /// The formula should simply be `tip / bounded_{weight|length}`, but since we are using + /// integer division, we have no guarantees it's going to give results in any reasonable + /// range (might simply end up being zero). Hence we use a scaling factor: + /// `tip * (max_block_{weight|length} / bounded_{weight|length})`, since given current + /// state of-the-art blockchains, number of per-block transactions is expected to be in a + /// range reasonable enough to not saturate the `Balance` type while multiplying by the tip. + pub fn get_priority( + info: &DispatchInfoOf, len: usize, - info: &DispatchInfoOf, + tip: BalanceOf, final_fee: BalanceOf, ) -> TransactionPriority { - let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1); - let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal); - let len_saturation = max_block_length as u64 / (len as u64).max(1); - let coefficient: BalanceOf = - weight_saturation.min(len_saturation).saturated_into::>(); - final_fee.saturating_mul(coefficient).saturated_into::() + // Calculate how many such extrinsics we could fit into an empty block and take + // the limitting factor. + let max_block_weight = T::BlockWeights::get().max_block; + let max_block_length = *T::BlockLength::get().max.get(info.class) as u64; + + // TODO: Take into account all dimensions of weight + let max_block_weight = max_block_weight.ref_time(); + let info_weight = info.weight.ref_time(); + + let bounded_weight = info_weight.clamp(1, max_block_weight); + let bounded_length = (len as u64).clamp(1, max_block_length); + + let max_tx_per_block_weight = max_block_weight / bounded_weight; + let max_tx_per_block_length = max_block_length / bounded_length; + // Given our current knowledge this value is going to be in a reasonable range - i.e. + // less than 10^9 (2^30), so multiplying by the `tip` value is unlikely to overflow the + // balance type. We still use saturating ops obviously, but the point is to end up with some + // `priority` distribution instead of having all transactions saturate the priority. + let max_tx_per_block = max_tx_per_block_length + .min(max_tx_per_block_weight) + .saturated_into::>(); + let max_reward = |val: BalanceOf| val.saturating_mul(max_tx_per_block); + + // To distribute no-tip transactions a little bit, we increase the tip value by one. + // This means that given two transactions without a tip, smaller one will be preferred. + let tip = tip.saturating_add(One::one()); + let scaled_tip = max_reward(tip); + + match info.class { + DispatchClass::Normal => { + // For normal class we simply take the `tip_per_weight`. + scaled_tip + }, + DispatchClass::Mandatory => { + // Mandatory extrinsics should be prohibited (e.g. by the [`CheckWeight`] + // extensions), but just to be safe let's return the same priority as `Normal` here. + scaled_tip + }, + DispatchClass::Operational => { + // A "virtual tip" value added to an `Operational` extrinsic. + // This value should be kept high enough to allow `Operational` extrinsics + // to get in even during congestion period, but at the same time low + // enough to prevent a possible spam attack by sending invalid operational + // extrinsics which push away regular transactions from the pool. + let fee_multiplier = T::OperationalFeeMultiplier::get().saturated_into(); + let virtual_tip = final_fee.saturating_mul(fee_multiplier); + let scaled_virtual_tip = max_reward(virtual_tip); + + scaled_tip.saturating_add(scaled_virtual_tip) + }, + } + .saturated_into::() } } @@ -602,16 +773,16 @@ impl sp_std::fmt::Debug for ChargeTransactionPayment { impl SignedExtension for ChargeTransactionPayment where BalanceOf: Send + Sync + From + FixedPointOperand, - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { const IDENTIFIER: &'static str = "ChargeTransactionPayment"; type AccountId = T::AccountId; - type Call = T::Call; + type Call = T::RuntimeCall; type AdditionalSigned = (); type Pre = ( // tip BalanceOf, - // who paid the fee + // who paid the fee - this is an option to allow for a Default impl. Self::AccountId, // imbalance resulting from withdrawing the fee <::OnChargeTransaction as OnChargeTransaction>::LiquidityInfo, @@ -627,8 +798,12 @@ where info: &DispatchInfoOf, len: usize, ) -> TransactionValidity { - let (fee, _) = self.withdraw_fee(who, call, info, len)?; - Ok(ValidTransaction { priority: Self::get_priority(len, info, fee), ..Default::default() }) + let (final_fee, _) = self.withdraw_fee(who, call, info, len)?; + let tip = self.0; + Ok(ValidTransaction { + priority: Self::get_priority(info, len, tip, final_fee), + ..Default::default() + }) } fn pre_dispatch( @@ -643,17 +818,19 @@ where } fn post_dispatch( - pre: Self::Pre, + maybe_pre: Option, info: &DispatchInfoOf, post_info: &PostDispatchInfoOf, len: usize, _result: &DispatchResult, ) -> Result<(), TransactionValidityError> { - let (tip, who, imbalance) = pre; - let actual_fee = Pallet::::compute_actual_fee(len as u32, info, post_info, tip); - T::OnChargeTransaction::correct_and_deposit_fee( - &who, info, post_info, actual_fee, tip, imbalance, - )?; + if let Some((tip, who, imbalance)) = maybe_pre { + let actual_fee = Pallet::::compute_actual_fee(len as u32, info, post_info, tip); + T::OnChargeTransaction::correct_and_deposit_fee( + &who, info, post_info, actual_fee, tip, imbalance, + )?; + Pallet::::deposit_event(Event::::TransactionFeePaid { who, actual_fee, tip }); + } Ok(()) } } @@ -662,7 +839,7 @@ impl EstimateCallFee where BalanceOf: FixedPointOperand, - T::Call: Dispatchable, + T::RuntimeCall: Dispatchable, { fn estimate_call_fee(call: &AnyCall, post_info: PostDispatchInfo) -> BalanceOf { let len = call.encoded_size() as u32; @@ -676,26 +853,21 @@ mod tests { use super::*; use crate as pallet_transaction_payment; - use std::cell::RefCell; - use codec::Encode; - use smallvec::smallvec; use sp_core::H256; use sp_runtime::{ testing::{Header, TestXt}, traits::{BlakeTwo256, IdentityLookup, One}, transaction_validity::InvalidTransaction, - Perbill, }; use frame_support::{ - assert_noop, assert_ok, parameter_types, - traits::{Currency, Imbalance, OnUnbalanced}, - weights::{ - DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo, Weight, - WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, - }, + assert_noop, assert_ok, + dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo}, + parameter_types, + traits::{ConstU32, ConstU64, Currency, GenesisBuild, Imbalance, OnUnbalanced}, + weights::{Weight, WeightToFee as WeightToFeeT}, }; use frame_system as system; use pallet_balances::Call as BalancesCall; @@ -711,36 +883,36 @@ mod tests { { System: system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, } ); - const CALL: &::Call = - &Call::Balances(BalancesCall::transfer { dest: 2, value: 69 }); + const CALL: &::RuntimeCall = + &RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 69 }); - thread_local! { - static EXTRINSIC_BASE_WEIGHT: RefCell = RefCell::new(0); + parameter_types! { + static ExtrinsicBaseWeight: Weight = Weight::zero(); } pub struct BlockWeights; impl Get for BlockWeights { fn get() -> frame_system::limits::BlockWeights { frame_system::limits::BlockWeights::builder() - .base_block(0) + .base_block(Weight::zero()) .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = EXTRINSIC_BASE_WEIGHT.with(|v| *v.borrow()).into(); + weights.base_extrinsic = ExtrinsicBaseWeight::get().into(); }) .for_class(DispatchClass::non_mandatory(), |weights| { - weights.max_total = 1024.into(); + weights.max_total = Weight::from_ref_time(1024).set_proof_size(u64::MAX).into(); }) .build_or_panic() } } parameter_types! { - pub const BlockHashCount: u64 = 250; - pub static TransactionByteFee: u64 = 1; pub static WeightToFee: u64 = 1; + pub static TransactionByteFee: u64 = 1; + pub static OperationalFeeMultiplier: u8 = 5; } impl frame_system::Config for Runtime { @@ -748,17 +920,17 @@ mod tests { type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -767,17 +939,14 @@ mod tests { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); - } - - parameter_types! { - pub const ExistentialDeposit: u64 = 1; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Runtime { type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type MaxLocks = (); type MaxReserves = (); @@ -785,22 +954,27 @@ mod tests { type WeightInfo = (); } - impl WeightToFeePolynomial for WeightToFee { + impl WeightToFeeT for WeightToFee { + type Balance = u64; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(weight.ref_time()) + .saturating_mul(WEIGHT_TO_FEE.with(|v| *v.borrow())) + } + } + + impl WeightToFeeT for TransactionByteFee { type Balance = u64; - fn polynomial() -> WeightToFeeCoefficients { - smallvec![WeightToFeeCoefficient { - degree: 1, - coeff_frac: Perbill::zero(), - coeff_integer: WEIGHT_TO_FEE.with(|v| *v.borrow()), - negative: false, - }] + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(weight.ref_time()) + .saturating_mul(TRANSACTION_BYTE_FEE.with(|v| *v.borrow())) } } - thread_local! { - static TIP_UNBALANCED_AMOUNT: RefCell = RefCell::new(0); - static FEE_UNBALANCED_AMOUNT: RefCell = RefCell::new(0); + parameter_types! { + static TipUnbalancedAmount: u64 = 0; + static FeeUnbalancedAmount: u64 = 0; } pub struct DealWithFees; @@ -809,36 +983,45 @@ mod tests { mut fees_then_tips: impl Iterator>, ) { if let Some(fees) = fees_then_tips.next() { - FEE_UNBALANCED_AMOUNT.with(|a| *a.borrow_mut() += fees.peek()); + FeeUnbalancedAmount::mutate(|a| *a += fees.peek()); if let Some(tips) = fees_then_tips.next() { - TIP_UNBALANCED_AMOUNT.with(|a| *a.borrow_mut() += tips.peek()); + TipUnbalancedAmount::mutate(|a| *a += tips.peek()); } } } } impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = CurrencyAdapter; - type TransactionByteFee = TransactionByteFee; + type OperationalFeeMultiplier = OperationalFeeMultiplier; type WeightToFee = WeightToFee; + type LengthToFee = TransactionByteFee; type FeeMultiplierUpdate = (); } pub struct ExtBuilder { balance_factor: u64, - base_weight: u64, + base_weight: Weight, byte_fee: u64, weight_to_fee: u64, + initial_multiplier: Option, } impl Default for ExtBuilder { fn default() -> Self { - Self { balance_factor: 1, base_weight: 0, byte_fee: 1, weight_to_fee: 1 } + Self { + balance_factor: 1, + base_weight: Weight::zero(), + byte_fee: 1, + weight_to_fee: 1, + initial_multiplier: None, + } } } impl ExtBuilder { - pub fn base_weight(mut self, base_weight: u64) -> Self { + pub fn base_weight(mut self, base_weight: Weight) -> Self { self.base_weight = base_weight; self } @@ -854,8 +1037,12 @@ mod tests { self.balance_factor = factor; self } + pub fn with_initial_multiplier(mut self, multiplier: Multiplier) -> Self { + self.initial_multiplier = Some(multiplier); + self + } fn set_constants(&self) { - EXTRINSIC_BASE_WEIGHT.with(|v| *v.borrow_mut() = self.base_weight); + ExtrinsicBaseWeight::mutate(|v| *v = self.base_weight); TRANSACTION_BYTE_FEE.with(|v| *v.borrow_mut() = self.byte_fee); WEIGHT_TO_FEE.with(|v| *v.borrow_mut() = self.weight_to_fee); } @@ -878,6 +1065,12 @@ mod tests { } .assimilate_storage(&mut t) .unwrap(); + + if let Some(multiplier) = self.initial_multiplier { + let genesis = pallet::GenesisConfig { multiplier }; + GenesisBuild::::assimilate_storage(&genesis, &mut t).unwrap(); + } + t.into() } } @@ -904,43 +1097,43 @@ mod tests { fn signed_extension_transaction_payment_work() { ExtBuilder::default() .balance_factor(10) - .base_weight(5) + .base_weight(Weight::from_ref_time(5)) .build() .execute_with(|| { let len = 10; let pre = ChargeTransactionPayment::::from(0) - .pre_dispatch(&1, CALL, &info_from_weight(5), len) + .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_ref_time(5)), len) .unwrap(); assert_eq!(Balances::free_balance(1), 100 - 5 - 5 - 10); assert_ok!(ChargeTransactionPayment::::post_dispatch( - pre, - &info_from_weight(5), + Some(pre), + &info_from_weight(Weight::from_ref_time(5)), &default_post_info(), len, &Ok(()) )); assert_eq!(Balances::free_balance(1), 100 - 5 - 5 - 10); - assert_eq!(FEE_UNBALANCED_AMOUNT.with(|a| a.borrow().clone()), 5 + 5 + 10); - assert_eq!(TIP_UNBALANCED_AMOUNT.with(|a| a.borrow().clone()), 0); + assert_eq!(FeeUnbalancedAmount::get(), 5 + 5 + 10); + assert_eq!(TipUnbalancedAmount::get(), 0); - FEE_UNBALANCED_AMOUNT.with(|a| *a.borrow_mut() = 0); + FeeUnbalancedAmount::mutate(|a| *a = 0); let pre = ChargeTransactionPayment::::from(5 /* tipped */) - .pre_dispatch(&2, CALL, &info_from_weight(100), len) + .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_ref_time(100)), len) .unwrap(); assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 100 - 5); assert_ok!(ChargeTransactionPayment::::post_dispatch( - pre, - &info_from_weight(100), - &post_info_from_weight(50), + Some(pre), + &info_from_weight(Weight::from_ref_time(100)), + &post_info_from_weight(Weight::from_ref_time(50)), len, &Ok(()) )); assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 50 - 5); - assert_eq!(FEE_UNBALANCED_AMOUNT.with(|a| a.borrow().clone()), 5 + 10 + 50); - assert_eq!(TIP_UNBALANCED_AMOUNT.with(|a| a.borrow().clone()), 5); + assert_eq!(FeeUnbalancedAmount::get(), 5 + 10 + 50); + assert_eq!(TipUnbalancedAmount::get(), 5); }); } @@ -948,22 +1141,22 @@ mod tests { fn signed_extension_transaction_payment_multiplied_refund_works() { ExtBuilder::default() .balance_factor(10) - .base_weight(5) + .base_weight(Weight::from_ref_time(5)) .build() .execute_with(|| { let len = 10; >::put(Multiplier::saturating_from_rational(3, 2)); let pre = ChargeTransactionPayment::::from(5 /* tipped */) - .pre_dispatch(&2, CALL, &info_from_weight(100), len) + .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_ref_time(100)), len) .unwrap(); // 5 base fee, 10 byte fee, 3/2 * 100 weight fee, 5 tip assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 150 - 5); assert_ok!(ChargeTransactionPayment::::post_dispatch( - pre, - &info_from_weight(100), - &post_info_from_weight(50), + Some(pre), + &info_from_weight(Weight::from_ref_time(100)), + &post_info_from_weight(Weight::from_ref_time(50)), len, &Ok(()) )); @@ -979,13 +1172,14 @@ mod tests { assert_ok!(ChargeTransactionPayment::::from(0).pre_dispatch( &1, CALL, - &info_from_weight(Weight::max_value()), + &info_from_weight(Weight::MAX), 10 )); // fee will be proportional to what is the actual maximum weight in the runtime. assert_eq!( Balances::free_balance(&1), - (10000 - ::BlockWeights::get().max_block) as u64 + (10000 - + ::BlockWeights::get().max_block.ref_time()) as u64 ); }); } @@ -993,7 +1187,7 @@ mod tests { #[test] fn signed_extension_allows_free_transactions() { ExtBuilder::default() - .base_weight(100) + .base_weight(Weight::from_ref_time(100)) .balance_factor(0) .build() .execute_with(|| { @@ -1004,7 +1198,7 @@ mod tests { // This is a completely free (and thus wholly insecure/DoS-ridden) transaction. let operational_transaction = DispatchInfo { - weight: 0, + weight: Weight::from_ref_time(0), class: DispatchClass::Operational, pays_fee: Pays::No, }; @@ -1016,8 +1210,11 @@ mod tests { )); // like a InsecureFreeNormal - let free_transaction = - DispatchInfo { weight: 0, class: DispatchClass::Normal, pays_fee: Pays::Yes }; + let free_transaction = DispatchInfo { + weight: Weight::from_ref_time(0), + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }; assert_noop!( ChargeTransactionPayment::::from(0).validate( &1, @@ -1033,7 +1230,7 @@ mod tests { #[test] fn signed_ext_length_fee_is_also_updated_per_congestion() { ExtBuilder::default() - .base_weight(5) + .base_weight(Weight::from_ref_time(5)) .balance_factor(10) .build() .execute_with(|| { @@ -1041,8 +1238,10 @@ mod tests { >::put(Multiplier::saturating_from_rational(3, 2)); let len = 10; - assert_ok!(ChargeTransactionPayment::::from(10) // tipped - .pre_dispatch(&1, CALL, &info_from_weight(3), len)); + assert_ok!( + ChargeTransactionPayment::::from(10) // tipped + .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_ref_time(3)), len) + ); assert_eq!( Balances::free_balance(1), 100 // original @@ -1055,35 +1254,115 @@ mod tests { } #[test] - fn query_info_works() { - let call = Call::Balances(BalancesCall::transfer { dest: 2, value: 69 }); + fn query_info_and_fee_details_works() { + let call = RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 69 }); let origin = 111111; let extra = (); - let xt = TestXt::new(call, Some((origin, extra))); + let xt = TestXt::new(call.clone(), Some((origin, extra))); let info = xt.get_dispatch_info(); let ext = xt.encode(); let len = ext.len() as u32; - ExtBuilder::default().base_weight(5).weight_fee(2).build().execute_with(|| { - // all fees should be x1.5 - >::put(Multiplier::saturating_from_rational(3, 2)); - assert_eq!( - TransactionPayment::query_info(xt, len), - RuntimeDispatchInfo { - weight: info.weight, - class: info.class, - partial_fee: 5 * 2 /* base * weight_fee */ + let unsigned_xt = TestXt::<_, ()>::new(call, None); + let unsigned_xt_info = unsigned_xt.get_dispatch_info(); + + ExtBuilder::default() + .base_weight(Weight::from_ref_time(5)) + .weight_fee(2) + .build() + .execute_with(|| { + // all fees should be x1.5 + >::put(Multiplier::saturating_from_rational(3, 2)); + + assert_eq!( + TransactionPayment::query_info(xt.clone(), len), + RuntimeDispatchInfo { + weight: info.weight, + class: info.class, + partial_fee: 5 * 2 /* base * weight_fee */ + len as u64 /* len * 1 */ - + info.weight.min(BlockWeights::get().max_block) as u64 * 2 * 3 / 2 /* weight */ - }, - ); - }); + + info.weight.min(BlockWeights::get().max_block).ref_time() as u64 * 2 * 3 / 2 /* weight */ + }, + ); + + assert_eq!( + TransactionPayment::query_info(unsigned_xt.clone(), len), + RuntimeDispatchInfo { + weight: unsigned_xt_info.weight, + class: unsigned_xt_info.class, + partial_fee: 0, + }, + ); + + assert_eq!( + TransactionPayment::query_fee_details(xt, len), + FeeDetails { + inclusion_fee: Some(InclusionFee { + base_fee: 5 * 2, + len_fee: len as u64, + adjusted_weight_fee: info + .weight + .min(BlockWeights::get().max_block) + .ref_time() as u64 * 2 * 3 / 2 + }), + tip: 0, + }, + ); + + assert_eq!( + TransactionPayment::query_fee_details(unsigned_xt, len), + FeeDetails { inclusion_fee: None, tip: 0 }, + ); + }); + } + + #[test] + fn query_call_info_and_fee_details_works() { + let call = RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 69 }); + let info = call.get_dispatch_info(); + let encoded_call = call.encode(); + let len = encoded_call.len() as u32; + + ExtBuilder::default() + .base_weight(Weight::from_ref_time(5)) + .weight_fee(2) + .build() + .execute_with(|| { + // all fees should be x1.5 + >::put(Multiplier::saturating_from_rational(3, 2)); + + assert_eq!( + TransactionPayment::query_call_info(call.clone(), len), + RuntimeDispatchInfo { + weight: info.weight, + class: info.class, + partial_fee: 5 * 2 /* base * weight_fee */ + + len as u64 /* len * 1 */ + + info.weight.min(BlockWeights::get().max_block).ref_time() as u64 * 2 * 3 / 2 /* weight */ + }, + ); + + assert_eq!( + TransactionPayment::query_call_fee_details(call, len), + FeeDetails { + inclusion_fee: Some(InclusionFee { + base_fee: 5 * 2, /* base * weight_fee */ + len_fee: len as u64, /* len * 1 */ + adjusted_weight_fee: info + .weight + .min(BlockWeights::get().max_block) + .ref_time() as u64 * 2 * 3 / 2 /* weight * weight_fee * multipler */ + }), + tip: 0, + }, + ); + }); } #[test] fn compute_fee_works_without_multiplier() { ExtBuilder::default() - .base_weight(100) + .base_weight(Weight::from_ref_time(100)) .byte_fee(10) .balance_factor(0) .build() @@ -1093,14 +1372,14 @@ mod tests { // Tip only, no fees works let dispatch_info = DispatchInfo { - weight: 0, + weight: Weight::from_ref_time(0), class: DispatchClass::Operational, pays_fee: Pays::No, }; assert_eq!(Pallet::::compute_fee(0, &dispatch_info, 10), 10); // No tip, only base fee works let dispatch_info = DispatchInfo { - weight: 0, + weight: Weight::from_ref_time(0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -1111,7 +1390,7 @@ mod tests { assert_eq!(Pallet::::compute_fee(42, &dispatch_info, 0), 520); // Weight fee + base fee works let dispatch_info = DispatchInfo { - weight: 1000, + weight: Weight::from_ref_time(1000), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -1122,7 +1401,7 @@ mod tests { #[test] fn compute_fee_works_with_multiplier() { ExtBuilder::default() - .base_weight(100) + .base_weight(Weight::from_ref_time(100)) .byte_fee(10) .balance_factor(0) .build() @@ -1131,7 +1410,7 @@ mod tests { >::put(Multiplier::saturating_from_rational(3, 2)); // Base fee is unaffected by multiplier let dispatch_info = DispatchInfo { - weight: 0, + weight: Weight::from_ref_time(0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -1139,7 +1418,7 @@ mod tests { // Everything works together :) let dispatch_info = DispatchInfo { - weight: 123, + weight: Weight::from_ref_time(123), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -1154,7 +1433,7 @@ mod tests { #[test] fn compute_fee_works_with_negative_multiplier() { ExtBuilder::default() - .base_weight(100) + .base_weight(Weight::from_ref_time(100)) .byte_fee(10) .balance_factor(0) .build() @@ -1164,7 +1443,7 @@ mod tests { // Base fee is unaffected by multiplier. let dispatch_info = DispatchInfo { - weight: 0, + weight: Weight::from_ref_time(0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -1172,7 +1451,7 @@ mod tests { // Everything works together. let dispatch_info = DispatchInfo { - weight: 123, + weight: Weight::from_ref_time(123), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -1187,14 +1466,14 @@ mod tests { #[test] fn compute_fee_does_not_overflow() { ExtBuilder::default() - .base_weight(100) + .base_weight(Weight::from_ref_time(100)) .byte_fee(10) .balance_factor(0) .build() .execute_with(|| { // Overflow is handled let dispatch_info = DispatchInfo { - weight: Weight::max_value(), + weight: Weight::MAX, class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -1209,14 +1488,14 @@ mod tests { fn refund_does_not_recreate_account() { ExtBuilder::default() .balance_factor(10) - .base_weight(5) + .base_weight(Weight::from_ref_time(5)) .build() .execute_with(|| { // So events are emitted System::set_block_number(10); let len = 10; let pre = ChargeTransactionPayment::::from(5 /* tipped */) - .pre_dispatch(&2, CALL, &info_from_weight(100), len) + .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_ref_time(100)), len) .unwrap(); assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 100 - 5); @@ -1225,19 +1504,21 @@ mod tests { assert_eq!(Balances::free_balance(2), 0); assert_ok!(ChargeTransactionPayment::::post_dispatch( - pre, - &info_from_weight(100), - &post_info_from_weight(50), + Some(pre), + &info_from_weight(Weight::from_ref_time(100)), + &post_info_from_weight(Weight::from_ref_time(50)), len, &Ok(()) )); assert_eq!(Balances::free_balance(2), 0); // Transfer Event - System::assert_has_event(Event::Balances(pallet_balances::Event::Transfer( - 2, 3, 80, - ))); + System::assert_has_event(RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from: 2, to: 3, amount: 80 }, + )); // Killed Event - System::assert_has_event(Event::System(system::Event::KilledAccount(2))); + System::assert_has_event(RuntimeEvent::System(system::Event::KilledAccount { + account: 2, + })); }); } @@ -1245,19 +1526,19 @@ mod tests { fn actual_weight_higher_than_max_refunds_nothing() { ExtBuilder::default() .balance_factor(10) - .base_weight(5) + .base_weight(Weight::from_ref_time(5)) .build() .execute_with(|| { let len = 10; let pre = ChargeTransactionPayment::::from(5 /* tipped */) - .pre_dispatch(&2, CALL, &info_from_weight(100), len) + .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_ref_time(100)), len) .unwrap(); assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 100 - 5); assert_ok!(ChargeTransactionPayment::::post_dispatch( - pre, - &info_from_weight(100), - &post_info_from_weight(101), + Some(pre), + &info_from_weight(Weight::from_ref_time(100)), + &post_info_from_weight(Weight::from_ref_time(101)), len, &Ok(()) )); @@ -1269,29 +1550,38 @@ mod tests { fn zero_transfer_on_free_transaction() { ExtBuilder::default() .balance_factor(10) - .base_weight(5) + .base_weight(Weight::from_ref_time(5)) .build() .execute_with(|| { // So events are emitted System::set_block_number(10); let len = 10; - let dispatch_info = - DispatchInfo { weight: 100, pays_fee: Pays::No, class: DispatchClass::Normal }; + let dispatch_info = DispatchInfo { + weight: Weight::from_ref_time(100), + pays_fee: Pays::No, + class: DispatchClass::Normal, + }; let user = 69; let pre = ChargeTransactionPayment::::from(0) .pre_dispatch(&user, CALL, &dispatch_info, len) .unwrap(); assert_eq!(Balances::total_balance(&user), 0); assert_ok!(ChargeTransactionPayment::::post_dispatch( - pre, + Some(pre), &dispatch_info, &default_post_info(), len, &Ok(()) )); assert_eq!(Balances::total_balance(&user), 0); - // No events for such a scenario - assert_eq!(System::events().len(), 0); + // TransactionFeePaid Event + System::assert_has_event(RuntimeEvent::TransactionPayment( + pallet_transaction_payment::Event::TransactionFeePaid { + who: user, + actual_fee: 0, + tip: 0, + }, + )); }); } @@ -1299,11 +1589,11 @@ mod tests { fn refund_consistent_with_actual_weight() { ExtBuilder::default() .balance_factor(10) - .base_weight(7) + .base_weight(Weight::from_ref_time(7)) .build() .execute_with(|| { - let info = info_from_weight(100); - let post_info = post_info_from_weight(33); + let info = info_from_weight(Weight::from_ref_time(100)); + let post_info = post_info_from_weight(Weight::from_ref_time(33)); let prev_balance = Balances::free_balance(2); let len = 10; let tip = 5; @@ -1315,7 +1605,7 @@ mod tests { .unwrap(); ChargeTransactionPayment::::post_dispatch( - pre, + Some(pre), &info, &post_info, len, @@ -1333,14 +1623,136 @@ mod tests { }); } + #[test] + fn should_alter_operational_priority() { + let tip = 5; + let len = 10; + + ExtBuilder::default().balance_factor(100).build().execute_with(|| { + let normal = DispatchInfo { + weight: Weight::from_ref_time(100), + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }; + let priority = ChargeTransactionPayment::(tip) + .validate(&2, CALL, &normal, len) + .unwrap() + .priority; + + assert_eq!(priority, 60); + + let priority = ChargeTransactionPayment::(2 * tip) + .validate(&2, CALL, &normal, len) + .unwrap() + .priority; + + assert_eq!(priority, 110); + }); + + ExtBuilder::default().balance_factor(100).build().execute_with(|| { + let op = DispatchInfo { + weight: Weight::from_ref_time(100), + class: DispatchClass::Operational, + pays_fee: Pays::Yes, + }; + let priority = ChargeTransactionPayment::(tip) + .validate(&2, CALL, &op, len) + .unwrap() + .priority; + assert_eq!(priority, 5810); + + let priority = ChargeTransactionPayment::(2 * tip) + .validate(&2, CALL, &op, len) + .unwrap() + .priority; + assert_eq!(priority, 6110); + }); + } + + #[test] + fn no_tip_has_some_priority() { + let tip = 0; + let len = 10; + + ExtBuilder::default().balance_factor(100).build().execute_with(|| { + let normal = DispatchInfo { + weight: Weight::from_ref_time(100), + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }; + let priority = ChargeTransactionPayment::(tip) + .validate(&2, CALL, &normal, len) + .unwrap() + .priority; + + assert_eq!(priority, 10); + }); + + ExtBuilder::default().balance_factor(100).build().execute_with(|| { + let op = DispatchInfo { + weight: Weight::from_ref_time(100), + class: DispatchClass::Operational, + pays_fee: Pays::Yes, + }; + let priority = ChargeTransactionPayment::(tip) + .validate(&2, CALL, &op, len) + .unwrap() + .priority; + assert_eq!(priority, 5510); + }); + } + + #[test] + fn higher_tip_have_higher_priority() { + let get_priorities = |tip: u64| { + let mut priority1 = 0; + let mut priority2 = 0; + let len = 10; + ExtBuilder::default().balance_factor(100).build().execute_with(|| { + let normal = DispatchInfo { + weight: Weight::from_ref_time(100), + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }; + priority1 = ChargeTransactionPayment::(tip) + .validate(&2, CALL, &normal, len) + .unwrap() + .priority; + }); + + ExtBuilder::default().balance_factor(100).build().execute_with(|| { + let op = DispatchInfo { + weight: Weight::from_ref_time(100), + class: DispatchClass::Operational, + pays_fee: Pays::Yes, + }; + priority2 = ChargeTransactionPayment::(tip) + .validate(&2, CALL, &op, len) + .unwrap() + .priority; + }); + + (priority1, priority2) + }; + + let mut prev_priorities = get_priorities(0); + + for tip in 1..3 { + let priorities = get_priorities(tip); + assert!(prev_priorities.0 < priorities.0); + assert!(prev_priorities.1 < priorities.1); + prev_priorities = priorities; + } + } + #[test] fn post_info_can_change_pays_fee() { ExtBuilder::default() .balance_factor(10) - .base_weight(7) + .base_weight(Weight::from_ref_time(7)) .build() .execute_with(|| { - let info = info_from_weight(100); + let info = info_from_weight(Weight::from_ref_time(100)); let post_info = post_info_from_pays(Pays::No); let prev_balance = Balances::free_balance(2); let len = 10; @@ -1353,7 +1765,7 @@ mod tests { .unwrap(); ChargeTransactionPayment::::post_dispatch( - pre, + Some(pre), &info, &post_info, len, @@ -1370,4 +1782,24 @@ mod tests { assert_eq!(refund_based_fee, actual_fee); }); } + + #[test] + fn genesis_config_works() { + ExtBuilder::default() + .with_initial_multiplier(Multiplier::from_u32(100)) + .build() + .execute_with(|| { + assert_eq!( + >::get(), + Multiplier::saturating_from_integer(100) + ); + }); + } + + #[test] + fn genesis_default_works() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(>::get(), Multiplier::saturating_from_integer(1)); + }); + } } diff --git a/frame/transaction-payment/src/payment.rs b/frame/transaction-payment/src/payment.rs index 58e6ef63109a3..ebc9c5c5afd62 100644 --- a/frame/transaction-payment/src/payment.rs +++ b/frame/transaction-payment/src/payment.rs @@ -12,7 +12,7 @@ use sp_runtime::{ use sp_std::{fmt::Debug, marker::PhantomData}; use frame_support::{ - traits::{Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, WithdrawReasons}, + traits::{Currency, ExistenceRequirement, Imbalance, OnUnbalanced, WithdrawReasons}, unsigned::TransactionValidityError, }; @@ -37,8 +37,8 @@ pub trait OnChargeTransaction { /// Note: The `fee` already includes the `tip`. fn withdraw_fee( who: &T::AccountId, - call: &T::Call, - dispatch_info: &DispatchInfoOf, + call: &T::RuntimeCall, + dispatch_info: &DispatchInfoOf, fee: Self::Balance, tip: Self::Balance, ) -> Result; @@ -50,8 +50,8 @@ pub trait OnChargeTransaction { /// Note: The `fee` already includes the `tip`. fn correct_and_deposit_fee( who: &T::AccountId, - dispatch_info: &DispatchInfoOf, - post_info: &PostDispatchInfoOf, + dispatch_info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, corrected_fee: Self::Balance, tip: Self::Balance, already_withdrawn: Self::LiquidityInfo, @@ -73,7 +73,6 @@ pub struct CurrencyAdapter(PhantomData<(C, OU)>); impl OnChargeTransaction for CurrencyAdapter where T: Config, - T::TransactionByteFee: Get<::AccountId>>::Balance>, C: Currency<::AccountId>, C::PositiveImbalance: Imbalance< ::AccountId>>::Balance, @@ -93,8 +92,8 @@ where /// Note: The `fee` already includes the `tip`. fn withdraw_fee( who: &T::AccountId, - _call: &T::Call, - _info: &DispatchInfoOf, + _call: &T::RuntimeCall, + _info: &DispatchInfoOf, fee: Self::Balance, tip: Self::Balance, ) -> Result { @@ -121,8 +120,8 @@ where /// Note: The `corrected_fee` already includes the `tip`. fn correct_and_deposit_fee( who: &T::AccountId, - _dispatch_info: &DispatchInfoOf, - _post_info: &PostDispatchInfoOf, + _dispatch_info: &DispatchInfoOf, + _post_info: &PostDispatchInfoOf, corrected_fee: Self::Balance, tip: Self::Balance, already_withdrawn: Self::LiquidityInfo, @@ -133,7 +132,7 @@ where // refund to the the account that paid the fees. If this fails, the // account might have dropped below the existential balance. In // that case we don't refund anything. - let refund_imbalance = C::deposit_into_existing(&who, refund_amount) + let refund_imbalance = C::deposit_into_existing(who, refund_amount) .unwrap_or_else(|_| C::PositiveImbalance::zero()); // merge the imbalance caused by paying the fees and refunding parts of it again. let adjusted_paid = paid diff --git a/frame/transaction-payment/src/types.rs b/frame/transaction-payment/src/types.rs index 3ce5bcf890bd1..d1a480b64e116 100644 --- a/frame/transaction-payment/src/types.rs +++ b/frame/transaction-payment/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize}; use sp_runtime::traits::{AtLeast32BitUnsigned, Zero}; use sp_std::prelude::*; -use frame_support::weights::{DispatchClass, Weight}; +use frame_support::dispatch::DispatchClass; /// The base fee and adjusted weight and length fees constitute the _inclusion fee_. #[derive(Encode, Decode, Clone, Eq, PartialEq)] @@ -94,9 +94,15 @@ impl FeeDetails { #[derive(Eq, PartialEq, Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "std", serde(bound(serialize = "Balance: std::fmt::Display")))] -#[cfg_attr(feature = "std", serde(bound(deserialize = "Balance: std::str::FromStr")))] -pub struct RuntimeDispatchInfo { +#[cfg_attr( + feature = "std", + serde(bound(serialize = "Balance: std::fmt::Display, Weight: Serialize")) +)] +#[cfg_attr( + feature = "std", + serde(bound(deserialize = "Balance: std::str::FromStr, Weight: Deserialize<'de>")) +)] +pub struct RuntimeDispatchInfo { /// Weight of this dispatch. pub weight: Weight, /// Class of this dispatch. @@ -131,16 +137,18 @@ mod serde_balance { #[cfg(test)] mod tests { use super::*; + use frame_support::weights::Weight; #[test] fn should_serialize_and_deserialize_properly_with_string() { let info = RuntimeDispatchInfo { - weight: 5, + weight: Weight::from_ref_time(5), class: DispatchClass::Normal, partial_fee: 1_000_000_u64, }; - let json_str = r#"{"weight":5,"class":"normal","partialFee":"1000000"}"#; + let json_str = + r#"{"weight":{"ref_time":5,"proof_size":0},"class":"normal","partialFee":"1000000"}"#; assert_eq!(serde_json::to_string(&info).unwrap(), json_str); assert_eq!(serde_json::from_str::>(json_str).unwrap(), info); @@ -152,12 +160,12 @@ mod tests { #[test] fn should_serialize_and_deserialize_properly_large_value() { let info = RuntimeDispatchInfo { - weight: 5, + weight: Weight::from_ref_time(5), class: DispatchClass::Normal, partial_fee: u128::max_value(), }; - let json_str = r#"{"weight":5,"class":"normal","partialFee":"340282366920938463463374607431768211455"}"#; + let json_str = r#"{"weight":{"ref_time":5,"proof_size":0},"class":"normal","partialFee":"340282366920938463463374607431768211455"}"#; assert_eq!(serde_json::to_string(&info).unwrap(), json_str); assert_eq!(serde_json::from_str::>(json_str).unwrap(), info); diff --git a/frame/transaction-storage/Cargo.toml b/frame/transaction-storage/Cargo.toml index a4ebd5cfbc876..73867c3643a69 100644 --- a/frame/transaction-storage/Cargo.toml +++ b/frame/transaction-storage/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-transaction-storage" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Unlicense" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Storage chain pallet" readme = "README.md" @@ -13,36 +13,41 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.126", optional = true } -hex-literal = { version = "0.3.1", optional = true } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +array-bytes = { version = "4.1", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } sp-transaction-storage-proof = { version = "4.0.0-dev", default-features = false, path = "../../primitives/transaction-storage-proof" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +log = { version = "0.4.17", default-features = false } [dev-dependencies] +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } sp-transaction-storage-proof = { version = "4.0.0-dev", default-features = true, path = "../../primitives/transaction-storage-proof" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core", default-features = false } [features] default = ["std"] -runtime-benchmarks = ["frame-benchmarking", "hex-literal"] +runtime-benchmarks = ["array-bytes", "frame-benchmarking/runtime-benchmarks"] std = [ - "serde", + "log/std", + "frame-benchmarking?/std", "codec/std", - "scale-info/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", "pallet-balances/std", + "scale-info/std", + "serde", + "sp-inherents/std", "sp-io/std", + "sp-runtime/std", "sp-std/std", - "sp-inherents/std", + "sp-transaction-storage-proof/std", ] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/transaction-storage/src/benchmarking.rs b/frame/transaction-storage/src/benchmarking.rs index d5da6a42b46f0..c7fbd00fb565d 100644 --- a/frame/transaction-storage/src/benchmarking.rs +++ b/frame/transaction-storage/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,8 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -use frame_support::traits::{Currency, OnFinalize, OnInitialize}; +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::traits::{Currency, Get, OnFinalize, OnInitialize}; use frame_system::{EventRecord, Pallet as System, RawOrigin}; use sp_runtime::traits::{Bounded, One, Zero}; use sp_std::*; @@ -29,62 +29,86 @@ use sp_transaction_storage_proof::TransactionStorageProof; use crate::Pallet as TransactionStorage; -const PROOF: &[u8] = &hex_literal::hex!( - " - 0104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - 000000000000000000000000000000014cd0780ffff80e316a478e2f1fcb13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe8 - 7d12a3662c4c0080e316a478e2f1fcb13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe87d12a3662c4c80e316a478e2f1fcb - 13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe87d12a3662c4c80e316a478e2f1fcb13cf22fd0b2dbb54a6f53cff93f3ca2 - f3dfe87d12a3662c4c80e316a478e2f1fcb13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe87d12a3662c4c80e316a478e2f - 1fcb13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe87d12a3662c4c80e316a478e2f1fcb13cf22fd0b2dbb54a6f53cff93f - 3ca2f3dfe87d12a3662c4c80e316a478e2f1fcb13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe87d12a3662c4c80e316a47 - 8e2f1fcb13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe87d12a3662c4c80e316a478e2f1fcb13cf22fd0b2dbb54a6f53cf - f93f3ca2f3dfe87d12a3662c4c80e316a478e2f1fcb13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe87d12a3662c4c80e31 - 6a478e2f1fcb13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe87d12a3662c4c80e316a478e2f1fcb13cf22fd0b2dbb54a6f - 53cff93f3ca2f3dfe87d12a3662c4c80e316a478e2f1fcb13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe87d12a3662c4c8 - 0e316a478e2f1fcb13cf22fd0b2dbb54a6f53cff93f3ca2f3dfe87d12a3662c4cbd05807777809a5d7a720ce5f9d9a012 - fbf25e92c30e732dadba8f312b05e02976313ea64d9f807d43bcbf8a3dc2f6b9e957d129e610c06d411e11743062dc1cf - 3ac289390ae4c8008592aa2d915f52941036afbe72bac4ebe7ce186c4ddc53f118e0ddd4decd8cc809a5d7a720ce5f9d9 - a012fbf25e92c30e732dadba8f312b05e02976313ea64d9f807d43bcbf8a3dc2f6b9e957d129e610c06d411e11743062d - c1cf3ac289390ae4c00809a5d7a720ce5f9d9a012fbf25e92c30e732dadba8f312b05e02976313ea64d9f807d43bcbf8a - 3dc2f6b9e957d129e610c06d411e11743062dc1cf3ac289390ae4c8008592aa2d915f52941036afbe72bac4ebe7ce186c - 4ddc53f118e0ddd4decd8cc809a5d7a720ce5f9d9a012fbf25e92c30e732dadba8f312b05e02976313ea64d9f807d43bc - bf8a3dc2f6b9e957d129e610c06d411e11743062dc1cf3ac289390ae4c8008592aa2d915f52941036afbe72bac4ebe7ce - 186c4ddc53f118e0ddd4decd8cccd0780ffff8081b825bfa9b2ba8f5f253515e7db09eb1ad3d4f02f322d3aa1398e0cb0 - 3bdb31008081b825bfa9b2ba8f5f253515e7db09eb1ad3d4f02f322d3aa1398e0cb03bdb318081b825bfa9b2ba8f5f253 - 515e7db09eb1ad3d4f02f322d3aa1398e0cb03bdb318081b825bfa9b2ba8f5f253515e7db09eb1ad3d4f02f322d3aa139 - 8e0cb03bdb318081b825bfa9b2ba8f5f253515e7db09eb1ad3d4f02f322d3aa1398e0cb03bdb318081b825bfa9b2ba8f5 - f253515e7db09eb1ad3d4f02f322d3aa1398e0cb03bdb318081b825bfa9b2ba8f5f253515e7db09eb1ad3d4f02f322d3a - a1398e0cb03bdb318081b825bfa9b2ba8f5f253515e7db09eb1ad3d4f02f322d3aa1398e0cb03bdb318081b825bfa9b2b - a8f5f253515e7db09eb1ad3d4f02f322d3aa1398e0cb03bdb318081b825bfa9b2ba8f5f253515e7db09eb1ad3d4f02f32 - 2d3aa1398e0cb03bdb318081b825bfa9b2ba8f5f253515e7db09eb1ad3d4f02f322d3aa1398e0cb03bdb318081b825bfa - 9b2ba8f5f253515e7db09eb1ad3d4f02f322d3aa1398e0cb03bdb318081b825bfa9b2ba8f5f253515e7db09eb1ad3d4f0 - 2f322d3aa1398e0cb03bdb318081b825bfa9b2ba8f5f253515e7db09eb1ad3d4f02f322d3aa1398e0cb03bdb318081b82 - 5bfa9b2ba8f5f253515e7db09eb1ad3d4f02f322d3aa1398e0cb03bdb31cd0780ffff80b4f23ac50c8e67d9b280f2b31a - 5707d52b892977acaac84d530bd188544c5f9b80b4f23ac50c8e67d9b280f2b31a5707d52b892977acaac84d530bd1885 - 44c5f9b80b4f23ac50c8e67d9b280f2b31a5707d52b892977acaac84d530bd188544c5f9b80b4f23ac50c8e67d9b280f2 - b31a5707d52b892977acaac84d530bd188544c5f9b80b4f23ac50c8e67d9b280f2b31a5707d52b892977acaac84d530bd - 188544c5f9b0080b4f23ac50c8e67d9b280f2b31a5707d52b892977acaac84d530bd188544c5f9b80b4f23ac50c8e67d9 - b280f2b31a5707d52b892977acaac84d530bd188544c5f9b80b4f23ac50c8e67d9b280f2b31a5707d52b892977acaac84 - d530bd188544c5f9b80b4f23ac50c8e67d9b280f2b31a5707d52b892977acaac84d530bd188544c5f9b80b4f23ac50c8e - 67d9b280f2b31a5707d52b892977acaac84d530bd188544c5f9b80b4f23ac50c8e67d9b280f2b31a5707d52b892977aca - ac84d530bd188544c5f9b80b4f23ac50c8e67d9b280f2b31a5707d52b892977acaac84d530bd188544c5f9b80b4f23ac5 - 0c8e67d9b280f2b31a5707d52b892977acaac84d530bd188544c5f9b80b4f23ac50c8e67d9b280f2b31a5707d52b89297 - 7acaac84d530bd188544c5f9b80b4f23ac50c8e67d9b280f2b31a5707d52b892977acaac84d530bd188544c5f9b104401 - 0000 -" -); +// Proof generated from max size storage: +// ``` +// let mut transactions = Vec::new(); +// let tx_size = DEFAULT_MAX_TRANSACTION_SIZE; +// for _ in 0..DEFAULT_MAX_BLOCK_TRANSACTIONS { +// transactions.push(vec![0; tx_size]); +// } +// let hash = vec![0; 32]; +// build_proof(hash.as_slice(), transactions).unwrap().encode() +// ``` +// while hardforcing target chunk key in `build_proof` to [22, 21, 1, 0]. +const PROOF: &str = "\ + 0104000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000\ + 00000000000000000000000000000000000000000000000000000000000014cd0780ffff8030\ + 2eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba0080302eb0a6d2\ + f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15\ + f1e729d1c1004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1\ + 004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e304\ + 8cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697\ + eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a\ + 30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba80302e\ + b0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b\ + 834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e7\ + 29d1c1004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c10046\ + 57e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf2\ + 06d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb1\ + 53f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba\ + bd058077778010fd81bc1359802f0b871aeb95e4410a8ec92b93af10ea767a2027cf4734e8de\ + 808da338e6b722f7bf2051901bd5bccee5e71d5cf6b1faff338ad7120b0256c28380221ce17f\ + 19117affa96e077905fe48a99723a065969c638593b7d9ab57b538438010fd81bc1359802f0b\ + 871aeb95e4410a8ec92b93af10ea767a2027cf4734e8de808da338e6b722f7bf2051901bd5bc\ + cee5e71d5cf6b1faff338ad7120b0256c283008010fd81bc1359802f0b871aeb95e4410a8ec9\ + 2b93af10ea767a2027cf4734e8de808da338e6b722f7bf2051901bd5bccee5e71d5cf6b1faff\ + 338ad7120b0256c28380221ce17f19117affa96e077905fe48a99723a065969c638593b7d9ab\ + 57b538438010fd81bc1359802f0b871aeb95e4410a8ec92b93af10ea767a2027cf4734e8de80\ + 8da338e6b722f7bf2051901bd5bccee5e71d5cf6b1faff338ad7120b0256c28380221ce17f19\ + 117affa96e077905fe48a99723a065969c638593b7d9ab57b53843cd0780ffff804509f59593\ + fd47b1a97189127ba65a5649cfb0346637f9836e155eaf891a939c00804509f59593fd47b1a9\ + 7189127ba65a5649cfb0346637f9836e155eaf891a939c804509f59593fd47b1a97189127ba6\ + 5a5649cfb0346637f9836e155eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb0\ + 346637f9836e155eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb0346637f983\ + 6e155eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb0346637f9836e155eaf89\ + 1a939c804509f59593fd47b1a97189127ba65a5649cfb0346637f9836e155eaf891a939c8045\ + 09f59593fd47b1a97189127ba65a5649cfb0346637f9836e155eaf891a939c804509f59593fd\ + 47b1a97189127ba65a5649cfb0346637f9836e155eaf891a939c804509f59593fd47b1a97189\ + 127ba65a5649cfb0346637f9836e155eaf891a939c804509f59593fd47b1a97189127ba65a56\ + 49cfb0346637f9836e155eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb03466\ + 37f9836e155eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb0346637f9836e15\ + 5eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb0346637f9836e155eaf891a93\ + 9c804509f59593fd47b1a97189127ba65a5649cfb0346637f9836e155eaf891a939ccd0780ff\ + ff8078916e776c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e\ + 776c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea\ + 05e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea05e958559f\ + 015c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea05e958559f015c082d9d\ + 06feafa3610fc44a5b2ef543cb81008078916e776c64ccea05e958559f015c082d9d06feafa3\ + 610fc44a5b2ef543cb818078916e776c64ccea05e958559f015c082d9d06feafa3610fc44a5b\ + 2ef543cb818078916e776c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef543cb81\ + 8078916e776c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e77\ + 6c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea05\ + e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea05e958559f01\ + 5c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea05e958559f015c082d9d06\ + feafa3610fc44a5b2ef543cb818078916e776c64ccea05e958559f015c082d9d06feafa3610f\ + c44a5b2ef543cb818078916e776c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef5\ + 43cb811044010000\ +"; +fn proof() -> Vec { + array_bytes::hex2bytes_unchecked(PROOF) +} type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -fn assert_last_event(generic_event: ::Event) { +fn assert_last_event(generic_event: ::RuntimeEvent) { let events = System::::events(); - let system_event: ::Event = generic_event.into(); + let system_event: ::RuntimeEvent = generic_event.into(); let EventRecord { event, .. } = &events[events.len() - 1]; assert_eq!(event, &system_event); } @@ -103,13 +127,13 @@ pub fn run_to_block(n: T::BlockNumber) { benchmarks! { store { - let l in 1 .. MaxTransactionSize::::get(); + let l in 1 .. T::MaxTransactionSize::get(); let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); }: _(RawOrigin::Signed(caller.clone()), vec![0u8; l as usize]) verify { assert!(!BlockTransactions::::get().is_empty()); - assert_last_event::(Event::Stored(0).into()); + assert_last_event::(Event::Stored { index: 0 }.into()); } renew { @@ -117,32 +141,31 @@ benchmarks! { T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); TransactionStorage::::store( RawOrigin::Signed(caller.clone()).into(), - vec![0u8; MaxTransactionSize::::get() as usize], + vec![0u8; T::MaxTransactionSize::get() as usize], )?; run_to_block::(1u32.into()); }: _(RawOrigin::Signed(caller.clone()), T::BlockNumber::zero(), 0) verify { - assert_last_event::(Event::Renewed(0).into()); + assert_last_event::(Event::Renewed { index: 0 }.into()); } check_proof_max { run_to_block::(1u32.into()); let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - for _ in 0 .. MaxBlockTransactions::::get() { + for _ in 0 .. T::MaxBlockTransactions::get() { TransactionStorage::::store( RawOrigin::Signed(caller.clone()).into(), - vec![0u8; MaxTransactionSize::::get() as usize], + vec![0u8; T::MaxTransactionSize::get() as usize], )?; } run_to_block::(StoragePeriod::::get() + T::BlockNumber::one()); - let random_hash = [0u8]; - let mut encoded_proof = PROOF; - let proof = TransactionStorageProof::decode(&mut encoded_proof).unwrap(); + let encoded_proof = proof(); + let proof = TransactionStorageProof::decode(&mut &*encoded_proof).unwrap(); }: check_proof(RawOrigin::None, proof) verify { assert_last_event::(Event::ProofChecked.into()); } -} -impl_benchmark_test_suite!(TransactionStorage, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!(TransactionStorage, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/transaction-storage/src/lib.rs b/frame/transaction-storage/src/lib.rs index 2fe3c04e0229f..cda7610efdf87 100644 --- a/frame/transaction-storage/src/lib.rs +++ b/frame/transaction-storage/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ mod mock; #[cfg(test)] mod tests; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo}, traits::{Currency, OnUnbalanced, ReservableCurrency}, @@ -37,7 +37,7 @@ use sp_runtime::traits::{BlakeTwo256, Hash, One, Saturating, Zero}; use sp_std::{prelude::*, result}; use sp_transaction_storage_proof::{ encode_index, random_chunk, InherentError, TransactionStorageProof, CHUNK_SIZE, - DEFAULT_STORAGE_PERIOD, INHERENT_IDENTIFIER, + INHERENT_IDENTIFIER, }; /// A type alias for the balance type from this pallet's point of view. @@ -57,7 +57,16 @@ pub const DEFAULT_MAX_TRANSACTION_SIZE: u32 = 8 * 1024 * 1024; pub const DEFAULT_MAX_BLOCK_TRANSACTIONS: u32 = 512; /// State data for a stored transaction. -#[derive(Encode, Decode, Clone, sp_runtime::RuntimeDebug, PartialEq, Eq, scale_info::TypeInfo)] +#[derive( + Encode, + Decode, + Clone, + sp_runtime::RuntimeDebug, + PartialEq, + Eq, + scale_info::TypeInfo, + MaxEncodedLen, +)] pub struct TransactionInfo { /// Chunk trie root. chunk_root: ::Output, @@ -83,10 +92,10 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// A dispatchable call. - type Call: Parameter - + Dispatchable + type RuntimeCall: Parameter + + Dispatchable + GetDispatchInfo + From>; /// The currency trait. @@ -95,6 +104,10 @@ pub mod pallet { type FeeDestination: OnUnbalanced>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + /// Maximum number of indexed transactions in the block. + type MaxBlockTransactions: Get; + /// Maximum data set in a single transaction in bytes. + type MaxTransactionSize: Get; } #[pallet::error] @@ -169,17 +182,18 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Index and store data on chain. Minimum data size is 1 bytes, maximum is + /// Index and store data off chain. Minimum data size is 1 bytes, maximum is /// `MaxTransactionSize`. Data will be removed after `STORAGE_PERIOD` blocks, unless `renew` /// is called. # /// - n*log(n) of data size, as all data is pushed to an in-memory trie. /// Additionally contains a DB write. /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::store(data.len() as u32))] pub fn store(origin: OriginFor, data: Vec) -> DispatchResult { ensure!(data.len() > 0, Error::::EmptyTransaction); ensure!( - data.len() <= MaxTransactionSize::::get() as usize, + data.len() <= T::MaxTransactionSize::get() as usize, Error::::TransactionTooLarge ); let sender = ensure_signed(origin)?; @@ -188,29 +202,31 @@ pub mod pallet { // Chunk data and compute storage root let chunk_count = num_chunks(data.len() as u32); let chunks = data.chunks(CHUNK_SIZE).map(|c| c.to_vec()).collect(); - let root = sp_io::trie::blake2_256_ordered_root(chunks); + let root = sp_io::trie::blake2_256_ordered_root(chunks, sp_runtime::StateVersion::V1); let content_hash = sp_io::hashing::blake2_256(&data); - let extrinsic_index = >::extrinsic_index() - .ok_or_else(|| Error::::BadContext)?; + let extrinsic_index = + >::extrinsic_index().ok_or(Error::::BadContext)?; sp_io::transaction_index::index(extrinsic_index, data.len() as u32, content_hash); let mut index = 0; >::mutate(|transactions| { - if transactions.len() + 1 > MaxBlockTransactions::::get() as usize { + if transactions.len() + 1 > T::MaxBlockTransactions::get() as usize { return Err(Error::::TooManyTransactions) } let total_chunks = transactions.last().map_or(0, |t| t.block_chunks) + chunk_count; index = transactions.len() as u32; - transactions.push(TransactionInfo { - chunk_root: root, - size: data.len() as u32, - content_hash: content_hash.into(), - block_chunks: total_chunks, - }); + transactions + .try_push(TransactionInfo { + chunk_root: root, + size: data.len() as u32, + content_hash: content_hash.into(), + block_chunks: total_chunks, + }) + .map_err(|_| Error::::TooManyTransactions)?; Ok(()) })?; - Self::deposit_event(Event::Stored(index)); + Self::deposit_event(Event::Stored { index }); Ok(()) } @@ -221,6 +237,7 @@ pub mod pallet { /// # /// - Constant. /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::renew())] pub fn renew( origin: OriginFor, @@ -230,28 +247,31 @@ pub mod pallet { let sender = ensure_signed(origin)?; let transactions = >::get(block).ok_or(Error::::RenewedNotFound)?; let info = transactions.get(index as usize).ok_or(Error::::RenewedNotFound)?; + let extrinsic_index = + >::extrinsic_index().ok_or(Error::::BadContext)?; + Self::apply_fee(sender, info.size)?; - let extrinsic_index = >::extrinsic_index().unwrap(); sp_io::transaction_index::renew(extrinsic_index, info.content_hash.into()); let mut index = 0; >::mutate(|transactions| { - if transactions.len() + 1 > MaxBlockTransactions::::get() as usize { + if transactions.len() + 1 > T::MaxBlockTransactions::get() as usize { return Err(Error::::TooManyTransactions) } let chunks = num_chunks(info.size); let total_chunks = transactions.last().map_or(0, |t| t.block_chunks) + chunks; index = transactions.len() as u32; - transactions.push(TransactionInfo { - chunk_root: info.chunk_root, - size: info.size, - content_hash: info.content_hash, - block_chunks: total_chunks, - }); - Ok(()) + transactions + .try_push(TransactionInfo { + chunk_root: info.chunk_root, + size: info.size, + content_hash: info.content_hash, + block_chunks: total_chunks, + }) + .map_err(|_| Error::::TooManyTransactions) })?; - Self::deposit_event(Event::Renewed(index)); + Self::deposit_event(Event::Renewed { index }); Ok(().into()) } @@ -263,6 +283,7 @@ pub mod pallet { /// There's a DB read for each transaction. /// Here we assume a maximum of 100 probed transactions. /// # + #[pallet::call_index(2)] #[pallet::weight((T::WeightInfo::check_proof_max(), DispatchClass::Mandatory))] pub fn check_proof( origin: OriginFor, @@ -286,13 +307,12 @@ pub mod pallet { Ok(index) => index, Err(index) => index, }; - let info = - infos.get(index).ok_or_else(|| Error::::MissingStateData)?.clone(); + let info = infos.get(index).ok_or(Error::::MissingStateData)?.clone(); let chunks = num_chunks(info.size); let prev_chunks = info.block_chunks - chunks; (info, selected_chunk_index - prev_chunks) }, - None => Err(Error::::MissingStateData)?, + None => return Err(Error::::MissingStateData.into()), }; ensure!( sp_io::trie::blake2_256_verify_proof( @@ -300,6 +320,7 @@ pub mod pallet { &proof.proof, &encode_index(chunk_index), &proof.chunk, + sp_runtime::StateVersion::V1, ), Error::::InvalidProof ); @@ -313,9 +334,9 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Stored data under specified index. - Stored(u32), + Stored { index: u32 }, /// Renewed data under specified index. - Renewed(u32), + Renewed { index: u32 }, /// Storage proof was successfully checked. ProofChecked, } @@ -323,8 +344,13 @@ pub mod pallet { /// Collection of transaction metadata by block number. #[pallet::storage] #[pallet::getter(fn transaction_roots)] - pub(super) type Transactions = - StorageMap<_, Blake2_128Concat, T::BlockNumber, Vec, OptionQuery>; + pub(super) type Transactions = StorageMap< + _, + Blake2_128Concat, + T::BlockNumber, + BoundedVec, + OptionQuery, + >; /// Count indexed chunks for each block. #[pallet::storage] @@ -341,16 +367,6 @@ pub mod pallet { /// Storage fee per transaction. pub(super) type EntryFee = StorageValue<_, BalanceOf>; - #[pallet::storage] - #[pallet::getter(fn max_transaction_size)] - /// Maximum data set in a single transaction in bytes. - pub(super) type MaxTransactionSize = StorageValue<_, u32, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn max_block_transactions)] - /// Maximum number of indexed transactions in the block. - pub(super) type MaxBlockTransactions = StorageValue<_, u32, ValueQuery>; - /// Storage period for data in blocks. Should match `sp_storage_proof::DEFAULT_STORAGE_PERIOD` /// for block authoring. #[pallet::storage] @@ -359,7 +375,7 @@ pub mod pallet { // Intermediates #[pallet::storage] pub(super) type BlockTransactions = - StorageValue<_, Vec, ValueQuery>; + StorageValue<_, BoundedVec, ValueQuery>; /// Was the proof checked in this block? #[pallet::storage] @@ -370,8 +386,6 @@ pub mod pallet { pub byte_fee: BalanceOf, pub entry_fee: BalanceOf, pub storage_period: T::BlockNumber, - pub max_block_transactions: u32, - pub max_transaction_size: u32, } #[cfg(feature = "std")] @@ -380,9 +394,7 @@ pub mod pallet { Self { byte_fee: 10u32.into(), entry_fee: 1000u32.into(), - storage_period: DEFAULT_STORAGE_PERIOD.into(), - max_block_transactions: DEFAULT_MAX_BLOCK_TRANSACTIONS, - max_transaction_size: DEFAULT_MAX_TRANSACTION_SIZE, + storage_period: sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(), } } } @@ -392,8 +404,6 @@ pub mod pallet { fn build(&self) { >::put(&self.byte_fee); >::put(&self.entry_fee); - >::put(&self.max_transaction_size); - >::put(&self.max_block_transactions); >::put(&self.storage_period); } } diff --git a/frame/transaction-storage/src/mock.rs b/frame/transaction-storage/src/mock.rs index 38d14129d76e2..8764b16c31d8d 100644 --- a/frame/transaction-storage/src/mock.rs +++ b/frame/transaction-storage/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,12 +17,11 @@ //! Test environment for transaction-storage pallet. -use crate as pallet_transaction_storage; -use crate::TransactionStorageProof; -use frame_support::{ - parameter_types, - traits::{OnFinalize, OnInitialize}, +use crate::{ + self as pallet_transaction_storage, TransactionStorageProof, DEFAULT_MAX_BLOCK_TRANSACTIONS, + DEFAULT_MAX_TRANSACTION_SIZE, }; +use frame_support::traits::{ConstU16, ConstU32, ConstU64, OnFinalize, OnInitialize}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -48,17 +47,12 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -66,8 +60,8 @@ impl frame_system::Config for Test { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; @@ -75,19 +69,16 @@ impl frame_system::Config for Test { type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; + type SS58Prefix = ConstU16<42>; type OnSetCode = (); -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 1; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { type Balance = u64; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); type MaxLocks = (); @@ -96,11 +87,13 @@ impl pallet_balances::Config for Test { } impl pallet_transaction_storage::Config for Test { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type Currency = Balances; type FeeDestination = (); type WeightInfo = (); + type MaxBlockTransactions = ConstU32<{ DEFAULT_MAX_BLOCK_TRANSACTIONS }>; + type MaxTransactionSize = ConstU32<{ DEFAULT_MAX_TRANSACTION_SIZE }>; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -113,8 +106,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { storage_period: 10, byte_fee: 2, entry_fee: 200, - max_block_transactions: crate::DEFAULT_MAX_BLOCK_TRANSACTIONS, - max_transaction_size: crate::DEFAULT_MAX_TRANSACTION_SIZE, }, } .build_storage() @@ -125,7 +116,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pub fn run_to_block(n: u64, f: impl Fn() -> Option) { while System::block_number() < n { if let Some(proof) = f() { - TransactionStorage::check_proof(Origin::none(), proof).unwrap(); + TransactionStorage::check_proof(RuntimeOrigin::none(), proof).unwrap(); } TransactionStorage::on_finalize(System::block_number()); System::on_finalize(System::block_number()); diff --git a/frame/transaction-storage/src/tests.rs b/frame/transaction-storage/src/tests.rs index c443f51ffb50f..01b71a7851ac3 100644 --- a/frame/transaction-storage/src/tests.rs +++ b/frame/transaction-storage/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,11 +31,11 @@ fn discards_data() { run_to_block(1, || None); let caller = 1; assert_ok!(TransactionStorage::::store( - RawOrigin::Signed(caller.clone()).into(), + RawOrigin::Signed(caller).into(), vec![0u8; 2000 as usize] )); assert_ok!(TransactionStorage::::store( - RawOrigin::Signed(caller.clone()).into(), + RawOrigin::Signed(caller).into(), vec![0u8; 2000 as usize] )); let proof_provider = || { @@ -74,7 +74,7 @@ fn burns_fee() { Error::::InsufficientFunds, ); assert_ok!(TransactionStorage::::store( - RawOrigin::Signed(caller.clone()).into(), + RawOrigin::Signed(caller).into(), vec![0u8; 2000 as usize] )); assert_eq!(Balances::free_balance(1), 1_000_000_000 - 2000 * 2 - 200); @@ -87,7 +87,7 @@ fn checks_proof() { run_to_block(1, || None); let caller = 1; assert_ok!(TransactionStorage::::store( - RawOrigin::Signed(caller.clone()).into(), + RawOrigin::Signed(caller).into(), vec![0u8; MAX_DATA_SIZE as usize] )); run_to_block(10, || None); @@ -95,7 +95,7 @@ fn checks_proof() { let proof = build_proof(parent_hash.as_ref(), vec![vec![0u8; MAX_DATA_SIZE as usize]]).unwrap(); assert_noop!( - TransactionStorage::::check_proof(Origin::none(), proof,), + TransactionStorage::::check_proof(RuntimeOrigin::none(), proof,), Error::::UnexpectedProof, ); run_to_block(11, || None); @@ -103,13 +103,13 @@ fn checks_proof() { let invalid_proof = build_proof(parent_hash.as_ref(), vec![vec![0u8; 1000]]).unwrap(); assert_noop!( - TransactionStorage::::check_proof(Origin::none(), invalid_proof,), + TransactionStorage::::check_proof(RuntimeOrigin::none(), invalid_proof,), Error::::InvalidProof, ); let proof = build_proof(parent_hash.as_ref(), vec![vec![0u8; MAX_DATA_SIZE as usize]]).unwrap(); - assert_ok!(TransactionStorage::::check_proof(Origin::none(), proof)); + assert_ok!(TransactionStorage::::check_proof(RuntimeOrigin::none(), proof)); }); } @@ -119,13 +119,13 @@ fn renews_data() { run_to_block(1, || None); let caller = 1; assert_ok!(TransactionStorage::::store( - RawOrigin::Signed(caller.clone()).into(), + RawOrigin::Signed(caller).into(), vec![0u8; 2000] )); let info = BlockTransactions::::get().last().unwrap().clone(); run_to_block(6, || None); assert_ok!(TransactionStorage::::renew( - RawOrigin::Signed(caller.clone()).into(), + RawOrigin::Signed(caller).into(), 1, // block 0, // transaction )); diff --git a/frame/transaction-storage/src/weights.rs b/frame/transaction-storage/src/weights.rs index 104b18d3f92ce..16d12aa75ab4d 100644 --- a/frame/transaction-storage/src/weights.rs +++ b/frame/transaction-storage/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_transaction_storage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/transaction-storage/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -53,29 +55,29 @@ pub trait WeightInfo { /// Weights for pallet_transaction_storage using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: TransactionStorage MaxTransactionSize (r:1 w:0) // Storage: TransactionStorage ByteFee (r:1 w:0) // Storage: TransactionStorage EntryFee (r:1 w:0) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: TransactionStorage BlockTransactions (r:1 w:1) - // Storage: TransactionStorage MaxBlockTransactions (r:1 w:0) + /// The range of component `l` is `[1, 8388608]`. fn store(l: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 0 - .saturating_add((8_000 as Weight).saturating_mul(l as Weight)) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 46_730 nanoseconds. + Weight::from_ref_time(46_922_000 as u64) + // Standard Error: 2 + .saturating_add(Weight::from_ref_time(5_601 as u64).saturating_mul(l as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: TransactionStorage Transactions (r:1 w:0) + // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: TransactionStorage ByteFee (r:1 w:0) // Storage: TransactionStorage EntryFee (r:1 w:0) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: TransactionStorage BlockTransactions (r:1 w:1) - // Storage: TransactionStorage MaxBlockTransactions (r:1 w:0) fn renew() -> Weight { - (67_532_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 56_802 nanoseconds. + Weight::from_ref_time(58_670_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: TransactionStorage ProofChecked (r:1 w:1) // Storage: TransactionStorage StoragePeriod (r:1 w:0) @@ -83,37 +85,38 @@ impl WeightInfo for SubstrateWeight { // Storage: System ParentHash (r:1 w:0) // Storage: TransactionStorage Transactions (r:1 w:0) fn check_proof_max() -> Weight { - (182_886_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 74_016 nanoseconds. + Weight::from_ref_time(94_111_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: TransactionStorage MaxTransactionSize (r:1 w:0) // Storage: TransactionStorage ByteFee (r:1 w:0) // Storage: TransactionStorage EntryFee (r:1 w:0) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: TransactionStorage BlockTransactions (r:1 w:1) - // Storage: TransactionStorage MaxBlockTransactions (r:1 w:0) + /// The range of component `l` is `[1, 8388608]`. fn store(l: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 0 - .saturating_add((8_000 as Weight).saturating_mul(l as Weight)) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 46_730 nanoseconds. + Weight::from_ref_time(46_922_000 as u64) + // Standard Error: 2 + .saturating_add(Weight::from_ref_time(5_601 as u64).saturating_mul(l as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: TransactionStorage Transactions (r:1 w:0) + // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: TransactionStorage ByteFee (r:1 w:0) // Storage: TransactionStorage EntryFee (r:1 w:0) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: TransactionStorage BlockTransactions (r:1 w:1) - // Storage: TransactionStorage MaxBlockTransactions (r:1 w:0) fn renew() -> Weight { - (67_532_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 56_802 nanoseconds. + Weight::from_ref_time(58_670_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: TransactionStorage ProofChecked (r:1 w:1) // Storage: TransactionStorage StoragePeriod (r:1 w:0) @@ -121,8 +124,9 @@ impl WeightInfo for () { // Storage: System ParentHash (r:1 w:0) // Storage: TransactionStorage Transactions (r:1 w:0) fn check_proof_max() -> Weight { - (182_886_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 74_016 nanoseconds. + Weight::from_ref_time(94_111_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } } diff --git a/frame/treasury/Cargo.toml b/frame/treasury/Cargo.toml index b2991f3febcad..993f89ff0faa5 100644 --- a/frame/treasury/Cargo.toml +++ b/frame/treasury/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-treasury" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet to manage treasury" readme = "README.md" @@ -13,41 +13,39 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", "max-encoded-len", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.126", features = ["derive"], optional = true } -impl-trait-for-tuples = "0.2.1" - -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +impl-trait-for-tuples = "0.2.2" +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } - +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "codec/std", - "scale-info/std", - "serde", - "sp-std/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", "pallet-balances/std", + "scale-info/std", + "serde", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/treasury/src/benchmarking.rs b/frame/treasury/src/benchmarking.rs index 2fe0bad704f2b..d718a5fb89521 100644 --- a/frame/treasury/src/benchmarking.rs +++ b/frame/treasury/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,12 @@ use super::{Pallet as Treasury, *}; -use frame_benchmarking::{account, benchmarks_instance_pallet, impl_benchmark_test_suite}; -use frame_support::{ensure, traits::OnInitialize}; +use frame_benchmarking::{account, benchmarks_instance_pallet}; +use frame_support::{ + dispatch::UnfilteredDispatchable, + ensure, + traits::{EnsureOrigin, OnInitialize}, +}; use frame_system::RawOrigin; const SEED: u32 = 0; @@ -30,7 +34,7 @@ const SEED: u32 = 0; // Create the pre-requisite information needed to create a treasury `propose_spend`. fn setup_proposal, I: 'static>( u: u32, -) -> (T::AccountId, BalanceOf, ::Source) { +) -> (T::AccountId, BalanceOf, AccountIdLookupOf) { let caller = account("caller", u, SEED); let value: BalanceOf = T::ProposalBondMinimum::get().saturating_mul(100u32.into()); let _ = T::Currency::make_free_balance_be(&caller, value); @@ -57,7 +61,29 @@ fn setup_pot_account, I: 'static>() { let _ = T::Currency::make_free_balance_be(&pot_account, value); } +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + benchmarks_instance_pallet! { + // This benchmark is short-circuited if `SpendOrigin` cannot provide + // a successful origin, in which case `spend` is un-callable and can use weight=0. + spend { + let (_, value, beneficiary_lookup) = setup_proposal::(SEED); + let origin = T::SpendOrigin::try_successful_origin(); + let beneficiary = T::Lookup::lookup(beneficiary_lookup.clone()).unwrap(); + let call = Call::::spend { amount: value, beneficiary: beneficiary_lookup }; + }: { + if let Ok(origin) = origin.clone() { + call.dispatch_bypass_filter(origin)?; + } + } + verify { + if origin.is_ok() { + assert_last_event::(Event::SpendApproved { proposal_index: 0, amount: value, beneficiary }.into()) + } + } + propose_spend { let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); // Whitelist caller account from further DB operations. @@ -73,7 +99,8 @@ benchmarks_instance_pallet! { beneficiary_lookup )?; let proposal_id = Treasury::::proposal_count() - 1; - }: _(RawOrigin::Root, proposal_id) + let reject_origin = T::RejectOrigin::successful_origin(); + }: _(reject_origin, proposal_id) approve_proposal { let p in 0 .. T::MaxApprovals::get() - 1; @@ -85,7 +112,20 @@ benchmarks_instance_pallet! { beneficiary_lookup )?; let proposal_id = Treasury::::proposal_count() - 1; - }: _(RawOrigin::Root, proposal_id) + let approve_origin = T::ApproveOrigin::successful_origin(); + }: _(approve_origin, proposal_id) + + remove_approval { + let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); + Treasury::::propose_spend( + RawOrigin::Signed(caller).into(), + value, + beneficiary_lookup + )?; + let proposal_id = Treasury::::proposal_count() - 1; + Treasury::::approve_proposal(RawOrigin::Root.into(), proposal_id)?; + let reject_origin = T::RejectOrigin::successful_origin(); + }: _(reject_origin, proposal_id) on_initialize_proposals { let p in 0 .. T::MaxApprovals::get(); @@ -94,6 +134,6 @@ benchmarks_instance_pallet! { }: { Treasury::::on_initialize(T::BlockNumber::zero()); } -} -impl_benchmark_test_suite!(Treasury, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(Treasury, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 646baa99b99b0..0ffc53d8b7978 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,6 +50,7 @@ //! - `propose_spend` - Make a spending proposal and stake the required deposit. //! - `reject_proposal` - Reject a proposal, slashing the deposit. //! - `approve_proposal` - Accept the proposal, returning the deposit. +//! - `remove_approval` - Remove an approval, the deposit will no longer be returned. //! //! ## GenesisConfig //! @@ -92,6 +93,7 @@ pub type PositiveImbalanceOf = <>::Currency as Currenc pub type NegativeImbalanceOf = <>::Currency as Currency< ::AccountId, >>::NegativeImbalance; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; /// A trait to allow the Treasury Pallet to spend it's funds for other purposes. /// There is an expectation that the implementer of this trait will correctly manage @@ -139,7 +141,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::generate_storage_info] pub struct Pallet(PhantomData<(T, I)>); #[pallet::config] @@ -148,13 +149,14 @@ pub mod pallet { type Currency: Currency + ReservableCurrency; /// Origin from which approvals must come. - type ApproveOrigin: EnsureOrigin; + type ApproveOrigin: EnsureOrigin; /// Origin from which rejections must come. - type RejectOrigin: EnsureOrigin; + type RejectOrigin: EnsureOrigin; /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; /// Handler for the unbalanced decrease when slashing for a rejected proposal or bounty. type OnSlash: OnUnbalanced>; @@ -168,6 +170,10 @@ pub mod pallet { #[pallet::constant] type ProposalBondMinimum: Get>; + /// Maximum amount of funds that should be placed in a deposit for making a proposal. + #[pallet::constant] + type ProposalBondMaximum: Get>>; + /// Period between successive spends. #[pallet::constant] type SpendPeriod: Get; @@ -190,8 +196,15 @@ pub mod pallet { type SpendFunds: SpendFunds; /// The maximum number of approvals that can wait in the spending queue. + /// + /// NOTE: This parameter is also used within the Bounties Pallet extension if enabled. #[pallet::constant] type MaxApprovals: Get; + + /// The origin required for approving spends from the treasury outside of the proposal + /// process. The `Success` value is the maximum amount that this origin is allowed to + /// spend at a time. + type SpendOrigin: EnsureOrigin>; } /// Number of proposals that have been made. @@ -210,6 +223,10 @@ pub mod pallet { OptionQuery, >; + /// The amount which has been reported as inactive to Currency. + #[pallet::storage] + pub type Inactive, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; + /// Proposal indices that have been approved but not yet awarded. #[pallet::storage] #[pallet::getter(fn approvals)] @@ -255,27 +272,28 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { - /// New proposal. \[proposal_index\] - Proposed(ProposalIndex), - /// We have ended a spend period and will now allocate funds. \[budget_remaining\] - Spending(BalanceOf), - /// Some funds have been allocated. \[proposal_index, award, beneficiary\] - Awarded(ProposalIndex, BalanceOf, T::AccountId), - /// A proposal was rejected; funds were slashed. \[proposal_index, slashed\] - Rejected(ProposalIndex, BalanceOf), - /// Some of our funds have been burnt. \[burn\] - Burnt(BalanceOf), + /// New proposal. + Proposed { proposal_index: ProposalIndex }, + /// We have ended a spend period and will now allocate funds. + Spending { budget_remaining: BalanceOf }, + /// Some funds have been allocated. + Awarded { proposal_index: ProposalIndex, award: BalanceOf, account: T::AccountId }, + /// A proposal was rejected; funds were slashed. + Rejected { proposal_index: ProposalIndex, slashed: BalanceOf }, + /// Some of our funds have been burnt. + Burnt { burnt_funds: BalanceOf }, /// Spending has finished; this is the amount that rolls over until next spend. - /// \[budget_remaining\] - Rollover(BalanceOf), - /// Some funds have been deposited. \[deposit\] - Deposit(BalanceOf), + Rollover { rollover_balance: BalanceOf }, + /// Some funds have been deposited. + Deposit { value: BalanceOf }, + /// A new spend proposal has been approved. + SpendApproved { + proposal_index: ProposalIndex, + amount: BalanceOf, + beneficiary: T::AccountId, + }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - /// Error for the treasury pallet. #[pallet::error] pub enum Error { @@ -285,6 +303,11 @@ pub mod pallet { InvalidIndex, /// Too many approvals in the queue. TooManyApprovals, + /// The spend origin is valid but the amount it is allowed to spend is lower than the + /// amount to be spent. + InsufficientPermission, + /// Proposal has not been approved. + ProposalNotApproved, } #[pallet::hooks] @@ -297,11 +320,21 @@ pub mod pallet { /// - The weight is overestimated if some approvals got missed. /// # fn on_initialize(n: T::BlockNumber) -> Weight { + let pot = Self::pot(); + let deactivated = Inactive::::get(); + if pot != deactivated { + match (pot > deactivated, pot.max(deactivated) - pot.min(deactivated)) { + (true, delta) => T::Currency::deactivate(delta), + (false, delta) => T::Currency::reactivate(delta), + } + Inactive::::put(&pot); + } + // Check to see if we should spend some funds! if (n % T::SpendPeriod::get()).is_zero() { Self::spend_funds() } else { - 0 + Weight::zero() } } } @@ -317,11 +350,12 @@ pub mod pallet { /// - DbReads: `ProposalCount`, `origin account` /// - DbWrites: `ProposalCount`, `Proposals`, `origin account` /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::propose_spend())] pub fn propose_spend( origin: OriginFor, #[pallet::compact] value: BalanceOf, - beneficiary: ::Source, + beneficiary: AccountIdLookupOf, ) -> DispatchResult { let proposer = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; @@ -334,7 +368,7 @@ pub mod pallet { >::put(c + 1); >::insert(c, Proposal { proposer, value, beneficiary, bond }); - Self::deposit_event(Event::Proposed(c)); + Self::deposit_event(Event::Proposed { proposal_index: c }); Ok(()) } @@ -347,6 +381,7 @@ pub mod pallet { /// - DbReads: `Proposals`, `rejected proposer account` /// - DbWrites: `Proposals`, `rejected proposer account` /// # + #[pallet::call_index(1)] #[pallet::weight((T::WeightInfo::reject_proposal(), DispatchClass::Operational))] pub fn reject_proposal( origin: OriginFor, @@ -360,7 +395,10 @@ pub mod pallet { let imbalance = T::Currency::slash_reserved(&proposal.proposer, value).0; T::OnSlash::on_unbalanced(imbalance); - Self::deposit_event(Event::::Rejected(proposal_id, value)); + Self::deposit_event(Event::::Rejected { + proposal_index: proposal_id, + slashed: value, + }); Ok(()) } @@ -374,6 +412,7 @@ pub mod pallet { /// - DbReads: `Proposals`, `Approvals` /// - DbWrite: `Approvals` /// # + #[pallet::call_index(2)] #[pallet::weight((T::WeightInfo::approve_proposal(T::MaxApprovals::get()), DispatchClass::Operational))] pub fn approve_proposal( origin: OriginFor, @@ -386,6 +425,76 @@ pub mod pallet { .map_err(|_| Error::::TooManyApprovals)?; Ok(()) } + + /// Propose and approve a spend of treasury funds. + /// + /// - `origin`: Must be `SpendOrigin` with the `Success` value being at least `amount`. + /// - `amount`: The amount to be transferred from the treasury to the `beneficiary`. + /// - `beneficiary`: The destination account for the transfer. + /// + /// NOTE: For record-keeping purposes, the proposer is deemed to be equivalent to the + /// beneficiary. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::spend())] + pub fn spend( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + beneficiary: AccountIdLookupOf, + ) -> DispatchResult { + let max_amount = T::SpendOrigin::ensure_origin(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; + + ensure!(amount <= max_amount, Error::::InsufficientPermission); + let proposal_index = Self::proposal_count(); + Approvals::::try_append(proposal_index) + .map_err(|_| Error::::TooManyApprovals)?; + let proposal = Proposal { + proposer: beneficiary.clone(), + value: amount, + beneficiary: beneficiary.clone(), + bond: Default::default(), + }; + Proposals::::insert(proposal_index, proposal); + ProposalCount::::put(proposal_index + 1); + + Self::deposit_event(Event::SpendApproved { proposal_index, amount, beneficiary }); + Ok(()) + } + + /// Force a previously approved proposal to be removed from the approval queue. + /// The original deposit will no longer be returned. + /// + /// May only be called from `T::RejectOrigin`. + /// - `proposal_id`: The index of a proposal + /// + /// # + /// - Complexity: O(A) where `A` is the number of approvals + /// - Db reads and writes: `Approvals` + /// # + /// + /// Errors: + /// - `ProposalNotApproved`: The `proposal_id` supplied was not found in the approval queue, + /// i.e., the proposal has not been approved. This could also mean the proposal does not + /// exist altogether, thus there is no way it would have been approved in the first place. + #[pallet::call_index(4)] + #[pallet::weight((T::WeightInfo::remove_approval(), DispatchClass::Operational))] + pub fn remove_approval( + origin: OriginFor, + #[pallet::compact] proposal_id: ProposalIndex, + ) -> DispatchResult { + T::RejectOrigin::ensure_origin(origin)?; + + Approvals::::try_mutate(|v| -> DispatchResult { + if let Some(index) = v.iter().position(|x| x == &proposal_id) { + v.remove(index); + Ok(()) + } else { + Err(Error::::ProposalNotApproved.into()) + } + })?; + + Ok(()) + } } } @@ -397,20 +506,24 @@ impl, I: 'static> Pallet { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() + T::PalletId::get().into_account_truncating() } /// The needed bond for a proposal whose spend is `value`. fn calculate_bond(value: BalanceOf) -> BalanceOf { - T::ProposalBondMinimum::get().max(T::ProposalBond::get() * value) + let mut r = T::ProposalBondMinimum::get().max(T::ProposalBond::get() * value); + if let Some(m) = T::ProposalBondMaximum::get() { + r = r.min(m); + } + r } /// Spend some money! returns number of approvals before spend. pub fn spend_funds() -> Weight { - let mut total_weight: Weight = Zero::zero(); + let mut total_weight = Weight::zero(); let mut budget_remaining = Self::pot(); - Self::deposit_event(Event::Spending(budget_remaining)); + Self::deposit_event(Event::Spending { budget_remaining }); let account_id = Self::account_id(); let mut missed_any = false; @@ -431,7 +544,11 @@ impl, I: 'static> Pallet { // provide the allocation. imbalance.subsume(T::Currency::deposit_creating(&p.beneficiary, p.value)); - Self::deposit_event(Event::Awarded(index, p.value, p.beneficiary)); + Self::deposit_event(Event::Awarded { + proposal_index: index, + award: p.value, + account: p.beneficiary, + }); false } else { missed_any = true; @@ -462,7 +579,7 @@ impl, I: 'static> Pallet { let (debit, credit) = T::Currency::pair(burn); imbalance.subsume(debit); T::BurnDestination::on_unbalanced(credit); - Self::deposit_event(Event::Burnt(burn)) + Self::deposit_event(Event::Burnt { burnt_funds: burn }) } // Must never be an error, but better to be safe. @@ -477,7 +594,7 @@ impl, I: 'static> Pallet { drop(problem); } - Self::deposit_event(Event::Rollover(budget_remaining)); + Self::deposit_event(Event::Rollover { rollover_balance: budget_remaining }); total_weight } @@ -498,6 +615,6 @@ impl, I: 'static> OnUnbalanced> for Palle // Must resolve into existing but better to be safe. let _ = T::Currency::resolve_creating(&Self::account_id(), amount); - Self::deposit_event(Event::Deposit(numeric_amount)); + Self::deposit_event(Event::Deposit { value: numeric_amount }); } } diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs index 534661b2773bb..9cfe147ec4ce4 100644 --- a/frame/treasury/src/tests.rs +++ b/frame/treasury/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,16 +19,17 @@ #![cfg(test)] -use std::cell::RefCell; - use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + traits::{BadOrigin, BlakeTwo256, IdentityLookup}, }; use frame_support::{ - assert_noop, assert_ok, pallet_prelude::GenesisBuild, parameter_types, traits::OnInitialize, + assert_noop, assert_ok, + pallet_prelude::GenesisBuild, + parameter_types, + traits::{ConstU32, ConstU64, OnInitialize}, PalletId, }; @@ -51,26 +52,25 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -79,50 +79,60 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); -} -parameter_types! { - pub const ExistentialDeposit: u64 = 1; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } -thread_local! { - static TEN_TO_FOURTEEN: RefCell> = RefCell::new(vec![10,11,12,13,14]); -} parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); - pub const ProposalBondMinimum: u64 = 1; - pub const SpendPeriod: u64 = 2; pub const Burn: Permill = Permill::from_percent(50); pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); - pub const BountyUpdatePeriod: u32 = 20; - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); - pub const BountyValueMinimum: u64 = 1; - pub const MaxApprovals: u32 = 100; } +pub struct TestSpendOrigin; +impl frame_support::traits::EnsureOrigin for TestSpendOrigin { + type Success = u64; + fn try_origin(o: RuntimeOrigin) -> Result { + Result::, RuntimeOrigin>::from(o).and_then(|o| match o { + frame_system::RawOrigin::Root => Ok(u64::max_value()), + frame_system::RawOrigin::Signed(10) => Ok(5), + frame_system::RawOrigin::Signed(11) => Ok(10), + frame_system::RawOrigin::Signed(12) => Ok(20), + frame_system::RawOrigin::Signed(13) => Ok(50), + r => Err(RuntimeOrigin::from(r)), + }) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(RuntimeOrigin::root()) + } +} + impl Config for Test { type PalletId = TreasuryPalletId; type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; - type Event = Event; + type RuntimeEvent = RuntimeEvent; type OnSlash = (); type ProposalBond = ProposalBond; - type ProposalBondMinimum = ProposalBondMinimum; - type SpendPeriod = SpendPeriod; + type ProposalBondMinimum = ConstU64<1>; + type ProposalBondMaximum = (); + type SpendPeriod = ConstU64<2>; type Burn = Burn; type BurnDestination = (); // Just gets burned. type WeightInfo = (); type SpendFunds = (); - type MaxApprovals = MaxApprovals; + type MaxApprovals = ConstU32<100>; + type SpendOrigin = TestSpendOrigin; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -145,6 +155,51 @@ fn genesis_config_works() { }); } +#[test] +fn spend_origin_permissioning_works() { + new_test_ext().execute_with(|| { + assert_noop!(Treasury::spend(RuntimeOrigin::signed(1), 1, 1), BadOrigin); + assert_noop!( + Treasury::spend(RuntimeOrigin::signed(10), 6, 1), + Error::::InsufficientPermission + ); + assert_noop!( + Treasury::spend(RuntimeOrigin::signed(11), 11, 1), + Error::::InsufficientPermission + ); + assert_noop!( + Treasury::spend(RuntimeOrigin::signed(12), 21, 1), + Error::::InsufficientPermission + ); + assert_noop!( + Treasury::spend(RuntimeOrigin::signed(13), 51, 1), + Error::::InsufficientPermission + ); + }); +} + +#[test] +fn spend_origin_works() { + new_test_ext().execute_with(|| { + // Check that accumulate works when we have Some value in Dummy already. + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(11), 10, 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(12), 20, 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(13), 50, 6)); + + >::on_initialize(1); + assert_eq!(Balances::free_balance(6), 0); + + >::on_initialize(2); + assert_eq!(Balances::free_balance(6), 100); + assert_eq!(Treasury::pot(), 0); + }); +} + #[test] fn minting_works() { new_test_ext().execute_with(|| { @@ -157,7 +212,7 @@ fn minting_works() { #[test] fn spend_proposal_takes_min_deposit() { new_test_ext().execute_with(|| { - assert_ok!(Treasury::propose_spend(Origin::signed(0), 1, 3)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 1, 3)); assert_eq!(Balances::free_balance(0), 99); assert_eq!(Balances::reserved_balance(0), 1); }); @@ -166,7 +221,7 @@ fn spend_proposal_takes_min_deposit() { #[test] fn spend_proposal_takes_proportional_deposit() { new_test_ext().execute_with(|| { - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); assert_eq!(Balances::free_balance(0), 95); assert_eq!(Balances::reserved_balance(0), 5); }); @@ -176,7 +231,7 @@ fn spend_proposal_takes_proportional_deposit() { fn spend_proposal_fails_when_proposer_poor() { new_test_ext().execute_with(|| { assert_noop!( - Treasury::propose_spend(Origin::signed(2), 100, 3), + Treasury::propose_spend(RuntimeOrigin::signed(2), 100, 3), Error::::InsufficientProposersBalance, ); }); @@ -187,8 +242,8 @@ fn accepted_spend_proposal_ignored_outside_spend_period() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); >::on_initialize(1); assert_eq!(Balances::free_balance(3), 0); @@ -214,8 +269,8 @@ fn rejected_spend_proposal_ignored_on_spend_period() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::reject_proposal(Origin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::reject_proposal(RuntimeOrigin::root(), 0)); >::on_initialize(2); assert_eq!(Balances::free_balance(3), 0); @@ -228,23 +283,32 @@ fn reject_already_rejected_spend_proposal_fails() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::reject_proposal(Origin::root(), 0)); - assert_noop!(Treasury::reject_proposal(Origin::root(), 0), Error::::InvalidIndex); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::reject_proposal(RuntimeOrigin::root(), 0)); + assert_noop!( + Treasury::reject_proposal(RuntimeOrigin::root(), 0), + Error::::InvalidIndex + ); }); } #[test] fn reject_non_existent_spend_proposal_fails() { new_test_ext().execute_with(|| { - assert_noop!(Treasury::reject_proposal(Origin::root(), 0), Error::::InvalidIndex); + assert_noop!( + Treasury::reject_proposal(RuntimeOrigin::root(), 0), + Error::::InvalidIndex + ); }); } #[test] fn accept_non_existent_spend_proposal_fails() { new_test_ext().execute_with(|| { - assert_noop!(Treasury::approve_proposal(Origin::root(), 0), Error::::InvalidIndex); + assert_noop!( + Treasury::approve_proposal(RuntimeOrigin::root(), 0), + Error::::InvalidIndex + ); }); } @@ -253,9 +317,12 @@ fn accept_already_rejected_spend_proposal_fails() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::reject_proposal(Origin::root(), 0)); - assert_noop!(Treasury::approve_proposal(Origin::root(), 0), Error::::InvalidIndex); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::reject_proposal(RuntimeOrigin::root(), 0)); + assert_noop!( + Treasury::approve_proposal(RuntimeOrigin::root(), 0), + Error::::InvalidIndex + ); }); } @@ -265,8 +332,8 @@ fn accepted_spend_proposal_enacted_on_spend_period() { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); >::on_initialize(2); assert_eq!(Balances::free_balance(3), 100); @@ -280,8 +347,8 @@ fn pot_underflow_should_not_diminish() { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 150, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 150, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); >::on_initialize(2); assert_eq!(Treasury::pot(), 100); // Pot hasn't changed @@ -302,14 +369,14 @@ fn treasury_account_doesnt_get_deleted() { assert_eq!(Treasury::pot(), 100); let treasury_balance = Balances::free_balance(&Treasury::account_id()); - assert_ok!(Treasury::propose_spend(Origin::signed(0), treasury_balance, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), treasury_balance, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); >::on_initialize(2); assert_eq!(Treasury::pot(), 100); // Pot hasn't changed - assert_ok!(Treasury::propose_spend(Origin::signed(0), Treasury::pot(), 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 1)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), Treasury::pot(), 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 1)); >::on_initialize(4); assert_eq!(Treasury::pot(), 0); // Pot is emptied @@ -332,10 +399,10 @@ fn inexistent_account_works() { assert_eq!(Balances::free_balance(Treasury::account_id()), 0); // Account does not exist assert_eq!(Treasury::pot(), 0); // Pot is empty - assert_ok!(Treasury::propose_spend(Origin::signed(0), 99, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); - assert_ok!(Treasury::propose_spend(Origin::signed(0), 1, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 1)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 99, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 1, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 1)); >::on_initialize(2); assert_eq!(Treasury::pot(), 0); // Pot hasn't changed assert_eq!(Balances::free_balance(3), 0); // Balance of `3` hasn't changed @@ -376,16 +443,34 @@ fn max_approvals_limited() { Balances::make_free_balance_be(&Treasury::account_id(), u64::MAX); Balances::make_free_balance_be(&0, u64::MAX); - for _ in 0..MaxApprovals::get() { - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); + for _ in 0..::MaxApprovals::get() { + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); } // One too many will fail - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); assert_noop!( - Treasury::approve_proposal(Origin::root(), 0), + Treasury::approve_proposal(RuntimeOrigin::root(), 0), Error::::TooManyApprovals ); }); } + +#[test] +fn remove_already_removed_approval_fails() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + + assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); + assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); + assert_eq!(Treasury::approvals(), vec![0]); + assert_ok!(Treasury::remove_approval(RuntimeOrigin::root(), 0)); + assert_eq!(Treasury::approvals(), vec![]); + + assert_noop!( + Treasury::remove_approval(RuntimeOrigin::root(), 0), + Error::::ProposalNotApproved + ); + }); +} diff --git a/frame/treasury/src/weights.rs b/frame/treasury/src/weights.rs index 126c8a1766268..3ee071ac700f1 100644 --- a/frame/treasury/src/weights.rs +++ b/frame/treasury/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_treasury //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/treasury/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -45,89 +47,125 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_treasury. pub trait WeightInfo { + fn spend() -> Weight; fn propose_spend() -> Weight; fn reject_proposal() -> Weight; fn approve_proposal(p: u32, ) -> Weight; + fn remove_approval() -> Weight; fn on_initialize_proposals(p: u32, ) -> Weight; } /// Weights for pallet_treasury using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + fn spend() -> Weight { + // Minimum execution time: 137 nanoseconds. + Weight::from_ref_time(153_000 as u64) + } // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { - (41_567_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 31_437 nanoseconds. + Weight::from_ref_time(32_241_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Treasury Proposals (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_proposal() -> Weight { - (38_993_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 38_351 nanoseconds. + Weight::from_ref_time(38_828_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Treasury Proposals (r:1 w:0) // Storage: Treasury Approvals (r:1 w:1) + /// The range of component `p` is `[0, 99]`. fn approve_proposal(p: u32, ) -> Weight { - (13_543_000 as Weight) - // Standard Error: 1_000 - .saturating_add((55_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 11_937 nanoseconds. + Weight::from_ref_time(15_541_763 as u64) + // Standard Error: 1_036 + .saturating_add(Weight::from_ref_time(128_326 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Treasury Approvals (r:1 w:1) + fn remove_approval() -> Weight { + // Minimum execution time: 9_611 nanoseconds. + Weight::from_ref_time(10_012_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Treasury Approvals (r:1 w:1) - // Storage: Treasury BountyApprovals (r:1 w:1) + // Storage: Bounties BountyApprovals (r:1 w:1) // Storage: Treasury Proposals (r:2 w:2) // Storage: System Account (r:4 w:4) + /// The range of component `p` is `[0, 100]`. fn on_initialize_proposals(p: u32, ) -> Weight { - (51_708_000 as Weight) - // Standard Error: 21_000 - .saturating_add((57_926_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(p as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(p as Weight))) + // Minimum execution time: 43_016 nanoseconds. + Weight::from_ref_time(56_538_751 as u64) + // Standard Error: 14_890 + .saturating_add(Weight::from_ref_time(26_789_120 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(p as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(p as u64))) } } // For backwards compatibility and tests impl WeightInfo for () { + fn spend() -> Weight { + // Minimum execution time: 137 nanoseconds. + Weight::from_ref_time(153_000 as u64) + } // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { - (41_567_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 31_437 nanoseconds. + Weight::from_ref_time(32_241_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Treasury Proposals (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_proposal() -> Weight { - (38_993_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 38_351 nanoseconds. + Weight::from_ref_time(38_828_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Treasury Proposals (r:1 w:0) // Storage: Treasury Approvals (r:1 w:1) + /// The range of component `p` is `[0, 99]`. fn approve_proposal(p: u32, ) -> Weight { - (13_543_000 as Weight) - // Standard Error: 1_000 - .saturating_add((55_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 11_937 nanoseconds. + Weight::from_ref_time(15_541_763 as u64) + // Standard Error: 1_036 + .saturating_add(Weight::from_ref_time(128_326 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Treasury Approvals (r:1 w:1) + fn remove_approval() -> Weight { + // Minimum execution time: 9_611 nanoseconds. + Weight::from_ref_time(10_012_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Treasury Approvals (r:1 w:1) - // Storage: Treasury BountyApprovals (r:1 w:1) + // Storage: Bounties BountyApprovals (r:1 w:1) // Storage: Treasury Proposals (r:2 w:2) // Storage: System Account (r:4 w:4) + /// The range of component `p` is `[0, 100]`. fn on_initialize_proposals(p: u32, ) -> Weight { - (51_708_000 as Weight) - // Standard Error: 21_000 - .saturating_add((57_926_000 as Weight).saturating_mul(p as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(p as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(p as Weight))) + // Minimum execution time: 43_016 nanoseconds. + Weight::from_ref_time(56_538_751 as u64) + // Standard Error: 14_890 + .saturating_add(Weight::from_ref_time(26_789_120 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(p as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(p as u64))) } } diff --git a/frame/try-runtime/Cargo.toml b/frame/try-runtime/Cargo.toml index 0ff534767607d..87aca0d1ed9f0 100644 --- a/frame/try-runtime/Cargo.toml +++ b/frame/try-runtime/Cargo.toml @@ -2,28 +2,31 @@ name = "frame-try-runtime" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for democracy" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-api = { version = "4.0.0-dev", path = "../../primitives/api", default-features = false } -sp-std = { version = "4.0.0-dev", path = "../../primitives/std" , default-features = false } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" , default-features = false } - -frame-support = { version = "4.0.0-dev", path = "../support", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"]} +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [features] default = [ "std" ] std = [ + "codec/std", + "frame-support/std", "sp-api/std", - "sp-std/std", "sp-runtime/std", - "frame-support/std", + "sp-std/std", +] +try-runtime = [ + "frame-support/try-runtime", ] diff --git a/frame/try-runtime/src/lib.rs b/frame/try-runtime/src/lib.rs index b2dfdfac6429e..ed1247bd8e6f2 100644 --- a/frame/try-runtime/src/lib.rs +++ b/frame/try-runtime/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,10 @@ //! 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; use frame_support::weights::Weight; -use sp_std::prelude::*; sp_api::decl_runtime_apis! { /// Runtime api for testing the execution of a runtime upgrade. @@ -32,6 +33,12 @@ sp_api::decl_runtime_apis! { /// /// Returns the consumed weight of the migration in case of a successful one, combined with /// the total allowed block weight of the runtime. - fn on_runtime_upgrade() -> Result<(Weight, Weight), sp_runtime::RuntimeString>; + fn on_runtime_upgrade() -> (Weight, Weight); + + /// Execute the given block, but don't check that its state root matches that of yours. + /// + /// This is only sensible where the incoming block is from a different network, yet it has + /// the same block format as the runtime implementing this API. + fn execute_block(block: Block, state_root_check: bool, try_state: TryStateSelect) -> Weight; } } diff --git a/frame/uniques/Cargo.toml b/frame/uniques/Cargo.toml index 4f664ecc2b6a9..6e36240748c4b 100644 --- a/frame/uniques/Cargo.toml +++ b/frame/uniques/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-uniques" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME NFT asset management pallet" readme = "README.md" @@ -13,34 +13,36 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-std = { version = "4.0.0-dev", path = "../../primitives/std" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-io = { version = "4.0.0-dev", path = "../../primitives/io" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-std = { version = "5.0.0", path = "../../primitives/std" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "frame-benchmarking/std", + "log/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", - "sp-runtime/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/uniques/README.md b/frame/uniques/README.md index b924e338452ff..8a91a558b5b5f 100644 --- a/frame/uniques/README.md +++ b/frame/uniques/README.md @@ -10,9 +10,9 @@ The Uniques module provides functionality for asset management of non-fungible a * Asset Transfer * Asset Destruction -To use it in your runtime, you need to implement the assets [`uniques::Config`](https://docs.rs/pallet-uniques/latest/pallet_uniques/pallet/trait.Config.html). +To use it in your runtime, you need to implement the assets [`uniques::Config`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/trait.Config.html). -The supported dispatchable functions are documented in the [`uniques::Call`](https://docs.rs/pallet-uniques/latest/pallet_uniques/pallet/enum.Call.html) enum. +The supported dispatchable functions are documented in the [`uniques::Call`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/enum.Call.html) enum. ### Terminology @@ -20,7 +20,7 @@ The supported dispatchable functions are documented in the [`uniques::Call`](htt * **Asset transfer:** The action of transferring an asset instance from one account to another. * **Asset burning:** The destruction of an asset instance. * **Non-fungible asset:** An asset for which each unit has unique characteristics. There is exactly - one instance of such an asset in existance and there is exactly one owning account. + one instance of such an asset in existence and there is exactly one owning account. ### Goals @@ -66,13 +66,13 @@ The Uniques pallet in Substrate is designed to make the following possible: * `force_create`: Create a new asset class. * `force_asset_status`: Alter the underlying characteristics of an asset class. -Please refer to the [`Call`](https://docs.rs/pallet-assets/latest/pallet_assets/enum.Call.html) enum +Please refer to the [`Call`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/enum.Call.html) enum and its associated variants for documentation on each function. ## Related Modules * [`System`](https://docs.rs/frame-system/latest/frame_system/) * [`Support`](https://docs.rs/frame-support/latest/frame_support/) -* [`Assets`](https://docs.rs/pallet-assets/latest/pallet_assetss/) +* [`Assets`](https://docs.rs/pallet-assets/latest/pallet_assets/) License: Apache-2.0 diff --git a/frame/uniques/src/benchmarking.rs b/frame/uniques/src/benchmarking.rs index 5c777dc961e9e..ab34558f95eb3 100644 --- a/frame/uniques/src/benchmarking.rs +++ b/frame/uniques/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,14 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Assets pallet benchmarking. +//! Uniques pallet benchmarking. #![cfg(feature = "runtime-benchmarks")] use super::*; use frame_benchmarking::{ - account, benchmarks_instance_pallet, impl_benchmark_test_suite, whitelist_account, - whitelisted_caller, + account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, }; use frame_support::{ dispatch::UnfilteredDispatchable, @@ -31,37 +30,37 @@ use frame_support::{ }; use frame_system::RawOrigin as SystemOrigin; use sp_runtime::traits::Bounded; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; use crate::Pallet as Uniques; const SEED: u32 = 0; -fn create_class, I: 'static>( -) -> (T::ClassId, T::AccountId, ::Source) { +fn create_collection, I: 'static>( +) -> (T::CollectionId, T::AccountId, AccountIdLookupOf) { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - let class = Default::default(); + let collection = T::Helper::collection(0); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - assert!(Uniques::::create( - SystemOrigin::Signed(caller.clone()).into(), - class, + assert!(Uniques::::force_create( + SystemOrigin::Root.into(), + collection, caller_lookup.clone(), + false, ) .is_ok()); - (class, caller, caller_lookup) + (collection, caller, caller_lookup) } -fn add_class_metadata, I: 'static>( -) -> (T::AccountId, ::Source) { - let caller = Class::::get(T::ClassId::default()).unwrap().owner; +fn add_collection_metadata, I: 'static>() -> (T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert!(Uniques::::set_class_metadata( + assert!(Uniques::::set_collection_metadata( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + T::Helper::collection(0), vec![0; T::StringLimit::get() as usize].try_into().unwrap(), false, ) @@ -69,37 +68,37 @@ fn add_class_metadata, I: 'static>( (caller, caller_lookup) } -fn mint_instance, I: 'static>( +fn mint_item, I: 'static>( index: u16, -) -> (T::InstanceId, T::AccountId, ::Source) { - let caller = Class::::get(T::ClassId::default()).unwrap().admin; +) -> (T::ItemId, T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().admin; if caller != whitelisted_caller() { whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - let instance = index.into(); + let item = T::Helper::item(index); assert!(Uniques::::mint( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), - instance, + T::Helper::collection(0), + item, caller_lookup.clone(), ) .is_ok()); - (instance, caller, caller_lookup) + (item, caller, caller_lookup) } -fn add_instance_metadata, I: 'static>( - instance: T::InstanceId, -) -> (T::AccountId, ::Source) { - let caller = Class::::get(T::ClassId::default()).unwrap().owner; +fn add_item_metadata, I: 'static>( + item: T::ItemId, +) -> (T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); assert!(Uniques::::set_metadata( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), - instance, + T::Helper::collection(0), + item, vec![0; T::StringLimit::get() as usize].try_into().unwrap(), false, ) @@ -107,10 +106,10 @@ fn add_instance_metadata, I: 'static>( (caller, caller_lookup) } -fn add_instance_attribute, I: 'static>( - instance: T::InstanceId, -) -> (BoundedVec, T::AccountId, ::Source) { - let caller = Class::::get(T::ClassId::default()).unwrap().owner; +fn add_item_attribute, I: 'static>( + item: T::ItemId, +) -> (BoundedVec, T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } @@ -118,8 +117,8 @@ fn add_instance_attribute, I: 'static>( let key: BoundedVec<_, _> = vec![0; T::KeyLimit::get() as usize].try_into().unwrap(); assert!(Uniques::::set_attribute( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), - Some(instance), + T::Helper::collection(0), + Some(item), key.clone(), vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), ) @@ -127,9 +126,9 @@ fn add_instance_attribute, I: 'static>( (key, caller, caller_lookup) } -fn assert_last_event, I: 'static>(generic_event: >::Event) { +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { let events = frame_system::Pallet::::events(); - let system_event: ::Event = generic_event.into(); + 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); @@ -137,20 +136,24 @@ fn assert_last_event, I: 'static>(generic_event: >:: benchmarks_instance_pallet! { create { - let caller: T::AccountId = whitelisted_caller(); - let caller_lookup = T::Lookup::unlookup(caller.clone()); + let collection = T::Helper::collection(0); + let origin = T::CreateOrigin::successful_origin(&collection); + let caller = T::CreateOrigin::ensure_origin(origin.clone(), &collection).unwrap(); + whitelist_account!(caller); + let admin = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) + let call = Call::::create { collection, admin }; + }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::Created(Default::default(), caller.clone(), caller).into()); + assert_last_event::(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); } force_create { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, Default::default(), caller_lookup, true) + }: _(SystemOrigin::Root, T::Helper::collection(0), caller_lookup, true) verify { - assert_last_event::(Event::ForceCreated(Default::default(), caller).into()); + assert_last_event::(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); } destroy { @@ -158,227 +161,287 @@ benchmarks_instance_pallet! { let m in 0 .. 1_000; let a in 0 .. 1_000; - let (class, caller, caller_lookup) = create_class::(); - add_class_metadata::(); + let (collection, caller, caller_lookup) = create_collection::(); + add_collection_metadata::(); for i in 0..n { - mint_instance::(i as u16); + mint_item::(i as u16); } for i in 0..m { - add_instance_metadata::((i as u16).into()); + add_item_metadata::(T::Helper::item(i as u16)); } for i in 0..a { - add_instance_attribute::((i as u16).into()); + add_item_attribute::(T::Helper::item(i as u16)); } - let witness = Class::::get(class).unwrap().destroy_witness(); - }: _(SystemOrigin::Signed(caller), class, witness) + let witness = Collection::::get(collection).unwrap().destroy_witness(); + }: _(SystemOrigin::Signed(caller), collection, witness) verify { - assert_last_event::(Event::Destroyed(class).into()); + assert_last_event::(Event::Destroyed { collection }.into()); } mint { - let (class, caller, caller_lookup) = create_class::(); - let instance = Default::default(); - }: _(SystemOrigin::Signed(caller.clone()), class, instance, caller_lookup) + let (collection, caller, caller_lookup) = create_collection::(); + let item = T::Helper::item(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup) verify { - assert_last_event::(Event::Issued(class, instance, caller).into()); + assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); } burn { - let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(0); - }: _(SystemOrigin::Signed(caller.clone()), class, instance, Some(caller_lookup)) + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(caller_lookup)) verify { - assert_last_event::(Event::Burned(class, instance, caller).into()); + assert_last_event::(Event::Burned { collection, item, owner: caller }.into()); } transfer { - let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(Default::default()); + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), class, instance, target_lookup) + }: _(SystemOrigin::Signed(caller.clone()), collection, item, target_lookup) verify { - assert_last_event::(Event::Transferred(class, instance, caller, target).into()); + assert_last_event::(Event::Transferred { collection, item, from: caller, to: target }.into()); } redeposit { let i in 0 .. 5_000; - let (class, caller, caller_lookup) = create_class::(); - let instances = (0..i).map(|x| mint_instance::(x as u16).0).collect::>(); - Uniques::::force_asset_status( + let (collection, caller, caller_lookup) = create_collection::(); + let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); + Uniques::::force_item_status( SystemOrigin::Root.into(), - class, - caller_lookup.clone(), + collection, caller_lookup.clone(), caller_lookup.clone(), caller_lookup.clone(), + caller_lookup, true, false, )?; - }: _(SystemOrigin::Signed(caller.clone()), class, instances.clone()) + }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) verify { - assert_last_event::(Event::Redeposited(class, instances).into()); + assert_last_event::(Event::Redeposited { collection, successful_items: items }.into()); } freeze { - let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(Default::default()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), Default::default()) + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller.clone()), T::Helper::collection(0), T::Helper::item(0)) verify { - assert_last_event::(Event::Frozen(Default::default(), Default::default()).into()); + assert_last_event::(Event::Frozen { collection: T::Helper::collection(0), item: T::Helper::item(0) }.into()); } thaw { - let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(Default::default()); + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); Uniques::::freeze( SystemOrigin::Signed(caller.clone()).into(), - class, - instance, + collection, + item, )?; - }: _(SystemOrigin::Signed(caller.clone()), class, instance) + }: _(SystemOrigin::Signed(caller.clone()), collection, item) verify { - assert_last_event::(Event::Thawed(class, instance).into()); + assert_last_event::(Event::Thawed { collection, item }.into()); } - freeze_class { - let (class, caller, caller_lookup) = create_class::(); - }: _(SystemOrigin::Signed(caller.clone()), class) + freeze_collection { + let (collection, caller, caller_lookup) = create_collection::(); + }: _(SystemOrigin::Signed(caller.clone()), collection) verify { - assert_last_event::(Event::ClassFrozen(class).into()); + assert_last_event::(Event::CollectionFrozen { collection }.into()); } - thaw_class { - let (class, caller, caller_lookup) = create_class::(); + thaw_collection { + let (collection, caller, caller_lookup) = create_collection::(); let origin = SystemOrigin::Signed(caller.clone()).into(); - Uniques::::freeze_class(origin, class)?; - }: _(SystemOrigin::Signed(caller.clone()), class) + Uniques::::freeze_collection(origin, collection)?; + }: _(SystemOrigin::Signed(caller.clone()), collection) verify { - assert_last_event::(Event::ClassThawed(class).into()); + assert_last_event::(Event::CollectionThawed { collection }.into()); } transfer_ownership { - let (class, caller, _) = create_class::(); + let (collection, caller, _) = create_collection::(); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); - }: _(SystemOrigin::Signed(caller), class, target_lookup) + let origin = SystemOrigin::Signed(target.clone()).into(); + Uniques::::set_accept_ownership(origin, Some(collection))?; + }: _(SystemOrigin::Signed(caller), collection, target_lookup) verify { - assert_last_event::(Event::OwnerChanged(class, target).into()); + assert_last_event::(Event::OwnerChanged { collection, new_owner: target }.into()); } set_team { - let (class, caller, _) = create_class::(); + let (collection, caller, _) = create_collection::(); let target0 = T::Lookup::unlookup(account("target", 0, SEED)); let target1 = T::Lookup::unlookup(account("target", 1, SEED)); let target2 = T::Lookup::unlookup(account("target", 2, SEED)); - }: _(SystemOrigin::Signed(caller), Default::default(), target0.clone(), target1.clone(), target2.clone()) + }: _(SystemOrigin::Signed(caller), collection, target0, target1, target2) verify { - assert_last_event::(Event::TeamChanged( - class, - account("target", 0, SEED), - account("target", 1, SEED), - account("target", 2, SEED), - ).into()); + assert_last_event::(Event::TeamChanged{ + collection, + issuer: account("target", 0, SEED), + admin: account("target", 1, SEED), + freezer: account("target", 2, SEED), + }.into()); } - force_asset_status { - let (class, caller, caller_lookup) = create_class::(); + force_item_status { + let (collection, caller, caller_lookup) = create_collection::(); let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_asset_status { - class, + let call = Call::::force_item_status { + collection, owner: caller_lookup.clone(), issuer: caller_lookup.clone(), admin: caller_lookup.clone(), - freezer: caller_lookup.clone(), + freezer: caller_lookup, free_holding: true, is_frozen: false, }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::AssetStatusChanged(class).into()); + assert_last_event::(Event::ItemStatusChanged { collection }.into()); } set_attribute { let key: BoundedVec<_, _> = vec![0u8; T::KeyLimit::get() as usize].try_into().unwrap(); let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); - add_instance_metadata::(instance); - }: _(SystemOrigin::Signed(caller), class, Some(instance), key.clone(), value.clone()) + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone(), value.clone()) verify { - assert_last_event::(Event::AttributeSet(class, Some(instance), key, value).into()); + assert_last_event::(Event::AttributeSet { collection, maybe_item: Some(item), key, value }.into()); } clear_attribute { - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); - add_instance_metadata::(instance); - let (key, ..) = add_instance_attribute::(instance); - }: _(SystemOrigin::Signed(caller), class, Some(instance), key.clone()) + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + let (key, ..) = add_item_attribute::(item); + }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone()) verify { - assert_last_event::(Event::AttributeCleared(class, Some(instance), key).into()); + assert_last_event::(Event::AttributeCleared { collection, maybe_item: Some(item), key }.into()); } set_metadata { let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); - }: _(SystemOrigin::Signed(caller), class, instance, data.clone(), false) + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller), collection, item, data.clone(), false) verify { - assert_last_event::(Event::MetadataSet(class, instance, data, false).into()); + assert_last_event::(Event::MetadataSet { collection, item, data, is_frozen: false }.into()); } clear_metadata { - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); - add_instance_metadata::(instance); - }: _(SystemOrigin::Signed(caller), class, instance) + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + }: _(SystemOrigin::Signed(caller), collection, item) verify { - assert_last_event::(Event::MetadataCleared(class, instance).into()); + assert_last_event::(Event::MetadataCleared { collection, item }.into()); } - set_class_metadata { + set_collection_metadata { let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); - let (class, caller, _) = create_class::(); - }: _(SystemOrigin::Signed(caller), class, data.clone(), false) + let (collection, caller, _) = create_collection::(); + }: _(SystemOrigin::Signed(caller), collection, data.clone(), false) verify { - assert_last_event::(Event::ClassMetadataSet(class, data, false).into()); + assert_last_event::(Event::CollectionMetadataSet { collection, data, is_frozen: false }.into()); } - clear_class_metadata { - let (class, caller, _) = create_class::(); - add_class_metadata::(); - }: _(SystemOrigin::Signed(caller), class) + clear_collection_metadata { + let (collection, caller, _) = create_collection::(); + add_collection_metadata::(); + }: _(SystemOrigin::Signed(caller), collection) verify { - assert_last_event::(Event::ClassMetadataCleared(class).into()); + assert_last_event::(Event::CollectionMetadataCleared { collection }.into()); } approve_transfer { - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - }: _(SystemOrigin::Signed(caller.clone()), class, instance, delegate_lookup) + }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) verify { - assert_last_event::(Event::ApprovedTransfer(class, instance, caller, delegate).into()); + assert_last_event::(Event::ApprovedTransfer { collection, item, owner: caller, delegate }.into()); } cancel_approval { - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); - Uniques::::approve_transfer(origin, class, instance, delegate_lookup.clone())?; - }: _(SystemOrigin::Signed(caller.clone()), class, instance, Some(delegate_lookup)) + Uniques::::approve_transfer(origin, collection, item, delegate_lookup.clone())?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(delegate_lookup)) verify { - assert_last_event::(Event::ApprovalCancelled(class, instance, caller, delegate).into()); + assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); + } + + set_accept_ownership { + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + let collection = T::Helper::collection(0); + }: _(SystemOrigin::Signed(caller.clone()), Some(collection)) + verify { + assert_last_event::(Event::OwnershipAcceptanceChanged { + who: caller, + maybe_collection: Some(collection), + }.into()); } -} -impl_benchmark_test_suite!(Uniques, crate::mock::new_test_ext(), crate::mock::Test); + set_collection_max_supply { + let (collection, caller, _) = create_collection::(); + }: _(SystemOrigin::Signed(caller.clone()), collection, u32::MAX) + verify { + assert_last_event::(Event::CollectionMaxSupplySet { + collection, + max_supply: u32::MAX, + }.into()); + } + + set_price { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let price = ItemPrice::::from(100u32); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(price), Some(delegate_lookup)) + verify { + assert_last_event::(Event::ItemPriceSet { + collection, + item, + price, + whitelisted_buyer: Some(delegate), + }.into()); + } + + buy_item { + let (collection, seller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let buyer: T::AccountId = account("buyer", 0, SEED); + let buyer_lookup = T::Lookup::unlookup(buyer.clone()); + let price = ItemPrice::::from(0u32); + let origin = SystemOrigin::Signed(seller.clone()).into(); + Uniques::::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?; + T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::::max_value()); + }: _(SystemOrigin::Signed(buyer.clone()), collection, item, price.clone()) + verify { + assert_last_event::(Event::ItemBought { + collection, + item, + price, + seller, + buyer, + }.into()); + } + + impl_benchmark_test_suite!(Uniques, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index a878a4910f769..4e68f1139420d 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,128 +18,265 @@ //! Various pieces of common functionality. use super::*; -use frame_support::{ensure, traits::Get}; +use frame_support::{ + ensure, + traits::{ExistenceRequirement, Get}, +}; use sp_runtime::{DispatchError, DispatchResult}; impl, I: 'static> Pallet { - pub(crate) fn do_transfer( - class: T::ClassId, - instance: T::InstanceId, + pub fn do_transfer( + collection: T::CollectionId, + item: T::ItemId, dest: T::AccountId, with_details: impl FnOnce( - &ClassDetailsFor, - &mut InstanceDetailsFor, + &CollectionDetailsFor, + &mut ItemDetailsFor, ) -> DispatchResult, ) -> DispatchResult { - let class_details = Class::::get(&class).ok_or(Error::::Unknown)?; - ensure!(!class_details.is_frozen, Error::::Frozen); + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(!collection_details.is_frozen, Error::::Frozen); + ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); - let mut details = Asset::::get(&class, &instance).ok_or(Error::::Unknown)?; + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; ensure!(!details.is_frozen, Error::::Frozen); - with_details(&class_details, &mut details)?; + with_details(&collection_details, &mut details)?; - Account::::remove((&details.owner, &class, &instance)); - Account::::insert((&dest, &class, &instance), ()); + Account::::remove((&details.owner, &collection, &item)); + Account::::insert((&dest, &collection, &item), ()); let origin = details.owner; details.owner = dest; - Asset::::insert(&class, &instance, &details); - Self::deposit_event(Event::Transferred(class, instance, origin, details.owner)); + // The approved account has to be reset to None, because otherwise pre-approve attack would + // be possible, where the owner can approve his second account before making the transaction + // and then claiming the item back. + details.approved = None; + + Item::::insert(&collection, &item, &details); + ItemPriceOf::::remove(&collection, &item); + + Self::deposit_event(Event::Transferred { + collection, + item, + from: origin, + to: details.owner, + }); Ok(()) } - pub(super) fn do_create_class( - class: T::ClassId, + pub fn do_create_collection( + collection: T::CollectionId, owner: T::AccountId, admin: T::AccountId, deposit: DepositBalanceOf, free_holding: bool, event: Event, ) -> DispatchResult { - ensure!(!Class::::contains_key(class), Error::::InUse); + ensure!(!Collection::::contains_key(collection), Error::::InUse); T::Currency::reserve(&owner, deposit)?; - Class::::insert( - class, - ClassDetails { + Collection::::insert( + collection, + CollectionDetails { owner: owner.clone(), issuer: admin.clone(), admin: admin.clone(), - freezer: admin.clone(), + freezer: admin, total_deposit: deposit, free_holding, - instances: 0, - instance_metadatas: 0, + items: 0, + item_metadatas: 0, attributes: 0, is_frozen: false, }, ); + CollectionAccount::::insert(&owner, &collection, ()); Self::deposit_event(event); Ok(()) } - pub(super) fn do_mint( - class: T::ClassId, - instance: T::InstanceId, + pub fn do_destroy_collection( + collection: T::CollectionId, + witness: DestroyWitness, + maybe_check_owner: Option, + ) -> Result { + Collection::::try_mutate_exists(collection, |maybe_details| { + let collection_details = + maybe_details.take().ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = maybe_check_owner { + ensure!(collection_details.owner == check_owner, Error::::NoPermission); + } + ensure!(collection_details.items == witness.items, Error::::BadWitness); + ensure!( + collection_details.item_metadatas == witness.item_metadatas, + Error::::BadWitness + ); + ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); + + for (item, details) in Item::::drain_prefix(&collection) { + Account::::remove((&details.owner, &collection, &item)); + } + #[allow(deprecated)] + ItemMetadataOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + ItemPriceOf::::remove_prefix(&collection, None); + CollectionMetadataOf::::remove(&collection); + #[allow(deprecated)] + Attribute::::remove_prefix((&collection,), None); + CollectionAccount::::remove(&collection_details.owner, &collection); + T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); + CollectionMaxSupply::::remove(&collection); + + Self::deposit_event(Event::Destroyed { collection }); + + Ok(DestroyWitness { + items: collection_details.items, + item_metadatas: collection_details.item_metadatas, + attributes: collection_details.attributes, + }) + }) + } + + pub fn do_mint( + collection: T::CollectionId, + item: T::ItemId, owner: T::AccountId, - with_details: impl FnOnce(&ClassDetailsFor) -> DispatchResult, + with_details: impl FnOnce(&CollectionDetailsFor) -> DispatchResult, ) -> DispatchResult { - ensure!(!Asset::::contains_key(class, instance), Error::::AlreadyExists); + ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); - Class::::try_mutate(&class, |maybe_class_details| -> DispatchResult { - let class_details = maybe_class_details.as_mut().ok_or(Error::::Unknown)?; + Collection::::try_mutate( + &collection, + |maybe_collection_details| -> DispatchResult { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; - with_details(&class_details)?; + with_details(collection_details)?; - let instances = - class_details.instances.checked_add(1).ok_or(ArithmeticError::Overflow)?; - class_details.instances = instances; + if let Ok(max_supply) = CollectionMaxSupply::::try_get(&collection) { + ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); + } - let deposit = match class_details.free_holding { - true => Zero::zero(), - false => T::InstanceDeposit::get(), - }; - T::Currency::reserve(&class_details.owner, deposit)?; - class_details.total_deposit += deposit; + let items = + collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; + collection_details.items = items; - let owner = owner.clone(); - Account::::insert((&owner, &class, &instance), ()); - let details = InstanceDetails { owner, approved: None, is_frozen: false, deposit }; - Asset::::insert(&class, &instance, details); - Ok(()) - })?; + let deposit = match collection_details.free_holding { + true => Zero::zero(), + false => T::ItemDeposit::get(), + }; + T::Currency::reserve(&collection_details.owner, deposit)?; + collection_details.total_deposit += deposit; - Self::deposit_event(Event::Issued(class, instance, owner)); + let owner = owner.clone(); + Account::::insert((&owner, &collection, &item), ()); + let details = ItemDetails { owner, approved: None, is_frozen: false, deposit }; + Item::::insert(&collection, &item, details); + Ok(()) + }, + )?; + + Self::deposit_event(Event::Issued { collection, item, owner }); Ok(()) } - pub(super) fn do_burn( - class: T::ClassId, - instance: T::InstanceId, - with_details: impl FnOnce(&ClassDetailsFor, &InstanceDetailsFor) -> DispatchResult, + pub fn do_burn( + collection: T::CollectionId, + item: T::ItemId, + with_details: impl FnOnce(&CollectionDetailsFor, &ItemDetailsFor) -> DispatchResult, ) -> DispatchResult { - let owner = Class::::try_mutate( - &class, - |maybe_class_details| -> Result { - let class_details = maybe_class_details.as_mut().ok_or(Error::::Unknown)?; - let details = - Asset::::get(&class, &instance).ok_or(Error::::Unknown)?; - with_details(&class_details, &details)?; + let owner = Collection::::try_mutate( + &collection, + |maybe_collection_details| -> Result { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + let details = Item::::get(&collection, &item) + .ok_or(Error::::UnknownCollection)?; + with_details(collection_details, &details)?; // Return the deposit. - T::Currency::unreserve(&class_details.owner, details.deposit); - class_details.total_deposit.saturating_reduce(details.deposit); - class_details.instances.saturating_dec(); + T::Currency::unreserve(&collection_details.owner, details.deposit); + collection_details.total_deposit.saturating_reduce(details.deposit); + collection_details.items.saturating_dec(); Ok(details.owner) }, )?; - Asset::::remove(&class, &instance); - Account::::remove((&owner, &class, &instance)); + Item::::remove(&collection, &item); + Account::::remove((&owner, &collection, &item)); + ItemPriceOf::::remove(&collection, &item); + + Self::deposit_event(Event::Burned { collection, item, owner }); + Ok(()) + } + + pub fn do_set_price( + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + price: Option>, + whitelisted_buyer: Option, + ) -> DispatchResult { + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner == sender, Error::::NoPermission); + + if let Some(ref price) = price { + ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); + Self::deposit_event(Event::ItemPriceSet { + collection, + item, + price: *price, + whitelisted_buyer, + }); + } else { + ItemPriceOf::::remove(&collection, &item); + Self::deposit_event(Event::ItemPriceRemoved { collection, item }); + } + + Ok(()) + } + + pub fn do_buy_item( + collection: T::CollectionId, + item: T::ItemId, + buyer: T::AccountId, + bid_price: ItemPrice, + ) -> DispatchResult { + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner != buyer, Error::::NoPermission); + + let price_info = + ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; + + ensure!(bid_price >= price_info.0, Error::::BidTooLow); + + if let Some(only_buyer) = price_info.1 { + ensure!(only_buyer == buyer, Error::::NoPermission); + } + + T::Currency::transfer( + &buyer, + &details.owner, + price_info.0, + ExistenceRequirement::KeepAlive, + )?; + + let old_owner = details.owner.clone(); + + Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?; + + Self::deposit_event(Event::ItemBought { + collection, + item, + price: price_info.0, + seller: old_owner, + buyer, + }); - Self::deposit_event(Event::Burned(class, instance, owner)); Ok(()) } } diff --git a/frame/uniques/src/impl_nonfungibles.rs b/frame/uniques/src/impl_nonfungibles.rs index c5d5c6089f865..75d193ad19605 100644 --- a/frame/uniques/src/impl_nonfungibles.rs +++ b/frame/uniques/src/impl_nonfungibles.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,69 +19,67 @@ use super::*; use frame_support::{ - traits::{ - tokens::nonfungibles::{Create, Inspect, InspectEnumerable, Mutate, Transfer}, - Get, - }, + storage::KeyPrefixIterator, + traits::{tokens::nonfungibles::*, Get}, BoundedSlice, }; -use sp_runtime::DispatchResult; -use sp_std::convert::TryFrom; +use sp_runtime::{DispatchError, DispatchResult}; +use sp_std::prelude::*; impl, I: 'static> Inspect<::AccountId> for Pallet { - type InstanceId = T::InstanceId; - type ClassId = T::ClassId; + type ItemId = T::ItemId; + type CollectionId = T::CollectionId; fn owner( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, ) -> Option<::AccountId> { - Asset::::get(class, instance).map(|a| a.owner) + Item::::get(collection, item).map(|a| a.owner) } - fn class_owner(class: &Self::ClassId) -> Option<::AccountId> { - Class::::get(class).map(|a| a.owner) + fn collection_owner(collection: &Self::CollectionId) -> Option<::AccountId> { + Collection::::get(collection).map(|a| a.owner) } - /// Returns the attribute value of `instance` of `class` corresponding to `key`. + /// Returns the attribute value of `item` of `collection` corresponding to `key`. /// - /// When `key` is empty, we return the instance metadata value. + /// When `key` is empty, we return the item metadata value. /// /// By default this is `None`; no attributes are defined. fn attribute( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, key: &[u8], ) -> Option> { if key.is_empty() { - // We make the empty key map to the instance metadata value. - InstanceMetadataOf::::get(class, instance).map(|m| m.data.into()) + // We make the empty key map to the item metadata value. + ItemMetadataOf::::get(collection, item).map(|m| m.data.into()) } else { let key = BoundedSlice::<_, _>::try_from(key).ok()?; - Attribute::::get((class, Some(instance), key)).map(|a| a.0.into()) + Attribute::::get((collection, Some(item), key)).map(|a| a.0.into()) } } - /// Returns the attribute value of `instance` of `class` corresponding to `key`. + /// Returns the attribute value of `item` of `collection` corresponding to `key`. /// - /// When `key` is empty, we return the instance metadata value. + /// When `key` is empty, we return the item metadata value. /// /// By default this is `None`; no attributes are defined. - fn class_attribute(class: &Self::ClassId, key: &[u8]) -> Option> { + fn collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> Option> { if key.is_empty() { - // We make the empty key map to the instance metadata value. - ClassMetadataOf::::get(class).map(|m| m.data.into()) + // We make the empty key map to the item metadata value. + CollectionMetadataOf::::get(collection).map(|m| m.data.into()) } else { let key = BoundedSlice::<_, _>::try_from(key).ok()?; - Attribute::::get((class, Option::::None, key)).map(|a| a.0.into()) + Attribute::::get((collection, Option::::None, key)).map(|a| a.0.into()) } } - /// Returns `true` if the asset `instance` of `class` may be transferred. + /// Returns `true` if the `item` of `collection` may be transferred. /// - /// Default implementation is that all assets are transferable. - fn can_transfer(class: &Self::ClassId, instance: &Self::InstanceId) -> bool { - match (Class::::get(class), Asset::::get(class, instance)) { + /// Default implementation is that all items are transferable. + fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool { + match (Collection::::get(collection), Item::::get(collection, item)) { (Some(cd), Some(id)) if !cd.is_frozen && !id.is_frozen => true, _ => false, } @@ -89,76 +87,109 @@ impl, I: 'static> Inspect<::AccountId> for Palle } impl, I: 'static> Create<::AccountId> for Pallet { - /// Create a `class` of nonfungible assets to be owned by `who` and managed by `admin`. - fn create_class( - class: &Self::ClassId, + /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. + fn create_collection( + collection: &Self::CollectionId, who: &T::AccountId, admin: &T::AccountId, ) -> DispatchResult { - Self::do_create_class( - class.clone(), + Self::do_create_collection( + *collection, who.clone(), admin.clone(), - T::ClassDeposit::get(), + T::CollectionDeposit::get(), false, - Event::Created(class.clone(), who.clone(), admin.clone()), + Event::Created { collection: *collection, creator: who.clone(), owner: admin.clone() }, ) } } +impl, I: 'static> Destroy<::AccountId> for Pallet { + type DestroyWitness = DestroyWitness; + + fn get_destroy_witness(collection: &Self::CollectionId) -> Option { + Collection::::get(collection).map(|a| a.destroy_witness()) + } + + fn destroy( + collection: Self::CollectionId, + witness: Self::DestroyWitness, + maybe_check_owner: Option, + ) -> Result { + Self::do_destroy_collection(collection, witness, maybe_check_owner) + } +} + impl, I: 'static> Mutate<::AccountId> for Pallet { fn mint_into( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, who: &T::AccountId, ) -> DispatchResult { - Self::do_mint(class.clone(), instance.clone(), who.clone(), |_| Ok(())) + Self::do_mint(*collection, *item, who.clone(), |_| Ok(())) } - fn burn_from(class: &Self::ClassId, instance: &Self::InstanceId) -> DispatchResult { - Self::do_burn(class.clone(), instance.clone(), |_, _| Ok(())) + fn burn( + collection: &Self::CollectionId, + item: &Self::ItemId, + maybe_check_owner: Option<&T::AccountId>, + ) -> DispatchResult { + Self::do_burn(*collection, *item, |_, d| { + if let Some(check_owner) = maybe_check_owner { + if &d.owner != check_owner { + return Err(Error::::NoPermission.into()) + } + } + Ok(()) + }) } } impl, I: 'static> Transfer for Pallet { fn transfer( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, destination: &T::AccountId, ) -> DispatchResult { - Self::do_transfer(class.clone(), instance.clone(), destination.clone(), |_, _| Ok(())) + Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(())) } } impl, I: 'static> InspectEnumerable for Pallet { - /// Returns an iterator of the asset classes in existence. + type CollectionsIterator = KeyPrefixIterator<>::CollectionId>; + type ItemsIterator = KeyPrefixIterator<>::ItemId>; + type OwnedIterator = + KeyPrefixIterator<(>::CollectionId, >::ItemId)>; + type OwnedInCollectionIterator = KeyPrefixIterator<>::ItemId>; + + /// Returns an iterator of the collections in existence. /// /// NOTE: iterating this list invokes a storage read per item. - fn classes() -> Box> { - Box::new(ClassMetadataOf::::iter_keys()) + fn collections() -> Self::CollectionsIterator { + CollectionMetadataOf::::iter_keys() } - /// Returns an iterator of the instances of an asset `class` in existence. + /// Returns an iterator of the items of a `collection` in existence. /// /// NOTE: iterating this list invokes a storage read per item. - fn instances(class: &Self::ClassId) -> Box> { - Box::new(InstanceMetadataOf::::iter_key_prefix(class)) + fn items(collection: &Self::CollectionId) -> Self::ItemsIterator { + ItemMetadataOf::::iter_key_prefix(collection) } - /// Returns an iterator of the asset instances of all classes owned by `who`. + /// Returns an iterator of the items of all collections owned by `who`. /// /// NOTE: iterating this list invokes a storage read per item. - fn owned(who: &T::AccountId) -> Box> { - Box::new(Account::::iter_key_prefix((who,))) + fn owned(who: &T::AccountId) -> Self::OwnedIterator { + Account::::iter_key_prefix((who,)) } - /// Returns an iterator of the asset instances of `class` owned by `who`. + /// Returns an iterator of the items of `collection` owned by `who`. /// /// NOTE: iterating this list invokes a storage read per item. - fn owned_in_class( - class: &Self::ClassId, + fn owned_in_collection( + collection: &Self::CollectionId, who: &T::AccountId, - ) -> Box> { - Box::new(Account::::iter_key_prefix((who, class))) + ) -> Self::OwnedInCollectionIterator { + Account::::iter_key_prefix((who, collection)) } } diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index 8c716694051b5..8157817d4166e 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,15 +15,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Unique (Assets) Module +//! # Unique (Items) Module //! -//! A simple, secure module for dealing with non-fungible assets. +//! A simple, secure module for dealing with non-fungible items. //! //! ## Related Modules //! //! * [`System`](../frame_system/index.html) //! * [`Support`](../frame_support/index.html) +#![recursion_limit = "256"] // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -33,15 +34,21 @@ mod benchmarking; pub mod mock; #[cfg(test)] mod tests; -pub mod weights; mod functions; mod impl_nonfungibles; mod types; -pub use types::*; -use codec::{Decode, Encode, HasCompact}; -use frame_support::traits::{BalanceStatus::Reserved, Currency, ReservableCurrency}; +pub mod migration; +pub mod weights; + +use codec::{Decode, Encode}; +use frame_support::{ + traits::{ + tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, + }, + transactional, +}; use frame_system::Config as SystemConfig; use sp_runtime::{ traits::{Saturating, StaticLookup, Zero}, @@ -50,8 +57,11 @@ use sp_runtime::{ use sp_std::prelude::*; pub use pallet::*; +pub use types::*; pub use weights::WeightInfo; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -62,38 +72,65 @@ pub mod pallet { #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn collection(i: u16) -> CollectionId; + fn item(i: u16) -> ItemId; + } + #[cfg(feature = "runtime-benchmarks")] + impl, ItemId: From> BenchmarkHelper for () { + fn collection(i: u16) -> CollectionId { + i.into() + } + fn item(i: u16) -> ItemId { + i.into() + } + } + #[pallet::config] /// The module configuration trait. pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; - /// Identifier for the class of asset. - type ClassId: Member + Parameter + Default + Copy + HasCompact; + /// Identifier for the collection of item. + type CollectionId: Member + Parameter + MaxEncodedLen + Copy; - /// The type used to identify a unique asset within an asset class. - type InstanceId: Member + Parameter + Default + Copy + HasCompact + From; + /// The type used to identify a unique item within a collection. + type ItemId: Member + Parameter + MaxEncodedLen + Copy; /// The currency mechanism, used for paying for reserves. type Currency: ReservableCurrency; - /// The origin which may forcibly create or destroy an asset or otherwise alter privileged + /// The origin which may forcibly create or destroy an item or otherwise alter privileged /// attributes. - type ForceOrigin: EnsureOrigin; + type ForceOrigin: EnsureOrigin; + + /// Standard collection creation is only allowed if the origin attempting it and the + /// collection are in this set. + type CreateOrigin: EnsureOriginWithArg< + Self::RuntimeOrigin, + Self::CollectionId, + Success = Self::AccountId, + >; + + /// Locker trait to enable Locking mechanism downstream. + type Locker: Locker; - /// The basic amount of funds that must be reserved for an asset class. + /// The basic amount of funds that must be reserved for collection. #[pallet::constant] - type ClassDeposit: Get>; + type CollectionDeposit: Get>; - /// The basic amount of funds that must be reserved for an asset instance. + /// The basic amount of funds that must be reserved for an item. #[pallet::constant] - type InstanceDeposit: Get>; + type ItemDeposit: Get>; - /// The basic amount of funds that must be reserved when adding metadata to your asset. + /// The basic amount of funds that must be reserved when adding metadata to your item. #[pallet::constant] type MetadataDepositBase: Get>; - /// The basic amount of funds that must be reserved when adding an attribute to an asset. + /// The basic amount of funds that must be reserved when adding an attribute to an item. #[pallet::constant] type AttributeDepositBase: Get>; @@ -114,157 +151,249 @@ pub mod pallet { #[pallet::constant] type ValueLimit: Get; + #[cfg(feature = "runtime-benchmarks")] + /// A set of helper functions for benchmarking. + type Helper: BenchmarkHelper; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } #[pallet::storage] - /// Details of an asset class. - pub(super) type Class, I: 'static = ()> = StorageMap< + #[pallet::storage_prefix = "Class"] + /// Details of a collection. + pub(super) type Collection, I: 'static = ()> = StorageMap< _, Blake2_128Concat, - T::ClassId, - ClassDetails>, + T::CollectionId, + CollectionDetails>, >; #[pallet::storage] - /// The assets held by any given account; set out this way so that assets owned by a single + /// The collection, if any, of which an account is willing to take ownership. + pub(super) type OwnershipAcceptance, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>; + + #[pallet::storage] + /// The items held by any given account; set out this way so that items owned by a single /// account can be enumerated. pub(super) type Account, I: 'static = ()> = StorageNMap< _, ( NMapKey, // owner - NMapKey, - NMapKey, + NMapKey, + NMapKey, ), (), OptionQuery, >; #[pallet::storage] - /// The assets in existence and their ownership details. - pub(super) type Asset, I: 'static = ()> = StorageDoubleMap< + #[pallet::storage_prefix = "ClassAccount"] + /// The collections owned by any given account; set out this way so that collections owned by + /// a single account can be enumerated. + pub(super) type CollectionAccount, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + T::CollectionId, + (), + OptionQuery, + >; + + #[pallet::storage] + #[pallet::storage_prefix = "Asset"] + /// The items in existence and their ownership details. + pub(super) type Item, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, - T::ClassId, + T::CollectionId, Blake2_128Concat, - T::InstanceId, - InstanceDetails>, + T::ItemId, + ItemDetails>, OptionQuery, >; #[pallet::storage] - /// Metadata of an asset class. - pub(super) type ClassMetadataOf, I: 'static = ()> = StorageMap< + #[pallet::storage_prefix = "ClassMetadataOf"] + /// Metadata of a collection. + pub(super) type CollectionMetadataOf, I: 'static = ()> = StorageMap< _, Blake2_128Concat, - T::ClassId, - ClassMetadata, T::StringLimit>, + T::CollectionId, + CollectionMetadata, T::StringLimit>, OptionQuery, >; #[pallet::storage] - /// Metadata of an asset instance. - pub(super) type InstanceMetadataOf, I: 'static = ()> = StorageDoubleMap< + #[pallet::storage_prefix = "InstanceMetadataOf"] + /// Metadata of an item. + pub(super) type ItemMetadataOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, - T::ClassId, + T::CollectionId, Blake2_128Concat, - T::InstanceId, - InstanceMetadata, T::StringLimit>, + T::ItemId, + ItemMetadata, T::StringLimit>, OptionQuery, >; #[pallet::storage] - /// Metadata of an asset class. + /// Attributes of a collection. pub(super) type Attribute, I: 'static = ()> = StorageNMap< _, ( - NMapKey, - NMapKey>, + NMapKey, + NMapKey>, NMapKey>, ), (BoundedVec, DepositBalanceOf), OptionQuery, >; + #[pallet::storage] + /// Price of an asset instance. + pub(super) type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + (ItemPrice, Option), + OptionQuery, + >; + + #[pallet::storage] + /// Keeps track of the number of items a collection might have. + pub(super) type CollectionMaxSupply, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { - /// An asset class was created. \[ class, creator, owner \] - Created(T::ClassId, T::AccountId, T::AccountId), - /// An asset class was force-created. \[ class, owner \] - ForceCreated(T::ClassId, T::AccountId), - /// An asset `class` was destroyed. \[ class \] - Destroyed(T::ClassId), - /// An asset `instance` was issued. \[ class, instance, owner \] - Issued(T::ClassId, T::InstanceId, T::AccountId), - /// An asset `instance` was transferred. \[ class, instance, from, to \] - Transferred(T::ClassId, T::InstanceId, T::AccountId, T::AccountId), - /// An asset `instance` was destroyed. \[ class, instance, owner \] - Burned(T::ClassId, T::InstanceId, T::AccountId), - /// Some asset `instance` was frozen. \[ class, instance \] - Frozen(T::ClassId, T::InstanceId), - /// Some asset `instance` was thawed. \[ class, instance \] - Thawed(T::ClassId, T::InstanceId), - /// Some asset `class` was frozen. \[ class \] - ClassFrozen(T::ClassId), - /// Some asset `class` was thawed. \[ class \] - ClassThawed(T::ClassId), - /// The owner changed \[ class, new_owner \] - OwnerChanged(T::ClassId, T::AccountId), - /// The management team changed \[ class, issuer, admin, freezer \] - TeamChanged(T::ClassId, T::AccountId, T::AccountId, T::AccountId), - /// An `instance` of an asset `class` has been approved by the `owner` for transfer by a - /// `delegate`. - /// \[ class, instance, owner, delegate \] - ApprovedTransfer(T::ClassId, T::InstanceId, T::AccountId, T::AccountId), - /// An approval for a `delegate` account to transfer the `instance` of an asset `class` was - /// cancelled by its `owner`. - /// \[ class, instance, owner, delegate \] - ApprovalCancelled(T::ClassId, T::InstanceId, T::AccountId, T::AccountId), - /// An asset `class` has had its attributes changed by the `Force` origin. - /// \[ class \] - AssetStatusChanged(T::ClassId), - /// New metadata has been set for an asset class. \[ class, data, is_frozen \] - ClassMetadataSet(T::ClassId, BoundedVec, bool), - /// Metadata has been cleared for an asset class. \[ class \] - ClassMetadataCleared(T::ClassId), - /// New metadata has been set for an asset instance. - /// \[ class, instance, data, is_frozen \] - MetadataSet(T::ClassId, T::InstanceId, BoundedVec, bool), - /// Metadata has been cleared for an asset instance. \[ class, instance \] - MetadataCleared(T::ClassId, T::InstanceId), - /// Metadata has been cleared for an asset instance. \[ class, successful_instances \] - Redeposited(T::ClassId, Vec), - /// New attribute metadata has been set for an asset class or instance. - /// \[ class, maybe_instance, key, value \] - AttributeSet( - T::ClassId, - Option, - BoundedVec, - BoundedVec, - ), - /// Attribute metadata has been cleared for an asset class or instance. - /// \[ class, maybe_instance, key, maybe_value \] - AttributeCleared(T::ClassId, Option, BoundedVec), + /// A `collection` was created. + Created { collection: T::CollectionId, creator: T::AccountId, owner: T::AccountId }, + /// A `collection` was force-created. + ForceCreated { collection: T::CollectionId, owner: T::AccountId }, + /// A `collection` was destroyed. + Destroyed { collection: T::CollectionId }, + /// An `item` was issued. + Issued { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// An `item` was transferred. + Transferred { + collection: T::CollectionId, + item: T::ItemId, + from: T::AccountId, + to: T::AccountId, + }, + /// An `item` was destroyed. + Burned { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// Some `item` was frozen. + Frozen { collection: T::CollectionId, item: T::ItemId }, + /// Some `item` was thawed. + Thawed { collection: T::CollectionId, item: T::ItemId }, + /// Some `collection` was frozen. + CollectionFrozen { collection: T::CollectionId }, + /// Some `collection` was thawed. + CollectionThawed { collection: T::CollectionId }, + /// The owner changed. + OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId }, + /// The management team changed. + TeamChanged { + collection: T::CollectionId, + issuer: T::AccountId, + admin: T::AccountId, + freezer: T::AccountId, + }, + /// An `item` of a `collection` has been approved by the `owner` for transfer by + /// a `delegate`. + ApprovedTransfer { + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + delegate: T::AccountId, + }, + /// An approval for a `delegate` account to transfer the `item` of an item + /// `collection` was cancelled by its `owner`. + ApprovalCancelled { + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + delegate: T::AccountId, + }, + /// A `collection` has had its attributes changed by the `Force` origin. + ItemStatusChanged { collection: T::CollectionId }, + /// New metadata has been set for a `collection`. + CollectionMetadataSet { + collection: T::CollectionId, + data: BoundedVec, + is_frozen: bool, + }, + /// Metadata has been cleared for a `collection`. + CollectionMetadataCleared { collection: T::CollectionId }, + /// New metadata has been set for an item. + MetadataSet { + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + is_frozen: bool, + }, + /// Metadata has been cleared for an item. + MetadataCleared { collection: T::CollectionId, item: T::ItemId }, + /// Metadata has been cleared for an item. + Redeposited { collection: T::CollectionId, successful_items: Vec }, + /// New attribute metadata has been set for a `collection` or `item`. + AttributeSet { + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + value: BoundedVec, + }, + /// Attribute metadata has been cleared for a `collection` or `item`. + AttributeCleared { + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + }, + /// Ownership acceptance has changed for an account. + OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, + /// Max supply has been set for a collection. + CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, + /// The price was set for the instance. + ItemPriceSet { + collection: T::CollectionId, + item: T::ItemId, + price: ItemPrice, + whitelisted_buyer: Option, + }, + /// The price for the instance was removed. + ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId }, + /// An item was bought. + ItemBought { + collection: T::CollectionId, + item: T::ItemId, + price: ItemPrice, + seller: T::AccountId, + buyer: T::AccountId, + }, } #[pallet::error] pub enum Error { /// The signing account has no permission to do the operation. NoPermission, - /// The given asset ID is unknown. - Unknown, - /// The asset instance ID has already been used for an asset. + /// The given item ID is unknown. + UnknownCollection, + /// The item ID has already been used for an item. AlreadyExists, /// The owner turned out to be different to what was expected. WrongOwner, /// Invalid witness data given. BadWitness, - /// The asset ID is already taken. + /// The item ID is already taken. InUse, - /// The asset instance or class is frozen. + /// The item or collection is frozen. Frozen, /// The delegate turned out to be different to what was expected. WrongDelegate, @@ -272,200 +401,206 @@ pub mod pallet { NoDelegate, /// No approval exists that would allow the transfer. Unapproved, + /// The named owner has not signed ownership of the collection is acceptable. + Unaccepted, + /// The item is locked. + Locked, + /// All items have been minted. + MaxSupplyReached, + /// The max supply has already been set. + MaxSupplyAlreadySet, + /// The provided max supply is less to the amount of items a collection already has. + MaxSupplyTooSmall, + /// The given item ID is unknown. + UnknownItem, + /// Item is not for sale. + NotForSale, + /// The provided bid is too low. + BidTooLow, } - #[pallet::hooks] - impl, I: 'static> Hooks> for Pallet {} - impl, I: 'static> Pallet { - /// Get the owner of the asset instance, if the asset exists. - pub fn owner(class: T::ClassId, instance: T::InstanceId) -> Option { - Asset::::get(class, instance).map(|i| i.owner) + /// Get the owner of the item, if the item exists. + pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { + Item::::get(collection, item).map(|i| i.owner) + } + + /// Get the owner of the item, if the item exists. + pub fn collection_owner(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.owner) } } #[pallet::call] impl, I: 'static> Pallet { - /// Issue a new class of non-fungible assets from a public origin. + /// Issue a new collection of non-fungible items from a public origin. /// - /// This new asset class has no assets initially and its owner is the origin. + /// This new collection has no items initially and its owner is the origin. /// - /// The origin must be Signed and the sender must have sufficient funds free. + /// The origin must conform to the configured `CreateOrigin` and have sufficient funds free. /// - /// `AssetDeposit` funds of sender are reserved. + /// `ItemDeposit` funds of sender are reserved. /// /// Parameters: - /// - `class`: The identifier of the new asset class. This must not be currently in use. - /// - `admin`: The admin of this class of assets. The admin is the initial address of each - /// member of the asset class's admin team. + /// - `collection`: The identifier of the new collection. This must not be currently in use. + /// - `admin`: The admin of this collection. The admin is the initial address of each + /// member of the collection's admin team. /// /// Emits `Created` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - admin: ::Source, + collection: T::CollectionId, + admin: AccountIdLookupOf, ) -> DispatchResult { - let owner = ensure_signed(origin)?; + let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; let admin = T::Lookup::lookup(admin)?; - Self::do_create_class( - class, + Self::do_create_collection( + collection, owner.clone(), admin.clone(), - T::ClassDeposit::get(), + T::CollectionDeposit::get(), false, - Event::Created(class, owner, admin), + Event::Created { collection, creator: owner, owner: admin }, ) } - /// Issue a new class of non-fungible assets from a privileged origin. + /// Issue a new collection of non-fungible items from a privileged origin. /// - /// This new asset class has no assets initially. + /// This new collection has no items initially. /// /// The origin must conform to `ForceOrigin`. /// /// Unlike `create`, no funds are reserved. /// - /// - `class`: The identifier of the new asset. This must not be currently in use. - /// - `owner`: The owner of this class of assets. The owner has full superuser permissions - /// over this asset, but may later change and configure the permissions using + /// - `collection`: The identifier of the new item. This must not be currently in use. + /// - `owner`: The owner of this collection of items. The owner has full superuser + /// permissions + /// over this item, but may later change and configure the permissions using /// `transfer_ownership` and `set_team`. /// /// Emits `ForceCreated` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - owner: ::Source, + collection: T::CollectionId, + owner: AccountIdLookupOf, free_holding: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; - Self::do_create_class( - class, + Self::do_create_collection( + collection, owner.clone(), owner.clone(), Zero::zero(), free_holding, - Event::ForceCreated(class, owner), + Event::ForceCreated { collection, owner }, ) } - /// Destroy a class of fungible assets. + /// Destroy a collection of fungible items. /// /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the - /// owner of the asset `class`. + /// owner of the `collection`. /// - /// - `class`: The identifier of the asset class to be destroyed. - /// - `witness`: Information on the instances minted in the asset class. This must be + /// - `collection`: The identifier of the collection to be destroyed. + /// - `witness`: Information on the items minted in the collection. This must be /// correct. /// /// Emits `Destroyed` event when successful. /// /// Weight: `O(n + m)` where: - /// - `n = witness.instances` - /// - `m = witness.instance_metadatas` + /// - `n = witness.items` + /// - `m = witness.item_metadatas` /// - `a = witness.attributes` + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::destroy( - witness.instances, - witness.instance_metadatas, + witness.items, + witness.item_metadatas, witness.attributes, ))] pub fn destroy( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + collection: T::CollectionId, witness: DestroyWitness, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { Ok(_) => None, Err(origin) => Some(ensure_signed(origin)?), }; - Class::::try_mutate_exists(class, |maybe_details| { - let class_details = maybe_details.take().ok_or(Error::::Unknown)?; - if let Some(check_owner) = maybe_check_owner { - ensure!(class_details.owner == check_owner, Error::::NoPermission); - } - ensure!(class_details.instances == witness.instances, Error::::BadWitness); - ensure!( - class_details.instance_metadatas == witness.instance_metadatas, - Error::::BadWitness - ); - ensure!(class_details.attributes == witness.attributes, Error::::BadWitness); - - for (instance, details) in Asset::::drain_prefix(&class) { - Account::::remove((&details.owner, &class, &instance)); - } - InstanceMetadataOf::::remove_prefix(&class, None); - ClassMetadataOf::::remove(&class); - Attribute::::remove_prefix((&class,), None); - T::Currency::unreserve(&class_details.owner, class_details.total_deposit); - - Self::deposit_event(Event::Destroyed(class)); - - // NOTE: could use postinfo to reflect the actual number of - // accounts/sufficient/approvals - Ok(()) - }) + let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?; + + Ok(Some(T::WeightInfo::destroy( + details.items, + details.item_metadatas, + details.attributes, + )) + .into()) } - /// Mint an asset instance of a particular class. + /// Mint an item of a particular collection. /// - /// The origin must be Signed and the sender must be the Issuer of the asset `class`. + /// The origin must be Signed and the sender must be the Issuer of the `collection`. /// - /// - `class`: The class of the asset to be minted. - /// - `instance`: The instance value of the asset to be minted. - /// - `beneficiary`: The initial owner of the minted asset. + /// - `collection`: The collection of the item to be minted. + /// - `item`: The item value of the item to be minted. + /// - `beneficiary`: The initial owner of the minted item. /// /// Emits `Issued` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::mint())] pub fn mint( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, - owner: ::Source, + collection: T::CollectionId, + item: T::ItemId, + owner: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - Self::do_mint(class, instance, owner, |class_details| { - ensure!(class_details.issuer == origin, Error::::NoPermission); + Self::do_mint(collection, item, owner, |collection_details| { + ensure!(collection_details.issuer == origin, Error::::NoPermission); Ok(()) }) } - /// Destroy a single asset instance. + /// Destroy a single item. /// - /// Origin must be Signed and the sender should be the Admin of the asset `class`. + /// Origin must be Signed and the sender should be the Admin of the `collection`. /// - /// - `class`: The class of the asset to be burned. - /// - `instance`: The instance of the asset to be burned. + /// - `collection`: The collection of the item to be burned. + /// - `item`: The item of the item to be burned. /// - `check_owner`: If `Some` then the operation will fail with `WrongOwner` unless the - /// asset is owned by this value. + /// item is owned by this value. /// /// Emits `Burned` with the actual amount burned. /// /// Weight: `O(1)` /// Modes: `check_owner.is_some()`. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, - check_owner: Option<::Source>, + collection: T::CollectionId, + item: T::ItemId, + check_owner: Option>, ) -> DispatchResult { let origin = ensure_signed(origin)?; let check_owner = check_owner.map(T::Lookup::lookup).transpose()?; - Self::do_burn(class, instance, |class_details, details| { - let is_permitted = class_details.admin == origin || details.owner == origin; + Self::do_burn(collection, item, |collection_details, details| { + let is_permitted = collection_details.admin == origin || details.owner == origin; ensure!(is_permitted, Error::::NoPermission); ensure!( check_owner.map_or(true, |o| o == details.owner), @@ -475,33 +610,36 @@ pub mod pallet { }) } - /// Move an asset from the sender account to another. + /// Move an item from the sender account to another. + /// + /// This resets the approved account of the item. /// /// Origin must be Signed and the signing account must be either: - /// - the Admin of the asset `class`; - /// - the Owner of the asset `instance`; - /// - the approved delegate for the asset `instance` (in this case, the approval is reset). + /// - the Admin of the `collection`; + /// - the Owner of the `item`; + /// - the approved delegate for the `item` (in this case, the approval is reset). /// /// Arguments: - /// - `class`: The class of the asset to be transferred. - /// - `instance`: The instance of the asset to be transferred. - /// - `dest`: The account to receive ownership of the asset. + /// - `collection`: The collection of the item to be transferred. + /// - `item`: The item of the item to be transferred. + /// - `dest`: The account to receive ownership of the item. /// /// Emits `Transferred`. /// /// Weight: `O(1)` + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, - dest: ::Source, + collection: T::CollectionId, + item: T::ItemId, + dest: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; - Self::do_transfer(class, instance, dest, |class_details, details| { - if details.owner != origin && class_details.admin != origin { + Self::do_transfer(collection, item, dest, |collection_details, details| { + if details.owner != origin && collection_details.admin != origin { let approved = details.approved.take().map_or(false, |i| i == origin); ensure!(approved, Error::::NoPermission); } @@ -509,205 +647,221 @@ pub mod pallet { }) } - /// Reevaluate the deposits on some assets. + /// Reevaluate the deposits on some items. /// - /// Origin must be Signed and the sender should be the Owner of the asset `class`. + /// Origin must be Signed and the sender should be the Owner of the `collection`. /// - /// - `class`: The class of the asset to be frozen. - /// - `instances`: The instances of the asset class whose deposits will be reevaluated. + /// - `collection`: The collection to be frozen. + /// - `items`: The items of the collection whose deposits will be reevaluated. /// - /// NOTE: This exists as a best-effort function. Any asset instances which are unknown or + /// NOTE: This exists as a best-effort function. Any items which are unknown or /// in the case that the owner account does not have reservable funds to pay for a - /// deposit increase are ignored. Generally the owner isn't going to call this on instances + /// deposit increase are ignored. Generally the owner isn't going to call this on items /// whose existing deposit is less than the refreshed deposit as it would only cost them, /// so it's of little consequence. /// - /// It will still return an error in the case that the class is unknown of the signer is - /// not permitted to call it. + /// It will still return an error in the case that the collection is unknown of the signer + /// is not permitted to call it. /// - /// Weight: `O(instances.len())` - #[pallet::weight(T::WeightInfo::redeposit(instances.len() as u32))] + /// Weight: `O(items.len())` + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))] pub fn redeposit( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - instances: Vec, + collection: T::CollectionId, + items: Vec, ) -> DispatchResult { let origin = ensure_signed(origin)?; - let mut class_details = Class::::get(&class).ok_or(Error::::Unknown)?; - ensure!(class_details.owner == origin, Error::::NoPermission); - let deposit = match class_details.free_holding { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.owner == origin, Error::::NoPermission); + let deposit = match collection_details.free_holding { true => Zero::zero(), - false => T::InstanceDeposit::get(), + false => T::ItemDeposit::get(), }; - let mut successful = Vec::with_capacity(instances.len()); - for instance in instances.into_iter() { - let mut details = match Asset::::get(&class, &instance) { + let mut successful = Vec::with_capacity(items.len()); + for item in items.into_iter() { + let mut details = match Item::::get(&collection, &item) { Some(x) => x, None => continue, }; let old = details.deposit; if old > deposit { - T::Currency::unreserve(&class_details.owner, old - deposit); + T::Currency::unreserve(&collection_details.owner, old - deposit); } else if deposit > old { - if T::Currency::reserve(&class_details.owner, deposit - old).is_err() { - // NOTE: No alterations made to class_details in this iteration so far, so - // this is OK to do. + if T::Currency::reserve(&collection_details.owner, deposit - old).is_err() { + // NOTE: No alterations made to collection_details in this iteration so far, + // so this is OK to do. continue } } else { continue } - class_details.total_deposit.saturating_accrue(deposit); - class_details.total_deposit.saturating_reduce(old); + collection_details.total_deposit.saturating_accrue(deposit); + collection_details.total_deposit.saturating_reduce(old); details.deposit = deposit; - Asset::::insert(&class, &instance, &details); - successful.push(instance); + Item::::insert(&collection, &item, &details); + successful.push(item); } - Class::::insert(&class, &class_details); + Collection::::insert(&collection, &collection_details); - Self::deposit_event(Event::::Redeposited(class, successful)); + Self::deposit_event(Event::::Redeposited { + collection, + successful_items: successful, + }); Ok(()) } - /// Disallow further unprivileged transfer of an asset instance. + /// Disallow further unprivileged transfer of an item. /// - /// Origin must be Signed and the sender should be the Freezer of the asset `class`. + /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// - /// - `class`: The class of the asset to be frozen. - /// - `instance`: The instance of the asset to be frozen. + /// - `collection`: The collection of the item to be frozen. + /// - `item`: The item of the item to be frozen. /// /// Emits `Frozen`. /// /// Weight: `O(1)` + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; let mut details = - Asset::::get(&class, &instance).ok_or(Error::::Unknown)?; - let class_details = Class::::get(&class).ok_or(Error::::Unknown)?; - ensure!(class_details.freezer == origin, Error::::NoPermission); + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.freezer == origin, Error::::NoPermission); details.is_frozen = true; - Asset::::insert(&class, &instance, &details); + Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::::Frozen(class, instance)); + Self::deposit_event(Event::::Frozen { collection, item }); Ok(()) } - /// Re-allow unprivileged transfer of an asset instance. + /// Re-allow unprivileged transfer of an item. /// - /// Origin must be Signed and the sender should be the Freezer of the asset `class`. + /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// - /// - `class`: The class of the asset to be thawed. - /// - `instance`: The instance of the asset to be thawed. + /// - `collection`: The collection of the item to be thawed. + /// - `item`: The item of the item to be thawed. /// /// Emits `Thawed`. /// /// Weight: `O(1)` + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::thaw())] pub fn thaw( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; let mut details = - Asset::::get(&class, &instance).ok_or(Error::::Unknown)?; - let class_details = Class::::get(&class).ok_or(Error::::Unknown)?; - ensure!(class_details.admin == origin, Error::::NoPermission); + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.admin == origin, Error::::NoPermission); details.is_frozen = false; - Asset::::insert(&class, &instance, &details); + Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::::Thawed(class, instance)); + Self::deposit_event(Event::::Thawed { collection, item }); Ok(()) } - /// Disallow further unprivileged transfers for a whole asset class. + /// Disallow further unprivileged transfers for a whole collection. /// - /// Origin must be Signed and the sender should be the Freezer of the asset `class`. + /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// - /// - `class`: The asset class to be frozen. + /// - `collection`: The collection to be frozen. /// - /// Emits `ClassFrozen`. + /// Emits `CollectionFrozen`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::freeze_class())] - pub fn freeze_class( + #[pallet::call_index(9)] + #[pallet::weight(T::WeightInfo::freeze_collection())] + pub fn freeze_collection( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + collection: T::CollectionId, ) -> DispatchResult { let origin = ensure_signed(origin)?; - Class::::try_mutate(class, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.freezer, Error::::NoPermission); + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.freezer, Error::::NoPermission); details.is_frozen = true; - Self::deposit_event(Event::::ClassFrozen(class)); + Self::deposit_event(Event::::CollectionFrozen { collection }); Ok(()) }) } - /// Re-allow unprivileged transfers for a whole asset class. + /// Re-allow unprivileged transfers for a whole collection. /// - /// Origin must be Signed and the sender should be the Admin of the asset `class`. + /// Origin must be Signed and the sender should be the Admin of the `collection`. /// - /// - `class`: The class to be thawed. + /// - `collection`: The collection to be thawed. /// - /// Emits `ClassThawed`. + /// Emits `CollectionThawed`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::thaw_class())] - pub fn thaw_class( + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::thaw_collection())] + pub fn thaw_collection( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + collection: T::CollectionId, ) -> DispatchResult { let origin = ensure_signed(origin)?; - Class::::try_mutate(class, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.admin, Error::::NoPermission); + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.admin, Error::::NoPermission); details.is_frozen = false; - Self::deposit_event(Event::::ClassThawed(class)); + Self::deposit_event(Event::::CollectionThawed { collection }); Ok(()) }) } - /// Change the Owner of an asset class. + /// Change the Owner of a collection. /// - /// Origin must be Signed and the sender should be the Owner of the asset `class`. + /// Origin must be Signed and the sender should be the Owner of the `collection`. /// - /// - `class`: The asset class whose owner should be changed. - /// - `owner`: The new Owner of this asset class. + /// - `collection`: The collection whose owner should be changed. + /// - `owner`: The new Owner of this collection. They must have called + /// `set_accept_ownership` with `collection` in order for this operation to succeed. /// /// Emits `OwnerChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - owner: ::Source, + collection: T::CollectionId, + owner: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - Class::::try_mutate(class, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); + let acceptable_collection = OwnershipAcceptance::::get(&owner); + ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); + + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.owner, Error::::NoPermission); if details.owner == owner { return Ok(()) } @@ -719,68 +873,76 @@ pub mod pallet { details.total_deposit, Reserved, )?; + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); details.owner = owner.clone(); + OwnershipAcceptance::::remove(&owner); - Self::deposit_event(Event::OwnerChanged(class, owner)); + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); Ok(()) }) } - /// Change the Issuer, Admin and Freezer of an asset class. + /// Change the Issuer, Admin and Freezer of a collection. /// - /// Origin must be Signed and the sender should be the Owner of the asset `class`. + /// Origin must be Signed and the sender should be the Owner of the `collection`. /// - /// - `class`: The asset class whose team should be changed. - /// - `issuer`: The new Issuer of this asset class. - /// - `admin`: The new Admin of this asset class. - /// - `freezer`: The new Freezer of this asset class. + /// - `collection`: The collection whose team should be changed. + /// - `issuer`: The new Issuer of this collection. + /// - `admin`: The new Admin of this collection. + /// - `freezer`: The new Freezer of this collection. /// /// Emits `TeamChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - issuer: ::Source, - admin: ::Source, - freezer: ::Source, + collection: T::CollectionId, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; let issuer = T::Lookup::lookup(issuer)?; let admin = T::Lookup::lookup(admin)?; let freezer = T::Lookup::lookup(freezer)?; - Class::::try_mutate(class, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.owner, Error::::NoPermission); details.issuer = issuer.clone(); details.admin = admin.clone(); details.freezer = freezer.clone(); - Self::deposit_event(Event::TeamChanged(class, issuer, admin, freezer)); + Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); Ok(()) }) } - /// Approve an instance to be transferred by a delegated third-party account. + /// Approve an item to be transferred by a delegated third-party account. /// - /// Origin must be Signed and must be the owner of the asset `instance`. + /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be + /// either the owner of the `item` or the admin of the collection. /// - /// - `class`: The class of the asset to be approved for delegated transfer. - /// - `instance`: The instance of the asset to be approved for delegated transfer. - /// - `delegate`: The account to delegate permission to transfer the asset. + /// - `collection`: The collection of the item to be approved for delegated transfer. + /// - `item`: The item of the item to be approved for delegated transfer. + /// - `delegate`: The account to delegate permission to transfer the item. + /// + /// Important NOTE: The `approved` account gets reset after each transfer. /// /// Emits `ApprovedTransfer` on success. /// /// Weight: `O(1)` + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, - delegate: ::Source, + collection: T::CollectionId, + item: T::ItemId, + delegate: AccountIdLookupOf, ) -> DispatchResult { let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) @@ -788,56 +950,64 @@ pub mod pallet { let delegate = T::Lookup::lookup(delegate)?; - let class_details = Class::::get(&class).ok_or(Error::::Unknown)?; + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let mut details = - Asset::::get(&class, &instance).ok_or(Error::::Unknown)?; + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; if let Some(check) = maybe_check { - let permitted = &check == &class_details.admin || &check == &details.owner; + let permitted = check == collection_details.admin || check == details.owner; ensure!(permitted, Error::::NoPermission); } details.approved = Some(delegate); - Asset::::insert(&class, &instance, &details); + Item::::insert(&collection, &item, &details); let delegate = details.approved.expect("set as Some above; qed"); - Self::deposit_event(Event::ApprovedTransfer(class, instance, details.owner, delegate)); + Self::deposit_event(Event::ApprovedTransfer { + collection, + item, + owner: details.owner, + delegate, + }); Ok(()) } - /// Cancel the prior approval for the transfer of an asset by a delegate. + /// Cancel the prior approval for the transfer of an item by a delegate. /// /// Origin must be either: /// - the `Force` origin; - /// - `Signed` with the signer being the Admin of the asset `class`; - /// - `Signed` with the signer being the Owner of the asset `instance`; + /// - `Signed` with the signer being the Admin of the `collection`; + /// - `Signed` with the signer being the Owner of the `item`; /// /// Arguments: - /// - `class`: The class of the asset of whose approval will be cancelled. - /// - `instance`: The instance of the asset of whose approval will be cancelled. + /// - `collection`: The collection of the item of whose approval will be cancelled. + /// - `item`: The item of the item of whose approval will be cancelled. /// - `maybe_check_delegate`: If `Some` will ensure that the given account is the one to /// which permission of transfer is delegated. /// /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, - maybe_check_delegate: Option<::Source>, + collection: T::CollectionId, + item: T::ItemId, + maybe_check_delegate: Option>, ) -> DispatchResult { let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let class_details = Class::::get(&class).ok_or(Error::::Unknown)?; + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let mut details = - Asset::::get(&class, &instance).ok_or(Error::::Unknown)?; + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; if let Some(check) = maybe_check { - let permitted = &check == &class_details.admin || &check == &details.owner; + let permitted = check == collection_details.admin || check == details.owner; ensure!(permitted, Error::::NoPermission); } let maybe_check_delegate = maybe_check_delegate.map(T::Lookup::lookup).transpose()?; @@ -846,79 +1016,89 @@ pub mod pallet { ensure!(check_delegate == old, Error::::WrongDelegate); } - Asset::::insert(&class, &instance, &details); - Self::deposit_event(Event::ApprovalCancelled(class, instance, details.owner, old)); + Item::::insert(&collection, &item, &details); + Self::deposit_event(Event::ApprovalCancelled { + collection, + item, + owner: details.owner, + delegate: old, + }); Ok(()) } - /// Alter the attributes of a given asset. + /// Alter the attributes of a given item. /// /// Origin must be `ForceOrigin`. /// - /// - `class`: The identifier of the asset. - /// - `owner`: The new Owner of this asset. - /// - `issuer`: The new Issuer of this asset. - /// - `admin`: The new Admin of this asset. - /// - `freezer`: The new Freezer of this asset. - /// - `free_holding`: Whether a deposit is taken for holding an instance of this asset - /// class. - /// - `is_frozen`: Whether this asset class is frozen except for permissioned/admin + /// - `collection`: The identifier of the item. + /// - `owner`: The new Owner of this item. + /// - `issuer`: The new Issuer of this item. + /// - `admin`: The new Admin of this item. + /// - `freezer`: The new Freezer of this item. + /// - `free_holding`: Whether a deposit is taken for holding an item of this collection. + /// - `is_frozen`: Whether this collection is frozen except for permissioned/admin /// instructions. /// - /// Emits `AssetStatusChanged` with the identity of the asset. + /// Emits `ItemStatusChanged` with the identity of the item. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::force_asset_status())] - pub fn force_asset_status( + #[pallet::call_index(15)] + #[pallet::weight(T::WeightInfo::force_item_status())] + pub fn force_item_status( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - owner: ::Source, - issuer: ::Source, - admin: ::Source, - freezer: ::Source, + collection: T::CollectionId, + owner: AccountIdLookupOf, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, free_holding: bool, is_frozen: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; - Class::::try_mutate(class, |maybe_asset| { - let mut asset = maybe_asset.take().ok_or(Error::::Unknown)?; - asset.owner = T::Lookup::lookup(owner)?; - asset.issuer = T::Lookup::lookup(issuer)?; - asset.admin = T::Lookup::lookup(admin)?; - asset.freezer = T::Lookup::lookup(freezer)?; - asset.free_holding = free_holding; - asset.is_frozen = is_frozen; - *maybe_asset = Some(asset); - - Self::deposit_event(Event::AssetStatusChanged(class)); + Collection::::try_mutate(collection, |maybe_item| { + let mut item = maybe_item.take().ok_or(Error::::UnknownCollection)?; + let old_owner = item.owner; + let new_owner = T::Lookup::lookup(owner)?; + item.owner = new_owner.clone(); + item.issuer = T::Lookup::lookup(issuer)?; + item.admin = T::Lookup::lookup(admin)?; + item.freezer = T::Lookup::lookup(freezer)?; + item.free_holding = free_holding; + item.is_frozen = is_frozen; + *maybe_item = Some(item); + CollectionAccount::::remove(&old_owner, &collection); + CollectionAccount::::insert(&new_owner, &collection, ()); + + Self::deposit_event(Event::ItemStatusChanged { collection }); Ok(()) }) } - /// Set an attribute for an asset class or instance. + /// Set an attribute for a collection or item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// asset `class`. + /// `collection`. /// /// If the origin is Signed, then funds of signer are reserved according to the formula: /// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into /// account any already reserved funds. /// - /// - `class`: The identifier of the asset class whose instance's metadata to set. - /// - `maybe_instance`: The identifier of the asset instance whose metadata to set. + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `maybe_item`: The identifier of the item whose metadata to set. /// - `key`: The key of the attribute. /// - `value`: The value to which to set the attribute. /// /// Emits `AttributeSet`. /// /// Weight: `O(1)` + #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::set_attribute())] pub fn set_attribute( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - maybe_instance: Option, + collection: T::CollectionId, + maybe_item: Option, key: BoundedVec, value: BoundedVec, ) -> DispatchResult { @@ -926,113 +1106,112 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let mut class_details = Class::::get(&class).ok_or(Error::::Unknown)?; + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &class_details.owner, Error::::NoPermission); + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let maybe_is_frozen = match maybe_instance { - None => ClassMetadataOf::::get(class).map(|v| v.is_frozen), - Some(instance) => - InstanceMetadataOf::::get(class, instance).map(|v| v.is_frozen), + let maybe_is_frozen = match maybe_item { + None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), + Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), }; ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); - let attribute = Attribute::::get((class, maybe_instance, &key)); + let attribute = Attribute::::get((collection, maybe_item, &key)); if attribute.is_none() { - class_details.attributes.saturating_inc(); + collection_details.attributes.saturating_inc(); } let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); - class_details.total_deposit.saturating_reduce(old_deposit); + collection_details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if !class_details.free_holding && maybe_check_owner.is_some() { + if !collection_details.free_holding && maybe_check_owner.is_some() { deposit = T::DepositPerByte::get() .saturating_mul(((key.len() + value.len()) as u32).into()) .saturating_add(T::AttributeDepositBase::get()); } - class_details.total_deposit.saturating_accrue(deposit); + collection_details.total_deposit.saturating_accrue(deposit); if deposit > old_deposit { - T::Currency::reserve(&class_details.owner, deposit - old_deposit)?; + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; } else if deposit < old_deposit { - T::Currency::unreserve(&class_details.owner, old_deposit - deposit); + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); } - Attribute::::insert((&class, maybe_instance, &key), (&value, deposit)); - Class::::insert(class, &class_details); - Self::deposit_event(Event::AttributeSet(class, maybe_instance, key, value)); + Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); Ok(()) } - /// Set an attribute for an asset class or instance. + /// Clear an attribute for a collection or item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// asset `class`. + /// `collection`. /// - /// If the origin is Signed, then funds of signer are reserved according to the formula: - /// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into - /// account any already reserved funds. + /// Any deposit is freed for the collection's owner. /// - /// - `class`: The identifier of the asset class whose instance's metadata to set. - /// - `instance`: The identifier of the asset instance whose metadata to set. + /// - `collection`: The identifier of the collection whose item's metadata to clear. + /// - `maybe_item`: The identifier of the item whose metadata to clear. /// - `key`: The key of the attribute. - /// - `value`: The value to which to set the attribute. /// - /// Emits `AttributeSet`. + /// Emits `AttributeCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::clear_attribute())] pub fn clear_attribute( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - maybe_instance: Option, + collection: T::CollectionId, + maybe_item: Option, key: BoundedVec, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let mut class_details = Class::::get(&class).ok_or(Error::::Unknown)?; + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &class_details.owner, Error::::NoPermission); + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let maybe_is_frozen = match maybe_instance { - None => ClassMetadataOf::::get(class).map(|v| v.is_frozen), - Some(instance) => - InstanceMetadataOf::::get(class, instance).map(|v| v.is_frozen), + let maybe_is_frozen = match maybe_item { + None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), + Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), }; ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); - if let Some((_, deposit)) = Attribute::::take((class, maybe_instance, &key)) { - class_details.attributes.saturating_dec(); - class_details.total_deposit.saturating_reduce(deposit); - T::Currency::unreserve(&class_details.owner, deposit); - Class::::insert(class, &class_details); - Self::deposit_event(Event::AttributeCleared(class, maybe_instance, key)); + if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { + collection_details.attributes.saturating_dec(); + collection_details.total_deposit.saturating_reduce(deposit); + T::Currency::unreserve(&collection_details.owner, deposit); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); } Ok(()) } - /// Set the metadata for an asset instance. + /// Set the metadata for an item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// asset `class`. + /// `collection`. /// /// If the origin is Signed, then funds of signer are reserved according to the formula: /// `MetadataDepositBase + DepositPerByte * data.len` taking into /// account any already reserved funds. /// - /// - `class`: The identifier of the asset class whose instance's metadata to set. - /// - `instance`: The identifier of the asset instance whose metadata to set. - /// - `data`: The general information of this asset. Limited in length by `StringLimit`. + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `item`: The identifier of the item whose metadata to set. + /// - `data`: The general information of this item. Limited in length by `StringLimit`. /// - `is_frozen`: Whether the metadata should be frozen against further changes. /// /// Emits `MetadataSet`. /// /// Weight: `O(1)` + #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::set_metadata())] pub fn set_metadata( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, data: BoundedVec, is_frozen: bool, ) -> DispatchResult { @@ -1040,107 +1219,111 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let mut class_details = Class::::get(&class).ok_or(Error::::Unknown)?; + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &class_details.owner, Error::::NoPermission); + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - InstanceMetadataOf::::try_mutate_exists(class, instance, |metadata| { + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); if metadata.is_none() { - class_details.instance_metadatas.saturating_inc(); + collection_details.item_metadatas.saturating_inc(); } let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - class_details.total_deposit.saturating_reduce(old_deposit); + collection_details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if !class_details.free_holding && maybe_check_owner.is_some() { + if !collection_details.free_holding && maybe_check_owner.is_some() { deposit = T::DepositPerByte::get() .saturating_mul(((data.len()) as u32).into()) .saturating_add(T::MetadataDepositBase::get()); } if deposit > old_deposit { - T::Currency::reserve(&class_details.owner, deposit - old_deposit)?; + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; } else if deposit < old_deposit { - T::Currency::unreserve(&class_details.owner, old_deposit - deposit); + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); } - class_details.total_deposit.saturating_accrue(deposit); + collection_details.total_deposit.saturating_accrue(deposit); - *metadata = Some(InstanceMetadata { deposit, data: data.clone(), is_frozen }); + *metadata = Some(ItemMetadata { deposit, data: data.clone(), is_frozen }); - Class::::insert(&class, &class_details); - Self::deposit_event(Event::MetadataSet(class, instance, data, is_frozen)); + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataSet { collection, item, data, is_frozen }); Ok(()) }) } - /// Clear the metadata for an asset instance. + /// Clear the metadata for an item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// asset `instance`. + /// `item`. /// - /// Any deposit is freed for the asset class owner. + /// Any deposit is freed for the collection's owner. /// - /// - `class`: The identifier of the asset class whose instance's metadata to clear. - /// - `instance`: The identifier of the asset instance whose metadata to clear. + /// - `collection`: The identifier of the collection whose item's metadata to clear. + /// - `item`: The identifier of the item whose metadata to clear. /// /// Emits `MetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::clear_metadata())] pub fn clear_metadata( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let mut class_details = Class::::get(&class).ok_or(Error::::Unknown)?; + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &class_details.owner, Error::::NoPermission); + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - InstanceMetadataOf::::try_mutate_exists(class, instance, |metadata| { + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); if metadata.is_some() { - class_details.instance_metadatas.saturating_dec(); + collection_details.item_metadatas.saturating_dec(); } - let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; - T::Currency::unreserve(&class_details.owner, deposit); - class_details.total_deposit.saturating_reduce(deposit); + let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; + T::Currency::unreserve(&collection_details.owner, deposit); + collection_details.total_deposit.saturating_reduce(deposit); - Class::::insert(&class, &class_details); - Self::deposit_event(Event::MetadataCleared(class, instance)); + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataCleared { collection, item }); Ok(()) }) } - /// Set the metadata for an asset class. + /// Set the metadata for a collection. /// /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of - /// the asset `class`. + /// the `collection`. /// /// If the origin is `Signed`, then funds of signer are reserved according to the formula: /// `MetadataDepositBase + DepositPerByte * data.len` taking into /// account any already reserved funds. /// - /// - `class`: The identifier of the asset whose metadata to update. - /// - `data`: The general information of this asset. Limited in length by `StringLimit`. + /// - `collection`: The identifier of the item whose metadata to update. + /// - `data`: The general information of this item. Limited in length by `StringLimit`. /// - `is_frozen`: Whether the metadata should be frozen against further changes. /// - /// Emits `ClassMetadataSet`. + /// Emits `CollectionMetadataSet`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::set_class_metadata())] - pub fn set_class_metadata( + #[pallet::call_index(20)] + #[pallet::weight(T::WeightInfo::set_collection_metadata())] + pub fn set_collection_metadata( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + collection: T::CollectionId, data: BoundedVec, is_frozen: bool, ) -> DispatchResult { @@ -1148,12 +1331,13 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let mut details = Class::::get(&class).ok_or(Error::::Unknown)?; + let mut details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &details.owner, Error::::NoPermission); } - ClassMetadataOf::::try_mutate_exists(class, |metadata| { + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); @@ -1172,50 +1356,175 @@ pub mod pallet { } details.total_deposit.saturating_accrue(deposit); - Class::::insert(&class, details); + Collection::::insert(&collection, details); - *metadata = Some(ClassMetadata { deposit, data: data.clone(), is_frozen }); + *metadata = Some(CollectionMetadata { deposit, data: data.clone(), is_frozen }); - Self::deposit_event(Event::ClassMetadataSet(class, data, is_frozen)); + Self::deposit_event(Event::CollectionMetadataSet { collection, data, is_frozen }); Ok(()) }) } - /// Clear the metadata for an asset class. + /// Clear the metadata for a collection. /// /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of - /// the asset `class`. + /// the `collection`. /// - /// Any deposit is freed for the asset class owner. + /// Any deposit is freed for the collection's owner. /// - /// - `class`: The identifier of the asset class whose metadata to clear. + /// - `collection`: The identifier of the collection whose metadata to clear. /// - /// Emits `ClassMetadataCleared`. + /// Emits `CollectionMetadataCleared`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::clear_class_metadata())] - pub fn clear_class_metadata( + #[pallet::call_index(21)] + #[pallet::weight(T::WeightInfo::clear_collection_metadata())] + pub fn clear_collection_metadata( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + collection: T::CollectionId, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let details = Class::::get(&class).ok_or(Error::::Unknown)?; + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &details.owner, Error::::NoPermission); } - ClassMetadataOf::::try_mutate_exists(class, |metadata| { + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); - let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; + let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; T::Currency::unreserve(&details.owner, deposit); - Self::deposit_event(Event::ClassMetadataCleared(class)); + Self::deposit_event(Event::CollectionMetadataCleared { collection }); Ok(()) }) } + + /// Set (or reset) the acceptance of ownership for a particular account. + /// + /// Origin must be `Signed` and if `maybe_collection` is `Some`, then the signer must have a + /// provider reference. + /// + /// - `maybe_collection`: The identifier of the collection whose ownership the signer is + /// willing to accept, or if `None`, an indication that the signer is willing to accept no + /// ownership transferal. + /// + /// Emits `OwnershipAcceptanceChanged`. + #[pallet::call_index(22)] + #[pallet::weight(T::WeightInfo::set_accept_ownership())] + pub fn set_accept_ownership( + origin: OriginFor, + maybe_collection: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let old = OwnershipAcceptance::::get(&who); + match (old.is_some(), maybe_collection.is_some()) { + (false, true) => { + frame_system::Pallet::::inc_consumers(&who)?; + }, + (true, false) => { + frame_system::Pallet::::dec_consumers(&who); + }, + _ => {}, + } + if let Some(collection) = maybe_collection.as_ref() { + OwnershipAcceptance::::insert(&who, collection); + } else { + OwnershipAcceptance::::remove(&who); + } + Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); + Ok(()) + } + + /// Set the maximum amount of items a collection could have. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// Note: This function can only succeed once per collection. + /// + /// - `collection`: The identifier of the collection to change. + /// - `max_supply`: The maximum amount of items a collection could have. + /// + /// Emits `CollectionMaxSupplySet` event when successful. + #[pallet::call_index(23)] + #[pallet::weight(T::WeightInfo::set_collection_max_supply())] + pub fn set_collection_max_supply( + origin: OriginFor, + collection: T::CollectionId, + max_supply: u32, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + ensure!( + !CollectionMaxSupply::::contains_key(&collection), + Error::::MaxSupplyAlreadySet + ); + + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); + + CollectionMaxSupply::::insert(&collection, max_supply); + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + Ok(()) + } + + /// Set (or reset) the price for an item. + /// + /// Origin must be Signed and must be the owner of the asset `item`. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item to set the price for. + /// - `price`: The price for the item. Pass `None`, to reset the price. + /// - `buyer`: Restricts the buy operation to a specific account. + /// + /// Emits `ItemPriceSet` on success if the price is not `None`. + /// Emits `ItemPriceRemoved` on success if the price is `None`. + #[pallet::call_index(24)] + #[pallet::weight(T::WeightInfo::set_price())] + pub fn set_price( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + price: Option>, + whitelisted_buyer: Option>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?; + Self::do_set_price(collection, item, origin, price, whitelisted_buyer) + } + + /// Allows to buy an item if it's up for sale. + /// + /// Origin must be Signed and must not be the owner of the `item`. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item the sender wants to buy. + /// - `bid_price`: The price the sender is willing to pay. + /// + /// Emits `ItemBought` on success. + #[pallet::call_index(25)] + #[pallet::weight(T::WeightInfo::buy_item())] + #[transactional] + pub fn buy_item( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + bid_price: ItemPrice, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_buy_item(collection, item, origin, bid_price) + } } } diff --git a/frame/uniques/src/migration.rs b/frame/uniques/src/migration.rs new file mode 100644 index 0000000000000..8a2a0ef808d90 --- /dev/null +++ b/frame/uniques/src/migration.rs @@ -0,0 +1,54 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Various pieces of common functionality. +use super::*; +use frame_support::traits::{Get, GetStorageVersion, PalletInfoAccess, StorageVersion}; + +/// Migrate the pallet storage to v1. +pub fn migrate_to_v1, I: 'static, P: GetStorageVersion + PalletInfoAccess>( +) -> frame_support::weights::Weight { + let on_chain_storage_version =

::on_chain_storage_version(); + log::info!( + target: "runtime::uniques", + "Running migration storage v1 for uniques with storage version {:?}", + on_chain_storage_version, + ); + + if on_chain_storage_version < 1 { + let mut count = 0; + for (collection, detail) in Collection::::iter() { + CollectionAccount::::insert(&detail.owner, &collection, ()); + count += 1; + } + StorageVersion::new(1).put::

for Vec

{ fn normalize(&self, targeted_sum: P) -> Result, &'static str> { - let uppers = self - .iter() - .map(|p| >::from(p.clone().deconstruct())) - .collect::>(); + let uppers = self.iter().map(|p| >::from(p.deconstruct())).collect::>(); let normalized = normalize(uppers.as_ref(), >::from(targeted_sum.deconstruct()))?; diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index f388c19de6b43..fc3767761175c 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,11 +24,9 @@ use crate::traits::{ }; use codec::{CompactAs, Encode}; use num_traits::{Pow, SaturatingAdd, SaturatingSub}; -use sp_debug_derive::RuntimeDebug; use sp_std::{ - convert::{TryFrom, TryInto}, fmt, ops, - ops::{Add, Sub}, + ops::{Add, AddAssign, Div, Rem, Sub}, prelude::*, }; @@ -38,6 +36,57 @@ pub type InnerOf

=

::Inner; /// Get the upper type of a `PerThing`. pub type UpperOf

=

::Upper; +pub trait RationalArg: + Clone + + Ord + + ops::Div + + ops::Rem + + ops::Add + + ops::AddAssign + + Unsigned + + Zero + + One +{ +} + +impl< + T: Clone + + Ord + + ops::Div + + ops::Rem + + ops::Add + + ops::AddAssign + + Unsigned + + Zero + + One, + > RationalArg for T +{ +} + +pub trait MultiplyArg: + Clone + + ops::Rem + + ops::Div + + ops::Mul + + ops::Add + + Unsigned +{ +} + +impl< + T: Clone + + ops::Rem + + ops::Div + + ops::Mul + + ops::Add + + Unsigned, + > MultiplyArg for T +{ +} + +pub trait ReciprocalArg: MultiplyArg + Saturating {} +impl ReciprocalArg for T {} + /// Something that implements a fixed point ration with an arbitrary granularity `X`, as _parts per /// `X`_. pub trait PerThing: @@ -91,6 +140,40 @@ pub trait PerThing: self.deconstruct() == Self::ACCURACY } + /// Return the next lower value to `self` or `self` if it is already zero. + fn less_epsilon(self) -> Self { + if self.is_zero() { + return self + } + Self::from_parts(self.deconstruct() - One::one()) + } + + /// Return the next lower value to `self` or an error with the same value if `self` is already + /// zero. + fn try_less_epsilon(self) -> Result { + if self.is_zero() { + return Err(self) + } + Ok(Self::from_parts(self.deconstruct() - One::one())) + } + + /// Return the next higher value to `self` or `self` if it is already one. + fn plus_epsilon(self) -> Self { + if self.is_one() { + return self + } + Self::from_parts(self.deconstruct() + One::one()) + } + + /// Return the next higher value to `self` or an error with the same value if `self` is already + /// one. + fn try_plus_epsilon(self) -> Result { + if self.is_one() { + return Err(self) + } + Ok(Self::from_parts(self.deconstruct() + One::one())) + } + /// Build this type from a percent. Equivalent to `Self::from_parts(x * Self::ACCURACY / 100)` /// but more accurate and can cope with potential type overflows. fn from_percent(x: Self::Inner) -> Self { @@ -128,13 +211,7 @@ pub trait PerThing: /// ``` fn mul_floor(self, b: N) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Rem - + ops::Div - + ops::Mul - + ops::Add - + Unsigned, + N: MultiplyArg + UniqueSaturatedInto, Self::Inner: Into, { overflow_prune_mul::(b, self.deconstruct(), Rounding::Down) @@ -157,13 +234,7 @@ pub trait PerThing: /// ``` fn mul_ceil(self, b: N) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Rem - + ops::Div - + ops::Mul - + ops::Add - + Unsigned, + N: MultiplyArg + UniqueSaturatedInto, Self::Inner: Into, { overflow_prune_mul::(b, self.deconstruct(), Rounding::Up) @@ -180,17 +251,10 @@ pub trait PerThing: /// ``` fn saturating_reciprocal_mul(self, b: N) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Rem - + ops::Div - + ops::Mul - + ops::Add - + Saturating - + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto, Self::Inner: Into, { - saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Nearest) + saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::NearestPrefUp) } /// Saturating multiplication by the reciprocal of `self`. The result is rounded down to the @@ -207,14 +271,7 @@ pub trait PerThing: /// ``` fn saturating_reciprocal_mul_floor(self, b: N) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Rem - + ops::Div - + ops::Mul - + ops::Add - + Saturating - + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto, Self::Inner: Into, { saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Down) @@ -234,14 +291,7 @@ pub trait PerThing: /// ``` fn saturating_reciprocal_mul_ceil(self, b: N) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Rem - + ops::Div - + ops::Mul - + ops::Add - + Saturating - + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto, Self::Inner: Into, { saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Up) @@ -275,53 +325,139 @@ pub trait PerThing: /// ```rust /// # use sp_arithmetic::{Percent, PerThing}; /// # fn main () { - /// // 989/100 is technically closer to 99%. + /// // 989/1000 is technically closer to 99%. /// assert_eq!( - /// Percent::from_rational(989u64, 1000), - /// Percent::from_parts(98), - /// ); + /// Percent::from_rational(989u64, 1000), + /// Percent::from_parts(98), + /// ); /// # } /// ``` fn from_rational(p: N, q: N) -> Self where - N: Clone - + Ord - + TryInto - + TryInto - + ops::Div - + ops::Rem - + ops::Add - + Unsigned, + N: RationalArg + TryInto + TryInto, + Self::Inner: Into, + { + Self::from_rational_with_rounding(p, q, Rounding::Down).unwrap_or_else(|_| Self::one()) + } + + /// Approximate the fraction `p/q` into a per-thing fraction. + /// + /// The computation of this approximation is performed in the generic type `N`. Given + /// `M` as the data type that can hold the maximum value of this per-thing (e.g. `u32` for + /// `Perbill`), this can only work if `N == M` or `N: From + TryInto`. + /// + /// In the case of an overflow (or divide by zero), an `Err` is returned. + /// + /// Rounding is determined by the parameter `rounding`, i.e. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing, Rounding::*}; + /// # fn main () { + /// // 989/100 is technically closer to 99%. + /// assert_eq!( + /// Percent::from_rational_with_rounding(989u64, 1000, Down).unwrap(), + /// Percent::from_parts(98), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(984u64, 1000, NearestPrefUp).unwrap(), + /// Percent::from_parts(98), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(985u64, 1000, NearestPrefDown).unwrap(), + /// Percent::from_parts(98), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(985u64, 1000, NearestPrefUp).unwrap(), + /// Percent::from_parts(99), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(986u64, 1000, NearestPrefDown).unwrap(), + /// Percent::from_parts(99), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(981u64, 1000, Up).unwrap(), + /// Percent::from_parts(99), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(1001u64, 1000, Up), + /// Err(()), + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing, Rounding::*}; + /// # fn main () { + /// assert_eq!( + /// Percent::from_rational_with_rounding(981u64, 1000, Up).unwrap(), + /// Percent::from_parts(99), + /// ); + /// # } + /// ``` + fn from_rational_with_rounding(p: N, q: N, rounding: Rounding) -> Result + where + N: RationalArg + TryInto + TryInto, Self::Inner: Into; /// Same as `Self::from_rational`. #[deprecated = "Use from_rational instead"] fn from_rational_approximation(p: N, q: N) -> Self where - N: Clone - + Ord - + TryInto - + TryInto - + ops::Div - + ops::Rem - + ops::Add - + Unsigned - + Zero - + One, + N: RationalArg + TryInto + TryInto, Self::Inner: Into, { Self::from_rational(p, q) } } -/// The rounding method to use. -/// -/// `PerThing`s are unsigned so `Up` means towards infinity and `Down` means towards zero. -/// `Nearest` will round an exact half down. -enum Rounding { +/// The rounding method to use for unsigned quantities. +#[derive(Copy, Clone, sp_std::fmt::Debug)] +pub enum Rounding { + // Towards infinity. Up, + // Towards zero. Down, - Nearest, + // Nearest integer, rounding as `Up` when equidistant. + NearestPrefUp, + // Nearest integer, rounding as `Down` when equidistant. + NearestPrefDown, +} + +/// The rounding method to use. +#[derive(Copy, Clone, sp_std::fmt::Debug)] +pub enum SignedRounding { + // Towards positive infinity. + High, + // Towards negative infinity. + Low, + // Nearest integer, rounding as `High` when exactly equidistant. + NearestPrefHigh, + // Nearest integer, rounding as `Low` when exactly equidistant. + NearestPrefLow, + // Away from zero (up when positive, down when negative). When positive, equivalent to `High`. + Major, + // Towards zero (down when positive, up when negative). When positive, equivalent to `Low`. + Minor, + // Nearest integer, rounding as `Major` when exactly equidistant. + NearestPrefMajor, + // Nearest integer, rounding as `Minor` when exactly equidistant. + NearestPrefMinor, +} + +impl Rounding { + /// Returns the value for `Rounding` which would give the same result ignorant of the sign. + pub const fn from_signed(rounding: SignedRounding, negative: bool) -> Self { + use Rounding::*; + use SignedRounding::*; + match (rounding, negative) { + (Low, true) | (Major, _) | (High, false) => Up, + (High, true) | (Minor, _) | (Low, false) => Down, + (NearestPrefMajor, _) | (NearestPrefHigh, false) | (NearestPrefLow, true) => + NearestPrefUp, + (NearestPrefMinor, _) | (NearestPrefLow, false) | (NearestPrefHigh, true) => + NearestPrefDown, + } + } } /// Saturating reciprocal multiplication. Compute `x / self`, saturating at the numeric @@ -347,13 +483,7 @@ where /// Overflow-prune multiplication. Accurately multiply a value by `self` without overflowing. fn overflow_prune_mul(x: N, part: P::Inner, rounding: Rounding) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Div - + ops::Mul - + ops::Add - + ops::Rem - + Unsigned, + N: MultiplyArg + UniqueSaturatedInto, P: PerThing, P::Inner: Into, { @@ -369,12 +499,7 @@ where /// to `x / denom * numer` for an accurate result. fn rational_mul_correction(x: N, numer: P::Inner, denom: P::Inner, rounding: Rounding) -> N where - N: UniqueSaturatedInto - + ops::Div - + ops::Mul - + ops::Add - + ops::Rem - + Unsigned, + N: MultiplyArg + UniqueSaturatedInto, P: PerThing, P::Inner: Into, { @@ -399,18 +524,53 @@ where rem_mul_div_inner += 1.into(); } }, - // Round up if the fractional part of the result is greater than a half. An exact half is - // rounded down. - Rounding::Nearest => { + Rounding::NearestPrefDown => if rem_mul_upper % denom_upper > denom_upper / 2.into() { // `rem * numer / denom` is less than `numer`, so this will not overflow. rem_mul_div_inner += 1.into(); - } - }, + }, + Rounding::NearestPrefUp => + if rem_mul_upper % denom_upper >= denom_upper / 2.into() + denom_upper % 2.into() { + // `rem * numer / denom` is less than `numer`, so this will not overflow. + rem_mul_div_inner += 1.into(); + }, } rem_mul_div_inner.into() } +/// Just a simple generic integer divide with custom rounding. +fn div_rounded(n: N, d: N, r: Rounding) -> N +where + N: Clone + + Eq + + Ord + + Zero + + One + + AddAssign + + Add + + Rem + + Div, +{ + let mut o = n.clone() / d.clone(); + use Rounding::*; + let two = || N::one() + N::one(); + if match r { + Up => !((n % d).is_zero()), + NearestPrefDown => { + let rem = n % d.clone(); + rem > d / two() + }, + NearestPrefUp => { + let rem = n % d.clone(); + rem >= d.clone() / two() + d % two() + }, + Down => false, + } { + o += N::one() + } + o +} + macro_rules! implement_per_thing { ( $name:ident, @@ -425,7 +585,7 @@ macro_rules! implement_per_thing { /// #[doc = $title] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] - #[derive(Encode, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, scale_info::TypeInfo)] + #[derive(Encode, Copy, Clone, PartialEq, Eq, codec::MaxEncodedLen, PartialOrd, Ord, scale_info::TypeInfo)] pub struct $name($type); /// Implementation makes any compact encoding of `PerThing::Inner` valid, @@ -447,6 +607,56 @@ macro_rules! implement_per_thing { } } + #[cfg(feature = "std")] + impl sp_std::fmt::Debug for $name { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + if $max == <$type>::max_value() { + // Not a power of ten: show as N/D and approx % + let pc = (self.0 as f64) / (self.0 as f64) * 100f64; + write!(fmt, "{:.2}% ({}/{})", pc, self.0, $max) + } else { + // A power of ten: calculate exact percent + let divisor = $max / 100; + let units = self.0 / divisor; + let rest = self.0 % divisor; + write!(fmt, "{}", units)?; + if rest > 0 { + write!(fmt, ".")?; + let mut m = $max / 100; + while rest % m > 0 { + m /= 10; + write!(fmt, "{:01}", rest / m % 10)?; + } + } + write!(fmt, "%") + } + } + } + + #[cfg(not(feature = "std"))] + impl sp_std::fmt::Debug for $name { + fn fmt(&self, fmt: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + if $max == <$type>::max_value() { + // Not a power of ten: show as N/D and approx % + write!(fmt, "{}/{}", self.0, $max) + } else { + // A power of ten: calculate exact percent + let units = self.0 / ($max / 100); + let rest = self.0 % ($max / 100); + write!(fmt, "{}", units)?; + if rest > 0 { + write!(fmt, ".")?; + let mut m = $max / 100; + while rest % m > 0 { + m /= 10; + write!(fmt, "{:01}", rest / m % 10)?; + } + } + write!(fmt, "%") + } + } + } + impl PerThing for $name { type Inner = $type; type Upper = $upper_type; @@ -465,53 +675,50 @@ macro_rules! implement_per_thing { Self::from_parts((x.max(0.).min(1.) * $max as f64) as Self::Inner) } - fn from_rational(p: N, q: N) -> Self + fn from_rational_with_rounding(p: N, q: N, r: Rounding) -> Result where - N: Clone + Ord + TryInto + TryInto - + ops::Div + ops::Rem + ops::Add + Unsigned - + Zero + One, - Self::Inner: Into, + N: Clone + + Ord + + TryInto + + TryInto + + ops::Div + + ops::Rem + + ops::Add + + ops::AddAssign + + Unsigned + + Zero + + One, + Self::Inner: Into { - let div_ceil = |x: N, f: N| -> N { - let mut o = x.clone() / f.clone(); - let r = x.rem(f.clone()); - if r > N::zero() { - o = o + N::one(); - } - o - }; - // q cannot be zero. - let q: N = q.max((1 as Self::Inner).into()); + if q.is_zero() { return Err(()) } // p should not be bigger than q. - let p: N = p.min(q.clone()); + if p > q { return Err(()) } - let factor: N = div_ceil(q.clone(), $max.into()).max((1 as Self::Inner).into()); + let factor = div_rounded::(q.clone(), $max.into(), Rounding::Up).max(One::one()); // q cannot overflow: (q / (q/$max)) < $max. p < q hence p also cannot overflow. - let q_reduce: $type = (q.clone() / factor.clone()) + let q_reduce: $type = div_rounded(q, factor.clone(), r) .try_into() .map_err(|_| "Failed to convert") .expect( - "q / ceil(q/$max) < $max. Macro prevents any type being created that \ + "`q / ceil(q/$max) < $max`; macro prevents any type being created that \ does not satisfy this; qed" ); - let p_reduce: $type = (p / factor) + let p_reduce: $type = div_rounded(p, factor, r) .try_into() .map_err(|_| "Failed to convert") .expect( - "q / ceil(q/$max) < $max. Macro prevents any type being created that \ + "`p / ceil(p/$max) < $max`; macro prevents any type being created that \ does not satisfy this; qed" ); - // `p_reduced` and `q_reduced` are withing Self::Inner. Mul by another $max will - // always fit in $upper_type. This is guaranteed by the macro tests. - let part = - p_reduce as $upper_type - * <$upper_type>::from($max) - / q_reduce as $upper_type; - - $name(part as Self::Inner) + // `p_reduced` and `q_reduced` are within `Self::Inner`. Multiplication by another + // `$max` will always fit in `$upper_type`. This is guaranteed by the macro tests. + let n = p_reduce as $upper_type * <$upper_type>::from($max); + let d = q_reduce as $upper_type; + let part = div_rounded(n, d, r); + Ok($name(part as Self::Inner)) } } @@ -572,30 +779,36 @@ macro_rules! implement_per_thing { /// See [`PerThing::from_rational`]. #[deprecated = "Use `PerThing::from_rational` instead"] pub fn from_rational_approximation(p: N, q: N) -> Self - where N: Clone + Ord + TryInto<$type> + - TryInto<$upper_type> + ops::Div + ops::Rem + - ops::Add + Unsigned, - $type: Into, + where + N: RationalArg+ TryInto<$type> + TryInto<$upper_type>, + $type: Into { ::from_rational(p, q) } /// See [`PerThing::from_rational`]. pub fn from_rational(p: N, q: N) -> Self - where N: Clone + Ord + TryInto<$type> + - TryInto<$upper_type> + ops::Div + ops::Rem + - ops::Add + Unsigned, - $type: Into, + where + N: RationalArg+ TryInto<$type> + TryInto<$upper_type>, + $type: Into { ::from_rational(p, q) } + /// Integer multiplication with another value, saturating at 1. + pub fn int_mul(self, b: $type) -> Self { + PerThing::from_parts(self.0.saturating_mul(b)) + } + + /// Integer division with another value, rounding down. + pub fn int_div(self, b: Self) -> $type { + self.0 / b.0 + } + /// See [`PerThing::mul_floor`]. pub fn mul_floor(self, b: N) -> N where - N: Clone + UniqueSaturatedInto<$type> + - ops::Rem + ops::Div + ops::Mul + - ops::Add + Unsigned, + N: MultiplyArg + UniqueSaturatedInto<$type>, $type: Into, { @@ -605,9 +818,7 @@ macro_rules! implement_per_thing { /// See [`PerThing::mul_ceil`]. pub fn mul_ceil(self, b: N) -> N where - N: Clone + UniqueSaturatedInto<$type> + - ops::Rem + ops::Div + ops::Mul + - ops::Add + Unsigned, + N: MultiplyArg + UniqueSaturatedInto<$type>, $type: Into, { PerThing::mul_ceil(self, b) @@ -616,9 +827,7 @@ macro_rules! implement_per_thing { /// See [`PerThing::saturating_reciprocal_mul`]. pub fn saturating_reciprocal_mul(self, b: N) -> N where - N: Clone + UniqueSaturatedInto<$type> + ops::Rem + - ops::Div + ops::Mul + ops::Add + - Saturating + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto<$type>, $type: Into, { PerThing::saturating_reciprocal_mul(self, b) @@ -627,9 +836,7 @@ macro_rules! implement_per_thing { /// See [`PerThing::saturating_reciprocal_mul_floor`]. pub fn saturating_reciprocal_mul_floor(self, b: N) -> N where - N: Clone + UniqueSaturatedInto<$type> + ops::Rem + - ops::Div + ops::Mul + ops::Add + - Saturating + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto<$type>, $type: Into, { PerThing::saturating_reciprocal_mul_floor(self, b) @@ -638,13 +845,43 @@ macro_rules! implement_per_thing { /// See [`PerThing::saturating_reciprocal_mul_ceil`]. pub fn saturating_reciprocal_mul_ceil(self, b: N) -> N where - N: Clone + UniqueSaturatedInto<$type> + ops::Rem + - ops::Div + ops::Mul + ops::Add + - Saturating + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto<$type>, $type: Into, { PerThing::saturating_reciprocal_mul_ceil(self, b) } + + /// Saturating division. Compute `self / rhs`, saturating at one if `rhs < self`. + /// + /// The `rounding` method must be specified. e.g.: + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing, Rounding::*}; + /// # fn main () { + /// let pc = |x| Percent::from_percent(x); + /// assert_eq!( + /// pc(2).saturating_div(pc(3), Down), + /// pc(66), + /// ); + /// assert_eq!( + /// pc(1).saturating_div(pc(3), NearestPrefUp), + /// pc(33), + /// ); + /// assert_eq!( + /// pc(2).saturating_div(pc(3), NearestPrefDown), + /// pc(67), + /// ); + /// assert_eq!( + /// pc(1).saturating_div(pc(3), Up), + /// pc(34), + /// ); + /// # } + /// ``` + pub fn saturating_div(self, rhs: Self, r: Rounding) -> Self { + let p = self.0; + let q = rhs.0; + Self::from_rational_with_rounding(p, q, r).unwrap_or_else(|_| Self::one()) + } } impl Saturating for $name { @@ -758,7 +995,7 @@ macro_rules! implement_per_thing { { type Output = N; fn mul(self, b: N) -> Self::Output { - overflow_prune_mul::(b, self.deconstruct(), Rounding::Nearest) + overflow_prune_mul::(b, self.deconstruct(), Rounding::NearestPrefDown) } } @@ -833,11 +1070,26 @@ macro_rules! implement_per_thing { } } + impl $crate::traits::Zero for $name { + fn zero() -> Self { + Self::zero() + } + + fn is_zero(&self) -> bool { + self == &Self::zero() + } + } + + impl $crate::traits::One for $name { + fn one() -> Self { + Self::one() + } + } #[cfg(test)] mod $test_mod { use codec::{Encode, Decode}; - use super::{$name, Saturating, RuntimeDebug, PerThing}; + use super::{$name, Saturating, PerThing}; use crate::traits::Zero; #[test] @@ -861,7 +1113,7 @@ macro_rules! implement_per_thing { assert!(<$upper_type>::from($max) * <$upper_type>::from($max) < <$upper_type>::max_value()); } - #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug)] + #[derive(Encode, Decode, PartialEq, Eq, Debug)] struct WithCompact { data: T, } @@ -895,6 +1147,20 @@ macro_rules! implement_per_thing { } } + #[test] + fn from_parts_cannot_overflow() { + assert_eq!(<$name>::from_parts($max.saturating_add(1)), <$name>::one()); + } + + #[test] + fn has_max_encoded_len() { + struct AsMaxEncodedLen { + _data: T, + } + + let _ = AsMaxEncodedLen { _data: $name(1) }; + } + #[test] fn fail_on_invalid_encoded_value() { let value = <$upper_type>::from($max) * 2; @@ -1023,10 +1289,10 @@ macro_rules! implement_per_thing { #[test] fn per_thing_mul_rounds_to_nearest_number() { - assert_eq!($name::from_float(0.33) * 10u64, 3); - assert_eq!($name::from_float(0.34) * 10u64, 3); - assert_eq!($name::from_float(0.35) * 10u64, 3); - assert_eq!($name::from_float(0.36) * 10u64, 4); + assert_eq!($name::from_percent(33) * 10u64, 3); + assert_eq!($name::from_percent(34) * 10u64, 3); + assert_eq!($name::from_percent(35) * 10u64, 3); + assert_eq!($name::from_percent(36) * 10u64, 4); } #[test] @@ -1334,7 +1600,7 @@ macro_rules! implement_per_thing { <$type>::max_value(), <$type>::max_value(), <$type>::max_value(), - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), 0, ); @@ -1343,7 +1609,7 @@ macro_rules! implement_per_thing { <$type>::max_value() - 1, <$type>::max_value(), <$type>::max_value(), - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), <$type>::max_value() - 1, ); @@ -1352,7 +1618,7 @@ macro_rules! implement_per_thing { ((<$type>::max_value() - 1) as $upper_type).pow(2), <$type>::max_value(), <$type>::max_value(), - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), 1, ); @@ -1362,7 +1628,7 @@ macro_rules! implement_per_thing { (<$type>::max_value() as $upper_type).pow(2) - 1, <$type>::max_value(), <$type>::max_value(), - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), <$upper_type>::from((<$type>::max_value() - 1)), ); @@ -1372,7 +1638,7 @@ macro_rules! implement_per_thing { (<$type>::max_value() as $upper_type).pow(2), <$type>::max_value(), 2 as $type, - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), <$type>::max_value() as $upper_type / 2, ); @@ -1382,7 +1648,7 @@ macro_rules! implement_per_thing { (<$type>::max_value() as $upper_type).pow(2) - 1, 2 as $type, <$type>::max_value(), - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), 2, ); @@ -1569,6 +1835,33 @@ macro_rules! implement_per_thing_with_perthousand { } } +#[test] +fn from_rational_with_rounding_works_in_extreme_case() { + use Rounding::*; + for &r in [Down, NearestPrefDown, NearestPrefUp, Up].iter() { + Percent::from_rational_with_rounding(1, u64::max_value(), r).unwrap(); + Percent::from_rational_with_rounding(1, u32::max_value(), r).unwrap(); + Percent::from_rational_with_rounding(1, u16::max_value(), r).unwrap(); + Percent::from_rational_with_rounding(u64::max_value() - 1, u64::max_value(), r).unwrap(); + Percent::from_rational_with_rounding(u32::max_value() - 1, u32::max_value(), r).unwrap(); + Percent::from_rational_with_rounding(u16::max_value() - 1, u16::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(1, u64::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(1, u32::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(1, u16::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(u64::max_value() - 1, u64::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(u32::max_value() - 1, u32::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(u16::max_value() - 1, u16::max_value(), r).unwrap(); + Permill::from_rational_with_rounding(1, u64::max_value(), r).unwrap(); + Permill::from_rational_with_rounding(1, u32::max_value(), r).unwrap(); + Permill::from_rational_with_rounding(u64::max_value() - 1, u64::max_value(), r).unwrap(); + Permill::from_rational_with_rounding(u32::max_value() - 1, u32::max_value(), r).unwrap(); + Perbill::from_rational_with_rounding(1, u64::max_value(), r).unwrap(); + Perbill::from_rational_with_rounding(1, u32::max_value(), r).unwrap(); + Perbill::from_rational_with_rounding(u64::max_value() - 1, u64::max_value(), r).unwrap(); + Perbill::from_rational_with_rounding(u32::max_value() - 1, u32::max_value(), r).unwrap(); + } +} + implement_per_thing!(Percent, test_per_cent, [u32, u64, u128], 100u8, u8, u16, "_Percent_",); implement_per_thing_with_perthousand!( PerU16, diff --git a/primitives/arithmetic/src/rational.rs b/primitives/arithmetic/src/rational.rs index 225e1d9521827..54cabfc6214e8 100644 --- a/primitives/arithmetic/src/rational.rs +++ b/primitives/arithmetic/src/rational.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{biguint::BigUint, helpers_128bit}; +use crate::{biguint::BigUint, helpers_128bit, Rounding}; use num_traits::{Bounded, One, Zero}; use sp_std::{cmp::Ordering, prelude::*}; @@ -63,14 +63,14 @@ impl Ord for RationalInfinite { fn cmp(&self, other: &Self) -> Ordering { // handle some edge cases. if self.d() == other.d() { - self.n().cmp(&other.n()) + self.n().cmp(other.n()) } else if self.d().is_zero() { Ordering::Greater } else if other.d().is_zero() { Ordering::Less } else { // (a/b) cmp (c/d) => (a*d) cmp (c*b) - self.n().clone().mul(&other.d()).cmp(&other.n().clone().mul(&self.d())) + self.n().clone().mul(other.d()).cmp(&other.n().clone().mul(self.d())) } } } @@ -143,27 +143,38 @@ impl Rational128 { /// Convert `self` to a similar rational number where denominator is the given `den`. // - /// This only returns if the result is accurate. `Err` is returned if the result cannot be + /// This only returns if the result is accurate. `None` is returned if the result cannot be /// accurately calculated. - pub fn to_den(self, den: u128) -> Result { + pub fn to_den(self, den: u128) -> Option { if den == self.1 { - Ok(self) + Some(self) } else { - helpers_128bit::multiply_by_rational(self.0, den, self.1).map(|n| Self(n, den)) + helpers_128bit::multiply_by_rational_with_rounding( + self.0, + den, + self.1, + Rounding::NearestPrefDown, + ) + .map(|n| Self(n, den)) } } /// Get the least common divisor of `self` and `other`. /// - /// This only returns if the result is accurate. `Err` is returned if the result cannot be + /// This only returns if the result is accurate. `None` is returned if the result cannot be /// accurately calculated. - pub fn lcm(&self, other: &Self) -> Result { + pub fn lcm(&self, other: &Self) -> Option { // this should be tested better: two large numbers that are almost the same. if self.1 == other.1 { - return Ok(self.1) + return Some(self.1) } let g = helpers_128bit::gcd(self.1, other.1); - helpers_128bit::multiply_by_rational(self.1, other.1, g) + helpers_128bit::multiply_by_rational_with_rounding( + self.1, + other.1, + g, + Rounding::NearestPrefDown, + ) } /// A saturating add that assumes `self` and `other` have the same denominator. @@ -188,9 +199,11 @@ impl Rational128 { /// /// Overflow might happen during any of the steps. Error is returned in such cases. pub fn checked_add(self, other: Self) -> Result { - let lcm = self.lcm(&other).map_err(|_| "failed to scale to denominator")?; - let self_scaled = self.to_den(lcm).map_err(|_| "failed to scale to denominator")?; - let other_scaled = other.to_den(lcm).map_err(|_| "failed to scale to denominator")?; + let lcm = self.lcm(&other).ok_or(0).map_err(|_| "failed to scale to denominator")?; + let self_scaled = + self.to_den(lcm).ok_or(0).map_err(|_| "failed to scale to denominator")?; + let other_scaled = + other.to_den(lcm).ok_or(0).map_err(|_| "failed to scale to denominator")?; let n = self_scaled .0 .checked_add(other_scaled.0) @@ -202,9 +215,11 @@ impl Rational128 { /// /// Overflow might happen during any of the steps. None is returned in such cases. pub fn checked_sub(self, other: Self) -> Result { - let lcm = self.lcm(&other).map_err(|_| "failed to scale to denominator")?; - let self_scaled = self.to_den(lcm).map_err(|_| "failed to scale to denominator")?; - let other_scaled = other.to_den(lcm).map_err(|_| "failed to scale to denominator")?; + let lcm = self.lcm(&other).ok_or(0).map_err(|_| "failed to scale to denominator")?; + let self_scaled = + self.to_den(lcm).ok_or(0).map_err(|_| "failed to scale to denominator")?; + let other_scaled = + other.to_den(lcm).ok_or(0).map_err(|_| "failed to scale to denominator")?; let n = self_scaled .0 @@ -272,6 +287,7 @@ impl PartialEq for Rational128 { #[cfg(test)] mod tests { use super::{helpers_128bit::*, *}; + use static_assertions::const_assert; const MAX128: u128 = u128::MAX; const MAX64: u128 = u64::MAX as u128; @@ -313,18 +329,18 @@ mod tests { #[test] fn to_denom_works() { // simple up and down - assert_eq!(r(1, 5).to_den(10), Ok(r(2, 10))); - assert_eq!(r(4, 10).to_den(5), Ok(r(2, 5))); + assert_eq!(r(1, 5).to_den(10), Some(r(2, 10))); + assert_eq!(r(4, 10).to_den(5), Some(r(2, 5))); // up and down with large numbers - assert_eq!(r(MAX128 - 10, MAX128).to_den(10), Ok(r(10, 10))); - assert_eq!(r(MAX128 / 2, MAX128).to_den(10), Ok(r(5, 10))); + assert_eq!(r(MAX128 - 10, MAX128).to_den(10), Some(r(10, 10))); + assert_eq!(r(MAX128 / 2, MAX128).to_den(10), Some(r(5, 10))); // large to perbill. This is very well needed for npos-elections. - assert_eq!(r(MAX128 / 2, MAX128).to_den(1000_000_000), Ok(r(500_000_000, 1000_000_000))); + assert_eq!(r(MAX128 / 2, MAX128).to_den(1000_000_000), Some(r(500_000_000, 1000_000_000))); // large to large - assert_eq!(r(MAX128 / 2, MAX128).to_den(MAX128 / 2), Ok(r(MAX128 / 4, MAX128 / 2))); + assert_eq!(r(MAX128 / 2, MAX128).to_den(MAX128 / 2), Some(r(MAX128 / 4, MAX128 / 2))); } #[test] @@ -341,16 +357,13 @@ mod tests { assert_eq!(r(5, 30).lcm(&r(1, 10)).unwrap(), 30); // large numbers - assert_eq!( - r(1_000_000_000, MAX128).lcm(&r(7_000_000_000, MAX128 - 1)), - Err("result cannot fit in u128"), - ); + assert_eq!(r(1_000_000_000, MAX128).lcm(&r(7_000_000_000, MAX128 - 1)), None,); assert_eq!( r(1_000_000_000, MAX64).lcm(&r(7_000_000_000, MAX64 - 1)), - Ok(340282366920938463408034375210639556610), + Some(340282366920938463408034375210639556610), ); - assert!(340282366920938463408034375210639556610 < MAX128); - assert!(340282366920938463408034375210639556610 == MAX64 * (MAX64 - 1)); + const_assert!(340282366920938463408034375210639556610 < MAX128); + const_assert!(340282366920938463408034375210639556610 == MAX64 * (MAX64 - 1)); } #[test] @@ -407,55 +420,87 @@ mod tests { } #[test] - fn multiply_by_rational_works() { - assert_eq!(multiply_by_rational(7, 2, 3).unwrap(), 7 * 2 / 3); - assert_eq!(multiply_by_rational(7, 20, 30).unwrap(), 7 * 2 / 3); - assert_eq!(multiply_by_rational(20, 7, 30).unwrap(), 7 * 2 / 3); + fn multiply_by_rational_with_rounding_works() { + assert_eq!(multiply_by_rational_with_rounding(7, 2, 3, Rounding::Down).unwrap(), 7 * 2 / 3); + assert_eq!( + multiply_by_rational_with_rounding(7, 20, 30, Rounding::Down).unwrap(), + 7 * 2 / 3 + ); + assert_eq!( + multiply_by_rational_with_rounding(20, 7, 30, Rounding::Down).unwrap(), + 7 * 2 / 3 + ); assert_eq!( // MAX128 % 3 == 0 - multiply_by_rational(MAX128, 2, 3).unwrap(), + multiply_by_rational_with_rounding(MAX128, 2, 3, Rounding::Down).unwrap(), MAX128 / 3 * 2, ); assert_eq!( // MAX128 % 7 == 3 - multiply_by_rational(MAX128, 5, 7).unwrap(), + multiply_by_rational_with_rounding(MAX128, 5, 7, Rounding::Down).unwrap(), (MAX128 / 7 * 5) + (3 * 5 / 7), ); assert_eq!( // MAX128 % 7 == 3 - multiply_by_rational(MAX128, 11, 13).unwrap(), + multiply_by_rational_with_rounding(MAX128, 11, 13, Rounding::Down).unwrap(), (MAX128 / 13 * 11) + (8 * 11 / 13), ); assert_eq!( // MAX128 % 1000 == 455 - multiply_by_rational(MAX128, 555, 1000).unwrap(), + multiply_by_rational_with_rounding(MAX128, 555, 1000, Rounding::Down).unwrap(), (MAX128 / 1000 * 555) + (455 * 555 / 1000), ); - assert_eq!(multiply_by_rational(2 * MAX64 - 1, MAX64, MAX64).unwrap(), 2 * MAX64 - 1); - assert_eq!(multiply_by_rational(2 * MAX64 - 1, MAX64 - 1, MAX64).unwrap(), 2 * MAX64 - 3); + assert_eq!( + multiply_by_rational_with_rounding(2 * MAX64 - 1, MAX64, MAX64, Rounding::Down) + .unwrap(), + 2 * MAX64 - 1 + ); + assert_eq!( + multiply_by_rational_with_rounding(2 * MAX64 - 1, MAX64 - 1, MAX64, Rounding::Down) + .unwrap(), + 2 * MAX64 - 3 + ); assert_eq!( - multiply_by_rational(MAX64 + 100, MAX64_2, MAX64_2 / 2).unwrap(), + multiply_by_rational_with_rounding(MAX64 + 100, MAX64_2, MAX64_2 / 2, Rounding::Down) + .unwrap(), (MAX64 + 100) * 2, ); assert_eq!( - multiply_by_rational(MAX64 + 100, MAX64_2 / 100, MAX64_2 / 200).unwrap(), + multiply_by_rational_with_rounding( + MAX64 + 100, + MAX64_2 / 100, + MAX64_2 / 200, + Rounding::Down + ) + .unwrap(), (MAX64 + 100) * 2, ); assert_eq!( - multiply_by_rational(2u128.pow(66) - 1, 2u128.pow(65) - 1, 2u128.pow(65)).unwrap(), + multiply_by_rational_with_rounding( + 2u128.pow(66) - 1, + 2u128.pow(65) - 1, + 2u128.pow(65), + Rounding::Down + ) + .unwrap(), 73786976294838206461, ); - assert_eq!(multiply_by_rational(1_000_000_000, MAX128 / 8, MAX128 / 2).unwrap(), 250000000); + assert_eq!( + multiply_by_rational_with_rounding(1_000_000_000, MAX128 / 8, MAX128 / 2, Rounding::Up) + .unwrap(), + 250000000 + ); assert_eq!( - multiply_by_rational( + multiply_by_rational_with_rounding( 29459999999999999988000u128, 1000000000000000000u128, - 10000000000000000000u128 + 10000000000000000000u128, + Rounding::Down ) .unwrap(), 2945999999999999998800u128 @@ -463,17 +508,28 @@ mod tests { } #[test] - fn multiply_by_rational_a_b_are_interchangeable() { - assert_eq!(multiply_by_rational(10, MAX128, MAX128 / 2), Ok(20)); - assert_eq!(multiply_by_rational(MAX128, 10, MAX128 / 2), Ok(20)); + fn multiply_by_rational_with_rounding_a_b_are_interchangeable() { + assert_eq!( + multiply_by_rational_with_rounding(10, MAX128, MAX128 / 2, Rounding::NearestPrefDown), + Some(20) + ); + assert_eq!( + multiply_by_rational_with_rounding(MAX128, 10, MAX128 / 2, Rounding::NearestPrefDown), + Some(20) + ); } #[test] #[ignore] - fn multiply_by_rational_fuzzed_equation() { + fn multiply_by_rational_with_rounding_fuzzed_equation() { assert_eq!( - multiply_by_rational(154742576605164960401588224, 9223376310179529214, 549756068598), - Ok(2596149632101417846585204209223679) + multiply_by_rational_with_rounding( + 154742576605164960401588224, + 9223376310179529214, + 549756068598, + Rounding::NearestPrefDown + ), + Some(2596149632101417846585204209223679) ); } } diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 53341117b1fee..466d5696c7136 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,12 +23,8 @@ pub use num_traits::{ checked_pow, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, One, Signed, Unsigned, Zero, }; -use sp_std::{ - self, - convert::{TryFrom, TryInto}, - ops::{ - Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shl, Shr, Sub, SubAssign, - }, +use sp_std::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shl, Shr, Sub, SubAssign, }; /// A meta trait for arithmetic type operations, regardless of any limitation on size. @@ -62,6 +58,7 @@ pub trait BaseArithmetic: + Bounded + HasCompact + Sized + + Clone + TryFrom + TryInto + TryFrom @@ -117,6 +114,7 @@ impl< + Bounded + HasCompact + Sized + + Clone + TryFrom + TryInto + TryFrom diff --git a/primitives/authority-discovery/Cargo.toml b/primitives/authority-discovery/Cargo.toml index 6638e478b4cd7..4b450a4da4a88 100644 --- a/primitives/authority-discovery/Cargo.toml +++ b/primitives/authority-discovery/Cargo.toml @@ -3,9 +3,9 @@ name = "sp-authority-discovery" version = "4.0.0-dev" authors = ["Parity Technologies "] description = "Authority discovery primitives" -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,20 +13,20 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../application-crypto" } -codec = { package = "parity-scale-codec", default-features = false, version = "2.0.0" } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = ["std"] std = [ - "sp-application-crypto/std", "codec/std", "scale-info/std", - "sp-std/std", "sp-api/std", - "sp-runtime/std" + "sp-application-crypto/std", + "sp-runtime/std", + "sp-std/std", ] diff --git a/primitives/authority-discovery/src/lib.rs b/primitives/authority-discovery/src/lib.rs index 871a35e6bf487..95bb458b1be8c 100644 --- a/primitives/authority-discovery/src/lib.rs +++ b/primitives/authority-discovery/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/authorship/Cargo.toml b/primitives/authorship/Cargo.toml index 15e4dc57ff5ab..49107ebed1db0 100644 --- a/primitives/authorship/Cargo.toml +++ b/primitives/authorship/Cargo.toml @@ -3,9 +3,9 @@ name = "sp-authorship" version = "4.0.0-dev" authors = ["Parity Technologies "] description = "Authorship primitives" -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,18 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = { version = "0.1.57", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -async-trait = { version = "0.1.50", optional = true } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] std = [ + "async-trait", "codec/std", - "sp-std/std", "sp-inherents/std", "sp-runtime/std", - "async-trait", + "sp-std/std", ] diff --git a/primitives/authorship/src/lib.rs b/primitives/authorship/src/lib.rs index ac4b5fd315dc2..6ff6607a896cc 100644 --- a/primitives/authorship/src/lib.rs +++ b/primitives/authorship/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -81,20 +81,20 @@ impl InherentDataProvider { #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { inherent_data.put_data(INHERENT_IDENTIFIER, &self.uncles) } async fn try_handle_error( &self, identifier: &InherentIdentifier, - error: &[u8], + mut error: &[u8], ) -> Option> { if *identifier != INHERENT_IDENTIFIER { return None } - let error = InherentError::decode(&mut &error[..]).ok()?; + let error = InherentError::decode(&mut error).ok()?; Some(Err(Error::Application(Box::from(format!("{:?}", error))))) } diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml new file mode 100644 index 0000000000000..b286d9878b44e --- /dev/null +++ b/primitives/beefy/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "sp-beefy" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate" +description = "Primitives for BEEFY protocol." + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-io = { version = "7.0.0", default-features = false, path = "../io" } +sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../merkle-mountain-range" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } + +[dev-dependencies] +array-bytes = "4.1" +sp-keystore = { version = "0.13.0", path = "../keystore" } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "serde", + "sp-api/std", + "sp-application-crypto/std", + "sp-core/std", + "sp-io/std", + "sp-mmr-primitives/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/primitives/beefy/src/commitment.rs b/primitives/beefy/src/commitment.rs new file mode 100644 index 0000000000000..5765ff3609dbb --- /dev/null +++ b/primitives/beefy/src/commitment.rs @@ -0,0 +1,440 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 codec::{Decode, Encode, Error, Input}; +use scale_info::TypeInfo; +use sp_std::{cmp, prelude::*}; + +use crate::{Payload, ValidatorSetId}; + +/// A commitment signed by GRANDPA validators as part of BEEFY protocol. +/// +/// The commitment contains a [payload](Commitment::payload) extracted from the finalized block at +/// height [block_number](Commitment::block_number). +/// GRANDPA validators collect signatures on commitments and a stream of such signed commitments +/// (see [SignedCommitment]) forms the BEEFY protocol. +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo)] +pub struct Commitment { + /// A collection of payloads to be signed, see [`Payload`] for details. + /// + /// One of the payloads should be some form of cumulative representation of the chain (think + /// MMR root hash). Additionally one of the payloads should also contain some details that + /// allow the light client to verify next validator set. The protocol does not enforce any + /// particular format of this data, nor how often it should be present in commitments, however + /// the light client has to be provided with full validator set whenever it performs the + /// transition (i.e. importing first block with + /// [validator_set_id](Commitment::validator_set_id) incremented). + pub payload: Payload, + + /// Finalized block number this commitment is for. + /// + /// GRANDPA validators agree on a block they create a commitment for and start collecting + /// signatures. This process is called a round. + /// There might be multiple rounds in progress (depending on the block choice rule), however + /// since the payload is supposed to be cumulative, it is not required to import all + /// commitments. + /// BEEFY light client is expected to import at least one commitment per epoch, + /// but is free to import as many as it requires. + pub block_number: TBlockNumber, + + /// BEEFY validator set supposed to sign this commitment. + /// + /// Validator set is changing once per epoch. The Light Client must be provided by details + /// about the validator set whenever it's importing first commitment with a new + /// `validator_set_id`. Validator set data MUST be verifiable, for instance using + /// [payload](Commitment::payload) information. + pub validator_set_id: ValidatorSetId, +} + +impl cmp::PartialOrd for Commitment +where + TBlockNumber: cmp::Ord, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl cmp::Ord for Commitment +where + TBlockNumber: cmp::Ord, +{ + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.validator_set_id + .cmp(&other.validator_set_id) + .then_with(|| self.block_number.cmp(&other.block_number)) + } +} + +/// A commitment with matching GRANDPA validators' signatures. +/// +/// Note that SCALE-encoding of the structure is optimized for size efficiency over the wire, +/// please take a look at custom [`Encode`] and [`Decode`] implementations and +/// `CompactSignedCommitment` struct. +#[derive(Clone, Debug, PartialEq, Eq, TypeInfo)] +pub struct SignedCommitment { + /// The commitment signatures are collected for. + pub commitment: Commitment, + /// GRANDPA validators' signatures for the commitment. + /// + /// The length of this `Vec` must match number of validators in the current set (see + /// [Commitment::validator_set_id]). + pub signatures: Vec>, +} + +impl SignedCommitment { + /// Return the number of collected signatures. + pub fn no_of_signatures(&self) -> usize { + self.signatures.iter().filter(|x| x.is_some()).count() + } +} + +/// Type to be used to denote placement of signatures +type BitField = Vec; +/// Compress 8 bit values into a single u8 Byte +const CONTAINER_BIT_SIZE: usize = 8; + +/// Compressed representation of [`SignedCommitment`], used for encoding efficiency. +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +struct CompactSignedCommitment { + /// The commitment, unchanged compared to regular [`SignedCommitment`]. + commitment: Commitment, + /// A bitfield representing presence of a signature coming from a validator at some index. + /// + /// The bit at index `0` is set to `1` in case we have a signature coming from a validator at + /// index `0` in in the original validator set. In case the [`SignedCommitment`] does not + /// contain that signature the `bit` will be set to `0`. Bits are packed into `Vec` + signatures_from: BitField, + /// Number of validators in the Validator Set and hence number of significant bits in the + /// [`signatures_from`] collection. + /// + /// Note this might be smaller than the size of `signatures_compact` in case some signatures + /// are missing. + validator_set_len: u32, + /// A `Vec` containing all `Signature`s present in the original [`SignedCommitment`]. + /// + /// Note that in order to associate a `Signature` from this `Vec` with a validator, one needs + /// to look at the `signatures_from` bitfield, since some validators might have not produced a + /// signature. + signatures_compact: Vec, +} + +impl<'a, TBlockNumber: Clone, TSignature> CompactSignedCommitment { + /// Packs a `SignedCommitment` into the compressed `CompactSignedCommitment` format for + /// efficient network transport. + fn pack(signed_commitment: &'a SignedCommitment) -> Self { + let SignedCommitment { commitment, signatures } = signed_commitment; + let validator_set_len = signatures.len() as u32; + + let signatures_compact: Vec<&'a TSignature> = + signatures.iter().filter_map(|x| x.as_ref()).collect(); + let bits = { + let mut bits: Vec = + signatures.iter().map(|x| if x.is_some() { 1 } else { 0 }).collect(); + // Resize with excess bits for placement purposes + let excess_bits_len = + CONTAINER_BIT_SIZE - (validator_set_len as usize % CONTAINER_BIT_SIZE); + bits.resize(bits.len() + excess_bits_len, 0); + bits + }; + + let mut signatures_from: BitField = vec![]; + let chunks = bits.chunks(CONTAINER_BIT_SIZE); + for chunk in chunks { + let mut iter = chunk.iter().copied(); + let mut v = iter.next().unwrap() as u8; + + for bit in iter { + v <<= 1; + v |= bit as u8; + } + + signatures_from.push(v); + } + + Self { + commitment: commitment.clone(), + signatures_from, + validator_set_len, + signatures_compact, + } + } + + /// Unpacks a `CompactSignedCommitment` into the uncompressed `SignedCommitment` form. + fn unpack( + temporary_signatures: CompactSignedCommitment, + ) -> SignedCommitment { + let CompactSignedCommitment { + commitment, + signatures_from, + validator_set_len, + signatures_compact, + } = temporary_signatures; + let mut bits: Vec = vec![]; + + for block in signatures_from { + for bit in 0..CONTAINER_BIT_SIZE { + bits.push((block >> (CONTAINER_BIT_SIZE - bit - 1)) & 1); + } + } + + bits.truncate(validator_set_len as usize); + + let mut next_signature = signatures_compact.into_iter(); + let signatures: Vec> = bits + .iter() + .map(|&x| if x == 1 { next_signature.next() } else { None }) + .collect(); + + SignedCommitment { commitment, signatures } + } +} + +impl Encode for SignedCommitment +where + TBlockNumber: Encode + Clone, + TSignature: Encode, +{ + fn using_encoded R>(&self, f: F) -> R { + let temp = CompactSignedCommitment::pack(self); + temp.using_encoded(f) + } +} + +impl Decode for SignedCommitment +where + TBlockNumber: Decode + Clone, + TSignature: Decode, +{ + fn decode(input: &mut I) -> Result { + let temp = CompactSignedCommitment::decode(input)?; + Ok(CompactSignedCommitment::unpack(temp)) + } +} + +/// A [SignedCommitment] with a version number. +/// +/// This variant will be appended to the block justifications for the block +/// for which the signed commitment has been generated. +/// +/// Note that this enum is subject to change in the future with introduction +/// of additional cryptographic primitives to BEEFY. +#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)] +pub enum VersionedFinalityProof { + #[codec(index = 1)] + /// Current active version + V1(SignedCommitment), +} + +impl From> for VersionedFinalityProof { + fn from(commitment: SignedCommitment) -> Self { + VersionedFinalityProof::V1(commitment) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{crypto, known_payloads, KEY_TYPE}; + use codec::Decode; + use sp_core::{keccak_256, Pair}; + use sp_keystore::{testing::KeyStore, SyncCryptoStore, SyncCryptoStorePtr}; + + type TestCommitment = Commitment; + type TestSignedCommitment = SignedCommitment; + type TestVersionedFinalityProof = VersionedFinalityProof; + + const LARGE_RAW_COMMITMENT: &[u8] = include_bytes!("../test-res/large-raw-commitment"); + + // The mock signatures are equivalent to the ones produced by the BEEFY keystore + fn mock_signatures() -> (crypto::Signature, crypto::Signature) { + let store: SyncCryptoStorePtr = KeyStore::new().into(); + + let alice = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap(); + let _ = + SyncCryptoStore::insert_unknown(&*store, KEY_TYPE, "//Alice", alice.public().as_ref()) + .unwrap(); + + let msg = keccak_256(b"This is the first message"); + let sig1 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg) + .unwrap() + .unwrap(); + + let msg = keccak_256(b"This is the second message"); + let sig2 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg) + .unwrap() + .unwrap(); + + (sig1.into(), sig2.into()) + } + + #[test] + fn commitment_encode_decode() { + // given + let payload = + Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode()); + let commitment: TestCommitment = + Commitment { payload, block_number: 5, validator_set_id: 0 }; + + // when + let encoded = codec::Encode::encode(&commitment); + let decoded = TestCommitment::decode(&mut &*encoded); + + // then + assert_eq!(decoded, Ok(commitment)); + assert_eq!( + encoded, + array_bytes::hex2bytes_unchecked( + "046d68343048656c6c6f20576f726c6421050000000000000000000000000000000000000000000000" + ) + ); + } + + #[test] + fn signed_commitment_encode_decode() { + // given + let payload = + Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode()); + let commitment: TestCommitment = + Commitment { payload, block_number: 5, validator_set_id: 0 }; + + let sigs = mock_signatures(); + + let signed = SignedCommitment { + commitment, + signatures: vec![None, None, Some(sigs.0), Some(sigs.1)], + }; + + // when + let encoded = codec::Encode::encode(&signed); + let decoded = TestSignedCommitment::decode(&mut &*encoded); + + // then + assert_eq!(decoded, Ok(signed)); + assert_eq!( + encoded, + array_bytes::hex2bytes_unchecked( + "\ + 046d68343048656c6c6f20576f726c64210500000000000000000000000000000000000000000000000\ + 4300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746c\ + c321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba012d6e1f8105c337a86cdd9aa\ + acdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6\ + a0046395a71681be3d0c2a00\ + " + ) + ); + } + + #[test] + fn signed_commitment_count_signatures() { + // given + let payload = + Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode()); + let commitment: TestCommitment = + Commitment { payload, block_number: 5, validator_set_id: 0 }; + + let sigs = mock_signatures(); + + let mut signed = SignedCommitment { + commitment, + signatures: vec![None, None, Some(sigs.0), Some(sigs.1)], + }; + assert_eq!(signed.no_of_signatures(), 2); + + // when + signed.signatures[2] = None; + + // then + assert_eq!(signed.no_of_signatures(), 1); + } + + #[test] + fn commitment_ordering() { + fn commitment( + block_number: u128, + validator_set_id: crate::ValidatorSetId, + ) -> TestCommitment { + let payload = + Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode()); + Commitment { payload, block_number, validator_set_id } + } + + // given + let a = commitment(1, 0); + let b = commitment(2, 1); + let c = commitment(10, 0); + let d = commitment(10, 1); + + // then + assert!(a < b); + assert!(a < c); + assert!(c < b); + assert!(c < d); + assert!(b < d); + } + + #[test] + fn versioned_commitment_encode_decode() { + let payload = + Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode()); + let commitment: TestCommitment = + Commitment { payload, block_number: 5, validator_set_id: 0 }; + + let sigs = mock_signatures(); + + let signed = SignedCommitment { + commitment, + signatures: vec![None, None, Some(sigs.0), Some(sigs.1)], + }; + + let versioned = TestVersionedFinalityProof::V1(signed.clone()); + + let encoded = codec::Encode::encode(&versioned); + + assert_eq!(1, encoded[0]); + assert_eq!(encoded[1..], codec::Encode::encode(&signed)); + + let decoded = TestVersionedFinalityProof::decode(&mut &*encoded); + + assert_eq!(decoded, Ok(versioned)); + } + + #[test] + fn large_signed_commitment_encode_decode() { + // given + let payload = + Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode()); + let commitment: TestCommitment = + Commitment { payload, block_number: 5, validator_set_id: 0 }; + + let sigs = mock_signatures(); + + let signatures: Vec> = (0..1024) + .into_iter() + .map(|x| if x < 340 { None } else { Some(sigs.0.clone()) }) + .collect(); + let signed = SignedCommitment { commitment, signatures }; + + // when + let encoded = codec::Encode::encode(&signed); + let decoded = TestSignedCommitment::decode(&mut &*encoded); + + // then + assert_eq!(decoded, Ok(signed)); + assert_eq!(encoded, LARGE_RAW_COMMITMENT); + } +} diff --git a/primitives/beefy/src/lib.rs b/primitives/beefy/src/lib.rs new file mode 100644 index 0000000000000..d7ac091491bff --- /dev/null +++ b/primitives/beefy/src/lib.rs @@ -0,0 +1,270 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] + +//! Primitives for BEEFY protocol. +//! +//! The crate contains shared data types used by BEEFY protocol and documentation (in a form of +//! code) for building a BEEFY light client. +//! +//! BEEFY is a gadget that runs alongside another finality gadget (for instance GRANDPA). +//! For simplicity (and the initially intended use case) the documentation says GRANDPA in places +//! where a more abstract "Finality Gadget" term could be used, but there is no reason why BEEFY +//! wouldn't run with some other finality scheme. +//! BEEFY validator set is supposed to be tracking the Finality Gadget validator set, but note that +//! it will use a different set of keys. For Polkadot use case we plan to use `secp256k1` for BEEFY, +//! while GRANDPA uses `ed25519`. + +mod commitment; +pub mod mmr; +mod payload; +pub mod witness; + +pub use commitment::{Commitment, SignedCommitment, VersionedFinalityProof}; +pub use payload::{known_payloads, BeefyPayloadId, Payload, PayloadProvider}; + +use codec::{Codec, Decode, Encode}; +use scale_info::TypeInfo; +use sp_application_crypto::RuntimeAppPublic; +use sp_core::H256; +use sp_runtime::traits::Hash; +use sp_std::prelude::*; + +/// Key type for BEEFY module. +pub const KEY_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::KeyTypeId(*b"beef"); + +/// Trait representing BEEFY authority id. +pub trait BeefyAuthorityId: RuntimeAppPublic {} + +/// Means of verification for a BEEFY authority signature. +/// +/// Accepts custom hashing fn for the message and custom convertor fn for the signer. +pub trait BeefyVerify { + /// Type of the signer. + type Signer: BeefyAuthorityId; + + /// Verify a signature. + /// + /// Return `true` if signature is valid for the value. + fn verify(&self, msg: &[u8], signer: &Self::Signer) -> bool; +} + +/// BEEFY cryptographic types +/// +/// This module basically introduces three crypto types: +/// - `crypto::Pair` +/// - `crypto::Public` +/// - `crypto::Signature` +/// +/// Your code should use the above types as concrete types for all crypto related +/// functionality. +/// +/// The current underlying crypto scheme used is ECDSA. This can be changed, +/// without affecting code restricted against the above listed crypto types. +pub mod crypto { + use super::{BeefyAuthorityId, BeefyVerify, Hash}; + use sp_application_crypto::{app_crypto, ecdsa}; + use sp_core::crypto::Wraps; + app_crypto!(ecdsa, crate::KEY_TYPE); + + /// Identity of a BEEFY authority using ECDSA as its crypto. + pub type AuthorityId = Public; + + /// Signature for a BEEFY authority using ECDSA as its crypto. + pub type AuthoritySignature = Signature; + + impl BeefyAuthorityId for AuthorityId {} + + impl BeefyVerify for AuthoritySignature + where + ::Output: Into<[u8; 32]>, + { + type Signer = AuthorityId; + + fn verify(&self, msg: &[u8], signer: &Self::Signer) -> bool { + let msg_hash = ::hash(msg).into(); + match sp_io::crypto::secp256k1_ecdsa_recover_compressed( + self.as_inner_ref().as_ref(), + &msg_hash, + ) { + Ok(raw_pubkey) => raw_pubkey.as_ref() == AsRef::<[u8]>::as_ref(signer), + _ => false, + } + } + } +} + +/// The `ConsensusEngineId` of BEEFY. +pub const BEEFY_ENGINE_ID: sp_runtime::ConsensusEngineId = *b"BEEF"; + +/// Authority set id starts with zero at BEEFY pallet genesis. +pub const GENESIS_AUTHORITY_SET_ID: u64 = 0; + +/// A typedef for validator set id. +pub type ValidatorSetId = u64; + +/// A set of BEEFY authorities, a.k.a. validators. +#[derive(Decode, Encode, Debug, PartialEq, Clone, TypeInfo)] +pub struct ValidatorSet { + /// Public keys of the validator set elements + validators: Vec, + /// Identifier of the validator set + id: ValidatorSetId, +} + +impl ValidatorSet { + /// Return a validator set with the given validators and set id. + pub fn new(validators: I, id: ValidatorSetId) -> Option + where + I: IntoIterator, + { + let validators: Vec = validators.into_iter().collect(); + if validators.is_empty() { + // No validators; the set would be empty. + None + } else { + Some(Self { validators, id }) + } + } + + /// Return a reference to the vec of validators. + pub fn validators(&self) -> &[AuthorityId] { + &self.validators + } + + /// Return the validator set id. + pub fn id(&self) -> ValidatorSetId { + self.id + } + + /// Return the number of validators in the set. + pub fn len(&self) -> usize { + self.validators.len() + } +} + +/// The index of an authority. +pub type AuthorityIndex = u32; + +/// The type used to represent an MMR root hash. +pub type MmrRootHash = H256; + +/// A consensus log item for BEEFY. +#[derive(Decode, Encode, TypeInfo)] +pub enum ConsensusLog { + /// The authorities have changed. + #[codec(index = 1)] + AuthoritiesChange(ValidatorSet), + /// Disable the authority with given index. + #[codec(index = 2)] + OnDisabled(AuthorityIndex), + /// MMR root hash. + #[codec(index = 3)] + MmrRoot(MmrRootHash), +} + +/// BEEFY vote message. +/// +/// A vote message is a direct vote created by a BEEFY node on every voting round +/// and is gossiped to its peers. +#[derive(Debug, Decode, Encode, TypeInfo)] +pub struct VoteMessage { + /// Commit to information extracted from a finalized block + pub commitment: Commitment, + /// Node authority id + pub id: Id, + /// Node signature + pub signature: Signature, +} + +/// New BEEFY validator set notification hook. +pub trait OnNewValidatorSet { + /// Function called by the pallet when BEEFY validator set changes. + fn on_new_validator_set( + validator_set: &ValidatorSet, + next_validator_set: &ValidatorSet, + ); +} + +/// No-op implementation of [OnNewValidatorSet]. +impl OnNewValidatorSet for () { + fn on_new_validator_set(_: &ValidatorSet, _: &ValidatorSet) {} +} + +sp_api::decl_runtime_apis! { + /// API necessary for BEEFY voters. + pub trait BeefyApi + { + /// Return the current active BEEFY validator set + fn validator_set() -> Option>; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_application_crypto::ecdsa::{self, Public}; + use sp_core::{blake2_256, crypto::Wraps, keccak_256, Pair}; + use sp_runtime::traits::{BlakeTwo256, Keccak256}; + + #[test] + fn validator_set() { + // Empty set not allowed. + assert_eq!(ValidatorSet::::new(vec![], 0), None); + + let alice = ecdsa::Pair::from_string("//Alice", None).unwrap(); + let set_id = 0; + let validators = ValidatorSet::::new(vec![alice.public()], set_id).unwrap(); + + assert_eq!(validators.id(), set_id); + assert_eq!(validators.validators(), &vec![alice.public()]); + } + + #[test] + fn beefy_verify_works() { + let msg = &b"test-message"[..]; + let (pair, _) = crypto::Pair::generate(); + + let keccak_256_signature: crypto::Signature = + pair.as_inner_ref().sign_prehashed(&keccak_256(msg)).into(); + + let blake2_256_signature: crypto::Signature = + pair.as_inner_ref().sign_prehashed(&blake2_256(msg)).into(); + + // Verification works if same hashing function is used when signing and verifying. + assert!(BeefyVerify::::verify(&keccak_256_signature, msg, &pair.public())); + assert!(BeefyVerify::::verify(&blake2_256_signature, msg, &pair.public())); + // Verification fails if distinct hashing functions are used when signing and verifying. + assert!(!BeefyVerify::::verify(&blake2_256_signature, msg, &pair.public())); + assert!(!BeefyVerify::::verify(&keccak_256_signature, msg, &pair.public())); + + // Other public key doesn't work + let (other_pair, _) = crypto::Pair::generate(); + assert!(!BeefyVerify::::verify( + &keccak_256_signature, + msg, + &other_pair.public() + )); + assert!(!BeefyVerify::::verify( + &blake2_256_signature, + msg, + &other_pair.public() + )); + } +} diff --git a/primitives/beefy/src/mmr.rs b/primitives/beefy/src/mmr.rs new file mode 100644 index 0000000000000..549d2edbdf287 --- /dev/null +++ b/primitives/beefy/src/mmr.rs @@ -0,0 +1,250 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! BEEFY + MMR utilties. +//! +//! While BEEFY can be used completely independently as an additional consensus gadget, +//! it is designed around a main use case of bridging standalone networks together. +//! For that use case it's common to use some aggregated data structure (like MMR) to be +//! used in conjunction with BEEFY, to be able to efficiently prove any past blockchain data. +//! +//! This module contains primitives used by Polkadot implementation of the BEEFY+MMR bridge, +//! but we imagine they will be useful for other chains that either want to bridge with Polkadot +//! or are completely standalone, but heavily inspired by Polkadot. + +use crate::{crypto::AuthorityId, ConsensusLog, MmrRootHash, Vec, BEEFY_ENGINE_ID}; +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{ + generic::OpaqueDigestItemId, + traits::{Block, Header}, +}; + +/// A provider for extra data that gets added to the Mmr leaf +pub trait BeefyDataProvider { + /// Return a vector of bytes, ideally should be a merkle root hash + fn extra_data() -> ExtraData; +} + +/// A default implementation for runtimes. +impl BeefyDataProvider> for () { + fn extra_data() -> Vec { + Vec::new() + } +} + +/// A standard leaf that gets added every block to the MMR constructed by Substrate's `pallet_mmr`. +#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)] +pub struct MmrLeaf { + /// Version of the leaf format. + /// + /// Can be used to enable future format migrations and compatibility. + /// See [`MmrLeafVersion`] documentation for details. + pub version: MmrLeafVersion, + /// Current block parent number and hash. + pub parent_number_and_hash: (BlockNumber, Hash), + /// A merkle root of the next BEEFY authority set. + pub beefy_next_authority_set: BeefyNextAuthoritySet, + /// Arbitrary extra leaf data to be used by downstream pallets to include custom data in the + /// [`MmrLeaf`] + pub leaf_extra: ExtraData, +} + +/// An MMR leaf versioning scheme. +/// +/// Version is a single byte that constist of two components: +/// - `major` - 3 bits +/// - `minor` - 5 bits +/// +/// Any change in encoding that adds new items to the structure is considered non-breaking, hence +/// only requires an update of `minor` version. Any backward incompatible change (i.e. decoding to a +/// previous leaf format fails) should be indicated with `major` version bump. +/// +/// Given that adding new struct elements in SCALE is backward compatible (i.e. old format can be +/// still decoded, the new fields will simply be ignored). We expect the major version to be bumped +/// very rarely (hopefuly never). +#[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)] +pub struct MmrLeafVersion(u8); +impl MmrLeafVersion { + /// Create new version object from `major` and `minor` components. + /// + /// Panics if any of the component occupies more than 4 bits. + pub fn new(major: u8, minor: u8) -> Self { + if major > 0b111 || minor > 0b11111 { + panic!("Version components are too big."); + } + let version = (major << 5) + minor; + Self(version) + } + + /// Split the version into `major` and `minor` sub-components. + pub fn split(&self) -> (u8, u8) { + let major = self.0 >> 5; + let minor = self.0 & 0b11111; + (major, minor) + } +} + +/// Details of a BEEFY authority set. +#[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct BeefyAuthoritySet { + /// Id of the set. + /// + /// Id is required to correlate BEEFY signed commitments with the validator set. + /// Light Client can easily verify that the commitment witness it is getting is + /// produced by the latest validator set. + pub id: crate::ValidatorSetId, + /// Number of validators in the set. + /// + /// Some BEEFY Light Clients may use an interactive protocol to verify only a subset + /// of signatures. We put set length here, so that these clients can verify the minimal + /// number of required signatures. + pub len: u32, + /// Merkle Root Hash built from BEEFY AuthorityIds. + /// + /// This is used by Light Clients to confirm that the commitments are signed by the correct + /// validator set. Light Clients using interactive protocol, might verify only subset of + /// signatures, hence don't require the full list here (will receive inclusion proofs). + pub root: MerkleRoot, +} + +/// Details of the next BEEFY authority set. +pub type BeefyNextAuthoritySet = BeefyAuthoritySet; + +/// Extract the MMR root hash from a digest in the given header, if it exists. +pub fn find_mmr_root_digest(header: &B::Header) -> Option { + let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); + + let filter = |log: ConsensusLog| match log { + ConsensusLog::MmrRoot(root) => Some(root), + _ => None, + }; + header.digest().convert_first(|l| l.try_to(id).and_then(filter)) +} + +#[cfg(feature = "std")] +pub use mmr_root_provider::MmrRootProvider; +#[cfg(feature = "std")] +mod mmr_root_provider { + use super::*; + use crate::{known_payloads, payload::PayloadProvider, Payload}; + use sp_api::{NumberFor, ProvideRuntimeApi}; + use sp_mmr_primitives::MmrApi; + use sp_runtime::generic::BlockId; + use sp_std::{marker::PhantomData, sync::Arc}; + + /// A [`crate::Payload`] provider where payload is Merkle Mountain Range root hash. + /// + /// Encoded payload contains a [`crate::MmrRootHash`] type (i.e. 32-bytes hash). + pub struct MmrRootProvider { + runtime: Arc, + _phantom: PhantomData, + } + + impl MmrRootProvider + where + B: Block, + R: ProvideRuntimeApi, + R::Api: MmrApi>, + { + /// Create new BEEFY Payload provider with MMR Root as payload. + pub fn new(runtime: Arc) -> Self { + Self { runtime, _phantom: PhantomData } + } + + /// Simple wrapper that gets MMR root from header digests or from client state. + fn mmr_root_from_digest_or_runtime(&self, header: &B::Header) -> Option { + find_mmr_root_digest::(header).or_else(|| { + self.runtime + .runtime_api() + .mmr_root(&BlockId::hash(header.hash())) + .ok() + .and_then(|r| r.ok()) + }) + } + } + + impl PayloadProvider for MmrRootProvider + where + B: Block, + R: ProvideRuntimeApi, + R::Api: MmrApi>, + { + fn payload(&self, header: &B::Header) -> Option { + self.mmr_root_from_digest_or_runtime(header).map(|mmr_root| { + Payload::from_single_entry(known_payloads::MMR_ROOT_ID, mmr_root.encode()) + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::H256; + use sp_runtime::{traits::BlakeTwo256, Digest, DigestItem, OpaqueExtrinsic}; + + #[test] + fn should_construct_version_correctly() { + let tests = vec![(0, 0, 0b00000000), (7, 2, 0b11100010), (7, 31, 0b11111111)]; + + for (major, minor, version) in tests { + let v = MmrLeafVersion::new(major, minor); + assert_eq!(v.encode(), vec![version], "Encoding does not match."); + assert_eq!(v.split(), (major, minor)); + } + } + + #[test] + #[should_panic] + fn should_panic_if_major_too_large() { + MmrLeafVersion::new(8, 0); + } + + #[test] + #[should_panic] + fn should_panic_if_minor_too_large() { + MmrLeafVersion::new(0, 32); + } + + #[test] + fn extract_mmr_root_digest() { + type Header = sp_runtime::generic::Header; + type Block = sp_runtime::generic::Block; + let mut header = Header::new( + 1u64, + Default::default(), + Default::default(), + Default::default(), + Digest::default(), + ); + + // verify empty digest shows nothing + assert!(find_mmr_root_digest::(&header).is_none()); + + let mmr_root_hash = H256::random(); + header.digest_mut().push(DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::MmrRoot(mmr_root_hash).encode(), + )); + + // verify validator set is correctly extracted from digest + let extracted = find_mmr_root_digest::(&header); + assert_eq!(extracted, Some(mmr_root_hash)); + } +} diff --git a/primitives/beefy/src/payload.rs b/primitives/beefy/src/payload.rs new file mode 100644 index 0000000000000..0f23c3f381f19 --- /dev/null +++ b/primitives/beefy/src/payload.rs @@ -0,0 +1,105 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::traits::Block; +use sp_std::prelude::*; + +/// Id of different payloads in the [`crate::Commitment`] data. +pub type BeefyPayloadId = [u8; 2]; + +/// Registry of all known [`BeefyPayloadId`]. +pub mod known_payloads { + use crate::BeefyPayloadId; + + /// A [`Payload`](super::Payload) identifier for Merkle Mountain Range root hash. + /// + /// Encoded value should contain a [`crate::MmrRootHash`] type (i.e. 32-bytes hash). + pub const MMR_ROOT_ID: BeefyPayloadId = *b"mh"; +} + +/// A BEEFY payload type allowing for future extensibility of adding additional kinds of payloads. +/// +/// The idea is to store a vector of SCALE-encoded values with an extra identifier. +/// Identifiers MUST be sorted by the [`BeefyPayloadId`] to allow efficient lookup of expected +/// value. Duplicated identifiers are disallowed. It's okay for different implementations to only +/// support a subset of possible values. +#[derive(Decode, Encode, Debug, PartialEq, Eq, Clone, Ord, PartialOrd, Hash, TypeInfo)] +pub struct Payload(Vec<(BeefyPayloadId, Vec)>); + +impl Payload { + /// Construct a new payload given an initial vallue + pub fn from_single_entry(id: BeefyPayloadId, value: Vec) -> Self { + Self(vec![(id, value)]) + } + + /// Returns a raw payload under given `id`. + /// + /// If the [`BeefyPayloadId`] is not found in the payload `None` is returned. + pub fn get_raw(&self, id: &BeefyPayloadId) -> Option<&Vec> { + let index = self.0.binary_search_by(|probe| probe.0.cmp(id)).ok()?; + Some(&self.0[index].1) + } + + /// Returns a decoded payload value under given `id`. + /// + /// In case the value is not there or it cannot be decoded does not match `None` is returned. + pub fn get_decoded(&self, id: &BeefyPayloadId) -> Option { + self.get_raw(id).and_then(|raw| T::decode(&mut &raw[..]).ok()) + } + + /// Push a `Vec` with a given id into the payload vec. + /// This method will internally sort the payload vec after every push. + /// + /// Returns self to allow for daisy chaining. + pub fn push_raw(mut self, id: BeefyPayloadId, value: Vec) -> Self { + self.0.push((id, value)); + self.0.sort_by_key(|(id, _)| *id); + self + } +} + +/// Trait for custom BEEFY payload providers. +pub trait PayloadProvider { + /// Provide BEEFY payload if available for `header`. + fn payload(&self, header: &B::Header) -> Option; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn payload_methods_work_as_expected() { + let id1: BeefyPayloadId = *b"hw"; + let msg1: String = "1. Hello World!".to_string(); + let id2: BeefyPayloadId = *b"yb"; + let msg2: String = "2. Yellow Board!".to_string(); + let id3: BeefyPayloadId = *b"cs"; + let msg3: String = "3. Cello Cord!".to_string(); + + let payload = Payload::from_single_entry(id1, msg1.encode()) + .push_raw(id2, msg2.encode()) + .push_raw(id3, msg3.encode()); + + assert_eq!(payload.get_decoded(&id1), Some(msg1)); + assert_eq!(payload.get_decoded(&id2), Some(msg2)); + assert_eq!(payload.get_raw(&id3), Some(&msg3.encode())); + assert_eq!(payload.get_raw(&known_payloads::MMR_ROOT_ID), None); + } +} diff --git a/primitives/beefy/src/witness.rs b/primitives/beefy/src/witness.rs new file mode 100644 index 0000000000000..2c45e0ade90c4 --- /dev/null +++ b/primitives/beefy/src/witness.rs @@ -0,0 +1,164 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Primitives for light, 2-phase interactive verification protocol. +//! +//! Instead of submitting full list of signatures, it's possible to submit first a witness +//! form of [SignedCommitment]. +//! This can later be verified by the client requesting only some (out of all) signatures for +//! verification. This allows lowering the data and computation cost of verifying the +//! signed commitment. + +use sp_std::prelude::*; + +use crate::commitment::{Commitment, SignedCommitment}; + +/// A light form of [SignedCommitment]. +/// +/// This is a light ("witness") form of the signed commitment. Instead of containing full list of +/// signatures, which might be heavy and expensive to verify, it only contains a bit vector of +/// validators which signed the original [SignedCommitment] and a merkle root of all signatures. +/// +/// This can be used by light clients for 2-phase interactive verification (for instance for +/// Ethereum Mainnet), in a commit-reveal like scheme, where first we submit only the signed +/// commitment witness and later on, the client picks only some signatures to verify at random. +#[derive(Debug, PartialEq, Eq, codec::Encode, codec::Decode)] +pub struct SignedCommitmentWitness { + /// The full content of the commitment. + pub commitment: Commitment, + + /// The bit vector of validators who signed the commitment. + pub signed_by: Vec, // TODO [ToDr] Consider replacing with bitvec crate + + /// A merkle root of signatures in the original signed commitment. + pub signatures_merkle_root: TMerkleRoot, +} + +impl SignedCommitmentWitness { + /// Convert [SignedCommitment] into [SignedCommitmentWitness]. + /// + /// This takes a [SignedCommitment], which contains full signatures + /// and converts it into a witness form, which does not contain full signatures, + /// only a bit vector indicating which validators have signed the original [SignedCommitment] + /// and a merkle root of all signatures. + /// + /// Returns the full list of signatures along with the witness. + pub fn from_signed( + signed: SignedCommitment, + merkelize: TMerkelize, + ) -> (Self, Vec>) + where + TMerkelize: FnOnce(&[Option]) -> TMerkleRoot, + { + let SignedCommitment { commitment, signatures } = signed; + let signed_by = signatures.iter().map(|s| s.is_some()).collect(); + let signatures_merkle_root = merkelize(&signatures); + + (Self { commitment, signed_by, signatures_merkle_root }, signatures) + } +} + +#[cfg(test)] +mod tests { + + use sp_core::{keccak_256, Pair}; + use sp_keystore::{testing::KeyStore, SyncCryptoStore, SyncCryptoStorePtr}; + + use super::*; + use codec::Decode; + + use crate::{crypto, known_payloads, Payload, KEY_TYPE}; + + type TestCommitment = Commitment; + type TestSignedCommitment = SignedCommitment; + type TestSignedCommitmentWitness = + SignedCommitmentWitness>>; + + // The mock signatures are equivalent to the ones produced by the BEEFY keystore + fn mock_signatures() -> (crypto::Signature, crypto::Signature) { + let store: SyncCryptoStorePtr = KeyStore::new().into(); + + let alice = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap(); + let _ = + SyncCryptoStore::insert_unknown(&*store, KEY_TYPE, "//Alice", alice.public().as_ref()) + .unwrap(); + + let msg = keccak_256(b"This is the first message"); + let sig1 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg) + .unwrap() + .unwrap(); + + let msg = keccak_256(b"This is the second message"); + let sig2 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg) + .unwrap() + .unwrap(); + + (sig1.into(), sig2.into()) + } + + fn signed_commitment() -> TestSignedCommitment { + let payload = Payload::from_single_entry( + known_payloads::MMR_ROOT_ID, + "Hello World!".as_bytes().to_vec(), + ); + let commitment: TestCommitment = + Commitment { payload, block_number: 5, validator_set_id: 0 }; + + let sigs = mock_signatures(); + + SignedCommitment { commitment, signatures: vec![None, None, Some(sigs.0), Some(sigs.1)] } + } + + #[test] + fn should_convert_signed_commitment_to_witness() { + // given + let signed = signed_commitment(); + + // when + let (witness, signatures) = + TestSignedCommitmentWitness::from_signed(signed, |sigs| sigs.to_vec()); + + // then + assert_eq!(witness.signatures_merkle_root, signatures); + } + + #[test] + fn should_encode_and_decode_witness() { + // given + let signed = signed_commitment(); + let (witness, _) = TestSignedCommitmentWitness::from_signed(signed, |sigs| sigs.to_vec()); + + // when + let encoded = codec::Encode::encode(&witness); + let decoded = TestSignedCommitmentWitness::decode(&mut &*encoded); + + // then + assert_eq!(decoded, Ok(witness)); + assert_eq!( + encoded, + array_bytes::hex2bytes_unchecked( + "\ + 046d683048656c6c6f20576f726c642105000000000000000000000000000000000000000000000010\ + 0000010110000001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c\ + 746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a8\ + 6cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487b\ + ca2324b6a0046395a71681be3d0c2a00\ + " + ) + ); + } +} diff --git a/primitives/beefy/test-res/large-raw-commitment b/primitives/beefy/test-res/large-raw-commitment new file mode 100644 index 0000000000000..d5dbbe402a88e Binary files /dev/null and b/primitives/beefy/test-res/large-raw-commitment differ diff --git a/primitives/block-builder/Cargo.toml b/primitives/block-builder/Cargo.toml index d7fa0f2ef85cf..7770a0e210c63 100644 --- a/primitives/block-builder/Cargo.toml +++ b/primitives/block-builder/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-block-builder" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "The block builder runtime api." readme = "README.md" @@ -13,18 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] std = [ - "sp-runtime/std", "codec/std", - "sp-inherents/std", "sp-api/std", + "sp-inherents/std", + "sp-runtime/std", "sp-std/std", ] diff --git a/primitives/block-builder/src/lib.rs b/primitives/block-builder/src/lib.rs index 3741b19200647..cf1bfa256084d 100644 --- a/primitives/block-builder/src/lib.rs +++ b/primitives/block-builder/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ use sp_runtime::{traits::Block as BlockT, ApplyExtrinsicResult}; sp_api::decl_runtime_apis! { /// The `BlockBuilder` api trait that provides the required functionality for building a block. - #[api_version(5)] + #[api_version(6)] pub trait BlockBuilder { /// Apply the given extrinsic. /// @@ -32,6 +32,11 @@ sp_api::decl_runtime_apis! { /// this block or not. fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult; + #[changed_in(6)] + fn apply_extrinsic( + extrinsic: ::Extrinsic, + ) -> sp_runtime::legacy::byte_sized_error::ApplyExtrinsicResult; + /// Finish the current block. #[renamed("finalise_block", 3)] fn finalize_block() -> ::Header; diff --git a/primitives/blockchain/Cargo.toml b/primitives/blockchain/Cargo.toml index 66d9152c230df..7f5f22fe09b73 100644 --- a/primitives/blockchain/Cargo.toml +++ b/primitives/blockchain/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-blockchain" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate blockchain traits and primitives." documentation = "https://docs.rs/sp-blockchain" @@ -14,14 +14,14 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.11" -lru = "0.6.6" -parking_lot = "0.11.1" -thiserror = "1.0.21" -futures = "0.3.9" -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +futures = "0.3.21" +log = "0.4.17" +lru = "0.8.1" +parking_lot = "0.12.1" +thiserror = "1.0.30" +sp-api = { version = "4.0.0-dev", path = "../api" } sp-consensus = { version = "0.10.0-dev", path = "../consensus/common" } -sp-runtime = { version = "4.0.0-dev", path = "../runtime" } -sp-state-machine = { version = "0.10.0-dev", path = "../state-machine" } sp-database = { version = "4.0.0-dev", path = "../database" } -sp-api = { version = "4.0.0-dev", path = "../api" } +sp-runtime = { version = "7.0.0", path = "../runtime" } +sp-state-machine = { version = "0.13.0", path = "../state-machine" } diff --git a/primitives/blockchain/src/backend.rs b/primitives/blockchain/src/backend.rs index bb34a0449b5f7..33edc56d4b6ba 100644 --- a/primitives/blockchain/src/backend.rs +++ b/primitives/blockchain/src/backend.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,15 +17,14 @@ //! Substrate blockchain trait -use std::sync::Arc; - use log::warn; use parking_lot::RwLock; use sp_runtime::{ generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, NumberFor}, + traits::{Block as BlockT, Header as HeaderT, NumberFor, Saturating}, Justifications, }; +use std::collections::btree_set::BTreeSet; use crate::header_metadata::HeaderMetadata; @@ -80,30 +79,107 @@ pub trait HeaderBackend: Send + Sync { /// Convert an arbitrary block ID into a block hash. Returns `UnknownBlock` error if block is /// not found. fn expect_block_hash_from_id(&self, id: &BlockId) -> Result { - self.block_hash_from_id(id).and_then(|n| { - n.ok_or_else(|| Error::UnknownBlock(format!("Expect block hash from id: {}", id))) + self.block_hash_from_id(id).and_then(|h| { + h.ok_or_else(|| Error::UnknownBlock(format!("Expect block hash from id: {}", id))) }) } } +/// Handles stale forks. +pub trait ForkBackend: + HeaderMetadata + HeaderBackend + Send + Sync +{ + /// Best effort to get all the header hashes that are part of the provided forks + /// starting only from the fork heads. + /// + /// The function tries to reconstruct the route from the fork head to the canonical chain. + /// If any of the hashes on the route can't be found in the db, the function won't be able + /// to reconstruct the route anymore. In this case it will give up expanding the current fork, + /// move on to the next ones and at the end it will return an error that also contains + /// the partially expanded forks. + fn expand_forks( + &self, + fork_heads: &[Block::Hash], + ) -> std::result::Result, (BTreeSet, Error)> { + let mut missing_blocks = vec![]; + let mut expanded_forks = BTreeSet::new(); + for fork_head in fork_heads { + let mut route_head = *fork_head; + // Insert stale blocks hashes until canonical chain is reached. + // If we reach a block that is already part of the `expanded_forks` we can stop + // processing the fork. + while expanded_forks.insert(route_head) { + match self.header_metadata(route_head) { + Ok(meta) => { + // If the parent is part of the canonical chain or there doesn't exist a + // block hash for the parent number (bug?!), we can abort adding blocks. + let parent_number = meta.number.saturating_sub(1u32.into()); + match self.hash(parent_number) { + Ok(Some(parent_hash)) => + if parent_hash == meta.parent { + break + }, + Ok(None) | Err(_) => { + missing_blocks.push(BlockId::::Number(parent_number)); + break + }, + } + + route_head = meta.parent; + }, + Err(_e) => { + missing_blocks.push(BlockId::::Hash(route_head)); + break + }, + } + } + } + + if !missing_blocks.is_empty() { + return Err(( + expanded_forks, + Error::UnknownBlocks(format!( + "Missing stale headers {:?} while expanding forks {:?}.", + fork_heads, missing_blocks + )), + )) + } + + Ok(expanded_forks) + } +} + +impl ForkBackend for T +where + Block: BlockT, + T: HeaderMetadata + HeaderBackend + Send + Sync, +{ +} + /// Blockchain database backend. Does not perform any validation. pub trait Backend: HeaderBackend + HeaderMetadata { /// Get block body. Returns `None` if block is not found. - fn body(&self, id: BlockId) -> Result::Extrinsic>>>; + fn body(&self, hash: Block::Hash) -> Result::Extrinsic>>>; /// Get block justifications. Returns `None` if no justification exists. - fn justifications(&self, id: BlockId) -> Result>; + fn justifications(&self, hash: Block::Hash) -> Result>; /// Get last finalized block hash. fn last_finalized(&self) -> Result; - /// Returns data cache reference, if it is enabled on this backend. - fn cache(&self) -> Option>>; /// Returns hashes of all blocks that are leaves of the block tree. /// in other words, that have no children, are chain heads. /// Results must be ordered best (longest, highest) chain first. fn leaves(&self) -> Result>; + /// Returns displaced leaves after the given block would be finalized. + /// + /// The returned leaves do not contain the leaves from the same height as `block_number`. + fn displaced_leaves_after_finalizing( + &self, + block_number: NumberFor, + ) -> Result>; + /// Return hashes of all blocks that are children of the block with `parent_hash`. fn children(&self, parent_hash: Block::Hash) -> Result>; @@ -180,7 +256,7 @@ pub trait Backend: if let Some(max_number) = maybe_max_number { loop { let current_header = self - .header(BlockId::Hash(current_hash.clone()))? + .header(BlockId::Hash(current_hash))? .ok_or_else(|| Error::MissingHeader(current_hash.to_string()))?; if current_header.number() <= &max_number { @@ -200,7 +276,7 @@ pub trait Backend: } let current_header = self - .header(BlockId::Hash(current_hash.clone()))? + .header(BlockId::Hash(current_hash))? .ok_or_else(|| Error::MissingHeader(current_hash.to_string()))?; // stop search in this chain once we go below the target's block number @@ -227,41 +303,14 @@ pub trait Backend: /// Get single indexed transaction by content hash. Note that this will only fetch transactions /// that are indexed by the runtime with `storage_index_transaction`. - fn indexed_transaction(&self, hash: &Block::Hash) -> Result>>; + fn indexed_transaction(&self, hash: Block::Hash) -> Result>>; /// Check if indexed transaction exists. - fn has_indexed_transaction(&self, hash: &Block::Hash) -> Result { + fn has_indexed_transaction(&self, hash: Block::Hash) -> Result { Ok(self.indexed_transaction(hash)?.is_some()) } - fn block_indexed_body(&self, id: BlockId) -> Result>>>; -} - -/// Provides access to the optional cache. -pub trait ProvideCache { - /// Returns data cache reference, if it is enabled on this backend. - fn cache(&self) -> Option>>; -} - -/// Blockchain optional data cache. -pub trait Cache: Send + Sync { - /// Initialize genesis value for the given cache. - /// - /// The operation should be performed once before anything else is inserted in the cache. - /// Otherwise cache may end up in inconsistent state. - fn initialize(&self, key: &well_known_cache_keys::Id, value_at_genesis: Vec) -> Result<()>; - /// Returns cached value by the given key. - /// - /// Returned tuple is the range where value has been active and the value itself. - /// Fails if read from cache storage fails or if the value for block is discarded - /// (i.e. if block is earlier that best finalized, but it is not in canonical chain). - fn get_at( - &self, - key: &well_known_cache_keys::Id, - block: &BlockId, - ) -> Result< - Option<((NumberFor, Block::Hash), Option<(NumberFor, Block::Hash)>, Vec)>, - >; + fn block_indexed_body(&self, hash: Block::Hash) -> Result>>>; } /// Blockchain info @@ -281,6 +330,8 @@ pub struct Info { pub finalized_state: Option<(Block::Hash, <::Header as HeaderT>::Number)>, /// Number of concurrent leave forks. pub number_leaves: usize, + /// Missing blocks after warp sync. (start, end). + pub block_gap: Option<(NumberFor, NumberFor)>, } /// Block status. diff --git a/primitives/blockchain/src/error.rs b/primitives/blockchain/src/error.rs index ef3afa5bce942..783c40c4061ad 100644 --- a/primitives/blockchain/src/error.rs +++ b/primitives/blockchain/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,6 +59,9 @@ pub enum Error { #[error("UnknownBlock: {0}")] UnknownBlock(String), + #[error("UnknownBlocks: {0}")] + UnknownBlocks(String), + #[error(transparent)] ApplyExtrinsicFailed(#[from] ApplyExtrinsicFailed), @@ -69,7 +72,7 @@ pub enum Error { ExtrinsicRootInvalid { received: String, expected: String }, // `inner` cannot be made member, since it lacks `std::error::Error` trait bounds. - #[error("Execution failed: {0:?}")] + #[error("Execution failed: {0}")] Execution(Box), #[error("Blockchain")] diff --git a/primitives/blockchain/src/header_metadata.rs b/primitives/blockchain/src/header_metadata.rs index 928409963bcd4..87ac44459987e 100644 --- a/primitives/blockchain/src/header_metadata.rs +++ b/primitives/blockchain/src/header_metadata.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,8 @@ use lru::LruCache; use parking_lot::RwLock; -use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor, One}; +use std::num::NonZeroUsize; /// Set to the expected max difference between `best` and `finalized` blocks at sync. const LRU_CACHE_SIZE: usize = 5_000; @@ -37,7 +38,14 @@ pub fn lowest_common_ancestor + ?Sized>( id_two: Block::Hash, ) -> Result, T::Error> { let mut header_one = backend.header_metadata(id_one)?; + if header_one.parent == id_two { + return Ok(HashAndNumber { hash: id_two, number: header_one.number - One::one() }) + } + let mut header_two = backend.header_metadata(id_two)?; + if header_two.parent == id_one { + return Ok(HashAndNumber { hash: id_one, number: header_one.number }) + } let mut orig_header_one = header_one.clone(); let mut orig_header_two = header_two.clone(); @@ -169,6 +177,21 @@ pub struct TreeRoute { } impl TreeRoute { + /// Creates a new `TreeRoute`. + /// + /// To preserve the structure safety invariats it is required that `pivot < route.len()`. + pub fn new(route: Vec>, pivot: usize) -> Result { + if pivot < route.len() { + Ok(TreeRoute { route, pivot }) + } else { + Err(format!( + "TreeRoute pivot ({}) should be less than route length ({})", + pivot, + route.len() + )) + } + } + /// Get a slice of all retracted blocks in reverse order (towards common ancestor). pub fn retracted(&self) -> &[HashAndNumber] { &self.route[..self.pivot] @@ -194,12 +217,17 @@ impl TreeRoute { pub fn enacted(&self) -> &[HashAndNumber] { &self.route[self.pivot + 1..] } + + /// Returns the last block. + pub fn last(&self) -> Option<&HashAndNumber> { + self.route.last() + } } /// Handles header metadata: hash, number, parent hash, etc. pub trait HeaderMetadata { /// Error used in case the header metadata is not found. - type Error; + type Error: std::error::Error; fn header_metadata( &self, @@ -220,14 +248,15 @@ pub struct HeaderMetadataCache { impl HeaderMetadataCache { /// Creates a new LRU header metadata cache with `capacity`. - pub fn new(capacity: usize) -> Self { + pub fn new(capacity: NonZeroUsize) -> Self { HeaderMetadataCache { cache: RwLock::new(LruCache::new(capacity)) } } } impl Default for HeaderMetadataCache { fn default() -> Self { - HeaderMetadataCache { cache: RwLock::new(LruCache::new(LRU_CACHE_SIZE)) } + let cap = NonZeroUsize::new(LRU_CACHE_SIZE).expect("cache capacity is not zero"); + HeaderMetadataCache { cache: RwLock::new(LruCache::new(cap)) } } } @@ -263,11 +292,11 @@ pub struct CachedHeaderMetadata { impl From<&Block::Header> for CachedHeaderMetadata { fn from(header: &Block::Header) -> Self { CachedHeaderMetadata { - hash: header.hash().clone(), - number: header.number().clone(), - parent: header.parent_hash().clone(), - state_root: header.state_root().clone(), - ancestor: header.parent_hash().clone(), + hash: header.hash(), + number: *header.number(), + parent: *header.parent_hash(), + state_root: *header.state_root(), + ancestor: *header.parent_hash(), } } } diff --git a/primitives/blockchain/src/lib.rs b/primitives/blockchain/src/lib.rs index cd36cabe15517..2fdef6cba9e5f 100644 --- a/primitives/blockchain/src/lib.rs +++ b/primitives/blockchain/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/aura/Cargo.toml b/primitives/consensus/aura/Cargo.toml index c228b88fd6570..51b20e4fb7e01 100644 --- a/primitives/consensus/aura/Cargo.toml +++ b/primitives/consensus/aura/Cargo.toml @@ -3,9 +3,9 @@ name = "sp-consensus-aura" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Primitives for Aura consensus" -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,30 +13,30 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../../application-crypto" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../std" } +async-trait = { version = "0.1.57", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../runtime" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../application-crypto" } +sp-consensus = { version = "0.10.0-dev", optional = true, path = "../common" } +sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../inherents" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../timestamp" } -sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" } -sp-consensus = { version = "0.10.0-dev", path = "../common", optional = true } -async-trait = { version = "0.1.50", optional = true } [features] default = ["std"] std = [ - "sp-application-crypto/std", + "async-trait", "codec/std", "scale-info/std", - "sp-std/std", "sp-api/std", - "sp-runtime/std", + "sp-application-crypto/std", + "sp-consensus", + "sp-consensus-slots/std", "sp-inherents/std", + "sp-runtime/std", + "sp-std/std", "sp-timestamp/std", - "sp-consensus-slots/std", - "sp-consensus", - "async-trait", ] diff --git a/primitives/consensus/aura/src/digests.rs b/primitives/consensus/aura/src/digests.rs index eaa29036d98a1..b71930b6c6b80 100644 --- a/primitives/consensus/aura/src/digests.rs +++ b/primitives/consensus/aura/src/digests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -25,7 +25,6 @@ use crate::AURA_ENGINE_ID; use codec::{Codec, Encode}; use sp_consensus_slots::Slot; use sp_runtime::generic::DigestItem; -use sp_std::fmt::Debug; /// A digest item which is usable with aura consensus. pub trait CompatibleDigestItem: Sized { @@ -42,10 +41,9 @@ pub trait CompatibleDigestItem: Sized { fn as_aura_pre_digest(&self) -> Option; } -impl CompatibleDigestItem for DigestItem +impl CompatibleDigestItem for DigestItem where Signature: Codec, - Hash: Debug + Send + Sync + Eq + Clone + Codec + 'static, { fn aura_seal(signature: Signature) -> Self { DigestItem::Seal(AURA_ENGINE_ID, signature.encode()) diff --git a/primitives/consensus/aura/src/inherents.rs b/primitives/consensus/aura/src/inherents.rs index 2a797b5d3f393..135ae2660b32f 100644 --- a/primitives/consensus/aura/src/inherents.rs +++ b/primitives/consensus/aura/src/inherents.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,12 +58,11 @@ impl InherentDataProvider { /// Creates the inherent data provider by calculating the slot from the given /// `timestamp` and `duration`. - pub fn from_timestamp_and_duration( + pub fn from_timestamp_and_slot_duration( timestamp: sp_timestamp::Timestamp, - duration: std::time::Duration, + slot_duration: sp_consensus_slots::SlotDuration, ) -> Self { - let slot = - InherentType::from((timestamp.as_duration().as_millis() / duration.as_millis()) as u64); + let slot = InherentType::from_timestamp(timestamp, slot_duration); Self { slot } } @@ -81,7 +80,7 @@ impl sp_std::ops::Deref for InherentDataProvider { #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { inherent_data.put_data(INHERENT_IDENTIFIER, &self.slot) } diff --git a/primitives/consensus/aura/src/lib.rs b/primitives/consensus/aura/src/lib.rs index e6a319c1d1590..2c6a97b934137 100644 --- a/primitives/consensus/aura/src/lib.rs +++ b/primitives/consensus/aura/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,7 +62,7 @@ pub mod ed25519 { pub type AuthorityId = app_ed25519::Public; } -pub use sp_consensus_slots::Slot; +pub use sp_consensus_slots::{Slot, SlotDuration}; /// The `ConsensusEngineId` of AuRa. pub const AURA_ENGINE_ID: ConsensusEngineId = [b'a', b'u', b'r', b'a']; @@ -89,34 +89,7 @@ sp_api::decl_runtime_apis! { /// Currently, only the value provided by this type at genesis will be used. fn slot_duration() -> SlotDuration; - // Return the current set of authorities. + /// Return the current set of authorities. fn authorities() -> Vec; } } - -/// Aura slot duration. -/// -/// Internally stored as milliseconds. -#[derive(sp_runtime::RuntimeDebug, Encode, Decode, PartialEq, Clone, Copy)] -pub struct SlotDuration(u64); - -impl SlotDuration { - /// Initialize from the given milliseconds. - pub fn from_millis(val: u64) -> Self { - Self(val) - } - - /// Returns the slot duration in milli seconds. - pub fn get(&self) -> u64 { - self.0 - } -} - -#[cfg(feature = "std")] -impl sp_consensus::SlotData for SlotDuration { - fn slot_duration(&self) -> std::time::Duration { - std::time::Duration::from_millis(self.0) - } - - const SLOT_KEY: &'static [u8] = b"aura_slot_duration"; -} diff --git a/primitives/consensus/babe/Cargo.toml b/primitives/consensus/babe/Cargo.toml index 5f6bfec219739..25cb8a2bf64da 100644 --- a/primitives/consensus/babe/Cargo.toml +++ b/primitives/consensus/babe/Cargo.toml @@ -3,9 +3,9 @@ name = "sp-consensus-babe" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Primitives for BABE consensus" -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,32 +13,33 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../../application-crypto" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +async-trait = { version = "0.1.57", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } merlin = { version = "2.0", default-features = false } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../std" } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../application-crypto" } sp-consensus = { version = "0.10.0-dev", optional = true, path = "../common" } sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" } -sp-consensus-vrf = { version = "0.10.0-dev", path = "../vrf", default-features = false } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../core" } +sp-consensus-vrf = { version = "0.10.0-dev", default-features = false, path = "../vrf" } +sp-core = { version = "7.0.0", default-features = false, path = "../../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../inherents" } -sp-keystore = { version = "0.10.0-dev", default-features = false, path = "../../keystore", optional = true } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../runtime" } -sp-timestamp = { version = "4.0.0-dev", path = "../../timestamp", optional = true } -serde = { version = "1.0.126", features = ["derive"], optional = true } -async-trait = { version = "0.1.50", optional = true } +sp-keystore = { version = "0.13.0", default-features = false, optional = true, path = "../../keystore" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } +sp-timestamp = { version = "4.0.0-dev", optional = true, path = "../../timestamp" } [features] default = ["std"] std = [ - "sp-application-crypto/std", + "async-trait", "codec/std", - "scale-info/std", "merlin/std", - "sp-std/std", + "scale-info/std", + "serde", "sp-api/std", + "sp-application-crypto/std", "sp-consensus", "sp-consensus-slots/std", "sp-consensus-vrf/std", @@ -46,7 +47,6 @@ std = [ "sp-inherents/std", "sp-keystore", "sp-runtime/std", - "serde", + "sp-std/std", "sp-timestamp", - "async-trait", ] diff --git a/primitives/consensus/babe/src/digests.rs b/primitives/consensus/babe/src/digests.rs index 470a028021ca1..1e4c820379d7a 100644 --- a/primitives/consensus/babe/src/digests.rs +++ b/primitives/consensus/babe/src/digests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,14 +21,15 @@ use super::{ AllowedSlots, AuthorityId, AuthorityIndex, AuthoritySignature, BabeAuthorityWeight, BabeEpochConfiguration, Slot, BABE_ENGINE_ID, }; -use codec::{Codec, Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; use sp_runtime::{DigestItem, RuntimeDebug}; use sp_std::vec::Vec; use sp_consensus_vrf::schnorrkel::{Randomness, VRFOutput, VRFProof}; /// Raw BABE primary slot assignment pre-digest. -#[derive(Clone, RuntimeDebug, Encode, Decode)] +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct PrimaryPreDigest { /// Authority index pub authority_index: super::AuthorityIndex, @@ -41,7 +42,7 @@ pub struct PrimaryPreDigest { } /// BABE secondary slot assignment pre-digest. -#[derive(Clone, RuntimeDebug, Encode, Decode)] +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct SecondaryPlainPreDigest { /// Authority index /// @@ -55,7 +56,7 @@ pub struct SecondaryPlainPreDigest { } /// BABE secondary deterministic slot assignment with VRF outputs. -#[derive(Clone, RuntimeDebug, Encode, Decode)] +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct SecondaryVRFPreDigest { /// Authority index pub authority_index: super::AuthorityIndex, @@ -70,7 +71,7 @@ pub struct SecondaryVRFPreDigest { /// A BABE pre-runtime digest. This contains all data required to validate a /// block and for the BABE runtime module. Slots can be assigned to a primary /// (VRF based) and to a secondary (slot number based). -#[derive(Clone, RuntimeDebug, Encode, Decode)] +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub enum PreDigest { /// A primary VRF-based slot assignment. #[codec(index = 1)] @@ -102,6 +103,11 @@ impl PreDigest { } } + /// Returns true if this pre-digest is for a primary slot assignment. + pub fn is_primary(&self) -> bool { + matches!(self, PreDigest::Primary(..)) + } + /// Returns the weight _added_ by this digest, not the cumulative weight /// of the chain. pub fn added_weight(&self) -> crate::BabeBlockWeight { @@ -111,11 +117,12 @@ impl PreDigest { } } - /// Returns the VRF output, if it exists. - pub fn vrf_output(&self) -> Option<&VRFOutput> { + /// Returns the VRF output and proof, if they exist. + pub fn vrf(&self) -> Option<(&VRFOutput, &VRFProof)> { match self { - PreDigest::Primary(primary) => Some(&primary.vrf_output), - PreDigest::SecondaryVRF(secondary) => Some(&secondary.vrf_output), + PreDigest::Primary(primary) => Some((&primary.vrf_output, &primary.vrf_proof)), + PreDigest::SecondaryVRF(secondary) => + Some((&secondary.vrf_output, &secondary.vrf_proof)), PreDigest::SecondaryPlain(_) => None, } } @@ -134,7 +141,9 @@ pub struct NextEpochDescriptor { /// Information about the next epoch config, if changed. This is broadcast in the first /// block of the epoch, and applies using the same rules as `NextEpochDescriptor`. -#[derive(Decode, Encode, PartialEq, Eq, Clone, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + Decode, Encode, PartialEq, Eq, Clone, RuntimeDebug, MaxEncodedLen, scale_info::TypeInfo, +)] pub enum NextConfigDescriptor { /// Version 1. #[codec(index = 1)] @@ -175,10 +184,7 @@ pub trait CompatibleDigestItem: Sized { fn as_next_config_descriptor(&self) -> Option; } -impl CompatibleDigestItem for DigestItem -where - Hash: Send + Sync + Eq + Clone + Codec + 'static, -{ +impl CompatibleDigestItem for DigestItem { fn babe_pre_digest(digest: PreDigest) -> Self { DigestItem::PreRuntime(BABE_ENGINE_ID, digest.encode()) } diff --git a/primitives/consensus/babe/src/inherents.rs b/primitives/consensus/babe/src/inherents.rs index cecd61998a4db..2634507968f8e 100644 --- a/primitives/consensus/babe/src/inherents.rs +++ b/primitives/consensus/babe/src/inherents.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ //! Inherents for BABE use sp_inherents::{Error, InherentData, InherentIdentifier}; - use sp_std::result::Result; /// The BABE inherent identifier. @@ -60,12 +59,11 @@ impl InherentDataProvider { /// Creates the inherent data provider by calculating the slot from the given /// `timestamp` and `duration`. - pub fn from_timestamp_and_duration( + pub fn from_timestamp_and_slot_duration( timestamp: sp_timestamp::Timestamp, - duration: std::time::Duration, + slot_duration: sp_consensus_slots::SlotDuration, ) -> Self { - let slot = - InherentType::from((timestamp.as_duration().as_millis() / duration.as_millis()) as u64); + let slot = InherentType::from_timestamp(timestamp, slot_duration); Self { slot } } @@ -88,7 +86,7 @@ impl sp_std::ops::Deref for InherentDataProvider { #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { inherent_data.put_data(INHERENT_IDENTIFIER, &self.slot) } diff --git a/primitives/consensus/babe/src/lib.rs b/primitives/consensus/babe/src/lib.rs index 4417670f4144b..621ab859b914f 100644 --- a/primitives/consensus/babe/src/lib.rs +++ b/primitives/consensus/babe/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ pub use sp_consensus_vrf::schnorrkel::{ Randomness, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH, }; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; @@ -79,7 +79,7 @@ pub const MEDIAN_ALGORITHM_CARDINALITY: usize = 1200; // arbitrary suggestion by /// The index of an authority. pub type AuthorityIndex = u32; -pub use sp_consensus_slots::Slot; +pub use sp_consensus_slots::{Slot, SlotDuration}; /// An equivocation proof for multiple block authorships on the same slot (i.e. double vote). pub type EquivocationProof = sp_consensus_slots::EquivocationProof; @@ -137,7 +137,7 @@ pub enum ConsensusLog { /// Configuration data used by the BABE consensus engine. #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub struct BabeGenesisConfigurationV1 { +pub struct BabeConfigurationV1 { /// The slot duration in milliseconds for BABE. Currently, only /// the value provided by this type at genesis will be used. /// @@ -156,7 +156,7 @@ pub struct BabeGenesisConfigurationV1 { pub c: (u64, u64), /// The authorities for the genesis epoch. - pub genesis_authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, + pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, /// The randomness for the genesis epoch. pub randomness: Randomness, @@ -166,13 +166,13 @@ pub struct BabeGenesisConfigurationV1 { pub secondary_slots: bool, } -impl From for BabeGenesisConfiguration { - fn from(v1: BabeGenesisConfigurationV1) -> Self { +impl From for BabeConfiguration { + fn from(v1: BabeConfigurationV1) -> Self { Self { slot_duration: v1.slot_duration, epoch_length: v1.epoch_length, c: v1.c, - genesis_authorities: v1.genesis_authorities, + authorities: v1.authorities, randomness: v1.randomness, allowed_slots: if v1.secondary_slots { AllowedSlots::PrimaryAndSecondaryPlainSlots @@ -185,7 +185,7 @@ impl From for BabeGenesisConfiguration { /// Configuration data used by the BABE consensus engine. #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub struct BabeGenesisConfiguration { +pub struct BabeConfiguration { /// The slot duration in milliseconds for BABE. Currently, only /// the value provided by this type at genesis will be used. /// @@ -203,18 +203,25 @@ pub struct BabeGenesisConfiguration { /// of a slot being empty. pub c: (u64, u64), - /// The authorities for the genesis epoch. - pub genesis_authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, + /// The authorities + pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, - /// The randomness for the genesis epoch. + /// The randomness pub randomness: Randomness, /// Type of allowed slots. pub allowed_slots: AllowedSlots, } +impl BabeConfiguration { + /// Convenience method to get the slot duration as a `SlotDuration` value. + pub fn slot_duration(&self) -> SlotDuration { + SlotDuration::from_millis(self.slot_duration) + } +} + /// Types of allowed slots. -#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum AllowedSlots { /// Only allow primary slots. @@ -237,17 +244,8 @@ impl AllowedSlots { } } -#[cfg(feature = "std")] -impl sp_consensus::SlotData for BabeGenesisConfiguration { - fn slot_duration(&self) -> std::time::Duration { - std::time::Duration::from_millis(self.slot_duration) - } - - const SLOT_KEY: &'static [u8] = b"babe_configuration"; -} - -/// Configuration data used by the BABE consensus engine. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +/// Configuration data used by the BABE consensus engine that may change with epochs. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct BabeEpochConfiguration { /// A constant value that is used in the threshold calculation formula. @@ -366,12 +364,12 @@ sp_api::decl_runtime_apis! { /// API necessary for block authorship with BABE. #[api_version(2)] pub trait BabeApi { - /// Return the genesis configuration for BABE. The configuration is only read on genesis. - fn configuration() -> BabeGenesisConfiguration; + /// Return the configuration for BABE. + fn configuration() -> BabeConfiguration; /// Return the configuration for BABE. Version 1. #[changed_in(2)] - fn configuration() -> BabeGenesisConfigurationV1; + fn configuration() -> BabeConfigurationV1; /// Returns the slot that started the current epoch. fn current_epoch_start() -> Slot; diff --git a/primitives/consensus/common/Cargo.toml b/primitives/consensus/common/Cargo.toml index ecfc1c1b31826..6df4e5c7232a8 100644 --- a/primitives/consensus/common/Cargo.toml +++ b/primitives/consensus/common/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-consensus" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Common utilities for building and using consensus engines in substrate." documentation = "https://docs.rs/sp-consensus/" @@ -14,23 +14,23 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = "0.1.42" -codec = { package = "parity-scale-codec", version = "2.0.0", features = [ - "derive", +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", ] } -futures = { version = "0.3.1", features = ["thread-pool"] } -log = "0.4.8" -sp-core = { path = "../../core", version = "4.0.0-dev" } -sp-inherents = { version = "4.0.0-dev", path = "../../inherents" } -sp-state-machine = { version = "0.10.0-dev", path = "../../state-machine" } +futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" -sp-std = { version = "4.0.0-dev", path = "../../std" } -sp-version = { version = "4.0.0-dev", path = "../../version" } -sp-runtime = { version = "4.0.0-dev", path = "../../runtime" } -thiserror = "1.0.21" +log = "0.4.17" +thiserror = "1.0.30" +sp-core = { version = "7.0.0", path = "../../core" } +sp-inherents = { version = "4.0.0-dev", path = "../../inherents" } +sp-runtime = { version = "7.0.0", path = "../../runtime" } +sp-state-machine = { version = "0.13.0", path = "../../state-machine" } +sp-std = { version = "5.0.0", path = "../../std" } +sp-version = { version = "5.0.0", path = "../../version" } [dev-dependencies] -futures = "0.3.9" +futures = "0.3.21" sp-test-primitives = { version = "2.0.0", path = "../../test-primitives" } [features] diff --git a/primitives/consensus/common/src/block_validation.rs b/primitives/consensus/common/src/block_validation.rs index 54a70a402b060..71f3a80b27a64 100644 --- a/primitives/consensus/common/src/block_validation.rs +++ b/primitives/consensus/common/src/block_validation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/common/src/error.rs b/primitives/consensus/common/src/error.rs index 546f30d3e8202..0656b5761fb38 100644 --- a/primitives/consensus/common/src/error.rs +++ b/primitives/consensus/common/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -86,13 +86,13 @@ pub enum Error { CannotSign(Vec, String), } -impl core::convert::From for Error { +impl From for Error { fn from(p: Public) -> Self { Self::InvalidAuthority(p) } } -impl core::convert::From for Error { +impl From for Error { fn from(s: String) -> Self { Self::StateUnavailable(s) } diff --git a/primitives/consensus/common/src/evaluation.rs b/primitives/consensus/common/src/evaluation.rs deleted file mode 100644 index 19be5e5526349..0000000000000 --- a/primitives/consensus/common/src/evaluation.rs +++ /dev/null @@ -1,69 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 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. - -//! Block evaluation and evaluation errors. - -use codec::Encode; -use sp_runtime::traits::{Block as BlockT, CheckedConversion, Header as HeaderT, One}; - -// This is just a best effort to encode the number. None indicated that it's too big to encode -// in a u128. -type BlockNumber = Option; - -/// Result type alias. -pub type Result = std::result::Result; - -/// Error type. -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// Proposal provided not a block. - #[error("Proposal provided not a block: decoding error: {0}")] - BadProposalFormat(#[from] codec::Error), - /// Proposal had wrong parent hash. - #[error("Proposal had wrong parent hash. Expected {expected:?}, got {got:?}")] - WrongParentHash { expected: String, got: String }, - /// Proposal had wrong number. - #[error("Proposal had wrong number. Expected {expected:?}, got {got:?}")] - WrongNumber { expected: BlockNumber, got: BlockNumber }, -} - -/// Attempt to evaluate a substrate block as a node block, returning error -/// upon any initial validity checks failing. -pub fn evaluate_initial( - proposal: &Block, - parent_hash: &::Hash, - parent_number: <::Header as HeaderT>::Number, -) -> Result<()> { - let encoded = Encode::encode(proposal); - let proposal = Block::decode(&mut &encoded[..]).map_err(|e| Error::BadProposalFormat(e))?; - - if *parent_hash != *proposal.header().parent_hash() { - return Err(Error::WrongParentHash { - expected: format!("{:?}", *parent_hash), - got: format!("{:?}", proposal.header().parent_hash()), - }) - } - - if parent_number + One::one() != *proposal.header().number() { - return Err(Error::WrongNumber { - expected: parent_number.checked_into::().map(|x| x + 1), - got: (*proposal.header().number()).checked_into::(), - }) - } - - Ok(()) -} diff --git a/primitives/consensus/common/src/lib.rs b/primitives/consensus/common/src/lib.rs index d7979baf47c11..458a5eee259a9 100644 --- a/primitives/consensus/common/src/lib.rs +++ b/primitives/consensus/common/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,14 +25,13 @@ use std::{sync::Arc, time::Duration}; use futures::prelude::*; use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, DigestFor, HashFor, NumberFor}, + traits::{Block as BlockT, HashFor}, + Digest, }; use sp_state_machine::StorageProof; pub mod block_validation; pub mod error; -pub mod evaluation; mod select_chain; pub use self::error::Error; @@ -97,7 +96,7 @@ pub trait Environment { + Unpin + 'static; /// Error which can occur upon creation. - type Error: From + std::fmt::Debug + 'static; + type Error: From + std::error::Error + 'static; /// Initialize the proposal logic on top of a specific header. Provide /// the authorities at that header. @@ -111,8 +110,7 @@ pub struct Proposal { /// Proof that was recorded while building the block. pub proof: Proof, /// The storage changes while building this block. - pub storage_changes: - sp_state_machine::StorageChanges, NumberFor>, + pub storage_changes: sp_state_machine::StorageChanges>, } /// Error that is returned when [`ProofRecording`] requested to record a proof, @@ -169,7 +167,7 @@ impl ProofRecording for EnableProofRecording { const ENABLED: bool = true; fn into_proof(proof: Option) -> Result { - proof.ok_or_else(|| NoProofRecorded) + proof.ok_or(NoProofRecorded) } } @@ -191,7 +189,7 @@ mod private { /// Proposers are generic over bits of "consensus data" which are engine-specific. pub trait Proposer { /// Error type which can occur when proposing or evaluating. - type Error: From + std::fmt::Debug + 'static; + type Error: From + std::error::Error + 'static; /// The transaction type used by the backend. type Transaction: Default + Send + 'static; /// Future that resolves to a committed proposal with an optional proof. @@ -224,7 +222,7 @@ pub trait Proposer { fn propose( self, inherent_data: InherentData, - inherent_digests: DigestFor, + inherent_digests: Digest, max_duration: Duration, block_size_limit: Option, ) -> Self::Proposal; @@ -237,10 +235,10 @@ pub trait Proposer { pub trait SyncOracle { /// Whether the synchronization service is undergoing major sync. /// Returns true if so. - fn is_major_syncing(&mut self) -> bool; + fn is_major_syncing(&self) -> bool; /// Whether the synchronization service is offline. /// Returns true if so. - fn is_offline(&mut self) -> bool; + fn is_offline(&self) -> bool; } /// A synchronization oracle for when there is no network. @@ -248,10 +246,10 @@ pub trait SyncOracle { pub struct NoNetwork; impl SyncOracle for NoNetwork { - fn is_major_syncing(&mut self) -> bool { + fn is_major_syncing(&self) -> bool { false } - fn is_offline(&mut self) -> bool { + fn is_offline(&self) -> bool { false } } @@ -259,80 +257,13 @@ impl SyncOracle for NoNetwork { impl SyncOracle for Arc where T: ?Sized, - for<'r> &'r T: SyncOracle, + T: SyncOracle, { - fn is_major_syncing(&mut self) -> bool { - <&T>::is_major_syncing(&mut &**self) + fn is_major_syncing(&self) -> bool { + T::is_major_syncing(self) } - fn is_offline(&mut self) -> bool { - <&T>::is_offline(&mut &**self) + fn is_offline(&self) -> bool { + T::is_offline(self) } } - -/// Checks if the current active native block authoring implementation can author with the runtime -/// at the given block. -pub trait CanAuthorWith { - /// See trait docs for more information. - /// - /// # Return - /// - /// - Returns `Ok(())` when authoring is supported. - /// - Returns `Err(_)` when authoring is not supported. - fn can_author_with(&self, at: &BlockId) -> Result<(), String>; -} - -/// Checks if the node can author blocks by using -/// [`NativeVersion::can_author_with`](sp_version::NativeVersion::can_author_with). -#[derive(Clone)] -pub struct CanAuthorWithNativeVersion(T); - -impl CanAuthorWithNativeVersion { - /// Creates a new instance of `Self`. - pub fn new(inner: T) -> Self { - Self(inner) - } -} - -impl + sp_version::GetNativeVersion, Block: BlockT> - CanAuthorWith for CanAuthorWithNativeVersion -{ - fn can_author_with(&self, at: &BlockId) -> Result<(), String> { - match self.0.runtime_version(at) { - Ok(version) => self.0.native_version().can_author_with(&version), - Err(e) => Err(format!( - "Failed to get runtime version at `{}` and will disable authoring. Error: {}", - at, e, - )), - } - } -} - -/// Returns always `true` for `can_author_with`. This is useful for tests. -#[derive(Clone)] -pub struct AlwaysCanAuthor; - -impl CanAuthorWith for AlwaysCanAuthor { - fn can_author_with(&self, _: &BlockId) -> Result<(), String> { - Ok(()) - } -} - -/// Never can author. -#[derive(Clone)] -pub struct NeverCanAuthor; - -impl CanAuthorWith for NeverCanAuthor { - fn can_author_with(&self, _: &BlockId) -> Result<(), String> { - Err("Authoring is always disabled.".to_string()) - } -} - -/// A type from which a slot duration can be obtained. -pub trait SlotData { - /// Gets the slot duration. - fn slot_duration(&self) -> sp_std::time::Duration; - - /// The static slot key - const SLOT_KEY: &'static [u8]; -} diff --git a/primitives/consensus/common/src/select_chain.rs b/primitives/consensus/common/src/select_chain.rs index 5408fc86b7bd4..f366cd34c51ea 100644 --- a/primitives/consensus/common/src/select_chain.rs +++ b/primitives/consensus/common/src/select_chain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,7 +50,7 @@ pub trait SelectChain: Sync + Send + Clone { &self, target_hash: ::Hash, _maybe_max_number: Option>, - ) -> Result::Hash>, Error> { - Ok(Some(target_hash)) + ) -> Result<::Hash, Error> { + Ok(target_hash) } } diff --git a/primitives/consensus/pow/Cargo.toml b/primitives/consensus/pow/Cargo.toml index f93eeca2fb24e..495372089e195 100644 --- a/primitives/consensus/pow/Cargo.toml +++ b/primitives/consensus/pow/Cargo.toml @@ -3,9 +3,9 @@ name = "sp-consensus-pow" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Primitives for Aura consensus" -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,18 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../runtime" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../core" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +sp-core = { version = "7.0.0", default-features = false, path = "../../core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } [features] default = ["std"] std = [ - "sp-std/std", + "codec/std", "sp-api/std", - "sp-runtime/std", "sp-core/std", - "codec/std", + "sp-runtime/std", + "sp-std/std", ] diff --git a/primitives/consensus/pow/src/lib.rs b/primitives/consensus/pow/src/lib.rs index ac8bc589c136f..fe10ee808db98 100644 --- a/primitives/consensus/pow/src/lib.rs +++ b/primitives/consensus/pow/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/slots/Cargo.toml b/primitives/consensus/slots/Cargo.toml index 3ad204f973961..a7b941b3f498d 100644 --- a/primitives/consensus/slots/Cargo.toml +++ b/primitives/consensus/slots/Cargo.toml @@ -3,9 +3,9 @@ name = "sp-consensus-slots" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Primitives for slots-based consensus" -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,16 +13,22 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../runtime" } -sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../../arithmetic" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } +serde = { version = "1.0", features = ["derive"], optional = true } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../arithmetic" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } +sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../timestamp" } [features] default = ["std"] std = [ "codec/std", "scale-info/std", - "sp-runtime/std", + "serde", "sp-arithmetic/std", + "sp-runtime/std", + "sp-std/std", + "sp-timestamp/std", ] diff --git a/primitives/consensus/slots/src/lib.rs b/primitives/consensus/slots/src/lib.rs index 89b57dca83082..21b3cad1e7167 100644 --- a/primitives/consensus/slots/src/lib.rs +++ b/primitives/consensus/slots/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,11 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +use sp_timestamp::Timestamp; /// Unit type wrapper that represents a slot. #[derive(Debug, Encode, MaxEncodedLen, Decode, Eq, Clone, Copy, Default, Ord, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct Slot(u64); impl core::ops::Deref for Slot { @@ -63,6 +65,11 @@ impl + Copy> core::cmp::PartialOrd for Slot { } impl Slot { + /// Create a new slot by calculating it from the given timestamp and slot duration. + pub const fn from_timestamp(timestamp: Timestamp, slot_duration: SlotDuration) -> Self { + Slot(timestamp.as_millis() / slot_duration.as_millis()) + } + /// Saturating addition. pub fn saturating_add>(self, rhs: T) -> Self { Self(self.0.saturating_add(rhs.into())) @@ -93,6 +100,32 @@ impl From for u64 { } } +/// A slot duration defined in milliseconds. +#[derive(Clone, Copy, Debug, Encode, Decode, Hash, PartialOrd, Ord, PartialEq, Eq, TypeInfo)] +pub struct SlotDuration(u64); + +impl SlotDuration { + /// Initialize from the given milliseconds. + pub const fn from_millis(millis: u64) -> Self { + Self(millis) + } +} + +impl SlotDuration { + /// Returns `self` as a `u64` representing the duration in milliseconds. + pub const fn as_millis(&self) -> u64 { + self.0 + } +} + +#[cfg(feature = "std")] +impl SlotDuration { + /// Returns `self` as [`sp_std::time::Duration`]. + pub const fn as_duration(&self) -> sp_std::time::Duration { + sp_std::time::Duration::from_millis(self.0) + } +} + /// Represents an equivocation proof. An equivocation happens when a validator /// produces more than one block on the same slot. The proof of equivocation /// are the given distinct headers that were signed by the validator and which diff --git a/primitives/consensus/vrf/Cargo.toml b/primitives/consensus/vrf/Cargo.toml index 124cbf423f068..7159da2aa1883 100644 --- a/primitives/consensus/vrf/Cargo.toml +++ b/primitives/consensus/vrf/Cargo.toml @@ -3,28 +3,30 @@ name = "sp-consensus-vrf" version = "0.10.0-dev" authors = ["Parity Technologies "] description = "Primitives for VRF based consensus" -edition = "2018" +edition = "2021" license = "Apache-2.0" repository = "https://github.com/paritytech/substrate/" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { version = "2.0.0", package = "parity-scale-codec", default-features = false } -schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false } -sp-std = { version = "4.0.0-dev", path = "../../std", default-features = false } -sp-core = { version = "4.0.0-dev", path = "../../core", default-features = false } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false } +schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } +sp-core = { version = "7.0.0", default-features = false, path = "../../core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } [features] default = ["std"] std = [ "codec/std", + "scale-info/std", "schnorrkel/std", - "sp-std/std", "sp-core/std", "sp-runtime/std", + "sp-std/std", ] diff --git a/primitives/consensus/vrf/src/lib.rs b/primitives/consensus/vrf/src/lib.rs index 19391c6c1c84f..07e3f2c319706 100644 --- a/primitives/consensus/vrf/src/lib.rs +++ b/primitives/consensus/vrf/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/vrf/src/schnorrkel.rs b/primitives/consensus/vrf/src/schnorrkel.rs index 687e0bd231820..8666de6c4bc0c 100644 --- a/primitives/consensus/vrf/src/schnorrkel.rs +++ b/primitives/consensus/vrf/src/schnorrkel.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +17,11 @@ //! Schnorrkel-based VRF. -use codec::{Decode, Encode, EncodeLike}; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use scale_info::TypeInfo; use schnorrkel::errors::MultiSignatureStage; use sp_core::U512; use sp_std::{ - convert::TryFrom, ops::{Deref, DerefMut}, prelude::*, }; @@ -66,6 +66,20 @@ impl Decode for VRFOutput { } } +impl MaxEncodedLen for VRFOutput { + fn max_encoded_len() -> usize { + <[u8; VRF_OUTPUT_LENGTH]>::max_encoded_len() + } +} + +impl TypeInfo for VRFOutput { + type Identity = [u8; VRF_OUTPUT_LENGTH]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } +} + impl TryFrom<[u8; VRF_OUTPUT_LENGTH]> for VRFOutput { type Error = SignatureError; @@ -118,6 +132,20 @@ impl Decode for VRFProof { } } +impl MaxEncodedLen for VRFProof { + fn max_encoded_len() -> usize { + <[u8; VRF_PROOF_LENGTH]>::max_encoded_len() + } +} + +impl TypeInfo for VRFProof { + type Identity = [u8; VRF_PROOF_LENGTH]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } +} + impl TryFrom<[u8; VRF_PROOF_LENGTH]> for VRFProof { type Error = SignatureError; diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 73c3d454ed584..bfec09a13c135 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-core" -version = "4.0.0-dev" +version = "7.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Shareable Substrate types." documentation = "https://docs.rs/sp-core" @@ -13,68 +13,59 @@ documentation = "https://docs.rs/sp-core" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", "max-encoded-len", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -log = { version = "0.4.11", default-features = false } -serde = { version = "1.0.126", optional = true, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +serde = { version = "1.0.136", optional = true, features = ["derive"] } byteorder = { version = "1.3.2", default-features = false } -primitive-types = { version = "0.10.1", default-features = false, features = [ - "codec", - "scale-info" -] } -impl-serde = { version = "0.3.0", optional = true } -wasmi = { version = "0.9.0", optional = true } +primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info"] } +impl-serde = { version = "0.4.0", optional = true } +wasmi = { version = "0.13", optional = true } hash-db = { version = "0.15.2", default-features = false } hash256-std-hasher = { version = "0.15.2", default-features = false } -base58 = { version = "0.1.0", optional = true } +base58 = { version = "0.2.0", optional = true } rand = { version = "0.7.3", optional = true, features = ["small_rng"] } -substrate-bip39 = { version = "0.4.2", optional = true } -tiny-bip39 = { version = "0.8", optional = true } -regex = { version = "1.4.2", optional = true } +substrate-bip39 = { version = "0.4.4", optional = true } +tiny-bip39 = { version = "0.8.2", optional = true } +regex = { version = "1.6.0", optional = true } num-traits = { version = "0.2.8", default-features = false } -zeroize = { version = "1.4.1", default-features = false } -secrecy = { version = "0.7.0", default-features = false } +zeroize = { version = "1.4.3", default-features = false } +secrecy = { version = "0.8.0", default-features = false } lazy_static = { version = "1.4.0", default-features = false, optional = true } -parking_lot = { version = "0.11.1", optional = true } -sp-debug-derive = { version = "3.0.0", path = "../debug-derive" } -sp-externalities = { version = "0.10.0-dev", optional = true, path = "../externalities" } -sp-storage = { version = "4.0.0-dev", default-features = false, path = "../storage" } -parity-util-mem = { version = "0.10.0", default-features = false, features = [ - "primitive-types", -] } -futures = { version = "0.3.1", optional = true } +parking_lot = { version = "0.12.1", optional = true } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } +sp-storage = { version = "7.0.0", default-features = false, path = "../storage" } +sp-externalities = { version = "0.13.0", optional = true, path = "../externalities" } +futures = { version = "0.3.21", optional = true } dyn-clonable = { version = "0.9.0", optional = true } -thiserror = { version = "1.0.21", optional = true } +thiserror = { version = "1.0.30", optional = true } +bitflags = "1.3" # full crypto -ed25519-dalek = { version = "1.0.1", default-features = false, features = [ - "u64_backend", - "alloc", -], optional = true } -blake2-rfc = { version = "0.2.18", default-features = false, optional = true } -tiny-keccak = { version = "2.0.1", features = ["keccak"], optional = true } +array-bytes = { version = "4.1", optional = true } +ed25519-zebra = { version = "3.1.0", default-features = false, optional = true } +blake2 = { version = "0.10.4", default-features = false, optional = true } schnorrkel = { version = "0.9.1", features = [ "preaudit_deprecated", "u64_backend", ], default-features = false, optional = true } -sha2 = { version = "0.9.2", default-features = false, optional = true } -hex = { version = "0.4", default-features = false, optional = true } -twox-hash = { version = "1.5.0", default-features = false, optional = true } -libsecp256k1 = { version = "0.6", default-features = false, features = ["hmac", "static-context"], optional = true } +libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } merlin = { version = "2.0", default-features = false, optional = true } - -sp-runtime-interface = { version = "4.0.0-dev", default-features = false, path = "../runtime-interface" } +secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"], optional = true } +ss58-registry = { version = "1.34.0", default-features = false } +sp-core-hashing = { version = "5.0.0", path = "./hashing", default-features = false, optional = true } +sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../runtime-interface" } [dev-dependencies] -sp-serializer = { version = "3.0.0", path = "../serializer" } -hex-literal = "0.3.1" +sp-serializer = { version = "4.0.0-dev", path = "../serializer" } rand = "0.7.2" criterion = "0.3.3" serde_json = "1.0" +sp-core-hashing-proc-macro = { version = "5.0.0", path = "./hashing/proc-macro" } [[bench]] name = "bench" @@ -86,6 +77,7 @@ bench = false [features] default = ["std"] std = [ + "merlin?/std", "full_crypto", "log/std", "thiserror", @@ -103,24 +95,25 @@ std = [ "hash-db/std", "sp-std/std", "serde", - "twox-hash/std", - "blake2-rfc/std", - "ed25519-dalek/std", - "hex/std", + "blake2/std", + "array-bytes", + "ed25519-zebra/std", "base58", "substrate-bip39", "tiny-bip39", "byteorder/std", "rand", - "sha2/std", "schnorrkel/std", "regex", "num-traits/std", - "tiny-keccak", + "secp256k1/std", + "secp256k1/global-context", + "sp-core-hashing/std", "sp-debug-derive/std", "sp-externalities", "sp-storage/std", "sp-runtime-interface/std", + "ss58-registry/std", "zeroize/alloc", "secrecy/alloc", "futures", @@ -133,14 +126,13 @@ std = [ # or Intel SGX. # For the regular wasm runtime builds this should not be used. full_crypto = [ - "ed25519-dalek", - "blake2-rfc", - "tiny-keccak", + "array-bytes", + "ed25519-zebra", + "blake2", "schnorrkel", - "hex", - "sha2", - "twox-hash", "libsecp256k1", + "secp256k1", + "sp-core-hashing", "sp-runtime-interface/disable_target_static_assertions", "merlin", ] diff --git a/primitives/core/benches/bench.rs b/primitives/core/benches/bench.rs index 44bcd657ba3f0..53421278dca26 100644 --- a/primitives/core/benches/bench.rs +++ b/primitives/core/benches/bench.rs @@ -79,7 +79,7 @@ fn bench_hash_128_dyn_size(c: &mut Criterion) { fn bench_ed25519(c: &mut Criterion) { let mut group = c.benchmark_group("ed25519"); - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::ed25519::Pair::generate().0; group.bench_function(BenchmarkId::new("signing", format!("{}", msg_size)), |b| { @@ -87,7 +87,7 @@ fn bench_ed25519(c: &mut Criterion) { }); } - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::ed25519::Pair::generate().0; let sig = key.sign(&msg); @@ -103,7 +103,7 @@ fn bench_ed25519(c: &mut Criterion) { fn bench_sr25519(c: &mut Criterion) { let mut group = c.benchmark_group("sr25519"); - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::sr25519::Pair::generate().0; group.bench_function(BenchmarkId::new("signing", format!("{}", msg_size)), |b| { @@ -111,7 +111,7 @@ fn bench_sr25519(c: &mut Criterion) { }); } - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::sr25519::Pair::generate().0; let sig = key.sign(&msg); @@ -127,7 +127,7 @@ fn bench_sr25519(c: &mut Criterion) { fn bench_ecdsa(c: &mut Criterion) { let mut group = c.benchmark_group("ecdsa"); - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::ecdsa::Pair::generate().0; group.bench_function(BenchmarkId::new("signing", format!("{}", msg_size)), |b| { @@ -135,7 +135,7 @@ fn bench_ecdsa(c: &mut Criterion) { }); } - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::ecdsa::Pair::generate().0; let sig = key.sign(&msg); diff --git a/primitives/core/hashing/Cargo.toml b/primitives/core/hashing/Cargo.toml new file mode 100644 index 0000000000000..1bb67ffff5142 --- /dev/null +++ b/primitives/core/hashing/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "sp-core-hashing" +version = "5.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Primitive core crate hashing implementation." +documentation = "https://docs.rs/sp-core-hashing" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +blake2 = { version = "0.10.4", default-features = false } +byteorder = { version = "1.3.2", default-features = false } +digest = { version = "0.10.3", default-features = false } +sha2 = { version = "0.10.2", default-features = false } +sha3 = { version = "0.10.0", default-features = false } +twox-hash = { version = "1.6.3", default-features = false, features = ["digest_0_10"] } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } + +[features] +default = ["std"] +std = [ + "digest/std", + "blake2/std", + "byteorder/std", + "sha2/std", + "sha3/std", + "sp-std/std", + "twox-hash/std", +] diff --git a/primitives/core/hashing/proc-macro/Cargo.toml b/primitives/core/hashing/proc-macro/Cargo.toml new file mode 100644 index 0000000000000..6d9747de871c7 --- /dev/null +++ b/primitives/core/hashing/proc-macro/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "sp-core-hashing-proc-macro" +version = "5.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "This crate provides procedural macros for calculating static hash." +documentation = "https://docs.rs/sp-core-hashing-proc-macro" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.37" +quote = "1.0.6" +syn = { version = "1.0.98", features = ["full", "parsing"] } +sp-core-hashing = { version = "5.0.0", default-features = false, path = "../" } diff --git a/primitives/core/hashing/proc-macro/src/impls.rs b/primitives/core/hashing/proc-macro/src/impls.rs new file mode 100644 index 0000000000000..3058cf019b143 --- /dev/null +++ b/primitives/core/hashing/proc-macro/src/impls.rs @@ -0,0 +1,124 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 quote::quote; +use syn::parse::{Parse, ParseStream}; + +use proc_macro::TokenStream; + +pub(super) struct InputBytes(pub Vec); + +pub(super) struct MultipleInputBytes(pub Vec>); + +impl MultipleInputBytes { + pub(super) fn concatenated(mut self) -> Vec { + if self.0.is_empty() { + Vec::new() + } else { + let mut result = core::mem::take(&mut self.0[0]); + for other in self.0[1..].iter_mut() { + result.append(other); + } + result + } + } +} + +impl Parse for InputBytes { + fn parse(input: ParseStream) -> syn::Result { + match syn::ExprArray::parse(input) { + Ok(array) => { + let mut bytes = Vec::::new(); + for expr in array.elems.iter() { + match expr { + syn::Expr::Lit(lit) => match &lit.lit { + syn::Lit::Int(b) => bytes.push(b.base10_parse()?), + syn::Lit::Byte(b) => bytes.push(b.value()), + _ => + return Err(syn::Error::new( + input.span(), + "Expected array of u8 elements.".to_string(), + )), + }, + _ => + return Err(syn::Error::new( + input.span(), + "Expected array of u8 elements.".to_string(), + )), + } + } + return Ok(InputBytes(bytes)) + }, + Err(_e) => (), + } + // use rust names as a vec of their utf8 bytecode. + match syn::Ident::parse(input) { + Ok(ident) => return Ok(InputBytes(ident.to_string().as_bytes().to_vec())), + Err(_e) => (), + } + Ok(InputBytes(syn::LitByteStr::parse(input)?.value())) + } +} + +impl Parse for MultipleInputBytes { + fn parse(input: ParseStream) -> syn::Result { + let elts = + syn::punctuated::Punctuated::::parse_terminated(input)?; + Ok(MultipleInputBytes(elts.into_iter().map(|elt| elt.0).collect())) + } +} + +pub(super) fn twox_64(bytes: Vec) -> TokenStream { + bytes_to_array(sp_core_hashing::twox_64(bytes.as_slice())) +} + +pub(super) fn twox_128(bytes: Vec) -> TokenStream { + bytes_to_array(sp_core_hashing::twox_128(bytes.as_slice())) +} + +pub(super) fn blake2b_512(bytes: Vec) -> TokenStream { + bytes_to_array(sp_core_hashing::blake2_512(bytes.as_slice())) +} + +pub(super) fn blake2b_256(bytes: Vec) -> TokenStream { + bytes_to_array(sp_core_hashing::blake2_256(bytes.as_slice())) +} + +pub(super) fn blake2b_64(bytes: Vec) -> TokenStream { + bytes_to_array(sp_core_hashing::blake2_64(bytes.as_slice())) +} + +pub(super) fn keccak_256(bytes: Vec) -> TokenStream { + bytes_to_array(sp_core_hashing::keccak_256(bytes.as_slice())) +} + +pub(super) fn keccak_512(bytes: Vec) -> TokenStream { + bytes_to_array(sp_core_hashing::keccak_512(bytes.as_slice())) +} + +pub(super) fn sha2_256(bytes: Vec) -> TokenStream { + bytes_to_array(sp_core_hashing::sha2_256(bytes.as_slice())) +} + +fn bytes_to_array(bytes: impl IntoIterator) -> TokenStream { + let bytes = bytes.into_iter(); + + quote!( + [ #( #bytes ),* ] + ) + .into() +} diff --git a/primitives/core/hashing/proc-macro/src/lib.rs b/primitives/core/hashing/proc-macro/src/lib.rs new file mode 100644 index 0000000000000..2db292d8dc0cb --- /dev/null +++ b/primitives/core/hashing/proc-macro/src/lib.rs @@ -0,0 +1,129 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Macros to calculate constant hash bytes result. +//! +//! Macros from this crate does apply a specific hash function on input. +//! Input can be literal byte array as `b"content"` or array of bytes +//! as `[1, 2, 3]`. +//! Rust identifier can also be use, in this case we use their utf8 string +//! byte representation, for instance if the ident is `MyStruct`, then +//! `b"MyStruct"` will be hashed. +//! If multiple arguments comma separated are passed, they are concatenated +//! then hashed. +//! +//! Examples: +//! +//! ```rust +//! assert_eq!( +//! sp_core_hashing_proc_macro::blake2b_256!(b"test"), +//! sp_core_hashing::blake2_256(b"test"), +//! ); +//! assert_eq!( +//! sp_core_hashing_proc_macro::blake2b_256!([1u8]), +//! sp_core_hashing::blake2_256(&[1u8]), +//! ); +//! assert_eq!( +//! sp_core_hashing_proc_macro::blake2b_256!([1, 2, 3]), +//! sp_core_hashing::blake2_256(&[1, 2, 3]), +//! ); +//! assert_eq!( +//! sp_core_hashing_proc_macro::blake2b_256!(identifier), +//! sp_core_hashing::blake2_256(b"identifier"), +//! ); +//! assert_eq!( +//! sp_core_hashing_proc_macro::blake2b_256!(identifier, b"/string"), +//! sp_core_hashing::blake2_256(b"identifier/string"), +//! ); +//! ``` + +mod impls; + +use impls::MultipleInputBytes; +use proc_macro::TokenStream; + +/// Process a Blake2 64-bit hash of bytes parameter outputs a `[u8; 8]`. +/// Multiple inputs are concatenated before hashing. +/// Input can be identifier (name of identifier as bytes is used), byte string or +/// array of bytes. +#[proc_macro] +pub fn blake2b_64(input: TokenStream) -> TokenStream { + impls::blake2b_64(syn::parse_macro_input!(input as MultipleInputBytes).concatenated()) +} + +/// Apply a Blake2 256-bit hash of bytes parameter, outputs a `[u8; 32]`. +/// Multiple inputs are concatenated before hashing. +/// Input can be identifier (name of identifier as bytes is used), byte string or +/// array of bytes. +#[proc_macro] +pub fn blake2b_256(input: TokenStream) -> TokenStream { + impls::blake2b_256(syn::parse_macro_input!(input as MultipleInputBytes).concatenated()) +} + +/// Apply a Blake2 512-bit hash of bytes parameter, outputs a `[u8; 64]`. +/// Multiple inputs are concatenated before hashing. +/// Input can be identifier (name of identifier as bytes is used), byte string or +/// array of bytes. +#[proc_macro] +pub fn blake2b_512(input: TokenStream) -> TokenStream { + impls::blake2b_512(syn::parse_macro_input!(input as MultipleInputBytes).concatenated()) +} + +/// Apply a XX 64-bit hash on its bytes parameter, outputs a `[u8; 8]`. +/// Multiple inputs are concatenated before hashing. +/// Input can be identifier (name of identifier as bytes is used), byte string or +/// array of bytes. +#[proc_macro] +pub fn twox_64(input: TokenStream) -> TokenStream { + impls::twox_64(syn::parse_macro_input!(input as MultipleInputBytes).concatenated()) +} + +/// Apply a XX 128-bit hash on its bytes parameter, outputs a `[u8; 16]`. +/// Multiple inputs are concatenated before hashing. +/// Input can be identifier (name of identifier as bytes is used), byte string or +/// array of bytes. +#[proc_macro] +pub fn twox_128(input: TokenStream) -> TokenStream { + impls::twox_128(syn::parse_macro_input!(input as MultipleInputBytes).concatenated()) +} + +/// Apply a keccak 256-bit hash on its bytes parameter, outputs a `[u8; 32]`. +/// Multiple inputs are concatenated before hashing. +/// Input can be identifier (name of identifier as bytes is used), byte string or +/// array of bytes. +#[proc_macro] +pub fn keccak_256(input: TokenStream) -> TokenStream { + impls::keccak_256(syn::parse_macro_input!(input as MultipleInputBytes).concatenated()) +} + +/// Apply a keccak 512-bit hash on its bytes parameter, outputs a `[u8; 64]`. +/// Multiple inputs are concatenated before hashing. +/// Input can be identifier (name of identifier as bytes is used), byte string or +/// array of bytes. +#[proc_macro] +pub fn keccak_512(input: TokenStream) -> TokenStream { + impls::keccak_512(syn::parse_macro_input!(input as MultipleInputBytes).concatenated()) +} + +/// Apply a sha2 256-bit hash on its bytes parameter, outputs a `[u8; 32]`. +/// Multiple inputs are concatenated before hashing. +/// Input can be identifier (name of identifier as bytes is used), byte string or +/// array of bytes. +#[proc_macro] +pub fn sha2_256(input: TokenStream) -> TokenStream { + impls::sha2_256(syn::parse_macro_input!(input as MultipleInputBytes).concatenated()) +} diff --git a/primitives/core/hashing/src/lib.rs b/primitives/core/hashing/src/lib.rs new file mode 100644 index 0000000000000..e6ccd5aaa8fb9 --- /dev/null +++ b/primitives/core/hashing/src/lib.rs @@ -0,0 +1,148 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Hashing Functions. + +#![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +use core::hash::Hasher; + +use byteorder::{ByteOrder, LittleEndian}; +use digest::{ + consts::{U16, U32, U8}, + Digest, +}; + +/// Do a Blake2 512-bit hash and place result in `dest`. +pub fn blake2_512_into(data: &[u8], dest: &mut [u8; 64]) { + dest.copy_from_slice(blake2::Blake2b512::digest(data).as_slice()); +} + +/// Do a Blake2 512-bit hash and return result. +pub fn blake2_512(data: &[u8]) -> [u8; 64] { + let mut r = [0; 64]; + blake2_512_into(data, &mut r); + r +} + +/// Do a Blake2 256-bit hash and place result in `dest`. +pub fn blake2_256_into(data: &[u8], dest: &mut [u8; 32]) { + type Blake2b256 = blake2::Blake2b; + dest.copy_from_slice(Blake2b256::digest(data).as_slice()); +} + +/// Do a Blake2 256-bit hash and return result. +pub fn blake2_256(data: &[u8]) -> [u8; 32] { + let mut r = [0; 32]; + blake2_256_into(data, &mut r); + r +} + +/// Do a Blake2 128-bit hash and place result in `dest`. +pub fn blake2_128_into(data: &[u8], dest: &mut [u8; 16]) { + type Blake2b128 = blake2::Blake2b; + dest.copy_from_slice(Blake2b128::digest(data).as_slice()); +} + +/// Do a Blake2 128-bit hash and return result. +pub fn blake2_128(data: &[u8]) -> [u8; 16] { + let mut r = [0; 16]; + blake2_128_into(data, &mut r); + r +} + +/// Do a Blake2 64-bit hash and place result in `dest`. +pub fn blake2_64_into(data: &[u8], dest: &mut [u8; 8]) { + type Blake2b64 = blake2::Blake2b; + dest.copy_from_slice(Blake2b64::digest(data).as_slice()); +} + +/// Do a Blake2 64-bit hash and return result. +pub fn blake2_64(data: &[u8]) -> [u8; 8] { + let mut r = [0; 8]; + blake2_64_into(data, &mut r); + r +} + +/// Do a XX 64-bit hash and place result in `dest`. +pub fn twox_64_into(data: &[u8], dest: &mut [u8; 8]) { + let r0 = twox_hash::XxHash::with_seed(0).chain_update(data).finish(); + LittleEndian::write_u64(&mut dest[0..8], r0); +} + +/// Do a XX 64-bit hash and return result. +pub fn twox_64(data: &[u8]) -> [u8; 8] { + let mut r: [u8; 8] = [0; 8]; + twox_64_into(data, &mut r); + r +} + +/// Do a XX 128-bit hash and place result in `dest`. +pub fn twox_128_into(data: &[u8], dest: &mut [u8; 16]) { + let r0 = twox_hash::XxHash::with_seed(0).chain_update(data).finish(); + let r1 = twox_hash::XxHash::with_seed(1).chain_update(data).finish(); + LittleEndian::write_u64(&mut dest[0..8], r0); + LittleEndian::write_u64(&mut dest[8..16], r1); +} + +/// Do a XX 128-bit hash and return result. +pub fn twox_128(data: &[u8]) -> [u8; 16] { + let mut r: [u8; 16] = [0; 16]; + twox_128_into(data, &mut r); + r +} + +/// Do a XX 256-bit hash and place result in `dest`. +pub fn twox_256_into(data: &[u8], dest: &mut [u8; 32]) { + let r0 = twox_hash::XxHash::with_seed(0).chain_update(data).finish(); + let r1 = twox_hash::XxHash::with_seed(1).chain_update(data).finish(); + let r2 = twox_hash::XxHash::with_seed(2).chain_update(data).finish(); + let r3 = twox_hash::XxHash::with_seed(3).chain_update(data).finish(); + LittleEndian::write_u64(&mut dest[0..8], r0); + LittleEndian::write_u64(&mut dest[8..16], r1); + LittleEndian::write_u64(&mut dest[16..24], r2); + LittleEndian::write_u64(&mut dest[24..32], r3); +} + +/// Do a XX 256-bit hash and return result. +pub fn twox_256(data: &[u8]) -> [u8; 32] { + let mut r: [u8; 32] = [0; 32]; + twox_256_into(data, &mut r); + r +} + +/// Do a keccak 256-bit hash and return result. +pub fn keccak_256(data: &[u8]) -> [u8; 32] { + let mut output = [0u8; 32]; + output.copy_from_slice(sha3::Keccak256::digest(data).as_slice()); + output +} + +/// Do a keccak 512-bit hash and return result. +pub fn keccak_512(data: &[u8]) -> [u8; 64] { + let mut output = [0u8; 64]; + output.copy_from_slice(sha3::Keccak512::digest(data).as_slice()); + output +} + +/// Do a sha2 256-bit hash and return result. +pub fn sha2_256(data: &[u8]) -> [u8; 32] { + let mut output = [0u8; 32]; + output.copy_from_slice(sha2::Sha256::digest(data).as_slice()); + output +} diff --git a/primitives/core/src/bounded.rs b/primitives/core/src/bounded.rs new file mode 100644 index 0000000000000..45b4a9ca623d4 --- /dev/null +++ b/primitives/core/src/bounded.rs @@ -0,0 +1,28 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Bounded collection types. + +pub mod bounded_btree_map; +pub mod bounded_btree_set; +pub mod bounded_vec; +pub mod weak_bounded_vec; + +pub use bounded_btree_map::BoundedBTreeMap; +pub use bounded_btree_set::BoundedBTreeSet; +pub use bounded_vec::{BoundedSlice, BoundedVec}; +pub use weak_bounded_vec::WeakBoundedVec; diff --git a/primitives/core/src/bounded/bounded_btree_map.rs b/primitives/core/src/bounded/bounded_btree_map.rs new file mode 100644 index 0000000000000..d2c148d6de9c5 --- /dev/null +++ b/primitives/core/src/bounded/bounded_btree_map.rs @@ -0,0 +1,625 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Traits, types and structs to support a bounded BTreeMap. + +use crate::{Get, TryCollect}; +use codec::{Decode, Encode, MaxEncodedLen}; +use sp_std::{borrow::Borrow, collections::btree_map::BTreeMap, marker::PhantomData, ops::Deref}; + +/// A bounded map based on a B-Tree. +/// +/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing +/// the amount of work performed in a search. See [`BTreeMap`] for more details. +/// +/// Unlike a standard `BTreeMap`, there is an enforced upper limit to the number of items in the +/// map. All internal operations ensure this bound is respected. +#[derive(Encode, scale_info::TypeInfo)] +#[scale_info(skip_type_params(S))] +pub struct BoundedBTreeMap(BTreeMap, PhantomData); + +impl Decode for BoundedBTreeMap +where + K: Decode + Ord, + V: Decode, + S: Get, +{ + fn decode(input: &mut I) -> Result { + let inner = BTreeMap::::decode(input)?; + if inner.len() > S::get() as usize { + return Err("BoundedBTreeMap exceeds its limit".into()) + } + Ok(Self(inner, PhantomData)) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + BTreeMap::::skip(input) + } +} + +impl BoundedBTreeMap +where + S: Get, +{ + /// Get the bound of the type in `usize`. + pub fn bound() -> usize { + S::get() as usize + } +} + +impl BoundedBTreeMap +where + K: Ord, + S: Get, +{ + /// Create `Self` from `t` without any checks. + fn unchecked_from(t: BTreeMap) -> Self { + Self(t, Default::default()) + } + + /// Exactly the same semantics as `BTreeMap::retain`. + /// + /// The is a safe `&mut self` borrow because `retain` can only ever decrease the length of the + /// inner map. + pub fn retain bool>(&mut self, f: F) { + self.0.retain(f) + } + + /// Create a new `BoundedBTreeMap`. + /// + /// Does not allocate. + pub fn new() -> Self { + BoundedBTreeMap(BTreeMap::new(), PhantomData) + } + + /// Consume self, and return the inner `BTreeMap`. + /// + /// This is useful when a mutating API of the inner type is desired, and closure-based mutation + /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. + pub fn into_inner(self) -> BTreeMap { + debug_assert!(self.0.len() <= Self::bound()); + self.0 + } + + /// Consumes self and mutates self via the given `mutate` function. + /// + /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is + /// returned. + /// + /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> + /// [`Self::try_from`]. + pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut BTreeMap)) -> Option { + mutate(&mut self.0); + (self.0.len() <= Self::bound()).then(move || self) + } + + /// Clears the map, removing all elements. + pub fn clear(&mut self) { + self.0.clear() + } + + /// Return a mutable reference to the value corresponding to the key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed + /// form _must_ match the ordering on the key type. + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Ord + ?Sized, + { + self.0.get_mut(key) + } + + /// Exactly the same semantics as [`BTreeMap::insert`], but returns an `Err` (and is a noop) if + /// the new length of the map exceeds `S`. + /// + /// In the `Err` case, returns the inserted pair so it can be further used without cloning. + pub fn try_insert(&mut self, key: K, value: V) -> Result, (K, V)> { + if self.len() < Self::bound() || self.0.contains_key(&key) { + Ok(self.0.insert(key, value)) + } else { + Err((key, value)) + } + } + + /// Remove a key from the map, returning the value at the key if the key was previously in the + /// map. + /// + /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed + /// form _must_ match the ordering on the key type. + pub fn remove(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: Ord + ?Sized, + { + self.0.remove(key) + } + + /// Remove a key from the map, returning the value at the key if the key was previously in the + /// map. + /// + /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed + /// form _must_ match the ordering on the key type. + pub fn remove_entry(&mut self, key: &Q) -> Option<(K, V)> + where + K: Borrow, + Q: Ord + ?Sized, + { + self.0.remove_entry(key) + } + + /// Gets a mutable iterator over the entries of the map, sorted by key. + /// + /// See [`BTreeMap::iter_mut`] for more information. + pub fn iter_mut(&mut self) -> sp_std::collections::btree_map::IterMut { + self.0.iter_mut() + } + + /// Consume the map, applying `f` to each of it's values and returning a new map. + pub fn map(self, mut f: F) -> BoundedBTreeMap + where + F: FnMut((&K, V)) -> T, + { + BoundedBTreeMap::::unchecked_from( + self.0 + .into_iter() + .map(|(k, v)| { + let t = f((&k, v)); + (k, t) + }) + .collect(), + ) + } + + /// Consume the map, applying `f` to each of it's values as long as it returns successfully. If + /// an `Err(E)` is ever encountered, the mapping is short circuited and the error is returned; + /// otherwise, a new map is returned in the contained `Ok` value. + pub fn try_map(self, mut f: F) -> Result, E> + where + F: FnMut((&K, V)) -> Result, + { + Ok(BoundedBTreeMap::::unchecked_from( + self.0 + .into_iter() + .map(|(k, v)| (f((&k, v)).map(|t| (k, t)))) + .collect::, _>>()?, + )) + } +} + +impl Default for BoundedBTreeMap +where + K: Ord, + S: Get, +{ + fn default() -> Self { + Self::new() + } +} + +impl Clone for BoundedBTreeMap +where + BTreeMap: Clone, +{ + fn clone(&self) -> Self { + BoundedBTreeMap(self.0.clone(), PhantomData) + } +} + +impl sp_std::fmt::Debug for BoundedBTreeMap +where + BTreeMap: sp_std::fmt::Debug, + S: Get, +{ + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + f.debug_tuple("BoundedBTreeMap").field(&self.0).field(&Self::bound()).finish() + } +} + +impl PartialEq> for BoundedBTreeMap +where + BTreeMap: PartialEq, + S1: Get, + S2: Get, +{ + fn eq(&self, other: &BoundedBTreeMap) -> bool { + S1::get() == S2::get() && self.0 == other.0 + } +} + +impl Eq for BoundedBTreeMap +where + BTreeMap: Eq, + S: Get, +{ +} + +impl PartialEq> for BoundedBTreeMap +where + BTreeMap: PartialEq, +{ + fn eq(&self, other: &BTreeMap) -> bool { + self.0 == *other + } +} + +impl PartialOrd for BoundedBTreeMap +where + BTreeMap: PartialOrd, + S: Get, +{ + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for BoundedBTreeMap +where + BTreeMap: Ord, + S: Get, +{ + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl IntoIterator for BoundedBTreeMap { + type Item = (K, V); + type IntoIter = sp_std::collections::btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a BoundedBTreeMap { + type Item = (&'a K, &'a V); + type IntoIter = sp_std::collections::btree_map::Iter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a mut BoundedBTreeMap { + type Item = (&'a K, &'a mut V); + type IntoIter = sp_std::collections::btree_map::IterMut<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + +impl MaxEncodedLen for BoundedBTreeMap +where + K: MaxEncodedLen, + V: MaxEncodedLen, + S: Get, +{ + fn max_encoded_len() -> usize { + Self::bound() + .saturating_mul(K::max_encoded_len().saturating_add(V::max_encoded_len())) + .saturating_add(codec::Compact(S::get()).encoded_size()) + } +} + +impl Deref for BoundedBTreeMap +where + K: Ord, +{ + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef> for BoundedBTreeMap +where + K: Ord, +{ + fn as_ref(&self) -> &BTreeMap { + &self.0 + } +} + +impl From> for BTreeMap +where + K: Ord, +{ + fn from(map: BoundedBTreeMap) -> Self { + map.0 + } +} + +impl TryFrom> for BoundedBTreeMap +where + K: Ord, + S: Get, +{ + type Error = (); + + fn try_from(value: BTreeMap) -> Result { + (value.len() <= Self::bound()) + .then(move || BoundedBTreeMap(value, PhantomData)) + .ok_or(()) + } +} + +impl codec::DecodeLength for BoundedBTreeMap { + fn len(self_encoded: &[u8]) -> Result { + // `BoundedBTreeMap` is stored just a `BTreeMap`, which is stored as a + // `Compact` with its length followed by an iteration of its items. We can just use + // the underlying implementation. + as codec::DecodeLength>::len(self_encoded) + } +} + +impl codec::EncodeLike> for BoundedBTreeMap where + BTreeMap: Encode +{ +} + +impl TryCollect> for I +where + K: Ord, + I: ExactSizeIterator + Iterator, + Bound: Get, +{ + type Error = &'static str; + + fn try_collect(self) -> Result, Self::Error> { + if self.len() > Bound::get() as usize { + Err("iterator length too big") + } else { + Ok(BoundedBTreeMap::::unchecked_from(self.collect::>())) + } + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::ConstU32; + + fn map_from_keys(keys: &[K]) -> BTreeMap + where + K: Ord + Copy, + { + keys.iter().copied().zip(std::iter::repeat(())).collect() + } + + fn boundedmap_from_keys(keys: &[K]) -> BoundedBTreeMap + where + K: Ord + Copy, + S: Get, + { + map_from_keys(keys).try_into().unwrap() + } + + #[test] + fn try_insert_works() { + let mut bounded = boundedmap_from_keys::>(&[1, 2, 3]); + bounded.try_insert(0, ()).unwrap(); + assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); + + assert!(bounded.try_insert(9, ()).is_err()); + assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); + } + + #[test] + fn deref_coercion_works() { + let bounded = boundedmap_from_keys::>(&[1, 2, 3]); + // these methods come from deref-ed vec. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + + #[test] + fn try_mutate_works() { + let bounded = boundedmap_from_keys::>(&[1, 2, 3, 4, 5, 6]); + let bounded = bounded + .try_mutate(|v| { + v.insert(7, ()); + }) + .unwrap(); + assert_eq!(bounded.len(), 7); + assert!(bounded + .try_mutate(|v| { + v.insert(8, ()); + }) + .is_none()); + } + + #[test] + fn btree_map_eq_works() { + let bounded = boundedmap_from_keys::>(&[1, 2, 3, 4, 5, 6]); + assert_eq!(bounded, map_from_keys(&[1, 2, 3, 4, 5, 6])); + } + + #[test] + fn too_big_fail_to_decode() { + let v: Vec<(u32, u32)> = vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]; + assert_eq!( + BoundedBTreeMap::>::decode(&mut &v.encode()[..]), + Err("BoundedBTreeMap exceeds its limit".into()), + ); + } + + #[test] + fn unequal_eq_impl_insert_works() { + // given a struct with a strange notion of equality + #[derive(Debug)] + struct Unequal(u32, bool); + + impl PartialEq for Unequal { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + impl Eq for Unequal {} + + impl Ord for Unequal { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } + } + + impl PartialOrd for Unequal { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + let mut map = BoundedBTreeMap::>::new(); + + // when the set is full + + for i in 0..4 { + map.try_insert(Unequal(i, false), i).unwrap(); + } + + // can't insert a new distinct member + map.try_insert(Unequal(5, false), 5).unwrap_err(); + + // but _can_ insert a distinct member which compares equal, though per the documentation, + // neither the set length nor the actual member are changed, but the value is + map.try_insert(Unequal(0, true), 6).unwrap(); + assert_eq!(map.len(), 4); + let (zero_key, zero_value) = map.get_key_value(&Unequal(0, true)).unwrap(); + assert_eq!(zero_key.0, 0); + assert_eq!(zero_key.1, false); + assert_eq!(*zero_value, 6); + } + + #[test] + fn eq_works() { + // of same type + let b1 = boundedmap_from_keys::>(&[1, 2]); + let b2 = boundedmap_from_keys::>(&[1, 2]); + assert_eq!(b1, b2); + + // of different type, but same value and bound. + crate::parameter_types! { + B1: u32 = 7; + B2: u32 = 7; + } + let b1 = boundedmap_from_keys::(&[1, 2]); + let b2 = boundedmap_from_keys::(&[1, 2]); + assert_eq!(b1, b2); + } + + #[test] + fn can_be_collected() { + let b1 = boundedmap_from_keys::>(&[1, 2, 3, 4]); + let b2: BoundedBTreeMap> = + b1.iter().map(|(k, v)| (k + 1, *v)).try_collect().unwrap(); + assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3, 4, 5]); + + // can also be collected into a collection of length 4. + let b2: BoundedBTreeMap> = + b1.iter().map(|(k, v)| (k + 1, *v)).try_collect().unwrap(); + assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3, 4, 5]); + + // can be mutated further into iterators that are `ExactSizedIterator`. + let b2: BoundedBTreeMap> = + b1.iter().map(|(k, v)| (k + 1, *v)).rev().skip(2).try_collect().unwrap(); + // note that the binary tree will re-sort this, so rev() is not really seen + assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3]); + + let b2: BoundedBTreeMap> = + b1.iter().map(|(k, v)| (k + 1, *v)).take(2).try_collect().unwrap(); + assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3]); + + // but these won't work + let b2: Result>, _> = + b1.iter().map(|(k, v)| (k + 1, *v)).try_collect(); + assert!(b2.is_err()); + + let b2: Result>, _> = + b1.iter().map(|(k, v)| (k + 1, *v)).skip(2).try_collect(); + assert!(b2.is_err()); + } + + #[test] + fn test_iter_mut() { + let mut b1: BoundedBTreeMap> = + [1, 2, 3, 4].into_iter().map(|k| (k, k)).try_collect().unwrap(); + + let b2: BoundedBTreeMap> = + [1, 2, 3, 4].into_iter().map(|k| (k, k * 2)).try_collect().unwrap(); + + b1.iter_mut().for_each(|(_, v)| *v *= 2); + + assert_eq!(b1, b2); + } + + #[test] + fn map_retains_size() { + let b1 = boundedmap_from_keys::>(&[1, 2]); + let b2 = b1.clone(); + + assert_eq!(b1.len(), b2.map(|(_, _)| 5_u32).len()); + } + + #[test] + fn map_maps_properly() { + let b1: BoundedBTreeMap> = + [1, 2, 3, 4].into_iter().map(|k| (k, k * 2)).try_collect().unwrap(); + let b2: BoundedBTreeMap> = + [1, 2, 3, 4].into_iter().map(|k| (k, k)).try_collect().unwrap(); + + assert_eq!(b1, b2.map(|(_, v)| v * 2)); + } + + #[test] + fn try_map_retains_size() { + let b1 = boundedmap_from_keys::>(&[1, 2]); + let b2 = b1.clone(); + + assert_eq!(b1.len(), b2.try_map::<_, (), _>(|(_, _)| Ok(5_u32)).unwrap().len()); + } + + #[test] + fn try_map_maps_properly() { + let b1: BoundedBTreeMap> = + [1, 2, 3, 4].into_iter().map(|k| (k, k * 2)).try_collect().unwrap(); + let b2: BoundedBTreeMap> = + [1, 2, 3, 4].into_iter().map(|k| (k, k)).try_collect().unwrap(); + + assert_eq!(b1, b2.try_map::<_, (), _>(|(_, v)| Ok(v * 2)).unwrap()); + } + + #[test] + fn try_map_short_circuit() { + let b1: BoundedBTreeMap> = + [1, 2, 3, 4].into_iter().map(|k| (k, k)).try_collect().unwrap(); + + assert_eq!(Err("overflow"), b1.try_map(|(_, v)| v.checked_mul(100).ok_or("overflow"))); + } + + #[test] + fn try_map_ok() { + let b1: BoundedBTreeMap> = + [1, 2, 3, 4].into_iter().map(|k| (k, k)).try_collect().unwrap(); + let b2: BoundedBTreeMap> = + [1, 2, 3, 4].into_iter().map(|k| (k, (k as u16) * 100)).try_collect().unwrap(); + + assert_eq!(Ok(b2), b1.try_map(|(_, v)| (v as u16).checked_mul(100_u16).ok_or("overflow"))); + } +} diff --git a/primitives/core/src/bounded/bounded_btree_set.rs b/primitives/core/src/bounded/bounded_btree_set.rs new file mode 100644 index 0000000000000..5feac6b7150f0 --- /dev/null +++ b/primitives/core/src/bounded/bounded_btree_set.rs @@ -0,0 +1,482 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Traits, types and structs to support a bounded `BTreeSet`. + +use crate::{Get, TryCollect}; +use codec::{Decode, Encode, MaxEncodedLen}; +use sp_std::{borrow::Borrow, collections::btree_set::BTreeSet, marker::PhantomData, ops::Deref}; + +/// A bounded set based on a B-Tree. +/// +/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing +/// the amount of work performed in a search. See [`BTreeSet`] for more details. +/// +/// Unlike a standard `BTreeSet`, there is an enforced upper limit to the number of items in the +/// set. All internal operations ensure this bound is respected. +#[derive(Encode, scale_info::TypeInfo)] +#[scale_info(skip_type_params(S))] +pub struct BoundedBTreeSet(BTreeSet, PhantomData); + +impl Decode for BoundedBTreeSet +where + T: Decode + Ord, + S: Get, +{ + fn decode(input: &mut I) -> Result { + let inner = BTreeSet::::decode(input)?; + if inner.len() > S::get() as usize { + return Err("BoundedBTreeSet exceeds its limit".into()) + } + Ok(Self(inner, PhantomData)) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + BTreeSet::::skip(input) + } +} + +impl BoundedBTreeSet +where + S: Get, +{ + /// Get the bound of the type in `usize`. + pub fn bound() -> usize { + S::get() as usize + } +} + +impl BoundedBTreeSet +where + T: Ord, + S: Get, +{ + /// Create `Self` from `t` without any checks. + fn unchecked_from(t: BTreeSet) -> Self { + Self(t, Default::default()) + } + + /// Create a new `BoundedBTreeSet`. + /// + /// Does not allocate. + pub fn new() -> Self { + BoundedBTreeSet(BTreeSet::new(), PhantomData) + } + + /// Consume self, and return the inner `BTreeSet`. + /// + /// This is useful when a mutating API of the inner type is desired, and closure-based mutation + /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. + pub fn into_inner(self) -> BTreeSet { + debug_assert!(self.0.len() <= Self::bound()); + self.0 + } + + /// Consumes self and mutates self via the given `mutate` function. + /// + /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is + /// returned. + /// + /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> + /// [`Self::try_from`]. + pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut BTreeSet)) -> Option { + mutate(&mut self.0); + (self.0.len() <= Self::bound()).then(move || self) + } + + /// Clears the set, removing all elements. + pub fn clear(&mut self) { + self.0.clear() + } + + /// Exactly the same semantics as [`BTreeSet::insert`], but returns an `Err` (and is a noop) if + /// the new length of the set exceeds `S`. + /// + /// In the `Err` case, returns the inserted item so it can be further used without cloning. + pub fn try_insert(&mut self, item: T) -> Result { + if self.len() < Self::bound() || self.0.contains(&item) { + Ok(self.0.insert(item)) + } else { + Err(item) + } + } + + /// Remove an item from the set, returning whether it was previously in the set. + /// + /// The item may be any borrowed form of the set's item type, but the ordering on the borrowed + /// form _must_ match the ordering on the item type. + pub fn remove(&mut self, item: &Q) -> bool + where + T: Borrow, + Q: Ord + ?Sized, + { + self.0.remove(item) + } + + /// Removes and returns the value in the set, if any, that is equal to the given one. + /// + /// The value may be any borrowed form of the set's value type, but the ordering on the borrowed + /// form _must_ match the ordering on the value type. + pub fn take(&mut self, value: &Q) -> Option + where + T: Borrow + Ord, + Q: Ord + ?Sized, + { + self.0.take(value) + } +} + +impl Default for BoundedBTreeSet +where + T: Ord, + S: Get, +{ + fn default() -> Self { + Self::new() + } +} + +impl Clone for BoundedBTreeSet +where + BTreeSet: Clone, +{ + fn clone(&self) -> Self { + BoundedBTreeSet(self.0.clone(), PhantomData) + } +} + +impl sp_std::fmt::Debug for BoundedBTreeSet +where + BTreeSet: sp_std::fmt::Debug, + S: Get, +{ + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + f.debug_tuple("BoundedBTreeSet").field(&self.0).field(&Self::bound()).finish() + } +} + +impl PartialEq> for BoundedBTreeSet +where + BTreeSet: PartialEq, + S1: Get, + S2: Get, +{ + fn eq(&self, other: &BoundedBTreeSet) -> bool { + S1::get() == S2::get() && self.0 == other.0 + } +} + +impl Eq for BoundedBTreeSet +where + BTreeSet: Eq, + S: Get, +{ +} + +impl PartialEq> for BoundedBTreeSet +where + BTreeSet: PartialEq, + S: Get, +{ + fn eq(&self, other: &BTreeSet) -> bool { + self.0 == *other + } +} + +impl PartialOrd for BoundedBTreeSet +where + BTreeSet: PartialOrd, + S: Get, +{ + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for BoundedBTreeSet +where + BTreeSet: Ord, + S: Get, +{ + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl IntoIterator for BoundedBTreeSet { + type Item = T; + type IntoIter = sp_std::collections::btree_set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T, S> IntoIterator for &'a BoundedBTreeSet { + type Item = &'a T; + type IntoIter = sp_std::collections::btree_set::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl MaxEncodedLen for BoundedBTreeSet +where + T: MaxEncodedLen, + S: Get, +{ + fn max_encoded_len() -> usize { + Self::bound() + .saturating_mul(T::max_encoded_len()) + .saturating_add(codec::Compact(S::get()).encoded_size()) + } +} + +impl Deref for BoundedBTreeSet +where + T: Ord, +{ + type Target = BTreeSet; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef> for BoundedBTreeSet +where + T: Ord, +{ + fn as_ref(&self) -> &BTreeSet { + &self.0 + } +} + +impl From> for BTreeSet +where + T: Ord, +{ + fn from(set: BoundedBTreeSet) -> Self { + set.0 + } +} + +impl TryFrom> for BoundedBTreeSet +where + T: Ord, + S: Get, +{ + type Error = (); + + fn try_from(value: BTreeSet) -> Result { + (value.len() <= Self::bound()) + .then(move || BoundedBTreeSet(value, PhantomData)) + .ok_or(()) + } +} + +impl codec::DecodeLength for BoundedBTreeSet { + fn len(self_encoded: &[u8]) -> Result { + // `BoundedBTreeSet` is stored just a `BTreeSet`, which is stored as a + // `Compact` with its length followed by an iteration of its items. We can just use + // the underlying implementation. + as codec::DecodeLength>::len(self_encoded) + } +} + +impl codec::EncodeLike> for BoundedBTreeSet where BTreeSet: Encode {} + +impl TryCollect> for I +where + T: Ord, + I: ExactSizeIterator + Iterator, + Bound: Get, +{ + type Error = &'static str; + + fn try_collect(self) -> Result, Self::Error> { + if self.len() > Bound::get() as usize { + Err("iterator length too big") + } else { + Ok(BoundedBTreeSet::::unchecked_from(self.collect::>())) + } + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::ConstU32; + + fn set_from_keys(keys: &[T]) -> BTreeSet + where + T: Ord + Copy, + { + keys.iter().copied().collect() + } + + fn boundedset_from_keys(keys: &[T]) -> BoundedBTreeSet + where + T: Ord + Copy, + S: Get, + { + set_from_keys(keys).try_into().unwrap() + } + + #[test] + fn try_insert_works() { + let mut bounded = boundedset_from_keys::>(&[1, 2, 3]); + bounded.try_insert(0).unwrap(); + assert_eq!(*bounded, set_from_keys(&[1, 0, 2, 3])); + + assert!(bounded.try_insert(9).is_err()); + assert_eq!(*bounded, set_from_keys(&[1, 0, 2, 3])); + } + + #[test] + fn deref_coercion_works() { + let bounded = boundedset_from_keys::>(&[1, 2, 3]); + // these methods come from deref-ed vec. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + + #[test] + fn try_mutate_works() { + let bounded = boundedset_from_keys::>(&[1, 2, 3, 4, 5, 6]); + let bounded = bounded + .try_mutate(|v| { + v.insert(7); + }) + .unwrap(); + assert_eq!(bounded.len(), 7); + assert!(bounded + .try_mutate(|v| { + v.insert(8); + }) + .is_none()); + } + + #[test] + fn btree_map_eq_works() { + let bounded = boundedset_from_keys::>(&[1, 2, 3, 4, 5, 6]); + assert_eq!(bounded, set_from_keys(&[1, 2, 3, 4, 5, 6])); + } + + #[test] + fn too_big_fail_to_decode() { + let v: Vec = vec![1, 2, 3, 4, 5]; + assert_eq!( + BoundedBTreeSet::>::decode(&mut &v.encode()[..]), + Err("BoundedBTreeSet exceeds its limit".into()), + ); + } + + #[test] + fn unequal_eq_impl_insert_works() { + // given a struct with a strange notion of equality + #[derive(Debug)] + struct Unequal(u32, bool); + + impl PartialEq for Unequal { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + impl Eq for Unequal {} + + impl Ord for Unequal { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } + } + + impl PartialOrd for Unequal { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + let mut set = BoundedBTreeSet::>::new(); + + // when the set is full + + for i in 0..4 { + set.try_insert(Unequal(i, false)).unwrap(); + } + + // can't insert a new distinct member + set.try_insert(Unequal(5, false)).unwrap_err(); + + // but _can_ insert a distinct member which compares equal, though per the documentation, + // neither the set length nor the actual member are changed + set.try_insert(Unequal(0, true)).unwrap(); + assert_eq!(set.len(), 4); + let zero_item = set.get(&Unequal(0, true)).unwrap(); + assert_eq!(zero_item.0, 0); + assert_eq!(zero_item.1, false); + } + + #[test] + fn eq_works() { + // of same type + let b1 = boundedset_from_keys::>(&[1, 2]); + let b2 = boundedset_from_keys::>(&[1, 2]); + assert_eq!(b1, b2); + + // of different type, but same value and bound. + crate::parameter_types! { + B1: u32 = 7; + B2: u32 = 7; + } + let b1 = boundedset_from_keys::(&[1, 2]); + let b2 = boundedset_from_keys::(&[1, 2]); + assert_eq!(b1, b2); + } + + #[test] + fn can_be_collected() { + let b1 = boundedset_from_keys::>(&[1, 2, 3, 4]); + let b2: BoundedBTreeSet> = b1.iter().map(|k| k + 1).try_collect().unwrap(); + assert_eq!(b2.into_iter().collect::>(), vec![2, 3, 4, 5]); + + // can also be collected into a collection of length 4. + let b2: BoundedBTreeSet> = b1.iter().map(|k| k + 1).try_collect().unwrap(); + assert_eq!(b2.into_iter().collect::>(), vec![2, 3, 4, 5]); + + // can be mutated further into iterators that are `ExactSizedIterator`. + let b2: BoundedBTreeSet> = + b1.iter().map(|k| k + 1).rev().skip(2).try_collect().unwrap(); + // note that the binary tree will re-sort this, so rev() is not really seen + assert_eq!(b2.into_iter().collect::>(), vec![2, 3]); + + let b2: BoundedBTreeSet> = + b1.iter().map(|k| k + 1).take(2).try_collect().unwrap(); + assert_eq!(b2.into_iter().collect::>(), vec![2, 3]); + + // but these worn't work + let b2: Result>, _> = + b1.iter().map(|k| k + 1).try_collect(); + assert!(b2.is_err()); + + let b2: Result>, _> = + b1.iter().map(|k| k + 1).skip(2).try_collect(); + assert!(b2.is_err()); + } +} diff --git a/primitives/core/src/bounded/bounded_vec.rs b/primitives/core/src/bounded/bounded_vec.rs new file mode 100644 index 0000000000000..6e1e1c7cfda64 --- /dev/null +++ b/primitives/core/src/bounded/bounded_vec.rs @@ -0,0 +1,1305 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Traits, types and structs to support putting a bounded vector into storage, as a raw value, map +//! or a double map. + +use super::WeakBoundedVec; +use crate::{Get, TryCollect}; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use core::{ + ops::{Deref, Index, IndexMut, RangeBounds}, + slice::SliceIndex, +}; +#[cfg(feature = "std")] +use serde::{ + de::{Error, SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use sp_std::{marker::PhantomData, prelude::*}; + +/// A bounded vector. +/// +/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once +/// put into storage as a raw value, map or double-map. +/// +/// As the name suggests, the length of the queue is always bounded. All internal operations ensure +/// this bound is respected. +#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] +#[derive(Encode, scale_info::TypeInfo)] +#[scale_info(skip_type_params(S))] +pub struct BoundedVec( + pub(super) Vec, + #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData, +); + +/// Create an object through truncation. +pub trait TruncateFrom { + /// Create an object through truncation. + fn truncate_from(unbound: T) -> Self; +} + +#[cfg(feature = "std")] +impl<'de, T, S: Get> Deserialize<'de> for BoundedVec +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VecVisitor>(PhantomData<(T, S)>); + + impl<'de, T, S: Get> Visitor<'de> for VecVisitor + where + T: Deserialize<'de>, + { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let size = seq.size_hint().unwrap_or(0); + let max = match usize::try_from(S::get()) { + Ok(n) => n, + Err(_) => return Err(A::Error::custom("can't convert to usize")), + }; + if size > max { + Err(A::Error::custom("out of bounds")) + } else { + let mut values = Vec::with_capacity(size); + + while let Some(value) = seq.next_element()? { + values.push(value); + if values.len() > max { + return Err(A::Error::custom("out of bounds")) + } + } + + Ok(values) + } + } + } + + let visitor: VecVisitor = VecVisitor(PhantomData); + deserializer + .deserialize_seq(visitor) + .map(|v| BoundedVec::::try_from(v).map_err(|_| Error::custom("out of bounds")))? + } +} + +/// A bounded slice. +/// +/// Similar to a `BoundedVec`, but not owned and cannot be decoded. +#[derive(Encode)] +pub struct BoundedSlice<'a, T, S>(pub(super) &'a [T], PhantomData); + +// This can be replaced with +// #[derive(scale_info::TypeInfo)] +// #[scale_info(skip_type_params(S))] +// again once this issue is fixed in the rust compiler: https://github.com/rust-lang/rust/issues/96956 +// Tracking issues: https://github.com/paritytech/substrate/issues/11915 +impl<'a, T, S> scale_info::TypeInfo for BoundedSlice<'a, T, S> +where + &'a [T]: scale_info::TypeInfo + 'static, + PhantomData: scale_info::TypeInfo + 'static, + T: scale_info::TypeInfo + 'static, + S: 'static, +{ + type Identity = Self; + + fn type_info() -> ::scale_info::Type { + scale_info::Type::builder() + .path(scale_info::Path::new("BoundedSlice", "sp_runtime::bounded::bounded_vec")) + .type_params(<[_]>::into_vec(Box::new([ + scale_info::TypeParameter::new( + "T", + core::option::Option::Some(::scale_info::meta_type::()), + ), + scale_info::TypeParameter::new("S", ::core::option::Option::None), + ]))) + .docs(&[ + "A bounded slice.", + "", + "Similar to a `BoundedVec`, but not owned and cannot be decoded.", + ]) + .composite( + scale_info::build::Fields::unnamed() + .field(|f| f.ty::<&'static [T]>().type_name("&'static[T]").docs(&[])) + .field(|f| f.ty::>().type_name("PhantomData").docs(&[])), + ) + } +} + +// `BoundedSlice`s encode to something which will always decode into a `BoundedVec`, +// `WeakBoundedVec`, or a `Vec`. +impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} +impl<'a, T: Encode + Decode, S: Get> EncodeLike> + for BoundedSlice<'a, T, S> +{ +} +impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} + +impl<'a, T, BoundSelf, BoundRhs> PartialEq> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> bool { + self.0 == other.0 + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialEq> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, other: &BoundedVec) -> bool { + self.0 == other.0 + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialEq> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, other: &WeakBoundedVec) -> bool { + self.0 == other.0 + } +} + +impl<'a, T, S: Get> Eq for BoundedSlice<'a, T, S> where T: Eq {} + +impl<'a, T, BoundSelf, BoundRhs> PartialOrd> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { + self.0.partial_cmp(other.0) + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialOrd> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedVec) -> Option { + self.0.partial_cmp(&*other.0) + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialOrd> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { + self.0.partial_cmp(&*other.0) + } +} + +impl<'a, T: Ord, Bound: Get> Ord for BoundedSlice<'a, T, Bound> { + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl<'a, T, S: Get> TryFrom<&'a [T]> for BoundedSlice<'a, T, S> { + type Error = &'a [T]; + fn try_from(t: &'a [T]) -> Result { + if t.len() <= S::get() as usize { + Ok(BoundedSlice(t, PhantomData)) + } else { + Err(t) + } + } +} + +impl<'a, T, S> From> for &'a [T] { + fn from(t: BoundedSlice<'a, T, S>) -> Self { + t.0 + } +} + +impl<'a, T, S: Get> TruncateFrom<&'a [T]> for BoundedSlice<'a, T, S> { + fn truncate_from(unbound: &'a [T]) -> Self { + BoundedSlice::::truncate_from(unbound) + } +} + +impl<'a, T, S> Clone for BoundedSlice<'a, T, S> { + fn clone(&self) -> Self { + BoundedSlice(self.0, PhantomData) + } +} + +impl<'a, T, S> sp_std::fmt::Debug for BoundedSlice<'a, T, S> +where + &'a [T]: sp_std::fmt::Debug, + S: Get, +{ + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + f.debug_tuple("BoundedSlice").field(&self.0).field(&S::get()).finish() + } +} + +// Since a reference `&T` is always `Copy`, so is `BoundedSlice<'a, T, S>`. +impl<'a, T, S> Copy for BoundedSlice<'a, T, S> {} + +// will allow for all immutable operations of `[T]` on `BoundedSlice`. +impl<'a, T, S> Deref for BoundedSlice<'a, T, S> { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for BoundedSlice<'a, T, S> { + type Item = &'a T; + type IntoIter = sp_std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, T, S: Get> BoundedSlice<'a, T, S> { + /// Create an instance from the first elements of the given slice (or all of it if it is smaller + /// than the length bound). + pub fn truncate_from(s: &'a [T]) -> Self { + Self(&s[0..(s.len().min(S::get() as usize))], PhantomData) + } +} + +impl> Decode for BoundedVec { + fn decode(input: &mut I) -> Result { + let inner = Vec::::decode(input)?; + if inner.len() > S::get() as usize { + return Err("BoundedVec exceeds its limit".into()) + } + Ok(Self(inner, PhantomData)) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + Vec::::skip(input) + } +} + +// `BoundedVec`s encode to something which will always decode as a `Vec`. +impl> EncodeLike> for BoundedVec {} + +impl BoundedVec { + /// Create `Self` from `t` without any checks. + fn unchecked_from(t: Vec) -> Self { + Self(t, Default::default()) + } + + /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an + /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can + /// be used. + /// + /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which + /// is not provided by the wrapper `BoundedVec`. + pub fn into_inner(self) -> Vec { + self.0 + } + + /// Exactly the same semantics as [`slice::sort_by`]. + /// + /// This is safe since sorting cannot change the number of elements in the vector. + pub fn sort_by(&mut self, compare: F) + where + F: FnMut(&T, &T) -> sp_std::cmp::Ordering, + { + self.0.sort_by(compare) + } + + /// Exactly the same semantics as [`slice::sort_by_key`]. + /// + /// This is safe since sorting cannot change the number of elements in the vector. + pub fn sort_by_key(&mut self, f: F) + where + F: FnMut(&T) -> K, + K: sp_std::cmp::Ord, + { + self.0.sort_by_key(f) + } + + /// Exactly the same semantics as [`slice::sort`]. + /// + /// This is safe since sorting cannot change the number of elements in the vector. + pub fn sort(&mut self) + where + T: sp_std::cmp::Ord, + { + self.0.sort() + } + + /// Exactly the same semantics as `Vec::remove`. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn remove(&mut self, index: usize) -> T { + self.0.remove(index) + } + + /// Exactly the same semantics as `slice::swap_remove`. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn swap_remove(&mut self, index: usize) -> T { + self.0.swap_remove(index) + } + + /// Exactly the same semantics as `Vec::retain`. + pub fn retain bool>(&mut self, f: F) { + self.0.retain(f) + } + + /// Exactly the same semantics as `slice::get_mut`. + pub fn get_mut>( + &mut self, + index: I, + ) -> Option<&mut >::Output> { + self.0.get_mut(index) + } + + /// Exactly the same semantics as `Vec::truncate`. + /// + /// This is safe because `truncate` can never increase the length of the internal vector. + pub fn truncate(&mut self, s: usize) { + self.0.truncate(s); + } + + /// Exactly the same semantics as `Vec::pop`. + /// + /// This is safe since popping can only shrink the inner vector. + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + /// Exactly the same semantics as [`slice::iter_mut`]. + pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> { + self.0.iter_mut() + } + + /// Exactly the same semantics as [`slice::last_mut`]. + pub fn last_mut(&mut self) -> Option<&mut T> { + self.0.last_mut() + } + + /// Exact same semantics as [`Vec::drain`]. + pub fn drain(&mut self, range: R) -> sp_std::vec::Drain<'_, T> + where + R: RangeBounds, + { + self.0.drain(range) + } +} + +impl> From> for Vec { + fn from(x: BoundedVec) -> Vec { + x.0 + } +} + +impl> BoundedVec { + /// Pre-allocate `capacity` items in self. + /// + /// If `capacity` is greater than [`Self::bound`], then the minimum of the two is used. + pub fn with_bounded_capacity(capacity: usize) -> Self { + let capacity = capacity.min(Self::bound()); + Self(Vec::with_capacity(capacity), Default::default()) + } + + /// Allocate self with the maximum possible capacity. + pub fn with_max_capacity() -> Self { + Self::with_bounded_capacity(Self::bound()) + } + + /// Consume and truncate the vector `v` in order to create a new instance of `Self` from it. + pub fn truncate_from(mut v: Vec) -> Self { + v.truncate(Self::bound()); + Self::unchecked_from(v) + } + + /// Get the bound of the type in `usize`. + pub fn bound() -> usize { + S::get() as usize + } + + /// Returns true of this collection is full. + pub fn is_full(&self) -> bool { + self.len() >= Self::bound() + } + + /// Forces the insertion of `element` into `self` retaining all items with index at least + /// `index`. + /// + /// If `index == 0` and `self.len() == Self::bound()`, then this is a no-op. + /// + /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. + /// + /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is + /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if + /// `element` cannot be inserted. + pub fn force_insert_keep_right( + &mut self, + index: usize, + mut element: T, + ) -> Result, ()> { + // Check against panics. + if Self::bound() < index || self.len() < index { + Err(()) + } else if self.len() < Self::bound() { + // Cannot panic since self.len() >= index; + self.0.insert(index, element); + Ok(None) + } else { + if index == 0 { + return Err(()) + } + sp_std::mem::swap(&mut self[0], &mut element); + // `[0..index] cannot panic since self.len() >= index. + // `rotate_left(1)` cannot panic because there is at least 1 element. + self[0..index].rotate_left(1); + Ok(Some(element)) + } + } + + /// Forces the insertion of `element` into `self` retaining all items with index at most + /// `index`. + /// + /// If `index == Self::bound()` and `self.len() == Self::bound()`, then this is a no-op. + /// + /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. + /// + /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is + /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if + /// `element` cannot be inserted. + pub fn force_insert_keep_left(&mut self, index: usize, element: T) -> Result, ()> { + // Check against panics. + if Self::bound() < index || self.len() < index || Self::bound() == 0 { + return Err(()) + } + // Noop condition. + if Self::bound() == index && self.len() <= Self::bound() { + return Err(()) + } + let maybe_removed = if self.is_full() { + // defensive-only: since we are at capacity, this is a noop. + self.0.truncate(Self::bound()); + // if we truncate anything, it will be the last one. + self.0.pop() + } else { + None + }; + + // Cannot panic since `self.len() >= index`; + self.0.insert(index, element); + Ok(maybe_removed) + } + + /// Move the position of an item from one location to another in the slice. + /// + /// Except for the item being moved, the order of the slice remains the same. + /// + /// - `index` is the location of the item to be moved. + /// - `insert_position` is the index of the item in the slice which should *immediately follow* + /// the item which is being moved. + /// + /// Returns `true` of the operation was successful, otherwise `false` if a noop. + pub fn slide(&mut self, index: usize, insert_position: usize) -> bool { + // Check against panics. + if self.len() <= index || self.len() < insert_position || index == usize::MAX { + return false + } + // Noop conditions. + if index == insert_position || index + 1 == insert_position { + return false + } + if insert_position < index && index < self.len() { + // --- --- --- === === === === @@@ --- --- --- + // ^-- N ^O^ + // ... + // /-----<<<-----\ + // --- --- --- === === === === @@@ --- --- --- + // >>> >>> >>> >>> + // ... + // --- --- --- @@@ === === === === --- --- --- + // ^N^ + self[insert_position..index + 1].rotate_right(1); + return true + } else if insert_position > 0 && index + 1 < insert_position { + // Note that the apparent asymmetry of these two branches is due to the + // fact that the "new" position is the position to be inserted *before*. + // --- --- --- @@@ === === === === --- --- --- + // ^O^ ^-- N + // ... + // /----->>>-----\ + // --- --- --- @@@ === === === === --- --- --- + // <<< <<< <<< <<< + // ... + // --- --- --- === === === === @@@ --- --- --- + // ^N^ + self[index..insert_position].rotate_left(1); + return true + } + + debug_assert!(false, "all noop conditions should have been covered above"); + false + } + + /// Forces the insertion of `s` into `self` truncating first if necessary. + /// + /// Infallible, but if the bound is zero, then it's a no-op. + pub fn force_push(&mut self, element: T) { + if Self::bound() > 0 { + self.0.truncate(Self::bound() as usize - 1); + self.0.push(element); + } + } + + /// Same as `Vec::resize`, but if `size` is more than [`Self::bound`], then [`Self::bound`] is + /// used. + pub fn bounded_resize(&mut self, size: usize, value: T) + where + T: Clone, + { + let size = size.min(Self::bound()); + self.0.resize(size, value); + } + + /// Exactly the same semantics as [`Vec::extend`], but returns an error and does nothing if the + /// length of the outcome is larger than the bound. + pub fn try_extend( + &mut self, + with: impl IntoIterator + ExactSizeIterator, + ) -> Result<(), ()> { + if with.len().saturating_add(self.len()) <= Self::bound() { + self.0.extend(with); + Ok(()) + } else { + Err(()) + } + } + + /// Exactly the same semantics as [`Vec::append`], but returns an error and does nothing if the + /// length of the outcome is larger than the bound. + pub fn try_append(&mut self, other: &mut Vec) -> Result<(), ()> { + if other.len().saturating_add(self.len()) <= Self::bound() { + self.0.append(other); + Ok(()) + } else { + Err(()) + } + } + + /// Consumes self and mutates self via the given `mutate` function. + /// + /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is + /// returned. + /// + /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> + /// [`Self::try_from`]. + pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { + mutate(&mut self.0); + (self.0.len() <= Self::bound()).then(move || self) + } + + /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the + /// new length of the vector exceeds `S`. + /// + /// # Panics + /// + /// Panics if `index > len`. + pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), T> { + if self.len() < Self::bound() { + self.0.insert(index, element); + Ok(()) + } else { + Err(element) + } + } + + /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the + /// new length of the vector exceeds `S`. + /// + /// # Panics + /// + /// Panics if the new capacity exceeds isize::MAX bytes. + pub fn try_push(&mut self, element: T) -> Result<(), T> { + if self.len() < Self::bound() { + self.0.push(element); + Ok(()) + } else { + Err(element) + } + } +} + +impl BoundedVec { + /// Return a [`BoundedSlice`] with the content and bound of [`Self`]. + pub fn as_bounded_slice(&self) -> BoundedSlice { + BoundedSlice(&self.0[..], PhantomData::default()) + } +} + +impl Default for BoundedVec { + fn default() -> Self { + // the bound cannot be below 0, which is satisfied by an empty vector + Self::unchecked_from(Vec::default()) + } +} + +impl sp_std::fmt::Debug for BoundedVec +where + Vec: sp_std::fmt::Debug, + S: Get, +{ + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + f.debug_tuple("BoundedVec").field(&self.0).field(&Self::bound()).finish() + } +} + +impl Clone for BoundedVec +where + T: Clone, +{ + fn clone(&self) -> Self { + // bound is retained + Self::unchecked_from(self.0.clone()) + } +} + +impl> TryFrom> for BoundedVec { + type Error = Vec; + fn try_from(t: Vec) -> Result { + if t.len() <= Self::bound() { + // explicit check just above + Ok(Self::unchecked_from(t)) + } else { + Err(t) + } + } +} + +impl> TruncateFrom> for BoundedVec { + fn truncate_from(unbound: Vec) -> Self { + BoundedVec::::truncate_from(unbound) + } +} + +// It is okay to give a non-mutable reference of the inner vec to anyone. +impl AsRef> for BoundedVec { + fn as_ref(&self) -> &Vec { + &self.0 + } +} + +impl AsRef<[T]> for BoundedVec { + fn as_ref(&self) -> &[T] { + &self.0 + } +} + +impl AsMut<[T]> for BoundedVec { + fn as_mut(&mut self) -> &mut [T] { + &mut self.0 + } +} + +// will allow for all immutable operations of `Vec` on `BoundedVec`. +impl Deref for BoundedVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. +impl Index for BoundedVec +where + I: SliceIndex<[T]>, +{ + type Output = I::Output; + + #[inline] + fn index(&self, index: I) -> &Self::Output { + self.0.index(index) + } +} + +impl IndexMut for BoundedVec +where + I: SliceIndex<[T]>, +{ + #[inline] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.0.index_mut(index) + } +} + +impl sp_std::iter::IntoIterator for BoundedVec { + type Item = T; + type IntoIter = sp_std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for &'a BoundedVec { + type Item = &'a T; + type IntoIter = sp_std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut BoundedVec { + type Item = &'a mut T; + type IntoIter = sp_std::slice::IterMut<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + +impl codec::DecodeLength for BoundedVec { + fn len(self_encoded: &[u8]) -> Result { + // `BoundedVec` stored just a `Vec`, thus the length is at the beginning in + // `Compact` form, and same implementation as `Vec` can be used. + as codec::DecodeLength>::len(self_encoded) + } +} + +impl PartialEq> for BoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &BoundedVec) -> bool { + self.0 == rhs.0 + } +} + +impl PartialEq> for BoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &WeakBoundedVec) -> bool { + self.0 == rhs.0 + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialEq> + for BoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &BoundedSlice<'a, T, BoundRhs>) -> bool { + self.0 == rhs.0 + } +} + +impl<'a, T: PartialEq, S: Get> PartialEq<&'a [T]> for BoundedSlice<'a, T, S> { + fn eq(&self, other: &&'a [T]) -> bool { + &self.0 == other + } +} + +impl> PartialEq> for BoundedVec { + fn eq(&self, other: &Vec) -> bool { + &self.0 == other + } +} + +impl> Eq for BoundedVec where T: Eq {} + +impl PartialOrd> for BoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedVec) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl PartialOrd> for BoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialOrd> + for BoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { + (&*self.0).partial_cmp(other.0) + } +} + +impl> Ord for BoundedVec { + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl MaxEncodedLen for BoundedVec +where + T: MaxEncodedLen, + S: Get, + BoundedVec: Encode, +{ + fn max_encoded_len() -> usize { + // BoundedVec encodes like Vec which encodes like [T], which is a compact u32 + // plus each item in the slice: + // See: https://docs.substrate.io/reference/scale-codec/ + codec::Compact(S::get()) + .encoded_size() + .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) + } +} + +impl TryCollect> for I +where + I: ExactSizeIterator + Iterator, + Bound: Get, +{ + type Error = &'static str; + + fn try_collect(self) -> Result, Self::Error> { + if self.len() > Bound::get() as usize { + Err("iterator length too big") + } else { + Ok(BoundedVec::::unchecked_from(self.collect::>())) + } + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::{bounded_vec, ConstU32}; + + #[test] + fn slice_truncate_from_works() { + let bounded = BoundedSlice::>::truncate_from(&[1, 2, 3, 4, 5]); + assert_eq!(bounded.deref(), &[1, 2, 3, 4]); + let bounded = BoundedSlice::>::truncate_from(&[1, 2, 3, 4]); + assert_eq!(bounded.deref(), &[1, 2, 3, 4]); + let bounded = BoundedSlice::>::truncate_from(&[1, 2, 3]); + assert_eq!(bounded.deref(), &[1, 2, 3]); + } + + #[test] + fn slide_works() { + let mut b: BoundedVec> = bounded_vec![0, 1, 2, 3, 4, 5]; + assert!(b.slide(1, 5)); + assert_eq!(*b, vec![0, 2, 3, 4, 1, 5]); + assert!(b.slide(4, 0)); + assert_eq!(*b, vec![1, 0, 2, 3, 4, 5]); + assert!(b.slide(0, 2)); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + assert!(b.slide(1, 6)); + assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); + assert!(b.slide(0, 6)); + assert_eq!(*b, vec![2, 3, 4, 5, 1, 0]); + assert!(b.slide(5, 0)); + assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); + assert!(!b.slide(6, 0)); + assert!(!b.slide(7, 0)); + assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); + + let mut c: BoundedVec> = bounded_vec![0, 1, 2]; + assert!(!c.slide(1, 5)); + assert_eq!(*c, vec![0, 1, 2]); + assert!(!c.slide(4, 0)); + assert_eq!(*c, vec![0, 1, 2]); + assert!(!c.slide(3, 0)); + assert_eq!(*c, vec![0, 1, 2]); + assert!(c.slide(2, 0)); + assert_eq!(*c, vec![2, 0, 1]); + } + + #[test] + fn slide_noops_work() { + let mut b: BoundedVec> = bounded_vec![0, 1, 2, 3, 4, 5]; + assert!(!b.slide(3, 3)); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + assert!(!b.slide(3, 4)); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + } + + #[test] + fn force_insert_keep_left_works() { + let mut b: BoundedVec> = bounded_vec![]; + assert_eq!(b.force_insert_keep_left(1, 10), Err(())); + assert!(b.is_empty()); + + assert_eq!(b.force_insert_keep_left(0, 30), Ok(None)); + assert_eq!(b.force_insert_keep_left(0, 10), Ok(None)); + assert_eq!(b.force_insert_keep_left(1, 20), Ok(None)); + assert_eq!(b.force_insert_keep_left(3, 40), Ok(None)); + assert_eq!(*b, vec![10, 20, 30, 40]); + // at capacity. + assert_eq!(b.force_insert_keep_left(4, 41), Err(())); + assert_eq!(*b, vec![10, 20, 30, 40]); + assert_eq!(b.force_insert_keep_left(3, 31), Ok(Some(40))); + assert_eq!(*b, vec![10, 20, 30, 31]); + assert_eq!(b.force_insert_keep_left(1, 11), Ok(Some(31))); + assert_eq!(*b, vec![10, 11, 20, 30]); + assert_eq!(b.force_insert_keep_left(0, 1), Ok(Some(30))); + assert_eq!(*b, vec![1, 10, 11, 20]); + + let mut z: BoundedVec> = bounded_vec![]; + assert!(z.is_empty()); + assert_eq!(z.force_insert_keep_left(0, 10), Err(())); + assert!(z.is_empty()); + } + + #[test] + fn force_insert_keep_right_works() { + let mut b: BoundedVec> = bounded_vec![]; + assert_eq!(b.force_insert_keep_right(1, 10), Err(())); + assert!(b.is_empty()); + + assert_eq!(b.force_insert_keep_right(0, 30), Ok(None)); + assert_eq!(b.force_insert_keep_right(0, 10), Ok(None)); + assert_eq!(b.force_insert_keep_right(1, 20), Ok(None)); + assert_eq!(b.force_insert_keep_right(3, 40), Ok(None)); + assert_eq!(*b, vec![10, 20, 30, 40]); + + // at capacity. + assert_eq!(b.force_insert_keep_right(0, 0), Err(())); + assert_eq!(*b, vec![10, 20, 30, 40]); + assert_eq!(b.force_insert_keep_right(1, 11), Ok(Some(10))); + assert_eq!(*b, vec![11, 20, 30, 40]); + assert_eq!(b.force_insert_keep_right(3, 31), Ok(Some(11))); + assert_eq!(*b, vec![20, 30, 31, 40]); + assert_eq!(b.force_insert_keep_right(4, 41), Ok(Some(20))); + assert_eq!(*b, vec![30, 31, 40, 41]); + + assert_eq!(b.force_insert_keep_right(5, 69), Err(())); + assert_eq!(*b, vec![30, 31, 40, 41]); + + let mut z: BoundedVec> = bounded_vec![]; + assert!(z.is_empty()); + assert_eq!(z.force_insert_keep_right(0, 10), Err(())); + assert!(z.is_empty()); + } + + #[test] + fn bound_returns_correct_value() { + assert_eq!(BoundedVec::>::bound(), 7); + } + + #[test] + fn try_insert_works() { + let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; + bounded.try_insert(1, 0).unwrap(); + assert_eq!(*bounded, vec![1, 0, 2, 3]); + + assert!(bounded.try_insert(0, 9).is_err()); + assert_eq!(*bounded, vec![1, 0, 2, 3]); + } + + #[test] + fn constructor_macro_works() { + // With values. Use some brackets to make sure the macro doesn't expand. + let bv: BoundedVec<(u32, u32), ConstU32<3>> = bounded_vec![(1, 2), (1, 2), (1, 2)]; + assert_eq!(bv, vec![(1, 2), (1, 2), (1, 2)]); + + // With repetition. + let bv: BoundedVec<(u32, u32), ConstU32<3>> = bounded_vec![(1, 2); 3]; + assert_eq!(bv, vec![(1, 2), (1, 2), (1, 2)]); + } + + #[test] + #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] + fn try_inert_panics_if_oob() { + let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; + bounded.try_insert(9, 0).unwrap(); + } + + #[test] + fn try_push_works() { + let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; + bounded.try_push(0).unwrap(); + assert_eq!(*bounded, vec![1, 2, 3, 0]); + + assert!(bounded.try_push(9).is_err()); + } + + #[test] + fn deref_vec_coercion_works() { + let bounded: BoundedVec> = bounded_vec![1, 2, 3]; + // these methods come from deref-ed vec. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + + #[test] + fn deref_slice_coercion_works() { + let bounded = BoundedSlice::>::try_from(&[1, 2, 3][..]).unwrap(); + // these methods come from deref-ed slice. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + + #[test] + fn try_mutate_works() { + let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; + let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); + assert_eq!(bounded.len(), 7); + assert!(bounded.try_mutate(|v| v.push(8)).is_none()); + } + + #[test] + fn slice_indexing_works() { + let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; + assert_eq!(&bounded[0..=2], &[1, 2, 3]); + } + + #[test] + fn vec_eq_works() { + let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; + assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); + } + + #[test] + fn too_big_vec_fail_to_decode() { + let v: Vec = vec![1, 2, 3, 4, 5]; + assert_eq!( + BoundedVec::>::decode(&mut &v.encode()[..]), + Err("BoundedVec exceeds its limit".into()), + ); + } + + #[test] + fn eq_works() { + // of same type + let b1: BoundedVec> = bounded_vec![1, 2, 3]; + let b2: BoundedVec> = bounded_vec![1, 2, 3]; + assert_eq!(b1, b2); + + // of different type, but same value and bound. + crate::parameter_types! { + B1: u32 = 7; + B2: u32 = 7; + } + let b1: BoundedVec = bounded_vec![1, 2, 3]; + let b2: BoundedVec = bounded_vec![1, 2, 3]; + assert_eq!(b1, b2); + } + + #[test] + fn ord_works() { + use std::cmp::Ordering; + let b1: BoundedVec> = bounded_vec![1, 2, 3]; + let b2: BoundedVec> = bounded_vec![1, 3, 2]; + + // ordering for vec is lexicographic. + assert_eq!(b1.cmp(&b2), Ordering::Less); + assert_eq!(b1.cmp(&b2), b1.into_inner().cmp(&b2.into_inner())); + } + + #[test] + fn try_extend_works() { + let mut b: BoundedVec> = bounded_vec![1, 2, 3]; + + assert!(b.try_extend(vec![4].into_iter()).is_ok()); + assert_eq!(*b, vec![1, 2, 3, 4]); + + assert!(b.try_extend(vec![5].into_iter()).is_ok()); + assert_eq!(*b, vec![1, 2, 3, 4, 5]); + + assert!(b.try_extend(vec![6].into_iter()).is_err()); + assert_eq!(*b, vec![1, 2, 3, 4, 5]); + + let mut b: BoundedVec> = bounded_vec![1, 2, 3]; + assert!(b.try_extend(vec![4, 5].into_iter()).is_ok()); + assert_eq!(*b, vec![1, 2, 3, 4, 5]); + + let mut b: BoundedVec> = bounded_vec![1, 2, 3]; + assert!(b.try_extend(vec![4, 5, 6].into_iter()).is_err()); + assert_eq!(*b, vec![1, 2, 3]); + } + + #[test] + fn test_serializer() { + let c: BoundedVec> = bounded_vec![0, 1, 2]; + assert_eq!(serde_json::json!(&c).to_string(), r#"[0,1,2]"#); + } + + #[test] + fn test_deserializer() { + let c: BoundedVec> = serde_json::from_str(r#"[0,1,2]"#).unwrap(); + + assert_eq!(c.len(), 3); + assert_eq!(c[0], 0); + assert_eq!(c[1], 1); + assert_eq!(c[2], 2); + } + + #[test] + fn test_deserializer_failed() { + let c: Result>, serde_json::error::Error> = + serde_json::from_str(r#"[0,1,2,3,4,5]"#); + + match c { + Err(msg) => assert_eq!(msg.to_string(), "out of bounds at line 1 column 11"), + _ => unreachable!("deserializer must raise error"), + } + } + + #[test] + fn bounded_vec_try_from_works() { + assert!(BoundedVec::>::try_from(vec![0]).is_ok()); + assert!(BoundedVec::>::try_from(vec![0, 1]).is_ok()); + assert!(BoundedVec::>::try_from(vec![0, 1, 2]).is_err()); + } + + #[test] + fn bounded_slice_try_from_works() { + assert!(BoundedSlice::>::try_from(&[0][..]).is_ok()); + assert!(BoundedSlice::>::try_from(&[0, 1][..]).is_ok()); + assert!(BoundedSlice::>::try_from(&[0, 1, 2][..]).is_err()); + } + + #[test] + fn can_be_collected() { + let b1: BoundedVec> = bounded_vec![1, 2, 3, 4]; + let b2: BoundedVec> = b1.iter().map(|x| x + 1).try_collect().unwrap(); + assert_eq!(b2, vec![2, 3, 4, 5]); + + // can also be collected into a collection of length 4. + let b2: BoundedVec> = b1.iter().map(|x| x + 1).try_collect().unwrap(); + assert_eq!(b2, vec![2, 3, 4, 5]); + + // can be mutated further into iterators that are `ExactSizedIterator`. + let b2: BoundedVec> = + b1.iter().map(|x| x + 1).rev().try_collect().unwrap(); + assert_eq!(b2, vec![5, 4, 3, 2]); + + let b2: BoundedVec> = + b1.iter().map(|x| x + 1).rev().skip(2).try_collect().unwrap(); + assert_eq!(b2, vec![3, 2]); + let b2: BoundedVec> = + b1.iter().map(|x| x + 1).rev().skip(2).try_collect().unwrap(); + assert_eq!(b2, vec![3, 2]); + + let b2: BoundedVec> = + b1.iter().map(|x| x + 1).rev().take(2).try_collect().unwrap(); + assert_eq!(b2, vec![5, 4]); + let b2: BoundedVec> = + b1.iter().map(|x| x + 1).rev().take(2).try_collect().unwrap(); + assert_eq!(b2, vec![5, 4]); + + // but these worn't work + let b2: Result>, _> = b1.iter().map(|x| x + 1).try_collect(); + assert!(b2.is_err()); + + let b2: Result>, _> = + b1.iter().map(|x| x + 1).rev().take(2).try_collect(); + assert!(b2.is_err()); + } + + #[test] + fn bounded_vec_debug_works() { + let bound = BoundedVec::>::truncate_from(vec![1, 2, 3]); + assert_eq!(format!("{:?}", bound), "BoundedVec([1, 2, 3], 5)"); + } + + #[test] + fn bounded_slice_debug_works() { + let bound = BoundedSlice::>::truncate_from(&[1, 2, 3]); + assert_eq!(format!("{:?}", bound), "BoundedSlice([1, 2, 3], 5)"); + } + + #[test] + fn bounded_vec_sort_by_key_works() { + let mut v: BoundedVec> = bounded_vec![-5, 4, 1, -3, 2]; + // Sort by absolute value. + v.sort_by_key(|k| k.abs()); + assert_eq!(v, vec![1, 2, -3, 4, -5]); + } + + #[test] + fn bounded_vec_truncate_from_works() { + let unbound = vec![1, 2, 3, 4, 5]; + let bound = BoundedVec::>::truncate_from(unbound.clone()); + assert_eq!(bound, vec![1, 2, 3]); + } + + #[test] + fn bounded_slice_truncate_from_works() { + let unbound = [1, 2, 3, 4, 5]; + let bound = BoundedSlice::>::truncate_from(&unbound); + assert_eq!(bound, &[1, 2, 3][..]); + } + + #[test] + fn bounded_slice_partialeq_slice_works() { + let unbound = [1, 2, 3]; + let bound = BoundedSlice::>::truncate_from(&unbound); + + assert_eq!(bound, &unbound[..]); + assert!(bound == &unbound[..]); + } +} diff --git a/primitives/core/src/bounded/weak_bounded_vec.rs b/primitives/core/src/bounded/weak_bounded_vec.rs new file mode 100644 index 0000000000000..5aff35f010c8b --- /dev/null +++ b/primitives/core/src/bounded/weak_bounded_vec.rs @@ -0,0 +1,524 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Traits, types and structs to support putting a bounded vector into storage, as a raw value, map +//! or a double map. + +use super::{BoundedSlice, BoundedVec}; +use crate::Get; +use codec::{Decode, Encode, MaxEncodedLen}; +use core::{ + ops::{Deref, Index, IndexMut}, + slice::SliceIndex, +}; +#[cfg(feature = "std")] +use serde::{ + de::{Error, SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use sp_std::{marker::PhantomData, prelude::*}; + +/// A weakly bounded vector. +/// +/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once +/// put into storage as a raw value, map or double-map. +/// +/// The length of the vec is not strictly bounded. Decoding a vec with more element that the bound +/// is accepted, and some method allow to bypass the restriction with warnings. +#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] +#[derive(Encode, scale_info::TypeInfo)] +#[scale_info(skip_type_params(S))] +pub struct WeakBoundedVec( + pub(super) Vec, + #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData, +); + +#[cfg(feature = "std")] +impl<'de, T, S: Get> Deserialize<'de> for WeakBoundedVec +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VecVisitor>(PhantomData<(T, S)>); + + impl<'de, T, S: Get> Visitor<'de> for VecVisitor + where + T: Deserialize<'de>, + { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let size = seq.size_hint().unwrap_or(0); + let max = match usize::try_from(S::get()) { + Ok(n) => n, + Err(_) => return Err(A::Error::custom("can't convert to usize")), + }; + if size > max { + log::warn!( + target: "runtime", + "length of a bounded vector while deserializing is not respected.", + ); + } + let mut values = Vec::with_capacity(size); + + while let Some(value) = seq.next_element()? { + values.push(value); + if values.len() > max { + log::warn!( + target: "runtime", + "length of a bounded vector while deserializing is not respected.", + ); + } + } + + Ok(values) + } + } + + let visitor: VecVisitor = VecVisitor(PhantomData); + deserializer.deserialize_seq(visitor).map(|v| { + WeakBoundedVec::::try_from(v).map_err(|_| Error::custom("out of bounds")) + })? + } +} + +impl> Decode for WeakBoundedVec { + fn decode(input: &mut I) -> Result { + let inner = Vec::::decode(input)?; + Ok(Self::force_from(inner, Some("decode"))) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + Vec::::skip(input) + } +} + +impl WeakBoundedVec { + /// Create `Self` from `t` without any checks. + fn unchecked_from(t: Vec) -> Self { + Self(t, Default::default()) + } + + /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an + /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can + /// be used. + /// + /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which + /// is not provided by the wrapper `WeakBoundedVec`. + pub fn into_inner(self) -> Vec { + self.0 + } + + /// Exactly the same semantics as [`Vec::remove`]. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn remove(&mut self, index: usize) -> T { + self.0.remove(index) + } + + /// Exactly the same semantics as [`Vec::swap_remove`]. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn swap_remove(&mut self, index: usize) -> T { + self.0.swap_remove(index) + } + + /// Exactly the same semantics as [`Vec::retain`]. + pub fn retain bool>(&mut self, f: F) { + self.0.retain(f) + } + + /// Exactly the same semantics as [`slice::get_mut`]. + pub fn get_mut>( + &mut self, + index: I, + ) -> Option<&mut >::Output> { + self.0.get_mut(index) + } +} + +impl> WeakBoundedVec { + /// Get the bound of the type in `usize`. + pub fn bound() -> usize { + S::get() as usize + } + + /// Create `Self` from `t` without any checks. Logs warnings if the bound is not being + /// respected. The additional scope can be used to indicate where a potential overflow is + /// happening. + pub fn force_from(t: Vec, scope: Option<&'static str>) -> Self { + if t.len() > Self::bound() { + log::warn!( + target: "runtime", + "length of a bounded vector in scope {} is not respected.", + scope.unwrap_or("UNKNOWN"), + ); + } + + Self::unchecked_from(t) + } + + /// Consumes self and mutates self via the given `mutate` function. + /// + /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is + /// returned. + /// + /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> + /// [`Self::try_from`]. + pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { + mutate(&mut self.0); + (self.0.len() <= Self::bound()).then(move || self) + } + + /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the + /// new length of the vector exceeds `S`. + /// + /// # Panics + /// + /// Panics if `index > len`. + pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), ()> { + if self.len() < Self::bound() { + self.0.insert(index, element); + Ok(()) + } else { + Err(()) + } + } + + /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the + /// new length of the vector exceeds `S`. + /// + /// # Panics + /// + /// Panics if the new capacity exceeds isize::MAX bytes. + pub fn try_push(&mut self, element: T) -> Result<(), ()> { + if self.len() < Self::bound() { + self.0.push(element); + Ok(()) + } else { + Err(()) + } + } +} + +impl Default for WeakBoundedVec { + fn default() -> Self { + // the bound cannot be below 0, which is satisfied by an empty vector + Self::unchecked_from(Vec::default()) + } +} + +impl sp_std::fmt::Debug for WeakBoundedVec +where + Vec: sp_std::fmt::Debug, + S: Get, +{ + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + f.debug_tuple("WeakBoundedVec").field(&self.0).field(&Self::bound()).finish() + } +} + +impl Clone for WeakBoundedVec +where + T: Clone, +{ + fn clone(&self) -> Self { + // bound is retained + Self::unchecked_from(self.0.clone()) + } +} + +impl> TryFrom> for WeakBoundedVec { + type Error = (); + fn try_from(t: Vec) -> Result { + if t.len() <= Self::bound() { + // explicit check just above + Ok(Self::unchecked_from(t)) + } else { + Err(()) + } + } +} + +// It is okay to give a non-mutable reference of the inner vec to anyone. +impl AsRef> for WeakBoundedVec { + fn as_ref(&self) -> &Vec { + &self.0 + } +} + +impl AsRef<[T]> for WeakBoundedVec { + fn as_ref(&self) -> &[T] { + &self.0 + } +} + +impl AsMut<[T]> for WeakBoundedVec { + fn as_mut(&mut self) -> &mut [T] { + &mut self.0 + } +} + +// will allow for immutable all operations of `Vec` on `WeakBoundedVec`. +impl Deref for WeakBoundedVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. +impl Index for WeakBoundedVec +where + I: SliceIndex<[T]>, +{ + type Output = I::Output; + + #[inline] + fn index(&self, index: I) -> &Self::Output { + self.0.index(index) + } +} + +impl IndexMut for WeakBoundedVec +where + I: SliceIndex<[T]>, +{ + #[inline] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.0.index_mut(index) + } +} + +impl sp_std::iter::IntoIterator for WeakBoundedVec { + type Item = T; + type IntoIter = sp_std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for &'a WeakBoundedVec { + type Item = &'a T; + type IntoIter = sp_std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut WeakBoundedVec { + type Item = &'a mut T; + type IntoIter = sp_std::slice::IterMut<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + +impl codec::DecodeLength for WeakBoundedVec { + fn len(self_encoded: &[u8]) -> Result { + // `WeakBoundedVec` stored just a `Vec`, thus the length is at the beginning in + // `Compact` form, and same implementation as `Vec` can be used. + as codec::DecodeLength>::len(self_encoded) + } +} + +impl PartialEq> for WeakBoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &WeakBoundedVec) -> bool { + self.0 == rhs.0 + } +} + +impl PartialEq> for WeakBoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &BoundedVec) -> bool { + self.0 == rhs.0 + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialEq> + for WeakBoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &BoundedSlice<'a, T, BoundRhs>) -> bool { + self.0 == rhs.0 + } +} + +impl> PartialEq> for WeakBoundedVec { + fn eq(&self, other: &Vec) -> bool { + &self.0 == other + } +} + +impl> Eq for WeakBoundedVec where T: Eq {} + +impl PartialOrd> + for WeakBoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl PartialOrd> for WeakBoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedVec) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialOrd> + for WeakBoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { + (&*self.0).partial_cmp(other.0) + } +} + +impl> Ord for WeakBoundedVec { + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl MaxEncodedLen for WeakBoundedVec +where + T: MaxEncodedLen, + S: Get, + WeakBoundedVec: Encode, +{ + fn max_encoded_len() -> usize { + // WeakBoundedVec encodes like Vec which encodes like [T], which is a compact u32 + // plus each item in the slice: + // See: https://docs.substrate.io/reference/scale-codec/ + codec::Compact(S::get()) + .encoded_size() + .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::ConstU32; + + #[test] + fn bound_returns_correct_value() { + assert_eq!(WeakBoundedVec::>::bound(), 7); + } + + #[test] + fn try_insert_works() { + let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); + bounded.try_insert(1, 0).unwrap(); + assert_eq!(*bounded, vec![1, 0, 2, 3]); + + assert!(bounded.try_insert(0, 9).is_err()); + assert_eq!(*bounded, vec![1, 0, 2, 3]); + } + + #[test] + #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] + fn try_inert_panics_if_oob() { + let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); + bounded.try_insert(9, 0).unwrap(); + } + + #[test] + fn try_push_works() { + let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); + bounded.try_push(0).unwrap(); + assert_eq!(*bounded, vec![1, 2, 3, 0]); + + assert!(bounded.try_push(9).is_err()); + } + + #[test] + fn deref_coercion_works() { + let bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); + // these methods come from deref-ed vec. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + + #[test] + fn try_mutate_works() { + let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); + let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); + assert_eq!(bounded.len(), 7); + assert!(bounded.try_mutate(|v| v.push(8)).is_none()); + } + + #[test] + fn slice_indexing_works() { + let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); + assert_eq!(&bounded[0..=2], &[1, 2, 3]); + } + + #[test] + fn vec_eq_works() { + let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); + assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); + } + + #[test] + fn too_big_succeed_to_decode() { + let v: Vec = vec![1, 2, 3, 4, 5]; + let w = WeakBoundedVec::>::decode(&mut &v.encode()[..]).unwrap(); + assert_eq!(v, *w); + } +} diff --git a/primitives/core/src/changes_trie.rs b/primitives/core/src/changes_trie.rs deleted file mode 100644 index f4ce83dc2c877..0000000000000 --- a/primitives/core/src/changes_trie.rs +++ /dev/null @@ -1,321 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 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. - -//! Substrate changes trie configuration. - -use codec::{Decode, Encode}; -use num_traits::Zero; -#[cfg(any(feature = "std", test))] -use serde::{Deserialize, Serialize}; - -/// Substrate changes trie configuration. -#[cfg_attr( - any(feature = "std", test), - derive(Serialize, Deserialize, parity_util_mem::MallocSizeOf) -)] -#[derive(Debug, Clone, PartialEq, Eq, Default, Encode, Decode, scale_info::TypeInfo)] -pub struct ChangesTrieConfiguration { - /// Interval (in blocks) at which level1-digests are created. Digests are not - /// created when this is less or equal to 1. - pub digest_interval: u32, - /// Maximal number of digest levels in hierarchy. 0 means that digests are not - /// created at all (even level1 digests). 1 means only level1-digests are created. - /// 2 means that every digest_interval^2 there will be a level2-digest, and so on. - /// Please ensure that maximum digest interval (i.e. digest_interval^digest_levels) - /// is within `u32` limits. Otherwise you'll never see digests covering such intervals - /// && maximal digests interval will be truncated to the last interval that fits - /// `u32` limits. - pub digest_levels: u32, -} - -/// Substrate changes trie configuration range. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ChangesTrieConfigurationRange { - /// Zero block of configuration. - pub zero: (Number, Hash), - /// Last block of configuration (if configuration has been deactivated at some point). - pub end: Option<(Number, Hash)>, - /// The configuration itself. None if changes tries were disabled in this range. - pub config: Option, -} - -impl ChangesTrieConfiguration { - /// Create new configuration given digest interval and levels. - pub fn new(digest_interval: u32, digest_levels: u32) -> Self { - Self { digest_interval, digest_levels } - } - - /// Is digest build enabled? - pub fn is_digest_build_enabled(&self) -> bool { - self.digest_interval > 1 && self.digest_levels > 0 - } - - /// Do we need to build digest at given block? - pub fn is_digest_build_required_at_block(&self, zero: Number, block: Number) -> bool - where - Number: From - + PartialEq - + ::sp_std::ops::Rem - + ::sp_std::ops::Sub - + ::sp_std::cmp::PartialOrd - + Zero, - { - block > zero && - self.is_digest_build_enabled() && - ((block - zero) % self.digest_interval.into()).is_zero() - } - - /// Returns max digest interval. One if digests are not created at all. - pub fn max_digest_interval(&self) -> u32 { - if !self.is_digest_build_enabled() { - return 1 - } - - // we'll get >1 loop iteration only when bad configuration parameters are selected - let mut current_level = self.digest_levels; - loop { - if let Some(max_digest_interval) = self.digest_interval.checked_pow(current_level) { - return max_digest_interval - } - - current_level -= 1; - } - } - - /// Returns max level digest block number that has been created at block <= passed block number. - /// - /// Returns None if digests are not created at all. - pub fn prev_max_level_digest_block(&self, zero: Number, block: Number) -> Option - where - Number: Clone - + From - + PartialOrd - + PartialEq - + ::sp_std::ops::Add - + ::sp_std::ops::Sub - + ::sp_std::ops::Div - + ::sp_std::ops::Mul - + Zero, - { - if block <= zero { - return None - } - - let (next_begin, next_end) = - self.next_max_level_digest_range(zero.clone(), block.clone())?; - - // if 'next' digest includes our block, then it is a also a previous digest - if next_end == block { - return Some(block) - } - - // if previous digest ends at zero block, then there are no previous digest - let prev_end = next_begin - 1.into(); - if prev_end == zero { - None - } else { - Some(prev_end) - } - } - - /// Returns max level digest blocks range (inclusive) which includes passed block. - /// - /// Returns None if digests are not created at all. - /// It will return the first max-level digest if block is <= zero. - pub fn next_max_level_digest_range( - &self, - zero: Number, - mut block: Number, - ) -> Option<(Number, Number)> - where - Number: Clone - + From - + PartialOrd - + PartialEq - + ::sp_std::ops::Add - + ::sp_std::ops::Sub - + ::sp_std::ops::Div - + ::sp_std::ops::Mul, - { - if !self.is_digest_build_enabled() { - return None - } - - if block <= zero { - block = zero.clone() + 1.into(); - } - - let max_digest_interval: Number = self.max_digest_interval().into(); - let max_digests_since_zero = (block.clone() - zero.clone()) / max_digest_interval.clone(); - if max_digests_since_zero == 0.into() { - return Some((zero.clone() + 1.into(), zero + max_digest_interval)) - } - let last_max_digest_block = zero + max_digests_since_zero * max_digest_interval.clone(); - Some(if block == last_max_digest_block { - (block.clone() - max_digest_interval + 1.into(), block) - } else { - (last_max_digest_block.clone() + 1.into(), last_max_digest_block + max_digest_interval) - }) - } - - /// Returns Some if digest must be built at given block number. - /// The tuple is: - /// ( - /// digest level - /// digest interval (in blocks) - /// step between blocks we're interested in when digest is built - /// ) - pub fn digest_level_at_block( - &self, - zero: Number, - block: Number, - ) -> Option<(u32, u32, u32)> - where - Number: Clone - + From - + PartialEq - + ::sp_std::ops::Rem - + ::sp_std::ops::Sub - + ::sp_std::cmp::PartialOrd - + Zero, - { - if !self.is_digest_build_required_at_block(zero.clone(), block.clone()) { - return None - } - - let relative_block = block - zero; - let mut digest_interval = self.digest_interval; - let mut current_level = 1u32; - let mut digest_step = 1u32; - while current_level < self.digest_levels { - let new_digest_interval = match digest_interval.checked_mul(self.digest_interval) { - Some(new_digest_interval) - if (relative_block.clone() % new_digest_interval.into()).is_zero() => - new_digest_interval, - _ => break, - }; - - digest_step = digest_interval; - digest_interval = new_digest_interval; - current_level += 1; - } - - Some((current_level, digest_interval, digest_step)) - } -} - -#[cfg(test)] -mod tests { - use super::ChangesTrieConfiguration; - - fn config(interval: u32, levels: u32) -> ChangesTrieConfiguration { - ChangesTrieConfiguration { digest_interval: interval, digest_levels: levels } - } - - #[test] - fn is_digest_build_enabled_works() { - assert!(!config(0, 100).is_digest_build_enabled()); - assert!(!config(1, 100).is_digest_build_enabled()); - assert!(config(2, 100).is_digest_build_enabled()); - assert!(!config(100, 0).is_digest_build_enabled()); - assert!(config(100, 1).is_digest_build_enabled()); - } - - #[test] - fn is_digest_build_required_at_block_works() { - fn test_with_zero(zero: u64) { - assert!(!config(8, 4).is_digest_build_required_at_block(zero, zero)); - assert!(!config(8, 4).is_digest_build_required_at_block(zero, zero + 1u64)); - assert!(!config(8, 4).is_digest_build_required_at_block(zero, zero + 2u64)); - assert!(!config(8, 4).is_digest_build_required_at_block(zero, zero + 4u64)); - assert!(config(8, 4).is_digest_build_required_at_block(zero, zero + 8u64)); - assert!(!config(8, 4).is_digest_build_required_at_block(zero, zero + 9u64)); - assert!(config(8, 4).is_digest_build_required_at_block(zero, zero + 64u64)); - assert!(config(8, 4).is_digest_build_required_at_block(zero, zero + 64u64)); - assert!(config(8, 4).is_digest_build_required_at_block(zero, zero + 512u64)); - assert!(config(8, 4).is_digest_build_required_at_block(zero, zero + 4096u64)); - assert!(!config(8, 4).is_digest_build_required_at_block(zero, zero + 4103u64)); - assert!(config(8, 4).is_digest_build_required_at_block(zero, zero + 4104u64)); - assert!(!config(8, 4).is_digest_build_required_at_block(zero, zero + 4108u64)); - } - - test_with_zero(0); - test_with_zero(8); - test_with_zero(17); - } - - #[test] - fn digest_level_at_block_works() { - fn test_with_zero(zero: u64) { - assert_eq!(config(8, 4).digest_level_at_block(zero, zero), None); - assert_eq!(config(8, 4).digest_level_at_block(zero, zero + 7u64), None); - assert_eq!(config(8, 4).digest_level_at_block(zero, zero + 63u64), None); - assert_eq!(config(8, 4).digest_level_at_block(zero, zero + 8u64), Some((1, 8, 1))); - assert_eq!(config(8, 4).digest_level_at_block(zero, zero + 64u64), Some((2, 64, 8))); - assert_eq!(config(8, 4).digest_level_at_block(zero, zero + 512u64), Some((3, 512, 64))); - assert_eq!( - config(8, 4).digest_level_at_block(zero, zero + 4096u64), - Some((4, 4096, 512)) - ); - assert_eq!(config(8, 4).digest_level_at_block(zero, zero + 4112u64), Some((1, 8, 1))); - } - - test_with_zero(0); - test_with_zero(8); - test_with_zero(17); - } - - #[test] - fn max_digest_interval_works() { - assert_eq!(config(0, 0).max_digest_interval(), 1); - assert_eq!(config(2, 2).max_digest_interval(), 4); - assert_eq!(config(8, 4).max_digest_interval(), 4096); - assert_eq!(config(::std::u32::MAX, 1024).max_digest_interval(), ::std::u32::MAX); - } - - #[test] - fn next_max_level_digest_range_works() { - assert_eq!(config(0, 0).next_max_level_digest_range(0u64, 16), None); - assert_eq!(config(1, 1).next_max_level_digest_range(0u64, 16), None); - assert_eq!(config(2, 1).next_max_level_digest_range(0u64, 16), Some((15, 16))); - assert_eq!(config(4, 1).next_max_level_digest_range(0u64, 16), Some((13, 16))); - assert_eq!(config(32, 1).next_max_level_digest_range(0u64, 16), Some((1, 32))); - assert_eq!(config(2, 3).next_max_level_digest_range(0u64, 10), Some((9, 16))); - assert_eq!(config(2, 3).next_max_level_digest_range(0u64, 8), Some((1, 8))); - assert_eq!(config(2, 1).next_max_level_digest_range(1u64, 1), Some((2, 3))); - assert_eq!(config(2, 2).next_max_level_digest_range(7u64, 9), Some((8, 11))); - - assert_eq!(config(2, 2).next_max_level_digest_range(7u64, 5), Some((8, 11))); - } - - #[test] - fn prev_max_level_digest_block_works() { - assert_eq!(config(0, 0).prev_max_level_digest_block(0u64, 16), None); - assert_eq!(config(1, 1).prev_max_level_digest_block(0u64, 16), None); - assert_eq!(config(2, 1).prev_max_level_digest_block(0u64, 16), Some(16)); - assert_eq!(config(4, 1).prev_max_level_digest_block(0u64, 16), Some(16)); - assert_eq!(config(4, 2).prev_max_level_digest_block(0u64, 16), Some(16)); - assert_eq!(config(4, 2).prev_max_level_digest_block(0u64, 17), Some(16)); - assert_eq!(config(4, 2).prev_max_level_digest_block(0u64, 33), Some(32)); - assert_eq!(config(32, 1).prev_max_level_digest_block(0u64, 16), None); - assert_eq!(config(2, 3).prev_max_level_digest_block(0u64, 10), Some(8)); - assert_eq!(config(2, 3).prev_max_level_digest_block(0u64, 8), Some(8)); - assert_eq!(config(2, 2).prev_max_level_digest_block(7u64, 8), None); - - assert_eq!(config(2, 2).prev_max_level_digest_block(7u64, 5), None); - } -} diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index 4764a0cac1b14..06703acea7202 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,8 +26,6 @@ use crate::{ed25519, sr25519}; use base58::{FromBase58, ToBase58}; use codec::{Decode, Encode, MaxEncodedLen}; #[cfg(feature = "std")] -use parking_lot::Mutex; -#[cfg(feature = "std")] use rand::{rngs::OsRng, RngCore}; #[cfg(feature = "std")] use regex::Regex; @@ -38,14 +36,15 @@ pub use secrecy::ExposeSecret; #[cfg(feature = "std")] pub use secrecy::SecretString; use sp_runtime_interface::pass_by::PassByInner; -#[cfg(feature = "std")] -use sp_std::convert::TryInto; #[doc(hidden)] pub use sp_std::ops::Deref; -use sp_std::{convert::TryFrom, hash::Hash, str, vec::Vec}; +use sp_std::{hash::Hash, str, vec::Vec}; /// Trait to zeroize a memory buffer. pub use zeroize::Zeroize; +#[cfg(feature = "full_crypto")] +pub use ss58_registry::{from_known_address_format, Ss58AddressFormat, Ss58AddressFormatRegistry}; + /// The root phrase for our publicly known keys. pub const DEV_PHRASE: &str = "bottom drive obey lake curtain smoke basket hold race lonely fit walk"; @@ -133,9 +132,7 @@ impl DeriveJunction { let mut cc: [u8; JUNCTION_ID_LEN] = Default::default(); index.using_encoded(|data| { if data.len() > JUNCTION_ID_LEN { - let hash_result = blake2_rfc::blake2b::blake2b(JUNCTION_ID_LEN, &[], data); - let hash = hash_result.as_bytes(); - cc.copy_from_slice(hash); + cc.copy_from_slice(&sp_core_hashing::blake2_256(data)); } else { cc[0..data.len()].copy_from_slice(data); } @@ -199,35 +196,55 @@ impl> From for DeriveJunction { } /// An error type for SS58 decoding. +#[cfg_attr(feature = "std", derive(thiserror::Error))] +#[cfg_attr(not(feature = "std"), derive(Debug))] +#[derive(Clone, Copy, Eq, PartialEq)] +#[allow(missing_docs)] #[cfg(feature = "full_crypto")] -#[derive(Clone, Copy, Eq, PartialEq, Debug)] pub enum PublicError { - /// Bad alphabet. + #[cfg_attr(feature = "std", error("Base 58 requirement is violated"))] BadBase58, - /// Bad length. + #[cfg_attr(feature = "std", error("Length is bad"))] BadLength, - /// Unknown identifier for the encoding. - UnknownVersion, - /// Invalid checksum. + #[cfg_attr( + feature = "std", + error( + "Unknown SS58 address format `{}`. ` \ + `To support this address format, you need to call `set_default_ss58_version` at node start up.", + _0 + ) + )] + UnknownSs58AddressFormat(Ss58AddressFormat), + #[cfg_attr(feature = "std", error("Invalid checksum"))] InvalidChecksum, - /// Invalid format. + #[cfg_attr(feature = "std", error("Invalid SS58 prefix byte."))] + InvalidPrefix, + #[cfg_attr(feature = "std", error("Invalid SS58 format."))] InvalidFormat, - /// Invalid derivation path. + #[cfg_attr(feature = "std", error("Invalid derivation path."))] InvalidPath, - /// Disallowed SS58 Address Format for this datatype. + #[cfg_attr(feature = "std", error("Disallowed SS58 Address Format for this datatype."))] FormatNotAllowed, } +#[cfg(feature = "std")] +impl sp_std::fmt::Debug for PublicError { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + // Just use the `Display` implementation + write!(f, "{}", self) + } +} + /// Key that can be encoded to/from SS58. /// -/// See +/// See /// for information on the codec. #[cfg(feature = "full_crypto")] -pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + Default { +pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + ByteArray { /// A format filterer, can be used to ensure that `from_ss58check` family only decode for /// allowed identifiers. By default just refuses the two reserved identifiers. fn format_is_allowed(f: Ss58AddressFormat) -> bool { - !matches!(f, Ss58AddressFormat::Reserved46 | Ss58AddressFormat::Reserved47) + !f.is_reserved() } /// Some if the string is a properly encoded SS58Check address. @@ -235,8 +252,8 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + Default { fn from_ss58check(s: &str) -> Result { Self::from_ss58check_with_version(s).and_then(|(r, v)| match v { v if !v.is_custom() => Ok(r), - v if v == *DEFAULT_VERSION.lock() => Ok(r), - _ => Err(PublicError::UnknownVersion), + v if v == default_ss58_version() => Ok(r), + v => Err(PublicError::UnknownSs58AddressFormat(v)), }) } @@ -244,10 +261,7 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + Default { #[cfg(feature = "std")] fn from_ss58check_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError> { const CHECKSUM_LEN: usize = 2; - let mut res = Self::default(); - - // Must decode to our type. - let body_len = res.as_mut().len(); + let body_len = Self::LEN; let data = s.from_base58().map_err(|_| PublicError::BadBase58)?; if data.len() < 2 { @@ -265,24 +279,26 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + Default { let upper = data[1] & 0b00111111; (2, (lower as u16) | ((upper as u16) << 8)) }, - _ => return Err(PublicError::UnknownVersion), + _ => return Err(PublicError::InvalidPrefix), }; if data.len() != prefix_len + body_len + CHECKSUM_LEN { return Err(PublicError::BadLength) } - let format = ident.try_into().map_err(|_: ()| PublicError::UnknownVersion)?; + let format = ident.into(); if !Self::format_is_allowed(format) { return Err(PublicError::FormatNotAllowed) } let hash = ss58hash(&data[0..body_len + prefix_len]); - let checksum = &hash.as_bytes()[0..CHECKSUM_LEN]; + let checksum = &hash[0..CHECKSUM_LEN]; if data[body_len + prefix_len..body_len + prefix_len + CHECKSUM_LEN] != *checksum { // Invalid checksum. return Err(PublicError::InvalidChecksum) } - res.as_mut().copy_from_slice(&data[prefix_len..body_len + prefix_len]); - Ok((res, format)) + + let result = Self::from_slice(&data[prefix_len..body_len + prefix_len]) + .map_err(|()| PublicError::BadLength)?; + Ok((result, format)) } /// Some if the string is a properly encoded SS58Check address, optionally with @@ -291,8 +307,8 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + Default { fn from_string(s: &str) -> Result { Self::from_string_with_version(s).and_then(|(r, v)| match v { v if !v.is_custom() => Ok(r), - v if v == *DEFAULT_VERSION.lock() => Ok(r), - _ => Err(PublicError::UnknownVersion), + v if v == default_ss58_version() => Ok(r), + v => Err(PublicError::UnknownSs58AddressFormat(v)), }) } @@ -315,14 +331,14 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + Default { }; v.extend(self.as_ref()); let r = ss58hash(&v); - v.extend(&r.as_bytes()[0..2]); + v.extend(&r[0..2]); v.to_base58() } /// Return the ss58-check string for this key. #[cfg(feature = "std")] fn to_ss58check(&self) -> String { - self.to_ss58check_with_version(*DEFAULT_VERSION.lock()) + self.to_ss58check_with_version(default_ss58_version()) } /// Some if the string is a properly encoded SS58Check address, optionally with @@ -348,287 +364,45 @@ pub trait Derive: Sized { const PREFIX: &[u8] = b"SS58PRE"; #[cfg(feature = "std")] -fn ss58hash(data: &[u8]) -> blake2_rfc::blake2b::Blake2bResult { - let mut context = blake2_rfc::blake2b::Blake2b::new(64); - context.update(PREFIX); - context.update(data); - context.finalize() -} +fn ss58hash(data: &[u8]) -> Vec { + use blake2::{Blake2b512, Digest}; -#[cfg(feature = "std")] -lazy_static::lazy_static! { - static ref DEFAULT_VERSION: Mutex - = Mutex::new(Ss58AddressFormat::SubstrateAccount); + let mut ctx = Blake2b512::new(); + ctx.update(PREFIX); + ctx.update(data); + ctx.finalize().to_vec() } -#[cfg(feature = "full_crypto")] -macro_rules! ss58_address_format { - ( $( $identifier:tt => ($number:expr, $name:expr, $desc:tt) )* ) => ( - /// A known address (sub)format/network ID for SS58. - #[derive(Copy, Clone, PartialEq, Eq, crate::RuntimeDebug)] - pub enum Ss58AddressFormat { - $(#[doc = $desc] $identifier),*, - /// Use a manually provided numeric value as a standard identifier - Custom(u16), - } - - #[cfg(feature = "std")] - impl std::fmt::Display for Ss58AddressFormat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - $( - Ss58AddressFormat::$identifier => write!(f, "{}", $name), - )* - Ss58AddressFormat::Custom(x) => write!(f, "{}", x), - } - - } - } - - static ALL_SS58_ADDRESS_FORMATS: [Ss58AddressFormat; 0 $(+ { let _ = $number; 1})*] = [ - $(Ss58AddressFormat::$identifier),*, - ]; - - impl Ss58AddressFormat { - /// names of all address formats - pub fn all_names() -> &'static [&'static str] { - &[ - $($name),*, - ] - } - /// All known address formats. - pub fn all() -> &'static [Ss58AddressFormat] { - &ALL_SS58_ADDRESS_FORMATS - } - - /// Whether the address is custom. - pub fn is_custom(&self) -> bool { - matches!(self, Self::Custom(_)) - } - } - - impl TryFrom for Ss58AddressFormat { - type Error = (); - - fn try_from(x: u8) -> Result { - Ss58AddressFormat::try_from(x as u16) - } - } - - impl From for u16 { - fn from(x: Ss58AddressFormat) -> u16 { - match x { - $(Ss58AddressFormat::$identifier => $number),*, - Ss58AddressFormat::Custom(n) => n, - } - } - } - - impl TryFrom for Ss58AddressFormat { - type Error = (); - - fn try_from(x: u16) -> Result { - match x { - $($number => Ok(Ss58AddressFormat::$identifier)),*, - _ => Ok(Ss58AddressFormat::Custom(x)), - } - } - } - - /// Error encountered while parsing `Ss58AddressFormat` from &'_ str - /// unit struct for now. - #[derive(Copy, Clone, PartialEq, Eq, crate::RuntimeDebug)] - pub struct ParseError; - - impl<'a> TryFrom<&'a str> for Ss58AddressFormat { - type Error = ParseError; - - fn try_from(x: &'a str) -> Result { - match x { - $($name => Ok(Ss58AddressFormat::$identifier)),*, - a => a.parse::().map(Ss58AddressFormat::Custom).map_err(|_| ParseError), - } - } - } - - #[cfg(feature = "std")] - impl std::str::FromStr for Ss58AddressFormat { - type Err = ParseError; - - fn from_str(data: &str) -> Result { - Self::try_from(data) - } - } - - #[cfg(feature = "std")] - impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "failed to parse network value as u8") - } - } - - #[cfg(feature = "std")] - impl Default for Ss58AddressFormat { - fn default() -> Self { - *DEFAULT_VERSION.lock() - } - } +/// Default prefix number +#[cfg(feature = "std")] +static DEFAULT_VERSION: core::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new( + from_known_address_format(Ss58AddressFormatRegistry::SubstrateAccount), +); - #[cfg(feature = "std")] - impl From for String { - fn from(x: Ss58AddressFormat) -> String { - x.to_string() - } - } - ) +/// Returns default SS58 format used by the current active process. +#[cfg(feature = "std")] +pub fn default_ss58_version() -> Ss58AddressFormat { + DEFAULT_VERSION.load(std::sync::atomic::Ordering::Relaxed).into() } -#[cfg(feature = "full_crypto")] -ss58_address_format!( - PolkadotAccount => - (0, "polkadot", "Polkadot Relay-chain, standard account (*25519).") - BareSr25519 => - (1, "sr25519", "Bare 32-bit Schnorr/Ristretto 25519 (S/R 25519) key.") - KusamaAccount => - (2, "kusama", "Kusama Relay-chain, standard account (*25519).") - BareEd25519 => - (3, "ed25519", "Bare 32-bit Edwards Ed25519 key.") - KatalChainAccount => - (4, "katalchain", "Katal Chain, standard account (*25519).") - PlasmAccount => - (5, "plasm", "Plasm Network, standard account (*25519).") - BifrostAccount => - (6, "bifrost", "Bifrost mainnet, direct checksum, standard account (*25519).") - EdgewareAccount => - (7, "edgeware", "Edgeware mainnet, standard account (*25519).") - KaruraAccount => - (8, "karura", "Acala Karura canary network, standard account (*25519).") - ReynoldsAccount => - (9, "reynolds", "Laminar Reynolds canary network, standard account (*25519).") - AcalaAccount => - (10, "acala", "Acala mainnet, standard account (*25519).") - LaminarAccount => - (11, "laminar", "Laminar mainnet, standard account (*25519).") - PolymathAccount => - (12, "polymath", "Polymath network, standard account (*25519).") - SubstraTeeAccount => - (13, "substratee", "Any SubstraTEE off-chain network private account (*25519).") - TotemAccount => - (14, "totem", "Any Totem Live Accounting network standard account (*25519).") - SynesthesiaAccount => - (15, "synesthesia", "Synesthesia mainnet, standard account (*25519).") - KulupuAccount => - (16, "kulupu", "Kulupu mainnet, standard account (*25519).") - DarkAccount => - (17, "dark", "Dark mainnet, standard account (*25519).") - DarwiniaAccount => - (18, "darwinia", "Darwinia Chain mainnet, standard account (*25519).") - GeekAccount => - (19, "geek", "GeekCash mainnet, standard account (*25519).") - StafiAccount => - (20, "stafi", "Stafi mainnet, standard account (*25519).") - DockTestAccount => - (21, "dock-testnet", "Dock testnet, standard account (*25519).") - DockMainAccount => - (22, "dock-mainnet", "Dock mainnet, standard account (*25519).") - ShiftNrg => - (23, "shift", "ShiftNrg mainnet, standard account (*25519).") - ZeroAccount => - (24, "zero", "ZERO mainnet, standard account (*25519).") - AlphavilleAccount => - (25, "alphaville", "ZERO testnet, standard account (*25519).") - JupiterAccount => - (26, "jupiter", "Jupiter testnet, standard account (*25519).") - SubsocialAccount => - (28, "subsocial", "Subsocial network, standard account (*25519).") - DhiwayAccount => - (29, "cord", "Dhiway CORD network, standard account (*25519).") - PhalaAccount => - (30, "phala", "Phala Network, standard account (*25519).") - LitentryAccount => - (31, "litentry", "Litentry Network, standard account (*25519).") - RobonomicsAccount => - (32, "robonomics", "Any Robonomics network standard account (*25519).") - DataHighwayAccount => - (33, "datahighway", "DataHighway mainnet, standard account (*25519).") - AresAccount => - (34, "ares", "Ares Protocol, standard account (*25519).") - ValiuAccount => - (35, "vln", "Valiu Liquidity Network mainnet, standard account (*25519).") - CentrifugeAccount => - (36, "centrifuge", "Centrifuge Chain mainnet, standard account (*25519).") - NodleAccount => - (37, "nodle", "Nodle Chain mainnet, standard account (*25519).") - KiltAccount => - (38, "kilt", "KILT Chain mainnet, standard account (*25519).") - PolimecAccount => - (41, "poli", "Polimec Chain mainnet, standard account (*25519).") - SubstrateAccount => - (42, "substrate", "Any Substrate network, standard account (*25519).") - BareSecp256k1 => - (43, "secp256k1", "Bare ECDSA SECP256k1 key.") - ChainXAccount => - (44, "chainx", "ChainX mainnet, standard account (*25519).") - UniartsAccount => - (45, "uniarts", "UniArts Chain mainnet, standard account (*25519).") - Reserved46 => - (46, "reserved46", "Reserved for future use (46).") - Reserved47 => - (47, "reserved47", "Reserved for future use (47).") - NeatcoinAccount => - (48, "neatcoin", "Neatcoin mainnet, standard account (*25519).") - PicassoAccount => - (49, "picasso", "Composable Canary Network, standard account (*25519).") - ComposableAccount => - (50, "composable", "Composable mainnet, standard account (*25519).") - HydraDXAccount => - (63, "hydradx", "HydraDX standard account (*25519).") - AventusAccount => - (65, "aventus", "Aventus Chain mainnet, standard account (*25519).") - CrustAccount => - (66, "crust", "Crust Network, standard account (*25519).") - EquilibriumAccount => - (67, "equilibrium", "Equilibrium Network, standard account (*25519).") - SoraAccount => - (69, "sora", "SORA Network, standard account (*25519).") - ZeitgeistAccount => - (73, "zeitgeist", "Zeitgeist network, standard account (*25519).") - MantaAccount => - (77, "manta", "Manta Network, standard account (*25519).") - CalamariAccount => - (78, "calamari", "Manta Canary Network, standard account (*25519).") - PolkaSmith => - (98, "polkasmith", "PolkaSmith Canary Network, standard account (*25519).") - PolkaFoundry => - (99, "polkafoundry", "PolkaFoundry Network, standard account (*25519).") - OriginTrailAccount => - (101, "origintrail-parachain", "OriginTrail Parachain, ethereumm account (ECDSA).") - HeikoAccount => - (110, "heiko", "Heiko, session key (*25519).") - CloverAccount => - (128, "clover", "Clover Finance, standard account (*25519).") - ParallelAccount => - (172, "parallel", "Parallel, session key (*25519).") - SocialAccount => - (252, "social-network", "Social Network, standard account (*25519).") - Moonbeam => - (1284, "moonbeam", "Moonbeam, session key (*25519).") - Moonriver => - (1285, "moonriver", "Moonriver, session key (*25519).") - BasiliskAccount => - (10041, "basilisk", "Basilisk standard account (*25519).") - - // Note: 16384 and above are reserved. -); +/// Returns either the input address format or the default. +#[cfg(feature = "std")] +pub fn unwrap_or_default_ss58_version(network: Option) -> Ss58AddressFormat { + network.unwrap_or_else(default_ss58_version) +} -/// Set the default "version" (actually, this is a bit of a misnomer and the version byte is -/// typically used not just to encode format/version but also network identity) that is used for -/// encoding and decoding SS58 addresses. If an unknown version is provided then it fails. +/// Set the default SS58 "version". +/// +/// This SS58 version/format will be used when encoding/decoding SS58 addresses. /// -/// See `ss58_address_format!` for all current known "versions". +/// If you want to support a custom SS58 prefix (that isn't yet registered in the `ss58-registry`), +/// you are required to call this function with your desired prefix [`Ss58AddressFormat::custom`]. +/// This will enable the node to decode ss58 addresses with this prefix. +/// +/// This SS58 version/format is also only used by the node and not by the runtime. #[cfg(feature = "std")] -pub fn set_default_ss58_version(version: Ss58AddressFormat) { - *DEFAULT_VERSION.lock() = version +pub fn set_default_ss58_version(new_default: Ss58AddressFormat) { + DEFAULT_VERSION.store(new_default.into(), std::sync::atomic::Ordering::Relaxed); } #[cfg(feature = "std")] @@ -642,19 +416,13 @@ lazy_static::lazy_static! { } #[cfg(feature = "std")] -impl + AsRef<[u8]> + Default + Derive> Ss58Codec for T { +impl + AsRef<[u8]> + Public + Derive> Ss58Codec for T { fn from_string(s: &str) -> Result { let cap = SS58_REGEX.captures(s).ok_or(PublicError::InvalidFormat)?; let s = cap.name("ss58").map(|r| r.as_str()).unwrap_or(DEV_ADDRESS); let addr = if let Some(stripped) = s.strip_prefix("0x") { - let d = hex::decode(stripped).map_err(|_| PublicError::InvalidFormat)?; - let mut r = Self::default(); - if d.len() == r.as_ref().len() { - r.as_mut().copy_from_slice(&d); - r - } else { - return Err(PublicError::BadLength) - } + let d = array_bytes::hex2bytes(stripped).map_err(|_| PublicError::InvalidFormat)?; + Self::from_slice(&d).map_err(|()| PublicError::BadLength)? } else { Self::from_ss58check(s)? }; @@ -682,25 +450,15 @@ impl + AsRef<[u8]> + Default + Derive> Ss58Codec for T { } } -/// Trait suitable for typical cryptographic PKI key public type. -pub trait Public: - AsRef<[u8]> - + AsMut<[u8]> - + Default - + Derive - + CryptoType - + PartialEq - + Eq - + Clone - + Send - + Sync - + for<'a> TryFrom<&'a [u8]> -{ - /// A new instance from the given slice. - /// - /// NOTE: No checking goes on to ensure this is a real public key. Only use it if - /// you are certain that the array actually is a pubkey. GIGO! - fn from_slice(data: &[u8]) -> Self; +/// Trait used for types that are really just a fixed-length array. +pub trait ByteArray: AsRef<[u8]> + AsMut<[u8]> + for<'a> TryFrom<&'a [u8], Error = ()> { + /// The "length" of the values of this type, which is always the same. + const LEN: usize; + + /// A new instance from the given slice that should be `Self::LEN` bytes long. + fn from_slice(data: &[u8]) -> Result { + Self::try_from(data) + } /// Return a `Vec` filled with raw data. fn to_raw_vec(&self) -> Vec { @@ -711,14 +469,16 @@ pub trait Public: fn as_slice(&self) -> &[u8] { self.as_ref() } +} + +/// Trait suitable for typical cryptographic PKI key public type. +pub trait Public: ByteArray + Derive + CryptoType + PartialEq + Eq + Clone + Send + Sync { /// Return `CryptoTypePublicPair` from public key. fn to_public_crypto_pair(&self) -> CryptoTypePublicPair; } /// An opaque 32-byte cryptographic identifier. -#[derive( - Clone, Eq, PartialEq, Ord, PartialOrd, Default, Encode, Decode, MaxEncodedLen, TypeInfo, -)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Hash))] pub struct AccountId32([u8; 32]); @@ -738,6 +498,10 @@ impl UncheckedFrom for AccountId32 { } } +impl ByteArray for AccountId32 { + const LEN: usize = 32; +} + #[cfg(feature = "std")] impl Ss58Codec for AccountId32 {} @@ -771,13 +535,13 @@ impl From<[u8; 32]> for AccountId32 { } } -impl<'a> sp_std::convert::TryFrom<&'a [u8]> for AccountId32 { +impl<'a> TryFrom<&'a [u8]> for AccountId32 { type Error = (); fn try_from(x: &'a [u8]) -> Result { if x.len() == 32 { - let mut r = AccountId32::default(); - r.0.copy_from_slice(x); - Ok(r) + let mut data = [0; 32]; + data.copy_from_slice(x); + Ok(AccountId32(data)) } else { Err(()) } @@ -850,10 +614,7 @@ impl sp_std::str::FromStr for AccountId32 { fn from_str(s: &str) -> Result { let hex_or_ss58_without_prefix = s.trim_start_matches("0x"); if hex_or_ss58_without_prefix.len() == 64 { - let mut bytes = [0u8; 32]; - hex::decode_to_slice(hex_or_ss58_without_prefix, &mut bytes) - .map_err(|_| "invalid hex address.") - .map(|_| Self::from(bytes)) + array_bytes::hex_n_into(hex_or_ss58_without_prefix).map_err(|_| "invalid hex address.") } else { Self::from_ss58check(s).map_err(|_| "invalid ss58 address.") } @@ -900,9 +661,10 @@ mod dummy { impl Derive for Dummy {} - impl Public for Dummy { - fn from_slice(_: &[u8]) -> Self { - Self + impl ByteArray for Dummy { + const LEN: usize = 0; + fn from_slice(_: &[u8]) -> Result { + Ok(Self) } #[cfg(feature = "std")] fn to_raw_vec(&self) -> Vec { @@ -911,8 +673,10 @@ mod dummy { fn as_slice(&self) -> &[u8] { b"" } + } + impl Public for Dummy { fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { - CryptoTypePublicPair(CryptoTypeId(*b"dumm"), Public::to_raw_vec(self)) + CryptoTypePublicPair(CryptoTypeId(*b"dumm"), ::to_raw_vec(self)) } } @@ -960,6 +724,104 @@ mod dummy { } } +/// A secret uri (`SURI`) that can be used to generate a key pair. +/// +/// The `SURI` can be parsed from a string. The string is interpreted in the following way: +/// +/// - If `string` is a possibly `0x` prefixed 64-digit hex string, then it will be interpreted +/// directly as a `MiniSecretKey` (aka "seed" in `subkey`). +/// - If `string` is a valid BIP-39 key phrase of 12, 15, 18, 21 or 24 words, then the key will +/// be derived from it. In this case: +/// - the phrase may be followed by one or more items delimited by `/` characters. +/// - the path may be followed by `///`, in which case everything after the `///` is treated +/// as a password. +/// - If `string` begins with a `/` character it is prefixed with the Substrate public `DEV_PHRASE` +/// and interpreted as above. +/// +/// In this case they are interpreted as HDKD junctions; purely numeric items are interpreted as +/// integers, non-numeric items as strings. Junctions prefixed with `/` are interpreted as soft +/// junctions, and with `//` as hard junctions. +/// +/// There is no correspondence mapping between `SURI` strings and the keys they represent. +/// Two different non-identical strings can actually lead to the same secret being derived. +/// Notably, integer junction indices may be legally prefixed with arbitrary number of zeros. +/// Similarly an empty password (ending the `SURI` with `///`) is perfectly valid and will +/// generally be equivalent to no password at all. +/// +/// # Example +/// +/// Parse [`DEV_PHRASE`] secret uri with junction: +/// +/// ``` +/// # use sp_core::crypto::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret}; +/// # use std::str::FromStr; +/// let suri = SecretUri::from_str("//Alice").expect("Parse SURI"); +/// +/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions); +/// assert_eq!(DEV_PHRASE, suri.phrase.expose_secret()); +/// assert!(suri.password.is_none()); +/// ``` +/// +/// Parse [`DEV_PHRASE`] secret ui with junction and password: +/// +/// ``` +/// # use sp_core::crypto::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret}; +/// # use std::str::FromStr; +/// let suri = SecretUri::from_str("//Alice///SECRET_PASSWORD").expect("Parse SURI"); +/// +/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions); +/// assert_eq!(DEV_PHRASE, suri.phrase.expose_secret()); +/// assert_eq!("SECRET_PASSWORD", suri.password.unwrap().expose_secret()); +/// ``` +/// +/// Parse [`DEV_PHRASE`] secret ui with hex phrase and junction: +/// +/// ``` +/// # use sp_core::crypto::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret}; +/// # use std::str::FromStr; +/// let suri = SecretUri::from_str("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a//Alice").expect("Parse SURI"); +/// +/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions); +/// assert_eq!("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a", suri.phrase.expose_secret()); +/// assert!(suri.password.is_none()); +/// ``` +#[cfg(feature = "std")] +pub struct SecretUri { + /// The phrase to derive the private key. + /// + /// This can either be a 64-bit hex string or a BIP-39 key phrase. + pub phrase: SecretString, + /// Optional password as given as part of the uri. + pub password: Option, + /// The junctions as part of the uri. + pub junctions: Vec, +} + +#[cfg(feature = "std")] +impl sp_std::str::FromStr for SecretUri { + type Err = SecretStringError; + + fn from_str(s: &str) -> Result { + let cap = SECRET_PHRASE_REGEX.captures(s).ok_or(SecretStringError::InvalidFormat)?; + + let junctions = JUNCTION_REGEX + .captures_iter(&cap["path"]) + .map(|f| DeriveJunction::from(&f[1])) + .collect::>(); + + let phrase = cap.name("phrase").map(|r| r.as_str()).unwrap_or(DEV_PHRASE); + let password = cap.name("password"); + + Ok(Self { + phrase: SecretString::from_str(phrase).expect("Returns infallible error; qed"), + password: password.map(|v| { + SecretString::from_str(v.as_str()).expect("Returns infallible error; qed") + }), + junctions, + }) + } +} + /// Trait suitable for typical cryptographic PKI key pair type. /// /// For now it just specifies how to create a key from a phrase and derivation path. @@ -1072,15 +934,13 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { s: &str, password_override: Option<&str>, ) -> Result<(Self, Option), SecretStringError> { - let cap = SECRET_PHRASE_REGEX.captures(s).ok_or(SecretStringError::InvalidFormat)?; - - let path = JUNCTION_REGEX.captures_iter(&cap["path"]).map(|f| DeriveJunction::from(&f[1])); + use sp_std::str::FromStr; + let SecretUri { junctions, phrase, password } = SecretUri::from_str(s)?; + let password = + password_override.or_else(|| password.as_ref().map(|p| p.expose_secret().as_str())); - let phrase = cap.name("phrase").map(|r| r.as_str()).unwrap_or(DEV_PHRASE); - let password = password_override.or_else(|| cap.name("password").map(|m| m.as_str())); - - let (root, seed) = if let Some(stripped) = phrase.strip_prefix("0x") { - hex::decode(stripped) + let (root, seed) = if let Some(stripped) = phrase.expose_secret().strip_prefix("0x") { + array_bytes::hex2bytes(stripped) .ok() .and_then(|seed_vec| { let mut seed = Self::Seed::default(); @@ -1093,9 +953,11 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { }) .ok_or(SecretStringError::InvalidSeed)? } else { - Self::from_phrase(phrase, password).map_err(|_| SecretStringError::InvalidPhrase)? + Self::from_phrase(phrase.expose_secret().as_str(), password) + .map_err(|_| SecretStringError::InvalidPhrase)? }; - root.derive(path, Some(seed)).map_err(|_| SecretStringError::InvalidPath) + root.derive(junctions.into_iter(), Some(seed)) + .map_err(|_| SecretStringError::InvalidPath) } /// Interprets the string `s` in order to generate a key pair. @@ -1122,6 +984,11 @@ pub trait IsWrappedBy: From + Into { pub trait Wraps: Sized { /// The inner type it is wrapping. type Inner: IsWrappedBy; + + /// Get a reference to the inner type that is wrapped. + fn as_inner_ref(&self) -> &Self::Inner { + Self::Inner::from_ref(self) + } } impl IsWrappedBy for T @@ -1254,8 +1121,6 @@ pub mod key_types { pub const AUTHORITY_DISCOVERY: KeyTypeId = KeyTypeId(*b"audi"); /// Key type for staking, built-in. Identified as `stak`. pub const STAKING: KeyTypeId = KeyTypeId(*b"stak"); - /// Key type for equivocation reporting, built-in. Identified as `fish`. - pub const REPORTING: KeyTypeId = KeyTypeId(*b"fish"); /// A key type ID useful for tests. pub const DUMMY: KeyTypeId = KeyTypeId(*b"dumy"); } @@ -1264,7 +1129,6 @@ pub mod key_types { mod tests { use super::*; use crate::DeriveJunction; - use hex_literal::hex; #[derive(Clone, Eq, PartialEq, Debug)] enum TestPair { @@ -1298,17 +1162,22 @@ mod tests { impl<'a> TryFrom<&'a [u8]> for TestPublic { type Error = (); - fn try_from(_: &'a [u8]) -> Result { - Ok(Self) + fn try_from(data: &'a [u8]) -> Result { + Self::from_slice(data) } } impl CryptoType for TestPublic { type Pair = TestPair; } impl Derive for TestPublic {} - impl Public for TestPublic { - fn from_slice(_bytes: &[u8]) -> Self { - Self + impl ByteArray for TestPublic { + const LEN: usize = 0; + fn from_slice(bytes: &[u8]) -> Result { + if bytes.is_empty() { + Ok(Self) + } else { + Err(()) + } } fn as_slice(&self) -> &[u8] { &[] @@ -1316,6 +1185,8 @@ mod tests { fn to_raw_vec(&self) -> Vec { vec![] } + } + impl Public for TestPublic { fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { CryptoTypePublicPair(CryptoTypeId(*b"dumm"), self.to_raw_vec()) } @@ -1399,7 +1270,7 @@ mod tests { fn interpret_std_seed_should_work() { assert_eq!( TestPair::from_string("0x0123456789abcdef", None), - Ok(TestPair::Seed(hex!["0123456789abcdef"][..].to_owned())) + Ok(TestPair::Seed(array_bytes::hex2bytes_unchecked("0123456789abcdef"))) ); } @@ -1441,6 +1312,14 @@ mod tests { path: vec![DeriveJunction::soft("DOT")] }) ); + assert_eq!( + TestPair::from_string("hello world/0123456789012345678901234567890123456789", None), + Ok(TestPair::Standard { + phrase: "hello world".to_owned(), + password: None, + path: vec![DeriveJunction::soft("0123456789012345678901234567890123456789")] + }) + ); assert_eq!( TestPair::from_string("hello world//1", None), Ok(TestPair::Standard { @@ -1457,6 +1336,14 @@ mod tests { path: vec![DeriveJunction::hard("DOT")] }) ); + assert_eq!( + TestPair::from_string("hello world//0123456789012345678901234567890123456789", None), + Ok(TestPair::Standard { + phrase: "hello world".to_owned(), + password: None, + path: vec![DeriveJunction::hard("0123456789012345678901234567890123456789")] + }) + ); assert_eq!( TestPair::from_string("hello world//1/DOT", None), Ok(TestPair::Standard { diff --git a/primitives/core/src/defer.rs b/primitives/core/src/defer.rs new file mode 100644 index 0000000000000..d14b26d59e4dd --- /dev/null +++ b/primitives/core/src/defer.rs @@ -0,0 +1,140 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Contains the [`crate::defer!`] macro for *deferring* the execution +//! of code until the current scope is dropped. +//! This helps with *always* executing cleanup code. + +/// Executes the wrapped closure on drop. +/// +/// Should be used together with the [`crate::defer!`] macro. +#[must_use] +pub struct DeferGuard(pub Option); + +impl Drop for DeferGuard { + fn drop(&mut self) { + self.0.take().map(|f| f()); + } +} + +/// Executes the given code when the current scope is dropped. +/// +/// Multiple calls to [`crate::defer!`] will execute the passed codes in reverse order. +/// This also applies to panic stack unwinding. +/// +/// # Example +/// +/// ```rust +/// use sp_core::defer; +/// +/// let message = std::cell::RefCell::new("".to_string()); +/// { +/// defer!( +/// message.borrow_mut().push_str("world!"); +/// ); +/// defer!( +/// message.borrow_mut().push_str("Hello "); +/// ); +/// } +/// assert_eq!(*message.borrow(), "Hello world!"); +/// ``` +#[macro_export] +macro_rules! defer( + ( $( $code:tt )* ) => { + let _guard = $crate::defer::DeferGuard(Some(|| { $( $code )* })); + }; +); + +#[cfg(test)] +mod test { + #[test] + fn defer_guard_works() { + let mut called = false; + { + defer!( + called = true; + ); + } + assert!(called, "DeferGuard should have executed the closure"); + } + + #[test] + /// `defer` executes the code in reverse order of being called. + fn defer_guard_order_works() { + let called = std::cell::RefCell::new(1); + + defer!( + assert_eq!(*called.borrow(), 3); + ); + defer!( + assert_eq!(*called.borrow(), 2); + *called.borrow_mut() = 3; + ); + defer!({ + assert_eq!(*called.borrow(), 1); + *called.borrow_mut() = 2; + }); + } + + #[test] + #[allow(unused_braces)] + #[allow(clippy::unnecessary_operation)] + fn defer_guard_syntax_works() { + let called = std::cell::RefCell::new(0); + { + defer!(*called.borrow_mut() += 1); + defer!(*called.borrow_mut() += 1;); // With ; + defer!({ *called.borrow_mut() += 1 }); + defer!({ *called.borrow_mut() += 1 };); // With ; + } + assert_eq!(*called.borrow(), 4); + } + + #[test] + /// `defer` executes the code even in case of a panic. + fn defer_guard_panic_unwind_works() { + use std::panic::{catch_unwind, AssertUnwindSafe}; + let mut called = false; + + let should_panic = catch_unwind(AssertUnwindSafe(|| { + defer!(called = true); + panic!(); + })); + + assert!(should_panic.is_err(), "DeferGuard should have panicked"); + assert!(called, "DeferGuard should have executed the closure"); + } + + #[test] + /// `defer` executes the code even in case another `defer` panics. + fn defer_guard_defer_panics_unwind_works() { + use std::panic::{catch_unwind, AssertUnwindSafe}; + let counter = std::cell::RefCell::new(0); + + let should_panic = catch_unwind(AssertUnwindSafe(|| { + defer!(*counter.borrow_mut() += 1); + defer!( + *counter.borrow_mut() += 1; + panic!(); + ); + defer!(*counter.borrow_mut() += 1); + })); + + assert!(should_panic.is_err(), "DeferGuard should have panicked"); + assert_eq!(*counter.borrow(), 3, "DeferGuard should have executed the closure"); + } +} diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 11e9b9d71d80e..ca6b800625bc2 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,19 +15,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -// tag::description[] -//! Simple ECDSA API. -// end::description[] +//! Simple ECDSA secp256k1 API. use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime_interface::pass_by::PassByInner; -use sp_std::cmp::Ordering; #[cfg(feature = "std")] use crate::crypto::Ss58Codec; use crate::crypto::{ - CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, UncheckedFrom, + ByteArray, CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, + UncheckedFrom, }; #[cfg(feature = "full_crypto")] use crate::{ @@ -36,10 +34,15 @@ use crate::{ }; #[cfg(feature = "std")] use bip39::{Language, Mnemonic, MnemonicType}; +#[cfg(all(feature = "full_crypto", not(feature = "std")))] +use secp256k1::Secp256k1; +#[cfg(feature = "std")] +use secp256k1::SECP256K1; #[cfg(feature = "full_crypto")] -use core::convert::{TryFrom, TryInto}; -#[cfg(feature = "full_crypto")] -use libsecp256k1::{PublicKey, SecretKey}; +use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Message, PublicKey, SecretKey, +}; #[cfg(feature = "std")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "full_crypto")] @@ -55,43 +58,22 @@ pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds"); type Seed = [u8; 32]; /// The ECDSA compressed public key. -#[derive(Clone, Encode, Decode, PassByInner, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "full_crypto", derive(Hash))] +#[derive( + Clone, + Copy, + Encode, + Decode, + PassByInner, + MaxEncodedLen, + TypeInfo, + Eq, + PartialEq, + PartialOrd, + Ord, +)] pub struct Public(pub [u8; 33]); -impl PartialOrd for Public { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Public { - fn cmp(&self, other: &Self) -> Ordering { - self.as_ref().cmp(&other.as_ref()) - } -} - -impl PartialEq for Public { - fn eq(&self, other: &Self) -> bool { - self.as_ref() == other.as_ref() - } -} - -impl Eq for Public {} - -/// An error type for SS58 decoding. -#[cfg(feature = "std")] -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub enum PublicError { - /// Bad alphabet. - BadBase58, - /// Bad length. - BadLength, - /// Unknown version. - UnknownVersion, - /// Invalid checksum. - InvalidChecksum, -} - impl Public { /// A new instance from the given 33-byte `data`. /// @@ -106,24 +88,24 @@ impl Public { /// This will convert the full public key into the compressed format. #[cfg(feature = "std")] pub fn from_full(full: &[u8]) -> Result { - libsecp256k1::PublicKey::parse_slice(full, None) - .map(|k| k.serialize_compressed()) - .map(Self) - .map_err(|_| ()) + let pubkey = if full.len() == 64 { + // Tag it as uncompressed public key. + let mut tagged_full = [0u8; 65]; + tagged_full[0] = 0x04; + tagged_full[1..].copy_from_slice(full); + secp256k1::PublicKey::from_slice(&tagged_full) + } else { + secp256k1::PublicKey::from_slice(full) + }; + pubkey.map(|k| Self(k.serialize())).map_err(|_| ()) } } -impl TraitPublic for Public { - /// A new instance from the given slice that should be 33 bytes long. - /// - /// NOTE: No checking goes on to ensure this is a real public key. Only use it if - /// you are certain that the array actually is a pubkey. GIGO! - fn from_slice(data: &[u8]) -> Self { - let mut r = [0u8; 33]; - r.copy_from_slice(data); - Self(r) - } +impl ByteArray for Public { + const LEN: usize = 33; +} +impl TraitPublic for Public { fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { CryptoTypePublicPair(CRYPTO_ID, self.to_raw_vec()) } @@ -143,12 +125,6 @@ impl From<&Public> for CryptoTypePublicPair { impl Derive for Public {} -impl Default for Public { - fn default() -> Self { - Public([0u8; 33]) - } -} - impl AsRef<[u8]> for Public { fn as_ref(&self) -> &[u8] { &self.0[..] @@ -161,15 +137,16 @@ impl AsMut<[u8]> for Public { } } -impl sp_std::convert::TryFrom<&[u8]> for Public { +impl TryFrom<&[u8]> for Public { type Error = (); fn try_from(data: &[u8]) -> Result { - if data.len() == 33 { - Ok(Self::from_slice(data)) - } else { - Err(()) + if data.len() != Self::LEN { + return Err(()) } + let mut r = [0u8; Self::LEN]; + r.copy_from_slice(data); + Ok(Self::unchecked_from(r)) } } @@ -227,18 +204,12 @@ impl<'de> Deserialize<'de> for Public { } } -#[cfg(feature = "full_crypto")] -impl sp_std::hash::Hash for Public { - fn hash(&self, state: &mut H) { - self.as_ref().hash(state); - } -} - /// A signature (a 512-bit value, plus 8 bits for recovery ID). -#[derive(Encode, Decode, PassByInner, TypeInfo)] +#[cfg_attr(feature = "full_crypto", derive(Hash))] +#[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] pub struct Signature(pub [u8; 65]); -impl sp_std::convert::TryFrom<&[u8]> for Signature { +impl TryFrom<&[u8]> for Signature { type Error = (); fn try_from(data: &[u8]) -> Result { @@ -258,7 +229,7 @@ impl Serialize for Signature { where S: Serializer, { - serializer.serialize_str(&hex::encode(self)) + serializer.serialize_str(&array_bytes::bytes2hex("", self.as_ref())) } } @@ -268,7 +239,7 @@ impl<'de> Deserialize<'de> for Signature { where D: Deserializer<'de>, { - let signature_hex = hex::decode(&String::deserialize(deserializer)?) + let signature_hex = array_bytes::hex2bytes(&String::deserialize(deserializer)?) .map_err(|e| de::Error::custom(format!("{:?}", e)))?; Signature::try_from(signature_hex.as_ref()) .map_err(|e| de::Error::custom(format!("{:?}", e))) @@ -289,14 +260,6 @@ impl Default for Signature { } } -impl PartialEq for Signature { - fn eq(&self, b: &Self) -> bool { - self.0[..] == b.0[..] - } -} - -impl Eq for Signature {} - impl From for [u8; 65] { fn from(v: Signature) -> [u8; 65] { v.0 @@ -333,10 +296,9 @@ impl sp_std::fmt::Debug for Signature { } } -#[cfg(feature = "full_crypto")] -impl sp_std::hash::Hash for Signature { - fn hash(&self, state: &mut H) { - sp_std::hash::Hash::hash(&self.0[..], state); +impl UncheckedFrom<[u8; 65]> for Signature { + fn unchecked_from(data: [u8; 65]) -> Signature { + Signature(data) } } @@ -353,63 +315,56 @@ impl Signature { /// /// NOTE: No checking goes on to ensure this is a real signature. Only use it if /// you are certain that the array actually is a signature. GIGO! - pub fn from_slice(data: &[u8]) -> Self { + pub fn from_slice(data: &[u8]) -> Option { + if data.len() != 65 { + return None + } let mut r = [0u8; 65]; r.copy_from_slice(data); - Signature(r) + Some(Signature(r)) } /// Recover the public key from this signature and a message. #[cfg(feature = "full_crypto")] pub fn recover>(&self, message: M) -> Option { - let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); - let sig: (_, _) = self.try_into().ok()?; - libsecp256k1::recover(&message, &sig.0, &sig.1) - .ok() - .map(|recovered| Public(recovered.serialize_compressed())) + self.recover_prehashed(&blake2_256(message.as_ref())) } /// Recover the public key from this signature and a pre-hashed message. #[cfg(feature = "full_crypto")] pub fn recover_prehashed(&self, message: &[u8; 32]) -> Option { - let message = libsecp256k1::Message::parse(message); + let rid = RecoveryId::from_i32(self.0[64] as i32).ok()?; + let sig = RecoverableSignature::from_compact(&self.0[..64], rid).ok()?; + let message = Message::from_slice(message).expect("Message is 32 bytes; qed"); - let sig: (_, _) = self.try_into().ok()?; + #[cfg(feature = "std")] + let context = SECP256K1; + #[cfg(not(feature = "std"))] + let context = Secp256k1::verification_only(); - libsecp256k1::recover(&message, &sig.0, &sig.1) + context + .recover_ecdsa(&message, &sig) .ok() - .map(|key| Public(key.serialize_compressed())) + .map(|pubkey| Public(pubkey.serialize())) } } #[cfg(feature = "full_crypto")] -impl From<(libsecp256k1::Signature, libsecp256k1::RecoveryId)> for Signature { - fn from(x: (libsecp256k1::Signature, libsecp256k1::RecoveryId)) -> Signature { +impl From for Signature { + fn from(recsig: RecoverableSignature) -> Signature { let mut r = Self::default(); - r.0[0..64].copy_from_slice(&x.0.serialize()[..]); - r.0[64] = x.1.serialize(); + let (recid, sig) = recsig.serialize_compact(); + r.0[..64].copy_from_slice(&sig); + // This is safe due to the limited range of possible valid ids. + r.0[64] = recid.to_i32() as u8; r } } -#[cfg(feature = "full_crypto")] -impl<'a> TryFrom<&'a Signature> for (libsecp256k1::Signature, libsecp256k1::RecoveryId) { - type Error = (); - fn try_from( - x: &'a Signature, - ) -> Result<(libsecp256k1::Signature, libsecp256k1::RecoveryId), Self::Error> { - parse_signature_standard(&x.0).map_err(|_| ()) - } -} - /// Derive a single hard junction. #[cfg(feature = "full_crypto")] fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { - ("Secp256k1HDKD", secret_seed, cc).using_encoded(|data| { - let mut res = [0u8; 32]; - res.copy_from_slice(blake2_rfc::blake2b::blake2b(32, &[], data).as_bytes()); - res - }) + ("Secp256k1HDKD", secret_seed, cc).using_encoded(sp_core_hashing::blake2_256) } /// An error when deriving a key. @@ -423,7 +378,7 @@ pub enum DeriveError { #[cfg(feature = "full_crypto")] #[derive(Clone)] pub struct Pair { - public: PublicKey, + public: Public, secret: SecretKey, } @@ -477,8 +432,15 @@ impl TraitPair for Pair { /// You should never need to use this; generate(), generate_with_phrase fn from_seed_slice(seed_slice: &[u8]) -> Result { let secret = - SecretKey::parse_slice(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?; - let public = PublicKey::from_secret_key(&secret); + SecretKey::from_slice(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?; + + #[cfg(feature = "std")] + let context = SECP256K1; + #[cfg(not(feature = "std"))] + let context = Secp256k1::signing_only(); + + let public = PublicKey::from_secret_key(&context, &secret); + let public = Public(public.serialize()); Ok(Pair { public, secret }) } @@ -488,7 +450,7 @@ impl TraitPair for Pair { path: Iter, _seed: Option, ) -> Result<(Pair, Option), DeriveError> { - let mut acc = self.secret.serialize(); + let mut acc = self.seed(); for j in path { match j { DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), @@ -500,25 +462,19 @@ impl TraitPair for Pair { /// Get the public key. fn public(&self) -> Public { - Public(self.public.serialize_compressed()) + self.public } /// Sign a message. fn sign(&self, message: &[u8]) -> Signature { - let message = libsecp256k1::Message::parse(&blake2_256(message)); - libsecp256k1::sign(&message, &self.secret).into() + self.sign_prehashed(&blake2_256(message)) } /// Verify a signature on a message. Returns true if the signature is good. fn verify>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool { - let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); - let sig: (_, _) = match sig.try_into() { - Ok(x) => x, - _ => return false, - }; - match libsecp256k1::recover(&message, &sig.0, &sig.1) { - Ok(actual) => pubkey.0[..] == actual.serialize_compressed()[..], - _ => false, + match sig.recover(message) { + Some(actual) => actual == *pubkey, + None => false, } } @@ -527,17 +483,9 @@ impl TraitPair for Pair { /// This doesn't use the type system to ensure that `sig` and `pubkey` are the correct /// size. Use it only if you're coming from byte buffers and need the speed. fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { - let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); - if sig.len() != 65 { - return false - } - let (sig, ri) = match parse_signature_standard(&sig) { - Ok(sigri) => sigri, - _ => return false, - }; - match libsecp256k1::recover(&message, &sig, &ri) { - Ok(actual) => pubkey.as_ref() == &actual.serialize()[1..], - _ => false, + match Signature::from_slice(sig).and_then(|sig| sig.recover(message)) { + Some(actual) => actual.as_ref() == pubkey.as_ref(), + None => false, } } @@ -551,7 +499,7 @@ impl TraitPair for Pair { impl Pair { /// Get the seed for this key. pub fn seed(&self) -> Seed { - self.secret.serialize() + self.secret.secret_bytes() } /// Exactly as `from_string` except that if no matches are found then, the the first 32 @@ -568,57 +516,63 @@ impl Pair { /// Sign a pre-hashed message pub fn sign_prehashed(&self, message: &[u8; 32]) -> Signature { - let message = libsecp256k1::Message::parse(message); - libsecp256k1::sign(&message, &self.secret).into() + let message = Message::from_slice(message).expect("Message is 32 bytes; qed"); + + #[cfg(feature = "std")] + let context = SECP256K1; + #[cfg(not(feature = "std"))] + let context = Secp256k1::signing_only(); + + context.sign_ecdsa_recoverable(&message, &self.secret).into() } /// Verify a signature on a pre-hashed message. Return `true` if the signature is valid /// and thus matches the given `public` key. pub fn verify_prehashed(sig: &Signature, message: &[u8; 32], public: &Public) -> bool { - let message = libsecp256k1::Message::parse(message); - - let sig: (_, _) = match sig.try_into() { - Ok(x) => x, - _ => return false, - }; - - match libsecp256k1::recover(&message, &sig.0, &sig.1) { - Ok(actual) => public.0[..] == actual.serialize_compressed()[..], - _ => false, + match sig.recover_prehashed(message) { + Some(actual) => actual == *public, + None => false, } } /// Verify a signature on a message. Returns true if the signature is good. - /// Parses Signature using parse_overflowing_slice + /// Parses Signature using parse_overflowing_slice. + #[deprecated(note = "please use `verify` instead")] pub fn verify_deprecated>(sig: &Signature, message: M, pubkey: &Public) -> bool { let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); - let (sig, ri) = match parse_signature_overflowing(&sig.0) { - Ok(sigri) => sigri, + + let parse_signature_overflowing = |x: [u8; 65]| { + let sig = libsecp256k1::Signature::parse_overflowing_slice(&x[..64]).ok()?; + let rid = libsecp256k1::RecoveryId::parse(x[64]).ok()?; + Some((sig, rid)) + }; + + let (sig, rid) = match parse_signature_overflowing(sig.0) { + Some(sigri) => sigri, _ => return false, }; - match libsecp256k1::recover(&message, &sig, &ri) { - Ok(actual) => pubkey.0[..] == actual.serialize_compressed()[..], + match libsecp256k1::recover(&message, &sig, &rid) { + Ok(actual) => pubkey.0 == actual.serialize_compressed(), _ => false, } } } +// The `secp256k1` backend doesn't implement cleanup for their private keys. +// Currently we should take care of wiping the secret from memory. +// NOTE: this solution is not effective when `Pair` is moved around memory. +// The very same problem affects other cryptographic backends that are just using +// `zeroize`for their secrets. #[cfg(feature = "full_crypto")] -fn parse_signature_standard( - x: &[u8], -) -> Result<(libsecp256k1::Signature, libsecp256k1::RecoveryId), libsecp256k1::Error> { - let sig = libsecp256k1::Signature::parse_standard_slice(&x[..64])?; - let ri = libsecp256k1::RecoveryId::parse(x[64])?; - Ok((sig, ri)) -} - -#[cfg(feature = "full_crypto")] -fn parse_signature_overflowing( - x: &[u8], -) -> Result<(libsecp256k1::Signature, libsecp256k1::RecoveryId), libsecp256k1::Error> { - let sig = libsecp256k1::Signature::parse_overflowing_slice(&x[..64])?; - let ri = libsecp256k1::RecoveryId::parse(x[64])?; - Ok((sig, ri)) +impl Drop for Pair { + fn drop(&mut self) { + let ptr = self.secret.as_mut_ptr(); + for off in 0..self.secret.len() { + unsafe { + core::ptr::write_volatile(ptr.add(off), 0); + } + } + } } impl CryptoType for Public { @@ -639,11 +593,10 @@ impl CryptoType for Pair { #[cfg(test)] mod test { use super::*; - use crate::{ - crypto::{set_default_ss58_version, PublicError, DEV_PHRASE}, - keccak_256, + use crate::crypto::{ + set_default_ss58_version, PublicError, Ss58AddressFormat, Ss58AddressFormatRegistry, + DEV_PHRASE, }; - use hex_literal::hex; use serde_json; #[test] @@ -658,31 +611,35 @@ mod test { #[test] fn seed_and_derive_should_work() { - let seed = hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"); + let seed = array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + ); let pair = Pair::from_seed(&seed); assert_eq!(pair.seed(), seed); let path = vec![DeriveJunction::Hard([0u8; 32])]; let derived = pair.derive(path.into_iter(), None).ok().unwrap(); assert_eq!( derived.0.seed(), - hex!("b8eefc4937200a8382d00050e050ced2d4ab72cc2ef1b061477afb51564fdd61") + array_bytes::hex2array_unchecked::<32>( + "b8eefc4937200a8382d00050e050ced2d4ab72cc2ef1b061477afb51564fdd61" + ) ); } #[test] fn test_vector_should_work() { - let pair = Pair::from_seed(&hex!( - "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + let pair = Pair::from_seed(&array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", )); let public = pair.public(); assert_eq!( public, Public::from_full( - &hex!("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4")[..], + &array_bytes::hex2bytes_unchecked("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4"), ).unwrap(), ); let message = b""; - let signature = hex!("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00"); + let signature = array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00"); let signature = Signature::from_raw(signature); assert!(pair.sign(&message[..]) == signature); assert!(Pair::verify(&signature, &message[..], &public)); @@ -699,11 +656,11 @@ mod test { assert_eq!( public, Public::from_full( - &hex!("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4")[..], + &array_bytes::hex2bytes_unchecked("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4"), ).unwrap(), ); let message = b""; - let signature = hex!("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00"); + let signature = array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00"); let signature = Signature::from_raw(signature); assert!(pair.sign(&message[..]) == signature); assert!(Pair::verify(&signature, &message[..], &public)); @@ -726,10 +683,10 @@ mod test { assert_eq!( public, Public::from_full( - &hex!("5676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba995840f3de562156558efbfdac3f16af0065e5f66795f4dd8262a228ef8c6d813")[..], + &array_bytes::hex2bytes_unchecked("5676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba995840f3de562156558efbfdac3f16af0065e5f66795f4dd8262a228ef8c6d813"), ).unwrap(), ); - let message = hex!("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000"); + let message = array_bytes::hex2bytes_unchecked("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000"); let signature = pair.sign(&message[..]); println!("Correct signature: {:?}", signature); assert!(Pair::verify(&signature, &message[..], &public)); @@ -772,26 +729,24 @@ mod test { #[test] fn ss58check_format_check_works() { - use crate::crypto::Ss58AddressFormat; let pair = Pair::from_seed(b"12345678901234567890123456789012"); let public = pair.public(); - let format = Ss58AddressFormat::Reserved46; + let format = Ss58AddressFormatRegistry::Reserved46Account.into(); let s = public.to_ss58check_with_version(format); assert_eq!(Public::from_ss58check_with_version(&s), Err(PublicError::FormatNotAllowed)); } #[test] fn ss58check_full_roundtrip_works() { - use crate::crypto::Ss58AddressFormat; let pair = Pair::from_seed(b"12345678901234567890123456789012"); let public = pair.public(); - let format = Ss58AddressFormat::PolkadotAccount; + let format = Ss58AddressFormatRegistry::PolkadotAccount.into(); let s = public.to_ss58check_with_version(format); let (k, f) = Public::from_ss58check_with_version(&s).unwrap(); assert_eq!(k, public); assert_eq!(f, format); - let format = Ss58AddressFormat::Custom(64); + let format = Ss58AddressFormat::custom(64); let s = public.to_ss58check_with_version(format); let (k, f) = Public::from_ss58check_with_version(&s).unwrap(); assert_eq!(k, public); @@ -805,18 +760,18 @@ mod test { if std::env::var("RUN_CUSTOM_FORMAT_TEST") == Ok("1".into()) { use crate::crypto::Ss58AddressFormat; // temp save default format version - let default_format = Ss58AddressFormat::default(); + let default_format = crate::crypto::default_ss58_version(); // set current ss58 version is custom "200" `Ss58AddressFormat::Custom(200)` - set_default_ss58_version(Ss58AddressFormat::Custom(200)); + set_default_ss58_version(Ss58AddressFormat::custom(200)); // custom addr encoded by version 200 let addr = "4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV"; - Public::from_ss58check(&addr).unwrap(); + Public::from_ss58check(addr).unwrap(); set_default_ss58_version(default_format); // set current ss58 version to default version let addr = "KWAfgC2aRG5UVD6CpbPQXCx4YZZUhvWqqAJE6qcYc9Rtr6g5C"; - Public::from_ss58check(&addr).unwrap(); + Public::from_ss58check(addr).unwrap(); println!("CUSTOM_FORMAT_SUCCESSFUL"); } else { @@ -862,22 +817,20 @@ mod test { // `msg` shouldn't be mangled let msg = [0u8; 32]; let sig1 = pair.sign_prehashed(&msg); - let sig2: Signature = - libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), &pair.secret).into(); - + let sig2: Signature = { + let message = Message::from_slice(&msg).unwrap(); + SECP256K1.sign_ecdsa_recoverable(&message, &pair.secret).into() + }; assert_eq!(sig1, sig2); // signature is actually different let sig2 = pair.sign(&msg); - assert_ne!(sig1, sig2); // using pre-hashed `msg` works - let msg = keccak_256(b"this should be hashed"); - let sig1 = pair.sign_prehashed(&msg); - let sig2: Signature = - libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), &pair.secret).into(); - + let msg = b"this should be hashed"; + let sig1 = pair.sign_prehashed(&blake2_256(msg)); + let sig2 = pair.sign(msg); assert_eq!(sig1, sig2); } @@ -886,12 +839,12 @@ mod test { let (pair, _, _) = Pair::generate_with_phrase(Some("password")); // `msg` and `sig` match - let msg = keccak_256(b"this should be hashed"); + let msg = blake2_256(b"this should be hashed"); let sig = pair.sign_prehashed(&msg); assert!(Pair::verify_prehashed(&sig, &msg, &pair.public())); // `msg` and `sig` don't match - let msg = keccak_256(b"this is a different message"); + let msg = blake2_256(b"this is a different message"); assert!(!Pair::verify_prehashed(&sig, &msg, &pair.public())); } @@ -900,7 +853,7 @@ mod test { let (pair, _, _) = Pair::generate_with_phrase(Some("password")); // recovered key matches signing key - let msg = keccak_256(b"this should be hashed"); + let msg = blake2_256(b"this should be hashed"); let sig = pair.sign_prehashed(&msg); let key = sig.recover_prehashed(&msg).unwrap(); assert_eq!(pair.public(), key); @@ -909,7 +862,7 @@ mod test { assert!(Pair::verify_prehashed(&sig, &msg, &key)); // recovered key and signing key don't match - let msg = keccak_256(b"this is a different message"); + let msg = blake2_256(b"this is a different message"); let key = sig.recover_prehashed(&msg).unwrap(); assert_ne!(pair.public(), key); } diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index d786ee9d255ff..e85eb87c9fd83 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,10 @@ #[cfg(feature = "full_crypto")] use sp_std::vec::Vec; -use crate::hash::{H256, H512}; +use crate::{ + crypto::ByteArray, + hash::{H256, H512}, +}; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -38,7 +41,7 @@ use bip39::{Language, Mnemonic, MnemonicType}; #[cfg(feature = "full_crypto")] use core::convert::TryFrom; #[cfg(feature = "full_crypto")] -use ed25519_dalek::{Signer as _, Verifier as _}; +use ed25519_zebra::{SigningKey, VerificationKey}; #[cfg(feature = "std")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use sp_runtime_interface::pass_by::PassByInner; @@ -66,7 +69,6 @@ type Seed = [u8; 32]; Copy, Encode, Decode, - Default, PassByInner, MaxEncodedLen, TypeInfo, @@ -75,17 +77,10 @@ pub struct Public(pub [u8; 32]); /// A key pair. #[cfg(feature = "full_crypto")] -pub struct Pair(ed25519_dalek::Keypair); - -#[cfg(feature = "full_crypto")] -impl Clone for Pair { - fn clone(&self) -> Self { - Pair(ed25519_dalek::Keypair { - public: self.0.public, - secret: ed25519_dalek::SecretKey::from_bytes(self.0.secret.as_bytes()) - .expect("key is always the correct size; qed"), - }) - } +#[derive(Copy, Clone)] +pub struct Pair { + public: VerificationKey, + secret: SigningKey, } impl AsRef<[u8; 32]> for Public { @@ -114,17 +109,16 @@ impl Deref for Public { } } -impl sp_std::convert::TryFrom<&[u8]> for Public { +impl TryFrom<&[u8]> for Public { type Error = (); fn try_from(data: &[u8]) -> Result { - if data.len() == 32 { - let mut inner = [0u8; 32]; - inner.copy_from_slice(data); - Ok(Public(inner)) - } else { - Err(()) + if data.len() != Self::LEN { + return Err(()) } + let mut r = [0u8; Self::LEN]; + r.copy_from_slice(data); + Ok(Self::unchecked_from(r)) } } @@ -210,10 +204,11 @@ impl<'de> Deserialize<'de> for Public { } /// A signature (a 512-bit value). -#[derive(Encode, Decode, PassByInner, TypeInfo)] +#[cfg_attr(feature = "full_crypto", derive(Hash))] +#[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] pub struct Signature(pub [u8; 64]); -impl sp_std::convert::TryFrom<&[u8]> for Signature { +impl TryFrom<&[u8]> for Signature { type Error = (); fn try_from(data: &[u8]) -> Result { @@ -233,7 +228,7 @@ impl Serialize for Signature { where S: Serializer, { - serializer.serialize_str(&hex::encode(self)) + serializer.serialize_str(&array_bytes::bytes2hex("", self.as_ref())) } } @@ -243,7 +238,7 @@ impl<'de> Deserialize<'de> for Signature { where D: Deserializer<'de>, { - let signature_hex = hex::decode(&String::deserialize(deserializer)?) + let signature_hex = array_bytes::hex2bytes(&String::deserialize(deserializer)?) .map_err(|e| de::Error::custom(format!("{:?}", e)))?; Signature::try_from(signature_hex.as_ref()) .map_err(|e| de::Error::custom(format!("{:?}", e))) @@ -258,20 +253,6 @@ impl Clone for Signature { } } -impl Default for Signature { - fn default() -> Self { - Signature([0u8; 64]) - } -} - -impl PartialEq for Signature { - fn eq(&self, b: &Self) -> bool { - self.0[..] == b.0[..] - } -} - -impl Eq for Signature {} - impl From for H512 { fn from(v: Signature) -> H512 { H512::from(v.0) @@ -314,10 +295,9 @@ impl sp_std::fmt::Debug for Signature { } } -#[cfg(feature = "full_crypto")] -impl sp_std::hash::Hash for Signature { - fn hash(&self, state: &mut H) { - sp_std::hash::Hash::hash(&self.0[..], state); +impl UncheckedFrom<[u8; 64]> for Signature { + fn unchecked_from(data: [u8; 64]) -> Signature { + Signature(data) } } @@ -334,10 +314,13 @@ impl Signature { /// /// NOTE: No checking goes on to ensure this is a real signature. Only use it if /// you are certain that the array actually is a signature. GIGO! - pub fn from_slice(data: &[u8]) -> Self { + pub fn from_slice(data: &[u8]) -> Option { + if data.len() != 64 { + return None + } let mut r = [0u8; 64]; r.copy_from_slice(data); - Signature(r) + Some(Signature(r)) } /// A new instance from an H512. @@ -359,24 +342,6 @@ pub struct LocalizedSignature { pub signature: Signature, } -/// An error type for SS58 decoding. -#[cfg(feature = "std")] -#[derive(Clone, Copy, Eq, PartialEq, Debug, thiserror::Error)] -pub enum PublicError { - /// Bad alphabet. - #[error("Base 58 requirement is violated")] - BadBase58, - /// Bad length. - #[error("Length is bad")] - BadLength, - /// Unknown version. - #[error("Unknown version")] - UnknownVersion, - /// Invalid checksum. - #[error("Invalid checksum")] - InvalidChecksum, -} - impl Public { /// A new instance from the given 32-byte `data`. /// @@ -400,17 +365,11 @@ impl Public { } } -impl TraitPublic for Public { - /// A new instance from the given slice that should be 32 bytes long. - /// - /// NOTE: No checking goes on to ensure this is a real public key. Only use it if - /// you are certain that the array actually is a pubkey. GIGO! - fn from_slice(data: &[u8]) -> Self { - let mut r = [0u8; 32]; - r.copy_from_slice(data); - Public(r) - } +impl ByteArray for Public { + const LEN: usize = 32; +} +impl TraitPublic for Public { fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { CryptoTypePublicPair(CRYPTO_ID, self.to_raw_vec()) } @@ -433,11 +392,7 @@ impl From<&Public> for CryptoTypePublicPair { /// Derive a single hard junction. #[cfg(feature = "full_crypto")] fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { - ("Ed25519HDKD", secret_seed, cc).using_encoded(|data| { - let mut res = [0u8; 32]; - res.copy_from_slice(blake2_rfc::blake2b::blake2b(32, &[], data).as_bytes()); - res - }) + ("Ed25519HDKD", secret_seed, cc).using_encoded(sp_core_hashing::blake2_256) } /// An error when deriving a key. @@ -496,10 +451,10 @@ impl TraitPair for Pair { /// /// You should never need to use this; generate(), generate_with_phrase fn from_seed_slice(seed_slice: &[u8]) -> Result { - let secret = ed25519_dalek::SecretKey::from_bytes(seed_slice) - .map_err(|_| SecretStringError::InvalidSeedLength)?; - let public = ed25519_dalek::PublicKey::from(&secret); - Ok(Pair(ed25519_dalek::Keypair { secret, public })) + let secret = + SigningKey::try_from(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?; + let public = VerificationKey::from(&secret); + Ok(Pair { secret, public }) } /// Derive a child key from a series of given junctions. @@ -508,7 +463,7 @@ impl TraitPair for Pair { path: Iter, _seed: Option, ) -> Result<(Pair, Option), DeriveError> { - let mut acc = self.0.secret.to_bytes(); + let mut acc = self.secret.into(); for j in path { match j { DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), @@ -520,16 +475,12 @@ impl TraitPair for Pair { /// Get the public key. fn public(&self) -> Public { - let mut r = [0u8; 32]; - let pk = self.0.public.as_bytes(); - r.copy_from_slice(pk); - Public(r) + Public(self.public.into()) } /// Sign a message. fn sign(&self, message: &[u8]) -> Signature { - let r = self.0.sign(message).to_bytes(); - Signature::from_raw(r) + Signature::from_raw(self.secret.sign(message).into()) } /// Verify a signature on a message. Returns true if the signature is good. @@ -542,17 +493,17 @@ impl TraitPair for Pair { /// This doesn't use the type system to ensure that `sig` and `pubkey` are the correct /// size. Use it only if you're coming from byte buffers and need the speed. fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { - let public_key = match ed25519_dalek::PublicKey::from_bytes(pubkey.as_ref()) { + let public_key = match VerificationKey::try_from(pubkey.as_ref()) { Ok(pk) => pk, Err(_) => return false, }; - let sig = match ed25519_dalek::Signature::try_from(sig) { + let sig = match ed25519_zebra::Signature::try_from(sig) { Ok(s) => s, Err(_) => return false, }; - public_key.verify(message.as_ref(), &sig).is_ok() + public_key.verify(&sig, message.as_ref()).is_ok() } /// Return a vec filled with raw data. @@ -564,8 +515,8 @@ impl TraitPair for Pair { #[cfg(feature = "full_crypto")] impl Pair { /// Get the seed for this key. - pub fn seed(&self) -> &Seed { - self.0.secret.as_bytes() + pub fn seed(&self) -> Seed { + self.secret.into() } /// Exactly as `from_string` except that if no matches are found then, the the first 32 @@ -600,7 +551,6 @@ impl CryptoType for Pair { mod test { use super::*; use crate::crypto::DEV_PHRASE; - use hex_literal::hex; use serde_json; #[test] @@ -615,31 +565,35 @@ mod test { #[test] fn seed_and_derive_should_work() { - let seed = hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"); + let seed = array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + ); let pair = Pair::from_seed(&seed); - assert_eq!(pair.seed(), &seed); + assert_eq!(pair.seed(), seed); let path = vec![DeriveJunction::Hard([0u8; 32])]; let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; assert_eq!( derived.seed(), - &hex!("ede3354e133f9c8e337ddd6ee5415ed4b4ffe5fc7d21e933f4930a3730e5b21c") + array_bytes::hex2array_unchecked::<32>( + "ede3354e133f9c8e337ddd6ee5415ed4b4ffe5fc7d21e933f4930a3730e5b21c" + ) ); } #[test] fn test_vector_should_work() { - let pair = Pair::from_seed(&hex!( - "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + let pair = Pair::from_seed(&array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", )); let public = pair.public(); assert_eq!( public, - Public::from_raw(hex!( + Public::from_raw(array_bytes::hex2array_unchecked( "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" )) ); let message = b""; - let signature = hex!("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); + let signature = array_bytes::hex2array_unchecked("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); let signature = Signature::from_raw(signature); assert!(pair.sign(&message[..]) == signature); assert!(Pair::verify(&signature, &message[..], &public)); @@ -655,12 +609,12 @@ mod test { let public = pair.public(); assert_eq!( public, - Public::from_raw(hex!( + Public::from_raw(array_bytes::hex2array_unchecked( "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" )) ); let message = b""; - let signature = hex!("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); + let signature = array_bytes::hex2array_unchecked("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); let signature = Signature::from_raw(signature); assert!(pair.sign(&message[..]) == signature); assert!(Pair::verify(&signature, &message[..], &public)); @@ -682,11 +636,11 @@ mod test { let public = pair.public(); assert_eq!( public, - Public::from_raw(hex!( + Public::from_raw(array_bytes::hex2array_unchecked( "2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee" )) ); - let message = hex!("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000"); + let message = array_bytes::hex2bytes_unchecked("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000"); let signature = pair.sign(&message[..]); println!("Correct signature: {:?}", signature); assert!(Pair::verify(&signature, &message[..], &public)); diff --git a/primitives/core/src/hash.rs b/primitives/core/src/hash.rs index 55a9664c9dad4..f2974e9372ad5 100644 --- a/primitives/core/src/hash.rs +++ b/primitives/core/src/hash.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/core/src/hasher.rs b/primitives/core/src/hasher.rs index 01680de083762..173bd560ad1e3 100644 --- a/primitives/core/src/hasher.rs +++ b/primitives/core/src/hasher.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/core/src/hashing.rs b/primitives/core/src/hashing.rs index 4c719f7c69832..1c439355a33c0 100644 --- a/primitives/core/src/hashing.rs +++ b/primitives/core/src/hashing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,147 +22,45 @@ //! unless you know what you're doing. Using `sp_io` will be more performant, since instead of //! computing the hash in WASM it delegates that computation to the host client. -use sha2::{Digest, Sha256}; -use tiny_keccak::{Hasher, Keccak}; - -/// Do a Blake2 512-bit hash and place result in `dest`. -pub fn blake2_512_into(data: &[u8], dest: &mut [u8; 64]) { - dest.copy_from_slice(blake2_rfc::blake2b::blake2b(64, &[], data).as_bytes()); -} - -/// Do a Blake2 512-bit hash and return result. -pub fn blake2_512(data: &[u8]) -> [u8; 64] { - let mut r = [0; 64]; - blake2_512_into(data, &mut r); - r -} - -/// Do a Blake2 256-bit hash and place result in `dest`. -pub fn blake2_256_into(data: &[u8], dest: &mut [u8; 32]) { - dest.copy_from_slice(blake2_rfc::blake2b::blake2b(32, &[], data).as_bytes()); -} - -/// Do a Blake2 256-bit hash and return result. -pub fn blake2_256(data: &[u8]) -> [u8; 32] { - let mut r = [0; 32]; - blake2_256_into(data, &mut r); - r -} - -/// Do a Blake2 128-bit hash and place result in `dest`. -pub fn blake2_128_into(data: &[u8], dest: &mut [u8; 16]) { - dest.copy_from_slice(blake2_rfc::blake2b::blake2b(16, &[], data).as_bytes()); -} - -/// Do a Blake2 128-bit hash and return result. -pub fn blake2_128(data: &[u8]) -> [u8; 16] { - let mut r = [0; 16]; - blake2_128_into(data, &mut r); - r -} - -/// Do a Blake2 64-bit hash and place result in `dest`. -pub fn blake2_64_into(data: &[u8], dest: &mut [u8; 8]) { - dest.copy_from_slice(blake2_rfc::blake2b::blake2b(8, &[], data).as_bytes()); -} - -/// Do a Blake2 64-bit hash and return result. -pub fn blake2_64(data: &[u8]) -> [u8; 8] { - let mut r = [0; 8]; - blake2_64_into(data, &mut r); - r -} - -/// Do a XX 64-bit hash and place result in `dest`. -pub fn twox_64_into(data: &[u8], dest: &mut [u8; 8]) { - use core::hash::Hasher; - let mut h0 = twox_hash::XxHash::with_seed(0); - h0.write(data); - let r0 = h0.finish(); - use byteorder::{ByteOrder, LittleEndian}; - LittleEndian::write_u64(&mut dest[0..8], r0); -} - -/// Do a XX 64-bit hash and return result. -pub fn twox_64(data: &[u8]) -> [u8; 8] { - let mut r: [u8; 8] = [0; 8]; - twox_64_into(data, &mut r); - r -} - -/// Do a XX 128-bit hash and place result in `dest`. -pub fn twox_128_into(data: &[u8], dest: &mut [u8; 16]) { - use core::hash::Hasher; - let mut h0 = twox_hash::XxHash::with_seed(0); - let mut h1 = twox_hash::XxHash::with_seed(1); - h0.write(data); - h1.write(data); - let r0 = h0.finish(); - let r1 = h1.finish(); - use byteorder::{ByteOrder, LittleEndian}; - LittleEndian::write_u64(&mut dest[0..8], r0); - LittleEndian::write_u64(&mut dest[8..16], r1); -} - -/// Do a XX 128-bit hash and return result. -pub fn twox_128(data: &[u8]) -> [u8; 16] { - let mut r: [u8; 16] = [0; 16]; - twox_128_into(data, &mut r); - r -} - -/// Do a XX 256-bit hash and place result in `dest`. -pub fn twox_256_into(data: &[u8], dest: &mut [u8; 32]) { - use ::core::hash::Hasher; - use byteorder::{ByteOrder, LittleEndian}; - let mut h0 = twox_hash::XxHash::with_seed(0); - let mut h1 = twox_hash::XxHash::with_seed(1); - let mut h2 = twox_hash::XxHash::with_seed(2); - let mut h3 = twox_hash::XxHash::with_seed(3); - h0.write(data); - h1.write(data); - h2.write(data); - h3.write(data); - let r0 = h0.finish(); - let r1 = h1.finish(); - let r2 = h2.finish(); - let r3 = h3.finish(); - LittleEndian::write_u64(&mut dest[0..8], r0); - LittleEndian::write_u64(&mut dest[8..16], r1); - LittleEndian::write_u64(&mut dest[16..24], r2); - LittleEndian::write_u64(&mut dest[24..32], r3); -} - -/// Do a XX 256-bit hash and return result. -pub fn twox_256(data: &[u8]) -> [u8; 32] { - let mut r: [u8; 32] = [0; 32]; - twox_256_into(data, &mut r); - r -} - -/// Do a keccak 256-bit hash and return result. -pub fn keccak_256(data: &[u8]) -> [u8; 32] { - let mut keccak = Keccak::v256(); - keccak.update(data); - let mut output = [0u8; 32]; - keccak.finalize(&mut output); - output -} - -/// Do a keccak 512-bit hash and return result. -pub fn keccak_512(data: &[u8]) -> [u8; 64] { - let mut keccak = Keccak::v512(); - keccak.update(data); - let mut output = [0u8; 64]; - keccak.finalize(&mut output); - output -} - -/// Do a sha2 256-bit hash and return result. -pub fn sha2_256(data: &[u8]) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(data); - let mut output = [0u8; 32]; - output.copy_from_slice(&hasher.finalize()); - output +pub use sp_core_hashing::*; + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn blake2b() { + assert_eq!(sp_core_hashing_proc_macro::blake2b_64!(b""), blake2_64(b"")[..]); + assert_eq!(sp_core_hashing_proc_macro::blake2b_256!(b"test"), blake2_256(b"test")[..]); + assert_eq!(sp_core_hashing_proc_macro::blake2b_512!(b""), blake2_512(b"")[..]); + } + + #[test] + fn keccak() { + assert_eq!(sp_core_hashing_proc_macro::keccak_256!(b"test"), keccak_256(b"test")[..]); + assert_eq!(sp_core_hashing_proc_macro::keccak_512!(b"test"), keccak_512(b"test")[..]); + } + + #[test] + fn sha2() { + assert_eq!(sp_core_hashing_proc_macro::sha2_256!(b"test"), sha2_256(b"test")[..]); + } + + #[test] + fn twox() { + assert_eq!(sp_core_hashing_proc_macro::twox_128!(b"test"), twox_128(b"test")[..]); + assert_eq!(sp_core_hashing_proc_macro::twox_64!(b""), twox_64(b"")[..]); + } + + #[test] + fn twox_concats() { + assert_eq!( + sp_core_hashing_proc_macro::twox_128!(b"test", b"123", b"45", b"", b"67890"), + super::twox_128(&b"test1234567890"[..]), + ); + assert_eq!( + sp_core_hashing_proc_macro::twox_128!(b"test", test, b"45", b"", b"67890"), + super::twox_128(&b"testtest4567890"[..]), + ); + } } diff --git a/primitives/core/src/hexdisplay.rs b/primitives/core/src/hexdisplay.rs index 4d91db1567920..26c04c433b49f 100644 --- a/primitives/core/src/hexdisplay.rs +++ b/primitives/core/src/hexdisplay.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -69,13 +69,13 @@ impl AsBytesRef for &[u8] { impl AsBytesRef for [u8] { fn as_bytes_ref(&self) -> &[u8] { - &self + self } } impl AsBytesRef for sp_std::vec::Vec { fn as_bytes_ref(&self) -> &[u8] { - &self + self } } diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index a6229fe43a1a5..fda7604d5337f 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,7 @@ macro_rules! map { } #[doc(hidden)] -pub use codec::{Decode, Encode}; +pub use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; #[cfg(feature = "std")] pub use serde; @@ -40,8 +40,6 @@ pub use serde; use serde::{Deserialize, Serialize}; use sp_runtime_interface::pass_by::{PassByEnum, PassByInner}; use sp_std::{ops::Deref, prelude::*}; -#[cfg(feature = "std")] -use std::borrow::Cow; pub use sp_debug_derive::RuntimeDebug; @@ -53,19 +51,17 @@ pub mod hashing; #[cfg(feature = "full_crypto")] pub use hashing::{blake2_128, blake2_256, keccak_256, twox_128, twox_256, twox_64}; +pub mod bounded; pub mod crypto; pub mod hexdisplay; -pub mod u32_trait; - -mod changes_trie; +pub mod defer; pub mod ecdsa; pub mod ed25519; pub mod hash; #[cfg(feature = "std")] mod hasher; pub mod offchain; -pub mod sandbox; pub mod sr25519; pub mod testing; #[cfg(feature = "std")] @@ -76,9 +72,8 @@ pub use self::{ hash::{convert_hash, H160, H256, H512}, uint::{U256, U512}, }; -pub use changes_trie::{ChangesTrieConfiguration, ChangesTrieConfigurationRange}; #[cfg(feature = "full_crypto")] -pub use crypto::{DeriveJunction, Pair, Public}; +pub use crypto::{ByteArray, DeriveJunction, Pair, Public}; #[cfg(feature = "std")] pub use self::hasher::blake2::Blake2Hasher; @@ -100,7 +95,7 @@ pub enum ExecutionContext { /// We distinguish between major sync and import so that validators who are running /// their initial sync (or catching up after some time offline) can use the faster /// native runtime (since we can reasonably assume the network as a whole has already - /// come to a broad conensus on the block and it probably hasn't been crafted + /// come to a broad consensus on the block and it probably hasn't been crafted /// specifically to attack this node), but when importing blocks at the head of the /// chain in normal operation they can use the safer Wasm version. Syncing, @@ -118,15 +113,13 @@ impl ExecutionContext { use ExecutionContext::*; match self { - Importing | Syncing | BlockConstruction => offchain::Capabilities::none(), + Importing | Syncing | BlockConstruction => offchain::Capabilities::empty(), // Enable keystore, transaction pool and Offchain DB reads by default for offchain // calls. - OffchainCall(None) => [ - offchain::Capability::Keystore, - offchain::Capability::OffchainDbRead, - offchain::Capability::TransactionPool, - ][..] - .into(), + OffchainCall(None) => + offchain::Capabilities::KEYSTORE | + offchain::Capabilities::OFFCHAIN_DB_READ | + offchain::Capabilities::TRANSACTION_POOL, OffchainCall(Some((_, capabilities))) => *capabilities, } } @@ -214,85 +207,6 @@ impl OpaquePeerId { } } -/// Something that is either a native or an encoded value. -#[cfg(feature = "std")] -pub enum NativeOrEncoded { - /// The native representation. - Native(R), - /// The encoded representation. - Encoded(Vec), -} - -#[cfg(feature = "std")] -impl From for NativeOrEncoded { - fn from(val: R) -> Self { - Self::Native(val) - } -} - -#[cfg(feature = "std")] -impl sp_std::fmt::Debug for NativeOrEncoded { - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - hexdisplay::HexDisplay::from(&self.as_encoded().as_ref()).fmt(f) - } -} - -#[cfg(feature = "std")] -impl NativeOrEncoded { - /// Return the value as the encoded format. - pub fn as_encoded(&self) -> Cow<'_, [u8]> { - match self { - NativeOrEncoded::Encoded(e) => Cow::Borrowed(e.as_slice()), - NativeOrEncoded::Native(n) => Cow::Owned(n.encode()), - } - } - - /// Return the value as the encoded format. - pub fn into_encoded(self) -> Vec { - match self { - NativeOrEncoded::Encoded(e) => e, - NativeOrEncoded::Native(n) => n.encode(), - } - } -} - -#[cfg(feature = "std")] -impl PartialEq for NativeOrEncoded { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (NativeOrEncoded::Native(l), NativeOrEncoded::Native(r)) => l == r, - (NativeOrEncoded::Native(n), NativeOrEncoded::Encoded(e)) | - (NativeOrEncoded::Encoded(e), NativeOrEncoded::Native(n)) => - Some(n) == codec::Decode::decode(&mut &e[..]).ok().as_ref(), - (NativeOrEncoded::Encoded(l), NativeOrEncoded::Encoded(r)) => l == r, - } - } -} - -/// A value that is never in a native representation. -/// This is type is useful in conjunction with `NativeOrEncoded`. -#[cfg(feature = "std")] -#[derive(PartialEq)] -pub enum NeverNativeValue {} - -#[cfg(feature = "std")] -impl codec::Encode for NeverNativeValue { - fn encode(&self) -> Vec { - // The enum is not constructable, so this function should never be callable! - unreachable!() - } -} - -#[cfg(feature = "std")] -impl codec::EncodeLike for NeverNativeValue {} - -#[cfg(feature = "std")] -impl codec::Decode for NeverNativeValue { - fn decode(_: &mut I) -> Result { - Err("`NeverNativeValue` should never be decoded".into()) - } -} - /// Provide a simple 4 byte identifier for a type. pub trait TypeId { /// Simple 4 byte identifier. @@ -425,7 +339,7 @@ pub fn to_substrate_wasm_fn_return_value(value: &impl Encode) -> u64 { /// The void type - it cannot exist. // Oh rust, you crack me up... -#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum Void {} /// Macro for creating `Maybe*` marker traits. @@ -472,3 +386,239 @@ macro_rules! impl_maybe_marker { // The maximum possible allocation size was chosen rather arbitrary, 32 MiB should be enough for // everybody. pub const MAX_POSSIBLE_ALLOCATION: u32 = 33554432; // 2^25 bytes, 32 MiB + +/// A trait for querying a single value from a type defined in the trait. +/// +/// It is not required that the value is constant. +pub trait TypedGet { + /// The type which is returned. + type Type; + /// Return the current value. + fn get() -> Self::Type; +} + +/// A trait for querying a single value from a type. +/// +/// It is not required that the value is constant. +pub trait Get { + /// Return the current value. + fn get() -> T; +} + +impl Get for () { + fn get() -> T { + T::default() + } +} + +/// Implement Get by returning Default for any type that implements Default. +pub struct GetDefault; +impl Get for GetDefault { + fn get() -> T { + T::default() + } +} + +macro_rules! impl_const_get { + ($name:ident, $t:ty) => { + #[doc = "Const getter for a basic type."] + #[derive($crate::RuntimeDebug)] + pub struct $name; + impl Get<$t> for $name { + fn get() -> $t { + T + } + } + impl Get> for $name { + fn get() -> Option<$t> { + Some(T) + } + } + impl TypedGet for $name { + type Type = $t; + fn get() -> $t { + T + } + } + }; +} + +impl_const_get!(ConstBool, bool); +impl_const_get!(ConstU8, u8); +impl_const_get!(ConstU16, u16); +impl_const_get!(ConstU32, u32); +impl_const_get!(ConstU64, u64); +impl_const_get!(ConstU128, u128); +impl_const_get!(ConstI8, i8); +impl_const_get!(ConstI16, i16); +impl_const_get!(ConstI32, i32); +impl_const_get!(ConstI64, i64); +impl_const_get!(ConstI128, i128); + +/// Try and collect into a collection `C`. +pub trait TryCollect { + /// The error type that gets returned when a collection can't be made from `self`. + type Error; + /// Consume self and try to collect the results into `C`. + /// + /// This is useful in preventing the undesirable `.collect().try_into()` call chain on + /// collections that need to be converted into a bounded type (e.g. `BoundedVec`). + fn try_collect(self) -> Result; +} + +/// Create new implementations of the [`Get`](crate::Get) trait. +/// +/// The so-called parameter type can be created in four different ways: +/// +/// - Using `const` to create a parameter type that provides a `const` getter. It is required that +/// the `value` is const. +/// +/// - Declare the parameter type without `const` to have more freedom when creating the value. +/// +/// NOTE: A more substantial version of this macro is available in `frame_support` crate which +/// allows mutable and persistant variants. +/// +/// # Examples +/// +/// ``` +/// # use sp_core::Get; +/// # use sp_core::parameter_types; +/// // This function cannot be used in a const context. +/// fn non_const_expression() -> u64 { 99 } +/// +/// const FIXED_VALUE: u64 = 10; +/// parameter_types! { +/// pub const Argument: u64 = 42 + FIXED_VALUE; +/// /// Visibility of the type is optional +/// OtherArgument: u64 = non_const_expression(); +/// } +/// +/// trait Config { +/// type Parameter: Get; +/// type OtherParameter: Get; +/// } +/// +/// struct Runtime; +/// impl Config for Runtime { +/// type Parameter = Argument; +/// type OtherParameter = OtherArgument; +/// } +/// ``` +/// +/// # Invalid example: +/// +/// ```compile_fail +/// # use sp_core::Get; +/// # use sp_core::parameter_types; +/// // This function cannot be used in a const context. +/// fn non_const_expression() -> u64 { 99 } +/// +/// parameter_types! { +/// pub const Argument: u64 = non_const_expression(); +/// } +/// ``` +#[macro_export] +macro_rules! parameter_types { + ( + $( #[ $attr:meta ] )* + $vis:vis const $name:ident: $type:ty = $value:expr; + $( $rest:tt )* + ) => ( + $( #[ $attr ] )* + $vis struct $name; + $crate::parameter_types!(@IMPL_CONST $name , $type , $value); + $crate::parameter_types!( $( $rest )* ); + ); + ( + $( #[ $attr:meta ] )* + $vis:vis $name:ident: $type:ty = $value:expr; + $( $rest:tt )* + ) => ( + $( #[ $attr ] )* + $vis struct $name; + $crate::parameter_types!(@IMPL $name, $type, $value); + $crate::parameter_types!( $( $rest )* ); + ); + () => (); + (@IMPL_CONST $name:ident, $type:ty, $value:expr) => { + impl $name { + /// Returns the value of this parameter type. + pub const fn get() -> $type { + $value + } + } + + impl> $crate::Get for $name { + fn get() -> I { + I::from(Self::get()) + } + } + + impl $crate::TypedGet for $name { + type Type = $type; + fn get() -> $type { + Self::get() + } + } + }; + (@IMPL $name:ident, $type:ty, $value:expr) => { + impl $name { + /// Returns the value of this parameter type. + pub fn get() -> $type { + $value + } + } + + impl> $crate::Get for $name { + fn get() -> I { + I::from(Self::get()) + } + } + + impl $crate::TypedGet for $name { + type Type = $type; + fn get() -> $type { + Self::get() + } + } + }; +} + +/// Build a bounded vec from the given literals. +/// +/// The type of the outcome must be known. +/// +/// Will not handle any errors and just panic if the given literals cannot fit in the corresponding +/// bounded vec type. Thus, this is only suitable for testing and non-consensus code. +#[macro_export] +#[cfg(feature = "std")] +macro_rules! bounded_vec { + ($ ($values:expr),* $(,)?) => { + { + $crate::sp_std::vec![$($values),*].try_into().unwrap() + } + }; + ( $value:expr ; $repetition:expr ) => { + { + $crate::sp_std::vec![$value ; $repetition].try_into().unwrap() + } + } +} + +/// Build a bounded btree-map from the given literals. +/// +/// The type of the outcome must be known. +/// +/// Will not handle any errors and just panic if the given literals cannot fit in the corresponding +/// bounded vec type. Thus, this is only suitable for testing and non-consensus code. +#[macro_export] +#[cfg(feature = "std")] +macro_rules! bounded_btree_map { + ($ ( $key:expr => $value:expr ),* $(,)?) => { + { + $crate::TryCollect::<$crate::bounded::BoundedBTreeMap<_, _, _>>::try_collect( + $crate::sp_std::vec![$(($key, $value)),*].into_iter() + ).unwrap() + } + }; +} diff --git a/primitives/core/src/offchain/mod.rs b/primitives/core/src/offchain/mod.rs index 640f4d2583b79..3f0eed87a3a64 100644 --- a/primitives/core/src/offchain/mod.rs +++ b/primitives/core/src/offchain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +21,7 @@ use crate::{OpaquePeerId, RuntimeDebug}; use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime_interface::pass_by::{PassByCodec, PassByEnum, PassByInner}; -use sp_std::{ - convert::TryFrom, - prelude::{Box, Vec}, -}; +use sp_std::prelude::{Box, Vec}; pub use crate::crypto::KeyTypeId; @@ -211,12 +208,14 @@ impl OpaqueMultiaddr { #[derive( Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default, RuntimeDebug, PassByInner, Encode, Decode, )] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct Timestamp(u64); /// Duration type #[derive( Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default, RuntimeDebug, PassByInner, Encode, Decode, )] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct Duration(u64); impl Duration { @@ -258,65 +257,37 @@ impl Timestamp { } } -/// Execution context extra capabilities. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[repr(u8)] -pub enum Capability { - /// Access to transaction pool. - TransactionPool = 1, - /// External http calls. - Http = 2, - /// Keystore access. - Keystore = 4, - /// Randomness source. - Randomness = 8, - /// Access to opaque network state. - NetworkState = 16, - /// Access to offchain worker DB (read only). - OffchainDbRead = 32, - /// Access to offchain worker DB (writes). - OffchainDbWrite = 64, - /// Manage the authorized nodes - NodeAuthorization = 128, -} - -/// A set of capabilities -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct Capabilities(u8); - -impl Capabilities { - /// Return an object representing an empty set of capabilities. - pub fn none() -> Self { - Self(0) - } - - /// Return an object representing all capabilities enabled. - pub fn all() -> Self { - Self(u8::MAX) +bitflags::bitflags! { + /// Execution context extra capabilities. + pub struct Capabilities: u32 { + /// Access to transaction pool. + const TRANSACTION_POOL = 0b0000_0000_0001; + /// External http calls. + const HTTP = 0b0000_0000_0010; + /// Keystore access. + const KEYSTORE = 0b0000_0000_0100; + /// Randomness source. + const RANDOMNESS = 0b0000_0000_1000; + /// Access to opaque network state. + const NETWORK_STATE = 0b0000_0001_0000; + /// Access to offchain worker DB (read only). + const OFFCHAIN_DB_READ = 0b0000_0010_0000; + /// Access to offchain worker DB (writes). + const OFFCHAIN_DB_WRITE = 0b0000_0100_0000; + /// Manage the authorized nodes + const NODE_AUTHORIZATION = 0b0000_1000_0000; + /// Access time related functionality + const TIME = 0b0001_0000_0000; } +} +impl Capabilities { /// Return capabilities for rich offchain calls. /// /// Those calls should be allowed to sign and submit transactions /// and access offchain workers database (but read only!). pub fn rich_offchain_call() -> Self { - [Capability::TransactionPool, Capability::Keystore, Capability::OffchainDbRead][..].into() - } - - /// Check if particular capability is enabled. - pub fn has(&self, capability: Capability) -> bool { - self.0 & capability as u8 != 0 - } - - /// Check if this capability object represents all capabilities. - pub fn has_all(&self) -> bool { - self == &Capabilities::all() - } -} - -impl<'a> From<&'a [Capability]> for Capabilities { - fn from(list: &'a [Capability]) -> Self { - Capabilities(list.iter().fold(0_u8, |a, b| a | *b as u8)) + Capabilities::TRANSACTION_POOL | Capabilities::KEYSTORE | Capabilities::OFFCHAIN_DB_READ } } @@ -552,8 +523,8 @@ impl LimitedExternalities { /// Check if given capability is allowed. /// /// Panics in case it is not. - fn check(&self, capability: Capability, name: &'static str) { - if !self.capabilities.has(capability) { + fn check(&self, capability: Capabilities, name: &'static str) { + if !self.capabilities.contains(capability) { panic!("Accessing a forbidden API: {}. No: {:?} capability.", name, capability); } } @@ -561,27 +532,27 @@ impl LimitedExternalities { impl Externalities for LimitedExternalities { fn is_validator(&self) -> bool { - self.check(Capability::Keystore, "is_validator"); + self.check(Capabilities::KEYSTORE, "is_validator"); self.externalities.is_validator() } fn network_state(&self) -> Result { - self.check(Capability::NetworkState, "network_state"); + self.check(Capabilities::NETWORK_STATE, "network_state"); self.externalities.network_state() } fn timestamp(&mut self) -> Timestamp { - self.check(Capability::Http, "timestamp"); + self.check(Capabilities::TIME, "timestamp"); self.externalities.timestamp() } fn sleep_until(&mut self, deadline: Timestamp) { - self.check(Capability::Http, "sleep_until"); + self.check(Capabilities::TIME, "sleep_until"); self.externalities.sleep_until(deadline) } fn random_seed(&mut self) -> [u8; 32] { - self.check(Capability::Randomness, "random_seed"); + self.check(Capabilities::RANDOMNESS, "random_seed"); self.externalities.random_seed() } @@ -591,7 +562,7 @@ impl Externalities for LimitedExternalities { uri: &str, meta: &[u8], ) -> Result { - self.check(Capability::Http, "http_request_start"); + self.check(Capabilities::HTTP, "http_request_start"); self.externalities.http_request_start(method, uri, meta) } @@ -601,7 +572,7 @@ impl Externalities for LimitedExternalities { name: &str, value: &str, ) -> Result<(), ()> { - self.check(Capability::Http, "http_request_add_header"); + self.check(Capabilities::HTTP, "http_request_add_header"); self.externalities.http_request_add_header(request_id, name, value) } @@ -611,7 +582,7 @@ impl Externalities for LimitedExternalities { chunk: &[u8], deadline: Option, ) -> Result<(), HttpError> { - self.check(Capability::Http, "http_request_write_body"); + self.check(Capabilities::HTTP, "http_request_write_body"); self.externalities.http_request_write_body(request_id, chunk, deadline) } @@ -620,12 +591,12 @@ impl Externalities for LimitedExternalities { ids: &[HttpRequestId], deadline: Option, ) -> Vec { - self.check(Capability::Http, "http_response_wait"); + self.check(Capabilities::HTTP, "http_response_wait"); self.externalities.http_response_wait(ids, deadline) } fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec, Vec)> { - self.check(Capability::Http, "http_response_headers"); + self.check(Capabilities::HTTP, "http_response_headers"); self.externalities.http_response_headers(request_id) } @@ -635,12 +606,12 @@ impl Externalities for LimitedExternalities { buffer: &mut [u8], deadline: Option, ) -> Result { - self.check(Capability::Http, "http_response_read_body"); + self.check(Capabilities::HTTP, "http_response_read_body"); self.externalities.http_response_read_body(request_id, buffer, deadline) } fn set_authorized_nodes(&mut self, nodes: Vec, authorized_only: bool) { - self.check(Capability::NodeAuthorization, "set_authorized_nodes"); + self.check(Capabilities::NODE_AUTHORIZATION, "set_authorized_nodes"); self.externalities.set_authorized_nodes(nodes, authorized_only) } } @@ -724,12 +695,12 @@ impl DbExternalities for Box { impl DbExternalities for LimitedExternalities { fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) { - self.check(Capability::OffchainDbWrite, "local_storage_set"); + self.check(Capabilities::OFFCHAIN_DB_WRITE, "local_storage_set"); self.externalities.local_storage_set(kind, key, value) } fn local_storage_clear(&mut self, kind: StorageKind, key: &[u8]) { - self.check(Capability::OffchainDbWrite, "local_storage_clear"); + self.check(Capabilities::OFFCHAIN_DB_WRITE, "local_storage_clear"); self.externalities.local_storage_clear(kind, key) } @@ -740,13 +711,13 @@ impl DbExternalities for LimitedExternalities { old_value: Option<&[u8]>, new_value: &[u8], ) -> bool { - self.check(Capability::OffchainDbWrite, "local_storage_compare_and_set"); + self.check(Capabilities::OFFCHAIN_DB_WRITE, "local_storage_compare_and_set"); self.externalities .local_storage_compare_and_set(kind, key, old_value, new_value) } fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option> { - self.check(Capability::OffchainDbRead, "local_storage_get"); + self.check(Capabilities::OFFCHAIN_DB_READ, "local_storage_get"); self.externalities.local_storage_get(kind, key) } } @@ -815,15 +786,15 @@ mod tests { #[test] fn capabilities() { - let none = Capabilities::none(); + let none = Capabilities::empty(); let all = Capabilities::all(); - let some = Capabilities::from(&[Capability::Keystore, Capability::Randomness][..]); - - assert!(!none.has(Capability::Keystore)); - assert!(all.has(Capability::Keystore)); - assert!(some.has(Capability::Keystore)); - assert!(!none.has(Capability::TransactionPool)); - assert!(all.has(Capability::TransactionPool)); - assert!(!some.has(Capability::TransactionPool)); + let some = Capabilities::KEYSTORE | Capabilities::RANDOMNESS; + + assert!(!none.contains(Capabilities::KEYSTORE)); + assert!(all.contains(Capabilities::KEYSTORE)); + assert!(some.contains(Capabilities::KEYSTORE)); + assert!(!none.contains(Capabilities::TRANSACTION_POOL)); + assert!(all.contains(Capabilities::TRANSACTION_POOL)); + assert!(!some.contains(Capabilities::TRANSACTION_POOL)); } } diff --git a/primitives/core/src/offchain/storage.rs b/primitives/core/src/offchain/storage.rs index ff72006cffd60..cf2c93641f245 100644 --- a/primitives/core/src/offchain/storage.rs +++ b/primitives/core/src/offchain/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/core/src/offchain/testing.rs b/primitives/core/src/offchain/testing.rs index 26bcdb66de836..a2065eb17717f 100644 --- a/primitives/core/src/offchain/testing.rs +++ b/primitives/core/src/offchain/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -341,7 +341,7 @@ impl offchain::Externalities for TestOffchainExt { Ok(0) } else { let read = std::cmp::min(buffer.len(), response[req.read..].len()); - buffer[0..read].copy_from_slice(&response[req.read..read]); + buffer[0..read].copy_from_slice(&response[req.read..req.read + read]); req.read += read; Ok(read) } diff --git a/primitives/core/src/sandbox.rs b/primitives/core/src/sandbox.rs deleted file mode 100644 index acc3fda5e9b17..0000000000000 --- a/primitives/core/src/sandbox.rs +++ /dev/null @@ -1,118 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 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. - -//! Definition of a sandbox environment. - -use codec::{Decode, Encode}; -use sp_std::vec::Vec; - -/// Error error that can be returned from host function. -#[derive(Encode, Decode, crate::RuntimeDebug)] -pub struct HostError; - -/// Describes an entity to define or import into the environment. -#[derive(Clone, PartialEq, Eq, Encode, Decode, crate::RuntimeDebug)] -pub enum ExternEntity { - /// Function that is specified by an index in a default table of - /// a module that creates the sandbox. - #[codec(index = 1)] - Function(u32), - - /// Linear memory that is specified by some identifier returned by sandbox - /// module upon creation new sandboxed memory. - #[codec(index = 2)] - Memory(u32), -} - -/// An entry in a environment definition table. -/// -/// Each entry has a two-level name and description of an entity -/// being defined. -#[derive(Clone, PartialEq, Eq, Encode, Decode, crate::RuntimeDebug)] -pub struct Entry { - /// Module name of which corresponding entity being defined. - pub module_name: Vec, - /// Field name in which corresponding entity being defined. - pub field_name: Vec, - /// External entity being defined. - pub entity: ExternEntity, -} - -/// Definition of runtime that could be used by sandboxed code. -#[derive(Clone, PartialEq, Eq, Encode, Decode, crate::RuntimeDebug)] -pub struct EnvironmentDefinition { - /// Vector of all entries in the environment definition. - pub entries: Vec, -} - -/// Constant for specifying no limit when creating a sandboxed -/// memory instance. For FFI purposes. -pub const MEM_UNLIMITED: u32 = -1i32 as u32; - -/// No error happened. -/// -/// For FFI purposes. -pub const ERR_OK: u32 = 0; - -/// Validation or instantiation error occurred when creating new -/// sandboxed module instance. -/// -/// For FFI purposes. -pub const ERR_MODULE: u32 = -1i32 as u32; - -/// Out-of-bounds access attempted with memory or table. -/// -/// For FFI purposes. -pub const ERR_OUT_OF_BOUNDS: u32 = -2i32 as u32; - -/// Execution error occurred (typically trap). -/// -/// For FFI purposes. -pub const ERR_EXECUTION: u32 = -3i32 as u32; - -#[cfg(test)] -mod tests { - use super::*; - use codec::Codec; - use std::fmt; - - fn roundtrip(s: S) { - let encoded = s.encode(); - assert_eq!(S::decode(&mut &encoded[..]).unwrap(), s); - } - - #[test] - fn env_def_roundtrip() { - roundtrip(EnvironmentDefinition { entries: vec![] }); - - roundtrip(EnvironmentDefinition { - entries: vec![Entry { - module_name: b"kernel"[..].into(), - field_name: b"memory"[..].into(), - entity: ExternEntity::Memory(1337), - }], - }); - - roundtrip(EnvironmentDefinition { - entries: vec![Entry { - module_name: b"env"[..].into(), - field_name: b"abort"[..].into(), - entity: ExternEntity::Function(228), - }], - }); - } -} diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index 4787c2d9d13ee..9064fb7427393 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,13 +35,11 @@ use schnorrkel::{ #[cfg(feature = "full_crypto")] use sp_std::vec::Vec; #[cfg(feature = "std")] -use std::convert::TryFrom; -#[cfg(feature = "std")] use substrate_bip39::mini_secret_from_entropy; use crate::{ crypto::{ - CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, + ByteArray, CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, UncheckedFrom, }, hash::{H256, H512}, @@ -74,7 +72,6 @@ pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"sr25"); Copy, Encode, Decode, - Default, PassByInner, MaxEncodedLen, TypeInfo, @@ -143,17 +140,16 @@ impl std::str::FromStr for Public { } } -impl sp_std::convert::TryFrom<&[u8]> for Public { +impl TryFrom<&[u8]> for Public { type Error = (); fn try_from(data: &[u8]) -> Result { - if data.len() == 32 { - let mut inner = [0u8; 32]; - inner.copy_from_slice(data); - Ok(Public(inner)) - } else { - Err(()) + if data.len() != Self::LEN { + return Err(()) } + let mut r = [0u8; 32]; + r.copy_from_slice(data); + Ok(Self::unchecked_from(r)) } } @@ -213,10 +209,11 @@ impl<'de> Deserialize<'de> for Public { /// An Schnorrkel/Ristretto x25519 ("sr25519") signature. /// /// Instead of importing it for the local module, alias it to be available as a public type -#[derive(Encode, Decode, PassByInner, TypeInfo)] +#[cfg_attr(feature = "full_crypto", derive(Hash))] +#[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] pub struct Signature(pub [u8; 64]); -impl sp_std::convert::TryFrom<&[u8]> for Signature { +impl TryFrom<&[u8]> for Signature { type Error = (); fn try_from(data: &[u8]) -> Result { @@ -236,7 +233,7 @@ impl Serialize for Signature { where S: Serializer, { - serializer.serialize_str(&hex::encode(self)) + serializer.serialize_str(&array_bytes::bytes2hex("", self.as_ref())) } } @@ -246,7 +243,7 @@ impl<'de> Deserialize<'de> for Signature { where D: Deserializer<'de>, { - let signature_hex = hex::decode(&String::deserialize(deserializer)?) + let signature_hex = array_bytes::hex2bytes(&String::deserialize(deserializer)?) .map_err(|e| de::Error::custom(format!("{:?}", e)))?; Signature::try_from(signature_hex.as_ref()) .map_err(|e| de::Error::custom(format!("{:?}", e))) @@ -261,20 +258,6 @@ impl Clone for Signature { } } -impl Default for Signature { - fn default() -> Self { - Signature([0u8; 64]) - } -} - -impl PartialEq for Signature { - fn eq(&self, b: &Self) -> bool { - self.0[..] == b.0[..] - } -} - -impl Eq for Signature {} - impl From for [u8; 64] { fn from(v: Signature) -> [u8; 64] { v.0 @@ -324,13 +307,6 @@ impl sp_std::fmt::Debug for Signature { } } -#[cfg(feature = "full_crypto")] -impl sp_std::hash::Hash for Signature { - fn hash(&self, state: &mut H) { - sp_std::hash::Hash::hash(&self.0[..], state); - } -} - /// A localized signature also contains sender information. /// NOTE: Encode and Decode traits are supported in ed25519 but not possible for now here. #[cfg(feature = "std")] @@ -342,6 +318,12 @@ pub struct LocalizedSignature { pub signature: Signature, } +impl UncheckedFrom<[u8; 64]> for Signature { + fn unchecked_from(data: [u8; 64]) -> Signature { + Signature(data) + } +} + impl Signature { /// A new instance from the given 64-byte `data`. /// @@ -357,10 +339,13 @@ impl Signature { /// /// NOTE: No checking goes on to ensure this is a real signature. Only use it if /// you are certain that the array actually is a signature. GIGO! - pub fn from_slice(data: &[u8]) -> Self { + pub fn from_slice(data: &[u8]) -> Option { + if data.len() != 64 { + return None + } let mut r = [0u8; 64]; r.copy_from_slice(data); - Signature(r) + Some(Signature(r)) } /// A new instance from an H512. @@ -412,17 +397,11 @@ impl Public { } } -impl TraitPublic for Public { - /// A new instance from the given slice that should be 32 bytes long. - /// - /// NOTE: No checking goes on to ensure this is a real public key. Only use it if - /// you are certain that the array actually is a pubkey. GIGO! - fn from_slice(data: &[u8]) -> Self { - let mut r = [0u8; 32]; - r.copy_from_slice(data); - Public(r) - } +impl ByteArray for Public { + const LEN: usize = 32; +} +impl TraitPublic for Public { fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { CryptoTypePublicPair(CRYPTO_ID, self.to_raw_vec()) } @@ -685,7 +664,6 @@ pub fn verify_batch( mod compatibility_test { use super::*; use crate::crypto::DEV_PHRASE; - use hex_literal::hex; // NOTE: tests to ensure addresses that are created with the `0.1.x` version (pre-audit) are // still functional. @@ -694,7 +672,9 @@ mod compatibility_test { fn derive_soft_known_pair_should_work() { let pair = Pair::from_string(&format!("{}/Alice", DEV_PHRASE), None).unwrap(); // known address of DEV_PHRASE with 1.1 - let known = hex!("d6c71059dbbe9ad2b0ed3f289738b800836eb425544ce694825285b958ca755e"); + let known = array_bytes::hex2bytes_unchecked( + "d6c71059dbbe9ad2b0ed3f289738b800836eb425544ce694825285b958ca755e", + ); assert_eq!(pair.public().to_raw_vec(), known); } @@ -702,17 +682,19 @@ mod compatibility_test { fn derive_hard_known_pair_should_work() { let pair = Pair::from_string(&format!("{}//Alice", DEV_PHRASE), None).unwrap(); // known address of DEV_PHRASE with 1.1 - let known = hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"); + let known = array_bytes::hex2bytes_unchecked( + "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + ); assert_eq!(pair.public().to_raw_vec(), known); } #[test] fn verify_known_old_message_should_work() { - let public = Public::from_raw(hex!( - "b4bfa1f7a5166695eb75299fd1c4c03ea212871c342f2c5dfea0902b2c246918" + let public = Public::from_raw(array_bytes::hex2array_unchecked( + "b4bfa1f7a5166695eb75299fd1c4c03ea212871c342f2c5dfea0902b2c246918", )); // signature generated by the 1.1 version with the same ^^ public key. - let signature = Signature::from_raw(hex!( + let signature = Signature::from_raw(array_bytes::hex2array_unchecked( "5a9755f069939f45d96aaf125cf5ce7ba1db998686f87f2fb3cbdea922078741a73891ba265f70c31436e18a9acd14d189d73c12317ab6c313285cd938453202" )); let message = b"Verifying that I am the owner of 5G9hQLdsKQswNPgB499DeA5PkFBbgkLPJWkkS6FAM6xGQ8xD. Hash: 221455a3\n"; @@ -725,7 +707,6 @@ mod compatibility_test { mod test { use super::*; use crate::crypto::{Ss58Codec, DEV_ADDRESS, DEV_PHRASE}; - use hex_literal::hex; use serde_json; #[test] @@ -766,8 +747,8 @@ mod test { #[test] fn derive_soft_should_work() { - let pair = Pair::from_seed(&hex!( - "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + let pair = Pair::from_seed(&array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", )); let derive_1 = pair.derive(Some(DeriveJunction::soft(1)).into_iter(), None).unwrap().0; let derive_1b = pair.derive(Some(DeriveJunction::soft(1)).into_iter(), None).unwrap().0; @@ -778,8 +759,8 @@ mod test { #[test] fn derive_hard_should_work() { - let pair = Pair::from_seed(&hex!( - "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + let pair = Pair::from_seed(&array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", )); let derive_1 = pair.derive(Some(DeriveJunction::hard(1)).into_iter(), None).unwrap().0; let derive_1b = pair.derive(Some(DeriveJunction::hard(1)).into_iter(), None).unwrap().0; @@ -790,8 +771,8 @@ mod test { #[test] fn derive_soft_public_should_work() { - let pair = Pair::from_seed(&hex!( - "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + let pair = Pair::from_seed(&array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", )); let path = Some(DeriveJunction::soft(1)); let pair_1 = pair.derive(path.into_iter(), None).unwrap().0; @@ -801,8 +782,8 @@ mod test { #[test] fn derive_hard_public_should_fail() { - let pair = Pair::from_seed(&hex!( - "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + let pair = Pair::from_seed(&array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", )); let path = Some(DeriveJunction::hard(1)); assert!(pair.public().derive(path.into_iter()).is_none()); @@ -810,13 +791,13 @@ mod test { #[test] fn sr_test_vector_should_work() { - let pair = Pair::from_seed(&hex!( - "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + let pair = Pair::from_seed(&array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", )); let public = pair.public(); assert_eq!( public, - Public::from_raw(hex!( + Public::from_raw(array_bytes::hex2array_unchecked( "44a996beb1eef7bdcab976ab6d2ca26104834164ecf28fb375600576fcc6eb0f" )) ); @@ -861,11 +842,11 @@ mod test { let public = pair.public(); assert_eq!( public, - Public::from_raw(hex!( + Public::from_raw(array_bytes::hex2array_unchecked( "741c08a06f41c596608f6774259bd9043304adfa5d3eea62760bd9be97634d63" )) ); - let message = hex!("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000"); + let message = array_bytes::hex2bytes_unchecked("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000"); let signature = pair.sign(&message[..]); assert!(Pair::verify(&signature, &message[..], &public)); } @@ -886,11 +867,11 @@ mod test { // schnorrkel-js. // // This is to make sure that the wasm library is compatible. - let pk = Pair::from_seed(&hex!( - "0000000000000000000000000000000000000000000000000000000000000000" + let pk = Pair::from_seed(&array_bytes::hex2array_unchecked( + "0000000000000000000000000000000000000000000000000000000000000000", )); let public = pk.public(); - let js_signature = Signature::from_raw(hex!( + let js_signature = Signature::from_raw(array_bytes::hex2array_unchecked( "28a854d54903e056f89581c691c1f7d2ff39f8f896c9e9c22475e60902cc2b3547199e0e91fa32902028f2ca2355e8cdd16cfe19ba5e8b658c94aa80f3b81a00" )); assert!(Pair::verify_deprecated(&js_signature, b"SUBSTRATE", &public)); diff --git a/primitives/core/src/testing.rs b/primitives/core/src/testing.rs index a7fff0def83f2..d3fa3fc86fce5 100644 --- a/primitives/core/src/testing.rs +++ b/primitives/core/src/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -90,7 +90,7 @@ macro_rules! wasm_export_functions { &mut &input[..], ).expect("Input data is correctly encoded"); - $( $fn_impl )* + (|| { $( $fn_impl )* })() } $crate::to_substrate_wasm_fn_return_value(&()) @@ -118,7 +118,7 @@ macro_rules! wasm_export_functions { &mut &input[..], ).expect("Input data is correctly encoded"); - $( $fn_impl )* + (|| { $( $fn_impl )* })() }; $crate::to_substrate_wasm_fn_return_value(&output) @@ -152,10 +152,20 @@ impl Default for TaskExecutor { #[cfg(feature = "std")] impl crate::traits::SpawnNamed for TaskExecutor { - fn spawn_blocking(&self, _: &'static str, future: futures::future::BoxFuture<'static, ()>) { + fn spawn_blocking( + &self, + _name: &'static str, + _group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { self.0.spawn_ok(future); } - fn spawn(&self, _: &'static str, future: futures::future::BoxFuture<'static, ()>) { + fn spawn( + &self, + _name: &'static str, + _group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { self.0.spawn_ok(future); } } @@ -165,11 +175,17 @@ impl crate::traits::SpawnEssentialNamed for TaskExecutor { fn spawn_essential_blocking( &self, _: &'static str, + _: Option<&'static str>, future: futures::future::BoxFuture<'static, ()>, ) { self.0.spawn_ok(future); } - fn spawn_essential(&self, _: &'static str, future: futures::future::BoxFuture<'static, ()>) { + fn spawn_essential( + &self, + _: &'static str, + _: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { self.0.spawn_ok(future); } } diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index 47639f9d87ba6..c4b7f20f7e9a0 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ use std::{ borrow::Cow, fmt::{Debug, Display}, - panic::UnwindSafe, }; pub use sp_externalities::{Externalities, ExternalitiesExt}; @@ -32,18 +31,14 @@ pub trait CodeExecutor: Sized + Send + Sync + ReadRuntimeVersion + Clone + 'stat /// Call a given method in the runtime. Returns a tuple of the result (either the output data /// or an execution error) together with a `bool`, which is true if native execution was used. - fn call< - R: codec::Codec + PartialEq, - NC: FnOnce() -> Result> + UnwindSafe, - >( + fn call( &self, ext: &mut dyn Externalities, runtime_code: &RuntimeCode, method: &str, data: &[u8], use_native: bool, - native_call: Option, - ) -> (Result, Self::Error>, bool); + ) -> (Result, Self::Error>, bool); } /// Something that can fetch the runtime `:code`. @@ -184,64 +179,91 @@ pub trait RuntimeSpawn: Send { fn join(&self, handle: u64) -> Vec; } -#[cfg(feature = "std")] -sp_externalities::decl_extension! { - /// Extension that supports spawning extra runtime instances in externalities. - pub struct RuntimeSpawnExt(Box); -} - -/// Something that can spawn tasks (blocking and non-blocking) with an assigned name. +/// Something that can spawn tasks (blocking and non-blocking) with an assigned name +/// and optional group. #[dyn_clonable::clonable] pub trait SpawnNamed: Clone + Send + Sync { /// Spawn the given blocking future. /// - /// The given `name` is used to identify the future in tracing. - fn spawn_blocking(&self, name: &'static str, future: futures::future::BoxFuture<'static, ()>); + /// The given `group` and `name` is used to identify the future in tracing. + fn spawn_blocking( + &self, + name: &'static str, + group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ); /// Spawn the given non-blocking future. /// - /// The given `name` is used to identify the future in tracing. - fn spawn(&self, name: &'static str, future: futures::future::BoxFuture<'static, ()>); + /// The given `group` and `name` is used to identify the future in tracing. + fn spawn( + &self, + name: &'static str, + group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ); } impl SpawnNamed for Box { - fn spawn_blocking(&self, name: &'static str, future: futures::future::BoxFuture<'static, ()>) { - (**self).spawn_blocking(name, future) + fn spawn_blocking( + &self, + name: &'static str, + group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { + (**self).spawn_blocking(name, group, future) } - - fn spawn(&self, name: &'static str, future: futures::future::BoxFuture<'static, ()>) { - (**self).spawn(name, future) + fn spawn( + &self, + name: &'static str, + group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { + (**self).spawn(name, group, future) } } -/// Something that can spawn essential tasks (blocking and non-blocking) with an assigned name. +/// Something that can spawn essential tasks (blocking and non-blocking) with an assigned name +/// and optional group. /// /// Essential tasks are special tasks that should take down the node when they end. #[dyn_clonable::clonable] pub trait SpawnEssentialNamed: Clone + Send + Sync { /// Spawn the given blocking future. /// - /// The given `name` is used to identify the future in tracing. + /// The given `group` and `name` is used to identify the future in tracing. fn spawn_essential_blocking( &self, name: &'static str, + group: Option<&'static str>, future: futures::future::BoxFuture<'static, ()>, ); /// Spawn the given non-blocking future. /// - /// The given `name` is used to identify the future in tracing. - fn spawn_essential(&self, name: &'static str, future: futures::future::BoxFuture<'static, ()>); + /// The given `group` and `name` is used to identify the future in tracing. + fn spawn_essential( + &self, + name: &'static str, + group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ); } impl SpawnEssentialNamed for Box { fn spawn_essential_blocking( &self, name: &'static str, + group: Option<&'static str>, future: futures::future::BoxFuture<'static, ()>, ) { - (**self).spawn_essential_blocking(name, future) + (**self).spawn_essential_blocking(name, group, future) } - fn spawn_essential(&self, name: &'static str, future: futures::future::BoxFuture<'static, ()>) { - (**self).spawn_essential(name, future) + fn spawn_essential( + &self, + name: &'static str, + group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { + (**self).spawn_essential(name, group, future) } } diff --git a/primitives/core/src/u32_trait.rs b/primitives/core/src/u32_trait.rs deleted file mode 100644 index 37837e7c0548c..0000000000000 --- a/primitives/core/src/u32_trait.rs +++ /dev/null @@ -1,570 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! An u32 trait with "values" as impl'd types. - -/// A u32 value, wrapped in a trait because we don't yet have const generics. -pub trait Value { - /// The actual value represented by the impl'ing type. - const VALUE: u32; -} - -/// Type representing the value 0 for the `Value` trait. -pub struct _0; -impl Value for _0 { - const VALUE: u32 = 0; -} -/// Type representing the value 1 for the `Value` trait. -pub struct _1; -impl Value for _1 { - const VALUE: u32 = 1; -} -/// Type representing the value 2 for the `Value` trait. -pub struct _2; -impl Value for _2 { - const VALUE: u32 = 2; -} -/// Type representing the value 3 for the `Value` trait. -pub struct _3; -impl Value for _3 { - const VALUE: u32 = 3; -} -/// Type representing the value 4 for the `Value` trait. -pub struct _4; -impl Value for _4 { - const VALUE: u32 = 4; -} -/// Type representing the value 5 for the `Value` trait. -pub struct _5; -impl Value for _5 { - const VALUE: u32 = 5; -} -/// Type representing the value 6 for the `Value` trait. -pub struct _6; -impl Value for _6 { - const VALUE: u32 = 6; -} -/// Type representing the value 7 for the `Value` trait. -pub struct _7; -impl Value for _7 { - const VALUE: u32 = 7; -} -/// Type representing the value 8 for the `Value` trait. -pub struct _8; -impl Value for _8 { - const VALUE: u32 = 8; -} -/// Type representing the value 9 for the `Value` trait. -pub struct _9; -impl Value for _9 { - const VALUE: u32 = 9; -} -/// Type representing the value 10 for the `Value` trait. -pub struct _10; -impl Value for _10 { - const VALUE: u32 = 10; -} -/// Type representing the value 11 for the `Value` trait. -pub struct _11; -impl Value for _11 { - const VALUE: u32 = 11; -} -/// Type representing the value 12 for the `Value` trait. -pub struct _12; -impl Value for _12 { - const VALUE: u32 = 12; -} -/// Type representing the value 13 for the `Value` trait. -pub struct _13; -impl Value for _13 { - const VALUE: u32 = 13; -} -/// Type representing the value 14 for the `Value` trait. -pub struct _14; -impl Value for _14 { - const VALUE: u32 = 14; -} -/// Type representing the value 15 for the `Value` trait. -pub struct _15; -impl Value for _15 { - const VALUE: u32 = 15; -} -/// Type representing the value 16 for the `Value` trait. -pub struct _16; -impl Value for _16 { - const VALUE: u32 = 16; -} -/// Type representing the value 17 for the `Value` trait. -pub struct _17; -impl Value for _17 { - const VALUE: u32 = 17; -} -/// Type representing the value 18 for the `Value` trait. -pub struct _18; -impl Value for _18 { - const VALUE: u32 = 18; -} -/// Type representing the value 19 for the `Value` trait. -pub struct _19; -impl Value for _19 { - const VALUE: u32 = 19; -} -/// Type representing the value 20 for the `Value` trait. -pub struct _20; -impl Value for _20 { - const VALUE: u32 = 20; -} -/// Type representing the value 21 for the `Value` trait. -pub struct _21; -impl Value for _21 { - const VALUE: u32 = 21; -} -/// Type representing the value 22 for the `Value` trait. -pub struct _22; -impl Value for _22 { - const VALUE: u32 = 22; -} -/// Type representing the value 23 for the `Value` trait. -pub struct _23; -impl Value for _23 { - const VALUE: u32 = 23; -} -/// Type representing the value 24 for the `Value` trait. -pub struct _24; -impl Value for _24 { - const VALUE: u32 = 24; -} -/// Type representing the value 25 for the `Value` trait. -pub struct _25; -impl Value for _25 { - const VALUE: u32 = 25; -} -/// Type representing the value 26 for the `Value` trait. -pub struct _26; -impl Value for _26 { - const VALUE: u32 = 26; -} -/// Type representing the value 27 for the `Value` trait. -pub struct _27; -impl Value for _27 { - const VALUE: u32 = 27; -} -/// Type representing the value 28 for the `Value` trait. -pub struct _28; -impl Value for _28 { - const VALUE: u32 = 28; -} -/// Type representing the value 29 for the `Value` trait. -pub struct _29; -impl Value for _29 { - const VALUE: u32 = 29; -} -/// Type representing the value 30 for the `Value` trait. -pub struct _30; -impl Value for _30 { - const VALUE: u32 = 30; -} -/// Type representing the value 31 for the `Value` trait. -pub struct _31; -impl Value for _31 { - const VALUE: u32 = 31; -} -/// Type representing the value 32 for the `Value` trait. -pub struct _32; -impl Value for _32 { - const VALUE: u32 = 32; -} -/// Type representing the value 33 for the `Value` trait. -pub struct _33; -impl Value for _33 { - const VALUE: u32 = 33; -} -/// Type representing the value 34 for the `Value` trait. -pub struct _34; -impl Value for _34 { - const VALUE: u32 = 34; -} -/// Type representing the value 35 for the `Value` trait. -pub struct _35; -impl Value for _35 { - const VALUE: u32 = 35; -} -/// Type representing the value 36 for the `Value` trait. -pub struct _36; -impl Value for _36 { - const VALUE: u32 = 36; -} -/// Type representing the value 37 for the `Value` trait. -pub struct _37; -impl Value for _37 { - const VALUE: u32 = 37; -} -/// Type representing the value 38 for the `Value` trait. -pub struct _38; -impl Value for _38 { - const VALUE: u32 = 38; -} -/// Type representing the value 39 for the `Value` trait. -pub struct _39; -impl Value for _39 { - const VALUE: u32 = 39; -} -/// Type representing the value 40 for the `Value` trait. -pub struct _40; -impl Value for _40 { - const VALUE: u32 = 40; -} -/// Type representing the value 41 for the `Value` trait. -pub struct _41; -impl Value for _41 { - const VALUE: u32 = 41; -} -/// Type representing the value 42 for the `Value` trait. -pub struct _42; -impl Value for _42 { - const VALUE: u32 = 42; -} -/// Type representing the value 43 for the `Value` trait. -pub struct _43; -impl Value for _43 { - const VALUE: u32 = 43; -} -/// Type representing the value 44 for the `Value` trait. -pub struct _44; -impl Value for _44 { - const VALUE: u32 = 44; -} -/// Type representing the value 45 for the `Value` trait. -pub struct _45; -impl Value for _45 { - const VALUE: u32 = 45; -} -/// Type representing the value 46 for the `Value` trait. -pub struct _46; -impl Value for _46 { - const VALUE: u32 = 46; -} -/// Type representing the value 47 for the `Value` trait. -pub struct _47; -impl Value for _47 { - const VALUE: u32 = 47; -} -/// Type representing the value 48 for the `Value` trait. -pub struct _48; -impl Value for _48 { - const VALUE: u32 = 48; -} -/// Type representing the value 49 for the `Value` trait. -pub struct _49; -impl Value for _49 { - const VALUE: u32 = 49; -} -/// Type representing the value 50 for the `Value` trait. -pub struct _50; -impl Value for _50 { - const VALUE: u32 = 50; -} -/// Type representing the value 51 for the `Value` trait. -pub struct _51; -impl Value for _51 { - const VALUE: u32 = 51; -} -/// Type representing the value 52 for the `Value` trait. -pub struct _52; -impl Value for _52 { - const VALUE: u32 = 52; -} -/// Type representing the value 53 for the `Value` trait. -pub struct _53; -impl Value for _53 { - const VALUE: u32 = 53; -} -/// Type representing the value 54 for the `Value` trait. -pub struct _54; -impl Value for _54 { - const VALUE: u32 = 54; -} -/// Type representing the value 55 for the `Value` trait. -pub struct _55; -impl Value for _55 { - const VALUE: u32 = 55; -} -/// Type representing the value 56 for the `Value` trait. -pub struct _56; -impl Value for _56 { - const VALUE: u32 = 56; -} -/// Type representing the value 57 for the `Value` trait. -pub struct _57; -impl Value for _57 { - const VALUE: u32 = 57; -} -/// Type representing the value 58 for the `Value` trait. -pub struct _58; -impl Value for _58 { - const VALUE: u32 = 58; -} -/// Type representing the value 59 for the `Value` trait. -pub struct _59; -impl Value for _59 { - const VALUE: u32 = 59; -} -/// Type representing the value 60 for the `Value` trait. -pub struct _60; -impl Value for _60 { - const VALUE: u32 = 60; -} -/// Type representing the value 61 for the `Value` trait. -pub struct _61; -impl Value for _61 { - const VALUE: u32 = 61; -} -/// Type representing the value 62 for the `Value` trait. -pub struct _62; -impl Value for _62 { - const VALUE: u32 = 62; -} -/// Type representing the value 63 for the `Value` trait. -pub struct _63; -impl Value for _63 { - const VALUE: u32 = 63; -} -/// Type representing the value 64 for the `Value` trait. -pub struct _64; -impl Value for _64 { - const VALUE: u32 = 64; -} -/// Type representing the value 65 for the `Value` trait. -pub struct _65; -impl Value for _65 { - const VALUE: u32 = 65; -} -/// Type representing the value 66 for the `Value` trait. -pub struct _66; -impl Value for _66 { - const VALUE: u32 = 66; -} -/// Type representing the value 67 for the `Value` trait. -pub struct _67; -impl Value for _67 { - const VALUE: u32 = 67; -} -/// Type representing the value 68 for the `Value` trait. -pub struct _68; -impl Value for _68 { - const VALUE: u32 = 68; -} -/// Type representing the value 69 for the `Value` trait. -pub struct _69; -impl Value for _69 { - const VALUE: u32 = 69; -} -/// Type representing the value 70 for the `Value` trait. -pub struct _70; -impl Value for _70 { - const VALUE: u32 = 70; -} -/// Type representing the value 71 for the `Value` trait. -pub struct _71; -impl Value for _71 { - const VALUE: u32 = 71; -} -/// Type representing the value 72 for the `Value` trait. -pub struct _72; -impl Value for _72 { - const VALUE: u32 = 72; -} -/// Type representing the value 73 for the `Value` trait. -pub struct _73; -impl Value for _73 { - const VALUE: u32 = 73; -} -/// Type representing the value 74 for the `Value` trait. -pub struct _74; -impl Value for _74 { - const VALUE: u32 = 74; -} -/// Type representing the value 75 for the `Value` trait. -pub struct _75; -impl Value for _75 { - const VALUE: u32 = 75; -} -/// Type representing the value 76 for the `Value` trait. -pub struct _76; -impl Value for _76 { - const VALUE: u32 = 76; -} -/// Type representing the value 77 for the `Value` trait. -pub struct _77; -impl Value for _77 { - const VALUE: u32 = 77; -} -/// Type representing the value 78 for the `Value` trait. -pub struct _78; -impl Value for _78 { - const VALUE: u32 = 78; -} -/// Type representing the value 79 for the `Value` trait. -pub struct _79; -impl Value for _79 { - const VALUE: u32 = 79; -} -/// Type representing the value 80 for the `Value` trait. -pub struct _80; -impl Value for _80 { - const VALUE: u32 = 80; -} -/// Type representing the value 81 for the `Value` trait. -pub struct _81; -impl Value for _81 { - const VALUE: u32 = 81; -} -/// Type representing the value 82 for the `Value` trait. -pub struct _82; -impl Value for _82 { - const VALUE: u32 = 82; -} -/// Type representing the value 83 for the `Value` trait. -pub struct _83; -impl Value for _83 { - const VALUE: u32 = 83; -} -/// Type representing the value 84 for the `Value` trait. -pub struct _84; -impl Value for _84 { - const VALUE: u32 = 84; -} -/// Type representing the value 85 for the `Value` trait. -pub struct _85; -impl Value for _85 { - const VALUE: u32 = 85; -} -/// Type representing the value 86 for the `Value` trait. -pub struct _86; -impl Value for _86 { - const VALUE: u32 = 86; -} -/// Type representing the value 87 for the `Value` trait. -pub struct _87; -impl Value for _87 { - const VALUE: u32 = 87; -} -/// Type representing the value 88 for the `Value` trait. -pub struct _88; -impl Value for _88 { - const VALUE: u32 = 88; -} -/// Type representing the value 89 for the `Value` trait. -pub struct _89; -impl Value for _89 { - const VALUE: u32 = 89; -} -/// Type representing the value 90 for the `Value` trait. -pub struct _90; -impl Value for _90 { - const VALUE: u32 = 90; -} -/// Type representing the value 91 for the `Value` trait. -pub struct _91; -impl Value for _91 { - const VALUE: u32 = 91; -} -/// Type representing the value 92 for the `Value` trait. -pub struct _92; -impl Value for _92 { - const VALUE: u32 = 92; -} -/// Type representing the value 93 for the `Value` trait. -pub struct _93; -impl Value for _93 { - const VALUE: u32 = 93; -} -/// Type representing the value 94 for the `Value` trait. -pub struct _94; -impl Value for _94 { - const VALUE: u32 = 94; -} -/// Type representing the value 95 for the `Value` trait. -pub struct _95; -impl Value for _95 { - const VALUE: u32 = 95; -} -/// Type representing the value 96 for the `Value` trait. -pub struct _96; -impl Value for _96 { - const VALUE: u32 = 96; -} -/// Type representing the value 97 for the `Value` trait. -pub struct _97; -impl Value for _97 { - const VALUE: u32 = 97; -} -/// Type representing the value 98 for the `Value` trait. -pub struct _98; -impl Value for _98 { - const VALUE: u32 = 98; -} -/// Type representing the value 99 for the `Value` trait. -pub struct _99; -impl Value for _99 { - const VALUE: u32 = 99; -} -/// Type representing the value 100 for the `Value` trait. -pub struct _100; -impl Value for _100 { - const VALUE: u32 = 100; -} -/// Type representing the value 112 for the `Value` trait. -pub struct _112; -impl Value for _112 { - const VALUE: u32 = 112; -} -/// Type representing the value 128 for the `Value` trait. -pub struct _128; -impl Value for _128 { - const VALUE: u32 = 128; -} -/// Type representing the value 160 for the `Value` trait. -pub struct _160; -impl Value for _160 { - const VALUE: u32 = 160; -} -/// Type representing the value 192 for the `Value` trait. -pub struct _192; -impl Value for _192 { - const VALUE: u32 = 192; -} -/// Type representing the value 224 for the `Value` trait. -pub struct _224; -impl Value for _224 { - const VALUE: u32 = 224; -} -/// Type representing the value 256 for the `Value` trait. -pub struct _256; -impl Value for _256 { - const VALUE: u32 = 256; -} -/// Type representing the value 384 for the `Value` trait. -pub struct _384; -impl Value for _384 { - const VALUE: u32 = 384; -} -/// Type representing the value 512 for the `Value` trait. -pub struct _512; -impl Value for _512 { - const VALUE: u32 = 512; -} diff --git a/primitives/core/src/uint.rs b/primitives/core/src/uint.rs index a74980332ad28..4bf914bde2ee1 100644 --- a/primitives/core/src/uint.rs +++ b/primitives/core/src/uint.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,6 @@ mod tests { ($name::from(2), "0x2"), ($name::from(10), "0xa"), ($name::from(15), "0xf"), - ($name::from(15), "0xf"), ($name::from(16), "0x10"), ($name::from(1_000), "0x3e8"), ($name::from(100_000), "0x186a0"), @@ -52,8 +51,6 @@ mod tests { assert!(ser::from_str::<$name>("\"0x\"").unwrap_err().is_data()); assert!(ser::from_str::<$name>("\"0xg\"").unwrap_err().is_data()); assert!(ser::from_str::<$name>("\"\"").unwrap_err().is_data()); - assert!(ser::from_str::<$name>("\"10\"").unwrap_err().is_data()); - assert!(ser::from_str::<$name>("\"0\"").unwrap_err().is_data()); } }; } diff --git a/primitives/database/Cargo.toml b/primitives/database/Cargo.toml index c99651d4ef042..b1105f88ba50f 100644 --- a/primitives/database/Cargo.toml +++ b/primitives/database/Cargo.toml @@ -2,15 +2,14 @@ name = "sp-database" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate database trait." documentation = "https://docs.rs/sp-database" readme = "README.md" [dependencies] -parking_lot = "0.11.1" -kvdb = "0.10.0" - +kvdb = "0.13.0" +parking_lot = "0.12.1" diff --git a/primitives/database/src/error.rs b/primitives/database/src/error.rs index 4bf5a20aff401..78646427a2b5d 100644 --- a/primitives/database/src/error.rs +++ b/primitives/database/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/database/src/kvdb.rs b/primitives/database/src/kvdb.rs index 1a2b0513dc28a..5fe5fda307a1e 100644 --- a/primitives/database/src/kvdb.rs +++ b/primitives/database/src/kvdb.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/database/src/lib.rs b/primitives/database/src/lib.rs index d30c7eb3323e9..3ec62a2b78148 100644 --- a/primitives/database/src/lib.rs +++ b/primitives/database/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -110,6 +110,11 @@ pub trait Database>: Send + Sync { fn supports_ref_counting(&self) -> bool { false } + + /// Remove a possible path-prefix from the key. + /// + /// Not all database implementations use a prefix for keys, so this function may be a noop. + fn sanitize_key(&self, _key: &mut Vec) {} } impl std::fmt::Debug for dyn Database { diff --git a/primitives/database/src/mem.rs b/primitives/database/src/mem.rs index d1b1861e98fdd..6ecd44c6005b9 100644 --- a/primitives/database/src/mem.rs +++ b/primitives/database/src/mem.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/debug-derive/Cargo.toml b/primitives/debug-derive/Cargo.toml index 0d3ba805100c4..a903b704410c2 100644 --- a/primitives/debug-derive/Cargo.toml +++ b/primitives/debug-derive/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-debug-derive" -version = "3.0.0" +version = "5.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Macros to derive runtime debug implementation." documentation = "https://docs.rs/sp-debug-derive" @@ -17,11 +17,17 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.3" -syn = "1.0.58" +quote = "1.0.10" +syn = "1.0.98" proc-macro2 = "1.0" [features] +default = [ "std" ] std = [] +# By default `RuntimeDebug` implements `Debug` that outputs `` when `std` is +# disabled. However, sometimes downstream users need to have the real `Debug` implementation for +# debugging purposes. If this is required, a user only needs to add this crate as a dependency of +# their runtime and enable the `force-debug` feature. +force-debug = [] [dev-dependencies] diff --git a/primitives/debug-derive/src/impls.rs b/primitives/debug-derive/src/impls.rs index 4d79ee9880160..51a4d876c79b6 100644 --- a/primitives/debug-derive/src/impls.rs +++ b/primitives/debug-derive/src/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,7 +43,7 @@ pub fn debug_derive(ast: DeriveInput) -> proc_macro::TokenStream { gen.into() } -#[cfg(not(feature = "std"))] +#[cfg(all(not(feature = "std"), not(feature = "force-debug")))] mod implementation { use super::*; @@ -58,7 +58,7 @@ mod implementation { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "force-debug"))] mod implementation { use super::*; use proc_macro2::Span; @@ -67,10 +67,9 @@ mod implementation { /// Derive the inner implementation of `Debug::fmt` function. pub fn derive(name_str: &str, data: &Data) -> TokenStream { match *data { - Data::Struct(ref s) => derive_struct(&name_str, &s.fields), - Data::Union(ref u) => - derive_fields(&name_str, Fields::new(u.fields.named.iter(), None)), - Data::Enum(ref e) => derive_enum(&name_str, &e), + Data::Struct(ref s) => derive_struct(name_str, &s.fields), + Data::Union(ref u) => derive_fields(name_str, Fields::new(u.fields.named.iter(), None)), + Data::Enum(ref e) => derive_enum(name_str, e), } } @@ -101,7 +100,7 @@ mod implementation { } } - fn derive_fields<'a>(name_str: &str, fields: Fields) -> TokenStream { + fn derive_fields(name_str: &str, fields: Fields) -> TokenStream { match fields { Fields::Named { names, this } => { let names_str: Vec<_> = names.iter().map(|x| x.to_string()).collect(); diff --git a/primitives/debug-derive/src/lib.rs b/primitives/debug-derive/src/lib.rs index 7eaa3a0020e93..c98610ce47808 100644 --- a/primitives/debug-derive/src/lib.rs +++ b/primitives/debug-derive/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/debug-derive/tests/tests.rs b/primitives/debug-derive/tests/tests.rs index 4f4c7f4caabc2..39414da86bf45 100644 --- a/primitives/debug-derive/tests/tests.rs +++ b/primitives/debug-derive/tests/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/externalities/Cargo.toml b/primitives/externalities/Cargo.toml index 52a6300688cd9..c3d32370dc32f 100644 --- a/primitives/externalities/Cargo.toml +++ b/primitives/externalities/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-externalities" -version = "0.10.0-dev" +version = "0.13.0" license = "Apache-2.0" authors = ["Parity Technologies "] -edition = "2018" -homepage = "https://substrate.dev" +edition = "2021" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate externalities abstraction" documentation = "https://docs.rs/sp-externalities" @@ -14,10 +14,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-storage = { version = "4.0.0-dev", path = "../storage", default-features = false } -sp-std = { version = "4.0.0-dev", path = "../std", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } environmental = { version = "1.1.3", default-features = false } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-storage = { version = "7.0.0", default-features = false, path = "../storage" } [features] default = ["std"] diff --git a/primitives/externalities/src/extensions.rs b/primitives/externalities/src/extensions.rs index 37086a707b648..ecb489e5ec829 100644 --- a/primitives/externalities/src/extensions.rs +++ b/primitives/externalities/src/extensions.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -89,6 +89,19 @@ macro_rules! decl_extension { Self(inner) } } + }; + ( + $( #[ $attr:meta ] )* + $vis:vis struct $ext_name:ident; + ) => { + $( #[ $attr ] )* + $vis struct $ext_name; + + impl $crate::Extension for $ext_name { + fn as_mut_any(&mut self) -> &mut dyn std::any::Any { + self + } + } } } @@ -112,7 +125,7 @@ pub trait ExtensionStore { extension: Box, ) -> Result<(), Error>; - /// Deregister extension with speicifed 'type_id' and drop it. + /// Deregister extension with specified 'type_id' and drop it. /// /// It should return error if extension is not registered. fn deregister_extension_by_type_id(&mut self, type_id: TypeId) -> Result<(), Error>; @@ -174,13 +187,18 @@ impl Extensions { } /// Returns a mutable iterator over all extensions. - pub fn iter_mut<'a>( - &'a mut self, - ) -> impl Iterator)> { + pub fn iter_mut(&mut self) -> impl Iterator)> { self.extensions.iter_mut() } } +impl Extend for Extensions { + fn extend>(&mut self, iter: T) { + iter.into_iter() + .for_each(|ext| self.extensions.extend(ext.extensions.into_iter())); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/primitives/externalities/src/lib.rs b/primitives/externalities/src/lib.rs index e6a8f8caa8d33..b343b8c393e00 100644 --- a/primitives/externalities/src/lib.rs +++ b/primitives/externalities/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,7 +31,7 @@ use sp_std::{ vec::Vec, }; -use sp_storage::{ChildInfo, TrackedStorageKey}; +use sp_storage::{ChildInfo, StateVersion, TrackedStorageKey}; pub use extensions::{Extension, ExtensionStore, Extensions}; pub use scope_limited::{set_and_run_with_externalities, with_externalities}; @@ -52,6 +52,30 @@ pub enum Error { StorageUpdateFailed(&'static str), } +/// Results concerning an operation to remove many keys. +#[derive(codec::Encode, codec::Decode)] +#[must_use] +pub struct MultiRemovalResults { + /// A continuation cursor which, if `Some` must be provided to the subsequent removal call. + /// If `None` then all removals are complete and no further calls are needed. + pub maybe_cursor: Option>, + /// The number of items removed from the backend database. + pub backend: u32, + /// The number of unique keys removed, taking into account both the backend and the overlay. + pub unique: u32, + /// The number of iterations (each requiring a storage seek/read) which were done. + pub loops: u32, +} + +impl MultiRemovalResults { + /// Deconstruct into the internal components. + /// + /// Returns `(maybe_cursor, backend, unique, loops)`. + pub fn deconstruct(self) -> (Option>, u32, u32, u32) { + (self.maybe_cursor, self.backend, self.unique, self.loops) + } +} + /// The Substrate externalities. /// /// Provides access to the storage and to other registered extensions. @@ -118,32 +142,47 @@ pub trait Externalities: ExtensionStore { /// Clear an entire child storage. /// - /// Deletes all keys from the overlay and up to `limit` keys from the backend. No - /// limit is applied if `limit` is `None`. Returned boolean is `true` if the child trie was - /// removed completely and `false` if there are remaining keys after the function - /// returns. Returned `u32` is the number of keys that was removed at the end of the - /// operation. + /// Deletes all keys from the overlay and up to `maybe_limit` keys from the backend. No + /// limit is applied if `maybe_limit` is `None`. Returns the cursor for the next call as `Some` + /// if the child trie deletion operation is incomplete. In this case, it should be passed into + /// the next call to avoid unaccounted iterations on the backend. Returns also the the number + /// of keys that were removed from the backend, the number of unique keys removed in total + /// (including from the overlay) and the number of backend iterations done. + /// + /// As long as `maybe_cursor` is passed from the result of the previous call, then the number of + /// iterations done will only ever be one more than the number of keys removed. /// /// # Note /// /// An implementation is free to delete more keys than the specified limit as long as /// it is able to do that in constant time. - fn kill_child_storage(&mut self, child_info: &ChildInfo, limit: Option) -> (bool, u32); + fn kill_child_storage( + &mut self, + child_info: &ChildInfo, + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults; /// Clear storage entries which keys are start with the given prefix. /// - /// `limit` and result works as for `kill_child_storage`. - fn clear_prefix(&mut self, prefix: &[u8], limit: Option) -> (bool, u32); + /// `maybe_limit`, `maybe_cursor` and result works as for `kill_child_storage`. + fn clear_prefix( + &mut self, + prefix: &[u8], + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults; /// Clear child storage entries which keys are start with the given prefix. /// - /// `limit` and result works as for `kill_child_storage`. + /// `maybe_limit`, `maybe_cursor` and result works as for `kill_child_storage`. fn clear_child_prefix( &mut self, child_info: &ChildInfo, prefix: &[u8], - limit: Option, - ) -> (bool, u32); + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults; /// Set or clear a storage entry (`key`) of current contract being called (effective /// immediately). @@ -157,7 +196,7 @@ pub trait Externalities: ExtensionStore { /// This will also update all child storage keys in the top-level storage map. /// /// The returned hash is defined by the `Block` and is SCALE encoded. - fn storage_root(&mut self) -> Vec; + fn storage_root(&mut self, state_version: StateVersion) -> Vec; /// Get the trie root of a child storage map. /// @@ -165,7 +204,11 @@ pub trait Externalities: ExtensionStore { /// /// If the storage root equals the default hash as defined by the trie, the key in the top-level /// storage map will be removed. - fn child_storage_root(&mut self, child_info: &ChildInfo) -> Vec; + fn child_storage_root( + &mut self, + child_info: &ChildInfo, + state_version: StateVersion, + ) -> Vec; /// Append storage item. /// @@ -173,13 +216,6 @@ pub trait Externalities: ExtensionStore { /// operation. fn storage_append(&mut self, key: Vec, value: Vec); - /// Get the changes trie root of the current storage overlay at a block with given `parent`. - /// - /// `parent` expects a SCALE encoded hash. - /// - /// The returned hash is defined by the `Block` and is SCALE encoded. - fn storage_changes_root(&mut self, parent: &[u8]) -> Result>, ()>; - /// Start a new nested transaction. /// /// This allows to either commit or roll back all changes made after this call to the diff --git a/primitives/externalities/src/scope_limited.rs b/primitives/externalities/src/scope_limited.rs index 15a670a9abeee..1d2e6863c9e58 100644 --- a/primitives/externalities/src/scope_limited.rs +++ b/primitives/externalities/src/scope_limited.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index c0c2a654270f7..1c8011ff764e3 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-finality-grandpa" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Primitives for GRANDPA integration, suitable for WASM compilation." documentation = "https://docs.rs/sp-finality-grandpa" @@ -13,28 +13,27 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -grandpa = { package = "finality-grandpa", version = "0.14.1", default-features = false, features = ["derive-codec"] } -log = { version = "0.4.8", optional = true } -serde = { version = "1.0.126", optional = true, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +grandpa = { package = "finality-grandpa", version = "0.16.0", default-features = false, features = ["derive-codec"] } +log = { version = "0.4.17", optional = true } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../application-crypto" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -sp-keystore = { version = "0.10.0-dev", default-features = false, path = "../keystore", optional = true } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-keystore = { version = "0.13.0", default-features = false, optional = true, path = "../keystore" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = ["std"] std = [ - "log", - "serde", "codec/std", - "scale-info/std", "grandpa/std", + "log", + "scale-info/std", + "serde", "sp-api/std", "sp-application-crypto/std", "sp-core/std", diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index d99a4c1882222..f92fee4f12963 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,10 @@ use codec::{Codec, Decode, Encode, Input}; use scale_info::TypeInfo; #[cfg(feature = "std")] use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; -use sp_runtime::{traits::NumberFor, ConsensusEngineId, RuntimeDebug}; +use sp_runtime::{ + traits::{Header as HeaderT, NumberFor}, + ConsensusEngineId, RuntimeDebug, +}; use sp_std::{borrow::Cow, vec::Vec}; #[cfg(feature = "std")] @@ -59,7 +62,7 @@ pub const GRANDPA_ENGINE_ID: ConsensusEngineId = *b"FRNK"; /// The storage key for the current set of weighted Grandpa authorities. /// The value stored is an encoded VersionedAuthorityList. -pub const GRANDPA_AUTHORITIES_KEY: &'static [u8] = b":grandpa_authorities"; +pub const GRANDPA_AUTHORITIES_KEY: &[u8] = b":grandpa_authorities"; /// The weight of an authority. pub type AuthorityWeight = u64; @@ -76,9 +79,67 @@ pub type RoundNumber = u64; /// A list of Grandpa authorities with associated weights. pub type AuthorityList = Vec<(AuthorityId, AuthorityWeight)>; +/// A GRANDPA message for a substrate chain. +pub type Message

= grandpa::Message<
::Hash,
::Number>; + +/// A signed message. +pub type SignedMessage
= grandpa::SignedMessage< +
::Hash, +
::Number, + AuthoritySignature, + AuthorityId, +>; + +/// A primary propose message for this chain's block type. +pub type PrimaryPropose
= + grandpa::PrimaryPropose<
::Hash,
::Number>; +/// A prevote message for this chain's block type. +pub type Prevote
= grandpa::Prevote<
::Hash,
::Number>; +/// A precommit message for this chain's block type. +pub type Precommit
= + grandpa::Precommit<
::Hash,
::Number>; +/// A catch up message for this chain's block type. +pub type CatchUp
= grandpa::CatchUp< +
::Hash, +
::Number, + AuthoritySignature, + AuthorityId, +>; +/// A commit message for this chain's block type. +pub type Commit
= grandpa::Commit< +
::Hash, +
::Number, + AuthoritySignature, + AuthorityId, +>; + +/// A compact commit message for this chain's block type. +pub type CompactCommit
= grandpa::CompactCommit< +
::Hash, +
::Number, + AuthoritySignature, + AuthorityId, +>; + +/// A GRANDPA justification for block finality, it includes a commit message and +/// an ancestry proof including all headers routing all precommit target blocks +/// to the commit target block. Due to the current voting strategy the precommit +/// targets should be the same as the commit target, since honest voters don't +/// vote past authority set change blocks. +/// +/// This is meant to be stored in the db and passed around the network to other +/// nodes, and are used by syncing nodes to prove authority set handoffs. +#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct GrandpaJustification { + pub round: u64, + pub commit: Commit
, + pub votes_ancestries: Vec
, +} + /// A scheduled change of authority set. #[cfg_attr(feature = "std", derive(Serialize))] -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ScheduledChange { /// The new authorities after the change, along with their respective weights. pub next_authorities: AuthorityList, @@ -389,7 +450,6 @@ where { use sp_application_crypto::AppKey; use sp_core::crypto::Public; - use sp_std::convert::TryInto; let encoded = localized_payload(round, set_id, &message); let signature = SyncCryptoStore::sign_with( diff --git a/primitives/inherents/Cargo.toml b/primitives/inherents/Cargo.toml index 23558750b5cf8..8f6d8aef155ac 100644 --- a/primitives/inherents/Cargo.toml +++ b/primitives/inherents/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-inherents" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Provides types and traits for creating and checking inherents." documentation = "https://docs.rs/sp-inherents" @@ -13,26 +13,25 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -sp-runtime = { version = "4.0.0-dev", path = "../runtime", optional = true } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.21", optional = true } -impl-trait-for-tuples = "0.2.0" -async-trait = { version = "0.1.50", optional = true } +async-trait = { version = "0.1.57", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +impl-trait-for-tuples = "0.2.2" +thiserror = { version = "1.0.30", optional = true } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "7.0.0", optional = true, default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [dev-dependencies] -futures = "0.3.9" +futures = "0.3.21" [features] default = [ "std" ] std = [ - "sp-std/std", + "async-trait", "codec/std", "sp-core/std", + "sp-runtime/std", + "sp-std/std", "thiserror", - "sp-runtime", - "async-trait", ] diff --git a/primitives/inherents/src/client_side.rs b/primitives/inherents/src/client_side.rs index 18877cae5f343..1ece7e1e4dea3 100644 --- a/primitives/inherents/src/client_side.rs +++ b/primitives/inherents/src/client_side.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -83,16 +83,16 @@ pub trait InherentDataProvider: Send + Sync { /// Convenience function for creating [`InherentData`]. /// /// Basically maps around [`Self::provide_inherent_data`]. - fn create_inherent_data(&self) -> Result { + async fn create_inherent_data(&self) -> Result { let mut inherent_data = InherentData::new(); - self.provide_inherent_data(&mut inherent_data)?; + self.provide_inherent_data(&mut inherent_data).await?; Ok(inherent_data) } /// Provide inherent data that should be included in a block. /// /// The data should be stored in the given `InherentData` structure. - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error>; + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error>; /// Convert the given encoded error to a string. /// @@ -108,8 +108,8 @@ pub trait InherentDataProvider: Send + Sync { #[async_trait::async_trait] impl InherentDataProvider for Tuple { for_tuples!( where #( Tuple: Send + Sync )* ); - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { - for_tuples!( #( Tuple.provide_inherent_data(inherent_data)?; )* ); + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + for_tuples!( #( Tuple.provide_inherent_data(inherent_data).await?; )* ); Ok(()) } diff --git a/primitives/inherents/src/lib.rs b/primitives/inherents/src/lib.rs index 90f4e455a42d3..59db5ef3e8802 100644 --- a/primitives/inherents/src/lib.rs +++ b/primitives/inherents/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,14 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Substrate inherent extrinsics +//! Substrate Inherent Extrinsics //! //! Inherent extrinsics are extrinsics that are inherently added to each block. However, it is up to -//! runtime implementation to require an inherent for each block or to make it optional. Inherents -//! are mainly used to pass data from the block producer to the runtime. So, inherents require some -//! part that is running on the client side and some part that is running on the runtime side. Any -//! data that is required by an inherent is passed as [`InherentData`] from the client to the -//! runtime when the inherents are constructed. +//! the runtime implementation to require an inherent for each block or to make it optional. +//! Inherents are mainly used to pass data from the block producer to the runtime. So, inherents +//! require some part that is running on the client side and some part that is running on the +//! runtime side. Any data that is required by an inherent is passed as [`InherentData`] from the +//! client to the runtime when the inherents are constructed. //! //! The process of constructing and applying inherents is the following: //! @@ -56,7 +56,7 @@ //! //! #[async_trait::async_trait] //! impl sp_inherents::InherentDataProvider for InherentDataProvider { -//! fn provide_inherent_data( +//! async fn provide_inherent_data( //! &self, //! inherent_data: &mut InherentData, //! ) -> Result<(), sp_inherents::Error> { @@ -106,7 +106,7 @@ //! # struct InherentDataProvider; //! # #[async_trait::async_trait] //! # impl sp_inherents::InherentDataProvider for InherentDataProvider { -//! # fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), sp_inherents::Error> { +//! # async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), sp_inherents::Error> { //! # inherent_data.put_data(INHERENT_IDENTIFIER, &"hello") //! # } //! # async fn try_handle_error( @@ -443,7 +443,7 @@ mod tests { #[async_trait::async_trait] impl InherentDataProvider for TestInherentDataProvider { - fn provide_inherent_data(&self, data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data(&self, data: &mut InherentData) -> Result<(), Error> { data.put_data(TEST_INHERENT_0, &42) } @@ -460,7 +460,7 @@ mod tests { fn create_inherent_data() { let provider = TestInherentDataProvider; - let inherent_data = provider.create_inherent_data().unwrap(); + let inherent_data = futures::executor::block_on(provider.create_inherent_data()).unwrap(); assert_eq!(inherent_data.get_data::(&TEST_INHERENT_0).unwrap().unwrap(), 42u32); } diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index d3a2b56705926..cd900b8f158ef 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-io" -version = "4.0.0-dev" +version = "7.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "I/O for Substrate runtimes" documentation = "https://docs.rs/sp-io" @@ -15,46 +15,50 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } +bytes = { version = "1.1.0", default-features = false } +codec = { package = "parity-scale-codec", version = "3.1.3", default-features = false, features = ["bytes"] } hash-db = { version = "0.15.2", default-features = false } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -sp-keystore = { version = "0.10.0-dev", default-features = false, optional = true, path = "../keystore" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -libsecp256k1 = { version = "0.6", optional = true } -sp-state-machine = { version = "0.10.0-dev", optional = true, path = "../state-machine" } -sp-wasm-interface = { version = "4.0.0-dev", path = "../wasm-interface", default-features = false } -sp-runtime-interface = { version = "4.0.0-dev", default-features = false, path = "../runtime-interface" } -sp-maybe-compressed-blob = { version = "4.0.0-dev", optional = true, path = "../maybe-compressed-blob" } -sp-trie = { version = "4.0.0-dev", optional = true, path = "../trie" } -sp-externalities = { version = "0.10.0-dev", optional = true, path = "../externalities" } -sp-tracing = { version = "4.0.0-dev", default-features = false, path = "../tracing" } -log = { version = "0.4.8", optional = true } -futures = { version = "0.3.1", features = ["thread-pool"], optional = true } -parking_lot = { version = "0.11.1", optional = true } -tracing = { version = "0.1.25", default-features = false } -tracing-core = { version = "0.1.17", default-features = false} +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-keystore = { version = "0.13.0", default-features = false, optional = true, path = "../keystore" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +libsecp256k1 = { version = "0.7", optional = true } +sp-state-machine = { version = "0.13.0", default-features = false, optional = true, path = "../state-machine" } +sp-wasm-interface = { version = "7.0.0", path = "../wasm-interface", default-features = false } +sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../runtime-interface" } +sp-trie = { version = "7.0.0", default-features = false, optional = true, path = "../trie" } +sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } +sp-tracing = { version = "6.0.0", default-features = false, path = "../tracing" } +log = { version = "0.4.17", optional = true } +futures = { version = "0.3.21", features = ["thread-pool"], optional = true } +parking_lot = { version = "0.12.1", optional = true } +secp256k1 = { version = "0.24.0", features = ["recovery", "global-context"], optional = true } +tracing = { version = "0.1.29", default-features = false } +tracing-core = { version = "0.1.28", default-features = false} +ed25519-dalek = { version = "1.0.1", default-features = false, optional = true } [features] default = ["std"] std = [ + "bytes/std", + "sp-externalities/std", "sp-core/std", "sp-keystore", "codec/std", "sp-std/std", "hash-db/std", - "sp-trie", - "sp-state-machine", + "sp-trie/std", + "sp-state-machine/std", "libsecp256k1", + "secp256k1", "sp-runtime-interface/std", - "sp-externalities", "sp-wasm-interface/std", - "sp-maybe-compressed-blob", "sp-tracing/std", "tracing/std", "tracing-core/std", "log", "futures", "parking_lot", + "ed25519-dalek", ] with-tracing = [ @@ -68,3 +72,22 @@ with-tracing = [ disable_panic_handler = [] disable_oom = [] disable_allocator = [] + +# This feature flag controls the runtime's behavior when encountering +# a panic or when it runs out of memory, improving the diagnostics. +# +# When enabled the runtime will marshal the relevant error message +# to the host through the `PanicHandler::abort_on_panic` runtime interface. +# This gives the caller direct programmatic access to the error message. +# +# When disabled the error message will only be printed out in the +# logs, with the caller receving a generic "wasm `unreachable` instruction executed" +# error message. +# +# This has no effect if both `disable_panic_handler` and `disable_oom` +# are enabled. +# +# WARNING: Enabling this feature flag requires the `PanicHandler::abort_on_panic` +# host function to be supported by the host. Do *not* enable it for your +# runtime without first upgrading your host client! +improved_panic_error_reporting = [] diff --git a/primitives/io/src/batch_verifier.rs b/primitives/io/src/batch_verifier.rs index b6da1d85907bd..501d986758bf2 100644 --- a/primitives/io/src/batch_verifier.rs +++ b/primitives/io/src/batch_verifier.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -74,6 +74,7 @@ impl BatchVerifier { self.scheduler.spawn( name, + None, async move { if !f() { invalid_clone.store(true, AtomicOrdering::Relaxed); @@ -177,7 +178,8 @@ impl BatchVerifier { if pending.len() > 0 { let (sender, receiver) = std::sync::mpsc::channel(); self.scheduler.spawn( - "substrate_batch_verify_join", + "substrate-batch-verify-join", + None, async move { futures::future::join_all(pending).await; sender.send(()).expect( diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 5faeb59c72db6..600d76b3b4300 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,7 +40,7 @@ use sp_core::{ hexdisplay::HexDisplay, offchain::{OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}, storage::ChildInfo, - traits::{RuntimeSpawnExt, TaskExecutorExt}, + traits::TaskExecutorExt, }; #[cfg(feature = "std")] use sp_keystore::{KeystoreExt, SyncCryptoStore}; @@ -51,11 +51,13 @@ use sp_core::{ offchain::{ HttpError, HttpRequestId, HttpRequestStatus, OpaqueNetworkState, StorageKind, Timestamp, }, - sr25519, LogLevel, LogLevelFilter, OpaquePeerId, H256, + sr25519, + storage::StateVersion, + LogLevel, LogLevelFilter, OpaquePeerId, H256, }; #[cfg(feature = "std")] -use sp_trie::{trie_types::Layout, TrieConfiguration}; +use sp_trie::{LayoutV0, LayoutV1, TrieConfiguration}; use sp_runtime_interface::{ pass_by::{PassBy, PassByCodec}, @@ -64,6 +66,12 @@ use sp_runtime_interface::{ use codec::{Decode, Encode}; +#[cfg(feature = "std")] +use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Message, SECP256K1, +}; + #[cfg(feature = "std")] use sp_externalities::{Externalities, ExternalitiesExt}; @@ -73,6 +81,9 @@ mod batch_verifier; #[cfg(feature = "std")] use batch_verifier::BatchVerifier; +pub use sp_externalities::MultiRemovalResults; + +#[cfg(feature = "std")] const LOG_TARGET: &str = "runtime::io"; /// Error verifying ECDSA signature @@ -87,21 +98,35 @@ pub enum EcdsaVerifyError { } /// The outcome of calling `storage_kill`. Returned value is the number of storage items -/// removed from the trie from making the `storage_kill` call. +/// removed from the backend from making the `storage_kill` call. #[derive(PassByCodec, Encode, Decode)] pub enum KillStorageResult { - /// No key remains in the child trie. + /// All keys to remove were removed, return number of iterations performed during the + /// operation. AllRemoved(u32), - /// At least one key still resides in the child trie due to the supplied limit. + /// Not all key to remove were removed, return number of iterations performed during the + /// operation. SomeRemaining(u32), } +impl From for KillStorageResult { + fn from(r: MultiRemovalResults) -> Self { + // We use `loops` here rather than `backend` because that's the same as the original + // functionality pre-#11490. This won't matter once we switch to the new host function + // since we won't be using the `KillStorageResult` type in the runtime any more. + match r.maybe_cursor { + None => Self::AllRemoved(r.loops), + Some(..) => Self::SomeRemaining(r.loops), + } + } +} + /// Interface for accessing the storage from within the runtime. #[runtime_interface] 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| s.to_vec()) + fn get(&self, key: &[u8]) -> Option { + self.storage(key).map(|s| bytes::Bytes::from(s.to_vec())) } /// Get `key` from storage, placing the value into `value_out` and return the number of @@ -136,7 +161,7 @@ pub trait Storage { /// Clear the storage of each key-value pair where the key starts with the given `prefix`. fn clear_prefix(&mut self, prefix: &[u8]) { - let _ = Externalities::clear_prefix(*self, prefix, None); + let _ = Externalities::clear_prefix(*self, prefix, None, None); } /// Clear the storage of each key-value pair where the key starts with the given `prefix`. @@ -149,9 +174,7 @@ pub trait Storage { /// The limit can be used to partially delete a prefix storage in case it is too large /// to delete in one go (block). /// - /// It returns a boolean false iff some keys are remaining in - /// the prefix after the functions returns. Also returns a `u32` with - /// the number of keys removed from the process. + /// Returns [`KillStorageResult`] to inform about the result. /// /// # Note /// @@ -162,15 +185,60 @@ pub trait Storage { /// /// Calling this function multiple times per block for the same `prefix` does /// not make much sense because it is not cumulative when called inside the same block. - /// Use this function to distribute the deletion of a single child trie across multiple - /// blocks. + /// The deletion would always start from `prefix` resulting in the same keys being deleted + /// every time this function is called with the exact same arguments per block. This happens + /// because the keys in the overlay are not taken into account when deleting keys in the + /// backend. #[version(2)] fn clear_prefix(&mut self, prefix: &[u8], limit: Option) -> KillStorageResult { - let (all_removed, num_removed) = Externalities::clear_prefix(*self, prefix, limit); - match all_removed { - true => KillStorageResult::AllRemoved(num_removed), - false => KillStorageResult::SomeRemaining(num_removed), - } + Externalities::clear_prefix(*self, prefix, limit, None).into() + } + + /// Partially clear the storage of each key-value pair where the key starts with the given + /// prefix. + /// + /// # Limit + /// + /// A *limit* should always be provided through `maybe_limit`. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A *limit* of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// The limit can be used to partially delete a prefix storage in case it is too large or costly + /// to delete in a single operation. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given `maybe_prefix` value. Subsequent calls + /// operating on the same prefix should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given prefix, it is important that no keys further + /// keys under the same prefix are inserted. If so, then they may or may not be deleted by + /// subsequent calls. + /// + /// # Note + /// + /// Please note that keys which are residing in the overlay for that prefix when + /// issuing this call are deleted without counting towards the `limit`. + #[version(3, register_only)] + fn clear_prefix( + &mut self, + maybe_prefix: &[u8], + maybe_limit: Option, + maybe_cursor: Option>, //< TODO Make work or just Option>? + ) -> MultiRemovalResults { + Externalities::clear_prefix( + *self, + maybe_prefix, + maybe_limit, + maybe_cursor.as_ref().map(|x| &x[..]), + ) + .into() } /// Append the encoded `value` to the storage item at `key`. @@ -191,24 +259,27 @@ pub trait Storage { /// /// Returns a `Vec` that holds the SCALE encoded hash. fn root(&mut self) -> Vec { - self.storage_root() + self.storage_root(StateVersion::V0) } - /// "Commit" all existing operations and get the resulting storage change root. - /// `parent_hash` is a SCALE encoded hash. + /// "Commit" all existing operations and compute the resulting storage root. /// /// The hashing algorithm is defined by the `Block`. /// - /// Returns `Some(Vec)` which holds the SCALE encoded hash or `None` when - /// changes trie is disabled. - fn changes_root(&mut self, parent_hash: &[u8]) -> Option> { - self.storage_changes_root(parent_hash) - .expect("Invalid `parent_hash` given to `changes_root`.") + /// Returns a `Vec` that holds the SCALE encoded hash. + #[version(2)] + fn root(&mut self, version: StateVersion) -> Vec { + self.storage_root(version) + } + + /// Always returns `None`. This function exists for compatibility reasons. + fn changes_root(&mut self, _parent_hash: &[u8]) -> Option> { + None } /// Get the next key in storage after the given one in lexicographic order. fn next_key(&mut self, key: &[u8]) -> Option> { - self.next_storage_key(&key) + self.next_storage_key(key) } /// Start a new nested transaction. @@ -311,7 +382,7 @@ pub trait DefaultChildStorage { /// is removed. fn storage_kill(&mut self, storage_key: &[u8]) { let child_info = ChildInfo::new_default(storage_key); - self.kill_child_storage(&child_info, None); + let _ = self.kill_child_storage(&child_info, None, None); } /// Clear a child storage key. @@ -320,8 +391,8 @@ pub trait DefaultChildStorage { #[version(2)] fn storage_kill(&mut self, storage_key: &[u8], limit: Option) -> bool { let child_info = ChildInfo::new_default(storage_key); - let (all_removed, _num_removed) = self.kill_child_storage(&child_info, limit); - all_removed + let r = self.kill_child_storage(&child_info, limit, None); + r.maybe_cursor.is_none() } /// Clear a child storage key. @@ -330,11 +401,22 @@ pub trait DefaultChildStorage { #[version(3)] fn storage_kill(&mut self, storage_key: &[u8], limit: Option) -> KillStorageResult { let child_info = ChildInfo::new_default(storage_key); - let (all_removed, num_removed) = self.kill_child_storage(&child_info, limit); - match all_removed { - true => KillStorageResult::AllRemoved(num_removed), - false => KillStorageResult::SomeRemaining(num_removed), - } + self.kill_child_storage(&child_info, limit, None).into() + } + + /// Clear a child storage key. + /// + /// See `Storage` module `clear_prefix` documentation for `limit` usage. + #[version(4, register_only)] + fn storage_kill( + &mut self, + storage_key: &[u8], + maybe_limit: Option, + maybe_cursor: Option>, + ) -> MultiRemovalResults { + let child_info = ChildInfo::new_default(storage_key); + self.kill_child_storage(&child_info, maybe_limit, maybe_cursor.as_ref().map(|x| &x[..])) + .into() } /// Check a child storage key. @@ -350,7 +432,7 @@ pub trait DefaultChildStorage { /// Clear the child storage of each key-value pair where the key starts with the given `prefix`. fn clear_prefix(&mut self, storage_key: &[u8], prefix: &[u8]) { let child_info = ChildInfo::new_default(storage_key); - let _ = self.clear_child_prefix(&child_info, prefix, None); + let _ = self.clear_child_prefix(&child_info, prefix, None, None); } /// Clear the child storage of each key-value pair where the key starts with the given `prefix`. @@ -364,11 +446,28 @@ pub trait DefaultChildStorage { limit: Option, ) -> KillStorageResult { let child_info = ChildInfo::new_default(storage_key); - let (all_removed, num_removed) = self.clear_child_prefix(&child_info, prefix, limit); - match all_removed { - true => KillStorageResult::AllRemoved(num_removed), - false => KillStorageResult::SomeRemaining(num_removed), - } + self.clear_child_prefix(&child_info, prefix, limit, None).into() + } + + /// Clear the child storage of each key-value pair where the key starts with the given `prefix`. + /// + /// See `Storage` module `clear_prefix` documentation for `limit` usage. + #[version(3, register_only)] + fn clear_prefix( + &mut self, + storage_key: &[u8], + prefix: &[u8], + maybe_limit: Option, + maybe_cursor: Option>, + ) -> MultiRemovalResults { + let child_info = ChildInfo::new_default(storage_key); + self.clear_child_prefix( + &child_info, + prefix, + maybe_limit, + maybe_cursor.as_ref().map(|x| &x[..]), + ) + .into() } /// Default child root calculation. @@ -379,7 +478,19 @@ pub trait DefaultChildStorage { /// Returns a `Vec` that holds the SCALE encoded hash. fn root(&mut self, storage_key: &[u8]) -> Vec { let child_info = ChildInfo::new_default(storage_key); - self.child_storage_root(&child_info) + self.child_storage_root(&child_info, StateVersion::V0) + } + + /// Default child root calculation. + /// + /// "Commit" all existing operations and compute the resulting child storage root. + /// The hashing algorithm is defined by the `Block`. + /// + /// Returns a `Vec` that holds the SCALE encoded hash. + #[version(2)] + fn root(&mut self, storage_key: &[u8], version: StateVersion) -> Vec { + let child_info = ChildInfo::new_default(storage_key); + self.child_storage_root(&child_info, version) } /// Child storage key iteration. @@ -396,27 +507,63 @@ pub trait DefaultChildStorage { pub trait Trie { /// A trie root formed from the iterated items. fn blake2_256_root(input: Vec<(Vec, Vec)>) -> H256 { - Layout::::trie_root(input) + LayoutV0::::trie_root(input) + } + + /// A trie root formed from the iterated items. + #[version(2)] + fn blake2_256_root(input: Vec<(Vec, Vec)>, version: StateVersion) -> H256 { + match version { + StateVersion::V0 => LayoutV0::::trie_root(input), + StateVersion::V1 => LayoutV1::::trie_root(input), + } } /// A trie root formed from the enumerated items. fn blake2_256_ordered_root(input: Vec>) -> H256 { - Layout::::ordered_trie_root(input) + LayoutV0::::ordered_trie_root(input) + } + + /// A trie root formed from the enumerated items. + #[version(2)] + fn blake2_256_ordered_root(input: Vec>, version: StateVersion) -> H256 { + match version { + StateVersion::V0 => LayoutV0::::ordered_trie_root(input), + StateVersion::V1 => LayoutV1::::ordered_trie_root(input), + } } /// A trie root formed from the iterated items. fn keccak_256_root(input: Vec<(Vec, Vec)>) -> H256 { - Layout::::trie_root(input) + LayoutV0::::trie_root(input) + } + + /// A trie root formed from the iterated items. + #[version(2)] + fn keccak_256_root(input: Vec<(Vec, Vec)>, version: StateVersion) -> H256 { + match version { + StateVersion::V0 => LayoutV0::::trie_root(input), + StateVersion::V1 => LayoutV1::::trie_root(input), + } } /// A trie root formed from the enumerated items. fn keccak_256_ordered_root(input: Vec>) -> H256 { - Layout::::ordered_trie_root(input) + LayoutV0::::ordered_trie_root(input) + } + + /// A trie root formed from the enumerated items. + #[version(2)] + fn keccak_256_ordered_root(input: Vec>, version: StateVersion) -> H256 { + match version { + StateVersion::V0 => LayoutV0::::ordered_trie_root(input), + StateVersion::V1 => LayoutV1::::ordered_trie_root(input), + } } /// Verify trie proof fn blake2_256_verify_proof(root: H256, proof: &[Vec], key: &[u8], value: &[u8]) -> bool { - sp_trie::verify_trie_proof::, _, _, _>( + sp_trie::verify_trie_proof::, _, _, _>( &root, proof, &[(key, Some(value))], @@ -424,15 +571,69 @@ pub trait Trie { .is_ok() } + /// Verify trie proof + #[version(2)] + fn blake2_256_verify_proof( + root: H256, + proof: &[Vec], + key: &[u8], + value: &[u8], + version: StateVersion, + ) -> bool { + match version { + StateVersion::V0 => sp_trie::verify_trie_proof::< + LayoutV0, + _, + _, + _, + >(&root, proof, &[(key, Some(value))]) + .is_ok(), + StateVersion::V1 => sp_trie::verify_trie_proof::< + LayoutV1, + _, + _, + _, + >(&root, proof, &[(key, Some(value))]) + .is_ok(), + } + } + /// Verify trie proof fn keccak_256_verify_proof(root: H256, proof: &[Vec], key: &[u8], value: &[u8]) -> bool { - sp_trie::verify_trie_proof::, _, _, _>( + sp_trie::verify_trie_proof::, _, _, _>( &root, proof, &[(key, Some(value))], ) .is_ok() } + + /// Verify trie proof + #[version(2)] + fn keccak_256_verify_proof( + root: H256, + proof: &[Vec], + key: &[u8], + value: &[u8], + version: StateVersion, + ) -> bool { + match version { + StateVersion::V0 => sp_trie::verify_trie_proof::< + LayoutV0, + _, + _, + _, + >(&root, proof, &[(key, Some(value))]) + .is_ok(), + StateVersion::V1 => sp_trie::verify_trie_proof::< + LayoutV1, + _, + _, + _, + >(&root, proof, &[(key, Some(value))]) + .is_ok(), + } + } } /// Interface that provides miscellaneous functions for communicating between the runtime and the @@ -497,6 +698,34 @@ pub trait Misc { } } +#[cfg(feature = "std")] +sp_externalities::decl_extension! { + /// Extension to signal to [`crypt::ed25519_verify`] to use the dalek crate. + /// + /// The switch from `ed25519-dalek` to `ed25519-zebra` was a breaking change. + /// `ed25519-zebra` is more permissive when it comes to the verification of signatures. + /// This means that some chains may fail to sync from genesis when using `ed25519-zebra`. + /// So, this extension can be registered to the runtime execution environment to signal + /// that `ed25519-dalek` should be used for verification. The extension can be registered + /// in the following way: + /// + /// ```nocompile + /// client.execution_extensions().set_extensions_factory( + /// // Let the `UseDalekExt` extension being registered for each runtime invocation + /// // until the execution happens in the context of block `1000`. + /// sc_client_api::execution_extensions::ExtensionBeforeBlock::::new(1000) + /// ); + /// ``` + pub struct UseDalekExt; +} + +#[cfg(feature = "std")] +impl Default for UseDalekExt { + fn default() -> Self { + Self + } +} + /// Interfaces for working with crypto related types from within the runtime. #[runtime_interface] pub trait Crypto { @@ -515,7 +744,7 @@ pub trait Crypto { /// /// Returns the public key. fn ed25519_generate(&mut self, id: KeyTypeId, seed: Option>) -> ed25519::Public { - let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!")); + let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); let keystore = &***self .extension::() .expect("No `keystore` associated for the current context!"); @@ -539,20 +768,39 @@ pub trait Crypto { SyncCryptoStore::sign_with(keystore, id, &pub_key.into(), msg) .ok() .flatten() - .map(|sig| ed25519::Signature::from_slice(sig.as_slice())) + .and_then(|sig| ed25519::Signature::from_slice(&sig)) } /// Verify `ed25519` signature. /// /// Returns `true` when the verification was successful. fn ed25519_verify(sig: &ed25519::Signature, msg: &[u8], pub_key: &ed25519::Public) -> bool { - ed25519::Pair::verify(sig, msg, pub_key) + // We don't want to force everyone needing to call the function in an externalities context. + // So, we assume that we should not use dalek when we are not in externalities context. + // Otherwise, we check if the extension is present. + if sp_externalities::with_externalities(|mut e| e.extension::().is_some()) + .unwrap_or_default() + { + use ed25519_dalek::Verifier; + + let public_key = if let Ok(vk) = ed25519_dalek::PublicKey::from_bytes(&pub_key.0) { + vk + } else { + return false + }; + + let sig = ed25519_dalek::Signature::from(sig.0); + + public_key.verify(msg, &sig).is_ok() + } else { + ed25519::Pair::verify(sig, msg, pub_key) + } } /// Register a `ed25519` signature for batch verification. /// /// Batch verification must be enabled by calling [`start_batch_verify`]. - /// If batch verification is not enabled, the signature will be verified immediatley. + /// If batch verification is not enabled, the signature will be verified immediately. /// To get the result of the batch verification, [`finish_batch_verify`] /// needs to be called. /// @@ -564,7 +812,7 @@ pub trait Crypto { pub_key: &ed25519::Public, ) -> bool { self.extension::() - .map(|extension| extension.push_ed25519(sig.clone(), pub_key.clone(), msg.to_vec())) + .map(|extension| extension.push_ed25519(sig.clone(), *pub_key, msg.to_vec())) .unwrap_or_else(|| ed25519_verify(sig, msg, pub_key)) } @@ -579,7 +827,7 @@ pub trait Crypto { /// Register a `sr25519` signature for batch verification. /// /// Batch verification must be enabled by calling [`start_batch_verify`]. - /// If batch verification is not enabled, the signature will be verified immediatley. + /// If batch verification is not enabled, the signature will be verified immediately. /// To get the result of the batch verification, [`finish_batch_verify`] /// needs to be called. /// @@ -591,7 +839,7 @@ pub trait Crypto { pub_key: &sr25519::Public, ) -> bool { self.extension::() - .map(|extension| extension.push_sr25519(sig.clone(), pub_key.clone(), msg.to_vec())) + .map(|extension| extension.push_sr25519(sig.clone(), *pub_key, msg.to_vec())) .unwrap_or_else(|| sr25519_verify(sig, msg, pub_key)) } @@ -639,7 +887,7 @@ pub trait Crypto { /// /// Returns the public key. fn sr25519_generate(&mut self, id: KeyTypeId, seed: Option>) -> sr25519::Public { - let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!")); + let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); let keystore = &***self .extension::() .expect("No `keystore` associated for the current context!"); @@ -663,7 +911,7 @@ pub trait Crypto { SyncCryptoStore::sign_with(keystore, id, &pub_key.into(), msg) .ok() .flatten() - .map(|sig| sr25519::Signature::from_slice(sig.as_slice())) + .and_then(|sig| sr25519::Signature::from_slice(&sig)) } /// Verify an `sr25519` signature. @@ -689,7 +937,7 @@ pub trait Crypto { /// /// Returns the public key. fn ecdsa_generate(&mut self, id: KeyTypeId, seed: Option>) -> ecdsa::Public { - let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!")); + let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); let keystore = &***self .extension::() .expect("No `keystore` associated for the current context!"); @@ -712,13 +960,31 @@ pub trait Crypto { SyncCryptoStore::sign_with(keystore, id, &pub_key.into(), msg) .ok() .flatten() - .map(|sig| ecdsa::Signature::from_slice(sig.as_slice())) + .and_then(|sig| ecdsa::Signature::from_slice(&sig)) + } + + /// Sign the given a pre-hashed `msg` with the `ecdsa` key that corresponds to the given public + /// key and key type in the keystore. + /// + /// Returns the signature. + fn ecdsa_sign_prehashed( + &mut self, + id: KeyTypeId, + pub_key: &ecdsa::Public, + msg: &[u8; 32], + ) -> Option { + let keystore = &***self + .extension::() + .expect("No `keystore` associated for the current context!"); + SyncCryptoStore::ecdsa_sign_prehashed(keystore, id, pub_key, msg).ok().flatten() } /// Verify `ecdsa` signature. /// /// Returns `true` when the verification was successful. + /// This version is able to handle, non-standard, overflowing signatures. fn ecdsa_verify(sig: &ecdsa::Signature, msg: &[u8], pub_key: &ecdsa::Public) -> bool { + #[allow(deprecated)] ecdsa::Pair::verify_deprecated(sig, msg, pub_key) } @@ -730,6 +996,17 @@ pub trait Crypto { ecdsa::Pair::verify(sig, msg, pub_key) } + /// Verify `ecdsa` signature with pre-hashed `msg`. + /// + /// Returns `true` when the verification was successful. + fn ecdsa_verify_prehashed( + sig: &ecdsa::Signature, + msg: &[u8; 32], + pub_key: &ecdsa::Public, + ) -> bool { + ecdsa::Pair::verify_prehashed(sig, msg, pub_key) + } + /// Register a `ecdsa` signature for batch verification. /// /// Batch verification must be enabled by calling [`start_batch_verify`]. @@ -745,7 +1022,7 @@ pub trait Crypto { pub_key: &ecdsa::Public, ) -> bool { self.extension::() - .map(|extension| extension.push_ecdsa(sig.clone(), pub_key.clone(), msg.to_vec())) + .map(|extension| extension.push_ecdsa(sig.clone(), *pub_key, msg.to_vec())) .unwrap_or_else(|| ecdsa_verify(sig, msg, pub_key)) } @@ -756,18 +1033,20 @@ pub trait Crypto { /// /// Returns `Err` if the signature is bad, otherwise the 64-byte pubkey /// (doesn't include the 0x04 prefix). + /// This version is able to handle, non-standard, overflowing signatures. fn secp256k1_ecdsa_recover( sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 64], EcdsaVerifyError> { - let rs = libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64]) - .map_err(|_| EcdsaVerifyError::BadRS)?; - let v = libsecp256k1::RecoveryId::parse( - if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8 + let rid = libsecp256k1::RecoveryId::parse( + if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8, ) .map_err(|_| EcdsaVerifyError::BadV)?; - let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) - .map_err(|_| EcdsaVerifyError::BadSignature)?; + let sig = libsecp256k1::Signature::parse_overflowing_slice(&sig[..64]) + .map_err(|_| EcdsaVerifyError::BadRS)?; + let msg = libsecp256k1::Message::parse(msg); + let pubkey = + libsecp256k1::recover(&msg, &sig, &rid).map_err(|_| EcdsaVerifyError::BadSignature)?; let mut res = [0u8; 64]; res.copy_from_slice(&pubkey.serialize()[1..65]); Ok(res) @@ -785,16 +1064,16 @@ pub trait Crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 64], EcdsaVerifyError> { - let rs = libsecp256k1::Signature::parse_standard_slice(&sig[0..64]) + let rid = RecoveryId::from_i32(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as i32) + .map_err(|_| EcdsaVerifyError::BadV)?; + let sig = RecoverableSignature::from_compact(&sig[..64], rid) .map_err(|_| EcdsaVerifyError::BadRS)?; - let v = libsecp256k1::RecoveryId::parse( - if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8 - ) - .map_err(|_| EcdsaVerifyError::BadV)?; - let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) + let msg = Message::from_slice(msg).expect("Message is 32 bytes; qed"); + let pubkey = SECP256K1 + .recover_ecdsa(&msg, &sig) .map_err(|_| EcdsaVerifyError::BadSignature)?; let mut res = [0u8; 64]; - res.copy_from_slice(&pubkey.serialize()[1..65]); + res.copy_from_slice(&pubkey.serialize_uncompressed()[1..]); Ok(res) } @@ -808,14 +1087,15 @@ pub trait Crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 33], EcdsaVerifyError> { - let rs = libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64]) - .map_err(|_| EcdsaVerifyError::BadRS)?; - let v = libsecp256k1::RecoveryId::parse( - if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8 + let rid = libsecp256k1::RecoveryId::parse( + if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8, ) .map_err(|_| EcdsaVerifyError::BadV)?; - let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) - .map_err(|_| EcdsaVerifyError::BadSignature)?; + let sig = libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64]) + .map_err(|_| EcdsaVerifyError::BadRS)?; + let msg = libsecp256k1::Message::parse(msg); + let pubkey = + libsecp256k1::recover(&msg, &sig, &rid).map_err(|_| EcdsaVerifyError::BadSignature)?; Ok(pubkey.serialize_compressed()) } @@ -830,15 +1110,15 @@ pub trait Crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 33], EcdsaVerifyError> { - let rs = libsecp256k1::Signature::parse_standard_slice(&sig[0..64]) + let rid = RecoveryId::from_i32(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as i32) + .map_err(|_| EcdsaVerifyError::BadV)?; + let sig = RecoverableSignature::from_compact(&sig[..64], rid) .map_err(|_| EcdsaVerifyError::BadRS)?; - let v = libsecp256k1::RecoveryId::parse( - if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8 - ) - .map_err(|_| EcdsaVerifyError::BadV)?; - let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) + let msg = Message::from_slice(msg).expect("Message is 32 bytes; qed"); + let pubkey = SECP256K1 + .recover_ecdsa(&msg, &sig) .map_err(|_| EcdsaVerifyError::BadSignature)?; - Ok(pubkey.serialize_compressed()) + Ok(pubkey.serialize()) } } @@ -1155,6 +1435,17 @@ pub trait Allocator { } } +/// WASM-only interface which allows for aborting the execution in case +/// of an unrecoverable error. +#[runtime_interface(wasm_only)] +pub trait PanicHandler { + /// Aborts the current execution with the given error message. + #[trap_on_return] + fn abort_on_panic(&mut self, message: &str) { + self.register_panic_error_message(message); + } +} + /// Interface that provides functions for logging from within the runtime. #[runtime_interface] pub trait Logging { @@ -1278,17 +1569,17 @@ mod tracing_setup { fn new_span(&self, attrs: &Attributes<'_>) -> Id { Id::from_u64(wasm_tracing::enter_span(Crossing(attrs.into()))) } - fn enter(&self, span: &Id) { + fn enter(&self, _: &Id) { // Do nothing, we already entered the span previously } /// Not implemented! We do not support recording values later /// Will panic when used. - fn record(&self, span: &Id, values: &Record<'_>) { + fn record(&self, _: &Id, _: &Record<'_>) { unimplemented! {} // this usage is not supported } /// Not implemented! We do not support recording values later /// Will panic when used. - fn record_follows_from(&self, span: &Id, follows: &Id) { + fn record_follows_from(&self, _: &Id, _: &Id) { unimplemented! {} // this usage is not supported } fn event(&self, event: &Event<'_>) { @@ -1320,147 +1611,15 @@ mod tracing_setup { pub use tracing_setup::init_tracing; -/// Wasm-only interface that provides functions for interacting with the sandbox. -#[runtime_interface(wasm_only)] -pub trait Sandbox { - /// Instantiate a new sandbox instance with the given `wasm_code`. - fn instantiate( - &mut self, - dispatch_thunk: u32, - wasm_code: &[u8], - env_def: &[u8], - state_ptr: Pointer, - ) -> u32 { - self.sandbox() - .instance_new(dispatch_thunk, wasm_code, env_def, state_ptr.into()) - .expect("Failed to instantiate a new sandbox") - } - - /// Invoke `function` in the sandbox with `sandbox_idx`. - fn invoke( - &mut self, - instance_idx: u32, - function: &str, - args: &[u8], - return_val_ptr: Pointer, - return_val_len: u32, - state_ptr: Pointer, - ) -> u32 { - self.sandbox() - .invoke( - instance_idx, - &function, - &args, - return_val_ptr, - return_val_len, - state_ptr.into(), - ) - .expect("Failed to invoke function with sandbox") - } - - /// Create a new memory instance with the given `initial` and `maximum` size. - fn memory_new(&mut self, initial: u32, maximum: u32) -> u32 { - self.sandbox() - .memory_new(initial, maximum) - .expect("Failed to create new memory with sandbox") - } - - /// Get the memory starting at `offset` from the instance with `memory_idx` into the buffer. - fn memory_get( - &mut self, - memory_idx: u32, - offset: u32, - buf_ptr: Pointer, - buf_len: u32, - ) -> u32 { - self.sandbox() - .memory_get(memory_idx, offset, buf_ptr, buf_len) - .expect("Failed to get memory with sandbox") - } - - /// Set the memory in the given `memory_idx` to the given value at `offset`. - fn memory_set( - &mut self, - memory_idx: u32, - offset: u32, - val_ptr: Pointer, - val_len: u32, - ) -> u32 { - self.sandbox() - .memory_set(memory_idx, offset, val_ptr, val_len) - .expect("Failed to set memory with sandbox") - } - - /// Teardown the memory instance with the given `memory_idx`. - fn memory_teardown(&mut self, memory_idx: u32) { - self.sandbox() - .memory_teardown(memory_idx) - .expect("Failed to teardown memory with sandbox") - } - - /// Teardown the sandbox instance with the given `instance_idx`. - fn instance_teardown(&mut self, instance_idx: u32) { - self.sandbox() - .instance_teardown(instance_idx) - .expect("Failed to teardown sandbox instance") - } - - /// Get the value from a global with the given `name`. The sandbox is determined by the given - /// `instance_idx`. - /// - /// Returns `Some(_)` when the requested global variable could be found. - fn get_global_val( - &mut self, - instance_idx: u32, - name: &str, - ) -> Option { - self.sandbox() - .get_global_val(instance_idx, name) - .expect("Failed to get global from sandbox") - } -} - -/// Wasm host functions for managing tasks. -/// -/// This should not be used directly. Use `sp_tasks` for running parallel tasks instead. -#[runtime_interface(wasm_only)] -pub trait RuntimeTasks { - /// Wasm host function for spawning task. - /// - /// This should not be used directly. Use `sp_tasks::spawn` instead. - fn spawn(dispatcher_ref: u32, entry: u32, payload: Vec) -> u64 { - sp_externalities::with_externalities(|mut ext| { - let runtime_spawn = ext - .extension::() - .expect("Cannot spawn without dynamic runtime dispatcher (RuntimeSpawnExt)"); - runtime_spawn.spawn_call(dispatcher_ref, entry, payload) - }) - .expect("`RuntimeTasks::spawn`: called outside of externalities context") - } - - /// Wasm host function for joining a task. - /// - /// This should not be used directly. Use `join` of `sp_tasks::spawn` result instead. - fn join(handle: u64) -> Vec { - sp_externalities::with_externalities(|mut ext| { - let runtime_spawn = ext - .extension::() - .expect("Cannot join without dynamic runtime dispatcher (RuntimeSpawnExt)"); - runtime_spawn.join(handle) - }) - .expect("`RuntimeTasks::join`: called outside of externalities context") - } -} - /// Allocator used by Substrate when executing the Wasm runtime. -#[cfg(not(feature = "std"))] +#[cfg(all(target_arch = "wasm32", not(feature = "std")))] struct WasmAllocator; -#[cfg(all(not(feature = "disable_allocator"), not(feature = "std")))] +#[cfg(all(target_arch = "wasm32", not(feature = "disable_allocator"), not(feature = "std")))] #[global_allocator] static ALLOCATOR: WasmAllocator = WasmAllocator; -#[cfg(not(feature = "std"))] +#[cfg(all(target_arch = "wasm32", not(feature = "std")))] mod allocator_impl { use super::*; use core::alloc::{GlobalAlloc, Layout}; @@ -1481,8 +1640,13 @@ mod allocator_impl { #[panic_handler] #[no_mangle] pub fn panic(info: &core::panic::PanicInfo) -> ! { - unsafe { - let message = sp_std::alloc::format!("{}", info); + let message = sp_std::alloc::format!("{}", info); + #[cfg(feature = "improved_panic_error_reporting")] + { + panic_handler::abort_on_panic(&message); + } + #[cfg(not(feature = "improved_panic_error_reporting"))] + { logging::log(LogLevel::Error, "runtime", message.as_bytes()); core::arch::wasm32::unreachable(); } @@ -1492,7 +1656,12 @@ pub fn panic(info: &core::panic::PanicInfo) -> ! { #[cfg(all(not(feature = "disable_oom"), not(feature = "std")))] #[alloc_error_handler] pub fn oom(_: core::alloc::Layout) -> ! { - unsafe { + #[cfg(feature = "improved_panic_error_reporting")] + { + panic_handler::abort_on_panic("Runtime memory exhausted."); + } + #[cfg(not(feature = "improved_panic_error_reporting"))] + { logging::log(LogLevel::Error, "runtime", b"Runtime memory exhausted. Aborting"); core::arch::wasm32::unreachable(); } @@ -1500,7 +1669,7 @@ pub fn oom(_: core::alloc::Layout) -> ! { /// Type alias for Externalities implementation used in tests. #[cfg(feature = "std")] -pub type TestExternalities = sp_state_machine::TestExternalities; +pub type TestExternalities = sp_state_machine::TestExternalities; /// The host functions Substrate provides for the Wasm runtime environment. /// @@ -1515,18 +1684,20 @@ pub type SubstrateHostFunctions = ( crypto::HostFunctions, hashing::HostFunctions, allocator::HostFunctions, + panic_handler::HostFunctions, logging::HostFunctions, - sandbox::HostFunctions, crate::trie::HostFunctions, offchain_index::HostFunctions, - runtime_tasks::HostFunctions, transaction_index::HostFunctions, ); #[cfg(test)] mod tests { use super::*; - use sp_core::{map, storage::Storage, testing::TaskExecutor, traits::TaskExecutorExt}; + use sp_core::{ + crypto::UncheckedInto, map, storage::Storage, testing::TaskExecutor, + traits::TaskExecutorExt, + }; use sp_state_machine::BasicExternalities; use std::any::TypeId; @@ -1536,7 +1707,7 @@ mod tests { t.execute_with(|| { assert_eq!(storage::get(b"hello"), None); storage::set(b"hello", b"world"); - assert_eq!(storage::get(b"hello"), Some(b"world".to_vec())); + assert_eq!(storage::get(b"hello"), Some(b"world".to_vec().into())); assert_eq!(storage::get(b"foo"), None); storage::set(b"foo", &[1, 2, 3][..]); }); @@ -1548,7 +1719,17 @@ mod tests { t.execute_with(|| { assert_eq!(storage::get(b"hello"), None); - assert_eq!(storage::get(b"foo"), Some(b"bar".to_vec())); + assert_eq!(storage::get(b"foo"), Some(b"bar".to_vec().into())); + }); + + let value = vec![7u8; 35]; + let storage = + Storage { top: map![b"foo00".to_vec() => value.clone()], children_default: map![] }; + t = BasicExternalities::new(storage); + + t.execute_with(|| { + assert_eq!(storage::get(b"hello"), None); + assert_eq!(storage::get(b"foo00"), Some(value.clone().into())); }); } @@ -1583,15 +1764,30 @@ mod tests { }); t.execute_with(|| { + // We can switch to this once we enable v3 of the `clear_prefix`. + //assert!(matches!( + // storage::clear_prefix(b":abc", None), + // MultiRemovalResults::NoneLeft { db: 2, total: 2 } + //)); assert!(matches!( storage::clear_prefix(b":abc", None), - KillStorageResult::AllRemoved(2) + KillStorageResult::AllRemoved(2), )); assert!(storage::get(b":a").is_some()); assert!(storage::get(b":abdd").is_some()); assert!(storage::get(b":abcd").is_none()); assert!(storage::get(b":abc").is_none()); + + // We can switch to this once we enable v3 of the `clear_prefix`. + //assert!(matches!( + // storage::clear_prefix(b":abc", None), + // MultiRemovalResults::NoneLeft { db: 0, total: 0 } + //)); + assert!(matches!( + storage::clear_prefix(b":abc", None), + KillStorageResult::AllRemoved(0), + )); }); } @@ -1619,6 +1815,7 @@ mod tests { ext.register_extension(TaskExecutorExt::new(TaskExecutor::new())); ext.execute_with(|| { let pair = sr25519::Pair::generate_with_phrase(None).0; + let pair_unused = sr25519::Pair::generate_with_phrase(None).0; crypto::start_batch_verify(); for it in 0..70 { let msg = format!("Schnorrkel {}!", it); @@ -1626,8 +1823,10 @@ mod tests { crypto::sr25519_batch_verify(&signature, msg.as_bytes(), &pair.public()); } - // push invlaid - crypto::sr25519_batch_verify(&Default::default(), &Vec::new(), &Default::default()); + // push invalid + let msg = b"asdf!"; + let signature = pair.sign(msg); + crypto::sr25519_batch_verify(&signature, msg, &pair_unused.public()); assert!(!crypto::finish_batch_verify()); crypto::start_batch_verify(); @@ -1640,15 +1839,32 @@ mod tests { }); } + fn zero_ed_pub() -> ed25519::Public { + [0u8; 32].unchecked_into() + } + + fn zero_ed_sig() -> ed25519::Signature { + ed25519::Signature::from_raw([0u8; 64]) + } + + fn zero_sr_pub() -> sr25519::Public { + [0u8; 32].unchecked_into() + } + + fn zero_sr_sig() -> sr25519::Signature { + sr25519::Signature::from_raw([0u8; 64]) + } + #[test] fn batching_works() { let mut ext = BasicExternalities::default(); ext.register_extension(TaskExecutorExt::new(TaskExecutor::new())); + ext.execute_with(|| { - // invalid ed25519 signature + // valid ed25519 signature crypto::start_batch_verify(); - crypto::ed25519_batch_verify(&Default::default(), &Vec::new(), &Default::default()); - assert!(!crypto::finish_batch_verify()); + crypto::ed25519_batch_verify(&zero_ed_sig(), &Vec::new(), &zero_ed_pub()); + assert!(crypto::finish_batch_verify()); // 2 valid ed25519 signatures crypto::start_batch_verify(); @@ -1668,12 +1884,14 @@ mod tests { // 1 valid, 1 invalid ed25519 signature crypto::start_batch_verify(); - let pair = ed25519::Pair::generate_with_phrase(None).0; + let pair1 = ed25519::Pair::generate_with_phrase(None).0; + let pair2 = ed25519::Pair::generate_with_phrase(None).0; let msg = b"Important message"; - let signature = pair.sign(msg); - crypto::ed25519_batch_verify(&signature, msg, &pair.public()); + let signature = pair1.sign(msg); - crypto::ed25519_batch_verify(&Default::default(), &Vec::new(), &Default::default()); + crypto::ed25519_batch_verify(&zero_ed_sig(), &Vec::new(), &zero_ed_pub()); + crypto::ed25519_batch_verify(&signature, msg, &pair1.public()); + crypto::ed25519_batch_verify(&signature, msg, &pair2.public()); assert!(!crypto::finish_batch_verify()); @@ -1700,14 +1918,32 @@ mod tests { // 1 valid sr25519, 1 invalid sr25519 crypto::start_batch_verify(); - let pair = sr25519::Pair::generate_with_phrase(None).0; + let pair1 = sr25519::Pair::generate_with_phrase(None).0; + let pair2 = sr25519::Pair::generate_with_phrase(None).0; let msg = b"Schnorrkcel!"; - let signature = pair.sign(msg); - crypto::sr25519_batch_verify(&signature, msg, &pair.public()); + let signature = pair1.sign(msg); - crypto::sr25519_batch_verify(&Default::default(), &Vec::new(), &Default::default()); + crypto::sr25519_batch_verify(&signature, msg, &pair1.public()); + crypto::sr25519_batch_verify(&signature, msg, &pair2.public()); + crypto::sr25519_batch_verify(&zero_sr_sig(), &Vec::new(), &zero_sr_pub()); assert!(!crypto::finish_batch_verify()); }); } + + #[test] + fn use_dalek_ext_works() { + let mut ext = BasicExternalities::default(); + ext.register_extension(UseDalekExt::default()); + + // With dalek the zero signature should fail to verify. + ext.execute_with(|| { + assert!(!crypto::ed25519_verify(&zero_ed_sig(), &Vec::new(), &zero_ed_pub())); + }); + + // But with zebra it should work. + BasicExternalities::default().execute_with(|| { + assert!(crypto::ed25519_verify(&zero_ed_sig(), &Vec::new(), &zero_ed_pub())); + }) + } } diff --git a/primitives/keyring/Cargo.toml b/primitives/keyring/Cargo.toml index a14e98d3d8059..0646dde4f077a 100644 --- a/primitives/keyring/Cargo.toml +++ b/primitives/keyring/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-keyring" -version = "4.0.0-dev" +version = "7.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Keyring support code for the runtime. A set of test accounts." documentation = "https://docs.rs/sp-keyring" @@ -13,9 +13,8 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -sp-core = { version = "4.0.0-dev", path = "../core" } -sp-runtime = { version = "4.0.0-dev", path = "../runtime" } lazy_static = "1.4.0" -strum = { version = "0.20.0", features = ["derive"] } +strum = { version = "0.24.1", features = ["derive"] } +sp-core = { version = "7.0.0", path = "../core" } +sp-runtime = { version = "7.0.0", path = "../runtime" } diff --git a/primitives/keyring/src/ed25519.rs b/primitives/keyring/src/ed25519.rs index 65341a360579b..404e4121e71a3 100644 --- a/primitives/keyring/src/ed25519.rs +++ b/primitives/keyring/src/ed25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ use lazy_static::lazy_static; pub use sp_core::ed25519; use sp_core::{ ed25519::{Pair, Public, Signature}, - Pair as PairT, Public as PublicT, H256, + ByteArray, Pair as PairT, H256, }; use sp_runtime::AccountId32; use std::{collections::HashMap, ops::Deref}; @@ -125,7 +125,7 @@ lazy_static! { impl From for Public { fn from(k: Keyring) -> Self { - (*PUBLIC_KEYS).get(&k).unwrap().clone() + *(*PUBLIC_KEYS).get(&k).unwrap() } } diff --git a/primitives/keyring/src/lib.rs b/primitives/keyring/src/lib.rs index d7fb7c4fd2f2b..170a4c3d01b23 100644 --- a/primitives/keyring/src/lib.rs +++ b/primitives/keyring/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/keyring/src/sr25519.rs b/primitives/keyring/src/sr25519.rs index 6a7aa3635a43a..115a7fce70122 100644 --- a/primitives/keyring/src/sr25519.rs +++ b/primitives/keyring/src/sr25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ use lazy_static::lazy_static; pub use sp_core::sr25519; use sp_core::{ sr25519::{Pair, Public, Signature}, - Pair as PairT, Public as PublicT, H256, + ByteArray, Pair as PairT, H256, }; use sp_runtime::AccountId32; use std::{collections::HashMap, ops::Deref}; @@ -89,9 +89,20 @@ impl Keyring { pub fn public(self) -> Public { self.pair().public() } + pub fn to_seed(self) -> String { format!("//{}", self) } + + /// Create a crypto `Pair` from a numeric value. + pub fn numeric(idx: usize) -> Pair { + Pair::from_string(&format!("//{}", idx), None).expect("numeric values are known good; qed") + } + + /// Get account id of a `numeric` account. + pub fn numeric_id(idx: usize) -> AccountId32 { + (*Self::numeric(idx).public().as_array_ref()).into() + } } impl From for &'static str { @@ -157,7 +168,7 @@ impl From for AccountId32 { impl From for Public { fn from(k: Keyring) -> Self { - (*PUBLIC_KEYS).get(&k).unwrap().clone() + *(*PUBLIC_KEYS).get(&k).unwrap() } } diff --git a/primitives/keystore/Cargo.toml b/primitives/keystore/Cargo.toml index 35c66ef93f7aa..0d5d7ca5637eb 100644 --- a/primitives/keystore/Cargo.toml +++ b/primitives/keystore/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-keystore" -version = "0.10.0-dev" +version = "0.13.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Keystore primitives." documentation = "https://docs.rs/sp-core" @@ -13,25 +13,28 @@ documentation = "https://docs.rs/sp-core" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = "0.1.50" -derive_more = "0.99.2" -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -futures = { version = "0.3.1" } -schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false } +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +futures = "0.3.21" merlin = { version = "2.0", default-features = false } -parking_lot = { version = "0.11.1", default-features = false } -serde = { version = "1.0", optional = true} -sp-core = { version = "4.0.0-dev", path = "../core" } -sp-externalities = { version = "0.10.0-dev", path = "../externalities", default-features = false } +parking_lot = { version = "0.12.1", default-features = false } +schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } +serde = { version = "1.0", optional = true } +thiserror = "1.0" +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } [dev-dependencies] rand = "0.7.2" rand_chacha = "0.2.2" - [features] default = ["std"] std = [ - "serde", + "codec/std", + "merlin/std", "schnorrkel/std", + "serde", + "sp-core/std", + "sp-externalities/std", ] diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index c45e8a6f5d2be..6540e71bc3fe0 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,19 +29,19 @@ use sp_core::{ use std::sync::Arc; /// CryptoStore error -#[derive(Debug, derive_more::Display)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// Public key type is not supported - #[display(fmt = "Key not supported: {:?}", _0)] + #[error("Key not supported: {0:?}")] KeyNotSupported(KeyTypeId), /// Validation error - #[display(fmt = "Validation error: {}", _0)] + #[error("Validation error: {0}")] ValidationError(String), /// Keystore unavailable - #[display(fmt = "Keystore unavailable")] + #[error("Keystore unavailable")] Unavailable, /// Programming errors - #[display(fmt = "An unknown keystore error occurred: {}", _0)] + #[error("An unknown keystore error occurred: {0}")] Other(String), } diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index 718ba798dc0f3..a9ec6709d912a 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Types that should only be used for testing! use sp_core::{ - crypto::{CryptoTypePublicPair, KeyTypeId, Pair, Public}, + crypto::{ByteArray, CryptoTypePublicPair, KeyTypeId, Pair}, ecdsa, ed25519, sr25519, }; @@ -316,7 +316,7 @@ impl SyncCryptoStore for KeyStore { fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { public_keys .iter() - .all(|(k, t)| self.keys.read().get(&t).and_then(|s| s.get(k)).is_some()) + .all(|(k, t)| self.keys.read().get(t).and_then(|s| s.get(k)).is_some()) } fn supported_keys( @@ -340,20 +340,20 @@ impl SyncCryptoStore for KeyStore { match key.0 { ed25519::CRYPTO_ID => { - let key_pair = - self.ed25519_key_pair(id, &ed25519::Public::from_slice(key.1.as_slice())); + let key_pair = self + .ed25519_key_pair(id, &ed25519::Public::from_slice(key.1.as_slice()).unwrap()); key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, sr25519::CRYPTO_ID => { - let key_pair = - self.sr25519_key_pair(id, &sr25519::Public::from_slice(key.1.as_slice())); + let key_pair = self + .sr25519_key_pair(id, &sr25519::Public::from_slice(key.1.as_slice()).unwrap()); key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, ecdsa::CRYPTO_ID => { let key_pair = - self.ecdsa_key_pair(id, &ecdsa::Public::from_slice(key.1.as_slice())); + self.ecdsa_key_pair(id, &ecdsa::Public::from_slice(key.1.as_slice()).unwrap()); key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, @@ -482,9 +482,7 @@ mod tests { assert!(res.is_none()); // insert key, sign again - let res = - SyncCryptoStore::insert_unknown(&store, ECDSA, suri, pair.public().as_ref()).unwrap(); - assert_eq!((), res); + SyncCryptoStore::insert_unknown(&store, ECDSA, suri, pair.public().as_ref()).unwrap(); let res = SyncCryptoStore::ecdsa_sign_prehashed(&store, ECDSA, &pair.public(), &msg).unwrap(); diff --git a/primitives/keystore/src/vrf.rs b/primitives/keystore/src/vrf.rs index 383abb77e17c7..7409353afe9f4 100644 --- a/primitives/keystore/src/vrf.rs +++ b/primitives/keystore/src/vrf.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/maybe-compressed-blob/Cargo.toml b/primitives/maybe-compressed-blob/Cargo.toml index 8d47c89ea8ebe..7e2780aa23355 100644 --- a/primitives/maybe-compressed-blob/Cargo.toml +++ b/primitives/maybe-compressed-blob/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "sp-maybe-compressed-blob" -version = "4.0.0-dev" +version = "4.1.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Handling of blobs, usually Wasm code, which may be compresed" documentation = "https://docs.rs/sp-maybe-compressed-blob" readme = "README.md" [dependencies] -zstd = { version = "0.6.0", default-features = false } +thiserror = "1.0" +zstd = { version = "0.11.2", default-features = false } diff --git a/primitives/maybe-compressed-blob/src/lib.rs b/primitives/maybe-compressed-blob/src/lib.rs index e8a7e42b4eace..99c12ed39bc04 100644 --- a/primitives/maybe-compressed-blob/src/lib.rs +++ b/primitives/maybe-compressed-blob/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,10 @@ //! Handling of blobs that may be compressed, based on an 8-byte magic identifier //! at the head. -use std::{borrow::Cow, io::Read}; +use std::{ + borrow::Cow, + io::{Read, Write}, +}; // An arbitrary prefix, that indicates a blob beginning with should be decompressed with // Zstd compression. @@ -34,25 +37,16 @@ const ZSTD_PREFIX: [u8; 8] = [82, 188, 83, 118, 70, 219, 142, 5]; pub const CODE_BLOB_BOMB_LIMIT: usize = 50 * 1024 * 1024; /// A possible bomb was encountered. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum Error { /// Decoded size was too large, and the code payload may be a bomb. + #[error("Possible compression bomb encountered")] PossibleBomb, /// The compressed value had an invalid format. + #[error("Blob had invalid format")] Invalid, } -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match *self { - Error::PossibleBomb => write!(f, "Possible compression bomb encountered"), - Error::Invalid => write!(f, "Blob had invalid format"), - } - } -} - -impl std::error::Error for Error {} - fn read_from_decoder( decoder: impl Read, blob_len: usize, @@ -90,8 +84,6 @@ pub fn decompress(blob: &[u8], bomb_limit: usize) -> Result, Error> { /// this will not compress the blob, as the decoder will not be able to be /// able to differentiate it from a compression bomb. pub fn compress(blob: &[u8], bomb_limit: usize) -> Option> { - use std::io::Write; - if blob.len() > bomb_limit { return None } @@ -109,7 +101,6 @@ pub fn compress(blob: &[u8], bomb_limit: usize) -> Option> { #[cfg(test)] mod tests { use super::*; - use std::io::Write; const BOMB_LIMIT: usize = 10; diff --git a/primitives/merkle-mountain-range/Cargo.toml b/primitives/merkle-mountain-range/Cargo.toml new file mode 100644 index 0000000000000..2e532990a5caf --- /dev/null +++ b/primitives/merkle-mountain-range/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "sp-mmr-primitives" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Merkle Mountain Range primitives." + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.5.2", default-features = false } +serde = { version = "1.0.136", features = ["derive"], optional = true } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +thiserror = "1.0" + +[dev-dependencies] +array-bytes = "4.1" + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "mmr-lib/std", + "serde", + "sp-api/std", + "sp-core/std", + "sp-debug-derive/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/frame/merkle-mountain-range/primitives/src/lib.rs b/primitives/merkle-mountain-range/src/lib.rs similarity index 81% rename from frame/merkle-mountain-range/primitives/src/lib.rs rename to primitives/merkle-mountain-range/src/lib.rs index dac57bd42cd35..606906185077c 100644 --- a/frame/merkle-mountain-range/primitives/src/lib.rs +++ b/primitives/merkle-mountain-range/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,12 +20,30 @@ #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] -use frame_support::RuntimeDebug; -use sp_runtime::traits::{self, One, Saturating}; +pub use mmr_lib; + +use scale_info::TypeInfo; +use sp_debug_derive::RuntimeDebug; +use sp_runtime::traits; use sp_std::fmt; #[cfg(not(feature = "std"))] use sp_std::prelude::Vec; +pub mod utils; + +/// Prefix for elements stored in the Off-chain DB via Indexing API. +pub const INDEXING_PREFIX: &'static [u8] = b"mmr"; + +/// A type to describe node position in the MMR (node index). +pub type NodeIndex = u64; + +/// A type to describe leaf position in the MMR. +/// +/// Note this is different from [`NodeIndex`], which can be applied to +/// both leafs and inner nodes. Leafs will always have consecutive `LeafIndex`, +/// but might be actually at different positions in the MMR `NodeIndex`. +pub type LeafIndex = u64; + /// A provider of the MMR's leaf data. pub trait LeafDataProvider { /// A type that should end up in the leaf of MMR. @@ -47,21 +65,6 @@ impl LeafDataProvider for () { } } -/// The most common use case for MMRs is to store historical block hashes, -/// so that any point in time in the future we can receive a proof about some past -/// blocks without using excessive on-chain storage. -/// -/// Hence we implement the [LeafDataProvider] for [frame_system::Pallet]. Since the -/// current block hash is not available (since the block is not finished yet), -/// we use the `parent_hash` here along with parent block number. -impl LeafDataProvider for frame_system::Pallet { - type LeafData = (::BlockNumber, ::Hash); - - fn leaf_data() -> Self::LeafData { - (Self::block_number().saturating_sub(One::one()), Self::parent_hash()) - } -} - /// New MMR root notification hook. pub trait OnNewRoot { /// Function called by the pallet in case new MMR root has been computed. @@ -75,7 +78,7 @@ impl OnNewRoot for () { /// A full leaf content stored in the offchain-db. pub trait FullLeaf: Clone + PartialEq + fmt::Debug { - /// Encode the leaf either in it's full or compact form. + /// Encode the leaf either in its full or compact form. /// /// NOTE the encoding returned here MUST be `Decode`able into `FullLeaf`. fn using_encoded R>(&self, f: F, compact: bool) -> R; @@ -87,18 +90,92 @@ impl FullLeaf } } -/// An element representing either full data or it's hash. +/// A helper type to allow using arbitrary SCALE-encoded leaf data in the RuntimeApi. +/// +/// The point is to be able to verify MMR proofs from external MMRs, where we don't +/// know the exact leaf type, but it's enough for us to have it SCALE-encoded. +/// +/// Note the leaf type should be encoded in its compact form when passed through this type. +/// See [FullLeaf] documentation for details. +/// +/// This type does not implement SCALE encoding/decoding on purpose to avoid confusion, +/// it would have to be SCALE-compatible with the concrete leaf type, but due to SCALE limitations +/// it's not possible to know how many bytes the encoding of concrete leaf type uses. +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[derive(RuntimeDebug, Clone, PartialEq)] +pub struct OpaqueLeaf( + /// Raw bytes of the leaf type encoded in its compact form. + /// + /// NOTE it DOES NOT include length prefix (like `Vec` encoding would). + #[cfg_attr(feature = "std", serde(with = "sp_core::bytes"))] + pub Vec, +); + +impl OpaqueLeaf { + /// Convert a concrete MMR leaf into an opaque type. + pub fn from_leaf(leaf: &T) -> Self { + let encoded_leaf = leaf.using_encoded(|d| d.to_vec(), true); + OpaqueLeaf::from_encoded_leaf(encoded_leaf) + } + + /// Create a `OpaqueLeaf` given raw bytes of compact-encoded leaf. + pub fn from_encoded_leaf(encoded_leaf: Vec) -> Self { + OpaqueLeaf(encoded_leaf) + } + + /// Attempt to decode the leaf into expected concrete type. + pub fn try_decode(&self) -> Option { + codec::Decode::decode(&mut &*self.0).ok() + } +} + +impl FullLeaf for OpaqueLeaf { + fn using_encoded R>(&self, f: F, _compact: bool) -> R { + f(&self.0) + } +} + +/// A type-safe wrapper for the concrete leaf type. +/// +/// This structure serves merely to avoid passing raw `Vec` around. +/// It must be `Vec`-encoding compatible. +/// +/// It is different from [`OpaqueLeaf`], because it does implement `Codec` +/// and the encoding has to match raw `Vec` encoding. +#[derive(codec::Encode, codec::Decode, RuntimeDebug, PartialEq, Eq)] +pub struct EncodableOpaqueLeaf(pub Vec); + +impl EncodableOpaqueLeaf { + /// Convert a concrete leaf into encodable opaque version. + pub fn from_leaf(leaf: &T) -> Self { + let opaque = OpaqueLeaf::from_leaf(leaf); + Self::from_opaque_leaf(opaque) + } + + /// Given an opaque leaf, make it encodable. + pub fn from_opaque_leaf(opaque: OpaqueLeaf) -> Self { + Self(opaque.0) + } + + /// Try to convert into a [OpaqueLeaf]. + pub fn into_opaque_leaf(self) -> OpaqueLeaf { + // wrap into `OpaqueLeaf` type + OpaqueLeaf::from_encoded_leaf(self.0) + } +} + +/// An element representing either full data or its hash. /// /// See [Compact] to see how it may be used in practice to reduce the size /// of proofs in case multiple [LeafDataProvider]s are composed together. /// This is also used internally by the MMR to differentiate leaf nodes (data) /// and inner nodes (hashes). /// -/// [DataOrHash::hash] method calculates the hash of this element in it's compact form, +/// [DataOrHash::hash] method calculates the hash of this element in its compact form, /// so should be used instead of hashing the encoded form (which will always be non-compact). #[derive(RuntimeDebug, Clone, PartialEq)] pub enum DataOrHash { - /// Arbitrary data in it's full form. + /// Arbitrary data in its full form. Data(L), /// A hash of some data. Hash(H::Output), @@ -151,7 +228,7 @@ impl DataOrHash { pub fn hash(&self) -> H::Output { match *self { Self::Data(ref leaf) => leaf.using_encoded(::hash, true), - Self::Hash(ref hash) => hash.clone(), + Self::Hash(ref hash) => *hash, } } } @@ -259,7 +336,7 @@ where A: FullLeaf, B: FullLeaf, { - /// Retrieve a hash of this item in it's compact form. + /// Retrieve a hash of this item in its compact form. pub fn hash(&self) -> H::Output { self.using_encoded(::hash, true) } @@ -271,32 +348,51 @@ impl_leaf_data_for_tuple!(A:0, B:1, C:2); impl_leaf_data_for_tuple!(A:0, B:1, C:2, D:3); impl_leaf_data_for_tuple!(A:0, B:1, C:2, D:3, E:4); -/// A MMR proof data for one of the leaves. -#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq)] +/// An MMR proof data for a group of leaves. +#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)] pub struct Proof { - /// The index of the leaf the proof is for. - pub leaf_index: u64, + /// The indices of the leaves the proof is for. + pub leaf_indices: Vec, /// Number of leaves in MMR, when the proof was generated. - pub leaf_count: u64, + pub leaf_count: NodeIndex, /// Proof elements (hashes of siblings of inner nodes on the path to the leaf). pub items: Vec, } /// Merkle Mountain Range operation error. +#[cfg_attr(feature = "std", derive(thiserror::Error))] #[derive(RuntimeDebug, codec::Encode, codec::Decode, PartialEq, Eq)] pub enum Error { + /// Error during translation of a block number into a leaf index. + #[cfg_attr(feature = "std", error("Error performing numeric op"))] + InvalidNumericOp, /// Error while pushing new node. + #[cfg_attr(feature = "std", error("Error pushing new node"))] Push, /// Error getting the new root. + #[cfg_attr(feature = "std", error("Error getting new root"))] GetRoot, - /// Error commiting changes. + /// Error committing changes. + #[cfg_attr(feature = "std", error("Error committing changes"))] Commit, /// Error during proof generation. + #[cfg_attr(feature = "std", error("Error generating proof"))] GenerateProof, /// Proof verification error. + #[cfg_attr(feature = "std", error("Invalid proof"))] Verify, /// Leaf not found in the storage. + #[cfg_attr(feature = "std", error("Leaf was not found"))] LeafNotFound, + /// Mmr Pallet not included in runtime + #[cfg_attr(feature = "std", error("MMR pallet not included in runtime"))] + PalletNotIncluded, + /// Cannot find the requested leaf index + #[cfg_attr(feature = "std", error("Requested leaf index invalid"))] + InvalidLeafIndex, + /// The provided best know block number is invalid. + #[cfg_attr(feature = "std", error("Provided best known block number invalid"))] + InvalidBestKnownBlock, } impl Error { @@ -324,100 +420,37 @@ impl Error { } } -/// A helper type to allow using arbitrary SCALE-encoded leaf data in the RuntimeApi. -/// -/// The point is to be able to verify MMR proofs from external MMRs, where we don't -/// know the exact leaf type, but it's enough for us to have it SCALE-encoded. -/// -/// Note the leaf type should be encoded in its compact form when passed through this type. -/// See [FullLeaf] documentation for details. -/// -/// This type does not implement SCALE encoding/decoding on purpose to avoid confusion, -/// it would have to be SCALE-compatible with the concrete leaf type, but due to SCALE limitations -/// it's not possible to know how many bytes the encoding of concrete leaf type uses. -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -#[derive(RuntimeDebug, Clone, PartialEq)] -pub struct OpaqueLeaf( - /// Raw bytes of the leaf type encoded in its compact form. - /// - /// NOTE it DOES NOT include length prefix (like `Vec` encoding would). - #[cfg_attr(feature = "std", serde(with = "sp_core::bytes"))] - pub Vec, -); - -impl OpaqueLeaf { - /// Convert a concrete MMR leaf into an opaque type. - pub fn from_leaf(leaf: &T) -> Self { - let encoded_leaf = leaf.using_encoded(|d| d.to_vec(), true); - OpaqueLeaf::from_encoded_leaf(encoded_leaf) - } - - /// Create a `OpaqueLeaf` given raw bytes of compact-encoded leaf. - pub fn from_encoded_leaf(encoded_leaf: Vec) -> Self { - OpaqueLeaf(encoded_leaf) - } - - /// Attempt to decode the leaf into expected concrete type. - pub fn try_decode(&self) -> Option { - codec::Decode::decode(&mut &*self.0).ok() - } -} - -impl FullLeaf for OpaqueLeaf { - fn using_encoded R>(&self, f: F, _compact: bool) -> R { - f(&self.0) - } -} - -/// A type-safe wrapper for the concrete leaf type. -/// -/// This structure serves merely to avoid passing raw `Vec` around. -/// It must be `Vec`-encoding compatible. -/// -/// It is different from [`OpaqueLeaf`], because it does implement `Codec` -/// and the encoding has to match raw `Vec` encoding. -#[derive(codec::Encode, codec::Decode, RuntimeDebug, PartialEq, Eq)] -pub struct EncodableOpaqueLeaf(pub Vec); - -impl EncodableOpaqueLeaf { - /// Convert a concrete leaf into encodable opaque version. - pub fn from_leaf(leaf: &T) -> Self { - let opaque = OpaqueLeaf::from_leaf(leaf); - Self::from_opaque_leaf(opaque) - } - - /// Given an opaque leaf, make it encodable. - pub fn from_opaque_leaf(opaque: OpaqueLeaf) -> Self { - Self(opaque.0) - } - - /// Try to convert into a [OpaqueLeaf]. - pub fn into_opaque_leaf(self) -> OpaqueLeaf { - // wrap into `OpaqueLeaf` type - OpaqueLeaf::from_encoded_leaf(self.0) - } -} - sp_api::decl_runtime_apis! { /// API to interact with MMR pallet. - pub trait MmrApi { - /// Generate MMR proof for a leaf under given index. - fn generate_proof(leaf_index: u64) -> Result<(EncodableOpaqueLeaf, Proof), Error>; + pub trait MmrApi { + /// Return the on-chain MMR root hash. + fn mmr_root() -> Result; + + /// Return the number of MMR blocks in the chain. + fn mmr_leaf_count() -> Result; + + /// Generate MMR proof for a series of block numbers. If `best_known_block_number = Some(n)`, + /// use historical MMR state at given block height `n`. Else, use current MMR state. + fn generate_proof( + block_numbers: Vec, + best_known_block_number: Option + ) -> Result<(Vec, Proof), Error>; - /// Verify MMR proof against on-chain MMR. + /// Verify MMR proof against on-chain MMR for a batch of leaves. /// - /// Note this function will use on-chain MMR root hash and check if the proof - /// matches the hash. - /// See [Self::verify_proof_stateless] for a stateless verifier. - fn verify_proof(leaf: EncodableOpaqueLeaf, proof: Proof) -> Result<(), Error>; + /// Note this function will use on-chain MMR root hash and check if the proof matches the hash. + /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the + /// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [Proof] + fn verify_proof(leaves: Vec, proof: Proof) -> Result<(), Error>; - /// Verify MMR proof against given root hash. + /// Verify MMR proof against given root hash for a batch of leaves. /// /// Note this function does not require any on-chain storage - the /// proof is verified against given MMR root hash. /// - /// The leaf data is expected to be encoded in it's compact form. - fn verify_proof_stateless(root: Hash, leaf: EncodableOpaqueLeaf, proof: Proof) + /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the + /// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [Proof] + fn verify_proof_stateless(root: Hash, leaves: Vec, proof: Proof) -> Result<(), Error>; } } @@ -442,7 +475,7 @@ mod tests { fn should_encode_decode_proof() { // given let proof: TestProof = Proof { - leaf_index: 5, + leaf_indices: vec![5], leaf_count: 10, items: vec![ hex("c3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd"), @@ -480,11 +513,16 @@ mod tests { cases.into_iter().map(Result::<_, codec::Error>::Ok).collect::>() ); // check encoding correctness - assert_eq!(&encoded[0], &hex_literal::hex!("00343048656c6c6f20576f726c6421")); + assert_eq!( + &encoded[0], + &array_bytes::hex2bytes_unchecked("00343048656c6c6f20576f726c6421") + ); assert_eq!( encoded[1].as_slice(), - hex_literal::hex!("01c3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd") - .as_ref() + array_bytes::hex2bytes_unchecked( + "01c3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd" + ) + .as_slice() ); } diff --git a/primitives/merkle-mountain-range/src/utils.rs b/primitives/merkle-mountain-range/src/utils.rs new file mode 100644 index 0000000000000..619ca7e98160f --- /dev/null +++ b/primitives/merkle-mountain-range/src/utils.rs @@ -0,0 +1,215 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Merkle Mountain Range utilities. + +use codec::Encode; +use mmr_lib::helper; + +use sp_runtime::traits::{CheckedAdd, CheckedSub, Header, One}; +#[cfg(not(feature = "std"))] +use sp_std::prelude::Vec; + +use crate::{Error, LeafIndex, NodeIndex}; + +/// Get the first block with MMR. +pub fn first_mmr_block_num( + best_block_num: H::Number, + mmr_leaf_count: LeafIndex, +) -> Result { + let mmr_blocks_count = mmr_leaf_count.try_into().map_err(|_| { + Error::InvalidNumericOp + .log_debug("The number of leaves couldn't be converted to a block number.") + })?; + best_block_num + .checked_sub(&mmr_blocks_count) + .and_then(|last_non_mmr_block| last_non_mmr_block.checked_add(&One::one())) + .ok_or_else(|| { + Error::InvalidNumericOp + .log_debug("The best block should be greater than the number of mmr blocks.") + }) +} + +/// Convert a block number into a leaf index. +pub fn block_num_to_leaf_index( + block_num: H::Number, + first_mmr_block_num: H::Number, +) -> Result { + let leaf_idx = block_num.checked_sub(&first_mmr_block_num).ok_or_else(|| { + Error::InvalidNumericOp + .log_debug("The provided block should be greater than the first mmr block.") + })?; + + leaf_idx.try_into().map_err(|_| { + Error::InvalidNumericOp.log_debug("Couldn't convert the leaf index to `LeafIndex`.") + }) +} + +/// MMR nodes & size -related utilities. +pub struct NodesUtils { + no_of_leaves: LeafIndex, +} + +impl NodesUtils { + /// Create new instance of MMR nodes utilities for given number of leaves. + pub fn new(no_of_leaves: LeafIndex) -> Self { + Self { no_of_leaves } + } + + /// Calculate number of peaks in the MMR. + pub fn number_of_peaks(&self) -> NodeIndex { + self.number_of_leaves().count_ones() as NodeIndex + } + + /// Return the number of leaves in the MMR. + pub fn number_of_leaves(&self) -> LeafIndex { + self.no_of_leaves + } + + /// Calculate the total size of MMR (number of nodes). + pub fn size(&self) -> NodeIndex { + 2 * self.no_of_leaves - self.number_of_peaks() + } + + /// Calculate `LeafIndex` for the leaf that added `node_index` to the MMR. + pub fn leaf_index_that_added_node(node_index: NodeIndex) -> LeafIndex { + let rightmost_leaf_pos = Self::rightmost_leaf_node_index_from_pos(node_index); + Self::leaf_node_index_to_leaf_index(rightmost_leaf_pos) + } + + // Translate a _leaf_ `NodeIndex` to its `LeafIndex`. + fn leaf_node_index_to_leaf_index(pos: NodeIndex) -> LeafIndex { + if pos == 0 { + return 0 + } + let peaks = helper::get_peaks(pos); + (pos + peaks.len() as u64) >> 1 + } + + // Starting from any node position get position of rightmost leaf; this is the leaf + // responsible for the addition of node `pos`. + fn rightmost_leaf_node_index_from_pos(pos: NodeIndex) -> NodeIndex { + pos - (helper::pos_height_in_tree(pos) as u64) + } + + /// Starting from any leaf index, get the sequence of positions of the nodes added + /// to the mmr when this leaf was added (inclusive of the leaf's position itself). + /// That is, all of these nodes are right children of their respective parents. + pub fn right_branch_ending_in_leaf(leaf_index: LeafIndex) -> Vec { + let pos = helper::leaf_index_to_pos(leaf_index); + let num_parents = leaf_index.trailing_ones() as u64; + return (pos..=pos + num_parents).collect() + } + + /// Build offchain key from `parent_hash` of block that originally added node `pos` to MMR. + /// + /// This combination makes the offchain (key,value) entry resilient to chain forks. + pub fn node_temp_offchain_key( + prefix: &[u8], + pos: NodeIndex, + parent_hash: H::Hash, + ) -> Vec { + (prefix, pos, parent_hash).encode() + } + + /// Build canonical offchain key for node `pos` in MMR. + /// + /// Used for nodes added by now finalized blocks. + /// Never read keys using `node_canon_offchain_key` unless you sure that + /// there's no `node_offchain_key` key in the storage. + pub fn node_canon_offchain_key(prefix: &[u8], pos: NodeIndex) -> sp_std::prelude::Vec { + (prefix, pos).encode() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mmr_lib::helper::leaf_index_to_pos; + + #[test] + fn should_calculate_node_index_from_leaf_index() { + for index in 0..100000 { + let pos = leaf_index_to_pos(index); + assert_eq!(NodesUtils::leaf_node_index_to_leaf_index(pos), index); + } + } + + #[test] + fn should_calculate_right_branch_correctly() { + fn left_jump_sequence(leaf_index: LeafIndex) -> Vec { + let pos = leaf_index_to_pos(leaf_index); + let mut right_branch_ending_in_leaf = vec![pos]; + let mut next_pos = pos + 1; + while mmr_lib::helper::pos_height_in_tree(next_pos) > 0 { + right_branch_ending_in_leaf.push(next_pos); + next_pos += 1; + } + right_branch_ending_in_leaf + } + + for leaf_index in 0..100000 { + let pos = mmr_lib::helper::leaf_index_to_pos(leaf_index); + assert_eq!(NodesUtils::right_branch_ending_in_leaf(pos), left_jump_sequence(pos)); + } + } + + #[test] + fn should_calculate_rightmost_leaf_node_index_from_pos() { + for pos in 0..100000 { + let leaf_pos = NodesUtils::rightmost_leaf_node_index_from_pos(pos); + let leaf_index = NodesUtils::leaf_node_index_to_leaf_index(leaf_pos); + assert!(NodesUtils::right_branch_ending_in_leaf(leaf_index).contains(&pos)); + } + } + + #[test] + fn should_calculate_depth_correctly() { + assert_eq!( + vec![0, 1, 2, 3, 4, 9, 15, 21] + .into_iter() + .map(|n| NodesUtils::new(n).number_of_leaves()) + .collect::>(), + vec![0, 1, 2, 3, 4, 9, 15, 21] + ); + } + + #[test] + fn should_calculate_number_of_peaks_correctly() { + assert_eq!( + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21] + .into_iter() + .map(|n| NodesUtils::new(n).number_of_peaks()) + .collect::>(), + vec![0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 3] + ); + } + + #[test] + fn should_calculate_the_size_correctly() { + let leaves = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21]; + let sizes = vec![0, 1, 3, 4, 7, 8, 10, 11, 15, 16, 18, 19, 22, 23, 25, 26, 39]; + assert_eq!( + leaves + .clone() + .into_iter() + .map(|n| NodesUtils::new(n).size()) + .collect::>(), + sizes.clone() + ); + } +} diff --git a/primitives/npos-elections/Cargo.toml b/primitives/npos-elections/Cargo.toml index b277df8f58f12..b99b05e0e3a09 100644 --- a/primitives/npos-elections/Cargo.toml +++ b/primitives/npos-elections/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-npos-elections" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "NPoS election algorithm primitives" readme = "README.md" @@ -13,18 +13,17 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.126", optional = true, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -sp-npos-elections-solution-type = { version = "4.0.0-dev", path = "./solution-type" } -sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../arithmetic" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -sp-runtime = { version = "4.0.0-dev", path = "../runtime", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../arithmetic" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [dev-dependencies] -substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } rand = "0.7.3" +substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } [features] default = ["std"] @@ -33,8 +32,8 @@ std = [ "codec/std", "scale-info/std", "serde", - "sp-std/std", "sp-arithmetic/std", "sp-core/std", "sp-runtime/std", + "sp-std/std", ] diff --git a/primitives/npos-elections/README.md b/primitives/npos-elections/README.md index b518e63615fa6..6881fc6418f3a 100644 --- a/primitives/npos-elections/README.md +++ b/primitives/npos-elections/README.md @@ -8,7 +8,7 @@ sub-system. Notable implementation include: it can achieve a constant factor approximation of the maximin problem, similar to that of the MMS algorithm. - [`balance_solution`]: Implements the star balancing algorithm. This iterative process can push - a solution toward being more `balances`, which in turn can increase its score. + a solution toward being more `balanced`, which in turn can increase its score. ### Terminology @@ -46,7 +46,7 @@ let election_result = ElectionResult { winners, assignments }; The `Assignment` field of the election result is voter-major, i.e. it is from the perspective of the voter. The struct that represents the opposite is called a `Support`. This struct is usually -accessed in a map-like manner, i.e. keyed vy voters, therefor it is stored as a mapping called +accessed in a map-like manner, i.e. keyed by voters, therefore it is stored as a mapping called `SupportMap`. Moreover, the support is built from absolute backing values, not ratios like the example above. diff --git a/primitives/npos-elections/fuzzer/Cargo.lock b/primitives/npos-elections/fuzzer/Cargo.lock deleted file mode 100644 index cd172421aeb28..0000000000000 --- a/primitives/npos-elections/fuzzer/Cargo.lock +++ /dev/null @@ -1,1602 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "ahash" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f33b5018f120946c1dcf279194f238a9f146725593ead1c08fa47ff22b0b5d3" -dependencies = [ - "const-random", -] - -[[package]] -name = "aho-corasick" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" -dependencies = [ - "memchr", -] - -[[package]] -name = "arbitrary" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cf76cb6e2222ed0ea86b2b0ee2f71c96ec6edd5af42e84d59160e91b836ec4" - -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" -dependencies = [ - "nodrop", -] - -[[package]] -name = "arrayvec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" - -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - -[[package]] -name = "autocfg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "backtrace" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" -dependencies = [ - "backtrace-sys", - "cfg-if", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "base58" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "bitvec" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6" - -[[package]] -name = "blake2-rfc" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" -dependencies = [ - "arrayvec 0.4.12", - "constant_time_eq", -] - -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - -[[package]] -name = "byte-slice-cast" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - -[[package]] -name = "byteorder" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" - -[[package]] -name = "cc" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "clear_on_drop" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" -dependencies = [ - "cc", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - -[[package]] -name = "const-random" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f1af9ac737b2dd2d577701e59fd09ba34822f6f2ebdb30a7647405d9e55e16a" -dependencies = [ - "const-random-macro", - "proc-macro-hack", -] - -[[package]] -name = "const-random-macro" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a" -dependencies = [ - "getrandom", - "proc-macro-hack", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array", - "subtle 1.0.0", -] - -[[package]] -name = "curve25519-dalek" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d" -dependencies = [ - "byteorder", - "clear_on_drop", - "digest", - "rand_core 0.3.1", - "subtle 2.2.2", -] - -[[package]] -name = "curve25519-dalek" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" -dependencies = [ - "byteorder", - "digest", - "rand_core 0.5.1", - "subtle 2.2.2", - "zeroize 1.1.0", -] - -[[package]] -name = "derive_more" -version = "0.99.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a806e96c59a76a5ba6e18735b6cf833344671e61e7863f2edb5c518ea2cac95c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.0-pre.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" -dependencies = [ - "clear_on_drop", - "curve25519-dalek 2.0.0", - "rand 0.7.3", - "sha2", -] - -[[package]] -name = "environmental" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516aa8d7a71cb00a1c4146f0798549b93d083d4f189b3ced8f3de6b8f11ee6c4" - -[[package]] -name = "failure" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - -[[package]] -name = "fixed-hash" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3367952ceb191f4ab95dd5685dc163ac539e36202f9fcfd0cb22f9f9c542fefc" -dependencies = [ - "byteorder", - "libc", - "rand 0.7.3", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "generic-array" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -dependencies = [ - "typenum", -] - -[[package]] -name = "getrandom" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hash-db" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" - -[[package]] -name = "hash256-std-hasher" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" -dependencies = [ - "crunchy", -] - -[[package]] -name = "hashbrown" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6073d0ca812575946eb5f35ff68dbe519907b25c42530389ff946dc84c6ead" -dependencies = [ - "ahash", - "autocfg 0.1.7", -] - -[[package]] -name = "hex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" - -[[package]] -name = "hmac" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" -dependencies = [ - "crypto-mac", - "digest", -] - -[[package]] -name = "hmac-drbg" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" -dependencies = [ - "digest", - "generic-array", - "hmac", -] - -[[package]] -name = "honggfuzz" -version = "0.5.45" -dependencies = [ - "arbitrary", - "lazy_static", - "memmap", -] - -[[package]] -name = "impl-codec" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-serde" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e3cae7e99c7ff5a995da2cf78dd0a5383740eda71d98cf7b1910c301ac69b8" -dependencies = [ - "serde", -] - -[[package]] -name = "impl-serde" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bbe9ea9b182f0fb1cabbd61f4ff9b7b7b9197955e95a7e4c27de5055eb29ff8" -dependencies = [ - "serde", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef5550a42e3740a0e71f909d4c861056a284060af885ae7aa6242820f920d9d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "integer-sqrt" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65877bf7d44897a473350b1046277941cee20b263397e90869c50b6e766088b" - -[[package]] -name = "keccak" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" - -[[package]] -name = "libsecp256k1" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" -dependencies = [ - "arrayref", - "crunchy", - "digest", - "hmac-drbg", - "rand 0.7.3", - "sha2", - "subtle 2.2.2", - "typenum", -] - -[[package]] -name = "lock_api" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - -[[package]] -name = "memchr" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" - -[[package]] -name = "memmap" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "memory-db" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198831fe8722331a395bc199a5d08efbc197497ef354cb4c77b969c02ffc0fc4" -dependencies = [ - "ahash", - "hash-db", - "hashbrown", - "parity-util-mem", -] - -[[package]] -name = "memory_units" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" - -[[package]] -name = "merlin" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0942b357c1b4d0dc43ba724674ec89c3218e6ca2b3e8269e7cb53bcecd2f6e" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.4.2", - "zeroize 1.1.0", -] - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "num-bigint" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" -dependencies = [ - "autocfg 1.0.0", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" -dependencies = [ - "autocfg 1.0.0", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" -dependencies = [ - "autocfg 1.0.0", - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" -dependencies = [ - "autocfg 1.0.0", -] - -[[package]] -name = "once_cell" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" -dependencies = [ - "parking_lot 0.9.0", -] - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[package]] -name = "parity-scale-codec" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f509c5e67ca0605ee17dcd3f91ef41cadd685c75a298fb6261b781a5acb3f910" -dependencies = [ - "arrayvec 0.5.1", - "bitvec", - "byte-slice-cast", - "parity-scale-codec-derive", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0ec292e92e8ec7c58e576adacc1e3f399c597c8f263c42f18420abe58e7245" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "parity-util-mem" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1476e40bf8f5c6776e9600983435821ca86eb9819d74a6207cca69d091406a" -dependencies = [ - "cfg-if", - "impl-trait-for-tuples", - "parity-util-mem-derive", - "parking_lot 0.10.0", - "primitive-types", - "winapi", -] - -[[package]] -name = "parity-util-mem-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" -dependencies = [ - "proc-macro2", - "syn", - "synstructure", -] - -[[package]] -name = "parity-wasm" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" - -[[package]] -name = "parking_lot" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -dependencies = [ - "lock_api", - "parking_lot_core 0.6.2", - "rustc_version", -] - -[[package]] -name = "parking_lot" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" -dependencies = [ - "lock_api", - "parking_lot_core 0.7.0", -] - -[[package]] -name = "parking_lot_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -dependencies = [ - "cfg-if", - "cloudabi", - "libc", - "redox_syscall", - "rustc_version", - "smallvec 0.6.13", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" -dependencies = [ - "cfg-if", - "cloudabi", - "libc", - "redox_syscall", - "smallvec 1.3.0", - "winapi", -] - -[[package]] -name = "paste" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e1afe738d71b1ebab5f1207c055054015427dbfc7bbe9ee1266894156ec046" -dependencies = [ - "paste-impl", - "proc-macro-hack", -] - -[[package]] -name = "paste-impl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4dc4a7f6f743211c5aab239640a65091535d97d43d92a52bca435a640892bb" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pbkdf2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" -dependencies = [ - "byteorder", - "crypto-mac", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" - -[[package]] -name = "primitive-types" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4336f4f5d5524fa60bcbd6fe626f9223d8142a50e7053e979acdf0da41ab975" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-serde 0.3.0", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.7", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" - -[[package]] -name = "regex" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", - "thread_local", -] - -[[package]] -name = "regex-syntax" -version = "0.6.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1" - -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "schnorrkel" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eacd8381b3c37840c9c9f40472af529e49975bdcbc24f83c31059fd6539023d3" -dependencies = [ - "curve25519-dalek 1.2.3", - "failure", - "merlin", - "rand 0.6.5", - "rand_core 0.4.2", - "rand_os", - "sha2", - "subtle 2.2.2", - "zeroize 0.9.3", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sha2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" -dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", -] - -[[package]] -name = "smallvec" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" -dependencies = [ - "maybe-uninit", -] - -[[package]] -name = "smallvec" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" - -[[package]] -name = "sp-application-crypto" -version = "2.0.0-alpha.3" -dependencies = [ - "parity-scale-codec", - "serde", - "sp-core", - "sp-io", - "sp-std", -] - -[[package]] -name = "sp-arithmetic" -version = "2.0.0-alpha.3" -dependencies = [ - "integer-sqrt", - "num-traits", - "parity-scale-codec", - "serde", - "sp-debug-derive", - "sp-std", -] - -[[package]] -name = "sp-core" -version = "2.0.0-alpha.3" -dependencies = [ - "base58", - "blake2-rfc", - "byteorder", - "ed25519-dalek", - "hash-db", - "hash256-std-hasher", - "hex", - "impl-serde 0.3.0", - "lazy_static", - "libsecp256k1", - "log", - "num-traits", - "parity-scale-codec", - "parity-util-mem", - "parking_lot 0.10.0", - "primitive-types", - "rand 0.7.3", - "regex", - "rustc-hex", - "schnorrkel", - "serde", - "sha2", - "sp-debug-derive", - "sp-externalities", - "sp-runtime-interface", - "sp-std", - "sp-storage", - "substrate-bip39", - "tiny-bip39", - "tiny-keccak", - "twox-hash", - "wasmi", - "zeroize 1.1.0", -] - -[[package]] -name = "sp-debug-derive" -version = "2.0.0-alpha.3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sp-externalities" -version = "0.8.0-alpha.3" -dependencies = [ - "environmental", - "sp-std", - "sp-storage", -] - -[[package]] -name = "sp-inherents" -version = "2.0.0-alpha.3" -dependencies = [ - "derive_more", - "parity-scale-codec", - "parking_lot 0.10.0", - "sp-core", - "sp-std", -] - -[[package]] -name = "sp-io" -version = "2.0.0-alpha.3" -dependencies = [ - "hash-db", - "libsecp256k1", - "log", - "parity-scale-codec", - "sp-core", - "sp-externalities", - "sp-runtime-interface", - "sp-state-machine", - "sp-std", - "sp-trie", - "sp-wasm-interface", -] - -[[package]] -name = "sp-panic-handler" -version = "2.0.0-alpha.3" -dependencies = [ - "backtrace", - "log", -] - -[[package]] -name = "sp-npos-elections" -version = "2.0.0-alpha.3" -dependencies = [ - "parity-scale-codec", - "serde", - "sp-core", - "sp-npos-elections-compact", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "sp-npos-elections-compact" -version = "2.0.0-rc3" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sp-npos-elections-fuzzer" -version = "2.0.0" -dependencies = [ - "honggfuzz", - "rand 0.7.3", - "sp-npos-elections", -] - -[[package]] -name = "sp-runtime" -version = "2.0.0-alpha.3" -dependencies = [ - "hash256-std-hasher", - "impl-trait-for-tuples", - "log", - "parity-scale-codec", - "parity-util-mem", - "paste", - "rand 0.7.3", - "serde", - "sp-application-crypto", - "sp-arithmetic", - "sp-core", - "sp-inherents", - "sp-io", - "sp-std", -] - -[[package]] -name = "sp-runtime-interface" -version = "2.0.0-alpha.3" -dependencies = [ - "parity-scale-codec", - "primitive-types", - "sp-externalities", - "sp-runtime-interface-proc-macro", - "sp-std", - "sp-wasm-interface", - "static_assertions", -] - -[[package]] -name = "sp-runtime-interface-proc-macro" -version = "2.0.0-alpha.3" -dependencies = [ - "Inflector", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sp-state-machine" -version = "0.8.0-alpha.3" -dependencies = [ - "hash-db", - "log", - "num-traits", - "parity-scale-codec", - "parking_lot 0.10.0", - "rand 0.7.3", - "sp-core", - "sp-externalities", - "sp-panic-handler", - "sp-trie", - "trie-db", - "trie-root", -] - -[[package]] -name = "sp-std" -version = "2.0.0-alpha.3" - -[[package]] -name = "sp-storage" -version = "2.0.0-alpha.3" -dependencies = [ - "impl-serde 0.2.3", - "serde", - "sp-debug-derive", - "sp-std", -] - -[[package]] -name = "sp-trie" -version = "2.0.0-alpha.3" -dependencies = [ - "hash-db", - "memory-db", - "parity-scale-codec", - "sp-core", - "sp-std", - "trie-db", - "trie-root", -] - -[[package]] -name = "sp-wasm-interface" -version = "2.0.0-alpha.3" -dependencies = [ - "impl-trait-for-tuples", - "parity-scale-codec", - "sp-std", - "wasmi", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "substrate-bip39" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be511be555a3633e71739a79e4ddff6a6aaa6579fa6114182a51d72c3eb93c5" -dependencies = [ - "hmac", - "pbkdf2", - "schnorrkel", - "sha2", -] - -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - -[[package]] -name = "subtle" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" - -[[package]] -name = "syn" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "synstructure" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "thread_local" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "tiny-bip39" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6848cd8f566953ce1e8faeba12ee23cbdbb0437754792cd857d44628b5685e3" -dependencies = [ - "failure", - "hmac", - "once_cell", - "pbkdf2", - "rand 0.7.3", - "rustc-hash", - "sha2", - "unicode-normalization", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2953ca5148619bc99695c1274cb54c5275bbb913c6adad87e72eaf8db9787f69" -dependencies = [ - "crunchy", -] - -[[package]] -name = "toml" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" -dependencies = [ - "serde", -] - -[[package]] -name = "trie-db" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9222c50cc325855621271157c973da27a0dcd26fa06f8edf81020bd2333df0" -dependencies = [ - "hash-db", - "hashbrown", - "log", - "rustc-hex", - "smallvec 1.3.0", -] - -[[package]] -name = "trie-root" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "652931506d2c1244d7217a70b99f56718a7b4161b37f04e7cd868072a99f68cd" -dependencies = [ - "hash-db", -] - -[[package]] -name = "twox-hash" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56" -dependencies = [ - "rand 0.7.3", -] - -[[package]] -name = "typenum" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" - -[[package]] -name = "uint" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75a4cdd7b87b28840dba13c483b9a88ee6bbf16ba5c951ee1ecfcf723078e0d" -dependencies = [ - "byteorder", - "crunchy", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" -dependencies = [ - "smallvec 1.3.0", -] - -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasmi" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf617d864d25af3587aa745529f7aaa541066c876d57e050c0d0c85c61c92aff" -dependencies = [ - "libc", - "memory_units", - "num-rational", - "num-traits", - "parity-wasm", - "wasmi-validation", -] - -[[package]] -name = "wasmi-validation" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea78c597064ba73596099281e2f4cfc019075122a65cdda3205af94f0b264d93" -dependencies = [ - "parity-wasm", -] - -[[package]] -name = "winapi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "zeroize" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86" - -[[package]] -name = "zeroize" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] diff --git a/primitives/npos-elections/fuzzer/Cargo.toml b/primitives/npos-elections/fuzzer/Cargo.toml index d6fcc09c8b586..860ed6b18810d 100644 --- a/primitives/npos-elections/fuzzer/Cargo.toml +++ b/primitives/npos-elections/fuzzer/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Fuzzer for phragmén implementation." documentation = "https://docs.rs/sp-npos-elections-fuzzer" @@ -14,13 +14,13 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +clap = { version = "4.0.9", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } honggfuzz = "0.5" -rand = { version = "0.7.3", features = ["std", "small_rng"] } +rand = { version = "0.8", features = ["std", "small_rng"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-npos-elections = { version = "4.0.0-dev", path = ".." } -sp-runtime = { version = "4.0.0-dev", path = "../../runtime" } -structopt = "0.3.21" +sp-runtime = { version = "7.0.0", path = "../../runtime" } [[bin]] name = "reduce" @@ -34,10 +34,6 @@ path = "src/phragmen_balancing.rs" name = "phragmms_balancing" path = "src/phragmms_balancing.rs" -[[bin]] -name = "compact" -path = "src/compact.rs" - [[bin]] name = "phragmen_pjr" path = "src/phragmen_pjr.rs" diff --git a/primitives/npos-elections/fuzzer/src/common.rs b/primitives/npos-elections/fuzzer/src/common.rs index e97f7f7df8b11..ad9bd43f9bce0 100644 --- a/primitives/npos-elections/fuzzer/src/common.rs +++ b/primitives/npos-elections/fuzzer/src/common.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ #![allow(dead_code)] use rand::{self, seq::SliceRandom, Rng, RngCore}; -use sp_npos_elections::{phragmms, seq_phragmen, ElectionResult, VoteWeight}; +use sp_npos_elections::{phragmms, seq_phragmen, BalancingConfig, ElectionResult, VoteWeight}; use sp_runtime::Perbill; use std::collections::{BTreeMap, HashSet}; @@ -38,8 +38,8 @@ pub fn to_range(x: usize, a: usize, b: usize) -> usize { } pub enum ElectionType { - Phragmen(Option<(usize, u128)>), - Phragmms(Option<(usize, u128)>), + Phragmen(Option), + Phragmms(Option), } pub type AccountId = u64; @@ -68,7 +68,7 @@ pub fn generate_random_npos_inputs( // always generate a sensible desired number of candidates: elections are uninteresting if we // desire 0 candidates, or a number of candidates >= the actual number of candidates present - let rounds = rng.gen_range(1, candidate_count); + let rounds = rng.gen_range(1..candidate_count); // candidates are easy: just a completely random set of IDs let mut candidates: Vec = Vec::with_capacity(candidate_count); @@ -80,7 +80,7 @@ pub fn generate_random_npos_inputs( } candidates.push(id); } - candidates.sort_unstable(); + candidates.sort(); candidates.dedup(); assert_eq!(candidates.len(), candidate_count); @@ -95,7 +95,7 @@ pub fn generate_random_npos_inputs( let vote_weight = rng.gen(); // it's not interesting if a voter chooses 0 or all candidates, so rule those cases out. - let n_candidates_chosen = rng.gen_range(1, candidates.len()); + let n_candidates_chosen = rng.gen_range(1..candidates.len()); let mut chosen_candidates = Vec::with_capacity(n_candidates_chosen); chosen_candidates.extend(candidates.choose_multiple(&mut rng, n_candidates_chosen)); @@ -103,7 +103,7 @@ pub fn generate_random_npos_inputs( voters.push((id, vote_weight, chosen_candidates)); } - voters.sort_unstable(); + voters.sort(); voters.dedup_by_key(|(id, _weight, _chosen_candidates)| *id); assert_eq!(voters.len(), voter_count); @@ -132,25 +132,25 @@ pub fn generate_random_npos_result( (1..=target_count).for_each(|acc| { candidates.push(acc); - let stake_var = rng.gen_range(ed, 100 * ed); + let stake_var = rng.gen_range(ed..100 * ed); stake_of.insert(acc, base_stake + stake_var); }); let mut voters = Vec::with_capacity(voter_count as usize); (prefix..=(prefix + voter_count)).for_each(|acc| { - let edge_per_this_voter = rng.gen_range(1, candidates.len()); + let edge_per_this_voter = rng.gen_range(1..candidates.len()); // all possible targets let mut all_targets = candidates.clone(); // we remove and pop into `targets` `edge_per_this_voter` times. let targets = (0..edge_per_this_voter) .map(|_| { let upper = all_targets.len() - 1; - let idx = rng.gen_range(0, upper); + let idx = rng.gen_range(0..upper); all_targets.remove(idx) }) .collect::>(); - let stake_var = rng.gen_range(ed, 100 * ed); + let stake_var = rng.gen_range(ed..100 * ed); let stake = base_stake + stake_var; stake_of.insert(acc, stake); voters.push((acc, stake, targets)); @@ -158,20 +158,10 @@ pub fn generate_random_npos_result( ( match election_type { - ElectionType::Phragmen(conf) => seq_phragmen::( - to_elect, - candidates.clone(), - voters.clone(), - conf, - ) - .unwrap(), - ElectionType::Phragmms(conf) => phragmms::( - to_elect, - candidates.clone(), - voters.clone(), - conf, - ) - .unwrap(), + ElectionType::Phragmen(conf) => + seq_phragmen(to_elect, candidates.clone(), voters.clone(), conf).unwrap(), + ElectionType::Phragmms(conf) => + phragmms(to_elect, candidates.clone(), voters.clone(), conf).unwrap(), }, candidates, voters, diff --git a/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs b/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs index 0c140a8ce6fad..e053f9aa0cddd 100644 --- a/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs +++ b/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,8 +23,8 @@ use common::*; use honggfuzz::fuzz; use rand::{self, SeedableRng}; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, is_score_better, seq_phragmen, to_supports, - EvaluateSupport, VoteWeight, + assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, BalancingConfig, + ElectionResult, EvaluateSupport, VoteWeight, }; use sp_runtime::Perbill; @@ -53,14 +53,12 @@ fn main() { let stake_of = |who: &AccountId| -> VoteWeight { *stake_of_tree.get(who).unwrap() }; let unbalanced_score = { - let staked = assignment_ratio_to_staked_normalized( - unbalanced.assignments.clone(), - &stake_of, - ) - .unwrap(); + let staked = + assignment_ratio_to_staked_normalized(unbalanced.assignments, &stake_of) + .unwrap(); let score = to_supports(staked.as_ref()).evaluate(); - if score[0] == 0 { + if score.minimal_stake == 0 { // such cases cannot be improved by balancing. return } @@ -68,24 +66,19 @@ fn main() { }; if iterations > 0 { - let balanced = seq_phragmen::( - to_elect, - candidates, - voters, - Some((iterations, 0)), - ) - .unwrap(); + let config = BalancingConfig { iterations, tolerance: 0 }; + let balanced: ElectionResult = + seq_phragmen(to_elect, candidates, voters, Some(config)).unwrap(); let balanced_score = { - let staked = assignment_ratio_to_staked_normalized( - balanced.assignments.clone(), - &stake_of, - ) - .unwrap(); + let staked = + assignment_ratio_to_staked_normalized(balanced.assignments, &stake_of) + .unwrap(); to_supports(staked.as_ref()).evaluate() }; - let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero()); + let enhance = + balanced_score.strict_threshold_better(unbalanced_score, Perbill::zero()); println!( "iter = {} // {:?} -> {:?} [{}]", @@ -95,9 +88,9 @@ fn main() { // The only guarantee of balancing is such that the first and third element of the // score cannot decrease. assert!( - balanced_score[0] >= unbalanced_score[0] && - balanced_score[1] == unbalanced_score[1] && - balanced_score[2] <= unbalanced_score[2] + balanced_score.minimal_stake >= unbalanced_score.minimal_stake && + balanced_score.sum_stake == unbalanced_score.sum_stake && + balanced_score.sum_stake_squared <= unbalanced_score.sum_stake_squared ); } }); diff --git a/primitives/npos-elections/fuzzer/src/phragmen_pjr.rs b/primitives/npos-elections/fuzzer/src/phragmen_pjr.rs index f1110da8ef8b0..0401249a3df1d 100644 --- a/primitives/npos-elections/fuzzer/src/phragmen_pjr.rs +++ b/primitives/npos-elections/fuzzer/src/phragmen_pjr.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,7 +42,7 @@ use honggfuzz::fuzz; #[cfg(not(fuzzing))] -use structopt::StructOpt; +use clap::Parser; mod common; use common::{generate_random_npos_inputs, to_range}; @@ -67,24 +67,25 @@ fn main() { } #[cfg(not(fuzzing))] -#[derive(Debug, StructOpt)] +#[derive(Debug, Parser)] +#[command(author, version, about)] struct Opt { /// How many candidates participate in this election - #[structopt(short, long)] + #[arg(short, long)] candidates: Option, /// How many voters participate in this election - #[structopt(short, long)] + #[arg(short, long)] voters: Option, /// Random seed to use in this election - #[structopt(long)] + #[arg(long)] seed: Option, } #[cfg(not(fuzzing))] fn main() { - let opt = Opt::from_args(); + let opt = Opt::parse(); // candidates and voters by default use the maxima, which turn out to be one less than // the constant. iteration( diff --git a/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs b/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs index 7b2aacfa85882..3f114674e29d9 100644 --- a/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs +++ b/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,8 +23,8 @@ use common::*; use honggfuzz::fuzz; use rand::{self, SeedableRng}; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, is_score_better, phragmms, to_supports, EvaluateSupport, - VoteWeight, + assignment_ratio_to_staked_normalized, phragmms, to_supports, BalancingConfig, ElectionResult, + EvaluateSupport, VoteWeight, }; use sp_runtime::Perbill; @@ -53,36 +53,29 @@ fn main() { let stake_of = |who: &AccountId| -> VoteWeight { *stake_of_tree.get(who).unwrap() }; let unbalanced_score = { - let staked = assignment_ratio_to_staked_normalized( - unbalanced.assignments.clone(), - &stake_of, - ) - .unwrap(); + let staked = + assignment_ratio_to_staked_normalized(unbalanced.assignments, &stake_of) + .unwrap(); let score = to_supports(&staked).evaluate(); - if score[0] == 0 { + if score.minimal_stake == 0 { // such cases cannot be improved by balancing. return } score }; - let balanced = phragmms::( - to_elect, - candidates, - voters, - Some((iterations, 0)), - ) - .unwrap(); + let config = BalancingConfig { iterations, tolerance: 0 }; + let balanced: ElectionResult = + phragmms(to_elect, candidates, voters, Some(config)).unwrap(); let balanced_score = { let staked = - assignment_ratio_to_staked_normalized(balanced.assignments.clone(), &stake_of) - .unwrap(); + assignment_ratio_to_staked_normalized(balanced.assignments, &stake_of).unwrap(); to_supports(staked.as_ref()).evaluate() }; - let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero()); + let enhance = balanced_score.strict_threshold_better(unbalanced_score, Perbill::zero()); println!( "iter = {} // {:?} -> {:?} [{}]", @@ -92,9 +85,9 @@ fn main() { // The only guarantee of balancing is such that the first and third element of the score // cannot decrease. assert!( - balanced_score[0] >= unbalanced_score[0] && - balanced_score[1] == unbalanced_score[1] && - balanced_score[2] <= unbalanced_score[2] + balanced_score.minimal_stake >= unbalanced_score.minimal_stake && + balanced_score.sum_stake == unbalanced_score.sum_stake && + balanced_score.sum_stake_squared <= unbalanced_score.sum_stake_squared ); }); } diff --git a/primitives/npos-elections/fuzzer/src/reduce.rs b/primitives/npos-elections/fuzzer/src/reduce.rs index 5f8a4f0e13844..602467a343884 100644 --- a/primitives/npos-elections/fuzzer/src/reduce.rs +++ b/primitives/npos-elections/fuzzer/src/reduce.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -79,8 +79,7 @@ fn generate_random_phragmen_assignment( let mut targets_to_chose_from = all_targets.clone(); let targets_to_chose = if edge_per_voter_var > 0 { rng.gen_range( - avg_edge_per_voter - edge_per_voter_var, - avg_edge_per_voter + edge_per_voter_var, + avg_edge_per_voter - edge_per_voter_var..avg_edge_per_voter + edge_per_voter_var, ) } else { avg_edge_per_voter @@ -89,11 +88,11 @@ fn generate_random_phragmen_assignment( let distribution = (0..targets_to_chose) .map(|_| { let target = - targets_to_chose_from.remove(rng.gen_range(0, targets_to_chose_from.len())); - if winners.iter().find(|w| **w == target).is_none() { - winners.push(target.clone()); + targets_to_chose_from.remove(rng.gen_range(0..targets_to_chose_from.len())); + if winners.iter().all(|w| *w != target) { + winners.push(target); } - (target, rng.gen_range(1 * KSM, 100 * KSM)) + (target, rng.gen_range(1 * KSM..100 * KSM)) }) .collect::>(); @@ -119,7 +118,7 @@ fn reduce_and_compare(assignment: &Vec>, winners: &V let n = assignment.len() as u32; let m = winners.len() as u32; - let edges_before = assignment_len(&assignment); + let edges_before = assignment_len(assignment); let num_changed = reduce(&mut altered_assignment); let edges_after = edges_before - num_changed; diff --git a/primitives/npos-elections/solution-type/Cargo.toml b/primitives/npos-elections/solution-type/Cargo.toml deleted file mode 100644 index cbe6750266f01..0000000000000 --- a/primitives/npos-elections/solution-type/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "sp-npos-elections-solution-type" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "NPoS Solution Type" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[lib] -proc-macro = true - -[dependencies] -syn = { version = "1.0.58", features = ["full", "visit"] } -quote = "1.0" -proc-macro2 = "1.0.29" -proc-macro-crate = "1.0.0" - -[dev-dependencies] -parity-scale-codec = "2.0.1" -scale-info = "1.0" -sp-arithmetic = { path = "../../arithmetic", version = "4.0.0-dev" } -# used by generate_solution_type: -sp-npos-elections = { path = "..", version = "4.0.0-dev" } -trybuild = "1.0.43" diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/no_annotations.rs b/primitives/npos-elections/solution-type/tests/ui/fail/no_annotations.rs deleted file mode 100644 index cfca2841db633..0000000000000 --- a/primitives/npos-elections/solution-type/tests/ui/fail/no_annotations.rs +++ /dev/null @@ -1,9 +0,0 @@ -use sp_npos_elections_solution_type::generate_solution_type; - -generate_solution_type!(pub struct TestSolution::< - u16, - u8, - Perbill, ->(8)); - -fn main() {} diff --git a/primitives/npos-elections/src/assignments.rs b/primitives/npos-elections/src/assignments.rs index bdd1e2cd281bb..fc88ef40010b7 100644 --- a/primitives/npos-elections/src/assignments.rs +++ b/primitives/npos-elections/src/assignments.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ //! Structs and helpers for distributing a voter's stake among various winners. -use crate::{Error, ExtendedBalance, IdentifierT, PerThing128, __OrInvalidIndex}; +use crate::{ExtendedBalance, IdentifierT, PerThing128}; #[cfg(feature = "std")] use codec::{Decode, Encode}; use sp_arithmetic::{ @@ -120,18 +120,17 @@ impl StakedAssignment { AccountId: IdentifierT, { let stake = self.total(); - let distribution = self - .distribution - .into_iter() - .filter_map(|(target, w)| { - let per_thing = P::from_rational(w, stake); - if per_thing == Bounded::min_value() { - None - } else { - Some((target, per_thing)) - } - }) - .collect::>(); + // most likely, the size of the staked assignment and normal assignments will be the same, + // so we pre-allocate it to prevent a sudden 2x allocation. `filter_map` starts with a size + // of 0 by default. + // https://www.reddit.com/r/rust/comments/3spfh1/does_collect_allocate_more_than_once_while/ + let mut distribution = Vec::<(AccountId, P)>::with_capacity(self.distribution.len()); + self.distribution.into_iter().for_each(|(target, w)| { + let per_thing = P::from_rational(w, stake); + if per_thing != Bounded::min_value() { + distribution.push((target, per_thing)); + } + }); Assignment { who: self.who, distribution } } @@ -166,44 +165,3 @@ impl StakedAssignment { self.distribution.iter().fold(Zero::zero(), |a, b| a.saturating_add(b.1)) } } -/// The [`IndexAssignment`] type is an intermediate between the assignments list -/// ([`&[Assignment]`][Assignment]) and `SolutionOf`. -/// -/// The voter and target identifiers have already been replaced with appropriate indices, -/// making it fast to repeatedly encode into a `SolutionOf`. This property turns out -/// to be important when trimming for solution length. -#[derive(RuntimeDebug, Clone, Default)] -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] -pub struct IndexAssignment { - /// Index of the voter among the voters list. - pub who: VoterIndex, - /// The distribution of the voter's stake among winning targets. - /// - /// Targets are identified by their index in the canonical list. - pub distribution: Vec<(TargetIndex, P)>, -} - -impl IndexAssignment { - pub fn new( - assignment: &Assignment, - voter_index: impl Fn(&AccountId) -> Option, - target_index: impl Fn(&AccountId) -> Option, - ) -> Result { - Ok(Self { - who: voter_index(&assignment.who).or_invalid_index()?, - distribution: assignment - .distribution - .iter() - .map(|(target, proportion)| Some((target_index(target)?, proportion.clone()))) - .collect::>>() - .or_invalid_index()?, - }) - } -} - -/// A type alias for [`IndexAssignment`] made from [`crate::Solution`]. -pub type IndexAssignmentOf = IndexAssignment< - ::VoterIndex, - ::TargetIndex, - ::Accuracy, ->; diff --git a/primitives/npos-elections/src/balancing.rs b/primitives/npos-elections/src/balancing.rs index 63164049e5269..4a713658ad38f 100644 --- a/primitives/npos-elections/src/balancing.rs +++ b/primitives/npos-elections/src/balancing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,18 +26,19 @@ //! //! See [`balance`] for more information. -use crate::{Edge, ExtendedBalance, IdentifierT, Voter}; +use crate::{BalancingConfig, Edge, ExtendedBalance, IdentifierT, Voter}; use sp_arithmetic::traits::Zero; use sp_std::prelude::*; /// Balance the weight distribution of a given `voters` at most `iterations` times, or up until the /// point where the biggest difference created per iteration of all stakes is `tolerance`. If this /// is called with `tolerance = 0`, then exactly `iterations` rounds will be executed, except if no -/// change has been made (`difference = 0`). +/// change has been made (`difference = 0`). `tolerance` and `iterations` are part of the +/// [`BalancingConfig`] struct. /// /// In almost all cases, a balanced solution will have a better score than an unbalanced solution, /// yet this is not 100% guaranteed because the first element of a [`crate::ElectionScore`] does not -/// directly related to balancing. +/// directly relate to balancing. /// /// Note that some reference implementation adopt an approach in which voters are balanced randomly /// per round. To advocate determinism, we don't do this. In each round, all voters are exactly @@ -52,12 +53,13 @@ use sp_std::prelude::*; /// - [A new approach to the maximum flow problem](https://dl.acm.org/doi/10.1145/48014.61051). /// - [Validator election in nominated proof-of-stake](https://arxiv.org/abs/2004.12990) (Appendix /// A.) +/// - [Computing a balanced solution](https://research.web3.foundation/en/latest/polkadot/NPoS/3.%20Balancing.html), +/// which contains the details of the algorithm implementation. pub fn balance( voters: &mut Vec>, - iterations: usize, - tolerance: ExtendedBalance, + config: &BalancingConfig, ) -> usize { - if iterations == 0 { + if config.iterations == 0 { return 0 } @@ -65,14 +67,14 @@ pub fn balance( loop { let mut max_diff = 0; for voter in voters.iter_mut() { - let diff = balance_voter(voter, tolerance); + let diff = balance_voter(voter, config.tolerance); if diff > max_diff { max_diff = diff; } } iter += 1; - if max_diff <= tolerance || iter >= iterations { + if max_diff <= config.tolerance || iter >= config.iterations { break iter } } @@ -158,7 +160,7 @@ pub(crate) fn balance_voter( .get(last_index) .expect( "length of elected_edges is greater than or equal 2; last_index index is at the \ - minimum elected_edges.len() - 1; index is within range; qed", + minimum elected_edges.len() - 1; index is within range; qed", ) .candidate .borrow() diff --git a/primitives/npos-elections/src/helpers.rs b/primitives/npos-elections/src/helpers.rs index ca97aeb996e48..598eb3b2ecc2c 100644 --- a/primitives/npos-elections/src/helpers.rs +++ b/primitives/npos-elections/src/helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,13 +49,9 @@ where for<'r> FS: Fn(&'r A) -> VoteWeight, { let mut staked = assignment_ratio_to_staked(ratio, &stake_of); - staked - .iter_mut() - .map(|a| { - a.try_normalize(stake_of(&a.who).into()) - .map_err(|err| Error::ArithmeticError(err)) - }) - .collect::>()?; + staked.iter_mut().try_for_each(|a| { + a.try_normalize(stake_of(&a.who).into()).map_err(Error::ArithmeticError) + })?; Ok(staked) } @@ -74,7 +70,7 @@ pub fn assignment_staked_to_ratio_normalized( ) -> Result>, Error> { let mut ratio = staked.into_iter().map(|a| a.into_assignment()).collect::>(); for assignment in ratio.iter_mut() { - assignment.try_normalize().map_err(|err| Error::ArithmeticError(err))?; + assignment.try_normalize().map_err(Error::ArithmeticError)?; } Ok(ratio) } diff --git a/primitives/npos-elections/src/lib.rs b/primitives/npos-elections/src/lib.rs index afe85ef53b3a7..d0c9ed18caddc 100644 --- a/primitives/npos-elections/src/lib.rs +++ b/primitives/npos-elections/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2019-2022 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 @@ -62,7 +62,7 @@ //! //! The `Assignment` field of the election result is voter-major, i.e. it is from the perspective of //! the voter. The struct that represents the opposite is called a `Support`. This struct is usually -//! accessed in a map-like manner, i.e. keyed by voters, therefor it is stored as a mapping called +//! accessed in a map-like manner, i.e. keyed by voters, therefore it is stored as a mapping called //! `SupportMap`. //! //! Moreover, the support is built from absolute backing values, not ratios like the example above. @@ -74,13 +74,15 @@ #![cfg_attr(not(feature = "std"), no_std)] -use sp_arithmetic::{traits::Zero, Normalizable, PerThing, Rational128, ThresholdOrd}; -use sp_core::RuntimeDebug; -use sp_std::{cell::RefCell, cmp::Ordering, collections::btree_map::BTreeMap, prelude::*, rc::Rc}; - -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; +use sp_arithmetic::{traits::Zero, Normalizable, PerThing, Rational128, ThresholdOrd}; +use sp_core::{bounded::BoundedVec, RuntimeDebug}; +use sp_std::{ + cell::RefCell, cmp::Ordering, collections::btree_map::BTreeMap, prelude::*, rc::Rc, vec, +}; #[cfg(test)] mod mock; @@ -97,29 +99,16 @@ pub mod pjr; pub mod reduce; pub mod traits; -pub use assignments::{Assignment, IndexAssignment, IndexAssignmentOf, StakedAssignment}; +pub use assignments::{Assignment, StakedAssignment}; pub use balancing::*; pub use helpers::*; pub use phragmen::*; pub use phragmms::*; pub use pjr::*; pub use reduce::reduce; -pub use traits::{IdentifierT, NposSolution, PerThing128, __OrInvalidIndex}; - -// re-export for the solution macro, with the dependencies of the macro. -#[doc(hidden)] -pub use codec; -#[doc(hidden)] -pub use scale_info; -#[doc(hidden)] -pub use sp_arithmetic; -#[doc(hidden)] -pub use sp_std; - -// re-export the solution type macro. -pub use sp_npos_elections_solution_type::generate_solution_type; - -/// The errors that might occur in the this crate and solution-type. +pub use traits::{IdentifierT, PerThing128}; + +/// The errors that might occur in this crate and `frame-election-provider-solution-type`. #[derive(Eq, PartialEq, RuntimeDebug)] pub enum Error { /// While going from solution indices to ratio, the weight of all the edges has gone above the @@ -129,12 +118,14 @@ pub enum Error { SolutionTargetOverflow, /// One of the index functions returned none. SolutionInvalidIndex, - /// One of the page indices was invalid + /// One of the page indices was invalid. SolutionInvalidPageIndex, /// An error occurred in some arithmetic operation. ArithmeticError(&'static str), /// The data provided to create support map was invalid. InvalidSupportEdge, + /// The number of voters is bigger than the `MaxVoters` bound. + TooManyVoters, } /// A type which is used in the API of this crate as a numeric weight of a vote, most often the @@ -144,9 +135,93 @@ pub type VoteWeight = u64; /// A type in which performing operations on vote weights are safe. pub type ExtendedBalance = u128; -/// The score of an assignment. This can be computed from the support map via -/// [`EvaluateSupport::evaluate`]. -pub type ElectionScore = [ExtendedBalance; 3]; +/// The score of an election. This is the main measure of an election's quality. +/// +/// By definition, the order of significance in [`ElectionScore`] is: +/// +/// 1. `minimal_stake`. +/// 2. `sum_stake`. +/// 3. `sum_stake_squared`. +#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug, Default)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ElectionScore { + /// The minimal winner, in terms of total backing stake. + /// + /// This parameter should be maximized. + pub minimal_stake: ExtendedBalance, + /// The sum of the total backing of all winners. + /// + /// This parameter should maximized + pub sum_stake: ExtendedBalance, + /// The sum squared of the total backing of all winners, aka. the variance. + /// + /// Ths parameter should be minimized. + pub sum_stake_squared: ExtendedBalance, +} + +impl ElectionScore { + /// Iterate over the inner items, first visiting the most significant one. + fn iter_by_significance(self) -> impl Iterator { + [self.minimal_stake, self.sum_stake, self.sum_stake_squared].into_iter() + } + + /// Compares two sets of election scores based on desirability, returning true if `self` is + /// strictly `threshold` better than `other`. In other words, each element of `self` must be + /// `self * threshold` better than `other`. + /// + /// Evaluation is done based on the order of significance of the fields of [`ElectionScore`]. + pub fn strict_threshold_better(self, other: Self, threshold: impl PerThing) -> bool { + match self + .iter_by_significance() + .zip(other.iter_by_significance()) + .map(|(this, that)| (this.ge(&that), this.tcmp(&that, threshold.mul_ceil(that)))) + .collect::>() + .as_slice() + { + // threshold better in the `score.minimal_stake`, accept. + [(x, Ordering::Greater), _, _] => { + debug_assert!(x); + true + }, + + // less than threshold better in `score.minimal_stake`, but more than threshold better + // in `score.sum_stake`. + [(true, Ordering::Equal), (_, Ordering::Greater), _] => true, + + // less than threshold better in `score.minimal_stake` and `score.sum_stake`, but more + // than threshold better in `score.sum_stake_squared`. + [(true, Ordering::Equal), (true, Ordering::Equal), (_, Ordering::Less)] => true, + + // anything else is not a good score. + _ => false, + } + } +} + +impl sp_std::cmp::Ord for ElectionScore { + fn cmp(&self, other: &Self) -> Ordering { + // we delegate this to the lexicographic cmp of slices`, and to incorporate that we want the + // third element to be minimized, we swap them. + [self.minimal_stake, self.sum_stake, other.sum_stake_squared].cmp(&[ + other.minimal_stake, + other.sum_stake, + self.sum_stake_squared, + ]) + } +} + +impl sp_std::cmp::PartialOrd for ElectionScore { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// Utility struct to group parameters for the balancing algorithm. +#[derive(Clone, Copy)] +pub struct BalancingConfig { + pub iterations: usize, + pub tolerance: ExtendedBalance, +} /// A pointer to a candidate struct with interior mutability. pub type CandidatePtr = Rc>>; @@ -178,7 +253,7 @@ impl Candidate { } /// A vote being casted by a [`Voter`] to a [`Candidate`] is an `Edge`. -#[derive(Clone, Default)] +#[derive(Clone)] pub struct Edge { /// Identifier of the target. /// @@ -193,6 +268,15 @@ pub struct Edge { weight: ExtendedBalance, } +#[cfg(test)] +impl Edge { + fn new(candidate: Candidate, weight: ExtendedBalance) -> Self { + let who = candidate.who.clone(); + let candidate = Rc::new(RefCell::new(candidate)); + Self { weight, who, candidate, load: Default::default() } + } +} + #[cfg(feature = "std")] impl sp_std::fmt::Debug for Edge { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { @@ -223,7 +307,12 @@ impl std::fmt::Debug for Voter { impl Voter { /// Create a new `Voter`. pub fn new(who: AccountId) -> Self { - Self { who, ..Default::default() } + Self { + who, + edges: Default::default(), + budget: Default::default(), + load: Default::default(), + } } /// Returns `true` if `self` votes for `target`. @@ -237,7 +326,7 @@ impl Voter { /// /// Note that this might create _un-normalized_ assignments, due to accuracy loss of `P`. Call /// site might compensate by calling `normalize()` on the returned `Assignment` as a - /// post-precessing. + /// post-processing. pub fn into_assignment(self) -> Option> { let who = self.who; let budget = self.budget; @@ -339,7 +428,7 @@ pub struct ElectionResult { /// /// This, at the current version, resembles the `Exposure` defined in the Staking pallet, yet they /// do not necessarily have to be the same. -#[derive(Default, RuntimeDebug, Encode, Decode, Clone, Eq, PartialEq, scale_info::TypeInfo)] +#[derive(RuntimeDebug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct Support { /// Total support. @@ -348,6 +437,12 @@ pub struct Support { pub voters: Vec<(AccountId, ExtendedBalance)>, } +impl Default for Support { + fn default() -> Self { + Self { total: Default::default(), voters: vec![] } + } +} + /// A target-major representation of the the election outcome. /// /// Essentially a flat variant of [`SupportMap`]. @@ -355,6 +450,11 @@ pub struct Support { /// The main advantage of this is that it is encodable. pub type Supports = Vec<(A, Support)>; +/// Same as `Supports` but bounded by `B`. +/// +/// To note, the inner `Support` is still unbounded. +pub type BoundedSupports = BoundedVec<(A, Support), B>; + /// Linkage from a winner to their [`Support`]. /// /// This is more helpful than a normal [`Supports`] as it allows faster error checking. @@ -367,8 +467,8 @@ pub fn to_support_map( let mut supports = >>::new(); // build support struct. - for StakedAssignment { who, distribution } in assignments.into_iter() { - for (c, weight_extended) in distribution.into_iter() { + for StakedAssignment { who, distribution } in assignments.iter() { + for (c, weight_extended) in distribution.iter() { let mut support = supports.entry(c.clone()).or_default(); support.total = support.total.saturating_add(*weight_extended); support.voters.push((who.clone(), *weight_extended)); @@ -398,49 +498,22 @@ pub trait EvaluateSupport { impl EvaluateSupport for Supports { fn evaluate(&self) -> ElectionScore { - let mut min_support = ExtendedBalance::max_value(); - let mut sum: ExtendedBalance = Zero::zero(); + let mut minimal_stake = ExtendedBalance::max_value(); + let mut sum_stake: ExtendedBalance = Zero::zero(); // NOTE: The third element might saturate but fine for now since this will run on-chain and // need to be fast. - let mut sum_squared: ExtendedBalance = Zero::zero(); + let mut sum_stake_squared: ExtendedBalance = Zero::zero(); + for (_, support) in self { - sum = sum.saturating_add(support.total); + sum_stake = sum_stake.saturating_add(support.total); let squared = support.total.saturating_mul(support.total); - sum_squared = sum_squared.saturating_add(squared); - if support.total < min_support { - min_support = support.total; + sum_stake_squared = sum_stake_squared.saturating_add(squared); + if support.total < minimal_stake { + minimal_stake = support.total; } } - [min_support, sum, sum_squared] - } -} -/// Compares two sets of election scores based on desirability and returns true if `this` is better -/// than `that`. -/// -/// Evaluation is done in a lexicographic manner, and if each element of `this` is `that * epsilon` -/// greater or less than `that`. -/// -/// Note that the third component should be minimized. -pub fn is_score_better(this: ElectionScore, that: ElectionScore, epsilon: P) -> bool { - match this - .iter() - .zip(that.iter()) - .map(|(thi, tha)| (thi.ge(&tha), thi.tcmp(&tha, epsilon.mul_ceil(*tha)))) - .collect::>() - .as_slice() - { - // epsilon better in the score[0], accept. - [(_, Ordering::Greater), _, _] => true, - - // less than epsilon better in score[0], but more than epsilon better in the second. - [(true, Ordering::Equal), (_, Ordering::Greater), _] => true, - - // less than epsilon better in score[0, 1], but more than epsilon better in the third - [(true, Ordering::Equal), (true, Ordering::Equal), (_, Ordering::Less)] => true, - - // anything else is not a good score. - _ => false, + ElectionScore { minimal_stake, sum_stake, sum_stake_squared } } } @@ -451,7 +524,7 @@ pub fn is_score_better(this: ElectionScore, that: ElectionScore, ep /// - It drops duplicate targets within a voter. pub fn setup_inputs( initial_candidates: Vec, - initial_voters: Vec<(AccountId, VoteWeight, Vec)>, + initial_voters: Vec<(AccountId, VoteWeight, impl IntoIterator)>, ) -> (Vec>, Vec>) { // used to cache and access candidates index. let mut c_idx_cache = BTreeMap::::new(); @@ -461,14 +534,22 @@ pub fn setup_inputs( .enumerate() .map(|(idx, who)| { c_idx_cache.insert(who.clone(), idx); - Candidate { who, ..Default::default() }.to_ptr() + Candidate { + who, + score: Default::default(), + approval_stake: Default::default(), + backed_stake: Default::default(), + elected: Default::default(), + round: Default::default(), + } + .to_ptr() }) .collect::>>(); let voters = initial_voters .into_iter() .filter_map(|(who, voter_stake, votes)| { - let mut edges: Vec> = Vec::with_capacity(votes.len()); + let mut edges: Vec> = Vec::new(); for v in votes { if edges.iter().any(|e| e.who == v) { // duplicate edge. @@ -482,7 +563,8 @@ pub fn setup_inputs( edges.push(Edge { who: v.clone(), candidate: Rc::clone(&candidates[*idx]), - ..Default::default() + load: Default::default(), + weight: Default::default(), }); } // else {} would be wrong votes. We don't really care about it. } diff --git a/primitives/npos-elections/src/mock.rs b/primitives/npos-elections/src/mock.rs index 36fd78b5757ee..5a06e3f3c88ca 100644 --- a/primitives/npos-elections/src/mock.rs +++ b/primitives/npos-elections/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,13 +19,6 @@ #![cfg(test)] -use std::{ - collections::{HashMap, HashSet}, - convert::TryInto, - hash::Hash, -}; - -use rand::{self, seq::SliceRandom, Rng}; use sp_arithmetic::{ traits::{One, SaturatedConversion, Zero}, PerThing, @@ -37,27 +30,6 @@ use crate::{seq_phragmen, Assignment, ElectionResult, ExtendedBalance, PerThing1 pub type AccountId = u64; -/// The candidate mask allows easy disambiguation between voters and candidates: accounts -/// for which this bit is set are candidates, and without it, are voters. -pub const CANDIDATE_MASK: AccountId = 1 << ((std::mem::size_of::() * 8) - 1); - -pub type TestAccuracy = sp_runtime::Perbill; - -crate::generate_solution_type! { - pub struct TestSolution::< - VoterIndex = u32, - TargetIndex = u16, - Accuracy = TestAccuracy, - >(16) -} - -pub fn p(p: u8) -> TestAccuracy { - TestAccuracy::from_percent(p.into()) -} - -pub type MockAssignment = crate::Assignment; -pub type Voter = (AccountId, VoteWeight, Vec); - #[derive(Default, Debug)] pub(crate) struct _Candidate { who: A, @@ -120,7 +92,7 @@ where .into_iter() .enumerate() .map(|(idx, who)| { - c_idx_cache.insert(who.clone(), idx); + c_idx_cache.insert(who, idx); _Candidate { who, ..Default::default() } }) .collect::>>(); @@ -131,7 +103,7 @@ where for v in votes { if let Some(idx) = c_idx_cache.get(&v) { candidates[*idx].approval_stake = candidates[*idx].approval_stake + voter_stake; - edges.push(_Edge { who: v.clone(), candidate_index: *idx, ..Default::default() }); + edges.push(_Edge { who: v, candidate_index: *idx, ..Default::default() }); } } _Voter { who, edges, budget: voter_stake, load: 0f64 } @@ -171,21 +143,21 @@ where } } - elected_candidates.push((winner.who.clone(), winner.approval_stake as ExtendedBalance)); + elected_candidates.push((winner.who, winner.approval_stake as ExtendedBalance)); } else { break } } for n in &mut voters { - let mut assignment = (n.who.clone(), vec![]); + let mut assignment = (n.who, vec![]); for e in &mut n.edges { if let Some(c) = elected_candidates.iter().cloned().map(|(c, _)| c).find(|c| *c == e.who) { if c != n.who { let ratio = e.load / n.load; - assignment.1.push((e.who.clone(), ratio)); + assignment.1.push((e.who, ratio)); } } } @@ -344,12 +316,12 @@ pub(crate) fn run_and_compare( FS: Fn(&AccountId) -> VoteWeight, { // run fixed point code. - let ElectionResult { winners, assignments } = seq_phragmen::<_, Output>( + let ElectionResult::<_, Output> { winners, assignments } = seq_phragmen( to_elect, candidates.clone(), voters .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, ) @@ -396,7 +368,7 @@ pub(crate) fn build_support_map_float( let mut supports = <_SupportMap>::new(); result.winners.iter().map(|(e, _)| (e, stake_of(e) as f64)).for_each(|(e, s)| { let item = _Support { own: s, total: s, ..Default::default() }; - supports.insert(e.clone(), item); + supports.insert(*e, item); }); for (n, assignment) in result.assignments.iter_mut() { @@ -405,143 +377,10 @@ pub(crate) fn build_support_map_float( let other_stake = nominator_stake * *r; if let Some(support) = supports.get_mut(c) { support.total = support.total + other_stake; - support.others.push((n.clone(), other_stake)); + support.others.push((*n, other_stake)); } *r = other_stake; } } supports } - -/// Generate voter and assignment lists. Makes no attempt to be realistic about winner or assignment -/// fairness. -/// -/// Maintains these invariants: -/// -/// - candidate ids have `CANDIDATE_MASK` bit set -/// - voter ids do not have `CANDIDATE_MASK` bit set -/// - assignments have the same ordering as voters -/// - `assignments.distribution.iter().map(|(_, frac)| frac).sum() == One::one()` -/// - a coherent set of winners is chosen. -/// - the winner set is a subset of the candidate set. -/// - `assignments.distribution.iter().all(|(who, _)| winners.contains(who))` -pub fn generate_random_votes( - candidate_count: usize, - voter_count: usize, - mut rng: impl Rng, -) -> (Vec, Vec, Vec) { - // cache for fast generation of unique candidate and voter ids - let mut used_ids = HashSet::with_capacity(candidate_count + voter_count); - - // candidates are easy: just a completely random set of IDs - let mut candidates: Vec = Vec::with_capacity(candidate_count); - while candidates.len() < candidate_count { - let mut new = || rng.gen::() | CANDIDATE_MASK; - let mut id = new(); - // insert returns `false` when the value was already present - while !used_ids.insert(id) { - id = new(); - } - candidates.push(id); - } - - // voters are random ids, random weights, random selection from the candidates - let mut voters = Vec::with_capacity(voter_count); - while voters.len() < voter_count { - let mut new = || rng.gen::() & !CANDIDATE_MASK; - let mut id = new(); - // insert returns `false` when the value was already present - while !used_ids.insert(id) { - id = new(); - } - - let vote_weight = rng.gen(); - - // it's not interesting if a voter chooses 0 or all candidates, so rule those cases out. - // also, let's not generate any cases which result in a compact overflow. - let n_candidates_chosen = - rng.gen_range(1, candidates.len().min(::LIMIT)); - - let mut chosen_candidates = Vec::with_capacity(n_candidates_chosen); - chosen_candidates.extend(candidates.choose_multiple(&mut rng, n_candidates_chosen)); - voters.push((id, vote_weight, chosen_candidates)); - } - - // always generate a sensible number of winners: elections are uninteresting if nobody wins, - // or everybody wins - let num_winners = rng.gen_range(1, candidate_count); - let mut winners: HashSet = HashSet::with_capacity(num_winners); - winners.extend(candidates.choose_multiple(&mut rng, num_winners)); - assert_eq!(winners.len(), num_winners); - - let mut assignments = Vec::with_capacity(voters.len()); - for (voter_id, _, votes) in voters.iter() { - let chosen_winners = votes.iter().filter(|vote| winners.contains(vote)).cloned(); - let num_chosen_winners = chosen_winners.clone().count(); - - // distribute the available stake randomly - let stake_distribution = if num_chosen_winners == 0 { - continue - } else { - let mut available_stake = 1000; - let mut stake_distribution = Vec::with_capacity(num_chosen_winners); - for _ in 0..num_chosen_winners - 1 { - let stake = rng.gen_range(0, available_stake).min(1); - stake_distribution.push(TestAccuracy::from_perthousand(stake)); - available_stake -= stake; - } - stake_distribution.push(TestAccuracy::from_perthousand(available_stake)); - stake_distribution.shuffle(&mut rng); - stake_distribution - }; - - assignments.push(MockAssignment { - who: *voter_id, - distribution: chosen_winners.zip(stake_distribution).collect(), - }); - } - - (voters, assignments, candidates) -} - -fn generate_cache(voters: Voters) -> HashMap -where - Voters: Iterator, - Item: Hash + Eq + Copy, -{ - let mut cache = HashMap::new(); - for (idx, voter_id) in voters.enumerate() { - cache.insert(voter_id, idx); - } - cache -} - -/// Create a function that returns the index of a voter in the voters list. -pub fn make_voter_fn(voters: &[Voter]) -> impl Fn(&AccountId) -> Option -where - usize: TryInto, -{ - let cache = generate_cache(voters.iter().map(|(id, _, _)| *id)); - move |who| { - if cache.get(who).is_none() { - println!("WARNING: voter {} will raise InvalidIndex", who); - } - cache.get(who).cloned().and_then(|i| i.try_into().ok()) - } -} - -/// Create a function that returns the index of a candidate in the candidates list. -pub fn make_target_fn( - candidates: &[AccountId], -) -> impl Fn(&AccountId) -> Option -where - usize: TryInto, -{ - let cache = generate_cache(candidates.iter().cloned()); - move |who| { - if cache.get(who).is_none() { - println!("WARNING: target {} will raise InvalidIndex", who); - } - cache.get(who).cloned().and_then(|i| i.try_into().ok()) - } -} diff --git a/primitives/npos-elections/src/node.rs b/primitives/npos-elections/src/node.rs index 62b728d52258b..6642a9ae39736 100644 --- a/primitives/npos-elections/src/node.rs +++ b/primitives/npos-elections/src/node.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/src/phragmen.rs b/primitives/npos-elections/src/phragmen.rs index 5ed472284351a..ca32780ed84b4 100644 --- a/primitives/npos-elections/src/phragmen.rs +++ b/primitives/npos-elections/src/phragmen.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,13 +21,13 @@ //! to the Maximin problem. use crate::{ - balancing, setup_inputs, CandidatePtr, ElectionResult, ExtendedBalance, IdentifierT, - PerThing128, VoteWeight, Voter, + balancing, setup_inputs, BalancingConfig, CandidatePtr, ElectionResult, ExtendedBalance, + IdentifierT, PerThing128, VoteWeight, Voter, }; use sp_arithmetic::{ - helpers_128bit::multiply_by_rational, + helpers_128bit::multiply_by_rational_with_rounding, traits::{Bounded, Zero}, - Rational128, + Rational128, Rounding, }; use sp_std::prelude::*; @@ -70,17 +70,17 @@ const DEN: ExtendedBalance = ExtendedBalance::max_value(); pub fn seq_phragmen( to_elect: usize, candidates: Vec, - voters: Vec<(AccountId, VoteWeight, Vec)>, - balancing: Option<(usize, ExtendedBalance)>, + voters: Vec<(AccountId, VoteWeight, impl IntoIterator)>, + balancing: Option, ) -> Result, crate::Error> { let (candidates, voters) = setup_inputs(candidates, voters); let (candidates, mut voters) = seq_phragmen_core::(to_elect, candidates, voters)?; - if let Some((iterations, tolerance)) = balancing { + if let Some(ref config) = balancing { // NOTE: might create zero-edges, but we will strip them again when we convert voter into // assignment. - let _iters = balancing::balance::(&mut voters, iterations, tolerance); + let _iters = balancing::balance::(&mut voters, config); } let mut winners = candidates @@ -97,8 +97,7 @@ pub fn seq_phragmen( voters.into_iter().filter_map(|v| v.into_assignment()).collect::>(); let _ = assignments .iter_mut() - .map(|a| a.try_normalize().map_err(|e| crate::Error::ArithmeticError(e))) - .collect::>()?; + .try_for_each(|a| a.try_normalize().map_err(crate::Error::ArithmeticError))?; let winners = winners .into_iter() .map(|w_ptr| (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake)) @@ -144,10 +143,11 @@ pub fn seq_phragmen_core( for edge in &voter.edges { let mut candidate = edge.candidate.borrow_mut(); if !candidate.elected && !candidate.approval_stake.is_zero() { - let temp_n = multiply_by_rational( + let temp_n = multiply_by_rational_with_rounding( voter.load.n(), voter.budget, candidate.approval_stake, + Rounding::Down, ) .unwrap_or(Bounded::max_value()); let temp_d = voter.load.d(); @@ -185,9 +185,14 @@ pub fn seq_phragmen_core( for edge in &mut voter.edges { if edge.candidate.borrow().elected { // update internal state. - edge.weight = multiply_by_rational(voter.budget, edge.load.n(), voter.load.n()) - // If result cannot fit in u128. Not much we can do about it. - .unwrap_or(Bounded::max_value()); + edge.weight = multiply_by_rational_with_rounding( + voter.budget, + edge.load.n(), + voter.load.n(), + Rounding::Down, + ) + // If result cannot fit in u128. Not much we can do about it. + .unwrap_or(Bounded::max_value()); } else { edge.weight = 0 } @@ -200,7 +205,7 @@ pub fn seq_phragmen_core( // edge of all candidates that eventually have a non-zero weight must be elected. debug_assert!(voter.edges.iter().all(|e| e.candidate.borrow().elected)); // inc budget to sum the budget. - voter.try_normalize_elected().map_err(|e| crate::Error::ArithmeticError(e))?; + voter.try_normalize_elected().map_err(crate::Error::ArithmeticError)?; } Ok((candidates, voters)) diff --git a/primitives/npos-elections/src/phragmms.rs b/primitives/npos-elections/src/phragmms.rs index e9135a13190c6..3fbbad75e2f8f 100644 --- a/primitives/npos-elections/src/phragmms.rs +++ b/primitives/npos-elections/src/phragmms.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,30 +22,30 @@ //! MMS algorithm. use crate::{ - balance, setup_inputs, CandidatePtr, ElectionResult, ExtendedBalance, IdentifierT, PerThing128, - VoteWeight, Voter, + balance, setup_inputs, BalancingConfig, CandidatePtr, ElectionResult, ExtendedBalance, + IdentifierT, PerThing128, VoteWeight, Voter, }; use sp_arithmetic::{traits::Bounded, PerThing, Rational128}; use sp_std::{prelude::*, rc::Rc}; /// Execute the phragmms method. /// -/// This can be used interchangeably with [`seq-phragmen`] and offers a similar API, namely: +/// This can be used interchangeably with `seq-phragmen` and offers a similar API, namely: /// /// - The resulting edge weight distribution is normalized (thus, safe to use for submission). /// - The accuracy can be configured via the generic type `P`. /// - The algorithm is a _best-effort_ to elect `to_elect`. If less candidates are provided, less /// winners are returned, without an error. /// -/// This can only fail of the normalization fails. This can happen if for any of the resulting +/// This can only fail if the normalization fails. This can happen if for any of the resulting /// assignments, `assignment.distribution.map(|p| p.deconstruct()).sum()` fails to fit inside /// `UpperOf

`. A user of this crate may statically assert that this can never happen and safely /// `expect` this to return `Ok`. pub fn phragmms( to_elect: usize, candidates: Vec, - voters: Vec<(AccountId, VoteWeight, Vec)>, - balancing: Option<(usize, ExtendedBalance)>, + voters: Vec<(AccountId, VoteWeight, impl IntoIterator)>, + balancing: Option, ) -> Result, crate::Error> { let (candidates, mut voters) = setup_inputs(candidates, voters); @@ -58,8 +58,8 @@ pub fn phragmms( round_winner.borrow_mut().elected = true; winners.push(round_winner); - if let Some((iterations, tolerance)) = balancing { - balance(&mut voters, iterations, tolerance); + if let Some(ref config) = balancing { + balance(&mut voters, config); } } else { break @@ -70,9 +70,8 @@ pub fn phragmms( voters.into_iter().filter_map(|v| v.into_assignment()).collect::>(); let _ = assignments .iter_mut() - .map(|a| a.try_normalize()) - .collect::>() - .map_err(|e| crate::Error::ArithmeticError(e))?; + .try_for_each(|a| a.try_normalize()) + .map_err(crate::Error::ArithmeticError)?; let winners = winners .into_iter() .map(|w_ptr| (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake)) @@ -157,16 +156,14 @@ pub(crate) fn calculate_max_score( // `RationalInfinite` as the score type does not introduce significant overhead. Then we // can switch the score type to `RationalInfinite` and ensure compatibility with any // crazy token scale. - let score_n = candidate - .approval_stake - .checked_mul(one) - .unwrap_or_else(|| Bounded::max_value()); + let score_n = + candidate.approval_stake.checked_mul(one).unwrap_or_else(Bounded::max_value); candidate.score = Rational128::from(score_n, score_d); // check if we have a new winner. if !candidate.elected && candidate.score > best_score { best_score = candidate.score; - best_candidate = Some(Rc::clone(&c_ptr)); + best_candidate = Some(Rc::clone(c_ptr)); } } else { candidate.score = Rational128::zero(); @@ -278,7 +275,8 @@ mod tests { drop(winner); // balancing makes no difference here but anyhow. - balance(&mut voters, 10, 0); + let config = BalancingConfig { iterations: 10, tolerance: 0 }; + balance(&mut voters, &config); // round 2 let winner = @@ -318,7 +316,7 @@ mod tests { drop(winner); // balancing will improve stuff here. - balance(&mut voters, 10, 0); + balance(&mut voters, &config); assert_eq!( voters @@ -351,8 +349,9 @@ mod tests { let candidates = vec![1, 2, 3]; let voters = vec![(10, 10, vec![1, 2]), (20, 20, vec![1, 3]), (30, 30, vec![2, 3])]; - let ElectionResult { winners, assignments } = - phragmms::<_, Perbill>(2, candidates, voters, Some((2, 0))).unwrap(); + let config = BalancingConfig { iterations: 2, tolerance: 0 }; + let ElectionResult::<_, Perbill> { winners, assignments } = + phragmms(2, candidates, voters, Some(config)).unwrap(); assert_eq!(winners, vec![(3, 30), (2, 30)]); assert_eq!( assignments, @@ -383,8 +382,9 @@ mod tests { (130, 1000, vec![61, 71]), ]; - let ElectionResult { winners, assignments: _ } = - phragmms::<_, Perbill>(4, candidates, voters, Some((2, 0))).unwrap(); + let config = BalancingConfig { iterations: 2, tolerance: 0 }; + let ElectionResult::<_, Perbill> { winners, assignments: _ } = + phragmms(4, candidates, voters, Some(config)).unwrap(); assert_eq!(winners, vec![(11, 3000), (31, 2000), (51, 1500), (61, 1500),]); } @@ -396,8 +396,9 @@ mod tests { // give a bit more to 1 and 3. voters.push((2, u64::MAX, vec![1, 3])); - let ElectionResult { winners, assignments: _ } = - phragmms::<_, Perbill>(2, candidates, voters, Some((2, 0))).unwrap(); + let config = BalancingConfig { iterations: 2, tolerance: 0 }; + let ElectionResult::<_, Perbill> { winners, assignments: _ } = + phragmms(2, candidates, voters, Some(config)).unwrap(); assert_eq!(winners.into_iter().map(|(w, _)| w).collect::>(), vec![1u32, 3]); } } diff --git a/primitives/npos-elections/src/pjr.rs b/primitives/npos-elections/src/pjr.rs index e27acf1408f96..fd7c8ef539241 100644 --- a/primitives/npos-elections/src/pjr.rs +++ b/primitives/npos-elections/src/pjr.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -212,7 +212,7 @@ fn validate_pjr_challenge_core( None => return false, Some(candidate) => candidate.clone(), }; - pre_score(candidate, &voters, threshold) >= threshold + pre_score(candidate, voters, threshold) >= threshold } /// Convert the data types that the user runtime has into ones that can be used by this module. @@ -287,7 +287,15 @@ fn prepare_pjr_input( let elected = maybe_support.is_some(); let backed_stake = maybe_support.map(|support| support.total).unwrap_or_default(); - Candidate { who, elected, backed_stake, ..Default::default() }.to_ptr() + Candidate { + who, + elected, + backed_stake, + score: Default::default(), + approval_stake: Default::default(), + round: Default::default(), + } + .to_ptr() }) .collect::>(); @@ -315,14 +323,14 @@ fn prepare_pjr_input( who: t, candidate: Rc::clone(&candidates[*idx]), weight, - ..Default::default() + load: Default::default(), }); } } let who = v; let budget: ExtendedBalance = w.into(); - Voter { who, budget, edges, ..Default::default() } + Voter { who, budget, edges, load: Default::default() } }) .collect::>(); @@ -343,7 +351,7 @@ fn pre_score( debug_assert!(!unelected.borrow().elected); voters .iter() - .filter(|ref v| v.votes_for(&unelected.borrow().who)) + .filter(|v| v.votes_for(&unelected.borrow().who)) .fold(Zero::zero(), |acc: ExtendedBalance, voter| acc.saturating_add(slack(voter, t))) } @@ -387,7 +395,14 @@ mod tests { .into_iter() .map(|(t, w, e)| { budget += w; - Candidate { who: t, elected: e, backed_stake: w, ..Default::default() } + Candidate { + who: t, + elected: e, + backed_stake: w, + score: Default::default(), + approval_stake: Default::default(), + round: Default::default(), + } }) .collect::>(); let edges = candidates @@ -396,7 +411,7 @@ mod tests { who: c.who, weight: c.backed_stake, candidate: c.to_ptr(), - ..Default::default() + load: Default::default(), }) .collect::>(); voter.edges = edges; @@ -432,7 +447,15 @@ mod tests { // will give 10 slack. let v3 = setup_voter(30, vec![(1, 20, true), (2, 20, true), (3, 0, false)]); - let unelected = Candidate { who: 3u32, elected: false, ..Default::default() }.to_ptr(); + let unelected = Candidate { + who: 3u32, + elected: false, + score: Default::default(), + approval_stake: Default::default(), + backed_stake: Default::default(), + round: Default::default(), + } + .to_ptr(); let score = pre_score(unelected, &vec![v1, v2, v3], 15); assert_eq!(score, 15); @@ -458,7 +481,7 @@ mod tests { assert_eq!( candidates .iter() - .map(|c| (c.borrow().who.clone(), c.borrow().elected, c.borrow().backed_stake)) + .map(|c| (c.borrow().who, c.borrow().elected, c.borrow().backed_stake)) .collect::>(), vec![(10, false, 0), (20, true, 15), (30, false, 0), (40, true, 15)], ); diff --git a/primitives/npos-elections/src/reduce.rs b/primitives/npos-elections/src/reduce.rs index 8b90796af85ca..c802a29504709 100644 --- a/primitives/npos-elections/src/reduce.rs +++ b/primitives/npos-elections/src/reduce.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -55,7 +55,6 @@ use sp_arithmetic::traits::{Bounded, Zero}; use sp_std::{ collections::btree_map::{BTreeMap, Entry::*}, prelude::*, - vec, }; /// Map type used for reduce_4. Can be easily swapped with HashMap. @@ -95,7 +94,7 @@ fn merge(voter_root_path: Vec>, target_root_path: Vec shorter_path .iter() .zip(shorter_path.iter().skip(1)) - .for_each(|(voter, next)| Node::set_parent_of(&next, &voter)); + .for_each(|(voter, next)| Node::set_parent_of(next, voter)); Node::set_parent_of(&shorter_path[0], &longer_path[0]); } @@ -276,9 +275,8 @@ fn reduce_4(assignments: &mut Vec>) -> u32 { }); // remove either one of them. - let who_removed = remove_indices.iter().find(|i| **i < 2usize).is_some(); - let other_removed = - remove_indices.into_iter().find(|i| *i >= 2usize).is_some(); + let who_removed = remove_indices.iter().any(|i| *i < 2usize); + let other_removed = remove_indices.into_iter().any(|i| i >= 2usize); match (who_removed, other_removed) { (false, true) => { @@ -356,7 +354,7 @@ fn reduce_all(assignments: &mut Vec>) -> u32 .or_insert_with(|| Node::new(target_id).into_ref()) .clone(); - // If one exists but the other one doesn't, or if both does not, then set the existing + // If one exists but the other one doesn't, or if both do not, then set the existing // one as the parent of the non-existing one and move on. Else, continue with the rest // of the code. match (voter_exists, target_exists) { @@ -390,39 +388,44 @@ fn reduce_all(assignments: &mut Vec>) -> u32 let common_count = trailing_common(&voter_root_path, &target_root_path); // because roots are the same. - #[cfg(feature = "std")] - debug_assert_eq!(target_root_path.last().unwrap(), voter_root_path.last().unwrap()); + //debug_assert_eq!(target_root_path.last().unwrap(), + // voter_root_path.last().unwrap()); TODO: @kian + // the common path must be non-void.. debug_assert!(common_count > 0); + // and smaller than btoh + debug_assert!(common_count <= voter_root_path.len()); + debug_assert!(common_count <= target_root_path.len()); // cycle part of each path will be `path[path.len() - common_count - 1 : 0]` // NOTE: the order of chaining is important! it is always build from [target, ..., // voter] let cycle = target_root_path .iter() - .take(target_root_path.len() - common_count + 1) + .take(target_root_path.len().saturating_sub(common_count).saturating_add(1)) .cloned() .chain( voter_root_path .iter() - .take(voter_root_path.len() - common_count) + .take(voter_root_path.len().saturating_sub(common_count)) .rev() .cloned(), ) .collect::>>(); // a cycle's length shall always be multiple of two. - #[cfg(feature = "std")] debug_assert_eq!(cycle.len() % 2, 0); // find minimum of cycle. let mut min_value: ExtendedBalance = Bounded::max_value(); - // The voter and the target pair that create the min edge. - let mut min_target: A = Default::default(); - let mut min_voter: A = Default::default(); + // The voter and the target pair that create the min edge. These MUST be set by the + // end of this code block, otherwise we skip. + let mut maybe_min_target: Option = None; + let mut maybe_min_voter: Option = None; // The index of the min in opaque cycle list. - let mut min_index = 0usize; + let mut maybe_min_index: Option = None; // 1 -> next // 0 -> prev - let mut min_direction = 0u32; + let mut maybe_min_direction: Option = None; + // helpers let next_index = |i| { if i < (cycle.len() - 1) { @@ -438,6 +441,7 @@ fn reduce_all(assignments: &mut Vec>) -> u32 cycle.len() - 1 } }; + for i in 0..cycle.len() { if cycle[i].borrow().id.role == NodeRole::Voter { // NOTE: sadly way too many clones since I don't want to make A: Copy @@ -448,10 +452,10 @@ fn reduce_all(assignments: &mut Vec>) -> u32 ass.distribution.iter().find(|d| d.0 == next).map(|(_, w)| { if *w < min_value { min_value = *w; - min_target = next.clone(); - min_voter = current.clone(); - min_index = i; - min_direction = 1; + maybe_min_target = Some(next.clone()); + maybe_min_voter = Some(current.clone()); + maybe_min_index = Some(i); + maybe_min_direction = Some(1); } }) }); @@ -459,16 +463,39 @@ fn reduce_all(assignments: &mut Vec>) -> u32 ass.distribution.iter().find(|d| d.0 == prev).map(|(_, w)| { if *w < min_value { min_value = *w; - min_target = prev.clone(); - min_voter = current.clone(); - min_index = i; - min_direction = 0; + maybe_min_target = Some(prev.clone()); + maybe_min_voter = Some(current.clone()); + maybe_min_index = Some(i); + maybe_min_direction = Some(0); } }) }); } } + // all of these values must be set by now, we assign them to un-mut values, no + // longer being optional either. + let (min_value, min_target, min_voter, min_index, min_direction) = + match ( + min_value, + maybe_min_target, + maybe_min_voter, + maybe_min_index, + maybe_min_direction, + ) { + ( + min_value, + Some(min_target), + Some(min_voter), + Some(min_index), + Some(min_direction), + ) => (min_value, min_target, min_voter, min_index, min_direction), + _ => { + sp_runtime::print("UNREACHABLE code reached in `reduce` algorithm. This must be a bug."); + break + }, + }; + // if the min edge is in the voter's sub-chain. // [target, ..., X, Y, ... voter] let target_chunk = target_root_path.len() - common_count; @@ -497,12 +524,10 @@ fn reduce_all(assignments: &mut Vec>) -> u32 } else { ass.distribution[idx].1.saturating_sub(min_value) } + } else if start_operation_add { + ass.distribution[idx].1.saturating_sub(min_value) } else { - if start_operation_add { - ass.distribution[idx].1.saturating_sub(min_value) - } else { - ass.distribution[idx].1.saturating_add(min_value) - } + ass.distribution[idx].1.saturating_add(min_value) }; if next_value.is_zero() { @@ -542,12 +567,10 @@ fn reduce_all(assignments: &mut Vec>) -> u32 } else { ass.distribution[idx].1.saturating_add(min_value) } + } else if start_operation_add { + ass.distribution[idx].1.saturating_add(min_value) } else { - if start_operation_add { - ass.distribution[idx].1.saturating_add(min_value) - } else { - ass.distribution[idx].1.saturating_sub(min_value) - } + ass.distribution[idx].1.saturating_sub(min_value) }; if next_value.is_zero() { @@ -624,8 +647,8 @@ fn reduce_all(assignments: &mut Vec>) -> u32 num_changed } -/// Reduce the given [`Vec>`]. This removes redundant edges from -/// without changing the overall backing of any of the elected candidates. +/// Reduce the given [`Vec>`]. This removes redundant edges without +/// changing the overall backing of any of the elected candidates. /// /// Returns the number of edges removed. /// diff --git a/primitives/npos-elections/src/tests.rs b/primitives/npos-elections/src/tests.rs index bf9ca57677efa..6f2e4fca77115 100644 --- a/primitives/npos-elections/src/tests.rs +++ b/primitives/npos-elections/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,13 +18,10 @@ //! Tests for npos-elections. use crate::{ - balancing, helpers::*, is_score_better, mock::*, seq_phragmen, seq_phragmen_core, setup_inputs, - to_support_map, Assignment, ElectionResult, ExtendedBalance, IndexAssignment, NposSolution, - StakedAssignment, Support, Voter, + balancing, helpers::*, mock::*, seq_phragmen, seq_phragmen_core, setup_inputs, to_support_map, + Assignment, BalancingConfig, ElectionResult, ExtendedBalance, StakedAssignment, Support, Voter, }; -use rand::{self, SeedableRng}; use sp_arithmetic::{PerU16, Perbill, Percent, Permill}; -use std::convert::TryInto; use substrate_test_utils::assert_eq_uvec; #[test] @@ -145,7 +142,8 @@ fn balancing_core_works() { let (candidates, voters) = setup_inputs(candidates, voters); let (candidates, mut voters) = seq_phragmen_core(4, candidates, voters).unwrap(); - let iters = balancing::balance::(&mut voters, 4, 0); + let config = BalancingConfig { iterations: 4, tolerance: 0 }; + let iters = balancing::balance::(&mut voters, &config); assert!(iters > 0); @@ -192,16 +190,15 @@ fn balancing_core_works() { #[test] fn voter_normalize_ops_works() { use crate::{Candidate, Edge}; - use sp_std::{cell::RefCell, rc::Rc}; // normalize { let c1 = Candidate { who: 10, elected: false, ..Default::default() }; let c2 = Candidate { who: 20, elected: false, ..Default::default() }; let c3 = Candidate { who: 30, elected: false, ..Default::default() }; - let e1 = Edge { candidate: Rc::new(RefCell::new(c1)), weight: 30, ..Default::default() }; - let e2 = Edge { candidate: Rc::new(RefCell::new(c2)), weight: 33, ..Default::default() }; - let e3 = Edge { candidate: Rc::new(RefCell::new(c3)), weight: 30, ..Default::default() }; + let e1 = Edge::new(c1, 30); + let e2 = Edge::new(c2, 33); + let e3 = Edge::new(c3, 30); let mut v = Voter { who: 1, budget: 100, edges: vec![e1, e2, e3], ..Default::default() }; @@ -214,9 +211,9 @@ fn voter_normalize_ops_works() { let c2 = Candidate { who: 20, elected: true, ..Default::default() }; let c3 = Candidate { who: 30, elected: true, ..Default::default() }; - let e1 = Edge { candidate: Rc::new(RefCell::new(c1)), weight: 30, ..Default::default() }; - let e2 = Edge { candidate: Rc::new(RefCell::new(c2)), weight: 33, ..Default::default() }; - let e3 = Edge { candidate: Rc::new(RefCell::new(c3)), weight: 30, ..Default::default() }; + let e1 = Edge::new(c1, 30); + let e2 = Edge::new(c2, 33); + let e3 = Edge::new(c3, 30); let mut v = Voter { who: 1, budget: 100, edges: vec![e1, e2, e3], ..Default::default() }; @@ -231,12 +228,12 @@ fn phragmen_poc_works() { let voters = vec![(10, vec![1, 2]), (20, vec![1, 3]), (30, vec![2, 3])]; let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]); - let ElectionResult { winners, assignments } = seq_phragmen::<_, Perbill>( + let ElectionResult::<_, Perbill> { winners, assignments } = seq_phragmen( 2, candidates, voters .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, ) @@ -286,14 +283,15 @@ fn phragmen_poc_works_with_balancing() { let voters = vec![(10, vec![1, 2]), (20, vec![1, 3]), (30, vec![2, 3])]; let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]); - let ElectionResult { winners, assignments } = seq_phragmen::<_, Perbill>( + let config = BalancingConfig { iterations: 4, tolerance: 0 }; + let ElectionResult::<_, Perbill> { winners, assignments } = seq_phragmen( 2, candidates, voters .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), - Some((4, 0)), + Some(config), ) .unwrap(); @@ -373,12 +371,12 @@ fn phragmen_accuracy_on_large_scale_only_candidates() { (5, (u64::MAX - 2).into()), ]); - let ElectionResult { winners, assignments } = seq_phragmen::<_, Perbill>( + let ElectionResult::<_, Perbill> { winners, assignments } = seq_phragmen( 2, candidates.clone(), auto_generate_self_voters(&candidates) .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, ) @@ -404,12 +402,12 @@ fn phragmen_accuracy_on_large_scale_voters_and_candidates() { (14, u64::MAX.into()), ]); - let ElectionResult { winners, assignments } = seq_phragmen::<_, Perbill>( + let ElectionResult::<_, Perbill> { winners, assignments } = seq_phragmen( 2, candidates, voters .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, ) @@ -436,12 +434,12 @@ fn phragmen_accuracy_on_small_scale_self_vote() { let voters = auto_generate_self_voters(&candidates); let stake_of = create_stake_of(&[(40, 0), (10, 1), (20, 2), (30, 1)]); - let ElectionResult { winners, assignments } = seq_phragmen::<_, Perbill>( + let ElectionResult::<_, Perbill> { winners, assignments } = seq_phragmen( 3, candidates, voters .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, ) @@ -466,12 +464,12 @@ fn phragmen_accuracy_on_small_scale_no_self_vote() { (3, 1), ]); - let ElectionResult { winners, assignments } = seq_phragmen::<_, Perbill>( + let ElectionResult::<_, Perbill> { winners, assignments } = seq_phragmen( 3, candidates, voters .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, ) @@ -502,12 +500,12 @@ fn phragmen_large_scale_test() { (50, 990000000000000000), ]); - let ElectionResult { winners, assignments } = seq_phragmen::<_, Perbill>( + let ElectionResult::<_, Perbill> { winners, assignments } = seq_phragmen( 2, candidates, voters .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, ) @@ -529,12 +527,12 @@ fn phragmen_large_scale_test_2() { let stake_of = create_stake_of(&[(2, c_budget.into()), (4, c_budget.into()), (50, nom_budget.into())]); - let ElectionResult { winners, assignments } = seq_phragmen::<_, Perbill>( + let ElectionResult::<_, Perbill> { winners, assignments } = seq_phragmen( 2, candidates, voters .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, ) @@ -598,12 +596,12 @@ fn elect_has_no_entry_barrier() { let voters = vec![(1, vec![10]), (2, vec![20])]; let stake_of = create_stake_of(&[(1, 10), (2, 10)]); - let ElectionResult { winners, assignments: _ } = seq_phragmen::<_, Perbill>( + let ElectionResult::<_, Perbill> { winners, assignments: _ } = seq_phragmen( 3, candidates, voters .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, ) @@ -619,12 +617,12 @@ fn phragmen_self_votes_should_be_kept() { let voters = vec![(5, vec![5]), (10, vec![10]), (20, vec![20]), (1, vec![10, 20])]; let stake_of = create_stake_of(&[(5, 5), (10, 10), (20, 20), (1, 8)]); - let result = seq_phragmen::<_, Perbill>( + let result: ElectionResult<_, Perbill> = seq_phragmen( 2, candidates, voters .iter() - .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, ) @@ -665,8 +663,8 @@ fn duplicate_target_is_ignored() { let candidates = vec![1, 2, 3]; let voters = vec![(10, 100, vec![1, 1, 2, 3]), (20, 100, vec![2, 3]), (30, 50, vec![1, 1, 2])]; - let ElectionResult { winners, assignments } = - seq_phragmen::<_, Perbill>(2, candidates, voters, None).unwrap(); + let ElectionResult::<_, Perbill> { winners, assignments } = + seq_phragmen(2, candidates, voters, None).unwrap(); assert_eq!(winners, vec![(2, 140), (3, 110)]); assert_eq!( @@ -683,8 +681,8 @@ fn duplicate_target_is_ignored_when_winner() { let candidates = vec![1, 2, 3]; let voters = vec![(10, 100, vec![1, 1, 2, 3]), (20, 100, vec![1, 2])]; - let ElectionResult { winners, assignments } = - seq_phragmen::<_, Perbill>(2, candidates, voters, None).unwrap(); + let ElectionResult::<_, Perbill> { winners, assignments } = + seq_phragmen(2, candidates, voters, None).unwrap(); assert_eq!(winners, vec![(1, 100), (2, 100)]); assert_eq!( @@ -793,6 +791,21 @@ mod assignment_convert_normalize { mod score { use super::*; + use crate::ElectionScore; + use sp_arithmetic::PerThing; + + /// NOTE: in tests, we still use the legacy [u128; 3] since it is more compact. Each `u128` + /// corresponds to element at the respective field index of `ElectionScore`. + impl From<[ExtendedBalance; 3]> for ElectionScore { + fn from(t: [ExtendedBalance; 3]) -> Self { + Self { minimal_stake: t[0], sum_stake: t[1], sum_stake_squared: t[2] } + } + } + + fn is_score_better(this: [u128; 3], that: [u128; 3], p: impl PerThing) -> bool { + ElectionScore::from(this).strict_threshold_better(ElectionScore::from(that), p) + } + #[test] fn score_comparison_is_lexicographical_no_epsilon() { let epsilon = Perbill::zero(); @@ -859,355 +872,34 @@ mod score { let claim = [12488167277027543u128, 5559266368032409496, 118700736389524721358337889258988054]; - assert_eq!( - is_score_better(claim.clone(), initial.clone(), Perbill::from_rational(1u32, 10_000),), - true, - ); - - assert_eq!( - is_score_better(claim.clone(), initial.clone(), Perbill::from_rational(2u32, 10_000),), - true, - ); - - assert_eq!( - is_score_better(claim.clone(), initial.clone(), Perbill::from_rational(3u32, 10_000),), - true, - ); - - assert_eq!( - is_score_better(claim.clone(), initial.clone(), Perbill::from_rational(4u32, 10_000),), - true, - ); - - assert_eq!( - is_score_better(claim.clone(), initial.clone(), Perbill::from_rational(5u32, 10_000),), - false, - ); - } -} - -mod solution_type { - use super::*; - use codec::{Decode, Encode}; - // these need to come from the same dev-dependency `sp-npos-elections`, not from the crate. - use crate::{generate_solution_type, Assignment, Error as NposError, NposSolution}; - use sp_std::{convert::TryInto, fmt::Debug}; - - #[allow(dead_code)] - mod __private { - // This is just to make sure that the solution can be generated in a scope without any - // imports. - use crate::generate_solution_type; - generate_solution_type!( - #[compact] - struct InnerTestSolutionIsolated::(12) - ); - } - - #[test] - fn solution_struct_works_with_and_without_compact() { - // we use u32 size to make sure compact is smaller. - let without_compact = { - generate_solution_type!( - pub struct InnerTestSolution::< - VoterIndex = u32, - TargetIndex = u32, - Accuracy = TestAccuracy, - >(16) - ); - let solution = InnerTestSolution { - votes1: vec![(2, 20), (4, 40)], - votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], - ..Default::default() - }; - - solution.encode().len() - }; - - let with_compact = { - generate_solution_type!( - #[compact] - pub struct InnerTestSolutionCompact::< - VoterIndex = u32, - TargetIndex = u32, - Accuracy = TestAccuracy, - >(16) - ); - let compact = InnerTestSolutionCompact { - votes1: vec![(2, 20), (4, 40)], - votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], - ..Default::default() - }; - - compact.encode().len() - }; - - assert!(with_compact < without_compact); - } - - #[test] - fn solution_struct_is_codec() { - let solution = TestSolution { - votes1: vec![(2, 20), (4, 40)], - votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], - ..Default::default() - }; - - let encoded = solution.encode(); - - assert_eq!(solution, Decode::decode(&mut &encoded[..]).unwrap()); - assert_eq!(solution.voter_count(), 4); - assert_eq!(solution.edge_count(), 2 + 4); - assert_eq!(solution.unique_targets(), vec![10, 11, 20, 40, 50, 51]); - } - - #[test] - fn remove_voter_works() { - let mut solution = TestSolution { - votes1: vec![(0, 2), (1, 6)], - votes2: vec![(2, [(0, p(80))], 1), (3, [(7, p(85))], 8)], - votes3: vec![(4, [(3, p(50)), (4, p(25))], 5)], - ..Default::default() - }; - - assert!(!solution.remove_voter(11)); - assert!(solution.remove_voter(2)); - assert_eq!( - solution, - TestSolution { - votes1: vec![(0, 2), (1, 6)], - votes2: vec![(3, [(7, p(85))], 8)], - votes3: vec![(4, [(3, p(50)), (4, p(25))], 5,)], - ..Default::default() - }, - ); - - assert!(solution.remove_voter(4)); - assert_eq!( - solution, - TestSolution { - votes1: vec![(0, 2), (1, 6)], - votes2: vec![(3, [(7, p(85))], 8)], - ..Default::default() - }, - ); - - assert!(solution.remove_voter(1)); - assert_eq!( - solution, - TestSolution { - votes1: vec![(0, 2)], - votes2: vec![(3, [(7, p(85))], 8),], - ..Default::default() - }, - ); - } - - #[test] - fn from_and_into_assignment_works() { - let voters = vec![2 as AccountId, 4, 1, 5, 3]; - let targets = vec![ - 10 as AccountId, - 11, - 20, // 2 - 30, - 31, // 4 - 32, - 40, // 6 - 50, - 51, // 8 - ]; - - let assignments = vec![ - Assignment { who: 2 as AccountId, distribution: vec![(20u64, p(100))] }, - Assignment { who: 4, distribution: vec![(40, p(100))] }, - Assignment { who: 1, distribution: vec![(10, p(80)), (11, p(20))] }, - Assignment { who: 5, distribution: vec![(50, p(85)), (51, p(15))] }, - Assignment { who: 3, distribution: vec![(30, p(50)), (31, p(25)), (32, p(25))] }, - ]; - - let voter_index = |a: &AccountId| -> Option { - voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() - }; - let target_index = |a: &AccountId| -> Option { - targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() - }; - - let solution = - TestSolution::from_assignment(&assignments, voter_index, target_index).unwrap(); - - // basically number of assignments that it is encoding. - assert_eq!(solution.voter_count(), assignments.len()); - assert_eq!( - solution.edge_count(), - assignments.iter().fold(0, |a, b| a + b.distribution.len()), - ); - - assert_eq!( - solution, - TestSolution { - votes1: vec![(0, 2), (1, 6)], - votes2: vec![(2, [(0, p(80))], 1), (3, [(7, p(85))], 8)], - votes3: vec![(4, [(3, p(50)), (4, p(25))], 5)], - ..Default::default() - } - ); - - assert_eq!(solution.unique_targets(), vec![0, 1, 2, 3, 4, 5, 6, 7, 8]); - - let voter_at = |a: u32| -> Option { - voters.get(>::try_into(a).unwrap()).cloned() - }; - let target_at = |a: u16| -> Option { - targets.get(>::try_into(a).unwrap()).cloned() - }; - - assert_eq!(solution.into_assignment(voter_at, target_at).unwrap(), assignments); - } - - #[test] - fn unique_targets_len_edge_count_works() { - // we don't really care about voters here so all duplicates. This is not invalid per se. - let solution = TestSolution { - votes1: vec![(99, 1), (99, 2)], - votes2: vec![(99, [(3, p(10))], 7), (99, [(4, p(10))], 8)], - votes3: vec![(99, [(11, p(10)), (12, p(10))], 13)], - // ensure the last one is also counted. - votes16: vec![( - 99, - [ - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - ], - 67, - )], - ..Default::default() - }; - - assert_eq!(solution.unique_targets(), vec![1, 2, 3, 4, 7, 8, 11, 12, 13, 66, 67]); - assert_eq!(solution.edge_count(), 2 + (2 * 2) + 3 + 16); - assert_eq!(solution.voter_count(), 6); - - // this one has some duplicates. - let solution = TestSolution { - votes1: vec![(99, 1), (99, 1)], - votes2: vec![(99, [(3, p(10))], 7), (99, [(4, p(10))], 8)], - votes3: vec![(99, [(11, p(10)), (11, p(10))], 13)], - ..Default::default() - }; - - assert_eq!(solution.unique_targets(), vec![1, 3, 4, 7, 8, 11, 13]); - assert_eq!(solution.edge_count(), 2 + (2 * 2) + 3); - assert_eq!(solution.voter_count(), 5); - } - - #[test] - fn solution_into_assignment_must_report_overflow() { - // in votes2 - let solution = TestSolution { - votes1: Default::default(), - votes2: vec![(0, [(1, p(100))], 2)], - ..Default::default() - }; - - let voter_at = |a: u32| -> Option { Some(a as AccountId) }; - let target_at = |a: u16| -> Option { Some(a as AccountId) }; - - assert_eq!( - solution.into_assignment(&voter_at, &target_at).unwrap_err(), - NposError::SolutionWeightOverflow, - ); + assert_eq!(is_score_better(claim, initial, Perbill::from_rational(1u32, 10_000),), true,); - // in votes3 onwards - let solution = TestSolution { - votes1: Default::default(), - votes2: Default::default(), - votes3: vec![(0, [(1, p(70)), (2, p(80))], 3)], - ..Default::default() - }; - - assert_eq!( - solution.into_assignment(&voter_at, &target_at).unwrap_err(), - NposError::SolutionWeightOverflow, - ); - } + assert_eq!(is_score_better(claim, initial, Perbill::from_rational(2u32, 10_000),), true,); - #[test] - fn target_count_overflow_is_detected() { - let voter_index = |a: &AccountId| -> Option { Some(*a as u32) }; - let target_index = |a: &AccountId| -> Option { Some(*a as u16) }; + assert_eq!(is_score_better(claim, initial, Perbill::from_rational(3u32, 10_000),), true,); - let assignments = vec![Assignment { - who: 1 as AccountId, - distribution: (10..27).map(|i| (i as AccountId, p(i as u8))).collect::>(), - }]; + assert_eq!(is_score_better(claim, initial, Perbill::from_rational(4u32, 10_000),), true,); - let solution = TestSolution::from_assignment(&assignments, voter_index, target_index); - assert_eq!(solution.unwrap_err(), NposError::SolutionTargetOverflow); + assert_eq!(is_score_better(claim, initial, Perbill::from_rational(5u32, 10_000),), false,); } #[test] - fn zero_target_count_is_ignored() { - let voters = vec![1 as AccountId, 2]; - let targets = vec![10 as AccountId, 11]; - - let assignments = vec![ - Assignment { who: 1 as AccountId, distribution: vec![(10, p(50)), (11, p(50))] }, - Assignment { who: 2, distribution: vec![] }, - ]; - - let voter_index = |a: &AccountId| -> Option { - voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() - }; - let target_index = |a: &AccountId| -> Option { - targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() - }; - - let solution = - TestSolution::from_assignment(&assignments, voter_index, target_index).unwrap(); - - assert_eq!( - solution, - TestSolution { - votes1: Default::default(), - votes2: vec![(0, [(0, p(50))], 1)], - ..Default::default() - } - ); + fn ord_works() { + // equal only when all elements are equal + assert!(ElectionScore::from([10, 5, 15]) == ElectionScore::from([10, 5, 15])); + assert!(ElectionScore::from([10, 5, 15]) != ElectionScore::from([9, 5, 15])); + assert!(ElectionScore::from([10, 5, 15]) != ElectionScore::from([10, 5, 14])); + + // first element greater, rest don't matter + assert!(ElectionScore::from([10, 5, 15]) > ElectionScore::from([8, 5, 25])); + assert!(ElectionScore::from([10, 5, 15]) > ElectionScore::from([9, 20, 5])); + + // second element greater, rest don't matter + assert!(ElectionScore::from([10, 5, 15]) > ElectionScore::from([10, 4, 25])); + assert!(ElectionScore::from([10, 5, 15]) > ElectionScore::from([10, 4, 5])); + + // second element is less, rest don't matter. Note that this is swapped. + assert!(ElectionScore::from([10, 5, 15]) > ElectionScore::from([10, 5, 16])); + assert!(ElectionScore::from([10, 5, 15]) > ElectionScore::from([10, 5, 25])); } } - -#[test] -fn index_assignments_generate_same_solution_as_plain_assignments() { - let rng = rand::rngs::SmallRng::seed_from_u64(0); - - let (voters, assignments, candidates) = generate_random_votes(1000, 2500, rng); - let voter_index = make_voter_fn(&voters); - let target_index = make_target_fn(&candidates); - - let solution = - TestSolution::from_assignment(&assignments, &voter_index, &target_index).unwrap(); - - let index_assignments = assignments - .into_iter() - .map(|assignment| IndexAssignment::new(&assignment, &voter_index, &target_index)) - .collect::, _>>() - .unwrap(); - - let index_compact = index_assignments.as_slice().try_into().unwrap(); - - assert_eq!(solution, index_compact); -} diff --git a/primitives/npos-elections/src/traits.rs b/primitives/npos-elections/src/traits.rs index 45b6fa368ae2a..91026f9de4b6b 100644 --- a/primitives/npos-elections/src/traits.rs +++ b/primitives/npos-elections/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -10,145 +10,22 @@ // 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. +// distributed under the License is distributed on an "AS IS" BASIS, // See the License for the specific language governing permissions and // limitations under the License. //! Traits for the npos-election operations. -use crate::{ - Assignment, ElectionScore, Error, EvaluateSupport, ExtendedBalance, IndexAssignmentOf, - VoteWeight, -}; -use codec::Encode; -use sp_arithmetic::{ - traits::{Bounded, UniqueSaturatedInto}, - PerThing, -}; -use sp_std::{ - convert::{TryFrom, TryInto}, - fmt::Debug, - ops::Mul, - prelude::*, -}; +use crate::ExtendedBalance; +use sp_arithmetic::PerThing; +use sp_std::{fmt::Debug, ops::Mul, prelude::*}; /// an aggregator trait for a generic type of a voter/target identifier. This usually maps to /// substrate's account id. -pub trait IdentifierT: Clone + Eq + Default + Ord + Debug + codec::Codec {} -impl IdentifierT for T {} +pub trait IdentifierT: Clone + Eq + Ord + Debug + codec::Codec {} +impl IdentifierT for T {} /// Aggregator trait for a PerThing that can be multiplied by u128 (ExtendedBalance). pub trait PerThing128: PerThing + Mul {} impl> PerThing128 for T {} - -/// Simple Extension trait to easily convert `None` from index closures to `Err`. -/// -/// This is only generated and re-exported for the solution code to use. -#[doc(hidden)] -pub trait __OrInvalidIndex { - fn or_invalid_index(self) -> Result; -} - -impl __OrInvalidIndex for Option { - fn or_invalid_index(self) -> Result { - self.ok_or(Error::SolutionInvalidIndex) - } -} - -/// An opaque index-based, NPoS solution type. -pub trait NposSolution -where - Self: Sized + for<'a> sp_std::convert::TryFrom<&'a [IndexAssignmentOf], Error = Error>, -{ - /// The maximum number of votes that are allowed. - const LIMIT: usize; - - /// The voter type. Needs to be an index (convert to usize). - type VoterIndex: UniqueSaturatedInto - + TryInto - + TryFrom - + Debug - + Copy - + Clone - + Bounded - + Encode; - - /// The target type. Needs to be an index (convert to usize). - type TargetIndex: UniqueSaturatedInto - + TryInto - + TryFrom - + Debug - + Copy - + Clone - + Bounded - + Encode; - - /// The weight/accuracy type of each vote. - type Accuracy: PerThing128; - - /// Get the length of all the voters that this type is encoding. - /// - /// This is basically the same as the number of assignments, or number of active voters. - fn voter_count(&self) -> usize; - - /// Get the total count of edges. - /// - /// This is effectively in the range of {[`Self::voter_count`], [`Self::voter_count`] * - /// [`Self::LIMIT`]}. - fn edge_count(&self) -> usize; - - /// Get the number of unique targets in the whole struct. - /// - /// Once presented with a list of winners, this set and the set of winners must be - /// equal. - fn unique_targets(&self) -> Vec; - - /// Get the average edge count. - fn average_edge_count(&self) -> usize { - self.edge_count().checked_div(self.voter_count()).unwrap_or(0) - } - - /// Compute the score of this solution type. - fn score( - self, - stake_of: FS, - voter_at: impl Fn(Self::VoterIndex) -> Option, - target_at: impl Fn(Self::TargetIndex) -> Option, - ) -> Result - where - for<'r> FS: Fn(&'r A) -> VoteWeight, - A: IdentifierT, - { - let ratio = self.into_assignment(voter_at, target_at)?; - let staked = crate::helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?; - let supports = crate::to_supports(&staked); - Ok(supports.evaluate()) - } - - /// Remove a certain voter. - /// - /// This will only search until the first instance of `to_remove`, and return true. If - /// no instance is found (no-op), then it returns false. - /// - /// In other words, if this return true, exactly **one** element must have been removed self. - fn remove_voter(&mut self, to_remove: Self::VoterIndex) -> bool; - - /// Build self from a list of assignments. - fn from_assignment( - assignments: &[Assignment], - voter_index: FV, - target_index: FT, - ) -> Result - where - A: IdentifierT, - for<'r> FV: Fn(&'r A) -> Option, - for<'r> FT: Fn(&'r A) -> Option; - - /// Convert self into a `Vec>` - fn into_assignment( - self, - voter_at: impl Fn(Self::VoterIndex) -> Option, - target_at: impl Fn(Self::TargetIndex) -> Option, - ) -> Result>, Error>; -} diff --git a/primitives/offchain/Cargo.toml b/primitives/offchain/Cargo.toml index dd54147b6c629..cb567893776e0 100644 --- a/primitives/offchain/Cargo.toml +++ b/primitives/offchain/Cargo.toml @@ -4,8 +4,8 @@ name = "sp-offchain" version = "4.0.0-dev" license = "Apache-2.0" authors = ["Parity Technologies "] -edition = "2018" -homepage = "https://substrate.dev" +edition = "2021" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,10 +13,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } [features] default = ["std"] -std = ["sp-core/std", "sp-api/std", "sp-runtime/std"] +std = ["sp-api/std", "sp-core/std", "sp-runtime/std"] diff --git a/primitives/offchain/src/lib.rs b/primitives/offchain/src/lib.rs index 72ceca80cfbf8..3e967f16d2a84 100644 --- a/primitives/offchain/src/lib.rs +++ b/primitives/offchain/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/panic-handler/Cargo.toml b/primitives/panic-handler/Cargo.toml index ad03baca24ebb..9da052b4a05e1 100644 --- a/primitives/panic-handler/Cargo.toml +++ b/primitives/panic-handler/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-panic-handler" -version = "3.0.0" +version = "5.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Custom panic hook with bug report link" documentation = "https://docs.rs/sp-panic-handler" @@ -14,4 +14,6 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -backtrace = "0.3.38" +backtrace = "0.3.64" +lazy_static = "1.4.0" +regex = "1.6.0" diff --git a/primitives/panic-handler/src/lib.rs b/primitives/panic-handler/src/lib.rs index 75b057cebf3e4..e06fe90ad6f3b 100644 --- a/primitives/panic-handler/src/lib.rs +++ b/primitives/panic-handler/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,7 @@ //! temporarily be disabled by using an [`AbortGuard`]. use backtrace::Backtrace; +use regex::Regex; use std::{ cell::Cell, io::{self, Write}, @@ -125,6 +126,24 @@ impl Drop for AbortGuard { } } +// NOTE: When making any changes here make sure to also change this function in `sc-tracing`. +fn strip_control_codes(input: &str) -> std::borrow::Cow { + lazy_static::lazy_static! { + static ref RE: Regex = Regex::new(r#"(?x) + \x1b\[[^m]+m| # VT100 escape codes + [ + \x00-\x09\x0B-\x1F # ASCII control codes / Unicode C0 control codes, except \n + \x7F # ASCII delete + \u{80}-\u{9F} # Unicode C1 control codes + \u{202A}-\u{202E} # Unicode left-to-right / right-to-left control characters + \u{2066}-\u{2069} # Same as above + ] + "#).expect("regex parsing doesn't fail; qed"); + } + + RE.replace_all(input, "") +} + /// Function being called when a panic happens. fn panic_hook(info: &PanicInfo, report_url: &str, version: &str) { let location = info.location(); @@ -139,6 +158,8 @@ fn panic_hook(info: &PanicInfo, report_url: &str, version: &str) { }, }; + let msg = strip_control_codes(msg); + let thread = thread::current(); let name = thread.name().unwrap_or(""); @@ -146,13 +167,13 @@ fn panic_hook(info: &PanicInfo, report_url: &str, version: &str) { let mut stderr = io::stderr(); - let _ = writeln!(stderr, ""); + let _ = writeln!(stderr); let _ = writeln!(stderr, "===================="); - let _ = writeln!(stderr, ""); + let _ = writeln!(stderr); let _ = writeln!(stderr, "Version: {}", version); - let _ = writeln!(stderr, ""); + let _ = writeln!(stderr); let _ = writeln!(stderr, "{:?}", backtrace); - let _ = writeln!(stderr, ""); + let _ = writeln!(stderr); let _ = writeln!(stderr, "Thread '{}' panicked at '{}', {}:{}", name, msg, file, line); let _ = writeln!(stderr, ABOUT_PANIC!(), report_url); @@ -181,4 +202,44 @@ mod tests { let _guard = AbortGuard::force_abort(); std::panic::catch_unwind(|| panic!()).ok(); } + + fn run_test_in_another_process( + test_name: &str, + test_body: impl FnOnce(), + ) -> Option { + if std::env::var("RUN_FORKED_TEST").is_ok() { + test_body(); + None + } else { + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .arg(test_name) + .env("RUN_FORKED_TEST", "1") + .output() + .unwrap(); + + assert!(output.status.success()); + Some(output) + } + } + + #[test] + fn control_characters_are_always_stripped_out_from_the_panic_messages() { + const RAW_LINE: &str = "$$START$$\x1B[1;32mIn\u{202a}\u{202e}\u{2066}\u{2069}ner\n\r\x7ftext!\u{80}\u{9f}\x1B[0m$$END$$"; + const SANITIZED_LINE: &str = "$$START$$Inner\ntext!$$END$$"; + + let output = run_test_in_another_process( + "control_characters_are_always_stripped_out_from_the_panic_messages", + || { + set("test", "1.2.3"); + let _guard = AbortGuard::force_unwind(); + let _ = std::panic::catch_unwind(|| panic!("{}", RAW_LINE)); + }, + ); + + if let Some(output) = output { + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(!stderr.contains(RAW_LINE)); + assert!(stderr.contains(SANITIZED_LINE)); + } + } } diff --git a/primitives/rpc/Cargo.toml b/primitives/rpc/Cargo.toml index 8e1b91a9acb21..ef9fdc544301d 100644 --- a/primitives/rpc/Cargo.toml +++ b/primitives/rpc/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-rpc" -version = "4.0.0-dev" +version = "6.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate RPC primitives and utilities." readme = "README.md" @@ -13,9 +13,9 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.126", features = ["derive"] } -sp-core = { version = "4.0.0-dev", path = "../core" } rustc-hash = "1.1.0" +serde = { version = "1.0.136", features = ["derive"] } +sp-core = { version = "7.0.0", path = "../core" } [dev-dependencies] -serde_json = "1.0.68" +serde_json = "1.0.85" diff --git a/primitives/rpc/src/lib.rs b/primitives/rpc/src/lib.rs index 0d716d5a07c18..915482e5fca70 100644 --- a/primitives/rpc/src/lib.rs +++ b/primitives/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/rpc/src/list.rs b/primitives/rpc/src/list.rs index b3d0a4f546e94..7ec4d3491c7ea 100644 --- a/primitives/rpc/src/list.rs +++ b/primitives/rpc/src/list.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/rpc/src/number.rs b/primitives/rpc/src/number.rs index 916f2c3d83266..81084a09d4ac8 100644 --- a/primitives/rpc/src/number.rs +++ b/primitives/rpc/src/number.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,10 +20,7 @@ use serde::{Deserialize, Serialize}; use sp_core::U256; -use std::{ - convert::{TryFrom, TryInto}, - fmt::Debug, -}; +use std::fmt::Debug; /// A number type that can be serialized both as a number or a string that encodes a number in a /// string. diff --git a/primitives/rpc/src/tracing.rs b/primitives/rpc/src/tracing.rs index 737ace241037c..a923ffcb69e0d 100644 --- a/primitives/rpc/src/tracing.rs +++ b/primitives/rpc/src/tracing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/Cargo.toml b/primitives/runtime-interface/Cargo.toml index dd1b84eabfe93..09f4b83d68b34 100644 --- a/primitives/runtime-interface/Cargo.toml +++ b/primitives/runtime-interface/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-runtime-interface" -version = "4.0.0-dev" +version = "7.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate runtime interface" documentation = "https://docs.rs/sp-runtime-interface/" @@ -14,33 +14,36 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-wasm-interface = { version = "4.0.0-dev", path = "../wasm-interface", default-features = false } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -sp-tracing = { version = "4.0.0-dev", default-features = false, path = "../tracing" } -sp-runtime-interface-proc-macro = { version = "4.0.0-dev", path = "proc-macro" } -sp-externalities = { version = "0.10.0-dev", optional = true, path = "../externalities" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } +bytes = { version = "1.1.0", default-features = false } +sp-wasm-interface = { version = "7.0.0", path = "../wasm-interface", default-features = false } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-tracing = { version = "6.0.0", default-features = false, path = "../tracing" } +sp-runtime-interface-proc-macro = { version = "6.0.0", path = "proc-macro" } +sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["bytes"] } static_assertions = "1.0.0" -primitive-types = { version = "0.10.1", default-features = false } -sp-storage = { version = "4.0.0-dev", default-features = false, path = "../storage" } -impl-trait-for-tuples = "0.2.1" +primitive-types = { version = "0.12.0", default-features = false } +sp-storage = { version = "7.0.0", default-features = false, path = "../storage" } +impl-trait-for-tuples = "0.2.2" [dev-dependencies] sp-runtime-interface-test-wasm = { version = "2.0.0", path = "test-wasm" } -sp-state-machine = { version = "0.10.0-dev", path = "../state-machine" } -sp-core = { version = "4.0.0-dev", path = "../core" } -sp-io = { version = "4.0.0-dev", path = "../io" } -rustversion = "1.0.0" -trybuild = "1.0.43" +sp-state-machine = { version = "0.13.0", path = "../state-machine" } +sp-core = { version = "7.0.0", path = "../core" } +sp-io = { version = "7.0.0", path = "../io" } +rustversion = "1.0.6" +trybuild = "1.0.60" [features] default = [ "std" ] std = [ + "sp-storage/std", + "bytes/std", "sp-wasm-interface/std", "sp-std/std", "sp-tracing/std", "codec/std", - "sp-externalities", + "sp-externalities/std", "primitive-types/std", ] diff --git a/primitives/runtime-interface/proc-macro/Cargo.toml b/primitives/runtime-interface/proc-macro/Cargo.toml index 1eb3bdd9039d9..9bc7161012cb1 100644 --- a/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/primitives/runtime-interface/proc-macro/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-runtime-interface-proc-macro" -version = "4.0.0-dev" +version = "6.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "This crate provides procedural macros for usage within the context of the Substrate runtime interface." documentation = "https://docs.rs/sp-runtime-interface-proc-macro" @@ -16,8 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "1.0.58", features = ["full", "visit", "fold", "extra-traits"] } -quote = "1.0.3" -proc-macro2 = "1.0.29" Inflector = "0.11.4" -proc-macro-crate = "1.0.0" +proc-macro-crate = "1.1.3" +proc-macro2 = "1.0.37" +quote = "1.0.10" +syn = { version = "1.0.98", features = ["full", "visit", "fold", "extra-traits"] } diff --git a/primitives/runtime-interface/proc-macro/src/lib.rs b/primitives/runtime-interface/proc-macro/src/lib.rs index 6b0669a298e1c..afba38993fe76 100644 --- a/primitives/runtime-interface/proc-macro/src/lib.rs +++ b/primitives/runtime-interface/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/proc-macro/src/pass_by/codec.rs b/primitives/runtime-interface/proc-macro/src/pass_by/codec.rs index 2be455d17a47b..4259b137d67cb 100644 --- a/primitives/runtime-interface/proc-macro/src/pass_by/codec.rs +++ b/primitives/runtime-interface/proc-macro/src/pass_by/codec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs b/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs index f614e4d9f294d..e25295fdca5cb 100644 --- a/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs +++ b/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,7 +51,7 @@ pub fn derive_impl(input: DeriveInput) -> Result { type PassBy = #crate_::pass_by::Enum<#ident>; } - impl #crate_::sp_std::convert::TryFrom for #ident { + impl TryFrom for #ident { type Error = (); fn try_from(inner: u8) -> #crate_::sp_std::result::Result { @@ -79,7 +79,7 @@ pub fn derive_impl(input: DeriveInput) -> Result { /// /// Returns an error if the number of variants is greater than `256`, the given `data` is not an /// enum or a variant is not an unit. -fn get_enum_field_idents<'a>(data: &'a Data) -> Result>> { +fn get_enum_field_idents(data: &Data) -> Result>> { match data { Data::Enum(d) => if d.variants.len() <= 256 { diff --git a/primitives/runtime-interface/proc-macro/src/pass_by/inner.rs b/primitives/runtime-interface/proc-macro/src/pass_by/inner.rs index 6eaa689d6293a..7a527af129467 100644 --- a/primitives/runtime-interface/proc-macro/src/pass_by/inner.rs +++ b/primitives/runtime-interface/proc-macro/src/pass_by/inner.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/proc-macro/src/pass_by/mod.rs b/primitives/runtime-interface/proc-macro/src/pass_by/mod.rs index 80ac3396759fb..e32c2beb8b72a 100644 --- a/primitives/runtime-interface/proc-macro/src/pass_by/mod.rs +++ b/primitives/runtime-interface/proc-macro/src/pass_by/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs b/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs index c951dedb67713..b5745e25deb4c 100644 --- a/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs +++ b/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,11 +32,12 @@ use crate::utils::{ create_exchangeable_host_function_ident, create_function_ident_with_version, generate_crate_access, get_function_argument_names, get_function_arguments, - get_runtime_interface, + get_runtime_interface, RuntimeInterfaceFunction, }; use syn::{ - parse_quote, spanned::Spanned, FnArg, Ident, ItemTrait, Result, Signature, TraitItemMethod, + parse_quote, spanned::Spanned, FnArg, Ident, ItemTrait, Result, Signature, Token, + TraitItemMethod, }; use proc_macro2::{Span, TokenStream}; @@ -52,7 +53,7 @@ pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool, tracing: bool) -> Res let runtime_interface = get_runtime_interface(trait_def)?; // latest version dispatch - let token_stream: Result = runtime_interface.latest_versions().try_fold( + let token_stream: Result = runtime_interface.latest_versions_to_call().try_fold( TokenStream::new(), |mut t, (latest_version, method)| { t.extend(function_for_method(method, latest_version, is_wasm_only)?); @@ -74,14 +75,14 @@ pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool, tracing: bool) -> Res /// Generates the bare function implementation for the given method for the host and wasm side. fn function_for_method( - method: &TraitItemMethod, + method: &RuntimeInterfaceFunction, latest_version: u32, is_wasm_only: bool, ) -> Result { let std_impl = if !is_wasm_only { function_std_latest_impl(method, latest_version)? } else { quote!() }; - let no_std_impl = function_no_std_impl(method)?; + let no_std_impl = function_no_std_impl(method, is_wasm_only)?; Ok(quote! { #std_impl @@ -91,20 +92,46 @@ fn function_for_method( } /// Generates the bare function implementation for `cfg(not(feature = "std"))`. -fn function_no_std_impl(method: &TraitItemMethod) -> Result { +fn function_no_std_impl( + method: &RuntimeInterfaceFunction, + is_wasm_only: bool, +) -> Result { let function_name = &method.sig.ident; let host_function_name = create_exchangeable_host_function_ident(&method.sig.ident); let args = get_function_arguments(&method.sig); let arg_names = get_function_argument_names(&method.sig); - let return_value = &method.sig.output; + let return_value = if method.should_trap_on_return() { + syn::ReturnType::Type( + ]>::default(), + Box::new(syn::TypeNever { bang_token: ::default() }.into()), + ) + } else { + method.sig.output.clone() + }; + let maybe_unreachable = if method.should_trap_on_return() { + quote! { + ; core::arch::wasm32::unreachable(); + } + } else { + quote! {} + }; + let attrs = method.attrs.iter().filter(|a| !a.path.is_ident("version")); + let cfg_wasm_only = if is_wasm_only { + quote! { #[cfg(target_arch = "wasm32")] } + } else { + quote! {} + }; + Ok(quote! { + #cfg_wasm_only #[cfg(not(feature = "std"))] #( #attrs )* pub fn #function_name( #( #args, )* ) #return_value { // Call the host function #host_function_name.get()( #( #arg_names, )* ) + #maybe_unreachable } }) } diff --git a/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs b/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs index 75498c09c18c7..03da0bed59815 100644 --- a/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs +++ b/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,7 +26,7 @@ use crate::utils::{ create_host_function_ident, generate_crate_access, get_function_argument_names, get_function_argument_names_and_types_without_ref, get_function_argument_types, get_function_argument_types_ref_and_mut, get_function_argument_types_without_ref, - get_function_arguments, get_runtime_interface, + get_function_arguments, get_runtime_interface, RuntimeInterfaceFunction, }; use syn::{ @@ -35,29 +35,28 @@ use syn::{ use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens}; +use quote::quote; use inflector::Inflector; -use std::iter::{self, Iterator}; +use std::iter::Iterator; /// Generate the extern host functions for wasm and the `HostFunctions` struct that provides the /// implementations for the host functions on the host. pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result { let trait_name = &trait_def.ident; - let extern_host_function_impls = get_runtime_interface(trait_def)?.latest_versions().try_fold( - TokenStream::new(), - |mut t, (version, method)| { + let extern_host_function_impls = get_runtime_interface(trait_def)? + .latest_versions_to_call() + .try_fold(TokenStream::new(), |mut t, (version, method)| { t.extend(generate_extern_host_function(method, version, trait_name)?); Ok::<_, Error>(t) - }, - )?; + })?; let exchangeable_host_functions = get_runtime_interface(trait_def)? - .latest_versions() + .latest_versions_to_call() .try_fold(TokenStream::new(), |mut t, (_, m)| { - t.extend(generate_exchangeable_host_function(m)?); - Ok::<_, Error>(t) - })?; + t.extend(generate_exchangeable_host_function(m)?); + Ok::<_, Error>(t) + })?; let host_functions_struct = generate_host_functions_struct(trait_def, is_wasm_only)?; Ok(quote! { @@ -163,14 +162,20 @@ fn generate_host_functions_struct( ) -> Result { let crate_ = generate_crate_access(); - let host_functions = get_runtime_interface(trait_def)? - .all_versions() - .map(|(version, method)| { - generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only) - }) - .collect::>>()?; + let mut host_function_impls = Vec::new(); + let mut host_function_names = Vec::new(); + let mut register_bodies = Vec::new(); + for (version, method) in get_runtime_interface(trait_def)?.all_versions() { + let (implementation, name, register_body) = + generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?; + host_function_impls.push(implementation); + host_function_names.push(name); + register_bodies.push(register_body); + } Ok(quote! { + #(#host_function_impls)* + /// Provides implementations for the extern host functions. #[cfg(feature = "std")] pub struct HostFunctions; @@ -178,7 +183,16 @@ fn generate_host_functions_struct( #[cfg(feature = "std")] impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions { fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> { - vec![ #( #host_functions ),* ] + vec![ #( &#host_function_names as &dyn #crate_::sp_wasm_interface::Function ),* ] + } + + #crate_::sp_wasm_interface::if_wasmtime_is_enabled! { + fn register_static(registry: &mut T) -> core::result::Result<(), T::Error> + where T: #crate_::sp_wasm_interface::HostFunctionRegistry + { + #(#register_bodies)* + Ok(()) + } } } }) @@ -191,50 +205,201 @@ fn generate_host_functions_struct( /// implementation of the function. fn generate_host_function_implementation( trait_name: &Ident, - method: &TraitItemMethod, + method: &RuntimeInterfaceFunction, version: u32, is_wasm_only: bool, -) -> Result { +) -> Result<(TokenStream, Ident, TokenStream)> { let name = create_host_function_ident(&method.sig.ident, version, trait_name).to_string(); let struct_name = Ident::new(&name.to_pascal_case(), Span::call_site()); let crate_ = generate_crate_access(); let signature = generate_wasm_interface_signature_for_host_function(&method.sig)?; - let wasm_to_ffi_values = - generate_wasm_to_ffi_values(&method.sig, trait_name).collect::>>()?; - let ffi_to_host_values = generate_ffi_to_host_value(&method.sig).collect::>>()?; - let host_function_call = generate_host_function_call(&method.sig, version, is_wasm_only); - let into_preallocated_ffi_value = generate_into_preallocated_ffi_value(&method.sig)?; - let convert_return_value = generate_return_value_into_wasm_value(&method.sig); - Ok(quote! { - { - struct #struct_name; + let fn_name = create_function_ident_with_version(&method.sig.ident, version); + let ref_and_mut = get_function_argument_types_ref_and_mut(&method.sig); + + // List of variable names containing WASM FFI-compatible arguments. + let mut ffi_names = Vec::new(); + + // List of `$name: $ty` tokens containing WASM FFI-compatible arguments. + let mut ffi_args_prototype = Vec::new(); + + // List of variable names containing arguments already converted into native Rust types. + // Also includes the preceding `&` or `&mut`. To be used to call the actual implementation of + // the host function. + let mut host_names_with_ref = Vec::new(); + + // List of code snippets to copy over the results returned from a host function through + // any `&mut` arguments back into WASM's linear memory. + let mut copy_data_into_ref_mut_args = Vec::new(); + + // List of code snippets to convert dynamic FFI args (`Value` enum) into concrete static FFI + // types (`u32`, etc.). + let mut convert_args_dynamic_ffi_to_static_ffi = Vec::new(); + + // List of code snippets to convert static FFI args (`u32`, etc.) into native Rust types. + let mut convert_args_static_ffi_to_host = Vec::new(); + + for ((host_name, host_ty), ref_and_mut) in + get_function_argument_names_and_types_without_ref(&method.sig).zip(ref_and_mut) + { + let ffi_name = generate_ffi_value_var_name(&host_name)?; + let host_name_ident = match *host_name { + Pat::Ident(ref pat_ident) => pat_ident.ident.clone(), + _ => unreachable!("`generate_ffi_value_var_name` above would return an error on `Pat` != `Ident`; qed"), + }; + + let ffi_ty = quote! { <#host_ty as #crate_::RIType>::FFIType }; + ffi_args_prototype.push(quote! { #ffi_name: #ffi_ty }); + ffi_names.push(quote! { #ffi_name }); + + let convert_arg_error = format!( + "could not marshal the '{}' argument through the WASM FFI boundary while executing '{}' from interface '{}'", + host_name_ident, + method.sig.ident, + trait_name + ); + convert_args_static_ffi_to_host.push(quote! { + let mut #host_name = <#host_ty as #crate_::host::FromFFIValue>::from_ffi_value(__function_context__, #ffi_name) + .map_err(|err| format!("{}: {}", err, #convert_arg_error))?; + }); - impl #crate_::sp_wasm_interface::Function for #struct_name { - fn name(&self) -> &str { - #name - } + let ref_and_mut_tokens = + ref_and_mut.map(|(token_ref, token_mut)| quote!(#token_ref #token_mut)); - fn signature(&self) -> #crate_::sp_wasm_interface::Signature { - #signature - } + host_names_with_ref.push(quote! { #ref_and_mut_tokens #host_name }); - fn execute( - &self, - __function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext, - args: &mut dyn Iterator, - ) -> std::result::Result, String> { - #( #wasm_to_ffi_values )* - #( #ffi_to_host_values )* - #host_function_call - #into_preallocated_ffi_value - #convert_return_value - } + if ref_and_mut.map(|(_, token_mut)| token_mut.is_some()).unwrap_or(false) { + copy_data_into_ref_mut_args.push(quote! { + <#host_ty as #crate_::host::IntoPreallocatedFFIValue>::into_preallocated_ffi_value( + #host_name, + __function_context__, + #ffi_name, + )?; + }); + } + + let arg_count_mismatch_error = format!( + "missing argument '{}': number of arguments given to '{}' from interface '{}' does not match the expected number of arguments", + host_name_ident, + method.sig.ident, + trait_name + ); + convert_args_dynamic_ffi_to_static_ffi.push(quote! { + let #ffi_name = args.next().ok_or_else(|| #arg_count_mismatch_error.to_owned())?; + let #ffi_name: #ffi_ty = #crate_::sp_wasm_interface::TryFromValue::try_from_value(#ffi_name) + .ok_or_else(|| #convert_arg_error.to_owned())?; + }); + } + + let ffi_return_ty = match &method.sig.output { + ReturnType::Type(_, ty) => quote! { <#ty as #crate_::RIType>::FFIType }, + ReturnType::Default => quote! { () }, + }; + + let convert_return_value_host_to_static_ffi = match &method.sig.output { + ReturnType::Type(_, ty) => quote! { + let __result__ = <#ty as #crate_::host::IntoFFIValue>::into_ffi_value( + __result__, + __function_context__ + ); + }, + ReturnType::Default => quote! { + let __result__ = Ok(__result__); + }, + }; + + let convert_return_value_static_ffi_to_dynamic_ffi = match &method.sig.output { + ReturnType::Type(_, _) => quote! { + let __result__ = Ok(Some(#crate_::sp_wasm_interface::IntoValue::into_value(__result__))); + }, + ReturnType::Default => quote! { + let __result__ = Ok(None); + }, + }; + + if is_wasm_only { + host_names_with_ref.push(quote! { + __function_context__ + }); + } + + let implementation = quote! { + #[cfg(feature = "std")] + struct #struct_name; + + #[cfg(feature = "std")] + impl #struct_name { + fn call( + __function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext, + #(#ffi_args_prototype),* + ) -> std::result::Result<#ffi_return_ty, String> { + #(#convert_args_static_ffi_to_host)* + let __result__ = #fn_name(#(#host_names_with_ref),*); + #(#copy_data_into_ref_mut_args)* + #convert_return_value_host_to_static_ffi + __result__ } + } - &#struct_name as &dyn #crate_::sp_wasm_interface::Function + #[cfg(feature = "std")] + impl #crate_::sp_wasm_interface::Function for #struct_name { + fn name(&self) -> &str { + #name + } + + fn signature(&self) -> #crate_::sp_wasm_interface::Signature { + #signature + } + + fn execute( + &self, + __function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext, + args: &mut dyn Iterator, + ) -> std::result::Result, String> { + #(#convert_args_dynamic_ffi_to_static_ffi)* + let __result__ = Self::call( + __function_context__, + #(#ffi_names),* + )?; + #convert_return_value_static_ffi_to_dynamic_ffi + __result__ + } } - }) + }; + + let register_body = quote! { + registry.register_static( + #crate_::sp_wasm_interface::Function::name(&#struct_name), + |mut caller: #crate_::sp_wasm_interface::wasmtime::Caller, #(#ffi_args_prototype),*| + -> std::result::Result<#ffi_return_ty, #crate_::sp_wasm_interface::wasmtime::Trap> + { + T::with_function_context(caller, move |__function_context__| { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + #struct_name::call( + __function_context__, + #(#ffi_names,)* + ).map_err(#crate_::sp_wasm_interface::wasmtime::Trap::new) + })); + match result { + Ok(result) => result, + Err(panic) => { + let message = + if let Some(message) = panic.downcast_ref::() { + format!("host code panicked while being called by the runtime: {}", message) + } else if let Some(message) = panic.downcast_ref::<&'static str>() { + format!("host code panicked while being called by the runtime: {}", message) + } else { + "host code panicked while being called by the runtime".to_owned() + }; + return Err(#crate_::sp_wasm_interface::wasmtime::Trap::new(message)); + } + } + }) + } + )?; + }; + + Ok((implementation, struct_name, register_body)) } /// Generate the `wasm_interface::Signature` for the given host function `sig`. @@ -260,86 +425,6 @@ fn generate_wasm_interface_signature_for_host_function(sig: &Signature) -> Resul }) } -/// Generate the code that converts the wasm values given to `HostFunctions::execute` into the FFI -/// values. -fn generate_wasm_to_ffi_values<'a>( - sig: &'a Signature, - trait_name: &'a Ident, -) -> impl Iterator> + 'a { - let crate_ = generate_crate_access(); - let function_name = &sig.ident; - let error_message = format!( - "Number of arguments given to `{}` does not match the expected number of arguments!", - function_name, - ); - - get_function_argument_names_and_types_without_ref(sig).map(move |(name, ty)| { - let try_from_error = format!( - "Could not instantiate `{}` from wasm value while executing `{}` from interface `{}`!", - name.to_token_stream(), - function_name, - trait_name, - ); - - let var_name = generate_ffi_value_var_name(&name)?; - - Ok(quote! { - let val = args.next().ok_or_else(|| #error_message)?; - let #var_name = < - <#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::TryFromValue - >::try_from_value(val).ok_or_else(|| #try_from_error)?; - }) - }) -} - -/// Generate the code to convert the ffi values on the host to the host values using `FromFFIValue`. -fn generate_ffi_to_host_value<'a>( - sig: &'a Signature, -) -> impl Iterator> + 'a { - let mut_access = get_function_argument_types_ref_and_mut(sig); - let crate_ = generate_crate_access(); - - get_function_argument_names_and_types_without_ref(sig) - .zip(mut_access.map(|v| v.and_then(|m| m.1))) - .map(move |((name, ty), mut_access)| { - let ffi_value_var_name = generate_ffi_value_var_name(&name)?; - - Ok(quote! { - let #mut_access #name = <#ty as #crate_::host::FromFFIValue>::from_ffi_value( - __function_context__, - #ffi_value_var_name, - )?; - }) - }) -} - -/// Generate the code to call the host function and the ident that stores the result. -fn generate_host_function_call(sig: &Signature, version: u32, is_wasm_only: bool) -> TokenStream { - let host_function_name = create_function_ident_with_version(&sig.ident, version); - let result_var_name = generate_host_function_result_var_name(&sig.ident); - let ref_and_mut = - get_function_argument_types_ref_and_mut(sig).map(|ram| ram.map(|(vr, vm)| quote!(#vr #vm))); - let names = get_function_argument_names(sig); - - let var_access = names - .zip(ref_and_mut) - .map(|(n, ref_and_mut)| quote!( #ref_and_mut #n )) - // If this is a wasm only interface, we add the function context as last parameter. - .chain( - iter::from_fn(|| if is_wasm_only { Some(quote!(__function_context__)) } else { None }) - .take(1), - ); - - quote! { - let #result_var_name = #host_function_name ( #( #var_access ),* ); - } -} - -/// Generate the variable name that stores the result of the host function. -fn generate_host_function_result_var_name(name: &Ident) -> Ident { - Ident::new(&format!("{}_result", name), Span::call_site()) -} - /// Generate the variable name that stores the FFI value. fn generate_ffi_value_var_name(pat: &Pat) -> Result { match pat { @@ -354,49 +439,3 @@ fn generate_ffi_value_var_name(pat: &Pat) -> Result { _ => Err(Error::new(pat.span(), "Not supported as variable name!")), } } - -/// Generate code that copies data from the host back to preallocated wasm memory. -/// -/// Any argument that is given as `&mut` is interpreted as preallocated memory and it is expected -/// that the type implements `IntoPreAllocatedFFIValue`. -fn generate_into_preallocated_ffi_value(sig: &Signature) -> Result { - let crate_ = generate_crate_access(); - let ref_and_mut = get_function_argument_types_ref_and_mut(sig) - .map(|ram| ram.and_then(|(vr, vm)| vm.map(|v| (vr, v)))); - let names_and_types = get_function_argument_names_and_types_without_ref(sig); - - ref_and_mut - .zip(names_and_types) - .filter_map(|(ram, (name, ty))| ram.map(|_| (name, ty))) - .map(|(name, ty)| { - let ffi_var_name = generate_ffi_value_var_name(&name)?; - - Ok(quote! { - <#ty as #crate_::host::IntoPreallocatedFFIValue>::into_preallocated_ffi_value( - #name, - __function_context__, - #ffi_var_name, - )?; - }) - }) - .collect() -} - -/// Generate the code that converts the return value into the appropriate wasm value. -fn generate_return_value_into_wasm_value(sig: &Signature) -> TokenStream { - let crate_ = generate_crate_access(); - - match &sig.output { - ReturnType::Default => quote!(Ok(None)), - ReturnType::Type(_, ty) => { - let result_var_name = generate_host_function_result_var_name(&sig.ident); - - quote! { - <#ty as #crate_::host::IntoFFIValue>::into_ffi_value( - #result_var_name, - __function_context__, - ).map(#crate_::sp_wasm_interface::IntoValue::into_value).map(Some) - } - }, - } -} diff --git a/primitives/runtime-interface/proc-macro/src/runtime_interface/mod.rs b/primitives/runtime-interface/proc-macro/src/runtime_interface/mod.rs index 78feda663850c..d14c1f67ecff5 100644 --- a/primitives/runtime-interface/proc-macro/src/runtime_interface/mod.rs +++ b/primitives/runtime-interface/proc-macro/src/runtime_interface/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs b/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs index c62e3ba87ccd3..0ae0f5260286c 100644 --- a/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs +++ b/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -153,7 +153,7 @@ fn impl_trait_for_externalities(trait_def: &ItemTrait, is_wasm_only: bool) -> Re let crate_ = generate_crate_access(); let interface = get_runtime_interface(trait_def)?; let methods = interface.all_versions().map(|(version, method)| { - let mut cloned = method.clone(); + let mut cloned = (*method).clone(); cloned.attrs.retain(|a| !a.path.is_ident("version")); cloned.sig.ident = create_function_ident_with_version(&cloned.sig.ident, version); cloned diff --git a/primitives/runtime-interface/proc-macro/src/utils.rs b/primitives/runtime-interface/proc-macro/src/utils.rs index 42ce09c573932..386eef153f45c 100644 --- a/primitives/runtime-interface/proc-macro/src/utils.rs +++ b/primitives/runtime-interface/proc-macro/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,8 @@ use proc_macro2::{Span, TokenStream}; use syn::{ - parse_quote, spanned::Spanned, token, Attribute, Error, FnArg, Ident, ItemTrait, Lit, Meta, - NestedMeta, Pat, PatType, Result, Signature, TraitItem, TraitItemMethod, Type, + parse::Parse, parse_quote, spanned::Spanned, token, Error, FnArg, Ident, ItemTrait, LitInt, + Pat, PatType, Result, Signature, TraitItem, TraitItemMethod, Type, }; use proc_macro_crate::{crate_name, FoundCrate}; @@ -35,49 +35,132 @@ use quote::quote; use inflector::Inflector; -/// Runtime interface function with all associated versions of this function. -pub struct RuntimeInterfaceFunction<'a> { - latest_version: u32, - versions: BTreeMap, +mod attributes { + syn::custom_keyword!(register_only); } -impl<'a> RuntimeInterfaceFunction<'a> { - fn new(version: u32, trait_item: &'a TraitItemMethod) -> Self { - Self { - latest_version: version, - versions: { - let mut res = BTreeMap::new(); - res.insert(version, trait_item); - res - }, +/// A concrete, specific version of a runtime interface function. +pub struct RuntimeInterfaceFunction { + item: TraitItemMethod, + should_trap_on_return: bool, +} + +impl std::ops::Deref for RuntimeInterfaceFunction { + type Target = TraitItemMethod; + fn deref(&self) -> &Self::Target { + &self.item + } +} + +impl RuntimeInterfaceFunction { + fn new(item: &TraitItemMethod) -> Result { + let mut item = item.clone(); + let mut should_trap_on_return = false; + item.attrs.retain(|attr| { + if attr.path.is_ident("trap_on_return") { + should_trap_on_return = true; + false + } else { + true + } + }); + + if should_trap_on_return && !matches!(item.sig.output, syn::ReturnType::Default) { + return Err(Error::new( + item.sig.ident.span(), + "Methods marked as #[trap_on_return] cannot return anything", + )) } + + Ok(Self { item, should_trap_on_return }) } - pub fn latest_version(&self) -> (u32, &TraitItemMethod) { - ( - self.latest_version, - self.versions.get(&self.latest_version).expect( - "If latest_version has a value, the key with this value is in the versions; qed", + pub fn should_trap_on_return(&self) -> bool { + self.should_trap_on_return + } +} + +/// Runtime interface function with all associated versions of this function. +struct RuntimeInterfaceFunctionSet { + latest_version_to_call: Option, + versions: BTreeMap, +} + +impl RuntimeInterfaceFunctionSet { + fn new(version: VersionAttribute, trait_item: &TraitItemMethod) -> Result { + Ok(Self { + latest_version_to_call: version.is_callable().then(|| version.version), + versions: BTreeMap::from([( + version.version, + RuntimeInterfaceFunction::new(trait_item)?, + )]), + }) + } + + /// Returns the latest version of this runtime interface function plus the actual function + /// implementation. + /// + /// This isn't required to be the latest version, because a runtime interface function can be + /// annotated with `register_only` to ensure that the host exposes the host function but it + /// isn't used when compiling the runtime. + pub fn latest_version_to_call(&self) -> Option<(u32, &RuntimeInterfaceFunction)> { + self.latest_version_to_call.map(|v| { + ( + v, + self.versions.get(&v).expect( + "If latest_version_to_call has a value, the key with this value is in the versions; qed", ), ) + }) + } + + /// Add a different version of the function. + fn add_version( + &mut self, + version: VersionAttribute, + trait_item: &TraitItemMethod, + ) -> Result<()> { + if let Some(existing_item) = self.versions.get(&version.version) { + let mut err = Error::new(trait_item.span(), "Duplicated version attribute"); + err.combine(Error::new( + existing_item.span(), + "Previous version with the same number defined here", + )); + + return Err(err) + } + + self.versions + .insert(version.version, RuntimeInterfaceFunction::new(trait_item)?); + if self.latest_version_to_call.map_or(true, |v| v < version.version) && + version.is_callable() + { + self.latest_version_to_call = Some(version.version); + } + + Ok(()) } } /// All functions of a runtime interface grouped by the function names. -pub struct RuntimeInterface<'a> { - items: BTreeMap>, +pub struct RuntimeInterface { + items: BTreeMap, } -impl<'a> RuntimeInterface<'a> { - pub fn latest_versions(&self) -> impl Iterator { - self.items.iter().map(|(_, item)| item.latest_version()) +impl RuntimeInterface { + /// Returns an iterator over all runtime interface function + /// [`latest_version_to_call`](RuntimeInterfaceFunctionSet::latest_version). + pub fn latest_versions_to_call( + &self, + ) -> impl Iterator { + self.items.iter().filter_map(|(_, item)| item.latest_version_to_call()) } - pub fn all_versions(&self) -> impl Iterator { + pub fn all_versions(&self) -> impl Iterator { self.items .iter() .flat_map(|(_, item)| item.versions.iter()) - .map(|(v, i)| (*v, *i)) + .map(|(v, i)| (*v, i)) } } @@ -127,7 +210,7 @@ pub fn create_function_ident_with_version(name: &Ident, version: u32) -> Ident { } /// Returns the function arguments of the given `Signature`, minus any `self` arguments. -pub fn get_function_arguments<'a>(sig: &'a Signature) -> impl Iterator + 'a { +pub fn get_function_arguments(sig: &Signature) -> impl Iterator + '_ { sig.inputs .iter() .filter_map(|a| match a { @@ -149,20 +232,20 @@ pub fn get_function_arguments<'a>(sig: &'a Signature) -> impl Iterator(sig: &'a Signature) -> impl Iterator> + 'a { +pub fn get_function_argument_names(sig: &Signature) -> impl Iterator> + '_ { get_function_arguments(sig).map(|pt| pt.pat) } /// Returns the function argument types of the given `Signature`, minus any `Self` type. -pub fn get_function_argument_types<'a>(sig: &'a Signature) -> impl Iterator> + 'a { +pub fn get_function_argument_types(sig: &Signature) -> impl Iterator> + '_ { get_function_arguments(sig).map(|pt| pt.ty) } /// Returns the function argument types, minus any `Self` type. If any of the arguments /// is a reference, the underlying type without the ref is returned. -pub fn get_function_argument_types_without_ref<'a>( - sig: &'a Signature, -) -> impl Iterator> + 'a { +pub fn get_function_argument_types_without_ref( + sig: &Signature, +) -> impl Iterator> + '_ { get_function_arguments(sig).map(|pt| pt.ty).map(|ty| match *ty { Type::Reference(type_ref) => type_ref.elem, _ => ty, @@ -171,9 +254,9 @@ pub fn get_function_argument_types_without_ref<'a>( /// Returns the function argument names and types, minus any `self`. If any of the arguments /// is a reference, the underlying type without the ref is returned. -pub fn get_function_argument_names_and_types_without_ref<'a>( - sig: &'a Signature, -) -> impl Iterator, Box)> + 'a { +pub fn get_function_argument_names_and_types_without_ref( + sig: &Signature, +) -> impl Iterator, Box)> + '_ { get_function_arguments(sig).map(|pt| match *pt.ty { Type::Reference(type_ref) => (pt.pat, type_ref.elem), _ => (pt.pat, pt.ty), @@ -182,9 +265,9 @@ pub fn get_function_argument_names_and_types_without_ref<'a>( /// Returns the `&`/`&mut` for all function argument types, minus the `self` arg. If a function /// argument is not a reference, `None` is returned. -pub fn get_function_argument_types_ref_and_mut<'a>( - sig: &'a Signature, -) -> impl Iterator)>> + 'a { +pub fn get_function_argument_types_ref_and_mut( + sig: &Signature, +) -> impl Iterator)>> + '_ { get_function_arguments(sig).map(|pt| pt.ty).map(|ty| match *ty { Type::Reference(type_ref) => Some((type_ref.and_token, type_ref.mutability)), _ => None, @@ -192,74 +275,83 @@ pub fn get_function_argument_types_ref_and_mut<'a>( } /// Returns an iterator over all trait methods for the given trait definition. -fn get_trait_methods<'a>(trait_def: &'a ItemTrait) -> impl Iterator { +fn get_trait_methods(trait_def: &ItemTrait) -> impl Iterator { trait_def.items.iter().filter_map(|i| match i { TraitItem::Method(ref method) => Some(method), _ => None, }) } -/// Parse version attribute. +/// The version attribute that can be found above a runtime interface function. /// -/// Returns error if it is in incorrent format. Correct format is only `#[version(X)]`. -fn parse_version_attribute(version: &Attribute) -> Result { - let meta = version.parse_meta()?; - - let err = Err(Error::new( - meta.span(), - "Unexpected `version` attribute. The supported format is `#[version(1)]`", - )); - - match meta { - Meta::List(list) => - if list.nested.len() != 1 { - err - } else if let Some(NestedMeta::Lit(Lit::Int(i))) = list.nested.first() { - i.base10_parse() - } else { - err - }, - _ => err, +/// Supports the following formats: +/// - `#[version(1)]` +/// - `#[version(1, register_only)]` +/// +/// While this struct is only for parsing the inner parts inside the `()`. +struct VersionAttribute { + version: u32, + register_only: Option, +} + +impl VersionAttribute { + /// Is this function version callable? + fn is_callable(&self) -> bool { + self.register_only.is_none() } } -/// Return item version (`#[version(X)]`) attribute, if present. -fn get_item_version(item: &TraitItemMethod) -> Result> { +impl Default for VersionAttribute { + fn default() -> Self { + Self { version: 1, register_only: None } + } +} + +impl Parse for VersionAttribute { + fn parse(input: syn::parse::ParseStream) -> Result { + let version: LitInt = input.parse()?; + let register_only = if input.peek(token::Comma) { + let _ = input.parse::(); + Some(input.parse()?) + } else { + if !input.is_empty() { + return Err(Error::new(input.span(), "Unexpected token, expected `,`.")) + } + + None + }; + + Ok(Self { version: version.base10_parse()?, register_only }) + } +} + +/// Return [`VersionAttribute`], if present. +fn get_item_version(item: &TraitItemMethod) -> Result> { item.attrs .iter() .find(|attr| attr.path.is_ident("version")) - .map(|attr| parse_version_attribute(attr)) + .map(|attr| attr.parse_args()) .transpose() } /// Returns all runtime interface members, with versions. -pub fn get_runtime_interface<'a>(trait_def: &'a ItemTrait) -> Result> { - let mut functions: BTreeMap> = BTreeMap::new(); +pub fn get_runtime_interface(trait_def: &ItemTrait) -> Result { + let mut functions: BTreeMap = BTreeMap::new(); for item in get_trait_methods(trait_def) { let name = item.sig.ident.clone(); - let version = get_item_version(item)?.unwrap_or(1); + let version = get_item_version(item)?.unwrap_or_default(); + + if version.version < 1 { + return Err(Error::new(item.span(), "Version needs to be at least `1`.")) + } match functions.entry(name.clone()) { Entry::Vacant(entry) => { - entry.insert(RuntimeInterfaceFunction::new(version, item)); + entry.insert(RuntimeInterfaceFunctionSet::new(version, item)?); }, Entry::Occupied(mut entry) => { - if let Some(existing_item) = entry.get().versions.get(&version) { - let mut err = Error::new(item.span(), "Duplicated version attribute"); - err.combine(Error::new( - existing_item.span(), - "Previous version with the same number defined here", - )); - - return Err(err) - } - - let interface_item = entry.get_mut(); - if interface_item.latest_version < version { - interface_item.latest_version = version; - } - interface_item.versions.insert(version, item); + entry.get_mut().add_version(version, item)?; }, } } diff --git a/primitives/runtime-interface/src/host.rs b/primitives/runtime-interface/src/host.rs index a6ea96af90043..36492430266a7 100644 --- a/primitives/runtime-interface/src/host.rs +++ b/primitives/runtime-interface/src/host.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/src/impls.rs b/primitives/runtime-interface/src/impls.rs index 40f8e90479f95..e801931c306cf 100644 --- a/primitives/runtime-interface/src/impls.rs +++ b/primitives/runtime-interface/src/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -95,36 +95,36 @@ macro_rules! impl_traits_for_primitives { } impl_traits_for_primitives! { - u8, u8, - u16, u16, + u8, u32, + u16, u32, u32, u32, u64, u64, - i8, i8, - i16, i16, + i8, i32, + i16, i32, i32, i32, i64, i64, } -/// `bool` is passed as `u8`. +/// `bool` is passed as `u32`. /// /// - `1`: true /// - `0`: false impl RIType for bool { - type FFIType = u8; + type FFIType = u32; } #[cfg(not(feature = "std"))] impl IntoFFIValue for bool { type Owned = (); - fn into_ffi_value(&self) -> WrappedFFIValue { + fn into_ffi_value(&self) -> WrappedFFIValue { if *self { 1 } else { 0 }.into() } } #[cfg(not(feature = "std"))] impl FromFFIValue for bool { - fn from_ffi_value(arg: u8) -> bool { + fn from_ffi_value(arg: u32) -> bool { arg == 1 } } @@ -133,14 +133,14 @@ impl FromFFIValue for bool { impl FromFFIValue for bool { type SelfInstance = bool; - fn from_ffi_value(_: &mut dyn FunctionContext, arg: u8) -> Result { + fn from_ffi_value(_: &mut dyn FunctionContext, arg: u32) -> Result { Ok(arg == 1) } } #[cfg(feature = "std")] impl IntoFFIValue for bool { - fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result { + fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result { Ok(if self { 1 } else { 0 }) } } @@ -318,9 +318,8 @@ macro_rules! impl_traits_for_arrays { type SelfInstance = [u8; $n]; fn from_ffi_value(context: &mut dyn FunctionContext, arg: u32) -> Result<[u8; $n]> { - let data = context.read_memory(Pointer::new(arg), $n)?; let mut res = [0u8; $n]; - res.copy_from_slice(&data); + context.read_memory_into(Pointer::new(arg), &mut res)?; Ok(res) } } @@ -514,10 +513,8 @@ macro_rules! for_u128_i128 { type SelfInstance = $type; fn from_ffi_value(context: &mut dyn FunctionContext, arg: u32) -> Result<$type> { - let data = - context.read_memory(Pointer::new(arg), mem::size_of::<$type>() as u32)?; let mut res = [0u8; mem::size_of::<$type>()]; - res.copy_from_slice(&data); + context.read_memory_into(Pointer::new(arg), &mut res)?; Ok(<$type>::from_le_bytes(res)) } } @@ -547,3 +544,11 @@ impl PassBy for sp_wasm_interface::Value { impl PassBy for sp_storage::TrackedStorageKey { type PassBy = Codec; } + +impl PassBy for sp_storage::StateVersion { + type PassBy = Enum; +} + +impl PassBy for sp_externalities::MultiRemovalResults { + type PassBy = Codec; +} diff --git a/primitives/runtime-interface/src/lib.rs b/primitives/runtime-interface/src/lib.rs index 27c4422ed9006..975d7158b8dcd 100644 --- a/primitives/runtime-interface/src/lib.rs +++ b/primitives/runtime-interface/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Custom inner attributes are unstable, so we need to faky disable the attribute. +// rustfmt still honors the attribute to not format the rustdocs below. +#![cfg_attr(feature = "never", rustfmt::skip)] //! Substrate runtime interface //! //! This crate provides types, traits and macros around runtime interfaces. A runtime interface is @@ -80,28 +83,27 @@ //! //! | Type | FFI type | Conversion | //! |----|----|----| -//! | `u8` | `u8` | `Identity` | -//! | `u16` | `u16` | `Identity` | +//! | `u8` | `u32` | zero-extended to 32-bits | +//! | `u16` | `u32` | zero-extended to 32-bits | //! | `u32` | `u32` | `Identity` | //! | `u64` | `u64` | `Identity` | //! | `i128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) | -//! | `i8` | `i8` | `Identity` | -//! | `i16` | `i16` | `Identity` | +//! | `i8` | `i32` | sign-extended to 32-bits | +//! | `i16` | `i32` | sign-extended to 32-bits | //! | `i32` | `i32` | `Identity` | //! | `i64` | `i64` | `Identity` | //! | `u128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) | -//! | `bool` | `u8` | `if v { 1 } else { 0 }` | +//! | `bool` | `u32` | `if v { 1 } else { 0 }` | //! | `&str` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | //! | `&[u8]` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | //! | `Vec` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | -//! | `Vec where T: Encode` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 -//! | e.as_ptr() 32bit | | `&[T] where T: Encode` | `u64` | `let e = -//! v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | | `[u8; N]` | -//! `u32` | `v.as_ptr()` | | `*const T` | `u32` | `Identity` | -//! | `Option` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() -//! 32bit | | [`T where T: PassBy`](./pass_by#Inner) | Depends on inner | -//! Depends on inner | | [`T where T: PassBy`](./pass_by#Codec)|`u64`|v.len() -//! 32bit << 32 |v.as_ptr() 32bit| +//! | `Vec where T: Encode` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +//! | `&[T] where T: Encode` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +//! | `[u8; N]` | `u32` | `v.as_ptr()` | +//! | `*const T` | `u32` | `Identity` | +//! | `Option` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +//! | [`T where T: PassBy`](./pass_by#Inner) | Depends on inner | Depends on inner | +//! | [`T where T: PassBy`](./pass_by#Codec)|`u64`|v.len() 32bit << 32 |v.as_ptr() 32bit| //! //! `Identity` means that the value is converted directly into the corresponding FFI type. @@ -153,6 +155,22 @@ pub use sp_std; /// [17].to_vec() /// } /// +/// /// Call function, different version and only being registered. +/// /// +/// /// This `register_only` version is only being registered, aka exposed to the runtime, +/// /// but the runtime will still use the version 2 of this function. This is useful for when +/// /// new host functions should be introduced. Adding new host functions requires that all +/// /// nodes have the host functions available, because otherwise they fail at instantiation +/// /// of the runtime. With `register_only` the function will not be used when compiling the +/// /// runtime, but it will already be there for a future version of the runtime that will +/// /// switch to using these host function. +/// #[version(3, register_only)] +/// fn call(data: &[u8]) -> Vec { +/// // Here you could call some rather complex code that only compiles on native or +/// // is way faster in native than executing it in wasm. +/// [18].to_vec() +/// } +/// /// /// A function can take a `&self` or `&mut self` argument to get access to the /// /// `Externalities`. (The generated method does not require /// /// this argument, so the function can be called just with the `optional` argument) @@ -177,12 +195,14 @@ pub use sp_std; /// trait Interface { /// fn call_version_1(data: &[u8]) -> Vec; /// fn call_version_2(data: &[u8]) -> Vec; +/// fn call_version_3(data: &[u8]) -> Vec; /// fn set_or_clear_version_1(&mut self, optional: Option>); /// } /// /// impl Interface for &mut dyn sp_externalities::Externalities { /// fn call_version_1(data: &[u8]) -> Vec { Vec::new() } /// fn call_version_2(data: &[u8]) -> Vec { [17].to_vec() } +/// fn call_version_3(data: &[u8]) -> Vec { [18].to_vec() } /// fn set_or_clear_version_1(&mut self, optional: Option>) { /// match optional { /// Some(value) => self.set_storage([1, 2, 3, 4].to_vec(), value), @@ -204,6 +224,10 @@ pub use sp_std; /// <&mut dyn sp_externalities::Externalities as Interface>::call_version_2(data) /// } /// +/// fn call_version_3(data: &[u8]) -> Vec { +/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_3(data) +/// } +/// /// pub fn set_or_clear(optional: Option>) { /// set_or_clear_version_1(optional) /// } @@ -285,11 +309,11 @@ pub use sp_std; /// This instructs the macro to make two significant changes to the generated code: /// /// 1. The generated functions are not callable from the native side. -/// 2. The trait as shown above is not implemented for `Externalities` and is instead -/// implemented for `FunctionExecutor` (from `sp-wasm-interface`). +/// 2. The trait as shown above is not implemented for [`Externalities`] and is instead +/// implemented for `FunctionContext` (from `sp-wasm-interface`). /// /// # Disable tracing -/// By addding `no_tracing` to the list of options you can prevent the wasm-side interface from +/// By adding `no_tracing` to the list of options you can prevent the wasm-side interface from /// generating the default `sp-tracing`-calls. Note that this is rarely needed but only meant /// for the case when that would create a circular dependency. You usually _do not_ want to add /// this flag, as tracing doesn't cost you anything by default anyways (it is added as a no-op) @@ -325,7 +349,9 @@ pub use util::{pack_ptr_and_len, unpack_ptr_and_len}; pub trait RIType { /// The ffi type that is used to represent `Self`. #[cfg(feature = "std")] - type FFIType: sp_wasm_interface::IntoValue + sp_wasm_interface::TryFromValue; + type FFIType: sp_wasm_interface::IntoValue + + sp_wasm_interface::TryFromValue + + sp_wasm_interface::WasmTy; #[cfg(not(feature = "std"))] type FFIType; } diff --git a/primitives/runtime-interface/src/pass_by.rs b/primitives/runtime-interface/src/pass_by.rs index 7324e9363804b..ac6f0def9cad0 100644 --- a/primitives/runtime-interface/src/pass_by.rs +++ b/primitives/runtime-interface/src/pass_by.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,7 @@ use crate::wasm::*; #[cfg(feature = "std")] use sp_wasm_interface::{FunctionContext, Pointer, Result}; -use sp_std::{convert::TryFrom, marker::PhantomData}; +use sp_std::marker::PhantomData; #[cfg(not(feature = "std"))] use sp_std::vec::Vec; @@ -248,12 +248,12 @@ impl PassByImpl for Codec { let len = len as usize; let encoded = if len == 0 { - Vec::new() + bytes::Bytes::new() } else { - unsafe { Vec::from_raw_parts(ptr as *mut u8, len, len) } + bytes::Bytes::from(unsafe { Vec::from_raw_parts(ptr as *mut u8, len, len) }) }; - T::decode(&mut &encoded[..]).expect("Host to wasm values are encoded correctly; qed") + codec::decode_from_bytes(encoded).expect("Host to wasm values are encoded correctly; qed") } } @@ -382,7 +382,7 @@ impl, I: RIType> RIType for Inner { /// } /// } /// -/// impl std::convert::TryFrom for Test { +/// impl TryFrom for Test { /// type Error = (); /// /// fn try_from(val: u8) -> Result { @@ -403,11 +403,11 @@ pub struct Enum + TryFrom>(PhantomData); #[cfg(feature = "std")] impl + TryFrom> PassByImpl for Enum { fn into_ffi_value(instance: T, _: &mut dyn FunctionContext) -> Result { - Ok(instance.into()) + Ok(instance.into() as u32) } fn from_ffi_value(_: &mut dyn FunctionContext, arg: Self::FFIType) -> Result { - T::try_from(arg).map_err(|_| format!("Invalid enum discriminant: {}", arg)) + T::try_from(arg as u8).map_err(|_| format!("Invalid enum discriminant: {}", arg)) } } @@ -417,17 +417,17 @@ impl + TryFrom> PassByImpl for Enum { fn into_ffi_value(instance: &T) -> WrappedFFIValue { let value: u8 = (*instance).into(); - value.into() + (value as u32).into() } fn from_ffi_value(arg: Self::FFIType) -> T { - T::try_from(arg).expect("Host to wasm provides a valid enum discriminant; qed") + T::try_from(arg as u8).expect("Host to wasm provides a valid enum discriminant; qed") } } -/// The type is passed as `u8`. +/// The type is passed as `u32`. /// /// The value is corresponds to the discriminant of the variant. impl + TryFrom> RIType for Enum { - type FFIType = u8; + type FFIType = u32; } diff --git a/primitives/runtime-interface/src/util.rs b/primitives/runtime-interface/src/util.rs index 31045c83c9dcd..fe8afe99508a8 100644 --- a/primitives/runtime-interface/src/util.rs +++ b/primitives/runtime-interface/src/util.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/src/wasm.rs b/primitives/runtime-interface/src/wasm.rs index 28613f81a68b2..4ed27687a10a5 100644 --- a/primitives/runtime-interface/src/wasm.rs +++ b/primitives/runtime-interface/src/wasm.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml index 3ae5d78b0ef95..32d78ec2cee41 100644 --- a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml +++ b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml @@ -2,10 +2,10 @@ name = "sp-runtime-interface-test-wasm-deprecated" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" build = "build.rs" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" publish = false @@ -13,14 +13,14 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-runtime-interface = { version = "4.0.0-dev", default-features = false, path = "../" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../io" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../core" } +sp-core = { version = "7.0.0", default-features = false, path = "../../core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../io" } +sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../" } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } [features] default = [ "std" ] -std = [ "sp-runtime-interface/std", "sp-std/std", "sp-core/std", "sp-io/std" ] +std = [ "sp-core/std", "sp-io/std", "sp-runtime-interface/std", "sp-std/std" ] diff --git a/primitives/runtime-interface/test-wasm-deprecated/build.rs b/primitives/runtime-interface/test-wasm-deprecated/build.rs index a1c4b2d892cfe..b773ed9cf6fb7 100644 --- a/primitives/runtime-interface/test-wasm-deprecated/build.rs +++ b/primitives/runtime-interface/test-wasm-deprecated/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs b/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs index 4a59e4fe8aa5f..512d52214c12b 100644 --- a/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs +++ b/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/test-wasm/Cargo.toml b/primitives/runtime-interface/test-wasm/Cargo.toml index 7c7d3e10b2d0c..5fb4850af8d9b 100644 --- a/primitives/runtime-interface/test-wasm/Cargo.toml +++ b/primitives/runtime-interface/test-wasm/Cargo.toml @@ -2,10 +2,10 @@ name = "sp-runtime-interface-test-wasm" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" build = "build.rs" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" publish = false @@ -13,14 +13,15 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-runtime-interface = { version = "4.0.0-dev", default-features = false, path = "../" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../io" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../core" } +bytes = { version = "1.1.0", default-features = false } +sp-core = { version = "7.0.0", default-features = false, path = "../../core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../io" } +sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../" } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } [features] default = [ "std" ] -std = [ "sp-runtime-interface/std", "sp-std/std", "sp-core/std", "sp-io/std" ] +std = [ "sp-core/std", "sp-io/std", "sp-runtime-interface/std", "sp-std/std" ] diff --git a/primitives/runtime-interface/test-wasm/build.rs b/primitives/runtime-interface/test-wasm/build.rs index a1c4b2d892cfe..b773ed9cf6fb7 100644 --- a/primitives/runtime-interface/test-wasm/build.rs +++ b/primitives/runtime-interface/test-wasm/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/test-wasm/src/lib.rs b/primitives/runtime-interface/test-wasm/src/lib.rs index 72acdd4ff8d6e..3720735ac773b 100644 --- a/primitives/runtime-interface/test-wasm/src/lib.rs +++ b/primitives/runtime-interface/test-wasm/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use sp_runtime_interface::runtime_interface; #[cfg(not(feature = "std"))] -use sp_std::{convert::TryFrom, mem, prelude::*}; +use sp_std::{mem, prelude::*}; use sp_core::{sr25519::Public, wasm_export_functions}; @@ -54,12 +54,24 @@ pub trait TestApi { /// # Note /// /// We return a `Vec` because this will use the code path that uses SCALE - /// to pass the data between native/wasm. (Vec is passed without encoding the + /// to pass the data between native/wasm. (`Vec` is passed without encoding the /// data) fn return_16kb() -> Vec { vec![0; 4 * 1024] } + fn return_option_vec() -> Option> { + let mut vec = Vec::new(); + vec.resize(16 * 1024, 0xAA); + Some(vec) + } + + fn return_option_bytes() -> Option { + let mut vec = Vec::new(); + vec.resize(16 * 1024, 0xAA); + Some(vec.into()) + } + /// Set the storage at key with value. fn set_storage(&mut self, key: &[u8], data: &[u8]) { self.place_storage(key.to_vec(), Some(data.to_vec())); @@ -123,6 +135,15 @@ pub trait TestApi { data == 42 } + fn test_versionning_register_only(&self, data: u32) -> bool { + data == 80 + } + + #[version(2, register_only)] + fn test_versionning_register_only(&self, data: u32) -> bool { + data == 42 + } + /// Returns the input values as tuple. fn return_input_as_tuple( a: Vec, @@ -271,6 +292,13 @@ wasm_export_functions! { assert!(!test_api::test_versionning(102)); } + fn test_versionning_register_only_works() { + // Ensure that we will import the version of the runtime interface function that + // isn't tagged with `register_only`. + assert!(!test_api::test_versionning_register_only(42)); + assert!(test_api::test_versionning_register_only(80)); + } + fn test_return_input_as_tuple() { let a = vec![1, 3, 4, 5]; let b = 10000; @@ -284,4 +312,12 @@ wasm_export_functions! { assert_eq!(c, res.2); assert_eq!(d, res.3); } + + fn test_return_option_vec() { + test_api::return_option_vec(); + } + + fn test_return_option_bytes() { + test_api::return_option_bytes(); + } } diff --git a/primitives/runtime-interface/test/Cargo.toml b/primitives/runtime-interface/test/Cargo.toml index 377729521fcfe..4e4522fd93dd2 100644 --- a/primitives/runtime-interface/test/Cargo.toml +++ b/primitives/runtime-interface/test/Cargo.toml @@ -2,23 +2,23 @@ name = "sp-runtime-interface-test" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" publish = false -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-runtime-interface = { version = "4.0.0-dev", path = "../" } +tracing = "0.1.29" +tracing-core = "0.1.28" sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sc-executor-common = { version = "0.10.0-dev", path = "../../../client/executor/common" } +sp-io = { version = "7.0.0", path = "../../io" } +sp-runtime = { version = "7.0.0", path = "../../runtime" } +sp-runtime-interface = { version = "7.0.0", path = "../" } sp-runtime-interface-test-wasm = { version = "2.0.0", path = "../test-wasm" } sp-runtime-interface-test-wasm-deprecated = { version = "2.0.0", path = "../test-wasm-deprecated" } -sp-state-machine = { version = "0.10.0-dev", path = "../../state-machine" } -sp-runtime = { version = "4.0.0-dev", path = "../../runtime" } -sp-io = { version = "4.0.0-dev", path = "../../io" } -tracing = "0.1.25" -tracing-core = "0.1.17" +sp-state-machine = { version = "0.13.0", path = "../../state-machine" } diff --git a/primitives/runtime-interface/test/src/lib.rs b/primitives/runtime-interface/test/src/lib.rs index 82c50fffeb8d7..d1db3e064295e 100644 --- a/primitives/runtime-interface/test/src/lib.rs +++ b/primitives/runtime-interface/test/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,61 +23,57 @@ use sp_runtime_interface::*; use sp_runtime_interface_test_wasm::{test_api::HostFunctions, wasm_binary_unwrap}; use sp_runtime_interface_test_wasm_deprecated::wasm_binary_unwrap as wasm_binary_deprecated_unwrap; -use sc_executor_common::runtime_blob::RuntimeBlob; -use sp_wasm_interface::HostFunctions as HostFunctionsT; +use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::AllocationStats}; +use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions as HostFunctionsT}; use std::{ collections::HashSet, sync::{Arc, Mutex}, }; -type TestExternalities = sp_state_machine::TestExternalities; +type TestExternalities = sp_state_machine::TestExternalities; fn call_wasm_method_with_result( binary: &[u8], method: &str, -) -> Result { +) -> (Result, Option) { let mut ext = TestExternalities::default(); let mut ext_ext = ext.ext(); - let mut host_functions = HF::host_functions(); - host_functions.extend(sp_io::SubstrateHostFunctions::host_functions()); - - let executor = sc_executor::WasmExecutor::new( - sc_executor::WasmExecutionMethod::Interpreted, - Some(8), - host_functions, - 8, - None, + + let executor = sc_executor::WasmExecutor::< + ExtendedHostFunctions, + >::new(sc_executor::WasmExecutionMethod::Interpreted, Some(8), 8, None, 2); + + let (result, allocation_stats) = executor.uncached_call_with_allocation_stats( + RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"), + &mut ext_ext, + false, + method, + &[], ); - executor - .uncached_call( - RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"), - &mut ext_ext, - false, - method, - &[], - ) - .map_err(|e| format!("Failed to execute `{}`: {}", method, e))?; - Ok(ext) + let result = result + .map_err(|e| format!("Failed to execute `{}`: {}", method, e)) + .map(|_| ext); + (result, allocation_stats) } fn call_wasm_method(binary: &[u8], method: &str) -> TestExternalities { - call_wasm_method_with_result::(binary, method).unwrap() + call_wasm_method_with_result::(binary, method).0.unwrap() } #[test] fn test_return_data() { - call_wasm_method::(&wasm_binary_unwrap()[..], "test_return_data"); + call_wasm_method::(wasm_binary_unwrap(), "test_return_data"); } #[test] fn test_return_option_data() { - call_wasm_method::(&wasm_binary_unwrap()[..], "test_return_option_data"); + call_wasm_method::(wasm_binary_unwrap(), "test_return_option_data"); } #[test] fn test_set_storage() { - let mut ext = call_wasm_method::(&wasm_binary_unwrap()[..], "test_set_storage"); + let mut ext = call_wasm_method::(wasm_binary_unwrap(), "test_set_storage"); let expected = "world"; assert_eq!(expected.as_bytes(), &ext.ext().storage("hello".as_bytes()).unwrap()[..]); @@ -86,29 +82,30 @@ fn test_set_storage() { #[test] fn test_return_value_into_mutable_reference() { call_wasm_method::( - &wasm_binary_unwrap()[..], + wasm_binary_unwrap(), "test_return_value_into_mutable_reference", ); } #[test] fn test_get_and_return_array() { - call_wasm_method::(&wasm_binary_unwrap()[..], "test_get_and_return_array"); + call_wasm_method::(wasm_binary_unwrap(), "test_get_and_return_array"); } #[test] fn test_array_as_mutable_reference() { - call_wasm_method::(&wasm_binary_unwrap()[..], "test_array_as_mutable_reference"); + call_wasm_method::(wasm_binary_unwrap(), "test_array_as_mutable_reference"); } #[test] fn test_return_input_public_key() { - call_wasm_method::(&wasm_binary_unwrap()[..], "test_return_input_public_key"); + call_wasm_method::(wasm_binary_unwrap(), "test_return_input_public_key"); } #[test] fn host_function_not_found() { - let err = call_wasm_method_with_result::<()>(&wasm_binary_unwrap()[..], "test_return_data") + let err = call_wasm_method_with_result::<()>(wasm_binary_unwrap(), "test_return_data") + .0 .unwrap_err(); assert!(err.contains("Instantiation: Export ")); @@ -119,7 +116,7 @@ fn host_function_not_found() { #[should_panic(expected = "Invalid utf8 data provided")] fn test_invalid_utf8_data_should_return_an_error() { call_wasm_method::( - &wasm_binary_unwrap()[..], + wasm_binary_unwrap(), "test_invalid_utf8_data_should_return_an_error", ); } @@ -127,7 +124,7 @@ fn test_invalid_utf8_data_should_return_an_error() { #[test] fn test_overwrite_native_function_implementation() { call_wasm_method::( - &wasm_binary_unwrap()[..], + wasm_binary_unwrap(), "test_overwrite_native_function_implementation", ); } @@ -135,7 +132,7 @@ fn test_overwrite_native_function_implementation() { #[test] fn test_u128_i128_as_parameter_and_return_value() { call_wasm_method::( - &wasm_binary_unwrap()[..], + wasm_binary_unwrap(), "test_u128_i128_as_parameter_and_return_value", ); } @@ -143,7 +140,7 @@ fn test_u128_i128_as_parameter_and_return_value() { #[test] fn test_vec_return_value_memory_is_freed() { call_wasm_method::( - &wasm_binary_unwrap()[..], + wasm_binary_unwrap(), "test_vec_return_value_memory_is_freed", ); } @@ -151,7 +148,7 @@ fn test_vec_return_value_memory_is_freed() { #[test] fn test_encoded_return_value_memory_is_freed() { call_wasm_method::( - &wasm_binary_unwrap()[..], + wasm_binary_unwrap(), "test_encoded_return_value_memory_is_freed", ); } @@ -159,7 +156,7 @@ fn test_encoded_return_value_memory_is_freed() { #[test] fn test_array_return_value_memory_is_freed() { call_wasm_method::( - &wasm_binary_unwrap()[..], + wasm_binary_unwrap(), "test_array_return_value_memory_is_freed", ); } @@ -167,14 +164,16 @@ fn test_array_return_value_memory_is_freed() { #[test] fn test_versionining_with_new_host_works() { // We call to the new wasm binary with new host function. - call_wasm_method::(&wasm_binary_unwrap()[..], "test_versionning_works"); + call_wasm_method::(wasm_binary_unwrap(), "test_versionning_works"); // we call to the old wasm binary with a new host functions // old versions of host functions should be called and test should be ok! - call_wasm_method::( - &wasm_binary_deprecated_unwrap()[..], - "test_versionning_works", - ); + call_wasm_method::(wasm_binary_deprecated_unwrap(), "test_versionning_works"); +} + +#[test] +fn test_versionining_register_only() { + call_wasm_method::(wasm_binary_unwrap(), "test_versionning_register_only_works"); } #[test] @@ -229,7 +228,7 @@ fn test_tracing() { let _guard = tracing::subscriber::set_default(subscriber.clone()); // Call some method to generate a trace - call_wasm_method::(&wasm_binary_unwrap()[..], "test_return_data"); + call_wasm_method::(wasm_binary_unwrap(), "test_return_data"); let inner = subscriber.0.lock().unwrap(); assert!(inner.spans.contains("return_input_version_1")); @@ -237,5 +236,45 @@ fn test_tracing() { #[test] fn test_return_input_as_tuple() { - call_wasm_method::(&wasm_binary_unwrap()[..], "test_return_input_as_tuple"); + call_wasm_method::(wasm_binary_unwrap(), "test_return_input_as_tuple"); +} + +#[test] +fn test_returning_option_bytes_from_a_host_function_is_efficient() { + let (result, stats_vec) = call_wasm_method_with_result::( + wasm_binary_unwrap(), + "test_return_option_vec", + ); + result.unwrap(); + let (result, stats_bytes) = call_wasm_method_with_result::( + wasm_binary_unwrap(), + "test_return_option_bytes", + ); + result.unwrap(); + + let stats_vec = stats_vec.unwrap(); + let stats_bytes = stats_bytes.unwrap(); + + // The way we currently implement marshaling of `Option>` through + // the WASM FFI boundary from the host to the runtime requires that it is + // marshaled through SCALE. This is quite inefficient as it requires two + // memory allocations inside of the runtime: + // + // 1) the first allocation to copy the SCALE-encoded blob into the runtime; + // 2) and another allocation for the resulting `Vec` when decoding that blob. + // + // Both of these allocations are are as big as the `Vec` which is being + // passed to the runtime. This is especially bad when fetching big values + // from storage, as it can lead to an out-of-memory situation. + // + // Our `Option` marshaling is better; it still must go through SCALE, + // and it still requires two allocations, however since `Bytes` is zero-copy + // only the first allocation is `Vec`-sized, and the second allocation + // which creates the deserialized `Bytes` is tiny, and is only necessary because + // the underlying `Bytes` buffer from which we're deserializing gets automatically + // turned into an `Arc`. + // + // So this assertion tests that deserializing `Option` allocates less than + // deserializing `Option>`. + assert_eq!(stats_bytes.bytes_allocated_sum + 16 * 1024 + 8, stats_vec.bytes_allocated_sum); } diff --git a/primitives/runtime-interface/tests/ui.rs b/primitives/runtime-interface/tests/ui.rs index 5a6025f463af0..f3d6aa59a0336 100644 --- a/primitives/runtime-interface/tests/ui.rs +++ b/primitives/runtime-interface/tests/ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,11 @@ use std::env; #[rustversion::attr(not(stable), ignore)] #[test] fn ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/primitives/runtime-interface/tests/ui/pass_by_enum_with_struct.stderr b/primitives/runtime-interface/tests/ui/pass_by_enum_with_struct.stderr index c7ed1af3b1a03..44fb5a244e03d 100644 --- a/primitives/runtime-interface/tests/ui/pass_by_enum_with_struct.stderr +++ b/primitives/runtime-interface/tests/ui/pass_by_enum_with_struct.stderr @@ -4,4 +4,4 @@ error: `PassByEnum` only supports enums as input type. 3 | #[derive(PassByEnum)] | ^^^^^^^^^^ | - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `PassByEnum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/primitives/runtime-interface/tests/ui/pass_by_enum_with_value_variant.stderr b/primitives/runtime-interface/tests/ui/pass_by_enum_with_value_variant.stderr index f6c85ed2bba3e..633dc3bbe8bc4 100644 --- a/primitives/runtime-interface/tests/ui/pass_by_enum_with_value_variant.stderr +++ b/primitives/runtime-interface/tests/ui/pass_by_enum_with_value_variant.stderr @@ -4,4 +4,4 @@ error: `PassByEnum` only supports unit variants. 3 | #[derive(PassByEnum)] | ^^^^^^^^^^ | - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `PassByEnum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/primitives/runtime-interface/tests/ui/pass_by_inner_with_two_fields.stderr b/primitives/runtime-interface/tests/ui/pass_by_inner_with_two_fields.stderr index 9afbce76f0c23..0ffee00210e79 100644 --- a/primitives/runtime-interface/tests/ui/pass_by_inner_with_two_fields.stderr +++ b/primitives/runtime-interface/tests/ui/pass_by_inner_with_two_fields.stderr @@ -4,4 +4,4 @@ error: Only newtype/one field structs are supported by `PassByInner`! 3 | #[derive(PassByInner)] | ^^^^^^^^^^^ | - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `PassByInner` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 5ac5bcf1963e0..8ea6ed3eb3b19 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-runtime" -version = "4.0.0-dev" +version = "7.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Runtime Modules shared primitive types." documentation = "https://docs.rs/sp-runtime" @@ -13,48 +13,47 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -serde = { version = "1.0.126", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../application-crypto" } -sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../arithmetic" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../io" } -log = { version = "0.4.14", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +either = { version = "1.5", default-features = false } +hash256-std-hasher = { version = "0.15.2", default-features = false } +impl-trait-for-tuples = "0.2.2" +log = { version = "0.4.17", default-features = false } paste = "1.0" rand = { version = "0.7.2", optional = true } -impl-trait-for-tuples = "0.2.1" -parity-util-mem = { version = "0.10.0", default-features = false, features = ["primitive-types"] } -hash256-std-hasher = { version = "0.15.2", default-features = false } -either = { version = "1.5", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../arithmetic" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-io = { version = "7.0.0", default-features = false, path = "../io" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-weights = { version = "4.0.0", default-features = false, path = "../weights" } [dev-dependencies] -serde_json = "1.0.68" rand = "0.7.2" -sp-state-machine = { version = "0.10.0-dev", path = "../state-machine" } +serde_json = "1.0.85" +zstd = { version = "0.11.2", default-features = false } sp-api = { version = "4.0.0-dev", path = "../api" } +sp-state-machine = { version = "0.13.0", path = "../state-machine" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -sp-tracing = { version = "4.0.0-dev", path = "../../primitives/tracing" } [features] -bench = [] runtime-benchmarks = [] default = ["std"] std = [ - "sp-application-crypto/std", - "sp-arithmetic/std", "codec/std", - "scale-info/std", + "either/use_std", + "hash256-std-hasher/std", "log/std", - "sp-core/std", "rand", - "sp-std/std", - "sp-io/std", + "scale-info/std", "serde", - "parity-util-mem/std", - "hash256-std-hasher/std", - "either/use_std", + "sp-application-crypto/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-std/std", + "sp-weights/std", ] diff --git a/primitives/runtime/src/curve.rs b/primitives/runtime/src/curve.rs index d6bd94c2bff70..c040b7cf517e0 100644 --- a/primitives/runtime/src/curve.rs +++ b/primitives/runtime/src/curve.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ use crate::{ use core::ops::Sub; /// Piecewise Linear function in [0, 1] -> [0, 1]. -#[derive(PartialEq, Eq, sp_core::RuntimeDebug, scale_info::TypeInfo)] +#[derive(PartialEq, Eq, sp_core::RuntimeDebug)] pub struct PiecewiseLinear<'a> { /// Array of points. Must be in order from the lowest abscissas to the highest. pub points: &'a [(Perbill, Perbill)], @@ -32,6 +32,36 @@ pub struct PiecewiseLinear<'a> { pub maximum: Perbill, } +// This can be replaced with +// #[derive(scale_info::TypeInfo)] +// #[scale_info(skip_type_params(S))] +// again once this issue is fixed in the rust compiler: https://github.com/rust-lang/rust/issues/96956 +// Tracking issues: https://github.com/paritytech/substrate/issues/11915 +impl scale_info::TypeInfo for PiecewiseLinear<'static> { + type Identity = Self; + fn type_info() -> ::scale_info::Type { + scale_info::Type::builder() + .path(scale_info::Path::new("PiecewiseLinear", "sp_runtime::curve")) + .type_params(crate::Vec::new()) + .docs(&["Piecewise Linear function in [0, 1] -> [0, 1]."]) + .composite( + scale_info::build::Fields::named() + .field(|f| { + f.ty::<&'static[(Perbill, Perbill)]>() + .name("points") + .type_name("&'static[(Perbill, Perbill)]") + .docs(&["Array of points. Must be in order from the lowest abscissas to the highest."]) + }) + .field(|f| { + f.ty::() + .name("maximum") + .type_name("Perbill") + .docs(&["The maximum value that can be returned."]) + }), + ) + } +} + fn abs_sub + Clone>(a: N, b: N) -> N where { a.clone().max(b.clone()) - a.min(b) } @@ -110,8 +140,6 @@ where #[test] fn test_multiply_by_rational_saturating() { - use std::convert::TryInto; - let div = 100u32; for value in 0..=div { for p in 0..=div { @@ -132,8 +160,6 @@ fn test_multiply_by_rational_saturating() { #[test] fn test_calculate_for_fraction_times_denominator() { - use std::convert::TryInto; - let curve = PiecewiseLinear { points: &[ (Perbill::from_parts(0_000_000_000), Perbill::from_parts(0_500_000_000)), @@ -144,7 +170,7 @@ fn test_calculate_for_fraction_times_denominator() { }; pub fn formal_calculate_for_fraction_times_denominator(n: u64, d: u64) -> u64 { - if n <= Perbill::from_parts(0_500_000_000) * d.clone() { + if n <= Perbill::from_parts(0_500_000_000) * d { n + d / 2 } else { (d as u128 * 2 - n as u128 * 2).try_into().unwrap() diff --git a/primitives/runtime/src/generic/block.rs b/primitives/runtime/src/generic/block.rs index 21a01933bc691..3b01633635c24 100644 --- a/primitives/runtime/src/generic/block.rs +++ b/primitives/runtime/src/generic/block.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,17 +25,14 @@ use serde::{Deserialize, Serialize}; use crate::{ codec::{Codec, Decode, Encode}, - traits::{ - self, Block as BlockT, Header as HeaderT, MaybeMallocSizeOf, MaybeSerialize, Member, - NumberFor, - }, + traits::{self, Block as BlockT, Header as HeaderT, MaybeSerialize, Member, NumberFor}, Justifications, }; use sp_core::RuntimeDebug; use sp_std::prelude::*; /// Something to identify a block. -#[derive(PartialEq, Eq, Clone, RuntimeDebug)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Serialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] #[cfg_attr(feature = "std", serde(deny_unknown_fields))] @@ -82,7 +79,7 @@ impl fmt::Display for BlockId { /// Abstraction over a substrate block. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, parity_util_mem::MallocSizeOf))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] #[cfg_attr(feature = "std", serde(deny_unknown_fields))] pub struct Block { @@ -95,7 +92,7 @@ pub struct Block { impl traits::Block for Block where Header: HeaderT, - Extrinsic: Member + Codec + traits::Extrinsic + MaybeMallocSizeOf, + Extrinsic: Member + Codec + traits::Extrinsic, { type Extrinsic = Extrinsic; type Header = Header; diff --git a/primitives/runtime/src/generic/checked_extrinsic.rs b/primitives/runtime/src/generic/checked_extrinsic.rs index b2044a6cf74fd..fd7745c6031ff 100644 --- a/primitives/runtime/src/generic/checked_extrinsic.rs +++ b/primitives/runtime/src/generic/checked_extrinsic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,12 +39,13 @@ pub struct CheckedExtrinsic { pub function: Call, } -impl traits::Applyable for CheckedExtrinsic +impl traits::Applyable + for CheckedExtrinsic where AccountId: Member + MaybeDisplay, - Call: Member + Dispatchable, + Call: Member + Dispatchable, Extra: SignedExtension, - Origin: From>, + RuntimeOrigin: From>, { type Call = Call; @@ -70,20 +71,26 @@ where info: &DispatchInfoOf, len: usize, ) -> crate::ApplyExtrinsicResultWithInfo> { - let (maybe_who, pre) = if let Some((id, extra)) = self.signed { + let (maybe_who, maybe_pre) = if let Some((id, extra)) = self.signed { let pre = Extra::pre_dispatch(extra, &id, &self.function, info, len)?; - (Some(id), pre) + (Some(id), Some(pre)) } else { - let pre = Extra::pre_dispatch_unsigned(&self.function, info, len)?; + Extra::pre_dispatch_unsigned(&self.function, info, len)?; U::pre_dispatch(&self.function)?; - (None, pre) + (None, None) }; - let res = self.function.dispatch(Origin::from(maybe_who)); + let res = self.function.dispatch(RuntimeOrigin::from(maybe_who)); let post_info = match res { Ok(info) => info, Err(err) => err.post_info, }; - Extra::post_dispatch(pre, info, &post_info, len, &res.map(|_| ()).map_err(|e| e.error))?; + Extra::post_dispatch( + maybe_pre, + info, + &post_info, + len, + &res.map(|_| ()).map_err(|e| e.error), + )?; Ok(res) } } diff --git a/primitives/runtime/src/generic/digest.rs b/primitives/runtime/src/generic/digest.rs index 87af9bc77a5fa..1d1173057ea8d 100644 --- a/primitives/runtime/src/generic/digest.rs +++ b/primitives/runtime/src/generic/digest.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,59 +26,43 @@ use crate::{ codec::{Decode, Encode, Error, Input}, scale_info::{ build::{Fields, Variants}, - meta_type, Path, Type, TypeInfo, TypeParameter, + Path, Type, TypeInfo, }, ConsensusEngineId, }; -use sp_core::{ChangesTrieConfiguration, RuntimeDebug}; +use sp_core::RuntimeDebug; /// Generic header digest. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, parity_util_mem::MallocSizeOf))] -pub struct Digest { +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Default)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Digest { /// A list of logs in the digest. - #[cfg_attr( - feature = "std", - serde(bound(serialize = "Hash: codec::Codec", deserialize = "Hash: codec::Codec")) - )] - pub logs: Vec>, + pub logs: Vec, } -impl Default for Digest { - fn default() -> Self { - Self { logs: Vec::new() } - } -} - -impl Digest { +impl Digest { /// Get reference to all digest items. - pub fn logs(&self) -> &[DigestItem] { + pub fn logs(&self) -> &[DigestItem] { &self.logs } /// Push new digest item. - pub fn push(&mut self, item: DigestItem) { + pub fn push(&mut self, item: DigestItem) { self.logs.push(item); } /// Pop a digest item. - pub fn pop(&mut self) -> Option> { + pub fn pop(&mut self) -> Option { self.logs.pop() } /// Get reference to the first digest item that matches the passed predicate. - pub fn log) -> Option<&T>>( - &self, - predicate: F, - ) -> Option<&T> { + pub fn log Option<&T>>(&self, predicate: F) -> Option<&T> { self.logs().iter().find_map(predicate) } /// Get a conversion of the first digest item that successfully converts using the function. - pub fn convert_first) -> Option>( - &self, - predicate: F, - ) -> Option { + pub fn convert_first Option>(&self, predicate: F) -> Option { self.logs().iter().find_map(predicate) } } @@ -86,13 +70,7 @@ impl Digest { /// Digest item that is able to encode/decode 'system' digest items and /// provide opaque access to other items. #[derive(PartialEq, Eq, Clone, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(parity_util_mem::MallocSizeOf))] -pub enum DigestItem { - /// System digest item that contains the root of changes trie at given - /// block. It is created for every block iff runtime supports changes - /// trie creation. - ChangesTrieRoot(Hash), - +pub enum DigestItem { /// A pre-runtime digest. /// /// These are messages from the consensus engine to the runtime, although @@ -116,10 +94,6 @@ pub enum DigestItem { /// by runtimes. Seal(ConsensusEngineId, Vec), - /// Digest item that contains signal from changes tries manager to the - /// native code. - ChangesTrieSignal(ChangesTrieSignal), - /// Some other thing. Unsupported and experimental. Other(Vec), @@ -132,25 +106,8 @@ pub enum DigestItem { RuntimeEnvironmentUpdated, } -/// Available changes trie signals. -#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo)] -#[cfg_attr(feature = "std", derive(Debug, parity_util_mem::MallocSizeOf))] -pub enum ChangesTrieSignal { - /// New changes trie configuration is enacted, starting from **next block**. - /// - /// The block that emits this signal will contain changes trie (CT) that covers - /// blocks range [BEGIN; current block], where BEGIN is (order matters): - /// - LAST_TOP_LEVEL_DIGEST_BLOCK+1 if top level digest CT has ever been created using current - /// configuration AND the last top level digest CT has been created at block - /// LAST_TOP_LEVEL_DIGEST_BLOCK; - /// - LAST_CONFIGURATION_CHANGE_BLOCK+1 if there has been CT configuration change before and - /// the last configuration change happened at block LAST_CONFIGURATION_CHANGE_BLOCK; - /// - 1 otherwise. - NewConfiguration(Option), -} - #[cfg(feature = "std")] -impl serde::Serialize for DigestItem { +impl serde::Serialize for DigestItem { fn serialize(&self, seq: S) -> Result where S: serde::Serializer, @@ -160,7 +117,7 @@ impl serde::Serialize for DigestItem { } #[cfg(feature = "std")] -impl<'a, Hash: Decode> serde::Deserialize<'a> for DigestItem { +impl<'a> serde::Deserialize<'a> for DigestItem { fn deserialize(de: D) -> Result where D: serde::Deserializer<'a>, @@ -171,94 +128,64 @@ impl<'a, Hash: Decode> serde::Deserialize<'a> for DigestItem { } } -impl TypeInfo for DigestItem -where - Hash: TypeInfo + 'static, -{ +impl TypeInfo for DigestItem { type Identity = Self; fn type_info() -> Type { - Type::builder() - .path(Path::new("DigestItem", module_path!())) - .type_params(vec![TypeParameter::new("Hash", Some(meta_type::()))]) - .variant( - Variants::new() - .variant("ChangesTrieRoot", |v| { - v.index(DigestItemType::ChangesTrieRoot as u8) - .fields(Fields::unnamed().field(|f| f.ty::().type_name("Hash"))) - }) - .variant("PreRuntime", |v| { - v.index(DigestItemType::PreRuntime as u8).fields( - Fields::unnamed() - .field(|f| { - f.ty::().type_name("ConsensusEngineId") - }) - .field(|f| f.ty::>().type_name("Vec")), - ) - }) - .variant("Consensus", |v| { - v.index(DigestItemType::Consensus as u8).fields( - Fields::unnamed() - .field(|f| { - f.ty::().type_name("ConsensusEngineId") - }) - .field(|f| f.ty::>().type_name("Vec")), - ) - }) - .variant("Seal", |v| { - v.index(DigestItemType::Seal as u8).fields( - Fields::unnamed() - .field(|f| { - f.ty::().type_name("ConsensusEngineId") - }) - .field(|f| f.ty::>().type_name("Vec")), - ) - }) - .variant("ChangesTrieSignal", |v| { - v.index(DigestItemType::ChangesTrieSignal as u8).fields( - Fields::unnamed().field(|f| { - f.ty::().type_name("ChangesTrieSignal") - }), - ) - }) - .variant("Other", |v| { - v.index(DigestItemType::Other as u8).fields( - Fields::unnamed().field(|f| f.ty::>().type_name("Vec")), - ) - }) - .variant("RuntimeEnvironmentUpdated", |v| { - v.index(DigestItemType::RuntimeEnvironmentUpdated as u8) - .fields(Fields::unit()) - }), - ) + Type::builder().path(Path::new("DigestItem", module_path!())).variant( + Variants::new() + .variant("PreRuntime", |v| { + v.index(DigestItemType::PreRuntime as u8).fields( + Fields::unnamed() + .field(|f| f.ty::().type_name("ConsensusEngineId")) + .field(|f| f.ty::>().type_name("Vec")), + ) + }) + .variant("Consensus", |v| { + v.index(DigestItemType::Consensus as u8).fields( + Fields::unnamed() + .field(|f| f.ty::().type_name("ConsensusEngineId")) + .field(|f| f.ty::>().type_name("Vec")), + ) + }) + .variant("Seal", |v| { + v.index(DigestItemType::Seal as u8).fields( + Fields::unnamed() + .field(|f| f.ty::().type_name("ConsensusEngineId")) + .field(|f| f.ty::>().type_name("Vec")), + ) + }) + .variant("Other", |v| { + v.index(DigestItemType::Other as u8) + .fields(Fields::unnamed().field(|f| f.ty::>().type_name("Vec"))) + }) + .variant("RuntimeEnvironmentUpdated", |v| { + v.index(DigestItemType::RuntimeEnvironmentUpdated as u8).fields(Fields::unit()) + }), + ) } } /// A 'referencing view' for digest item. Does not own its contents. Used by /// final runtime implementations for encoding/decoding its log items. #[derive(PartialEq, Eq, Clone, RuntimeDebug)] -pub enum DigestItemRef<'a, Hash: 'a> { - /// Reference to `DigestItem::ChangesTrieRoot`. - ChangesTrieRoot(&'a Hash), +pub enum DigestItemRef<'a> { /// A pre-runtime digest. /// /// These are messages from the consensus engine to the runtime, although /// the consensus engine can (and should) read them itself to avoid /// code and state duplication. It is erroneous for a runtime to produce /// these, but this is not (yet) checked. - PreRuntime(&'a ConsensusEngineId, &'a Vec), + PreRuntime(&'a ConsensusEngineId, &'a [u8]), /// A message from the runtime to the consensus engine. This should *never* /// be generated by the native code of any consensus engine, but this is not /// checked (yet). - Consensus(&'a ConsensusEngineId, &'a Vec), + Consensus(&'a ConsensusEngineId, &'a [u8]), /// Put a Seal on it. This is only used by native code, and is never seen /// by runtimes. - Seal(&'a ConsensusEngineId, &'a Vec), - /// Digest item that contains signal from changes tries manager to the - /// native code. - ChangesTrieSignal(&'a ChangesTrieSignal), + Seal(&'a ConsensusEngineId, &'a [u8]), /// Any 'non-system' digest item, opaque to the native code. - Other(&'a Vec), + Other(&'a [u8]), /// Runtime code or heap pages updated. RuntimeEnvironmentUpdated, } @@ -271,11 +198,9 @@ pub enum DigestItemRef<'a, Hash: 'a> { #[derive(Encode, Decode)] pub enum DigestItemType { Other = 0, - ChangesTrieRoot = 2, Consensus = 4, Seal = 5, PreRuntime = 6, - ChangesTrieSignal = 7, RuntimeEnvironmentUpdated = 8, } @@ -293,25 +218,18 @@ pub enum OpaqueDigestItemId<'a> { Other, } -impl DigestItem { +impl DigestItem { /// Returns a 'referencing view' for this digest item. - pub fn dref(&self) -> DigestItemRef { + pub fn dref(&self) -> DigestItemRef { match *self { - Self::ChangesTrieRoot(ref v) => DigestItemRef::ChangesTrieRoot(v), Self::PreRuntime(ref v, ref s) => DigestItemRef::PreRuntime(v, s), Self::Consensus(ref v, ref s) => DigestItemRef::Consensus(v, s), Self::Seal(ref v, ref s) => DigestItemRef::Seal(v, s), - Self::ChangesTrieSignal(ref s) => DigestItemRef::ChangesTrieSignal(s), Self::Other(ref v) => DigestItemRef::Other(v), Self::RuntimeEnvironmentUpdated => DigestItemRef::RuntimeEnvironmentUpdated, } } - /// Returns `Some` if the entry is the `ChangesTrieRoot` entry. - pub fn as_changes_trie_root(&self) -> Option<&Hash> { - self.dref().as_changes_trie_root() - } - /// Returns `Some` if this entry is the `PreRuntime` entry. pub fn as_pre_runtime(&self) -> Option<(ConsensusEngineId, &[u8])> { self.dref().as_pre_runtime() @@ -327,11 +245,6 @@ impl DigestItem { self.dref().as_seal() } - /// Returns `Some` if the entry is the `ChangesTrieSignal` entry. - pub fn as_changes_trie_signal(&self) -> Option<&ChangesTrieSignal> { - self.dref().as_changes_trie_signal() - } - /// Returns Some if `self` is a `DigestItem::Other`. pub fn as_other(&self) -> Option<&[u8]> { self.dref().as_other() @@ -372,20 +285,19 @@ impl DigestItem { } } -impl Encode for DigestItem { +impl Encode for DigestItem { fn encode(&self) -> Vec { self.dref().encode() } } -impl codec::EncodeLike for DigestItem {} +impl codec::EncodeLike for DigestItem {} -impl Decode for DigestItem { +impl Decode for DigestItem { #[allow(deprecated)] fn decode(input: &mut I) -> Result { let item_type: DigestItemType = Decode::decode(input)?; match item_type { - DigestItemType::ChangesTrieRoot => Ok(Self::ChangesTrieRoot(Decode::decode(input)?)), DigestItemType::PreRuntime => { let vals: (ConsensusEngineId, Vec) = Decode::decode(input)?; Ok(Self::PreRuntime(vals.0, vals.1)) @@ -398,27 +310,17 @@ impl Decode for DigestItem { let vals: (ConsensusEngineId, Vec) = Decode::decode(input)?; Ok(Self::Seal(vals.0, vals.1)) }, - DigestItemType::ChangesTrieSignal => - Ok(Self::ChangesTrieSignal(Decode::decode(input)?)), DigestItemType::Other => Ok(Self::Other(Decode::decode(input)?)), DigestItemType::RuntimeEnvironmentUpdated => Ok(Self::RuntimeEnvironmentUpdated), } } } -impl<'a, Hash> DigestItemRef<'a, Hash> { - /// Cast this digest item into `ChangesTrieRoot`. - pub fn as_changes_trie_root(&self) -> Option<&'a Hash> { - match *self { - Self::ChangesTrieRoot(ref changes_trie_root) => Some(changes_trie_root), - _ => None, - } - } - +impl<'a> DigestItemRef<'a> { /// Cast this digest item into `PreRuntime` pub fn as_pre_runtime(&self) -> Option<(ConsensusEngineId, &'a [u8])> { match *self { - Self::PreRuntime(consensus_engine_id, ref data) => Some((*consensus_engine_id, data)), + Self::PreRuntime(consensus_engine_id, data) => Some((*consensus_engine_id, data)), _ => None, } } @@ -426,7 +328,7 @@ impl<'a, Hash> DigestItemRef<'a, Hash> { /// Cast this digest item into `Consensus` pub fn as_consensus(&self) -> Option<(ConsensusEngineId, &'a [u8])> { match *self { - Self::Consensus(consensus_engine_id, ref data) => Some((*consensus_engine_id, data)), + Self::Consensus(consensus_engine_id, data) => Some((*consensus_engine_id, data)), _ => None, } } @@ -434,15 +336,7 @@ impl<'a, Hash> DigestItemRef<'a, Hash> { /// Cast this digest item into `Seal` pub fn as_seal(&self) -> Option<(ConsensusEngineId, &'a [u8])> { match *self { - Self::Seal(consensus_engine_id, ref data) => Some((*consensus_engine_id, data)), - _ => None, - } - } - - /// Cast this digest item into `ChangesTrieSignal`. - pub fn as_changes_trie_signal(&self) -> Option<&'a ChangesTrieSignal> { - match *self { - Self::ChangesTrieSignal(ref changes_trie_signal) => Some(changes_trie_signal), + Self::Seal(consensus_engine_id, data) => Some((*consensus_engine_id, data)), _ => None, } } @@ -450,7 +344,7 @@ impl<'a, Hash> DigestItemRef<'a, Hash> { /// Cast this digest item into `PreRuntime` pub fn as_other(&self) -> Option<&'a [u8]> { match *self { - Self::Other(ref data) => Some(data), + Self::Other(data) => Some(data), _ => None, } } @@ -463,8 +357,8 @@ impl<'a, Hash> DigestItemRef<'a, Hash> { (OpaqueDigestItemId::Seal(w), &Self::Seal(v, s)) | (OpaqueDigestItemId::PreRuntime(w), &Self::PreRuntime(v, s)) if v == w => - Some(&s[..]), - (OpaqueDigestItemId::Other, &Self::Other(s)) => Some(&s[..]), + Some(s), + (OpaqueDigestItemId::Other, &Self::Other(s)) => Some(s), _ => None, } } @@ -508,15 +402,11 @@ impl<'a, Hash> DigestItemRef<'a, Hash> { } } -impl<'a, Hash: Encode> Encode for DigestItemRef<'a, Hash> { +impl<'a> Encode for DigestItemRef<'a> { fn encode(&self) -> Vec { let mut v = Vec::new(); match *self { - Self::ChangesTrieRoot(changes_trie_root) => { - DigestItemType::ChangesTrieRoot.encode_to(&mut v); - changes_trie_root.encode_to(&mut v); - }, Self::Consensus(val, data) => { DigestItemType::Consensus.encode_to(&mut v); (val, data).encode_to(&mut v); @@ -529,10 +419,6 @@ impl<'a, Hash: Encode> Encode for DigestItemRef<'a, Hash> { DigestItemType::PreRuntime.encode_to(&mut v); (val, data).encode_to(&mut v); }, - Self::ChangesTrieSignal(changes_trie_signal) => { - DigestItemType::ChangesTrieSignal.encode_to(&mut v); - changes_trie_signal.encode_to(&mut v); - }, Self::Other(val) => { DigestItemType::Other.encode_to(&mut v); val.encode_to(&mut v); @@ -546,16 +432,7 @@ impl<'a, Hash: Encode> Encode for DigestItemRef<'a, Hash> { } } -impl ChangesTrieSignal { - /// Try to cast this signal to NewConfiguration. - pub fn as_new_configuration(&self) -> Option<&Option> { - match self { - Self::NewConfiguration(config) => Some(config), - } - } -} - -impl<'a, Hash: Encode> codec::EncodeLike for DigestItemRef<'a, Hash> {} +impl<'a> codec::EncodeLike for DigestItemRef<'a> {} #[cfg(test)] mod tests { @@ -564,22 +441,18 @@ mod tests { #[test] fn should_serialize_digest() { let digest = Digest { - logs: vec![ - DigestItem::ChangesTrieRoot(4), - DigestItem::Other(vec![1, 2, 3]), - DigestItem::Seal(*b"test", vec![1, 2, 3]), - ], + logs: vec![DigestItem::Other(vec![1, 2, 3]), DigestItem::Seal(*b"test", vec![1, 2, 3])], }; assert_eq!( serde_json::to_string(&digest).unwrap(), - r#"{"logs":["0x0204000000","0x000c010203","0x05746573740c010203"]}"# + r#"{"logs":["0x000c010203","0x05746573740c010203"]}"# ); } #[test] fn digest_item_type_info() { - let type_info = DigestItem::::type_info(); + let type_info = DigestItem::type_info(); let variants = if let scale_info::TypeDef::Variant(variant) = type_info.type_def() { variant.variants() } else { @@ -589,21 +462,13 @@ mod tests { // ensure that all variants are covered by manual TypeInfo impl let check = |digest_item_type: DigestItemType| { let (variant_name, digest_item) = match digest_item_type { - DigestItemType::Other => ("Other", DigestItem::::Other(Default::default())), - DigestItemType::ChangesTrieRoot => - ("ChangesTrieRoot", DigestItem::ChangesTrieRoot(Default::default())), + DigestItemType::Other => ("Other", DigestItem::Other(Default::default())), DigestItemType::Consensus => ("Consensus", DigestItem::Consensus(Default::default(), Default::default())), DigestItemType::Seal => ("Seal", DigestItem::Seal(Default::default(), Default::default())), DigestItemType::PreRuntime => ("PreRuntime", DigestItem::PreRuntime(Default::default(), Default::default())), - DigestItemType::ChangesTrieSignal => ( - "ChangesTrieSignal", - DigestItem::ChangesTrieSignal(ChangesTrieSignal::NewConfiguration( - Default::default(), - )), - ), DigestItemType::RuntimeEnvironmentUpdated => ("RuntimeEnvironmentUpdated", DigestItem::RuntimeEnvironmentUpdated), }; @@ -617,11 +482,9 @@ mod tests { }; check(DigestItemType::Other); - check(DigestItemType::ChangesTrieRoot); check(DigestItemType::Consensus); check(DigestItemType::Seal); check(DigestItemType::PreRuntime); - check(DigestItemType::ChangesTrieSignal); check(DigestItemType::RuntimeEnvironmentUpdated); } } diff --git a/primitives/runtime/src/generic/era.rs b/primitives/runtime/src/generic/era.rs index 9d831b679c5e4..b26545fb8404e 100644 --- a/primitives/runtime/src/generic/era.rs +++ b/primitives/runtime/src/generic/era.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -63,7 +63,7 @@ impl Era { /// does not exceed `BlockHashCount` parameter passed to `system` module, since that /// prunes old blocks and renders transactions immediately invalid. pub fn mortal(period: u64, current: u64) -> Self { - let period = period.checked_next_power_of_two().unwrap_or(1 << 16).max(4).min(1 << 16); + let period = period.checked_next_power_of_two().unwrap_or(1 << 16).clamp(4, 1 << 16); let phase = current % period; let quantize_factor = (period >> 12).max(1); let quantized_phase = phase / quantize_factor * quantize_factor; @@ -105,7 +105,7 @@ impl Encode for Era { Self::Immortal => output.push_byte(0), Self::Mortal(period, phase) => { let quantize_factor = (*period as u64 >> 12).max(1); - let encoded = (period.trailing_zeros() - 1).max(1).min(15) as u16 | + let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16 | ((phase / quantize_factor) << 4) as u16; encoded.encode_to(output); }, diff --git a/primitives/runtime/src/generic/header.rs b/primitives/runtime/src/generic/header.rs index 82f081c0d70b0..04d09f6b15541 100644 --- a/primitives/runtime/src/generic/header.rs +++ b/primitives/runtime/src/generic/header.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,14 +22,14 @@ use crate::{ generic::Digest, scale_info::TypeInfo, traits::{ - self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeMallocSizeOf, MaybeSerialize, + self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeSerialize, MaybeSerializeDeserialize, Member, SimpleBitOps, }, }; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_core::U256; -use sp_std::{convert::TryFrom, fmt::Debug}; +use sp_std::fmt::Debug; /// Abstraction over a block header for a substrate chain. #[derive(Encode, Decode, PartialEq, Eq, Clone, sp_core::RuntimeDebug, TypeInfo)] @@ -51,23 +51,7 @@ pub struct Header + TryFrom, Hash: HashT> { /// The merkle root of the extrinsics. pub extrinsics_root: Hash::Output, /// A chain-specific digest of data useful for light clients or referencing auxiliary data. - pub digest: Digest, -} - -#[cfg(feature = "std")] -impl parity_util_mem::MallocSizeOf for Header -where - Number: Copy + Into + TryFrom + parity_util_mem::MallocSizeOf, - Hash: HashT, - Hash::Output: parity_util_mem::MallocSizeOf, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - self.parent_hash.size_of(ops) + - self.number.size_of(ops) + - self.state_root.size_of(ops) + - self.extrinsics_root.size_of(ops) + - self.digest.size_of(ops) - } + pub digest: Digest, } #[cfg(feature = "std")] @@ -103,8 +87,7 @@ where + Copy + Into + TryFrom - + sp_std::str::FromStr - + MaybeMallocSizeOf, + + sp_std::str::FromStr, Hash: HashT, Hash::Output: Default + sp_std::hash::Hash @@ -115,8 +98,7 @@ where + Debug + MaybeDisplay + SimpleBitOps - + Codec - + MaybeMallocSizeOf, + + Codec, { type Number = Number; type Hash = ::Output; @@ -150,11 +132,11 @@ where self.parent_hash = hash } - fn digest(&self) -> &Digest { + fn digest(&self) -> &Digest { &self.digest } - fn digest_mut(&mut self) -> &mut Digest { + fn digest_mut(&mut self) -> &mut Digest { #[cfg(feature = "std")] log::debug!(target: "header", "Retrieving mutable reference to digest"); &mut self.digest @@ -165,7 +147,7 @@ where extrinsics_root: Self::Hash, state_root: Self::Hash, parent_hash: Self::Hash, - digest: Digest, + digest: Digest, ) -> Self { Self { number, extrinsics_root, state_root, parent_hash, digest } } @@ -235,10 +217,7 @@ mod tests { state_root: BlakeTwo256::hash(b"3"), extrinsics_root: BlakeTwo256::hash(b"4"), digest: crate::generic::Digest { - logs: vec![ - crate::generic::DigestItem::ChangesTrieRoot(BlakeTwo256::hash(b"5")), - crate::generic::DigestItem::Other(b"6".to_vec()), - ], + logs: vec![crate::generic::DigestItem::Other(b"6".to_vec())], }, }; @@ -251,9 +230,7 @@ mod tests { 72, 51, 123, 15, 62, 20, 134, 32, 23, 61, 170, 165, 249, 77, 0, 216, 129, 112, 93, 203, 240, 170, 131, 239, 218, 186, 97, 210, 237, 225, 235, 134, 73, 33, 73, 151, 87, 78, 32, 196, 100, 56, 138, 23, 36, 32, 210, 84, 3, 104, 43, 187, 184, 12, 73, - 104, 49, 200, 204, 31, 143, 13, 8, 2, 112, 178, 1, 53, 47, 36, 191, 28, 151, 112, - 185, 159, 143, 113, 32, 24, 33, 65, 28, 244, 20, 55, 124, 155, 140, 45, 188, 238, - 97, 219, 135, 214, 0, 4, 54 + 104, 49, 200, 204, 31, 143, 13, 4, 0, 4, 54 ], ); assert_eq!(header, Header::::decode(&mut &header_encoded[..]).unwrap()); @@ -264,10 +241,7 @@ mod tests { state_root: BlakeTwo256::hash(b"3000"), extrinsics_root: BlakeTwo256::hash(b"4000"), digest: crate::generic::Digest { - logs: vec![ - crate::generic::DigestItem::Other(b"5000".to_vec()), - crate::generic::DigestItem::ChangesTrieRoot(BlakeTwo256::hash(b"6000")), - ], + logs: vec![crate::generic::DigestItem::Other(b"5000".to_vec())], }, }; @@ -280,9 +254,7 @@ mod tests { 47, 12, 107, 88, 153, 146, 55, 21, 226, 186, 110, 48, 167, 187, 67, 183, 228, 232, 118, 136, 30, 254, 11, 87, 48, 112, 7, 97, 31, 82, 146, 110, 96, 87, 152, 68, 98, 162, 227, 222, 78, 14, 244, 194, 120, 154, 112, 97, 222, 144, 174, 101, 220, 44, - 111, 126, 54, 34, 155, 220, 253, 124, 8, 0, 16, 53, 48, 48, 48, 2, 42, 105, 109, - 150, 206, 223, 24, 44, 164, 77, 27, 137, 177, 220, 25, 170, 140, 35, 156, 246, 233, - 112, 26, 23, 192, 61, 226, 14, 84, 219, 144, 252 + 111, 126, 54, 34, 155, 220, 253, 124, 4, 0, 16, 53, 48, 48, 48 ], ); assert_eq!(header, Header::::decode(&mut &header_encoded[..]).unwrap()); diff --git a/primitives/runtime/src/generic/mod.rs b/primitives/runtime/src/generic/mod.rs index 71127e88ec32c..049b0e1624e7e 100644 --- a/primitives/runtime/src/generic/mod.rs +++ b/primitives/runtime/src/generic/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,7 +31,7 @@ mod unchecked_extrinsic; pub use self::{ block::{Block, BlockId, SignedBlock}, checked_extrinsic::CheckedExtrinsic, - digest::{ChangesTrieSignal, Digest, DigestItem, DigestItemRef, OpaqueDigestItemId}, + digest::{Digest, DigestItem, DigestItemRef, OpaqueDigestItemId}, era::{Era, Phase}, header::Header, unchecked_extrinsic::{SignedPayload, UncheckedExtrinsic}, diff --git a/primitives/runtime/src/generic/tests.rs b/primitives/runtime/src/generic/tests.rs index 095bcb717bb11..d0536a5673125 100644 --- a/primitives/runtime/src/generic/tests.rs +++ b/primitives/runtime/src/generic/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,29 +19,26 @@ use super::DigestItem; use crate::codec::{Decode, Encode}; -use sp_core::H256; #[test] fn system_digest_item_encoding() { - let item = DigestItem::ChangesTrieRoot::(H256::default()); + let item = DigestItem::Consensus([1, 2, 3, 4], vec![5, 6, 7, 8]); let encoded = item.encode(); assert_eq!( encoded, vec![ - // type = DigestItemType::ChangesTrieRoot - 2, // trie root - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, + 4, // type = DigestItemType::Consensus + 1, 2, 3, 4, 16, 5, 6, 7, 8, ] ); - let decoded: DigestItem = Decode::decode(&mut &encoded[..]).unwrap(); + let decoded: DigestItem = Decode::decode(&mut &encoded[..]).unwrap(); assert_eq!(item, decoded); } #[test] fn non_system_digest_item_encoding() { - let item = DigestItem::Other::(vec![10, 20, 30]); + let item = DigestItem::Other(vec![10, 20, 30]); let encoded = item.encode(); assert_eq!( encoded, @@ -53,6 +50,6 @@ fn non_system_digest_item_encoding() { ] ); - let decoded: DigestItem = Decode::decode(&mut &encoded[..]).unwrap(); + let decoded: DigestItem = Decode::decode(&mut &encoded[..]).unwrap(); assert_eq!(item, decoded); } diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index 95f4f2f3584d9..5d378410e4756 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,8 +31,12 @@ use scale_info::{build::Fields, meta_type, Path, StaticTypeInfo, Type, TypeInfo, use sp_io::hashing::blake2_256; use sp_std::{fmt, prelude::*}; -/// Current version of the [`UncheckedExtrinsic`] format. -const EXTRINSIC_VERSION: u8 = 4; +/// Current version of the [`UncheckedExtrinsic`] encoded format. +/// +/// This version needs to be bumped if the encoded representation changes. +/// It ensures that if the representation is changed and the format is not known, +/// the decoding fails. +const EXTRINSIC_FORMAT_VERSION: u8 = 4; /// A extrinsic right from the external world. This is unchecked and so /// can contain a signature. @@ -83,18 +87,6 @@ where } } -#[cfg(feature = "std")] -impl parity_util_mem::MallocSizeOf - for UncheckedExtrinsic -where - Extra: SignedExtension, -{ - fn size_of(&self, _ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - // Instantiated only in runtime. - 0 - } -} - impl UncheckedExtrinsic { @@ -164,7 +156,7 @@ impl ExtrinsicMetadata where Extra: SignedExtension, { - const VERSION: u8 = EXTRINSIC_VERSION; + const VERSION: u8 = EXTRINSIC_FORMAT_VERSION; type SignedExtensions = Extra; } @@ -235,23 +227,33 @@ where { fn decode(input: &mut I) -> Result { // This is a little more complicated than usual since the binary format must be compatible - // with substrate's generic `Vec` type. Basically this just means accepting that there - // will be a prefix of vector length (we don't need - // to use this). - let _length_do_not_remove_me_see_above: Compact = Decode::decode(input)?; + // with SCALE's generic `Vec` type. Basically this just means accepting that there + // will be a prefix of vector length. + let expected_length: Compact = Decode::decode(input)?; + let before_length = input.remaining_len()?; let version = input.read_byte()?; let is_signed = version & 0b1000_0000 != 0; let version = version & 0b0111_1111; - if version != EXTRINSIC_VERSION { + if version != EXTRINSIC_FORMAT_VERSION { return Err("Invalid transaction version".into()) } - Ok(Self { - signature: if is_signed { Some(Decode::decode(input)?) } else { None }, - function: Decode::decode(input)?, - }) + let signature = is_signed.then(|| Decode::decode(input)).transpose()?; + let function = Decode::decode(input)?; + + if let Some((before_length, after_length)) = + input.remaining_len()?.and_then(|a| before_length.map(|b| (b, a))) + { + let length = before_length.saturating_sub(after_length); + + if length != expected_length.0 as usize { + return Err("Invalid length prefix".into()) + } + } + + Ok(Self { signature, function }) } } @@ -268,11 +270,11 @@ where // 1 byte version id. match self.signature.as_ref() { Some(s) => { - tmp.push(EXTRINSIC_VERSION | 0b1000_0000); + tmp.push(EXTRINSIC_FORMAT_VERSION | 0b1000_0000); s.encode_to(&mut tmp); }, None => { - tmp.push(EXTRINSIC_VERSION & 0b0111_1111); + tmp.push(EXTRINSIC_FORMAT_VERSION & 0b0111_1111); }, } self.function.encode_to(&mut tmp); @@ -364,7 +366,7 @@ mod tests { use crate::{ codec::{Decode, Encode}, testing::TestSignature as TestSig, - traits::{IdentityLookup, SignedExtension}, + traits::{DispatchInfoOf, IdentityLookup, SignedExtension}, }; use sp_io::hashing::blake2_256; @@ -387,6 +389,16 @@ mod tests { fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } } type Ex = UncheckedExtrinsic; @@ -399,6 +411,17 @@ mod tests { assert_eq!(Ex::decode(&mut &encoded[..]), Ok(ux)); } + #[test] + fn invalid_length_prefix_is_detected() { + let ux = Ex::new_unsigned(vec![0u8; 0]); + let mut encoded = ux.encode(); + + let length = Compact::::decode(&mut &encoded[..]).unwrap(); + Compact(length.0 + 10).encode_to(&mut &mut encoded[..1]); + + assert_eq!(Ex::decode(&mut &encoded[..]), Err("Invalid length prefix".into())); + } + #[test] fn signed_codec_should_work() { let ux = Ex::new_signed( diff --git a/primitives/runtime/src/legacy.rs b/primitives/runtime/src/legacy.rs new file mode 100644 index 0000000000000..7bc7c88a7e10d --- /dev/null +++ b/primitives/runtime/src/legacy.rs @@ -0,0 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Runtime types that existed in old API versions. + +pub mod byte_sized_error; diff --git a/primitives/runtime/src/legacy/byte_sized_error.rs b/primitives/runtime/src/legacy/byte_sized_error.rs new file mode 100644 index 0000000000000..049abff69ff1a --- /dev/null +++ b/primitives/runtime/src/legacy/byte_sized_error.rs @@ -0,0 +1,100 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Runtime types that existed prior to BlockBuilder API version 6. + +use crate::{ArithmeticError, TokenError}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +/// [`ModuleError`] type definition before BlockBuilder API version 6. +#[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ModuleError { + /// Module index, matching the metadata module index. + pub index: u8, + /// Module specific error value. + pub error: u8, + /// Optional error message. + #[codec(skip)] + #[cfg_attr(feature = "std", serde(skip_deserializing))] + pub message: Option<&'static str>, +} + +impl PartialEq for ModuleError { + fn eq(&self, other: &Self) -> bool { + (self.index == other.index) && (self.error == other.error) + } +} + +/// [`DispatchError`] type definition before BlockBuilder API version 6. +#[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum DispatchError { + /// Some error occurred. + Other( + #[codec(skip)] + #[cfg_attr(feature = "std", serde(skip_deserializing))] + &'static str, + ), + /// Failed to lookup some data. + CannotLookup, + /// A bad origin. + BadOrigin, + /// A custom error in a module. + Module(ModuleError), + /// At least one consumer is remaining so the account cannot be destroyed. + ConsumerRemaining, + /// There are no providers so the account cannot be created. + NoProviders, + /// There are too many consumers so the account cannot be created. + TooManyConsumers, + /// An error to do with tokens. + Token(TokenError), + /// An arithmetic error. + Arithmetic(ArithmeticError), +} + +/// [`DispatchOutcome`] type definition before BlockBuilder API version 6. +pub type DispatchOutcome = Result<(), DispatchError>; + +/// [`ApplyExtrinsicResult`] type definition before BlockBuilder API version 6. +pub type ApplyExtrinsicResult = + Result; + +/// Convert the legacy `ApplyExtrinsicResult` type to the latest version. +pub fn convert_to_latest(old: ApplyExtrinsicResult) -> crate::ApplyExtrinsicResult { + old.map(|outcome| { + outcome.map_err(|e| match e { + DispatchError::Other(s) => crate::DispatchError::Other(s), + DispatchError::CannotLookup => crate::DispatchError::CannotLookup, + DispatchError::BadOrigin => crate::DispatchError::BadOrigin, + DispatchError::Module(err) => crate::DispatchError::Module(crate::ModuleError { + index: err.index, + error: [err.error, 0, 0, 0], + message: err.message, + }), + DispatchError::ConsumerRemaining => crate::DispatchError::ConsumerRemaining, + DispatchError::NoProviders => crate::DispatchError::NoProviders, + DispatchError::TooManyConsumers => crate::DispatchError::TooManyConsumers, + DispatchError::Token(err) => crate::DispatchError::Token(err), + DispatchError::Arithmetic(err) => crate::DispatchError::Arithmetic(err), + }) + }) +} diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 80293fe734844..e94efda86aa03 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,10 +19,6 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -// to allow benchmarking -#![cfg_attr(feature = "bench", feature(test))] -#[cfg(feature = "bench")] -extern crate test; #[doc(hidden)] pub use codec; @@ -36,26 +32,30 @@ pub use sp_std; #[doc(hidden)] pub use paste; +#[doc(hidden)] +pub use sp_arithmetic::traits::Saturating; #[doc(hidden)] pub use sp_application_crypto as app_crypto; +pub use sp_core::storage::StateVersion; #[cfg(feature = "std")] pub use sp_core::storage::{Storage, StorageChild}; use sp_core::{ - crypto::{self, Public}, + crypto::{self, ByteArray}, ecdsa, ed25519, hash::{H256, H512}, sr25519, }; -use sp_std::{convert::TryFrom, prelude::*}; +use sp_std::prelude::*; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; pub mod curve; pub mod generic; +pub mod legacy; mod multiaddress; pub mod offchain; pub mod runtime_logger; @@ -76,9 +76,13 @@ pub use generic::{Digest, DigestItem}; pub use sp_application_crypto::{BoundToRuntimeAppPublic, RuntimeAppPublic}; /// Re-export this since it's part of the API of this crate. pub use sp_core::{ + bounded::{BoundedBTreeMap, BoundedBTreeSet, BoundedSlice, BoundedVec, WeakBoundedVec}, crypto::{key_types, AccountId32, CryptoType, CryptoTypeId, KeyTypeId}, TypeId, }; +/// Re-export bounded_vec and bounded_btree_map macros only when std is enabled. +#[cfg(feature = "std")] +pub use sp_core::{bounded_btree_map, bounded_vec}; /// Re-export `RuntimeDebug`, to avoid dependency clutter. pub use sp_core::RuntimeDebug; @@ -91,11 +95,15 @@ pub use sp_arithmetic::helpers_128bit; pub use sp_arithmetic::{ traits::SaturatedConversion, FixedI128, FixedI64, FixedPointNumber, FixedPointOperand, FixedU128, InnerOf, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, Rational128, - UpperOf, + Rounding, UpperOf, }; pub use either::Either; +/// The number of bytes of the module-specific `error` field defined in [`ModuleError`]. +/// In FRAME, this is the maximum encoded size of a pallet error type. +pub const MAX_MODULE_ERROR_ENCODED_SIZE: usize = 4; + /// An abstraction over justification for a block's validity under a consensus algorithm. /// /// Essentially a finality proof. The exact formulation will vary between consensus @@ -141,6 +149,11 @@ impl Justifications { self.iter().find(|j| j.0 == engine_id).map(|j| &j.1) } + /// Remove the encoded justification for the given consensus engine, if it exists. + pub fn remove(&mut self, engine_id: ConsensusEngineId) { + self.0.retain(|j| j.0 != engine_id) + } + /// Return a copy of the encoded justification for the given consensus /// engine, if it exists. pub fn into_justification(self, engine_id: ConsensusEngineId) -> Option { @@ -221,9 +234,9 @@ impl BuildStorage for () { /// Consensus engine unique ID. pub type ConsensusEngineId = [u8; 4]; -/// Signature verify that can work with any known signature types.. +/// Signature verify that can work with any known signature types. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Eq, PartialEq, Clone, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)] pub enum MultiSignature { /// An Ed25519 signature. Ed25519(ed25519::Signature), @@ -284,12 +297,6 @@ impl TryFrom for ecdsa::Signature { } } -impl Default for MultiSignature { - fn default() -> Self { - Self::Ed25519(Default::default()) - } -} - /// Public key for any known crypto algorithm. #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] @@ -302,12 +309,6 @@ pub enum MultiSigner { Ecdsa(ecdsa::Public), } -impl Default for MultiSigner { - fn default() -> Self { - Self::Ed25519(Default::default()) - } -} - /// NOTE: This implementations is required by `SimpleAddressDeterminer`, /// we convert the hash into some AccountId, it's fine to use any scheme. impl> crypto::UncheckedFrom for MultiSigner { @@ -403,10 +404,14 @@ impl Verify for MultiSignature { type Signer = MultiSigner; fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { match (self, signer) { - (Self::Ed25519(ref sig), who) => - sig.verify(msg, &ed25519::Public::from_slice(who.as_ref())), - (Self::Sr25519(ref sig), who) => - sig.verify(msg, &sr25519::Public::from_slice(who.as_ref())), + (Self::Ed25519(ref sig), who) => match ed25519::Public::from_slice(who.as_ref()) { + Ok(signer) => sig.verify(msg, &signer), + Err(()) => false, + }, + (Self::Sr25519(ref sig), who) => match sr25519::Public::from_slice(who.as_ref()) { + Ok(signer) => sig.verify(msg, &signer), + Err(()) => false, + }, (Self::Ecdsa(ref sig), who) => { let m = sp_io::hashing::blake2_256(msg.get()); match sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) { @@ -433,7 +438,10 @@ impl Verify for AnySignature { .map(|s| s.verify(msg, signer)) .unwrap_or(false) || ed25519::Signature::try_from(self.0.as_fixed_bytes().as_ref()) - .map(|s| s.verify(msg, &ed25519::Public::from_slice(signer.as_ref()))) + .map(|s| match ed25519::Public::from_slice(signer.as_ref()) { + Err(()) => false, + Ok(signer) => s.verify(msg, &signer), + }) .unwrap_or(false) } } @@ -465,9 +473,54 @@ pub type DispatchResult = sp_std::result::Result<(), DispatchError>; /// about the `Dispatchable` that is only known post dispatch. pub type DispatchResultWithInfo = sp_std::result::Result>; -/// Reason why a dispatch call failed. +/// Reason why a pallet call failed. #[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ModuleError { + /// Module index, matching the metadata module index. + pub index: u8, + /// Module specific error value. + pub error: [u8; MAX_MODULE_ERROR_ENCODED_SIZE], + /// Optional error message. + #[codec(skip)] + #[cfg_attr(feature = "std", serde(skip_deserializing))] + pub message: Option<&'static str>, +} + +impl PartialEq for ModuleError { + fn eq(&self, other: &Self) -> bool { + (self.index == other.index) && (self.error == other.error) + } +} + +/// Errors related to transactional storage layers. +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum TransactionalError { + /// Too many transactional layers have been spawned. + LimitReached, + /// A transactional layer was expected, but does not exist. + NoLayer, +} + +impl From for &'static str { + fn from(e: TransactionalError) -> &'static str { + match e { + TransactionalError::LimitReached => "Too many transactional layers have been spawned", + TransactionalError::NoLayer => "A transactional layer was expected, but does not exist", + } + } +} + +impl From for DispatchError { + fn from(e: TransactionalError) -> DispatchError { + Self::Transactional(e) + } +} + +/// Reason why a dispatch call failed. +#[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum DispatchError { /// Some error occurred. Other( @@ -480,29 +533,31 @@ pub enum DispatchError { /// A bad origin. BadOrigin, /// A custom error in a module. - Module { - /// Module index, matching the metadata module index. - index: u8, - /// Module specific error value. - error: u8, - /// Optional error message. - #[codec(skip)] - #[cfg_attr(feature = "std", serde(skip_deserializing))] - message: Option<&'static str>, - }, + Module(ModuleError), /// At least one consumer is remaining so the account cannot be destroyed. ConsumerRemaining, /// There are no providers so the account cannot be created. NoProviders, + /// There are too many consumers so the account cannot be created. + TooManyConsumers, /// An error to do with tokens. Token(TokenError), /// An arithmetic error. Arithmetic(ArithmeticError), + /// The number of transactional layers has been reached, or we are not in a transactional + /// layer. + Transactional(TransactionalError), + /// Resources exhausted, e.g. attempt to read/write data which is too large to manipulate. + Exhausted, + /// The state is corrupt; this is generally not going to fix itself. + Corruption, + /// Some resource (e.g. a preimage) is unavailable right now. This might fix itself later. + Unavailable, } /// Result of a `Dispatchable` which contains the `DispatchResult` and additional information about /// the `Dispatchable` that is only known post dispatch. -#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug)] +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct DispatchErrorWithPostInfo where Info: Eq + PartialEq + Clone + Copy + Encode + Decode + traits::Printable, @@ -517,8 +572,8 @@ impl DispatchError { /// Return the same error but without the attached message. pub fn stripped(self) -> Self { match self { - DispatchError::Module { index, error, message: Some(_) } => - DispatchError::Module { index, error, message: None }, + DispatchError::Module(ModuleError { index, error, message: Some(_) }) => + DispatchError::Module(ModuleError { index, error, message: None }), m => m, } } @@ -622,15 +677,21 @@ impl From<&'static str> for DispatchError { impl From for &'static str { fn from(err: DispatchError) -> &'static str { + use DispatchError::*; match err { - DispatchError::Other(msg) => msg, - DispatchError::CannotLookup => "Cannot lookup", - DispatchError::BadOrigin => "Bad origin", - DispatchError::Module { message, .. } => message.unwrap_or("Unknown module error"), - DispatchError::ConsumerRemaining => "Consumer remaining", - DispatchError::NoProviders => "No providers", - DispatchError::Token(e) => e.into(), - DispatchError::Arithmetic(e) => e.into(), + Other(msg) => msg, + CannotLookup => "Cannot lookup", + BadOrigin => "Bad origin", + Module(ModuleError { message, .. }) => message.unwrap_or("Unknown module error"), + ConsumerRemaining => "Consumer remaining", + NoProviders => "No providers", + TooManyConsumers => "Too many consumers", + Token(e) => e.into(), + Arithmetic(e) => e.into(), + Transactional(e) => e.into(), + Exhausted => "Resources exhausted", + Corruption => "State corrupt", + Unavailable => "Resource unavailable", } } } @@ -646,28 +707,37 @@ where impl traits::Printable for DispatchError { fn print(&self) { + use DispatchError::*; "DispatchError".print(); match self { - Self::Other(err) => err.print(), - Self::CannotLookup => "Cannot lookup".print(), - Self::BadOrigin => "Bad origin".print(), - Self::Module { index, error, message } => { + Other(err) => err.print(), + CannotLookup => "Cannot lookup".print(), + BadOrigin => "Bad origin".print(), + Module(ModuleError { index, error, message }) => { index.print(); error.print(); if let Some(msg) = message { msg.print(); } }, - Self::ConsumerRemaining => "Consumer remaining".print(), - Self::NoProviders => "No providers".print(), - Self::Token(e) => { + ConsumerRemaining => "Consumer remaining".print(), + NoProviders => "No providers".print(), + TooManyConsumers => "Too many consumers".print(), + Token(e) => { "Token error: ".print(); <&'static str>::from(*e).print(); }, - Self::Arithmetic(e) => { + Arithmetic(e) => { "Arithmetic error: ".print(); <&'static str>::from(*e).print(); }, + Transactional(e) => { + "Transactional error: ".print(); + <&'static str>::from(*e).print(); + }, + Exhausted => "Resources exhausted".print(), + Corruption => "State corrupt".print(), + Unavailable => "Resource unavailable".print(), } } } @@ -683,30 +753,6 @@ where } } -impl PartialEq for DispatchError { - fn eq(&self, other: &Self) -> bool { - use DispatchError::*; - - match (self, other) { - (CannotLookup, CannotLookup) | - (BadOrigin, BadOrigin) | - (ConsumerRemaining, ConsumerRemaining) | - (NoProviders, NoProviders) => true, - - (Token(l), Token(r)) => l == r, - (Other(l), Other(r)) => l == r, - (Arithmetic(l), Arithmetic(r)) => l == r, - - ( - Module { index: index_l, error: error_l, .. }, - Module { index: index_r, error: error_r, .. }, - ) => (index_l == index_r) && (error_l == error_r), - - _ => false, - } - } -} - /// This type specifies the outcome of dispatching a call to a module. /// /// In case of failure an error specific to the module is returned. @@ -790,7 +836,24 @@ pub fn verify_encoded_lazy( macro_rules! assert_eq_error_rate { ($x:expr, $y:expr, $error:expr $(,)?) => { assert!( - ($x) >= (($y) - ($error)) && ($x) <= (($y) + ($error)), + ($x >= $crate::Saturating::saturating_sub($y, $error)) && + ($x <= $crate::Saturating::saturating_add($y, $error)), + "{:?} != {:?} (with error rate {:?})", + $x, + $y, + $error, + ); + }; +} + +/// Same as [`assert_eq_error_rate`], but intended to be used with floating point number, or +/// generally those who do not have over/underflow potentials. +#[macro_export] +#[cfg(feature = "std")] +macro_rules! assert_eq_error_rate_float { + ($x:expr, $y:expr, $error:expr $(,)?) => { + assert!( + ($x >= $y - $error) && ($x <= $y + $error), "{:?} != {:?} (with error rate {:?})", $x, $y, @@ -801,7 +864,7 @@ macro_rules! assert_eq_error_rate { /// Simple blob to hold an extrinsic without committing to its format and ensure it is serialized /// correctly. -#[derive(PartialEq, Eq, Clone, Default, Encode, Decode)] +#[derive(PartialEq, Eq, Clone, Default, Encode, Decode, TypeInfo)] pub struct OpaqueExtrinsic(Vec); impl OpaqueExtrinsic { @@ -811,13 +874,6 @@ impl OpaqueExtrinsic { } } -#[cfg(feature = "std")] -impl parity_util_mem::MallocSizeOf for OpaqueExtrinsic { - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - self.0.size_of(ops) - } -} - impl sp_std::fmt::Debug for OpaqueExtrinsic { #[cfg(feature = "std")] fn fmt(&self, fmt: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { @@ -916,9 +972,13 @@ impl TransactionOutcome { #[cfg(test)] mod tests { + use crate::traits::BlakeTwo256; + use super::*; use codec::{Decode, Encode}; - use sp_core::crypto::Pair; + use sp_core::crypto::{Pair, UncheckedFrom}; + use sp_io::TestExternalities; + use sp_state_machine::create_proof_check_backend; #[test] fn opaque_extrinsic_serialization() { @@ -928,11 +988,18 @@ mod tests { #[test] fn dispatch_error_encoding() { - let error = DispatchError::Module { index: 1, error: 2, message: Some("error message") }; + let error = DispatchError::Module(ModuleError { + index: 1, + error: [2, 0, 0, 0], + message: Some("error message"), + }); let encoded = error.encode(); let decoded = DispatchError::decode(&mut &encoded[..]).unwrap(); - assert_eq!(encoded, vec![3, 1, 2]); - assert_eq!(decoded, DispatchError::Module { index: 1, error: 2, message: None }); + assert_eq!(encoded, vec![3, 1, 2, 0, 0, 0]); + assert_eq!( + decoded, + DispatchError::Module(ModuleError { index: 1, error: [2, 0, 0, 0], message: None }) + ); } #[test] @@ -944,9 +1011,9 @@ mod tests { Other("bar"), CannotLookup, BadOrigin, - Module { index: 1, error: 1, message: None }, - Module { index: 1, error: 2, message: None }, - Module { index: 2, error: 1, message: None }, + Module(ModuleError { index: 1, error: [1, 0, 0, 0], message: None }), + Module(ModuleError { index: 1, error: [2, 0, 0, 0], message: None }), + Module(ModuleError { index: 2, error: [1, 0, 0, 0], message: None }), ConsumerRemaining, NoProviders, Token(TokenError::NoFunds), @@ -971,8 +1038,8 @@ mod tests { // Ignores `message` field in `Module` variant. assert_eq!( - Module { index: 1, error: 1, message: Some("foo") }, - Module { index: 1, error: 1, message: None }, + Module(ModuleError { index: 1, error: [1, 0, 0, 0], message: Some("foo") }), + Module(ModuleError { index: 1, error: [1, 0, 0, 0], message: None }), ); } @@ -1002,7 +1069,9 @@ mod tests { ext.execute_with(|| { let _batching = SignatureBatching::start(); - sp_io::crypto::sr25519_verify(&Default::default(), &Vec::new(), &Default::default()); + let dummy = UncheckedFrom::unchecked_from([1; 32]); + let dummy_sig = UncheckedFrom::unchecked_from([1; 64]); + sp_io::crypto::sr25519_verify(&dummy_sig, &Vec::new(), &dummy); }); } @@ -1019,4 +1088,47 @@ mod tests { panic!("Hey, I'm an error"); }); } + + #[test] + fn execute_and_generate_proof_works() { + use codec::Encode; + use sp_state_machine::Backend; + let mut ext = TestExternalities::default(); + + ext.insert(b"a".to_vec(), vec![1u8; 33]); + ext.insert(b"b".to_vec(), vec![2u8; 33]); + ext.insert(b"c".to_vec(), vec![3u8; 33]); + ext.insert(b"d".to_vec(), vec![4u8; 33]); + + let pre_root = *ext.backend.root(); + let (_, proof) = ext.execute_and_prove(|| { + sp_io::storage::get(b"a"); + sp_io::storage::get(b"b"); + sp_io::storage::get(b"v"); + sp_io::storage::get(b"d"); + }); + + let compact_proof = proof.clone().into_compact_proof::(pre_root).unwrap(); + let compressed_proof = zstd::stream::encode_all(&compact_proof.encode()[..], 0).unwrap(); + + // just an example of how you'd inspect the size of the proof. + println!("proof size: {:?}", proof.encoded_size()); + println!("compact proof size: {:?}", compact_proof.encoded_size()); + println!("zstd-compressed compact proof size: {:?}", &compressed_proof.len()); + + // create a new trie-backed from the proof and make sure it contains everything + let proof_check = create_proof_check_backend::(pre_root, proof).unwrap(); + assert_eq!(proof_check.storage(b"a",).unwrap().unwrap(), vec![1u8; 33]); + + let _ = ext.execute_and_prove(|| { + sp_io::storage::set(b"a", &vec![1u8; 44]); + }); + + // ensure that these changes are propagated to the backend. + + ext.execute_with(|| { + assert_eq!(sp_io::storage::get(b"a").unwrap(), vec![1u8; 44]); + assert_eq!(sp_io::storage::get(b"b").unwrap(), vec![2u8; 33]); + }); + } } diff --git a/primitives/runtime/src/multiaddress.rs b/primitives/runtime/src/multiaddress.rs index 46d80608352dc..b2d46fb106cbc 100644 --- a/primitives/runtime/src/multiaddress.rs +++ b/primitives/runtime/src/multiaddress.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,9 +62,3 @@ impl From for MultiAddress Default for MultiAddress { - fn default() -> Self { - Self::Id(Default::default()) - } -} diff --git a/primitives/runtime/src/offchain/http.rs b/primitives/runtime/src/offchain/http.rs index 469f2fb5aff3a..dede4db5dd3de 100644 --- a/primitives/runtime/src/offchain/http.rs +++ b/primitives/runtime/src/offchain/http.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -452,7 +452,7 @@ impl Headers { let raw = name.as_bytes(); for &(ref key, ref val) in &self.raw { if &**key == raw { - return str::from_utf8(&val).ok() + return str::from_utf8(val).ok() } } None @@ -535,6 +535,38 @@ mod tests { }) } + #[test] + fn should_send_huge_response() { + let (offchain, state) = testing::TestOffchainExt::new(); + let mut t = TestExternalities::default(); + t.register_extension(OffchainWorkerExt::new(offchain)); + + t.execute_with(|| { + let request: Request = Request::get("http://localhost:1234"); + let pending = request.add_header("X-Auth", "hunter2").send().unwrap(); + // make sure it's sent correctly + state.write().fulfill_pending_request( + 0, + testing::PendingRequest { + method: "GET".into(), + uri: "http://localhost:1234".into(), + headers: vec![("X-Auth".into(), "hunter2".into())], + sent: true, + ..Default::default() + }, + vec![0; 5923], + None, + ); + + // wait + let response = pending.wait().unwrap(); + + let body = response.body(); + assert_eq!(body.clone().collect::>(), vec![0; 5923]); + assert_eq!(body.error(), &None); + }) + } + #[test] fn should_send_a_post_request() { let (offchain, state) = testing::TestOffchainExt::new(); diff --git a/primitives/runtime/src/offchain/mod.rs b/primitives/runtime/src/offchain/mod.rs index c9d1eda0f8738..bbff693dea038 100644 --- a/primitives/runtime/src/offchain/mod.rs +++ b/primitives/runtime/src/offchain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime/src/offchain/storage.rs b/primitives/runtime/src/offchain/storage.rs index 3bc5b10f161f7..38ad268e58945 100644 --- a/primitives/runtime/src/offchain/storage.rs +++ b/primitives/runtime/src/offchain/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime/src/offchain/storage_lock.rs b/primitives/runtime/src/offchain/storage_lock.rs index b4833bf345fc0..47325743bd2f3 100644 --- a/primitives/runtime/src/offchain/storage_lock.rs +++ b/primitives/runtime/src/offchain/storage_lock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -77,8 +77,8 @@ const STORAGE_LOCK_DEFAULT_EXPIRY_DURATION: Duration = Duration::from_millis(20_ const STORAGE_LOCK_DEFAULT_EXPIRY_BLOCKS: u32 = 4; /// Time between checks if the lock is still being held in milliseconds. -const STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MIN: Duration = Duration::from_millis(100); -const STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX: Duration = Duration::from_millis(10); +const STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MIN: Duration = Duration::from_millis(10); +const STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX: Duration = Duration::from_millis(100); /// Lockable item for use with a persisted storage lock. /// @@ -134,13 +134,12 @@ impl Lockable for Time { fn snooze(deadline: &Self::Deadline) { let now = offchain::timestamp(); - let remainder: Duration = now.diff(&deadline); + let remainder: Duration = now.diff(deadline); // do not snooze the full duration, but instead snooze max 100ms // it might get unlocked in another thread - use core::cmp::{max, min}; - let snooze = max( - min(remainder, STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX), + let snooze = remainder.clamp( STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MIN, + STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX, ); sp_io::offchain::sleep_until(now.add(snooze)); } @@ -239,10 +238,9 @@ impl Lockable for BlockAndTime { fn snooze(deadline: &Self::Deadline) { let now = offchain::timestamp(); let remainder: Duration = now.diff(&(deadline.timestamp)); - use core::cmp::{max, min}; - let snooze = max( - min(remainder, STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX), + let snooze = remainder.clamp( STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MIN, + STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX, ); sp_io::offchain::sleep_until(now.add(snooze)); } diff --git a/primitives/runtime/src/runtime_logger.rs b/primitives/runtime/src/runtime_logger.rs index ff0e531ed814f..8b5a15762e1f3 100644 --- a/primitives/runtime/src/runtime_logger.rs +++ b/primitives/runtime/src/runtime_logger.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime/src/runtime_string.rs b/primitives/runtime/src/runtime_string.rs index 179e881451813..762af0acd9250 100644 --- a/primitives/runtime/src/runtime_string.rs +++ b/primitives/runtime/src/runtime_string.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -92,6 +92,18 @@ impl AsRef<[u8]> for RuntimeString { } } +#[cfg(feature = "std")] +impl std::ops::Deref for RuntimeString { + type Target = str; + + fn deref(&self) -> &str { + match self { + Self::Borrowed(val) => val, + Self::Owned(val) => val, + } + } +} + impl Encode for RuntimeString { fn encode(&self) -> Vec { match self { diff --git a/primitives/runtime/src/testing.rs b/primitives/runtime/src/testing.rs index 781f342d43c1e..0cd78ba6267dd 100644 --- a/primitives/runtime/src/testing.rs +++ b/primitives/runtime/src/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Testing utilities. use crate::{ - codec::{Codec, Decode, Encode}, + codec::{Codec, Decode, Encode, MaxEncodedLen}, generic, scale_info::TypeInfo, traits::{ @@ -30,7 +30,7 @@ use crate::{ }; use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer}; use sp_core::{ - crypto::{key_types, CryptoType, Dummy, Public}, + crypto::{key_types, ByteArray, CryptoType, Dummy}, U256, }; pub use sp_core::{sr25519, H256}; @@ -59,6 +59,7 @@ use std::{ Deserialize, PartialOrd, Ord, + MaxEncodedLen, TypeInfo, )] pub struct UintAuthorityId(pub u64); @@ -76,10 +77,10 @@ impl From for u64 { } impl UintAuthorityId { - /// Convert this authority id into a public key. - pub fn to_public_key(&self) -> T { + /// Convert this authority ID into a public key. + pub fn to_public_key(&self) -> T { let bytes: [u8; 32] = U256::from(self.0).into(); - T::from_slice(&bytes) + T::from_slice(&bytes).unwrap() } } @@ -181,10 +182,10 @@ impl traits::Verify for TestSignature { } /// Digest item -pub type DigestItem = generic::DigestItem; +pub type DigestItem = generic::DigestItem; /// Header Digest -pub type Digest = generic::Digest; +pub type Digest = generic::Digest; /// Block Header pub type Header = generic::Header; @@ -203,13 +204,10 @@ impl Header { } /// An opaque extrinsic wrapper type. -#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode, parity_util_mem::MallocSizeOf)] +#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode)] pub struct ExtrinsicWrapper(Xt); -impl traits::Extrinsic for ExtrinsicWrapper -where - Xt: parity_util_mem::MallocSizeOf, -{ +impl traits::Extrinsic for ExtrinsicWrapper { type Call = (); type SignaturePayload = (); @@ -242,7 +240,7 @@ impl Deref for ExtrinsicWrapper { } /// Testing block -#[derive(PartialEq, Eq, Clone, Serialize, Debug, Encode, Decode, parity_util_mem::MallocSizeOf)] +#[derive(PartialEq, Eq, Clone, Serialize, Debug, Encode, Decode)] pub struct Block { /// Block header pub header: Header, @@ -305,9 +303,6 @@ impl TestXt { } } -// Non-opaque extrinsics always 0. -parity_util_mem::malloc_size_of_is_0!(any: TestXt); - impl Serialize for TestXt where TestXt: Encode, @@ -357,8 +352,15 @@ where impl Applyable for TestXt where - Call: - 'static + Sized + Send + Sync + Clone + Eq + Codec + Debug + Dispatchable, + Call: 'static + + Sized + + Send + + Sync + + Clone + + Eq + + Codec + + Debug + + Dispatchable, Extra: SignedExtension, Origin: From>, { diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 65c063fde1696..c69f8616b4be5 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ use crate::{ codec::{Codec, Decode, Encode, MaxEncodedLen}, - generic::{Digest, DigestItem}, + generic::Digest, scale_info::{MetaType, StaticTypeInfo, TypeInfo}, transaction_validity::{ TransactionSource, TransactionValidity, TransactionValidityError, UnknownTransaction, @@ -36,14 +36,15 @@ pub use sp_arithmetic::traits::{ CheckedShr, CheckedSub, IntegerSquareRoot, One, SaturatedConversion, Saturating, UniqueSaturatedFrom, UniqueSaturatedInto, Zero, }; -use sp_core::{self, Hasher, RuntimeDebug, TypeId}; -use sp_std::{ - self, - convert::{TryFrom, TryInto}, - fmt::Debug, - marker::PhantomData, - prelude::*, +use sp_core::{self, storage::StateVersion, Hasher, RuntimeDebug, TypeId}; +#[doc(hidden)] +pub use sp_core::{ + parameter_types, ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, + ConstU16, ConstU32, ConstU64, ConstU8, Get, GetDefault, TryCollect, TypedGet, }; +#[doc(hidden)] +pub use sp_std::marker::PhantomData; +use sp_std::{self, fmt::Debug, prelude::*}; #[cfg(feature = "std")] use std::fmt::Display; #[cfg(feature = "std")] @@ -59,7 +60,7 @@ pub trait Lazy { impl<'a> Lazy<[u8]> for &'a [u8] { fn get(&mut self) -> &[u8] { - &**self + self } } @@ -165,7 +166,7 @@ where use sp_application_crypto::IsWrappedBy; let inner: &S = self.as_ref(); let inner_pubkey = - <::Public as sp_application_crypto::AppPublic>::Generic::from_ref(&signer); + <::Public as sp_application_crypto::AppPublic>::Generic::from_ref(signer); Verify::verify(inner, msg, inner_pubkey) } } @@ -280,6 +281,221 @@ where } } +/// Extensible conversion trait. Generic over only source type, with destination type being +/// associated. +pub trait Morph
{ + /// The type into which `A` is mutated. + type Outcome; + + /// Make conversion. + fn morph(a: A) -> Self::Outcome; +} + +/// A structure that performs identity conversion. +impl Morph for Identity { + type Outcome = T; + fn morph(a: T) -> T { + a + } +} + +/// Extensible conversion trait. Generic over only source type, with destination type being +/// associated. +pub trait TryMorph { + /// The type into which `A` is mutated. + type Outcome; + + /// Make conversion. + fn try_morph(a: A) -> Result; +} + +/// A structure that performs identity conversion. +impl TryMorph for Identity { + type Outcome = T; + fn try_morph(a: T) -> Result { + Ok(a) + } +} + +/// Create a `Morph` and/or `TryMorph` impls with a simple closure-like expression. +/// +/// # Examples +/// +/// ``` +/// # use sp_runtime::{morph_types, traits::{Morph, TryMorph, TypedGet, ConstU32}}; +/// # use sp_arithmetic::traits::CheckedSub; +/// +/// morph_types! { +/// /// Replace by some other value; produce both `Morph` and `TryMorph` implementations +/// pub type Replace = |_| -> V::Type { V::get() }; +/// /// A private `Morph` implementation to reduce a `u32` by 10. +/// type ReduceU32ByTen: Morph = |r: u32| -> u32 { r - 10 }; +/// /// A `TryMorph` implementation to reduce a scalar by a particular amount, checking for +/// /// underflow. +/// pub type CheckedReduceBy: TryMorph = |r: N::Type| -> Result { +/// r.checked_sub(&N::get()).ok_or(()) +/// } where N::Type: CheckedSub; +/// } +/// +/// trait Config { +/// type TestMorph1: Morph; +/// type TestTryMorph1: TryMorph; +/// type TestMorph2: Morph; +/// type TestTryMorph2: TryMorph; +/// } +/// +/// struct Runtime; +/// impl Config for Runtime { +/// type TestMorph1 = Replace>; +/// type TestTryMorph1 = Replace>; +/// type TestMorph2 = ReduceU32ByTen; +/// type TestTryMorph2 = CheckedReduceBy>; +/// } +/// ``` +#[macro_export] +macro_rules! morph_types { + ( + @DECL $( #[doc = $doc:expr] )* $vq:vis $name:ident () + ) => { + $( #[doc = $doc] )* $vq struct $name; + }; + ( + @DECL $( #[doc = $doc:expr] )* $vq:vis $name:ident ( $( $bound_id:ident ),+ ) + ) => { + $( #[doc = $doc] )* + $vq struct $name < $($bound_id,)* > ( $crate::traits::PhantomData< ( $($bound_id,)* ) > ) ; + }; + ( + @IMPL $name:ty : ( $( $bounds:tt )* ) ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl<$($bounds)*> $crate::traits::Morph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn morph($var: $var_type) -> Self::Outcome { $( $ex )* } + } + }; + ( + @IMPL_TRY $name:ty : ( $( $bounds:tt )* ) ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl<$($bounds)*> $crate::traits::TryMorph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn try_morph($var: $var_type) -> Result { $( $ex )* } + } + }; + ( + @IMPL $name:ty : () ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl $crate::traits::Morph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn morph($var: $var_type) -> Self::Outcome { $( $ex )* } + } + }; + ( + @IMPL_TRY $name:ty : () ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl $crate::traits::TryMorph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn try_morph($var: $var_type) -> Result { $( $ex )* } + } + }; + ( + @IMPL_BOTH $name:ty : ( $( $bounds:tt )* ) ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + morph_types! { + @IMPL $name : ($($bounds)*) ($($where)*) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types! { + @IMPL_TRY $name : ($($bounds)*) ($($where)*) + = |$var: $var_type| -> $outcome { Ok({$( $ex )*}) } + } + }; + + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $(: $type:tt)? + = |_| -> $outcome:ty { $( $ex:expr )* }; + $( $rest:tt )* + ) => { + morph_types! { + $( #[doc = $doc] )* $vq type $name + $( < $( $bound_id $( : $bound_head $( | $bound_tail )* )? ),+ > )? + EXTRA_GENERIC(X) + $(: $type)? + = |_x: X| -> $outcome { $( $ex )* }; + $( $rest )* + } + }; + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $( EXTRA_GENERIC ($extra:ident) )? + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + $( where $( $where_path:ty : $where_bound_head:path $( | $where_bound_tail:path )* ),* )?; + $( $rest:tt )* + ) => { + morph_types! { @DECL $( #[doc = $doc] )* $vq $name ( $( $( $bound_id ),+ )? ) } + morph_types! { + @IMPL_BOTH $name $( < $( $bound_id ),* > )? : + ( $( $( $bound_id $( : $bound_head $( + $bound_tail )* )? , )+ )? $( $extra )? ) + ( $( where $( $where_path : $where_bound_head $( + $where_bound_tail )* ),* )? ) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types!{ $($rest)* } + }; + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $( EXTRA_GENERIC ($extra:ident) )? + : Morph + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + $( where $( $where_path:ty : $where_bound_head:path $( | $where_bound_tail:path )* ),* )?; + $( $rest:tt )* + ) => { + morph_types! { @DECL $( #[doc = $doc] )* $vq $name ( $( $( $bound_id ),+ )? ) } + morph_types! { + @IMPL $name $( < $( $bound_id ),* > )? : + ( $( $( $bound_id $( : $bound_head $( + $bound_tail )* )? , )+ )? $( $extra )? ) + ( $( where $( $where_path : $where_bound_head $( + $where_bound_tail )* ),* )? ) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types!{ $($rest)* } + }; + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $( EXTRA_GENERIC ($extra:ident) )? + : TryMorph + = |$var:ident: $var_type:ty| -> Result<$outcome:ty, ()> { $( $ex:expr )* } + $( where $( $where_path:ty : $where_bound_head:path $( | $where_bound_tail:path )* ),* )?; + $( $rest:tt )* + ) => { + morph_types! { @DECL $( #[doc = $doc] )* $vq $name ( $( $( $bound_id ),+ )? ) } + morph_types! { + @IMPL_TRY $name $( < $( $bound_id ),* > )? : + ( $( $( $bound_id $( : $bound_head $( + $bound_tail )* )? , )+ )? $( $extra )? ) + ( $( where $( $where_path : $where_bound_head $( + $where_bound_tail )* ),* )? ) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types!{ $($rest)* } + }; + () => {} +} + +morph_types! { + /// Morpher to disregard the source value and replace with another. + pub type Replace = |_| -> V::Type { V::get() }; + /// Mutator which reduces a scalar by a particular amount. + pub type ReduceBy = |r: N::Type| -> N::Type { + r.checked_sub(&N::get()).unwrap_or(Zero::zero()) + } where N::Type: CheckedSub | Zero; +} + /// Extensible conversion trait. Generic over both source and destination types. pub trait Convert { /// Make conversion. @@ -299,6 +515,11 @@ impl Convert for Identity { a } } +impl ConvertBack for Identity { + fn convert_back(a: T) -> T { + a + } +} /// A structure that performs standard conversion using the standard Rust conversion traits. pub struct ConvertInto; @@ -308,6 +529,12 @@ impl> Convert for ConvertInto { } } +/// Extensible conversion trait. Generic over both source and destination types. +pub trait ConvertBack: Convert { + /// Make conversion back. + fn convert_back(b: B) -> A; +} + /// Convenience type to work around the highly unergonomic syntax needed /// to invoke the functions of overloaded generic traits, in this case /// `TryFrom` and `TryInto`. @@ -459,10 +686,10 @@ pub trait Hash: } /// The ordered Patricia tree root of the given `input`. - fn ordered_trie_root(input: Vec>) -> Self::Output; + fn ordered_trie_root(input: Vec>, state_version: StateVersion) -> Self::Output; /// The Patricia tree root of the given mapping. - fn trie_root(input: Vec<(Vec, Vec)>) -> Self::Output; + fn trie_root(input: Vec<(Vec, Vec)>, state_version: StateVersion) -> Self::Output; } /// Blake2-256 Hash implementation. @@ -483,12 +710,12 @@ impl Hasher for BlakeTwo256 { impl Hash for BlakeTwo256 { type Output = sp_core::H256; - fn trie_root(input: Vec<(Vec, Vec)>) -> Self::Output { - sp_io::trie::blake2_256_root(input) + fn trie_root(input: Vec<(Vec, Vec)>, version: StateVersion) -> Self::Output { + sp_io::trie::blake2_256_root(input, version) } - fn ordered_trie_root(input: Vec>) -> Self::Output { - sp_io::trie::blake2_256_ordered_root(input) + fn ordered_trie_root(input: Vec>, version: StateVersion) -> Self::Output { + sp_io::trie::blake2_256_ordered_root(input, version) } } @@ -510,12 +737,12 @@ impl Hasher for Keccak256 { impl Hash for Keccak256 { type Output = sp_core::H256; - fn trie_root(input: Vec<(Vec, Vec)>) -> Self::Output { - sp_io::trie::keccak_256_root(input) + fn trie_root(input: Vec<(Vec, Vec)>, version: StateVersion) -> Self::Output { + sp_io::trie::keccak_256_root(input, version) } - fn ordered_trie_root(input: Vec>) -> Self::Output { - sp_io::trie::keccak_256_ordered_root(input) + fn ordered_trie_root(input: Vec>, version: StateVersion) -> Self::Output { + sp_io::trie::keccak_256_ordered_root(input, version) } } @@ -548,10 +775,7 @@ impl CheckEqual for sp_core::H256 { } } -impl CheckEqual for super::generic::DigestItem -where - H: Encode, -{ +impl CheckEqual for super::generic::DigestItem { #[cfg(feature = "std")] fn check_equal(&self, other: &Self) { if self != other { @@ -584,9 +808,6 @@ sp_core::impl_maybe_marker!( /// A type that implements Serialize, DeserializeOwned and Debug when in std environment. trait MaybeSerializeDeserialize: DeserializeOwned, Serialize; - - /// A type that implements MallocSizeOf. - trait MaybeMallocSizeOf: parity_util_mem::MallocSizeOf; ); /// A type that can be used in runtime structures. @@ -604,9 +825,7 @@ pub trait IsMember { /// `parent_hash`, as well as a `digest` and a block `number`. /// /// You can also create a `new` one from those fields. -pub trait Header: - Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + MaybeMallocSizeOf + 'static -{ +pub trait Header: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + 'static { /// Header number. type Number: Member + MaybeSerializeDeserialize @@ -616,8 +835,7 @@ pub trait Header: + MaybeDisplay + AtLeast32BitUnsigned + Codec - + sp_std::str::FromStr - + MaybeMallocSizeOf; + + sp_std::str::FromStr; /// Header hash type type Hash: Member + MaybeSerializeDeserialize @@ -631,7 +849,6 @@ pub trait Header: + Codec + AsRef<[u8]> + AsMut<[u8]> - + MaybeMallocSizeOf + TypeInfo; /// Hashing algorithm type Hashing: Hash; @@ -642,7 +859,7 @@ pub trait Header: extrinsics_root: Self::Hash, state_root: Self::Hash, parent_hash: Self::Hash, - digest: Digest, + digest: Digest, ) -> Self; /// Returns a reference to the header number. @@ -666,9 +883,9 @@ pub trait Header: fn set_parent_hash(&mut self, hash: Self::Hash); /// Returns a reference to the digest. - fn digest(&self) -> &Digest; + fn digest(&self) -> &Digest; /// Get a mutable reference to the digest. - fn digest_mut(&mut self) -> &mut Digest; + fn digest_mut(&mut self) -> &mut Digest; /// Returns the hash of the header. fn hash(&self) -> Self::Hash { @@ -680,13 +897,11 @@ pub trait Header: /// `Extrinsic` pieces of information as well as a `Header`. /// /// You can get an iterator over each of the `extrinsics` and retrieve the `header`. -pub trait Block: - Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + MaybeMallocSizeOf + 'static -{ +pub trait Block: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + 'static { /// Type for extrinsics. - type Extrinsic: Member + Codec + Extrinsic + MaybeSerialize + MaybeMallocSizeOf; + type Extrinsic: Member + Codec + Extrinsic + MaybeSerialize; /// Header type. - type Header: Header + MaybeMallocSizeOf; + type Header: Header; /// Block hash type. type Hash: Member + MaybeSerializeDeserialize @@ -700,7 +915,6 @@ pub trait Block: + Codec + AsRef<[u8]> + AsMut<[u8]> - + MaybeMallocSizeOf + TypeInfo; /// Returns a reference to the header. @@ -721,7 +935,7 @@ pub trait Block: } /// Something that acts like an `Extrinsic`. -pub trait Extrinsic: Sized + MaybeMallocSizeOf { +pub trait Extrinsic: Sized { /// The function call. type Call; @@ -751,7 +965,9 @@ pub trait Extrinsic: Sized + MaybeMallocSizeOf { /// Implementor is an [`Extrinsic`] and provides metadata about this extrinsic. pub trait ExtrinsicMetadata { - /// The version of the `Extrinsic`. + /// The format version of the `Extrinsic`. + /// + /// By format is meant the encoded representation of the `Extrinsic`. const VERSION: u8; /// Signed extensions attached to this `Extrinsic`. @@ -763,9 +979,6 @@ pub type HashFor = <::Header as Header>::Hashing; /// Extract the number type for a block. pub type NumberFor = <::Header as Header>::Number; /// Extract the digest type for a block. -pub type DigestFor = Digest<<::Header as Header>::Hash>; -/// Extract the digest item type for a block. -pub type DigestItemFor = DigestItem<<::Header as Header>::Hash>; /// A "checkable" piece of information, used by the standard Substrate Executive in order to /// check the validity of a piece of extrinsic information, usually by verifying the signature. @@ -806,7 +1019,7 @@ pub trait Dispatchable { /// Every function call from your runtime has an origin, which specifies where the extrinsic was /// generated from. In the case of a signed extrinsic (transaction), the origin contains an /// identifier for the caller. The origin can be empty in the case of an inherent extrinsic. - type Origin; + type RuntimeOrigin; /// ... type Config; /// An opaque set of information attached to the transaction. This could be constructed anywhere @@ -817,7 +1030,8 @@ pub trait Dispatchable { /// with information about a `Dispatchable` that is ownly known post dispatch. type PostInfo: Eq + PartialEq + Clone + Copy + Encode + Decode + Printable; /// Actually dispatch this call and return the result of it. - fn dispatch(self, origin: Self::Origin) -> crate::DispatchResultWithInfo; + fn dispatch(self, origin: Self::RuntimeOrigin) + -> crate::DispatchResultWithInfo; } /// Shortcut to reference the `Info` type of a `Dispatchable`. @@ -826,12 +1040,15 @@ pub type DispatchInfoOf = ::Info; pub type PostDispatchInfoOf = ::PostInfo; impl Dispatchable for () { - type Origin = (); + type RuntimeOrigin = (); type Config = (); type Info = (); type PostInfo = (); - fn dispatch(self, _origin: Self::Origin) -> crate::DispatchResultWithInfo { - panic!("This implemention should not be used for actual dispatch."); + fn dispatch( + self, + _origin: Self::RuntimeOrigin, + ) -> crate::DispatchResultWithInfo { + panic!("This implementation should not be used for actual dispatch."); } } @@ -857,7 +1074,7 @@ pub trait SignedExtension: type AdditionalSigned: Encode + TypeInfo; /// The type that encodes information that can be passed from pre_dispatch to post-dispatch. - type Pre: Default; + type Pre; /// Construct any additional data that should be in the signed payload of the transaction. Can /// also perform any pre-signature-verification checks and return an error if needed. @@ -884,23 +1101,14 @@ pub trait SignedExtension: /// Do any pre-flight stuff for a signed transaction. /// - /// Note this function by default delegates to `validate`, so that - /// all checks performed for the transaction queue are also performed during - /// the dispatch phase (applying the extrinsic). - /// - /// If you ever override this function, you need to make sure to always - /// perform the same validation as in `validate`. + /// Make sure to perform the same checks as in [`Self::validate`]. fn pre_dispatch( self, who: &Self::AccountId, call: &Self::Call, info: &DispatchInfoOf, len: usize, - ) -> Result { - self.validate(who, call, info, len) - .map(|_| Self::Pre::default()) - .map_err(Into::into) - } + ) -> Result; /// Validate an unsigned transaction for the transaction queue. /// @@ -930,14 +1138,15 @@ pub trait SignedExtension: call: &Self::Call, info: &DispatchInfoOf, len: usize, - ) -> Result { - Self::validate_unsigned(call, info, len) - .map(|_| Self::Pre::default()) - .map_err(Into::into) + ) -> Result<(), TransactionValidityError> { + Self::validate_unsigned(call, info, len).map(|_| ()).map_err(Into::into) } /// Do any post-flight stuff for an extrinsic. /// + /// If the transaction is signed, then `_pre` will contain the output of `pre_dispatch`, + /// and `None` otherwise. + /// /// This gets given the `DispatchResult` `_result` from the extrinsic and can, if desired, /// introduce a `TransactionValidityError`, causing the block to become invalid for including /// it. @@ -950,7 +1159,7 @@ pub trait SignedExtension: /// introduced by the current block author; generally this implies that it is an inherent and /// will come from either an offchain-worker or via `InherentData`. fn post_dispatch( - _pre: Self::Pre, + _pre: Option, _info: &DispatchInfoOf, _post_info: &PostDispatchInfoOf, _len: usize, @@ -1035,18 +1244,26 @@ impl SignedExtension for Tuple { call: &Self::Call, info: &DispatchInfoOf, len: usize, - ) -> Result { - Ok(for_tuples!( ( #( Tuple::pre_dispatch_unsigned(call, info, len)? ),* ) )) + ) -> Result<(), TransactionValidityError> { + for_tuples!( #( Tuple::pre_dispatch_unsigned(call, info, len)?; )* ); + Ok(()) } fn post_dispatch( - pre: Self::Pre, + pre: Option, info: &DispatchInfoOf, post_info: &PostDispatchInfoOf, len: usize, result: &DispatchResult, ) -> Result<(), TransactionValidityError> { - for_tuples!( #( Tuple::post_dispatch(pre.Tuple, info, post_info, len, result)?; )* ); + match pre { + Some(x) => { + for_tuples!( #( Tuple::post_dispatch(Some(x.Tuple), info, post_info, len, result)?; )* ); + }, + None => { + for_tuples!( #( Tuple::post_dispatch(None, info, post_info, len, result)?; )* ); + }, + } Ok(()) } @@ -1068,6 +1285,15 @@ impl SignedExtension for () { fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) } + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } } /// An "executable" piece of information, used by the standard Substrate Executive in order to @@ -1109,12 +1335,13 @@ pub trait GetNodeBlockType { type NodeBlock: self::Block; } -/// Something that can validate unsigned extrinsics for the transaction pool. +/// Provide validation for unsigned extrinsics. /// -/// Note that any checks done here are only used for determining the validity of -/// the transaction for the transaction pool. -/// During block execution phase one need to perform the same checks anyway, -/// since this function is not being called. +/// This trait provides two functions [`pre_dispatch`](Self::pre_dispatch) and +/// [`validate_unsigned`](Self::validate_unsigned). The [`pre_dispatch`](Self::pre_dispatch) +/// function is called right before dispatching the call wrapped by an unsigned extrinsic. The +/// [`validate_unsigned`](Self::validate_unsigned) function is mainly being used in the context of +/// the transaction pool to check the validity of the call wrapped by an unsigned extrinsic. pub trait ValidateUnsigned { /// The call to validate type Call; @@ -1122,13 +1349,15 @@ pub trait ValidateUnsigned { /// Validate the call right before dispatch. /// /// This method should be used to prevent transactions already in the pool - /// (i.e. passing `validate_unsigned`) from being included in blocks - /// in case we know they now became invalid. + /// (i.e. passing [`validate_unsigned`](Self::validate_unsigned)) from being included in blocks + /// in case they became invalid since being added to the pool. /// - /// By default it's a good idea to call `validate_unsigned` from within - /// this function again to make sure we never include an invalid transaction. + /// By default it's a good idea to call [`validate_unsigned`](Self::validate_unsigned) from + /// within this function again to make sure we never include an invalid transaction. Otherwise + /// the implementation of the call or this method will need to provide proper validation to + /// ensure that the transaction is valid. /// - /// Changes made to storage WILL be persisted if the call returns `Ok`. + /// Changes made to storage *WILL* be persisted if the call returns `Ok`. fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { Self::validate_unsigned(TransactionSource::InBlock, call) .map(|_| ()) @@ -1137,8 +1366,14 @@ pub trait ValidateUnsigned { /// Return the validity of the call /// - /// This doesn't execute any side-effects; it merely checks - /// whether the transaction would panic if it were included or not. + /// This method has no side-effects. It merely checks whether the call would be rejected + /// by the runtime in an unsigned extrinsic. + /// + /// The validity checks should be as lightweight as possible because every node will execute + /// this code before the unsigned extrinsic enters the transaction pool and also periodically + /// afterwards to ensure the validity. To prevent dos-ing a network with unsigned + /// extrinsics, these validity checks should include some checks around uniqueness, for example, + /// like checking that the unsigned extrinsic was send by an authority in the active set. /// /// Changes made to storage should be discarded by caller. fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity; @@ -1218,6 +1453,11 @@ impl<'a> TrailingZeroInput<'a> { pub fn new(data: &'a [u8]) -> Self { Self(data) } + + /// Create a new instance which only contains zeroes as input. + pub fn zeroes() -> Self { + Self::new(&[][..]) + } } impl<'a> codec::Input for TrailingZeroInput<'a> { @@ -1239,9 +1479,16 @@ impl<'a> codec::Input for TrailingZeroInput<'a> { /// This type can be converted into and possibly from an AccountId (which itself is generic). pub trait AccountIdConversion: Sized { - /// Convert into an account ID. This is infallible. - fn into_account(&self) -> AccountId { - self.into_sub_account(&()) + /// Convert into an account ID. This is infallible, and may truncate bytes to provide a result. + /// This may lead to duplicate accounts if the size of `AccountId` is less than the seed. + fn into_account_truncating(&self) -> AccountId { + self.into_sub_account_truncating(&()) + } + + /// Convert into an account ID, checking that all bytes of the seed are being used in the final + /// `AccountId` generated. If any bytes are dropped, this returns `None`. + fn try_into_account(&self) -> Option { + self.try_into_sub_account(&()) } /// Try to convert an account ID into this type. Might not succeed. @@ -1249,8 +1496,8 @@ pub trait AccountIdConversion: Sized { Self::try_from_sub_account::<()>(a).map(|x| x.0) } - /// Convert this value amalgamated with the a secondary "sub" value into an account ID. This is - /// infallible. + /// Convert this value amalgamated with the a secondary "sub" value into an account ID, + /// truncating any unused bytes. This is infallible. /// /// NOTE: The account IDs from this and from `into_account` are *not* guaranteed to be distinct /// for any given value of `self`, nor are different invocations to this with different types @@ -1258,24 +1505,50 @@ pub trait AccountIdConversion: Sized { /// - `self.into_sub_account(0u32)` /// - `self.into_sub_account(vec![0u8; 0])` /// - `self.into_account()` - fn into_sub_account(&self, sub: S) -> AccountId; + /// + /// Also, if the seed provided to this function is greater than the number of bytes which fit + /// into this `AccountId` type, then it will lead to truncation of the seed, and potentially + /// non-unique accounts. + fn into_sub_account_truncating(&self, sub: S) -> AccountId; + + /// Same as `into_sub_account_truncating`, but ensuring that all bytes of the account's seed are + /// used when generating an account. This can help guarantee that different accounts are unique, + /// besides types which encode the same as noted above. + fn try_into_sub_account(&self, sub: S) -> Option; /// Try to convert an account ID into this type. Might not succeed. fn try_from_sub_account(x: &AccountId) -> Option<(Self, S)>; } -/// Format is TYPE_ID ++ encode(parachain ID) ++ 00.... where 00... is indefinite trailing zeroes to +/// Format is TYPE_ID ++ encode(sub-seed) ++ 00.... where 00... is indefinite trailing zeroes to /// fill AccountId. -impl AccountIdConversion for Id { - fn into_sub_account(&self, sub: S) -> T { +impl AccountIdConversion for Id { + // Take the `sub` seed, and put as much of it as possible into the generated account, but + // allowing truncation of the seed if it would not fit into the account id in full. This can + // lead to two different `sub` seeds with the same account generated. + fn into_sub_account_truncating(&self, sub: S) -> T { (Id::TYPE_ID, self, sub) .using_encoded(|b| T::decode(&mut TrailingZeroInput(b))) - .unwrap_or_default() + .expect("All byte sequences are valid `AccountIds`; qed") + } + + // Same as `into_sub_account_truncating`, but returns `None` if any bytes would be truncated. + fn try_into_sub_account(&self, sub: S) -> Option { + let encoded_seed = (Id::TYPE_ID, self, sub).encode(); + let account = T::decode(&mut TrailingZeroInput(&encoded_seed)) + .expect("All byte sequences are valid `AccountIds`; qed"); + // If the `account` generated has less bytes than the `encoded_seed`, then we know that + // bytes were truncated, and we return `None`. + if encoded_seed.len() <= account.encoded_size() { + Some(account) + } else { + None + } } fn try_from_sub_account(x: &T) -> Option<(Self, S)> { x.using_encoded(|d| { - if &d[0..4] != Id::TYPE_ID { + if d[0..4] != Id::TYPE_ID { return None } let mut cursor = &d[4..]; @@ -1323,7 +1596,7 @@ macro_rules! impl_opaque_keys_inner { ) => { $( #[ $attr ] )* #[derive( - Default, Clone, PartialEq, Eq, + Clone, PartialEq, Eq, $crate::codec::Encode, $crate::codec::Decode, $crate::scale_info::TypeInfo, @@ -1474,6 +1747,7 @@ macro_rules! impl_opaque_keys { #[macro_export] #[cfg(not(feature = "std"))] +#[doc(hidden)] macro_rules! impl_opaque_keys { { $( #[ $attr:meta ] )* @@ -1538,6 +1812,12 @@ impl Printable for &[u8] { } } +impl Printable for [u8; N] { + fn print(&self) { + sp_io::misc::print_hex(&self[..]); + } +} + impl Printable for &str { fn print(&self) { sp_io::misc::print_utf8(self.as_bytes()); @@ -1554,6 +1834,12 @@ impl Printable for bool { } } +impl Printable for sp_weights::Weight { + fn print(&self) { + self.ref_time().print() + } +} + impl Printable for () { fn print(&self) { "()".print() @@ -1571,7 +1857,7 @@ impl Printable for Tuple { #[cfg(feature = "std")] pub trait BlockIdTo { /// The error type that will be returned by the functions. - type Error: std::fmt::Debug; + type Error: std::error::Error; /// Convert the given `block_id` to the corresponding block hash. fn to_hash( @@ -1621,7 +1907,10 @@ pub trait BlockNumberProvider { mod tests { use super::*; use crate::codec::{Decode, Encode, Input}; - use sp_core::{crypto::Pair, ecdsa}; + use sp_core::{ + crypto::{Pair, UncheckedFrom}, + ecdsa, + }; mod t { use sp_application_crypto::{app_crypto, sr25519}; @@ -1634,10 +1923,17 @@ mod tests { use super::AppVerify; use t::*; - let s = Signature::default(); - let _ = s.verify(&[0u8; 100][..], &Public::default()); + let s = Signature::try_from(vec![0; 64]).unwrap(); + let _ = s.verify(&[0u8; 100][..], &Public::unchecked_from([0; 32])); } + #[derive(Encode, Decode, Default, PartialEq, Debug)] + struct U128Value(u128); + impl super::TypeId for U128Value { + const TYPE_ID: [u8; 4] = [0x0d, 0xf0, 0x0d, 0xf0]; + } + // f00df00d + #[derive(Encode, Decode, Default, PartialEq, Debug)] struct U32Value(u32); impl super::TypeId for U32Value { @@ -1655,9 +1951,19 @@ mod tests { type AccountId = u64; #[test] - fn into_account_should_work() { - let r: AccountId = U32Value::into_account(&U32Value(0xdeadbeef)); + fn into_account_truncating_should_work() { + let r: AccountId = U32Value::into_account_truncating(&U32Value(0xdeadbeef)); + assert_eq!(r, 0x_deadbeef_cafef00d); + } + + #[test] + fn try_into_account_should_work() { + let r: AccountId = U32Value::try_into_account(&U32Value(0xdeadbeef)).unwrap(); assert_eq!(r, 0x_deadbeef_cafef00d); + + // u128 is bigger than u64 would fit + let maybe: Option = U128Value::try_into_account(&U128Value(u128::MAX)); + assert!(maybe.is_none()); } #[test] @@ -1667,11 +1973,24 @@ mod tests { } #[test] - fn into_account_with_fill_should_work() { - let r: AccountId = U16Value::into_account(&U16Value(0xc0da)); + fn into_account_truncating_with_fill_should_work() { + let r: AccountId = U16Value::into_account_truncating(&U16Value(0xc0da)); assert_eq!(r, 0x_0000_c0da_f00dcafe); } + #[test] + fn try_into_sub_account_should_work() { + let r: AccountId = U16Value::try_into_account(&U16Value(0xc0da)).unwrap(); + assert_eq!(r, 0x_0000_c0da_f00dcafe); + + let maybe: Option = U16Value::try_into_sub_account( + &U16Value(0xc0da), + "a really large amount of additional encoded information which will certainly overflow the account id type ;)" + ); + + assert!(maybe.is_none()) + } + #[test] fn try_from_account_with_fill_should_work() { let r = U16Value::try_from_account(&0x0000_c0da_f00dcafe_u64); diff --git a/primitives/runtime/src/transaction_validity.rs b/primitives/runtime/src/transaction_validity.rs index e114bb5985460..3b89644c28647 100644 --- a/primitives/runtime/src/transaction_validity.rs +++ b/primitives/runtime/src/transaction_validity.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,7 @@ use crate::{ codec::{Decode, Encode}, RuntimeDebug, }; +use scale_info::TypeInfo; use sp_std::prelude::*; /// Priority for a transaction. Additive. Higher is better. @@ -34,7 +35,7 @@ pub type TransactionLongevity = u64; pub type TransactionTag = Vec; /// An invalid transaction validity. -#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum InvalidTransaction { /// The call of the transaction is not expected. @@ -76,9 +77,11 @@ pub enum InvalidTransaction { /// malicious validator or a buggy `provide_inherent`. In any case, it can result in /// dangerously overweight blocks and therefore if found, invalidates the block. BadMandatory, - /// A transaction with a mandatory dispatch. This is invalid; only inherent extrinsics are - /// allowed to have mandatory dispatches. - MandatoryDispatch, + /// An extrinsic with a mandatory dispatch tried to be validated. + /// This is invalid; only inherent extrinsics are allowed to have mandatory dispatches. + MandatoryValidation, + /// The sending address is disabled or known to be invalid. + BadSigner, } impl InvalidTransaction { @@ -106,15 +109,16 @@ impl From for &'static str { "Inability to pay some fees (e.g. account balance too low)", InvalidTransaction::BadMandatory => "A call was labelled as mandatory, but resulted in an Error.", - InvalidTransaction::MandatoryDispatch => - "Transaction dispatch is mandatory; transactions may not have mandatory dispatches.", + InvalidTransaction::MandatoryValidation => + "Transaction dispatch is mandatory; transactions must not be validated.", InvalidTransaction::Custom(_) => "InvalidTransaction custom error", + InvalidTransaction::BadSigner => "Invalid signing address", } } } /// An unknown transaction validity. -#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum UnknownTransaction { /// Could not lookup some information that is required to validate the transaction. @@ -138,7 +142,7 @@ impl From for &'static str { } /// Errors that can occur while checking the validity of a transaction. -#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum TransactionValidityError { /// The transaction is invalid. @@ -222,9 +226,7 @@ impl From for TransactionValidity { /// Depending on the source we might apply different validation schemes. /// For instance we can disallow specific kinds of transactions if they were not produced /// by our local node (for instance off-chain workers). -#[derive( - Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, parity_util_mem::MallocSizeOf, -)] +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] pub enum TransactionSource { /// Transaction is already included in block. /// diff --git a/primitives/sandbox/Cargo.toml b/primitives/sandbox/Cargo.toml deleted file mode 100755 index a4d4a4d5d031a..0000000000000 --- a/primitives/sandbox/Cargo.toml +++ /dev/null @@ -1,46 +0,0 @@ -[package] -name = "sp-sandbox" -version = "0.10.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "This crate provides means to instantiate and execute wasm modules." -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[target.'cfg(target_arch = "wasm32")'.dependencies] -wasmi = { version = "0.9.0", default-features = false, features = ["core"] } - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -wasmi = "0.9.0" - -[dependencies] -wasmi = { version = "0.9.0", optional = true } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../io" } -sp-wasm-interface = { version = "4.0.0-dev", default-features = false, path = "../wasm-interface" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -log = { version = "0.4", default-features = false } - -[dev-dependencies] -wat = "1.0" -assert_matches = "1.3.0" - -[features] -default = ["std"] -std = [ - "wasmi", - "sp-core/std", - "sp-std/std", - "codec/std", - "sp-io/std", - "sp-wasm-interface/std", - "log/std", -] -strict = [] -wasmer-sandbox = [] diff --git a/primitives/sandbox/README.md b/primitives/sandbox/README.md deleted file mode 100644 index 9335b53ae1fb9..0000000000000 --- a/primitives/sandbox/README.md +++ /dev/null @@ -1,21 +0,0 @@ -This crate provides means to instantiate and execute wasm modules. - -It works even when the user of this library executes from -inside the wasm VM. In this case the same VM is used for execution -of both the sandbox owner and the sandboxed module, without compromising security -and without the performance penalty of full wasm emulation inside wasm. - -This is achieved by using bindings to the wasm VM, which are published by the host API. -This API is thin and consists of only a handful functions. It contains functions for instantiating -modules and executing them, but doesn't contain functions for inspecting the module -structure. The user of this library is supposed to read the wasm module. - -When this crate is used in the `std` environment all these functions are implemented by directly -calling the wasm VM. - -Examples of possible use-cases for this library are not limited to the following: - -- implementing smart-contract runtimes that use wasm for contract code -- executing a wasm substrate runtime inside of a wasm parachain - -License: Apache-2.0 \ No newline at end of file diff --git a/primitives/sandbox/embedded_executor.rs b/primitives/sandbox/embedded_executor.rs deleted file mode 100755 index 678da3c3aeaf5..0000000000000 --- a/primitives/sandbox/embedded_executor.rs +++ /dev/null @@ -1,462 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 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::{Error, HostError, HostFuncType, ReturnValue, Value, TARGET}; -use alloc::string::String; -use log::debug; -use sp_std::{ - borrow::ToOwned, collections::btree_map::BTreeMap, fmt, marker::PhantomData, prelude::*, -}; -use wasmi::{ - memory_units::Pages, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalRef, - ImportResolver, MemoryDescriptor, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, - RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableRef, Trap, TrapKind, -}; - -#[derive(Clone)] -pub struct Memory { - memref: MemoryRef, -} - -impl Memory { - pub fn new(initial: u32, maximum: Option) -> Result { - Ok(Memory { - memref: MemoryInstance::alloc( - Pages(initial as usize), - maximum.map(|m| Pages(m as usize)), - ) - .map_err(|_| Error::Module)?, - }) - } - - pub fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error> { - self.memref.get_into(ptr, buf).map_err(|_| Error::OutOfBounds)?; - Ok(()) - } - - pub fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error> { - self.memref.set(ptr, value).map_err(|_| Error::OutOfBounds)?; - Ok(()) - } -} - -struct HostFuncIndex(usize); - -struct DefinedHostFunctions { - funcs: Vec>, -} - -impl Clone for DefinedHostFunctions { - fn clone(&self) -> DefinedHostFunctions { - DefinedHostFunctions { funcs: self.funcs.clone() } - } -} - -impl DefinedHostFunctions { - fn new() -> DefinedHostFunctions { - DefinedHostFunctions { funcs: Vec::new() } - } - - fn define(&mut self, f: HostFuncType) -> HostFuncIndex { - let idx = self.funcs.len(); - self.funcs.push(f); - HostFuncIndex(idx) - } -} - -#[derive(Debug)] -struct DummyHostError; - -impl fmt::Display for DummyHostError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DummyHostError") - } -} - -impl wasmi::HostError for DummyHostError {} - -struct GuestExternals<'a, T: 'a> { - state: &'a mut T, - defined_host_functions: &'a DefinedHostFunctions, -} - -impl<'a, T> Externals for GuestExternals<'a, T> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> Result, Trap> { - let args = args.as_ref().iter().cloned().map(to_interface).collect::>(); - - let result = (self.defined_host_functions.funcs[index])(self.state, &args); - match result { - Ok(value) => Ok(match value { - ReturnValue::Value(v) => Some(to_wasmi(v)), - ReturnValue::Unit => None, - }), - Err(HostError) => Err(TrapKind::Host(Box::new(DummyHostError)).into()), - } - } -} - -enum ExternVal { - HostFunc(HostFuncIndex), - Memory(Memory), -} - -pub struct EnvironmentDefinitionBuilder { - map: BTreeMap<(Vec, Vec), ExternVal>, - defined_host_functions: DefinedHostFunctions, -} - -impl EnvironmentDefinitionBuilder { - pub fn new() -> EnvironmentDefinitionBuilder { - EnvironmentDefinitionBuilder { - map: BTreeMap::new(), - defined_host_functions: DefinedHostFunctions::new(), - } - } - - pub fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) - where - N1: Into>, - N2: Into>, - { - let idx = self.defined_host_functions.define(f); - self.map.insert((module.into(), field.into()), ExternVal::HostFunc(idx)); - } - - pub fn add_memory(&mut self, module: N1, field: N2, mem: Memory) - where - N1: Into>, - N2: Into>, - { - self.map.insert((module.into(), field.into()), ExternVal::Memory(mem)); - } -} - -impl ImportResolver for EnvironmentDefinitionBuilder { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &Signature, - ) -> Result { - let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned()); - let externval = self.map.get(&key).ok_or_else(|| { - debug!(target: TARGET, "Export {}:{} not found", module_name, field_name); - wasmi::Error::Instantiation(String::new()) - })?; - let host_func_idx = match *externval { - ExternVal::HostFunc(ref idx) => idx, - _ => { - debug!(target: TARGET, "Export {}:{} is not a host func", module_name, field_name); - return Err(wasmi::Error::Instantiation(String::new())) - }, - }; - Ok(FuncInstance::alloc_host(signature.clone(), host_func_idx.0)) - } - - fn resolve_global( - &self, - _module_name: &str, - _field_name: &str, - _global_type: &GlobalDescriptor, - ) -> Result { - debug!(target: TARGET, "Importing globals is not supported yet"); - Err(wasmi::Error::Instantiation(String::new())) - } - - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - _memory_type: &MemoryDescriptor, - ) -> Result { - let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned()); - let externval = self.map.get(&key).ok_or_else(|| { - debug!(target: TARGET, "Export {}:{} not found", module_name, field_name); - wasmi::Error::Instantiation(String::new()) - })?; - let memory = match *externval { - ExternVal::Memory(ref m) => m, - _ => { - debug!(target: TARGET, "Export {}:{} is not a memory", module_name, field_name); - return Err(wasmi::Error::Instantiation(String::new())) - }, - }; - Ok(memory.memref.clone()) - } - - fn resolve_table( - &self, - _module_name: &str, - _field_name: &str, - _table_type: &TableDescriptor, - ) -> Result { - debug!("Importing tables is not supported yet"); - Err(wasmi::Error::Instantiation(String::new())) - } -} - -pub struct Instance { - instance: ModuleRef, - defined_host_functions: DefinedHostFunctions, - _marker: PhantomData, -} - -impl Instance { - pub fn new( - code: &[u8], - env_def_builder: &EnvironmentDefinitionBuilder, - state: &mut T, - ) -> Result, Error> { - let module = Module::from_buffer(code).map_err(|_| Error::Module)?; - let not_started_instance = - ModuleInstance::new(&module, env_def_builder).map_err(|_| Error::Module)?; - - let defined_host_functions = env_def_builder.defined_host_functions.clone(); - let instance = { - let mut externals = - GuestExternals { state, defined_host_functions: &defined_host_functions }; - let instance = - not_started_instance.run_start(&mut externals).map_err(|_| Error::Execution)?; - instance - }; - - Ok(Instance { instance, defined_host_functions, _marker: PhantomData:: }) - } - - pub fn invoke( - &mut self, - name: &str, - args: &[Value], - state: &mut T, - ) -> Result { - let args = args.iter().cloned().map(to_wasmi).collect::>(); - - let mut externals = - GuestExternals { state, defined_host_functions: &self.defined_host_functions }; - let result = self.instance.invoke_export(&name, &args, &mut externals); - - match result { - Ok(None) => Ok(ReturnValue::Unit), - Ok(Some(val)) => Ok(ReturnValue::Value(to_interface(val))), - Err(_err) => Err(Error::Execution), - } - } - - pub fn get_global_val(&self, name: &str) -> Option { - let global = self.instance.export_by_name(name)?.as_global()?.get(); - - Some(to_interface(global)) - } -} - -/// Convert the substrate value type to the wasmi value type. -fn to_wasmi(value: Value) -> RuntimeValue { - match value { - Value::I32(val) => RuntimeValue::I32(val), - Value::I64(val) => RuntimeValue::I64(val), - Value::F32(val) => RuntimeValue::F32(val.into()), - Value::F64(val) => RuntimeValue::F64(val.into()), - } -} - -/// Convert the wasmi value type to the substrate value type. -fn to_interface(value: RuntimeValue) -> Value { - match value { - RuntimeValue::I32(val) => Value::I32(val), - RuntimeValue::I64(val) => Value::I64(val), - RuntimeValue::F32(val) => Value::F32(val.into()), - RuntimeValue::F64(val) => Value::F64(val.into()), - } -} - -#[cfg(test)] -mod tests { - use crate::{EnvironmentDefinitionBuilder, Error, HostError, Instance, ReturnValue, Value}; - use assert_matches::assert_matches; - - fn execute_sandboxed(code: &[u8], args: &[Value]) -> Result { - struct State { - counter: u32, - } - - fn env_assert(_e: &mut State, args: &[Value]) -> Result { - if args.len() != 1 { - return Err(HostError) - } - let condition = args[0].as_i32().ok_or_else(|| HostError)?; - if condition != 0 { - Ok(ReturnValue::Unit) - } else { - Err(HostError) - } - } - fn env_inc_counter(e: &mut State, args: &[Value]) -> Result { - if args.len() != 1 { - return Err(HostError) - } - let inc_by = args[0].as_i32().ok_or_else(|| HostError)?; - e.counter += inc_by as u32; - Ok(ReturnValue::Value(Value::I32(e.counter as i32))) - } - /// Function that takes one argument of any type and returns that value. - fn env_polymorphic_id(_e: &mut State, args: &[Value]) -> Result { - if args.len() != 1 { - return Err(HostError) - } - Ok(ReturnValue::Value(args[0])) - } - - let mut state = State { counter: 0 }; - - let mut env_builder = EnvironmentDefinitionBuilder::new(); - env_builder.add_host_func("env", "assert", env_assert); - env_builder.add_host_func("env", "inc_counter", env_inc_counter); - env_builder.add_host_func("env", "polymorphic_id", env_polymorphic_id); - - let mut instance = Instance::new(code, &env_builder, &mut state)?; - let result = instance.invoke("call", args, &mut state); - - result.map_err(|_| HostError) - } - - #[test] - fn invoke_args() { - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - - (func (export "call") (param $x i32) (param $y i64) - ;; assert that $x = 0x12345678 - (call $assert - (i32.eq - (get_local $x) - (i32.const 0x12345678) - ) - ) - - (call $assert - (i64.eq - (get_local $y) - (i64.const 0x1234567887654321) - ) - ) - ) - ) - "#, - ) - .unwrap(); - - let result = - execute_sandboxed(&code, &[Value::I32(0x12345678), Value::I64(0x1234567887654321)]); - assert!(result.is_ok()); - } - - #[test] - fn return_value() { - let code = wat::parse_str( - r#" - (module - (func (export "call") (param $x i32) (result i32) - (i32.add - (get_local $x) - (i32.const 1) - ) - ) - ) - "#, - ) - .unwrap(); - - let return_val = execute_sandboxed(&code, &[Value::I32(0x1336)]).unwrap(); - assert_eq!(return_val, ReturnValue::Value(Value::I32(0x1337))); - } - - #[test] - fn signatures_dont_matter() { - let code = wat::parse_str( - r#" - (module - (import "env" "polymorphic_id" (func $id_i32 (param i32) (result i32))) - (import "env" "polymorphic_id" (func $id_i64 (param i64) (result i64))) - (import "env" "assert" (func $assert (param i32))) - - (func (export "call") - ;; assert that we can actually call the "same" function with different - ;; signatures. - (call $assert - (i32.eq - (call $id_i32 - (i32.const 0x012345678) - ) - (i32.const 0x012345678) - ) - ) - (call $assert - (i64.eq - (call $id_i64 - (i64.const 0x0123456789abcdef) - ) - (i64.const 0x0123456789abcdef) - ) - ) - ) - ) - "#, - ) - .unwrap(); - - let return_val = execute_sandboxed(&code, &[]).unwrap(); - assert_eq!(return_val, ReturnValue::Unit); - } - - #[test] - fn cant_return_unmatching_type() { - fn env_returns_i32(_e: &mut (), _args: &[Value]) -> Result { - Ok(ReturnValue::Value(Value::I32(42))) - } - - let mut env_builder = EnvironmentDefinitionBuilder::new(); - env_builder.add_host_func("env", "returns_i32", env_returns_i32); - - let code = wat::parse_str( - r#" - (module - ;; It's actually returns i32, but imported as if it returned i64 - (import "env" "returns_i32" (func $returns_i32 (result i64))) - - (func (export "call") - (drop - (call $returns_i32) - ) - ) - ) - "#, - ) - .unwrap(); - - // It succeeds since we are able to import functions with types we want. - let mut instance = Instance::new(&code, &env_builder, &mut ()).unwrap(); - - // But this fails since we imported a function that returns i32 as if it returned i64. - assert_matches!(instance.invoke("call", &[], &mut ()), Err(Error::Execution)); - } -} diff --git a/primitives/sandbox/host_executor.rs b/primitives/sandbox/host_executor.rs deleted file mode 100755 index d2836e2ffd1eb..0000000000000 --- a/primitives/sandbox/host_executor.rs +++ /dev/null @@ -1,283 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 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 codec::{Decode, Encode}; -use sp_core::sandbox as sandbox_primitives; -use sp_io::sandbox; -use sp_std::{prelude::*, slice, marker, mem, vec, rc::Rc}; -use super::{Error, Value, ReturnValue, HostFuncType}; - -mod ffi { - use sp_std::mem; - use super::HostFuncType; - - /// Index into the default table that points to a `HostFuncType`. - pub type HostFuncIndex = usize; - - /// Coerce `HostFuncIndex` to a callable host function pointer. - /// - /// # Safety - /// - /// This function should be only called with a `HostFuncIndex` that was previously registered - /// in the environment definition. Typically this should only - /// be called with an argument received in `dispatch_thunk`. - pub unsafe fn coerce_host_index_to_func(idx: HostFuncIndex) -> HostFuncType { - // We need to ensure that sizes of a callable function pointer and host function index is - // indeed equal. - // We can't use `static_assertions` create because it makes compiler panic, fallback to runtime assert. - // const_assert!(mem::size_of::() == mem::size_of::>()); - assert!(mem::size_of::() == mem::size_of::>()); - mem::transmute::>(idx) - } -} - -struct MemoryHandle { - memory_idx: u32, -} - -impl Drop for MemoryHandle { - fn drop(&mut self) { - sandbox::memory_teardown(self.memory_idx); - } -} - -#[derive(Clone)] -pub struct Memory { - // Handle to memory instance is wrapped to add reference-counting semantics - // to `Memory`. - handle: Rc, -} - -impl Memory { - pub fn new(initial: u32, maximum: Option) -> Result { - let maximum = if let Some(maximum) = maximum { - maximum - } else { - sandbox_primitives::MEM_UNLIMITED - }; - - match sandbox::memory_new(initial, maximum) { - sandbox_primitives::ERR_MODULE => Err(Error::Module), - memory_idx => Ok(Memory { - handle: Rc::new(MemoryHandle { memory_idx, }), - }), - } - } - - pub fn get(&self, offset: u32, buf: &mut [u8]) -> Result<(), Error> { - let result = sandbox::memory_get( - self.handle.memory_idx, - offset, - buf.as_mut_ptr(), - buf.len() as u32, - ); - match result { - sandbox_primitives::ERR_OK => Ok(()), - sandbox_primitives::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), - _ => unreachable!(), - } - } - - pub fn set(&self, offset: u32, val: &[u8]) -> Result<(), Error> { - let result = sandbox::memory_set( - self.handle.memory_idx, - offset, - val.as_ptr() as _ , - val.len() as u32, - ); - match result { - sandbox_primitives::ERR_OK => Ok(()), - sandbox_primitives::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), - _ => unreachable!(), - } - } -} - -pub struct EnvironmentDefinitionBuilder { - env_def: sandbox_primitives::EnvironmentDefinition, - retained_memories: Vec, - _marker: marker::PhantomData, -} - -impl EnvironmentDefinitionBuilder { - pub fn new() -> EnvironmentDefinitionBuilder { - EnvironmentDefinitionBuilder { - env_def: sandbox_primitives::EnvironmentDefinition { - entries: Vec::new(), - }, - retained_memories: Vec::new(), - _marker: marker::PhantomData::, - } - } - - fn add_entry( - &mut self, - module: N1, - field: N2, - extern_entity: sandbox_primitives::ExternEntity, - ) where - N1: Into>, - N2: Into>, - { - let entry = sandbox_primitives::Entry { - module_name: module.into(), - field_name: field.into(), - entity: extern_entity, - }; - self.env_def.entries.push(entry); - } - - pub fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) - where - N1: Into>, - N2: Into>, - { - let f = sandbox_primitives::ExternEntity::Function(f as u32); - self.add_entry(module, field, f); - } - - pub fn add_memory(&mut self, module: N1, field: N2, mem: Memory) - where - N1: Into>, - N2: Into>, - { - // We need to retain memory to keep it alive while the EnvironmentDefinitionBuilder alive. - self.retained_memories.push(mem.clone()); - - let mem = sandbox_primitives::ExternEntity::Memory(mem.handle.memory_idx as u32); - self.add_entry(module, field, mem); - } -} - -pub struct Instance { - instance_idx: u32, - _retained_memories: Vec, - _marker: marker::PhantomData, -} - -/// The primary responsibility of this thunk is to deserialize arguments and -/// call the original function, specified by the index. -extern "C" fn dispatch_thunk( - serialized_args_ptr: *const u8, - serialized_args_len: usize, - state: usize, - f: ffi::HostFuncIndex, -) -> u64 { - let serialized_args = unsafe { - if serialized_args_len == 0 { - &[] - } else { - slice::from_raw_parts(serialized_args_ptr, serialized_args_len) - } - }; - let args = Vec::::decode(&mut &serialized_args[..]).expect( - "serialized args should be provided by the runtime; - correctly serialized data should be deserializable; - qed", - ); - - unsafe { - // This should be safe since `coerce_host_index_to_func` is called with an argument - // received in an `dispatch_thunk` implementation, so `f` should point - // on a valid host function. - let f = ffi::coerce_host_index_to_func(f); - - // This should be safe since mutable reference to T is passed upon the invocation. - let state = &mut *(state as *mut T); - - // Pass control flow to the designated function. - let result = f(state, &args).encode(); - - // Leak the result vector and return the pointer to return data. - let result_ptr = result.as_ptr() as u64; - let result_len = result.len() as u64; - mem::forget(result); - - (result_ptr << 32) | result_len - } -} - -impl Instance { - pub fn new( - code: &[u8], - env_def_builder: &EnvironmentDefinitionBuilder, - state: &mut T, - ) -> Result, Error> { - let serialized_env_def: Vec = env_def_builder.env_def.encode(); - // It's very important to instantiate thunk with the right type. - let dispatch_thunk = dispatch_thunk::; - let result = sandbox::instantiate( - dispatch_thunk as u32, - code, - &serialized_env_def, - state as *const T as _, - ); - - let instance_idx = match result { - sandbox_primitives::ERR_MODULE => return Err(Error::Module), - sandbox_primitives::ERR_EXECUTION => return Err(Error::Execution), - instance_idx => instance_idx, - }; - - // We need to retain memories to keep them alive while the Instance is alive. - let retained_memories = env_def_builder.retained_memories.clone(); - Ok(Instance { - instance_idx, - _retained_memories: retained_memories, - _marker: marker::PhantomData::, - }) - } - - pub fn invoke( - &mut self, - name: &str, - args: &[Value], - state: &mut T, - ) -> Result { - let serialized_args = args.to_vec().encode(); - let mut return_val = vec![0u8; ReturnValue::ENCODED_MAX_SIZE]; - - let result = sandbox::invoke( - self.instance_idx, - name, - &serialized_args, - return_val.as_mut_ptr() as _, - return_val.len() as u32, - state as *const T as _, - ); - - match result { - sandbox_primitives::ERR_OK => { - let return_val = ReturnValue::decode(&mut &return_val[..]) - .map_err(|_| Error::Execution)?; - Ok(return_val) - } - sandbox_primitives::ERR_EXECUTION => Err(Error::Execution), - _ => unreachable!(), - } - } - - pub fn get_global_val(&self, name: &str) -> Option { - sandbox::get_global_val(self.instance_idx, name) - } -} - -impl Drop for Instance { - fn drop(&mut self) { - sandbox::instance_teardown(self.instance_idx); - } -} diff --git a/primitives/sandbox/src/lib.rs b/primitives/sandbox/src/lib.rs deleted file mode 100755 index 1724b4152ff3d..0000000000000 --- a/primitives/sandbox/src/lib.rs +++ /dev/null @@ -1,214 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2021 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. - -//! This crate provides means to instantiate and execute wasm modules. -//! -//! It works even when the user of this library executes from -//! inside the wasm VM. In this case the same VM is used for execution -//! of both the sandbox owner and the sandboxed module, without compromising security -//! and without the performance penalty of full wasm emulation inside wasm. -//! -//! This is achieved by using bindings to the wasm VM, which are published by the host API. -//! This API is thin and consists of only a handful functions. It contains functions for -//! instantiating modules and executing them, but doesn't contain functions for inspecting the -//! module structure. The user of this library is supposed to read the wasm module. -//! -//! When this crate is used in the `std` environment all these functions are implemented by directly -//! calling the wasm VM. -//! -//! Examples of possible use-cases for this library are not limited to the following: -//! -//! - implementing smart-contract runtimes that use wasm for contract code -//! - executing a wasm substrate runtime inside of a wasm parachain - -#![warn(missing_docs)] -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -use sp_std::prelude::*; - -pub use sp_core::sandbox::HostError; -pub use sp_wasm_interface::{ReturnValue, Value}; - -/// The target used for logging. -const TARGET: &str = "runtime::sandbox"; - -mod imp { - #[cfg(all(feature = "wasmer-sandbox", not(feature = "std")))] - include!("../host_executor.rs"); - - #[cfg(not(all(feature = "wasmer-sandbox", not(feature = "std"))))] - include!("../embedded_executor.rs"); -} - -/// Error that can occur while using this crate. -#[derive(sp_core::RuntimeDebug)] -pub enum Error { - /// Module is not valid, couldn't be instantiated. - Module, - - /// Access to a memory or table was made with an address or an index which is out of bounds. - /// - /// Note that if wasm module makes an out-of-bounds access then trap will occur. - OutOfBounds, - - /// Failed to invoke the start function or an exported function for some reason. - Execution, -} - -impl From for HostError { - fn from(_e: Error) -> HostError { - HostError - } -} - -/// Function pointer for specifying functions by the -/// supervisor in [`EnvironmentDefinitionBuilder`]. -/// -/// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html -pub type HostFuncType = fn(&mut T, &[Value]) -> Result; - -/// Reference to a sandboxed linear memory, that -/// will be used by the guest module. -/// -/// The memory can't be directly accessed by supervisor, but only -/// through designated functions [`get`](Memory::get) and [`set`](Memory::set). -#[derive(Clone)] -pub struct Memory { - inner: imp::Memory, -} - -impl Memory { - /// Construct a new linear memory instance. - /// - /// The memory allocated with initial number of pages specified by `initial`. - /// Minimal possible value for `initial` is 0 and maximum possible is `65536`. - /// (Since maximum addressable memory is 232 = 4GiB = 65536 * 64KiB). - /// - /// It is possible to limit maximum number of pages this memory instance can have by specifying - /// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB. - /// - /// Allocated memory is always zeroed. - pub fn new(initial: u32, maximum: Option) -> Result { - Ok(Memory { inner: imp::Memory::new(initial, maximum)? }) - } - - /// Read a memory area at the address `ptr` with the size of the provided slice `buf`. - /// - /// Returns `Err` if the range is out-of-bounds. - pub fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error> { - self.inner.get(ptr, buf) - } - - /// Write a memory area at the address `ptr` with contents of the provided slice `buf`. - /// - /// Returns `Err` if the range is out-of-bounds. - pub fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error> { - self.inner.set(ptr, value) - } -} - -/// Struct that can be used for defining an environment for a sandboxed module. -/// -/// The sandboxed module can access only the entities which were defined and passed -/// to the module at the instantiation time. -pub struct EnvironmentDefinitionBuilder { - inner: imp::EnvironmentDefinitionBuilder, -} - -impl EnvironmentDefinitionBuilder { - /// Construct a new `EnvironmentDefinitionBuilder`. - pub fn new() -> EnvironmentDefinitionBuilder { - EnvironmentDefinitionBuilder { inner: imp::EnvironmentDefinitionBuilder::new() } - } - - /// Register a host function in this environment definition. - /// - /// NOTE that there is no constraints on type of this function. An instance - /// can import function passed here with any signature it wants. It can even import - /// the same function (i.e. with same `module` and `field`) several times. It's up to - /// the user code to check or constrain the types of signatures. - pub fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) - where - N1: Into>, - N2: Into>, - { - self.inner.add_host_func(module, field, f); - } - - /// Register a memory in this environment definition. - pub fn add_memory(&mut self, module: N1, field: N2, mem: Memory) - where - N1: Into>, - N2: Into>, - { - self.inner.add_memory(module, field, mem.inner); - } -} - -/// Sandboxed instance of a wasm module. -/// -/// This instance can be used for invoking exported functions. -pub struct Instance { - inner: imp::Instance, -} - -impl Instance { - /// Instantiate a module with the given [`EnvironmentDefinitionBuilder`]. It will - /// run the `start` function (if it is present in the module) with the given `state`. - /// - /// Returns `Err(Error::Module)` if this module can't be instantiated with the given - /// environment. If execution of `start` function generated a trap, then `Err(Error::Execution)` - /// will be returned. - /// - /// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html - pub fn new( - code: &[u8], - env_def_builder: &EnvironmentDefinitionBuilder, - state: &mut T, - ) -> Result, Error> { - Ok(Instance { inner: imp::Instance::new(code, &env_def_builder.inner, state)? }) - } - - /// Invoke an exported function with the given name. - /// - /// # Errors - /// - /// Returns `Err(Error::Execution)` if: - /// - /// - An export function name isn't a proper utf8 byte sequence, - /// - This module doesn't have an exported function with the given name, - /// - If types of the arguments passed to the function doesn't match function signature then - /// trap occurs (as if the exported function was called via call_indirect), - /// - Trap occurred at the execution time. - pub fn invoke( - &mut self, - name: &str, - args: &[Value], - state: &mut T, - ) -> Result { - self.inner.invoke(name, args, state) - } - - /// Get the value from a global with the given `name`. - /// - /// Returns `Some(_)` if the global could be found. - pub fn get_global_val(&self, name: &str) -> Option { - self.inner.get_global_val(name) - } -} diff --git a/primitives/serializer/Cargo.toml b/primitives/serializer/Cargo.toml index 2200274e0628d..585e4b4e0dc20 100644 --- a/primitives/serializer/Cargo.toml +++ b/primitives/serializer/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-serializer" -version = "3.0.0" +version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate customizable serde serializer." documentation = "https://docs.rs/sp-serializer" @@ -14,5 +14,5 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = "1.0.126" -serde_json = "1.0.68" +serde = "1.0.136" +serde_json = "1.0.85" diff --git a/primitives/serializer/src/lib.rs b/primitives/serializer/src/lib.rs index ccdbbf27f179b..d1e364a133ba1 100644 --- a/primitives/serializer/src/lib.rs +++ b/primitives/serializer/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/session/Cargo.toml b/primitives/session/Cargo.toml index 8e1e2464e49ec..94f9e8a23505a 100644 --- a/primitives/session/Cargo.toml +++ b/primitives/session/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-session" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Primitives for sessions" readme = "README.md" @@ -13,13 +13,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "7.0.0", optional = true, path = "../runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../staking" } -sp-runtime = { version = "4.0.0-dev", optional = true, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] @@ -28,7 +28,7 @@ std = [ "scale-info/std", "sp-api/std", "sp-core/std", - "sp-std/std", - "sp-staking/std", "sp-runtime/std", + "sp-staking/std", + "sp-std/std", ] diff --git a/primitives/session/src/lib.rs b/primitives/session/src/lib.rs index d85b6af4349e4..dde262738ad71 100644 --- a/primitives/session/src/lib.rs +++ b/primitives/session/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -118,6 +118,10 @@ where T: ProvideRuntimeApi, T::Api: SessionKeys, { + if seeds.is_empty() { + return Ok(()) + } + let runtime_api = client.runtime_api(); for seed in seeds { diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 9e852319ede42..550c1485e992c 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-staking" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "A crate which contains primitives that are useful for implementation that uses staking approaches in general. Definitions related to sessions, slashing, etc go here." readme = "README.md" @@ -13,10 +13,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = ["std"] @@ -26,3 +26,4 @@ std = [ "sp-runtime/std", "sp-std/std", ] +runtime-benchmarks = [] diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 4bb8ed93f88a1..703f0abe80458 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,173 @@ //! A crate which contains primitives that are useful for implementation that uses staking //! approaches in general. Definitions related to sessions, slashing, etc go here. +use sp_runtime::{DispatchError, DispatchResult}; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + pub mod offence; /// Simple index type with which we can count sessions. pub type SessionIndex = u32; + +/// Counter for the number of eras that have passed. +pub type EraIndex = u32; + +/// Trait describing something that implements a hook for any operations to perform when a staker is +/// slashed. +pub trait OnStakerSlash { + /// A hook for any operations to perform when a staker is slashed. + /// + /// # Arguments + /// + /// * `stash` - The stash of the staker whom the slash was applied to. + /// * `slashed_active` - The new bonded balance of the staker after the slash was applied. + /// * `slashed_unlocking` - A map of slashed eras, and the balance of that unlocking chunk after + /// the slash is applied. Any era not present in the map is not affected at all. + fn on_slash( + stash: &AccountId, + slashed_active: Balance, + slashed_unlocking: &BTreeMap, + ); +} + +impl OnStakerSlash for () { + fn on_slash(_: &AccountId, _: Balance, _: &BTreeMap) { + // Nothing to do here + } +} + +/// A struct that reflects stake that an account has in the staking system. Provides a set of +/// methods to operate on it's properties. Aimed at making `StakingInterface` more concise. +pub struct Stake { + /// The stash account whose balance is actually locked and at stake. + pub stash: T::AccountId, + /// The total stake that `stash` has in the staking system. This includes the + /// `active` stake, and any funds currently in the process of unbonding via + /// [`StakingInterface::unbond`]. + /// + /// # Note + /// + /// This is only guaranteed to reflect the amount locked by the staking system. If there are + /// non-staking locks on the bonded pair's balance this amount is going to be larger in + /// reality. + pub total: T::Balance, + /// The total amount of the stash's balance that will be at stake in any forthcoming + /// rounds. + pub active: T::Balance, +} + +/// A generic representation of a staking implementation. +/// +/// This interface uses the terminology of NPoS, but it is aims to be generic enough to cover other +/// implementations as well. +pub trait StakingInterface { + /// Balance type used by the staking system. + type Balance: PartialEq; + + /// AccountId type used by the staking system + type AccountId; + + /// The minimum amount required to bond in order to set nomination intentions. This does not + /// necessarily mean the nomination will be counted in an election, but instead just enough to + /// be stored as a nominator. In other words, this is the minimum amount to register the + /// intention to nominate. + fn minimum_nominator_bond() -> Self::Balance; + + /// The minimum amount required to bond in order to set validation intentions. + fn minimum_validator_bond() -> Self::Balance; + + /// Return a stash account that is controlled by a `controller`. + /// + /// ## Note + /// + /// The controller abstraction is not permanent and might go away. Avoid using this as much as + /// possible. + fn stash_by_ctrl(controller: &Self::AccountId) -> Result; + + /// Number of eras that staked funds must remain bonded for. + fn bonding_duration() -> EraIndex; + + /// The current era index. + /// + /// This should be the latest planned era that the staking system knows about. + fn current_era() -> EraIndex; + + /// Returns the stake of `who`. + fn stake(who: &Self::AccountId) -> Result, DispatchError>; + + fn total_stake(who: &Self::AccountId) -> Result { + Self::stake(who).map(|s| s.total) + } + + fn active_stake(who: &Self::AccountId) -> Result { + Self::stake(who).map(|s| s.active) + } + + fn is_unbonding(who: &Self::AccountId) -> Result { + Self::stake(who).map(|s| s.active != s.total) + } + + fn fully_unbond(who: &Self::AccountId) -> DispatchResult { + Self::unbond(who, Self::stake(who)?.active) + } + + /// Bond (lock) `value` of `who`'s balance, while forwarding any rewards to `payee`. + fn bond(who: &Self::AccountId, value: Self::Balance, payee: &Self::AccountId) + -> DispatchResult; + + /// Have `who` nominate `validators`. + fn nominate(who: &Self::AccountId, validators: Vec) -> DispatchResult; + + /// Chill `who`. + fn chill(who: &Self::AccountId) -> DispatchResult; + + /// Bond some extra amount in `who`'s free balance against the active bonded balance of + /// the account. The amount extra actually bonded will never be more than `who`'s free + /// balance. + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult; + + /// Schedule a portion of the active bonded balance to be unlocked at era + /// [Self::current_era] + [`Self::bonding_duration`]. + /// + /// Once the unlock era has been reached, [`Self::withdraw_unbonded`] can be called to unlock + /// the funds. + /// + /// The amount of times this can be successfully called is limited based on how many distinct + /// eras funds are schedule to unlock in. Calling [`Self::withdraw_unbonded`] after some unlock + /// schedules have reached their unlocking era should allow more calls to this function. + fn unbond(stash: &Self::AccountId, value: Self::Balance) -> 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. + fn withdraw_unbonded( + stash: Self::AccountId, + num_slashing_spans: u32, + ) -> Result; + + /// The ideal number of active validators. + fn desired_validator_count() -> u32; + + /// Whether or not there is an ongoing election. + fn election_ongoing() -> bool; + + /// Force a current staker to become completely unstaked, immediately. + fn force_unstake(who: Self::AccountId) -> DispatchResult; + + /// Checks whether an account `staker` has been exposed in an era. + fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool; + + /// Get the nominations of a stash, if they are a nominator, `None` otherwise. + #[cfg(feature = "runtime-benchmarks")] + fn nominations(who: Self::AccountId) -> Option>; + + #[cfg(feature = "runtime-benchmarks")] + fn add_era_stakers( + current_era: &EraIndex, + stash: &Self::AccountId, + exposures: Vec<(Self::AccountId, Self::Balance)>, + ); + + #[cfg(feature = "runtime-benchmarks")] + fn set_current_era(era: EraIndex); +} diff --git a/primitives/staking/src/offence.rs b/primitives/staking/src/offence.rs index a91cb47c117b6..f6517b9e9028b 100644 --- a/primitives/staking/src/offence.rs +++ b/primitives/staking/src/offence.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,6 +37,29 @@ 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. @@ -79,12 +102,16 @@ 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 kind for the given parameters that happened at a singular `TimeSlot`. + /// particular offence for the `offenders_count` that happened at a singular `TimeSlot`. /// - /// `offenders_count` - the count of unique offending authorities. It is >0. - /// `validator_set_count` - the cardinality of the validator set at the time of offence. - fn slash_fraction(offenders_count: u32, validator_set_count: u32) -> Perbill; + /// `offenders_count` - the count of unique offending authorities for this `TimeSlot`. It is >0. + fn slash_fraction(&self, offenders_count: u32) -> Perbill; } /// Errors that may happen on offence reports. @@ -150,12 +177,15 @@ 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; } @@ -164,6 +194,7 @@ impl OnOffenceHandler _offenders: &[OffenceDetails], _slash_fraction: &[Perbill], _session: SessionIndex, + _disable_strategy: DisableStrategy, ) -> Res { Default::default() } diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index 457bbac5d2640..98794db60d30b 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "sp-state-machine" -version = "0.10.0-dev" +version = "0.13.0" authors = ["Parity Technologies "] description = "Substrate State Machine" -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sp-state-machine" readme = "README.md" @@ -14,45 +14,45 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.11", optional = true } -thiserror = { version = "1.0.21", optional = true } -parking_lot = { version = "0.11.1", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } hash-db = { version = "0.15.2", default-features = false } -trie-db = { version = "0.22.6", default-features = false } -trie-root = { version = "0.16.0", default-features = false } -sp-trie = { version = "4.0.0-dev", path = "../trie", default-features = false } -sp-core = { version = "4.0.0-dev", path = "../core", default-features = false } -sp-panic-handler = { version = "3.0.0", path = "../panic-handler", optional = true } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } +log = { version = "0.4.17", optional = true } num-traits = { version = "0.2.8", default-features = false } +parking_lot = { version = "0.12.1", optional = true } rand = { version = "0.7.2", optional = true } -sp-externalities = { version = "0.10.0-dev", path = "../externalities", default-features = false } -smallvec = "1.4.1" -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -tracing = { version = "0.1.22", optional = true } +smallvec = "1.8.0" +thiserror = { version = "1.0.30", optional = true } +tracing = { version = "0.1.29", optional = true } +trie-root = { version = "0.17.0", default-features = false } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } +sp-panic-handler = { version = "5.0.0", optional = true, path = "../panic-handler" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-trie = { version = "7.0.0", default-features = false, path = "../trie" } [dev-dependencies] -hex-literal = "0.3.1" -sp-runtime = { version = "4.0.0-dev", path = "../runtime" } -pretty_assertions = "0.6.1" -rand = { version = "0.7.2" } +array-bytes = "4.1" +pretty_assertions = "1.2.1" +rand = "0.7.2" +sp-runtime = { version = "7.0.0", path = "../runtime" } +trie-db = "0.24.0" +assert_matches = "1.5" [features] default = ["std"] std = [ "codec/std", "hash-db/std", + "log", "num-traits/std", + "parking_lot", + "rand", "sp-core/std", "sp-externalities/std", + "sp-panic-handler", "sp-std/std", "sp-trie/std", - "trie-db/std", - "trie-root/std", - "log", "thiserror", - "parking_lot", - "rand", - "sp-panic-handler", - "tracing" + "tracing", + "trie-root/std", ] diff --git a/primitives/state-machine/src/backend.rs b/primitives/state-machine/src/backend.rs index 1b1a732f8d0fc..791183c4d7e4d 100644 --- a/primitives/state-machine/src/backend.rs +++ b/primitives/state-machine/src/backend.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,15 @@ //! State machine backends. These manage the code and storage of contracts. +#[cfg(feature = "std")] +use crate::trie_backend::TrieBackend; use crate::{ - trie_backend::TrieBackend, trie_backend_essence::TrieBackendStorage, ChildStorageCollection, - StorageCollection, StorageKey, StorageValue, UsageInfo, + trie_backend_essence::TrieBackendStorage, ChildStorageCollection, StorageCollection, + StorageKey, StorageValue, UsageInfo, }; -use codec::{Decode, Encode}; +use codec::Encode; use hash_db::Hasher; -use sp_core::storage::{well_known_keys, ChildInfo, TrackedStorageKey}; +use sp_core::storage::{ChildInfo, StateVersion, TrackedStorageKey}; #[cfg(feature = "std")] use sp_core::traits::RuntimeCode; use sp_std::vec::Vec; @@ -40,15 +42,13 @@ pub trait Backend: sp_std::fmt::Debug { type Transaction: Consolidate + Default + Send; /// Type of trie backend storage. - type TrieBackendStorage: TrieBackendStorage; + type TrieBackendStorage: TrieBackendStorage; /// Get keyed storage or None if there is nothing associated. fn storage(&self, key: &[u8]) -> Result, Self::Error>; /// Get keyed storage value hash or None if there is nothing associated. - fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { - self.storage(key).map(|v| v.map(|v| H::hash(&v))) - } + fn storage_hash(&self, key: &[u8]) -> Result, Self::Error>; /// Get keyed child storage or None if there is nothing associated. fn child_storage( @@ -62,13 +62,11 @@ pub trait Backend: sp_std::fmt::Debug { &self, child_info: &ChildInfo, key: &[u8], - ) -> Result, Self::Error> { - self.child_storage(child_info, key).map(|v| v.map(|v| H::hash(&v))) - } + ) -> Result, Self::Error>; /// true if a key exists in storage. fn exists_storage(&self, key: &[u8]) -> Result { - Ok(self.storage(key)?.is_some()) + Ok(self.storage_hash(key)?.is_some()) } /// true if a key exists in child storage. @@ -77,7 +75,7 @@ pub trait Backend: sp_std::fmt::Debug { child_info: &ChildInfo, key: &[u8], ) -> Result { - Ok(self.child_storage(child_info, key)?.is_some()) + Ok(self.child_storage_hash(child_info, key)?.is_some()) } /// Return the next key in storage in lexicographic order or `None` if there is no value. @@ -112,6 +110,7 @@ pub trait Backend: sp_std::fmt::Debug { &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ); @@ -140,6 +139,7 @@ pub trait Backend: sp_std::fmt::Debug { fn storage_root<'a>( &self, delta: impl Iterator)>, + state_version: StateVersion, ) -> (H::Out, Self::Transaction) where H::Out: Ord; @@ -151,6 +151,7 @@ pub trait Backend: sp_std::fmt::Debug { &self, child_info: &ChildInfo, delta: impl Iterator)>, + state_version: StateVersion, ) -> (H::Out, bool, Self::Transaction) where H::Out: Ord; @@ -172,11 +173,6 @@ pub trait Backend: sp_std::fmt::Debug { all } - /// Try convert into trie backend. - fn as_trie_backend(&self) -> Option<&TrieBackend> { - None - } - /// Calculate the storage root, with given delta over what is already stored /// in the backend, and produce a "transaction" that can be used to commit. /// Does include child storage updates. @@ -186,6 +182,7 @@ pub trait Backend: sp_std::fmt::Debug { child_deltas: impl Iterator< Item = (&'a ChildInfo, impl Iterator)>), >, + state_version: StateVersion, ) -> (H::Out, Self::Transaction) where H::Out: Ord + Encode, @@ -194,7 +191,8 @@ pub trait Backend: sp_std::fmt::Debug { let mut child_roots: Vec<_> = Default::default(); // child first for (child_info, child_delta) in child_deltas { - let (child_root, empty, child_txs) = self.child_storage_root(&child_info, child_delta); + let (child_root, empty, child_txs) = + self.child_storage_root(child_info, child_delta, state_version); let prefixed_storage_key = child_info.prefixed_storage_key(); txs.consolidate(child_txs); if empty { @@ -207,6 +205,7 @@ pub trait Backend: sp_std::fmt::Debug { delta .map(|(k, v)| (k, v.as_ref().map(|v| &v[..]))) .chain(child_roots.iter().map(|(k, v)| (&k[..], v.as_ref().map(|v| &v[..])))), + state_version, ); txs.consolidate(parent_txs); (root, txs) @@ -268,6 +267,16 @@ pub trait Backend: sp_std::fmt::Debug { } } +/// Something that can be converted into a [`TrieBackend`]. +#[cfg(feature = "std")] +pub trait AsTrieBackend> { + /// Type of trie backend storage. + type TrieBackendStorage: TrieBackendStorage; + + /// Return the type as [`TrieBackend`]. + fn as_trie_backend(&self) -> &TrieBackend; +} + /// Trait that allows consolidate two transactions together. pub trait Consolidate { /// Consolidate two transactions into one. @@ -286,36 +295,14 @@ impl Consolidate for Vec<(Option, StorageCollection)> { } } -impl> Consolidate for sp_trie::GenericMemoryDB { - fn consolidate(&mut self, other: Self) { - sp_trie::GenericMemoryDB::consolidate(self, other) - } -} - -/// Insert input pairs into memory db. -#[cfg(test)] -pub(crate) fn insert_into_memory_db( - mdb: &mut sp_trie::MemoryDB, - input: I, -) -> Option +impl Consolidate for sp_trie::GenericMemoryDB where H: Hasher, - I: IntoIterator, + KF: sp_trie::KeyFunction, { - use sp_trie::{trie_types::TrieDBMut, TrieMut}; - - let mut root = ::Out::default(); - { - let mut trie = TrieDBMut::::new(mdb, &mut root); - for (key, value) in input { - if let Err(e) = trie.insert(&key, &value) { - log::warn!(target: "trie", "Failed to write to trie: {}", e); - return None - } - } + fn consolidate(&mut self, other: Self) { + sp_trie::GenericMemoryDB::consolidate(self, other) } - - Some(root) } /// Wrapper to create a [`RuntimeCode`] from a type that implements [`Backend`]. @@ -329,8 +316,12 @@ pub struct BackendRuntimeCode<'a, B, H> { impl<'a, B: Backend, H: Hasher> sp_core::traits::FetchRuntimeCode for BackendRuntimeCode<'a, B, H> { - fn fetch_runtime_code<'b>(&'b self) -> Option> { - self.backend.storage(well_known_keys::CODE).ok().flatten().map(Into::into) + fn fetch_runtime_code(&self) -> Option> { + self.backend + .storage(sp_core::storage::well_known_keys::CODE) + .ok() + .flatten() + .map(Into::into) } } @@ -348,17 +339,17 @@ where pub fn runtime_code(&self) -> Result { let hash = self .backend - .storage_hash(well_known_keys::CODE) + .storage_hash(sp_core::storage::well_known_keys::CODE) .ok() .flatten() .ok_or("`:code` hash not found")? .encode(); let heap_pages = self .backend - .storage(well_known_keys::HEAP_PAGES) + .storage(sp_core::storage::well_known_keys::HEAP_PAGES) .ok() .flatten() - .and_then(|d| Decode::decode(&mut &d[..]).ok()); + .and_then(|d| codec::Decode::decode(&mut &d[..]).ok()); Ok(RuntimeCode { code_fetcher: self, hash, heap_pages }) } diff --git a/primitives/state-machine/src/basic.rs b/primitives/state-machine/src/basic.rs index 0bbd2d0a8e8e6..fdc50e3f8f207 100644 --- a/primitives/state-machine/src/basic.rs +++ b/primitives/state-machine/src/basic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,37 +17,36 @@ //! Basic implementation for Externalities. -use crate::{Backend, StorageKey, StorageValue}; +use crate::{Backend, OverlayedChanges, StorageKey, StorageValue}; use codec::Encode; use hash_db::Hasher; use log::warn; use sp_core::{ storage::{ - well_known_keys::is_child_storage_key, ChildInfo, Storage, StorageChild, TrackedStorageKey, + well_known_keys::is_child_storage_key, ChildInfo, StateVersion, Storage, TrackedStorageKey, }, traits::Externalities, Blake2Hasher, }; -use sp_externalities::{Extension, Extensions}; -use sp_trie::{empty_child_trie_root, trie_types::Layout, TrieConfiguration}; +use sp_externalities::{Extension, Extensions, MultiRemovalResults}; +use sp_trie::{empty_child_trie_root, HashKey, LayoutV0, LayoutV1, TrieConfiguration}; use std::{ any::{Any, TypeId}, collections::BTreeMap, iter::FromIterator, - ops::Bound, }; /// Simple Map-based Externalities impl. #[derive(Debug)] pub struct BasicExternalities { - inner: Storage, + overlay: OverlayedChanges, extensions: Extensions, } impl BasicExternalities { /// Create a new instance of `BasicExternalities` pub fn new(inner: Storage) -> Self { - BasicExternalities { inner, extensions: Default::default() } + BasicExternalities { overlay: inner.into(), extensions: Default::default() } } /// New basic externalities with empty storage. @@ -56,13 +55,34 @@ impl BasicExternalities { } /// Insert key/value - pub fn insert(&mut self, k: StorageKey, v: StorageValue) -> Option { - self.inner.top.insert(k, v) + pub fn insert(&mut self, k: StorageKey, v: StorageValue) { + self.overlay.set_storage(k, Some(v)); } /// Consume self and returns inner storages pub fn into_storages(self) -> Storage { - self.inner + Storage { + top: self + .overlay + .changes() + .filter_map(|(k, v)| v.value().map(|v| (k.to_vec(), v.to_vec()))) + .collect(), + children_default: self + .overlay + .children() + .map(|(iter, i)| { + ( + i.storage_key().to_vec(), + sp_core::storage::StorageChild { + data: iter + .filter_map(|(k, v)| v.value().map(|v| (k.to_vec(), v.to_vec()))) + .collect(), + child_info: i.clone(), + }, + ) + }) + .collect(), + } } /// Execute the given closure `f` with the externalities set and initialized with `storage`. @@ -72,13 +92,7 @@ impl BasicExternalities { storage: &mut sp_core::storage::Storage, f: impl FnOnce() -> R, ) -> R { - let mut ext = Self { - inner: Storage { - top: std::mem::take(&mut storage.top), - children_default: std::mem::take(&mut storage.children_default), - }, - extensions: Default::default(), - }; + let mut ext = Self::new(std::mem::take(storage)); let r = ext.execute_with(f); @@ -107,15 +121,26 @@ impl BasicExternalities { impl PartialEq for BasicExternalities { fn eq(&self, other: &BasicExternalities) -> bool { - self.inner.top.eq(&other.inner.top) && - self.inner.children_default.eq(&other.inner.children_default) + self.overlay.changes().map(|(k, v)| (k, v.value())).collect::>() == + other.overlay.changes().map(|(k, v)| (k, v.value())).collect::>() && + self.overlay + .children() + .map(|(iter, i)| (i, iter.map(|(k, v)| (k, v.value())).collect::>())) + .collect::>() == + other + .overlay + .children() + .map(|(iter, i)| { + (i, iter.map(|(k, v)| (k, v.value())).collect::>()) + }) + .collect::>() } } impl FromIterator<(StorageKey, StorageValue)> for BasicExternalities { fn from_iter>(iter: I) -> Self { let mut t = Self::default(); - t.inner.top.extend(iter); + iter.into_iter().for_each(|(k, v)| t.insert(k, v)); t } } @@ -127,11 +152,8 @@ impl Default for BasicExternalities { } impl From> for BasicExternalities { - fn from(hashmap: BTreeMap) -> Self { - BasicExternalities { - inner: Storage { top: hashmap, children_default: Default::default() }, - extensions: Default::default(), - } + fn from(map: BTreeMap) -> Self { + Self::from_iter(map.into_iter()) } } @@ -139,7 +161,7 @@ impl Externalities for BasicExternalities { fn set_offchain_storage(&mut self, _key: &[u8], _value: Option<&[u8]>) {} fn storage(&self, key: &[u8]) -> Option { - self.inner.top.get(key).cloned() + self.overlay.storage(key).and_then(|v| v.map(|v| v.to_vec())) } fn storage_hash(&self, key: &[u8]) -> Option> { @@ -147,11 +169,7 @@ impl Externalities for BasicExternalities { } fn child_storage(&self, child_info: &ChildInfo, key: &[u8]) -> Option { - self.inner - .children_default - .get(child_info.storage_key()) - .and_then(|child| child.data.get(key)) - .cloned() + self.overlay.child_storage(child_info, key).and_then(|v| v.map(|v| v.to_vec())) } fn child_storage_hash(&self, child_info: &ChildInfo, key: &[u8]) -> Option> { @@ -159,16 +177,13 @@ impl Externalities for BasicExternalities { } fn next_storage_key(&self, key: &[u8]) -> Option { - let range = (Bound::Excluded(key), Bound::Unbounded); - self.inner.top.range::<[u8], _>(range).next().map(|(k, _)| k).cloned() + self.overlay.iter_after(key).find_map(|(k, v)| v.value().map(|_| k.to_vec())) } fn next_child_storage_key(&self, child_info: &ChildInfo, key: &[u8]) -> Option { - let range = (Bound::Excluded(key), Bound::Unbounded); - self.inner - .children_default - .get(child_info.storage_key()) - .and_then(|child| child.data.range::<[u8], _>(range).next().map(|(k, _)| k).cloned()) + self.overlay + .child_iter_after(child_info.storage_key(), key) + .find_map(|(k, v)| v.value().map(|_| k.to_vec())) } fn place_storage(&mut self, key: StorageKey, maybe_value: Option) { @@ -177,14 +192,7 @@ impl Externalities for BasicExternalities { return } - match maybe_value { - Some(value) => { - self.inner.top.insert(key, value); - }, - None => { - self.inner.top.remove(&key); - }, - } + self.overlay.set_storage(key, maybe_value) } fn place_child_storage( @@ -193,136 +201,106 @@ impl Externalities for BasicExternalities { key: StorageKey, value: Option, ) { - let child_map = self - .inner - .children_default - .entry(child_info.storage_key().to_vec()) - .or_insert_with(|| StorageChild { - data: Default::default(), - child_info: child_info.to_owned(), - }); - if let Some(value) = value { - child_map.data.insert(key, value); - } else { - child_map.data.remove(&key); - } + self.overlay.set_child_storage(child_info, key, value); } - fn kill_child_storage(&mut self, child_info: &ChildInfo, _limit: Option) -> (bool, u32) { - let num_removed = self - .inner - .children_default - .remove(child_info.storage_key()) - .map(|c| c.data.len()) - .unwrap_or(0); - (true, num_removed as u32) + fn kill_child_storage( + &mut self, + child_info: &ChildInfo, + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { + let count = self.overlay.clear_child_storage(child_info); + MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count } } - fn clear_prefix(&mut self, prefix: &[u8], _limit: Option) -> (bool, u32) { + fn clear_prefix( + &mut self, + prefix: &[u8], + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { if is_child_storage_key(prefix) { warn!( target: "trie", "Refuse to clear prefix that is part of child storage key via main storage" ); - return (false, 0) + let maybe_cursor = Some(prefix.to_vec()); + return MultiRemovalResults { maybe_cursor, backend: 0, unique: 0, loops: 0 } } - let to_remove = self - .inner - .top - .range::<[u8], _>((Bound::Included(prefix), Bound::Unbounded)) - .map(|(k, _)| k) - .take_while(|k| k.starts_with(prefix)) - .cloned() - .collect::>(); - - let num_removed = to_remove.len(); - for key in to_remove { - self.inner.top.remove(&key); - } - (true, num_removed as u32) + let count = self.overlay.clear_prefix(prefix); + MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count } } fn clear_child_prefix( &mut self, child_info: &ChildInfo, prefix: &[u8], - _limit: Option, - ) -> (bool, u32) { - if let Some(child) = self.inner.children_default.get_mut(child_info.storage_key()) { - let to_remove = child - .data - .range::<[u8], _>((Bound::Included(prefix), Bound::Unbounded)) - .map(|(k, _)| k) - .take_while(|k| k.starts_with(prefix)) - .cloned() - .collect::>(); - - let num_removed = to_remove.len(); - for key in to_remove { - child.data.remove(&key); - } - (true, num_removed as u32) - } else { - (true, 0) - } + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { + let count = self.overlay.clear_child_prefix(child_info, prefix); + MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count } } fn storage_append(&mut self, key: Vec, value: Vec) { - let current = self.inner.top.entry(key).or_default(); - crate::ext::StorageAppend::new(current).append(value); - } - - fn storage_root(&mut self) -> Vec { - let mut top = self.inner.top.clone(); - let prefixed_keys: Vec<_> = self - .inner - .children_default - .iter() - .map(|(_k, v)| (v.child_info.prefixed_storage_key(), v.child_info.clone())) - .collect(); + let current_value = self.overlay.value_mut_or_insert_with(&key, || Default::default()); + crate::ext::StorageAppend::new(current_value).append(value); + } + + fn storage_root(&mut self, state_version: StateVersion) -> Vec { + let mut top = self + .overlay + .changes() + .filter_map(|(k, v)| v.value().map(|v| (k.clone(), v.clone()))) + .collect::>(); // Single child trie implementation currently allows using the same child // empty root for all child trie. Using null storage key until multiple // type of child trie support. - let empty_hash = empty_child_trie_root::>(); - for (prefixed_storage_key, child_info) in prefixed_keys { - let child_root = self.child_storage_root(&child_info); - if &empty_hash[..] == &child_root[..] { - top.remove(prefixed_storage_key.as_slice()); + let empty_hash = empty_child_trie_root::>(); + for child_info in self.overlay.children().map(|d| d.1.clone()).collect::>() { + let child_root = self.child_storage_root(&child_info, state_version); + if empty_hash[..] == child_root[..] { + top.remove(child_info.prefixed_storage_key().as_slice()); } else { - top.insert(prefixed_storage_key.into_inner(), child_root); + top.insert(child_info.prefixed_storage_key().into_inner(), child_root); } } - Layout::::trie_root(self.inner.top.clone()).as_ref().into() + match state_version { + StateVersion::V0 => LayoutV0::::trie_root(top).as_ref().into(), + StateVersion::V1 => LayoutV1::::trie_root(top).as_ref().into(), + } } - fn child_storage_root(&mut self, child_info: &ChildInfo) -> Vec { - if let Some(child) = self.inner.children_default.get(child_info.storage_key()) { - let delta = child.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))); - crate::in_memory_backend::new_in_mem::() - .child_storage_root(&child.child_info, delta) + fn child_storage_root( + &mut self, + child_info: &ChildInfo, + state_version: StateVersion, + ) -> Vec { + if let Some((data, child_info)) = self.overlay.child_changes(child_info.storage_key()) { + let delta = + data.into_iter().map(|(k, v)| (k.as_ref(), v.value().map(|v| v.as_slice()))); + crate::in_memory_backend::new_in_mem::>() + .child_storage_root(&child_info, delta, state_version) .0 } else { - empty_child_trie_root::>() + empty_child_trie_root::>() } .encode() } - fn storage_changes_root(&mut self, _parent: &[u8]) -> Result>, ()> { - Ok(None) - } - fn storage_start_transaction(&mut self) { - unimplemented!("Transactions are not supported by BasicExternalities"); + self.overlay.start_transaction() } fn storage_rollback_transaction(&mut self) -> Result<(), ()> { - unimplemented!("Transactions are not supported by BasicExternalities"); + self.overlay.rollback_transaction().map_err(drop) } fn storage_commit_transaction(&mut self) -> Result<(), ()> { - unimplemented!("Transactions are not supported by BasicExternalities"); + self.overlay.commit_transaction().map_err(drop) } fn wipe(&mut self) {} @@ -378,7 +356,6 @@ impl sp_externalities::ExtensionStore for BasicExternalities { #[cfg(test)] mod tests { use super::*; - use hex_literal::hex; use sp_core::{ map, storage::{well_known_keys::CODE, Storage, StorageChild}, @@ -390,10 +367,11 @@ mod tests { ext.set_storage(b"doe".to_vec(), b"reindeer".to_vec()); ext.set_storage(b"dog".to_vec(), b"puppy".to_vec()); ext.set_storage(b"dogglesworth".to_vec(), b"cat".to_vec()); - const ROOT: [u8; 32] = - hex!("39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa"); + let root = array_bytes::hex2bytes_unchecked( + "39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa", + ); - assert_eq!(&ext.storage_root()[..], &ROOT); + assert_eq!(&ext.storage_root(StateVersion::default())[..], &root); } #[test] @@ -428,7 +406,7 @@ mod tests { ext.clear_child_storage(child_info, b"dog"); assert_eq!(ext.child_storage(child_info, b"dog"), None); - ext.kill_child_storage(child_info, None); + let _ = ext.kill_child_storage(child_info, None, None); assert_eq!(ext.child_storage(child_info, b"doe"), None); } @@ -450,8 +428,8 @@ mod tests { ], }); - let res = ext.kill_child_storage(child_info, None); - assert_eq!(res, (true, 3)); + let res = ext.kill_child_storage(child_info, None, None); + assert_eq!(res.deconstruct(), (None, 3, 3, 3)); } #[test] diff --git a/primitives/state-machine/src/changes_trie/build.rs b/primitives/state-machine/src/changes_trie/build.rs deleted file mode 100644 index d3c6c12122c4f..0000000000000 --- a/primitives/state-machine/src/changes_trie/build.rs +++ /dev/null @@ -1,1083 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! Structures and functions required to build changes trie for given block. - -use crate::{ - backend::Backend, - changes_trie::{ - build_iterator::digest_build_iterator, - input::{ChildIndex, DigestIndex, ExtrinsicIndex, InputKey, InputPair}, - AnchorBlockId, BlockNumber, ConfigurationRange, Storage, - }, - overlayed_changes::{OverlayedChanges, OverlayedValue}, - trie_backend_essence::TrieBackendEssence, - StorageKey, -}; -use codec::{Decode, Encode}; -use hash_db::Hasher; -use num_traits::One; -use sp_core::storage::{ChildInfo, PrefixedStorageKey}; -use std::collections::{btree_map::Entry, BTreeMap}; - -/// Prepare input pairs for building a changes trie of given block. -/// -/// Returns Err if storage error has occurred OR if storage haven't returned -/// required data. -pub(crate) fn prepare_input<'a, B, H, Number>( - backend: &'a B, - storage: &'a dyn Storage, - config: ConfigurationRange<'a, Number>, - overlay: &'a OverlayedChanges, - parent: &'a AnchorBlockId, -) -> Result< - ( - impl Iterator> + 'a, - Vec<(ChildIndex, impl Iterator> + 'a)>, - Vec, - ), - String, -> -where - B: Backend, - H: Hasher + 'a, - H::Out: Encode, - Number: BlockNumber, -{ - let number = parent.number.clone() + One::one(); - let (extrinsics_input, children_extrinsics_input) = - prepare_extrinsics_input(backend, &number, overlay)?; - let (digest_input, mut children_digest_input, digest_input_blocks) = - prepare_digest_input::(parent, config, number, storage)?; - - let mut children_digest = Vec::with_capacity(children_extrinsics_input.len()); - for (child_index, ext_iter) in children_extrinsics_input.into_iter() { - let dig_iter = children_digest_input.remove(&child_index); - children_digest.push(( - child_index, - Some(ext_iter).into_iter().flatten().chain(dig_iter.into_iter().flatten()), - )); - } - for (child_index, dig_iter) in children_digest_input.into_iter() { - children_digest.push(( - child_index, - None.into_iter().flatten().chain(Some(dig_iter).into_iter().flatten()), - )); - } - - Ok((extrinsics_input.chain(digest_input), children_digest, digest_input_blocks)) -} -/// Prepare ExtrinsicIndex input pairs. -fn prepare_extrinsics_input<'a, B, H, Number>( - backend: &'a B, - block: &Number, - overlay: &'a OverlayedChanges, -) -> Result< - ( - impl Iterator> + 'a, - BTreeMap, impl Iterator> + 'a>, - ), - String, -> -where - B: Backend, - H: Hasher + 'a, - Number: BlockNumber, -{ - let mut children_result = BTreeMap::new(); - - for (child_changes, child_info) in overlay.children() { - let child_index = ChildIndex:: { - block: block.clone(), - storage_key: child_info.prefixed_storage_key(), - }; - - let iter = prepare_extrinsics_input_inner( - backend, - block, - overlay, - Some(child_info.clone()), - child_changes, - )?; - children_result.insert(child_index, iter); - } - - let top = prepare_extrinsics_input_inner(backend, block, overlay, None, overlay.changes())?; - - Ok((top, children_result)) -} - -fn prepare_extrinsics_input_inner<'a, B, H, Number>( - backend: &'a B, - block: &Number, - overlay: &'a OverlayedChanges, - child_info: Option, - changes: impl Iterator, -) -> Result> + 'a, String> -where - B: Backend, - H: Hasher, - Number: BlockNumber, -{ - changes - .filter_map(|(k, v)| { - let extrinsics = v.extrinsics(); - if !extrinsics.is_empty() { - Some((k, extrinsics)) - } else { - None - } - }) - .try_fold( - BTreeMap::new(), - |mut map: BTreeMap<&[u8], (ExtrinsicIndex, Vec)>, (k, extrinsics)| { - match map.entry(k) { - Entry::Vacant(entry) => { - // ignore temporary values (values that have null value at the end of - // operation AND are not in storage at the beginning of operation - if let Some(child_info) = child_info.as_ref() { - if !overlay - .child_storage(child_info, k) - .map(|v| v.is_some()) - .unwrap_or_default() - { - if !backend - .exists_child_storage(&child_info, k) - .map_err(|e| format!("{}", e))? - { - return Ok(map) - } - } - } else { - if !overlay.storage(k).map(|v| v.is_some()).unwrap_or_default() { - if !backend.exists_storage(k).map_err(|e| format!("{}", e))? { - return Ok(map) - } - } - }; - - let extrinsics = extrinsics.into_iter().collect(); - entry.insert(( - ExtrinsicIndex { block: block.clone(), key: k.to_vec() }, - extrinsics, - )); - }, - Entry::Occupied(mut entry) => { - // we do not need to check for temporary values here, because entry is - // Occupied AND we are checking it before insertion - let entry_extrinsics = &mut entry.get_mut().1; - entry_extrinsics.extend(extrinsics.into_iter()); - entry_extrinsics.sort(); - }, - } - - Ok(map) - }, - ) - .map(|pairs| pairs.into_iter().map(|(_, (k, v))| InputPair::ExtrinsicIndex(k, v))) -} - -/// Prepare DigestIndex input pairs. -fn prepare_digest_input<'a, H, Number>( - parent: &'a AnchorBlockId, - config: ConfigurationRange, - block: Number, - storage: &'a dyn Storage, -) -> Result< - ( - impl Iterator> + 'a, - BTreeMap, impl Iterator> + 'a>, - Vec, - ), - String, -> -where - H: Hasher, - H::Out: 'a + Encode, - Number: BlockNumber, -{ - let build_skewed_digest = config.end.as_ref() == Some(&block); - let block_for_digest = if build_skewed_digest { - config - .config - .next_max_level_digest_range(config.zero.clone(), block.clone()) - .map(|(_, end)| end) - .unwrap_or_else(|| block.clone()) - } else { - block.clone() - }; - - let digest_input_blocks = digest_build_iterator(config, block_for_digest).collect::>(); - digest_input_blocks - .clone() - .into_iter() - .try_fold( - (BTreeMap::new(), BTreeMap::new()), - move |(mut map, mut child_map), digest_build_block| { - let extrinsic_prefix = - ExtrinsicIndex::key_neutral_prefix(digest_build_block.clone()); - let digest_prefix = DigestIndex::key_neutral_prefix(digest_build_block.clone()); - let child_prefix = ChildIndex::key_neutral_prefix(digest_build_block.clone()); - let trie_root = storage.root(parent, digest_build_block.clone())?; - let trie_root = trie_root.ok_or_else(|| { - format!("No changes trie root for block {}", digest_build_block.clone()) - })?; - - let insert_to_map = |map: &mut BTreeMap<_, _>, key: StorageKey| { - match map.entry(key.clone()) { - Entry::Vacant(entry) => { - entry.insert(( - DigestIndex { block: block.clone(), key }, - vec![digest_build_block.clone()], - )); - }, - Entry::Occupied(mut entry) => { - // DigestIndexValue must be sorted. Here we are relying on the fact that - // digest_build_iterator() returns blocks in ascending order => we only - // need to check for duplicates - // - // is_dup_block could be true when key has been changed in both digest - // block AND other blocks that it covers - let is_dup_block = entry.get().1.last() == Some(&digest_build_block); - if !is_dup_block { - entry.get_mut().1.push(digest_build_block.clone()); - } - }, - } - }; - - // try to get all updated keys from cache - let populated_from_cache = - storage.with_cached_changed_keys(&trie_root, &mut |changed_keys| { - for (storage_key, changed_keys) in changed_keys { - let map = match storage_key { - Some(storage_key) => child_map - .entry(ChildIndex:: { - block: block.clone(), - storage_key: storage_key.clone(), - }) - .or_default(), - None => &mut map, - }; - for changed_key in changed_keys.iter().cloned() { - insert_to_map(map, changed_key); - } - } - }); - if populated_from_cache { - return Ok((map, child_map)) - } - - let mut children_roots = BTreeMap::::new(); - { - let trie_storage = TrieBackendEssence::<_, H>::new( - crate::changes_trie::TrieBackendStorageAdapter(storage), - trie_root, - ); - - trie_storage.for_key_values_with_prefix(&child_prefix, |mut key, mut value| { - if let Ok(InputKey::ChildIndex::(trie_key)) = - Decode::decode(&mut key) - { - if let Ok(value) = >::decode(&mut value) { - let mut trie_root = ::Out::default(); - trie_root.as_mut().copy_from_slice(&value[..]); - children_roots.insert(trie_key.storage_key, trie_root); - } - } - }); - - trie_storage.for_keys_with_prefix(&extrinsic_prefix, |mut key| { - if let Ok(InputKey::ExtrinsicIndex::(trie_key)) = - Decode::decode(&mut key) - { - insert_to_map(&mut map, trie_key.key); - } - }); - - trie_storage.for_keys_with_prefix(&digest_prefix, |mut key| { - if let Ok(InputKey::DigestIndex::(trie_key)) = - Decode::decode(&mut key) - { - insert_to_map(&mut map, trie_key.key); - } - }); - } - - for (storage_key, trie_root) in children_roots.into_iter() { - let child_index = ChildIndex:: { block: block.clone(), storage_key }; - - let mut map = child_map.entry(child_index).or_default(); - let trie_storage = TrieBackendEssence::<_, H>::new( - crate::changes_trie::TrieBackendStorageAdapter(storage), - trie_root, - ); - trie_storage.for_keys_with_prefix(&extrinsic_prefix, |mut key| { - if let Ok(InputKey::ExtrinsicIndex::(trie_key)) = - Decode::decode(&mut key) - { - insert_to_map(&mut map, trie_key.key); - } - }); - - trie_storage.for_keys_with_prefix(&digest_prefix, |mut key| { - if let Ok(InputKey::DigestIndex::(trie_key)) = - Decode::decode(&mut key) - { - insert_to_map(&mut map, trie_key.key); - } - }); - } - Ok((map, child_map)) - }, - ) - .map(|(pairs, child_pairs)| { - ( - pairs.into_iter().map(|(_, (k, v))| InputPair::DigestIndex(k, v)), - child_pairs - .into_iter() - .map(|(sk, pairs)| { - (sk, pairs.into_iter().map(|(_, (k, v))| InputPair::DigestIndex(k, v))) - }) - .collect(), - digest_input_blocks, - ) - }) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{ - changes_trie::{ - build_cache::{IncompleteCacheAction, IncompleteCachedBuildData}, - storage::InMemoryStorage, - Configuration, RootsStorage, - }, - InMemoryBackend, - }; - use sp_core::Blake2Hasher; - - fn prepare_for_build( - zero: u64, - ) -> ( - InMemoryBackend, - InMemoryStorage, - OverlayedChanges, - Configuration, - ) { - let child_info_1 = ChildInfo::new_default(b"storage_key1"); - let child_info_2 = ChildInfo::new_default(b"storage_key2"); - let backend: InMemoryBackend<_> = vec![ - (vec![100], vec![255]), - (vec![101], vec![255]), - (vec![102], vec![255]), - (vec![103], vec![255]), - (vec![104], vec![255]), - (vec![105], vec![255]), - ] - .into_iter() - .collect::>() - .into(); - let prefixed_child_trie_key1 = child_info_1.prefixed_storage_key(); - let storage = InMemoryStorage::with_inputs( - vec![ - ( - zero + 1, - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 1, key: vec![100] }, - vec![1, 3], - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 1, key: vec![101] }, - vec![0, 2], - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 1, key: vec![105] }, - vec![0, 2, 4], - ), - ], - ), - ( - zero + 2, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 2, key: vec![102] }, - vec![0], - )], - ), - ( - zero + 3, - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 3, key: vec![100] }, - vec![0], - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 3, key: vec![105] }, - vec![1], - ), - ], - ), - ( - zero + 4, - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![100] }, - vec![0, 2, 3], - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![101] }, - vec![1], - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![103] }, - vec![0, 1], - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![100] }, - vec![zero + 1, zero + 3], - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![101] }, - vec![zero + 1], - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![102] }, - vec![zero + 2], - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![105] }, - vec![zero + 1, zero + 3], - ), - ], - ), - (zero + 5, Vec::new()), - ( - zero + 6, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 6, key: vec![105] }, - vec![2], - )], - ), - (zero + 7, Vec::new()), - ( - zero + 8, - vec![InputPair::DigestIndex( - DigestIndex { block: zero + 8, key: vec![105] }, - vec![zero + 6], - )], - ), - (zero + 9, Vec::new()), - (zero + 10, Vec::new()), - (zero + 11, Vec::new()), - (zero + 12, Vec::new()), - (zero + 13, Vec::new()), - (zero + 14, Vec::new()), - (zero + 15, Vec::new()), - ], - vec![( - prefixed_child_trie_key1.clone(), - vec![ - ( - zero + 1, - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 1, key: vec![100] }, - vec![1, 3], - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 1, key: vec![101] }, - vec![0, 2], - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 1, key: vec![105] }, - vec![0, 2, 4], - ), - ], - ), - ( - zero + 2, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 2, key: vec![102] }, - vec![0], - )], - ), - ( - zero + 4, - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 2, key: vec![102] }, - vec![0, 3], - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![102] }, - vec![zero + 2], - ), - ], - ), - ], - )], - ); - - let mut changes = OverlayedChanges::default(); - changes.set_collect_extrinsics(true); - - changes.start_transaction(); - - changes.set_extrinsic_index(1); - changes.set_storage(vec![101], Some(vec![203])); - - changes.set_extrinsic_index(3); - changes.set_storage(vec![100], Some(vec![202])); - changes.set_child_storage(&child_info_1, vec![100], Some(vec![202])); - - changes.commit_transaction().unwrap(); - - changes.set_extrinsic_index(0); - changes.set_storage(vec![100], Some(vec![0])); - changes.set_extrinsic_index(2); - changes.set_storage(vec![100], Some(vec![200])); - - changes.set_extrinsic_index(0); - changes.set_storage(vec![103], Some(vec![0])); - changes.set_extrinsic_index(1); - changes.set_storage(vec![103], None); - - changes.set_extrinsic_index(0); - changes.set_child_storage(&child_info_1, vec![100], Some(vec![0])); - changes.set_extrinsic_index(2); - changes.set_child_storage(&child_info_1, vec![100], Some(vec![200])); - - changes.set_extrinsic_index(0); - changes.set_child_storage(&child_info_2, vec![100], Some(vec![0])); - changes.set_extrinsic_index(2); - changes.set_child_storage(&child_info_2, vec![100], Some(vec![200])); - - changes.set_extrinsic_index(1); - - let config = Configuration { digest_interval: 4, digest_levels: 2 }; - - (backend, storage, changes, config) - } - - fn configuration_range<'a>( - config: &'a Configuration, - zero: u64, - ) -> ConfigurationRange<'a, u64> { - ConfigurationRange { config, zero, end: None } - } - - #[test] - fn build_changes_trie_nodes_on_non_digest_block() { - fn test_with_zero(zero: u64) { - let child_trie_key1 = ChildInfo::new_default(b"storage_key1").prefixed_storage_key(); - let child_trie_key2 = ChildInfo::new_default(b"storage_key2").prefixed_storage_key(); - let (backend, storage, changes, config) = prepare_for_build(zero); - let parent = AnchorBlockId { hash: Default::default(), number: zero + 4 }; - let changes_trie_nodes = prepare_input( - &backend, - &storage, - configuration_range(&config, zero), - &changes, - &parent, - ) - .unwrap(); - assert_eq!( - changes_trie_nodes.0.collect::>>(), - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 5, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 5, key: vec![101] }, - vec![1] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 5, key: vec![103] }, - vec![0, 1] - ), - ] - ); - assert_eq!( - changes_trie_nodes - .1 - .into_iter() - .map(|(k, v)| (k, v.collect::>())) - .collect::>(), - vec![ - ( - ChildIndex { block: zero + 5u64, storage_key: child_trie_key1 }, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 5u64, key: vec![100] }, - vec![0, 2, 3] - ),] - ), - ( - ChildIndex { block: zero + 5, storage_key: child_trie_key2 }, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 5, key: vec![100] }, - vec![0, 2] - ),] - ), - ] - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn build_changes_trie_nodes_on_digest_block_l1() { - fn test_with_zero(zero: u64) { - let child_trie_key1 = ChildInfo::new_default(b"storage_key1").prefixed_storage_key(); - let child_trie_key2 = ChildInfo::new_default(b"storage_key2").prefixed_storage_key(); - let (backend, storage, changes, config) = prepare_for_build(zero); - let parent = AnchorBlockId { hash: Default::default(), number: zero + 3 }; - let changes_trie_nodes = prepare_input( - &backend, - &storage, - configuration_range(&config, zero), - &changes, - &parent, - ) - .unwrap(); - assert_eq!( - changes_trie_nodes.0.collect::>>(), - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![101] }, - vec![1] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![103] }, - vec![0, 1] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![100] }, - vec![zero + 1, zero + 3] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![101] }, - vec![zero + 1] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![102] }, - vec![zero + 2] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![105] }, - vec![zero + 1, zero + 3] - ), - ] - ); - assert_eq!( - changes_trie_nodes - .1 - .into_iter() - .map(|(k, v)| (k, v.collect::>())) - .collect::>(), - vec![ - ( - ChildIndex { block: zero + 4u64, storage_key: child_trie_key1.clone() }, - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4u64, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![100] }, - vec![zero + 1] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![101] }, - vec![zero + 1] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![102] }, - vec![zero + 2] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![105] }, - vec![zero + 1] - ), - ] - ), - ( - ChildIndex { block: zero + 4, storage_key: child_trie_key2.clone() }, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![100] }, - vec![0, 2] - ),] - ), - ] - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn build_changes_trie_nodes_on_digest_block_l2() { - fn test_with_zero(zero: u64) { - let child_trie_key1 = ChildInfo::new_default(b"storage_key1").prefixed_storage_key(); - let child_trie_key2 = ChildInfo::new_default(b"storage_key2").prefixed_storage_key(); - let (backend, storage, changes, config) = prepare_for_build(zero); - let parent = AnchorBlockId { hash: Default::default(), number: zero + 15 }; - let changes_trie_nodes = prepare_input( - &backend, - &storage, - configuration_range(&config, zero), - &changes, - &parent, - ) - .unwrap(); - assert_eq!( - changes_trie_nodes.0.collect::>>(), - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 16, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 16, key: vec![101] }, - vec![1] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 16, key: vec![103] }, - vec![0, 1] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 16, key: vec![100] }, - vec![zero + 4] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 16, key: vec![101] }, - vec![zero + 4] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 16, key: vec![102] }, - vec![zero + 4] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 16, key: vec![103] }, - vec![zero + 4] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 16, key: vec![105] }, - vec![zero + 4, zero + 8] - ), - ] - ); - assert_eq!( - changes_trie_nodes - .1 - .into_iter() - .map(|(k, v)| (k, v.collect::>())) - .collect::>(), - vec![ - ( - ChildIndex { block: zero + 16u64, storage_key: child_trie_key1.clone() }, - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 16u64, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 16, key: vec![102] }, - vec![zero + 4] - ), - ] - ), - ( - ChildIndex { block: zero + 16, storage_key: child_trie_key2.clone() }, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 16, key: vec![100] }, - vec![0, 2] - ),] - ), - ] - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn build_changes_trie_nodes_on_skewed_digest_block() { - fn test_with_zero(zero: u64) { - let (backend, storage, changes, config) = prepare_for_build(zero); - let parent = AnchorBlockId { hash: Default::default(), number: zero + 10 }; - - let mut configuration_range = configuration_range(&config, zero); - let changes_trie_nodes = - prepare_input(&backend, &storage, configuration_range.clone(), &changes, &parent) - .unwrap(); - assert_eq!( - changes_trie_nodes.0.collect::>>(), - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 11, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 11, key: vec![101] }, - vec![1] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 11, key: vec![103] }, - vec![0, 1] - ), - ] - ); - - configuration_range.end = Some(zero + 11); - let changes_trie_nodes = - prepare_input(&backend, &storage, configuration_range, &changes, &parent).unwrap(); - assert_eq!( - changes_trie_nodes.0.collect::>>(), - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 11, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 11, key: vec![101] }, - vec![1] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 11, key: vec![103] }, - vec![0, 1] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 11, key: vec![100] }, - vec![zero + 4] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 11, key: vec![101] }, - vec![zero + 4] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 11, key: vec![102] }, - vec![zero + 4] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 11, key: vec![103] }, - vec![zero + 4] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 11, key: vec![105] }, - vec![zero + 4, zero + 8] - ), - ] - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn build_changes_trie_nodes_ignores_temporary_storage_values() { - fn test_with_zero(zero: u64) { - let child_trie_key1 = ChildInfo::new_default(b"storage_key1").prefixed_storage_key(); - let child_trie_key2 = ChildInfo::new_default(b"storage_key2").prefixed_storage_key(); - let (backend, storage, mut changes, config) = prepare_for_build(zero); - - // 110: missing from backend, set to None in overlay - changes.set_storage(vec![110], None); - - let parent = AnchorBlockId { hash: Default::default(), number: zero + 3 }; - let changes_trie_nodes = prepare_input( - &backend, - &storage, - configuration_range(&config, zero), - &changes, - &parent, - ) - .unwrap(); - assert_eq!( - changes_trie_nodes.0.collect::>>(), - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![101] }, - vec![1] - ), - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![103] }, - vec![0, 1] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![100] }, - vec![zero + 1, zero + 3] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![101] }, - vec![zero + 1] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![102] }, - vec![zero + 2] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![105] }, - vec![zero + 1, zero + 3] - ), - ] - ); - assert_eq!( - changes_trie_nodes - .1 - .into_iter() - .map(|(k, v)| (k, v.collect::>())) - .collect::>(), - vec![ - ( - ChildIndex { block: zero + 4u64, storage_key: child_trie_key1.clone() }, - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4u64, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![100] }, - vec![zero + 1] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![101] }, - vec![zero + 1] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![102] }, - vec![zero + 2] - ), - InputPair::DigestIndex( - DigestIndex { block: zero + 4, key: vec![105] }, - vec![zero + 1] - ), - ] - ), - ( - ChildIndex { block: zero + 4, storage_key: child_trie_key2.clone() }, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: zero + 4, key: vec![100] }, - vec![0, 2] - ),] - ), - ] - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn cache_is_used_when_changes_trie_is_built() { - let child_trie_key1 = ChildInfo::new_default(b"storage_key1").prefixed_storage_key(); - let child_trie_key2 = ChildInfo::new_default(b"storage_key2").prefixed_storage_key(); - let (backend, mut storage, changes, config) = prepare_for_build(0); - let parent = AnchorBlockId { hash: Default::default(), number: 15 }; - - // override some actual values from storage with values from the cache - // - // top-level storage: - // (keys 100, 101, 103, 105 are now missing from block#4 => they do not appear - // in l2 digest at block 16) - // - // "1" child storage: - // key 102 is now missing from block#4 => it doesn't appear in l2 digest at block 16 - // (keys 103, 104) are now added to block#4 => they appear in l2 digest at block 16 - // - // "2" child storage: - // (keys 105, 106) are now added to block#4 => they appear in l2 digest at block 16 - let trie_root4 = storage.root(&parent, 4).unwrap().unwrap(); - let cached_data4 = IncompleteCacheAction::CacheBuildData(IncompleteCachedBuildData::new()) - .set_digest_input_blocks(vec![1, 2, 3]) - .insert(None, vec![vec![100], vec![102]].into_iter().collect()) - .insert(Some(child_trie_key1.clone()), vec![vec![103], vec![104]].into_iter().collect()) - .insert(Some(child_trie_key2.clone()), vec![vec![105], vec![106]].into_iter().collect()) - .complete(4, &trie_root4); - storage.cache_mut().perform(cached_data4); - - let (root_changes_trie_nodes, child_changes_tries_nodes, _) = - prepare_input(&backend, &storage, configuration_range(&config, 0), &changes, &parent) - .unwrap(); - assert_eq!( - root_changes_trie_nodes.collect::>>(), - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: 16, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 16, key: vec![101] }, vec![1]), - InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 16, key: vec![103] }, vec![0, 1]), - InputPair::DigestIndex(DigestIndex { block: 16, key: vec![100] }, vec![4]), - InputPair::DigestIndex(DigestIndex { block: 16, key: vec![102] }, vec![4]), - InputPair::DigestIndex(DigestIndex { block: 16, key: vec![105] }, vec![8]), - ] - ); - - let child_changes_tries_nodes = child_changes_tries_nodes - .into_iter() - .map(|(k, i)| (k, i.collect::>())) - .collect::>(); - assert_eq!( - child_changes_tries_nodes - .get(&ChildIndex { block: 16u64, storage_key: child_trie_key1.clone() }) - .unwrap(), - &vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: 16u64, key: vec![100] }, - vec![0, 2, 3] - ), - InputPair::DigestIndex(DigestIndex { block: 16u64, key: vec![103] }, vec![4]), - InputPair::DigestIndex(DigestIndex { block: 16u64, key: vec![104] }, vec![4]), - ], - ); - assert_eq!( - child_changes_tries_nodes - .get(&ChildIndex { block: 16u64, storage_key: child_trie_key2.clone() }) - .unwrap(), - &vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: 16u64, key: vec![100] }, - vec![0, 2] - ), - InputPair::DigestIndex(DigestIndex { block: 16u64, key: vec![105] }, vec![4]), - InputPair::DigestIndex(DigestIndex { block: 16u64, key: vec![106] }, vec![4]), - ], - ); - } -} diff --git a/primitives/state-machine/src/changes_trie/build_cache.rs b/primitives/state-machine/src/changes_trie/build_cache.rs deleted file mode 100644 index 04820242d9d08..0000000000000 --- a/primitives/state-machine/src/changes_trie/build_cache.rs +++ /dev/null @@ -1,278 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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. - -//! Changes tries build cache. - -use std::collections::{HashMap, HashSet}; - -use crate::StorageKey; -use sp_core::storage::PrefixedStorageKey; - -/// Changes trie build cache. -/// -/// Helps to avoid read of changes tries from the database when digest trie -/// is built. It holds changed keys for every block (indexed by changes trie -/// root) that could be referenced by future digest items. For digest entries -/// it also holds keys covered by this digest. Entries for top level digests -/// are never created, because they'll never be used to build other digests. -/// -/// Entries are pruned from the cache once digest block that is using this entry -/// is inserted (because digest block will includes all keys from this entry). -/// When there's a fork, entries are pruned when first changes trie is inserted. -pub struct BuildCache { - /// Map of block (implies changes trie) number => changes trie root. - roots_by_number: HashMap, - /// Map of changes trie root => set of storage keys that are in this trie. - /// The `Option>` in inner `HashMap` stands for the child storage key. - /// If it is `None`, then the `HashSet` contains keys changed in top-level storage. - /// If it is `Some`, then the `HashSet` contains keys changed in child storage, identified by - /// the key. - changed_keys: HashMap, HashSet>>, -} - -/// The action to perform when block-with-changes-trie is imported. -#[derive(Debug, PartialEq)] -pub enum CacheAction { - /// Cache data that has been collected when CT has been built. - CacheBuildData(CachedBuildData), - /// Clear cache from all existing entries. - Clear, -} - -/// The data that has been cached during changes trie building. -#[derive(Debug, PartialEq)] -pub struct CachedBuildData { - block: N, - trie_root: H, - digest_input_blocks: Vec, - changed_keys: HashMap, HashSet>, -} - -/// The action to perform when block-with-changes-trie is imported. -#[derive(Debug, PartialEq)] -pub(crate) enum IncompleteCacheAction { - /// Cache data that has been collected when CT has been built. - CacheBuildData(IncompleteCachedBuildData), - /// Clear cache from all existing entries. - Clear, -} - -/// The data (without changes trie root) that has been cached during changes trie building. -#[derive(Debug, PartialEq)] -pub(crate) struct IncompleteCachedBuildData { - digest_input_blocks: Vec, - changed_keys: HashMap, HashSet>, -} - -impl BuildCache -where - N: Eq + ::std::hash::Hash, - H: Eq + ::std::hash::Hash + Clone, -{ - /// Create new changes trie build cache. - pub fn new() -> Self { - BuildCache { roots_by_number: HashMap::new(), changed_keys: HashMap::new() } - } - - /// Get cached changed keys for changes trie with given root. - pub fn get( - &self, - root: &H, - ) -> Option<&HashMap, HashSet>> { - self.changed_keys.get(&root) - } - - /// Execute given functor with cached entry for given block. - /// Returns true if the functor has been called and false otherwise. - pub fn with_changed_keys( - &self, - root: &H, - functor: &mut dyn FnMut(&HashMap, HashSet>), - ) -> bool { - match self.changed_keys.get(&root) { - Some(changed_keys) => { - functor(changed_keys); - true - }, - None => false, - } - } - - /// Insert data into cache. - pub fn perform(&mut self, action: CacheAction) { - match action { - CacheAction::CacheBuildData(data) => { - self.roots_by_number.insert(data.block, data.trie_root.clone()); - self.changed_keys.insert(data.trie_root, data.changed_keys); - - for digest_input_block in data.digest_input_blocks { - let digest_input_block_hash = self.roots_by_number.remove(&digest_input_block); - if let Some(digest_input_block_hash) = digest_input_block_hash { - self.changed_keys.remove(&digest_input_block_hash); - } - } - }, - CacheAction::Clear => { - self.roots_by_number.clear(); - self.changed_keys.clear(); - }, - } - } -} - -impl IncompleteCacheAction { - /// Returns true if we need to collect changed keys for this action. - pub fn collects_changed_keys(&self) -> bool { - match *self { - IncompleteCacheAction::CacheBuildData(_) => true, - IncompleteCacheAction::Clear => false, - } - } - - /// Complete cache action with computed changes trie root. - pub(crate) fn complete(self, block: N, trie_root: &H) -> CacheAction { - match self { - IncompleteCacheAction::CacheBuildData(build_data) => - CacheAction::CacheBuildData(build_data.complete(block, trie_root.clone())), - IncompleteCacheAction::Clear => CacheAction::Clear, - } - } - - /// Set numbers of blocks that are superseded by this new entry. - /// - /// If/when this build data is committed to the cache, entries for these blocks - /// will be removed from the cache. - pub(crate) fn set_digest_input_blocks(self, digest_input_blocks: Vec) -> Self { - match self { - IncompleteCacheAction::CacheBuildData(build_data) => - IncompleteCacheAction::CacheBuildData( - build_data.set_digest_input_blocks(digest_input_blocks), - ), - IncompleteCacheAction::Clear => IncompleteCacheAction::Clear, - } - } - - /// Insert changed keys of given storage into cached data. - pub(crate) fn insert( - self, - storage_key: Option, - changed_keys: HashSet, - ) -> Self { - match self { - IncompleteCacheAction::CacheBuildData(build_data) => - IncompleteCacheAction::CacheBuildData(build_data.insert(storage_key, changed_keys)), - IncompleteCacheAction::Clear => IncompleteCacheAction::Clear, - } - } -} - -impl IncompleteCachedBuildData { - /// Create new cached data. - pub(crate) fn new() -> Self { - IncompleteCachedBuildData { digest_input_blocks: Vec::new(), changed_keys: HashMap::new() } - } - - fn complete(self, block: N, trie_root: H) -> CachedBuildData { - CachedBuildData { - block, - trie_root, - digest_input_blocks: self.digest_input_blocks, - changed_keys: self.changed_keys, - } - } - - fn set_digest_input_blocks(mut self, digest_input_blocks: Vec) -> Self { - self.digest_input_blocks = digest_input_blocks; - self - } - - fn insert( - mut self, - storage_key: Option, - changed_keys: HashSet, - ) -> Self { - self.changed_keys.insert(storage_key, changed_keys); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn updated_keys_are_stored_when_non_top_level_digest_is_built() { - let mut data = IncompleteCachedBuildData::::new(); - data = data.insert(None, vec![vec![1]].into_iter().collect()); - assert_eq!(data.changed_keys.len(), 1); - - let mut cache = BuildCache::new(); - cache.perform(CacheAction::CacheBuildData(data.complete(1, 1))); - assert_eq!(cache.changed_keys.len(), 1); - assert_eq!( - cache.get(&1).unwrap().clone(), - vec![(None, vec![vec![1]].into_iter().collect())].into_iter().collect(), - ); - } - - #[test] - fn obsolete_entries_are_purged_when_new_ct_is_built() { - let mut cache = BuildCache::::new(); - cache.perform(CacheAction::CacheBuildData( - IncompleteCachedBuildData::new() - .insert(None, vec![vec![1]].into_iter().collect()) - .complete(1, 1), - )); - cache.perform(CacheAction::CacheBuildData( - IncompleteCachedBuildData::new() - .insert(None, vec![vec![2]].into_iter().collect()) - .complete(2, 2), - )); - cache.perform(CacheAction::CacheBuildData( - IncompleteCachedBuildData::new() - .insert(None, vec![vec![3]].into_iter().collect()) - .complete(3, 3), - )); - - assert_eq!(cache.changed_keys.len(), 3); - - cache.perform(CacheAction::CacheBuildData( - IncompleteCachedBuildData::new() - .set_digest_input_blocks(vec![1, 2, 3]) - .complete(4, 4), - )); - - assert_eq!(cache.changed_keys.len(), 1); - - cache.perform(CacheAction::CacheBuildData( - IncompleteCachedBuildData::new() - .insert(None, vec![vec![8]].into_iter().collect()) - .complete(8, 8), - )); - cache.perform(CacheAction::CacheBuildData( - IncompleteCachedBuildData::new() - .insert(None, vec![vec![12]].into_iter().collect()) - .complete(12, 12), - )); - - assert_eq!(cache.changed_keys.len(), 3); - - cache.perform(CacheAction::Clear); - - assert_eq!(cache.changed_keys.len(), 0); - } -} diff --git a/primitives/state-machine/src/changes_trie/build_iterator.rs b/primitives/state-machine/src/changes_trie/build_iterator.rs deleted file mode 100644 index 62bb00a2f8829..0000000000000 --- a/primitives/state-machine/src/changes_trie/build_iterator.rs +++ /dev/null @@ -1,487 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! Structures and functions to return blocks whose changes are to be included -//! in given block's changes trie. - -use crate::changes_trie::{BlockNumber, ConfigurationRange}; -use num_traits::Zero; - -/// Returns iterator of OTHER blocks that are required for inclusion into -/// changes trie of given block. Blocks are guaranteed to be returned in -/// ascending order. -/// -/// Skewed digest is built IF block >= config.end. -pub fn digest_build_iterator<'a, Number: BlockNumber>( - config: ConfigurationRange<'a, Number>, - block: Number, -) -> DigestBuildIterator { - // prepare digest build parameters - let (_, _, digest_step) = match config.config.digest_level_at_block(config.zero, block.clone()) - { - Some((current_level, digest_interval, digest_step)) => - (current_level, digest_interval, digest_step), - None => return DigestBuildIterator::empty(), - }; - - DigestBuildIterator::new( - block.clone(), - config.end.unwrap_or(block), - config.config.digest_interval, - digest_step, - ) -} - -/// Changes trie build iterator that returns numbers of OTHER blocks that are -/// required for inclusion into changes trie of given block. -#[derive(Debug)] -pub struct DigestBuildIterator { - /// Block we're building changes trie for. It could (logically) be a post-end block if we are - /// creating skewed digest. - block: Number, - /// Block that is a last block where current configuration is active. We have never yet created - /// anything after this block => digest that we're creating can't reference any blocks that are - /// >= end. - end: Number, - /// Interval of L1 digest blocks. - digest_interval: u32, - /// Max step that could be used when digest is created. - max_step: u32, - - // Mutable data below: - /// Step of current blocks range. - current_step: u32, - /// Reverse step of current blocks range. - current_step_reverse: u32, - /// Current blocks range. - current_range: Option>, - /// Last block that we have returned. - last_block: Option, -} - -impl DigestBuildIterator { - /// Create new digest build iterator. - pub fn new(block: Number, end: Number, digest_interval: u32, max_step: u32) -> Self { - DigestBuildIterator { - block, - end, - digest_interval, - max_step, - current_step: max_step, - current_step_reverse: 0, - current_range: None, - last_block: None, - } - } - - /// Create empty digest build iterator. - pub fn empty() -> Self { - Self::new(Zero::zero(), Zero::zero(), 0, 0) - } -} - -impl Iterator for DigestBuildIterator { - type Item = Number; - - fn next(&mut self) -> Option { - // when we're building skewed digest, we might want to skip some blocks if - // they're not covered by current configuration - loop { - if let Some(next) = self.current_range.as_mut().and_then(|iter| iter.next()) { - if next < self.end { - self.last_block = Some(next.clone()); - return Some(next) - } - } - - // we are safe to use non-checking mul/sub versions here because: - // DigestBuildIterator is created only by internal function that is checking - // that all multiplications/subtractions are safe within max_step limit - - let next_step_reverse = if self.current_step_reverse == 0 { - 1 - } else { - self.current_step_reverse * self.digest_interval - }; - if next_step_reverse > self.max_step { - return None - } - - self.current_step_reverse = next_step_reverse; - self.current_range = Some(BlocksRange::new( - match self.last_block.clone() { - Some(last_block) => last_block + self.current_step.into(), - None => - self.block.clone() - - (self.current_step * self.digest_interval - self.current_step).into(), - }, - self.block.clone(), - self.current_step.into(), - )); - - self.current_step = self.current_step / self.digest_interval; - if self.current_step == 0 { - self.current_step = 1; - } - } - } -} - -/// Blocks range iterator with builtin step_by support. -#[derive(Debug)] -struct BlocksRange { - current: Number, - end: Number, - step: Number, -} - -impl BlocksRange { - pub fn new(begin: Number, end: Number, step: Number) -> Self { - BlocksRange { current: begin, end, step } - } -} - -impl Iterator for BlocksRange { - type Item = Number; - - fn next(&mut self) -> Option { - if self.current >= self.end { - return None - } - - let current = Some(self.current.clone()); - self.current += self.step.clone(); - current - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::changes_trie::Configuration; - - fn digest_build_iterator( - digest_interval: u32, - digest_levels: u32, - zero: u64, - block: u64, - end: Option, - ) -> DigestBuildIterator { - super::digest_build_iterator( - ConfigurationRange { - config: &Configuration { digest_interval, digest_levels }, - zero, - end, - }, - block, - ) - } - - fn digest_build_iterator_basic( - digest_interval: u32, - digest_levels: u32, - zero: u64, - block: u64, - ) -> (u64, u32, u32) { - let iter = digest_build_iterator(digest_interval, digest_levels, zero, block, None); - (iter.block, iter.digest_interval, iter.max_step) - } - - fn digest_build_iterator_blocks( - digest_interval: u32, - digest_levels: u32, - zero: u64, - block: u64, - end: Option, - ) -> Vec { - digest_build_iterator(digest_interval, digest_levels, zero, block, end).collect() - } - - #[test] - fn suggest_digest_inclusion_returns_empty_iterator() { - fn test_with_zero(zero: u64) { - let empty = (0, 0, 0); - assert_eq!(digest_build_iterator_basic(4, 16, zero, zero + 0), empty, "block is 0"); - assert_eq!( - digest_build_iterator_basic(0, 16, zero, zero + 64), - empty, - "digest_interval is 0" - ); - assert_eq!( - digest_build_iterator_basic(1, 16, zero, zero + 64), - empty, - "digest_interval is 1" - ); - assert_eq!( - digest_build_iterator_basic(4, 0, zero, zero + 64), - empty, - "digest_levels is 0" - ); - assert_eq!( - digest_build_iterator_basic(4, 16, zero, zero + 1), - empty, - "digest is not required for this block", - ); - assert_eq!( - digest_build_iterator_basic(4, 16, zero, zero + 2), - empty, - "digest is not required for this block", - ); - assert_eq!( - digest_build_iterator_basic(4, 16, zero, zero + 15), - empty, - "digest is not required for this block", - ); - assert_eq!( - digest_build_iterator_basic(4, 16, zero, zero + 17), - empty, - "digest is not required for this block", - ); - assert_eq!( - digest_build_iterator_basic(::std::u32::MAX / 2 + 1, 16, zero, ::std::u64::MAX,), - empty, - "digest_interval * 2 is greater than u64::MAX" - ); - } - - test_with_zero(0); - test_with_zero(1); - test_with_zero(2); - test_with_zero(4); - test_with_zero(17); - } - - #[test] - fn suggest_digest_inclusion_returns_level1_iterator() { - fn test_with_zero(zero: u64) { - assert_eq!( - digest_build_iterator_basic(16, 1, zero, zero + 16), - (zero + 16, 16, 1), - "!(block % interval) && first digest level == block", - ); - assert_eq!( - digest_build_iterator_basic(16, 1, zero, zero + 256), - (zero + 256, 16, 1), - "!(block % interval^2), but there's only 1 digest level", - ); - assert_eq!( - digest_build_iterator_basic(16, 2, zero, zero + 32), - (zero + 32, 16, 1), - "second level digest is not required for this block", - ); - assert_eq!( - digest_build_iterator_basic(16, 3, zero, zero + 4080), - (zero + 4080, 16, 1), - "second && third level digest are not required for this block", - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn suggest_digest_inclusion_returns_level2_iterator() { - fn test_with_zero(zero: u64) { - assert_eq!( - digest_build_iterator_basic(16, 2, zero, zero + 256), - (zero + 256, 16, 16), - "second level digest", - ); - assert_eq!( - digest_build_iterator_basic(16, 2, zero, zero + 4096), - (zero + 4096, 16, 16), - "!(block % interval^3), but there's only 2 digest levels", - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn suggest_digest_inclusion_returns_level3_iterator() { - fn test_with_zero(zero: u64) { - assert_eq!( - digest_build_iterator_basic(16, 3, zero, zero + 4096), - (zero + 4096, 16, 256), - "third level digest: beginning", - ); - assert_eq!( - digest_build_iterator_basic(16, 3, zero, zero + 8192), - (zero + 8192, 16, 256), - "third level digest: next", - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn digest_iterator_returns_level1_blocks() { - fn test_with_zero(zero: u64) { - assert_eq!( - digest_build_iterator_blocks(16, 1, zero, zero + 16, None), - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - .iter() - .map(|item| zero + item) - .collect::>() - ); - assert_eq!( - digest_build_iterator_blocks(16, 1, zero, zero + 256, None), - [241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255] - .iter() - .map(|item| zero + item) - .collect::>() - ); - assert_eq!( - digest_build_iterator_blocks(16, 2, zero, zero + 32, None), - [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31] - .iter() - .map(|item| zero + item) - .collect::>() - ); - assert_eq!( - digest_build_iterator_blocks(16, 3, zero, zero + 4080, None), - [ - 4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073, 4074, 4075, 4076, 4077, - 4078, 4079 - ] - .iter() - .map(|item| zero + item) - .collect::>() - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn digest_iterator_returns_level1_and_level2_blocks() { - fn test_with_zero(zero: u64) { - assert_eq!( - digest_build_iterator_blocks(16, 2, zero, zero + 256, None), - [ - // level2 points to previous 16-1 level1 digests: - 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, - // level2 is a level1 digest of 16-1 previous blocks: - 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, - ] - .iter() - .map(|item| zero + item) - .collect::>(), - ); - assert_eq!( - digest_build_iterator_blocks(16, 2, zero, zero + 4096, None), - [ - // level2 points to previous 16-1 level1 digests: - 3856, 3872, 3888, 3904, 3920, 3936, 3952, 3968, 3984, 4000, 4016, 4032, 4048, - 4064, 4080, // level2 is a level1 digest of 16-1 previous blocks: - 4081, 4082, 4083, 4084, 4085, 4086, 4087, 4088, 4089, 4090, 4091, 4092, 4093, - 4094, 4095, - ] - .iter() - .map(|item| zero + item) - .collect::>(), - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn digest_iterator_returns_level1_and_level2_and_level3_blocks() { - fn test_with_zero(zero: u64) { - assert_eq!( - digest_build_iterator_blocks(16, 3, zero, zero + 4096, None), - [ - // level3 points to previous 16-1 level2 digests: - 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, // level3 points to previous 16-1 level1 digests: - 3856, 3872, 3888, 3904, 3920, 3936, 3952, 3968, 3984, 4000, 4016, 4032, 4048, - 4064, 4080, // level3 is a level1 digest of 16-1 previous blocks: - 4081, 4082, 4083, 4084, 4085, 4086, 4087, 4088, 4089, 4090, 4091, 4092, 4093, - 4094, 4095, - ] - .iter() - .map(|item| zero + item) - .collect::>(), - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn digest_iterator_returns_skewed_digest_blocks() { - fn test_with_zero(zero: u64) { - assert_eq!( - digest_build_iterator_blocks(16, 3, zero, zero + 4096, Some(zero + 1338)), - [ - // level3 MUST point to previous 16-1 level2 digests, BUT there are only 5: - 256, 512, 768, 1024, 1280, - // level3 MUST point to previous 16-1 level1 digests, BUT there are only 3: - 1296, 1312, 1328, - // level3 MUST be a level1 digest of 16-1 previous blocks, BUT there are only - // 9: - 1329, 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, - ] - .iter() - .map(|item| zero + item) - .collect::>(), - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } - - #[test] - fn digest_iterator_returns_skewed_digest_blocks_skipping_level() { - fn test_with_zero(zero: u64) { - assert_eq!( - digest_build_iterator_blocks(16, 3, zero, zero + 4096, Some(zero + 1284)), - [ - // level3 MUST point to previous 16-1 level2 digests, BUT there are only 5: - 256, 512, 768, 1024, 1280, - // level3 MUST point to previous 16-1 level1 digests, BUT there are NO ANY - // L1-digests: level3 MUST be a level1 digest of 16-1 previous blocks, BUT - // there are only 3: - 1281, 1282, 1283, - ] - .iter() - .map(|item| zero + item) - .collect::>(), - ); - } - - test_with_zero(0); - test_with_zero(16); - test_with_zero(17); - } -} diff --git a/primitives/state-machine/src/changes_trie/changes_iterator.rs b/primitives/state-machine/src/changes_trie/changes_iterator.rs deleted file mode 100644 index 9343a226a3aa8..0000000000000 --- a/primitives/state-machine/src/changes_trie/changes_iterator.rs +++ /dev/null @@ -1,748 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! Functions + iterator that traverses changes tries and returns all -//! (block, extrinsic) pairs where given key has been changed. - -use crate::{ - changes_trie::{ - input::{ChildIndex, DigestIndex, DigestIndexValue, ExtrinsicIndex, ExtrinsicIndexValue}, - storage::{InMemoryStorage, TrieBackendAdapter}, - surface_iterator::{surface_iterator, SurfaceIterator}, - AnchorBlockId, BlockNumber, ConfigurationRange, RootsStorage, Storage, - }, - proving_backend::ProvingBackendRecorder, - trie_backend_essence::TrieBackendEssence, -}; -use codec::{Codec, Decode, Encode}; -use hash_db::Hasher; -use num_traits::Zero; -use sp_core::storage::PrefixedStorageKey; -use sp_trie::Recorder; -use std::{cell::RefCell, collections::VecDeque}; - -/// Return changes of given key at given blocks range. -/// `max` is the number of best known block. -/// Changes are returned in descending order (i.e. last block comes first). -pub fn key_changes<'a, H: Hasher, Number: BlockNumber>( - config: ConfigurationRange<'a, Number>, - storage: &'a dyn Storage, - begin: Number, - end: &'a AnchorBlockId, - max: Number, - storage_key: Option<&'a PrefixedStorageKey>, - key: &'a [u8], -) -> Result, String> { - // we can't query any roots before root - let max = std::cmp::min(max, end.number.clone()); - - Ok(DrilldownIterator { - essence: DrilldownIteratorEssence { - storage_key, - key, - roots_storage: storage.as_roots_storage(), - storage, - begin: begin.clone(), - end, - config: config.clone(), - surface: surface_iterator(config, max, begin, end.number.clone())?, - - extrinsics: Default::default(), - blocks: Default::default(), - - _hasher: ::std::marker::PhantomData::::default(), - }, - }) -} - -/// Returns proof of changes of given key at given blocks range. -/// `max` is the number of best known block. -pub fn key_changes_proof<'a, H: Hasher, Number: BlockNumber>( - config: ConfigurationRange<'a, Number>, - storage: &dyn Storage, - begin: Number, - end: &AnchorBlockId, - max: Number, - storage_key: Option<&PrefixedStorageKey>, - key: &[u8], -) -> Result>, String> -where - H::Out: Codec, -{ - // we can't query any roots before root - let max = std::cmp::min(max, end.number.clone()); - - let mut iter = ProvingDrilldownIterator { - essence: DrilldownIteratorEssence { - storage_key, - key, - roots_storage: storage.as_roots_storage(), - storage, - begin: begin.clone(), - end, - config: config.clone(), - surface: surface_iterator(config, max, begin, end.number.clone())?, - - extrinsics: Default::default(), - blocks: Default::default(), - - _hasher: ::std::marker::PhantomData::::default(), - }, - proof_recorder: Default::default(), - }; - - // iterate to collect proof - while let Some(item) = iter.next() { - item?; - } - - Ok(iter.extract_proof()) -} - -/// Check key changes proof and return changes of the key at given blocks range. -/// `max` is the number of best known block. -/// Changes are returned in descending order (i.e. last block comes first). -pub fn key_changes_proof_check<'a, H: Hasher, Number: BlockNumber>( - config: ConfigurationRange<'a, Number>, - roots_storage: &dyn RootsStorage, - proof: Vec>, - begin: Number, - end: &AnchorBlockId, - max: Number, - storage_key: Option<&PrefixedStorageKey>, - key: &[u8], -) -> Result, String> -where - H::Out: Encode, -{ - key_changes_proof_check_with_db( - config, - roots_storage, - &InMemoryStorage::with_proof(proof), - begin, - end, - max, - storage_key, - key, - ) -} - -/// Similar to the `key_changes_proof_check` function, but works with prepared proof storage. -pub fn key_changes_proof_check_with_db<'a, H: Hasher, Number: BlockNumber>( - config: ConfigurationRange<'a, Number>, - roots_storage: &dyn RootsStorage, - proof_db: &InMemoryStorage, - begin: Number, - end: &AnchorBlockId, - max: Number, - storage_key: Option<&PrefixedStorageKey>, - key: &[u8], -) -> Result, String> -where - H::Out: Encode, -{ - // we can't query any roots before root - let max = std::cmp::min(max, end.number.clone()); - - DrilldownIterator { - essence: DrilldownIteratorEssence { - storage_key, - key, - roots_storage, - storage: proof_db, - begin: begin.clone(), - end, - config: config.clone(), - surface: surface_iterator(config, max, begin, end.number.clone())?, - - extrinsics: Default::default(), - blocks: Default::default(), - - _hasher: ::std::marker::PhantomData::::default(), - }, - } - .collect() -} - -/// Drilldown iterator - receives 'digest points' from surface iterator and explores -/// every point until extrinsic is found. -pub struct DrilldownIteratorEssence<'a, H, Number> -where - H: Hasher, - Number: BlockNumber, - H::Out: 'a, -{ - storage_key: Option<&'a PrefixedStorageKey>, - key: &'a [u8], - roots_storage: &'a dyn RootsStorage, - storage: &'a dyn Storage, - begin: Number, - end: &'a AnchorBlockId, - config: ConfigurationRange<'a, Number>, - surface: SurfaceIterator<'a, Number>, - - extrinsics: VecDeque<(Number, u32)>, - blocks: VecDeque<(Number, Option)>, - - _hasher: ::std::marker::PhantomData, -} - -impl<'a, H, Number> DrilldownIteratorEssence<'a, H, Number> -where - H: Hasher, - Number: BlockNumber, - H::Out: 'a, -{ - pub fn next(&mut self, trie_reader: F) -> Option> - where - F: FnMut(&dyn Storage, H::Out, &[u8]) -> Result>, String>, - { - match self.do_next(trie_reader) { - Ok(Some(res)) => Some(Ok(res)), - Ok(None) => None, - Err(err) => Some(Err(err)), - } - } - - fn do_next(&mut self, mut trie_reader: F) -> Result, String> - where - F: FnMut(&dyn Storage, H::Out, &[u8]) -> Result>, String>, - { - loop { - if let Some((block, extrinsic)) = self.extrinsics.pop_front() { - return Ok(Some((block, extrinsic))) - } - - if let Some((block, level)) = self.blocks.pop_front() { - // not having a changes trie root is an error because: - // we never query roots for future blocks - // AND trie roots for old blocks are known (both on full + light node) - let trie_root = - self.roots_storage.root(&self.end, block.clone())?.ok_or_else(|| { - format!("Changes trie root for block {} is not found", block.clone()) - })?; - let trie_root = if let Some(storage_key) = self.storage_key { - let child_key = - ChildIndex { block: block.clone(), storage_key: storage_key.clone() } - .encode(); - if let Some(trie_root) = trie_reader(self.storage, trie_root, &child_key)? - .and_then(|v| >::decode(&mut &v[..]).ok()) - .map(|v| { - let mut hash = H::Out::default(); - hash.as_mut().copy_from_slice(&v[..]); - hash - }) { - trie_root - } else { - continue - } - } else { - trie_root - }; - - // only return extrinsics for blocks before self.max - // most of blocks will be filtered out before pushing to `self.blocks` - // here we just throwing away changes at digest blocks we're processing - debug_assert!( - block >= self.begin, - "We shall not touch digests earlier than a range' begin" - ); - if block <= self.end.number { - let extrinsics_key = - ExtrinsicIndex { block: block.clone(), key: self.key.to_vec() }.encode(); - let extrinsics = trie_reader(self.storage, trie_root, &extrinsics_key); - if let Some(extrinsics) = extrinsics? { - if let Ok(extrinsics) = ExtrinsicIndexValue::decode(&mut &extrinsics[..]) { - self.extrinsics - .extend(extrinsics.into_iter().rev().map(|e| (block.clone(), e))); - } - } - } - - let blocks_key = - DigestIndex { block: block.clone(), key: self.key.to_vec() }.encode(); - let blocks = trie_reader(self.storage, trie_root, &blocks_key); - if let Some(blocks) = blocks? { - if let Ok(blocks) = >::decode(&mut &blocks[..]) { - // filter level0 blocks here because we tend to use digest blocks, - // AND digest block changes could also include changes for out-of-range - // blocks - let begin = self.begin.clone(); - let end = self.end.number.clone(); - let config = self.config.clone(); - self.blocks.extend( - blocks - .into_iter() - .rev() - .filter(|b| { - level.map(|level| level > 1).unwrap_or(true) || - (*b >= begin && *b <= end) - }) - .map(|b| { - let prev_level = - level.map(|level| Some(level - 1)).unwrap_or_else(|| { - Some( - config - .config - .digest_level_at_block( - config.zero.clone(), - b.clone(), - ) - .map(|(level, _, _)| level) - .unwrap_or_else(|| Zero::zero()), - ) - }); - (b, prev_level) - }), - ); - } - } - - continue - } - - match self.surface.next() { - Some(Ok(block)) => self.blocks.push_back(block), - Some(Err(err)) => return Err(err), - None => return Ok(None), - } - } - } -} - -/// Exploring drilldown operator. -pub struct DrilldownIterator<'a, H, Number> -where - Number: BlockNumber, - H: Hasher, - H::Out: 'a, -{ - essence: DrilldownIteratorEssence<'a, H, Number>, -} - -impl<'a, H: Hasher, Number: BlockNumber> Iterator for DrilldownIterator<'a, H, Number> -where - H::Out: Encode, -{ - type Item = Result<(Number, u32), String>; - - fn next(&mut self) -> Option { - self.essence.next(|storage, root, key| { - TrieBackendEssence::<_, H>::new(TrieBackendAdapter::new(storage), root).storage(key) - }) - } -} - -/// Proving drilldown iterator. -struct ProvingDrilldownIterator<'a, H, Number> -where - Number: BlockNumber, - H: Hasher, - H::Out: 'a, -{ - essence: DrilldownIteratorEssence<'a, H, Number>, - proof_recorder: RefCell>, -} - -impl<'a, H, Number> ProvingDrilldownIterator<'a, H, Number> -where - Number: BlockNumber, - H: Hasher, - H::Out: 'a, -{ - /// Consume the iterator, extracting the gathered proof in lexicographical order - /// by value. - pub fn extract_proof(self) -> Vec> { - self.proof_recorder - .into_inner() - .drain() - .into_iter() - .map(|n| n.data.to_vec()) - .collect() - } -} - -impl<'a, H, Number> Iterator for ProvingDrilldownIterator<'a, H, Number> -where - Number: BlockNumber, - H: Hasher, - H::Out: 'a + Codec, -{ - type Item = Result<(Number, u32), String>; - - fn next(&mut self) -> Option { - let proof_recorder = &mut *self - .proof_recorder - .try_borrow_mut() - .expect("only fails when already borrowed; storage() is non-reentrant; qed"); - self.essence.next(|storage, root, key| { - ProvingBackendRecorder::<_, H> { - backend: &TrieBackendEssence::new(TrieBackendAdapter::new(storage), root), - proof_recorder, - } - .storage(key) - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::changes_trie::{input::InputPair, storage::InMemoryStorage, Configuration}; - use sp_runtime::traits::BlakeTwo256; - use std::iter::FromIterator; - - fn child_key() -> PrefixedStorageKey { - let child_info = sp_core::storage::ChildInfo::new_default(&b"1"[..]); - child_info.prefixed_storage_key() - } - - fn prepare_for_drilldown() -> (Configuration, InMemoryStorage) { - let config = Configuration { digest_interval: 4, digest_levels: 2 }; - let backend = InMemoryStorage::with_inputs( - vec![ - // digest: 1..4 => [(3, 0)] - (1, vec![]), - (2, vec![]), - ( - 3, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: 3, key: vec![42] }, - vec![0], - )], - ), - (4, vec![InputPair::DigestIndex(DigestIndex { block: 4, key: vec![42] }, vec![3])]), - // digest: 5..8 => [(6, 3), (8, 1+2)] - (5, vec![]), - ( - 6, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: 6, key: vec![42] }, - vec![3], - )], - ), - (7, vec![]), - ( - 8, - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: 8, key: vec![42] }, - vec![1, 2], - ), - InputPair::DigestIndex(DigestIndex { block: 8, key: vec![42] }, vec![6]), - ], - ), - // digest: 9..12 => [] - (9, vec![]), - (10, vec![]), - (11, vec![]), - (12, vec![]), - // digest: 0..16 => [4, 8] - (13, vec![]), - (14, vec![]), - (15, vec![]), - ( - 16, - vec![InputPair::DigestIndex( - DigestIndex { block: 16, key: vec![42] }, - vec![4, 8], - )], - ), - ], - vec![( - child_key(), - vec![ - ( - 1, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: 1, key: vec![42] }, - vec![0], - )], - ), - ( - 2, - vec![InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: 2, key: vec![42] }, - vec![3], - )], - ), - ( - 16, - vec![ - InputPair::ExtrinsicIndex( - ExtrinsicIndex { block: 16, key: vec![42] }, - vec![5], - ), - InputPair::DigestIndex( - DigestIndex { block: 16, key: vec![42] }, - vec![2], - ), - ], - ), - ], - )], - ); - - (config, backend) - } - - fn configuration_range<'a>( - config: &'a Configuration, - zero: u64, - ) -> ConfigurationRange<'a, u64> { - ConfigurationRange { config, zero, end: None } - } - - #[test] - fn drilldown_iterator_works() { - let (config, storage) = prepare_for_drilldown(); - let drilldown_result = key_changes::( - configuration_range(&config, 0), - &storage, - 1, - &AnchorBlockId { hash: Default::default(), number: 16 }, - 16, - None, - &[42], - ) - .and_then(Result::from_iter); - assert_eq!(drilldown_result, Ok(vec![(8, 2), (8, 1), (6, 3), (3, 0)])); - - let drilldown_result = key_changes::( - configuration_range(&config, 0), - &storage, - 1, - &AnchorBlockId { hash: Default::default(), number: 2 }, - 4, - None, - &[42], - ) - .and_then(Result::from_iter); - assert_eq!(drilldown_result, Ok(vec![])); - - let drilldown_result = key_changes::( - configuration_range(&config, 0), - &storage, - 1, - &AnchorBlockId { hash: Default::default(), number: 3 }, - 4, - None, - &[42], - ) - .and_then(Result::from_iter); - assert_eq!(drilldown_result, Ok(vec![(3, 0)])); - - let drilldown_result = key_changes::( - configuration_range(&config, 0), - &storage, - 1, - &AnchorBlockId { hash: Default::default(), number: 7 }, - 7, - None, - &[42], - ) - .and_then(Result::from_iter); - assert_eq!(drilldown_result, Ok(vec![(6, 3), (3, 0)])); - - let drilldown_result = key_changes::( - configuration_range(&config, 0), - &storage, - 7, - &AnchorBlockId { hash: Default::default(), number: 8 }, - 8, - None, - &[42], - ) - .and_then(Result::from_iter); - assert_eq!(drilldown_result, Ok(vec![(8, 2), (8, 1)])); - - let drilldown_result = key_changes::( - configuration_range(&config, 0), - &storage, - 5, - &AnchorBlockId { hash: Default::default(), number: 7 }, - 8, - None, - &[42], - ) - .and_then(Result::from_iter); - assert_eq!(drilldown_result, Ok(vec![(6, 3)])); - } - - #[test] - fn drilldown_iterator_fails_when_storage_fails() { - let (config, storage) = prepare_for_drilldown(); - storage.clear_storage(); - - assert!(key_changes::( - configuration_range(&config, 0), - &storage, - 1, - &AnchorBlockId { hash: Default::default(), number: 100 }, - 1000, - None, - &[42], - ) - .and_then(|i| i.collect::, _>>()) - .is_err()); - - assert!(key_changes::( - configuration_range(&config, 0), - &storage, - 1, - &AnchorBlockId { hash: Default::default(), number: 100 }, - 1000, - Some(&child_key()), - &[42], - ) - .and_then(|i| i.collect::, _>>()) - .is_err()); - } - - #[test] - fn drilldown_iterator_fails_when_range_is_invalid() { - let (config, storage) = prepare_for_drilldown(); - assert!(key_changes::( - configuration_range(&config, 0), - &storage, - 1, - &AnchorBlockId { hash: Default::default(), number: 100 }, - 50, - None, - &[42], - ) - .is_err()); - assert!(key_changes::( - configuration_range(&config, 0), - &storage, - 20, - &AnchorBlockId { hash: Default::default(), number: 10 }, - 100, - None, - &[42], - ) - .is_err()); - } - - #[test] - fn proving_drilldown_iterator_works() { - // happens on remote full node: - - // create drilldown iterator that records all trie nodes during drilldown - let (remote_config, remote_storage) = prepare_for_drilldown(); - let remote_proof = key_changes_proof::( - configuration_range(&remote_config, 0), - &remote_storage, - 1, - &AnchorBlockId { hash: Default::default(), number: 16 }, - 16, - None, - &[42], - ) - .unwrap(); - - let (remote_config, remote_storage) = prepare_for_drilldown(); - let remote_proof_child = key_changes_proof::( - configuration_range(&remote_config, 0), - &remote_storage, - 1, - &AnchorBlockId { hash: Default::default(), number: 16 }, - 16, - Some(&child_key()), - &[42], - ) - .unwrap(); - - // happens on local light node: - - // create drilldown iterator that works the same, but only depends on trie - let (local_config, local_storage) = prepare_for_drilldown(); - local_storage.clear_storage(); - let local_result = key_changes_proof_check::( - configuration_range(&local_config, 0), - &local_storage, - remote_proof, - 1, - &AnchorBlockId { hash: Default::default(), number: 16 }, - 16, - None, - &[42], - ); - - let (local_config, local_storage) = prepare_for_drilldown(); - local_storage.clear_storage(); - let local_result_child = key_changes_proof_check::( - configuration_range(&local_config, 0), - &local_storage, - remote_proof_child, - 1, - &AnchorBlockId { hash: Default::default(), number: 16 }, - 16, - Some(&child_key()), - &[42], - ); - - // check that drilldown result is the same as if it was happening at the full node - assert_eq!(local_result, Ok(vec![(8, 2), (8, 1), (6, 3), (3, 0)])); - assert_eq!(local_result_child, Ok(vec![(16, 5), (2, 3)])); - } - - #[test] - fn drilldown_iterator_works_with_skewed_digest() { - let config = Configuration { digest_interval: 4, digest_levels: 3 }; - let mut config_range = configuration_range(&config, 0); - config_range.end = Some(91); - - // when 4^3 deactivates at block 91: - // last L3 digest has been created at block#64 - // skewed digest covers: - // L2 digests at blocks: 80 - // L1 digests at blocks: 84, 88 - // regular blocks: 89, 90, 91 - let mut input = (1u64..92u64).map(|b| (b, vec![])).collect::>(); - // changed at block#63 and covered by L3 digest at block#64 - input[63 - 1] - .1 - .push(InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 63, key: vec![42] }, vec![0])); - input[64 - 1] - .1 - .push(InputPair::DigestIndex(DigestIndex { block: 64, key: vec![42] }, vec![63])); - // changed at block#79 and covered by L2 digest at block#80 + skewed digest at block#91 - input[79 - 1] - .1 - .push(InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 79, key: vec![42] }, vec![1])); - input[80 - 1] - .1 - .push(InputPair::DigestIndex(DigestIndex { block: 80, key: vec![42] }, vec![79])); - input[91 - 1] - .1 - .push(InputPair::DigestIndex(DigestIndex { block: 91, key: vec![42] }, vec![80])); - let storage = InMemoryStorage::with_inputs(input, vec![]); - - let drilldown_result = key_changes::( - config_range, - &storage, - 1, - &AnchorBlockId { hash: Default::default(), number: 91 }, - 100_000u64, - None, - &[42], - ) - .and_then(Result::from_iter); - assert_eq!(drilldown_result, Ok(vec![(79, 1), (63, 0)])); - } -} diff --git a/primitives/state-machine/src/changes_trie/input.rs b/primitives/state-machine/src/changes_trie/input.rs deleted file mode 100644 index af0a423e57267..0000000000000 --- a/primitives/state-machine/src/changes_trie/input.rs +++ /dev/null @@ -1,207 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! Different types of changes trie input pairs. - -use crate::{changes_trie::BlockNumber, StorageKey, StorageValue}; -use codec::{Decode, Encode, Error, Input, Output}; -use sp_core::storage::PrefixedStorageKey; - -/// Key of { changed key => set of extrinsic indices } mapping. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ExtrinsicIndex { - /// Block at which this key has been inserted in the trie. - pub block: Number, - /// Storage key this node is responsible for. - pub key: StorageKey, -} - -/// Value of { changed key => set of extrinsic indices } mapping. -pub type ExtrinsicIndexValue = Vec; - -/// Key of { changed key => block/digest block numbers } mapping. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct DigestIndex { - /// Block at which this key has been inserted in the trie. - pub block: Number, - /// Storage key this node is responsible for. - pub key: StorageKey, -} - -/// Key of { childtrie key => Childchange trie } mapping. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct ChildIndex { - /// Block at which this key has been inserted in the trie. - pub block: Number, - /// Storage key this node is responsible for. - pub storage_key: PrefixedStorageKey, -} - -/// Value of { changed key => block/digest block numbers } mapping. -pub type DigestIndexValue = Vec; - -/// Value of { changed key => block/digest block numbers } mapping. -/// That is the root of the child change trie. -pub type ChildIndexValue = Vec; - -/// Single input pair of changes trie. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum InputPair { - /// Element of { key => set of extrinsics where key has been changed } element mapping. - ExtrinsicIndex(ExtrinsicIndex, ExtrinsicIndexValue), - /// Element of { key => set of blocks/digest blocks where key has been changed } element - /// mapping. - DigestIndex(DigestIndex, DigestIndexValue), - /// Element of { childtrie key => Childchange trie } where key has been changed } element - /// mapping. - ChildIndex(ChildIndex, ChildIndexValue), -} - -/// Single input key of changes trie. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum InputKey { - /// Key of { key => set of extrinsics where key has been changed } element mapping. - ExtrinsicIndex(ExtrinsicIndex), - /// Key of { key => set of blocks/digest blocks where key has been changed } element mapping. - DigestIndex(DigestIndex), - /// Key of { childtrie key => Childchange trie } where key has been changed } element mapping. - ChildIndex(ChildIndex), -} - -impl InputPair { - /// Extract storage key that this pair corresponds to. - pub fn key(&self) -> Option<&[u8]> { - match *self { - InputPair::ExtrinsicIndex(ref key, _) => Some(&key.key), - InputPair::DigestIndex(ref key, _) => Some(&key.key), - InputPair::ChildIndex(_, _) => None, - } - } -} - -impl Into<(StorageKey, StorageValue)> for InputPair { - fn into(self) -> (StorageKey, StorageValue) { - match self { - InputPair::ExtrinsicIndex(key, value) => (key.encode(), value.encode()), - InputPair::DigestIndex(key, value) => (key.encode(), value.encode()), - InputPair::ChildIndex(key, value) => (key.encode(), value.encode()), - } - } -} - -impl Into> for InputPair { - fn into(self) -> InputKey { - match self { - InputPair::ExtrinsicIndex(key, _) => InputKey::ExtrinsicIndex(key), - InputPair::DigestIndex(key, _) => InputKey::DigestIndex(key), - InputPair::ChildIndex(key, _) => InputKey::ChildIndex(key), - } - } -} - -impl ExtrinsicIndex { - pub fn key_neutral_prefix(block: Number) -> Vec { - let mut prefix = vec![1]; - prefix.extend(block.encode()); - prefix - } -} - -impl Encode for ExtrinsicIndex { - fn encode_to(&self, dest: &mut W) { - dest.push_byte(1); - self.block.encode_to(dest); - self.key.encode_to(dest); - } -} - -impl codec::EncodeLike for ExtrinsicIndex {} - -impl DigestIndex { - pub fn key_neutral_prefix(block: Number) -> Vec { - let mut prefix = vec![2]; - prefix.extend(block.encode()); - prefix - } -} - -impl Encode for DigestIndex { - fn encode_to(&self, dest: &mut W) { - dest.push_byte(2); - self.block.encode_to(dest); - self.key.encode_to(dest); - } -} - -impl ChildIndex { - pub fn key_neutral_prefix(block: Number) -> Vec { - let mut prefix = vec![3]; - prefix.extend(block.encode()); - prefix - } -} - -impl Encode for ChildIndex { - fn encode_to(&self, dest: &mut W) { - dest.push_byte(3); - self.block.encode_to(dest); - self.storage_key.encode_to(dest); - } -} - -impl codec::EncodeLike for DigestIndex {} - -impl Decode for InputKey { - fn decode(input: &mut I) -> Result { - match input.read_byte()? { - 1 => Ok(InputKey::ExtrinsicIndex(ExtrinsicIndex { - block: Decode::decode(input)?, - key: Decode::decode(input)?, - })), - 2 => Ok(InputKey::DigestIndex(DigestIndex { - block: Decode::decode(input)?, - key: Decode::decode(input)?, - })), - 3 => Ok(InputKey::ChildIndex(ChildIndex { - block: Decode::decode(input)?, - storage_key: PrefixedStorageKey::new(Decode::decode(input)?), - })), - _ => Err("Invalid input key variant".into()), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn extrinsic_index_serialized_and_deserialized() { - let original = ExtrinsicIndex { block: 777u64, key: vec![42] }; - let serialized = original.encode(); - let deserialized: InputKey = Decode::decode(&mut &serialized[..]).unwrap(); - assert_eq!(InputKey::ExtrinsicIndex(original), deserialized); - } - - #[test] - fn digest_index_serialized_and_deserialized() { - let original = DigestIndex { block: 777u64, key: vec![42] }; - let serialized = original.encode(); - let deserialized: InputKey = Decode::decode(&mut &serialized[..]).unwrap(); - assert_eq!(InputKey::DigestIndex(original), deserialized); - } -} diff --git a/primitives/state-machine/src/changes_trie/mod.rs b/primitives/state-machine/src/changes_trie/mod.rs deleted file mode 100644 index 40148095247dd..0000000000000 --- a/primitives/state-machine/src/changes_trie/mod.rs +++ /dev/null @@ -1,428 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! Changes trie related structures and functions. -//! -//! Changes trie is a trie built of { storage key => extrinsics } pairs -//! at the end of each block. For every changed storage key it contains -//! a pair, mapping key to the set of extrinsics where it has been changed. -//! -//! Optionally, every N blocks, additional level1-digest nodes are appended -//! to the changes trie, containing pairs { storage key => blocks }. For every -//! storage key that has been changed in PREVIOUS N-1 blocks (except for genesis -//! block) it contains a pair, mapping this key to the set of blocks where it -//! has been changed. -//! -//! Optionally, every N^digest_level (where digest_level > 1) blocks, additional -//! digest_level digest is created. It is built out of pairs { storage key => digest -//! block }, containing entries for every storage key that has been changed in -//! the last N*digest_level-1 blocks (except for genesis block), mapping these keys -//! to the set of lower-level digest blocks. -//! -//! Changes trie configuration could change within a time. The range of blocks, where -//! configuration has been active, is given by two blocks: zero and end. Zero block is -//! the block where configuration has been set. But the first changes trie that uses -//! this configuration will be built at the block zero+1. If configuration deactivates -//! at some block, this will be the end block of the configuration. It is also the -//! zero block of the next configuration. -//! -//! If configuration has the end block, it also means that 'skewed digest' has/should -//! been built at that block. If this is the block where max-level digest should have -//! been created, than it is simply max-level digest of this configuration. Otherwise, -//! it is the digest that covers all blocks since last max-level digest block was -//! created. -//! -//! Changes trie only contains the top level storage changes. Sub-level changes -//! are propagated through its storage root on the top level storage. - -mod build; -mod build_cache; -mod build_iterator; -mod changes_iterator; -mod input; -mod prune; -mod storage; -mod surface_iterator; - -pub use self::{ - build_cache::{BuildCache, CacheAction, CachedBuildData}, - changes_iterator::{ - key_changes, key_changes_proof, key_changes_proof_check, key_changes_proof_check_with_db, - }, - prune::prune, - storage::InMemoryStorage, -}; - -use crate::{ - backend::Backend, - changes_trie::{ - build::prepare_input, - build_cache::{IncompleteCacheAction, IncompleteCachedBuildData}, - }, - overlayed_changes::OverlayedChanges, - StorageKey, -}; -use codec::{Decode, Encode}; -use hash_db::{Hasher, Prefix}; -use num_traits::{One, Zero}; -use sp_core::{self, storage::PrefixedStorageKey}; -use sp_trie::{trie_types::TrieDBMut, DBValue, MemoryDB, TrieMut}; -use std::{ - collections::{HashMap, HashSet}, - convert::TryInto, -}; - -/// Requirements for block number that can be used with changes tries. -pub trait BlockNumber: - Send - + Sync - + 'static - + std::fmt::Display - + Clone - + From - + TryInto - + One - + Zero - + PartialEq - + Ord - + std::hash::Hash - + std::ops::Add - + ::std::ops::Sub - + std::ops::Mul - + ::std::ops::Div - + std::ops::Rem - + std::ops::AddAssign - + num_traits::CheckedMul - + num_traits::CheckedSub - + Decode - + Encode -{ -} - -impl BlockNumber for T where - T: Send - + Sync - + 'static - + std::fmt::Display - + Clone - + From - + TryInto - + One - + Zero - + PartialEq - + Ord - + std::hash::Hash - + std::ops::Add - + ::std::ops::Sub - + std::ops::Mul - + ::std::ops::Div - + std::ops::Rem - + std::ops::AddAssign - + num_traits::CheckedMul - + num_traits::CheckedSub - + Decode - + Encode -{ -} - -/// Block identifier that could be used to determine fork of this block. -#[derive(Debug)] -pub struct AnchorBlockId { - /// Hash of this block. - pub hash: Hash, - /// Number of this block. - pub number: Number, -} - -/// Changes tries state at some block. -pub struct State<'a, H, Number> { - /// Configuration that is active at given block. - pub config: Configuration, - /// Configuration activation block number. Zero if it is the first configuration on the chain, - /// or number of the block that have emit NewConfiguration signal (thus activating - /// configuration starting from the **next** block). - pub zero: Number, - /// Underlying changes tries storage reference. - pub storage: &'a dyn Storage, -} - -/// Changes trie storage. Provides access to trie roots and trie nodes. -pub trait RootsStorage: Send + Sync { - /// Resolve hash of the block into anchor. - fn build_anchor(&self, hash: H::Out) -> Result, String>; - /// Get changes trie root for the block with given number which is an ancestor (or the block - /// itself) of the anchor_block (i.e. anchor_block.number >= block). - fn root( - &self, - anchor: &AnchorBlockId, - block: Number, - ) -> Result, String>; -} - -/// Changes trie storage. Provides access to trie roots and trie nodes. -pub trait Storage: RootsStorage { - /// Casts from self reference to RootsStorage reference. - fn as_roots_storage(&self) -> &dyn RootsStorage; - /// Execute given functor with cached entry for given trie root. - /// Returns true if the functor has been called (cache entry exists) and false otherwise. - fn with_cached_changed_keys( - &self, - root: &H::Out, - functor: &mut dyn FnMut(&HashMap, HashSet>), - ) -> bool; - /// Get a trie node. - fn get(&self, key: &H::Out, prefix: Prefix) -> Result, String>; -} - -/// Changes trie storage -> trie backend essence adapter. -pub struct TrieBackendStorageAdapter<'a, H: Hasher, Number: BlockNumber>( - pub &'a dyn Storage, -); - -impl<'a, H: Hasher, N: BlockNumber> crate::TrieBackendStorage - for TrieBackendStorageAdapter<'a, H, N> -{ - type Overlay = sp_trie::MemoryDB; - - fn get(&self, key: &H::Out, prefix: Prefix) -> Result, String> { - self.0.get(key, prefix) - } -} - -/// Changes trie configuration. -pub type Configuration = sp_core::ChangesTrieConfiguration; - -/// Blocks range where configuration has been constant. -#[derive(Clone)] -pub struct ConfigurationRange<'a, N> { - /// Active configuration. - pub config: &'a Configuration, - /// Zero block of this configuration. The configuration is active starting from the next block. - pub zero: N, - /// End block of this configuration. It is the last block where configuration has been active. - pub end: Option, -} - -impl<'a, H, Number> State<'a, H, Number> { - /// Create state with given config and storage. - pub fn new(config: Configuration, zero: Number, storage: &'a dyn Storage) -> Self { - Self { config, zero, storage } - } -} - -impl<'a, H, Number: Clone> Clone for State<'a, H, Number> { - fn clone(&self) -> Self { - State { config: self.config.clone(), zero: self.zero.clone(), storage: self.storage } - } -} - -/// Create state where changes tries are disabled. -pub fn disabled_state<'a, H, Number>() -> Option> { - None -} - -/// Compute the changes trie root and transaction for given block. -/// Returns Err(()) if unknown `parent_hash` has been passed. -/// Returns Ok(None) if there's no data to perform computation. -/// Panics if background storage returns an error OR if insert to MemoryDB fails. -pub fn build_changes_trie<'a, B: Backend, H: Hasher, Number: BlockNumber>( - backend: &B, - state: Option<&'a State<'a, H, Number>>, - changes: &OverlayedChanges, - parent_hash: H::Out, - panic_on_storage_error: bool, -) -> Result, H::Out, CacheAction)>, ()> -where - H::Out: Ord + 'static + Encode, -{ - /// Panics when `res.is_err() && panic`, otherwise it returns `Err(())` on an error. - fn maybe_panic( - res: std::result::Result, - panic: bool, - ) -> std::result::Result { - res.map(Ok).unwrap_or_else(|e| { - if panic { - panic!( - "changes trie: storage access is not allowed to fail within runtime: {:?}", - e - ) - } else { - Err(()) - } - }) - } - - // when storage isn't provided, changes tries aren't created - let state = match state { - Some(state) => state, - None => return Ok(None), - }; - - // build_anchor error should not be considered fatal - let parent = state.storage.build_anchor(parent_hash).map_err(|_| ())?; - let block = parent.number.clone() + One::one(); - - // prepare configuration range - we already know zero block. Current block may be the end block - // if configuration has been changed in this block - let is_config_changed = - match changes.storage(sp_core::storage::well_known_keys::CHANGES_TRIE_CONFIG) { - Some(Some(new_config)) => new_config != &state.config.encode()[..], - Some(None) => true, - None => false, - }; - let config_range = ConfigurationRange { - config: &state.config, - zero: state.zero.clone(), - end: if is_config_changed { Some(block.clone()) } else { None }, - }; - - // storage errors are considered fatal (similar to situations when runtime fetches values from - // storage) - let (input_pairs, child_input_pairs, digest_input_blocks) = maybe_panic( - prepare_input::( - backend, - state.storage, - config_range.clone(), - changes, - &parent, - ), - panic_on_storage_error, - )?; - - // prepare cached data - let mut cache_action = prepare_cached_build_data(config_range, block.clone()); - let needs_changed_keys = cache_action.collects_changed_keys(); - cache_action = cache_action.set_digest_input_blocks(digest_input_blocks); - - let mut mdb = MemoryDB::default(); - let mut child_roots = Vec::with_capacity(child_input_pairs.len()); - for (child_index, input_pairs) in child_input_pairs { - let mut not_empty = false; - let mut root = Default::default(); - { - let mut trie = TrieDBMut::::new(&mut mdb, &mut root); - let mut storage_changed_keys = HashSet::new(); - for input_pair in input_pairs { - if needs_changed_keys { - if let Some(key) = input_pair.key() { - storage_changed_keys.insert(key.to_vec()); - } - } - - let (key, value) = input_pair.into(); - not_empty = true; - maybe_panic(trie.insert(&key, &value), panic_on_storage_error)?; - } - - cache_action = - cache_action.insert(Some(child_index.storage_key.clone()), storage_changed_keys); - } - if not_empty { - child_roots.push(input::InputPair::ChildIndex(child_index, root.as_ref().to_vec())); - } - } - let mut root = Default::default(); - { - let mut trie = TrieDBMut::::new(&mut mdb, &mut root); - for (key, value) in child_roots.into_iter().map(Into::into) { - maybe_panic(trie.insert(&key, &value), panic_on_storage_error)?; - } - - let mut storage_changed_keys = HashSet::new(); - for input_pair in input_pairs { - if needs_changed_keys { - if let Some(key) = input_pair.key() { - storage_changed_keys.insert(key.to_vec()); - } - } - - let (key, value) = input_pair.into(); - maybe_panic(trie.insert(&key, &value), panic_on_storage_error)?; - } - - cache_action = cache_action.insert(None, storage_changed_keys); - } - - let cache_action = cache_action.complete(block, &root); - Ok(Some((mdb, root, cache_action))) -} - -/// Prepare empty cached build data for given block. -fn prepare_cached_build_data( - config: ConfigurationRange, - block: Number, -) -> IncompleteCacheAction { - // when digests are not enabled in configuration, we do not need to cache anything - // because it'll never be used again for building other tries - // => let's clear the cache - if !config.config.is_digest_build_enabled() { - return IncompleteCacheAction::Clear - } - - // when this is the last block where current configuration is active - // => let's clear the cache - if config.end.as_ref() == Some(&block) { - return IncompleteCacheAction::Clear - } - - // we do not need to cache anything when top-level digest trie is created, because - // it'll never be used again for building other tries - // => let's clear the cache - match config.config.digest_level_at_block(config.zero.clone(), block) { - Some((digest_level, _, _)) if digest_level == config.config.digest_levels => - IncompleteCacheAction::Clear, - _ => IncompleteCacheAction::CacheBuildData(IncompleteCachedBuildData::new()), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn cache_is_cleared_when_digests_are_disabled() { - let config = Configuration { digest_interval: 0, digest_levels: 0 }; - let config_range = ConfigurationRange { zero: 0, end: None, config: &config }; - assert_eq!(prepare_cached_build_data(config_range, 8u32), IncompleteCacheAction::Clear); - } - - #[test] - fn build_data_is_cached_when_digests_are_enabled() { - let config = Configuration { digest_interval: 8, digest_levels: 2 }; - let config_range = ConfigurationRange { zero: 0, end: None, config: &config }; - assert!(prepare_cached_build_data(config_range.clone(), 4u32).collects_changed_keys()); - assert!(prepare_cached_build_data(config_range.clone(), 7u32).collects_changed_keys()); - assert!(prepare_cached_build_data(config_range, 8u32).collects_changed_keys()); - } - - #[test] - fn cache_is_cleared_when_digests_are_enabled_and_top_level_digest_is_built() { - let config = Configuration { digest_interval: 8, digest_levels: 2 }; - let config_range = ConfigurationRange { zero: 0, end: None, config: &config }; - assert_eq!(prepare_cached_build_data(config_range, 64u32), IncompleteCacheAction::Clear); - } - - #[test] - fn cache_is_cleared_when_end_block_of_configuration_is_built() { - let config = Configuration { digest_interval: 8, digest_levels: 2 }; - let config_range = ConfigurationRange { zero: 0, end: Some(4u32), config: &config }; - assert_eq!( - prepare_cached_build_data(config_range.clone(), 4u32), - IncompleteCacheAction::Clear - ); - } -} diff --git a/primitives/state-machine/src/changes_trie/prune.rs b/primitives/state-machine/src/changes_trie/prune.rs deleted file mode 100644 index 2ca540562b47f..0000000000000 --- a/primitives/state-machine/src/changes_trie/prune.rs +++ /dev/null @@ -1,204 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! Changes trie pruning-related functions. - -use crate::{ - changes_trie::{ - input::{ChildIndex, InputKey}, - storage::TrieBackendAdapter, - AnchorBlockId, BlockNumber, Storage, - }, - proving_backend::ProvingBackendRecorder, - trie_backend_essence::TrieBackendEssence, -}; -use codec::{Codec, Decode}; -use hash_db::Hasher; -use log::warn; -use num_traits::One; -use sp_trie::Recorder; - -/// Prune obsolete changes tries. Pruning happens at the same block, where highest -/// level digest is created. Pruning guarantees to save changes tries for last -/// `min_blocks_to_keep` blocks. We only prune changes tries at `max_digest_interval` -/// ranges. -pub fn prune( - storage: &dyn Storage, - first: Number, - last: Number, - current_block: &AnchorBlockId, - mut remove_trie_node: F, -) where - H::Out: Codec, -{ - // delete changes trie for every block in range - let mut block = first; - loop { - if block >= last.clone() + One::one() { - break - } - - let prev_block = block.clone(); - block += One::one(); - - let block = prev_block; - let root = match storage.root(current_block, block.clone()) { - Ok(Some(root)) => root, - Ok(None) => continue, - Err(error) => { - // try to delete other tries - warn!(target: "trie", "Failed to read changes trie root from DB: {}", error); - continue - }, - }; - let children_roots = { - let trie_storage = TrieBackendEssence::<_, H>::new( - crate::changes_trie::TrieBackendStorageAdapter(storage), - root, - ); - let child_prefix = ChildIndex::key_neutral_prefix(block.clone()); - let mut children_roots = Vec::new(); - trie_storage.for_key_values_with_prefix(&child_prefix, |mut key, mut value| { - if let Ok(InputKey::ChildIndex::(_trie_key)) = Decode::decode(&mut key) { - if let Ok(value) = >::decode(&mut value) { - let mut trie_root = ::Out::default(); - trie_root.as_mut().copy_from_slice(&value[..]); - children_roots.push(trie_root); - } - } - }); - - children_roots - }; - for root in children_roots.into_iter() { - prune_trie(storage, root, &mut remove_trie_node); - } - - prune_trie(storage, root, &mut remove_trie_node); - } -} - -// Prune a trie. -fn prune_trie( - storage: &dyn Storage, - root: H::Out, - remove_trie_node: &mut F, -) where - H::Out: Codec, -{ - // enumerate all changes trie' keys, recording all nodes that have been 'touched' - // (effectively - all changes trie nodes) - let mut proof_recorder: Recorder = Default::default(); - { - let mut trie = ProvingBackendRecorder::<_, H> { - backend: &TrieBackendEssence::new(TrieBackendAdapter::new(storage), root), - proof_recorder: &mut proof_recorder, - }; - trie.record_all_keys(); - } - - // all nodes of this changes trie should be pruned - remove_trie_node(root); - for node in proof_recorder.drain().into_iter().map(|n| n.hash) { - remove_trie_node(node); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{backend::insert_into_memory_db, changes_trie::storage::InMemoryStorage}; - use codec::Encode; - use sp_core::H256; - use sp_runtime::traits::BlakeTwo256; - use sp_trie::MemoryDB; - use std::collections::HashSet; - - fn prune_by_collect( - storage: &dyn Storage, - first: u64, - last: u64, - current_block: u64, - ) -> HashSet { - let mut pruned_trie_nodes = HashSet::new(); - let anchor = AnchorBlockId { hash: Default::default(), number: current_block }; - prune(storage, first, last, &anchor, |node| { - pruned_trie_nodes.insert(node); - }); - pruned_trie_nodes - } - - #[test] - fn prune_works() { - fn prepare_storage() -> InMemoryStorage { - let child_info = sp_core::storage::ChildInfo::new_default(&b"1"[..]); - let child_key = - ChildIndex { block: 67u64, storage_key: child_info.prefixed_storage_key() } - .encode(); - let mut mdb1 = MemoryDB::::default(); - let root1 = - insert_into_memory_db::(&mut mdb1, vec![(vec![10], vec![20])]) - .unwrap(); - let mut mdb2 = MemoryDB::::default(); - let root2 = insert_into_memory_db::( - &mut mdb2, - vec![(vec![11], vec![21]), (vec![12], vec![22])], - ) - .unwrap(); - let mut mdb3 = MemoryDB::::default(); - let ch_root3 = - insert_into_memory_db::(&mut mdb3, vec![(vec![110], vec![120])]) - .unwrap(); - let root3 = insert_into_memory_db::( - &mut mdb3, - vec![ - (vec![13], vec![23]), - (vec![14], vec![24]), - (child_key, ch_root3.as_ref().encode()), - ], - ) - .unwrap(); - let mut mdb4 = MemoryDB::::default(); - let root4 = - insert_into_memory_db::(&mut mdb4, vec![(vec![15], vec![25])]) - .unwrap(); - let storage = InMemoryStorage::new(); - storage.insert(65, root1, mdb1); - storage.insert(66, root2, mdb2); - storage.insert(67, root3, mdb3); - storage.insert(68, root4, mdb4); - - storage - } - - let storage = prepare_storage(); - assert!(prune_by_collect(&storage, 20, 30, 90).is_empty()); - assert!(!storage.into_mdb().drain().is_empty()); - - let storage = prepare_storage(); - let prune60_65 = prune_by_collect(&storage, 60, 65, 90); - assert!(!prune60_65.is_empty()); - storage.remove_from_storage(&prune60_65); - assert!(!storage.into_mdb().drain().is_empty()); - - let storage = prepare_storage(); - let prune60_70 = prune_by_collect(&storage, 60, 70, 90); - assert!(!prune60_70.is_empty()); - storage.remove_from_storage(&prune60_70); - assert!(storage.into_mdb().drain().is_empty()); - } -} diff --git a/primitives/state-machine/src/changes_trie/storage.rs b/primitives/state-machine/src/changes_trie/storage.rs deleted file mode 100644 index bd5e3a32b5657..0000000000000 --- a/primitives/state-machine/src/changes_trie/storage.rs +++ /dev/null @@ -1,214 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! Changes trie storage utilities. - -use crate::{ - changes_trie::{AnchorBlockId, BlockNumber, BuildCache, RootsStorage, Storage}, - trie_backend_essence::TrieBackendStorage, - StorageKey, -}; -use hash_db::{Hasher, Prefix, EMPTY_PREFIX}; -use parking_lot::RwLock; -use sp_core::storage::PrefixedStorageKey; -use sp_trie::{DBValue, MemoryDB}; -use std::collections::{BTreeMap, HashMap, HashSet}; - -#[cfg(test)] -use crate::backend::insert_into_memory_db; -#[cfg(test)] -use crate::changes_trie::input::{ChildIndex, InputPair}; - -/// In-memory implementation of changes trie storage. -pub struct InMemoryStorage { - data: RwLock>, - cache: BuildCache, -} - -/// Adapter for using changes trie storage as a TrieBackendEssence' storage. -pub struct TrieBackendAdapter<'a, H: Hasher, Number: BlockNumber> { - storage: &'a dyn Storage, - _hasher: std::marker::PhantomData<(H, Number)>, -} - -struct InMemoryStorageData { - roots: BTreeMap, - mdb: MemoryDB, -} - -impl InMemoryStorage { - /// Creates storage from given in-memory database. - pub fn with_db(mdb: MemoryDB) -> Self { - Self { - data: RwLock::new(InMemoryStorageData { roots: BTreeMap::new(), mdb }), - cache: BuildCache::new(), - } - } - - /// Creates storage with empty database. - pub fn new() -> Self { - Self::with_db(Default::default()) - } - - /// Creates storage with given proof. - pub fn with_proof(proof: Vec>) -> Self { - use hash_db::HashDB; - - let mut proof_db = MemoryDB::::default(); - for item in proof { - proof_db.insert(EMPTY_PREFIX, &item); - } - Self::with_db(proof_db) - } - - /// Get mutable cache reference. - pub fn cache_mut(&mut self) -> &mut BuildCache { - &mut self.cache - } - - /// Create the storage with given blocks. - pub fn with_blocks(blocks: Vec<(Number, H::Out)>) -> Self { - Self { - data: RwLock::new(InMemoryStorageData { - roots: blocks.into_iter().collect(), - mdb: MemoryDB::default(), - }), - cache: BuildCache::new(), - } - } - - #[cfg(test)] - pub fn with_inputs( - mut top_inputs: Vec<(Number, Vec>)>, - children_inputs: Vec<(PrefixedStorageKey, Vec<(Number, Vec>)>)>, - ) -> Self { - let mut mdb = MemoryDB::default(); - let mut roots = BTreeMap::new(); - for (storage_key, child_input) in children_inputs { - for (block, pairs) in child_input { - let root = - insert_into_memory_db::(&mut mdb, pairs.into_iter().map(Into::into)); - - if let Some(root) = root { - let ix = if let Some(ix) = top_inputs.iter().position(|v| v.0 == block) { - ix - } else { - top_inputs.push((block.clone(), Default::default())); - top_inputs.len() - 1 - }; - top_inputs[ix].1.push(InputPair::ChildIndex( - ChildIndex { block: block.clone(), storage_key: storage_key.clone() }, - root.as_ref().to_vec(), - )); - } - } - } - - for (block, pairs) in top_inputs { - let root = insert_into_memory_db::(&mut mdb, pairs.into_iter().map(Into::into)); - if let Some(root) = root { - roots.insert(block, root); - } - } - - InMemoryStorage { - data: RwLock::new(InMemoryStorageData { roots, mdb }), - cache: BuildCache::new(), - } - } - - #[cfg(test)] - pub fn clear_storage(&self) { - self.data.write().mdb = MemoryDB::default(); // use new to be more correct - } - - #[cfg(test)] - pub fn remove_from_storage(&self, keys: &HashSet) { - let mut data = self.data.write(); - for key in keys { - data.mdb.remove_and_purge(key, hash_db::EMPTY_PREFIX); - } - } - - #[cfg(test)] - pub fn into_mdb(self) -> MemoryDB { - self.data.into_inner().mdb - } - - /// Insert changes trie for given block. - pub fn insert(&self, block: Number, changes_trie_root: H::Out, trie: MemoryDB) { - let mut data = self.data.write(); - data.roots.insert(block, changes_trie_root); - data.mdb.consolidate(trie); - } -} - -impl RootsStorage for InMemoryStorage { - fn build_anchor(&self, parent_hash: H::Out) -> Result, String> { - self.data - .read() - .roots - .iter() - .find(|(_, v)| **v == parent_hash) - .map(|(k, _)| AnchorBlockId { hash: parent_hash, number: k.clone() }) - .ok_or_else(|| format!("Can't find associated number for block {:?}", parent_hash)) - } - - fn root( - &self, - _anchor_block: &AnchorBlockId, - block: Number, - ) -> Result, String> { - Ok(self.data.read().roots.get(&block).cloned()) - } -} - -impl Storage for InMemoryStorage { - fn as_roots_storage(&self) -> &dyn RootsStorage { - self - } - - fn with_cached_changed_keys( - &self, - root: &H::Out, - functor: &mut dyn FnMut(&HashMap, HashSet>), - ) -> bool { - self.cache.with_changed_keys(root, functor) - } - - fn get(&self, key: &H::Out, prefix: Prefix) -> Result, String> { - MemoryDB::::get(&self.data.read().mdb, key, prefix) - } -} - -impl<'a, H: Hasher, Number: BlockNumber> TrieBackendAdapter<'a, H, Number> { - pub fn new(storage: &'a dyn Storage) -> Self { - Self { storage, _hasher: Default::default() } - } -} - -impl<'a, H, Number> TrieBackendStorage for TrieBackendAdapter<'a, H, Number> -where - Number: BlockNumber, - H: Hasher, -{ - type Overlay = MemoryDB; - - fn get(&self, key: &H::Out, prefix: Prefix) -> Result, String> { - self.storage.get(key, prefix) - } -} diff --git a/primitives/state-machine/src/changes_trie/surface_iterator.rs b/primitives/state-machine/src/changes_trie/surface_iterator.rs deleted file mode 100644 index b3e5a490cd184..0000000000000 --- a/primitives/state-machine/src/changes_trie/surface_iterator.rs +++ /dev/null @@ -1,326 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! The best way to understand how this iterator works is to imagine some 2D terrain that have some -//! mountains (digest changes tries) and valleys (changes tries for regular blocks). There are gems -//! (blocks) beneath the terrain. Given the request to find all gems in the range [X1; X2] this -//! iterator will return **minimal set** of points at the terrain (mountains and valleys) inside -//! this range that have to be drilled down to search for gems. - -use crate::changes_trie::{BlockNumber, ConfigurationRange}; -use num_traits::One; - -/// Returns surface iterator for given range of blocks. -/// -/// `max` is the number of best block, known to caller. We can't access any changes tries -/// that are built after this block, even though we may have them built already. -pub fn surface_iterator<'a, Number: BlockNumber>( - config: ConfigurationRange<'a, Number>, - max: Number, - begin: Number, - end: Number, -) -> Result, String> { - let (current, current_begin, digest_step, digest_level) = - lower_bound_max_digest(config.clone(), max.clone(), begin.clone(), end)?; - Ok(SurfaceIterator { - config, - begin, - max, - current: Some(current), - current_begin, - digest_step, - digest_level, - }) -} - -/// Surface iterator - only traverses top-level digests from given range and tries to find -/// all valid digest changes. -/// -/// Iterator item is the tuple of (last block of the current point + digest level of the current -/// point). Digest level is Some(0) when it is regular block, is Some(non-zero) when it is digest -/// block and None if it is skewed digest block. -pub struct SurfaceIterator<'a, Number: BlockNumber> { - config: ConfigurationRange<'a, Number>, - begin: Number, - max: Number, - current: Option, - current_begin: Number, - digest_step: u32, - digest_level: Option, -} - -impl<'a, Number: BlockNumber> Iterator for SurfaceIterator<'a, Number> { - type Item = Result<(Number, Option), String>; - - fn next(&mut self) -> Option { - let current = self.current.clone()?; - let digest_level = self.digest_level; - - if current < self.digest_step.into() { - self.current = None; - } else { - let next = current.clone() - self.digest_step.into(); - if next.is_zero() || next < self.begin { - self.current = None; - } else if next > self.current_begin { - self.current = Some(next); - } else { - let max_digest_interval = lower_bound_max_digest( - self.config.clone(), - self.max.clone(), - self.begin.clone(), - next, - ); - let (current, current_begin, digest_step, digest_level) = match max_digest_interval - { - Err(err) => return Some(Err(err)), - Ok(range) => range, - }; - - self.current = Some(current); - self.current_begin = current_begin; - self.digest_step = digest_step; - self.digest_level = digest_level; - } - } - - Some(Ok((current, digest_level))) - } -} - -/// Returns parameters of highest level digest block that includes the end of given range -/// and tends to include the whole range. -fn lower_bound_max_digest<'a, Number: BlockNumber>( - config: ConfigurationRange<'a, Number>, - max: Number, - begin: Number, - end: Number, -) -> Result<(Number, Number, u32, Option), String> { - if end > max || begin > end { - return Err(format!("invalid changes range: {}..{}/{}", begin, end, max)) - } - if begin <= config.zero || - config.end.as_ref().map(|config_end| end > *config_end).unwrap_or(false) - { - return Err(format!( - "changes trie range is not covered by configuration: {}..{}/{}..{}", - begin, - end, - config.zero, - match config.end.as_ref() { - Some(config_end) => format!("{}", config_end), - None => "None".into(), - } - )) - } - - let mut digest_level = 0u32; - let mut digest_step = 1u32; - let mut digest_interval = 0u32; - let mut current = end.clone(); - let mut current_begin = begin.clone(); - if current_begin != current { - while digest_level != config.config.digest_levels { - // try to use next level digest - let new_digest_level = digest_level + 1; - let new_digest_step = digest_step * config.config.digest_interval; - let new_digest_interval = config.config.digest_interval * { - if digest_interval == 0 { - 1 - } else { - digest_interval - } - }; - let new_digest_begin = config.zero.clone() + - ((current.clone() - One::one() - config.zero.clone()) / - new_digest_interval.into()) * - new_digest_interval.into(); - let new_digest_end = new_digest_begin.clone() + new_digest_interval.into(); - let new_current = new_digest_begin.clone() + new_digest_interval.into(); - - // check if we met skewed digest - if let Some(skewed_digest_end) = config.end.as_ref() { - if new_digest_end > *skewed_digest_end { - let skewed_digest_start = config.config.prev_max_level_digest_block( - config.zero.clone(), - skewed_digest_end.clone(), - ); - if let Some(skewed_digest_start) = skewed_digest_start { - let skewed_digest_range = (skewed_digest_end.clone() - - skewed_digest_start.clone()) - .try_into() - .ok() - .expect( - "skewed digest range is always <= max level digest range;\ - max level digest range always fits u32; qed", - ); - return Ok(( - skewed_digest_end.clone(), - skewed_digest_start, - skewed_digest_range, - None, - )) - } - } - } - - // we can't use next level digest if it touches any unknown (> max) blocks - if new_digest_end > max { - if begin < new_digest_begin { - current_begin = new_digest_begin; - } - break - } - - // we can (and will) use this digest - digest_level = new_digest_level; - digest_step = new_digest_step; - digest_interval = new_digest_interval; - current = new_current; - current_begin = new_digest_begin; - - // if current digest covers the whole range => no need to use next level digest - if current_begin <= begin && new_digest_end >= end { - break - } - } - } - - Ok((current, current_begin, digest_step, Some(digest_level))) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::changes_trie::Configuration; - - fn configuration_range<'a>( - config: &'a Configuration, - zero: u64, - ) -> ConfigurationRange<'a, u64> { - ConfigurationRange { config, zero, end: None } - } - - #[test] - fn lower_bound_max_digest_works() { - let config = Configuration { digest_interval: 4, digest_levels: 2 }; - - // when config activates at 0 - assert_eq!( - lower_bound_max_digest(configuration_range(&config, 0u64), 100_000u64, 20u64, 180u64) - .unwrap(), - (192, 176, 16, Some(2)), - ); - - // when config activates at 30 - assert_eq!( - lower_bound_max_digest(configuration_range(&config, 30u64), 100_000u64, 50u64, 210u64) - .unwrap(), - (222, 206, 16, Some(2)), - ); - } - - #[test] - fn surface_iterator_works() { - let config = Configuration { digest_interval: 4, digest_levels: 2 }; - - // when config activates at 0 - assert_eq!( - surface_iterator(configuration_range(&config, 0u64), 100_000u64, 40u64, 180u64,) - .unwrap() - .collect::>(), - vec![ - Ok((192, Some(2))), - Ok((176, Some(2))), - Ok((160, Some(2))), - Ok((144, Some(2))), - Ok((128, Some(2))), - Ok((112, Some(2))), - Ok((96, Some(2))), - Ok((80, Some(2))), - Ok((64, Some(2))), - Ok((48, Some(2))), - ], - ); - - // when config activates at 30 - assert_eq!( - surface_iterator(configuration_range(&config, 30u64), 100_000u64, 40u64, 180u64,) - .unwrap() - .collect::>(), - vec![ - Ok((190, Some(2))), - Ok((174, Some(2))), - Ok((158, Some(2))), - Ok((142, Some(2))), - Ok((126, Some(2))), - Ok((110, Some(2))), - Ok((94, Some(2))), - Ok((78, Some(2))), - Ok((62, Some(2))), - Ok((46, Some(2))), - ], - ); - - // when config activates at 0 AND max block is before next digest - assert_eq!( - surface_iterator(configuration_range(&config, 0u64), 183u64, 40u64, 183u64) - .unwrap() - .collect::>(), - vec![ - Ok((183, Some(0))), - Ok((182, Some(0))), - Ok((181, Some(0))), - Ok((180, Some(1))), - Ok((176, Some(2))), - Ok((160, Some(2))), - Ok((144, Some(2))), - Ok((128, Some(2))), - Ok((112, Some(2))), - Ok((96, Some(2))), - Ok((80, Some(2))), - Ok((64, Some(2))), - Ok((48, Some(2))), - ], - ); - } - - #[test] - fn surface_iterator_works_with_skewed_digest() { - let config = Configuration { digest_interval: 4, digest_levels: 2 }; - let mut config_range = configuration_range(&config, 0u64); - - // when config activates at 0 AND ends at 170 - config_range.end = Some(170); - assert_eq!( - surface_iterator(config_range, 100_000u64, 40u64, 170u64) - .unwrap() - .collect::>(), - vec![ - Ok((170, None)), - Ok((160, Some(2))), - Ok((144, Some(2))), - Ok((128, Some(2))), - Ok((112, Some(2))), - Ok((96, Some(2))), - Ok((80, Some(2))), - Ok((64, Some(2))), - Ok((48, Some(2))), - ], - ); - } -} diff --git a/primitives/state-machine/src/error.rs b/primitives/state-machine/src/error.rs index acc5b6080c7a3..a12b3eae71bd7 100644 --- a/primitives/state-machine/src/error.rs +++ b/primitives/state-machine/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/state-machine/src/ext.rs b/primitives/state-machine/src/ext.rs index c9693ca6a88c1..1db0ec517015b 100644 --- a/primitives/state-machine/src/ext.rs +++ b/primitives/state-machine/src/ext.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,27 +17,25 @@ //! Concrete externalities implementation. -use crate::{ - backend::Backend, overlayed_changes::OverlayedExtensions, IndexOperation, OverlayedChanges, - StorageKey, StorageValue, -}; +#[cfg(feature = "std")] +use crate::overlayed_changes::OverlayedExtensions; +use crate::{backend::Backend, IndexOperation, OverlayedChanges, StorageKey, StorageValue}; use codec::{Decode, Encode, EncodeAppend}; use hash_db::Hasher; -use sp_core::{ - hexdisplay::HexDisplay, - storage::{well_known_keys::is_child_storage_key, ChildInfo, TrackedStorageKey}, +#[cfg(feature = "std")] +use sp_core::hexdisplay::HexDisplay; +use sp_core::storage::{ + well_known_keys::is_child_storage_key, ChildInfo, StateVersion, TrackedStorageKey, }; -use sp_externalities::{Extension, ExtensionStore, Extensions, Externalities}; -use sp_trie::{empty_child_trie_root, trie_types::Layout}; +use sp_externalities::{Extension, ExtensionStore, Externalities, MultiRemovalResults}; +use sp_trie::{empty_child_trie_root, LayoutV1}; -#[cfg(feature = "std")] -use crate::changes_trie::State as ChangesTrieState; use crate::{log_error, trace, warn, StorageTransactionCache}; use sp_std::{ any::{Any, TypeId}, boxed::Box, cmp::Ordering, - fmt, vec, + vec, vec::Vec, }; #[cfg(feature = "std")] @@ -72,8 +70,8 @@ pub enum Error { } #[cfg(feature = "std")] -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { Error::Backend(ref e) => write!(f, "Storage backend error: {}", e), Error::Executor(ref e) => write!(f, "Sub-call execution error: {}", e), @@ -92,62 +90,52 @@ impl error::Error for Error { } /// Wraps a read-only backend, call executor, and current overlayed changes. -pub struct Ext<'a, H, N, B> +pub struct Ext<'a, H, B> where H: Hasher, B: 'a + Backend, - N: crate::changes_trie::BlockNumber, { /// The overlayed changes to write to. overlay: &'a mut OverlayedChanges, /// The storage backend to read from. backend: &'a B, /// The cache for the storage transactions. - storage_transaction_cache: &'a mut StorageTransactionCache, - /// Changes trie state to read from. - #[cfg(feature = "std")] - changes_trie_state: Option>, + storage_transaction_cache: &'a mut StorageTransactionCache, /// Pseudo-unique id used for tracing. pub id: u16, - /// Dummy usage of N arg. - _phantom: sp_std::marker::PhantomData, /// Extensions registered with this instance. #[cfg(feature = "std")] extensions: Option>, } -impl<'a, H, N, B> Ext<'a, H, N, B> +impl<'a, H, B> Ext<'a, H, B> where H: Hasher, B: Backend, - N: crate::changes_trie::BlockNumber, { /// Create a new `Ext`. #[cfg(not(feature = "std"))] pub fn new( overlay: &'a mut OverlayedChanges, - storage_transaction_cache: &'a mut StorageTransactionCache, + storage_transaction_cache: &'a mut StorageTransactionCache, backend: &'a B, ) -> Self { - Ext { overlay, backend, id: 0, storage_transaction_cache, _phantom: Default::default() } + Ext { overlay, backend, id: 0, storage_transaction_cache } } /// Create a new `Ext` from overlayed changes and read-only backend #[cfg(feature = "std")] pub fn new( overlay: &'a mut OverlayedChanges, - storage_transaction_cache: &'a mut StorageTransactionCache, + storage_transaction_cache: &'a mut StorageTransactionCache, backend: &'a B, - changes_trie_state: Option>, - extensions: Option<&'a mut Extensions>, + extensions: Option<&'a mut sp_externalities::Extensions>, ) -> Self { Self { overlay, backend, - changes_trie_state, storage_transaction_cache, id: rand::random(), - _phantom: Default::default(), extensions: extensions.map(OverlayedExtensions::new), } } @@ -161,12 +149,11 @@ where } #[cfg(test)] -impl<'a, H, N, B> Ext<'a, H, N, B> +impl<'a, H, B> Ext<'a, H, B> where H: Hasher, H::Out: Ord + 'static, B: 'a + Backend, - N: crate::changes_trie::BlockNumber, { pub fn storage_pairs(&self) -> Vec<(StorageKey, StorageValue)> { use std::collections::HashMap; @@ -183,12 +170,11 @@ where } } -impl<'a, H, N, B> Externalities for Ext<'a, H, N, B> +impl<'a, H, B> Externalities for Ext<'a, H, B> where H: Hasher, H::Out: Ord + 'static + codec::Codec, B: Backend, - N: crate::changes_trie::BlockNumber, { fn set_offchain_storage(&mut self, key: &[u8], value: Option<&[u8]>) { self.overlay.set_offchain_storage(key, value) @@ -330,8 +316,8 @@ where match (&next_backend_key, overlay_changes.peek()) { (_, None) => next_backend_key, (Some(_), Some(_)) => { - while let Some(overlay_key) = overlay_changes.next() { - let cmp = next_backend_key.as_deref().map(|v| v.cmp(&overlay_key.0)); + for overlay_key in overlay_changes { + let cmp = next_backend_key.as_deref().map(|v| v.cmp(overlay_key.0)); // If `backend_key` is less than the `overlay_key`, we found out next key. if cmp == Some(Ordering::Less) { @@ -346,7 +332,7 @@ where // this key. next_backend_key = self .backend - .next_storage_key(&overlay_key.0) + .next_storage_key(overlay_key.0) .expect(EXT_NOT_ALLOWED_TO_FAIL); } } @@ -371,8 +357,8 @@ where match (&next_backend_key, overlay_changes.peek()) { (_, None) => next_backend_key, (Some(_), Some(_)) => { - while let Some(overlay_key) = overlay_changes.next() { - let cmp = next_backend_key.as_deref().map(|v| v.cmp(&overlay_key.0)); + for overlay_key in overlay_changes { + let cmp = next_backend_key.as_deref().map(|v| v.cmp(overlay_key.0)); // If `backend_key` is less than the `overlay_key`, we found out next key. if cmp == Some(Ordering::Less) { @@ -387,7 +373,7 @@ where // this key. next_backend_key = self .backend - .next_child_storage_key(child_info, &overlay_key.0) + .next_child_storage_key(child_info, overlay_key.0) .expect(EXT_NOT_ALLOWED_TO_FAIL); } } @@ -447,7 +433,12 @@ where self.overlay.set_child_storage(child_info, key, value); } - fn kill_child_storage(&mut self, child_info: &ChildInfo, limit: Option) -> (bool, u32) { + fn kill_child_storage( + &mut self, + child_info: &ChildInfo, + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { trace!( target: "state", method = "ChildKill", @@ -456,11 +447,18 @@ where ); let _guard = guard(); self.mark_dirty(); - self.overlay.clear_child_storage(child_info); - self.limit_remove_from_backend(Some(child_info), None, limit) + let overlay = self.overlay.clear_child_storage(child_info); + let (maybe_cursor, backend, loops) = + self.limit_remove_from_backend(Some(child_info), None, maybe_limit, maybe_cursor); + MultiRemovalResults { maybe_cursor, backend, unique: overlay + backend, loops } } - fn clear_prefix(&mut self, prefix: &[u8], limit: Option) -> (bool, u32) { + fn clear_prefix( + &mut self, + prefix: &[u8], + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { trace!( target: "state", method = "ClearPrefix", @@ -474,20 +472,23 @@ where target: "trie", "Refuse to directly clear prefix that is part or contains of child storage key", ); - return (false, 0) + return MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } } self.mark_dirty(); - self.overlay.clear_prefix(prefix); - self.limit_remove_from_backend(None, Some(prefix), limit) + let overlay = self.overlay.clear_prefix(prefix); + let (maybe_cursor, backend, loops) = + self.limit_remove_from_backend(None, Some(prefix), maybe_limit, maybe_cursor); + MultiRemovalResults { maybe_cursor, backend, unique: overlay + backend, loops } } fn clear_child_prefix( &mut self, child_info: &ChildInfo, prefix: &[u8], - limit: Option, - ) -> (bool, u32) { + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { trace!( target: "state", method = "ChildClearPrefix", @@ -498,8 +499,14 @@ where let _guard = guard(); self.mark_dirty(); - self.overlay.clear_child_prefix(child_info, prefix); - self.limit_remove_from_backend(Some(child_info), Some(prefix), limit) + let overlay = self.overlay.clear_child_prefix(child_info, prefix); + let (maybe_cursor, backend, loops) = self.limit_remove_from_backend( + Some(child_info), + Some(prefix), + maybe_limit, + maybe_cursor, + ); + MultiRemovalResults { maybe_cursor, backend, unique: overlay + backend, loops } } fn storage_append(&mut self, key: Vec, value: Vec) { @@ -521,7 +528,7 @@ where StorageAppend::new(current_value).append(value); } - fn storage_root(&mut self) -> Vec { + fn storage_root(&mut self, state_version: StateVersion) -> Vec { let _guard = guard(); if let Some(ref root) = self.storage_transaction_cache.transaction_storage_root { trace!( @@ -534,7 +541,9 @@ where return root.encode() } - let root = self.overlay.storage_root(self.backend, self.storage_transaction_cache); + let root = + self.overlay + .storage_root(self.backend, self.storage_transaction_cache, state_version); trace!( target: "state", method = "StorageRoot", @@ -545,7 +554,11 @@ where root.encode() } - fn child_storage_root(&mut self, child_info: &ChildInfo) -> Vec { + fn child_storage_root( + &mut self, + child_info: &ChildInfo, + state_version: StateVersion, + ) -> Vec { let _guard = guard(); let storage_key = child_info.storage_key(); let prefixed_storage_key = child_info.prefixed_storage_key(); @@ -553,7 +566,8 @@ where let root = self .storage(prefixed_storage_key.as_slice()) .and_then(|k| Decode::decode(&mut &k[..]).ok()) - .unwrap_or_else(|| empty_child_trie_root::>()); + // V1 is equivalent to V0 on empty root. + .unwrap_or_else(empty_child_trie_root::>); trace!( target: "state", method = "ChildStorageRoot", @@ -566,7 +580,7 @@ where } else { let root = if let Some((changes, info)) = self.overlay.child_changes(storage_key) { let delta = changes.map(|(k, v)| (k.as_ref(), v.value().map(AsRef::as_ref))); - Some(self.backend.child_storage_root(info, delta)) + Some(self.backend.child_storage_root(info, delta, state_version)) } else { None }; @@ -588,7 +602,7 @@ where target: "state", method = "ChildStorageRoot", ext_id = %HexDisplay::from(&self.id.to_le_bytes()), - child_info = %HexDisplay::from(&storage_key.as_ref()), + child_info = %HexDisplay::from(&storage_key), storage_root = %HexDisplay::from(&root.as_ref()), cached = false, ); @@ -599,13 +613,14 @@ where let root = self .storage(prefixed_storage_key.as_slice()) .and_then(|k| Decode::decode(&mut &k[..]).ok()) - .unwrap_or_else(|| empty_child_trie_root::>()); + // V1 is equivalent to V0 on empty root. + .unwrap_or_else(empty_child_trie_root::>); trace!( target: "state", method = "ChildStorageRoot", ext_id = %HexDisplay::from(&self.id.to_le_bytes()), - child_info = %HexDisplay::from(&storage_key.as_ref()), + child_info = %HexDisplay::from(&storage_key), storage_root = %HexDisplay::from(&root.as_ref()), cached = false, ); @@ -646,54 +661,6 @@ where .add_transaction_index(IndexOperation::Renew { extrinsic: index, hash: hash.to_vec() }); } - #[cfg(not(feature = "std"))] - fn storage_changes_root(&mut self, _parent_hash: &[u8]) -> Result>, ()> { - Ok(None) - } - - #[cfg(feature = "std")] - fn storage_changes_root(&mut self, mut parent_hash: &[u8]) -> Result>, ()> { - let _guard = guard(); - if let Some(ref root) = self.storage_transaction_cache.changes_trie_transaction_storage_root - { - trace!( - target: "state", - method = "ChangesRoot", - ext_id = %HexDisplay::from(&self.id.to_le_bytes()), - parent_hash = %HexDisplay::from(&parent_hash), - ?root, - cached = true, - ); - - Ok(Some(root.encode())) - } else { - let root = self.overlay.changes_trie_root( - self.backend, - self.changes_trie_state.as_ref(), - Decode::decode(&mut parent_hash).map_err(|e| { - trace!( - target: "state", - error = %e, - "Failed to decode changes root parent hash", - ) - })?, - true, - self.storage_transaction_cache, - ); - - trace!( - target: "state", - method = "ChangesRoot", - ext_id = %HexDisplay::from(&self.id.to_le_bytes()), - parent_hash = %HexDisplay::from(&parent_hash), - ?root, - cached = false, - ); - - root.map(|r| r.map(|o| o.encode())) - } - } - fn storage_start_transaction(&mut self) { self.overlay.start_transaction() } @@ -714,10 +681,8 @@ where self.overlay .drain_storage_changes( self.backend, - #[cfg(feature = "std")] - None, - Default::default(), self.storage_transaction_cache, + Default::default(), // using any state ) .expect(EXT_NOT_ALLOWED_TO_FAIL); self.backend.wipe().expect(EXT_NOT_ALLOWED_TO_FAIL); @@ -728,18 +693,14 @@ where } fn commit(&mut self) { + // Bench always use latest state. + let state_version = StateVersion::default(); for _ in 0..self.overlay.transaction_depth() { self.overlay.commit_transaction().expect(BENCHMARKING_FN); } let changes = self .overlay - .drain_storage_changes( - self.backend, - #[cfg(feature = "std")] - None, - Default::default(), - self.storage_transaction_cache, - ) + .drain_storage_changes(self.backend, self.storage_transaction_cache, state_version) .expect(EXT_NOT_ALLOWED_TO_FAIL); self.backend .commit( @@ -780,54 +741,45 @@ where } } -impl<'a, H, N, B> Ext<'a, H, N, B> +impl<'a, H, B> Ext<'a, H, B> where H: Hasher, H::Out: Ord + 'static + codec::Codec, B: Backend, - N: crate::changes_trie::BlockNumber, { fn limit_remove_from_backend( &mut self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - limit: Option, - ) -> (bool, u32) { - let mut num_deleted: u32 = 0; - - if let Some(limit) = limit { - let mut all_deleted = true; - self.backend.apply_to_keys_while(child_info, prefix, |key| { - if num_deleted == limit { - all_deleted = false; - return false - } - if let Some(num) = num_deleted.checked_add(1) { - num_deleted = num; - } else { - all_deleted = false; + maybe_child: Option<&ChildInfo>, + maybe_prefix: Option<&[u8]>, + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> (Option>, u32, u32) { + let mut delete_count: u32 = 0; + let mut loop_count: u32 = 0; + let mut maybe_next_key = None; + self.backend + .apply_to_keys_while(maybe_child, maybe_prefix, maybe_cursor, |key| { + if maybe_limit.map_or(false, |limit| loop_count == limit) { + maybe_next_key = Some(key.to_vec()); return false } - if let Some(child_info) = child_info { - self.overlay.set_child_storage(child_info, key.to_vec(), None); - } else { - self.overlay.set_storage(key.to_vec(), None); - } - true - }); - (all_deleted, num_deleted) - } else { - self.backend.apply_to_keys_while(child_info, prefix, |key| { - num_deleted = num_deleted.saturating_add(1); - if let Some(child_info) = child_info { - self.overlay.set_child_storage(child_info, key.to_vec(), None); - } else { - self.overlay.set_storage(key.to_vec(), None); + let overlay = match maybe_child { + Some(child_info) => self.overlay.child_storage(child_info, key), + None => self.overlay.storage(key), + }; + if !matches!(overlay, Some(None)) { + // not pending deletion from the backend - delete it. + if let Some(child_info) = maybe_child { + self.overlay.set_child_storage(child_info, key.to_vec(), None); + } else { + self.overlay.set_storage(key.to_vec(), None); + } + delete_count = delete_count.saturating_add(1); } + loop_count = loop_count.saturating_add(1); true }); - (true, num_deleted) - } + (maybe_next_key, delete_count, loop_count) } } @@ -871,12 +823,11 @@ impl<'a> StorageAppend<'a> { } #[cfg(not(feature = "std"))] -impl<'a, H, N, B> ExtensionStore for Ext<'a, H, N, B> +impl<'a, H, B> ExtensionStore for Ext<'a, H, B> where H: Hasher, H::Out: Ord + 'static + codec::Codec, B: Backend, - N: crate::changes_trie::BlockNumber, { fn extension_by_type_id(&mut self, _type_id: TypeId) -> Option<&mut dyn Any> { None @@ -899,11 +850,10 @@ where } #[cfg(feature = "std")] -impl<'a, H, N, B> ExtensionStore for Ext<'a, H, N, B> +impl<'a, H, B> ExtensionStore for Ext<'a, H, B> where H: Hasher, B: 'a + Backend, - N: crate::changes_trie::BlockNumber, { fn extension_by_type_id(&mut self, type_id: TypeId) -> Option<&mut dyn Any> { self.extensions.as_mut().and_then(|exts| exts.get_mut(type_id)) @@ -940,86 +890,16 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{ - changes_trie::{ - Configuration as ChangesTrieConfiguration, InMemoryStorage as TestChangesTrieStorage, - }, - InMemoryBackend, - }; + use crate::InMemoryBackend; use codec::Encode; - use hex_literal::hex; - use num_traits::Zero; use sp_core::{ map, - storage::{well_known_keys::EXTRINSIC_INDEX, Storage, StorageChild}, - Blake2Hasher, H256, + storage::{Storage, StorageChild}, + Blake2Hasher, }; type TestBackend = InMemoryBackend; - type TestExt<'a> = Ext<'a, Blake2Hasher, u64, TestBackend>; - - fn prepare_overlay_with_changes() -> OverlayedChanges { - let mut changes = OverlayedChanges::default(); - changes.set_collect_extrinsics(true); - changes.set_extrinsic_index(1); - changes.set_storage(vec![1], Some(vec![100])); - changes.set_storage(EXTRINSIC_INDEX.to_vec(), Some(3u32.encode())); - changes.set_offchain_storage(b"k1", Some(b"v1")); - changes.set_offchain_storage(b"k2", Some(b"v2")); - changes - } - - fn changes_trie_config() -> ChangesTrieConfiguration { - ChangesTrieConfiguration { digest_interval: 0, digest_levels: 0 } - } - - #[test] - fn storage_changes_root_is_none_when_storage_is_not_provided() { - let mut overlay = prepare_overlay_with_changes(); - let mut cache = StorageTransactionCache::default(); - let backend = TestBackend::default(); - let mut ext = TestExt::new(&mut overlay, &mut cache, &backend, None, None); - assert_eq!(ext.storage_changes_root(&H256::default().encode()).unwrap(), None); - } - - #[test] - fn storage_changes_root_is_none_when_state_is_not_provided() { - let mut overlay = prepare_overlay_with_changes(); - let mut cache = StorageTransactionCache::default(); - let backend = TestBackend::default(); - let mut ext = TestExt::new(&mut overlay, &mut cache, &backend, None, None); - assert_eq!(ext.storage_changes_root(&H256::default().encode()).unwrap(), None); - } - - #[test] - fn storage_changes_root_is_some_when_extrinsic_changes_are_non_empty() { - let mut overlay = prepare_overlay_with_changes(); - let mut cache = StorageTransactionCache::default(); - let storage = TestChangesTrieStorage::with_blocks(vec![(99, Default::default())]); - let state = Some(ChangesTrieState::new(changes_trie_config(), Zero::zero(), &storage)); - let backend = TestBackend::default(); - let mut ext = TestExt::new(&mut overlay, &mut cache, &backend, state, None); - assert_eq!( - ext.storage_changes_root(&H256::default().encode()).unwrap(), - Some(hex!("bb0c2ef6e1d36d5490f9766cfcc7dfe2a6ca804504c3bb206053890d6dd02376").to_vec()), - ); - } - - #[test] - fn storage_changes_root_is_some_when_extrinsic_changes_are_empty() { - let mut overlay = prepare_overlay_with_changes(); - let mut cache = StorageTransactionCache::default(); - overlay.set_collect_extrinsics(false); - overlay.set_storage(vec![1], None); - let storage = TestChangesTrieStorage::with_blocks(vec![(99, Default::default())]); - let state = Some(ChangesTrieState::new(changes_trie_config(), Zero::zero(), &storage)); - let backend = TestBackend::default(); - let mut ext = TestExt::new(&mut overlay, &mut cache, &backend, state, None); - assert_eq!( - ext.storage_changes_root(&H256::default().encode()).unwrap(), - Some(hex!("96f5aae4690e7302737b6f9b7f8567d5bbb9eac1c315f80101235a92d9ec27f4").to_vec()), - ); - } + type TestExt<'a> = Ext<'a, Blake2Hasher, TestBackend>; #[test] fn next_storage_key_works() { @@ -1027,17 +907,20 @@ mod tests { let mut overlay = OverlayedChanges::default(); overlay.set_storage(vec![20], None); overlay.set_storage(vec![30], Some(vec![31])); - let backend = Storage { - top: map![ - vec![10] => vec![10], - vec![20] => vec![20], - vec![40] => vec![40] - ], - children_default: map![], - } - .into(); + let backend = ( + Storage { + top: map![ + vec![10] => vec![10], + vec![20] => vec![20], + vec![40] => vec![40] + ], + children_default: map![], + }, + StateVersion::default(), + ) + .into(); - let ext = TestExt::new(&mut overlay, &mut cache, &backend, None, None); + let ext = TestExt::new(&mut overlay, &mut cache, &backend, None); // next_backend < next_overlay assert_eq!(ext.next_storage_key(&[5]), Some(vec![10])); @@ -1053,7 +936,7 @@ mod tests { drop(ext); overlay.set_storage(vec![50], Some(vec![50])); - let ext = TestExt::new(&mut overlay, &mut cache, &backend, None, None); + let ext = TestExt::new(&mut overlay, &mut cache, &backend, None); // next_overlay exist but next_backend doesn't exist assert_eq!(ext.next_storage_key(&[40]), Some(vec![50])); @@ -1073,15 +956,18 @@ mod tests { overlay.set_storage(vec![27], None); overlay.set_storage(vec![28], None); overlay.set_storage(vec![29], None); - let backend = Storage { - top: map![ - vec![30] => vec![30] - ], - children_default: map![], - } - .into(); + let backend = ( + Storage { + top: map![ + vec![30] => vec![30] + ], + children_default: map![], + }, + StateVersion::default(), + ) + .into(); - let ext = TestExt::new(&mut overlay, &mut cache, &backend, None, None); + let ext = TestExt::new(&mut overlay, &mut cache, &backend, None); assert_eq!(ext.next_storage_key(&[5]), Some(vec![30])); @@ -1097,22 +983,25 @@ mod tests { let mut overlay = OverlayedChanges::default(); overlay.set_child_storage(child_info, vec![20], None); overlay.set_child_storage(child_info, vec![30], Some(vec![31])); - let backend = Storage { - top: map![], - children_default: map![ - child_info.storage_key().to_vec() => StorageChild { - data: map![ - vec![10] => vec![10], - vec![20] => vec![20], - vec![40] => vec![40] - ], - child_info: child_info.to_owned(), - } - ], - } - .into(); + let backend = ( + Storage { + top: map![], + children_default: map![ + child_info.storage_key().to_vec() => StorageChild { + data: map![ + vec![10] => vec![10], + vec![20] => vec![20], + vec![40] => vec![40] + ], + child_info: child_info.to_owned(), + } + ], + }, + StateVersion::default(), + ) + .into(); - let ext = TestExt::new(&mut overlay, &mut cache, &backend, None, None); + let ext = TestExt::new(&mut overlay, &mut cache, &backend, None); // next_backend < next_overlay assert_eq!(ext.next_child_storage_key(child_info, &[5]), Some(vec![10])); @@ -1128,7 +1017,7 @@ mod tests { drop(ext); overlay.set_child_storage(child_info, vec![50], Some(vec![50])); - let ext = TestExt::new(&mut overlay, &mut cache, &backend, None, None); + let ext = TestExt::new(&mut overlay, &mut cache, &backend, None); // next_overlay exist but next_backend doesn't exist assert_eq!(ext.next_child_storage_key(child_info, &[40]), Some(vec![50])); @@ -1142,22 +1031,25 @@ mod tests { let mut overlay = OverlayedChanges::default(); overlay.set_child_storage(child_info, vec![20], None); overlay.set_child_storage(child_info, vec![30], Some(vec![31])); - let backend = Storage { - top: map![], - children_default: map![ - child_info.storage_key().to_vec() => StorageChild { - data: map![ - vec![10] => vec![10], - vec![20] => vec![20], - vec![30] => vec![40] - ], - child_info: child_info.to_owned(), - } - ], - } - .into(); + let backend = ( + Storage { + top: map![], + children_default: map![ + child_info.storage_key().to_vec() => StorageChild { + data: map![ + vec![10] => vec![10], + vec![20] => vec![20], + vec![30] => vec![40] + ], + child_info: child_info.to_owned(), + } + ], + }, + StateVersion::default(), + ) + .into(); - let ext = TestExt::new(&mut overlay, &mut cache, &backend, None, None); + let ext = TestExt::new(&mut overlay, &mut cache, &backend, None); assert_eq!(ext.child_storage(child_info, &[10]), Some(vec![10])); assert_eq!( @@ -1181,20 +1073,23 @@ mod tests { let child_info = &child_info; let mut cache = StorageTransactionCache::default(); let mut overlay = OverlayedChanges::default(); - let backend = Storage { - top: map![], - children_default: map![ - child_info.storage_key().to_vec() => StorageChild { - data: map![ - vec![30] => vec![40] - ], - child_info: child_info.to_owned(), - } - ], - } - .into(); + let backend = ( + Storage { + top: map![], + children_default: map![ + child_info.storage_key().to_vec() => StorageChild { + data: map![ + vec![30] => vec![40] + ], + child_info: child_info.to_owned(), + } + ], + }, + StateVersion::default(), + ) + .into(); - let ext = TestExt::new(&mut overlay, &mut cache, &backend, None, None); + let ext = TestExt::new(&mut overlay, &mut cache, &backend, None); use sp_core::storage::well_known_keys; let mut ext = ext; @@ -1203,14 +1098,14 @@ mod tests { not_under_prefix.extend(b"path"); ext.set_storage(not_under_prefix.clone(), vec![10]); - ext.clear_prefix(&[], None); - ext.clear_prefix(&well_known_keys::CHILD_STORAGE_KEY_PREFIX[..4], None); + let _ = ext.clear_prefix(&[], None, None); + let _ = ext.clear_prefix(&well_known_keys::CHILD_STORAGE_KEY_PREFIX[..4], None, None); let mut under_prefix = well_known_keys::CHILD_STORAGE_KEY_PREFIX.to_vec(); under_prefix.extend(b"path"); - ext.clear_prefix(&well_known_keys::CHILD_STORAGE_KEY_PREFIX[..4], None); + let _ = ext.clear_prefix(&well_known_keys::CHILD_STORAGE_KEY_PREFIX[..4], None, None); assert_eq!(ext.child_storage(child_info, &[30]), Some(vec![40])); assert_eq!(ext.storage(not_under_prefix.as_slice()), Some(vec![10])); - ext.clear_prefix(¬_under_prefix[..5], None); + let _ = ext.clear_prefix(¬_under_prefix[..5], None, None); assert_eq!(ext.storage(not_under_prefix.as_slice()), None); } diff --git a/primitives/state-machine/src/in_memory_backend.rs b/primitives/state-machine/src/in_memory_backend.rs index f9f94c0c50d60..06714fb41405a 100644 --- a/primitives/state-machine/src/in_memory_backend.rs +++ b/primitives/state-machine/src/in_memory_backend.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,33 +19,50 @@ use crate::{ backend::Backend, trie_backend::TrieBackend, StorageCollection, StorageKey, StorageValue, + TrieBackendBuilder, }; use codec::Codec; use hash_db::Hasher; -use sp_core::storage::{ChildInfo, Storage}; -use sp_trie::{empty_trie_root, Layout, MemoryDB}; +use sp_core::storage::{ChildInfo, StateVersion, Storage}; +use sp_trie::{empty_trie_root, GenericMemoryDB, HashKey, KeyFunction, LayoutV1, MemoryDB}; use std::collections::{BTreeMap, HashMap}; /// Create a new empty instance of in-memory backend. -pub fn new_in_mem() -> TrieBackend, H> +/// +/// It will use [`HashKey`] to store the keys internally. +pub fn new_in_mem_hash_key() -> TrieBackend, H> where + H: Hasher, H::Out: Codec + Ord, { - let db = MemoryDB::default(); - TrieBackend::new(db, empty_trie_root::>()) + new_in_mem::>() } -impl TrieBackend, H> +/// Create a new empty instance of in-memory backend. +pub fn new_in_mem() -> TrieBackend, H> +where + H: Hasher, + H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, +{ + let db = GenericMemoryDB::default(); + // V1 is same as V0 for an empty trie. + TrieBackendBuilder::new(db, empty_trie_root::>()).build() +} + +impl TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { /// Copy the state, with applied updates pub fn update, StorageCollection)>>( &self, changes: T, + state_version: StateVersion, ) -> Self { let mut clone = self.clone(); - clone.insert(changes); + clone.insert(changes, state_version); clone } @@ -53,30 +70,32 @@ where pub fn insert, StorageCollection)>>( &mut self, changes: T, + state_version: StateVersion, ) { let (top, child) = changes.into_iter().partition::, _>(|v| v.0.is_none()); let (root, transaction) = self.full_storage_root( - top.iter().map(|(_, v)| v).flatten().map(|(k, v)| (&k[..], v.as_deref())), + top.iter().flat_map(|(_, v)| v).map(|(k, v)| (&k[..], v.as_deref())), child.iter().filter_map(|v| { v.0.as_ref().map(|c| (c, v.1.iter().map(|(k, v)| (&k[..], v.as_deref())))) }), + state_version, ); self.apply_transaction(root, transaction); } /// Merge trie nodes into this backend. - pub fn update_backend(&self, root: H::Out, changes: MemoryDB) -> Self { + pub fn update_backend(&self, root: H::Out, changes: GenericMemoryDB) -> Self { let mut clone = self.backend_storage().clone(); clone.consolidate(changes); - Self::new(clone, root) + TrieBackendBuilder::new(clone, root).build() } /// Apply the given transaction to this backend and set the root to the given value. - pub fn apply_transaction(&mut self, root: H::Out, transaction: MemoryDB) { + pub fn apply_transaction(&mut self, root: H::Out, transaction: GenericMemoryDB) { let mut storage = sp_std::mem::take(self).into_storage(); storage.consolidate(transaction); - *self = TrieBackend::new(storage, root); + *self = TrieBackendBuilder::new(storage, root).build(); } /// Compare with another in-memory backend. @@ -85,71 +104,89 @@ where } } -impl Clone for TrieBackend, H> +impl Clone for TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { fn clone(&self) -> Self { - TrieBackend::new(self.backend_storage().clone(), self.root().clone()) + TrieBackendBuilder::new(self.backend_storage().clone(), *self.root()).build() } } -impl Default for TrieBackend, H> +impl Default for TrieBackend, H> where + H: Hasher, H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { fn default() -> Self { new_in_mem() } } -impl From, BTreeMap>> - for TrieBackend, H> +impl + From<(HashMap, BTreeMap>, StateVersion)> + for TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { - fn from(inner: HashMap, BTreeMap>) -> Self { + fn from( + (inner, state_version): ( + HashMap, BTreeMap>, + StateVersion, + ), + ) -> Self { let mut backend = new_in_mem(); backend.insert( inner .into_iter() .map(|(k, m)| (k, m.into_iter().map(|(k, v)| (k, Some(v))).collect())), + state_version, ); backend } } -impl From for TrieBackend, H> +impl From<(Storage, StateVersion)> for TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { - fn from(inners: Storage) -> Self { + fn from((inners, state_version): (Storage, StateVersion)) -> Self { let mut inner: HashMap, BTreeMap> = inners .children_default - .into_iter() - .map(|(_k, c)| (Some(c.child_info), c.data)) + .into_values() + .map(|c| (Some(c.child_info), c.data)) .collect(); inner.insert(None, inners.top); - inner.into() + (inner, state_version).into() } } -impl From> for TrieBackend, H> +impl From<(BTreeMap, StateVersion)> + for TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { - fn from(inner: BTreeMap) -> Self { + fn from((inner, state_version): (BTreeMap, StateVersion)) -> Self { let mut expanded = HashMap::new(); expanded.insert(None, inner); - expanded.into() + (expanded, state_version).into() } } -impl From, StorageCollection)>> for TrieBackend, H> +impl From<(Vec<(Option, StorageCollection)>, StateVersion)> + for TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { - fn from(inner: Vec<(Option, StorageCollection)>) -> Self { + fn from( + (inner, state_version): (Vec<(Option, StorageCollection)>, StateVersion), + ) -> Self { let mut expanded: HashMap, BTreeMap> = HashMap::new(); for (child_info, key_values) in inner { @@ -160,25 +197,29 @@ where } } } - expanded.into() + (expanded, state_version).into() } } #[cfg(test)] mod tests { use super::*; - use crate::backend::Backend; + use crate::backend::{AsTrieBackend, Backend}; + use sp_core::storage::StateVersion; use sp_runtime::traits::BlakeTwo256; /// Assert in memory backend with only child trie keys works as trie backend. #[test] fn in_memory_with_child_trie_only() { - let storage = new_in_mem::(); + let state_version = StateVersion::default(); + let storage = new_in_mem_hash_key::(); let child_info = ChildInfo::new_default(b"1"); let child_info = &child_info; - let storage = storage - .update(vec![(Some(child_info.clone()), vec![(b"2".to_vec(), Some(b"3".to_vec()))])]); - let trie_backend = storage.as_trie_backend().unwrap(); + let storage = storage.update( + vec![(Some(child_info.clone()), vec![(b"2".to_vec(), Some(b"3".to_vec()))])], + state_version, + ); + let trie_backend = storage.as_trie_backend(); assert_eq!(trie_backend.child_storage(child_info, b"2").unwrap(), Some(b"3".to_vec())); let storage_key = child_info.prefixed_storage_key(); assert!(trie_backend.storage(storage_key.as_slice()).unwrap().is_some()); @@ -186,13 +227,18 @@ mod tests { #[test] fn insert_multiple_times_child_data_works() { - let mut storage = new_in_mem::(); + let state_version = StateVersion::default(); + let mut storage = new_in_mem_hash_key::(); let child_info = ChildInfo::new_default(b"1"); - storage - .insert(vec![(Some(child_info.clone()), vec![(b"2".to_vec(), Some(b"3".to_vec()))])]); - storage - .insert(vec![(Some(child_info.clone()), vec![(b"1".to_vec(), Some(b"3".to_vec()))])]); + storage.insert( + vec![(Some(child_info.clone()), vec![(b"2".to_vec(), Some(b"3".to_vec()))])], + state_version, + ); + storage.insert( + vec![(Some(child_info.clone()), vec![(b"1".to_vec(), Some(b"3".to_vec()))])], + state_version, + ); assert_eq!(storage.child_storage(&child_info, &b"2"[..]), Ok(Some(b"3".to_vec()))); assert_eq!(storage.child_storage(&child_info, &b"1"[..]), Ok(Some(b"3".to_vec()))); diff --git a/primitives/state-machine/src/lib.rs b/primitives/state-machine/src/lib.rs index 05d2c6d20ccee..225fe1582e752 100644 --- a/primitives/state-machine/src/lib.rs +++ b/primitives/state-machine/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,16 +23,12 @@ pub mod backend; #[cfg(feature = "std")] mod basic; -#[cfg(feature = "std")] -mod changes_trie; mod error; mod ext; #[cfg(feature = "std")] mod in_memory_backend; pub(crate) mod overlayed_changes; #[cfg(feature = "std")] -mod proving_backend; -#[cfg(feature = "std")] mod read_only; mod stats; #[cfg(feature = "std")] @@ -55,11 +51,19 @@ pub use tracing::trace; #[cfg(not(feature = "std"))] #[macro_export] macro_rules! warn { - (target: $target:expr, $($arg:tt)+) => { - () + (target: $target:expr, $message:expr $( , $arg:ident )* $( , )?) => { + { + $( + let _ = &$arg; + )* + } }; - ($($arg:tt)+) => { - () + ($message:expr, $( $arg:expr, )*) => { + { + $( + let _ = &$arg; + )* + } }; } @@ -68,11 +72,12 @@ macro_rules! warn { #[cfg(not(feature = "std"))] #[macro_export] macro_rules! debug { - (target: $target:expr, $($arg:tt)+) => { - () - }; - ($($arg:tt)+) => { - () + (target: $target:expr, $message:expr $( , $arg:ident )* $( , )?) => { + { + $( + let _ = &$arg; + )* + } }; } @@ -119,6 +124,7 @@ impl sp_std::fmt::Display for DefaultError { pub use crate::{ backend::Backend, + error::{Error, ExecutionError}, ext::Ext, overlayed_changes::{ ChildStorageCollection, IndexOperation, OffchainChangesCollection, @@ -126,74 +132,53 @@ pub use crate::{ StorageTransactionCache, StorageValue, }, stats::{StateMachineStats, UsageInfo, UsageUnit}, - trie_backend::TrieBackend, + trie_backend::{TrieBackend, TrieBackendBuilder}, trie_backend_essence::{Storage, TrieBackendStorage}, }; -pub use error::{Error, ExecutionError}; - -#[cfg(not(feature = "std"))] -mod changes_trie { - /// Stub for change trie block number until - /// change trie move to no_std. - pub trait BlockNumber {} - - impl BlockNumber for N {} -} #[cfg(feature = "std")] mod std_reexport { pub use crate::{ basic::BasicExternalities, - changes_trie::{ - disabled_state as disabled_changes_trie_state, key_changes, key_changes_proof, - key_changes_proof_check, key_changes_proof_check_with_db, prune as prune_changes_tries, - AnchorBlockId as ChangesTrieAnchorBlockId, BlockNumber as ChangesTrieBlockNumber, - BuildCache as ChangesTrieBuildCache, CacheAction as ChangesTrieCacheAction, - ConfigurationRange as ChangesTrieConfigurationRange, - InMemoryStorage as InMemoryChangesTrieStorage, RootsStorage as ChangesTrieRootsStorage, - State as ChangesTrieState, Storage as ChangesTrieStorage, - }, error::{Error, ExecutionError}, - in_memory_backend::new_in_mem, - proving_backend::{ - create_proof_check_backend, ProofRecorder, ProvingBackend, ProvingBackendRecorder, - }, + in_memory_backend::{new_in_mem, new_in_mem_hash_key}, read_only::{InspectState, ReadOnlyExternalities}, testing::TestExternalities, + trie_backend::create_proof_check_backend, }; pub use sp_trie::{ - trie_types::{Layout, TrieDBMut}, - DBValue, MemoryDB, StorageProof, TrieMut, + trie_types::{TrieDBMutV0, TrieDBMutV1}, + CompactProof, DBValue, LayoutV0, LayoutV1, MemoryDB, StorageProof, TrieMut, }; } #[cfg(feature = "std")] mod execution { + use crate::backend::AsTrieBackend; + use super::*; - use codec::{Codec, Decode, Encode}; + use codec::Codec; use hash_db::Hasher; + use smallvec::SmallVec; use sp_core::{ hexdisplay::HexDisplay, - storage::ChildInfo, + storage::{ChildInfo, ChildType, PrefixedStorageKey}, traits::{CodeExecutor, ReadRuntimeVersionExt, RuntimeCode, SpawnNamed}, - NativeOrEncoded, NeverNativeValue, }; use sp_externalities::Extensions; - use std::{collections::HashMap, fmt, panic::UnwindSafe, result}; - use tracing::{trace, warn}; + use std::{ + collections::{HashMap, HashSet}, + fmt, + }; const PROOF_CLOSE_TRANSACTION: &str = "\ Closing a transaction that was started in this function. Client initiated transactions are protected from being closed by the runtime. qed"; - pub(crate) type CallResult = Result, E>; + pub(crate) type CallResult = Result, E>; /// Default handler of the execution manager. - pub type DefaultHandler = fn(CallResult, CallResult) -> CallResult; - - /// Type of changes trie transaction. - pub type ChangesTrieTransaction = - (MemoryDB, ChangesTrieCacheAction<::Out, N>); + pub type DefaultHandler = fn(CallResult, CallResult) -> CallResult; /// Trie backend with in-memory storage. pub type InMemoryBackend = TrieBackend, H>; @@ -255,9 +240,7 @@ mod execution { impl ExecutionStrategy { /// Gets the corresponding manager for the execution strategy. - pub fn get_manager( - self, - ) -> ExecutionManager> { + pub fn get_manager(self) -> ExecutionManager> { match self { ExecutionStrategy::AlwaysWasm => ExecutionManager::AlwaysWasm(BackendTrustLevel::Trusted), @@ -277,28 +260,27 @@ mod execution { } /// Evaluate to ExecutionManager::NativeElseWasm, without having to figure out the type. - pub fn native_else_wasm() -> ExecutionManager> { + pub fn native_else_wasm() -> ExecutionManager> { ExecutionManager::NativeElseWasm } /// Evaluate to ExecutionManager::AlwaysWasm with trusted backend, without having to figure out /// the type. - fn always_wasm() -> ExecutionManager> { + fn always_wasm() -> ExecutionManager> { ExecutionManager::AlwaysWasm(BackendTrustLevel::Trusted) } /// Evaluate ExecutionManager::AlwaysWasm with untrusted backend, without having to figure out /// the type. - fn always_untrusted_wasm() -> ExecutionManager> { + fn always_untrusted_wasm() -> ExecutionManager> { ExecutionManager::AlwaysWasm(BackendTrustLevel::Untrusted) } /// The substrate state machine. - pub struct StateMachine<'a, B, H, N, Exec> + pub struct StateMachine<'a, B, H, Exec> where H: Hasher, B: Backend, - N: ChangesTrieBlockNumber, { backend: &'a B, exec: &'a Exec, @@ -306,8 +288,7 @@ mod execution { call_data: &'a [u8], overlay: &'a mut OverlayedChanges, extensions: Extensions, - changes_trie_state: Option>, - storage_transaction_cache: Option<&'a mut StorageTransactionCache>, + storage_transaction_cache: Option<&'a mut StorageTransactionCache>, runtime_code: &'a RuntimeCode<'a>, stats: StateMachineStats, /// The hash of the block the state machine will be executed on. @@ -316,29 +297,26 @@ mod execution { parent_hash: Option, } - impl<'a, B, H, N, Exec> Drop for StateMachine<'a, B, H, N, Exec> + impl<'a, B, H, Exec> Drop for StateMachine<'a, B, H, Exec> where H: Hasher, B: Backend, - N: ChangesTrieBlockNumber, { fn drop(&mut self) { self.backend.register_overlay_stats(&self.stats); } } - impl<'a, B, H, N, Exec> StateMachine<'a, B, H, N, Exec> + impl<'a, B, H, Exec> StateMachine<'a, B, H, Exec> where H: Hasher, H::Out: Ord + 'static + codec::Codec, Exec: CodeExecutor + Clone + 'static, B: Backend, - N: crate::changes_trie::BlockNumber, { /// Creates new substrate state machine. pub fn new( backend: &'a B, - changes_trie_state: Option>, overlay: &'a mut OverlayedChanges, exec: &'a Exec, method: &'a str, @@ -357,7 +335,6 @@ mod execution { call_data, extensions, overlay, - changes_trie_state, storage_transaction_cache: None, runtime_code, stats: StateMachineStats::default(), @@ -372,7 +349,7 @@ mod execution { /// build that will be cached. pub fn with_storage_transaction_cache( mut self, - cache: Option<&'a mut StorageTransactionCache>, + cache: Option<&'a mut StorageTransactionCache>, ) -> Self { self.storage_transaction_cache = cache; self @@ -397,23 +374,10 @@ mod execution { pub fn execute(&mut self, strategy: ExecutionStrategy) -> Result, Box> { // We are not giving a native call and thus we are sure that the result can never be a // native value. - self.execute_using_consensus_failure_handler::<_, NeverNativeValue, fn() -> _>( - strategy.get_manager(), - None, - ) - .map(NativeOrEncoded::into_encoded) + self.execute_using_consensus_failure_handler(strategy.get_manager()) } - fn execute_aux( - &mut self, - use_native: bool, - native_call: Option, - ) -> (CallResult, bool) - where - R: Decode + Encode + PartialEq, - NC: FnOnce() -> result::Result> - + UnwindSafe, - { + fn execute_aux(&mut self, use_native: bool) -> (CallResult, bool) { let mut cache = StorageTransactionCache::default(); let cache = match self.storage_transaction_cache.as_mut() { @@ -425,13 +389,7 @@ mod execution { .enter_runtime() .expect("StateMachine is never called from the runtime; qed"); - let mut ext = Ext::new( - self.overlay, - cache, - self.backend, - self.changes_trie_state.clone(), - Some(&mut self.extensions), - ); + let mut ext = Ext::new(self.overlay, cache, self.backend, Some(&mut self.extensions)); let ext_id = ext.id; @@ -450,7 +408,6 @@ mod execution { self.method, self.call_data, use_native, - native_call, ); self.overlay @@ -468,26 +425,20 @@ mod execution { (result, was_native) } - fn execute_call_with_both_strategy( + fn execute_call_with_both_strategy( &mut self, - mut native_call: Option, on_consensus_failure: Handler, - ) -> CallResult + ) -> CallResult where - R: Decode + Encode + PartialEq, - NC: FnOnce() -> result::Result> - + UnwindSafe, - Handler: FnOnce( - CallResult, - CallResult, - ) -> CallResult, + Handler: + FnOnce(CallResult, CallResult) -> CallResult, { self.overlay.start_transaction(); - let (result, was_native) = self.execute_aux(true, native_call.take()); + let (result, was_native) = self.execute_aux(true); if was_native { self.overlay.rollback_transaction().expect(PROOF_CLOSE_TRANSACTION); - let (wasm_result, _) = self.execute_aux(false, native_call); + let (wasm_result, _) = self.execute_aux(false); if (result.is_ok() && wasm_result.is_ok() && result.as_ref().ok() == wasm_result.as_ref().ok()) || @@ -503,25 +454,16 @@ mod execution { } } - fn execute_call_with_native_else_wasm_strategy( - &mut self, - mut native_call: Option, - ) -> CallResult - where - R: Decode + Encode + PartialEq, - NC: FnOnce() -> result::Result> - + UnwindSafe, - { + fn execute_call_with_native_else_wasm_strategy(&mut self) -> CallResult { self.overlay.start_transaction(); - let (result, was_native) = self.execute_aux(true, native_call.take()); + let (result, was_native) = self.execute_aux(true); if !was_native || result.is_ok() { self.overlay.commit_transaction().expect(PROOF_CLOSE_TRANSACTION); result } else { self.overlay.rollback_transaction().expect(PROOF_CLOSE_TRANSACTION); - let (wasm_result, _) = self.execute_aux(false, native_call); - wasm_result + self.execute_aux(false).0 } } @@ -534,38 +476,29 @@ mod execution { /// /// Returns the result of the executed function either in native representation `R` or /// in SCALE encoded representation. - pub fn execute_using_consensus_failure_handler( + pub fn execute_using_consensus_failure_handler( &mut self, manager: ExecutionManager, - mut native_call: Option, - ) -> Result, Box> + ) -> Result, Box> where - R: Decode + Encode + PartialEq, - NC: FnOnce() -> result::Result> - + UnwindSafe, - Handler: FnOnce( - CallResult, - CallResult, - ) -> CallResult, + Handler: + FnOnce(CallResult, CallResult) -> CallResult, { - let changes_tries_enabled = self.changes_trie_state.is_some(); - self.overlay.set_collect_extrinsics(changes_tries_enabled); - let result = { match manager { - ExecutionManager::Both(on_consensus_failure) => self - .execute_call_with_both_strategy(native_call.take(), on_consensus_failure), + ExecutionManager::Both(on_consensus_failure) => + self.execute_call_with_both_strategy(on_consensus_failure), ExecutionManager::NativeElseWasm => - self.execute_call_with_native_else_wasm_strategy(native_call.take()), + self.execute_call_with_native_else_wasm_strategy(), ExecutionManager::AlwaysWasm(trust_level) => { let _abort_guard = match trust_level { BackendTrustLevel::Trusted => None, BackendTrustLevel::Untrusted => Some(sp_panic_handler::AbortGuard::never_abort()), }; - self.execute_aux(false, native_call).0 + self.execute_aux(false).0 }, - ExecutionManager::NativeWhenPossible => self.execute_aux(true, native_call).0, + ExecutionManager::NativeWhenPossible => self.execute_aux(true).0, } }; @@ -574,7 +507,7 @@ mod execution { } /// Prove execution using the given state backend, overlayed changes, and call executor. - pub fn prove_execution( + pub fn prove_execution( backend: &mut B, overlay: &mut OverlayedChanges, exec: &Exec, @@ -584,17 +517,14 @@ mod execution { runtime_code: &RuntimeCode, ) -> Result<(Vec, StorageProof), Box> where - B: Backend, + B: AsTrieBackend, H: Hasher, H::Out: Ord + 'static + codec::Codec, Exec: CodeExecutor + Clone + 'static, - N: crate::changes_trie::BlockNumber, Spawn: SpawnNamed + Send + 'static, { - let trie_backend = backend - .as_trie_backend() - .ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box)?; - prove_execution_on_trie_backend::<_, _, N, _, _>( + let trie_backend = backend.as_trie_backend(); + prove_execution_on_trie_backend::<_, _, _, _>( trie_backend, overlay, exec, @@ -602,6 +532,7 @@ mod execution { method, call_data, runtime_code, + Default::default(), ) } @@ -614,7 +545,7 @@ mod execution { /// /// Note: changes to code will be in place if this call is made again. For running partial /// blocks (e.g. a transaction at a time), ensure a different method is used. - pub fn prove_execution_on_trie_backend( + pub fn prove_execution_on_trie_backend( trie_backend: &TrieBackend, overlay: &mut OverlayedChanges, exec: &Exec, @@ -622,38 +553,39 @@ mod execution { method: &str, call_data: &[u8], runtime_code: &RuntimeCode, + extensions: Extensions, ) -> Result<(Vec, StorageProof), Box> where S: trie_backend_essence::TrieBackendStorage, H: Hasher, H::Out: Ord + 'static + codec::Codec, Exec: CodeExecutor + 'static + Clone, - N: crate::changes_trie::BlockNumber, Spawn: SpawnNamed + Send + 'static, { - let proving_backend = proving_backend::ProvingBackend::new(trie_backend); - let mut sm = StateMachine::<_, H, N, Exec>::new( + let proving_backend = + TrieBackendBuilder::wrap(trie_backend).with_recorder(Default::default()).build(); + + let result = StateMachine::<_, H, Exec>::new( &proving_backend, - None, overlay, exec, method, call_data, - Extensions::default(), + extensions, runtime_code, spawn_handle, - ); + ) + .execute_using_consensus_failure_handler::<_>(always_wasm())?; - let result = sm.execute_using_consensus_failure_handler::<_, NeverNativeValue, fn() -> _>( - always_wasm(), - None, - )?; - let proof = sm.backend.extract_proof(); - Ok((result.into_encoded(), proof)) + let proof = proving_backend + .extract_proof() + .expect("A recorder was set and thus, a storage proof can be extracted; qed"); + + Ok((result, proof)) } /// Check execution proof, generated by `prove_execution` call. - pub fn execution_proof_check( + pub fn execution_proof_check( root: H::Out, proof: StorageProof, overlay: &mut OverlayedChanges, @@ -664,14 +596,13 @@ mod execution { runtime_code: &RuntimeCode, ) -> Result, Box> where - H: Hasher, + H: Hasher + 'static, Exec: CodeExecutor + Clone + 'static, H::Out: Ord + 'static + codec::Codec, - N: crate::changes_trie::BlockNumber, Spawn: SpawnNamed + Send + 'static, { - let trie_backend = create_proof_check_backend::(root.into(), proof)?; - execution_proof_check_on_trie_backend::<_, N, _, _>( + let trie_backend = create_proof_check_backend::(root, proof)?; + execution_proof_check_on_trie_backend::<_, _, _>( &trie_backend, overlay, exec, @@ -683,7 +614,7 @@ mod execution { } /// Check execution proof on proving backend, generated by `prove_execution` call. - pub fn execution_proof_check_on_trie_backend( + pub fn execution_proof_check_on_trie_backend( trie_backend: &TrieBackend, H>, overlay: &mut OverlayedChanges, exec: &Exec, @@ -696,12 +627,10 @@ mod execution { H: Hasher, H::Out: Ord + 'static + codec::Codec, Exec: CodeExecutor + Clone + 'static, - N: crate::changes_trie::BlockNumber, Spawn: SpawnNamed + Send + 'static, { - let mut sm = StateMachine::<_, H, N, Exec>::new( + StateMachine::<_, H, Exec>::new( trie_backend, - None, overlay, exec, method, @@ -709,30 +638,273 @@ mod execution { Extensions::default(), runtime_code, spawn_handle, - ); - - sm.execute_using_consensus_failure_handler::<_, NeverNativeValue, fn() -> _>( - always_untrusted_wasm(), - None, ) - .map(NativeOrEncoded::into_encoded) + .execute_using_consensus_failure_handler(always_untrusted_wasm()) } /// Generate storage read proof. pub fn prove_read(backend: B, keys: I) -> Result> where - B: Backend, + B: AsTrieBackend, H: Hasher, H::Out: Ord + Codec, I: IntoIterator, I::Item: AsRef<[u8]>, { - let trie_backend = backend - .as_trie_backend() - .ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box)?; + let trie_backend = backend.as_trie_backend(); prove_read_on_trie_backend(trie_backend, keys) } + /// State machine only allows a single level + /// of child trie. + pub const MAX_NESTED_TRIE_DEPTH: usize = 2; + + /// Multiple key value state. + /// States are ordered by root storage key. + #[derive(PartialEq, Eq, Clone)] + pub struct KeyValueStates(pub Vec); + + /// A key value state at any storage level. + #[derive(PartialEq, Eq, Clone)] + pub struct KeyValueStorageLevel { + /// State root of the level, for + /// top trie it is as an empty byte array. + pub state_root: Vec, + /// Storage of parents, empty for top root or + /// when exporting (building proof). + pub parent_storage_keys: Vec>, + /// Pair of key and values from this state. + pub key_values: Vec<(Vec, Vec)>, + } + + impl From for KeyValueStates + where + I: IntoIterator, (Vec<(Vec, Vec)>, Vec>))>, + { + fn from(b: I) -> Self { + let mut result = Vec::new(); + for (state_root, (key_values, storage_paths)) in b.into_iter() { + result.push(KeyValueStorageLevel { + state_root, + key_values, + parent_storage_keys: storage_paths, + }) + } + KeyValueStates(result) + } + } + + impl KeyValueStates { + /// Return total number of key values in states. + pub fn len(&self) -> usize { + self.0.iter().fold(0, |nb, state| nb + state.key_values.len()) + } + + /// Update last keys accessed from this state. + pub fn update_last_key( + &self, + stopped_at: usize, + last: &mut SmallVec<[Vec; 2]>, + ) -> bool { + if stopped_at == 0 || stopped_at > MAX_NESTED_TRIE_DEPTH { + return false + } + match stopped_at { + 1 => { + let top_last = + self.0.get(0).and_then(|s| s.key_values.last().map(|kv| kv.0.clone())); + if let Some(top_last) = top_last { + match last.len() { + 0 => { + last.push(top_last); + return true + }, + 2 => { + last.pop(); + }, + _ => (), + } + // update top trie access. + last[0] = top_last; + return true + } else { + // No change in top trie accesses. + // Indicates end of reading of a child trie. + last.truncate(1); + return true + } + }, + 2 => { + let top_last = + self.0.get(0).and_then(|s| s.key_values.last().map(|kv| kv.0.clone())); + let child_last = + self.0.last().and_then(|s| s.key_values.last().map(|kv| kv.0.clone())); + + if let Some(child_last) = child_last { + if last.is_empty() { + if let Some(top_last) = top_last { + last.push(top_last) + } else { + return false + } + } else if let Some(top_last) = top_last { + last[0] = top_last; + } + if last.len() == 2 { + last.pop(); + } + last.push(child_last); + return true + } else { + // stopped at level 2 so child last is define. + return false + } + }, + _ => (), + } + false + } + } + + /// Generate range storage read proof, with child tries + /// content. + /// A size limit is applied to the proof with the + /// exception that `start_at` and its following element + /// are always part of the proof. + /// If a key different than `start_at` is a child trie root, + /// the child trie content will be included in the proof. + pub fn prove_range_read_with_child_with_size( + backend: B, + size_limit: usize, + start_at: &[Vec], + ) -> Result<(StorageProof, u32), Box> + where + B: AsTrieBackend, + H: Hasher, + H::Out: Ord + Codec, + { + let trie_backend = backend.as_trie_backend(); + prove_range_read_with_child_with_size_on_trie_backend(trie_backend, size_limit, start_at) + } + + /// Generate range storage read proof, with child tries + /// content. + /// See `prove_range_read_with_child_with_size`. + pub fn prove_range_read_with_child_with_size_on_trie_backend( + trie_backend: &TrieBackend, + size_limit: usize, + start_at: &[Vec], + ) -> Result<(StorageProof, u32), Box> + where + S: trie_backend_essence::TrieBackendStorage, + H: Hasher, + H::Out: Ord + Codec, + { + if start_at.len() > MAX_NESTED_TRIE_DEPTH { + return Err(Box::new("Invalid start of range.")) + } + + let recorder = sp_trie::recorder::Recorder::default(); + let proving_backend = + TrieBackendBuilder::wrap(trie_backend).with_recorder(recorder.clone()).build(); + let mut count = 0; + + let mut child_roots = HashSet::new(); + let (mut child_key, mut start_at) = if start_at.len() == 2 { + let storage_key = start_at.get(0).expect("Checked length.").clone(); + if let Some(state_root) = proving_backend + .storage(&storage_key) + .map_err(|e| Box::new(e) as Box)? + { + child_roots.insert(state_root); + } else { + return Err(Box::new("Invalid range start child trie key.")) + } + + (Some(storage_key), start_at.get(1).cloned()) + } else { + (None, start_at.get(0).cloned()) + }; + + loop { + let (child_info, depth) = if let Some(storage_key) = child_key.as_ref() { + let storage_key = PrefixedStorageKey::new_ref(storage_key); + ( + Some(match ChildType::from_prefixed_key(storage_key) { + Some((ChildType::ParentKeyId, storage_key)) => + ChildInfo::new_default(storage_key), + None => return Err(Box::new("Invalid range start child trie key.")), + }), + 2, + ) + } else { + (None, 1) + }; + + let start_at_ref = start_at.as_ref().map(AsRef::as_ref); + let mut switch_child_key = None; + let mut first = start_at.is_some(); + let completed = proving_backend + .apply_to_key_values_while( + child_info.as_ref(), + None, + start_at_ref, + |key, value| { + if first && + start_at_ref + .as_ref() + .map(|start| &key.as_slice() > start) + .unwrap_or(true) + { + first = false; + } + + if first { + true + } else if depth < MAX_NESTED_TRIE_DEPTH && + sp_core::storage::well_known_keys::is_child_storage_key( + key.as_slice(), + ) { + count += 1; + if !child_roots.contains(value.as_slice()) { + child_roots.insert(value); + switch_child_key = Some(key); + false + } else { + // do not add two child trie with same root + true + } + } else if recorder.estimate_encoded_size() <= size_limit { + count += 1; + true + } else { + false + } + }, + false, + ) + .map_err(|e| Box::new(e) as Box)?; + + if switch_child_key.is_none() { + if depth == 1 { + break + } else if completed { + start_at = child_key.take(); + } else { + break + } + } else { + child_key = switch_child_key; + start_at = None; + } + } + + let proof = proving_backend + .extract_proof() + .expect("A recorder was set and thus, a storage proof can be extracted; qed"); + Ok((proof, count)) + } + /// Generate range storage read proof. pub fn prove_range_read_with_size( backend: B, @@ -742,13 +914,11 @@ mod execution { start_at: Option<&[u8]>, ) -> Result<(StorageProof, u32), Box> where - B: Backend, + B: AsTrieBackend, H: Hasher, H::Out: Ord + Codec, { - let trie_backend = backend - .as_trie_backend() - .ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box)?; + let trie_backend = backend.as_trie_backend(); prove_range_read_with_size_on_trie_backend( trie_backend, child_info, @@ -771,7 +941,9 @@ mod execution { H: Hasher, H::Out: Ord + Codec, { - let proving_backend = proving_backend::ProvingBackend::::new(trie_backend); + let recorder = sp_trie::recorder::Recorder::default(); + let proving_backend = + TrieBackendBuilder::wrap(trie_backend).with_recorder(recorder.clone()).build(); let mut count = 0; proving_backend .apply_to_key_values_while( @@ -779,7 +951,7 @@ mod execution { prefix, start_at, |_key, _value| { - if count == 0 || proving_backend.estimate_encoded_size() <= size_limit { + if count == 0 || recorder.estimate_encoded_size() <= size_limit { count += 1; true } else { @@ -789,7 +961,11 @@ mod execution { false, ) .map_err(|e| Box::new(e) as Box)?; - Ok((proving_backend.extract_proof(), count)) + + let proof = proving_backend + .extract_proof() + .expect("A recorder was set and thus, a storage proof can be extracted; qed"); + Ok((proof, count)) } /// Generate child storage read proof. @@ -799,15 +975,13 @@ mod execution { keys: I, ) -> Result> where - B: Backend, + B: AsTrieBackend, H: Hasher, H::Out: Ord + Codec, I: IntoIterator, I::Item: AsRef<[u8]>, { - let trie_backend = backend - .as_trie_backend() - .ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box)?; + let trie_backend = backend.as_trie_backend(); prove_child_read_on_trie_backend(trie_backend, child_info, keys) } @@ -823,13 +997,17 @@ mod execution { I: IntoIterator, I::Item: AsRef<[u8]>, { - let proving_backend = proving_backend::ProvingBackend::<_, H>::new(trie_backend); + let proving_backend = + TrieBackendBuilder::wrap(trie_backend).with_recorder(Default::default()).build(); for key in keys.into_iter() { proving_backend .storage(key.as_ref()) .map_err(|e| Box::new(e) as Box)?; } - Ok(proving_backend.extract_proof()) + + Ok(proving_backend + .extract_proof() + .expect("A recorder was set and thus, a storage proof can be extracted; qed")) } /// Generate storage read proof on pre-created trie backend. @@ -845,13 +1023,17 @@ mod execution { I: IntoIterator, I::Item: AsRef<[u8]>, { - let proving_backend = proving_backend::ProvingBackend::<_, H>::new(trie_backend); + let proving_backend = + TrieBackendBuilder::wrap(trie_backend).with_recorder(Default::default()).build(); for key in keys.into_iter() { proving_backend .child_storage(child_info, key.as_ref()) .map_err(|e| Box::new(e) as Box)?; } - Ok(proving_backend.extract_proof()) + + Ok(proving_backend + .extract_proof() + .expect("A recorder was set and thus, a storage proof can be extracted; qed")) } /// Check storage read proof, generated by `prove_read` call. @@ -861,7 +1043,7 @@ mod execution { keys: I, ) -> Result, Option>>, Box> where - H: Hasher, + H: Hasher + 'static, H::Out: Ord + Codec, I: IntoIterator, I::Item: AsRef<[u8]>, @@ -875,7 +1057,25 @@ mod execution { Ok(result) } - /// Check child storage range proof, generated by `prove_range_read` call. + /// Check storage range proof with child trie included, generated by + /// `prove_range_read_with_child_with_size` call. + /// + /// Returns key values contents and the depth of the pending state iteration + /// (0 if completed). + pub fn read_range_proof_check_with_child( + root: H::Out, + proof: StorageProof, + start_at: &[Vec], + ) -> Result<(KeyValueStates, usize), Box> + where + H: Hasher + 'static, + H::Out: Ord + Codec, + { + let proving_backend = create_proof_check_backend::(root, proof)?; + read_range_proof_check_with_child_on_proving_backend(&proving_backend, start_at) + } + + /// Check child storage range proof, generated by `prove_range_read_with_size` call. pub fn read_range_proof_check( root: H::Out, proof: StorageProof, @@ -885,7 +1085,7 @@ mod execution { start_at: Option<&[u8]>, ) -> Result<(Vec<(Vec, Vec)>, bool), Box> where - H: Hasher, + H: Hasher + 'static, H::Out: Ord + Codec, { let proving_backend = create_proof_check_backend::(root, proof)?; @@ -906,7 +1106,7 @@ mod execution { keys: I, ) -> Result, Option>>, Box> where - H: Hasher, + H: Hasher + 'static, H::Out: Ord + Codec, I: IntoIterator, I::Item: AsRef<[u8]>, @@ -982,30 +1182,150 @@ mod execution { Err(e) => Err(Box::new(e) as Box), } } + + /// Check storage range proof on pre-created proving backend. + /// + /// See `read_range_proof_check_with_child`. + pub fn read_range_proof_check_with_child_on_proving_backend( + proving_backend: &TrieBackend, H>, + start_at: &[Vec], + ) -> Result<(KeyValueStates, usize), Box> + where + H: Hasher, + H::Out: Ord + Codec, + { + let mut result = vec![KeyValueStorageLevel { + state_root: Default::default(), + key_values: Default::default(), + parent_storage_keys: Default::default(), + }]; + if start_at.len() > MAX_NESTED_TRIE_DEPTH { + return Err(Box::new("Invalid start of range.")) + } + + let mut child_roots = HashSet::new(); + let (mut child_key, mut start_at) = if start_at.len() == 2 { + let storage_key = start_at.get(0).expect("Checked length.").clone(); + let child_key = if let Some(state_root) = proving_backend + .storage(&storage_key) + .map_err(|e| Box::new(e) as Box)? + { + child_roots.insert(state_root.clone()); + Some((storage_key, state_root)) + } else { + return Err(Box::new("Invalid range start child trie key.")) + }; + + (child_key, start_at.get(1).cloned()) + } else { + (None, start_at.get(0).cloned()) + }; + + let completed = loop { + let (child_info, depth) = if let Some((storage_key, state_root)) = child_key.as_ref() { + result.push(KeyValueStorageLevel { + state_root: state_root.clone(), + key_values: Default::default(), + parent_storage_keys: Default::default(), + }); + + let storage_key = PrefixedStorageKey::new_ref(storage_key); + ( + Some(match ChildType::from_prefixed_key(storage_key) { + Some((ChildType::ParentKeyId, storage_key)) => + ChildInfo::new_default(storage_key), + None => return Err(Box::new("Invalid range start child trie key.")), + }), + 2, + ) + } else { + (None, 1) + }; + + let values = if child_info.is_some() { + &mut result.last_mut().expect("Added above").key_values + } else { + &mut result[0].key_values + }; + let start_at_ref = start_at.as_ref().map(AsRef::as_ref); + let mut switch_child_key = None; + let mut first = start_at.is_some(); + let completed = proving_backend + .apply_to_key_values_while( + child_info.as_ref(), + None, + start_at_ref, + |key, value| { + if first && + start_at_ref + .as_ref() + .map(|start| &key.as_slice() > start) + .unwrap_or(true) + { + first = false; + } + + if !first { + values.push((key.to_vec(), value.to_vec())); + } + if first { + true + } else if depth < MAX_NESTED_TRIE_DEPTH && + sp_core::storage::well_known_keys::is_child_storage_key( + key.as_slice(), + ) { + if child_roots.contains(value.as_slice()) { + // Do not add two chid trie with same root. + true + } else { + child_roots.insert(value.clone()); + switch_child_key = Some((key, value)); + false + } + } else { + true + } + }, + true, + ) + .map_err(|e| Box::new(e) as Box)?; + + if switch_child_key.is_none() { + if !completed { + break depth + } + if depth == 1 { + break 0 + } else { + start_at = child_key.take().map(|entry| entry.0); + } + } else { + child_key = switch_child_key; + start_at = None; + } + }; + Ok((KeyValueStates(result), completed)) + } } #[cfg(test)] mod tests { - use super::{changes_trie::Configuration as ChangesTrieConfig, ext::Ext, *}; - use crate::execution::CallResult; - use codec::{Decode, Encode}; + use super::{backend::AsTrieBackend, ext::Ext, *}; + use crate::{execution::CallResult, in_memory_backend::new_in_mem_hash_key}; + use assert_matches::assert_matches; + use codec::Encode; use sp_core::{ map, - storage::ChildInfo, + storage::{ChildInfo, StateVersion}, testing::TaskExecutor, traits::{CodeExecutor, Externalities, RuntimeCode}, - NativeOrEncoded, NeverNativeValue, }; use sp_runtime::traits::BlakeTwo256; - use std::{ - collections::{BTreeMap, HashMap}, - panic::UnwindSafe, - result, - }; + use sp_trie::trie_types::{TrieDBMutBuilderV0, TrieDBMutBuilderV1}; + use std::collections::{BTreeMap, HashMap}; #[derive(Clone)] struct DummyCodeExecutor { - change_changes_trie_config: bool, native_available: bool, native_succeeds: bool, fallback_succeeds: bool, @@ -1014,35 +1334,20 @@ mod tests { impl CodeExecutor for DummyCodeExecutor { type Error = u8; - fn call< - R: Encode + Decode + PartialEq, - NC: FnOnce() -> result::Result> + UnwindSafe, - >( + fn call( &self, ext: &mut dyn Externalities, _: &RuntimeCode, _method: &str, _data: &[u8], use_native: bool, - native_call: Option, - ) -> (CallResult, bool) { - if self.change_changes_trie_config { - ext.place_storage( - sp_core::storage::well_known_keys::CHANGES_TRIE_CONFIG.to_vec(), - Some(ChangesTrieConfig { digest_interval: 777, digest_levels: 333 }.encode()), - ); - } - + ) -> (CallResult, bool) { let using_native = use_native && self.native_available; - match (using_native, self.native_succeeds, self.fallback_succeeds, native_call) { - (true, true, _, Some(call)) => { - let res = sp_externalities::set_and_run_with_externalities(ext, || call()); - (res.map(NativeOrEncoded::Native).map_err(|_| 0), true) - }, - (true, true, _, None) | (false, _, true, None) => ( - Ok(NativeOrEncoded::Encoded(vec![ + match (using_native, self.native_succeeds, self.fallback_succeeds) { + (true, true, _) | (false, _, true) => ( + Ok(vec![ ext.storage(b"value1").unwrap()[0] + ext.storage(b"value2").unwrap()[0], - ])), + ]), using_native, ), _ => (Err(0), using_native), @@ -1062,16 +1367,18 @@ mod tests { #[test] fn execute_works() { - let backend = trie_backend::tests::test_trie(); + execute_works_inner(StateVersion::V0); + execute_works_inner(StateVersion::V1); + } + fn execute_works_inner(state_version: StateVersion) { + let backend = trie_backend::tests::test_trie(state_version, None, None); let mut overlayed_changes = Default::default(); let wasm_code = RuntimeCode::empty(); let mut state_machine = StateMachine::new( &backend, - changes_trie::disabled_state::<_, u64>(), &mut overlayed_changes, &DummyCodeExecutor { - change_changes_trie_config: false, native_available: true, native_succeeds: true, fallback_succeeds: true, @@ -1088,16 +1395,18 @@ mod tests { #[test] fn execute_works_with_native_else_wasm() { - let backend = trie_backend::tests::test_trie(); + execute_works_with_native_else_wasm_inner(StateVersion::V0); + execute_works_with_native_else_wasm_inner(StateVersion::V1); + } + fn execute_works_with_native_else_wasm_inner(state_version: StateVersion) { + let backend = trie_backend::tests::test_trie(state_version, None, None); let mut overlayed_changes = Default::default(); let wasm_code = RuntimeCode::empty(); let mut state_machine = StateMachine::new( &backend, - changes_trie::disabled_state::<_, u64>(), &mut overlayed_changes, &DummyCodeExecutor { - change_changes_trie_config: false, native_available: true, native_succeeds: true, fallback_succeeds: true, @@ -1114,17 +1423,19 @@ mod tests { #[test] fn dual_execution_strategy_detects_consensus_failure() { + dual_execution_strategy_detects_consensus_failure_inner(StateVersion::V0); + dual_execution_strategy_detects_consensus_failure_inner(StateVersion::V1); + } + fn dual_execution_strategy_detects_consensus_failure_inner(state_version: StateVersion) { let mut consensus_failed = false; - let backend = trie_backend::tests::test_trie(); + let backend = trie_backend::tests::test_trie(state_version, None, None); let mut overlayed_changes = Default::default(); let wasm_code = RuntimeCode::empty(); let mut state_machine = StateMachine::new( &backend, - changes_trie::disabled_state::<_, u64>(), &mut overlayed_changes, &DummyCodeExecutor { - change_changes_trie_config: false, native_available: true, native_succeeds: true, fallback_succeeds: false, @@ -1137,30 +1448,30 @@ mod tests { ); assert!(state_machine - .execute_using_consensus_failure_handler::<_, NeverNativeValue, fn() -> _>( - ExecutionManager::Both(|we, _ne| { - consensus_failed = true; - we - }), - None, - ) + .execute_using_consensus_failure_handler(ExecutionManager::Both(|we, _ne| { + consensus_failed = true; + we + }),) .is_err()); assert!(consensus_failed); } #[test] fn prove_execution_and_proof_check_works() { + prove_execution_and_proof_check_works_inner(StateVersion::V0); + prove_execution_and_proof_check_works_inner(StateVersion::V1); + } + fn prove_execution_and_proof_check_works_inner(state_version: StateVersion) { let executor = DummyCodeExecutor { - change_changes_trie_config: false, native_available: true, native_succeeds: true, fallback_succeeds: true, }; // fetch execution proof from 'remote' full node - let mut remote_backend = trie_backend::tests::test_trie(); - let remote_root = remote_backend.storage_root(std::iter::empty()).0; - let (remote_result, remote_proof) = prove_execution::<_, _, u64, _, _>( + let mut remote_backend = trie_backend::tests::test_trie(state_version, None, None); + let remote_root = remote_backend.storage_root(std::iter::empty(), state_version).0; + let (remote_result, remote_proof) = prove_execution( &mut remote_backend, &mut Default::default(), &executor, @@ -1172,7 +1483,7 @@ mod tests { .unwrap(); // check proof locally - let local_result = execution_proof_check::( + let local_result = execution_proof_check::( remote_root, remote_proof, &mut Default::default(), @@ -1197,8 +1508,8 @@ mod tests { b"abc".to_vec() => b"2".to_vec(), b"bbb".to_vec() => b"3".to_vec() ]; - let state = InMemoryBackend::::from(initial); - let backend = state.as_trie_backend().unwrap(); + let state = InMemoryBackend::::from((initial, StateVersion::default())); + let backend = state.as_trie_backend(); let mut overlay = OverlayedChanges::default(); overlay.set_storage(b"aba".to_vec(), Some(b"1312".to_vec())); @@ -1210,14 +1521,8 @@ mod tests { let overlay_limit = overlay.clone(); { let mut cache = StorageTransactionCache::default(); - let mut ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); - ext.clear_prefix(b"ab", None); + let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); + let _ = ext.clear_prefix(b"ab", None, None); } overlay.commit_transaction().unwrap(); @@ -1227,27 +1532,24 @@ mod tests { .map(|(k, v)| (k.clone(), v.value().cloned())) .collect::>(), map![ - b"abc".to_vec() => None.into(), - b"abb".to_vec() => None.into(), - b"aba".to_vec() => None.into(), - b"abd".to_vec() => None.into(), + b"abc".to_vec() => None, + b"abb".to_vec() => None, + b"aba".to_vec() => None, + b"abd".to_vec() => None, - b"bab".to_vec() => Some(b"228".to_vec()).into(), - b"bbd".to_vec() => Some(b"42".to_vec()).into() + b"bab".to_vec() => Some(b"228".to_vec()), + b"bbd".to_vec() => Some(b"42".to_vec()) ], ); let mut overlay = overlay_limit; { let mut cache = StorageTransactionCache::default(); - let mut ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, + let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); + assert_matches!( + ext.clear_prefix(b"ab", Some(1), None).deconstruct(), + (Some(_), 1, 3, 1) ); - assert_eq!((false, 1), ext.clear_prefix(b"ab", Some(1))); } overlay.commit_transaction().unwrap(); @@ -1257,12 +1559,12 @@ mod tests { .map(|(k, v)| (k.clone(), v.value().cloned())) .collect::>(), map![ - b"abb".to_vec() => None.into(), - b"aba".to_vec() => None.into(), - b"abd".to_vec() => None.into(), + b"abb".to_vec() => None, + b"aba".to_vec() => None, + b"abd".to_vec() => None, - b"bab".to_vec() => Some(b"228".to_vec()).into(), - b"bbd".to_vec() => Some(b"42".to_vec()).into() + b"bab".to_vec() => Some(b"228".to_vec()), + b"bbd".to_vec() => Some(b"42".to_vec()) ], ); } @@ -1278,7 +1580,7 @@ mod tests { b"d".to_vec() => b"3".to_vec() ], ]; - let backend = InMemoryBackend::::from(initial); + let backend = InMemoryBackend::::from((initial, StateVersion::default())); let mut overlay = OverlayedChanges::default(); overlay.set_child_storage(&child_info, b"1".to_vec(), Some(b"1312".to_vec())); @@ -1288,29 +1590,24 @@ mod tests { { let mut cache = StorageTransactionCache::default(); - let mut ext = Ext::new( - &mut overlay, - &mut cache, - &backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); - assert_eq!(ext.kill_child_storage(&child_info, Some(2)), (false, 2)); + let mut ext = Ext::new(&mut overlay, &mut cache, &backend, None); + let r = ext.kill_child_storage(&child_info, Some(2), None); + assert_matches!(r.deconstruct(), (Some(_), 2, 6, 2)); } assert_eq!( overlay .children() .flat_map(|(iter, _child_info)| iter) - .map(|(k, v)| (k.clone(), v.value().clone())) + .map(|(k, v)| (k.clone(), v.value())) .collect::>(), map![ - b"1".to_vec() => None.into(), - b"2".to_vec() => None.into(), - b"3".to_vec() => None.into(), - b"4".to_vec() => None.into(), - b"a".to_vec() => None.into(), - b"b".to_vec() => None.into(), + b"1".to_vec() => None, + b"2".to_vec() => None, + b"3".to_vec() => None, + b"4".to_vec() => None, + b"a".to_vec() => None, + b"b".to_vec() => None, ], ); } @@ -1326,45 +1623,56 @@ mod tests { b"d".to_vec() => b"3".to_vec() ], ]; - let backend = InMemoryBackend::::from(initial); + let backend = InMemoryBackend::::from((initial, StateVersion::default())); let mut overlay = OverlayedChanges::default(); let mut cache = StorageTransactionCache::default(); - let mut ext = Ext::new( - &mut overlay, - &mut cache, - &backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); - assert_eq!(ext.kill_child_storage(&child_info, Some(0)), (false, 0)); - assert_eq!(ext.kill_child_storage(&child_info, Some(1)), (false, 1)); - assert_eq!(ext.kill_child_storage(&child_info, Some(2)), (false, 2)); - assert_eq!(ext.kill_child_storage(&child_info, Some(3)), (false, 3)); - assert_eq!(ext.kill_child_storage(&child_info, Some(4)), (true, 4)); - // Only 4 items to remove - assert_eq!(ext.kill_child_storage(&child_info, Some(5)), (true, 4)); - assert_eq!(ext.kill_child_storage(&child_info, None), (true, 4)); + let mut ext = Ext::new(&mut overlay, &mut cache, &backend, None); + let r = ext.kill_child_storage(&child_info, Some(0), None).deconstruct(); + assert_matches!(r, (Some(_), 0, 0, 0)); + let r = ext + .kill_child_storage(&child_info, Some(1), r.0.as_ref().map(|x| &x[..])) + .deconstruct(); + assert_matches!(r, (Some(_), 1, 1, 1)); + let r = ext + .kill_child_storage(&child_info, Some(4), r.0.as_ref().map(|x| &x[..])) + .deconstruct(); + // Only 3 items remaining to remove + assert_matches!(r, (None, 3, 3, 3)); + let r = ext.kill_child_storage(&child_info, Some(1), None).deconstruct(); + assert_matches!(r, (Some(_), 0, 0, 1)); + } + + #[test] + fn limited_child_kill_off_by_one_works_without_limit() { + let child_info = ChildInfo::new_default(b"sub1"); + let initial: HashMap<_, BTreeMap<_, _>> = map![ + Some(child_info.clone()) => map![ + b"a".to_vec() => b"0".to_vec(), + b"b".to_vec() => b"1".to_vec(), + b"c".to_vec() => b"2".to_vec(), + b"d".to_vec() => b"3".to_vec() + ], + ]; + let backend = InMemoryBackend::::from((initial, StateVersion::default())); + let mut overlay = OverlayedChanges::default(); + let mut cache = StorageTransactionCache::default(); + let mut ext = Ext::new(&mut overlay, &mut cache, &backend, None); + assert_eq!(ext.kill_child_storage(&child_info, None, None).deconstruct(), (None, 4, 4, 4)); } #[test] fn set_child_storage_works() { let child_info = ChildInfo::new_default(b"sub1"); let child_info = &child_info; - let state = new_in_mem::(); - let backend = state.as_trie_backend().unwrap(); + let state = new_in_mem_hash_key::(); + let backend = state.as_trie_backend(); let mut overlay = OverlayedChanges::default(); let mut cache = StorageTransactionCache::default(); - let mut ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); + let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); ext.set_child_storage(child_info, b"abc".to_vec(), b"def".to_vec()); assert_eq!(ext.child_storage(child_info, b"abc"), Some(b"def".to_vec())); - ext.kill_child_storage(child_info, None); + let _ = ext.kill_child_storage(child_info, None, None); assert_eq!(ext.child_storage(child_info, b"abc"), None); } @@ -1372,31 +1680,19 @@ mod tests { fn append_storage_works() { let reference_data = vec![b"data1".to_vec(), b"2".to_vec(), b"D3".to_vec(), b"d4".to_vec()]; let key = b"key".to_vec(); - let state = new_in_mem::(); - let backend = state.as_trie_backend().unwrap(); + let state = new_in_mem_hash_key::(); + let backend = state.as_trie_backend(); let mut overlay = OverlayedChanges::default(); let mut cache = StorageTransactionCache::default(); { - let mut ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); + let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); ext.storage_append(key.clone(), reference_data[0].encode()); assert_eq!(ext.storage(key.as_slice()), Some(vec![reference_data[0].clone()].encode())); } overlay.start_transaction(); { - let mut ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); + let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); for i in reference_data.iter().skip(1) { ext.storage_append(key.clone(), i.encode()); @@ -1405,13 +1701,7 @@ mod tests { } overlay.rollback_transaction().unwrap(); { - let ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); + let ext = Ext::new(&mut overlay, &mut cache, backend, None); assert_eq!(ext.storage(key.as_slice()), Some(vec![reference_data[0].clone()].encode())); } } @@ -1427,19 +1717,13 @@ mod tests { let key = b"events".to_vec(); let mut cache = StorageTransactionCache::default(); - let state = new_in_mem::(); - let backend = state.as_trie_backend().unwrap(); + let state = new_in_mem_hash_key::(); + let backend = state.as_trie_backend(); let mut overlay = OverlayedChanges::default(); // For example, block initialization with event. { - let mut ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); + let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); ext.clear_storage(key.as_slice()); ext.storage_append(key.clone(), Item::InitializationItem.encode()); } @@ -1447,13 +1731,7 @@ mod tests { // For example, first transaction resulted in panic during block building { - let mut ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); + let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); assert_eq!(ext.storage(key.as_slice()), Some(vec![Item::InitializationItem].encode())); @@ -1468,13 +1746,7 @@ mod tests { // Then we apply next transaction which is valid this time. { - let mut ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); + let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); assert_eq!(ext.storage(key.as_slice()), Some(vec![Item::InitializationItem].encode())); @@ -1487,15 +1759,9 @@ mod tests { } overlay.start_transaction(); - // Then only initlaization item and second (commited) item should persist. + // Then only initlaization item and second (committed) item should persist. { - let ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); + let ext = Ext::new(&mut overlay, &mut cache, backend, None); assert_eq!( ext.storage(key.as_slice()), Some(vec![Item::InitializationItem, Item::CommitedItem].encode()), @@ -1505,7 +1771,7 @@ mod tests { fn test_compact(remote_proof: StorageProof, remote_root: &sp_core::H256) -> StorageProof { let compact_remote_proof = - remote_proof.into_compact_proof::(remote_root.clone()).unwrap(); + remote_proof.into_compact_proof::(*remote_root).unwrap(); compact_remote_proof .to_storage_proof::(Some(remote_root)) .unwrap() @@ -1514,13 +1780,17 @@ mod tests { #[test] fn prove_read_and_proof_check_works() { + prove_read_and_proof_check_works_inner(StateVersion::V0); + prove_read_and_proof_check_works_inner(StateVersion::V1); + } + fn prove_read_and_proof_check_works_inner(state_version: StateVersion) { let child_info = ChildInfo::new_default(b"sub1"); let missing_child_info = ChildInfo::new_default(b"sub1sub2"); // key will include other child root to proof. let child_info = &child_info; let missing_child_info = &missing_child_info; // fetch read proof from 'remote' full node - let remote_backend = trie_backend::tests::test_trie(); - let remote_root = remote_backend.storage_root(std::iter::empty()).0; + let remote_backend = trie_backend::tests::test_trie(state_version, None, None); + let remote_root = remote_backend.storage_root(std::iter::empty(), state_version).0; let remote_proof = prove_read(remote_backend, &[b"value2"]).unwrap(); let remote_proof = test_compact(remote_proof, &remote_root); // check proof locally @@ -1528,8 +1798,7 @@ mod tests { read_proof_check::(remote_root, remote_proof.clone(), &[b"value2"]) .unwrap(); let local_result2 = - read_proof_check::(remote_root, remote_proof.clone(), &[&[0xff]]) - .is_ok(); + read_proof_check::(remote_root, remote_proof, &[&[0xff]]).is_ok(); // check that results are correct assert_eq!( local_result1.into_iter().collect::>(), @@ -1537,8 +1806,8 @@ mod tests { ); assert_eq!(local_result2, false); // on child trie - let remote_backend = trie_backend::tests::test_trie(); - let remote_root = remote_backend.storage_root(std::iter::empty()).0; + let remote_backend = trie_backend::tests::test_trie(state_version, None, None); + let remote_root = remote_backend.storage_root(std::iter::empty(), state_version).0; let remote_proof = prove_child_read(remote_backend, child_info, &[b"value3"]).unwrap(); let remote_proof = test_compact(remote_proof, &remote_root); let local_result1 = read_child_proof_check::( @@ -1557,7 +1826,7 @@ mod tests { .unwrap(); let local_result3 = read_child_proof_check::( remote_root, - remote_proof.clone(), + remote_proof, missing_child_info, &[b"dummy"], ) @@ -1565,7 +1834,7 @@ mod tests { assert_eq!( local_result1.into_iter().collect::>(), - vec![(b"value3".to_vec(), Some(vec![142]))], + vec![(b"value3".to_vec(), Some(vec![142; 33]))], ); assert_eq!(local_result2.into_iter().collect::>(), vec![(b"value2".to_vec(), None)]); assert_eq!(local_result3.into_iter().collect::>(), vec![(b"dummy".to_vec(), None)]); @@ -1602,9 +1871,10 @@ mod tests { storage.insert(Some(child_info), items); } - let trie: InMemoryBackend = storage.clone().into(); - let trie_root = trie.root().clone(); - let backend = crate::ProvingBackend::new(&trie); + let trie: InMemoryBackend = + (storage.clone(), StateVersion::default()).into(); + let trie_root = *trie.root(); + let backend = TrieBackendBuilder::wrap(&trie).with_recorder(Default::default()).build(); let mut queries = Vec::new(); for c in 0..(5 + nb_child_trie / 2) { // random existing query @@ -1649,7 +1919,7 @@ mod tests { } } - let storage_proof = backend.extract_proof(); + let storage_proof = backend.extract_proof().expect("Failed to extract proof"); let remote_proof = test_compact(storage_proof, &trie_root); let proof_check = create_proof_check_backend::(trie_root, remote_proof).unwrap(); @@ -1665,18 +1935,19 @@ mod tests { #[test] fn prove_read_with_size_limit_works() { - let remote_backend = trie_backend::tests::test_trie(); - let remote_root = remote_backend.storage_root(::std::iter::empty()).0; + let state_version = StateVersion::V0; + let remote_backend = trie_backend::tests::test_trie(state_version, None, None); + let remote_root = remote_backend.storage_root(::std::iter::empty(), state_version).0; let (proof, count) = prove_range_read_with_size(remote_backend, None, None, 0, None).unwrap(); - // Alwasys contains at least some nodes. - assert_eq!(proof.into_memory_db::().drain().len(), 3); + // Always contains at least some nodes. + assert_eq!(proof.to_memory_db::().drain().len(), 3); assert_eq!(count, 1); - let remote_backend = trie_backend::tests::test_trie(); + let remote_backend = trie_backend::tests::test_trie(state_version, None, None); let (proof, count) = prove_range_read_with_size(remote_backend, None, None, 800, Some(&[])).unwrap(); - assert_eq!(proof.clone().into_memory_db::().drain().len(), 9); + assert_eq!(proof.to_memory_db::().drain().len(), 9); assert_eq!(count, 85); let (results, completed) = read_range_proof_check::( remote_root, @@ -1696,40 +1967,143 @@ mod tests { assert_eq!(results.len() as u32, 101); assert_eq!(completed, false); - let remote_backend = trie_backend::tests::test_trie(); + let remote_backend = trie_backend::tests::test_trie(state_version, None, None); let (proof, count) = prove_range_read_with_size(remote_backend, None, None, 50000, Some(&[])).unwrap(); - assert_eq!(proof.clone().into_memory_db::().drain().len(), 11); + assert_eq!(proof.to_memory_db::().drain().len(), 11); assert_eq!(count, 132); - let (results, completed) = read_range_proof_check::( - remote_root, - proof.clone(), - None, - None, - None, - None, - ) - .unwrap(); + let (results, completed) = + read_range_proof_check::(remote_root, proof, None, None, None, None) + .unwrap(); assert_eq!(results.len() as u32, count); assert_eq!(completed, true); } + #[test] + fn inner_state_versioning_switch_proofs() { + let mut state_version = StateVersion::V0; + let (mut mdb, mut root) = trie_backend::tests::test_db(state_version); + { + let mut trie = TrieDBMutBuilderV0::from_existing(&mut mdb, &mut root).build(); + trie.insert(b"foo", vec![1u8; 1_000].as_slice()) // big inner hash + .expect("insert failed"); + trie.insert(b"foo2", vec![3u8; 16].as_slice()) // no inner hash + .expect("insert failed"); + trie.insert(b"foo222", vec![5u8; 100].as_slice()) // inner hash + .expect("insert failed"); + } + + let check_proof = |mdb, root, state_version| -> StorageProof { + let remote_backend = TrieBackendBuilder::new(mdb, root).build(); + let remote_root = remote_backend.storage_root(std::iter::empty(), state_version).0; + let remote_proof = prove_read(remote_backend, &[b"foo222"]).unwrap(); + // check proof locally + let local_result1 = + read_proof_check::(remote_root, remote_proof.clone(), &[b"foo222"]) + .unwrap(); + // check that results are correct + assert_eq!( + local_result1.into_iter().collect::>(), + vec![(b"foo222".to_vec(), Some(vec![5u8; 100]))], + ); + remote_proof + }; + + let remote_proof = check_proof(mdb.clone(), root, state_version); + // check full values in proof + assert!(remote_proof.encode().len() > 1_100); + assert!(remote_proof.encoded_size() > 1_100); + let root1 = root; + + // do switch + state_version = StateVersion::V1; + { + let mut trie = TrieDBMutBuilderV1::from_existing(&mut mdb, &mut root).build(); + trie.insert(b"foo222", vec![5u8; 100].as_slice()) // inner hash + .expect("insert failed"); + // update with same value do change + trie.insert(b"foo", vec![1u8; 1000].as_slice()) // inner hash + .expect("insert failed"); + } + let root3 = root; + assert!(root1 != root3); + let remote_proof = check_proof(mdb.clone(), root, state_version); + // nodes foo is replaced by its hashed value form. + assert!(remote_proof.encode().len() < 1000); + assert!(remote_proof.encoded_size() < 1000); + assert_eq!(remote_proof.encode().len(), remote_proof.encoded_size()); + } + + #[test] + fn prove_range_with_child_works() { + let state_version = StateVersion::V0; + let remote_backend = trie_backend::tests::test_trie(state_version, None, None); + let remote_root = remote_backend.storage_root(std::iter::empty(), state_version).0; + let mut start_at = smallvec::SmallVec::<[Vec; 2]>::new(); + let trie_backend = remote_backend.as_trie_backend(); + let max_iter = 1000; + let mut nb_loop = 0; + loop { + nb_loop += 1; + if max_iter == nb_loop { + panic!("Too many loop in prove range"); + } + let (proof, count) = prove_range_read_with_child_with_size_on_trie_backend( + trie_backend, + 1, + start_at.as_slice(), + ) + .unwrap(); + // Always contains at least some nodes. + assert!(proof.to_memory_db::().drain().len() > 0); + assert!(count < 3); // when doing child we include parent and first child key. + + let (result, completed_depth) = read_range_proof_check_with_child::( + remote_root, + proof.clone(), + start_at.as_slice(), + ) + .unwrap(); + + if completed_depth == 0 { + break + } + assert!(result.update_last_key(completed_depth, &mut start_at)); + } + + assert_eq!(nb_loop, 10); + } + #[test] fn compact_multiple_child_trie() { + let size_no_inner_hash = compact_multiple_child_trie_inner(StateVersion::V0); + let size_inner_hash = compact_multiple_child_trie_inner(StateVersion::V1); + assert!(size_inner_hash < size_no_inner_hash); + } + fn compact_multiple_child_trie_inner(state_version: StateVersion) -> usize { // this root will be queried let child_info1 = ChildInfo::new_default(b"sub1"); // this root will not be include in proof let child_info2 = ChildInfo::new_default(b"sub2"); // this root will be include in proof let child_info3 = ChildInfo::new_default(b"sub"); - let remote_backend = trie_backend::tests::test_trie(); + let remote_backend = trie_backend::tests::test_trie(state_version, None, None); + let long_vec: Vec = (0..1024usize).map(|_| 8u8).collect(); let (remote_root, transaction) = remote_backend.full_storage_root( std::iter::empty(), vec![ ( &child_info1, - vec![(&b"key1"[..], Some(&b"val2"[..])), (&b"key2"[..], Some(&b"val3"[..]))] - .into_iter(), + vec![ + // a inner hashable node + (&b"k"[..], Some(&long_vec[..])), + // need to ensure this is not an inline node + // otherwhise we do not know what is accessed when + // storing proof. + (&b"key1"[..], Some(&vec![5u8; 32][..])), + (&b"key2"[..], Some(&b"val3"[..])), + ] + .into_iter(), ), ( &child_info2, @@ -1743,25 +2117,29 @@ mod tests { ), ] .into_iter(), + state_version, ); - let mut remote_storage = remote_backend.into_storage(); + let mut remote_storage = remote_backend.backend_storage().clone(); remote_storage.consolidate(transaction); - let remote_backend = TrieBackend::new(remote_storage, remote_root); + let remote_backend = TrieBackendBuilder::new(remote_storage, remote_root).build(); let remote_proof = prove_child_read(remote_backend, &child_info1, &[b"key1"]).unwrap(); + let size = remote_proof.encoded_size(); let remote_proof = test_compact(remote_proof, &remote_root); let local_result1 = read_child_proof_check::( remote_root, - remote_proof.clone(), + remote_proof, &child_info1, &[b"key1"], ) .unwrap(); assert_eq!(local_result1.len(), 1); - assert_eq!(local_result1.get(&b"key1"[..]), Some(&Some(b"val2".to_vec()))); + assert_eq!(local_result1.get(&b"key1"[..]), Some(&Some(vec![5u8; 32]))); + size } #[test] fn child_storage_uuid() { + let state_version = StateVersion::V0; let child_info_1 = ChildInfo::new_default(b"sub_test1"); let child_info_2 = ChildInfo::new_default(b"sub_test2"); @@ -1769,18 +2147,12 @@ mod tests { let mut overlay = OverlayedChanges::default(); let mut transaction = { - let backend = test_trie(); + let backend = test_trie(state_version, None, None); let mut cache = StorageTransactionCache::default(); - let mut ext = Ext::new( - &mut overlay, - &mut cache, - &backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); + let mut ext = Ext::new(&mut overlay, &mut cache, &backend, None); ext.set_child_storage(&child_info_1, b"abc".to_vec(), b"def".to_vec()); ext.set_child_storage(&child_info_2, b"abc".to_vec(), b"def".to_vec()); - ext.storage_root(); + ext.storage_root(state_version); cache.transaction.unwrap() }; let mut duplicate = false; @@ -1800,8 +2172,8 @@ mod tests { b"aaa".to_vec() => b"0".to_vec(), b"bbb".to_vec() => b"".to_vec() ]; - let state = InMemoryBackend::::from(initial); - let backend = state.as_trie_backend().unwrap(); + let state = InMemoryBackend::::from((initial, StateVersion::default())); + let backend = state.as_trie_backend(); let mut overlay = OverlayedChanges::default(); overlay.start_transaction(); @@ -1814,13 +2186,7 @@ mod tests { { let mut cache = StorageTransactionCache::default(); - let mut ext = Ext::new( - &mut overlay, - &mut cache, - backend, - changes_trie::disabled_state::<_, u64>(), - None, - ); + let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); assert_eq!(ext.storage(b"bbb"), Some(vec![])); assert_eq!(ext.storage(b"ccc"), Some(vec![])); ext.clear_storage(b"ccc"); @@ -1829,52 +2195,4 @@ mod tests { overlay.commit_transaction().unwrap(); assert_eq!(overlay.storage(b"ccc"), Some(None)); } - - #[test] - fn runtime_registered_extensions_are_removed_after_execution() { - use sp_externalities::ExternalitiesExt; - sp_externalities::decl_extension! { - struct DummyExt(u32); - } - - let backend = trie_backend::tests::test_trie(); - let mut overlayed_changes = Default::default(); - let wasm_code = RuntimeCode::empty(); - - let mut state_machine = StateMachine::new( - &backend, - changes_trie::disabled_state::<_, u64>(), - &mut overlayed_changes, - &DummyCodeExecutor { - change_changes_trie_config: false, - native_available: true, - native_succeeds: true, - fallback_succeeds: false, - }, - "test", - &[], - Default::default(), - &wasm_code, - TaskExecutor::new(), - ); - - let run_state_machine = |state_machine: &mut StateMachine<_, _, _, _>| { - state_machine - .execute_using_consensus_failure_handler:: _, _, _>( - ExecutionManager::NativeWhenPossible, - Some(|| { - sp_externalities::with_externalities(|mut ext| { - ext.register_extension(DummyExt(2)).unwrap(); - }) - .unwrap(); - - Ok(()) - }), - ) - .unwrap(); - }; - - run_state_machine(&mut state_machine); - run_state_machine(&mut state_machine); - } } diff --git a/primitives/state-machine/src/overlayed_changes/changeset.rs b/primitives/state-machine/src/overlayed_changes/changeset.rs index 1ffd569e2828b..e5dad7157c731 100644 --- a/primitives/state-machine/src/overlayed_changes/changeset.rs +++ b/primitives/state-machine/src/overlayed_changes/changeset.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,7 +57,7 @@ pub struct NotInRuntime; /// Describes in which mode the node is currently executing. #[derive(Debug, Clone, Copy)] pub enum ExecutionMode { - /// Exeuting in client mode: Removal of all transactions possible. + /// Executing in client mode: Removal of all transactions possible. Client, /// Executing in runtime mode: Transactions started by the client are protected. Runtime, @@ -69,7 +69,6 @@ struct InnerValue { /// Current value. None if value has been deleted. value: V, /// The set of extrinsic indices where the values has been changed. - /// Is filled only if runtime has announced changes trie support. extrinsics: Extrinsics, } @@ -96,7 +95,7 @@ pub type OverlayedChangeSet = OverlayedMap>; /// Holds a set of changes with the ability modify them using nested transactions. #[derive(Debug, Clone)] -pub struct OverlayedMap { +pub struct OverlayedMap { /// Stores the changes that this overlay constitutes. changes: BTreeMap>, /// Stores which keys are dirty per transaction. Needed in order to determine which @@ -111,7 +110,7 @@ pub struct OverlayedMap { execution_mode: ExecutionMode, } -impl Default for OverlayedMap { +impl Default for OverlayedMap { fn default() -> Self { Self { changes: BTreeMap::new(), @@ -122,6 +121,31 @@ impl Default for OverlayedMap { } } +#[cfg(feature = "std")] +impl From for OverlayedMap> { + fn from(storage: sp_core::storage::StorageMap) -> Self { + Self { + changes: storage + .into_iter() + .map(|(k, v)| { + ( + k, + OverlayedEntry { + transactions: SmallVec::from_iter([InnerValue { + value: Some(v), + extrinsics: Default::default(), + }]), + }, + ) + }) + .collect(), + dirty_keys: Default::default(), + num_client_transactions: 0, + execution_mode: ExecutionMode::Client, + } + } +} + impl Default for ExecutionMode { fn default() -> Self { Self::Client @@ -411,10 +435,15 @@ impl OverlayedChangeSet { &mut self, predicate: impl Fn(&[u8], &OverlayedValue) -> bool, at_extrinsic: Option, - ) { + ) -> u32 { + let mut count = 0; for (key, val) in self.changes.iter_mut().filter(|(k, v)| predicate(k, v)) { + if val.value_ref().is_some() { + count += 1; + } val.set(None, insert_dirty(&mut self.dirty_keys, key.clone()), at_extrinsic); } + count } /// Get the iterator over all changes that follow the supplied `key`. diff --git a/primitives/state-machine/src/overlayed_changes/mod.rs b/primitives/state-machine/src/overlayed_changes/mod.rs index a0558e06a380e..9eb26d52f79f8 100644 --- a/primitives/state-machine/src/overlayed_changes/mod.rs +++ b/primitives/state-machine/src/overlayed_changes/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,32 +21,26 @@ mod changeset; mod offchain; use self::changeset::OverlayedChangeSet; -use crate::{backend::Backend, stats::StateMachineStats}; -pub use offchain::OffchainOverlayedChanges; -use sp_std::{ - any::{Any, TypeId}, - boxed::Box, - vec::Vec, -}; - -use crate::{changes_trie::BlockNumber, DefaultError}; -#[cfg(feature = "std")] -use crate::{ - changes_trie::{build_changes_trie, State as ChangesTrieState}, - ChangesTrieTransaction, -}; +use crate::{backend::Backend, stats::StateMachineStats, DefaultError}; use codec::{Decode, Encode}; use hash_db::Hasher; +pub use offchain::OffchainOverlayedChanges; use sp_core::{ offchain::OffchainOverlayedChange, - storage::{well_known_keys::EXTRINSIC_INDEX, ChildInfo}, + storage::{well_known_keys::EXTRINSIC_INDEX, ChildInfo, StateVersion}, }; +#[cfg(feature = "std")] use sp_externalities::{Extension, Extensions}; #[cfg(not(feature = "std"))] -use sp_std::collections::btree_map::{BTreeMap as Map, Entry as MapEntry}; -use sp_std::collections::btree_set::BTreeSet; +use sp_std::collections::btree_map::BTreeMap as Map; +use sp_std::{collections::btree_set::BTreeSet, vec::Vec}; #[cfg(feature = "std")] use std::collections::{hash_map::Entry as MapEntry, HashMap as Map}; +#[cfg(feature = "std")] +use std::{ + any::{Any, TypeId}, + boxed::Box, +}; pub use self::changeset::{AlreadyInRuntime, NoOpenTransaction, NotInRuntime, OverlayedValue}; @@ -110,7 +104,7 @@ pub struct OverlayedChanges { stats: StateMachineStats, } -/// Transcation index operation. +/// Transaction index operation. #[derive(Debug, Clone)] pub enum IndexOperation { /// Insert transaction into index. @@ -135,7 +129,7 @@ pub enum IndexOperation { /// /// This contains all the changes to the storage and transactions to apply theses changes to the /// backend. -pub struct StorageChanges { +pub struct StorageChanges { /// All changes to the main storage. /// /// A value of `None` means that it was deleted. @@ -151,22 +145,13 @@ pub struct StorageChanges { pub transaction: Transaction, /// The storage root after applying the transaction. pub transaction_storage_root: H::Out, - /// Contains the transaction for the backend for the changes trie. - /// - /// If changes trie is disabled the value is set to `None`. - #[cfg(feature = "std")] - pub changes_trie_transaction: Option>, - /// Phantom data for block number until change trie support no_std. - #[cfg(not(feature = "std"))] - pub _ph: sp_std::marker::PhantomData, - /// Changes to the transaction index, #[cfg(feature = "std")] pub transaction_index_changes: Vec, } #[cfg(feature = "std")] -impl StorageChanges { +impl StorageChanges { /// Deconstruct into the inner values pub fn into_inner( self, @@ -176,7 +161,6 @@ impl StorageChanges { OffchainChangesCollection, Transaction, H::Out, - Option>, Vec, ) { ( @@ -185,58 +169,35 @@ impl StorageChanges { self.offchain_storage_changes, self.transaction, self.transaction_storage_root, - self.changes_trie_transaction, self.transaction_index_changes, ) } } -/// The storage transaction are calculated as part of the `storage_root` and -/// `changes_trie_storage_root`. These transactions can be reused for importing the block into the +/// Storage transactions are calculated as part of the `storage_root`. +/// These transactions can be reused for importing the block into the /// storage. So, we cache them to not require a recomputation of those transactions. -pub struct StorageTransactionCache { +pub struct StorageTransactionCache { /// Contains the changes for the main and the child storages as one transaction. pub(crate) transaction: Option, /// The storage root after applying the transaction. pub(crate) transaction_storage_root: Option, - /// Contains the changes trie transaction. - #[cfg(feature = "std")] - pub(crate) changes_trie_transaction: Option>>, - /// The storage root after applying the changes trie transaction. - #[cfg(feature = "std")] - pub(crate) changes_trie_transaction_storage_root: Option>, - /// Phantom data for block number until change trie support no_std. - #[cfg(not(feature = "std"))] - pub(crate) _ph: sp_std::marker::PhantomData, } -impl StorageTransactionCache { +impl StorageTransactionCache { /// Reset the cached transactions. pub fn reset(&mut self) { *self = Self::default(); } } -impl Default - for StorageTransactionCache -{ +impl Default for StorageTransactionCache { fn default() -> Self { - Self { - transaction: None, - transaction_storage_root: None, - #[cfg(feature = "std")] - changes_trie_transaction: None, - #[cfg(feature = "std")] - changes_trie_transaction_storage_root: None, - #[cfg(not(feature = "std"))] - _ph: Default::default(), - } + Self { transaction: None, transaction_storage_root: None } } } -impl Default - for StorageChanges -{ +impl Default for StorageChanges { fn default() -> Self { Self { main_storage_changes: Default::default(), @@ -245,10 +206,6 @@ impl Default transaction: Default::default(), transaction_storage_root: Default::default(), #[cfg(feature = "std")] - changes_trie_transaction: None, - #[cfg(not(feature = "std"))] - _ph: Default::default(), - #[cfg(feature = "std")] transaction_index_changes: Default::default(), } } @@ -342,7 +299,7 @@ impl OverlayedChanges { /// Clear child storage of given storage key. /// /// Can be rolled back or committed when called inside a transaction. - pub(crate) fn clear_child_storage(&mut self, child_info: &ChildInfo) { + pub(crate) fn clear_child_storage(&mut self, child_info: &ChildInfo) -> u32 { let extrinsic_index = self.extrinsic_index(); let storage_key = child_info.storage_key().to_vec(); let top = &self.top; @@ -352,20 +309,20 @@ impl OverlayedChanges { .or_insert_with(|| (top.spawn_child(), child_info.clone())); let updatable = info.try_update(child_info); debug_assert!(updatable); - changeset.clear_where(|_, _| true, extrinsic_index); + changeset.clear_where(|_, _| true, extrinsic_index) } /// Removes all key-value pairs which keys share the given prefix. /// /// Can be rolled back or committed when called inside a transaction. - pub(crate) fn clear_prefix(&mut self, prefix: &[u8]) { - self.top.clear_where(|key, _| key.starts_with(prefix), self.extrinsic_index()); + pub(crate) fn clear_prefix(&mut self, prefix: &[u8]) -> u32 { + self.top.clear_where(|key, _| key.starts_with(prefix), self.extrinsic_index()) } /// Removes all key-value pairs which keys share the given prefix. /// /// Can be rolled back or committed when called inside a transaction - pub(crate) fn clear_child_prefix(&mut self, child_info: &ChildInfo, prefix: &[u8]) { + pub(crate) fn clear_child_prefix(&mut self, child_info: &ChildInfo, prefix: &[u8]) -> u32 { let extrinsic_index = self.extrinsic_index(); let storage_key = child_info.storage_key().to_vec(); let top = &self.top; @@ -375,7 +332,7 @@ impl OverlayedChanges { .or_insert_with(|| (top.spawn_child(), child_info.clone())); let updatable = info.try_update(child_info); debug_assert!(updatable); - changeset.clear_where(|key, _| key.starts_with(prefix), extrinsic_index); + changeset.clear_where(|key, _| key.starts_with(prefix), extrinsic_index) } /// Returns the current nesting depth of the transaction stack. @@ -517,7 +474,7 @@ impl OverlayedChanges { pub fn children( &self, ) -> impl Iterator, &ChildInfo)> { - self.children.iter().map(|(_, v)| (v.0.changes(), &v.1)) + self.children.values().map(|v| (v.0.changes(), &v.1)) } /// Get an iterator over all top changes as been by the current transaction. @@ -540,33 +497,31 @@ impl OverlayedChanges { /// Convert this instance with all changes into a [`StorageChanges`] instance. #[cfg(feature = "std")] - pub fn into_storage_changes, H: Hasher, N: BlockNumber>( + pub fn into_storage_changes, H: Hasher>( mut self, backend: &B, - changes_trie_state: Option<&ChangesTrieState>, - parent_hash: H::Out, - mut cache: StorageTransactionCache, - ) -> Result, DefaultError> + mut cache: StorageTransactionCache, + state_version: StateVersion, + ) -> Result, DefaultError> where H::Out: Ord + Encode + 'static, { - self.drain_storage_changes(backend, changes_trie_state, parent_hash, &mut cache) + self.drain_storage_changes(backend, &mut cache, state_version) } /// Drain all changes into a [`StorageChanges`] instance. Leave empty overlay in place. - pub fn drain_storage_changes, H: Hasher, N: BlockNumber>( + pub fn drain_storage_changes, H: Hasher>( &mut self, backend: &B, - #[cfg(feature = "std")] changes_trie_state: Option<&ChangesTrieState>, - parent_hash: H::Out, - mut cache: &mut StorageTransactionCache, - ) -> Result, DefaultError> + cache: &mut StorageTransactionCache, + state_version: StateVersion, + ) -> Result, DefaultError> where H::Out: Ord + Encode + 'static, { // If the transaction does not exist, we generate it. if cache.transaction.is_none() { - self.storage_root(backend, &mut cache); + self.storage_root(backend, cache, state_version); } let (transaction, transaction_storage_root) = cache @@ -575,19 +530,6 @@ impl OverlayedChanges { .and_then(|t| cache.transaction_storage_root.take().map(|tr| (t, tr))) .expect("Transaction was be generated as part of `storage_root`; qed"); - // If the transaction does not exist, we generate it. - #[cfg(feature = "std")] - if cache.changes_trie_transaction.is_none() { - self.changes_trie_root(backend, changes_trie_state, parent_hash, false, &mut cache) - .map_err(|_| "Failed to generate changes trie transaction")?; - } - - #[cfg(feature = "std")] - let changes_trie_transaction = cache - .changes_trie_transaction - .take() - .expect("Changes trie transaction was generated by `changes_trie_root`; qed"); - let (main_storage_changes, child_storage_changes) = self.drain_committed(); let offchain_storage_changes = self.offchain_drain_committed().collect(); @@ -603,11 +545,7 @@ impl OverlayedChanges { transaction, transaction_storage_root, #[cfg(feature = "std")] - changes_trie_transaction, - #[cfg(feature = "std")] transaction_index_changes, - #[cfg(not(feature = "std"))] - _ph: Default::default(), }) } @@ -638,10 +576,11 @@ impl OverlayedChanges { /// as seen by the current transaction. /// /// Returns the storage root and caches storage transaction in the given `cache`. - pub fn storage_root>( + pub fn storage_root>( &self, backend: &B, - cache: &mut StorageTransactionCache, + cache: &mut StorageTransactionCache, + state_version: StateVersion, ) -> H::Out where H::Out: Ord + Encode, @@ -651,7 +590,7 @@ impl OverlayedChanges { (info, changes.map(|(k, v)| (&k[..], v.value().map(|v| &v[..])))) }); - let (root, transaction) = backend.full_storage_root(delta, child_delta); + let (root, transaction) = backend.full_storage_root(delta, child_delta, state_version); cache.transaction = Some(transaction); cache.transaction_storage_root = Some(root); @@ -659,40 +598,6 @@ impl OverlayedChanges { root } - /// Generate the changes trie root. - /// - /// Returns the changes trie root and caches the storage transaction into the given `cache`. - /// - /// # Panics - /// - /// Panics on storage error, when `panic_on_storage_error` is set. - #[cfg(feature = "std")] - pub fn changes_trie_root<'a, H: Hasher, N: BlockNumber, B: Backend>( - &self, - backend: &B, - changes_trie_state: Option<&'a ChangesTrieState<'a, H, N>>, - parent_hash: H::Out, - panic_on_storage_error: bool, - cache: &mut StorageTransactionCache, - ) -> Result, ()> - where - H::Out: Ord + Encode + 'static, - { - build_changes_trie::<_, H, N>( - backend, - changes_trie_state, - self, - parent_hash, - panic_on_storage_error, - ) - .map(|r| { - let root = r.as_ref().map(|r| r.1).clone(); - cache.changes_trie_transaction = Some(r.map(|(db, _, cache)| (db, cache))); - cache.changes_trie_transaction_storage_root = Some(root); - root - }) - } - /// Returns an iterator over the keys (in lexicographic order) following `key` (excluding `key`) /// alongside its value. pub fn iter_after(&self, key: &[u8]) -> impl Iterator { @@ -733,6 +638,21 @@ impl OverlayedChanges { } } +#[cfg(feature = "std")] +impl From for OverlayedChanges { + fn from(storage: sp_core::storage::Storage) -> Self { + Self { + top: storage.top.into(), + children: storage + .children_default + .into_iter() + .map(|(k, v)| (k, (v.data.into(), v.child_info))) + .collect(), + ..Default::default() + } + } +} + #[cfg(feature = "std")] fn retain_map(map: &mut Map, f: F) where @@ -758,6 +678,7 @@ where /// An overlayed extension is either a mutable reference /// or an owned extension. +#[cfg(feature = "std")] pub enum OverlayedExtension<'a> { MutRef(&'a mut Box), Owned(Box), @@ -770,10 +691,12 @@ pub enum OverlayedExtension<'a> { /// as owned references. After the execution of a runtime function, we /// can safely drop this object while not having modified the original /// list. +#[cfg(feature = "std")] pub struct OverlayedExtensions<'a> { extensions: Map>, } +#[cfg(feature = "std")] impl<'a> OverlayedExtensions<'a> { /// Create a new instance of overalyed extensions from the given extensions. pub fn new(extensions: &'a mut Extensions) -> Self { @@ -820,7 +743,6 @@ impl<'a> OverlayedExtensions<'a> { mod tests { use super::*; use crate::{ext::Ext, InMemoryBackend}; - use hex_literal::hex; use sp_core::{traits::Externalities, Blake2Hasher}; use std::collections::BTreeMap; @@ -923,6 +845,7 @@ mod tests { #[test] fn overlayed_storage_root_works() { + let state_version = StateVersion::default(); let initial: BTreeMap<_, _> = vec![ (b"doe".to_vec(), b"reindeer".to_vec()), (b"dog".to_vec(), b"puppyXXX".to_vec()), @@ -931,9 +854,8 @@ mod tests { ] .into_iter() .collect(); - let backend = InMemoryBackend::::from(initial); + let backend = InMemoryBackend::::from((initial, state_version)); let mut overlay = OverlayedChanges::default(); - overlay.set_collect_extrinsics(false); overlay.start_transaction(); overlay.set_storage(b"dog".to_vec(), Some(b"puppy".to_vec())); @@ -946,17 +868,12 @@ mod tests { overlay.set_storage(b"doug".to_vec(), None); let mut cache = StorageTransactionCache::default(); - let mut ext = Ext::new( - &mut overlay, - &mut cache, - &backend, - crate::changes_trie::disabled_state::<_, u64>(), - None, + let mut ext = Ext::new(&mut overlay, &mut cache, &backend, None); + let root = array_bytes::hex2bytes_unchecked( + "39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa", ); - const ROOT: [u8; 32] = - hex!("39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa"); - assert_eq!(&ext.storage_root()[..], &ROOT); + assert_eq!(&ext.storage_root(state_version)[..], &root); } #[test] diff --git a/primitives/state-machine/src/overlayed_changes/offchain.rs b/primitives/state-machine/src/overlayed_changes/offchain.rs index ac67ca3303008..a9643c265e755 100644 --- a/primitives/state-machine/src/overlayed_changes/offchain.rs +++ b/primitives/state-machine/src/overlayed_changes/offchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,7 +42,7 @@ impl OffchainOverlayedChanges { } /// Iterate over all key value pairs by reference. - pub fn iter<'a>(&'a self) -> impl Iterator> { + pub fn iter(&self) -> impl Iterator { self.0.changes().map(|kv| (kv.0, kv.1.value_ref())) } diff --git a/primitives/state-machine/src/proving_backend.rs b/primitives/state-machine/src/proving_backend.rs deleted file mode 100644 index 690266dab1e72..0000000000000 --- a/primitives/state-machine/src/proving_backend.rs +++ /dev/null @@ -1,539 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2021 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. - -//! Proving state machine backend. - -use crate::{ - trie_backend::TrieBackend, - trie_backend_essence::{Ephemeral, TrieBackendEssence, TrieBackendStorage}, - Backend, DBValue, Error, ExecutionError, -}; -use codec::{Codec, Decode, Encode}; -use hash_db::{HashDB, Hasher, Prefix, EMPTY_PREFIX}; -use log::debug; -use parking_lot::RwLock; -use sp_core::storage::ChildInfo; -use sp_trie::{ - empty_child_trie_root, read_child_trie_value_with, read_trie_value_with, record_all_keys, - MemoryDB, StorageProof, -}; -pub use sp_trie::{ - trie_types::{Layout, TrieError}, - Recorder, -}; -use std::{ - collections::{hash_map::Entry, HashMap}, - sync::Arc, -}; - -/// Patricia trie-based backend specialized in get value proofs. -pub struct ProvingBackendRecorder<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> { - pub(crate) backend: &'a TrieBackendEssence, - pub(crate) proof_recorder: &'a mut Recorder, -} - -impl<'a, S, H> ProvingBackendRecorder<'a, S, H> -where - S: TrieBackendStorage, - H: Hasher, - H::Out: Codec, -{ - /// Produce proof for a key query. - pub fn storage(&mut self, key: &[u8]) -> Result>, String> { - let mut read_overlay = S::Overlay::default(); - let eph = Ephemeral::new(self.backend.backend_storage(), &mut read_overlay); - - let map_e = |e| format!("Trie lookup error: {}", e); - - read_trie_value_with::, _, Ephemeral>( - &eph, - self.backend.root(), - key, - &mut *self.proof_recorder, - ) - .map_err(map_e) - } - - /// Produce proof for a child key query. - pub fn child_storage( - &mut self, - child_info: &ChildInfo, - key: &[u8], - ) -> Result>, String> { - let storage_key = child_info.storage_key(); - let root = self - .storage(storage_key)? - .and_then(|r| Decode::decode(&mut &r[..]).ok()) - .unwrap_or_else(|| empty_child_trie_root::>()); - - let mut read_overlay = S::Overlay::default(); - let eph = Ephemeral::new(self.backend.backend_storage(), &mut read_overlay); - - let map_e = |e| format!("Trie lookup error: {}", e); - - read_child_trie_value_with::, _, _>( - child_info.keyspace(), - &eph, - &root.as_ref(), - key, - &mut *self.proof_recorder, - ) - .map_err(map_e) - } - - /// Produce proof for the whole backend. - pub fn record_all_keys(&mut self) { - let mut read_overlay = S::Overlay::default(); - let eph = Ephemeral::new(self.backend.backend_storage(), &mut read_overlay); - - let mut iter = move || -> Result<(), Box>> { - let root = self.backend.root(); - record_all_keys::, _>(&eph, root, &mut *self.proof_recorder) - }; - - if let Err(e) = iter() { - debug!(target: "trie", "Error while recording all keys: {}", e); - } - } -} - -#[derive(Default)] -struct ProofRecorderInner { - /// All the records that we have stored so far. - records: HashMap>, - /// The encoded size of all recorded values. - encoded_size: usize, -} - -/// Global proof recorder, act as a layer over a hash db for recording queried data. -#[derive(Clone, Default)] -pub struct ProofRecorder { - inner: Arc>>, -} - -impl ProofRecorder { - /// Record the given `key` => `val` combination. - pub fn record(&self, key: Hash, val: Option) { - let mut inner = self.inner.write(); - let encoded_size = if let Entry::Vacant(entry) = inner.records.entry(key) { - let encoded_size = val.as_ref().map(Encode::encoded_size).unwrap_or(0); - - entry.insert(val); - encoded_size - } else { - 0 - }; - - inner.encoded_size += encoded_size; - } - - /// Returns the value at the given `key`. - pub fn get(&self, key: &Hash) -> Option> { - self.inner.read().records.get(key).cloned() - } - - /// Returns the estimated encoded size of the proof. - /// - /// The estimation is maybe bigger (by in maximum 4 bytes), but never smaller than the actual - /// encoded proof. - pub fn estimate_encoded_size(&self) -> usize { - let inner = self.inner.read(); - inner.encoded_size + codec::Compact(inner.records.len() as u32).encoded_size() - } - - /// Convert into a [`StorageProof`]. - pub fn to_storage_proof(&self) -> StorageProof { - let trie_nodes = self - .inner - .read() - .records - .iter() - .filter_map(|(_k, v)| v.as_ref().map(|v| v.to_vec())) - .collect(); - - StorageProof::new(trie_nodes) - } - - /// Reset the internal state. - pub fn reset(&self) { - let mut inner = self.inner.write(); - inner.records.clear(); - inner.encoded_size = 0; - } -} - -/// Patricia trie-based backend which also tracks all touched storage trie values. -/// These can be sent to remote node and used as a proof of execution. -pub struct ProvingBackend<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher>( - TrieBackend, H>, -); - -/// Trie backend storage with its proof recorder. -pub struct ProofRecorderBackend<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> { - backend: &'a S, - proof_recorder: ProofRecorder, -} - -impl<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> ProvingBackend<'a, S, H> -where - H::Out: Codec, -{ - /// Create new proving backend. - pub fn new(backend: &'a TrieBackend) -> Self { - let proof_recorder = Default::default(); - Self::new_with_recorder(backend, proof_recorder) - } - - /// Create new proving backend with the given recorder. - pub fn new_with_recorder( - backend: &'a TrieBackend, - proof_recorder: ProofRecorder, - ) -> Self { - let essence = backend.essence(); - let root = essence.root().clone(); - let recorder = ProofRecorderBackend { backend: essence.backend_storage(), proof_recorder }; - ProvingBackend(TrieBackend::new(recorder, root)) - } - - /// Extracting the gathered unordered proof. - pub fn extract_proof(&self) -> StorageProof { - self.0.essence().backend_storage().proof_recorder.to_storage_proof() - } - - /// Returns the estimated encoded size of the proof. - /// - /// The estimation is maybe bigger (by in maximum 4 bytes), but never smaller than the actual - /// encoded proof. - pub fn estimate_encoded_size(&self) -> usize { - self.0.essence().backend_storage().proof_recorder.estimate_encoded_size() - } -} - -impl<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> TrieBackendStorage - for ProofRecorderBackend<'a, S, H> -{ - type Overlay = S::Overlay; - - fn get(&self, key: &H::Out, prefix: Prefix) -> Result, String> { - if let Some(v) = self.proof_recorder.get(key) { - return Ok(v) - } - - let backend_value = self.backend.get(key, prefix)?; - self.proof_recorder.record(key.clone(), backend_value.clone()); - Ok(backend_value) - } -} - -impl<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> std::fmt::Debug - for ProvingBackend<'a, S, H> -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ProvingBackend") - } -} - -impl<'a, S, H> Backend for ProvingBackend<'a, S, H> -where - S: 'a + TrieBackendStorage, - H: 'a + Hasher, - H::Out: Ord + Codec, -{ - type Error = String; - type Transaction = S::Overlay; - type TrieBackendStorage = S; - - fn storage(&self, key: &[u8]) -> Result>, Self::Error> { - self.0.storage(key) - } - - fn child_storage( - &self, - child_info: &ChildInfo, - key: &[u8], - ) -> Result>, Self::Error> { - self.0.child_storage(child_info, key) - } - - fn apply_to_key_values_while, Vec) -> bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - allow_missing: bool, - ) -> Result { - self.0.apply_to_key_values_while(child_info, prefix, start_at, f, allow_missing) - } - - fn apply_to_keys_while bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - f: F, - ) { - self.0.apply_to_keys_while(child_info, prefix, f) - } - - fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { - self.0.next_storage_key(key) - } - - fn next_child_storage_key( - &self, - child_info: &ChildInfo, - key: &[u8], - ) -> Result>, Self::Error> { - self.0.next_child_storage_key(child_info, key) - } - - fn for_keys_with_prefix(&self, prefix: &[u8], f: F) { - self.0.for_keys_with_prefix(prefix, f) - } - - fn for_key_values_with_prefix(&self, prefix: &[u8], f: F) { - self.0.for_key_values_with_prefix(prefix, f) - } - - fn for_child_keys_with_prefix( - &self, - child_info: &ChildInfo, - prefix: &[u8], - f: F, - ) { - self.0.for_child_keys_with_prefix(child_info, prefix, f) - } - - fn pairs(&self) -> Vec<(Vec, Vec)> { - self.0.pairs() - } - - fn keys(&self, prefix: &[u8]) -> Vec> { - self.0.keys(prefix) - } - - fn child_keys(&self, child_info: &ChildInfo, prefix: &[u8]) -> Vec> { - self.0.child_keys(child_info, prefix) - } - - fn storage_root<'b>( - &self, - delta: impl Iterator)>, - ) -> (H::Out, Self::Transaction) - where - H::Out: Ord, - { - self.0.storage_root(delta) - } - - fn child_storage_root<'b>( - &self, - child_info: &ChildInfo, - delta: impl Iterator)>, - ) -> (H::Out, bool, Self::Transaction) - where - H::Out: Ord, - { - self.0.child_storage_root(child_info, delta) - } - - fn register_overlay_stats(&self, _stats: &crate::stats::StateMachineStats) {} - - fn usage_info(&self) -> crate::stats::UsageInfo { - self.0.usage_info() - } -} - -/// Create proof check backend. -pub fn create_proof_check_backend( - root: H::Out, - proof: StorageProof, -) -> Result, H>, Box> -where - H: Hasher, - H::Out: Codec, -{ - let db = proof.into_memory_db(); - - if db.contains(&root, EMPTY_PREFIX) { - Ok(TrieBackend::new(db, root)) - } else { - Err(Box::new(ExecutionError::InvalidProof)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - proving_backend::create_proof_check_backend, trie_backend::tests::test_trie, - InMemoryBackend, - }; - use sp_runtime::traits::BlakeTwo256; - use sp_trie::PrefixedMemoryDB; - - fn test_proving<'a>( - trie_backend: &'a TrieBackend, BlakeTwo256>, - ) -> ProvingBackend<'a, PrefixedMemoryDB, BlakeTwo256> { - ProvingBackend::new(trie_backend) - } - - #[test] - fn proof_is_empty_until_value_is_read() { - let trie_backend = test_trie(); - assert!(test_proving(&trie_backend).extract_proof().is_empty()); - } - - #[test] - fn proof_is_non_empty_after_value_is_read() { - let trie_backend = test_trie(); - let backend = test_proving(&trie_backend); - assert_eq!(backend.storage(b"key").unwrap(), Some(b"value".to_vec())); - assert!(!backend.extract_proof().is_empty()); - } - - #[test] - fn proof_is_invalid_when_does_not_contains_root() { - use sp_core::H256; - let result = create_proof_check_backend::( - H256::from_low_u64_be(1), - StorageProof::empty(), - ); - assert!(result.is_err()); - } - - #[test] - fn passes_through_backend_calls() { - let trie_backend = test_trie(); - let proving_backend = test_proving(&trie_backend); - assert_eq!(trie_backend.storage(b"key").unwrap(), proving_backend.storage(b"key").unwrap()); - assert_eq!(trie_backend.pairs(), proving_backend.pairs()); - - let (trie_root, mut trie_mdb) = trie_backend.storage_root(std::iter::empty()); - let (proving_root, mut proving_mdb) = proving_backend.storage_root(std::iter::empty()); - assert_eq!(trie_root, proving_root); - assert_eq!(trie_mdb.drain(), proving_mdb.drain()); - } - - #[test] - fn proof_recorded_and_checked() { - let contents = (0..64).map(|i| (vec![i], Some(vec![i]))).collect::>(); - let in_memory = InMemoryBackend::::default(); - let in_memory = in_memory.update(vec![(None, contents)]); - let in_memory_root = in_memory.storage_root(::std::iter::empty()).0; - (0..64).for_each(|i| assert_eq!(in_memory.storage(&[i]).unwrap().unwrap(), vec![i])); - - let trie = in_memory.as_trie_backend().unwrap(); - let trie_root = trie.storage_root(::std::iter::empty()).0; - assert_eq!(in_memory_root, trie_root); - (0..64).for_each(|i| assert_eq!(trie.storage(&[i]).unwrap().unwrap(), vec![i])); - - let proving = ProvingBackend::new(trie); - assert_eq!(proving.storage(&[42]).unwrap().unwrap(), vec![42]); - - let proof = proving.extract_proof(); - - let proof_check = - create_proof_check_backend::(in_memory_root.into(), proof).unwrap(); - assert_eq!(proof_check.storage(&[42]).unwrap().unwrap(), vec![42]); - } - - #[test] - fn proof_recorded_and_checked_with_child() { - let child_info_1 = ChildInfo::new_default(b"sub1"); - let child_info_2 = ChildInfo::new_default(b"sub2"); - let child_info_1 = &child_info_1; - let child_info_2 = &child_info_2; - let contents = vec![ - (None, (0..64).map(|i| (vec![i], Some(vec![i]))).collect()), - (Some(child_info_1.clone()), (28..65).map(|i| (vec![i], Some(vec![i]))).collect()), - (Some(child_info_2.clone()), (10..15).map(|i| (vec![i], Some(vec![i]))).collect()), - ]; - let in_memory = InMemoryBackend::::default(); - let in_memory = in_memory.update(contents); - let child_storage_keys = vec![child_info_1.to_owned(), child_info_2.to_owned()]; - let in_memory_root = in_memory - .full_storage_root( - std::iter::empty(), - child_storage_keys.iter().map(|k| (k, std::iter::empty())), - ) - .0; - (0..64).for_each(|i| assert_eq!(in_memory.storage(&[i]).unwrap().unwrap(), vec![i])); - (28..65).for_each(|i| { - assert_eq!(in_memory.child_storage(child_info_1, &[i]).unwrap().unwrap(), vec![i]) - }); - (10..15).for_each(|i| { - assert_eq!(in_memory.child_storage(child_info_2, &[i]).unwrap().unwrap(), vec![i]) - }); - - let trie = in_memory.as_trie_backend().unwrap(); - let trie_root = trie.storage_root(std::iter::empty()).0; - assert_eq!(in_memory_root, trie_root); - (0..64).for_each(|i| assert_eq!(trie.storage(&[i]).unwrap().unwrap(), vec![i])); - - let proving = ProvingBackend::new(trie); - assert_eq!(proving.storage(&[42]).unwrap().unwrap(), vec![42]); - - let proof = proving.extract_proof(); - - let proof_check = - create_proof_check_backend::(in_memory_root.into(), proof).unwrap(); - assert!(proof_check.storage(&[0]).is_err()); - assert_eq!(proof_check.storage(&[42]).unwrap().unwrap(), vec![42]); - // note that it is include in root because proof close - assert_eq!(proof_check.storage(&[41]).unwrap().unwrap(), vec![41]); - assert_eq!(proof_check.storage(&[64]).unwrap(), None); - - let proving = ProvingBackend::new(trie); - assert_eq!(proving.child_storage(child_info_1, &[64]), Ok(Some(vec![64]))); - - let proof = proving.extract_proof(); - let proof_check = - create_proof_check_backend::(in_memory_root.into(), proof).unwrap(); - assert_eq!(proof_check.child_storage(child_info_1, &[64]).unwrap().unwrap(), vec![64]); - } - - #[test] - fn storage_proof_encoded_size_estimation_works() { - let trie_backend = test_trie(); - let backend = test_proving(&trie_backend); - - let check_estimation = - |backend: &ProvingBackend<'_, PrefixedMemoryDB, BlakeTwo256>| { - let storage_proof = backend.extract_proof(); - let estimation = - backend.0.essence().backend_storage().proof_recorder.estimate_encoded_size(); - - assert_eq!(storage_proof.encoded_size(), estimation); - }; - - assert_eq!(backend.storage(b"key").unwrap(), Some(b"value".to_vec())); - check_estimation(&backend); - - assert_eq!(backend.storage(b"value1").unwrap(), Some(vec![42])); - check_estimation(&backend); - - assert_eq!(backend.storage(b"value2").unwrap(), Some(vec![24])); - check_estimation(&backend); - - assert!(backend.storage(b"doesnotexist").unwrap().is_none()); - check_estimation(&backend); - - assert!(backend.storage(b"doesnotexist2").unwrap().is_none()); - check_estimation(&backend); - } -} diff --git a/primitives/state-machine/src/read_only.rs b/primitives/state-machine/src/read_only.rs index 5b7d568b0311e..e58fb760f4d7e 100644 --- a/primitives/state-machine/src/read_only.rs +++ b/primitives/state-machine/src/read_only.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +21,10 @@ use crate::{Backend, StorageKey, StorageValue}; use codec::Encode; use hash_db::Hasher; use sp_core::{ - storage::{ChildInfo, TrackedStorageKey}, + storage::{ChildInfo, StateVersion, TrackedStorageKey}, traits::Externalities, - Blake2Hasher, }; +use sp_externalities::MultiRemovalResults; use std::{ any::{Any, TypeId}, marker::PhantomData, @@ -43,7 +43,10 @@ pub trait InspectState> { fn inspect_state R, R>(&self, f: F) -> R; } -impl> InspectState for B { +impl> InspectState for B +where + H::Out: Encode, +{ fn inspect_state R, R>(&self, f: F) -> R { ReadOnlyExternalities::from(self).execute_with(f) } @@ -65,7 +68,10 @@ impl<'a, H: Hasher, B: 'a + Backend> From<&'a B> for ReadOnlyExternalities<'a } } -impl<'a, H: Hasher, B: 'a + Backend> ReadOnlyExternalities<'a, H, B> { +impl<'a, H: Hasher, B: 'a + Backend> ReadOnlyExternalities<'a, H, B> +where + H::Out: Encode, +{ /// Execute the given closure while `self` is set as externalities. /// /// Returns the result of the given closure. @@ -74,7 +80,10 @@ impl<'a, H: Hasher, B: 'a + Backend> ReadOnlyExternalities<'a, H, B> { } } -impl<'a, H: Hasher, B: 'a + Backend> Externalities for ReadOnlyExternalities<'a, H, B> { +impl<'a, H: Hasher, B: 'a + Backend> Externalities for ReadOnlyExternalities<'a, H, B> +where + H::Out: Encode, +{ fn set_offchain_storage(&mut self, _key: &[u8], _value: Option<&[u8]>) { panic!("Should not be used in read-only externalities!") } @@ -86,7 +95,10 @@ impl<'a, H: Hasher, B: 'a + Backend> Externalities for ReadOnlyExternalities< } fn storage_hash(&self, key: &[u8]) -> Option> { - self.storage(key).map(|v| Blake2Hasher::hash(&v).encode()) + self.backend + .storage_hash(key) + .expect("Backed failed for storage_hash in ReadOnlyExternalities") + .map(|h| h.encode()) } fn child_storage(&self, child_info: &ChildInfo, key: &[u8]) -> Option { @@ -96,7 +108,10 @@ impl<'a, H: Hasher, B: 'a + Backend> Externalities for ReadOnlyExternalities< } fn child_storage_hash(&self, child_info: &ChildInfo, key: &[u8]) -> Option> { - self.child_storage(child_info, key).map(|v| Blake2Hasher::hash(&v).encode()) + self.backend + .child_storage_hash(child_info, key) + .expect("Backed failed for child_storage_hash in ReadOnlyExternalities") + .map(|h| h.encode()) } fn next_storage_key(&self, key: &[u8]) -> Option { @@ -124,11 +139,21 @@ impl<'a, H: Hasher, B: 'a + Backend> Externalities for ReadOnlyExternalities< unimplemented!("place_child_storage not supported in ReadOnlyExternalities") } - fn kill_child_storage(&mut self, _child_info: &ChildInfo, _limit: Option) -> (bool, u32) { + fn kill_child_storage( + &mut self, + _child_info: &ChildInfo, + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { unimplemented!("kill_child_storage is not supported in ReadOnlyExternalities") } - fn clear_prefix(&mut self, _prefix: &[u8], _limit: Option) -> (bool, u32) { + fn clear_prefix( + &mut self, + _prefix: &[u8], + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { unimplemented!("clear_prefix is not supported in ReadOnlyExternalities") } @@ -136,8 +161,9 @@ impl<'a, H: Hasher, B: 'a + Backend> Externalities for ReadOnlyExternalities< &mut self, _child_info: &ChildInfo, _prefix: &[u8], - _limit: Option, - ) -> (bool, u32) { + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { unimplemented!("clear_child_prefix is not supported in ReadOnlyExternalities") } @@ -145,18 +171,18 @@ impl<'a, H: Hasher, B: 'a + Backend> Externalities for ReadOnlyExternalities< unimplemented!("storage_append is not supported in ReadOnlyExternalities") } - fn storage_root(&mut self) -> Vec { + fn storage_root(&mut self, _state_version: StateVersion) -> Vec { unimplemented!("storage_root is not supported in ReadOnlyExternalities") } - fn child_storage_root(&mut self, _child_info: &ChildInfo) -> Vec { + fn child_storage_root( + &mut self, + _child_info: &ChildInfo, + _state_version: StateVersion, + ) -> Vec { unimplemented!("child_storage_root is not supported in ReadOnlyExternalities") } - fn storage_changes_root(&mut self, _parent: &[u8]) -> Result>, ()> { - unimplemented!("storage_changes_root is not supported in ReadOnlyExternalities") - } - fn storage_start_transaction(&mut self) { unimplemented!("Transactions are not supported by ReadOnlyExternalities"); } diff --git a/primitives/state-machine/src/stats.rs b/primitives/state-machine/src/stats.rs index affd71f9d2e5d..863ea5cfed7ec 100644 --- a/primitives/state-machine/src/stats.rs +++ b/primitives/state-machine/src/stats.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/state-machine/src/testing.rs b/primitives/state-machine/src/testing.rs index ec1772ba8666f..e94d34b5560cd 100644 --- a/primitives/state-machine/src/testing.rs +++ b/primitives/state-machine/src/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,89 +23,85 @@ use std::{ }; use crate::{ - backend::Backend, - changes_trie::{ - BlockNumber as ChangesTrieBlockNumber, Configuration as ChangesTrieConfiguration, - InMemoryStorage as ChangesTrieInMemoryStorage, State as ChangesTrieState, - }, - ext::Ext, - InMemoryBackend, OverlayedChanges, StorageKey, StorageTransactionCache, StorageValue, + backend::Backend, ext::Ext, InMemoryBackend, OverlayedChanges, StorageKey, + StorageTransactionCache, StorageValue, TrieBackendBuilder, }; -use codec::Decode; use hash_db::Hasher; use sp_core::{ offchain::testing::TestPersistentOffchainDB, storage::{ - well_known_keys::{is_child_storage_key, CHANGES_TRIE_CONFIG, CODE}, - Storage, + well_known_keys::{is_child_storage_key, CODE}, + StateVersion, Storage, }, testing::TaskExecutor, traits::TaskExecutorExt, }; use sp_externalities::{Extension, ExtensionStore, Extensions}; +use sp_trie::StorageProof; /// Simple HashMap-based Externalities impl. -pub struct TestExternalities +pub struct TestExternalities where + H: Hasher + 'static, H::Out: codec::Codec + Ord, { /// The overlay changed storage. overlay: OverlayedChanges, offchain_db: TestPersistentOffchainDB, storage_transaction_cache: - StorageTransactionCache< as Backend>::Transaction, H, N>, + StorageTransactionCache< as Backend>::Transaction, H>, /// Storage backend. pub backend: InMemoryBackend, - changes_trie_config: Option, - changes_trie_storage: ChangesTrieInMemoryStorage, /// Extensions. pub extensions: Extensions, + /// State version to use during tests. + pub state_version: StateVersion, } -impl TestExternalities +impl TestExternalities where + H: Hasher + 'static, H::Out: Ord + 'static + codec::Codec, { /// Get externalities implementation. - pub fn ext(&mut self) -> Ext> { + pub fn ext(&mut self) -> Ext> { Ext::new( &mut self.overlay, &mut self.storage_transaction_cache, &self.backend, - match self.changes_trie_config.clone() { - Some(config) => Some(ChangesTrieState { - config, - zero: 0.into(), - storage: &self.changes_trie_storage, - }), - None => None, - }, Some(&mut self.extensions), ) } /// Create a new instance of `TestExternalities` with storage. pub fn new(storage: Storage) -> Self { - Self::new_with_code(&[], storage) + Self::new_with_code_and_state(&[], storage, Default::default()) + } + + /// Create a new instance of `TestExternalities` with storage for a given state version. + pub fn new_with_state_version(storage: Storage, state_version: StateVersion) -> Self { + Self::new_with_code_and_state(&[], storage, state_version) } /// New empty test externalities. pub fn new_empty() -> Self { - Self::new_with_code(&[], Storage::default()) + Self::new_with_code_and_state(&[], Storage::default(), Default::default()) } /// Create a new instance of `TestExternalities` with code and storage. - pub fn new_with_code(code: &[u8], mut storage: Storage) -> Self { - let mut overlay = OverlayedChanges::default(); - let changes_trie_config = storage - .top - .get(CHANGES_TRIE_CONFIG) - .and_then(|v| Decode::decode(&mut &v[..]).ok()); - overlay.set_collect_extrinsics(changes_trie_config.is_some()); + pub fn new_with_code(code: &[u8], storage: Storage) -> Self { + Self::new_with_code_and_state(code, storage, Default::default()) + } + /// Create a new instance of `TestExternalities` with code and storage for a given state + /// version. + pub fn new_with_code_and_state( + code: &[u8], + mut storage: Storage, + state_version: StateVersion, + ) -> Self { assert!(storage.top.keys().all(|key| !is_child_storage_key(key))); - assert!(storage.children_default.keys().all(|key| is_child_storage_key(key))); storage.top.insert(CODE.to_vec(), code.to_vec()); @@ -114,14 +110,15 @@ where let offchain_db = TestPersistentOffchainDB::new(); + let backend = (storage, state_version).into(); + TestExternalities { - overlay, + overlay: OverlayedChanges::default(), offchain_db, - changes_trie_config, extensions, - changes_trie_storage: ChangesTrieInMemoryStorage::new(), - backend: storage.into(), + backend, storage_transaction_cache: Default::default(), + state_version, } } @@ -142,7 +139,14 @@ where /// Insert key/value into backend pub fn insert(&mut self, k: StorageKey, v: StorageValue) { - self.backend.insert(vec![(None, vec![(k, Some(v))])]); + self.backend.insert(vec![(None, vec![(k, Some(v))])], self.state_version); + } + + /// Insert key/value into backend. + /// + /// This only supports inserting keys in child tries. + pub fn insert_child(&mut self, c: sp_core::storage::ChildInfo, k: StorageKey, v: StorageValue) { + self.backend.insert(vec![(Some(c), vec![(k, Some(v))])], self.state_version); } /// Registers the given extension for this instance. @@ -150,16 +154,11 @@ where self.extensions.register(ext); } - /// Get mutable reference to changes trie storage. - pub fn changes_trie_storage(&mut self) -> &mut ChangesTrieInMemoryStorage { - &mut self.changes_trie_storage - } - /// Return a new backend with all pending changes. /// /// In contrast to [`commit_all`](Self::commit_all) this will not panic if there are open /// transactions. - fn as_backend(&self) -> InMemoryBackend { + pub fn as_backend(&self) -> InMemoryBackend { let top: Vec<_> = self.overlay.changes().map(|(k, v)| (k.clone(), v.value().cloned())).collect(); let mut transaction = vec![(None, top)]; @@ -171,7 +170,7 @@ where )) } - self.backend.update(transaction) + self.backend.update(transaction, self.state_version) } /// Commit all pending changes to the underlying backend. @@ -180,11 +179,10 @@ where /// /// This will panic if there are still open transactions. pub fn commit_all(&mut self) -> Result<(), String> { - let changes = self.overlay.drain_storage_changes::<_, _, N>( + let changes = self.overlay.drain_storage_changes::<_, _>( &self.backend, - None, - Default::default(), &mut Default::default(), + self.state_version, )?; self.backend @@ -200,9 +198,31 @@ where sp_externalities::set_and_run_with_externalities(&mut ext, execute) } + /// Execute the given closure while `self`, with `proving_backend` as backend, is set as + /// externalities. + /// + /// This implementation will wipe the proof recorded in between calls. Consecutive calls will + /// get their own proof from scratch. + pub fn execute_and_prove(&mut self, execute: impl FnOnce() -> R) -> (R, StorageProof) { + let proving_backend = TrieBackendBuilder::wrap(&self.backend) + .with_recorder(Default::default()) + .build(); + let mut proving_ext = Ext::new( + &mut self.overlay, + &mut self.storage_transaction_cache, + &proving_backend, + Some(&mut self.extensions), + ); + + let outcome = sp_externalities::set_and_run_with_externalities(&mut proving_ext, execute); + let proof = proving_backend.extract_proof().expect("Failed to extract storage proof"); + + (outcome, proof) + } + /// Execute the given closure while `self` is set as externalities. /// - /// Returns the result of the given closure, if no panics occured. + /// Returns the result of the given closure, if no panics occurred. /// Otherwise, returns `Err`. pub fn execute_with_safe( &mut self, @@ -216,7 +236,7 @@ where } } -impl std::fmt::Debug for TestExternalities +impl std::fmt::Debug for TestExternalities where H::Out: Ord + codec::Codec, { @@ -225,40 +245,49 @@ where } } -impl PartialEq for TestExternalities +impl PartialEq for TestExternalities where H::Out: Ord + 'static + codec::Codec, { /// This doesn't test if they are in the same state, only if they contains the /// same data at this state - fn eq(&self, other: &TestExternalities) -> bool { + fn eq(&self, other: &TestExternalities) -> bool { self.as_backend().eq(&other.as_backend()) } } -impl Default for TestExternalities +impl Default for TestExternalities where H::Out: Ord + 'static + codec::Codec, { fn default() -> Self { - Self::new(Default::default()) + // default to default version. + Self::new_with_state_version(Storage::default(), Default::default()) } } -impl From for TestExternalities +impl From for TestExternalities where H::Out: Ord + 'static + codec::Codec, { fn from(storage: Storage) -> Self { - Self::new(storage) + Self::new_with_state_version(storage, Default::default()) + } +} + +impl From<(Storage, StateVersion)> for TestExternalities +where + H::Out: Ord + 'static + codec::Codec, +{ + fn from((storage, state_version): (Storage, StateVersion)) -> Self { + Self::new_with_state_version(storage, state_version) } } -impl sp_externalities::ExtensionStore for TestExternalities +impl sp_externalities::ExtensionStore for TestExternalities where H: Hasher, H::Out: Ord + codec::Codec, - N: ChangesTrieBlockNumber, { fn extension_by_type_id(&mut self, type_id: TypeId) -> Option<&mut dyn Any> { self.extensions.get_mut(type_id) @@ -284,11 +313,10 @@ where } } -impl sp_externalities::ExternalitiesExt for TestExternalities +impl sp_externalities::ExternalitiesExt for TestExternalities where H: Hasher, H::Out: Ord + codec::Codec, - N: ChangesTrieBlockNumber, { fn extension(&mut self) -> Option<&mut T> { self.extension_by_type_id(TypeId::of::()).and_then(::downcast_mut) @@ -306,25 +334,26 @@ where #[cfg(test)] mod tests { use super::*; - use hex_literal::hex; use sp_core::{storage::ChildInfo, traits::Externalities, H256}; use sp_runtime::traits::BlakeTwo256; #[test] fn commit_should_work() { - let mut ext = TestExternalities::::default(); + let storage = Storage::default(); // avoid adding the trie threshold. + let mut ext = TestExternalities::::from((storage, Default::default())); let mut ext = ext.ext(); ext.set_storage(b"doe".to_vec(), b"reindeer".to_vec()); ext.set_storage(b"dog".to_vec(), b"puppy".to_vec()); ext.set_storage(b"dogglesworth".to_vec(), b"cat".to_vec()); - let root = - H256::from(hex!("ed4d8c799d996add422395a6abd7545491d40bd838d738afafa1b8a4de625489")); - assert_eq!(H256::from_slice(ext.storage_root().as_slice()), root); + let root = array_bytes::hex_n_into_unchecked::( + "ed4d8c799d996add422395a6abd7545491d40bd838d738afafa1b8a4de625489", + ); + assert_eq!(H256::from_slice(ext.storage_root(Default::default()).as_slice()), root); } #[test] fn set_and_retrieve_code() { - let mut ext = TestExternalities::::default(); + let mut ext = TestExternalities::::default(); let mut ext = ext.ext(); let code = vec![1, 2, 3]; @@ -336,12 +365,12 @@ mod tests { #[test] fn check_send() { fn assert_send() {} - assert_send::>(); + assert_send::>(); } #[test] fn commit_all_and_kill_child_storage() { - let mut ext = TestExternalities::::default(); + let mut ext = TestExternalities::::default(); let child_info = ChildInfo::new_default(&b"test_child"[..]); { @@ -356,7 +385,10 @@ mod tests { { let mut ext = ext.ext(); - assert!(!ext.kill_child_storage(&child_info, Some(2)).0, "Should not delete all keys"); + assert!( + ext.kill_child_storage(&child_info, Some(2), None).maybe_cursor.is_some(), + "Should not delete all keys" + ); assert!(ext.child_storage(&child_info, &b"doe"[..]).is_none()); assert!(ext.child_storage(&child_info, &b"dog"[..]).is_none()); @@ -366,7 +398,7 @@ mod tests { #[test] fn as_backend_generates_same_backend_as_commit_all() { - let mut ext = TestExternalities::::default(); + let mut ext = TestExternalities::::default(); { let mut ext = ext.ext(); ext.set_storage(b"doe".to_vec(), b"reindeer".to_vec()); diff --git a/primitives/state-machine/src/trie_backend.rs b/primitives/state-machine/src/trie_backend.rs index 7cb725a80503d..da4250b6ba3e1 100644 --- a/primitives/state-machine/src/trie_backend.rs +++ b/primitives/state-machine/src/trie_backend.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,37 +17,182 @@ //! Trie-based state machine backend. +#[cfg(feature = "std")] +use crate::backend::AsTrieBackend; use crate::{ - debug, - trie_backend_essence::{Ephemeral, TrieBackendEssence, TrieBackendStorage}, - warn, Backend, StorageKey, StorageValue, + trie_backend_essence::{TrieBackendEssence, TrieBackendStorage}, + Backend, StorageKey, StorageValue, }; -use codec::{Codec, Decode}; +use codec::Codec; +#[cfg(feature = "std")] +use hash_db::HashDB; use hash_db::Hasher; -use sp_core::storage::{ChildInfo, ChildType}; -use sp_std::{boxed::Box, vec::Vec}; -use sp_trie::{ - child_delta_trie_root, delta_trie_root, empty_child_trie_root, - trie_types::{Layout, TrieDB, TrieError}, - Trie, -}; +use sp_core::storage::{ChildInfo, StateVersion}; +use sp_std::vec::Vec; +#[cfg(feature = "std")] +use sp_trie::{cache::LocalTrieCache, recorder::Recorder}; +#[cfg(feature = "std")] +use sp_trie::{MemoryDB, StorageProof}; -/// Patricia trie-based backend. Transaction type is an overlay of changes to commit. -pub struct TrieBackend, H: Hasher> { - pub(crate) essence: TrieBackendEssence, +/// Dummy type to be used in `no_std`. +/// +/// This is required to have the type available for [`TrieBackendBuilder`] and [`TrieBackend`]. +#[cfg(not(feature = "std"))] +pub struct LocalTrieCache(sp_std::marker::PhantomData); + +/// Special trait to support taking the [`LocalTrieCache`] by value or by reference. +/// +/// This trait is internal use only and to emphasize this, the trait is sealed. +pub trait AsLocalTrieCache: sealed::Sealed { + /// Returns `self` as [`LocalTrieCache`]. + #[cfg(feature = "std")] + fn as_local_trie_cache(&self) -> &LocalTrieCache; +} + +impl AsLocalTrieCache for LocalTrieCache { + #[cfg(feature = "std")] + fn as_local_trie_cache(&self) -> &LocalTrieCache { + self + } +} + +#[cfg(feature = "std")] +impl AsLocalTrieCache for &LocalTrieCache { + fn as_local_trie_cache(&self) -> &LocalTrieCache { + self + } } -impl, H: Hasher> TrieBackend +/// Special module that contains the `Sealed` trait. +mod sealed { + use super::*; + + /// A special trait which prevents externals to implement the [`AsLocalTrieCache`] outside + /// of this crate. + pub trait Sealed {} + + impl Sealed for LocalTrieCache {} + impl Sealed for &LocalTrieCache {} +} + +/// Builder for creating a [`TrieBackend`]. +pub struct TrieBackendBuilder, H: Hasher, C = LocalTrieCache> { + storage: S, + root: H::Out, + #[cfg(feature = "std")] + recorder: Option>, + cache: Option, +} + +impl TrieBackendBuilder> where - H::Out: Codec, + S: TrieBackendStorage, + H: Hasher, { - /// Create new trie-based backend. + /// Create a new builder instance. pub fn new(storage: S, root: H::Out) -> Self { - TrieBackend { essence: TrieBackendEssence::new(storage, root) } + Self { + storage, + root, + #[cfg(feature = "std")] + recorder: None, + cache: None, + } + } +} + +impl TrieBackendBuilder +where + S: TrieBackendStorage, + H: Hasher, +{ + /// Wrap the given [`TrieBackend`]. + /// + /// This can be used for example if all accesses to the trie should + /// be recorded while some other functionality still uses the non-recording + /// backend. + /// + /// The backend storage and the cache will be taken from `other`. + pub fn wrap(other: &TrieBackend) -> TrieBackendBuilder<&S, H, &C> { + TrieBackendBuilder { + storage: other.essence.backend_storage(), + root: *other.essence.root(), + #[cfg(feature = "std")] + recorder: None, + #[cfg(feature = "std")] + cache: other.essence.trie_node_cache.as_ref(), + #[cfg(not(feature = "std"))] + cache: None, + } + } + + /// Use the given optional `recorder` for the to be configured [`TrieBackend`]. + #[cfg(feature = "std")] + pub fn with_optional_recorder(self, recorder: Option>) -> Self { + Self { recorder, ..self } + } + + /// Use the given `recorder` for the to be configured [`TrieBackend`]. + #[cfg(feature = "std")] + pub fn with_recorder(self, recorder: Recorder) -> Self { + Self { recorder: Some(recorder), ..self } + } + + /// Use the given optional `cache` for the to be configured [`TrieBackend`]. + #[cfg(feature = "std")] + pub fn with_optional_cache(self, cache: Option) -> TrieBackendBuilder { + TrieBackendBuilder { + cache, + root: self.root, + storage: self.storage, + recorder: self.recorder, + } } + /// Use the given `cache` for the to be configured [`TrieBackend`]. + #[cfg(feature = "std")] + pub fn with_cache(self, cache: LC) -> TrieBackendBuilder { + TrieBackendBuilder { + cache: Some(cache), + root: self.root, + storage: self.storage, + recorder: self.recorder, + } + } + + /// Build the configured [`TrieBackend`]. + #[cfg(feature = "std")] + pub fn build(self) -> TrieBackend { + TrieBackend { + essence: TrieBackendEssence::new_with_cache_and_recorder( + self.storage, + self.root, + self.cache, + self.recorder, + ), + } + } + + /// Build the configured [`TrieBackend`]. + #[cfg(not(feature = "std"))] + pub fn build(self) -> TrieBackend { + let _ = self.cache; + + TrieBackend { essence: TrieBackendEssence::new(self.storage, self.root) } + } +} + +/// Patricia trie-based backend. Transaction type is an overlay of changes to commit. +pub struct TrieBackend, H: Hasher, C = LocalTrieCache> { + pub(crate) essence: TrieBackendEssence, +} + +impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> TrieBackend +where + H::Out: Codec, +{ /// Get backend essence reference. - pub fn essence(&self) -> &TrieBackendEssence { + pub fn essence(&self) -> &TrieBackendEssence { &self.essence } @@ -65,15 +210,26 @@ where pub fn into_storage(self) -> S { self.essence.into_storage() } + + /// Extract the [`StorageProof`]. + /// + /// This only returns `Some` when there was a recorder set. + #[cfg(feature = "std")] + pub fn extract_proof(mut self) -> Option { + self.essence.recorder.take().map(|r| r.drain_storage_proof()) + } } -impl, H: Hasher> sp_std::fmt::Debug for TrieBackend { +impl, H: Hasher, C: AsLocalTrieCache> sp_std::fmt::Debug + for TrieBackend +{ fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { write!(f, "TrieBackend") } } -impl, H: Hasher> Backend for TrieBackend +impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> Backend + for TrieBackend where H::Out: Ord + Codec, { @@ -81,10 +237,22 @@ where type Transaction = S::Overlay; type TrieBackendStorage = S; + fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { + self.essence.storage_hash(key) + } + fn storage(&self, key: &[u8]) -> Result, Self::Error> { self.essence.storage(key) } + fn child_storage_hash( + &self, + child_info: &ChildInfo, + key: &[u8], + ) -> Result, Self::Error> { + self.essence.child_storage_hash(child_info, key) + } + fn child_storage( &self, child_info: &ChildInfo, @@ -129,9 +297,10 @@ where &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { - self.essence.apply_to_keys_while(child_info, prefix, f) + self.essence.apply_to_keys_while(child_info, prefix, start_at, f) } fn for_child_keys_with_prefix( @@ -144,112 +313,34 @@ where } fn pairs(&self) -> Vec<(StorageKey, StorageValue)> { - let collect_all = || -> Result<_, Box>> { - let trie = TrieDB::::new(self.essence(), self.essence.root())?; - let mut v = Vec::new(); - for x in trie.iter()? { - let (key, value) = x?; - v.push((key.to_vec(), value.to_vec())); - } - - Ok(v) - }; - - match collect_all() { - Ok(v) => v, - Err(e) => { - debug!(target: "trie", "Error extracting trie values: {}", e); - Vec::new() - }, - } + self.essence.pairs() } fn keys(&self, prefix: &[u8]) -> Vec { - let collect_all = || -> Result<_, Box>> { - let trie = TrieDB::::new(self.essence(), self.essence.root())?; - let mut v = Vec::new(); - for x in trie.iter()? { - let (key, _) = x?; - if key.starts_with(prefix) { - v.push(key.to_vec()); - } - } - - Ok(v) - }; - - collect_all() - .map_err(|e| debug!(target: "trie", "Error extracting trie keys: {}", e)) - .unwrap_or_default() + self.essence.keys(prefix) } fn storage_root<'a>( &self, delta: impl Iterator)>, + state_version: StateVersion, ) -> (H::Out, Self::Transaction) where H::Out: Ord, { - let mut write_overlay = S::Overlay::default(); - let mut root = *self.essence.root(); - - { - let mut eph = Ephemeral::new(self.essence.backend_storage(), &mut write_overlay); - - match delta_trie_root::, _, _, _, _, _>(&mut eph, root, delta) { - Ok(ret) => root = ret, - Err(e) => warn!(target: "trie", "Failed to write to trie: {}", e), - } - } - - (root, write_overlay) + self.essence.storage_root(delta, state_version) } fn child_storage_root<'a>( &self, child_info: &ChildInfo, delta: impl Iterator)>, + state_version: StateVersion, ) -> (H::Out, bool, Self::Transaction) where H::Out: Ord, { - let default_root = match child_info.child_type() { - ChildType::ParentKeyId => empty_child_trie_root::>(), - }; - - let mut write_overlay = S::Overlay::default(); - let prefixed_storage_key = child_info.prefixed_storage_key(); - let mut root = match self.storage(prefixed_storage_key.as_slice()) { - Ok(value) => value - .and_then(|r| Decode::decode(&mut &r[..]).ok()) - .unwrap_or_else(|| default_root.clone()), - Err(e) => { - warn!(target: "trie", "Failed to read child storage root: {}", e); - default_root.clone() - }, - }; - - { - let mut eph = Ephemeral::new(self.essence.backend_storage(), &mut write_overlay); - - match child_delta_trie_root::, _, _, _, _, _, _>( - child_info.keyspace(), - &mut eph, - root, - delta, - ) { - Ok(ret) => root = ret, - Err(e) => warn!(target: "trie", "Failed to write to trie: {}", e), - } - } - - let is_default = root == default_root; - - (root, is_default, write_overlay) - } - - fn as_trie_backend(&self) -> Option<&TrieBackend> { - Some(self) + self.essence.child_storage_root(child_info, delta, state_version) } fn register_overlay_stats(&self, _stats: &crate::stats::StateMachineStats) {} @@ -263,63 +354,191 @@ where } } +#[cfg(feature = "std")] +impl, H: Hasher, C> AsTrieBackend for TrieBackend { + type TrieBackendStorage = S; + + fn as_trie_backend(&self) -> &TrieBackend { + self + } +} + +/// Create a backend used for checking the proof, using `H` as hasher. +/// +/// `proof` and `root` must match, i.e. `root` must be the correct root of `proof` nodes. +#[cfg(feature = "std")] +pub fn create_proof_check_backend( + root: H::Out, + proof: StorageProof, +) -> Result, H>, Box> +where + H: Hasher, + H::Out: Codec, +{ + let db = proof.into_memory_db(); + + if db.contains(&root, hash_db::EMPTY_PREFIX) { + Ok(TrieBackendBuilder::new(db, root).build()) + } else { + Err(Box::new(crate::ExecutionError::InvalidProof)) + } +} + #[cfg(test)] pub mod tests { + use crate::{new_in_mem, InMemoryBackend}; + use super::*; use codec::Encode; use sp_core::H256; use sp_runtime::traits::BlakeTwo256; - use sp_trie::{trie_types::TrieDBMut, KeySpacedDBMut, PrefixedMemoryDB, TrieMut}; + use sp_trie::{ + cache::{CacheSize, SharedTrieCache}, + trie_types::{TrieDBBuilder, TrieDBMutBuilderV0, TrieDBMutBuilderV1}, + KeySpacedDBMut, PrefixedKey, PrefixedMemoryDB, Trie, TrieCache, TrieMut, + }; use std::{collections::HashSet, iter}; + use trie_db::NodeCodec; const CHILD_KEY_1: &[u8] = b"sub1"; - fn test_db() -> (PrefixedMemoryDB, H256) { + type Recorder = sp_trie::recorder::Recorder; + type Cache = LocalTrieCache; + type SharedCache = SharedTrieCache; + + macro_rules! parameterized_test { + ($name:ident, $internal_name:ident) => { + #[test] + fn $name() { + let parameters = vec![ + (StateVersion::V0, None, None), + (StateVersion::V0, Some(SharedCache::new(CacheSize::Unlimited)), None), + (StateVersion::V0, None, Some(Recorder::default())), + ( + StateVersion::V0, + Some(SharedCache::new(CacheSize::Unlimited)), + Some(Recorder::default()), + ), + (StateVersion::V1, None, None), + (StateVersion::V1, Some(SharedCache::new(CacheSize::Unlimited)), None), + (StateVersion::V1, None, Some(Recorder::default())), + ( + StateVersion::V1, + Some(SharedCache::new(CacheSize::Unlimited)), + Some(Recorder::default()), + ), + ]; + + for (version, cache, recorder) in parameters { + eprintln!( + "Running with version {:?}, cache enabled {} and recorder enabled {}", + version, + cache.is_some(), + recorder.is_some() + ); + + let cache = cache.as_ref().map(|c| c.local_cache()); + + $internal_name(version, cache, recorder.clone()); + } + } + }; + } + + pub(crate) fn test_db(state_version: StateVersion) -> (PrefixedMemoryDB, H256) { let child_info = ChildInfo::new_default(CHILD_KEY_1); let mut root = H256::default(); let mut mdb = PrefixedMemoryDB::::default(); { let mut mdb = KeySpacedDBMut::new(&mut mdb, child_info.keyspace()); - let mut trie = TrieDBMut::new(&mut mdb, &mut root); - trie.insert(b"value3", &[142]).expect("insert failed"); - trie.insert(b"value4", &[124]).expect("insert failed"); + match state_version { + StateVersion::V0 => { + let mut trie = TrieDBMutBuilderV0::new(&mut mdb, &mut root).build(); + trie.insert(b"value3", &[142; 33]).expect("insert failed"); + trie.insert(b"value4", &[124; 33]).expect("insert failed"); + }, + StateVersion::V1 => { + let mut trie = TrieDBMutBuilderV1::new(&mut mdb, &mut root).build(); + trie.insert(b"value3", &[142; 33]).expect("insert failed"); + trie.insert(b"value4", &[124; 33]).expect("insert failed"); + }, + }; }; { let mut sub_root = Vec::new(); root.encode_to(&mut sub_root); - let mut trie = TrieDBMut::new(&mut mdb, &mut root); - trie.insert(child_info.prefixed_storage_key().as_slice(), &sub_root[..]) - .expect("insert failed"); - trie.insert(b"key", b"value").expect("insert failed"); - trie.insert(b"value1", &[42]).expect("insert failed"); - trie.insert(b"value2", &[24]).expect("insert failed"); - trie.insert(b":code", b"return 42").expect("insert failed"); - for i in 128u8..255u8 { - trie.insert(&[i], &[i]).unwrap(); + + fn build( + mut trie: sp_trie::TrieDBMut, + child_info: &ChildInfo, + sub_root: &[u8], + ) { + trie.insert(child_info.prefixed_storage_key().as_slice(), sub_root) + .expect("insert failed"); + trie.insert(b"key", b"value").expect("insert failed"); + trie.insert(b"value1", &[42]).expect("insert failed"); + trie.insert(b"value2", &[24]).expect("insert failed"); + trie.insert(b":code", b"return 42").expect("insert failed"); + for i in 128u8..255u8 { + trie.insert(&[i], &[i]).unwrap(); + } } + + match state_version { + StateVersion::V0 => { + let trie = TrieDBMutBuilderV0::new(&mut mdb, &mut root).build(); + build(trie, &child_info, &sub_root[..]) + }, + StateVersion::V1 => { + let trie = TrieDBMutBuilderV1::new(&mut mdb, &mut root).build(); + build(trie, &child_info, &sub_root[..]) + }, + }; } (mdb, root) } - pub(crate) fn test_trie() -> TrieBackend, BlakeTwo256> { - let (mdb, root) = test_db(); - TrieBackend::new(mdb, root) + pub(crate) fn test_trie( + hashed_value: StateVersion, + cache: Option, + recorder: Option, + ) -> TrieBackend, BlakeTwo256> { + let (mdb, root) = test_db(hashed_value); + + TrieBackendBuilder::new(mdb, root) + .with_optional_cache(cache) + .with_optional_recorder(recorder) + .build() } - #[test] - fn read_from_storage_returns_some() { - assert_eq!(test_trie().storage(b"key").unwrap(), Some(b"value".to_vec())); + parameterized_test!(read_from_storage_returns_some, read_from_storage_returns_some_inner); + fn read_from_storage_returns_some_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + assert_eq!( + test_trie(state_version, cache, recorder).storage(b"key").unwrap(), + Some(b"value".to_vec()) + ); } - #[test] - fn read_from_child_storage_returns_some() { - let test_trie = test_trie(); + parameterized_test!( + read_from_child_storage_returns_some, + read_from_child_storage_returns_some_inner + ); + fn read_from_child_storage_returns_some_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + let test_trie = test_trie(state_version, cache, recorder); assert_eq!( test_trie .child_storage(&ChildInfo::new_default(CHILD_KEY_1), b"value3") .unwrap(), - Some(vec![142u8]), + Some(vec![142u8; 33]), ); // Change cache entry to check that caching is active. test_trie @@ -339,47 +558,81 @@ pub mod tests { ); } - #[test] - fn read_from_storage_returns_none() { - assert_eq!(test_trie().storage(b"non-existing-key").unwrap(), None); + parameterized_test!(read_from_storage_returns_none, read_from_storage_returns_none_inner); + fn read_from_storage_returns_none_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + assert_eq!( + test_trie(state_version, cache, recorder).storage(b"non-existing-key").unwrap(), + None + ); } - #[test] - fn pairs_are_not_empty_on_non_empty_storage() { - assert!(!test_trie().pairs().is_empty()); + parameterized_test!( + pairs_are_not_empty_on_non_empty_storage, + pairs_are_not_empty_on_non_empty_storage_inner + ); + fn pairs_are_not_empty_on_non_empty_storage_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + assert!(!test_trie(state_version, cache, recorder).pairs().is_empty()); } #[test] fn pairs_are_empty_on_empty_storage() { - assert!(TrieBackend::, BlakeTwo256>::new( + assert!(TrieBackendBuilder::, BlakeTwo256>::new( PrefixedMemoryDB::default(), Default::default(), ) + .build() .pairs() .is_empty()); } - #[test] - fn storage_root_is_non_default() { - assert!(test_trie().storage_root(iter::empty()).0 != H256::repeat_byte(0)); - } - - #[test] - fn storage_root_transaction_is_empty() { - assert!(test_trie().storage_root(iter::empty()).1.drain().is_empty()); + parameterized_test!(storage_root_is_non_default, storage_root_is_non_default_inner); + fn storage_root_is_non_default_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + assert!( + test_trie(state_version, cache, recorder) + .storage_root(iter::empty(), state_version) + .0 != H256::repeat_byte(0) + ); } - #[test] - fn storage_root_transaction_is_non_empty() { - let (new_root, mut tx) = - test_trie().storage_root(iter::once((&b"new-key"[..], Some(&b"new-value"[..])))); + parameterized_test!( + storage_root_transaction_is_non_empty, + storage_root_transaction_is_non_empty_inner + ); + fn storage_root_transaction_is_non_empty_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + let (new_root, mut tx) = test_trie(state_version, cache, recorder) + .storage_root(iter::once((&b"new-key"[..], Some(&b"new-value"[..]))), state_version); assert!(!tx.drain().is_empty()); - assert!(new_root != test_trie().storage_root(iter::empty()).0); + assert!( + new_root != + test_trie(state_version, None, None) + .storage_root(iter::empty(), state_version) + .0 + ); } - #[test] - fn prefix_walking_works() { - let trie = test_trie(); + parameterized_test!(prefix_walking_works, prefix_walking_works_inner); + fn prefix_walking_works_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + let trie = test_trie(state_version, cache, recorder); let mut seen = HashSet::new(); trie.for_keys_with_prefix(b"value", |key| { @@ -392,4 +645,567 @@ pub mod tests { expected.insert(b"value2".to_vec()); assert_eq!(seen, expected); } + + parameterized_test!( + keys_with_empty_prefix_returns_all_keys, + keys_with_empty_prefix_returns_all_keys_inner + ); + fn keys_with_empty_prefix_returns_all_keys_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + let (test_db, test_root) = test_db(state_version); + let expected = TrieDBBuilder::new(&test_db, &test_root) + .build() + .iter() + .unwrap() + .map(|d| d.unwrap().0.to_vec()) + .collect::>(); + + let trie = test_trie(state_version, cache, recorder); + let keys = trie.keys(&[]); + + assert_eq!(expected, keys); + } + + parameterized_test!( + proof_is_empty_until_value_is_read, + proof_is_empty_until_value_is_read_inner + ); + fn proof_is_empty_until_value_is_read_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + let trie_backend = test_trie(state_version, cache, recorder); + assert!(TrieBackendBuilder::wrap(&trie_backend) + .with_recorder(Recorder::default()) + .build() + .extract_proof() + .unwrap() + .is_empty()); + } + + parameterized_test!( + proof_is_non_empty_after_value_is_read, + proof_is_non_empty_after_value_is_read_inner + ); + fn proof_is_non_empty_after_value_is_read_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + let trie_backend = test_trie(state_version, cache, recorder); + let backend = TrieBackendBuilder::wrap(&trie_backend) + .with_recorder(Recorder::default()) + .build(); + assert_eq!(backend.storage(b"key").unwrap(), Some(b"value".to_vec())); + assert!(!backend.extract_proof().unwrap().is_empty()); + } + + #[test] + fn proof_is_invalid_when_does_not_contains_root() { + let result = create_proof_check_backend::( + H256::from_low_u64_be(1), + StorageProof::empty(), + ); + assert!(result.is_err()); + } + + parameterized_test!(passes_through_backend_calls, passes_through_backend_calls_inner); + fn passes_through_backend_calls_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + let trie_backend = test_trie(state_version, cache, recorder); + let proving_backend = TrieBackendBuilder::wrap(&trie_backend) + .with_recorder(Recorder::default()) + .build(); + assert_eq!(trie_backend.storage(b"key").unwrap(), proving_backend.storage(b"key").unwrap()); + assert_eq!(trie_backend.pairs(), proving_backend.pairs()); + + let (trie_root, mut trie_mdb) = + trie_backend.storage_root(std::iter::empty(), state_version); + let (proving_root, mut proving_mdb) = + proving_backend.storage_root(std::iter::empty(), state_version); + assert_eq!(trie_root, proving_root); + assert_eq!(trie_mdb.drain(), proving_mdb.drain()); + } + + #[test] + fn proof_recorded_and_checked_top() { + proof_recorded_and_checked_inner(StateVersion::V0); + proof_recorded_and_checked_inner(StateVersion::V1); + } + fn proof_recorded_and_checked_inner(state_version: StateVersion) { + let size_content = 34; // above hashable value threshold. + let value_range = 0..64; + let contents = value_range + .clone() + .map(|i| (vec![i], Some(vec![i; size_content]))) + .collect::>(); + let in_memory = InMemoryBackend::::default(); + let in_memory = in_memory.update(vec![(None, contents)], state_version); + let in_memory_root = in_memory.storage_root(std::iter::empty(), state_version).0; + value_range.clone().for_each(|i| { + assert_eq!(in_memory.storage(&[i]).unwrap().unwrap(), vec![i; size_content]) + }); + + let trie = in_memory.as_trie_backend(); + let trie_root = trie.storage_root(std::iter::empty(), state_version).0; + assert_eq!(in_memory_root, trie_root); + value_range + .clone() + .for_each(|i| assert_eq!(trie.storage(&[i]).unwrap().unwrap(), vec![i; size_content])); + + for cache in [Some(SharedTrieCache::new(CacheSize::Unlimited)), None] { + // Run multiple times to have a different cache conditions. + for i in 0..5 { + if let Some(cache) = &cache { + if i == 2 { + cache.reset_node_cache(); + } else if i == 3 { + cache.reset_value_cache(); + } + } + + let proving = TrieBackendBuilder::wrap(&trie) + .with_recorder(Recorder::default()) + .with_optional_cache(cache.as_ref().map(|c| c.local_cache())) + .build(); + assert_eq!(proving.storage(&[42]).unwrap().unwrap(), vec![42; size_content]); + + let proof = proving.extract_proof().unwrap(); + + let proof_check = + create_proof_check_backend::(in_memory_root.into(), proof) + .unwrap(); + assert_eq!(proof_check.storage(&[42]).unwrap().unwrap(), vec![42; size_content]); + } + } + } + + #[test] + fn proof_record_works_with_iter() { + proof_record_works_with_iter_inner(StateVersion::V0); + proof_record_works_with_iter_inner(StateVersion::V1); + } + fn proof_record_works_with_iter_inner(state_version: StateVersion) { + for cache in [Some(SharedTrieCache::new(CacheSize::Unlimited)), None] { + // Run multiple times to have a different cache conditions. + for i in 0..5 { + if let Some(cache) = &cache { + if i == 2 { + cache.reset_node_cache(); + } else if i == 3 { + cache.reset_value_cache(); + } + } + + let contents = (0..64).map(|i| (vec![i], Some(vec![i]))).collect::>(); + let in_memory = InMemoryBackend::::default(); + let in_memory = in_memory.update(vec![(None, contents)], state_version); + let in_memory_root = in_memory.storage_root(std::iter::empty(), state_version).0; + (0..64) + .for_each(|i| assert_eq!(in_memory.storage(&[i]).unwrap().unwrap(), vec![i])); + + let trie = in_memory.as_trie_backend(); + let trie_root = trie.storage_root(std::iter::empty(), state_version).0; + assert_eq!(in_memory_root, trie_root); + (0..64).for_each(|i| assert_eq!(trie.storage(&[i]).unwrap().unwrap(), vec![i])); + + let proving = TrieBackendBuilder::wrap(&trie) + .with_recorder(Recorder::default()) + .with_optional_cache(cache.as_ref().map(|c| c.local_cache())) + .build(); + + (0..63).for_each(|i| { + assert_eq!(proving.next_storage_key(&[i]).unwrap(), Some(vec![i + 1])) + }); + + let proof = proving.extract_proof().unwrap(); + + let proof_check = + create_proof_check_backend::(in_memory_root.into(), proof) + .unwrap(); + (0..63).for_each(|i| { + assert_eq!(proof_check.next_storage_key(&[i]).unwrap(), Some(vec![i + 1])) + }); + } + } + } + + #[test] + fn proof_recorded_and_checked_with_child() { + proof_recorded_and_checked_with_child_inner(StateVersion::V0); + proof_recorded_and_checked_with_child_inner(StateVersion::V1); + } + fn proof_recorded_and_checked_with_child_inner(state_version: StateVersion) { + let child_info_1 = ChildInfo::new_default(b"sub1"); + let child_info_2 = ChildInfo::new_default(b"sub2"); + let child_info_1 = &child_info_1; + let child_info_2 = &child_info_2; + let contents = vec![ + (None, (0..64).map(|i| (vec![i], Some(vec![i]))).collect::>()), + (Some(child_info_1.clone()), (28..65).map(|i| (vec![i], Some(vec![i]))).collect()), + (Some(child_info_2.clone()), (10..15).map(|i| (vec![i], Some(vec![i]))).collect()), + ]; + let in_memory = new_in_mem::>(); + let in_memory = in_memory.update(contents, state_version); + let child_storage_keys = vec![child_info_1.to_owned(), child_info_2.to_owned()]; + let in_memory_root = in_memory + .full_storage_root( + std::iter::empty(), + child_storage_keys.iter().map(|k| (k, std::iter::empty())), + state_version, + ) + .0; + (0..64).for_each(|i| assert_eq!(in_memory.storage(&[i]).unwrap().unwrap(), vec![i])); + (28..65).for_each(|i| { + assert_eq!(in_memory.child_storage(child_info_1, &[i]).unwrap().unwrap(), vec![i]) + }); + (10..15).for_each(|i| { + assert_eq!(in_memory.child_storage(child_info_2, &[i]).unwrap().unwrap(), vec![i]) + }); + + for cache in [Some(SharedTrieCache::new(CacheSize::Unlimited)), None] { + // Run multiple times to have a different cache conditions. + for i in 0..5 { + eprintln!("Running with cache {}, iteration {}", cache.is_some(), i); + + if let Some(cache) = &cache { + if i == 2 { + cache.reset_node_cache(); + } else if i == 3 { + cache.reset_value_cache(); + } + } + + let trie = in_memory.as_trie_backend(); + let trie_root = trie.storage_root(std::iter::empty(), state_version).0; + assert_eq!(in_memory_root, trie_root); + (0..64).for_each(|i| assert_eq!(trie.storage(&[i]).unwrap().unwrap(), vec![i])); + + let proving = TrieBackendBuilder::wrap(&trie) + .with_recorder(Recorder::default()) + .with_optional_cache(cache.as_ref().map(|c| c.local_cache())) + .build(); + assert_eq!(proving.storage(&[42]).unwrap().unwrap(), vec![42]); + + let proof = proving.extract_proof().unwrap(); + + let proof_check = + create_proof_check_backend::(in_memory_root.into(), proof) + .unwrap(); + assert!(proof_check.storage(&[0]).is_err()); + assert_eq!(proof_check.storage(&[42]).unwrap().unwrap(), vec![42]); + // note that it is include in root because proof close + assert_eq!(proof_check.storage(&[41]).unwrap().unwrap(), vec![41]); + assert_eq!(proof_check.storage(&[64]).unwrap(), None); + + let proving = TrieBackendBuilder::wrap(&trie) + .with_recorder(Recorder::default()) + .with_optional_cache(cache.as_ref().map(|c| c.local_cache())) + .build(); + assert_eq!(proving.child_storage(child_info_1, &[64]), Ok(Some(vec![64]))); + assert_eq!(proving.child_storage(child_info_1, &[25]), Ok(None)); + assert_eq!(proving.child_storage(child_info_2, &[14]), Ok(Some(vec![14]))); + assert_eq!(proving.child_storage(child_info_2, &[25]), Ok(None)); + + let proof = proving.extract_proof().unwrap(); + let proof_check = + create_proof_check_backend::(in_memory_root.into(), proof) + .unwrap(); + assert_eq!( + proof_check.child_storage(child_info_1, &[64]).unwrap().unwrap(), + vec![64] + ); + assert_eq!(proof_check.child_storage(child_info_1, &[25]).unwrap(), None); + + assert_eq!( + proof_check.child_storage(child_info_2, &[14]).unwrap().unwrap(), + vec![14] + ); + assert_eq!(proof_check.child_storage(child_info_2, &[25]).unwrap(), None); + } + } + } + + /// This tests an edge case when recording a child trie access with a cache. + /// + /// The accessed value/node is in the cache, but not the nodes to get to this value. So, + /// the recorder will need to traverse the trie to access these nodes from the backend when the + /// storage proof is generated. + #[test] + fn child_proof_recording_with_edge_cases_works() { + child_proof_recording_with_edge_cases_works_inner(StateVersion::V0); + child_proof_recording_with_edge_cases_works_inner(StateVersion::V1); + } + fn child_proof_recording_with_edge_cases_works_inner(state_version: StateVersion) { + let child_info_1 = ChildInfo::new_default(b"sub1"); + let child_info_1 = &child_info_1; + let contents = vec![ + (None, (0..64).map(|i| (vec![i], Some(vec![i]))).collect::>()), + ( + Some(child_info_1.clone()), + (28..65) + .map(|i| (vec![i], Some(vec![i]))) + // Some big value to ensure we get a new node + .chain(std::iter::once((vec![65], Some(vec![65; 128])))) + .collect(), + ), + ]; + let in_memory = new_in_mem::>(); + let in_memory = in_memory.update(contents, state_version); + let child_storage_keys = vec![child_info_1.to_owned()]; + let in_memory_root = in_memory + .full_storage_root( + std::iter::empty(), + child_storage_keys.iter().map(|k| (k, std::iter::empty())), + state_version, + ) + .0; + + let child_1_root = + in_memory.child_storage_root(child_info_1, std::iter::empty(), state_version).0; + let trie = in_memory.as_trie_backend(); + let nodes = { + let backend = TrieBackendBuilder::wrap(trie).with_recorder(Default::default()).build(); + let value = backend.child_storage(child_info_1, &[65]).unwrap().unwrap(); + let value_hash = BlakeTwo256::hash(&value); + assert_eq!(value, vec![65; 128]); + + let proof = backend.extract_proof().unwrap(); + + let mut nodes = Vec::new(); + for node in proof.into_iter_nodes() { + let hash = BlakeTwo256::hash(&node); + // Only insert the node/value that contains the important data. + if hash != value_hash { + let node = sp_trie::NodeCodec::::decode(&node) + .unwrap() + .to_owned_node::>() + .unwrap(); + + if let Some(data) = node.data() { + if data == &vec![65; 128] { + nodes.push((hash, node)); + } + } + } else if hash == value_hash { + nodes.push((hash, trie_db::node::NodeOwned::Value(node.into(), hash))); + } + } + + nodes + }; + + let cache = SharedTrieCache::::new(CacheSize::Unlimited); + { + let local_cache = cache.local_cache(); + let mut trie_cache = local_cache.as_trie_db_cache(child_1_root); + + // Put the value/node into the cache. + for (hash, node) in nodes { + trie_cache.get_or_insert_node(hash, &mut || Ok(node.clone())).unwrap(); + + if let Some(data) = node.data() { + trie_cache.cache_value_for_key(&[65], (data.clone(), hash).into()); + } + } + } + + { + // Record the access + let proving = TrieBackendBuilder::wrap(&trie) + .with_recorder(Recorder::default()) + .with_cache(cache.local_cache()) + .build(); + assert_eq!(proving.child_storage(child_info_1, &[65]), Ok(Some(vec![65; 128]))); + + let proof = proving.extract_proof().unwrap(); + // And check that we have a correct proof. + let proof_check = + create_proof_check_backend::(in_memory_root.into(), proof).unwrap(); + assert_eq!( + proof_check.child_storage(child_info_1, &[65]).unwrap().unwrap(), + vec![65; 128] + ); + } + } + + parameterized_test!( + storage_proof_encoded_size_estimation_works, + storage_proof_encoded_size_estimation_works_inner + ); + fn storage_proof_encoded_size_estimation_works_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + let has_cache = cache.is_some(); + let trie_backend = test_trie(state_version, cache, recorder); + let keys = &[ + &b"key"[..], + &b"value1"[..], + &b"value2"[..], + &b"doesnotexist"[..], + &b"doesnotexist2"[..], + ]; + + fn check_estimation( + backend: TrieBackend< + impl TrieBackendStorage, + BlakeTwo256, + &'_ LocalTrieCache, + >, + has_cache: bool, + ) { + let estimation = backend.essence.recorder.as_ref().unwrap().estimate_encoded_size(); + let storage_proof = backend.extract_proof().unwrap(); + let storage_proof_size = + storage_proof.into_nodes().into_iter().map(|n| n.encoded_size()).sum::(); + + if has_cache { + // Estimation is not entirely correct when we have values already cached. + assert!(estimation >= storage_proof_size) + } else { + assert_eq!(storage_proof_size, estimation); + } + } + + for n in 0..keys.len() { + let backend = TrieBackendBuilder::wrap(&trie_backend) + .with_recorder(Recorder::default()) + .build(); + + // Read n keys + (0..n).for_each(|i| { + backend.storage(keys[i]).unwrap(); + }); + + // Check the estimation + check_estimation(backend, has_cache); + } + } + + #[test] + fn new_data_is_added_to_the_cache() { + let shared_cache = SharedTrieCache::new(CacheSize::Unlimited); + let new_data = vec![ + (&b"new_data0"[..], Some(&b"0"[..])), + (&b"new_data1"[..], Some(&b"1"[..])), + (&b"new_data2"[..], Some(&b"2"[..])), + (&b"new_data3"[..], Some(&b"3"[..])), + (&b"new_data4"[..], Some(&b"4"[..])), + ]; + + let new_root = { + let trie = test_trie(StateVersion::V1, Some(shared_cache.local_cache()), None); + trie.storage_root(new_data.clone().into_iter(), StateVersion::V1).0 + }; + + let local_cache = shared_cache.local_cache(); + let mut cache = local_cache.as_trie_db_cache(new_root); + // All the data should be cached now + for (key, value) in new_data { + assert_eq!( + value.unwrap(), + cache.lookup_value_for_key(key).unwrap().data().flatten().unwrap().as_ref() + ); + } + } + + /// Test to ensure that recording the same `key` for different tries works as expected. + /// + /// Each trie stores a different value under the same key. The values are big enough to + /// be not inlined with `StateVersion::V1`, this is important to test the expected behavior. The + /// trie recorder is expected to differentiate key access based on the different storage roots + /// of the tries. + #[test] + fn recording_same_key_access_in_different_tries() { + recording_same_key_access_in_different_tries_inner(StateVersion::V0); + recording_same_key_access_in_different_tries_inner(StateVersion::V1); + } + fn recording_same_key_access_in_different_tries_inner(state_version: StateVersion) { + let key = b"test_key".to_vec(); + // Use some big values to ensure that we don't keep them inline + let top_trie_val = vec![1; 1024]; + let child_trie_1_val = vec![2; 1024]; + let child_trie_2_val = vec![3; 1024]; + + let child_info_1 = ChildInfo::new_default(b"sub1"); + let child_info_2 = ChildInfo::new_default(b"sub2"); + let child_info_1 = &child_info_1; + let child_info_2 = &child_info_2; + let contents = vec![ + (None, vec![(key.clone(), Some(top_trie_val.clone()))]), + (Some(child_info_1.clone()), vec![(key.clone(), Some(child_trie_1_val.clone()))]), + (Some(child_info_2.clone()), vec![(key.clone(), Some(child_trie_2_val.clone()))]), + ]; + let in_memory = new_in_mem::>(); + let in_memory = in_memory.update(contents, state_version); + let child_storage_keys = vec![child_info_1.to_owned(), child_info_2.to_owned()]; + let in_memory_root = in_memory + .full_storage_root( + std::iter::empty(), + child_storage_keys.iter().map(|k| (k, std::iter::empty())), + state_version, + ) + .0; + assert_eq!(in_memory.storage(&key).unwrap().unwrap(), top_trie_val); + assert_eq!(in_memory.child_storage(child_info_1, &key).unwrap().unwrap(), child_trie_1_val); + assert_eq!(in_memory.child_storage(child_info_2, &key).unwrap().unwrap(), child_trie_2_val); + + for cache in [Some(SharedTrieCache::new(CacheSize::Unlimited)), None] { + // Run multiple times to have a different cache conditions. + for i in 0..5 { + eprintln!("Running with cache {}, iteration {}", cache.is_some(), i); + + if let Some(cache) = &cache { + if i == 2 { + cache.reset_node_cache(); + } else if i == 3 { + cache.reset_value_cache(); + } + } + + let trie = in_memory.as_trie_backend(); + let trie_root = trie.storage_root(std::iter::empty(), state_version).0; + assert_eq!(in_memory_root, trie_root); + + let proving = TrieBackendBuilder::wrap(&trie) + .with_recorder(Recorder::default()) + .with_optional_cache(cache.as_ref().map(|c| c.local_cache())) + .build(); + assert_eq!(proving.storage(&key).unwrap().unwrap(), top_trie_val); + assert_eq!( + proving.child_storage(child_info_1, &key).unwrap().unwrap(), + child_trie_1_val + ); + assert_eq!( + proving.child_storage(child_info_2, &key).unwrap().unwrap(), + child_trie_2_val + ); + + let proof = proving.extract_proof().unwrap(); + + let proof_check = + create_proof_check_backend::(in_memory_root.into(), proof) + .unwrap(); + + assert_eq!(proof_check.storage(&key).unwrap().unwrap(), top_trie_val); + assert_eq!( + proof_check.child_storage(child_info_1, &key).unwrap().unwrap(), + child_trie_1_val + ); + assert_eq!( + proof_check.child_storage(child_info_2, &key).unwrap().unwrap(), + child_trie_2_val + ); + } + } + } } diff --git a/primitives/state-machine/src/trie_backend_essence.rs b/primitives/state-machine/src/trie_backend_essence.rs index 557a098fbaf79..cdd1bb0bba055 100644 --- a/primitives/state-machine/src/trie_backend_essence.rs +++ b/primitives/state-machine/src/trie_backend_essence.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,27 +18,40 @@ //! Trie-based state machine backend essence used to read values //! from storage. -use crate::{backend::Consolidate, debug, warn, StorageKey, StorageValue}; -use codec::Encode; -use hash_db::{self, Hasher, Prefix}; +use crate::{ + backend::Consolidate, debug, trie_backend::AsLocalTrieCache, warn, StorageKey, StorageValue, +}; +use codec::Codec; +use hash_db::{self, AsHashDB, HashDB, HashDBRef, Hasher, Prefix}; #[cfg(feature = "std")] use parking_lot::RwLock; -use sp_core::storage::ChildInfo; -use sp_std::{boxed::Box, ops::Deref, vec::Vec}; +use sp_core::storage::{ChildInfo, ChildType, StateVersion}; +#[cfg(not(feature = "std"))] +use sp_std::marker::PhantomData; +use sp_std::{boxed::Box, vec::Vec}; +#[cfg(feature = "std")] +use sp_trie::recorder::Recorder; use sp_trie::{ - empty_child_trie_root, read_child_trie_value, read_trie_value, - trie_types::{Layout, TrieDB, TrieError}, - DBValue, KeySpacedDB, MemoryDB, PrefixedMemoryDB, Trie, TrieDBIterator, + child_delta_trie_root, delta_trie_root, empty_child_trie_root, read_child_trie_hash, + read_child_trie_value, read_trie_value, + trie_types::{TrieDBBuilder, TrieError}, + DBValue, KeySpacedDB, NodeCodec, Trie, TrieCache, TrieDBIterator, TrieDBKeyIterator, + TrieRecorder, }; #[cfg(feature = "std")] -use std::collections::HashMap; -#[cfg(feature = "std")] -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; + +// In this module, we only use layout for read operation and empty root, +// where V1 and V0 are equivalent. +use sp_trie::LayoutV1 as Layout; #[cfg(not(feature = "std"))] macro_rules! format { - ($($arg:tt)+) => { - crate::DefaultError + ( $message:expr, $( $arg:expr )* ) => { + { + $( let _ = &$arg; )* + crate::DefaultError + } }; } @@ -52,30 +65,33 @@ pub trait Storage: Send + Sync { /// Local cache for child root. #[cfg(feature = "std")] -pub(crate) struct Cache { - pub child_root: HashMap, Option>>, +pub(crate) struct Cache { + pub child_root: HashMap, Option>, } #[cfg(feature = "std")] -impl Cache { +impl Cache { fn new() -> Self { Cache { child_root: HashMap::new() } } } /// Patricia trie-based pairs storage essence. -pub struct TrieBackendEssence, H: Hasher> { +pub struct TrieBackendEssence, H: Hasher, C> { storage: S, root: H::Out, empty: H::Out, #[cfg(feature = "std")] - pub(crate) cache: Arc>, + pub(crate) cache: Arc>>, + #[cfg(feature = "std")] + pub(crate) trie_node_cache: Option, + #[cfg(feature = "std")] + pub(crate) recorder: Option>, + #[cfg(not(feature = "std"))] + _phantom: PhantomData, } -impl, H: Hasher> TrieBackendEssence -where - H::Out: Encode, -{ +impl, H: Hasher, C> TrieBackendEssence { /// Create new trie-based backend. pub fn new(storage: S, root: H::Out) -> Self { TrieBackendEssence { @@ -84,6 +100,30 @@ where empty: H::hash(&[0u8]), #[cfg(feature = "std")] cache: Arc::new(RwLock::new(Cache::new())), + #[cfg(feature = "std")] + trie_node_cache: None, + #[cfg(feature = "std")] + recorder: None, + #[cfg(not(feature = "std"))] + _phantom: PhantomData, + } + } + + /// Create new trie-based backend. + #[cfg(feature = "std")] + pub fn new_with_cache_and_recorder( + storage: S, + root: H::Out, + cache: Option, + recorder: Option>, + ) -> Self { + TrieBackendEssence { + storage, + root, + empty: H::hash(&[0u8]), + cache: Arc::new(RwLock::new(Cache::new())), + trie_node_cache: cache, + recorder, } } @@ -92,6 +132,11 @@ where &self.storage } + /// Get backend storage mutable reference. + pub fn backend_storage_mut(&mut self) -> &mut S { + &mut self.storage + } + /// Get trie root. pub fn root(&self) -> &H::Out { &self.root @@ -116,7 +161,107 @@ where pub fn into_storage(self) -> S { self.storage } +} + +impl, H: Hasher, C: AsLocalTrieCache> TrieBackendEssence { + /// Call the given closure passing it the recorder and the cache. + /// + /// If the given `storage_root` is `None`, `self.root` will be used. + #[cfg(feature = "std")] + fn with_recorder_and_cache( + &self, + storage_root: Option, + callback: impl FnOnce( + Option<&mut dyn TrieRecorder>, + Option<&mut dyn TrieCache>>, + ) -> R, + ) -> R { + let storage_root = storage_root.unwrap_or_else(|| self.root); + let mut recorder = self.recorder.as_ref().map(|r| r.as_trie_recorder(storage_root)); + let recorder = match recorder.as_mut() { + Some(recorder) => Some(recorder as &mut dyn TrieRecorder), + None => None, + }; + + let mut cache = self + .trie_node_cache + .as_ref() + .map(|c| c.as_local_trie_cache().as_trie_db_cache(storage_root)); + let cache = cache.as_mut().map(|c| c as _); + + callback(recorder, cache) + } + #[cfg(not(feature = "std"))] + fn with_recorder_and_cache( + &self, + _: Option, + callback: impl FnOnce( + Option<&mut dyn TrieRecorder>, + Option<&mut dyn TrieCache>>, + ) -> R, + ) -> R { + callback(None, None) + } + + /// Call the given closure passing it the recorder and the cache. + /// + /// This function must only be used when the operation in `callback` is + /// calculating a `storage_root`. It is expected that `callback` returns + /// the new storage root. This is required to register the changes in the cache + /// for the correct storage root. The given `storage_root` corresponds to the root of the "old" + /// trie. If the value is not given, `self.root` is used. + #[cfg(feature = "std")] + fn with_recorder_and_cache_for_storage_root( + &self, + storage_root: Option, + callback: impl FnOnce( + Option<&mut dyn TrieRecorder>, + Option<&mut dyn TrieCache>>, + ) -> (Option, R), + ) -> R { + let storage_root = storage_root.unwrap_or_else(|| self.root); + let mut recorder = self.recorder.as_ref().map(|r| r.as_trie_recorder(storage_root)); + let recorder = match recorder.as_mut() { + Some(recorder) => Some(recorder as &mut dyn TrieRecorder), + None => None, + }; + + let result = if let Some(local_cache) = self.trie_node_cache.as_ref() { + let mut cache = local_cache.as_local_trie_cache().as_trie_db_mut_cache(); + + let (new_root, r) = callback(recorder, Some(&mut cache)); + + if let Some(new_root) = new_root { + cache.merge_into(local_cache.as_local_trie_cache(), new_root); + } + + r + } else { + callback(recorder, None).1 + }; + + result + } + + #[cfg(not(feature = "std"))] + fn with_recorder_and_cache_for_storage_root( + &self, + _: Option, + callback: impl FnOnce( + Option<&mut dyn TrieRecorder>, + Option<&mut dyn TrieCache>>, + ) -> (Option, R), + ) -> R { + callback(None, None).1 + } +} + +impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> + TrieBackendEssence +where + H::Out: Codec + Ord, +{ /// Return the next key in the trie i.e. the minimum key that is strictly superior to `key` in /// lexicographic order. pub fn next_storage_key(&self, key: &[u8]) -> Result> { @@ -124,22 +269,26 @@ where } /// Access the root of the child storage in its parent trie - fn child_root(&self, child_info: &ChildInfo) -> Result> { + fn child_root(&self, child_info: &ChildInfo) -> Result> { #[cfg(feature = "std")] { if let Some(result) = self.cache.read().child_root.get(child_info.storage_key()) { - return Ok(result.clone()) + return Ok(*result) } } - let result = self.storage(child_info.prefixed_storage_key().as_slice())?; + let result = self.storage(child_info.prefixed_storage_key().as_slice())?.map(|r| { + let mut hash = H::Out::default(); + + // root is fetched from DB, not writable by runtime, so it's always valid. + hash.as_mut().copy_from_slice(&r[..]); + + hash + }); #[cfg(feature = "std")] { - self.cache - .write() - .child_root - .insert(child_info.storage_key().to_vec(), result.clone()); + self.cache.write().child_root.insert(child_info.storage_key().to_vec(), result); } Ok(result) @@ -157,15 +306,7 @@ where None => return Ok(None), }; - let mut hash = H::Out::default(); - - if child_root.len() != hash.as_ref().len() { - return Err(format!("Invalid child storage hash at {:?}", child_info.storage_key())) - } - // note: child_root and hash must be same size, panics otherwise. - hash.as_mut().copy_from_slice(&child_root[..]); - - self.next_storage_key_from_root(&hash, Some(child_info), key) + self.next_storage_key_from_root(&child_root, Some(child_info), key) } /// Return next key from main trie or child trie by providing corresponding root. @@ -175,7 +316,7 @@ where child_info: Option<&ChildInfo>, key: &[u8], ) -> Result> { - let dyn_eph: &dyn hash_db::HashDBRef<_, _>; + let dyn_eph: &dyn HashDBRef<_, _>; let keyspace_eph; if let Some(child_info) = child_info.as_ref() { keyspace_eph = KeySpacedDB::new(self, child_info.keyspace()); @@ -184,39 +325,82 @@ where dyn_eph = self; } - let trie = - TrieDB::::new(dyn_eph, root).map_err(|e| format!("TrieDB creation error: {}", e))?; - let mut iter = trie.iter().map_err(|e| format!("TrieDB iteration error: {}", e))?; + self.with_recorder_and_cache(Some(*root), |recorder, cache| { + let trie = TrieDBBuilder::::new(dyn_eph, root) + .with_optional_recorder(recorder) + .with_optional_cache(cache) + .build(); - // The key just after the one given in input, basically `key++0`. - // Note: We are sure this is the next key if: - // * size of key has no limit (i.e. we can always add 0 to the path), - // * and no keys can be inserted between `key` and `key++0` (this is ensured by sp-io). - let mut potential_next_key = Vec::with_capacity(key.len() + 1); - potential_next_key.extend_from_slice(key); - potential_next_key.push(0); + let mut iter = trie.key_iter().map_err(|e| format!("TrieDB iteration error: {}", e))?; - iter.seek(&potential_next_key) - .map_err(|e| format!("TrieDB iterator seek error: {}", e))?; + // The key just after the one given in input, basically `key++0`. + // Note: We are sure this is the next key if: + // * size of key has no limit (i.e. we can always add 0 to the path), + // * and no keys can be inserted between `key` and `key++0` (this is ensured by sp-io). + let mut potential_next_key = Vec::with_capacity(key.len() + 1); + potential_next_key.extend_from_slice(key); + potential_next_key.push(0); - let next_element = iter.next(); + iter.seek(&potential_next_key) + .map_err(|e| format!("TrieDB iterator seek error: {}", e))?; - let next_key = if let Some(next_element) = next_element { - let (next_key, _) = - next_element.map_err(|e| format!("TrieDB iterator next error: {}", e))?; - Some(next_key) - } else { - None - }; + let next_element = iter.next(); + + let next_key = if let Some(next_element) = next_element { + let next_key = + next_element.map_err(|e| format!("TrieDB iterator next error: {}", e))?; + Some(next_key) + } else { + None + }; + + Ok(next_key) + }) + } - Ok(next_key) + /// Returns the hash value + pub fn storage_hash(&self, key: &[u8]) -> Result> { + let map_e = |e| format!("Trie lookup error: {}", e); + + self.with_recorder_and_cache(None, |recorder, cache| { + TrieDBBuilder::new(self, &self.root) + .with_optional_cache(cache) + .with_optional_recorder(recorder) + .build() + .get_hash(key) + .map_err(map_e) + }) } /// Get the value of storage at given key. pub fn storage(&self, key: &[u8]) -> Result> { let map_e = |e| format!("Trie lookup error: {}", e); - read_trie_value::, _>(self, &self.root, key).map_err(map_e) + self.with_recorder_and_cache(None, |recorder, cache| { + read_trie_value::, _>(self, &self.root, key, recorder, cache).map_err(map_e) + }) + } + + /// Returns the hash value + pub fn child_storage_hash(&self, child_info: &ChildInfo, key: &[u8]) -> Result> { + let child_root = match self.child_root(child_info)? { + Some(root) => root, + None => return Ok(None), + }; + + let map_e = |e| format!("Trie lookup error: {}", e); + + self.with_recorder_and_cache(Some(child_root), |recorder, cache| { + read_child_trie_hash::, _>( + child_info.keyspace(), + self, + &child_root, + key, + recorder, + cache, + ) + .map_err(map_e) + }) } /// Get the value of child storage at given key. @@ -225,14 +409,24 @@ where child_info: &ChildInfo, key: &[u8], ) -> Result> { - let root = self - .child_root(child_info)? - .unwrap_or_else(|| empty_child_trie_root::>().encode()); + let child_root = match self.child_root(child_info)? { + Some(root) => root, + None => return Ok(None), + }; let map_e = |e| format!("Trie lookup error: {}", e); - read_child_trie_value::, _>(child_info.keyspace(), self, &root, key) + self.with_recorder_and_cache(Some(child_root), |recorder, cache| { + read_child_trie_value::, _>( + child_info.keyspace(), + self, + &child_root, + key, + recorder, + cache, + ) .map_err(map_e) + }) } /// Retrieve all entries keys of storage and call `f` for each of those keys. @@ -247,19 +441,13 @@ where f: impl FnMut(Vec, Vec) -> bool, allow_missing_nodes: bool, ) -> Result { - let mut child_root; let root = if let Some(child_info) = child_info.as_ref() { - if let Some(fetched_child_root) = self.child_root(child_info)? { - child_root = H::Out::default(); - // root is fetched from DB, not writable by runtime, so it's always valid. - child_root.as_mut().copy_from_slice(fetched_child_root.as_slice()); - - &child_root - } else { - return Ok(true) + match self.child_root(child_info)? { + Some(child_root) => child_root, + None => return Ok(true), } } else { - &self.root + self.root }; self.trie_iter_inner(&root, prefix, f, child_info, start_at, allow_missing_nodes) @@ -271,34 +459,24 @@ where &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, - mut f: F, + start_at: Option<&[u8]>, + f: F, ) { - let mut child_root = H::Out::default(); let root = if let Some(child_info) = child_info.as_ref() { - let root_vec = match self.child_root(child_info) { - Ok(v) => v.unwrap_or_else(|| empty_child_trie_root::>().encode()), + match self.child_root(child_info) { + Ok(Some(v)) => v, + // If the child trie doesn't exist, there is no need to continue. + Ok(None) => return, Err(e) => { debug!(target: "trie", "Error while iterating child storage: {}", e); return }, - }; - child_root.as_mut().copy_from_slice(&root_vec); - &child_root + } } else { - &self.root + self.root }; - let _ = self.trie_iter_inner( - root, - prefix, - |k, _v| { - f(&k); - true - }, - child_info, - None, - false, - ); + self.trie_iter_key_inner(&root, prefix, f, child_info, start_at) } /// Execute given closure for all keys starting with prefix. @@ -308,41 +486,89 @@ where prefix: &[u8], mut f: impl FnMut(&[u8]), ) { - let root_vec = match self.child_root(child_info) { - Ok(v) => v.unwrap_or_else(|| empty_child_trie_root::>().encode()), + let root = match self.child_root(child_info) { + Ok(Some(v)) => v, + // If the child trie doesn't exist, there is no need to continue. + Ok(None) => return, Err(e) => { debug!(target: "trie", "Error while iterating child storage: {}", e); return }, }; - let mut root = H::Out::default(); - root.as_mut().copy_from_slice(&root_vec); - let _ = self.trie_iter_inner( + + self.trie_iter_key_inner( &root, Some(prefix), - |k, _v| { - f(&k); + |k| { + f(k); true }, Some(child_info), None, - false, - ); + ) } /// Execute given closure for all keys starting with prefix. pub fn for_keys_with_prefix(&self, prefix: &[u8], mut f: F) { - let _ = self.trie_iter_inner( + self.trie_iter_key_inner( &self.root, Some(prefix), - |k, _v| { - f(&k); + |k| { + f(k); true }, None, None, - false, - ); + ) + } + + fn trie_iter_key_inner bool>( + &self, + root: &H::Out, + maybe_prefix: Option<&[u8]>, + mut f: F, + child_info: Option<&ChildInfo>, + maybe_start_at: Option<&[u8]>, + ) { + let mut iter = move |db| -> sp_std::result::Result<(), Box>> { + self.with_recorder_and_cache(Some(*root), |recorder, cache| { + let trie = TrieDBBuilder::::new(db, root) + .with_optional_recorder(recorder) + .with_optional_cache(cache) + .build(); + let prefix = maybe_prefix.unwrap_or(&[]); + let iter = match maybe_start_at { + Some(start_at) => + TrieDBKeyIterator::new_prefixed_then_seek(&trie, prefix, start_at), + None => TrieDBKeyIterator::new_prefixed(&trie, prefix), + }?; + + for x in iter { + let key = x?; + + debug_assert!(maybe_prefix + .as_ref() + .map(|prefix| key.starts_with(prefix)) + .unwrap_or(true)); + + if !f(&key) { + break + } + } + + Ok(()) + }) + }; + + let result = if let Some(child_info) = child_info { + let db = KeySpacedDB::new(self, child_info.keyspace()); + iter(&db) + } else { + iter(self) + }; + if let Err(e) = result { + debug!(target: "trie", "Error while iterating by prefix: {}", e); + } } fn trie_iter_inner, Vec) -> bool>( @@ -355,25 +581,30 @@ where allow_missing_nodes: bool, ) -> Result { let mut iter = move |db| -> sp_std::result::Result>> { - let trie = TrieDB::::new(db, root)?; - - let prefix = prefix.unwrap_or(&[]); - let iterator = if let Some(start_at) = start_at { - TrieDBIterator::new_prefixed_then_seek(&trie, prefix, start_at)? - } else { - TrieDBIterator::new_prefixed(&trie, prefix)? - }; - for x in iterator { - let (key, value) = x?; - - debug_assert!(key.starts_with(prefix)); - - if !f(key, value) { - return Ok(false) + self.with_recorder_and_cache(Some(*root), |recorder, cache| { + let trie = TrieDBBuilder::::new(db, root) + .with_optional_recorder(recorder) + .with_optional_cache(cache) + .build(); + + let prefix = prefix.unwrap_or(&[]); + let iterator = if let Some(start_at) = start_at { + TrieDBIterator::new_prefixed_then_seek(&trie, prefix, start_at)? + } else { + TrieDBIterator::new_prefixed(&trie, prefix)? + }; + for x in iterator { + let (key, value) = x?; + + debug_assert!(key.starts_with(prefix)); + + if !f(key, value) { + return Ok(false) + } } - } - Ok(true) + Ok(true) + }) }; let result = if let Some(child_info) = child_info { @@ -404,6 +635,129 @@ where false, ); } + + /// Returns all `(key, value)` pairs in the trie. + pub fn pairs(&self) -> Vec<(StorageKey, StorageValue)> { + let collect_all = || -> sp_std::result::Result<_, Box>> { + self.with_recorder_and_cache(None, |recorder, cache| { + let trie = TrieDBBuilder::::new(self, self.root()) + .with_optional_cache(cache) + .with_optional_recorder(recorder) + .build(); + + let mut v = Vec::new(); + for x in trie.iter()? { + let (key, value) = x?; + v.push((key.to_vec(), value.to_vec())); + } + + Ok(v) + }) + }; + + match collect_all() { + Ok(v) => v, + Err(e) => { + debug!(target: "trie", "Error extracting trie values: {}", e); + Vec::new() + }, + } + } + + /// Returns all keys that start with the given `prefix`. + pub fn keys(&self, prefix: &[u8]) -> Vec { + let mut keys = Vec::new(); + self.for_keys_with_prefix(prefix, |k| keys.push(k.to_vec())); + keys + } + + /// Return the storage root after applying the given `delta`. + pub fn storage_root<'a>( + &self, + delta: impl Iterator)>, + state_version: StateVersion, + ) -> (H::Out, S::Overlay) { + let mut write_overlay = S::Overlay::default(); + + let root = self.with_recorder_and_cache_for_storage_root(None, |recorder, cache| { + let mut eph = Ephemeral::new(self.backend_storage(), &mut write_overlay); + let res = match state_version { + StateVersion::V0 => delta_trie_root::, _, _, _, _, _>( + &mut eph, self.root, delta, recorder, cache, + ), + StateVersion::V1 => delta_trie_root::, _, _, _, _, _>( + &mut eph, self.root, delta, recorder, cache, + ), + }; + + match res { + Ok(ret) => (Some(ret), ret), + Err(e) => { + warn!(target: "trie", "Failed to write to trie: {}", e); + (None, self.root) + }, + } + }); + + (root, write_overlay) + } + + /// Returns the child storage root for the child trie `child_info` after applying the given + /// `delta`. + pub fn child_storage_root<'a>( + &self, + child_info: &ChildInfo, + delta: impl Iterator)>, + state_version: StateVersion, + ) -> (H::Out, bool, S::Overlay) { + let default_root = match child_info.child_type() { + ChildType::ParentKeyId => empty_child_trie_root::>(), + }; + let mut write_overlay = S::Overlay::default(); + let child_root = match self.child_root(child_info) { + Ok(Some(hash)) => hash, + Ok(None) => default_root, + Err(e) => { + warn!(target: "trie", "Failed to read child storage root: {}", e); + default_root + }, + }; + + let new_child_root = + self.with_recorder_and_cache_for_storage_root(Some(child_root), |recorder, cache| { + let mut eph = Ephemeral::new(self.backend_storage(), &mut write_overlay); + match match state_version { + StateVersion::V0 => + child_delta_trie_root::, _, _, _, _, _, _>( + child_info.keyspace(), + &mut eph, + child_root, + delta, + recorder, + cache, + ), + StateVersion::V1 => + child_delta_trie_root::, _, _, _, _, _, _>( + child_info.keyspace(), + &mut eph, + child_root, + delta, + recorder, + cache, + ), + } { + Ok(ret) => (Some(ret), ret), + Err(e) => { + warn!(target: "trie", "Failed to write to trie: {}", e); + (None, child_root) + }, + } + }); + + let is_default = new_child_root == default_root; + + (new_child_root, is_default, write_overlay) + } } pub(crate) struct Ephemeral<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> { @@ -411,13 +765,13 @@ pub(crate) struct Ephemeral<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> { overlay: &'a mut S::Overlay, } -impl<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> hash_db::AsHashDB +impl<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> AsHashDB for Ephemeral<'a, S, H> { - fn as_hash_db<'b>(&'b self) -> &'b (dyn hash_db::HashDB + 'b) { + fn as_hash_db<'b>(&'b self) -> &'b (dyn HashDB + 'b) { self } - fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn hash_db::HashDB + 'b) { + fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn HashDB + 'b) { self } } @@ -432,100 +786,99 @@ impl<'a, S: 'a + TrieBackendStorage, H: Hasher> hash_db::HashDB for Ephemeral<'a, S, H> { fn get(&self, key: &H::Out, prefix: Prefix) -> Option { - if let Some(val) = hash_db::HashDB::get(self.overlay, key, prefix) { - Some(val) - } else { - match self.storage.get(&key, prefix) { - Ok(x) => x, - Err(e) => { - warn!(target: "trie", "Failed to read from DB: {}", e); - None - }, - } - } + HashDB::get(self.overlay, key, prefix).or_else(|| { + self.storage.get(key, prefix).unwrap_or_else(|e| { + warn!(target: "trie", "Failed to read from DB: {}", e); + None + }) + }) } fn contains(&self, key: &H::Out, prefix: Prefix) -> bool { - hash_db::HashDB::get(self, key, prefix).is_some() + HashDB::get(self, key, prefix).is_some() } fn insert(&mut self, prefix: Prefix, value: &[u8]) -> H::Out { - hash_db::HashDB::insert(self.overlay, prefix, value) + HashDB::insert(self.overlay, prefix, value) } fn emplace(&mut self, key: H::Out, prefix: Prefix, value: DBValue) { - hash_db::HashDB::emplace(self.overlay, key, prefix, value) + HashDB::emplace(self.overlay, key, prefix, value) } fn remove(&mut self, key: &H::Out, prefix: Prefix) { - hash_db::HashDB::remove(self.overlay, key, prefix) + HashDB::remove(self.overlay, key, prefix) } } -impl<'a, S: 'a + TrieBackendStorage, H: Hasher> hash_db::HashDBRef - for Ephemeral<'a, S, H> -{ +impl<'a, S: 'a + TrieBackendStorage, H: Hasher> HashDBRef for Ephemeral<'a, S, H> { fn get(&self, key: &H::Out, prefix: Prefix) -> Option { - hash_db::HashDB::get(self, key, prefix) + HashDB::get(self, key, prefix) } fn contains(&self, key: &H::Out, prefix: Prefix) -> bool { - hash_db::HashDB::contains(self, key, prefix) + HashDB::contains(self, key, prefix) } } /// Key-value pairs storage that is used by trie backend essence. pub trait TrieBackendStorage: Send + Sync { /// Type of in-memory overlay. - type Overlay: hash_db::HashDB + Default + Consolidate; + type Overlay: HashDB + Default + Consolidate; + /// Get the value stored at key. fn get(&self, key: &H::Out, prefix: Prefix) -> Result>; } -// This implementation is used by normal storage trie clients. -#[cfg(feature = "std")] -impl TrieBackendStorage for Arc> { - type Overlay = PrefixedMemoryDB; +impl, H: Hasher> TrieBackendStorage for &T { + type Overlay = T::Overlay; fn get(&self, key: &H::Out, prefix: Prefix) -> Result> { - Storage::::get(self.deref(), key, prefix) + (*self).get(key, prefix) } } -// This implementation is used by test storage trie clients. -impl TrieBackendStorage for PrefixedMemoryDB { - type Overlay = PrefixedMemoryDB; +// This implementation is used by normal storage trie clients. +#[cfg(feature = "std")] +impl TrieBackendStorage for Arc> { + type Overlay = sp_trie::PrefixedMemoryDB; fn get(&self, key: &H::Out, prefix: Prefix) -> Result> { - Ok(hash_db::HashDB::get(self, key, prefix)) + Storage::::get(std::ops::Deref::deref(self), key, prefix) } } -impl TrieBackendStorage for MemoryDB { - type Overlay = MemoryDB; +impl TrieBackendStorage for sp_trie::GenericMemoryDB +where + H: Hasher, + KF: sp_trie::KeyFunction + Send + Sync, +{ + type Overlay = Self; fn get(&self, key: &H::Out, prefix: Prefix) -> Result> { Ok(hash_db::HashDB::get(self, key, prefix)) } } -impl, H: Hasher> hash_db::AsHashDB - for TrieBackendEssence +impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> AsHashDB + for TrieBackendEssence { - fn as_hash_db<'b>(&'b self) -> &'b (dyn hash_db::HashDB + 'b) { + fn as_hash_db<'b>(&'b self) -> &'b (dyn HashDB + 'b) { self } - fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn hash_db::HashDB + 'b) { + fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn HashDB + 'b) { self } } -impl, H: Hasher> hash_db::HashDB for TrieBackendEssence { +impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> HashDB + for TrieBackendEssence +{ fn get(&self, key: &H::Out, prefix: Prefix) -> Option { if *key == self.empty { return Some([0u8].to_vec()) } - match self.storage.get(&key, prefix) { + match self.storage.get(key, prefix) { Ok(x) => x, Err(e) => { warn!(target: "trie", "Failed to read from DB: {}", e); @@ -535,7 +888,7 @@ impl, H: Hasher> hash_db::HashDB for TrieBa } fn contains(&self, key: &H::Out, prefix: Prefix) -> bool { - hash_db::HashDB::get(self, key, prefix).is_some() + HashDB::get(self, key, prefix).is_some() } fn insert(&mut self, _prefix: Prefix, _value: &[u8]) -> H::Out { @@ -551,15 +904,15 @@ impl, H: Hasher> hash_db::HashDB for TrieBa } } -impl, H: Hasher> hash_db::HashDBRef - for TrieBackendEssence +impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> + HashDBRef for TrieBackendEssence { fn get(&self, key: &H::Out, prefix: Prefix) -> Option { - hash_db::HashDB::get(self, key, prefix) + HashDB::get(self, key, prefix) } fn contains(&self, key: &H::Out, prefix: Prefix) -> bool { - hash_db::HashDB::contains(self, key, prefix) + HashDB::contains(self, key, prefix) } } @@ -567,7 +920,10 @@ impl, H: Hasher> hash_db::HashDBRef mod test { use super::*; use sp_core::{Blake2Hasher, H256}; - use sp_trie::{trie_types::TrieDBMut, KeySpacedDBMut, PrefixedMemoryDB, TrieMut}; + use sp_trie::{ + cache::LocalTrieCache, trie_types::TrieDBMutBuilderV1 as TrieDBMutBuilder, KeySpacedDBMut, + PrefixedMemoryDB, TrieMut, + }; #[test] fn next_storage_key_and_next_child_storage_key_work() { @@ -580,7 +936,7 @@ mod test { let mut mdb = PrefixedMemoryDB::::default(); { - let mut trie = TrieDBMut::new(&mut mdb, &mut root_1); + let mut trie = TrieDBMutBuilder::new(&mut mdb, &mut root_1).build(); trie.insert(b"3", &[1]).expect("insert failed"); trie.insert(b"4", &[1]).expect("insert failed"); trie.insert(b"6", &[1]).expect("insert failed"); @@ -589,18 +945,18 @@ mod test { let mut mdb = KeySpacedDBMut::new(&mut mdb, child_info.keyspace()); // reuse of root_1 implicitly assert child trie root is same // as top trie (contents must remain the same). - let mut trie = TrieDBMut::new(&mut mdb, &mut root_1); + let mut trie = TrieDBMutBuilder::new(&mut mdb, &mut root_1).build(); trie.insert(b"3", &[1]).expect("insert failed"); trie.insert(b"4", &[1]).expect("insert failed"); trie.insert(b"6", &[1]).expect("insert failed"); } { - let mut trie = TrieDBMut::new(&mut mdb, &mut root_2); + let mut trie = TrieDBMutBuilder::new(&mut mdb, &mut root_2).build(); trie.insert(child_info.prefixed_storage_key().as_slice(), root_1.as_ref()) .expect("insert failed"); }; - let essence_1 = TrieBackendEssence::new(mdb, root_1); + let essence_1 = TrieBackendEssence::<_, _, LocalTrieCache<_>>::new(mdb, root_1); assert_eq!(essence_1.next_storage_key(b"2"), Ok(Some(b"3".to_vec()))); assert_eq!(essence_1.next_storage_key(b"3"), Ok(Some(b"4".to_vec()))); @@ -608,8 +964,8 @@ mod test { assert_eq!(essence_1.next_storage_key(b"5"), Ok(Some(b"6".to_vec()))); assert_eq!(essence_1.next_storage_key(b"6"), Ok(None)); - let mdb = essence_1.into_storage(); - let essence_2 = TrieBackendEssence::new(mdb, root_2); + let mdb = essence_1.backend_storage().clone(); + let essence_2 = TrieBackendEssence::<_, _, LocalTrieCache<_>>::new(mdb, root_2); assert_eq!(essence_2.next_child_storage_key(child_info, b"2"), Ok(Some(b"3".to_vec()))); assert_eq!(essence_2.next_child_storage_key(child_info, b"3"), Ok(Some(b"4".to_vec()))); diff --git a/primitives/std/Cargo.toml b/primitives/std/Cargo.toml index bf815c1c80c56..87ab1a46d8777 100644 --- a/primitives/std/Cargo.toml +++ b/primitives/std/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "sp-std" -version = "4.0.0-dev" +version = "5.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" - description = "Lowest-abstraction level for the Substrate runtime: just exports useful primitives from std or client/alloc to be used with any code that depends on the runtime." documentation = "https://docs.rs/sp-std" readme = "README.md" diff --git a/primitives/std/src/lib.rs b/primitives/std/src/lib.rs index 3af4d07ac6297..6653c3d7eadec 100644 --- a/primitives/std/src/lib.rs +++ b/primitives/std/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,9 +28,13 @@ doc = "Substrate's runtime standard library as compiled without Rust's standard library." )] +/// Initialize a key-value collection from array. +/// +/// Creates a vector of given pairs and calls `collect` on the iterator from it. +/// Can be used to create a `HashMap`. #[macro_export] macro_rules! map { - ($( $name:expr => $value:expr ),*) => ( + ($( $name:expr => $value:expr ),* $(,)? ) => ( vec![ $( ( $name, $value ) ),* ].into_iter().collect() ) } @@ -95,9 +99,11 @@ impl Writer { /// This should include only things which are in the normal std prelude. pub mod prelude { pub use crate::{ + borrow::ToOwned, boxed::Box, clone::Clone, cmp::{Eq, PartialEq, Reverse}, + iter::IntoIterator, vec::Vec, }; diff --git a/primitives/std/with_std.rs b/primitives/std/with_std.rs index 8a283e8fe333c..b5fa3f85ed702 100644 --- a/primitives/std/with_std.rs +++ b/primitives/std/with_std.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/std/without_std.rs b/primitives/std/without_std.rs index 38c3a8421dacb..7a6d5851dface 100755 --- a/primitives/std/without_std.rs +++ b/primitives/std/without_std.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/storage/Cargo.toml b/primitives/storage/Cargo.toml index 1a05fb9969197..eb166ee3730ff 100644 --- a/primitives/storage/Cargo.toml +++ b/primitives/storage/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "sp-storage" -version = "4.0.0-dev" +version = "7.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" description = "Storage related primitives" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sp-storage/" readme = "README.md" @@ -14,13 +14,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -serde = { version = "1.0.126", optional = true, features = ["derive"] } -impl-serde = { version = "0.3.1", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +impl-serde = { version = "0.4.0", optional = true } ref-cast = "1.0.0" -sp-debug-derive = { version = "3.0.0", path = "../debug-derive" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] -std = [ "sp-std/std", "serde", "impl-serde", "codec/std" ] +std = [ "codec/std", "impl-serde", "serde", "sp-debug-derive/std", "sp-std/std" ] diff --git a/primitives/storage/src/lib.rs b/primitives/storage/src/lib.rs index 45474a44693ab..79c1012196bde 100644 --- a/primitives/storage/src/lib.rs +++ b/primitives/storage/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +use core::fmt::Display; + #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_debug_derive::RuntimeDebug; @@ -47,8 +49,9 @@ impl AsRef<[u8]> for StorageKey { } /// Storage key with read/write tracking information. -#[derive(PartialEq, Eq, RuntimeDebug, Clone, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Hash, PartialOrd, Ord))] +#[derive( + PartialEq, Eq, Ord, PartialOrd, sp_std::hash::Hash, RuntimeDebug, Clone, Encode, Decode, +)] pub struct TrackedStorageKey { pub key: Vec, pub reads: u32, @@ -119,8 +122,7 @@ impl DerefMut for PrefixedStorageKey { } impl PrefixedStorageKey { - /// Create a prefixed storage key from its byte array - /// representation. + /// Create a prefixed storage key from its byte array representation. pub fn new(inner: Vec) -> Self { PrefixedStorageKey(inner) } @@ -130,9 +132,7 @@ impl PrefixedStorageKey { PrefixedStorageKey::ref_cast(inner) } - /// Get inner key, this should - /// only be needed when writing - /// into parent trie to avoid an + /// Get inner key, this should only be needed when writing into parent trie to avoid an /// allocation. pub fn into_inner(self) -> Vec { self.0 @@ -171,10 +171,8 @@ pub struct StorageChild { pub struct Storage { /// Top trie storage data. pub top: StorageMap, - /// Children trie storage data. - /// The key does not including prefix, for the `default` - /// trie kind, so this is exclusively for the `ChildType::ParentKeyId` - /// tries. + /// Children trie storage data. Key does not include prefix, only for the `default` trie kind, + /// of `ChildType::ParentKeyId` type. pub children_default: std::collections::HashMap, StorageChild>, } @@ -194,24 +192,29 @@ pub mod well_known_keys { /// Wasm code of the runtime. /// /// Stored as a raw byte vector. Required by substrate. - pub const CODE: &'static [u8] = b":code"; + pub const CODE: &[u8] = b":code"; /// Number of wasm linear memory pages required for execution of the runtime. /// /// The type of this value is encoded `u64`. - pub const HEAP_PAGES: &'static [u8] = b":heappages"; + pub const HEAP_PAGES: &[u8] = b":heappages"; /// Current extrinsic index (u32) is stored under this key. - pub const EXTRINSIC_INDEX: &'static [u8] = b":extrinsic_index"; - - /// Changes trie configuration is stored under this key. - pub const CHANGES_TRIE_CONFIG: &'static [u8] = b":changes_trie"; + pub const EXTRINSIC_INDEX: &[u8] = b":extrinsic_index"; /// Prefix of child storage keys. - pub const CHILD_STORAGE_KEY_PREFIX: &'static [u8] = b":child_storage:"; + pub const CHILD_STORAGE_KEY_PREFIX: &[u8] = b":child_storage:"; /// Prefix of the default child storage keys in the top trie. - pub const DEFAULT_CHILD_STORAGE_KEY_PREFIX: &'static [u8] = b":child_storage:default:"; + pub const DEFAULT_CHILD_STORAGE_KEY_PREFIX: &[u8] = b":child_storage:default:"; + + /// Whether a key is a default child storage key. + /// + /// This is convenience function which basically checks if the given `key` starts + /// with `DEFAULT_CHILD_STORAGE_KEY_PREFIX` and doesn't do anything apart from that. + pub fn is_default_child_storage_key(key: &[u8]) -> bool { + key.starts_with(DEFAULT_CHILD_STORAGE_KEY_PREFIX) + } /// Whether a key is a child storage key. /// @@ -232,9 +235,12 @@ pub mod well_known_keys { } } +/// Threshold size to start using trie value nodes in state. +pub const TRIE_VALUE_NODE_THRESHOLD: u32 = 33; + /// Information related to a child state. #[derive(Debug, Clone)] -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Hash, PartialOrd, Ord))] +#[cfg_attr(feature = "std", derive(PartialEq, Eq, Hash, PartialOrd, Ord, Encode, Decode))] pub enum ChildInfo { /// This is the one used by default. ParentKeyId(ChildTrieParentKeyId), @@ -356,7 +362,7 @@ impl ChildType { fn do_prefix_key(&self, key: &mut Vec) { let parent_prefix = self.parent_prefix(); let key_len = key.len(); - if parent_prefix.len() > 0 { + if !parent_prefix.is_empty() { key.resize(key_len + parent_prefix.len(), 0); key.copy_within(..key_len, parent_prefix.len()); key[..parent_prefix.len()].copy_from_slice(parent_prefix); @@ -373,16 +379,14 @@ impl ChildType { } /// A child trie of default type. -/// It uses the same default implementation as the top trie, -/// top trie being a child trie with no keyspace and no storage key. -/// Its keyspace is the variable (unprefixed) part of its storage key. -/// It shares its trie nodes backend storage with every other -/// child trie, so its storage key needs to be a unique id -/// that will be use only once. -/// Those unique id also required to be long enough to avoid any -/// unique id to be prefixed by an other unique id. +/// +/// It uses the same default implementation as the top trie, top trie being a child trie with no +/// keyspace and no storage key. Its keyspace is the variable (unprefixed) part of its storage key. +/// It shares its trie nodes backend storage with every other child trie, so its storage key needs +/// to be a unique id that will be use only once. Those unique id also required to be long enough to +/// avoid any unique id to be prefixed by an other unique id. #[derive(Debug, Clone)] -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Hash, PartialOrd, Ord))] +#[cfg_attr(feature = "std", derive(PartialEq, Eq, Hash, PartialOrd, Ord, Encode, Decode))] pub struct ChildTrieParentKeyId { /// Data is the storage key without prefix. data: Vec, @@ -398,6 +402,63 @@ impl ChildTrieParentKeyId { } } +/// Different possible state version. +/// +/// V0 and V1 uses a same trie implementation, but V1 will write external value node in the trie for +/// value with size at least `TRIE_VALUE_NODE_THRESHOLD`. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum StateVersion { + /// Old state version, no value nodes. + V0 = 0, + /// New state version can use value nodes. + V1 = 1, +} + +impl Display for StateVersion { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + StateVersion::V0 => f.write_str("0"), + StateVersion::V1 => f.write_str("1"), + } + } +} + +impl Default for StateVersion { + fn default() -> Self { + StateVersion::V1 + } +} + +impl From for u8 { + fn from(version: StateVersion) -> u8 { + version as u8 + } +} + +impl TryFrom for StateVersion { + type Error = (); + fn try_from(val: u8) -> sp_std::result::Result { + match val { + 0 => Ok(StateVersion::V0), + 1 => Ok(StateVersion::V1), + _ => Err(()), + } + } +} + +impl StateVersion { + /// If defined, values in state of size bigger or equal + /// to this threshold will use a separate trie node. + /// Otherwhise, value will be inlined in branch or leaf + /// node. + pub fn state_value_threshold(&self) -> Option { + match self { + StateVersion::V0 => None, + StateVersion::V1 => Some(TRIE_VALUE_NODE_THRESHOLD), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/primitives/tasks/Cargo.toml b/primitives/tasks/Cargo.toml deleted file mode 100644 index ee503ae9b855f..0000000000000 --- a/primitives/tasks/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "sp-tasks" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2018" -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository = "https://github.com/paritytech/substrate/" -description = "Runtime asynchronous, pure computational tasks" -documentation = "https://docs.rs/sp-tasks" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -log = { version = "0.4.8", optional = true } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -sp-externalities = { version = "0.10.0-dev", optional = true, path = "../externalities" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../io" } -sp-runtime-interface = { version = "4.0.0-dev", default-features = false, path = "../runtime-interface" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } - -[dev-dependencies] -codec = { package = "parity-scale-codec", default-features = false, version = "2.0.0" } - -[features] -default = ["std"] -std = [ - "log", - "sp-core/std", - "sp-externalities", - "sp-io/std", - "sp-runtime-interface/std", - "sp-std/std", -] diff --git a/primitives/tasks/README.md b/primitives/tasks/README.md deleted file mode 100644 index 1235e1bd933d4..0000000000000 --- a/primitives/tasks/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Runtime asynchronous, pure computational tasks. - -License: Apache-2.0 \ No newline at end of file diff --git a/primitives/tasks/src/async_externalities.rs b/primitives/tasks/src/async_externalities.rs deleted file mode 100644 index 975a81af4f53d..0000000000000 --- a/primitives/tasks/src/async_externalities.rs +++ /dev/null @@ -1,201 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -//! Async externalities. - -use sp_core::{ - storage::{ChildInfo, TrackedStorageKey}, - traits::{Externalities, RuntimeSpawn, RuntimeSpawnExt, SpawnNamed, TaskExecutorExt}, -}; -use sp_externalities::{Extensions, ExternalitiesExt as _}; -use std::any::{Any, TypeId}; - -/// Simple state-less externalities for use in async context. -/// -/// Will panic if anything is accessing the storage. -#[derive(Debug)] -pub struct AsyncExternalities { - extensions: Extensions, -} - -/// New Async externalities. -pub fn new_async_externalities( - scheduler: Box, -) -> Result { - let mut res = AsyncExternalities { extensions: Default::default() }; - let mut ext = &mut res as &mut dyn Externalities; - ext.register_extension::(TaskExecutorExt(scheduler.clone())) - .map_err(|_| "Failed to register task executor extension.")?; - - Ok(res) -} - -impl AsyncExternalities { - /// Extend async externalities with the ability to spawn wasm instances. - pub fn with_runtime_spawn( - mut self, - runtime_ext: Box, - ) -> Result { - let mut ext = &mut self as &mut dyn Externalities; - ext.register_extension::(RuntimeSpawnExt(runtime_ext)) - .map_err(|_| "Failed to register task executor extension.")?; - - Ok(self) - } -} - -type StorageKey = Vec; - -type StorageValue = Vec; - -impl Externalities for AsyncExternalities { - fn set_offchain_storage(&mut self, _key: &[u8], _value: Option<&[u8]>) { - panic!("`set_offchain_storage`: should not be used in async externalities!") - } - - fn storage(&self, _key: &[u8]) -> Option { - panic!("`storage`: should not be used in async externalities!") - } - - fn storage_hash(&self, _key: &[u8]) -> Option> { - panic!("`storage_hash`: should not be used in async externalities!") - } - - fn child_storage(&self, _child_info: &ChildInfo, _key: &[u8]) -> Option { - panic!("`child_storage`: should not be used in async externalities!") - } - - fn child_storage_hash(&self, _child_info: &ChildInfo, _key: &[u8]) -> Option> { - panic!("`child_storage_hash`: should not be used in async externalities!") - } - - fn next_storage_key(&self, _key: &[u8]) -> Option { - panic!("`next_storage_key`: should not be used in async externalities!") - } - - fn next_child_storage_key(&self, _child_info: &ChildInfo, _key: &[u8]) -> Option { - panic!("`next_child_storage_key`: should not be used in async externalities!") - } - - fn place_storage(&mut self, _key: StorageKey, _maybe_value: Option) { - panic!("`place_storage`: should not be used in async externalities!") - } - - fn place_child_storage( - &mut self, - _child_info: &ChildInfo, - _key: StorageKey, - _value: Option, - ) { - panic!("`place_child_storage`: should not be used in async externalities!") - } - - fn kill_child_storage(&mut self, _child_info: &ChildInfo, _limit: Option) -> (bool, u32) { - panic!("`kill_child_storage`: should not be used in async externalities!") - } - - fn clear_prefix(&mut self, _prefix: &[u8], _limit: Option) -> (bool, u32) { - panic!("`clear_prefix`: should not be used in async externalities!") - } - - fn clear_child_prefix( - &mut self, - _child_info: &ChildInfo, - _prefix: &[u8], - _limit: Option, - ) -> (bool, u32) { - panic!("`clear_child_prefix`: should not be used in async externalities!") - } - - fn storage_append(&mut self, _key: Vec, _value: Vec) { - panic!("`storage_append`: should not be used in async externalities!") - } - - fn storage_root(&mut self) -> Vec { - panic!("`storage_root`: should not be used in async externalities!") - } - - fn child_storage_root(&mut self, _child_info: &ChildInfo) -> Vec { - panic!("`child_storage_root`: should not be used in async externalities!") - } - - fn storage_changes_root(&mut self, _parent: &[u8]) -> Result>, ()> { - panic!("`storage_changes_root`: should not be used in async externalities!") - } - - fn storage_start_transaction(&mut self) { - unimplemented!("Transactions are not supported by AsyncExternalities"); - } - - fn storage_rollback_transaction(&mut self) -> Result<(), ()> { - unimplemented!("Transactions are not supported by AsyncExternalities"); - } - - fn storage_commit_transaction(&mut self) -> Result<(), ()> { - unimplemented!("Transactions are not supported by AsyncExternalities"); - } - - fn wipe(&mut self) {} - - fn commit(&mut self) {} - - fn read_write_count(&self) -> (u32, u32, u32, u32) { - unimplemented!("read_write_count is not supported in AsyncExternalities") - } - - fn reset_read_write_count(&mut self) { - unimplemented!("reset_read_write_count is not supported in AsyncExternalities") - } - - fn get_whitelist(&self) -> Vec { - unimplemented!("get_whitelist is not supported in AsyncExternalities") - } - - fn set_whitelist(&mut self, _: Vec) { - unimplemented!("set_whitelist is not supported in AsyncExternalities") - } - - fn get_read_and_written_keys(&self) -> Vec<(Vec, u32, u32, bool)> { - unimplemented!("get_read_and_written_keys is not supported in AsyncExternalities") - } -} - -impl sp_externalities::ExtensionStore for AsyncExternalities { - fn extension_by_type_id(&mut self, type_id: TypeId) -> Option<&mut dyn Any> { - self.extensions.get_mut(type_id) - } - - fn register_extension_with_type_id( - &mut self, - type_id: TypeId, - extension: Box, - ) -> Result<(), sp_externalities::Error> { - self.extensions.register_with_type_id(type_id, extension) - } - - fn deregister_extension_by_type_id( - &mut self, - type_id: TypeId, - ) -> Result<(), sp_externalities::Error> { - if self.extensions.deregister(type_id) { - Ok(()) - } else { - Err(sp_externalities::Error::ExtensionIsNotRegistered(type_id)) - } - } -} diff --git a/primitives/tasks/src/lib.rs b/primitives/tasks/src/lib.rs deleted file mode 100644 index e9c80ae5ff4c8..0000000000000 --- a/primitives/tasks/src/lib.rs +++ /dev/null @@ -1,256 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 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. - -//! Runtime tasks. -//! -//! Contains runtime-usable functions for spawning parallel purely computational tasks. -//! -//! NOTE: This is experimental API. -//! NOTE: When using in actual runtime, make sure you don't produce unbounded parallelism. -//! So this is bad example to use it: -//! ```rust -//! fn my_parallel_computator(data: Vec) -> Vec { -//! unimplemented!() -//! } -//! fn test(dynamic_variable: i32) { -//! for _ in 0..dynamic_variable { sp_tasks::spawn(my_parallel_computator, vec![]); } -//! } -//! ``` -//! -//! While this is a good example: -//! ```rust -//! use codec::Encode; -//! static STATIC_VARIABLE: i32 = 4; -//! -//! fn my_parallel_computator(data: Vec) -> Vec { -//! unimplemented!() -//! } -//! -//! fn test(computation_payload: Vec) { -//! let parallel_tasks = (0..STATIC_VARIABLE).map(|idx| -//! sp_tasks::spawn(my_parallel_computator, computation_payload.chunks(10).nth(idx as _).encode()) -//! ); -//! } -//! ``` -//! -//! When allowing unbounded parallelism, malicious transactions can exploit it and partition -//! network consensus based on how much resources nodes have. - -#![cfg_attr(not(feature = "std"), no_std)] - -#[cfg(feature = "std")] -mod async_externalities; - -#[cfg(feature = "std")] -pub use async_externalities::{new_async_externalities, AsyncExternalities}; - -#[cfg(feature = "std")] -mod inner { - use sp_core::traits::TaskExecutorExt; - use sp_externalities::ExternalitiesExt as _; - use std::{panic::AssertUnwindSafe, sync::mpsc}; - - /// Task handle (wasm). - /// - /// This can be `join`-ed to get (blocking) the result of - /// the spawned task execution. - #[must_use] - pub struct DataJoinHandle { - receiver: mpsc::Receiver>, - } - - impl DataJoinHandle { - /// Join handle returned by `spawn` function - pub fn join(self) -> Vec { - self.receiver - .recv() - .expect("Spawned runtime task terminated before sending result.") - } - } - - /// Spawn new runtime task (native). - pub fn spawn(entry_point: fn(Vec) -> Vec, data: Vec) -> DataJoinHandle { - let scheduler = sp_externalities::with_externalities(|mut ext| { - ext.extension::() - .expect("No task executor associated with the current context!") - .clone() - }) - .expect("Spawn called outside of externalities context!"); - - let (sender, receiver) = mpsc::channel(); - let extra_scheduler = scheduler.clone(); - scheduler.spawn( - "parallel-runtime-spawn", - Box::pin(async move { - let result = match crate::new_async_externalities(extra_scheduler) { - Ok(mut ext) => { - let mut ext = AssertUnwindSafe(&mut ext); - match std::panic::catch_unwind(move || { - sp_externalities::set_and_run_with_externalities( - &mut **ext, - move || entry_point(data), - ) - }) { - Ok(result) => result, - Err(panic) => { - log::error!( - target: "runtime", - "Spawned task panicked: {:?}", - panic, - ); - - // This will drop sender without sending anything. - return - }, - } - }, - Err(e) => { - log::error!( - target: "runtime", - "Unable to run async task: {}", - e, - ); - - return - }, - }; - - let _ = sender.send(result); - }), - ); - - DataJoinHandle { receiver } - } -} - -#[cfg(not(feature = "std"))] -mod inner { - use core::mem; - use sp_std::prelude::*; - - /// Dispatch wrapper for wasm blob. - /// - /// Serves as trampoline to call any rust function with (Vec) -> Vec compiled - /// into the runtime. - /// - /// Function item should be provided with `func_ref`. Argument for the call - /// will be generated from bytes at `payload_ptr` with `payload_len`. - /// - /// NOTE: Since this dynamic dispatch function and the invoked function are compiled with - /// the same compiler, there should be no problem with ABI incompatibility. - extern "C" fn dispatch_wrapper( - func_ref: *const u8, - payload_ptr: *mut u8, - payload_len: u32, - ) -> u64 { - let payload_len = payload_len as usize; - let output = unsafe { - let payload = Vec::from_raw_parts(payload_ptr, payload_len, payload_len); - let ptr: fn(Vec) -> Vec = mem::transmute(func_ref); - (ptr)(payload) - }; - sp_runtime_interface::pack_ptr_and_len(output.as_ptr() as usize as _, output.len() as _) - } - - /// Spawn new runtime task (wasm). - pub fn spawn(entry_point: fn(Vec) -> Vec, payload: Vec) -> DataJoinHandle { - let func_ptr: usize = unsafe { mem::transmute(entry_point) }; - - let handle = - sp_io::runtime_tasks::spawn(dispatch_wrapper as usize as _, func_ptr as u32, payload); - DataJoinHandle { handle } - } - - /// Task handle (wasm). - /// - /// This can be `join`-ed to get (blocking) the result of - /// the spawned task execution. - #[must_use] - pub struct DataJoinHandle { - handle: u64, - } - - impl DataJoinHandle { - /// Join handle returned by `spawn` function - pub fn join(self) -> Vec { - sp_io::runtime_tasks::join(self.handle) - } - } -} - -pub use inner::{spawn, DataJoinHandle}; - -#[cfg(test)] -mod tests { - - use super::*; - - fn async_runner(mut data: Vec) -> Vec { - data.sort(); - data - } - - fn async_panicker(_data: Vec) -> Vec { - panic!("panic in async panicker!") - } - - #[test] - fn basic() { - sp_io::TestExternalities::default().execute_with(|| { - let a1 = spawn(async_runner, vec![5, 2, 1]).join(); - assert_eq!(a1, vec![1, 2, 5]); - }) - } - - #[test] - fn panicking() { - let res = sp_io::TestExternalities::default().execute_with_safe(|| { - spawn(async_panicker, vec![5, 2, 1]).join(); - }); - - assert!(res.unwrap_err().contains("Closure panicked")); - } - - #[test] - fn many_joins() { - sp_io::TestExternalities::default() - .execute_with_safe(|| { - // converges to 1 only after 1000+ steps - let mut running_val = 9780657630u64; - let mut data = vec![]; - let handles = (0..1024) - .map(|_| { - running_val = if running_val % 2 == 0 { - running_val / 2 - } else { - 3 * running_val + 1 - }; - data.push(running_val as u8); - (spawn(async_runner, data.clone()), data.clone()) - }) - .collect::>(); - - for (handle, mut data) in handles { - let result = handle.join(); - data.sort(); - - assert_eq!(result, data); - } - }) - .expect("Failed to run with externalities"); - } -} diff --git a/primitives/test-primitives/Cargo.toml b/primitives/test-primitives/Cargo.toml index 5aed5d679dd49..1d12ed74ee6ce 100644 --- a/primitives/test-primitives/Cargo.toml +++ b/primitives/test-primitives/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-test-primitives" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" publish = false @@ -12,18 +12,20 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../application-crypto" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -serde = { version = "1.0.126", optional = true, features = ["derive"] } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } -parity-util-mem = { version = "0.10.0", default-features = false, features = ["primitive-types"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } [features] default = [ "std", ] std = [ - "sp-application-crypto/std", + "codec/std", "serde", + "sp-application-crypto/std", + "sp-core/std", + "sp-runtime/std", ] diff --git a/primitives/test-primitives/src/lib.rs b/primitives/test-primitives/src/lib.rs index d988160b1dc7b..9779fe2393c35 100644 --- a/primitives/test-primitives/src/lib.rs +++ b/primitives/test-primitives/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,6 @@ use sp_runtime::traits::{BlakeTwo256, Extrinsic as ExtrinsicT, Verify}; /// Extrinsic for test-runtime. #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(parity_util_mem::MallocSizeOf))] pub enum Extrinsic { IncludeData(Vec), StorageChange(Vec, Option>), @@ -73,15 +72,10 @@ pub type BlockNumber = u64; /// Index of a transaction. pub type Index = u64; /// The item of a block digest. -pub type DigestItem = sp_runtime::generic::DigestItem; +pub type DigestItem = sp_runtime::generic::DigestItem; /// The digest of a block. -pub type Digest = sp_runtime::generic::Digest; +pub type Digest = sp_runtime::generic::Digest; /// A test block. pub type Block = sp_runtime::generic::Block; /// A test block's header. pub type Header = sp_runtime::generic::Header; - -/// Changes trie configuration (optionally) used in tests. -pub fn changes_trie_config() -> sp_core::ChangesTrieConfiguration { - sp_core::ChangesTrieConfiguration { digest_interval: 4, digest_levels: 2 } -} diff --git a/primitives/timestamp/Cargo.toml b/primitives/timestamp/Cargo.toml index 60daf9642df6b..72266a48b0d6c 100644 --- a/primitives/timestamp/Cargo.toml +++ b/primitives/timestamp/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-timestamp" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate core types and inherents for timestamps." readme = "README.md" @@ -13,26 +13,26 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = { version = "0.1.57", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +futures-timer = { version = "3.0.2", optional = true } +log = { version = "0.4.17", optional = true } +thiserror = { version = "1.0.30", optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } -thiserror = { version = "1.0.21", optional = true } -log = { version = "0.4.8", optional = true } -futures-timer = { version = "3.0.2", optional = true } -async-trait = { version = "0.1.50", optional = true } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] std = [ - "sp-api/std", - "sp-std/std", - "sp-runtime/std", + "async-trait", "codec/std", + "futures-timer", + "log", + "sp-api/std", "sp-inherents/std", + "sp-runtime/std", + "sp-std/std", "thiserror", - "log", - "futures-timer", - "async-trait", ] diff --git a/primitives/timestamp/src/lib.rs b/primitives/timestamp/src/lib.rs index 02a579497b527..14b06779340f2 100644 --- a/primitives/timestamp/src/lib.rs +++ b/primitives/timestamp/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,14 +42,31 @@ impl Timestamp { } /// Returns `self` as [`Duration`]. - pub fn as_duration(self) -> Duration { + pub const fn as_duration(self) -> Duration { Duration::from_millis(self.0) } + /// Returns `self` as a `u64` representing the elapsed time since the UNIX_EPOCH in + /// milliseconds. + pub const fn as_millis(&self) -> u64 { + self.0 + } + /// Checked subtraction that returns `None` on an underflow. pub fn checked_sub(self, other: Self) -> Option { self.0.checked_sub(other.0).map(Self) } + + /// The current timestamp using the system time. + #[cfg(feature = "std")] + pub fn current() -> Self { + use std::time::SystemTime; + + let now = SystemTime::now(); + now.duration_since(SystemTime::UNIX_EPOCH) + .expect("Current time is always after unix epoch; qed") + .into() + } } impl sp_std::ops::Deref for Timestamp { @@ -138,9 +155,9 @@ impl IsFatalError for InherentError { impl InherentError { /// Try to create an instance ouf of the given identifier and data. #[cfg(feature = "std")] - pub fn try_from(id: &InherentIdentifier, data: &[u8]) -> Option { + pub fn try_from(id: &InherentIdentifier, mut data: &[u8]) -> Option { if id == &INHERENT_IDENTIFIER { - ::decode(&mut &data[..]).ok() + ::decode(&mut data).ok() } else { None } @@ -159,18 +176,6 @@ impl TimestampInherentData for InherentData { } } -/// The current timestamp using the system time. -/// -/// This timestamp is the time since the UNIX epoch. -#[cfg(feature = "std")] -fn current_timestamp() -> std::time::Duration { - use std::time::SystemTime; - - let now = SystemTime::now(); - now.duration_since(SystemTime::UNIX_EPOCH) - .expect("Current time is always after unix epoch; qed") -} - /// Provide duration since unix epoch in millisecond for timestamp inherent. #[cfg(feature = "std")] pub struct InherentDataProvider { @@ -184,7 +189,7 @@ impl InherentDataProvider { pub fn from_system_time() -> Self { Self { max_drift: std::time::Duration::from_secs(60).into(), - timestamp: current_timestamp().into(), + timestamp: Timestamp::current(), } } @@ -223,11 +228,11 @@ impl sp_std::ops::Deref for InherentDataProvider { #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - fn provide_inherent_data( + async fn provide_inherent_data( &self, inherent_data: &mut InherentData, ) -> Result<(), sp_inherents::Error> { - inherent_data.put_data(INHERENT_IDENTIFIER, &InherentType::from(self.timestamp)) + inherent_data.put_data(INHERENT_IDENTIFIER, &self.timestamp) } async fn try_handle_error( diff --git a/primitives/tracing/Cargo.toml b/primitives/tracing/Cargo.toml index 3be09dcd576df..794785085c8b4 100644 --- a/primitives/tracing/Cargo.toml +++ b/primitives/tracing/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-tracing" -version = "4.0.0-dev" +version = "6.0.0" license = "Apache-2.0" authors = ["Parity Technologies "] -edition = "2018" -homepage = "https://substrate.dev" +edition = "2021" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Instrumentation primitives and macros for Substrate." readme = "README.md" @@ -18,21 +18,15 @@ features = ["with-tracing"] targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [dependencies] -sp-std = { version = "4.0.0-dev", path = "../std", default-features = false } -codec = { version = "2.0.0", package = "parity-scale-codec", default-features = false, features = [ +sp-std = { version = "5.0.0", path = "../std", default-features = false } +codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = [ "derive", ] } -tracing = { version = "0.1.25", default-features = false } -tracing-core = { version = "0.1.17", default-features = false } -log = { version = "0.4.8", optional = true } -tracing-subscriber = { version = "0.2.19", optional = true, features = [ +tracing = { version = "0.1.29", default-features = false } +tracing-core = { version = "0.1.28", default-features = false } +tracing-subscriber = { version = "0.2.25", optional = true, features = [ "tracing-log", ] } -parking_lot = { version = "0.10.0", optional = true } -erased-serde = { version = "0.3.9", optional = true } -serde = { version = "1.0.126", optional = true } -serde_json = { version = "1.0.68", optional = true } -slog = { version = "2.5.2", features = ["nested-values"], optional = true } [features] default = ["std"] @@ -43,11 +37,5 @@ std = [ "tracing-core/std", "codec/std", "sp-std/std", - "log", "tracing-subscriber", - "parking_lot", - "erased-serde", - "serde", - "serde_json", - "slog", ] diff --git a/primitives/tracing/src/lib.rs b/primitives/tracing/src/lib.rs index 9522e6df633ac..1efae226a546f 100644 --- a/primitives/tracing/src/lib.rs +++ b/primitives/tracing/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -108,6 +108,7 @@ pub use crate::types::{WASM_NAME_KEY, WASM_TARGET_KEY, WASM_TRACE_IDENTIFIER}; mod types; /// Try to init a simple tracing subscriber with log compatibility layer. +/// /// Ignores any error. Useful for testing. #[cfg(feature = "std")] pub fn try_init_simple() { @@ -117,6 +118,22 @@ pub fn try_init_simple() { .try_init(); } +/// Init a tracing subscriber for logging in tests. +/// +/// Be aware that this enables `TRACE` by default. It also ignores any error +/// while setting up the logger. +/// +/// The logs are not shown by default, logs are only shown when the test fails +/// or if [`nocapture`](https://doc.rust-lang.org/cargo/commands/cargo-test.html#display-options) +/// is being used. +#[cfg(feature = "std")] +pub fn init_for_tests() { + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + .with_test_writer() + .try_init(); +} + /// Runs given code within a tracing span, measuring it's execution time. /// /// If tracing is not enabled, the code is still executed. Pass in level and name or @@ -126,20 +143,20 @@ pub fn try_init_simple() { /// /// ``` /// sp_tracing::within_span! { -/// sp_tracing::Level::TRACE, +/// sp_tracing::Level::TRACE, /// "test-span"; /// 1 + 1; /// // some other complex code /// } /// /// sp_tracing::within_span! { -/// sp_tracing::span!(sp_tracing::Level::WARN, "warn-span", you_can_pass="any params"); +/// sp_tracing::span!(sp_tracing::Level::WARN, "warn-span", you_can_pass="any params"); /// 1 + 1; /// // some other complex code /// } /// /// sp_tracing::within_span! { -/// sp_tracing::debug_span!("debug-span", you_can_pass="any params"); +/// sp_tracing::debug_span!("debug-span", you_can_pass="any params"); /// 1 + 1; /// // some other complex code /// } diff --git a/primitives/tracing/src/types.rs b/primitives/tracing/src/types.rs index 377bd0f42c6eb..e7d5abfb27d9a 100644 --- a/primitives/tracing/src/types.rs +++ b/primitives/tracing/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -639,7 +639,7 @@ mod std_features { tracing::span::Span::child_of( a.parent_id.map(tracing_core::span::Id::from_u64), - &metadata, + metadata, &tracing::valueset! { metadata.fields(), target, name, file, line, module_path, ?params }, ) } @@ -658,7 +658,7 @@ mod std_features { tracing_core::Event::child_of( self.parent_id.map(tracing_core::span::Id::from_u64), - &metadata, + metadata, &tracing::valueset! { metadata.fields(), target, name, file, line, module_path, ?params }, ) } diff --git a/primitives/transaction-pool/Cargo.toml b/primitives/transaction-pool/Cargo.toml index 3f77014ac53b0..63b34a10cd09f 100644 --- a/primitives/transaction-pool/Cargo.toml +++ b/primitives/transaction-pool/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-transaction-pool" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Transaction pool runtime facing API." documentation = "https://docs.rs/sp-transaction-pool" @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } [features] default = [ "std" ] diff --git a/primitives/transaction-pool/src/lib.rs b/primitives/transaction-pool/src/lib.rs index 3c71149255ce0..143958f06d165 100644 --- a/primitives/transaction-pool/src/lib.rs +++ b/primitives/transaction-pool/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/transaction-pool/src/runtime_api.rs b/primitives/transaction-pool/src/runtime_api.rs index be631ee03b9d7..87a0c82e9133e 100644 --- a/primitives/transaction-pool/src/runtime_api.rs +++ b/primitives/transaction-pool/src/runtime_api.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/transaction-storage-proof/Cargo.toml b/primitives/transaction-storage-proof/Cargo.toml index 8a41105b20b74..ea6419dfaa1ba 100644 --- a/primitives/transaction-storage-proof/Cargo.toml +++ b/primitives/transaction-storage-proof/Cargo.toml @@ -3,9 +3,9 @@ name = "sp-transaction-storage-proof" version = "4.0.0-dev" authors = ["Parity Technologies "] description = "Transaction storage proof primitives" -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,26 +13,26 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = { version = "0.1.57", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", optional = true } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-core = { version = "7.0.0", optional = true, path = "../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -sp-trie = { version = "4.0.0-dev", optional = true, path = "../trie" } -sp-core = { version = "4.0.0-dev", path = "../core", optional = true } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -log = { version = "0.4.8", optional = true } -async-trait = { version = "0.1.50", optional = true } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-trie = { version = "7.0.0", optional = true, path = "../trie" } [features] default = [ "std" ] std = [ + "async-trait", "codec/std", + "log", "scale-info/std", - "sp-std/std", + "sp-core", "sp-inherents/std", "sp-runtime/std", + "sp-std/std", "sp-trie/std", - "sp-core", - "log", - "async-trait", ] diff --git a/primitives/transaction-storage-proof/src/lib.rs b/primitives/transaction-storage-proof/src/lib.rs index 4b01a8d45d454..43928c83f21ed 100644 --- a/primitives/transaction-storage-proof/src/lib.rs +++ b/primitives/transaction-storage-proof/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -67,7 +67,7 @@ pub trait TransactionStorageProofInherentData { impl TransactionStorageProofInherentData for InherentData { fn storage_proof(&self) -> Result, Error> { - Ok(self.get_data(&INHERENT_IDENTIFIER)?) + self.get_data(&INHERENT_IDENTIFIER) } } @@ -87,7 +87,7 @@ impl InherentDataProvider { #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { if let Some(proof) = &self.proof { inherent_data.put_data(INHERENT_IDENTIFIER, proof) } else { @@ -98,13 +98,13 @@ impl sp_inherents::InherentDataProvider for InherentDataProvider { async fn try_handle_error( &self, identifier: &InherentIdentifier, - error: &[u8], + mut error: &[u8], ) -> Option> { if *identifier != INHERENT_IDENTIFIER { return None } - let error = InherentError::decode(&mut &error[..]).ok()?; + let error = InherentError::decode(&mut error).ok()?; Some(Err(Error::Application(Box::from(format!("{:?}", error))))) } @@ -143,7 +143,7 @@ pub mod registration { use sp_trie::TrieMut; type Hasher = sp_core::Blake2Hasher; - type TrieLayout = sp_trie::Layout; + type TrieLayout = sp_trie::LayoutV1; /// Create a new inherent data provider instance for a given parent block hash. pub fn new_data_provider( @@ -154,7 +154,7 @@ pub mod registration { B: BlockT, C: IndexedBody, { - let parent_number = client.number(parent.clone())?.unwrap_or(Zero::zero()); + let parent_number = client.number(*parent)?.unwrap_or(Zero::zero()); let number = parent_number .saturating_add(One::one()) .saturating_sub(DEFAULT_STORAGE_PERIOD.into()); @@ -200,7 +200,8 @@ pub mod registration { let mut transaction_root = sp_trie::empty_trie_root::(); { let mut trie = - sp_trie::TrieDBMut::::new(&mut db, &mut transaction_root); + sp_trie::TrieDBMutBuilder::::new(&mut db, &mut transaction_root) + .build(); let chunks = transaction.chunks(CHUNK_SIZE).map(|c| c.to_vec()); for (index, chunk) in chunks.enumerate() { let index = encode_index(index as u32); @@ -214,10 +215,10 @@ pub mod registration { trie.commit(); } if target_chunk.is_some() && target_root == Default::default() { - target_root = transaction_root.clone(); + target_root = transaction_root; chunk_proof = sp_trie::generate_trie_proof::( &db, - transaction_root.clone(), + transaction_root, &[target_chunk_key.clone()], ) .map_err(|e| Error::Application(Box::new(e)))?; diff --git a/primitives/trie/Cargo.toml b/primitives/trie/Cargo.toml index 5a2de4f16f9a4..68a5fb17c3c34 100644 --- a/primitives/trie/Cargo.toml +++ b/primitives/trie/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "sp-trie" -version = "4.0.0-dev" +version = "7.0.0" authors = ["Parity Technologies "] description = "Patricia trie stuff using a parity-scale-codec node format" repository = "https://github.com/paritytech/substrate/" license = "Apache-2.0" -edition = "2018" -homepage = "https://substrate.dev" +edition = "2021" +homepage = "https://substrate.io" documentation = "https://docs.rs/sp-trie" readme = "README.md" @@ -18,32 +18,47 @@ name = "bench" harness = false [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } +ahash = { version = "0.7.6", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +hashbrown = { version = "0.12.3", optional = true } hash-db = { version = "0.15.2", default-features = false } -trie-db = { version = "0.22.6", default-features = false } -trie-root = { version = "0.16.0", default-features = false } -memory-db = { version = "0.27.0", default-features = false } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } +lazy_static = { version = "1.4.0", optional = true } +lru = { version = "0.8.1", optional = true } +memory-db = { version = "0.31.0", default-features = false } +nohash-hasher = { version = "0.2.0", optional = true } +parking_lot = { version = "0.12.1", optional = true } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.30", optional = true } +tracing = { version = "0.1.29", optional = true } +trie-db = { version = "0.24.0", default-features = false } +trie-root = { version = "0.17.0", default-features = false } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [dev-dependencies] -trie-bench = "0.28.0" -trie-standardmap = "0.15.2" +array-bytes = "4.1" criterion = "0.3.3" -hex-literal = "0.3.1" -sp-runtime = { version = "4.0.0-dev", path = "../runtime" } +trie-bench = "0.33.0" +trie-standardmap = "0.15.2" +sp-runtime = { version = "7.0.0", path = "../runtime" } [features] default = ["std"] std = [ - "sp-std/std", + "ahash", "codec/std", - "scale-info/std", + "hashbrown", "hash-db/std", + "lazy_static", + "lru", "memory-db/std", + "nohash-hasher", + "parking_lot", + "scale-info/std", + "sp-core/std", + "sp-std/std", + "thiserror", + "tracing", "trie-db/std", "trie-root/std", - "sp-core/std", ] -memory-tracker = [] diff --git a/primitives/trie/benches/bench.rs b/primitives/trie/benches/bench.rs index 8c84c6354f2c3..f1670b0c2fa42 100644 --- a/primitives/trie/benches/bench.rs +++ b/primitives/trie/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,11 +21,11 @@ criterion_main!(benches); fn benchmark(c: &mut Criterion) { trie_bench::standard_benchmark::< - sp_trie::Layout, + sp_trie::LayoutV1, sp_trie::TrieStream, >(c, "substrate-blake2"); trie_bench::standard_benchmark::< - sp_trie::Layout, + sp_trie::LayoutV1, sp_trie::TrieStream, >(c, "substrate-keccak"); } diff --git a/primitives/trie/src/cache/mod.rs b/primitives/trie/src/cache/mod.rs new file mode 100644 index 0000000000000..85539cf626857 --- /dev/null +++ b/primitives/trie/src/cache/mod.rs @@ -0,0 +1,692 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Trie Cache +//! +//! Provides an implementation of the [`TrieCache`](trie_db::TrieCache) trait. +//! The implementation is split into three types [`SharedTrieCache`], [`LocalTrieCache`] and +//! [`TrieCache`]. The [`SharedTrieCache`] is the instance that should be kept around for the entire +//! lifetime of the node. It will store all cached trie nodes and values on a global level. Then +//! there is the [`LocalTrieCache`] that should be kept around per state instance requested from the +//! backend. As there are very likely multiple accesses to the state per instance, this +//! [`LocalTrieCache`] is used to cache the nodes and the values before they are merged back to the +//! shared instance. Last but not least there is the [`TrieCache`] that is being used per access to +//! the state. It will use the [`SharedTrieCache`] and the [`LocalTrieCache`] to fulfill cache +//! requests. If both of them don't provide the requested data it will be inserted into the +//! [`LocalTrieCache`] and then later into the [`SharedTrieCache`]. +//! +//! The [`SharedTrieCache`] is bound to some maximum number of bytes. It is ensured that it never +//! runs above this limit. However as long as data is cached inside a [`LocalTrieCache`] it isn't +//! taken into account when limiting the [`SharedTrieCache`]. This means that for the lifetime of a +//! [`LocalTrieCache`] the actual memory usage could be above the allowed maximum. + +use crate::{Error, NodeCodec}; +use hash_db::Hasher; +use hashbrown::HashSet; +use nohash_hasher::BuildNoHashHasher; +use parking_lot::{Mutex, MutexGuard, RwLockReadGuard}; +use shared_cache::{SharedValueCache, ValueCacheKey}; +use std::{ + collections::{hash_map::Entry as MapEntry, HashMap}, + sync::Arc, +}; +use trie_db::{node::NodeOwned, CachedValue}; + +mod shared_cache; + +pub use shared_cache::SharedTrieCache; + +use self::shared_cache::{SharedTrieCacheInner, ValueCacheKeyHash}; + +const LOG_TARGET: &str = "trie-cache"; + +/// The size of the cache. +#[derive(Debug, Clone, Copy)] +pub enum CacheSize { + /// Do not limit the cache size. + Unlimited, + /// Let the cache in maximum use the given amount of bytes. + Maximum(usize), +} + +impl CacheSize { + /// Returns `true` if the `current_size` exceeds the allowed size. + fn exceeds(&self, current_size: usize) -> bool { + match self { + Self::Unlimited => false, + Self::Maximum(max) => *max < current_size, + } + } +} + +/// The local trie cache. +/// +/// This cache should be used per state instance created by the backend. One state instance is +/// referring to the state of one block. It will cache all the accesses that are done to the state +/// which could not be fullfilled by the [`SharedTrieCache`]. These locally cached items are merged +/// back to the shared trie cache when this instance is dropped. +/// +/// When using [`Self::as_trie_db_cache`] or [`Self::as_trie_db_mut_cache`], it will lock Mutexes. +/// So, it is important that these methods are not called multiple times, because they otherwise +/// deadlock. +pub struct LocalTrieCache { + /// The shared trie cache that created this instance. + shared: SharedTrieCache, + /// The local cache for the trie nodes. + node_cache: Mutex>>, + /// Keeps track of all the trie nodes accessed in the shared cache. + /// + /// This will be used to ensure that these nodes are brought to the front of the lru when this + /// local instance is merged back to the shared cache. + shared_node_cache_access: Mutex>, + /// The local cache for the values. + value_cache: Mutex< + HashMap< + ValueCacheKey<'static, H::Out>, + CachedValue, + BuildNoHashHasher>, + >, + >, + /// Keeps track of all values accessed in the shared cache. + /// + /// This will be used to ensure that these nodes are brought to the front of the lru when this + /// local instance is merged back to the shared cache. This can actually lead to collision when + /// two [`ValueCacheKey`]s with different storage roots and keys map to the same hash. However, + /// as we only use this set to update the lru position it is fine, even if we bring the wrong + /// value to the top. The important part is that we always get the correct value from the value + /// cache for a given key. + shared_value_cache_access: + Mutex>>, +} + +impl LocalTrieCache { + /// Return self as a [`TrieDB`](trie_db::TrieDB) compatible cache. + /// + /// The given `storage_root` needs to be the storage root of the trie this cache is used for. + pub fn as_trie_db_cache(&self, storage_root: H::Out) -> TrieCache<'_, H> { + let shared_inner = self.shared.read_lock_inner(); + + let value_cache = ValueCache::ForStorageRoot { + storage_root, + local_value_cache: self.value_cache.lock(), + shared_value_cache_access: self.shared_value_cache_access.lock(), + }; + + TrieCache { + shared_inner, + local_cache: self.node_cache.lock(), + value_cache, + shared_node_cache_access: self.shared_node_cache_access.lock(), + } + } + + /// Return self as [`TrieDBMut`](trie_db::TrieDBMut) compatible cache. + /// + /// After finishing all operations with [`TrieDBMut`](trie_db::TrieDBMut) and having obtained + /// the new storage root, [`TrieCache::merge_into`] should be called to update this local + /// cache instance. If the function is not called, cached data is just thrown away and not + /// propagated to the shared cache. So, accessing these new items will be slower, but nothing + /// would break because of this. + pub fn as_trie_db_mut_cache(&self) -> TrieCache<'_, H> { + TrieCache { + shared_inner: self.shared.read_lock_inner(), + local_cache: self.node_cache.lock(), + value_cache: ValueCache::Fresh(Default::default()), + shared_node_cache_access: self.shared_node_cache_access.lock(), + } + } +} + +impl Drop for LocalTrieCache { + fn drop(&mut self) { + let mut shared_inner = self.shared.write_lock_inner(); + + shared_inner + .node_cache_mut() + .update(self.node_cache.lock().drain(), self.shared_node_cache_access.lock().drain()); + + shared_inner + .value_cache_mut() + .update(self.value_cache.lock().drain(), self.shared_value_cache_access.lock().drain()); + } +} + +/// The abstraction of the value cache for the [`TrieCache`]. +enum ValueCache<'a, H> { + /// The value cache is fresh, aka not yet associated to any storage root. + /// This is used for example when a new trie is being build, to cache new values. + Fresh(HashMap, CachedValue>), + /// The value cache is already bound to a specific storage root. + ForStorageRoot { + shared_value_cache_access: MutexGuard< + 'a, + HashSet>, + >, + local_value_cache: MutexGuard< + 'a, + HashMap< + ValueCacheKey<'static, H>, + CachedValue, + nohash_hasher::BuildNoHashHasher>, + >, + >, + storage_root: H, + }, +} + +impl + std::hash::Hash + Eq + Clone + Copy> ValueCache<'_, H> { + /// Get the value for the given `key`. + fn get<'a>( + &'a mut self, + key: &[u8], + shared_value_cache: &'a SharedValueCache, + ) -> Option<&CachedValue> { + match self { + Self::Fresh(map) => map.get(key), + Self::ForStorageRoot { local_value_cache, shared_value_cache_access, storage_root } => { + let key = ValueCacheKey::new_ref(key, *storage_root); + + // We first need to look up in the local cache and then the shared cache. + // It can happen that some value is cached in the shared cache, but the + // weak reference of the data can not be upgraded anymore. This for example + // happens when the node is dropped that contains the strong reference to the data. + // + // So, the logic of the trie would lookup the data and the node and store both + // in our local caches. + local_value_cache + .get(unsafe { + // SAFETY + // + // We need to convert the lifetime to make the compiler happy. However, as + // we only use the `key` to looking up the value this lifetime conversion is + // safe. + std::mem::transmute::<&ValueCacheKey<'_, H>, &ValueCacheKey<'static, H>>( + &key, + ) + }) + .or_else(|| { + shared_value_cache.get(&key).map(|v| { + shared_value_cache_access.insert(key.get_hash()); + v + }) + }) + }, + } + } + + /// Insert some new `value` under the given `key`. + fn insert(&mut self, key: &[u8], value: CachedValue) { + match self { + Self::Fresh(map) => { + map.insert(key.into(), value); + }, + Self::ForStorageRoot { local_value_cache, storage_root, .. } => { + local_value_cache.insert(ValueCacheKey::new_value(key, *storage_root), value); + }, + } + } +} + +/// The actual [`TrieCache`](trie_db::TrieCache) implementation. +/// +/// If this instance was created for using it with a [`TrieDBMut`](trie_db::TrieDBMut), it needs to +/// be merged back into the [`LocalTrieCache`] with [`Self::merge_into`] after all operations are +/// done. +pub struct TrieCache<'a, H: Hasher> { + shared_inner: RwLockReadGuard<'a, SharedTrieCacheInner>, + shared_node_cache_access: MutexGuard<'a, HashSet>, + local_cache: MutexGuard<'a, HashMap>>, + value_cache: ValueCache<'a, H::Out>, +} + +impl<'a, H: Hasher> TrieCache<'a, H> { + /// Merge this cache into the given [`LocalTrieCache`]. + /// + /// This function is only required to be called when this instance was created through + /// [`LocalTrieCache::as_trie_db_mut_cache`], otherwise this method is a no-op. The given + /// `storage_root` is the new storage root that was obtained after finishing all operations + /// using the [`TrieDBMut`](trie_db::TrieDBMut). + pub fn merge_into(self, local: &LocalTrieCache, storage_root: H::Out) { + let cache = if let ValueCache::Fresh(cache) = self.value_cache { cache } else { return }; + + if !cache.is_empty() { + let mut value_cache = local.value_cache.lock(); + let partial_hash = ValueCacheKey::hash_partial_data(&storage_root); + + cache + .into_iter() + .map(|(k, v)| { + let hash = + ValueCacheKeyHash::from_hasher_and_storage_key(partial_hash.clone(), &k); + (ValueCacheKey::Value { storage_key: k, storage_root, hash }, v) + }) + .for_each(|(k, v)| { + value_cache.insert(k, v); + }); + } + } +} + +impl<'a, H: Hasher> trie_db::TrieCache> for TrieCache<'a, H> { + fn get_or_insert_node( + &mut self, + hash: H::Out, + fetch_node: &mut dyn FnMut() -> trie_db::Result, H::Out, Error>, + ) -> trie_db::Result<&NodeOwned, H::Out, Error> { + if let Some(res) = self.shared_inner.node_cache().get(&hash) { + tracing::trace!(target: LOG_TARGET, ?hash, "Serving node from shared cache"); + self.shared_node_cache_access.insert(hash); + return Ok(res) + } + + match self.local_cache.entry(hash) { + MapEntry::Occupied(res) => { + tracing::trace!(target: LOG_TARGET, ?hash, "Serving node from local cache"); + Ok(res.into_mut()) + }, + MapEntry::Vacant(vacant) => { + let node = (*fetch_node)(); + + tracing::trace!( + target: LOG_TARGET, + ?hash, + fetch_successful = node.is_ok(), + "Node not found, needed to fetch it." + ); + + Ok(vacant.insert(node?)) + }, + } + } + + fn get_node(&mut self, hash: &H::Out) -> Option<&NodeOwned> { + if let Some(node) = self.shared_inner.node_cache().get(hash) { + tracing::trace!(target: LOG_TARGET, ?hash, "Getting node from shared cache"); + self.shared_node_cache_access.insert(*hash); + return Some(node) + } + + let res = self.local_cache.get(hash); + + tracing::trace!( + target: LOG_TARGET, + ?hash, + found = res.is_some(), + "Getting node from local cache" + ); + + res + } + + fn lookup_value_for_key(&mut self, key: &[u8]) -> Option<&CachedValue> { + let res = self.value_cache.get(key, self.shared_inner.value_cache()); + + tracing::trace!( + target: LOG_TARGET, + key = ?sp_core::hexdisplay::HexDisplay::from(&key), + found = res.is_some(), + "Looked up value for key", + ); + + res + } + + fn cache_value_for_key(&mut self, key: &[u8], data: CachedValue) { + tracing::trace!( + target: LOG_TARGET, + key = ?sp_core::hexdisplay::HexDisplay::from(&key), + "Caching value for key", + ); + + self.value_cache.insert(key.into(), data); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use trie_db::{Bytes, Trie, TrieDBBuilder, TrieDBMutBuilder, TrieHash, TrieMut}; + + type MemoryDB = crate::MemoryDB; + type Layout = crate::LayoutV1; + type Cache = super::SharedTrieCache; + type Recorder = crate::recorder::Recorder; + + const TEST_DATA: &[(&[u8], &[u8])] = + &[(b"key1", b"val1"), (b"key2", &[2; 64]), (b"key3", b"val3"), (b"key4", &[4; 64])]; + const CACHE_SIZE_RAW: usize = 1024 * 10; + const CACHE_SIZE: CacheSize = CacheSize::Maximum(CACHE_SIZE_RAW); + + fn create_trie() -> (MemoryDB, TrieHash) { + let mut db = MemoryDB::default(); + let mut root = Default::default(); + + { + let mut trie = TrieDBMutBuilder::::new(&mut db, &mut root).build(); + for (k, v) in TEST_DATA { + trie.insert(k, v).expect("Inserts data"); + } + } + + (db, root) + } + + #[test] + fn basic_cache_works() { + let (db, root) = create_trie(); + + let shared_cache = Cache::new(CACHE_SIZE); + let local_cache = shared_cache.local_cache(); + + { + let mut cache = local_cache.as_trie_db_cache(root); + let trie = TrieDBBuilder::::new(&db, &root).with_cache(&mut cache).build(); + assert_eq!(TEST_DATA[0].1.to_vec(), trie.get(TEST_DATA[0].0).unwrap().unwrap()); + } + + // Local cache wasn't dropped yet, so there should nothing in the shared caches. + assert!(shared_cache.read_lock_inner().value_cache().lru.is_empty()); + assert!(shared_cache.read_lock_inner().node_cache().lru.is_empty()); + + drop(local_cache); + + // Now we should have the cached items in the shared cache. + assert!(shared_cache.read_lock_inner().node_cache().lru.len() >= 1); + let cached_data = shared_cache + .read_lock_inner() + .value_cache() + .lru + .peek(&ValueCacheKey::new_value(TEST_DATA[0].0, root)) + .unwrap() + .clone(); + assert_eq!(Bytes::from(TEST_DATA[0].1.to_vec()), cached_data.data().flatten().unwrap()); + + let fake_data = Bytes::from(&b"fake_data"[..]); + + let local_cache = shared_cache.local_cache(); + shared_cache.write_lock_inner().value_cache_mut().lru.put( + ValueCacheKey::new_value(TEST_DATA[1].0, root), + (fake_data.clone(), Default::default()).into(), + ); + + { + let mut cache = local_cache.as_trie_db_cache(root); + let trie = TrieDBBuilder::::new(&db, &root).with_cache(&mut cache).build(); + + // We should now get the "fake_data", because we inserted this manually to the cache. + assert_eq!(b"fake_data".to_vec(), trie.get(TEST_DATA[1].0).unwrap().unwrap()); + } + } + + #[test] + fn trie_db_mut_cache_works() { + let (mut db, root) = create_trie(); + + let new_key = b"new_key".to_vec(); + // Use some long value to not have it inlined + let new_value = vec![23; 64]; + + let shared_cache = Cache::new(CACHE_SIZE); + let mut new_root = root; + + { + let local_cache = shared_cache.local_cache(); + + let mut cache = local_cache.as_trie_db_mut_cache(); + + { + let mut trie = TrieDBMutBuilder::::from_existing(&mut db, &mut new_root) + .with_cache(&mut cache) + .build(); + + trie.insert(&new_key, &new_value).unwrap(); + } + + cache.merge_into(&local_cache, new_root); + } + + // After the local cache is dropped, all changes should have been merged back to the shared + // cache. + let cached_data = shared_cache + .read_lock_inner() + .value_cache() + .lru + .peek(&ValueCacheKey::new_value(new_key, new_root)) + .unwrap() + .clone(); + assert_eq!(Bytes::from(new_value), cached_data.data().flatten().unwrap()); + } + + #[test] + fn trie_db_cache_and_recorder_work_together() { + let (db, root) = create_trie(); + + let shared_cache = Cache::new(CACHE_SIZE); + + for i in 0..5 { + // Clear some of the caches. + if i == 2 { + shared_cache.reset_node_cache(); + } else if i == 3 { + shared_cache.reset_value_cache(); + } + + let local_cache = shared_cache.local_cache(); + let recorder = Recorder::default(); + + { + let mut cache = local_cache.as_trie_db_cache(root); + let mut recorder = recorder.as_trie_recorder(root); + let trie = TrieDBBuilder::::new(&db, &root) + .with_cache(&mut cache) + .with_recorder(&mut recorder) + .build(); + + for (key, value) in TEST_DATA { + assert_eq!(*value, trie.get(&key).unwrap().unwrap()); + } + } + + let storage_proof = recorder.drain_storage_proof(); + let memory_db: MemoryDB = storage_proof.into_memory_db(); + + { + let trie = TrieDBBuilder::::new(&memory_db, &root).build(); + + for (key, value) in TEST_DATA { + assert_eq!(*value, trie.get(&key).unwrap().unwrap()); + } + } + } + } + + #[test] + fn trie_db_mut_cache_and_recorder_work_together() { + const DATA_TO_ADD: &[(&[u8], &[u8])] = &[(b"key11", &[45; 78]), (b"key33", &[78; 89])]; + + let (db, root) = create_trie(); + + let shared_cache = Cache::new(CACHE_SIZE); + + // Run this twice so that we use the data cache in the second run. + for i in 0..5 { + // Clear some of the caches. + if i == 2 { + shared_cache.reset_node_cache(); + } else if i == 3 { + shared_cache.reset_value_cache(); + } + + let recorder = Recorder::default(); + let local_cache = shared_cache.local_cache(); + let mut new_root = root; + + { + let mut db = db.clone(); + let mut cache = local_cache.as_trie_db_cache(root); + let mut recorder = recorder.as_trie_recorder(root); + let mut trie = TrieDBMutBuilder::::from_existing(&mut db, &mut new_root) + .with_cache(&mut cache) + .with_recorder(&mut recorder) + .build(); + + for (key, value) in DATA_TO_ADD { + trie.insert(key, value).unwrap(); + } + } + + let storage_proof = recorder.drain_storage_proof(); + let mut memory_db: MemoryDB = storage_proof.into_memory_db(); + let mut proof_root = root; + + { + let mut trie = + TrieDBMutBuilder::::from_existing(&mut memory_db, &mut proof_root) + .build(); + + for (key, value) in DATA_TO_ADD { + trie.insert(key, value).unwrap(); + } + } + + assert_eq!(new_root, proof_root) + } + } + + #[test] + fn cache_lru_works() { + let (db, root) = create_trie(); + + let shared_cache = Cache::new(CACHE_SIZE); + + { + let local_cache = shared_cache.local_cache(); + + let mut cache = local_cache.as_trie_db_cache(root); + let trie = TrieDBBuilder::::new(&db, &root).with_cache(&mut cache).build(); + + for (k, _) in TEST_DATA { + trie.get(k).unwrap().unwrap(); + } + } + + // Check that all items are there. + assert!(shared_cache + .read_lock_inner() + .value_cache() + .lru + .iter() + .map(|d| d.0) + .all(|l| TEST_DATA.iter().any(|d| l.storage_key().unwrap() == d.0))); + + // Run this in a loop. The first time we check that with the filled value cache, + // the expected values are at the top of the LRU. + // The second run is using an empty value cache to ensure that we access the nodes. + for _ in 0..2 { + { + let local_cache = shared_cache.local_cache(); + + let mut cache = local_cache.as_trie_db_cache(root); + let trie = TrieDBBuilder::::new(&db, &root).with_cache(&mut cache).build(); + + for (k, _) in TEST_DATA.iter().take(2) { + trie.get(k).unwrap().unwrap(); + } + } + + // Ensure that the accessed items are most recently used items of the shared value + // cache. + assert!(shared_cache + .read_lock_inner() + .value_cache() + .lru + .iter() + .take(2) + .map(|d| d.0) + .all(|l| { TEST_DATA.iter().take(2).any(|d| l.storage_key().unwrap() == d.0) })); + + // Delete the value cache, so that we access the nodes. + shared_cache.reset_value_cache(); + } + + let most_recently_used_nodes = shared_cache + .read_lock_inner() + .node_cache() + .lru + .iter() + .map(|d| *d.0) + .collect::>(); + + { + let local_cache = shared_cache.local_cache(); + + let mut cache = local_cache.as_trie_db_cache(root); + let trie = TrieDBBuilder::::new(&db, &root).with_cache(&mut cache).build(); + + for (k, _) in TEST_DATA.iter().skip(2) { + trie.get(k).unwrap().unwrap(); + } + } + + // Ensure that the most recently used nodes changed as well. + assert_ne!( + most_recently_used_nodes, + shared_cache + .read_lock_inner() + .node_cache() + .lru + .iter() + .map(|d| *d.0) + .collect::>() + ); + } + + #[test] + fn cache_respects_bounds() { + let (mut db, root) = create_trie(); + + let shared_cache = Cache::new(CACHE_SIZE); + { + let local_cache = shared_cache.local_cache(); + + let mut new_root = root; + + { + let mut cache = local_cache.as_trie_db_cache(root); + { + let mut trie = + TrieDBMutBuilder::::from_existing(&mut db, &mut new_root) + .with_cache(&mut cache) + .build(); + + let value = vec![10u8; 100]; + // Ensure we add enough data that would overflow the cache. + for i in 0..CACHE_SIZE_RAW / 100 * 2 { + trie.insert(format!("key{}", i).as_bytes(), &value).unwrap(); + } + } + + cache.merge_into(&local_cache, new_root); + } + } + + let node_cache_size = shared_cache.read_lock_inner().node_cache().size_in_bytes; + let value_cache_size = shared_cache.read_lock_inner().value_cache().size_in_bytes; + + assert!(node_cache_size + value_cache_size < CACHE_SIZE_RAW); + } +} diff --git a/primitives/trie/src/cache/shared_cache.rs b/primitives/trie/src/cache/shared_cache.rs new file mode 100644 index 0000000000000..9d4d36b83a28a --- /dev/null +++ b/primitives/trie/src/cache/shared_cache.rs @@ -0,0 +1,677 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +///! Provides the [`SharedNodeCache`], the [`SharedValueCache`] and the [`SharedTrieCache`] +///! that combines both caches and is exported to the outside. +use super::{CacheSize, LOG_TARGET}; +use hash_db::Hasher; +use hashbrown::{hash_set::Entry as SetEntry, HashSet}; +use lru::LruCache; +use nohash_hasher::BuildNoHashHasher; +use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::{ + hash::{BuildHasher, Hasher as _}, + mem, + sync::Arc, +}; +use trie_db::{node::NodeOwned, CachedValue}; + +lazy_static::lazy_static! { + static ref RANDOM_STATE: ahash::RandomState = ahash::RandomState::default(); +} + +/// No hashing [`LruCache`]. +type NoHashingLruCache = LruCache>; + +/// The shared node cache. +/// +/// Internally this stores all cached nodes in a [`LruCache`]. It ensures that when updating the +/// cache, that the cache stays within its allowed bounds. +pub(super) struct SharedNodeCache { + /// The cached nodes, ordered by least recently used. + pub(super) lru: LruCache>, + /// The size of [`Self::lru`] in bytes. + pub(super) size_in_bytes: usize, + /// The maximum cache size of [`Self::lru`]. + maximum_cache_size: CacheSize, +} + +impl + Eq + std::hash::Hash> SharedNodeCache { + /// Create a new instance. + fn new(cache_size: CacheSize) -> Self { + Self { lru: LruCache::unbounded(), size_in_bytes: 0, maximum_cache_size: cache_size } + } + + /// Get the node for `key`. + /// + /// This doesn't change the least recently order in the internal [`LruCache`]. + pub fn get(&self, key: &H) -> Option<&NodeOwned> { + self.lru.peek(key) + } + + /// Update the cache with the `added` nodes and the `accessed` nodes. + /// + /// The `added` nodes are the ones that have been collected by doing operations on the trie and + /// now should be stored in the shared cache. The `accessed` nodes are only referenced by hash + /// and represent the nodes that were retrieved from this shared cache through [`Self::get`]. + /// These `accessed` nodes are being put to the front of the internal [`LruCache`] like the + /// `added` ones. + /// + /// After the internal [`LruCache`] was updated, it is ensured that the internal [`LruCache`] is + /// inside its bounds ([`Self::maximum_size_in_bytes`]). + pub fn update( + &mut self, + added: impl IntoIterator)>, + accessed: impl IntoIterator, + ) { + let update_size_in_bytes = |size_in_bytes: &mut usize, key: &H, node: &NodeOwned| { + if let Some(new_size_in_bytes) = + size_in_bytes.checked_sub(key.as_ref().len() + node.size_in_bytes()) + { + *size_in_bytes = new_size_in_bytes; + } else { + *size_in_bytes = 0; + tracing::error!(target: LOG_TARGET, "`SharedNodeCache` underflow detected!",); + } + }; + + accessed.into_iter().for_each(|key| { + // Access every node in the lru to put it to the front. + self.lru.get(&key); + }); + added.into_iter().for_each(|(key, node)| { + self.size_in_bytes += key.as_ref().len() + node.size_in_bytes(); + + if let Some((r_key, r_node)) = self.lru.push(key, node) { + update_size_in_bytes(&mut self.size_in_bytes, &r_key, &r_node); + } + + // Directly ensure that we respect the maximum size. By doing it directly here we ensure + // that the internal map of the [`LruCache`] doesn't grow too much. + while self.maximum_cache_size.exceeds(self.size_in_bytes) { + // This should always be `Some(_)`, otherwise something is wrong! + if let Some((key, node)) = self.lru.pop_lru() { + update_size_in_bytes(&mut self.size_in_bytes, &key, &node); + } + } + }); + } + + /// Reset the cache. + fn reset(&mut self) { + self.size_in_bytes = 0; + self.lru.clear(); + } +} + +/// The hash of [`ValueCacheKey`]. +#[derive(Eq, Clone, Copy)] +pub struct ValueCacheKeyHash(u64); + +impl ValueCacheKeyHash { + pub fn from_hasher_and_storage_key( + mut hasher: impl std::hash::Hasher, + storage_key: &[u8], + ) -> Self { + hasher.write(storage_key); + + Self(hasher.finish()) + } +} + +impl PartialEq for ValueCacheKeyHash { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl std::hash::Hash for ValueCacheKeyHash { + fn hash(&self, state: &mut Hasher) { + state.write_u64(self.0); + } +} + +impl nohash_hasher::IsEnabled for ValueCacheKeyHash {} + +/// A type that can only be constructed inside of this file. +/// +/// It "requires" that the user has read the docs to prevent fuck ups. +#[derive(Eq, PartialEq)] +pub(super) struct IReadTheDocumentation(()); + +/// The key type that is being used to address a [`CachedValue`]. +/// +/// This type is implemented as `enum` to improve the performance when accessing the value cache. +/// The problem being that we need to calculate the `hash` of [`Self`] in worst case three times +/// when trying to find a value in the value cache. First to lookup the local cache, then the shared +/// cache and if we found it in the shared cache a third time to insert it into the list of accessed +/// values. To work around each variant stores the `hash` to identify a unique combination of +/// `storage_key` and `storage_root`. However, be aware that this `hash` can lead to collisions when +/// there are two different `storage_key` and `storage_root` pairs that map to the same `hash`. This +/// type also has the `Hash` variant. This variant should only be used for the use case of updating +/// the lru for a key. Because when using only the `Hash` variant to getting a value from a hash map +/// it could happen that a wrong value is returned when there is another key in the same hash map +/// that maps to the same `hash`. The [`PartialEq`] implementation is written in a way that when one +/// of the two compared instances is the `Hash` variant, we will only compare the hashes. This +/// ensures that we can use the `Hash` variant to bring values up in the lru. +#[derive(Eq)] +pub(super) enum ValueCacheKey<'a, H> { + /// Variant that stores the `storage_key` by value. + Value { + /// The storage root of the trie this key belongs to. + storage_root: H, + /// The key to access the value in the storage. + storage_key: Arc<[u8]>, + /// The hash that identifying this instance of `storage_root` and `storage_key`. + hash: ValueCacheKeyHash, + }, + /// Variant that only references the `storage_key`. + Ref { + /// The storage root of the trie this key belongs to. + storage_root: H, + /// The key to access the value in the storage. + storage_key: &'a [u8], + /// The hash that identifying this instance of `storage_root` and `storage_key`. + hash: ValueCacheKeyHash, + }, + /// Variant that only stores the hash that represents the `storage_root` and `storage_key`. + /// + /// This should be used by caution, because it can lead to accessing the wrong value in a + /// hash map/set when there exists two different `storage_root`s and `storage_key`s that + /// map to the same `hash`. + Hash { hash: ValueCacheKeyHash, _i_read_the_documentation: IReadTheDocumentation }, +} + +impl<'a, H> ValueCacheKey<'a, H> { + /// Constructs [`Self::Value`]. + pub fn new_value(storage_key: impl Into>, storage_root: H) -> Self + where + H: AsRef<[u8]>, + { + let storage_key = storage_key.into(); + let hash = Self::hash_data(&storage_key, &storage_root); + Self::Value { storage_root, storage_key, hash } + } + + /// Constructs [`Self::Ref`]. + pub fn new_ref(storage_key: &'a [u8], storage_root: H) -> Self + where + H: AsRef<[u8]>, + { + let storage_key = storage_key.into(); + let hash = Self::hash_data(storage_key, &storage_root); + Self::Ref { storage_root, storage_key, hash } + } + + /// Returns a hasher prepared to build the final hash to identify [`Self`]. + /// + /// See [`Self::hash_data`] for building the hash directly. + pub fn hash_partial_data(storage_root: &H) -> impl std::hash::Hasher + Clone + where + H: AsRef<[u8]>, + { + let mut hasher = RANDOM_STATE.build_hasher(); + hasher.write(storage_root.as_ref()); + hasher + } + + /// Hash the `key` and `storage_root` that identify [`Self`]. + /// + /// Returns a `u64` which represents the unique hash for the given inputs. + pub fn hash_data(key: &[u8], storage_root: &H) -> ValueCacheKeyHash + where + H: AsRef<[u8]>, + { + let hasher = Self::hash_partial_data(storage_root); + + ValueCacheKeyHash::from_hasher_and_storage_key(hasher, key) + } + + /// Returns the `hash` that identifies the current instance. + pub fn get_hash(&self) -> ValueCacheKeyHash { + match self { + Self::Value { hash, .. } | Self::Ref { hash, .. } | Self::Hash { hash, .. } => *hash, + } + } + + /// Returns the stored storage root. + pub fn storage_root(&self) -> Option<&H> { + match self { + Self::Value { storage_root, .. } | Self::Ref { storage_root, .. } => Some(storage_root), + Self::Hash { .. } => None, + } + } + + /// Returns the stored storage key. + pub fn storage_key(&self) -> Option<&[u8]> { + match self { + Self::Ref { storage_key, .. } => Some(&storage_key), + Self::Value { storage_key, .. } => Some(storage_key), + Self::Hash { .. } => None, + } + } +} + +// Implement manually to ensure that the `Value` and `Hash` are treated equally. +impl std::hash::Hash for ValueCacheKey<'_, H> { + fn hash(&self, state: &mut Hasher) { + self.get_hash().hash(state) + } +} + +impl nohash_hasher::IsEnabled for ValueCacheKey<'_, H> {} + +// Implement manually to ensure that the `Value` and `Hash` are treated equally. +impl PartialEq for ValueCacheKey<'_, H> { + fn eq(&self, other: &Self) -> bool { + // First check if `self` or `other` is only the `Hash`. + // Then we only compare the `hash`. So, there could actually be some collision + // if two different storage roots and keys are mapping to the same key. See the + // [`ValueCacheKey`] docs for more information. + match (self, other) { + (Self::Hash { hash, .. }, Self::Hash { hash: other_hash, .. }) => hash == other_hash, + (Self::Hash { hash, .. }, _) => *hash == other.get_hash(), + (_, Self::Hash { hash: other_hash, .. }) => self.get_hash() == *other_hash, + // If both are not the `Hash` variant, we compare all the values. + _ => + self.get_hash() == other.get_hash() && + self.storage_root() == other.storage_root() && + self.storage_key() == other.storage_key(), + } + } +} + +/// The shared value cache. +/// +/// The cache ensures that it stays in the configured size bounds. +pub(super) struct SharedValueCache { + /// The cached nodes, ordered by least recently used. + pub(super) lru: NoHashingLruCache, CachedValue>, + /// The size of [`Self::lru`] in bytes. + pub(super) size_in_bytes: usize, + /// The maximum cache size of [`Self::lru`]. + maximum_cache_size: CacheSize, + /// All known storage keys that are stored in [`Self::lru`]. + /// + /// This is used to de-duplicate keys in memory that use the + /// same [`SharedValueCache::storage_key`], but have a different + /// [`SharedValueCache::storage_root`]. + known_storage_keys: HashSet>, +} + +impl> SharedValueCache { + /// Create a new instance. + fn new(cache_size: CacheSize) -> Self { + Self { + lru: NoHashingLruCache::unbounded_with_hasher(Default::default()), + size_in_bytes: 0, + maximum_cache_size: cache_size, + known_storage_keys: Default::default(), + } + } + + /// Get the [`CachedValue`] for `key`. + /// + /// This doesn't change the least recently order in the internal [`LruCache`]. + pub fn get<'a>(&'a self, key: &ValueCacheKey) -> Option<&'a CachedValue> { + debug_assert!( + !matches!(key, ValueCacheKey::Hash { .. }), + "`get` can not be called with `Hash` variant as this may returns the wrong value." + ); + + self.lru.peek(unsafe { + // SAFETY + // + // We need to convert the lifetime to make the compiler happy. However, as + // we only use the `key` to looking up the value this lifetime conversion is + // safe. + mem::transmute::<&ValueCacheKey<'_, H>, &ValueCacheKey<'static, H>>(key) + }) + } + + /// Update the cache with the `added` values and the `accessed` values. + /// + /// The `added` values are the ones that have been collected by doing operations on the trie and + /// now should be stored in the shared cache. The `accessed` values are only referenced by the + /// [`ValueCacheKeyHash`] and represent the values that were retrieved from this shared cache + /// through [`Self::get`]. These `accessed` values are being put to the front of the internal + /// [`LruCache`] like the `added` ones. + /// + /// After the internal [`LruCache`] was updated, it is ensured that the internal [`LruCache`] is + /// inside its bounds ([`Self::maximum_size_in_bytes`]). + pub fn update( + &mut self, + added: impl IntoIterator, CachedValue)>, + accessed: impl IntoIterator, + ) { + // The base size in memory per ([`ValueCacheKey`], [`CachedValue`]). + let base_size = mem::size_of::>() + mem::size_of::>(); + let known_keys_entry_size = mem::size_of::>(); + + let update_size_in_bytes = + |size_in_bytes: &mut usize, r_key: Arc<[u8]>, known_keys: &mut HashSet>| { + // If the `strong_count == 2`, it means this is the last instance of the key. + // One being `r_key` and the other being stored in `known_storage_keys`. + let last_instance = Arc::strong_count(&r_key) == 2; + + let key_len = if last_instance { + known_keys.remove(&r_key); + r_key.len() + known_keys_entry_size + } else { + // The key is still in `keys`, because it is still used by another + // `ValueCacheKey`. + 0 + }; + + if let Some(new_size_in_bytes) = size_in_bytes.checked_sub(key_len + base_size) { + *size_in_bytes = new_size_in_bytes; + } else { + *size_in_bytes = 0; + tracing::error!(target: LOG_TARGET, "`SharedValueCache` underflow detected!",); + } + }; + + accessed.into_iter().for_each(|key| { + // Access every node in the lru to put it to the front. + // As we are using the `Hash` variant here, it may leads to putting the wrong value to + // the top. However, the only consequence of this is that we may prune a recently used + // value to early. + self.lru.get(&ValueCacheKey::Hash { + hash: key, + _i_read_the_documentation: IReadTheDocumentation(()), + }); + }); + + added.into_iter().for_each(|(key, value)| { + let (storage_root, storage_key, key_hash) = match key { + ValueCacheKey::Hash { .. } => { + // Ignore the hash variant and try the next. + tracing::error!( + target: LOG_TARGET, + "`SharedValueCached::update` was called with a key to add \ + that uses the `Hash` variant. This would lead to potential hash collision!", + ); + return + }, + ValueCacheKey::Ref { storage_key, storage_root, hash } => + (storage_root, storage_key.into(), hash), + ValueCacheKey::Value { storage_root, storage_key, hash } => + (storage_root, storage_key, hash), + }; + + let (size_update, storage_key) = + match self.known_storage_keys.entry(storage_key.clone()) { + SetEntry::Vacant(v) => { + let len = v.get().len(); + v.insert(); + + // If the key was unknown, we need to also take its length and the size of + // the entry of `known_keys` into account. + (len + base_size + known_keys_entry_size, storage_key) + }, + SetEntry::Occupied(o) => { + // Key is known + (base_size, o.get().clone()) + }, + }; + + self.size_in_bytes += size_update; + + if let Some((r_key, _)) = self + .lru + .push(ValueCacheKey::Value { storage_key, storage_root, hash: key_hash }, value) + { + if let ValueCacheKey::Value { storage_key, .. } = r_key { + update_size_in_bytes( + &mut self.size_in_bytes, + storage_key, + &mut self.known_storage_keys, + ); + } + } + + // Directly ensure that we respect the maximum size. By doing it directly here we + // ensure that the internal map of the [`LruCache`] doesn't grow too much. + while self.maximum_cache_size.exceeds(self.size_in_bytes) { + // This should always be `Some(_)`, otherwise something is wrong! + if let Some((r_key, _)) = self.lru.pop_lru() { + if let ValueCacheKey::Value { storage_key, .. } = r_key { + update_size_in_bytes( + &mut self.size_in_bytes, + storage_key, + &mut self.known_storage_keys, + ); + } + } + } + }); + } + + /// Reset the cache. + fn reset(&mut self) { + self.size_in_bytes = 0; + self.lru.clear(); + self.known_storage_keys.clear(); + } +} + +/// The inner of [`SharedTrieCache`]. +pub(super) struct SharedTrieCacheInner { + node_cache: SharedNodeCache, + value_cache: SharedValueCache, +} + +impl SharedTrieCacheInner { + /// Returns a reference to the [`SharedValueCache`]. + pub(super) fn value_cache(&self) -> &SharedValueCache { + &self.value_cache + } + + /// Returns a mutable reference to the [`SharedValueCache`]. + pub(super) fn value_cache_mut(&mut self) -> &mut SharedValueCache { + &mut self.value_cache + } + + /// Returns a reference to the [`SharedNodeCache`]. + pub(super) fn node_cache(&self) -> &SharedNodeCache { + &self.node_cache + } + + /// Returns a mutable reference to the [`SharedNodeCache`]. + pub(super) fn node_cache_mut(&mut self) -> &mut SharedNodeCache { + &mut self.node_cache + } +} + +/// The shared trie cache. +/// +/// It should be instantiated once per node. It will hold the trie nodes and values of all +/// operations to the state. To not use all available memory it will ensure to stay in the +/// bounds given via the [`CacheSize`] at startup. +/// +/// The instance of this object can be shared between multiple threads. +pub struct SharedTrieCache { + inner: Arc>>, +} + +impl Clone for SharedTrieCache { + fn clone(&self) -> Self { + Self { inner: self.inner.clone() } + } +} + +impl SharedTrieCache { + /// Create a new [`SharedTrieCache`]. + pub fn new(cache_size: CacheSize) -> Self { + let (node_cache_size, value_cache_size) = match cache_size { + CacheSize::Maximum(max) => { + // Allocate 20% for the value cache. + let value_cache_size_in_bytes = (max as f32 * 0.20) as usize; + + ( + CacheSize::Maximum(max - value_cache_size_in_bytes), + CacheSize::Maximum(value_cache_size_in_bytes), + ) + }, + CacheSize::Unlimited => (CacheSize::Unlimited, CacheSize::Unlimited), + }; + + Self { + inner: Arc::new(RwLock::new(SharedTrieCacheInner { + node_cache: SharedNodeCache::new(node_cache_size), + value_cache: SharedValueCache::new(value_cache_size), + })), + } + } + + /// Create a new [`LocalTrieCache`](super::LocalTrieCache) instance from this shared cache. + pub fn local_cache(&self) -> super::LocalTrieCache { + super::LocalTrieCache { + shared: self.clone(), + node_cache: Default::default(), + value_cache: Default::default(), + shared_node_cache_access: Default::default(), + shared_value_cache_access: Default::default(), + } + } + + /// Returns the used memory size of this cache in bytes. + pub fn used_memory_size(&self) -> usize { + let inner = self.inner.read(); + let value_cache_size = inner.value_cache.size_in_bytes; + let node_cache_size = inner.node_cache.size_in_bytes; + + node_cache_size + value_cache_size + } + + /// Reset the node cache. + pub fn reset_node_cache(&self) { + self.inner.write().node_cache.reset(); + } + + /// Reset the value cache. + pub fn reset_value_cache(&self) { + self.inner.write().value_cache.reset(); + } + + /// Reset the entire cache. + pub fn reset(&self) { + self.reset_node_cache(); + self.reset_value_cache(); + } + + /// Returns the read locked inner. + pub(super) fn read_lock_inner(&self) -> RwLockReadGuard<'_, SharedTrieCacheInner> { + self.inner.read() + } + + /// Returns the write locked inner. + pub(super) fn write_lock_inner(&self) -> RwLockWriteGuard<'_, SharedTrieCacheInner> { + self.inner.write() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::H256 as Hash; + + #[test] + fn shared_value_cache_works() { + let base_size = mem::size_of::>() + mem::size_of::>(); + let arc_size = mem::size_of::>(); + + let mut cache = SharedValueCache::::new(CacheSize::Maximum( + (base_size + arc_size + 10) * 10, + )); + + let key = vec![0; 10]; + + let root0 = Hash::repeat_byte(1); + let root1 = Hash::repeat_byte(2); + + cache.update( + vec![ + (ValueCacheKey::new_value(&key[..], root0), CachedValue::NonExisting), + (ValueCacheKey::new_value(&key[..], root1), CachedValue::NonExisting), + ], + vec![], + ); + + // Ensure that the basics are working + assert_eq!(1, cache.known_storage_keys.len()); + assert_eq!(3, Arc::strong_count(cache.known_storage_keys.get(&key[..]).unwrap())); + assert_eq!(base_size * 2 + key.len() + arc_size, cache.size_in_bytes); + + // Just accessing a key should not change anything on the size and number of entries. + cache.update(vec![], vec![ValueCacheKey::hash_data(&key[..], &root0)]); + assert_eq!(1, cache.known_storage_keys.len()); + assert_eq!(3, Arc::strong_count(cache.known_storage_keys.get(&key[..]).unwrap())); + assert_eq!(base_size * 2 + key.len() + arc_size, cache.size_in_bytes); + + // Add 9 other entries and this should move out the key for `root1`. + cache.update( + (1..10) + .map(|i| vec![i; 10]) + .map(|key| (ValueCacheKey::new_value(&key[..], root0), CachedValue::NonExisting)), + vec![], + ); + + assert_eq!(10, cache.known_storage_keys.len()); + assert_eq!(2, Arc::strong_count(cache.known_storage_keys.get(&key[..]).unwrap())); + assert_eq!((base_size + key.len() + arc_size) * 10, cache.size_in_bytes); + assert!(matches!( + cache.get(&ValueCacheKey::new_ref(&key, root0)).unwrap(), + CachedValue::::NonExisting + )); + assert!(cache.get(&ValueCacheKey::new_ref(&key, root1)).is_none()); + + cache.update( + vec![(ValueCacheKey::new_value(vec![10; 10], root0), CachedValue::NonExisting)], + vec![], + ); + + assert!(cache.known_storage_keys.get(&key[..]).is_none()); + } + + #[test] + fn value_cache_key_eq_works() { + let storage_key = &b"something"[..]; + let storage_key2 = &b"something2"[..]; + let storage_root = Hash::random(); + + let value = ValueCacheKey::new_value(storage_key, storage_root); + // Ref gets the same hash, but a different storage key + let ref_ = + ValueCacheKey::Ref { storage_root, storage_key: storage_key2, hash: value.get_hash() }; + let hash = ValueCacheKey::Hash { + hash: value.get_hash(), + _i_read_the_documentation: IReadTheDocumentation(()), + }; + + // Ensure that the hash variants is equal to `value`, `ref_` and itself. + assert!(hash == value); + assert!(value == hash); + assert!(hash == ref_); + assert!(ref_ == hash); + assert!(hash == hash); + + // But when we compare `value` and `ref_` the different storage key is detected. + assert!(value != ref_); + assert!(ref_ != value); + } +} diff --git a/primitives/trie/src/error.rs b/primitives/trie/src/error.rs index 30a164c614755..a781d408e994f 100644 --- a/primitives/trie/src/error.rs +++ b/primitives/trie/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,42 +15,33 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(feature = "std")] -use std::error::Error as StdError; -#[cfg(feature = "std")] -use std::fmt; +use sp_std::{boxed::Box, vec::Vec}; +/// Error type used for trie related errors. #[derive(Debug, PartialEq, Eq, Clone)] -/// Error for trie node decoding. -pub enum Error { - /// Bad format. +#[cfg_attr(feature = "std", derive(thiserror::Error))] +pub enum Error { + #[cfg_attr(feature = "std", error("Bad format"))] BadFormat, - /// Decoding error. - Decode(codec::Error), + #[cfg_attr(feature = "std", error("Decoding failed: {0}"))] + Decode(#[cfg_attr(feature = "std", source)] codec::Error), + #[cfg_attr( + feature = "std", + error("Recorded key ({0:x?}) access with value as found={1}, but could not confirm with trie.") + )] + InvalidRecording(Vec, bool), + #[cfg_attr(feature = "std", error("Trie error: {0:?}"))] + TrieError(Box>), } -impl From for Error { +impl From for Error { fn from(x: codec::Error) -> Self { Error::Decode(x) } } -#[cfg(feature = "std")] -impl StdError for Error { - fn description(&self) -> &str { - match self { - Error::BadFormat => "Bad format error", - Error::Decode(_) => "Decoding error", - } - } -} - -#[cfg(feature = "std")] -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::Decode(e) => write!(f, "Decode error: {}", e), - Error::BadFormat => write!(f, "Bad format"), - } +impl From>> for Error { + fn from(x: Box>) -> Self { + Error::TrieError(x) } } diff --git a/primitives/trie/src/lib.rs b/primitives/trie/src/lib.rs index 8ba13284d379f..01650e9a376be 100644 --- a/primitives/trie/src/lib.rs +++ b/primitives/trie/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +19,13 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "std")] +pub mod cache; mod error; mod node_codec; mod node_header; +#[cfg(feature = "std")] +pub mod recorder; mod storage_proof; mod trie_codec; mod trie_stream; @@ -31,9 +35,8 @@ pub use error::Error; /// Various re-exports from the `hash-db` crate. pub use hash_db::{HashDB as HashDBT, EMPTY_PREFIX}; use hash_db::{Hasher, Prefix}; -pub use memory_db::prefixed_key; /// Various re-exports from the `memory-db` crate. -pub use memory_db::KeyFunction; +pub use memory_db::{prefixed_key, HashKey, KeyFunction, PrefixedKey}; /// The Substrate format implementation of `NodeCodec`. pub use node_codec::NodeCodec; use sp_std::{borrow::Borrow, boxed::Box, marker::PhantomData, vec::Vec}; @@ -45,31 +48,43 @@ pub use trie_db::proof::VerifyError; use trie_db::proof::{generate_proof, verify_proof}; /// Various re-exports from the `trie-db` crate. pub use trie_db::{ - nibble_ops, CError, DBValue, Query, Recorder, Trie, TrieConfiguration, TrieDBIterator, - TrieLayout, TrieMut, + nibble_ops, + node::{NodePlan, ValuePlan}, + CError, DBValue, Query, Recorder, Trie, TrieCache, TrieConfiguration, TrieDBIterator, + TrieDBKeyIterator, TrieLayout, TrieMut, TrieRecorder, }; /// The Substrate format implementation of `TrieStream`. pub use trie_stream::TrieStream; -#[derive(Default)] /// substrate trie layout -pub struct Layout(sp_std::marker::PhantomData); +pub struct LayoutV0(PhantomData); -impl TrieLayout for Layout { +/// substrate trie layout, with external value nodes. +pub struct LayoutV1(PhantomData); + +impl TrieLayout for LayoutV0 +where + H: Hasher, +{ const USE_EXTENSION: bool = false; const ALLOW_EMPTY: bool = true; + const MAX_INLINE_VALUE: Option = None; + type Hash = H; type Codec = NodeCodec; } -impl TrieConfiguration for Layout { +impl TrieConfiguration for LayoutV0 +where + H: Hasher, +{ fn trie_root(input: I) -> ::Out where I: IntoIterator, A: AsRef<[u8]> + Ord, B: AsRef<[u8]>, { - trie_root::trie_root_no_extension::(input) + trie_root::trie_root_no_extension::(input, Self::MAX_INLINE_VALUE) } fn trie_root_unhashed(input: I) -> Vec @@ -78,7 +93,10 @@ impl TrieConfiguration for Layout { A: AsRef<[u8]> + Ord, B: AsRef<[u8]>, { - trie_root::unhashed_trie_no_extension::(input) + trie_root::unhashed_trie_no_extension::( + input, + Self::MAX_INLINE_VALUE, + ) } fn encode_index(input: u32) -> Vec { @@ -86,10 +104,47 @@ impl TrieConfiguration for Layout { } } -#[cfg(not(feature = "memory-tracker"))] -type MemTracker = memory_db::NoopTracker; -#[cfg(feature = "memory-tracker")] -type MemTracker = memory_db::MemCounter; +impl TrieLayout for LayoutV1 +where + H: Hasher, +{ + const USE_EXTENSION: bool = false; + const ALLOW_EMPTY: bool = true; + const MAX_INLINE_VALUE: Option = Some(sp_core::storage::TRIE_VALUE_NODE_THRESHOLD); + + type Hash = H; + type Codec = NodeCodec; +} + +impl TrieConfiguration for LayoutV1 +where + H: Hasher, +{ + fn trie_root(input: I) -> ::Out + where + I: IntoIterator, + A: AsRef<[u8]> + Ord, + B: AsRef<[u8]>, + { + trie_root::trie_root_no_extension::(input, Self::MAX_INLINE_VALUE) + } + + fn trie_root_unhashed(input: I) -> Vec + where + I: IntoIterator, + A: AsRef<[u8]> + Ord, + B: AsRef<[u8]>, + { + trie_root::unhashed_trie_no_extension::( + input, + Self::MAX_INLINE_VALUE, + ) + } + + fn encode_index(input: u32) -> Vec { + codec::Encode::encode(&codec::Compact(input)) + } +} /// TrieDB error over `TrieConfiguration` trait. pub type TrieError = trie_db::TrieError, CError>; @@ -101,36 +156,49 @@ pub type HashDB<'a, H> = dyn hash_db::HashDB + 'a; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. /// This uses a `KeyFunction` for prefixing keys internally (avoiding /// key conflict for non random keys). -pub type PrefixedMemoryDB = - memory_db::MemoryDB, trie_db::DBValue, MemTracker>; +pub type PrefixedMemoryDB = memory_db::MemoryDB, trie_db::DBValue>; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. /// This uses a noops `KeyFunction` (key addressing must be hashed or using /// an encoding scheme that avoid key conflict). -pub type MemoryDB = memory_db::MemoryDB, trie_db::DBValue, MemTracker>; +pub type MemoryDB = memory_db::MemoryDB, trie_db::DBValue>; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. -pub type GenericMemoryDB = memory_db::MemoryDB; +pub type GenericMemoryDB = memory_db::MemoryDB; /// Persistent trie database read-access interface for the a given hasher. -pub type TrieDB<'a, L> = trie_db::TrieDB<'a, L>; +pub type TrieDB<'a, 'cache, L> = trie_db::TrieDB<'a, 'cache, L>; +/// Builder for creating a [`TrieDB`]. +pub type TrieDBBuilder<'a, 'cache, L> = trie_db::TrieDBBuilder<'a, 'cache, L>; /// Persistent trie database write-access interface for the a given hasher. pub type TrieDBMut<'a, L> = trie_db::TrieDBMut<'a, L>; +/// Builder for creating a [`TrieDBMut`]. +pub type TrieDBMutBuilder<'a, L> = trie_db::TrieDBMutBuilder<'a, L>; /// Querying interface, as in `trie_db` but less generic. -pub type Lookup<'a, L, Q> = trie_db::Lookup<'a, L, Q>; +pub type Lookup<'a, 'cache, L, Q> = trie_db::Lookup<'a, 'cache, L, Q>; /// Hash type for a trie layout. pub type TrieHash = <::Hash as Hasher>::Out; - /// This module is for non generic definition of trie type. /// Only the `Hasher` trait is generic in this case. pub mod trie_types { - pub type Layout = super::Layout; + use super::*; + /// Persistent trie database read-access interface for the a given hasher. - pub type TrieDB<'a, H> = super::TrieDB<'a, Layout>; + /// + /// Read only V1 and V0 are compatible, thus we always use V1. + pub type TrieDB<'a, 'cache, H> = super::TrieDB<'a, 'cache, LayoutV1>; + /// Builder for creating a [`TrieDB`]. + pub type TrieDBBuilder<'a, 'cache, H> = super::TrieDBBuilder<'a, 'cache, LayoutV1>; + /// Persistent trie database write-access interface for the a given hasher. + pub type TrieDBMutV0<'a, H> = super::TrieDBMut<'a, LayoutV0>; + /// Builder for creating a [`TrieDBMutV0`]. + pub type TrieDBMutBuilderV0<'a, H> = super::TrieDBMutBuilder<'a, LayoutV0>; /// Persistent trie database write-access interface for the a given hasher. - pub type TrieDBMut<'a, H> = super::TrieDBMut<'a, Layout>; + pub type TrieDBMutV1<'a, H> = super::TrieDBMut<'a, LayoutV1>; + /// Builder for creating a [`TrieDBMutV1`]. + pub type TrieDBMutBuilderV1<'a, H> = super::TrieDBMutBuilder<'a, LayoutV1>; /// Querying interface, as in `trie_db` but less generic. - pub type Lookup<'a, H, Q> = trie_db::Lookup<'a, Layout, Q>; + pub type Lookup<'a, 'cache, H, Q> = trie_db::Lookup<'a, 'cache, LayoutV1, Q>; /// As in `trie_db`, but less generic, error type for the crate. - pub type TrieError = trie_db::TrieError; + pub type TrieError = trie_db::TrieError>; } /// Create a proof for a subset of keys in a trie. @@ -141,18 +209,18 @@ pub mod trie_types { /// For a key `K` that is included in the `db` a proof of inclusion is generated. /// For a key `K` that is not included in the `db` a proof of non-inclusion is generated. /// These can be later checked in `verify_trie_proof`. -pub fn generate_trie_proof<'a, L: TrieConfiguration, I, K, DB>( +pub fn generate_trie_proof<'a, L, I, K, DB>( db: &DB, root: TrieHash, keys: I, ) -> Result>, Box>> where + L: TrieConfiguration, I: IntoIterator, K: 'a + AsRef<[u8]>, DB: hash_db::HashDBRef, { - let trie = TrieDB::::new(db, &root)?; - generate_proof(&trie, keys) + generate_proof::<_, L, _, _>(db, &root, keys) } /// Verify a set of key-value pairs against a trie root and a proof. @@ -163,17 +231,18 @@ where /// checked for inclusion in the proof. /// If the value is omitted (`(key, None)`), this key will be checked for non-inclusion in the /// proof. -pub fn verify_trie_proof<'a, L: TrieConfiguration, I, K, V>( +pub fn verify_trie_proof<'a, L, I, K, V>( root: &TrieHash, proof: &[Vec], items: I, -) -> Result<(), VerifyError, error::Error>> +) -> Result<(), VerifyError, CError>> where + L: TrieConfiguration, I: IntoIterator)>, K: 'a + AsRef<[u8]>, V: 'a + AsRef<[u8]>, { - verify_proof::, _, _, _>(root, proof, items) + verify_proof::(root, proof, items) } /// Determine a trie root given a hash DB and delta values. @@ -181,6 +250,8 @@ pub fn delta_trie_root( db: &mut DB, mut root: TrieHash, delta: I, + recorder: Option<&mut dyn trie_db::TrieRecorder>>, + cache: Option<&mut dyn TrieCache>, ) -> Result, Box>> where I: IntoIterator, @@ -190,7 +261,10 @@ where DB: hash_db::HashDB, { { - let mut trie = TrieDBMut::::from_existing(db, &mut root)?; + let mut trie = TrieDBMutBuilder::::from_existing(db, &mut root) + .with_optional_cache(cache) + .with_optional_recorder(recorder) + .build(); let mut delta = delta.into_iter().collect::>(); delta.sort_by(|l, r| l.0.borrow().cmp(r.0.borrow())); @@ -207,18 +281,24 @@ where } /// Read a value from the trie. -pub fn read_trie_value>( +pub fn read_trie_value>( db: &DB, root: &TrieHash, key: &[u8], + recorder: Option<&mut dyn TrieRecorder>>, + cache: Option<&mut dyn TrieCache>, ) -> Result>, Box>> { - TrieDB::::new(&*db, root)?.get(key).map(|x| x.map(|val| val.to_vec())) + TrieDBBuilder::::new(db, root) + .with_optional_cache(cache) + .with_optional_recorder(recorder) + .build() + .get(key) } /// Read a value from the trie with given Query. pub fn read_trie_value_with< - L: TrieConfiguration, - Q: Query, + L: TrieLayout, + Q: Query>, DB: hash_db::HashDBRef, >( db: &DB, @@ -226,9 +306,7 @@ pub fn read_trie_value_with< key: &[u8], query: Q, ) -> Result>, Box>> { - TrieDB::::new(&*db, root)? - .get_with(key, query) - .map(|x| x.map(|val| val.to_vec())) + TrieDBBuilder::::new(db, root).build().get_with(key, query) } /// Determine the empty trie root. @@ -259,6 +337,8 @@ pub fn child_delta_trie_root( db: &mut DB, root_data: RD, delta: I, + recorder: Option<&mut dyn TrieRecorder>>, + cache: Option<&mut dyn TrieCache>, ) -> Result<::Out, Box>> where I: IntoIterator, @@ -272,54 +352,53 @@ where // root is fetched from DB, not writable by runtime, so it's always valid. root.as_mut().copy_from_slice(root_data.as_ref()); - let mut db = KeySpacedDBMut::new(&mut *db, keyspace); - delta_trie_root::(&mut db, root, delta) + let mut db = KeySpacedDBMut::new(db, keyspace); + delta_trie_root::(&mut db, root, delta, recorder, cache) } -/// Record all keys for a given root. -pub fn record_all_keys( +/// Read a value from the child trie. +pub fn read_child_trie_value( + keyspace: &[u8], db: &DB, root: &TrieHash, - recorder: &mut Recorder>, -) -> Result<(), Box>> + key: &[u8], + recorder: Option<&mut dyn TrieRecorder>>, + cache: Option<&mut dyn TrieCache>, +) -> Result>, Box>> where DB: hash_db::HashDBRef, { - let trie = TrieDB::::new(&*db, root)?; - let iter = trie.iter()?; - - for x in iter { - let (key, _) = x?; - - // there's currently no API like iter_with() - // => use iter to enumerate all keys AND lookup each - // key using get_with - trie.get_with(&key, &mut *recorder)?; - } - - Ok(()) + let db = KeySpacedDB::new(db, keyspace); + TrieDBBuilder::::new(&db, &root) + .with_optional_recorder(recorder) + .with_optional_cache(cache) + .build() + .get(key) + .map(|x| x.map(|val| val.to_vec())) } -/// Read a value from the child trie. -pub fn read_child_trie_value( +/// Read a hash from the child trie. +pub fn read_child_trie_hash( keyspace: &[u8], db: &DB, - root_slice: &[u8], + root: &TrieHash, key: &[u8], -) -> Result>, Box>> + recorder: Option<&mut dyn TrieRecorder>>, + cache: Option<&mut dyn TrieCache>, +) -> Result>, Box>> where DB: hash_db::HashDBRef, { - let mut root = TrieHash::::default(); - // root is fetched from DB, not writable by runtime, so it's always valid. - root.as_mut().copy_from_slice(root_slice); - - let db = KeySpacedDB::new(&*db, keyspace); - TrieDB::::new(&db, &root)?.get(key).map(|x| x.map(|val| val.to_vec())) + let db = KeySpacedDB::new(db, keyspace); + TrieDBBuilder::::new(&db, &root) + .with_optional_recorder(recorder) + .with_optional_cache(cache) + .build() + .get_hash(key) } /// Read a value from the child trie with given query. -pub fn read_child_trie_value_with, DB>( +pub fn read_child_trie_value_with( keyspace: &[u8], db: &DB, root_slice: &[u8], @@ -327,27 +406,30 @@ pub fn read_child_trie_value_with Result>, Box>> where + L: TrieConfiguration, + Q: Query, DB: hash_db::HashDBRef, { let mut root = TrieHash::::default(); // root is fetched from DB, not writable by runtime, so it's always valid. root.as_mut().copy_from_slice(root_slice); - let db = KeySpacedDB::new(&*db, keyspace); - TrieDB::::new(&db, &root)? + let db = KeySpacedDB::new(db, keyspace); + TrieDBBuilder::::new(&db, &root) + .build() .get_with(key, query) .map(|x| x.map(|val| val.to_vec())) } /// `HashDB` implementation that append a encoded prefix (unique id bytes) in addition to the /// prefix of every key value. -pub struct KeySpacedDB<'a, DB, H>(&'a DB, &'a [u8], PhantomData); +pub struct KeySpacedDB<'a, DB: ?Sized, H>(&'a DB, &'a [u8], PhantomData); /// `HashDBMut` implementation that append a encoded prefix (unique id bytes) in addition to the /// prefix of every key value. /// /// Mutable variant of `KeySpacedDB`, see [`KeySpacedDB`]. -pub struct KeySpacedDBMut<'a, DB, H>(&'a mut DB, &'a [u8], PhantomData); +pub struct KeySpacedDBMut<'a, DB: ?Sized, H>(&'a mut DB, &'a [u8], PhantomData); /// Utility function used to merge some byte data (keyspace) and `prefix` data /// before calling key value database primitives. @@ -358,20 +440,14 @@ fn keyspace_as_prefix_alloc(ks: &[u8], prefix: Prefix) -> (Vec, Option) (result, prefix.1) } -impl<'a, DB, H> KeySpacedDB<'a, DB, H> -where - H: Hasher, -{ +impl<'a, DB: ?Sized, H> KeySpacedDB<'a, DB, H> { /// instantiate new keyspaced db pub fn new(db: &'a DB, ks: &'a [u8]) -> Self { KeySpacedDB(db, ks, PhantomData) } } -impl<'a, DB, H> KeySpacedDBMut<'a, DB, H> -where - H: Hasher, -{ +impl<'a, DB: ?Sized, H> KeySpacedDBMut<'a, DB, H> { /// instantiate new keyspaced db pub fn new(db: &'a mut DB, ks: &'a [u8]) -> Self { KeySpacedDBMut(db, ks, PhantomData) @@ -380,7 +456,7 @@ where impl<'a, DB, H, T> hash_db::HashDBRef for KeySpacedDB<'a, DB, H> where - DB: hash_db::HashDBRef, + DB: hash_db::HashDBRef + ?Sized, H: Hasher, T: From<&'static [u8]>, { @@ -434,7 +510,7 @@ where T: Default + PartialEq + for<'b> From<&'b [u8]> + Clone + Send + Sync, { fn as_hash_db(&self) -> &dyn hash_db::HashDB { - &*self + self } fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn hash_db::HashDB + 'b) { @@ -444,11 +520,14 @@ where /// Constants used into trie simplification codec. mod trie_constants { - pub const EMPTY_TRIE: u8 = 0; - pub const NIBBLE_SIZE_BOUND: usize = u16::MAX as usize; + const FIRST_PREFIX: u8 = 0b_00 << 6; pub const LEAF_PREFIX_MASK: u8 = 0b_01 << 6; pub const BRANCH_WITHOUT_MASK: u8 = 0b_10 << 6; pub const BRANCH_WITH_MASK: u8 = 0b_11 << 6; + pub const EMPTY_TRIE: u8 = FIRST_PREFIX | (0b_00 << 4); + pub const ALT_HASHING_LEAF_PREFIX_MASK: u8 = FIRST_PREFIX | (0b_1 << 5); + pub const ALT_HASHING_BRANCH_WITH_MASK: u8 = FIRST_PREFIX | (0b_01 << 4); + pub const ESCAPE_COMPACT_HEADER: u8 = EMPTY_TRIE | 0b_00_01; } #[cfg(test)] @@ -456,12 +535,14 @@ mod tests { use super::*; use codec::{Compact, Decode, Encode}; use hash_db::{HashDB, Hasher}; - use hex_literal::hex; use sp_core::Blake2Hasher; use trie_db::{DBValue, NodeCodec as NodeCodecT, Trie, TrieMut}; use trie_standardmap::{Alphabet, StandardMap, ValueMode}; - type Layout = super::Layout; + type LayoutV0 = super::LayoutV0; + type LayoutV1 = super::LayoutV1; + + type MemoryDBMeta = memory_db::MemoryDB, trie_db::DBValue>; fn hashed_null_node() -> TrieHash { ::hashed_null_node() @@ -473,29 +554,29 @@ mod tests { let d = T::trie_root_unhashed(input.clone()); println!("Data: {:#x?}, {:#x?}", d, Blake2Hasher::hash(&d[..])); let persistent = { - let mut memdb = MemoryDB::default(); + let mut memdb = MemoryDBMeta::default(); let mut root = Default::default(); - let mut t = TrieDBMut::::new(&mut memdb, &mut root); + let mut t = TrieDBMutBuilder::::new(&mut memdb, &mut root).build(); for (x, y) in input.iter().rev() { t.insert(x, y).unwrap(); } - t.root().clone() + *t.root() }; assert_eq!(closed_form, persistent); } } fn check_iteration(input: &Vec<(&[u8], &[u8])>) { - let mut memdb = MemoryDB::default(); + let mut memdb = MemoryDBMeta::default(); let mut root = Default::default(); { - let mut t = TrieDBMut::::new(&mut memdb, &mut root); + let mut t = TrieDBMutBuilder::::new(&mut memdb, &mut root).build(); for (x, y) in input.clone() { t.insert(x, y).unwrap(); } } { - let t = TrieDB::::new(&mut memdb, &root).unwrap(); + let t = TrieDBBuilder::::new(&memdb, &root).build(); assert_eq!( input.iter().map(|(i, j)| (i.to_vec(), j.to_vec())).collect::>(), t.iter() @@ -506,14 +587,21 @@ mod tests { } } + fn check_input(input: &Vec<(&[u8], &[u8])>) { + check_equivalent::(input); + check_iteration::(input); + check_equivalent::(input); + check_iteration::(input); + } + #[test] fn default_trie_root() { let mut db = MemoryDB::default(); - let mut root = TrieHash::::default(); - let mut empty = TrieDBMut::::new(&mut db, &mut root); + let mut root = TrieHash::::default(); + let mut empty = TrieDBMutBuilder::::new(&mut db, &mut root).build(); empty.commit(); let root1 = empty.root().as_ref().to_vec(); - let root2: Vec = Layout::trie_root::<_, Vec, Vec>(std::iter::empty()) + let root2: Vec = LayoutV1::trie_root::<_, Vec, Vec>(std::iter::empty()) .as_ref() .iter() .cloned() @@ -525,31 +613,27 @@ mod tests { #[test] fn empty_is_equivalent() { let input: Vec<(&[u8], &[u8])> = vec![]; - check_equivalent::(&input); - check_iteration::(&input); + check_input(&input); } #[test] fn leaf_is_equivalent() { let input: Vec<(&[u8], &[u8])> = vec![(&[0xaa][..], &[0xbb][..])]; - check_equivalent::(&input); - check_iteration::(&input); + check_input(&input); } #[test] fn branch_is_equivalent() { let input: Vec<(&[u8], &[u8])> = vec![(&[0xaa][..], &[0x10][..]), (&[0xba][..], &[0x11][..])]; - check_equivalent::(&input); - check_iteration::(&input); + check_input(&input); } #[test] fn extension_and_branch_is_equivalent() { let input: Vec<(&[u8], &[u8])> = vec![(&[0xaa][..], &[0x10][..]), (&[0xab][..], &[0x11][..])]; - check_equivalent::(&input); - check_iteration::(&input); + check_input(&input); } #[test] @@ -564,8 +648,7 @@ mod tests { let mut d = st.make(); d.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b)); let dr = d.iter().map(|v| (&v.0[..], &v.1[..])).collect(); - check_equivalent::(&dr); - check_iteration::(&dr); + check_input(&dr); } #[test] @@ -575,8 +658,7 @@ mod tests { (&[0xaa, 0xaa][..], &[0xaa][..]), (&[0xaa, 0xbb][..], &[0xab][..]), ]; - check_equivalent::(&input); - check_iteration::(&input); + check_input(&input); } #[test] @@ -589,8 +671,7 @@ mod tests { (&[0xbb, 0xbb][..], &[0xbb][..]), (&[0xbb, 0xcc][..], &[0xbc][..]), ]; - check_equivalent::(&input); - check_iteration::(&input); + check_input(&input); } #[test] @@ -602,8 +683,7 @@ mod tests { ), (&[0xba][..], &[0x11][..]), ]; - check_equivalent::(&input); - check_iteration::(&input); + check_input(&input); } #[test] @@ -618,8 +698,7 @@ mod tests { &b"ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC"[..], ), ]; - check_equivalent::(&input); - check_iteration::(&input); + check_input(&input); } fn populate_trie<'db, T: TrieConfiguration>( @@ -627,7 +706,7 @@ mod tests { root: &'db mut TrieHash, v: &[(Vec, Vec)], ) -> TrieDBMut<'db, T> { - let mut t = TrieDBMut::::new(db, root); + let mut t = TrieDBMutBuilder::::new(db, root).build(); for i in 0..v.len() { let key: &[u8] = &v[i].0; let val: &[u8] = &v[i].1; @@ -648,8 +727,12 @@ mod tests { #[test] fn random_should_work() { + random_should_work_inner::(); + random_should_work_inner::(); + } + fn random_should_work_inner() { let mut seed = ::Out::zero(); - for test_i in 0..10000 { + for test_i in 0..10_000 { if test_i % 50 == 0 { println!("{:?} of 10000 stress tests done", test_i); } @@ -662,27 +745,28 @@ mod tests { } .make_with(seed.as_fixed_bytes_mut()); - let real = Layout::trie_root(x.clone()); + let real = L::trie_root(x.clone()); let mut memdb = MemoryDB::default(); let mut root = Default::default(); - let mut memtrie = populate_trie::(&mut memdb, &mut root, &x); + + let mut memtrie = populate_trie::(&mut memdb, &mut root, &x); memtrie.commit(); if *memtrie.root() != real { println!("TRIE MISMATCH"); - println!(""); + println!(); println!("{:?} vs {:?}", memtrie.root(), real); for i in &x { println!("{:#x?} -> {:#x?}", i.0, i.1); } } assert_eq!(*memtrie.root(), real); - unpopulate_trie::(&mut memtrie, &x); + unpopulate_trie::(&mut memtrie, &x); memtrie.commit(); - let hashed_null_node = hashed_null_node::(); + let hashed_null_node = hashed_null_node::(); if *memtrie.root() != hashed_null_node { println!("- TRIE MISMATCH"); - println!(""); + println!(); println!("{:?} vs {:?}", memtrie.root(), hashed_null_node); for i in &x { println!("{:#x?} -> {:#x?}", i.0, i.1); @@ -699,7 +783,7 @@ mod tests { #[test] fn codec_trie_empty() { let input: Vec<(&[u8], &[u8])> = vec![]; - let trie = Layout::trie_root_unhashed::<_, _, _>(input); + let trie = LayoutV1::trie_root_unhashed(input); println!("trie: {:#x?}", trie); assert_eq!(trie, vec![0x0]); } @@ -707,7 +791,7 @@ mod tests { #[test] fn codec_trie_single_tuple() { let input = vec![(vec![0xaa], vec![0xbb])]; - let trie = Layout::trie_root_unhashed::<_, _, _>(input); + let trie = LayoutV1::trie_root_unhashed(input); println!("trie: {:#x?}", trie); assert_eq!( trie, @@ -723,7 +807,7 @@ mod tests { #[test] fn codec_trie_two_tuples_disjoint_keys() { let input = vec![(&[0x48, 0x19], &[0xfe]), (&[0x13, 0x14], &[0xff])]; - let trie = Layout::trie_root_unhashed::<_, _, _>(input); + let trie = LayoutV1::trie_root_unhashed(input); println!("trie: {:#x?}", trie); let mut ex = Vec::::new(); ex.push(0x80); // branch, no value (0b_10..) no nibble @@ -747,22 +831,32 @@ mod tests { #[test] fn iterator_works() { + iterator_works_inner::(); + iterator_works_inner::(); + } + fn iterator_works_inner() { let pairs = vec![ - (hex!("0103000000000000000464").to_vec(), hex!("0400000000").to_vec()), - (hex!("0103000000000000000469").to_vec(), hex!("0401000000").to_vec()), + ( + array_bytes::hex2bytes_unchecked("0103000000000000000464"), + array_bytes::hex2bytes_unchecked("0400000000"), + ), + ( + array_bytes::hex2bytes_unchecked("0103000000000000000469"), + array_bytes::hex2bytes_unchecked("0401000000"), + ), ]; let mut mdb = MemoryDB::default(); let mut root = Default::default(); let _ = populate_trie::(&mut mdb, &mut root, &pairs); - let trie = TrieDB::::new(&mdb, &root).unwrap(); + let trie = TrieDBBuilder::::new(&mdb, &root).build(); let iter = trie.iter().unwrap(); let mut iter_pairs = Vec::new(); for pair in iter { let (key, value) = pair.unwrap(); - iter_pairs.push((key, value.to_vec())); + iter_pairs.push((key, value)); } assert_eq!(pairs, iter_pairs); @@ -771,21 +865,21 @@ mod tests { #[test] fn proof_non_inclusion_works() { let pairs = vec![ - (hex!("0102").to_vec(), hex!("01").to_vec()), - (hex!("0203").to_vec(), hex!("0405").to_vec()), + (array_bytes::hex2bytes_unchecked("0102"), array_bytes::hex2bytes_unchecked("01")), + (array_bytes::hex2bytes_unchecked("0203"), array_bytes::hex2bytes_unchecked("0405")), ]; let mut memdb = MemoryDB::default(); let mut root = Default::default(); - populate_trie::(&mut memdb, &mut root, &pairs); + populate_trie::(&mut memdb, &mut root, &pairs); - let non_included_key: Vec = hex!("0909").to_vec(); + let non_included_key: Vec = array_bytes::hex2bytes_unchecked("0909"); let proof = - generate_trie_proof::(&memdb, root, &[non_included_key.clone()]) + generate_trie_proof::(&memdb, root, &[non_included_key.clone()]) .unwrap(); // Verifying that the K was not included into the trie should work. - assert!(verify_trie_proof::>( + assert!(verify_trie_proof::>( &root, &proof, &[(non_included_key.clone(), None)], @@ -793,10 +887,10 @@ mod tests { .is_ok()); // Verifying that the K was included into the trie should fail. - assert!(verify_trie_proof::>( + assert!(verify_trie_proof::>( &root, &proof, - &[(non_included_key, Some(hex!("1010").to_vec()))], + &[(non_included_key, Some(array_bytes::hex2bytes_unchecked("1010")))], ) .is_err()); } @@ -804,19 +898,19 @@ mod tests { #[test] fn proof_inclusion_works() { let pairs = vec![ - (hex!("0102").to_vec(), hex!("01").to_vec()), - (hex!("0203").to_vec(), hex!("0405").to_vec()), + (array_bytes::hex2bytes_unchecked("0102"), array_bytes::hex2bytes_unchecked("01")), + (array_bytes::hex2bytes_unchecked("0203"), array_bytes::hex2bytes_unchecked("0405")), ]; let mut memdb = MemoryDB::default(); let mut root = Default::default(); - populate_trie::(&mut memdb, &mut root, &pairs); + populate_trie::(&mut memdb, &mut root, &pairs); let proof = - generate_trie_proof::(&memdb, root, &[pairs[0].0.clone()]).unwrap(); + generate_trie_proof::(&memdb, root, &[pairs[0].0.clone()]).unwrap(); // Check that a K, V included into the proof are verified. - assert!(verify_trie_proof::( + assert!(verify_trie_proof::( &root, &proof, &[(pairs[0].0.clone(), Some(pairs[0].1.clone()))] @@ -824,7 +918,7 @@ mod tests { .is_ok()); // Absence of the V is not verified with the proof that has K, V included. - assert!(verify_trie_proof::>( + assert!(verify_trie_proof::>( &root, &proof, &[(pairs[0].0.clone(), None)] @@ -832,15 +926,15 @@ mod tests { .is_err()); // K not included into the trie is not verified. - assert!(verify_trie_proof::( + assert!(verify_trie_proof::( &root, &proof, - &[(hex!("4242").to_vec(), Some(pairs[0].1.clone()))] + &[(array_bytes::hex2bytes_unchecked("4242"), Some(pairs[0].1.clone()))] ) .is_err()); // K included into the trie but not included into the proof is not verified. - assert!(verify_trie_proof::( + assert!(verify_trie_proof::( &root, &proof, &[(pairs[1].0.clone(), Some(pairs[1].1.clone()))] @@ -865,19 +959,49 @@ mod tests { .unwrap(); let proof_db = proof.into_memory_db::(); - let first_storage_root = delta_trie_root::( + let first_storage_root = delta_trie_root::( &mut proof_db.clone(), storage_root, valid_delta, + None, + None, ) .unwrap(); - let second_storage_root = delta_trie_root::( + let second_storage_root = delta_trie_root::( &mut proof_db.clone(), storage_root, invalid_delta, + None, + None, ) .unwrap(); assert_eq!(first_storage_root, second_storage_root); } + + #[test] + fn big_key() { + let check = |keysize: usize| { + let mut memdb = PrefixedMemoryDB::::default(); + let mut root = Default::default(); + let mut t = TrieDBMutBuilder::::new(&mut memdb, &mut root).build(); + t.insert(&vec![0x01u8; keysize][..], &[0x01u8, 0x23]).unwrap(); + std::mem::drop(t); + let t = TrieDBBuilder::::new(&memdb, &root).build(); + assert_eq!(t.get(&vec![0x01u8; keysize][..]).unwrap(), Some(vec![0x01u8, 0x23])); + }; + check(u16::MAX as usize / 2); // old limit + check(u16::MAX as usize / 2 + 1); // value over old limit still works + } + + #[test] + fn node_with_no_children_fail_decoding() { + let branch = NodeCodec::::branch_node_nibbled( + b"some_partial".iter().copied(), + 24, + vec![None; 16].into_iter(), + Some(trie_db::node::Value::Inline(b"value"[..].into())), + ); + assert!(NodeCodec::::decode(branch.as_slice()).is_err()); + } } diff --git a/primitives/trie/src/node_codec.rs b/primitives/trie/src/node_codec.rs index d5ffb3219cf68..f632320dd296d 100644 --- a/primitives/trie/src/node_codec.rs +++ b/primitives/trie/src/node_codec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,9 +23,9 @@ use codec::{Compact, Decode, Encode, Input}; use hash_db::Hasher; use sp_std::{borrow::Borrow, marker::PhantomData, ops::Range, vec::Vec}; use trie_db::{ - self, nibble_ops, - node::{NibbleSlicePlan, NodeHandlePlan, NodePlan}, - ChildReference, NodeCodec as NodeCodecT, Partial, + nibble_ops, + node::{NibbleSlicePlan, NodeHandlePlan, NodePlan, Value, ValuePlan}, + ChildReference, NodeCodec as NodeCodecT, }; /// Helper struct for trie node decoder. This implements `codec::Input` on a byte slice, while @@ -54,9 +54,7 @@ impl<'a> ByteSliceInput<'a> { impl<'a> Input for ByteSliceInput<'a> { fn remaining_len(&mut self) -> Result, codec::Error> { - let remaining = - if self.offset <= self.data.len() { Some(self.data.len() - self.offset) } else { None }; - Ok(remaining) + Ok(Some(self.data.len().saturating_sub(self.offset))) } fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> { @@ -76,23 +74,40 @@ impl<'a> Input for ByteSliceInput<'a> { } } -/// Concrete implementation of a `NodeCodec` with Parity Codec encoding, generic over the `Hasher` +/// Concrete implementation of a [`NodeCodecT`] with SCALE encoding. +/// +/// It is generic over `H` the [`Hasher`]. #[derive(Default, Clone)] pub struct NodeCodec(PhantomData); -impl NodeCodecT for NodeCodec { - type Error = Error; +impl NodeCodecT for NodeCodec +where + H: Hasher, +{ + const ESCAPE_HEADER: Option = Some(trie_constants::ESCAPE_COMPACT_HEADER); + type Error = Error; type HashOut = H::Out; fn hashed_null_node() -> ::Out { H::hash(::empty_node()) } - fn decode_plan(data: &[u8]) -> sp_std::result::Result { + fn decode_plan(data: &[u8]) -> Result { let mut input = ByteSliceInput::new(data); - match NodeHeader::decode(&mut input)? { + + let header = NodeHeader::decode(&mut input)?; + let contains_hash = header.contains_hash_of_value(); + + let branch_has_value = if let NodeHeader::Branch(has_value, _) = &header { + *has_value + } else { + // hashed_value_branch + true + }; + + match header { NodeHeader::Null => Ok(NodePlan::Empty), - NodeHeader::Branch(has_value, nibble_count) => { + NodeHeader::HashedValueBranch(nibble_count) | NodeHeader::Branch(_, nibble_count) => { let padding = nibble_count % nibble_ops::NIBBLE_PER_BYTE != 0; // check that the padding is valid (if any) if padding && nibble_ops::pad_left(data[input.offset]) != 0 { @@ -105,9 +120,13 @@ impl NodeCodecT for NodeCodec { let partial_padding = nibble_ops::number_padding(nibble_count); let bitmap_range = input.take(BITMAP_LENGTH)?; let bitmap = Bitmap::decode(&data[bitmap_range])?; - let value = if has_value { - let count = >::decode(&mut input)?.0 as usize; - Some(input.take(count)?) + let value = if branch_has_value { + Some(if contains_hash { + ValuePlan::Node(input.take(H::LENGTH)?) + } else { + let count = >::decode(&mut input)?.0 as usize; + ValuePlan::Inline(input.take(count)?) + }) } else { None }; @@ -132,7 +151,7 @@ impl NodeCodecT for NodeCodec { children, }) }, - NodeHeader::Leaf(nibble_count) => { + NodeHeader::HashedValueLeaf(nibble_count) | NodeHeader::Leaf(nibble_count) => { let padding = nibble_count % nibble_ops::NIBBLE_PER_BYTE != 0; // check that the padding is valid (if any) if padding && nibble_ops::pad_left(data[input.offset]) != 0 { @@ -143,10 +162,16 @@ impl NodeCodecT for NodeCodec { nibble_ops::NIBBLE_PER_BYTE, )?; let partial_padding = nibble_ops::number_padding(nibble_count); - let count = >::decode(&mut input)?.0 as usize; + let value = if contains_hash { + ValuePlan::Node(input.take(H::LENGTH)?) + } else { + let count = >::decode(&mut input)?.0 as usize; + ValuePlan::Inline(input.take(count)?) + }; + Ok(NodePlan::Leaf { partial: NibbleSlicePlan::new(partial, partial_padding), - value: input.take(count)?, + value, }) }, } @@ -160,9 +185,23 @@ impl NodeCodecT for NodeCodec { &[trie_constants::EMPTY_TRIE] } - fn leaf_node(partial: Partial, value: &[u8]) -> Vec { - let mut output = partial_encode(partial, NodeKind::Leaf); - value.encode_to(&mut output); + fn leaf_node(partial: impl Iterator, number_nibble: usize, value: Value) -> Vec { + let contains_hash = matches!(&value, Value::Node(..)); + let mut output = if contains_hash { + partial_from_iterator_encode(partial, number_nibble, NodeKind::HashedValueLeaf) + } else { + partial_from_iterator_encode(partial, number_nibble, NodeKind::Leaf) + }; + match value { + Value::Inline(value) => { + Compact(value.len() as u32).encode_to(&mut output); + output.extend_from_slice(value); + }, + Value::Node(hash) => { + debug_assert!(hash.len() == H::LENGTH); + output.extend_from_slice(hash); + }, + } output } @@ -171,33 +210,46 @@ impl NodeCodecT for NodeCodec { _nbnibble: usize, _child: ChildReference<::Out>, ) -> Vec { - unreachable!() + unreachable!("No extension codec.") } fn branch_node( _children: impl Iterator::Out>>>>, - _maybe_value: Option<&[u8]>, + _maybe_value: Option, ) -> Vec { - unreachable!() + unreachable!("No extension codec.") } fn branch_node_nibbled( partial: impl Iterator, number_nibble: usize, children: impl Iterator::Out>>>>, - maybe_value: Option<&[u8]>, + value: Option, ) -> Vec { - let mut output = if maybe_value.is_some() { - partial_from_iterator_encode(partial, number_nibble, NodeKind::BranchWithValue) - } else { - partial_from_iterator_encode(partial, number_nibble, NodeKind::BranchNoValue) + let contains_hash = matches!(&value, Some(Value::Node(..))); + let mut output = match (&value, contains_hash) { + (&None, _) => + partial_from_iterator_encode(partial, number_nibble, NodeKind::BranchNoValue), + (_, false) => + partial_from_iterator_encode(partial, number_nibble, NodeKind::BranchWithValue), + (_, true) => + partial_from_iterator_encode(partial, number_nibble, NodeKind::HashedValueBranch), }; + let bitmap_index = output.len(); let mut bitmap: [u8; BITMAP_LENGTH] = [0; BITMAP_LENGTH]; (0..BITMAP_LENGTH).for_each(|_| output.push(0)); - if let Some(value) = maybe_value { - value.encode_to(&mut output); - }; + match value { + Some(Value::Inline(value)) => { + Compact(value.len() as u32).encode_to(&mut output); + output.extend_from_slice(value); + }, + Some(Value::Node(hash)) => { + debug_assert!(hash.len() == H::LENGTH); + output.extend_from_slice(hash); + }, + None => (), + } Bitmap::encode( children.map(|maybe_child| match maybe_child.borrow() { Some(ChildReference::Hash(h)) => { @@ -227,39 +279,20 @@ fn partial_from_iterator_encode>( nibble_count: usize, node_kind: NodeKind, ) -> Vec { - let nibble_count = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, nibble_count); - - let mut output = Vec::with_capacity(3 + (nibble_count / nibble_ops::NIBBLE_PER_BYTE)); + let mut output = Vec::with_capacity(4 + (nibble_count / nibble_ops::NIBBLE_PER_BYTE)); match node_kind { NodeKind::Leaf => NodeHeader::Leaf(nibble_count).encode_to(&mut output), NodeKind::BranchWithValue => NodeHeader::Branch(true, nibble_count).encode_to(&mut output), NodeKind::BranchNoValue => NodeHeader::Branch(false, nibble_count).encode_to(&mut output), + NodeKind::HashedValueLeaf => + NodeHeader::HashedValueLeaf(nibble_count).encode_to(&mut output), + NodeKind::HashedValueBranch => + NodeHeader::HashedValueBranch(nibble_count).encode_to(&mut output), }; output.extend(partial); output } -/// Encode and allocate node type header (type and size), and partial value. -/// Same as `partial_from_iterator_encode` but uses non encoded `Partial` as input. -fn partial_encode(partial: Partial, node_kind: NodeKind) -> Vec { - let number_nibble_encoded = (partial.0).0 as usize; - let nibble_count = partial.1.len() * nibble_ops::NIBBLE_PER_BYTE + number_nibble_encoded; - - let nibble_count = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, nibble_count); - - let mut output = Vec::with_capacity(3 + partial.1.len()); - match node_kind { - NodeKind::Leaf => NodeHeader::Leaf(nibble_count).encode_to(&mut output), - NodeKind::BranchWithValue => NodeHeader::Branch(true, nibble_count).encode_to(&mut output), - NodeKind::BranchNoValue => NodeHeader::Branch(false, nibble_count).encode_to(&mut output), - }; - if number_nibble_encoded > 0 { - output.push(nibble_ops::pad_right((partial.0).1)); - } - output.extend_from_slice(partial.1); - output -} - const BITMAP_LENGTH: usize = 2; /// Radix 16 trie, bitmap encoding implementation, @@ -269,8 +302,13 @@ const BITMAP_LENGTH: usize = 2; pub(crate) struct Bitmap(u16); impl Bitmap { - pub fn decode(mut data: &[u8]) -> Result { - Ok(Bitmap(u16::decode(&mut data)?)) + pub fn decode(data: &[u8]) -> Result { + let value = u16::decode(&mut &data[..])?; + if value == 0 { + Err("Bitmap without a child.".into()) + } else { + Ok(Bitmap(value)) + } } pub fn value_at(&self, i: usize) -> bool { diff --git a/primitives/trie/src/node_header.rs b/primitives/trie/src/node_header.rs index 9f05113a35935..f3544be65b2e9 100644 --- a/primitives/trie/src/node_header.rs +++ b/primitives/trie/src/node_header.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,8 +25,20 @@ use sp_std::iter::once; #[derive(Copy, Clone, PartialEq, Eq, sp_core::RuntimeDebug)] pub(crate) enum NodeHeader { Null, + // contains wether there is a value and nibble count Branch(bool, usize), + // contains nibble count Leaf(usize), + // contains nibble count. + HashedValueBranch(usize), + // contains nibble count. + HashedValueLeaf(usize), +} + +impl NodeHeader { + pub(crate) fn contains_hash_of_value(&self) -> bool { + matches!(self, NodeHeader::HashedValueBranch(_) | NodeHeader::HashedValueLeaf(_)) + } } /// NodeHeader without content @@ -34,6 +46,8 @@ pub(crate) enum NodeKind { Leaf, BranchNoValue, BranchWithValue, + HashedValueLeaf, + HashedValueBranch, } impl Encode for NodeHeader { @@ -41,11 +55,27 @@ impl Encode for NodeHeader { match self { NodeHeader::Null => output.push_byte(trie_constants::EMPTY_TRIE), NodeHeader::Branch(true, nibble_count) => - encode_size_and_prefix(*nibble_count, trie_constants::BRANCH_WITH_MASK, output), - NodeHeader::Branch(false, nibble_count) => - encode_size_and_prefix(*nibble_count, trie_constants::BRANCH_WITHOUT_MASK, output), + encode_size_and_prefix(*nibble_count, trie_constants::BRANCH_WITH_MASK, 2, output), + NodeHeader::Branch(false, nibble_count) => encode_size_and_prefix( + *nibble_count, + trie_constants::BRANCH_WITHOUT_MASK, + 2, + output, + ), NodeHeader::Leaf(nibble_count) => - encode_size_and_prefix(*nibble_count, trie_constants::LEAF_PREFIX_MASK, output), + encode_size_and_prefix(*nibble_count, trie_constants::LEAF_PREFIX_MASK, 2, output), + NodeHeader::HashedValueBranch(nibble_count) => encode_size_and_prefix( + *nibble_count, + trie_constants::ALT_HASHING_BRANCH_WITH_MASK, + 4, + output, + ), + NodeHeader::HashedValueLeaf(nibble_count) => encode_size_and_prefix( + *nibble_count, + trie_constants::ALT_HASHING_LEAF_PREFIX_MASK, + 3, + output, + ), } } } @@ -59,13 +89,22 @@ impl Decode for NodeHeader { return Ok(NodeHeader::Null) } match i & (0b11 << 6) { - trie_constants::LEAF_PREFIX_MASK => Ok(NodeHeader::Leaf(decode_size(i, input)?)), - trie_constants::BRANCH_WITHOUT_MASK => - Ok(NodeHeader::Branch(false, decode_size(i, input)?)), + trie_constants::LEAF_PREFIX_MASK => Ok(NodeHeader::Leaf(decode_size(i, input, 2)?)), trie_constants::BRANCH_WITH_MASK => - Ok(NodeHeader::Branch(true, decode_size(i, input)?)), - // do not allow any special encoding - _ => Err("Unallowed encoding".into()), + Ok(NodeHeader::Branch(true, decode_size(i, input, 2)?)), + trie_constants::BRANCH_WITHOUT_MASK => + Ok(NodeHeader::Branch(false, decode_size(i, input, 2)?)), + trie_constants::EMPTY_TRIE => { + if i & (0b111 << 5) == trie_constants::ALT_HASHING_LEAF_PREFIX_MASK { + Ok(NodeHeader::HashedValueLeaf(decode_size(i, input, 3)?)) + } else if i & (0b1111 << 4) == trie_constants::ALT_HASHING_BRANCH_WITH_MASK { + Ok(NodeHeader::HashedValueBranch(decode_size(i, input, 4)?)) + } else { + // do not allow any special encoding + Err("Unallowed encoding".into()) + } + }, + _ => unreachable!(), } } } @@ -73,12 +112,18 @@ impl Decode for NodeHeader { /// Returns an iterator over encoded bytes for node header and size. /// Size encoding allows unlimited, length inefficient, representation, but /// is bounded to 16 bit maximum value to avoid possible DOS. -pub(crate) fn size_and_prefix_iterator(size: usize, prefix: u8) -> impl Iterator { - let size = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, size); - - let l1 = sp_std::cmp::min(62, size); - let (first_byte, mut rem) = - if size == l1 { (once(prefix + l1 as u8), 0) } else { (once(prefix + 63), size - l1) }; +pub(crate) fn size_and_prefix_iterator( + size: usize, + prefix: u8, + prefix_mask: usize, +) -> impl Iterator { + let max_value = 255u8 >> prefix_mask; + let l1 = sp_std::cmp::min((max_value as usize).saturating_sub(1), size); + let (first_byte, mut rem) = if size == l1 { + (once(prefix + l1 as u8), 0) + } else { + (once(prefix + max_value as u8), size - l1) + }; let next_bytes = move || { if rem > 0 { if rem < 256 { @@ -97,25 +142,32 @@ pub(crate) fn size_and_prefix_iterator(size: usize, prefix: u8) -> impl Iterator } /// Encodes size and prefix to a stream output. -fn encode_size_and_prefix(size: usize, prefix: u8, out: &mut W) { - for b in size_and_prefix_iterator(size, prefix) { +fn encode_size_and_prefix(size: usize, prefix: u8, prefix_mask: usize, out: &mut W) +where + W: Output + ?Sized, +{ + for b in size_and_prefix_iterator(size, prefix, prefix_mask) { out.push_byte(b) } } /// Decode size only from stream input and header byte. -fn decode_size(first: u8, input: &mut impl Input) -> Result { - let mut result = (first & 255u8 >> 2) as usize; - if result < 63 { +fn decode_size( + first: u8, + input: &mut impl Input, + prefix_mask: usize, +) -> Result { + let max_value = 255u8 >> prefix_mask; + let mut result = (first & max_value) as usize; + if result < max_value as usize { return Ok(result) } result -= 1; - while result <= trie_constants::NIBBLE_SIZE_BOUND { + loop { let n = input.read_byte()? as usize; if n < 255 { return Ok(result + n + 1) } result += 255; } - Ok(trie_constants::NIBBLE_SIZE_BOUND) } diff --git a/primitives/trie/src/recorder.rs b/primitives/trie/src/recorder.rs new file mode 100644 index 0000000000000..bc67cfc287942 --- /dev/null +++ b/primitives/trie/src/recorder.rs @@ -0,0 +1,302 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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. + +//! Trie recorder +//! +//! Provides an implementation of the [`TrieRecorder`](trie_db::TrieRecorder) trait. It can be used +//! to record storage accesses to the state to generate a [`StorageProof`]. + +use crate::{NodeCodec, StorageProof}; +use codec::Encode; +use hash_db::Hasher; +use parking_lot::Mutex; +use std::{ + collections::HashMap, + marker::PhantomData, + mem, + ops::DerefMut, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, +}; +use trie_db::{RecordedForKey, TrieAccess}; + +const LOG_TARGET: &str = "trie-recorder"; + +/// The internals of [`Recorder`]. +struct RecorderInner { + /// The keys for that we have recorded the trie nodes and if we have recorded up to the value. + recorded_keys: HashMap, RecordedForKey>>, + /// The encoded nodes we accessed while recording. + accessed_nodes: HashMap>, +} + +impl Default for RecorderInner { + fn default() -> Self { + Self { recorded_keys: Default::default(), accessed_nodes: Default::default() } + } +} + +/// The trie recorder. +/// +/// It can be used to record accesses to the trie and then to convert them into a [`StorageProof`]. +pub struct Recorder { + inner: Arc>>, + /// The estimated encoded size of the storage proof this recorder will produce. + /// + /// We store this in an atomic to be able to fetch the value while the `inner` is may locked. + encoded_size_estimation: Arc, +} + +impl Default for Recorder { + fn default() -> Self { + Self { inner: Default::default(), encoded_size_estimation: Arc::new(0.into()) } + } +} + +impl Clone for Recorder { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + encoded_size_estimation: self.encoded_size_estimation.clone(), + } + } +} + +impl Recorder { + /// Returns the recorder as [`TrieRecorder`](trie_db::TrieRecorder) compatible type. + /// + /// - `storage_root`: The storage root of the trie for which accesses are recorded. This is + /// important when recording access to different tries at once (like top and child tries). + pub fn as_trie_recorder( + &self, + storage_root: H::Out, + ) -> impl trie_db::TrieRecorder + '_ { + TrieRecorder:: { + inner: self.inner.lock(), + storage_root, + encoded_size_estimation: self.encoded_size_estimation.clone(), + _phantom: PhantomData, + } + } + + /// Drain the recording into a [`StorageProof`]. + /// + /// While a recorder can be cloned, all share the same internal state. After calling this + /// function, all other instances will have their internal state reset as well. + /// + /// If you don't want to drain the recorded state, use [`Self::to_storage_proof`]. + /// + /// Returns the [`StorageProof`]. + pub fn drain_storage_proof(self) -> StorageProof { + let mut recorder = mem::take(&mut *self.inner.lock()); + StorageProof::new(recorder.accessed_nodes.drain().map(|(_, v)| v)) + } + + /// Convert the recording to a [`StorageProof`]. + /// + /// In contrast to [`Self::drain_storage_proof`] this doesn't consumes and doesn't clears the + /// recordings. + /// + /// Returns the [`StorageProof`]. + pub fn to_storage_proof(&self) -> StorageProof { + let recorder = self.inner.lock(); + StorageProof::new(recorder.accessed_nodes.values().cloned()) + } + + /// Returns the estimated encoded size of the proof. + /// + /// The estimation is based on all the nodes that were accessed until now while + /// accessing the trie. + pub fn estimate_encoded_size(&self) -> usize { + self.encoded_size_estimation.load(Ordering::Relaxed) + } + + /// Reset the state. + /// + /// This discards all recorded data. + pub fn reset(&self) { + mem::take(&mut *self.inner.lock()); + self.encoded_size_estimation.store(0, Ordering::Relaxed); + } +} + +/// The [`TrieRecorder`](trie_db::TrieRecorder) implementation. +struct TrieRecorder { + inner: I, + storage_root: H::Out, + encoded_size_estimation: Arc, + _phantom: PhantomData, +} + +impl>> trie_db::TrieRecorder + for TrieRecorder +{ + fn record<'b>(&mut self, access: TrieAccess<'b, H::Out>) { + let mut encoded_size_update = 0; + + match access { + TrieAccess::NodeOwned { hash, node_owned } => { + tracing::trace!( + target: LOG_TARGET, + hash = ?hash, + "Recording node", + ); + + self.inner.accessed_nodes.entry(hash).or_insert_with(|| { + let node = node_owned.to_encoded::>(); + + encoded_size_update += node.encoded_size(); + + node + }); + }, + TrieAccess::EncodedNode { hash, encoded_node } => { + tracing::trace!( + target: LOG_TARGET, + hash = ?hash, + "Recording node", + ); + + self.inner.accessed_nodes.entry(hash).or_insert_with(|| { + let node = encoded_node.into_owned(); + + encoded_size_update += node.encoded_size(); + + node + }); + }, + TrieAccess::Value { hash, value, full_key } => { + tracing::trace!( + target: LOG_TARGET, + hash = ?hash, + key = ?sp_core::hexdisplay::HexDisplay::from(&full_key), + "Recording value", + ); + + self.inner.accessed_nodes.entry(hash).or_insert_with(|| { + let value = value.into_owned(); + + encoded_size_update += value.encoded_size(); + + value + }); + + self.inner + .recorded_keys + .entry(self.storage_root) + .or_default() + .entry(full_key.to_vec()) + .and_modify(|e| *e = RecordedForKey::Value) + .or_insert(RecordedForKey::Value); + }, + TrieAccess::Hash { full_key } => { + tracing::trace!( + target: LOG_TARGET, + key = ?sp_core::hexdisplay::HexDisplay::from(&full_key), + "Recorded hash access for key", + ); + + // We don't need to update the `encoded_size_update` as the hash was already + // accounted for by the recorded node that holds the hash. + self.inner + .recorded_keys + .entry(self.storage_root) + .or_default() + .entry(full_key.to_vec()) + .or_insert(RecordedForKey::Hash); + }, + TrieAccess::NonExisting { full_key } => { + tracing::trace!( + target: LOG_TARGET, + key = ?sp_core::hexdisplay::HexDisplay::from(&full_key), + "Recorded non-existing value access for key", + ); + + // Non-existing access means we recorded all trie nodes up to the value. + // Not the actual value, as it doesn't exist, but all trie nodes to know + // that the value doesn't exist in the trie. + self.inner + .recorded_keys + .entry(self.storage_root) + .or_default() + .entry(full_key.to_vec()) + .and_modify(|e| *e = RecordedForKey::Value) + .or_insert(RecordedForKey::Value); + }, + }; + + self.encoded_size_estimation.fetch_add(encoded_size_update, Ordering::Relaxed); + } + + fn trie_nodes_recorded_for_key(&self, key: &[u8]) -> RecordedForKey { + self.inner + .recorded_keys + .get(&self.storage_root) + .and_then(|k| k.get(key).copied()) + .unwrap_or(RecordedForKey::None) + } +} + +#[cfg(test)] +mod tests { + use trie_db::{Trie, TrieDBBuilder, TrieDBMutBuilder, TrieHash, TrieMut}; + + type MemoryDB = crate::MemoryDB; + type Layout = crate::LayoutV1; + type Recorder = super::Recorder; + + const TEST_DATA: &[(&[u8], &[u8])] = + &[(b"key1", b"val1"), (b"key2", b"val2"), (b"key3", b"val3"), (b"key4", b"val4")]; + + fn create_trie() -> (MemoryDB, TrieHash) { + let mut db = MemoryDB::default(); + let mut root = Default::default(); + + { + let mut trie = TrieDBMutBuilder::::new(&mut db, &mut root).build(); + for (k, v) in TEST_DATA { + trie.insert(k, v).expect("Inserts data"); + } + } + + (db, root) + } + + #[test] + fn recorder_works() { + let (db, root) = create_trie(); + + let recorder = Recorder::default(); + + { + let mut trie_recorder = recorder.as_trie_recorder(root); + let trie = TrieDBBuilder::::new(&db, &root) + .with_recorder(&mut trie_recorder) + .build(); + assert_eq!(TEST_DATA[0].1.to_vec(), trie.get(TEST_DATA[0].0).unwrap().unwrap()); + } + + let storage_proof = recorder.drain_storage_proof(); + let memory_db: MemoryDB = storage_proof.into_memory_db(); + + // Check that we recorded the required data + let trie = TrieDBBuilder::::new(&memory_db, &root).build(); + assert_eq!(TEST_DATA[0].1.to_vec(), trie.get(TEST_DATA[0].0).unwrap().unwrap()); + } +} diff --git a/primitives/trie/src/storage_proof.rs b/primitives/trie/src/storage_proof.rs index cfdb8566ea75f..5351e8de6fd82 100644 --- a/primitives/trie/src/storage_proof.rs +++ b/primitives/trie/src/storage_proof.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,14 @@ use codec::{Decode, Encode}; use hash_db::{HashDB, Hasher}; use scale_info::TypeInfo; -use sp_std::vec::Vec; +use sp_std::{ + collections::btree_set::BTreeSet, + iter::{DoubleEndedIterator, IntoIterator}, + vec::Vec, +}; +// Note that `LayoutV1` usage here (proof compaction) is compatible +// with `LayoutV0`. +use crate::LayoutV1 as Layout; /// A proof that some set of key-value pairs are included in the storage trie. The proof contains /// the storage values so that the partial storage backend can be reconstructed by a verifier that @@ -29,19 +36,13 @@ use sp_std::vec::Vec; /// the serialized nodes and performing the key lookups. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)] pub struct StorageProof { - trie_nodes: Vec>, -} - -/// Storage proof in compact form. -#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)] -pub struct CompactProof { - pub encoded_nodes: Vec>, + trie_nodes: BTreeSet>, } impl StorageProof { /// Constructs a storage proof from a subset of encoded trie nodes in a storage backend. - pub fn new(trie_nodes: Vec>) -> Self { - StorageProof { trie_nodes } + pub fn new(trie_nodes: impl IntoIterator>) -> Self { + StorageProof { trie_nodes: BTreeSet::from_iter(trie_nodes) } } /// Returns a new empty proof. @@ -49,7 +50,7 @@ impl StorageProof { /// An empty proof is capable of only proving trivial statements (ie. that an empty set of /// key-value pairs exist in storage). pub fn empty() -> Self { - StorageProof { trie_nodes: Vec::new() } + StorageProof { trie_nodes: BTreeSet::new() } } /// Returns whether this is an empty proof. @@ -57,51 +58,70 @@ impl StorageProof { self.trie_nodes.is_empty() } - /// Create an iterator over trie nodes constructed from the proof. The nodes are not guaranteed - /// to be traversed in any particular order. - pub fn iter_nodes(self) -> StorageProofNodeIterator { - StorageProofNodeIterator::new(self) + /// Convert into an iterator over encoded trie nodes in lexicographical order constructed + /// from the proof. + pub fn into_iter_nodes(self) -> impl Sized + DoubleEndedIterator> { + self.trie_nodes.into_iter() + } + + /// Create an iterator over encoded trie nodes in lexicographical order constructed + /// from the proof. + pub fn iter_nodes(&self) -> impl Sized + DoubleEndedIterator> { + self.trie_nodes.iter() } /// Convert into plain node vector. - pub fn into_nodes(self) -> Vec> { + pub fn into_nodes(self) -> BTreeSet> { self.trie_nodes } - /// Creates a `MemoryDB` from `Self`. + + /// Creates a [`MemoryDB`](crate::MemoryDB) from `Self`. pub fn into_memory_db(self) -> crate::MemoryDB { self.into() } + /// Creates a [`MemoryDB`](crate::MemoryDB) from `Self` reference. + pub fn to_memory_db(&self) -> crate::MemoryDB { + self.into() + } + /// Merges multiple storage proofs covering potentially different sets of keys into one proof /// covering all keys. The merged proof output may be smaller than the aggregate size of the /// input proofs due to deduplication of trie nodes. - pub fn merge(proofs: I) -> Self - where - I: IntoIterator, - { + pub fn merge(proofs: impl IntoIterator) -> Self { let trie_nodes = proofs .into_iter() - .flat_map(|proof| proof.iter_nodes()) - .collect::>() + .flat_map(|proof| proof.into_iter_nodes()) + .collect::>() .into_iter() .collect(); Self { trie_nodes } } - /// Encode as a compact proof with default - /// trie layout. + /// Encode as a compact proof with default trie layout. pub fn into_compact_proof( self, root: H::Out, - ) -> Result>> { - crate::encode_compact::>(self, root) + ) -> Result>> { + let db = self.into_memory_db(); + crate::encode_compact::, crate::MemoryDB>(&db, &root) + } + + /// Encode as a compact proof with default trie layout. + pub fn to_compact_proof( + &self, + root: H::Out, + ) -> Result>> { + let db = self.to_memory_db(); + crate::encode_compact::, crate::MemoryDB>(&db, &root) } /// Returns the estimated encoded size of the compact proof. /// - /// Runing this operation is a slow operation (build the whole compact proof) and should only be - /// in non sensitive path. + /// Running this operation is a slow operation (build the whole compact proof) and should only + /// be in non sensitive path. + /// /// Return `None` on error. pub fn encoded_compact_size(self, root: H::Out) -> Option { let compact_proof = self.into_compact_proof::(root); @@ -109,6 +129,28 @@ impl StorageProof { } } +impl From for crate::MemoryDB { + fn from(proof: StorageProof) -> Self { + From::from(&proof) + } +} + +impl From<&StorageProof> for crate::MemoryDB { + fn from(proof: &StorageProof) -> Self { + let mut db = crate::MemoryDB::default(); + proof.iter_nodes().for_each(|n| { + db.insert(crate::EMPTY_PREFIX, &n); + }); + db + } +} + +/// Storage proof in compact form. +#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)] +pub struct CompactProof { + pub encoded_nodes: Vec>, +} + impl CompactProof { /// Return an iterator on the compact encoded nodes. pub fn iter_compact_encoded_nodes(&self) -> impl Iterator { @@ -116,57 +158,45 @@ impl CompactProof { } /// Decode to a full storage_proof. - /// - /// Method use a temporary `HashDB`, and `sp_trie::decode_compact` - /// is often better. pub fn to_storage_proof( &self, expected_root: Option<&H::Out>, - ) -> Result<(StorageProof, H::Out), crate::CompactProofError>> { + ) -> Result<(StorageProof, H::Out), crate::CompactProofError>> { let mut db = crate::MemoryDB::::new(&[]); - let root = crate::decode_compact::, _, _>( + let root = crate::decode_compact::, _, _>( &mut db, self.iter_compact_encoded_nodes(), expected_root, )?; Ok(( - StorageProof::new( - db.drain() - .into_iter() - .filter_map(|kv| if (kv.1).1 > 0 { Some((kv.1).0) } else { None }) - .collect(), - ), + StorageProof::new(db.drain().into_iter().filter_map(|kv| { + if (kv.1).1 > 0 { + Some((kv.1).0) + } else { + None + } + })), root, )) } -} -/// An iterator over trie nodes constructed from a storage proof. The nodes are not guaranteed to -/// be traversed in any particular order. -pub struct StorageProofNodeIterator { - inner: > as IntoIterator>::IntoIter, -} - -impl StorageProofNodeIterator { - fn new(proof: StorageProof) -> Self { - StorageProofNodeIterator { inner: proof.trie_nodes.into_iter() } - } -} - -impl Iterator for StorageProofNodeIterator { - type Item = Vec; - - fn next(&mut self) -> Option { - self.inner.next() - } -} + /// Convert self into a [`MemoryDB`](crate::MemoryDB). + /// + /// `expected_root` is the expected root of this compact proof. + /// + /// Returns the memory db and the root of the trie. + pub fn to_memory_db( + &self, + expected_root: Option<&H::Out>, + ) -> Result<(crate::MemoryDB, H::Out), crate::CompactProofError>> + { + let mut db = crate::MemoryDB::::new(&[]); + let root = crate::decode_compact::, _, _>( + &mut db, + self.iter_compact_encoded_nodes(), + expected_root, + )?; -impl From for crate::MemoryDB { - fn from(proof: StorageProof) -> Self { - let mut db = crate::MemoryDB::default(); - for item in proof.iter_nodes() { - db.insert(crate::EMPTY_PREFIX, &item); - } - db + Ok((db, root)) } } diff --git a/primitives/trie/src/trie_codec.rs b/primitives/trie/src/trie_codec.rs index 1596229f2b5de..d5ae9a43fb1eb 100644 --- a/primitives/trie/src/trie_codec.rs +++ b/primitives/trie/src/trie_codec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,80 +20,34 @@ //! This uses compact proof from trie crate and extends //! it to substrate specific layout and child trie system. -use crate::{ - CompactProof, HashDBT, StorageProof, TrieConfiguration, TrieError, TrieHash, EMPTY_PREFIX, -}; +use crate::{CompactProof, HashDBT, TrieConfiguration, TrieHash, EMPTY_PREFIX}; use sp_std::{boxed::Box, vec::Vec}; -#[cfg(feature = "std")] -use std::error::Error as StdError; -#[cfg(feature = "std")] -use std::fmt; -use trie_db::Trie; +use trie_db::{CError, Trie}; /// Error for trie node decoding. -pub enum Error { - /// Verification failed due to root mismatch. - RootMismatch(TrieHash, TrieHash), - /// Missing nodes in proof. +#[derive(Debug)] +#[cfg_attr(feature = "std", derive(thiserror::Error))] +pub enum Error { + #[cfg_attr(feature = "std", error("Invalid root {0:x?}, expected {1:x?}"))] + RootMismatch(H, H), + #[cfg_attr(feature = "std", error("Missing nodes in the proof"))] IncompleteProof, - /// Compact node is not needed. + #[cfg_attr(feature = "std", error("Child node content with no root in proof"))] ExtraneousChildNode, - /// Child content with root not in proof. - ExtraneousChildProof(TrieHash), - /// Bad child trie root. + #[cfg_attr(feature = "std", error("Proof of child trie {0:x?} not in parent proof"))] + ExtraneousChildProof(H), + #[cfg_attr(feature = "std", error("Invalid root {0:x?}, expected {1:x?}"))] InvalidChildRoot(Vec, Vec), - /// Errors from trie crate. - TrieError(Box>), + #[cfg_attr(feature = "std", error("Trie error: {0:?}"))] + TrieError(Box>), } -impl From>> for Error { - fn from(error: Box>) -> Self { +impl From>> for Error { + fn from(error: Box>) -> Self { Error::TrieError(error) } } -#[cfg(feature = "std")] -impl StdError for Error { - fn description(&self) -> &str { - match self { - Error::InvalidChildRoot(..) => "Invalid child root error", - Error::TrieError(..) => "Trie db error", - Error::RootMismatch(..) => "Trie db error", - Error::IncompleteProof => "Incomplete proof", - Error::ExtraneousChildNode => "Extraneous child node", - Error::ExtraneousChildProof(..) => "Extraneous child proof", - } - } -} - -#[cfg(feature = "std")] -impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(&self, f) - } -} - -#[cfg(feature = "std")] -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::InvalidChildRoot(k, v) => write!(f, "InvalidChildRoot at {:x?}: {:x?}", k, v), - Error::TrieError(e) => write!(f, "Trie error: {}", e), - Error::IncompleteProof => write!(f, "Incomplete proof"), - Error::ExtraneousChildNode => write!(f, "Child node content with no root in proof"), - Error::ExtraneousChildProof(root) => { - write!(f, "Proof of child trie {:x?} not in parent proof", root.as_ref()) - }, - Error::RootMismatch(root, expected) => write!( - f, - "Verification error, root is {:x?}, expected: {:x?}", - root.as_ref(), - expected.as_ref(), - ), - } - } -} - /// Decode a compact proof. /// /// Takes as input a destination `db` for decoded node and `encoded` @@ -105,27 +59,26 @@ pub fn decode_compact<'a, L, DB, I>( db: &mut DB, encoded: I, expected_root: Option<&TrieHash>, -) -> Result, Error> +) -> Result, Error, CError>> where L: TrieConfiguration, DB: HashDBT + hash_db::HashDBRef, I: IntoIterator, { let mut nodes_iter = encoded.into_iter(); - let (top_root, _nb_used) = - trie_db::decode_compact_from_iter::(db, &mut nodes_iter)?; + let (top_root, _nb_used) = trie_db::decode_compact_from_iter::(db, &mut nodes_iter)?; // Only check root if expected root is passed as argument. if let Some(expected_root) = expected_root { if expected_root != &top_root { - return Err(Error::RootMismatch(top_root.clone(), expected_root.clone())) + return Err(Error::RootMismatch(top_root, *expected_root)) } } let mut child_tries = Vec::new(); { // fetch child trie roots - let trie = crate::TrieDB::::new(db, &top_root)?; + let trie = crate::TrieDBBuilder::::new(db, &top_root).build(); let mut iter = trie.iter()?; @@ -164,8 +117,7 @@ where let mut nodes_iter = nodes_iter.peekable(); for child_root in child_tries.into_iter() { if previous_extracted_child_trie.is_none() && nodes_iter.peek().is_some() { - let (top_root, _) = - trie_db::decode_compact_from_iter::(db, &mut nodes_iter)?; + let (top_root, _) = trie_db::decode_compact_from_iter::(db, &mut nodes_iter)?; previous_extracted_child_trie = Some(top_root); } @@ -197,14 +149,17 @@ where /// Then parse all child trie root and compress main trie content first /// then all child trie contents. /// Child trie are ordered by the order of their roots in the top trie. -pub fn encode_compact(proof: StorageProof, root: TrieHash) -> Result> +pub fn encode_compact( + partial_db: &DB, + root: &TrieHash, +) -> Result, CError>> where L: TrieConfiguration, + DB: HashDBT + hash_db::HashDBRef, { let mut child_tries = Vec::new(); - let partial_db = proof.into_memory_db(); let mut compact_proof = { - let trie = crate::TrieDB::::new(&partial_db, &root)?; + let trie = crate::TrieDBBuilder::::new(partial_db, root).build(); let mut iter = trie.iter()?; @@ -236,13 +191,13 @@ where }; for child_root in child_tries { - if !HashDBT::::contains(&partial_db, &child_root, EMPTY_PREFIX) { + if !HashDBT::::contains(partial_db, &child_root, EMPTY_PREFIX) { // child proof are allowed to be missing (unused root can be included // due to trie structure modification). continue } - let trie = crate::TrieDB::::new(&partial_db, &child_root)?; + let trie = crate::TrieDBBuilder::::new(partial_db, &child_root).build(); let child_proof = trie_db::encode_compact::(&trie)?; compact_proof.extend(child_proof); diff --git a/primitives/trie/src/trie_stream.rs b/primitives/trie/src/trie_stream.rs index e0e26fea67c2e..435e6a986722e 100644 --- a/primitives/trie/src/trie_stream.rs +++ b/primitives/trie/src/trie_stream.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,21 +18,18 @@ //! `TrieStream` implementation for Substrate's trie format. use crate::{ - node_codec::Bitmap, node_header::{size_and_prefix_iterator, NodeKind}, trie_constants, }; -use codec::Encode; +use codec::{Compact, Encode}; use hash_db::Hasher; use sp_std::vec::Vec; use trie_root; -const BRANCH_NODE_NO_VALUE: u8 = 254; -const BRANCH_NODE_WITH_VALUE: u8 = 255; - -#[derive(Default, Clone)] /// Codec-flavored TrieStream. +#[derive(Default, Clone)] pub struct TrieStream { + /// Current node buffer. buffer: Vec, } @@ -56,55 +53,79 @@ fn branch_node_bit_mask(has_children: impl Iterator) -> (u8, u8) { } /// Create a leaf/branch node, encoding a number of nibbles. -fn fuse_nibbles_node<'a>(nibbles: &'a [u8], kind: NodeKind) -> impl Iterator + 'a { - let size = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, nibbles.len()); - +fn fuse_nibbles_node(nibbles: &[u8], kind: NodeKind) -> impl Iterator + '_ { + let size = nibbles.len(); let iter_start = match kind { - NodeKind::Leaf => size_and_prefix_iterator(size, trie_constants::LEAF_PREFIX_MASK), + NodeKind::Leaf => size_and_prefix_iterator(size, trie_constants::LEAF_PREFIX_MASK, 2), NodeKind::BranchNoValue => - size_and_prefix_iterator(size, trie_constants::BRANCH_WITHOUT_MASK), + size_and_prefix_iterator(size, trie_constants::BRANCH_WITHOUT_MASK, 2), NodeKind::BranchWithValue => - size_and_prefix_iterator(size, trie_constants::BRANCH_WITH_MASK), + size_and_prefix_iterator(size, trie_constants::BRANCH_WITH_MASK, 2), + NodeKind::HashedValueLeaf => + size_and_prefix_iterator(size, trie_constants::ALT_HASHING_LEAF_PREFIX_MASK, 3), + NodeKind::HashedValueBranch => + size_and_prefix_iterator(size, trie_constants::ALT_HASHING_BRANCH_WITH_MASK, 4), }; iter_start .chain(if nibbles.len() % 2 == 1 { Some(nibbles[0]) } else { None }) .chain(nibbles[nibbles.len() % 2..].chunks(2).map(|ch| ch[0] << 4 | ch[1])) } +use trie_root::Value as TrieStreamValue; impl trie_root::TrieStream for TrieStream { fn new() -> Self { - TrieStream { buffer: Vec::new() } + Self { buffer: Vec::new() } } fn append_empty_data(&mut self) { self.buffer.push(trie_constants::EMPTY_TRIE); } - fn append_leaf(&mut self, key: &[u8], value: &[u8]) { - self.buffer.extend(fuse_nibbles_node(key, NodeKind::Leaf)); - value.encode_to(&mut self.buffer); + fn append_leaf(&mut self, key: &[u8], value: TrieStreamValue) { + let kind = match &value { + TrieStreamValue::Inline(..) => NodeKind::Leaf, + TrieStreamValue::Node(..) => NodeKind::HashedValueLeaf, + }; + self.buffer.extend(fuse_nibbles_node(key, kind)); + match &value { + TrieStreamValue::Inline(value) => { + Compact(value.len() as u32).encode_to(&mut self.buffer); + self.buffer.extend_from_slice(value); + }, + TrieStreamValue::Node(hash) => { + self.buffer.extend_from_slice(hash.as_slice()); + }, + }; } fn begin_branch( &mut self, maybe_partial: Option<&[u8]>, - maybe_value: Option<&[u8]>, + maybe_value: Option, has_children: impl Iterator, ) { if let Some(partial) = maybe_partial { - if maybe_value.is_some() { - self.buffer.extend(fuse_nibbles_node(partial, NodeKind::BranchWithValue)); - } else { - self.buffer.extend(fuse_nibbles_node(partial, NodeKind::BranchNoValue)); - } + let kind = match &maybe_value { + None => NodeKind::BranchNoValue, + Some(TrieStreamValue::Inline(..)) => NodeKind::BranchWithValue, + Some(TrieStreamValue::Node(..)) => NodeKind::HashedValueBranch, + }; + + self.buffer.extend(fuse_nibbles_node(partial, kind)); let bm = branch_node_bit_mask(has_children); self.buffer.extend([bm.0, bm.1].iter()); } else { - debug_assert!(false, "trie stream codec only for no extension trie"); - self.buffer.extend(&branch_node(maybe_value.is_some(), has_children)); + unreachable!("trie stream codec only for no extension trie"); } - if let Some(value) = maybe_value { - value.encode_to(&mut self.buffer); + match maybe_value { + None => (), + Some(TrieStreamValue::Inline(value)) => { + Compact(value.len() as u32).encode_to(&mut self.buffer); + self.buffer.extend_from_slice(value); + }, + Some(TrieStreamValue::Node(hash)) => { + self.buffer.extend_from_slice(hash.as_slice()); + }, } } @@ -124,18 +145,3 @@ impl trie_root::TrieStream for TrieStream { self.buffer } } - -fn branch_node(has_value: bool, has_children: impl Iterator) -> [u8; 3] { - let mut result = [0, 0, 0]; - branch_node_buffered(has_value, has_children, &mut result[..]); - result -} - -fn branch_node_buffered(has_value: bool, has_children: I, output: &mut [u8]) -where - I: Iterator, -{ - let first = if has_value { BRANCH_NODE_WITH_VALUE } else { BRANCH_NODE_NO_VALUE }; - output[0] = first; - Bitmap::encode(has_children, &mut output[1..]); -} diff --git a/primitives/version/Cargo.toml b/primitives/version/Cargo.toml index fcab1eeabcaf4..56fabcd566475 100644 --- a/primitives/version/Cargo.toml +++ b/primitives/version/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-version" -version = "4.0.0-dev" +version = "5.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Version module for the Substrate runtime; Provides a function that returns the runtime version." documentation = "https://docs.rs/sp-version" @@ -13,27 +13,27 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -impl-serde = { version = "0.3.1", optional = true } -serde = { version = "1.0.126", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +impl-serde = { version = "0.4.0", optional = true } +parity-wasm = { version = "0.45", optional = true } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +thiserror = { version = "1.0.30", optional = true } +sp-core-hashing-proc-macro = { version = "5.0.0", path = "../core/hashing/proc-macro" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } sp-version-proc-macro = { version = "4.0.0-dev", default-features = false, path = "proc-macro" } -parity-wasm = { version = "0.42.2", optional = true } -thiserror = { version = "1.0.21", optional = true } [features] default = ["std"] std = [ - "impl-serde", - "serde", "codec/std", + "impl-serde", + "parity-wasm", "scale-info/std", - "sp-std/std", + "serde", "sp-runtime/std", - "parity-wasm", + "sp-std/std", "thiserror", ] diff --git a/primitives/version/proc-macro/Cargo.toml b/primitives/version/proc-macro/Cargo.toml index c3c801431434a..3d129d4753a15 100644 --- a/primitives/version/proc-macro/Cargo.toml +++ b/primitives/version/proc-macro/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-version-proc-macro" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Macro for defining a runtime version." documentation = "https://docs.rs/sp-api-proc-macro" @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.3" -syn = { version = "1.0.58", features = ["full", "fold", "extra-traits", "visit"] } -proc-macro2 = "1.0.29" -codec = { package = "parity-scale-codec", version = "2.0.0", features = [ "derive" ] } +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive" ] } +proc-macro2 = "1.0.37" +quote = "1.0.10" +syn = { version = "1.0.98", features = ["full", "fold", "extra-traits", "visit"] } [dev-dependencies] -sp-version = { version = "4.0.0-dev", path = ".." } +sp-version = { version = "5.0.0", path = ".." } diff --git a/primitives/version/proc-macro/src/decl_runtime_version.rs b/primitives/version/proc-macro/src/decl_runtime_version.rs index eef6314be4c81..9a25adfa5fca2 100644 --- a/primitives/version/proc-macro/src/decl_runtime_version.rs +++ b/primitives/version/proc-macro/src/decl_runtime_version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +37,7 @@ pub fn decl_runtime_version_impl(input: proc_macro::TokenStream) -> proc_macro:: } fn decl_runtime_version_impl_inner(item: ItemConst) -> Result { - let runtime_version = ParseRuntimeVersion::parse_expr(&*item.expr)?.build(item.expr.span())?; + let runtime_version = ParseRuntimeVersion::parse_expr(&item.expr)?.build(item.expr.span())?; let link_section = generate_emit_link_section_decl(&runtime_version.encode(), "runtime_version"); @@ -63,6 +63,7 @@ struct RuntimeVersion { impl_version: u32, apis: u8, transaction_version: u32, + state_version: u8, } #[derive(Default, Debug)] @@ -73,6 +74,7 @@ struct ParseRuntimeVersion { spec_version: Option, impl_version: Option, transaction_version: Option, + state_version: Option, } impl ParseRuntimeVersion { @@ -103,7 +105,7 @@ impl ParseRuntimeVersion { parser: impl FnOnce(&Expr) -> Result, ) -> Result<()> { if value.is_some() { - return Err(Error::new(field.span(), "field is already initialized before")) + Err(Error::new(field.span(), "field is already initialized before")) } else { *value = Some(parser(&field.expr)?); Ok(()) @@ -122,6 +124,8 @@ impl ParseRuntimeVersion { parse_once(&mut self.impl_version, field_value, Self::parse_num_literal)?; } else if field_name == "transaction_version" { parse_once(&mut self.transaction_version, field_value, Self::parse_num_literal)?; + } else if field_name == "state_version" { + parse_once(&mut self.state_version, field_value, Self::parse_num_literal_u8)?; } else if field_name == "apis" { // Intentionally ignored // @@ -147,6 +151,18 @@ impl ParseRuntimeVersion { lit.base10_parse::() } + fn parse_num_literal_u8(expr: &Expr) -> Result { + let lit = match *expr { + Expr::Lit(ExprLit { lit: Lit::Int(ref lit), .. }) => lit, + _ => + return Err(Error::new( + expr.span(), + "only numeric literals (e.g. `10`) are supported here", + )), + }; + lit.base10_parse::() + } + fn parse_str_literal(expr: &Expr) -> Result { let mac = match *expr { Expr::Macro(syn::ExprMacro { ref mac, .. }) => mac, @@ -182,6 +198,7 @@ impl ParseRuntimeVersion { spec_version, impl_version, transaction_version, + state_version, } = self; Ok(RuntimeVersion { @@ -191,6 +208,7 @@ impl ParseRuntimeVersion { spec_version: required!(spec_version), impl_version: required!(impl_version), transaction_version: required!(transaction_version), + state_version: required!(state_version), apis: 0, }) } @@ -210,7 +228,6 @@ fn generate_emit_link_section_decl(contents: &[u8], section_name: &str) -> Token #[cfg(test)] mod tests { use super::*; - use codec::DecodeAll; use std::borrow::Cow; #[test] @@ -223,11 +240,13 @@ mod tests { impl_version: 1, apis: 0, transaction_version: 2, + state_version: 1, } .encode(); assert_eq!( - sp_version::RuntimeVersion::decode_all(&mut &version_bytes[..]).unwrap(), + sp_version::RuntimeVersion::decode_with_version_hint(&mut &version_bytes[..], Some(4)) + .unwrap(), sp_version::RuntimeVersion { spec_name: "hello".into(), impl_name: "world".into(), @@ -236,6 +255,7 @@ mod tests { impl_version: 1, apis: Cow::Owned(vec![]), transaction_version: 2, + state_version: 1, }, ); } diff --git a/primitives/version/proc-macro/src/lib.rs b/primitives/version/proc-macro/src/lib.rs index 9a6d4d60bbf9f..8be18b15868f9 100644 --- a/primitives/version/proc-macro/src/lib.rs +++ b/primitives/version/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/version/src/embed.rs b/primitives/version/src/embed.rs index 452762dcf687a..c71849238fe33 100644 --- a/primitives/version/src/embed.rs +++ b/primitives/version/src/embed.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,8 +44,7 @@ pub fn embed_runtime_version( .apis .iter() .map(Encode::encode) - .map(|v| v.into_iter()) - .flatten() + .flat_map(|v| v.into_iter()) .collect::>(); module.set_custom_section("runtime_apis", apis); diff --git a/primitives/version/src/lib.rs b/primitives/version/src/lib.rs index 58216bc494dd7..0bd62f0bac5aa 100644 --- a/primitives/version/src/lib.rs +++ b/primitives/version/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Version module for the Substrate runtime; Provides a function that returns the runtime version. +//! Substrate runtime version +//! +//! Each runtime that should be executed by a Substrate based node needs to have a runtime version. +//! The runtime version is defined by [`RuntimeVersion`]. The runtime version is used to +//! distinguish different runtimes. The most important field is the +//! [`spec_version`](RuntimeVersion::spec_version). The `spec_version` should be increased in a +//! runtime when a new runtime build includes breaking changes that would make other runtimes unable +//! to import blocks built by this runtime or vice-versa, where the new runtime could not import +//! blocks built by the old runtime. The runtime version also carries other version information +//! about the runtime, see [`RuntimeVersion`] for more information on this. +//! +//! Substrate will fetch the runtime version from a `wasm` blob by first checking the +//! `runtime_version` link section or calling the `Core::version` runtime api. The link section can +//! be generated in the runtime using the [`runtime_version`] attribute. The `Core` runtime api also +//! needs to be implemented for the runtime using `impl_runtime_apis!`. #![cfg_attr(not(feature = "std"), no_std)] @@ -26,10 +40,10 @@ use std::collections::HashSet; #[cfg(feature = "std")] use std::fmt; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, Input}; use scale_info::TypeInfo; -pub use sp_runtime::create_runtime_str; use sp_runtime::RuntimeString; +pub use sp_runtime::{create_runtime_str, StateVersion}; #[doc(hidden)] pub use sp_std; @@ -65,6 +79,7 @@ pub mod embed; /// impl_version: 1, /// apis: RUNTIME_API_VERSIONS, /// transaction_version: 2, +/// state_version: 1, /// }; /// /// # const RUNTIME_API_VERSIONS: sp_version::ApisVec = sp_version::create_apis_vec!([]); @@ -104,7 +119,23 @@ pub use sp_version_proc_macro::runtime_version; /// The id is generated by hashing the name of the runtime api with BLAKE2 using a hash size /// of 8 bytes. /// -/// The name of the runtime api is the name of the trait when using `decl_runtime_apis!` macro. +/// The name of the runtime api is the name of the trait when using `decl_runtime_apis!` macro. So, +/// in the following runtime api declaration: +/// +/// ```nocompile +/// decl_runtime_apis! { +/// trait TestApi { +/// fn do_test(); +/// } +/// } +/// ``` +/// +/// The name of the trait would be `TestApi` and would be taken as input to the BLAKE2 hash +/// function. +/// +/// As Rust supports renaming of traits, the name of a runtime api given to `impl_runtime_apis!` +/// doesn't need to be the same as in `decl_runtime_apis!`, but only the name in +/// `decl_runtime_apis!` is the important one! pub type ApiId = [u8; 8]; /// A vector of pairs of `ApiId` and a `u32` for version. @@ -124,7 +155,7 @@ macro_rules! create_apis_vec { /// In particular: bug fixes should result in an increment of `spec_version` and possibly /// `authoring_version`, absolutely not `impl_version` since they change the semantics of the /// runtime. -#[derive(Clone, PartialEq, Eq, Encode, Decode, Default, sp_runtime::RuntimeDebug, TypeInfo)] +#[derive(Clone, PartialEq, Eq, Encode, Default, sp_runtime::RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] pub struct RuntimeVersion { @@ -177,6 +208,53 @@ pub struct RuntimeVersion { /// /// It need *not* change when a new module is added or when a dispatchable is added. pub transaction_version: u32, + + /// Version of the state implementation used by this runtime. + /// Use of an incorrect version is consensus breaking. + pub state_version: u8, +} + +impl RuntimeVersion { + /// `Decode` while giving a "version hint" + /// + /// There exists multiple versions of [`RuntimeVersion`] and they are versioned using the `Core` + /// runtime api: + /// - `Core` version < 3 is a runtime version without a transaction version and state version. + /// - `Core` version 3 is a runtime version without a state version. + /// - `Core` version 4 is the latest runtime version. + pub fn decode_with_version_hint( + input: &mut I, + core_version: Option, + ) -> Result { + let spec_name = Decode::decode(input)?; + let impl_name = Decode::decode(input)?; + let authoring_version = Decode::decode(input)?; + let spec_version = Decode::decode(input)?; + let impl_version = Decode::decode(input)?; + let apis = Decode::decode(input)?; + let core_version = + if core_version.is_some() { core_version } else { core_version_from_apis(&apis) }; + let transaction_version = + if core_version.map(|v| v >= 3).unwrap_or(false) { Decode::decode(input)? } else { 1 }; + let state_version = + if core_version.map(|v| v >= 4).unwrap_or(false) { Decode::decode(input)? } else { 0 }; + Ok(RuntimeVersion { + spec_name, + impl_name, + authoring_version, + spec_version, + impl_version, + apis, + transaction_version, + state_version, + }) + } +} + +impl Decode for RuntimeVersion { + fn decode(input: &mut I) -> Result { + Self::decode_with_version_hint(input, None) + } } #[cfg(feature = "std")] @@ -195,6 +273,17 @@ impl fmt::Display for RuntimeVersion { } } +#[cfg(feature = "std")] +fn has_api_with bool>(apis: &ApisVec, id: &ApiId, predicate: P) -> bool { + apis.iter().any(|(s, v)| s == id && predicate(*v)) +} + +/// Returns the version of the `Core` runtime api. +pub fn core_version_from_apis(apis: &ApisVec) -> Option { + let id = sp_core_hashing_proc_macro::blake2b_64!(b"Core"); + apis.iter().find(|(s, _v)| s == &id).map(|(_s, v)| *v) +} + #[cfg(feature = "std")] impl RuntimeVersion { /// Check if this version matches other version for calling into runtime. @@ -207,7 +296,7 @@ impl RuntimeVersion { /// Check if the given api with `api_id` is implemented and the version passes the given /// `predicate`. pub fn has_api_with bool>(&self, id: &ApiId, predicate: P) -> bool { - self.apis.iter().any(|(s, v)| s == id && predicate(*v)) + has_api_with(&self.apis, id, predicate) } /// Returns the api version found for api with `id`. @@ -216,12 +305,28 @@ impl RuntimeVersion { } } -#[cfg(feature = "std")] +impl RuntimeVersion { + /// Returns state version to use for update. + /// + /// For runtime with core api version less than 4, + /// V0 trie version will be applied to state. + /// Otherwhise, V1 trie version will be use. + pub fn state_version(&self) -> StateVersion { + // If version > than 1, keep using latest version. + self.state_version.try_into().unwrap_or(StateVersion::V1) + } +} + +/// The version of the native runtime. +/// +/// In contrast to the bare [`RuntimeVersion`] this also carries a list of `spec_version`s of +/// runtimes this native runtime can be used to author blocks for. #[derive(Debug)] +#[cfg(feature = "std")] pub struct NativeVersion { /// Basic runtime version info. pub runtime_version: RuntimeVersion, - /// Authoring runtimes that this native runtime supports. + /// Authoring runtimes (`spec_version`s) that this native runtime supports. pub can_author_with: HashSet, } diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index ba8a7b4e4b466..1822ae43edb68 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-wasm-interface" -version = "4.0.0-dev" +version = "7.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Types and traits for interfacing between the host and the wasm runtime." documentation = "https://docs.rs/sp-wasm-interface" @@ -14,11 +14,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -wasmi = { version = "0.9.0", optional = true } -impl-trait-for-tuples = "0.2.1" -sp-std = { version = "4.0.0-dev", path = "../std", default-features = false } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +impl-trait-for-tuples = "0.2.2" +log = { version = "0.4.17", optional = true } +wasmi = { version = "0.13", optional = true } +wasmtime = { version = "1.0.0", default-features = false, optional = true } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] -std = [ "wasmi", "sp-std/std", "codec/std" ] +std = [ "codec/std", "log", "sp-std/std", "wasmi", "wasmtime" ] diff --git a/primitives/wasm-interface/src/lib.rs b/primitives/wasm-interface/src/lib.rs index e1903ef425aeb..1ecff5a0ce91e 100644 --- a/primitives/wasm-interface/src/lib.rs +++ b/primitives/wasm-interface/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,28 @@ use sp_std::{borrow::Cow, iter::Iterator, marker::PhantomData, mem, result, vec, vec::Vec}; -#[cfg(feature = "std")] +#[cfg(feature = "wasmi")] mod wasmi_impl; +#[cfg(not(all(feature = "std", feature = "wasmtime")))] +#[macro_export] +macro_rules! if_wasmtime_is_enabled { + ($($token:tt)*) => {}; +} + +#[cfg(all(feature = "std", feature = "wasmtime"))] +#[macro_export] +macro_rules! if_wasmtime_is_enabled { + ($($token:tt)*) => { + $($token)* + } +} + +if_wasmtime_is_enabled! { + // Reexport wasmtime so that its types are accessible from the procedural macro. + pub use wasmtime; +} + /// Result type used by traits in this crate. #[cfg(feature = "std")] pub type Result = result::Result; @@ -54,7 +73,7 @@ impl From for u8 { } } -impl sp_std::convert::TryFrom for ValueType { +impl TryFrom for ValueType { type Error = (); fn try_from(val: u8) -> sp_std::result::Result { @@ -105,7 +124,8 @@ impl Value { } } -/// Provides `Sealed` trait to prevent implementing trait `PointerType` outside of this crate. +/// Provides `Sealed` trait to prevent implementing trait `PointerType` and `WasmTy` outside of this +/// crate. mod private { pub trait Sealed {} @@ -113,6 +133,9 @@ mod private { impl Sealed for u16 {} impl Sealed for u32 {} impl Sealed for u64 {} + + impl Sealed for i32 {} + impl Sealed for i64 {} } /// Something that can be wrapped in a wasm `Pointer`. @@ -280,68 +303,72 @@ pub trait FunctionContext { fn allocate_memory(&mut self, size: WordSize) -> Result>; /// Deallocate a given memory instance. fn deallocate_memory(&mut self, ptr: Pointer) -> Result<()>; - /// Provides access to the sandbox. - fn sandbox(&mut self) -> &mut dyn Sandbox; -} - -/// Sandbox memory identifier. -pub type MemoryId = u32; - -/// Something that provides access to the sandbox. -pub trait Sandbox { - /// Get sandbox memory from the `memory_id` instance at `offset` into the given buffer. - fn memory_get( - &mut self, - memory_id: MemoryId, - offset: WordSize, - buf_ptr: Pointer, - buf_len: WordSize, - ) -> Result; - /// Set sandbox memory from the given value. - fn memory_set( - &mut self, - memory_id: MemoryId, - offset: WordSize, - val_ptr: Pointer, - val_len: WordSize, - ) -> Result; - /// Delete a memory instance. - fn memory_teardown(&mut self, memory_id: MemoryId) -> Result<()>; - /// Create a new memory instance with the given `initial` size and the `maximum` size. - /// The size is given in wasm pages. - fn memory_new(&mut self, initial: u32, maximum: u32) -> Result; - /// Invoke an exported function by a name. - fn invoke( - &mut self, - instance_id: u32, - export_name: &str, - args: &[u8], - return_val: Pointer, - return_val_len: WordSize, - state: u32, - ) -> Result; - /// Delete a sandbox instance. - fn instance_teardown(&mut self, instance_id: u32) -> Result<()>; - /// Create a new sandbox instance. - fn instance_new( - &mut self, - dispatch_thunk_id: u32, - wasm: &[u8], - raw_env_def: &[u8], - state: u32, - ) -> Result; - - /// Get the value from a global with the given `name`. The sandbox is determined by the - /// given `instance_idx` instance. + /// Registers a panic error message within the executor. + /// + /// This is meant to be used in situations where the runtime + /// encounters an unrecoverable error and intends to panic. + /// + /// Panicking in WASM is done through the [`unreachable`](https://webassembly.github.io/spec/core/syntax/instructions.html#syntax-instr-control) + /// instruction which causes an unconditional trap and immediately aborts + /// the execution. It does not however allow for any diagnostics to be + /// passed through to the host, so while we do know that *something* went + /// wrong we don't have any direct indication of what *exactly* went wrong. + /// + /// As a workaround we use this method right before the execution is + /// actually aborted to pass an error message to the host so that it + /// can associate it with the next trap, and return that to the caller. + /// + /// A WASM trap should be triggered immediately after calling this method; + /// otherwise the error message might be associated with a completely + /// unrelated trap. + /// + /// It should only be called once, however calling it more than once + /// is harmless and will overwrite the previously set error message. + fn register_panic_error_message(&mut self, message: &str); +} + +if_wasmtime_is_enabled! { + /// A trait used to statically register host callbacks with the WASM executor, + /// so that they call be called from within the runtime with minimal overhead. /// - /// Returns `Some(_)` when the requested global variable could be found. - fn get_global_val(&self, instance_idx: u32, name: &str) -> Result>; + /// This is used internally to interface the wasmtime-based executor with the + /// host functions' definitions generated through the runtime interface macro, + /// and is not meant to be used directly. + pub trait HostFunctionRegistry { + type State; + type Error; + type FunctionContext: FunctionContext; + + /// Wraps the given `caller` in a type which implements `FunctionContext` + /// and calls the given `callback`. + fn with_function_context( + caller: wasmtime::Caller, + callback: impl FnOnce(&mut dyn FunctionContext) -> R, + ) -> R; + + /// Registers a given host function with the WASM executor. + /// + /// The function has to be statically callable, and all of its arguments + /// and its return value have to be compatible with WASM FFI. + fn register_static( + &mut self, + fn_name: &str, + func: impl wasmtime::IntoFunc + 'static, + ) -> core::result::Result<(), Self::Error>; + } } /// Something that provides implementations for host functions. -pub trait HostFunctions: 'static { +pub trait HostFunctions: 'static + Send + Sync { /// Returns the host functions `Self` provides. fn host_functions() -> Vec<&'static dyn Function>; + + if_wasmtime_is_enabled! { + /// Statically registers the host functions. + fn register_static(registry: &mut T) -> core::result::Result<(), T::Error> + where + T: HostFunctionRegistry; + } } #[impl_trait_for_tuples::impl_for_tuples(30)] @@ -353,8 +380,146 @@ impl HostFunctions for Tuple { host_functions } + + #[cfg(all(feature = "std", feature = "wasmtime"))] + fn register_static(registry: &mut T) -> core::result::Result<(), T::Error> + where + T: HostFunctionRegistry, + { + for_tuples!( + #( Tuple::register_static(registry)?; )* + ); + + Ok(()) + } +} + +/// A wrapper which merges two sets of host functions, and allows the second set to override +/// the host functions from the first set. +pub struct ExtendedHostFunctions { + phantom: PhantomData<(Base, Overlay)>, +} + +impl HostFunctions for ExtendedHostFunctions +where + Base: HostFunctions, + Overlay: HostFunctions, +{ + fn host_functions() -> Vec<&'static dyn Function> { + let mut base = Base::host_functions(); + let overlay = Overlay::host_functions(); + base.retain(|host_fn| { + !overlay.iter().any(|ext_host_fn| host_fn.name() == ext_host_fn.name()) + }); + base.extend(overlay); + base + } + + if_wasmtime_is_enabled! { + fn register_static(registry: &mut T) -> core::result::Result<(), T::Error> + where + T: HostFunctionRegistry, + { + struct Proxy<'a, T> { + registry: &'a mut T, + seen_overlay: std::collections::HashSet, + seen_base: std::collections::HashSet, + overlay_registered: bool, + } + + impl<'a, T> HostFunctionRegistry for Proxy<'a, T> + where + T: HostFunctionRegistry, + { + type State = T::State; + type Error = T::Error; + type FunctionContext = T::FunctionContext; + + fn with_function_context( + caller: wasmtime::Caller, + callback: impl FnOnce(&mut dyn FunctionContext) -> R, + ) -> R { + T::with_function_context(caller, callback) + } + + fn register_static( + &mut self, + fn_name: &str, + func: impl wasmtime::IntoFunc + 'static, + ) -> core::result::Result<(), Self::Error> { + if self.overlay_registered { + if !self.seen_base.insert(fn_name.to_owned()) { + log::warn!( + target: "extended_host_functions", + "Duplicate base host function: '{}'", + fn_name, + ); + + // TODO: Return an error here? + return Ok(()) + } + + if self.seen_overlay.contains(fn_name) { + // Was already registered when we went through the overlay, so just ignore it. + log::debug!( + target: "extended_host_functions", + "Overriding base host function: '{}'", + fn_name, + ); + + return Ok(()) + } + } else if !self.seen_overlay.insert(fn_name.to_owned()) { + log::warn!( + target: "extended_host_functions", + "Duplicate overlay host function: '{}'", + fn_name, + ); + + // TODO: Return an error here? + return Ok(()) + } + + self.registry.register_static(fn_name, func) + } + } + + let mut proxy = Proxy { + registry, + seen_overlay: Default::default(), + seen_base: Default::default(), + overlay_registered: false, + }; + + // The functions from the `Overlay` can override those from the `Base`, + // so `Overlay` is registered first, and then we skip those functions + // in `Base` if they were already registered from the `Overlay`. + Overlay::register_static(&mut proxy)?; + proxy.overlay_registered = true; + Base::register_static(&mut proxy)?; + + Ok(()) + } + } } +/// A trait for types directly usable at the WASM FFI boundary without any conversion at all. +/// +/// This trait is sealed and should not be implemented downstream. +#[cfg(all(feature = "std", feature = "wasmtime"))] +pub trait WasmTy: wasmtime::WasmTy + private::Sealed {} + +/// A trait for types directly usable at the WASM FFI boundary without any conversion at all. +/// +/// This trait is sealed and should not be implemented downstream. +#[cfg(not(all(feature = "std", feature = "wasmtime")))] +pub trait WasmTy: private::Sealed {} + +impl WasmTy for i32 {} +impl WasmTy for u32 {} +impl WasmTy for i64 {} +impl WasmTy for u64 {} + /// Something that can be converted into a wasm compatible `Value`. pub trait IntoValue { /// The type of the value in wasm. @@ -405,48 +570,6 @@ impl_into_and_from_value! { i64, I64, } -/// Something that can write a primitive to wasm memory location. -pub trait WritePrimitive { - /// Write the given value `t` to the given memory location `ptr`. - fn write_primitive(&mut self, ptr: Pointer, t: T) -> Result<()>; -} - -impl WritePrimitive for &mut dyn FunctionContext { - fn write_primitive(&mut self, ptr: Pointer, t: u32) -> Result<()> { - let r = t.to_le_bytes(); - self.write_memory(ptr.cast(), &r) - } -} - -impl WritePrimitive for &mut dyn FunctionContext { - fn write_primitive(&mut self, ptr: Pointer, t: u64) -> Result<()> { - let r = t.to_le_bytes(); - self.write_memory(ptr.cast(), &r) - } -} - -/// Something that can read a primitive from a wasm memory location. -pub trait ReadPrimitive { - /// Read a primitive from the given memory location `ptr`. - fn read_primitive(&self, ptr: Pointer) -> Result; -} - -impl ReadPrimitive for &mut dyn FunctionContext { - fn read_primitive(&self, ptr: Pointer) -> Result { - let mut r = [0u8; 4]; - self.read_memory_into(ptr.cast(), &mut r)?; - Ok(u32::from_le_bytes(r)) - } -} - -impl ReadPrimitive for &mut dyn FunctionContext { - fn read_primitive(&self, ptr: Pointer) -> Result { - let mut r = [0u8; 8]; - self.read_memory_into(ptr.cast(), &mut r)?; - Ok(u64::from_le_bytes(r)) - } -} - /// Typed value that can be returned from a function. /// /// Basically a `TypedValue` plus `Unit`, for functions which return nothing. diff --git a/primitives/wasm-interface/src/wasmi_impl.rs b/primitives/wasm-interface/src/wasmi_impl.rs index f7e0ec6f16d4a..977b4fc606fa7 100644 --- a/primitives/wasm-interface/src/wasmi_impl.rs +++ b/primitives/wasm-interface/src/wasmi_impl.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,8 @@ // limitations under the License. //! Implementation of conversions between Substrate and wasmi types. - use crate::{Signature, Value, ValueType}; +use sp_std::vec::Vec; impl From for wasmi::RuntimeValue { fn from(value: Value) -> Self { @@ -73,7 +73,7 @@ impl From for wasmi::Signature { impl From<&wasmi::Signature> for Signature { fn from(sig: &wasmi::Signature) -> Self { Signature::new( - sig.params().into_iter().copied().map(Into::into).collect::>(), + sig.params().iter().copied().map(Into::into).collect::>(), sig.return_type().map(Into::into), ) } diff --git a/primitives/weights/Cargo.toml b/primitives/weights/Cargo.toml new file mode 100644 index 0000000000000..00996458362fe --- /dev/null +++ b/primitives/weights/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "sp-weights" +version = "4.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Types and traits for interfacing between the host and the wasm runtime." +documentation = "https://docs.rs/sp-wasm-interface" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +impl-trait-for-tuples = "0.2.2" +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true, features = ["derive"] } +smallvec = "1.8.0" +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../arithmetic" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } + +[features] +default = [ "std" ] +std = [ + "codec/std", + "scale-info/std", + "serde", + "sp-arithmetic/std", + "sp-core/std", + "sp-debug-derive/std", + "sp-std/std" +] +# By default some types have documentation, `full-metadata-docs` allows to add documentation to +# more types in the metadata. +full-metadata-docs = ["scale-info/docs"] diff --git a/primitives/weights/src/lib.rs b/primitives/weights/src/lib.rs new file mode 100644 index 0000000000000..928080d139864 --- /dev/null +++ b/primitives/weights/src/lib.rs @@ -0,0 +1,323 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! # Primitives for transaction weighting. +//! +//! Latest machine specification used to benchmark are: +//! - Digital Ocean: ubuntu-s-2vcpu-4gb-ams3-01 +//! - 2x Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20GHz +//! - 4GB RAM +//! - Ubuntu 19.10 (GNU/Linux 5.3.0-18-generic x86_64) +//! - rustc 1.42.0 (b8cedc004 2020-03-09) + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate self as sp_weights; + +mod weight_meter; +mod weight_v2; + +use codec::{CompactAs, Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use smallvec::SmallVec; +use sp_arithmetic::{ + traits::{BaseArithmetic, SaturatedConversion, Saturating, Unsigned}, + Perbill, +}; +use sp_core::Get; +use sp_debug_derive::RuntimeDebug; + +pub use weight_meter::*; +pub use weight_v2::*; + +pub mod constants { + pub const WEIGHT_REF_TIME_PER_SECOND: u64 = 1_000_000_000_000; + pub const WEIGHT_REF_TIME_PER_MILLIS: u64 = 1_000_000_000; + pub const WEIGHT_REF_TIME_PER_MICROS: u64 = 1_000_000; + pub const WEIGHT_REF_TIME_PER_NANOS: u64 = 1_000; + + pub const WEIGHT_PROOF_SIZE_PER_MB: u64 = 1024 * 1024; + pub const WEIGHT_PROOF_SIZE_PER_KB: u64 = 1024; +} + +/// The old weight type. +/// +/// NOTE: This type exists purely for compatibility purposes! Use [`weight_v2::Weight`] in all other +/// cases. +#[derive( + Decode, + Encode, + CompactAs, + PartialEq, + Eq, + Clone, + Copy, + RuntimeDebug, + Default, + MaxEncodedLen, + TypeInfo, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(transparent))] +pub struct OldWeight(pub u64); + +/// The weight of database operations that the runtime can invoke. +/// +/// NOTE: This is currently only measured in computational time, and will probably +/// be updated all together once proof size is accounted for. +#[derive(Clone, Copy, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] +pub struct RuntimeDbWeight { + pub read: u64, + pub write: u64, +} + +impl RuntimeDbWeight { + pub fn reads(self, r: u64) -> Weight { + Weight::from_ref_time(self.read.saturating_mul(r)) + } + + pub fn writes(self, w: u64) -> Weight { + Weight::from_ref_time(self.write.saturating_mul(w)) + } + + pub fn reads_writes(self, r: u64, w: u64) -> Weight { + let read_weight = self.read.saturating_mul(r); + let write_weight = self.write.saturating_mul(w); + Weight::from_ref_time(read_weight.saturating_add(write_weight)) + } +} + +/// One coefficient and its position in the `WeightToFee`. +/// +/// One term of polynomial is calculated as: +/// +/// ```ignore +/// coeff_integer * x^(degree) + coeff_frac * x^(degree) +/// ``` +/// +/// The `negative` value encodes whether the term is added or subtracted from the +/// overall polynomial result. +#[derive(Clone, Encode, Decode, TypeInfo)] +pub struct WeightToFeeCoefficient { + /// The integral part of the coefficient. + pub coeff_integer: Balance, + /// The fractional part of the coefficient. + pub coeff_frac: Perbill, + /// True iff the coefficient should be interpreted as negative. + pub negative: bool, + /// Degree/exponent of the term. + pub degree: u8, +} + +/// A list of coefficients that represent one polynomial. +pub type WeightToFeeCoefficients = SmallVec<[WeightToFeeCoefficient; 4]>; + +/// A trait that describes the weight to fee calculation. +pub trait WeightToFee { + /// The type that is returned as result from calculation. + type Balance: BaseArithmetic + From + Copy + Unsigned; + + /// Calculates the fee from the passed `weight`. + fn weight_to_fee(weight: &Weight) -> Self::Balance; +} + +/// A trait that describes the weight to fee calculation as polynomial. +/// +/// An implementor should only implement the `polynomial` function. +pub trait WeightToFeePolynomial { + /// The type that is returned as result from polynomial evaluation. + type Balance: BaseArithmetic + From + Copy + Unsigned; + + /// Returns a polynomial that describes the weight to fee conversion. + /// + /// This is the only function that should be manually implemented. Please note + /// that all calculation is done in the probably unsigned `Balance` type. This means + /// that the order of coefficients is important as putting the negative coefficients + /// first will most likely saturate the result to zero mid evaluation. + fn polynomial() -> WeightToFeeCoefficients; +} + +impl WeightToFee for T +where + T: WeightToFeePolynomial, +{ + type Balance = ::Balance; + + /// Calculates the fee from the passed `weight` according to the `polynomial`. + /// + /// This should not be overridden in most circumstances. Calculation is done in the + /// `Balance` type and never overflows. All evaluation is saturating. + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::polynomial() + .iter() + .fold(Self::Balance::saturated_from(0u32), |mut acc, args| { + let w = Self::Balance::saturated_from(weight.ref_time()) + .saturating_pow(args.degree.into()); + + // The sum could get negative. Therefore we only sum with the accumulator. + // The Perbill Mul implementation is non overflowing. + let frac = args.coeff_frac * w; + let integer = args.coeff_integer.saturating_mul(w); + + if args.negative { + acc = acc.saturating_sub(frac); + acc = acc.saturating_sub(integer); + } else { + acc = acc.saturating_add(frac); + acc = acc.saturating_add(integer); + } + + acc + }) + } +} + +/// Implementor of `WeightToFee` that maps one unit of weight to one unit of fee. +pub struct IdentityFee(sp_std::marker::PhantomData); + +impl WeightToFee for IdentityFee +where + T: BaseArithmetic + From + Copy + Unsigned, +{ + type Balance = T; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(weight.ref_time()) + } +} + +/// Implementor of [`WeightToFee`] that uses a constant multiplier. +/// # Example +/// +/// ``` +/// # use sp_core::ConstU128; +/// # use sp_weights::ConstantMultiplier; +/// // Results in a multiplier of 10 for each unit of weight (or length) +/// type LengthToFee = ConstantMultiplier::>; +/// ``` +pub struct ConstantMultiplier(sp_std::marker::PhantomData<(T, M)>); + +impl WeightToFee for ConstantMultiplier +where + T: BaseArithmetic + From + Copy + Unsigned, + M: Get, +{ + type Balance = T; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(weight.ref_time()).saturating_mul(M::get()) + } +} + +#[cfg(test)] +#[allow(dead_code)] +mod tests { + use super::*; + use smallvec::smallvec; + + type Balance = u64; + + // 0.5x^3 + 2.333x^2 + 7x - 10_000 + struct Poly; + impl WeightToFeePolynomial for Poly { + type Balance = Balance; + + fn polynomial() -> WeightToFeeCoefficients { + smallvec![ + WeightToFeeCoefficient { + coeff_integer: 0, + coeff_frac: Perbill::from_float(0.5), + negative: false, + degree: 3 + }, + WeightToFeeCoefficient { + coeff_integer: 2, + coeff_frac: Perbill::from_rational(1u32, 3u32), + negative: false, + degree: 2 + }, + WeightToFeeCoefficient { + coeff_integer: 7, + coeff_frac: Perbill::zero(), + negative: false, + degree: 1 + }, + WeightToFeeCoefficient { + coeff_integer: 10_000, + coeff_frac: Perbill::zero(), + negative: true, + degree: 0 + }, + ] + } + } + + #[test] + fn polynomial_works() { + // 100^3/2=500000 100^2*(2+1/3)=23333 700 -10000 + assert_eq!(Poly::weight_to_fee(&Weight::from_ref_time(100)), 514033); + // 10123^3/2=518677865433 10123^2*(2+1/3)=239108634 70861 -10000 + assert_eq!(Poly::weight_to_fee(&Weight::from_ref_time(10_123)), 518917034928); + } + + #[test] + fn polynomial_does_not_underflow() { + assert_eq!(Poly::weight_to_fee(&Weight::zero()), 0); + assert_eq!(Poly::weight_to_fee(&Weight::from_ref_time(10)), 0); + } + + #[test] + fn polynomial_does_not_overflow() { + assert_eq!(Poly::weight_to_fee(&Weight::MAX), Balance::max_value() - 10_000); + } + + #[test] + fn identity_fee_works() { + assert_eq!(IdentityFee::::weight_to_fee(&Weight::zero()), 0); + assert_eq!(IdentityFee::::weight_to_fee(&Weight::from_ref_time(50)), 50); + assert_eq!(IdentityFee::::weight_to_fee(&Weight::MAX), Balance::max_value()); + } + + #[test] + fn constant_fee_works() { + use sp_core::ConstU128; + assert_eq!( + ConstantMultiplier::>::weight_to_fee(&Weight::zero()), + 0 + ); + assert_eq!( + ConstantMultiplier::>::weight_to_fee(&Weight::from_ref_time( + 50 + )), + 500 + ); + assert_eq!( + ConstantMultiplier::>::weight_to_fee(&Weight::from_ref_time( + 16 + )), + 16384 + ); + assert_eq!( + ConstantMultiplier::>::weight_to_fee( + &Weight::from_ref_time(2) + ), + u128::MAX + ); + } +} diff --git a/primitives/weights/src/weight_meter.rs b/primitives/weights/src/weight_meter.rs new file mode 100644 index 0000000000000..17c5da1502e9e --- /dev/null +++ b/primitives/weights/src/weight_meter.rs @@ -0,0 +1,182 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Contains the `WeightMeter` primitive to meter weight usage. + +use super::Weight; + +use sp_arithmetic::Perbill; + +/// Meters consumed weight and a hard limit for the maximal consumable weight. +/// +/// Can be used to check if enough weight for an operation is available before committing to it. +/// +/// # Example +/// +/// ```rust +/// use sp_weights::{Weight, WeightMeter}; +/// +/// // The weight is limited to (10, 0). +/// let mut meter = WeightMeter::from_limit(Weight::from_parts(10, 0)); +/// // There is enough weight remaining for an operation with (5, 0) weight. +/// assert!(meter.check_accrue(Weight::from_parts(5, 0))); +/// // There is not enough weight remaining for an operation with (6, 0) weight. +/// assert!(!meter.check_accrue(Weight::from_parts(6, 0))); +/// ``` +#[derive(Debug, Clone)] +pub struct WeightMeter { + /// The already consumed weight. + pub consumed: Weight, + + /// The maximal consumable weight. + pub limit: Weight, +} + +impl WeightMeter { + /// Creates [`Self`] from a limit for the maximal consumable weight. + pub fn from_limit(limit: Weight) -> Self { + Self { consumed: Weight::zero(), limit } + } + + /// Creates [`Self`] with the maximal possible limit for the consumable weight. + pub fn max_limit() -> Self { + Self::from_limit(Weight::MAX) + } + + /// The remaining weight that can still be consumed. + pub fn remaining(&self) -> Weight { + self.limit.saturating_sub(self.consumed) + } + + /// The ratio of consumed weight to the limit. + /// + /// Calculates one ratio per component and returns the largest. + pub fn consumed_ratio(&self) -> Perbill { + let time = Perbill::from_rational(self.consumed.ref_time(), self.limit.ref_time()); + let pov = Perbill::from_rational(self.consumed.proof_size(), self.limit.proof_size()); + time.max(pov) + } + + /// Consume some weight and defensively fail if it is over the limit. Saturate in any case. + pub fn defensive_saturating_accrue(&mut self, w: Weight) { + self.consumed.saturating_accrue(w); + debug_assert!(self.consumed.all_lte(self.limit), "Weight counter overflow"); + } + + /// Consume the given weight after checking that it can be consumed. Otherwise do nothing. + pub fn check_accrue(&mut self, w: Weight) -> bool { + self.consumed.checked_add(&w).map_or(false, |test| { + if test.any_gt(self.limit) { + false + } else { + self.consumed = test; + true + } + }) + } + + /// Check if the given weight can be consumed. + pub fn can_accrue(&self, w: Weight) -> bool { + self.consumed.checked_add(&w).map_or(false, |t| t.all_lte(self.limit)) + } +} + +#[cfg(test)] +mod tests { + use crate::*; + + #[test] + fn weight_meter_remaining_works() { + let mut meter = WeightMeter::from_limit(Weight::from_parts(10, 20)); + + assert!(meter.check_accrue(Weight::from_parts(5, 0))); + assert_eq!(meter.consumed, Weight::from_parts(5, 0)); + assert_eq!(meter.remaining(), Weight::from_parts(5, 20)); + + assert!(meter.check_accrue(Weight::from_parts(2, 10))); + assert_eq!(meter.consumed, Weight::from_parts(7, 10)); + assert_eq!(meter.remaining(), Weight::from_parts(3, 10)); + + assert!(meter.check_accrue(Weight::from_parts(3, 10))); + assert_eq!(meter.consumed, Weight::from_parts(10, 20)); + assert_eq!(meter.remaining(), Weight::from_parts(0, 0)); + } + + #[test] + fn weight_meter_can_accrue_works() { + let meter = WeightMeter::from_limit(Weight::from_parts(1, 1)); + + assert!(meter.can_accrue(Weight::from_parts(0, 0))); + assert!(meter.can_accrue(Weight::from_parts(1, 1))); + assert!(!meter.can_accrue(Weight::from_parts(0, 2))); + assert!(!meter.can_accrue(Weight::from_parts(2, 0))); + assert!(!meter.can_accrue(Weight::from_parts(2, 2))); + } + + #[test] + fn weight_meter_check_accrue_works() { + let mut meter = WeightMeter::from_limit(Weight::from_parts(2, 2)); + + assert!(meter.check_accrue(Weight::from_parts(0, 0))); + assert!(meter.check_accrue(Weight::from_parts(1, 1))); + assert!(!meter.check_accrue(Weight::from_parts(0, 2))); + assert!(!meter.check_accrue(Weight::from_parts(2, 0))); + assert!(!meter.check_accrue(Weight::from_parts(2, 2))); + assert!(meter.check_accrue(Weight::from_parts(0, 1))); + assert!(meter.check_accrue(Weight::from_parts(1, 0))); + } + + #[test] + fn weight_meter_check_and_can_accrue_works() { + let mut meter = WeightMeter::max_limit(); + + assert!(meter.can_accrue(Weight::from_parts(u64::MAX, 0))); + assert!(meter.check_accrue(Weight::from_parts(u64::MAX, 0))); + + assert!(meter.can_accrue(Weight::from_parts(0, u64::MAX))); + assert!(meter.check_accrue(Weight::from_parts(0, u64::MAX))); + + assert!(!meter.can_accrue(Weight::from_parts(0, 1))); + assert!(!meter.check_accrue(Weight::from_parts(0, 1))); + + assert!(!meter.can_accrue(Weight::from_parts(1, 0))); + assert!(!meter.check_accrue(Weight::from_parts(1, 0))); + + assert!(meter.can_accrue(Weight::zero())); + assert!(meter.check_accrue(Weight::zero())); + } + + #[test] + fn consumed_ratio_works() { + let mut meter = WeightMeter::from_limit(Weight::from_parts(10, 20)); + + assert!(meter.check_accrue(Weight::from_parts(5, 0))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(50)); + assert!(meter.check_accrue(Weight::from_parts(0, 12))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(60)); + + assert!(meter.check_accrue(Weight::from_parts(2, 0))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(70)); + assert!(meter.check_accrue(Weight::from_parts(0, 4))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(80)); + + assert!(meter.check_accrue(Weight::from_parts(3, 0))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100)); + assert!(meter.check_accrue(Weight::from_parts(0, 4))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100)); + } +} diff --git a/primitives/weights/src/weight_v2.rs b/primitives/weights/src/weight_v2.rs new file mode 100644 index 0000000000000..2933d80099dd7 --- /dev/null +++ b/primitives/weights/src/weight_v2.rs @@ -0,0 +1,462 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 codec::{Decode, Encode, MaxEncodedLen}; +use core::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}; +use sp_arithmetic::traits::{Bounded, CheckedAdd, CheckedSub, Zero}; +use sp_debug_derive::RuntimeDebug; + +use super::*; + +#[derive( + Encode, Decode, MaxEncodedLen, TypeInfo, Eq, PartialEq, Copy, Clone, RuntimeDebug, Default, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Weight { + #[codec(compact)] + /// The weight of computational time used based on some reference hardware. + ref_time: u64, + #[codec(compact)] + /// The weight of storage space used by proof of validity. + proof_size: u64, +} + +impl From for Weight { + fn from(old: OldWeight) -> Self { + Weight::from_ref_time(old.0) + } +} + +impl Weight { + /// Set the reference time part of the weight. + pub const fn set_ref_time(mut self, c: u64) -> Self { + self.ref_time = c; + self + } + + /// Set the storage size part of the weight. + pub const fn set_proof_size(mut self, c: u64) -> Self { + self.proof_size = c; + self + } + + /// Return the reference time part of the weight. + pub const fn ref_time(&self) -> u64 { + self.ref_time + } + + /// Return the storage size part of the weight. + pub const fn proof_size(&self) -> u64 { + self.proof_size + } + + /// Return a mutable reference to the reference time part of the weight. + pub fn ref_time_mut(&mut self) -> &mut u64 { + &mut self.ref_time + } + + /// Return a mutable reference to the storage size part of the weight. + pub fn proof_size_mut(&mut self) -> &mut u64 { + &mut self.proof_size + } + + pub const MAX: Self = Self { ref_time: u64::MAX, proof_size: u64::MAX }; + + /// Get the conservative min of `self` and `other` weight. + pub fn min(&self, other: Self) -> Self { + Self { + ref_time: self.ref_time.min(other.ref_time), + proof_size: self.proof_size.min(other.proof_size), + } + } + + /// Get the aggressive max of `self` and `other` weight. + pub fn max(&self, other: Self) -> Self { + Self { + ref_time: self.ref_time.max(other.ref_time), + proof_size: self.proof_size.max(other.proof_size), + } + } + + /// Try to add some `other` weight while upholding the `limit`. + pub fn try_add(&self, other: &Self, limit: &Self) -> Option { + let total = self.checked_add(other)?; + if total.any_gt(*limit) { + None + } else { + Some(total) + } + } + + /// Construct [`Weight`] with reference time weight and 0 storage size weight. + pub const fn from_ref_time(ref_time: u64) -> Self { + Self { ref_time, proof_size: 0 } + } + + /// Construct [`Weight`] with storage size weight and 0 reference time weight. + pub const fn from_proof_size(proof_size: u64) -> Self { + Self { ref_time: 0, proof_size } + } + + /// Construct [`Weight`] from weight parts, namely reference time and proof size weights. + pub const fn from_parts(ref_time: u64, proof_size: u64) -> Self { + Self { ref_time, proof_size } + } + + /// Saturating [`Weight`] addition. Computes `self + rhs`, saturating at the numeric bounds of + /// all fields instead of overflowing. + pub const fn saturating_add(self, rhs: Self) -> Self { + Self { + ref_time: self.ref_time.saturating_add(rhs.ref_time), + proof_size: self.proof_size.saturating_add(rhs.proof_size), + } + } + + /// Saturating [`Weight`] subtraction. Computes `self - rhs`, saturating at the numeric bounds + /// of all fields instead of overflowing. + pub const fn saturating_sub(self, rhs: Self) -> Self { + Self { + ref_time: self.ref_time.saturating_sub(rhs.ref_time), + proof_size: self.proof_size.saturating_sub(rhs.proof_size), + } + } + + /// Saturating [`Weight`] scalar multiplication. Computes `self.field * scalar` for all fields, + /// saturating at the numeric bounds of all fields instead of overflowing. + pub const fn saturating_mul(self, scalar: u64) -> Self { + Self { + ref_time: self.ref_time.saturating_mul(scalar), + proof_size: self.proof_size.saturating_mul(scalar), + } + } + + /// Saturating [`Weight`] scalar division. Computes `self.field / scalar` for all fields, + /// saturating at the numeric bounds of all fields instead of overflowing. + pub const fn saturating_div(self, scalar: u64) -> Self { + Self { + ref_time: self.ref_time.saturating_div(scalar), + proof_size: self.proof_size.saturating_div(scalar), + } + } + + /// Saturating [`Weight`] scalar exponentiation. Computes `self.field.pow(exp)` for all fields, + /// saturating at the numeric bounds of all fields instead of overflowing. + pub const fn saturating_pow(self, exp: u32) -> Self { + Self { + ref_time: self.ref_time.saturating_pow(exp), + proof_size: self.proof_size.saturating_pow(exp), + } + } + + /// Increment [`Weight`] by `amount` via saturating addition. + pub fn saturating_accrue(&mut self, amount: Self) { + *self = self.saturating_add(amount); + } + + /// Checked [`Weight`] addition. Computes `self + rhs`, returning `None` if overflow occurred. + pub const fn checked_add(&self, rhs: &Self) -> Option { + let ref_time = match self.ref_time.checked_add(rhs.ref_time) { + Some(t) => t, + None => return None, + }; + let proof_size = match self.proof_size.checked_add(rhs.proof_size) { + Some(s) => s, + None => return None, + }; + Some(Self { ref_time, proof_size }) + } + + /// Checked [`Weight`] subtraction. Computes `self - rhs`, returning `None` if overflow + /// occurred. + pub const fn checked_sub(&self, rhs: &Self) -> Option { + let ref_time = match self.ref_time.checked_sub(rhs.ref_time) { + Some(t) => t, + None => return None, + }; + let proof_size = match self.proof_size.checked_sub(rhs.proof_size) { + Some(s) => s, + None => return None, + }; + Some(Self { ref_time, proof_size }) + } + + /// Checked [`Weight`] scalar multiplication. Computes `self.field * scalar` for each field, + /// returning `None` if overflow occurred. + pub const fn checked_mul(self, scalar: u64) -> Option { + let ref_time = match self.ref_time.checked_mul(scalar) { + Some(t) => t, + None => return None, + }; + let proof_size = match self.proof_size.checked_mul(scalar) { + Some(s) => s, + None => return None, + }; + Some(Self { ref_time, proof_size }) + } + + /// Checked [`Weight`] scalar division. Computes `self.field / scalar` for each field, returning + /// `None` if overflow occurred. + pub const fn checked_div(self, scalar: u64) -> Option { + let ref_time = match self.ref_time.checked_div(scalar) { + Some(t) => t, + None => return None, + }; + let proof_size = match self.proof_size.checked_div(scalar) { + Some(s) => s, + None => return None, + }; + Some(Self { ref_time, proof_size }) + } + + /// Return a [`Weight`] where all fields are zero. + pub const fn zero() -> Self { + Self { ref_time: 0, proof_size: 0 } + } + + /// Constant version of Add with u64. + /// + /// Is only overflow safe when evaluated at compile-time. + pub const fn add(self, scalar: u64) -> Self { + Self { ref_time: self.ref_time + scalar, proof_size: self.proof_size + scalar } + } + + /// Constant version of Sub with u64. + /// + /// Is only overflow safe when evaluated at compile-time. + pub const fn sub(self, scalar: u64) -> Self { + Self { ref_time: self.ref_time - scalar, proof_size: self.proof_size - scalar } + } + + /// Constant version of Div with u64. + /// + /// Is only overflow safe when evaluated at compile-time. + pub const fn div(self, scalar: u64) -> Self { + Self { ref_time: self.ref_time / scalar, proof_size: self.proof_size / scalar } + } + + /// Constant version of Mul with u64. + /// + /// Is only overflow safe when evaluated at compile-time. + pub const fn mul(self, scalar: u64) -> Self { + Self { ref_time: self.ref_time * scalar, proof_size: self.proof_size * scalar } + } + + /// Returns true if any of `self`'s constituent weights is strictly greater than that of the + /// `other`'s, otherwise returns false. + pub const fn any_gt(self, other: Self) -> bool { + self.ref_time > other.ref_time || self.proof_size > other.proof_size + } + + /// Returns true if all of `self`'s constituent weights is strictly greater than that of the + /// `other`'s, otherwise returns false. + pub const fn all_gt(self, other: Self) -> bool { + self.ref_time > other.ref_time && self.proof_size > other.proof_size + } + + /// Returns true if any of `self`'s constituent weights is strictly less than that of the + /// `other`'s, otherwise returns false. + pub const fn any_lt(self, other: Self) -> bool { + self.ref_time < other.ref_time || self.proof_size < other.proof_size + } + + /// Returns true if all of `self`'s constituent weights is strictly less than that of the + /// `other`'s, otherwise returns false. + pub const fn all_lt(self, other: Self) -> bool { + self.ref_time < other.ref_time && self.proof_size < other.proof_size + } + + /// Returns true if any of `self`'s constituent weights is greater than or equal to that of the + /// `other`'s, otherwise returns false. + pub const fn any_gte(self, other: Self) -> bool { + self.ref_time >= other.ref_time || self.proof_size >= other.proof_size + } + + /// Returns true if all of `self`'s constituent weights is greater than or equal to that of the + /// `other`'s, otherwise returns false. + pub const fn all_gte(self, other: Self) -> bool { + self.ref_time >= other.ref_time && self.proof_size >= other.proof_size + } + + /// Returns true if any of `self`'s constituent weights is less than or equal to that of the + /// `other`'s, otherwise returns false. + pub const fn any_lte(self, other: Self) -> bool { + self.ref_time <= other.ref_time || self.proof_size <= other.proof_size + } + + /// Returns true if all of `self`'s constituent weights is less than or equal to that of the + /// `other`'s, otherwise returns false. + pub const fn all_lte(self, other: Self) -> bool { + self.ref_time <= other.ref_time && self.proof_size <= other.proof_size + } + + /// Returns true if any of `self`'s constituent weights is equal to that of the `other`'s, + /// otherwise returns false. + pub const fn any_eq(self, other: Self) -> bool { + self.ref_time == other.ref_time || self.proof_size == other.proof_size + } + + // NOTE: `all_eq` does not exist, as it's simply the `eq` method from the `PartialEq` trait. +} + +impl Zero for Weight { + fn zero() -> Self { + Self::zero() + } + + fn is_zero(&self) -> bool { + self == &Self::zero() + } +} + +impl Add for Weight { + type Output = Self; + fn add(self, rhs: Self) -> Self { + Self { + ref_time: self.ref_time + rhs.ref_time, + proof_size: self.proof_size + rhs.proof_size, + } + } +} + +impl Sub for Weight { + type Output = Self; + fn sub(self, rhs: Self) -> Self { + Self { + ref_time: self.ref_time - rhs.ref_time, + proof_size: self.proof_size - rhs.proof_size, + } + } +} + +impl Mul for Weight +where + T: Mul + Copy, +{ + type Output = Self; + fn mul(self, b: T) -> Self { + Self { ref_time: b * self.ref_time, proof_size: b * self.proof_size } + } +} + +macro_rules! weight_mul_per_impl { + ($($t:ty),* $(,)?) => { + $( + impl Mul for $t { + type Output = Weight; + fn mul(self, b: Weight) -> Weight { + Weight { + ref_time: self * b.ref_time, + proof_size: self * b.proof_size, + } + } + } + )* + } +} +weight_mul_per_impl!( + sp_arithmetic::Percent, + sp_arithmetic::PerU16, + sp_arithmetic::Permill, + sp_arithmetic::Perbill, + sp_arithmetic::Perquintill, +); + +macro_rules! weight_mul_primitive_impl { + ($($t:ty),* $(,)?) => { + $( + impl Mul for $t { + type Output = Weight; + fn mul(self, b: Weight) -> Weight { + Weight { + ref_time: u64::from(self) * b.ref_time, + proof_size: u64::from(self) * b.proof_size, + } + } + } + )* + } +} +weight_mul_primitive_impl!(u8, u16, u32, u64); + +impl Div for Weight +where + u64: Div, + T: Copy, +{ + type Output = Self; + fn div(self, b: T) -> Self { + Self { ref_time: self.ref_time / b, proof_size: self.proof_size / b } + } +} + +impl CheckedAdd for Weight { + fn checked_add(&self, rhs: &Self) -> Option { + self.checked_add(rhs) + } +} + +impl CheckedSub for Weight { + fn checked_sub(&self, rhs: &Self) -> Option { + self.checked_sub(rhs) + } +} + +impl core::fmt::Display for Weight { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Weight(ref_time: {}, proof_size: {})", self.ref_time, self.proof_size) + } +} + +impl Bounded for Weight { + fn min_value() -> Self { + Zero::zero() + } + fn max_value() -> Self { + Self::MAX + } +} + +impl AddAssign for Weight { + fn add_assign(&mut self, other: Self) { + *self = Self { + ref_time: self.ref_time + other.ref_time, + proof_size: self.proof_size + other.proof_size, + }; + } +} + +impl SubAssign for Weight { + fn sub_assign(&mut self, other: Self) { + *self = Self { + ref_time: self.ref_time - other.ref_time, + proof_size: self.proof_size - other.proof_size, + }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_zero_works() { + assert!(Weight::zero().is_zero()); + assert!(!Weight::from_parts(1, 0).is_zero()); + assert!(!Weight::from_parts(0, 1).is_zero()); + assert!(!Weight::MAX.is_zero()); + } +} diff --git a/rustfmt.toml b/rustfmt.toml index 441913f619cdc..f6fbe80064fce 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -21,3 +21,4 @@ match_block_trailing_comma = true trailing_comma = "Vertical" trailing_semicolon = false use_field_init_shorthand = true +edition = "2021" diff --git a/.maintain/common/lib.sh b/scripts/ci/common/lib.sh similarity index 100% rename from .maintain/common/lib.sh rename to scripts/ci/common/lib.sh diff --git a/.maintain/deny.toml b/scripts/ci/deny.toml similarity index 99% rename from .maintain/deny.toml rename to scripts/ci/deny.toml index 8cc7635d5049b..bc41f746cbb1e 100644 --- a/.maintain/deny.toml +++ b/scripts/ci/deny.toml @@ -160,7 +160,6 @@ allow = [ ] # List of crates to deny deny = [ - { name = "parity-util-mem", version = "<0.6" } # Each entry the name of a crate and a version range. If version is # not specified, all versions will be matched. ] diff --git a/.maintain/docker/subkey.Dockerfile b/scripts/ci/docker/subkey.Dockerfile similarity index 87% rename from .maintain/docker/subkey.Dockerfile rename to scripts/ci/docker/subkey.Dockerfile index 5797295806d00..a24595d915a1a 100644 --- a/.maintain/docker/subkey.Dockerfile +++ b/scripts/ci/docker/subkey.Dockerfile @@ -3,12 +3,13 @@ FROM docker.io/library/ubuntu:20.04 # metadata ARG VCS_REF ARG BUILD_DATE +ARG IMAGE_NAME LABEL io.parity.image.authors="devops-team@parity.io" \ io.parity.image.vendor="Parity Technologies" \ - io.parity.image.title="parity/subkey" \ + io.parity.image.title="${IMAGE_NAME}" \ io.parity.image.description="Subkey: key generating utility for Substrate." \ - io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/.maintain/docker/subkey.Dockerfile" \ + io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/scripts/ci/docker/subkey.Dockerfile" \ io.parity.image.revision="${VCS_REF}" \ io.parity.image.created="${BUILD_DATE}" \ io.parity.image.documentation="https://github.com/paritytech/substrate/tree/${VCS_REF}/subkey" diff --git a/.maintain/docker/substrate.Dockerfile b/scripts/ci/docker/substrate.Dockerfile similarity index 91% rename from .maintain/docker/substrate.Dockerfile rename to scripts/ci/docker/substrate.Dockerfile index e13dfb426adfd..5c7909afc0eec 100644 --- a/.maintain/docker/substrate.Dockerfile +++ b/scripts/ci/docker/substrate.Dockerfile @@ -3,12 +3,13 @@ FROM docker.io/library/ubuntu:20.04 # metadata ARG VCS_REF ARG BUILD_DATE +ARG IMAGE_NAME LABEL io.parity.image.authors="devops-team@parity.io" \ io.parity.image.vendor="Parity Technologies" \ - io.parity.image.title="parity/substrate" \ + io.parity.image.title="${IMAGE_NAME}" \ io.parity.image.description="Substrate: The platform for blockchain innovators." \ - io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/.maintain/docker/Dockerfile" \ + io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/scripts/ci/docker/Dockerfile" \ io.parity.image.revision="${VCS_REF}" \ io.parity.image.created="${BUILD_DATE}" \ io.parity.image.documentation="https://wiki.parity.io/Parity-Substrate" diff --git a/.maintain/github/check_labels.sh b/scripts/ci/github/check_labels.sh similarity index 100% rename from .maintain/github/check_labels.sh rename to scripts/ci/github/check_labels.sh diff --git a/.maintain/gitlab/generate_changelog.sh b/scripts/ci/github/generate_changelog.sh similarity index 100% rename from .maintain/gitlab/generate_changelog.sh rename to scripts/ci/github/generate_changelog.sh diff --git a/scripts/ci/gitlab/check-each-crate.sh b/scripts/ci/gitlab/check-each-crate.sh new file mode 100755 index 0000000000000..24cad67007e73 --- /dev/null +++ b/scripts/ci/gitlab/check-each-crate.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +## A script that checks each workspace crate individually. +## It's relevant to check workspace crates individually because otherwise their compilation problems +## due to feature misconfigurations won't be caught, as exemplified by +## https://github.com/paritytech/substrate/issues/12705 + +set -Eeu -o pipefail +shopt -s inherit_errexit + +set -vx + +target_group="$1" +groups_total="$2" + +readarray -t workspace_crates < <(\ + cargo tree --workspace --depth 0 --prefix none | + awk '{ if (length($1) == 0 || substr($1, 1, 1) == "[") { skip } else { print $1 } }' | + sort | + uniq +) + +crates_total=${#workspace_crates[*]} +if [ "$crates_total" -lt 1 ]; then + >&2 echo "No crates detected for $PWD" + exit 1 +fi + +if [ "$crates_total" -lt "$groups_total" ]; then + # `crates_total / groups_total` would result in 0, so round it up to 1 + crates_per_group=1 +else + # We add `crates_total % groups_total > 0` (which evaluates to 1 in case there's a remainder for + # `crates_total % groups_total`) to round UP `crates_total / groups_total` 's + # potentially-fractional result to the nearest integer. Doing that ensures that we'll not miss any + # crate in case `crates_total / groups_total` would normally result in a fractional number, since + # in those cases Bash would round DOWN the result to the nearest integer. For example, if + # `crates_total = 5` and `groups_total = 2`, then `crates_total / groups_total` would round down + # to 2; since the loop below would then step by 2, we'd miss the 5th crate. + crates_per_group=$(( (crates_total / groups_total) + (crates_total % groups_total > 0) )) +fi + +group=1 +for ((i=0; i < crates_total; i += crates_per_group)); do + if [ $group -eq "$target_group" ]; then + crates_in_group=("${workspace_crates[@]:$i:$crates_per_group}") + echo "crates in the group: ${crates_in_group[*]}" >/dev/null # >/dev/null due to "set -x" + for crate in "${crates_in_group[@]}"; do + cargo check --locked --release -p "$crate" + done + break + fi + group=$(( group + 1 )) +done diff --git a/.maintain/gitlab/check_runtime.sh b/scripts/ci/gitlab/check_runtime.sh similarity index 100% rename from .maintain/gitlab/check_runtime.sh rename to scripts/ci/gitlab/check_runtime.sh diff --git a/.maintain/gitlab/check_signed.sh b/scripts/ci/gitlab/check_signed.sh similarity index 100% rename from .maintain/gitlab/check_signed.sh rename to scripts/ci/gitlab/check_signed.sh diff --git a/.maintain/ensure-deps.sh b/scripts/ci/gitlab/ensure-deps.sh similarity index 100% rename from .maintain/ensure-deps.sh rename to scripts/ci/gitlab/ensure-deps.sh diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml new file mode 100644 index 0000000000000..2f8cff7b3ffa6 --- /dev/null +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -0,0 +1,166 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "build" stage + +# PIPELINE_SCRIPTS_TAG can be found in the project variables + +.check-dependent-project: + stage: build + # DAG: this is artificial dependency + needs: + - job: cargo-clippy + artifacts: false + extends: + - .docker-env + - .test-refs-no-trigger-prs-only + script: + - git clone + --depth=1 + --branch="$PIPELINE_SCRIPTS_TAG" + https://github.com/paritytech/pipeline-scripts + - ./pipeline-scripts/check_dependent_project.sh + --org paritytech + --dependent-repo "$DEPENDENT_REPO" + --github-api-token "$GITHUB_PR_TOKEN" + --extra-dependencies "$EXTRA_DEPENDENCIES" + --companion-overrides "$COMPANION_OVERRIDES" + +# Individual jobs are set up for each dependent project so that they can be ran in parallel. +# Arguably we could generate a job for each companion in the PR's description using Gitlab's +# parent-child pipelines but that's more complicated. + +check-dependent-polkadot: + extends: .check-dependent-project + variables: + DEPENDENT_REPO: polkadot + COMPANION_OVERRIDES: | + substrate: polkadot-v* + polkadot: release-v* + rules: + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ #PRs + +check-dependent-cumulus: + extends: .check-dependent-project + variables: + DEPENDENT_REPO: cumulus + EXTRA_DEPENDENCIES: polkadot + COMPANION_OVERRIDES: | + substrate: polkadot-v* + polkadot: release-v* + rules: + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ #PRs + +build-linux-substrate: + stage: build + extends: + - .collect-artifacts + - .docker-env + - .build-refs + variables: + # this variable gets overriden by "rusty-cachier environment inject", use the value as default + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" + needs: + - job: test-linux-stable + artifacts: false + before_script: + - mkdir -p ./artifacts/substrate/ + - !reference [.rusty-cachier, before_script] + # tldr: we need to checkout the branch HEAD explicitly because of our dynamic versioning approach while building the substrate binary + # see https://github.com/paritytech/ci_cd/issues/682#issuecomment-1340953589 + - git checkout -B "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA" + script: + - rusty-cachier snapshot create + - WASM_BUILD_NO_COLOR=1 time cargo build --locked --release --verbose + - mv $CARGO_TARGET_DIR/release/substrate ./artifacts/substrate/. + - echo -n "Substrate version = " + - if [ "${CI_COMMIT_TAG}" ]; then + echo "${CI_COMMIT_TAG}" | tee ./artifacts/substrate/VERSION; + else + ./artifacts/substrate/substrate --version | + cut -d ' ' -f 2 | tee ./artifacts/substrate/VERSION; + fi + - sha256sum ./artifacts/substrate/substrate | tee ./artifacts/substrate/substrate.sha256 + - cp -r ./scripts/ci/docker/substrate.Dockerfile ./artifacts/substrate/ + - printf '\n# building node-template\n\n' + - ./scripts/ci/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz + - rusty-cachier cache upload + +.build-subkey: + stage: build + extends: + - .collect-artifacts + - .docker-env + - .publish-refs + variables: + # this variable gets overriden by "rusty-cachier environment inject", use the value as default + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" + before_script: + - mkdir -p ./artifacts/subkey + - !reference [.rusty-cachier, before_script] + script: + - rusty-cachier snapshot create + - cd ./bin/utils/subkey + - SKIP_WASM_BUILD=1 time cargo build --locked --release --verbose + - cd - + - mv $CARGO_TARGET_DIR/release/subkey ./artifacts/subkey/. + - echo -n "Subkey version = " + - ./artifacts/subkey/subkey --version | + sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | + tee ./artifacts/subkey/VERSION; + - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 + - cp -r ./scripts/ci/docker/subkey.Dockerfile ./artifacts/subkey/ + - rusty-cachier cache upload + +build-subkey-linux: + extends: .build-subkey + +build-subkey-macos: + extends: .build-subkey + # duplicating before_script & script sections from .build-subkey hidden job + # to overwrite rusty-cachier integration as it doesn't work on macos + before_script: + - mkdir -p ./artifacts/subkey + script: + - cd ./bin/utils/subkey + - SKIP_WASM_BUILD=1 time cargo build --locked --release --verbose + - cd - + - mv ./target/release/subkey ./artifacts/subkey/. + - echo -n "Subkey version = " + - ./artifacts/subkey/subkey --version | + sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | + tee ./artifacts/subkey/VERSION; + - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 + - cp -r ./scripts/ci/docker/subkey.Dockerfile ./artifacts/subkey/ + after_script: [""] + tags: + - osx + +build-rustdoc: + stage: build + extends: + - .docker-env + - .test-refs + variables: + SKIP_WASM_BUILD: 1 + DOC_INDEX_PAGE: "sc_service/index.html" # default redirected page + RUSTY_CACHIER_TOOLCHAIN: nightly + # this variable gets overriden by "rusty-cachier environment inject", use the value as default + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" + when: on_success + expire_in: 7 days + paths: + - ./crate-docs/ + # DAG: this is artificial dependency + needs: + - job: cargo-clippy + artifacts: false + script: + - rusty-cachier snapshot create + - time cargo +nightly doc --locked --workspace --all-features --verbose --no-deps + - rm -f $CARGO_TARGET_DIR/doc/.lock + - mv $CARGO_TARGET_DIR/doc ./crate-docs + # FIXME: remove me after CI image gets nonroot + - chown -R nonroot:nonroot ./crate-docs + - echo "" > ./crate-docs/index.html + - rusty-cachier cache upload diff --git a/scripts/ci/gitlab/pipeline/check.yml b/scripts/ci/gitlab/pipeline/check.yml new file mode 100644 index 0000000000000..55f0061501076 --- /dev/null +++ b/scripts/ci/gitlab/pipeline/check.yml @@ -0,0 +1,65 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "check" stage + +check-runtime: + stage: check + extends: + - .kubernetes-env + - .test-refs-no-trigger-prs-only + variables: + CI_IMAGE: "paritytech/tools:latest" + GITLAB_API: "https://gitlab.parity.io/api/v4" + GITHUB_API_PROJECT: "parity%2Finfrastructure%2Fgithub-api" + script: + - ./scripts/ci/gitlab/check_runtime.sh + allow_failure: true + +check-signed-tag: + stage: check + extends: .kubernetes-env + variables: + CI_IMAGE: "paritytech/tools:latest" + rules: + - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + script: + - ./scripts/ci/gitlab/check_signed.sh + +test-dependency-rules: + stage: check + extends: + - .kubernetes-env + - .test-refs-no-trigger-prs-only + variables: + CI_IMAGE: "paritytech/tools:latest" + script: + - ./scripts/ci/gitlab/ensure-deps.sh + +test-rust-features: + stage: check + extends: + - .kubernetes-env + - .test-refs-no-trigger-prs-only + script: + - git clone + --depth=1 + --branch="$PIPELINE_SCRIPTS_TAG" + https://github.com/paritytech/pipeline-scripts + - bash ./pipeline-scripts/rust-features.sh . + +test-prometheus-alerting-rules: + stage: check + extends: .kubernetes-env + variables: + CI_IMAGE: "paritytech/tools:latest" + rules: + - if: $CI_PIPELINE_SOURCE == "pipeline" + when: never + - if: $CI_COMMIT_BRANCH + changes: + - .gitlab-ci.yml + - ./scripts/ci/monitoring/**/* + script: + - promtool check rules ./scripts/ci/monitoring/alerting-rules/alerting-rules.yaml + - cat ./scripts/ci/monitoring/alerting-rules/alerting-rules.yaml | + promtool test rules ./scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml new file mode 100644 index 0000000000000..381a1bc420ef3 --- /dev/null +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -0,0 +1,237 @@ + +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "publish" stage + +.build-push-docker-image-common: + extends: + - .kubernetes-env + stage: publish + variables: + CI_IMAGE: $BUILDAH_IMAGE + GIT_STRATEGY: none + DOCKERFILE: $PRODUCT.Dockerfile + IMAGE_NAME: docker.io/$IMAGE_PATH + before_script: + - cd ./artifacts/$PRODUCT/ + - VERSION="$(cat ./VERSION)" + - echo "${PRODUCT} version = ${VERSION}" + - test -z "${VERSION}" && exit 1 + script: + - test "$DOCKER_USER" -a "$DOCKER_PASS" || + ( echo "no docker credentials provided"; exit 1 ) + - buildah bud + --format=docker + --build-arg VCS_REF="${CI_COMMIT_SHA}" + --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + --build-arg IMAGE_NAME="${IMAGE_PATH}" + --tag "$IMAGE_NAME:$VERSION" + --tag "$IMAGE_NAME:latest" + --file "$DOCKERFILE" . + - echo "$DOCKER_PASS" | + buildah login --username "$DOCKER_USER" --password-stdin docker.io + - buildah info + - buildah push --format=v2s2 "$IMAGE_NAME:$VERSION" + - buildah push --format=v2s2 "$IMAGE_NAME:latest" + after_script: + - buildah logout --all + - echo "SUBSTRATE_IMAGE_NAME=${IMAGE_NAME}" | tee -a ./artifacts/$PRODUCT/build.env + - IMAGE_TAG="$(cat ./artifacts/$PRODUCT/VERSION)" + - echo "SUBSTRATE_IMAGE_TAG=${IMAGE_TAG}" | tee -a ./artifacts/$PRODUCT/build.env + - cat ./artifacts/$PRODUCT/build.env + +.build-push-docker-image: + extends: + - .publish-refs + - .build-push-docker-image-common + variables: + IMAGE_PATH: parity/$PRODUCT + DOCKER_USER: $Docker_Hub_User_Parity + DOCKER_PASS: $Docker_Hub_Pass_Parity + + +# publish image to docker.io/paritypr, (e.g. for later use in zombienet testing) +.build-push-image-temporary: + extends: + - .build-refs + - .build-push-docker-image-common + variables: + IMAGE_PATH: paritypr/$PRODUCT + DOCKER_USER: $PARITYPR_USER + DOCKER_PASS: $PARITYPR_PASS + +publish-docker-substrate: + extends: .build-push-docker-image + needs: + - job: build-linux-substrate + artifacts: true + variables: + PRODUCT: substrate + +publish-docker-substrate-temporary: + extends: .build-push-image-temporary + needs: + - job: build-linux-substrate + artifacts: true + variables: + PRODUCT: substrate + artifacts: + reports: + # this artifact is used in zombienet-tests job + # https://docs.gitlab.com/ee/ci/multi_project_pipelines.html#with-variable-inheritance + dotenv: ./artifacts/$PRODUCT/build.env + expire_in: 24h + +publish-docker-subkey: + extends: .build-push-docker-image + needs: + - job: build-subkey-linux + artifacts: true + variables: + PRODUCT: subkey + +publish-s3-release: + stage: publish + extends: + - .publish-refs + - .kubernetes-env + needs: + - job: build-linux-substrate + artifacts: true + - job: build-subkey-linux + artifacts: true + image: paritytech/awscli:latest + variables: + GIT_STRATEGY: none + BUCKET: "releases.parity.io" + PREFIX: "substrate/${ARCH}-${DOCKER_OS}" + script: + - aws s3 sync ./artifacts/ s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/substrate/VERSION)/ + - echo "update objects in latest path" + - aws s3 sync s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/substrate/VERSION)/ s3://${BUCKET}/${PREFIX}/latest/ + after_script: + - aws s3 ls s3://${BUCKET}/${PREFIX}/latest/ + --recursive --human-readable --summarize + +publish-rustdoc: + stage: publish + extends: .kubernetes-env + variables: + CI_IMAGE: node:16 + GIT_DEPTH: 100 + RUSTDOCS_DEPLOY_REFS: "master" + rules: + - if: $CI_PIPELINE_SOURCE == "pipeline" + when: never + - if: $CI_PIPELINE_SOURCE == "web" && $CI_COMMIT_REF_NAME == "master" + - if: $CI_COMMIT_REF_NAME == "master" + - if: $CI_COMMIT_REF_NAME =~ /^monthly-20[0-9]{2}-[0-9]{2}.*$/ # to support: monthly-2021-09+1 + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + # `needs:` can be removed after CI image gets nonroot. In this case `needs:` stops other + # artifacts from being dowloaded by this job. + needs: + - job: build-rustdoc + artifacts: true + script: + # If $CI_COMMIT_REF_NAME doesn't match one of $RUSTDOCS_DEPLOY_REFS space-separated values, we + # exit immediately. + # Putting spaces at the front and back to ensure we are not matching just any substring, but the + # whole space-separated value. + - '[[ " ${RUSTDOCS_DEPLOY_REFS} " =~ " ${CI_COMMIT_REF_NAME} " ]] || exit 0' + # setup ssh + - eval $(ssh-agent) + - ssh-add - <<< ${GITHUB_SSH_PRIV_KEY} + - mkdir ~/.ssh && touch ~/.ssh/known_hosts + - ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + # Set git config + - git config user.email "devops-team@parity.io" + - git config user.name "${GITHUB_USER}" + - git config remote.origin.url "git@github.com:/paritytech/${CI_PROJECT_NAME}.git" + - git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" + - git fetch origin gh-pages + # Save README and docs + - cp -r ./crate-docs/ /tmp/doc/ + - cp README.md /tmp/doc/ + # we don't need to commit changes because we copy docs to /tmp + - git checkout gh-pages --force + # Install `index-tpl-crud` and generate index.html based on RUSTDOCS_DEPLOY_REFS + - which index-tpl-crud &> /dev/null || yarn global add @substrate/index-tpl-crud + - index-tpl-crud upsert ./index.html ${CI_COMMIT_REF_NAME} + # Ensure the destination dir doesn't exist. + - rm -rf ${CI_COMMIT_REF_NAME} + - mv -f /tmp/doc ${CI_COMMIT_REF_NAME} + # Upload files + - git add --all + # `git commit` has an exit code of > 0 if there is nothing to commit. + # This causes GitLab to exit immediately and marks this job failed. + # We don't want to mark the entire job failed if there's nothing to + # publish though, hence the `|| true`. + - git commit -m "___Updated docs for ${CI_COMMIT_REF_NAME}___" || + echo "___Nothing to commit___" + - git push origin gh-pages --force + after_script: + - rm -rf .git/ ./* + +publish-draft-release: + stage: publish + image: paritytech/tools:latest + rules: + - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + script: + - ./scripts/ci/gitlab/publish_draft_release.sh + allow_failure: true + +# Ref: https://github.com/paritytech/opstooling/issues/111 +update-node-template: + stage: publish + extends: .kubernetes-env + rules: + - if: $CI_COMMIT_REF_NAME =~ /^polkadot-v[0-9]+\.[0-9]+.*$/ # i.e. polkadot-v1.0.99, polkadot-v2.1rc1 + script: + - git clone --depth=1 --branch="$PIPELINE_SCRIPTS_TAG" https://github.com/paritytech/pipeline-scripts + - ./pipeline-scripts/update_substrate_template.sh + --repo-name "substrate-node-template" + --template-path "bin/node-template" + --github-api-token "$GITHUB_TOKEN" + --polkadot-branch "$CI_COMMIT_REF_NAME" + +.publish-crates-template: + stage: publish + extends: .crates-publishing-template + # We don't want multiple jobs racing to publish crates as it's redundant and they might overwrite + # the releases of one another. Use resource_group to ensure that at most one instance of this job + # is running at any given time. + resource_group: crates-publishing + variables: + # crates.io rate limits crates publishing by 1 per minute, so a delay needs to be inserted + # slightly higher than that after publishing each crate. The value is specified in seconds. + SPUB_AFTER_PUBLISH_DELAY: 64 + # We might have to publish lots of crates at a time. Given the 1 minute delay introduced above and + # taking into account the 202 (as of Dec 07, 2022) publishable Substrate crates, that would equate + # to roughly 202 minutes of delay, or 3h and 22 minutes. As such, the job needs to have a much + # higher timeout than average. + timeout: 5h + # A custom publishing environment is used for us to be able to set up protected secrets + # specifically for it + environment: publish-crates + script: + - rusty-cachier snapshot create + - git clone + --depth 1 + --branch "$RELENG_SCRIPTS_BRANCH" + https://github.com/paritytech/releng-scripts.git + - CRATESIO_TARGET_INSTANCE=default ./releng-scripts/publish-crates + - rusty-cachier cache upload + +publish-crates: + extends: .publish-crates-template + needs: + - job: publish-crates-locally + artifacts: false + rules: + - if: $CI_COMMIT_REF_NAME == "master" + +publish-crates-manual: + extends: .publish-crates-template + when: manual + allow_failure: true diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml new file mode 100644 index 0000000000000..ea1240af8bd57 --- /dev/null +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -0,0 +1,426 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "test" stage + +cargo-deny: + stage: test + extends: + - .docker-env + - .nightly-pipeline + script: + - rusty-cachier snapshot create + - cargo deny check --hide-inclusion-graph -c ./scripts/ci/deny.toml + - rusty-cachier cache upload + after_script: + - !reference [.rusty-cachier, after_script] + - echo "___The complete log is in the artifacts___" + - cargo deny check -c ./scripts/ci/deny.toml 2> deny.log + artifacts: + name: $CI_COMMIT_SHORT_SHA + expire_in: 3 days + when: always + paths: + - deny.log + # FIXME: Temporarily allow to fail. + allow_failure: true + +cargo-fmt: + stage: test + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly + extends: + - .docker-env + - .test-refs + script: + - rusty-cachier snapshot create + - cargo +nightly fmt --all -- --check + - rusty-cachier cache upload + +cargo-clippy: + stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: cargo-fmt + artifacts: false + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly + extends: + - .docker-env + - .test-refs + script: + - rusty-cachier snapshot create + - SKIP_WASM_BUILD=1 env -u RUSTFLAGS cargo +nightly clippy --all-targets + - rusty-cachier cache upload + +cargo-check-benches: + stage: test + variables: + # Override to use nightly toolchain + RUSTY_CACHIER_TOOLCHAIN: "nightly" + CI_JOB_NAME: "cargo-check-benches" + extends: + - .docker-env + - .test-refs-check-benches + - .collect-artifacts + - .pipeline-stopper-artifacts + before_script: + # perform rusty-cachier operations before any further modifications to the git repo to make cargo feel cheated not so much + - !reference [.rust-info-script, script] + - !reference [.rusty-cachier, before_script] + - !reference [.pipeline-stopper-vars, script] + # merges in the master branch on PRs + - | + export BASE=$(curl -s -H "Authorization: Bearer ${GITHUB_PR_TOKEN}" https://api.github.com/repos/paritytech/substrate/pulls/${$CI_COMMIT_REF_NAME} | jq .base.ref) + - if [ $CI_COMMIT_REF_NAME != "master" ]; then + git fetch origin +${BASE}:${BASE}; + git fetch origin +$CI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME; + git checkout ${BASE}; + git config user.email "ci@gitlab.parity.io"; + git merge $CI_COMMIT_REF_NAME --verbose --no-edit; + fi + parallel: 2 + script: + - rusty-cachier snapshot create + - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA + # this job is executed in parallel on two runners + - echo "___Running benchmarks___"; + - case ${CI_NODE_INDEX} in + 1) + SKIP_WASM_BUILD=1 time cargo +nightly check --locked --benches --all; + cargo run --locked --release -p node-bench -- ::trie::read::small --json + | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json; + echo "___Uploading cache for rusty-cachier___"; + rusty-cachier cache upload + ;; + 2) + cargo run --locked --release -p node-bench -- ::node::import::native::sr25519::transfer_keep_alive::paritydb::small --json + | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::native::sr25519::transfer_keep_alive::paritydb::small.json + ;; + esac + tags: + - linux-docker-benches + +node-bench-regression-guard: + # it's not belong to `build` semantically, but dag jobs can't depend on each other + # within the single stage - https://gitlab.com/gitlab-org/gitlab/-/issues/30632 + # more: https://github.com/paritytech/substrate/pull/8519#discussion_r608012402 + stage: build + extends: + - .docker-env + - .test-refs-no-trigger-prs-only + needs: + # this is a DAG + - job: cargo-check-benches + artifacts: true + # polls artifact from master to compare with current result + # need to specify both parallel jobs from master because of the bug + # https://gitlab.com/gitlab-org/gitlab/-/issues/39063 + - project: $CI_PROJECT_PATH + job: "cargo-check-benches 1/2" + ref: master + artifacts: true + - project: $CI_PROJECT_PATH + job: "cargo-check-benches 2/2" + ref: master + artifacts: true + variables: + CI_IMAGE: "paritytech/node-bench-regression-guard:latest" + before_script: [""] + script: + - echo "------- IMPORTANT -------" + - echo "node-bench-regression-guard depends on the results of a cargo-check-benches job" + - echo "In case of this job failure, check your pipeline's cargo-check-benches" + - 'node-bench-regression-guard --reference artifacts/benches/master-* + --compare-with artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA' + after_script: [""] + +cargo-check-try-runtime: + stage: test + extends: + - .docker-env + - .test-refs + script: + - rusty-cachier snapshot create + - time cargo check --locked --features try-runtime + - rusty-cachier cache upload + +test-deterministic-wasm: + stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: cargo-check-try-runtime + artifacts: false + extends: + - .docker-env + - .test-refs + variables: + WASM_BUILD_NO_COLOR: 1 + # this variable gets overriden by "rusty-cachier environment inject", use the value as default + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" + script: + - rusty-cachier snapshot create + # build runtime + - cargo build --locked --verbose --release -p kitchensink-runtime + # make checksum + - sha256sum $CARGO_TARGET_DIR/release/wbuild/kitchensink-runtime/target/wasm32-unknown-unknown/release/kitchensink_runtime.wasm > checksum.sha256 + # clean up + - rm -rf $CARGO_TARGET_DIR/release/wbuild + # build again + - cargo build --locked --verbose --release -p kitchensink-runtime + # confirm checksum + - sha256sum -c ./checksum.sha256 + # clean up again, don't put release binaries into the cache + - rm -rf $CARGO_TARGET_DIR/release/wbuild + - rusty-cachier cache upload + +test-linux-stable: + stage: test + extends: + - .docker-env + - .test-refs + - .pipeline-stopper-artifacts + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 + # needed for rusty-cachier to keep cache in test-linux-stable folder and not in test-linux-stable-1/3 + CI_JOB_NAME: "test-linux-stable" + parallel: 3 + script: + - rusty-cachier snapshot create + # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests + # tests are partitioned by nextest and executed in parallel on $CI_NODE_TOTAL runners + # node-cli is excluded until https://github.com/paritytech/substrate/issues/11321 fixed + - echo "Node index - ${CI_NODE_INDEX}. Total amount - ${CI_NODE_TOTAL}" + - time cargo nextest run --workspace + --locked + --release + --verbose + --features runtime-benchmarks + --manifest-path ./bin/node/cli/Cargo.toml + --exclude node-cli + --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} + # we need to update cache only from one job + - if [ ${CI_NODE_INDEX} == 1 ]; then rusty-cachier cache upload; fi + +test-frame-support: + stage: test + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 + script: + - rusty-cachier snapshot create + - time cargo test --locked -p frame-support-test --features=frame-feature-testing,no-metadata-docs --manifest-path ./frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec + - time cargo test --locked -p frame-support-test --features=frame-feature-testing,frame-feature-testing-2,no-metadata-docs --manifest-path ./frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec + - SUBSTRATE_TEST_TIMEOUT=1 time cargo test -p substrate-test-utils --release --verbose --locked -- --ignored timeout + - rusty-cachier cache upload + +# This job runs tests that don't work with cargo-nextest in test-linux-stable +test-linux-stable-extra: + stage: test + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 + script: + - rusty-cachier snapshot create + # Run node-cli tests + # TODO: add to test-linux-stable-nextest after fix https://github.com/paritytech/substrate/issues/11321 + - time cargo test node-cli --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml + # Run doctests + # TODO: add to test-linux-stable-nextest after fix https://github.com/nextest-rs/nextest/issues/16 + - time cargo test --doc --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml + - rusty-cachier cache upload + +# This job runs all benchmarks defined in the `/bin/node/runtime` once to check that there are no errors. +quick-benchmarks: + stage: test + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUST_BACKTRACE: "full" + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + script: + - rusty-cachier snapshot create + - time cargo run --locked --release --features runtime-benchmarks -- benchmark pallet --execution wasm --wasm-execution compiled --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 + - rusty-cachier cache upload + +test-frame-examples-compile-to-wasm: + # into one job + stage: test + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y" + RUST_BACKTRACE: 1 + script: + - rusty-cachier snapshot create + - cd ./frame/examples/offchain-worker/ + - cargo +nightly build --locked --target=wasm32-unknown-unknown --no-default-features + - cd ../basic + - cargo +nightly build --locked --target=wasm32-unknown-unknown --no-default-features + - rusty-cachier cache upload + +test-linux-stable-int: + stage: test + extends: + - .docker-env + - .test-refs + - .pipeline-stopper-artifacts + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 + script: + - rusty-cachier snapshot create + - WASM_BUILD_NO_COLOR=1 + RUST_LOG=sync=trace,consensus=trace,client=trace,state-db=trace,db=trace,forks=trace,state_db=trace,storage_cache=trace + time cargo test -p node-cli --release --verbose --locked -- --ignored + - rusty-cachier cache upload + +# more information about this job can be found here: +# https://github.com/paritytech/substrate/pull/6916 +check-tracing: + stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: test-linux-stable-int + artifacts: false + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly + extends: + - .docker-env + - .test-refs + - .pipeline-stopper-artifacts + script: + - rusty-cachier snapshot create + # with-tracing must be explicitly activated, we run a test to ensure this works as expected in both cases + - time cargo +nightly test --locked --manifest-path ./primitives/tracing/Cargo.toml --no-default-features + - time cargo +nightly test --locked --manifest-path ./primitives/tracing/Cargo.toml --no-default-features --features=with-tracing + - rusty-cachier cache upload + +# more information about this job can be found here: +# https://github.com/paritytech/substrate/pull/3778 +test-full-crypto-feature: + stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: check-tracing + artifacts: false + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y" + RUST_BACKTRACE: 1 + script: + - rusty-cachier snapshot create + - cd primitives/core/ + - time cargo +nightly build --locked --verbose --no-default-features --features full_crypto + - cd ../application-crypto + - time cargo +nightly build --locked --verbose --no-default-features --features full_crypto + - rusty-cachier cache upload + +check-rustdoc: + stage: test + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly + extends: + - .docker-env + - .test-refs + variables: + SKIP_WASM_BUILD: 1 + RUSTDOCFLAGS: "-Dwarnings" + script: + - rusty-cachier snapshot create + - time cargo +nightly doc --locked --workspace --all-features --verbose --no-deps + - rusty-cachier cache upload + +cargo-check-each-crate: + stage: test + extends: + - .docker-env + - .test-refs + - .collect-artifacts + - .pipeline-stopper-artifacts + variables: + # $CI_JOB_NAME is set manually so that rusty-cachier can share the cache for all + # "cargo-check-each-crate I/N" jobs + CI_JOB_NAME: cargo-check-each-crate + script: + - rusty-cachier snapshot create + - time ./scripts/ci/gitlab/check-each-crate.sh "$CI_NODE_INDEX" "$CI_NODE_TOTAL" + # need to update cache only from one job + - if [ "$CI_NODE_INDEX" == 1 ]; then rusty-cachier cache upload; fi + parallel: 2 + +publish-crates-locally: + extends: + - .test-refs + - .crates-publishing-template + script: + - rusty-cachier snapshot create + - git clone + --depth 1 + --branch "$RELENG_SCRIPTS_BRANCH" + https://github.com/paritytech/releng-scripts.git + - CRATESIO_TARGET_INSTANCE=local ./releng-scripts/publish-crates + - rusty-cachier cache upload + +cargo-check-each-crate-macos: + stage: test + extends: + - .test-refs + - .collect-artifacts + - .pipeline-stopper-artifacts + before_script: + - !reference [.rust-info-script, script] + - !reference [.pipeline-stopper-vars, script] + variables: + SKIP_WASM_BUILD: 1 + script: + # TODO: enable rusty-cachier once it supports Mac + # TODO: use parallel jobs, as per cargo-check-each-crate, once more Mac runners are available + - time ./scripts/ci/gitlab/check-each-crate.sh 1 1 + tags: + - osx diff --git a/scripts/ci/gitlab/pipeline/zombienet.yml b/scripts/ci/gitlab/pipeline/zombienet.yml new file mode 100644 index 0000000000000..8d772ff51f9a7 --- /dev/null +++ b/scripts/ci/gitlab/pipeline/zombienet.yml @@ -0,0 +1,53 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "zombienet" stage + +# common settings for all zombienet jobs +.zombienet-common: + before_script: + - echo "Zombie-net Tests Config" + - echo "${ZOMBIENET_IMAGE}" + - echo "${SUBSTRATE_IMAGE_NAME} ${SUBSTRATE_IMAGE_TAG}" + - echo "${GH_DIR}" + - export DEBUG=zombie,zombie::network-node + - export ZOMBIENET_INTEGRATION_TEST_IMAGE=${SUBSTRATE_IMAGE_NAME}:${SUBSTRATE_IMAGE_TAG} + - echo "${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + stage: zombienet + image: "${ZOMBIENET_IMAGE}" + needs: + - job: publish-docker-substrate-temporary + extends: + - .kubernetes-env + - .zombienet-refs + variables: + GH_DIR: "https://github.com/paritytech/substrate/tree/${CI_COMMIT_SHA}/zombienet" + FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: always + expire_in: 2 days + paths: + - ./zombienet-logs + after_script: + - mkdir -p ./zombienet-logs + - cp /tmp/zombie*/logs/* ./zombienet-logs/ + allow_failure: true + retry: 2 + tags: + - zombienet-polkadot-integration-test + +zombienet-0000-block-building: + extends: + - .zombienet-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-env-manager.sh + --github-remote-dir="${GH_DIR}/0000-block-building" + --test="block-building.zndsl" + + +zombienet-0001-basic-warp-sync: + extends: + - .zombienet-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-env-manager.sh + --github-remote-dir="${GH_DIR}/0001-basic-warp-sync" + --test="test-warp-sync.zndsl" diff --git a/.maintain/gitlab/publish_draft_release.sh b/scripts/ci/gitlab/publish_draft_release.sh similarity index 100% rename from .maintain/gitlab/publish_draft_release.sh rename to scripts/ci/gitlab/publish_draft_release.sh diff --git a/.maintain/gitlab/skip_if_draft.sh b/scripts/ci/gitlab/skip_if_draft.sh similarity index 100% rename from .maintain/gitlab/skip_if_draft.sh rename to scripts/ci/gitlab/skip_if_draft.sh diff --git a/.maintain/monitoring/alerting-rules/alerting-rule-tests.yaml b/scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml similarity index 61% rename from .maintain/monitoring/alerting-rules/alerting-rule-tests.yaml rename to scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml index 7ad916f022154..df5e020d067ea 100644 --- a/.maintain/monitoring/alerting-rules/alerting-rule-tests.yaml +++ b/scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml @@ -6,39 +6,39 @@ evaluation_interval: 1m tests: - interval: 1m input_series: - - series: 'polkadot_sub_libp2p_peers_count{ - job="polkadot", - pod="polkadot-abcdef01234-abcdef", - instance="polkadot-abcdef01234-abcdef", + - series: 'substrate_sub_libp2p_peers_count{ + job="substrate", + pod="substrate-abcdef01234-abcdef", + instance="substrate-abcdef01234-abcdef", }' values: '3 2+0x4 1+0x9' # 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 - - series: 'polkadot_sub_txpool_validations_scheduled{ - job="polkadot", - pod="polkadot-abcdef01234-abcdef", - instance="polkadot-abcdef01234-abcdef", + - series: 'substrate_sub_txpool_validations_scheduled{ + job="substrate", + pod="substrate-abcdef01234-abcdef", + instance="substrate-abcdef01234-abcdef", }' values: '11+1x10 22+2x30 10043x5' - - series: 'polkadot_sub_txpool_validations_finished{ - job="polkadot", - pod="polkadot-abcdef01234-abcdef", - instance="polkadot-abcdef01234-abcdef", + - series: 'substrate_sub_txpool_validations_finished{ + job="substrate", + pod="substrate-abcdef01234-abcdef", + instance="substrate-abcdef01234-abcdef", }' values: '0+1x42 42x5' - - series: 'polkadot_block_height{ - status="best", job="polkadot", - pod="polkadot-abcdef01234-abcdef", - instance="polkadot-abcdef01234-abcdef", + - series: 'substrate_block_height{ + status="best", job="substrate", + pod="substrate-abcdef01234-abcdef", + instance="substrate-abcdef01234-abcdef", }' values: '1+1x3 4+0x13' # 1 2 3 4 4 4 4 4 4 4 4 4 ... - - series: 'polkadot_block_height{ + - series: 'substrate_block_height{ status="finalized", - job="polkadot", - pod="polkadot-abcdef01234-abcdef", - instance="polkadot-abcdef01234-abcdef", + job="substrate", + pod="substrate-abcdef01234-abcdef", + instance="substrate-abcdef01234-abcdef", }' values: '1+1x3 4+0x13' # 1 2 3 4 4 4 4 4 4 4 4 4 ... @@ -56,13 +56,13 @@ tests: exp_alerts: - exp_labels: severity: warning - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate status: best exp_annotations: message: "Best block on instance - polkadot-abcdef01234-abcdef increases by less than 1 per + substrate-abcdef01234-abcdef increases by less than 1 per minute for more than 3 minutes." - eval_time: 14m @@ -70,23 +70,23 @@ tests: exp_alerts: - exp_labels: severity: warning - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate status: best exp_annotations: message: "Best block on instance - polkadot-abcdef01234-abcdef increases by less than 1 per + substrate-abcdef01234-abcdef increases by less than 1 per minute for more than 3 minutes." - exp_labels: severity: critical - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate status: best exp_annotations: message: "Best block on instance - polkadot-abcdef01234-abcdef increases by less than 1 per + substrate-abcdef01234-abcdef increases by less than 1 per minute for more than 10 minutes." ###################################################################### @@ -101,13 +101,13 @@ tests: exp_alerts: - exp_labels: severity: warning - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate status: finalized exp_annotations: message: "Finalized block on instance - polkadot-abcdef01234-abcdef increases by less than 1 per + substrate-abcdef01234-abcdef increases by less than 1 per minute for more than 3 minutes." - eval_time: 14m @@ -115,23 +115,23 @@ tests: exp_alerts: - exp_labels: severity: warning - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate status: finalized exp_annotations: message: "Finalized block on instance - polkadot-abcdef01234-abcdef increases by less than 1 per + substrate-abcdef01234-abcdef increases by less than 1 per minute for more than 3 minutes." - exp_labels: severity: critical - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate status: finalized exp_annotations: message: "Finalized block on instance - polkadot-abcdef01234-abcdef increases by less than 1 per + substrate-abcdef01234-abcdef increases by less than 1 per minute for more than 10 minutes." ###################################################################### @@ -152,12 +152,12 @@ tests: exp_alerts: - exp_labels: severity: warning - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate exp_annotations: message: "The transaction pool size on node - polkadot-abcdef01234-abcdef has been monotonically + substrate-abcdef01234-abcdef has been monotonically increasing for more than 10 minutes." - eval_time: 43m alertname: TransactionQueueSizeIncreasing @@ -167,21 +167,21 @@ tests: exp_alerts: - exp_labels: severity: warning - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate exp_annotations: message: "The transaction pool size on node - polkadot-abcdef01234-abcdef has been monotonically + substrate-abcdef01234-abcdef has been monotonically increasing for more than 10 minutes." - exp_labels: severity: warning - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate exp_annotations: message: "The transaction pool size on node - polkadot-abcdef01234-abcdef has been monotonically + substrate-abcdef01234-abcdef has been monotonically increasing for more than 30 minutes." - eval_time: 49m alertname: TransactionQueueSizeHigh @@ -191,12 +191,12 @@ tests: exp_alerts: - exp_labels: severity: warning - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate exp_annotations: message: "The transaction pool size on node - polkadot-abcdef01234-abcdef has been above 10_000 for more + substrate-abcdef01234-abcdef has been above 10_000 for more than 5 minutes." ###################################################################### @@ -211,11 +211,11 @@ tests: exp_alerts: - exp_labels: severity: warning - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate exp_annotations: - message: "The node polkadot-abcdef01234-abcdef has less + message: "The node substrate-abcdef01234-abcdef has less than 3 peers for more than 3 minutes" - eval_time: 16m # Values: 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 @@ -223,17 +223,17 @@ tests: exp_alerts: - exp_labels: severity: warning - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate exp_annotations: - message: "The node polkadot-abcdef01234-abcdef has less + message: "The node substrate-abcdef01234-abcdef has less than 3 peers for more than 3 minutes" - exp_labels: severity: critical - pod: polkadot-abcdef01234-abcdef - instance: polkadot-abcdef01234-abcdef - job: polkadot + pod: substrate-abcdef01234-abcdef + instance: substrate-abcdef01234-abcdef + job: substrate exp_annotations: - message: "The node polkadot-abcdef01234-abcdef has less + message: "The node substrate-abcdef01234-abcdef has less than 3 peers for more than 15 minutes" diff --git a/.maintain/monitoring/alerting-rules/alerting-rules.yaml b/scripts/ci/monitoring/alerting-rules/alerting-rules.yaml similarity index 70% rename from .maintain/monitoring/alerting-rules/alerting-rules.yaml rename to scripts/ci/monitoring/alerting-rules/alerting-rules.yaml index 7a69cba66c3f3..4171f92f68fef 100644 --- a/.maintain/monitoring/alerting-rules/alerting-rules.yaml +++ b/scripts/ci/monitoring/alerting-rules/alerting-rules.yaml @@ -1,5 +1,5 @@ groups: -- name: polkadot.rules +- name: substrate.rules rules: ############################################################################## @@ -10,7 +10,7 @@ groups: annotations: message: 'Best block on instance {{ $labels.instance }} increases by less than 1 per minute for more than 3 minutes.' - expr: increase(polkadot_block_height{status="best"}[1m]) < 1 + expr: increase(substrate_block_height{status="best"}[1m]) < 1 for: 3m labels: severity: warning @@ -18,7 +18,7 @@ groups: annotations: message: 'Best block on instance {{ $labels.instance }} increases by less than 1 per minute for more than 10 minutes.' - expr: increase(polkadot_block_height{status="best"}[1m]) < 1 + expr: increase(substrate_block_height{status="best"}[1m]) < 1 for: 10m labels: severity: critical @@ -28,7 +28,7 @@ groups: ############################################################################## - alert: BlockFinalizationSlow - expr: increase(polkadot_block_height{status="finalized"}[1m]) < 1 + expr: increase(substrate_block_height{status="finalized"}[1m]) < 1 for: 3m labels: severity: warning @@ -36,7 +36,7 @@ groups: message: 'Finalized block on instance {{ $labels.instance }} increases by less than 1 per minute for more than 3 minutes.' - alert: BlockFinalizationSlow - expr: increase(polkadot_block_height{status="finalized"}[1m]) < 1 + expr: increase(substrate_block_height{status="finalized"}[1m]) < 1 for: 10m labels: severity: critical @@ -47,8 +47,8 @@ groups: # Under the assumption of an average block production of 6 seconds, # "best" and "finalized" being more than 10 blocks apart would imply # more than a 1 minute delay between block production and finalization. - expr: '(polkadot_block_height{status="best"} - ignoring(status) - polkadot_block_height{status="finalized"}) > 10' + expr: '(substrate_block_height{status="best"} - ignoring(status) + substrate_block_height{status="finalized"}) > 10' for: 8m labels: severity: critical @@ -61,8 +61,8 @@ groups: ############################################################################## - alert: TransactionQueueSizeIncreasing - expr: 'increase(polkadot_sub_txpool_validations_scheduled[5m]) - - increase(polkadot_sub_txpool_validations_finished[5m]) > 0' + expr: 'increase(substrate_sub_txpool_validations_scheduled[5m]) - + increase(substrate_sub_txpool_validations_finished[5m]) > 0' for: 10m labels: severity: warning @@ -70,8 +70,8 @@ groups: message: 'The transaction pool size on node {{ $labels.instance }} has been monotonically increasing for more than 10 minutes.' - alert: TransactionQueueSizeIncreasing - expr: 'increase(polkadot_sub_txpool_validations_scheduled[5m]) - - increase(polkadot_sub_txpool_validations_finished[5m]) > 0' + expr: 'increase(substrate_sub_txpool_validations_scheduled[5m]) - + increase(substrate_sub_txpool_validations_finished[5m]) > 0' for: 30m labels: severity: warning @@ -79,8 +79,8 @@ groups: message: 'The transaction pool size on node {{ $labels.instance }} has been monotonically increasing for more than 30 minutes.' - alert: TransactionQueueSizeHigh - expr: 'polkadot_sub_txpool_validations_scheduled - - polkadot_sub_txpool_validations_finished > 10000' + expr: 'substrate_sub_txpool_validations_scheduled - + substrate_sub_txpool_validations_finished > 10000' for: 5m labels: severity: warning @@ -93,7 +93,7 @@ groups: ############################################################################## - alert: NumberOfPeersLow - expr: polkadot_sub_libp2p_peers_count < 3 + expr: substrate_sub_libp2p_peers_count < 3 for: 3m labels: severity: warning @@ -101,7 +101,7 @@ groups: message: 'The node {{ $labels.instance }} has less than 3 peers for more than 3 minutes' - alert: NumberOfPeersLow - expr: polkadot_sub_libp2p_peers_count < 3 + expr: substrate_sub_libp2p_peers_count < 3 for: 15m labels: severity: critical @@ -109,7 +109,7 @@ groups: message: 'The node {{ $labels.instance }} has less than 3 peers for more than 15 minutes' - alert: NoIncomingConnection - expr: increase(polkadot_sub_libp2p_incoming_connections_total[20m]) == 0 + expr: increase(substrate_sub_libp2p_incoming_connections_total[20m]) == 0 labels: severity: warning annotations: @@ -121,7 +121,7 @@ groups: ############################################################################## - alert: NumberOfFileDescriptorsHigh - expr: 'node_filefd_allocated{domain=~"kusama|polkadot"} > 10000' + expr: 'node_filefd_allocated{chain!=""} > 10000' for: 3m labels: severity: warning @@ -133,20 +133,10 @@ groups: # Others ############################################################################## - - alert: ContinuousTaskEnded - expr: '(polkadot_tasks_spawned_total{task_name != "basic-authorship-proposer", task_name != "substrate-rpc-subscription"} == 1) - - on(instance, task_name) group_left() (polkadot_tasks_ended_total == 1)' - for: 5m - labels: - severity: warning - annotations: - message: 'Continuous task {{ $labels.task_name }} on node - {{ $labels.instance }} ended unexpectedly.' - - alert: AuthorityDiscoveryDiscoveryFailureHigh - expr: 'polkadot_authority_discovery_handle_value_found_event_failure / + expr: 'substrate_authority_discovery_handle_value_found_event_failure / ignoring(name) - polkadot_authority_discovery_dht_event_received{name="value_found"} > 0.5' + substrate_authority_discovery_dht_event_received{name="value_found"} > 0.5' for: 2h labels: severity: warning @@ -157,9 +147,9 @@ groups: - alert: UnboundedChannelPersistentlyLarge expr: '( - (polkadot_unbounded_channel_len{action = "send"} - - ignoring(action) polkadot_unbounded_channel_len{action = "received"}) - or on(instance) polkadot_unbounded_channel_len{action = "send"} + (substrate_unbounded_channel_len{action = "send"} - + ignoring(action) substrate_unbounded_channel_len{action = "received"}) + or on(instance) substrate_unbounded_channel_len{action = "send"} ) >= 200' for: 5m labels: @@ -170,9 +160,9 @@ groups: - alert: UnboundedChannelVeryLarge expr: '( - (polkadot_unbounded_channel_len{action = "send"} - - ignoring(action) polkadot_unbounded_channel_len{action = "received"}) - or on(instance) polkadot_unbounded_channel_len{action = "send"} + (substrate_unbounded_channel_len{action = "send"} - + ignoring(action) substrate_unbounded_channel_len{action = "received"}) + or on(instance) substrate_unbounded_channel_len{action = "send"} ) > 15000' labels: severity: warning diff --git a/.maintain/monitoring/grafana-dashboards/README_dashboard.md b/scripts/ci/monitoring/grafana-dashboards/README_dashboard.md similarity index 73% rename from .maintain/monitoring/grafana-dashboards/README_dashboard.md rename to scripts/ci/monitoring/grafana-dashboards/README_dashboard.md index e00b89449cfaf..50f54a107e933 100644 --- a/.maintain/monitoring/grafana-dashboards/README_dashboard.md +++ b/scripts/ci/monitoring/grafana-dashboards/README_dashboard.md @@ -4,4 +4,4 @@ Shared templated Grafana dashboards. To import the dashboards follow the [Grafana documentation](https://grafana.com/docs/grafana/latest/reference/export_import/). -You can see an example setup [here](../../../.maintain/sentry-node). +You can see an example setup [here](./substrate-networking.json). diff --git a/.maintain/monitoring/grafana-dashboards/substrate-networking.json b/scripts/ci/monitoring/grafana-dashboards/substrate-networking.json similarity index 99% rename from .maintain/monitoring/grafana-dashboards/substrate-networking.json rename to scripts/ci/monitoring/grafana-dashboards/substrate-networking.json index 46942cf582fc6..abd675ed13ec3 100644 --- a/.maintain/monitoring/grafana-dashboards/substrate-networking.json +++ b/scripts/ci/monitoring/grafana-dashboards/substrate-networking.json @@ -4,7 +4,7 @@ "name": "VAR_METRIC_NAMESPACE", "type": "constant", "label": "Prefix of the metrics", - "value": "polkadot", + "value": "substrate", "description": "" } ], diff --git a/.maintain/monitoring/grafana-dashboards/substrate-service-tasks.json b/scripts/ci/monitoring/grafana-dashboards/substrate-service-tasks.json similarity index 99% rename from .maintain/monitoring/grafana-dashboards/substrate-service-tasks.json rename to scripts/ci/monitoring/grafana-dashboards/substrate-service-tasks.json index 2f08ac7bb34c5..ce7e9f78cd8ae 100644 --- a/.maintain/monitoring/grafana-dashboards/substrate-service-tasks.json +++ b/scripts/ci/monitoring/grafana-dashboards/substrate-service-tasks.json @@ -4,7 +4,7 @@ "name": "VAR_METRIC_NAMESPACE", "type": "constant", "label": "Prefix of the metrics", - "value": "polkadot", + "value": "substrate", "description": "" } ], diff --git a/.maintain/node-template-release.sh b/scripts/ci/node-template-release.sh similarity index 84% rename from .maintain/node-template-release.sh rename to scripts/ci/node-template-release.sh index cb5e72e7fa98f..09ef98e04627a 100755 --- a/.maintain/node-template-release.sh +++ b/scripts/ci/node-template-release.sh @@ -11,6 +11,6 @@ if [ "$#" -ne 1 ]; then fi PATH_TO_ARCHIVE=$1 -cd $PROJECT_ROOT/.maintain/node-template-release +cd $PROJECT_ROOT/scripts/ci/node-template-release cargo run $PROJECT_ROOT/bin/node-template $PROJECT_ROOT/$PATH_TO_ARCHIVE diff --git a/.maintain/node-template-release/Cargo.toml b/scripts/ci/node-template-release/Cargo.toml similarity index 73% rename from .maintain/node-template-release/Cargo.toml rename to scripts/ci/node-template-release/Cargo.toml index c1d9f2da7faea..668e0f3f62a7f 100644 --- a/.maintain/node-template-release/Cargo.toml +++ b/scripts/ci/node-template-release/Cargo.toml @@ -2,20 +2,22 @@ name = "node-template-release" version = "3.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0" +homepage = "https://substrate.io" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] [dependencies] -toml = "0.4" -tar = "0.4" -glob = "0.2" -structopt = "0.3" -tempfile = "3" +clap = { version = "4.0.9", features = ["derive"] } +flate2 = "1.0" fs_extra = "1" git2 = "0.8" -flate2 = "1.0" +glob = "0.2" +tar = "0.4" +tempfile = "3" +toml = "0.4" [workspace] - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] diff --git a/.maintain/node-template-release/src/main.rs b/scripts/ci/node-template-release/src/main.rs similarity index 97% rename from .maintain/node-template-release/src/main.rs rename to scripts/ci/node-template-release/src/main.rs index 7dcb1f0f4d816..91a7e865458cf 100644 --- a/.maintain/node-template-release/src/main.rs +++ b/scripts/ci/node-template-release/src/main.rs @@ -1,4 +1,4 @@ -use structopt::StructOpt; +use clap::Parser; use std::{ collections::HashMap, @@ -26,13 +26,13 @@ const SUBSTRATE_GIT_URL: &str = "https://github.com/paritytech/substrate.git"; type CargoToml = HashMap; -#[derive(StructOpt)] +#[derive(Parser)] struct Options { /// The path to the `node-template` source. - #[structopt(parse(from_os_str))] + #[arg()] node_template: PathBuf, /// The path where to output the generated `tar.gz` file. - #[structopt(parse(from_os_str))] + #[arg()] output: PathBuf, } @@ -209,7 +209,7 @@ fn build_and_test(path: &Path, cargo_tomls: &[PathBuf]) { } fn main() { - let options = Options::from_args(); + let options = Options::parse(); let build_dir = tempfile::tempdir().expect("Creates temp build dir"); @@ -261,8 +261,7 @@ fn main() { // adding root rustfmt to node template build path let node_template_rustfmt_toml_path = node_template_path.join("rustfmt.toml"); - let root_rustfmt_toml = - &options.node_template.join("../../rustfmt.toml"); + let root_rustfmt_toml = &options.node_template.join("../../rustfmt.toml"); if root_rustfmt_toml.exists() { fs::copy(&root_rustfmt_toml, &node_template_rustfmt_toml_path) .expect("Copying rustfmt.toml."); diff --git a/scripts/run_all_benchmarks.sh b/scripts/run_all_benchmarks.sh new file mode 100755 index 0000000000000..dd5d2e182baf2 --- /dev/null +++ b/scripts/run_all_benchmarks.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +# This file is part of Substrate. +# Copyright (C) 2022 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. + +# This script has three parts which all use the Substrate runtime: +# - Pallet benchmarking to update the pallet weights +# - Overhead benchmarking for the Extrinsic and Block weights +# - Machine benchmarking +# +# Should be run on a reference machine to gain accurate benchmarks +# current reference machine: https://github.com/paritytech/substrate/pull/5848 + +while getopts 'bfp:v' flag; do + case "${flag}" in + b) + # Skip build. + skip_build='true' + ;; + f) + # Fail if any sub-command in a pipe fails, not just the last one. + set -o pipefail + # Fail on undeclared variables. + set -u + # Fail if any sub-command fails. + set -e + # Fail on traps. + set -E + ;; + p) + # Start at pallet + start_pallet="${OPTARG}" + ;; + v) + # Echo all executed commands. + set -x + ;; + *) + # Exit early. + echo "Bad options. Check Script." + exit 1 + ;; + esac +done + + +if [ "$skip_build" != true ] +then + echo "[+] Compiling Substrate benchmarks..." + cargo build --profile=production --locked --features=runtime-benchmarks +fi + +# The executable to use. +SUBSTRATE=./target/production/substrate + +# Manually exclude some pallets. +EXCLUDED_PALLETS=( + # Helper pallets + "pallet_election_provider_support_benchmarking" + # Pallets without automatic benchmarking + "pallet_babe" + "pallet_grandpa" + "pallet_mmr" + "pallet_offences" +) + +# Load all pallet names in an array. +ALL_PALLETS=($( + $SUBSTRATE benchmark pallet --list --chain=dev |\ + tail -n+2 |\ + cut -d',' -f1 |\ + sort |\ + uniq +)) + +# Filter out the excluded pallets by concatenating the arrays and discarding duplicates. +PALLETS=($({ printf '%s\n' "${ALL_PALLETS[@]}" "${EXCLUDED_PALLETS[@]}"; } | sort | uniq -u)) + +echo "[+] Benchmarking ${#PALLETS[@]} Substrate pallets by excluding ${#EXCLUDED_PALLETS[@]} from ${#ALL_PALLETS[@]}." + +# Define the error file. +ERR_FILE="benchmarking_errors.txt" +# Delete the error file before each run. +rm -f $ERR_FILE + +# Benchmark each pallet. +for PALLET in "${PALLETS[@]}"; do + # If `-p` is used, skip benchmarks until the start pallet. + if [ ! -z "$start_pallet" ] && [ "$start_pallet" != "$PALLET" ] + then + echo "[+] Skipping ${PALLET}..." + continue + else + unset start_pallet + fi + + FOLDER="$(echo "${PALLET#*_}" | tr '_' '-')"; + WEIGHT_FILE="./frame/${FOLDER}/src/weights.rs" + echo "[+] Benchmarking $PALLET with weight file $WEIGHT_FILE"; + + OUTPUT=$( + $SUBSTRATE benchmark pallet \ + --chain=dev \ + --steps=50 \ + --repeat=20 \ + --pallet="$PALLET" \ + --extrinsic="*" \ + --execution=wasm \ + --wasm-execution=compiled \ + --heap-pages=4096 \ + --output="$WEIGHT_FILE" \ + --header="./HEADER-APACHE2" \ + --template=./.maintain/frame-weight-template.hbs 2>&1 + ) + if [ $? -ne 0 ]; then + echo "$OUTPUT" >> "$ERR_FILE" + echo "[-] Failed to benchmark $PALLET. Error written to $ERR_FILE; continuing..." + fi +done + +# Update the block and extrinsic overhead weights. +echo "[+] Benchmarking block and extrinsic overheads..." +OUTPUT=$( + $SUBSTRATE benchmark overhead \ + --chain=dev \ + --execution=wasm \ + --wasm-execution=compiled \ + --weight-path="./frame/support/src/weights/" \ + --header="./HEADER-APACHE2" \ + --warmup=10 \ + --repeat=100 2>&1 +) +if [ $? -ne 0 ]; then + echo "$OUTPUT" >> "$ERR_FILE" + echo "[-] Failed to benchmark the block and extrinsic overheads. Error written to $ERR_FILE; continuing..." +fi + +echo "[+] Benchmarking the machine..." +OUTPUT=$( + $SUBSTRATE benchmark machine --chain=dev 2>&1 +) +if [ $? -ne 0 ]; then + # Do not write the error to the error file since it is not a benchmarking error. + echo "[-] Failed the machine benchmark:\n$OUTPUT" +fi + +# Check if the error file exists. +if [ -f "$ERR_FILE" ]; then + echo "[-] Some benchmarks failed. See: $ERR_FILE" + exit 1 +else + echo "[+] All benchmarks passed." + exit 0 +fi diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 9a2d30400631f..0000000000000 --- a/shell.nix +++ /dev/null @@ -1,27 +0,0 @@ -let - mozillaOverlay = - import (builtins.fetchGit { - url = "https://github.com/mozilla/nixpkgs-mozilla.git"; - rev = "4a07484cf0e49047f82d83fd119acffbad3b235f"; - }); - nixpkgs = import { overlays = [ mozillaOverlay ]; }; - rust-nightly = with nixpkgs; ((rustChannelOf { date = "2021-07-06"; channel = "nightly"; }).rust.override { - extensions = [ "rust-src" ]; - targets = [ "wasm32-unknown-unknown" ]; - }); -in -with nixpkgs; pkgs.mkShell { - buildInputs = [ - clang - openssl.dev - pkg-config - rust-nightly - ] ++ lib.optionals stdenv.isDarwin [ - darwin.apple_sdk.frameworks.Security - ]; - - RUST_SRC_PATH = "${rust-nightly}/lib/rustlib/src/rust/src"; - LIBCLANG_PATH = "${llvmPackages.libclang.lib}/lib"; - PROTOC = "${protobuf}/bin/protoc"; - ROCKSDB_LIB_DIR = "${rocksdb}/lib"; -} diff --git a/simnet_tests/README.md b/simnet_tests/README.md deleted file mode 100644 index cb1b13ae98505..0000000000000 --- a/simnet_tests/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Simulation tests, or high level integration tests. - - -_The content of this directory is meant to be used by Parity's private CI/CD -infrastructure with private tools. At the moment those tools are still early -stage of development and we don't when if / when they will available for -public use._ - - -## Content of this dir. - -`configs` dir contains config files in toml format that describe how to -configure the simulation network that you want to launch. - -`tests` dir contains [cucumber](https://cucumber.io/) files. Those are -Behavior-Driven Development test files that describe tests in plain English. -Under the hood there are assertions that specific metrics should have specific -values. - -At the moment we have 2 tests: `tests/quick/001-smoketest.feature` and -`tests/long/002-loadtest.feature` -The load test uses a JS script that we added to simnet image and it's launched -by this step in the cucumber file: -`Then launch 'node' with parameters '/usr/local/bin/sub-flood --finalization --url ws://localhost:11222'` - -`run_test.sh` is a script meant to ease up launching a test. -In order to use this script locally, you need to install -[gurke](https://github.com/paritytech/gurke) -This script also helps preparing the test environment. Once you have access to -a kubernetes cluster (meaning you can do `kubectl get pods`) you can run this -script with no arguments, like `./run_test.sh` and tests should run. -Kubernetes cluster can be local, spawned with -[kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) -or an instance living in the -[cloud](https://github.com/paritytech/gurke/blob/main/docs/How-to-setup-access-to-gke-k8s-cluster.md) - - -### [Here is link to barcamp presenation of simnet](https://www.crowdcast.io/e/ph49xu01) -### [Here is link to the simnet repo, hosted on private gitlab](https://gitlab.parity.io/parity/simnet/-/tree/master) diff --git a/simnet_tests/configs/default_local_testnet.toml b/simnet_tests/configs/default_local_testnet.toml deleted file mode 100644 index 066bd4c9e332f..0000000000000 --- a/simnet_tests/configs/default_local_testnet.toml +++ /dev/null @@ -1,14 +0,0 @@ -[settings] -bootnode-domain-name = "bootnode.{{get_env(name="NAMESPACE")}}.svc.cluster.local" - - -[settings.setup] -timeout = 300 - -[settings.defaults] -timeout = 300 - -[nodes] -alice = { extra-args = ["--alice"], validator = true } -bob = { extra-args = ["--bob"], validator = true } -charlie = { extra-args = ["--charlie"], validator = true } diff --git a/simnet_tests/run_tests.sh b/simnet_tests/run_tests.sh deleted file mode 100755 index 3b8ac8a71dadf..0000000000000 --- a/simnet_tests/run_tests.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/bash - -### ARGS FOR THIS SCRIPT ### -# ./${SCRIPT_NAME} NAMESPACE IMAGE LOG_PATH FEATURES -# NAMESPACE the kubernetes namespace where the test will run -# IMAGE Substrate image used to spawn network -# LOG_PATH path to dir where to save logs from external JS script that is run as part -# of step in features file -# FEATURES directory containing cucumber files or single cucumber file that describes -# what to test. -# -# All args have default values, specify args to override -# e.g: ./${SCRIPT_NAME} test-name parity/substrate:latest logs quick - -set -eou pipefail -SCRIPT_NAME="$0" -SCRIPT_PATH=$(dirname "${SCRIPT_NAME}") # relative -SCRIPT_PATH=$(cd "${SCRIPT_PATH}" && pwd) # absolutized and normalized - -function random_string { - head -1 <(fold -w 30 <(tr -dc 'a-z0-9' < /dev/urandom)) - } - -# -### Script args -# - -NAMESPACE=${1:-gurke-"$(random_string)"-runtest} -IMAGE=${2:-"parity/substrate:latest"} -LOG_PATH=${3:-"${SCRIPT_PATH}/logs"} -FEATURES=${4:-"ALL"} - -mkdir -p "${SCRIPT_PATH}"/logs - -echo "Running tests in namespace: ${NAMESPACE}" -echo "Testing image: ${IMAGE}" -echo "Storing scripts logs to: ${LOG_PATH}" -echo "Using features files from: ${FEATURES}" - -# -### Script logic -# - -function forward_port { - # RUN_IN_CONTAINER is env var that is set in the dockerfile - # use the -v operator to explicitly test if a variable is set - if [[ ! -v RUN_IN_CONTAINER ]] ; then - if is_port_forward_running ; then - kill_previous_job - fi - fi - start_forwading_job -} - -FORWARD_GREP_FILTER='kubectl.*[p]ort-forward.*svc/rpc.*11222' - -function is_port_forward_running { - # shellcheck disable=SC2009 - ps aux | grep -qE "${FORWARD_GREP_FILTER}" -} - -function kill_previous_job { - # shellcheck disable=SC2009 - job_pid=$(ps aux | grep -E "${FORWARD_GREP_FILTER}" | awk '{ print $2 }') - echo "INFO Killed forwading port 9944 into bootnode" - kill "${job_pid}" -} - -function start_forwading_job { - kubectl -n "${NAMESPACE}" \ - expose pod bootnode \ - --name=rpc \ - --type=NodePort \ - --target-port=9944 \ - --port=9944 - kubectl -n "${NAMESPACE}" \ - port-forward svc/rpc 11222:9944 &> "${LOG_PATH}/forward-${NAMESPACE}.log" & - sleep 2 - echo "INFO Started forwading port 9944 into bootnode" -} - -function update_api { - echo "INFO: Updating Polkadot JS API" - pwd - cd "${SCRIPT_PATH}"/../../sub-flood/ - npm run build - cd - -} - -function run_test { - case "${FEATURES}" in - quick) - gurke test "${NAMESPACE}" "${SCRIPT_PATH}"/tests/quick --log-path "${LOG_PATH}" - ;; - long) - gurke test "${NAMESPACE}" "${SCRIPT_PATH}"/tests/long --log-path "${LOG_PATH}" - ;; - ALL ) - gurke test "${NAMESPACE}" "${SCRIPT_PATH}"/tests --log-path "${LOG_PATH}" - ;; - ??* ) - gurke test \ - "${NAMESPACE}" \ - "${SCRIPT_PATH}"/"${FEATURES}" \ - --log-path "${LOG_PATH}" - ;; - esac -} - - -export NAMESPACE="${NAMESPACE}" - -set -x # echo the commands to stdout -gurke spawn --config "${SCRIPT_PATH}"/configs/default_local_testnet.toml \ - -n "${NAMESPACE}" \ - --image "${IMAGE}" - -echo "INFO: Checking if pods launched correctly" -kubectl -n "${NAMESPACE}" get pods -o wide - -update_api - -forward_port -run_test - - diff --git a/simnet_tests/tests/long/002-loadtest.feature b/simnet_tests/tests/long/002-loadtest.feature deleted file mode 100644 index 67d108ea55416..0000000000000 --- a/simnet_tests/tests/long/002-loadtest.feature +++ /dev/null @@ -1,5 +0,0 @@ -Feature: LoadTesting - - Scenario: spawn 50k transactions and wait their finalization - Given a test network - Then launch 'node' with parameters '/usr/local/bin/sub-flood --finalization --url ws://localhost:11222' diff --git a/simnet_tests/tests/quick/001-smoketest.feature b/simnet_tests/tests/quick/001-smoketest.feature deleted file mode 100644 index a07041e4ea622..0000000000000 --- a/simnet_tests/tests/quick/001-smoketest.feature +++ /dev/null @@ -1,16 +0,0 @@ -Feature: Smoketest - - Scenario: Minimal Example - Given a test network - Then alice is up - And alice reports substrate_node_roles is 4 - And alice reports substrate_sub_libp2p_is_major_syncing is 0 - When alice's best block should be above 30 - Then alice reports block height is greater than 30 - And alice reports peers count is at least 2 - Then bob is up - And bob reports block height is greater than 30 - And bob reports peers count is at least 2 - Then charlie is up - And charlie reports block height is greater than 30 - And charlie reports peers count is at least 2 diff --git a/ss58-registry.json b/ss58-registry.json deleted file mode 100644 index 563cc248db9dd..0000000000000 --- a/ss58-registry.json +++ /dev/null @@ -1,644 +0,0 @@ -{ - "specification": "https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58)", - "schema": { - "prefix": "The address prefix. Must be an integer and unique.", - "network": "Unique identifier for the network that will use this prefix, string, no spaces. To integrate with CLI tools, e.g. `--network polkadot`.", - "displayName": "The name of the network that will use this prefix, in a format friendly for display.", - "symbols": "Array of symbols of any tokens the chain uses, usually 2-5 characters. Most chains will only have one. Chains that have multiple instances of the Balances pallet should order the array by instance.", - "decimals": "Array of integers representing the number of decimals that represent a single unit to the end user. Must be same length as `symbols` to represent each token's denomination.", - "standardAccount": "Signing curve for standard account. Substrate supports ed25519, sr25519, and secp256k1.", - "website": "A website or Github repo associated with the network." - }, - "registry": [ - { - "prefix": 0, - "network": "polkadot", - "displayName": "Polkadot Relay Chain", - "symbols": ["DOT"], - "decimals": [10], - "standardAccount": "*25519", - "website": "https://polkadot.network" - }, - { - "prefix": 1, - "network": null, - "displayName": "Bare 32-bit Schnorr/Ristretto (S/R 25519) public key.", - "symbols": null, - "decimals": null, - "standardAccount": null, - "website": null - }, - { - "prefix": 2, - "network": "kusama", - "displayName": "Kusama Relay Chain", - "symbols": ["KSM"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://kusama.network" - }, - { - "prefix": 3, - "network": null, - "displayName": "Bare 32-bit Ed25519 public key.", - "symbols": null, - "decimals": null, - "standardAccount": null, - "website": null - }, - { - "prefix": 4, - "network": "katalchain", - "displayName": "Katal Chain", - "symbols": null, - "decimals": null, - "standardAccount": "*25519", - "website": null - }, - { - "prefix": 5, - "network": "plasm", - "displayName": "Plasm Network", - "symbols": ["PLM"], - "decimals": [15], - "standardAccount": "*25519", - "website": "https://plasmnet.io" - }, - { - "prefix": 6, - "network": "bifrost", - "displayName": "Bifrost", - "symbols": ["BNC"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://bifrost.finance/" - }, - { - "prefix": 7, - "network": "edgeware", - "displayName": "Edgeware", - "symbols": ["EDG"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://edgewa.re" - }, - { - "prefix": 8, - "network": "karura", - "displayName": "Karura", - "symbols": ["KAR"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://karura.network/" - }, - { - "prefix": 9, - "network": "reynolds", - "displayName": "Laminar Reynolds Canary", - "symbols": ["REY"], - "decimals": [18], - "standardAccount": "*25519", - "website": "http://laminar.network/" - }, - { - "prefix": 10, - "network": "acala", - "displayName": "Acala", - "symbols": ["ACA"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://acala.network/" - }, - { - "prefix": 11, - "network": "laminar", - "displayName": "Laminar", - "symbols": ["LAMI"], - "decimals": [18], - "standardAccount": "*25519", - "website": "http://laminar.network/" - }, - { - "prefix": 12, - "network": "polymesh", - "displayName": "Polymesh", - "symbols": ["POLYX"], - "decimals": [6], - "standardAccount": "*25519", - "website": "https://polymath.network/" - }, - { - "prefix": 13, - "network": "integritee", - "displayName": "Integritee", - "symbols": ["TEER"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://integritee.network" - }, - { - "prefix": 14, - "network": "totem", - "displayName": "Totem", - "symbols": ["XTX"], - "decimals": [0], - "standardAccount": "*25519", - "website": "https://totemaccounting.com" - }, - { - "prefix": 15, - "network": "synesthesia", - "displayName": "Synesthesia", - "symbols": ["SYN"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://synesthesia.network/" - }, - { - "prefix": 16, - "network": "kulupu", - "displayName": "Kulupu", - "symbols": ["KLP"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://kulupu.network/" - }, - { - "prefix": 17, - "network": "dark", - "displayName": "Dark Mainnet", - "symbols": null, - "decimals": null, - "standardAccount": "*25519", - "website": null - }, - { - "prefix": 18, - "network": "darwinia", - "displayName": "Darwinia Network", - "symbols": ["RING", "KTON"], - "decimals": [9, 9], - "standardAccount": "*25519", - "website": "https://darwinia.network/" - }, - { - "prefix": 19, - "network": "geek", - "displayName": "GeekCash", - "symbols": ["GEEK"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://geekcash.org" - }, - { - "prefix": 20, - "network": "stafi", - "displayName": "Stafi", - "symbols": ["FIS"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://stafi.io" - }, - { - "prefix": 21, - "network": "dock-testnet", - "displayName": "Dock Testnet", - "symbols": ["DCK"], - "decimals": [6], - "standardAccount": "*25519", - "website": "https://dock.io" - }, - { - "prefix": 22, - "network": "dock-mainnet", - "displayName": "Dock Mainnet", - "symbols": ["DCK"], - "decimals": [6], - "standardAccount": "*25519", - "website": "https://dock.io" - }, - { - "prefix": 23, - "network": "shift", - "displayName": "ShiftNrg", - "symbols": null, - "decimals": null, - "standardAccount": "*25519", - "website": null - }, - { - "prefix": 24, - "network": "zero", - "displayName": "ZERO", - "symbols": ["PLAY"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://zero.io" - }, - { - "prefix": 25, - "network": "zero-alphaville", - "displayName": "ZERO Alphaville", - "symbols": ["PLAY"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://zero.io" - }, - { - "prefix": 26, - "network": "jupiter", - "displayName": "Jupiter", - "symbols": ["jDOT"], - "decimals": [10], - "standardAccount": "*25519", - "website": "https://jupiter.patract.io" - }, - { - "prefix": 28, - "network": "subsocial", - "displayName": "Subsocial", - "symbols": null, - "decimals": null, - "standardAccount": "*25519", - "website": null - }, - { - "prefix": 29, - "network": "cord", - "displayName": "Dhiway CORD Network", - "symbols": ["DCU"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://dhiway.com/" - }, - { - "prefix": 30, - "network": "phala", - "displayName": "Phala Network", - "symbols": ["PHA"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://phala.network" - }, - { - "prefix": 31, - "network": "litentry", - "displayName": "Litentry Network", - "symbols": ["LIT"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://litentry.com/" - }, - { - "prefix": 32, - "network": "robonomics", - "displayName": "Robonomics", - "symbols": ["XRT"], - "decimals": [9], - "standardAccount": "*25519", - "website": "https://robonomics.network" - }, - { - "prefix": 33, - "network": "datahighway", - "displayName": "DataHighway", - "symbols": null, - "decimals": null, - "standardAccount": "*25519", - "website": null - }, - { - "prefix": 34, - "network": "ares", - "displayName": "Ares Protocol", - "symbols": ["ARES"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://www.aresprotocol.com/" - }, - { - "prefix": 35, - "network": "vln", - "displayName": "Valiu Liquidity Network", - "symbols": ["USDv"], - "decimals": [15], - "standardAccount": "*25519", - "website": "https://valiu.com/" - }, - { - "prefix": 36, - "network": "centrifuge", - "displayName": "Centrifuge Chain", - "symbols": ["CFG"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://centrifuge.io/" - }, - { - "prefix": 37, - "network": "nodle", - "displayName": "Nodle Chain", - "symbols": ["NODL"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://nodle.io/" - }, - { - "prefix": 38, - "network": "kilt", - "displayName": "KILT Chain", - "symbols": ["KILT"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://kilt.io/" - }, - { - "prefix": 39, - "network": "mathchain", - "displayName": "MathChain mainnet", - "symbols": ["MATH"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://mathwallet.org" - }, - { - "prefix": 40, - "network": "mathchain-testnet", - "displayName": "MathChain testnet", - "symbols": ["MATH"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://mathwallet.org" - }, - { - "prefix": 41, - "network": "poli", - "displayName": "Polimec Chain", - "symbols": null, - "decimals": null, - "standardAccount": "*25519", - "website": "https://polimec.io/" - }, - { - "prefix": 42, - "network": "substrate", - "displayName": "Substrate", - "symbols": null, - "decimals": null, - "standardAccount": "*25519", - "website": "https://substrate.dev/" - }, - { - "prefix": 43, - "network": null, - "displayName": "Bare 32-bit ECDSA SECP-256k1 public key.", - "symbols": null, - "decimals": null, - "standardAccount": null, - "website": null - }, - { - "prefix": 44, - "network": "chainx", - "displayName": "ChainX", - "symbols": ["PCX"], - "decimals": [8], - "standardAccount": "*25519", - "website": "https://chainx.org/" - }, - { - "prefix": 45, - "network": "uniarts", - "displayName": "UniArts Network", - "symbols": ["UART", "UINK"], - "decimals": [12, 12], - "standardAccount": "*25519", - "website": "https://uniarts.me" - }, - { - "prefix": 46, - "network": "reserved46", - "displayName": "This prefix is reserved.", - "symbols": null, - "decimals": null, - "standardAccount": null, - "website": null - }, - { - "prefix": 47, - "network": "reserved47", - "displayName": "This prefix is reserved.", - "symbols": null, - "decimals": null, - "standardAccount": null, - "website": null - }, - { - "prefix": 48, - "network": "neatcoin", - "displayName": "Neatcoin Mainnet", - "symbols": ["NEAT"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://neatcoin.org" - }, - { - "prefix": 49, - "network": "picasso", - "displayName": "Picasso", - "symbols": ["PICA"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://picasso.composable.finance" - }, - { - "prefix": 50, - "network": "composable", - "displayName": "Composable", - "symbols": ["LAYR"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://composable.finance" - }, - { - "prefix": 63, - "network": "hydradx", - "displayName": "HydraDX", - "symbols": ["HDX"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://hydradx.io" - }, - { - "prefix": 65, - "network": "aventus", - "displayName": "AvN Mainnet", - "symbols": ["AVT"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://aventus.io" - }, - { - "prefix": 66, - "network": "crust", - "displayName": "Crust Network", - "symbols": ["CRU"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://crust.network" - }, - { - "prefix": 67, - "network": "equilibrium", - "displayName": "Equilibrium Network", - "symbols": ["Unknown", "USD", "EQ", "ETH", "BTC", "EOS", "DOT", "CRV"], - "decimals": [0,9,9,9,9,9,9,9], - "standardAccount": "*25519", - "website": "https://equilibrium.io" - }, - { - "prefix": 69, - "network": "sora", - "displayName": "SORA Network", - "symbols": ["XOR"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://sora.org" - }, - { - "prefix": 73, - "network": "zeitgeist", - "displayName": "Zeitgeist", - "symbols": ["ZTG"], - "decimals": [10], - "standardAccount": "*25519", - "website": "https://zeitgeist.pm" - }, - { - "prefix": 77, - "network": "manta", - "displayName": "Manta network", - "symbols": ["MA"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://manta.network" - }, - { - "prefix": 78, - "network": "calamari", - "displayName": "Calamari: Manta Canary Network", - "symbols": ["KMA"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://manta.network" - }, - { - "prefix": 98, - "network": "polkasmith", - "displayName": "PolkaSmith Canary Network", - "symbols": ["PKS"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://polkafoundry.com" - }, - { - "prefix": 99, - "network": "polkafoundry", - "displayName": "PolkaFoundry Network", - "symbols": ["PKF"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://polkafoundry.com" - }, - { - "prefix": 101, - "network": "origintrail-parachain", - "displayName": "OriginTrail Parachain", - "symbols": ["TRAC"], - "decimals": [18], - "standardAccount": "secp256k1", - "website": "https://origintrail.io" - }, - { - "prefix": 110, - "network": "heiko", - "displayName": "Heiko", - "symbols": ["HKO"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://parallel.fi/" - }, - { - "prefix": 113, - "network": "integritee-incognito", - "displayName": "Integritee Incognito", - "symbols": null, - "decimals": null, - "standardAccount": "*25519", - "website": "https://integritee.network" - }, - { - "prefix": 128, - "network": "clover", - "displayName": "Clover Finance", - "symbols": ["CLV"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://clover.finance" - }, - { - "prefix": 136, - "network": "altair", - "displayName": "Altair", - "symbols": ["AIR"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://centrifuge.io/" - }, - { - "prefix": 172, - "network": "parallel", - "displayName": "Parallel", - "symbols": ["PARA"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://parallel.fi/" - }, - { - "prefix": 252, - "network": "social-network", - "displayName": "Social Network", - "symbols": ["NET"], - "decimals": [18], - "standardAccount": "*25519", - "website": "https://social.network" - }, - { - "prefix": 1284, - "network": "moonbeam", - "displayName": "Moonbeam", - "symbols": ["GLMR"], - "decimals": [18], - "standardAccount": "secp256k1", - "website": "https://moonbeam.network" - }, - { - "prefix": 1285, - "network": "moonriver", - "displayName": "Moonriver", - "symbols": ["MOVR"], - "decimals": [18], - "standardAccount": "secp256k1", - "website": "https://moonbeam.network" - }, - { - "prefix": 10041, - "network": "basilisk", - "displayName": "Basilisk", - "symbols": ["BSX"], - "decimals": [12], - "standardAccount": "*25519", - "website": "https://bsx.fi" - } - ] -} diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 4eed6e5e29133..cd4839fa1cbd7 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -2,20 +2,21 @@ name = "substrate-test-utils" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate test utilities" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.16" +tokio = { version = "1.22.0", features = ["macros", "time"] } substrate-test-utils-derive = { version = "0.10.0-dev", path = "./derive" } -tokio = { version = "1.10", features = ["macros", "time"] } [dev-dependencies] +trybuild = { version = "1.0.53", features = [ "diff" ] } sc-service = { version = "0.10.0-dev", path = "../client/service" } -trybuild = { version = "1.0.43", features = [ "diff" ] } diff --git a/test-utils/client/Cargo.toml b/test-utils/client/Cargo.toml index 34238872cad84..106ec21d79464 100644 --- a/test-utils/client/Cargo.toml +++ b/test-utils/client/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "substrate-test-client" +description = "Client testing utilities" version = "2.0.1" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" publish = false @@ -12,27 +13,26 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0" } -futures = "0.3.16" -hex = "0.4" -serde = "1.0.126" -serde_json = "1.0.68" +array-bytes = "4.1" +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" +serde = "1.0.136" +serde_json = "1.0.85" sc-client-api = { version = "4.0.0-dev", path = "../../client/api" } -sc-client-db = { version = "0.10.0-dev", features = [ - "test-helpers", +sc-client-db = { version = "0.10.0-dev", default-features = false, features = [ + "test-helpers", ], path = "../../client/db" } sc-consensus = { version = "0.10.0-dev", path = "../../client/consensus/common" } sc-executor = { version = "0.10.0-dev", path = "../../client/executor" } -sc-light = { version = "4.0.0-dev", path = "../../client/light" } sc-offchain = { version = "4.0.0-dev", path = "../../client/offchain" } sc-service = { version = "0.10.0-dev", default-features = false, features = [ - "test-helpers", + "test-helpers", ], path = "../../client/service" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", path = "../../primitives/keystore" } -sp-keyring = { version = "4.0.0-dev", path = "../../primitives/keyring" } -sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime" } -sp-state-machine = { version = "0.10.0-dev", path = "../../primitives/state-machine" } -async-trait = "0.1.50" +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } diff --git a/test-utils/client/src/client_ext.rs b/test-utils/client/src/client_ext.rs index bf1c9898972ca..881c50d434264 100644 --- a/test-utils/client/src/client_ext.rs +++ b/test-utils/client/src/client_ext.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,14 +22,14 @@ use sc_client_api::{backend::Finalizer, client::BlockBackend}; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; use sc_service::client::Client; use sp_consensus::{BlockOrigin, Error as ConsensusError}; -use sp_runtime::{generic::BlockId, traits::Block as BlockT, Justification, Justifications}; +use sp_runtime::{traits::Block as BlockT, Justification, Justifications}; /// Extension trait for a test client. pub trait ClientExt: Sized { /// Finalize a block. fn finalize_block( &self, - id: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()>; @@ -69,16 +69,16 @@ pub trait ClientBlockImportExt: Sized { impl ClientExt for Client where B: sc_client_api::backend::Backend, - E: sc_client_api::CallExecutor + 'static, + E: sc_client_api::CallExecutor + sc_executor::RuntimeVersionOf + 'static, Self: BlockImport, Block: BlockT, { fn finalize_block( &self, - id: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()> { - Finalizer::finalize_block(self, id, justification, true) + Finalizer::finalize_block(self, hash, justification, true) } fn genesis_hash(&self) -> ::Hash { diff --git a/test-utils/client/src/lib.rs b/test-utils/client/src/lib.rs index 9bc411af5d3ed..8ee652abe2c70 100644 --- a/test-utils/client/src/lib.rs +++ b/test-utils/client/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,9 +26,9 @@ pub use sc_client_api::{ execution_extensions::{ExecutionExtensions, ExecutionStrategies}, BadBlocks, ForkBlocks, }; -pub use sc_client_db::{self, Backend}; +pub use sc_client_db::{self, Backend, BlocksPruning}; pub use sc_executor::{self, NativeElseWasmExecutor, WasmExecutionMethod}; -pub use sc_service::{client, RpcHandlers, RpcSession}; +pub use sc_service::{client, RpcHandlers}; pub use sp_consensus; pub use sp_keyring::{ ed25519::Keyring as Ed25519Keyring, sr25519::Keyring as Sr25519Keyring, AccountKeyring, @@ -37,29 +37,18 @@ pub use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; pub use sp_runtime::{Storage, StorageChild}; pub use sp_state_machine::ExecutionStrategy; -use futures::{ - future::{Future, FutureExt}, - stream::StreamExt, -}; +use futures::{future::Future, stream::StreamExt}; use sc_client_api::BlockchainEvents; use sc_service::client::{ClientConfig, LocalCallExecutor}; use serde::Deserialize; use sp_core::storage::ChildInfo; -use sp_runtime::{ - codec::Encode, - traits::{BlakeTwo256, Block as BlockT}, - OpaqueExtrinsic, -}; +use sp_runtime::{codec::Encode, traits::Block as BlockT, OpaqueExtrinsic}; use std::{ collections::{HashMap, HashSet}, pin::Pin, sync::Arc, }; -/// Test client light database backend. -pub type LightBackend = - sc_light::Backend, BlakeTwo256>; - /// A genesis storage initialization trait. pub trait GenesisInit: Default { /// Construct genesis storage. @@ -106,18 +95,15 @@ impl } /// Create new `TestClientBuilder` with default backend and pruning window size - pub fn with_pruning_window(keep_blocks: u32) -> Self { - let backend = Arc::new(Backend::new_test(keep_blocks, 0)); + pub fn with_pruning_window(blocks_pruning: u32) -> Self { + let backend = Arc::new(Backend::new_test(blocks_pruning, 0)); Self::with_backend(backend) } /// Create new `TestClientBuilder` with default backend and storage chain mode - pub fn with_tx_storage(keep_blocks: u32) -> Self { - let backend = Arc::new(Backend::new_test_with_tx_storage( - keep_blocks, - 0, - sc_client_db::TransactionStorageMode::StorageChain, - )); + pub fn with_tx_storage(blocks_pruning: u32) -> Self { + let backend = + Arc::new(Backend::new_test_with_tx_storage(BlocksPruning::Some(blocks_pruning), 0)); Self::with_backend(backend) } } @@ -216,13 +202,13 @@ impl sc_consensus::LongestChain, ) where - ExecutorDispatch: sc_client_api::CallExecutor + 'static, + ExecutorDispatch: + sc_client_api::CallExecutor + sc_executor::RuntimeVersionOf + 'static, Backend: sc_client_api::backend::Backend, >::OffchainStorage: 'static, { let storage = { let mut storage = self.genesis_init.genesis_storage(); - // Add some child storage keys. for (key, child_content) in self.child_storage_extension { storage.children_default.insert( @@ -243,11 +229,6 @@ impl &storage, self.fork_blocks, self.bad_blocks, - ExecutionExtensions::new( - self.execution_strategies, - self.keystore, - sc_offchain::OffchainDb::factory_from_backend(&*self.backend), - ), None, None, ClientConfig { @@ -270,7 +251,8 @@ impl client::LocalCallExecutor>, Backend, G, - > + > where + D: sc_executor::NativeExecutionDispatch, { /// Build the test client with the given native executor. pub fn build_with_native_executor( @@ -291,13 +273,18 @@ impl Backend: sc_client_api::backend::Backend + 'static, { let executor = executor.into().unwrap_or_else(|| { - NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8) + NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8, 2) }); let executor = LocalCallExecutor::new( self.backend.clone(), executor, Box::new(sp_core::testing::TaskExecutor::new()), Default::default(), + ExecutionExtensions::new( + self.execution_strategies.clone(), + self.keystore.clone(), + sc_offchain::OffchainDb::factory_from_backend(&*self.backend), + ), ) .expect("Creates LocalCallExecutor"); @@ -308,16 +295,14 @@ impl /// The output of an RPC transaction. pub struct RpcTransactionOutput { /// The output string of the transaction if any. - pub result: Option, - /// The session object. - pub session: RpcSession, + pub result: String, /// An async receiver if data will be returned via a callback. pub receiver: futures::channel::mpsc::UnboundedReceiver, } impl std::fmt::Debug for RpcTransactionOutput { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "RpcTransactionOutput {{ result: {:?}, session, receiver }}", self.result) + write!(f, "RpcTransactionOutput {{ result: {:?}, receiver }}", self.result) } } @@ -339,56 +324,51 @@ impl std::fmt::Display for RpcTransactionError { } /// An extension trait for `RpcHandlers`. +#[async_trait::async_trait] pub trait RpcHandlersExt { /// Send a transaction through the RpcHandlers. - fn send_transaction( + async fn send_transaction( &self, extrinsic: OpaqueExtrinsic, - ) -> Pin> + Send>>; + ) -> Result; } +#[async_trait::async_trait] impl RpcHandlersExt for RpcHandlers { - fn send_transaction( + async fn send_transaction( &self, extrinsic: OpaqueExtrinsic, - ) -> Pin> + Send>> { - let (tx, rx) = futures::channel::mpsc::unbounded(); - let mem = RpcSession::new(tx.into()); - Box::pin( - self.rpc_query( - &mem, - &format!( - r#"{{ + ) -> Result { + let (result, rx) = self + .rpc_query(&format!( + r#"{{ "jsonrpc": "2.0", "method": "author_submitExtrinsic", "params": ["0x{}"], "id": 0 }}"#, - hex::encode(extrinsic.encode()) - ), - ) - .map(move |result| parse_rpc_result(result, mem, rx)), - ) + array_bytes::bytes2hex("", &extrinsic.encode()) + )) + .await + .expect("valid JSON-RPC request object; qed"); + parse_rpc_result(result, rx) } } pub(crate) fn parse_rpc_result( - result: Option, - session: RpcSession, + result: String, receiver: futures::channel::mpsc::UnboundedReceiver, ) -> Result { - if let Some(ref result) = result { - let json: serde_json::Value = - serde_json::from_str(result).expect("the result can only be a JSONRPC string; qed"); - let error = json.as_object().expect("JSON result is always an object; qed").get("error"); - - if let Some(error) = error { - return Err(serde_json::from_value(error.clone()) - .expect("the JSONRPC result's error is always valid; qed")) - } + let json: serde_json::Value = + serde_json::from_str(&result).expect("the result can only be a JSONRPC string; qed"); + let error = json.as_object().expect("JSON result is always an object; qed").get("error"); + + if let Some(error) = error { + return Err(serde_json::from_value(error.clone()) + .expect("the JSONRPC result's error is always valid; qed")) } - Ok(RpcTransactionOutput { result, session, receiver }) + Ok(RpcTransactionOutput { result, receiver }) } /// An extension trait for `BlockchainEvents`. @@ -429,40 +409,23 @@ where #[cfg(test)] mod tests { - use sc_service::RpcSession; - - fn create_session_and_receiver( - ) -> (RpcSession, futures::channel::mpsc::UnboundedReceiver) { - let (tx, rx) = futures::channel::mpsc::unbounded(); - let mem = RpcSession::new(tx.into()); - - (mem, rx) - } - #[test] fn parses_error_properly() { - let (mem, rx) = create_session_and_receiver(); - assert!(super::parse_rpc_result(None, mem, rx).is_ok()); - - let (mem, rx) = create_session_and_receiver(); + let (_, rx) = futures::channel::mpsc::unbounded(); assert!(super::parse_rpc_result( - Some( - r#"{ + r#"{ "jsonrpc": "2.0", "result": 19, "id": 1 }"# - .to_string() - ), - mem, + .to_string(), rx ) - .is_ok(),); + .is_ok()); - let (mem, rx) = create_session_and_receiver(); + let (_, rx) = futures::channel::mpsc::unbounded(); let error = super::parse_rpc_result( - Some( - r#"{ + r#"{ "jsonrpc": "2.0", "error": { "code": -32601, @@ -470,9 +433,7 @@ mod tests { }, "id": 1 }"# - .to_string(), - ), - mem, + .to_string(), rx, ) .unwrap_err(); @@ -480,10 +441,9 @@ mod tests { assert_eq!(error.message, "Method not found"); assert!(error.data.is_none()); - let (mem, rx) = create_session_and_receiver(); + let (_, rx) = futures::channel::mpsc::unbounded(); let error = super::parse_rpc_result( - Some( - r#"{ + r#"{ "jsonrpc": "2.0", "error": { "code": -32601, @@ -492,9 +452,7 @@ mod tests { }, "id": 1 }"# - .to_string(), - ), - mem, + .to_string(), rx, ) .unwrap_err(); diff --git a/test-utils/derive/Cargo.toml b/test-utils/derive/Cargo.toml index 545e8cf332618..b55ace822ceb3 100644 --- a/test-utils/derive/Cargo.toml +++ b/test-utils/derive/Cargo.toml @@ -2,17 +2,18 @@ name = "substrate-test-utils-derive" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate test utilities macros" +publish = false [dependencies] -quote = "1.0.6" -syn = { version = "1.0.58", features = ["full"] } -proc-macro-crate = "1.0.0" -proc-macro2 = "1.0.29" +proc-macro-crate = "1.1.3" +proc-macro2 = "1.0.37" +quote = "1.0.10" +syn = { version = "1.0.98", features = ["full"] } [lib] proc-macro = true diff --git a/test-utils/derive/src/lib.rs b/test-utils/derive/src/lib.rs index 3f14f67477fad..06b7d2463cbd8 100644 --- a/test-utils/derive/src/lib.rs +++ b/test-utils/derive/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 24f4d404c18bd..7576a63ac3eb1 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -2,10 +2,10 @@ name = "substrate-test-runtime" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" build = "build.rs" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" publish = false @@ -13,49 +13,50 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "4.0.0-dev", default-features = false, path = "../../primitives/application-crypto" } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } +beefy-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "../../frame/beefy-mmr/primitives" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/aura" } sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe" } sp-block-builder = { version = "4.0.0-dev", default-features = false, path = "../../primitives/block-builder" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } -sp-keyring = { version = "4.0.0-dev", optional = true, path = "../../primitives/keyring" } -memory-db = { version = "0.27.0", default-features = false } +sp-keyring = { version = "7.0.0", optional = true, path = "../../primitives/keyring" } +memory-db = { version = "0.31.0", default-features = false } sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../primitives/offchain" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime-interface = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime-interface" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../../primitives/runtime-interface" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../frame/support" } -sp-version = { version = "4.0.0-dev", default-features = false, path = "../../primitives/version" } +sp-version = { version = "5.0.0", default-features = false, path = "../../primitives/version" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } pallet-babe = { version = "4.0.0-dev", default-features = false, path = "../../frame/babe" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../frame/system" } frame-system-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../frame/system/rpc/runtime-api" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../frame/timestamp" } sp-finality-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../primitives/finality-grandpa" } -sp-trie = { version = "4.0.0-dev", default-features = false, path = "../../primitives/trie" } +sp-trie = { version = "7.0.0", default-features = false, path = "../../primitives/trie" } sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../primitives/transaction-pool" } -trie-db = { version = "0.22.6", default-features = false } -parity-util-mem = { version = "0.10.0", default-features = false, features = ["primitive-types"] } +trie-db = { version = "0.24.0", default-features = false } sc-service = { version = "0.10.0-dev", default-features = false, optional = true, features = ["test-helpers"], path = "../../client/service" } -sp-state-machine = { version = "0.10.0-dev", default-features = false, path = "../../primitives/state-machine" } -sp-externalities = { version = "0.10.0-dev", default-features = false, path = "../../primitives/externalities" } +sp-state-machine = { version = "0.13.0", default-features = false, path = "../../primitives/state-machine" } +sp-externalities = { version = "0.13.0", default-features = false, path = "../../primitives/externalities" } # 3rd party cfg-if = "1.0" -log = { version = "0.4.14", default-features = false } -serde = { version = "1.0.126", optional = true, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +serde = { version = "1.0.136", optional = true, features = ["derive"] } [dev-dependencies] sc-block-builder = { version = "0.10.0-dev", path = "../../client/block-builder" } sc-executor = { version = "0.10.0-dev", path = "../../client/executor" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } substrate-test-runtime-client = { version = "2.0.0", path = "./client" } -futures = "0.3.9" +futures = "0.3.21" [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../utils/wasm-builder" } @@ -65,6 +66,8 @@ default = [ "std", ] std = [ + "beefy-primitives/std", + "beefy-merkle-tree/std", "sp-application-crypto/std", "sp-consensus-aura/std", "sp-consensus-babe/std", diff --git a/test-utils/runtime/build.rs b/test-utils/runtime/build.rs index 50c455b4ad831..5a7b518d0bd75 100644 --- a/test-utils/runtime/build.rs +++ b/test-utils/runtime/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test-utils/runtime/client/Cargo.toml b/test-utils/runtime/client/Cargo.toml index 3561697042f2a..2ac944edc637f 100644 --- a/test-utils/runtime/client/Cargo.toml +++ b/test-utils/runtime/client/Cargo.toml @@ -2,9 +2,9 @@ name = "substrate-test-runtime-client" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" publish = false @@ -12,16 +12,15 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sc-light = { version = "4.0.0-dev", path = "../../../client/light" } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } -substrate-test-client = { version = "2.0.0", path = "../../client" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -substrate-test-runtime = { version = "2.0.0", path = "../../runtime" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } +sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -codec = { package = "parity-scale-codec", version = "2.0.0" } -sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } -futures = "0.3.9" +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +substrate-test-client = { version = "2.0.0", path = "../../client" } +substrate-test-runtime = { version = "2.0.0", path = "../../runtime" } diff --git a/test-utils/runtime/client/src/block_builder_ext.rs b/test-utils/runtime/client/src/block_builder_ext.rs index e8c1d2ac5cd48..66b0b4c3b881b 100644 --- a/test-utils/runtime/client/src/block_builder_ext.rs +++ b/test-utils/runtime/client/src/block_builder_ext.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ use sc_client_api::backend; use sp_api::{ApiExt, ProvideRuntimeApi}; -use sp_core::ChangesTrieConfiguration; use sc_block_builder::BlockBuilderApi; @@ -36,11 +35,6 @@ pub trait BlockBuilderExt { key: Vec, value: Option>, ) -> Result<(), sp_blockchain::Error>; - /// Add changes trie configuration update extrinsic to the block. - fn push_changes_trie_configuration_update( - &mut self, - new_config: Option, - ) -> Result<(), sp_blockchain::Error>; } impl<'a, A, B> BlockBuilderExt @@ -68,11 +62,4 @@ where ) -> Result<(), sp_blockchain::Error> { self.push(substrate_test_runtime::Extrinsic::StorageChange(key, value)) } - - fn push_changes_trie_configuration_update( - &mut self, - new_config: Option, - ) -> Result<(), sp_blockchain::Error> { - self.push(substrate_test_runtime::Extrinsic::ChangesTrieConfigUpdate(new_config)) - } } diff --git a/test-utils/runtime/client/src/lib.rs b/test-utils/runtime/client/src/lib.rs index dc5ccadc4574f..99d4e1163e272 100644 --- a/test-utils/runtime/client/src/lib.rs +++ b/test-utils/runtime/client/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,22 +24,18 @@ pub mod trait_tests; mod block_builder_ext; pub use sc_consensus::LongestChain; -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; pub use substrate_test_client::*; pub use substrate_test_runtime as runtime; pub use self::block_builder_ext::BlockBuilderExt; -use sc_client_api::light::{ - Fetcher, RemoteBodyRequest, RemoteCallRequest, RemoteChangesRequest, RemoteHeaderRequest, - RemoteReadChildRequest, RemoteReadRequest, -}; use sp_core::{ sr25519, storage::{ChildInfo, Storage, StorageChild}, - ChangesTrieConfiguration, + Pair, }; -use sp_runtime::traits::{Block as BlockT, Hash as HashT, HashFor, Header as HeaderT, NumberFor}; +use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT}; use substrate_test_runtime::genesismap::{additional_storage_with_genesis, GenesisConfig}; /// A prelude to import in tests. @@ -51,8 +47,8 @@ pub mod prelude { }; // Client structs pub use super::{ - Backend, ExecutorDispatch, LightBackend, LightExecutor, LocalExecutorDispatch, - NativeElseWasmExecutor, TestClient, TestClientBuilder, WasmExecutionMethod, + Backend, ExecutorDispatch, LocalExecutorDispatch, NativeElseWasmExecutor, TestClient, + TestClientBuilder, WasmExecutionMethod, }; // Keyring pub use super::{AccountKeyring, Sr25519Keyring}; @@ -84,26 +80,9 @@ pub type ExecutorDispatch = client::LocalCallExecutor< NativeElseWasmExecutor, >; -/// Test client light database backend. -pub type LightBackend = substrate_test_client::LightBackend; - -/// Test client light executor. -pub type LightExecutor = sc_light::GenesisCallExecutor< - LightBackend, - client::LocalCallExecutor< - substrate_test_runtime::Block, - sc_light::Backend< - sc_client_db::light::LightStorage, - HashFor, - >, - NativeElseWasmExecutor, - >, ->; - /// Parameters of test-client builder with test-runtime. #[derive(Default)] pub struct GenesisParameters { - changes_trie_config: Option, heap_pages_override: Option, extra_storage: Storage, wasm_code: Option>, @@ -112,17 +91,20 @@ pub struct GenesisParameters { impl GenesisParameters { fn genesis_config(&self) -> GenesisConfig { GenesisConfig::new( - self.changes_trie_config.clone(), vec![ sr25519::Public::from(Sr25519Keyring::Alice).into(), sr25519::Public::from(Sr25519Keyring::Bob).into(), sr25519::Public::from(Sr25519Keyring::Charlie).into(), ], - vec![ - AccountKeyring::Alice.into(), - AccountKeyring::Bob.into(), - AccountKeyring::Charlie.into(), - ], + (0..16_usize) + .into_iter() + .map(|i| AccountKeyring::numeric(i).public()) + .chain(vec![ + AccountKeyring::Alice.into(), + AccountKeyring::Bob.into(), + AccountKeyring::Charlie.into(), + ]) + .collect(), 1000, self.heap_pages_override, self.extra_storage.clone(), @@ -133,6 +115,11 @@ impl GenesisParameters { pub fn set_wasm_code(&mut self, code: Vec) { self.wasm_code = Some(code); } + + /// Access extra genesis storage. + pub fn extra_storage(&mut self) -> &mut Storage { + &mut self.extra_storage + } } impl substrate_test_client::GenesisInit for GenesisParameters { @@ -147,10 +134,11 @@ impl substrate_test_client::GenesisInit for GenesisParameters { .insert(sp_core::storage::well_known_keys::CODE.to_vec(), code.clone()); } - let child_roots = storage.children_default.iter().map(|(_sk, child_content)| { + let child_roots = storage.children_default.values().map(|child_content| { let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( child_content.data.clone().into_iter().collect(), + sp_runtime::StateVersion::V1, ); let prefixed_storage_key = child_content.child_info.prefixed_storage_key(); (prefixed_storage_key.into_inner(), state_root.encode()) @@ -158,6 +146,7 @@ impl substrate_test_client::GenesisInit for GenesisParameters { let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( storage.top.clone().into_iter().chain(child_roots).collect(), + sp_runtime::StateVersion::V1, ); let block: runtime::Block = client::genesis::construct_genesis_block(state_root); storage.top.extend(additional_storage_with_genesis(&block)); @@ -206,12 +195,6 @@ pub trait TestClientBuilderExt: Sized { /// Returns a mutable reference to the genesis parameters. fn genesis_init_mut(&mut self) -> &mut GenesisParameters; - /// Set changes trie configuration for genesis. - fn changes_trie_config(mut self, config: Option) -> Self { - self.genesis_init_mut().changes_trie_config = config; - self - } - /// Override the default value for Wasm heap pages. fn set_heap_pages(mut self, heap_pages: u64) -> Self { self.genesis_init_mut().heap_pages_override = Some(heap_pages); @@ -299,143 +282,17 @@ impl TestClientBuilderExt } } -/// Type of optional fetch callback. -type MaybeFetcherCallback = - Option Result + Send + Sync>>; - -/// Type of fetcher future result. -type FetcherFutureResult = futures::future::Ready>; - -/// Implementation of light client fetcher used in tests. -#[derive(Default)] -pub struct LightFetcher { - call: MaybeFetcherCallback, Vec>, - body: MaybeFetcherCallback< - RemoteBodyRequest, - Vec, - >, -} - -impl LightFetcher { - /// Sets remote call callback. - pub fn with_remote_call( - self, - call: MaybeFetcherCallback, Vec>, - ) -> Self { - LightFetcher { call, body: self.body } - } - - /// Sets remote body callback. - pub fn with_remote_body( - self, - body: MaybeFetcherCallback< - RemoteBodyRequest, - Vec, - >, - ) -> Self { - LightFetcher { call: self.call, body } - } -} - -impl Fetcher for LightFetcher { - type RemoteHeaderResult = FetcherFutureResult; - type RemoteReadResult = FetcherFutureResult, Option>>>; - type RemoteCallResult = FetcherFutureResult>; - type RemoteChangesResult = - FetcherFutureResult, u32)>>; - type RemoteBodyResult = FetcherFutureResult>; - - fn remote_header( - &self, - _: RemoteHeaderRequest, - ) -> Self::RemoteHeaderResult { - unimplemented!() - } - - fn remote_read( - &self, - _: RemoteReadRequest, - ) -> Self::RemoteReadResult { - unimplemented!() - } - - fn remote_read_child( - &self, - _: RemoteReadChildRequest, - ) -> Self::RemoteReadResult { - unimplemented!() - } - - fn remote_call( - &self, - req: RemoteCallRequest, - ) -> Self::RemoteCallResult { - match self.call { - Some(ref call) => futures::future::ready(call(req)), - None => unimplemented!(), - } - } - - fn remote_changes( - &self, - _: RemoteChangesRequest, - ) -> Self::RemoteChangesResult { - unimplemented!() - } - - fn remote_body( - &self, - req: RemoteBodyRequest, - ) -> Self::RemoteBodyResult { - match self.body { - Some(ref body) => futures::future::ready(body(req)), - None => unimplemented!(), - } - } -} - /// Creates new client instance used for tests. pub fn new() -> Client { TestClientBuilder::new().build() } -/// Creates new light client instance used for tests. -pub fn new_light() -> ( - client::Client< - LightBackend, - LightExecutor, - substrate_test_runtime::Block, - substrate_test_runtime::RuntimeApi, - >, - Arc, -) { - let storage = sc_client_db::light::LightStorage::new_test(); - let blockchain = Arc::new(sc_light::Blockchain::new(storage)); - let backend = Arc::new(LightBackend::new(blockchain)); - let executor = new_native_executor(); - let local_call_executor = client::LocalCallExecutor::new( - backend.clone(), - executor, - Box::new(sp_core::testing::TaskExecutor::new()), - Default::default(), - ) - .expect("Creates LocalCallExecutor"); - let call_executor = LightExecutor::new(backend.clone(), local_call_executor); - - ( - TestClientBuilder::with_backend(backend.clone()) - .build_with_executor(call_executor) - .0, - backend, - ) -} - -/// Creates new light client fetcher used for tests. -pub fn new_light_fetcher() -> LightFetcher { - LightFetcher::default() -} - /// Create a new native executor. pub fn new_native_executor() -> sc_executor::NativeElseWasmExecutor { - sc_executor::NativeElseWasmExecutor::new(sc_executor::WasmExecutionMethod::Interpreted, None, 8) + sc_executor::NativeElseWasmExecutor::new( + sc_executor::WasmExecutionMethod::Interpreted, + None, + 8, + 2, + ) } diff --git a/test-utils/runtime/client/src/trait_tests.rs b/test-utils/runtime/client/src/trait_tests.rs index c5e0ba49fcf5b..65aa3e65e6141 100644 --- a/test-utils/runtime/client/src/trait_tests.rs +++ b/test-utils/runtime/client/src/trait_tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -67,7 +67,6 @@ where .block; block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); - #[allow(deprecated)] assert_eq!(blockchain.leaves().unwrap(), vec![a2.hash()]); // A2 -> A3 diff --git a/test-utils/runtime/src/genesismap.rs b/test-utils/runtime/src/genesismap.rs index a8801b8519dfe..42706730eb9ac 100644 --- a/test-utils/runtime/src/genesismap.rs +++ b/test-utils/runtime/src/genesismap.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,13 @@ //! Tool for creating the genesis block. -use super::{system, wasm_binary_unwrap, AccountId, AuthorityId}; +use super::{system, wasm_binary_unwrap, AccountId, AuthorityId, Runtime}; use codec::{Encode, Joiner, KeyedVec}; +use frame_support::traits::GenesisBuild; use sc_service::client::genesis; use sp_core::{ map, storage::{well_known_keys, Storage}, - ChangesTrieConfiguration, }; use sp_io::hashing::{blake2_256, twox_128}; use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT}; @@ -31,7 +31,6 @@ use std::collections::BTreeMap; /// Configuration of a general Substrate test genesis block. pub struct GenesisConfig { - changes_trie_config: Option, authorities: Vec, balances: Vec<(AccountId, u64)>, heap_pages_override: Option, @@ -41,7 +40,6 @@ pub struct GenesisConfig { impl GenesisConfig { pub fn new( - changes_trie_config: Option, authorities: Vec, endowed_accounts: Vec, balance: u64, @@ -49,7 +47,6 @@ impl GenesisConfig { extra_storage: Storage, ) -> Self { GenesisConfig { - changes_trie_config, authorities, balances: endowed_accounts.into_iter().map(|a| (a, balance)).collect(), heap_pages_override, @@ -71,15 +68,12 @@ impl GenesisConfig { (well_known_keys::CODE.into(), wasm_runtime), ( well_known_keys::HEAP_PAGES.into(), - vec![].and(&(self.heap_pages_override.unwrap_or(16 as u64))), + vec![].and(&(self.heap_pages_override.unwrap_or(16_u64))), ), ] .into_iter(), ) .collect(); - if let Some(ref changes_trie_config) = self.changes_trie_config { - map.insert(well_known_keys::CHANGES_TRIE_CONFIG.to_vec(), changes_trie_config.encode()); - } map.insert(twox_128(&b"sys:auth"[..])[..].to_vec(), self.authorities.encode()); // Add the extra storage entries. map.extend(self.extra_storage.top.clone().into_iter()); @@ -87,11 +81,11 @@ impl GenesisConfig { // Assimilate the system genesis config. let mut storage = Storage { top: map, children_default: self.extra_storage.children_default.clone() }; - let mut config = system::GenesisConfig::default(); - config.authorities = self.authorities.clone(); - config - .assimilate_storage(&mut storage) - .expect("Adding `system::GensisConfig` to the genesis"); + >::assimilate_storage( + &system::GenesisConfig { authorities: self.authorities.clone() }, + &mut storage, + ) + .expect("Adding `system::GensisConfig` to the genesis"); storage } @@ -102,6 +96,7 @@ pub fn insert_genesis_block(storage: &mut Storage) -> sp_core::hash::H256 { let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( child_content.data.clone().into_iter().collect(), + sp_runtime::StateVersion::V1, ); (sk.clone(), state_root.encode()) }); @@ -109,6 +104,7 @@ pub fn insert_genesis_block(storage: &mut Storage) -> sp_core::hash::H256 { storage.top.extend(child_roots); let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( storage.top.clone().into_iter().collect(), + sp_runtime::StateVersion::V1, ); let block: crate::Block = genesis::construct_genesis_block(state_root); let genesis_hash = block.header.hash(); diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 0d880d508ef38..8b64528e243bd 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,20 +23,25 @@ pub mod genesismap; pub mod system; -use codec::{Decode, Encode, Error, Input}; +use codec::{Decode, Encode, Error, Input, MaxEncodedLen}; use scale_info::TypeInfo; use sp_std::{marker::PhantomData, prelude::*}; use sp_application_crypto::{ecdsa, ed25519, sr25519, RuntimeAppPublic}; -use sp_core::{offchain::KeyTypeId, ChangesTrieConfiguration, OpaqueMetadata, RuntimeDebug}; +use sp_core::{offchain::KeyTypeId, OpaqueMetadata, RuntimeDebug}; use sp_trie::{ - trie_types::{TrieDB, TrieDBMut}, + trie_types::{TrieDBBuilder, TrieDBMutBuilderV1}, PrefixedMemoryDB, StorageProof, }; use trie_db::{Trie, TrieMut}; use cfg_if::cfg_if; -use frame_support::{parameter_types, traits::KeyOwnerProofSystem, weights::RuntimeDbWeight}; +use frame_support::{ + dispatch::RawOrigin, + parameter_types, + traits::{CallerTrait, ConstU32, ConstU64, CrateVersion, KeyOwnerProofSystem}, + weights::{RuntimeDbWeight, Weight}, +}; use frame_system::limits::{BlockLength, BlockWeights}; use sp_api::{decl_runtime_apis, impl_runtime_apis}; pub use sp_core::hash::H256; @@ -101,6 +106,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 2, apis: RUNTIME_API_VERSIONS, transaction_version: 1, + state_version: 1, }; fn version() -> RuntimeVersion { @@ -114,7 +120,7 @@ pub fn native_version() -> NativeVersion { } /// Calls in transactions. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct Transfer { pub from: AccountId, pub to: AccountId, @@ -128,8 +134,7 @@ impl Transfer { pub fn into_signed_tx(self) -> Extrinsic { let signature = sp_keyring::AccountKeyring::from_public(&self.from) .expect("Creates keyring from public key.") - .sign(&self.encode()) - .into(); + .sign(&self.encode()); Extrinsic::Transfer { transfer: self, signature, exhaust_resources_when_not_first: false } } @@ -140,14 +145,13 @@ impl Transfer { pub fn into_resources_exhausting_tx(self) -> Extrinsic { let signature = sp_keyring::AccountKeyring::from_public(&self.from) .expect("Creates keyring from public key.") - .sign(&self.encode()) - .into(); + .sign(&self.encode()); Extrinsic::Transfer { transfer: self, signature, exhaust_resources_when_not_first: true } } } /// Extrinsic for test-runtime. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum Extrinsic { AuthoritiesChange(Vec), Transfer { @@ -157,14 +161,11 @@ pub enum Extrinsic { }, IncludeData(Vec), StorageChange(Vec, Option>), - ChangesTrieConfigUpdate(Option), OffchainIndexSet(Vec, Vec), OffchainIndexClear(Vec), Store(Vec), } -parity_util_mem::malloc_size_of_is_0!(Extrinsic); // non-opaque extrinsic does not need this - #[cfg(feature = "std")] impl serde::Serialize for Extrinsic { fn serialize(&self, seq: S) -> Result @@ -175,6 +176,19 @@ impl serde::Serialize for Extrinsic { } } +// rustc can't deduce this trait bound https://github.com/rust-lang/rust/issues/48214 +#[cfg(feature = "std")] +impl<'a> serde::Deserialize<'a> for Extrinsic { + fn deserialize(de: D) -> Result + where + D: serde::Deserializer<'a>, + { + let r = sp_core::bytes::deserialize(de)?; + Decode::decode(&mut &r[..]) + .map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e))) + } +} + impl BlindCheckable for Extrinsic { type Checked = Self; @@ -193,8 +207,6 @@ impl BlindCheckable for Extrinsic { }, Extrinsic::IncludeData(v) => Ok(Extrinsic::IncludeData(v)), Extrinsic::StorageChange(key, value) => Ok(Extrinsic::StorageChange(key, value)), - Extrinsic::ChangesTrieConfigUpdate(new_config) => - Ok(Extrinsic::ChangesTrieConfigUpdate(new_config)), Extrinsic::OffchainIndexSet(key, value) => Ok(Extrinsic::OffchainIndexSet(key, value)), Extrinsic::OffchainIndexClear(key) => Ok(Extrinsic::OffchainIndexClear(key)), Extrinsic::Store(data) => Ok(Extrinsic::Store(data)), @@ -220,12 +232,15 @@ impl ExtrinsicT for Extrinsic { } impl sp_runtime::traits::Dispatchable for Extrinsic { - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Config = (); type Info = (); type PostInfo = (); - fn dispatch(self, _origin: Self::Origin) -> sp_runtime::DispatchResultWithInfo { - panic!("This implemention should not be used for actual dispatch."); + fn dispatch( + self, + _origin: Self::RuntimeOrigin, + ) -> sp_runtime::DispatchResultWithInfo { + panic!("This implementation should not be used for actual dispatch."); } } @@ -261,9 +276,9 @@ pub type BlockNumber = u64; /// Index of a transaction. pub type Index = u64; /// The item of a block digest. -pub type DigestItem = sp_runtime::generic::DigestItem; +pub type DigestItem = sp_runtime::generic::DigestItem; /// The digest of a block. -pub type Digest = sp_runtime::generic::Digest; +pub type Digest = sp_runtime::generic::Digest; /// A test block. pub type Block = sp_runtime::generic::Block; /// A test block's header. @@ -276,9 +291,9 @@ pub fn run_tests(mut input: &[u8]) -> Vec { print("run_tests..."); let block = Block::decode(&mut input).unwrap(); print("deserialized block."); - let stxs = block.extrinsics.iter().map(Encode::encode).collect::>(); + let stxs = block.extrinsics.iter().map(Encode::encode); print("reserialized transactions."); - [stxs.len() as u8].encode() + [stxs.count() as u8].encode() } /// A type that can not be decoded. @@ -295,9 +310,9 @@ impl Encode for DecodeFails { impl codec::EncodeLike for DecodeFails {} -impl DecodeFails { - /// Create a new instance. - pub fn new() -> DecodeFails { +impl Default for DecodeFails { + /// Create a default instance. + fn default() -> DecodeFails { DecodeFails { _phantom: Default::default() } } } @@ -361,6 +376,8 @@ cfg_if! { fn test_multiple_arguments(data: Vec, other: Vec, num: u32); /// Traces log "Hey I'm runtime." fn do_trace_log(); + /// Verify the given signature, public & message bundle. + fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec) -> bool; } } } else { @@ -411,6 +428,8 @@ cfg_if! { fn test_multiple_arguments(data: Vec, other: Vec, num: u32); /// Traces log "Hey I'm runtime." fn do_trace_log(); + /// Verify the given signature, public & message bundle. + fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec) -> bool; } } } @@ -427,23 +446,34 @@ impl GetRuntimeBlockType for Runtime { type RuntimeBlock = Block; } -#[derive(Clone, RuntimeDebug)] -pub struct Origin; +#[derive(Clone, RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct RuntimeOrigin; + +impl From::AccountId>> for RuntimeOrigin { + fn from(_: RawOrigin<::AccountId>) -> Self { + unimplemented!("Not required in tests!") + } +} + +impl CallerTrait<::AccountId> for RuntimeOrigin { + fn into_system(self) -> Option::AccountId>> { + unimplemented!("Not required in tests!") + } -impl From> for Origin { - fn from(_o: frame_system::Origin) -> Self { + fn as_system_ref(&self) -> Option<&RawOrigin<::AccountId>> { unimplemented!("Not required in tests!") } } -impl Into, Origin>> for Origin { - fn into(self) -> Result, Origin> { + +impl From for Result, RuntimeOrigin> { + fn from(_origin: RuntimeOrigin) -> Result, RuntimeOrigin> { unimplemented!("Not required in tests!") } } -impl frame_support::traits::OriginTrait for Origin { - type Call = ::Call; - type PalletsOrigin = Origin; +impl frame_support::traits::OriginTrait for RuntimeOrigin { + type Call = ::RuntimeCall; + type PalletsOrigin = RuntimeOrigin; type AccountId = ::AccountId; fn add_filter(&mut self, _filter: impl Fn(&Self::Call) -> bool + 'static) { @@ -466,6 +496,10 @@ impl frame_support::traits::OriginTrait for Origin { unimplemented!("Not required in tests!") } + fn into_caller(self) -> Self::PalletsOrigin { + unimplemented!("Not required in tests!") + } + fn try_with_caller( self, _f: impl FnOnce(Self::PalletsOrigin) -> Result, @@ -479,15 +513,21 @@ impl frame_support::traits::OriginTrait for Origin { fn root() -> Self { unimplemented!("Not required in tests!") } - fn signed(_by: ::AccountId) -> Self { + fn signed(_by: Self::AccountId) -> Self { + unimplemented!("Not required in tests!") + } + fn as_signed(self) -> Option { + unimplemented!("Not required in tests!") + } + fn as_system_ref(&self) -> Option<&RawOrigin> { unimplemented!("Not required in tests!") } } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct Event; +pub struct RuntimeEvent; -impl From> for Event { +impl From> for RuntimeEvent { fn from(_evt: frame_system::Event) -> Self { unimplemented!("Not required in tests!") } @@ -520,13 +560,40 @@ impl frame_support::traits::PalletInfo for Runtime { return Some("Babe") } + None + } + fn module_name() -> Option<&'static str> { + let type_id = sp_std::any::TypeId::of::

(); + if type_id == sp_std::any::TypeId::of::>() { + return Some("system") + } + if type_id == sp_std::any::TypeId::of::>() { + return Some("pallet_timestamp") + } + if type_id == sp_std::any::TypeId::of::>() { + return Some("pallet_babe") + } + + None + } + fn crate_version() -> Option { + use frame_support::traits::PalletInfoAccess as _; + let type_id = sp_std::any::TypeId::of::

(); + if type_id == sp_std::any::TypeId::of::>() { + return Some(system::Pallet::::crate_version()) + } + if type_id == sp_std::any::TypeId::of::>() { + return Some(pallet_timestamp::Pallet::::crate_version()) + } + if type_id == sp_std::any::TypeId::of::>() { + return Some(pallet_babe::Pallet::::crate_version()) + } + None } } parameter_types! { - pub const BlockHashCount: BlockNumber = 2400; - pub const MinimumPeriod: u64 = 5; pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 100, write: 1000, @@ -534,15 +601,21 @@ parameter_types! { pub RuntimeBlockLength: BlockLength = BlockLength::max(4 * 1024 * 1024); pub RuntimeBlockWeights: BlockWeights = - BlockWeights::with_sensible_defaults(4 * 1024 * 1024, Perbill::from_percent(75)); + BlockWeights::with_sensible_defaults(Weight::from_ref_time(4 * 1024 * 1024), Perbill::from_percent(75)); } -impl frame_system::Config for Runtime { +impl From> for Extrinsic { + fn from(_: frame_system::Call) -> Self { + unimplemented!("Not required in tests!") + } +} + +impl frame_system::pallet::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; - type Origin = Origin; - type Call = Extrinsic; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = Extrinsic; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -550,8 +623,8 @@ impl frame_system::Config for Runtime { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<2400>; type DbWeight = (); type Version = (); type PalletInfo = Self; @@ -561,24 +634,26 @@ impl frame_system::Config for Runtime { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } +impl system::Config for Runtime {} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<5>; type WeightInfo = (); } parameter_types! { pub const EpochDuration: u64 = 6; - pub const ExpectedBlockTime: u64 = 10_000; } impl pallet_babe::Config for Runtime { type EpochDuration = EpochDuration; - type ExpectedBlockTime = ExpectedBlockTime; + type ExpectedBlockTime = ConstU64<10_000>; // there is no actual runtime in this test-runtime, so testing crates // are manually adding the digests. normally in this situation you'd use // pallet_babe::SameAuthoritiesForever. @@ -596,8 +671,9 @@ impl pallet_babe::Config for Runtime { )>>::IdentificationTuple; type HandleEquivocation = (); - type WeightInfo = (); + + type MaxAuthorities = ConstU32<10>; } /// Adds one to the given input and returns the final result. @@ -620,34 +696,19 @@ fn code_using_trie() -> u64 { let mut mdb = PrefixedMemoryDB::default(); let mut root = sp_std::default::Default::default(); - let _ = { - let v = &pairs; - let mut t = TrieDBMut::::new(&mut mdb, &mut root); - for i in 0..v.len() { - let key: &[u8] = &v[i].0; - let val: &[u8] = &v[i].1; - if !t.insert(key, val).is_ok() { + { + let mut t = TrieDBMutBuilderV1::::new(&mut mdb, &mut root).build(); + for (key, value) in &pairs { + if t.insert(key, value).is_err() { return 101 } } - t - }; - - if let Ok(trie) = TrieDB::::new(&mdb, &root) { - if let Ok(iter) = trie.iter() { - let mut iter_pairs = Vec::new(); - for pair in iter { - if let Ok((key, value)) = pair { - iter_pairs.push((key, value.to_vec())); - } - } - iter_pairs.len() as u64 - } else { - 102 - } - } else { - 103 } + + let trie = TrieDBBuilder::::new(&mdb, &root).build(); + let res = if let Ok(iter) = trie.iter() { iter.flatten().count() as u64 } else { 102 }; + + res } impl_opaque_keys! { @@ -737,7 +798,7 @@ cfg_if! { fn fail_convert_parameter(_: DecodeFails) {} fn fail_convert_return_value() -> DecodeFails { - DecodeFails::new() + DecodeFails::default() } fn function_signature_changed() -> u64 { @@ -804,6 +865,10 @@ cfg_if! { fn do_trace_log() { log::trace!("Hey I'm runtime"); } + + fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec) -> bool { + sp_io::crypto::ed25519_verify(&sig, &message, &public) + } } impl sp_consensus_aura::AuraApi for Runtime { @@ -820,12 +885,12 @@ cfg_if! { } impl sp_consensus_babe::BabeApi for Runtime { - fn configuration() -> sp_consensus_babe::BabeGenesisConfiguration { - sp_consensus_babe::BabeGenesisConfiguration { + fn configuration() -> sp_consensus_babe::BabeConfiguration { + sp_consensus_babe::BabeConfiguration { slot_duration: 1000, epoch_length: EpochDuration::get(), c: (3, 10), - genesis_authorities: system::authorities() + authorities: system::authorities() .into_iter().map(|x|(x, 1)).collect(), randomness: >::randomness(), allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots, @@ -907,6 +972,22 @@ cfg_if! { } } + impl beefy_primitives::BeefyApi for Runtime { + fn validator_set() -> Option> { + None + } + } + + impl beefy_merkle_tree::BeefyMmrApi for Runtime { + fn authority_set_proof() -> beefy_primitives::mmr::BeefyAuthoritySet { + Default::default() + } + + fn next_authority_set_proof() -> beefy_primitives::mmr::BeefyNextAuthoritySet { + Default::default() + } + } + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(_account: AccountId) -> Index { 0 @@ -991,7 +1072,7 @@ cfg_if! { fn fail_convert_parameter(_: DecodeFails) {} fn fail_convert_return_value() -> DecodeFails { - DecodeFails::new() + DecodeFails::default() } fn function_signature_changed() -> Vec { @@ -1062,6 +1143,10 @@ cfg_if! { fn do_trace_log() { log::trace!("Hey I'm runtime: {}", log::STATIC_MAX_LEVEL); } + + fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec) -> bool { + sp_io::crypto::ed25519_verify(&sig, &message, &public) + } } impl sp_consensus_aura::AuraApi for Runtime { @@ -1078,12 +1163,12 @@ cfg_if! { } impl sp_consensus_babe::BabeApi for Runtime { - fn configuration() -> sp_consensus_babe::BabeGenesisConfiguration { - sp_consensus_babe::BabeGenesisConfiguration { + fn configuration() -> sp_consensus_babe::BabeConfiguration { + sp_consensus_babe::BabeConfiguration { slot_duration: 1000, epoch_length: EpochDuration::get(), c: (3, 10), - genesis_authorities: system::authorities() + authorities: system::authorities() .into_iter().map(|x|(x, 1)).collect(), randomness: >::randomness(), allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots, @@ -1227,22 +1312,20 @@ fn test_read_child_storage() { fn test_witness(proof: StorageProof, root: crate::Hash) { use sp_externalities::Externalities; let db: sp_trie::MemoryDB = proof.into_memory_db(); - let backend = sp_state_machine::TrieBackend::<_, crate::Hashing>::new(db, root); + let backend = sp_state_machine::TrieBackendBuilder::<_, crate::Hashing>::new(db, root).build(); let mut overlay = sp_state_machine::OverlayedChanges::default(); - let mut cache = sp_state_machine::StorageTransactionCache::<_, _, BlockNumber>::default(); + let mut cache = sp_state_machine::StorageTransactionCache::<_, _>::default(); let mut ext = sp_state_machine::Ext::new( &mut overlay, &mut cache, &backend, #[cfg(feature = "std")] None, - #[cfg(feature = "std")] - None, ); assert!(ext.storage(b"value3").is_some()); - assert!(ext.storage_root().as_slice() == &root[..]); + assert!(ext.storage_root(Default::default()).as_slice() == &root[..]); ext.place_storage(vec![0], Some(vec![1])); - assert!(ext.storage_root().as_slice() != &root[..]); + assert!(ext.storage_root(Default::default()).as_slice() != &root[..]); } #[cfg(test)] @@ -1267,7 +1350,7 @@ mod tests { .set_execution_strategy(ExecutionStrategy::AlwaysWasm) .set_heap_pages(8) .build(); - let block_id = BlockId::Number(client.chain_info().best_number); + let block_id = BlockId::Hash(client.chain_info().best_hash); // Try to allocate 1024k of memory on heap. This is going to fail since it is twice larger // than the heap. @@ -1296,7 +1379,7 @@ mod tests { let client = TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::Both).build(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); + let block_id = BlockId::Hash(client.chain_info().best_hash); runtime_api.test_storage(&block_id).unwrap(); } @@ -1306,7 +1389,8 @@ mod tests { let mut root = crate::Hash::default(); let mut mdb = sp_trie::MemoryDB::::default(); { - let mut trie = sp_trie::trie_types::TrieDBMut::new(&mut mdb, &mut root); + let mut trie = + sp_trie::trie_types::TrieDBMutBuilderV1::new(&mut mdb, &mut root).build(); trie.insert(b"value3", &[142]).expect("insert failed"); trie.insert(b"value4", &[124]).expect("insert failed"); }; @@ -1316,12 +1400,13 @@ mod tests { #[test] fn witness_backend_works() { let (db, root) = witness_backend(); - let backend = sp_state_machine::TrieBackend::<_, crate::Hashing>::new(db, root); + let backend = + sp_state_machine::TrieBackendBuilder::<_, crate::Hashing>::new(db, root).build(); let proof = sp_state_machine::prove_read(backend, vec![b"value3"]).unwrap(); let client = TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::Both).build(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); + let block_id = BlockId::Hash(client.chain_info().best_hash); runtime_api.test_witness(&block_id, proof, root).unwrap(); } diff --git a/test-utils/runtime/src/system.rs b/test-utils/runtime/src/system.rs index 334569d055a0c..6e33d5c25fe6f 100644 --- a/test-utils/runtime/src/system.rs +++ b/test-utils/runtime/src/system.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,17 +19,13 @@ //! and depositing logs. use crate::{ - AccountId, AuthorityId, Block, BlockNumber, Digest, Extrinsic, Header, Transfer, H256 as Hash, + AccountId, AuthorityId, Block, BlockNumber, Digest, Extrinsic, Header, Runtime, Transfer, + H256 as Hash, }; use codec::{Decode, Encode, KeyedVec}; -use frame_support::{decl_module, decl_storage, storage}; -use frame_system::Config; -use sp_core::{storage::well_known_keys, ChangesTrieConfiguration}; -use sp_io::{ - hashing::blake2_256, - storage::{changes_root as storage_changes_root, root as storage_root}, - trie, -}; +use frame_support::storage; +use sp_core::storage::well_known_keys; +use sp_io::{hashing::blake2_256, storage::root as storage_root, trie}; use sp_runtime::{ generic, traits::Header as _, @@ -43,20 +39,51 @@ use sp_std::prelude::*; const NONCE_OF: &[u8] = b"nonce:"; const BALANCE_OF: &[u8] = b"balance:"; -decl_module! { - pub struct Module for enum Call where origin: T::Origin {} -} +pub use self::pallet::*; + +#[frame_support::pallet] +mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::storage] + pub type ExtrinsicData = StorageMap<_, Blake2_128Concat, u32, Vec, ValueQuery>; + + // The current block number being processed. Set by `execute_block`. + #[pallet::storage] + pub type Number = StorageValue<_, BlockNumber, OptionQuery>; + + #[pallet::storage] + pub type ParentHash = StorageValue<_, Hash, ValueQuery>; + + #[pallet::storage] + pub type NewAuthorities = StorageValue<_, Vec, OptionQuery>; + + #[pallet::storage] + pub type StorageDigest = StorageValue<_, Digest, OptionQuery>; + + #[pallet::storage] + pub type Authorities = StorageValue<_, Vec, ValueQuery>; -decl_storage! { - trait Store for Module as TestRuntime { - ExtrinsicData: map hasher(blake2_128_concat) u32 => Vec; - // The current block number being processed. Set by `execute_block`. - Number get(fn number): Option; - ParentHash get(fn parent_hash): Hash; - NewAuthorities get(fn new_authorities): Option>; - NewChangesTrieConfig get(fn new_changes_trie_config): Option>; - StorageDigest get(fn storage_digest): Option; - Authorities get(fn authorities) config(): Vec; + #[pallet::genesis_config] + #[cfg_attr(feature = "std", derive(Default))] + pub struct GenesisConfig { + pub authorities: Vec, + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + >::put(self.authorities.clone()); + } } } @@ -74,28 +101,28 @@ pub fn nonce_of(who: AccountId) -> u64 { pub fn initialize_block(header: &Header) { // populate environment. - ::put(&header.number); - ::put(&header.parent_hash); - ::put(header.digest()); + >::put(&header.number); + >::put(&header.parent_hash); + >::put(header.digest()); storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &0u32); // try to read something that depends on current header digest // so that it'll be included in execution proof if let Some(generic::DigestItem::Other(v)) = header.digest().logs().iter().next() { - let _: Option = storage::unhashed::get(&v); + let _: Option = storage::unhashed::get(v); } } pub fn authorities() -> Vec { - Authorities::get() + >::get() } pub fn get_block_number() -> Option { - Number::get() + >::get() } pub fn take_block_number() -> Option { - Number::take() + >::take() } #[derive(Copy, Clone)] @@ -129,8 +156,8 @@ fn execute_block_with_state_root_handler(block: &mut Block, mode: Mode) -> Heade header.state_root = new_header.state_root; } else { info_expect_equal_hash(&new_header.state_root, &header.state_root); - assert!( - new_header.state_root == header.state_root, + assert_eq!( + new_header.state_root, header.state_root, "Storage root must match that calculated.", ); } @@ -139,8 +166,8 @@ fn execute_block_with_state_root_handler(block: &mut Block, mode: Mode) -> Heade header.extrinsics_root = new_header.extrinsics_root; } else { info_expect_equal_hash(&new_header.extrinsics_root, &header.extrinsics_root); - assert!( - new_header.extrinsics_root == header.extrinsics_root, + assert_eq!( + new_header.extrinsics_root, header.extrinsics_root, "Transaction trie root must be valid.", ); } @@ -192,45 +219,34 @@ pub fn execute_transaction(utx: Extrinsic) -> ApplyExtrinsicResult { let extrinsic_index: u32 = storage::unhashed::get(well_known_keys::EXTRINSIC_INDEX).unwrap_or_default(); let result = execute_transaction_backend(&utx, extrinsic_index); - ExtrinsicData::insert(extrinsic_index, utx.encode()); + >::insert(extrinsic_index, utx.encode()); storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &(extrinsic_index + 1)); result } /// Finalize the block. pub fn finalize_block() -> Header { + use sp_core::storage::StateVersion; let extrinsic_index: u32 = storage::unhashed::take(well_known_keys::EXTRINSIC_INDEX).unwrap(); - let txs: Vec<_> = (0..extrinsic_index).map(ExtrinsicData::take).collect(); - let extrinsics_root = trie::blake2_256_ordered_root(txs).into(); - let number = ::take().expect("Number is set by `initialize_block`"); - let parent_hash = ::take(); - let mut digest = ::take().expect("StorageDigest is set by `initialize_block`"); + let txs: Vec<_> = (0..extrinsic_index).map(>::take).collect(); + let extrinsics_root = trie::blake2_256_ordered_root(txs, StateVersion::V0); + let number = >::take().expect("Number is set by `initialize_block`"); + let parent_hash = >::take(); + let mut digest = + >::take().expect("StorageDigest is set by `initialize_block`"); - let o_new_authorities = ::take(); - let new_changes_trie_config = ::take(); + let o_new_authorities = >::take(); // This MUST come after all changes to storage are done. Otherwise we will fail the // “Storage root does not match that calculated” assertion. - let storage_root = - Hash::decode(&mut &storage_root()[..]).expect("`storage_root` is a valid hash"); - let storage_changes_root = storage_changes_root(&parent_hash.encode()) - .map(|r| Hash::decode(&mut &r[..]).expect("`storage_changes_root` is a valid hash")); - - if let Some(storage_changes_root) = storage_changes_root { - digest.push(generic::DigestItem::ChangesTrieRoot(storage_changes_root)); - } + let storage_root = Hash::decode(&mut &storage_root(StateVersion::V1)[..]) + .expect("`storage_root` is a valid hash"); if let Some(new_authorities) = o_new_authorities { digest.push(generic::DigestItem::Consensus(*b"aura", new_authorities.encode())); digest.push(generic::DigestItem::Consensus(*b"babe", new_authorities.encode())); } - if let Some(new_config) = new_changes_trie_config { - digest.push(generic::DigestItem::ChangesTrieSignal( - generic::ChangesTrieSignal::NewConfiguration(new_config), - )); - } - Header { number, extrinsics_root, state_root: storage_root, parent_hash, digest } } @@ -251,14 +267,12 @@ fn execute_transaction_backend(utx: &Extrinsic, extrinsic_index: u32) -> ApplyEx Extrinsic::IncludeData(_) => Ok(Ok(())), Extrinsic::StorageChange(key, value) => execute_storage_change(key, value.as_ref().map(|v| &**v)), - Extrinsic::ChangesTrieConfigUpdate(ref new_config) => - execute_changes_trie_config_update(new_config.clone()), Extrinsic::OffchainIndexSet(key, value) => { - sp_io::offchain_index::set(&key, &value); + sp_io::offchain_index::set(key, value); Ok(Ok(())) }, Extrinsic::OffchainIndexClear(key) => { - sp_io::offchain_index::clear(&key); + sp_io::offchain_index::clear(key); Ok(Ok(())) }, Extrinsic::Store(data) => execute_store(data.clone()), @@ -269,7 +283,7 @@ fn execute_transfer_backend(tx: &Transfer) -> ApplyExtrinsicResult { // check nonce let nonce_key = tx.from.to_keyed_vec(NONCE_OF); let expected_nonce: u64 = storage::hashed::get_or(&blake2_256, &nonce_key, 0); - if !(tx.nonce == expected_nonce) { + if tx.nonce != expected_nonce { return Err(InvalidTransaction::Stale.into()) } @@ -281,7 +295,7 @@ fn execute_transfer_backend(tx: &Transfer) -> ApplyExtrinsicResult { let from_balance: u64 = storage::hashed::get_or(&blake2_256, &from_balance_key, 0); // enact transfer - if !(tx.amount <= from_balance) { + if tx.amount > from_balance { return Err(InvalidTransaction::Payment.into()) } let to_balance_key = tx.to.to_keyed_vec(BALANCE_OF); @@ -299,7 +313,7 @@ fn execute_store(data: Vec) -> ApplyExtrinsicResult { } fn execute_new_authorities_backend(new_authorities: &[AuthorityId]) -> ApplyExtrinsicResult { - NewAuthorities::put(new_authorities.to_vec()); + >::put(new_authorities.to_vec()); Ok(Ok(())) } @@ -311,18 +325,6 @@ fn execute_storage_change(key: &[u8], value: Option<&[u8]>) -> ApplyExtrinsicRes Ok(Ok(())) } -fn execute_changes_trie_config_update( - new_config: Option, -) -> ApplyExtrinsicResult { - match new_config.clone() { - Some(new_config) => - storage::unhashed::put_raw(well_known_keys::CHANGES_TRIE_CONFIG, &new_config.encode()), - None => storage::unhashed::kill(well_known_keys::CHANGES_TRIE_CONFIG), - } - ::put(new_config); - Ok(Ok(())) -} - #[cfg(feature = "std")] fn info_expect_equal_hash(given: &Hash, expected: &Hash) { use sp_core::hexdisplay::HexDisplay; @@ -353,7 +355,6 @@ mod tests { use sp_core::{ map, traits::{CodeExecutor, RuntimeCode}, - NeverNativeValue, }; use sp_io::{hashing::twox_128, TestExternalities}; use substrate_test_runtime_client::{AccountKeyring, Sr25519Keyring}; @@ -374,7 +375,7 @@ mod tests { } fn executor() -> NativeElseWasmExecutor { - NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8) + NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8, 2) } fn new_test_ext() -> TestExternalities { @@ -383,6 +384,7 @@ mod tests { Sr25519Keyring::Bob.to_raw_public(), Sr25519Keyring::Charlie.to_raw_public(), ]; + TestExternalities::new_with_code( wasm_binary_unwrap(), sp_core::storage::Storage { @@ -391,7 +393,7 @@ mod tests { twox_128(b"sys:auth").to_vec() => authorities.encode(), blake2_256(&AccountKeyring::Alice.to_raw_public().to_keyed_vec(b"balance:")).to_vec() => { vec![111u8, 0, 0, 0, 0, 0, 0, 0] - } + }, ], children_default: map![], }, @@ -436,14 +438,7 @@ mod tests { }; executor() - .call:: _>( - &mut ext, - &runtime_code, - "Core_execute_block", - &b.encode(), - false, - None, - ) + .call(&mut ext, &runtime_code, "Core_execute_block", &b.encode(), false) .0 .unwrap(); }) @@ -545,14 +540,7 @@ mod tests { }; executor() - .call:: _>( - &mut ext, - &runtime_code, - "Core_execute_block", - &b.encode(), - false, - None, - ) + .call(&mut ext, &runtime_code, "Core_execute_block", &b.encode(), false) .0 .unwrap(); }) diff --git a/test-utils/runtime/transaction-pool/Cargo.toml b/test-utils/runtime/transaction-pool/Cargo.toml index 09839ebae6ffe..f5cba2b99be56 100644 --- a/test-utils/runtime/transaction-pool/Cargo.toml +++ b/test-utils/runtime/transaction-pool/Cargo.toml @@ -2,9 +2,9 @@ name = "substrate-test-runtime-transaction-pool" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" publish = false @@ -12,12 +12,12 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -substrate-test-runtime-client = { version = "2.0.0", path = "../client" } -parking_lot = "0.11.1" -codec = { package = "parity-scale-codec", version = "2.0.0" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool", features = ["test-helpers"] } +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" +parking_lot = "0.12.1" +thiserror = "1.0" +sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } -futures = "0.3.16" -derive_more = "0.99.2" +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +substrate-test-runtime-client = { version = "2.0.0", path = "../client" } diff --git a/test-utils/runtime/transaction-pool/src/lib.rs b/test-utils/runtime/transaction-pool/src/lib.rs index d0cd50394c533..f8d551a6fa5bd 100644 --- a/test-utils/runtime/transaction-pool/src/lib.rs +++ b/test-utils/runtime/transaction-pool/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,10 +22,12 @@ use codec::Encode; use futures::future::ready; use parking_lot::RwLock; -use sp_blockchain::CachedHeaderMetadata; +use sp_blockchain::{CachedHeaderMetadata, TreeRoute}; use sp_runtime::{ generic::{self, BlockId}, - traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as _}, + traits::{ + BlakeTwo256, Block as BlockT, Hash as HashT, Header as _, NumberFor, TrailingZeroInput, + }, transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, @@ -38,8 +40,9 @@ use substrate_test_runtime_client::{ }; /// Error type used by [`TestApi`]. -#[derive(Debug, derive_more::From, derive_more::Display)] -pub struct Error(sc_transaction_pool_api::error::Error); +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct Error(#[from] sc_transaction_pool_api::error::Error); impl sc_transaction_pool_api::error::IntoPoolError for Error { fn into_pool_error(self) -> Result { @@ -47,12 +50,6 @@ impl sc_transaction_pool_api::error::IntoPoolError for Error { } } -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.0) - } -} - pub enum IsBestBlock { Yes, No, @@ -177,13 +174,13 @@ impl TestApi { /// Add a block to the internal state. pub fn add_block(&self, block: Block, is_best_block: bool) { let hash = block.header.hash(); - let block_number = block.header.number().clone(); + let block_number = block.header.number(); let mut chain = self.chain.write(); chain.block_by_hash.insert(hash, block.clone()); chain .block_by_number - .entry(block_number) + .entry(*block_number) .or_default() .push((block, is_best_block.into())); } @@ -227,7 +224,7 @@ impl TestApi { } } -impl sc_transaction_pool::test_helpers::ChainApi for TestApi { +impl sc_transaction_pool::ChainApi for TestApi { type Block = Block; type Error = Error; type ValidationFuture = futures::future::Ready>; @@ -237,7 +234,7 @@ impl sc_transaction_pool::test_helpers::ChainApi for TestApi { &self, at: &BlockId, _source: TransactionSource, - uxt: sc_transaction_pool::test_helpers::ExtrinsicFor, + uxt: ::Extrinsic, ) -> Self::ValidationFuture { self.validation_requests.write().push(uxt.clone()); @@ -257,15 +254,13 @@ impl sc_transaction_pool::test_helpers::ChainApi for TestApi { if !found_best { return ready(Ok(Err(TransactionValidityError::Invalid( InvalidTransaction::Custom(1), - ) - .into()))) + )))) } }, Ok(None) => return ready(Ok(Err(TransactionValidityError::Invalid( InvalidTransaction::Custom(2), - ) - .into()))), + )))), Err(e) => return ready(Err(e)), } @@ -281,9 +276,7 @@ impl sc_transaction_pool::test_helpers::ChainApi for TestApi { }; if self.chain.read().invalid_hashes.contains(&self.hash_and_length(&uxt).0) { - return ready(Ok(Err( - TransactionValidityError::Invalid(InvalidTransaction::Custom(0)).into() - ))) + return ready(Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(0))))) } let mut validity = @@ -297,7 +290,7 @@ impl sc_transaction_pool::test_helpers::ChainApi for TestApi { fn block_id_to_number( &self, at: &BlockId, - ) -> Result>, Error> { + ) -> Result>, Error> { Ok(match at { generic::BlockId::Hash(x) => self.chain.read().block_by_hash.get(x).map(|b| *b.header.number()), @@ -308,9 +301,9 @@ impl sc_transaction_pool::test_helpers::ChainApi for TestApi { fn block_id_to_hash( &self, at: &BlockId, - ) -> Result>, Error> { + ) -> Result::Hash>, Error> { Ok(match at { - generic::BlockId::Hash(x) => Some(x.clone()), + generic::BlockId::Hash(x) => Some(*x), generic::BlockId::Number(num) => self.chain.read().block_by_number.get(num).and_then(|blocks| { blocks.iter().find(|b| b.1.is_best()).map(|b| b.0.header().hash()) @@ -318,20 +311,17 @@ impl sc_transaction_pool::test_helpers::ChainApi for TestApi { }) } - fn hash_and_length( - &self, - ex: &sc_transaction_pool::test_helpers::ExtrinsicFor, - ) -> (Hash, usize) { + fn hash_and_length(&self, ex: &::Extrinsic) -> (Hash, usize) { Self::hash_and_length_inner(ex) } - fn block_body(&self, id: &BlockId) -> Self::BodyFuture { - futures::future::ready(Ok(match id { - BlockId::Number(num) => - self.chain.read().block_by_number.get(num).map(|b| b[0].0.extrinsics().to_vec()), - BlockId::Hash(hash) => - self.chain.read().block_by_hash.get(hash).map(|b| b.extrinsics().to_vec()), - })) + fn block_body(&self, hash: ::Hash) -> Self::BodyFuture { + futures::future::ready(Ok(self + .chain + .read() + .block_by_hash + .get(&hash) + .map(|b| b.extrinsics().to_vec()))) } fn block_header( @@ -345,6 +335,14 @@ impl sc_transaction_pool::test_helpers::ChainApi for TestApi { self.chain.read().block_by_hash.get(hash).map(|b| b.header().clone()), }) } + + fn tree_route( + &self, + from: ::Hash, + to: ::Hash, + ) -> Result, Self::Error> { + sp_blockchain::tree_route::(self, from, to).map_err(Into::into) + } } impl sp_blockchain::HeaderMetadata for TestApi { @@ -370,7 +368,8 @@ impl sp_blockchain::HeaderMetadata for TestApi { /// /// Part of the test api. pub fn uxt(who: AccountKeyring, nonce: Index) -> Extrinsic { - let transfer = Transfer { from: who.into(), to: AccountId::default(), nonce, amount: 1 }; - let signature = transfer.using_encoded(|e| who.sign(e)).into(); + let dummy = codec::Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap(); + let transfer = Transfer { from: who.into(), to: dummy, nonce, amount: 1 }; + let signature = transfer.using_encoded(|e| who.sign(e)); Extrinsic::Transfer { transfer, signature, exhaust_resources_when_not_first: false } } diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index b68994926533a..643985940792e 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test-utils/test-crate/Cargo.toml b/test-utils/test-crate/Cargo.toml index fff39c3964ad8..67966dd2b6015 100644 --- a/test-utils/test-crate/Cargo.toml +++ b/test-utils/test-crate/Cargo.toml @@ -2,9 +2,9 @@ name = "substrate-test-utils-test-crate" version = "0.1.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" publish = false @@ -12,6 +12,6 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dev-dependencies] -tokio = { version = "1.10", features = ["macros"] } -test-utils = { version = "4.0.0-dev", path = "..", package = "substrate-test-utils" } +tokio = { version = "1.22.0", features = ["macros"] } sc-service = { version = "0.10.0-dev", path = "../../client/service" } +test-utils = { package = "substrate-test-utils", version = "4.0.0-dev", path = ".." } diff --git a/test-utils/test-crate/src/main.rs b/test-utils/test-crate/src/main.rs index 554adcb884064..4696e71779c12 100644 --- a/test-utils/test-crate/src/main.rs +++ b/test-utils/test-crate/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/test-utils/test-runner/Cargo.toml b/test-utils/test-runner/Cargo.toml deleted file mode 100644 index b5b115771b539..0000000000000 --- a/test-utils/test-runner/Cargo.toml +++ /dev/null @@ -1,54 +0,0 @@ -[package] -name = "test-runner" -version = "0.9.0" -authors = ["Parity Technologies "] -edition = "2018" -publish = false - -[dependencies] -# client deps -sc-executor = { path = "../../client/executor" } -sc-service = { path = "../../client/service" } -sc-informant = { path = "../../client/informant" } -sc-network = { path = "../../client/network" } -sc-cli = { path = "../../client/cli" } -sc-basic-authorship = { path = "../../client/basic-authorship" } -sc-rpc = { path = "../../client/rpc" } -sc-transaction-pool = { path = "../../client/transaction-pool" } -grandpa = { package = "sc-finality-grandpa", path = "../../client/finality-grandpa" } -sp-finality-grandpa = { path = "../../primitives/finality-grandpa" } -sp-consensus-babe = { path = "../../primitives/consensus/babe" } -sc-consensus-babe = { path = "../../client/consensus/babe" } -sc-consensus = { path = "../../client/consensus/common" } -sc-transaction-pool-api = { path = "../../client/transaction-pool/api" } -sc-client-api = { path = "../../client/api" } -sc-rpc-server = { path = "../../client/rpc-servers" } -manual-seal = { package = "sc-consensus-manual-seal", path = "../../client/consensus/manual-seal" } - -# primitive deps -sp-core = { path = "../../primitives/core" } -sp-blockchain = { path = "../../primitives/blockchain" } -sp-block-builder = { path = "../../primitives/block-builder" } -sp-api = { path = "../../primitives/api" } -sp-transaction-pool = { path = "../../primitives/transaction-pool" } -sp-consensus = { path = "../../primitives/consensus/common" } -sp-runtime = { path = "../../primitives/runtime" } -sp-session = { path = "../../primitives/session" } -sp-offchain = { path = "../../primitives/offchain" } -sp-inherents = { path = "../../primitives/inherents" } -sp-keyring = { path = "../../primitives/keyring" } - -sp-externalities = { path = "../../primitives/externalities" } -sp-state-machine = { path = "../../primitives/state-machine" } -sp-wasm-interface = { path = "../../primitives/wasm-interface" } -sp-runtime-interface = { path = "../../primitives/runtime-interface" } - -# pallets -frame-system = { path = "../../frame/system" } - -log = "0.4.8" -futures = "0.3.16" -tokio = { version = "1.10", features = ["signal"] } -# Calling RPC -jsonrpc-core = "18.0" -num-traits = "0.2.14" diff --git a/test-utils/test-runner/src/client.rs b/test-utils/test-runner/src/client.rs deleted file mode 100644 index 58c4cf6503a93..0000000000000 --- a/test-utils/test-runner/src/client.rs +++ /dev/null @@ -1,244 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . -//! Client parts -use crate::{default_config, ChainInfo}; -use futures::channel::mpsc; -use jsonrpc_core::MetaIoHandler; -use manual_seal::{ - consensus::babe::{BabeConsensusDataProvider, SlotTimestampProvider}, - import_queue, - rpc::{ManualSeal, ManualSealApi}, - run_manual_seal, EngineCommand, ManualSealParams, -}; -use sc_client_api::backend::Backend; -use sc_executor::NativeElseWasmExecutor; -use sc_service::{ - build_network, new_full_parts, spawn_tasks, BuildNetworkParams, ChainSpec, Configuration, - SpawnTasksParams, TFullBackend, TFullClient, TaskManager, -}; -use sc_transaction_pool::BasicPool; -use sc_transaction_pool_api::TransactionPool; -use sp_api::{ApiExt, ConstructRuntimeApi, Core, Metadata}; -use sp_block_builder::BlockBuilder; -use sp_consensus_babe::BabeApi; -use sp_finality_grandpa::GrandpaApi; -use sp_keyring::sr25519::Keyring::Alice; -use sp_offchain::OffchainWorkerApi; -use sp_runtime::traits::{Block as BlockT, Header}; -use sp_session::SessionKeys; -use sp_transaction_pool::runtime_api::TaggedTransactionQueue; -use std::{str::FromStr, sync::Arc}; - -type ClientParts = ( - Arc>, - TaskManager, - Arc< - TFullClient< - ::Block, - ::RuntimeApi, - NativeElseWasmExecutor<::ExecutorDispatch>, - >, - >, - Arc< - dyn TransactionPool< - Block = ::Block, - Hash = <::Block as BlockT>::Hash, - Error = sc_transaction_pool::error::Error, - InPoolTransaction = sc_transaction_pool::Transaction< - <::Block as BlockT>::Hash, - <::Block as BlockT>::Extrinsic, - >, - >, - >, - mpsc::Sender::Block as BlockT>::Hash>>, - Arc::Block>>, -); - -/// Provide the config or chain spec for a given chain -pub enum ConfigOrChainSpec { - /// Configuration object - Config(Configuration), - /// Chain spec object - ChainSpec(Box, tokio::runtime::Handle), -} -/// Creates all the client parts you need for [`Node`](crate::node::Node) -pub fn client_parts( - config_or_chain_spec: ConfigOrChainSpec, -) -> Result, sc_service::Error> -where - T: ChainInfo + 'static, - >, - >>::RuntimeApi: Core - + Metadata - + OffchainWorkerApi - + SessionKeys - + TaggedTransactionQueue - + BlockBuilder - + BabeApi - + ApiExt as Backend>::State> - + GrandpaApi, - ::Call: From>, - <::Block as BlockT>::Hash: FromStr + Unpin, - <::Block as BlockT>::Header: Unpin, - <<::Block as BlockT>::Header as Header>::Number: - num_traits::cast::AsPrimitive, -{ - use sp_consensus_babe::AuthorityId; - let config = match config_or_chain_spec { - ConfigOrChainSpec::Config(config) => config, - ConfigOrChainSpec::ChainSpec(chain_spec, tokio_handle) => - default_config(tokio_handle, chain_spec), - }; - - let executor = NativeElseWasmExecutor::::new( - config.wasm_method, - config.default_heap_pages, - config.max_runtime_instances, - ); - - let (client, backend, keystore, mut task_manager) = - new_full_parts::(&config, None, executor)?; - let client = Arc::new(client); - - let select_chain = sc_consensus::LongestChain::new(backend.clone()); - - let (grandpa_block_import, ..) = grandpa::block_import( - client.clone(), - &(client.clone() as Arc<_>), - select_chain.clone(), - None, - )?; - - let slot_duration = sc_consensus_babe::Config::get_or_compute(&*client)?; - let (block_import, babe_link) = sc_consensus_babe::block_import( - slot_duration.clone(), - grandpa_block_import, - client.clone(), - )?; - - let consensus_data_provider = BabeConsensusDataProvider::new( - client.clone(), - keystore.sync_keystore(), - babe_link.epoch_changes().clone(), - vec![(AuthorityId::from(Alice.public()), 1000)], - ) - .expect("failed to create ConsensusDataProvider"); - - let import_queue = - import_queue(Box::new(block_import.clone()), &task_manager.spawn_essential_handle(), None); - - let transaction_pool = BasicPool::new_full( - config.transaction_pool.clone(), - true.into(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), - ); - - let (network, system_rpc_tx, network_starter) = { - let params = BuildNetworkParams { - config: &config, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - spawn_handle: task_manager.spawn_handle(), - import_queue, - on_demand: None, - block_announce_validator_builder: None, - warp_sync: None, - }; - build_network(params)? - }; - - // offchain workers - sc_service::build_offchain_workers( - &config, - task_manager.spawn_handle(), - client.clone(), - network.clone(), - ); - - // Proposer object for block authorship. - let env = sc_basic_authorship::ProposerFactory::new( - task_manager.spawn_handle(), - client.clone(), - transaction_pool.clone(), - config.prometheus_registry(), - None, - ); - - // Channel for the rpc handler to communicate with the authorship task. - let (command_sink, commands_stream) = mpsc::channel(10); - - let rpc_sink = command_sink.clone(); - - let rpc_handlers = { - let params = SpawnTasksParams { - config, - client: client.clone(), - backend: backend.clone(), - task_manager: &mut task_manager, - keystore: keystore.sync_keystore(), - on_demand: None, - transaction_pool: transaction_pool.clone(), - rpc_extensions_builder: Box::new(move |_, _| { - let mut io = jsonrpc_core::IoHandler::default(); - io.extend_with(ManualSealApi::to_delegate(ManualSeal::new(rpc_sink.clone()))); - Ok(io) - }), - remote_blockchain: None, - network, - system_rpc_tx, - telemetry: None, - }; - spawn_tasks(params)? - }; - - let cloned_client = client.clone(); - let create_inherent_data_providers = Box::new(move |_, _| { - let client = cloned_client.clone(); - async move { - let timestamp = - SlotTimestampProvider::new(client.clone()).map_err(|err| format!("{:?}", err))?; - let babe = - sp_consensus_babe::inherents::InherentDataProvider::new(timestamp.slot().into()); - Ok((timestamp, babe)) - } - }); - - // Background authorship future. - let authorship_future = run_manual_seal(ManualSealParams { - block_import, - env, - client: client.clone(), - pool: transaction_pool.clone(), - commands_stream, - select_chain, - consensus_data_provider: Some(Box::new(consensus_data_provider)), - create_inherent_data_providers, - }); - - // spawn the authorship task as an essential task. - task_manager.spawn_essential_handle().spawn("manual-seal", authorship_future); - - network_starter.start_network(); - let rpc_handler = rpc_handlers.io_handler(); - - Ok((rpc_handler, task_manager, client, transaction_pool, command_sink, backend)) -} diff --git a/test-utils/test-runner/src/host_functions.rs b/test-utils/test-runner/src/host_functions.rs deleted file mode 100644 index 731abfbb9db05..0000000000000 --- a/test-utils/test-runner/src/host_functions.rs +++ /dev/null @@ -1,90 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -/// Use this to override host functions. -/// eg -/// ```rust -/// use test_runner::override_host_functions; -/// pub struct SignatureVerificationOverride; -/// -/// impl sp_wasm_interface::HostFunctions for SignatureVerificationOverride { -/// fn host_functions() -> Vec<&'static dyn sp_wasm_interface::Function> { -/// override_host_functions!( -/// "ext_crypto_ecdsa_verify_version_1", EcdsaVerify, -/// ) -/// } -/// } -/// ``` -#[macro_export] -macro_rules! override_host_functions { - ($($fn_name:expr, $name:ident,)*) => {{ - let mut host_functions = vec![]; - $( - struct $name; - impl sp_wasm_interface::Function for $name { - fn name(&self) -> &str { - &$fn_name - } - - fn signature(&self) -> sp_wasm_interface::Signature { - sp_wasm_interface::Signature { - args: std::borrow::Cow::Owned(vec![ - sp_wasm_interface::ValueType::I32, - sp_wasm_interface::ValueType::I64, - sp_wasm_interface::ValueType::I32, - ]), - return_value: Some(sp_wasm_interface::ValueType::I32), - } - } - - fn execute( - &self, - context: &mut dyn sp_wasm_interface::FunctionContext, - _args: &mut dyn Iterator, - ) -> Result, String> { - ::into_ffi_value(true, context) - .map(sp_wasm_interface::IntoValue::into_value) - .map(Some) - } - } - host_functions.push(&$name as &'static dyn sp_wasm_interface::Function); - )* - host_functions - }}; -} - -/// Provides host functions that overrides runtime signature verification -/// to always return true. -pub struct SignatureVerificationOverride; - -impl sp_wasm_interface::HostFunctions for SignatureVerificationOverride { - fn host_functions() -> Vec<&'static dyn sp_wasm_interface::Function> { - override_host_functions!( - "ext_crypto_ecdsa_verify_version_1", - EcdsaVerify, - "ext_crypto_ecdsa_verify_version_2", - EcdsaVerifyV2, - "ext_crypto_ed25519_verify_version_1", - Ed25519Verify, - "ext_crypto_sr25519_verify_version_1", - Sr25519Verify, - "ext_crypto_sr25519_verify_version_2", - Sr25519VerifyV2, - ) - } -} diff --git a/test-utils/test-runner/src/lib.rs b/test-utils/test-runner/src/lib.rs deleted file mode 100644 index ca2c518fd6926..0000000000000 --- a/test-utils/test-runner/src/lib.rs +++ /dev/null @@ -1,309 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . -#![deny(missing_docs, unused_extern_crates)] - -//! Test runner -//! # Substrate Test Runner -//! -//! Allows you to test -//!
-//! -//! - Migrations -//! - Runtime Upgrades -//! - Pallets and general runtime functionality. -//! -//! This works by running a full node with a Manual Seal-BABE™ hybrid consensus for block authoring. -//! -//!

Note

-//! The running node has no signature verification, which allows us author extrinsics for any -//! account on chain.
-//!
-//! -//!

How do I Use this?

-//! -//! -//! ```rust,ignore -//! use test_runner::{Node, ChainInfo, SignatureVerificationOverride, base_path, NodeConfig}; -//! use sc_finality_grandpa::GrandpaBlockImport; -//! use sc_service::{ -//! TFullBackend, TFullClient, Configuration, TaskManager, new_full_parts, BasePath, -//! DatabaseSource, KeepBlocks, TransactionStorageMode, ChainSpec, Role, -//! config::{NetworkConfiguration, KeystoreConfig}, -//! }; -//! use std::sync::Arc; -//! use sp_inherents::InherentDataProviders; -//! use sc_consensus_babe::BabeBlockImport; -//! use sp_keystore::SyncCryptoStorePtr; -//! use sp_keyring::sr25519::Keyring::{Alice, Bob}; -//! use node_cli::chain_spec::development_config; -//! use sp_consensus_babe::AuthorityId; -//! use manual_seal::{ConsensusDataProvider, consensus::babe::BabeConsensusDataProvider}; -//! use sp_runtime::{traits::IdentifyAccount, MultiSigner, generic::Era}; -//! use sc_executor::WasmExecutionMethod; -//! use sc_network::{multiaddr, config::TransportConfig}; -//! use sc_client_api::execution_extensions::ExecutionStrategies; -//! use sc_informant::OutputFormat; -//! use sp_api::TransactionFor; -//! -//! type BlockImport = BabeBlockImport>; -//! -//! pub struct ExecutorDispatch; -//! -//! impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { -//! type ExtendHostFunctions = SignatureVerificationOverride; -//! -//! fn dispatch(method: &str, data: &[u8]) -> Option> { -//! node_runtime::api::dispatch(method, data) -//! } -//! -//! fn native_version() -> sc_executor::NativeVersion { -//! node_runtime::native_version() -//! } -//! } -//! -//! struct Requirements; -//! -//! impl ChainInfo for Requirements { -//! /// Provide a Block type with an OpaqueExtrinsic -//! type Block = node_primitives::Block; -//! /// Provide an ExecutorDispatch type for the runtime -//! type ExecutorDispatch = ExecutorDispatch; -//! /// Provide the runtime itself -//! type Runtime = node_runtime::Runtime; -//! /// A touch of runtime api -//! type RuntimeApi = node_runtime::RuntimeApi; -//! /// A pinch of SelectChain implementation -//! type SelectChain = sc_consensus::LongestChain, Self::Block>; -//! /// A slice of concrete BlockImport type -//! type BlockImport = BlockImport< -//! Self::Block, -//! TFullBackend, -//! TFullClient>, -//! Self::SelectChain, -//! >; -//! /// and a dash of SignedExtensions -//! type SignedExtras = node_runtime::SignedExtra; -//! -//! /// Create your signed extras here. -//! fn signed_extras( -//! from: ::AccountId, -//! ) -> Self::SignedExtension { -//! let nonce = frame_system::Pallet::::account_nonce(from); -//! -//! ( -//! frame_system::CheckSpecVersion::::new(), -//! frame_system::CheckTxVersion::::new(), -//! frame_system::CheckGenesis::::new(), -//! frame_system::CheckMortality::::from(Era::Immortal), -//! frame_system::CheckNonce::::from(nonce), -//! frame_system::CheckWeight::::new(), -//! pallet_transaction_payment::ChargeTransactionPayment::::from(0), -//! ) -//! } -//! -//! /// The function signature tells you all you need to know. ;) -//! fn create_client_parts(config: &Configuration) -> Result< -//! ( -//! Arc>>, -//! Arc>, -//! KeyStorePtr, -//! TaskManager, -//! InherentDataProviders, -//! Option>, -//! Self::Block -//! >, -//! > -//! >>, -//! Self::SelectChain, -//! Self::BlockImport -//! ), -//! sc_service::Error -//! > { -//! let ( -//! client, -//! backend, -//! keystore, -//! task_manager, -//! ) = new_full_parts::>(config)?; -//! let client = Arc::new(client); -//! -//! let inherent_providers = InherentDataProviders::new(); -//! let select_chain = sc_consensus::LongestChain::new(backend.clone()); -//! -//! let (grandpa_block_import, ..) = -//! sc_finality_grandpa::block_import(client.clone(), &(client.clone() as Arc<_>), select_chain.clone())?; -//! -//! let (block_import, babe_link) = sc_consensus_babe::block_import( -//! sc_consensus_babe::Config::get_or_compute(&*client)?, -//! grandpa_block_import, -//! client.clone(), -//! )?; -//! -//! let consensus_data_provider = BabeConsensusDataProvider::new( -//! client.clone(), -//! keystore.clone(), -//! &inherent_providers, -//! babe_link.epoch_changes().clone(), -//! vec![(AuthorityId::from(Alice.public()), 1000)] -//! ) -//! .expect("failed to create ConsensusDataProvider"); -//! -//! Ok(( -//! client, -//! backend, -//! keystore, -//! task_manager, -//! inherent_providers, -//! Some(Box::new(consensus_data_provider)), -//! select_chain, -//! block_import -//! )) -//! } -//! -//! fn dispatch_with_root(call: ::Call, node: &mut Node) { -//! let alice = MultiSigner::from(Alice.public()).into_account(); -//! // for chains that support sudo, otherwise, you'd have to use pallet-democracy here. -//! let call = pallet_sudo::Call::sudo(Box::new(call)); -//! node.submit_extrinsic(call, alice); -//! node.seal_blocks(1); -//! } -//! } -//! -//! /// And now for the most basic test -//! -//! #[test] -//! fn simple_balances_test() { -//! // given -//! let config = NodeConfig { -//! execution_strategies: ExecutionStrategies { -//! syncing: sc_client_api::ExecutionStrategy::NativeWhenPossible, -//! importing: sc_client_api::ExecutionStrategy::NativeWhenPossible, -//! block_construction: sc_client_api::ExecutionStrategy::NativeWhenPossible, -//! offchain_worker: sc_client_api::ExecutionStrategy::NativeWhenPossible, -//! other: sc_client_api::ExecutionStrategy::NativeWhenPossible, -//! }, -//! chain_spec: Box::new(development_config()), -//! log_targets: vec![], -//! }; -//! let mut node = Node::::new(config).unwrap(); -//! -//! type Balances = pallet_balances::Pallet; -//! -//! let (alice, bob) = (Alice.pair(), Bob.pair()); -//! let (alice_account_id, bob_acount_id) = ( -//! MultiSigner::from(alice.public()).into_account(), -//! MultiSigner::from(bob.public()).into_account() -//! ); -//! -//! /// the function with_state allows us to read state, pretty cool right? :D -//! let old_balance = node.with_state(|| Balances::free_balance(alice_account_id.clone())); -//! -//! // 70 dots -//! let amount = 70_000_000_000_000; -//! -//! /// Send extrinsic in action. -//! node.submit_extrinsic(BalancesCall::transfer(bob_acount_id.clone(), amount), alice_account_id.clone()); -//! -//! /// Produce blocks in action, Powered by manual-seal™. -//! node.seal_blocks(1); -//! -//! /// we can check the new state :D -//! let new_balance = node.with_state(|| Balances::free_balance(alice_account_id)); -//! -//! /// we can now make assertions on how state has changed. -//! assert_eq!(old_balance + amount, new_balance); -//! } -//! ``` - -use sc_consensus::BlockImport; -use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; -use sc_service::TFullClient; -use sp_api::{ConstructRuntimeApi, TransactionFor}; -use sp_consensus::SelectChain; -use sp_inherents::InherentDataProvider; -use sp_runtime::traits::{Block as BlockT, SignedExtension}; - -mod client; -mod host_functions; -mod node; -mod utils; - -pub use client::*; -pub use host_functions::*; -pub use node::*; -pub use utils::*; - -/// Wrapper trait for concrete type required by this testing framework. -pub trait ChainInfo: Sized { - /// Opaque block type - type Block: BlockT; - - /// ExecutorDispatch dispatch type - type ExecutorDispatch: NativeExecutionDispatch + 'static; - - /// Runtime - type Runtime: frame_system::Config; - - /// RuntimeApi - type RuntimeApi: Send - + Sync - + 'static - + ConstructRuntimeApi< - Self::Block, - TFullClient< - Self::Block, - Self::RuntimeApi, - NativeElseWasmExecutor, - >, - >; - - /// select chain type. - type SelectChain: SelectChain + 'static; - - /// Block import type. - type BlockImport: Send - + Sync - + Clone - + BlockImport< - Self::Block, - Error = sp_consensus::Error, - Transaction = TransactionFor< - TFullClient< - Self::Block, - Self::RuntimeApi, - NativeElseWasmExecutor, - >, - Self::Block, - >, - > + 'static; - - /// The signed extras required by the runtime - type SignedExtras: SignedExtension; - - /// The inherent data providers. - type InherentDataProviders: InherentDataProvider + 'static; - - /// Signed extras, this function is caled in an externalities provided environment. - fn signed_extras( - from: ::AccountId, - ) -> Self::SignedExtras; -} diff --git a/test-utils/test-runner/src/node.rs b/test-utils/test-runner/src/node.rs deleted file mode 100644 index 9114013b747f7..0000000000000 --- a/test-utils/test-runner/src/node.rs +++ /dev/null @@ -1,288 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use std::sync::Arc; - -use crate::ChainInfo; -use futures::{ - channel::{mpsc, oneshot}, - FutureExt, SinkExt, -}; -use jsonrpc_core::MetaIoHandler; -use manual_seal::EngineCommand; -use sc_client_api::{ - backend::{self, Backend}, - CallExecutor, ExecutorProvider, -}; -use sc_executor::NativeElseWasmExecutor; -use sc_service::{TFullBackend, TFullCallExecutor, TFullClient, TaskManager}; -use sc_transaction_pool_api::TransactionPool; -use sp_api::{OverlayedChanges, StorageTransactionCache}; -use sp_blockchain::HeaderBackend; -use sp_core::ExecutionContext; -use sp_runtime::{ - generic::{BlockId, UncheckedExtrinsic}, - traits::{Block as BlockT, Extrinsic, Header, NumberFor}, - transaction_validity::TransactionSource, - MultiAddress, MultiSignature, -}; -use sp_state_machine::Ext; - -/// This holds a reference to a running node on another thread, -/// the node process is dropped when this struct is dropped -/// also holds logs from the process. -pub struct Node { - /// rpc handler for communicating with the node over rpc. - rpc_handler: Arc>, - /// handle to the running node. - task_manager: Option, - /// client instance - client: Arc>>, - /// transaction pool - pool: Arc< - dyn TransactionPool< - Block = ::Block, - Hash = <::Block as BlockT>::Hash, - Error = sc_transaction_pool::error::Error, - InPoolTransaction = sc_transaction_pool::Transaction< - <::Block as BlockT>::Hash, - <::Block as BlockT>::Extrinsic, - >, - >, - >, - /// channel to communicate with manual seal on. - manual_seal_command_sink: mpsc::Sender::Hash>>, - /// backend type. - backend: Arc>, - /// Block number at initialization of this Node. - initial_block_number: NumberFor, -} - -type EventRecord = frame_system::EventRecord< - ::Event, - ::Hash, ->; - -impl Node -where - T: ChainInfo, - <::Header as Header>::Number: From, -{ - /// Creates a new node. - pub fn new( - rpc_handler: Arc>, - task_manager: TaskManager, - client: Arc< - TFullClient>, - >, - pool: Arc< - dyn TransactionPool< - Block = ::Block, - Hash = <::Block as BlockT>::Hash, - Error = sc_transaction_pool::error::Error, - InPoolTransaction = sc_transaction_pool::Transaction< - <::Block as BlockT>::Hash, - <::Block as BlockT>::Extrinsic, - >, - >, - >, - command_sink: mpsc::Sender::Hash>>, - backend: Arc>, - ) -> Self { - Self { - rpc_handler, - task_manager: Some(task_manager), - client: client.clone(), - pool, - backend, - manual_seal_command_sink: command_sink, - initial_block_number: client.info().best_number, - } - } - - /// Returns a reference to the rpc handlers, use this to send rpc requests. - /// eg - /// ```ignore - /// let request = r#"{"jsonrpc":"2.0","method":"engine_createBlock","params": [true, true],"id":1}"#; - /// let response = node.rpc_handler() - /// .handle_request_sync(request, Default::default()); - /// ``` - pub fn rpc_handler( - &self, - ) -> Arc> { - self.rpc_handler.clone() - } - - /// Return a reference to the Client - pub fn client( - &self, - ) -> Arc>> { - self.client.clone() - } - - /// Return a reference to the pool. - pub fn pool( - &self, - ) -> Arc< - dyn TransactionPool< - Block = ::Block, - Hash = <::Block as BlockT>::Hash, - Error = sc_transaction_pool::error::Error, - InPoolTransaction = sc_transaction_pool::Transaction< - <::Block as BlockT>::Hash, - <::Block as BlockT>::Extrinsic, - >, - >, - > { - self.pool.clone() - } - - /// Executes closure in an externalities provided environment. - pub fn with_state(&self, closure: impl FnOnce() -> R) -> R - where - > as CallExecutor>::Error: - std::fmt::Debug, - { - let id = BlockId::Hash(self.client.info().best_hash); - let mut overlay = OverlayedChanges::default(); - let changes_trie = - backend::changes_tries_state_at_block(&id, self.backend.changes_trie_storage()) - .unwrap(); - let mut cache = StorageTransactionCache::< - T::Block, - as Backend>::State, - >::default(); - let mut extensions = self - .client - .execution_extensions() - .extensions(&id, ExecutionContext::BlockConstruction); - let state_backend = self - .backend - .state_at(id.clone()) - .expect(&format!("State at block {} not found", id)); - - let mut ext = Ext::new( - &mut overlay, - &mut cache, - &state_backend, - changes_trie.clone(), - Some(&mut extensions), - ); - sp_externalities::set_and_run_with_externalities(&mut ext, closure) - } - - /// submit some extrinsic to the node. if signer is None, will submit unsigned_extrinsic. - pub async fn submit_extrinsic( - &self, - call: impl Into<::Call>, - signer: Option<::AccountId>, - ) -> Result<::Hash, sc_transaction_pool::error::Error> - where - ::Extrinsic: From< - UncheckedExtrinsic< - MultiAddress< - ::AccountId, - ::Index, - >, - ::Call, - MultiSignature, - T::SignedExtras, - >, - >, - { - let signed_data = if let Some(signer) = signer { - let extra = self.with_state(|| T::signed_extras(signer.clone())); - Some((signer.into(), MultiSignature::Sr25519(Default::default()), extra)) - } else { - None - }; - let ext = UncheckedExtrinsic::< - MultiAddress< - ::AccountId, - ::Index, - >, - ::Call, - MultiSignature, - T::SignedExtras, - >::new(call.into(), signed_data) - .expect("UncheckedExtrinsic::new() always returns Some"); - let at = self.client.info().best_hash; - - self.pool - .submit_one(&BlockId::Hash(at), TransactionSource::Local, ext.into()) - .await - } - - /// Get the events of the most recently produced block - pub fn events(&self) -> Vec> { - self.with_state(|| frame_system::Pallet::::events()) - } - - /// Instructs manual seal to seal new, possibly empty blocks. - pub async fn seal_blocks(&self, num: usize) { - let mut sink = self.manual_seal_command_sink.clone(); - - for count in 0..num { - let (sender, future_block) = oneshot::channel(); - let future = sink.send(EngineCommand::SealNewBlock { - create_empty: true, - finalize: false, - parent_hash: None, - sender: Some(sender), - }); - - const ERROR: &'static str = "manual-seal authorship task is shutting down"; - future.await.expect(ERROR); - - match future_block.await.expect(ERROR) { - Ok(block) => { - log::info!("sealed {} (hash: {}) of {} blocks", count + 1, block.hash, num) - }, - Err(err) => { - log::error!("failed to seal block {} of {}, error: {:?}", count + 1, num, err) - }, - } - } - } - - /// Revert count number of blocks from the chain. - pub fn revert_blocks(&self, count: NumberFor) { - self.backend.revert(count, true).expect("Failed to revert blocks: "); - } - - /// so you've decided to run the test runner as a binary, use this to shutdown gracefully. - pub async fn until_shutdown(mut self) { - let manager = self.task_manager.take(); - if let Some(mut task_manager) = manager { - let task = task_manager.future().fuse(); - let signal = tokio::signal::ctrl_c(); - futures::pin_mut!(signal); - futures::future::select(task, signal).await; - // we don't really care whichever comes first. - task_manager.clean_shutdown().await - } - } -} - -impl Drop for Node { - fn drop(&mut self) { - // Revert all blocks added since creation of the node. - let diff = self.client.info().best_number - self.initial_block_number; - self.revert_blocks(diff); - } -} diff --git a/test-utils/test-runner/src/utils.rs b/test-utils/test-runner/src/utils.rs deleted file mode 100644 index 8e8c84e6b4f8a..0000000000000 --- a/test-utils/test-runner/src/utils.rs +++ /dev/null @@ -1,117 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use sc_client_api::execution_extensions::ExecutionStrategies; -use sc_executor::WasmExecutionMethod; -use sc_informant::OutputFormat; -use sc_network::{ - config::{NetworkConfiguration, Role, TransportConfig}, - multiaddr, -}; -use sc_service::{ - config::KeystoreConfig, BasePath, ChainSpec, Configuration, DatabaseSource, KeepBlocks, - TransactionStorageMode, -}; -use sp_keyring::sr25519::Keyring::Alice; -use tokio::runtime::Handle; - -pub use sc_cli::build_runtime; - -/// Base db path gotten from env -pub fn base_path() -> BasePath { - if let Some(base) = std::env::var("DB_BASE_PATH").ok() { - BasePath::new(base) - } else { - BasePath::new_temp_dir().expect("couldn't create a temp dir") - } -} - -/// Produces a default configuration object, suitable for use with most set ups. -pub fn default_config(tokio_handle: Handle, mut chain_spec: Box) -> Configuration { - let base_path = base_path(); - let root_path = base_path.path().to_path_buf().join("chains").join(chain_spec.id()); - - let storage = chain_spec - .as_storage_builder() - .build_storage() - .expect("could not build storage"); - - chain_spec.set_storage(storage); - let key_seed = Alice.to_seed(); - - let mut network_config = NetworkConfiguration::new( - format!("Test Node for: {}", key_seed), - "network/test/0.1", - Default::default(), - None, - ); - let informant_output_format = OutputFormat { enable_color: false }; - network_config.allow_non_globals_in_dht = true; - - network_config.listen_addresses.push(multiaddr::Protocol::Memory(0).into()); - - network_config.transport = TransportConfig::MemoryOnly; - - Configuration { - impl_name: "test-node".to_string(), - impl_version: "0.1".to_string(), - role: Role::Authority, - tokio_handle, - transaction_pool: Default::default(), - network: network_config, - keystore: KeystoreConfig::Path { path: root_path.join("key"), password: None }, - database: DatabaseSource::RocksDb { path: root_path.join("db"), cache_size: 128 }, - state_cache_size: 16777216, - state_cache_child_ratio: None, - chain_spec, - wasm_method: WasmExecutionMethod::Interpreted, - execution_strategies: ExecutionStrategies { - syncing: sc_client_api::ExecutionStrategy::AlwaysWasm, - importing: sc_client_api::ExecutionStrategy::AlwaysWasm, - block_construction: sc_client_api::ExecutionStrategy::AlwaysWasm, - offchain_worker: sc_client_api::ExecutionStrategy::AlwaysWasm, - other: sc_client_api::ExecutionStrategy::AlwaysWasm, - }, - rpc_http: None, - rpc_ws: None, - rpc_ipc: None, - rpc_ws_max_connections: None, - rpc_cors: None, - rpc_methods: Default::default(), - rpc_max_payload: None, - prometheus_config: None, - telemetry_endpoints: None, - default_heap_pages: None, - offchain_worker: Default::default(), - force_authoring: false, - disable_grandpa: false, - dev_key_seed: Some(key_seed), - tracing_targets: None, - tracing_receiver: Default::default(), - max_runtime_instances: 8, - announce_block: true, - base_path: Some(base_path), - wasm_runtime_overrides: None, - informant_output_format, - disable_log_reloading: false, - keystore_remote: None, - keep_blocks: KeepBlocks::All, - state_pruning: Default::default(), - transaction_storage: TransactionStorageMode::BlockBody, - } -} diff --git a/test-utils/tests/basic.rs b/test-utils/tests/basic.rs index 527ca3e365edb..4daca836c7fd9 100644 --- a/test-utils/tests/basic.rs +++ b/test-utils/tests/basic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/test-utils/tests/ui.rs b/test-utils/tests/ui.rs index 119162fdc21b8..be3b9c111b3e9 100644 --- a/test-utils/tests/ui.rs +++ b/test-utils/tests/ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/test-utils/tests/ui/too-many-func-parameters.rs b/test-utils/tests/ui/too-many-func-parameters.rs index b1789b9d3ee7e..51059f37ab629 100644 --- a/test-utils/tests/ui/too-many-func-parameters.rs +++ b/test-utils/tests/ui/too-many-func-parameters.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/utils/build-script-utils/Cargo.toml b/utils/build-script-utils/Cargo.toml index fbef70db93bfd..7150b4fa5adcb 100644 --- a/utils/build-script-utils/Cargo.toml +++ b/utils/build-script-utils/Cargo.toml @@ -2,9 +2,9 @@ name = "substrate-build-script-utils" version = "3.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Crate with utility functions for `build.rs` scripts." readme = "README.md" @@ -13,4 +13,4 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -platforms = "1.1" +platforms = "2.0" diff --git a/utils/build-script-utils/src/git.rs b/utils/build-script-utils/src/git.rs index 66a15737f84ca..db9a4b291ffdb 100644 --- a/utils/build-script-utils/src/git.rs +++ b/utils/build-script-utils/src/git.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/build-script-utils/src/lib.rs b/utils/build-script-utils/src/lib.rs index 0c45c4b34ebe8..7e69f2ac85d4f 100644 --- a/utils/build-script-utils/src/lib.rs +++ b/utils/build-script-utils/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/build-script-utils/src/version.rs b/utils/build-script-utils/src/version.rs index 52336eb0b6a24..19b507ba261e0 100644 --- a/utils/build-script-utils/src/version.rs +++ b/utils/build-script-utils/src/version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,51 +15,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -use platforms::*; use std::{borrow::Cow, process::Command}; /// Generate the `cargo:` key output pub fn generate_cargo_keys() { - let output = Command::new("git").args(&["rev-parse", "--short", "HEAD"]).output(); - - let commit = match output { - Ok(o) if o.status.success() => { - let sha = String::from_utf8_lossy(&o.stdout).trim().to_owned(); - Cow::from(sha) - }, - Ok(o) => { - println!("cargo:warning=Git command failed with status: {}", o.status); - Cow::from("unknown") - }, - Err(err) => { - println!("cargo:warning=Failed to execute git command: {}", err); - Cow::from("unknown") - }, + let commit = if let Ok(hash) = std::env::var("SUBSTRATE_CLI_GIT_COMMIT_HASH") { + Cow::from(hash.trim().to_owned()) + } else { + // We deliberately set the length here to `11` to ensure that + // the emitted hash is always of the same length; otherwise + // it can (and will!) vary between different build environments. + match Command::new("git").args(&["rev-parse", "--short=11", "HEAD"]).output() { + Ok(o) if o.status.success() => { + let sha = String::from_utf8_lossy(&o.stdout).trim().to_owned(); + Cow::from(sha) + }, + Ok(o) => { + println!("cargo:warning=Git command failed with status: {}", o.status); + Cow::from("unknown") + }, + Err(err) => { + println!("cargo:warning=Failed to execute git command: {}", err); + Cow::from("unknown") + }, + } }; println!("cargo:rustc-env=SUBSTRATE_CLI_IMPL_VERSION={}", get_version(&commit)) } -fn get_platform() -> String { - let env_dash = if TARGET_ENV.is_some() { "-" } else { "" }; - - format!( - "{}-{}{}{}", - TARGET_ARCH.as_str(), - TARGET_OS.as_str(), - env_dash, - TARGET_ENV.map(|x| x.as_str()).unwrap_or(""), - ) -} - fn get_version(impl_commit: &str) -> String { let commit_dash = if impl_commit.is_empty() { "" } else { "-" }; format!( - "{}{}{}-{}", + "{}{}{}", std::env::var("CARGO_PKG_VERSION").unwrap_or_default(), commit_dash, - impl_commit, - get_platform(), + impl_commit ) } diff --git a/utils/fork-tree/Cargo.toml b/utils/fork-tree/Cargo.toml index 11c269bc3cba8..4ac176f645bd1 100644 --- a/utils/fork-tree/Cargo.toml +++ b/utils/fork-tree/Cargo.toml @@ -2,9 +2,9 @@ name = "fork-tree" version = "3.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Utility library for managing tree-like ordered data with logic for pruning the tree while finalizing nodes." documentation = "https://docs.rs/fork-tree" @@ -14,4 +14,4 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } diff --git a/utils/fork-tree/src/lib.rs b/utils/fork-tree/src/lib.rs index 9143da89a77e0..76c28d910f943 100644 --- a/utils/fork-tree/src/lib.rs +++ b/utils/fork-tree/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -69,7 +69,19 @@ pub enum FinalizationResult { Unchanged, } +/// Filtering action. +#[derive(Debug, PartialEq)] +pub enum FilterAction { + /// Remove the node and its subtree. + Remove, + /// Maintain the node. + KeepNode, + /// Maintain the node and its subtree. + KeepTree, +} + /// A tree data structure that stores several nodes across multiple branches. +/// /// Top-level branches are called roots. The tree has functionality for /// finalizing nodes, which means that that node is traversed, and all competing /// branches are pruned. It also guarantees that nodes in the tree are finalized @@ -82,90 +94,6 @@ pub struct ForkTree { best_finalized_number: Option, } -impl ForkTree -where - H: PartialEq + Clone, - N: Ord + Clone, - V: Clone, -{ - /// Prune the tree, removing all non-canonical nodes. We find the node in the - /// tree that is the deepest ancestor of the given hash and that passes the - /// given predicate. If such a node exists, we re-root the tree to this - /// node. Otherwise the tree remains unchanged. The given function - /// `is_descendent_of` should return `true` if the second hash (target) is a - /// descendent of the first hash (base). - /// - /// Returns all pruned node data. - pub fn prune( - &mut self, - hash: &H, - number: &N, - is_descendent_of: &F, - predicate: &P, - ) -> Result, Error> - where - E: std::error::Error, - F: Fn(&H, &H) -> Result, - P: Fn(&V) -> bool, - { - let new_root_index = - self.find_node_index_where(hash, number, is_descendent_of, predicate)?; - - let removed = if let Some(mut root_index) = new_root_index { - let mut old_roots = std::mem::take(&mut self.roots); - - let mut root = None; - let mut cur_children = Some(&mut old_roots); - - while let Some(cur_index) = root_index.pop() { - if let Some(children) = cur_children.take() { - if root_index.is_empty() { - root = Some(children.remove(cur_index)); - } else { - cur_children = Some(&mut children[cur_index].children); - } - } - } - - let mut root = root.expect( - "find_node_index_where will return array with at least one index; \ - this results in at least one item in removed; qed", - ); - - let mut removed = old_roots; - - // we found the deepest ancestor of the finalized block, so we prune - // out any children that don't include the finalized block. - let root_children = std::mem::take(&mut root.children); - let mut is_first = true; - - for child in root_children { - if is_first && - (child.number == *number && child.hash == *hash || - child.number < *number && is_descendent_of(&child.hash, hash)?) - { - root.children.push(child); - // assuming that the tree is well formed only one child should pass this - // requirement due to ancestry restrictions (i.e. they must be different forks). - is_first = false; - } else { - removed.push(child); - } - } - - self.roots = vec![root]; - - removed - } else { - Vec::new() - }; - - self.rebalance(); - - Ok(RemovedIterator { stack: removed }) - } -} - impl ForkTree where H: PartialEq, @@ -176,19 +104,19 @@ where ForkTree { roots: Vec::new(), best_finalized_number: None } } - /// Rebalance the tree, i.e. sort child nodes by max branch depth - /// (decreasing). + /// Rebalance the tree, i.e. sort child nodes by max branch depth (decreasing). /// /// Most operations in the tree are performed with depth-first search /// starting from the leftmost node at every level, since this tree is meant /// to be used in a blockchain context, a good heuristic is that the node - /// we'll be looking - /// for at any point will likely be in one of the deepest chains (i.e. the - /// longest ones). + /// we'll be looking for at any point will likely be in one of the deepest chains + /// (i.e. the longest ones). pub fn rebalance(&mut self) { self.roots.sort_by_key(|n| Reverse(n.max_depth())); - for root in &mut self.roots { - root.rebalance(); + let mut stack: Vec<_> = self.roots.iter_mut().collect(); + while let Some(node) = stack.pop() { + node.children.sort_by_key(|n| Reverse(n.max_depth())); + stack.extend(node.children.iter_mut()); } } @@ -198,11 +126,16 @@ where /// imported in order. /// /// Returns `true` if the imported node is a root. + // WARNING: some users of this method (i.e. consensus epoch changes tree) currently silently + // rely on a **post-order DFS** traversal. If we are using instead a top-down traversal method + // then the `is_descendent_of` closure, when used after a warp-sync, may end up querying the + // backend for a block (the one corresponding to the root) that is not present and thus will + // return a wrong result. pub fn import( &mut self, - mut hash: H, - mut number: N, - mut data: V, + hash: H, + number: N, + data: V, is_descendent_of: &F, ) -> Result> where @@ -215,29 +148,24 @@ where } } - for root in self.roots.iter_mut() { - if root.hash == hash { - return Err(Error::Duplicate) - } + let (children, is_root) = + match self.find_node_where_mut(&hash, &number, is_descendent_of, &|_| true)? { + Some(parent) => (&mut parent.children, false), + None => (&mut self.roots, true), + }; - match root.import(hash, number, data, is_descendent_of)? { - Some((h, n, d)) => { - hash = h; - number = n; - data = d; - }, - None => { - self.rebalance(); - return Ok(false) - }, - } + if children.iter().any(|elem| elem.hash == hash) { + return Err(Error::Duplicate) } - self.roots.push(Node { data, hash, number, children: Vec::new() }); + children.push(Node { data, hash, number, children: Default::default() }); - self.rebalance(); + if children.len() == 1 { + // Rebalance may be required only if we've extended the branch depth. + self.rebalance(); + } - Ok(true) + Ok(is_root) } /// Iterates over the existing roots in the tree. @@ -256,6 +184,53 @@ where self.node_iter().map(|node| (&node.hash, &node.number, &node.data)) } + /// Map fork tree into values of new types. + /// + /// Tree traversal technique (e.g. BFS vs DFS) is left as not specified and + /// may be subject to change in the future. In other words, your predicates + /// should not rely on the observed traversal technique currently in use. + pub fn map(self, f: &mut F) -> ForkTree + where + F: FnMut(&H, &N, V) -> VT, + { + let mut queue: Vec<_> = + self.roots.into_iter().rev().map(|node| (usize::MAX, node)).collect(); + let mut next_queue = Vec::new(); + let mut output = Vec::new(); + + while !queue.is_empty() { + for (parent_index, node) in queue.drain(..) { + let new_data = f(&node.hash, &node.number, node.data); + let new_node = Node { + hash: node.hash, + number: node.number, + data: new_data, + children: Vec::with_capacity(node.children.len()), + }; + + let node_id = output.len(); + output.push((parent_index, new_node)); + + for child in node.children.into_iter().rev() { + next_queue.push((node_id, child)); + } + } + + std::mem::swap(&mut queue, &mut next_queue); + } + + let mut roots = Vec::new(); + while let Some((parent_index, new_node)) = output.pop() { + if parent_index == usize::MAX { + roots.push(new_node); + } else { + output[parent_index].1.children.push(new_node); + } + } + + ForkTree { roots, best_finalized_number: self.best_finalized_number } + } + /// Find a node in the tree that is the deepest ancestor of the given /// block hash and which passes the given predicate. The given function /// `is_descendent_of` should return `true` if the second hash (target) @@ -272,27 +247,12 @@ where F: Fn(&H, &H) -> Result, P: Fn(&V) -> bool, { - // search for node starting from all roots - for root in self.roots.iter() { - let node = root.find_node_where(hash, number, is_descendent_of, predicate)?; - - // found the node, early exit - if let FindOutcome::Found(node) = node { - return Ok(Some(node)) - } - } - - Ok(None) - } - - /// Map fork tree into values of new types. - pub fn map(self, f: &mut F) -> ForkTree - where - F: FnMut(&H, &N, V) -> VT, - { - let roots = self.roots.into_iter().map(|root| root.map(f)).collect(); - - ForkTree { roots, best_finalized_number: self.best_finalized_number } + let maybe_path = self.find_node_index_where(hash, number, is_descendent_of, predicate)?; + Ok(maybe_path.map(|path| { + let children = + path.iter().take(path.len() - 1).fold(&self.roots, |curr, &i| &curr[i].children); + &children[path[path.len() - 1]] + })) } /// Same as [`find_node_where`](ForkTree::find_node_where), but returns mutable reference. @@ -308,44 +268,148 @@ where F: Fn(&H, &H) -> Result, P: Fn(&V) -> bool, { - // search for node starting from all roots - for root in self.roots.iter_mut() { - let node = root.find_node_where_mut(hash, number, is_descendent_of, predicate)?; + let maybe_path = self.find_node_index_where(hash, number, is_descendent_of, predicate)?; + Ok(maybe_path.map(|path| { + let children = path + .iter() + .take(path.len() - 1) + .fold(&mut self.roots, |curr, &i| &mut curr[i].children); + &mut children[path[path.len() - 1]] + })) + } - // found the node, early exit - if let FindOutcome::Found(node) = node { - return Ok(Some(node)) + /// Same as [`find_node_where`](ForkTree::find_node_where), but returns indices. + /// + /// The returned indices represent the full path to reach the matching node starting + /// from first to last, i.e. the earliest index in the traverse path goes first, and the final + /// index in the traverse path goes last. If a node is found that matches the predicate + /// the returned path should always contain at least one index, otherwise `None` is + /// returned. + // WARNING: some users of this method (i.e. consensus epoch changes tree) currently silently + // rely on a **post-order DFS** traversal. If we are using instead a top-down traversal method + // then the `is_descendent_of` closure, when used after a warp-sync, will end up querying the + // backend for a block (the one corresponding to the root) that is not present and thus will + // return a wrong result. + pub fn find_node_index_where( + &self, + hash: &H, + number: &N, + is_descendent_of: &F, + predicate: &P, + ) -> Result>, Error> + where + E: std::error::Error, + F: Fn(&H, &H) -> Result, + P: Fn(&V) -> bool, + { + let mut stack = vec![]; + let mut root_idx = 0; + let mut found = false; + let mut is_descendent = false; + + while root_idx < self.roots.len() { + if *number <= self.roots[root_idx].number { + root_idx += 1; + continue } + // The second element in the stack tuple tracks what is the **next** children + // index to search into. If we find an ancestor then we stop searching into + // alternative branches and we focus on the current path up to the root. + stack.push((&self.roots[root_idx], 0)); + while let Some((node, i)) = stack.pop() { + if i < node.children.len() && !is_descendent { + stack.push((node, i + 1)); + if node.children[i].number < *number { + stack.push((&node.children[i], 0)); + } + } else if is_descendent || is_descendent_of(&node.hash, hash)? { + is_descendent = true; + if predicate(&node.data) { + found = true; + break + } + } + } + + // If the element we are looking for is a descendent of the current root + // then we can stop the search. + if is_descendent { + break + } + root_idx += 1; } - Ok(None) + Ok(if found { + // The path is the root index followed by the indices of all the children + // we were processing when we found the element (remember the stack + // contains the index of the **next** children to process). + let path: Vec<_> = + std::iter::once(root_idx).chain(stack.iter().map(|(_, i)| *i - 1)).collect(); + Some(path) + } else { + None + }) } - /// Same as [`find_node_where`](ForkTree::find_node_where), but returns indexes. - pub fn find_node_index_where( - &self, + /// Prune the tree, removing all non-canonical nodes. We find the node in the + /// tree that is the deepest ancestor of the given hash and that passes the + /// given predicate. If such a node exists, we re-root the tree to this + /// node. Otherwise the tree remains unchanged. The given function + /// `is_descendent_of` should return `true` if the second hash (target) is a + /// descendent of the first hash (base). + /// + /// Returns all pruned node data. + pub fn prune( + &mut self, hash: &H, number: &N, is_descendent_of: &F, predicate: &P, - ) -> Result>, Error> + ) -> Result, Error> where E: std::error::Error, F: Fn(&H, &H) -> Result, P: Fn(&V) -> bool, { - // search for node starting from all roots - for (index, root) in self.roots.iter().enumerate() { - let node = root.find_node_index_where(hash, number, is_descendent_of, predicate)?; - - // found the node, early exit - if let FindOutcome::Found(mut node) = node { - node.push(index); - return Ok(Some(node)) + let root_index = + match self.find_node_index_where(hash, number, is_descendent_of, predicate)? { + Some(idx) => idx, + None => return Ok(RemovedIterator { stack: Vec::new() }), + }; + + let mut old_roots = std::mem::take(&mut self.roots); + + let curr_children = root_index + .iter() + .take(root_index.len() - 1) + .fold(&mut old_roots, |curr, idx| &mut curr[*idx].children); + let mut root = curr_children.remove(root_index[root_index.len() - 1]); + + let mut removed = old_roots; + + // we found the deepest ancestor of the finalized block, so we prune + // out any children that don't include the finalized block. + let root_children = std::mem::take(&mut root.children); + let mut is_first = true; + + for child in root_children { + if is_first && + (child.number == *number && child.hash == *hash || + child.number < *number && is_descendent_of(&child.hash, hash)?) + { + root.children.push(child); + // assuming that the tree is well formed only one child should pass this + // requirement due to ancestry restrictions (i.e. they must be different forks). + is_first = false; + } else { + removed.push(child); } } - Ok(None) + self.roots = vec![root]; + self.rebalance(); + + Ok(RemovedIterator { stack: removed }) } /// Finalize a root in the tree and return it, return `None` in case no root @@ -363,7 +427,7 @@ where let node = self.roots.swap_remove(position); self.roots = node.children; self.best_finalized_number = Some(node.number); - return node.data + node.data } /// Finalize a node in the tree. This method will make sure that the node @@ -498,14 +562,14 @@ where } /// Checks if any node in the tree is finalized by either finalizing the - /// node itself or a child node that's not in the tree, guaranteeing that - /// the node being finalized isn't a descendent of any of the node's - /// children. Returns `Some(true)` if the node being finalized is a root, - /// `Some(false)` if the node being finalized is not a root, and `None` if - /// no node in the tree is finalized. The given `predicate` is checked on - /// the prospective finalized root and must pass for finalization to occur. - /// The given function `is_descendent_of` should return `true` if the second - /// hash (target) is a descendent of the first hash (base). + /// node itself or a node's descendent that's not in the tree, guaranteeing + /// that the node being finalized isn't a descendent of (or equal to) any of + /// the node's children. Returns `Some(true)` if the node being finalized is + /// a root, `Some(false)` if the node being finalized is not a root, and + /// `None` if no node in the tree is finalized. The given `predicate` is + /// checked on the prospective finalized root and must pass for finalization + /// to occur. The given function `is_descendent_of` should return `true` if + /// the second hash (target) is a descendent of the first hash (base). pub fn finalizes_any_with_descendent_if( &self, hash: &H, @@ -528,16 +592,17 @@ where // tree, if we find a valid node that passes the predicate then we must // ensure that we're not finalizing past any of its child nodes. for node in self.node_iter() { - if predicate(&node.data) { - if node.hash == *hash || is_descendent_of(&node.hash, hash)? { - for node in node.children.iter() { - if node.number <= number && is_descendent_of(&node.hash, &hash)? { - return Err(Error::UnfinalizedAncestor) - } + if predicate(&node.data) && (node.hash == *hash || is_descendent_of(&node.hash, hash)?) + { + for child in node.children.iter() { + if child.number <= number && + (child.hash == *hash || is_descendent_of(&child.hash, hash)?) + { + return Err(Error::UnfinalizedAncestor) } - - return Ok(Some(self.roots.iter().any(|root| root.hash == node.hash))) } + + return Ok(Some(self.roots.iter().any(|root| root.hash == node.hash))) } } @@ -545,12 +610,12 @@ where } /// Finalize a root in the tree by either finalizing the node itself or a - /// child node that's not in the tree, guaranteeing that the node being - /// finalized isn't a descendent of any of the root's children. The given - /// `predicate` is checked on the prospective finalized root and must pass for - /// finalization to occur. The given function `is_descendent_of` should - /// return `true` if the second hash (target) is a descendent of the first - /// hash (base). + /// node's descendent that's not in the tree, guaranteeing that the node + /// being finalized isn't a descendent of (or equal to) any of the root's + /// children. The given `predicate` is checked on the prospective finalized + /// root and must pass for finalization to occur. The given function + /// `is_descendent_of` should return `true` if the second hash (target) is a + /// descendent of the first hash (base). pub fn finalize_with_descendent_if( &mut self, hash: &H, @@ -574,17 +639,18 @@ where // we're not finalizing past any children node. let mut position = None; for (i, root) in self.roots.iter().enumerate() { - if predicate(&root.data) { - if root.hash == *hash || is_descendent_of(&root.hash, hash)? { - for node in root.children.iter() { - if node.number <= number && is_descendent_of(&node.hash, &hash)? { - return Err(Error::UnfinalizedAncestor) - } + if predicate(&root.data) && (root.hash == *hash || is_descendent_of(&root.hash, hash)?) + { + for child in root.children.iter() { + if child.number <= number && + (child.hash == *hash || is_descendent_of(&child.hash, hash)?) + { + return Err(Error::UnfinalizedAncestor) } - - position = Some(i); - break } + + position = Some(i); + break } } @@ -595,12 +661,11 @@ where node.data }); - // if the block being finalized is earlier than a given root, then it - // must be its ancestor, otherwise we can prune the root. if there's a - // root at the same height then the hashes must match. otherwise the - // node being finalized is higher than the root so it must be its - // descendent (in this case the node wasn't finalized earlier presumably - // because the predicate didn't pass). + // Retain only roots that are descendents of the finalized block (this + // happens if the node has been properly finalized) or that are + // ancestors (or equal) to the finalized block (in this case the node + // wasn't finalized earlier presumably because the predicate didn't + // pass). let mut changed = false; let roots = std::mem::take(&mut self.roots); @@ -624,23 +689,73 @@ where (None, false) => Ok(FinalizationResult::Unchanged), } } + + /// Remove from the tree some nodes (and their subtrees) using a `filter` predicate. + /// + /// The `filter` is called over tree nodes and returns a filter action: + /// - `Remove` if the node and its subtree should be removed; + /// - `KeepNode` if we should maintain the node and keep processing the tree. + /// - `KeepTree` if we should maintain the node and its entire subtree. + /// + /// An iterator over all the pruned nodes is returned. + pub fn drain_filter(&mut self, filter: F) -> impl Iterator + where + F: Fn(&H, &N, &V) -> FilterAction, + { + let mut removed = vec![]; + let mut retained = Vec::new(); + + let mut queue: Vec<_> = std::mem::take(&mut self.roots) + .into_iter() + .rev() + .map(|node| (usize::MAX, node)) + .collect(); + let mut next_queue = Vec::new(); + + while !queue.is_empty() { + for (parent_idx, mut node) in queue.drain(..) { + match filter(&node.hash, &node.number, &node.data) { + FilterAction::KeepNode => { + let node_idx = retained.len(); + let children = std::mem::take(&mut node.children); + retained.push((parent_idx, node)); + for child in children.into_iter().rev() { + next_queue.push((node_idx, child)); + } + }, + FilterAction::KeepTree => { + retained.push((parent_idx, node)); + }, + FilterAction::Remove => { + removed.push(node); + }, + } + } + + std::mem::swap(&mut queue, &mut next_queue); + } + + while let Some((parent_idx, node)) = retained.pop() { + if parent_idx == usize::MAX { + self.roots.push(node); + } else { + retained[parent_idx].1.children.push(node); + } + } + + if !removed.is_empty() { + self.rebalance(); + } + RemovedIterator { stack: removed } + } } // Workaround for: https://github.com/rust-lang/rust/issues/34537 +use node_implementation::Node; + mod node_implementation { use super::*; - /// The outcome of a search within a node. - pub enum FindOutcome { - // this is the node we were looking for. - Found(T), - // not the node we're looking for. contains a flag indicating - // whether the node was a descendent. true implies the predicate failed. - Failure(bool), - // Abort search. - Abort, - } - #[derive(Clone, Debug, Decode, Encode, PartialEq)] pub struct Node { pub hash: H, @@ -650,211 +765,21 @@ mod node_implementation { } impl Node { - /// Rebalance the tree, i.e. sort child nodes by max branch depth (decreasing). - pub fn rebalance(&mut self) { - self.children.sort_by_key(|n| Reverse(n.max_depth())); - for child in &mut self.children { - child.rebalance(); - } - } - /// Finds the max depth among all branches descendent from this node. pub fn max_depth(&self) -> usize { - let mut max = 0; - - for node in &self.children { - max = node.max_depth().max(max) - } - - max + 1 - } - - /// Map node data into values of new types. - pub fn map(self, f: &mut F) -> Node - where - F: FnMut(&H, &N, V) -> VT, - { - let children = self.children.into_iter().map(|node| node.map(f)).collect(); - - let vt = f(&self.hash, &self.number, self.data); - Node { hash: self.hash, number: self.number, data: vt, children } - } - - pub fn import( - &mut self, - mut hash: H, - mut number: N, - mut data: V, - is_descendent_of: &F, - ) -> Result, Error> - where - E: fmt::Debug, - F: Fn(&H, &H) -> Result, - { - if self.hash == hash { - return Err(Error::Duplicate) - }; - - if number <= self.number { - return Ok(Some((hash, number, data))) - } - - for node in self.children.iter_mut() { - match node.import(hash, number, data, is_descendent_of)? { - Some((h, n, d)) => { - hash = h; - number = n; - data = d; - }, - None => return Ok(None), - } - } - - if is_descendent_of(&self.hash, &hash)? { - self.children.push(Node { data, hash, number, children: Vec::new() }); - - Ok(None) - } else { - Ok(Some((hash, number, data))) - } - } - - /// Find a node in the tree that is the deepest ancestor of the given - /// block hash which also passes the given predicate, backtracking - /// when the predicate fails. - /// The given function `is_descendent_of` should return `true` if the second hash (target) - /// is a descendent of the first hash (base). - /// - /// The returned indices are from last to first. The earliest index in the traverse path - /// goes last, and the final index in the traverse path goes first. An empty list means - /// that the current node is the result. - pub fn find_node_index_where( - &self, - hash: &H, - number: &N, - is_descendent_of: &F, - predicate: &P, - ) -> Result>, Error> - where - E: std::error::Error, - F: Fn(&H, &H) -> Result, - P: Fn(&V) -> bool, - { - // stop searching this branch - if *number < self.number { - return Ok(FindOutcome::Failure(false)) - } - - let mut known_descendent_of = false; - - // continue depth-first search through all children - for (i, node) in self.children.iter().enumerate() { - // found node, early exit - match node.find_node_index_where(hash, number, is_descendent_of, predicate)? { - FindOutcome::Abort => return Ok(FindOutcome::Abort), - FindOutcome::Found(mut x) => { - x.push(i); - return Ok(FindOutcome::Found(x)) - }, - FindOutcome::Failure(true) => { - // if the block was a descendent of this child, - // then it cannot be a descendent of any others, - // so we don't search them. - known_descendent_of = true; - break - }, - FindOutcome::Failure(false) => {}, + let mut max: usize = 0; + let mut stack = vec![(self, 0)]; + while let Some((node, height)) = stack.pop() { + if height > max { + max = height; } + node.children.iter().for_each(|n| stack.push((n, height + 1))); } - - // node not found in any of the descendents, if the node we're - // searching for is a descendent of this node then we will stop the - // search here, since there aren't any more children and we found - // the correct node so we don't want to backtrack. - let is_descendent_of = known_descendent_of || is_descendent_of(&self.hash, hash)?; - if is_descendent_of { - // if the predicate passes we return the node - if predicate(&self.data) { - return Ok(FindOutcome::Found(Vec::new())) - } - } - - // otherwise, tell our ancestor that we failed, and whether - // the block was a descendent. - Ok(FindOutcome::Failure(is_descendent_of)) - } - - /// Find a node in the tree that is the deepest ancestor of the given - /// block hash which also passes the given predicate, backtracking - /// when the predicate fails. - /// The given function `is_descendent_of` should return `true` if the second hash (target) - /// is a descendent of the first hash (base). - pub fn find_node_where( - &self, - hash: &H, - number: &N, - is_descendent_of: &F, - predicate: &P, - ) -> Result>, Error> - where - E: std::error::Error, - F: Fn(&H, &H) -> Result, - P: Fn(&V) -> bool, - { - let outcome = self.find_node_index_where(hash, number, is_descendent_of, predicate)?; - - match outcome { - FindOutcome::Abort => Ok(FindOutcome::Abort), - FindOutcome::Failure(f) => Ok(FindOutcome::Failure(f)), - FindOutcome::Found(mut indexes) => { - let mut cur = self; - - while let Some(i) = indexes.pop() { - cur = &cur.children[i]; - } - Ok(FindOutcome::Found(cur)) - }, - } - } - - /// Find a node in the tree that is the deepest ancestor of the given - /// block hash which also passes the given predicate, backtracking - /// when the predicate fails. - /// The given function `is_descendent_of` should return `true` if the second hash (target) - /// is a descendent of the first hash (base). - pub fn find_node_where_mut( - &mut self, - hash: &H, - number: &N, - is_descendent_of: &F, - predicate: &P, - ) -> Result>, Error> - where - E: std::error::Error, - F: Fn(&H, &H) -> Result, - P: Fn(&V) -> bool, - { - let outcome = self.find_node_index_where(hash, number, is_descendent_of, predicate)?; - - match outcome { - FindOutcome::Abort => Ok(FindOutcome::Abort), - FindOutcome::Failure(f) => Ok(FindOutcome::Failure(f)), - FindOutcome::Found(mut indexes) => { - let mut cur = self; - - while let Some(i) = indexes.pop() { - cur = &mut cur.children[i]; - } - Ok(FindOutcome::Found(cur)) - }, - } + max } } } -// Workaround for: https://github.com/rust-lang/rust/issues/34537 -use node_implementation::{FindOutcome, Node}; - struct ForkTreeIterator<'a, H, N, V> { stack: Vec<&'a Node>, } @@ -895,6 +820,8 @@ impl Iterator for RemovedIterator { #[cfg(test)] mod test { + use crate::FilterAction; + use super::{Error, FinalizationResult, ForkTree}; #[derive(Debug, PartialEq)] @@ -919,11 +846,11 @@ mod test { // / - G // / / // A - F - H - I - // \ - // - L - M \ - // - O - // \ - // — J - K + // \ \ + // \ - L - M - N + // \ \ + // \ - O + // - J - K // // (where N is not a part of fork tree) // @@ -931,7 +858,7 @@ mod test { // diagram above. the children will be ordered by subtree depth and the longest branches // will be on the leftmost side of the tree. let is_descendent_of = |base: &&str, block: &&str| -> Result { - let letters = vec!["B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "O"]; + let letters = vec!["B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"]; match (*base, *block) { ("A", b) => Ok(letters.into_iter().any(|n| n == b)), ("B", b) => Ok(b == "C" || b == "D" || b == "E"), @@ -939,14 +866,14 @@ mod test { ("D", b) => Ok(b == "E"), ("E", _) => Ok(false), ("F", b) => - Ok(b == "G" || b == "H" || b == "I" || b == "L" || b == "M" || b == "O"), + Ok(b == "G" || b == "H" || b == "I" || b == "L" || b == "M" || b == "N" || b == "O"), ("G", _) => Ok(false), - ("H", b) => Ok(b == "I" || b == "L" || b == "M" || b == "O"), + ("H", b) => Ok(b == "I" || b == "L" || b == "M" || b == "N" || b == "O"), ("I", _) => Ok(false), ("J", b) => Ok(b == "K"), ("K", _) => Ok(false), - ("L", b) => Ok(b == "M" || b == "O"), - ("M", _) => Ok(false), + ("L", b) => Ok(b == "M" || b == "O" || b == "N"), + ("M", b) => Ok(b == "N"), ("O", _) => Ok(false), ("0", _) => Ok(true), _ => Ok(false), @@ -1004,16 +931,13 @@ mod test { let finalize_a = || { let (mut tree, ..) = test_fork_tree(); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![("A", 1)], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("A", 1)]); // finalizing "A" opens up three possible forks tree.finalize_root(&"A"); assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("B", 2), ("F", 2), ("J", 2)], ); @@ -1026,10 +950,7 @@ mod test { // finalizing "B" will progress on its fork and remove any other competing forks tree.finalize_root(&"B"); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![("C", 3)], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("C", 3)],); // all the other forks have been pruned assert!(tree.roots.len() == 1); @@ -1041,10 +962,7 @@ mod test { // finalizing "J" will progress on its fork and remove any other competing forks tree.finalize_root(&"J"); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![("K", 3)], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("K", 3)],); // all the other forks have been pruned assert!(tree.roots.len() == 1); @@ -1069,7 +987,7 @@ mod test { ); assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("B", 2), ("F", 2), ("J", 2)], ); @@ -1093,7 +1011,7 @@ mod test { ); assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("L", 4), ("I", 4)], ); @@ -1127,7 +1045,7 @@ mod test { ); assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("B", 2), ("F", 2), ("J", 2)], ); @@ -1141,7 +1059,7 @@ mod test { ); assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("L", 4), ("I", 4)], ); @@ -1157,10 +1075,7 @@ mod test { Ok(FinalizationResult::Changed(None)), ); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![],); assert_eq!(tree.best_finalized_number, Some(6)); } @@ -1184,7 +1099,7 @@ mod test { // // Nodes B, C, F and G are not part of the tree. match (*base, *block) { - ("A0", b) => Ok(b == "B" || b == "C" || b == "D" || b == "G"), + ("A0", b) => Ok(b == "B" || b == "C" || b == "D" || b == "E" || b == "G"), ("A1", _) => Ok(false), ("C", b) => Ok(b == "D"), ("D", b) => Ok(b == "E" || b == "F" || b == "G"), @@ -1193,10 +1108,16 @@ mod test { } }; - tree.import("A0", 1, Change { effective: 5 }, &is_descendent_of).unwrap(); - tree.import("A1", 1, Change { effective: 5 }, &is_descendent_of).unwrap(); - tree.import("D", 10, Change { effective: 10 }, &is_descendent_of).unwrap(); - tree.import("E", 15, Change { effective: 50 }, &is_descendent_of).unwrap(); + let is_root = tree.import("A0", 1, Change { effective: 5 }, &is_descendent_of).unwrap(); + assert!(is_root); + let is_root = tree.import("A1", 1, Change { effective: 5 }, &is_descendent_of).unwrap(); + assert!(is_root); + let is_root = + tree.import("D", 10, Change { effective: 10 }, &is_descendent_of).unwrap(); + assert!(!is_root); + let is_root = + tree.import("E", 15, Change { effective: 50 }, &is_descendent_of).unwrap(); + assert!(!is_root); (tree, is_descendent_of) }; @@ -1211,25 +1132,35 @@ mod test { Ok(None), ); + // finalizing "D" is not allowed since it is not a root. + assert_eq!( + tree.finalize_with_descendent_if(&"D", 10, &is_descendent_of, |c| c.effective <= 10), + Err(Error::UnfinalizedAncestor) + ); + // finalizing "D" will finalize a block from the tree, but it can't be applied yet - // since it is not a root change + // since it is not a root change. assert_eq!( tree.finalizes_any_with_descendent_if(&"D", 10, &is_descendent_of, |c| c.effective == - 10,), + 10), Ok(Some(false)), ); + // finalizing "E" is not allowed since there are not finalized anchestors. + assert_eq!( + tree.finalizes_any_with_descendent_if(&"E", 15, &is_descendent_of, |c| c.effective == + 10), + Err(Error::UnfinalizedAncestor) + ); + // finalizing "B" doesn't finalize "A0" since the predicate doesn't pass, // although it will clear out "A1" from the tree assert_eq!( - tree.finalize_with_descendent_if(&"B", 2, &is_descendent_of, |c| c.effective <= 2,), + tree.finalize_with_descendent_if(&"B", 2, &is_descendent_of, |c| c.effective <= 2), Ok(FinalizationResult::Changed(None)), ); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![("A0", 1)], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("A0", 1)],); // finalizing "C" will finalize the node "A0" and prune it out of the tree assert_eq!( @@ -1243,14 +1174,11 @@ mod test { ); assert_eq!( - tree.finalize_with_descendent_if(&"C", 5, &is_descendent_of, |c| c.effective <= 5,), + tree.finalize_with_descendent_if(&"C", 5, &is_descendent_of, |c| c.effective <= 5), Ok(FinalizationResult::Changed(Some(Change { effective: 5 }))), ); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![("D", 10)], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("D", 10)],); // finalizing "F" will fail since it would finalize past "E" without finalizing "D" first assert_eq!( @@ -1262,12 +1190,12 @@ mod test { // it will work with "G" though since it is not in the same branch as "E" assert_eq!( tree.finalizes_any_with_descendent_if(&"G", 100, &is_descendent_of, |c| c.effective <= - 100,), + 100), Ok(Some(true)), ); assert_eq!( - tree.finalize_with_descendent_if(&"G", 100, &is_descendent_of, |c| c.effective <= 100,), + tree.finalize_with_descendent_if(&"G", 100, &is_descendent_of, |c| c.effective <= 100), Ok(FinalizationResult::Changed(Some(Change { effective: 10 }))), ); @@ -1279,7 +1207,7 @@ mod test { fn iter_iterates_in_preorder() { let (tree, ..) = test_fork_tree(); assert_eq!( - tree.iter().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.iter().map(|(h, n, _)| (*h, *n)).collect::>(), vec![ ("A", 1), ("B", 2), @@ -1356,20 +1284,25 @@ mod test { } #[test] - fn find_node_works() { - let (tree, is_descendent_of) = test_fork_tree(); + fn map_works() { + let (mut tree, _) = test_fork_tree(); - let node = tree.find_node_where(&"D", &4, &is_descendent_of, &|_| true).unwrap().unwrap(); + // Extend the single root fork-tree to also excercise the roots order during map. + let is_descendent_of = |_: &&str, _: &&str| -> Result { Ok(false) }; + let is_root = tree.import("A1", 1, (), &is_descendent_of).unwrap(); + assert!(is_root); + let is_root = tree.import("A2", 1, (), &is_descendent_of).unwrap(); + assert!(is_root); - assert_eq!(node.hash, "C"); - assert_eq!(node.number, 3); - } + let old_tree = tree.clone(); + let new_tree = tree.map(&mut |hash, _, _| hash.to_owned()); - #[test] - fn map_works() { - let (tree, _is_descendent_of) = test_fork_tree(); - - let _tree = tree.map(&mut |_, _, _| ()); + // Check content and order + assert!(new_tree.iter().all(|(hash, _, data)| hash == data)); + assert_eq!( + old_tree.iter().map(|(hash, _, _)| *hash).collect::>(), + new_tree.iter().map(|(hash, _, _)| *hash).collect::>(), + ); } #[test] @@ -1429,7 +1362,7 @@ mod test { } #[test] - fn tree_rebalance() { + fn rebalance_works() { let (mut tree, _) = test_fork_tree(); // the tree is automatically rebalanced on import, therefore we should iterate in preorder @@ -1443,7 +1376,7 @@ mod test { // let's add a block "P" which is a descendent of block "O" let is_descendent_of = |base: &&str, block: &&str| -> Result { match (*base, *block) { - (b, "P") => Ok(vec!["A", "F", "L", "O"].into_iter().any(|n| n == b)), + (b, "P") => Ok(vec!["A", "F", "H", "L", "O"].into_iter().any(|n| n == b)), _ => Ok(false), } }; @@ -1458,4 +1391,142 @@ mod test { ["A", "F", "H", "L", "O", "P", "M", "I", "G", "B", "C", "D", "E", "J", "K"] ); } + + #[test] + fn drain_filter_works() { + let (mut tree, _) = test_fork_tree(); + + let filter = |h: &&str, _: &u64, _: &()| match *h { + "A" | "B" | "F" | "G" => FilterAction::KeepNode, + "C" => FilterAction::KeepTree, + "H" | "J" => FilterAction::Remove, + _ => panic!("Unexpected filtering for node: {}", *h), + }; + + let removed = tree.drain_filter(filter); + + assert_eq!( + tree.iter().map(|(h, _, _)| *h).collect::>(), + ["A", "B", "C", "D", "E", "F", "G"] + ); + + assert_eq!( + removed.map(|(h, _, _)| h).collect::>(), + ["H", "L", "M", "O", "I", "J", "K"] + ); + } + + #[test] + fn find_node_index_works() { + let (tree, is_descendent_of) = test_fork_tree(); + + let path = tree + .find_node_index_where(&"D", &4, &is_descendent_of, &|_| true) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 0, 0]); + + let path = tree + .find_node_index_where(&"O", &5, &is_descendent_of, &|_| true) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 1, 0, 0]); + + let path = tree + .find_node_index_where(&"N", &6, &is_descendent_of, &|_| true) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 1, 0, 0, 0]); + } + + #[test] + fn find_node_index_with_predicate_works() { + let is_descendent_of = |parent: &char, child: &char| match *parent { + 'A' => Ok(['B', 'C', 'D', 'E', 'F'].contains(child)), + 'B' => Ok(['C', 'D'].contains(child)), + 'C' => Ok(['D'].contains(child)), + 'E' => Ok(['F'].contains(child)), + 'D' | 'F' => Ok(false), + _ => Err(TestError), + }; + + // A(t) --- B(f) --- C(t) --- D(f) + // \-- E(t) --- F(f) + let mut tree: ForkTree = ForkTree::new(); + tree.import('A', 1, true, &is_descendent_of).unwrap(); + tree.import('B', 2, false, &is_descendent_of).unwrap(); + tree.import('C', 3, true, &is_descendent_of).unwrap(); + tree.import('D', 4, false, &is_descendent_of).unwrap(); + + tree.import('E', 2, true, &is_descendent_of).unwrap(); + tree.import('F', 3, false, &is_descendent_of).unwrap(); + + let path = tree + .find_node_index_where(&'D', &4, &is_descendent_of, &|&value| !value) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 0]); + + let path = tree + .find_node_index_where(&'D', &4, &is_descendent_of, &|&value| value) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 0, 0]); + + let path = tree + .find_node_index_where(&'F', &3, &is_descendent_of, &|&value| !value) + .unwrap(); + assert_eq!(path, None); + + let path = tree + .find_node_index_where(&'F', &3, &is_descendent_of, &|&value| value) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 1]); + } + + #[test] + fn find_node_works() { + let (tree, is_descendent_of) = test_fork_tree(); + + let node = tree.find_node_where(&"B", &2, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("A", 1)); + + let node = tree.find_node_where(&"D", &4, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("C", 3)); + + let node = tree.find_node_where(&"O", &5, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("L", 4)); + + let node = tree.find_node_where(&"N", &6, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("M", 5)); + } + + #[test] + fn post_order_traversal_requirement() { + let (mut tree, is_descendent_of) = test_fork_tree(); + + // Test for the post-order DFS traversal requirement as specified by the + // `find_node_index_where` and `import` comments. + let is_descendent_of_for_post_order = |parent: &&str, child: &&str| match *parent { + "A" => Err(TestError), + "K" if *child == "Z" => Ok(true), + _ => is_descendent_of(parent, child), + }; + + // Post order traversal requirement for `find_node_index_where` + let path = tree + .find_node_index_where(&"N", &6, &is_descendent_of_for_post_order, &|_| true) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 1, 0, 0, 0]); + + // Post order traversal requirement for `import` + let res = tree.import(&"Z", 100, (), &is_descendent_of_for_post_order); + assert_eq!(res, Ok(false)); + assert_eq!( + tree.iter().map(|node| *node.0).collect::>(), + vec!["A", "B", "C", "D", "E", "F", "H", "L", "M", "O", "I", "G", "J", "K", "Z"], + ); + } } diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 93616b590f61e..5173340f7d0d9 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -2,9 +2,9 @@ name = "frame-benchmarking-cli" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "CLI for benchmarking FRAME" readme = "README.md" @@ -13,26 +13,53 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } -frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service" } -sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } -sc-client-db = { version = "0.10.0-dev", path = "../../../client/db" } -sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } -sp-externalities = { version = "0.10.0-dev", path = "../../../primitives/externalities" } -sp-keystore = { version = "0.10.0-dev", path = "../../../primitives/keystore" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.10.0-dev", path = "../../../primitives/state-machine" } -codec = { version = "2.0.0", package = "parity-scale-codec" } -structopt = "0.3.8" +array-bytes = "4.1" chrono = "0.4" -serde = "1.0.126" -handlebars = "3.5.0" +clap = { version = "4.0.9", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0" } +comfy-table = { version = "6.0.0", default-features = false } +handlebars = "4.2.2" +hash-db = "0.15.2" Inflector = "0.11.4" +itertools = "0.10.3" +kvdb = "0.13.0" +lazy_static = "1.4.0" linked-hash-map = "0.5.4" -log = "0.4.8" +log = "0.4.17" +memory-db = "0.31.0" +rand = { version = "0.8.4", features = ["small_rng"] } +rand_pcg = "0.3.1" +serde = "1.0.136" +serde_json = "1.0.85" +serde_nanos = "0.1.2" +tempfile = "3.2.0" +thiserror = "1.0.30" +thousands = "0.2.0" +frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } +frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } +frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } +sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } +sc-cli = { version = "0.10.0-dev", default-features = false, path = "../../../client/cli" } +sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } +sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../../../client/db" } +sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } +sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service" } +sc-sysinfo = { version = "6.0.0-dev", path = "../../../client/sysinfo" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-database = { version = "4.0.0-dev", path = "../../../primitives/database" } +sp-externalities = { version = "0.13.0", path = "../../../primitives/externalities" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } +sp-std = { version = "5.0.0", path = "../../../primitives/std" } +sp-storage = { version = "7.0.0", path = "../../../primitives/storage" } +sp-trie = { version = "7.0.0", path = "../../../primitives/trie" } +gethostname = "0.2.3" [features] -default = ["db"] -db = ["sc-client-db/with-kvdb-rocksdb", "sc-client-db/with-parity-db"] +default = ["rocksdb"] +runtime-benchmarks = ["sc-client-db/runtime-benchmarks"] +rocksdb = ["sc-cli/rocksdb", "sc-client-db/rocksdb"] diff --git a/utils/frame/benchmarking-cli/README.md b/utils/frame/benchmarking-cli/README.md index 9718db58b37e9..e6a48b61fd227 100644 --- a/utils/frame/benchmarking-cli/README.md +++ b/utils/frame/benchmarking-cli/README.md @@ -1 +1,46 @@ -License: Apache-2.0 \ No newline at end of file +# The Benchmarking CLI + +This crate contains commands to benchmark various aspects of Substrate and the hardware. +All commands are exposed by the Substrate node but can be exposed by any Substrate client. +The goal is to have a comprehensive suite of benchmarks that cover all aspects of Substrate and the hardware that its running on. + +Invoking the root benchmark command prints a help menu: +```sh +$ cargo run --profile=production -- benchmark + +Sub-commands concerned with benchmarking. + +USAGE: + substrate benchmark + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +SUBCOMMANDS: + block Benchmark the execution time of historic blocks + machine Command to benchmark the hardware. + overhead Benchmark the execution overhead per-block and per-extrinsic + pallet Benchmark the extrinsic weight of FRAME Pallets + storage Benchmark the storage speed of a chain snapshot +``` + +All examples use the `production` profile for correctness which makes the compilation *very* slow; for testing you can use `--release`. +For the final results the `production` profile and reference hardware should be used, otherwise the results are not comparable. + +The sub-commands are explained in depth here: +- [block] Compare the weight of a historic block to its actual resource usage +- [machine] Gauges the speed of the hardware +- [overhead] Creates weight files for the *Block*- and *Extrinsic*-base weights +- [pallet] Creates weight files for a Pallet +- [storage] Creates weight files for *Read* and *Write* storage operations + +License: Apache-2.0 + + + +[pallet]: ../../../frame/benchmarking/README.md +[machine]: src/machine/README.md +[storage]: src/storage/README.md +[overhead]: src/overhead/README.md +[block]: src/block/README.md diff --git a/utils/frame/benchmarking-cli/build.rs b/utils/frame/benchmarking-cli/build.rs new file mode 100644 index 0000000000000..4347804156815 --- /dev/null +++ b/utils/frame/benchmarking-cli/build.rs @@ -0,0 +1,31 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 std::env; + +/// Exposes build environment variables to the rust code. +/// +/// - The build profile as `build_profile` +/// - The optimization level as `build_opt_level` +pub fn main() { + if let Ok(opt_level) = env::var("OPT_LEVEL") { + println!("cargo:rustc-cfg=build_opt_level={:?}", opt_level); + } + if let Ok(profile) = env::var("PROFILE") { + println!("cargo:rustc-cfg=build_profile={:?}", profile); + } +} diff --git a/utils/frame/benchmarking-cli/src/block/README.md b/utils/frame/benchmarking-cli/src/block/README.md new file mode 100644 index 0000000000000..7e99f0df9d43b --- /dev/null +++ b/utils/frame/benchmarking-cli/src/block/README.md @@ -0,0 +1,118 @@ +# The `benchmark block` command + +The whole benchmarking process in Substrate aims to predict the resource usage of an unexecuted block. +This command measures how accurate this prediction was by executing a block and comparing the predicted weight to its actual resource usage. +It can be used to measure the accuracy of the pallet benchmarking. + +In the following it will be explained once for Polkadot and once for Substrate. + +## Polkadot # 1 +(Also works for Kusama, Westend and Rococo) + + +Suppose you either have a synced Polkadot node or downloaded a snapshot from [Polkachu]. +This example uses a pruned ParityDB snapshot from the 2022-4-19 with the last block being 9939462. +For pruned snapshots you need to know the number of the last block (to be improved [here]). +Pruned snapshots normally store the last 256 blocks, archive nodes can use any block range. + +In this example we will benchmark just the last 10 blocks: +```sh +cargo run --profile=production -- benchmark block --from 9939453 --to 9939462 --db paritydb +``` + +Output: +```pre +Block 9939453 with 2 tx used 4.57% of its weight ( 26,458,801 of 579,047,053 ns) +Block 9939454 with 3 tx used 4.80% of its weight ( 28,335,826 of 590,414,831 ns) +Block 9939455 with 2 tx used 4.76% of its weight ( 27,889,567 of 586,484,595 ns) +Block 9939456 with 2 tx used 4.65% of its weight ( 27,101,306 of 582,789,723 ns) +Block 9939457 with 2 tx used 4.62% of its weight ( 26,908,882 of 582,789,723 ns) +Block 9939458 with 2 tx used 4.78% of its weight ( 28,211,440 of 590,179,467 ns) +Block 9939459 with 4 tx used 4.78% of its weight ( 27,866,077 of 583,260,451 ns) +Block 9939460 with 3 tx used 4.72% of its weight ( 27,845,836 of 590,462,629 ns) +Block 9939461 with 2 tx used 4.58% of its weight ( 26,685,119 of 582,789,723 ns) +Block 9939462 with 2 tx used 4.60% of its weight ( 26,840,938 of 583,697,101 ns) +``` + +### Output Interpretation + +(Only results from reference hardware are relevant) + +Each block is executed multiple times and the results are averaged. +The percent number is the interesting part and indicates how much weight was used as compared to how much was predicted. +The closer to 100% this is without exceeding 100%, the better. +If it exceeds 100%, the block is marked with "**OVER WEIGHT!**" to easier spot them. This is not good since then the benchmarking under-estimated the weight. +This would mean that an honest validator would possibly not be able to keep up with importing blocks since users did not pay for enough weight. +If that happens the validator could lag behind the chain and get slashed for missing deadlines. +It is therefore important to investigate any overweight blocks. + +In this example you can see an unexpected result; only < 5% of the weight was used! +The measured blocks can be executed much faster than predicted. +This means that the benchmarking process massively over-estimated the execution time. +Since they are off by so much, it is an issue [polkadot#5192]. + +The ideal range for these results would be 85-100%. + +## Polkadot # 2 + +Let's take a more interesting example where the blocks use more of their predicted weight. +Every day when validators pay out rewards, the blocks are nearly full. +Using an archive node here is the easiest. + +The Polkadot blocks TODO-TODO for example contain large batch transactions for staking payout. + +```sh +cargo run --profile=production -- benchmark block --from TODO --to TODO --db paritydb +``` + +```pre +TODO +``` + +## Substrate + +It is also possible to try the procedure in Substrate, although it's a bit boring. + +First you need to create some blocks with either a local or dev chain. +This example will use the standard development spec. +Pick a non existing directory where the chain data will be stored, eg `/tmp/dev`. +```sh +cargo run --profile=production -- --dev -d /tmp/dev +``` +You should see after some seconds that it started to produce blocks: +```pre +… +✨ Imported #1 (0x801d…9189) +… +``` +You can now kill the node with `Ctrl+C`. Then measure how long it takes to execute these blocks: +```sh +cargo run --profile=production -- benchmark block --from 1 --to 1 --dev -d /tmp/dev --pruning archive +``` +This will benchmark the first block. If you killed the node at a later point, you can measure multiple blocks. +```pre +Block 1 with 1 tx used 72.04% of its weight ( 4,945,664 of 6,864,702 ns) +``` + +In this example the block used ~72% of its weight. +The benchmarking therefore over-estimated the effort to execute the block. +Since this block is empty, its not very interesting. + +## Arguments + +- `--from` Number of the first block to measure (inclusive). +- `--to` Number of the last block to measure (inclusive). +- `--repeat` How often each block should be measured. +- [`--db`] +- [`--pruning`] + +License: Apache-2.0 + + + +[Polkachu]: https://polkachu.com/snapshots +[here]: https://github.com/paritytech/substrate/issues/11141 +[polkadot#5192]: https://github.com/paritytech/polkadot/issues/5192 + +[`--db`]: ../shared/README.md#arguments +[`--pruning`]: ../shared/README.md#arguments diff --git a/utils/frame/benchmarking-cli/src/block/bench.rs b/utils/frame/benchmarking-cli/src/block/bench.rs new file mode 100644 index 0000000000000..578158d8a2356 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/block/bench.rs @@ -0,0 +1,183 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Contains the core benchmarking logic. + +use codec::DecodeAll; +use frame_support::weights::constants::WEIGHT_REF_TIME_PER_NANOS; +use frame_system::ConsumedWeight; +use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; +use sc_cli::{Error, Result}; +use sc_client_api::{ + Backend as ClientBackend, BlockBackend, HeaderBackend, StorageProvider, UsageProvider, +}; +use sp_api::{ApiExt, Core, HeaderT, ProvideRuntimeApi}; +use sp_blockchain::Error::RuntimeApiError; +use sp_runtime::{generic::BlockId, traits::Block as BlockT, DigestItem, OpaqueExtrinsic}; +use sp_storage::StorageKey; + +use clap::Args; +use log::{info, warn}; +use serde::Serialize; +use std::{fmt::Debug, marker::PhantomData, sync::Arc, time::Instant}; +use thousands::Separable; + +use crate::shared::{StatSelect, Stats}; + +/// Log target for printing block weight info. +const LOG_TARGET: &'static str = "benchmark::block::weight"; + +/// Parameters for modifying the benchmark behaviour. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct BenchmarkParams { + /// Number of the first block to consider. + #[arg(long)] + pub from: u32, + + /// Last block number to consider. + #[arg(long)] + pub to: u32, + + /// Number of times that the benchmark should be repeated for each block. + #[arg(long, default_value_t = 10)] + pub repeat: u32, +} + +/// Convenience closure for the [`Benchmark::run()`] function. +pub struct Benchmark { + client: Arc, + params: BenchmarkParams, + _p: PhantomData<(Block, BA, C)>, +} + +/// Helper for nano seconds. +type NanoSeconds = u64; + +impl Benchmark +where + Block: BlockT, + BA: ClientBackend, + C: BlockBuilderProvider + + ProvideRuntimeApi + + StorageProvider + + UsageProvider + + BlockBackend + + HeaderBackend, + C::Api: ApiExt + BlockBuilderApi, +{ + /// Returns a new [`Self`] from the arguments. + pub fn new(client: Arc, params: BenchmarkParams) -> Self { + Self { client, params, _p: PhantomData } + } + + /// Benchmark the execution speed of historic blocks and log the results. + pub fn run(&self) -> Result<()> { + if self.params.from == 0 { + return Err("Cannot benchmark the genesis block".into()) + } + + for i in self.params.from..=self.params.to { + let block_num = BlockId::Number(i.into()); + let parent_num = BlockId::Number(((i - 1) as u32).into()); + let consumed = self.consumed_weight(&block_num)?; + + let block = + self.client.block(&block_num)?.ok_or(format!("Block {} not found", block_num))?; + let block = self.unsealed(block.block); + let took = self.measure_block(&block, &parent_num)?; + + self.log_weight(i, block.extrinsics().len(), consumed, took); + } + + Ok(()) + } + + /// Return the average *execution* aka. *import* time of the block. + fn measure_block(&self, block: &Block, parent_num: &BlockId) -> Result { + let mut record = Vec::::default(); + // Interesting part here: + // Execute the block multiple times and collect stats about its execution time. + for _ in 0..self.params.repeat { + let block = block.clone(); + let runtime_api = self.client.runtime_api(); + let start = Instant::now(); + + runtime_api + .execute_block(&parent_num, block) + .map_err(|e| Error::Client(RuntimeApiError(e)))?; + + record.push(start.elapsed().as_nanos() as NanoSeconds); + } + + let took = Stats::new(&record)?.select(StatSelect::Average); + Ok(took) + } + + /// Returns the total nanoseconds of a [`frame_system::ConsumedWeight`] for a block number. + /// + /// This is the post-dispatch corrected weight and is only available + /// after executing the block. + fn consumed_weight(&self, block: &BlockId) -> Result { + // Hard-coded key for System::BlockWeight. It could also be passed in as argument + // for the benchmark, but I think this should work as well. + let hash = array_bytes::hex2bytes( + "26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96", + )?; + let key = StorageKey(hash); + + let block_hash = self.client.expect_block_hash_from_id(block)?; + let mut raw_weight = &self + .client + .storage(block_hash, &key)? + .ok_or(format!("Could not find System::BlockWeight for block: {}", block))? + .0[..]; + + let weight = ConsumedWeight::decode_all(&mut raw_weight)?; + // Should be divisible, but still use floats in case we ever change that. + Ok((weight.total().ref_time() as f64 / WEIGHT_REF_TIME_PER_NANOS as f64).floor() + as NanoSeconds) + } + + /// Prints the weight info of a block to the console. + fn log_weight(&self, num: u32, num_ext: usize, consumed: NanoSeconds, took: NanoSeconds) { + // The ratio of weight that the block used vs what it consumed. + // This should in general not exceed 100% (minus outliers). + let percent = (took as f64 / consumed as f64) * 100.0; + + let msg = format!( + "Block {} with {: >5} tx used {: >6.2}% of its weight ({: >14} of {: >14} ns)", + num, + num_ext, + percent, + took.separate_with_commas(), + consumed.separate_with_commas() + ); + + if took <= consumed { + info!(target: LOG_TARGET, "{}", msg); + } else { + warn!(target: LOG_TARGET, "{} - OVER WEIGHT!", msg); + } + } + + /// Removes the consensus seal from the block. + fn unsealed(&self, block: Block) -> Block { + let (mut header, exts) = block.deconstruct(); + header.digest_mut().logs.retain(|item| !matches!(item, DigestItem::Seal(_, _))); + Block::new(header, exts) + } +} diff --git a/utils/frame/benchmarking-cli/src/block/cmd.rs b/utils/frame/benchmarking-cli/src/block/cmd.rs new file mode 100644 index 0000000000000..8bac04110f7ab --- /dev/null +++ b/utils/frame/benchmarking-cli/src/block/cmd.rs @@ -0,0 +1,117 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Contains the [`BlockCmd`] as entry point for the CLI to execute +//! the *block* benchmark. + +use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; +use sc_cli::{CliConfiguration, ImportParams, Result, SharedParams}; +use sc_client_api::{Backend as ClientBackend, BlockBackend, StorageProvider, UsageProvider}; +use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_blockchain::HeaderBackend; +use sp_runtime::{traits::Block as BlockT, OpaqueExtrinsic}; + +use clap::Parser; +use std::{fmt::Debug, sync::Arc}; + +use super::bench::{Benchmark, BenchmarkParams}; + +/// Benchmark the execution time of historic blocks. +/// +/// This can be used to verify that blocks do not use more weight than they consumed +/// in their `WeightInfo`. Example: +/// +/// Let's say you are on a Substrate chain and want to verify that the first 3 blocks +/// did not use more weight than declared which would otherwise be an issue. +/// To test this with a dev node, first create one with a temp directory: +/// +/// $ substrate --dev -d /tmp/my-dev --execution wasm --wasm-execution compiled +/// +/// And wait some time to let it produce 3 blocks. Then benchmark them with: +/// +/// $ substrate benchmark-block --from 1 --to 3 --dev -d /tmp/my-dev +/// --execution wasm --wasm-execution compiled --pruning archive +/// +/// The output will be similar to this: +/// +/// Block 1 with 1 tx used 77.34% of its weight ( 5,308,964 of 6,864,645 ns) +/// Block 2 with 1 tx used 77.99% of its weight ( 5,353,992 of 6,864,645 ns) +/// Block 3 with 1 tx used 75.91% of its weight ( 5,305,938 of 6,989,645 ns) +/// +/// The percent number is important and indicates how much weight +/// was used as compared to the consumed weight. +/// This number should be below 100% for reference hardware. +#[derive(Debug, Parser)] +pub struct BlockCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub import_params: ImportParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub params: BenchmarkParams, + + /// Enable the Trie cache. + /// + /// This should only be used for performance analysis and not for final results. + #[arg(long)] + pub enable_trie_cache: bool, +} + +impl BlockCmd { + /// Benchmark the execution time of historic blocks and compare it to their consumed weight. + /// + /// Output will be printed to console. + pub fn run(&self, client: Arc) -> Result<()> + where + Block: BlockT, + BA: ClientBackend, + C: BlockBuilderProvider + + BlockBackend + + ProvideRuntimeApi + + StorageProvider + + UsageProvider + + HeaderBackend, + C::Api: ApiExt + BlockBuilderApi, + { + // Put everything in the benchmark type to have the generic types handy. + Benchmark::new(client, self.params.clone()).run() + } +} + +// Boilerplate +impl CliConfiguration for BlockCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } + + fn import_params(&self) -> Option<&ImportParams> { + Some(&self.import_params) + } + + fn trie_cache_maximum_size(&self) -> Result> { + if self.enable_trie_cache { + Ok(self.import_params().map(|x| x.trie_cache_maximum_size()).unwrap_or_default()) + } else { + Ok(None) + } + } +} diff --git a/utils/frame/benchmarking-cli/src/block/mod.rs b/utils/frame/benchmarking-cli/src/block/mod.rs new file mode 100644 index 0000000000000..97fdb6ad2c20c --- /dev/null +++ b/utils/frame/benchmarking-cli/src/block/mod.rs @@ -0,0 +1,24 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Crate to benchmark the execution time of historic blocks +//! and compare it to their consumed weight. + +mod bench; +mod cmd; + +pub use cmd::BlockCmd; diff --git a/utils/frame/benchmarking-cli/src/extrinsic/bench.rs b/utils/frame/benchmarking-cli/src/extrinsic/bench.rs new file mode 100644 index 0000000000000..2a86c10e7ad27 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/extrinsic/bench.rs @@ -0,0 +1,202 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Contains the core benchmarking logic. + +use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; +use sc_cli::{Error, Result}; +use sc_client_api::Backend as ClientBackend; +use sp_api::{ApiExt, BlockId, Core, ProvideRuntimeApi}; +use sp_blockchain::{ + ApplyExtrinsicFailed::Validity, + Error::{ApplyExtrinsicFailed, RuntimeApiError}, +}; +use sp_runtime::{ + traits::{Block as BlockT, Zero}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, + Digest, DigestItem, OpaqueExtrinsic, +}; + +use clap::Args; +use log::info; +use serde::Serialize; +use std::{marker::PhantomData, sync::Arc, time::Instant}; + +use super::ExtrinsicBuilder; +use crate::shared::{StatSelect, Stats}; + +/// Parameters to configure an *overhead* benchmark. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct BenchmarkParams { + /// Rounds of warmups before measuring. + #[arg(long, default_value_t = 10)] + pub warmup: u32, + + /// How many times the benchmark should be repeated. + #[arg(long, default_value_t = 100)] + pub repeat: u32, + + /// Maximal number of extrinsics that should be put into a block. + /// + /// Only useful for debugging. + #[arg(long)] + pub max_ext_per_block: Option, +} + +/// The results of multiple runs in nano seconds. +pub(crate) type BenchRecord = Vec; + +/// Holds all objects needed to run the *overhead* benchmarks. +pub(crate) struct Benchmark { + client: Arc, + params: BenchmarkParams, + inherent_data: sp_inherents::InherentData, + digest_items: Vec, + _p: PhantomData<(Block, BA)>, +} + +impl Benchmark +where + Block: BlockT, + BA: ClientBackend, + C: BlockBuilderProvider + ProvideRuntimeApi, + C::Api: ApiExt + BlockBuilderApi, +{ + /// Create a new [`Self`] from the arguments. + pub fn new( + client: Arc, + params: BenchmarkParams, + inherent_data: sp_inherents::InherentData, + digest_items: Vec, + ) -> Self { + Self { client, params, inherent_data, digest_items, _p: PhantomData } + } + + /// Benchmark a block with only inherents. + pub fn bench_block(&self) -> Result { + let (block, _) = self.build_block(None)?; + let record = self.measure_block(&block)?; + Stats::new(&record) + } + + /// Benchmark the time of an extrinsic in a full block. + /// + /// First benchmarks an empty block, analogous to `bench_block` and use it as baseline. + /// Then benchmarks a full block built with the given `ext_builder` and subtracts the baseline + /// from the result. + /// This is necessary to account for the time the inherents use. + pub fn bench_extrinsic(&self, ext_builder: &dyn ExtrinsicBuilder) -> Result { + let (block, _) = self.build_block(None)?; + let base = self.measure_block(&block)?; + let base_time = Stats::new(&base)?.select(StatSelect::Average); + + let (block, num_ext) = self.build_block(Some(ext_builder))?; + let num_ext = num_ext.ok_or_else(|| Error::Input("Block was empty".into()))?; + let mut records = self.measure_block(&block)?; + + for r in &mut records { + // Subtract the base time. + *r = r.saturating_sub(base_time); + // Divide by the number of extrinsics in the block. + *r = ((*r as f64) / (num_ext as f64)).ceil() as u64; + } + + Stats::new(&records) + } + + /// Builds a block with some optional extrinsics. + /// + /// Returns the block and the number of extrinsics in the block + /// that are not inherents. + /// Returns a block with only inherents if `ext_builder` is `None`. + fn build_block( + &self, + ext_builder: Option<&dyn ExtrinsicBuilder>, + ) -> Result<(Block, Option)> { + let mut builder = self.client.new_block(Digest { logs: self.digest_items.clone() })?; + // Create and insert the inherents. + let inherents = builder.create_inherents(self.inherent_data.clone())?; + for inherent in inherents { + builder.push(inherent)?; + } + + // Return early if `ext_builder` is `None`. + let ext_builder = if let Some(ext_builder) = ext_builder { + ext_builder + } else { + return Ok((builder.build()?.block, None)) + }; + + // Put as many extrinsics into the block as possible and count them. + info!("Building block, this takes some time..."); + let mut num_ext = 0; + for nonce in 0..self.max_ext_per_block() { + let ext = ext_builder.build(nonce)?; + match builder.push(ext.clone()) { + Ok(()) => {}, + Err(ApplyExtrinsicFailed(Validity(TransactionValidityError::Invalid( + InvalidTransaction::ExhaustsResources, + )))) => break, // Block is full + Err(e) => return Err(Error::Client(e)), + } + num_ext += 1; + } + if num_ext == 0 { + return Err("A Block must hold at least one extrinsic".into()) + } + info!("Extrinsics per block: {}", num_ext); + let block = builder.build()?.block; + + Ok((block, Some(num_ext))) + } + + /// Measures the time that it take to execute a block or an extrinsic. + fn measure_block(&self, block: &Block) -> Result { + let mut record = BenchRecord::new(); + let genesis = BlockId::Number(Zero::zero()); + + info!("Running {} warmups...", self.params.warmup); + for _ in 0..self.params.warmup { + self.client + .runtime_api() + .execute_block(&genesis, block.clone()) + .map_err(|e| Error::Client(RuntimeApiError(e)))?; + } + + info!("Executing block {} times", self.params.repeat); + // Interesting part here: + // Execute a block multiple times and record each execution time. + for _ in 0..self.params.repeat { + let block = block.clone(); + let runtime_api = self.client.runtime_api(); + let start = Instant::now(); + + runtime_api + .execute_block(&genesis, block) + .map_err(|e| Error::Client(RuntimeApiError(e)))?; + + let elapsed = start.elapsed().as_nanos(); + record.push(elapsed as u64); + } + + Ok(record) + } + + fn max_ext_per_block(&self) -> u32 { + self.params.max_ext_per_block.unwrap_or(u32::MAX) + } +} diff --git a/utils/frame/benchmarking-cli/src/extrinsic/cmd.rs b/utils/frame/benchmarking-cli/src/extrinsic/cmd.rs new file mode 100644 index 0000000000000..b95cd6b5c2e42 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/extrinsic/cmd.rs @@ -0,0 +1,149 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; +use sc_cli::{CliConfiguration, ImportParams, Result, SharedParams}; +use sc_client_api::Backend as ClientBackend; +use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_runtime::{traits::Block as BlockT, DigestItem, OpaqueExtrinsic}; + +use clap::{Args, Parser}; +use log::info; +use serde::Serialize; +use std::{fmt::Debug, sync::Arc}; + +use super::{ + bench::{Benchmark, BenchmarkParams}, + extrinsic_factory::ExtrinsicFactory, +}; + +/// Benchmark the execution time of different extrinsics. +/// +/// This is calculated by filling a block with a specific extrinsic and executing the block. +/// The result time is then divided by the number of extrinsics in that block. +/// +/// NOTE: The BlockExecutionWeight is ignored in this case since it +// is very small compared to the total block execution time. +#[derive(Debug, Parser)] +pub struct ExtrinsicCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub import_params: ImportParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub params: ExtrinsicParams, +} + +/// The params for the [`ExtrinsicCmd`]. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct ExtrinsicParams { + #[clap(flatten)] + pub bench: BenchmarkParams, + + /// List all available pallets and extrinsics. + /// + /// The format is CSV with header `pallet, extrinsic`. + #[arg(long)] + pub list: bool, + + /// Pallet name of the extrinsic to benchmark. + #[arg(long, value_name = "PALLET", required_unless_present = "list")] + pub pallet: Option, + + /// Extrinsic to benchmark. + #[arg(long, value_name = "EXTRINSIC", required_unless_present = "list")] + pub extrinsic: Option, + + /// Enable the Trie cache. + /// + /// This should only be used for performance analysis and not for final results. + #[arg(long)] + pub enable_trie_cache: bool, +} + +impl ExtrinsicCmd { + /// Benchmark the execution time of a specific type of extrinsic. + /// + /// The output will be printed to console. + pub fn run( + &self, + client: Arc, + inherent_data: sp_inherents::InherentData, + digest_items: Vec, + ext_factory: &ExtrinsicFactory, + ) -> Result<()> + where + Block: BlockT, + BA: ClientBackend, + C: BlockBuilderProvider + ProvideRuntimeApi, + C::Api: ApiExt + BlockBuilderApi, + { + // Short circuit if --list was specified. + if self.params.list { + let list: Vec = ext_factory.0.iter().map(|b| b.name()).collect(); + info!( + "Listing available extrinsics ({}):\npallet, extrinsic\n{}", + list.len(), + list.join("\n") + ); + return Ok(()) + } + + let pallet = self.params.pallet.clone().unwrap_or_default(); + let extrinsic = self.params.extrinsic.clone().unwrap_or_default(); + let ext_builder = match ext_factory.try_get(&pallet, &extrinsic) { + Some(ext_builder) => ext_builder, + None => + return Err("Unknown pallet or extrinsic. Use --list for a complete list.".into()), + }; + + let bench = Benchmark::new(client, self.params.bench.clone(), inherent_data, digest_items); + let stats = bench.bench_extrinsic(ext_builder)?; + info!( + "Executing a {}::{} extrinsic takes[ns]:\n{:?}", + ext_builder.pallet(), + ext_builder.extrinsic(), + stats + ); + + Ok(()) + } +} + +// Boilerplate +impl CliConfiguration for ExtrinsicCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } + + fn import_params(&self) -> Option<&ImportParams> { + Some(&self.import_params) + } + + fn trie_cache_maximum_size(&self) -> Result> { + if self.params.enable_trie_cache { + Ok(self.import_params().map(|x| x.trie_cache_maximum_size()).unwrap_or_default()) + } else { + Ok(None) + } + } +} diff --git a/utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs b/utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs new file mode 100644 index 0000000000000..7e1a22ccd1419 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs @@ -0,0 +1,70 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Provides the [`ExtrinsicFactory`] and the [`ExtrinsicBuilder`] types. +//! Is used by the *overhead* and *extrinsic* benchmarks to build extrinsics. + +use sp_runtime::OpaqueExtrinsic; + +/// Helper to manage [`ExtrinsicBuilder`] instances. +#[derive(Default)] +pub struct ExtrinsicFactory(pub Vec>); + +impl ExtrinsicFactory { + /// Returns a builder for a pallet and extrinsic name. + /// + /// Is case in-sensitive. + pub fn try_get(&self, pallet: &str, extrinsic: &str) -> Option<&dyn ExtrinsicBuilder> { + let pallet = pallet.to_lowercase(); + let extrinsic = extrinsic.to_lowercase(); + + self.0 + .iter() + .find(|b| b.pallet() == pallet && b.extrinsic() == extrinsic) + .map(|b| b.as_ref()) + } +} + +/// Used by the benchmark to build signed extrinsics. +/// +/// The built extrinsics only need to be valid in the first block +/// who's parent block is the genesis block. +/// This assumption simplifies the generation of the extrinsics. +/// The signer should be one of the pre-funded dev accounts. +pub trait ExtrinsicBuilder { + /// Name of the pallet this builder is for. + /// + /// Should be all lowercase. + fn pallet(&self) -> &str; + + /// Name of the extrinsic this builder is for. + /// + /// Should be all lowercase. + fn extrinsic(&self) -> &str; + + /// Builds an extrinsic. + /// + /// Will be called multiple times with increasing nonces. + fn build(&self, nonce: u32) -> std::result::Result; +} + +impl dyn ExtrinsicBuilder + '_ { + /// Name of this builder in CSV format: `pallet, extrinsic`. + pub fn name(&self) -> String { + format!("{}, {}", self.pallet(), self.extrinsic()) + } +} diff --git a/utils/frame/benchmarking-cli/src/extrinsic/mod.rs b/utils/frame/benchmarking-cli/src/extrinsic/mod.rs new file mode 100644 index 0000000000000..12a09c3b1af63 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/extrinsic/mod.rs @@ -0,0 +1,27 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Benchmark the time it takes to execute a specific extrinsic. +//! This is a generalization of the *overhead* benchmark which can only measure `System::Remark` +//! extrinsics. + +pub mod bench; +pub mod cmd; +pub mod extrinsic_factory; + +pub use cmd::ExtrinsicCmd; +pub use extrinsic_factory::{ExtrinsicBuilder, ExtrinsicFactory}; diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index 316ddfb8d0c10..a44a208b16ae9 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,137 +15,107 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod command; -mod writer; - -use sc_cli::{ExecutionStrategy, WasmExecutionMethod}; -use std::fmt::Debug; +//! Contains the root [`BenchmarkCmd`] command and exports its sub-commands. + +mod block; +mod extrinsic; +mod machine; +mod overhead; +mod pallet; +mod shared; +mod storage; + +pub use block::BlockCmd; +pub use extrinsic::{ExtrinsicBuilder, ExtrinsicCmd, ExtrinsicFactory}; +pub use machine::{MachineCmd, Requirements, SUBSTRATE_REFERENCE_HARDWARE}; +pub use overhead::OverheadCmd; +pub use pallet::PalletCmd; +pub use sc_service::BasePath; +pub use storage::StorageCmd; + +use sc_cli::{CliConfiguration, DatabaseParams, ImportParams, PruningParams, Result, SharedParams}; + +/// The root `benchmarking` command. +/// +/// Has no effect itself besides printing a help menu of the sub-commands. +#[derive(Debug, clap::Subcommand)] +pub enum BenchmarkCmd { + Pallet(PalletCmd), + Storage(StorageCmd), + Overhead(OverheadCmd), + Block(BlockCmd), + Machine(MachineCmd), + Extrinsic(ExtrinsicCmd), +} -// Add a more relaxed parsing for pallet names by allowing pallet directory names with `-` to be -// used like crate names with `_` -fn parse_pallet_name(pallet: &str) -> String { - pallet.replace("-", "_") +/// Unwraps a [`BenchmarkCmd`] into its concrete sub-command. +macro_rules! unwrap_cmd { + { + $self:expr, + $cmd:ident, + $code:expr + } => { + match $self { + BenchmarkCmd::Pallet($cmd) => $code, + BenchmarkCmd::Storage($cmd) => $code, + BenchmarkCmd::Overhead($cmd) => $code, + BenchmarkCmd::Block($cmd) => $code, + BenchmarkCmd::Machine($cmd) => $code, + BenchmarkCmd::Extrinsic($cmd) => $code, + } + } } -/// The `benchmark` command used to benchmark FRAME Pallets. -#[derive(Debug, structopt::StructOpt)] -pub struct BenchmarkCmd { - /// Select a FRAME Pallet to benchmark, or `*` for all (in which case `extrinsic` must be `*`). - #[structopt(short, long, parse(from_str = parse_pallet_name), required_unless = "list")] - pub pallet: Option, - - /// Select an extrinsic inside the pallet to benchmark, or `*` for all. - #[structopt(short, long, required_unless = "list")] - pub extrinsic: Option, - - /// Select how many samples we should take across the variable components. - #[structopt(short, long, default_value = "1")] - pub steps: u32, - - /// Indicates lowest values for each of the component ranges. - #[structopt(long = "low", use_delimiter = true)] - pub lowest_range_values: Vec, - - /// Indicates highest values for each of the component ranges. - #[structopt(long = "high", use_delimiter = true)] - pub highest_range_values: Vec, - - /// Select how many repetitions of this benchmark should run from within the wasm. - #[structopt(short, long, default_value = "1")] - pub repeat: u32, - - /// Select how many repetitions of this benchmark should run from the client. - /// - /// NOTE: Using this alone may give slower results, but will afford you maximum Wasm memory. - #[structopt(long, default_value = "1")] - pub external_repeat: u32, - - /// Print the raw results. - #[structopt(long = "raw")] - pub raw_data: bool, - - /// Don't print the median-slopes linear regression analysis. - #[structopt(long)] - pub no_median_slopes: bool, - - /// Don't print the min-squares linear regression analysis. - #[structopt(long)] - pub no_min_squares: bool, - - /// Output the benchmarks to a Rust file at the given path. - #[structopt(long)] - pub output: Option, - - /// Add a header file to your outputted benchmarks - #[structopt(long)] - pub header: Option, - - /// Path to Handlebars template file used for outputting benchmark results. (Optional) - #[structopt(long)] - pub template: Option, - - /// Which analysis function to use when outputting benchmarks: - /// * min-squares (default) - /// * median-slopes - /// * max (max of min squares and median slopes for each value) - #[structopt(long)] - pub output_analysis: Option, - - /// Set the heap pages while running benchmarks. If not set, the default value from the client - /// is used. - #[structopt(long)] - pub heap_pages: Option, - - /// Disable verification logic when running benchmarks. - #[structopt(long)] - pub no_verify: bool, - - /// Display and run extra benchmarks that would otherwise not be needed for weight - /// construction. - #[structopt(long)] - pub extra: bool, - - /// Estimate PoV size. - #[structopt(long)] - pub record_proof: bool, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub shared_params: sc_cli::SharedParams, - - /// The execution strategy that should be used for benchmarks - #[structopt( - long = "execution", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - )] - pub execution: Option, - - /// Method for executing Wasm runtime code. - #[structopt( - long = "wasm-execution", - value_name = "METHOD", - possible_values = &WasmExecutionMethod::variants(), - case_insensitive = true, - default_value = "compiled" - )] - pub wasm_method: WasmExecutionMethod, - - /// Limit the memory the database cache can use. - #[structopt(long = "db-cache", value_name = "MiB", default_value = "128")] - pub database_cache_size: u32, - - /// List the benchmarks that match your query rather than running them. - /// - /// When nothing is provided, we list all benchmarks. - #[structopt(long)] - pub list: bool, - - /// If enabled, the storage info is not displayed in the output next to the analysis. - /// - /// This is independent of the storage info appearing in the *output file*. Use a Handlebar - /// template for that purpose. - #[structopt(long)] - pub no_storage_info: bool, +/// Forward the [`CliConfiguration`] trait implementation. +/// +/// Each time a sub-command exposes a new config option, it must be added here. +impl CliConfiguration for BenchmarkCmd { + fn shared_params(&self) -> &SharedParams { + unwrap_cmd! { + self, cmd, cmd.shared_params() + } + } + + fn import_params(&self) -> Option<&ImportParams> { + unwrap_cmd! { + self, cmd, cmd.import_params() + } + } + + fn database_params(&self) -> Option<&DatabaseParams> { + unwrap_cmd! { + self, cmd, cmd.database_params() + } + } + + fn base_path(&self) -> Result> { + let inner = unwrap_cmd! { + self, cmd, cmd.base_path() + }; + + // If the base path was not provided, benchmark command shall use temporary path. Otherwise + // we may end up using shared path, which may be inappropriate for benchmarking. + match inner { + Ok(None) => Some(BasePath::new_temp_dir()).transpose().map_err(|e| e.into()), + e => e, + } + } + + fn pruning_params(&self) -> Option<&PruningParams> { + unwrap_cmd! { + self, cmd, cmd.pruning_params() + } + } + + fn trie_cache_maximum_size(&self) -> Result> { + unwrap_cmd! { + self, cmd, cmd.trie_cache_maximum_size() + } + } + + fn chain_id(&self, is_dev: bool) -> Result { + unwrap_cmd! { + self, cmd, cmd.chain_id(is_dev) + } + } } diff --git a/utils/frame/benchmarking-cli/src/machine/README.md b/utils/frame/benchmarking-cli/src/machine/README.md new file mode 100644 index 0000000000000..f22a8ea54b81d --- /dev/null +++ b/utils/frame/benchmarking-cli/src/machine/README.md @@ -0,0 +1,71 @@ +# The `benchmark machine` command + +Different Substrate chains can have different hardware requirements. +It is therefore important to be able to quickly gauge if a piece of hardware fits a chains' requirements. +The `benchmark machine` command archives this by measuring key metrics and making them comparable. + +Invoking the command looks like this: +```sh +cargo run --profile=production -- benchmark machine --dev +``` + +## Output + +The output on reference hardware: + +```pre ++----------+----------------+---------------+--------------+-------------------+ +| Category | Function | Score | Minimum | Result | ++----------+----------------+---------------+--------------+-------------------+ +| CPU | BLAKE2-256 | 1023.00 MiB/s | 1.00 GiB/s | ✅ Pass ( 99.4 %) | ++----------+----------------+---------------+--------------+-------------------+ +| CPU | SR25519-Verify | 665.13 KiB/s | 666.00 KiB/s | ✅ Pass ( 99.9 %) | ++----------+----------------+---------------+--------------+-------------------+ +| Memory | Copy | 14.39 GiB/s | 14.32 GiB/s | ✅ Pass (100.4 %) | ++----------+----------------+---------------+--------------+-------------------+ +| Disk | Seq Write | 457.00 MiB/s | 450.00 MiB/s | ✅ Pass (101.6 %) | ++----------+----------------+---------------+--------------+-------------------+ +| Disk | Rnd Write | 190.00 MiB/s | 200.00 MiB/s | ✅ Pass ( 95.0 %) | ++----------+----------------+---------------+--------------+-------------------+ +``` + +The *score* is the average result of each benchmark. It always adheres to "higher is better". + +The *category* indicate which part of the hardware was benchmarked: +- **CPU** Processor intensive task +- **Memory** RAM intensive task +- **Disk** Hard drive intensive task + +The *function* is the concrete benchmark that was run: +- **BLAKE2-256** The throughput of the [Blake2-256] cryptographic hashing function with 32 KiB input. The [blake2_256 function] is used in many places in Substrate. The throughput of a hash function strongly depends on the input size, therefore we settled to use a fixed input size for comparable results. +- **SR25519 Verify** Sr25519 is an optimized version of the [Curve25519] signature scheme. Signature verification is used by Substrate when verifying extrinsics and blocks. +- **Copy** The throughput of copying memory from one place in the RAM to another. +- **Seq Write** The throughput of writing data to the storage location sequentially. It is important that the same disk is used that will later-on be used to store the chain data. +- **Rnd Write** The throughput of writing data to the storage location in a random order. This is normally much slower than the sequential write. + +The *score* needs to reach the *minimum* in order to pass the benchmark. This can be reduced with the `--tolerance` flag. + +The *result* indicated if a specific benchmark was passed by the machine or not. The percent number is the relative score reached to the *minimum* that is needed. The `--tolerance` flag is taken into account for this decision. For example a benchmark that passes even with 95% since the *tolerance* was set to 10% would look like this: `✅ Pass ( 95.0 %)`. + +## Interpretation + +Ideally all results show a `Pass` and the program exits with code 0. Currently some of the benchmarks can fail even on reference hardware; they are still being improved to make them more deterministic. +Make sure to run nothing else on the machine when benchmarking it. +You can re-run them multiple times to get more reliable results. + +## Arguments + +- `--tolerance` A percent number to reduce the *minimum* requirement. This should be used to ignore outliers of the benchmarks. The default value is 10%. +- `--verify-duration` How long the verification benchmark should run. +- `--disk-duration` How long the *read* and *write* benchmarks should run each. +- `--allow-fail` Always exit the program with code 0. +- `--chain` / `--dev` Specify the chain config to use. This will be used to compare the results with the requirements of the chain (WIP). +- [`--base-path`] + +License: Apache-2.0 + + +[Blake2-256]: https://www.blake2.net/ +[blake2_256 function]: https://crates.parity.io/sp_core/hashing/fn.blake2_256.html +[Curve25519]: https://en.wikipedia.org/wiki/Curve25519 +[`--base-path`]: ../shared/README.md#arguments diff --git a/utils/frame/benchmarking-cli/src/machine/hardware.rs b/utils/frame/benchmarking-cli/src/machine/hardware.rs new file mode 100644 index 0000000000000..50c88ec74646c --- /dev/null +++ b/utils/frame/benchmarking-cli/src/machine/hardware.rs @@ -0,0 +1,138 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Contains types to define hardware requirements. + +use lazy_static::lazy_static; +use sc_sysinfo::Throughput; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +use sp_std::{fmt, fmt::Formatter}; + +/// Serializes throughput into MiBs and represents it as `f64`. +fn serialize_throughput_as_f64(throughput: &Throughput, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_f64(throughput.as_mibs()) +} + +struct ThroughputVisitor; +impl<'de> Visitor<'de> for ThroughputVisitor { + type Value = Throughput; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("A value that is a f64.") + } + + fn visit_f64(self, value: f64) -> Result + where + E: serde::de::Error, + { + Ok(Throughput::from_mibs(value)) + } +} + +fn deserialize_throughput<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + Ok(deserializer.deserialize_f64(ThroughputVisitor))? +} + +lazy_static! { + /// The hardware requirements as measured on reference hardware. + /// + /// These values are provided by Parity, however it is possible + /// to use your own requirements if you are running a custom chain. + /// + /// The reference hardware is describe here: + /// + pub static ref SUBSTRATE_REFERENCE_HARDWARE: Requirements = { + let raw = include_bytes!("reference_hardware.json").as_slice(); + serde_json::from_slice(raw).expect("Hardcoded data is known good; qed") + }; +} + +/// Multiple requirements for the hardware. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Requirements(pub Vec); + +/// A single requirement for the hardware. +#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] +pub struct Requirement { + /// The metric to measure. + pub metric: Metric, + /// The minimal throughput that needs to be archived for this requirement. + #[serde( + serialize_with = "serialize_throughput_as_f64", + deserialize_with = "deserialize_throughput" + )] + pub minimum: Throughput, +} + +/// A single hardware metric. +/// +/// The implementation of these is in `sc-sysinfo`. +#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] +pub enum Metric { + /// SR25519 signature verification. + Sr25519Verify, + /// Blake2-256 hashing algorithm. + Blake2256, + /// Copying data in RAM. + MemCopy, + /// Disk sequential write. + DiskSeqWrite, + /// Disk random write. + DiskRndWrite, +} + +impl Metric { + /// The category of the metric. + pub fn category(&self) -> &'static str { + match self { + Self::Sr25519Verify | Self::Blake2256 => "CPU", + Self::MemCopy => "Memory", + Self::DiskSeqWrite | Self::DiskRndWrite => "Disk", + } + } + + /// The name of the metric. It is always prefixed by the [`self::category()`]. + pub fn name(&self) -> &'static str { + match self { + Self::Sr25519Verify => "SR25519-Verify", + Self::Blake2256 => "BLAKE2-256", + Self::MemCopy => "Copy", + Self::DiskSeqWrite => "Seq Write", + Self::DiskRndWrite => "Rnd Write", + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// `SUBSTRATE_REFERENCE_HARDWARE` can be en- and decoded. + #[test] + fn json_static_data() { + let raw = serde_json::to_string(&*SUBSTRATE_REFERENCE_HARDWARE).unwrap(); + let decoded: Requirements = serde_json::from_str(&raw).unwrap(); + + assert_eq!(decoded, SUBSTRATE_REFERENCE_HARDWARE.clone()); + } +} diff --git a/utils/frame/benchmarking-cli/src/machine/mod.rs b/utils/frame/benchmarking-cli/src/machine/mod.rs new file mode 100644 index 0000000000000..82b4e5be7358e --- /dev/null +++ b/utils/frame/benchmarking-cli/src/machine/mod.rs @@ -0,0 +1,239 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Contains the [`MachineCmd`] as entry point for the node +//! and the core benchmarking logic. + +pub mod hardware; + +use std::{boxed::Box, fs, path::Path}; + +use clap::Parser; +use comfy_table::{Row, Table}; +use log::{error, info, warn}; + +use sc_cli::{CliConfiguration, Result, SharedParams}; +use sc_service::Configuration; +use sc_sysinfo::{ + benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes, + benchmark_memory, benchmark_sr25519_verify, ExecutionLimit, Throughput, +}; + +use crate::shared::check_build_profile; +pub use hardware::{Metric, Requirement, Requirements, SUBSTRATE_REFERENCE_HARDWARE}; + +/// Command to benchmark the hardware. +/// +/// Runs multiple benchmarks and prints their output to console. +/// Can be used to gauge if the hardware is fast enough to keep up with a chain's requirements. +/// This command must be integrated by the client since the client can set compiler flags +/// which influence the results. +/// +/// You can use the `--base-path` flag to set a location for the disk benchmarks. +#[derive(Debug, Parser)] +pub struct MachineCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + /// Do not return an error if any check fails. + /// + /// Should only be used for debugging. + #[arg(long)] + pub allow_fail: bool, + + /// Set a fault tolerance for passing a requirement. + /// + /// 10% means that the test would pass even when only 90% score was archived. + /// Can be used to mitigate outliers of the benchmarks. + #[arg(long, default_value_t = 10.0, value_name = "PERCENT")] + pub tolerance: f64, + + /// Time limit for the verification benchmark. + #[arg(long, default_value_t = 5.0, value_name = "SECONDS")] + pub verify_duration: f32, + + /// Time limit for the hash function benchmark. + #[arg(long, default_value_t = 5.0, value_name = "SECONDS")] + pub hash_duration: f32, + + /// Time limit for the memory benchmark. + #[arg(long, default_value_t = 5.0, value_name = "SECONDS")] + pub memory_duration: f32, + + /// Time limit for each disk benchmark. + #[arg(long, default_value_t = 5.0, value_name = "SECONDS")] + pub disk_duration: f32, +} + +/// Helper for the result of a concrete benchmark. +struct BenchResult { + /// Did the hardware pass the benchmark? + passed: bool, + + /// The absolute score that was archived. + score: Throughput, + + /// The score relative to the minimal required score. + /// + /// Is in range [0, 1]. + rel_score: f64, +} + +/// Errors that can be returned by the this command. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("One of the benchmarks had a score that was lower than its requirement")] + UnmetRequirement, + + #[error("The build profile is unfit for benchmarking: {0}")] + BadBuildProfile(String), + + #[error("Benchmark results are off by at least factor 100")] + BadResults, +} + +impl MachineCmd { + /// Execute the benchmark and print the results. + pub fn run(&self, cfg: &Configuration, requirements: Requirements) -> Result<()> { + self.validate_args()?; + // Ensure that the dir exists since the node is not started to take care of it. + let dir = cfg.database.path().ok_or("No DB directory provided")?; + fs::create_dir_all(dir)?; + + info!("Running machine benchmarks..."); + let mut results = Vec::new(); + for requirement in &requirements.0 { + let result = self.run_benchmark(requirement, &dir)?; + results.push(result); + } + self.print_summary(requirements, results) + } + + /// Benchmarks a specific metric of the hardware and judges the resulting score. + fn run_benchmark(&self, requirement: &Requirement, dir: &Path) -> Result { + // Dispatch the concrete function from `sc-sysinfo`. + + let score = self.measure(&requirement.metric, dir)?; + let rel_score = score.as_bytes() / requirement.minimum.as_bytes(); + + // Sanity check if the result is off by factor >100x. + if rel_score >= 100.0 || rel_score <= 0.01 { + self.check_failed(Error::BadResults)?; + } + let passed = rel_score >= (1.0 - (self.tolerance / 100.0)); + Ok(BenchResult { passed, score, rel_score }) + } + + /// Measures a metric of the hardware. + fn measure(&self, metric: &Metric, dir: &Path) -> Result { + let verify_limit = ExecutionLimit::from_secs_f32(self.verify_duration); + let disk_limit = ExecutionLimit::from_secs_f32(self.disk_duration); + let hash_limit = ExecutionLimit::from_secs_f32(self.hash_duration); + let memory_limit = ExecutionLimit::from_secs_f32(self.memory_duration); + + let score = match metric { + Metric::Blake2256 => benchmark_cpu(hash_limit), + Metric::Sr25519Verify => benchmark_sr25519_verify(verify_limit), + Metric::MemCopy => benchmark_memory(memory_limit), + Metric::DiskSeqWrite => benchmark_disk_sequential_writes(disk_limit, dir)?, + Metric::DiskRndWrite => benchmark_disk_random_writes(disk_limit, dir)?, + }; + Ok(score) + } + + /// Prints a human-readable summary. + fn print_summary(&self, requirements: Requirements, results: Vec) -> Result<()> { + // Use a table for nicer console output. + let mut table = Table::new(); + table.set_header(["Category", "Function", "Score", "Minimum", "Result"]); + // Count how many passed and how many failed. + let (mut passed, mut failed) = (0, 0); + for (requirement, result) in requirements.0.iter().zip(results.iter()) { + if result.passed { + passed += 1 + } else { + failed += 1 + } + + table.add_row(result.to_row(requirement)); + } + // Print the table and a summary. + info!( + "\n{}\nFrom {} benchmarks in total, {} passed and {} failed ({:.0?}% fault tolerance).", + table, + passed + failed, + passed, + failed, + self.tolerance + ); + // Print the final result. + if failed != 0 { + info!("The hardware fails to meet the requirements"); + self.check_failed(Error::UnmetRequirement)?; + } else { + info!("The hardware meets the requirements "); + } + // Check that the results were not created by a bad build profile. + if let Err(err) = check_build_profile() { + self.check_failed(Error::BadBuildProfile(err))?; + } + Ok(()) + } + + /// Returns `Ok` if [`self.allow_fail`] is set and otherwise the error argument. + fn check_failed(&self, e: Error) -> Result<()> { + if !self.allow_fail { + error!("Failing since --allow-fail is not set"); + Err(sc_cli::Error::Application(Box::new(e))) + } else { + warn!("Ignoring error since --allow-fail is set: {:?}", e); + Ok(()) + } + } + + /// Validates the CLI arguments. + fn validate_args(&self) -> Result<()> { + if self.tolerance > 100.0 || self.tolerance < 0.0 { + return Err("The --tolerance argument is out of range".into()) + } + Ok(()) + } +} + +impl BenchResult { + /// Format [`Self`] as row that can be printed in a table. + fn to_row(&self, req: &Requirement) -> Row { + let passed = if self.passed { "✅ Pass" } else { "❌ Fail" }; + vec![ + req.metric.category().into(), + req.metric.name().into(), + format!("{}", self.score), + format!("{}", req.minimum), + format!("{} ({: >5.1?} %)", passed, self.rel_score * 100.0), + ] + .into() + } +} + +// Boilerplate +impl CliConfiguration for MachineCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } +} diff --git a/utils/frame/benchmarking-cli/src/machine/reference_hardware.json b/utils/frame/benchmarking-cli/src/machine/reference_hardware.json new file mode 100644 index 0000000000000..2a451d31403f1 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/machine/reference_hardware.json @@ -0,0 +1,22 @@ +[ + { + "metric": "Blake2256", + "minimum": 1029.0 + }, + { + "metric": "Sr25519Verify", + "minimum": 0.650391 + }, + { + "metric": "MemCopy", + "minimum": 14666.752 + }, + { + "metric": "DiskSeqWrite", + "minimum": 450.0 + }, + { + "metric": "DiskRndWrite", + "minimum": 200.0 + } +] diff --git a/utils/frame/benchmarking-cli/src/overhead/README.md b/utils/frame/benchmarking-cli/src/overhead/README.md new file mode 100644 index 0000000000000..1584c2affe0a3 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/README.md @@ -0,0 +1,139 @@ +# The `benchmark overhead` command + +Each time an extrinsic or a block is executed, a fixed weight is charged as "execution overhead". +This is necessary since the weight that is calculated by the pallet benchmarks does not include this overhead. +The exact overhead to can vary per Substrate chain and needs to be calculated per chain. +This command calculates the exact values of these overhead weights for any Substrate chain that supports it. + +## How does it work? + +The benchmark consists of two parts; the [`BlockExecutionWeight`] and the [`ExtrinsicBaseWeight`]. +Both are executed sequentially when invoking the command. + +## BlockExecutionWeight + +The block execution weight is defined as the weight that it takes to execute an *empty block*. +It is measured by constructing an empty block and measuring its executing time. +The result are written to a `block_weights.rs` file which is created from a template. +The file will contain the concrete weight value and various statistics about the measurements. For example: +```rust +/// Time to execute an empty block. +/// Calculated by multiplying the *Average* with `1` and adding `0`. +/// +/// Stats [NS]: +/// Min, Max: 3_508_416, 3_680_498 +/// Average: 3_532_484 +/// Median: 3_522_111 +/// Std-Dev: 27070.23 +/// +/// Percentiles [NS]: +/// 99th: 3_631_863 +/// 95th: 3_595_674 +/// 75th: 3_526_435 +pub const BlockExecutionWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(3_532_484)); +``` + +In this example it takes 3.5 ms to execute an empty block. That means that it always takes at least 3.5 ms to execute *any* block. +This constant weight is therefore added to each block to ensure that Substrate budgets enough time to execute it. + +## ExtrinsicBaseWeight + +The extrinsic base weight is defined as the weight that it takes to execute an *empty* extrinsic. +An *empty* extrinsic is also called a *NO-OP*. It does nothing and is the equivalent to the empty block form above. +The benchmark now constructs a block which is filled with only NO-OP extrinsics. +This block is then executed many times and the weights are measured. +The result is divided by the number of extrinsics in that block and the results are written to `extrinsic_weights.rs`. + +The relevant section in the output file looks like this: +```rust + /// Time to execute a NO-OP extrinsic, for example `System::remark`. +/// Calculated by multiplying the *Average* with `1` and adding `0`. +/// +/// Stats [NS]: +/// Min, Max: 67_561, 69_855 +/// Average: 67_745 +/// Median: 67_701 +/// Std-Dev: 264.68 +/// +/// Percentiles [NS]: +/// 99th: 68_758 +/// 95th: 67_843 +/// 75th: 67_749 +pub const ExtrinsicBaseWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(67_745)); +``` + +In this example it takes 67.7 µs to execute a NO-OP extrinsic. That means that it always takes at least 67.7 µs to execute *any* extrinsic. +This constant weight is therefore added to each extrinsic to ensure that Substrate budgets enough time to execute it. + +## Invocation + +The base command looks like this (for debugging you can use `--release`): +```sh +cargo run --profile=production -- benchmark overhead --dev +``` + +Output: +```pre +# BlockExecutionWeight +Running 10 warmups... +Executing block 100 times +Per-block execution overhead [ns]: +Total: 353248430 +Min: 3508416, Max: 3680498 +Average: 3532484, Median: 3522111, Stddev: 27070.23 +Percentiles 99th, 95th, 75th: 3631863, 3595674, 3526435 +Writing weights to "block_weights.rs" + +# Setup +Building block, this takes some time... +Extrinsics per block: 12000 + +# ExtrinsicBaseWeight +Running 10 warmups... +Executing block 100 times +Per-extrinsic execution overhead [ns]: +Total: 6774590 +Min: 67561, Max: 69855 +Average: 67745, Median: 67701, Stddev: 264.68 +Percentiles 99th, 95th, 75th: 68758, 67843, 67749 +Writing weights to "extrinsic_weights.rs" +``` + +The complete command for Polkadot looks like this: +```sh +cargo run --profile=production -- benchmark overhead --chain=polkadot-dev --execution=wasm --wasm-execution=compiled --weight-path=runtime/polkadot/constants/src/weights/ +``` + +This will overwrite the the [block_weights.rs](https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/block_weights.rs) and [extrinsic_weights.rs](https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/extrinsic_weights.rs) files in the Polkadot runtime directory. +You can try the same for *Rococo* and to see that the results slightly differ. +👉 It is paramount to use `--profile=production`, `--execution=wasm` and `--wasm-execution=compiled` as the results are otherwise useless. + +## Output Interpretation + +Lower is better. The less weight the execution overhead needs, the better. +Since the weights of the overhead is charged per extrinsic and per block, a larger weight results in less extrinsics per block. +Minimizing this is important to have a large transaction throughput. + +## Arguments + +- `--chain` / `--dev` Set the chain specification. +- `--weight-path` Set the output directory or file to write the weights to. +- `--repeat` Set the repetitions of both benchmarks. +- `--warmup` Set the rounds of warmup before measuring. +- `--execution` Should be set to `wasm` for correct results. +- `--wasm-execution` Should be set to `compiled` for correct results. +- [`--mul`](../shared/README.md#arguments) +- [`--add`](../shared/README.md#arguments) +- [`--metric`](../shared/README.md#arguments) +- [`--weight-path`](../shared/README.md#arguments) +- [`--header`](../shared/README.md#arguments) + +License: Apache-2.0 + + +[`ExtrinsicBaseWeight`]: https://github.com/paritytech/substrate/blob/580ebae17fa30082604f1c9720f6f4a1cfe95b50/frame/support/src/weights/extrinsic_weights.rs#L26 +[`BlockExecutionWeight`]: https://github.com/paritytech/substrate/blob/580ebae17fa30082604f1c9720f6f4a1cfe95b50/frame/support/src/weights/block_weights.rs#L26 + +[System::Remark]: https://github.com/paritytech/substrate/blob/580ebae17fa30082604f1c9720f6f4a1cfe95b50/frame/system/src/lib.rs#L382 diff --git a/utils/frame/benchmarking-cli/src/overhead/cmd.rs b/utils/frame/benchmarking-cli/src/overhead/cmd.rs new file mode 100644 index 0000000000000..74c1e7efc24d4 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/cmd.rs @@ -0,0 +1,173 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Contains the [`OverheadCmd`] as entry point for the CLI to execute +//! the *overhead* benchmarks. + +use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; +use sc_cli::{CliConfiguration, ImportParams, Result, SharedParams}; +use sc_client_api::Backend as ClientBackend; +use sc_service::Configuration; +use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_runtime::{traits::Block as BlockT, DigestItem, OpaqueExtrinsic}; + +use clap::{Args, Parser}; +use log::info; +use serde::Serialize; +use std::{fmt::Debug, path::PathBuf, sync::Arc}; + +use crate::{ + extrinsic::{ + bench::{Benchmark, BenchmarkParams as ExtrinsicBenchmarkParams}, + ExtrinsicBuilder, + }, + overhead::template::TemplateData, + shared::{HostInfoParams, WeightParams}, +}; + +/// Benchmark the execution overhead per-block and per-extrinsic. +#[derive(Debug, Parser)] +pub struct OverheadCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub import_params: ImportParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub params: OverheadParams, +} + +/// Configures the benchmark, the post-processing and weight generation. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct OverheadParams { + #[allow(missing_docs)] + #[clap(flatten)] + pub weight: WeightParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub bench: ExtrinsicBenchmarkParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub hostinfo: HostInfoParams, + + /// Add a header to the generated weight output file. + /// + /// Good for adding LICENSE headers. + #[arg(long, value_name = "PATH")] + pub header: Option, + + /// Enable the Trie cache. + /// + /// This should only be used for performance analysis and not for final results. + #[arg(long)] + pub enable_trie_cache: bool, +} + +/// Type of a benchmark. +#[derive(Serialize, Clone, PartialEq, Copy)] +pub(crate) enum BenchmarkType { + /// Measure the per-extrinsic execution overhead. + Extrinsic, + /// Measure the per-block execution overhead. + Block, +} + +impl OverheadCmd { + /// Measure the per-block and per-extrinsic execution overhead. + /// + /// Writes the results to console and into two instances of the + /// `weights.hbs` template, one for each benchmark. + pub fn run( + &self, + cfg: Configuration, + client: Arc, + inherent_data: sp_inherents::InherentData, + digest_items: Vec, + ext_builder: &dyn ExtrinsicBuilder, + ) -> Result<()> + where + Block: BlockT, + BA: ClientBackend, + C: BlockBuilderProvider + ProvideRuntimeApi, + C::Api: ApiExt + BlockBuilderApi, + { + if ext_builder.pallet() != "system" || ext_builder.extrinsic() != "remark" { + return Err(format!("The extrinsic builder is required to build `System::Remark` extrinsics but builds `{}` extrinsics instead", ext_builder.name()).into()); + } + let bench = Benchmark::new(client, self.params.bench.clone(), inherent_data, digest_items); + + // per-block execution overhead + { + let stats = bench.bench_block()?; + info!("Per-block execution overhead [ns]:\n{:?}", stats); + let template = TemplateData::new(BenchmarkType::Block, &cfg, &self.params, &stats)?; + template.write(&self.params.weight.weight_path)?; + } + // per-extrinsic execution overhead + { + let stats = bench.bench_extrinsic(ext_builder)?; + info!("Per-extrinsic execution overhead [ns]:\n{:?}", stats); + let template = TemplateData::new(BenchmarkType::Extrinsic, &cfg, &self.params, &stats)?; + template.write(&self.params.weight.weight_path)?; + } + + Ok(()) + } +} + +impl BenchmarkType { + /// Short name of the benchmark type. + pub(crate) fn short_name(&self) -> &'static str { + match self { + Self::Extrinsic => "extrinsic", + Self::Block => "block", + } + } + + /// Long name of the benchmark type. + pub(crate) fn long_name(&self) -> &'static str { + match self { + Self::Extrinsic => "ExtrinsicBase", + Self::Block => "BlockExecution", + } + } +} + +// Boilerplate +impl CliConfiguration for OverheadCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } + + fn import_params(&self) -> Option<&ImportParams> { + Some(&self.import_params) + } + + fn trie_cache_maximum_size(&self) -> Result> { + if self.params.enable_trie_cache { + Ok(self.import_params().map(|x| x.trie_cache_maximum_size()).unwrap_or_default()) + } else { + Ok(None) + } + } +} diff --git a/utils/frame/benchmarking-cli/src/overhead/mod.rs b/utils/frame/benchmarking-cli/src/overhead/mod.rs new file mode 100644 index 0000000000000..fc3db912f7a30 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/mod.rs @@ -0,0 +1,21 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 mod cmd; +pub mod template; + +pub use cmd::OverheadCmd; diff --git a/utils/frame/benchmarking-cli/src/overhead/template.rs b/utils/frame/benchmarking-cli/src/overhead/template.rs new file mode 100644 index 0000000000000..ceed34d1981f9 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/template.rs @@ -0,0 +1,125 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Converts a benchmark result into [`TemplateData`] and writes +//! it into the `weights.hbs` template. + +use sc_cli::Result; +use sc_service::Configuration; + +use handlebars::Handlebars; +use log::info; +use serde::Serialize; +use std::{env, fs, path::PathBuf}; + +use crate::{ + overhead::cmd::{BenchmarkType, OverheadParams}, + shared::{Stats, UnderscoreHelper}, +}; + +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static TEMPLATE: &str = include_str!("./weights.hbs"); + +/// Data consumed by Handlebar to fill out the `weights.hbs` template. +#[derive(Serialize, Debug, Clone)] +pub(crate) struct TemplateData { + /// Short name of the benchmark. Can be "block" or "extrinsic". + long_name: String, + /// Long name of the benchmark. Can be "BlockExecution" or "ExtrinsicBase". + short_name: String, + /// Name of the runtime. Taken from the chain spec. + runtime_name: String, + /// Version of the benchmarking CLI used. + version: String, + /// Date that the template was filled out. + date: String, + /// Hostname of the machine that executed the benchmarks. + hostname: String, + /// CPU name of the machine that executed the benchmarks. + cpuname: String, + /// Header for the generated file. + header: String, + /// Command line arguments that were passed to the CLI. + args: Vec, + /// Params of the executed command. + params: OverheadParams, + /// Stats about the benchmark result. + stats: Stats, + /// The resulting weight in ns. + weight: u64, +} + +impl TemplateData { + /// Returns a new [`Self`] from the given params. + pub(crate) fn new( + t: BenchmarkType, + cfg: &Configuration, + params: &OverheadParams, + stats: &Stats, + ) -> Result { + let weight = params.weight.calc_weight(stats)?; + let header = params + .header + .as_ref() + .map(|p| std::fs::read_to_string(p)) + .transpose()? + .unwrap_or_default(); + + Ok(TemplateData { + short_name: t.short_name().into(), + long_name: t.long_name().into(), + runtime_name: cfg.chain_spec.name().into(), + version: VERSION.into(), + date: chrono::Utc::now().format("%Y-%m-%d (Y/M/D)").to_string(), + hostname: params.hostinfo.hostname(), + cpuname: params.hostinfo.cpuname(), + header, + args: env::args().collect::>(), + params: params.clone(), + stats: stats.clone(), + weight, + }) + } + + /// Fill out the `weights.hbs` HBS template with its own data. + /// Writes the result to `path` which can be a directory or a file. + pub fn write(&self, path: &Option) -> Result<()> { + let mut handlebars = Handlebars::new(); + // Format large integers with underscores. + handlebars.register_helper("underscore", Box::new(UnderscoreHelper)); + // Don't HTML escape any characters. + handlebars.register_escape_fn(|s| -> String { s.to_string() }); + + let out_path = self.build_path(path)?; + let mut fd = fs::File::create(&out_path)?; + info!("Writing weights to {:?}", fs::canonicalize(&out_path)?); + handlebars + .render_template_to_write(TEMPLATE, &self, &mut fd) + .map_err(|e| format!("HBS template write: {:?}", e).into()) + } + + /// Build a path for the weight file. + fn build_path(&self, weight_out: &Option) -> Result { + let mut path = weight_out.clone().unwrap_or_else(|| PathBuf::from(".")); + + if !path.is_dir() { + return Err("Need directory as --weight-path".into()) + } + path.push(format!("{}_weights.rs", self.short_name)); + Ok(path) + } +} diff --git a/utils/frame/benchmarking-cli/src/overhead/weights.hbs b/utils/frame/benchmarking-cli/src/overhead/weights.hbs new file mode 100644 index 0000000000000..c54393d200bd3 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/weights.hbs @@ -0,0 +1,76 @@ +{{header}} +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} +//! DATE: {{date}} +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` +//! +//! SHORT-NAME: `{{short_name}}`, LONG-NAME: `{{long_name}}`, RUNTIME: `{{runtime_name}}` +//! WARMUPS: `{{params.bench.warmup}}`, REPEAT: `{{params.bench.repeat}}` +//! WEIGHT-PATH: `{{params.weight.weight_path}}` +//! WEIGHT-METRIC: `{{params.weight.weight_metric}}`, WEIGHT-MUL: `{{params.weight.weight_mul}}`, WEIGHT-ADD: `{{params.weight.weight_add}}` + +// Executed Command: +{{#each args as |arg|}} +// {{arg}} +{{/each}} + +use sp_core::parameter_types; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; + +parameter_types! { + {{#if (eq short_name "block")}} + /// Time to execute an empty block. + {{else}} + /// Time to execute a NO-OP extrinsic, for example `System::remark`. + {{/if}} + /// Calculated by multiplying the *{{params.weight.weight_metric}}* with `{{params.weight.weight_mul}}` and adding `{{params.weight.weight_add}}`. + /// + /// Stats nanoseconds: + /// Min, Max: {{underscore stats.min}}, {{underscore stats.max}} + /// Average: {{underscore stats.avg}} + /// Median: {{underscore stats.median}} + /// Std-Dev: {{stats.stddev}} + /// + /// Percentiles nanoseconds: + /// 99th: {{underscore stats.p99}} + /// 95th: {{underscore stats.p95}} + /// 75th: {{underscore stats.p75}} + pub const {{long_name}}Weight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul({{underscore weight}})); +} + +#[cfg(test)] +mod test_weights { + use sp_weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::{{long_name}}Weight::get(); + + {{#if (eq short_name "block")}} + // At least 100 µs. + assert!( + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 100 µs." + ); + // At most 50 ms. + assert!( + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 50 ms." + ); + {{else}} + // At least 10 µs. + assert!( + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 10 µs." + ); + // At most 1 ms. + assert!( + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 1 ms." + ); + {{/if}} + } +} diff --git a/utils/frame/benchmarking-cli/src/pallet/README.md b/utils/frame/benchmarking-cli/src/pallet/README.md new file mode 100644 index 0000000000000..72845652de653 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/pallet/README.md @@ -0,0 +1,3 @@ +The pallet command is explained in [frame/benchmarking](../../../../../frame/benchmarking/README.md). + +License: Apache-2.0 diff --git a/utils/frame/benchmarking-cli/src/command.rs b/utils/frame/benchmarking-cli/src/pallet/command.rs similarity index 63% rename from utils/frame/benchmarking-cli/src/command.rs rename to utils/frame/benchmarking-cli/src/pallet/command.rs index 5efa970d93580..242f0e685290f 100644 --- a/utils/frame/benchmarking-cli/src/command.rs +++ b/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::BenchmarkCmd; +use super::{writer, PalletCmd}; use codec::{Decode, Encode}; use frame_benchmarking::{ Analysis, BenchmarkBatch, BenchmarkBatchSplitResults, BenchmarkList, BenchmarkParameter, @@ -23,19 +23,36 @@ use frame_benchmarking::{ }; use frame_support::traits::StorageInfo; use linked_hash_map::LinkedHashMap; -use sc_cli::{CliConfiguration, ExecutionStrategy, Result, SharedParams}; +use sc_cli::{ + execution_method_from_cli, CliConfiguration, ExecutionStrategy, Result, SharedParams, +}; use sc_client_db::BenchmarkingState; use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; +use serde::Serialize; use sp_core::offchain::{ testing::{TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }; use sp_externalities::Extensions; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStorePtr}; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use sp_state_machine::StateMachine; -use std::{fmt::Debug, sync::Arc, time}; +use std::{collections::HashMap, fmt::Debug, fs, sync::Arc, time}; + +/// Logging target +const LOG_TARGET: &'static str = "frame::benchmark::pallet"; + +/// The inclusive range of a component. +#[derive(Serialize, Debug, Clone, Eq, PartialEq)] +pub(crate) struct ComponentRange { + /// Name of the component. + name: String, + /// Minimal valid value of the component. + min: u32, + /// Maximal valid value of the component. + max: u32, +} // This takes multiple benchmark batches and combines all the results where the pallet, instance, // and benchmark are the same. @@ -87,7 +104,13 @@ fn combine_batches( .collect::>() } -impl BenchmarkCmd { +/// Explains possible reasons why the metadata for the benchmarking could not be found. +const ERROR_METADATA_NOT_FOUND: &'static str = "Did not find the benchmarking metadata. \ +This could mean that you either did not build the node correctly with the \ +`--features runtime-benchmarks` flag, or the chain spec that you are using was \ +not created by a node that was compiled with the flag"; + +impl PalletCmd { /// Runs the command and benchmarks the chain. pub fn run(&self, config: Configuration) -> Result<()> where @@ -114,13 +137,27 @@ impl BenchmarkCmd { }; } + if let Some(json_input) = &self.json_input { + let raw_data = match std::fs::read(json_input) { + Ok(raw_data) => raw_data, + Err(error) => + return Err(format!("Failed to read {:?}: {}", json_input, error).into()), + }; + let batches: Vec = match serde_json::from_slice(&raw_data) { + Ok(batches) => batches, + Err(error) => + return Err(format!("Failed to deserialize {:?}: {}", json_input, error).into()), + }; + return self.output_from_results(&batches) + } + let spec = config.chain_spec; - let wasm_method = self.wasm_method.into(); let strategy = self.execution.unwrap_or(ExecutionStrategy::Native); - let pallet = self.pallet.clone().unwrap_or_else(|| String::new()); + let pallet = self.pallet.clone().unwrap_or_default(); let pallet = pallet.as_bytes(); - let extrinsic = self.extrinsic.clone().unwrap_or_else(|| String::new()); - let extrinsic = extrinsic.as_bytes(); + let extrinsic = self.extrinsic.clone().unwrap_or_default(); + let extrinsic_split: Vec<&str> = extrinsic.split(',').collect(); + let extrinsics: Vec<_> = extrinsic_split.iter().map(|x| x.trim().as_bytes()).collect(); let genesis_storage = spec.build_storage()?; let mut changes = Default::default(); @@ -134,9 +171,10 @@ impl BenchmarkCmd { let state_without_tracking = BenchmarkingState::::new(genesis_storage, cache_size, self.record_proof, false)?; let executor = NativeElseWasmExecutor::::new( - wasm_method, + execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy), self.heap_pages, 2, // The runtime instances cache size. + 2, // The runtime cache size ); let extensions = || -> Extensions { @@ -147,14 +185,13 @@ impl BenchmarkCmd { extensions.register(OffchainWorkerExt::new(offchain.clone())); extensions.register(OffchainDbExt::new(offchain)); extensions.register(TransactionPoolExt::new(pool)); - return extensions + extensions }; // Get Benchmark List let state = &state_without_tracking; - let result = StateMachine::<_, _, NumberFor, _>::new( + let result = StateMachine::new( state, - None, &mut changes, &executor, "Benchmark_benchmark_metadata", @@ -164,7 +201,7 @@ impl BenchmarkCmd { sp_core::testing::TaskExecutor::new(), ) .execute(strategy.into()) - .map_err(|e| format!("Error getting benchmark list: {:?}", e))?; + .map_err(|e| format!("{}: {}", ERROR_METADATA_NOT_FOUND, e))?; let (list, storage_info) = <(Vec, Vec) as Decode>::decode(&mut &result[..]) @@ -176,9 +213,10 @@ impl BenchmarkCmd { .filter(|item| pallet.is_empty() || pallet == &b"*"[..] || pallet == &item.pallet[..]) .for_each(|item| { for benchmark in &item.benchmarks { + let benchmark_name = &benchmark.name; if extrinsic.is_empty() || - &extrinsic[..] == &b"*"[..] || - extrinsic == benchmark.name + extrinsic.as_bytes() == &b"*"[..] || + extrinsics.contains(&&benchmark_name[..]) { benchmarks_to_run.push(( item.pallet.clone(), @@ -203,7 +241,16 @@ impl BenchmarkCmd { let mut batches = Vec::new(); let mut batches_db = Vec::new(); let mut timer = time::SystemTime::now(); + // Maps (pallet, extrinsic) to its component ranges. + let mut component_ranges = HashMap::<(Vec, Vec), Vec>::new(); + for (pallet, extrinsic, components) in benchmarks_to_run { + log::info!( + target: LOG_TARGET, + "Starting benchmark: {}::{}", + String::from_utf8(pallet.clone()).expect("Encoded from String; qed"), + String::from_utf8(extrinsic.clone()).expect("Encoded from String; qed"), + ); let all_components = if components.is_empty() { vec![Default::default()] } else { @@ -212,14 +259,21 @@ impl BenchmarkCmd { let lowest = self.lowest_range_values.get(idx).cloned().unwrap_or(*low); let highest = self.highest_range_values.get(idx).cloned().unwrap_or(*high); - let diff = highest - lowest; + let diff = + highest.checked_sub(lowest).ok_or("`low` cannot be higher than `high`")?; + + // The slope logic needs at least two points + // to compute a slope. + if self.steps < 2 { + return Err("`steps` must be at least 2.".into()) + } + + let step_size = (diff as f32 / (self.steps - 1) as f32).max(0.0); - // Create up to `STEPS` steps for that component between high and low. - let step_size = (diff / self.steps).max(1); - let num_of_steps = diff / step_size + 1; - for s in 0..num_of_steps { + for s in 0..self.steps { // This is the value we will be testing for component `name` - let component_value = lowest + step_size * s; + let component_value = + ((lowest as f32 + step_size * s as f32) as u32).clamp(lowest, highest); // Select the max value for all the other components. let c: Vec<(BenchmarkParameter, u32)> = components @@ -235,23 +289,26 @@ impl BenchmarkCmd { .collect(); all_components.push(c); } + + component_ranges + .entry((pallet.clone(), extrinsic.clone())) + .or_default() + .push(ComponentRange { name: name.to_string(), min: lowest, max: highest }); } all_components }; for (s, selected_components) in all_components.iter().enumerate() { // First we run a verification if !self.no_verify { - // Dont use these results since verification code will add overhead let state = &state_without_tracking; - let _results = StateMachine::<_, _, NumberFor, _>::new( + let result = StateMachine::new( state, - None, &mut changes, &executor, "Benchmark_dispatch_benchmark", &( - &pallet.clone(), - &extrinsic.clone(), + &pallet, + &extrinsic, &selected_components.clone(), true, // run verification code 1, // no need to do internal repeats @@ -264,15 +321,28 @@ impl BenchmarkCmd { ) .execute(strategy.into()) .map_err(|e| { - format!("Error executing and verifying runtime benchmark: {:?}", e) + format!("Error executing and verifying runtime benchmark: {}", e) })?; + // Dont use these results since verification code will add overhead. + let _batch = + , String> as Decode>::decode( + &mut &result[..], + ) + .map_err(|e| format!("Failed to decode benchmark results: {:?}", e))? + .map_err(|e| { + format!( + "Benchmark {}::{} failed: {}", + String::from_utf8_lossy(&pallet), + String::from_utf8_lossy(&extrinsic), + e + ) + })?; } // Do one loop of DB tracking. { let state = &state_with_tracking; - let result = StateMachine::<_, _, NumberFor, _>::new( + let result = StateMachine::new( state, // todo remove tracking - None, &mut changes, &executor, "Benchmark_dispatch_benchmark", @@ -290,7 +360,7 @@ impl BenchmarkCmd { sp_core::testing::TaskExecutor::new(), ) .execute(strategy.into()) - .map_err(|e| format!("Error executing runtime benchmark: {:?}", e))?; + .map_err(|e| format!("Error executing runtime benchmark: {}", e))?; let batch = , String> as Decode>::decode( @@ -303,9 +373,8 @@ impl BenchmarkCmd { // Finally run a bunch of loops to get extrinsic timing information. for r in 0..self.external_repeat { let state = &state_without_tracking; - let result = StateMachine::<_, _, NumberFor, _>::new( + let result = StateMachine::new( state, // todo remove tracking - None, &mut changes, &executor, "Benchmark_dispatch_benchmark", @@ -323,7 +392,7 @@ impl BenchmarkCmd { sp_core::testing::TaskExecutor::new(), ) .execute(strategy.into()) - .map_err(|e| format!("Error executing runtime benchmark: {:?}", e))?; + .map_err(|e| format!("Error executing runtime benchmark: {}", e))?; let batch = , String> as Decode>::decode( @@ -334,18 +403,21 @@ impl BenchmarkCmd { batches.extend(batch); // Show progress information - if let Some(elapsed) = timer.elapsed().ok() { + if let Ok(elapsed) = timer.elapsed() { if elapsed >= time::Duration::from_secs(5) { timer = time::SystemTime::now(); + log::info!( - "Running Benchmark:\t{}\t{}\t{}/{}\t{}/{}", + target: LOG_TARGET, + "Running Benchmark: {}.{}({} args) {}/{} {}/{}", String::from_utf8(pallet.clone()) .expect("Encoded from String; qed"), String::from_utf8(extrinsic.clone()) .expect("Encoded from String; qed"), - s, // todo show step - self.steps, - r, + components.len(), + s + 1, // s starts at 0. + all_components.len(), + r + 1, self.external_repeat, ); } @@ -356,69 +428,111 @@ impl BenchmarkCmd { // Combine all of the benchmark results, so that benchmarks of the same pallet/function // are together. - let batches: Vec = combine_batches(batches, batches_db); + let batches = combine_batches(batches, batches_db); + self.output(&batches, &storage_info, &component_ranges) + } + fn output( + &self, + batches: &[BenchmarkBatchSplitResults], + storage_info: &[StorageInfo], + component_ranges: &HashMap<(Vec, Vec), Vec>, + ) -> Result<()> { + // Jsonify the result and write it to a file or stdout if desired. + if !self.jsonify(&batches)? { + // Print the summary only if `jsonify` did not write to stdout. + self.print_summary(&batches, &storage_info) + } + + // Create the weights.rs file. if let Some(output_path) = &self.output { - crate::writer::write_results(&batches, &storage_info, output_path, self)?; + writer::write_results(&batches, &storage_info, &component_ranges, output_path, self)?; + } + + Ok(()) + } + + fn output_from_results(&self, batches: &[BenchmarkBatchSplitResults]) -> Result<()> { + let mut component_ranges = + HashMap::<(Vec, Vec), HashMap>::new(); + for batch in batches { + let range = component_ranges + .entry((batch.pallet.clone(), batch.benchmark.clone())) + .or_default(); + for result in &batch.time_results { + for (param, value) in &result.components { + let name = param.to_string(); + let (ref mut min, ref mut max) = range.entry(name).or_insert((*value, *value)); + if *value < *min { + *min = *value; + } + if *value > *max { + *max = *value; + } + } + } } - for batch in batches.into_iter() { + let component_ranges: HashMap<_, _> = component_ranges + .into_iter() + .map(|(key, ranges)| { + let ranges = ranges + .into_iter() + .map(|(name, (min, max))| ComponentRange { name, min, max }) + .collect(); + (key, ranges) + }) + .collect(); + + self.output(batches, &[], &component_ranges) + } + + /// Jsonifies the passed batches and writes them to stdout or into a file. + /// Can be configured via `--json` and `--json-file`. + /// Returns whether it wrote to stdout. + fn jsonify(&self, batches: &[BenchmarkBatchSplitResults]) -> Result { + if self.json_output || self.json_file.is_some() { + let json = serde_json::to_string_pretty(&batches) + .map_err(|e| format!("Serializing into JSON: {:?}", e))?; + + if let Some(path) = &self.json_file { + fs::write(path, json)?; + } else { + print!("{json}"); + return Ok(true) + } + } + + Ok(false) + } + + /// Prints the results as human-readable summary without raw timing data. + fn print_summary(&self, batches: &[BenchmarkBatchSplitResults], storage_info: &[StorageInfo]) { + for batch in batches.iter() { // Print benchmark metadata println!( - "Pallet: {:?}, Extrinsic: {:?}, Lowest values: {:?}, Highest values: {:?}, Steps: {:?}, Repeat: {:?}", - String::from_utf8(batch.pallet).expect("Encoded from String; qed"), - String::from_utf8(batch.benchmark).expect("Encoded from String; qed"), - self.lowest_range_values, - self.highest_range_values, - self.steps, - self.repeat, - ); + "Pallet: {:?}, Extrinsic: {:?}, Lowest values: {:?}, Highest values: {:?}, Steps: {:?}, Repeat: {:?}", + String::from_utf8(batch.pallet.clone()).expect("Encoded from String; qed"), + String::from_utf8(batch.benchmark.clone()).expect("Encoded from String; qed"), + self.lowest_range_values, + self.highest_range_values, + self.steps, + self.repeat, + ); // Skip raw data + analysis if there are no results if batch.time_results.is_empty() { continue } - if self.raw_data { - // Print the table header - batch.time_results[0] - .components - .iter() - .for_each(|param| print!("{:?},", param.0)); - - print!("extrinsic_time_ns,storage_root_time_ns,reads,repeat_reads,writes,repeat_writes,proof_size_bytes\n"); - // Print the values - batch.time_results.iter().for_each(|result| { - let parameters = &result.components; - parameters.iter().for_each(|param| print!("{:?},", param.1)); - // Print extrinsic time and storage root time - print!( - "{:?},{:?},{:?},{:?},{:?},{:?},{:?}\n", - result.extrinsic_time, - result.storage_root_time, - result.reads, - result.repeat_reads, - result.writes, - result.repeat_writes, - result.proof_size, - ); - }); - - println!(); - } - if !self.no_storage_info { let mut comments: Vec = Default::default(); - crate::writer::add_storage_comments( - &mut comments, - &batch.db_results, - &storage_info, - ); + writer::add_storage_comments(&mut comments, &batch.db_results, storage_info); println!("Raw Storage Info\n========"); for comment in comments { println!("{}", comment); } - println!(""); + println!(); } // Conduct analysis. @@ -439,7 +553,7 @@ impl BenchmarkCmd { { println!("Writes = {:?}", analysis); } - println!(""); + println!(); } if !self.no_min_squares { println!("Min Squares Analysis\n========"); @@ -458,15 +572,13 @@ impl BenchmarkCmd { { println!("Writes = {:?}", analysis); } - println!(""); + println!(); } } - - Ok(()) } } -impl CliConfiguration for BenchmarkCmd { +impl CliConfiguration for PalletCmd { fn shared_params(&self) -> &SharedParams { &self.shared_params } diff --git a/utils/frame/benchmarking-cli/src/pallet/mod.rs b/utils/frame/benchmarking-cli/src/pallet/mod.rs new file mode 100644 index 0000000000000..b10f531bc0aed --- /dev/null +++ b/utils/frame/benchmarking-cli/src/pallet/mod.rs @@ -0,0 +1,175 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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 command; +mod writer; + +use crate::shared::HostInfoParams; +use sc_cli::{ + ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy, + DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, +}; +use std::{fmt::Debug, path::PathBuf}; + +// Add a more relaxed parsing for pallet names by allowing pallet directory names with `-` to be +// used like crate names with `_` +fn parse_pallet_name(pallet: &str) -> std::result::Result { + Ok(pallet.replace("-", "_")) +} + +/// Benchmark the extrinsic weight of FRAME Pallets. +#[derive(Debug, clap::Parser)] +pub struct PalletCmd { + /// Select a FRAME Pallet to benchmark, or `*` for all (in which case `extrinsic` must be `*`). + #[arg(short, long, value_parser = parse_pallet_name, required_unless_present_any = ["list", "json_input"])] + pub pallet: Option, + + /// Select an extrinsic inside the pallet to benchmark, or `*` for all. + #[arg(short, long, required_unless_present_any = ["list", "json_input"])] + pub extrinsic: Option, + + /// Select how many samples we should take across the variable components. + #[arg(short, long, default_value_t = 2)] + pub steps: u32, + + /// Indicates lowest values for each of the component ranges. + #[arg(long = "low", value_delimiter = ',')] + pub lowest_range_values: Vec, + + /// Indicates highest values for each of the component ranges. + #[arg(long = "high", value_delimiter = ',')] + pub highest_range_values: Vec, + + /// Select how many repetitions of this benchmark should run from within the wasm. + #[arg(short, long, default_value_t = 1)] + pub repeat: u32, + + /// Select how many repetitions of this benchmark should run from the client. + /// + /// NOTE: Using this alone may give slower results, but will afford you maximum Wasm memory. + #[arg(long, default_value_t = 1)] + pub external_repeat: u32, + + /// Print the raw results in JSON format. + #[arg(long = "json")] + pub json_output: bool, + + /// Write the raw results in JSON format into the given file. + #[arg(long, conflicts_with = "json_output")] + pub json_file: Option, + + /// Don't print the median-slopes linear regression analysis. + #[arg(long)] + pub no_median_slopes: bool, + + /// Don't print the min-squares linear regression analysis. + #[arg(long)] + pub no_min_squares: bool, + + /// Output the benchmarks to a Rust file at the given path. + #[arg(long)] + pub output: Option, + + /// Add a header file to your outputted benchmarks. + #[arg(long)] + pub header: Option, + + /// Path to Handlebars template file used for outputting benchmark results. (Optional) + #[arg(long)] + pub template: Option, + + #[allow(missing_docs)] + #[clap(flatten)] + pub hostinfo_params: HostInfoParams, + + /// Which analysis function to use when outputting benchmarks: + /// * min-squares (default) + /// * median-slopes + /// * max (max of min squares and median slopes for each value) + #[arg(long)] + pub output_analysis: Option, + + /// Set the heap pages while running benchmarks. If not set, the default value from the client + /// is used. + #[arg(long)] + pub heap_pages: Option, + + /// Disable verification logic when running benchmarks. + #[arg(long)] + pub no_verify: bool, + + /// Display and run extra benchmarks that would otherwise not be needed for weight + /// construction. + #[arg(long)] + pub extra: bool, + + /// Estimate PoV size. + #[arg(long)] + pub record_proof: bool, + + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: sc_cli::SharedParams, + + /// The execution strategy that should be used for benchmarks. + #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] + pub execution: Option, + + /// Method for executing Wasm runtime code. + #[arg( + long = "wasm-execution", + value_name = "METHOD", + value_enum, + ignore_case = true, + default_value_t = DEFAULT_WASM_EXECUTION_METHOD, + )] + pub wasm_method: WasmExecutionMethod, + + /// The WASM instantiation method to use. + /// + /// Only has an effect when `wasm-execution` is set to `compiled`. + #[arg( + long = "wasm-instantiation-strategy", + value_name = "STRATEGY", + default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, + value_enum, + )] + pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, + + /// Limit the memory the database cache can use. + #[arg(long = "db-cache", value_name = "MiB", default_value_t = 1024)] + pub database_cache_size: u32, + + /// List the benchmarks that match your query rather than running them. + /// + /// When nothing is provided, we list all benchmarks. + #[arg(long)] + pub list: bool, + + /// If enabled, the storage info is not displayed in the output next to the analysis. + /// + /// This is independent of the storage info appearing in the *output file*. Use a Handlebar + /// template for that purpose. + #[arg(long)] + pub no_storage_info: bool, + + /// A path to a `.json` file with existing benchmark results generated with `--json` or + /// `--json-file`. When specified the benchmarks are not actually executed, and the data for + /// the analysis is read from this file. + #[arg(long)] + pub json_input: Option, +} diff --git a/utils/frame/benchmarking-cli/src/template.hbs b/utils/frame/benchmarking-cli/src/pallet/template.hbs similarity index 51% rename from utils/frame/benchmarking-cli/src/template.hbs rename to utils/frame/benchmarking-cli/src/pallet/template.hbs index 36abf27f59a6e..1a69a3ae20c5f 100644 --- a/utils/frame/benchmarking-cli/src/template.hbs +++ b/utils/frame/benchmarking-cli/src/pallet/template.hbs @@ -3,10 +3,11 @@ //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} //! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` //! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} // Executed Command: -{{#each args as |arg|~}} +{{#each args as |arg|}} // {{arg}} {{/each}} @@ -20,32 +21,36 @@ use sp_std::marker::PhantomData; /// Weight functions for `{{pallet}}`. pub struct WeightInfo(PhantomData); impl {{pallet}}::WeightInfo for WeightInfo { - {{~#each benchmarks as |benchmark|}} - {{~#each benchmark.comments as |comment|}} + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} // {{comment}} - {{~/each}} + {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} ) -> Weight { - ({{underscore benchmark.base_weight}} as Weight) - {{~#each benchmark.component_weight as |cw|}} + // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. + Weight::from_ref_time({{underscore benchmark.base_weight}}) + {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)) - {{~/each}} - {{~#if (ne benchmark.base_reads "0")}} - .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}} as Weight)) - {{~/if}} - {{~#each benchmark.component_reads as |cr|}} - .saturating_add(T::DbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight))) - {{~/each}} - {{~#if (ne benchmark.base_writes "0")}} - .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}} as Weight)) - {{~/if}} - {{~#each benchmark.component_writes as |cw|}} - .saturating_add(T::DbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))) - {{~/each}} + .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} } - {{~/each}} + {{/each}} } diff --git a/utils/frame/benchmarking-cli/src/writer.rs b/utils/frame/benchmarking-cli/src/pallet/writer.rs similarity index 85% rename from utils/frame/benchmarking-cli/src/writer.rs rename to utils/frame/benchmarking-cli/src/pallet/writer.rs index ede5b2d1355a7..a52bbcd229cb1 100644 --- a/utils/frame/benchmarking-cli/src/writer.rs +++ b/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ // Outputs benchmark results to Rust files that can be ingested by the runtime. -use core::convert::TryInto; use std::{ collections::{HashMap, HashSet}, fs, @@ -25,18 +24,18 @@ use std::{ }; use inflector::Inflector; +use itertools::Itertools; use serde::Serialize; -use crate::BenchmarkCmd; +use crate::{pallet::command::ComponentRange, shared::UnderscoreHelper, PalletCmd}; use frame_benchmarking::{ Analysis, AnalysisChoice, BenchmarkBatchSplitResults, BenchmarkResult, BenchmarkSelector, - RegressionModel, }; use frame_support::traits::StorageInfo; use sp_core::hexdisplay::HexDisplay; use sp_runtime::traits::Zero; -const VERSION: &'static str = env!("CARGO_PKG_VERSION"); +const VERSION: &str = env!("CARGO_PKG_VERSION"); const TEMPLATE: &str = include_str!("./template.hbs"); // This is the final structure we will pass to the Handlebars template. @@ -44,6 +43,8 @@ const TEMPLATE: &str = include_str!("./template.hbs"); struct TemplateData { args: Vec, date: String, + hostname: String, + cpuname: String, version: String, pallet: String, instance: String, @@ -66,10 +67,13 @@ struct BenchmarkData { component_weight: Vec, component_reads: Vec, component_writes: Vec, + component_ranges: Vec, comments: Vec, + #[serde(serialize_with = "string_serialize")] + min_execution_time: u128, } -// This forwards some specific metadata from the `BenchmarkCmd` +// This forwards some specific metadata from the `PalletCmd` #[derive(Serialize, Default, Debug, Clone)] struct CmdData { steps: u32, @@ -117,6 +121,7 @@ fn io_error(s: &str) -> std::io::Error { fn map_results( batches: &[BenchmarkBatchSplitResults], storage_info: &[StorageInfo], + component_ranges: &HashMap<(Vec, Vec), Vec>, analysis_choice: &AnalysisChoice, ) -> Result>, std::io::Error> { // Skip if batches is empty. @@ -134,26 +139,31 @@ fn map_results( let pallet_string = String::from_utf8(batch.pallet.clone()).unwrap(); let instance_string = String::from_utf8(batch.instance.clone()).unwrap(); - let benchmark_data = get_benchmark_data(batch, storage_info, analysis_choice); + let benchmark_data = + get_benchmark_data(batch, storage_info, &component_ranges, analysis_choice); let pallet_benchmarks = all_benchmarks.entry((pallet_string, instance_string)).or_default(); pallet_benchmarks.push(benchmark_data); } Ok(all_benchmarks) } -// Get an iterator of errors from a model. If the model is `None` all errors are zero. -fn extract_errors(model: &Option) -> impl Iterator + '_ { - let mut errors = model.as_ref().map(|m| m.se.regressor_values.iter()); - std::iter::from_fn(move || match &mut errors { - Some(model) => model.next().map(|val| *val as u128), - _ => Some(0), - }) +// Get an iterator of errors. +fn extract_errors(errors: &Option>) -> impl Iterator + '_ { + errors + .as_ref() + .map(|e| e.as_slice()) + .unwrap_or(&[]) + .iter() + .copied() + .chain(std::iter::repeat(0)) } // Analyze and return the relevant results for a given benchmark. fn get_benchmark_data( batch: &BenchmarkBatchSplitResults, storage_info: &[StorageInfo], + // Per extrinsic component ranges. + component_ranges: &HashMap<(Vec, Vec), Vec>, analysis_choice: &AnalysisChoice, ) -> BenchmarkData { // You can use this to put any additional comments with the benchmarking output. @@ -184,24 +194,20 @@ fn get_benchmark_data( .slopes .into_iter() .zip(extrinsic_time.names.iter()) - .zip(extract_errors(&extrinsic_time.model)) + .zip(extract_errors(&extrinsic_time.errors)) .for_each(|((slope, name), error)| { if !slope.is_zero() { if !used_components.contains(&name) { used_components.push(name); } - used_extrinsic_time.push(ComponentSlope { - name: name.clone(), - slope: slope.saturating_mul(1000), - error: error.saturating_mul(1000), - }); + used_extrinsic_time.push(ComponentSlope { name: name.clone(), slope, error }); } }); reads .slopes .into_iter() .zip(reads.names.iter()) - .zip(extract_errors(&reads.model)) + .zip(extract_errors(&reads.errors)) .for_each(|((slope, name), error)| { if !slope.is_zero() { if !used_components.contains(&name) { @@ -214,7 +220,7 @@ fn get_benchmark_data( .slopes .into_iter() .zip(writes.names.iter()) - .zip(extract_errors(&writes.model)) + .zip(extract_errors(&writes.errors)) .for_each(|((slope, name), error)| { if !slope.is_zero() { if !used_components.contains(&name) { @@ -237,26 +243,33 @@ fn get_benchmark_data( // We add additional comments showing which storage items were touched. add_storage_comments(&mut comments, &batch.db_results, storage_info); + let component_ranges = component_ranges + .get(&(batch.pallet.clone(), batch.benchmark.clone())) + .map(|c| c.clone()) + .unwrap_or_default(); BenchmarkData { name: String::from_utf8(batch.benchmark.clone()).unwrap(), components, - base_weight: extrinsic_time.base.saturating_mul(1000), + base_weight: extrinsic_time.base, base_reads: reads.base, base_writes: writes.base, component_weight: used_extrinsic_time, component_reads: used_reads, component_writes: used_writes, + component_ranges, comments, + min_execution_time: extrinsic_time.minimum, } } // Create weight file from benchmark data and Handlebars template. -pub fn write_results( +pub(crate) fn write_results( batches: &[BenchmarkBatchSplitResults], storage_info: &[StorageInfo], + component_ranges: &HashMap<(Vec, Vec), Vec>, path: &PathBuf, - cmd: &BenchmarkCmd, + cmd: &PalletCmd, ) -> Result<(), std::io::Error> { // Use custom template if provided. let template: String = match &cmd.template { @@ -281,12 +294,12 @@ pub fn write_results( // Which analysis function should be used when outputting benchmarks let analysis_choice: AnalysisChoice = - cmd.output_analysis.clone().try_into().map_err(|e| io_error(e))?; + cmd.output_analysis.clone().try_into().map_err(io_error)?; // Capture individual args let cmd_data = CmdData { - steps: cmd.steps.clone(), - repeat: cmd.repeat.clone(), + steps: cmd.steps, + repeat: cmd.repeat, lowest_range_values: cmd.lowest_range_values.clone(), highest_range_values: cmd.highest_range_values.clone(), execution: format!("{:?}", cmd.execution), @@ -304,25 +317,30 @@ pub fn write_results( handlebars.register_escape_fn(|s| -> String { s.to_string() }); // Organize results by pallet into a JSON map - let all_results = map_results(batches, storage_info, &analysis_choice)?; + let all_results = map_results(batches, storage_info, component_ranges, &analysis_choice)?; + let mut created_files = Vec::new(); + for ((pallet, instance), results) in all_results.iter() { let mut file_path = path.clone(); // If a user only specified a directory... if file_path.is_dir() { + // Start with "path/to/pallet_name". + let mut file_name = pallet.clone(); // Check if there might be multiple instances benchmarked. if all_results.keys().any(|(p, i)| p == pallet && i != instance) { - // Create new file: "path/to/pallet_name_instance_name.rs". - file_path.push(pallet.clone() + "_" + &instance.to_snake_case()); - } else { - // Create new file: "path/to/pallet_name.rs". - file_path.push(pallet.clone()); + // Append "_instance_name". + file_name = format!("{}_{}", file_name, instance.to_snake_case()); } + // "mod::pallet_name.rs" becomes "mod_pallet_name.rs". + file_path.push(file_name.replace("::", "_")); file_path.set_extension("rs"); } let hbs_data = TemplateData { args: args.clone(), date: date.clone(), + hostname: cmd.hostinfo_params.hostname(), + cpuname: cmd.hostinfo_params.cpuname(), version: VERSION.to_string(), pallet: pallet.to_string(), instance: instance.to_string(), @@ -331,10 +349,18 @@ pub fn write_results( benchmarks: results.clone(), }; - let mut output_file = fs::File::create(file_path)?; + let mut output_file = fs::File::create(&file_path)?; handlebars .render_template_to_write(&template, &hbs_data, &mut output_file) .map_err(|e| io_error(&e.to_string()))?; + println!("Created file: {:?}", &file_path); + created_files.push(file_path); + } + + for file in created_files.iter().duplicates() { + // This can happen when there are multiple instances of a pallet deployed + // and `--output` forces the output of all instances into the same file. + println!("Multiple benchmarks were written to the same file: {:?}.", file); } Ok(()) } @@ -375,7 +401,7 @@ pub(crate) fn add_storage_comments( // This tracks the keys we already identified, so we only generate a single comment. let mut identified = HashSet::>::new(); - for result in results.clone() { + for result in results { for (key, reads, writes, whitelisted) in &result.keys { // skip keys which are whitelisted if *whitelisted { @@ -417,44 +443,6 @@ pub(crate) fn add_storage_comments( } } -// Add an underscore after every 3rd character, i.e. a separator for large numbers. -fn underscore(i: Number) -> String -where - Number: std::string::ToString, -{ - let mut s = String::new(); - let i_str = i.to_string(); - let a = i_str.chars().rev().enumerate(); - for (idx, val) in a { - if idx != 0 && idx % 3 == 0 { - s.insert(0, '_'); - } - s.insert(0, val); - } - s -} - -// A Handlebars helper to add an underscore after every 3rd character, -// i.e. a separator for large numbers. -#[derive(Clone, Copy)] -struct UnderscoreHelper; -impl handlebars::HelperDef for UnderscoreHelper { - fn call<'reg: 'rc, 'rc>( - &self, - h: &handlebars::Helper, - _: &handlebars::Handlebars, - _: &handlebars::Context, - _rc: &mut handlebars::RenderContext, - out: &mut dyn handlebars::Output, - ) -> handlebars::HelperResult { - use handlebars::JsonRender; - let param = h.param(0).unwrap(); - let underscore_param = underscore(param.value().render()); - out.write(&underscore_param)?; - Ok(()) - } -} - // A helper to join a string of vectors. #[derive(Clone, Copy)] struct JoinHelper; @@ -566,6 +554,7 @@ mod test { test_data(b"second", b"first", BenchmarkParameter::c, 3, 4), ], &[], + &Default::default(), &AnalysisChoice::default(), ) .unwrap(); diff --git a/utils/frame/benchmarking-cli/src/shared/README.md b/utils/frame/benchmarking-cli/src/shared/README.md new file mode 100644 index 0000000000000..08e25b0e08f76 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/shared/README.md @@ -0,0 +1,16 @@ +# Shared code + +Contains code that is shared among multiple sub-commands. + +## Arguments + +- `--mul` Multiply the result with a factor. Can be used to manually adjust for future chain growth. +- `--add` Add a value to the result. Can be used to manually offset the results. +- `--metric` Set the metric to use for calculating the final weight from the raw data. Defaults to `average`. +- `--weight-path` Set the file or directory to write the weight files to. +- `--db` The database backend to use. This depends on your snapshot. +- `--pruning` Set the pruning mode of the node. Some benchmarks require you to set this to `archive`. +- `--base-path` The location on the disk that should be used for the benchmarks. You can try this on different disks or even on a mounted RAM-disk. It is important to use the same location that will later-on be used to store the chain data to get the correct results. +- `--header` Optional file header which will be prepended to the weight output file. Can be used for adding LICENSE headers. + +License: Apache-2.0 diff --git a/utils/frame/benchmarking-cli/src/shared/mod.rs b/utils/frame/benchmarking-cli/src/shared/mod.rs new file mode 100644 index 0000000000000..ea5415f33f020 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/shared/mod.rs @@ -0,0 +1,134 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Code that is shared among all benchmarking sub-commands. + +pub mod record; +pub mod stats; +pub mod weight_params; + +pub use record::BenchRecord; +pub use stats::{StatSelect, Stats}; +pub use weight_params::WeightParams; + +use clap::Args; +use rand::prelude::*; +use sc_sysinfo::gather_sysinfo; +use serde::Serialize; + +/// A Handlebars helper to add an underscore after every 3rd character, +/// i.e. a separator for large numbers. +#[derive(Clone, Copy)] +pub struct UnderscoreHelper; + +impl handlebars::HelperDef for UnderscoreHelper { + fn call<'reg: 'rc, 'rc>( + &self, + h: &handlebars::Helper, + _: &handlebars::Handlebars, + _: &handlebars::Context, + _rc: &mut handlebars::RenderContext, + out: &mut dyn handlebars::Output, + ) -> handlebars::HelperResult { + use handlebars::JsonRender; + let param = h.param(0).unwrap(); + let underscore_param = underscore(param.value().render()); + out.write(&underscore_param)?; + Ok(()) + } +} + +/// Add an underscore after every 3rd character, i.e. a separator for large numbers. +fn underscore(i: Number) -> String +where + Number: std::string::ToString, +{ + let mut s = String::new(); + let i_str = i.to_string(); + let a = i_str.chars().rev().enumerate(); + for (idx, val) in a { + if idx != 0 && idx % 3 == 0 { + s.insert(0, '_'); + } + s.insert(0, val); + } + s +} + +/// Returns an rng and the seed that was used to create it. +/// +/// Uses a random seed if none is provided. +pub fn new_rng(seed: Option) -> (impl rand::Rng, u64) { + let seed = seed.unwrap_or(rand::thread_rng().gen::()); + (rand_pcg::Pcg64::seed_from_u64(seed), seed) +} + +/// Returns an error if a debug profile is detected. +/// +/// The rust compiler only exposes the binary information whether +/// or not we are in a `debug` build. +/// This means that `release` and `production` cannot be told apart. +/// This function additionally checks for OPT-LEVEL = 3. +pub fn check_build_profile() -> Result<(), String> { + if cfg!(build_profile = "debug") { + Err("Detected a `debug` profile".into()) + } else if !cfg!(build_opt_level = "3") { + Err("The optimization level is not set to 3".into()) + } else { + Ok(()) + } +} + +/// Parameters to configure how the host info will be determined. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +#[command(rename_all = "kebab-case")] +pub struct HostInfoParams { + /// Manually override the hostname to use. + #[arg(long)] + pub hostname_override: Option, + + /// Specify a fallback hostname if no-one could be detected automatically. + /// + /// Note: This only exists to make the `hostname` function infallible. + #[arg(long, default_value = "")] + pub hostname_fallback: String, + + /// Specify a fallback CPU name if no-one could be detected automatically. + /// + /// Note: This only exists to make the `cpuname` function infallible. + #[arg(long, default_value = "")] + pub cpuname_fallback: String, +} + +impl HostInfoParams { + /// Returns the hostname of the machine. + /// + /// Can be used to track on which machine a benchmark was run. + pub fn hostname(&self) -> String { + self.hostname_override + .clone() + .or(gethostname::gethostname().into_string().ok()) + .unwrap_or(self.hostname_fallback.clone()) + } + + /// Returns the CPU name of the current machine. + /// + /// Can be used to track on which machine a benchmark was run. + pub fn cpuname(&self) -> String { + gather_sysinfo().cpu.unwrap_or(self.cpuname_fallback.clone()) + } +} diff --git a/utils/frame/benchmarking-cli/src/shared/record.rs b/utils/frame/benchmarking-cli/src/shared/record.rs new file mode 100644 index 0000000000000..79ab37f651528 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/shared/record.rs @@ -0,0 +1,72 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Defines the [`BenchRecord`] and its facilities for computing [`super::Stats`]. + +use sc_cli::Result; +use sc_service::Configuration; + +use log::info; +use serde::Serialize; +use std::{fs, path::PathBuf, time::Duration}; + +use super::Stats; + +/// Raw output of a Storage benchmark. +#[derive(Debug, Default, Clone, Serialize)] +pub struct BenchRecord { + /// Multi-Map of value sizes and the time that it took to access them. + ns_per_size: Vec<(u64, u64)>, +} + +impl BenchRecord { + /// Appends a new record. Uses safe casts. + pub fn append(&mut self, size: usize, d: Duration) -> Result<()> { + let size: u64 = size.try_into().map_err(|e| format!("Size overflow u64: {}", e))?; + let ns: u64 = d + .as_nanos() + .try_into() + .map_err(|e| format!("Nanoseconds overflow u64: {}", e))?; + self.ns_per_size.push((size, ns)); + Ok(()) + } + + /// Returns the statistics for *time* and *value size*. + pub fn calculate_stats(self) -> Result<(Stats, Stats)> { + let (size, time): (Vec<_>, Vec<_>) = self.ns_per_size.into_iter().unzip(); + let size = Stats::new(&size)?; + let time = Stats::new(&time)?; + Ok((time, size)) // The swap of time/size here is intentional. + } + + /// Unless a path is specified, saves the raw results in a json file in the current directory. + /// Prefixes it with the DB name and suffixed with `path_suffix`. + pub fn save_json(&self, cfg: &Configuration, out_path: &PathBuf, suffix: &str) -> Result<()> { + let mut path = PathBuf::from(out_path); + if path.is_dir() || path.as_os_str().is_empty() { + path.push(&format!("{}_{}", cfg.database, suffix).to_lowercase()); + path.set_extension("json"); + } + + let json = serde_json::to_string_pretty(&self) + .map_err(|e| format!("Serializing as JSON: {:?}", e))?; + + fs::write(&path, json)?; + info!("Raw data written to {:?}", fs::canonicalize(&path)?); + Ok(()) + } +} diff --git a/utils/frame/benchmarking-cli/src/shared/stats.rs b/utils/frame/benchmarking-cli/src/shared/stats.rs new file mode 100644 index 0000000000000..ffae4a17724f8 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/shared/stats.rs @@ -0,0 +1,188 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Handles statistics that were generated from benchmarking results and +//! that can be used to fill out weight templates. + +use sc_cli::Result; + +use serde::Serialize; +use std::{fmt, result, str::FromStr}; + +/// Various statistics that help to gauge the quality of the produced weights. +/// Will be written to the weight file and printed to console. +#[derive(Serialize, Default, Clone)] +pub struct Stats { + /// Sum of all values. + pub sum: u64, + /// Minimal observed value. + pub min: u64, + /// Maximal observed value. + pub max: u64, + + /// Average of all values. + pub avg: u64, + /// Median of all values. + pub median: u64, + /// Standard derivation of all values. + pub stddev: f64, + + /// 99th percentile. At least 99% of all values are below this threshold. + pub p99: u64, + /// 95th percentile. At least 95% of all values are below this threshold. + pub p95: u64, + /// 75th percentile. At least 75% of all values are below this threshold. + pub p75: u64, +} + +/// Selects a specific field from a [`Stats`] object. +/// Not all fields are available. +#[derive(Debug, Clone, Copy, Serialize, PartialEq)] +pub enum StatSelect { + /// Select the maximum. + Maximum, + /// Select the average. + Average, + /// Select the median. + Median, + /// Select the 99th percentile. + P99Percentile, + /// Select the 95th percentile. + P95Percentile, + /// Select the 75th percentile. + P75Percentile, +} + +impl Stats { + /// Calculates statistics and returns them. + pub fn new(xs: &Vec) -> Result { + if xs.is_empty() { + return Err("Empty input is invalid".into()) + } + let (avg, stddev) = Self::avg_and_stddev(xs); + + Ok(Self { + sum: xs.iter().sum(), + min: *xs.iter().min().expect("Checked for non-empty above"), + max: *xs.iter().max().expect("Checked for non-empty above"), + + avg: avg as u64, + median: Self::percentile(xs.clone(), 0.50), + stddev: (stddev * 100.0).round() / 100.0, // round to 1/100 + + p99: Self::percentile(xs.clone(), 0.99), + p95: Self::percentile(xs.clone(), 0.95), + p75: Self::percentile(xs.clone(), 0.75), + }) + } + + /// Returns the selected stat. + pub fn select(&self, s: StatSelect) -> u64 { + match s { + StatSelect::Maximum => self.max, + StatSelect::Average => self.avg, + StatSelect::Median => self.median, + StatSelect::P99Percentile => self.p99, + StatSelect::P95Percentile => self.p95, + StatSelect::P75Percentile => self.p75, + } + } + + /// Returns the *average* and the *standard derivation*. + fn avg_and_stddev(xs: &Vec) -> (f64, f64) { + let avg = xs.iter().map(|x| *x as f64).sum::() / xs.len() as f64; + let variance = xs.iter().map(|x| (*x as f64 - avg).powi(2)).sum::() / xs.len() as f64; + (avg, variance.sqrt()) + } + + /// Returns the specified percentile for the given data. + /// This is best effort since it ignores the interpolation case. + fn percentile(mut xs: Vec, p: f64) -> u64 { + xs.sort(); + let index = (xs.len() as f64 * p).ceil() as usize - 1; + xs[index.clamp(0, xs.len() - 1)] + } +} + +impl fmt::Debug for Stats { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Total: {}", self.sum)?; + writeln!(f, "Min: {}, Max: {}", self.min, self.max)?; + writeln!(f, "Average: {}, Median: {}, Stddev: {}", self.avg, self.median, self.stddev)?; + write!(f, "Percentiles 99th, 95th, 75th: {}, {}, {}", self.p99, self.p95, self.p75) + } +} + +impl Default for StatSelect { + /// Returns the `Average` selector. + fn default() -> Self { + Self::Average + } +} + +impl FromStr for StatSelect { + type Err = &'static str; + + fn from_str(day: &str) -> result::Result { + match day.to_lowercase().as_str() { + "max" => Ok(Self::Maximum), + "average" => Ok(Self::Average), + "median" => Ok(Self::Median), + "p99" => Ok(Self::P99Percentile), + "p95" => Ok(Self::P95Percentile), + "p75" => Ok(Self::P75Percentile), + _ => Err("String was not a StatSelect"), + } + } +} + +#[cfg(test)] +mod test_stats { + use super::Stats; + use rand::{seq::SliceRandom, thread_rng}; + + #[test] + fn stats_correct() { + let mut data: Vec = (1..=100).collect(); + data.shuffle(&mut thread_rng()); + let stats = Stats::new(&data).unwrap(); + + assert_eq!(stats.sum, 5050); + assert_eq!(stats.min, 1); + assert_eq!(stats.max, 100); + + assert_eq!(stats.avg, 50); + assert_eq!(stats.median, 50); // 50.5 to be exact. + assert_eq!(stats.stddev, 28.87); // Rounded with 1/100 precision. + + assert_eq!(stats.p99, 99); + assert_eq!(stats.p95, 95); + assert_eq!(stats.p75, 75); + } + + #[test] + fn no_panic_short_lengths() { + // Empty input does error. + assert!(Stats::new(&vec![]).is_err()); + + // Different small input lengths are fine. + for l in 1..10 { + let data = (0..=l).collect(); + assert!(Stats::new(&data).is_ok()); + } + } +} diff --git a/utils/frame/benchmarking-cli/src/shared/weight_params.rs b/utils/frame/benchmarking-cli/src/shared/weight_params.rs new file mode 100644 index 0000000000000..030bbfa00d468 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/shared/weight_params.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Calculates a weight from the [`super::Stats`] of a benchmark result. + +use sc_cli::Result; + +use clap::Args; +use serde::Serialize; +use std::path::PathBuf; + +use super::{StatSelect, Stats}; + +/// Configures the weight generation. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct WeightParams { + /// File or directory to write the *weight* files to. + /// + /// For Substrate this should be `frame/support/src/weights`. + #[arg(long)] + pub weight_path: Option, + + /// Select a specific metric to calculate the final weight output. + #[arg(long = "metric", default_value = "average")] + pub weight_metric: StatSelect, + + /// Multiply the resulting weight with the given factor. Must be positive. + /// + /// Is applied before `weight_add`. + #[arg(long = "mul", default_value_t = 1.0)] + pub weight_mul: f64, + + /// Add the given offset to the resulting weight. + /// + /// Is applied after `weight_mul`. + #[arg(long = "add", default_value_t = 0)] + pub weight_add: u64, +} + +/// Calculates the final weight by multiplying the selected metric with +/// `weight_mul` and adding `weight_add`. +/// Does not use safe casts and can overflow. +impl WeightParams { + pub fn calc_weight(&self, stat: &Stats) -> Result { + if self.weight_mul.is_sign_negative() || !self.weight_mul.is_normal() { + return Err("invalid floating number for `weight_mul`".into()) + } + let s = stat.select(self.weight_metric) as f64; + let w = s.mul_add(self.weight_mul, self.weight_add as f64).ceil(); + Ok(w as u64) // No safe cast here since there is no `From` for `u64`. + } +} + +#[cfg(test)] +mod test_weight_params { + use super::WeightParams; + use crate::shared::{StatSelect, Stats}; + + #[test] + fn calc_weight_works() { + let stats = Stats { avg: 113, ..Default::default() }; + let params = WeightParams { + weight_metric: StatSelect::Average, + weight_mul: 0.75, + weight_add: 3, + ..Default::default() + }; + + let want = (113.0f64 * 0.75 + 3.0).ceil() as u64; // Ceil for overestimation. + let got = params.calc_weight(&stats).unwrap(); + assert_eq!(want, got); + } + + #[test] + fn calc_weight_detects_negative_mul() { + let stats = Stats::default(); + let params = WeightParams { weight_mul: -0.75, ..Default::default() }; + + assert!(params.calc_weight(&stats).is_err()); + } +} diff --git a/utils/frame/benchmarking-cli/src/storage/README.md b/utils/frame/benchmarking-cli/src/storage/README.md new file mode 100644 index 0000000000000..f61b7ba1bddd0 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/storage/README.md @@ -0,0 +1,106 @@ +# The `benchmark storage` command + +The cost of storage operations in a Substrate chain depends on the current chain state. +It is therefore important to regularly update these weights as the chain grows. +This sub-command measures the cost of storage operations for a concrete snapshot. + +For the Substrate node it looks like this (for debugging you can use `--release`): +```sh +cargo run --profile=production -- benchmark storage --dev --state-version=1 +``` + +Running the command on Substrate itself is not verify meaningful, since the genesis state of the `--dev` chain spec is used. + +The output for the Polkadot client with a recent chain snapshot will give you a better impression. A recent snapshot can be downloaded from [Polkachu]. +Then run (remove the `--db=paritydb` if you have a RocksDB snapshot): +```sh +cargo run --profile=production -- benchmark storage --dev --state-version=0 --db=paritydb --weight-path runtime/polkadot/constants/src/weights +``` + +This takes a while since reads and writes all keys from the snapshot: +```pre +# The 'read' benchmark +Preparing keys from block BlockId::Number(9939462) +Reading 1379083 keys +Time summary [ns]: +Total: 19668919930 +Min: 6450, Max: 1217259 +Average: 14262, Median: 14190, Stddev: 3035.79 +Percentiles 99th, 95th, 75th: 18270, 16190, 14819 +Value size summary: +Total: 265702275 +Min: 1, Max: 1381859 +Average: 192, Median: 80, Stddev: 3427.53 +Percentiles 99th, 95th, 75th: 3368, 383, 80 + +# The 'write' benchmark +Preparing keys from block BlockId::Number(9939462) +Writing 1379083 keys +Time summary [ns]: +Total: 98393809781 +Min: 12969, Max: 13282577 +Average: 71347, Median: 69499, Stddev: 25145.27 +Percentiles 99th, 95th, 75th: 135839, 106129, 79239 +Value size summary: +Total: 265702275 +Min: 1, Max: 1381859 +Average: 192, Median: 80, Stddev: 3427.53 +Percentiles 99th, 95th, 75th: 3368, 383, 80 + +Writing weights to "paritydb_weights.rs" +``` +You will see that the [paritydb_weights.rs] files was modified and now contains new weights. +The exact command for Polkadot can be seen at the top of the file. +This uses the most recent block from your snapshot which is printed at the top. +The value size summary tells us that the pruned Polkadot chain state is ~253 MiB in size. +Reading a value on average takes (in this examples) 14.3 µs and writing 71.3 µs. +The interesting part in the generated weight file tells us the weight constants and some statistics about the measurements: +```rust +/// Time to read one storage item. +/// Calculated by multiplying the *Average* of all values with `1.1` and adding `0`. +/// +/// Stats [NS]: +/// Min, Max: 4_611, 1_217_259 +/// Average: 14_262 +/// Median: 14_190 +/// Std-Dev: 3035.79 +/// +/// Percentiles [NS]: +/// 99th: 18_270 +/// 95th: 16_190 +/// 75th: 14_819 +read: 14_262 * constants::WEIGHT_REF_TIME_PER_NANOS, + +/// Time to write one storage item. +/// Calculated by multiplying the *Average* of all values with `1.1` and adding `0`. +/// +/// Stats [NS]: +/// Min, Max: 12_969, 13_282_577 +/// Average: 71_347This works under the assumption that the *average* read a +/// Median: 69_499 +/// Std-Dev: 25145.27 +/// +/// Percentiles [NS]: +/// 99th: 135_839 +/// 95th: 106_129 +/// 75th: 79_239 +write: 71_347 * constants::WEIGHT_REF_TIME_PER_NANOS, +``` + +## Arguments + +- `--db` Specify which database backend to use. This greatly influences the results. +- `--state-version` Set the version of the state encoding that this snapshot uses. Should be set to `1` for Substrate `--dev` and `0` for Polkadot et al. Using the wrong version can corrupt the snapshot. +- [`--mul`](../shared/README.md#arguments) +- [`--add`](../shared/README.md#arguments) +- [`--metric`](../shared/README.md#arguments) +- [`--weight-path`](../shared/README.md#arguments) +- `--json-read-path` Write the raw 'read' results to this file or directory. +- `--json-write-path` Write the raw 'write' results to this file or directory. +- [`--header`](../shared/README.md#arguments) + +License: Apache-2.0 + + +[Polkachu]: https://polkachu.com/snapshots +[paritydb_weights.rs]: https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/paritydb_weights.rs#L60 diff --git a/utils/frame/benchmarking-cli/src/storage/cmd.rs b/utils/frame/benchmarking-cli/src/storage/cmd.rs new file mode 100644 index 0000000000000..ce2d52e57d641 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/storage/cmd.rs @@ -0,0 +1,235 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 sc_cli::{CliConfiguration, DatabaseParams, PruningParams, Result, SharedParams}; +use sc_client_api::{Backend as ClientBackend, StorageProvider, UsageProvider}; +use sc_client_db::DbHash; +use sc_service::Configuration; +use sp_blockchain::HeaderBackend; +use sp_core::storage::StorageKey; +use sp_database::{ColumnId, Database}; +use sp_runtime::traits::{Block as BlockT, HashFor}; +use sp_state_machine::Storage; +use sp_storage::{ChildInfo, ChildType, PrefixedStorageKey, StateVersion}; + +use clap::{Args, Parser}; +use log::info; +use rand::prelude::*; +use serde::Serialize; +use sp_runtime::generic::BlockId; +use std::{fmt::Debug, path::PathBuf, sync::Arc}; + +use super::template::TemplateData; +use crate::shared::{new_rng, HostInfoParams, WeightParams}; + +/// Benchmark the storage speed of a chain snapshot. +#[derive(Debug, Parser)] +pub struct StorageCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub database_params: DatabaseParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub pruning_params: PruningParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub params: StorageParams, +} + +/// Parameters for modifying the benchmark behaviour and the post processing of the results. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct StorageParams { + #[allow(missing_docs)] + #[clap(flatten)] + pub weight_params: WeightParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub hostinfo: HostInfoParams, + + /// Skip the `read` benchmark. + #[arg(long)] + pub skip_read: bool, + + /// Skip the `write` benchmark. + #[arg(long)] + pub skip_write: bool, + + /// Specify the Handlebars template to use for outputting benchmark results. + #[arg(long)] + pub template_path: Option, + + /// Add a header to the generated weight output file. + /// + /// Good for adding LICENSE headers. + #[arg(long, value_name = "PATH")] + pub header: Option, + + /// Path to write the raw 'read' results in JSON format to. Can be a file or directory. + #[arg(long)] + pub json_read_path: Option, + + /// Path to write the raw 'write' results in JSON format to. Can be a file or directory. + #[arg(long)] + pub json_write_path: Option, + + /// Rounds of warmups before measuring. + #[arg(long, default_value_t = 1)] + pub warmups: u32, + + /// The `StateVersion` to use. Substrate `--dev` should use `V1` and Polkadot `V0`. + /// Selecting the wrong version can corrupt the DB. + #[arg(long, value_parser = clap::value_parser!(u8).range(0..=1))] + pub state_version: u8, + + /// Trie cache size in bytes. + /// + /// Providing `0` will disable the cache. + #[arg(long, value_name = "Bytes", default_value_t = 67108864)] + pub trie_cache_size: usize, + + /// Enable the Trie cache. + /// + /// This should only be used for performance analysis and not for final results. + #[arg(long)] + pub enable_trie_cache: bool, + + /// Include child trees in benchmark. + #[arg(long)] + pub include_child_trees: bool, +} + +impl StorageCmd { + /// Calls into the Read and Write benchmarking functions. + /// Processes the output and writes it into files and stdout. + pub fn run( + &self, + cfg: Configuration, + client: Arc, + db: (Arc>, ColumnId), + storage: Arc>>, + ) -> Result<()> + where + BA: ClientBackend, + Block: BlockT, + C: UsageProvider + StorageProvider + HeaderBackend, + { + let mut template = TemplateData::new(&cfg, &self.params)?; + + let block_id = BlockId::::Number(client.usage_info().chain.best_number); + template.set_block_number(block_id.to_string()); + + if !self.params.skip_read { + self.bench_warmup(&client)?; + let record = self.bench_read(client.clone())?; + if let Some(path) = &self.params.json_read_path { + record.save_json(&cfg, path, "read")?; + } + let stats = record.calculate_stats()?; + info!("Time summary [ns]:\n{:?}\nValue size summary:\n{:?}", stats.0, stats.1); + template.set_stats(Some(stats), None)?; + } + + if !self.params.skip_write { + self.bench_warmup(&client)?; + let record = self.bench_write(client, db, storage)?; + if let Some(path) = &self.params.json_write_path { + record.save_json(&cfg, path, "write")?; + } + let stats = record.calculate_stats()?; + info!("Time summary [ns]:\n{:?}\nValue size summary:\n{:?}", stats.0, stats.1); + template.set_stats(None, Some(stats))?; + } + + template.write(&self.params.weight_params.weight_path, &self.params.template_path) + } + + /// Returns the specified state version. + pub(crate) fn state_version(&self) -> StateVersion { + match self.params.state_version { + 0 => StateVersion::V0, + 1 => StateVersion::V1, + _ => unreachable!("Clap set to only allow 0 and 1"), + } + } + + /// Returns Some if child node and None if regular + pub(crate) fn is_child_key(&self, key: Vec) -> Option { + if let Some((ChildType::ParentKeyId, storage_key)) = + ChildType::from_prefixed_key(&PrefixedStorageKey::new(key)) + { + return Some(ChildInfo::new_default(storage_key)) + } + None + } + + /// Run some rounds of the (read) benchmark as warmup. + /// See `frame_benchmarking_cli::storage::read::bench_read` for detailed comments. + fn bench_warmup(&self, client: &Arc) -> Result<()> + where + C: UsageProvider + StorageProvider, + B: BlockT + Debug, + BA: ClientBackend, + { + let hash = client.usage_info().chain.best_hash; + let empty_prefix = StorageKey(Vec::new()); + let mut keys = client.storage_keys(hash, &empty_prefix)?; + let (mut rng, _) = new_rng(None); + keys.shuffle(&mut rng); + + for i in 0..self.params.warmups { + info!("Warmup round {}/{}", i + 1, self.params.warmups); + for key in keys.as_slice() { + let _ = client + .storage(hash, &key) + .expect("Checked above to exist") + .ok_or("Value unexpectedly empty"); + } + } + + Ok(()) + } +} + +// Boilerplate +impl CliConfiguration for StorageCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } + + fn database_params(&self) -> Option<&DatabaseParams> { + Some(&self.database_params) + } + + fn pruning_params(&self) -> Option<&PruningParams> { + Some(&self.pruning_params) + } + + fn trie_cache_maximum_size(&self) -> Result> { + if self.params.enable_trie_cache && self.params.trie_cache_size > 0 { + Ok(Some(self.params.trie_cache_size)) + } else { + Ok(None) + } + } +} diff --git a/utils/frame/benchmarking-cli/src/storage/mod.rs b/utils/frame/benchmarking-cli/src/storage/mod.rs new file mode 100644 index 0000000000000..0c722fdd47029 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/storage/mod.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 mod cmd; +pub mod read; +pub mod template; +pub mod write; + +pub use cmd::StorageCmd; diff --git a/utils/frame/benchmarking-cli/src/storage/read.rs b/utils/frame/benchmarking-cli/src/storage/read.rs new file mode 100644 index 0000000000000..20c41e4a5196b --- /dev/null +++ b/utils/frame/benchmarking-cli/src/storage/read.rs @@ -0,0 +1,90 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 sc_cli::Result; +use sc_client_api::{Backend as ClientBackend, StorageProvider, UsageProvider}; +use sp_core::storage::StorageKey; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; + +use log::info; +use rand::prelude::*; +use std::{fmt::Debug, sync::Arc, time::Instant}; + +use super::cmd::StorageCmd; +use crate::shared::{new_rng, BenchRecord}; + +impl StorageCmd { + /// Benchmarks the time it takes to read a single Storage item. + /// Uses the latest state that is available for the given client. + pub(crate) fn bench_read(&self, client: Arc) -> Result + where + C: UsageProvider + StorageProvider, + B: BlockT + Debug, + BA: ClientBackend, + <::Header as HeaderT>::Number: From, + { + let mut record = BenchRecord::default(); + let best_hash = client.usage_info().chain.best_hash; + + info!("Preparing keys from block {}", best_hash); + // Load all keys and randomly shuffle them. + let empty_prefix = StorageKey(Vec::new()); + let mut keys = client.storage_keys(best_hash, &empty_prefix)?; + let (mut rng, _) = new_rng(None); + keys.shuffle(&mut rng); + + let mut child_nodes = Vec::new(); + // Interesting part here: + // Read all the keys in the database and measure the time it takes to access each. + info!("Reading {} keys", keys.len()); + for key in keys.as_slice() { + match (self.params.include_child_trees, self.is_child_key(key.clone().0)) { + (true, Some(info)) => { + // child tree key + let child_keys = client.child_storage_keys(best_hash, &info, &empty_prefix)?; + for ck in child_keys { + child_nodes.push((ck.clone(), info.clone())); + } + }, + _ => { + // regular key + let start = Instant::now(); + let v = client + .storage(best_hash, &key) + .expect("Checked above to exist") + .ok_or("Value unexpectedly empty")?; + record.append(v.0.len(), start.elapsed())?; + }, + } + } + + if self.params.include_child_trees { + child_nodes.shuffle(&mut rng); + + info!("Reading {} child keys", child_nodes.len()); + for (key, info) in child_nodes.as_slice() { + let start = Instant::now(); + let v = client + .child_storage(best_hash, info, key) + .expect("Checked above to exist") + .ok_or("Value unexpectedly empty")?; + record.append(v.0.len(), start.elapsed())?; + } + } + Ok(record) + } +} diff --git a/utils/frame/benchmarking-cli/src/storage/template.rs b/utils/frame/benchmarking-cli/src/storage/template.rs new file mode 100644 index 0000000000000..ebc415ccb8189 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/storage/template.rs @@ -0,0 +1,149 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 sc_cli::Result; +use sc_service::Configuration; + +use log::info; +use serde::Serialize; +use std::{env, fs, path::PathBuf}; + +use super::cmd::StorageParams; +use crate::shared::{Stats, UnderscoreHelper}; + +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static TEMPLATE: &str = include_str!("./weights.hbs"); + +/// Data consumed by Handlebar to fill out the `weights.hbs` template. +#[derive(Serialize, Default, Debug, Clone)] +pub(crate) struct TemplateData { + /// Name of the database used. + db_name: String, + /// Block number that was used. + block_number: String, + /// Name of the runtime. Taken from the chain spec. + runtime_name: String, + /// Version of the benchmarking CLI used. + version: String, + /// Date that the template was filled out. + date: String, + /// Hostname of the machine that executed the benchmarks. + hostname: String, + /// CPU name of the machine that executed the benchmarks. + cpuname: String, + /// Header for the generated file. + header: String, + /// Command line arguments that were passed to the CLI. + args: Vec, + /// Storage params of the executed command. + params: StorageParams, + /// The weight for one `read`. + read_weight: u64, + /// The weight for one `write`. + write_weight: u64, + /// Stats about a `read` benchmark. Contains *time* and *value size* stats. + /// The *value size* stats are currently not used in the template. + read: Option<(Stats, Stats)>, + /// Stats about a `write` benchmark. Contains *time* and *value size* stats. + /// The *value size* stats are currently not used in the template. + write: Option<(Stats, Stats)>, +} + +impl TemplateData { + /// Returns a new [`Self`] from the given configuration. + pub fn new(cfg: &Configuration, params: &StorageParams) -> Result { + let header = params + .header + .as_ref() + .map(|p| std::fs::read_to_string(p)) + .transpose()? + .unwrap_or_default(); + + Ok(TemplateData { + db_name: format!("{}", cfg.database), + runtime_name: cfg.chain_spec.name().into(), + version: VERSION.into(), + date: chrono::Utc::now().format("%Y-%m-%d (Y/M/D)").to_string(), + hostname: params.hostinfo.hostname(), + cpuname: params.hostinfo.cpuname(), + header, + args: env::args().collect::>(), + params: params.clone(), + ..Default::default() + }) + } + + /// Sets the stats and calculates the final weights. + pub fn set_stats( + &mut self, + read: Option<(Stats, Stats)>, + write: Option<(Stats, Stats)>, + ) -> Result<()> { + if let Some(read) = read { + self.read_weight = self.params.weight_params.calc_weight(&read.0)?; + self.read = Some(read); + } + if let Some(write) = write { + self.write_weight = self.params.weight_params.calc_weight(&write.0)?; + self.write = Some(write); + } + Ok(()) + } + + /// Sets the block id that was used. + pub fn set_block_number(&mut self, block_number: String) { + self.block_number = block_number + } + + /// Fills out the `weights.hbs` or specified HBS template with its own data. + /// Writes the result to `path` which can be a directory or file. + pub fn write(&self, path: &Option, hbs_template: &Option) -> Result<()> { + let mut handlebars = handlebars::Handlebars::new(); + // Format large integers with underscore. + handlebars.register_helper("underscore", Box::new(UnderscoreHelper)); + // Don't HTML escape any characters. + handlebars.register_escape_fn(|s| -> String { s.to_string() }); + // Use custom template if provided. + let template = match hbs_template { + Some(template) if template.is_file() => fs::read_to_string(template)?, + Some(_) => return Err("Handlebars template is not a valid file!".into()), + None => TEMPLATE.to_string(), + }; + + let out_path = self.build_path(path); + let mut fd = fs::File::create(&out_path)?; + info!("Writing weights to {:?}", fs::canonicalize(&out_path)?); + + handlebars + .render_template_to_write(&template, &self, &mut fd) + .map_err(|e| format!("HBS template write: {:?}", e).into()) + } + + /// Builds a path for the weight file. + fn build_path(&self, weight_out: &Option) -> PathBuf { + let mut path = match weight_out { + Some(p) => PathBuf::from(p), + None => PathBuf::new(), + }; + + if path.is_dir() || path.as_os_str().is_empty() { + path.push(format!("{}_weights", self.db_name.to_lowercase())); + path.set_extension("rs"); + } + path + } +} diff --git a/utils/frame/benchmarking-cli/src/storage/weights.hbs b/utils/frame/benchmarking-cli/src/storage/weights.hbs new file mode 100644 index 0000000000000..135b18b193746 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/storage/weights.hbs @@ -0,0 +1,95 @@ +{{header}} +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} +//! DATE: {{date}} +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` +//! +//! DATABASE: `{{db_name}}`, RUNTIME: `{{runtime_name}}` +//! BLOCK-NUM: `{{block_number}}` +//! SKIP-WRITE: `{{params.skip_write}}`, SKIP-READ: `{{params.skip_read}}`, WARMUPS: `{{params.warmups}}` +//! STATE-VERSION: `V{{params.state_version}}`, STATE-CACHE-SIZE: `{{params.state_cache_size}}` +//! WEIGHT-PATH: `{{params.weight_params.weight_path}}` +//! METRIC: `{{params.weight_params.weight_metric}}`, WEIGHT-MUL: `{{params.weight_params.weight_mul}}`, WEIGHT-ADD: `{{params.weight_params.weight_add}}` + +// Executed Command: +{{#each args as |arg|}} +// {{arg}} +{{/each}} + +/// Storage DB weights for the `{{runtime_name}}` runtime and `{{db_name}}`. +pub mod constants { + use frame_support::weights::constants; + use sp_core::parameter_types; + use sp_weights::RuntimeDbWeight; + + parameter_types! { + {{#if (eq db_name "ParityDb")}} + /// `ParityDB` can be enabled with a feature flag, but is still experimental. These weights + /// are available for brave runtime engineers who may want to try this out as default. + {{else}} + /// By default, Substrate uses `RocksDB`, so this will be the weight used throughout + /// the runtime. + {{/if}} + pub const {{db_name}}Weight: RuntimeDbWeight = RuntimeDbWeight { + /// Time to read one storage item. + /// Calculated by multiplying the *{{params.weight_params.weight_metric}}* of all values with `{{params.weight_params.weight_mul}}` and adding `{{params.weight_params.weight_add}}`. + /// + /// Stats nanoseconds: + /// Min, Max: {{underscore read.0.min}}, {{underscore read.0.max}} + /// Average: {{underscore read.0.avg}} + /// Median: {{underscore read.0.median}} + /// Std-Dev: {{read.0.stddev}} + /// + /// Percentiles nanoseconds: + /// 99th: {{underscore read.0.p99}} + /// 95th: {{underscore read.0.p95}} + /// 75th: {{underscore read.0.p75}} + read: {{underscore read_weight}} * constants::WEIGHT_REF_TIME_PER_NANOS, + + /// Time to write one storage item. + /// Calculated by multiplying the *{{params.weight_params.weight_metric}}* of all values with `{{params.weight_params.weight_mul}}` and adding `{{params.weight_params.weight_add}}`. + /// + /// Stats nanoseconds: + /// Min, Max: {{underscore write.0.min}}, {{underscore write.0.max}} + /// Average: {{underscore write.0.avg}} + /// Median: {{underscore write.0.median}} + /// Std-Dev: {{write.0.stddev}} + /// + /// Percentiles nanoseconds: + /// 99th: {{underscore write.0.p99}} + /// 95th: {{underscore write.0.p95}} + /// 75th: {{underscore write.0.p75}} + write: {{underscore write_weight}} * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::{{db_name}}Weight as W; + use sp_weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn bound() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/utils/frame/benchmarking-cli/src/storage/write.rs b/utils/frame/benchmarking-cli/src/storage/write.rs new file mode 100644 index 0000000000000..55a7b60d55552 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/storage/write.rs @@ -0,0 +1,247 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 sc_cli::Result; +use sc_client_api::{Backend as ClientBackend, StorageProvider, UsageProvider}; +use sc_client_db::{DbHash, DbState, DbStateBuilder}; +use sp_api::StateBackend; +use sp_blockchain::HeaderBackend; +use sp_database::{ColumnId, Transaction}; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, HashFor, Header as HeaderT}, +}; +use sp_trie::PrefixedMemoryDB; + +use log::{info, trace}; +use rand::prelude::*; +use sp_storage::{ChildInfo, StateVersion}; +use std::{ + fmt::Debug, + sync::Arc, + time::{Duration, Instant}, +}; + +use super::cmd::StorageCmd; +use crate::shared::{new_rng, BenchRecord}; + +impl StorageCmd { + /// Benchmarks the time it takes to write a single Storage item. + /// Uses the latest state that is available for the given client. + pub(crate) fn bench_write( + &self, + client: Arc, + (db, state_col): (Arc>, ColumnId), + storage: Arc>>, + ) -> Result + where + Block: BlockT
+ Debug, + H: HeaderT, + BA: ClientBackend, + C: UsageProvider + HeaderBackend + StorageProvider, + { + // Store the time that it took to write each value. + let mut record = BenchRecord::default(); + + let best_hash = client.usage_info().chain.best_hash; + let header = client.header(BlockId::Hash(best_hash))?.ok_or("Header not found")?; + let original_root = *header.state_root(); + let trie = DbStateBuilder::::new(storage.clone(), original_root).build(); + + info!("Preparing keys from block {}", best_hash); + // Load all KV pairs and randomly shuffle them. + let mut kvs = trie.pairs(); + let (mut rng, _) = new_rng(None); + kvs.shuffle(&mut rng); + info!("Writing {} keys", kvs.len()); + + let mut child_nodes = Vec::new(); + + // Generate all random values first; Make sure there are no collisions with existing + // db entries, so we can rollback all additions without corrupting existing entries. + for (k, original_v) in kvs { + match (self.params.include_child_trees, self.is_child_key(k.to_vec())) { + (true, Some(info)) => { + let child_keys = + client.child_storage_keys_iter(best_hash, info.clone(), None, None)?; + for ck in child_keys { + child_nodes.push((ck.clone(), info.clone())); + } + }, + _ => { + // regular key + let mut new_v = vec![0; original_v.len()]; + loop { + // Create a random value to overwrite with. + // NOTE: We use a possibly higher entropy than the original value, + // could be improved but acts as an over-estimation which is fine for now. + rng.fill_bytes(&mut new_v[..]); + if check_new_value::( + db.clone(), + &trie, + &k.to_vec(), + &new_v, + self.state_version(), + state_col, + None, + ) { + break + } + } + + // Write each value in one commit. + let (size, duration) = measure_write::( + db.clone(), + &trie, + k.to_vec(), + new_v.to_vec(), + self.state_version(), + state_col, + None, + )?; + record.append(size, duration)?; + }, + } + } + + if self.params.include_child_trees { + child_nodes.shuffle(&mut rng); + info!("Writing {} child keys", child_nodes.len()); + + for (key, info) in child_nodes { + if let Some(original_v) = client + .child_storage(best_hash, &info.clone(), &key) + .expect("Checked above to exist") + { + let mut new_v = vec![0; original_v.0.len()]; + loop { + rng.fill_bytes(&mut new_v[..]); + if check_new_value::( + db.clone(), + &trie, + &key.0, + &new_v, + self.state_version(), + state_col, + Some(&info), + ) { + break + } + } + + let (size, duration) = measure_write::( + db.clone(), + &trie, + key.0, + new_v.to_vec(), + self.state_version(), + state_col, + Some(&info), + )?; + record.append(size, duration)?; + } + } + } + + Ok(record) + } +} + +/// Converts a Trie transaction into a DB transaction. +/// Removals are ignored and will not be included in the final tx. +/// `invert_inserts` replaces all inserts with removals. +fn convert_tx( + db: Arc>, + mut tx: PrefixedMemoryDB>, + invert_inserts: bool, + col: ColumnId, +) -> Transaction { + let mut ret = Transaction::::default(); + + for (mut k, (v, rc)) in tx.drain().into_iter() { + if rc > 0 { + db.sanitize_key(&mut k); + if invert_inserts { + ret.remove(col, &k); + } else { + ret.set(col, &k, &v); + } + } + // < 0 means removal - ignored. + // 0 means no modification. + } + ret +} + +/// Measures write benchmark +/// if `child_info` exist then it means this is a child tree key +fn measure_write( + db: Arc>, + trie: &DbState, + key: Vec, + new_v: Vec, + version: StateVersion, + col: ColumnId, + child_info: Option<&ChildInfo>, +) -> Result<(usize, Duration)> { + let start = Instant::now(); + // Create a TX that will modify the Trie in the DB and + // calculate the root hash of the Trie after the modification. + let replace = vec![(key.as_ref(), Some(new_v.as_ref()))]; + let stx = match child_info { + Some(info) => trie.child_storage_root(info, replace.iter().cloned(), version).2, + None => trie.storage_root(replace.iter().cloned(), version).1, + }; + // Only the keep the insertions, since we do not want to benchmark pruning. + let tx = convert_tx::(db.clone(), stx.clone(), false, col); + db.commit(tx).map_err(|e| format!("Writing to the Database: {}", e))?; + let result = (new_v.len(), start.elapsed()); + + // Now undo the changes by removing what was added. + let tx = convert_tx::(db.clone(), stx.clone(), true, col); + db.commit(tx).map_err(|e| format!("Writing to the Database: {}", e))?; + Ok(result) +} + +/// Checks if a new value causes any collision in tree updates +/// returns true if there is no collision +/// if `child_info` exist then it means this is a child tree key +fn check_new_value( + db: Arc>, + trie: &DbState, + key: &Vec, + new_v: &Vec, + version: StateVersion, + col: ColumnId, + child_info: Option<&ChildInfo>, +) -> bool { + let new_kv = vec![(key.as_ref(), Some(new_v.as_ref()))]; + let mut stx = match child_info { + Some(info) => trie.child_storage_root(info, new_kv.iter().cloned(), version).2, + None => trie.storage_root(new_kv.iter().cloned(), version).1, + }; + for (mut k, (_, rc)) in stx.drain().into_iter() { + if rc > 0 { + db.sanitize_key(&mut k); + if db.get(col, &k).is_some() { + trace!("Benchmark-store key creation: Key collision detected, retry"); + return false + } + } + } + true +} diff --git a/utils/frame/frame-utilities-cli/Cargo.toml b/utils/frame/frame-utilities-cli/Cargo.toml index 1b6597fc9f2fc..26e07c79d8f81 100644 --- a/utils/frame/frame-utilities-cli/Cargo.toml +++ b/utils/frame/frame-utilities-cli/Cargo.toml @@ -2,23 +2,21 @@ name = "substrate-frame-cli" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "cli interface for FRAME" documentation = "https://docs.rs/substrate-frame-cli" readme = "README.md" [dependencies] -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -structopt = "0.3.8" -frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } +clap = { version = "4.0.9", features = ["derive"] } frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } - -[dev-dependencies] +frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } +sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } [features] default = [] diff --git a/utils/frame/frame-utilities-cli/src/lib.rs b/utils/frame/frame-utilities-cli/src/lib.rs index 4f5b1da5766a3..9a18d39263c98 100644 --- a/utils/frame/frame-utilities-cli/src/lib.rs +++ b/utils/frame/frame-utilities-cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/frame-utilities-cli/src/pallet_id.rs b/utils/frame/frame-utilities-cli/src/pallet_id.rs index 2caac7db588a9..2a80e3a3d312d 100644 --- a/utils/frame/frame-utilities-cli/src/pallet_id.rs +++ b/utils/frame/frame-utilities-cli/src/pallet_id.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,43 +17,41 @@ //! Implementation of the `palletid` subcommand +use clap::Parser; use frame_support::PalletId; use sc_cli::{ utils::print_from_uri, with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, OutputTypeFlag, }; -use sp_core::crypto::{Ss58AddressFormat, Ss58Codec}; +use sp_core::crypto::{unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec}; use sp_runtime::traits::AccountIdConversion; -use std::convert::{TryFrom, TryInto}; -use structopt::StructOpt; /// The `palletid` command -#[derive(Debug, StructOpt)] -#[structopt(name = "palletid", about = "Inspect a module ID address")] +#[derive(Debug, Parser)] +#[command(name = "palletid", about = "Inspect a module ID address")] pub struct PalletIdCmd { /// The module ID used to derive the account id: String, /// network address format - #[structopt( + #[arg( long, value_name = "NETWORK", - possible_values = &Ss58AddressFormat::all_names()[..], - parse(try_from_str = Ss58AddressFormat::try_from), - case_insensitive = true, + value_parser = sc_cli::parse_ss58_address_format, + ignore_case = true, )] pub network: Option, #[allow(missing_docs)] - #[structopt(flatten)] + #[command(flatten)] pub output_scheme: OutputTypeFlag, #[allow(missing_docs)] - #[structopt(flatten)] + #[command(flatten)] pub crypto_scheme: CryptoSchemeFlag, #[allow(missing_docs)] - #[structopt(flatten)] + #[command(flatten)] pub keystore_params: KeystoreParams, } @@ -65,7 +63,7 @@ impl PalletIdCmd { R::AccountId: Ss58Codec, { if self.id.len() != 8 { - Err("a module id must be a string of 8 characters")? + return Err("a module id must be a string of 8 characters".into()) } let password = self.keystore_params.read_password()?; @@ -73,15 +71,15 @@ impl PalletIdCmd { "Cannot convert argument to palletid: argument should be 8-character string" })?; - let account_id: R::AccountId = PalletId(id_fixed_array).into_account(); + let account_id: R::AccountId = PalletId(id_fixed_array).into_account_truncating(); with_crypto_scheme!( self.crypto_scheme.scheme, print_from_uri( - &account_id.to_ss58check_with_version(self.network.clone().unwrap_or_default()), + &account_id.to_ss58check_with_version(unwrap_or_default_ss58_version(self.network)), password, self.network, - self.output_scheme.output_type.clone() + self.output_scheme.output_type ) ); diff --git a/utils/frame/generate-bags/Cargo.toml b/utils/frame/generate-bags/Cargo.toml new file mode 100644 index 0000000000000..04d598c7843c4 --- /dev/null +++ b/utils/frame/generate-bags/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "generate-bags" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Bag threshold generation script for pallet-bag-list" + +[dependencies] +# FRAME +frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../../../frame/election-provider-support" } +frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } +pallet-staking = { version = "4.0.0-dev", path = "../../../frame/staking" } + +# primitives +sp-io = { version = "7.0.0", path = "../../../primitives/io" } + +# third party +chrono = { version = "0.4.19" } +git2 = { version = "0.14.2", default-features = false } +num-format = "0.4.3" diff --git a/utils/frame/generate-bags/node-runtime/Cargo.toml b/utils/frame/generate-bags/node-runtime/Cargo.toml new file mode 100644 index 0000000000000..46d999daba9cf --- /dev/null +++ b/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "node-runtime-generate-bags" +version = "3.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Bag threshold generation script for pallet-bag-list and kitchensink-runtime." +publish = false + +[dependencies] +kitchensink-runtime = { version = "3.0.0-dev", path = "../../../../bin/node/runtime" } +generate-bags = { version = "4.0.0-dev", path = "../" } + +# third-party +clap = { version = "4.0.9", features = ["derive"] } diff --git a/utils/frame/generate-bags/node-runtime/src/main.rs b/utils/frame/generate-bags/node-runtime/src/main.rs new file mode 100644 index 0000000000000..27e51b205f8ce --- /dev/null +++ b/utils/frame/generate-bags/node-runtime/src/main.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Make the set of bag thresholds to be used with pallet-bags-list. + +use clap::Parser; +use generate_bags::generate_thresholds; +use std::path::PathBuf; + +#[derive(Debug, Parser)] +// #[clap(author, version, about)] +struct Opt { + /// How many bags to generate. + #[arg(long, default_value_t = 200)] + n_bags: usize, + + /// Where to write the output. + output: PathBuf, + + /// The total issuance of the currency used to create `VoteWeight`. + #[arg(short, long)] + total_issuance: u128, + + /// The minimum account balance (i.e. existential deposit) for the currency used to create + /// `VoteWeight`. + #[arg(short, long)] + minimum_balance: u128, +} + +fn main() -> Result<(), std::io::Error> { + let Opt { n_bags, output, total_issuance, minimum_balance } = Opt::parse(); + generate_thresholds::( + n_bags, + &output, + total_issuance, + minimum_balance, + ) +} diff --git a/utils/frame/generate-bags/src/lib.rs b/utils/frame/generate-bags/src/lib.rs new file mode 100644 index 0000000000000..23da131a668d8 --- /dev/null +++ b/utils/frame/generate-bags/src/lib.rs @@ -0,0 +1,260 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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. + +//! Support code to ease the process of generating bag thresholds. +//! +//! NOTE: this assume the runtime implements [`pallet_staking::Config`], as it requires an +//! implementation of the traits [`frame_support::traits::Currency`] and +//! [`frame_support::traits::CurrencyToVote`]. +//! +//! The process of adding bags to a runtime requires only four steps. +//! +//! 1. Update the runtime definition. +//! +//! ```ignore +//! parameter_types!{ +//! pub const BagThresholds: &'static [u64] = &[]; +//! } +//! +//! impl pallet_bags_list::Config for Runtime { +//! // +//! type BagThresholds = BagThresholds; +//! } +//! ``` +//! +//! 2. Write a little program to generate the definitions. This program exists only to hook together +//! the runtime definitions with the various calculations here. Take a look at +//! _utils/frame/generate_bags/node-runtime_ for an example. +//! +//! 3. Run that program: +//! +//! ```sh,notrust +//! $ cargo run -p node-runtime-generate-bags -- --total-issuance 1234 --minimum-balance 1 +//! output.rs ``` +//! +//! 4. Update the runtime definition. +//! +//! ```diff,notrust +//! + mod output; +//! - pub const BagThresholds: &'static [u64] = &[]; +//! + pub const BagThresholds: &'static [u64] = &output::THRESHOLDS; +//! ``` + +use frame_election_provider_support::VoteWeight; +use frame_support::traits::Get; +use std::{ + io::Write, + path::{Path, PathBuf}, +}; + +/// Compute the existential weight for the specified configuration. +/// +/// Note that this value depends on the current issuance, a quantity known to change over time. +/// This makes the project of computing a static value suitable for inclusion in a static, +/// generated file _excitingly unstable_. +fn existential_weight( + total_issuance: u128, + minimum_balance: u128, +) -> VoteWeight { + use frame_support::traits::CurrencyToVote; + + T::CurrencyToVote::to_vote( + minimum_balance + .try_into() + .map_err(|_| "failed to convert minimum_balance to type Balance") + .unwrap(), + total_issuance + .try_into() + .map_err(|_| "failed to convert total_issuance to type Balance") + .unwrap(), + ) +} + +/// Return the path to a header file used in this repository if is exists. +/// +/// Just searches the git working directory root for files matching certain patterns; it's +/// pretty naive. +fn path_to_header_file() -> Option { + let repo = git2::Repository::open_from_env().ok()?; + let workdir = repo.workdir()?; + for file_name in &["HEADER-APACHE2", "HEADER-GPL3", "HEADER", "file_header.txt"] { + let path = workdir.join(file_name); + if path.exists() { + return Some(path) + } + } + None +} + +/// Create an underscore formatter: a formatter which inserts `_` every 3 digits of a number. +fn underscore_formatter() -> num_format::CustomFormat { + num_format::CustomFormat::builder() + .grouping(num_format::Grouping::Standard) + .separator("_") + .build() + .expect("format described here meets all constraints") +} + +/// Compute the constant ratio for the thresholds. +/// +/// This ratio ensures that each bag, with the possible exceptions of certain small ones and the +/// final one, is a constant multiple of the previous, while fully occupying the `VoteWeight` +/// space. +pub fn constant_ratio(existential_weight: VoteWeight, n_bags: usize) -> f64 { + ((VoteWeight::MAX as f64 / existential_weight as f64).ln() / ((n_bags - 1) as f64)).exp() +} + +/// Compute the list of bag thresholds. +/// +/// Returns a list of exactly `n_bags` elements, except in the case of overflow. +/// The first element is always `existential_weight`. +/// The last element is always `VoteWeight::MAX`. +/// +/// All other elements are computed from the previous according to the formula +/// `threshold[k + 1] = (threshold[k] * ratio).max(threshold[k] + 1);` +pub fn thresholds( + existential_weight: VoteWeight, + constant_ratio: f64, + n_bags: usize, +) -> Vec { + const WEIGHT_LIMIT: f64 = VoteWeight::MAX as f64; + + let mut thresholds = Vec::with_capacity(n_bags); + + if n_bags > 1 { + thresholds.push(existential_weight); + } + + while n_bags > 0 && thresholds.len() < n_bags - 1 { + let last = thresholds.last().copied().unwrap_or(existential_weight); + let successor = (last as f64 * constant_ratio).round().max(last as f64 + 1.0); + if successor < WEIGHT_LIMIT { + thresholds.push(successor as VoteWeight); + } else { + eprintln!("unexpectedly exceeded weight limit; breaking threshold generation loop"); + break + } + } + + thresholds.push(VoteWeight::MAX); + + debug_assert_eq!(thresholds.len(), n_bags); + debug_assert!(n_bags == 0 || thresholds[0] == existential_weight); + debug_assert!(n_bags == 0 || thresholds[thresholds.len() - 1] == VoteWeight::MAX); + + thresholds +} + +/// Write a thresholds module to the path specified. +/// +/// Parameters: +/// - `n_bags` the number of bags to generate. +/// - `output` the path to write to; should terminate with a Rust module name, i.e. +/// `foo/bar/thresholds.rs`. +/// - `total_issuance` the total amount of the currency in the network. +/// - `minimum_balance` the minimum balance of the currency required for an account to exist (i.e. +/// existential deposit). +/// +/// This generated module contains, in order: +/// +/// - The contents of the header file in this repository's root, if found. +/// - Module documentation noting that this is autogenerated and when. +/// - Some associated constants. +/// - The constant array of thresholds. +pub fn generate_thresholds( + n_bags: usize, + output: &Path, + total_issuance: u128, + minimum_balance: u128, +) -> Result<(), std::io::Error> { + // ensure the file is accessable + if let Some(parent) = output.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)?; + } + } + + // copy the header file + if let Some(header_path) = path_to_header_file() { + std::fs::copy(header_path, output)?; + } + + // open an append buffer + let file = std::fs::OpenOptions::new().create(true).append(true).open(output)?; + let mut buf = std::io::BufWriter::new(file); + + // create underscore formatter and format buffer + let mut num_buf = num_format::Buffer::new(); + let format = underscore_formatter(); + + // module docs + let now = chrono::Utc::now(); + writeln!(buf)?; + writeln!(buf, "//! Autogenerated bag thresholds.")?; + writeln!(buf, "//!")?; + writeln!(buf, "//! Generated on {}", now.to_rfc3339())?; + writeln!(buf, "//! Arguments")?; + writeln!(buf, "//! Total issuance: {}", &total_issuance)?; + writeln!(buf, "//! Minimum balance: {}", &minimum_balance)?; + + writeln!( + buf, + "//! for the {} runtime.", + ::Version::get().spec_name, + )?; + + let existential_weight = existential_weight::(total_issuance, minimum_balance); + num_buf.write_formatted(&existential_weight, &format); + writeln!(buf)?; + writeln!(buf, "/// Existential weight for this runtime.")?; + writeln!(buf, "#[cfg(any(test, feature = \"std\"))]")?; + writeln!(buf, "#[allow(unused)]")?; + writeln!(buf, "pub const EXISTENTIAL_WEIGHT: u64 = {};", num_buf.as_str())?; + + // constant ratio + let constant_ratio = constant_ratio(existential_weight, n_bags); + writeln!(buf)?; + writeln!(buf, "/// Constant ratio between bags for this runtime.")?; + writeln!(buf, "#[cfg(any(test, feature = \"std\"))]")?; + writeln!(buf, "#[allow(unused)]")?; + writeln!(buf, "pub const CONSTANT_RATIO: f64 = {:.16};", constant_ratio)?; + + // thresholds + let thresholds = thresholds(existential_weight, constant_ratio, n_bags); + writeln!(buf)?; + writeln!(buf, "/// Upper thresholds delimiting the bag list.")?; + writeln!(buf, "pub const THRESHOLDS: [u64; {}] = [", thresholds.len())?; + for threshold in &thresholds { + num_buf.write_formatted(threshold, &format); + // u64::MAX, with spacers every 3 digits, is 26 characters wide + writeln!(buf, " {:>26},", num_buf.as_str())?; + } + writeln!(buf, "];")?; + + // thresholds balance + writeln!(buf)?; + writeln!(buf, "/// Upper thresholds delimiting the bag list.")?; + writeln!(buf, "pub const THRESHOLDS_BALANCES: [u128; {}] = [", thresholds.len())?; + for threshold in thresholds { + num_buf.write_formatted(&threshold, &format); + // u64::MAX, with spacers every 3 digits, is 26 characters wide + writeln!(buf, " {:>26},", num_buf.as_str())?; + } + writeln!(buf, "];")?; + + Ok(()) +} diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index d255499d6c3ad..4cb847867f374 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -1,37 +1,33 @@ [package] -name = "remote-externalities" +name = "frame-remote-externalities" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -description = "An externalities provided environemnt that can load itself from remote nodes or cache files" -readme = "README.md" +description = "An externalities provided environment that can load itself from remote nodes or cached files" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee-ws-client = { version = "0.3.0", default-features = false, features = [ - "tokio1", -] } -jsonrpsee-proc-macros = "0.3.0" - +codec = { package = "parity-scale-codec", version = "3.0.0" } env_logger = "0.9" -log = "0.4.11" -codec = { package = "parity-scale-codec", version = "2.0.0" } +log = "0.4.17" +serde = "1.0.136" serde_json = "1.0" -serde = "1.0.126" - -sp-io = { version = "4.0.0-dev", path = "../../../primitives/io" } -sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", path = "../../../primitives/runtime" } -sp-version = { version = "4.0.0-dev", path = "../../../primitives/version" } +frame-support = { version = "4.0.0-dev", optional = true, path = "../../../frame/support" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-version = { version = "5.0.0", path = "../../../primitives/version" } +substrate-rpc-client = { path = "../rpc/client" } [dev-dependencies] -tokio = { version = "1.10", features = ["macros", "rt-multi-thread"] } -pallet-elections-phragmen = { path = "../../../frame/elections-phragmen", version = "5.0.0-dev" } +tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] } +frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } +pallet-elections-phragmen = { version = "5.0.0-dev", path = "../../../frame/elections-phragmen" } [features] -remote-test = [] +remote-test = ["frame-support"] diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index addb3d1dd3c17..db062e246ceef 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,51 +21,46 @@ //! based chain, or a local state snapshot file. use codec::{Decode, Encode}; -use jsonrpsee_ws_client::{types::v2::params::JsonRpcParams, WsClient, WsClientBuilder}; + use log::*; +use serde::de::DeserializeOwned; use sp_core::{ hashing::twox_128, hexdisplay::HexDisplay, - storage::{StorageData, StorageKey}, + storage::{ + well_known_keys::{is_default_child_storage_key, DEFAULT_CHILD_STORAGE_KEY_PREFIX}, + ChildInfo, ChildType, PrefixedStorageKey, StorageData, StorageKey, + }, }; pub use sp_io::TestExternalities; -use sp_runtime::traits::Block as BlockT; +use sp_runtime::{traits::Block as BlockT, StateVersion}; use std::{ fs, path::{Path, PathBuf}, + sync::Arc, +}; +use substrate_rpc_client::{ + rpc_params, ws_client, BatchRequestBuilder, ChainApi, ClientT, StateApi, WsClient, }; -pub mod rpc_api; - -type KeyPair = (StorageKey, StorageData); +type KeyValue = (StorageKey, StorageData); +type TopKeyValues = Vec; +type ChildKeyValues = Vec<(ChildInfo, Vec)>; const LOG_TARGET: &str = "remote-ext"; -const DEFAULT_TARGET: &str = "wss://rpc.polkadot.io"; +const DEFAULT_TARGET: &str = "wss://rpc.polkadot.io:443"; const BATCH_SIZE: usize = 1000; - -jsonrpsee_proc_macros::rpc_client_api! { - RpcApi { - #[rpc(method = "state_getStorage", positional_params)] - fn get_storage(prefix: StorageKey, hash: Option) -> StorageData; - #[rpc(method = "state_getKeysPaged", positional_params)] - fn get_keys_paged( - prefix: Option, - count: u32, - start_key: Option, - hash: Option, - ) -> Vec; - #[rpc(method = "chain_getFinalizedHead", positional_params)] - fn finalized_head() -> B::Hash; - } -} +const PAGE: u32 = 1000; /// The execution mode. #[derive(Clone)] pub enum Mode { - /// Online. + /// Online. Potentially writes to a cache file. Online(OnlineConfig), /// Offline. Uses a state snapshot file and needs not any client config. Offline(OfflineConfig), + /// Prefer using a cache file if it exists, else use a remote server. + OfflineOrElseOnline(OfflineConfig, OnlineConfig), } impl Default for Mode { @@ -84,21 +79,48 @@ pub struct OfflineConfig { } /// Description of the transport protocol (for online execution). -#[derive(Debug)] -pub struct Transport { - uri: String, - client: Option, +#[derive(Debug, Clone)] +pub enum Transport { + /// Use the `URI` to open a new WebSocket connection. + Uri(String), + /// Use existing WebSocket connection. + RemoteClient(Arc), } -impl Clone for Transport { - fn clone(&self) -> Self { - Self { uri: self.uri.clone(), client: None } +impl Transport { + fn as_client(&self) -> Option<&WsClient> { + match self { + Self::RemoteClient(client) => Some(client), + _ => None, + } + } + + // Open a new WebSocket connection if it's not connected. + async fn map_uri(&mut self) -> Result<(), &'static str> { + if let Self::Uri(uri) = self { + log::debug!(target: LOG_TARGET, "initializing remote client to {:?}", uri); + + let ws_client = ws_client(uri).await.map_err(|e| { + log::error!(target: LOG_TARGET, "error: {:?}", e); + "failed to build ws client" + })?; + + *self = Self::RemoteClient(Arc::new(ws_client)) + } + + Ok(()) } } impl From for Transport { - fn from(t: String) -> Self { - Self { uri: t, client: None } + fn from(uri: String) -> Self { + Transport::Uri(uri) + } +} + +impl From> for Transport { + fn from(client: Arc) -> Self { + Transport::RemoteClient(client) } } @@ -112,18 +134,19 @@ pub struct OnlineConfig { pub at: Option, /// An optional state snapshot file to WRITE to, not for reading. Not written if set to `None`. pub state_snapshot: Option, - /// The modules to scrape. If empty, entire chain state will be scraped. - pub modules: Vec, + /// The pallets to scrape. If empty, entire chain state will be scraped. + pub pallets: Vec, /// Transport config. pub transport: Transport, + /// Lookout for child-keys, and scrape them as well if set to true. + pub scrape_children: bool, } impl OnlineConfig { /// Return rpc (ws) client. fn rpc_client(&self) -> &WsClient { self.transport - .client - .as_ref() + .as_client() .expect("ws client must have been initialized by now; qed.") } } @@ -131,14 +154,21 @@ impl OnlineConfig { impl Default for OnlineConfig { fn default() -> Self { Self { - transport: Transport { uri: DEFAULT_TARGET.to_owned(), client: None }, + transport: Transport::Uri(DEFAULT_TARGET.to_owned()), at: None, state_snapshot: None, - modules: vec![], + pallets: vec![], + scrape_children: true, } } } +impl From for OnlineConfig { + fn from(s: String) -> Self { + Self { transport: s.into(), ..Default::default() } + } +} + /// Configuration of the state snapshot. #[derive(Clone)] pub struct SnapshotConfig { @@ -152,6 +182,12 @@ impl SnapshotConfig { } } +impl From for SnapshotConfig { + fn from(s: String) -> Self { + Self::new(s) + } +} + impl Default for SnapshotConfig { fn default() -> Self { Self { path: Path::new("SNAPSHOT").into() } @@ -160,15 +196,20 @@ impl Default for SnapshotConfig { /// Builder for remote-externalities. pub struct Builder { - /// Custom key-pairs to be injected into the externalities. - inject: Vec, + /// Custom key-pairs to be injected into the externalities. The *hashed* keys and values must + /// be given. + hashed_key_values: Vec, /// Storage entry key prefixes to be injected into the externalities. The *hashed* prefix must /// be given. hashed_prefixes: Vec>, /// Storage entry keys to be injected into the externalities. The *hashed* key must be given. hashed_keys: Vec>, + /// The keys that will be excluded from the final externality. The *hashed* key must be given. + hashed_blacklist: Vec>, /// connectivity mode, online or offline. mode: Mode, + /// The state version being used. + state_version: StateVersion, } // NOTE: ideally we would use `DefaultNoBound` here, but not worth bringing in frame-support for @@ -176,10 +217,12 @@ pub struct Builder { impl Default for Builder { fn default() -> Self { Self { - inject: Default::default(), mode: Default::default(), + hashed_key_values: Default::default(), hashed_prefixes: Default::default(), hashed_keys: Default::default(), + hashed_blacklist: Default::default(), + state_version: StateVersion::V1, } } } @@ -188,7 +231,8 @@ impl Default for Builder { impl Builder { fn as_online(&self) -> &OnlineConfig { match &self.mode { - Mode::Online(config) => &config, + Mode::Online(config) => config, + Mode::OfflineOrElseOnline(_, config) => config, _ => panic!("Unexpected mode: Online"), } } @@ -196,69 +240,78 @@ impl Builder { fn as_online_mut(&mut self) -> &mut OnlineConfig { match &mut self.mode { Mode::Online(config) => config, + Mode::OfflineOrElseOnline(_, config) => config, _ => panic!("Unexpected mode: Online"), } } } // RPC methods -impl Builder { +impl Builder +where + B::Hash: DeserializeOwned, + B::Header: DeserializeOwned, +{ async fn rpc_get_storage( &self, key: StorageKey, maybe_at: Option, ) -> Result { trace!(target: LOG_TARGET, "rpc: get_storage"); - RpcApi::::get_storage(self.as_online().rpc_client(), key, maybe_at) - .await - .map_err(|e| { - error!("Error = {:?}", e); - "rpc get_storage failed." - }) + match self.as_online().rpc_client().storage(key, maybe_at).await { + Ok(Some(res)) => Ok(res), + Ok(None) => Err("get_storage not found"), + Err(e) => { + error!(target: LOG_TARGET, "Error = {:?}", e); + Err("rpc get_storage failed.") + }, + } } + /// Get the latest finalized head. async fn rpc_get_head(&self) -> Result { trace!(target: LOG_TARGET, "rpc: finalized_head"); - RpcApi::::finalized_head(self.as_online().rpc_client()).await.map_err(|e| { - error!("Error = {:?}", e); - "rpc finalized_head failed." - }) + + // sadly this pretty much unreadable... + ChainApi::<(), _, B::Header, ()>::finalized_head(self.as_online().rpc_client()) + .await + .map_err(|e| { + error!(target: LOG_TARGET, "Error = {:?}", e); + "rpc finalized_head failed." + }) } /// Get all the keys at `prefix` at `hash` using the paged, safe RPC methods. - async fn get_keys_paged( + async fn rpc_get_keys_paged( &self, prefix: StorageKey, at: B::Hash, ) -> Result, &'static str> { - const PAGE: u32 = 512; let mut last_key: Option = None; let mut all_keys: Vec = vec![]; let keys = loop { - let page = RpcApi::::get_keys_paged( - self.as_online().rpc_client(), - Some(prefix.clone()), - PAGE, - last_key.clone(), - Some(at), - ) - .await - .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); - "rpc get_keys failed" - })?; + let page = self + .as_online() + .rpc_client() + .storage_keys_paged(Some(prefix.clone()), PAGE, last_key.clone(), Some(at)) + .await + .map_err(|e| { + error!(target: LOG_TARGET, "Error = {:?}", e); + "rpc get_keys failed" + })?; let page_len = page.len(); + all_keys.extend(page); if page_len < PAGE as usize { - debug!(target: LOG_TARGET, "last page received: {}", page_len); + log::debug!(target: LOG_TARGET, "last page received: {}", page_len); break all_keys } else { let new_last_key = all_keys.last().expect("all_keys is populated; has .last(); qed"); - debug!( + log::debug!( target: LOG_TARGET, - "new total = {}, full page received: {:?}", + "new total = {}, full page received: {}", all_keys.len(), HexDisplay::from(new_last_key) ); @@ -277,49 +330,59 @@ impl Builder { &self, prefix: StorageKey, at: B::Hash, - ) -> Result, &'static str> { - use jsonrpsee_ws_client::types::traits::Client; - use serde_json::to_value; - let keys = self.get_keys_paged(prefix, at).await?; + ) -> Result, &'static str> { + let keys = self.rpc_get_keys_paged(prefix, at).await?; let keys_count = keys.len(); - debug!(target: LOG_TARGET, "Querying a total of {} keys", keys.len()); + log::debug!(target: LOG_TARGET, "Querying a total of {} keys", keys.len()); + + let mut key_values: Vec = vec![]; + let mut batch_success = true; - let mut key_values: Vec = vec![]; let client = self.as_online().rpc_client(); for chunk_keys in keys.chunks(BATCH_SIZE) { - let batch = chunk_keys - .iter() - .cloned() - .map(|key| { - ( - "state_getStorage", - JsonRpcParams::Array(vec![ - to_value(key).expect("json serialization will work; qed."), - to_value(at).expect("json serialization will work; qed."), - ]), - ) - }) - .collect::>(); - let values = client.batch_request::>(batch).await.map_err(|e| { - log::error!( - target: LOG_TARGET, - "failed to execute batch: {:?}. Error: {:?}", - chunk_keys, - e - ); - "batch failed." - })?; - assert_eq!(chunk_keys.len(), values.len()); - for (idx, key) in chunk_keys.into_iter().enumerate() { - let maybe_value = values[idx].clone(); - let value = maybe_value.unwrap_or_else(|| { - log::warn!(target: LOG_TARGET, "key {:?} had none corresponding value.", &key); - StorageData(vec![]) - }); - key_values.push((key.clone(), value)); + let mut batch = BatchRequestBuilder::new(); + + for key in chunk_keys.iter() { + batch + .insert("state_getStorage", rpc_params![key, at]) + .map_err(|_| "Invalid batch params")?; + } + + let batch_response = + client.batch_request::>(batch).await.map_err(|e| { + log::error!( + target: LOG_TARGET, + "failed to execute batch: {:?}. Error: {:?}", + chunk_keys.iter().map(HexDisplay::from).collect::>(), + e + ); + "batch failed." + })?; + + assert_eq!(chunk_keys.len(), batch_response.len()); + + for (key, maybe_value) in chunk_keys.into_iter().zip(batch_response) { + match maybe_value { + Ok(Some(v)) => { + key_values.push((key.clone(), v)); + }, + Ok(None) => { + log::warn!( + target: LOG_TARGET, + "key {:?} had none corresponding value.", + &key + ); + key_values.push((key.clone(), StorageData(vec![]))); + }, + Err(e) => { + log::error!(target: LOG_TARGET, "key {:?} failed: {:?}", &key, e); + batch_success = false; + }, + }; + if key_values.len() % (10 * BATCH_SIZE) == 0 { let ratio: f64 = key_values.len() as f64 / keys_count as f64; - debug!( + log::debug!( target: LOG_TARGET, "progress = {:.2} [{} / {}]", ratio, @@ -330,58 +393,272 @@ impl Builder { } } - Ok(key_values) + if batch_success { + Ok(key_values) + } else { + Err("batch failed.") + } + } + + /// Get the values corresponding to `child_keys` at the given `prefixed_top_key`. + pub(crate) async fn rpc_child_get_storage_paged( + &self, + prefixed_top_key: &StorageKey, + child_keys: Vec, + at: B::Hash, + ) -> Result, &'static str> { + let mut child_kv_inner = vec![]; + let mut batch_success = true; + + for batch_child_key in child_keys.chunks(BATCH_SIZE) { + let mut batch_request = BatchRequestBuilder::new(); + + for key in batch_child_key { + batch_request + .insert( + "childstate_getStorage", + rpc_params![ + PrefixedStorageKey::new(prefixed_top_key.as_ref().to_vec()), + key, + at + ], + ) + .map_err(|_| "Invalid batch params")?; + } + + let batch_response = self + .as_online() + .rpc_client() + .batch_request::>(batch_request) + .await + .map_err(|e| { + log::error!( + target: LOG_TARGET, + "failed to execute batch: {:?}. Error: {:?}", + batch_child_key, + e + ); + "batch failed." + })?; + + assert_eq!(batch_child_key.len(), batch_response.len()); + + for (key, maybe_value) in batch_child_key.iter().zip(batch_response) { + match maybe_value { + Ok(Some(v)) => { + child_kv_inner.push((key.clone(), v)); + }, + Ok(None) => { + log::warn!( + target: LOG_TARGET, + "key {:?} had none corresponding value.", + &key + ); + child_kv_inner.push((key.clone(), StorageData(vec![]))); + }, + Err(e) => { + log::error!(target: LOG_TARGET, "key {:?} failed: {:?}", &key, e); + batch_success = false; + }, + }; + } + } + + if batch_success { + Ok(child_kv_inner) + } else { + Err("batch failed.") + } + } + + pub(crate) async fn rpc_child_get_keys( + &self, + prefixed_top_key: &StorageKey, + 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( + self.as_online().rpc_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." + })?; + + debug!( + target: LOG_TARGET, + "scraped {} child-keys of the child-bearing top key: {}", + child_keys.len(), + HexDisplay::from(prefixed_top_key) + ); + + Ok(child_keys) } } // Internal methods -impl Builder { - /// Save the given data as state snapshot. - fn save_state_snapshot(&self, data: &[KeyPair], path: &Path) -> Result<(), &'static str> { - debug!(target: LOG_TARGET, "writing to state snapshot file {:?}", path); - fs::write(path, data.encode()).map_err(|_| "fs::write failed.")?; +impl Builder +where + B::Hash: DeserializeOwned, + B::Header: DeserializeOwned, +{ + /// Save the given data to the top keys snapshot. + fn save_top_snapshot(&self, data: &[KeyValue], path: &PathBuf) -> Result<(), &'static str> { + let mut path = path.clone(); + let encoded = data.encode(); + path.set_extension("top"); + debug!( + target: LOG_TARGET, + "writing {} bytes to state snapshot file {:?}", + encoded.len(), + path + ); + fs::write(path, encoded).map_err(|_| "fs::write failed.")?; Ok(()) } - /// initialize `Self` from state snapshot. Panics if the file does not exist. - fn load_state_snapshot(&self, path: &Path) -> Result, &'static str> { - info!(target: LOG_TARGET, "scraping key-pairs from state snapshot {:?}", path); + /// Save the given data to the child keys snapshot. + fn save_child_snapshot( + &self, + data: &ChildKeyValues, + path: &PathBuf, + ) -> Result<(), &'static str> { + let mut path = path.clone(); + path.set_extension("child"); + let encoded = data.encode(); + debug!( + target: LOG_TARGET, + "writing {} bytes to state snapshot file {:?}", + encoded.len(), + path + ); + fs::write(path, encoded).map_err(|_| "fs::write failed.")?; + Ok(()) + } + + fn load_top_snapshot(&self, path: &PathBuf) -> Result { + let mut path = path.clone(); + path.set_extension("top"); + info!(target: LOG_TARGET, "loading top key-pairs from snapshot {:?}", path); + let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; + Decode::decode(&mut &*bytes).map_err(|e| { + log::error!(target: LOG_TARGET, "{:?}", e); + "decode failed" + }) + } + + fn load_child_snapshot(&self, path: &PathBuf) -> Result { + let mut path = path.clone(); + path.set_extension("child"); + info!(target: LOG_TARGET, "loading child key-pairs from snapshot {:?}", path); let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; - Decode::decode(&mut &*bytes).map_err(|_| "decode failed") + Decode::decode(&mut &*bytes).map_err(|e| { + log::error!(target: LOG_TARGET, "{:?}", e); + "decode failed" + }) + } + + /// Load all the `top` keys from the remote config, and maybe write then to cache. + async fn load_top_remote_and_maybe_save(&self) -> Result { + let top_kv = self.load_top_remote().await?; + if let Some(c) = &self.as_online().state_snapshot { + self.save_top_snapshot(&top_kv, &c.path)?; + } + Ok(top_kv) + } + + /// Load all of the child keys from the remote config, given the already scraped list of top key + /// pairs. + /// + /// Stores all values to cache as well, if provided. + async fn load_child_remote_and_maybe_save( + &self, + top_kv: &[KeyValue], + ) -> Result { + let child_kv = self.load_child_remote(top_kv).await?; + if let Some(c) = &self.as_online().state_snapshot { + self.save_child_snapshot(&child_kv, &c.path)?; + } + Ok(child_kv) + } + + /// Load all of the child keys from the remote config, given the already scraped list of top key + /// pairs. + /// + /// `top_kv` need not be only child-bearing top keys. It should be all of the top keys that are + /// included thus far. + async fn load_child_remote(&self, top_kv: &[KeyValue]) -> Result { + let child_roots = top_kv + .iter() + .filter_map(|(k, _)| is_default_child_storage_key(k.as_ref()).then(|| k)) + .collect::>(); + + info!( + target: LOG_TARGET, + "👩‍👦 scraping child-tree data from {} top keys", + child_roots.len() + ); + + let mut child_kv = vec![]; + for prefixed_top_key in child_roots { + let at = self.as_online().at.expect("at must be initialized in online mode."); + let child_keys = + self.rpc_child_get_keys(prefixed_top_key, StorageKey(vec![]), at).await?; + let child_kv_inner = + self.rpc_child_get_storage_paged(prefixed_top_key, child_keys, at).await?; + + let prefixed_top_key = PrefixedStorageKey::new(prefixed_top_key.clone().0); + let un_prefixed = match ChildType::from_prefixed_key(&prefixed_top_key) { + Some((ChildType::ParentKeyId, storage_key)) => storage_key, + None => { + log::error!(target: LOG_TARGET, "invalid key: {:?}", prefixed_top_key); + return Err("Invalid child key") + }, + }; + + child_kv.push((ChildInfo::new_default(un_prefixed), child_kv_inner)); + } + + Ok(child_kv) } /// Build `Self` from a network node denoted by `uri`. - async fn load_remote(&self) -> Result, &'static str> { + async fn load_top_remote(&self) -> Result { let config = self.as_online(); let at = self .as_online() .at - .expect("online config must be initialized by this point; qed.") - .clone(); - info!(target: LOG_TARGET, "scraping key-pairs from remote @ {:?}", at); + .expect("online config must be initialized by this point; qed."); + log::info!(target: LOG_TARGET, "scraping key-pairs from remote @ {:?}", at); - let mut keys_and_values = if config.modules.len() > 0 { + let mut keys_and_values = if config.pallets.len() > 0 { let mut filtered_kv = vec![]; - for f in config.modules.iter() { - let hashed_prefix = StorageKey(twox_128(f.as_bytes()).to_vec()); - let module_kv = self.rpc_get_pairs_paged(hashed_prefix.clone(), at).await?; - info!( + for p in config.pallets.iter() { + let hashed_prefix = StorageKey(twox_128(p.as_bytes()).to_vec()); + let pallet_kv = self.rpc_get_pairs_paged(hashed_prefix.clone(), at).await?; + log::info!( target: LOG_TARGET, - "downloaded data for module {} (count: {} / prefix: {:?}).", - f, - module_kv.len(), + "downloaded data for module {} (count: {} / prefix: {}).", + p, + pallet_kv.len(), HexDisplay::from(&hashed_prefix), ); - filtered_kv.extend(module_kv); + filtered_kv.extend(pallet_kv); } filtered_kv } else { - info!(target: LOG_TARGET, "downloading data for all modules."); + log::info!(target: LOG_TARGET, "downloading data for all pallets."); self.rpc_get_pairs_paged(StorageKey(vec![]), at).await? }; for prefix in &self.hashed_prefixes { - debug!( + log::info!( target: LOG_TARGET, "adding data for hashed prefix: {:?}", HexDisplay::from(prefix) @@ -393,7 +670,11 @@ impl Builder { for key in &self.hashed_keys { let key = StorageKey(key.to_vec()); - debug!(target: LOG_TARGET, "adding data for hashed key: {:?}", HexDisplay::from(&key)); + log::info!( + target: LOG_TARGET, + "adding data for hashed key: {:?}", + HexDisplay::from(&key) + ); let value = self.rpc_get_storage(key.clone(), Some(at)).await?; keys_and_values.push((key, value)); } @@ -402,16 +683,8 @@ impl Builder { } pub(crate) async fn init_remote_client(&mut self) -> Result<(), &'static str> { - let mut online = self.as_online_mut(); - debug!(target: LOG_TARGET, "initializing remote client to {:?}", online.transport.uri); - // First, initialize the ws client. - let ws_client = WsClientBuilder::default() - .max_request_body_size(u32::MAX) - .build(&online.transport.uri) - .await - .map_err(|_| "failed to build ws client")?; - online.transport.client = Some(ws_client); + self.as_online_mut().transport.map_uri().await?; // Then, if `at` is not set, set it. if self.as_online().at.is_none() { @@ -422,26 +695,67 @@ impl Builder { Ok(()) } - pub(crate) async fn pre_build(mut self) -> Result, &'static str> { - let mut base_kv = match self.mode.clone() { - Mode::Offline(config) => self.load_state_snapshot(&config.state_snapshot.path)?, - Mode::Online(config) => { + pub(crate) async fn pre_build( + mut self, + ) -> Result<(TopKeyValues, ChildKeyValues), &'static str> { + let mut top_kv = match self.mode.clone() { + Mode::Offline(config) => self.load_top_snapshot(&config.state_snapshot.path)?, + Mode::Online(_) => { self.init_remote_client().await?; - let kp = self.load_remote().await?; - if let Some(c) = config.state_snapshot { - self.save_state_snapshot(&kp, &c.path)?; + self.load_top_remote_and_maybe_save().await? + }, + Mode::OfflineOrElseOnline(offline_config, _) => { + if let Ok(kv) = self.load_top_snapshot(&offline_config.state_snapshot.path) { + kv + } else { + self.init_remote_client().await?; + self.load_top_remote_and_maybe_save().await? } - kp }, }; - debug!( - target: LOG_TARGET, - "extending externalities with {} manually injected key-values", - self.inject.len() - ); - base_kv.extend(self.inject.clone()); - Ok(base_kv) + // inject manual key values. + if !self.hashed_key_values.is_empty() { + log::info!( + target: LOG_TARGET, + "extending externalities with {} manually injected key-values", + self.hashed_key_values.len() + ); + top_kv.extend(self.hashed_key_values.clone()); + } + + // exclude manual key values. + if !self.hashed_blacklist.is_empty() { + log::info!( + target: LOG_TARGET, + "excluding externalities from {} keys", + self.hashed_blacklist.len() + ); + top_kv.retain(|(k, _)| !self.hashed_blacklist.contains(&k.0)) + } + + let child_kv = match self.mode.clone() { + Mode::Online(_) => self.load_child_remote_and_maybe_save(&top_kv).await?, + Mode::OfflineOrElseOnline(offline_config, _) => { + if let Ok(kv) = self.load_child_snapshot(&offline_config.state_snapshot.path) { + kv + } else { + self.load_child_remote_and_maybe_save(&top_kv).await? + } + }, + Mode::Offline(ref config) => self + .load_child_snapshot(&config.state_snapshot.path) + .map_err(|why| { + log::warn!( + target: LOG_TARGET, + "failed to load child-key file due to {:?}.", + why + ) + }) + .unwrap_or_default(), + }; + + Ok((top_kv, child_kv)) } } @@ -453,47 +767,125 @@ impl Builder { } /// Inject a manual list of key and values to the storage. - pub fn inject_key_value(mut self, injections: &[KeyPair]) -> Self { + pub fn inject_hashed_key_value(mut self, injections: &[KeyValue]) -> Self { for i in injections { - self.inject.push(i.clone()); + self.hashed_key_values.push(i.clone()); } self } /// Inject a hashed prefix. This is treated as-is, and should be pre-hashed. /// + /// Only relevant is `Mode::Online` is being used. Noop otherwise. + /// /// This should be used to inject a "PREFIX", like a storage (double) map. pub fn inject_hashed_prefix(mut self, hashed: &[u8]) -> Self { self.hashed_prefixes.push(hashed.to_vec()); self } + /// Just a utility wrapper of [`Self::inject_hashed_prefix`] that injects + /// [`DEFAULT_CHILD_STORAGE_KEY_PREFIX`] as a prefix. + /// + /// Only relevant is `Mode::Online` is being used. Noop otherwise. + /// + /// If set, this will guarantee that the child-tree data of ALL pallets will be downloaded. + /// + /// This is not needed if the entire state is being downloaded. + /// + /// Otherwise, the only other way to make sure a child-tree is manually included is to inject + /// its root (`DEFAULT_CHILD_STORAGE_KEY_PREFIX`, plus some other postfix) into + /// [`Self::inject_hashed_key`]. Unfortunately, there's no federated way of managing child tree + /// roots as of now and each pallet does its own thing. Therefore, it is not possible for this + /// library to automatically include child trees of pallet X, when its top keys are included. + pub fn inject_default_child_tree_prefix(self) -> Self { + self.inject_hashed_prefix(DEFAULT_CHILD_STORAGE_KEY_PREFIX) + } + /// Inject a hashed key to scrape. This is treated as-is, and should be pre-hashed. /// + /// Only relevant is `Mode::Online` is being used. Noop otherwise. + /// /// This should be used to inject a "KEY", like a storage value. pub fn inject_hashed_key(mut self, hashed: &[u8]) -> Self { self.hashed_keys.push(hashed.to_vec()); self } + /// Blacklist this hashed key from the final externalities. This is treated as-is, and should be + /// pre-hashed. + pub fn blacklist_hashed_key(mut self, hashed: &[u8]) -> Self { + self.hashed_blacklist.push(hashed.to_vec()); + self + } + /// Configure a state snapshot to be used. pub fn mode(mut self, mode: Mode) -> Self { self.mode = mode; self } + /// The state version to use. + pub fn state_version(mut self, version: StateVersion) -> Self { + self.state_version = version; + self + } + + /// overwrite the `at` value, if `mode` is set to [`Mode::Online`]. + /// + /// noop if `mode` is [`Mode::Offline`] + pub fn overwrite_online_at(mut self, at: B::Hash) -> Self { + if let Mode::Online(mut online) = self.mode.clone() { + online.at = Some(at); + self.mode = Mode::Online(online); + } + self + } +} + +// Public methods +impl Builder +where + B::Header: DeserializeOwned, +{ /// Build the test externalities. pub async fn build(self) -> Result { - let kv = self.pre_build().await?; - let mut ext = TestExternalities::new_empty(); - - debug!(target: LOG_TARGET, "injecting a total of {} keys", kv.len()); - for (k, v) in kv { - let (k, v) = (k.0, v.0); - // Insert the key,value pair into the test trie backend - ext.insert(k, v); + let state_version = self.state_version; + let (top_kv, child_kv) = self.pre_build().await?; + let mut ext = TestExternalities::new_with_code_and_state( + Default::default(), + Default::default(), + state_version, + ); + + info!(target: LOG_TARGET, "injecting a total of {} top keys", top_kv.len()); + for (k, v) in top_kv { + // skip writing the child root data. + if is_default_child_storage_key(k.as_ref()) { + continue + } + ext.insert(k.0, v.0); } + info!( + target: LOG_TARGET, + "injecting a total of {} child keys", + child_kv.iter().flat_map(|(_, kv)| kv).count() + ); + + for (info, key_values) in child_kv { + for (k, v) in key_values { + ext.insert_child(info.clone(), k.0, v.0); + } + } + + ext.commit_all().unwrap(); + info!( + target: LOG_TARGET, + "initialized state externalities with storage root {:?}", + ext.as_backend().root() + ); + Ok(ext) } } @@ -530,6 +922,36 @@ mod tests { .expect("Can't read state snapshot file") .execute_with(|| {}); } + + #[tokio::test] + async fn can_exclude_from_cache() { + init_logger(); + + // get the first key from the cache file. + let some_key = Builder::::new() + .mode(Mode::Offline(OfflineConfig { + state_snapshot: SnapshotConfig::new("test_data/proxy_test"), + })) + .build() + .await + .expect("Can't read state snapshot file") + .execute_with(|| { + let key = + sp_io::storage::next_key(&[]).expect("some key must exist in the snapshot"); + assert!(sp_io::storage::get(&key).is_some()); + key + }); + + Builder::::new() + .mode(Mode::Offline(OfflineConfig { + state_snapshot: SnapshotConfig::new("test_data/proxy_test"), + })) + .blacklist_hashed_key(&some_key) + .build() + .await + .expect("Can't read state snapshot file") + .execute_with(|| assert!(sp_io::storage::get(&some_key).is_none())); + } } #[cfg(all(test, feature = "remote-test"))] @@ -537,96 +959,202 @@ mod remote_tests { use super::test_prelude::*; #[tokio::test] - async fn can_build_one_pallet() { + async fn offline_else_online_works() { + init_logger(); + // this shows that in the second run, we use the remote and create a cache. + Builder::::new() + .mode(Mode::OfflineOrElseOnline( + OfflineConfig { + state_snapshot: SnapshotConfig::new("offline_else_online_works_data"), + }, + OnlineConfig { + pallets: vec!["Proxy".to_owned()], + state_snapshot: Some(SnapshotConfig::new("offline_else_online_works_data")), + ..Default::default() + }, + )) + .build() + .await + .unwrap() + .execute_with(|| {}); + + // this shows that in the second run, we are not using the remote + Builder::::new() + .mode(Mode::OfflineOrElseOnline( + OfflineConfig { + state_snapshot: SnapshotConfig::new("offline_else_online_works_data"), + }, + OnlineConfig { + pallets: vec!["Proxy".to_owned()], + state_snapshot: Some(SnapshotConfig::new("offline_else_online_works_data")), + transport: "ws://non-existent:666".to_owned().into(), + ..Default::default() + }, + )) + .build() + .await + .unwrap() + .execute_with(|| {}); + + let to_delete = std::fs::read_dir(Path::new(".")) + .unwrap() + .into_iter() + .map(|d| d.unwrap()) + .filter(|p| { + p.path().file_name().unwrap_or_default() == "offline_else_online_works_data" || + p.path().extension().unwrap_or_default() == "top" || + p.path().extension().unwrap_or_default() == "child" + }) + .collect::>(); + assert!(to_delete.len() > 0); + for d in to_delete { + std::fs::remove_file(d.path()).unwrap(); + } + } + + #[tokio::test] + #[ignore = "too slow"] + async fn can_build_one_big_pallet() { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - modules: vec!["System".to_owned()], + pallets: vec!["System".to_owned()], ..Default::default() })) .build() .await - .expect("Can't reach the remote node. Is it running?") + .unwrap() .execute_with(|| {}); } #[tokio::test] - async fn can_build_few_pallet() { + async fn can_build_one_small_pallet() { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - modules: vec![ - "Proxy".to_owned(), - "Multisig".to_owned(), - "PhragmenElection".to_owned(), - ], + transport: "wss://kusama-rpc.polkadot.io:443".to_owned().into(), + pallets: vec!["Council".to_owned()], + ..Default::default() + })) + .build() + .await + .unwrap() + .execute_with(|| {}); + + Builder::::new() + .mode(Mode::Online(OnlineConfig { + transport: "wss://rpc.polkadot.io:443".to_owned().into(), + pallets: vec!["Council".to_owned()], ..Default::default() })) .build() .await - .expect("Can't reach the remote node. Is it running?") + .unwrap() .execute_with(|| {}); } #[tokio::test] - async fn sanity_check_decoding() { - use pallet_elections_phragmen::SeatHolder; - use sp_core::crypto::Ss58Codec; - type AccountId = sp_runtime::AccountId32; - type Balance = u128; - frame_support::generate_storage_alias!( - PhragmenElection, - Members => - Value>> - ); + async fn can_build_few_pallet() { + init_logger(); + Builder::::new() + .mode(Mode::Online(OnlineConfig { + transport: "wss://kusama-rpc.polkadot.io:443".to_owned().into(), + pallets: vec!["Proxy".to_owned(), "Multisig".to_owned()], + ..Default::default() + })) + .build() + .await + .unwrap() + .execute_with(|| {}); + + Builder::::new() + .mode(Mode::Online(OnlineConfig { + transport: "wss://rpc.polkadot.io:443".to_owned().into(), + pallets: vec!["Proxy".to_owned(), "Multisig".to_owned()], + ..Default::default() + })) + .build() + .await + .unwrap() + .execute_with(|| {}); + } + #[tokio::test] + async fn can_create_top_snapshot() { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - modules: vec!["PhragmenElection".to_owned()], + state_snapshot: Some(SnapshotConfig::new("can_create_top_snapshot_data")), + pallets: vec!["Proxy".to_owned()], ..Default::default() })) .build() .await - .expect("Can't reach the remote node. Is it running?") - .execute_with(|| { - // Gav's polkadot account. 99% this will be in the council. - let gav_polkadot = - AccountId::from_ss58check("13RDY9nrJpyTDBSUdBw12dGwhk19sGwsrVZ2bxkzYHBSagP2") - .unwrap(); - let members = Members::get().unwrap(); - assert!(members - .iter() - .map(|s| s.who.clone()) - .find(|a| a == &gav_polkadot) - .is_some()); - }); + .unwrap() + .execute_with(|| {}); + + let to_delete = std::fs::read_dir(Path::new(".")) + .unwrap() + .into_iter() + .map(|d| d.unwrap()) + .filter(|p| { + p.path().file_name().unwrap_or_default() == "can_create_top_snapshot_data" || + p.path().extension().unwrap_or_default() == "top" || + p.path().extension().unwrap_or_default() == "child" + }) + .collect::>(); + + assert!(to_delete.len() > 0); + + for d in to_delete { + use std::os::unix::fs::MetadataExt; + if d.path().extension().unwrap_or_default() == "top" { + // if this is the top snapshot it must not be empty. + assert!(std::fs::metadata(d.path()).unwrap().size() > 1); + } else { + // the child is empty for this pallet. + assert!(std::fs::metadata(d.path()).unwrap().size() == 1); + } + std::fs::remove_file(d.path()).unwrap(); + } } #[tokio::test] - async fn can_create_state_snapshot() { + async fn can_create_child_snapshot() { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - state_snapshot: Some(SnapshotConfig::new("test_snapshot_to_remove.bin")), - modules: vec!["Balances".to_owned()], + state_snapshot: Some(SnapshotConfig::new("can_create_child_snapshot_data")), + pallets: vec!["Crowdloan".to_owned()], ..Default::default() })) + .inject_default_child_tree_prefix() .build() .await - .expect("Can't reach the remote node. Is it running?") + .unwrap() .execute_with(|| {}); - let to_delete = std::fs::read_dir(SnapshotConfig::default().path) + let to_delete = std::fs::read_dir(Path::new(".")) .unwrap() .into_iter() .map(|d| d.unwrap()) - .filter(|p| p.path().extension().unwrap_or_default() == "bin") + .filter(|p| { + p.path().file_name().unwrap_or_default() == "can_create_child_snapshot_data" || + p.path().extension().unwrap_or_default() == "top" || + p.path().extension().unwrap_or_default() == "child" + }) .collect::>(); assert!(to_delete.len() > 0); for d in to_delete { + use std::os::unix::fs::MetadataExt; + // if this is the top snapshot it must not be empty + if d.path().extension().unwrap_or_default() == "child" { + assert!(std::fs::metadata(d.path()).unwrap().size() > 1); + } else { + assert!(std::fs::metadata(d.path()).unwrap().size() > 1); + } std::fs::remove_file(d.path()).unwrap(); } } @@ -635,9 +1163,52 @@ mod remote_tests { async fn can_fetch_all() { init_logger(); Builder::::new() + .mode(Mode::Online(OnlineConfig { + state_snapshot: Some(SnapshotConfig::new("can_fetch_all_data")), + ..Default::default() + })) + .build() + .await + .unwrap() + .execute_with(|| {}); + + let to_delete = std::fs::read_dir(Path::new(".")) + .unwrap() + .into_iter() + .map(|d| d.unwrap()) + .filter(|p| { + p.path().file_name().unwrap_or_default() == "can_fetch_all_data" || + p.path().extension().unwrap_or_default() == "top" || + p.path().extension().unwrap_or_default() == "child" + }) + .collect::>(); + + assert!(to_delete.len() > 0); + + for d in to_delete { + use std::os::unix::fs::MetadataExt; + // if we download everything, child tree must also be filled. + if d.path().extension().unwrap_or_default() == "child" { + assert!(std::fs::metadata(d.path()).unwrap().size() > 1); + } else { + assert!(std::fs::metadata(d.path()).unwrap().size() > 1); + } + std::fs::remove_file(d.path()).unwrap(); + } + } + + #[tokio::test] + async fn can_build_child_tree() { + init_logger(); + Builder::::new() + .mode(Mode::Online(OnlineConfig { + transport: "wss://rpc.polkadot.io:443".to_owned().into(), + pallets: vec!["Crowdloan".to_owned()], + ..Default::default() + })) .build() .await - .expect("Can't reach the remote node. Is it running?") + .unwrap() .execute_with(|| {}); } } diff --git a/utils/frame/remote-externalities/src/rpc_api.rs b/utils/frame/remote-externalities/src/rpc_api.rs deleted file mode 100644 index 24050856a96a1..0000000000000 --- a/utils/frame/remote-externalities/src/rpc_api.rs +++ /dev/null @@ -1,111 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 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. - -//! WS RPC API for one off RPC calls to a substrate node. -// TODO: Consolidate one off RPC calls https://github.com/paritytech/substrate/issues/8988 - -use jsonrpsee_ws_client::{ - types::{traits::Client, v2::params::JsonRpcParams}, - WsClient, WsClientBuilder, -}; -use sp_runtime::{ - generic::SignedBlock, - traits::{Block as BlockT, Header as HeaderT}, -}; - -/// Get the header of the block identified by `at` -pub async fn get_header(from: S, at: Block::Hash) -> Result -where - Block: BlockT, - Block::Header: serde::de::DeserializeOwned, - S: AsRef, -{ - let params = vec![hash_to_json::(at)?]; - let client = build_client(from).await?; - - client - .request::("chain_getHeader", JsonRpcParams::Array(params)) - .await - .map_err(|e| format!("chain_getHeader request failed: {:?}", e)) -} - -/// Get the finalized head -pub async fn get_finalized_head(from: S) -> Result -where - Block: BlockT, - S: AsRef, -{ - let client = build_client(from).await?; - - client - .request::("chain_getFinalizedHead", JsonRpcParams::NoParams) - .await - .map_err(|e| format!("chain_getFinalizedHead request failed: {:?}", e)) -} - -/// Get the signed block identified by `at`. -pub async fn get_block(from: S, at: Block::Hash) -> Result -where - S: AsRef, - Block: BlockT + serde::de::DeserializeOwned, - Block::Header: HeaderT, -{ - let params = vec![hash_to_json::(at)?]; - let client = build_client(from).await?; - let signed_block = client - .request::>("chain_getBlock", JsonRpcParams::Array(params)) - .await - .map_err(|e| format!("chain_getBlock request failed: {:?}", e))?; - - Ok(signed_block.block) -} - -/// Convert a block hash to a serde json value. -fn hash_to_json(hash: Block::Hash) -> Result { - serde_json::to_value(hash) - .map_err(|e| format!("Block hash could not be converted to JSON: {:?}", e)) -} - -/// Build a website client that connects to `from`. -async fn build_client>(from: S) -> Result { - WsClientBuilder::default() - .max_request_body_size(u32::MAX) - .build(from.as_ref()) - .await - .map_err(|e| format!("`WsClientBuilder` failed to build: {:?}", e)) -} - -/// Get the runtime version of a given chain. -pub async fn get_runtime_version( - from: S, - at: Option, -) -> Result -where - S: AsRef, - Block: BlockT + serde::de::DeserializeOwned, - Block::Header: HeaderT, -{ - let params = if let Some(at) = at { vec![hash_to_json::(at)?] } else { vec![] }; - let client = build_client(from).await?; - client - .request::( - "state_getRuntimeVersion", - JsonRpcParams::Array(params), - ) - .await - .map_err(|e| format!("state_getRuntimeVersion request failed: {:?}", e)) -} diff --git a/utils/frame/remote-externalities/test_data/proxy_test b/utils/frame/remote-externalities/test_data/proxy_test.top similarity index 100% rename from utils/frame/remote-externalities/test_data/proxy_test rename to utils/frame/remote-externalities/test_data/proxy_test.top diff --git a/utils/frame/rpc/client/Cargo.toml b/utils/frame/rpc/client/Cargo.toml new file mode 100644 index 0000000000000..ee9982971cee3 --- /dev/null +++ b/utils/frame/rpc/client/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "substrate-rpc-client" +version = "0.10.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Shared JSON-RPC client" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +jsonrpsee = { version = "0.16.2", features = ["ws-client"] } +sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } +async-trait = "0.1.57" +serde = "1" +sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +log = "0.4" + +[dev-dependencies] +tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread", "sync"] } +sp-core = { path = "../../../../primitives/core" } diff --git a/utils/frame/rpc/client/src/lib.rs b/utils/frame/rpc/client/src/lib.rs new file mode 100644 index 0000000000000..a211fc6c6983e --- /dev/null +++ b/utils/frame/rpc/client/src/lib.rs @@ -0,0 +1,268 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 JSON-RPC client related code and abstractions. +//! +//! It exposes a `WebSocket JSON-RPC` client that implements the RPC interface in [`sc-rpc-api`] +//! along with some abstractions. +//! +//! ## Usage +//! +//! ```no_run +//! # use substrate_rpc_client::{ws_client, StateApi}; +//! # use sp_core::{H256, storage::StorageKey}; +//! +//! #[tokio::main] +//! async fn main() { +//! +//! let client = ws_client("ws://127.0.0.1:9944").await.unwrap(); +//! client.storage(StorageKey(vec![]), Some(H256::zero())).await.unwrap(); +//! +//! // if all type params are not known you need to provide type params +//! StateApi::::storage(&client, StorageKey(vec![]), None).await.unwrap(); +//! } +//! ``` + +use async_trait::async_trait; +use serde::de::DeserializeOwned; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use std::collections::VecDeque; + +pub use jsonrpsee::{ + core::{ + client::{ClientT, Subscription, SubscriptionClientT}, + params::BatchRequestBuilder, + }, + rpc_params, + ws_client::{WsClient, WsClientBuilder}, +}; +pub use sc_rpc_api::{ + author::AuthorApiClient as AuthorApi, chain::ChainApiClient as ChainApi, + child_state::ChildStateApiClient as ChildStateApi, dev::DevApiClient as DevApi, + offchain::OffchainApiClient as OffchainApi, state::StateApiClient as StateApi, + system::SystemApiClient as SystemApi, +}; + +/// Create a new `WebSocket` connection with shared settings. +pub async fn ws_client(uri: impl AsRef) -> Result { + WsClientBuilder::default() + .max_request_body_size(u32::MAX) + .request_timeout(std::time::Duration::from_secs(60 * 10)) + .connection_timeout(std::time::Duration::from_secs(60)) + .max_notifs_per_subscription(1024) + .build(uri) + .await + .map_err(|e| format!("`WsClientBuilder` failed to build: {:?}", e)) +} + +/// Abstraction over RPC calling for headers. +#[async_trait] +pub trait HeaderProvider +where + Block::Header: HeaderT, +{ + /// Awaits for the header of the block with hash `hash`. + async fn get_header(&self, hash: Block::Hash) -> Block::Header; +} + +#[async_trait] +impl HeaderProvider for WsClient +where + Block::Header: DeserializeOwned, +{ + async fn get_header(&self, hash: Block::Hash) -> Block::Header { + ChainApi::<(), Block::Hash, Block::Header, ()>::header(self, Some(hash)) + .await + .unwrap() + .unwrap() + } +} + +/// Abstraction over RPC subscription for finalized headers. +#[async_trait] +pub trait HeaderSubscription +where + Block::Header: HeaderT, +{ + /// Await for the next finalized header from the subscription. + /// + /// Returns `None` if either the subscription has been closed or there was an error when reading + /// an object from the client. + async fn next_header(&mut self) -> Option; +} + +#[async_trait] +impl HeaderSubscription for Subscription +where + Block::Header: DeserializeOwned, +{ + async fn next_header(&mut self) -> Option { + match self.next().await { + Some(Ok(header)) => Some(header), + None => { + log::warn!("subscription closed"); + None + }, + Some(Err(why)) => { + log::warn!("subscription returned error: {:?}. Probably decoding has failed.", why); + None + }, + } + } +} + +/// Stream of all finalized headers. +/// +/// Returned headers are guaranteed to be ordered. There are no missing headers (even if some of +/// them lack justification). +pub struct FinalizedHeaders< + 'a, + Block: BlockT, + HP: HeaderProvider, + HS: HeaderSubscription, +> { + header_provider: &'a HP, + subscription: HS, + fetched_headers: VecDeque, + last_returned: Option<::Hash>, +} + +impl<'a, Block: BlockT, HP: HeaderProvider, HS: HeaderSubscription> + FinalizedHeaders<'a, Block, HP, HS> +where + ::Header: DeserializeOwned, +{ + pub fn new(header_provider: &'a HP, subscription: HS) -> Self { + Self { + header_provider, + subscription, + fetched_headers: VecDeque::new(), + last_returned: None, + } + } + + /// Reads next finalized header from the subscription. If some headers (without justification) + /// have been skipped, fetches them as well. Returns number of headers that have been fetched. + /// + /// All fetched headers are stored in `self.fetched_headers`. + async fn fetch(&mut self) -> usize { + let last_finalized = match self.subscription.next_header().await { + Some(header) => header, + None => return 0, + }; + + self.fetched_headers.push_front(last_finalized.clone()); + + let mut last_finalized_parent = *last_finalized.parent_hash(); + let last_returned = self.last_returned.unwrap_or(last_finalized_parent); + + while last_finalized_parent != last_returned { + let parent_header = self.header_provider.get_header(last_finalized_parent).await; + self.fetched_headers.push_front(parent_header.clone()); + last_finalized_parent = *parent_header.parent_hash(); + } + + self.fetched_headers.len() + } + + /// Get the next finalized header. + pub async fn next(&mut self) -> Option { + if self.fetched_headers.is_empty() { + self.fetch().await; + } + + if let Some(header) = self.fetched_headers.pop_front() { + self.last_returned = Some(header.hash()); + Some(header) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_runtime::testing::{Block as TBlock, ExtrinsicWrapper, Header, H256}; + use std::sync::Arc; + use tokio::sync::Mutex; + + type Block = TBlock>; + type BlockNumber = u64; + type Hash = H256; + + struct MockHeaderProvider(pub Arc>>); + + fn headers() -> Vec
{ + let mut headers = vec![Header::new_from_number(0)]; + for n in 1..11 { + headers.push(Header { + parent_hash: headers.last().unwrap().hash(), + ..Header::new_from_number(n) + }) + } + headers + } + + #[async_trait] + impl HeaderProvider for MockHeaderProvider { + async fn get_header(&self, _hash: Hash) -> Header { + let height = self.0.lock().await.pop_front().unwrap(); + headers()[height as usize].clone() + } + } + + struct MockHeaderSubscription(pub VecDeque); + + #[async_trait] + impl HeaderSubscription for MockHeaderSubscription { + async fn next_header(&mut self) -> Option
{ + self.0.pop_front().map(|h| headers()[h as usize].clone()) + } + } + + #[tokio::test] + async fn finalized_headers_works_when_every_block_comes_from_subscription() { + let heights = vec![4, 5, 6, 7]; + + let provider = MockHeaderProvider(Default::default()); + let subscription = MockHeaderSubscription(heights.clone().into()); + let mut headers = FinalizedHeaders::new(&provider, subscription); + + for h in heights { + assert_eq!(h, headers.next().await.unwrap().number); + } + assert_eq!(None, headers.next().await); + } + + #[tokio::test] + async fn finalized_headers_come_from_subscription_and_provider_if_in_need() { + let all_heights = 3..11; + let heights_in_subscription = vec![3, 4, 6, 10]; + // Consecutive headers will be requested in the reversed order. + let heights_not_in_subscription = vec![5, 9, 8, 7]; + + let provider = MockHeaderProvider(Arc::new(Mutex::new(heights_not_in_subscription.into()))); + let subscription = MockHeaderSubscription(heights_in_subscription.into()); + let mut headers = FinalizedHeaders::new(&provider, subscription); + + for h in all_heights { + assert_eq!(h, headers.next().await.unwrap().number); + } + assert_eq!(None, headers.next().await); + } +} diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml new file mode 100644 index 0000000000000..3a1b4b8b6cbf8 --- /dev/null +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "substrate-state-trie-migration-rpc" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Node-specific RPC methods for interaction with state trie migration." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +serde = { version = "1", features = ["derive"] } +log = { version = "0.4.17", default-features = false } + +sp-std = { path = "../../../../primitives/std" } +sp-io = { path = "../../../../primitives/io" } +sp-core = { path = "../../../../primitives/core" } +sp-state-machine = { path = "../../../../primitives/state-machine" } +sp-trie = { path = "../../../../primitives/trie" } +trie-db = "0.24.0" + +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } + +# Substrate Dependencies +sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } +sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } +sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } + +[dev-dependencies] +serde_json = "1" diff --git a/utils/frame/rpc/state-trie-migration-rpc/README.md b/utils/frame/rpc/state-trie-migration-rpc/README.md new file mode 100644 index 0000000000000..03bbfdf1b5939 --- /dev/null +++ b/utils/frame/rpc/state-trie-migration-rpc/README.md @@ -0,0 +1,3 @@ +Node-specific RPC methods for interaction with trie migration. + +License: Apache-2.0 diff --git a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs new file mode 100644 index 0000000000000..2140ee8845625 --- /dev/null +++ b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs @@ -0,0 +1,177 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Rpc for state migration. + +use jsonrpsee::{ + core::{Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorCode, ErrorObject}, +}; +use sc_rpc_api::DenyUnsafe; +use serde::{Deserialize, Serialize}; +use sp_runtime::traits::Block as BlockT; +use std::sync::Arc; + +use sp_core::{ + storage::{ChildInfo, ChildType, PrefixedStorageKey}, + Hasher, +}; +use sp_state_machine::backend::AsTrieBackend; +use sp_trie::{ + trie_types::{TrieDB, TrieDBBuilder}, + KeySpacedDB, Trie, +}; +use trie_db::{ + node::{NodePlan, ValuePlan}, + TrieDBNodeIterator, +}; + +fn count_migrate<'a, H: Hasher>( + storage: &'a dyn trie_db::HashDBRef>, + root: &'a H::Out, +) -> std::result::Result<(u64, u64, TrieDB<'a, 'a, H>), String> { + let mut nb = 0u64; + let mut total_nb = 0u64; + let trie = TrieDBBuilder::new(storage, root).build(); + let iter_node = + TrieDBNodeIterator::new(&trie).map_err(|e| format!("TrieDB node iterator error: {}", e))?; + for node in iter_node { + let node = node.map_err(|e| format!("TrieDB node iterator error: {}", e))?; + match node.2.node_plan() { + NodePlan::Leaf { value, .. } | NodePlan::NibbledBranch { value: Some(value), .. } => { + total_nb += 1; + if let ValuePlan::Inline(range) = value { + if (range.end - range.start) as u32 >= + sp_core::storage::TRIE_VALUE_NODE_THRESHOLD + { + nb += 1; + } + } + }, + _ => (), + } + } + Ok((nb, total_nb, trie)) +} + +/// Check trie migration status. +pub fn migration_status(backend: &B) -> std::result::Result +where + H: Hasher, + H::Out: codec::Codec, + B: AsTrieBackend, +{ + let trie_backend = backend.as_trie_backend(); + let essence = trie_backend.essence(); + let (top_remaining_to_migrate, total_top, trie) = count_migrate(essence, essence.root())?; + + let mut child_remaining_to_migrate = 0; + let mut total_child = 0; + let mut child_roots: Vec<(ChildInfo, Vec)> = Vec::new(); + // get all child trie roots + for key_value in trie.iter().map_err(|e| format!("TrieDB node iterator error: {}", e))? { + let (key, value) = key_value.map_err(|e| format!("TrieDB node iterator error: {}", e))?; + if key[..].starts_with(sp_core::storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX) + { + let prefixed_key = PrefixedStorageKey::new(key); + let (_type, unprefixed) = ChildType::from_prefixed_key(&prefixed_key).unwrap(); + child_roots.push((ChildInfo::new_default(unprefixed), value)); + } + } + for (child_info, root) in child_roots { + let mut child_root = H::Out::default(); + let storage = KeySpacedDB::new(essence, child_info.keyspace()); + + child_root.as_mut()[..].copy_from_slice(&root[..]); + let (nb, total_top, _) = count_migrate(&storage, &child_root)?; + child_remaining_to_migrate += nb; + total_child += total_top; + } + + Ok(MigrationStatusResult { + top_remaining_to_migrate, + child_remaining_to_migrate, + total_top, + total_child, + }) +} + +/// Current state migration status. +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct MigrationStatusResult { + /// Number of top items that should migrate. + pub top_remaining_to_migrate: u64, + /// Number of child items that should migrate. + pub child_remaining_to_migrate: u64, + /// Number of top items that we will iterate on. + pub total_top: u64, + /// Number of child items that we will iterate on. + pub total_child: u64, +} + +/// Migration RPC methods. +#[rpc(server)] +pub trait StateMigrationApi { + /// Check current migration state. + /// + /// This call is performed locally without submitting any transactions. Thus executing this + /// won't change any state. Nonetheless it is a VERY costy call that should be + /// only exposed to trusted peers. + #[method(name = "state_trieMigrationStatus")] + fn call(&self, at: Option) -> RpcResult; +} + +/// An implementation of state migration specific RPC methods. +pub struct StateMigration { + client: Arc, + backend: Arc, + deny_unsafe: DenyUnsafe, + _marker: std::marker::PhantomData<(B, BA)>, +} + +impl StateMigration { + /// Create new state migration rpc for the given reference to the client. + pub fn new(client: Arc, backend: Arc, deny_unsafe: DenyUnsafe) -> Self { + StateMigration { client, backend, deny_unsafe, _marker: Default::default() } + } +} + +impl StateMigrationApiServer<::Hash> for StateMigration +where + B: BlockT, + C: Send + Sync + 'static + sc_client_api::HeaderBackend, + BA: 'static + sc_client_api::backend::Backend, +{ + fn call(&self, at: Option<::Hash>) -> RpcResult { + self.deny_unsafe.check_if_safe()?; + + let hash = at.unwrap_or_else(|| self.client.info().best_hash); + let state = self.backend.state_at(hash).map_err(error_into_rpc_err)?; + migration_status(&state).map_err(error_into_rpc_err) + } +} + +fn error_into_rpc_err(err: impl std::fmt::Display) -> JsonRpseeError { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InternalError.code(), + "Error while checking migration state", + Some(err.to_string()), + ))) +} diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index a94f18d0e8925..d098877e7302c 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -2,12 +2,12 @@ name = "substrate-frame-rpc-support" version = "3.0.0" authors = [ - "Parity Technologies ", - "Andrew Dirksen ", + "Parity Technologies ", + "Andrew Dirksen ", ] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate RPC for FRAME's support" @@ -15,15 +15,18 @@ description = "Substrate RPC for FRAME's support" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures = "0.3.16" -jsonrpc-client-transports = { version = "18.0.0", features = ["http"] } -codec = { package = "parity-scale-codec", version = "2.0.0" } +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" +jsonrpsee = { version = "0.16.2", features = ["jsonrpsee-types"] } serde = "1" frame-support = { version = "4.0.0-dev", path = "../../../../frame/support" } -sp-storage = { version = "4.0.0-dev", path = "../../../../primitives/storage" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } +sp-storage = { version = "7.0.0", path = "../../../../primitives/storage" } [dev-dependencies] +scale-info = "2.1.1" +jsonrpsee = { version = "0.16.2", features = ["ws-client", "jsonrpsee-types"] } +tokio = "1.22.0" +sp-core = { version = "7.0.0", path = "../../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" } -scale-info = "1.0" -tokio = "1.10" diff --git a/utils/frame/rpc/support/src/lib.rs b/utils/frame/rpc/support/src/lib.rs index 1b2453c361d97..a13d4f707797d 100644 --- a/utils/frame/rpc/support/src/lib.rs +++ b/utils/frame/rpc/support/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Combines [sc_rpc_api::state::StateClient] with [frame_support::storage::generator] traits +//! Combines [sc_rpc_api::state::StateApiClient] with [frame_support::storage::generator] traits //! to provide strongly typed chain state queries over rpc. #![warn(missing_docs)] @@ -23,67 +23,119 @@ use codec::{DecodeAll, FullCodec, FullEncode}; use core::marker::PhantomData; use frame_support::storage::generator::{StorageDoubleMap, StorageMap, StorageValue}; -use jsonrpc_client_transports::RpcError; -use sc_rpc_api::state::StateClient; +use jsonrpsee::core::Error as RpcError; +use sc_rpc_api::state::StateApiClient; use serde::{de::DeserializeOwned, Serialize}; use sp_storage::{StorageData, StorageKey}; /// A typed query on chain state usable from an RPC client. /// /// ```no_run -/// # use jsonrpc_client_transports::RpcError; -/// # use jsonrpc_client_transports::transports::http; +/// # use jsonrpsee::core::Error as RpcError; +/// # use jsonrpsee::ws_client::WsClientBuilder; /// # use codec::Encode; -/// # use frame_support::{decl_storage, decl_module}; +/// # use frame_support::{construct_runtime, traits::ConstU32}; /// # use substrate_frame_rpc_support::StorageQuery; -/// # use frame_system::Config; -/// # use sc_rpc_api::state::StateClient; +/// # use sc_rpc_api::state::StateApiClient; +/// # use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header}; /// # -/// # // Hash would normally be ::Hash, but we don't have -/// # // frame_system::Config implemented for TestRuntime. Here we just pretend. -/// # type Hash = (); +/// # construct_runtime!( +/// # pub enum TestRuntime where +/// # Block = frame_system::mocking::MockBlock, +/// # NodeBlock = frame_system::mocking::MockBlock, +/// # UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic, +/// # { +/// # System: frame_system::{Pallet, Call, Config, Storage, Event}, +/// # Test: pallet_test::{Pallet, Storage}, +/// # } +/// # ); /// # -/// # fn main() -> Result<(), RpcError> { -/// # tokio::runtime::Runtime::new().unwrap().block_on(test()) -/// # } -/// # -/// # struct TestRuntime; +/// # type Hash = sp_core::H256; /// # -/// # decl_module! { -/// # pub struct Module for enum Call where origin: T::Origin {} +/// # impl frame_system::Config for TestRuntime { +/// # type BaseCallFilter = (); +/// # type BlockWeights = (); +/// # type BlockLength = (); +/// # type RuntimeOrigin = RuntimeOrigin; +/// # type RuntimeCall = RuntimeCall; +/// # type Index = u64; +/// # type BlockNumber = u64; +/// # type Hash = Hash; +/// # type Hashing = BlakeTwo256; +/// # type AccountId = u64; +/// # type Lookup = IdentityLookup; +/// # type Header = Header; +/// # type RuntimeEvent = RuntimeEvent; +/// # type BlockHashCount = (); +/// # type DbWeight = (); +/// # type Version = (); +/// # type PalletInfo = PalletInfo; +/// # type AccountData = (); +/// # type OnNewAccount = (); +/// # type OnKilledAccount = (); +/// # type SystemWeightInfo = (); +/// # type SS58Prefix = (); +/// # type OnSetCode = (); +/// # type MaxConsumers = ConstU32<16>; /// # } /// # +/// # impl pallet_test::Config for TestRuntime {} +/// # +/// /// pub type Loc = (i64, i64, i64); /// pub type Block = u8; /// /// // Note that all fields are marked pub. -/// decl_storage! { -/// trait Store for Module as TestRuntime { -/// pub LastActionId: u64; -/// pub Voxels: map hasher(blake2_128_concat) Loc => Block; -/// pub Actions: map hasher(blake2_128_concat) u64 => Loc; -/// pub Prefab: double_map hasher(blake2_128_concat) u128, hasher(blake2_128_concat) (i8, i8, i8) => Block; -/// } +/// pub use self::pallet_test::*; +/// +/// #[frame_support::pallet] +/// mod pallet_test { +/// use super::*; +/// use frame_support::pallet_prelude::*; +/// +/// #[pallet::pallet] +/// #[pallet::generate_store(pub(super) trait Store)] +/// pub struct Pallet(PhantomData); +/// +/// #[pallet::config] +/// pub trait Config: frame_system::Config {} +/// +/// #[pallet::storage] +/// pub type LastActionId = StorageValue<_, u64, ValueQuery>; +/// +/// #[pallet::storage] +/// pub type Voxels = StorageMap<_, Blake2_128Concat, Loc, Block>; +/// +/// #[pallet::storage] +/// pub type Actions = StorageMap<_, Blake2_128Concat, u64, Loc>; +/// +/// #[pallet::storage] +/// pub type Prefab = StorageDoubleMap< +/// _, +/// Blake2_128Concat, u128, +/// Blake2_128Concat, (i8, i8, i8), Block +/// >; /// } /// -/// # async fn test() -> Result<(), RpcError> { -/// let conn = http::connect("http://[::1]:9933").await?; -/// let cl = StateClient::::new(conn); +/// #[tokio::main] +/// async fn main() -> Result<(), RpcError> { +/// let cl = WsClientBuilder::default().build("ws://[::1]:9944").await?; /// -/// let q = StorageQuery::value::(); -/// let _: Option = q.get(&cl, None).await?; +/// let q = StorageQuery::value::>(); +/// let hash = None::; +/// let _: Option = q.get(&cl, hash).await?; /// -/// let q = StorageQuery::map::((0, 0, 0)); -/// let _: Option = q.get(&cl, None).await?; +/// let q = StorageQuery::map::, _>((0, 0, 0)); +/// let _: Option = q.get(&cl, hash).await?; /// -/// let q = StorageQuery::map::(12); -/// let _: Option = q.get(&cl, None).await?; +/// let q = StorageQuery::map::, _>(12); +/// let _: Option = q.get(&cl, hash).await?; /// -/// let q = StorageQuery::double_map::(3, (0, 0, 0)); -/// let _: Option = q.get(&cl, None).await?; -/// # -/// # Ok(()) -/// # } +/// let q = StorageQuery::double_map::, _, _>(3, (0, 0, 0)); +/// let _: Option = q.get(&cl, hash).await?; +/// +/// Ok(()) +/// } /// ``` #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct StorageQuery { @@ -112,7 +164,7 @@ impl StorageQuery { /// Send this query over RPC, await the typed result. /// - /// Hash should be ::Hash. + /// Hash should be `::Hash`. /// /// # Arguments /// @@ -120,14 +172,18 @@ impl StorageQuery { /// /// block_index indicates the block for which state will be queried. A value of None indicates /// the latest block. - pub async fn get( + pub async fn get( self, - state_client: &StateClient, + state_client: &StateClient, block_index: Option, - ) -> Result, RpcError> { + ) -> Result, RpcError> + where + Hash: Send + Sync + 'static + DeserializeOwned + Serialize, + StateClient: StateApiClient + Sync, + { let opt: Option = state_client.storage(self.key, block_index).await?; - opt.map(|encoded| V::decode_all(&encoded.0)) + opt.map(|encoded| V::decode_all(&mut &encoded.0[..])) .transpose() - .map_err(|decode_err| RpcError::Other(Box::new(decode_err))) + .map_err(|decode_err| RpcError::Custom(decode_err.to_string())) } } diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index e9ae506ef6b06..92cf6882a10f1 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -2,9 +2,9 @@ name = "substrate-frame-rpc-system" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME's system exposed over Substrate RPC" readme = "README.md" @@ -13,23 +13,24 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } -codec = { package = "parity-scale-codec", version = "2.0.0" } -futures = "0.3.16" -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -log = "0.4.8" -sp-runtime = { version = "4.0.0-dev", path = "../../../../primitives/runtime" } -sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } +serde_json = "1" +codec = { package = "parity-scale-codec", version = "3.0.0" } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +futures = "0.3.21" +log = "0.4.17" frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } -sp-core = { version = "4.0.0-dev", path = "../../../../primitives/core" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } +sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } +sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../../client/transaction-pool/api" } +sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", path = "../../../../primitives/block-builder" } -sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] -substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } -sp-tracing = { version = "4.0.0-dev", path = "../../../../primitives/tracing" } sc-transaction-pool = { version = "4.0.0-dev", path = "../../../../client/transaction-pool" } +tokio = "1.22.0" +assert_matches = "1.3.0" +sp-tracing = { version = "6.0.0", path = "../../../../primitives/tracing" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } diff --git a/utils/frame/rpc/system/src/lib.rs b/utils/frame/rpc/system/src/lib.rs index f0f37f0b20675..72ad99e435f72 100644 --- a/utils/frame/rpc/system/src/lib.rs +++ b/utils/frame/rpc/system/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,40 +17,39 @@ //! System FRAME specific RPC methods. -use std::sync::Arc; +use std::{fmt::Display, sync::Arc}; + +use codec::{self, Codec, Decode, Encode}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorObject}, +}; -use codec::{Codec, Decode, Encode}; -use futures::{future::ready, FutureExt, TryFutureExt}; -use jsonrpc_core::{Error as RpcError, ErrorCode}; -use jsonrpc_derive::rpc; -use sc_client_api::light::{future_header, Fetcher, RemoteBlockchain, RemoteCallRequest}; use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; +use sp_api::ApiExt; use sp_block_builder::BlockBuilder; -use sp_blockchain::{Error as ClientError, HeaderBackend}; +use sp_blockchain::HeaderBackend; use sp_core::{hexdisplay::HexDisplay, Bytes}; -use sp_runtime::{generic::BlockId, traits}; +use sp_runtime::{generic::BlockId, legacy, traits}; -pub use self::gen_client::Client as SystemClient; pub use frame_system_rpc_runtime_api::AccountNonceApi; -/// Future that resolves to account nonce. -type FutureResult = jsonrpc_core::BoxFuture>; - /// System RPC methods. -#[rpc] +#[rpc(client, server)] pub trait SystemApi { /// Returns the next valid index (aka nonce) for given account. /// /// This method takes into consideration all pending transactions /// currently in the pool and if no transactions are found in the pool /// it fallbacks to query the index from the runtime (aka. state nonce). - #[rpc(name = "system_accountNextIndex", alias("account_nextIndex"))] - fn nonce(&self, account: AccountId) -> FutureResult; + #[method(name = "system_accountNextIndex", aliases = ["account_nextIndex"])] + async fn nonce(&self, account: AccountId) -> RpcResult; /// Dry run an extrinsic at a given block. Return SCALE encoded ApplyExtrinsicResult. - #[rpc(name = "system_dryRun", alias("system_dryRunAt"))] - fn dry_run(&self, extrinsic: Bytes, at: Option) -> FutureResult; + #[method(name = "system_dryRun", aliases = ["system_dryRunAt"])] + async fn dry_run(&self, extrinsic: Bytes, at: Option) -> RpcResult; } /// Error type of this RPC api. @@ -61,8 +60,8 @@ pub enum Error { RuntimeError, } -impl From for i64 { - fn from(e: Error) -> i64 { +impl From for i32 { + fn from(e: Error) -> i32 { match e { Error::RuntimeError => 1, Error::DecodeError => 2, @@ -71,22 +70,23 @@ impl From for i64 { } /// An implementation of System-specific RPC methods on full client. -pub struct FullSystem { +pub struct System { client: Arc, pool: Arc

, deny_unsafe: DenyUnsafe, _marker: std::marker::PhantomData, } -impl FullSystem { +impl System { /// Create new `FullSystem` given client and transaction pool. pub fn new(client: Arc, pool: Arc

, deny_unsafe: DenyUnsafe) -> Self { - FullSystem { client, pool, deny_unsafe, _marker: Default::default() } + Self { client, pool, deny_unsafe, _marker: Default::default() } } } -impl SystemApi<::Hash, AccountId, Index> - for FullSystem +#[async_trait] +impl + SystemApiServer<::Hash, AccountId, Index> for System where C: sp_api::ProvideRuntimeApi, C: HeaderBackend, @@ -95,146 +95,83 @@ where C::Api: BlockBuilder, P: TransactionPool + 'static, Block: traits::Block, - AccountId: Clone + std::fmt::Display + Codec, - Index: Clone + std::fmt::Display + Codec + Send + traits::AtLeast32Bit + 'static, + AccountId: Clone + Display + Codec + Send + 'static, + Index: Clone + Display + Codec + Send + traits::AtLeast32Bit + 'static, { - fn nonce(&self, account: AccountId) -> FutureResult { - let get_nonce = || { - let api = self.client.runtime_api(); - let best = self.client.info().best_hash; - let at = BlockId::hash(best); - - let nonce = api.account_nonce(&at, account.clone()).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to query nonce.".into(), - data: Some(format!("{:?}", e).into()), - })?; - - Ok(adjust_nonce(&*self.pool, account, nonce)) - }; - - let res = get_nonce(); - async move { res }.boxed() + async fn nonce(&self, account: AccountId) -> RpcResult { + let api = self.client.runtime_api(); + let best = self.client.info().best_hash; + let at = BlockId::hash(best); + + let nonce = api.account_nonce(&at, account.clone()).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query nonce.", + Some(e.to_string()), + )) + })?; + Ok(adjust_nonce(&*self.pool, account, nonce)) } - fn dry_run( + async fn dry_run( &self, extrinsic: Bytes, at: Option<::Hash>, - ) -> FutureResult { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return async move { Err(err.into()) }.boxed() - } + ) -> RpcResult { + self.deny_unsafe.check_if_safe()?; + let api = self.client.runtime_api(); + let at = BlockId::::hash(at.unwrap_or_else(|| + // If the block hash is not supplied assume the best block. + self.client.info().best_hash)); + + let uxt: ::Extrinsic = + Decode::decode(&mut &*extrinsic).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::DecodeError.into(), + "Unable to dry run extrinsic", + Some(e.to_string()), + )) + })?; - let dry_run = || { - let api = self.client.runtime_api(); - let at = BlockId::::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); - - let uxt: ::Extrinsic = Decode::decode(&mut &*extrinsic) - .map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::DecodeError.into()), - message: "Unable to dry run extrinsic.".into(), - data: Some(format!("{:?}", e).into()), - })?; - - let result = api.apply_extrinsic(&at, uxt).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to dry run extrinsic.".into(), - data: Some(format!("{:?}", e).into()), + let api_version = api + .api_version::>(&at) + .map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to dry run extrinsic.", + Some(e.to_string()), + )) + })? + .ok_or_else(|| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to dry run extrinsic.", + Some(format!("Could not find `BlockBuilder` api for block `{:?}`.", at)), + )) })?; - Ok(Encode::encode(&result).into()) + let result = if api_version < 6 { + #[allow(deprecated)] + api.apply_extrinsic_before_version_6(&at, uxt) + .map(legacy::byte_sized_error::convert_to_latest) + .map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to dry run extrinsic.", + Some(e.to_string()), + )) + })? + } else { + api.apply_extrinsic(&at, uxt).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to dry run extrinsic.", + Some(e.to_string()), + )) + })? }; - let res = dry_run(); - - async move { res }.boxed() - } -} - -/// An implementation of System-specific RPC methods on light client. -pub struct LightSystem { - client: Arc, - remote_blockchain: Arc>, - fetcher: Arc, - pool: Arc

, -} - -impl LightSystem { - /// Create new `LightSystem`. - pub fn new( - client: Arc, - remote_blockchain: Arc>, - fetcher: Arc, - pool: Arc

, - ) -> Self { - LightSystem { client, remote_blockchain, fetcher, pool } - } -} - -impl SystemApi<::Hash, AccountId, Index> - for LightSystem -where - P: TransactionPool + 'static, - C: HeaderBackend, - C: Send + Sync + 'static, - F: Fetcher + 'static, - Block: traits::Block, - AccountId: Clone + std::fmt::Display + Codec + Send + 'static, - Index: Clone + std::fmt::Display + Codec + Send + traits::AtLeast32Bit + 'static, -{ - fn nonce(&self, account: AccountId) -> FutureResult { - let best_hash = self.client.info().best_hash; - let best_id = BlockId::hash(best_hash); - let future_best_header = future_header(&*self.remote_blockchain, &*self.fetcher, best_id); - let fetcher = self.fetcher.clone(); - let call_data = account.encode(); - let future_best_header = future_best_header.and_then(move |maybe_best_header| { - ready( - maybe_best_header - .ok_or_else(|| ClientError::UnknownBlock(format!("{}", best_hash))), - ) - }); - - let future_nonce = future_best_header.and_then(move |best_header| { - fetcher.remote_call(RemoteCallRequest { - block: best_hash, - header: best_header, - method: "AccountNonceApi_account_nonce".into(), - call_data, - retry_count: None, - }) - }); - - let future_nonce = future_nonce.and_then(|nonce| async move { - Index::decode(&mut &nonce[..]) - .map_err(|e| ClientError::CallResultDecode("Cannot decode account nonce", e)) - }); - let future_nonce = future_nonce.map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to query nonce.".into(), - data: Some(format!("{:?}", e).into()), - }); - - let pool = self.pool.clone(); - future_nonce.map_ok(move |nonce| adjust_nonce(&*pool, account, nonce)).boxed() - } - - fn dry_run( - &self, - _extrinsic: Bytes, - _at: Option<::Hash>, - ) -> FutureResult { - async { - Err(RpcError { - code: ErrorCode::MethodNotFound, - message: "Unable to dry run extrinsic.".into(), - data: None, - }) - } - .boxed() + Ok(Encode::encode(&result).into()) } } @@ -278,7 +215,9 @@ where mod tests { use super::*; + use assert_matches::assert_matches; use futures::executor::block_on; + use jsonrpsee::{core::Error as JsonRpseeError, types::error::CallError}; use sc_transaction_pool::BasicPool; use sp_runtime::{ transaction_validity::{InvalidTransaction, TransactionValidityError}, @@ -286,8 +225,8 @@ mod tests { }; use substrate_test_runtime_client::{runtime::Transfer, AccountKeyring}; - #[test] - fn should_return_next_nonce_for_some_account() { + #[tokio::test] + async fn should_return_next_nonce_for_some_account() { sp_tracing::try_init_simple(); // given @@ -312,17 +251,17 @@ mod tests { let ext1 = new_transaction(1); block_on(pool.submit_one(&BlockId::number(0), source, ext1)).unwrap(); - let accounts = FullSystem::new(client, pool, DenyUnsafe::Yes); + let accounts = System::new(client, pool, DenyUnsafe::Yes); // when - let nonce = accounts.nonce(AccountKeyring::Alice.into()); + let nonce = accounts.nonce(AccountKeyring::Alice.into()).await; // then - assert_eq!(block_on(nonce).unwrap(), 2); + assert_eq!(nonce.unwrap(), 2); } - #[test] - fn dry_run_should_deny_unsafe() { + #[tokio::test] + async fn dry_run_should_deny_unsafe() { sp_tracing::try_init_simple(); // given @@ -331,17 +270,17 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = FullSystem::new(client, pool, DenyUnsafe::Yes); + let accounts = System::new(client, pool, DenyUnsafe::Yes); // when - let res = accounts.dry_run(vec![].into(), None); - - // then - assert_eq!(block_on(res), Err(RpcError::method_not_found())); + let res = accounts.dry_run(vec![].into(), None).await; + assert_matches!(res, Err(JsonRpseeError::Call(CallError::Custom(e))) => { + assert!(e.message().contains("RPC call is unsafe to be called externally")); + }); } - #[test] - fn dry_run_should_work() { + #[tokio::test] + async fn dry_run_should_work() { sp_tracing::try_init_simple(); // given @@ -350,7 +289,7 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = FullSystem::new(client, pool, DenyUnsafe::No); + let accounts = System::new(client, pool, DenyUnsafe::No); let tx = Transfer { from: AccountKeyring::Alice.into(), @@ -361,16 +300,15 @@ mod tests { .into_signed_tx(); // when - let res = accounts.dry_run(tx.encode().into(), None); + let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful"); // then - let bytes = block_on(res).unwrap().0; - let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_slice()).unwrap(); + let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_ref()).unwrap(); assert_eq!(apply_res, Ok(Ok(()))); } - #[test] - fn dry_run_should_indicate_error() { + #[tokio::test] + async fn dry_run_should_indicate_error() { sp_tracing::try_init_simple(); // given @@ -379,7 +317,7 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = FullSystem::new(client, pool, DenyUnsafe::No); + let accounts = System::new(client, pool, DenyUnsafe::No); let tx = Transfer { from: AccountKeyring::Alice.into(), @@ -390,11 +328,10 @@ mod tests { .into_signed_tx(); // when - let res = accounts.dry_run(tx.encode().into(), None); + let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful"); // then - let bytes = block_on(res).unwrap().0; - let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_slice()).unwrap(); + let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_ref()).unwrap(); assert_eq!(apply_res, Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))); } } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 5cc5ae6ee58bb..2b095fc9419b9 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -2,29 +2,41 @@ name = "try-runtime-cli" version = "0.10.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Cli command runtime testing and dry-running" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.8" -parity-scale-codec = { version = "2.0.0" } -serde = "1.0.126" -structopt = "0.3.8" - -sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../../client/service" } +clap = { version = "4.0.9", features = ["derive"] } +log = "0.4.17" +parity-scale-codec = "3.0.0" +serde = "1.0.136" +zstd = { version = "0.11.2", default-features = false } +remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities", package = "frame-remote-externalities" } +sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } -sp-state-machine = { version = "0.10.0-dev", path = "../../../../primitives/state-machine" } -sp-runtime = { version = "4.0.0-dev", path = "../../../../primitives/runtime" } -sp-core = { version = "4.0.0-dev", path = "../../../../primitives/core" } -sp-keystore = { version = "0.10.0-dev", path = "../../../../primitives/keystore" } +sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../../client/service" } +sp-core = { version = "7.0.0", path = "../../../../primitives/core" } +sp-externalities = { version = "0.13.0", path = "../../../../primitives/externalities" } +sp-io = { version = "7.0.0", path = "../../../../primitives/io" } +sp-keystore = { version = "0.13.0", path = "../../../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../../../primitives/state-machine" } +sp-version = { version = "5.0.0", path = "../../../../primitives/version" } +sp-weights = { version = "4.0.0", path = "../../../../primitives/weights" } +frame-try-runtime = { optional = true, path = "../../../../frame/try-runtime" } +substrate-rpc-client = { path = "../../rpc/client" } + +[dev-dependencies] +tokio = "1.22.0" -remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } +[features] +try-runtime = [ + "frame-try-runtime/try-runtime", +] diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs new file mode 100644 index 0000000000000..56d88b9cb8919 --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -0,0 +1,220 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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::{ + build_executor, ensure_matching_spec, extract_code, full_extensions, hash_of, local_spec, + state_machine_call_with_proof, SharedParams, State, LOG_TARGET, +}; +use parity_scale_codec::Encode; +use sc_service::{Configuration, NativeExecutionDispatch}; +use sp_core::storage::well_known_keys; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use std::{fmt::Debug, str::FromStr}; +use substrate_rpc_client::{ws_client, ChainApi}; + +/// Configurations of the [`Command::ExecuteBlock`]. +/// +/// This will always call into `TryRuntime_execute_block`, which can optionally skip the state-root +/// check (useful for trying a unreleased runtime), and can execute runtime sanity checks as well. +#[derive(Debug, Clone, clap::Parser)] +pub struct ExecuteBlockCmd { + /// Overwrite the wasm code in state or not. + #[arg(long)] + overwrite_wasm_code: bool, + + /// If set the state root check is disabled. + #[arg(long)] + no_state_root_check: bool, + + /// Which try-state targets to execute when running this command. + /// + /// Expected values: + /// - `all` + /// - `none` + /// - A comma separated list of pallets, as per pallet names in `construct_runtime!()` (e.g. + /// `Staking, System`). + /// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a + /// round-robin fashion. + #[arg(long, default_value = "none")] + try_state: frame_try_runtime::TryStateSelect, + + /// The block hash at which to fetch the block. + /// + /// If the `live` state type is being used, then this can be omitted, and is equal to whatever + /// the `state::at` is. Only use this (with care) when combined with a snapshot. + #[arg( + long, + value_parser = crate::parse::hash + )] + block_at: Option, + + /// The ws uri from which to fetch the block. + /// + /// If the `live` state type is being used, then this can be omitted, and is equal to whatever + /// the `state::uri` is. Only use this (with care) when combined with a snapshot. + #[arg( + long, + value_parser = crate::parse::url + )] + block_ws_uri: Option, + + /// The state type to use. + /// + /// For this command only, if the `live` is used, then state of the parent block is fetched. + /// + /// If `block_at` is provided, then the [`State::Live::at`] is being ignored. + #[command(subcommand)] + state: State, +} + +impl ExecuteBlockCmd { + async fn block_at(&self, ws_uri: String) -> sc_cli::Result + where + Block::Hash: FromStr + serde::de::DeserializeOwned, + ::Err: Debug, + Block::Header: serde::de::DeserializeOwned, + { + let rpc = ws_client(&ws_uri).await?; + + match (&self.block_at, &self.state) { + (Some(block_at), State::Snap { .. }) => hash_of::(block_at), + (Some(block_at), State::Live { .. }) => { + log::warn!(target: LOG_TARGET, "--block-at is provided while state type is live. the `Live::at` will be ignored"); + hash_of::(block_at) + }, + (None, State::Live { at: None, .. }) => { + log::warn!( + target: LOG_TARGET, + "No --block-at or --at provided, using the latest finalized block instead" + ); + ChainApi::<(), Block::Hash, Block::Header, ()>::finalized_head(&rpc) + .await + .map_err(|e| e.to_string().into()) + }, + (None, State::Live { at: Some(at), .. }) => hash_of::(at), + _ => { + panic!("either `--block-at` must be provided, or state must be `live with a proper `--at``"); + }, + } + } + + fn block_ws_uri(&self) -> String + where + Block::Hash: FromStr, + ::Err: Debug, + { + match (&self.block_ws_uri, &self.state) { + (Some(block_ws_uri), State::Snap { .. }) => block_ws_uri.to_owned(), + (Some(block_ws_uri), State::Live { .. }) => { + log::error!(target: LOG_TARGET, "--block-uri is provided while state type is live, Are you sure you know what you are doing?"); + block_ws_uri.to_owned() + }, + (None, State::Live { uri, .. }) => uri.clone(), + (None, State::Snap { .. }) => { + panic!("either `--block-uri` must be provided, or state must be `live`"); + }, + } + } +} + +pub(crate) async fn execute_block( + shared: SharedParams, + command: ExecuteBlockCmd, + config: Configuration, +) -> sc_cli::Result<()> +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Hash: FromStr, + ::Err: Debug, + Block::Hash: serde::de::DeserializeOwned, + Block::Header: serde::de::DeserializeOwned, + NumberFor: FromStr, + as FromStr>::Err: Debug, + ExecDispatch: NativeExecutionDispatch + 'static, +{ + let executor = build_executor::(&shared, &config); + let execution = shared.execution; + + let block_ws_uri = command.block_ws_uri::(); + let block_at = command.block_at::(block_ws_uri.clone()).await?; + let rpc = ws_client(&block_ws_uri).await?; + let block: Block = ChainApi::<(), Block::Hash, Block::Header, _>::block(&rpc, Some(block_at)) + .await + .unwrap() + .unwrap(); + let parent_hash = block.header().parent_hash(); + log::info!( + target: LOG_TARGET, + "fetched block #{:?} from {:?}, parent_hash to fetch the state {:?}", + block.header().number(), + block_ws_uri, + parent_hash + ); + + let ext = { + let builder = command + .state + .builder::()? + // make sure the state is being build with the parent hash, if it is online. + .overwrite_online_at(parent_hash.to_owned()) + .state_version(shared.state_version); + + let builder = if command.overwrite_wasm_code { + log::info!( + target: LOG_TARGET, + "replacing the in-storage :code: with the local code from {}'s chain_spec (your local repo)", + config.chain_spec.name(), + ); + let (code_key, code) = extract_code(&config.chain_spec)?; + builder.inject_hashed_key_value(&[(code_key, code)]) + } else { + builder.inject_hashed_key(well_known_keys::CODE) + }; + + builder.build().await? + }; + + // A digest item gets added when the runtime is processing the block, so we need to pop + // the last one to be consistent with what a gossiped block would contain. + let (mut header, extrinsics) = block.deconstruct(); + header.digest_mut().pop(); + let block = Block::new(header, extrinsics); + let payload = (block.clone(), !command.no_state_root_check, command.try_state).encode(); + + let (expected_spec_name, expected_spec_version, _) = + local_spec::(&ext, &executor); + ensure_matching_spec::( + block_ws_uri.clone(), + expected_spec_name, + expected_spec_version, + shared.no_spec_check_panic, + ) + .await; + + let _ = state_machine_call_with_proof::( + &ext, + &executor, + execution, + "TryRuntime_execute_block", + &payload, + full_extensions(), + )?; + + log::info!(target: LOG_TARGET, "Core_execute_block executed without errors."); + + Ok(()) +} diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs new file mode 100644 index 0000000000000..1cc371c8f22fd --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -0,0 +1,197 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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::{ + build_executor, ensure_matching_spec, extract_code, full_extensions, local_spec, parse, + state_machine_call_with_proof, SharedParams, LOG_TARGET, +}; +use parity_scale_codec::{Decode, Encode}; +use remote_externalities::{Builder, Mode, OnlineConfig}; +use sc_executor::NativeExecutionDispatch; +use sc_service::Configuration; +use serde::{de::DeserializeOwned, Serialize}; +use sp_core::H256; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use std::{fmt::Debug, str::FromStr}; +use substrate_rpc_client::{ws_client, ChainApi, FinalizedHeaders, Subscription, WsClient}; + +const SUB: &str = "chain_subscribeFinalizedHeads"; +const UN_SUB: &str = "chain_unsubscribeFinalizedHeads"; + +/// Configurations of the [`Command::FollowChain`]. +#[derive(Debug, Clone, clap::Parser)] +pub struct FollowChainCmd { + /// The url to connect to. + #[arg(short, long, value_parser = parse::url)] + uri: String, + + /// If set, then the state root check is enabled. + #[arg(long)] + state_root_check: bool, + + /// Which try-state targets to execute when running this command. + /// + /// Expected values: + /// - `all` + /// - `none` + /// - A comma separated list of pallets, as per pallet names in `construct_runtime!()` (e.g. + /// `Staking, System`). + /// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a + /// round-robin fashion. + #[arg(long, default_value = "none")] + try_state: frame_try_runtime::TryStateSelect, + + /// If present, a single connection to a node will be kept and reused for fetching blocks. + #[arg(long)] + keep_connection: bool, +} + +/// Start listening for with `SUB` at `url`. +/// +/// Returns a pair `(client, subscription)` - `subscription` alone will be useless, because it +/// relies on the related alive `client`. +async fn start_subscribing( + url: &str, +) -> sc_cli::Result<(WsClient, Subscription

)> { + let client = ws_client(url).await.map_err(|e| sc_cli::Error::Application(e.into()))?; + + log::info!(target: LOG_TARGET, "subscribing to {:?} / {:?}", SUB, UN_SUB); + + let sub = ChainApi::<(), (), Header, ()>::subscribe_finalized_heads(&client) + .await + .map_err(|e| sc_cli::Error::Application(e.into()))?; + Ok((client, sub)) +} + +pub(crate) async fn follow_chain( + shared: SharedParams, + command: FollowChainCmd, + config: Configuration, +) -> sc_cli::Result<()> +where + Block: BlockT + DeserializeOwned, + Block::Hash: FromStr, + Block::Header: DeserializeOwned, + ::Err: Debug, + NumberFor: FromStr, + as FromStr>::Err: Debug, + ExecDispatch: NativeExecutionDispatch + 'static, +{ + let mut maybe_state_ext = None; + let (rpc, subscription) = start_subscribing::(&command.uri).await?; + + let (code_key, code) = extract_code(&config.chain_spec)?; + let executor = build_executor::(&shared, &config); + let execution = shared.execution; + + let mut finalized_headers: FinalizedHeaders = + FinalizedHeaders::new(&rpc, subscription); + + while let Some(header) = finalized_headers.next().await { + let hash = header.hash(); + let number = header.number(); + + let block: Block = ChainApi::<(), Block::Hash, Block::Header, _>::block(&rpc, Some(hash)) + .await + .unwrap() + .unwrap(); + + log::debug!( + target: LOG_TARGET, + "new block event: {:?} => {:?}, extrinsics: {}", + hash, + number, + block.extrinsics().len() + ); + + // create an ext at the state of this block, whatever is the first subscription event. + if maybe_state_ext.is_none() { + let builder = Builder::::new() + .mode(Mode::Online(OnlineConfig { + transport: command.uri.clone().into(), + at: Some(*header.parent_hash()), + ..Default::default() + })) + .state_version(shared.state_version); + + let new_ext = builder + .inject_hashed_key_value(&[(code_key.clone(), code.clone())]) + .build() + .await?; + log::info!( + target: LOG_TARGET, + "initialized state externalities at {:?}, storage root {:?}", + number, + new_ext.as_backend().root() + ); + + let (expected_spec_name, expected_spec_version, spec_state_version) = + local_spec::(&new_ext, &executor); + ensure_matching_spec::( + command.uri.clone(), + expected_spec_name, + expected_spec_version, + shared.no_spec_check_panic, + ) + .await; + + maybe_state_ext = Some((new_ext, spec_state_version)); + } + + let (state_ext, spec_state_version) = + maybe_state_ext.as_mut().expect("state_ext either existed or was just created"); + + let (mut changes, encoded_result) = state_machine_call_with_proof::( + state_ext, + &executor, + execution, + "TryRuntime_execute_block", + (block, command.state_root_check, command.try_state.clone()).encode().as_ref(), + full_extensions(), + )?; + + let consumed_weight = ::decode(&mut &*encoded_result) + .map_err(|e| format!("failed to decode weight: {:?}", e))?; + + let storage_changes = changes + .drain_storage_changes( + &state_ext.backend, + &mut Default::default(), + // Note that in case a block contains a runtime upgrade, + // state version could potentially be incorrect here, + // this is very niche and would only result in unaligned + // roots, so this use case is ignored for now. + *spec_state_version, + ) + .unwrap(); + state_ext.backend.apply_transaction( + storage_changes.transaction_storage_root, + storage_changes.transaction, + ); + + log::info!( + target: LOG_TARGET, + "executed block {}, consumed weight {}, new storage root {:?}", + number, + consumed_weight, + state_ext.as_backend().root(), + ); + } + + log::error!(target: LOG_TARGET, "ws subscription must have terminated."); + Ok(()) +} diff --git a/utils/frame/try-runtime/cli/src/commands/mod.rs b/utils/frame/try-runtime/cli/src/commands/mod.rs new file mode 100644 index 0000000000000..4861d94f077ce --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/mod.rs @@ -0,0 +1,21 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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(crate) mod execute_block; +pub(crate) mod follow_chain; +pub(crate) mod offchain_worker; +pub(crate) mod on_runtime_upgrade; diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs new file mode 100644 index 0000000000000..8d2585372b4a8 --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -0,0 +1,172 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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::{ + build_executor, ensure_matching_spec, extract_code, full_extensions, hash_of, local_spec, + parse, state_machine_call, SharedParams, State, LOG_TARGET, +}; +use parity_scale_codec::Encode; +use sc_executor::NativeExecutionDispatch; +use sc_service::Configuration; +use sp_core::storage::well_known_keys; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; +use std::{fmt::Debug, str::FromStr}; +use substrate_rpc_client::{ws_client, ChainApi}; + +/// Configurations of the [`Command::OffchainWorker`]. +#[derive(Debug, Clone, clap::Parser)] +pub struct OffchainWorkerCmd { + /// Overwrite the wasm code in state or not. + #[arg(long)] + overwrite_wasm_code: bool, + + /// The block hash at which to fetch the header. + /// + /// If the `live` state type is being used, then this can be omitted, and is equal to whatever + /// the `state::at` is. Only use this (with care) when combined with a snapshot. + #[arg( + long, + value_parser = parse::hash + )] + header_at: Option, + + /// The ws uri from which to fetch the header. + /// + /// If the `live` state type is being used, then this can be omitted, and is equal to whatever + /// the `state::uri` is. Only use this (with care) when combined with a snapshot. + #[arg( + long, + value_parser = parse::url + )] + header_ws_uri: Option, + + /// The state type to use. + #[command(subcommand)] + pub state: State, +} + +impl OffchainWorkerCmd { + fn header_at(&self) -> sc_cli::Result + where + Block::Hash: FromStr, + ::Err: Debug, + { + match (&self.header_at, &self.state) { + (Some(header_at), State::Snap { .. }) => hash_of::(header_at), + (Some(header_at), State::Live { .. }) => { + log::error!(target: LOG_TARGET, "--header-at is provided while state type is live, this will most likely lead to a nonsensical result."); + hash_of::(header_at) + }, + (None, State::Live { at: Some(at), .. }) => hash_of::(at), + _ => { + panic!("either `--header-at` must be provided, or state must be `live` with a proper `--at`"); + }, + } + } + + fn header_ws_uri(&self) -> String + where + Block::Hash: FromStr, + ::Err: Debug, + { + match (&self.header_ws_uri, &self.state) { + (Some(header_ws_uri), State::Snap { .. }) => header_ws_uri.to_owned(), + (Some(header_ws_uri), State::Live { .. }) => { + log::error!(target: LOG_TARGET, "--header-uri is provided while state type is live, this will most likely lead to a nonsensical result."); + header_ws_uri.to_owned() + }, + (None, State::Live { uri, .. }) => uri.clone(), + (None, State::Snap { .. }) => { + panic!("either `--header-uri` must be provided, or state must be `live`"); + }, + } + } +} + +pub(crate) async fn offchain_worker( + shared: SharedParams, + command: OffchainWorkerCmd, + config: Configuration, +) -> sc_cli::Result<()> +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Hash: FromStr, + Block::Header: serde::de::DeserializeOwned, + ::Err: Debug, + NumberFor: FromStr, + as FromStr>::Err: Debug, + ExecDispatch: NativeExecutionDispatch + 'static, +{ + let executor = build_executor(&shared, &config); + let execution = shared.execution; + + let header_at = command.header_at::()?; + let header_ws_uri = command.header_ws_uri::(); + + let rpc = ws_client(&header_ws_uri).await?; + let header = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, Some(header_at)) + .await + .unwrap() + .unwrap(); + log::info!( + target: LOG_TARGET, + "fetched header from {:?}, block number: {:?}", + header_ws_uri, + header.number() + ); + + let ext = { + let builder = command.state.builder::()?.state_version(shared.state_version); + + let builder = if command.overwrite_wasm_code { + log::info!( + target: LOG_TARGET, + "replacing the in-storage :code: with the local code from {}'s chain_spec (your local repo)", + config.chain_spec.name(), + ); + let (code_key, code) = extract_code(&config.chain_spec)?; + builder.inject_hashed_key_value(&[(code_key, code)]) + } else { + builder.inject_hashed_key(well_known_keys::CODE) + }; + + builder.build().await? + }; + + let (expected_spec_name, expected_spec_version, _) = + local_spec::(&ext, &executor); + ensure_matching_spec::( + header_ws_uri, + expected_spec_name, + expected_spec_version, + shared.no_spec_check_panic, + ) + .await; + + let _ = state_machine_call::( + &ext, + &executor, + execution, + "OffchainWorkerApi_offchain_worker", + header.encode().as_ref(), + full_extensions(), + )?; + + log::info!(target: LOG_TARGET, "OffchainWorkerApi_offchain_worker executed without errors."); + + Ok(()) +} diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs new file mode 100644 index 0000000000000..fba34ddfb5060 --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 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 std::{fmt::Debug, str::FromStr}; + +use parity_scale_codec::Decode; +use sc_executor::NativeExecutionDispatch; +use sc_service::Configuration; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_weights::Weight; + +use crate::{ + build_executor, ensure_matching_spec, extract_code, local_spec, state_machine_call_with_proof, + SharedParams, State, LOG_TARGET, +}; + +/// Configurations of the [`Command::OnRuntimeUpgrade`]. +#[derive(Debug, Clone, clap::Parser)] +pub struct OnRuntimeUpgradeCmd { + /// The state type to use. + #[command(subcommand)] + pub state: State, +} + +pub(crate) async fn on_runtime_upgrade( + shared: SharedParams, + command: OnRuntimeUpgradeCmd, + config: Configuration, +) -> sc_cli::Result<()> +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Hash: FromStr, + ::Err: Debug, + Block::Header: serde::de::DeserializeOwned, + NumberFor: FromStr, + as FromStr>::Err: Debug, + ExecDispatch: NativeExecutionDispatch + 'static, +{ + let executor = build_executor(&shared, &config); + let execution = shared.execution; + + let ext = { + let builder = command.state.builder::()?.state_version(shared.state_version); + let (code_key, code) = extract_code(&config.chain_spec)?; + builder.inject_hashed_key_value(&[(code_key, code)]).build().await? + }; + + if let Some(uri) = command.state.live_uri() { + let (expected_spec_name, expected_spec_version, _) = + local_spec::(&ext, &executor); + ensure_matching_spec::( + uri, + expected_spec_name, + expected_spec_version, + shared.no_spec_check_panic, + ) + .await; + } + + let (_, encoded_result) = state_machine_call_with_proof::( + &ext, + &executor, + execution, + "TryRuntime_on_runtime_upgrade", + &[], + Default::default(), // we don't really need any extensions here. + )?; + + let (weight, total_weight) = <(Weight, Weight) as Decode>::decode(&mut &*encoded_result) + .map_err(|e| format!("failed to decode weight: {:?}", e))?; + log::info!( + target: LOG_TARGET, + "TryRuntime_on_runtime_upgrade executed without errors. Consumed weight = ({} ps, {} byte), total weight = ({} ps, {} byte) ({:.2} %, {:.2} %).", + weight.ref_time(), weight.proof_size(), + total_weight.ref_time(), total_weight.proof_size(), + (weight.ref_time() as f64 / total_weight.ref_time().max(1) as f64) * 100.0, + (weight.proof_size() as f64 / total_weight.proof_size().max(1) as f64) * 100.0, + ); + + Ok(()) +} diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index c92c3959535e9..f54354342bf28 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,434 +15,537 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! `Structopt`-ready structs for `try-runtime`. - -use parity_scale_codec::{Decode, Encode}; -use remote_externalities::{rpc_api, Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig}; +//! # Try-runtime +//! +//! Substrate's ultimate testing framework for the power users. +//! +//! > As the name suggests, `try-runtime` is a detailed testing framework that gives you a lot of +//! control over what is being executed in which environment. It is recommended that user's first +//! familiarize themselves with substrate in depth, particularly the execution model. It is critical +//! to deeply understand how the wasm/native interactions, and the runtime apis work in the +//! substrate runtime, before commencing to working with `try-runtime`. +//! +//! #### Resources +//! +//! Some resources about the above: +//! +//! 1. +//! 2. +//! 3. +//! +//! --- +//! +//! ## Overview +//! +//! The basis of all try-runtime commands is the same: connect to a live node, scrape its *state* +//! and put it inside a `TestExternalities`, then call into a *specific runtime-api* using the given +//! state and some *runtime*. +//! +//! All of the variables in the above statement are made *italic*. Let's look at each of them: +//! +//! 1. **State** is the key-value pairs of data that comprise the canonical information that any +//! blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs +//! related to some pallets). Moreover, some keys are special and are not related to specific +//! pallets, known as [`well_known_keys`] in substrate. The most important of these is the +//! `:CODE:` key, which contains the code used for execution, when wasm execution is chosen. +//! +//! 2. *A runtime-api* call is a call into a function defined in the runtime, *on top of a given +//! state*. Each subcommand of `try-runtime` utilizes a specific *runtime-api*. +//! +//! 3. Finally, the **runtime** is the actual code that is used to execute the aforementioned +//! runtime-api. All substrate based chains always have two runtimes: native and wasm. The +//! decision of which one is chosen is non-trivial. First, let's look at the options: +//! +//! 1. Native: this means that the runtime that is **in your codebase**, aka whatever you see in +//! your editor, is being used. This runtime is easier for diagnostics. We refer to this as +//! the "local runtime". +//! +//! 2. Wasm: this means that whatever is stored in the `:CODE:` key of the state that your +//! scrape is being used. In plain sight, since the entire state (including `:CODE:`) is +//! scraped from a remote chain, you could conclude that the wasm runtime, if used, is always +//! equal to the canonical runtime of the live chain (i.e. NOT the "local runtime"). That's +//! factually true, but then the testing would be quite lame. Typically, with try-runtime, +//! you don't want to execute whatever code is already on the live chain. Instead, you want +//! your local runtime (which typically includes a non-released feature) to be used. This is +//! why try-runtime overwrites the wasm runtime (at `:CODE:`) with the local runtime as well. +//! That being said, this behavior can be controlled in certain subcommands with a special +//! flag (`--overwrite-wasm-code`). +//! +//! The decision of which runtime is eventually used is based on two facts: +//! +//! 1. `--execution` flag. If you specify `wasm`, then it is *always* wasm. If it is `native`, then +//! if and ONLY IF the spec versions match, then the native runtime is used. Else, wasm runtime +//! is used again. +//! 2. `--chain` flag (if present in your cli), which determines *which local runtime*, is selected. +//! This will specify: +//! 1. which native runtime is used, if you select `--execution Native` +//! 2. which wasm runtime is used to replace the `:CODE:`, if try-runtime is instructed to do +//! so. +//! +//! All in all, if the term "local runtime" is used in the rest of this crate's documentation, it +//! means either the native runtime, or the wasm runtime when overwritten inside `:CODE:`. In other +//! words, it means your... well, "local runtime", regardless of wasm or native. +//! +//! //! See [`Command`] for more information about each command's specific customization flags, and +//! assumptions regarding the runtime being used. +//! +//! Finally, To make sure there are no errors regarding this, always run any `try-runtime` command +//! with `executor=trace` logging targets, which will specify which runtime is being used per api +//! call. +//! +//! Furthermore, other relevant log targets are: `try-runtime::cli`, `remote-ext`, and `runtime`. +//! +//! ## Spec name check +//! +//! A common pitfall is that you might be running some test on top of the state of chain `x`, with +//! the runtime of chain `y`. To avoid this all commands do a spec-name check before executing +//! anything by default. This will check the spec name of the remote node your are connected to, +//! with the spec name of your local runtime and ensure that they match. +//! +//! Should you need to disable this on certain occasions, a top level flag of `--no-spec-name-check` +//! can be used. +//! +//! The spec version is also always inspected, but if it is a mismatch, it will only emit a warning. +//! +//! ## Note nodes that operate with `try-runtime` +//! +//! There are a number of flags that need to be preferably set on a running node in order to work +//! well with try-runtime's expensive RPC queries: +//! +//! - set `--rpc-max-payload 1000` to ensure large RPC queries can work. +//! - set `--ws-max-out-buffer-capacity 1000` to ensure the websocket connection can handle large +//! RPC queries. +//! - set `--rpc-cors all` to ensure ws connections can come through. +//! +//! Note that *none* of the try-runtime operations need unsafe RPCs. +//! +//! ## Migration Best Practices +//! +//! One of the main use-cases of try-runtime is using it for testing storage migrations. The +//! following points makes sure you can *effectively* test your migrations with try-runtime. +//! +//! #### Adding pre/post hooks +//! +//! One of the gems that come only in the `try-runtime` feature flag is the `pre_upgrade` and +//! `post_upgrade` hooks for `OnRuntimeUpgrade`. This trait is implemented either inside the +//! pallet, or manually in a runtime, to define a migration. In both cases, these functions can be +//! added, given the right flag: +//! +//! ```ignore +//! +//! #[cfg(feature = try-runtime)] +//! fn pre_upgrade() -> Result, &'static str> {} +//! +//! #[cfg(feature = try-runtime)] +//! fn post_upgrade(state: Vec) -> Result<(), &'static str> {} +//! ``` +//! +//! (The pallet macro syntax will support this simply as a part of `#[pallet::hooks]`). +//! +//! These hooks allow you to execute some code, only within the `on-runtime-upgrade` command, before +//! and after the migration. Moreover, `pre_upgrade` can return a `Vec` that contains arbitrary +//! encoded data (usually some pre-upgrade state) which will be passed to `post_upgrade` after +//! upgrading and used for post checking. +//! +//! #### Logging +//! +//! It is super helpful to make sure your migration code uses logging (always with a `runtime` log +//! target prefix, e.g. `runtime::balance`) and state exactly at which stage it is, and what it is +//! doing. +//! +//! #### Guarding migrations +//! +//! Always make sure that any migration code is guarded either by `StorageVersion`, or by some +//! custom storage item, so that it is NEVER executed twice, even if the code lives in two +//! consecutive runtimes. +//! +//! ## Examples +//! +//! Run the migrations of the local runtime on the state of polkadot, from the polkadot repo where +//! we have `--chain polkadot-dev`, on the latest finalized block's state +//! +//! ```sh +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Native \ +//! --chain polkadot-dev \ +//! on-runtime-upgrade \ +//! live \ +//! --uri wss://rpc.polkadot.io +//! # note that we don't pass any --at, nothing means latest block. +//! ``` +//! +//! Same as previous one, but let's say we want to run this command from the substrate repo, where +//! we don't have a matching spec name/version. +//! +//! ```sh +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Native \ +//! --chain dev \ +//! --no-spec-name-check \ # mind this one! +//! on-runtime-upgrade \ +//! live \ +//! --uri wss://rpc.polkadot.io +//! ``` +//! +//! Same as the previous one, but run it at specific block number's state. This means that this +//! block hash's state shall not yet have been pruned in `rpc.polkadot.io`. +//! +//! ```sh +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Native \ +//! --chain dev \ +//! --no-spec-name-check \ # mind this one! on-runtime-upgrade \ +//! on-runtime-upgrade \ +//! live \ +//! --uri wss://rpc.polkadot.io \ +//! --at +//! ``` +//! +//! Moving to `execute-block` and `offchain-workers`. For these commands, you always needs to +//! specify a block hash. For the rest of these examples, we assume we're in the polkadot repo. +//! +//! First, let's assume you are in a branch that has the same spec name/version as the live polkadot +//! network. +//! +//! ```sh +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Wasm \ +//! --chain polkadot-dev \ +//! --uri wss://rpc.polkadot.io \ +//! execute-block \ +//! live \ +//! --at +//! ``` +//! +//! This is wasm, so it will technically execute the code that lives on the live network. Let's say +//! you want to execute your local runtime. Since you have a matching spec versions, you can simply +//! change `--execution Wasm` to `--execution Native` to achieve this. Your logs of `executor=trace` +//! should show something among the lines of: +//! +//! ```text +//! Request for native execution succeeded (native: polkadot-9900 (parity-polkadot-0.tx7.au0), chain: polkadot-9900 (parity-polkadot-0.tx7.au0)) +//! ``` +//! +//! If you don't have matching spec versions, then are doomed to execute wasm. In this case, you can +//! manually overwrite the wasm code with your local runtime: +//! +//! ```sh +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Wasm \ +//! --chain polkadot-dev \ +//! execute-block \ +//! live \ +//! --uri wss://rpc.polkadot.io \ +//! --at \ +//! --overwrite-wasm-code +//! ``` +//! +//! For all of these blocks, the block with hash `` is being used, and the initial state +//! is the state of the parent hash. This is because by omitting `ExecuteBlockCmd::block_at`, the +//! `--at` is used for both. This should be good enough for 99% of the cases. The only case where +//! you need to specify `block-at` and `block-ws-uri` is with snapshots. Let's say you have a file +//! `snap` and you know it corresponds to the state of the parent block of `X`. Then you'd do: +//! +//! ```sh +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Wasm \ +//! --chain polkadot-dev \ +//! --uri wss://rpc.polkadot.io \ +//! execute-block \ +//! --block-at \ +//! --block-ws-uri wss://rpc.polkadot.io \ +//! --overwrite-wasm-code \ +//! snap \ +//! -s snap \ +//! ``` + +#![cfg(feature = "try-runtime")] + +use parity_scale_codec::Decode; +use remote_externalities::{ + Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig, TestExternalities, +}; use sc_chain_spec::ChainSpec; -use sc_cli::{CliConfiguration, ExecutionStrategy, WasmExecutionMethod}; +use sc_cli::{ + execution_method_from_cli, CliConfiguration, ExecutionStrategy, WasmExecutionMethod, + WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, + DEFAULT_WASM_EXECUTION_METHOD, +}; use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; use sp_core::{ - hashing::twox_128, offchain::{ testing::{TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, storage::{well_known_keys, StorageData, StorageKey}, + testing::TaskExecutor, + traits::TaskExecutorExt, + twox_128, H256, }; +use sp_externalities::Extensions; use sp_keystore::{testing::KeyStore, KeystoreExt}; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; -use sp_state_machine::StateMachine; -use std::{fmt::Debug, path::PathBuf, str::FromStr, sync::Arc}; +use sp_runtime::{ + traits::{Block as BlockT, NumberFor}, + DeserializeOwned, +}; +use sp_state_machine::{OverlayedChanges, StateMachine, TrieBackendBuilder}; +use sp_version::StateVersion; +use std::{fmt::Debug, path::PathBuf, str::FromStr}; +use substrate_rpc_client::{ws_client, StateApi}; -mod parse; +mod commands; +pub(crate) mod parse; +pub(crate) const LOG_TARGET: &str = "try-runtime::cli"; -/// Possible subcommands of `try-runtime`. -#[derive(Debug, Clone, structopt::StructOpt)] +/// Possible commands of `try-runtime`. +#[derive(Debug, Clone, clap::Subcommand)] pub enum Command { - /// Execute "TryRuntime_on_runtime_upgrade" against the given runtime state. - OnRuntimeUpgrade(OnRuntimeUpgradeCmd), - /// Execute "OffchainWorkerApi_offchain_worker" against the given runtime state. - OffchainWorker(OffchainWorkerCmd), - /// Execute "Core_execute_block" using the given block and the runtime state of the parent - /// block. - ExecuteBlock(ExecuteBlockCmd), -} - -#[derive(Debug, Clone, structopt::StructOpt)] -pub struct OnRuntimeUpgradeCmd { - #[structopt(subcommand)] - pub state: State, + /// Execute the migrations of the "local runtime". + /// + /// This uses a custom runtime api call, namely "TryRuntime_on_runtime_upgrade". + /// + /// This always overwrites the wasm code with the local runtime (specified by `--chain`), to + /// ensure the new migrations are being executed. Re-executing already existing migrations is + /// evidently not very exciting. + OnRuntimeUpgrade(commands::on_runtime_upgrade::OnRuntimeUpgradeCmd), + + /// Executes the given block against some state. + /// + /// Unlike [`Command::OnRuntimeUpgrade`], this command needs two inputs: the state, and the + /// block data. Since the state could be cached (see [`State::Snap`]), different flags are + /// provided for both. `--block-at` and `--block-uri`, if provided, are only used for fetching + /// the block. For convenience, these flags can be both emitted, if the [`State::Live`] is + /// being used. + /// + /// Note that by default, this command does not overwrite the code, so in wasm execution, the + /// live chain's code is used. This can be disabled if desired, see + /// `ExecuteBlockCmd::overwrite_wasm_code`. + /// + /// Note that if you do overwrite the wasm code, or generally use the local runtime for this, + /// you might + /// - not be able to decode the block, if the block format has changed. + /// - quite possibly will get a signature verification failure, since the spec and + /// transaction version are part of the signature's payload, and if they differ between + /// your local runtime and the remote counterparts, the signatures cannot be verified. + /// - almost certainly will get a state root mismatch, since, well, you are executing a + /// different state transition function. + /// + /// To make testing slightly more dynamic, you can disable the state root check by enabling + /// `ExecuteBlockCmd::no_check`. If you get signature verification errors, you should manually + /// tweak your local runtime's spec version to fix this. + /// + /// A subtle detail of execute block is that if you want to execute block 100 of a live chain + /// again, you need to scrape the state of block 99. This is already done automatically if you + /// use [`State::Live`], and the parent hash of the target block is used to scrape the state. + /// If [`State::Snap`] is being used, then this needs to be manually taken into consideration. + /// + /// This does not execute the same runtime api as normal block import do, namely + /// `Core_execute_block`. Instead, it uses `TryRuntime_execute_block`, which can optionally + /// skip state-root check (useful for trying a unreleased runtime), and can execute runtime + /// sanity checks as well. + ExecuteBlock(commands::execute_block::ExecuteBlockCmd), + + /// Executes *the offchain worker hooks* of a given block against some state. + /// + /// Similar to [`Command::ExecuteBlock`], this command needs two inputs: the state, and the + /// header data. Likewise, `--header-at` and `--header-uri` can be filled, or omitted if + /// [`State::Live`] is used. + /// + /// Similar to [`Command::ExecuteBlock`], this command does not overwrite the code, so in wasm + /// execution, the live chain's code is used. This can be disabled if desired, see + /// `OffchainWorkerCmd::overwrite_wasm_code`. + /// + /// This executes the same runtime api as normal block import, namely + /// `OffchainWorkerApi_offchain_worker`. + OffchainWorker(commands::offchain_worker::OffchainWorkerCmd), + + /// Follow the given chain's finalized blocks and apply all of its extrinsics. + /// + /// This is essentially repeated calls to [`Command::ExecuteBlock`], whilst the local runtime + /// is always at use, the state root check is disabled, and the state is persisted between + /// executions. + /// + /// This allows the behavior of a new runtime to be inspected over a long period of time, with + /// realistic transactions coming as input. + /// + /// NOTE: this does NOT execute the offchain worker hooks of mirrored blocks. This might be + /// added in the future. + /// + /// This does not support snapshot states, and can only work with a remote chain. Upon first + /// connections, starts listening for finalized block events. Upon first block notification, it + /// initializes the state from the remote node, and starts applying that block, plus all the + /// blocks that follow, to the same growing state. + FollowChain(commands::follow_chain::FollowChainCmd), } -#[derive(Debug, Clone, structopt::StructOpt)] -pub struct OffchainWorkerCmd { - #[structopt(subcommand)] - pub state: State, -} - -#[derive(Debug, Clone, structopt::StructOpt)] -pub struct ExecuteBlockCmd { - #[structopt(subcommand)] - pub state: State, -} - -#[derive(Debug, Clone, structopt::StructOpt)] +/// Shared parameters of the `try-runtime` commands +#[derive(Debug, Clone, clap::Parser)] +#[group(skip)] pub struct SharedParams { - /// The shared parameters + /// Shared parameters of substrate cli. #[allow(missing_docs)] - #[structopt(flatten)] + #[clap(flatten)] pub shared_params: sc_cli::SharedParams, - /// The execution strategy that should be used for benchmarks - #[structopt( - long = "execution", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - default_value = "Wasm", - )] + /// The execution strategy that should be used. + #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true, default_value_t = ExecutionStrategy::Wasm)] pub execution: ExecutionStrategy, - /// Method for executing Wasm runtime code. - #[structopt( + /// Type of wasm execution used. + #[arg( long = "wasm-execution", value_name = "METHOD", - possible_values = &WasmExecutionMethod::variants(), - case_insensitive = true, - default_value = "Compiled" + value_enum, + ignore_case = true, + default_value_t = DEFAULT_WASM_EXECUTION_METHOD, )] pub wasm_method: WasmExecutionMethod, + /// The WASM instantiation method to use. + /// + /// Only has an effect when `wasm-execution` is set to `compiled`. + #[arg( + long = "wasm-instantiation-strategy", + value_name = "STRATEGY", + default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, + value_enum, + )] + pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, + /// The number of 64KB pages to allocate for Wasm execution. Defaults to - /// sc_service::Configuration.default_heap_pages. - #[structopt(long)] + /// [`sc_service::Configuration.default_heap_pages`]. + #[arg(long)] pub heap_pages: Option, - /// The block hash at which to read state. This is required for execute-block, offchain-worker, - /// or any command that used the live subcommand. - #[structopt( - short, - long, - multiple = false, - parse(try_from_str = parse::hash), - required_ifs( - &[("command", "offchain-worker"), ("command", "execute-block"), ("subcommand", "live")] - ) - )] - block_at: String, - - /// Whether or not to overwrite the code from state with the code from - /// the specified chain spec. - #[structopt(long)] - pub overwrite_code: bool, - - /// The url to connect to. - // TODO having this a shared parm is a temporary hack; the url is used just - // to get the header/block. We should try and get that out of state, OR allow - // the user to feed in a header/block via file. - // https://github.com/paritytech/substrate/issues/9027 - #[structopt(short, long, default_value = "ws://localhost:9944", parse(try_from_str = parse::url))] - url: String, -} + /// When enabled, the spec check will not panic, and instead only show a warning. + #[arg(long)] + pub no_spec_check_panic: bool, -impl SharedParams { - /// Get the configured value of `block_at`, interpreted as the hash type of `Block`. - pub fn block_at(&self) -> sc_cli::Result - where - Block: BlockT, - ::Hash: FromStr, - <::Hash as FromStr>::Err: Debug, - { - self.block_at - .parse::<::Hash>() - .map_err(|e| format!("Could not parse block hash: {:?}", e).into()) - } + /// State version that is used by the chain. + #[arg(long, default_value_t = StateVersion::V1, value_parser = parse::state_version)] + pub state_version: StateVersion, } -/// Various commands to try out against runtime state at a specific block. -#[derive(Debug, Clone, structopt::StructOpt)] +/// Our `try-runtime` command. +/// +/// See [`Command`] for more info. +#[derive(Debug, Clone, clap::Parser)] pub struct TryRuntimeCmd { - #[structopt(flatten)] + #[clap(flatten)] pub shared: SharedParams, - #[structopt(subcommand)] + #[command(subcommand)] pub command: Command, } -/// The source of runtime state to try operations against. -#[derive(Debug, Clone, structopt::StructOpt)] +/// The source of runtime *state* to use. +#[derive(Debug, Clone, clap::Subcommand)] pub enum State { - /// Use a state snapshot as the source of runtime state. NOTE: for the offchain-worker and - /// execute-block command this is only partially supported and requires a archive node url. + /// Use a state snapshot as the source of runtime state. + /// + /// This can be crated by passing a value to [`State::Live::snapshot_path`]. Snap { - #[structopt(short, long)] + #[arg(short, long)] snapshot_path: PathBuf, }, /// Use a live chain as the source of runtime state. Live { + /// The url to connect to. + #[arg( + short, + long, + value_parser = parse::url, + )] + uri: String, + + /// The block hash at which to fetch the state. + /// + /// If non provided, then the latest finalized head is used. This is particularly useful + /// for [`Command::OnRuntimeUpgrade`]. + #[arg( + short, + long, + value_parser = parse::hash, + )] + at: Option, + /// An optional state snapshot file to WRITE to. Not written if set to `None`. - #[structopt(short, long)] + #[arg(short, long)] snapshot_path: Option, - /// The modules to scrape. If empty, entire chain state will be scraped. - #[structopt(short, long, require_delimiter = true)] - modules: Option>, + /// A pallet to scrape. Can be provided multiple times. If empty, entire chain state will + /// be scraped. + #[arg(short, long, num_args = 1..)] + pallet: Vec, + + /// Fetch the child-keys as well. + /// + /// Default is `false`, if specific `--pallets` are specified, `true` otherwise. In other + /// words, if you scrape the whole state the child tree data is included out of the box. + /// Otherwise, it must be enabled explicitly using this flag. + #[arg(long)] + child_tree: bool, }, } -async fn on_runtime_upgrade( - shared: SharedParams, - command: OnRuntimeUpgradeCmd, - config: Configuration, -) -> sc_cli::Result<()> -where - Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, - ::Err: Debug, - NumberFor: FromStr, - as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, -{ - let wasm_method = shared.wasm_method; - let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); - - check_spec_name::(shared.url.clone(), config.chain_spec.name().to_string()).await; - - let ext = { - let builder = match command.state { +impl State { + /// Create the [`remote_externalities::Builder`] from self. + pub(crate) fn builder(&self) -> sc_cli::Result> + where + Block::Hash: FromStr, + ::Err: Debug, + { + Ok(match self { State::Snap { snapshot_path } => Builder::::new().mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path), })), - State::Live { snapshot_path, modules } => - Builder::::new().mode(Mode::Online(OnlineConfig { - transport: shared.url.to_owned().into(), - state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), - modules: modules.to_owned().unwrap_or_default(), - at: Some(shared.block_at::()?), - ..Default::default() - })), - }; - - let (code_key, code) = extract_code(config.chain_spec)?; - builder - .inject_key_value(&[(code_key, code)]) - .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()) - .build() - .await? - }; - - let encoded_result = StateMachine::<_, _, NumberFor, _>::new( - &ext.backend, - None, - &mut changes, - &executor, - "TryRuntime_on_runtime_upgrade", - &[], - ext.extensions, - &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, - sp_core::testing::TaskExecutor::new(), - ) - .execute(execution.into()) - .map_err(|e| format!("failed to execute 'TryRuntime_on_runtime_upgrade': {:?}", e))?; - - let (weight, total_weight) = <(u64, u64) as Decode>::decode(&mut &*encoded_result) - .map_err(|e| format!("failed to decode output: {:?}", e))?; - log::info!( - "TryRuntime_on_runtime_upgrade executed without errors. Consumed weight = {}, total weight = {} ({})", - weight, - total_weight, - weight as f64 / total_weight as f64 - ); - - Ok(()) -} - -async fn offchain_worker( - shared: SharedParams, - command: OffchainWorkerCmd, - config: Configuration, -) -> sc_cli::Result<()> -where - Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, - Block::Header: serde::de::DeserializeOwned, - ::Err: Debug, - NumberFor: FromStr, - as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, -{ - let wasm_method = shared.wasm_method; - let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); - - check_spec_name::(shared.url.clone(), config.chain_spec.name().to_string()).await; - - let mode = match command.state { - State::Live { snapshot_path, modules } => { - let at = shared.block_at::()?; - let online_config = OnlineConfig { - transport: shared.url.to_owned().into(), - state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), - modules: modules.to_owned().unwrap_or_default(), - at: Some(at), - ..Default::default() - }; - - Mode::Online(online_config) - }, - State::Snap { snapshot_path } => { - let mode = - Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path) }); - - mode - }, - }; - let builder = Builder::::new() - .mode(mode) - .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()); - let mut ext = if shared.overwrite_code { - let (code_key, code) = extract_code(config.chain_spec)?; - builder.inject_key_value(&[(code_key, code)]).build().await? - } else { - builder.inject_hashed_key(well_known_keys::CODE).build().await? - }; - - let (offchain, _offchain_state) = TestOffchainExt::new(); - let (pool, _pool_state) = TestTransactionPoolExt::new(); - ext.register_extension(OffchainDbExt::new(offchain.clone())); - ext.register_extension(OffchainWorkerExt::new(offchain)); - ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); - ext.register_extension(TransactionPoolExt::new(pool)); - - let header_hash = shared.block_at::()?; - let header = rpc_api::get_header::(shared.url, header_hash).await?; - - let _ = StateMachine::<_, _, NumberFor, _>::new( - &ext.backend, - None, - &mut changes, - &executor, - "OffchainWorkerApi_offchain_worker", - header.encode().as_ref(), - ext.extensions, - &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, - sp_core::testing::TaskExecutor::new(), - ) - .execute(execution.into()) - .map_err(|e| format!("failed to execute 'OffchainWorkerApi_offchain_worker': {:?}", e))?; - - log::info!("OffchainWorkerApi_offchain_worker executed without errors."); - - Ok(()) -} - -async fn execute_block( - shared: SharedParams, - command: ExecuteBlockCmd, - config: Configuration, -) -> sc_cli::Result<()> -where - Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, - ::Err: Debug, - NumberFor: FromStr, - as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, -{ - let wasm_method = shared.wasm_method; - let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); - - let block_hash = shared.block_at::()?; - let block: Block = rpc_api::get_block::(shared.url.clone(), block_hash).await?; - - check_spec_name::(shared.url.clone(), config.chain_spec.name().to_string()).await; - - let mode = match command.state { - State::Snap { snapshot_path } => { - let mode = - Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path) }); - - mode - }, - State::Live { snapshot_path, modules } => { - let parent_hash = block.header().parent_hash(); - - let mode = Mode::Online(OnlineConfig { - transport: shared.url.to_owned().into(), - state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), - modules: modules.to_owned().unwrap_or_default(), - at: Some(parent_hash.to_owned()), - ..Default::default() - }); - - mode - }, - }; - - let ext = { - let builder = Builder::::new() - .mode(mode) - .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()); - let mut ext = if shared.overwrite_code { - let (code_key, code) = extract_code(config.chain_spec)?; - builder.inject_key_value(&[(code_key, code)]).build().await? - } else { - builder.inject_hashed_key(well_known_keys::CODE).build().await? - }; - - // register externality extensions in order to provide host interface for OCW to the - // runtime. - let (offchain, _offchain_state) = TestOffchainExt::new(); - let (pool, _pool_state) = TestTransactionPoolExt::new(); - ext.register_extension(OffchainDbExt::new(offchain.clone())); - ext.register_extension(OffchainWorkerExt::new(offchain)); - ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); - ext.register_extension(TransactionPoolExt::new(pool)); - - ext - }; - - // A digest item gets added when the runtime is processing the block, so we need to pop - // the last one to be consistent with what a gossiped block would contain. - let (mut header, extrinsics) = block.deconstruct(); - header.digest_mut().pop(); - let block = Block::new(header, extrinsics); - - let _encoded_result = StateMachine::<_, _, NumberFor, _>::new( - &ext.backend, - None, - &mut changes, - &executor, - "Core_execute_block", - block.encode().as_ref(), - ext.extensions, - &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, - sp_core::testing::TaskExecutor::new(), - ) - .execute(execution.into()) - .map_err(|e| format!("failed to execute 'Core_execute_block': {:?}", e))?; - debug_assert!(_encoded_result == vec![1]); - - log::info!("Core_execute_block executed without errors."); + State::Live { snapshot_path, pallet, uri, at, child_tree } => { + let at = match at { + Some(at_str) => Some(hash_of::(at_str)?), + None => None, + }; + let mut builder = Builder::::new() + .mode(Mode::Online(OnlineConfig { + transport: uri.to_owned().into(), + state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), + pallets: pallet.clone(), + scrape_children: true, + at, + })) + .inject_hashed_key( + &[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat(), + ); + if *child_tree { + builder = builder.inject_default_child_tree_prefix(); + } + builder + }, + }) + } - Ok(()) + /// Get the uri, if self is `Live`. + pub(crate) fn live_uri(&self) -> Option { + match self { + State::Live { uri, .. } => Some(uri.clone()), + _ => None, + } + } } impl TryRuntimeCmd { pub async fn run(&self, config: Configuration) -> sc_cli::Result<()> where - Block: BlockT + serde::de::DeserializeOwned, - Block::Header: serde::de::DeserializeOwned, + Block: BlockT + DeserializeOwned, + Block::Header: DeserializeOwned, Block::Hash: FromStr, ::Err: Debug, NumberFor: FromStr, @@ -451,13 +554,33 @@ impl TryRuntimeCmd { { match &self.command { Command::OnRuntimeUpgrade(ref cmd) => - on_runtime_upgrade::(self.shared.clone(), cmd.clone(), config) - .await, + commands::on_runtime_upgrade::on_runtime_upgrade::( + self.shared.clone(), + cmd.clone(), + config, + ) + .await, Command::OffchainWorker(cmd) => - offchain_worker::(self.shared.clone(), cmd.clone(), config) - .await, + commands::offchain_worker::offchain_worker::( + self.shared.clone(), + cmd.clone(), + config, + ) + .await, Command::ExecuteBlock(cmd) => - execute_block::(self.shared.clone(), cmd.clone(), config).await, + commands::execute_block::execute_block::( + self.shared.clone(), + cmd.clone(), + config, + ) + .await, + Command::FollowChain(cmd) => + commands::follow_chain::follow_chain::( + self.shared.clone(), + cmd.clone(), + config, + ) + .await, } } } @@ -477,7 +600,7 @@ impl CliConfiguration for TryRuntimeCmd { /// Extract `:code` from the given chain spec and return as `StorageData` along with the /// corresponding `StorageKey`. -fn extract_code(spec: Box) -> sc_cli::Result<(StorageKey, StorageData)> { +pub(crate) fn extract_code(spec: &Box) -> sc_cli::Result<(StorageKey, StorageData)> { let genesis_storage = spec.build_storage()?; let code = StorageData( genesis_storage @@ -491,31 +614,239 @@ fn extract_code(spec: Box) -> sc_cli::Result<(StorageKey, Storage Ok((code_key, code)) } +/// Get the hash type of the generic `Block` from a `hash_str`. +pub(crate) fn hash_of(hash_str: &str) -> sc_cli::Result +where + Block::Hash: FromStr, + ::Err: Debug, +{ + hash_str + .parse::<::Hash>() + .map_err(|e| format!("Could not parse block hash: {:?}", e).into()) +} + /// Check the spec_name of an `ext` /// -/// If the version does not exist, or if it does not match with the given, it emits a warning. -async fn check_spec_name( +/// If the spec names don't match, if `relaxed`, then it emits a warning, else it panics. +/// If the spec versions don't match, it only ever emits a warning. +pub(crate) async fn ensure_matching_spec( uri: String, expected_spec_name: String, + expected_spec_version: u32, + relaxed: bool, ) { - let expected_spec_name = expected_spec_name.to_lowercase(); - match remote_externalities::rpc_api::get_runtime_version::(uri.clone(), None) + let rpc = ws_client(&uri).await.unwrap(); + match StateApi::::runtime_version(&rpc, None) .await - .map(|version| String::from(version.spec_name.clone())) - .map(|spec_name| spec_name.to_lowercase()) + .map(|version| (String::from(version.spec_name.clone()), version.spec_version)) + .map(|(spec_name, spec_version)| (spec_name.to_lowercase(), spec_version)) { - Ok(spec) if spec == expected_spec_name => { - log::debug!("found matching spec name: {:?}", spec); - }, - Ok(spec) => { - log::warn!( - "version mismatch: remote spec name: '{}', expected (local chain spec, aka. `--chain`): '{}'", - spec, - expected_spec_name, - ); + Ok((name, version)) => { + // first, deal with spec name + if expected_spec_name.to_lowercase() == name { + log::info!(target: LOG_TARGET, "found matching spec name: {:?}", name); + } else { + let msg = format!( + "version mismatch: remote spec name: '{}', expected (local chain spec, aka. `--chain`): '{}'", + name, + expected_spec_name + ); + if relaxed { + log::warn!(target: LOG_TARGET, "{}", msg); + } else { + panic!("{}", msg); + } + } + + if expected_spec_version == version { + log::info!(target: LOG_TARGET, "found matching spec version: {:?}", version); + } else { + let msg = format!( + "spec version mismatch (local {} != remote {}). This could cause some issues.", + expected_spec_version, version + ); + if relaxed { + log::warn!(target: LOG_TARGET, "{}", msg); + } else { + panic!("{}", msg); + } + } }, Err(why) => { - log::error!("failed to fetch runtime version from {}: {:?}", uri, why); + let msg = format!( + "failed to fetch runtime version from {}: {:?}. Skipping the check", + uri, why + ); + if relaxed { + log::error!(target: LOG_TARGET, "{}", msg); + } else { + panic!("{}", msg); + } }, } } + +/// Build all extensions that we typically use. +pub(crate) fn full_extensions() -> Extensions { + let mut extensions = Extensions::default(); + extensions.register(TaskExecutorExt::new(TaskExecutor::new())); + let (offchain, _offchain_state) = TestOffchainExt::new(); + let (pool, _pool_state) = TestTransactionPoolExt::new(); + extensions.register(OffchainDbExt::new(offchain.clone())); + extensions.register(OffchainWorkerExt::new(offchain)); + extensions.register(KeystoreExt(std::sync::Arc::new(KeyStore::new()))); + extensions.register(TransactionPoolExt::new(pool)); + + extensions +} + +/// Build a default execution that we typically use. +pub(crate) fn build_executor( + shared: &SharedParams, + config: &sc_service::Configuration, +) -> NativeElseWasmExecutor { + let heap_pages = shared.heap_pages.or(config.default_heap_pages); + let max_runtime_instances = config.max_runtime_instances; + let runtime_cache_size = config.runtime_cache_size; + + NativeElseWasmExecutor::::new( + execution_method_from_cli(shared.wasm_method, shared.wasmtime_instantiation_strategy), + heap_pages, + max_runtime_instances, + runtime_cache_size, + ) +} + +/// Execute the given `method` and `data` on top of `ext`, returning the results (encoded) and the +/// state `changes`. +pub(crate) fn state_machine_call( + ext: &TestExternalities, + executor: &NativeElseWasmExecutor, + execution: sc_cli::ExecutionStrategy, + method: &'static str, + data: &[u8], + extensions: Extensions, +) -> sc_cli::Result<(OverlayedChanges, Vec)> { + let mut changes = Default::default(); + let encoded_results = StateMachine::new( + &ext.backend, + &mut changes, + executor, + method, + data, + extensions, + &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, + sp_core::testing::TaskExecutor::new(), + ) + .execute(execution.into()) + .map_err(|e| format!("failed to execute '{}': {}", method, e)) + .map_err::(Into::into)?; + + Ok((changes, encoded_results)) +} + +/// Same as [`state_machine_call`], but it also computes and prints the storage proof in different +/// size and formats. +/// +/// Make sure [`LOG_TARGET`] is enabled in logging. +pub(crate) fn state_machine_call_with_proof( + ext: &TestExternalities, + executor: &NativeElseWasmExecutor, + execution: sc_cli::ExecutionStrategy, + method: &'static str, + data: &[u8], + extensions: Extensions, +) -> sc_cli::Result<(OverlayedChanges, Vec)> { + use parity_scale_codec::Encode; + use sp_core::hexdisplay::HexDisplay; + + let mut changes = Default::default(); + let backend = ext.backend.clone(); + let runtime_code_backend = sp_state_machine::backend::BackendRuntimeCode::new(&backend); + + let proving_backend = + TrieBackendBuilder::wrap(&backend).with_recorder(Default::default()).build(); + + let runtime_code = runtime_code_backend.runtime_code()?; + + let pre_root = *backend.root(); + + let encoded_results = StateMachine::new( + &proving_backend, + &mut changes, + executor, + method, + data, + extensions, + &runtime_code, + sp_core::testing::TaskExecutor::new(), + ) + .execute(execution.into()) + .map_err(|e| format!("failed to execute {}: {}", method, e)) + .map_err::(Into::into)?; + + let proof = proving_backend + .extract_proof() + .expect("A recorder was set and thus, a storage proof can be extracted; qed"); + let proof_size = proof.encoded_size(); + let compact_proof = proof + .clone() + .into_compact_proof::(pre_root) + .map_err(|e| format!("failed to generate compact proof {}: {:?}", method, e))?; + + let compact_proof_size = compact_proof.encoded_size(); + let compressed_proof = zstd::stream::encode_all(&compact_proof.encode()[..], 0) + .map_err(|e| format!("failed to generate compact proof {}: {:?}", method, e))?; + + let proof_nodes = proof.into_nodes(); + + let humanize = |s| { + if s < 1024 * 1024 { + format!("{:.2} KB ({} bytes)", s as f64 / 1024f64, s) + } else { + format!( + "{:.2} MB ({} KB) ({} bytes)", + s as f64 / (1024f64 * 1024f64), + s as f64 / 1024f64, + s + ) + } + }; + log::debug!( + target: LOG_TARGET, + "proof: {} / {} nodes", + HexDisplay::from(&proof_nodes.iter().flatten().cloned().collect::>()), + proof_nodes.len() + ); + log::debug!(target: LOG_TARGET, "proof size: {}", humanize(proof_size)); + log::debug!(target: LOG_TARGET, "compact proof size: {}", humanize(compact_proof_size),); + log::debug!( + target: LOG_TARGET, + "zstd-compressed compact proof {}", + humanize(compressed_proof.len()), + ); + Ok((changes, encoded_results)) +} + +/// Get the spec `(name, version)` from the local runtime. +pub(crate) fn local_spec( + ext: &TestExternalities, + executor: &NativeElseWasmExecutor, +) -> (String, u32, sp_core::storage::StateVersion) { + let (_, encoded) = state_machine_call::( + ext, + executor, + sc_cli::ExecutionStrategy::NativeElseWasm, + "Core_version", + &[], + Default::default(), + ) + .expect("all runtimes should have version; qed"); + ::decode(&mut &*encoded) + .map_err(|e| format!("failed to decode output: {:?}", e)) + .map(|v| { + let state_version = v.state_version(); + (v.spec_name.into(), v.spec_version, state_version) + }) + .expect("all runtimes should have version; qed") +} diff --git a/utils/frame/try-runtime/cli/src/parse.rs b/utils/frame/try-runtime/cli/src/parse.rs index 7f205fbacd310..257a99566979f 100644 --- a/utils/frame/try-runtime/cli/src/parse.rs +++ b/utils/frame/try-runtime/cli/src/parse.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,9 +17,14 @@ //! Utils for parsing user input +use sp_version::StateVersion; + pub(crate) fn hash(block_hash: &str) -> Result { - let (block_hash, offset) = - if block_hash.starts_with("0x") { (&block_hash[2..], 2) } else { (block_hash, 0) }; + let (block_hash, offset) = if let Some(block_hash) = block_hash.strip_prefix("0x") { + (block_hash, 2) + } else { + (block_hash, 0) + }; if let Some(pos) = block_hash.chars().position(|c| !c.is_ascii_hexdigit()) { Err(format!( @@ -39,3 +44,10 @@ pub(crate) fn url(s: &str) -> Result { Err("not a valid WS(S) url: must start with 'ws://' or 'wss://'") } } + +pub(crate) fn state_version(s: &str) -> Result { + s.parse::() + .map_err(|_| ()) + .and_then(StateVersion::try_from) + .map_err(|_| "Invalid state version.") +} diff --git a/utils/prometheus/Cargo.toml b/utils/prometheus/Cargo.toml index 4a6cec2cac774..1371fe6f408c0 100644 --- a/utils/prometheus/Cargo.toml +++ b/utils/prometheus/Cargo.toml @@ -1,11 +1,11 @@ [package] description = "Endpoint to expose Prometheus metrics" name = "substrate-prometheus-endpoint" -version = "0.9.0" +version = "0.10.0-dev" license = "Apache-2.0" authors = ["Parity Technologies "] -edition = "2018" -homepage = "https://substrate.dev" +edition = "2021" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" @@ -13,14 +13,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.8" -prometheus = { version = "0.11.0", default-features = false } -futures-util = { version = "0.3.17", default-features = false, features = ["io"] } -derive_more = "0.99" -async-std = { version = "1.6.5", features = ["unstable"] } -tokio = "1.10" -hyper = { version = "0.14.11", default-features = false, features = ["http1", "server", "tcp"] } +futures-util = { version = "0.3.19", default-features = false, features = ["io"] } +hyper = { version = "0.14.16", default-features = false, features = ["http1", "server", "tcp"] } +log = "0.4.17" +prometheus = { version = "0.13.0", default-features = false } +thiserror = "1.0" +tokio = { version = "1.22.0", features = ["parking_lot"] } [dev-dependencies] -hyper = { version = "0.14.11", features = ["client"] } -tokio = { version = "1.10", features = ["rt-multi-thread"] } +hyper = { version = "0.14.16", features = ["client"] } +tokio = { version = "1.22.0", features = ["rt-multi-thread"] } diff --git a/utils/prometheus/src/lib.rs b/utils/prometheus/src/lib.rs index f81b82cb17646..3ea9d45d48b11 100644 --- a/utils/prometheus/src/lib.rs +++ b/utils/prometheus/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use futures_util::future::Future; use hyper::{ http::StatusCode, server::Server, @@ -34,7 +33,6 @@ pub use prometheus::{ use prometheus::{core::Collector, Encoder, TextEncoder}; use std::net::SocketAddr; -mod networking; mod sourced; pub use sourced::{MetricSource, SourcedCounter, SourcedGauge, SourcedMetric}; @@ -47,27 +45,22 @@ pub fn register( Ok(metric) } -#[derive(Debug, derive_more::Display, derive_more::From)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// Hyper internal error. - Hyper(hyper::Error), + #[error(transparent)] + Hyper(#[from] hyper::Error), + /// Http request error. - Http(hyper::http::Error), + #[error(transparent)] + Http(#[from] hyper::http::Error), + /// i/o error. - Io(std::io::Error), - #[display(fmt = "Prometheus port {} already in use.", _0)] - PortInUse(SocketAddr), -} + #[error(transparent)] + Io(#[from] std::io::Error), -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::Hyper(error) => Some(error), - Error::Http(error) => Some(error), - Error::Io(error) => Some(error), - Error::PortInUse(_) => None, - } - } + #[error("Prometheus port {0} already in use.")] + PortInUse(SocketAddr), } async fn request_metrics(req: Request, registry: Registry) -> Result, Error> { @@ -90,23 +83,10 @@ async fn request_metrics(req: Request, registry: Registry) -> Result hyper::rt::Executor for Executor -where - T: Future + Send + 'static, - T::Output: Send + 'static, -{ - fn execute(&self, future: T) { - async_std::task::spawn(future); - } -} - /// Initializes the metrics context, and starts an HTTP server /// to serve metrics. pub async fn init_prometheus(prometheus_addr: SocketAddr, registry: Registry) -> Result<(), Error> { - let listener = async_std::net::TcpListener::bind(&prometheus_addr) + let listener = tokio::net::TcpListener::bind(&prometheus_addr) .await .map_err(|_| Error::PortInUse(prometheus_addr))?; @@ -115,12 +95,11 @@ pub async fn init_prometheus(prometheus_addr: SocketAddr, registry: Registry) -> /// Init prometheus using the given listener. async fn init_prometheus_with_listener( - listener: async_std::net::TcpListener, + listener: tokio::net::TcpListener, registry: Registry, ) -> Result<(), Error> { - use networking::Incoming; - - log::info!("〽️ Prometheus exporter started at {}", listener.local_addr()?); + let listener = hyper::server::conn::AddrIncoming::from_listener(listener)?; + log::info!("〽️ Prometheus exporter started at {}", listener.local_addr()); let service = make_service_fn(move |_| { let registry = registry.clone(); @@ -132,7 +111,7 @@ async fn init_prometheus_with_listener( } }); - let server = Server::builder(Incoming(listener.incoming())).executor(Executor).serve(service); + let server = Server::builder(listener).serve(service); let result = server.await.map_err(Into::into); @@ -143,7 +122,6 @@ async fn init_prometheus_with_listener( mod tests { use super::*; use hyper::{Client, Uri}; - use std::convert::TryFrom; #[test] fn prometheus_works() { @@ -152,7 +130,7 @@ mod tests { let runtime = tokio::runtime::Runtime::new().expect("Creates the runtime"); let listener = runtime - .block_on(async_std::net::TcpListener::bind("127.0.0.1:0")) + .block_on(tokio::net::TcpListener::bind("127.0.0.1:0")) .expect("Creates listener"); let local_addr = listener.local_addr().expect("Returns the local addr"); diff --git a/utils/prometheus/src/networking.rs b/utils/prometheus/src/networking.rs deleted file mode 100644 index a24216bd23629..0000000000000 --- a/utils/prometheus/src/networking.rs +++ /dev/null @@ -1,71 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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 async_std::pin::Pin; -use futures_util::{ - io::{AsyncRead, AsyncWrite}, - stream::Stream, -}; -use std::task::{Context, Poll}; - -pub struct Incoming<'a>(pub async_std::net::Incoming<'a>); - -impl hyper::server::accept::Accept for Incoming<'_> { - type Conn = TcpStream; - type Error = async_std::io::Error; - - fn poll_accept( - self: Pin<&mut Self>, - cx: &mut Context, - ) -> Poll>> { - Pin::new(&mut Pin::into_inner(self).0) - .poll_next(cx) - .map(|opt| opt.map(|res| res.map(TcpStream))) - } -} - -pub struct TcpStream(pub async_std::net::TcpStream); - -impl tokio::io::AsyncRead for TcpStream { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> Poll> { - Pin::new(&mut Pin::into_inner(self).0) - .poll_read(cx, buf.initialize_unfilled()) - .map_ok(|s| buf.set_filled(s)) - } -} - -impl tokio::io::AsyncWrite for TcpStream { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context, - buf: &[u8], - ) -> Poll> { - Pin::new(&mut Pin::into_inner(self).0).poll_write(cx, buf) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - Pin::new(&mut Pin::into_inner(self).0).poll_flush(cx) - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - Pin::new(&mut Pin::into_inner(self).0).poll_close(cx) - } -} diff --git a/utils/prometheus/src/sourced.rs b/utils/prometheus/src/sourced.rs index ca37eef021f68..9f52d1eff47cd 100644 --- a/utils/prometheus/src/sourced.rs +++ b/utils/prometheus/src/sourced.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/wasm-builder/Cargo.toml b/utils/wasm-builder/Cargo.toml index 721f332e130f0..46c5929969fbb 100644 --- a/utils/wasm-builder/Cargo.toml +++ b/utils/wasm-builder/Cargo.toml @@ -3,21 +3,23 @@ name = "substrate-wasm-builder" version = "5.0.0-dev" authors = ["Parity Technologies "] description = "Utility for building WASM binaries" -edition = "2018" +edition = "2021" readme = "README.md" repository = "https://github.com/paritytech/substrate/" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] +ansi_term = "0.12.1" build-helper = "0.1.1" -cargo_metadata = "0.13.1" +cargo_metadata = "0.14.2" +strum = { version = "0.24.1", features = ["derive"] } tempfile = "3.1.0" toml = "0.5.4" walkdir = "2.3.2" -wasm-gc-api = "0.1.11" -ansi_term = "0.12.1" -sp-maybe-compressed-blob = { version = "4.0.0-dev", path = "../../primitives/maybe-compressed-blob" } +sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/maybe-compressed-blob" } +filetime = "0.2.16" +wasm-opt = "0.110" \ No newline at end of file diff --git a/utils/wasm-builder/README.md b/utils/wasm-builder/README.md index 3868faf1acab5..547468c7ca11d 100644 --- a/utils/wasm-builder/README.md +++ b/utils/wasm-builder/README.md @@ -64,6 +64,7 @@ By using environment variables, you can configure which Wasm binaries are built to be absolute. - `WASM_BUILD_TOOLCHAIN` - The toolchain that should be used to build the Wasm binaries. The format needs to be the same as used by cargo, e.g. `nightly-2020-02-20`. +- `CARGO_NET_OFFLINE` - If `true`, `--offline` will be passed to all processes launched to prevent network access. Useful in offline environments. Each project can be skipped individually by using the environment variable `SKIP_PROJECT_NAME_WASM_BUILD`. Where `PROJECT_NAME` needs to be replaced by the name of the cargo project, e.g. `node-runtime` will diff --git a/utils/wasm-builder/src/builder.rs b/utils/wasm-builder/src/builder.rs index 113b5eb068da4..81a8693968188 100644 --- a/utils/wasm-builder/src/builder.rs +++ b/utils/wasm-builder/src/builder.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/wasm-builder/src/lib.rs b/utils/wasm-builder/src/lib.rs index b13ecc4e4ab34..fc86a06170a50 100644 --- a/utils/wasm-builder/src/lib.rs +++ b/utils/wasm-builder/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -83,10 +83,16 @@ //! needs to be absolute. //! - `WASM_BUILD_TOOLCHAIN` - The toolchain that should be used to build the Wasm binaries. The //! format needs to be the same as used by cargo, e.g. `nightly-2020-02-20`. +//! - `WASM_BUILD_WORKSPACE_HINT` - Hint the workspace that is being built. This is normally not +//! required as we walk up from the target directory until we find a `Cargo.toml`. If the target +//! directory is changed for the build, this environment variable can be used to point to the +//! actual workspace. +//! - `CARGO_NET_OFFLINE` - If `true`, `--offline` will be passed to all processes launched to +//! prevent network access. Useful in offline environments. //! //! Each project can be skipped individually by using the environment variable //! `SKIP_PROJECT_NAME_WASM_BUILD`. Where `PROJECT_NAME` needs to be replaced by the name of the -//! cargo project, e.g. `node-runtime` will be `NODE_RUNTIME`. +//! cargo project, e.g. `kitchensink-runtime` will be `NODE_RUNTIME`. //! //! ## Prerequisites: //! @@ -115,10 +121,15 @@ pub use builder::{WasmBuilder, WasmBuilderSelectProject}; /// Environment variable that tells us to skip building the wasm binary. const SKIP_BUILD_ENV: &str = "SKIP_WASM_BUILD"; +/// Environment variable that tells us whether we should avoid network requests +const OFFLINE: &str = "CARGO_NET_OFFLINE"; + /// Environment variable to force a certain build type when building the wasm binary. -/// Expects "debug" or "release" as value. +/// Expects "debug", "release" or "production" as value. /// -/// By default the WASM binary uses the same build type as the main cargo build. +/// When unset the WASM binary uses the same build type as the main cargo build with +/// the exception of a debug build: In this case the wasm build defaults to `release` in +/// order to avoid a slowdown when not explicitly requested. const WASM_BUILD_TYPE_ENV: &str = "WASM_BUILD_TYPE"; /// Environment variable to extend the `RUSTFLAGS` variable given to the wasm build. @@ -138,6 +149,9 @@ const WASM_BUILD_TOOLCHAIN: &str = "WASM_BUILD_TOOLCHAIN"; /// Environment variable that makes sure the WASM build is triggered. const FORCE_WASM_BUILD_ENV: &str = "FORCE_WASM_BUILD"; +/// Environment variable that hints the workspace we are building. +const WASM_BUILD_WORKSPACE_HINT: &str = "WASM_BUILD_WORKSPACE_HINT"; + /// Write to the given `file` if the `content` is different. fn write_file_if_changed(file: impl AsRef, content: impl AsRef) { if fs::read_to_string(file.as_ref()).ok().as_deref() != Some(content.as_ref()) { diff --git a/utils/wasm-builder/src/prerequisites.rs b/utils/wasm-builder/src/prerequisites.rs index 0dad8b781ae5a..fb04dc3c98fb2 100644 --- a/utils/wasm-builder/src/prerequisites.rs +++ b/utils/wasm-builder/src/prerequisites.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,7 +58,7 @@ fn create_check_toolchain_project(project_dir: &Path) { [package] name = "wasm-test" version = "1.0.0" - edition = "2018" + edition = "2021" build = "build.rs" [lib] @@ -120,6 +120,9 @@ fn check_wasm_toolchain_installed( let manifest_path = temp.path().join("Cargo.toml").display().to_string(); let mut build_cmd = cargo_command.command(); + // Chdir to temp to avoid including project's .cargo/config.toml + // by accident - it can happen in some CI environments. + build_cmd.current_dir(&temp); build_cmd.args(&[ "build", "--target=wasm32-unknown-unknown", @@ -132,12 +135,22 @@ fn check_wasm_toolchain_installed( } let mut run_cmd = cargo_command.command(); + // Chdir to temp to avoid including project's .cargo/config.toml + // by accident - it can happen in some CI environments. + run_cmd.current_dir(&temp); run_cmd.args(&["run", "--manifest-path", &manifest_path]); // Unset the `CARGO_TARGET_DIR` to prevent a cargo deadlock build_cmd.env_remove("CARGO_TARGET_DIR"); run_cmd.env_remove("CARGO_TARGET_DIR"); + // Make sure the host's flags aren't used here, e.g. if an alternative linker is specified + // in the RUSTFLAGS then the check we do here will break unless we clear these. + build_cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); + run_cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); + build_cmd.env_remove("RUSTFLAGS"); + run_cmd.env_remove("RUSTFLAGS"); + build_cmd.output().map_err(|_| err_msg.clone()).and_then(|s| { if s.status.success() { let version = run_cmd.output().ok().and_then(|o| String::from_utf8(o.stdout).ok()); diff --git a/utils/wasm-builder/src/wasm_project.rs b/utils/wasm-builder/src/wasm_project.rs index 868692d341ff0..7688069dd7cca 100644 --- a/utils/wasm-builder/src/wasm_project.rs +++ b/utils/wasm-builder/src/wasm_project.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,8 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{write_file_if_changed, CargoCommandVersioned}; +use crate::{write_file_if_changed, CargoCommandVersioned, OFFLINE}; +use build_helper::rerun_if_changed; +use cargo_metadata::{CargoOpt, Metadata, MetadataCommand}; use std::{ borrow::ToOwned, collections::HashSet, @@ -26,13 +28,8 @@ use std::{ path::{Path, PathBuf}, process, }; - +use strum::{EnumIter, IntoEnumIterator}; use toml::value::Table; - -use build_helper::rerun_if_changed; - -use cargo_metadata::{Metadata, MetadataCommand}; - use walkdir::WalkDir; /// Colorize an info message. @@ -54,6 +51,11 @@ impl WasmBinaryBloaty { pub fn wasm_binary_bloaty_path_escaped(&self) -> String { self.0.display().to_string().escape_default().to_string() } + + /// Returns the path to the wasm binary. + pub fn wasm_binary_bloaty_path(&self) -> &Path { + &self.0 + } } /// Holds the path to the WASM binary. @@ -77,11 +79,21 @@ fn crate_metadata(cargo_manifest: &Path) -> Metadata { let cargo_lock_existed = cargo_lock.exists(); - let crate_metadata = MetadataCommand::new() - .manifest_path(cargo_manifest) + // If we can find a `Cargo.lock`, we assume that this is the workspace root and there exists a + // `Cargo.toml` that we can use for getting the metadata. + let cargo_manifest = if let Some(mut cargo_lock) = find_cargo_lock(cargo_manifest) { + cargo_lock.set_file_name("Cargo.toml"); + cargo_lock + } else { + cargo_manifest.to_path_buf() + }; + + let mut crate_metadata_command = create_metadata_command(cargo_manifest); + crate_metadata_command.features(CargoOpt::AllFeatures); + + let crate_metadata = crate_metadata_command .exec() .expect("`cargo metadata` can not fail on project `Cargo.toml`; qed"); - // If the `Cargo.lock` didn't exist, we need to remove it after // calling `cargo metadata`. This is required to ensure that we don't change // the build directory outside of the `target` folder. Commands like @@ -118,9 +130,9 @@ pub(crate) fn create_and_compile( features_to_enable, ); - build_project(&project, default_rustflags, cargo_cmd); + let profile = build_project(&project, default_rustflags, cargo_cmd); let (wasm_binary, wasm_binary_compressed, bloaty) = - compact_wasm_file(&project, project_cargo_toml, wasm_binary_name); + compact_wasm_file(&project, profile, project_cargo_toml, wasm_binary_name); wasm_binary .as_ref() @@ -130,9 +142,49 @@ pub(crate) fn create_and_compile( copy_wasm_to_target_directory(project_cargo_toml, wasm_binary_compressed) }); - generate_rerun_if_changed_instructions(project_cargo_toml, &project, &wasm_workspace); + let final_wasm_binary = wasm_binary_compressed.or(wasm_binary); + + generate_rerun_if_changed_instructions( + project_cargo_toml, + &project, + &wasm_workspace, + final_wasm_binary.as_ref(), + &bloaty, + ); + + if let Err(err) = adjust_mtime(&bloaty, final_wasm_binary.as_ref()) { + build_helper::warning!("Error while adjusting the mtime of the wasm binaries: {}", err) + } + + (final_wasm_binary, bloaty) +} + +/// Adjust the mtime of the bloaty and compressed/compact wasm files. +/// +/// We add the bloaty and the compressed/compact wasm file to the `rerun-if-changed` files. +/// Cargo/Rustc determines based on the timestamp of the `invoked.timestamp` file that can be found +/// in the `OUT_DIR/..`, if it needs to rerun a `build.rs` script. The problem is that this +/// `invoked.timestamp` is created when the `build.rs` is executed and the wasm binaries are created +/// later. This leads to them having a later mtime than the `invoked.timestamp` file and thus, +/// cargo/rustc always re-executes the `build.rs` script. To hack around this, we copy the mtime of +/// the `invoked.timestamp` to the wasm binaries. +fn adjust_mtime( + bloaty_wasm: &WasmBinaryBloaty, + compressed_or_compact_wasm: Option<&WasmBinary>, +) -> std::io::Result<()> { + let out_dir = build_helper::out_dir(); + let invoked_timestamp = out_dir.join("../invoked.timestamp"); + + // Get the mtime of the `invoked.timestamp` + let metadata = fs::metadata(invoked_timestamp)?; + let mtime = filetime::FileTime::from_last_modification_time(&metadata); + + filetime::set_file_mtime(bloaty_wasm.wasm_binary_bloaty_path(), mtime)?; + if let Some(binary) = compressed_or_compact_wasm.as_ref() { + filetime::set_file_mtime(binary.wasm_binary_path(), mtime)?; + } - (wasm_binary_compressed.or(wasm_binary), bloaty) + Ok(()) } /// Find the `Cargo.lock` relative to the `OUT_DIR` environment variable. @@ -151,18 +203,29 @@ fn find_cargo_lock(cargo_manifest: &Path) -> Option { } } - if let Some(path) = find_impl(build_helper::out_dir()) { - return Some(path) + if let Ok(workspace) = env::var(crate::WASM_BUILD_WORKSPACE_HINT) { + let path = PathBuf::from(workspace); + + if path.join("Cargo.lock").exists() { + return Some(path.join("Cargo.lock")) + } else { + build_helper::warning!( + "`{}` env variable doesn't point to a directory that contains a `Cargo.lock`.", + crate::WASM_BUILD_WORKSPACE_HINT, + ); + } } - if let Some(path) = find_impl(cargo_manifest.to_path_buf()) { + if let Some(path) = find_impl(build_helper::out_dir()) { return Some(path) } build_helper::warning!( - "Could not find `Cargo.lock` for `{}`, while searching from `{}`.", + "Could not find `Cargo.lock` for `{}`, while searching from `{}`. \ + To fix this, point the `{}` env variable to the directory of the workspace being compiled.", cargo_manifest.display(), - build_helper::out_dir().display() + build_helper::out_dir().display(), + crate::WASM_BUILD_WORKSPACE_HINT, ); None @@ -225,16 +288,22 @@ fn create_project_cargo_toml( let mut wasm_workspace_toml = Table::new(); - // Add `profile` with release and dev + // Add different profiles which are selected by setting `WASM_BUILD_TYPE`. let mut release_profile = Table::new(); release_profile.insert("panic".into(), "abort".into()); - release_profile.insert("lto".into(), true.into()); + release_profile.insert("lto".into(), "thin".into()); + + let mut production_profile = Table::new(); + production_profile.insert("inherits".into(), "release".into()); + production_profile.insert("lto".into(), "fat".into()); + production_profile.insert("codegen-units".into(), 1.into()); let mut dev_profile = Table::new(); dev_profile.insert("panic".into(), "abort".into()); let mut profile = Table::new(); profile.insert("release".into(), release_profile.into()); + profile.insert("production".into(), production_profile.into()); profile.insert("dev".into(), dev_profile.into()); wasm_workspace_toml.insert("profile".into(), profile.into()); @@ -266,8 +335,7 @@ fn create_project_cargo_toml( let mut package = Table::new(); package.insert("name".into(), format!("{}-wasm", crate_name).into()); package.insert("version".into(), "1.0.0".into()); - package.insert("edition".into(), "2018".into()); - package.insert("resolver".into(), "2".into()); + package.insert("edition".into(), "2021".into()); wasm_workspace_toml.insert("package".into(), package.into()); @@ -297,34 +365,73 @@ fn create_project_cargo_toml( ); } -/// Find a package by the given `manifest_path` in the metadata. +/// Find a package by the given `manifest_path` in the metadata. In case it can't be found by its +/// manifest_path, fallback to finding it by name; this is necessary during publish because the +/// package's manifest path will be *generated* within a specific packaging directory, thus it won't +/// be found by its original path anymore. /// /// Panics if the package could not be found. fn find_package_by_manifest_path<'a>( + pkg_name: &str, manifest_path: &Path, crate_metadata: &'a cargo_metadata::Metadata, ) -> &'a cargo_metadata::Package { - crate_metadata + if let Some(pkg) = crate_metadata.packages.iter().find(|p| p.manifest_path == manifest_path) { + return pkg + } + let pkgs_by_name = crate_metadata .packages .iter() - .find(|p| p.manifest_path == manifest_path) - .expect("Wasm project exists in its own metadata; qed") + .filter(|p| p.name == pkg_name) + .collect::>(); + let mut pkgs = pkgs_by_name.iter(); + if let Some(pkg) = pkgs.next() { + if pkgs.next().is_some() { + panic!( + "Found multiple packages matching the name {pkg_name} ({manifest_path:?}): {:?}", + pkgs_by_name + ); + } else { + return pkg + } + } else { + panic!("Failed to find entry for package {pkg_name} ({manifest_path:?})"); + } } /// Get a list of enabled features for the project. fn project_enabled_features( + pkg_name: &str, cargo_manifest: &Path, crate_metadata: &cargo_metadata::Metadata, ) -> Vec { - let package = find_package_by_manifest_path(cargo_manifest, crate_metadata); + let package = find_package_by_manifest_path(pkg_name, cargo_manifest, crate_metadata); + + let std_enabled = package.features.get("std"); let mut enabled_features = package .features - .keys() - .filter(|f| { + .iter() + .filter(|(f, v)| { let mut feature_env = f.replace("-", "_"); feature_env.make_ascii_uppercase(); + // If this is a feature that corresponds only to an optional dependency + // and this feature is enabled by the `std` feature, we assume that this + // is only done through the `std` feature. This is a bad heuristic and should + // be removed after namespaced features are landed: + // https://doc.rust-lang.org/cargo/reference/unstable.html#namespaced-features + // Then we can just express this directly in the `Cargo.toml` and do not require + // this heuristic anymore. However, for the transition phase between now and namespaced + // features already being present in nightly, we need this code to make + // runtimes compile with all the possible rustc versions. + if v.len() == 1 && + v.get(0).map_or(false, |v| *v == format!("dep:{}", f)) && + std_enabled.as_ref().map(|e| e.iter().any(|ef| ef == *f)).unwrap_or(false) + { + return false + } + // We don't want to enable the `std`/`default` feature for the wasm build and // we need to check if the feature is enabled by checking the env variable. *f != "std" && @@ -332,7 +439,7 @@ fn project_enabled_features( .map(|v| v == "1") .unwrap_or_default() }) - .cloned() + .map(|d| d.0.clone()) .collect::>(); enabled_features.sort(); @@ -341,10 +448,11 @@ fn project_enabled_features( /// Returns if the project has the `runtime-wasm` feature fn has_runtime_wasm_feature_declared( + pkg_name: &str, cargo_manifest: &Path, crate_metadata: &cargo_metadata::Metadata, ) -> bool { - let package = find_package_by_manifest_path(cargo_manifest, crate_metadata); + let package = find_package_by_manifest_path(pkg_name, cargo_manifest, crate_metadata); package.features.keys().any(|k| k == "runtime-wasm") } @@ -369,9 +477,10 @@ fn create_project( fs::create_dir_all(wasm_project_folder.join("src")) .expect("Wasm project dir create can not fail; qed"); - let mut enabled_features = project_enabled_features(&project_cargo_toml, &crate_metadata); + let mut enabled_features = + project_enabled_features(&crate_name, project_cargo_toml, crate_metadata); - if has_runtime_wasm_feature_declared(project_cargo_toml, crate_metadata) { + if has_runtime_wasm_feature_declared(&crate_name, project_cargo_toml, crate_metadata) { enabled_features.push("runtime-wasm".into()); } @@ -382,7 +491,7 @@ fn create_project( &wasm_project_folder, workspace_root_path, &crate_name, - &crate_path, + crate_path, &wasm_binary, enabled_features.into_iter(), ); @@ -400,25 +509,124 @@ fn create_project( wasm_project_folder } -/// Returns if the project should be built as a release. -fn is_release_build() -> bool { - if let Ok(var) = env::var(crate::WASM_BUILD_TYPE_ENV) { - match var.as_str() { - "release" => true, - "debug" => false, - var => panic!( - "Unexpected value for `{}` env variable: {}\nOne of the following are expected: `debug` or `release`.", - crate::WASM_BUILD_TYPE_ENV, - var, - ), +/// The cargo profile that is used to build the wasm project. +#[derive(Debug, EnumIter)] +enum Profile { + /// The `--profile dev` profile. + Debug, + /// The `--profile release` profile. + Release, + /// The `--profile production` profile. + Production, +} + +impl Profile { + /// Create a profile by detecting which profile is used for the main build. + /// + /// We cannot easily determine the profile that is used by the main cargo invocation + /// because the `PROFILE` environment variable won't contain any custom profiles like + /// "production". It would only contain the builtin profile where the custom profile + /// inherits from. This is why we inspect the build path to learn which profile is used. + /// + /// # Note + /// + /// Can be overriden by setting [`crate::WASM_BUILD_TYPE_ENV`]. + fn detect(wasm_project: &Path) -> Profile { + let (name, overriden) = if let Ok(name) = env::var(crate::WASM_BUILD_TYPE_ENV) { + (name, true) + } else { + // First go backwards to the beginning of the target directory. + // Then go forwards to find the "wbuild" directory. + // We need to go backwards first because when starting from the root there + // might be a chance that someone has a "wbuild" directory somewhere in the path. + let name = wasm_project + .components() + .rev() + .take_while(|c| c.as_os_str() != "target") + .collect::>() + .iter() + .rev() + .take_while(|c| c.as_os_str() != "wbuild") + .last() + .expect("We put the wasm project within a `target/.../wbuild` path; qed") + .as_os_str() + .to_str() + .expect("All our profile directory names are ascii; qed") + .to_string(); + (name, false) + }; + match (Profile::iter().find(|p| p.directory() == name), overriden) { + // When not overriden by a env variable we default to using the `Release` profile + // for the wasm build even when the main build uses the debug build. This + // is because the `Debug` profile is too slow for normal development activities. + (Some(Profile::Debug), false) => Profile::Release, + // For any other profile or when overriden we take it at face value. + (Some(profile), _) => profile, + // For non overriden unknown profiles we fall back to `Release`. + // This allows us to continue building when a custom profile is used for the + // main builds cargo. When explicitly passing a profile via env variable we are + // not doing a fallback. + (None, false) => { + let profile = Profile::Release; + build_helper::warning!( + "Unknown cargo profile `{}`. Defaulted to `{:?}` for the runtime build.", + name, + profile, + ); + profile + }, + // Invalid profile specified. + (None, true) => { + // We use println! + exit instead of a panic in order to have a cleaner output. + println!( + "Unexpected profile name: `{}`. One of the following is expected: {:?}", + name, + Profile::iter().map(|p| p.directory()).collect::>(), + ); + process::exit(1); + }, + } + } + + /// The name of the profile as supplied to the cargo `--profile` cli option. + fn name(&self) -> &'static str { + match self { + Self::Debug => "dev", + Self::Release => "release", + Self::Production => "production", + } + } + + /// The sub directory within `target` where cargo places the build output. + /// + /// # Note + /// + /// Usually this is the same as [`Self::name`] with the exception of the debug + /// profile which is called `dev`. + fn directory(&self) -> &'static str { + match self { + Self::Debug => "debug", + _ => self.name(), } - } else { - true + } + + /// Whether the resulting binary should be compacted and compressed. + fn wants_compact(&self) -> bool { + !matches!(self, Self::Debug) } } +/// Check environment whether we should build without network +fn offline_build() -> bool { + env::var(OFFLINE).map_or(false, |v| v == "true") +} + /// Build the project to create the WASM binary. -fn build_project(project: &Path, default_rustflags: &str, cargo_cmd: CargoCommandVersioned) { +fn build_project( + project: &Path, + default_rustflags: &str, + cargo_cmd: CargoCommandVersioned, +) -> Profile { let manifest_path = project.join("Cargo.toml"); let mut build_cmd = cargo_cmd.command(); @@ -436,6 +644,10 @@ fn build_project(project: &Path, default_rustflags: &str, cargo_cmd: CargoComman // exclusive). The runner project is created in `CARGO_TARGET_DIR` and executing it will // create a sub target directory inside of `CARGO_TARGET_DIR`. .env_remove("CARGO_TARGET_DIR") + // As we are being called inside a build-script, this env variable is set. However, we set + // our own `RUSTFLAGS` and thus, we need to remove this. Otherwise cargo favors this + // env variable. + .env_remove("CARGO_ENCODED_RUSTFLAGS") // We don't want to call ourselves recursively .env(crate::SKIP_BUILD_ENV, ""); @@ -443,16 +655,20 @@ fn build_project(project: &Path, default_rustflags: &str, cargo_cmd: CargoComman build_cmd.arg("--color=always"); } - if is_release_build() { - build_cmd.arg("--release"); - }; + let profile = Profile::detect(project); + build_cmd.arg("--profile"); + build_cmd.arg(profile.name()); + + if offline_build() { + build_cmd.arg("--offline"); + } println!("{}", colorize_info_message("Information that should be included in a bug report.")); println!("{} {:?}", colorize_info_message("Executing build command:"), build_cmd); println!("{} {}", colorize_info_message("Using rustc version:"), cargo_cmd.rustc_version()); match build_cmd.status().map(|s| s.success()) { - Ok(true) => {}, + Ok(true) => profile, // Use `process.exit(1)` to have a clean error output. _ => process::exit(1), } @@ -461,53 +677,41 @@ fn build_project(project: &Path, default_rustflags: &str, cargo_cmd: CargoComman /// Compact the WASM binary using `wasm-gc` and compress it using zstd. fn compact_wasm_file( project: &Path, + profile: Profile, cargo_manifest: &Path, - wasm_binary_name: Option, + out_name: Option, ) -> (Option, Option, WasmBinaryBloaty) { - let is_release_build = is_release_build(); - let target = if is_release_build { "release" } else { "debug" }; - let default_wasm_binary_name = get_wasm_binary_name(cargo_manifest); - let wasm_file = project + let default_out_name = get_wasm_binary_name(cargo_manifest); + let out_name = out_name.unwrap_or_else(|| default_out_name.clone()); + let in_path = project .join("target/wasm32-unknown-unknown") - .join(target) - .join(format!("{}.wasm", default_wasm_binary_name)); - - let wasm_compact_file = if is_release_build { - let wasm_compact_file = project.join(format!( - "{}.compact.wasm", - wasm_binary_name.clone().unwrap_or_else(|| default_wasm_binary_name.clone()), - )); - wasm_gc::garbage_collect_file(&wasm_file, &wasm_compact_file) + .join(profile.directory()) + .join(format!("{}.wasm", default_out_name)); + + let (wasm_compact_path, wasm_compact_compressed_path) = if profile.wants_compact() { + let wasm_compact_path = project.join(format!("{}.compact.wasm", out_name,)); + wasm_opt::OptimizationOptions::new_opt_level_0() + .mvp_features_only() + .debug_info(true) + .add_pass(wasm_opt::Pass::StripDwarf) + .run(&in_path, &wasm_compact_path) .expect("Failed to compact generated WASM binary."); - Some(WasmBinary(wasm_compact_file)) - } else { - None - }; - - let wasm_compact_compressed_file = wasm_compact_file.as_ref().and_then(|compact_binary| { - let file_name = - wasm_binary_name.clone().unwrap_or_else(|| default_wasm_binary_name.clone()); - - let wasm_compact_compressed_file = - project.join(format!("{}.compact.compressed.wasm", file_name)); - if compress_wasm(&compact_binary.0, &wasm_compact_compressed_file) { - Some(WasmBinary(wasm_compact_compressed_file)) + let wasm_compact_compressed_path = + project.join(format!("{}.compact.compressed.wasm", out_name)); + if compress_wasm(&wasm_compact_path, &wasm_compact_compressed_path) { + (Some(WasmBinary(wasm_compact_path)), Some(WasmBinary(wasm_compact_compressed_path))) } else { - None + (Some(WasmBinary(wasm_compact_path)), None) } - }); - - let bloaty_file_name = if let Some(name) = wasm_binary_name { - format!("{}.wasm", name) } else { - format!("{}.wasm", default_wasm_binary_name) + (None, None) }; - let bloaty_file = project.join(bloaty_file_name); - fs::copy(wasm_file, &bloaty_file).expect("Copying the bloaty file to the project dir."); + let bloaty_path = project.join(format!("{}.wasm", out_name)); + fs::copy(in_path, &bloaty_path).expect("Copying the bloaty file to the project dir."); - (wasm_compact_file, wasm_compact_compressed_file, WasmBinaryBloaty(bloaty_file)) + (wasm_compact_path, wasm_compact_compressed_path, WasmBinaryBloaty(bloaty_path)) } fn compress_wasm(wasm_binary_path: &Path, compressed_binary_out_path: &Path) -> bool { @@ -520,8 +724,8 @@ fn compress_wasm(wasm_binary_path: &Path, compressed_binary_out_path: &Path) -> true } else { - println!( - "cargo:warning=Writing uncompressed wasm. Exceeded maximum size {}", + build_helper::warning!( + "Writing uncompressed wasm. Exceeded maximum size {}", CODE_BLOB_BOMB_LIMIT, ); @@ -568,20 +772,31 @@ impl<'a> Deref for DeduplicatePackage<'a> { } } +fn create_metadata_command(path: impl Into) -> MetadataCommand { + let mut metadata_command = MetadataCommand::new(); + metadata_command.manifest_path(path); + + if offline_build() { + metadata_command.other_options(vec!["--offline".to_owned()]); + } + metadata_command +} + /// Generate the `rerun-if-changed` instructions for cargo to make sure that the WASM binary is /// rebuilt when needed. fn generate_rerun_if_changed_instructions( cargo_manifest: &Path, project_folder: &Path, wasm_workspace: &Path, + compressed_or_compact_wasm: Option<&WasmBinary>, + bloaty_wasm: &WasmBinaryBloaty, ) { // Rerun `build.rs` if the `Cargo.lock` changes if let Some(cargo_lock) = find_cargo_lock(cargo_manifest) { rerun_if_changed(cargo_lock); } - let metadata = MetadataCommand::new() - .manifest_path(project_folder.join("Cargo.toml")) + let metadata = create_metadata_command(project_folder.join("Cargo.toml")) .exec() .expect("`cargo metadata` can not fail!"); @@ -623,6 +838,9 @@ fn generate_rerun_if_changed_instructions( // Make sure that if any file/folder of a dependency change, we need to rerun the `build.rs` packages.iter().for_each(package_rerun_if_changed); + compressed_or_compact_wasm.map(|w| rerun_if_changed(w.wasm_binary_path())); + rerun_if_changed(bloaty_wasm.wasm_binary_bloaty_path()); + // Register our env variables println!("cargo:rerun-if-env-changed={}", crate::SKIP_BUILD_ENV); println!("cargo:rerun-if-env-changed={}", crate::WASM_BUILD_TYPE_ENV); @@ -651,7 +869,7 @@ fn package_rerun_if_changed(package: &DeduplicatePackage) { .filter(|p| { p.is_dir() || p.extension().map(|e| e == "rs" || e == "toml").unwrap_or_default() }) - .for_each(|p| rerun_if_changed(p)); + .for_each(rerun_if_changed); } /// Copy the WASM binary to the target directory set in `WASM_TARGET_DIRECTORY` environment @@ -663,11 +881,13 @@ fn copy_wasm_to_target_directory(cargo_manifest: &Path, wasm_binary: &WasmBinary }; if !target_dir.is_absolute() { - panic!( + // We use println! + exit instead of a panic in order to have a cleaner output. + println!( "Environment variable `{}` with `{}` is not an absolute path!", crate::WASM_TARGET_DIRECTORY, target_dir.display(), ); + process::exit(1); } fs::create_dir_all(&target_dir).expect("Creates `WASM_TARGET_DIRECTORY`."); diff --git a/zombienet/0000-block-building/block-building.toml b/zombienet/0000-block-building/block-building.toml new file mode 100644 index 0000000000000..42ebbb58ac1a6 --- /dev/null +++ b/zombienet/0000-block-building/block-building.toml @@ -0,0 +1,15 @@ +[settings] +enable_tracing = false + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +default_command = "substrate" +chain = "local" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true diff --git a/zombienet/0000-block-building/block-building.zndsl b/zombienet/0000-block-building/block-building.zndsl new file mode 100644 index 0000000000000..86a54773484b3 --- /dev/null +++ b/zombienet/0000-block-building/block-building.zndsl @@ -0,0 +1,20 @@ +Description: Block building +Network: ./block-building.toml +Creds: config + +alice: is up within 30 seconds +bob: is up within 30 seconds + +alice: reports node_roles is 4 +bob: reports node_roles is 4 + +alice: reports peers count is at least 1 +bob: reports peers count is at least 1 + +alice: reports block height is at least 5 within 20 seconds +bob: reports block height is at least 5 within 20 seconds + +alice: count of log lines containing "error" is 0 within 2 seconds +bob: count of log lines containing "error" is 0 within 2 seconds + +alice: js-script ./transaction-gets-finalized.js within 30 seconds diff --git a/zombienet/0000-block-building/transaction-gets-finalized.js b/zombienet/0000-block-building/transaction-gets-finalized.js new file mode 100644 index 0000000000000..a38cf603fc57e --- /dev/null +++ b/zombienet/0000-block-building/transaction-gets-finalized.js @@ -0,0 +1,59 @@ +//based on: https://polkadot.js.org/docs/api/examples/promise/transfer-events + +const assert = require("assert"); + +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + // Construct the keyring after the API (crypto has an async init) + const keyring = new zombie.Keyring({ type: "sr25519" }); + + // Add Alice to our keyring with a hard-derivation path (empty phrase, so uses dev) + const alice = keyring.addFromUri('//Alice'); + const bob = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'; + + // Create a extrinsic, transferring 10^20 units to Bob + const transfer = api.tx.balances.transfer(bob, 10n**20n); + + let transaction_success_event = false; + try { + await new Promise( async (resolve, reject) => { + const unsubscribe = await transfer + .signAndSend(alice, { nonce: -1 }, ({ events = [], status }) => { + console.log('Transaction status:', status.type); + + if (status.isInBlock) { + console.log('Included at block hash', status.asInBlock.toHex()); + console.log('Events:'); + + events.forEach(({ event: { data, method, section }, phase }) => { + console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString()); + + if (section=="system" && method =="ExtrinsicSuccess") { + transaction_success_event = true; + } + }); + } else if (status.isFinalized) { + console.log('Finalized block hash', status.asFinalized.toHex()); + unsubscribe(); + if (transaction_success_event) { + resolve(); + } else { + reject("ExtrinsicSuccess has not been seen"); + } + } else if (status.isError) { + unsubscribe(); + reject("Transaction status.isError"); + } + + }); + }); + } catch (error) { + assert.fail("Transfer promise failed, error: " + error); + } + + assert.ok("test passed"); +} + +module.exports = { run } diff --git a/zombienet/0001-basic-warp-sync/README.md b/zombienet/0001-basic-warp-sync/README.md new file mode 100644 index 0000000000000..7550ca89236fb --- /dev/null +++ b/zombienet/0001-basic-warp-sync/README.md @@ -0,0 +1,101 @@ +# Test design +The `warp-sync` test works on predefined database which is stored in the cloud and +fetched by the test. `alice` and `bob` nodes are spun up using this database snapshot in full node mode. + +As `warp-sync` requires at least 3 peers, the test spawns the `charlie` full node which uses the same database snapshot. + +The `dave` node executed with `--sync warp` syncs database with the rest of the network. + +# How to prepare database +Database was prepared using the following zombienet file (`generate-warp-sync-database.toml`): +``` +[relaychain] +default_image = "docker.io/parity/substrate:master" +default_command = "substrate" + +chain = "gen-db" + +chain_spec_path = "chain-spec.json" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true +``` + +The zombienet shall be executed with the following command, and run for some period of time to allow for few grandpa eras. +``` +./zombienet-linux spawn --dir ./db-test-gen --provider native generate-warp-sync-database.toml +``` + +Once the zombienet is stopped, the database snapshot +(`{alice,bob}/data/chains/local_testnet/db/` dirs) was created using the following +commands: +```bash +mkdir -p db-snapshot/{alice,bob}/data/chains/local_testnet/db/ +cp -r db-test-gen/alice/data/chains/local_testnet/db/full db-snapshot/alice/data/chains/local_testnet/db/ +cp -r db-test-gen/bob/data/chains/local_testnet/db/full db-snapshot/bob/data/chains/local_testnet/db/ +``` + +The file format should be `tar.gz`. File shall contain `local_testnet` folder and its subfolders, e.g.: +``` +$ tar tzf chains.tgz | head +local_testnet/ +local_testnet/db/ +local_testnet/db/full/ +... +local_testnet/db/full/000469.log +``` + +Sample command to prepare archive: +``` +tar -C db-snapshot/alice/data/chains/ -czf chains.tgz local_testnet +``` + +Also refer to: [zombienet#578](https://github.com/paritytech/zombienet/issues/578) + +The `raw` chain-spec shall also be saved: `db-test-gen/gen-db-raw.json`. + +# Where to upload database +The access to this [bucket](https://console.cloud.google.com/storage/browser/zombienet-db-snaps/) is required. + +Sample public path is: `https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz`. + +The database file path should be `substrate/XXXX-test-name/file-SHA1SUM.tgz`, where `SHA1SUM` is a `sha1sum` of the file. + +# Chain spec +Chain spec was simply built with: +``` +substrate build-spec --chain=local > chain-spec.json +``` + +Please note that `chain-spec.json` committed into repository is `raw` version produced by `zombienet` during database snapshot generation. Zombienet applies some modifications to plain versions of chain-spec. + +# Run the test +Test can be run with the following command: +``` +zombienet-linux test --dir db-snapshot --provider native test-warp-sync.zndsl +``` + +*NOTE*: currently blocked by: [zombienet#578](https://github.com/paritytech/zombienet/issues/578) + + +# Save some time hack +Substrate can be patched to reduce the grandpa session period. +``` +diff --git a/bin/node/runtime/src/constants.rs b/bin/node/runtime/src/constants.rs +index 23fb13cfb0..89f8646291 100644 +--- a/bin/node/runtime/src/constants.rs ++++ b/bin/node/runtime/src/constants.rs +@@ -63,7 +63,7 @@ pub mod time { + + // NOTE: Currently it is not possible to change the epoch duration after the chain has started. + // Attempting to do so will brick block production. +- pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; ++ pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * MINUTES / 2; + pub const EPOCH_DURATION_IN_SLOTS: u64 = { + const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64 +``` diff --git a/zombienet/0001-basic-warp-sync/chain-spec.json b/zombienet/0001-basic-warp-sync/chain-spec.json new file mode 100644 index 0000000000000..8c09e7c7b0321 --- /dev/null +++ b/zombienet/0001-basic-warp-sync/chain-spec.json @@ -0,0 +1,192 @@ +{ + "name": "Local Testnet", + "id": "local_testnet", + "chainType": "Local", + "bootNodes": [ + "/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWFvMbTsNZ8peGS8dbnRvNDBspstupzwYC9NVwbzGCLtDt" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": null, + "forkBlocks": null, + "badBlocks": null, + "lightSyncState": null, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x074b65e262fcd5bd9c785caf7f42e00a4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x0e7b504e5df47062be129a8958a7a1271689c014e0a5b9a8ca8aafdff753c41c": "0xe8030000000000000000000000000000", + "0x0e7b504e5df47062be129a8958a7a1274e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x0e7b504e5df47062be129a8958a7a127ecf0c2087a354172a7b5a9a7735fe2ff": "0xc0890100", + "0x0e7b504e5df47062be129a8958a7a127fb88d072992a4a52ce055d9181748f1f": "0x0a000000000000000000000000000000", + "0x0f6738a0ee80c8e74cd2c7417c1e25564e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1809d78346727a0ef58c0fa03bafa3234e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1a736d37504c2e3fb73dad160c55b2914e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000", + "0x1cb6f36e027abb2091cfb5110ab5087faacf00b9b41fda7a9268821c2a2b3e4c": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x0100000000000000040000000000000001", + "0x2099d7f109d6e535fb000bba623fd4404c014e6bf8b8c2c011e7290b85696bb3": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x2099d7f109d6e535fb000bba623fd4404e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2099d7f109d6e535fb000bba623fd4409f99a2ce711f3a31b2fc05604c93f179": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x267ada16405529c2f7ef2727d71edbde4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x00000000071c0d84db3a00", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9007cbc1270b5b091758f9c42f5915b3e8ac59e11963af19174d0b94d5d78041c233f55d2e19324665bafdfb62925af2d": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da923a05cabf6d3bde7ca3ef0d11596b5611cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da932a5935f6edc617ae178fef9eb1e211fbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x000000000300000001000000000000000000a0dec5adc935360000000000000000000000000000000000000000000000000064a7b3b6e00d0000000000000000000064a7b3b6e00d0000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95ecffd7b6c0f78751baa9d281e0bfa3a6d6f646c70792f74727372790000000000000000000000000000000000000000": "0x0000000000000000010000000000000000407a10f35a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da96f2e33376834a63c86a195bcf685aebbfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x000000000300000001000000000000000000a0dec5adc935360000000000000000000000000000000000000000000000000064a7b3b6e00d0000000000000000000064a7b3b6e00d0000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da98578796c363c105114787203e4d93ca6101191192fc877c24d725b337120fa3edc63d227bbc92705db1e2cb65f56981a": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b0edae20838083f2cde1c4080db8cf8090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b321d16960ce1d9190b61e2421cc60131e07379407fecc4b89eb7dbd287c2c781cfb1907a96947a3eb18e4f8e7198625": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e5e802737cce3a54b0bc9e3d3e6be26e306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9edeaa42c2163f68084a988529a0e2ec5e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f3f619a1c2956443880db9cc9a13d058e860f1b1c7227f7c22602f53f15af80747814dffd839719731ee3bba6edc126c": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x3104106e6f6465", + "0x2aeddc77fe58c98d50bd37f1b90840f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2b06af9719ac64d755623cda8ddd9b944e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2b06af9719ac64d755623cda8ddd9b949f99a2ce711f3a31b2fc05604c93f179": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x2c5de123c468aef7f3ac2ab3a76f87ce4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x2f85f1e1378cb2d7b83adbaf0b5869c24e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b65153cb1f00942ff401000000": "0x601de9615313b00e8cea3ef84e79e50b2fb70e2c8a47cff478b9fe8b3fa8f2db02000000", + "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b6b4def25cfda6ef3a00000000": "0x601de9615313b00e8cea3ef84e79e50b2fb70e2c8a47cff478b9fe8b3fa8f2db02000000", + "0x2f85f1e1378cb2d7b83adbaf0b5869c2ff3ae12770bea2e48d9bde7385e7a25f": "0x0000000002000000", + "0x3a2d6c9353500637d8f8e3e0fa0bb1c54e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x3a2d6c9353500637d8f8e3e0fa0bb1c5ba7fb8745735dc3be2a2c61a72c39e78": "0x00", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058dc2705bea5c66d15541040ac6c3ae971bcf5f1040221a8d1f8b7c17e61bf21cce87411480b10bd8daa5e3c2b65f8c4aa364e5a8ddf27c0313827d06a2d6334e074db9ca7d876ac8d052862b9984530f4d340bda4edddd6e4de5ba624536717e1139e14df2d875c7f7086dc3fa3d398b64f50fffc58fbf4fb73ffb0d39cc2b86761cfe6d3073b9c1fcee7fe611b7f4875f9a7d8cfe208fdf493f4d3b07d9a73ad05c8cf39e5c4dde679c3c8391b9ceec4fd78c824d218e4cfb79cb8be9cb301eace3b7f8a274c182edb72e2e83349855e7e4a2a795d7e27998b94f46d92b42eb543afcbcf5f5c9e5bf2d52dfd9b2f93dc33262710e3fc79b943e09fe2d00f8dd7edc4350b92a692a2facb7adddffddd3d89e9babb7b8e0b9d61050b2b4ddf3efdfdb3f629e9edfe9c18c4f8715bebf42d6f6e3ff74be1b9c0c8b9187cb89c83618e5bce6ebf90cb3918729745244063391ee9710cdb87bfbbbffc6047f275725e904518e7f53cec48efa788c4bd67d104a4eb81534cd2bd67cbeebb075b4c32deee59a43da30fd2d0c979410f3db2fb2972cfcad89d0f822c7ab644bade3b59d6eeddc9925eef8f34e8645980eb0fb2f8d9b2cafddec99273d7bf234bee5995292c3051e1fa97dcb31285eb5f76255cff23de917c3f72c8afaeaeac6ef74e96462e7d70864ed2ef48be2c9ef05d8f1cfa6e67cb79dd0ecdabababab5b225d7f4a72cfa6a53d9bcf739903246f521840fe916b813c65e41c0c50b7b60fe76078bafc61ff4c1bbfac7d3887b3bafc37979f73580490ef389f9f5920c8edc3f3cee7075fdc3e4cdba79f1f9c200db97df896f532c31e008355cff83a7dff7e1791b4e5097b400cb89ef19def24533b4524b567fe2c96c02292fe233d5900f2edfed96d8f34d29c2680f3270934e67054b7a4b7bfceaf95fd879b568e777e395e3aa7139fded3ff484f77779fef3ebbbfdeb6364afae9f74c32bf6dd9ef3de714c839e5df7e908f8c208b30f2fbb388847f8a2354c1e170305cb6dcb3b2de7eb0436e9ff9edc33deb3fd24ed29ef50f602cc7dbfda0ac7df8f683331cdba73fd63efefdb2f699b79f45244e492a38b708a9def9f54e6ba3643efdf9d41acdeb964524d396f3ce3fd247fa2f7380e4736a01f2e59c72bcfd606d9fb266c81ce3fc32bc0d3a198071be3f3f8b27cc0f725b7bc6cf3d2be99da45f916a7e3f38c349d21b7211a2597587be4b05e6ceef17b908d1c4dd21112e95f0ceef777111a289bb43225c2ade9ddfffe2224413778744b8f3fb594c4271b83bff48831d3217995ffa73cff86bfb94f3faf3834ed69ef11719fdf9592481f68cdf6ded19ff91063b9c64df908b10cddca5e2ddf9fd2217219ab83b24c29ddf3fc51346dc9d1fb60fdf7e16934cdc9d7fa48ff4913ed24c72cff839a76dd0789903a49e5d06c80dcee7f629f9a7e59ef1f323cd2903720ee774f91b22454f610414ad21610c79c190160c991a32c6102286c0604808865831440c2057860431c469881042da10f28221370c9961881742e6186245881c426630248b1035848421248e214d42de18b224644a081b42bc08a11212032164081943081842d6102203212e102286902e84782184cb902d845821a44ac8154248304520a449080d3d36e8a9ea99a3478e9e387ad4e8f1d233464f0c7a723d5b7aace849414f153d24e839a247aa678b1e287a9ee889ea71a207891e237a44d053821ea89e247a94e81941cf0a7a5ad0c3821d11f448d1d3448f163c3af070a046c68e1a3b69eca0b173c68e193b65ecc060478c9d17ec84b103c64e979d2f76bcd8e982878947069e251e259e241e2b3c31f0c0c083dbb9dab963c78e9d3776e0d891c1ce971d2e3b2ed8e162678b9da99d1becd86087063b74ecccb133831d3976e2d8a963c76aa7063c1be0b9b293c50e163b2cd859c18e0a76a476aed8b162a78a9da89d12ec3cb143821d26768cd82962c769e7879d1b767cd80162c7033b42ec7460070440ec0072c70e13901b00b9da69dab101481640ba00f102880b807001b20490278094000813407e000204902cb51acc20a61390a51a0d80c850fb52bba376558ba3660710a5da1c409280e06a5635388058a9cda026478d8e9a1bb5aada1b35366a30a86d51aba236454d899a096a4c0059a386a546430d57b3528361e76987089d2f74dcd0a9d25943270d1d2a1d18e88ca123860e1b3a73e880a123874e0c74e0d02943270c9d3674ccd0214387063a32d0a9814e171d2b9d3774d0d0a143470d9d17e8cc40270e1d2f3a67e808a143848e077478d0c9a263834e0d3a34e8e0a0d3019da61f6afc80e3c71a3fc2f8d1821f5dfcd8e207173fbc981ef8f1c40f277e90e047133f98f8b1c4ab04f34b8f0c604288573fbebca26a54d0c8f083aa2af9f0c2c71735197e88a1d3c33787ec8e9009f6434d1b36573a869a0bbc2f3a778473d49e422ec2168453e11630203c1bfce0522347784527869a17542d68984219603d7442c086a8f902e34167294cea425023469df26e50e3a5878c70a926d5259b1d6267c8bebc5eb0d303cc03a1d2384307467490a583a70e74a851eaa0033d8e10bfc070a8568c3db8be78687c74f408e2c545b481cb4a8f14e06c51657851856d8c3bc8bc78c5c047d40f2d6476c83421bb416581cb062f2f2f34646ed42d7c24e164f1ec78a9e15223e6c54b63d4e2c38a48c78c1d95a9068d5a11f54aad04b51d5c68b86010adc61a6ad4a0597aa541a39463031033726aa08901c9445e21ade05841c6602307a9f423f827300d1b19f4d4f1657000825718382da827e8b0886ef4808123021c23708ae809030704384ce02c81c3054709215d709cf8f1c1f900ce062a10383e8852b468d0b2c26962c686991a7048b09303ce08704a504bc306861a326ad8b0d9400d0c6ac0c8990187861a3a74385063464fe12ce1c4e003063ec6f831831e40f4f0c04b8c1f67782ce8d104a8460fa79d257afcd003043fcc905981103764a2e8a1030803d005433e007ab179b201818d10f406a01dd48ece868e0956431743dd42bff4e8402743074387ebac74576a0a2a15758aae06b1aadbc07f60c68c971daf2f558a1e5a3aa577aae9a24bea66a077b8946a9868c8a0a1010d1d3435a061636606345e66e2a091c18c1c346bd0a83143c7cc1c3469ec0411ce40860b4d0b605a68585093aa69a131a25ba2c982e605b52d343e8c1bb0c901f6011808604f3c88784551b9c8ae646dc072f820fe88991bbcc0e88ae888e88c70c9d03dc18aa8319851a3f683c8c60c1a325f646e206303992b19ab191a66b0c850f1a282460c9c20707ac00142e60a192b6049302b3255c8a4e03df03f00d181831d4419d4c6b0517a55895334493b30b41534565c4b2e1ccca99682300d980e5e0d6057604cf306e31a3655231b3e62f08241f5e245c66b0c0eb48c408c3f8c1eb08943868b07e2d5454d1aaf2b5e5b6cdc78a9604c0387089c271928402e5e55d86001af6aba8871d4d0a0070f3634d4d8d1c3071b2b21ce268b8d0d3367d4bc01bba1c68d1f2ea859e387979a18c09a6c7880d95073364b3565d0ab9a19d4b2b091e175831a2b180d3859d4d421b6f17ae2d5c4cb891f37a89101784718c3cb0697198e858f261717ae12b846e02ac2b585eb042e295c53b8927081c015e59a72b5c07584ab0617169716ae14b8b270f5e0f2800b0bd710ae255c4eb884706d7129e17ac2b5838b07d70f2e1b5c50b8a270657171c065848b05ae15b8987055e1bac275832b07d7932b042e205c1f7005e15241cf54cd183364b862a8b99a2943ac439cc3e606110e9106e21be21d40bec0711273220a8068b169c3956433c3aca3c6c4b303073cbccee891c30e0e385c841b7899a1e30510aa1a143555b5276a4e4c2d707ee080033b576021d88961a7069c295810af357464086100bfe0203141600387cc13322600bbc0f100e8458f25c02dc030769a00c1787d51b1709d51b3807140acc1cb8b9a06ce11343000c204a40c58075e65d868a9b1012ba9fe00dba1034137448ba047053369bc6cf0838d1e3df464515bd0566ab8ecf0a0d34563f1d9507bc0116205162a544da22e5195a81e783272a2462a9e8b189731052e3b64553eb8689911bee18901048d57171d2d3836e0d48033838e0e325f006143c68b8f06b5a57a458c8b2ae5aa636c72a5d1e303a21db58a1a11b5281f31e058a92800a2850f18ea0a5e50d4cc21dea0a606231a43b8801101f321b4e25b8833a82a10e5a849c2d10296c533e3470c6a5c5073064e08bc1fbc19bc2b5e0c3d33e8b1a367e81a7a072057b4161b1d76b4b8c2a01903e452bb41cf17206a0021a3cb0244059d53a743d7d4d1d075a0d3d2fdd005d1c305080f3d5d741ce87ce87ae878e880e0d1d2edd079a0cba1fb40874377031016e00c61f3464d0daf0d9d1c7ab010b700d7d0b9a1870d73861b367676e0e14167079e2606e060c101838621e3820ea39de8259a896ea29fe82e1dd55f34091a0a97aa5cfc106fc423417304cd123448e41891e38408845884e834163192603402e4400d23c6464c8e980c649e64909029a207971e60f4e822a7889c12e488409c41e4808885468a460b1a15c4ba88c120e60558450503b442d64566860c8c19ab993b66eae8b1460f1af460237644ac043124782cf168e22143ab8adaa56545cb8bda45eb8b2fe245f042f838818f2d3e50f02178103c11b12b625bc4a44627c629c6a81814b12d3113f810c207123e86a8d9e0b9e1d1e1bd018301ac0cd8182316a31763163eacf0d1021f57c8acd00981ec8a0f86efca1783a884480271095087ea0270872a06a8e585e5b5c38b86d612b5205a4c842c08c908b1a835d42cd586fa058d153eb4f808c2070fad21ea0b5a21086f107e69bd117620ecc1d3a2268697453746774617032058d870c026861aaa9a1bd8d450e3854d520d1c364d3638d4dc5113860dae260e1ba69a2f80cca16307101bec28d145f42cd136340d4056d0589aa973681e3acb9ca3710d03902ee6d5acc1b4c184a3c76a5acdaa49032052736ab66066b164a260b2604e31a5983f4ca989c584629e60e6a609261573cbfcc05cc1b46246318998219843cca46965c6309f260c1357c50215983e3c994d4ea8b820a505a1261098400424f08006a49000144e40c001941820090953082300100091282c38c07979c648c16a072a6a528125539a00b1b0640938fd4914a0254b5abf8127529c003dc1f92b5c548401202551a440292a025a00f94c1cbc0c34bf24d4c40914222946455058f0e0956c3e89895a68e1f556b8a80848a8c7c7c0310f03b7700099c7f1e05c090935712224d48400349c3bb848084a1323a0274b80802cf0018e1d2cc50251a02cc10011900b434ba04cb94651805ac04013141ee70b03ad7003067a22c5c992274da4084d71c1060c64d4048a519426464551a0c870ea6021a1264e8c9c3421328a228500301c2b067212454a94254d9c44918200a3282e4871e2e4f6c0a9019429170345500a80814be2d020041c3a7270e660189c19b01ccc82132953806c70e26022175680628128529c2c8132e50255a0894d06a78a8b9e1035b90010d19215a24079120568ca1228536e91901429284481d2821404581c38780a14168a8880a25c40ca120c00057d80f3063f018ab20294a2282e18357122850a14178680961811499902444404c70d7e026589115011910b4e6870dae0222028465088a4b060e44293252b2c8132e53699f2244a1322127ce0c8802f0065ca25020a2292e28013481c361808c80520171290c213a015566892c20f9c35b828c809100b4ebc7091511420222016a240791c35a240b1800d4e1aec044a5114284446558096604068ca94264b88809e084171c116e4448a90ed06070d6e614913a3264f9a18011d0086734606a00835816204250a14191c3338c88914a10a2c013272c109501520189c3298480a01429e404215686214c5050b2c3172024404e54914284b96d0e880367c2083918403c3ca97cb4820e770b83b919cc66355b1aae694f951efc6c6fb273d1bcff3bc27c99b1b8ffc1beadfc75f7f5d6b77adb39bf963effa7d5d2be576e68ffb03e9f4fad1a6def7791fd013fe987a7b5f777fb4995b74ff9add99b2338b226566ffd8fbbe9deea87760f7d755efcfbbfb1b476ef70aced44a99ba7762f56e96d8ac3a99bac74ce64a65cd72cf9d3f0fdbe9d7ec2f661bfe987ecceca4dde9e73895dbbb72f5cf690794fac7edb4d93f76fa71d3ef73f7bc8fb6536766ca9429534ad9fb28f5f6ef83317b53a64c993fca4e653e66a7cced9476bb575ae5cafef19021428430c8eed4e5a20e7a575abbdd0586cc937a5ead1f738fcc9f3b6598bb5377efd1dd2be0eeb0eab5d6dae3d8b4dbdde79c3e67d3f6f6a6d4bbbf6edaecd46beaf5fc2af5c09d3af5599dddbf70c645db43f7daa42ba5ee2ff6f656bb7fee1574265d57ef4a7b093b8d31b37fd3dd41b052a069482b0dc31aef1a760fc309788712a8719ed3bf5ab97b727fdc13e4eb09e21e0176770a1386ddd3b9bb99fba3f9421aeace94db2140a97baeaf7eeef4f3a8473d66af52eade5fb7fb473fef8ffbc6fb3effe6fc981bf600dab4ddbb2937b37fdd1f7bceee7d2008a34054a820303bbfda1dc0cc3e3bef98b9fb989bb9eb66c7ac6eba772c67ee58ecccdd64ef581d77ce9a5dd771d7755d73d779c71db3983b666618666f6731c73033b35831cc95997aadeedd9f7b8e7b7737a51ed0ee80f6cbddbfe6699e6e8fba7703ba76f7a49501ed7937ad1fe5e9cddd1f7f5fc7d49dba376d4f61d20528c0030e9d7f780218051ad6f41ac7a969202774cb7cddb06eeafd79b57e8e0098ce016a351d6f1a86ec734e9eacf6febeef874c7ba594c62adb7ccc95a73bedd0dd457710e6e476b06d427666a6dd4ca33edd41f77228655a29ad94b6f70ca5b45217a5d4044a5db429f5da5e2b58bfaf6777fb6cfe3e9c0efae3e993d99550e7ea6cc3ce6266af71f7cf6b6776f630c63e67c8ede2768ecd364077e8edce727767d51866f7ca732661febc69f3474343536b7dbd5e73ce5adbbd84f6f6aa57efd09d046feaeefe7277eef9f5d7947e1f87cedeee4dab778314ac2085e9eee6ea9f37280382ec3ed929ad15a4947e1ff7d7d49d7d4e0ec3574e873e42483d44e2341e76bb7bddcdeeeeecfc7ddc55669e9f659cf979e8ec79dd4eabd7dab47af5efebaed56937add4a35e53da3da7cf39270c4c7be73c3bca6af72e403b28d33dfba360d7fc79d8dd9336a594b67f4e00dbc7edec3d99bbdd3fff3e76e6767667766f6766a6a0e73ebdbda977f7f4cf999d5266fed89ddb9d32333365fe98bfef6b76ff989d7a5f6f7766767777fed8bddbbbbf23dff7cd6aedafcee97d3319113c99effbbcef930d4000edc950af7e9fabbdfdfbbeefebfebe98f77dfc7d9ff3f77decf4fb78ec9a366d9036edcfdbddbd73ea7ded1fcf39448a2153380f6e0c10496901a805cf8805a448219a0242009e0011013d2192b2c427841e20284446207c0043d323a5a60b72d2a402424252402852d3c3470f1a1e464550aa00dd742d48e981620120a22996031b98284053a634b9f10b00450122ea0e5a806214c50522cb81058c5ce8c14115201696184581423465899322282ddc0cf1e95145ca058c7c3461a1c8475013274e80827c42e8e1c14188f40459d20314d444a8024b5688028585a2262cbc102128413c00f8800807690244e4032868895115a0283a3e3e807922048505274b8c845c686201a027b4ce88a88951d1073d1cfcac00a5888c7190220c00052d710245a8022e4c99111575d0e29ea22220a2292d4871b28408888a142740444b9a143d7102b42448904f083d539c0015fdb0587062d4a4024b58288212058a05661f60c002508880a870608980a200adb00400414208a1e709d0122946533040e4a4039610101520a125559ab480035b418ad09215a240596105294234b3051f0c446401275284849adcc054017a12054acc94169a181501b500c5e87b449102c5c84993a2284d888c96184169a1022e14d1584f80808a96b420e5499426443606c8a88951d1922022291730028a92032bf2596116821150d19227519a2c01226a32a5052943c62640424e9600add084488a51d112a1264ea4088140250a90b338c8922852a014110151594224c5a8c91328424da82c2982b2240a94262c38b18005a42c31aa02c401952840fe03aba3f986d44cf805a466c28dc462c247472c9009b7c20e09e9e8e8ab48ee47484820528b09231d1dbd8e8e68bb33399a4847474794c9d13cbacddb64f2c27c611e1d1d1d1d3192787474243261a48f8f908e909c9190e6d1d16482348f909ac96dde6ecde485f9c20bcd848f90909cc9d13c3a6a2647f36832e1a3a3a323a48e1e1d21cda3598f26d264723499091f1d39133e426a267c74c448481f13a47974e4313942ea98204d24a46682349991281346426a268c8484e44c9026d264c248938905bc342633cf168a9a70ff6aba2dbecb9bc2179ba6703ceccdfb193b61ec6d8ad3fa61f6369d5af6e654377762d95b3bc9d8a6622aae72ea625e8cbd219102ec61fec6fa1bd3105beb6f48a4d0bd3775fb6f48a450bfeb5c0ec6de580fb373b4b709e5b4fe97bd4d97bd7993686fdd14da9ec24d2876c109ea68a220cbde9296425bcbde904881f5f46f93eaf6330db1517b63d949e5b47efa9ebdcd29a7f577f6d6d5766e7a36a8a2065a90800530cca9a9c58c21d4f882082b9f3dea2b9a5002872dbef460a37656517915a572a2534d533447713977a250de975e0555e5509eedb450755fba13d5955367bb266fa25ff6d477757507185faca087266a3ba6cecd37bc08c30c20a4c2b0b1cdbf6cdced8f8b1dbc7842862cdcb0b1cd6d87735a7f39ab6eff1cc138bfec70b7bba9cb1c1c59a4d933361c31c711671c31c611611cc165882e59dc0c8c9c1b4288db0f5ece0d41c5104fcc1a30bbb3b395796543826b58f0c86e3ffffc59fbcc3075fb6f74daa76fffecd86d16fe36cee5c961ce39d9c66a98c3873984d395f5cc2d371260cf8668c26947f0b30d091c10081bb60640bafc2fc840c4b644c4d6cf362bf3cad61609b64773ea809492486310fd0f6e89d41d5dfa25eb006cc6ed2c11b57b26eb7724253b57a2ca562d8eb568a25fb228f9c105278b10f8e01e4da4ae9b0b281f40e625e61c9ad77fde00f293f00738346f3f0f5e6ef8dd3759346f7f8b25f065d9a0f9ac269a5cebdea8de21bebae0b73842f8ad16d87db348ce817ed8e586cf7a10ecc26e3dab45253409ebc33fd2247c0ffe24c16791e1bba8a4fe9c96b936cbb6936bf3590f9265b3a8d43be477e08c269237db8dc9f47f5c3a9f7970bade7f28a8b003166eabf159fbd878dde7cc98b0204667f3d1a38ddbe9b44fe9fd8c3baeef25842a30699f8ea54c689f79bbe7d1c274bb1fd2519edb3117299f9a5bf219b7ab3fe57edffc46deed07694efb74443d45254beef7f5fbe6cf32ebb00559a2094852782dcffb727edf64b51fe99125d2f5823c31c967bfff6cf98267cb79bb497f70762bf16f25d3b34ae677f737e00e425c8fec260cb7accfb9197c18c158fb78df3d1549a85fbe709564c270eb83dcb8fbbd47d68f2cbde73fe21e0c777e7b55f502d6fe793dffd83ee2f3c7daa7f5cc7ff339ed539f7fd63ede23dd16297e25914010fc231d9236f559396cb9f4451dbc07b95cfa1e07ceb8f441fa208834c367b1c00727f8437ec1076b908d92ef3bfab93e259feb03bf3ec86a3d5f56cb8e509ff5ac1db25cfaaddf21e9d2a0eebf0fbf92f3b25c1ca1d56ab56c50f725fd22a4138a906ef841de9f40ffb3a5f7a165f165d52e6431b4e5ed86ef912fd27b89394839935ebfc41cc4fb693be2210976a147fabf48b6555209cb06894f5f7c16910c896314df3df03d5b8a0f5a1bf0bfef94d017ad0d6895d4f7be821ed9fa2fa8d64f49fd6a12fa2c5b7a0f3e089265f836ace70bbe47dab0ec08dd830f5abe1c98e3d2071fecd06b3d68cb794befc3073d6c91e197f386df8d002601c1f767d9a179c57a7ffaa02d5b1f92e0bc1e396f8be43b2ff3ed8618b9fcec359c0168182ebf8b87c8d1446a7568039884410f5924c16d39a75802fbcf13fafd595f562e52bf6479e86439aff760fbe5760ffb32c8a1279e40df6d59af7bef950e7e210d3bd10095eccb22e7edbc9fff89273458c5138edcd6b3c8790d8024c56d3dd80a5926208d10e50e1dd52421cefdaa880024a7db7ab04396480292d31d62725b0fd6b0251a00c986db7a100c5962121eae1259a20948b7f5a02b641da0cbd7fdb8997976aef2a8e97a0f86218b65cbda2e5779e474bd6f91cc3a65ebe787e209fd2d5b4e164356927ecfbacaa39b4588ebfdbc959c17ac618b49e8fb4f71c86f903802d2ed3eebd9f91e34df46498981dbcf17a94e2f892c583c0a0ee4c09405072f138710ecefb7e99e67f7ec9136ddcffbfd07a3ffc1f5ffbec9ee677bedb5c7df5ee59eecfd11f7c8ee821dd90fce59c3d83f3b0f3b1a765dbff7839d38028be17d3357f3dcc976ecd907d791bee95b803c3b3082b387b193b89cf3418ddb405cce7540eace2ee778b04a6264a5ea96485178b774e1062b2eff6bf6303a1597731f58e2ce2d977340bc71752ee780e0c01449686541758798dcaa1feed091eb9646658c2103e296464f408065dcd2688632ba5b168141821f504cddb2286aea561c8eaf2effe56f1dc0a179e98394ecbeebbeb8f300fe6dcb5c08dab8f4e9cfef49da28e9beb8f4bb2f6e598474e977cf7788afaeae1ae9f6776ddc79c4964b5ef2821d3689733bb2fbe24e9184a72233ad512b7927c1eeea5a9b24f3fd4998efd608e4dc105eae51dfce96fd54e6f547bae47592bc6d045e7ea46ef69739ee7cd9eb3ffbf9fd5d3401490adae4fca60d23e76e38e3d6ffde6b7d25cbd607b1be7ef7dffb834d860fba481bd7f32d91aeeb3fb2ecb643af4bc5a8afebc31769e3fa79c70769d80f36699384e270177c7afd82df645f16934c5c8b24b8fe458eff91ae6fd2c317492f48c38f643aee8bf40b76e147f20dee8bec0bd29edc7dd41b3ffc8f643bee8bbbd9d425be5f317c316e48f68fe4eb8ff88bec38e8fbfa6e029214f5b9d63fd260f308f3bf2926f9beda24f11bbedfd0964851e070b749f0c129ce1a46f05d24f8305cb04317093ee86f5c90cac1735cb00b5d64d177a9f45d81685ef0a98864d230820f52710416e3820f7247bb72e0027f0b2e86eb22792ed8627d83e233d75aa0f8618b2cfdb23e64852dd6832451288afd21eb5b248b26b00e8b9fe707ebecd8336aed752beb996b7e5ec029c3482fe76e78c19461ec2ee76e880173ad83eff3bceffb496ae0fbbe2f0d9049708add5cebbabbab5ceb7e921ae8ba67711124fddd73e5225420097c5f0f32333373cf6772dec9af906baf6f3203a3ebc50f1ffc0c8cad677dfdef33307afe7af5bf5edfaf31c05792efbf1659de777d677d7dcfbd7a2cf0abef2495796bfd6f72bbcbfdddfead36a8fef72c1ea0b26c8974599375abee5eff880fdd6ed76675edda3ff9491bfff9fe4ddaf83b59c291cbac537effbd937cbfff2a9151788744b873caadcf621226b77eb5cc3a247c64593fc8ff7bb092966888ab70b86b04735bef6e14ded6d767f1842997f547ba92444322dce69abf91776b5f7f164954dfc8bbad6751c992cb7a9c2111eee41aeb2bcb32ebb83842f7b592f3bf725efe8e2cfbe733b5b5674e8e757e5880ecc1bc7bb0f307a7c86d03bfb625d2ed79fb9899ffe33bc9a1dbfd6693437e5fd33259d25a2793ee43dfd5d5d5975b9b1cf25bed9077cfde7f64f93d95f10a511c0cb7ec1ffabec07075a9d4921544bf3edb72bcfc4d86fd54beb27f8888ca778b90eeb42c2261efdba86febbd6fd9a1f9e5521669d497beb5493273d7bf4524fe1ec9e57cef9da4d29ef7b61cba5dfa5d8974bff9555bde6efd26677777fd598434e423f0f77b437e29b5414aa892f96e83f89b59b3e2d03327768055c6166f787a2663152e32bf6fee52f1441f1f7cf1a5ede37df8fccc7359dc7a2e3243f2b9c673f9bd24f5c93abf4e92ef145b606b04d66b5e904524fcc1c69eb59ef5e04f11c9b425d29d2cb2458295a4326f392feb2749655e16eb2bc99782ae6987fcda8ce077feb4cf35fe792bc9b241f5c1a7e250bdde8321f862fd14e7d2e86f59967f82952cebb35ab644baad89a405daf276c19f248bf49eac33bf340092149759673eeb8ff8d0ed7a3fcb5be490b3fc59e4541afdc726f97e64ed7a6425c119ceff489b6987e6f52c6fc1e1ee8feb8900f426a431a8fb0f6ed9d989a5677c3937830fe0c4d23e7ce9037111229e4b043f12a7f1f399ad6793966a36ef99626c58da2776e9d70c29632c85eec4d233fa3c68a090e4fdfc23fdc2584e2c977ec9685cfa37fed16974f8e004104e4051e17036efa96d92382a821a4010431027b0252dd56cf399626cfc494b6dfb9e6900b6f91ec93400db0d8914e6df50701afd3975e97b7f432285ef3d7b3be234fa9fbd219182f7d3dede69f43dcbc3b9dcfc92a7980632a8800837c230820c2efed21f1283916d483a6071821f7e98a2cb1233b0b18dbfe4b9f4594ce17138dbc4d23e1f5cfa9c436dd06ceaa63f80cc454a1ea457d93e2dfa1da547d73b332eb55de97df43f1bc5b3a5f742ae44d5edacf7a5dbf276a92b612d6cb89e25a2765f1e5d21241fae17450049b9db7d496d59e57a14d6a2e9765f66e052cf46e1336e67a79e50b0f30515f4062df7e672aee9cb053bec48226efc47afabab2b1f6c2c227033632861650d1cfd39e4575797085c0f1a86c8e244d48153125238a3297cc62502b7f30515382a0248ca55c1b1164d36fe3203b77be60030ef949cb3a1896b342f1131f0193696c5f11936b637fea3283f7c000c18bee070b66a7149389c8dbfbcbddb720a48c34af253d2bfc506ec7c31c5958d9fc506c0c81823047758b9bab2f1c7aeb77702d02925bba7f3ab5df7fe951cbbce6b345cece2b8b29e754fc979410fe97b1d9d9776c3c0bf72112f65d77f7a7d67bd7befdefbb3bcaf9ebf7befdfbbfb77e4bca047f2ed90b41bf48f74bff7d37b1bfaf3f6b5a17078df7d5e47ce4b495ab9c6ed06c8dd532ee23d7fe791437ad6bd1997a767ddb3e865ad52df7f5adffdcc7fc0effea67d68cfea774f848b3cd7ea770f9263d3eab7c8dab4caa3fa8fac69f5bb97b50febbbe72db71c72bbe7c145ea3b55e891fcc1eafb43dd9b9ed5ef9f3fc5fa36dffbed6bf37dcfb91ad4b89cab018d2beb597d6f885e96e59ed58e6bfc2c528f9cf7a891ba7625406e9fcf9ffe47cedb91f45924a17be6da08fef4711082c503f8d3af4f6d592ffd7e8a4355926280017787ca0bdcc08eabeb7d11d2f56cd1674bfadd91423529061868951695545b56015924a1a34fc511bc67ae95dd47ceeb911d126a6d92f8d3f7a736a89fdaa1db2dfdf677b353007e0f4e1109f8e1775f49f1998bb8de7bd0458e8074cbf0c1677184ea0aaacf1f9265fdf9e014d9835dced520c6edb8e6b53842f78b64fd16392f08b278807e16eb4592f52d92e7b3888465cb79d996ac6716d90ffe9106990c1f24f9822c92c0367cb043909c9745125a97b956f283e00cc1f08f34872059490c8ce183dfba654d526f688358df2059b27e2ae967cd568be4cb22f956be5e475292f68cc3e95a409e32d22f65b77b9eaee3b9dd773fa47b5913b7fb9bfe695b67e4763ab7fbb17dfabbaeeb9e733a1bc4b923907932cf27207fb0b0673114daa77c1adab8b92573b9f4bbff6af7f7f3bee42ef77b229f4f594b689f7264a17dbe37d23e35f77b15da87733534ddef25d03fd4e6dc85b7dcefb97d6c6e19bb2591fb757fa46fc0263f21a608eef760874df6952171bf07b927bb6b685e5dfe3ed63ef4bfefbfe79ccf0671953fcdda074baee797b21bf6e176ec3e092ebba1fea37fec9200f20e19230d586eff386120bbc029c3487b369f67fc29e92414d0b9b87bcdedfff096e3cd097b46a3009e230826bad0800439dc20031bc760d4b288643e1db17ce9b17d64b73fac3c6d783987a58d4badac67937336009968ec1ab6cf1c3b00b91c1b745dcd30c70d888445871b9e7000a34b1177d85804e7734ec943ce7bc1497b36699f4049f06cf9d7fbca1f8cf6cc96467e5b2f6b1ff0fd6fdae7033f16cb96ac6f3deb0b7b36b6cfc77a7ff023e7055924873f451278dcd096e1b7fe23f986a4915fd683b379baeb23f98224df168be49e95b23bbffacf95b771d3986be5cd9db6f4409fd323f98a30bf9246dd9d21d86195eb3d93652d2b7fb0c9624ecffcbfff9e7d4c52b29cd7238dfcf24f72ec19dbced69ef578bffa0fc5e170389ccd7fc645fc7acee569cdce03eee493b1d6366e7f289bc187db7ff3649ccff34ee110c96bc42b6d5cce5d11e3967ffb0a16f7fd47a769fd8d047c79df5988b40fe7ae7cb9fd46a67882ff7c1681c672d63d11cecdc074fb75fa67dafa8ddcdcb44f3f933c3d6b9d9ef57b47922cce581c615e9d9e4d71d6b37e0a8c47b7b60fe774371d0790c9381ff48e9832e2703818669f30bb7da4670e23e736c0e57e709d72112e1a3977258acb972fd885948bf4e5d2bffbf9a00bb26b24bbfd4e96936888af6ebfd177e9839ea8e4d52473adf4af9cc4fbee5924c1ff3bd24ef2ad64d97d39afff47da28f1bebf6d50f7def3a5245f4f54f2c1f52c73cd9f8a48ba196576fb67dae6d31695700da2cc6e5b3bb9d6efe209b3eb96b9d6967b3691be498d00b9dd7f2673bb2d274e54f2baf49da4422fb5cccecc5ce3205dbc7a7693bd05387f82977332bc60a6d132ccc0bbcb3919eaa0fea0fbd0bc9acf62166f06f1724e863ba693f37e97734c42b02ee7984470653d6363d620037617b9d621d7fa99ccc0c839a62dd72fe7985070cbcadf2f32c9d4c495f1e775d535763b80dc0ee89e1af9ad1d69e437c9c4e170b83b9f92464eef7c4ae7146386ee0f3a39599c7fa4bbeefd5d44e23d73f2eb9fe2fc16bde7a40138b18cf3d9320d387f9224f0052739831657d6b2cbb9a52fae2c1eb688645eced55075653d6b71885ee61a7f8b4886e6d5a5e15a7f8b24cc4b9f4916f927392f9343f3eace771109b5b19ef54f52c6b57e273530724e862362345c846fff28e322ad20fac10e1685174e923d58d833ee597973e7bb2ee764c8e2ca7a361f64ea3db973790e20370876cf3dab5df7471a04596c40bf2d912efd239b3b8b2af8b7bd21d96ef4dbe64ede906cf48f746d9fcafed35f1af95561e4cb29cc777b3bb22d213124c2f57eda3a92da72deee3db2f4db7d93fe47bc6d2ca6e0b6f9479a927c9b641bf74fb521f9517733813c34726ea98ddb5dce2d65b90c63918479fd8fb423cda9c4a70246160d807429b521d29c0570b204e49e913091f8a74842503fdf20b64347977fb200b202e81d2ff8a29d9b8074fbdb863df3f7a79d4742bfff91e6f6b119a1dfdfbd9f4f6bfbb82dbb0ff2a7ef791fb64f5f6fbc2a00590123f8a2bf84e5f2fc72de69c39ed1a70fbe4c18fdfb878e2e1d9a97da0a80dc20b74f7bc824c8394a6d8497a77d4a23df3ee5cc48fbd0f7d7699feffa8fedc3392531ae7fac7fdae639d79fdb27259834ff120097ec88e099bf976002e096d4a1fca71301d3fc69ade328fbffe0961fb478025371fb6b869431f214976f49adae9b20e91af5a5cff405705954d25d6abb6e7e51204319f90b14e42eecf21727e052afecd2f940a0187279faf7a5ce65066282f173da87763f85726eb524bd030a19dcf9a3fffcf4ec87acc6ed9e26d9badbd95228e73e1b23dbe6b482883b3830f586153a9c61732ba048e3baec7e4ed3e603891abdcb3928a86ef9777aed5e80dc3eedd94a96f5d6ffb8c8e77111afe3229d73116f2ed2938b4cae79cfdeb441defb7b5e93b567f57bef67b565add7fbeff3bcaea3d4bdb9487def6792ef76d523eb7b5d3f3bf4dda07e4a96fe4548b7bf497b98028bcbb929a2e829a06e0bd13ede50fcd3494cebff61a6958d85e5c33c2b3bcbed2f6197610f8021978342ea1af5f5279a4d65be006e8b27787742b14e7f0f31f2852a29974b446fc95d54e8d9a463246f433581ce3c9b30da339d9e75397358ac6736582e7d1bcf8ed0d77bf642055f6ef7448c4471fb5930ea1fcff517a07d38f74413b779b84c223ee3ed248ac3a27f9240272438f7441bfd1ef9002b73900fc045910fc045d133be3ced53d621edc329788015b639447c76ec9b9e35ec0131e47ac677bcdec30fce56f25c2682f4c412414081613583191c61632a2ef3cf8ef40fb5f1330466940f4fe0da89266eb71351dc6e9e198ce53febfb6e0ab2489e32185bcfb7f5f3bfd6f741f4e9839d888465025292efa72d83be9f22fd8f0cf23e4a270f6bfa1b15ac19e286d3283c37c83fec197ffdf69e7e29c473ebb31eac64ed591be3f77c9b45d6cbfe536dfdb59e4065de69a98dc2733d1b44bfb32358aeb3677077f7f9eeb3c7985eb610caa17d93dc44c5718650cef422c4236344172210e2f9beeffbbeeffb84be28dce5234beec25d7478867011f0f96ffa67e6f530cf1f8e3d00a6b7c9c4629e5b16461221f331b171e61b1623ebd9477bf67d0c19ebd9f733e437edfb1639eb190cc917f6ef3f32b0960c599bf63d8c1c7bf6fd48f2f4ecfb1739a467dfbbc802f4ec7b912ca167df87a4093dfb1e2451e0daf7ec3f449af67d3f7f8a23f46d59eed95763ba0b0128da92874b17c6e2b22dc51785786e149e1b147eabe4b9b30c6dc973c3cf893dffb70fecf9cbd88d79181869a467fd317248d3622cf8230cf94e9bffca9189e28ffe03f3e3f7c3902c22196d19fe57fe15df4596e3156d19de72e48bc20dfabe1e39d2d8b487cf96b3fb31598e2e11c808c67f1739739aebbb63f7fbf0b97fc2a67dff3d7873bf77912212d1d69e7d7fc443922c6577da3246b2ec67bb882ee3f89e9d43e61879cb9d638cf5997462ccb83c1632def295467e8634adfbafd67194c9fe673c3debc68f2c6796ebd9982582c575e6be7624c039bbdd29ed989cff8948645f8c6cbd18d97a3306237fe7e22a76e8206d752c2fac3d63ffce3fa8b3b567ccb3d6cbcc9fabac72e9e5cbb34507a50e7ed37b1aae75f4e69486d487be77e1e4e2fb96eeee20839f0472b808e55cfade046bc8e4bbe82efd4f4442ffa667fc32aef14f19c930a21d2dbfeffba8571a399805e46286dc1b605b9670895cfa538c71be1091cbcc52e8bc80bad3d9656a4ba4282e7df79f618cf35d88b75c3acb18e74f2e287da702049fa76e39a48cf5112ea202d7c42fdff5e5ec87884effcc9ce6e1f74cbb7eb5338db11c5b3cac073b474906ba30a28ba62bc4c3c5849eb946a74d7186e4bc2209640463498488ffbcdeffc88b046718b2fc27d6347fff58f86a8c446e8b88ff2421517fe96daff72f40fb94d5a7be7f09edd3ad677d68cb1b6f81ef42442eeb5d547253010e42405d21ee7259f4b2c8b23d7b7d2491c953617b4ed417e7943377bbef723b59ff94e0b4ee8938ad7b7e06b2c548ffd6b30ec8d408648ab19c5aa696f6e96f6b342f121fdae7733aa27d3e4b0454fb44b58f4d4f2cb77bbedfe8b4eebbf17295a55c05640463f725a8c045d0184ba6badd3379d23ef5bb670137a56fed530acdeefcfeeeab98506dc95c5c44e259233a9be05af71d4219fb482023602d3dcf184baeba5df7538c4a2255e6531212f497de2644fb94d3a97de8779f0f8b48be5aad106f51a28121b8f042758598ea4e7a3d925e4a7208f363fdc3b66aebcaf176b652e7fa7733f7d9c0df97eeeeddc75dd7f1cc72bcefefc85276f9fbb2eb3aaed1779f1e59c62e7764195eae43b8c66347070d27173d3db2b6c6e8d9dab376a6773ecffe32bc73ea08d7d8081109e480219433847f96c3c577f937936a72573bebc541bbae637ecafcb2aeeb28384da0725ba270fd2759f2dc39e79ce5ec9ad0b37e27857898cbed233deb9fbc85889139bb5c29223d6b4a96425198ea064d8b44a73fa1d333de22813b9fff36c5a8bcb94239977bec596cda92c89ddffd4ce7bdf7294444c65bbce9348020c8df24ebdce6b94de476f9df7739b7ab48c25a8fb450cee5a79c33e43291cbb2d28bf5cc93fdf5a6673b7276bbfca6946b311691944244eefcb6b3fbc299bffbd2f3f83dfe208fe716fc938b7e3ab718e7cf1890e075dd7bd77d10a5f426d67d4929ed485b32b9dd53727631cea746dded3a4b72d9755de74f20b70fbd6077777b7bcb00f697eeee3d6bbfccf432fbf897dd3788a8af5f5d3107807ee9de414e49a2795b095f5d5d6effb2bb8d5a890817c9a76701e98325330d625ba70b2a7391c945f896f4b9b6c6382f7dfa5e7b367ffec806f885970dd0e6394b505582d9ed449313b8ef8201726fc0eaf60c07708740ee12702901167480f38f984c7c694288af7d027039c744976be4d7bfc8fdb84873cd595150b18aeec585f791f1b1bcd4d7e55c1371b0445ccb69006b53a8456c05e1a247bc6050239d02165601d3da2226468232623130323cf50a3855408239aedf2e0196db3fc4c80765c84528002e915bde9044fe4454c6cb3ff4978a5fce5d4af6b8200d295943ca2ee88594e471c12fa4e4cc056b48c9980bb2424ac25cb0155232c805c190923c170c434a861714434ab62ee80a29c9bae02ba4a477c131a46477415848497a419890927ec19890927dc1180c2c2626e3b24446e3ddc9ddd0b580f30aa8286e49a19a8082f280169d86c262ced9ed05ea0caa840f4a24415929d1841249283184a7441b6228a1c496fa8d97734ac8515d31b0bca6568b0360750ac1277189dc12276831e10ae7d42b146364282e505bc46496f012c25c2616de662249092c4b58dd3126b3c41bb5c28493891a6238c74416d8f8728921d8625518170c16e04cc1571954d3edc94dc059c41836146e323373126d24e18593f8726524c87c11d39fad4a9ebaced39673887333b72a798ada72e6b69c35151628d87f3f8e0ffbbcf88f0c4dd9552fc8946d35f323e3f15134b6b574ee3fd9287694d31aca69dd4f4e6ba75c12b95bce2a9eba652f4199ec74923dccccc7bccccfe75156198c2dff636c29b3e5e8d45aaa6664ac66aa50cdcce0bccc30cd3055cd34cd3459c954dd923249d9d1025bcdccd4cc8cac8b4cf6fd4de53f3c686c6c2ccf6a6a2c0d0d0d0f4b79f008656f4566ca66acf7f3a7d37492eae9659c54cf1ff3b3cbf3ebd9a614fb39f52fb2e78e71dad74f29191a1e5353aacb949215999fb132a599b7223369ec0b323b3363a79722a4db39ae75c76c59636c39c2d85206b3e56cb4e5f7bfc8ceb195950b9ef84007d0880304b6594b2db551830db87165fb5c8ba8343c80abc3043656d73147087032e08ab0816c2859e9d2050c4e6063a7b95e7ca79af1c2644714554d6ea585e25ea04e2fd0a714c0616b24a66e4999a46ee75cb3d9d8b2a54c566df9f7b3b9dced9fe5b4388d9d3c5bd6a7ce962394d3f8525bca8e2411e534be6ecbbf50311dd303c83925cddd999f89e241130545c38366522d4eebe741e79cb28ac9c46233333433333c66666a64646256c94aec9564373ff333df3236375f4dcddbd467ffe161797c0d8bc682cf36997d61c6be20f3ad076913283e65125f4f712fd8bbd5b7555b5575eef3a8ea50ac677fe2a9b5bdb853d15792b1de45e6adc4629e6d4a32d6a73e86a45ae69496a9db1ff3e59c1a81d32d1b57c563d556fed34f4eebafa3ac9f6e3f68ad1ad731b69f26ae9fc62a1be7d47c9a5652d3d64f9d1a14fff55552b7ab6e4ff194ffc8c4bac81e05a74d2a1e305e669e9d36be8cccb3d35c6fc563cf4e0bbf63969d063e28533ff6e28c65a7b15ee65f3c2c3badf5330fbbc92ccff7532dfec3c3a2e0b4f93330b6e42e773ecc965566b42553ddf92f5b8e31cb4e73d992bddcf9a22d65a12db9eace07a996dbcfe33f2d5b56962dc7caf3f178b6ac9dadf3eb6521c1348c1c20e780ec3face707adaaa68d454e1b4fd127a7e12ad3d8c44f71fee3ef24d3323631ddae2375f72e5053fec3b62e54d40b7d826a2aae7226ff513a8ab1d5af4fadf0c9296c396979a55ba8a5c97f94beb7e255eb4c4eebffac37b93375d465bafddec5f2fbf7f4bd9fdfb2b63fdb9e65a7cda6a6f2a2e5f6971d75fbbbc97fc62a1bddbbc828eef697dfdf915063e950936d2fa34fd73673d38b97cfab9a39a8692515859bca35531729a7b537a99a3edda64ffed3475831061a1da86105b67e0ae53fae830dbe1061441054d8fa6994ffcca519c0b8b2baf2c2d64f73fe13e40a1ecab85ada62eba752fe339da82307263a70c0d95a4bffd4e23fb76903bf5bdfacff89f21feffba784fd55fc07b42538adb79838ff695914fa2793ffb0ac0afdb3a95a262f789605a7f537f52e5cebf72eb7dfddbd9f8971d2aa0f89f6584515ff61b0b2be9c5a2e2f4d1beb5fb077f173abf52087e2873ffeebcbc974f92b09b333cacb6827d4d2b4bd443b9f467e1496a60df6adfa254b7dc939f04b2297c32f792effcc7f6e283c3f8c9c52e48b9c5d5ce4a422a717d6f3d72a4ef347c169e2f38f22682753cbcea6b6a5cc4e2d4e9bb6fc3bbf9c52f367b30abe3f4f0debfb13a995b258ef5f82ff80e0cf9c361fb4ffd992a5eefcf23d5b724ec6b263674b22d569d4963c7776cd8a01e49cb273255b3dfd92a7a82dd996b3cbb664abeb930463399d9e6ec9565655fed3a50b1595172fe594f29fdb1fc5d8fca793ffdcc4f7befbc207bf64a9af6b3deb4bce755ffdef4b22b79c4e2e7b7b41f4eccc697d7bc1fb723a85f641a95ccbca589c8baa76fc884079b6b2d34a9e6e2175d31480a327fb76da7ddebb9effc3faac16188ab19779feeaaed7088b81791ba6698bc9ccf0a8f9a4a56aa37999272df1d86c6c0d0d199391313c666448598ca4e120f363c8fab15a1c040c6b6c6e38480f1f381cc425be46184ccd746fba8db445df738deb9e7ef8370773b85ce35e56bcfef737f7a77f0bedadab9ce650537846960b1c5b7ff3b1a79ce6b73975e3d9e404c29acaebec6d56fd847a7d2e741c2bd7385c2ee46155415541b1fe6b2a707cb6815fb693d89a4ea3eb4b1e5ff65db64e89a1958176ec26961dab954d1dc5d83e5b9dd6cf36cfb28dfa0b69762b0b9073ca59553faf7bfef71ffafc1ed991569c9256fc236f325fc91b8f3f8ab1252d8936eafdd2b4794d947e47ff567f69daeadfba168bbb5f9ab6ce5b323154ad5f9ab6968cbd252d89b62426116c32bf346d37245288bdccdf78d8db8c5d9a3619ff987f8cbd4d2aa7f9c3d8a5696bf9c3ec14a7f9b3ec8d89d3fc47bb346d9dffcbfecd6597a6addadbe8347fd14ea71b3bcd3fb44bd3e6f9837636396daa7aef8d9dd3ccd129ef998ae7e3aad69793aa658f626cddb3ec518c8d3edbbeafcfb6fae584aae4477a6447ca9c369f92efb4396348b3bfafea8ef44b59f9b79cf5dc31127dcbf5ce62bd7d4104bf5b2dd0bec00a9f3efb4f685f68bd7e3efb8f52d873c73834ef2765530affc624c4f6b22f802efb42f879569e95ff88564914ad67e534ffd02a85e1f753eab3e583560904676eda52d6b24aadb7525fa96539ca69fed47a562cabc47a2bd5be409f6d4a2ccb4eeb9f504efb703d778c44def7cf2546ce2181eb27a927ce2181bb9e37359f9efce766c58f626cb5f5dfaa3259fdd638de723edd9884d858764cd9acf80bec3411aa6c4f3d778c7fdb9673887ecf1de308ccb5b256f6979fd44c629c498ce5e7e459959f5397f9d4b7fca43e29ff994f4ef3af751c65b25b7e52d79ffdffe23ea74fea73e2336ee94d7956de54cf2d3d9cf7e4e13aa82e1d54ec9694aa63a26add9236d128dad4ddd2735e5c8be7bacab5485955b513aea59eaed55339ad7826ee4beec253d6eb7389712a31cedb5fcea7afea32074715a9eb97179066c766c5a9288e15ff52d361ec26e78be9b159695cffd2fc6cfeb2390f9be3d8fc039bdb9bf74c426c9dc454b359712bdc92676ba6b675cbd630b6aeb1b5b53508b6b6b7ee9984786c24c9ca6763e2b1f52fb9cd3b9b8b3697b1790f9b7760737bf36712d24ebbb637fa4731b6ef6bcf7b2b2eeb3acf8e6e6f4c426cfe4c426cb718b7ecb4f94731362621366fd21cf8fec334b6d34f5a7a99edfba4a5d1e63df8fcd57f929886d89262bf44c4d6faa5d096b4f436d6d7e797f94f12d34d28be5c3ffe528e0d7cfe318969664b5a2262837dd2128f2de6976636985f92d9c64f5ae2b179335bc741e6272dbd8d7290f9cc21cce78f215d1c643e0c297290f930f2c541e68fe4c821cc7f91321c64be8b8c7190f92239c341e687240f0e321f24652c647e8bfc38c87c16197290f995ac1c020799ef91351c647e47de7090f994ecc141e63be98383cc6f128783cc3f8a99ad05e8d15f8a6123494c3c36ff256a6ba71d7dfeafb25addf3879dadaed70883f1e78fb111262ac34698c21936c214e3c146986e646c842987868d30e9d8daca9c6684a9deb011a6b1071b6192f960234c6febe7c761234c331b1ba1964536e276ac61236ddf69f3f98fe86c2a9073ca6e0a89b1ec9e3a29f1add05c14ebadb00b4e500714d3101bfd9b9527ffb137b732886013eded86f3eccd7b1944b0b1ec0de9082a64e8ec0d034a503029b18226ae6c371944b0517b3b52c38730b43c75c9c1e6f4bb4f5a0a6d5e8faf55b79f296663799de7e35bd489aae3f134f63c62ddcb4ce13ccc146da2f65dfc1b1229906fff063ef9379b07e9e76ecbbe86e2fe657fc3ddfe9c8b1c7ccecccddf904821e667fed6317ffbfec614b3f98714eab6573914cdbbfe0675fbab6ee7d89bf81cd8b7f63692f6760404dfc6de68aec6dea8ccdefcade778d81bc7622f636f74caa7702c6d82c9be656fd487a5b4875dd2b1d5677d39e658f5a6e66d605ee4f1dd7a98538beaf657279967395131958dbd895f639938ad7fc6de50705a7f8cbdf138adffb3b7251ddbf7a1bd5128af72a886a2b15df53cc5e3c9441e33cf637c2fe6f523d46d575d280e21c40b47257abe100422480d636ff0b0421925c6f9bee3f44b77770edddddd8180618e12351a86620e3a345dfc10c3d02592e0e038b95d25d205e1be42100b1a1d43590c66b090470c1fc0843de0f020268c3131cee79c58c8030b0732618b0576266cbd80e411be154f65a10f2a7068c287818f9a3096450f9b7044c10dbd0963399b1ea10f236a7c846710118387cb0c8be72d3223e390a9537322dca0508e713e75777797c1096169c4ea87231731950c7d60016343191630ca41f82d8d342714a35e1d842010aeea410802217e10be72086761cb0990d2c2708c560521fcae60fd087d00e1e9842d233a2021521312e37ce7aacb5f1391f07750e37c7777770a24a470305bba13864174e5093d2e6690b053639ceffccc3b406a3a3f40a0cd3ef0a0831c0e2cf9383e7adcd8d4d0c878ccc8c46260785c43f3eada24e1b93eaedbd202b608af5e3dfc3aea125fcf2292f18f34c82212d81f6998ef594412c3748c7d0ecf902305402105264fa618e1926653cf3a4bab95e32d5a9ae6985a9a6696f6d1021391cc2ae66c82b7cc288edc1614de64ea59e3dc0027132ea97d7c1ce793771daa00469c3052654a1b56538caabceaf0c1881025108922c2021339bc3079c2822b8e26225a4c1832c5101550f0d20585145410d7984943b0a0c043029e128ed0c152470a5042488615cfc4a5a053021d223c7610e11942045ca30d9d39549849319bbd0c669f336bd54135f322811c26847264630d1e7cb0caea773f4b4452ddb84e2f483a2994735b767eeb33c1c82dd2a8bbad56cbd69b198ce574fad6b784e8a72212f08477f0c90d15df4d8cf5e54d7973c1564bd602bf7ecb96e07f7fc46364949b5b3ef944245f3512c7752b747363be18cb822c0aef5ba4904c268bc53e161945764b16ae7ffdde1391781f8fecc278304484622808bf450ac5ca5819bb2d4a416e9fd67b6194d82d85aeff4796fede772292d0bd1cb742b10b0b613a53c613bce8077e39dedc502af47e0fb6c872bce00bfcd7d3fea14d03fff5e10c403877c07f3d8fafffc872ca108f4f45242f6b44ef28bbe3f82f925ea3f009d7b748a1f00365b2da7a6f916578c177b20caf0b7cd7d719803a77c0773d8b43e1f56c697443f09d74bd8b485ce183e477757555c70dad5078c50767e822e975d156f97018f9bdfb1669e4d73ba8919d9c2c67cb967f5bb29b1bd9188b8de16d7d19b640f0f35bff48b3482a2db2acb7f54e96357c7f3a03c073a7a4b7f5fe21c93d6bfd1107ed90b7ba67916595a1d92292eeb31e299e403b6a67cce4a00890bf2231f2cf0180ff334aa9fd2029891f1b500308aa314b07738cafcb393594408063ac76ccde185d7074808291950608368ca3173433466f8a668391750508698c2214514fb33ad200c20318a4418307b934965890460c52e377830fc8183d393ec861a46f7840c358d360638c0ed0183b30406862ac5b7800c4c892ea800553d0e2780206682cd102348ca08931d6343ad862a4657460c6c802c3832c63ddc28df15b0108478c314e80f0c4c81ac1075d8c303efcf8800e5acec0b2c6194a5db8d8f2410f638d0c3c9861ac29f062fca2e8e089b1b5841523ab08da0c660c7141b0327e977366fc70671c3043cb9d9fc3050f6a9871cbe761dd3c7f09fe439f7f8afff8b3cdf3cf9cffccd6f31f61c17f68cf38cff6f5fc44fcc7d5e3397cfe09e53f391ffb98e757c17f609e3d781e9f5ff6fc28f88f8fe7afe23f33cfa408822deb05e19bb499fd07dfc177e48d55aaf99bf748cbc1dbffff481cabe475f0b3af24cd2a75f7b407c91cab94f3cfc18b648ffeeeebc3480fac07effd072f439256c9929ff33c481f568926f3f56948259b57f2f15978b9207c4cc7d6c3c7dffc8b54f261affcb02989efe36bbe5563afb86c386f639568fef537f68a8c4de9fb9a97d97cec5b1f432ad9c4fee65d37effa98bde236251eff7a16a97463afd46c4ab0bf717deb29a9e4b25704605302df47cb5eb9b129b5bee66dec95984dc9e6e9c3904a32d55ea1b129d5aff9b657aa4dc9e6fb9d66af4c9b52ec6f5ec65ef16c4aaef7f19dbd02635392f9d7cf904a337bc5c6a6d4bdcd07f64a8e4d49f6aff7e04352c9037ba5655382f1ec95974d297c1fdf81bd42c4a6e47dcd8fa4921525245b8ebdf2814dc9e6fddf5ea1d99466fec5fa7ff227a9c481bdd236a5f16fde8a1292cdda2b03b029bdde87556259a5f19560af04f34ab18ff92bd3e6e3497be5c7a6c4fa1aab54f357a6edadd27ca57e257fa5eee9e3904a3656e9cab4d9bc0fabe4bdd2f74aac9fdf83548af91bab646395645e69e69578bc12cdcbfecab4d55825f095c257125fe9f5ae3fe2209047b94b43b2d3f865e4cc69fc3c48149cc63f4356711abf0c399d9cc61f23abd3f863481ea7f1c3902a380de734fe919c4f4e637e1749c469fc22c9c469fc2139999cc60f9213ca69fc2d52e6347e1679c469fc9564c1694d4ee3f7c819e534fe8e7ca7f153b204a7f13b39c569fc4d4e2d4ecb398d1f691ed1ba12017283fcdc73c738c284e25abf0a5cebf6c058765303317ee397b381184ba6fa619cb7ac138a8b14f10f054dfe8f6a4e8f6a2fcfa45d62087e3b8d51dd62d5afbbbb9b54e02245303f14f47abe4530b6a9b856ebe787d97236bebe9c512b98ba8d858bbcec7f2d8e9a358ddf6539b4207b5354546b4671b5519f8dba1dc5b5fe26b6d591868061b3aa4175db73652dc752e6e4d04bfc21309cf3f54114ea5f41f4881b97f841f44926065121626010751abd20ea43cffa893a5b06512d3deb328836f5accb8f36d1b3fe209aa567fded14fbf7be7bfafe9ef39f98a5514eeb87b1ecb40e46479bcbe572b95cee874659772fb7dd8b7b712d5ab468712d2d56fdaa5aacfa55454949494949b5935454545454543b39393939d1262ba7282b2b2b2bab693535353535f534f5f4f4f4f4549f260e377113377113a70277e12edc85bbf0f0f0f0f0f0f0781dad4d342a2a2a2aaafb69136da24d54ae2ef234e322e00fb59c862068cbefd97ca2962d6f7a36653d9bb19ecdb167734eff30ca25866054944b0cc1a828aa16ab7e54542d56fda8a85aacfa514545b9c4108c8a72d9b28ab61cc3a81b2515e54d2d56edffbca9d56255fb7969b1aa179d5c2e97cbb596afcb75aa5a4719ada202499d5b555555552555252525252535a5a6d3749a4efdd3a98a95959595150a3cc5533c35f33a3a6bafe7d3ea55fa51a6fe80538db2060093d314b79c5d3ab2ec926b531c8173458a3c5abba9fba0f97c51e0daa4f1cf6c62f1cfecd234c50880203272a4b9eeb41276fdc18ea47736f98f6cfc88fa0ecddb3f9b6617ff91c9c6b1cbfdbc26a70d5cf3aad9e7d1ef1ec67276995daeff6c721f6532dad44e1e0c40ce017bee1887986b1f0a5c842f5bb23d307a141929b841e1d77cbf47d13e6590f83639d7df6c797d0fa9f17d60017b9c29987f2e314f7ebf77699f92af626fbfdfc5689f3248e639285dcbd7e780742c3debb7a433f5ac9f243da967fd4f3aae67fd3864fb20dbaa67fd3dc8be21bbaa67fd3664d790eda56773d24cd9e43167a60ce9627817e7e2538e854bf916cf79141ee53476b716b0c5aadddddd44aa8f54d6a19792bdcc5cb3e03fb58ea34ce6e57e2587442691d9fd50277573924d5cebff6c39f3d23ebdb47be9597727f5ace7979ef59ca367fd234c25d336960e8ce5f40282b1e78e916f0a5c643ecf8b4596cb392c926ed95e5860753d4bfbf8f45cd3ed7ce1fc5318e2f926b2a9b8083b55d3d7677debc1ef20c6a27ef7a74ced1334ad479111059628ec68ca65d9a245ca072c9ca684e0f2d43e41dee58a1ed13e412ec6d5d597ebdf3e8c657b89baccb5efcb49e54d5eaebf37799337f984729affa4f3b3448ce564bafe93a9a96758faa7b364e99ff6c187fe692184e89f3ea29be8997f3b8d2591eb445eb6ace0d8126d2963fdadb63af27091cf76d4f765375151f97ca69d37a93a8a8b7cefad6e7a96fd6acbff9494df1677529df15f4ad99ca367d496e3d06ca367fe2328999dd481712289930505a5df7bffd510fcd68b1fbee892c17ebc8979988f199a5f24f3b197b132b11818d8f8728921d862d5cfeb6833d163bbab5dec76a7dd77d8c25f6a3d75dbf676e4bdf72fffa6dd1fdd34adfe47acef64fd47ded3efabff512b46bfffa8be077ef7fd51ebfd975a369817addf7fd4bd0763fdf74720cc78043ee85a6ad9446b0567fcdb52cbe6b256705a7fc40261fe1efda3eefb996a6cafbf3deb8fc0ff7ea9651bad151c97bdb1beeffa47fef4996a6c626841cb2dd6d3ee8fbeaf7f3beabe6b59fab72396ad7f3bf2af7ff43dad479f773beae891b7fd0e01363f2840ea03b8d93c538dcded8dfe6dc9b3f90fe11c7d5bd8dcde965c367fa6191b1d9ab4b3f2ba62aab1f9dface02cb53a6aade0dc8ec49f792baf1a5b4d0c667481b0f075139b81195d60cc0b269b81898d2e500616139b898d30957a3132638f99188ceb8fc21f6f40d6d7fd91f74e440f1b9a97752f93592b38b7a3dab2a1b156706e472d5ad9064db69b9a9918cc08fe51f8aebf2db56c36d60a0e8dccde986a6c3ceceda8f5333198d105b2beee8fc2f7bf1dd59f89c18c2e90e57f14fef7b723fa33b1bf1dc93cccdf8e627efcdb11ec5d7f3b7a3df8b723f1bf3f0a9f35637364ec18b31f636330f6066665a395bdec8dcbc6449bf347a17dd08e2dcb2c1b56fbd9da21f4df8e3c1b760821f41f515b3b84f96e6987d0cf7f6433595220fb73fdaf7cb074bdf83cfeeb19e661cfb18f29737a46c690dfb37e1812468ea4cc458a64488e2d16890433671b20b74f10578d4145b8bae38eeb4135280c1a836241b2a09ba0b7422204e5f8d5d51dd7e71060cf1d2351918f21fe21d60fd51ffa7ec8fba1ee87e80f157d53b7bf0809d16422b6e5b39e8865cb9bfa44d596b2ef893e5bc6bc27f26c39764fd4d932ec1913515b52a9b9fe44fddedc6da9ccdc69735873c600e4f629f2bca2aefba12216ab55148645afd758040313f345ee324533333c8a643f34bf48f643343688686868841186e60fc57ec87f08f643ae1fa20fbe92fa4afa8766dbf2f6a4b1a56cdaf279d872e62626634b8fd952068bb1250ccc9631d768cb97cb9623f8546a405b862d5bb2a8d45cfa546a6e2da9d4dccf96df16d7f3efec10ced5d5d5a5b6a45273dd96df16d7bf34f2ae5b2a33b7ad6cd227c09b1eb3c8b44c33f3f26a6c280f598d89c9843d9b45da8e21172132a2b7fba1d7a532c3e2224446dea53f24c2a512bb5d1122a3befe437ea9c844eff95d9e8db5b80895f07645a8d0db15a102738b506951e94bed4dcf66112ae3eda7e2ba5e848a77dde6f46c1669eb47804c9fc8e87b7eda7d5eebf96b6db15ccf1f82a14b7cfe1136fe2bf6fc31d78589bd917763de68e60ebd2e8fe7978557e689a8d0cbe38de89d79a3d81d12e1da1019c15cd91319b52ecd1351f1aecd1bf5ad792222a3f1fa7822a37a7bfccd1bc9ee905f1c2223d725a2d217c736d7e61b7977be0fd2b936bf0749b936ff86ecb836df8664716d7e0d59b9369f86fcb8365f467a5c9bcf830cb9367f8604b9365f866c716d7e8c7c716dd66a23d7605c83e1da0cd764b816e35a0cd768b826e31a0faedd70cd866b355cabd56af3917c4cb7ea26a9154859b1245585941452725f4460031ee478aafae28a10b0ae48828731368e35a717d4000c28a83881162fae08afc8218771e6724eaa885b8eef6b744edd690f358c327777291bae63b1b9ffbbbbf70da3eb724e0a861b5ece5d41c7ed2ee7aea8aa01581241dd1e21c18f449090107fdb2877fe5123cd1e01acedd34dd6f993e79c64cfb9022e5cb8542e5c64df5812b9a38f7fe3e0e978f6bb9c9e7161755688e77a9ee7d93f03acb4769de75531c6f5aa80010c150b1876f0210926a82742d0c515705cd1e50a29afbb2e2d83c7e58aa4eb793ef4522b1db9e08d29b810e38c2fbcb03117ae250fe74ca29e2d5e688163ea06343eb0851561b0ac30e3035dd7759d155bb07458baa2b5250b261e991552d430bb12b028a55356f4205af154831532b8acd0e1735070cbe7f18cc86d69aa62cb16301a872e72d862820f6879838c2b1b6dc1a53fc271691572e8d04ab1cbb92d465cf0726e0b102ab83296fd749bca7f3c2fb3ca9b534ef3ec0b1eae8b6745c5349ba6962a4f4c3cfef37d7f49a47b1b25df69de5447da34807ef79d7da13eb5b10d9ce1870485044c9bf77d47ffb347ded3ee864403e61b245801ce569ffeedc8fbefab3df2fe85a3cfe25c0c1964c1d9e8d3eefb65fe4343701cd94f651f71e73fd934a07befbb299b06d0f79ebe376d1d3992b643eafe3cc0e33f6de3075f4f77faeb5d4fa442eb59df302caa70ab475a01e20b302f3a6883bd5b2414a6c2bcdcf994eaceffaa5ce6e9cc53d807f3627d507c5004ad4d3fab71534e9b0fd3c5a6adee7c5733b55337b5963bbf2389ba17c1fa300f635f807d7db689301ff331f685ef619e6de0b745d2b18f817dec5954e1fb986f62baf3452d777e8cb4f11ec9167bb02389fa55783debfd895468bdebfd871a70033ffcd737e026fef8ad07a7a802eceb4f5185ef611e85a4fa30e1d787b136f4679eaa386d3e8cc4d9d0e974e7cb58a8182975e7c790365e54eeceef4652e634a2a27987868814a0801f2252c1a601ad7f7dab9f48059b06bc5e10fff56c83f9b648baa70ffbef41d8b3a882f81f0c09236dfa916cf5412a2a806d49ad7f3500fcd6b7a882f8af175508bff52caa30da97b5f1774d89644856929dd6226ddca2c01d42974ada34928d9d763422755d5d0072775227c5643763cc47d9181bc7366698819513d440b5851646884ed45063e3e51c1549b0018eded9efd97432a767b418dc510618531da0428a2c7ea82148a25688e7765dd7f97377871b556429430d2f6164810596ee061afe676b7ccefbcfe83f9ffdb276dd8fb8ebdd6ee9765d47ed1af53f27bc9ca30204b77c9e6e066f94c5a8e8c0951e65e5786354d8304367a9b8b2e44d41b798428c18a618c30a8fcbb929d0b8f4726e0a32b8413abf2fc3eb799e57dff33ccff3bc8fa43da39ef7a3ccfb59fb08f15c5a1b07907368bf0da57d3bb8f38914a07f2ad5cc9dc0eacea992e7ce1c0a7077feecce2a140c714b2377caeeb4bad4fb3ec08ad50c99c21e1083975bdbc7610f88c1a9677cc1c9ddaeb6338e31e672ee04572e4f39317af7a5ce0562022f2935c11b9732d9e8c7fa87da28a5b3ac945213b07129a5fea3db8e83cb39135c71c5cb39134071cbf1697b1726f0c10447c891e3e58cf096cb43faa7daf87352545dd8e59c144997c5fe233ddd18633fffa69c1d19eb99900c9c88c1972b1a8050818d5e71e98fb92a2efd9bfee11c0e8793c1463fa77fd8466de95688675c63e45b8eb7dae6fc26c79813a3783907051cb77eb9e5f828005fcc3e77a6902f403a521b92aee58ea45c73bf8d9c7ba2897291eefb3b2e22c7954d27e9144e97a7e8aaae3f38d4fd34724e055d9e6ec9530eb502dca5560ed573c7d8cf391574f1efa4fce7880aa09ac65153b92e5254d75530d534be65e7e5fa774efe43a7a895ffb49414e788102969f5c68e366e8a8bb42dfa7ea8e8b3fec435ff69c535fff9d4a58318dbce2f5c73ce498971cbee72fd5b88b1ac01e01f87fa1ff6e11f0fa54da8c4a9150cb4899a041d536a06680441400353156030482c168e08048a26d7f80114801098b2545a9fcaf42888814a19430821841802000020002030334c0002449b3da1f71f290cec0d6a2eda91cd9c18032ff0dca5a25b87109f947ed71b47ba08981d9bf2eb8274611b9bb453e3e7ac8da1b052cda092de2ca54eb20d304da0082804cda4a1f3c20b1db431f06a0187aaa837b4efb965f92729070114f4bcea59b995635e6ab964472cf4a36ab4b19709c2ca9de559662a421ffc529d1a7b238b3f2aedbd15738daf14666fd4f24795bdb76256f795d26a6f84fde51b60d7db9446a2d08e90226a9d8d122b1a6cb88ea4185e8ab8e828c0b547bbd13679d2e8eb62008f3df7c003341b2a56a1313c5bee8c133f55ef2b6be3a8f64de1d95bb8527d6f859fb221f71f6bcd8b6c8a9d7e5ae19cbd1d83b6ef5dca812c912fb71f29ccf63e3881fc2ddb5d2cbf8e2013a84318cfc5ea0ddb63bb22e7865cad61f213d625e0059b3965d0d0ba019f1b9c8f463543f237858748ee5ea3cc8ce2e9169db250f69c42c4957ef25f05bf927d337805e7a9c127a68ab304437b4fdf1cec75c4976720c5cf675cbc7c9600104ec31deab1daa6da90ea2e68b2d3fb183b47fada1739a348859ad424dfcd9b5a51a636050bdaa24005bb3e09d32a06af898c3ed46bd7ac2df71250bb69135da4b3230e9a600952a3cc8dcc1ef833daf8af458999fb360a2a0b55ccab6b747098d712665194cb54527b1c476b4e091333379dbd6d06867ef58142c14f115b2b940d25b4d25e5dc424683aff6af940c398b7e6d19b796ea7247d57f6f1b29f5a3042643962d66c46aea38ee900a581e4719715dac6a44d115c8616ed20a5c64ae32713d2162995829cb618dba14a67e654b9a7aa0ad972cd3565b969a3ac543a5ca6a717047923c352c8baaad3acab74ac18f070c0da4cd214b5d89b3bab2805208b9e7870c36ad252ece6816850c9a571009a29569705ebaac0e73f4d13750b574673759a8e009465223fd72f4de9a84d861936f12ed4982c4395d1424c35e51c48136a3bb71e1d79b70b521edddfdd699a24380f1faa9b9e956b7b3d41859a768f66a9d89e720e4ef1c4423e57ef896749b13efdcf6ce9e1356c06ce10e06fa93e1305e565f67da0669b831e088c05fa2f19bc2ebf67657f7d691c9ce87d1f5761c8bbe256fe5cb3f027af104fab7322ded5187c0c5e42ee42ce8a270b5b252e3f050235054a7b7bf28ab5bea6c6b4f4964b56e2cb65ff8d2cea2a8a4b3f71ed05c0442127b2aa9aa3d5949d533991365698308826d8568c9a443f916060611e49de48596136deddaaca8651c4e1c762906a01a1b40da9f7888ada2ba960e322b592daa3de0455ba1582813a54d77f8d896cf131506088a9bba9f2e218a5a46f5ec66a4faf1697b1753c30fa2d759189369d38b8d6059246990ab474780e435d3f1772b935304f056102dd56386b9372ef6b781125778254d44995bab3eda025a1b15072bad43cc1afd5682b16ce25195ebae8309c013ed5bbfaa9c7820349259df8681249971dfd2c01ddc3db884b424f3a0126be38a999dec88aa3d05b904b4bd83d2ee6043c1d394cfae52f77a340521e074963371f336152fe576a72774a16cc67fe0188393e5257b75f154c9409184dc7882e8e76c44c617f8bbe191a9eafe4d9243cae15e21df29d5863ca452ef3245a288ba66b90922243c5425aba5c8c845af81354dfb276445e7638df27521ac547dbc3cba9a395ba982649ee347c062f49167dbeb91b7d93f4daeadffcdf3b893749e28ea96747a2251dd12dcddf32d81258ac5e1626fd71cad511914d5d789326ef8c6c39e122d373d120c1974946253fc4bfb63eed5e467fba65f46440959dd836ea6d27bc4a21e599bf34788ad8bd92bc90f8902deffc3049c8b70c9de0629f23c561da895c73531c7471978f3e349ec1402aaeb5a6420bfec49334613ba38eb3267466eb8ce8d59adf4b3e7fa89e2274c0b2c1d5e34047f6fa091d695fe9d1064ba530cecb62fcef245c4b8f43d09f1cfb284d2456bb7aacc819ca1888b7dbe2ca0ff83c8994bef1da1806f9c27a7ebb01f4066e489df2076af97793f05fc779fded8cc8857e1fa2017d627eeefda020eaf90d931dea0bf2ef799e2b75c10b0f8c339059b86124a8db6a81dc435fb43b9890082202d636438c8a86aba073f75df8e3cb00f25ec6f0720a6cd342f71ee185cf3ecc02d4534e7487cf64be6feb6ec18d340400426452f811407ee619e2f0a440bf0d20f1252a42c6506345663530c1585405f964c212073a6ff8b86b0d61a2ae9c0511f5e57ec7bb433e6514688a56697d15b4c70ba4dff9ab8ba44f7a83dc758e4cf30c54e781d89513d83d6c9915014b882ac53f346c1c4b54bdb424b3db0688c95498cb9cd1f3912197cad54afadd7992b8f40e38baf374eb32a5e899740e46de3a43a8f39bfe7c9f77fb734e886a2212d4c175032075d54e4f44663a096901c81b5d71042a0f83841fc18dcfbda063064b2b09936097c6d75db9116b120847d2f7e01baca50e9ee5276548f69e0b460954143ed52ace21ac28e0c83f9e3d68202df581b25a558ab3dea6f7903571f32706ea6f46e7d1e53f6d15283d8ec329093f492b9eb51e5911020f4c6275593de84cc4fa54886d513dfec010bf4ac8111316299d70238b13cb64835f4849c7656d2e0a85cbaf474a1b9ef97cc84dbcad93e7bdf19c407ddfbbdfcb660f84cb303a705bea1c8ca65fb7955df5a2799b464b5499d4ffb010399475a9bf24a1a5aa03ef2c7dd2742b2336842a1b2c0ad87730a7c4ca3f80e15e3855f446d650449477629f313c4f6fb4a4217a4a3923e2f24408ec1d306fe55d3ce2f2f74536516422b51e0064cdd56b59be5baedd1e0d3f181055d0133f7cc1fe93eaa5410f74a11b148f70902a9f4e2676a6b4ac09fe8adc5f2a4f92aa5b8eda56735b937214772fda15e585d2c2a28777e3e755039e14322c5e88726dd471b446e35431e4a16d93141a72c3cc5fc1e0ce7731033d8ed114ad8b28fb8134a6a282c1fc20b6c5bd8caf1dc90d42bb6a4b01633d71b7b117faef2ff0a21c6a04c3e80736d1bbe6e843733cfd43091f91d2457f54f5355960682c21079aa9029fd89d215e307f6a4302057a57c42e62e034e52adf29c32f54f2d3d99d80053b9635ef497c564f733fd658c2b2819f189c57e9ab1ca23f1f2edf474eabbaa0371d966c29c6a30ec7cf09e866b7f88dddf0491ded82d9750976336d482c070633bb3b74b3fb1bd64564faff999dc059947cee8bc9d7815955e5e7a395c65a9988e184cfa2aba11d7a67263257d80c86466de8f6608bbe5ce27fe78cec69c143a2f2fa3064b76765d5c5d29e4f467b4d60793bfa8711d61bcd37bd612e548d115f7e3601fb39192e1fc068ec54b4eb8c9bfbc03f0d32c346d587193b8ea03f89d7c3b0388800ce66685019b0302d438b54cda15e038f79fc73f15bba5e4eefa3854b37d6ed5b9871a4c68d574525fd6e5007eac2a7ab425f0f09b43d6b8bb407bdd007d57dfec15cc52977cc66dbd6aad4cf6ebb25c7c66c7477729dbd1e4102b4911dfc436e9c29b3aad343aa41ef334246749f382e62d8d479cac350015db653088a4e93b24be39fa1beb8e1add7776c9826d28c4e8a96d554e4e62560277dd21e413f73e700c1b10e90a442a210b5c402d206d083c21b9b9a166ce94ac1f82a51040b1f3125afd009698a7b3faa49f4758298e68b352fbaf599bcece122f88bb819e604cc23088b88bd725c06333d184a2fe4285c4adb755ee942c78e0dffe0ca705cd465f6cf41406053e51ba9f5d14894bc014deaee964462db16cd2b60296041e8c4028d9db38eb6acb9a1451d6f72e963f8a96bb4ca7913b7e16a174162118e1d94dcb0593f660ee859b9736261b26643ce67ff9f7cb76152f0f222db9042a421a9d4b642938423414faca9aafca9ac52a4e42fefe1fcb3a9769c4daabe989c05ddc21873df5c3296475701c66f8e8e05e3e4626b0bb90c5c81913b61afcfa5bed9f78b5d667473e9752ece61a6b78bbdc0c0f572d765d9e2566c5112dffe7a7967743bb8568337ee2c091e2cd153c350a1ef642ca8e59a70b18be35a73b227334de8796aa5a5f908ce82566553f0758566843c57684a09a19e832946a089080e73ec42c0e15dffa5d3fc3a22ed6eee702894ef04c694441d3882bab5e3b19b77bb39b9134f94c88af0dacdf39e785c32ce9e98219758ee795b329c4a3283c89609c765679600e120e0da74e43590531452a8370f7acdc890af227ef833ed3f89dd90c8c4212c55c15753ebe2d2d9f8d70034966d14f77a869443d5e7dd4f25387ce285d9fe78cffb02122ba74e6ec3d7316672bde6f746497f635c08deb205c8fba9babffdf05937e10c0ce4b7c43971ab6ccd20005665d40581912a831504cc53d99e2078a2609b21aa271506b5435301ef04c6d6457862dfae833c81651b0f1ba9dcc52a791f2aff85393f5d68a7855ab398698775e08463158fb97bbf52eb4ee24fac01c1a5354f3d257ff4745dd952081fbdd08ea83dcd92f6ae1863a07dd78ba04d4290fed64b1c6014814cd81c90ec593fd8fc8c9e66b6c4284518ca65711cdf049454e5a59a16f34e93c1fd23c2445da0b891957c66f9b7466cbc1fc32b0a382351bc5b631b89834eddb2a8d35d2c4f2261733aaadb940cfacc87c0403e7d8ba238d3f1314c24f4941115f54a5041b2fc58816ae3864ec399039a91f8502577fc5e9918a5c0e752ab49da983b4b752de08a80bf1d76cba9f556393aa8ee96a2d9d76f3a8fec390782b9205334370d4f25d0f29510e2f471c6684044fa496ac299c91e9cf4dad132aba8437a109d4b188b6381dccd4af3d725174dbbca4ba552a85440ef9cd9a2a2ce1b0658827443de4eb17e69e3ebd75c3eacec4710b3ee4c188d391abb468db4fce44553bfd415f77672ba125a64755f54e99547dc8ca391131c80eb8b647d9f2b18d07b17d015be5927b2a40a66bbdc312e16731e49afc6efadd90b887593c8f0b8ab7c48a8837d2918e7b9b2ce4b12cc5211481b78b474713c641762d99a8aa0aa5fb258a62a393fb6320e78f168251db5e5b57aeb0d02ea9adcc80d00245fc80b9b1000f99ebffebcd30bdfd3fe403aa53a590a57f94ebb7d4511a1e9e0442465b695f028e6eba26cab08cce4bd653d01a2494db4487a3dcdc294f84dc59c341ce6f57c976863b57e1c376cd23fea5c09ae5ede3444c64404597f3f3700f12dc40fdbfcc89cb974adddc12545701f469326f207c133d0e620693202b01a031c8f102835953a465db484773104294f553ce22e39a560466b228950fe8594d689cadaff46a6b27f4e6e6b26313ac6365b1a27268a86e41bdb58d392abd83920819849a02631c279e96adda9889d39e3be786535a4ead7082aa2641d98820623ef3a77bcccebe6adc609d7d2eafda7efdb410b95844a8542b575d27ccda880f4c430f8c40f9ca179b09fff92163c85214f868e9a91fe9dcea6fdc336d507b73aaedbf3275509a8ddea008f3a04e5847b6ae6b006c7b4655231cc72021e81cb57cae946139c848cf14f3eac4f842b16b066d8e2354def71ae1498e3b6dac3375d7f1653a41de6187acfef508754220543135350ca52ba889eabb99aed267c66d7a7627acbe02a279711a81bbebaf5eae1c9133973f49e66f9c6835bea8ee90033c0dcb5a3dea5ba9d7c3a65c454448d6eda84c84ecd37120b0d8e18f3bf1491ae04dc9fb8a980d92486aaa7ff1795f166f6ce68079ac865491a2b63250b5eb7728acfc0968cf9f753ed242c871a6a4d98c43393c93d281ecf57fe38bfbe7df0bbf3f5cdd764dc7cffb471273a2c76a9810446a1c374f97bc526978649b85703a35e6a6e02429332ac0d99f08a41bbea26920312ce7a72d4ad4bcfb2682e1d1de0174d36aa0d34abd484d402463d7916b493b31c135971183a4214ae08f2ef4c8d5b16e2ff523225e1d5e2c263d71fa0b1e0e2b9739fbc53e46261cd3e6f7430c2baceab7dacfa6e80f3c78406efc56e47a642c4de674c8b5d7cd35161e98669cd281daea8be38ce44a3a790012e4e78f2da5c1c0ac58c0340db48d6e7a1e13255ead70dd2e4a27c4d3af4bb7a517290d679628884204515a079c8ae88ecb4681903a942059fb88880ddcb8d47415132e20251249bd05aa612adaabd5cc0832e680c9faef141a7ee4abcf6c5613159737bc49cd19fe629a5f1be80b35cf8c79d78d591a96cb121569ae5c61df116e892e4090bded4b1b15628c1f85e2831f5414c8d0aadd23e8950c7d3694885a57ace181a0bd4b93dd6742033b4b982c5cce4c66586c3f3b1f682a9ef939a64b197e260b6c5edfc670847719916f30c7127d7b410344119fa2c0af3f72066cae9a77af860fb5ebbce040579efc9174301ef5ced5da526b5f20689a7905da5c733fcb7f638ffe874cc977a9d16eabcfd6f82734be5bac2249c28ebcc59e54a2bad5c69e546c0080ed310755e92eaa5a74d9537f8921efbc44ac80fa2a7775c46ac323495bf245eec0a02e64b99e2e6f0acc6f7a2625bb6d81124e2f7c857d09d08c9f065f0d70d78e3aabf2c8fe93268e87915e7121768a0c5dae4a80b502002d86d462394dfb084145326330cf6ed07196d69bd5ce3dfbbfbc629e78ff792c4071317a6dd87cd79fab4f436a6ec909a98233ddb29e058b802a16b4c66d537bac5c8aad17412ae3d0c3f3a4f663dabb21d51b393a96c23e41cf7acb5d10044a7e8146bd97e9f43dba6bfb67fefc076d7826dbf2fc01672e0762c4708c5cb2088307d946e52d7cb124cf662a0cb003702adaf04c0345175b811ec78bcd83989788f4e1b4389789fe4dda5ee4d74da8d54fbde74852312ea0c790ec4ce2a98a419d4b735def0dc3f20e6c0177c1f676532c82487b1d5648541255404d0cab3c6bbb9483628e098e6e823a18406276b89152fddabe3ab79a3fb063e06277a710a31197a50d815da9ba850d8633da651e85bf2945d10f58d559a9eb2790fdfb65c841e04bd5afc866cfe55bce1dd1738d1d2526e04e7fd7e59e1a35302c3511cf560c8fbfd080e1da8b05137ebb0e1ee3afc97bf49fc18031abc78bb60536b59514be897565860f2b22db46d52daedf17e0476a962ca623042a2a79ef79b8f2815258014be8610c01b17c89c7a5111ec4bbde5e67255f7ff18b4ef6a23a5e2be22186c1256b5b985809773e3ed2de021e8354add2140e28e298e31c7b19feb0c3fe768aa8c33f9ff2c6895f2fc12c01713c7147e1ed5238b6f429e70f6dd475e5f2f26645005e23ca1bc15f2872c9f985331550a198863031a98da57103656a596bbec59350157ea33a60d0428a78d711bd6a807d49371cc728c1abfb8a6aae18c34d0b2b36fc0533f4c929d6d01572763904996ed490ff3f41c37a44fa6eb8993050330e71b5463223bd8b53788cf6f648d7d2db560290c1051e6450723fb978a0c213f0bd1af931561c271da4881e8397c02f79b1a75b67db28d6f6f20051bdda420fd7c4612b20a3cbf9778f96301c4a70981940a072d2f16feae28627b44a64981e84bd589c105d1406eacd04b583fc2d78b2162e5c2b6b32026ca2794d38464d52211b47f2101ec587dc58e470e0b8bcedc70e31f5e02e2ae12d31f99fbe766657e943ccabf125d627343edde9df62e9de6a5b5838121e3f9fcb21d66a893986b295dbb78e95dca72a153110bcbd2efca9f860203b717b06e1b2a2640dbd133a3c80d3b7f474f176898ceb2b2772e0dab1f460367f61d11795fa715b91d3f17e507b47031d7a432bc7861d1f45db58651491bd0fd197a30a51ffff9fa2ccf40e934aeb4dcfbef2fb76d4e74c7233840be7bb858202832b39cc70328959c10045cbb234e06a2c1281acac44e7a2f84c7f7fa30f4308bbd045612a72f1377d55dae2ba29ef36bc95c195437c1a2cc4313c5528f6790c9120f38157128e41cbf10406a72b85e0507b7e1228a3f2a80e6b85d29554fb1872046ed90deb2d0e170023928856dc52f3671e60d928cf63edf42886fd2d4bcb17359f59a07ce5048edf58f6fd55965d770220ce1fafa445a0c11ddc30c271540d3f8135d170bdba4ded555d8ad4b3534296f2b0a762da4bea83d60d89373d5c742bb07c4cfb76c0b823edb23ecf1871ed27300461a3d38e360ee80d2e338fca6ad682b06481e8e83888d758f9c4f37764f83d8ac023944ead7e0a5664559e8bab6451b120d42088dce7ada71cf4a1c3bed6fcb48193d39482b288abba24643bcec7442d5759e9b3094c52dd1a1390a166713d6a0a37b3c7350481b187e25d71cbfc1feafcda1eb22b5d8730dedcf9be91adb6ecb16144a25631a496c5c66ebcee41d00b4640003eb9b1d394f5f8ee79736e2929ce3791b1cc3df155e6dd80b2bc7f91ad13c1997874e15ac12488acc3f4b56d50582f0423087562b03c5e14cab16d17910cd795ed4c93e496aec1c8e42b259af81988b6fa1b51a5e488049e14d63290d30463343ae3cf120486af08f1c0f37fdceaa01ae14858b4a7b33a1b33c1a12588bd226edc4b4b9e1950f63c6c1b91ed74cdcf08498cac054231dfda990740021df3369b6b917e083f70bd4650c5f4debbf50c304c2c02920f1ff289abb20c24c23f22a7fd0d09b3913960cfa389173cb50b1f33181fd6abcfb34618c82a073353a14dae776fe127aa44a747296904b4dc0cbb7d2462e042552d0f9a7770e103a780df978a841f8cd6e4d18a91eba4e27d15e4e2bbb5fd1ce94b332fc4737a572399f759f2d2de70d92bbdbf391252b54d4f9d2a85d6aa832c5ac0f259bb6e013ba2f6d387fdefbe287834c341e91eaa162ea5470ea969e6aeacab5edc457b0bfcf526f0be7d998d3acb54876ee31fe93b885ca08e03fd1101a9f6283b664cd8b66f7ae81af8c330f168269a3a00a2c0835be103c2365db06607e1b80df50733be2f24002d1af85080efdf2891ce24b98475708d11c44a9ada84e8ec23e5b6540f76a277df5beb0078ff182e145082c0b48e70b745ad2651bbb70bc8e4a44da8bfd261341f8b1b9f0eff3f5bf8f4cecb0fe8637b6414d0fe7dede56801b0c153b3bc732fe6905419785c47e72f5c74dca782f222d82c987d84d46c3f83849577f2f1312d9f7c43231455d231806b26dfb51d8ba02fa1be8a6899e3eccb2f5296a1ce3657f20cd4173ab6d5c6642f29621cbff6f5f3ffef84151fb5da5bee1f5de807e861ef139f4b5d0c8eb07ada9136931878d4dbab9de4a8a783a8672f17e87d089e3d744b63154270cfa78a78fe1cfa704129f5fa478885f56fe8e6cd20e377c600fdf148f36798aa837e0bc17ac305960771d14666f739edab5822869e9f5131173b050a24d269c7bc12eef2af6cfe83165b10677ec60f740aa0e14e8edfa28e6412999eac68e15de1d1fff9b82492156030b289ba7836893213fde4d4ca4d6489c2133a8332edc8f8e483452c05ab127846b65fc5930b858c4f6fd0adc85e18fc675aeafc65d9b9ced088ef36a8602b422833128d4263be2cf94612d8b6809da3fc68f4611417b55ea332ec007171df88497c2d16f589402e30bb79ed859d31d9d83f6904bff7f2431e7120b7517bd94dc57fe1ccc3965094d4c8e8be8a2e5481b575cc5ec4f76122c0318b34f5ea50322def9c366a9a9956b9089d7326c330655b82edbad92b26b29f2fd87ab40b833cdcc711dfc62f9cd7bf9eb0738d4cbeeeec7ddd4b6015639ebc1f7d1c01f716642d54edbb40c3a5c5aa960a7dbe6a063966885fa62d49b28ea00681281ccdce548ebfcbf26e751f1b299a8dc3c21023195d54d3347bd3ce61a69da8605f89c6d161737efe6b5e527544cdf3827f3e62ebf33a7d7520031b79f3b8967ff40822c9467bda6d2bf3c06b0e5b42791c3b04db66ba7aa48f10424ed7cb763ab0cffab6af5e679af3183869c74b4e2f63d2ee48f26712972c2d4e1e4849dc4c75f7a3100f5a165f5e15697dc70a5a3ce1f9d3998e8ee3e110c87ef83a5fbc2a20f64abcebe87293ac719fe5b20ac2afcfa7dd558e9be395d0d972a074512b12391d7a856e1eab046b1542cf803f928923f0350a1d76efe4800c91d65eb9659b051104f7163bedae3d2157fade540eccb48e49522780ed91c88b4882f1e6d5a9e4e956859ef320223ea68a57c7bb565981bb56086007e70335f42c08ff6d1ce6a24850a91251ccf43e9b9617100fee0f149ba9b3bbc944eb403286996ff9774228e77b6f9fcec469e0a3704fee9f7077cbef9c3e93117f8cbd5d915b3c53ad34a76cc7decdc8fce33cb69fd1a3db3d11f9963ec58c302e7288290b5a3614a0be453e048ac50974dcbcf9dbae46a11a2d01ba9d9493d386007defb4108f7504d77601c4925a6daad1c5363942e014ef97b2e7dc4a6bde641f4abcd86c5c178ccae69e39aed0f09980653a7d598a67d87587c06110e005a754854af55ef79fdf2034e5efba7bcfff842e16cb8ad4f524331a30ed6ca4b7d121bc637b804f90307a5c9175644db5e0917add4308549dbd5e51a4b45a208df16be7b50dd54e58545da5ec79bb82174614fea40bba4e5433357ff87c143611e007b7ed55347838820b5364dfd7de952e6fd5bfb344420f8ecf3370f611f1b8ca86c9f1dfc5151f5c3b3de30d55ec60359d2292e926c6a7595f5dbc7300730539f47274c2ffe43306cf8ee6bb2b18693b1dc4095bda26f3360a7d247ec03d03693849c8e3f91a802bad6cc4a9a4620f93c0d3e1a4c744c6cccc1d7a9e955431c43649497d61f3d162d53379f79cee4cbe6bcdc2d61751f09612519cae44ba28c9d9c481806c777a2070465aa43dc7b50ad106e6dadf39cd189d95d647b22a8ca6d931d7cd4da0bf9fe28cc59e705ee3a7e3368e5df8e746a0540ea25c8e769f6dbe1d9b5ece225b7c796066500bf6ebc057520700e429d65a4bf05bd72cead9948242ae7131f21941a627a08c6ee5635749207777ec455eefce4c13a8e956b4509c4a88bb6d7190c73399899730719b5c97974cb960cb578e885a1e3d1e1099dd25a1565d2e3cbce28987d10a8a7d9125c41f0163c50d7bb3bc7eb4a9bec9cc4facbcc08e202dd50f820bd081f446fe007d35b7c60d01b1fa136a46e92cf79aefff226dc939b539589da8912f1a6b4481dfdccad4b23ee88ac7312ca70d9b3f2073aa1a579d8fe7573bc7fa2bef54c696c6fdf7e78d4519e4c70c0c24eb83497faf40dad191c6e70faa3e93ef1d4c0b9eb43ec8716fdaa9ffe9f2dd04c99625a72e36f9ac234b0b681f406702964653482d682f1f31fef22bf1fbfcc0abb32da30bc7887cd6e1080f2d610fdc3c070a05da2ff98797c02832842b37a31486af908247de748b1b685700e2429d718f46ca2b34d19be94d8182562464ef17dbbf66dc9a516a2ef0715879561d631f86bad67db5a330f5d3143db4864349b4e5e439e6a145353bfca7928f2feace6eb5e4f935d923728bcfb8b800028ef0487a83fbe1b680b9a91fbc49d1847f4b0bc6d1d810bd1501affba3eb024258d5b681c5257f7bcf598307101af3bb497916507da43ced9205c51360fdcfa0ec6f9c3d6907a49c3944942ed6b1403321c3f47189122d85e842c1bfea28ac0f786ea654cfca4badd50bc7521af9093e0bb0ce3af8d27af12451912a7e03f9ba0884a04a6f63c436d3e690703b53f8016f8b6c884824a342b9d4bd811eaa40f474e30e1c54b7ce52f91e860f07616aac89882820a69da2f20aa2f5a7e48b6ae869c82ad837cac55af7c9e5a685474730c7618be629379d8a54b4bf5d6354c2dde2d975fc749a699de5bcf32dfe13e06054df534d6608db699e91390cb95e39778d8f185a045c9a6ded78cbb8bebb23c9a1657a9b4865c78a40a224b6ca1b7e087d0ad7113b3ea9dcbf4e5c377949d01abf2c32bba86950ca4d43aa3e271cbfa86608830fed8589fd5f57021ecbd1e54da6438a2454ef04a642a9fd42f4fab6590527517b7dd68956e86bbe299359914199efaf4397ae386da63f6c14e4a36c81f054218576a384b5108b23cbe4cac271234053799dacbd91adcd74df345dce2345c3a9707059cba4dca3537d37ee0ea4cec7a8fe6a79446e06814c687a9c68818f439d39aaeaebcd3e2cfe75774397f32430b66bd3877911495c0b90ec1f01cb2451211e8fac316c1084aca550cac2d1cf43f788f935a21c0a17b13f88290c543e6931996c1e25e5f1cfd818d68f1d718a14012873a7568e4a9c3ace3871dc91a530c3f8b3c08a115bbc421eedf719a144536195e12bdd800777f408e6e9aaf1d3b1b0bb87ffc3c94e084a0819bd9d3abf590030552b44ce0fafb924615dfb78247e8c7fee88ab90e7e2fb0f2f3d0369979b5e7fa727d6eb82cbceccbe6c2cfd363a489149a724e02405120bf626e544ca0fc97abcdba3054a11e20cee642794a24f5032853cbea21072372b64424a011e0df224280a169000737dc72ca147dfb1449e13e8e677e24480e70a695a9813dcdc5b1e3b94335331815de9e233d6bb925f0d7d308a3643d99d31fb370823cab57677ea5ad136b1b45c389b3894ae5f5d2f3b31d2ebb1e1d08906819b867c13fbe7975824bee474b0ed6c4081ae26e36685abb04e98ba00838ccb34b2f802a02014695e898ded7a225c4104411f9f92120a301c48795bcf49412e8bdce70dbf5198df4dc5113a9d52dc7ba1289e4b06191ae36940f6b13dfc92f6f18f669e92e0c3d5136c18859daa4a2557298b148187e29d8705f06809d260a3e5f676e2316ab31df76b55d641610ff61c97b823ed61e45fb25b89ad18d96d9168c1be55674a901cd7174e0498f1a1959721c12bf32c59a6d1b0b7acc96e006f714aaf451669379fb9637e82057cb1e8ba710c43ed117efddc17d4b67ecbec195561f8d195d0b51a654142b29848324853cc777a8fc1a561382e3e232004670bb3cd56762ae8341344618d4d47208f9ae5cc52718b08f889da9a97e06a80f700af8dcf73618428cb7379e7c5b5052b59918b751a4f32224c61a66047a9d609ef5cf7252744b2127b7b0dcd7f4d8d13419201b06df235b024967dc87a922b4aac44fd1ef94ca98805203aced89ba43710323d67c2ffd03dc483c224fe43dbadfcce5aea24ad2bcede482b31e9ab7a13b324162d56635f5aebac9329954e1d1636841199d58b8b0842b667111739611d939f35196b8329c0015252758e78be56e19c3f1d6e7b73d4973a07edee1c8f31d9f1d20c23403b2a69d1afbc501a68c5d04a537f5b6f7f7d8170edf8dfa4ea72f16891514b5d8cbb41345bf16564761d5c89ee29274db5a9a08350f6a2952bd1a9f8631b85a59ce5d056e32625c6c658d19dbc973c83bf165489269f42f7c502bd3c52ebad09cd44b8ab5e1762a868c442724e615c2e6aabeaa3e46c1ab7e6104a9f8312b55e914184f51eface583add07f9da6228d3b514c31ddbaaca1e2c5399ea3000df3342ae2ce01dfebf17217421e038494c4b3f8a52cb91b36c77299fa336043a349a2a5f5d4896a244afe4726e94f2f1cd02e89a8cc400eca22979c08e8fbee7aa0ead455ad470eec33fc85211e56fca0e18f50dbb16823a543c0268c73bdc87d7a13ad4465e7599845368875b45e64e77f9ac29910108941c4df04224bee7e0af26808da05b6f30a0d558ff366302d5b9441918617386790e390d43dd8972b5025c6c003aea8906482493b7eb6987e1c89e66b4cf8d66a6432f225636563d47896aa06320b538703bd7233d2d1529cbe7c7ee2dd4423412b1455299adba9e05be87f440f5e14012d7e44c0a70f243ea516758995fb4641dad229c4b2df55f4ed2569830fb84e55ddc040e7687690240734c5bc05981b9d3ac122aa490e35b08dcf2cb5ca0f9c3affeeca04cbdbedc41302e1f76e456709531f609d1747f497efa36735819d20bb73efffac7e9eff52b0b59e4d3378f92827dfd5044f3097f927bcc27172a13e535873bc7ee971f36e2991b3e5285ca6d09c9b166b6b2383a7107018ab6b541699593207c7925a0c5050de446612391dac6b634082cad3711cadd66844153421af90ca6ec7a352020316d835c08861f77717622817ab926a6137490c2df60c003e46831b23ed3539f988fd966b029475e30ccfdedaa30e92e76fdaf4b1ae25e58bc6623d7b0929aaeac3d7481d9575a46aeda762796905a64186caeb0bd257d996e9fe03c602b168926d04f5b3968ef45f001c25311619ac1114e23fd8b1f382abdc0b49754ab15076aff23f4159bd0fe8d8546f24b812f542c1fab63a0b775563536c8fab6282a5450a59ba64521d033718dbbd107e388c2a55fd0e4711900eb7ffb18f10f7f17ec0bc10d484c1f998edbfa3218494f575857949868393238dd66bb50341c84409381a5c5858a3090a9a5e3b58533aaaedaec495607d3fc465dbb027d826ee187dc2d706a0e058902d099e6bc132daee3c5c5e197d20835cc6942e849f9fd7a20cb3c51f707a9caf9af5c11c19bb3a83385cc47d78e1191d231f8ab3d1d9e783c7a17147e14f2e8cafd98a4cf8f1f9ffe7b1de3e8fd2385fe6e16743bd53565ba155d8b4f40320b9581ece0b38edf24bdebc6ba75c1fcf395c07a846cdd367357e89fe81b277ba6c89bf63ea5ca94e4784b72e43615990f0c0d770aad51c735ee622927c9ac3672eb2a61cb3aceea79f601296b9ee528c6354265d92cf2e03311e97acfc850799408287c1e6ea83720320c2a0e42edff504fcd6828da11b30e5fcaa543794f068cbf16518f78073053f5056c69896d91d70b7f471f180938f2178d0d091153276065fb61fe168fec0a4768f2e52ec14dc5c6281026d6bca4897d4a04f074a38b2710160171b4d2dd41e0245ef7da941f11e1c2d79d6ca006d018b395bfc52b92cb4473910a29ce103749da71e5f1feb322497717a0000ecda2e68e412d0ee340f70aab402bc7fabf24c05ecfed15496fd39a6b658b6f098bce75a34662085a725f364e6fc72b38368ca87693e2ee25a06cab2f13c10633ad73b4610a0edb78bab3efc06f38b29d3540aa3f72bc90efa1e3bf52e0bcd928af40844e9cc78b7df1e7d01f822e608089c4007f1578090ec3b3c1e9b25b89c2b07d48d6c7c7fdb0dc4b3899a32c7456fd13a8082b44dd67413911bf6bc98e8502520b8b90edadeca68de6641375b4cb4535fa9283490fc18de9d4df6e03e9b66b13103cc9ba7d5f3536e4e8814b5d70c2f01c7fb69161128cd4aff928ec7a4ab49e6bab1c7fe7927dc7adda2a08f0d1c37391d73346781c2aefa346499ea2e5d2ede2108fc44a03a8c38166dd0c4a95162db39cd9b834c7d804c4d3ab9eb3635502deab599d5bfb95d72b59c90614a5b0d74eaf9cdfd00982b5aeaeba9ccee837140dac541f5d0f80381e1a6a9730c4f8f7e8b592c539f96e73b7516b998db9b6940b65d5a0f0221eda939af58f93e7d77a24b06c49ff53589f8342ac16f6fc94ff29dfb4a585afee9ea933122e8981223bf3efd4af410088ffca42ee334992d706f527a8d8f9989af067e5a9a2cb1dcf0bea18232260654431dd51726d2483c29add94c217d05bb8f8df072f3290689d49212f3cea2fbc337c2c0a9812ee72cfb1d7acb55f9c35d61e5443e6aff8c447252f4aa7a1c1a61c06ecdca76b8edf64881827726e0601e59b5fb844b99f7ada63a13627730e93ec90310a41ee5c744b0c1870a1844a91155f0f08c4e160f9e8ba0a0decef6d6ced168987d707bfbb50e03073924d44e4238f11b1b9002bcf11b746dce92af119f877e66bce32ede73a19bab999413994040d76bc561c893cec7801149659b8ef301d6256931b573ebcb4b5ce0df690b72f8c242a9586967dfecfd553b34ac214c3dae889f4ff465a916d8849cb108a4dc1bb46d2e4753dc85642762e9d4283287ba83db9657a2e62004f4b0d620a89cb1a8784b54e34aad099ebc1ad7da627aba9597158639a5bfc526e68674a278d7b0587598890192478083dc5015f28582c8c423d6b7df57e2c03e92f99a19231ca14db9243bf11a8bab0a0e52b3bd1d4669e67461f34c00dfd0c48617bd01416c725384c3ab741c7f9c3b6da7fd521d4298563ef8353d0496a901838de4541b4559cbd589b083a13a5fb626ea4c8c042f89535ce839ab143ec88a50e3db04b581c7278f551a8a0c88f2dcc588482abcca00ab84d498b8fe29b401bbbbfa42a4cc226c1723dd88090ae5b22ba8917b5a060f2238fb5182ee2d41fd51783b1ce5e08f16626f5150a104e8d63eb005c41916c3b78ff58b8c4eba0dbf7ccec9aa6d2ccc3187a3a3c3e57243b0440e80824362fcfce297ae40bf0dffc84b19e9a44a14ae41cb024b9744ab18ede9e9e2cac6836fdb89bfc895a99275fac83c1baa87f0efc48166f755a68405144199f467d9e57b559937a996ae5a5f18f8295ee7db7a98a459850aeed35e90a245a0c6575de32d8eb0a2a7de98741e77e219d80119beefd82086712390ec8a2a37e78aa88704aa86a250c2f91d85cfafa9debbb80f82eb54509b6bbbe00e6225374e4a47fb5554c833ca88ba7eec656e9a5a403f7c0843687d500d2e66659f5ac6e26a922e047d0d0c9e25d47b4e48879a0caef173b279536eeea391eaacc1c095d2bce4f5e1a700839dd29cff2c1a158d13d28883594972f14f4bc943dbf18c1486a472445615dbf93f665fd7422e5b5382f6b9d148d0608df5f9209313c53139c979a50c6ad89841cf08101b2c6a84b18fab12f08fbfc2ca794c2c51817d67f36a01b56cb36ad8911d2fe033cc1359926ab888be8b04e6456ecd63805422364c752783210f6ea0dbfbf1607c35e9d40945be8d54d4d7fc90e7571e20cc7f8bda3b6547dd170a3e917606cea48db513251ec0129194d1342637a7ba2387ed867bfe195e002436a2f8f93fd2e79ed9eec513d79d2a185daa39101982a301e09c7c1c225ac2a62d9a3fb42db993f9a3f774e321c6843008dd08f367d3ee56f0543be85e04438800b0a64513803b6ab4a312663f544386e738e6f443e3fc04d0a4f81269f1ce79121e9f9466cad5c5eb475f26834f72cf718638528c3e086107f32ae58b8e29b3c9b8583796e9c240c0faf9435b770d89b007d2397b2746a1d9851f1b9c9b59efeb06afb1620d64dd67687b5745721b673c94adad2a4404c602bddbb2d23658d969318f449527b0f128cac9931845bfc300420011f792e93535fd3ea2126caf2c6b864cbb6bab4d30214e246464078127706dea43bd1db23d6563ced164200c029ecdd9e3330ef7d76092a4c2806c348170dcfdc08625bcb20cdcc4ad32ad2d289bf6af7d119037f605b5afbf130a6092e072dce1beb864023a9f79c5a3a5f98dab3a65515609b404d5ced7bc1375aa94ca605f68846ac4ac88bb194d6f6da7aef92c63733af3c43c30810409578e3d8e22d31d0b06283d6924c9bb4901042a939610c44bd3feb72862b816866ad20a54de52c01758813ca8b6ac36be0c97708225c799a837cb3cccc967080bfaed5163cf3d8382d0a7c5f482bfbcdf0a4ea2513670bc39a5e00fdf19370dc6542f5c3226d04b4d2c0c8a65bae7c481bc49ac926aed78aac0af2cb110aece11b8f2b6fb6cad716f50441029f1ffddefdce253c4708d22604a82b2f5016a63271cd02e506c7ee1c80cb4ced4e47f6383fa6eee82aa0472af9552e176672d5592362440923fda58a8509a950cad878e023c075f585dc3552eaa4af92367ec4659bf5cfd8e6afd04c17df69629aaf19d1855479b5f1d7c722602babecbaa9186e8895c4115b8238bfab23413046e63be7970c971d902fb5ca2ff4dec4a236343bea3abf1845c1b469d73a1582d164635cfcf2a04ec1e215e89b940fca1cbafddc50fca43805600a8e086e85195800ed609459d79690f4a0514c53c0f32c1c1b08222a1950c04972abeed430e0f17eeff06544212756bf8d66c5dac489283fc60846329da5412f28a0a83d30ce860d16f201887c0af84de974d009e0018964aff08d4a9fb14785d023bc804899568df516bfa449263b931b92fb3cd3a4f2708fdd851783d1362ca45ff38a0cded50a959292da82accb2164b0ba6e0d6ae606d2109b53f666359995afa62923bb4c3254b0bef114505603218e9a56528fef28461b3483249d1e9be49f2445b7a3b878eb4cfb1036ffc01f57ad9908c34da333896b780d4f748046462c192b269501842175b0e67d9d29481ba563f82c6b56f3421c637bce9bb65f7017c4cbba2ee7d9616657655095c1319e295d1e00c640eb26f2f431e1ae4587a4343a79b870d4528c8e0f69595a624599e938458ba643a20681980ab6970b017ac0d7efb1b5e83f1b1d1b063926abd58deca36d3565288acc2d4088b8e13896da1e46350c39c5e761abfc4703271e668c1059894bdff08f0614aa4772d9f72e40ca6e04fc34193503ef1c082e75c79095e9839c18185ff4a1551f946cd70456766db1d0a4f39e59c52f62cdbce32ed8a8d3dcc41fbdf155471ab636e474c6eed245703acd50824f44e0d6295915c1412371d0dd2536287265c93a71b58b937d47b6a8671bc106c95d7aab570c244ccba305e40c4d55127dbd9c5a9ba9447fb46c313266191a701c076b04f76ec98206554d22c4669aa6c85c0c762d20bbe3f6a9f1ca611594438f7895240610c58f75aad70cd75470f9a007a30d8b5a66b6cd5bcf836809b187d1ae86248f569bc749900106cc2baf43949fc306c496b9a6a34ec5baddea773dcb7be6e2f04e0e8839f302abd884c807aa696edf973eeaf905e67d6d1ec80e4026696ed140ae4713a22c64b59ea37cbbfe1b35c3ca0e35f07a2990236b360f30ad7e84b23b55ba60505cfdf008b59d44305570b0a6c4b61ed2fb085c6be5482d0a77ff94280fec16f228d04adeeffec810cebc7be2a3943f40c67fd14b0b35c4a3764e1b639249720dd7827cdb22a04e235c9299802b7cfea5b7e9b07a554046400639ac884ba9f7ca5c0315daeae4ad29a4a780f5bd9929780f771215b229624002975ab68241f120a3611a03619244c0cf5d911a6e70449cb23930c93c99c03bb04c229418c1d2547720e3b3f5f11a0e84f0f51fba5af4768708271f0d432dcd8c1f808bb517fca85b7eb18b4ed187e5fe7dfb29277cdf1e9b9278c766e79590745e3c9648a4a94b9f5111b59ff4aad2266bd4dcbb7515195fab0ba485c4b62bd46873652e4fe998bc4fe4ac1a50d4b0eed44010b2540927b78d2ef5310655f3234c947811482fd9c0c6cf8d57d82494189b66697dc8798404ea234f787ffd54f04af4e555e138802ff0328b4c181612b4d80208dbfe6fd60659c42b0cf8af21dfba0e02b4ae205ceb8d55ba19c43d801e50abce08d4456940b0b515ad01552167dd7149183d6d7fcc918d5c22d5a993660a5d87f30da0149d4d0b147d6e525d843a242acd3368170c7568ac5c4dc2c83b5623bc4197daa2e0a13c07552705d4dc3717ea70adad8004b6c51d8766d22d61599fb693ace46d0d7edaf0a79f426834864b8fc06c7faf4e63cff3dfc316a740bde3001f18212b212881d40245ce78e0ec4fbc0ff945338c6d62800ae1449d7404da370037535090fb2dc3507c93e817a9f9b5dc5ba646915052059b0130c385531acd4a53d6ef234cad7165e4983c14920a6b1dd439844c3e49a52035ee80c3f81caf89952fc057db6bc1bff7c84b99672ba5292ae0930bb2ffacb8c8c6eaacceb0de43af7b79575dc5e36931cf83f68165942fb9de822b40f64fb78069f6d9c4665b39d6c5a356dea6c7f23c5f638119327621b2f5921f5a38475344a68af2ac4c0f4c06f1dac3127766da05c31da5ccbcf18b6cf3ecd417178c808cce370407c25219795f8fd540c9cc89977073c9fc4d8638d7f1b18a056b42ba00da1a4ec79674148924d98e4d9dfd73e5260f663507af88dbd72c9b713ed17a919c655c4fca9b71693899e4d54dd0d7fda5dee217f563d1dcec94593801d3a5284a4968d5cda17684dd1ef507c10caf1ca437aceb7d0af69e89afc4bab4a19d5a3cb79d2061cea20e49495ff6ff4f5b6da38298de4dbcf9188b581a927935ae7da7395a63d717c8ac5eb18f1f5915e6dcfb781c4092d23fa99817d889bedeb5190597d71c448f9b70346d64334d17cae44d9fd1cd6abd6a1d1d331ac63e837550d40ae851ee34b314dca48f9de4d3d458cd0c7b422494006ca41798f11c6fefca4879067a76c39bcf38af2791c92ea1e810d041f517ea35690b4c32cccfa2ab67cad629c718014d15d0e79764f1e06fbbee7388d92db5c918955692f2e4e6ce4802bbc02cbc6016b35dabd0b3d7c5e6c50973edb775a83f9f51cc05e6674d572f0bdab7dc19438ace4fcda2c8787ed7cc0094b21c5127c6ed177f6364cdd5e92076b7d9b7f9a4859ac965fd2e454bdb77ab009e205a2b8cf0ec12c3cc6800e2745c3cd5d021e34bfb6cced466231f4446b3b0843de787a01c1c04d7b7380246d95a9d82bd2cae6d967b9edd23a9f8e6a2a61f96d5e277650b2057ad66aa25138cc5e91266e3be80733d781805527e225d91b044f2b3a1cecfabb3ae8e095f8b1eafb2ad148b8c6d410701f6c4dffecb8f431a49e70f337d1dd584b9a6095069ea7d87e14402c5483ccb79c815860349a6fc9eeb639cfd52fe2fcd5808d066a66c6300322d74e199fa8c014c78b5424abc5949dcb81c7e5cae3d6dd813a45c4582bd5bfb8450b03ae76ee5ea2ff5a1a3db944a2d5c638414ed72eec1656b46e2167f77dc9761e4243021bc45f7990b953ad35eda2538395ed7870f350cef1ba503aba18f172dde2d46de2343e22de42d3ea1c2ce175a73bd67f36c108ad619e7fb06bd27262e320e95d21774dc1b8cb65355f3db34c36bbd2f596b1e4334be8265e6d27e6667a8e163336ffaacfec3473d3bbedfd5b8b964210594fd36304cd97cb09daf43e20eaeebe79f08829e0700e685a3f98078c98e302a0f5fdb7d8d3a1ab30521ad8f42dfd27c7f9974f55f2978efe8f1124b194eda34f5fc79a825b8868e3796bf411bfbea58b0afdb042941457f3f321dc53128a36b89dbb2dd85bb9f96faa21961a3448033f29b2ee181443999cc30fea01d5e24ccfd0c5463eb8f51e9a1a8983a30093ba61393e1e8aa5287c92009f1a7058b09acdddee731e7a6c4861afc55c7091f2aa5c8ef698fa39eb79e0af6ba2eabbc5df753a0b36cbdfe42d955a3143cd93271cb8f012652c43e54775a31cc3e3c9b60fe8e495c395937bdfb8422a340a3071b8245a12c4f2480edc87ea8fc93101fed600f825a4314fa0482adf0eacdb07a029c1f2f07de4c4fd3a5c0107a6631078867a3a812c272cd2b97addd7c3b4c0c7c19e13396721783c4f9272ea714669dee5c51d246dee723fecfff678deb7c37756c4538a2b1945250e67c6c117e75d0a619163be94891e7f13ea29b7fad1edb8988bb19ca2af144ac13acdabff82d07dd1a6d72f527d0c1c988f3ed1bf0fa1cdde0bdec42b838035e659a8b2c53e1231ceb843235bfb1d7eb52dac6a6af0dd4feda255bdfbe90a80d5b24ab259f358e4f1fdd42ce99778784529ce1088e9ddb178ac67345e4c80bcce2120715a1ab4e17c0790abaa4b395439b5153ec5ad3fa4018bca598f242c5200df5c3d4ca72f72f4236e70b797355843533b291a40c63ad423ed5c16838c586142cefeaf2f4073fd06fd1198db10d4e327a9454a04fdb0afbd25dbde2d15179920e06664e6986a9551e6d0e750231c91f81065aa5cc56f313373c110c7f022e05a39b2b021b6f59d28733088dc715c31a410483dc68f022e9286016b05ca4a306182b829f5fa3140aaadf5160359b14eae2daaf5d9e0e55d52a97313d46c0ab641de352f092cbacdd880d85e0ee3be096076fa84c23cf3280fe1d4406da0bf9f233e1ea908944b8bd1964c5f8f0f765ac4c0fe9a9e0dbbbe42a6f8fb8b4b9e423fbf56393256669012a4dc005c89325a5ee52f14f98402e22b5bb2ff04692007853f3f3a376c7cf9c23c987b5e443c2d488a05c8295d0f0f84c6ca7360d3f22f3632f0c30377167e8d75b10c515fc21ad07824e0ef2b53971f73edaf1653c5ccca861cc44cae8354b3e92b4d99bbf7e590ab7a982dbfe883b872b2bf4c72841209f0ca3895360cdc5e7e484738d7e4b6886e227c1e2e2f1526d583117d7c654a75688b0c0f1310b6cf6f2c7dd49dc3a84d0010f2b83839852265f1fff3c307404f18b5926493b6de12288b1e703cc5f7f5ea06ef1dd2011202c80ffcea302c6758de23336d03c5d25f3df577790a4de49efd09472ba626a60573fd3320afc9893dbb35cbeb08014003cc61bdff6bb1d5c46590164c2c7a0b82f1707b8f42789a6e9b96640fff53f71c62840ddc5eb34aa4d3f3ffd8b9cc782a993dc2fff8b636b75ee60087bfec016c72cba50c76d30f9da80d8638f029d4db221e00d40a908fb7ca99f32c671387cd3448972d84a69459dd1a17f1b40e5512b04e46cc28b5c8156ad54c13720b0faeb614a97e08bb4a78039ea027bfccff6db0637cdd6e0ec832423ca170ad3deb57ffe3343c34f069a5349fd8eb8de975a56587044472f49b370550fff00b16e9fe39ce7e0afb4b8c0e43e234a4db043c88d04284e5228cca274ff92236ffe0f68ee67be398a65dc407da34fd518f807840732e79b881a06fda39874e17964b0f4742c2714e3e70d92055827725bbc7942d9dc4828d3227c7735267230ed24fbad8e9883907a1e336963125a2366390709741dc89e9448fdd2145be4e6d34a4d1d91470dac20b4eb76b970ca456f8572fc4633fc2598247b792687b1c7b331a3b4461f0012e4e9c9ee9b04dc587dc8371141a655998648ff89e91ea9264bba62c8e825489d9c7b6913443b8b2141bea76f451f489d05cd8241023b948b112a703a173fd65c849ff96ad41837bd93a18a59dd35fee4e6f87a9e5aaf775041f5da2d8112a138c0cb8594454a45ca083886b4e310d0137d663668d80b30a9ecaadb345d561049d459bddf0ae98bcd165f3d1ec3fda3e856e095d7d0800feb329b39f95c9fe291876825792a3203f4cdc3addca09a724f687018a24e104c3dce0bf414d27c0bb2d14bfc1f5860f3bb1d1b84869e0c8949b867e390207d96b9506fdbd486deefac20a0b1fc459983bc0e0b98aee13da113a36ad2fdc654a2f9fd8128409c00ca213bd405f60f97c85f4332738288791db25ca328b4e7b3adb7c0fef638f5cb4b1b31e94e47b9ab1e2e9a13753e1ac41f849edc5777d3e79e911225d79a952d93ac11bc52b67451cd6b87cd75c58e5ec3d2c1790d1637276914bda8b547057b6f09f9056cff04dfd118043bae7b50b13af90a8cf86475d6391ca5b22b0a23d769a35ade080c36174db8df670d0b7555619623dba5f03abe959f440ef0fe46f5c3b243c71d9f7e8a50aea522d08a3a89548150983254d893e3c26c8258f62008eac8e3187c0db5942ae141e62412aec3771cd63e1fa224b42a59847c6e7484ed31b26a1fd8381cc0ac1b86e6873ad8ddff7a50b08836baee156592c2e418a7c6b3294d246683b645f046163430e170d4d989fae7861782e9e85a4a8d677f8bbe23ffc486cbe8efe00a4832bda64d452a3000311df6aaa591268dd750376ae9ae734810613de6931414f2c3d202c82bec35f761dbf0588b62177647db07a0e9f9643862cc51e6403f02914657a27f457d717f19afa00bf6084b6b34731f5b9964ea7cea07d5fc5a85c12a00210272f19b38803a94e9a412f7de54621888be887f213fc7d9c336a07c5cb0bdc4dd73508109047147332fdf6ac19d17d0afb263dea52bf6a651a5a55ca912ffb69c71e36c05dc2484dc2aac4d0f848e18ee5718edeb123600c02b8df0614883dd215e67070e83b980571522882f83a1e9ce8e828ab2bfe34f42558e6a9817745974927ae5333d244821f67d9df9c95495ea16745e861e5f70b72027b74e25f0d125871b44f3463eb9a068bcef7cc2e5516bb9f3328e897121b563e302d606248b579f57b2deaa605316584c1ee99e24c5a257a590124cbde59c00253c501317b40c2afce813c59075b831ad1e47fd01ce1b9ea012264450f8d083cb43541c6b772bb254f0ea63ece3168b9a27ae823339c97bf53bcc40755ef1ad035c9046fc2f43d0730498fc4f82c511b831d1f6f671ffbd0f60aed8d1eda8b0a6c318dd5006684b14fe4a9b0d4289fb208146c66bcfa93837cbdc17268465e11b3bc4ec000eba5b06edffda7bb5b530b8f076b6de88621c368be445efc6934f7da18263e4945fff49415072fdc2432368dc42cd6fde6a9dbc6999eb59bea5f2bc2c1a311322b57dbbc7d66815760a6245b0a8801501fc46d4c86204072ea71310dc2170ece85a1a4e93fdb5f3279ba9ef0d8b214e89733782b3073cd170ba670e47d832f8d44d8cc452cbaaeda04e4bc0c8a5243f98713b02cac0047c88d02d7be6a9e661c47367b2d984668ed2631e89d848a37ff8f8b7466fca64fc614be9c9da3cbc16fce7138d531ba6d1cbc5f82cc7fb4988c4693f6fad48d115a621fa5a1386c3a158c28c25a8ef9dc7afe78faa7a3563650455c591a0d943da8b1854a3284fb24f48f8520da6e6ce0a50ee7b2deced0701ec88adfcbced1801c4f10b605819f80522e20b15084141fbfca155e9687368b7bac253f77f83bbea5c3a0a03ac22aca32e7dfaa5bc33b3ddd928ab92af639675012a1f14c7ac8e4370cde2f184e1ede5465f7e7e567f3ba9313ea13e3b371c0ab55900b58463fa2b623f2c2080b8bd6f0f040552f2a3177953cf405d8d64f1cd6f260cd10bbb1601cf91239eef86da322d064eaba184c4ff115858062197f40b1b91bddc8db374aadd3248036e6107e12d416d5b45e4e0a87f5e7366446c84d16451d93f985e9dc5dbc64394b513bf93b560abd74eb79bac566cdfd7fb0fefe8da81f9fcb266991ede82bcc349e8bf7302175f6abc5a2416043252e160e04c232deba30f24f5b9cec79f61801c199db899468416c198e9adcb709034392d70076c5eaf585cfc1f102ca014081a18ae0a5e32bc6acca708a6c160c57acc3aa20169e2bf91ea91c10aa8a2a58b2d498c69fb5ee647cba92832fa4473bdb495e17f6bf2cdc42a82b2e2bc0f7aa8884b16f147021f23b06554a56a21f9b0d9f235bffb1a1a9edb2e3b012cc3777409541acf5b275561741ece824ac7d026f0c39ff21e284ecaadafa04c34bc72ec88c8082cc287295d9c217ef957de0e1bd90401db8dc3b7361777cd144deb06d680236367a2b055381d645d6330af6005ce078240fe72b7ca0c303286c50f68ac2a419106b6d863a9b97f5cd88fc70aad3b8cb8cad39af5f736e88bd98415ebd8574e562f1c614eacf50215e1c82a930f3e2e82df5a21da4bd7120bd320b8f2eb6cc6d542e995e34d900988f5a94954c33bbca43fb6ade09cc973c836d9230ee66a2c0b4831ad630f49691ee0a1cb1465ec1ae046407f225fa016553e46bfdc4ccf459105440ee49e7da5db84014e2ca337069a8998de3b3cc25aba6db94dac1530fd577f6846e09e4e1f5c9914d6b0c78bf7ff28e9881437cc0e00ac98277405a7e8066963a29156f6a41d2d6a9630c903da922c9d720ff8fba2adf52e117f595cc200ae9ef0e7b51966af7e78c5ca656c549b6d617b8ed4aa9127f7ae7d80630f5536ac75ad2b56300dc9e1bf14a71d6b7850c312c702bfae60d0a6d50f76e07a9e3f6347506f636b13d271468b01ac1605de2217f5f0beb2f2e85c26ab56e6a91a108275b0bf844c958b0af0222ae7ecac2b96ea4f56f9ae0aee3b7845a7dd181ee81575fc1f440eebe579d10078131b2379103566478b904b5f28680a5fc9f3684f84bd7eb99967a9f2278595f330bbc41d10f7088c540d0d19a93b65aabbee34b26f2d6002aa1dd673837adbca65866675b2a125a352fb68384298c38e825f986f052db5bfbd1216aa11855509527d20411442b376f6424aa518d081a9371b6cd032718b22a6f0cc6e9667db39525e5a671575a706e6e5e644bf8b3b158ce06bdd9593f11a0f1d641109c25c9c0ab03b9349633aa57318914c4702a53223eef1923db68cb1c5020d905c01d0dcd38433006ad5255116a3dc5aa7e084d8ab10d1f270d1c786724d6c8760784d109314249181fdeef836d59c79ae748aeb2fadf41ae298d92d0ddba53f1f1f1ff30ea125361a23578ad4b937caa11a384fafa1c4978c19b71ff2f3b609810971bc6be60c0fb31acc3ef21cde2b85fd21aff4958e738b981883b7f30dc7ce85aff73e4ba80d4759108954456b8601d3c93c4010b24ece63159ea22bb69f02b60db70af792a2fa220b1035cc745c6018a5e967edc1bcf2e9a2ead4ce3077810b6f5c2847b43b85511e3ef8dc4dcd31a6158fa8ab3ec035d8bd4f134eeab0bbe1fd52c5c141e06577af66599783ab038af045885f2aa6da28f48408f667f8ea5b59587b5c475a1b1e55262b217157993e50549d4927766119e674a733cbe8caba446bda7cef55a0364a41e1aaa69f965c139b829dc7427e62d36873d7e19081ee1ca699422c92bdf8288e340b8ca33a9a35c58a92263d3b5070c62ba0b6313ba837040f4639a22d34982149ed38fa1204117cc7af4c959e0650dbb161f97ec704463b16a1589a08c096da40464431affdb0d6c0ab84e554db7aea6cbc316ac951f79a8c9459daadaff17f543c9c4859d36334b7518c52e9e06dee6723a41f96840d2386d329eeb316709c1d95caddf882b8cfebf27ee199854b124e1adfcd81b3c1e5f4ccbfe23430cbf72b49bb05c7879ed1aeb2acb1bc7325b900938318c7104d8dd2057eb604426907a637ed7e19c6d7336d83d112b83f3aa5fc6ff0984ad06cc6b576f6ec62c38f37f7b5080ece2c62d3025205eec7ef77c2f5c618669f1e5bf20d792365e6c44d3983f83128179a89c7cce1f7f9add93c736c36eefabbd2f501d1d423199663227edc8b90efa5256ad8c8206dae862489644544aae2b5342f9a2f4b325477667b2fef471db6c1144b15fb04e1f41a87a43f14ba69515b4cab35d5ba326fe8ee6917f867b9bd4e50a8f2230b06c8bba75dfac5785815c846c533e0983e997aac1dc83ff73577f4740df9ca1fcc3bed13b611da3ed055b25b12780f32cbe708a56a79952bc6e3d23c99312246b90d38d3e3c6c2e3c55ff2f04ea1303ae45b3617988af9ecfa86c6808f1b6bdba20c4cfda71e6430c58fb6c0e9c82cc0d0a9a2e63bf8e93eece12a943eb702e748081e9b4d2e37fb920ccbfa6dde536d9f6dee7f7abf2c6c13b0effcd1dd4990a52cc3cc6687ca703675dd1cc074b308e7fa60d57076e56383d5e5f02e1bfe29592fa6ae0e8c90ffbd94e8757f89cbdb196d1502cc598368cba4989beb574a8ac3022354c30a9fcd6a560c9e53c4975fd7757d74334459df724a84d6706975a56094de3e9d39090389951b903fc16fe19e6f8c80716f7b9137f6911e40b5540f57c27a47c68f40475000231e8c613fbde599d0a08d9276c2f58fcb1df2f111428af03f2e9d7d1c942c4fa58c67eae69bd5e51400b097d544b6be51e8c28c66048315e3d5cdf79aebebdca54118d5f32f5e024144027a63c6f162cf51527d130c5a068c8a213318b7f40776de479df22dba62d764b5b57b88c7b864a62d088466307de16f7e2d36751ebc1e2c4d046d2d1b6fb46f14c592589eb6fc5ed105b73692674121b979988efaa673f93b32dfb4641ad085da16e1e3a2cd9a9abf9678d3c6db4ac74c51132a0bb1d858bd9f247c016bf870a2dcb824231408875fda4ef8fa2c52114c7ee586e213834540e622014d6acb682ffcc2cccbfd13571f1266051578669dbd9622319359d66d27f14042feec254952d3549d8b027bad35780315b0657360aa18ef18c10917a0a9937d276cee2e8a4495b1c2d7c6c3ac8a06a69f31324b026b0295fdb3474d5b3c1b606eb62d851a1e2d84fc3576f99a300016aa356b11baba1e0dfec87eb544743ba15c92ed5c3565706ca8b521e5ba8631070dc1d1d63b5307fd2e871acc1268f0da20719202ff0f99f7f0f53aff74b773676415e9b1455e749d291e126bd032798675fd4725a2cb877d0e83d9840c74cc6e93d4c0011ca01a4100e396ad83ad8ba3bf80d3e3a93b4048edd829678d3bf9060af91c49c06c5c92d5d4827b310cc21d75817f02cd8bf4515155f90812d30ee24f69743314677574515b5c0463fd3a49aadec3d61be3067e41755a76cfa96e44d4dce5990ec80cb998ace89e5c8c728af14cbbcad72ef6559e0d65100e879db9934b71c4856f53a49b490509c2984f8612793afe9f285d17338e80a47ce8392239a1c451025f70ff2390538b89a5c1fade36f54e28cb17391294919f780f945fd047b639787c588ff4e9913e77348fd09e8ffcdad96fc16f52f72967b50d2de81ef3037455bec94e6e6f36809804b7306776c639daea9a85fe120c60eb6019526d3493490f65d5c9f4d7260b6dff838b311e1240f849c44e40a8a082a86843461a033df32f2c5f10c13c642b22563f15e83696f71c875294cf5706c5fb10fc7feb9ac57a4bb033ae537d35b204c4a9b0e6569c85a1e4ddc8d48371519ad438a7623eaa621a57934518f2d867b65d6b2582daee8f78be9db3a1076d945cef90d7edc816074cd461f4a71171da3536e38517aa1a7c1bd18433e0d86f82da4eddd6defbdb79452ca5d0c400d950d3e6f5e740975cdbaf8fce0bafc74d1527e6a3d2009cdaad4805cec0431010a42e2484bebc5dc1bc82648c8c802f16f563d929511f2372dbabab4173f59e082b69c28d2dfb188b60555c1e566f563f5fb665d50fe7513f250516bee2322fa4d73376bb0ad185a31b482546188288bad5f42d1b7055b39ad9e18824b4b1f134254c2ef9ba5e2eb0df3d7c777909f766f3368eb9b762b2202cdca2ab38ab0ef25dc8bbffda6159d3d7c0ef45db384be2dc821118538dc02c2f466e122310516687ed3bcb7857a1f74a48505373f852a0e4cb418af5bb8f3ead14bcae8a814abffdc9b8a37472e4a513497545cc446bb40eaa97d282da791bedc219544bafa8f0a8b045ee1ca9aab9b46b33547ab2e54735724faebb5e63edf483aebdece68ebfb66fd75f24559ab8cfa0287c3e170385c08e6d09c035f0c94ecdae541b7d977c6212d0c43f4b34f038be70ebd04bdfcfc9d57443b507e46f78d0f511bdfb9bffd4380fb7dcf0cf913c1d26c212cf594481e99ad79953a532289efe7932c711903e641871ec2768ef7e03fb7ffa15faf52889a427a82c8311ebd3faf8e1f077f9fefc68f553cce589724974cebe1943c3ac4d9cf67b73b1e64d92b566a679633b618b63dc0284f3993415ba7953c3f7aca5717d1a77c89117f4acf522b83ac64a0d287082a673c08959a4a9e59115f10bd3ccd267b96a083e7be9f7d67ff9cc4e78be0f7dfb9af8a526646b495fa871565d00e43258d4b262ac310f2ad34a31495e411162579a445191b221a95f52af9ae9edd82f0b687ceeef80fbed6a3b21c23ba28431c2c5219b4c3952f26309e9030cca68a3fbbfd218005ff7bfc96f715c328b3123746592211934ad04ba6a0fcb47485f1028743ab63c4dde6aa458b8040de6bc3e826eef4bda86851c8b68635ac210882a07f3db0f2c4ad5fdcb4174317efed3b73e842cf81d0bfed1fa29846d31aac9ed1a334dabf3ec6f904b77b6f89b1115a6febad22997b6f0c245555adb5d65a6badb5561c36e24ad1d55e115b9ceb91bd169b35f2077ee1a7f1ecda9b74034caa3c2d74a9e88dfbefdaafe62f15fd7a908ab808a3903f10346b842888e2f88de398c7118fe3388e771ced388e63ddb99eb3d974d10f346b5cf3828bf3675e604bb3863541f333cd6c9a26bed634cdaac5518faf6bab6e6a7ad9d7eb553fd8ce318eaf175996668d7bab47d54d1396c1f0af63fd9a69dacb34807db234611606abb9b437566dbaea982607f60d017009dea233186d26c6625acbec27e1e0dc9a8c6adcb7d5235a53adc6fd593daaae5303415f746b2d3565b058f176496c9408ae180d92967034eecd4ffaa29a878746c3341a8d766d85c1768effd120968afefa6d7f8988e218414ae36a194e998473c63b7767c7eed411b64d2093e1e0e4e4cc664d3bb527043f112747cfececeaecd096684b3c97c77eb3593dd2d1d9d9e1e1b9d1683d3d311f1f5c9251ad4040f8da71c7a67b2abe9687a67b74896bad866bb55bb322cd6c227bcc1a3eb4ffb13f15f7f8d89f5bc340b9abc6fd1bf5a866f0d57b86e53d77c5caa01e55c7251989e8a8e26523cb7bae3022cb219308768b156916b2f29ebbac718d8c4a23f2351a1989a191ad3cb0d247cf4a5befcf4f2da82654ddab9baf4ef6ce1345672a542ccd48aad5665b3134444434a335dd6645452c74ac458b16b0162d5ab468f1657c5bd8da53339b6640b4594dac41b66c02f2201bbed1846c4dee33dab56fab3f43f1b5280ac27e0800fefdedc15f5488f4d3ac71ffdcfaaa5061eb07733f4fb309b5e605e58a3a7487ecd0a7c2244036c25d5bb1b862e887be2811d1ed5654c4a285db6824d228864848e08794f145b24848485506db3986cc26a2dbad286909572b8b92058b580bb3a9fc8c32bed6a88ab08de38988cc1a96bc990618c1772002dcad88450b23b3c6fd5a8f8ef0b5b51e552f329bc4ebf8a2b5bf7bf6778bb6d66a3d8b9f495cadb579cfc0d246ddba3316b4598dd455377f6b43f445599806b06f74a76817481e64fd30d1d769607bdab03a5ba4ea464797cce72e3fdf33a3d8baa88da02fba4b24dc8b180cb3a9c6b03144d8cef147f90343717c91a5098b69d9114ece4c678787d6e3f353030a12f21355615b3144742b62d1c2081f5d5b9182fea21b036f510838216bade5d72a02b25eb2d65a6beda006fe6c91acd7f879eb625d72496bd6c05fe33e07f64350ddf2e8d23d3615b0f315bd17a05ffebefce12f5f20d82ec1570ffa8a6e0c7c05edad6086596b6f40777e8ccb2aabf7265a74e7382f0dda7a07f9b768e27b4b6c7a366bd8372ff89c03fbe641149b4d1fca817d83f3f6e0e782dff96d8da4afb37b13af12ce68ae471fd8af393bd5bd4935b951301e3111b8d136b6641b94c1d1204bc4e0deec6bc6bd595ec525e5d86eb3269d7bab269947c7e78869dc11631c866088437c86a82649fbb9ce5967b9c4d556b0fcc8b004cdcf926399bd963d332c3d2f1d7fc57268268c06334d8f5d530d6e8b5b6f9af6edbe6f1bb797682ce1a01b67f418c750186aa2dfb967dc974ece399b5245659ef50a559e505010134035dfb8194035b4de22dd0d70aa8914ecace68c2ddc52122ee9b7d5cd48b28b8a9d1516b4d45bc344cd05f57a3d23c8a0a939a0ded501add15253a6f6e3fb9644d67ed07a8b2aaff2ec54af17c512d490277dd658121b9cd1cde877e525f9b03caa06c201d56a49fc18fdd45ba4857323029cb3ca8cb8fc86baed77e51971d9d8711a971c4bd1dc719db3feecac9ff3af276b8db465362f7839462f4fdb12aa9209355743894aa1d26bcd95636c24fdf3986fd14400e9a2d3b8d78afd604755c2f51642cdfda061b53acb542bd2ce327d20c7c37842af3527f3d07f646809f5196791b26b748bff64c2d09e2570389e1de7c3e39b66b348d849ecc383d65b647b18e7dcb5d7413e77199e3bf6dbeaac91cffe9df5a6b55366038c6edbe3711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cb168e3e91609635ca3618c6bbfadce26264b831f1feffdfcf47a3e3ebd5e8f8bdeb6b7ad4e4bcd56199e1a0d9ce3a9b7c842ddf8aeba6050b3d2b470f4ad67b519361f1eda8f4ea3b550f125e70a638c4b1f2743d78e4d04947ebb91a10ce63c8ebd5a267be537b7dd7078ceb07e20c7c3ec558865e8be619045caf64ac7501ae65ddd5bc6bab7fbc4bb4f50ecd57dba4f3d3eeba9b7483c79f6ca066d2909c7d333e3e1e9e909122228485ba49186e65b346ef1a9b43133bad974199ccbb7a8ac5d6e64afd1b0d49beba191e4575a74973f3aad6ca1a2462dcabec31f259863cfa9b91ec75e2f2ecbb284f59c5697b33c5c9443d27765122b52e915e631c7a6024ab7569bc7ea62386e9964ae694c342c920df4deb255118d2d967bcb8e516ba54f5c6fd86967588bb065aaf8cace0fe44000d66e9178384723866627419a571a6aafb2e72b9cb33d5c943d6334db5e6d076d755769e39cf22e9fae5216af52f8f5b3e346cf5e9ef8142dd2e83430e8e1299e1de4dfb8f2756e1be563073fd06cfa5ce7b43f3bede79cf6abc539edcb4efbfab41fb36fda27edfb540bf47df4733c2e3a46cff1ac16892c1d9f1bc7934e0b63be5b6c15347e94b4efd88f5e6b8ec777ce8fa66fd88f5e5626dbb348d9471f3d8fe8b661f4a1eb30bbcf4e8719a5dd9b0ee7689edd2d137ef3e685bec763354773acb3e3aa84e3d8c39af3713c3a769f33ac45a3e39cb8de46979db51ef59ca387423f3a6d44f78def47c72602467474b4bcb78cbe3e6bbd6df2b1f39cb516edd863a791ddf62c935891b067a2c71e137aecb0d76374db3035ec34cf7db397e716c1dfc01f3a79ee325b2411c561fe7c7739d7fa3882acb67d9f1ef41ad9e2ac35fad14551747b6ea3174791c6c5dd72a45be748ab93d7e2961e74129be6a9c12d02fd737b7ebef556f7d662b3e9739cf4721ad79ed8bf93036c36c57cebaf9bf675fbd77dfb5ac5ebb1f3e7f398cf5c9f3f5976fe64f4670132375d03999b9edd4471c090510db4eba0384cd78e63e632cf91c2ce9f05c47ce63f1faa41cc673f1ac0dc74989b8e73fe7caee339d29cf3e7437f1690e338ae418ee3f8e738280e183e54031cd74171fc7cae7111e839521cc7f1f3a11d68a0e33bce809af2107111e833df4171e0d8f11d5407cd91e6788ed4ace9ec9673d614e7aca9ecaca93e6b1a3b73a4b033475a9e9bf6a0db8bde001d708623e8514435b8b70c666d34f69b331de499e8006e1148e3de40bf4ff4e719e4815e6d20ca81fb145ef108f1c918a9b3f7fa6ffd585b5d35650fd83155af32b82e8a9ad694b484f6f377edda2da0166107a32bc229cdf9bb5e6133837a84773cdd5bfd8dabbab75aedc5b0ab9b45a1abf7aca0abdf1834f62aa6f8695c5e0884d669eb4d28f5d04081ed495276eaa744516b1acd337abbb70e7a3d124dd7dfc09fef0d076c8dd52ef500e7a3862a1e47a8c0c3619db0814956ea81a52188375569b53136f8d6ad1367c04ca5624ef0822e253f304c9a22bc948c9d3ab4d15b8cb5d16f64527052d8eaa51e845ae59945ef4c3eb1a384def982fb6280fce0a578aac8d653ad328e2c09612bbde77dd29638ea1345cce8520f88f45b5bc29523572919dbc9b23fa4802295064037e429256f894f2bc5c30b4e2989afad382b398344554ac684c6e82dead03e2488524826ce2abdb310114ec24e113d85de59d64368081cb687901ca4521c1ec89670aaf1a4d27a29d8b7b7be6342ac4b6f114848ad0b171d78b5fc02480fbc948c65367a67120a718a29466ff127434d255195de1d9c46d8a564d797035010bd45924de53255a54befe39cb64c38ee94142698e8520f5e5fa052187e0c50cbbcf55ae62dd2972bba948c8950f4ce2fc6a0ed25a9ce48e19091e155e68c68268af04c96b18c11b90b9b714144e3842ce36334234378e6074e991e6740087110c93019cb44e19cf9209b717a911973660d4c099c32278468869067ce9489612433251cc28c4e2c53430e9a15341925f98c0d26192e386782d065b68466bc8848f81c09c9408166ce8867ae40abd10c980f4d8f98192be11929ca7811cd2891cfac5006cc2884c606bc4a395bf9e94ed5dedd81533d98dd39ded226c8d381bd114fe55de5e5a97a759fb62c2feb70efbbcabc7b05ea763c7c957b1f0fdcd9a99e6eeadb029f2ecf5e7d4fe0957dc2b96f07c3d75a6b73c618631fc771b4d6da529cfdadfd35fab3c7da9f81bc00202f0dfa96f8626c36651367f382ecd96cca68fdf087718da4c7582b3f59afb5166374976fad78f517e77bd1259eac38e75c7ece18573dce5586f59cdda2376c6dd7f51cd7f9b9680633c7f12eebf312b7c02f5f9f9d77ea52603de7bc5597878bacec94f9f8723287312dd3315849be463104a770ce56657dbc9d9f6497b481ac7b09976b0f43de7a046e4e5bc0ba8d9bab3ceb343078eb349d75c739a61d64e0e95062862dbad4faed955aaf5d5f4d5bbb1e0ce993c4a555a0db03afc2ed49a25eaf52be81ff7a154dd0d77116579cf5ac16c9bd5ea50c8a3324fafaa967444367bdb9c8af67507c95344f415f17aa79d1adde2c0279bd4a2dfc7a06c55709d3b3bf6e33a2d2d7836e462d5814d59bc572f47a958efc7a06c55709d33833da5fc72dbda0afd792968e8c5ab0a8378ba5f6f17a9590fc7a06c55709d338b31d9a8dda5f9f310021fafa0f8d0120b938326a516f164b3d7be1d73328be4a98c699edd07c6a37bebf8e410647f4f59e0364f00287e4e2c8a8de2c967a46e3f17a9562f8f50c8aaf12a671663b349f5a905fd7200241f4759a03221003c60b1c928ba37ab358ea19ad76865eaf12057e3d83e2ab84699cd90ecda716e4a8edf6d737d080127d7dc7021aa02029068c17382417f566b1d4335acdef39cbb93832526a41018ba4a2183718442f86702b906c36ee0ddfb837ace3811b39250a9262c0788143aa378ba59ed16a6ebb275801928ba325a35c0b2516141425dd6210c1187ab10267f37b5d04ba26fa7a8e57b3060e5d054b39250a9262c07881ab378ba59ed16a6ebbc9bc5e2526bf9e41f155c234ce6c87e6530b72d436746361e40297c4c3470cfa3a8e930f2619152ce59428488a01e345bd592cf58c5673dbcde89e00f0eb19145f254ce3cc76683eb520476d433716462e703092969ea060d0d7f5102800cc609251c1524e8982a41830eacd62a967b49adb6e4638b3bc27cceb55b2c0af67507c95308d33dba1f9d4821cb50ddd5818b9c0c148525a9a119544067d3db64b6241000030834946054b39250a9262d49bc552cf6835b7dd8c7049f77cbf9e41f155c234ce6c87e6530b72d436746361e4020723496949c68c1ba0d4d40afabad964ea6958100000cc609251c1524e8982a418305ee0905c1c19b5605174231a5a6143cb7b0ac0af67cb94e4d741cb14c3af8b9609865f7f59a6d4af9796e9855f8759a6f7ebda32e1fc3a8e65a2e1d7679609c9afef58260bfc3acd32b9f0eb3e9629007ebd66998efc7a906502805f77cb64e4d751cb34c3afdb2c530bbf3e649998fcfacd32b1f0eb2c2c930cbf6e64998afcba0bcb54815fc759a69b5f87619996fc7a926522f2eb4a9629e7d7972cd3905f97619994fcfa0ccbb4c2af07c03251e0d76958269ba5baa74f94be356c156aa708e04cc1bffeafbf4e03f6d72dc0f9eb01d8f9eb00f0f9eb3382fe3a13fad7650cfdf50a58fcf525177f3d07e3af2b29fd750ace6a91b68cbf9e74568b743dc6792dd2751867b648d75f9ca245ba8e3b4b8b741de9d416e9ba8b736691ae1f9d348b74dde8ac59a4eb2d4eb748d7599c368b74bde8bc59a4ebb7d3c8225d273a7116e9fad0996491aeaf38972cd275db39c3225d2a1aebecc53832988af346feebe8e9a7d01974029db5f3e7dc3376c5b2abd563a761e7397715f3d8774e9d7357358f7d76eecaf5d873308fc72e3bb7f5f1d8f5193bb78d7aecb073db248fdd3ccb735baab753bef268380c361001cee5d44ae15c4ef56b44f752e589e8ae57352bc420baadcee943b785da6574db295bf5d8df1a599135337ad3bee646b8ab847f30fa559ea59a5279336e8052533396664425594a5a7a824ac2253df62f3b68847bec9fd1cd480dd7cd76eb69d9dc860d4b0c58fb40daacbc38dba83df64f033411dcf0c00c475bb23113c10d6db33133f280164149bb91fd46b9717cf938679ff349578b847d8be5cdd5d42784708a2d25903085ae8717352510117eb50732388ce912c4051282209115e69081d72386ac333a22241ce1252c011128d4c0c584294af092664bf8840b574ee083102e1f2768e120d38da03ba1491256824033259c80133444818035c58820cecc109aa0cd1340bcf940c90d55cc3819ba0a13840a6d94d47082324c3c18a306092134b4095302191680382289102098b9210d162354e0208a287e2051831211ae1491c2f77ddff77df58a13595db6a420458931bd20e80b1282d8f0f402991344208284f033cd0891a5a3049e8e26d23cc4e7127a7cceeeb7abf47ddf057a14896255e487ad11c6bcc981e9f7c1bef2802893132409149128165a5882462b3bd1c2536865245a4bb4866811d1ca45b4f0105a369c3f3014c717599a3272776f37c9bd5d276153f776a9eeedfacd57e92a5da57a95ae52bd4a96eae62c8f3745eae6ecd454b5bb5d92a79bb34f4f96c7cd599dcefaf8ea22b8390fd8b8b90d947a5c5f7d833bf35a97d06abe337af59b118f57c7252dd1bcfa8cab642ac1bcfa007abc3a0607c8c0c7ab6be080085ca5254e9efe78f59f2278158bb556349b6c93aca91b60b8cbb29b013211f3b4f419b9929461c0dac2e117ae7a46d76cca41ed550afa478bdef6ca8a59e2e469cd73fcbac5ba01a85da59cab94e3d76b39388d2ec5253f3f3229f4ec5ce2e3398d0f49c6a2746d498fe7343d262642dbfcdeee121acd34c99c451b2de1e11983e824dcbddd253bbe63863df48ca57bbb4b7496febace87b7f400ee9299e734da62dd227b756fd7730b1a837bbb9e465b191220b09e82af3c20a29e8cfd344086c3c608596276a9cc6b7aa5eece5c404d655ecd05dc5486ea0ba2a065cc4e8b18aa1f231ef49002892198f86063450c1018728200c2abf22901f1c2e585228c40e31466cdd83543ce194957948f1a6648a28627217ed8906f789203c8981c80a0d2040f3acc0026081f6fc2207e8040e583820f614029524723a465e9ab5dad6e8f5bda00575a57ddaa3b874e14d19d6347e3310284fe43b13f4675d809692bc63bfb5773ae18638c6eec1f8a2d768b51119bf12763d65a6badd5daeada005b895759342ec637dc92dbbb4bb1975729fba4a0eb571a37973f2760ce19dd9f874e7edf9773fef2f77d9f8bf9dc95778a637df988e67bab34b1ee172abae39cddd1b2aa156d49e3ae886efdb2ec2097204714f1206a7bb6cc55027368367d4c501122cd6fcbcb4df9bfd3fe0b225c31e2b7dde51d50570682d9e7339b3218fa7590891e65d0fcb6f7011f9cd83f27b3d9f4e126bbf572aa392ecb91c95ce6158699e7380e1c9fe5a01e5c29a63ce1a03a1cc741af0e17d92e5c64f5059900b184807db061041f9cf8d064874f903c7ee5f9f0f47921ab6d83bcb7d5b1e8bef2aef06aef2b0f87153e3f551956ab1e5906dcf442e1a211f4ce4f6015ce7d5074ed74dabfa92a9c9351648b148f87733952eb9fd5778573301d7cd8f2a44bad7f5938b7012ecaca3d9c6bf1c313434ce952eb590be7725479f3e6cd1b2bbef207e22cabd1fdf5defa063897b770917574572dd4b4254aa2dfeeedd3db4f2a4fe5aa9ef54feb09e7729a58c2422fb59ea1b670ce3a815579f76d655e9109469e9cde7e4f34fb41bdfd2eba7317a8ed2786ac968903fb1d84808310fc1af56b3db24cf5abd71564b56dbc55ad16abef8d096d7d6b7bf1ed8729460b153d543867c99f0e4e4e6f26f9c01cb41330ac584211872c16e49088664988e37805a7e3647c895680bebcc8320baef270608ad14205f8611ef766734296592cd83433486966090096f16f997bebf162c24e007cf51ff79910b0d80c724b4c972bfca631415928da105a2643eb9d37329c0a8094c0c9917a5a3d9a4de5cc723c23cc7494b6e8ec7ca1e025c40ecf0f49578e858796f318ee413c5aed19abff70095584e0e7617a024f5a7fdd678773396a7803b5440a13da30712487df3e5c9ae8fd20c148942474e9f6c1f2dba792397383086f8cd031e44dba7d70f8ed63a5c46f9f2b1f2c2d3e44b444fddd3eaf2b9fd795cfabd7f3eaf9bc7a3e3d3c9cb33991850c0f4c7c6872c495df3d25b061099b10ac38a10211e9eeb1f2bba7891d9a10f245ca153247d2ddc3fbdda375e677cf16d70b494f941792297f774fd54baaa7c94baa87ea55d5b3fbdb23f5d7693d9cab5746b6ac382199e2cd90b4ca6f9ad65f29765052f5419a3457e9a6ddf09be6e2062468b2a42cb1cb92ee1e2abf7b9c9e86fcf5dd03d513b5040dcc123431fe9b96d545ebd245c37a39d1aa5e4e34decbc9eaafd3743857a580e255094105104a3062ca6f9a5391d490ac1c74d0ea6242ba694f527ed3a0aad090c58b0b6894880122dd341b7ed3a4fefaa64dfdfd4d1bc1f08c607878d4f070fddd3c5dbdde5f1eadbfceb3c3b96a35441026a0f8e0893354d228bf79a4feeea4b2d2a4f0c31133e9e699aafacd531535044b08266868d811916e9e1a7ef3585d61fdf5cd93c5236664c243e49944fddd3c50533c3ea67886f0ba783c3a1ea7bfbec3c3b9edc48ea9a822524cb540c3ef9d094031460910281cdc2c49f7ce0cbf77ca1c4105c994a91a76a090ee1daadf3b5a7f7def6ced70b5d989d266cadfbd5335ea769a8cba1daaf169076a7cdad98d3b527f5da787732ba24822a48d14179b23a0fcd691009728322d0041c20e27a45b4786df3a5d1948212545083abce1724af7ce93df3b4e7f7def3ced0cd1a203468b98bf5b27eb4aa7cb950e564fa7aaa7c3d3b1faeb3a3a9c2362d3c49724d448c98a4aa77eeb0899a201092c4f5c31a12add3a4e7eeb4021400d1537663e6063048574ebc4f05b47eaaf6f9d291d2a91481b91889bbf7bd625eed4883baea959d6546f4aebafcf76385787f0f1440813c61899c22385e1f74c2acbc9c9852d4e4ca124ddb326bf670228a2862a40a208614c0ee99ebdf07b66f5d7f7ec6a86956646244dd4df3d0bb766e1d64cd4e588ba99a89be5d0c0820845ac10a5072e5f5cf89df332b264ca1a2e19485022dd394c7ee7f040a50405ae3146207993ee1ca9df395a7f7de76ce57069c989a225674a4e931caa9edf39555739505739bb1c29adae9c9d43fb9d133ec1099fe0e0e0d07ee3845538210e1627281ca9bfbe71a672af74e3508541da8441dca8e1e2f92deb0aa1645921542f84d29221ca191f3e64d8c1ab926ed992dfb22aabbfbe6557322c7009191170095994cc876cc8ce6f19545757974cd7e56495a5b7d65c5674142b53feea2ad04a37b1a2cad250597a9725f5d7633d9cab65821882ca961dba9a3ca5b685df312d28944cd165b1c1865dba634a7ec77688b082113b62d0b9a9926e9de4b776faeb5b3f6910490c44128bc5b0fec6b2c0581518e38131abbf1ed3e1dc986508104ce0a16b8824290bbf634e3574545cbe484121871ee98e9de077ac2e2143171790eca6d44049770cc9ef98d45fdfb1a918d5126d9670f317d6f575c1d47c5d5ca0130c7482814e30d80ee7162085cb0f2976f84164f71b56c14441452607246d8c18936ed80abf613ca826ac4ee044854c0ee9861df90ddb3018961518112b517f619f15ecb3827d59e69705fbb260260fe7727c70240c12416cd9c1c41babc26f33688a561b116490a2254cba4d1bf5dbbcfab1e48413aeb658e121dda635f2dbdca6c985c48c8264ca5fb3c95f73eaaf592565424999bb6d4afdf5b287739509146890f232246a2b4d5ae477a95501178e58f9a1042740504977b9f596c8efb28b053655a474ecca2c916e13eab7e9f4d7b7f9640ef97494603e1da598b24b89f5b7ccfa9ccaaacfa9e4edd2eaaf973a9c3b40126b9ca0e1618db92185dfa5539625b4788152c24b0c26a4bb44e17749d543af0c9621b8146193eef284dfa5d45fdfe554ceef92ca0bd9c68b9bbf6457ce22d5e42c2e2d324baba7a5f5d7c91dcee500f2e508167638e10986ad21bfc91ac4944eca0a503790c025dda409bfc9aa024001b36608992b4c82a49b14f29bb4faeb9bbc22b19e90449e9051a40f72c85f12aaaaab8ad46d9287733c49daa059e2e4c7961382fc7e591d200c1145b4803ca52993eed7d5d3ef57d60d26f8b801ca171d2944a5fbd5d3faebfbb5f5e2ca415e517290d794579317d5df57557e41e5d72ebf7a38577b505d4a1429e28d115da92de1f7a895801d2660713a8344d491748f5b16c8ef71851b98b4b015458519a8a4fbf5e3f7cbe9afefd7d36b081ec1e0514cf97bccc263173c62e1b10a8f3c3c5afdf551877320b841480c63c490b01322f5f17b749a8185146a006303971ee91e9f9e84df23d411dd0c312c41262b041de91e7723fc1ea5fefa1ea7462a6c456c83ad886e443522d75fb1cb4accb2ea59ed706e5cd2c4901e42662823432ac26f512a07942f49a69440e5871ebea45b9c72fa2d3ae143161eae7a38d1a149ba45de87f05bb4faeb5bbc12b1701091080e2246bd7e8b50184af4210ec1525d584ad461291ece6de089335474d0d1c2134a52107e873b4c6d21c14b218a3031a43bbcfa1ebfc3ac0d58e04161450b0f880ee90e7b3c7e875a7f7d875b215711619422c229e3efb04a2b6ca215527585505de12e94faeb600fe7ea159e0e2d4360d5d0c312e98edfa09690962c3fd610c9024256bac1ad0f7e835d7fa5da8809a31b42058974873a1dbf43a7bfbec3a7bfe1102d20182d62c474835db0fe825957e0942b100be85402160d52449460f2258874834f7f75bf412850eaaf6f702a074b37487589b4b944dcfcfdbaee4ecddd714d7d5953bd292da9bf417a28618286aa89287a48f737f56f7e7f559fd55fdfdf15ceef0f2bcd47244dd4df0f6aebf3f1776bc85fcf5d9fee86a9526303902e42ac4e904ef13b5bfded42a5851f94b8e92125ddf94a8adf994b0a2c418ad4b0860990746737bfb3d65fdf794bfc9db9bce4285ea6fcddb949aecacabb9da57232fc7802ca8e24ba256914bfb156d5902436403142e40459e9c650fcc65d5d57968082c85615978f74e736bfb3d3d390bfbe3354f83b473dc1609e88d9b88badc245559867b10ee7aa559b34407a3abef850a4eb3776c25039e81842ca933527004937de3df11b4bfdf5df98eaaf8e3656879bbf4eb8488d13d743e1a2acbbc3b95a86479612c82c59410a98d489df57aaea5a31b9bf2fd6022e91ea25eaefbe500ef0f1376bc83bfdf59c0670975205f5667ddb285fc194bf396618a2ebc70a3c64f8b2246de2b7b55a00102427f43802831b2ce9b63daeff6db9feda24346e91f56da3fefa03aad2586fd62ffe3d8af96b9b84e9b643ec1313bfad53056c8c10420424b844e892b27927217e408982cc942d4b6cd9f284e50b1a273a1de9016e4e57b4c7922f639e44118509e9c5b9ba0686105ad0a1c908315c49ef9aaf00981e11212c99024c557a95f8eb38f0affb127acb9ce82dd3e9abb76e6ba2b7eebd75f3047aebdd5b3791e8ada7deba6ea2776cebadeb17f4d6bab74e1ad13b76f5d663af58158faaf382de31a7b7aee382de315b0c7ac3786fdd0683deb0abb76e96a037ece9ad9b3ff48675d1dbecbdf59e2d7a9b5b597a9b5ff436796f9dcca2b7f9f4d66961f42e7b6f9d2684dee5d65bdf71a37739f5d677a0d0bbe4bd7520357a97bab70e8484dee5d35bcf50e84d3ad19b9c7aebb318f426796f9d4704bd49dd5be771d29b7c7aeb2f2bbd5f576ffd9545ef57efadbfb2f47e49bdf557958be8abeaad874588553fbcb8e2458f994da2f3f41ebbdeba57d1fbe5f4d6c75e888ebdb7be0208cee5d4ba4cea2ad5add00a0a940f6d1f3acc6c0a5d08bdc7a9b7ee60f41e796f7db645ef51f7d6675af41e9fdebab6416ff1eaadeb287a8bbdb73ea3a2b7389ba2b7b844efb005bd45dd5b171241eff0eaad8342e81d5ab10e6ee90d5abd75708dde60d65b9711a13728f5d6750f7a7f5d6f5df3a037e8f4d683b4e8fd59bdf5202bbdbfac87f2200882a283601623511ff27ce8a6d9149679d14bb34924cd269066a57768934aef9cf5d64d287a67adb74e12d13b57bd75b288de19eaadc79eacc780e88dbbdefa8bca7ac983de7807bd7110ebe293deb7ebadf7e460bd078bde77eaad0745e96db7de7a5011bdafee15c03583171d4800697325cd497cbe62e4c7679f65bfe1ec349d4ea77b4ab3111fb6f8d05f665398035daca3d9048a6653b6593827d3c9743624f496e9defa96eddeba6c274b72a5080f9539c9745f14fd5f0ef779fd7e979f7fcf3e7b95faecf92ac5f88c6585309fb1ec867c76d06ccab52a7d0f66b3e92ba5d0af2dfd5aa34b157419a5ad1859e13176db8d56b352a489ff3c5f2516ff55a50a840b9fadd98471ff15a1bf5ed1ee65bc1144e9a4e82d9e59e449e1b42a9538691e4b5f29320196b2b09489e505c58ac345d8afc61883a8888b70d5bde1f034deb6eab1ae1be7a2a979accbdcaab13cb5bcca9205b2e441014b59cac298a5e804acdc71428c953b49e81268069aac9465d1db5e5d5d5d5d5d5de1943b5b39a598c4ac14d55ce9944e75b553024599c18856caa674f5945a47f629655bc49f12a8aa572b3f1ab4804a5993a0527fd02554025df172674ded2c3f1d325a962da8284536b652e45a51c246182a452432515926b9955f1ad16e5c510964456f7b7525b228c5234a71a66b34bfb528714cd6f78b5c6599ac0d6f947345dc339e342a7b6a4eac4538173a765c95f1557f5199b2579ede9465caa82da22255e15c88da2b32323c268fca9b9a6faf663a92aed2b6568f1dbb6851ab12682eca0f8af592442a3f5114af704e0cfa7c75f86660448ab637e79c69554697067ffb571df40e1f7c38572cd2ea598de09716c1ec9f69dc100cbf0fbdf13d08826808becf68c6e126a4675c7e58c962d046ed267e9e453dea10c5a9a0ad6f1c4ec4b966d3222449ad5e95212b0d0dbaeb99d6355ba6cf33ba6fe01b2d63dabde1330404b83a5b8fb2676c367dd93400f9e6cd6f1c3add67b483d16f885f91b2571c21b83f82f8419403fc23fadf35b5b4f52dea19cdde1bf8b3df376f42803d3f652c6af03fd75ba4812e82a7067d8b354a54d3b2e72ca2f8f6998681116d7d572b119b4d1f885ad3003a2e00e1c20f016e463b80d3ad21abb5b5625a5355aa32aad7db757d6f5729a7deae5f1ae700ead1f59c8c36d5db1dc0bdbdbea216fca0d19bf6167ccd6d94d72da8325e7fbd8a4f1863a85b84c590062ec24966b5f681f6dcb6123bbe50160a63df007f7e4f31f41c8ae199dfa2153c71a8d634dadeb8c7f5fac65d259427de0f8d756b64abaa76aa4a5dd036a1592a5e2d4700639d2a5230377d8ba55b290fe01c0c45f57d6dcf8ecfd0471bb6a610b551e69fe5b4786934f75bb652958751297b355e9007d1f6642cbbce4fe801dc9cbeb8a58d2b1f04cffab9ad36fb8c73b6fd2dd7a2ecb5cf5b97b6c74ed06c7dfd1aab36cf15004e9ec67cebbc132e8df9e629ddab87d64df00b40ccab93d6afd9047a788a2f7a782e8179ec7412d3d1699c03b8b7ebaf13832ae6e9e7cde8c49d49df396346ebd23fe2579ef884f6df3acb0d51e0d31880d72bf2662ad2b595b69fff88fee342fe23a2190079cd831cfb2549bb23f393e8a4488aa4e8a26f27495288f42092249d244992244992244992c4c9f199a9cb4c9224e943923d64a9494d239dd42167a4e79038a4cb48922449922449922449922435192361a4499664261de8ac9782ecb553c445d97f4e1b2996f072cd45d77f81fe73ddc783fce7a219d4fcc7813c3bf6d1c5fcf432dd6a6151d4bae2a0f3ea70d1cb814edb858b5eaf97bf5e4ee3be5e2ff7e979bd5eafd7ebf57ac972665eea382cc6f3bd5eaf57cfeb4543b7c8836ebdf3d241b7d5fd6bf6ca7939ce4bf6728d6e0bf5afd7ebf57abd5eafd7ebf57abd5e31747f4e30747f4f26fa2a5f24ba3fddbf72d7bfbc76d64bc1e73fa7888b3ef739352efabcda5e2807409ebe2110a4f56ab356c7c32d6ab1b5d8626bb17f19049d043ff0033ff0fbec67cfcff3c901fe2e309f568faef730a9ba53ae9276faeaf9c4dddb122eba8e4bbab71b8446df2a7cabee5548020f540f3ae8a15fbbc4741a97667b9eaaf4005586ace71c6b9175515753ac47d663e7cf75d26d0a3b7fae979ed30bf07360dffc580527bebe66b1a184c7eddb8f6118e210dda367979db11376bedc9a178c2e827ecfcfc76b36ddd0ab79033f017aba9ca17af0a2366e0fba350dd0d3e50c9407d1254e9e9a68d22db2e29b674dabf9a138f14737554afe2ee14e1af5c8fad882f6df22f9354b95247fd1adafeeadbe58af5e170c81b426a8faec3f4cf4b6bb7dadfe7abd17ad36eee9817babb5d6ef73d2def2dbb9da8b737514f475a1223014185bae9ca1d8fc7dd94eb1903f300cc12fc3903f300ca33e10fcb21afb81e0976308c2e6efcb762a7f6018825f7efa2167305ac6e49c79743927f18484fc8161087e590b981cacad923f10fcb2182e536e905a22e74ce6ea03c12f73d1d223e74c43fec03004bf1c953f300cc12f47e50f0c43f0cb4168f8c1bafd40f0cb27a86073b64ba6c879899c57e821e76c06e37b31d8f5808688b77e1b418a2b34a001b3c3e38a4081bcf8e6308761ce91befda56bae1cbdba5895aa522ed14aa215fbdf1d7ef5af32cdb048d7ab93e1f9d5b1de3e7386e863eafce775dfdf33c07afb5e21787cebedcbe07f2478ec7914abcfc6797df55bb357276b48d3222a81c704705b2d8665d4deffdc667cc1effb2c0c58ad479fe31ce22d8390a4bd49f2ae3c2da8bf713f06f2050c9cc2ccfa47adf94a5951b5028be8cb9010222e2229a2158874ecb819290a43a464851344552c3e2822f2e4c663280a16405a90c04209d18e1622ecc06237a445f401113a2f5c3d845022081e0c584154f0a10513ed8344914ec3c614d9a071b89c408b3810e1a0c32945635644515a714249eca981826c9321493497c49001c68b3cdbc2e9c8806481b25c581b9423738891d8f3a5c52c0c0b7287a24c2be2b68308686b684665137980a8105f3ba0e26bcce9563c07e1201494673d00e92a353bbbf2d3c4070cc393412674b20ccd2c6bac9c1cc4038e48e341766b7c195f70ef05f7332fb8f55e6dda10dbc132b93373ec84b22a7dd96dce8ef3bd5d8b5ed4de7b2f112f116a24a9883d59c22024a0f87ae10381b0f98dd0d6efcd366808f26b42c3a277feac944ffa5665dc6a9dc80e38b06ffefa0df1b3571bf9bda0770622a37307e1df10ffe702ac538fac59c3fc8a8af588a719503fbf0ec8e987e2f020e7f0cd14babfe8ce135a3c33ce395f8b03db1efc7dbf5d5cc415875f79686cb8d7f2b2a665dc17ed5ec67b7b86042f5b9248abdb31da46f13194e0a1c3951f8e20a24c5a2b88820b550e3bd82401c142f1d66553aa8cfc96039f4f5da56fcf40073cf67beedb5f7c4fd4c6edb17f32a90f95ed6449eecd7acf11bd653b190f99d355cabd951129222223521455e4a368c8df5d0425dbdd64bb22d9aee8c6c3b91a4c52d0b1c044162042dafcbe59d00684221f38a10b018874dfba7edf96d270e95a808147096fd27d7be2f74debafefdbd64db7b9e936b7db8deaef6ddfa0feeefeea9bd45f27eae15c8e11ca08e11df18147922652277e1369fddd41260a0626929cc9926ea2267e137101e20305365c88981242ba6f5cbf6f4e7f7ddf9e6e43b41081d122e6ef26cad257445df4151196ee1155e91e114ff788acfe3a910ee7aa9a214852c0c1c81a18aa30f19bc8490a0612945c45e124dd444fcfe6371114971f393061e20b5710e926da1149fdf54d3445fe26a2d244da68226efeee21bd1bd2bb213d35a4a786f4d4d0d00ee7f609bcbc312187373a6459e2f7d00b3a2755218871626647ba87b67e0f518006303a6489a2030d27a47b68cdef21abbfbe87ae62bf87b0d20c1149331405e3f710d4d6908fd8d690bfbea24beb86747ac8e9afafe0e15c03c20061c6092423bc014289df2bac9ae84a82ca981c88689921dd2bae92f8bd220b4b6bab0d4f47e449ba57a8f9bd62af58c1a56545142d2ba6ac68b282eaef5e51155b115b115b21f5d76d3d9cc340cc112c167ab0c10d9a1489df362d105c684184351fc80025ddb6237edb7a1f6831028a1e5a745049f70add0aa7bfbe573cc17eaf18127b6203137b621363eb62c3fabb6d595536dedf6db3faeb361dcee588caa18525588c14699225cd6f1b9a188ee872e689d319a874dbb47edbb0b4c023ca09549e5449b70dcd6f9bd414d55fdfb62adb94980e156d623adcfcdd2aba624e2ad43871fd85c2453128adbfae628773357448783494800505efcc6f1552434dfc80626a0a4c1050d2ad62cacc6f1555171082e68a8b10639a48926e15bc32bf555805f15bc595fead020b96460511581a15512a7ca818f277ab80826da16e605b2a78fc75948773475a469280b0638a2921647ea35a47a889828d151d2a40916ef4ca88df6856932294e8a1890f51a8a41bed15f11bd5bafa8d6ea51be5824541a3c0a2a0538e7ea355301eda8447054361280c95faeb670fe76a9b1d08563d429042e607dbfb7d563160a80851040952b89192eed312f1fbb422021b2082d8d1852724dde810bf51a7bfbed12774084c84130c4c04317ff7097b3a614f276c77c276276c779e3b40f1c16e2a4a93273ec6fc3e3da0c5083233f08010c142ba4f31bfcf18d63cb952c1071a76a090ee73f7617e9f527f7d9f53e6ef930a4c1b306efe6eefeaa9e9716d79d6566f4bebaffb0ee754b8e131f1810213bb35a910bf7d04a7204ea08a820c9523e97630bfbd4a032b5ba8a0b0830c4d9049b767fd76abbfbefdcab198381126517fdda1cc29f7313584d7c5731dcfe9af0bf1702ec71122d0a4e08305166ab0f2e5b7908c1dce7039c1c525cc07e9160ae2b75056069c78a932844d1125b449b71010bf85b690902982902982909010d5df2d54650a4199423b5348eaaf07f5700e035d636e6842c9921f74c0fa1d54a138320213676ec04ac2857407fdf03b288745489a10aa70c16221dd423efc1672faeb5be849680898203060c4fcdd4159bda02ebd20acada0aaad20de0eb2faeb413a9c8bc0162740b0a10ae10b0ea997df414e2190828411509cb070440de90ebafa1d046505c5ca4aea863160d21dd4c3ef20a9bfbe83a682a84a266d4a266efe6ea0ae2935535c3ca02c5e8fa7f5d781763817aa41c24409251f9c8025e5e13790140792f0c035029b2e1ba64837d00ebf81a480a1ca13504988d8ca4a375097df401b0808ab0d109136517f37105409e4a3041a52d6ba4a205d09e4f4d76b3c9c7b00970f7690f24215ac255c7ed774a60c3145550a2a84a14a776dcbef5a16053c1c4cb08107066b8a74d7b4fcae69fdf55ddbaa7191b528646d4aad09d5df5dabead5a07ab55d4f4aeba7abe6f4d777ede96f6d08c9e4070cc944ccdf1f72ea879cfa21793f24ef87e4fdfce8702ec79414ab16744873830b4c58fdfe410ae2861b868042450712e9fec9f2fba7eaf0640bcb0a63acae9449f78f0ebf7fa4fefafe99027fff50b569d3c6cddfedd345ea7cd4903a1f2ef2c9278b7cf2e99101a022a814091a0f7349a714325323c00040002317003028180c8a06a328c9b144f90114000e63b65260521e0824f220857110648c210a004000008000008001a588ca00984ddc1a451fbaf9cfb961c8ff7b3289e65cadbdd42222f66c6dd6ab7c1bdf1cf354aef12ef285df769b694d6dc1a333d0b5a3f8c7961c74a0d18c4e3dd24813a7e9f27ff25e9a1dd91c9326343a600a343ef5146b088a2bb11db8e2bd1fc26e9e0acd637d1d476173dfddbcad8b587b7267872f7993e8e7dacf52614b1bdeaafcd3e9be3aa9665dfd2e71d8f38ead7514b0b51baa7d7eab015f23ca901cc98c0413bace4278363584745700bc2f7ca205439e90da644d1af0b2c967765d8da3d3df290edd2f9cf2f48b3bdca118d5d6219b6ae8b4c33159f190f0ad12d23661796dbf0d3d46d64c6096ca0565c41bc82e53925b09e0195e730346a1c0642ac0762c782667a657bd58983e3a8369db82fc14df4c805591c5fbe8a63c420b9f468bce76a6f6e011d37547d5725005fadc4e0fd345637e8e0cb18c4ae6dd1061371328c52b1243f80e949c444a63ba5761a8afe2977ec36594747496e8fef731c433c3eeb1d0d4fcc9fdf6b11859b24e1a25930c61d914d4faba54e98d5ed4fa360a3d63e77d8aa7829e273a89c921b8aed9487bb9631e24bb547289d42e9521f2525d787d157d6f87f49345f26d501e97472c6e0d1eab884ca5d0a58fd4dbf8f3918233d097deff66b59aafda223d13c89e77d3a2ef63da73678d9de076508298328aa1c0f6c8bbaeae0dcca05c424cec1a375b4140a5c3e1a600280a39219be2449bdc19889004b31d38a7abb8d11953520f59b045ae700a7da6a498aed89686f7e93aead269de6e7c31b9abd054b762f40cb42cda90661a5b4a99290828a69def47260d649b6b29dd69b987a21275aada3f4c3616e554ad56832442805a962a68854d48274f4a9379ea14136e4c3e8b34d6377e003f991b3b73e4954f2920b0a5cbaf78b2852ccc7d6f7bc11981e052475c585592380b8d49ed07aad0b6b4f3aee88367545be5f15a2636cf5a225089fc7fc859ff7d41bfda5379385a2293bc6a1e9bc464b9b2a059484323829b897417ddd929969b47ad0a1ddf0339df0f15388d7c1e88a801cce4e69b09988e7b99428bbb2bc51bb64ddf4f022d57f4feb932ef720745e33c403818245b4f786ec3445ff9b020a71299336c04ddadb0b968f1081b6981a7b05a70276f8a3e2475ec520972156ecb28d02b08c266e1ea436434c9b06eb243fd122bf46219028429b3c013b9b6746ab970370f957df6fd2f64f4edc25d7a53f220339b5337a1c145c448f46f9d2f638b9a1288abfe40a3ca171740f87c1da80fc6349a5cb97f64094a383086c0dcae24701e8b0e17ccc5c07accae4233dad06aa3f31c706e046b7e50e7e9012e270c941886884026fa8dd0cb94b8dfd96a9bb854b39853bb78b30b30d209e810e5f256e703a6a041dee10f28cc625cc31ed8dc3520c2f8593c4632196fd7a2a8d9a0739414c4692c11b771f94e059c92aab39bd05bd24ec8189dccbd68b43e246ae26b2169b03bf141a60d200e280b6e6826703a008e9eddaebb7c5c3e5aa63cd390c8884e9e5f203755dec2f9bcaf695e54d5990806af7b6795acc3450fbb5534e322068ed64018e2f83ed9ef25705b193f2eb28fc91190b3c4685e17053dbe12eaaad9e4b683e9e35b32dfa544f1e87e301a1c4a6a5519be059303174506af9813a142da9596c9f9be4025a77ffb80d9de20e5db07286065345dd331684fa9dc4c352704695315322e616356d685f5aa05b043aefcd8882a949190870984a2a0ac1711d1f32bbe380a5169c09fdb553805cb7839203d0e961b5c3b6712d736b0178dc0c8fdc3604458418ef59a1495ec17edbdb0fd93e482a505b98e645884c80cfccaa2ebcc151a4a59593d76476620572e0288eda5954c755846875d40ac7c68114296488107a4231b80358a59fa70b383f0d36f684a5119885bea4c36415da8f7e8e4b551d995b449472234e3e14f58c2e75bbb060e558b7014abdce08fc5bc86dd8d83d18df260375ab882fcb42866eaec9ad651cf204ef9790048beef049781281b24a0b6aa0af03f183259e16a4ae993d60d471ad0220cf65220df03161ea5d63150ac17e5828c4130603d662d2b7bca17cfeb7cb85913df739cc507e3edafa0525d79d0c432ee0611f908d7bbc0a4774701c159ccad6177486e090f8e1c26c2432b74dd77c9109bf448dc2b1a4d2594d3f90b5260b1f31bda13e67ff72b008d1c7b5813333008eabc0ab8e65b7943d79827b130f38fa6b3560131e08352224285d60ad88cf75986348845da5b733b53b4f6b2a722259df684eb8a197029e92f568a2328bff318b4f6bac434abf55c42b357f0955d4e879607e48cb80849dc3cee84f722fe6daedbfd4cc20cbaa08904212285acc9f76b2f9955511db465e06dd0a82d9052fdd53da7c962ca46052153222a0d7e8f7a82fd0cba40458bd8105b97c4fd3bd2728d0b0797559fcbe18279dab5b9a541ae2423f23e971d0fe1d60ef654a3b512932aa0ea8e369d8240358f3d177e410c37ec8b918b4a7e97a243b382ba64e96924e571117f7458b358242b6795f0afb0fa388445fe239e16cf02d1be3e6df042448478e1bc457769af7e9705c560038c239c0132892f0e270e30408e3f8ff30388a864794d9f1d8511e9940a802a88ad15716c87d127116145711ef66089106924c820dc365d58b73e8832248a65ade3ea71a224bf922885053a7b8371eee4c3988b55d516e545bc6e16468f800f27808423fd5291b48db1ea191a76afe23448a6af1277c06994c93b41354182680169c2843ab568f9ce0718fe09d05f3aa1f9b51aeb41cfa0c61be9935ed6841a1da7d44b15439934f91c5c412857b17b4fa0c89dd026c15ae385ea91c9a906505e3acc355dda2e9ea456de8a793d5f3299d80a9230f0ce2c4874d849c8041603445b9e26aed63592e7b51d4bdd4c88c9c39a7cdf40ac46fd4b48daba5523e440b8a4a44147ebe6666fb573dbf1b2359bcc94fbb656bdd1871efcf0d343a5a1cb3f4c545905919fb0e9da13a75a97a7cbe996b30fe5562190c095150871da3088bdfb3b235daf2378c3f1a6df731d7fc8875257a1b79fda8859abab929b323932b3e8059187c810170466ec1a012018f5c16fff5837e1e203cb1a110f85a23d09b23acc203f0818b7cb9b082fa1bd61446f68b5e03d58a54e2bd96a32437b3e817b459c661966891ada83fc2aac6da067c268ad1b58a88c1fa290553f059d3c30638956901de9d54485f5269b10befd432eec6d1a1ab0cc8a15b2453f2562c781526acde7801927b34893f06373533b7d45e6b48c4ba67d09900efee3e23162e238ac260bae8ef40a22b255264c0c3f21ede8b771d884692692e93f0ef41656946de9c9c48df1125a902fedfe302622fdfd23eed8633a1ab3c00a2b65b20fc67d2b913327b3a62b8912564d3781ccfc83713331dd27e782955322330f1ffb9859f8806e76ddde7021ad993051d9d6bd4eec18a7b4644e566a86d4d83864c2c426920dcd9c8895879b326902b2a14d5362e25053a64c2083ffb9b8d56f80b492721ce0ca09e3b13a3efedb415726ac3181ccff810dd5bb28d7e79890af32c6245a2eb3e2976034bd4e6f084d2f5aa0fd257493ff6cedd36c84b4087cd42bdbc44ba029a51dca82990a4767203b454a90ebefba85dbba68e356900edff736e47094354ea2855b898c9edc0ecc225bbb48fd7ee7896122cca36eb0bf3d850d82dae5665cfa9314c8b435b858f38556080139d75cf5da329d6345f4f12ae567c09dfa7213c130a08a4e90d4aa3b149384fed197e3e9477129567cc28f4a3847701d85cefb443f7718209ef39de54064c203cc2e07a9e31d01a934a57080af926fee6e5d37fe1ea521f6c674e5c8b44ed060246968eec115a0bb88e0098dd38be8e0d2a7832ba10eae4b325cf893e13232ffb06dae919aa1bae8d0d1f0b221c40a6ee862a2cb5e4d0714bcfb4a90fa3f3be27e64e92fdd64562c1caf3a87e0e96db1888000949f57014c69543bc7f05861d99cfe52db502d7b1af734c23b289c6182b2422d007b8195c932001c6c3fef3b1b3864f2067bf37cf8579a0c28466851c129e684437b6a7ef03cf06843c5d3cd0fe73ce5b215f54e373ffcb424e03654e12654f6e8fc1f49d92fa264323626bcfaae27342a64a33d4f09e32ed5a62cc85f0aa4c846381b297093222f4d1220fb4f64465e0ca7dcac093f3eaed7c36a7d09d4cc832dc5c15084cae1b96644ed951f18fb6d95930056e34cf3a2ca5b6ed0f6809c9da8b29517da5f008e020bb6ccd9799773b5999cd49eb715def6ec2eac642b4adc5eb185e9adf196977edbbd1331e867a8ce6dcc46cbd39d08edf85e80a3a967fb6a701457642b3b6f5be1855987f1396e9991d913a99868f6a55c23cb36fa9191101754a98a8e72a9f9e4a4cab429fd3a2771ec26d668590e339a39c94d7c687f245174db142e813901572587117075de0f83632315b37d0bf7dd455e649e9be53cf49c7cbecd459dc72821124a79601cc008aab5fec97a88375dfb7681a5aa4fb89d8bc40096e803c76a7ed9b7662c06a3f1327fd75d9dbf0a4f79729b0362ceca7466814a4fe0a9484d16b1b6acfa8cb5ace96f5aaa7f7cca31322ead83cfec42ce1205249bb845c14f9e1b7208f9c389a323aa820a8fe11c95a6a7d5bb5d4e35a7b190a4893578512ea3bed8484ba220a610d20c59aa5dd4c2b01dc5933a08e4bd6993e19922d8c509293b355736992042b58528d3280039fc0c3069eca6881234248338b9553a350df75c7f3956501c5cc0dca0c3f662ca6d9e0cce59f58b491c1aa71572702580c50698b7895da5a3e135e2d7a423024aaf7ca9fa1490b24cddc0e507b86167f263e5cf811e1b086eee70a1da8813507b5a0842b5e38a8fce326bd784108ed6a15f8eb5454850ee1d5a255d5cefb00b5e7a72234919859216dcbf7764b37cf55a32db7f6a6231a9b4c02b7d9478d263aa4ddbec17be80ae16f8bebf5f897e9658d4d7debff96542c658de7725754c5823eb134829f9c0da5cbb9d2c2f7d5f80e06ea244ccb5a3bb789963589fa5bc947dea01e293b509473be1dfe3cd60277f28ed50a59ba77c0c297a28d9e84629d64c80e9b7cd8dac03b02ac31f44477532884c09b00be7de1b71ac0d2c33c2461bcfa848d4983c833663d383393c590b80e9c8d9a95853ab5783c55c9c8c4afafba0347a7d192498e7b38829ddf3b8af967fee0023afbed51f7a5c73008cb8185dd3351a53b62bbd62fc74b02948005829938a905225c98302e795aefd0c2160bd7d113181824a3b8bb4ca79a17108c11faf4be4bcc4963b6b280f107787126fe5ce523a487019daceda0ac19d0a6a982247e0668f07955033fd80f668381a07443e6d854dc63156722aa9d71c9a90824dad985af379e78812b7e0451f161351403e74ad4bf00067c7cb433478e438e06779f3d7a1e4a8d69c1c63c0590bf4ca3e18b9841e8ae086ba00822f9478139895d6ed92e9e2a905b7d2daad989f46def36eeb5a37e2eec5899755eed980d75a2fbf5a1ec6e20b64fffe8681d999cda81e790dd0650bbaebe074bb9e78dd9ed1d4f7ed8e078e4ea2cf3f5cb501ee9b49cce98601063ceab24b4883d670fd72f301ba80ed73332f0edfd2390ff702226192f95a702cd767276ff17a11b87a79bfc81bfca5d02bfbb893eac560d4150bd82989659a32737d06256323d8e61020adc76a3a741101b506a1d501416d1a4abc7b9108bafc12b004f93da056573d9993f7a2822cefcd1034608d60f3be8d22a0a6311121eea938d6e8eda267294070b23e8f64994aafb95edd4708936005ebf2ba4282e2060176d1be11558a6664ac90e4eb923ebd25aee87d0cc9f3a83ff73d935c7e72bb8e794e6025fd5bb33fbcb32b8934164120c8fcb28307face6c2afbdf80ac6a06f640a816ca84a31380a3b138f96debb920689701f19327ae48be359c9b27963eb0f45687fa95f7d888beb4836607bf022c9a71b9d649079a271dd7226be9762643c9730b0cbd1b6d041baa65a447f231f84432ae121000326a32e488a01ce3d051f2d4e8b23e53eb0b4d5cb81712760de61828c35058b8c9ae89eaa85f8293eed0b70104e3e9f0997e4e879232bd392902816b792bf96264f442cf5825da4a24d6cce9a453c8e5188fbf0d508c5d468874c374f7050d8e58726810c81f1ed0062d303d0bc289b5ae7986409d6c586e9331021e874e0afb0e915ab0c8403b0c9e022dd6e974b7b61a60933e2b0c859df4bd48e0839288408cd1e1276550810871c30bd053b50153ff758d83967fd7c1779b9bf0b829c30c98d5c71df838891845b1f4256bc45c2268a33e40408df62df428d7800e4dd0bb0ebe1bdb84c7471986c0549cc2c0e7580fa422decd6a2ff549fb350766b16e063da049a96209dec7134ac46add0c501a430358fbcc7ba82496ad61553a5c3639bc3560d734fb13f0eba8a6f1159dad44f5ecbc6e0501422e8c18c223d80514bdf5e29b1734313cbff05a0820bf9bd124602d479094885b834e18772341028abb2d6616a820739c1305d59fc53323b73b7b3887b2da9def9060bde6570e7f40564ff7e598516f3cd75557c0736d25924e958430a0519a643a183d598fa7c50ae46fa34989ea6fc4fd0b0e5e022ea0b7a585b93f871fe91f0181aa7b8dfdf48a7e78dad35508581b5f75b7bf79898aef7b8724b53680afdbcfa692b72daeb800667fdf778c3232151a33c7dceba9e904e33642b15026e027593b26de1663fd3134c53efd07a705a2a277b6d65845bc8df1b1b0c754631ecc3dc1f622c1f19608e17727da8ed45c7ad38259694776dd713bef6ae7fb8296455859cbd49fe76b64e62ecb46dc35b2015882b80bf432c04c5e97ec9183d0e94053ec7ec53a24c887bbf63b4a30f3ac740cca4f0c4edccf83663764d3dde5118177fdccb8fa922b500b99e0dde9e98efb900cf727b1b715094475dd1506f070f3ccbd5330c7839d0bc2b1355c6351ed1a4b80b8b4fd662f1abe4677a0d39a82504afd79087554074b98613db55a5161c4825d19e5a173b9486a4fe9722201f8ac91b2deb558a83d1560740d10cb71fe45cbb125f1536f657daab26c995ac93ecd5623d0c49c6c29417475c8b7d7da8a49de1ef82e5c50aa85e92e9fa25bd3b59490ec1f182f90220e6c9d3dda03e676f26ef384c027078964f11b3acb9edf188734ab8cdc889bf71abc4041a7fc64e733951f06b49b551777b7f481df974784aced2540054d8bc14ddd38b8a593657a1390701e14b6178848346e0d2431e3b9f0b83b51e02d49bb82aa8d0907e71362838655a8d1c0b798a4fd0858ed8623f4cc7847f758be0bd1a894b489a54176f34af19786657be77fde0a0f66e970f0ab6c323ed33b9a2bd58d8768f535aa686c97b5bd12db1acc955066e47cdbb8b0ee0ffc4b78cf4e1104824ce3ededa5e168f6f0275c41120fe2aa41c4d56795225e82e18437c9f2464bafda3a7acf8825ee5eaf8346b494ff932c0dbfa1873afcaaf0be3f2d304ae5ac84ac7fda4baf5d3cdd94c9fcec4ea83d1ad99600b89ba928719117223081699196c1f058f0e2f458a9bdc172edcc8d6d833ebc5cf929a0ed3937dab80888a2e19ad5d2c6dfac4d842a8fb5515d4e7e278ea572729b1d75d2b8cbe65bd5d20ca72c5e70116d4a919b0c42458a8d1c5c0db95c7147343cd1ce75ae1cf0ddda17484995281ef575a06558fcbcd8f8955b78cedf8f3ad053482da3ca8a8c4268c6388b257d160cad4bb8634a3be743f3403e6b797014b9327ccc02d405f9a2472dc05830a1471a264ea008c968296e2da4bc80fc303462614614dcfcbcd71ac0a1af1cfc79bc233a4ae3c15691501abdc18440c409000d811e89caaf57c4734b1cda101535ae6a08390cc2aa778a44403e75b276fba8b20949531fa481ff8a7176f0e4a8b527acebd2ebc2af8a935aeab85e3b27cbb2643d4aa8d5cf052add460e5d0d50248abd0a309f0bdf883f843ef7e7219a968e64f9d6f7b8cf3334acd044c63a5316e1d0ea89e1ea13c6c0faaefe7c03c4853750c8bf591e6448a6a3dd0734e7ea2652b0b321d516cc98fe5a042bf754a5d6ed54e28ef1de1e6588cc280b1760dfa4b92d322d025f4c6c0f7a1800b8c9a31a8dbf9dc732d59a5caedf273963ca7f5b83ed9bbc38fff45702159e04ad5a41d9980b3d6cfa40f4e6e73096b17843d47887dfdfa867cc0899b16f57b5dc173d407072095ba490be9601d19560e025affd6c3fd7c72b888ae28a0ec089ea787cf622e4038bfc30c9e3b14430b77d3f9e2e9955c9fc53bec34e6b2a7fd9b6ec69a7437cbd112e3a8c52e5115aa5dfe206b53b0407521bc8c62b86fca04e4960f144d7f2f24972c019d6a001e8f5f0884b4f3ea124aab54cbdc12d6d0e7fbe953293a6c1359f3d2593f794c3a2898ed250cacea02ec41ecf08312b999794fafba93070a0883a7b1524cc59fba3bb85a02942bd2e0d4160f1686b98815f8b96ffccd2b406f581661b70e921a0a2ac80c15ff39c9f133aa0204cec49142024cb765397103bf1f92989af2d67116e133ea8a8e86bd323b5c256584661fe1f366d40e3aa20be536254ce39ae4e65f812459779a91ea84a1dc9f0d58533d205820462ad75f484d43151de8b765bb8fb060fd183739f834c8aa4387e483075a3c89f4d8fce0e7a163d0f7bcc9b276611b57122ade7f5a03916a861be49a04068093a094e11deeb2fa00b21795218eb897cf0e59b555f738c92299d7146995ea78b274a69353f632d5c31c39e4cdbdf79a91761bdf08305938617d5efc49f755aa0e6b5c6bf3223c9f9058b33fdf2a88479e96428419e484bfd46a8a741f31cfc7d4d521c11a9a1c680b1bff22b306173095c6d3224274dd22db0e4fd467f9990fff220b758e21b4f406f9acb6eac1c993250de2d35cda3156d71ebb25ef04476e3e0cbea9882590744c3be8cca0ba5146fbe7f3dcef9d7a74ecb14884158704ed2aeff71871939e9874a5c6606af1f80cf558c1857dc4855de7460d4511126c922215c3e742340b31d1b5c3b75c848a71d71e6f411274e3b1e0bbc306801ccafa0c24e30aeeff05bbf99de67a47cb6f9dd2a471819902dbbe4e13f2b700ec818e7d5cfdfb759c0909b0ffb1105e3475cc00d382ed67a2a9321e5eda935e9960ccbc2da161f65fcc009e8efd76dda3fb759ce9ca624b9a51bc1d25470eaec57cd68bdc9e40406221871d87eefc7e074c5b064de1f97a7f874603671b74156e2e9e2b8416234bfabb8b9b1498df26dda1995549ecaa2d6b47e406daf69f5c8824bd11d2bb6581f3f7012b74ee68ea0624c3ef044597e1bbc7bfd047c087376b1bb49a575e6b4ed52531f1ef9d8671e77c8c6f500a0365ddf2ec8f8e58d0177d8d8a52832298678e6f27781d35a3c4fd4c87eb124863e8f644a01a630ffcd5c45a137d57a14fe3a1e2f87d2f10cec055a5753c351b3355520821ae8e90144e65b3d4410b5046da3d54800aed416770de131bea0df5d0e6fa1abac86944d5bcef9a9061f5dc82757e998fdefb399ec5584834dfc4ba4f47db06991bd1c7ea8452443fbd12c9c65979f66093a5ccbe708e1a5498a1722619a93cca0004c355b70edb15d1f5b3a601aa6b8d647cdb147a034da108e24448ff708a5d34b979327258edeeadead7b2edc619199863ed14db7b5b4a7a54adb74f45a1988762a2e1098d81277134d7bae71e526b5473ed7a8729ddb5d0ea3d3a20a57b13c4825aa64ca96e0909c2384d7b0f30fb3de700afb1887675accb6e401711fba90632520fb680e5f28c518b2784dda01017dc01475a245e3560d324047758ea20dadcbe1fa590b641d3051697069985b00ac1cf8a5c1ff7ac920093ab8606e52264cbf63d7e8c034e32699455cf8d1a2ba5a7f80f77c3795940d8de31759c66af7c333103b7fce6182eabd3fe7f051c0a515c513bc87a5ab8b292a6d7b350eabaae8b4584e6d1dc67d85f9bc0c6e912f69209ecf93291272b659e2069d0354c36d4b744bf9d744e05aa98d94d40ef903cf4a33e4751b66cc238a9fba76e1bf2b136819a3be28e6fc16d8293d73b75ddc691a393f7793154d550d2ef70c8c99cf9de43c1fb58b1770835e819d8c590f9c34995e818d4734319dee4a18cbc9a4372002663e275d4283733c6ad80f69361f768fbd9cd1805e454074d487ebd9e398116f14f22810f94f826c01b903e9b463ae9d050e2a6cc801c4ef9e3d7afa8d174f172e44914d8208699d85f0fd77efdf3d7b849e922f52f24d5238bfa59e9be8e411b3cd0ed770f34fa8ee9d7e318632d1761fc03287710e6f86415989ed8e6857b82f81597b2917434d1a72f0423ce20afd758085b152d02e07aba0998e7c9a75993e3701319e70ba52ee49d07e65bb3491c9eb96cd1247779441ad1284a30be8b7a162188d97628898a318ad09485f419f44a2119d7aa6bb5c63b4885cfc65dc284474847c8954964f07f8db960545d6ed93fe8aa96dec8cdbb208c8963dce210dffa58f45a63923feef227c4f48659b27a2452ca04573f369efc7cfaa39e90f7bbc926617be5839d9aec0b85b0765f527ffa39deb652860e5a4a53ab64d6cee1dfd345a0cf0f7f7a54a120d6e3278d78ee1145c456ec76d58f454525baea32bcf14952008939d338160179510ef2ed0324525a2470f499733857cbb5132c3bede02842e5b5347186e8f5b13c3aee74af83818643eb4c23e7f10ee10696b90a07df12444af27d75765c52dfe9211869af2c1dbf0ad9bd768a31c9c3fcdbcc3c39d54a0b24408969c8ba15bc31398af27180bfdd0e47009e8a1a553623bf67d2f281a428e1ee9a70fbb98cbf234b4a0814be188d57bdf432380d3a6a6b1a800cda084184ef999d3843bd8fe96f4ccd7042670597696c382cdffff90137bd36d7bf6cef744ac335c5dd75ccc5250a954a1b600200a758f655bcfca7ca6a2448c340b242dddfbcc5b02ac6c7b62280067d49712319607c255c78b4466610e790eea16ebaf9765fa21c5a198e64260112c05ba9382fcb9573bf730504a0b59cc16f914fb8c640d9009bc94dd1128b260184970ad6d20af4476c883a557250abef836ea1a5820ff10456a84b29c24ca66aecb31dabc89b2d98c4dd50cc571101795fbe9677c2eadbe349b1fd22ad5ba6e6490e96934886376c79567291ccc13c10020c25811ea15691655a0417566a5c163dc6c60336e6cb08ca53e903ee50bf82edd7d5508169b2cc2fb2d9e9e79504455bce4ffa821b3bfa8c1f31faba1f9dfc81875f7b2f2f97fee01ef3f56f6e85c3cb428310494dea4454f03a1f182da2387a597878ae59f070b0b640f124be61e2d7b6459dac9288f438ab90b7f22cd750f74209e10d773bc2495909d36a13d4d4b6a3d5f96f83d9096a87b722d757b542efdf670a1aacfbc40b7f724b207ed9b0edaece34e7d1c4d651552af72881d9803e3f30b266654b461d6259f36125db2a50da697d4b561f8259f3652a5b6db06d3efbd6d24452f97b4ff27e13243ef683da48c43c1cc9b64b5a176d97da9fb86f02816b329924411489c501812d3a8ad606d0146113ecb42bc8b919cbaddae0c66948909a629a246d0683b65dda31d4605f79413619c505c757c809ac4c98c7780d25b9b032f0e313789efa0a4d4e017b329524018d15b9be4dedc4edbd35e8e6568bd34cb99bd4dcbcabd3f9699bd302e137b732c37f56a2c7ff6722c43eea558ceecede84e60ef233ae2c732a8f6c9f92658b9894c8f9099a9b60eeeb0c336dc80f19d7d5f01ff46d5f07393d53373b7ab2b717fa933e5865227e6ce566fcaad56ffc4cdaa0e999ba49e39775b5d99fb4b9d393794c7666ece708a19db4b663659197d7765ccb0571031c520420edda58622d7dec7e859ee4324e8dd874dd0741f3a41cb3edc04cdfb3009baefc32668dd8749d07a1f2641eb3ecc04adfb30095aedc32468dd8771da6509320ea2b50a79f4e6ce337e5499f81713949d4e2aa5969b2ed1e457350cb309e37bf76a8b26acd7e08d4250436914111a5a5020d44082e252c352148e1a84a2286888124553031214c41aaea258d4e08242b0861214911a5aa240d440f2e4510d5ae2ee01b89ff6b6d23e1967470a5b710389bd8a5da7bd22d105c84b6852c3703faab5d09075c08e60a65707108e6cf751226990044e47cae72df795af1f7e3ea1c14bb32978fb2d28a18b0c6a47d24407e86db92a3f3655e1bd88594e436c355481d09f19fba94f2da6039ececa6a2ba1db369304359350a72516ec7cecded51b1f4064ba791aa75bffc0da11fee13708c8b8fe9057483cee7b22cba86e228915ebb46bd2143a590672c82bf340cb89463f793628d9af50fcb3ccf3a6f0b8bada356f807fbe0afeba3c173428774d5b014a5a52d0ba091adaf7b38b308133229f6ab544fb40af14949c3dba11da660d92e10782c84df32ecab758743b1629114645abbf4998a1d0126ffcc3a1d0a04dc094fc10e31d7d7de0fc53c855d810ac644a513cfedad39bb61fa8218009f4549a1f1920122cb3b891ded681030a6e5060c54291c8a20af0068ff9f402097710e4155177efdb30b243496212cd5878d58650fef871238488f6cbdd69070db7f4f8a6dfe1baeea6ecd48060088d15101c0f9d5c2b175389f6f2d540243d884f0d4c47e0373273a72034ec36ae693fb5837a6a3bc959b85c6f026966187a10da300f610d99c0c3e17fb340959320be4a683ed3b530f8496ba55a0a18c527dbd8f347bb0e23647156a450f3431d6b8d4bde9ad9c55d0004012cc62418eb10e39509e6f77f6afa0c86d96fa2c3c27523d82fc5c040b2cf6a34bc06d9c26513a23dd1771a42dacab4a0c038d99f61c00b575eaa0ec8dbe42a773942d82a923816423a58af41b65d0d9528b05dd75c7540561e1611825c33ea0179594483e07d108218e109a7d31460277728bfb887a89155ea5ab6e5ba8f8a83c5a70a6d376ef4bb60dbe55bd971c4df31b61d48189dfdcb5870631e3257e67b5248517ace70b1e4b42253518224f0a8558b7719c4c91b9f837b10637c4cae21c6f8807fee5fb803fa7bfffed9aff4fb7dfddd5fe8f75efcbfdff41f066e8a505cb01b44e6242e3fd2aa67ce67fb8262fc0fd4ca1be1a153cc366ad50a9a5bf58296ab5ed0ecaaf7bfbdeaf5632ad5c303e73572c07558b9880016a73859ad0daa24dc943d1d3b0730078a86e85d311e69d769b1a321ed96873ac9775ba1240f040c27744670d94e8209d3049053f4c9a2850a4137916854b56a84aca506bca0d115e44185048d4dfb3b3d445ff8ba469a36cdbea834cb51a259105e39b72c1132aa7dcc519557243af3b5427e3df49070421cc0fb86d0a65a0059a4a4bfb360f8f49aea084dd5ca39254d8d255aa69a030bf3321dc609fb0966247802d67e10b5461567ff8980bfc97c831d6aad5181183f71c0ed3381b7e2d0b461b383b07dba7e8d2bc92670f843ef8d20f3bd74163e3aebd215d8c890036988238459bfc3263b6c3b889aac2a461f0b643f3cdbb402773be3f5a879120d2393a1c06c8b5a8ff8884d14206f8bb414c7f7bb5b3cf29f8472ec94409bb3e3083f64efc0f9e6d220114a6a02770391b31b86f320afa6d3d819bc6660a35281fc9d170d061a10796ad6000a8c8963684011c2a4395464856a656eef0193c3b662709eeed769bd3e60741b0ec67a837b4feb9f8017f097510e63f42b76fb0a96e306affe1b5299b0e38f9683ff51bd6f4144f551c2889b1838dc22db9a3b1a742e75ee1500231fd565b5adb1efc7afc6600cabdf84713377be876af28f2ccdf3cc6ef971b3ad034709c6ff8b30f1ff76bfc6b81541088d6f53d1f24d25a98f339704038d51be8c4e0ec2745057192a9c444530b38ba8d57ce150d15774a8c0654cf19debe0aaa829a6a8427bbea12b4f1a25b2c63a94b5fe5c9429ec51a617e22b3c2305c05f5104ad034746582c42dccdd1ac5d6ce523a2b383d4e98a83ec19aecd590f1c6097064cf7f0a8bf08d1f78e55b0fa5250c99ea43407d706431cb42248e4122686fc1f674860fd0765139f1526f451b415653c9dc1d2cd5f9fabb4bf2a0d44aea6bf4efd13f89726d653f60e3bfde2c92a1b871196f7f997e85a93fe8ec948d494be0597f2c6a873f780c8ef3c8238380acb747b462003e6463410a1fca9ca27503af986504a7365ea451c3588a5f7fa23c4199a0b1e6661587d821320eb323998176026156a3a0dd04495acf90425724b9c67f441b9413a1b02d145921929d5054711fe604cca4fa26084ead2912aecc4c16a261fabad8747859d19451036ae8d8be5fa1f479d40051d1144f6be80826c7846648a34df0af6f61674cb0d56adce67d8eff71f9ffa18e951bbdf9dfe66d671a0aec902c08e8fe2297c8edf6b823ef1010f7f71a44f2318612539b58ac033df658c0fc3b2747cac106a417305a37baff069cafe733c1a19067707713a6d543abadbed259b5f74247702e053801dca07add35e77ed4f4491c5624ad0abdc05c4d8641e84ef293327c875ab65653d8c81443a571f8e5f74e5a2f240996c441eb6567d64349392e7bfd5e513453d76eee12ea57dda92c2bb35c99f8f44a18ffe04e5aa8be4fa10d8558738bfb1ad28bc5c357cedb7e067861f186dbbb3a245960965927901f5c23f9004a2e27005ecb7c3b9352d4cb0c0b4d2d630d8e32371a64377dbd2af7900d45ecc30950e2643c76ccdb546daec8e6223e2bc9bf8b5425b0a2d24ad75e055472234cb101a81e7180dead1278b1e09abf3c7355aa4d5c0d16e97e81a711646c6975c6c3efbe6e8687a5a20304037f3e9cebda2d2978bd3028f7bcaa1d09e3889a49472e7655bd50428c67731fd43fc2443e0fe4063a48b880b9b75015dc58455fbeb2f81ba2923730cea811425dbd4dff6cb20e76c33e8e4f528506721fffde6ba9dad16a32d3949b4ef1930f8bedc94df49b7c002e4285cc5c8124656ef9021ebe6b9018aa1a7481daef874c19e7d710e52d1640a4809b3490562c75feba6ead42d17c6d2e1fef5aaff8b7d012ca1aa424838d4280e5052d2ff93cc1a20b2394cc889305e324ce2b5c643a06051cc679cbfd0811e503197350f38ded07eadd56cbcc0f9d222a21f14be5741b8ec479bd388ccc517c4d42908d3c398cf37b2ac243a6c78ade57ea7730c51b30273e2e5e637363ec74567635c6056609a3c6b59eecbede479deecd0b2a7a4655f73e3569f2ed324eba9e1ff1520a2b4905af6c0af514daab6ac69232e5637be5091385079cdc2248c063ca1b985149239f1433b06081381f11b81b3a968d90775d6f3e55442a4b8f3f3184fd181e4e6762f0afde111dd22080814703b1e8886fffda8de78dbd5eeba706bb1ed45803c233e05121f04a613dcac7ba7ee2b34ac59a2ca9f439ff06f7fd158f84f8d508a3d4aaca7505c7bd07b54ecaee5bc708619bfaa0ed09709df31c6c4f21310f11bdb3f2bb89df2ba91ee7f7d425116290b19ef5cc3cdf56404b46951c0c8874c678b38986c88b2fb6b2973c13a57269323f5ec79f876612d4b58fb91c44586ac06cedbf7ae14b67fb009cdb3c5318fb90adfbd6c6c48765b0b774cb05656b1ec65762fb6d728b2aad69ad00bc8a0f692348357eb87530467343bffeb1284262a3fb3dff9a8b075fada0b76e05065b0307f14a765f79af62ea7ddb916e23d76c7dee007dca04eb8d8187516c2ffe7f505d3f5abefb8b68c124288937dadb9930f77d395be797c2a4a7e60ab9aced9cc3d4976c77f1682e847b27571f284d8f3a514482b3f37b000b07950c45955f900f31d95c937b99bab8ca47d1e7c83b0bc459f11a19f74fe2c695e72bc8ea395cedac4b38abd2ff977eaf906c0c43eeae22a93032a7870d01e19bfafae85ffa9a0f3e069bc0374bbbe968176173b38e4c5383a35d80e4e863500e21905f53875dd4d78a0a5167e30fe1b87d34b058bcc55e4e22c83fe4f84cac685c81efa79ecae4a310b5946793fd85ad07c983daf78b0522b20105b88e251a85dc8a7348328f4ee44775e3e901eba5928c2e4a900c02be691f12fc17f014a3020f68370e63c242f120f822210281406693e1e897e47446785755bc4c3899b5f5220fb96a21742b45ff1c207c99369b21494b365a757307b070a11cbf3123ecb1bbb242996e907319e05e397b96caa6efbb7658ba41e4135c5f9962f7e0422be3ebe65e6d6cf69234bc15fa008f0780137fab22d3b01f08091f96e154e218c8c7c25fecf1851bcaa7a87a93572ee23fd5c07eb0bee3eb4243ec8d5d3fcfa0ac38931664682b7506be24d5f7927abb13d5220ada0198536889fa4369e97f1b60dea267dc127b17238b89b29de63cf74d19bdeab661fc9728796dd58b981f0f37a98124019be0afeea881ce2a58da5780f662531b30dd0e44035eff384e77c3c53d7204712b82224b5cd96cd69c6e05b61f8a8421f006741af436c36b573e0c32d62067db26a0edbd89616698bd4a2d7a8626f15dc670aaf110701e6f24d03d90aac01b7ee0ce42f957f7fc73b9c40d290d5dc885a1e1b911b9211398e8be81a3454f1994a26a89ea276f884bba7e8290b0bdf5ceff1eb39e70b749210340053aed762ececa2763938c8e3e1624aa79e9a388a5078631b4bf7b1d71b7ff492b03879d24f7d4efe48aced9423421656f270c5dc9b4587fce4a1197487ab5b92b7eb079cdbde043c6b60ce5c3480b4168ea0b335c2e32aebd5c4adfa95ccdb8796f854e097f0abeb4cd2a85d0be6ce5463bb14de77c2eb2d30cc17ea507d6a066e8727d2133c62a286376bbfeb5be8f6d4fe8adc44371685ebf9d843edde3cadb992ad9eae20272ec18fff90f44a602c13a64218756e0affc3ec8919d70f11b86eecadd3e481b5334de453a4ed9ba318a7df66a3366e18e16ffc6386452e0f1cfbe6f61e41b5b6423fb1cd40ae5a884dd68c2e396fc5041cc957f4827e8b796268b7c97c25f80ba7c6f8eb8906c240a52162402c6669a62f6953c77525d241e13a0cfc42e8e9107cddd492b08a452e9845a16eaaa942df3428150915e8e62a0dca2a6af6b7cb3cf2fee2a267cb831de31f1357f1ca9263a3cd0c05c87264e20f192dd59412abdff08bd5bfe8dad4ab0674a81cb631a1a202309ffcee12bd58ed3ab9c01585c3b20eb9aed7ebebae70c6326ed8eb19c1b05d48de221504784f30282bfb3a52e7d0c0ca25601abf79eabe090855f26880bc8b7f1d77c6514ac1f09ab2d29321de96c570d726f6467cde180f895ec3c2fbeae36680c66c08d053d7816d484578de03d64c22457b53b8cfbfa40fab9ad1cfd93a8343d7db99529b5cece2e0853bc7f0c20f348797e8f3bb208f2949e64bd788c9626880e8de2e21d092e906cf31e2bf756d33f27d8cbbf2a0a06916ccac5118b3cf9a15dc265f61f874637a17b3f026abb91651d02e2f74be731cc86d4d32e174bf2bbd84c31a6ae5d3f7e9a129d24422e2b61611f852a505706abe7f21893fd5bfb0c0f606260a06871796246bff54b9b1fb4bf57cfc8cd6d72ff3bf9b31471c84c1a51be4774e5c0a17911e8c35a3f4d281a061e94bb5a75df2b9e1f0f21b6bcb0b38a37a3ef32e549b74181e32230c950bb842061fcd8f6ece4b4178ff27b8b8c17da1eb987ac6c1ac84bb41afa445190652c6d8e21457b8677813399d59d40c9d5e4038edd0eb97992cfd3607ec75d1b5b2ff97ae76d93a041a598b9b42ae5e2a840a72058ab80e854af4e2ecdda6387520473db9c345ba22eaa93d33d6991ffbfc996ee939d17cde47d2d4b73b4d10ad6dc0762b0b626a8a411358f362e89a942c7a8b466cc21770b5061bcf26faa3811df30592b8194850377900d4da4dba5196c6eccab2cb091bf4551eca27df5afccb0a955b5f43ae71df731ec3b02c93ab27a000c7304cb3011bdf2db43bf375f1910d58b6c44e270423b5e480160b8301e05b28c0e2404d403636e0114aef7181ca5dbf17a0e274f34609e59fb6b023a50b969862209d4a0e0d1021305ecb60a13fbb58ce2f81306dd7ab2181409975458129d875694ae7a13c1a35495fc76647f80b310acbb78c4ede3c2812947472f011606919b6ba04dec376b3e734a07b506804377c7ea4f6120c7d851690fbd561c01ae76b8b79e1935c122910b263ec61be06c75bc2c7f611ec1508971615c22641ef0e0e3ae003ddd25a4ccfed87b82815fae19da3573c813479c6997212fe2b215be94be2921a73dec66bb97f25b92510bffa04c25a85bae883704c91cbc82ab3fccda12732d94a36b29823ca4746affa834bc7be26afc809abeb9269b5493fa9e9fd27794d1711a85adc3a93871591dac2ce4cc39a8332dab7290bd7423cbb63678dcf2dd2ecc72f243ec9489834080d2c2ee06f4ea5c20d69b9afee922fe2ef3e4c99e80fa54785d4d32990bee9051f460fe02dda20af7b70bfca7a1e3c1b5c42d2e509961a102745eacb08e8b3019e79dcc019444bd2042d4cef10f1bbc665ee2164051a4adf0d4a278b97dc50d69d387b4fe0a015a2d8d3b95492bbf54b10400af950c9cc5b7910ebaad51e09a0129f5183660db0f579242e6ab4d0e70e98d0590b427ba1969f05e949c2d28061351bf832e63c134bd2945dadf3e36823faa5f19bcce6b8ddb8af81b643b892594f0a4c35ed9b77383083ee8e06ad155ff61359a939f6dc39ad6af842b56b4304c562089a1e5f2b32d21a8a6cbe96cab661da67ce0baed4719fec895af4179814f0741b893bfdc21d275a4ed8a4d0af98d641ff746396e0d669c92fa6ec5094db8a3b10ac697fe3ab8aac1d4318cfa36a0f56e91f6de0579aee033f77e9a757d7284368ce5e8b374a08540c8bfa08d55ca6fa036d8c30ff3d6d8dda98c2b4e9742c68f1ef89c5935b0b930791aa39bc756cb2139b6114d856d9ac9cfa940abbd0abf6d959b56c169cd211c2b1cc72a89f43fb52b0e728ccb64d4b6b4f42e9d600dc0f8542ec300258f2275afd6c32860ea73f82f1d77504183906a9801372d70f56fc6700295b99a83e4cbfa524045a33896df7015237377d9748a0341e5a5faecbdd0d9f902f429913836eddcbeb276ebebbb1d2223beba63e69fe30298f387d87e1f04755200ac8cbc93529bef2bd80962a25ae865e4bafa033bf29b0924957812c228984c2738812d203f65af8ce20c281e940e9c9f0c6b99c4906ff397173ea1f815790e3a55088e78d3fd82f3f7638e30cde40384373c1e02477d9579bd6de1993560dae29e0b12e0624d3639217ed873c6926b3460688d428fad0aeb687db9fcc3b36af336f75806f920060b7c3c01ed9e76b43a67d31330f578187e684b9a7cb0773178070528f9e1be1f3c36a96f3812050cf491a1e414efda1f46dd1c4da13b7dc1614a627d27366d30dfc5e01a52f3212eb208398396a4818d774364bb814be91fd7e27f36f2ed61cbb6f81bae58dfe61a7b19a29978c79f9f1fa23c3da26a9124c81bd8a6e7ee09a5e1f4e6056c1ff6bb2a9131d0e2e5edf6034a59d4dc23beb14d673b55ce77618ad1e91b88a8ce0955291f773ee8be47fca68f44c64612a62db627296d50f74445b47222151142e0235701d166872d863298bad5d9a9bba068a030bedc470fb834cedff4dcca543f36e84515722dd1cdaec754a591e2b2ffe70c334d0c4e12e10f0fe9fbae674b9e743ed0f59d674a45b17c44de1149d4a0078062924580ef266394d70d4f5d436248bd5c5c9156342b9fdf0496988349aea451d356ecc639f8f2322aa0c354933e1aace38c4602b6419d424e27e873cd401f4d74bce890a33e6b831260c899a1be34f709f43f68d58796b80e3fc8d3970d8a29ea3d70353f550561faa2a76315adf09ee7f9414669e1ac9f87261dfdaa4b5ceaef76f9d34f9264711033aaec27ac1e5998e95ba38da9be1f83736f0b9057d0f6477ffff38ab0b9daee2b907688bf35681ab33372cd4431464f335ffd6039d1582957816b91348c0a66623b9422795d797eb7a087edce1b35e0debaa8ab2d53665c8e43880bf0fdeb1a0b38273e5bdfb6c16d52ebcf28db601d146b4bbee54857bec63d10fc2bcb486e7694eeacca8d91307cfbec3935d2d762316c5616d02c1f91553520266aa56949ce2487dd2617ac020d83e9b47f5d677cd0a455a24caaa1244126eee3d6c6ab6d94608dc5d0675658c3861348aac0f46deed4d0e19d1981ecea73f28e2854c62f5f7f5078869f3e18f7e77b2be0843935d50be495e9b5635978c77aade571ee4aac066895332300435e00e79418362cd2cb03f4164a8c1a348b80deec28c1553435e97aeea1a4cd3c0662108b32aeeec32ff109f58914e20c1b742c715e1b50b8b47aee944b64b11a691454887dd9a2316e7f7cdf38d195a2e91aaf97a46fbc29efb5dcbc63c89ffb5143ab8d4b243470a158b07850bf1084295148b422e024817830aa615c8874c208a8537a24385054725d462bfddedb83ccb227cf4b34287970257f545f3d34148e8e6c26fff538458b91ecbfd59229f178a2b395d68da5cf4a30ce4452698f0135306e7d7fcc0348ccaad10b25df686bdc8d131c46309012e115894744663756279538b17891640d460680f95cb89a5ad0434e13905bcf9da7a6fafe672301afef0c3b72d67aedb8ed8f147e68a164c60c4b65906765e564714f0b0d534ee1f1f91da6dfbe05f3109670b76be344f044cc2690898f25de1183e8340e5bd5c2a27a4b585e32b428d90a675a67cf610fe5761228b8e6c029961b36b64a840960ea1c3e9529cb1d6ade5cae23f6bbd671546d28304979c51f7aaac25528ddac359a11f8cd632758f401bfc44fcc19e76ba8832bb11ac80458cd8a6433b88821cbc80374b057dbcf4607d0072504c14434cf61de7feb2515dd597999f9270227dd9d7c9c242e9d5d8436a46d2195accd86d90c28ec236f44864c1363b734f6570ad865fd5edf34f4ffd3e6493b50b84509bff453382097ccf9ada958380befe2966463f86ce83dec5886e8fed484b62352fe2754bb457f411da78891766bcd7832f9eaeac14e4dd693b8c92b34ad0d821e81573b7bbff2205901092429cb9e2d8c841b730abe05b9d32a3f4081928fdab4395960d8a26e70b81d73030ef2a1399f708cc4663780716cc78ab8e56414b0f1139611735caaf157a9f7829f4595c4bcf35fcc61d67507d08f0aca0028e4f508b4b052737bdbf46a0f70a2d9e43d2b24b07dcc6f3052a2470da80d2c50d72204dca81f9246d4034838aeaa262608096de97dfdf00d491f0e8be274f02ca624a3fa3f5fb46af1ea3aa57e5f70ac1d478a4ab7e0f84460013ea1ba3d42517f185f1c7c854681c4218ca810ce011c30d5d205054c58812d1ac946d38671a00b3ff3ed8ad2be3f4f981f8c59712f14b8dcdef20d26d8082b7f139935e9cde131c350ef97b1f3c29f459ac456f6b543bb94b1407cb50d0ae19f0ee428f79935c73999730e15164627a23e8b7fbbc123ecc78bd505090e9a20c2f485f0cb8e074e16edebb753b9c41b74366539b9617f1a0753581eeb44402e93c5cb01ee8215da3f4eabcafc2c639ba18e55f192b29a24f8011a54716beb885ab10a79caa3bb75a690bedad7b9f832fdd5f112d579296fef347fb487e506d2d835fe05988130b3c7ab8058f735360287bee1775848e5400b473d8dee5e5e49e088065727d72673f788ad2d63905209c9c90d5d7970b96494a2733abe136454a48722ed19f392a6caefbe082c109a568ec110cf146020410e6ae3584444cb4d59754ae8b4d22d52c3bf7f2ec907dcda65e4ada081231df4562a7032f2c4d964d10b8a3287c615e89151649e08b97008e464a0867e15286277cb9492b71abf136ff0497ce8808a8c8cbf1e52f8a856dab61e56c1755937951ec7c70dea12eeb5ae848384457212c317d46c942f377f0463b30043b41f0a0f67bb12f4c0807f0c304ea2005dffad0057602fa7c633656d18bf681bda8ae2f7ae73b336c2d034719f0692398353aff7d16ca0671c61d7f5adbc448a1cca51f4de6d571c4751706094f1be1602be5664f6eaa6372071c8ccea8de6a2f06ab83c2273be27727082661878944d76228ea601c1531a01807d7c2f9298f0fa4eb8a4a2f0fa78b91ca7e0a9210dcce16249532a4630746383c072e303df59aa34a019e25af6f2dac4809a77d6f90a29ccfb9f94ca4e3d39473035e61594a49bc296d3ae5b1a804a7323a972a1552b56aa68f12684519f02be794d06c5e9849dd3f5150beac987d80d2572ba1103230c1030ce349618d8519ca772d9921d12a89f2c83103af0fc8b5d189dd90841df9c911ab72d28319cad9b544edabe35886309e107ccc09e96e3284e0c87948b598caa90b022fef9b03726c59b7f23e02bd34ebcc9a185d623f599527957a4846003dda1bbdaba360687323df9d5e9f0a10c911d45c4fb8bdf0e8557fdfb45323174f49bb33036a7046e8deb57e602cf8b890657da8ae5c1cf97c763bfcebb5e62307de5ec47f0a7c307c61702dff064907854ca69a55493da7273bb08222b17eaaee7fda7c0a18644d84505de606da2be649464765d77d111159e96a8c44d66b4d59bf162a1aaecaa8c464fea76cbb117b55a71c378cbdb38edd1e8ca58e00d3c8c97bdd8f12dbf147c13c01a4c9000e26788631d62d3b736036882f57a397a5ee2ebb0a9946b6aaaed0f56182d795941216a947afe632b3848765c00cb0cab8aa3f7797978d3bb54b088a4058bbfbb660377da40625952210c0e8083d1623402060ebdf8dc697ef93118f15190f36c911d9088a78ddb18ef7b6a353d16bfb28952f3df6a520abaf632a00a5115ac909586741495e172c1862cb9af65bd2472be1ad2c5a96093ad05be1d113c4ee6ab50e50d766d3a1021a790b9d20a1b40210d2c9875b8ba95b5b8e1ec3e2bc85d7c5e11ed456d4303b49408a6ed7d5844ddfc2185ed7bba8a265c63e47992ca6fb0e597721f44e1eecc1100b11e6f0b96ea0e4d7a8c35d79e865a32de001606555fe5c25aab513fe434779afb16190168038c20cc496e114c7987343d8adc9976bfb2e689e1e3da4af10f6b6c7f726a9b9abc9339e988169831dbdb1a52af73cf6cc7a83b2f18f6306bcafacb21fa1934c92eeb94f26e9b06b128ceee241f294056a7160f6273c071ad600cf1ad829f164590cd507011264bb0741676e4008d28fece5e0a8f59448e4b35a8b735c1ea2880333f07033d8815bf022e2285b0ca525ce3acac512b4dd1353e1cd97140886b1d48d03a6ca31c2d0b83bf98008c8ddbbd66a6bb192ec0689ed92c93b35f246f69e8e946e6af5e81ce52af6a9c2590a2790e46f6ccf38456a017aca453e93560f09b9981c2e422e2c834864f3545b026987fa5a01d729ba98fe84c0316946fcc4371653dd99f2e560ae0b4bc60162412b0bf3975987ba294b59e2877ece0b71b6c448dc2d99e1f856e1cb4b84699ec1311cea4d29a2e3824bb703480da5c08efee30408bfd6b9272481a39caed7c82402eb3988a6893a3f5f8576a84946a8e2f3600f31f7f2c9fc27aa4a70b6bba8f6609764bb2ae05e43411b34f4ae1e665084258e4c0904698c2d6b9da455f2b3a68c876c6ac722e7cfb22d658f26fe74a9b438d48f51f4529e02b8178f6457c832f4e8ecd3a7c762037552a5ee8b7fca1dee72be675431a99a289668201f6dcd4b37bbce033a3c59e5e115ca00f7702a86df9b5b27915122a7357b4a16bae0ccc855103271dc4dcce6112965d42de6ada72f51909a08351900146c317ee13b27d8bcadc42a860b1b0472ddb272afcdb71e89485044019a3ec47b3ca960463d651eac263959f01380f8f919e2437872c0759b8e5c09264e0dac8e027e662c2abf75c1b1940c21e84e87a754d703713872cf1699547109aec7dcfc3238ff05873f96c1554eaefd0bbe377a29e80de5b85d83700e540b3b9d3ce0b024e41104ba6537aa5820da19a703cdaa62056cecf1c67c309c701df078e6b31026ed56e5e75da84c77bcc89632450232d54fd155fb03b19508b05002a310906a7321517d409d21fef5df3695bbdf86bd0ea43c5562c30411d9ddb6dc52a694640a3f0a930a7a0a49baab77559830c7a7c6777777ff23dbc3af9478632d47f7ec6c867d0c2141fd7d521f305e89359e34a89f2251047586342258a1c41caa2e939821478d31ce58718253da345236ad60819af6464ec125205c6567da9a6823679ae60f3346ee1760de63188691e6d2442a1f46397f985e029991b46dc70b368d944dea038aa0a0f5b943c690109eb08365825dc7d102a232658b9cf1de8a5c2959ca28658c314a12c827121dc3c14475d62d7fb617836c624448d8633f4c4c462c462931a90452d65fdab4171489667452d77b3e856ac618e35c4f6e552aa191f387b9bdb0756d240ff5fbcee380f5e2a3e20ba5336872386b2576d1054432c618a39452f20aba075ac728a594524a19638c2d85849146d05926d823c5467bea07817888c5253e020285fd3c94f1100f4d3efab9d19e192c7105f6386b0510c208218430ce28230ce6466512ccd2a13a91214bb22c215a32549d159fb92f0956fd21843470f5603761a8773295793217c166009b4c5af4d87fb0496d9e73a7a8a87987818ea8e4ba39bc3337ea10c3a28c58ef6059e0cb5f722ea58cceeeebec316e841c9296d25f3e74a34590c51e93f882b9517e652a7882c5abbe22131cfabb7b978461779e654a39290a4b02859a1b151a827232544a29e5bb9498942e4991247f3d967a786c1913844390ec52c2b9514a29254c179bf5c984978c31c618a5941247062ed7faccd15bc818638c314a29a5143782a1b880a3ec22482388e2db8821cd1831677c4c76362f6463e3332c903354df81a33acee6f3bff9f25ff3d001eae8201e1c1d280cf62f5926dd3413a9bd1c1bd4cdac0659616b6bfa4b2468ed30b922b2ca8e48890414230a7a7102f44550955c34fc7e6805917b9c3366934d36516cdc666e34c610b988d18f986a8c928a18e3a909378521a5134fc5fffa6408598b17f8dc286b21e4594c76648c31c618a39452da3cf119a44c22e7d87d85aacb44af46e832665294bde8eeee32b6df444d34708da62db0eedb24accf199dd0969312ca871042093b1a21a53db06c73229b2fe3cbc766b7af611b0efed14b3db0c6a45319d316a94829638cac448c3e027667c87d53e3016a1c4069b7fdba1a3f2e25bfe66103d4d11f9da06c803ca00ed4d16f32c1bc984c5b47eae6954a7ac9f97af17968d55c9ad86f823a7352ba03777a073939f84a7ad13a6e6be277a4eebb40f587ebc507c2258645efe0cf0b9c4570f9a432533024647a073b14935366922435b94919a3141a236f949a4cff9e874205712224b4519b1d2fc28c9874f7f7624449ac0b82094921a1fe190fe551b0852b7f4eefedbf444a229f40a1a6a99238b842f59c2b01d86555ea0cb03451013568d30038f842264d28312c4a8ce314f772b06a7bf79f476c9acc49229988b4eccbcb71c4c663cf0676d99e4b3f622a94d6afb66e07a6e92b70bf34de90e9930336afc9d2073dd18b5adc622946295b40a564e7cd64faf73c14cac66688cfd090f7cc209baf7dc47ebd38434926ca038d5ee4a6324bffa2c52998905e5ed3344eaee2ca575a6f1ad72bcd378de39526378d83ab12b7c90702137fbec3ffe88e27d013101823f1e917bb13adf33f4a2af590118b353edd9afead293881788d2fe9de2ce504b411f85eb77e303e0ebb11f8f1bd3bfdf01adf6b746e7f7dcec738b6dca394524a19638ccd33371aa57494ff6cdfba787ea64419aa47f6cabc828357ef41e95af966d81dd5d97bd97254a7a4ffb68a55ffb83c8440df1bec4a75a70175f7652132a6fb4c4b4d2fa58deb53f6481829e388b0d715eb866c4d7f944d723dc0b9d12b252a425d5ca930702ab141d5dfcbf1cdb81f99a90c650fd8f57a977ac8ff7e6d548fc9b41923b2281bb03303180fadda3ac84af5286dc1da6ca4b4b1e12a090358e9c7d7ecb509975fb46eb78644ca7ac46e8b564bedef8c3126b8628c53ba122637794e789e6f3fa1ad9452c640e73b16b7927c79c1482e47e05b943039355226a794513a9531658c5101793029860e5ec293d2cedddd1fb58355a5f7df4e229fd88169fabf4d9decda1f720e2184117b77c5103b222cbab92f0ec9b51e3b4f2261ba74e9327d90b24a9cb3e3dc4a1fa594b2b4a132cc83d08a3d4a4391509913892e6397b14fe10546fb48caa67ccd378d944d140ff451933b0c7e8f1d9d7f2b976cc881b521e421a2d8cc629c9352938905514a8f95480aa29991668c2065267f9b1f9bf5e933444248299f638c12462965472b40992d605d8c31ff79f43f948cff6c62fcb7ae17d306a580ac173f3edc940ff67a8b2af693ba806e32b8189cc906c8a634b05e1361afb499b48e2483b4aa08114efda0814d01b15e0c5797263e0ce771585420fa8bb4cae6074eed7ad35597083930a8feb04a8e0e1a86e3686afc35000ed51f66dd733562a0fac327f273a35b946d8112030e688cfdc055a4ca773f62aaf29deb266c131554ca29698fd89d9c78ebd35d069a0d8aa46da5196440d9d94da67fcf43a16c6c823c212434434ccbca1333ec68d0abf092242d470fa594d216accf1c359e307a410e9fc2f424e39c93e161d773251e3960a2417c3860abfa0f3940395472a654ef207cc97d471cd6df1a7fe89877cba457c067009aeaa68f729bad1527439aa6df145fba52f7cd2d8618ea4765cdba1f4a23a0f0872cad7187dc7b805576356e7016e1ff91e3d7bec6183fcacb3c59857decbe24ae0435354dbf0c0c5ad008bd015dba74f1ea7567fa68638c1c638cec0426d9a3a0497fe8b0d46362f2bd63a23552168954f9f1715a8b5becbcd483df25d63986b1173b9451c739bfb93941e5c7ee9b1f8397eff0096d71288c8a1f464e760b7fc71328c40176499a9b35967c6c855d2c01d92e409274a90628857579e7d1dda33bc767c618638cf993891bb1ece1aaa8fb84a911a67e94865865e76d4d3f8ce7ad4f19042c71450a32ae1d48265a3254d9c71fa7997bae431f18507f9f506afc2fa6ca0e026d4d3ff30028723dc0fa41a0f5097da40ecf0ff461a21fccd803a13fbbbb7bc78fc31c343b0a9b7e26ff221d7c715075af82ba0fc117047a85754dca01dd57fd7888833a0542106272508fcf60ae63a5ffe8f69f09e6bf5fd5cb7fa41ae767a82428c744fb207860b158ac157d0d869bf1f2428d01a840898ba51a6718a9fe839638b934f1b7971a3fe37cc02a89c657fd7f60553ef683d6ecbd044456d921d92e95d4c5ad8932ebbe10d408975f66e75b13bf582310d57f13105d8947d4e48882d7dce28c524a29638c11670a97bbe646d751805109ab9450a894914b3da27b1258e543f9fcb2f301d6d83918fbc26646f2a098935293e91f6709972b4ae95e7d0521a270c8f88c1118b61ee61553e2476670007d0105e7c0cc10ca8b012966537a703e918f5ef56e08f67ceef28744312487d60acc103cd0c2d74a4f5d00e4386c22e4cffcfb1153f57fef09f5ee6f6f9aa691cf2528930aa69ff14c0330c2144698a65a8475cc3e3e6dd5f6f2f1513b30ac09c576ac176d24875a6f879217ee4b9ea3368e6a26129771cb039511930f8fd8d4f8b00424c6132c5430eb9622076cea129923d9a3f8c8aa7964734286bd76f7ef086f9539ce87a51f70044ba4f8630f5f3efcc861ddbe7663c713a8ffd6f8b3d423da90533d7ae7f3c86684298c30c556034e38c5ad1ddae75da2d8d08bbdf9e582cdcb00d42a197e6d3a195ec0829efa6d3e6775eaee44011e2fc30b51c0db744aa4f42be0bb13c2e36578193a253c3e679504b5f99bf72c45bdcf407b23f7a6eb3e5759157c35cdfec48212e1e324d4fdadb1053627a526d3bfe7a1b00d9680406c8bc79d84c8f0de7b9d12254bf68a26785652fe952ce11f7811b492c2bd005e86cf59cd1550f8de2f8edf6f56b73875fafdd661d5007ebf9b70eac6ef374fab08f0fbddc3291bbfdf4f5895e3f73b0aa74cbfdf3fadaaf9fd06e2548ddf6f2aac2ac0efb7154ed1f8fd0e6a958edf6f214ecdf8fdc6c22ad4afccaf017ebf8b3825e3f77b0bab0ef0fb6dd447ad42c0ef3bcb99b06ac7efbb4e02dc09ab52bfef4fdca755aadf6c67347d402406ea986e0600ddcc4c07b560b07f7a00852e48423dd0228469c70748e899010097030b88cb8145657b80edd4fde778e213f6f69fe366466362c8909941a386c9c60deebbefbaeff41cf75cf7e178fe9cbaa51edfc11e1edc76c3650ae06c38f8ea8fa1fb260cdd0bdd67dac7c191ae905ceced9f38d21047d2c2debe0d8ef4626fdfc491b8705a8bbd7d1a9cd684bdfd199cd623c36932380d88d3acb0b71fc36942eced534ecba215692a4e3b6a9afd14a76d619afd04701a51d3ecefe0342c4cb38f004e0b3a00a751619a7d03701a8ad39e30cdbe0e4ee3699afd02709a0ed3ecd7701aab69f673702423a6d92700472a6a9afd0170a42c4cb34fc391849a66dfe3485698663ff35998ee9b1fddbaefb5eef32aaaf29fb2f98c85531ff6535786bd53d6d1c035a3b2cf2e4edd1862222818d57d2ee22dac62b21169e9a98b62b1154e6d152864a9fb1cd4aa5eed6793b11f4e7d994fdddfcf7c3875823e300a36855318104685539c82ba8fb9302b9c6214d49db20f547f92a01f746547381d006e5718f7f1ab6eadba1f3920ae52f705c02951b2243be2b59272f25f82802a442b2927effc55dd88bde5a95f7624031700ee60506644bf7e99bfeac2eea34b85bdec0802b1b70fbdd89badfb31f0d00fbab69fba0058522327009ce3096d673e7ed57e1e9df992874b5402e52752e12f078027d49344f835c2a036bca5f6f3164ec9f77e6d967a402b4a38cdbac9200832cc1da5a139c51fa462a5967acc1fefce9c29d3027d1b7461a67bf6fa73b0e7ede09784c22a29f377df35274171dbbd79a1eeef600fc21d2c58425e0ef6a012edb727318944ca489dc7218128f6e092c642b4da5ee3966c53ecacb4b721087bf07db0870327f8c649e9d73829d873a9c7ecb6090a3f86236c0802b3391db0454ff5ca838f205bece860c362b9a1ab0366acf37bb6f468d85ee7b373fbb0a9a424519bdc5753e7673fb98fa6cecebe01d4993177c4a662d84f200da8185714a7b01d2cc51ffe7c2deab7afec6b08d1ec49af91b89f29ba52dbf38250448aa854f83bb08ab3879f638d2a3c41fd76d41802d51a0889c33ee3fa39853d01aad82a5330aa6c434424a808a7becdc203a7aa4c014b859f837dec00dbab70cb10e8967ef0e4fab774034fee6bc66d1529bc2a97cd39272966393e9b1a2712d8e5c70ac108203a706a76275a6588a644a938326e071e046da907a8cb8328b450616976a61a50d30ae8e755f8455ab52b081fe38a703bc0af82ae1076762842048876972e15b2480c5305160c62270a3b2b25374c3464b418982f75da155eaed546822e2082cf8af44b78044106aed5e97356383e6705c3e7acfe7356dde7ac6cd4982123062d2979f9527712927de94b9d92d2679fb37ae17356317cce8afb9c55ce6abfe376098e33695028fc18388ffb2f5ee07070a82ef4e3d7eb05d71084ad96d1d18b3d7eb1e746157b21817e04b437b0f2bb0df6463e7f16a2ca4fd8e3fe99e63ed4dc7a6ea6769f2938849c88b23daed791f6f34511f5eb27cd8a3db429ec71048a3fcf13a56391cb81e2eb753464145f928b5e6c0dbf47f7865cb61dac2b5c5b7e98f04e8b75459525ead74fbae30fa7bee8457dd5299c621d13d425aef038d9b6137792008e3f3d8e0e081c7ffa3867fc79acd4235e9103290e903838de86e823e827c6ae0738030714d61ebc7eb185838b91cbbc686f606cb57e34aefc71a855710a512ce510c491177bd35de3ab5571e85539c630047d810138e24fabbce8856e9b669be6f923117b1363b0354540559401a4e1ffbf71e3e78badeee2cfd630571b54e8e745ce434d4ae06abbcf5463cb8615f4e3d76b6f56d59449bffad5a4b2084e1d10a71702ae4ea7ee147f57a7e7f71e5a63c842e3891b628397ba5700fdd41d88e080dee06c70afa5062763050adff4847ef107a855fcc5d2f03387e1aa3c82ca1f9b88ca40ec12e257a603fa404c7810d47120f843839bc1411e2450f832fc32f8618802ece1600c0e965894870d0aabc2180eba60100f7de1e050d3f0938ce0a645e8064e7ca2f84cf9f9b9a1c42d6cd2a567e54da0f09d09744b3574750224799df0b2220bc756dc012ae271c25ed429a27eb149e579e23d3d0ed4aaf8b38329b6ecac3c0bede2a7f26b6fe4de7021d2c21e3fd1162f2aa2c21ebbab7320f6f86100837eb1058de20bbef6a62bbfd6d21b4211da424bebb057a942d4abf8d2db8be051855cd8732bb1ee1536d0c2a499e5452d9d56b39af0ce4ee52dd5f0428d3f4ba05ffca9b0f4a3eb27822abfc4ed4aeb0d08fc5ec8ced5bb6fc9ae366ea32085240cad346e57923302dfdf091d25a854e9c9520f50d7950517d47e6d0d7f3cc5893dbf38152b3f1b716a5d5c742a3f1f754f4761d5328188b9706aab58b1c3af2fbaa2d87eb814a17e3d31e9409cfa62cbc5a9af5f419cfa9c4788535ffca95438c52d22eae7565af5f32b7588fae1a89951fca92cb457e800063552910cd4048d2d4e9dd8c55666165cb17b5d81414cd08f836ef07eed4dec4e34c81a3f762cb435fc469c622c5916faf5e3d756f78a185851bf6671166e827e3cc429ef4e4060779b0d481c13715116e8a6020ae58d1c8c5346da6e24712721a49c95c69d84684a66d3f86b9fb3da2d66d98bdd89867571a1739240af4e12e0df7eeb44407afe5dc1f7d7be243c597e7c589add0f9b1a5535beaac6c7691cc66123d9c7245cb3eef4237ee7035723f2d7c5854e95aa2ab97515fd548ddb1589db55e4785055f83be39299075a16148abf8683e3005665f2d7abf3f73f9b39290d92714998a6794801dd69c2284545285e90b781df6462ef701887530b21dc66d80961af83b0d75c15d42bbf0338f590fd0b66be2144bd3237f110324326eaee76ef6e6feff676ffa1113a8311c3d6ece7d4ad5b82e1051ca71b364c3568cc9091112386c2bc94368d944d4c466f2ef5e330dd6e7709cc61662d1af42c28bfc6dceefe12bb6ef729e95e62a0eda6f87d57d5fcb0dbdd87bb8421057cd7fbb77b994de0b367edc7aa91e6947feeee1b77abdd95800855a0dfd736cd78393835e4dffe93bdc781df886fecf57774c83db3ee7cf8717a924ed215be0b216ef799e0cf55650f29cc370db71d5f76303eec9c3d538c5d0f5e8f982a7c29c828a57c9798c4a4d7d825e10adfa9e0eef0c6468feefe53da9003ab57ffe07fcc70c297330ba1186394d284ea41a1e6464d419a97c327cfdea29412c3b028636904d24b3df8796e14a3a2273e0c2207b1d6374a29a59432c618db05ebf38aefdec88f9a8a42a171dc7a4bda6c6757d9fdd6c4e84fe4dd87d8b418639c73d2dea22d6fee5bd45bd1dca84de5b701428c5146982e3c89b076be3531e70a4e93f4113e3d57e201f17be80453ba54434ef59fd8b70f5cb1d7e6d6a51fde0cfb65ce0e4957ac93bda51b62e745c02eda421e0da1e47c602863e46f1fb8eb41c687ab23fe46eeea48baca6e3958614bc84c76aa9c1897b1883c077bf23df6e4a3d893b5853a28e3fa0af659d7575a8853d8cbff30ef6d4e5658eaf14215146ba12afda769a4730b63b0873fadea16c6e5c022b928afe47bd1ded474517d8b2a3f3641950febe90822dfb4e662d5b00de36690251ab0da8f7158d5b00dfbaef2fb8a8c51e5f79133d991df58a4785265073db3a4b0523fcf1b6b71ea93d9e1d487c3d4c33a9ce256932ad949953cf5c381956ee00f6c6a7bf91ed814cccb1712c406f940ac0afe348d7cf9436a7c3c173c13a623a809aafc60104172bda85236e99d22ec658dfa75508d26823c7a0ad4217f4aee2754f9f0a755dc6a9ad27f35de53f9301bc72d2c3d05f2682c56877ce923a8e93ecfa8fe162d1f05c2d6c82aaaecda4efab1871c0631d8b0bbe6614f76cb3aecc91df65ea874d0d6cc981c82f069177bf25fba06624f9686a0f2fb49fbc497dfadeca589ca2b4c2325ed1a4ba95bf6b6eeeb2c59117bab7133b0275fcb36c8e1d0c05867f30215f49ba9f2bf47710a56f95e0e4ec927717d853d99752dc49efcd975107bf284f51556f50f8bc5626225bf8338b5a9ead3c57a5bbf1a5a6896a5d95a7db02ccdf6c04ed54bd9631c576d7138e36417f49bdf030a7d28ad5fd1ee84fdae5887694ed8c38a75dc624f7e2605fd2ab520bf0eaaf26515f49b55be7c29492cc384b633d88a9df268ad2eedbaf3273d73c23d8a5b76ca9a7f383585819808bef0c354b054c873a542d65261f6ed9287531fb37a38f5b98ff4e1d4d747d2c9d17ef2898c52e1cf1406ba822ea74eeb59b765ddc47100a74a0fd9461e65599665fd5fd72c6866d9c3ecb3eca83a15fea1428ae591b1a00e9f2fc78c8f8d3ce29e8c057970142d9bd977f771fdf88759d079380ae4e14fa08eae87ab951fcc873d1e7e88e91cc9a3c95d0f70739e5651da499ea6c93a2ddb6612f4c37c6a14f6a0ff34cd13c8c3a5581df0a14f2ab606fec7f5f327153e73e73eb16e95574ffde4d12ce2d409e661253dfc8cfbdc0763b52a16613e58cf9be6cb2dc097612c0fd904eae0c2a923188eca14b54af2b46c0279482b5007fc37519e58d4a4d2d9c9a31eb3339966117b5b785a25839a069aa8d4acfbb028f06510d67d33ebac3319f452e567cfcccc2c5bf667533b6bc9035f5a813ca411d4c163a5c2ee301ff68eb6868b34823c6418ab630b5b035f4aa30a7f4615d4ffcb58af0a3356c6246bcd17a78c38d5c3fc24b36a0f7f1e712afb197984b57290fe93473d24a195df3bcf9342f2cad6609143328b0cc2ea80cf9c1cc2d640199471415f062d2adb81611b58689a0679eb66e6c85c12f443d5c9d0e173fd6085023b254b564b20f30d7c23d710429844728c2ee690c400a94020f67aa83affb7aa16ba8999b3ebc8f5bc5a2d14b95b684b0f35d1164e9dd61b4b6751a27ead056ed0d52ad872d5102bb4b235fe730594e10b06cd49a9c9e4cd23e88732d5ee866c8d7f0bb52a87a5f1f77642757f8d3179c56f5467c2aa77412c9cda2a5e04d50f6af988a840e425561550503fc8059311eef070cabf5d9c3aed7812f5834eaa0f55c7e16f62820e6995d734fede3494aae7a2f07fa65f9b09b49dd1b64077c3381a8333f22396ce561172e2a8a8bd198891a8f061aaf93b46977ac4d0b46d7ef940302b2c6931629adbd8830e85edb319323eb26464c9c822bda67dd6efdda576271f5bfd6bd8eadd8e2ffce7e7541c7e79e75e301b0f6774321d5c1a9c263dd6652f349094d841939d1da8e0ca125cb0cdd9bd5b317607444f44396a96cb0a52b297eb78f38f886d1a473f868342e349dd897f57349e76277e58e56fede7a0a02f5d13753dd459d8831f040dae7598a69535a15b321b77f2df1a3f5691385ec8c274c594202afbe7f0bc2a8a2a77a82aa8869580f0b0002654bad30f9727e8a9fe393db43139b05eec6dd53458f267ce077892c00b2780a2490f6b45e3a1d4f8053071558e46b784c6d378cd937005b20026aedabf0026aeda1d9425343a29fd52fc736870529a8b559bdb0cecd9b007677085aaf5e00ea70afd96b469dc565802224fd082dacf251c505cb06a7748d0fdaf5b9bc5e4c072c11765cfa120027af9b0a1499090201042f86a5513f58b6a41e1179b65b0772835a1f8053db65a2d085403048240104b8b080241a01a5d340d7914424643746832b443dfd0465be58ba37ac4a953bf026a8cc161f8f810b9b6e8d76b6f4e2ff4809a2a4c85df437bc3117161556fb12a08e44d2a7426b5bfa85f85232a08b151e6ddea8ceec0f8f30e4c0627c43a974fec7cfae19bbe7e5261d7722d68276169e067f5eb2157510f6d0d7c1c9e4103fa75ab5bada2f1f0db8855d5781853bf6ef5f7cede34511b310d7c58a36b2d760c7ddd1afaba15d3aeee83810f879e40bf6e56d011a8c0840aeb0c20aac1284ee2c62eb81971a9d1680b976ef08d4b409c61bf7c4aaad635769f076af3cbd51f0cfc276cc43615a029157e4701721e226a17bfac20d42e3ea27edde48b55f1503fec9d21232afc9e25885c0bfaf5d0107c085953a0c154bbe3d7d6c077223aeb56b9024f6d22f6e07f5ce3b724015fd590a93dd4aa1a73526a32fd7b5eb76874ddea1935a05a7f01f4858f0637025441ab58a155bd76516bab8071d45b6a0c81c2d20ddc8d6d5d02d23380e82cf558538cbf476e87726009a5f6b3528fd6e1428d5c8c0a4b3ff6bfb81bf483a31ab95861e986fdd8ed7a5e7b873dd82df6206b68ab7c81a5c25ac261bf3b243570edffe1b597d4f68d39fa16fd50d1e18fde2ff8e2d409b6a08e0a9af00e4c41d8f3b335f0fbd43dc4a958e1779626f2963761157ce26a2d9cda2a5db02afc36fa9ce53a15cae8dddae1d4077f2a7c58e448ebc06e52e1cf2c40e8f0676fbabfbb1fb6063e57f8700aa79430810f59ec4116e4610ffe0b3ef48bac0a3fb25a059df4ace0c756dc8952a114ab2f36893d3398013f803e82ec8dac39a0cd5718c0a0ce0c83f6861f3ebcc2a97df804d81af87c621f9cea0adf06f8413875a3c2ff01326115f41112c2a9adc245940ab554b85c2ae48fde28f8397038f511000671ea94e2d60c15ae950ab9637803e7e0d4c9042b8ebd81b50a196d3f75dfb4022a43ab0200b323e8fcd951c685c422f990a6b0caa687c464a82ea949dd8fd4438a5217be738a531ff422b238f57193d3dcdcc888fa3997ba91c984dd476ad5a5375841492d4ef1d4ec8853cc5c482c66c9bc8cd88347ecedcbb05d3c3172ad92a0ff302441bdb731711fac375ef317cddffcab5537bf34de0de7b9ab69b8de781cb6e118688f6cd8f8ccc6bb8d8fcf47a7c7f1f088536e63834279debfc9c4dd112cd530bfbbcf6117b926f4c3a2d47dd3e9e38fbfbef9d1cff4fde7d9645bd85ba34affc37e6a76d4d5a031a3fb4c2fd37defc9e83e0f15a3cb7c62bacf86494033a0de65af0c003a14fe9715d57d2c0aa7e00d6e2b89fb3ca8eed313f7ad95e73e3efad68a0d1a6607b940f11e87e1ab55cc3c5e0c4ad02f3b621ee8a2d014bbece8e326fbabf116006e573e30c110786ab4a005364db30f03125ef72686ffe6c2f01fdd176c1efbf5d77a93813b09f1f6a80a32385a799fb3dad21d11b023051d1e2c75ffe38fb7442f728483ea26e9ee7878310fc31d51b8c2cc5354c4a9d3101d38d126caf8691f3633146193cdc6c0c23b2f226e93a90f6fcae8db05fd3628898fe5d2926a320618b36930193d749bbbbb79316e3eca6b150f41a8cfce07c6f14a060a283f377bd9cb975363f8e285080aff232a0ac2362818803ad355e66e69fc913932fb47766f006b414a2e5b08b9bb7f6fe1eededdc61d68ee1ee72faf296851281b5e158a72499886872a502f6d51108c319cc561140ac53242deeef80298232d998f6188a828089c1886cd396d5c41a8aeeca93999c79cdd3d7fd99bf3677f4fd39c1deca67ceadd67a23d9f605be4a62ab05ed32c033090753f96a6bf3faebe593706d45ffeb7434578f801b2b6ca16a6d4fe21adca59f5ac3a4ca8d72fc9f3421e2fac4a92ac853c26abee7329763fe277334da8425b06d21a5fcec7e128a1b47e356798a6df25778a0feb16e1d82113185a0f4966c57ee1ea805ca3f7472cc6f81873d862bb3bb1b918ff7cf66214babfdf8500eababa7083ba6dc951d7d545a7d22c51c618e30fa2b020fe0c630140a2928ac05cdc0750d7d58527d497baae30ac34c32e30c182164840794161042618c8034b851e72aad0b245123b34d882cb166ae081ed2183c15a931ee7cb462aedd30d81173e3a7861d1d30323aaf0000d586083214461052bf0a4000c29fead28d2c41456149e8085a585a7987eae94d27fd0050c301da7262a432915b02c91456271052be850da052ba84c951c0c5552dd2a2e29ea47a1cc00084940c2105d24618515fcacf81f85c222830758698214a0d0a0084f5c8941fd4c0c797a6044142bb4c0e4f50fc42a28383822092944a1042ef8c7f46f031858e0678b1457086309415491c50b725084235eb005125842f8008b140844c10509b478a9c0083ac8224a9421a8c064052c7801152014094e4c38a009275842093818421255a860c21646208494524a29603efe1447909ad0993e9b18429e40081a26a8e28a294498011458780a200d8b2634a1e352d4efb5d0821cb55d5a5842d69a257091832368e1043d2862d5388c9deabac2e84295755d611ccd1bf8a854b0042030907022075380d2802d88a2f841124f68190285ca228a1ca86c3982082bb610644416cf62b148f099989ffedb000a24559ad0320250115c6042164f88f84901128ae0420411522c9607fc60092ac842062e2eac7041e5a75f8427b60a472062a2488a249a5471e2e708294d3401b40510b0f4e4e04089297494782541c78a19c4ced4b680051e37f104284d749c3cf1411195d5ee20082c28a83882111257acf8b7c4a88ae5ab2b8c292ada335b80bb40a89f8c8e23258e6a503d0c1254f8cd50fd7795850549e86942ca162656fe9453eca4faa338e5bf83532fd5dfc60141f6065688048450b52be99cca51631735da38827ef4a12b0b46a834aa3f34716ab759c56f9dad12418c1bc4bf83b14e8a11f8fdb25392b392bfa520b23bed6a36cd0ef933d511e4c386203f4026426c56cb16ed6bb978a71835838d0e14ff0eeeee6462e61d6c89ddc19c3333c78da61a5136ecb54692dc45d86bced1348d0477b0581a89599cfab6cb163169e7540ff293cc9ab5e64ac6afb79261b392f15f8d19646c1a89967e3f07ccc7bc36a4fecec29d1dffbc36fd98d2cb520fda49622d7d978080a04b972e433576473ccac1ca9da53217a4f4d2750f51c372f6c6bd1669950d4cd3df3ed8b381038fc4b3714170d89afe1b56c79459263bc8ca02876a7f7f6bdf0f593e3f7b8d29cbc77ce85a9adfac5a0972562188f19a73d833a77d89db3824b08b977a68a46f9246fa56890008192f045cc950c9e84e41627c8c4e095c8940c6c7f89c553faa39c80daba37f72386c8db63725ac8b9c0d5bd36d037b1d3b0f870e6850ef7c49b8fa3369776ee0514046272546b7a52031bad3aefabbd44352c8da9ae6536cd2ae2bfdd0497774f611c48686acbdd9da5880426d1c4c38d8eb2cec7cb0c7ff896098860b34fa3bb393a47d64f833c31e435485306633750c8704da4882d6a8ebc2821335d62d4152851c3fbc02856010e5e01574296dda7b85091a407168b66da651ce14d331313486b6c9b4850d7605dcc10a0ae2d407afa87c8581f415f0676e948353ba586db8a810540254c9ec4d12f686fefef73764fc500c380489d81a7e22b008085ba43eaaddb75da01c0cda1a6ef8a7de2c5b8b534df8a196161f5566ee85bc44508761e987d7207ecac54a847f074e1580e115508769b8053b4839ae366e403f1311e80787b4186159960cfbd47cc6985583f636ad8af1a55f2bf205e6b71352408506a87088a6428f63e8da80705543fbfcdbb4fba829497ce1bef764fc6743e6bf18d58cffb25a7ab82a1a5f2a3d8d199c0c27835b4e3ee44affc221698097b8e592b07fc376137bd0bd189d088088f142c0558c183f572d444a975363e4ac62fc6c1ab601fdca8d031aa3c7c6178ed5102b7bf7ecb9e7413813c01ac3e210167b081288e8e0f13b76dddd9d65bf599665998c09676bfcb353961539e2cf03a7369e04fc246152dd1d8baec7c14cd46fdb1b2ed62bdca0faebc0a902547fcd27b0375185f21d5be35e6de480fa0f69d59c1b58ff22fb45f50dc3d4aa97edfd5d1301102faf9562d7dc02125053240075863f1b185c5591a629e2382de793beaf3375b351b798ba65757bb8aa18bf6d1f83dba5217d0c4782e14893f4cd6d5fe2e6f7c675fc2dcde79ecf73766d92b942bfc8114fd235fea08453f3b90577f885dba669f662077f484c89b4dfd83372423a65339048ef836866a872a551f9718208e941e5d78155db05178bc51ac1ba8c822a3f2acb814525f56d91f30a2a2f1121b74d235292341287636b60eb57da2fabb49fbf714a3edc39c334d98e2f28c923751e7b9c7133f3833a7eb9124e4132d2d3b9fbd8cc3aec5b7345923135daccd5fc96cd2afba1f6cb55863d4e3787c4541b07ac7a00a63a56bb33e2b4e58300f34e02dda50bf74bec1b5bb9ecdc5aa0824c38bc1c54a0eb7a15d5cff41aaadf34e1f0729c82ccdf2a84592bed69abb2e79f1a6ccdd57c12c744089bd5fcd65c69118ad691709a94713cd4f8b9bbbb8c08a85b02b253a8db4139f92b59d70a04f34190a5dc9bfffdbb20d97b17815d651d4b8e34d0b65535bad082b5da1b1ac4a11f6a6dd29a33e2b4d4cfdce9477c7eee3ed819e13f75e7fd3935a7f6e9c7e7d4e6706023fdd388bf372cf1b78c43ee02ef6abbcb3c789779975b08389f5f6bcd5586ed66dd94dcf743ed561a49c2d565d4947b330fde8575ee4299b3dba6e95d02ed82aecb8ba2fa4d2f788884306b158d78d1f28275f28f8fbde44eded1b08a5dcb8651edf9afe263bf2dd32a621f39b92dba8a0f4b3460753b1ffacbea72adb98abf252002a8db4101417c1cf6eed285b74b975594400356b1dba659f6b6b9abdc605b9bbc49595e02b72ea6c60ed4fe41a31663cf6de376353921ae7570131081f0ea2708947efb21a56efb21fb3bb650247b94fd16d59c55ce8a3d761fd43826da538ec9f6dc0e3028923daa497ad70092869cdabb8ace958020a062afc94deb6f58fda7c64969d9dddd5e7ac93a1fb8627362327af747d78eb6ad9282fd6e15e209705f2190c562b1e0d27856fd03952727c5dddd7d484b0a129b6a2449d7f95ecd59fde84702bb544e7ec631c95e0829486c6a9236407ce64a35e464dcaebc6309888c2a716858fbb18c93e20363dd3de3cbec7c607f8c1d092bda72ca35081debe07a1108efef770e562d629c0f2cb1f7d681d6bdb1c7ee8ddda8f2b1caed33f6e9c7c23af906d8a57a27a10efeb81a81ff4988c9ae86af2cf706f26824904beddaa51fdeddd6f0c76f23b1ab61ab07b1da7a80eed009ac5446d25db854f88ca4bb7479e903c3ae078c75c0eef3973fbccf5f7e977050e2f18708620948a56fa8442c98ca9a5432442410000000026315000020140a068482f1804cd223dd0714000c7eac486a4a170985610e04318a216388210400001000011019a26d003f377ab5bb613e6773c7b7ad172515035068d34b4714ff6b36f4da4ef25b18a866ebec8c5ed5ae3710351334a1fa1a35d8d8b174c1cb07a3cddcf370caa0a4f18ca71e5e99be454e27a5bbd3b4d9de8b10fe01d9231f0e709c8260969bde5826edd42652b55b6554c5c67c4d7150b093502931c43656081818b8e4e561203835855b6ba3580ab49429c045171e75af299afe39e2b17855625631fe2efcfd492ec320efbd472ac454ac0d48a413ca6d17ac683863bc003292ece4136a8965134b23c12d9811a31c2595432145f489df68ed5a99f95d0190fd1c40642dfdc45792c4d0df4a89242691056950718080448f40d6cd63c0ea76833a10ac5466b185d102a1a4e7355839cb5ebea1d23bd392a94185e4cefe1e6720f9f29400cd831ec228dea517aaa5eaa7fd2751af49dde8252993126f0f9581b70a8d136fe121d0b971d95314e17ec59010fac9bb7099e2554d4da0ea396844a78753a088b156a13c35b0eccd07f5e52c5748f32479dc65943ed57b9e024b7540fa8ab3b360f07ad38f0b4be0d40889f2839aa56db96b347e05a1a45117722763bb0b7ee74d12c4ae40f590b045b8f6bde26caaf3b7f07379cd3891ed63cd382985e53184e3302c1af07ae6302ccb3d0162060a79104d50032e374709d3b2352aa940126a124d79d880190b85fad77cbe4de686e74b00e1da5deb53274074d1fa9f4eff58fe56dfe05ed1b5a4a9dc50998fa3b8bc46dca3da9ac6d4d5e4878a5ce0b424aedccf5ed6175071495bab00615c6cdcc45b2bc936a77e46aec17f6a6741608de59c18565aaddf8f9a98592c3e6b9420226b4510ed8b4a4a44a2c55822d87c10f7293f21a2a941620984b42b51d2a423d1325f18d98a3fb7ecfd3cf49b48e4b259d678e7efbc6f2b21d87ba3151f0b7b8d3b16a8fb5bd8a3de6f575b6f462d367ddb2d0d6d04dfa37731b791b020fce10a45e8bb872140ff05f1fe61eda5fdf7a7cab18fe24cfcadb5fd4971c8d79274cd9c019e3c3c79af8af682a2ea5bc6855f9267ffff6699908005ebd5250ce40f72aa81d73cf979c417a32fa676b7e582309174706aa7ba44be174219de7c7521a5fa8e07609ddd62350b69e245fcb8f7506f9c9f803b330f2be207b12576284b970715b935c7925b216a1fc0e73f6124472a400b158805e52178a5c404a1d65c916f563415684f90bf0fa7309fe49d7c9eba8e64cb5b9784778059d16543dd218e5fd94730d675b81bfe103fef107f1bf9ae58375e39248ecf4625eca9d31ecbc31dff20b894cbdf73e49911aebcb04e34fa05dd270cf385476397d9e7c91acb81c6fc04bfac104d93f05f0f053c667b47348c0523852215fd47c6913073ddc5efb8378a65e143b861c687e438d9cccb30a89f7072af4b2aa5199301ff23dba48c45f6ba662b9a912fbd49533e14de2da404e36389f052a431bf84983ee5c586e2c73345554642e1ca39a4d0220162bd7e23a0b0e6cf0b1f051b8b9c8c9c11da9a91742d3b660994fc5a78fc09fdf2354dc9570ca1f3a798d40b861bc03d4916845d32063d460b76ec4e5674535bb53b1631522eb46873e13419d20412c24cd02ea3b0be9780bb0dd7ad4a4146a9cf9bea45012b4c3d643de2613dab88ed250903698938a8acc6c28cd685da380aef9d36c0f5a0c1815936a268d3e41957e15804be307f9ab066f5d8ef7701c57a22b946c337bfc63cabd55d7e2f1fab31951d9d8aefaa2cc918f74e3519dfba80947184987f5f22e05321df6a8ba96ba6edbaed4521888cb65a410f893c97482b2ed783d89a103583856d1e6108849072e3036ef39f6e76fe4d3a4b6111fa7badec21f45caf9efc9fb8331219df48783a862be17c2466eba2d3679f8e1fa728e84812dcdeb1c4e0d023e7f32e1f0768fbabe1fc896184363d9f314006a945ff67042a23813cc6b53ffaaafeb0f26480e5458c774b0a493fc8f8ead0774708975ca0d9a4069fb21354fc8037cb3c8849c2fa42c7157b89286b2da1f412556e66475199af4e450d6169a8cd68e07a65ffdd4c2857be10f02df592be35e5275857609e02980a7cf32b77ef9dfee5e6ebc6e5c675d3edc675d3f5e6f5e675c3fdc6cd6eb8ff19b99fbff927c4c5f109072ff7473bdd16df1df5eb8de6a5ac0df4edb08ca5579d79dda5ffe12a1d1018cf8bbe172168e0a7bbed5e2abc6d54fe26a8529266ba3c2477e67a6045d4e2ab5ef98173209562a4e71023df1a619548448218499e589ed90975f1c6d5aa394337c2e32f214e547d71aeeef764a028aae6798cce850b3f839ccee4ec37a4be16eb11e22033e67587b863675e130318f1d1602a414f824075f8357b151b85f110723038cb02bd8b462c65d0621e7993f4635267c8ebc5068d3df1cbb41e9b848967f232ed17db6dec092ff3151bc2658e3dfad7841f8a1e56c5e60c4d19dc88b4054a00ece8b87ac0793ebc1000eec281cf8b27615aa01f25e3afd0b8f7ce03c7ef8dd00898ad28e62dcdb11b408e27de71f3321341d9a8deb9f2f2db0471ca006148ec19054ee8302e35d9378e072dfcfd44e840dc61fa2550b52af66a96937c8a8cc5fe737f43c1a2f6591404c2729e645b9487e70efa7ebd05be6287de044badf7666c4ed4feb51193f42bb9613f79e293a96b204aa383eb9f941bed3347eef13eb25af4434358111b3096092ed31eeb06334504a4edf4dbe4ba59e782fc56495355beb7c5d6d9440b7a6e9489855ecce444c882fbbde221ce00c275921c6f93d8ef43ae1d47815c49201edf2aa880b29edd166c1748b11354ac7b365c6a14749454893af162fa8b78e3ab25bbca0ad69dcf3c3ab34d3a48ae6b4bf76e97a6a982c4b8449ac10c405c6cf077dd54940365253560b7ee4c4b4475403c65d28c2c759c53d7097235521fe5f5a8e9429965a43a6f548f83ca70427f05c3a35bed4bf1b4a95da7265b2513beee10942f2ca95c8daa5c073e6030fcbb60f5cff4cb003d2df06405e2d2655def0b0a1a7243423c409eeeb5d22a0f4cd2714d87459b6095b510db21feb7b7b4715c9b615d43bfa65f89bf941b27c80b1f41b48611daa2b11bda36c5bf3224ed2a4ceb1a377d3129b3af4a492815c298e734fd96cd5c056a1ccdb93527c65e4d47f11ac058d550140ca90eb39517638fddaace56822f30768faae5237e1f55c3d08f3a6321bcb90f8a599caf72c97404484e985f8f28e3c813507c10d42ba427ab83cdb76e86856260ed86b23e5a13cfe74041450f6013d516cb0aea6afa82aae6d1ecd1cfaad36d8a85acf24419c50e63da3ec8ee934cc4dd000e177a4df31d369901ab7670dfbd494b27a16dc6c8e8d77f585cfbf0c45bc9c8dc86a4584bbc24c1385289310d0e0cad3efd8a1a862d8b06074838b6a9b52dab0827a2cb60646f16cb4323da13615342c9cbfc866646e513f53a5b4101d92eb63cc811e198a2b76b99d21454e32f02d8a06c56446e4f90783f0d72ad3ca1f7eeb8e6ccfc8361495f79a53e1077d316e7dd8758c5d582199002e666a8a5e5c64aec2e37384615abe9c52a8eb4e915455144c7949fa9b7a7e69d81b275aef28d32a69e4cd4bc11e196cbeec649eb0838cad87536708627419d5799538c0924276af4e10eb867dd6448fcece16ec464d78856b2b40a8b2cb5745685d2544bad13666b4230d19cb5ab9ce9bfabd2a3e7f6df82b02e744bd312434e1c26c68a79f5a8d9c56eeac28360985291f9b020d4a4f30fe1c1da0668eee333f91d4208996271b57e39e7ac239b24d92f61561ee335007e08b3ffb5be396983ea0ed3dfc4f24cf8d3cf8a8f4a662870e057ce0924d031c1ba98bdbbca5dbdb0478352e6478089d96adfb243cd5adb2af412ffa8e85a7cc60d3bf0ed0fed6ebb638efa405bb183551b35f27da1abf6d5864845270eb8c284e1510f8cd8a8212aa13780eca6db6a02a406ccb9c8b7322c2779828efa72238792930b88594e3f7b14bde0bb007708869dc115327065423b7671e35380a8476a70b0c4ac28dc9ae62469a98a29aa88658c1f1fb472ea88de83eee85c72b295165d8922a560e249541274f441cea02cb6b885664052e1022fe209759114609260e06bbe326c2e73e725dc281136b5cf0417abfb3451ae9d83dd52657041ad6e7f70efc4b97ca602623581181f991d06e3e3fa25803a78d12acef6caaf1607127900b7225dc81e81cbf566ab1d005735278e026e041f3d10613981aef008fc75bc277bf243cb94f7af030530431d817a0bf7132e252fb3b6c64c6b5ad53b29290a38096823f22a23961fa0289add9851277f335edb08e90ce950575c2178dff053d927456ddb8c2ef58e68148685b7c4a07a1e429f28a71df96ab8b68d294411dd33ff1137600ab2399b1a2b9a9901fbccaa97fe886263dd8b4ddc7115220416d52dae0c3c04f9388ecca5e76ad5c84d09f60cade4ef45443733f0bcc55e1cf9609b4ac71636e7afcd59e03c36a9e2fe72e2de3108d2f1b75e8556ccf2218a9739721968add49eade57e3ebc15328cd1332c0f0c0e3de997b853c3a6b134da6865fdc8867a7186017dd812a1cb9026f321e49c27f409b26906afb980c5d95ad2042394bb1db3a7a094b4ff2eab417998f6eb7ebbe7bae9369b7521b6c87cd9a666c302ba0e4deea8a6904a0fb8a9b8f920420871230a3a4eb5e4a9282855b3b15f7543117832439fc1e7aaa3166c2bc45db3877a3b80569c606268bf6327093146a0e035134298f03c8edd736412c2ddfea52cb484618163e0b47848662586d1f4e19a6d17da0a561a68af1cf04c62c8760cce06f41f09ea0496fc186c4512cb54d265344864f81c47d58c043438485835d31b1526da80b46a3bda075df8616580db1c5eb11d701de4f37e86a8f11eb472ce4ccedb8884f038e617ccc486b6b4b34d7f39670a0ec2a013ec0b99aee1c2d473e2b1922d5b8deeb2cb9b359ab4ffd1293e64702c53a927288d6ccf579387a48622df94ad0e4540b3c0a4b5007de1ddb3d64caa4bc2bd52c75c9f901a973a0a41850961e52616f6aa27e4cf08c205b20704fa84588c891be84226479e1ac6411715d6dac3c00855e37e8633963f0fc291608a95132c856caa8b9b5a6ec7d62a15ac6fb4fea54299475597523ae48917c093ca6ed24d70138c4e89a67e8df9bcc7b7998fcf8f6c32a4f1d5baeeb2ac984c382b70d3d1c0a154c5dc28d60c2adbc70a01881e7d67c980f2e336c783399058070b2209bd4afd09e929226780fd5baa2ef7ff464031b05824a8ac42e889e85d9e6212732f4c3f30d3d8701ab37ad231a655a1ae023728a303557d5e6e98b68bd7da95b7b15e9ffb6b4de2daade39c9abcbc619885ece1414cb595c3bb9132b41e142c87403adfa706f0daef98af834507c9c0628962ab657a01d6e07e5aa1365d2e248eb7cf47b0a5cb42cc2b3c32f91000102ed721b51bf1602e7cd3d5a4f6606c96b6f3aa1745aa0b1a8ec0285e46f0a01d91c846d82b83100475fa9ed12250e13641b51128e5a69e7e123fea07aeeadb0d1627483797071053b81628c8bb42d0f5bc20cba0c0df9e844854704a116c3714381c7478f64e1daf2838d8fe7fcbb629cbeb2c21c3e060d1d39baf60068327c9284575ec96a8401c4791cc48ca3df2c1a1b421645ace5c1f66139222dce0adad47cea0174ecc3a374aa3d1f2b5e3e8b48748d85e3a940d7352a805bd7c9591e549b6a6330f31d851531a3f2780ea1729b0bb145e920610369e00446da2a428912e1b8509118fb39029bde68cf02093b047533bdf8d114c2ccb956ea7a1b90b6d63684be84d37273b9f06e65553e8ef6db3d2bcdb4e2bfc126edbb54aba3015dbc3e4227b96648af98671ed6d286f375d6dce40cccd60d6b2c34f910ba84dd22aae682a23a4676b7e34f3f9e0f347301ff35be4b1808765246aee8d0193030570510c2d16471e8620985b52f82282c0422a2d603a3d387599e1c247324cd74b6ddc7ca1b1ecb890a1c857454728a2b0474d72a7bb5163fa5bce701076dda943d32e8bea413af0e13253de6aa3fe9ec13016f30b828bf3be7fbcffdf3b36ef11b2a2bf4c8f95fa36aec40f96d5ee2085578b8008530dc2dfe6fa573f21702dcb2686a64fef40160e8738c1810b2ad7c298d37eb0a3953985f0af10e08d9ae67041d50d800c38028a0e38c7b9aad57b24752a67db9f771b5a959570e2efc39c8f900a6290d66f26cab4d3bb540d069ddb31bbb7e96d10cadf7dfd9337b95f65e006f0dbc8f732293c510cb8530fcd93e15e81564ea390a80ac2876b75f24e3ffbc217dd7d05d35bdaa11bc619e7734082034c7c485d2d4041b9610eb221aed05182242cde059461c3c0b818d4a0c03c83bbf864ddfd745f9a937ed2936394287350b4ccac57c6601b65ecfdf0a81c70e6e285dbfa38b2c47654621f002f12c1b33a5d4e23b45da467eeb6578f86f6840cda781b221c3f18b97ee9a2dcfef2a9c7fe16ede06004d067436990090c2585ececc6ee86964e02dec5de68eb3c64cbd016e25746bc18c491188c1d2c7fa4115bb05d1c8285ca51bf7090fe68ac50090f3a140ae70d88afd2d085b8649d146923f1facaa537badbed807b80d662ba08b0b728dd9f2e1193419b1a45bc6bcbd2418c07ae1b037c0b225487c7db855d19d05c4dcbd3537765cba5410df7b2fd08e030a4ac17b207bbd4cb9dfd6baf0e3d31db8918784055bbc3bf077316ea1dbabf74c831e5f7518556550d3e372ac08b28d3082ac26f5b145face5a8253143239e8c214cee939005d1eaf77063aa72b7e1bb6f656eefa5ab54d451f953ed61f788461d8cd50279074d341d1691668feb7ca4858e9d6c907e4e2401c4dc29077416014ac9f1345935b8b59beea17ea7d924471b9efd8c03971ae0d9bd40c663029954e9068f3974208b688caac6b1e90db97312e01d8d8cb97073cce05fcf85ac5fcbe831613a58299f07d9869d42bb3c84f0f448a1d9ea9850a457d590353d8244d423ea95e07fb494530d5ffd538fa03b85484d939dc0d59238cbc4726f7139d3ab699bd6a0aa99ba728aa1226a4235e6c09f038e7d3d3d28ca16ce021a2fbfd85c90e66281bcd2124808e92d5f1f9d46baa3577d93e58ebda38671baa9a212651f8bfc72b61bfc85b80da9f4d6aed850fbe6512c3b1740236bac56463a497df357cace294ae31493ccf5915e9d6313048da035c809e8285a23b711052196a82eca830585de3fc564e83cc0a965dc1b25cc0432e1db47f6d89c0969505151d5bfdfaf4cf02382dec9ea91e409b6411a26b5940a1bafc3da0c14b974a5ae1ba534a5afd108e6341f8f4f86bbabd07300007223fefeef8543fbc207342018e957b88802d6ea17b398ac4cb5a05133f6eab839537f6b0c89133f3a114aecab88bb776e409612419f09dea656c16b5f4125a3cca32a2c0938707e3663d6b8b2c0a982598d9b4c8b30ac2fcf5a71f858c0f121e871a09f8b2b259ddb6219cee464432e93b2143d405bbb5f30dabfb20260b2bad0be4cd45edd8979dc1905b57f50652ccb16ebf50ce68ccacd66f35816138e06406d5a45cfd67d1ff2251c29fb378f11410dcfa5d39886f3d6d2e13838bbbec5936ad912c562945812d4a02926e50fe89ccc29001572fdd5a0bc87c1213e829ac54a8bd5e39294e71e696941c1678698f5c9eb4bbd0c5b3dd834b54bd629eba7d45808c983b1f6556570f3d2ddde0860306105f3ef18b52632fce3f7d8511cd4d129180758890113d29c1f46e3910a1b7fffc8478a7e15a0c8a86510861614af5a86c66240533f6e2f5e79a997bbc05bb70fe5c69bc5c0dbb1242b2018f684a596e6928496b605db8962f91ad2748132247db5855f27989e869b4781c37df347cef335cd61ce99393e9c78dbde19e55099cd48251574931e615d6af68fb1830e742b6141c7347d0fe89f03866d4665cec8e188333df6a8168510f52a929de4180b4dbe97d2302d4dc733d3d057a0d44200eaa8562a464d429005cb804e00d4154df4a7e7572cd8c56c6f35b860416d80f4b164945f3551ee171b0e922ee3dd3746e77432c27457e49c9baf2c463c4eaa5fc1b2e55e84142773acf6cceb70becd25377050d1901fe5dc4f82777651ca2671a0994457372a88617f47e896365fb7d0d2630ef70d250fd8ad269a1d1b8d615d45b0e7947953867fbb8f82093ecc07501ae06f0699fd383e57d23427fda748868d46831683a375a0f2d522b6bb988dd8e5693d693882cad11b7fe74397496c79b7cfacce01232020fa2b13ff0b4017aebb24b37d2cae2ba2c8680ad70684ccc01923369eb6c08b2ccf3050a85baee3b5c7657c13bcbe4df0720766037f093ea6f3a745f7031b3367d24ac6f694e34c533c47838623335f49ad5a5f7b85e1980283841adb9be0a4b930354214391e796c051b3523d11987dbf4ddcc3aab83bc818191c76278c5e9468d01a3e9c3e3d08526d9c53b39e550b80abe04bc2193989f124ca73b3e4d373c7672e2df8d45a78c85608d61c02f81bfde5afab9fb845ddfb173d0e7c7719f65b89e3864524ed14e54887da35e192aa967a49e8712947449ccd465497a1e3f8e5aa4a56122385703260e781dc0e123be0688fcd2b4f655523941ab242aad40c461dc386c160a8ccf39ecc017c52d19ed38d1772b5c70d74e2a53232b020d75c6a0bdcec0d2a7e97114ff66f49aa358b8ab63a4eb451d5230351e20ebcfe4c6470b9825caff1e82b2d7fed3771902c1fbeb6dc2840f31e93b7bb44c18ad3244d152521210510b6e3d507eabfcc8f1169b1c503631c8d29d95fcff95221dc492a06a5d8f55decefef653e219e8d2d9c655507fe0de978befb3b694ecd660db1927c5460fcfea93eab27cbcbee0521ccf339dacac74f9323b149e9e50eba2bbd4e6d6f21c54e2fb489236e3266a0b0db1f27171b70148cf8404324384b17013f43c45f32690e92d3f36d24a32aadf180d936ce5e22a2f52ee46923b44be6ef6fa2e25bc257dc63d0b5ea5d6b7f54ca1e233f12d5a6c3dfac15e865f0aaa33e5d4b3cd44b62b3621c592e312da4c3884468c861b7e9ef5cf32d613a888320b8d506ebd336f404b7532da2bf93ecef13402278d1ab0c3cc38e2faf85a00508c675fc1dda1d407af9c9c9f0cbd45db155167fddc2a4fc702f7dc45e1205759e50691d2bb31c9c6ee900b63ae15f43c32c99c3d7843606f724374933b22405aa81700fabec8d034a6453f305f68eecb3883a579d90febc625194b9a84034fb72a95386aaa662e6b2b1e6353d5e3416ce3ca67e7463845179c43f633199ac4404ccef2ad050cfc2b1c575b368e14d928a0759f34664f3d078e56b17a027f3a62e19cfca5bdc81e5eb0cd7f73aca6a40adc8c45fd6d3ccbdf78362ca2f9e8e2f07e39b571dadfe9b08a959def576d7d7036b8421d45ddf888e6a0cc384bc57126488d1ec6779d9469f4494e60d4467c2e12223a7108ec564e2925b9e8f5c91119c4715a42035b4291474eed855cb294d69f232029db6b03b1a1751d34b038dbf934eb81d46c078fcb474516e9e04e72c4979478a2fb8697715fb18114e0e0320c3bc5e79835ebee5fb3468706b1d93d96fa2a3373ac7e5e28182740483bf5918405222e13641475310e63ac60295206a11fa141d1014101a4fbd0565e8845760fe424294b458a66670dc144aceae853a3009b12770926346606fa01cfd1422cd536bd7a34849d6d331d7370dd0adc365ad720063013bbf393dbc3abab4986280821944fd81837a65850e83eeae9c9e52c5fe403eafebc83775203a14a3d0d351cd0f0b40f6a9bc8340168dec8e6aa8b588885f687a1758a2a894ee7f7e5125d522cbb5f0389764815f3da7467e0e88f3c03ec8d2e15bb01ff53e5fbc22fb4211fc0dbc0472aaa8cee97bafc190b33b3cd1ed1bb281cf6c19c94617c5f496752448656e1abf3e37c3946a25845c2efdc8efe6df563c24334103132586c1aa7627fd95707308f33a693e7849698804bc99b6b0874b5e8ef94414c3320406021455faed68f05376b6290dd288fd50227966070afad681cfbe241ac50155ba00279adfe2a6d842f14c6fe54115efdcd503a264cccfc7dc9095ae909fdbbff6bfb881679b9e3080c4aecffbfcc043d7c2d731a652f60737a209361500c02e0099f2e9befd39d5eabba98626baa854beca1d51cdde819368b67195d12a0c3e7f0bb7df218d6ec85ef0757e764b87d00674148dda870d6e5cbc4d1476b30890f74687b7f64378da481c37183347c4143b0daa1aa4c6cbe35a98d9d61175204c73603e6814dc6cd00953b0f10ce6a5b3cbbba2e703c9fa0879297458793d95ca1575e03ff265f9a9b569383dc5e94df14ba7204f34aa86e51c00df341bc3a8eb57df6b135f16b01bac7da6ca265af0e8ef8e53d2f69dd9cc1b84d0a1337a8cf0901768be4d5a23a82deff1a7f68866d9280d7df16417bfca2f30ce5b117c81c7a2b2cf789ef2372465e31a9524fa965c0b0a2da0c325d0f69720d25bd49038ab1cee418a6e2d4b8a129f9dae8d07e64adaf9cea2485f477f03d72609bb99b0ec600af2a1e0a11946d18910f495872d1fbe0e3090271c03a9e74095da90234ba249616044c3d1038eaa3183a8aa9a8c84d3b673c7d840476830e244d1b19e6fdbd6c05bcf336eafa2128072b3d97dd345061b144c4bbcdafb0b16db59a2bfab1b847ea40d183c1bd7730787bc1f91db15e7b758a966992baa6837608eed4519ba45a1b611564788fce29e0f88d5c11a26746c6ee0a3e3327efd8a31774adafed080d4a49f3b0164903009f97ab9e532770ad0cef7beffcb607216eea8026fb497d6a48e4c0d8706282abeacd1a58bd876d39e5ad8b54e78a5f28881af27653c3539fd06f6d0d66463bd557441440469332b0272793e782eff237c73575fab3a030bfa284526d95d89c1b9c041ff0b68113ba713e191e2a40aaed97bfe44420a1b0954480d8fd5db81aa31e003afc0c292984250ed599b2df105124eb7afef43045f617c544424b07f241d151509992b6f19c09479a5c92185165bc595bc5a2e02b72bdec2eaa386667911b5a54d2c1f9e75886ecda5cc1a5af1ff3203c863f98a20f342da55da62b01ebdc48c93d3cc5b72171df8fcb62b4bcdd533d90d493b07ad2428261984a075387f702adea3ff1446183ca1bd706d870f82d6fdb22c8ef905a7cf0d34ff57570eb262cfa2e6255c331b4028c215f1eef3de545e069eca52d5a3725b57091296aae4b1ffc87987b28b2e591d4717015e6319c4182193e4269ec515993a83aba7c21b935190e56dcbe33aec6d6b501384c590fd7524464c370043c2becd636f574dc18d26ae1c27909b428be71190859d94ba77d6d64151825280178286b0fefe7f6a5cd8af505d0ba29c05bea2556acb0fde62878136fbd99d26e290816ca8afa03477d6be4b0eb4112c5bb7dfdcf962828bee15ebce9ad9bd001771241604289db16074947eaa6201a308eeacb2bbb75360e4ce80c32a2d0de4028c00d3a9ac8af175a42bf564b87579c1cb37f065751b9e53e067a70683c34e02bb34b95736acf6b31605654b86810ee64512a751d395159f52bf1905d156b3ab709f68b94b976cdc737eb6d96d47b816e91230cc51ac78f7de223c70364cc2214a043d4fb9349d26e94309540078b1f1472bbae0e84893f523c31440cda29c352a067983868b1cc4935c0981a7674a89c48fd3daf4524b300b9956e54e9359ca7016a9f6c29b1fedbc99ede995b41617240a0c9ab53a72b7996533f3584d3e2c6a0011346173ec65ec01b23578c0c358c0fc7af9173b081689c5930ee4b510967c602eaa2fbc28f56e66a41854363cad7b2ae0984cfec4fbeb4e96e5d559956ffa7fb02cc8991dc870b08bb8606ad9455950e9406c6b592f9d210950fe58fdb8ac101cff9f60f0b60fdcdebbb473122ef8730552e1479f462dad68a993a7dd1981b21634bbcbe9294d41493c87ffe26c1344572f38ff84900666250402bbe0fc1e86ef30d48521463e973a42085da45dd18ed0c3d59a56446cfe06fb8d63941d79a62f20715800baacf3fb40c769d0a7f0c290dff1764538a7e37f65a996939fba1d841ba9b90abfcc65e757b9488d38f884421da665881021f1c4365f1291e56ef5ec76eeab0d204c54835f82642a13fde36fbb80ebecfe4c8eebb56b13906ed4dd22829b48a0f530b6824a262be77b9171bd08d8c7154f5c0e90fa4691d8fa696a7835020c1841c5d549679d3473b4f07d3774390e8fca31f8c8f30c524a5eff708ec4e0f53b3ac9da378119046f14693918ff3499cbea0e0ffa0d6dcc21857c905c558d5039e1d0505fc174d2faeed90a3d1a24a80c6b50919e7f5af72841c03bd895ea3ec4c84fe85075c307e239f915c38262b98d49612ac3aa4ed0f464480ad3789251b2131012e1bea8efce99b60d8bae984ecd3f6696a61a0541d275d643390998aca62a9789c04de27e5644fe52182ca337614e3961df5ede92c65cbf8a5c6c0cf4045c229310dbfe9ef8a3a42fa34abc7a606e7e69466f8486615ef139d4e257caf13eaf634f6188bd04c037736ea6a7bf783463a23f40802f43bde08c80528aa6422f4b869541100a3ad677c41d28185c86cc4c3b509dc01dd1581a1f09635c547ff7b3bf792cefe3d3104040fea7302438492345dde3ec9cb916a01891323a9c34f5b38ceabe47c1919752dc444711de5670f702142570227e161541c4d1a900e32fc6b09b4e1cf4082e676647ecf0404c70c575bb90384078b340a4f4ae76c5c1ba1088bd518c4fc14d8c32e0cbc65c0a2cee3fc6edd5ccf2228f93a93cd622082c2cd4ee614776d45c67021e28ee6ec77e8c437f85d923ec6375a8d3d1500eaa5d465d08b9eee0ce59b9191dee77e77c37695a57a546d13b9ccf42bc8fc5bbcd2f70952921c20694411858e0225ea94d7d86d48e046833cd401b72ce94a4415233dba5b983e06a662b69f21624abc613d80398ac2177667f56489b48fb45e93c95636dc8c465aab680f040ab0c773e10e05cb129703c1000b5fc0dfc4f32ba4be3d9c738a9d6a4086c0706ffc4d1ec08bb5f9fa7df24a84a3924857410f2cd3e9186b42a0010fc0545b644de56e9ba2f209d9b6f95b8eacddebc8e2dafc10acec466e455d186e27443134b6e27f591f096de8bf365756a7eb12743c33ef03531a8e950b450dbd170524385d6d2ac9469357be0fce6a3401f647c3dbe8e8343f13000f3b624ed16f704855cf53804dc2af0fe7210abd5319f1f6558d5ff40a4323601c95ad2661046d2ec327f660e6b69aedff825b90b6ab01f5354e03d653ebea6270688e20095c6492fb5ebb6268851e95f27e167b77b19797ddaca6518d7283ad3e5eaab8ca880cedc37f667688085d4a29c1d791e002d983b3c31a87ef5229d3083ded95ead402b78f716989e315619b641f11203eb49b1a451b00701383e382e080c84d2c675be7eaf48452879f88d7516ca04baf1d5fa54d610f9798cc7928de56bfaf705389d6ce4438a16b7848ebb9f6ab418369c51f72f5bc16e926628666dc63148df66686deaa9d45aa2f36e54f14538ce105ab35ac04c44567be15387643ff0fdb6cae0ce9c3a85ec15cbe485e0b4e96cb12de946be7319c3527b8cecc58fc1a92e687ca66005e87e91fa5622e978abf8f508316a6ca220445901048167d8e7bedf98347600101d1c120506002028eb1750ab4c2be3473d6e4d0aae7c2cef6f8577a200bb1e6554b26de6e965c2594d76ed3167b341c0c49b48946902a4d808b82e1a36af0594909891cec57a4486581e37c04c89daa2181de7104f1c8308604ddd1e029b747f9b7af0f228b8690cfc0c9b68c5a34fa7c4d078dd5577bdd7bf443d9cca7823fc1635b5c0fae65b621276768c030c35d9d073613591d5a7916ecf5af7cdc0897fb68432d9e93f25ed5318b86a6a0da8e4126a42a011be885f0bf5df2071412bfecef2b8cbdfa90bb7e2ae8702e85bfff1e732bf541dd2cde0f5e39f498b65be3b83933cd624465b722a4e52e3038d4efe310be15d4735ed0570be421de546880fff9d502ea49ddb98dec4eafee9c93fb1bfc77461fdc35fb034092401cf74a125015b6f09e52eed0138c4ecd49ee64380515da49957512dad8ee044eef060b7b1debc48358e596e49f3f82fb38179e4d3fb25cd99af884caab2f6764613ef18030df31784ceab1bb40e9382f4aa696f334f908a370ddc14b70d316e9ae93f82b81b2e66a5742bca027acc94c7f1290aaea9130b9ee841123dcfcf6abbe94fc70621c8af7e09ad8b3cd3258f3621e1622fbd99c661a8431754dd49f7dd1bcc8a40c81628f2c0df6e23fb897841dbc7b82c2ab877739ad4e43f7466a987c20c4a4bf0e286fd1a34c02194c46280ef30eace9cbc62dbb1c21be78599d20979a5d8a4ecb31b5e8125031da50d8b3e645fa062639bd1ba340b2e22fcf06ba4bb2c58424ded7fd7c35068966bc859fe48fbf1c2e770cb89de049fc393e913a487a0c228c6c66f564f60d4c248247a51912a1b023ee9e92d43b3900ace1e1ae30201bdc4f59a8bbe512220a65cab16b6e952e0f98c7750bec33a11b96bb3eab5b03b4e03f338748eb3482e665a6648a6349291d068c522be61da3689a75749551c5c9e82aca10e62b8f05bc3e2882d4a30edb55d8f1b3ffa6f2d10d26f52b9a38e590aaa0115e0992e3406e09270f03a3093b26d5a9feada1e7aabeeccf8fb128be603fd7313c5e9754fd074a6e533d6033b62625ab2cbe7f47642cc719654b3b96f7aef73402b1241399e6f87b63dd6fdad57aa28caa1fc35dc11d0f6abf552a2a6a12b9739d226eb8029cf4a6b57114104a492ad170fa75e807f24c175ecd20a31a3205e51248fbbf7907e1887f013634fc21fb051d6036cf8f6dc220ed39e8d3056beed5f593fa00357b9c17327d14c480683390b57888e0aef319fa56805642d9c58965676340629f1729becedb473059650264b1bf2d6e595e7eda059f595a7edef8792c9e5210ab5322f509ecc5037e759e8242496f119a56d6d7a6b28985d7c10e23dbbbf127227460fc439cd8e9c30874c80c2a1951c1a15259f12644173088300c6b80d8b840cf6bee1f2e0f98414674ee7f323538b29a5f10736aa39743978479579f40c5ef5832fa211e315415578162ec1cfcfc0ae524fbe754e3c15c56adcd7e1d1dcd0cef8b9b7f1278f56762068242138372c5edd1f883f9ec903a12333966a5c71f6293623f7a63648e5e3d7924569765104b58374ad2c2654158b1e94c3ee075fe40b20c52c3da7450fa7a3bbd2830652121f56299af10a5739b87328886e26d810334f30481695bdfdd04650eb668628b8a487a6f045d423a90ad6fbd112059bc812c9791f9c4cdc9d68cbb791b19af03cdb7756b30e9a0d7041b58e091507df2eb039052d947011eab0a72046e9c55bc1a45f708633ce5b5cac508e2018c9a512fd48891f21888094e6ad3ab2bc8cb4ae8c804e0ce9ffcd5004872d3045cce3680e2f676912562a57f6260778ff8100ed544454f6b672e27f5aa0505ba5a648c61c13eb16579c76fa986fd82e6cd63cdfa1606b17d2004f643ae1b0fe0b13f3eac65dfe906376bb02b7e11c17438ca458b01733af6915ee5b8690fcaff7c0af759dd167b599dfc6cf9aabbca6cdcace2a94deebbdfca5c11e917e64cf961cc8c29168c410eca9f3f24fe23cacf51cf6c8317f69c2601b90ca761b92f3e2c063f5df41f9390af587acb014f8a19714c8de40dc0b234c71b2c5a5b8382c0cd9cdd1fc6e1690d48cfbbd17d60e0f325ae3feab4f9b7ac016bad58e96687a308263be84c924c03d663617ef8b2f88ce62381935dbf9a5b275a3ccebc298080083cff471d819e23713fe105c30c203e9ff99ae052fdac47aa6ec924e5a53831a5fdce3251ee85824284e0b1f45676e5cca603a45405eb98fc64f4bf8003d736e869ccd99c66fb4f4b4ef1387e0a145d81eeb6acd91d7ef274b60470a04a32d43fe53f98c2475149ebb0a425a3e87d8a16d664e5073abd6ba43d7748f54757d592b8c4f857b51f9d692a5a1b4620da0d95a48ee1b2626b75cacdb17a34264f91aecdaf6ea5e1a2a381a92af3546c204fac86a40a2f01f606264f4067a902c4261e1af98007c2c46e3148784b0bcd7ec6176197d64b76e104d7236eb577cb88ff4a3f6530b4bd8583a5d100cd02db88c64b6f5a2a456284cd022cfcd2981e8c889eb54464ef1650c1b0ea803974e05d727bc01723ec65d5dbd2c64195bc2f02acfb62165ef97ebcca9295ff6a31bfb96ed8b9c43e70b0e29d3925d43d6d07c1d058398c570da909dd23be25bb2c02c1b9a7f1885a5d0f7bb13317c76a772e698369948411a38a4a8161d0dd8be8eefdb6314702ff4ca8ef02da3500cf1b399dd291d086f008708614a1a5dde41ed364206e13f15fb00e54e5e4fdd7580e5bb09b507c542b09e2f46a82d558e6c640df3164e51d9aac1864cc9b982d6c8057ae2d6fcc31ff22533ccbb95a2df33566e071a306158dc01efd77ca1ff1a7b661af5d15330f2ed55efb73d8008efe6dbea3c03743cf21e8f5a6b0828900370badcd8df6e51610c725378646c5fc300e886b22104343ea1de191b41a05796c850e3b59c32983dd6feabbff874253292c6e464e00442ee1586b0b9452002650b7e147c55ea8aa6ca6129ef227f7b6d7c13442e7b01f48d98c47870328d9f961d9bf49911861d935cb18ce5246b04e0f3058d33e46a9f28d6e156dd62b5c633440c8221d71a5c12bf5b6104acea94e69955e2119ddf65b9fc2ad3d5a0524ec4fd765ec97cb9003135ce31d674d239b0a16a59b8021dd91aa215589618ed0b2e3fe62c57f42d1ed600707b5b764abf009daf8ed9d54d458a4b7d91f51a8961229a4ac01d6cecc9d9c1e0125eec0f2ccd9bc400e3a6640a1683e8596760ae73bcd9469bcef912c2109f0675eca2438a8313e7d4c06e087d1f8a07d8f50fdb963142c75e43d59801921701004f08b3ebee574a90c92066232a037b9fdd097e325f300b6f1d6793961180ca73dc58d9d5654cee84d62027f215736c2462d8e8694ac8039633419c563e7e0eddff8c591c5bec7f5e34d809d010301a55c65272a1d50418c7ed0edd1886e49b62e8e0363bbc8fecbc987fd5bbe31d1805251d88514cdfbf7d1a113504bf64133bbd776d5af1fc3e7785ba58d4334fa0033e20b4ac56974ea8b35bb11b8de12a3f2af067742f0774b2cc99645e6e3ff048887514dae88af3aac78f2257a5c627134c40a14aa10f42c8183d01075a6e60ab94012e58a7164ef0d714ad96b27d0395a7d9df22af6c02461e475fb1c03aca0e515831ce2164b70877ea4f359036289e1c257963aa827eeafd8f1a184f3dbec109f7728a9f03fba01846a9d7121c5bf6957a00a3c95e2e3effc5cc96c214450a383cc00eebde608a37f6e52c53802b6820b1208622bb870204332df8cb425c0230bd3faa6792f514462c0554c96b39301f7bc884c07d0b2b0ded407a6dc6829202e89904385e4a6c2d0bf3550ddf5007e5d4a18311882362ac5792cbf12b0ebc6aa7a358f5b18a72cf9821d7f804ca206bc804a0ae734bc86573c67e1df8b8e5f119274766ce6e6ef295af6706876065bf6b7535256444c58445f70e80901587d9b333a6f9dbf1440173af9e1632565ad9bea741eff3896e64f17d73427b24bcdecc50b80fe31ba345c84fbc7b3d82722a46070faec51879c1e5fd0b854eea3e09e2c0bdb009a06b7d5188c9aa1f904185bb3bf1fe192b7eef5ef7791c7c081d986b0304e3f9d3fd173af2d3bc06736aa03f50ba787f0d497509a0cd7a16629853dc84a8c852cb0c9b1cd72112120e5ac57f339af7448fdfd003dd04ecbefb19fddf55778b869d191581182c84da051f55169196193cd91a6dfa028b2e6411c7309141222c2d61947e5c5230cd69df1391f3312bac67045c1adfa0caa9022f5cab051d5302e36541083110d2abd78e7e1abb6222e1d488dd89a20e75a1112c001c4fc9f8747afa88516463aa48bfbe606a219dc30cef0af2f8d90fbed0344806558cba64997210f779e9e45f2b080c3499416fbb3eac1a978318ed9f4fd34c6fc764a689177105047697acf52d93e8472a3190c6798901b36dadae0864eed48165ce57765d61272031a229d6605be903e38b3ab044730ca7f0bf28c182cb4d892ecfeba20ee72dd13d7bcefcf92a053ca91ffaf301a486ad92fb396cbdebfb3920864116a41a1d9dd27f5ebb53e4dc6a6fb4d656a6f4678b7197132046141040add4b2a135c027da490c22ce2ef5fef5d569ab94275f00b9be8a1055fd0055a19870788f1f37e61e9bc24c8b4f1852ec1a2c3e622a73ac3aa7fe57c0d2ae1bf372047d1dde0b6e46069d71113e34824b9bfd6d7206d706b0df6b389a17441954b45665bad78ec6e24a5d222daa406158ee49db4f3d964f8d7d908a132a805b65b5439f62f2a679c764b7260c2eaecec3a35019a0ad9fa4b35be9dd113fbd9e3858ea8f243c038bf283856a14642f6cba12abd3b9c7df3843e148522d2a5906b93e035ffadf996bdf6c648c45c84aafbada85302d3053b4c9ad653a57490ae578358f6bbea9901146417586e9a11e57cf050a4d574fe8087db4b98035aa6e4c84c416f19832c0737f5578829dfc64c38ce32768adb9c81cc655ac1d12e19d0d696da098f8cc2d10c66a7fb8b7004e71f91376f2f35230988784b8e911eba83ee469078ba79d1cd8097e472ecc0d5f233bb558e6dcb17509010f71c225cab060d5991004bad2014e0ff4eacae4337dc325acb80e63e9c5963b7ad6e52012ef07748b21b7b5be9eb79dc97fd8820c848c8ded194317ba556b9b58e364ef82b6986629f56d4ff65aae25e90e94271f0825996302c5cf2833cb825895c97431e5f417a483b8e7f3a61c3fabec64f8b0688be30a1588d2a337a16eca94e4228e6c5e608586cf6dd13345b408b2650a62073ea6e3038a6e0aece2bf6800513041f4073de9d53888969ae66142d5dfb7e2f0ad8a99607f2deb46db4a1dce01b311608e7562587d1adb9a1e9fcb326025410c629e0aa2148a5863d6a9c76b353c67c43ecb158ff0efaf596570614edba03f98e9f55c31daf8a7f4d8200defa06055e14337a8db6af16049fdd257aba5f76c54556489ad91466f800751c79af635c37aefd3e665d57c830805428f0ecf2fdb2caaaf55e2d8ccb7f96219c2fd5306d931771d8e206a761300e50a937782372767f7edff86c5adea81bb92b8128cd17c23313bca0dc00db8bae083496e215a0de67582f6f20ceb75dac2bb4afd98f2a6cf9dd780eebcd6fcb66203bacfc293944cf93aa72441c23df722f07c07161bebe940464f8a8fbe6f9e3cea2cc834b7c30236d3bd6db17479b14bf63b8442aba68ac1e2573b79536e6155aedfb17b82417273558261b97f9bbaa2a80ea96273f1e8df597d91df824ace3452377c5b96a48bc8b434e838d2d95ec218a35799e81833c067de69ac2836a0f57cb37dc07396ae0bebd0abbb1072bd0fb0a2c30b2d9155b304754b175cd6d94cb4b44758ebaa38dc8aaa5957a9dd012aabf7620fc425d05a30979ef357253b1227b89569b685eac691d45aea740f30701b341b51869572430f1a1c70df06150df65eacc14a1df08d446bd51ba730056b143b086031a500a2a0129211b4f45fc11b311d37b0d9e273fb6b3dbd0f0cea5d13b3a67e5856a51af7835de2fa32904366fad847229dd4af2a72d3ec12cef9e301d8ea34b18c6531867e4127fcacfe419240b427c662b6fbbf0a7b969abd2fe6484fd6da591b8e03cbe21dfae0809d6de5b0418966ea510503b8f3f3599050b6dd7e11924f429dc69b2d1f82d9305a7401e716fbfd53f8d51b0b7fd5f3ec53eb0fe8b76062ed9260a740626f8ccd3cbeb38d646dee589075d039ef7208f83e3373bcec38b05820c05fff0b681ef98a384bfb1b933198f2df981e8445729247cfe186dfc7c756683914e5df31296b162623309c2f86db977e1acb8904f8ef9e3f3665826c1c2ddb5c3411f8424d040afc982042b8845f734d2a7c0bebf0f0da25684e54bcaf4062271a080c3a9b81f601378c9ab74a3a22d8b902256efa8c48c3ad3dfdb295c5fbecf286eab755cb8be7f5205ec61f17bebf8464f817713224da978b44f8dcb9a7cf720626b2a47aa3360b91d712477ed3c61220e6029802a904ef16cba696206f455781a71bb73f0127bbd002670c9ccf1f132ce0a32be28fec58d7418c1f3c5b4b0681c3b73fca26077a88939f4960ee07721f1859e33f9b8b2191022883e724494588741e2fb858c1c823b34c20c2b1b96c609cb396d4ef7891870ac572c5d5c60e60f6709bd613e3809764109305f01adae46105e533602452572f8690f09e8ae449ceaa82386c90d62c22c5236a31aa3589c6ac38f864238ab0bee3a86dfd674db6281557a9e034975d3da35976d6a3a42cd87494821d9dd5ca0cec2753d1a45f6f2dbae438fd077500bd3268389c57793af96d834854b5f02124dca720def1c66d285c63d316ac4bd482bc492147c60720ca0de0c1f5bc4608679e1226a1150a717a45c66468b04714ef17f88ad968169a43c80d6c7cfb8f50dde222eb957e7ddfe5ab517e8d17bc5aa3426097ab67ad2b6cb2d5a56e70494673a465868277ba7d2d3f5654d180fbb6cf727fdadb85260e3383e183ab0db365dd8b661a2189df0a9f93c0fba98640f5699859212933ede180d2056bf8fde789aa1317020d220b5236fb351112c838d25f3bbc054a197e76e4addd2b0edaaa3ba1eae32a39aab52c1a40d04f85e281a69e5f24502deabc3862b004d6ef22c6ef0e97bc0b7d3abccec9f40517bcc06cce9e5e83ca14229120ba21b1e4672f6a60b53543adb6013fd60c89de7bf59622350b5e054260ba5eaf461269d222dbcdbb3b3b1ed46269929396bae63a0ed98db64a35791f5a307c308b242182d1f881335e7470f24145446a9d71065a02759047d439e714783dba45693f8f9de0e490b834bf861c68d9fea284f4cb9c523efe3f9393591e25ec9cfec12f8f37bc8e42dc3664678941ad2768c73927711dea2f42af9256617bd527f339c68c3f95bef715b442c3eef239e07525cc8e68d2590e6d4386b111b42ac60507b9c4048633a390c89ddd42feb4995b8bcc71d27acd4d4452d071d5f1a7327f51b86b8b0a747f1b46faf80789b81f4c323f67256603040095a34d6477f47a17098d4661829be1be804e3d69e95fd3a0a3fdc4457c76de7908b73dfcbd24cb5cf69041be019449a573b5d6a972b1dfaf351e4c20224e70323cde8986d509413cec84b017c7f3de1349a6b78da62b5389060f37d5aa4165bd11c7f6238de1c0c08deb6c760e108897a9e5a53f281e8863d1c4758dc0a15f3e3e927cafb6f5bdcafa920bb86924c119c44e1a482208278e222140bac402068280953cef01e60838b1da8898112ff941a4969c496309ccb300fcdc7ed72457a131f67886587344b15e2c0b8753faf395b432ac1bc82af3a2e0967c250532f0858fb4f6bba4479952b1795a2164a78fa9e3aec4203aa5131281faec613b4489109bfb9d1a6ae516af4f11953d65f3b1bc15f2d66a46ed473caa07e321e697b06483957a0f1aad5dee791b005fc57580cb4b4c0872f597d7d7b5ebe53b9c34983eacd97a18f83cfc5697f8f9d01fc280f2af023efbb1a795135a1fac6d66ae10285bfe7efa17d4911fa650b088dc6e393340ec6cbf9879649c27f59733638e4c1d260fdf808ec9762254e6cb8b66e2694933db23a8052f11526c75083f212ce0b63831f5ffd93acb91f9716d042706c4d6ce06350b72b341cfe54fb30721a97e5a2e203edbeaddc482a634efade976873fe04b93679877fa4e17e1ea812930c4a755365466517cd649e53f9c7dc99406f9687c1620636971419c85cc37539c80823c9c4472e50a7c5e95ec1bd725c02bc768d31154bd70da3b2e7447ae8b438c6387d772904551fb9ec94b8b4ff52c3d9403a6421f946e10197a016be6e401c531497ad9ac3317465d6dc5f676bdecf28a67b05380d5205f05dfcf1ea6bd031f704d3819bb332e8a57cf1d74d66c82470db235f66f96483ee3b6f80b4e6ec1709f26c59aebb28d54b757a3b5e7d4b4ac6f53f596c6d9f0deb1f2ef343311bbeaed4320f22837114d58880d05b250203abbda419731584f001d30e633aa6f8395c04e1597162a0c3ea572e58c5b423eac970b180fa25b770b9380c120bb37e7968ad5da8fb6a58bbe1ec88f368a628a8cee426274f5a20134d933d785697cb3b58859573addaaf3e0adbfe6d182c2aa56d8e52d0270c2b44813560bf3739ee468ebe74e1e8d910885dcc48504a661506fd7744fc0091f8f26d08893dc85b0fc8917e273f129350bc268ac897f1792c2752112c64d3f2c18b4e3fb38782c3354aa0b81689dd087d742895d23c98aa26727e61125f764f4b78d78a6a82c543ed5423d88e395c2ae7e279794d67a6159c0faa5017b0c4bc03d231d53cdfc3abe089dee5c4209fdcb93acd3c802bcda62170d47156adce77cdaec5b6801e07adfe56153b80325d3a37df6fef3bc623423daa63640d08dd15a8b7a2a4f4650951702fcb5307980ef1fa87d53d874931b0d4e7198907b9c0cd1fdf87a79111376184e88554b08143c091235197971cdce96407d2749fc271d286c752fa1981c2871cc049990f6eec5c51a657fcf4ee7862f578562df757d674f80edfa86a826d080405b1ee0246e60e978241a6db45ed6f59a90b82843820294e269e2ea0e6066345992c4a607641c1fb48eabf528b2cd2ed3897fe3be18992734cdf1a1b6a0497443bb6a7688f70e0b3220c5d0c73569e889976c70e0cfdd9111ab1829919d9b8a840e6435dcd7274238cb8689abdba6a0b6abde895655020f6f4d28f7edded0ceedb8d77c64b098026a210ab6a5261ee66b6d73a4369f8001ffcc67b25c6760fbd5a8bb3c4ad8f3f50b00a85ff61a8ec6e96ed2ca5aa537f9b115af98f82f46c15780f44a57409c69884bf8ddc48625c4376874eecaf928426e751d125a4db14af2737e227c98ba713f69909b2b46ff00cdd9c58e647d9b9e1762da520607e8b39baf9b707584ee277c55e51b8ea0e9cb039f2badc3b1acb230d6da22f8525f3054418711c66c69fcae312bc3808f098c17ed489c9861702ecbc2f9b9f4e48bea2914c8c158d1804b30df29af34522307832faf004ec720a0abcb5199a18ecb816e6fc5a14f5090a541d264cf9dfa83c31ead138ca5615b8b72d94b321feedefa56e8c9b4baa64c358a974f9af29134fd304b798243ceb3477b032f2c638f73260c9685623c57ac0b33c450821c78b6be2f9917ef5779b137c3a4e06c28f287d7645cce97c8891825c1d442cfc38af9b9b6e452c90a71f27b55388ff34a4f56b5c5c1cd69fbf19e7947e1aace7b60378e375ee65ddd702f986d9c24c19d1ad712026fe021b3e98b7254c64634364626b2ec625082138c9517441677e0568326eba12b939c04e473641e49c49eb2ecd72bc0cbfbe8aa3a11d91e2c32db1ab2d90dd16b001f8b4d9711d3f684124c6d9bc2a9dd885536613d295d11bc51ac7b2005a4d299a6952a903c06235b2f97dd6c4cb364d41e43bc3b445b4a2afd81b86b222729418a62806ee42af7b877b67f9a1b17ce61f9bc907f2be311b7d74f70f300b4ad76f08480d3b6aca0b48ca68a57a3930f54f438fd046ea82fbda6c6cc76a87ae52a03f006b2b75b0b6f4a832e53d496455c3a42dfa667a8d380a72e8a451812f4964b21b1a67afc228554a8488757a01523d59048a5e4a8b1ee4ef13a45aa4c98dc39ed77642bf094d45c41e83919ef5b28eb06e99e8cf76066173d4b6cf7fe5662d18a8afe4c1c26dea11e79857135d25878abc0a02ec408a62eda2d7a6f861226c9a99b3daaefb4fa3cd07b9199609fb27c0d1af0810bb335c28dda91425542db993dffb7e127725c5002cd793f33197fb82800a076178913b54359a44cf5328e149cde8c1576b83544eaabf341deb45f394da6230e0862a1c7c6c10bdc66358771b5a144dd2db223b595f8cc32bc79225034fc05dbaec1302904c7291091ea791793e7adb41ee4b22564198445745750b274751cfa052e18f550c0b4a60645c6cc6eb748a4836e3e75153787496ff230f6b5fc9abcca6a19ca4288c1185fd1f27988a21f8d1784b0766304d2da89be3781df4b8807fd735387a4299674a0c2e6e005fb953b5ff8b1f6715fe9c71404a246f1cb0f06e96c8897f271a17556c7ba0896568590e2aa41638317bedd969febc50112bfdff8744e43475d6974bdc6118a322f69fdd07493254c9fcb47dc2cf87d89a7cbb65e2d2399dab7667174d7fb21e427b39614e2963155f18b22ebd8ae875c2dcbd8e864b903c13a8d7d9e1fbe7b46df27820759b844fd5bf47cc3a5fe53d3d48a2cb344217916ece7e6c00cb847ed9918a887c5373da3ca224341025ebc9437b903de8bd84d977493f3125774bb0fc9f6fc94e7a4af7397f97d60356babb2939032bef769694d79c291356d658ce5441a67ea118ccafba69a4c8e100d730389aacf46a6a49023bcdd9aef4aecf81a3a0b9144cbe9cf3cedf8d8b4040c3f6dae8e44a38c7acf6e0582b709f90fe38186b3d82ff56b800867b1fc4102bce2ad5ffb856725b82f08a7974f2ac14e52f75422834f1c589c6177166ac552fea5623afd8587d512973a993551454aee3e0ca01b7d9a2ddf44b0a6b563ae335946e1c485a2f149787a4c6e410586f57b1952f53c75be5d87123a0a733d3ae465651a508f172ed5a1768c1cdf1470cd7f1b61fc76c67daff94c3dd33a5de821f08b6d128fc6e72ce24821ea945366e05f0cef469c064ae33bf48912f1b03ff9ea20a5e7d0f2adfdd48d5de69812a8a921ac48f7ff90b832819e2c472c82aa584360633c32185fe92257eed0161b2f8ac8d836c7153f6a51957760b35a1d6d665b58e35a1006dcb37450e0a6bc425ddd4cdb281d5254f991c0a193ae1c1824191b8fc29133cf0f180cc62b2bfee3718f875f909792f7c51c2a228945b5413e15d5515e721576889f942c97657382c29e6bf201584929f4fbd60cb1dedebdc87952e8b516c069a701a15e0bbfed1af9517de5e85ddce3281d07dc20f2a0a65411ca6d54412d5763e3f8443e889a7aa49a831bbfc9c0e84fb1f04feec07c50031c2fe91e2fb02ce28261508e48fcd42027aa03d27da747b3de5f95f95ec1439262726c9fb27a676d70e0ecdb2472570b1e70a40f853569be95cc57bab0d7e57d7a6904c749dd7955cb55d35113aa481be3b98effc3d662f7a595dcbe57cc00cd7abb613a31829756462e2a971b99e8283556ad0469a981590da86f1e14e0c7fef61ea00ddaad8441bcd631e9f68c3ad1e420d59d24d184a3c5e0d1bc3f230a4d24fd4430a2907aebcf28322a0cd9cb368e53a3420f121362dfc4843b190fa026f56f9f006978e6913fe3afa1ee2641ecfd52cb0f1f5f1fa68517d9a4567e0958255c28787f6eb294cc01dde4a82c7c3ddd2934ebde822a2cb0c1d0343a7e4cdbc638a9345a78f9380a96af9ac1131c6d3544e4e734cd33d4e08fe77fbc32638b88b7a61359c79462b131b4a53dc3be738de25aef21dce548b2b462125d6e6e167c409bcfadb1dd3e8fd937017be40b0e2bd58312b13438d9aa92e10148bb1b496aa41d818446d48a688b4fb65947977f6fcb0d6a113a053bbb363a85da5dc6f5f91d54db2892d977f8c2c2e404ed0d403c8d607a6035e915164a5b5ef8bd462a04f24073f528649bab27510f61af5669bd98ac7a15b3d706e751f217e4f4582f9cac9e42a310c3688226076f761f5fbf102f2c9dbe30b573340401dc9edcdd2b3a9459a00021959d441cd1f59b53b2ee7bd62e8382351bbe8b5e3f95e9e282bebe9c48f34a24fe377ca46f559c754e46746ae5a2ec32367eaacf56ea9360b92b31a647542b99da86c3c8a6065c24e5f9d1cac23d41b8810bd2a2ec55f4a52ac3cbe8de1d94916d392497c31c0767828883f938c090f18db508274a4e80d206f0aba97f07f6aaa59b062bfb7c7f5f0f9d381ceb4b9e54103be29ffb4efcd43d6a076e91e7ae3bb79e2b1bc512ae0e54648327e0e3f47d347f5113aec681dfc71800d151773c3aed31c03e45d7f9a7917f144531fb726acec0fa583849fa8d7c36a39a7deb0c8c150884985f402e50b7b384555610150b7afbdda9d5a46c19e6e79ec9e14c835e9685bca997141c73f701cce9d94f4bccd5cfa7e1f25a0411ac7311b08a6e10ab633782e8fc2799debb0454b5adc5dd77ad97aabd3eff8cb373ccf8032ba461549508d74f7062aad45ac7e2012a07bc91ce41a253bfe2463de0e5ea2ef32186121aa3c65fea6c667c55304f776c621c0d99189ab5bd761deaaa021c011a6681084af1e61ffa6420b2a3ba73675505e7428138d47706aaa4b630e1fcde009daea0a84895a12bc1123826f9d69d0f3670778a0320039bc1ba93a43658d41139fd7f296b863e6a6ddf6f5ab75bfe707f1d97eea9fae733268e17e8f703400169994590c6f2da7d55b7a446c4a59e50c62dfac0670f261a87374b894b405cb3623370e0530aaae36982f31f57abf2c2f5de9f0fe8c5d2a226aba51ff0d13108b4f8c9d421ba9030d66aec6d88b0226974e219ccdf3617568483af6fb37485170b6e3977841048aa70bf493fd4a17eab2a20c7d41f4960e91a5af8d09ca7da9a34b62bf95fccb07353915d15cdbb238f53d6dc1b61b6c0ac853ce216c172183d971ebd9e90f19c7ed23efd3e642e95f8488bb55c8d4ec472bff9a8fa8accb8cc5a586990efb70f0b3445113479dd6fd517aa15e36de2204ff7c181ac898bd7129c17017d91cd8f8c57d01cb4ae8995f7043474a9e4fc1062d7f4e8d0d95cadc1a2f4f44999c02d962e8357981ced71fe64968fe72486563b6e1d070aa5f523abca9f9f99ac43fcafaa452c72684492e40ba4e8a2cb3abc21013b23508c34565318e4aada1cf6a645ae0906edf030b6b4936460942c603f5da29ca358473e7d08d0d53c1b5f059559cfae579bc3ada96ccf0fd56cb7a0b921e408d3afdecdd7cf36ea83ab805ede9c4cb1bd30ada118e2828899050829e0fa9afd306d53f0b6b42edb20014e80183d73f9f7d06c69950d436f8cb1be43a12a95a7b1abb94090d790875e92232cb213b516b36f3d9d4a7cbe67d55568fd095c70db0a52b2e0e18dff623b24735d002dad91ad0368e0f6f1b3d4fa3b2384056301a34e6406359f9d408e308c5b70a75db5f395b479ca2b001a2a5104a5f89c922cd4d4482539ddf20d589023bb0f07c70bd3405521b4a86b12036283a768defc7ec32b08a7e63c09cf4316cb6c9e242c56a207c422f4c97cbe0edbc2d3be4c7d3951567afad7bce6a7b8b1f85468f97d63e6fa833944dd5d8d31fd0c5ffe6af822ac9245432b75deeb6296058345f3a8ee36fe42aaef01edd48c541099e3182e76d1f19df1003f14eb997708dc25a42f46fa300ae10edbf73b45f748f002400f93de9fef9fe92c03b48e9e8bb41e92c868328c78eeb53e92908dd85596f04da1383a7b3e6194763d55187e3220780a7a20fd501b31e5cd32052925640135cde8219955aff51ea08c5973acba08816c681569be9120c785e0ed54b9659a071a036ea6b21ef874512193d0141863673402613c2b0ea3c6161312f0007c08161607bd97bc404ddc522b42abdc1a8af10abce2eaa93465d1ca1adaeaeb3535e269beec6977ae59c76d0dd5d5668c92444c02b1f48dc356ae6ac0c9891f597710e8940217ddda28f1d8c815af094c48194d96dec3f397a460e1358919defcbe6b6011f7e04670e96e5928a9e08d2aa04a8852c9ee8faf5dfa748b9b063d7013ed7c8c911b3d8f8da1f3a62a87b4dcb4da545ef17931d8beea0bfc86dcc99160aa56ecd047b54248b2f00ad85d38d7f693afee7fbfa2128d018f99937dce7094db93f6bf0eaafb4d736bb8cbf5b7fb84efb93150127f16a30b353a6a9c3d9273ed790d90ca5cbbea36d80555c025cfa488e25eba5ae7d5c566d8540dcad41c80be280a3def80daccb8be3958f4e144d8a52290017864e4d2fbbeadb30200f88fda044e0c7438d8fde36cc56dab54264ce91140c7cb7ca2835e28748aebdabff0f0c4ff606b69fec88aa00b5dfdc10be5c37db87e3ffe8c858207429d37d1650772c9e7acb15bdc46af3353219b0919c792334a74a3ad9fbfa646340a8961037ab4f5ad1879aedf735e6d092c6b778021529a342acf125a44047eb094a8066fc2803844994b947123c1d4548a7804a9a642a1b99636574b67531884f4068d3f3e84d795368fb21399462c01f79ed4c89f24c58755c8423fd8b821a96257874ce559d8bf92e8f00309587029872e9a0ab108f846a386c245dcd1892b5c3dfd9a86ff085478f350d12b4f9f11b0bcca23d26af3c53ffafc6120f653cc441d675ca61e6384a09a7b75b347b24409633e3bce09011330ca3e3e0a04728a926910a36e0ea91c9b7340c50e2347270ddc2a8da6b816507f0657d532678004a8fc4b13abb08d28a85596f293346d9d8a42a5a70f662fc9cb07a6703bdb242fa139c2d861a9575a8f6207a10f8eee1d6a0d832fcb39f65461e7e734be83f1795bad95d21646a99a9fc5f0bb32d672780c27fdd0bcdd9404d343a7526291ce4238dd835790efd1429da4bf874964572044e403430fa9161dba21f70f703e2f635fe0c77c846b517fd2203039cdcbbf8ad13c6f3af43af3a8c92fc144ad1b4f8bf6a71c1ce9a238d256277e93aac5a4c0521a150bbb01a873c8907479f28d820a51639650d9c6cec2d0885d7724955b670a346725a04e2fb3b33082e55c3191ed6f823abfe671ee28921d919c6a1eb5b11aa0ce8346d739ff81fdf452ae2d74dedecc6bd93c5d0b45f94d5cd1eb40b15ea3a2524197f3ebd198520ab466c97b0231fe8629a8c273399e17e7a23a7bf9b76c281555653a07f986c72bfde7636d4616592a953a79afd01f01286a30f83049e489b395903b0fec128f9f3a074d986f9a4764350f0bcd0e54c6a3cf8b29778b775fa95b50f0af0034e1ca4ed93c4d3206e7541a01c134c62291125625a940b12a8e308e2fab3962764a80735cfc823d0d08aab4728565565d1fc6a205fcdd42228492f1c3a6f17cb0f6b5f9ca0674a8c592f11fc6a98a249a52c1e8f548cc28fbb73bb6324c895f862b2565a60b31ca61cd461f52b523193e27c598477e46d043e87012314e7234649958b8d3ea9fae87ec089af840f1304e061b65736c7d8809075585dad0d3699a7904aa1a3a8cc510ef7d5331ab1aba16bbd2d94d080887f8ce7d5d42f4816d70ed637486f5d00e654ffe14d52c3081021a8cfedc416ba75d7d6b2bc7111729e2409f4a03602f510ad65b33ebea2e98a54988e063a851ae3f3c0ff43b8136206132793b4a3ae32ad5b60358ea4fd3fe03e4746a945183085c6127e3c0b7ae06fc6c0b4bda553f12c9538a0f9f0fb86925a00000393c60b2c3b410cd4532ec09b069c89a819f95ae60af26ec66cb619c502b69188ff2c7ac883445008b909a77b81f97c20e5027487020e1100223817a1ffc7ce380412c0151956c1884e72bf1dba0202a6b25e2f2ee1c876f2f0296bf6a8f790fda128c9ce581e5401caecb39b0605606d6680cb2c05b33e8069569f9150d0ec2f9defae1fd30af0dd01e4ca75f829c81d6a29080cc9667985b76bfbc799a2fb65ce1a43382a9a512bc0c4185d562977ded362468a8be1e9a3fcefaa1f6f33374fecf3ea3e4150b0dc108e5335ad6e317f426e4055dee4b0414ba9e6df0f73f270e3701fbe2031cdf2927723840115c8a08aa8f7072f303bd4bc6554edbb2cedbc12036ce2866200434117ae83a45e59526342d1ead13981147d1b91dea622ef50c8d1f378b005fc06d5ee210b61f92c6267e9cf46efc41b55d37b800f2a619c2c2bc255052399650e2a5a8b1f2818fef77d1ade04b052d45295a6b561d92455efc1981e3260cc568f25a496a164013bf83ab9ba1b17df021668be876f9b61a194cdb92a731269fff309ee834e8563d5ebcde36b8f801bc300c2c1140cbdc12fc28e978889f12dc25299acbd37b8fca11effa843e0b72cf0b0cd474e678844ac4a41aa39b4d6a0d4f43a0bc05c4034134e14ac7444bbd301677a2b10837d4e24cd467058d47aa21cfec762bdb72794a0a2bc0143b90596fb4a0ef9e67afccb35bec6c54f25aaf8d4edf6d2d1e73f3cc18eddc61cfc89ba29bcd1f5aaaaae0952e79f042a58ad68628d515d2a6ed0e9b10e5a42736b3172dad43e4659fcdffe275750c9be96506a066e6d1d839c255446991457248e3597dda864b4f00176959fc0f3a7c6f1bcc672f2b0ccd4a5ccd46ecdb3b9a63a05a85beff49e8f88e98d3890349f92207d338e1337b34472e09a0b098cb43c9c9a034882dd34d893a364f65131a07a2f5da74f592834db75a9b3066030ec30f5f28ce8bf08af571ff4c47e8af12c6196c85b0975f88a3f089143e835b7a1879ec3443460d62b2b60915b88ba0791c6475388f47c398d026d3ab1ee8dce226203e9fb13820fec0fcb9b34f82e16e60a8efc192e0c381a1417306bce978eb8d05e958931c9aa533e8d0116f32e0cbe982b0e6fff77e19ed904fd2ecede624924e8bff0545de88763d4decadb6b07dd6115e9d402b999da3ce19a66317c02215914d15bd016d5cc13e674c27a7d0e2221cbbbccb0092031b42d52749212c297ecf6c4ee983f48ec0756a318aa168b934838d3c3324a0d2498ba3206a7cf8fe2dd13a9a5c29cff5c905caa9469760c80739a75f67f28cd02299e57f46b8623484cb00cbf6859a4298e5117f1cc41a61d1943199dbdeeeef45040870d5d3aa48763433f2370b0cd2a52c4bcf9440c55f3492e2e65f3914b0b380a655cdcd48d49608c64d981a47de3933385535bff5952d9376b4287d3ead35b307663c79d71bb4c007e68fa298c080ae047064b2f636c189278a4bb97784de29675ad1d8042baa85a23f810b601dab69161cf493108bcf7f6a1f67c397db571cdd169caec777dcdda34abfabf6ea70beacb82593c7f680a58f8a09e988f845616d2a7ea2a15bb166d04c1d32ded1ddc3be2562f6c252a7e49de157a2482a4ae92715ea1461a3a0159a990fcb344c37ff81bb0998100252ed801d8b98b067d53d11801145e7a6300e104ebe01405445f37ffe7856b66ce4ca3ef3838e0d6439df1a4e86fe767fd16fdceaa0d20c74fe91f3bcbfe7078fb3cbc7a67e61b6ebc8044cbe1a72fe2f7015aa71bc464cc078b630038a555d985dd73c2ad80e4ca6bd372829564690c857f14f3671d7f4737d7ff5367ec68198a1569f563859f5c60b7443980cc1eca7e4a0419a6ac875bfc598fb890cedf31f430ceb416d344b7eb007ce3eb9c8483faa824c7313b84269850ce88471b05838889064ca57c31ac480678f38f3d94c14718995aed836bbb72986dad40dfe4a65a24f2da03fc25042a6157a95a30d815271df1a9248c74b1cddde5f6e2a8e6ba65edffe82edb4892c5790a2d1d4de854167a7c90fa12714c2f6e57e904a07904ec884081213bc56c66429fcb8f0660d4baf7a451d822a6ced5476bdbdbb61142c82664ef2df70e5c0acb0a680aa44ffa19defe76eeb3fb7cd3cc96d0e74806897e589f475bd55a0e5b49cdfda846f490ca54327d6693e964f2ccd8fc99407307fcdc79d6a51e587b2f0af583e092252c968c138aa9a43de5db8ef2713f7db7441a556b62cd1699b3c3c3e289f9c9c0cf8f063210f343b2dcb5d7dc6f246b4ddb4d1876bf1dc65dfb4c4bebbad6b49efd997ebfd2533ab47387d96e4f43ae3b636283b4414afaf6c990be5dfbec35d35fdc4d18a6530fb3e75ef1ebbe8461a4dfbfb4f697d52c69c3dcc8eb42a6c8403991647cf6299ff6d3c71de5db6efabc97beeea34f74d2177a0cc5560aa9a5b4565219625c99819f1f0dc4fc34bd01249555faa6efe354865da67b718a9e647fffb2bfa7bfda4918b65d3b8cbbb5daed8772ed3bddf4dd8f3e12d54894462f5c6bb3c3eeed33fcd27ec23052ab6297e95dabb3528652ae7b9209d7d488ec4b58dbba90e53c91e88b2dda212d405ba347b38436cbd60c3404a1826290cc913b4a3eee337cde515fe8299fe9324a285f8a9e4895862fb65264a0f0f8087991a0eb22080a1a81b5f7a2503f088a404890b5f7a2503f08364a173af71a138a0a78b225148e3bd1c87daca9924028b3e2ca7cc7a23964b1240e6d3343160d59b8b39ee4b8d039cf74ef332d1b02b283cc7d1b727517322513e9d3208c865d349443287353dcce618ca57470efbec3fbe931e442691f289b18f787931a55ca956a94777fa1bc8b13e58d5f29efde694ff94ccbce16946b32a77be7beee9c0e1947f94bc651304cc71ed65d068ed3e217ca5330eca461da9dfbabe3ba9317fa4ab8a686f49ad125cd0dce4e689433495f6c09098a693d1283a7eba2eb64b4b16449cc85424ae1b6a3bed3657cde51bed2492926ce76295f6c914cae26ae26381abfcc0cfcfc6820b6b5f7a2502c6bea80f47bbbf79a93c52e53d7e1549f8ba6eddd5fdbbbf73b0cd3e14aef7e3acc2bbdf3be9d708aceacab190f8943dba474a4fcf457ca4f18a6d387999ef23a1d05c34a3fe118327dde4b5fca9aeea55ea5772fa5ec7778377d47f7d2a7c384e3f44a385aef761f8de109f974fd1095b1d8be96acb985bc6ca150e157dc0cdfe9a8cfbb8c0fe5a42fe5a510377a7767f8620bf5c5968c2fb648251cb345e6ecc470cc98a99a583af9f991959be2ce4cc167e0e74703ac19ca40cccf9c2d76ce392de5b6a3fcf49a947bdc86537d199f69e9b8489f993bbdfbebd47da615aeae1637660ec498e832f37c8fe862f366a79d38b4f1a7a35e8ee8d29fa7400c65202dba0615dcd3bd7337dd3ee52a1db61da58461a5a7749d0cda60eb64f8e55d0686a5600fc350f009cbe86497b1291ddcb7bf381c67865fa6973009d7d4dcd7885ee39d7bcde8db4bd36efbbad0c813dd98fb0394f568c86b8484db970fa515794e94cf3fe5d38ef2653f7ddb475fe9a42f65ea2ef26228555b285f6c9dbed81a7db165127db1d5c5cc16a984c7496318895df49d8c19dcf0ae061257240e3102c547d9f203b78a2bb3f10f6cc59597178016ae7c877e48d5f3105ba45b207ddaa7407888ad9a1aeddb6bb297de3855b18bf498ede767ccb46444bf9f8f3ab4dbbf2c86e9347e6537615809cb88601b96d191317dbbf6995608e6a923633ae9abd922c2291da56f4ff545b7f6a2dbcf6f2f7dbbd9e7bbd297ea974ebae84be9289df454e35709c716e9a2df7b114e354eed4891020f4ee06447e9262ca3f312bd84613a2f7bd161dab7ef109d034a20e1890b68685a220ccb6e2c7d243cfa745ca2675f4ad3b62f25c23535a1d774afd9aebdc67b0df71afbecb1b5512153356f3fee9345640ecd4d6cd9d8ca7e4cd5fca4204933b33922ba947cb8a23126ad953597559248b0649784e879a29914095aafb821cbdec8228d43959c9a9996906575958078c3ccf017a03daedce0862c1edd129b7559837af2451799c562b1589946a94a5c913ff97451e5345d4e1f0e47e8014fab93dee9935538a20a39ad6e7a13264f70c3049ed6e91fac04318b214a5a76dac027a8a5332f714a0753e9a6cf64d267f2fd4c1e7d268b3e93439fc9de6772f799cc7d266f9fc9f63359fb4cce3e93eb676025c88410d43abd0434c2a0a48582f279b1c13b1285bc8edbec923caf61f19dbea6b1a2a06058095a51a54b5d81e8ec0c51d5f79ec3b3c4a851176eec398305573ec7c7160c4fcc22c9933c9fa387667122cf4320bac4aca5663847573c4fb36f2f8e9117aeec9f79d3459530fee40cdbd0d4e8a4224a33d1a45f77c9bd7bd7494f7bd7c54945b4a3d402cf3ad194807dbdf172afe86691cda2edf6bbdb760f257a2612fde2f08a3ec2212a775ff82cbf98c3d71cb877b7a11ba125fab65f9fbd5a955dc3ae2de85a8c03bf3498612737ac91b3d7a89fcc3294837db5e1fd667827d477950f9c1bee8752da2c6fb3c84c4a8baff59e33cfc69ca2077308e62035d71f2f097349ccfdb03f06c495c9720016b0c1d9ad7883767b613030080c0b813568823c4f6337bc12e8e6d44bbbbdc5b058869686532e6d66a69544fbe254e510cab2932909d3030d59b68087c7933c234b18b178354b2a521b53ce796f63db51074dc3b012b4aad0c5709e74b13b635629cd689665d9a946c4cd88c0a17999aa3ee5d9999167a785fc08d260cd3020ec3c519329f6a6c5d120fd15375ce5db60f78c46c5aee675b16b25907a5601c306701557faddb11a675927c4aca43102945393b29364c9901b213e3f82c0accacab31764d5b07142d59a932787489aec5dcfc62c91d56ca12d1db4b59a2a6a69dcd16c6896987b88c151e4c5460e14ea07c1d5aa8bce15f3c1751dd73dc41479b19143ce6957d765a74a4619623e9a25b4425e2e986649913435437a88e9e1e57ac1bc62ae18ce1671b55a1d86c68ae58ae1e1a3c60b1874151b66a3507395a3596068ac58ae181e3e6abc445a83c62a7a34583456ab154d0b2abda71936d21ce3ca6b344b7871a81c7c71bd7ab061754f36a8fcde19570554a974028971a3125dfaa5db565cd7719dc3c5b259d1a811dba28cb83d344b687bd878c111f323082c87cb7a396c34d8e2613b4fd6060e1b2f3796090f2bba34901cf25324c80f2137384790c07a2889e1e1831557807e04f9010403e2e142175552dd16635d276d78c4f49034f2864804830fa0dcffd16d9ccc71bd9aa55fc0d249906149eece85b4d08dfad260a31a943e70ef1e72f7ded13a49f5fdd436a359c2f853eb411aed57346a744bf40250184836aec6f0a033e28a6cd06bf0d9edd7e125bc73ef66e806d71cf57c34d87fbd77ed5ea4d5d60d5f1be98ec6c566511207896788376cd886384f6933959ecbf203bd3c4d9d81d7f2b8c71c230bf7d07eebece779d693527ad6fbe6799e77ee0b676c0dddf02c8b7cb8bdf33e9abbedd3b66bdb69839a77db2cdbfb1e0e376c716c508b59d34e3ba44376972be64d8cdb03c73498fd93c2ed87f127025523ba1fe2533bc46bef3c731ce5c4167ba459ea61eaadbd17853aaa491399a4c12a711aacb7b3451e69547d9548ecb5a87b14f81588caac1ca27245e57aed8b1905ae583c3eb9c678882d98a9aa625ca9a4c1fa0ccb9d06eb27852ba7ac4d38ddc88356e220f105e20df3d3621c110a793ee4f2f4a1691c71a50c37bcf9d6e89621ded2eebdf7765d4a323379a770c3e8130407bda49138b4bc41d2c8badddd6dfae28692c607874facf206f99b53cd4906f2071d0653b001142d450a72761a5996652846e415dc20d3537a148b434343938216b5414f2fcd810e4c34410c5d48c10bc400a345a3c8947699524a293d856e509397b36cc615395469400e6da091c525648f61b42267437c51051429a8428f0a8030040539fb8a358427677f3122673d446021672f856e64b11e1c81232160082264fa1bba416f2290f4a8c8e189460e67e44c256736e42c873472868224c09cdda2a0276747d9e4ec6f96cca6670814e4ec2356a6032a9a082253c0135114ba9145a146a60f152144cf8c9cdd339249968e4e11c418999e531142103b310959258a205240809cdd460186abc5584d105a90f5d414c139ab91b32ccbb2b39aa53ebac45c4f2917997edacc467616bdb3e50718b2e8c1177e983831452b83226755d080660753f8020c9d24b4b22539ab020956e4ec349a45cb599665d96807379663de71b133bc78b4f79e5033ac56356dc990aa0448f361013da19e68925920b23062064dd505828b18f445391114041a9bae77b64414bc40664dd3b4d3207c11842733d35059eb398112596341ab5efba34bcc5a56c49156c53c8125485a1573682f0a8922f754837278df1141bd44aa3dab59560f20834bca91878b2105f0220603d0c2f572e4e12227c9ed72e4e12249017eae8c1c79b8904245836b73e4e1a209aa2cae28471e2ea4001ee16639f270d185ca095c7ca1251f01345531d397f6420a99e6d873040ec5e962086eec0102498e3d401891b721c71e1300753c59a2c0f86329cd33356dfd992ad9627dfac90a77d0a499a40b00f1939f34283f693a30834c1f76900ea52a903a48faf8045dc004364b08a50f95ed65593f6995fcfc3a2805d75adad59fd962ab882af92b9c3801284b2a04fdcc96d8720214647de28a9ca1226d00c1556356835658a10609e5809a856679ee8ba0661965f90ea743d22df2c80f67827086d70a2f69b4a60f92d84e93243f4e0421cb276450b384bd24cb870d463d92e5659d42969776489697960a4b45db6021b5f0e9427ee15333443eb1ca611fe138eb0364e346e39721cd626f6c911b7b636fec8dbdb145ec8dcdb13b56490ee993e5b9d08dc638ea14341acda6fefc6ca11b5d8f344bcda93b35a7e6d49c9cd9125b3b4a787c6aac8769433726860123ba00e0e7a626cf7469b2836c6caa1a871588c70d9ed9626353257fdb07757d5041d9e736e1b13e1ccd7dae3f3eb21e4e9ef8110426a483105d3c10ad903d309338c2c429ba45a6817e92a6c11a9a2136f2ed4474f1403d9039c693ec236e91a9cf159ee4e863852d72687d7cb6f8c9d6a759a2cf15ac681612901b7d8c009467c8910a3168b20c51a04f9329f6000dede08189533be489c31c62f6c147dc22630fc42d1a89db3c3434b9af10db014dde22d74f1291370dde1829522409ce9128c0901b89f5c9c9d9d949d248942cf16162254f83f230e878bea027490c1189e14e2a633c2af5f80331e4e9cdeed460ac8127db49170d368bc4a913d56205c6624e6c036e0f4d1557b493a8ea8709c832f718bd85c9823c51d060c8bd0a23f7971869b01f672a82368c1134ca7d181905d0184e40d9438382ecc51772a53396819a259c4672374b7711841c71c88a0fc137d8a780912e3418b97b8cdcd12587987f40c5157a525c918fb345c3332da94d19144a1c197b8a6e1cc24dbdb6db6f384efba55ef6da2dd6709099e7e35313ebe85686230eedad8844c99e55d65a7b061157625a637615bb553a5bc64e947a79adec342f917934935996c52aaa2a204a7e5bc7d1d99ce4a2260a81c82f9c91e3b3500e5e68c38d2cdbb8f285b44a9c5c6bf5116333c680c46b73688e0be53033e56e846883f15d77e3dab822bfd0bb9103cd368a3a11271289449b4824125991481365a24a4553d42229baa1b6654207e4071d6e9e77d103c921e6781e628b5ef2f85e1f8c18b7c7c3f80324440b1720413daae2251039595681849decb595fdf0ad842c71b89a715483a89924c6a03b73fc0cdda89a0dba33b4430fc9d95f6237118c394630d59d7b97b7cf005914c5851bc626303dccb709721afc4f13458a18094216326d5ae9875570c3991cbb71634ce9aa473a6822884c7118794ec793a2c65092572231dce77005529ce77972b4aa51524ca49208273b59d2c7fd7a5493dddaa4cf5e633f7ae314c52e92f6793fd39ad9b5bf320dc3742e86d98f0edb9e5d510e8943db68cfaea3bde257f6118689bceedc76ab3d7b66394fdbba5006d469419f3c49e10923908c138acd913ba3fbd347bafd52dda62fd5da97ea6c33b99ab89ac8202020a01e69f1ea2d5a8cdb0f59150810d00fb3abcdbad61ad2b9571d2ac35dfb0764ee7b9cd231bafd0eeeda65745edab9cbd88f2ea3f3e2aee154cb8c6e71aa239008e20f21d16566ae08398434811c01a55ca6d2f6d7e8dbeb372ed53cc4d6a8d5de0f201a6e0cd6de8b42fd2048234392439b5d9f382f456688916232625e4cdadc948e3a9df4a13ce513ddfb42efbe7bee1b75cacb6c9145705c1966ad563a622cc678d0e18a2b73ce3963766b310f40329c6aaef4d36b4c4739c5a9c62e6edbba14ae54ea300ce5a6c34e2f95bafba5fa5d37a58d597d712a7b37c2a90c7bdfb68bbe947d77d1435f4a4777d15316efe82e4aedf01ebaf7d04cce707fb40c10a098a67c3af528dd63cb7b6c99be542d7da98a5d35f735a353981e427e4423576c47644457935db6b86e12ebdee91bef26f49d6cdf7491c6e99c50ece726d6a46f6c6868fa2674d15fba6f668ba88814e130fe28d13e9acf4b17d1377dd32de822fa265e30ff00a4def8a1e254db7881f911a45b9a8633591c25695347afe17eba992dd2364e1ee10d631a2c5bd97723d8ef5354f58c1b0dbbb6bee924ba88d73c681cb412d987fb4b7bf387c3b099968c4d9574d2431a049aaf466822cd32b3226da459ea90fbc596bc29822377244f8e92fbc956119c9c1d253cd927fbaa0d5da4735a49fb344eef74ac6fa66aaebc99ac4b4328994699151de5d34a1fe9a65166b78cc7498c931f4052020102fac1b4b5e8f735da4975db4aa34fd1b7bf441bd660573412e1687d709234362938397207f5d99f3eed29a3934a753b95eeddecb9d7681fdd6ddb4efabc3d75ff6a3761d8e81a9631c9a4bc64b5cba0dc3ebc2b199d17eaf73232ce7df4c9a07e9ff2c1745e28144ee9189d7baabffdde6ff81e86721998fb08cbe8bc4cdf2e83f2d139946f9fe9a3efd6c3d88f0ea37dbbbd8ed1ada69d3e9dfa6ef4d2b7d2a8f4eda4a75edb49df768c4678c7f6d14730edf7b1451a7da5ed2361ea1a5121d2a6484e698676e7822eaa507ab984d2f7982d324e23bd07572cd4c75dc6d73de543f9fd4cb75faa6ea72f551f4a652f7d4b94528a83524a51534529a594d24829a5946625d26b45a174ef122551fa55a9547abfd47587a19ca3b4eb3eee4b357e7ffb4c4bd3629a45667a1e37d248b7a4f8a08f4fa296b58bde94cefd553a87613a5dc2a97afcb2ef308c746b755cf7289f8c8ecc09a7babbe99321754f55d2b9a75ea47327e1780ea72ae621b62eaee941efd18be8b72e34a254f260a193fd05bd7c119a3c67509e4ea69db13c451867f4893e7724d752c293a74f9e9e2c325b66d054cdd9c4499e4524cedca1b8831a6806379c3bdbbc22a88a08d4134e27363c441e160fe6cedc993d789e3ca078eecc9daec1bd84b7c84ee835dc453b9ab5386c9a5463978753edfdb5ddc31b27c21268aa66ccf327679b2d5e1325b365eecc6edaed93adb933adbd77a7795a49fb28992df701dbc3f6699eed8bad0edd706defe6ce74622a993ff38a3c67153dd349c7162d21ea20a01db225c2b09956037dd3e666e27411ee9c4c9a25ec2e0228dfe91b9df4d7e824f0de843f55364e95bc992d323af332a48f2e53fabd7d0da574903efa8bf4d1c4afd22fbe53652f7bfec754d1897f78915e3a0fb1d50316838148b8f4b9a68a9ef48d7e1fc6d8c453c695b8321fa76af4f5177a5c0e1b4702149863e4d0b756a29d3ec66791a9768d6aef6f9f0f2dea6f13e1d40ea2cbe720c2b1c14dbe747fa16f5fe8338481b293af8b741291652ad14a9c14913c3eb14ce9c4892bf41d96390dd27358e2b492cdbbb36b1a4c20bbbbbbbbbbbbbbbbbb5b7677db8bea6f49109bab17128936d971b6a6e6f6a5e67cd7ddc3b0d0bbc33ce9356d7920b0d57bf7852ee2ecf62d13e14f76dc66cb4099306bad5ad4773f0d6e91ab3671e85e8e07d4a9cce0662fd246fa488ce6c5f5b1cae10766c255eede5de6ccda7de2d05ed4bb87e04d7409e52ac5edbcc3f4a13bfbf61cb2778dc3794e6a9f30dfbcac974344962e2282f5b58b7c98d9625bf5ffc47d1357ea43f76e441209e170fbdcae3d9cf7f09d1f6afbdceec3fca9c13aab7d2825f76aad943951552f71c87da806ebbb5a67d7dd13964570779106ebb57ac366a23613b9be999038f4ebb970c32ec2a2d22c757ece39a7176b6863c53ef44e280a1ac11f4184fc90427e48ef2fed7a892ef337c796ee947b188f9029f707704b8cce7d36b6157af7d508c16a856e635ba38732485ba0f7d116987d555f57347c61dc9adac18633d9e2983f32dcdac9195b52d1129ee8a1bf44384e5508a7ec438fadd7a74afb08a76ce83b460f61191d9911aeb161b5ba0b615bdc778444b8665e08dbea1e5b1e76bd7c39aae85b3542dc568c090863121610862203c25845a6bfb305e647e2e6a1b75fa4d69334190aee0bee15fd35bae8f222d14f18069b91f2a5d24f1806bb33a4df4b895ff7270c83cdd819aead10dd4dd3f8c1c34402cd1a1bdc340dd2110eae8ca3340b77da9d5e2a992d293ccdc2a45942a740d1a5e485c8f2d2a01514658a61d1448b08441095f2c596e94727c5ad398c403274e3be93e2663f82d450c4ed3cc369787dd83499c62f9a88c211b01006115f006ad1d3f0499b4f0060dc27cd92d96878862f5632dc50dad8d4e98cd3194e25ce8c2fa5fda92fa5e11f5ea9cf380fb13503c32e4e3db66678f8149ef1451a3ec3e50c34c8af741a3e199fe1433d86487fe9b8a21df5c9f8a44dabe869fb1c9146a8cffd2493d0279334d8207d0acae8933c9fb459c2f45367c212d0b27c04a81722486f634ec99ccc268925d08659ab06c15879f566d4dd4d273fa7cc892bf311cb9d06230f8e1fd927cbf679992371f0e28a892d8748225922f9b6772a49dcbef797e80244e6eef3f9cbd9261a8d46229168f4eddaa7e592c9f4fb1da5d26fe932285f4afef4d8227d2989533a4847f98efb139639fd1e05cbe8bc4ef85e86f4d8eab24fcbda2512890359d3aaf921448ca08efcf8ec072b7d2fae982fb644210dcb1c9b371ddc0a06d7b605940830b8f9b1a146534a299594de076aada596524a297e5981ac06a72b8c44c861244296407f3ed62c210ba6074ae99663cf12262cb511e7ce6740e858a64db46850cb6d93a495f4cee5473b3f3932269bcc16dba26f9be8a265fa1e22b2c03448b7e89698a9a27247f2cc16ee07faa2696a32c5400fc9b49443885e8a6bf397d2d1ede84ec23542bca5935d473bf7fbd40eeef7dc2fae21fdbef4e9a476e8db374ee9307bc2589283344e499be812caf47208a594def9d5a01bf64e2b992da553d3e99ba777327d67fa52db675aa52fb5e11f5ea59bce436c99300cc433ad9055c23a1d82a6aff4d100c38d2daed59deb1b7a243649a64b32bdc1992dd6de4b7f3fd27782d11830cdd243440cf490fb23b5904d1aa4177d92678786505bb48a9e760dcd939f2f32a5b28b4cfbd3b2b599f4c556e672b0b971dbc4151ddc8a5d158cdb8f65ddc5baa8427ff821c8cdf24066196872109bb3937e1fca941ea477c1348823bad8d3eb105950f4acc8928a200522c7a0adfb81c082e0e689551ae4bed1bd4f14fa40d64f4d5cee5c7d78999abfd3fb8bbbf7790fc374267e79bf1816a7273bcf4fcb327423a523f4d15f211ca76a845f9faaec33845753458f23aed07bdfa78abefb5834a54304b79eabe1a6f1a776bdc110c4f0931590e310ba004556a7bc46e965f6507ec391f6a53c8739eece7338dc3ecbc50064798e468a71acb8707978f4f07129b970c3f81379785cba1379e4b51c7ba0c8228777053702653f6861cd29d2826cf1405bf52f1d2324bac8cf8b20f6704d5304371408892b93155d4279561c2382084ed7d67329ae5fccb985161db1d08105686a9db3d67a591bcc76b3a19da15b35cb74e9af441c62bc2114b78883a4efc6e100b27c78036a0000c061cdf2214aeeb8d26f2d6e189b8820e24043ded0ef5f20b6b060a20b6509c30b510b395221776c61916608bfdc8f724e29e7074a1c06106f90af3400904359664ef89899899cac3bf372f6f8653bcc07913ae8e2bbb3ac3d309bc956cd128a38ac5bc5f2f7e44597ea925db6962d146e189b6020ba3421cbb3a6aa87980b441c5e30d1a5de47f2842c1044e6791f335319ca81c6184331c31ffdece7810dc6b366cbed97c4a1c61be2258f96e3c3d8042666d0c5568d7c8c57e9d67b6096090b2cbfb3de5213216ffb96e60909db431ac9939ab860e931a3138a438778c3fcc42c23b05e592fb86174d2835a7befa39356d95a71443de8bad8639ece5833ed2079c64fd3deefb2ac434a2badd92773d5c1cbd9bbfe428d6adaa95671d75f988591ce9a793674233e76ee7e16c3997bebf8b067e7b066cba4b392529a6913d3ec21a594ee44eb7516636a06a81c1f67a593d619638b26a594dad43cdb2c61bc8c93ce39e7ec8e0b5aa0546a418a49062d68215a21597e5a1a032dcc89238ef25352e9a493bdd560c5799b25c6497134d88f361a6c1afd15adda0fae0d2058b38cc2e0aa3cd3ea0ceaa4d32c3dddebd9d922a3aa3e461a7d5e5ec6b85e965846907a1bb789626b53cc0210714111d900446e28c71e17d428e17239f6204186fc9ba172d68344177296654560308a9ba98aa018ee28c71e247e4023dc9b630f1231247cae82044e0ebfaa2790544a2ae795d3e6d974d22a86541837a4d9569b9d5a39a2b2fa7431c618230ebf1c278d94ce397bce49bb637777ce6194360a3244dd21ae7cc7d67ec0dcb4717ee243b905503f8632a8e7082472d8345dd3437213c91da3a1a109a29b8432890872b408430c2ab0410f6243c8a1f4d9410e654c62d1319913ebe8a40540c8a28e4f72d7cc9c808830960862084bc08101931cca224608914389238fe48eabd9e24310648083253b4fb65085560b213721db196b42104f8c410b1e317e5a3d45ee5f9cece54682152a4600c1003f99126122532264c8f43674838239461c0c90b96ba11b5c4619b182fce5ed59e8c6c6534365468352e786a8972b9ab42afeb42a66ea04045a5d11c4a26955cc4ab069d56c41a1622d6092eded9b25a4ff28fd8cdb90e2db9843546e154185ac69a7a59f9c653c4520e9236ed8b19f1690868606081e1a1a3b5bfae7e7a68b4c234f27a42e58790576e1ca10e544e264c9495114f6ca1863dd682127e0450d4ce0042a827002103d4e94411453f8a6894c4f8375a2072567286591e98d0d5880844c8f0adda07726891b90a10c487821451450340f144540913296b08230327d4a8480154f38a2022504f2618cc1586b2d8ceb158b7fb960228d1a8df1f33dc4263e31c618e3a682ac09b3091fa8481423dcb630ae9cc748f923251631461a638cf81469157725c4adb13d7b26d1c959653fac92892b1fb1684a29a594524a69a59452dab19e2554fc4049cc162bd151634716d4fd4e344b89c32c561da02eaaa88c9260439e3f9805716b27133058b58cb3e7258e9f0ae8c5157a0a647f5149bce8327f85ae4f01f0b933f245e9e0cab7066ed7168951d4663c7d7170e5e365164839f6576badb931ea92727833496679f9cc8594e76d6c91a48c73e5a3ca6c25745165e200274f7ff28bbbcad6de3b43301603820f9cb83b477cea1463c874708489c9cf33450bace8eea69466b5577727c623d6c3478cbe6009cd6685c4b559ce9d314615f4102aa707c6cda1bd3f3589631aeca15dbb0f7d1e0dcec693ca39e59453ce4b1cf6889973db68f3e83135ec9a314d9afcc4f468c2c34793263051a820df0cd44595f06e3d37dec881916e8903144f1cb9993f11078e1c4bf29c3c730a2249b6b879d6501a2bf21444bac8da125411f2c431ada5d1e012f6abd962f1276d7b848a1f1b6a4829aba4524ada5236c1d3b35b543ac618e3a3c4df887059f3190d54661744e4f4e2662339311be4a9a494524a29b5d6524b29a594ca15488f70795e6ce0c841a9b53725c79e2558b841ac0875b670349e4e94c3a756d5c7305cfad076f40341672b23cfa3c41b503d327dd5e8274a5c49a963a044707e86700399be5a8066fb6dc3a7b8321fa2783ee4792e8227587e11661553d639e794724e29a7aca11b92ce1ccdd2b3e79c731291854b26aba856c898f4913e32267d60af4019963e71a51e003cb8dbedb577f64bedc09c2af53a890a52b841196a603ae11236b265bfa38629705a4038a086a755821aa6c069592c839aaa6aa7aad630058e9d42078253e5801a9e96c51f0d2f50997d5631a2a422d8cf6c1ad44595b0d264da302b1aa48ff93448e792e812338d5d6be9ec209a0eea201a1e164b071949b89904b61dce9b36d3264ecec4f521d833dbac26b38c7e4183324c812850a6ef9d69edbdf3872aa1c8d4c79b3eddf4e927dd12c21d9b35dd90da759ef499dc073355f4351c71a1d8bcf8dae17ed238b62a9c357348a6dd1de4136b42df1d343b1ac213e7389c4428928963278e3671b28933a5c8f491dad4bad32db932ff40e9251d22aae88bc834894b95c8f41587a84c73ba90e589a94d5ca19f5c1177d263267786120a9c4c3f71664b131155f447e4c4c0064ada2674a3876472c68c8a26964bdac6da7b51a89634422a8de905b4b9276eaf4212e85ca3cf3d71b5980b26ba94327d0f99e6ba7c0d5fdc17fbe1c81eb7e87dc3dd8e6e7b5703127706ad794c00bd10b6c686d5daf01755f442d8d60d2b35e4edadc4a79095b8c6d6c5c5f71e0d43bd6733e4a641b06d6e6ca88fe842730fd17ac81c92da214b452a71673645245132809be8c283835c95c835895cdb87c329b9e18dc3323aafd0b9cb78dfbe830b9df3bc875216bf369cb2dfb8879e7a710f9debcec9fa1a90b832f4e1a896082f727df7b54d6f32c8620964a3c4460229b119722381b61c1b4cd5b7cd270c44a14cf631f10a64693736077da4bb69cfee96565861450e591a62d20994859f5a4317eeaa4392c644972fd3f7882e2497d9b6e3fadb2605e9690d5f5c170ee2dae7a05fb1bacf292ffa4ccb1b8930ec7e34d36a199dd7fde832a48b2e733fa29d6374993f19882afa3801c0832b3ae9323342291da48bbee37ea44374d277884ec2309d1dd20a3be0e2062fa0a16991f09daa968e9c4662cb0be11da593b08c8e8ce8252c33ba5f6c75a21cd1a553de67b28747df2a4774e94c65e69eb85a5c999d3d9c23aed05beef3eea98b9b61f0ab22973ff50b72460e929d9c06737696d41c6a9454a0c9a1a4024dd67a882e33d3675996512a8f9044642a83c840f33386259206692c94e1304621d37758823c710f394d7bd29eb427fd86855bf151e04fd735721a046bf491adfa4e466a73844597104e7419407499b9be91ecc0a20bcd0258c2cd00b073654ed114c52eeedeeb87705fe37c9d33557513e3f60d8e146e289db48d932adae64b7243e9244ea18323b9737de7c4313861840b4b2801c517805a7d443a914e52605c0d770e1ec2adb48b066be3cc961cefa1eff01ec232ddb94e86bb875316df06e736c5d5b004c2d24983f5b80877c2d480c455d5fa6ef694dd54442986c595fa9211892ab5b6760521503124f4840655e8a20a08be0630dc50f2f0742f3ca28b448991690c43a6d18b4c1faf90e9398d4a9ed992b2b4ebcedd5afce2be61d8760ea7ec630bc6bdc3298b6d8b6e53dc8aa5120de322dc50eeb4f66672b51c2fb9d76aaa68c6e4d21c99dc28a5ec2ad2308fb8428f852b314824d2189dc2912194083231b129c1e0081f538e3d4b96a084114422a571efbdac958bd2d31a4b9ac8deb54a744055634224887e712bada75bf670db28addfb66b1bddb62daae86128add46a543bd55e1aa477f9c034cb8b521bcd224fa190020d6f96415759d1a55b624b8cac0b4417fa18558dbae25611df80d8e29aaa992cfbae596bad36f6d859638d353eca1669f871657ef20702ab42f0f9810abaa88223bacc56b7b8af1475b839f440a6b2d5511129ae4452a68d6f5cb93adc4c7f587b5936bae5029dbcaa32ca31267b8e115d648e4d5c54c84c0306c61583b9f75e18d72be67ac1b85e30ac55ccd535ba67ffd8c8227ee479d8c82e71ccd7a3c196f23c7ac4f9f81ecd127177ecd851bb7da77d3d627834d83c9a45daf77b584dc6f0e8d119c681590df6bf9706adb0c28a1cdd3de2974c91c3db3fb8a1cd194d271f35d20580501271ac001d820c60a045567d2ceadd034518446826788191eeeeeeeea63dbb9b3261c2c4a7079f263e384a39f62c319243544f7592592a8c484204303ca9f72ddd979d861abac171b53ed380c9cebd725cf6ec3e7030cdc2d5d75aeb39bc7d309febd560cd7e3badeb05f39cadb8706bbc6aed898248ceea911bdaf823457df7f282ef6db63be77d355a555970fbe14aae7498fdfe61766d2c2348bfaa24c8fd4af30441e719a748fb4bd355c7d16870fb3e55f516b5c44b532f6eb8ca3514325373ca6ccaf53d82748ba8de064eaebd53a35922cf929c5cebcf92277218bb90118b5c3fe9aa59263dcdec8c08223c8944c8f133a24bccf2973474a3b69438e8434dd3341ccda2e16e1c0dea109365966326571b3824061b6c9ea661a359ba098d1e2880624ddc7e54e95ad34515ce4991b8420fd420ed2f32d56692a964ee34487170399ce812caa3df67f473e77ead440060dc89a495a4e44b13091039464ae2cb934a1876b19d2aedb0119e69350e9be7c6fb911d7d8461a393704ccd9e58f2ccb42c0ebdfd3b676862dbc9eef9d9fbd15ff7a36b3a0a86dd9b1ae5a37b6bdff7afd1efe8250c5b8d7034dda27c2f5dfa8bf41289849a2aad278cf4128e5355fae88dbae0fb46dbb8e38a761957343b5517a859681660ec9b21779374644b2b99aaedfd2e64e38624301f7ae82fed63d5d1c073650ed10fc19138589c9e1e239de6cd39499ff6d117e7d612ef207d8465644aa35f1c5bb195ea1da4df7b1930dbefb8279d84654cf8e2b7ec6db9433bb774424316985802354849a49209e5f6d36442e9e429e5744af9c5d7963eba45f9bd2d7df457e9238cf28b47275de6a67c32a593a8a4ac1775414c95a2190120002000c314002020100c870362f18040226b931f14000d83aa4e6e4c1ac9d32888614e216308318400000001181111d2b40920ba942a8cbde81c14c3fdea0a82824b6abf724bd5fff5abba7e36cd081f42841a3e8d589954911c46deff43d4a417d352a5d0bf67278edc51fbabef31ae69679b2f1dd7c53718eb4ee3afb8976666a8e4a974971f14e7bfdf38fbde0ca8c6dc55ec5d42fa48ad8d2ecca73c5f6996e2f723d936103f66e275c9145969712f4612c0b4531b223d07758f54d2fe9d5b254d19c99c68c975561f8a97eb15c2e93bec2a5bbd2d9fac9874f4cbf027ae16f95b9dc32c4f565a0d2dde3ea347a88ea53a6e693af227ab8074de3b2db55046dc750c71d88229e79b6ba4ccb7bf918896d0459cd1e659e21344a9c2c948c1f580b88168fd34359736516b624f824f43d4cbceaa5157c429c9d4203232548654acd9f60cf4d5077d4f4e1249fd985da0b1dca5d7b1915f5fb94fc274ae5616a5b25e6d5170303271e1b05d68bf326a82f8d260bbf471cbaf7a35e9025707ca0043f5fb7ab67a840b8b3263ecc7a7ddcd4ca2c30387d8b46b73f237b8df39c7a7e7683cff2cbcbb2193eee4b46756a84d1c2b465119c01182ea14d3cd3412a64fa67c286e7f82c3ef05221cea799a616fb80a57c5270400df974dea340696b8046bfadbb678cce68e7c8d00faa8552057e02b5fb713560ae861610cf83d2e4e6bf81943810214c7fd0402c44a772326ea421f35d8bb1800341d473566097ecf7143f5b041522aba2f3f8bca1d41f35ad19a653f799877d78ea3a01b13047cb28f6a8a34ecaed45ef33d6bf160c45298e1c6ce52a533d086fe5afa71844882ac39f71a41367c99d302a77f10f59b4bd36df93aa7b2d1552dbc1232add751559116803536225b4b0a121dba218946e5136d0e835a344bf0ba5a7ce6e16d03068bdb3a852a69785b99a7949d32f24b00937601115dbf53e1e5f7a7287eae0f4532a0d76323d926d91bfc7010afc08de1a571209d26f999481337e3352418b104402042636689f359abcf1d93235f48d5872853dc3f7f3ad2bd331c7cdc15c9d26996c706b6d20785c8e81f1f91cf58df09529a6e46d78a0955eed735b51fd8ddbb65e6aae25de354b86647473697135a38ab943b7235353d654ecb64608e63cb11a83e88a9c62b920735e21b7831b5c2f39f289cfded3b3d641d8e781679335c8a9079e768c77461e814e3e0021cc9f25738c7225091cf68dcb2006da4adb010fc4f8c0546d0e76c93b1b3224ecc30c4ff942c4781139cc82ff2293312b3bf47f2aefc7eae52618ec3c09fc1d3df7203bc0c850d1039b68cbddcea7fb8c622d6b9d9c07aab8a1e94e21dbda163626c5317278f0f06ca4ba80220e22ca74839e36d422158da641399e40921df24305e7e82ed435c376bb999e24e1260d285b6a889a0d7228a209187a20001d53b9d38256b37eb7b20a24fc8d251f69ccb4dcf071ea9432bf5f31467bf588e446c442d0a3fcac9456c150de2f062ac465c443a4b26f569e763cc131502652ff50963634a3b7297ed7edca438a27f163d318a37d428c664bf4e2f501236ec7c18c1d4c97aae59be2c689146c1d4d0d90e5552f8216bc9e2b6de2c813ad9c1b7fc487a7c1bc86c4d279e2870207f616f06022e5fd97eca0a0f0c05f5271b51f3d21e15cfb147bb692a72ed7acd5988deeda8b571b821aa63329dc7676b69b753e1e288a4263bd9221bbd1271b345f8a62c23ca5c946d31b0ac65ee7c057af86d8e3ae749202f030baa6ca2cb060cebcdb88593f0733d995bed518ad7b36225a57ae709c23847026329eceed3d0a0b680e29db470a3ac7ce5b80c0d368bc1a3993d949e9a83e1a4319b740b02e58ad6a0820ab7a017adb3d06d00c8ba74dc4ea33e52ebd244af9152de63303c1e2b38e0b66d317bf611b9fb039518c189f8db5f6a5502e2c9210746fba55065f4631a725041fed2511b0da29ec8373c15a5efa4bb7eba86802f5d5a1dc854bae79daf08254c837fc2b36db3ebe78d09d7950dfbe9a5405010ea4de4aa0807a1340e88284e10cb8d90a2f13935479c3f43df595b3440f2889b7f35d87b3356e3b162afe6b4927ec0ebe42392ec9fb999b4fcc2588912d430e4558d174f54bb361c4ff8ef4b163ec0e790f9dff1ed7c29cf8bc9e64f572ff7dd22eca03f609bab34fe18a4762c25593cd178674ea7b502874384956254f085b83fb3e2d7f7e318f08a01cf8b8c49f495ee913b6101418c659b00c640104ede37e2a3a4024733529d721d4003b5e5b786c29a7388450b3a641ac258ad9091b9bb5cff234c89b72c886b043c21cbddb5c6102d24264426ba7fbe73faf96e1dd2e69c50fcb430792c64a0e3ff061b0439e4c8a88794aaf6d299ee70f8fb3a79c779936d6fafa153a11be724991b0069a8fa52ecf46709a6c4435665917ec884383125606c7775c21a035430de4293ac57cbbda062ccfc23f546c2be6049383bd3f83964a411475d24fbd8c9e43a6712183b120f5009fb080eba7d0b0aa3ac2149a8229f3091a94a820c14219ed5f5914b32bf934fb7ac5f128218b94c128b2b691350239d536e907b74ac039f3f2e69dc721927668118607c1b68fc0ab47f85e79026b2ce30f14c29834ec3f14ce5fbe05d0f1e4e60c1d9a1ee42a79c41d659f2f24b0e1f82edfd56bbe680cacf94694a1760c3144333d55bc7f4d9529d5f8489e2350ef480b787f865293c5152fd9ef71c432a358a0829676949b0fbacc62b9b36f623f03ddffb1e3e91531ec8c128d54407a1e2b82880c465afa49d2fbf0fdf099a8b6807bb2f881ee22caa658f6ac1f1616f133d5ca35d2193abb8d4baac97bd1c4cfc3128866f8eafb35ca1ee82f2b005b8575bea9f27ec427885d50214f1fe6277d71b09b07130556a2cabf00ed30c9c20d0dd9fac0dd6696baf139e6c07ca531e1c8c6e1a3084b0e6e0d50ae7275a2e14140c8696734ef8fc243ed3bb27ec253e2366b99804ce1f90b094e00a5826500b1ec278068d9c5bc017c71c307331f2b332ecbea224bdaa14fa6357ab8b300d2dc81d97144029a9c1f87efd592fe367c51b3836c8ebc51fe6b8b85c123e6f92a9fd7f0dd62bcefe2b0f00a51c75250851df3e74ba4e678e5c49d88dacea588556380fce909cff8039b82d811e0585eeffc727834bd8b8dc6601573e8fbbfba51489b97f8a12d8fe4849761f6578df5ffb8cc4a5ec3a3cde9060385227f28411e8b282fb124558a600deed95789138c922928e4de8c03a82eff7fd4d05dc0501e400388efbd2b91920156622d7f214b60721ff5e1fd3f75e121ad75f76fef59fe249d860b505865d44e486c6114580aa86ab911ce832c155ddd186b5ba2279c00a40c89261e14c926b4490364d17d1559fdb6f79d73482d6b400d73fb05aa973a3fa096317964ade506c46db2aa910f91ae70267c83d62a701817aac49d2d8db3bab47cc7b6198bc75a327cfff3695f557dabd673448d45115a151138d2d23dc5a31788916aaf88f3ca1bfcfb1240e0c32ad0eb13dc1f5b6342a3090695c1a87c735bab6f26a0af94b57b5480d4c9b4e91935899a82c662457cf91a7616ff42d541ba3ea0d46724a4729dbe6f5a063af1fcd6708ca048152fdab2efc64e85f9c2f93054524e7a21a743ea9824712d23921a4a1302109b4783609a22ef39407ad727ce30a02ef619ce69d4886502ea8703104510f52d1d961f642d0212b0bc87d6833b8e34fb6f4cdd55aae01a63fa305fe141d7a6b20541e3054642392c0dea3984fb6f7b3a8deb36c1111eba7dfcddbee491bf7dcf1fb590aeab2716cd9993af260eabdf21e3323295a34bf5f9583f6ad63030ccc6e32a2025c429ae047640390374f01822fa23c72ce5a9dfbe386498e706069f4895cc518b49f9069d39b2587875200bdbc830372e4003d57ac6448bab4402224bce39b1d2268de6cfa0e1bef3232dcc66810fa447280227ba8c81694e1b55a060295b66735bec3b69929d90e8d51db54bfaf4008ea06e5f7eec487dcadd53cb657495bff8dbe03d74bad404af68385497799faba6eb2207b6a943a63b3f03c68c80f3275bc1a0bd332ad79416df200bbce6ec7c8551de0ba6939d0f932a171ce482f1de0fb926b94ef5685febe1610cc3edb4aff83805153de8ea2e9f9e5e332a9967feddbc29c5a00e17b75b7d59b2a6d53c249730a749b6540c870f18388f102b085e44bdb44889c2d956a70360395465bd0e34b40ab5c009f2896278258f705fd28c2f2596a79c10de9d6657336b44f658b390e4e4149bb3682dddd2b2fb48dab1750726bbe1a059bc60c0831589ac33f750ad1beca08f5cfcf0a407dd8c09b28fdd3d9ec31fbd5ab1fc54978bcf9e6a857bd794c7bdbb3a4e9298d07ec1c855ad8030b0520d42df0ab770317a3235f78d1281622604401195becbee5143bada08130deea7f2301e3b6172d7a8715e223bcf31645b27e0e558e5e5ebe610ec4a8b73ee667fba15728dd0e2234009a7ff9e6b96579a3d04fc84dbb5a277181fb379315dd0dd7d820afeb4cc69668a8690d493f3a54604f06fcb73e0dafa07befe56198d76bc7e67d47da62698b0f3b1047386e110e2b5d84da07ce4930f25bcd6ec27fb98c2c7e52f5a4e3922fcd05587c3fcf4649449b8fea7cd947df28f9da4f6506fafd229008bdcb101ff1ad4e0493eff08cf720dceb6852e5d40827881130e003c2ad9e54f15794eabae60b5a7d29075d40c7e9c264d7c5c426b00719a1ac3369c1fa29f1c89d192e1304542709ebfd8a15e724912936aa3cbb48ade181c70ae8b4635c3d604a9686e8432bfe909f6365c1ba7ca85fd3c424f9ccd91556dac0a137022b57cbd5615a189f8c4ac403204bbd61ddc4fe568aa1b24eee6676d8e1a50c4e4d3aed42f21d0b0aa73a806cfd3561657979d4c335be15f0579ef847107b0562dc126b454e39443267a441722ac106583e7e06dfbf9704290b659e9e11ec901f31b00dfaf26e5edec1341ee2bb3260a1872ae7b471307bb97c112be0bafc848de2b7e7860d62e517b6f7516b2c5f26493d6f7cc9f2de67c93e44214cb72c04d46277184c4fa5a31bce681153484768618cd68e147553953517ba55c0ea5867a4088e2d480d31ae67c9b6a3c6d6bc9e21a32c85d89a9d4733ec97e5390942daec9e6d357b2609a6c58f20a76b7e0040ca48d0bc2fcea5132e9ece05fbe4669e275c5cdc02de7398513c79190f3ea0d16410e7bec85e7b055beeec354deb090617b64c72f4075e5ce16e590b599ad61cd043b603f9085be0d6af7d586e7359016dfcf26e5e18abfc99b32e4915346d9b76309d25ee98f7da743adce963b25b7eabf7f976cbd9d1af9f2f46a287c8cb79ec0ea37861a60838c00d2ec566bccd4e812f0842d4db134ed77359df06959eceac428239e854052fdd86d14941c71bb9adc208551f39e3329769104e0c8eb24742735b828c2e2247d8d6453ebb881ae45ca8887521833b545886039302b9ac39ebae366307a4d9d9d3f4f40276d073282e3c51e5f510da1cfdeb6ceb0f45bdb38a64320810a8efd61e0527d6290adceecbd775d47ea2b8f51cf6d06b68516b2dc305f11b656f6c3b6d8da64d4c7741a2a6c056cb64713abf8bdccb655be03cf8292f271ec0e8a468391b4dbf66d975a8005c418b8a56bca370a448b59aa1b37294616ca10c59fc130d7187520dc90a6b6864d2b7432d4ac0e3a7c87853c8dacb90c9630d7c2272fa499214ba803da96f7aba412410cca108698f74e0b2513748833427eca73a3e79ca3c54c6451611367efabcdd872406d8d3b28abbd91ab70c0ce1542f4464e957a59993dbeb2518961ee07201170b1049a010c407c01d9643bc42948e676f14b6271a416e05cb140832cd7601a2906a16876605ddbf5228a00b78efaab6662889966d902bfa16467ca55db9c9dcbbf43fcf4ed74d7bad523288f494f1c3dab4e2422c0ac4fbfc7e7d8cfaed6f97981546c5171023e460c447414c1bb1fe1807374da41f78d4240c54885655ce55fd4429484da394f1f7949b0239512fd02c2ba92398bc4d7eefdd94fdcd14532eec727be4e5c7bad11381c7c2544a7d9b67a5826f11b1e4776aae6eb636c2d0bf32ea995bdfb803ffd8a7a565e23587cd66c3986ae179f6b129399f286aad7e4ce26efce8532c01e85850ad58f609eb6370a7dce18b8018c85824f37e545b95df1b7e04638c11546c006ba5c904212833b48c0caac3a21e367370cc100df488d20e221d1c071e06c16c28682b7a877475b6e123dba7c4420809d3747b653928c2fbb55a8614be89a5aec4e20b36b0430939971975ba923d92c54a8fe760f14b5d9e78f6141234b6507fa84b06c87f47c7939de2dfcbe6f910c3d056fa4977b51eee5781c4c536ab4af17fd623b417e3be4c567f18c29353d42534ab4a460b55be0bb5eb85295b16697499d165de1d5f3e402d51563c83a3e25503ecc45343253ef75d71f358bdae4fc65dbd7397c8937530371b34c803d3ce74b464840b595b84ca08671a610281fe8c1b3db9ff89387926d21acaea6967ca3174cadae1dd6585dfb606ab396b2827e0a57195524ff9a1326d427964a27fa8d3dbfbff44e550fac481ee644e1d89552af309c9efe2cf841d2126fee500d5802f22b60411c899e14b0ff836ccb85429ca8a2050d4d623e691184941ee4afa9e29ef8b865110d6861c4e2f305d9a8351979f4ebbbe759ee0cc9bb1637a78285897fd321ecb937ef08cac77666e5caaab72e2355b11416ae100b304799f5d416e59066edc0b833c01a3410c1b9f04959a1b62e320316a9268d7f1774a215ff21b62ef2896b8353335a32903abd275760197aaba2c6d8918d24424eb0f492315abf235c01b1a29a8b713d121f9f32e0d48a952c2882191fd836e085cceac673cad2f847ff40292e3c12f2764d29c4b2931f2c2b489abacd2b32aff72b1b0c58d242fff95c88cc99bd122d147f6d6a270403c0d8569dfe492478fb051fdfd5564286104b78c1c9155c048000d26276925961efdd6b8feae919f1a5de380f474007b4f9149ebe8bb5e229e4fea7ca54e11efda06d5ae5b930406e0d8a94c16bed42ce73e6d217c6bb5fda479247be06b8fabec7b99954952feab89ca34eccc4a3039c527955fd78f12c85f3069a7758287c57c27a9651e4304e07e4acafc3f6b56152fb7f5d0484656d3c378f5ab38346e70735de0461db6ed4d491f9af1699a0cf3dcf6aafbc3b7a77a053433abc351a6af39d59fcba3aea431b19d7571ea22dfb11c71321ace3b8c37fe23c874766ff44a4320a75d54130d80ec7621df0219b8a8d3e116f5e1d0e85a38f0a84c15dd40af3ed4796a5291dcdf7b012b89c528a348a06b5f9db31b4e18d345f36ded3be82a879744cce0847b0e13fba15c013ae892f0ee8c7c6840c1a5ac7318de809d9c2ea43f2e3a5136e8858f82bafc7b14ad066120226191be7ea480658ed5103c85d3224b110359236e647d5163f2e2f9603a6851bd367e395c84b4898196faabf63eebf1e4314493bd14f09eee37c4f42b4b86de4afae186f5b5d75fa5941dcb0ed5afd66c7f4070ab0b4585632d3c0e8de767d1865ea03bf397cd50e4fe0d7e0203a00b656f2003d3f6f591ec2ea1c6ac7542a567e39c366ef52ae892915081e360014adf7a594227c8f4bb03c4371343f4e24ec34bc23ec80cf1e7db1534186dbada216d7b9cce945e87614222eca43e0e33304c05939e44f5754e7ede6ad2f6268fc142619351163e7b34778a5c7807ead0695d07113e7a11c176a94021c3ed9d0cef3e4b274ae293aed5d9d3e9cfc45e35f5dd2d4b4ae761229b05f8c81db99330c207131b319a13d527980cbc82cb0704fc465ea75f02c4cf9bcb9c320a9721e27a4e9c333133737412cf8af2b3692334d3c4a0fa6b0d6eec86f69c1f88582c6d7645c9cbec53a8a47658ec3e1de06c9cf5fa67e4031e6db6895b6ae9315f67339fa290511850ffdf64f1dd5ed66c08cfd53fecfda9b66c7b49d9fa242a835a9da124ac62891871c1fa5be35e0ad9eb2b27ce3cd42e767ed91e4486bba2ce72df8c112bdd0b79b3562ee5f531d0f01a97f64238ea8b28f054fe4f37b6ae859f56d17acea721d3b7eac6f3d7a85f2e8930bf1856b8ac75cde72eadbed59d53f717cb8cb54c56589f60c995801e94391fc4724e35f0551e3b301f17e40584ee415819b3b7fc12d92f3429c3b54079acc852647a023279c2d036a6e29d480618a9df651e150166a137efcc2e309c3f72b8bb590cad436477909f0b8b0d94b9e6c7e1d682d248d67403c55ce1ca82ce8207b626e4ba6d737a16c7612a61e93984dd6c28a46f281fb34122bcb7c248ae39207674ae287a1f79387df61395cb6679443e482493926bf10290ff5666ae014d6b82b6bf139ddd89a9e34460640b3caf0b70485f4d37ab6ab6362429ad0c8658b76b900602ae8ccad4bfa3582bc323ae946d1b75e44d88ca86d0e5f637a6150882ae0f6aae405ea12d6df77ac663e28ab6e63ac9d3b8082c951d2f83df4550f2e645d4892498c52a3dc023f4bbc5500a3dd810a382966ea97d0697cb4fbb338a4f0b078f367ba4938059f5ed83a54f39788bb8404c35309bb49943d730237444bcd1467f268d8c83c6356bd751820ee3c9c576d3e538d5a87d63e0fa4d35ec0ecc6c08976f9c768e593d7229d4f3e4670f7063698fd873ed5712c0bbc2fcb87a08c432d3523798cf0119d51270496dd5e2fe3e56132962dee12d2c2f3c8f41f8fb29fc13975fe24ac35d696fc52bde035f4134584a005936c80a4102fb56d517fbca6b689da0b0cbe0652848599c0737597fbae03741fb0bd756fd40ae849d8c15ebbdb1adcfce40091323cd7f0a2d06f0fcba69a35c0db345b3170d6d03b7cccaec060b39402f8ee244c649b2523db53c25b6b4ce6512e08d2dec88ec22b203a2901ce5f433f6748b6aff81085733a1174ec4a3c55219f0962c00d8dca47fb4953f768e0a7b45f2147cbb8fd0871a2f180d82af43d00b3cb0b88fd63ec21e89e1e2221d69c7bf8a01cac477750127a4d4e99ad4c9bb999ef51e493b8c8e39c305fa0b86284dcf6c1e714c40330316d555eddba7674a83d2bbc8762a22f69d4ae2ff64381a2fe68a6a2e07871d4f27a9df04aff1ee0ba714262ccd301c7c3e4f18ec3e05a0f9d49c0bf6bb287fc7611aaacda61533305e7b488d4e03701f8d26632847e523797198d2ee4c8c8865cf1876e9b2bcfc1cdfbe6271a231922616a2aaf372894f6f30d28b065e3274f89faf3e02a24b0a541f3d5995d509511d0eba57da48e857f40882b2a65683b1a48d59bcd823f34094b754365c2db67dee8857b277a206a21f42de6ed2c55fd11ffd8d665114c780d8b7de7e685dc7f6fb36b05db99afb5fb745d74984524677c6babd41bc08b1228a2288dbc19af134abd271d76de955e0c5f4aee350a12487ed1013310a72ae9bc44af61a650d1a8b3f006044ae90930423b0b45a9d669502b90bdb098f9b4ecc5c8622e7429a0dbcef72b0cf84c865a8e938138af67523985787f0ab9b97da16f9cc888beafe8823e0a02b8b200756d81ee15adeabfc0eab9e6a3180a6f7c7bb1dacc65de75bd082d13b1b5fd055c487c6f1bc423ece879ee7f87348cecb87b6a8513032635bd29017deb751c9e08582117b880f6c355db47ad76de14a141da2bb894063cadd5c70520ba0015b286f61eabed0d88ded949251b40a1a23b151a1877329e86cdbdb1310936f05cb4550e069192a93af62573c4b708ab221d94210b874e5825bd6b2c52e78e8644773420f3759c654ba1e5d258ade0963191b5a1dd2a1ddaca4669666deb77496021ed3a14f0ac93b0c7babdc59789ae8a7280149a8d38d1ff581fa38b94c16ee8654ca1b6507a9a2b8b0bbb06b58d49e109e2d9019766926667e3fb0c2c2a41e783f7e9eff953e86750555e0d24aec890e9951583924fe18987eb161cdb84b13161c8d41caadf7e0bd6f64ee815053ccc00bc967c84584fcdbe49890a747cb647d014d48deeaa024afde198581960957923c48d94b32e93af7f9eecf7c635c68c2742cec1c880f20207dde5f8ad586e55d6f0987ee5b2519fe8cef1379f8dce8b39a42e38889f7e1e4c6aa3066b780d7ef584c3fba1b54cb268ac10ea5db8aaf13f1ee2136dd87325541233fbee10f25b3d1515bfbf8ce2e3c66bb0867ff1ae219f8805caad54b0a3191ed46f7e61d18020c3c9d8f475d077e188218459fd796c46b7598899898b03a0464140a2154bb93cea55f60d6d6742781cb0494b0954483360abf280d7460b52d3fe84d95b60ac9ed9b977cd6b65cc9cfbe7b08dbfe3af58c2e8d8b847271e0bc4542c1989982a0a22f5453e0852eb76d6eb362ffe82e709133431e846acb9184fb4f44918fe97389528384c01f81eefc1a1fb7157b3b864a2007111d5fb554a6d0ff87097af4ccca2db136b7e23f79d6aaad245ff998800442ab84a424ba24ff38599655418db7c07da9376e5a474a9c7d81668f8acdbe6e17c3f49035e01954d57538a98ad3667d2b332916cedefb47e5202d76441e6acdef3c748ad0fcc267698327f32b8331bf3234590be98077ff14537119c4f2dcc7f64c25f2f76bb187dbcba8e5a227b4f41d43a56b6449279adeca752e449362c7d0818fd99a4baa620e7e057426b15b43daf1832c4219959dbc6534b6667d13312c8ab12d38f7f5ea7f8b69498eb8abaf8a716db958eb8706451414f956e505758890ccc4bcdb44e3ae04d1d2a21603c53367a9e6eb009d4171bba4779cba21816fe0ce2c371dbb09f018c59ca15813e0a04119475bab3c1f0d3ca79884a1ba3d8f264367db973ca699a26bba920aeffb8c98d4acd309a8eafc04f52a4f5d4ce1adda7e0dc7d89e759e2ff479463e34a5676f56f796f6ad4e5ee454660dd642cfb5a874d4886a402ce029744bcefe5f81bedb06b1dd5b1f63d6da35bf22636efe884ae42582bf46baf6e0592385a31c853d564e89ab538bc12ed56fceb7dfb3e037b93d70ae7c315edab48a9c561ccd9dc91955660d371eef1548c88cdb8acb9fbe5ddc4cf7e4f461c335e0f8a3e8f2bfa8e3ce1227b17cbb943fec9e811eb3be16b4bb442d3cacf352dc9feb05c0ee0b3f044dca5477f3994cd25088209bf36fdd82424d8d695d0c5a722aee9ecf28c352958b2882a901e93c4c10ca49e7973e17f96fd56b99efa37dc15a2f45ced1cd63b972b18c1f1d7a7fb6dbe4e483b91a6d842d8e141f3bba1277310b41acee62472f184d24aa2e6423a3943c05e408a20231099174373c763e690950b0ea6e31075d414818c4536aca58f98f5ebebe3488b4a1ffd1e023ea9df786666643d2a4b729f0060eb53886182a9f03435cd75f318c2549a79366761dddd71ddc21d5ff01aec0c9a33d4fb649f99759a393764df9c3db4119e8d73b53dac303a8ffa3f92936ca1f4abf8d0629837090a863d85775d3d4e26e99dcf03cca4c9cf32c9d98154e391b6c5ce72f28e912181ee7fda8046181f69caa360506644df207ee55e8a17223bcdb3719518dcbee8796a0a5ca43003a757a841e88563cd4b72eec2c62f2b081a60213e25b810ea0fcea7dad26861dd7ed56c98767fb7b5a5a6aee612b843a9930bfe15bbf25b6c2aecb170361c67faf22a5066ac01b1c4c9f416b13f4a1081604bd2054f9e46363b67866577d0c621046f7188ce6397ac60844e8d8a818ef1fa09e851958bb252381b84afc7f382fbfb9d7c857fcd79fc8acfaa2f78f88ce3899e0cf7146b5011880d1a1946103572b4eb3092ecc77c4d744d03229a4be0599007439da4ef36fc69c5f59b229a46e13c616064829997155caf03bb0fc07dcefba8477adce1d668edabd14a708fa5a496950148901429609dbafafe7d6ee9432376415a9d4ccdeb11446da6a17bfc3015c6c449a0c9a371fbcb7b24c9d75757ffdfca3f0c043d91124c776ec3b5083ff0cf93448ac917acd9ebb2bf44aa4f5d78b138c50cea407ea53887ba4c162dee9ad00aa1e7ae861a8219aedcade0f05b14374f8d12cc7562a4058c2620308902948dfe6099b0d6ab962ca116d6d97668413fe562fd1a6308ac0f25e8034d8366781757775c88417709273b14e5775f4aaae8d7592b848a2eea62cfbfba52884742dff83cb50b1ce52b31c2896077430c9b3e633e9784b6876e32d065276df0451a1d32c0cc6fbd87e75b93049d37e6caa0fc6ca82250742229863dc0c0dd58ea17249bd06bf7856b3e28d0f5959e9dfd0de7552a892821c1e9cebd1b54aae2bd5adcc79d6937997a086f2ac7e8d1491a27ecfa0e6bddf443b797689d04d576469934cd21fc9f80e65b1c8100fbf423a0a1ddf908dd24ff14bf8f05b9fc8506e4b8a90be677da9560c2afc83de491cc4b97f94403abac0f0a194fe64be626019bf8426dca240383b6b8b7f8ee2d330d5efad6939ccd898bd0d0d8e0142389737439d98512164a7c1a6882194e72292abb10cb6590c0ca258fc4eb274ea4e3c342bf379927aa4477b962f150394ea6832831c8fccf69a5d8a0b32128e18a4344f1551cb06d2950222ba23b1c8c89655dddab670f4846308349cb9c0232deec73a8f44e26fe46f03eb45e5818b704bc967528a9e3620ab508089ca2b9588d23a9b664742892c6927e758dc1223fd388b65fae4fb55b416935de3d050b654918b8e9f78d91b49b994a69c179e1a2d80789b4e1a70843111a8d3bb7350fcba57d154838bbc1dea4f9159001b5bdc71f35a629f69448d2650fa0032919423aa1ca27b606a6fb4256346dbd0a60e3384f5bc9e1f177a4205e8be892ca8f6919246029471131f4efa04f69f26a3d23a3ca07a57a993acda25b63457e1254400a61d9e4e5cdfc13199bc835bd22a848d6fe05801ae7b8fa8a10b30bb7d9c8e4cb583e8aa5ae4830c9f14b933354dee709f909c1df1c744e6124d06a7708e5b1dee475f0a2c06719bdddbc88e4e43b50733fb956e4033bddba17c3c2b66047f32f304e54f56db386d4a0d10e52f93b8440a164719928625ef4a683e9666a56b0320717c0f9f14fb5edf9fce986b23cff84a2aee5271ce5f9798bafa4fe6f6c7e7812711692e83363c017b946c0f91d6f8be40eacf2fb827bae3d54d6d4f9387b8e92f3f82a1392b25f22f64174a19e14149a533725048908b6292200567ed9fe2010c1a7ea3347727ee569d472d03e260065991e26af101c251ba23a7d7480643295fb203d5b7a3b3e6e25af36a9c47ad7b9c38498e771dcfcf31574b8390c9191075abab3f89d01bd559effae6fd0c75e488cfc6379b8bb0dd3be1f5374c87758c011ab5b0313a0b34a32375b190eaea9760f336c664952e07beabfc838b9b8a81a5c420bc1d2856a83b67e6ec93c1b6258aaeca26b04145cf218cefd7b613aa83e3b9cdeee9fb3f1f228bbd6ae65f874212705c407752a0b1d740caae552627de4ea66e87360a180e31827b7c9fb0835d9c7ab168706ff865dfc7e1deb6e85d1a544baf53944d11fa0f6cf35c6355e202dbcecc76c52f3d188a43db6cc9dd73a4f8469a824b196f62124d32370f5a85ea45c14abfd037b752383cb7c5ce4725f42251b4ddcef8b6ad07e5449de1e58cb0fe500f6251ca865ad36c08340110ece19144bab4cece6964cfe2cc3bd1d29d1f193260ce92e734034630f2a35dc34103de3a7dd0e8b8c03d6edd8dd277ab4a7785710528ba32afe6a9225ae4c188bc5ebd50634fba8cc59ea7aaf3c7d0d052325daa473d2871e2f3161fccc44876a1c79595f6a46366296ed1b0c1cc49bd5cab8e05f59855139b602dbb0e1825889b2315de3594e6883d720db5558ab61b58efb142ae4c35b26a14972fd034ce7a9c831207f2a17aafe4de3d0bd1dce621babaed2fc8e1f561660b9626a7902a80d0ea0867508c9c0236c2900dd38d595b3d1c5e5d787b3dd92a23edec5716aa1e56d28f1987b215ce02664fa6b212ac3c842ebb7c579b3881c61ebebced8a06acdacb30ccdaaeda6ea8dcf57cbeff0eaa54252f18709be6c46358d9656054aebb848c7cd1b53223f36e94a5262215b004176f6963f05102b736dde71a69ffc688758c0961515525228552c4f630ec468399d151891721bb00a3a1ab5a92fd29ba2f3f465784c904adae0b00190ca8b0ff5cf003e63c258a2dde978f139c25b7f591c064e854521d02336fa3ed5953f9e6b66795c1a3c58affa386f8e752d4d57b8b57a88a4bbd31a297bc82da93217c67f803278cc3b8c6618154445edadf6e512e79dddfd81383ccebeadbe3ecf42e7ad28a18880963254c9e661bff80a7c84f7d7c53aa7fe71afb3c5004ce2e37ce595e88c642599718affcbf64c904780f035931b5c98985693ff9c6403a2b10be6750e6eebc11867ea182f0134ab5bb5c79a5fb7f527750958cb0e08229b4377591d30f91d1a534ff23e87f5c2054c11a57ba12fa3c2b976c00c1346185f5ce14de8ae218266e45ddddf98344364f4f214178f6954bcee6855bd3296b610e0fb69c8bd219aad27987fa31879e4743006e5e0f92d6bd15cf2f4b95fa0d8b7218ab21647b0bb1c0b83fd16ef26d9148d389bb09c5963cf9c9d4f6f539d827fa269cbe81c815c4f0752eb32d86352ba3e3e0427b78244d1e360c5504f830394b40948ccc8686a6578759b6991a4df92984876467262e6636642cd55877420344bc6569715e12071e692a7cb22711d90feb500f83d3df3f1c32f2183ef4fae32de6e8afdcef1facf6bbd531703d0a2a8838a503aabeb506bbf9fb87e2836082e1c9c55b0e5c082c0005b62a7778580858ac9d4d82171888b2c605d24b137281cc4556faf70d786dcbd9ecb11836b15e5fd7ea078399ddbeea4c4394fce1c704fde18a3588ba080cec67ea2bc3312cacb2a500d60b8e54ef07e95b3ef689ededb284e8c9c30b587443c77a1628cb3340ca7796768fafdd32442bc2b182d8c30fa54967c312894179aeb37e6002bf92f76af8adf5c12a0a43a7b419a178ca582d4e7190fa735411f01b8246ddc10d866916bee3dc55b59e58facc2713350bdeec614c937012efd402a99c5ca83b1c801d879c456ec4ae621d58c01d65680e52329ad87700789000a5c802ec7734bd7048b6b03fa009bc98e4a7eb2c34d3bd5b811042424680f5bc6c257cc32726f24995f53cd1ff4c746631f090e60892fa7b9e7d1f0d683121e2fa89b09d8594974064b33d11a9ea517200b2d570355eeb9a39f8cae722f970053879cba5ffb90c551e72adaebbdd15fefdfa0f3cf4e04ef9b67f9fab96bfb5d72d6cbf196fa98f1ee1e878ffa145c9bc4aa3e411954494aff8b80bdd7049fb3c814ec70dbdb414c34db693d6f5787592a0f1ebf7c1b89bfcb3fff936d7d7cc07326a52a57206c33ab8571101a11ce9245a6196e1e5f922edd50a3ad03b8e36fcc21667453c4399b0f4ee39f15550985c911cce8d26cf7ab1b815e7216a8e96b8a812462207f8a51e9a54923138a0ac220bd00bb11ef91e1e79f53ac8a70783e7a6998680e36000d002d53ba97fd51a09774f18d9a4220ce50c39fe527ae1024f2f285848ae29af638a1f5eb056305609f5e15cabd7b90f15442765684a010cfe570db9ba66de5ada9bdcc9456a3957bfb48d7f8051ab2234dcce2b8b01ae557c182a8e9500d4867951925f01016cd52046d8325f45add9128200fcd952cbc13230d6ef0563b0bdaafc71ae3c4f3f6e68d5b873f164fa1b479ae98428bb1e3cc034dc658b8ea4980e0fdf7adf91d61992d50a36597734fece06d591028ac47ccc25d7d988101e6b5cb656c34e362af5094e92c20e0d1f3c36dbe2f8cfa0a84372210ba20357e76f18e47e3e4006f1cfa400d087a61fcd2f101b11befeaafdb2994576a78f75839b6a69174175af485abc0e0a6dd81e12377ef180101d3f6d21c36eb58ba64c26f12a1d97c086ca5846a36eb3462bf35697fffc9bc1b97d0266f85c8118dc6594cf4ca82fb8405433583610c50642728ab66df25b1bb94c5efba828b7df6eeaec9530cc9ea33106e18a71790e0e4e531247379efde8299d39980e50a9176e18ce6aa82ff11b5c7f1694ea16294f737bcff2a0d9b4b26bd39db9d4961a32210e5321c667a95be90acac699a906accaf29ef36762e878789970f13fe6160db446ad1d868fc6857e68ee66d6d085b863ed18846e55e501a8aed72dcf5f08bb7ff70b48e50a144e7a037d214043015c69b0f18f16fdb7f9166dfb55a68480567cadb4fb21fe84559bf57e35285b43e3aeb1e5ac4feee183e701a834a55b0cd4b2f61eb9f37ca8a9e1efdc97921304be01b18e1d8a2d50f2c7978d090590f5c8ad4ace6b78b551e65cea365c8e352c5b4d6ae57c5a32e6eb632426b6e6cfb290198222b29705b4219c632e95124ea3d35f2925e45358733ba4ad75a7490ee8e77f8c623bdd3ba5a7c054e922fb8269e684ae154ecc668738bafe1a0188dd3beda462d254bccbb8187a9cc0af38f7b86ba2750b4a83b7a69da5a8316aec1db8a4f1fae550f37deea4ae9d47ece3f63fcb8abd655a94e4a13c3dcea1c662ff564e18662602ee74df9841be146c1c537454c28c53f4ccdac8c632c4991e8eacc8671782fec92f9b7d2495692f01880d715eeb4be0389c94533b93703818e330155076f9aa9be168007635b1f526d1ae5c92df354b5595c8f6bed3cde41ad916a3ee440ba1b65a93bae9994c4f8e601e079493963ee67061874ed5da40cdc0dad7247f68596979ab7105188584309ab480e699430b5a433b3c3013fbd4a1103ee8b570ddae0e9bc2f4567a94da0d98b2f182c944f3b919caf8c009fb15b91057713ee370272adcd8199de644ded7c908803c28b47eea19f486e21cf39d0576605ad7018612958263f1e672d044026f20eb1998ce3a4eb5704353e56b9b34b852dc948dc9bdbf1e8ea0a92a11c0e69c1ddfe16f39449f730681897cab56c9ac9b7d010005b1a93201adcdeb8a75390c6ff54b5bde37028332de40ec70694ce8fecd780f66c031cbfd4aa7c7d4383e5db1d2451baec921a76bbfceeaaee1530d7bec19525713c63f37a1b18df145dcbed6e981d600a6e1ec26b540e92601e841eb7114379313738f413fbade80b2f501f9bea60d12a43a045780cb4b373aa6d610e206ca386b99ec7fa1ab6681a9320bb86402d6456a8866ef352efd816affa080c327aeb164b11c3a51c467a5c78ee303787b0ac4ff528feec12ca3914f778d57b31d8a7eb2b8763d95fc40662d230d0b38b0e5054c775ff232cca8182b3896e6c8d1a2deb67ef93701b60768ab267073c7c89a070257ab1c771f9859c63414cb4f7b00371efde5d423028016cd0110c06e83069ccc4b26762deb14628080bf62b3700526069166038d95e9b1b1124775efc20bab823a52505ba008f3be66d99009d7730983766a9101f6ec5da1c69405036fe5de161db26d6ea785083ac9114148221fb14858b5f911a561296961b2fb232b0adc013b6802a2df0a766aa0524007f932a585782a54be574ef37a4db1095cce6be681302874a9fb8b1f992e2e0fe8ddbb4e84898cc168153e21d70f2bb2c8b9492782e169a753d9cb525e8d49a2500d34eb0a2e938ed7307563d48b321749501595da7af439ccf5861677e3b90632669029110ba5d59e2bb4b6f832425830e954a04d994421dc8a739c9878282fcdc5a928e20cfb8498b4687f921469938265809e04fd7ad21a0773a96e034b02f066edc0fd0afbd445f88487a72973d333ca5037a2d9f90c5eb0619616dc4c575d80569d9827e26a277d841586f9eef6394a325b5a9da5816b0690c558aa29a1331c55c38ec713216efbfb9322ff24cdc82e4d4034b3d65ea8c84efefbd3e5b116471a9920b77088a9d898fcb49cf8c4db764fbebb39b39d7b7b25b121d7ef7958930de12ab652153b0e893673c23b12b60c96f8a7c28d0ba6b5fccfa50594cfa6890e32988cf4ac0cd60480a57a84e4e25f21ac2eed7c9ed841fad933e2777f7f09a13bcf80988dae86dbe877d4e2e6d57c49d22625db425d9c44611310c5f46514c1a2c0f6b61ff1620410c05dcba6e427b9650d3baa8ab87e031f5a8cab39c1fde0be43302f8404f5571e5af37901145875c1d3fce05d7f777e1ef2f012850594b004888133fac7d0e4dff2b04711364bc49509c2f7e629b3a4f991497d542efa31b4eab2e0625f9d3272247e1c4030f9a19416fb5935622bcace9f200fbd6b7704e655e223135a99f78ff664764b0174a824c4ee3b0192c2768094d7c30fc2ed5f70cc6ab68fa1b493902d21cad675f0934c31e77b79fd45011bd0e6c6fa386f39f942d62ba5adfe773eb958318a52ba4c81e075e1411f7538ae17ad9d89d7f6875940226f6d00f823a9c58ef23480e25949414a5f090ccea2542db643f22c2560e906ce76684a618e231164ebace175f8b31e3bfed98153775b3a2004dc9314832d5ae47daf51ee6a813b073832949408cf1ad7cfcfc7f51cd2bd4bdd969751863183fe118995b7a268f35ce53443b6e12cfa4bc34c084e6d3e9215b6b51e477cf5248998da802478bb3192781efc22bfd2b2f85fb536a2e55e0295c094f92ae205a15bbf77e709b5a6b8157832c2d3e67669fb054c1f37c535fccfa7e409f4cd81fe8fabcf52ea3991ccc37016b661ff5bdd1d362ecb0d547a4351d567e1a839ca9e3c5bf27a9769528d86762e0358b7cb1b5a51115ea31c2afa086c93865c2410096e29f320b21f1c0887f25bcc50b320934fc9daf1d6589cfd66bc267f77d1128003c18b5c9ac345e883ac5ee11c5c41f543df29718dcda23807b1736fcaf2613003844b709a76c35d89173668af77459416981f036830aeef0eaeb1ca09d711bea22350f2a49db81dfe1b5a97886d6a15cb98190dbcbb877a3fdf629c30f16a6dbc2d674285f746fb1c2e4dc81f42b52f064ffb8a32da578d4ce9b76abef5a91947760496d1281804ab6f5bc079e19f02fc1ac63f76402f7a01beda48a08230a9dee6de6b8c8998cece6a4b3d3a37e880dedabe170153c9abc9541140cd00b9be5a0678cfbb72f268f145f265eaa781c34e8572b96327a64fe48d57827d492fa52f48907c0726062fd737640661d835221668cbcfd44c18b8debdf7c4efc016d4def3660e89921e037c27c7cb8c350741ccf2ac9b16c7ec14acc7e4b10d25ebd205d7728224187ea93867da359a8a6ad25d56a6e55c9b56c7b5ee3abe763d6b672725238c8c6037e8435fbcad8120f61617690bb2c454aed5e3c3a96f7d9c09342a3170040ba6e8d29021c70326cb3b8187e14c9aa1465b010a933724962ee485d4a46e1aa9d2d34d485827f3cfe37039050029d6d9625888806cdd36c0e1735c0ef6da4e709518ac074949a3c299b54168abce3198140d1de4d1bd92e48d59daa82abf6efe19c71cbc62f58b4efe1290d3e53381f0fe6915aa39492fad9e830408fabd9f25f0aceff1d41e9f51670e3cde28d1f89dcca8c1205df05b2e6c49ce41a81a9bd1907fb8fd2827f41952e5cb779f1401f3893b6a09bd0aebaab5b136590235e02f74f98fd49390757d8e55df118b7accaebeff5e707b4c2dc06e0cdf03d6444b63ddf3f2b7642e585774130b8433a322f989b485f6947a1e9f961a9dfe9fc3ef02228fa1586503d389ccf054d5c5c75c3bd96e065180487783ece3d78f79fd925ddda2c371ffa5fcce34c1e1462d54090d56ad53fc0090617983fc9778893d016ffdd7d809d440149b3447c6567ab62e4b43a90c784f4d42c777dff2554775e325d1c3100bfaa1bb997aaeae09f5c8ec8418345aed6d18de9855388c3fc992e3dc70ddebbf76cceb7fe39883301da39e36de59900d16d9db725f864746823bb16eb2820f1f6e4f39a3026e90d14a0f773fe6ff0ee3cfc8ba0884e0fed0e24c5a4f97c6d0e5ed7e07606e06aa4d5171cb43105dde426b9f227e69800a77f0797874bc401cf356cbfc83f8ff46cf1a38163d58199403ff40ab0177d994b63771332b0a7adf6674aa05b720dfe350fe87907ce62cfbe711c34b75a3e6eef46582058425ceb36c6e28e091e262054059cac5b54050ce32e8c3174db761ab30fc66972d95cd0d64961633405807e0d007556f5102dc2a49007832771b84cc8253c71bd62b07fea9e4772a7ec7aab58b23316faa9baac9a6f32395c986bd4398077f13cf5d2c0ddf0e6ebc469f46eeaf22f1e21714ecb417406a1b90192da3f1d4495c24cf6ffd5d101283b4649796ce7d5a802891d59ece54cebee724ea25adbb4f35512ba0d03570b1990b77c0578cd3364b7e51def8863d4268d1d6f3ec90d2bab2f557233d0d94e1a364474485c72e07cc9f90dd393aa587200c7680cb7e0011238712be6eddae7d5658a5f87a60432fcf767210978716a2283cb0220e5f9724a85107dbe64019e2763722dc8c6319909ccd96161512e216b137d044536ff5eca2434182c31c0662e620cc56752162d5b3fc894812f3b6eb862b4d7b8da5921f73e5d03dc8599526509cdbda1d6e8d80198018a5a91665a9d9fa7b3e841b769834d97d49932971db5659070201c4116ff76a0ee14eac2ca39ebd5baf84eebbf3cf8c2e3af8bad70d973f1067d12744c4b652ce27c2c1b80112115cdca62fff9e27de482a5ea08c6a63b633c514c73b3b52aaa47b1d79a65ede2f58f5b8cf5caea9070050d4159c71c0b9a4d6b552531ecedfec19e61b69ce0591ff82c6197f73478eb6b8fa7e62743169c92b5dbddd52c185029dfe5b558c7af6c7a919dd947c7492798112fd9772a62303014c62702ef28846a4279d129118662791a8ad1c157b9095dc022165cae6f190824e5841cab8c67a9ccbb432a5f18b8d2d3c89c57251bd22d4b62e4865c1d230c32b4b81b825fb074665699c8507af4c97bf4582ee10a1829b3994710b27e19cc96a1d4a8b8385e9b3b95b13694750eea4820d7917b322e6817dd048ef06d7bb21069cb5e5faae526cc71d9823d58635d49cc3221eb57db3aa779b5d38d94c87261048282fabe0afb000f5c53a3ffdd98b3eee06d37c8536e50fbb6b54f197a94bf8ea7ee7036feb187fbe2dfe1cd57637a54acb74ba3bf009e1bbe308a2041c61584b1644fa562000ee0320f33f50d34ed1900cecb47f91eb82a2ccfce0c8707651e422e913aea2ccee913e8ec318dd4aadfd9f687c8d27e61d30695ca6599c07f7b0a8c03d2f0f4859cb69d082462aac6634c59928942cbed1a5a18e439f5e9a3e8dda433d092d38f4a1e2dac01f36cb7cf9e0ef41b21cd4e9898557d15c1318a884a3090364245b86af30149a19866506f0d5e80d8f946142bd4930761b994c6fee386a891704809442f8b43fa770185123c9dddb3918e6c9ebfae5adfe866f66c0627553982f7cff6112636462ab2aba37c4f6ce5cc77e3570354785c2436e49d1d774ca2b9b8299805896a4a88390ad8d821e0c96f8efe651e4f7da2bab403a1c7fb3c74529390ad51ada3c59fa0b02bd22198a3ea84e291957a0554f22948ae7262f74fc312b1dbfe4363a32c04f7d126561e88d0806e2658c38e83d88ce9ce88bffefba70cad88c090f9ad9c9f5c7f933e80b69862dbf43bfc3f0c726eac154eb71e6a8db358a60670b5a310b6541c22c2d81dd250fea72f899a641570219215d52145c703d39008b6e5d31775ca33c57db4818a6909f69ec5ab55a5a6c680775d281c593cb21efc3dffd4a1b78519a893d259d525ee0a9c8e980c9d339c348dd0315746f14b1d2276e5b34c83c60be9ee5bc173c5a7ef69a5fc66a93482a6cbb45690d0ee970e577ca1998c614abb5f35711e00bcb9f90fc50e84b8e47cabad62526873921bf58b5f8bce088c99cc48a377114c0699796897242ffb2d7e2ff4e334cbc1330d3ae17b92e004a56c937c58a6a44b386bda191846ba8d140d2a09bf46f374987d330d8789607a353f5b84bf979db1fcf591174074e6fa06141bc046725d481b39c60ecd7d1a5cd9246f3485719911c2e8a8cc71ee9b7897ab385dc5836d28aa8de7da5b44a6b16545267a718db680765bde65630bb513be1fd796097da98d13ff347daf38a8e535035682b3c530fca9438f7596c07b04ca338132899c851a09b0f0b9cd22807fd9ad4188c16b68bf28ef36a1b1942d474886d7d87518e2bb7b8fa8cabf6ff95038f39e724b979a600f73526f0bcbc59155c609ab86f5e13a009ef1c201616d9f3dfd08816988631464ea60baa276b8f114a8b25d11aa70871de5928f7c6924e22bfd23f8a9330e5144f429573b65d72c21697a070924a2ba0c96fb84b83bc369809b41dea790ec0ce2205e695cdf5e74f53a268ca6d222b21b134638ff7bc1752482f8c47c3bc972d15be545b56601eaf81b55f0d0d3c1eb7aa26dcaf3831ed471bf02cd247df61dd4a6919688ff15bf80b1f16f4d017a30d7121fccbd875372f28d585797202fbbd9cf499ab0082c1dc4cb12303689fbfa89f73765943e4e9c8615c9ecfbcdcc0554a06fb192ae3bd403e834b1adfb0fbf1e06e4f8e921afe7fe707fd0ec9aa0150249c80dbeadd8c3b00e409f83501c10450830edcb9c578e443ed9a81130731c15e41cc04bb00d0c19e06d28f873915cee7b67fd4bf4f1e56fa0f53a127d00337bef1b0daad2867259ead6dc3926d096c053bbec653f99cfd89e433ef5332b0eab9cf96ec78f7e4b9284330729fc23464064200f9e6a9d1da6b487db3c210763d0b6d3bb3070ea95b5d2604946214aecab35051e2f8bf9262be2bb49125ba70a599fbc0377f029a79f32460dfb0df9ee305211a2b4d190a82130b26a0fdad7270e88a5b455d73dc8bce88e1c9f4f6e02a2d9a558a1631406d2db441b32c513dd7b2c3739be0d2d5ea4b5b4a460d3d90af5f9ec20337c7c2c784cb9fd796b1b4e2ad9615a258bd11ac8e92652188896e7799e57f7f249ec23233294b88c8151b5995739219ef0fa17eea76493ee56ddd7c5cd47ff779c361a83f2d1407c584a4c9b64193f61d14bae8bfdd45d151484fcbd61566cae7b8f1eb51e8c958e398fab2bfa08c2c8f8611bdc7466b23230bc2469be14d2c9aa20de768e4368a6ed3848867296d88a165b009de62a00a4f512c93ff57a70dde11ab1078a74b5115be11e43c39517a5ca141346654353c9094d0d4bf35ceb90f383776c47d57420a0721040966b6c1d62e0e997f7c2d3bab51e0be7549c17982bf84e6950901acbb71a44458ef48191c6a7bc6965f61624b48fcfe41ea4c1d54327cb34f7ac221d063b2e0b25520fb4db3523d39be5f20c85ee5a5d88c7da538110dd9fda0d0a62e2391945220732a02885e7b98d0a58009d00a3a723ffb9e5470aaf7e0ef1eb2012ad42522930da389e2b7b8bf6dd8dd44687c5a40585df8dced13e219fc298c74662d9714039092aa6c3e1642880c6f8d4d5243dd87be90fdcf3fbdd9bf5813124fcd7bf3dbcab63201e88d252c876ac5194b4f6d086296d5935ffcfc5cb99cb54b966906a39cc28695a1851906484b392c27b7d195917dabc9f5b3958969dc0cecb925d8f55639722da479ff0d2c232c87d3981ef17a9ab12cc8a61b04ae12edca7f2a48f363c9d18d6d5819b19ee19003bd2e87455418272156ad11ebe17492f9dfb1d3e128689c15a16844f473e628f61fabd0b88fadbdf124eea6dad220042b32e11d87c6a1d5787dc6761127566bdab8357ed1b4768f90d6611483161d22b4a8389186d6996f643496734388306ce548ec410c36c00198ff25ac2f229b9ed53a29581cc744e2e7d8d92490dd28360c14c93598baac073f86fa123f80c531a66e8affc021df8e749432c82839a3bd28122108a8442bebac3434c8a16512ab55955018555f867b7642b9db597894a75ab0000c81f2d1ba821a1a87a1b3ec952e4d2cb8286934537700c6a8603d545bd127f6cfd3e51b35422d0b2e2f007e9a55c284005ddc70d85b2f8df037d3c75406614e0e6a1c077ed9d8d5632779eb870c6ffea54f21ce636b58c94e3a5c437c7d6976d98b3cfa85bde04e2de1553ab7a214abf4ef299652a954418e423bb0da3b49a2d341cbd563cf535779fd9630e88c4de16c02a17d7c36ffe88c10f29c1558e764ba7925ef6c544493b7cdaa8c64c604378ba3a02986fd92a4acdd341a8c0c7bb3f1f7d9d9b7ee58154979b526944e9f7751faa3274230904d4011379cf228be33625d4cec99b9baef68f972fa9f3211a9576535938589bed476b56a5729cb1d9707d4cf43fd6ed0b4905db78439c4318620aec3353342d9203c745d171e85652ada68c2171b556c98ef3f8b0b54a9d2cc0e83c6e5c83ff7c4a6ff5ba22e18d42cddedce718e217d9a21d08d61bebd244b612990112b9fe9cdec3872d4be6d386a73ebb4db0ba5d9f7e21476f6e9ff929a608f52fc711a40d68bb7fae3b06a76718dc9836f5b2cf5a0eb86b529d33183692b028de22db2127c7201a1522de8f22716d09b6cbf1c49b72fb7bd212cc57280db71fc19b9d210a6aa08410440ef76b5f40f73fdf1b7abb41b532e84a03d65dc1c8fe63badb0f5227ab847eb58772ce7dc80bac4287e1e1f5677e908d91a035e5808e9c907eef7876ccde5de7987da71a8f81ae3402dbce411f4515d24fd23bf26d7005d4794ccb965ba56dd83c7cbdfee0cbda9ff7005a1c6ad912a44754dae1a1da6ac49bf288bb8f89c8e7df2d49d98e0c92a304bed8974b46f3b0846296b6dcdc1910f82adfe392decbeb5711772e9f89cd384dcb39c44a1ee077eac184bcb95133c273b7654fbc32ca2a19d783555687aa08388391ab83687ba635e7c8117a620d2658e203fdd06f835623bdc0a9d1f67ea1899e5ec8ffe6224d1db6b3fab67e1252a5ec256ce229ffd39d41259bad1d69a46ce425a005628427d395c0971d0711cc7443e23f3e34b53066a47ce124d8a70c7fd9bf5d61716309d63bfd3aff346054cd1ea73bf0eefa90a1b8cc407069946ddc5b35f7affb6a2bdaddd9792c056c9aba563027e1dccb3119e25f723815bc87de3cbcc770ccbc8ace611f10e47dbe7bad85a2a77b17249c3cafcdd1ddac81f09bb8f70aaf17cb8ca28a5c3a4ea836ed7153f86773a160bf24457ba03809a6469a292ae03a58981a8b1fd87c42431a23ca944501a3c8be85c339c32aae8344a51882a537362f392112efd0a0f62e1b89ce5abe7b08e50a1a3788f8f5dc9e27d7acefde45394b27f77cd22ffbcb26002665e29161b6a908a2801615842cff2491dc1c32a4319dcf622f9a438eee9a029eb80f8bf9e3507a5b7ea938d7cd82169964b6e7d08118982b1328c5316951d699ce2b967607a94df334ec8db489cde8f03f85c1953dae353bc7580d248af29958f941655da334a4135a24539e0e9b88bd898e944d0005640dfd4fe42691ac8b002d08bb1f63c602bf4fed452c190a7b00d93e16cbbe4d24eef721a64d3c84063a32a86539dde55e826264557fce17c04e130918369ab05f5efa631156d2c60f7469b5ccd7ac47dad4714a4524af2a9586f3908d7b2b8a6803f3d6fb58d5a25a412963ac1417ae9edbe25ae17a05e2debc7b470bbaa96e75c3a5966d9daecef89f8b201277a91b8f1467691344bb74a9292822fb6a2fb56be348509e9fc25c1cdd7a0397fd918aa89d6cdc1d51ce9ea0f12ea90cf71682ee34c3362e86d4438df91269162ec7afe9c55b9446a957c4c54a3ff475145d9a6b6888117db4e5eaef1386c221a36cef00c16a2f045b9975e15386033a40ef364c3a1e6c33b74e531ed4592de4a34a84388d2c3aa5a23ded91e4095e5c759960fcfd6f92aaa77372c47715a99b2c48d256de89b11c09c7b5fe2fa78ff22af4e45b99d2c964e4b11c5a37764aed5b30d611ca8c24441e99f960107b4dd07932c5a866d51a0e0cc1ea511541673c44bb94063254860f2679491490640f8c9fb0eb357dbc7ff6c4c2c0778f245018f4da9eb1ebd01ded5c649568e9c6711d811727595ccd4fe4aa0d30647f6796dca042f24746ee7bbe4b5d8d97893cf04944666375771f159ece94482db13f4715a4cf55369689a23c53e170e0bb06bdfd2ee15f9a89d5fc132ade3713db680b7fc368d4bc6b39f6e1f68e7aabd8882deea7461df0211c5426e8cf441e863fdb02e051f5f2f3a15ba7555ebe67b8fea237905bff2e1c2722a412b7c860c3317b6b882db62da647dcaf31a8838088e0c3c0bf4dc411d807e81401e72bf114dea5c1c6436a1a298f332be146f2fec813f1bb54c5707f06be8707c016c9c5a31013e10581bba3c5858750e91514464413e7e88f71a89c92b29b5ad9859b920d7921c85643e18b4f28a75b7060a7218eca89946b62daab95ec39468e071c3861bd773c5cc8b3b639fea3fa278d65a75b9693f2dc0aae433aa4758816d5be7c52e8f64facaa5e78a359e4e39da8ba28991ecf200a3eef2564310bd5b2cd0bea1978c5a89b1a8f5898c637afb718dded1d2f173809134cf29ee3875e5bfae417608fc2bd3dff223a854b9702f9aedc06302d0681c199142edf24eddd33505133192ea41115d8d5242f14791067755ccd10ec98b1b8c4090d4dc5d1ed2bf804ae8eccba4a2d6b4a1b017080e105656f642bf99a80f71d98b7612a1c337048da3d10769c12373ab3c3866560abe4dddd8a01ebc0f00bf670b36e232e160ed07f6a4794d1f717cc2e2daec2e579baf1eaab862be879a8c33f7b9aeced65941ebf2bfa66d2662585caa87724d639dc1a87662439131371e71259f6307ba194d70fab5f59190a0f057b301220076dcbd02320d558602f7698781f5a609ffc291d339618cdd6f1a69e4fb7eccfae65c7507d1b361d880b2982075b676f64d5f3cc909cf50f833a2ce390eadfaa0d41d7996848ebcfff4333d9cd080eb120ecb0df74f92347bbdddcfae579dc14eb05ec23fe354b5d0f09fe94beb6d672706319ef985e5693703c818356242601effec71d2f204746608c53a1ae490d4a2dcfde8ee032c99d7057d6f635b260e811fd882eb490cbc510f7e6ace0c08a1f8e38406963829bd55db708381155a81a52c2b2b4f67796144d965b2c8d38908aa8f0372c3d91e0559aa37365a669c661380d49c107eb60a4c81440c33ab19a71234d167f8a89dfbed148ec217ed97447ace156b4537e0d460728b5c280385c65d313be39baa27ce3f44a532fcd56577947b2edb9bb24c95b5932778e2a6647b2e9bb4df2267eb0ae8329ef9ff284191aef781769b9575becff7d51c5d028974b4c3981c4f4c7b0fc47614d2a99791868f6331046603f3a2588194fdbbfdd998f8049a565bedaf51df07d6892174d19b4876a3f5b1c4f7c5e414373a2ad9874351fdd092dbf8f6fca5c3c645942809a341e7c16c2ad354c759dd39a81faafd4cefa27aa40a914d3c4a5c0419d12e10e039e1166564e1dfd0d1c11991b2f7075c212b69e1b2b1287a387a133a7945213e6d503ce976a5aaa58ea3cc4b0445b24a2c145bfea82778d1ccd086baae94c9518df7e185e404fdeda70e3b0f2dc53b1928b977cd4d9dfb526cf374b9e4b34e740e4bc975f1ead44b2632e1875773861a996388d9566c8037d19943067691b9d7a429418d5387314e69454c91ae403f99d717c92eb0f78fba6228e61ae2e7d01caed2c9fe645013b56b9926db39f0c4fd6d067c8b3b9c9d0bf72e040195ff6b7d6b8bcb5b78b302017869b8690e2ca1a728c1e17d9d154ae12031f01129fd4e955090f31098d3a1655ba9b87b307a9c2c78af427e7196470eb453410ff9870e70669b60651a221166ba6299bb95571825247aaf92e028c1bb5ebd9915c5215c462c8f4b1dab28a95dde7cf2bd1f5f76a7447dc3fb6521dddb6e7b1bedfd76c53d2eb43eab18b23a4bf06d4503e84054e87e666ae0339f87d1fca555bcfe01459818e053bc99aaafef4d5df2810e8db206e31068459c31538142c9311042036c65e98090f331ed60b0bd4b77f30000ed4636610e83a2a664f2e3592c7144fa89ef3e78fecf4fd4072168168a2c5620a5c60119e48cd8393fded6962b22d5f9091f83626ab1a0d1e47aaf37cc3c647590ca6450844d460c4bd8bcad09294af2f527bf63d9c9457826755ffe4d1494839a39b7aadf80198fd179f99d2260bd213770a450a58b7f4ffabeecb6c0cdf066210570ea29d05b2905aa47413d512980a404195bc1ebcdcb6620b62b120142ba720cdd4c36ebb705bf42098a0afa6b4287782b38f514d22b5829ef4ecd30240311c795e886988048f34acc6009a2dd95a82aafed03326905af4abbd258a09f834a312e5366b35b7eeb502ae405a21b5f8b2e68c3a9e71a28e7c2699908de08a9385ed73ef65351af80faf37f78d8a2473742f6df73d2f508cc790c8c0d23539d37f74bfa2a0254fda72ffa2b3acfe4d2fb99ae727738066ddd0b6eeab8907d00fe444a38ac1e8bf63c67f93a9de4944a54deebc49952878bcccab035b0e4871cbc220a5d7dbdd2aff185a6bd5a118a345a29c6c9111bfd96833283b6839664153dc43646c2deca06755095c82098e30213c985dbc15dd1bb967370078c65577a50302ddea93917f2060be444967424c96f6bf3b2dfcd1002138439e400fe3d66dd0d38969df0f61cfea2fa041ac8aa7cf89b02bb63053c040cd416af653ba2e8b7b9271f0f56f0438a491614917de7208e63e4c926cf514fc97a7884602c348dc20d02d14cd5aac8ec0ce7fba996e2ac4a2dca2a347876f88a3706f6274e21a91ff43596fb4f088360d314b7054c25c127840c00a7338f00ea50f952ca8b5cc6c3619aff0278317a7700498a3053c764c0c85331585d36466ac76e78b7dfa88e42ba633ddd3f1491f84a0c2cc1a6d56f78dbf4bcd48c53b9e6234e32d29acce12f06f793634c236afe1f360c294c78feb8d548dacd6cb5f861ff590b7d8041037cf918c25c5687e0c2b69f30bfeebabd6b76024f11fd04855e36b2f5666de151b8b681cdf2d97088a20388f0aba54c08123bf6982b2ee4a300ffc1bf292f25a5007df8b73a5dc5dea2a3226fb400f91deee720601ab4c63df6dc4ecc54661b9a18c93fcdb0199bc410ee40192b9608f1000b3a986ace01e7823ba0027cc1544ab4a003650d12af1227d66db74c78934d7216f378a3d20711f15723fa7b4bee9a880a1fe5060282738b75afb5661f95cb7cb635f42386dd299b28b9706f6c08b0a10cacf71fe250bd90b246b430972b6cfd8a5727b62119ca3afa9918aa36424121fa1ea0638cd4e4c0200585c1023d5127e93cf585a05dc7d485fbbdef7b68d6911d0ed0f4ab6be285a842ddd051c4958d8d9d89d403560b3e78a2d7cc37d08aac26783f31e607dd375f4c552ac477809712073838d30f4e57b24e729d580b781294d763b25c7d3f55e34fb5ec46922d948d2d364951129ba75bbcdc8592a67fe1355a5a4b42707d14b938467a9d43c5fea7a2ae564c0467281a6e3ce255ab60f0b7eff7f7f24dc7b057f2f02fb5479dd954f90f15063d1b4db248429d627e8f234dc2df15d639592e550b3915c7d8ad4e9e7fdcd4d43480930c450dc942630f3238c2e7b176e912e10d7ba2c590be9da0367e4ce7ca02c63a23dc3e2aec3f6e437fcb0753447260a556f8409e24486b2fa72ded0c624b14b0c53313c40244942de5f76e89cc44f1cd62124f48d18c1a0da12d775e9c40beafc8a36770863c9a58952e03ed141240ba04e0dd4cf88b11507b9bf58bf4746bee50cf3792d1af80db365290f25837da91f429a3d064a195876158a32c6ede883a0e245951777c163106de1822c82f093abda3ce1b5fcf61a77972af3de3f7e2892ef55dd25d25f6ae7df6b01927187925ec17feeca6a64f00c64f1646e4d3b52cf75706bda87454d01115b5f67202741c19b5ea04a3c9ae94a999346ad0e90c74d519c4aa4acc6a4a58affa368c5b66afbde5a6b0f7c4fd1bfb24dbce0df84693c0260f22366cb1b2d2aa24036ac494717f296bd1f4b6ce8e75a0451910c3c3fa2a5e48ebf0e25cebfe2738a50b44f313e9486e6ae1e50cac3f20229fe1934a7005665dabffbc725140d13788984a5c12b0c8095a648699946bc37ad362d2ab79a84cf8d4bc0346227a52b40b2ef6e7f1047bd98022b88e71767e8eb8f61f70c81436bbe0a17abad222d0010c26f5b0f26d4b28a3feb1eb2a34af3ec491d63ff6a7da0aae0fd8ead6977fc02b61961cdecf001a6984e41fe7bc0b9a8ad24dc6f3789b8d3eeddd443de6bd3a12abf34ebc2ee7152cc298369f2720e10371929b3b5c38e9a6d7ea91aa4e14c00115beb87b0aa0464eee762d5eb474e77a11bc9a8fe1462cd018dc18060dc3bc8f0703178b3177ea984321610afab35cdba70e7539a8120460383d11889645987b070c481a35e713a2db71acca2e9d06046a41dc2fff6d4d9c916443b5b83b7d99fda4a3b0998c292787b65e8219da5edc0a63488a1c04c37e1ad4469a2821bb38dc9467f05b70ca3f26e640c9b14675042f5125c54b060c70b3f1264c61d0fd0479034796340290a7230060b9be7969c07dd2fd7c01dd33f8542c67665266e51f59946be712209e1e8bed1847982c4788a20839aeab4cf35ffea55e9eeeae1eed2fe8566c81063f3fc8179dfd25004a41e41e347012c2dfe66809d1d659bc6ed1e4a0977a229ad8ecf356105dc845c1211758837d55fbd9ccbf6f7de4cf0fa6e9423e2ee376b303dd4dca73b490eec3ec7fc1253f4fc898802d593edf976c9b38c5d1f9d37d0f7e6d5cdbac952664913596750ef5805413101401176be1fffa292bce4dfaa2261300a0041254eb1ff38a75645bd4c1ffd0446a281ff6f5b1bfe820ed69d32dd47f8cb6f81843801e0f2ff3b52c103dd60abbff87388133e294c9411fb777bbc9818e26e5b80eedc054c40282b280fca915e313c911aa5a0d8f0348ed7072d0147c898f2317b2490e16c0cdf0f3065c035724a19245de8926f3647db59850f32e8e29fed16aa27df418ae91f5c93911aa0205a29e9c70aea54db6c261a569a8864f1002e89db5bd07ddf76ab8c00c767d1218f40f3db05d211f027cdf7debaa0e731a95df72381ef094df45cb9ca0ad7a81ae600c2df4977fb722de929c7f0922f41298f1cf79d0ae17a3e46a6e16cedff9490356a5d211cf8dde32f949b649f493065afa31f1f34c325c425132d0b9baaa196b8302354527579ef2b759eade6745eacbbc11dc4219cd6f1a01b9f05f82952f8233485c9f57079107a80aeb11def45b06c5b0c46a3c7040677bc09de93c9c3b21be4877e02f90f01e77492f25260e3ee9764df120b830c48376c86d206b5c0b2a4b4311b845fe757b7522f500e92aa636b539c14c01fc2c20fc956ceff9b74fd087c3314dcf81ff8e24c06adf0ffc669634bac29c1cbc0d142588bc3f52de2881b6290ea00750ecb9b2a9e204d45102405fa58af25c02166482632df5cc525c751ac1873aeaf9cbe6893f494c999b4bc2c68837bafa865949bbdc3c445290f6ddfce018de6d9bc19ec01931b1c1bb918f34755fbbd61f4bbb089c60cf9b14b169ff0a077754b47a9c4945a12ba50830f3fae3d6fa0e199f5a77211a942351979eb19cd1b9d97ad5bd340873fc5c19e7a83028fefc6f571883490dfbf1bcc41e89c0b7a404ae77b6b2318c17813d9f0ff29b1b147d49cc7553646feac11a47bd2a20c59eaab4b730085f06c0e6d19fb26922f1ee1f05328d76c7e11f0aa90aad0f7aff603d92d3d5eb7b99ba39ccfad6ee3abc1f400c2d770b0af645bc652dc6666bb4f24b8c93400179e52862beb6a1254c97ce6e9de196adab02b623ddde97d3cc766173290cded705e1a8e81519cc19836b77bda6478edb5ea9fa41ef2ed53561d6a4bef3e62ef84f0ed339b1eb4c9a5677644b12824557b609541f6690976ec64d7456dc1d712ec6fe573990a63537742e586d07b7a6d5d6b7a4f2ce3db2328905014762c809bf622ccbe50ccda3742ad30c9c4c19895b5681e6f338b92895fdc217f2bc408dd02502f9027723ca9b8640abd22b39a4332240a7634d9048838e0c3f5c18acbd36f6f87e8cb9ecc7abbb8089366a7d5eb6c3d01a7842c4af95fd11fb3c48a793e2956b3042e8fe14bba5aebd16b71cce0d5d892645ab16ab19da271bf8b755d4938cdacac96e64da98c20e29cc4acfdf1b4f56851e2d80e1dd277e1fd57c86d05340b602fd52c8059e3e3d7e90b6ca7bb78087c0d1fefd90b1a17a4db4cf817b33eb1d507a83929f9099c169f67c27eb6d83b85f48da368c4ab28db4a76de41bd9967f55e0fba0a4c55aca8222a3b79b53a7cd58a462f6234c2d210c60a7adb9d69370a20993a7fd0e60f22c9a0c84f3c4b8404f8c09a47f412e9456f8babe3c636f2500f28d49dbd3abe8678d4dc2535af179d4d2dce797e0f26881cf05b0ed804922dcabef989484114dad59acd856edb10b41400c10dd2dae21503072d95f2615451580a6f8e3222dfb890d5dbe27e41b46ae2720546ce8947d2e6c8866e7467366def50ac1f37fad5169d263cff7773dbb35f8b563290a2229df50a8df80b2841ed830a81e21ffa07f2435b0652bb8c51a279e60816aed7bbfe5ea35ccc2450eb2b3fb5728bcfdd1e5357ceb26b597bf2b71872231a136e67c36845990d8a959035456032a63d67a5a30145ea503994e4191ec1e2aaa874b6a029bdd3c29cfe5f63c1694a45895bb0f30e2c0a823ea2fd6918ebd058120d24bf33f000cbd49fc7ab310afae7e6b5ab39dd9481695cc0a48eca57e98a0f6edf52dd99b6cb9a59429a51475077307a907d099497bfe9e821dec58885b34dc62a1199ec80765f70df91ece8dd6a03c839ce9b1ecb949cd63268ee59e7a64b0d670344ff31a0d3fe4c73e9e6080cffc5c19cb9e460334199c3a78bc1b2e2e48ba5946506f7a9e8b870e560de892cbb5e41a8ad262c245c9e55a720d452162b96a66686a6866a6ccb2168b8b926bc9e5e2e2ea2f5a0708a00ac46a535b6538fee632c3bda79cbb56eadad59ecfb44be9071757abd56ab55aadafbdfc9801c9e572b95c2e170de887f250ae191a578d2b860ba929caf52ecfb514d3329454f8ec0411bcd4f9f3b3e7e745aa5a8cf7776d32f33edd62209583542c757bc80191595c4e539d5b696e37a05c6568af3f08e7466b50f24d1c0886542ce34080d948e0b404850a25966035a1f90915d0da63da63271858d37e25d39540e6655e93e156503c009fbf04503c003f7b7982811f76363054e881cf3a205eb5af407bdc8f36f39b0e9ff619ae37ae57edf9db88423346d51b29f413126fc00ea73d98b781049d9376a8da62d29e3b92f6dc6d7ca19ee448edf9c6c4855a67be7f659329eb3efe2dcbba8f5ffbd3fbb8c6cd6721b1cec73205fc6ab5f2b195bb130cdb84e924242714a93dc76213047579c1d23bbcf26f2425cf5c9eda9a755f67a9ce318ca53a9defa9786ae79380fe01d37d3ca7ee536d1d4472ee6c0cd18f4e28b95df21d99b70983c6aea886a391e11ac991609ac6096d6ad9fe9d5aa0f72eef4e2dddddbddd0db7683c4c544a9807de2e2a3f90de591f13892641c15ee63faf2148a1f2cecc333fceea6cafbd6927c6f3f3f44ecd0e7d7ede1dcc621cdcee803755724de8d3224f758aa974915cd3a6c970fbe3dc3e5127e7dc1ed128e2e48473ab0326f3098d829af227a1981cd023e8e6dc52716e7150316ebec62fb3859e34de3c2d736e6ad9e6cc6cce6d13319e38b746158339f598266c621b746e8b284e14fac1189b7308f0817e53c6e6dc01686824cdd89c3300adc9e21c8faaf1807ed0b91d4ce6131a0545c5b3a27ad60a9585733d558bfbf313655e724d64ba7ec935a1dd760bdf66b3395700cac5cdc6a43b0b0f1695158fca4f41457902c40a6a1555b138bef8520c2eae2d6973903c65e5886873aee01d9cc4e2e8ef25f848edaf41829eb039b7332305cee9987840bf89c2e65c0b262c9cd3f14d709b7304a89333bd0c87c3ff40f1d7b83dc7b429e0f838c939f61a0aa71ed88d630ebbcdb91cb4051ff4e05df483ceb1508344bf89b339a7faa2699173386a70a0c2e6dc00ba702ea79ab81b4d687c6cbed0af860ad114ed6d4ce8dc0d19977302d887ada453fdcde449eda7e198885b2e88f66085cdb9154e3882b7611ce4362fb5f151eb508ca3f105dd0eded89cf3b024657171692fdd45fb80e68bc780e8077336e70240e96989016ccea98063730e00345a10aacd391c9a1816b0b03997c26f3feb51284a592cae88cce3f03f321d94cdd2a9fe66a22f9a2af58959fbfef321fc98d75638f598a9ceb5f445b562226e716cce97428dc7ada76ab8754a04b3be3d3ba8e5d89ceb564504d89ce3e83c42bfa96373ee6990b02465e1d8c5b192a7963cc553d47e966271d07cf1543f0be80423d06fb6b0615c13da563ad59f718c85e324cec22e14b8078ba3df3914aae909fda04d127636e74e989ee27678886c90406980f563a2130f28d6cdf6b4026cced9548c6b32f3936322a6d21e4fe954bf9c827eb06773ae46c5b826f425c7454c45a66322203467a3ba609a01415a0cef88d5c60bfa4158dd7fba37f48636e0d3de81f06f1847671f7be4c36fde617af89a16398f5c57ad6bafff97d307a8fd156e8e7f370f7ac375e56aeab9f188df577fe7c19bb3a71ed039ef2ab150e89c066dbc58c85b64b2621dbaf70ea0f70e189b9b717c9a9c01e5f7b8bed19b3bc640d3ba09cabfeecec528bb5bba43e85cba4008210c1146e8485d60eb7c44e0af167d1b07be38ba75f7d2254e8de30df30e3861cba97295ef70159beaeee391e1a43d825150e835bdc39b5466a1ca2716aa3470fd3889fd0ae5c5e124e88fb168dee1244ef5735d15eb6c7b9c456d156c75f38e95d1575e4c4a6b5037988c527a49baf0d07d0df04244514a5a355a609dc510c2a8292cbc1861e2c50701e4404f758bbcbc4005197891818cba455cbc1c89777777bd07e70fb5226887eae88bde689df87d80d8d92cff77361d4df1674ee8abb808b63f5175d152151c8a8a54b9010de20e2fb943fc886508b14b0e3b4001862b2a7191c312e10ae6840653d7284912499e4cd102830371050cf08570922cf0935c1159052076eece76842f00156adf50a576078ed73c6ac307e23562988cd371f8bf19a7c296b638951fd6f0135446a11d028f69b9bbbb3b33bbbb4b8feece746803407c77976e7436c18068d105b010e5e66616742bd5b12240bdf26cdbb62c7b53f7e33de53f99fb28636f7a1bf9304fe427eb6cf8f4dadb48e0f4da9f3a1b6c03b5fff43612e663303d8ca9c3ba184e359cba205ac79265475459fea8fd9a8f8f7f5201aa9a71369889b3c13a096c6f83fde989fc642f01eddf068b61c3be86adc3ba186a78223fa66f55e5eefb51650744556568cf67f061ee6ce4f3dbc8ce05acce24b074581d56c27afefdeeee1856dff9cbe6388ab23226d67fb9fbb19c0cfb31b4e72bc38f0fceda407bfbab045b315a051fcc48a23c3e1ac6cfc3a30b4cc6d051162e35c6960ef55bd7567494850918b78a8b1a8150e3df88b2fb9b257e7eac5facd84709b335a2f163e499b2e3a992072e9892648b4d0c7376447ef6b7bb522ca6ae51932c6a7758f74dfac3a2dff415c4000911484a29a5921d322600e580a48909905026a07062d54f7c71021efebf091640a8b6402285d0a2c3115478d1610b0d3a0a2962600458e504592069a2a5dea8a1355ed7a889154e68cbcdcf809d9dfd3d39806de03e22bdf1a9477cc901cd1ff9918ba1573ff297fb4a50bf10c0daaab1f3ee2b410cf4477ef16d1460aab103414c855d13f9eb83aa7a9fe655c542f3d7c10d5f885054b78aba39f0c2a546098f66bc25a32641ac24f9e32f6164057dcc43edb701fa98090f7a4a6d278cb8a27af7b1d26c2c5c8dab803210cebbbbb8b104b19cc548ed25b5d7df37f04e06b86dd89e2dbb535d3c3d3d3972701c8dd861a0f3d99ce6d14259e9a7b99d6ccf3a8abfb8cff6b030b03dbbcdaecd699f9882148a39c675c03b1bf9fe1fdbc82e067ee8e37c54bd93b67648506f43825f4b010a9d2aa03f54a420b60895795136ecaad50a0a0a0ae2968cb0b165a32756b539cb4b9be29f29a8fcdf168157d4abdc3577a94c4434b9b5571ea0a500853a11acb3ed467887e7119aa4f22f2fa9fcaec3f6cce7ef2ebb3337c56f43e5c9df4a73528a4211050505316fcfd3c43fb66e3db55aadf679797fff019bb31f143429ea9b835ac789608f9f6750f959e096f3cfc9cc44ad266a2566a1ee100d910f4d6da373569f1dd42dedbf0ee22a9c72c94e36e5bf4d6c133d3d3972701c0d1adc921dec5848e3b8a5853ad1f6c0f7f7d76a402613b210b77a4a7bfe454de479d0c32dd53f460ff467d51e507b9d05ef2cb7d1f696a2edc1de86af504ba046d002550595c269c81e7b4d4a295fc3ae547e2bb2d002938f715c8922514f691da5a5a6e2de49d5db4b94b0e5cf4cdc280653aaf313db93bdbf133551018a7c9f4062b985663df889ad90fbb88adc42f9e57452a3d5b18daa176d4e134a5390424d2b45e58f2888c8777835dd79c5add841c0f49ac641272a47e4478b5c07606783d9605d0cfd3df810f0518d1c1f55c86dedf721ac6794a0f5c3ca5159541657d2c6e88162bb9caf529042b3ba3d337b7eb66103857228fb5dd230bb4e82e529e69ae81f5a89a0cd518259add36a9d1d6a1d9bfd45d23a41adc384db7699d59e867a42f7d4a36356ebb8bb43d6a232e44056b7d005f2d4f2f2b28fcde1ef1e720be5b81fe887aaad04a891b4cebac01cd81cf6b13dfbb13027a528d47a1116ead7409cc43f27a528d4ffe7f1764db439fc0ee4abf6d8573bfbc395c50de4acd9a720e452ed3a84973a80ba46432c51b5de94e6dc02d63e3a3a6a94114d62a72c22e65ba8a7a8f9654e2527aa44bd71059402142772c5852e872e560a4850fa7209efc415f32fb3b4616510db637afafd72b53dbcf207a982da520719c4e62ce15467dd278be20bb0ee934ac0ee93466cf749225ce82a579bd38f6384d20eb573ebd591efb0ab8196bfa0bec609fde2172d58c787faf4302fabf00ebd82b282816416b55f3e219dec4e5c49a3f8257e9142dbe39b66caa0cb7768bea78ccb559d88a6531a6a1da905c3749fdcc2c8897c2752147b353a510d117465d8546f922bf90e7520fa8fa2be4936d4fe5e6d8f33d2f6ecf397dacc456dee52794b6daea2f6c7952bb9922b05c09536a73fc6b81e520eb2c0d2f289dc1c00218b2bf90b2d9dd48e4ad854b752f003900ae04a87da3648a12446628e5fe2176050a9fd722585a2f40ec792455a678dbe78523b4a24416040513fd94232022308f5934f3e0954fb393252af1118536a7f2cb283fa451e221224107018aed43a6b34840a6abf7b699d156abf73f207949f4f326c4ad0fd4d09caef10908fc3fc99f99898a72fa3c37a0d7b882814a548744e4a55aaae407b5df3343f239fd854bf14b5bf0aa5ba45c21c6a7f0dcd8c944ea4112a0959446dd9fa47a1565f6aff4a0acd2003735e6391f65a0ac52f9bd32fc345256c0bfd948b602c01fa63b818c6b2d06f02a211287e59b9746a2e8af6d2aa58eb6ceda56269b55ad9f0b3c44a11205793fa39abb6890913f57325fda5f6c36521b0d47e9c661254fb864c5d1a59e5d40a75bb1c75bb9e8a6aa2ee6b6e05eb70506bef4af0ce6ca24af52bb5df93f01f76875b9bea4715f1b55210b5df83b647e968624747be33f30dd37d936edd87cae487a25dccd73e6aafffeba30fd68f839edf63a5d78e5ac7ad60cfb1b052db7fe8209f93d25652f220131065a1ae4382a600857eada4a4a198a07bea74a05f077590efa8828e1ec0eea9566d0f7caeada4a41463ec3eba425dba297e5e4e6dce7e9f3a4606da5229a59473e54af98e75b283b07b50d5d6d322c671c58afa8227155279c1120c6050210cb8a85015a5385185922258441942f203ad547845cb912184b8c1d5c5163f108a20c85042b7f20ed3096416355601000b02c1aaf04a0ca290e669fc385ef7d57736528ffec5213254fb97e3f83c1e0742fee6f7719fbee1a9452aea1cf8e112c40227f343082184703e67a6ccb565779ab529ffd9858bea4d5c2bd81d6eb50baad3d4af5932a8fefca4394a7d9e2cd45e2b5b84d94eebce6404ad47726c38438d147600be90b84e57cd1f0ed315e55ff72c4d68821236b129236cc2c464844d684213fc1d09249cedd1da4d0e3388b33dd3835128f4364e1cc6f1f90d1e38aca3539df26a04b039fdb1f3ce9398c4a2d79ee76d4e1838dbb34daca71235e8c8448db1c6e78931c6185315fed60005157ecf18638cd1852a460405810809546cf9899da5c62d6ac46a8c31c61b75594a961c434028e68c188649214241d7a0803c157d67a5e840870844d19212450f11e081d2cb2e2855ab8abad862071981a32f9dda5547e009a04e6d8da927ba50c20a5350d30d01d0820715b878f1c30bbc60b202165e144151188dba454b10a2c2d6124dca0ff55bf85ceccac085123ec16e0e413b104695dcd9f8d8caffc256eeba0f7e0888549f765a5b6653fd348ab8d40c28e8477d053ce79b9ea1474c4e547b0a80d5d4c1d9bc5009e324f562bd39243ec7249ac6eb0ce34f7f08b963b7504b11df414a29e50f5030821720615151855505952baee400042b60384141144140c8a658416b872794ae8851b4608a1e5e43614bc056498e3046080097141900200846528600c011c5ea1a4979c1f4a72ea5f420a548fdbef20c623a5ef50e54edeea851942f5555d7e88820542d46b9c3585aca855a90ca1f3dc98e91e00ffdd86584c414779ec244acb6a2c2671eaaa8f07dc0e7221d42d4f4df9cd23aa687dd42609d3f613f44a9eaee6024f6e13bbcfa767807d3c9ae7321d67e46e21dfdc3e2800f77d48f5d7cc5d57d2aef3b0c728783f763020ee3070af69f10af3aa0a08b650f5f83f1e32e60ba0ff57468534b5424431847da9e609cfba60386f43875db1eb7752bc4abf6262e6e409b5ad60cdad4d68a446b53bbfbbd9bb3b51765377f92be2852fb073e77c1406e6a3f3f7f6b7d6ac23dde0d55054880f3dd78ab03df91000bec7cedfaf2582bf575f8e985e8b54ebf00ab8f28b4b89ec9b471bc9e694d23a830fbc180f6fa3ddf89a153bc43015e60e8f6cec7e6f43fa13efc7df80560e22f41f14585eac1962da4173d4829a554819722546104282e20c2059d2404138694219a8a2a264802bc92859222f1e33671c5bc1206108e6843b8c088c88a14459cc00737cc2956469a8afabd0f2d1e27bc07f55b24a4badf2a1da1a56e171f688110aa68b9c1122d48e2c7b11c61a5ba472104510a8660a2080b58f0e3af4ae1080d848c8c8c5870c38fff0cfe85d6353a62ca1147645a767b365effad3d7f17bcef5437e7f48257f7d30b587517e00bd9e905b9dc524a97129a60fc7d7a6e4e3fbf8908b46b3f991e802350adaed11142d81b1d11449552055a943c95f24265ea1a1dc13a0248059de8eda671bcdef13ac5af52f114151d1d2121c9d87da1df561f7513655c0f821d4542f977773b082184104208216466ee7677a1bb37ef36ec61629cefee32fce973be4318338fd9c4a07c87d9c4645c8a02e5668f2d73df56b185fb37dac389c9fec9e35aa4221ecf3d1dde411f150f8ff612f0791b17949188d062988d8cc0a1327ba89bcd41fd6eaaff5052752f0249f522aaa83b447ebab97768da3069a8e7ac1046201f9610621d01a38a255354f80fb1183101054208e1a90796a37e5b0e8690d0208a2a290a5bd48c410aa0b8e0cb1660e030742503362842020a1f562084112994cc304b99c54e477bce037a315a70a37e5bc56680a562180d84a818851edc2a5b2c11e40589269696fc402427a298004e3a37fac333685529df291d5aad563b1ce105cca473a3d93eb1031dac503f1d15f2582a589c7051e14348045185464418c9c0880c7cf004880a9fc7e7891315be0f1fd6088aab2655da3a271acc099d60590192a7763a1182110864594ae221b1850d50758b8e0045b5d28c8c60c1beb0d59f856ce4b141961bb22fa43d66e6863437ff727333fbe021419613b2b44186c8204286ac8fca0b32a48656dadd20429a0767dbc9757f743e8a509cda36b406d55e3f1fd12bc4c7d6bf0fb70b226408f6dfee90d6599e9352f6b129fe211c7bc41844c8108fa73d5e0a524377b7c733a4756a689d8f37d03142d426e1ec7791cfb055d7263ed728e37d5a4b323284ba2e33011b5a49467e508178980c34b763f4fc966f0f4de5e5d5af9432cbb239b1971c08b03a1f62523a0c1c06ac7c9a0f8ba8d4dddd2625e0869658fd9c60e0971d10afdf7cc871434bac6a9f1c306374b2f380a972c7455a45d0cf592c2424f41d47696f635ed39eb34d487bcb4166e64c2205e140b043b86e0b5c5ea406b3ede90598ff1689b7188eab0d29d4a789ee2e96f6564224ed2d0cea20ab3d56dd77972bb5a40775bdbbefac2f4b4c7e7ddaa769e6b7ab756a7ebf5bbec3937ea8cfe3a11b43083f20f3634e3d32245de602e78145aa1d8043754f351d92b38b9fa6fbe6ce74dfa4b4fb509eb3f6b2fb78ea56f82d1598d36bbf9d7a9c3ade3856c241ededdb9801fd980933691d5eb5b73f817c27884dffd6227d80dd656dcf4cdd7724eec3ee746b53fbbb9f759f2ba9fb2dc4ee386bdfa609f473d6909f1833289d018d988052f92643710f399cbfe1792b6954fb655996bd7c9f6ea19d936ec6dad4b6507b926b26ed2dfca73931804895add6e12a65c74aecda82ee774ba875be5e62d24f5a67c906beb7eaf2903c3ecc25a04832c00717ea4a5ae7e101647436fcfb23e3f77765644e640c3fdadb476a269dda8f5cb7322c5a75f96568cf480333b4b7fbdc7dde02121894df1ec6098912c3a694d23d761edda5ecba854bdaff407dc6d8b967d3e1cbce3d7670c2d8cdb781e653e343b8df8120aafafd00a26a61b98f85baec39eac28f4788ac0e39fad9cc1a5ba8df6e0fef3e8f70dbcb511bbee6de428d1f2b6cd96d1c39288c4125a0220c5457f8f0a8fbd204384cd01d361b6a7cb871c2232a61c224e878f1e85015c173e2068e14aba3bd8552aada833096a0db9768eb1be37f83aaa11bb682083b1d081dee8e7686ee0edd89488e476032bb2123460dcd8c0c8d81396d5a669a37ac563c28254d89c51831d61833bd4f9b30e639e7d6850959c4706664680ccc69d33008fc40a2800c491998c105a4cb15083119a974dca360e3841b9b1a34503364c4a8a139c26225090a5ad26a11212464c4d0901244444d141555393aba828464455252162e97164a4a4e3049e70ca537287884224c663c37363568a066c888514333234363606e58ad8c00011d61b19204052d69b5881012326268688a92942c624f63604e9b86fdf8142dc0c711047e20558007ea42289b586366d6186a52ca8e060d4229a536a1d4349865d9369526f59245ccaba19991a13130a74dc34c33d6d025284609b1a247c1c60937363568a066c888514373c36a650408e8088b9524286849ab458490901143434a101135515454e5e8e80a12921549494a2e083119f9f52f145e365e27bc6e5e36af1a2f1a2fd46bc64bc62bc6abe645f39a79c9bce82be605f33abdb697f632bde62bbea494d2a7943e6334b9c7d8d160828ec5d88ef1438fb086eacf32c687f26394cf9551e093164c663730a74dcb4c533260ca528cd221364d28fcc7e3ff3b00f7df1675ff6d134b2576dca360e3841b9b1a34503364c4a8a19991a13130a7adcad1d11524242b9292b27069a1a464d2dca4b9c91402697a8dddf421e82ae794262d738f99d330db7bcbe0e61daf87612eb3cfa64f475fe218260a26b3f87abd5eafd7ebf57abd5eaf215f6e58ad8c00011d61b19204052d69b59e2879c164c623be6cbc4e78ddbc6c5e355e345ea8d78c978c578c57cd8be635f39279d157cc0be6757a6d2fed458490901143434a101135515464456935a4a3060dd40c19316a686664680ccc69bb61b53202047484c54a1214b4a4d57a22243444833324209df01f8f8dff7a78d838e1c6a6060dd40c19316a686664680ccc11162b4950d092568b0821212386869420226aa2a8c8cad1d109ac19f9de29927d111ca8093c8c307ea0c4c741f50365fe37e99ae69ca1f406857a08461631c905f3bc1c2a158f2e72c5ba2123460dcd8c0c8d81396d1a669a37ac563c0001c5e0998586c9ec690ccc69d332d3941590c115c3d80d3730a74dc34c3332c0c789a50e8a68fca7aaf15f0e9bff74dcfca7c373635383066a868c18353433323406e6b4699c24286849ab458490901143435388888a6cd85484c92cbe5eafd7ebf57abd5eafd7ebf5ca5eaf9764254a4b344c66386b9a7386d21a2854f7bf82e7e550a97a78787e7c8a16e0e308023f902820435206667001d90076b4c264e6c557cd8be635f39279d157cc0be6757a6d2fed95bd4caff992af1a86ac260ab89545ecf41f0ae6bf1a31ffddd0ff6cc8fcf733ff7534ffe1d4fca7428cff3c19ffad30e3bf1ba8ff06a0925c503364c4a8a199e17a78787e7c8a16e0e308023f90282043520666d8b86f034a34045912c1901b562b360204c44798c54a386806674f3099c535cd3943690d14aafbbfa11487344cce68ffd56cffc940c5d7f6d25e3550a8ee7f054f7239542f13cbe70c9a583e67d004a18931c69e1707d65d319618f6d8f27c3c11091aefc8fe9bf03f53fc6f9bffc198fea3d80e5ed39c3394d640a13ac6c138fa2197c734886910c3b48c7971ccdff9b0e7cc76fe3a176fc02e4a68b3757625774787f0e50eddddddddddddddddb0218410368410c2861042c6b1ce45dbd581d07b4743b82b3dba943a107a775dbbbbfb61175386685d232f5a35092b758dbc4842d535f2a2c8b99bf93eeddc56ee36fe1a6817f3dcd1ae06a89d1e06a6b379000dcdcccb3c09683a09c8689d4cb73f9048e537c51334c524787200063f21b0deb4d7031802a5375a68ec4e0dab68b375761f528f54833176b32b4177b300f573a5947008ab1c080dad8290c3135aae349150828014456b28b5f2c5529ce24a500a6a50b4260917248104126134667d2eb62fe1c77d47653bf3bda34e3d078d7f5de030ea6eceeee6f4360cd31b1cd00f35e1eac7dd77329e1f3304414914b7ae11e1f620b07824f49b3b284f39ca860a14109e6ef6807e1e8fd6b9d13ab1759689708344bb0685e3a9183e0a158361ae61355840b1179a4e87e93e140c46f331cc3c8d4cf779357b98ee9b35bca7b29fe9b8209a51ad63de61defdb5a3fa09e98a3dccc7701f911af37dea11f31a9f5c680e7b98a732446a0b8995761d04c6015333339853f7cdb9751fd53a1397753b6fd4ee38f6625454b2551886614934d635222af2bf0423300a2459832d7870a4a3f84f5d28e8f71ec70382b40653f4c0d81596cb1a1cedf02fc591cad403742e3249f7a6e7993ac36e4c4079e3c137aaea0bc0fe7e00c87e46f9180facced5d3a90767ee9ccd1b5e53e1110015e47cacc33887cec59845397fce2e4ad3ab4c3894670df40ab7530fd6d89db9adcced796d08bdb339a2692b6b6b304a6c9adaa3c466a6c9530fafcd4159db9db92f46754ed5ad71a76e9d4f067f3042df1cc6d8157e9dc39999b48dfb6454e7beaefa6b9a6fdb729f7fc3f63e186fdea142a8c44cda688296d10c0000044100f3150000300c0a870342a1509646cab03e14800b739e42745a329ac6b21c4761180419ea8001c010430040000666a4d100d0df795064311ead65739644e40f58d6d7d089910dde5a6450c003bd139a4667ee089c9e430be73a8ea1522c421564652a7fe88484262d4c7de3442e4ef095221ccd1477b1c01742d59e3aa3c6e52e799b1cff4561f37e28ac7fa8340ae0d1068d3c4e120163cf1eea548dfe0bf4beb6593d54af47777ca04379f0fec2dd2023f1904834a15428953881b2ac036fbb044643a4427c471ec9e520e7ad5707e81ea4bb5ba0c62270eea9deb49f2f50599a0e9c6d610d24017d80d3a1af5301defb70de48e57814baa059841ad02f0f1d09b6542fb7009c8d5a04bc01bc8bd6674c5c8a265273a75554ffbfb4fd99a50221497fb1c0cf988a351ed7adecce5f0d696c3c5a6d394f4f1b5f0caab64af889fab749fd56ad7bd06a8b72d83b1130f942ea13c1a34ecbf1812577ca9ce88a0df98353b6bf924231a30d45ba4a4154174554da53a0e06540ddd5bbd80bda6c712eacb2d393e50256644ca044672ac681d5721d57a3d2b36f5405d131f362d02d360f7086530543bdd056e6eddf7109841946237ad1030cc04927252ec439263842eedde300f25ce859e460148ee615029f20850064bd01235ede6813610c8e1b4cabc0b2d70d78539d0182fa1f40e5e7ef2d82ae070ca3b048407507d8f2e7f7f9068c31a46d093d8b956fc0840cd5ae720f788626094a750bd69c9ab65e7b8035c270dd85ce8e1f1576e0f025819324b9ac586f30663ee34bd12d18f376b7cdf40cb4ed162c0d75acb7411c4bb89fd10db699bd050b32b46edfb6b6070ce27a5c241fb61d386cf492097ccc059af8a3d4e4151d119235a768d469e6e1d565b567de6a635441691c91e989be5333902c92ec5e6cd308b17a601a8b6fbcec7c83d96cbb208671c05380771a8d0b0c3fb01ce3f1dc746f70d426ba00501d3f5dec5202669d090c68718fd50413988a3736373ec778e79210a88896951cd1e514514a581fab28c82f151e4da366e94782f3f1cc0644c571be3c65830a381fa0364adf7ce615553fbc65f8f26a72f4d6fb6c8802e65f61435ca39db3f85d08bb1f76ba6c8c3e0712a57d29c52e27e3dfb34edbc418de55a8696282919a75d60a099e5817c4d81b227c19f827ac932a455832c5bf4be1bba015ab1742b8b3290a2e9b9c2a7537624ee69173af4e34ab5c49b99b3acdf78586521b7d628addddc1a36859b8f79b8217b781c8f9a4b6b5827b9a1411aa811218fdc68b5c1c0678ca25b5081c0e3152db45eadd4e8cbd613bbdc991b758dd7a5e765a9b0cd76ba891f5c209e04be9196809ff53ff3f22632d9b47cabef33078b50740ed0f2a020bc8e1eeafe34422364d82264cc67994897afd3465dfe7944a7bc6e2a997e58ef464802fcdee263af90836a2f6456aba9cc6bfcb329d99f17daf7ff2a909b494ce5df9bf31ce92bbc7ccb9e732906ad4ee29a87541ee6d76349eccf5c9d7c0814262d4badc4123a4c6456da52462decc5c576d7df7858901dc2d0dc4e88c8712637cea1d1c82ade48f21dfc7ebb2e7e574b54cc80eba0634f80db44d8dbad324502308500c92e8eef143c24328020d45474dc3a0cc2bd01ea2880f1be68222e47278a9c6fc9382b08621dc09b4729b04c0f0e63d240dad460d77ff87d4595350c42eb728852205a312d2d76990c9f1c8589fe4f5b130e2194834cdcb54bb388c7f977d7a138e3d9ff8d5caeeb69268aa4b9fc0d43bef0c71c3dbcd5f1624ca8388d0b2805287b3f16fd9a66136baef238a7c3280add275d7ef67c499c4f69a30e7bc7f668c2bed1f33779ddbd78c0dc26615d33e7df328ae240e8c8fcf4ce12012810aef8ff1cde2e74124317de6ca06c6e5bbb7a182ad3c47c65067bf7135b1ce697ff70979407b833d413c20ef708f8007b0371c9e8db0f939ecfe9e184a5acd59e91c9d118338d091aa496bc48dd9d280ae2e67272dac33c635f0a1ba7dbaec6ca50c351344113856a3a59b731fa4667cc076c0158307c29479f835bb30b24c4523cb12808b0eedc8e31e260a18082814243048c902d9502516e584269c4fc8e50a923dec5ff2998a842f9ebc68a435962aad1b0ea76b7e793a917275110c2741018cb14be13ce6526387427d424f1e13ba53b4add431c9349f50b3c61f3d8145d6be94aa1b38a8e89213b584e434a1d253546f1617772f5e838c071b9127e40ace55ff197b06f35d83245b85ea3de48088a4bd2c4597f3b16fb3a7c36cec6d56d7b1ae3ad5bdee3ad791f54533837cf5a69a32628daf4f4729ef062f0a7dba733b7c8eb1d214936c081c953662ef163e7f0ef3ac14ee73bd0ef6c2049e1b0bacc8f69514a06b623842675d886316682a7fec877e8fdb7e0ed0eac30edfc049b69a97f05533d1fc3b387705fc2492301889e45595ae072f3c8c7eb33e420846a20a5c82b23668940c50cb45c23d467fb1ea415cba14f47f037a70ee9f8735bc0da0003facb729d6a0d0c8ec68413ee73e37905d0bc6de20c572880da62e3251ffb982d41c8f2e90906532fbc2d2a7d25c6747b0511a9653a66f7ac49be061bed33989e6f61b6c90dc4be5bdc3be29e1aeb4c1a9ef16a63e094e6390511fc83e453f3ea7c9c69d8e1b4b8eb7185e9db238b3ebe78810e2eeb7ad202bcff13112d149f469f42474bea1ab1b469fb67a6fa51577e5b8ba236d484af528b1c59fab24742877332ee36c2b08022aba9cc045685f7e274b3ab3c7df070bf839f6f3aa7f978549c77b6daa1de3baf7c5921f4386ef2ff34066265667e2c45376700089aebde43b96749b8e7fcb4a5ac0119fc22ad2d841b284f0f63f353c88fd675a0f69aedfa8c5f655b6caa866868907981936b27d7f19619686d503d90c97178534ae06ac93a59520ba355be79ccd1cedd67e15497a3c997c443ec9658b23f1b5f79164d52132b29a612c6723d1eb5ac9b8fa9f21e760f1d2c142b937cf45b41e068fac75015587e3e36fb249677e54df3fe83c318075450f819cf4f4b843a55914ca0da4ea296f3f1ff1a510220a5d8ec022b45f7c97acc9a07e477da97c98ae1abe554431dd3ce5725b9888549e545ba8ab119cc04468b444d5f11ddfca9cad6e7c4eb6f3b9105d5d57e996b4191d0e3407e46d068b36518a1d6ea0a3b52f7d87251df3a36fb24a03f7c5db8c5c6302b94aa5206179293f052dc9b3eb7aabc04b4cbb80848953ed738a210d7ccda514ba9cc1476f3ee61d96f4ddad8ad5514f76c840e205457dee2ed0a9479217e90e66bcd8c31079dfd0bd32111d8208e3091f20bf4f6f4332f128b596086ad7af5e09de29efb4e7c07e657f098fbcd34112aabe7b9fe07accc09279f643e3b8541f16f5ba4f32019bcd54c1eb531607796c2d789e33ec72c23216461ffa8ceaf9fa3d866307c0a8f6e8ad6c5a0b1de239f2ed0d923623fbbcb1dd3dc3ab19e1a610974a85f73b73ea315834fa5a42b2f7f88eaca71d4ba180ad2ad6c5c77d213cfc5d4a8cb2c3e633c4f38b0f9052ec8ec2fa0ec23087d612a77d41a8ae25706f71d8146c30a9276ff05138c6b008cc40ba9bf70f9cd2bc6ec67090291a34232045387e6d696d03b96703894d074ec635d0c15fc9cb736850c7ff4f6b98685ccc06e4eff2dd1026323aeaebfaae06e2b6633b998790b4bbadca9fd68700d9338de539cc53839f6a9a8b721d880bd8d9fa196dde968eef8e37297c57dbda24f3c4def97686c7350027db4760ee218dbad664b4c05b1735c77cbc73b876000e0dab04e5c7a3b34a60dddbb1a60214d874e29bba9155e5cd1475faec963ad9889d4abcac18a2bda43ad971332e3cd0f961263b155b63b19f6689acc082ec27d2b72a38bb609078dc85bd576045873172549ad4c600bc0e1706b5bf3426dc6ca74812025330451d81d8dc643a16817af9e13385942e3c6c0de4302501a43bb027fe3d18b6e156e19aebfe6b0bf6e3bcfd181cda0ed654b92a432ba0ffee05f613fc4bea680446ad040f6d0a2ca458b65c050769f94b1fd0ff492b6a0b6fb50ef3bd9063238646f96ae75a893ae15709aad73aacc2b8a7b8fe44d356fdf879ec319ef9985ad49e17cec4538b4e00bfb2c3d8495d1480fd1f1752767a18ee8459454fce06c8b02450d6dcf50dac32e951453b0c98e1fe7e2e3de61566cd3079f4bac8fe404acf07237d0406210e2f7f800dc2534ee689f48eab0fe0af6fc8e60a821759fa76383dad01a00b11f260b7cbad6079e05eabd455f88e608c3615ae191cd60727e7de3b08ee483d60689edf9bb43f765e7e0c47ffd1047d13dac247f1268d2c8e17a765d6ff48fc1fed4b3bb443605a5ee55b42d54464497785436a0f74fa4669dea8b3921012c73af3327dc75f82126a68fa36b0e8b059e0de4009fd6a20d715fda45227b9c1d82e37007702c52cf5cd929535a777269869596a7766301d9a734b98e9a8c8701e3e1711199a014feba06b91cba96b9234a97e3fca33b91d122db83a8d142202854ab2a3232761483f03f31df63de5cc63b58cd8b61252b9507ef8bddb540dc9d8b776b8b3174dfb65a9d626ab4219b01b1018d9146f59ce21a0e7e386986366c79d2552b165518b868187328943d20724055a8b1cded27b78c287b9494110f2e8abfc61794176ce0f999953ec14992ccd6cf66c85b5ffd798751f23458e4c177ae9fb3c30bad18d0ec743b40924b4a1f2b6e89250695e6d01935e3c0dc9ce3f2c61b062e739390cf475be0eccdab1655502b070949a5f499d59b9ac08c19cdf28c1850e5d2d072bd0eb64c442f1810fea0762972bdc05e69f2dca7de0a19b63f62c1f8bad0b49ea1be7c963d85f51195ce9f1bd0aa38a80b89c22216d3e88e7012934b65db8af45671f79eff83301efe623453172d0ba0cd103319619761f18fc4e34707d1358c32015e5b58d2e1774988b84db402d051572d3f9497e57ba965e3e3cad30a43a1f25ca25b43901580108d3e6170ce01806ae1a09ebd08a04b3e1169dbab9c1aff4cc3f15fc8e5e0eb16fc9ca8d0cf9fe54a5da107fa6daa580e007636c7146ebfdc2ae45a23059b58d90f203b48680743b458c6fa916e3a368d1720cdf07855e13bd1f53e9b0853754827d16f96e2d2809ab3cf1bef14fc37c0d36679f5a0ffbeae887b99a3959b64faeddd853285608e2fa0c9bd35febed6fec065e0d983cbd8c187924757664a8a1a52b2b8239682a5f8d825fc5d821967a86390d3f67f8587cc2a46a274bc406449f1052635c7680e4e9e36f918cf459759ebe40f5cdb278094c7a3fa635c163f922471f31902eed7d9e3cde038abad625f7da1ff2405cfb3bfe5813747a1607bc223ff872c27663e9c8113ac4b81c569b8adb78072b471bf497600deaece3631666a400f9d1d4993bd3ba4a98d7598443f529d7b2949584e26a8740af14e66aeb2974dfb2908a269e07a87a3b6d1910f956deffaada1417e463f4834feb0eba9bc4e7a58344bafa5de615bab8fbebd6c604a6c28f82923c423f7b18ddbb6c0d221a0839a40a9884d694588150112893a8a72f7c1b4984fcc1f0b666d4868b0b6f46490710d47defe952a20bbe4d46929a59086fd39989c6ff4e01f33775112dd31f0a1c6b9dcdd8b1e40c4aa7e3578ba4d29742bc796cc7fff3dbea996345ff02b705e253a16c7400479fa29d257c9024a38eeff5a36f386354167a59e86e7d04bd74238bc3fc1c020d8ef2eed09bf0c5ab03ebc5dac26c2670a53b3545997577faa1e952be4843c1d474001a87bb15bb92d4b919809bdcaaf77a2427b0113d4248e67253782de84f09487167295a3aa038e9091ea44ebc09b7b2e963b73bfb07312b55255e0777fb601fbf2ad561399947d7b55b36cadfe8c39eb7f07a606a4c348d4dfb7a7fa07c989f72739c42e096be1e0d3c35525f4799d89fd26273a32b7e1f2a7e01a83b7e4029e125218276b6e5568d97e7d5f78c449bfad2b66c991452890e2ec07bd555f5c99dd50e59212de51211af6cb6827cc6f9b17381056e433a18e4f3c132a4c6fa4708fcf3078143f80a1559a6d8b9d10a0689f4fc61425db27380b00e6cca6a2b6047de0ad42af6b6b7dd3adff083c0360a6214cd9e984c8f0a48f8bab19ea50a2b93d04bd972a3a04c05abcaa20e346732c91b58a5944c1505ca36ebcfae15ad9c5c0ec3f3cb6acc271be14d2927f4afa1282d5fc7673887d6c08a279152ec4292fa2a7e6a99bf32885ef541c4fb45c9d15b181c8511ed5c9d4e44d8b656374146cad1bd6c602b892a816efe15f4234266ea2f44d45a8abd90b06e54939d05055bec508c5b76017b815c893115f164dc0b9432d6417efbd137f214c8b4370813d997f26f2f0e8c44ab8ea402d6defb8d32c1879aad03afdc204fa3b03a8ad00baa06f5196b4e0cf582ba8e2c110856ff46edd4e524583796744266489d9423e3782aa9b25c515402456613775356028df4b348b77d9fab9612c22285022b40352346627a1388015821ea8a747d8ded86809e215402a9017250fd61b106f216d465a4c6e822f930ae7f723dd1c57a98dd91bd0bc3a94b7d01560477c0846323eba415220177449bde08ed83a2c3504e52958936ca2639b5d8e6a71ca5669686015f1c645e520dafad39addb64f3cdbc510127c4e495b55201d0afc324cdd87093bd90b6f83d0e0404a25d82dc0ec66a1bf7b601f80f65bb70edbd5edfeba657cb077f4969d7c5e323f5719a7bfb8662ac24996a8f4ff1a8762f057fc9211fd0cae983efc840ec734e605f5e6509629b18e64620206d58253bb22bb03f480e42ab31a796e0f5c1ef57625322cfd184843e6bcc2a34c1f58df7222a7c3790433b7b6cbfd47646155e420cdf42dfc1a27c366d1293ad92df4a7d01a81cfbfbcc98a3de66d935c8afe64335bf1480a8af8d443af41b0bfe830b940a718fa880fc74a83a85b7952e907385eef739858601124a21e0f9a4c898b21e04eeaab7c59315e9f40da080a04b94bd2403714ee0caf44d35319f9ca87e1a6bbb5f7810a100f957f95485e85713654d9fed3233fca2ae425c10475ce586bfbd55db18562ce13e57a293b11411363781cb950e8e70e38012d6c02e4f06251476c14a2a30e21f8d950860e20028f8bab834f1efae6c6e81c0ec8f4c834273398f06eac1a065717b58410a03c109633270851a1f7636684415fa4cc530efff811b7e2571760fb11ce09e797d0e599012013e376f2e04c6c68d47cf077ad701153f3ea2fa478348e50fcb2e05a1218e862bdf9616d603a8fdfd2adff361b9fef6f8b19edfcf2c1dfee1fc8e86ee47573d83ac16d7c871877bce5751bf1fb09948bf858d43d70f96347b052bf2054008810e84037daf658b41a34c7fc8678abb1b11eb37b2de8f8389e05bcf1fdc816cc736219bae92a531fdcd671b2ea485b6e56463d81ffceeaf34ba5062eb537249b136b9564b2a83373bc8e934bfed3d8c66ad83f0e5d851d6cb23c18832a22f4ef93e2641ddf13c8791f06cb2eb2017ae72138f89fe1c85f2e57f2438348e6999c0ddd0cdf5438622640b6ee30796a4b4d4fb517e425b1aa9de428eb56285414240aa00a418661b5724124e29d1f08af8f6dbc8e834f574e6c085950342ca7209794a794e14fd583f4c807860e87029112a7c58db8915d116499f0af0a1cde3867eba69ca6fec51a16c8bdc9e750c35181d4f7b54624dc6692a9e083ad55f69109ba79b2e5a7c8706b0eef5df4ed13f955833c4d9949e2e0d44894694540fff8cf50e253a26f4c094f4a176a162a8ba2be16d894b1c4dcc572d01e04e8d3fa6c19e007a0886600ed5e5228041dea74138ce05528cba891a70b935ba81b208001a75e29d2736b124a85a6ab50e440b147dcaec352575167876bb07fba9e06f3390bf2e46039aea0188f8653456b810805cd2dfea5fb7bc626194c123e97da879e63bb3be3b830c86661dbc29d29651e32fd9ef156c95128652ab3a47c6b5fad20fe61d18d1220bdbf6ee0dedbcfb5f549e765ee92eaa151908ef72b29baed959920547f96ecf38fded3dcb668c9c703d66f37efaabc092fdb4594b46b673589c67e4df6a2dde5c9525290f9272ccc963edc4cc47133888733c4bcfae643754f248e984f476e63653a7623808c32001a191fc4ede45b04afa77da9e2524d9b7e62f44bdcf8820c421022116874f40f2338146e3137be335016a8271ba53dcbae0485fa7a147dc45253e19c2995fb68b0385cfa8ee0e7b51b0075bd75d651fe7d3ddcd948edd303d9dd149c607b32d0a90174e7fc22f4828b0403040a1d602dc520ec239a1f6ddf523322c9fcbc2ea02585f92dab2683442d7fc519df92e6f0b0a4925c5224814128538ab53962f56acfb068e00628c54edf44a2c23014629036ac7211e07919f61ca1ec7bdd6d8cdc431e8a17b15f65268d5c5b46b84bc09706b708f62613b480bef02adc1b69a6d5bf041afedfb1015a439e3fd36516eae779024b7e607e1162b1ff69c5cfbcc25210fbe20257827019ebd97ca25b9d8c639c913c715a18636bffd41acac736f050da63012a53f6c1bc918b21b064008b7c952b505e20fdf3e6ee9b3487d92442d100326ea15da1162c12039e7aeb3698754ee526c182ac4b327596514a2dd126671513eb609fe787f0c98e03b048881f613a66cb636ef4b84e0d4066f1608439be18e23e60e42ddc1d423ef57f7094587c682ef243319eb1523482389fcaf2eda2e0e9a635ab67b0b161d8c4612ae2a8f7a465881762b3bdb04203e209d4c3a5a1d307d42af73016e1c4a47140d305e5d3d32df9b6780e7f32a5b7b7532ed50614b8316ca9187ff652744db4e18faf3658f9dac1000d88cf283a3fd0e5d6ca229599e6aaf4f8ead73048d5c0803e47a5516e8e80defba2929ced244a30f268398d2e6aca80367861d605b2b6f7881fbc4bb86d0b6c03bb4704782251d3935d398863df66aaf9531ab00649afa44bc478bde633e739abc7aaecee9387af290898538521e1e1160f9429f50769daf79506e85f3d8187c20d89555cbb5c398c502e376a80b5a5d8f853ae59648e99eeba0672e121da01c3f72783de68543eb9f24feb5d9dced675119f758ef2efe309099d5c33536eea5fa0c5960b9ac90694b498f4eea9ce02ea426d14a9b1bcb9bcfe8f743c69175a6d5f83aefd3b8a60d932c24bd6bb1945dc905949138db8b41f2da228efe3e7ddc80a689e77d80398d4b50f1a414ab513a3f9abc133a91623d6d20b8ebfd2d65d872291099a3fb4c807b087aa0c1902bee1e3501cf16b2ca68898a045e3c70974da81b4a0771e81bd8e54cba3ab409e92923b0a21a569701e95f800d2f061c752c3abb3d8debf906bcad8b952344817d84a486d4828201d84bacde7a8aa2948dd5488446ed65d34c3747b3631ad3e9f16735ffc227092f697a856f366b8014bc69557d6a7062139dfcbafc5c7b1c36662ce66ff97d3778d183693ecab98cfbe941602d6e3c60bf6a9388147a45896e29d4925fc7e12c72d32a25b5b256206640e8fbd7224202e584188f52ac593e13fdc7d4a5e81337af345796bbd65894e0b5e86c19d9c8bba0663dc6bf4a4663b167bc14592577c803916b0d4e2467c1a2d12ac1463744b18af81d28cbe8f822dd51c1201c4275d6b79e3acd08b00f475003057088abf5fdaa30571ff0e62e419c8d086994cb5163abb796e86943142a8e622a6efa5ecfab5041db15e26d1ffa69136f7ae3bb9eed26407385b72a02e81f3a2daaa6e094e5208c38a84624c64f80fc81d7d9caad4b0aca4953b0eee10529b4a4272f97ef05b49630273dde627f6121e39afce8e575917826fcc131cf113fbeba77f2a265f3eb38611c8a5418bfdbd916cfaa06f4b95fcc0ca5d1c6e5bc48cc1e5d221e3664f098c008ab98cbc929299bc6dfb89ad30580cb2d69830d9a885934218be71570c08f8aac52d4e32de961368063aa7f72a913d98ffa0e09c9d377ac42652ecfd29241ee6369a7b3f4b167f4d7799ac227378aac77aa1c431796c6cc27a422919556157f6f7e7aa17aef1d26239afbaa735b7f0d4941546e6e0bc27bbd4b848c2b55e3f7b7bd639fbc31ffce601225043bc09205b307f020fbf96e734a7f9fb4919dc3c30f82b8b63a35c88b6e007f6f82ba89c30d72238288599711e000fdfd84d8d05ccce8b02f63a5ef4af63764936f48a9b443fc621cb15e439b6043e98eb58d7d2ba85d0ca354025c35524c05393588c75d4ca621ed4f66197e893b5c4e5b04b9496ea94334d32c446d995da7c8d59335a42c5a0d4ae1cda57b3c3b5b6d2c68cc873e056726b059a22e7435eac8a5889bdd2d456c6bedfe386bf519ac9dcac4740f7724eadde37cf22de3a243b3ed97a4fb834107019bc8b97092036d60095ef0eea350d29cf0be4f1e6dde1691453de32a5469d2e675f91846284cd5ea828a85746cd7346f2f8426a1c19df7cb0815d9e9d35290772034f21c756f88d5006de821cae6851125a5da3dbb48b8725fd3a31a25c3195c2e8ba295521c62e856e66d7b68c3d232e2dfd4170939f514605c2a22f3766d52b5005b92c11cc0bf8acce29789eb33ac008f303651b3ff7c645650391f46d4cb1a87a743c524c5842490101eca0a0b56eb74421541bd9f0ac39683603c50c99efc3704c9bea057ecc451baa944b0822355e3f59108a67da51103337deff97dca62378c1d07f17393775e3800100e73e5ff1c24bfcada77d9fe10226546939a9d4b58d995fe679981f0533832bb11a9dd3eb602c7f65e3d35cb15bed3a6e1d8b11e1aa40177b45ed3199edd77e0a2efd584996c46a8913c6fc8589617fdef789253f9279e76c161cbe4d3a8f439c28cd963cb8163fab40b9a0cd8de13abf7c826040a5241fa1a8fd1324eb40906f1f40f9188944362e983d39ab89b87d622df8b32b19c1dbc4d97c4fb9b4cc287ff50de6ec9487bfc6767b2f1b781c853c9f139e1dcebe1c1d7927514b68eaf2b0abeae8c442c775a20598b91456910c819132d19b30fdce330aae2ef32a90db888ef5e09fd5d747c5e54dbf130af6c29677c120ea94e61812530b76b1e60c6dfa31a3a4230c36a90fbfc8846bf932620e3e28c4e863b7992012014668cca1e7346d593a8ac436de93830077cf915f948917d0c21ec2511d831b32ecb99c77fa3c8bbf8542b01ca252d179aed4c24109a2bba3fce9bce75b80f13a3ddf96af9094e3157edf29411e677afe1127188f442ae59a6a5d2ed26c9cd0ba8d9ae3560790e40cbc829b4950585e97bdad5b3908e9a4e108f6a3274fc7915677b74a16091904c21b546bb01beb7c3fee40c8b41ffd07bca9a1187d1eb35cd2cce60b1cef23d58618b255a12ac6a5f0badb9af5abf40bf5d0f201b91b4b9cafe4b0d66c60f7146a6ee25ef7f750a81d6f935027d00ef70e9f48b75d800b0d305db87b8d9e39a1d152fbd1abe6e6dfe0340c70fd2887af99a04a36f173d5ee7210aaca7db26727a5e5e261dceacd56ffcf3fc2038d07ef4a93c12699dae49de9079616e9b38dadbe6bd1c3bedc967f150ef4f02783de16eb8b74121287049024fef877ebd8de1682faacb7962a8fecacd5370bacee8c2302191b0fdef9a79d4a18cbe5ca5ef3bad66fe55fefd8d9dc35e08e8bb11ddbcb9bfbe9b122a6b76b8dc66d99a064f26faf3096a4de5465ae8dacfdbb4840f9334475b8357d42140802c104bbbfa2fffe827859ed650ccbcf5c7dee37b288b34158a355889067261faf5af94d6d2e2cec9caca7edc1662e68a74fb41d9d5ce39fc9fb38539b89e1ad71a7fe2d658385463eff43d63f106f2171c92c559885af31c0ab7ae85a8e031208e899844278eea1d3da9aa5e4a94e8d9b73549ead2085088f13bdd567e1e2e23923072ab76220881d015e3fb390687449bc8ad829ab6286823d18bb8440dbec107c72d15f281e316e4c0548aad0fd8e9d7e7706a427ae31db9c2732d4cfdd671cb99d5c7ad6555f9f583a60930ec2da44dc5423637f9a3b655c284b8269e9a0fd9e446096468056e7a6d598190398341421726f366ee0261c6d0ad1203198378f1881d9ffe0061319105af7f2f9fe91357cb5033999c0c0f32496a4cf22878368556c146eb1ba76433e3921bfb36dc09e1fa738ef170a634a64ad9c2c93d485a7dd7d38a0895d936e29284af993f425c224c3c22c4916790c311bd120eb970b43307cb9273c98501ead19ca024ddca20ed8a78b797d4b164b3223f8a33790f94f1daaedfcdf523808207e8383cdfc5fa2913507d176d1630a08b2f8a090081c41ad355ea28606ca789cc2aacbe421e13bfffc564ea6e245acb8f7514f6f94da50b60fa95a3d256697aec17c633e1ab278fb4c4ba3acda78ffdd628cc1277f9432d2c681419a8973500ab4ded261d4b42b3edb16f4198e89302822bf11727fe79a5143b43d40dfcb1ef38a6ad491a788c4bda413bde92ef99e44604db7fde66a0ef8a3c4c3a30498e5ec09b2a65abbd1c10e906853cf88a05119aa502b8f8fd03e9647c81e1887ef754ae16fbaf6e387db28643b8b66a4e9e7faf31d087f5e1350257bc5c802a07998bf666a33fc3f60b28d9835dcebf8c5809e622378670814c30b9dd457159fb26b53e1c95e168660809b882ddc1c3d4549288db429866d34c76220154ddd4ab9e5596c13f60e13cf96bf5f831737c841c40aafa8e905cd01d7ab146bfd12aad52c7a685d8515377ebc87b2a9b1c3e6a93e0ac6a54d68a86260ddf635adaf7b09598a7d21c060a04e0c9273540f449264330a1d8d3f2a33cd214b7d4b76489a8eb05246f31457281204fd66872eb4057ed6284e4ad0f8095c26b3f54a17ebbbc427c68b12586e5104ff44053f4ac1f17aa6a8a59a4567d7e30b5394b69ac5dda4b22ba27d1c0251d2e60f627cf05e2c984cf15f84e0a9842896dc232d1347dd9a1fb0cbf58c2c039258059c52731179d1fcb8913b1c1a158858a408172d333c2f894e7877824f0b1507daa2733aaba1cfcbeaf786dfac0cf36624f6146a8173df41f0daaceb7d50e5dc2472c079d5adc8361313e20d8f575c031ede05dddbb7865d982f272dce26837d7fb61c6990e77379061ae6ce5b9fc9f3355bd4ff9a7db73f940fdfe0eb48a1152e89c4928723e9edc14bce90b22779c1b72f36bf24856720cb33f6a809bc9d2ad421fed5227bce558898c88f3f23114a453e7af0b0a760f9483396b571a37a4f255037ea0b2986f9a1f398bc80869d5d7d1de832cc72a10645984cd021c490e97306ef075a842a3791b2ca4816f3d23733ca16e917342c1203d8c846b1ab1be32eafe49924c8e1b81489d892215e90c59026138d530d1577b2f8010057e218bf92446dd05de7d6b9678420e0ffe060cb9412de06edc13e639d5a655b08ac6e3523aadd118ee1b108b5902e9f0cd94f59f9b21b25abadd84112b91a60ef254c0f394da418e64431e6eded88c865a976ae8b6332b92e02c324ce0238aeeeb94f1776b98401f04da5fb7e167fe892753678f7bffbfc1829c2ad70529723034e1bd36b9ca33716114aa3b90bd8c13b4fcdb9fc8a0235715a1463a91d7ef8814842283b1258808f4ad671c0e7611d7ff28f98300b79b63e266b7d0443ff9c99f7ca59936cfb22543b319f4ae0f8612416d7ee803b04c63c275d721f6fad0b60dc7a053fb11c72e33a78424addca64160ec17c71c5f0094cd63d2df90d565873ce629ff1f0bec2412a5253932cb1f931b36f566f757061fee36a8b4c4548793ff9e71cf318743f4a1c9488be3994f7b7e86c6e1281db3e77804bf2c7622474d9d9c4ca648988fed0e6d84bca4aac8479dcd701568470bd61f0c0b2888a1f23a35b559c0b816acfd6c1e53b892f17b9ecdb8650498a2eb11c812c800ff12a02b563beb2ef9efdcaaab337fa9572e92eba5aea0bad4877fce4843427637b7fd88535a30829795afff493e9998b5aaae17cbb6cb83b561a84061beb8619da0ca7a2e5142f10096d9890c65502ac423a1b164c23afa34de779d0ff0ef6d883f44c9628dab15b6d5dd7ce05118633e841f563d65ebd0b2614094a2e1b0de1f9c3f230f167c8dd67e2a20fe7f90ddff7b06f55b7462db05987eac44bfe9432bff0133585b7494c9a4c93b76edaff2e6d22a0e93e1e9e1985c9f8dda50822ae7747a8ab46ba238f438985335129a133fba59fcb64e969414be2ed36efd293956e3c89c5ebf846283b6a2d78cd2a5e3858e18c51cfa4255a0121b4af12ec4a3295fe949668d6907f7b5f0b9f9640b239d6d6367073680ab93f2cb1851ed0aacb307fe3145bdf49df5fbd414e3c225d645aa86c26f5896f1d7aaf13cc0a00cc2dedd5d3453bbba6830d804ad401b8fab7f6510dba3a21d65d5468e1107a7ddd63c06645ce6c7f89e82fb95439384811ad02a1ef00f16bca32421490c87f6d3d140d1ba6234ff9a3b74e66e774aec2807fe18700587a5090a6aa5f0f00e868f459d93831af24186695eb3c33d68cf6df43d2d3d1bd6f6420f1d7ef572d8e5d1d60d9f5071ffaab15d02417b4d6c94ace7a8246fb6bb4c2d45f04ec2f44400dac6fdc84798834340191dc18b137a836fd394798eed4ebfc39be3850e33893de7f928deb834d8cfa9342e2eb1250506f0d555007098d0a21cc8e4e3a636eff171ce4160a842f925ff50c3b1d257d699723b3b0d47225b092139d06c9043b87c0bd370d17064c7fa9b35ecfa6c0acd3aa3ad8170850d3c9a5d2bcf14221d77378e28dce1070af311c3c5a72247d9a43eeae38c0fbede23f500cb8a372df5b2c6a1437725dbb82f94dacfe713b8854c1607ef473d8b437bcc5866cb10a8bf9f7d056e6501d2e89ee8b52546ac2c1153f00b7a765eb435eba65bac805fe592142aeafeb5019d6d0f15a6730ce85837e2a08478c2dbef608b0c7912667f3576817d187ac4a22836bca24addffac70c15cce5604b0d7749d856cf75f7875c50b747ede9a43d3878708283283bf908efb14d4544ac28e96a7ef042a54ed91599496c40bf9fbfeea1a49c7760449c5d4f7008d7224d4b3829aac6b55a3f2295966d31821e20de126a7bc34a17d65f1db753ad4868a847c95a79b6d1b037ca9403f12639c5d94eac2e8087b0d302f914fb068290f77be34a0ead151b5e673273b54c0a986c85101d3616387644d3d535905e9baa83618092c54841479a06043804e081821364af4a5bd7151e77606ae4756e415a6debc80fc31e0f40665cb4521ad0eb25f09c307687630652570d83defe779d94d8bda202dd68b64b7f64c39edea7b911f58b975bfbc2de1c1ccd55bbd5c5d98b3e3a6a44899d4733dd5e73f5091ae98d83cce87a252d710a63b68b6b4d738e78b24b4413463850def2ac43fa0a87b9fae3d84a53df24655d86dd6a367f92eec4f52a2dfe029bf0d39fdf1562f38419a95fa3f1a9cb44d35b944c02b499304de70db1430a535522898b77eb812a54d0a8bdc3eb5c07f12e5c215def461a268a01fc77e2f3d43ae5daaf1092ad043548adba9db475f903985cf2075e1dfe217d44105466f6f107349c0290aa4f7e2f5d3a1dc1c8fde1ca8c79332014b6cc839bc05faa9e5405948cb91955c07e00dadeda8f0dea0a8e6cc6a6d93d20b769821f5aa4db2c0998f274583e6e5a648eee828a996341e1497c63118aab06c06c7195b80aacb62d6be6e8a10fc50ca56b9b94ab03a3c338e3a16be7aaa6b3d385bbea89c81b5b907a0c8c2fed07770ad3b1c8390f4c1215313e6981235160fda3bf0f46421b834856c83c6c42a575cbcd255cf4e9419f56565647bc1406f4aeceaa601732585496c4d0fa05b221b55179c211b90934b756c7c00afe15894635538d2267924c6c244a01bb536e08dcd1b35887db3d746dd7bc14923b54a3497da8637dda356beccdc18696cf58e69ab762792d874aa1e44c5a8dd577974d1c667f1e8a0aa72814bca3f937786b74a13198b9355606f8b11fb174a8b8b2d9c81bbafe90bebec204a3a93050328eaa3285f2f056199597bcc353e14b308bb5c3e22d91edd401124e03727bad236899b8fc26d0b51dcacc2b0da9414aa7683c3af2393cb106ca6223d4bcb37b77706a31bff303252e2c015dd0cd91a28fe85a9c28391e988ce70827c1db78ced988d71963e3e6de4b0588101375f33b79cb8d57c09a1c3637663da52523aab19532c031b883922e4ef836f34b515b3dee861f7907fa04c0cd4e083d68225d86851cb9f671c81672804714a9020acd0ca0be4f9c33eafd9286b844fab9c2a8580b5f79c7ed7992bb34c19eb5ee1aa38ebd620c999b91ffec0902d5fa933c47667c0628272ff3e9d58e623049ae0c3caa0f7223a48bd647a58ce1e95a62ba21b7505121cbd13411982ddf338793177c821ef7e2454dd2180c7e968f24cf53efbf7e962f769ded72c00c7312ea2dbd47ca95ca1eff0aeec8a8f331bd7405b44840cb0ba3b0cfded40a208730fe62e537c99561714de7d4f3c81e0824d7a045db8da8f9c88e1114a9a0c97e1fccb7f0126a95d942350ac132157eed442117c22cd69487a004c08d03eff97569e18adf23d2b54dbc924433b6efe4e20f42311ccdd66943bd5e31db7d4b1052446626486800a178347c5475274751719c7ff66073d2120652cdc9f1affc2a350583ede431cc85ec00dc441a6b529592a8aeea99047d257a05744f281982f8178c9a1364e21c9dd63fdae573e2b47f87b138a4b3597377a507e898492dfa61bb255c40fafc8bd9349896abe0a08b191cf1adb21432660a663656c53bae3b8f7db657878b2bb2d93e62ea399aeb6f5757e775b8e7fb3696da882d24d6f9465f1566edc0d2d6c08d2cd1a5dc56f30f9c47f78fedbd58d09ddac40b279026d513b7f9c1ab721dd1f8a6835218b0c62d8e03c6af48d530d6ba7c74ed3f11b51ac0b9a8f319ced2cd2846b6ed308a1551551dd598fd2f406e8731af3a8da3dc6fbbed744e4b22b3d73d5331bbb7245082ac9df6d72fd59db877d9b8bd442d0f671ef0eb2b872439c1eeb6b92368c52b662536b1e85645705511119f00949d5f2890652d1a71d5457674eb761b9d02657498f9b9f7bd8a2cf1cd6b1d5b45d64c00c7f2be2e546ab6e74d491898addc3e762c4476cddd4e5711f5a4ab6153e333204faa185c56c2a81628ddbf4004ccf4ce649c120d20fa33219d0196dcc0c7bafe2e34eaf327171a72a52c93528559ccc5961e1d502b158a7a701114c47f35cfedff455c23a67f5de66887b46bcda187dcb46f00c16e2225e88fc9fcbe5667a0676bbd527d371bd08eb4abec65801c226e021da0e0526dd1882755901482e8070572a43366ac23da5a14bc96330afb604b5367f64ac9fea96321efb0ddad7c329b3feee015b6a668e4473aa520c94ec95d4e367b50fbcdfcfd81e173052526cb65a34aa748476112c0c25dd60eb7a532d90f541bb948dc26018f4443bc714c6e67c0a18dacd5c6141b2ba8cca6bc820658d6de9523a8a42066fb0b3b56eed8f9ddf2d3735f87fc49ed82542fc2f8c4b1b71b1ae93c62ea07f1cc052c9dfbfa52f2f180943ca05feb7d5dfb04f57d4de9136542f9facb60af908e6c4a047496bf382a1658ec47d3a27be7027ec8555222f9f16dff7029983ba3bf0e99a913ee6f98ae03bf295f86469f935e0c0e89658d116a697009a0ab5a99a7434d55c25a770d4b57a741a55dcd7ae33ee33e819bad15413789fa3f1a6f116bd45c9cfcd8f0de0fe6c71814939633b8cc3bbab36ba416e0fc45a5ede806620506a6e5e222f0eae213c7e3a3a359fa4354a6d9e04dab5ced04bd140a0ed447f9ca458a9899933495f0337ae057093e52ad9aab05c6e4e161a540cc70e4f24bc7140ba401b836897e8651eccabf4fc3a1c0c829668dbbfc3448dd5cb20575753e7b1fb97a098386a10c9fc551021eb23eb681dca3c440b98b9bdc80723afdf5f22bbe5989755e2129648133ac04c1975118af613dfe9883d2982cfae922a19b44a875ca75ed3f5b92f704a2a0c8ac787e9f090f1b0c6f66223985544b68a5696f2afffd15601443e29d2afc6d9ffdb4f32b67e4e8720fb29102cdf4383e58ccbbafb106264e5a2fe47ff0f0e573c2e98b849a9f1b3129686a071e9b57540cbad68dd72e553dca204ee40ed99e0367ca45e42ca058f9c14da69349c183d8fc49d8236a0e81c9daed7082366627e9c95f72f67acacb320ed35c915d1abb8e49340e41bd3c04cc591a115e1664a9b6d8397097d9a6622a7c60e7869194589be5c0c7d6f0854dcb9f535625af328875c6266c3c0ff3df818ade7283bc1aaedee4b29e90666ccfe4e2b0d451f0972105ad695abde4cb846bd99573060701d477496c20f5c8a611cda86e46e6194b8b2974cad6b5fed3af15df6447fedb1922d54f71e325e7f27a8303fa42f60c14c6caab36ec22053599b1091540235472e37e774eaf461a4a64c54b9a482629495326b53b08c825e0d19a725f14d12fa7e2efac47bb2cbdc598af68f24e7f338c5d55950b2d47e09b6037e30247083a29038785da56134b57863725531cd427c13af0298dce83e68f17d13a85c2e28fe7d34a05250583b1a4732ca0090be652819091c161205f7edba145eb9f65351a85ebbce227e9aced84dc34de4cab1af36f52a01f287f1b8d58f35a17306270a19c057809e24ff4b78082df40a77de032f987d7827299f616be6249f4e5a3e190cc6aeb85ef605e27b80001a4ec66357ef621ad06649953c201770cdf8ab794ba2d74500a65aad04b9c8d793237185e79aca34c5b8d3399be558c70e4e907e9e70d798d62cb1888b1b0d6aeea4ab6c90c9a2b045221ec0a1df232b6bec70ebd5accf45927311c96e3a463d03039a27c137caff88b36ad06388da213ec1fc0c75c6c646faa14da7fc748dc01be9e6b62236053d79a50181fffe0700fd7d78815c3b99445ae88140febede19210e7dbb3db72b4c55b63cfd78aaefd598595faa58f9e72d00bd9b102bedc15fd31f33fd3a840c19987a91b1142b1f26df9635230eacc243f27b4fce3b1ef62cbcee2b30f3ac216218376d40db08bc35ad5f4a6f5ac0008a9032ed17e78798de71ba21709c63fddc113f14f320799c29e7ba89871e9225ac953bc3457e45470ede7b9fcd70714abc8dc19a4802bd06ab1acd59d69816778d55e7c9f10ed257d28e9c6e9ae0e3d53485a20ef6c2a46d6355a64ccadb040450dfa5f12c3f535c296e08e1183a685b57ada0ff8960a6a0d895e09c511fc79aacbcd03026b47eeb3ab4eb57beac82492303e129c3e168028c112518bb7ebb9fa4269617cb2ab61484914993a79a3f117434dc928047f2f549bc7fd0a3c4e593c7a554d046a94e549cf505fadc3537301f3be458add522bdcf5fdbc9942dcb34c158cb713e2df0ef0db5c7ef5500bff6bb2822c71a88b2c094d1ffc7e130a53552b4b3fab54e8372be661614df20cb7396a788eb565e30979e3c2b318a51257323b0f99ae03d179247f888ab731227111791f95134c2efd1dd44ab7857440fab93bbe07e6ca3e9b0d5468aa1c478ebc60606f5716b6a1bb53c366fe23fcdd205369178acd5dd230abbb0734270ed92436b5e73d0bfab9e956be48f2c31ad986acd2dbc06dea2763ce85e55ca73fb13e3d12fb5e8267cb7450057e4f4a0d1a7e3436ab139ec637c0cfd8d5bf14cc6e43cc61665973524a86121c07ae6611e43a5adbebe35549b42a4488fa0914243d47d13368da811e38483b67ebe5a4ea2f09a23b70dad0dd3693ba771899dce8da192f9b9536b801d3e0391968da8416b762aac180e30add2c730c85e400c27b419a2af339e481522297e3283566d296aa7691c15a87bd8c3a831ef477e13e9ac3d44e62731757b1838ae3b88ea3a5db9eb9c7b38bf16aa75cbe2ae9faede2366a17f9a208b3da4af81b2af3a7ca7b6a8c874d5f0d59e20a26143e911d4ba99925a47c5cf23c72d788bcb756a75653bd55cb0c13cb93e930b2e4ab236318e85d441b52af6793996abeac4e692961f7d335baf155068a2e99625def90b8434b80357e484381a791ed7ebf981d7dd5c202a15bd2536845660dff3a9a1ca6e9b7a2f904d1920cd7954918812ba863411a026440fbae3dcb3459d8a2c44695d23eb3abb56e2dc1935632787b33989b9876c6c3b03e50021740d3ba464d536e85a486004812ad1544ed10c4751ae13344102c44e50695a569ffe6d4083d5b26c2f46d7c5840c27856f45938bb13bd91e193c4f61c1f5ec2ce8e3a322a3b535a992633c3c82b62e5a3b81eee21d620fab80af635b1f85da35d204b07e5b6455d3db7e352f9f2968edce3a0c34fb0ac425da027b98d509d0862ad351763d7c4c9a4432b2cf741e2385bb26e7acb333b093aed231bc695330b188f140dcb89d752b9f9cf11716a076bf19690e8196f0ac4483e775625500edbb88e78a3befb70cbac0634ae3b998aa0b34db39f0c088f2196550552015a405c20cb171bccd981445b21176c24ebbbc32b2931c15a0f84fdeb8a9650f6af563f60b374da42601a6f7c9e1ac6ecb854712ccd5a9e811cdbe2229d8d517ff7e809e758bf7c03a88884bd33f7f1cff3c4e317d612e57c42af0da0deca619ec7a16f49131aaa68d42c4119565da3af3aab8804cf3e81b9c4cd389bf1c95c229a049b696d2f20b348b68d1c41441158a1a866446164e9fb4367dd861f287bd543eb164f57b1e80985bfbb0a75be18ada1ab4079301502bc7fbc086d2691082f4fd94c90c9429c99a703d724ea4287b70d58759a75ea71106b0e9745fbac0ac1d4b8d6b6c06b885413e3165f0fbea378507ff3c470a9acf63d241777f486386cbc66755d170e8a5a80fdc88d26b018aa35739993e7e8992ccb2ccd4edcba28e6cfb6241efe1689dd2f1aae17a4c1f765e5136a2288db67da39672195e702596a0b70b47299741f4e83390e3f342c046f2545766807f7b5d7ec0d867ba5e086ca36067a3e9c48ae593e101c9b517df65a4d2aaa16d3c996a1fcbe7fceb97a1f5bba29715407900f03f4e072335c34be830a347f75b74431496e01bea0f6767c3e7094be5669b514526fe0e55e808ed29f649542d2b641308cbd9b5f899d3fa8557891cc925bce017ebb6ad0c2fea702cc88dbcee394038c6f0b340db763e5fe15587df7d396da30abb4e3261a3fd41a36b17646fedc3ab94e585e989dab3ab2ccf4601e272bd1340e99ea9ed62eeedc719733b3077aecc88a1a77635003e81c1192d817f52afe089fd96df5f61f2c0f4bcd6490acbb9ee362247b8553860b5192d32b9592343c52f51897b45e3184c2986c2281572bc18a50463a2047c234fe0e7182c4f80600dd2c282209d207027be10db7e706e71f17e9c2ea51fec610fe718a5bca17387fed873dd0ecd822921d280487ffce790df1cf8e538c3d2864ca7532db8208f4bddd4a0fa9fd8b38b87c921d762cbb10b490af8262ecc0eb162b9d4218721a71c19618ec7e0392b9cc9ee104ed99474ae02a21b9af976b7b126696d075bc0c253106ad98fb7112844a6f5c91d2dc74cbb81d0225b2ca6baccdb8a21d08b3c500449aff7cf3bc704827acf1ed25ab076a0756d4f435b96c7b3f78793c311739242dff63a0ea974782d6135d49222f2d66f3a801a9f87eddbbef3d4f8bb9d14697587ef19a190562f768ee29b79563f4a7c3b9f906cf54cd1badba02d2ed7fbd5748988759103732fb186d2d1f2107ee1e8a706dc5e82149d7ed370d425a67c3f61686c652616e5eb6d782798ffff47b0a697a02a767655534c1bc1ecb71534b1798dda5c59adcf3a606a08c470d630c48c04ac34f6fca0baee01628d8b52d35257db44e995f3902ed32a84115168399107f2e4ce29733e5e9885f890cf53bab977906c42053d92b91831f42ac0f9562495c29dabe1162d57b2afc61d53cc893522818ac13cb266406b6659c2b8256401656e6cf45e43faa3b08a05dfd0307e6abd2365db2c7b13cce1b62c296d1b839899fcb4ddd31d17929822870756b93efd48f65ce56c0456ed0d376d7ac275282beacdba789253affafc7c162cb205178b92814a447c0a58563eb212e2c57693c25a42d820dbd5bd9df82c38e2e0cfb37823dbb5eb9924a848c83f5e87d53b324855499615300e8bdfc06beeb90bb2357e729f1f5c9c1ed405708ef046d602192ed4c3314b2e882ca19a175bf3a2cf2fb579ca16a4a008147684d7d10cc978abc1a15df0999d00950b92c34fec9340a25bf95e3b82f74b046c20be1ee9cea051055961e997e5c2e314141177a745c328aba7fafd05149b4430135836eca3cf076cbed9c34c3f7a93b6a8e8231266b24368a8b85c694bf7a30a046b74e25955f3615d404585759d215c38ced97abc0eec14251fd0f3541a6124ebe4b0309aad20459f750910cf1fbaaa49b7ab023a7244815e1ae4f5a299123548226d512bf52995aaf7112bc5470142587bad7258cad0ffbd9e5cd720dddfb467a61cac7cdbf0ba8be4be4d5690cc6bf9c2cc7ba65e8ba0ac2bda65aeb969502aa4a1c78e079b1cab3dcac5fad3618953660b40eb0d3487153decebee55dd0fe8f3da986268e7f980ef85395d02b277eae1a079b0ffe9df8b7a3dc7fa208125193c597c5cffb5f6337d58374fc5fe08c0c11bc6d7d114acff3898afe8a8eb47c8855983dbf2c57e0742f3e909cca9221ca770001bea85283cae48429e1466ca9669a4823e5b2842c89a462f6bad5c9b257e8ba1d1adb45382c5ac695d92683f06fe1838ee8da71a35741d3e208e664abccac8f311e8eaae2702f75199eb32efa0ba3d986638fa160096ff52c6aa2eccb10994de7ca31d1b41e75e765d546f0d29ca1596c66e0a59117b2fffa8965517c79dcc1dca80245f37960f606b468de54d8c53a481070ec5a136a228e38daae7fade7a9901044410a0622afd92bd0f75e4186316e3848d975077b9cd2f9445cdd53f8b12d9e9b84a1253f28e335bb5e6b7ca142fc70ebfb9d1a0160d6b740c7f920cb6156ace9a7ddb1eda66d2526151335eb012482b2559b3dca10980349a6a79919a7f0a27b29ac634589715ec9960c9368a5d1e5ad6a54b502b99f81a72483e6db177ab9e6a388a6350b3c9d4dc1a9568f6ab37f0ca9fdff3cebcd75ed1a9d65ac211bb086247334fbbe72d1f2a1ec892446ff72e425f1bf535a9e352d6237dba279d24f2c4cfcc15f18a38073955ad568e208d5c2fbf44f6991d6bebc2314ef3114de5d33c5825c412fe9b8e1ecfdf5e6895522a11fd82c8520579b6699f756cacb3ad361d18f726f57607606f009de77c45a37eac0a5ef501ad875632f44b65696370613cee04283529bc539ff8ab8dc71609c730c682f997548c6b0f878fa5c3a915d4a414fa995b038582c18ac14cbc38eab69a0cd06cc3829fd1955736bea523608fa040ca5f25a1c3b078bd821a093775a22e45f03aa4e3f578d18e9653a78ee96968390f999a04d4d8dfa929f14585c3c59118acbac5731584424fb8b054105c18503ba616bd91acd517c11902cca023e9b339c2745b1441bf5902189d035786038ef7468d2102e1803ee2d0823380214cdb80aefe2d9542231a63182d1a4d1e8cb9aa95c7dfbec0e45fd99e01260ccc7f6a8797f842dc1b1c4fd00adf9c01213731d793ed1ef15cead98ef8ad8600a4b6c22c0b4a6ed630fc3fa6edab6ff1cdb93da7fdb01482529b0927e00dfd849da00a5f0a3af79ca756dc81bf38b55dd992a5c4b195fbad7879170eb6583ed6e6da9567f48b6c29165e094ac78c9314885ca81dce5e83d20ad5abe1748a85a8d77797f425d0c212ecb2d9c4340080c304ee4ec611bc1b4c746c2028b213cfda833a8ab031bbdc86f1573794918cdba8b9b12446de3ab41f432c06b0473964832934b056df907d4d0886174e93207012ba0510a2a720fbc123df3d7973994a2d37d17bbde65a9052c32ea4a9eeca6376f6892efaccb15f2d2f3147e6323012227e06df99c3290224fffdbefddfc0419b35c43f3ff605622c3f1f637d705c6f85735332539ddf944f08899e9f10db362360f1fd81887d9ff1b19883e160d33e8c43303543637dc2ef471838e675033d6422e75718011fad2d2aa95b88ede49e63515e01b4a67dfdfc10a7bdea9fc44c367b32b5bf1373c93f7408590214f8917fafdac853e0ac06caf29c02ddf46e3e9fe6dd42cbdba99f3443b32c03844d7317c46b3736ff1790c121d27f58d02e031464a79d14cb8fa87a0f36cac25e161ae0c86a579ba9dddc25c9f22a894988eb3f082a4db6364d4124b163c427433707bd65f8749a2fe9e48dbb95ec522ee1532647ef4c3d3914245934d1956b65e62c480e138ea381c008cc36ebe541931164619c0423ad3f1b006b7180a670f8ec42a1c5d23adb34f94e8923b6cbdab6e9bc461c1d4e760aa02c9a58ee57060bdd61e69e418db70caaf5d627c8c28b279b0616573d1156edd31aa896783d6b9d8476c399596f6212b2118d9c4252fe9582c1d7eabfa64b63eda4f02831d2c26877654ba5bff1f7221ac21e1978691d5f0ad133cbae796a41c4cfcee54742b8c32b3b3ebf665920ff9c5064256242f9b3123960dfcd6b8b565b05e6fa4c242869cd191ecea62ea130c4022cc75d73722e90214529268a1116da3c388f7a8444f2e38fdf232d6d937a6f927ff639e46822a590bc08334165ed541fe4df286b04e692b3dab68fccca13f343cf2b7194d964600e40c7e01bd482923517bb537f3dca989c551a8677e92a8dffe401a379e020e8cfc5a50f208cb28d2277a3f0b757346c8c5949910935816cff07c5907aa121737b5ec576ba6eb598ca38df1bf9f9ce58e7bba9a326f1f79c371601885cbf5f869ef7ab61de86aab68cb84ac34d8b00719f0b9d79202e34709ccd22c865f825a4592229932b87c68c24e499a99b9be071737a506e80644bda5c286e0eb35d7619a4a482c4f07da3b2775941a499b0b3f5fbd3a203fa0d071d210b358c4f40f33f26ab5ab24db6090e90ce194403aeaecd0c710dd65795a035a4ffd32be81f52b095c2eab2cc1c50ace483e6767ff07df49f26d32563d8694edff2c0dbe650cc0e0d8a78f71854d8b0dc1758155cfdce898d62e813727465086f016468572999e70c8f7599d3a1354c71f3e8daf43b02ade7568ebf03cb076ca99107f81f04cd8748062fa4fe710ad3539493982f23e810a3cd7cb9e0fafb22b939132219f65f8402a77a4dcdf086e2f299026bd5731088b97434779c3a91f063cc9067807b9929ba5c956722dc00482329780a6bc4934025743e02e0ac2b844c3acf44352203a5947804369db5ca4da286d75d68d6f2422be879c3877257725ca3a84f81a73e5a81878164c130a1b53ab3e07a3aa990fa30144de232cb800f910443088a9cf5087ec0425b0832b30765179ff971f412c48acc27764aa2970c71ea67c2ac841c0264e80213a495c07cd7b548cff5f87453f314772d0b64523a4ac346e9a7638a9778531cbd0ecb60ebf7c801ca4a42628954b6bbfb9fe749bf421efef45f71b970627fd0832f00159414c0fa210346f2d3845a5bc917325360827db8e375bae6c56f410c79ab3a7fd70f1a0fba61c1a621bd058c16c7118420a037f5ba8d7260f0c897b19b656565d633d350859f5648c8160c63f2ff1b56819d5bc90febcf4eda5c0e7a2ef10149d7a5baa45afa7f7d423f54220f4342625a48a5878718f04888ba5c4826dbdcd4555e8075537bc473dff94aefec723d89d81838f646611219e01df5ea5515f5530b358e16783424811bcca5e5316dfb61a8f67d84106c745b0b170bbb7d42d0197e39d1a6d12114ef37488a0715d063e84e98fad7e342e2d5b3b0843cc1f456b10a9a2644297791076801cad64081674c08925bb49b324eabfe08f75c12f46308e710d3219f6941204b87322f0d3164ed50d01c8238460597a83a713ddfa14a8bb5daa0e983775317d21c24c17cc8da373c96620ff27c31f57978213bc7edaf03112cfb1fb3997129eb31d5708c2a6b605a7d4b109540c96803eb938dba7cea21c5edb5ca7255aa480ff3ae4d1d188366b91b984cde973022509fd9d301368dadd383439e5dc7eea2bb0e9cbb660c7bfc0224403fff18a8daaba7fed38c06919eea938ed5adc281643325d1de87365f00aff9d50c68036a020b8313f0a67a971cbb6350e14f0a8c06aef9954c8efb6b4be2fb4a13d688109098852e02fc55bbcc01baf052648cae44a93cca4fca29168dfea1d06b9c9344b8a374f7c1058344a47001c9ffc08ed3d0156652d9f2882615ae76d8d1377224a84dfb298c83f2cfb6c3cef1f849519c9ecf5e7e52e62ce9c66f85a9400c63fbd02b3d37e13f2c89809e12741be003afc88f995ba60b5477650f63df60bd30ad03921bb65b4c84582d912426a153b537c7d824cec8e1dfbb33b2cf30ccb31ffd013232d3d53c691cc262cc171a3ae9da97cf5fe677433728acdc3294ea79dd250cea4d9aaf2bd51a0010b586dce393017e292cc0459a835fc652d7cd66abb0500d95e3f04eb309f3ca90dfec8c1119c691fff1a04a384ddcfceb74e3162940920b76e64c80cf4ce4a5b3cd449c839b19c2e0068dd8c46fc506e9a905a4a16c3d21665a0916014aca5468390f8ebcbb375601afe0544b194eb97283aaa499733fed3944241086a1d82001691b75fc051a011e1c98651318b48b8d109d821cef3663627986bc49ddb55b93d60fd720a8759f315ef947b541981c32c403ad430e5b9cc87be358fe514d4d9b6ed211c77e72c71c136412728fb037faddf07e84a03a91bb28feb3c44a96604d781e3b8d100b51b651e029d19fb6b5261307ebcf27bdadeeee7c8fc6658785cc1323aa2a7f5d12d639ad71e91332f80ceb3111d8e701a815ef294ce7e2d247052c99e9b64413e61c87e1b7e96198f8a6244b94b124e7b2228ef653dc35b48e15a148664a360d21278907392cec5b37ab60d703ccc71f2016f33eaab3fac531ff92d840230d2a038b5e15c3e482c42b2465ee1e0542fa0dda7d5f5241cf8536fe98c4de80910eaa0bffae1397a1967e7b00f2afcddcea6023357028ec884ed5e5dc38da4d704f6259f3058d4bc2614739cc6230c9f1467c35c8bb4b6421680020c365f619a407f45cc5bc0197069e6991aac28e3f1589bb59fc99ca1b5aca2284b08e441eb576f2b708b64d414b2223e46f6fb55225005395bb1c8ef94079a7df2d26437051e149f6544bd10a66585604ef294c9bf9a783b17117ffc3a05ddd7073b629bda6a582270f3ed12e3ca40a9a83cd1d8148aef840e8fde04a4f2942b5900b7c79dd941f72be5dddea884c8421293cf08e6527400099672e1288e1ab957df1ba3090fe9aebeda5b48ef6bac2a39fbca30b5baa3691d4ec7661bcd2bdf5ec4f2d7cc3018e9e7da8f9571aa2501f9d3d5678091ae413e81bc9368f1193fa95ee437cd27447d944094cbd034a04639f5a304cca9a0e7589ed157bf66477b1ff7608fca9fdfbff9b9b3b641b094267b4eaa879bbbe76e5fe3b015a3336b8f3383f78bf2ab4a968fc14f467b68e18009de8475ceb0a13775033867d5ac9e00a1c4d5a6f5ad2f69652ca9452b009ff09ad095eb8a6db7857f58d46d363fa463fc134fa3b11e9f6771a6f2818488a1dd0eba24361a01fecb7e7fa36c95879d371110431afe5b190ebf9bf96d76a7d656fd916b7ba6a3df72a22e7f8661fc8cb2f2f8bcc402dca7903099ea45a6bad319c9925d11b7ab228d140b9602559a94af294a5861dda3ca1823d7102aa82c902ca87294fbaf8e1066773434f1625fa54cd98ca2689c6d0a40b13830c459e89145190512203021688f0c2e48a270f6aa9dadac31319ea931b5ab0b8169a685f3248d3b2da7452da462ebdd1dff1a79452002061e5652d38a4db6f474f0bee86647a59b8e61d679836600c2ef3f08e993bd2f87570154d0a799e770156f23cb6e23a6b711d851f5b85978b599ed8595428782a5c7724fe3c65b26432b71b8070250ed368f7cc73698f10e4ada265209c0cc442f66c09f6e59d1439ce10eded5aed97ec58a3e1c4d2c8c1810e1a3b3d3e1198d4c795cf0a885151974f5e3c88b9590766f6251ffbb0eddd521c6ebf7d7d1222190c84c4ca6019d3ca72c752daa42802f57dfdaa61336d4e2a72370b4f3e661bdddf5a2b8714b8f2fb8bb96c96534a798f6af795efa4cd6d9fbf688773fab794d2369b062510490c2d24619e2419a1460b12aa27ac16992254f8d61a37355902b01801451528a84070c50a2c2c9cd43047c0b086504a29e5374f6e562062840e55508e65864ea1c2e3bc10224bee8e5fab51fc54a5544b96268cf820c92029cb113b6a94c8c2e58ef6a7c097f7dff368fdabf55feb83c8f79ee9eb61cfe3fb9818c4f5dfc3c4204e41c07f3ddb2ae879dffa59f74734f9336bb63694a306866c035fa2c758d36511efc8fb22f0bda7542c7a7deb0556af138b5a2f4523ffd61bc9f73e5777420326282dedc1d7acfb23db8e5927ee402261040d9ad896e779b226c2082f9cec2281af50be8b6afa2443fbce18305f7ed17c5934df138d48903ff4a7c8f2a74a833276bef4dc9334917a35d83673cfeb231b0f31484bb4df75eb7974deb7bee547363adf45084708e97be883e52a11e80a5fff0f03cd9e6be5fb17c97759c522f99e68f47246e5f3f0ef5c9cd978487146c51dadf73a845d96423590d65961e54f4a933ff3bdb09b73d22ecda17817ec8c490342ad9cc639a409dd751fb61ec4c2c73ef20776598706652cdb8c9c8a5c2edfe57bdf2c72bd271a59978b46523462514eeb123d7fb67963ed4efaae679b0ce57bcfc3df25067105b1cd04ccdf0104a9087786ad188bdf54d11c1c047105f9bec500efe543c0e554e412adfce9209ea844993eaf7eeb8beab7bebfd5bd271a79dfbd518bbd8732eb56e87d51cbf3de251abd510ed9f6a1d8514693b2497fd2bcccd77dcff53e88fff7dcef7a1ec2513fc793f0c3856c5071509f6bad5855378765cd1df9ea7aaba3b48a85ead67fa13bb251f9f38be4cff9522c7a19c97f8947362a8e3fe6a5dffd9ce6b4a6893e4eab5fff93e0d0044ad8d1e7d66ffde873d9fffb22ff4ff4c4a27e17787dfa369da2e0512ace18505f3e8fee5d0ce2dfbd14833815f9771f447efdbc56fdee6794b65cf5bb96ab25a97cb16e6fc640954f9fc7fc2a0611833815d59fe2cc16a47b29ce5adcf179dd34f9131e712ff4c1f6f31d6934ff0efdc9c73e9efc99fd4736125adc815482064e6cce69b2d6dc8124dfd6ddb5c4bec964322677cf75e00456260c8ac359ece6f67f356883fb61b1e0e5af6003d806eb6b89c81f0f2ad8facfbb76d878745be0c7be88be053bf96c03c3991465b656947d2c55ee68afec6ab70377c04422f5c1270212fd9275fd170bd9666d38d47d0d3656e7777e26ffa8fb991477207993fce9de99e48fd2d0ed5e03275609e7bb9cefbe8ff8d304dfb1ad7b9a31a03ef822d087e9fc4e0c49e902490a7db0f2f90279cd5eafef81746fd47ddf3ca8bbdd779307517121184fdd3752f7d475dd94dbf591dbfdeb7beadea978902b7dafee9d18a8fb9d1088fce95ea7fb5619eb504eebfe25fa93d3baeec5275676bf430ae588b3ef63247890fcee7548211c71f6893c40fab00f521f7c1eb08f7d4c0c421ff66ceb5e042964bfebbc8060c915c0652f3454dd0e08fbbdcf3748f4896f47be828560d81a796ebf7c2f949d2bcd1725564daa246c7e58ec8e9c5f65ea459b78cdc91d193765b2891d6560083e4ef31acfe52e3f20dd1935d9c48e3469e5abff4db6e1dd4343b50e5e5696071848135684115a6ca39fc19dfb00fea93fd20ec0dba2080c543f47093b7653d61bb621654c6c0b5944807471eb7718b6c1ced46a6e8de25632b70e89b9d5b3b2da4d4b4c52a889d6818586ba69484b3714c4d8ba4ddf3def251ab9bef546df473ba1c8f5df090d609b953ff54d6800dbbc6f00dbe67fa291914cfed43fb2c91f69b24619cbb6d6b36dfee8cdfc8f6cdd14ce5cdc81d438f953bfa15a8326d6f5f903a9ef79d666ddfa1a38b1557efd7e924250d599a450ebeb7b9314aa9f58df25d67770075238df15ca5752656a75538c27ab81be5e3ff4c176cf77eca66eaadfb5833b54af56ef755d68821df7a1b973d87ec3c73edf87c5c23e2c96079c31e08b604871662b3afa22850f30d8ba975fd4bd148bea7bfd23db2d72d96ab0b1ddd7df81d489550d0e25d95116fa60e9f31d87d8fc00a5852cacb842c35ebab9964fbe9295d16e4bffef0322979ce6f5482e8d4777be6d59f3ac1c7b35b28698a24b1a234b7a58c346bc61b2a68d99124c29520596272d1e2693c89552ca1987bbc1063254a4c8a0851a2c2c34998207a90c0e862c5ba496f0ab2b5c58f71cde04eba362ce39e79c73ce2b5cb430320409a41470b0cd3477ce39e79c5fceda862e1560acc8a003939929407089818a2b4c7460340095a58817485071e5031a299d264652b850a384eba1c817567ee5ca085a4c388922aa34fd80431a2f8e988200586438024a0b16397421c50f3e504a29a594524aa1585862045a72534c0461a3515c4a29a594524a7718c87fecb9fe3d6c835d72210127a2f0d084e588ac2dce5c5121a74991264fcc60254a114c707028de804e1c0cab01a3d3c4b160198105154cbcc062c604ac2d9e3c59a214849b27babc98f8110f63de2031c40b356099f29c95282a5ff28d99c27577e22c2bd084952d597c81c4cb5499194baec0f04407295c38391182167bd381e86047cb36baeb36b7c99e76dda54c227b5a7727ca3a83da878f4c27c1cc2cce21b3bae7dfb1879ed37cd049a778c2bc3e68d72d95c99852ea31f3147fd0eb2fbd88adcf5eadf2877af2a7abacb2ca8e79dacbf3798a9465e8e38e3b578eb992622945963ffd1e333f03b5ccaeb1fce3ebdaf80a37bc44ffb8c1652019069779f8877f8a2f83feb43b0ce442dc7d0acbfc1ecdc65a5ecbcbf2644e29a7b5ded7f758a8eb2c9e353c6be49c9d0b3feae59fdf2985dd009e73ce89355a2cdb3fca50b2fd9ed378ced7654aeb0b92a964fa94a7052b2995799776b13801dff33bdf231a39ed8845e1fb88463d1fbe518c17ffc866413863c0051fe3828f312bb2e07bde82ef7919e18cbecf1fd93efc97f145ff329ebe0c19ef231acde81fd964bcd18c8a2714f93c06df00b621c5780c4423230c1e03d1473cb27dcfb3ade747598fc8b618e205e1d10c7370feebd705e1914d0cd916866cfb906d3242b659101ed9186cbd53fda8b7257ad50a9647f430a33f4979d0d4070b9de67f3aaf0e5d5716b2edeb20e779fe6370e771be43fb3ce128de1d91e773489c30271c7d884e8b85f07d7ff0bd8f219e7fe1c8253b8ff339e17874731e0724caa1f43de0707c9e709c97e777c2b1efced3b0861536d49138bf131e5d1d71004d03e76738d6b8389f43e684e0e384ae8785e38c1b7b1b8eaf1b13619f43c2c2fa603895427f721a94d3e4c34022973897785fbdf9e5ca3975a5c089ebc18e8ee39c8e30899daf249bf9a79b6f87732e71c7c578f92ee5423df4e53b94074d318718e29c923f522a9c54f207e7342a6a74772d58bee3c44d9c377138a93495a6d2954e257f80b03f451c8e891b1af2260a12e5e0c08d4dc0e4dcac6f119594586105218c501652104e80bb630dcb1ee9788c283cb4765ecbf5bd4058cce2f4effc8e38e67c8bb30f76bebfc551e777de493af9f3849e6f30e7efe88438a1b58285af304aabdfebafcd3e13c76791efabeeeeeef4ba4f2969f2a7264a29a54bd9b49aacd297a27bd3d04567f70ef61da197ced9ad3a67ab9bd22a25129a90e70c0116e28030420abcbe0cdc7e497fb69c68269a2128173e5f57cb342af2ec98465ff67c815becff4cc1f6bc4c26853eb0e67ac19e6d36146301b874000bb832a7c509dcaf7330609b81a890d9c47b4911dc623f5a69cdf39a44cb3a5969ad34031fecce652d50b840ebc1e6bc01c01bdb61411bc27a6aac99322e18f9e23f1c8687913101a83273c41d2d2dcb0cee8e5ffbe1e3037c9cc3ce7f2a9b0a4ead31235bccdcb6dd563a2a4f539a62f676bbfda84f4d5198a0c0708e48dd8003e788940d51a02cb5129813c58b121f43bdd44a9dd4482f9daa23a0a8d1498dd437201f8f14313bd478a48891d2b7ac36fc3e5c14d8d086871914d8d026046daeac466859e5c614793e63a8f8757850ed3c2a291cd5c74b121c6ef776766f3b291c5dcd832a0d8018294bf89005408c143143b5193e9300307c9962070402c0f0458a199271b47bdbc94f4eca62a488d9e1766f67f7b6eb6a53ef19a45e776f6d37e7f5f0a86b9da68f7db22e8da56d9b27853c2b73a197e7341b7f87b2bbbbbbbb650f0ab6866c9320d10efa9ff77ab9d0ab461bdb59f7b9b13c293d2576ac5df953865eada291101b156fd5c9f45c36bda1e9796fbd9a1372a6dbed96c456eb53857aa2893b565c95fa1feb541d13e5e54d1e99e18e526949ce8f7249e74719e5098a0cd51d254e4aedfc28a7787e9461aebfcc492ba43b4a2cd966821f6516053f4e24a52527aedc7132cd28b21f67138c1f2795eb3f71e39c7222eb8e936a86a9e0c75935fb719eb9fe136b66e59ac0b92345ba1ee329136d9272474aa95cff9142dd1fa9d4f5a754b4eaca1d698e9eb9fea61e79a2883b56a5bae4ba6749284d5f64957c73640a8f73ccb4a24bd40aa557d76d236d731dcc94ebd31d0c133ef5a58601c3d379bcc40d2b9705d21554a67021e608054d535c3db504973b36d51d3bccf5aeea31d7cf4061024a0c848dbe347a13099cd8b9a353b9ee6ee5ba4b5d7f1e9dcaddcb1d3de767ae3f7390bdff3240a2ef8f80673968e4d1555421c2e72ff831079f0b442136a71e3ec4fff7219d942871bbe27ff8168044e1874cc59521e3638044322cf81e90c882d78db17363fc8cf19e991be36d0eeac628c3c28df133902886ec08a5dbf31580443db323eeec618044b30a5e06125500c3092617c6530012c190c97e029048e67329c85d0a9e0724a26082097e07249ac0f29009c1e5791d908867c7b6f33920d18ecef398aaabf33820918e276fce5b9028872685867098c04102078704575c1c0c2e0ece5b2717a704555c9c31572ecec740229c21da0fe530c25a31d61ac0e2b8638d6b9f77b8b600d7dab74c9cb9f66555415cfb9fe4daa73581c4b55f23810dd73e0c24b243ae1b7b10248ac196007361ff02896042e07f2011f8aabae3ce7d8577ecb92fa0fb7abdfd725f4b44b92f3134dcd7bb40a2d77fef72595d97eb3d90c8555b60baadef40a2d6f5c254b99e19cf76168009e256f96092eebcb9dba25b90c30fd5a65ad65e4d8937b6bead696ee5b9b5d65a2bce6b47833bf630a18416258a48820a1f3276f4e6eb460d246a88a105092c544b77fed3a8c0dcf9b5249ceefc184834876aad3a2e14e941892a374954f02d7aa0c0823164a6ee38e34b0e253022052398b418f1e68826ee7c1848346bad74bbdd54a0c2050c324dbc6a2c819bcabaf3655ec4dcf94f4408ee7c5a8d0821eefca122a898829a536d9ca25089c2218b93275c8b2c3cd82f83a924b7be8bca13b0560008d0c5aa4b955bdf135d40a2c9480d527ed022eb4a198f893beef4383144d4ad5f65530529983441040b524800e5a6cb08902845820b9b5b7f7a7fc4a44d15140d48a84052c536b3e8d283102f0baeb0a34cc6369ccb9958106768b398d99c71a700ee9c5235dc2995c59d5c906cc982021d3c18920b5f0a3930890bae214a2fa6262da4a7ef07275e0a2e235ee850a86a458d515289e0bca8354be810ac6ecb902693470a750b0c2f48f6a6c7a30813e52d4d30f01882dba225061e448e6c19f38407529519a8cfc1a2659dd75a31e8d245959f2bb82ea6a46c8ccbb82e8ec840073bbb8ceb220d00907851e40d086a489aea1839cfb16f31096e7b9e4be9628fcbbcad3c07d5583efd503220acec89359a459250e188cd68e785b89b9bcd29f66ddb098500e0c8cde624e5b374cfe8c8858923900b7b0fc7791ba55190204284e0ae7f8e7b61a10907f8e28bac5166dd9ccff910bc98788209fdc585b12d278cbd5b715ae3a0947a1022013cc536e86d98d75f6e5aa8609edc51d4223814b19c7f82ddd35cd88fbe060ad49b9cd0b1c00e625c173dc74057566718c8a75873fb5d7a3206732c35b718cc83c2cd46b080d3ac744bddfe5c4bf9f270d655fe04d368878281fa5b0ffb58ce83d03f9c56c28f9d43a32302c9f27ba38f348d2bb6e1a9e11bb06ffeeee839b631bbfd7e866dc8b7dfef51f08dd8f7fb146c23e763b19b63a517f6d591b173476e57e574ce2879100bf510b7bebaa2e2b97ed3d2c9f11c15da9de1343f83c51dfdeaf663d52072793be8cb9de3a07339e618dbb9b66a346c038b6db4d886ebfb3b8b6d7cdfdf6fd8c6ebfbfde6485af08d9ed234fa463f358dcef9b1dddcfec6826fe4bcce8fdd05dfd0f96ef2254953b8b86cd886f7bf5ad7437965d867ae988172574daca3601a7cf93be92a754e071ba3ba7df908a5db37a96f3495297da3ab9a46f78d1ed3b2a6536e3ff912032931902f5172fb1d89c770fb1d06a6d1af139e00fb1c49a56f3813a6d1ef4e6e3f0e95db6fc39ea2a3601afdb0b0d5308d7e306c2ca75d856dc540cd40fdadb0730cd41f463dd891b360583916a8aec8dd907076ec1bece948d2153bf6111eaa27dc0eadd44b5cb0632bc16e4f550cc19406ca12942476ec250a8ce45098d24c1de5891d9bc93e15914295a62939d8b19b5e4c8ec3228527a6272a4eecd84fb72e5c18e9e2e607282b33d8b1a126c09acaa2089b348d6ba99c1c1555509921d253fd85891d7baa02aa21724cc034558769c18e4da581522e4d19aa9c9baa3141d8b1ab5e4f5db6c0ea92c552e7fa8c153b76ce454595a686a099424a6275f5821ddbeaaabb4db539c3e436e50688a936423456b779b26363d1dc1553b92bca7456bf01811d3b6b3e2d2d21c40dc9c98e7ecb80ea48921f89620a8c4b1451a2b8442186cb992ca52558ece84a4f309eb8c0d0c40506325c9450e1b2e4c51214253bfa12ceed892c24a6284aece84c2da521d24861d5e44e831d5d09b7c50d6bb29ea8f8d304538986a7efc6a40a282b382911d8d1713bb928235f0c29e253fe658a1d7d6a4c054a5262c24889b9024b4a892b29313938958711c28e4e0584a3f2059557f918afaa54b919d8e0925879cecf006147cf71172afa06046ee5576e055261ad5003c814b502ceb1bc0d0f7674ac3617e4864073c5d4c2a73484153c0c8126cab3fc4d0d76f4ac2532aa2e5a246175e982885c973650ba643d7143a2624779a34f4833507961c591a4263bca233838a9b42489ebb66407a5255176944a4762514837798b2262064b0b17555143965882e2831de5d21230b70549d29314a62d48ae6c51ca3145b9c18e92a975cb59110595155c344d81623f6ff965eaf64b2a0f9ae0f6cb2c59180f629c1a9c3c63e5a3c4926fa6740975fb5b090aa766c9f5ef97563c68e6a27c725a3fcf95f2692a197971b5aa8c81fc9bb3f5c29b5b94239ae4a465a85879b57a6d6a509222a5345de1aca865802f4a3cd95ae2914d4a59ef63205bc50ebe1448d0643372bd908ea266cae624ffc8f6b54257c82091d7d55951eb67452d57382b3ab2e590267487d41582ecb68701d9ed6f2155b7fb1a8edd74ab27a56ced2ef4414122da6275797786de6dc9cec12e1fb3003e9642d236a5b777f05506f244727a908b3d7ec82bc48fdc35f0f5f821af8b33297ee0cf01797bf077df78a2f365f28786423a2b21b52648a86c4eb3fea3dbe2cc028d1442e34d53968d7d838f4e1ab29a2b2c4b36dad5f7c49b90fad5c8a9fbfa4642e8cfefaa2864be73d9a0ff6397019070b619b2ed897ddedddd3d0f62a64dc76eda83d28e763de83b9deeeed5ddeb8feeeeb5dbdddd6b37adb46b57698fdad5aed2b047f557ed78ba5e7ab35dca1e3de99c41d26ff4ed1eece8dde9cd3b6ba55cc1c06030180c06832989599c9c17ceeb735e39e0386f4c84812f58ef4cd17df7bb9444b97f9dda703d832074627f77fb9c507ff495df0ce57fde4f7faff6140c21f43c1cf57eca64d68e5eec5d9c9e373de8aff733304862bbefd75b80840bb08dee2995219359ae79907d9ca73fe441f43f1bbe9c063e4e689d063e831f607071c4e934f073b8f5faee2c4fe81b0b4fe80b0bc7d605c597d3acd36efdc4f979a2e7c9efeebb47cb1934a5777b3f767777777beeb3d66e6f47a1e594b8fbf1abc3d677e151ec82633803c17bdcd6bbf75edf0aa86cdf762f1cbd1edefb4f30047f31508ebed19752cfeb72f878c4583b4b782fb95852e537db70792f5d9fc33de48077fd7d4e087ff4edff5e14a42cf4f2a752faccccf45be2d8ba14e7524f1c615cda458a2fb782cb5da4e0e17eee7d47df7bf672ec6875debf14caf90a03ff25be2803ebf7f55ba0582ba0a28e1d2dd11347d98591b3d369948ab30edcbfe6bece6377fed49f320dc77eefe70c9a72fa8d13fab2e7795ef764af2b90c1f673b7cbe512e56d47a1a707fb150463f9c71d7da36f6dbd8be3bc1ce8eb89b30fe4175fb03b56cb6d12174873ce39e79c5ab6d8c2ca953f63c8c7837a9aaefcd7e8197dec3352a52b9fbfbf7850470025b6fb640d17727d5f1084f92ed1fb1ed6a0025ce2d823a3ff8130c348f90a7388bd2bf6333441c8ed76f6d2fa1efc75fd9c52a34b9cb99e8a2f97e89910bbde832fa9c658c936b7a4a5284f56a6c2e4ae661645f22047727ac9a90a8220deefbdbe1fb712ff97962ddadcd77f200852ca693ba29c0af9b6402b85fdc04ff080afeb3d68bf2d90081499698cdffbbf9e411082dcefa7260882df4f94524ee340dfeebdb0ef1667ae7f072a20769981bc10e4944c8281644b34a1e594d32437a5b0f9359461c620774e5152814ad851e2a86efb3355f29c2633b3bff4319fd2e94d3e376faeebe75f5da0cb5adc58b99fbf725caed073c8913c612e51feb844ef1369e174212427ef918abceb8bd06c4eafa7dff77db1b0ef9c733a61c71ebedf2be4eb66ca35c0652d592c5ddc652f97b56425dd9d1e0f8dedfe73c2ca6f70becbe572b93e27ece77a97cbe5daf120970873c55c2e97cbe582c580b03d4ef3d6cb157af7f57a851cf0ae0b043f19c85fac000c247362b1e9e5844e385370fea3af1fb621f6e070c99eb0d8b740d87f1ee8c1843d831dc4fe85a3033bf060de97f862a059072fa7bdc41eb18379633171c86931256ceb7b7a767660b158ec5fb1900023b0b1afd669fd31716708471c3d02046169072fd08a218c7d77c2eef6dc0edc715ae3709a1c62200240d94f1c77ae164f974b70594b9698fbb95e5ec89f1376945d097ecb55410d33f687bc2e79bf594c3c0184f9c1f7531c5dff89de77f70b47ef8b8923901b73b99e9dc6379675edc73e87bb4ce8ae15c7be31317489a06520091359bccc409d43ce5bb6f1bd74fd50d390eff964ff999f681968fe50dfc0611af3a5ce7d39cd479f11e7ce2b5f3db7e70fdba8f7831f06ea8fb90e13669ecccccc93999927f374f38db5878164723e9db3328737586571e48e3b35c6da8ed3e7015f134c9897190887813c7e859681b2eed853c1072c6bc1c2ea71d846d8d7f320b681e463af49e1103dae1d43b519bad25fe08338682fb3d5602199ac7910ecfbb99b89ed86751048170693fd912de719023c3fc11751f032d1c8a973c471e882df6211cf4f70646b71ac5d1004c57e863a73c10745761a4ccce1b19c3f22b23018ec3de070fc11c679753e271cfbe63c2c0473421c9d30f63938e2009a46ecc170ac71639f43dae9b41a56d8873d188e5c55e3824d60e875e883abaeace5b1abbe17569f41b33b12c7bd2b7dc4e290695d66c22acc6526aca42e1017bccc84150cb73bf0b1cf38b9fb56add5937df7934da92696bdebce0f5609c3ef41f6c5b2d0c3f88ffc0e3f9c170e48543f4cad46cee34c1c1c1c9c9fa109325e5447fe13159ec1a23f99d00e25f54bec3895c239c36e9eb7d3dab73c9fc33bb43f799e41229ecfe155f729c0c1365cc8a9e48f95424426f87f22305e06052f3e910a3e94fd054f64f616e4b8209cd1bfe0637c18ce76c470b623ce18207ecf1789013a5970d04982c5883539580069795d03a1b33618207ce5e5ba216e799db367bd4e66ab4e0224104d53c63ccd97d5837c24b7bc4ec7649a2c6786b1afee641b55555521034d1a65ecdc9de241f25fb46f592daf6ba6dab7acac2c20f2a78d1dffceffff7faf8a40fbaffaaaafd2d18940fbaaaffaaa87c23e791015176a1f206118ef2f9e959595e5b4fe2e060f1f5fe0052b6ca6d8e4961cf12d2ee042ae25edeb738d08a49c4fbb70a4728a3d66276957b1c7a44dbb6743f1e4e30bb880c4e6089b26595417545703de352176fb93bc7c7c811a97a358f8d242d3e5a8a42fb788cb5149b7fb3eb42f36ad6efda82bb26e157ff8dd80ec8e406eb7dbfd21bbfd6065a6cdaf5efd49c51ed7ebc6fa7356b1077d797bd4dfd9a9228e2345a46d486773d2c0bcfd7ec3dd6eee21d9d1ab4924a8e4ecd9fde524e9a0ed124bf0dff153af43fee8e4ecd84c5f241353333593753925ede7fcdc71e24d5a86d61c34b5eb81a5843cffe9762967cff939efe998f9d19ef7d211823733f1d43d5826f7a1c5c4403558a6afa1953f4dd5a3c4ec266e1eda9f81b837171b05a621bf134b903fb2c5212530501776ec269ab73463a96fcc97efd1a858fbda6b799d7b1e9691b1ba894f5337e91b93a984eeaef94f0fe508c5a6d15f4397459cb9ea2a69951ba16fd06a7fb214ea3c76b968dcf2baae25d6e48f7ccf7a9d4ca4c91f1d9c1dbba99b9a264e0998cee96a9da41c1e4c24aefc190e154046bb4193d17288d8916941e3ebe6c0b96c2c175a2707cf6b87d2491e340a09b18d66a0a6c91c363de5ec9e3b4ca64fcf0e5fdd4973da1c3a0077a131d0147f6402b8d2935db1e3506d2866c3c7e3bcfde1153bf298dbcccece8cc3c809cb357a54b02c8e3db7ad1447a32b9f8f26f3d16c1ad51ca5c29b2815982e6d1ba54c9f7ed75adbf5dc6ac960f00f7d119cd632cc62e97fb114ecd838cfb356267b991403d13fe3340f8d8dd1c48eb599fc7ae79dc9e725a2d8e2f212516471c7ce758e568a8bb1a455633e076c2376e988c325ca1888fe055d58cf72e541b866c25fd84cf887829d856d34e8f4c2e152974b1c5b864b41d90a3d90a8d5b53a113665297d0ba0b0f5ebe5a823315c7939eac8d2a5f5e7bf6a388d3e1dc01256c7e5c0698dd5420363ce29ddddc5277777779fb25f3bd7ffebe934af041edb609e724e1c9df8441bca5db5acd166481bee58c39d597ef9d8a756ab31820f1c595f86a8aafacd15bed16a9886cc72c75a96cb625fc1a8902de6d0c6835cf4b1e2683ef619fdd603a8358dda320b856dbd477f874cfe8823b05feb86be7f3d0fd7776290ee5dff1283381575ef1267fe3571e6570c54bffa19db594eab5f3f0463a5486b2c97c8a28cdb1d964315bd5a9349211db52a73d4fc26abbecadf1c634ba0973e093e74882085be2394f21f61f9c187bdeb3f4f6cbd80c81fea33f931ab99c46ab562e24bd4d1a2af1cf2879e2063a06efdeb257d5848935d38434ffecc8ac07f3d28b2fc697f7deb5f624b647feec0ed102b4b9039a4d0f7f475009142afa7fc632dacc91ffa3487d653f6a4508d7f78aa255af9c37e3b246ef669320b8ad8d90e24efbbc7b97d5da3cc2fff8de5cbcabef043990cf6fbd7fbcf9d1d2cdfef5fe26cc7f7b306b0ed13dbf6fa198fb6bdbe6d2ff1e58f8c81eac7b6589907dfb7dee8b50309c9f52fa4d67fe26cfec7b8d93462f9e3fad6cf6fb5fe138d5aa251ad32185aacf572d487affe67a590b4d51ffd368d2a59c643cfb2ca0069eb442b9fb0f4bfdb1bcd910d699e68b24203153a8c913514b1446e49c9932f5524c9a4a8335777e7725411365a2e3ffd915d0be36e0ffee9ce74ce661c53dc01986760fe5cccdc62af19a873af9479824c93eedea07706c6175632b851410635b43332e490666882f1c577b2d25aa3ae9248b232bb237380124c0188ba228204575277b4566677c4cb512c48b16f03922dc858d855065b2c5826032d1654a2089b7339aa0a2e071184a91285829cac65b437510c9171396a851ab620411249282811c3840b2637de9021b5d65a398082439aabdb11596b240d4542c026290d45e342eb7294162c53522debbcd25a690650589ccb5157b8d09454f941b2f672d4151c9eb8f2441645a4086965aa0c915a6bad33cc550d449e5a628604c1c290145ddc91712c1525451452e48cd4bce63f4e4fac60c4c2de6470439b0c6ef6438341d513187861e9e5a8dc981f2ad6bb1c950bf3349b0c5551556e789f28235f5c7a390a8ad51dbfd62447dfa33e6ad77a33dcf16b90e18e4338b424943b32d55208eec863ae3c73c44b142fa4a2aa9031442d3de9d45c5145d2940a498419e248ca1b23382c490551440b36f93aa4900796a678818d1a1e8c28d964eeda1ddae5282757dc9ccb514e7277b44f83068c90220d0f52acf04413f904eadbc242adb5562b48da90a0852e7e6823612002ea85c90313921e28a53074435c86247838628635e4b9bc4f9496a66b2f476959bae3d73a2f7ecc60f065c99f389a21c907334850b88105304b6868152d545beb981699289076d6b82e471589e2c2b81c55e48b10b59ad76ad349e9a44961e5e5a8225eee68a558bf1c55a4cb944e8660353de09c308249912f2cc8265145b87407966680c10b93149535b65a8d2a92e58e5fab8146abd59418695fab81a10245052a0c946432c40a0a2570814c0a6b9a904b34704314370481c20878b805e15794c8d8e18b2263b07893c3937f2899e48eb21741d21434ec60868724ff503cd29ccd1a2b68b24082839632b51a184fa3064a22e112e4b0830b6c567022428bff20388cfb5b28773327542a31c0a188353621ae289bd3f48cdc0808bcb06213a2010b36a749a9bf6ad83c30b9a264933571c6a37e97664c931b6c426057b6fa9d68d44e048182954d488e0bb62ad27c83fe23db375f7456548d3c230aacac10c2260414c25637e8d0680055aa18926513e2512bc860abf906fd95f61824b26461a37da553d4154bf787bc19cc78d059d39f4f43ebdede3e1b0528eedeee2e9f711fdb867090f083032a574a29a594b286d736f76e8b3ed3e50d1ffbcc781835154fb6fa4e10e89ebe37c666f48db159f913847d79b98b104977ac5d2995dc18f4c18eb22b7f2e40bb1c756b73a36e672e47ddc2005d8ebae1703f64a11ad45208bac45045893636f90d82d04d10c672d44d863bdfdd8bf8e4532674b87df2d541635df1847e138e6ef747dfe64aafb790759b1d69b1eb1f631b5dc17510013c639c2fbf7f8ade112ebfdab5d2ee64d470afd49bb8e8b47d8bcfbf63a07ec97dfb7b6ebf8e22179281a5c50b2b35866ef317e93c76fdafdee9d806ffcdc969934f5934b998b2d8c273045600545d5c58b20605ef65e50e76b4ddc213160d48724aacc87a7ab09e10b164c8fb2cb1e3d36684c08eb44ea6596a951953ab21c58eb5dbbca209d7550b433898ec3834a9a8b49082d3224b0e0e60b023e3a67094bc48c17991e28497262b2f529ae84840163bf2540c8b6a4892253b8a6cb0235329cd2b8e7c57ec204204b6d891ab3e2c2cb0a4a061040b70ce958b62418a112324f880b2235b794ab92ad250596151c28f2a7664ac1615152b50210390a114a884ccf695412c95b2810000001000a315002020100a87842291480ee4d1b87c14800b759852785295caa34912e428884106194000218400008c31c42034662400841b56c86f71fa51417e37e4a71efd4006fa43a135d3dd9925766406335d98e1f3e42b414e52679ac03f9b8fadb2dee55c91f30e1778182846c5412f0ffa798402a4f097292e3f54c25f9bf50c5a21bfbc262d9f838ccf13512bf4298100996c1df0280bc341994c870ab3476634a005339c3ac35044545f0142aaafcb7d202390cf0e4619ad6342aee4896ff35f326500502460c97e021613232e30b7204cbe25e8d5798ee4187c6478ed99443a591507d17ee3f4044db28af9c22619afd3a4239f17ec93c277f28adcd5b12617c01685ea8c298b4fa3d65432d88bc90533c25157b51de23d6e2949123f0c450d880224a9cbfed72488d92710e8b226ed4321e6827097a4e04bc62e2e0a1d64c3da301fbb474a3afd9bc9cdbfe5fe9b20f199d9f0a907e4e9c9b7781d3914c58569fa7036dea1014008d1ca956e1b0e3c39d5e9635d5bedb8c92cc67d04a3ffd72f4696a91e01014c7d70bb6622a11365cd44828e603513073a02a899d4b7a23e85e5a2bda008ca259ce31112c100d47009363ef61b191bc7c2f60784631d254fd276a92219cdcb8de3125580c78bf433350ff14ba7523a0f1c12370fb631cd1d121e115a9be519f7dd5c922f02fa60dc1de55cb7e4790180facc5afa2332de183dce8bee354b21b45c62d69236ef2117f24b4405fee26f65e12f22c25bca9ece60c8e7d7dee37f2c11ec111aec2cd05792923b459d8e705ea15603cdf780e8559bc0a233c572a11c44f6df3d4f933733e9aca49e091339e428d9152f693fdc3274887c1ed38afac03b8162d70c32bcd8c4cd123276b829764c4c55610929aa9823c6b90c8ce21238edeb5e85faea737c4667331cfe8021ce1b669c6a789678861770b2a3962896746e7d7c7e501f18f50377e6323d27fadba284e56d9c46440ddd99b80e0fb7f5790c79193e6991c8db54f059fb01842936962dba14f3a73b0b4f62b1ec615d9d9c4d6d16f8b58f5f86305e517f4a1e0f6f61257baaffdd9b5ce6c01a6f85ea0dc58fa0ea13706ffa74c43727a1352e52f47229761169b983a3e23a562d7721af8002cbb03f866f0426f1e1801f62f99bb2933ba87670e11faf1e3b5903e40da79c3c87b1f4f819f9e01842de0b853f88576255d8a9ed0af895fd66348f2a07b3a5088497b9551c71de3680ae0322c1aa22931084f63b6492df2705fa031976583f01e568ece8af6c4d79ce5f5c59859e18135340d925f33139bc3a3731fb57177ebc4469508db24e289f37635f6008889323ab619f542c1f4b14be1d67b08fba2b576748c32e764558742cc1c40d583fd07b256ce20f28c3484c020155d8c8e2adb1110e8c9e3a4df7c2b33e58a9a7e91b64ea71afa9de5c7a1477e44288d3969f0dc465e5303d132924e1a10225790e101df415554b9efb7343201ce30b3b7ce9b45930a0b5ea74e296274da48b24628f4c94f004e06a5c089d88ff510c7d32d020ce5749f9dd54b6144708df323539aca15de33859670536ada3ee520a7b26f779c21fd5ae9b08c9298dc99eaebf440c4ff7e789ae30a8d221a2818c72bc07627527953e2e6e5037b9e2df569de38a95a5297d9c4db126b4c17b7054d2185549476bc3a402612353abe2e2d53945376a5cbd34cea40eb305f028f655648a4db4fefda2b4b67bf9d85739d13963ec899175688d8a2d0c33cc15831e10040f02fd3d316878e79a528b78a0c6435da6c25ce340d11c9fbc71456e2794791c94c214d9052118ed20add31cade11088cf3a407de4bbe8102d7d28132a9bec53c4881bd99f47398332c0cd517ecf456fcc1179316f452c7e6c3249c4a0165eba2ba422eae611a5465b3e8a3faf6b35cc5c1a216b649e4bdb8afd3077940ac2635f7eb166dc10edb075d5e1735021fdffd40b51fd28ac28cd742b756b4db53133ec632cb56f5942347191cd7e6302f14fcaa6645482478598055d683c69c1a592c3f6ca1666aeba3d043e40afd031d11c79084d629c932679ad7f8e465826f199954919508baa410c204cca2a60446307875713040b242b6a223352e6452fc5b9374f90ddc0684fc98add717c36c9fa0111575e43c2514e535fab39d6392017714691276deab6621e547308212a62ac848a8d78dae616383eeb0f4326fc1ef4b460d6060eea79b96318c8ea79513259e56c95160e62cec5ac56e93331a81a1639eb4b8a6a687f4c1db40cd5850b11a45db9b946fbfd284085c10500c37e26354723ce43e00d335e227acacae44a6b3539f363cd35fcc772d8726ba3f44ba304f0c2f9722b894eecc9c2da2496672c8770a2ca56c63764ca5738923b8b37d5c42a0251e6b66d6c7d9abe487c1e7231dabedab3215adff85e1fc7f6513aed6eb14bcb70c19aaf24ad82d413e364b531ea42f54cc7858d969f07116be9bb2e0300c32915da84f61a6b1298c5472abd8e95228a1c660a62563268545b54ecd57ae16fc10aeb2c9eb4855e941ec079276aeb134b1c76d03d8f5245b8379f22e82b500939b4bc17639f547948145660c96af5ce8a391dc34fcd46d3fa2b3ea23c6745f799f3c4d42e8952de67a925d8f1189692d24d0f8049f239026bda8b68ce7d62a5c0ccf09ca400f349a7631529ee4e26e7332fc4ca9f623c486b32b20edd172fccb7354c6d032b3b9ecf5a5cb960c5cfc7c12ec65bbbed92983280356d989cbb4ebecd6569020aea7e20352b3bb2fb69aee8a6a51df0d067ce3790361571e73820820437ea77f28791374a8904907a70ad84b30595491009b21887301019d8cde5688095ca3b56317d39894ddf196e1128c5629707ee3949ccd528ef7c4e7a0754c231dbac62e0577390cbd9311f60ec3cb8e96c3d4f60a1b4019771bdde4bd4a7e4b780071829878b16304288c69aebc0d34444f6ad1a07dc9f2a3083f214f54b53511246c4134be6b180219d9fda0be16a1c4d1ca6671df8f1d72fc22fa607704f52c9c810cbce163c7e8bc8843a770b96c958ae5387cb6df7f36b210fbb3459225f06b83e6c7bc8756f9403ba56ccc47c59043d04d3b750a516e60f56928e3860504956004554f6b7a0affb84a0b536980414e880133edd9fbea08010fd5c0918a1ead00e6810c51030e0d6e17c14d3ef862857e0dd2c503b7db37c234497773a5483ec6d7d9fb7beb43b7cf25b7ac86ef370a2c42ce03b60848e6f18930b6f9fe19961a6450ac39d9a55de5cb6943fe226ca981a68d77a989aa17ab483c4dd957bd2ca80f6e5f9f61c0f5a5f5967b02d7f6161d0e58a7a2176bc64f8bbb0ff17797b7a1796d40b092b78d907b9970055e4537e2a6e3713772e07a58a7a326cd600c2add81aba8dc9f1466474ee7e126fe0e021300c97162ca537083d4c88bae6313e61c46bd7ea07e456504707cd1165880b636213703c068645cf205d855307ced1c81043a691c2351e6e571d93375ecebf127be3a3cb2c2094cb8a66d47dfdf26071603385f8661f65d099ddd7c22648f6c5e861de69184ae19ca63ee7a1d68607f56aceb9e369b12e7b1ed37261d9dc1361910e633806d77a8ea7a067d6cc101f39a2503ef46247f505726e242c3d2103140f401986032a030bfdb7ce5a6b1591dbf6381ea31e88dc47b03634fb1be460c0d9d7d91d7965e67c9e70a5c833de5d52cf12f64e94ce265efcac479f97809ff9ba13cdb4d7094635a1a63fa04eba1b1b1d3b71b3f2c2b2716cb92b0aa7922872de90d4be1bcf06ea21cda6653e7f2df5f3aa0b33ebead26ab7618e26cea3040d8b83406460f79390839cf83dcb63d26280333194036c26b5716aa10542667907dbfe5d3f1197b4aa0c4e85596d297f8527defa46280de22946203a49a2b48b363850ff260061072a49e42a7aec7b10d5b4e672716cfee4ad125bfc9741eaeffb3380c34cf02faf2be702901256a5ed80e4f684b87ffd87d962c46bcf7bc11b667d728da01e4f5f17adafc0e7e61baf43e3636b695266a2433559bcf5d7698fc913c59b518aeb4b6c66e8e8c37d30ef931212733329cdbfea178dd2804411f2b6591fffd5144a787f4b3cdb8cfd083257d81bdc4edac8fe5c2844f8228b6cc45da57d72b8fb0f62f30e42bda5dd50fa2e834090f509caf7e35bc5a4d43ba829916776256218d8d43d1094fc580796356c45ef880bdd476bbe9e933150892ef8a81b897fa46de126617d52c23c9d9d0aad823cee0e4f2bbbc0758b2ab0ce0d4d343a89d4779083544521a3b80480319c6f7d9587e08064381416a74e492478bcb0830a90598b90a2e3c115ded0db6fb04a438da79ca254c54bd77558ce2612800a7db2cfe7917076b8df4cfa1cf1faf20a668dde5d128fdca12374ec36fcbb1f77f7f1c9ee2bf12a744b6b53137edcaa9b776ea94f34c1e4cbcdf2bf02092e0a691e2883f16c852b119eb012640e7cb697426b133703df66e6615fc8753cecf57e28ac8ae88a6bf800b5da25343b89b6f868393bf0c79ce2719fb825b9568569be30bc3d5806aaab6a19f2d056857d275d563211d0c11d40d06f65d7a260cfcbafea4a6bcfd422c60ec284abb50a98465537bb4a7c83322aa591b6429b3a6698913153b799a0bf09cbefc7714b60b7ac6d3e20f984bec3620f53ae44153540daa67034fc85b09aff1f29cc015478da5559651122ba8501a67ea0ec02db71dd865bb49b381374fb84a23e44644badb7f17a6c4f606f6befe6b6f89a0510a10677bbb4d7e9e165010e3646b67beb85eb102a628ba121075705e536eaa2d6fe55bf0fbcc4250cf007ea711cfe59e5b6720850484d2890d591e5d12de986e75afceb10620b96f6ac0e27c8ca34fe4ca17b1a0852b392455912f45817f39cf02a968331031c823ed8e022c0d06789c61eda6832e9c192ce590b5ddc885d74fb570828880086f5ba29124dc73c9e1f18f8cb83a1eecc8d51ffbb0bbfbf4e671ea104004c4a97b1aaa346a87e02fe9373371ee006d5a4d4012c166c0e3f40a72c77a96bdde65caa49d3f084aed6f65c532abf408ae08cf3b513491a7e09140d7355c89410f16444cd855626314a3bdd0af929772252f17672207cc8239ddc75418d3aeb0e598d4263cd3eb0c4122b866b042d4fdd6cd32b200b0ab1e8fe08f5ca4dbc302807c2cdd494b91dea769564b1aa9d4d83529d1d786f471db47f9dbf812c3843e433611dcd4b07391db4d6e4db7ecff3cfc0c1ade2f451be34cf7d00d86a482614d1b015dc99437ab043756346f361466ffe977d8c6a4bedf4a37414cf7f8814a98a06a4d11ef139081432eb85e856f033ec62addfa2f91c683f52f6bc17108662b6cf4a899fcbc4e840b858fe3127d9912e4ba84352f88c8d34b4338473dc94c206deef759957c90157d712a0e534a485a8ea755d2acd8ff22b5ec754c21487e5d96551e275b10e20338bb0fc243cdf6511a04d66dbcd9a0c59b1657c9a924c78be832b1242a65c02f041293079ab2c598aeb600211b909fbe694ceb1a91faac3f8037dc370d8af630e4637d8644b517309f9074aba1c8edbbfb857cc617e972f10c672ecac8da1ccbc7c680d4d8e38dc6f83865446736e90b4b6540e1145a7e7d4f201833df2c22317721320a43f922a629b197460b21bac26470e3012cdbc5d384c5bef5209805648a41a4c5126e3c7129dbd1d48718bb316f71012f504fe1687cbffa8358f0667e528e578a0463ab8839ba62bfd36e8216d8d9fdf301723527c20f87cfa612652e6f657fc92d57f98359e733d481e8f1cb9ad14cdf7f725920572c678782d9271c8733aa1629a9c9dea4012e5b0e8117f1bfd81c74324a28d92661c9376196bb0ee04e3883889fd99a18a4131d189d9a0ca03e73d0d6c167d53c7f58db13aca35a323c572473bd03de31c1d69aa4337b8a794cf5ea962fd49c035137dc600bdeb23f5fbb080f30e7bdb3c5de8a24954aa534d0ea27fc20270eede74fc4d270781c39119a859af32ccd19c9ef0875032448bc93808db1c4304694485f53b4dc507b52879a027adf3476269a8434272bd12220505682fbe978702073925255462b3a5cb14d90742cc29592266dcbbf28dd6c3571358bc6428fa28993e333189c60b9821cc89f813ca58b0c6a3da74fcf0387f888f1943a1e3306ee4854577b25500dd28a84ac5fd1026f76933435ece7f38bccb0ef16c744284abf84f3a24b9f03d81a3c09449f6e2e7f684ae73749e08bf4992cfc9d7e4e95f3045ee15746c3aae5ff62b10cd9fb705b9eabaad56ebf5e1d2a4f975dc27965097c833016d59eda716530ad9e29e58e2f2ba171399cc3303e5ae3907584bf5346e6b119f0b0a9fb2f5a617bdefade95f4a36e2861a4ccc047b3a3f655b4ff9f65ed139f7c1308f32a22c6f5b56c0d38054d024fb26ad4b7c6220ffd7912ccfab37b59590f1a3490c53afa5870b8463d7e6251a9448d575e4327039a2416bb65d25cfd6ee136d595e6bf28e96b60a58590a805f166d4d0c8ea09eb431a81be04dcb6a5475dea07f0593afbc7dd138a9ff99aac9778bf440d3e8473fe34a663d0c018faeb09bdd5c1d283ac464cefbebc942b49ee6f4bccf3c2e59248cd7bd3c5c0d93c9b68e34c70403a73e2d839b36ad4b424adcd7eca3a4b34b2e4aa294c847bf977559c58eee92299ace0fabd21d7439c6659d7c74ddc78e9d0710354e45f45f6fe396a247be17fb7067eeb344b000787dca4e003d133fd045d75c97e176320d3264fa91324411d3d6080cb99eb2332a26f204cee4d7ce2d8d399618481dca8d8bb577a183f1ecf971c9b94ee228dce1384ee7c76a9715861055cb09a933ab602b2920d41091eb5c14ff15809430c901aaffcd70ecc051e87fa6d62f9a97e48e4a60347f96d0bce2e8dec8ec3ecafd291b9a2dfdcb1219650d1bcd0b4685a4b6302c3351a5f030a799172a6464d3032889fbe1777d41f42a5959442551cff5242c8247a03750457c416f0662b6041352d1435855348f81566769593b1c8eca46e86ae44ffa4eb90a2af3789d3ab3c893b54778742271342e390c1c686d59a22586fb3ac42439011697dfa5127a83f1412d36e3dcc7697087d07b6fa472b9abfa79535fde6adc820c5f0e65d1cdaec158fcb0fceee5641ba6ea5623b95410ae1367bd55a265c78805db58af2fc062916dff637a9ccd0afce96c349867ae41c7a1686d68f056219aacc81a8c291930f9f4602a9e861aa0af51710e05c594434d56445f23cdee64b8c5d43eafe2e6904e97a8fe59b36c8ad1e7e8574ba29ee1478a180d0bbffe8e5db0eac4b47f9f78d91285fa3414aa7293409848037188c0750cd2a349948b7c32795d0e48b4566f7255249f48d56fb2d1785ec3e4934a3365cb35af90b02ec768685ba199072041c4b3d6fc7f099495ae99da95255adabc5fc6b3ea26ee056a445950003afdc03891bd0964ae84e6f6361b98999515378da4037c2b2dba5d5f91da039afa378fc7a382be0079792ae9fcbb5e2920e4514c155eb025bd08bd142b5bc67fc3772fa6feb355d02c50f969b281e43804804b5b01703c9fc386888d4f3bc808c61412de13a3b70e66518e56ec8de304b2901d789effa31cdc8d12331663a1a9c61f62c0584c99f5ddd782158e3df32f5509d191e06bf97defdf704688d80fb48b68b42b0e009005d8361b758ad1fbcbf655c4b2a965449a359738c91ea7f13a528a83880022c893f51020941815c8f6996c36fdf537c9ae3e11272e9334a57a911f8a563c47089a4f33c6b05bc95c002db0e040089ba31503222b6b54525837bdc6966df58548e790a38941b7b28df87bc6c7c9c66860a15913999769cc125ef58f83203833625062ec5675aafb700b489366786c14d621ca9edb5297f590ca43f71a2c93211cd7e7338dc1dd8ebb1015c5d12424470c46a0dd7a8f2862171319cb482092da1a9866e56b5f20afc1cdfa0247d783368164409d05f95807e1f7fdc143ead9269e916c125dac91d2cc0b267fcb641e4c08a28067b4508b77d106fd4849642696f820982df7344ed9910a2004ccd8704c2cbf10917ad74c242e5808f9e39122299a3ff4dc66f3ef957666564a3225cbec265086bd05351c3ecbff9c41219ab8cf3ef1ac58501442b083c09a8d43b26b8d1aa886ccb046d04e152f471b77432816139a18ff6f20edbde91351ce7c4f4be25a6109812ca62dd66eb558ad28fd6decc03263fc7ce143f60af7575d2048213b5011b78ca3e2c78c64dd6e38291b46b34ae6abc1f188057d17296bbbecd5b08c0395120211f3e9e2d588d34975f8c6cdaa3929ef58fd1f393b085bfb0deb11b433c449961c48195ff4011a493492fba7d4e54e94a3c38d1521280116ba5ff19b627d858898c403b4dcb0d1c80e39fb9acdb1b836d6a7605e93281cb1f64746e6913a8910e27abc301b4ddf69a77038cacf1f36a4ca51706aadebe9e0313585adf901482c0de6b14154724ff40308a4076d024ade8b088441d7fe1d17b15bc8bed23921b6101b05f9436b382ebea409e6be863b340cfb103608a87da97775e8cd056c688353112942790ed56ab9da46322977ba0c426af2046bf4025cfa04ad8809dd04d9321f7087b135cadbaf5126dc5c1182421496f8b50d230f5d9a103f0fa1a2d42b4407f876a92b416be125ac60f0c9c10ead93145134cd1cd8b968768bd42af51da46073c0a19dbb405cc30976c1f3874324bbd425e846303b8c8c01542d6863233a122c1144910a8b2396ad987306a4a2dc8273a40d741b60625150922d0167738bac7e4f6b6ca52ab1cd0ada5e55d5d5752d9019f707206290d49b31c3018af200419258098cd159141c34d1fd803ad74a382a4fd32c57d11a932a0c6d9561fe176513e0cdbb61cee338fb54769c20084255b3ded5093fe0783796b18b85d5d93e650f5e39c53e317bc0e881a75c542a36653e517bc0525f5370c68487183b6e3c4f7e9e9ef258a0d67e09474c70c475e761cbd19bc62cb9714c79e9cc13e1d884e4c226f133e181068456373ae0cdae6b2d141dc72b477b0d864a57f5b5d3f32b6bec7e8d262d8a694a030ea07071418da035f40d4bed45df3c18aaefb09817a51e667e81e97d077849033beb4ecf4be80b504de9baba66bca16141bc89bdeab8bd66af424b266e6fb79d09ce85775615d43a99ddef8ad2f6813a7b0f8603ab8f6e6ff9b3064fef8b1843c8915d4616921e58c93593760469b8dad6017e6b289acf31b8b2ca59570ae8bbb622a1538cf7bcd7e41bf59101d56634d39064217d21d63ec8cce3a1b746ef54fcd124607fb34893792122e5cde06d875e2a7330c20383d2af3b07dc37a3389d1ec6197ad2e0586a46d700cd656a339bdc00792a796e3a07131a01a9f47d872938a326074abd889f81f8fad0f2634c1b8c6eac8af67ff9fc9ad0c1a862748e04fad1c8454e6cee6fe83804a0f16f68a6b4737aed674b5caf706e8ac8ef421e6e7f434e799bdfe8426c359764492900d6a7f1742e3a32a0277731c82430cb7bf2c4fde836c50632ee7cffdec82c03e336a1042d3307239ced5b886bc13a23e0a7bfef22c1721d97214e8c50fceb2b4189a583610eda9ad21a0a60866a165a799184b520baace846a7e79a6ac1dc74cbc53c67f30ebfb622a9586a3714e9592da159a5debe8ef376708fd5dec1b17e83d5ebbdb41814935a69ac4c5194d13927bf7fc501a5f5fbd929251f9ec33800c5632a1236f844a75b49106adad7254e06e48a1b13ae4cc0f09463d6ff283e68cd7a01c52720e0c22c9140447295798afb48c93c242df820ff06d70928fc6959be3a16cac9854b572de7f7006af27ec864b576f3b4222d91ed2aab11e66ae912c1040e0a1cf1cdefec6eaa263bbb85ae81c724145f093520014597200a9710d9127f0b3e6601928453910e17a85f1700038486bd8c132556b894455130a96cab26441c0bc9510a2977e87ff002991331f5aa8d4700632fb2f11ddaef0203ad8c14e2cc2cd7dc0163e03582a78a78074d103cc4790f0325056dc2f044b37a80ea8397ebb9581c5d22123415506f9a081e9424dd3413000460cb144a8f093f619971faf6d98a3bded5c8150e7413166232ed59b2a4f099696c62f47a84c39930d26ba981b41aa55d0469519d6a4acb8269aa5471e45c94652ef611a88460bb31c7bb2596fcf65929e2ca36297de3ff4e6027e4d503135649d8fdd91669d324ed39a1061a3439a53a5a60a326dfeb45eec36f832018b5b444f0bf1524b45cdbcf6133bd96a4dafa1c9efca189f8e1260cb2246d8f02c4842406161ad13ed4a55a87b294eb4789fd175437eed5b028727ffe7439b1c3b4962407c30ae26991404edc855c4a01c5b54010584e81680ce4808f1ba314b00ccfba5f19c02ca4a4e14357bfc41f98a3377238f98de8844d1c3ca022591707774f4352b2a1c5d3e856ac9f5660bcb587e8c3580d6c76fe9c396e06ac5e8040ccaa46037549f8d900d9c29c40f96a1863c86aeb7c375f5d3c484fb534902702304fef264025ab229fe65e509871800b224b20ccea288fe7a38787a3b7442f300101b79ce690583384cadcb2546d468d34318fb3280a853cb6d60bd4f33d2143dfd03c062772ca6c9ba4317a3b61dc658da3e35c91a2fc6e2b1552579c52359a109e27667e7294cffddd04a0937808253b7f84dd977f9a1e00b88a219ea7dc50d329fdf0d9d0ca0c23882d1aad09353bb1ff4a53f3250949c67d1da8d420556b4d399de51a0c1dc77b64dfd5c669e6d84efc158fec2313fe629cacd1733c4a5f8c71a77218f2a56950376c64d7302c4b56c31fd3d44959d6afad7bdd39dbdb4d514855c480f23d106dd50844209b0a546723a4c4c82b2f1d71dc3460338f8d59c139c842d5b22de4709c1317182084200a338990fd59f7ce54cd90991d2128454a0823be45aa93e14446f90d808f06ce74c76f3ed2ed03b0dbf5cb80fce8936eee86dd636dba53a6c66c51b567c1262da575c4e3f6bae55ed01830892898fa9578f4596ef081fc05ad20c6c58fdd0dcf9f316b42eff73de75ae1737f10f5f9aa6898b7c8838197a4cffce9ddb44465c92bb6e77a1f79a172ded3a893f17beec8431da52cf0ecc0f78ae3d6ecd3da29eedf8769669d3e9bc7e6e0e59a160110e4e82af2894800721638c65f60fbe3e80b6d89fc99b52cbfb50292f3c7f513bb221f42234d64197a56260bad1f06df318236cea18bb5700149cb01473e335d7cd6ec47d4ae8c902077106287d67db85809837bb821064c9ae0ff9552405a68929e054cb5af4d845b6d342184b55aa25e7efd55e379ceaa9f8672817762842a1958162306ff629d0bd6d6c7e991d61c00343e2b400244c5f483a163a9a76ef38b8e8750e0b07d4be5716ec971e0eb409db861747134338c8989bd4325f3d7b5c999cf7ff595f841959225f04d5d170522941e4242a972e12f5abc280ccb6738669007b59871ce7597f9596b003b41c233b50f35c950793c3a625898fdada168fd0d18c81db19f51313161545845d8db20b339eb7acc5cfe4119b746d671b1e80c3fcb29a0c86b22088fa77ac035a07eec5bbbd364c0d7c4258c3591cc102844fa863353590206604838b78558ecb075a4cb6738bd2e4c203ef9a6990b8077c627f472d0bee8c2140ef111468d8afa022d9d76117ca87b7f562bd0892f16b574a924ddbee141166977844943c09b3e65004a5bf0c34ccc6c60b70d774f6758b860de80cd08d5dae3d0951c5bda3eb9f3ead78e19b26e2b16cd280641c4a320c051ce540433997b337f5f2110be182a954eee183ef8a93ac4d9f250531e5a921c79e78bbde82d4995da489170a73f9e4db0df0c203655f0e52f41305e35365908131867fabcbe1a54d0198c1d001d11af417a6e0a7e75a7fceb8645563e33fe0ea2aaa4c4d84c7b03c7f05ec37e2bb1e011bca3f17df3076dc4ad2d2d242c42ca857cdaed821a0a95438cb5018b43e305a8d188b890adb3504180c7536a90daa81ec34130865db10c82f9501b67a8f50a0c02fa63b8a8ada4967fcd9d7096f7dce32d854e0e25339b217382d835c555f6d0d840f72d0b1a8f8078f55d7afa537ebccdc6a75981a74f60f7f11b600408a7e417ed66b889f940fdd779c7bd194af212853687378f56a0b70fc89b47aebc32dfaff70e392f74382fd14a06f4e43a74479bd371bbf86a09ecbb77bd6acd63e2673d392f61330dedc85c4531316016c08a1c3ebe72060b7a0df1615da352da4b849123a5f0390e3536b2b020899d99ad0c2ef0d9f963e0d203764cdd806f92e8485ad4537bac7756ed400946897d756a569b00687daf7f519e73b75161f0cddcf79cd4ab672e45a6d8e97637a3b2ed1e520a983782752ec266d22ab3b75479ef5a9ef74f5c2ffefa4523dea50a3ff507dadbdf1bcede271233116c70a9493fb8943b903b84b2b340b63109b327c12914cf9f200eb131b0e0df5f7982482c37bf784881c7d8c294221c8b989cf0dfbd0367f07202207ce08d0590e9507db901a2def4478df167fc7801bebeb7633ccbbe3aea84377b533d6d00c90cf9ffa7e20d8809bcdad167cf47ad4ea2790177825eeea966c020f83dccf087dcc27cd05c1bfe448419ca557c94d7513dd0228139b7ea58561c1ba303064f3b5b90580feab4ac661677cb373f9d52d8044c40e120b5b4459c9f795aac1ed50be1c1d18a72703a42ab317bf108bd2c701a955618b240e2fc14c22221085061c28cbcc83c491780115367c83057491b516cb15c1fefb132bdf617eb942fea8cc97526fc2ba21cd3266d82412876b9a0f83578e7faa3e631c16f8749329eaa03d7cd9718b44d2c8429539cfa8eed9ef2d634692c048645e5f2c641539c83563e8514f758bfd2850d7bfe15024421875917ae698cdc1108304ecd83539766e0b22e820edd45121b336072e67acebcb2e1a9beadb84f06248e4f72b0a00288ba59f67ccca91cd94b56cc6c1e8ea77b385bf2c7ce0aaf026c12fd2d7428a5e482a0fdb2362ec17be85fe971759ffd054919a45cdffa348ab0e5e812030159cc03b19d78b2d57be5c076a2d09f54a91f91a49ba38deefc794483c836ea63ed4252b1312f2617c9948a867d6d7c9f95ab61fd6af15f95d66c265bf9a98fbd75c3e73aa30e0af0f8b1e035b13a9d0bb613e8de0556ae00be92606bc95a90ab20462a7cd6b5b9b5fb252926704257f87f4e3887eccb60620efbe4d17cb84c9424c342f989162ecf423588cf36aad32cc19b0b0eb1b1ace3a87092bd070b0464216d203aab6ddef2b2758374d2f10094b4152c7ba5da4995e89bd4765a7adbaa87d5762846d79d52e6621c10e203ba175e7edf5ede13e761c2cfaa5cc17e5cb64618f69853c091ef9fbbabd8b57c9e2d72a14e23d3ebbb67010d8f472c9805423df1e68dcd526e21891d5b0f235e7a59aab86e75f882fcfd28fe8e2aebdd6d2b19d8b9b19a2807349686eb1606dea25cf97b2e587d203232137924fca1db45dd2be7de55b6a760dbb01e5b2661b3381994eb61a24ccbda60bd322646a7ac4114ab145a1c3473ea36a6628a5fed87a616fc3ea760ca41579e138b62731e66143167f791c88a67820b56292353c9f9f318db9a142be455127972a298055e93315f9e548c6518e1cae6fa1406b6ac8f136f4b0a217d2de576672ea170824b1dcea9dd37431707a0bf6a12d411f163bb30d90b5d972394d7b6c32e87b7b5b7b88f6c88e265c2324565f92fcb17008f6c5f9e99323271165a0aa6a81494490d7b935981dc43c5296da0ad648d16551654ee2b7a98bbe0f8d0a5841d2aaad430aa8cda85a2ddab1be7d2049f241c3e5baf1ec8d341b36f8eb49c1ee56083c6d28f71cd6f4653cbd98507081e795a066e226c0fb4d2c6e1fd7fb7e177bb0d390319b4e0948c720ba8b1ae58c6280247b777a7324eb1d57c9fe7330ae227f9e4a49350e44726936775937367fb46b624b6f69ce3a06bf0e7f9480d29061d72c495e0d7b4c9d66289d944857fc37925368e3c34d03429e1e5273bc27918535a15e7336c140ac8124e9c2e9b561880179812cd08e33ce09ea7ac52197b34c9da3aad9435be0d6aa53357fa1303870de46ddb4358694ca249e4632b25d56f4b458f9b0dcf5c91764bf8a94524c589c3756bc0b0af4f2badd46e6ee0ac4e6156723f6f6746312a50495dfe4d5a1f1fc85412b61363b5b1bf5f6d29e1e2c6946868852701d5bb057df911b71f550481dc191d8901a445c12b698ade5583ae67a0f226d1247d3fa6dce4a78a0cfe98d9b4b0581b47b88f36c34abb8e7737964df85e012e77640ca78265a13f4d3428329c1e53f6e152cf4715f8b129145142156d8dfff644233ff2d175d79f87bd37e1948fae2f574b1d274b0ec13bde05ef8db54f1982cc795a40119ee01099662614e7201635d490e7243caae301d912c0b703d8dbba7a78d299d1fa70293202bf7cd10a7f8bbb3bd231bc8a97b0b0060817e13e747687db9876febade1be570d631772395cb676fab1fce95322ec350d9672423da22ea02b930f3bfa90bda31dfd614047feed67c1e4cf11c4ca91fc2f6b74f5383521e5c1cec9e3d81a8e5d948b5400c87f9a0fac49508eff3e1f95911d5dd13f3f1df388466a3af4b1c397446e11f2630b55b061d9b76f2ff7b71885a8ad424ad8f4da9f5b901f5707b438c2246983018866826cf7eade1b921ec3251f5a6283d37699964998d56472bd1a08fe463da4a91ed118af90bf3a7ff0d49fc554f5aa79e38836f6b19ba777cf37f8241aa725dee6d7fc0f1dbd3e059c6b69b75dc280217426f1f3a251dda3fe726e24a0bd10bda449bd55df63cb9bc0ce77f0a32aef85c78d69978e9de58392a631dd21c0dea658065d1a5c2597763c846ac512e0cd160e9448d1d7cda8a5198cd8a825434d4a0170cc266aaef5a78c7d030ae80f136dc41692473b059d04b1e972943d86086ea019511d615e21272599c051e54e458b5873eb3450437a251dd7fcaa5f6b549f1cd2dd1c82dcb680be32a1af7f38f20067e7a249edaf75ff4699cf5d25b52ccf8c35945cedb620f0b9236b267e804141012da3b543abe6bcde1f1e363621ae94da82f61cda86eeb1b2c0e4523035499201ed5ec1933f73104ebaf827eb8bd1fbaa09f829a7206e8369619a47be9ef37f6563e31271c9cb44bd580747ec37cb50f32629a6233c07239cd8ca0b091cb24cce0bebaba8362701d7570b8d13a960d6ba7316ccb9d12737436617e8cac9921a7e15604cb692da81213980b4742882c2cf0d38cfe221b8deae1d01bb26e4ec56486e02cda2b172a7944cc79857a8744533ecb0e5d8d59a67be46712c48ba5d2fa34fac8fc8b3b12f3bcf5a06844ae2c44462761d752a8de2cb38b0018e868581ebacd22690392810704661b5b3536c8281fcb7e1ca7f216892a1b09abe4260a96cff14138920f514a3e86139bcd09875be55d9b45e8a674980170c2731f53f221f7937c9474a400d35900bc75a41ff4d50beef98cd403e4e7b30a89041d6a107bb376dccbb081368e5d5e64828a2e302ba0b5123c048249c60751ae5c747716ec4bbaecfc1c687ee3c34bae01e5ca8c22e1d791f12a72be28d16c4e6efb85c162741b27f2c9d5c7cf704734892da45aa0cf00062c390c5275fb52c7a2f900c1029afdcedd88cdde606925011a85c42d6f3c70af95c399ca9a7322b5cc23e58543fec4ee52a87532d33106f8a4d0167e9b7db2f16e016a0c86ba70fe6c03a712897083ead0d3b31175c331db5963c30408370c08b11e7f457ea44954a54a6f1c15ed771eb2a0fb1335a42967fd05809a1f6c3c3376a6fc80657a9842eda066c91f9fd9043b889f822f83c11fb28e83fcaf2864e5785d0ddbbf60da73b86539feaada1b551eccde7df9deb238a963666179416234587ffa89eb4e19b2a992ae6d68a07e18a672a958df1c69c1f872a9e2c9b2cea80b223aa626b3b23d81ce4a598d2d7c374cc978fff163eac42c84a6c0cf88ca6c279579ded7d6f621ab72e180f5b0279c7b72dbadf5e04ac30d0764834e83d5f15b1e50a9046a94c6fb653af7ef215a08428809b8a58032e78dad5ab058cafe3124c000144920d85333c62dac934dd83c7060d7dacd811dc81ad13275a020e202f8f9b8632577249dc1a8a2dc25ffb727fdcfdb7f9af9ac6ec8a6f096260f7af709b689a22dd2f187ebb73df5e439d2f9361a14b98c48f9eca0c5259abd3722dbb3bbf98b83ca4f440b6552b1837d7aef7c607aee0a78e18a0ff430591b78f62cd99fa314bf7396733b2e7d946b5ebc34d0d86158bc6175d5cdc99560970f34e1aeff5650cf47e4237cca0b53a981862d0e8931b0ef3d8500496e04dbebbbf15ccae0da299dc0fddc597027e4eaafef71bee2d6d74829fe1dbf3d2d7836115a80cc5aace84838ab19e5714dfb010b8c79b0008c6f8d350d3c8687dcb2e48ec269d8ccfaa9d773e7207e1dd28a3857144a4d52057cfaac8bfd41a34321509e9b52c16358b068917d5832c6654647c4e2d305072df04c35b2178bd08b8c29e91a417128ae5ebb95df1a24a06d1f1b1eab3b5b1ddf1818d613909ce3234242c2aa8cc5e163009bd4403e03732846ceadcc36fa935d62209bad635a69c4e13a71f4bed4f81a9d514aecc664cbfc638763add6f8b99ec84a280cf5e9683e07b20b11f1cb0fede3eb5ea69af42e40ec973ab65fe40f5bf283aeb72556df84c4808cb28231132532d2dca7f78a99c857e7c67ef2b1357b28b994988d90a8640d8ed9083d315e71d5162c7ce8d08d4660ba990109ab991ac399b951ce4c46b3fe7873a39021e7e12b04e653a305ba793a54003c46769dee3197f2ad465a298cbc42f3d7a1f480efe98d0253cd95342c4cd22049cbf0b0c267a104c8b33f961e2af95026fbda6d204736b18b49700f8e87a21d096acb73dc1a7ff29f49032c5c2e7a45594740fd4800a368f7e08758f2450a803a717f5ec012549c04bac9163b8a99ab94cd5941671781c0f8fcfafde5279e14be8e5360e18d26e85740bd89085e1df9bc3189fdd34c6e9538f47d061a2c1fdf2529f4b2494a5f0f6de1e40b3156dff9b59f4299844a507cbb36f7bfda9c35454f47b3690986958f46c80710edd2d116c5e0e4faf7c5a68682439bb925a164969048d9e86f2474aba183af13f6dbee1e839e9c36e0a6aa342c86722b6e171280e1ed4e58531ac8129cc0beaa041af085c63c777a5a66e21c2b58b18379948c3f5e5fb3051517b889348deadb315425536fe5dde70fecbfe37a5e3660efe3c87e8ece9576674bb93db3d0b47f3a771cbd8e7fff13887b7bac15735d7c6d9c6ac15dd22a400f3deccc24949ae9219c9ca1e8fc88fd798cfe5ecc8d259a84fd442be39514c7d0b7fd08ef112fb70df9f231362776ed9a766409b8b809b043c681159bcfb72331de75cdd6bebd4c453fd704bf7dd295499067191511415a4536897bb943a6f5f5397ed9a8ab85ed1cff8782a3e889769e5306737b81249517ee2a105479b54f45be9d580f83d85c8039d9b34ad6312d86c6bc48c30a3dc1c3297d855d49ced13e2f353f9e41f252b6a83e72b6afd2a5e7cf54068ee99f2191935166c7d7135db821b3fb4b5b598bb0413b1a30d8bb731e19f57b969083ee6bf340b5547dc0fc121de1d079ac11336e0cc865d22986d3b95826fa6de54a45e9d09f63f2154a6da26dc785d33b36e6004414424fd95dca6682601fc1d61417713aa6a2e0ad11c03e6cdebe24ef83ac1191f5c77f43135db0e3f7fe515b185873d6f6ceebaf125fef4500e4754569303240d40e6d5c7308cb1d27efe375c5a6d613abbd2b196cb04becd63fd8e1612bdb291848e6089a5258991254c0f4c6f4737f27c3e6fe218f59b7e3fdb5423066cd2eed6fa539b4d0018ae4ad65ef9034d7b1592556e7d5aa8051bbfb112236bc6869385e19ced1e2fc2c4db4625d439cca0a309146071687ec3f7cfc94e3aa282c84f080aaab2e4462f841537eaa96378bb74e2c2c7e24f38a3f720aa5f37b4b34bc5d1b2e38ae1fc869fc8b558a35cb6e509670a24a4fd9d024ff6b2d2ce730e0f0359495fcd98a2199ca42b35613db27865dafbcc75c719767c48708733e65b53d052ea0922b1546b33d76cd9b1b0b11bb1b79aed07a20e8c4a99f0a37132c6b86903f556db9130b354698e3303e50c73aabeaa670a6aa297c1ac7e9c66f708fc5bdbf397474a34274f448d7935d8b040285192b6600cd66042fd41400815fa9e2f838c315c1a02fb820c1dfb36d5fcbedca77aad73ce9420e7e52a757edef19e21909bcbac302c7b1075db767ff16e8c933ab79b7ba4b8938f1ee2e759cbd383aeb18bdc883d21b8d03d0a312cb2c307d754889022acca3169054122d191dec1546eeb6b12b8bc940307c36fdb9105d512b5d631ac344dafd65096e51dab35d1c33fb1c1bdc6f535f8467dc5b54b888c6e58e2894fea19fc684dcc35e22e1126a7520d75d736a8ce3e91655dc1e8f46a99cd05c5411d72b6e1ad6b34e5bcedb860ed62310e96a431fbfcebfa14a444bb134a73251c3c1a33f75c11f66d97c813fadfcf0af55f00efd498ab1bb1ca18dda349098c881046e7989b162888d63841172a5bcf65b42af81887b60e9aee9f27b6eb1a1957e6b1fcc8f807642e568621dbbabae3abaa7d03ec80b915f155d01e6aa08f2f6d3e24bbb4c881a49dbce2de1d45f7f999c8336304838de2d3a57db7f23d8fb29026021268cd3200bd7d8937fdd2052eddeb68d8510bf3562438ea0eb21f5eb5611f4f8e6e0871716e786e552e4b480c5026bcc85b7103d65c7059dd30239ea05a4cc337b658020b061174d5c00e5a19ac7c3bc42c2b9310dc491fd37d50451a67aad59a6f12ea4b65a5077e7ea387589955108d7d838457d82c90bbdf0d2ff79627ff9b604de9d2cb55e10329daae942a4c5eec8c53525c53367b1f3e971b63f4a05e36faa7065ac35eb21ae0dd8dac51b045d4fa68babfece59979cb36c54cef4e4363d86377d0d667bf30263d4d70c9f95d620d1a9b1c584bc3f08a940dbd6db17528dcd1ffe1e68d28b54be1347ce0c91e3abb56dda5ba9b32ed448cfde6368f41910160b19b908f3b5609cd833827c990579298fdbc527868f01aa563851f094847dca8644eeb300d19dd419da702f8bd79b2b77a2d210d5f849bf6f771533a9e9a5593baeae695252629a61fb341478dd8151571873031793548503f53791692d5318d39d93abd6cc5e77f5683bea336673382b647829101cc83b2c49405d10a8a50a5b4ac733cdfd8f55d4a2cc36d2144ac51c95a6878a041fe2c79302e7d69b11112d4214a3cc2d6d54ad40b9b08953842f1ae0352bd98938bb615ce3b9197a8b39e25fc62dfc7ef784695315aceb4724bcf8d3c7a4c0105fdcbd8d8508adeec78aca5d536d34840f2d0d4d33019e7fcb75df9becc703243141d5cf935f9d42f6b36e3f67655845ab79add0f050c0ce40e7e28179673bf54c52950469aafb65303d0e27bae68c18b741ba8f7c1fe8f9633066d49c47b523c6f09d7e441895fe50a71dcfac15e0fcb13f5082d2fc94a5df8965b5e11ddc4d11d36f69cae7e93694e214a0996a0705ea35139b3f6b9177343ab967c6d848a43367be183948083883f4765312b5094103b66e8c8d35cadfe3c776154649a9385a8442c86a391b23bba19ebbbf1d0201471337522399b781b5376b9c3ded4db9a00f271904655ac4ae42474a2cc0b1d0f3528f72c57e5e62f800a2c6c8d19413e51c42f6081bcca51ef3d87f16e16db9597beff6801a76c3adf2793a939d296f302a351902a3a9dffbd185ddc24f9eba8d57dc001be14c69b5749c6f6c6a7eb6163b1e1571c6f107abb3b9a1be3f3fc0f888e69d07bf07c24be2e01913f7df895511c74ed0197681ae1f86559ecb64a09f869c50412a9d8ea3759f356aad509da3617f739cb1b95da1e19b5140e8c0ceca139274d2ad7d41247b275ead5039d27667547520f7755f9e2c5c2c6836e5f4027b0160801bfd3240e73a65d1a97f3bb48e431c241028e49eb846939baec30c42c717f4cae9f89a62d2fc51ad6dae1cccd268f606eca0c96dcdaae574fd31152524bc787c244c69b31543bd1c488b29f7101f8e425eef3c6abd2bc0c55b308b305b30d1e7387462087522117596cf1dfd14a079430e312f137220959a27864c8f7b4a2a1fdecfdf7bce75508ee81679731f32d1f7f3198e3f535bfdfa09170ff6cc0a95719916083a0e8ad9d3e47120c9e98ffc7e9398dff0d74fcb3361b51b4f24d293c722cb7a5ad8954b114192833d3b1953ebc1e8d12fbab74116e56d0e07519ad354ab86056234cef3704aa91c1262b4d3db4fcc12c7a554a10051f52800a0dc718c519effe3fd329057226eee2121d848fe075fc439955a509a5c1ff8506c68d54e6bf338995b6750f9433dbebc89bbc3be0b8c650f2352925dd5b22dfb43834f15aed82f3f70c32a948cfb11b4867088fe0569c2f5ba14c012f23df7076eba3b55e2cc70527d42ab4e57191f8a25ceb41b9d4d01dc64a8efc67fada379ff5d48c33a0e1c3521387fb0952918d8427b695e8862f40c222f20d30a13c7c6cc8a679fc2cb8e469018c977c31168ad502dd28428530d1b630e95ca75d90543b64da9470b0a08ba9b1d45b956360cd7a61e4d9fd5f339de6c508b4831b55a222159f573d55de262c44e3efea22dccd0063826ae5882ffa001f78930142330c4d652c067431fb9a574fd40fc436b4b04d3e9cce778c02569f12dc180e092e8629fc7c38a70867360dc6da0d5e37c9c14733816595ec3e418887e9b9119fe8100374c6823bf5d06d5d1d1469354971be8640850805827dfa6f0475dc530737d8e552701cb47a35498dc2e01285f910aa51583e810d622bd16b450a78089c1dd5731e1cc5bb4fbfb2911b3c8aea094426c16985f27b085e89ed84baf28f85eb3cec2d31c65116978a44a575087b72150ddc6460babe6a332962faeb90c4002887d174dd01bd4194e04488afcfe402d54b05fd70f9d19daddfa35e9427eac7f4ea0c4cd24bbb1fa04e7543df268f32cf221b57b1fe9156c034c4aab5576d7e91820be44060874136b4bcbcc6b4f27681575d3ac2c0f396b3fc9e8913c225bf35b8cbbd3e4825412f53c6b4ce56305961ae0abf8ee5412eae33462bc489b6c28a3821e2f89afcdbc34fac7fd573699e46f2b381daedc84b34f91718ecb7de5268734f547f15d21089c88b5bb1bb1b5df6ff1ba655212acacb9a1d7afc9927abd09fb7493fe49bd010d73e0c6f5597c7babcc094c915c4a1414583e6239f0b75537d975540e73f3e8bc0d1b28c073072a1b0596bb36a69696c514dbe9bed38ca8b624962632461e4a784c64dc0f258d2c383d0fdbca7a08c39224e0dfa6e13d99d70cb5290cec2d725181c86ac2e4dc3fcb2a5e630ac60ac4085d76b162abf6c90a3969274ed348ff8c2493ab00b7251da422eacf82247cb2f0b0f4f31bcd82ce43c6a8160ceab52db4d5aaede3f71f878cd109e6795a01b7ecb23365511cf890ce9a45609fe555ee41680d902141d3cb45b46400cb08e53b688df527b14ec84264d0ca6ed9b3615f55e4f4384d0c6d4489cd75ead20a2db945632752c965d06a402f9ce0b0adae1caf70a77ca665e2b3642cbf93557356ba98b4a46dbea590f6fbcdfe359717f1c465a2b7aefa88894f329c22f04e1e3199189a71955e727edb4638f849d133ea0e747b1f3043400d4a2b8d881794cd24cd299bce6cce65ee63603c56d076a4a427b7b86fc48be1f436ac3cb21d969014c803f0ab5dcf72a72e8293db0c4f0d8b4751349e144df9aaaefdfd875a98cbbf9858aab07326e3e44d91c51813cdb84861117bde4e5dc7a84f9bca8e960801c35d29e3b592543f5bf197982646e510b6d53c697bd3518e462d7788b04d66626952dfc99e652064cf68eb6993eea16ea02c30361a1e69c413590f44cae96e8d6a9e1931a45379a742c246bdb78a11497d6622eb2e0aceb201781a1a22b6c1425319aaaa10a9dad236d99eb1019b884ff186d6759e18948b8fdd46d6cf83f5efbc390a14e97f8781fffb40811dfd57bc684fece0db13aa6a1c214493f19cb0707d9cf72d9602293ca31bbe5b8d3834094a8d0e92feb4aeafad66853ae8c3b6dd410abc5c1a5e94137b325200fcb0545d6121a790afc49a8c8cec757f68f5991b0c00fa1086d54040d1452a819a5c1aef4f3738a6db25505a7b703b11659fd0897b62c36f951a4e0abcf1aa3e10e0c9e0e44fea6c1a95cb7c5a36d9933f9445fd45752fe1f1f680af3e92172c55fc159d0f911f9ed2116a323ffdd64faa074e649017f7cedf28515ce3d46cc6d01611617fae87d82df6df30546c589e9817b9694e4db98c63b0b9ec204eed41186b62e4dc60e69dd9fb5e3db4264293b1111aa1be2bf9bd982f2635445f53c1688e52143233a10d374e01895d072ab317b6e878de15a65f991f93d367e634a8019b4166927c9a24b98ac0725278a7dc9afc616e381782d503be9e49422b45377d34ac9bfc0089b037bdfbb0fc7eddc00dcfcaa597d181e39b7ad17586fa8cdea62d8dd906f56333cc6a831b9d03cc5b90c37339b6227489ea9900dd5e1d5908c28020694d9a3cf024cbee07ac85add847c1aaf2b0b62b303e2d512b7c08fe72e17c603a9b7c581a7b3fda3c522c3e30f080921431e3a4596ffcc941612685df714c506e0b216eac8cc7d9691ade7cde74edc2fe418da8b3fde3224eb882444a4392807d44d161e5d6c51dd3b771dd6a4bb30b91a20eb64766b8540336473dd097dfc3490e8de885f345995c8142fcfc62747bea4983f02bb69e1d567a7c0784e456d326a41e697ca8a8f342c28203df70ea7534e6df76201d93acc2f9951ea250261215ac58348ef6f32e1e7fb5e7e69d9dc8107d9a253b4ce4efbd5c6a8075b23434b293fab8042e9ab62161373a9679132b775394c8c6013489ad462c86a24431ed68abbda932058ddd126a4b088e3b4575752404072d48cef7d4a83e6ba7034ef779bd527ff4906b6a62e15d4869f51bd30047a7b60c9e29522d6b02cbe992283552200559a76552a5cf4d684686376d2b69572d58ef12c2bb560c54a9e17295c8727e5ed5b601eebb2ed7bc9d4f52001b12d5ab85123dd70db77b346b12eba8aa653bdcc49ed987dcd9b6fa9e8073c71a311a06c31442e39f7d6954f05400284403af0c880a71f3a096289f9c97f8756a485baca2066f230e0abc16cc07ca8119caf43d44ed5ccdea9af525b3fa5e9c363121d15e6e898215d6a9dae0764599d66bc88bc037f291c879f31b05e9be4467522ee6d36fb1ce9603202d0f6cbb30532a962f3881e604af270b9d639d622457985216e1657d25bcdcc0df17bd023a5729c4f151162514e1874e3d970be14dd08704172488098370c27a90f74e15b0a5a1612d29feeef192c412454da4040ddcd0722963c254b874dfb158253845fbf7b6015b0833ca2cd024c9200096cc9df8a4a41ca0881011b1392401da68cec795093af2e2e7f83916089733fd4ec303dd9457e17a2e44eca9615f62ccfa3870ce672f2f34a0e92bf60072799442adf708c17c163c0d97b483fce83f12893abad2b3c66c1b1ed0e2484170799a561b5a6c9d6c3fcced52b61e14cab90698d4dc9093b6e09aeb491e278eee4c6f257fe4fbdf77f79a672cd5515c78e49e9dc3e81b0d51db9f4d970bceaa75e531383c16420759d972e8f7a7362410f0a2aeb0014fa0fa76b4546b42c8e15cd4f7c0b5228240e4f0c6ef6007debb09c1b4c8e63e21798d5554c21044bcc2a6e7a2136a6757bd456ea7217bd0e88aa3226d65611c205431747d132602d7f7409cd74c167471f8a7041f60b4d41ee292d551377078a5fb9361961513278cacef52356f1064a44ae356380b8bcb6739254beb5a3a6cc5feae7c4f99dbe81073fa38fd3905bc159d13b4e896bd023bba3f076e276444d87360b3811eeede71b96cee3682a27d35282fd6f44989e73f4138c4a29eaacb47cc28f2c5f2f2933218476c497737ed4501ce3e624598b6ef0efe7de6a37d25a051264094fb366622ad6f1be5ad5c3dd49655f0977c802a55e6e570622135047dd0ac1984ad7c6b0ef865ab2e64d15ba658edf0a89595c2a96470eb134283976accf90a919931e1247f39f15296f667b09700e990ca37ce8a85b9fa32cbecb6238a493a416201f6b24beb3e45371c973744430ae02d85237d510c2a5ba7106eb4106cabf774d04ad3a08c66e84675739dad7b11268a21bf2f7e51baf213f4dbfde2ce71fed0c043d37ab246a288d96ef4131124c9f6aa4eafba1d2355ed51a15711e2b827329ab1415cf7c61cc6d2ff614a2c9d05500b310efc37d8bc05f853d6da21d0eac7e9a91ca31841402d249db15a370a44f75ed3991ca3c7a51a9b84e5d765d316402c11ddb9df587908e08054961f9052c1aa5327002f1e2f8bbc3bb594fff81d8236558cf5548a40c4b1b03721e3275c561aa58b28f1fec346091afc844435828685e71cf35814d50886a0012f3fd0857e3303d15f11b2a0a2b6df2d71a9fac43b0084e11063cb411400f4fb08cf6e9021a498f018f66bab530b20e58f0fcea73f64c6d71906adf320cff8f1556eb4c9bd4570d362d1b7db57e8115a8bfde565a4a2430ff5bee598cd0e01ffea8b3fbeba0db07286a4fa5d61aea62608f6ca5376ca18bca8f3a60585fd47d8cb7ae18b2969adea0748bed7ec7442d0f88f5cae067d899c0b9023a76c6388e22cc050eb0b55801c1634a971b4ddbc2d3ad671a57928a13f321d9e335e56ad02fa724fcae024189cca7587d498df9fb9c978a2eeee7ce73c892459f0452ac026b20f1f08dd4d3daa48b8a508a5ca9aeaa8f67bda8fd57c837877d5924a4caff0f185688c4336beeb1034b700f52ea05eca02e38a3fe60e813522e082ef724cab829511e0fbaca9a8eade836e33d3356e41bf2e1f156984b189f56a464fd8cea525b448225407c1a41c845ff5e3fc49c0fe37ee766f0c0caa2efdeae03bba2c0d1a28b28a1d14abef0274162e109a54fd0279f86e9650d038bd6e5648611f1dbc141d110eea7ae35468598dff70a8a31da0516c45f40b5dd835c419fb1844fc593d343d78a2a68d05794fa08d5630fae41036431b8bb5393e139eba0eba01ea0876ed7f6a93ded9cc30733f55d9e67cedc7e741a20f5ebac0ad89fa43b8cc78aa42c886032cef1c11293cf21d23ea0873c0ef7e5be5179918b97d783cce1c1bb965c4dc30b6244e16bbc3d56deac22f5cd50625a95d3f6e4272609e3c0d85dcf23d66bbc3197e73a096ea919a88b1f0c62eb4eafc28162623cb1e96e523f6f08a1fee78a64f6949d1b4cde3361ea87455776582f7df350a30768c5550bd648d6f4234a66cfcf3696cd337357153e2a1fa3f57a6fad70b369b6766b46d9326ab4ddc3183f9e603c9ac3409017acb59636ad0b1e7a9c65743aceed41ddc86b0f08caf5e4e980dd5f326eabc28e1ce681f62307a2f4b7e2c031765f8931c449e8178688fccfbddebf6456f475de2791e12f8e62c434cdafb0a84cf00b5490a36b4548e59bd7a9a40c4417cad2567e8ab2725dd0bb6e1f31072dbe354b78e4695ab14c41824da053256cf8a8c08e118f0d335bef1d324ab554dc01e4b00962f272e9c092fe1134f4153f7f41f4362306088e5e936642260e35bb9a8a7de6f7833a0ccc26ee9ee69d5c5c48909f1bc01028fe64a0fa55bafe8b02302f0f8c2182c254a9fd584ced26ae066c3c56fa68281489fe8cf0dfcf08cf6a93fa9a83d7186145fb5034485b99724f8a3603b54fca0ba520d87a2151b20f75931dffb2dd128b8d79fa20a8da46ea6b97e8d4c4f2ec12bb07b6ba8d781d3712af3ca4d0b1a9f37c57ccb2bbc4c53fc3324feba1c7745f59ea2c010932994243bd40268841bc44eec3e40bbf5ab275a02b1abe007bd399e0b8ce14b12a28ba0c77864c89e05cb5a78ac626ba25e9bb487c5956c8eb16f42b8423536878352dac51a1c92b25b5f583f3bbd52c50a235958cf45b0497cada21a28ca88424c69bf6a8f5e270e27599802c69fdbc0af25e8f70b6e5d66360f95499904e5516c39e7e3b1486bd57a55ba24e002028c164d3614aecb67d141a5bca59172acfa2819f860aded79af98e2107d4da7fea951f16803630644afff40b27e8911a23916f958d579f7780775556001a64a80c10b3547119c29823c8e6f6221ea62f2cb7e7a16bae65baf063169db7b4451bb545da46b5f77f1ac5df1ff72ef38898164d53b23bebcdcdb71d7d45e3fa3266cca0027f87c8e8eb6bfcd5213c01407ee2810ca8a1ac73a63fc5e345be45174753380949448e786161e1de2863e4c76f32fecbf5443b07c1e31c7c579d4fc94632797586f8f363baa792849914a98484f91255aec102e65ff4a92d5602ffef95c437179a5d2c483b724c403d0a1b84ca08f7fd3ec0a78abdbb7b05e1bf110308fdff06e141927fcbc11f340c27e9bc82fb043e1db113239202307473cf339cb291014307da46022786cfbe62bce9f8877ebfb67134312a92345d24a88082d63e0cdd4b7870203e874255b458fe5f3804f3ed0ec25670af750b50f60fb8acff97d707741ccf770928634dbf63d6dfc3925ff9efe8f32ed70ff71f2dc5907aba4cf35a661875ae222693057ddeb481c668c00656b714d9e0391bc1c8c9983f2c308b2f4e69c1620a6dc6c3cae2e3e1873ea13e31d1fa29cc15806580e534e1a48c319f6f28f00e3c817ccd451c82cc28bb95623f4d8adacb54eb9ad0fe7e0b52385281a4930c28c2a33d1b2b2a141aa219dd6adee1c59b172159e466d0071b092368d658315f9e65c6f6ae63c376adf3309f05ca3411848e14af0347c2e446915951dcb91f2ce9918e09d1cc1201cd1da2e7ebf6c3bca0fd8c40985e2519c892915de2ffee9b50d76e54051d7d21353230092ff24e0dbfbadaacf9f85d56c9873974b4382f424ce4007b93d01a37490155e716103de87055724886d57c68468f440c5ab75317b0c101260d3d3f70b790b4e8a10a3fdd846b40ad1731a868d48bd5dc0ffcaa7c3fd6c0a4ce9e085386670175d056a53161b7b35caf514e93f4e51cd54e6906356c390b8c93dffb07c7b0ca69ffdd83bb7e0d309ed6c788bcd3e3d42584af4e49e103614dba2b78cc23b35b2e8228d81d849c7ff133bab5c8adf8264479f86126e1090b897df48f9b3778171049ff92b44010f1cf03f02e32e196a14fa09717cbc829c7cd619bf03ef832d995f2848d0a265abce8bbc8fa2a2d0e430071e8feb302bccc320a05d15aa8a99e88e414f2212bb81466179e51194d1c5a01d9e274d85782d49cddbc46cacf3a14ca1072be7e9bd1799621c41e050e4f423078e025acca7e2d6603f63ef4d61a3e79518036f0e1b0c4521b6dfb4fedc938308baaa7f8833282e24cc0037ce1b27ccb0a411c3bc735a0907045a6665f80e92ad1a844fd47eb4004ee922f5c7a586c46524242603aec740fe292eaf876d47699158faec3c784d0267142d2b9a4dab63acd4ae53ff7caf051615eaaa09d4293b0233e3498667e97a475e6472d8e9cf44fdb26729a570994d223d1e3a424ed686accf7f895d9459b1d5a95d79fbebf67057248922c6751163a1dcf8471b41c49470c29f1f922a2f4d77eb6d00da4135cb688e9960eb28eac5ac380a376e04b86b623b7b4a2cb1a051f5851ef0489afce94bb94b7a8c3682c00b4882051e8b9aa6aa6bd60eb0fc43410255b19749c08d4091fb20dd8e11bcf3f810ab03e7ccc98341747201268c649bee58110048d3d2aeadf623ef2a1fd0c77c30bd7224a93562dbf6377fad34b0dcc60445373e1ec6b2c591b0a450ea32f78623bba2cdf0ae3b1fb4661d4e68a865e6e4869d9727b371a78e87d7439b3bcf7125c169d47ec8d13da26820b215ca0cafac680bde1e66288d7cd7b5585dcd062272350ef5ab500a3644d368e5aac7379843a174ddfdf46150f926dc60d0fd5eb3b3bd399012c1ba36ad0d9d5d77e88c601cf410e56f83cb43896929e6745a0f8e4a43a30e5501205119577328783ffba93e6ece4b52cdaebea94e26dc7c228703ad2cbcfdedd949bb20ca040b786c441791a45a7c986ca4bca556d2b9a42c273008aa13bcd3309db652f608582348187809e4adfeb4a24d5abd7aa26da0791be3b799c1f3c83586ece4d63a83149309296f959fba783ac627cc009ad18f050c88a30be908eac13d503fbed0f6d981f16de606ff194cd97ee6c43407c894bb5c2ba7ff3102533fe649df333b924c2d5d28bc0149723578881229239fd802a8fdf6a56f1bb53c09a91a913c499649601248acda2845d015c14af42f0c9971346139de39630c20c5a60f651f02f6926b29632b641790516582f9f0a3e42e626228feb4ed6abcdad4a614e3cd57da7fddea64ce6303e4e2e6f26bbe629549943f6d6081ddd6997b9008f0dd4e78b761249f84f5632e4800c4fb05921191a6ca173fd1f17c4af8f2171c00509a48c4f8d960906cb86e3aab7b0e79fef798ef5938c8d822dfec981f2dd3bc31375fdd17635204acfac307a3487d0451171c12ee1a209f103cb2b7c45fe2d358e9c5d2b2dd9d300271d3735bb2514ad3adf67699c2b3ef3f802d0d1a08aebd49361c0f9c5b134d1f214768bc6e1cc588fb0fa3aec1221bbc1bb3757378d82807861fef9c634f13d9e9b9c3891039de80cfae1fb7c30d78212860da6046beb7317bc7e760c7777eaaa046cc4f02ba68314657309c19a50367919a9f3e8ff1b54898cbec5cee96c4aea4aa90266193660efbae272cac2e61f18c5c3202f108aba1620d8c2f020182801ec0f2100a6d1b363a90ae07c5bead6fe25784bcefacfb1bd97b4b29654a32a5145804040483043fc46088142a539e9052c462600b059835f57a71bd18d1a8dee2eba3d6d0f057b6592fdfb24adb238558bf2d8ff555b64232f7d547ad61f92adbac6c7b96678568f9172c991048ac96ab86c52ae960cdd130002a180e31f0dfe20d228a6079ec8b68f9173a2d4ac074b27ccbf7fbf2a88d96502c58e6434bf622d34637e410d866399b65fd0a673839d69095ddc8b1feff2dab94838363e87a1c1c39164ece6549232d9fe2d135faedfa95ccc655ba715d2ba3d168a5aa5aaa2a23addc609a6be5b7ea476f55a51b3682aaef01c7cacacaca252556246f304d2565766394d9b8c134bb7384a3aaaa1cd670dc108bc5fa4d8b2c8bf4625996949794524a79c9eaa5bca4252d69b9110dabaaa48d1ba7972775295ed8cfc52e33d8a5c205529230062e84fc4197f7eeb7efef377f98b53710deedededf0fd2d58227537d6edb0fb750bf166e2ab4143448c023aa19c1f73a113899452d3fea6d550a0c92367f93479ec98fc6e79dcf26c0f931d22c49789155a86425608733770ce71b303c2fda658cb9895df65cbec76cae9fb47d59178c75c6cab8028cbbddb238898a6a94a7a86e43712c800349a517ed49ad1cb6ce3afdee22ac6cc850443c68f5a0343662fe58bc7fe07cbb73c101931dbb8c25eb0582b2596910e2396571942acbcb3947e20a9c0e8c6e829a03256328de44cbfa24026eb480e816de68335ecdaf877db87d90e1cd678d7adfb2ded60cdc9dfc132a2b1d241964fb125df6df22f292fab64bd351ad1b0ac2b46964cd9147fd3118db8325ebfc5b7de8aa5527c7959d688c675c9aa5ac96854d9c51ae3304dccb6fd6aab321c9d384cb32f7fa755551fe3ea5428d4cd0cf86e9b19925e2084f0dab0c931cad2cf4a961a4208e146194ab028f22aa6e10c3de841163d903b4d3451d44415192eb420d4d811420861b8ac86f35ad6b2afe557cb57825143fe84ee1d3e7efb30779e915873dfcc211cbdaedc5d811e1743767e6420a4040c51e2c3f70f5e315648603fa900c217865c1a7b982afe00bc80d980ff227c09af8fa51e3ee1e8537c1919653060f6acbdf752098c0a28ba98efdd0fb01212cc7df5a92bdb80cceb65bc7e4735e05bee610f9faefaf70c2be8a073f3491852c6a7c409cf7c34ccf782fc5021ca3f29ea7192c4542ee693113e148a69f4c97c0f3303017dd2139db0d4d2eb033be3abacebbdeff8de7b3038ab015fbe7ced17b9c03097caba2eab8a80cef71edfc1a8117fbed23eaed8e224307f17d2c77e349c0e3a6174432402b044f38a111d57b3190c2430fc7d78cf26b0ffde87150249673ee8c31762ffbd10485ce603ccd409f1b21b9480adab29a989df14f9f92f2ad8f614d08782a53fb820899423528278ca114454a4f474a11342f8e0eb52ab6624c69d2396c2583e225c1fbb034405888a8bf921fe7a1b403afc36808080522faa1c71717952a031dc3b1f2b78e74365dc0b1f6c53206c8642d127eae40a05fa1915d113344b539518d1641a819f627fcf7746c7369719e1791930eb5ea559c2850648b8ac80861c4c71f1a1a10916eff251a2e779bc873023b06d77b41d229e69b4c741c13a6a138f1c6942e707a55924148a1645222c45c0b6f5f16142476de24f1678f972478029400108a00cb880c6708f4365acc0b41808d335f879453a3a7a042d113bb6e7b5f7105589cd8c065093d4d882699d796665db02d17126f73196743e46f72265541bee1f6804531b87a94d4735997879f898bc58131857035b31777ffee4cb2934c4e0d758c75cd7b80373bd45357e8d8198ebef618ac5ecbbe7c5fa1151889aba00379ddd9d1dc2989b1467bd8d85f7ebf7e083dd3b58b18bb0eeeef6e8839dbf113c9722780dbf9d045a8c7d02191a3b48c2ee4b62b06661caebf7fc79630f0667c5139a05afdff3e73d22c21a28ee04a902617b15c8e0a4bdfbf573944382f9bbfb7bbe648808ca69c60b9b9e417b601a7ffd1b3ee0e4d880d6a80c853c652663587337acdd50044c8b790ce1ab1e84aff2e7f253ec19ac640c6b314eb6d40a42c055c95635974073c7f7b2205e670fbe4cb3de7d8cd556104d8bc1c931a34c4e4142cffb47cac50a6cd3283b7ac44822318dba3410ac7de6c90aa32fafc4101114b70c3720bcea7ae5724f340b53dc498c6bf1c518939c463b38275d181244db97f272a2a3b3fbf3c7cccccc9c392f6d3de0e4871042f8aff478b004999cc1e601c6ccccddbd8ebbd7b97b9db9db77e3c608a360294c3fd74419afdf830f767cbe6193f302d6dd1a83bf479cac32fb1abf63d5de0826bfebeece56659663ec67448c4d42475595a5094c1fbedd1ed696486683b525d2dddd4b647b4a3b647bf4692cec59225de5e4d8d7d26e8411427f858e3fc5d2aa5e96aa4a36aaea5996f5a41cc9fe37aae17fbd0d55eb2d592ac9973f83b5eef15e4544395e5766433f6e31b391a584307d9dfafd02a834aaaa663e58eb1dac45e9d922f05b1deccc331d2d59609b0add70291bdabdddfcbd0203e75cf78e94eeee0f2ecb88c693b0d71f6cd75d42b573ce41e8205ce71e3a084fb09d73fd9e7b19cafd7b7ed1f1ce38e33d84ce41f7dcbdbb25bd406cbe2ef55477f7dff6e7de95600183b96285a5ab990ac1367ac04197c33937849c910ec59a0ed3c4f8d6c1cf6199a3c3555b7e4508e1cbec848270468c314a1d7e8d68b8846e79749f21a584988eb6fab78ebe33f60c2847343cba8dd6d1513df7482fefbd6ee8ba1b967e3ad70dddebe79c73eea4c8589285c9b41ad8defd1cce98d5d10e1c64deb0c97d8a515530ff4d0b01434063b46bf7209c4b30d9f735d7fe5e5dd3b217d6629c2efd8aec4c54877aef596f35c26a4403be3895b9e4dceebe760f3b704d20fad7c8f5298e9594efd9893594945e954634aaca23af389837aad16f392a7ef5562c954e712687bb4bc8f022f5a8068a6922cc50a7c928ae20c3767087df33379bdcbf0c87693ce30c4786d305ecc47f61158f9460694c40ddb8682edac60005d85545bf7e0f3ed81da7a0a0dd06bafbf337c39d80e955554546de17b5e13190610996830b62da0a4642069827fd6bdae904a439130e4370bd03054cf5af61ac397f2a92cc28606e27059c4ea7d3e974caa1323910718299519bbe809a0a1db41196bc7ecf9f7764d40d4980de7bfe9ca4fa5aa3833953ec42843a383848a4229c85333f6e899d9d203b5660c1ee6ed3d06c9a768108c484859a8914371c61db62f8b6f4a66abd9560baccb25bf55e611a46627cf32746830ee183cfc130835876e102b6ad0fff4cf113a39d4e33393a6e8e080d3d39d24414a7d3e9743a9d7252806dec73f26102867f9e3869a593ca9c26f39ed486e7d545c0bc446facbdfbf5731ef1c032e1c764b28ac482e8803b53d5f8da69820f112245889060dbf69c7a86c0a498462f1f2208050535219a1b6a6e1a4ad3b4d7b409401930c371261b54806d0c04c4049e5c0d94d12ce4031b4a2d2d9d6117c768afdff32122283c7e18e9e1f9fa3d7fdefe8a520522fd5cc96cb896ab840035f15725abf480d6d4c4eeb4794096d80373bd6532c63dd107c5348e089122fde4891327575cd1a40993b9699aa6695a0e36b9cfc1010b28a6713e37f8c1ebf79c6558ae3bc4aef184212228afda2f16403cf5b5289e7714495acadc5c5cf80626d8b63f3fcc404344507c5a7b01c9a016211b50bbeb2bcbf16a27227cae3b7b374cf32858ea520936cb5db0b43909a67ff3e0e6e387250622e59584c89c7a55e2ccca162e3b65f08135a1671a850de123826f3be57818ae19bd62a3920e9d3662a808c771fc3d0e0ee78793e1e028451cac392f6055c90de1c877590faca11c6ac6cce30724574604d30a2d85bff7f62d33ef430821fcb74bc4ddfbb9f773efb7ebfebabd776494214bb1845dae08330df7ab7dc7be355a262f06afee41f74f4387f00d114159c93670c7e8842a314b3848151ee22176e278dc107ea2b1141e622a6ec70999ee9f778cc638d818c9a4e289a955d66750356d8c646e3ce42cf0214e986e7bd1b8a8b95002dbbc4811a6790232136ac33e38e889d188f0a1280a17334cd35595654130c5a9d48c869026bed48c979af152d97b103e1acf66a466c05497092ea6d1d601cbdc1f690c6c87e395b9414c8e23d5f6d703936377cb6bf481c9afdad563f2bbaede7d4c75314d1341f99906a51a8cd6905093c78fb9af3a73df470560a647f6c8ea3e715a263f1bb442eba465587b9d2c1729988cf5611f159c6620e9b5d3ebeab6ac6c3b36838197ebe2466dfcc6010e40296a4335401900821e2a93c3110c6fd4c67b0a3e2722448a14e190cc806ddbd333e4f4989999b50984da04055058292ee22286c251e8e400036c632226e22729286e681540b8430522e810bea1202127344e34cd3d01db5648a8899290a48bbb7b555553f66b20e9d40080ad061b38e0520310bbab23c6a7043a84ef46131a7aa2314dccced41dfc41414242397c3a9d4e454551a250494d1e3973377b895d0574081f7c7e5d2e2e2f2f303537dc198a351bae88d1b58fcfe3e0189203a55272a8ecce0a41ddbc6abc82890bd3fd86000dc374bf2da0a43516180d816d27d4699f3323703398579baaa66da3a0408a2387ce0e95713cd4e478a2b041a6734d9ac09c50674e601bbb2017238675cf885b8e20c2c47d69bd04846387b183bebf0efaee3e04feaea43073615363320bd853e1832bccedc47cc4d21f9410d242480b283f509cdbe0101fbb0b86e3cb8be68af5dc624fe7fe456fe791ff65f3b930f02b1f5896aab2bad730ccddbcce9fdd7719863575cead7bfbbc9d332bb373ccbcda0e33332bb36bc15a67d700d8c63666deb25619552765dde3f3d8afbb338deedac58e0d9f7784cfabe817d67bafa5e9570783edebcd5a6520f4ded8cfb5eb6fa7704af9fb98cbac9bb461af99661f3412218470480adae7d160058e073310f99122b60842c2c303173f4158010f5b502942944491dcc1922e9a54d1c5130c3006aca74ed10591c9075d3cd186740125092225446a300322b438a245920dc8dce732401865f7b231b8549300d574360603d4c4598c262f99cca2433cd28177e7f2af940256aa02ebe74b4d2f231c56a24861e90f48489912450b213f54621005c67b0d3a8194a94127e862eacb6a6c357ddae8500485a5038c11f74cddfe0fcb296408172ec32a4c09218410c22020844f4addc27237fb9dafbbba2eb391f79c70c2df69d7775f73777f1d3358da74bc533f3fa90cbfe7ce637c8d84b5c8708d85e53a87cab876ce0531e9288e080b735e776b672eb2d6ceb96f6e02e327aa6ae0fbdf85fff103e27929e60a6cfbf8de1be2ac192f01ebdfddcdfcbb5b477392d7ef515fa639b42efc8659103d3d6329b8a7d52c805c6825444586869c14a181326099b0c412459ae05004b6ad90d0154588526af386869ca4b630379da2331dd217b06d8584ae48a9cd9b4452284263ad3252c2779aec1f137a0b163688bbc2ce921aba055878a719f705f7f71c421dc23f7952852a40010a4c60021290d080b9690dd0344dd3340568cd05d4a4b50d3b9a8f108478788444c1644811137b647f36898e0ecd622a16a652406b58244ba653844da11bd514217c9d18e1f2c408e52e0f0fcff22c4f559d2047336c11e5d3622c2dc6d262acaad262dc568eab7e107655599f62ab6a29a5bcaeb6b690a51baf2606520ca5d186aaaad0ec1b737fc75f2e8b18049ea1a5a8c1080a8542a152286772989017f02e84259e6ced0e0d64fab315a55ef2922069a84b357eec355447d5b875db31f78a7db590114c6af3def3e7fd1bea0028140a8532c0d2686035f7a80ca8cc4a91496db897f939e66d752b9b951cd0c95b6b8f394d406de09394308592b4484280d668cee4da5d8c1849d329801c61f2db080a0a7a95d912065e7a6ce0c6f0a236ec2f4a524db57d8ff1e5a53be3f1638bb83406662f31103f5615b73f8f511b864668fbb2022f6ada1ca4c9e3c7644c4dcbeb8ef0d81d82110f0d63bf0dc86c87fb63eef36a0c765e6776eb56de4329af070584fe5a922c55cdfa8dd1923700f568c4157b0a96b8e0744ab91a949af8af203e3ee48e838367480e873dc43804db3468eaa032ff026cd321140e1dd446a5081eeae66d2378dda81bd40deac6279883a8b48d9b0731ab118004100053160000180c060483e180703c502451f914800f638a3e745234140844418e03298ae228c618420030041963084146a1a14107e32dc3d1252b309dd7fa2bc0260079069572cfdd25f0912778bff438acfd9fc4976d00892659e065144eb16a5cc5703f884abc36ec39236ac9c68d22f6e8dfa9c3a2e0689183a8b9b2e418026f1413a23a74a79d2f4fc72542a96905712f3deb2e4531df19e7c0e10e287f6d526f88756e52b727ec51c9fede104e2c3f650d0d090a14c493773849f2a8d8e49a7722596df977ff473ddd6bf929325352d82d02c772d2f10a1ba7c21bc6f6669c807882078fcaa7754a09af00aca0de2b5c291d08c12261090a0f716772ee185e9a3f5fd7820abd449c3d71a03acfbabca4a9aa8ead156be86d6c98de91fbfda227167382dec6ba64cac7a1273009f2c48c16793ba387921bf88293cc582e19e2e058fbd918441e4d030758968975a6bc2a24ca886e9161b84f4f619fe4cba73e775f2985728c8d7cb80f84baef87cc221d1fdffdc0e8b36261c39ef596fb889522843127c9f02c5bf723e22d9c77103d53a8b3b467cbcd70d441bd9043c416a5b9894e80a28c580a7aad37a643bc85460e67af37ace7611ce4517666e2d1737b287fefbf53b37ddab7920e087a6ada1b2dc7a9222179c8fb465cd2c27124757e6476fb7317b7b070896f2b2a5725683da7ebd52634c1d0d732d25de98b488423cb0a07be5e440b8c3f7173c48ab255a8d4f08920620a0b0262ae3858d66f92bfc215a462f4ab9ec066a74c32c4411c77cbf2f2af5bd97ca2a0b8a4f34ace709d8862ad4f5c15a5f074acb7bc720397ea0e50f9b45f845b7fca5925fe750af5e3c4e039c9be96becf7affca4afd7315cb64d276a998bf5f7aefb6d64f70c4e0497604780aefba6bb9580ed4470635a02ce349bf79a2c87fbf87fb17def7793ec92667a06c322b5a46ccf986e89e52c221293b95c33d2dcf5cc8f9fea810e38a4c257d615f359989c57ed79212a9e20f04fd1e045dcb9619677c2353b809c48787f9c1f4c119a01b0aad3a8006a23623b173de268a308dd1f286643599a366c277fafcc6b304e80fd667c9474b61fd6c82a1d998cf082bb8c6dcf29da4957f3286bcb93f6e6d497669a4a4084e54222410d0d1c543030fd0e326c4c6903bbe3d7731db407a623a41a3d7f5b6943a2bb6fe63d2621e6eafc9cbd408e214fada0c4f7c6b7003b9e22c9ac29d851bda1be502af396da6567eef582742cbf80bdc1756e1974d332d2a90507934be2ddb9916c97c88fae15befd0b1acaa06cf0ae97b65ec5104b13e2b729a5e9c117c6503c78c55cdf8e2fb3bc1ae62becfed8a0aa76989c8e6e84e9bd4a66d308826a604d0a170d1dc5d37e88f9a1937630bed3ffd4ca3899990f005511c1d3999bf833e5ad7136548c3977e96c1f9fa6a22fb7f72e12fbbf9534f8bf7960de7e0b86081a2dc883200ef0046000d7b5424c4bdc443b374118dda2a3008007b99b625e52eda4b3d2180355a9591ca6dae395db38bd5e66ee8ed179ce12c97a46bbb4aff9a361f481487b12943c8e7bb34c577a180145288b34353dc659e999fe6142f6002ea0437a472dcc4d053ade531d56f2303a59c22a6ac0f213e5cc2bdfe40bda80bb0f41c4d73525fb8451d0c2506db1cc6a1c0948b986c243423d52ad65169166cafb029e770de7a25c6100aa75cbd122a5a29d5d2b23f92104ab1bc0d8569282a7dcbffeb66251c490c005c6f1c40ffcf6771f2230b80abf8468a92bcd8da023a0fc26269c3d574e66c9943d7c28614119e4e0a7b71b54a4a2b486bbfa9b8e6c996fb8cec6248db170f020a560765ae9df90214c5b1623cb33588f87c01effd151e26bf3b45650d0e2b18b0b1cd23b04842df3e687fa28de7aaca8436702c7a6e166569adf90da6e9aa1c0fd0c65f0b64812a2d1dc6a63924f3d3e2d13d648d97380ea56509c53526b002511e933543b46cb09463ad4bc5c3e4b121bf622f015742bf76835417224e5ebfc63bceb42200f80579eec1fe471a30905bdcad33ec7e36a33b3436a2617246af64f9543a422f53369a5a6e75eacf6b57307d8410b81e239cb9c130d45d143500ece27e0253baaf3f1095eb97acda7891c5755725c9452e5eaaec3e5fc740e707854b62185f74d26b756a599c8d82bb8126b28f0f4943d59a1c5d415d5597bc2cc0f643055500b84a455a768da16ee34a57696cfed198c89b6c509e0e2635c585229404e76f7147c5b6cc73d03a083a78e2c7ee96fff9aba14e925e458618540e171d3c929a094f3c396483ff9632e1c5ad8bfbb611097a85ea23fdfc13214ef0ee3dd06b67d805306a588029dd59873a9a84882ad100b799bbff483b44a54cf4af18961fe9422b20d9a2bfb20955115995842279365c97d77b145dc2d39b4431b753ab06bd573bad8a76b55d65a779238ae2c748150473a9bf3ce516a3283b03092d42f17d0af4109d54cbb324fe109a8998e40bc4341bb7fb2724ed875bfc04aa78cdf12fad6e0d73bb8370f3cd5dea055906a1472cf2df66e9f801ca39d174db1de9da875f416b7b1951d55a7d0a4681efbd7843f769ca8a55a7535f6dcb1df52df8ad8e82773acf407d7228d28b92851c60e2df939a7e5143b1a3eb0e8048ae7cc8a415bc276c6996d73f790f9f08ff254c4b7403481d0902e40acb349ec5f48407b05d5768d9fbfa0e5cbb88be68dbdc59740bf05ceb10d6785981635a8a871956b99dd85d25bbd2e8f5e5b3c44a83e1edc5b9efbfcb9cf902dac53f413e8a43b0942f521e2d6ab2ba237705f6bbd5d8d797e3f788b6d03d87360c5abaa967e74ab22d93fd226f5ca5b9a19875511abc0d883fdb7f060e520ede22391ba4e86ee181bcc5a1347a63ba29968ffcd16cd8c972382dcec61cc72083962a8ce45efad0611d940195a9b97e0b8a162d6e601507107a40efd12b09f455ff7953a34a57137a9cb7e7d95b8dac2068197c3786d8a8a11ba05213fe306bc99c070670be8a0926ef2a4dcebe3d09ac76ac206317c7c4acf6547dd51313e7f979092027c7951daaf7b24298a116736cfc1a2bcf90a91682571c325595548ae8fda47a62d8eea2670e7dd1a5d91c7abb4b299436ef807641cf19572ecc6fa4662979e1bd145efec0c26e39678c4d6984f45e143130c0071ae448a36758acbcf521560913c2600012135c879bb8dfd75afe6f568c571899c22744fdff38d2fcf8d72b90a691be4dcd0dc71d32953a37d0ae133ec1607304c42cc103e2092858d5301cdc7bdcc2c3d4090109071fa09e20142cd062af13d09f1da11e77a0711b97d0df886c21df7d4f0f23f5faf5021eeaf942bbb853d487048766646b481242ebc5e74d5ed648985208dc9c6a61943e7962a625c2d705fabbf840893302d6346f729c087c7f9d34ff97cb9ae3db3b49a215be9951a728558fcc2564dba3cece45a34799c181b5e2c6f7b03f98984262853fcc2269c93857a68ddd8a01c8a065737585202aa172cbaedd78228cd8f54d2fbcd24774d906e64fd8530170dbff84127f5ffec779c7e2a6a57dac009c357b00c9d63a8cc0c30ea20b6dd2b8916eae0f07db64199a8eecf0cdaa0113a8412866cb1336877f0c3d8a8800487888e03e0b0190ab936dae58cbbf610e0ab9e835963fc570b902d867781327c76d5d876268ff6642042c59415be738c04c48f1d46aeafcb171b9a898ea95bb7d2a142e4816ecfb96e5e67eae12f7965312111560fecd486b761bcb42054664986ac5a6781ce7f85070a611eea49fcda12684238fc73f90dc2d9c46ab11fddd48fd7e86d3d8f119ac2936bf1ceeb6b1424bbba8f0ebb8d1dcd02442fb92aaafd9eb9c028526a29d5fd1d34b5ed9c150cf915b81de6034343ff5e5ec45f1cb8cb82e2a0085772a728112c88b88f98caca38228e0479d8d2c4431a44dd7a4b43f513b037ed8a1167124230bb62434bf47786cad4b5ca2809ffd7af6a6c700b042d3c1d74e0590193f4197f65fb37885d86ff8a710cbc451f3cefb04bb7e13a538db8cf9cea37423d8ac44e016d8cf84c44b9f63f01b170b0a8abd869d824a399c46240043293b24c567b36b5a1df40a3119d4d17b5b2f21c70fbae7aa256159a113e1813f0cd272c8fa1edc833da0ab3910523c2577b1dfaff6cc93c5ac8f5ac33ef680c14786c6fd600f2dc180b9e96f63c896e35e43e13a60b05224f8cf11fed8d85b859e75d0341235f73e60b8d385efe0db1ca614babf3fdd073b7ac3eb03e5e8cf34db305188031b1899e9f30148f444074691256d7fdfcde7e7251e08697175d25b1a2cfc6a69e63eaa08a3bd3e925513b10c766c502eb616e5e2a34545682e7f4b12caf7d6a41c81197553d3185bf0fdaf187ca54fe7cc1af5c6463a41ca786c4d60e76a2cf71294fb0ecdb9da3e2765f679e3157bc48c7b684116d5e76a13a0474db2bae834e93e1408944a3377a0ee3e48e066e1a4b6d164a1f0dad493a8bcda73c8832d256375f7c8bbc3beecc3523ab3ff53c4b59bd5d7aa76a2d617df64d87743d5601cf306f1edcf67a742b2462e652fdecd1fadd5ff5a188847c090cedbe91b07f11be7a0c5ead55724fde695667b2748da1dd4b4ccc16f3ac9d1d12e238451e02a6e568299bbc482a695d7907131f1ff76e69876570e9fbc590547d0a578af61018046788c64f6a36c1c683f55c665e5c597f3f3c04149894547d691cd93a6fd3ead19c602d385a55ae653847f812c071ca623b58cb4aeef98861a58432e499e991c33d61004b102443283018bb8ff7420a1e0a0bfd955e79fdaf57c438b6687a7c382a93a16f78147fb2087a20066d0cc3645e4a6c0a7b6fc3428af50826441e7bccbb84c92567935caca940479dbd78b569d9dae0d693d0c83ae597223bfe23024292c36d1e3bd9d755fb4d7d59db78e75d9ab5c06af0de5582791b223606790bbaa6169cf38fa2caa82a1e46d549938987a9ec3de7df22b693c620f3b885c37d2a604e78dc26796016ef5b81a3274cddff09cdf898d84f9e1e990026ffce732c37cb35fcbf8f8272c3613b9a2b7312caa9b7556e16bd7e8ca811c85d8d59d3e358c1b6f44fa9449687a01a8da8878ccd2b73697f225667ecbbfcd166958d2185041ce9b03b0908070f44e9827440c7aa87653f354fbdd4589ff4ddcb27cffe4a0755aa4b1fd51602dfb3c842dc1c0ab78b748c7afd08d1d5418d874921bd0da8b447839aee22371fa4c2ca4a182ad249704d86db449ed91422fda4311018c7da08645f46d7ed8eb207349a1cedf2e4bed7449c5cf5615c16b887f186aa44e69e0b52cace5e8948b190b19ddab7a7fa22a7778589d3e7e1964ad2fbc142063e0adde6750c238a40d39bd93e3c8087822c869c4cd8e12c6d075ed958fc7e06a0172d99bc4328fcc952c60069e14c70427a91c43d8bd5c650cc874836224f6157563aa8af9ab1e1536d1e6967a2d8409959ff1e03b6ec44f46ae70b6d48d1ce93c1b3d38e1466300a3ec8980e9551ecfc90299124b957dd5062cdf648786c70d8c6596e8b57d287eaa87aaa1116e406df1708a2a03396be27ef79338e1498a8e82c747b099b2a791827cf97bb4b70ee24d612db8f6862a8bdc9553d831114be17ba9a1223d6f919e952174802a755753337a7a7ad63e7a2d9e3a5d723a2d530bb703838b20567f34101fd1c40ba07e60ed212f1bb6b0c89f963bb1133d59dfac88f2eab5c7fc146f51d321f830de09760ed9d8afa6c4e50bc02bc560f6d2f026065cc14107471d3b40b4ca80b76a9697266e6a58b239236ee06ecc25cb4bc5e353bee892da74527310810cc6eb7ac3f1f40c3ed7cba8ddf8e270ed0c53966b98797d06c6e75d7b55014c315617ae4560af12e639aebf60232b7f852ec9d479010b3f328affea318075f5731d1f68f4066060977428b1cc935a8a9681b4b144c394f8719e80db9d01f23a18952387d513181ad082e87264ed2f6ed18a4dc1533d0a35997712718bedb972b873eee40a2a2df8de6739aa9e322bdbc4fb71601e03a5a5c05da08f179dd5c2a1eb3076383d052ba2790b19d992f1001eaa70f61f17adf456b6bccdf0aae90dabb80cb0bf303e5fc1ee1fd129f74198490dd877e7eebf9a2df0e6032432bd0699f0229803debc0da4e58ada9f4979551a51f5a9cac181db016d4dcf17090910000abfb5d7c89f35f803f2af27259ea6f8c3e8c2849e2332697a89d5020f46ef07cd770bf1540fea48ab0fd336905e9c347d3379fe41f248a388bc004a050d072f381513f64f7843802f865ff3fd9805a10e93685d59403122f68d2bd8871d5157867e9b7635842690f6532d70b4cbb22b930548640b9cdccbd0ff1e6928f70d2305472d6a13cd4513f6b46745ff86f54318eef2eb8a023ae06211e0f6923df53f7c36574664c1987ed1df5c5adc085e3e110ce91cb791d1fcfd9903b38d3d6615d3e90e75ef81b27de481e9b454dbc9b4994da8b4ce704616499645e019c21d90d0a3daa60760832019816978fda118ba67f67ea0f930fda00e275eef1c1b17d7973726cecd9b75308f9631508f0ec34f606cb92a5d1ec3dc747e44b22fa88827059ae67f32d7beb422ed85948e4bcfa0b9ae1b7ed355a3792101e67619c9f2cba9c440d7f1c51adf1c21acbd7c46982c09467c4463fad0175f16ad4b1130b9ab20671d412274338313d0cba4caf1202674d2144a9501991d4585c8ac48d00cbc0828bfb15149c7311e1210949f74beb65e81a49403401d6a669621b52ce362a8911f04a60ec415980b78fcfebff641e093465091403aa4206bdfc7767b97cc0c8e6da82e97226ffd6fd40644aefec499a666268996d2ee7f27f50c066d97d2118d118b5f754c83632a791765e500002121ba23234c49e17310ad04be4592f31221ea5faf4ccb80af8c14befea77e4d3e1f1e056111a19ba29bbf688736fcfd4a1f04cecbb170dcf51c542ff6a3df4610e173c2d79d0708f976a75f7e1e6d8476294a62af717c92afe490d6221f5d1cdff520681516f47161480fe89c043257c60ac8635a3c57db15fba1d3cd8d71b5af4b121fa3c30e27fd2562d953e22343c816317e90fcb18784bebf259a8a3705fb249c4a74f0e178190bbe24cd352775911b53f09467fbdb3de80c9424a1f73b15e82895886920ee8147b3d1498a4a123d3191e680cc0aaf7a3295bbc64eda3f32fc3aa08764c2492ec5d59ebf4a002b2f2f1d42a6308a00fda7fef23b5c892317b24b00abd135a58ce5701c5d868642238cf32969f264e28803ac71f5483212c94b66bb0b58a719a181025d17f1cc381b9cd7c3c877d236ab68be2e5ca94cb8a3847b7da3a55af18b0dd460742730c790bc19130f33f6ea11019865b1ec1937ebb98378a26a4085c04b00b21d1a62b6b8e116242c5c303bd0e5971eece4e54230ba9ca99cee962b2e89319da81566eaf50dde148327073f93e9a275a665e938342bfa3843675048d0ef8183ab4bc015775e11722490a02c399a29ba427133a25b618f164b496807784f5d8a5f6ac49349d467320e740c9eb6ea08604874f07b906293e8a71a855298917b7060a845e40401ab40ed74082a31a97080605ae7626e256d4e8ea3bf68987c2f0a7b5c653e6431261a0b3266307673cc330c8fc84441abca6d1a95707d621a2aaaee762b0e01cfc89bbad795fc71b5bfef138ed619cffef71b9d05e2c8d394954c5c21a07be021318035bd3f2dd890eedcdff3d024740c9441a4c228651ee48144a1610886d52f07d52f80046620d17dbf58830fa30d1e086cbdde0e8798379629693a9b107643fd51430f5c20e85f5f16084b0a5f5971dcdabe0696c0fe41e1de64eab245d2a2ccb4811b2b2a5de54b57357a9a80a6bca9a79e1193dd1a9a6098ecf8162ea7b2c26a21484f08d32daa41b3b1a1d9826e575bafb7eb5d74b28dd46ab98f57d2d3712fb55a5bd55b955b518b0fc68ac7b9878e61a3c69c629916a6e6aa084e4fc17d47f48e3f843c7e8010eb6c8a88d184400f1bc4198322c1aaf55863fddb0021d3b3c0cf4fee7b2c685617f49e64884b3014e836c0e02a6c59516affa8446bb119c87ffff6b2aaa2fe3cde9a54204eacc1e38d49f3dfd656dda91b5cafe586f5655de7e4658625b600c1dac54176c7b10e81a2dd75f2a25c9743ec394c8347e10a225c2e0e5c0c26a6015e6aef8bb078fa888329d40b450766d639c041d3fddc1048f99e847b0031f09ff9acd3d97455760c55cee5709ad1a40339f879af2509cd68290c6809d3d0182c4cbbd8f611a40eb62ec1c72557edefdcecdda00e3134b5b8e41fec0a003dd2c3506b4bb2dbcf588398ee49040bab7460f1bf2af4cd0d061dc7126e54988505e64f596be25fc828a0c9f1b62cb4f1c5973f0cf9ed439a4dbe68414db052caeefe64008b2ea722b080ff3d0fad241241999f9468ac3bacc28c2776644250268b7c3a4b3906b4b3350a8d141023797459f6c02ede1b74b98db06bf44e30822aca84daebd80d82c32291cce91ac304a80b4869dfebb7151493169da0c419e2d0edab5813aa2febddd1dcdde643e97ebc2ae881074762fceb1dd4ccf9a57a03ac831397509fb913f02663d6ed03c2e03a41aa387fbbc62ef16d214744bf9d96df55f78392a4d25ccc34c0aaa7ca7c4cf3dd47f508391df3e839db57f7b93838c3dddeb73e8e75cdb8898178c868e325028a86eeb271394e8ca2e9b12fb2f172405d6322ab678a404b091aa52cd18978cd32230fa2e4e6f04ebc34faf5bab8f35e4d11d6ebb2640094da629fae19de32868d75ed825f8864108873194c39ebdf25417c4020d0f721ecc4ef525f3d36e4dd63d62a0b878a91a1662315b440f3b1c0d04140012744991c2f3585dd68bf73bfdc804e394ce17c9801074e8b270c616d0160dfb2e0e6435d3020f45de64f1fd55e1b93dd17ba2e6e4a2592351b44598d732114a764c45c895546af728cc919bc4013ec5dc48094aebc1b477d396c4356aa4cd233dc3dcd322e69295e6e246af8bdbf50b410d86136e0e38c5d2ca1b887f07a99d610e5f6b8c4ce27793c1358ab34ddcf63f2b64b65c6ed06207810576fb591543600107e80e49ca619c14936c4c029fa50a4cc2034bc398da83ab6968452f451c833f45b3558207004dc060e77e437e06a17f2c93d4405316ec26c3066b6c828abeeb80ca85c52c88b8ceeb1012513dd2e298c5625a4edabbd1e8b2b495337085df65eb7f05d12e1d03e1234721541f85546141d233326dafdb85a93ab615864e96c46042f700e6b4e43bc65d454b7798df39359e2681b8b6fcdb223d2723449626f106fb9187e043e9aaea82be91247708ef1111f31a46db2788234ffe29800553e2d3a7e73ec9fdf41e6e95ed35c97a4e28513baadb0ddd952cddfa0b91002c46a7a82d802dc73f66522ad3639f886d7aa52f32bac2f1564b819049fd87d7960cba44dcbaa5453c9144df9d684a708056a596309922cae60b8c123b88fd951b0859816791cc1f7232d01ae4fd5d3ab3bc7e15dab9224dc799c934e89e06d231f5bc475a685d4053a9f3991434416fc83b103490cf4e1d882e64fccb6d4aa3d4d19cee4f91a11002ef25288c2c0085cf053b4e7a81fbd1df14ae177f66382c271f78c0f605635d6b53569ffe09b339fa90e3ba63c08a25eafa97f5c554f65373ad254c359e79ae98404b5570793d20f9a92a323aed1aab908769de5c0dfeb13a808b4a58b832f4c6e2e9d0c890e8aad48e063c1b82e04c83e67e34f67bbe963404148bdd8cf6cc0797ae998c84068f3bb3dfbc592af6268bb39cda9b9bc55513933ef62ea18d2b69b4bb5f9ae515f718a77fed7363393fa1097a126847bbc556df26932a3797968dfd63bc928f7c73575c72fcfcf38e70a93e80f9823096bf7b920c2e2cf69be33486e7c90f78c01a75a0770ff0ea613452340755cd0c83696f19beefa7988f5a0772eca56b3db6b450d829854867462465d88a5b278bdc5b75a2dab07cf2a47b4bfff10bce98576ba64bc72cdc12fbd7cf0e675ff41d002582fdcc5ff80d4bc476130ac8fab9729e746a2c9a23782bb57ee515d01f4036042df31593935ede2305d54eb2bfa4cb64aa752a952f60aca5fc8d4fac98175cd697206c7e0961674fcb1031c28b2c9ae9b2ba8a8e861a009cac598eca6a72c26541f219f2a074cc6b88040008d5234f704bb44fa1a28f8b1a4e0aba961da23eb93ec452e8b1ce6d450c01de456eacbedf8845c0ef1ec507aa684d572a6c6d510fa3569066482f40cbccb6d0046227e45264a74139d3a31106d13764b6562f82fe0673351facb4ffffbdfcfc85a30b99b3f2ceb38a6858a7d5006283ce8d2f9526f7eb942a8c886736df899e7c8c6388c0600b4479ff9751013380d0c640ef9445c2e8a9c8d86e75f4ab0a045b12cdaf1fd0d81171dc4396f2b88149f1d6a714e58857f04b954be2d48cd198cf244150c8431abfa02a551d16db246199ac36327142dfb42e57dd990aead33bf41764b8161274801148d3fd506baed5aa18a3c39ca078d08f8b1823c1e0ebf83af0c5a5697407857d580e32573f717dc8a0325c46a8782e7f10af319c230c422a5c6d4f23bcc18e016b09fe90e1a0ee05072fa07e799abc40b7ee9ebc2ce6d5d835b05c7e17db5c1c813d05ef2d3b9aca1d3388293edc512f0624b95a8ca8a15012abf54b67348462b4efa5ddfb56491804236ead4d9d2ec0c1dfbe85ba3a47ed59b12c675e19308b515132b2601bd62b2a10fdba37550f9451e2bf5c01bcf6e45fb66439bd2239f2773c38739eb5c119d26c9a38d6c7475726dc8f6db7acb1bb48d5cd055a7bbf87b22d3bbaab0937d306a408b289155f748d63683d464341118270e86b631c2cc6448e34abf3904d6b27104f80e8762c398e39db084559ae88c3309a52a999bc40be1c09b8ed0c9bc027fd753935ed3cfe4875ec0a2354aee2fab065e1dc10b9f428621cec5c23e4b9058701c4efcb1d81fa68f5a6cf5b87415cd1fea63d5e8b74bcbbc94593373e9aa8552754554da69cce451d28770459d622b7eaa0e057f5a5b2174ce039c40fde8eedee81339981f5116e4b27de56d4d42d1235897cce28375aa95508b14cccaad574859e1084d0b9d581a637c14aa8b132a66c03d1eadac01a2fae834b083c7f606d911c5fd496059bcab9ff82c12da259eaa2d115f395c2a91f91ed154e22e4aa1a8e05e4527c4a19943cfd1c42accd41525586a01fff8e66fa06c876b2e80be2c22f1aa9c2a57860e18f78704999a83257e5686b197feb97f9ebe47494586c34ff91b587b68ce05700b9b72b94f28f1522f0f08cd2a1be22a83fc826daaa20552baf3c9a9abfa88d7313d5c99767482814d9146cc431edaf7849c12ac1a6f7828737acded992383d8622847980e30d116b00e70eec3043c839259601e7378fab8cd9ccda075ba245fb6a1f0b62cbfa3a69b8c1ae753c677f517c8e52aa5e8f861e54264a2c43e524a8e36d2fbbb8b949adcc2ced9dcfd09e3d82f3fd703acc4077cb8292f5ab61cd830c6d43be9855461ab7e2da48f233d53df31c37a058045c76ea4c7a2046f572f54873bcd2db17e78bd488e4af2002949bd5f3969f7de5f7053dc844a28702de30b8b104411c2f8d6bd527ab3321efe83947ef3318732bcfcc6a74e43800240fb0f028e53de04540b80b7ec1bf15605707140b0c57eafc8efaff8182bffefa5627551462394cd8ff91c3f3e1e87fcedd3cb504cc49a93cded15f9e2eb0f0c27732a2cf846c6cc77da5b3383d3c2a50d9d87b4ed94472ea7b55ba2f2ec0fac5b98fba936aab4bea63e791b40bb270775d8497c078d046d3069d28dbda02aa3e6ae0663e83bd102dceb4464e80d0b8d4bd19f33568075f3d508c1bf060ef9bb8a4caa0d15aa6ea98a120a1b6a6ebf599ab1b74d526edf7c9e00851465f26843bac5df8a0a3f604aa6cca8b088ff5859141a66dc6c506be24347086309fb9f161cc2b035475a3424c0ef3eb0f70bce43c280698aeb960f58fc608e662f4cc046af85219b88f460786c5b78f97aa0fc8f3e34537ebf86a2c0b4f5618d042b5c99c428068c3e79a9c7997d35fbd35c0f93439e48f022875a432755d5cec569a6904097a1a99fc1348413bba428fd672553a40c595da1e721838e0a547b6ab1a1f1a08e8d4265f2f9273258f60379ac15bd15077b8656239310758b422ac3667d1b14f4c91c88f6075e742a98b5ce08d1ce57e2643333ce97e42a76083f8f2cbdc484a152ddf43f9b26368cea0cc58987dbc690527a80ea8cce61cd8db7774a6563a3cc92983ad3564bb0e83be9b52bdbd32aef41193da50cf1e1a4d755997a5ede7288e4d48f43876080bedd047309cf83d2f651054a42719f599343978dd444bc6d0f3ec99f30ea4c0cd2de5e2f642784efe7aa4d595e3d6e3c528d2c3cb1f184132500605e51c9e2642a5db48e116f650905e49ed81373d1e2ed86726604938149e5f33a7293a013e9bd0d43d29f594ca0536166e33987dc61db4e0917329cc93c2833391f398e6a4c0383fe4a1fa1f709d9176bdb92536ab646d751e59dd02db2d7a9e0108409e7170277d5f858dac385bd40bce6e0886a7b4b33bf4924bc1f2423e3cc5698ef272edaa02bca8cb9000e4120fe35b43e7e9c801c0cc5da4a93013dcfb665d05f5a954266e20a9705b8799582f8f8cbf0291e681740caadb6cbcdae5eddd814e49c080bce664cab1d8ba040713f0d3849e898d7e36ac411919fb56018b80d403dfe62d603ad6745769b60d0b6046e9d1b4a9c896ece6ce644c2f6b922273abf60e825b639a8034238450f93f822f4d96630af25b68db1b8be58a8316f2313c5f09c6ac8ececbfff4081a9a938da4e1efe57ab3cfe69115911afa49897f0eff48f450c962168644b1ce7796058c260570ba229b8df9f7e85286cf1d38d5ce78b4a9f82d4b448a0124b1dec0f47e0473a5d8d07d9c13f42734ffcd29c450538c8f6c640d56919fc7f230fdbc387130c9fc8eaf02ed16cb74cf7a4eb3816371ab3192f6d82ce8472e42653d22a5fae0ab296d935e647c5a1c5d9909ec6528cf83cf7a4d00b0f74b0e4355900ed127e64baf6836cb8c18985a0bb48b28e2cddd0c83c05ecf088f45fdc814fd915e9207075dd8303281660411f70733f50a661d57d2dfd5a2a296313ba43c7571ee5c46e464826ccbe1a6d8726b67d0a4e56fc2ee76bf0b85c36dff559c1ecf33d4a32b97f81ac0fb3a1ef5fc8db98723f4cc5d2aff315ce2e0a4010112fb97e9b527f41bd67b2a66d43dd3586207f1d654948151cd44ca321e756c5cba9d44b9e8f724344c4e94814d82314394c28733b4877013d1e6cc237e4d74101c84f49ba9a7d00dcdb668b3c3b507e900887d9115aa991fc797609217b6a3de8f7bcb078659a668ec21166c538fe894eb143b3df42cafec0b8a583182ba2c8ae314afcdff3b215ccc60a21dc18b730d59d0d47ec0245c60a65e5440acc76c0dafc93ae373f8b6f56274e079d995ea79ab561fb4c15a5c307b19a617510b3c20c215e3da04ae61b9036685c3b105026d02ab1278da66627f11990970f347d22e47170894f02c2769f5aa0d5f6db46cf3ee996c68ede1dea816bf49158eb9a3a68ea6f613d4c4edd46a9b295637a4c6b2c8c3fe690d61090ad3aa6ea10321e6b559d6a453f716bf19860ca76fe01e8d8e794643beaf0bf322a0771494a2645a95016531daaf5c7a21b4db01c558749e3e159d952b02652d5f81a59fccaf8ae3f280e727c965e1d3fe67ee427e1fe64f1aab09032d7a09f5aaee06c2d4a34a897c1134d9a220e8e0c9b7fb935ca4401aeb6942732c99b8401ff2aca2398249c590f0cfa0d5639e22ecf1aacee33491a5a4e2d78882659dc06d3f0186b09ff8f4bbec1b6e161d164c9a8e3cafa1ed2278a2c8b283a85988884acd6484b618395181c9ba38abe4f7c57aef001fff311fb36504a8b11e9ae46272f810111dcbb5a62478691b84eb9ecdbb514a69646b4ed801f3e8b78e913b89de9cd902244d9cddf12244db728227795977a1814d065e8752d885edf1b4d65d427ac0550bba5417281eb56c05753720dc997c56a08c44a1332f4b46569e13c0568187d8cd7ac97a158c5720167da760119e015bdf1db7d893e0e4a5e40bf0254db2ec91155413d9cfd912a0242e6d27bb974a01444d99436296944c4dee492cbc1a086f250db52ccd47cf478b501332138f086fd367ba991573affd6bc0382975ff0bd10eeba529c6d9dfc12d987ebc49b7954053fecd8d1725f77d75704fcb258c836faf56b7d1a04702903b5a0a7c4c217feaa9b2dcdda821d7eada0da9766cb4996ebeff3fcb283897ae568417b4bda3e3c4dc729a39ec3e4274184316e842adf06e6a528231d82d3c2ddbf9fefbd06f749602089161601e4093f67c4cfd1e8aca4167104346836752cce382ba0868226f0d9428dfce013ed1da11469c158678cd9c8fbed77cde8287702e8bcb3a5f0b12caeb141701747caf5a6d9e694dbb6c24295241a1aed81a7d7e49e22fc0e4a22756e4548e1a222f3da9d318a387db1ab0d8e54a62bc9d2d48a50e98d7db21c18322e3032dfa2d5df302015bcb687910ac3a23708fb32b3128b03f653dae55d9b7370de05305bf6d5e54ee9488f2e426ce857a5fa9b33c3863b5810a1dd8664aac0b703439ca0d038c18b2c63fa24ce3575b5c4f42430088b140e7fbbf1420712f8bf92043c01bc522774d50a33da91a2f22e476bc05dc5d8a308a9829357e1b15477e171c960dec328cd521dcc3c8330ccdf52910b11223069e672ef024fe761a618befd11814951b2015da51ed54193c22d1d6836a6d706f4a42af4ab275ddb9f6ded2339722c9f3fd029b5304c30b07b0fbc0dd092a386b502f802aafd202f554242caa1059f85c7e41fc1d1b866b4349facaba4255b0f849b225b10a513d496c6ce70ba6cbdf166992fb329cf1f5ceed998770d56174de6f724881cdac946eb34d1c506f8ba86cc69b97d90c8b2a0e4400b54debe16e69c6654d2b9f1d36b34f80272169cbcfeed61c5a22f146da42a9b3b9411cd790b2a8b569c7554557e55e555d10b0d0474d75ce281cd59fa3701545170407dc76070bc03e81f6367175d54da3f9dc3d10534fd8869fea87afb0c7f4c527066fb025c2ebed3f5d8bbae1082a2cb5acb1b7bc659ec2ce0f95ff10d8ba2cb1a3ffe56fcd2e4eac18b5216cad870c9b81f4c29acdf211990dff82d7ad422d52e7f8d8b097358a596dd3f19dad9315fb5c51f1f9ed9a0095a396d7ee36c07d1320aaaa3df05d120362dd8490ce469a55b89b0b1bed5fe317edfa1c0e19106046396ab0934af72be7be909835f651a153c2bf208269ee4270067ee0c6dc0e66a52c7340c6d86da2e5dda9dd30d8f7a34e931b7625a30efb196ea2b8b1a1fac2d3250791c79f3ac92a92684107218119ef83d5374e4d78538b497f7ccc8425ea5cd1b7512f0ee1ba2bd1c5981b9f7741164743808341b91cafa456038417a15c049f2169a44e87cc50fb549a62cdaf0cbe85a4bba4af70adb5d479dca07fc654ad1105120b877c4d66f979e6149a7ceb655888f4b51d4d4f5e6b3a82e00bd423b23d334856ed1997562cca85921dfcc512cd01bffa23b5b88053c7d279720057a0933029fdbc049f8ceef897411a919a624198bea23c89b3211c6c84fd77712c9de5b38cf2cd73423ed15de063ead87a817cb85bbbd8e58ac46a202ce112babb60f648eab22a3e9a71e702d8a5e01192136bc1ef4a619d4d0310ac9c14e8d0e80ff9dcaf887dce2795a48416f4e64ae0188a67cb9d1abc3f1e85157bf3aeb6dbd3f6ae40257f95ec3005d530def59031e6360f6468c30a4cd34514240e08a8cc08961bdbaab75590a5692043c17b8cc3d6780d955c4f1792b781a93f45612b932e221210f17429b00b4b35008145482f218abab9870c30e191d0871727ae944c6ed1571978477328fd986509f1276fa6adad31fc978fc5e96db5b7404db0e7086d5b373be097a1390c8a92e617be7848e0856f5a5e53d4376f12c3967fddfe04ff868cd428131ef2444b573d0e7e7899b2a5a869a2ed19da1b2b70453b883e74394feb55f53ccb510e5e0965e2828492022a8a7f88c6f31c5bc6d72f41dca8744a54c562b22401a5dc368d5ae15980f0da0554a0ec133d8bcc023abb40646344e6d17f486dde77ebb0fe3e49ce0acb6afb6e11cf70d874de6c7c2b484e57efde757ac154c4d53ac149bc8b77f414808b2a8b71a3091a81c3710e2a9d36de5747066a9b54cca337b10ea947944079de1791066e959b3192d15ea7959bc1bb39663dd9cef5dbd1f36f20d6fe7b0949c37b7ae135ef57e70de21042b54266ebe7a743089a783920aa537d88f09208d8b392a4fe77d24793fd3484bf6de780b65b2b3fcdcecaffc48000d252076a2d44fde6c283ac595e0f99f8f389780271ec6852f3f63608b47183942aa757f07421b718522199772b50f07580b7e4e98b848e6d5f6d1d784e7b8b8994d0f2b66bd1ea465724f6ab50fc4157215ed830be3e792b0c908a61ae01875698cef994dcf63b8447532893ef274cc25b7a8230840d1d442bc30f3eccce9485472ab1b3ae7a20a7d40089e7d7287048de485c6ade633549feb12cfec21f13e9b93877f32302dc77ebf0bd6f20325c6590236c9c0eaf156777b282724361d3099eb116948601a6f41f5abf30834ed32338f169e4002b3d026231a48a70bef6d6b72cf3df05dfba0b03e4a357ca678b885f3f542c8fc195b52b4ae1e34df456fc42d28021c8ee01c3cc9ec2b6612d17044c5ad99431c9dd7c65155c85b8e0e8760b300f60e4e41e141743820c73b011acbfeb514662c53121e0e8d5e752eb1e8c639dae88638e4ef0334e50374d508e8b31a74acd35801724e8be6f70cd5f0b3c67854283912626aa3d8f2d861a2813752bb55059354e09025da3184235f983808bc11dd8371668c39d49360a4e81ce740d03cf7a4926726b908468029eb280c08f6ab1f32fde9b9beab2ad2fd2217c8c0946e928e49d8e6f901478ab904994568bd4bcca65a92ea0ac847efaf359dcce8517cca9e42c62506ccd0e8732256e1b0250603d41223da7346b75962b6129b46665962307d9563b9813049b0c4d099ee2b31e19544cd33a34392f9a1db0ccd83c7c7ed9598f484247a2d591f0851bd728d13607891ad099345082e1592e7de18e938315063c05cb5cfda080aa52b2ef8e12134c8015dd4287f456aec4b3a842b858e4e7fe6ac348cb0c17360cc32910022ce688921394672bfa1017c757c0cbb871bd85206f64d3c4530c1c6eee1435f61b0356813951920a5626a4a1ba512eb232940c80ba154db09a4782075e87c5cd399bdf80f8a09358b22c0dc5a65f3af50db313913b5bfc6b70d93104f4ea4707ec5c6413e96a849bb9ce589060b725689d5b4ebba4b5306c0d795eb505495ff90675b2dbbf023381e3271c5631ce9be67ef2d838684d3178625ed2d5be716e72fc965c690602f36295f64baaa5507984d45e3250971d9c0f97211b6e67e44a431bf3e7c09566de3f59fd7289ca0dbe7dcaccc49b6e68d8ea5fe81d2b9f793848c2b35196ee684d15d026e802013ca54a2798ada5b29e4d5fba9398ebcd5ab3db9baad6ac6b702375e8d3dffbf6ed5ec7794ec8b28b88b5a5edf578858ec65deb1b6c4c2ee4bde05c8325f9d3ff17a72db12083265590da06ab3f875b0735971ebe7a0048d4767e330647796454572423045047011cfc903a82dde9bbd4338319785fd38c34e19014a2bd6a920f6480c39aa17de8790334c562cfb61494a90dc14a50a95f01a2a82185e63d9190f0caa75a3daeb79f8025848e472da38c13036ce84eb48a79d0b0d1267a7c5ad0733d0bb050603142edeb4a06044d17427b781b27039abe21b17024b3f273903c4a7401957c90709df19a4451e404221ab48f05ac0914e0556086e15447f7e90309edd83a96a7f9ec3bdd37c87e8970f10043441363f3f769bdc48e299662b229b781c53f7d00a7d39bd8eb771662932fddc90d40a2becfec6c0206fff2a6a8ba62f933111591fefb4367cbab6bcc840d8bdd7e052925a878bc1d1369f88629f75c4729e2c7036f652dea484396fd48124f4187d144429aa57e9106d214e7dc212db4365ac8f4e0b3d24134113d1250b3afd606ad1bf7a141affefcd99530f6640e9dde85108b04474ebd1ab8bd673b51ce328dd4e3cdb314f0c8f962e3106b68589df552e11e7903f87527778c3fef9f4fcef42f7822c7ba86ec3e69ab27316a8f96ee6b9ccca6ac3ab346815144563d07b9be14af51fc21cce51dca2aec0910a5d29a8703331df63198139f9004459a0d90638ef7534c9028ed620e2faca92fcecd3aa57cbaadecbc7a0a9159fefd2ee89f793007820d3418991d907e61baa1fc668ee4f8ddff5c77cf488059b9a9b0f5a277b28842588e75270c1e77fe0d9263e940809a7bf95224078a6804448f2c4ea20521cc7b2171de2603585228bbc91a398df4dd927f690972a8ad928167649d34b116bdb020574c04370ed6da5e7f928bfdf263cf1591a4fb63ec147d880d0555e40c022092959e0e7e9f86014334f7afc874940e1f9d15d61db7c8d45ad7698db4b495d238494df84f7074352761afc8f4dc48da95174cd84f9fc02c0f3da4d53e685159ebab0c2a6e2a7af29fb8cd5b555f091e9d20ac014d8e7a32e34b3a030ff6937ca54596ffa865199e49c4e5257b6a5a843ae49bb2c8a58789c4639fd6e769b086f174d0b1f96a187a41b0f5656b8b80237aa8066169d12eb690944835ef2f9160212ec62378a09d43153c9ef9fef570953dabec02a51439575079e680d23b5a8bfe370020ff32a5909aa828f3b5bdb6fcc8af6d75b9f8ae112eefe561f51602a97225b3580264d105ec6fba8dc052d6f9e2000be6ee3c077f6020aee3d52ab39069c4015d8d9355386e7566dbf237d00b0f9326de98cae0fe1c44c4bcf047ccde6a4baf9a7f8b8d533bab5f2dcbea68193da8289a5d068c162002e93047de2d35e4f07cc52b6a65cbf66f8b536b1aba664e41fa7f67e7823b70894dd5937649a7c0a199526f9928bdf722e8aa444c01c8e31e8882a80d5d7ecedffde4637795161d06d9be4960d40ee103a3ca9aba9ae5e4d9beaa8c18b415d7e790c966213f8dd29d4b54ee55cd580d06b99427ead75fed4d1b1ce42a4ba29a9f41039f542d130eb2a331c41ca853941393e9bd1f7596d963e66506c4e06371334b65b79cfb677d61b6bfce635894c06f9ae1d6b5e9144d0c10622a57a934cbca5f6df39240ddba8073ce6fdc8454b74e43fb229e9f7b5450aa98ba4b85c0b14415cdd4433a0ac0ce309c52d7ad733d845952cc7246bdb406ac822d95096e90d814baa71821cb4e39ec432163f6f305c4fb76c6561cb2923dde27c17e5339145b47fb9bfe450c052d893b99e4f7302364433932e4a5e26b0184387bdb403c6e26e646dcf3ea58269f083ee1911b759e9af8d141c0a6cd24a1473194c228bf562426ae37667ea055cd4fd8b7f6c91b77040e239189a79607acbfa7f1038e3187ac3f9f4eec9bf1ce4f04b67a4aeafff2ed629e6ee031d7679774afa270bbb784175cb0a7644fffacbf40438177bb03ee10ee53346403c1a284430bfdd0de66af05430fe8413a26d6bd791c7bf4ebd1acf637098711efa7eae8a6e73aff547a421587cbf508c9b5271e8a5057d136dff739a1fc05f45ef44d553e2b95ff1eccce39b7c5a8447ae1b78a4a6bdb4a09505a7d8f25280ce2a7cde2f6268062a07c8c3985a0136001bbdb602d5713543e8389debcac84df0f156f61dcf2c3e3e462e49b39d3f16c009f8cecfeab0ee65b2a5ef168a9f21ae25e4061a1c2df63a409ab85a4506a2eee736139612ff79044f62bab0a4750e56dde97248eaf9d745346f5a4d6173849ccc8d5f02c2bd55886162c8a795332c2673c7c857f5d689fd37cba093c0ee588926a1f7f5a071ea3452340d41597682663f39de020bcc3b60b0f2c2a3890a4c893d41f26c7097e679a1bc798c3d4bae849079048866861b2305b8f7e9a4a111607c9ba33fa594b7922cbb620de516aa172e6b0d41c033f890110644963578e61e6eaca32cd2e57caf1825fb93bc6f09705f06dbb88160168ac5fa76296042e17b5cccd9c3720032882ff476ff7000ed4dad02d3186dc25bf0b41b02fc53f5a292fe25a4fac97afe7965a377a9fd09779d3e5eed756bc10b6b61bcdd2d354b43ae5cbdc75a2bdeb3a18b6736ffb671d5d1ba2fd8ed4430fca2edd450aad22c253f88805e2bfb44dc8d4a617f37f49bfeadc8b8a97b69f40ed43003ca5166785b30ce424a07026d0563d3624b9e585045b675d6f88af2f2650b0a863ac375b916ef185a765479f0392e82b1fc2c59c513b167383d6704629dbda1392dda331023d02c5379d0573c7b3edcb730b3f3cff125217befbde59652ca246584074d07580746320713d115f6efcb79c4d8398c0a696811fb8f161b4ac638dc9672bb94c7de85d409ebb17320a5443207fb66451e29d163b74618e76c8d1efb8ff65890635e038243192e13aa3d09276358e289e88a8b89ea10c456bfd76b074de4f9b5d62ded9980c3d13770f4e72e98bdf41c30fbbef454a8e1a763b7d66f10cb8473497966a6492e0b6369e462ff0eea3dadad991765cfdf2457f233f7d6124e32dbe06da5c2c9189688a5114ac3d4c5ac13cf3a33c59dd1692fbdd3da7c9a8a9b96b3fd1de89cc9134f3ba7a5d576fe80ca2b355666f369aa8ca394a336b7a320bdd5e6ebc9c9e916576e9bb3d3c0dc3d719cbc76823033a5a1c8aa524ad90e020eed6e5b314867edeead0739daa7e368bd4a5a6d7733ac6d574a99d17a86a33da59436b576d4a7b596a3b46a1d05b1ecada3603d2339ed4caeb5d64a67d6d7959d2caed5522f5c4a29a574aa524a596badb5562ff294b56a980911194ac9504a260d194ac9504aa64c1a329492212aa30d7112c76a3808f57e64d8478fa60c671680016b43397f3e85644efb0b9e0b2db6c7b4cdb6e0b3b47155051438ceebd19cf34e68c9c26915f734d055630d6431a95c6834b4032171b43f7d0b21731ac914125a80c7a389ebaac469d13d9f4353680aa986c8546b8d6a427c35e31acf4f8b9e4bc920e9124fcf52fca4ac1c4824f1a33c82f59c488c9933e69c530236c4d0303ddd508689d6a444492899dfc4738e538ad1112c871fe7d2e4b2f9386363a6ecf264c9044a82708202d49a94499432b36676b3835c2e170fd31b520288899a49b09fde4b930b2945e68cd9657ece897482a04963ce3969d83c0d973f3c0da741430a134fc367783d34da490d92124dbec6b3d75393b3a7bc9e9c8a42c6a71ce5f5a45028f7bc1e54f6a204f15ee79cd7d3d56c79ce2513cff9e6f5709b4fc9b3d56836afa95ef3aa6945535e4b0204afcde035c75e8ff601d2d0e31b24b47cf655e1ba520550bebcc5625c3aa0043de5e95cf434262a86a1dc7b7dd8c205cd099cd0e204d6bad488bf4d90f99bfa7befbd4fb0ecf82d96780bf298fd090e7ffde27ba99080bbf7de7b594eae08c20cab595e13a323db164e5d132c48b426ae89971c300b6584b6262c38920135925ec91f71ee02043008426a1206d0cbc94f08582043e4c48720169a849c9400a8014c8c9c2001c4c216474e9e48c2020c8c6aceb09ab55a926d5e221991428430b088b20598dcbd443232832422224cf9090a80999c7a89f433f4c50f124ffc40e12c2a9c8ca1095119436528a1b12e7d0447d4aade9609218b3c4e8b2717dbc4e4d3bf2ef26c59b98133d6a2f530296b3e8ba40b94c773eb228bf254b756ecaaff2856cefa9defbdd8859e0be93e50d5a2f5e0c81224436d2d097eccf116477ceb2d5abff19166aef8c287232e578b3a8fc67d5db65b89a399ba09123298ca70e5600229685c689ac660da62362c571966dba25a9dcee974ceef7a67860d25b78f5c0d25198bdc9e5d467ad0d57458ab614f81984d6dbd6066a90e101062b94b5d91658b1d7e39fefbc27de1beb8900497f558fa419f66901018114c245dcff51ed02dcd39bb55c9e35ca22658bf49a1ca505b94d8dbfba99f30cef99b42736922993ba6d2c4d14adf48be5b1cee7a08390dd1272257d1cb08cc8fd4c83775b5d84a734787d163cc2a6aa3986c435a5a4899e8ca4b6e400daf81ef837c4f81c8344fe5ac431910276338c2c818466328b9b8202aa2a34498248941248653bf60cc9827a630929e8ee88f112e308ac0782ae3e933901f080864a466a5d3cf34c9a7e9edb125a963a3c2f17a86a8b5d62aa7d3d945e8104dd3d4dedf5376eaa686758cba6a0f5121fab2e16d1acda514a3445a795b25a34396070934d44343f410ee21cad44df488aeda312c2f1db986a8100da2afef1b1a1a72ad00c263a74cfd31313149a61a86b0d323fa7d97c7d3a1efa3b6bab223629eb9d6229e007044ca5b8b268f19a988230178eb9bd763712de18857099400838594297489985a6bbd7992479694357ec479eb7289b75e7126810adefab7e4ad7bef48a4129c1173344009a4a126ae2d4b6468112788626b0e2290c04494f1d5b3a326967873f563f7b58aaf4844dc60f4d56db64ddd0f752888afd8fab0be1889b1394ba7c77810be7eb92ba3648a122c3708c5944c716448496c042a00b1a4a40b20158496104999ae8c5e896d1a2a6fede4412a8900f6d6dbf5226491062644b691921d0905c5c893525a6b6d254a7d299ca199acd43b0d6b476186f9f5254df333f46b91390d93396d43961842501ef1df681c74fb0cfde3acf9d622736a97e9332db616c9a342af7a8c18305eacf826244c4628500dbb89f369516cba847de0640c4a9c80f102230999ba1565fbd2b7a2dc33295545b65e613926501f75524f1b4ed89c3c351967f2d48425f41c75b6a24c7f86da0364be01648e0d9fa3ab38796daba595565aab05a9137db24ecf3c51273379a430aa449774cc64eb29bf6ec1ceb90c95023d3f9239d4fdb90b6af2a88aa437a1fa388fde8b9f312fd87c7a2798900a1b70bdf819e382f1c7a3fa68a4443cf50e1ca90d638b9982872dc4f450bb9ae480c482a7def1a74e943a5198cca14e614e79a44ed4a99337757aea488410947b86dbde0d7db2734939e8ca488b64e486f5518bd4c71c96671ed1d65affb99ea7516647cd534debd6b954388b64ce9469349350c2f3ba0c0c4d7120ab03459c1651a107bfb528c5a8c529472ddaec5b53ae97bb20933cc11b2d5acf204eaf2c0b9438accf10640e94b7d979b48e0cb717e4c92473cd79e1640c565e62573aa350f2f376ceafe79cf34eebd65a2c28161c754ed6306b9a38a4903a66986d51253b76c5624bb228e72862619c33a543928aa2fbf7e55cf47d2e242a8cf260b7b021dade03ba859faed3d3cde92967b41919519eed888a1f679435c13823a7ad4ace9ae60eae0989630ba9837b42e650a7463267883c72464f7d33a23c0d83e5cf45f1c7cd6886238bba165444e22029112bc5eb2079b420ba72cd1d163671507769413e04c128b5520cd156bdce84a44a4891ba85cd1dd9d3c4419dba164479e813623cb530091be229bd22f494a67ecc96640ef5322dd2cc296b6a91d2316b921486f3e74d14e98970ab2bf2a6183386081afaaa62a4e90a11a22b36c03a06548a10ba62435191e6f055ede029f68b4e9eda524b8d24f258b52c5b142f8f88cb4a749f9883be0d67f3957105ebbf3aaf87528ea31dfe6810875112e9a56be0248f386585ebb42ee348c963ad49c04bf77e988180a41ab13c561a366080687fb8bbbf5fbd33b106ddc7820d3a8c73630fcce4117fddcbe604b9060f073b747043c2d286854a67c991f2583de8a42aab168ff0d966e70be797f5e086d27ce5072c9d7f0902019e6c96f2511e2b88036673251cc019d939620949494a4a5c66d69522a961ad1f783dd5034a716a98002080a7dc45041b2fdd032f64e3d54baf60371ba857edaa7cbb1a68b028c256de13364410800e00bc748e881bf39512411e7108f57573268fb547cf11bf9e50109b624c4fe711ab7d7a3ddc00c258c794478c03e7fc7deea2c89a2fdc83bff4400679c40408ea845445373627050802238fd58397ce8dd065d747c0f325c4f79da1fea42474b5836e5eeee0965db493457d960e0fba9a35a6aa481e717e52a73c6ea3ca54990dcdbd837fcb22675511271c47bbc3584addde7b25186bedbcf25e6b6dadaa5aa59492524a697777eb349d61ed30febeee9e362fa90447a9e5e5ea65d5ba9f86e30cdf614d15f93e8d410c2c92dfe418c460bc6169cf51fc35fb5a29a55d6fd42c7bea17dbea9cd7d3dd466bf52c08e69c93ca7b9db22e9d5d6bad1a94c9e0386fe0b0c2a6b47691a758bbc8bc5dd5a2740dcae4e9b8c5fc72529ab95a25adf5de7b6ba594d6ec95b9f6da5a6bd55cdab95d8b6515d265aaefd3b67a2de6bc32da56b39a6badf55acc755e190f4b9e35f33a6bd5b86dd3327bc95cbfb6d6cc6b9d30d8b56450489cc471ae69ad813797a20b558a9a735a87bb70a4e135d5f7691d963ac2b983a250e36bea8e976da28f4f18176a21e6b84d7bade332ceb3365ece53e2a01fe86eab92af732994ddb814d8e3b3010cb28794a7739b95a2a65d97a207e6a62d4d58a5ce759dd763dd9b3b54ee58e917d549a9a3276c09d6e459dab33a795af0c70a7a2ef584101c1753923c28805053acf84c8eb179db658c278ce0a919a454f514c7a9e32a9e7aa6fe35d1c55377249e22393186d1d3536f297982de6e915f473c15a47ed8a1b3e20955840f1c97208f092642b07561eab0e8a0f9a880897be12d4bdbd22babb8cb8bae2413311f7c52006302063f3419b97af0b1c2c8a8071e7cae10322a010f3e312123293bf87081195d09f2d9f2649368c0c5899c7a896483183a10c94174caf825121120a62042048e75f9d571c660a8a4d7e1f74d27b80a562f05196cd76e3f417e8741ec6fa0abcfcbf4585a2f05d902cf1b94cf49aeadd7c33927bf10c1010636e09f1d7e248fcf3df2f8fe2087782d244a86915d24151f19327f21b9a03c124c2ff50e75ecf61ed12e7287b467465be6b1cb1ef398cb637bc563dfbae49a9caab42937e7061b80372d7c426e1ed0adeb1cf806fe8542a60e2598e2490e5a1414b49e68524b94d74f4bc890ee86d613940fc95460a5f544f331075d217b89969c280f8bae700aa6d43b35eea36db2461f1ead11c67e9fc84c3d63dbaae48cebaa64ef477f295bd5ebf1b9a17e9d83b0c6e80a7b06de50df20ac4c4d7485bd82c92f649fe82d7a7c891edfa1c7d7f5f8be1edfa0c76e63168be4c9fce6b1db29bd73d43b358edd26491ded1f76492d938d8faa1fad91063e5a2a1ebb7d519e2cb446f87fb42fb03ac99c6a46aeb06357961ac6e3ba4457d89f62c9a37db5973cd6a5c7d8ab1592875597a48e49231c3f3259cc58bd7c3351235532b74774855d7a12b86e8de80a3b8fa64979a444449cd68d9eda22d3f0b19f28914d980ac74c54c3f1c35b953c5aa3948fd5db3b98e11b0df0468bd81bbc115bc42e9f01a9b0fac6254fecfd243eb5881bcd63dfb6e4544d78420eab13c83d57ad95c730db84d43c1af2680f92c85928e4490b995f0f85c8319e285af282c9d5ca42ecae9634616e3fbd94134d049916b5883d1552a21631f64b947d70f619e00cf37f481d9b63f7a13ccd4457d8714e61e750a0fc0d94df4cd825b6fdf4f98de6b9d449af3095d22bdc44e66007e50e7a84c48167cf6d4bb6b676addfdced582cd11a69e196ecdf1b68034d9489e9e808069bb1212712a71165fa8d62fc7dce8302a13fcee7b6701cc2f43ee61005b14f30887dab5129bd6ac72115cafe45dbd712722c91bb27f2f4ab9467488d90c917d47ec8d2c49205f038ba99d685deb1dffe02d0b7cf97440193058c7c177d3b0cbd93999424795b6951ef8cd40895d23bd4c9b77737bd14ea1b6997af42fef68e873c9d47d3a3ad67fcc0c918b87079bd5e499cecbd768bead5722057f0c909f2250cf2acc19bad1952f08547af107f5459a194822d4a90b6cc0029055470607008b01553fcde9b44149a8e3a73ac00fa5a3b89134014e32c35e1a46f7e578a7c2c588a319d398382f602054f38bc709072c3d367003f9014b5010542568ee0f2d2ca112fa0f19b19194315463f473724f919ea19d8ae497ec65b6fa30204215d41f2d665116f2d75ba02e5c72e7aebba42c58f3de5ad9d6d9bacf0f58ebdd6553b640fca4f90c67333e90529c8a0797b7611dedfc673d36341f3ee6aafbcdced5a0be19194a6e0f670d8e099107c830078db610f9b947d04e9951dc149cebc1b49061a7d967111d2a29d209216a36fb083f71d72032579eb1f2c6951e2c7b9f401985cbd4738416925e8a5b74292d46225cb4b6f85ab85b20048567cca793427bd9e9437889de835b0bb3661fc04717ce6e998c9de09f4a3b802835f7e823bcf496e6eb2ceaa859f731bd24f5083e7384e7a0270411e7da83e9b1883a3086b7ae9e998495af2d33914cc043f4104fcf474ccc078a8f1bd42167a6d3d2d4b969fa0cd679e0928ef10b7e63ce8cd747933e705718338bc3d7f6bae037163253f41033cc7693ec1d9d1e95d97cf35d6b4c7a1750c66ae81a38f769486a588c14f90009f8155e6d010843eaa03471f5fd8655d9662ca4fb0009f819dccf911a47e56c1f80902e039e9ad90b578a73441f319de4cef97f3a078f47143eb13ec6e28b5b002cac151a565cb4f90f519a881a30534d087eafb06eaa8052f85ccfd2f289194927c8633e585207e82e14b6f85cc259682c84ff07b892d853d75bfa0449a22f617df5c9748515cffe0550f84f31b02998f0a479b4779d722762e93b351362080f01287a30fd5639fe1383c77935ef0d24b013b0daf711e3dbb098af017f452b8a10ccea3db6380e633ac1ecab9eb61efc0f92660e7c0f91c9e586250b68892dfddb44ba4242b2f91b018f113bce990019acb1645781b3fbdae459473d43be1b62009a416bc94621291b493a246218f8465e92798e3bb934851bc7e821fbcf452981b9a414db24703a2b1209ffed2d7c4f7216617cc300df0a6ebba5c3b1478a36929f046736d626d0678c339e79b731c5803e2c61c28df03b78e4e303ec1b994042848f593531e9b60687cf1aa1db2e73304c14249e6f56991bc0d225a1332c7e1a87aec5cf7dc8979b4e4d1b863b11853ac29e6147bdad1f2e29238a80d1b8f026de85c81478c375c446346b1a3182cb684029d9317dc41e6e040cd97ab45eb63b55e8af0e2c48b142f53bc24e96879519239f2b9bbfae9ad1e05ae1ed7a6a6a332f248e56533fcd16203c55c73fe0cd1bc0ba2a7855c665353513657d03c3b613e9a3e4a41f389a4458d869f6e83fc741cf167467dd84c14d8403a5e720fd11f5d6c8ca39e29682ea4458dca167dd864e7d10d94f35cd609c9951cc244b9e833922bed0a2800f05acef3681498f318942a787a097b89d465cb5ff1fa0f5e227591f29cd669e06c0a9bba145104d1b4ce659d06cea7a32b623bb449d64dd488af8e99acb92c0a421fb502ca37405f86d94def84ce35ce51e31606a13f9d73ebf54c07e14a96e102d3fd65389d66538b5665258f3e6cc2116716678fa45fb12c5bd4326e03c7efb7e975285be6e8e94cf753bb94d0b491dea12678eb5369168591517e7d7a5a389f269a89e425c99c4eca8c9b4b8f85e94e7a678e38484b5497d50ba2c29135def0e969e1db67e6bd834275a89a02c709e4538836f1b4931b37a68b3635925e59b7363d28cff7a1c2cc7edf2f160a3d2a6c003fca2d131bb7804af5d35b6cd15a950d68a4c84cd37a32db87b72ee50863f3f0d6fb8824c13a0ec71f8fa7d69a57c3006a3a923a2e3d821e71c32ecd91d4a185fdd2e9925d0852c1ce688777ce39a7ca6f03e970c9b8f2a0181cc37b7de2b468e7b401279a5e596fcda7b7de320686d2f374cb7a27e91eb3e9ad6b7367be756d86b66d87dd3d7dc020dd42d5622e7030c08035d1c2d0a2b52fc0d0a2d5c01c39509895434bbece594785e3073e47f3cf557ee3c60be78652bede8937faa5853e3e8e428b3a54e47b820a3e2dda51e543fccce96d816a5909bda3eda0dcde70b4c05f9f5c4d2da0455b82064e47813e7eb468bd877c9518294f01f58e9d28b7fea3772447bd9e6906956ac187ea512ffc0cc7394e478152f3f1f3c17a1cba4079a63c62593bc1348f8d4fb7311bf035f6ab815ab41bfc04259252166f2d90ccb17e25bf28400515578a145d314274a548100541ca6597edb89c2ca1c6a57fde0898e1d2bb1a0464976e933397dfdc9185aaf98df91167ee58491c7646e8a1386d0d88e92a4bbab2b2f581972cadcfa2deb961c01c737c367f1cef4b691e969559e6371bb54ddbfcef78b9ab6930f40ee79b11a9a3bab5ae0f2ffcca34c47963b264075b953c30d0150c94a731ced7fbfbfa0556c03b08a88786fdea320fe319b628c5021c8a88690103072db67001c5481364c8c08829a264b1c1c9ac4c492c0bc5124058e9258ba6af8edd71b22c82b8588a135e9d91c5ebeb972f6fad95ec3858108de0d3996441c4054b5d50b9610cfa848932342c8ce044d1b26035ab0373c60757181c7c180a134410511813bcc244e941284cd21108588185b102041038620af365070854390ab3050f548ec28c910304a8307d354b61352bca8f1850641a2f917ed26071430f4d727d897403098c18d1e58627696e90f2c31d01ca4285e3acfc7047b63280b250e1b828595c2e233680968881c2868d0fc9880d20d7123150d8b00145cc754ea65259a5d539314c9a74680919c215910255a69794e9ced6789d3dafabf1e6161d949ca0256488a6248a2459e4eebcc65acf7377eebc1a8bf20e47cf6f4620ed27b40ca3e75c8327c8cf35ed1c38c2f039e49c07f57cc2c42c91f97bcdfcbdb79f832a79ec180338bf01e3faf5eb9cedbae92fd3df8ec9ce42f2d0eb5507c943c306d3abebb5c7f8f0372cf3d7c72ac46da1d650c8e4c23485a92564c846054a4b0317326446085ad587cc2f925a4f524ebd86d6061b803588aeae6b0056a126e13a05ebab48f63ce5637dd5787b69bf28cf34fcfb824638662eb0163f4ed88b0bc7d48c70acae26e1067dcd8bf2b417b9ba391c5bcc5fe75ef5f51de34290abd39ee1f811c9f56738ba9099b40488574bc8900d8a0fad27d387cc145cd17a9272ffa8e4b1632160c1b9b763134657d72f1ef353afc92e6174c6955e082687b94e5dbde3f9756aa4773abfde31a963fbeb0d46eab87fc3f1eb2ffee6e6e2efd85ab080f29b145232749e72947b9ed2209c4abdea6eead407369816af7b6037b578bd03fbcced588bb7c77419b09d5abcd733a516efd2bd1e02963aa4c2827389b1c1b9c46c6564e1b632b2282063b744327669e7c4d22b9808902035408310960e175abcb5928ab7d60f80c3ddd039af013318c4aa5a5ce13a0e715ab4dfa7815fd6b44e2a2053df52b9e67a0d28e50afc361c9c3983d8e7401c15c7398fc6a07dae3b2e03f33b9f5e0fc63d1e8a0b551d289f66dbd488641a9e401dd72fa13bb9a32345b9430089a50e96ccb1ae52b1b20b6bae098ec894295b1954a8e08cd475b5495bd21fab4d8b99579f4b9e04a84fd8e80e4a1ded46fc0cfd33f45b2d896cadcd42ae660244965afc2f43418380b250e1a86099b2f9b724872046bea68750648ae937edf480861afc1cbda0a14dca921d9c704438224b2ce162441811652b632b830922b62a990a9d9146738a06e40fa9c3c65b0f2275c8ef495e328738e21979b528640119968c028f164340ca4b4b34bcd1ac07c92fa985eb47ac85ebabedb14e85c821189169add685fe9ac81ab6529cf81a560a2583415ab4aedc2f5d79cb001089c37a0d7fc81ceb1c9c208ffe35ab2e843e43e41fd631ce4ed65a4b438bd67f380d3fc21fd4696302280b150e0cb7440c1436c2d8b8a19ebd06fbdc504f79f69b94d7f80cf73e2a2160e11a8950074dc941720688025d59af01adb5b65abfd5390faad6bab5d67a2a64512c99456dcedfe7f54bf6f10305f6a02b6b9d26e5ead662c0ba0fa9a3bf71fe7c9664787f41ef258ebd55b060fbe284c8921e63230c14ce35440c60c6bc9a7208ba612c81a523de709c4f6593f9d8f5681fcdc3d2116f388e6c1a6dbae9e9e441f274109d403c6da78e01c993e94aeae09eba0fa9a37ef3279341bd83b2b0837c64ce03e48a3a8bae288bf53d2da8d32b8aa3e4116f3fe69f8eb73272ff985fdab6211199f66811ca8f1e48799447d80c182eef4aac0d883c666fcf9e045269d2a449d3aa2d64978b490d6d5a3b22fbc8829247dcdd587032862f498a94c6a83f5e8620f535ef3a07727da401871ba8f116a494524a432099b7822c3d0c643e760e879e0aa31602b93ee2e93ed7418c7f8621902c6442bf7012a79b2a2ddc5c7aee398f6ea9bc0b8b741de6b6ceafd7aef39f1a5a5df893726f0b81648e352628cfc0142acca173da85120cdbb9e7cdeadd9c5e89d0ac67f7a4938657c88bb195d01b88b73072b0bc9e59adb5f3a66a5eb34c63d54bfdc3517553de58a73818cf6ffab8f1d3592de668517a074359ceb00653a6dead754dd7a0647052aee44803f5ea3fd64720c058c38fd22509928451c88fb2a6907f86e38f9febd47f6ca5610e5feb877aed1bfe50ff09c71f6f4306d4fc0ca524806cd51426fee9429e4caf610ea96d812438e2f086d9e29c7399b3b9901a5a5bbb0c41ea03c97cd47c03f4997460f3bb85456a70b58a081992a64a165d80310619695a9bffd4d0dac29fceb9300492b9740b32d9864cd7d150d0cb08111b5a45649ad6ab466b0b73d8fc670b2f38fe781b6e60e4f0b57e36e79c8639e0d60fa62be943648c888c1f2e63ba94f1a4f5f3a47d0ba9cb1b1287d21e8e887ecc4743eea4fd907d74169625260fec4157ed1d8893701044a6432d8a7d5d903af0b7bf30a50ecebb8af1dc4490a7cdb6853917b528851aa1c92315a2453b45429489cc69bf61ca54c85f680474deee0215ea9d9132f9f6a644dfde4e2813a9c33bf0463b42f57dde8114a8574d5f1f6c91c7a654322dea1d3a4a399dfab0348221db4969b13da66971a38ad3b7d6a12cd6b7731ca5aea72727a7a69cf4a8348d1656410b99220800000100c314000028100c08c5429148200fe464df0314000b85903a72549bcb836910a3200a32c0184208008000000800801087aaa60800c804c07847778a62af42045c3eefb1395633e29e034b5cdf2546325fbc9c514f576782761573019d19b546730653acc2cc978365d94273680b7ab0d6ba402a841a45626b2a7826cc0148433ad8c14b87bdb74671d85c2fc8e1cd5df4eff07fc2f3af20fe69ede617637829b6f1ac2acea81d7b731e23d615868225d8d30e4a2fd73a9ee00fdb3bb0379f2c623787d6ff0e60bde4e2429651c41054f1b8e5eb177d93487f1a85c27fffeb02c5ee47001de08d4d4e3da1a43b78d0cd36823dfcfd63ab1559e12971e51e16ad1d9aac40d1b8a1d5c36535fd152cdc1dd743a6d1e713dd022c657f81aa1b1ddbb6ffcae518ad2be036bceaf2921aa2705002f800c7774a43b0ce527d08b6496bed3e58b2761805597beb0c54416617d55f1b56af38d66223fbd0a748bfc1abad4d37eb1cb6c53d2572046012dbc8ad1bb71d254c3a3cb7c2e3dafb5f5a980f5c0bcf895fafada1b01c51473e5f878fa12557bf4d03819c452e6f12e2930139c8d49e428e443fa2428e924dc486bc24fa881878a5eb7e4cac4db4c33e138c559a01664d15c9de976c24c17b5706157cccb889fe8b74a68fdc3dc5d1e105186a265886585229a734336ddc1636a4af7ccaaca6ddc215b168432cb21f5cc0beff97ed10668377117741ea4e0f3bd6094655b7986652629084ecb289add5684280d798e06fe7c76f2dd87b0b80c6d881dede7223cf0df9aabd8beb883623568337f4eabd45978e1e8874268ec27d758f2532d17ba33b412f940e2c3aa0f4ddacc85d4d89299fdb772c99897e86f4d6737bd32d3df748eec647ee3edc273419315e634e5a6c633eb08a285b4d240cd05aab26cd03c1faa733e8e2ddf046250cc643a48e2f4b14500262a0095c55191d8b7e95851ca50daefed250b591c12e1f27cea70f3844a68eaa475d344ce97aef58357f5a422136c8d1fe0da71686bef4fecd995dd063a9faef2bda19214a5b53976c43765fec464fd51921955d1cb6be50b676617246b8478ca199345ed4fe1a90006ac71abdd7e67b3b1780ddfa7087d73d18fc125f72379742ea53aa3087501ed05505f77d03e141d17fdca58a194ae6d0c3040397839e50c2346a68db07d05c2dfa9d7f84c1cf3973f67a0f7a382238e01e438756988ad898382dec85da89415f9d93bad46179f09ec8ef6e037a8ac7145f753d6528ebfe58bb482feed01572e5f6413e4f7eb147d9e7570bc205523acbbe61a3cb0387a5b206768dc06055259084c297f7d579bb1dc111afb5defbbf1d15ee906a5e81033acca506cb4a6e9468ed0563273e2d88d221c33e56bdb1c7160e48a876ce33926373eaf2aa82588be50b23e6366c5704446ae843ca862cdfec095680aab539b47e3f445bdf47b92032a3487f5b76d1e2202c3282bc794d2f216d7b16bf4593d654868899018f277eae3f8faba52e7c31aeaae28fdd02f09d4032d3c3b7b30e9411460b3cab5d78d182d63484b6cb23b69315305a9d9a496b9899c4defdcca32ab49c06efa833c6e110e30e0e8acc1ba2ea4561595cef8a4bb8bc6a5520ef01a4ff440c6bfd79a6d175045316452ce210924922315e8d66b83a70966a8f2962788b7730a48439c0c57b50a3163d96adcaf00e8c58928bf5e783eee9f7b9533683224120d8c6fbce093b5cf4e17bd49cd8ec0dc92480420e5ea6fa84c455798b19cccaf848dce911cba782654134648acd0024ba76416ea60957934c9b2586926a42e08bcc6a3915093a968ed2eaf9561160d5b5a798c41377d03a62ebd9ea92c56e65eaa1e918bb67ad93c4d8c0b7e91209736a37da56e81c540a097037024cb036586efa74a4262ca2d657bba891bea062f9026b07ffcb72aaee241b57c1d563027cdfcda26064625d9adcb55825ca7cd8ab0de4595c62f72e236d5595f588094b439dd8b73177fafc8b8518787ffe52df8a56d030a1819e651fcdea99b504db92a225d1202c6c09f9a13c11231a443ff9b99efa069efd92979f4f702dc291b11f316fdc25965832d7605c31193a2947ff72dc8dd500b70802f66daea38bda1eb816f1d2eb0a28fc2411f77a7d5445cd181120e65663e9ec3d12b9b088f9a365f215c0ced12e609c478f0f8bd0fa58d2f5dfecff35d7e121921b90809b93198aa87ba951c80be1f90036629d5b15c67cebf3d496f120e9df73c60bb3b831906e7825194d91319d9ba2b65720eec3e7e3292168157621f2796903e8cb0cd5ad2b5f85f5a1fdcaa42772ee681b7c3a0209ed17218475fa3928b0401e2bad58841677bc944569126e3f1d148abec182d675042ec67a6f1be04dc4716bf184b398c903621717c280132166226e5f549901c16d963e80b4d295543bcb52fbd3e57f548ddfa59c460ddc5a1efce7908f3af2647087f57258507ea2661a66c937bb28509516795888da0695c34e116d433e305ec12cf9d6912e881c2f8a76b1e2e4ecf4742f3118656312552ce3da13a8f37b4e846503f51520ce9d76b10f2419ee8d88b5e4715bad2ddaf1ad1ae58436353cf49b1a719577186b4bd0c48403564039398b9bd6054b8bf13732349794951afcc2adc2265cc215a8e7c17a2516c9a325940469611fc5ea0aed01c03d8d035ddf7fde924dd6b32b8866256f35fca8886f5a4153c445366f61c0bb452a2ff470b0c4f1ed6dd9bd229998be2910743241261156fbb9feda06160c6201a02782ca826232e4f799922bec0f2794a16588fa930952dc4b3cde1ac6a174df06ff9eaf4e46c7caa56040a429ce1a79f15828970cf01a5c806fd636ead0e9df1953890bde4c8fa4639c05db3e0fc9bc0078c67e52683aba9d5528ec84864fbf4e42879d1ac22c28f0977d2c5cc565455db7c7a7fb5160ce50a67a58ad1e6ef4eae88de23df15a9043b9b5297179c238abd060b40798bca8731db0557b35b51c8304a9d7fb63017622a05e2b0ef967f29d1dda46d5b740d2b9f72b4b4f3ec7829eef860d9217b54045dce60b23d4bc8921e5f33caf44bbbe4c05b55fd24d3c00a7f2fff1bfc02e6c2ccd077a2cd8fffbdf1d54245da179e3a1269fee5b49c77123fea85bba483f55a507b8d50f512c4566c2787a59514b02af6c508fcb0082fed5dd2a26b356ba7ef692c9dd18b6e59500b7524c2cb0722b25d082593da662e301b0d6c39769bfaea7f3ab1244e95c9fce51f95c75b11e383cc210e55194832dc2c4b5bc9a977ff0e205beccea0a7bb4ebd82d1bc3a9fd819da5cd48f959ce2c7298ca44bfa02023e4de912c2ba61389de414c837c8ce7bd587017ae64da958b875084bb77c306b8a0c8520dfddce7e072cc0eed5e62364336c02a841efe26f0bfbc79ca321c8eb8ac8935678c599cc85c898673ca03fa93d052917a94364561bd32d3cefd43de8c5656b538d2e151e9bf018f170ebf7f2a8b5a31a79bf049a3fbc9b00e45dce6d81b8d9b4f45e4053b6c741a8a3e1bb364ec15b7718d488fc3c59449992aaf97a1e24d108166cc200a653607ba74325e1b8f0ec9cb25a87c15b4188aa8fd4673e0d68c56067ed2ea874065176af3ed165484b2fc8b0d2f78c80d7aba42ef9d66e3619d3da9d10bb6562604d5adc509b5dad6be2c0205cc915c8f099118c4d0b0bdec8dfda7d266da81ae89f8a6eb30c3e515b8bd846d985c463fad024185eef147a81aaa23b4b761955b027711d49655df8854ec2ccb62206a45f8763634ff0374b5e02737791f02bb47edfb037296d897de719e91d50b92b27ec7d44debbb2776cef3d762408c1466ccdd991d906cb0586f0947fab9987aba1ed5b7beb899f20401f53a79c084d4abeacd3777a60c137220248aeec16e8db96666627e3c4f1b8a5edaaac1535925a8b17ab9595336c800c368f078dd7c497526858b2f330c953a18d1f199550d5b13b709f66255c42c90748c4d884dd8934a82d7c832674990e2c530523a1d9b70f7dad0da3190be9e8486735baab7af0c6a39cbf1f98f67fecfd9fcd81dc4d9d396b7d5862fb0effae3052bb49f27c3c06e46b2bb92977d4be43a32d513c89ef0644447d1697a3ead73d57eaba307b29d884289744de4937cb65864650997a207f7ac5d11c3553e3ca3005e683cb74e79be7b787c31d6bc2bc6fe1025d57cb72623819ae8eca69232971d645c381841b72de279ee4e7bb7f543841c264b85501bc492424bf1ab2184b7d2f4fe1d48450bac1e91512e1e6e117069f66298d3debb42b8999a261bc135446fa74d27219a5fbe8f45696577c87f6f7096c059546712b1fe7156b03d4428cb242d2b56290caccd8927aa18ab3f7db81bd0a6efc765266b76b4090cb6420f68aab7c5db9a54d8c326602dc6a0c27aa7689e159a148f494829b82bd2ae450757536e95c89b6b4da68e897fa20d4e737aa559493b19ad5675a451d27c785f034d5f3151cba0305840c72ea9cf0c6c3369a9abc998f035696f40605549c942c8c26df2591d8eeaf429fffe619f4f9afb78a10e57cf8e12b4298b459565b6f2ab3f7d5c13cb07ca53ebecf431881471fd49b0a6e0371c7266f4bd1529b546855c5406713d2373ae3a294c82ec1034012c956c95889f36afe71b77ff65bb7c0b05071e5f398d55c13537015f0b2f2f4d3c72bdb337addd708a41eaed5b626d09b49ce479e5be7676bd72ecbaf154a11d448ce1571091657c91cf73dd66c58160b61510851c17d90a9ccd9ffc435888c1f364dba51b11f65404ec8bbcdc945cf7b3d3ca1e758a130681879002bd0332d1149fd31bc9d1f2611754cc59d788b2b4bb40215abd0ca033e579292ea907f6b0f30dcc8540fd0ac55475698fe9fc28286208ab0d0fa1bbf09493889ee0ab19d4f7902815334182359be35cce0ba28918a8dba75202a470349f3cf4a8d00b9b765ec78bbca9679179bc57008ad28b77119b8ae8f0889d582318fa6ac6df0924af10399ca8f6c8047a88b651e3892d22d76e0166603796fd69522a3cb88c59ce36312830d16a88e35a43aae866d58d4c78b4c6f116de4fde286a8d9f2d8eef7d70cf9b28633e3760f046d331d15cb4aaaa766d4ee566fd6e0b1c15b29a870f6c335b3de247e267cd5020fb3c365dab1309af01107d8d4d69ef6824ef12d4b3bc5aac83bef9c636a27113e5bd09531089f5e514c09cbe9d80f8544dcf7bb2fa0f4f65548c2a827812dd36c1f49f2a85ba0337b1e278c26358b274335a3f9e632a2f75accb2bff78f45d8f3c142d34746abde7fb592668e568db0224aa35522c4fe6291670f0e0c7ec1b4e829bda17edc6cca97df589ff45b0aea3e9a6f70bd088d6c0959741423f87f3ec210c7fb93f79e393337d8e59f68ac40b652ef684322aeaea11affcc733676310be2860d8aa0183562eb00b198ec628ecca18edc841e533fa1a31b7a98dae68d094a3396b319997e289ef389d09c251f23296332eb3750fbe3c0bc9ca3b30c10aac1af8a89140feb4560004e758844d71fc26693e5020a5d35da4648213283be0862223c3bacca945e27a0dbb90939ce7d91d5e2ce1e6d2097a28c9a3237d32580e3183187961661ff6771d74aecd538be3c0891d26f0bbd9693dabdb1ce18135f2d7db42833d7a390166c48968397abf9c058ce251b279ee9ffc4c7709ccb077c0d21d4c915ad87c9143300c3c36b277eac6c2929d903beb9d1ca6251aeee830fed76d63844d7b73805b8846ddc7be2d377dd8883064528f0d05219905a70b370113a01227cc8fd4463360be2c62f5093bd1f7881d51fa7b71c69fb1bb1f6ebf00504dd41594d168ee702d81b9c6441d31f97d587ed7abc3b70c382eda4e9d3f64e86010196b8613090d3ea64e7036fb9ab876d5390daada4c74c0dc4fe89e2ec0735d3cedc3754d7b922a6d40410e51a47022c6477f036564555ba430978216ea1681fcc3ea06efa7d32ce98ab73f4823771e2c9f022cc84143aa1bc3913f9b1867c4fd3a27c6eb1d9290cfd457907655d44addcd3ad2726dd351e98a0c2bc831745e3ddff79b9d228ad638ce92273290bdaf47eb54b2d457c6df2226981c5b41cddb291e14d48058de736720fa7151ea79a6bbdb90f3779159bbd577b011680c7a1fe3db4fb5bbcb2ca2b7008ea0d1c22970fd6bae591564070006488eb5cd0bdd12f3a477125293a1fc7515329b1c1be54f53999e874f522b4633c6276e5d27e02bcad0b75c2f4bca4529a44b8ad2231b608b280bb7d9d1771d1baffedc9dcd906edaa43dbf43bac2652cc1c78586e520da1574b3161e043e755680380a511748d8d6c7078d2cc0bb699d6b2ed7e2e4d6a2508536462894188f06f570c60f182a92882b643350c22b85eae96ec9cf1300cf7acb00e1aa2e29a3872975be0ad3f104a126e5d37e640e53bfe7aaae15e74393cbd9984c99b293e9ea4636423e811dcbc585e6a6be97db4f9e187887568ebe04ba8e47375044fe5fac39b7446dbb1b0b196530896fc00c0b012fdbe40d13e5f2172b2dc3af6cfbdd62acd0b13174e13cc126ca5b44c341cc43d9ffda0d0cafd4b8a54ca125724f653e4864d51a797b63037f48c261745311bf8d27c410b60228c13afb6c184965469f25ce033550bbb03ce70d5cf28a2f219f7a30ad2cd72ac3ee52dc9b56a0fb3e5d2bcd8120c1260649621968891b1f1de6a626e14d7bb2f8389faa8a9e97838cc2a67c23bdeb028f6862f1fde349915ac1e69d419e5740f5971f559a4be3a2da91338831fa8dda71568e160f06021dd75dcc772b6802c70aaa5588087ff01d5795baedace066395bf32e13dc7477559200d799c4c717f28a21b82945a1a4c4a077e89a149b94d891d2a377a66d6eeb3aa92dc1ecb576ed1241a96814a46da8ccafbcb081b5736d4e90a666c3368ba6792bef17b6e02e5840dafbbaef7df9df9d84fd950d4f0ab930e0d735d93dbc923f4f19341e447b2ce22fb27ceaeccc67dd6faba4550c78b61925cd7d2fd789e7e41fe6bdc7e451032a442f547a4e64ff4dcea090dfbe5eb2ed04f5427df0d8808ae5bb2e9fa79626f4e2c2a6ec2485e54f251424747308560e11a24343e02eff07ec4961221f799614b8713908596d8a400bf91e5d741db91bf1890c3856a7bba7876e1f6d3501850eaf899c0eb76b50fa51b65876989b8634215a663cc54439d5d27a4999d4fe8270a00a2b30046aec809dddd8368885324c129f3a3ecb8cec18db0f046de988643a34a533b52b5dd140b9df3897c8c0aea401f30ff0892df608b26c5e7eb3b17913026dbfe757a3e7fd88bef645700ce3947c6868e369e8e60eeaf4e746c6e3f409cd6fbb0beb404188aaa6f4c5eae5e8ff89312f6a7d8063c3e008fc34cd74f97e52431d42857dc5f7bf86e214f78389e2464658786b5889d33c91ab278d2add0122ec450ce51ec529e307fb46aa7f96e98f925119ef3bc7d836346338d18bf4859e9d67f907748675be65321a46f3312877a34ffeb3ae7500429de8bc423ea758a55279402155d106a446566f0d684d50b77cc97b4034ecfd11a059e4e91e5cd40a7a78276330ead47a3a32c7886b4e3ab94d2978ca64adde5453ebd46df2f766582a6ae39377da521439479d4e70f03bb70b101a21f8eaa988843762525236d4212d1d31785f3b7f16911f897a0b0d6c4acb63fe03feffdfbe821c0d908bdb83813558b9db6253abec82205adcfe71e401e0897f30f3fd192e534f26736359ab24a959baaee44c446eca96a1729e4b900624533c710c1a95314fb106f15729dd62b74b7345dc242ac46ad0114bff663fc011cc2c16dd093413758ad1246c435c796d434123d5798baff894a6c60626b60750c505840776e32000f44d18748ec3b38dc2a4ef87e1ee4f130e2768b38d45eee75eee9d7faa68889bf22295914c9b43eb80ca335ef4a09e841cb00b2ac34573b342d4722eaf837091d6b4220e1263290f006c2500534ca478a29504aecda8bc7e1090fd63d81a9815af311f338f49bffae5f206385e2bd2ab9d6ccea77fb1b1263ecb428dda194dd756b5fb13ce1ae19532d41669cca28711eb44c86718bfae91a382a1e8ca4e1e502e8686533c04aa4734cc189df5c12594c96eee18d21706858c350a3129a873f7c936755e8ab373a35d90b2ef271673359fba688050318a9644a66737e2b66603097e4005e783656d71eab1b5c79c7dd21453cecaf9e417534333e3c4953dec02c44da472495a0086e29a017e0c5fbdfaee1948d5e4ead29c78c52eaf14fafdf09ec09c7a53a6a8773cae0be90d2b75c3be34dc3c36b6df59dc148c0163f3480746fa027a32c953d04996d1c8beaa2fed6c835c9a56675d1f2a24e731d8cdfc809f20744cc0c56fb7e0139769bd057ae106d0487954bbe51579cc42b303df1f07e42ca58a41714cac5d669e43457c7c387d3fc83975088198b395fd3b4965c42c7b0d20e2d5b231d03ac30961d6b9fa1d12e73b7078799f8412ca3021b4045e3a6b34c976b281e327155f67e5d76489d4243eba0150d3fc9af300926debb7d394b6b121adcbc2a2351465ad06b497059430d46f970d0475cf3c8d605c16094316cdb2c6f7f6a6141f4d5ae01ea3f476502bb9fa2bc04fef37625d89614c480a3732cb6643b984549bf623d33b5ba9fd50a77e8f86edbbc83316810b0ce86f4cd2170aeb9d611ec3d8c718a83c7a20b79dde15dadb7530ded64d4b5dcf38fce242582329f9d6b2d70e7f78add99abfe700b3c0741d6ccd0cb5a8a5e913724fc3a89ba16547d2b3ee0c48e36c1a5dda14e740b8955e03debb344bfccb25c1888524de63c6804b546c419fecb95ea41ae7324275ace92a99695afd356abf5c32d432ae104654a61ceae1cb409c5ec0c6e13a513ea6e67de8101046365b749c25d29e4fb1e193c224dc03ec48d9aad7d9687478395062766f1964e7f75243c98db05269174f145149f816113382d8bf337d028b2ed03b98449e48f1c28f3dd3669d9c17a8a27fcd18d28abdbd3a5057f054a8746ee9fa172a9467a1e7941aefa9f38790b2878f2b85c4ac240b06d9019761ce37d8782db30fc6a6ed3094223a3b96efd290ccd85702a4ecb8dfe069aeee384281f705c1a1710fe9060439b05df3c3f7673f38b24fbb9cccb87ad2e5dc84a75dc77f2bb03a4bb700ffa82a017d09b48e6e1373015469c09357e0894aa22d3a0eb5ecfb078acb222d004e8e1b3f46652b419bedf149815b8cd143402f70220a76046c1c3cdfc8b28ed911a6ce754c718c20fed989007d07389648f2e4f85aad282f0ec26cfd5afdd8478320cdea21884a44e618d514eded2d18e5f51b7c7cbbbeac558751ea844d937d7f1c7664dd10408aa2b23bf87dc57f8f4cb64f04bb2108e6899ef2f7b2e449f3e429669dbd69c5ff705ec8d616781a512a602469f1880c6ac596237d602ce35fdc4d9021269a97fb8074c890fd117dbc587d5ac360906cc07f799d3f17abdd6496cc8a9ebaf5b8c2231097ceef6409e63f2a25cbe306f79b00ea9bab38bfaa9bae97b2a14f6a50fb2156c9331c6dfd2950524d1c0108dcf44aba1002ea6a1ea530eab216c731131b482f52ea75415fe8eabf4f11ada7036dd8081523a17b6010b5ea8ef0795bddeac1e7f42d710371f00d881eecc0d7e8e0900f02927764ca33070ebd12a841d6d3495b380a2fbc346423cc4b11a2fa1021bf567c3a8e3e9ac87f02cc16098bea6ede1074c527dadc29a80ed57ce3eb684659039462ceef603a9ec1dfcbc26832f5e3a27c3b7b8e02b297e8881d9a528a02d01fed9b3c1f8792a5f0570c8e261a5a29713bda9bc0fa4c9b5af332d0bc0988a883f9f1dd4b1e0238b7aa34aaee15f23a90849e7be6af8919888128abd00afa80e839852b88bfa0298f878e0a62a39af6140ea1cda4d37c8baabaf07498239a04deeeb59b8b7f8e43fac96c7e3e11ac7d6fb613d26643189e8c068cba9ac075ffbfd3ffefa4c8f26eb34bc914384b07aba06eb957a21a02402ec0b4ca122d3d00de6c16bd64b04d98ada1ca2c71bf3fda34c0cb603558337619a531f4e786224777da08fe0e54e083ea0f6a48c019fcce2edc4732b16aa203c2dc77eb7ff6b7047b524dd9bdb88b01fab841b4c3cad0ce22e7c8870d31bce93967f09583e635de14fbb8a10fe95aa6101dbc5c4c3b1a7a2722cee3b0e94bdf39ee8dd9608e6e22e92c6e2262907f72f0a5a2b7af8908ea9aa924e41d13cbba7cf1ed1841aec16051a2faed66cf96713f9b05e502dcc5722485ac11dbc3889a08112c5f6b8e72dd57905ecf8891d98ff4fa615b4fca0fe93f56a21e7831d0a3eb363d5ea20b2120942b434bf4c3586b3924d375442c59411d8f91790a16ba8f2bf8322b8631628b3cb201b08dc02eb2f54fc6721cb2beb47ff5f62d01ad019177dcf94bde373d58991cad74b0b37db45e97f03765eb7f06e8641305519801e46dd422aff8c65dd78ce8dc2ae80552e658876613ae24e1e082943fc6ec1f53ebf60bf8acde3e5f9ccba39f2e443c190286bb4b526e5fc38c69be2abd470f15e3505e9f9446dab80712ef019abbfdc16a3d5b818225313471c0b45314fd9706126bc85c3bae0b9f7aecfc80c7ab898fb4456797e9a748e94dd08ceff58f18441e8bd41df47a7022239ffacad6a79325f9421d08f4c81c621fdee3bd9185c78f1768aae42f9d6e74c5d4b33fe454a754ccbd5feae7efb687004e569d79af6cc538eda04341dcdc4c140e22ac17b5270cd37b712b6195f3c0c6c299984e78aaf3be5d8dbd785e18b0b58de4ec46038d545227615983a5ba217ab7a0d400b1624d9fa254b7d8e092f06646c9ea557de72b8dcecda8267603759dc58a7ee097566048cfa4580f790b0e9c2a8de1fc05b48ffaa20600a10388a77d44b4829569202fbe10e6e54dc0d4c39642b6b0f3253ef7dc764c896d897a0ac5ce077d7f2aecb22416ec44e3fcae0cf99f77ecf9e3aa85eb7a50a1bc5a279c40a45cd0732a99fa967fc870cde5cd9865a89362bd5af786349e737b70fe5c54f52bfdf90f829a78072007880b2df682906e611ddbab939104299c0bb78c182642f8909a93e58d3d56002a9242259e0a06a674af7854203f2ff8c55b8f3c911a140630ba6832e7ced3799fa6e2e4f7cb6b2efc26327e028aeb52865b8328bf2745145bd868b7b5fe7bb1c03bda037be8b13faaa9c9da5db6fcf055e7f0fd59ddf66006a1e2b33a815bfc0d1ee7df62545f359ae0a07e001ac56b59a019674b2e0edaf2567a35abb0b40c612f65c21af620c1a8783a09cda88c01c908f688c893cc0e90f70bafdb3a80c7dbfcc574e0a0eefa05637be9d9cfc8856527a829b0207d17ad486c881e0c8074e8e807734523d403e0c4e7dc0a0bb3eb84aaf01df34d2c00069a8d3646224a7f4bce96804ca74d02069812c538bc4772b01b63d1895fb7e7a0771ca22e6efa73d2516251da333310f095608b9b9c9a0f8d8b808268e3d710218289a1293df318ba543521561fb57ccd98eef73e7565c362319d07b57a5c67df699719a4ab70e51235c18d59dcc014df08b1d75653369dc16b7102f4cb534fa828efaef19eec0df9f10389cbb233c06631ff5eb4e25547b21bc5d25e09aefc978e98a5af91dd5a748431d774b0a1d38423d271da3a9e1731f858cfbb6286c152241a667b723024f135d2a9436a035c5633b420f38175f6ecffe8ad869a56c14c78f89712fd35c5357f71bfbdac3104642b5a62f134e7fc8e9f047dd925b4d0e9a709172ca6cd3c344dccf0b5d17f7f882e2b5c81c570bf855cda92d1d97bfe2f103ee264332660fa57807a58f9ab6530c7b6b984bbc3a7daacce621642513e85f37dbcd421e6adf5ae764b9b64c2fdc47187192c68f16f615ca7edf987d117739dc7920de9d2b7f917034b37444f488161133bcb609a6b586cb32d41817cb502f42c96ef625ef6ba7045cb4c8d1c10f8cee86805e63056848534e04b01889d5e7614c1c1d87f9194044906db2bbbcc96d88cbcac930bb02a56de121a107b51eb67c86d66db7b59879ed79faf887449a34573f4bd38525acd4a355852e0e6328aeff41343a7bd285a9515cccf7d27636e3a0f0e755eee1bf5d4c8a716e9212a7d36bd7250d6255d7868f75ceb2510d8a36a14d2abf7abc0bcf2e33585cc2bff3784b890f78fea584dbd84d886ceb93d8a1cf6829147f22daa19d85d5a006e01753f484a99461619a567f1729f286b51e8d3d4205be768d22ca07375905853e5cb8c5a4b3532625f0bb4a1563df02ad233fe0f1e3e88a791ec4e0c07e1d621a9f052c963aa6dde02df6722649340088c26c83ebeb53c49473d0822443767526aa422afce8852fac6080dd90ed81b0290d3f1d8b8e141ac258c26dc469736855636f63e0f251fa4184678bb039ce0aaf5247a5aec47d93d17344b0d701a1168df0926e7551c847111ea00f37ec79d0ab674b5c01a2762198df803b0998561ff956a8114ddeac273b53c808c5774c632e26a1344608e71cd720d901db9334eb6e36e4c27773d9cd0a40de072cf3bd71afef9b1bff0d01737a8dab3ef9dc8cfc89956dd50e5eea663b7e266ff042b22ce2add235ea5628469ad7b50eb988cf020d80fddf977df93f1031f74994cdb4d88c3310197406d0fc1379e3b91421596465040bd500e275b5ef1f77b4bb105afc2c4684e0c6d6abf6227f48e8057c184bf7313933141581e95489902f6f0ea86c643d9ba018febc98694093300f851d8ff52817f69c805c1fe3535bd9ddbbfc400e3c217543950a5c3bc99838e114fd55ea9200b228bac4e9d20d87c15564f00d143d1e00a45954053da15242bfdef7ca2cd69ff60fea8896ff9a4616d6ae84d9408ab8733a87a5e3d642e6bc92e16ef57243a359e83c51d5adf6cf5f51636c9fa8ecac699b2ce78669db8ebe04f43f38bb53adfece5ef666251bd4795bd689716086815ef76c0d8d228343c097efdd4632555de08c69e98e2f91738776ef88d203adf3552b503fd4381385f9f7e7340bc8381da712fb952709199bf570b45069903c77dcb608526e8882724dd1a459affae4d999a1267be00a1f2042ddd899a028be5331860f9b39d001ff509e243d9e636346ad4189c4fd9351d06d4ba128c1c4e91df7f96fd71859cd07c8d378927d0d1040a31184a141722cf8e96d72cadbec660c178944a7106cc3a1b97c824c741f33d137503c1e55575d538db5032717969c7fdcf89d5faa692fe9016f1c7b7477185a06ec6ed03a660b36ec115825628b22f8621fe895c04e36d6f97fb523295d70a2eb1ec2a1c5f8ec2fe9dcad70094b63b890b05bcd173407f092dc8373f25c1420ccde3bc5084c70d5eaf2b2b1995745e4cfec75fa6b882fd09c31e5d36e2a57056efa4d475bc18f007891f83337fd5a9cf262daefb57e56d830562622265b29067fcc42e64867fc71501cfad996841e60f10244b41ee01622fbdbc5615050b9be2b08b4fd7e4f6c06227f720a8d02c410595ffe8cc4aed707f022ecf917284a8d3e871c50cef30328f69c5c526138b14298070215994634fef1cc2213713e90bcda24767b84172513f066652b9074f34a0499a6cd0365769c1a3ff5fa10b3380c970adead64306c51cbfc2d4c44c8c6f2776028b49f11718466c3861e40cfc49a1860067629f36d52fcf1cb222958c8c65269a947067662bf24082adf5d0f3fd9d34938be4e22e1ecb8453df2dfdd0f81c3c52ef28270c1b08feada6f9ec6ddc177b2f24b515021dd74df595bcc35a7aa154bf6e770161d2969a54bee6c3dca42d7cc444372da1542581e840c4c729259e51fb6e753fea441b6c4197cc6d4d57d3b491d68e8e4ad2a8f4d10f99b930a28695aa80c109982d1540289cca45f7267699ac0ccdf594598d370b185a117ac3afa9764d9525b550d12dc6f2143d41137b21f1ed0b31fc430d934ea2a938fdd60ee85c742645f24c6770e3448b59e7344b6a561afc7a76fc1a2efa8da16fd3601b9a6843fc83990156f6746e81836fda5fadef4c52eb54da10792a8e6048592d0b5f7cabe31963ef48b4f0c68b0c4b6a729de7030fc0596904f1b0e25419a225f407768e8253a989fd4306fd71001408f80beb6d3e7bc30755cf9ed10f0a515cb8dac5d4a0939ae50fd5e7fbf71e0fff878bc88e040a46f7c47439f9dc6e9e8430572da2d4efebdbcb59a7df4473e9c6015a84b742c519a8b5b9933a58135dab4d0feea5f0efa413d57737b3dba0de981dfdf23aa51e08db8aa24a7d68ad2caa1283134045f444ce97473271c76572c90aff2b4f10d006149818047cf2a900ef0ef34a6406e1bb9f5e438040a6c27e2ec197c2357c8cec32e231abb48b20407994403688940c4b8d6a0f8470ad695d24aa00c083992857a00d5889d73265894a0307bbc546ba01bf17feb60e2dcf4123a721da5d59f5a47e0d08d949d19d20c5ac7d694b6417916501c89fb0518b0c5ab0be8b3d8f60f15eaadb2a65511273255853cce06e7616db6dc48e79c06e63492d5243690d52ec79a66a0f1ac65d5ea2737b324c0a8a1c72a12768e406d54f8fb5419387a270c6627bab9f3f1cb00774e639333d696ebf6666699a574a283270d17d05d68c8e2eca900eb5e72a7d0e6dbb5a7b23d4c2e99ed7808131b5b04cfa281fc0d38cc5677e55fa3eac13ba2b325f887698d59029138dd3afcec9f950c6578e573973e04e6302a33c7a0a0d10544ea43d5f7eb5b5badbeaf63d4fc2d636e19aecc54ec6a12048226a878b66ff7e35c114c438c0423982df94ada9f80506e3aafa4f90eee5220030d15b746d0ab45b37ed63bda7c0e7241a84a46d79a6c5bbffac788e9119845b83364aabd5e2c1fbb8909f23ffa56001db509ffa3ca57da35c7ccb14d1ad21f89c1735a016407d60518c5b6bbce4e79dadd126ae0f883d80723f9e2e8ccd75990b80529c04ff568059b527b5c82cbb2c14851e7b252464cde9531eddd886ba096c8a904b516ea01f06f80e682234a1071722cffcff7dfafefaa73b2431b31e88271bb7cb93a596e48470ef32d4bfa7486d904fdd7babd89e1756b850ba6318c68f05ef45c6b92ceb47833f2f81950a19ff39c0f1042be5a442ba6fdb738580713b0058618b4685e081a32c8208eed57d50fd0bf271d721eb5a0bec1e753066be82714a0b93aecbb729d0df8ea368741ebec11781c067a6d48821822b370186b2e1e6c20e7b2c4b0e55e7d6e1211a3fd6fa81adfd3a7fd114cee79fd75c7aea2dd2e2aa87632840b2c75d13a4631a87d73c7fed14ebc9f4f604d22c1408ff7d27504fe68e4d0e8860daff1b9cfcecb29a425a63e004b454deceaf5f6e94b49fc775400f1ae021c6b582774c18d280c7cf6732e455798c1f719a3c42e6adf011716a38deb6ca293a57a1d4a51f9412851db92d054f22edb91dfd62e6b52be3a64f92521238d6393565a46cf0fac72f38a3cdc2b60a6ebb3eff89b1b5db544941b46cf64d68ff555c8653cb86ba6753b3e3e87bfcf62aa140be722bc452cec56fc8647d8b01edc7071ba5a04aeb598a2eb5067dc4c375cc81f553a11d586f9bed470db9916d6431708ef4f3f162549b0d8192f36159f20235cad552e822d6f27fb9ca14b1bab94dee7cf81be67aaf913eff0e512b7904afe179466adf109bd74bc0c105251b91557f050b13ed4185b69292a9131b03298da5dfeffdd8be5dda7f65f838d0b6fec298c8c445dfe6421afffc3ab88207974af4deac3c9805243cbe41a85cecba93f8ab6d2ee0a7cf30b091fc739fb7850d190a7289a0c297207f87b159a0561d55400fc6de42bc18db19b0673d45d1fde93d6ad557011883186c0c99f01439e51a213fea6958e784b5d7a89b74a64910c758a38425b9b9e6891c32665313b825909ce7c5cc3498b061edb562b8da00b0d94e3112051023c63848087e41cb9e9d5ef30e6dc1c772c730e92c36a00fb5a76f3184113b3aa680459b4e4ecb6575644e27144f46c93e9c2bb359428dd05fac819963db353defac9ba605f6506ebdadf953895a60f23cf55c2ada02c20c16f4887385383602b23aa16a6b75940d19b404bbd5a2058905cc34b70dadc14519a119bbcabd533f47de6f4756944966a96aac379460aa55231692cbbbba35606d037561a4769f19962d01dd2de5ff4b17dfbc4a04cb20ec7e3ebcb0d8326e0d77e9bfb3ec0b17dec52dc2de60b47c1ec360e0162ddfc82018b50962a7a7efb97cc038347646b2ef453c6af4a9d3b7c9e0df8a5d9018dafeafae65d8b6022d0400ea512bb80f3a3dff9dbddd909c792d289fffd196ffcc3fdc3ed4597e182b1cacc9eeb6de434545066cbdb5fcfdf0b28205170da8032a104199b5451e6a65ea15c6898e681531dd096741dc3546f6fcac4cce11c429574065544d085c2bbc02807993f386b6060ee0b56caafba312fed026f524ceadfc55490d8f41160fb4ed3d702359b320c0ca62944a833e344a8d24079ade725b853b3ac979023504cda981810cfcd6b230540da59783ee0cd4cdd773c16b1aa68f52bccf825fc6bf701c73b73522efe067ae4762dfbaf4b6823c6cd7d522a9b84b597283cfe5ba748c42c1cf9227d8a28d37be1843c54e91e2a7fc5cbce2f689a1b79a4d88898f8afd619ee6883752eee43233b12570db76eb9fb8791bee3ff20c7b5e77fde9c64bd8e8c3146850cf0bb81d74f72ac26303a866811da35388113158a891cc2ed6746a5581aa163ba9c736031e37b54bfb6ff00b424e50102554109567aa16b814cb4ee905800c820287c1512bb241da79cb4f5eb276579a83746181a071f6215b3bcdc90cd057e17fb06e43e19d92ed01df4101d43306b60b4aabaf073e4d10a7844c0d47ccaa06b30fd437488921521de8d354da6d2af9ff5873e4ab190cdc9445e4d629b5bd56d866a7d292256d51804be07f35c336a2a53910aeb12ee3ca68221e00d3909a87a0cf76a55d4140d9c5701eaeb7d05c2475610268207ad0d674f56db3eb62d8e89d800655150174b8a4cf6c747124dc15c9798e5ba0dd62a04e8e67b82f7c950dbf8af6aa9197382704a911aff2e458ab934fd7fda4a5620dc02185e972c93cb2b8a8a8317fdef0a9e7ee03f1babaca7760a941114f43c96ea71bc00f532a6dd63fb497aa8a4421c63eac8ca722f636a27cbbf94ac3a81f000ceb3b97864acd43c2f07fcd2b55c30507b2ef1481f6387c33b5902175de5b8baca2be6f900af631c26ae1c93ad9404691d337188555b4aaf9a70820e53ee9caf1b2f284f62908287bc1abdb7d55357006326a6177890a79642b0a270e94cd629d65e0a1eed0e8d340e901c3d720bdc3a28f01a1876e26db30e0d0323111c7f8b0bfdda65e88a6da16b9af2938582a1a16e9f8eb054a01a50ecf05b2dc5a7619aa6249d865974dfa1f2c69c5524029eb3a05ff382b2573c30077485397975a9e6bfa649a4a8072b7ec42349991b30328d330c6e111aac471c664210ca01ca57db6ceb3b01d9c3196140459b42f70df7bd24d1bac7a753c014e3a407df854e157e11b833c19e613ab7e7da017e16c1ac9bb1df67236ab56c013f48504c3b2dd19931384cc055fddbe83351d074af7da1119538b4332be2ba8406ac94c7cbb2c7e98cb68f08265d17f94244c0dd88c957bc58cecd1313bc1e0f1e3b2361381f1b58920d6618890860db7a321c8724c41d826facc5c56f9705b41d3efda5fbe0f7ca3bdb93099250e495136d2f8211b88202684749f86f3d26820c6c7b0c367a1ca3dbfb31f80986b1c3389b4872593d31a84e6a25b131091974764e01452fb5270637e41bfcc5aaba6632e164e68a2811ec5d17f6b94cd79128a9512b3f2f7dff1f18132dc540b36f1f2ce798daff58ef873e1209e6e2ff9821f26101241e8f47ba58c52875d6ca85dcbd1b72238b540bc0a9c4c41ff33868aa0822c70a8870b08cb5d80914ef160a3378da4c388d1a7335363806bbaf1b0e9cd689327dddfdb327b894b091457748d3dc30e605d63abb2ab27606473b94801791e3d9d0692782546ec582337c490fe09943d3e99e46371c1f788810f32b94acdda5936304a585b3d8ebe64092d1b8f6249989785cf13c6ea70bbd94eba8f281e38bd796c424dbda62fd2f9801690e16b6ccd9b3ff9ae9897b245294ccb71f25effbe4f0256d3f514e532794e792e0fcd5b017a4a02e68b86b0d22a169a1d42b7e20da054ba4f8c4d65fb330f729231de9a258a44895c2f926e4b9b4d16befbb2ff1ae9147cafebbe32523788f504bdc1f95b9f0a5e87423867d9e2e3ee6a44211021d46db7cececa656ddf6338ea0c77719217f192881d7cec3f3a26eef04cf2b1937900bb38f79f45bc95777bbc11636232d8e198a9330be3ec4b4677afd2342b98a22d6285279b49b2f6372f259f84fc38536f3900a4d63351744894b4ae9825da6ef650e1dbde149230482f64abc1250aa1c51c4c304db7323de36c3e6a78de0a5fa496246ecefb657edd048221be03d15baec12e72e34a1727947a66acd961c35368361717ecf9177ea0f15bf4eeb7c60aa1a5baf6e1b4a2445842cc3f8b2078b62df2424e59ac2773075a749661cf085119f8a0d997c8ba1efd3b84aa090b4aeddc42c730500d295c1462e993c7c63b338402843a307e16b2409a6b4bab98505dcd4e53ce1b339ae071f938aacf44853dd9b66c17d5a85e81baead308543ee1e8a1b64badc316b1a0b250d36b5a8c992c45379fda70f3dac5382b687f470b91640b2aebc55cd681f9f24df4b9da3303a61fdb1fbfb1efb93f38193c6d08b48d0946a2a7190202d08b7f65eb7fdd1567da5541540bec7314a250e10a1b2d4b476cccb05764acd82858c857392f532b8eb1de95a82c9df013709342ece1a1371c210791ac5b45b70e0c3f0cd0ba6048e413dc458b593450e32c63edce10ac9ef21ad1b4dc825c1510c4aa3278bea3e8bf7b9893dc9fcdeb4a510b65679f608db85940bad7071b7f83bfcb61d08b211e05b255e8b022ee90812189a69873934cf8e94f469cfc7a7488d51132ab8ae55d90bff0d5f9370ea6dd847c4f2d4d54c8fb6825004dbc32d869a056e9cf3cf3d1e0967579520e3eac734eb2621576dd5ab1ceb9fbaedcb225468fd3df2b473abc7578dcc33786773c1da1ebfe2d2108018ae2f93d957f72016bd2fa30eb64c9b3b9df3046edb284a9e86523040beca619afbfa1fe873c786c0f4d1afbc71928f58fbd1bd3ee2f1ee929a5181c64fc24a351d58b5567b4cfbf0228ec373f0a15834f66191bb7ec622ba406f58ae6ce5637e79d7bc83c632350845dcb8fa14241ea1988f0c41ac20ed17d3ab20e5b721737417532e98891c164717f0ffa66b1f8473f8bb8a6d5d2933cf26b46420f794acdd8e904e22fa4cdb530d8c43be850c85ec143b21c3a6faa21bd41078a8e509e9fa09fc48d5747f37652790bc6f29b60adc9f0faebe7ccc21a69670c58023355c4da98852a2f9ed027db7ca2012bc6021ca6b6fea003423405a3c3330803e7f0debfdd0e7a568ea4e9ae51e930dda296f42ff213df04fd1a5420e567125d4f27dbe5adfc67f4e8544f78a1fc3c78507e5292bb970d1be158484d394607000656bcb5f27a830ff7cc4fdce386ab7ad67147cbc66fd57d56a909c3e850a4adbe8192bd1567310673579364780f5d7da7a17dd2ad247b35580961ee44d288d14f72c84167204f455490aa47a39f955b56be55068365f87bf1307b3fab238801fb59ca41283ad2c16f85ced17dec48590f925ee84396ce2ad70ac400381ca959ff1f4129840a932daab92112788bff75cbcf97abe50844bc4166fbe97b7922617b5dd81a91b9c0a8f46823cb168a75b5eb9aaac37e6260fc40fa3afaefaa5bce19e43d1575e4a0f9d332a29f123aab1a590182d7dc1c5624de05d4508acee1bf9ee36d6a33f3385f347f4a885be7b31e0d4a6bc8249a1641eb43f1c1e6994a2ea3b16eebdec04acde26a27d6aa9d7d0339968e7c8721729d85537e38118519fae8a41c965b23c0b7f5c65a38ef79dbef49cd8f975dc7f87759560ecbcc4026540d90a142e953e571f25927fffde1891b22e3c07c1861cf1f37759230586b499a8f4fe2fa31f5645eedf1841ba6527a492c42d6484b25b7e0c2dc4bea2a70e13459c34274e33def0535ff417a851539ee782ac5db3621073d814cb7acaadf1834c73bb36801f802b4b96acbfa4c5004fe59d2bd32a24576cda0f33b41fdce3ab5eeac4c47c8419b501e22c94532c1899f50743a4adb682ceced311479af705b43f718e08946112c486232f6de6ec16569d1da3ed57174bf973035b52fd4d780b3262a732c1001c35faedf21a1d691354bc3662ee43d9453e5d393070d32b7c62b9f14e0ffb845a6d0e1eb2618d2e1b91d20c36d601620d4c64fff067b9c54891e30824e5f77c1944e8eee66d624d69e5465d667c326a9a8ba9a2a3919ca3bcf9afa0d70300337a9a109c3b77e52e07420b161cb735f8a55dc7c695ac5cb8a2bcea0365560c74f8160b10e879693db13a131f9cb6094a16b4076ba397c4bdcbc011189a6dd73dc7e9483a42ddea5ac11a7b3c0aa9655150eba55a3ba56aba086b51585b9037283c6b65955417cc8a6b514fbce1da452ed2aa0a83fc019c85ce05de6900191589ed772623ad73283a92fbb9b07299c89dca21d452b6537a695a55b286fdfa2e37eb4dda913c7cdc96d8042659533c9707a72ddf0bdd652722739402817887bb96cd8ea77fedf9da12e51aeb58e837b41002d4b7f563ae1f07f355dca70179e36cc41e55f0fde64a823708ae088a69694d4cb5db5f756a26f21b99cd56fa04ed050dde746f06162869244a8605d766074d590e3b5d350f06b68d731be29532bd24979080d78aac31c207fe53614a9e74cb36e5f8b2e5a41bbfbe0783004c77a0a649e1ccdab450f1544688da6ddb016d10a819c2793158997924ecef636c34c36427c29f0648423a59aa0ba9860594c1629f69a5039731c898fda902e17f5ce62e541b56a84c6917cacf72cc67a137ff34927c58ff84b6c8584c68141b6d3fd3a7ab403ecbdd08dc1ca63d9ddb8c2de8e7a9fb6f625140dd123707110a326227ccbc7842bfd5aa9ed027fc155a2ef8da030a6386b3312270f548eefc6c88eb20f5c46663795dd9631dbfe45575d5fa9035af6546b47d679b02036cc01881feaaf032c5b23241b675b41801d8b37be5a8961849b9f56c79e0bda89d700f529926fa3eab15f7351ab8d67a5fae2f6a71630667ced0ad0f9d5a754ef5ec8045c8dc438aefe8c0795557b2db4a93ae0b410281acd793a37b0d66697e15c1ad2f244d537a554bf36d53e160aa8ee9a77eb2053a3ea6ef5d435bcaa77ea8c29e9a373862d17038b3f41b7e342cee7b9e180ce21ee0dbd5838e7e989153353138d77d4903631410ffb91777db7f5b9bebd5d66aec6594aae2ef16fbadf63cd0ed9af01dc57b02ed7f489ab03d5e2eba8caf5d04d511840dce48355be0da1c8c5f65caeb8149a6acb1b59b50a4d8903868729d148e8d4274d7e91cb16b59e82ab10c8c3d714f4f54926c3ac55eb97b72d4ba78b72815187fcebef017dbeeed57b062df85b7df8f28348760806fea24f8c1600b6e5c9ad7e17e32cff29d42cfcaec2457f856ab7f30cd1ae01d321914ab0733a8921566407954b9fba629c0448a6ccf3187fa62750b70c07d89db7702be336f1001b33360acc7a05f2cc374a4f70ad15ff34c52ded921bb5599e5b86ebbb1e41b3117c465946caabcd25666fd1d7edf75c0706e2ab513775f8c8a238c49eb3b0e9c6ae3aba42f60094b7c7cc8ec07834d272dd997f7f44183bab7f6f5178e20479b7216729750a18ebf5677e9a1319e0b0a1c22d9fe55adc259ffaaba8f34f3b131e006b8ffbf47e82d244a6804293a9139147953c8171e7b0be689c35ff633e54ac95e6719ea7ea5cfeaad03f31c63a8dfbe06578c1abc8569ce1f9a00d1ef61d165e0d3e3b347a0ce03d44f4335074d86ad6e6a40f692d9415a01dc76a88ebc9030d41118f33be2ac3146de9345858c0d06ae288f2a7b7ac75eef79d35c84d1a3fd42b5cb7faf16a2a9eab9dcb33059ab867ac513bca348e091af38ee6ed54de90dcac28c86168038b5f599e713240bda2c40402984ace1410fd9c8d4e8fc31570865c574290a98f5768761ed68258807aae184709bfa986db86fa690129e9305a74e884df6c5cd5b45bfdbcca3aa3dea13b9b8e982e1805d21044a969a370889cced618bb32ca38ee18efeab32246355104e21a51ade50290b3206c8cb419c844ea497bff09863dc0adb8f5c3a025dec5e51ea3eec5f3ab824a5955cf590db9a875f9dbb52e735b634d8cfe5f28615474c354f346a0bbc22ab5dae87da94742ea3e7bd28a3aa8f7b0cb9cba91401092aa77b608d3518b2b04a7f1e93e065be6ae931a0e7a6eceb93ea78e57788bfb59d3b2b45ddb286a513fd0a48103ce416d08dd331f771526473435079441ca0a060e40f393b4b3fe77e16b8d6bc1c94b0800756e852bc0ddffa73e8c897dfd3c332ba21c507aed811da3d0be39ff6597a0fe50135a74eddcb7c4f68c863cb1837e4a208af34d561e0d84a7a45fe854b961d0fb1492db9862ac80f5ee5eccac19583283524049dc578b0b8284daec6181e1d4b308b1500584e5ec0504563a1de8fc00ef25a685101547a2aff6ab62886ab5811f4fb00e2005dffd74a21dfbb8debd16188c701514c472d12ca0a93d5e1f575d02cbb6520077f997465f797a2527d4c6dd28f1d1e98419c45d76b78f2af5df5ed4ba5de31127e4f1056f075a72f716d77e2a8d3201b1dacb59d22a61ece1ffd3e3d8e1b87e08fc7890b850fadf1334853acd03b1feb2f824fbc771c050494fee381c7542730b597b5eca41af19d4f9b43c22cee22cb27ed6ae5e0606f772ff849918b7de30b08a57bf21a1b89dcaf66c999507d032610e45d5094d913e842a98144744ccfaca195bcf61b96f9a5b751ca651d0a4af77e835c3e09e0efaf6665efda482573842bfe235b0ea00437d914d93277f0d76c0c97a5fed00b014560b27cbaf94e8e2b343d8869d771c9baaa7963a4f769ec11ae617228b5fedf936d75b7fb9cfda1fd0228a066cddcfde670fef4749f7f3b5ca0133893db5839bb2f69c9cc42d0d9854259c05e179f466ca8f15d9ac52a0401c5d4dbfd1606268457b7af377bdef8524079ed339e267ae1a65478999821828a6fa57108ca3a780a382a55b0558c3136e3bd3fe451731eed7e855d713aab78e9ebec43e6310ea79c7a10ccd187c86d35e9deb96c5adb4826783d21840e3c36048b3f2f96ccef819d125ab0ea24d9807d0535f1c855753cfa7def4a2863a8be3d3d40cac6055abf27b738bdfdba734f729ddb8f894429c05ee3efe25bd1d030a8d5a8e78e0edabb5d667b9d2c5331727cf167c6d5058747388a59844f0157ca85c4730251dfe464324010691bf767e287b21337a01133f1230cfbbac12d43bb40967f3775306b13e6e4fd7097b8694287d46f1a8671fa20cf33d51665f89de15cd6b8d4362574c690ba57187839447c95f69f5120e51e4d95efb0760bfd2c4c8991e1a4a8c0b79d1d614ca444c928a56675ecdaa31aa90931451a4ea464984691d322041bd05ebc8635900f4447809e503a112068034374f2dd364e16a95b85ce610786446efa473d9a0e350be60208cce974a09bb991a6406227c92711c01c1106ceec16c7ef1b218d0285bfb34af34d7b8253083eecc5360a820daa4237e441f071fc464fefd4b27edb0f480ef192ae587fa99a805045574ad5a0dc5149808549b6bb7d73852040dce7cbf0ed5f0721bbdc5824f56bf3875bd756c258d104e1f36f94b1364aa26a2e49ce0a11c1fb9d5e8dcb886abe4f0f9d1f27c0716ed71b1342b3348a599e422f56858ed5a87093683a82fe03022a95e4aad209fcd48eb1059091fa0744853cd044e69ba6c29f460e9f4d8af0a10b7fafb4f5c9c70aaae6f688925618bbc27d09cad29bbd93c37dcf6a3c9ceb309efdd0dd21e25a83645fbbfb2b87a7e93407e31264f89f6af4ed3e4022f4640961cf9d098b56fcb129cf4971dc3b9b8fc70eccca2ef332252979afac760e20af6cf26c6d09b54b42290f17ff4a9eec01dab5aa5d463b5c55573b9c7290ee33b953cc781fe99c81154fbe121e5e66f1750d326827c263ea3a9681e5dc0256d2a52975d400fece0314c8335a32e59980537ba80cf416b433dd6b9df0314bebb17446d0646c629235c5c6393d1b0d157094a1cee84bb1aa393074dbe4f16852fbae8a09db910af7a3bd2adccfeaf013d98587c51c78e96653539b62d4c636398491758c1746ff273716d7e0d78e63aea748bf9a48237a589d9baffe2fb64244480a4014e341c969d8fe6077cff35c998149cea5bd1706c6cfc2de6531ad1aa84ecddc68ae1c37360b95ce355293b8295b9c804f72c761f3b57ef2471fc2e183e90d14afdc70c9ef4e98e0b46643596e995263606c3867f45daee83114e74e50c3149d69fc933c0ed990789aa0b71f54b1c5a01a8afca063e29f4152054595a21e101d395a491ee8c6d9bf1d91eae4913a09d90910ac0513b10b0fc6513e17f96584c7d9cfa6bc86ab1ed366df18e1b4abc30d0efe08f1d3680096bf9b376ff81c43d04dd4cc802601329b3b26b435d2ec04c12fe278c66d20979941b91ab1ac55ace047b55014c0ede4ee29d0d99086c2821556f4a91179d56cd5ec1feaf0054a9fc0ce0af4ec6c917f156a639d91629985b13542d569483751a3cc134e22d614e84dd2049aab3ec31297a79acfdd5b2e77fc3b287baccee5157fcdaa1a2cdd6614f9db583b6abd06e90d50885a23489e7a9857472fefc7a5c475352a3aa08ad5d2d18845d8f0cb2f8859f2d758f0f67af6116888b3c5ade1e2ee78ced3e5f0bfe25940940b99e7bf1e45fb87f843462f3fbb7dfd8eda26c8af81b4b4ac7160a2e7f887edaef3a81912b5cd859c81e11946c72f6ffd0168437131f03a5340e714066e67f66b2ab2f55ed676e84fb27ba9428905ced0a5f7a119ace3a840a9c9af0d903e399ca912505c12cc4f3e6cdaba7e11bbb3135b4b7e36677fd6859830084d8c4b0e74ccd02d03157c021dfd255ff3537e405e3f1d0fe836c2f1cb4deebb392cc02e6d4c05c2dd910d0ffd18e42f674647bf1440f5166266f5d52be6fd9539fc52d487b82459e73b5e0ca251216d76c69e0fb4c4019e9b4a501ccb53158a967955c81eeece059a5b0207fc28eda87de2470627f8af7185a818b9893a24c81f3920a25e196d668589492cc434bde85ba8926c5fcb23c57f3d301937d59731a0a3d2008c28a5324098b2bbd263c0f9a1074fdabe4a85121b72a74d0b1314385c757be1a86b50537998f3efb14999ab9c69afd4fad3badc61eed9499025a69283ac0bb55bdea481d09ad2569bde73c6f6927e76035ee3a7f80c49cde86b0cff1bdd4cd575391b271755e2eb54e6191c58bfec1961029f178aa74497bfdb42aa6e59bc16d58c95073ac95842b62b46fdcbc309d9ee4b424dcaa48bb17c5dc6240e5b6a2ef2c3dba724a79f4ef1490a8797fad94073897f428eb08cc76b86a6bfd465a694e3d7c33fd9efd540e9c72d2ca80743774e246d1c8d47e55be6853f5ae0029283a522f46f93fdac991513ff38b7471c802023738a479ebd9d38e638da04d557dfa8cd4573f8a3b2a13c19e6baf482e8d9bd0885a58cfbe4dc53c107ac456ecd7a087b3bea4276d52a1479441a00505c66377d6fe693682738494b3af989a84b6252287b2d78fccb585b410c90d849cfe568103712239c1704148bbda4b342fe07a46d7b0cec8046a04fa23ff468c009ca41c4565a93b432fe50b46ac56cfc0f4e1840c4894bd3357726ca7c36ac2625d48104b707e126dbac7bd7b49f8958d6af26797c3037fa6c6cc0346872f0c4ee63a8c76faf67cc06f3dc6800e99bd53c3bd80ab522e700c78b0b1e052220427042ab99e8d5920d0ba4a0789afab4ff41b19c0d004ede52fd750e36730b063c54713c06f4eefc3c7a3c396b6910303b55651867664b102a26208e9c012ec964a317dce1fb2c3685af643016533fbd608ac93ef43cd92bd09917b4b99524a018508f708df0842b10b283f09d80a7026ca1f6dd7c69db8c65251523dd29d47d943e3f628b548130a0df5cead0d2e413be2a33efa78247feaf4d5d928234d182baf073e507e2ab859430d7270a60d30747f770d48adeed3292b7b2c958783e3717b685c822653efb304b304b46ecfc9ee45001ea15f7a3cf855de4a51a5b75bca745945c0e3f13649f070910081b74942ebdcd3517785c242d5b059ceb033d592b5e373a64d15b43f643dff7cff1d9fa3faf8fd9ccaebaaeac358551feb78a3c171c1eb5ab8c9056b7596719c4179d75f1d44acf1679051bebfa68b4625dbcfa06d9a8cf125ef96338ceabe6bc7866dba76f917bb5ddd7c0119be7f9bbe84a9fd1d83c12a5f408698c43bcaca2085294354c608856d9763d1b58ed569669943c1e3510f87358ba2acedb185862e1e8623619eb03d98ba6a079d9373b04b3d974a3df34c116997b7b0b33b3fb118f34ef348275baf7cd8fc77cd196936b2f97d2e6a215ca8aed9a51b165073dd125ae01170707890e3f77fb6c7bcf1011097c054780447e22fdf3175d571aa70659bc756783539292635cfd6b8eb8a2fbd6e5df1d928321fc95a167fd398411d2ca9e5c0f11894443c90e190ede01c600237bcdf17ff8d07848448112335ad5a0384f11013edece710a09d1b1e37b5028e54dfd1ce06a3da72ac05b46b57b833f0c6e37df1db78abfdca95c7a0f6d5782ceacfab292f060d77e640abd6f6c531891e69da9176a41db5f66d47234790340fcfded2d89c9ae5ff3411ef91cfdf4d3be397489123acfa987635d4eab837f9cb27106b37d55022c94f2bf2973b41c39648d59be7a979f871b035a1fdcdbd65993bc0cbeadf1851485299b9b977c025008c9ab8b32c97eff29392bf5ddccccc1f9ddbdca4ca42757021703b46f624c68b538cc98a12121528474c1475868c19d4986804b7bbbbbbbbbbbb628290e0b3b31d37f1c8e9b0c8fa1387002f16e06aea6e29a39400309c6c3163e8b5acf164c46dddc8ed07caa072c3531151173750bac1e886a4309cfaece8b19391b9eba279970b2e0422caef33b2affb141b63d4589aa67dfbde55dfe2b66d31fa5744f66ee8d2dd37c61853bdec3451882863163251926127d34f9ca5b9dcd98948501c1276b056aecb65f4d9f136668ccdae038e893613edba4f868cc2704c945f3291f74800d8907af94154bf3d173fa005536c50f9db0f69514aed555f90f6b5e2fbf8cb83e4d78adf0882c8d7be2097bf3c09b87d12d027f555e9d00534e587bf9ab08419d22e873242640bf59fc925987224c3ca4e8c3136e9581500329a54f9a908c61893d0004510485f286d6962058c47c42f0c1369ac18c71655fe54c30555fe46b184991a0863c450e5cbb83748428818638cd10331c610c3a5c6d805e7504950e26a982c66d5c8a096a48005944c8c998c0d1d8ec814dd1023f38215349119a2c80260c82cc124ca0c2616706a22c3e5862f51646650a444068c1b88c898b1c102543c8911030e16f87284130401c058c2e40585872c4ba0b858da07644e4d929a8c692f64311b906c20c306a2346cf0c42436864912d4f2482d6a1e5f933146c9bc05b7756577a4fa737b715d712291cc51b8fd40184e2b706202bba1090c3064326a46d54c8ddf0bdb997aa46b0b488e7aa3c62f4c81f6c9ad9bf632ba103f5617e2e344fab1564434aca9fd610df46de07098a1494abb3f5a23bd758de1f60363c42c812d61d2637ca006514a9c94c8606d300e1cd0fd30d6b8ad4292daea1a6afc3589732fa979ae72724137c62825ff7e2dff50a0a9fd6d6533a4fc5ce88f31da3e8a7493730e628c51070be87ed27d77e516a594a1ac45bad8b030ae0eebf8816effec662835d3b959fb26d77df9752791dc5a382b92b342a3b78203da68a38d36826ccce58713638c5e39d2a7f281c7f766f6707804fed87275687751ca9e1da3267bf8a0b6ab1177706298b56be3152e80baa9fe3c9a67f7a34ea8bbbb7bb84cd9ddd7290046d058c3ae76cbfc3a64386e1fb79f31ca9f3f5b93114afafaf3c7285beef4246ec2f776c13f8747e09f3bf6c5dfa4b5bb7ffb87ee66f160f6ff68bbda8b3c61a41b54ddbbe3ae9432eeee8e4be3393f8489839cda6fcf693cc250ff9652fe87c03b48000c60445c84616262064668a0c90e34389343163b98e2454049f913ef24452bd0e287a41df84003154f8a4fc2d6eeee6167ecd27425873262ae8851014622ea01c64c51c516269808a20897c1961a30c1c1f184aaa0faf7f6e8da3121ae80820c8c95b07240f96ba5c0fe49aa2bb50bb4c0a28595ca7f02901061988490c2534480a2fddaf342c5ff218292b0bf886b5f0b3d2415db2e4998a95df73739b93a21fef869178ba084b6526817be166bc7c7441ab573e4b7eb5d3cdac5eb84a5490dd74989863e4d34b9be38e38aeedeb5cb9d992977c739a7cfe9cfc349a1daefcae572bc82c0bfc06da852deceeaf84f6efa967aeee9b19e3f8efbfdb39eb5e38fb48b3ea1fee1ec7cf8330bdc49c5384e0a0dbbba404fb3a62fbcd4dd528699caaabba50c3195ab91ff020afcdafb884976482843631bd53bc6ed07bc80925153a25423538dae3dafa426b58f5a8c5fa87d2e685c94518b316ada27bf7dc12b7f8575b1e3eaede12cd5df54fe4d6a1abf4ca5bcd5e9dac57146d6b6692b5e1dde38068573c518638c316ad23deeaed4b64ddb18ddbdbbbbb5f8b1bbffa8abd32cd69ccc3fdab5b4bb37d6c6d327b46b3f046ef7a04b4d4ac571aa94a66d8cabc9d9dc944a95da1c57f456673fceb66d4bb172ea4a7777df388c7126d95dc6a86952326fdba64929b79a252dae5edd7ca6acb15dedda326874cf9152935195d32eafc6e75d1fdecdae9bbda5e3563c68341937d50a38c6892915192783855e09796215d7b9bb0fe936329b18db2aa678a4562c16a5fe43687cb058354dc494a879bc86de44d58daaae8a53a53457a9542ae7544e44cf709b23e78ef628b56d5b75504043625c6e95d3a57efce5e3a3c2ea476a05e4bda254bb9a48232202f235eeab7e56af38a75d3f9b8771dac5dbca472af08fa69641798fea5543ed045381add68243bed6146e41d94588004155debc6264a4d2a2a6b9e611082866fdf191ca63236e83c2b33585e91e0f92a4b49556ea587be94d6b2828a82abfc734cf92a9f2f90c17b54bee88d1fed83ba8e03687cb21a3b3ca3258122c06c5240595bafdf6feb1d991aa9b31b5f1a91bd05aa183a13585e6190c62908bf852bb9b53e551cee11f23cb1145c366637b18ccbe787ba8b13c1d667b3aca5f1c420c34aace9fac0fbb29ab27ef612ff6c54fa64ce577352aff93f70ccabfb065bebd87937aefaf7e1a4ff5b1886b7afe2e5bf273dcf5d3190ab5b35379d67ef29eee992b6366820fd00f7ff1bc12e3591028a076f17fd0792cf3177f121495839bbb6b9aa6757c0f716aaf2ae020c7fd148750a2822d757e3fb51857c845705445fac5ef1fab7f44188a05f70a4490534a54881a6e540b322068b850a9c631228b585d1dd4c5e26949cb512c06ab61d80a29507e2d5f30ae82b4a6b0c0f717abc0055b53b45f9065441935dc99bb76a4e7e3af2b43b5178885ba92c52e3b10b4cb818a29a104ae288b448a18d515eb68294a110c3c298a13356428bb9483931a466e77a10122f01ef91de0bf998081e1ac6bc4909e86b1a268f547c2b0a2a51f70a8fe4466ad2f701b2ea31449f9236fc87e78cff4214d3274c5d03fb8e9a57e67474b7d3796d0709b90782472d73aaa8ad66906a2eee9fad5bfaa256e8041c3598dcc663bdb59ca7be6f7e41eeeb7c7c63daad4eaf403790ff7a9f8d5dffcd2bef0c6497ee107b53fb6b7363882c4c89c4dadbdccd45c3a6132543ba85ffd48bc8784f66242ec9e552d41bbf697e3c47b4eb0affe18a692271c90ac0e0fe12f88a328d155f79777797957d3de59ba74e9d2fd71982e6cc27c0cd61ad6aee62149049dc5269a18d2344dd38e905490180263831a59a4903dc1a4c687a218c8a198e44a524a29a5c6b47d318368484a29935c79028b35a6502286882e9c698a303da8be94012545b524a7abcbb483ac0cd72df93442b04147101d0307b34359ff392e1e06604206008b1504c8328500aaa05add25a62e71dd054b4ef0a07ddd25a62975abbbc474e44d96fa0a099ce1e405982b76f8618cff2b5b0f9a51d1126954839438bb03254ee567668900a4d0ad2e130f3708a3868d8728d5290ed30e666aaa2e130f5cd490fe5cc2b7d490f500335fa0aefcb0035292ebf0af7445ffd36c41f77763661cd89318f3182d95dd573e689e7bd54b6edd66bdd77d230e272326ef3d52aa714a793bb1976d979d9aa7bf9bff7d766a9efd7ed058a27fd050da11f6b9a95decd42eae5c436eaa1c26a9dbd51ecafbd347351f3d1b7e72eeb86df536de115f7d8d27a45d71e58366cacdf7cac7ea93acec0d47357925e811627737efcb18a5bbdc26f6d23c2958d38b81daba1f3fb1d2c7499c5457c5ad8c454e9abcb84daae3525d978ace3aeac14ac7a58a624d4c36f1db091bf911151db02751b5417e2e95e4b1cacf58f8dba9297e69721813466a29ed62362ac2b1030d39eae663682855ee1b8fc911af4ffc73fbc2f9313f0d214284610cf5ad80aabe85e6514d6905b9f1fbb552d829ad203537bf5f6b63128eea9e7801f2976cc98ebbc48393ea41e51e1ba39913a3213889872d356c2a957bd7c9b35476a2863c2602aea3024ab43d36080530dcfc0d0d409ce7e728efc1f982664802a200e7614801ced3c020ce1704821fc09b1b5f1008a2802b213734006f40d615fafc9579da5c000605fb2a0e28d4c5507352eecf63a80f7ca0fba9d4f37a5b395ef5735c149ff11a66039d1f721217e73782197dcad971a620c314685a6afbfdf678ac34cf7e0c2526da5c00f6648408d582862b2352a47b3e0097a3b8547e3ed3b05dda010a6e871f87057787a356879f9f78f093cbd5319bce4983f382168c17322881422c45d1f442b4040a0c1a018b2d305088c13c394206c65640abb7d0c0cc0cecd14314483118c845efe834b16558230b224e76ee05ad0da03043060a390d4ead15be209e728319a240a1550d33d03f4619b460e6040aad5e8004fac72f8865e0a48918284473c611e8df6eabb112276494c0f616148adc38f4182bc0785a2e509834711919506902395f42c31846a0d026a31b460a18249093ee04ca4a1328d49d39e268dab08596797483153230888198c129060af90e47a076460a1033b06938228a285003a306982890eb68342d6b1c29f10ed6698b2632e712f58453ff10454313c8f96ed114451228d400323d84891a0c6a98411328b4d383981844275c684004725ad79e27110326723003b9ad6b2f686540d4c00914ea34aa84818019c0cc402e654506284ea0d00e94334dc09c56407b4108c0818c245068aba0828015283102b9ae6b0f0135d8900472abaebd03b4414311c8d174ed711618cc10052e93922e512047bbf6dc8719c8000672365d7b41ae0519658a40a1950e4f14dd74ed79d10f349881dc8daebd20f985071a66a0d03a1501469caebd78650b2d92400e47d79e63a146124720373dd0d13550e982040a31bb1358cc38710472dcb5d76a982992815b85194dc840cebbf63887299e20818cc31145c8c0a03ea3062398b604092b92402166bec107eb7bfa98dc75cf751dcd8faf7e3e7b21d8597561f5f397b43871dead9ea5303333f3b6d1d4d43c57b3d56c5b951f4b69573f8dec35e856b93528576e0d2a2b173bd51a5a975aa1a0aadbef340f8b45a993eb3ff0e19b67fbfa1989f2cf9cb88455ad41b515ffae363ea2195237b903a49582fc14f091ea4377e276f2399dbc67c1e94a1ee522384143f752bbdfbdc8afb5fd5657bf54f28e6d338d9e3ef8f0114be1595dfdacabd5b3e72b2f7e77146bd84523e0da0a838645e2d6004abfbf95be161fb5cb632967d0bf12553525ba8572636957c76a33c5ca1ed7560ae189324ddf708184adc7713268e0fa4bbed7efde1159b1a0618b69a7586d294c5868377d1dfbacd0fdb063304683e3a464667ed9d139c4bfb065a21605f8b55f41fe7e30eccbd73e18aa04d967180cf2f96b2cabe3bffef2e7dcb3820ec94f0890cd0d686a5ffeae239c5e66476a0e7ad05251430e780085b62cd0007fb4a630ac700128b45d010350bef605ad164d4e4a80429b1210a0fcb61510f2a8fc49407f7fad207f7ba92465c497d60a9f267ffbb4179a33dc40e70e166d0591d383f6092e567801063cec2fe5e73c1454fbd9cd5782d5c310ef7922eeda1e5e0b44b6ede5a1fd7aa14fd55ee09e9db5d0fc0d51a29188d742bbfaa714dba30544fbf8da47262a667599a880aa05a8cb4485953a9b87a97e5da62f522a173b5f49b949606b94c0566d86b2b216b5fed82cab06a46b7f093c1026a9fd496a7f3f0da4df05ed350f4800aafcb65d75733c32e198be38a9367599bec0bedd1853af799b53a5b7af79aa8f5e00ea265dc6df3eae50e89f807cd9d5cda95acec6af43a22e4c76256e66228d33c47df3040d5766831ab342458a07b6c7cff668ee9f2d1e83a7d82f8f45fc34ff682e445aca5ad6b226d24da4894c2dd409112832994cf603886a319b1cbac848e7b0cd3245462277195f70cf0356cbf67cfbe2af41ab600b1d587186146210000a0454f61e95dd0b5e4f29a985763191fe3eb023f87f42768c2d4d2e040d295454932225d174515c03494a9467686790ea0f6dc30119cda4ddcc8e934f4c16ee0c8b152ad508926d9a91718f4b4c14ef88f11943c61828471e738fcd8cf88b998d301bd16464239a9c363a90329ba5543355145c1a45508c60b1b2b9fcc53db7d6a28be23aee3eea280dc4aa8b7240c39d4d1ab9e7169ba3a0b151236ca53a9bcd664a9369622370699f1e4858b8a3847009fc710c438cb00c5a40b9913468d8b00ebba836a9bc89bc67f51edf699a703cde11ba8eedf3dee6711e9fe20d2e768cf8f8f8e4482aa28b895ec0eb4eaed1ced9a60eb149678a494a95834f556a23cee1a870dd8d0edd0a071a32d18a0627079a9a9b266ae80d26166a08b5c942e30e269b9b3528efcd0d336ee044a191c331e273860f0e19d0f8fc383aefe5222226f282fafbac8f07396e80918395c5647573a3abe33f844be0cf6647b8f1c54cc756417dce9821c30a13319940820924101111f9fcf08f65e5d0f16388dbe112f8a70008b0d9016b87fba6053d77543bce83c71b5de2dc51ed481c208af8949a130d37c6f270a763b2e3a345dec0a0e12209194af59f1d1f7f2d2e0e1d380e070dcabbbbdddd3631a0fc5a7c5985f24edffdb40f8165357edddd0d44eceeeed8dd317677f4c254f566efd8da1641f9e3abee8dbadb366b65709b13c68e9ddca6cdeac4df1882d4be6c44b0994046d9695b472937d9cd8a6f33048d326a51eedd44d149bbfa7520456ece2dceda1efc5df9bb591d790304d555fbea7d495509ba3d761dcbdf5d6af365949299e76b9d9c40ac470da58ddae51f1849d13185c6a32e4654b99aee612727560b8d1144598c9115437efae019c939c9830f5929c7c7c8454382e2508d2f24d620699ed6c718a353c4e98f1f7fb6d8a02def9b7e1869887ff0852c1d5f485939be90e5c117521c319c2f9c4621f59b161b34ec66ee365f386521ad29f21dde3a79cb521f51f50ffac610aec99afb2c5839ff0eff863e55b00d4766662766e06ea2061f93962dae2899a8419558a65489455665134330f183ecc9a104462e4dfeb38625a6b8e2a5ba6a36c1344e57643a7fce7905cb15236a76708ff8f30aac4625246afcd6ca47a4341011e58b0e9e58d0448728b248d1344dfb20f5a44445893035be8e958f488bf83259719f60cae149e205498c5102a96a4890a9dae758f9d05c55601113638cd183950f197657e454eda3f6338caa3d3d628caa3dcb4a1355fb474287236ea8dae358f9d05c581c01e6468c31e270d951441a463451841155fb1b2b1f1aa54bc49002eae8090dba28e150182104b793ce8ede6c971a76496324c5a0c6b759f9883963681874e5438b677471b50668da965043126a6842d59629842a59aab021832664f005112d20a28baafd5639aadad3346591a166a90a518df157ed84937c4dd3344d8bb17972545966aea6254a142f4a4a645c3465c9c2c251e4da1e4ee5892acc1237a8a416638c93a67eb99c06d490c7135a96ac3040d57ed218aaf62ced9f49d5de25c41455fb9d1554ed7d8670aadaffb0a06a4b31b8220b6e4372a2c6d7b4170c41668d1fd6b8f2e505dec5883238b9f221674e036a5c42ea414c8d1fe94e943151d4700415596600e35c9460c0006003d452e3c777aa695786543896a65099223c99da26b7edc809903094acec6003a8288c80c8949682f8c208880d8a5831a4a52c456c6abc92e84ddda52c2f4882eea8bb9445869913b3073821bb517709872527742883f67b6fef2e37af7cf65ee8caa05dc39f6550aee1772d5710e83a3dafda1534741de99e4e721e924907d89ebef9f83bdbc3e6e9872b5b2f56b387f86b6adae86071f7ae0ebf8dd749de53a45ff1e36b5ff1a39918d3a871e38259286a7cd5174bc5b88076aa317e38239176bd14347424588def48716e7c1f6a16941a9dd438654c20f0c154606b05185ab45d314aeaaa3102404643972b27d2744a8b18a4356541d6ae363eefbbfcb5fc85eb64820f945251513c8a694d4949fc1c0d1b51c16691e324ef6125fca4f247a548c44944f10b3916e78cb18bab18e5aa536ddf7393316a1a5be917d7481cda3d8d6ba7fb58feab5fb0fb9045e4323281e8a7f37ef82b16c41be22f21575443463256412d05015b828192f61a0c90ea9a517749ca957f0a43bd1899c0af02f7778c8cdcc87dce55d1eaf0774882f00f274569cc7bf84914218ae0278e19ad0e3bed60b1b4546a4e6e7ee4542b1fa9e7150af3b7d7e616a3f69af69aa63113738e07e1bef3f9e13e97efa83e745556815540a010fe9091d888a1b831565081db17d16c38aa53b62f88c1d60adbcf9f1f0cf3b7af06d97efe377bfe825b7703e12f2e0b9847c130e03d2278012606b6806cbb0aa27dad0952d54fef705610ede517d49ab2e3affe7eed7b97bf3af60fa02b50fef953d4a10b19454c45f3e733c1b468991d390c894a0f35eca893665d44651cac0b0065a2f88b37f2392a95f21ae6af281c2ccfaefc56b24df4042b4b37015086527e9ab2d509829fae18b183862573445136b6879f6417557ed8f116dbc3515c832adb68abf69325a1629d7cf7f7afe352352b1fbefdc79523f1fc6ececa3554cb5a42495977547f61cf729e15030d218a2e5964e02165e84aaee44a4a3e49d876bee7a4dd51fb926f6482f4915be891595bfd496a557dd8505256fe9c50ac1c28c74aded34ff6253f4a1155be92944e6db43a12ca09a71ad20a15c4a0ea7f3809003464074d367e8c9fe302628107e530ed35951f877754aef1fd9f66a73efc9bac3e2028eddf811ea9d1a4055328b78ceeee21cc7c0758aabbbbcbb8d345530c607bf47b7f5d506e57bb583a50ce3bd512b4e917a309f1f311e10a6522a8b7093f3eed62614157c77b806145b1c58747102933dcb66a620201fafddc0e2fa85d60031318b8c30b0a126223643cc4c02af377bc083aa040b8974254ee93d4766a9e30cda3e359bff9c66a0f7ad6b0380fa85d1bf7add607df4c1e4ebb5aad0f85ae2dae6edf5468533bb598ed7b4cf77094eddb8a53dd24b87deb03ee08168668336a9eb0a554212a109ff1c2c40994ad35bb99460d0ef7d1766d34eaf01a69fbe875d20e19286c7b96c74349dbb3d3f639be90a56a1c8ff385cfbaf185ac8d05866e543620a5e6a1f9ed1b4bf3a8d0db37d3f6d07e93cf79e1576ec8eaa777f3d10b8b2c540739d2757e3fde4fa14fbd01e16d3cd5afbcd67caef341f05a292198d8526b1a69076b7b6e87b75d10310403819a67fe4685053210a680f03b3ca0e6f9eeb7e7be1049e57e7a21923a9fdd0be777c441f85af31764a15fdb775f6b7eacf313c2c445cd99f3fb8f80fcd5d378ad14668802a85d714f65e5f168d79684d4aeed456883ce57a179e647a45ddbeff0b81ccc62e0ae2ab06df377dab5bd099e4fbbb64fe5405fc906757e612bd52d6ca4bafdb46b73b6cf0587511568dd1fb076a054287f1bbfbd233ea5fb58e8178f61a24e89d95e917d4261bfdfbd10ec537561bf91585f682b050f859eb457a576f77bce20ede2ef3d3e94ca3ba5fbcd2bb24fb55d8b71a7985591e45af4199130fd19699e0d970cba83052fa85ffcdb218942fb1b22a45d5f147de2518195449178096a62183f50f3c8a3caaf42f3b0a050fe1fdc2d7911537b3581fd0e6218128e88902726e2e13a21baacccccac04c6520c5566662ade4cad1d12b444f146f9f8ec33d7354a7032071774ab5c41802b03d16a2b85cf058d9bdb24abbf0a5400474b5ebc8ce1a63db369cbd462ed3021a6ba6e897a73fc58bb1878ea2e49115565f4a8e6e100fd10a10dca83846ddad9d99e6caf71e1769ca191080b438eb4a66d5d1847f45059fb8aac530a5d18473cf199554e56c2bf9d26e8d6d8cf0e25bc20edeaa75cc352146264f4d6d7d6b25e987d90419a877f9be1e989334a0051c39e2d085150ee57cfadbc86f217b39ed0d404bb75da50298772198c363bcc7bbaafe53df39eedb74a46cbbff9dd07c30743151852afe27ece397ffe0043270aaa1224154ed91eb67d42f361f8d4b79cea22f53d09c181ea43156c1f02559db27d3020f1d747c45faa2e54dffa2bf53191bf70c044f3abe2dfbdea4356179d8fbf1884293445f35bcdb7622b7e53bad80c8a6ac84620704137075dc6a00106c230bf05443ff54940fa0555f1dfbe06a95d9cdac2cbb1652876aa4d5e08183014ab468a30602856a7c020b4c9c0d46f9fda425b260216da5fd84f7593810ba63ed82603b7afc5338ef217d450c38e70f217ffa6c9dfbe4e1ad3444c5a16726da6767592bfa648bad2a4863d6b275dc4db5a9750ba87953419542a379da91cbdc3e6d6b61766a6957ffb884af3eccf28109cbf12b4d5bfe0a6b630abfab0f40416232c2d20db02c2ad9fa2fa84546095fe24759ff793d424b50544abfdb552bfadedabac1b695af27342bf260155e3ee744ee8cece67478436e886446d6666d5582bc149521db3fc6c36cf458c9663a9e90cb7d8ab7f10edd931455f869cd849141f691d817e0489c2e15504ba87edd1411b336307513c81421cc59725c02afda0062435811f4ea369fd3fedfdf0819ef00389f70075511cd43024ebf403c9eab88deae7871bd5b56b7bdea889a30534649db0f2a1fdaea226a36c7d0ed777252809ed72c7a9db05091e6ac84426f8d4241dabbae05a40fd49407e76fe1610f3b7fe62b0fb19ec8fbef6fe24e0802e9fa04203b58be3d57e394ee809f6e5ef2f85913e3e8a3a8a95c1450551fb439b96c02a02ea360d9d51696da212e539417168a1336e67bc8b1815e18ef0c38ebf711ff990d632b1d2f7d43d4def8186ac1424a9306385cb11182454c3c3141780f4855a32a411036b825a01fe351e9256803390d404e47327c5867f0b5989abef121458aa4735cc4bf56f22265090a9feeda47b968b24180c16c32e4d61a63ae8cf6d6c85612dd7e2af6a8e1d1224341c551aa2ddb3d2d675bef2d1d1acc47450fcb44350f304bdabc29dca4afcd3dda7f23ba11060a15382ae334cb56a354143b6c25492c4a86ea00cc54fedf2ff9ad0a54fabe36fa3023a47c0f5e90857b9339f676ca679564b104c75fe74549a4154fde9a0e6d96fa2213fc57797a2fab399edb1b5bb9f6284220a29b5bf9c6f9783536cb49fb6f16a94bcd4137eac50212cc4232ebfbbe3f7d7fa515598693c01390e5de718e2a3d0709f7866c84c5c8e01161242991583630c50804eeddf1edb7136b553b59fb74788105f7d47646aeaa8f36dea4cd5f9d3c6f358db0b0ee0df55737bf7053c046739fd82a05c4f5804833e3319e23d3f29eb818474169403dcc37dfc211bd81ea98ff353de065627fe72805ff1e38c7b465c356a8c435627c6c8c31237cca0e14ecafa77ad53051888029302abd31598abc57eb1646a8e9ae372fa6643b5046d9edd1e110c6003fbf20f67eaeb6f00db83045c9989b9690af1971764684531c6d2594c7fe9981ce3588f0bac0e06f8e5afa37e21463885e4b0802ec736a6e5c0a996a04cc6b22064a1a87c44d096dde430146e98392965b11c025b7dceee2c88b4d0aed412f42b83f277165886b828d13c6100b8abe9ed129959939b9c526eb29b638c31ae4777d75ec02a6f5d3235b70c547fee8eac52b1b721d3344d8bd107ae6776a8a3ae4df476775f01e1182a4ebf2825c3a293d9bb70817243ebcfb5e3f3365f3379720ddbf0cd17b2ea46433a3f74edf8a4bc3db9bddbb67c21847803a75ffbad1415f46342d070ce36a2e669178f76c9ee62f7a87e555e0bed496de7495bfe9fde96507e2d885b80431950606090d08241620656892f5423464b1358a5bf0532644370a2a88b47f7a89e559a27ff0b1d0214a479b69701a6a8a8a42ef64435756646000000008314000020100a860462b1482c1a1236691e14000c7b9a427c56990a84519203310a21630c01000000000008608066c4120025949beb342a08da99c14bd40aed6e5ae059c18bece3e31c32031202481e301737c55a5b07996a4ef450ab8c2c9344c846b0b4ed9c39f2e3e8455d22c6257bcaade8167c40387c9bf12f38617fdf5e8a2ea2c7bab614bd54564dd3a17670f89de9fe9a112a22d00169d178fb531749f72dd5877d220f48ff91ab95c5b8268c8f25c178a7404d68694635be7478476851009fafed2039e96ab767e9e83ac46814d3d4ca2b497301e6338da96dea5e2729b3d48a6dc9a6d87dbc21cfb8d6f41ed4a8735b32a40039082e76c6040c40bc1a446ba2c5f59ca560a3c5451c72bd6bfb325a370f2c9d559ff0d6f893ab4fd1f12021e520069a0b14516b6e0305a22945032da57ff829965ede15325b2ab9320edd598e99f7b3e45d98eb05bf6f44c1bec7a7860ed09932b335007cc3d6f09de42cb657c656d9edf2369de1cd2eb564b865d9fe0321ce12d8312c8509375c4f2a62f0cdeeadbcbcc761d82f2f5b80e6ca90018b2a9e704d2c94fcd6a170ad44d4ffa4faf775322fa1579b46436aa18636555991c2c3a06467aa2d27554715e24a3d38b2aa1d61f45d7398236e5d57b35a4d3011bd71c6ca5d175f0d215d1be0ea7eb60eabdea5ebdff72ed14081ac7342bb16eb2a84f3643b2707906d419451504e2ab2460bd8267ba6abe7e26782a7d0550e3d1b826450ce28e53d3d325785889bb5f17648aaa8e011338940c4c03c2be6fa9427e5ada20d6ada7118b4f507f07d0990ce6c835a1d9ae8d8ad303a4cc1a7975b6ce2a9b378a1cab18bef6e11bd7db76dbecc43830b5d3737dee3e5f06013567dcda06c34d1438de4423fe6f985ea5ba9bcea516c58a060cd26846d2b9abe4bc1c98b08d69255f68221b9a75c9018be647c0e5de9f24a5ba6844ed731fba7e47ac6fdb12cd543dedbe1c3b10ff92d4219b935d7485d0b8bfc446c94d299a4f87b9af0a838b0978f3481cab9933c6cc36f4cb86d52f3c61d63d307c9b28680d1f364b9b1c8dab150383a7222ac62492a5c686a9835a0d513a08dda2463c144a490ed1dcd06dc77f16e3c9d0b94dcde994b0f13d5069433403a4834ae2c03f76ac35318415d3f208e3748e1ee9ff10a859fab119769aa2a63ba9a7823d9c72f4b1ef903730a7a46d9d2cdbd77ff8367e2e871594a6e75dd028f7b9086bad736b6247c859011fa399f401c43b8a742cd9ae6cdcac91ac54a1c9e7cd241bea75782ae3bc9e53f9b7ca7c8f0ad8edc40a297681b2d32f574b0ddd1eae80e0eedbdd61d69e3390e359ea48a393985c96d8397d570f74d7e6e9ed7872bdd8f299815a6d5721d05c17845597d8bf9e64c8ebc5d33126c9c35b5d7011ee389bb5e19b8808cfe9055e606eed1795297ca82966dc8ba3532dba779e7f91f14c0f25e8460c9a4ee4f238247a07f5d624d25548d8f0d15d6dfaa3bf4afced947f0ddad0d1d9ac8b9254dc21f769e44db7bba0f96354d7b45a7e0d89523a666da797c33b17572127faf0ff3ac99d0088b7e966765ee61590731521e813f07311b2251a5959c858f9601aa44d11f53fa49a8e62fa06a5efff21a07a2647b5bd85e31a465ec3b2be336f94ed4db158173fe8719450e01f99cdb3486540eb93edb6036baa5853fc5f8d312491a6cad9896d0271892f12dfa7b8f0388017beb01c3faaadfd32a4b189b72ab2daed67499490dd426de2a40d1f7ec5d56271ae571b680d48d341c3269ee8a96442280579123815fd02e97bddeabd35bb7257ea4ec1af879df3f74ae0c01c6f56c7076fe3ef7e05bf0d552ccd2c733c7a0cbe9546053c312a870ee00db5f3ee538a969fca0ce58e672824025a679ce5216e6eeacf3f505820175ed3be7a5bcba5460dbf1ebd60ef02d610f3c3f90cba9e7648b4187455f724c5f9c9f7aec75da96555116ecf3f3fdc218649260d406174def4f2e2fd267e81cfc8d7881d1c92dbf9ea3f5f154e81832cc46cf1cb0344b7962e31fbd5471f728928e93e69d692670a3c84259f220c66045f1cf5b75baaa7cb0e7ecd804c5afcdb5bfc7c4f8c8ed88d8eed88eda0a6629be786b8048da6d3a2a29f2aa7d965a1550cac60aa52bbbd72c665f54ca6a5d5b2a96f02790f000dea89a31fdf32a9091c6cff0c4eb1f6fc6781d349876f8e1ac2b23bdd70d43348a09c35e3b22bba9ac6486b4de48388afd0203ed374b0346ef63650a0c0dc33fbcd6e3d1f550526c252c313beee7f8815c809c0a0aa431bf552228eabf8864a558b2c1139bec6d35a49e2b7d9d3289cb40cb3832960e6ae2a903bbface4bb5a99fd5b1a8bb24e897e62cdaa3518a0424df95d2146611e29742899449e0927ee966cd051005f36cf42e0bbaf5ffab32b0e7b5e892a665351b2023232ae6f5e8a59da1ce9b54d67b4c6ff3b1810cae5b9a154514266a98cf040645efb3dfb63aa5270217648623142fec434f1bc7e85c4dff0c03f4375fc9cec82941e533bd73a889b932f829ce7b343e6be751fa1fc5b7e5f5bb99af0842199218465909134e78404490113e247b12328fd15b10e40b4a9e70ba961ca33ab1a8098923d29288f1ad6ae190497d2538c52748d3ca7c63276961871ed89e44d2d781b8272a8b89eb47f24007038b2fdac94fb49cd859854ac757cd192a8c4346a01c6f3f0e55f0bfaa33cc79463166a2e2d1fe7b623b52ef03e2560e813f1ee2f13c9f125a1f49f259abf95b4add92ed9b2ad2ec303c41789bb1cfb3c9cff949653f6f967f462b7175d4919e65f021189b2c40db4cb6012c6db9462b70690208ef7dd6070191f7d000dbb022c630093cd3257f81b2d2c2e9d2bac7194ea88f4228766636ca966cd997ec13b20c7fa672291090e7a370e3b5dbe826dbb9f546e4fbca1d20597c57c7ba254d4a3b5c6f625ec79561012156c77d581878412c89f76401ecf2e684804aaab91525138b7d42255a4069cdb5d2c4e54234282ea76b05d96b6772ce236068cef19c5b0438ac8c37895c8352cc0fda0bc3add46852950dc1164546f8b853f1099b9fa9ddd38cd922481aa35c12ea5e1bdca18b577f1567d391fc64b7d5d04361bfdaa5c6536772f6e5db87efc213fdca1b38f20bf81d2a7918c74a5e0c6d3fdd4dee3c6db28791d667823316ac4e01b4e407aaac34edc907d790cc1353c67accaa7380c9a937fb58ef3630662d90e7b39b38c0101b606183eb5d5da9267fcd6801bc20d79528a571e3cce59b3b2e8da18fdf55806a3cb16fea53e3728b07eebc2979e108f8d2d9b532423d231e506c0546b2a7793284d9a07aaea5d03ecd9446d6a27f8b551dc233b1d869e85f654fe929477169c237d13542ad3810ef416c39e0e4dec26d093682873d6d909dd8cee81c07f72add41da08950eb414712285b63d1fa6a0a99b1d2a80bcf4dcbd8f407f3492cb86b4901141ade321c9b7147c20ec43306cf6d8d72b2fb3c962a2e1b547d18e239f4b9c43d65fb58a181e09a0e43ecef88bf95cc7858a6ddc8c0e8c8a8d3b3fb407e37d2a17cc05832d064a4779f1d3f346b74ff69f0a5e88cc5b7a56d6113aa55ac246f96bae29a6cbd62131b56973b7bae3bf81fa0ccafbde42e1996212a081d6d47b6807ed3a5d9186d4afe6d31535b3a71dd6fc41cb83f1906ef84eb21afbc487d95f16dcbf9301f03dc10cd19ab81ad9be7bd9a68b4a4e5d70d8d7d77789302ac51db2bf43f53ca3647b4bf3064c51ec0f5f5dcd40dfdd28bc2d94d132cc829d13349c9e6a0571e3848b2b0228222491c2ac0d7b31e56c942f5ed68bea2724deac0271b0c2dd8677f707d4b0636408e0eb619c5e89def5ccec3304378e622ca32ea91fbbdeb97849da203b157a8951e1ea4bda398ff50195f042fa29a464c76126be7191fe2365e7bef6f5adc696f30c5ab448edc05019b47533efd2d1a8e9a98fe2d2f4f71da215cb33d6b5527132174d202ce7f46eed2682782c472200bc296330a43d6164f4a4c0e8841e907f3bae7b61a9f00deeda2385a7ee777a3bf1f67c65f55c81ccbec3e582f22f2897432d2dbd6e071d5bd68678bb4562d645c79950edd76dc7ce9a26712c3b788875f6b7daf8dee73f79737f314c2623a24acbdbb193958ffe8c3f834c659dad64901b437b736e823824d066221f9f296c4b07c961e8431238518c86e4a20ad19a8e14bd1b772234e0568b6c690bd31935380b6cc9f50b5ef9eed3af09fb477359a3f226e6087793d5e49ba0343cc5e6b8e41534c1a6015eeda31e3871306aa5075471542bd9b324417e10f09789e663bae8f6464847544fb56fde9f8810ece2aa1af6df348888352d3d2dcfc0c19efe4445b65721baa97730f665fe1e3dfdd9cae5c7281e553868b48c43cc863540a8eec6422cb36620147aa6a07e8df4865b62bf0336f71d99058aa51aa302fb2e3f26aa795233cce1adc34ea64029ba611fb03070bf8f5c3a619cf28f282efe9deed6ef22142bd61d9e15c360e4df2b90b3e25e590b9ba564e2944a4966e7e334c64c71ec10163ed9c9e45ebe08a3940066b96bb75cab2c3020cade315faa88964168ae278fe6ffece6898f92a4e4820a84fc04da65026092b58d522dda43c703d746f61f198300635f47dd5640f3d07c15de9c05c05bf87e463e11867141e6949cc905cd84de3175e64b8918fb9a60297b654e8828a4e5dc978c3208b52a14f34a234086da23ae5427fb012be53d3d5a260e45755659458c9624ad84813ef999b24c3c05a4f1ac88e0580b437f4f001f7df7c344cc226f07dd43cb0e8832b6a330c0d5b89160d968c8795b246065516f958c9faad7f64532ef0c20c72d393ca7a0a63afd936835c53836e3c4949708cca9bd452f24d2df384ad2fba0636872482b2c72ca73a8272d5dca3081bb49022b312925c717be76cd4b20ba54d6fde0ba3a34ec9f520e42634a59f83ece07341c16e490bd7cb0ee488705fce61b00ba28282f14fe49dada049e96d3ea7c5ee08bb984c8896d16a83aee3aeb7255296f21b01bc43a2dd4eb06ba5e208fc5c2b343b691b96aba210c06ce91439f09ad60ecdbb596d885999b6758b9ecdbf8f96e966fc3af177a1efd6fd9197f55050c0a93ae7f5a4a1c38e855b462930a7282f7008aeb9d694f6e4134dc359085fda9ca36c205db9fd3aebe8bbbd5bec90cdb3b005c809d8af3aa4c1024a2afaf5bddab8f94a50ae23b4b5bd00fb9edb99bcf1fb0f3deeb6cf16ee2cb310f4b679d9470be66b7b4fa11a845eb2acde4687d1e0b19128fdc2d8815c222041cf6282cc4f7e2dfeb3a3aa13890fe4c19e223788914aa52082578214a027dcd12ac9558d59bce8bb718ba3592a6df05eabe9e83256bcfe2a00c114f513ffa81d1064684b0c9f8d488f913d518c08a9634f09e6a0c8776bd68e7868808832a1300ed1db4ab2719cdc4c6e4f8e2bc55702d411c351c29d2643738ee67ed12ce5da1d099d57590eabf96ecea7ed375516973bc9234af21d3c0c197d0b45a662133912d18caafcb44a09a5a3cfff99748e07dd590c5fce43d50631be3ab47936ee1c4e43f171f2b865fab372a60d956cc4025d6c93bb8afc259439ed9cf38f7a7c95fa2d7f7e3afb63e43b904d6f2cd12090ba11dd3ab90d08eee8d49b8fee235238d8d3275a8ff44962c4d00eb1cd41d2dc54ab0a41d7d86d60c93152cfbef5c0ca3f803831d0f1c14100c64a9a260ed6fe43e2dd41788795cab5e373e1ade5f354f858ec509d2b6e59736cb4d25f37749703ce7f6664f47ba82c21f2386b55aa9b427218d1195ca79f8072e7f0dee8d09f0cf7b868d14061a2cfaeea8d67791bb916f7e8d2a87b05c112730a4b0a9c064ab8134c6cafebabf20156ca9840a38e7e53975f061c76e8335c021ef521f7554e8eadb7bc726ff20a4c4297221642e3a7c4923b854f6888a9985fe6e3f1ca48296385683c5b9372c609068569459c9d9ece3d452826ef245008c09814804fc097aa40a7e0db1dca621041bd08207ca708c3c03537af66d52f4608fbf49c0a56e5d87c734e39a451c7c975a07d5a65aaf63005548b6b82b9645db2d6e4133b46cec7067eb8bac33f2dcee9e22479ed878b866b68fc75e51439457c42aec219b0401884a6f8db72bcd0a662f4e275c520f663565a10610135f5f4924e9d54a0a03d2ffde49039c684b95741e0f7b35c173c8f915aff228137912fba39a768cd4575b788bff724102b929155b91d74001393cbe84af7d46552fb7a9c52b92d86500fd41e4d4cf7465372c8bad7d15db2043fc9b7ca2df98cbaa90d1df75b504a7b82a3705423a17d22acc5c8de3444edef7c41e47995091bbf47fa343924752e6e3b51e210f1248a5960a79c3590933410efcd04a52001a73ea87c9a461d9fb476859a4d64a28c651056b2c93ce99e7f422751d21738ad857212e2baea08158efd8f47ce8b74460d6c928136aba46bc3674d853601026ad5fdfb2d485780be8da5b0d82c98714b6cde9d72a2a747fe6eb29389c40fa75c127812ad3afd5a15622fb55022f8dd0bf5fdfb79adbf08ae2de79131d476b7216818d133fecb35f3bb4c5eab12efed4e125daf8f0ac0a48e6cec99c4225ae4d913c5730186a081236616611d094d3b7fea045756a779eeade5c2c38cf99e5555de212e3506614343492f7f178072b698d77c124eb990fa12f80143ea745a46ed73e8b667d6a9e072e3d5c99a821918b3615c7932c85874236e8590a7814a3b9d8f6fb2c018c5ed045f9c37885b5beab2e5b8cbb05c4af5965bbd6e21ffd0ee71dde62559804070273904a0699d07634d6f373743836c68a5c0d27fb5c9e87ee55f6408aac5ea7aecf5dd543218acee9ca3183041a7993b42da88179c1a657f3a43bb3ff16241b519e8aeee937e80d4b00f892ea09bdeb76f908cb0d81e3984e42e8dcc0df59bde0448e5e5bba48437c1cca8aa4cd69bfc138a9ae8ff0b3c352ed1a489dd720555deb55f05ad0bc8a62a5cde8a82746aca9da02342d18aafaa63fde1c98e7ac68a3f832adaa9ebc594f70aeafaac5157d6d9584ce11b5b130cc2dd872dbfff93d0977f91bebfab4ed7f92c13c8ea7b2bb6425a6e7a501c819b76864eb305b27e004786d71a2e9b0b85a64b2a2326c55e60a586adefaeb15aa6d9357b542755b45cb27431f53206ce52c11f46ee6f22641001d83e28b801e4bad813e7e83c6bbe1a6ab456384f19723c140319846d7353b6e4ca01ffb7e5aa88ce57bfb4af38781b2d388b03864df8bef400bff1517482428067bd2f285bf43ac8593f54788b404370c757647705a15ba1e5d940cdae74c7623905d6b5d3cb356ac38badeff3e899a34deb7dce17f9cfc173613dcc9b62b1cdb0672d64a954dd90370d22c6d65e89c77c72b462014370f9bd57b49938dd17e6c4464aea2d0e63124ab89c6ffe10468ce7b23c828bc51a3ba7cc28d1457fd50bddec21b738340b0b5e6b9c8ab8416d01f89e36b76a667160d9a0039dfd885d35ad84264c9525aa531ae66dcef1822f703e3e909141e29f26325a0830c7b4972be562788ac5e50af9dfb99749cb544e0b21c3ae1cb1cfb44254cb8b162973bc17279c94215d5f4479d86f94f8910b633d6797d8a16adeeff1b126d6707c99d073ce089ddf3c475fc2a328917169d7c614d649c541a659155f523e44858fac6a804a31bd1252950804e844a430f64e0a203890bab444fd84425209496c5fa43d5f5452064482029c6c6c018f069d30577d018ba006fb44e4a0635422efbca3586892c0e01d0b35bed3abf1d02d9e32d02c907e9e4f9dbed8e5bf60a28c955dbd51dafad029bbfb829da53a655c8c3673ded35b7901c98c56a904b8bc557acbc264f38771909f90b2a20f79d8f24e37e41fa898068686235cae85ba9fc32e243e234fc3995bfa3d27ff8520bb491c0f356193557716b353a0f65e5b46acbf4e4a14b7f341b9dcd6122f9aba0e6aea55d3db4def82787882021ed08665181b35084f519f86b4e720e159d04c9c9915281e135c3f2ac4a1a790a67f591b01779214bf9b7e6f9f2c0b6ed88c1c907eda0e9cf552c6448692ed9829c2dafa593b32e2f7f20f94c41ca6c8098ab43537adcb9637d4d2e3afbd12931e875882aabe67a1c034211b575c5725fbbe98e4ae1aaec5aa44f1a9bcdbae868366c43968525459198fe89d368482d8d6154c0314849580abc34bfed219f171d08173991342b2b9dd3ce1e298e3dd5c87f634ea72ac6ac5253062cc4418b5c03af73adf7319068415775135d19ca4dfaafcc4a1c5c81382f689458b144d2d5c857a4fe9a012ce69b6398cbb8979b4c177f6f4478bb92e09f033f032c1b817d99e0968ef9f95b8a3b600ff4850c4830c8f226cdb4ac9b8192a138a12d32453786e4be7eb4abe343063e6a6a64454aea7632ec613dc872b3858fa4b2d29b1de3b111d8c1f1121540970999c73821623ba927afb9abd090c65daf437e1760de8977cda703e043f7016a128c2b17c2fb5ae246408ab280827dbf5525565507bc02ca56089384d007cb9dc4e649c9a4f4870137dea2611fe7dc54f09e2d0e2e83b67ec873c64dfd44f7bd25bceb52d08662389cedba8f174db93c24b7a6721170eb804d6953da084a6454c61780a8c4cc9a0ec816d26c19f0cfd5edf20086e037fb3905d490114c107aa59a08a3e09642f8f8197b7a5f121a6c61a3f3d1a3aa7e1fb7dc2974485f021eb0064da66515b41b77e0b4503d6c6351629f55f7be94859f4620c6aef4d34e2730255574883f33b4de348e09fd3089be315e1e775506b75455c5acd7517256aceb4a9650d1a6990f713b44512526d3cfdb064df0b0ce8788ddb2a433a265c67a6bf5a5fac5a5ee633008cc4271d044ca40e994420cb1412dff0b2c65b479dd20679a6ea5268e8a0d33b48530ad1b822111b4f312a4322ae2e4b7b00c2b8c4f1094fa7dc55798f01141d6eed89f88089cdb288315db50f1477f64b48c6fa1819c38e03e52e5eb563ad94a729dcdb8916a4794fb78dfa9c28d182a0ea12e5a560bfd34be1c3c413f13c52106e604b814f23f18ad3826a0109fb14c1adce0345831d39700d6f4f9ef22e8ceb46855c3609b0cc5c34a5fc546a97b74c5f3bd84ebede1e798de74ad54d963625cc00fb45e7652481dceacbd7280c30786067bd7b7228a0cd0c3427087aad96ab11684e8b59865373102a628605471a476023c9e79ca1096d1ef01f9fbc1519c55e19286e4909ec13dc20318f434c047a6ed0887a1a973835ff0ac7b757ccadbf24febc63a53cf5add643da960a5ad78b12623637959940ca7185a5b314e5ca1a9e7d777cd5002f682e05d3fc21e1624774086d5451b9420ad8b3d042e38b90079d78565be5185a68c4b113da0abdf7a758c35cc636ee7dddd53e078d77b8cc54701536b979491600bab02832332def1bcd73b34a441b525ae77aae318aa2786532684dc5ee13f7fc52a0cade15b086cb861edbf21f0907b2e73f50fdd3509799bb117522d3a5cc643cff9d5b07ae50f188441bee420d8a2efc3bb013f582b22439a5d0464919944fd8816edd694d827541a5c8eaa3aa7bbadc27391b82d8f442b5487f7100a186818d67031ea3103db375899b1eba23b3c0f6d41fee2eb18cc795a8c2723a26145beb918792112cdc5f5dcb93e11f748f18cc525a3459f474c7744bd5ea5b1a251ca33f199bbc87ccef9c0a3a43e2b82dedd3102ab17f0dec9d281fe0969c5f175eb90dbeaf92e061b667cf5c3819e1e3d5a91eef7b6a4d5466fdf2091eed321ceb78bea8e395573066d8df8b650215771b9d3f2e4fcc354a4fda1bf4d7fe5901903be8bc7696fe436a72e2e8cdc1205e754af016edf187a8f2cd2a2da3b335ddb47bd83949000d9743dc04ad0621d42471455f2322331e744ce7246dcca279a5f9f30bfc72d4e7de8a02d24930240280457b0cf212f465b7ae951c7d7ba1a54bbd28aa579375d3f3161a89c3df9f1740356e578b1b364aba992121962e36f2b96d4178dc1142d1d4cc5174e9b11030f6fef82541da218bdc49b6b115f967ed929fb019f2df5f51ea9c0a59fc6beb7ea99c30d08093d31f49ea9856bdb17eb1a73fb953ce362203eb7d3cde1b8d1a55f81233dff4a54ca3f60bb856793a855571004363099abf71a092bf3ea3fbf7f5b7241379a3b1347c603b0489995f60fd279c973f6b9f9d8cace18f2b7991591bef90fdf5283c21e65ad024fabac0de9a61af0fb69f268e8d69b5b5e0ae4001907a8715eb5c7f5c2d8d95830bc02ac5bbf059791c0fd28d1d2e2f6ceae94de1767058dc86d82e7bad218af6ba59fb1dd951974c274a43de9236d7f5c60a4394104c85b024e99140ec6a5baaede28bfaa256336b4274ab905c6e8bee5e92c55e8dc917c966015c6315f07c1d8c822d5dd036180a5dd2db3f6508ed8ee32a6a0efb0a4d1b8c59f26981f70b811ed26818c7004fe0ccf44e758c8c28ef019448be469422514de3fe4603f0cbb8dcd405029c7a63ae74a886e4ed392f374f742d4cdaab4d326160d47d559b8ec21f725d1cb9827bbec15599ab7191ffd09b514b246b7b50404b68ae576ccf2fb6198ff0ea591a7f15355e43a11af4c61625ea302a805b39d7fd8fcd97ca915b53c989655b2ca666bce5d2fc384096bc3a55d4e0d9d79607e829a708e888f7960bd6ee06c2e4e11948c2d35afc04b88cd50ce4c06ebbd07bf54999018bf9b2fa092d1e9436b4c1f0947123b27e5137858ab55b1a9f8913f3667d1c588d362065cdeb656cb0dfc1b1fea4b7e2eefa2e38ef09126c9a39926a40db466bedf2df53dd0201e00c914bb4cefe797353039141de0269ab7719d9d477b184ff63bb7634274af6fc5169f78fc65d1aba9b8216107bbb67bdaa3bcda654722add7f2b32582e798f8d074c838ae7b8c34017e30c87f5764d82bd020bd72d544a4fb2170061c19ce40d10fe8d11d5892a4db3331f055a757e6b3a42ea973efdf1e1ea6436755027ee967a0499b762cba390a1923da2978706703f28e3fb01456f3d5352c18644f16ec55e38a3907a13f2f63ca41b75383a855119e5d22fd60bdf1809d7e30f4b62beb9cad5d2717d97d700fa14480cbb8d6053ed86b2b618c5b3f680a00518dfc4ad6236c9a4128c8187c71bfab5c0c7568b51b2aac54d62b9fd604dba152c7c2b27606c3f8091367de8d04b703d3c3a2800cbb8b3b1b0cc50561fe09059ab97233a827577a435192fe00e8f1917008723551ac688068fe5c75081e33c62a23786cfed5a5c10b1e9b763cf4b7f384e8a9f382882aaef4263e8d4ca32106eddab2f7c87f7f15ef114577e0f1e0e3d5aea118158415f930e6b0f3135a77016c65f3dc810d64d161a9bef1611b7308fa516d96747743f8bf9a60decb6dd4e9f7ad9df420c25084dad62257f963e49288ee0dd309c854f73cc34d4a53edf54d654c49047b23a59c6f13f65cedb3902a226b61bff68b3b6e64a6f8c5330da8e636e0b819c0e76fd426132dbb12436a4cd280afedaed90e772f3c5c46c76cdb6fe6e7b770a521270dd6eb1510e59713fedbaf874434bd78f297bdcc42cb4c2f4da9fa018003cbcdddfab79b432e77cb8972a130a8b4c689d23017dd1753f7dad7c48de8d85e3933f5c9d718897119891c38d4a3a93e7454abb7808c8e4fc4fa230ef4925d0339e92d1ada7b5cc5a482df4a054fd1603548234a7443bc9a2d7b664967c3b5b84b6d9825e27f0fe8f3626ac41985f54807237a95cb2a9af87b01a7462cf43556b902a33b335cd17153b08780528d56871402c5a3107b1f40f1d62548b38b53346b7434ac301d5218b2a5b1a02078a1dfd4d75a27499398d1584dae3a7627d2e53de5c3b6bbb8222ad23a904863d68982a4646b027e21f6a8d5574398bd6f30a6c913027faee3b880a4f5c038a0eb7d3af133f96273c64c408a4001870a93384474678001a88789795ef91a3f214823ddfe8b833f64f61344e9ef3c8923fa1d2ea35927e1030b0a70921842374fe8cfbe1379c0d23c06024954fd4b4b77c98c8ef93beb96c6b165aa1669d32a0897eb705a9031f9636fc9ed8e98610a24ea878b8349198945285005435485833e707b3a91bf6907506f119aa4da0503c5dc7baee6e855ba98c7cc33abae0e23dae57cb8654e1e90fcbaa1ca35697a25061d225ba2b626699b4802515fdf271f324a3af9dac3fceeab0339bfbbf00934912ab1c05f80cfbfae102cd39c978437c61b17d391788a0b0613b86b51d8f5079a32f8498224025e835077f57ff1f055af2846245427d4cb4fd97e6c526a9d7c417c0f6e051b2e1d3520a45b6865bf53b05dfacaddc4a39ab1ac06657ae2fa80ffd19003e2448de43cef866a44d59b0e211264338bca0bc480308fee2ff9950660a2813029a85836ebc758abecd548fc96c020eefd2ae1ef93435cd692b26b72e3ec803eefaa7b8a908bcfa74e939ba82f50a49a1c4e45ef0b6b610f37911f8d28d01f455cf17588d9db97c47780cefd4d32be7c9a4309d31fa2710d4d0bb3c847e8b43cbae682a2974c01ad8fc5563954ec75094bf3f4e55e28728bc425160f4e9621a3a41486dc4c641c08ba3bf170b715d486e355d5059277007297c7a8dda904aab666a411a92a8a88e7314819cda8089653c38ac789fa7e1dfa0bcae9a9aa3eacf03368ca0f59a47e94af0b09a564602734b3e768e48e6b05eaf7be03586a5fa03c73121085295397903994b3c0238fad942ffa17bca3a774abdddc278de187079b60e00a4c775409e3b61dbf5a59b02df8b77ed1fb2ed8041dbf88f9028e36c78b20037a5d2bf60b1d8f58638e3c743289260a5bfcae7946951cd640be6cb80612796f746cdd76977522c4126101d3f0bb0b1f94bcbc9728cab904397719205ed21ee1ac9409052ed54e3e46cf391009956f1884a89c7f0c2b4dce98a5419cb78067dd7a1822b46777dd07945d68d4d7d34698a1a3f7ca2c4998835212e4ad403fdae85a02c45e226b8318abcff5b791182b64c1c90b118d9c29d91dc147d5818b3681154d32d3b33ff8e56ac2192d4052527cf7e8566b489135c78286ae6ac87a295df5d6561a9eeb3038836b58f8a563643afa1680440925d5398883f10508741be320ac6c58d230f8eeaf63140d6c60ac900d24f5c6b5b60f6d813edb94c991f5676157320f3db6fd33e2a8ab87e92f7f7c7718fb507399a85adafe3942f8267216486910f6787ad1b455993c05fb075a0fb6f048d01a502d8b9da96b3752356a944339ceded31007ea022e0d4796471fe2661040587232758e46d6714f81fa53883bcf0ea5590a9ec9e7c4b94a6ec399aad3b81f60e8a2a07f924efc52701ac53eb8e25718c301e16a373b10cd997f7a2ecce6f270ec04dd4c27a2c5636b6b20889e4e6911108764a8e48c2739d83a4e8e66f95a9121899625efaba6cd348aba1fe5b60ec1d6bd2803cb5eac75efab6472a9bddfb6453bc98dadc6c246a1152484c31352cd7785baa371a8785cf59f64fb0e840acc2dd9de1a64d17903e5cc707fc986271dbdba632caf479829eeaa6708945d4003ced1017b4769ff7038f83f32d21b67a3ee778fa1a5d2ebd33776dd796da8466ee68d07f35ee4107b61f00bed982f94d8069451b9b042e78e5ac365fc4816c2c9923ed63c483c7d639aa16e7ad4fbcb8354f748bc3bb67a5bafe70cb48ac175ddd9f9c6d86d58e0911e9bb5f7e9d598012f3135733a0c8a9106358d429ad64603bbf6c57c5d0a3a391c591dda9168bd1feae8de23640a41f1d7d9cee48798e21d0fb4a8d7cd8d7652d991a246b6d57d338d7f965d7e4683f0ae48eccecf0a475245f9f33bad5ec4c688943553a0f94b496de195b5eea86adad1adc0afc65dda9f70e43d853a184df9eed774a322a97d8d1b82304263067b17a5d50928baa384fae2bc007929c22c05bb74e3c7435330aec68df4a40e9622462881a9fa160a6e9493aaf00fc665c2baf1eca494086ee05b59cc74e4cfd91dd0818fcea0f53dbb50b75965b402dbd1e82664a592af2089980799ac343ec0b70c36754b9f7c82d237008b46994e8be4c6a00b545bd1186e370dad3e398e61abf20f980dd4eedf53003f9d3307b30ea7105590a29d2fe55e02388339be7d0cb8a243fd572bb4197e5fc5f5b912a69f09b6a5294848bdd19a652da4fd5c638d9d7879c04bdd441cca7a25a80da135373b5febeaec2f36b2fa4a33d143679e4eac89f15e2cf2ef34550c590c40426a854a627bd9c5ffa2c85cd2e43423ae40c1764a6b07acfe6be5040cc3477250b9fa725f4382e57e0ee2babe440e36434ab19b0ec2f001cbcf821788c2a02e8b2a23b525a7a7f01cfced901640d87c3a48bac72169d92d2c178fbc4881c1d8e2a9f1f411bbb578950b6c5b34af6013cc16af496938044b983981a97ef8031371ae8f028eef7d649f4a0560e70153ff6ccbb72c807b1f39e473de24b6d27d7b6808376b7553223fdc63ed22eaaa595c26e9f3ecb30fc6c9e1586a14773bdeaf2da5c6003e783ec65dfc25bcf354afb64368e9c538d3708e5dc635e2b62ba7a9f62344331510b7652f60e04fa399fe5723935d44d6cb78be7ebe96b57e7dd384d61e2b3cbfcd739dd13ee46959a537091d60b373acef04a3889286789287b060ed76d189fee6708c5f8f1af9119ca241f6b934f0b9a78d487f72912d943ee74fdf7267f2670e6ecc8bee4c2ace3dc3d6f61e3de2e92d993450d10cd5e29ac7e91c0f47c4e6b142258e075356271a64b122348e072762ada3f11899d5b71c8c0680a6b61aa142de67025b57640e35c577d937953d1791e1d34ec1529e7ec01535b875f87417cf4194e36843f664d5c7f72aa31765900be30d760fdad5245d4696d35a6cc05ea853173e50fac202a4e2d903b3d876b34f6ba09ce6eda810b6e5e0d47a664072d09967c18f71f37917aeb9e47cd1c5d02f5da2c0618aeba171719a03ceac7f94aeac2f10363f09a5cb8a3240703d1b6afe0d3ea0d00a72bee9acff19cbac28be12012a37d55b79fba3705763b75d16aac8209bd164771411b3ec96c0e7bfaaf604074e71336e1b60d8bb53801ae258c2d9659d9780514b9319fb8eef09bae972ceed2bfe91f7ce41c861b3c186be4119306f94d9b0a183ab0e0a0c20dd5e217cdecac65113f8a7c320e177544e6eee8b3ff4eb76ca1196eec3b722bd0a16ebefe705ef777ff4e86ed751c6a7f80562f1419b9e7dd01f7f9c0f7b919407316d824964ee2c14663b3c3b0602565dae13059336dd9afcfd9042c4af74abf25b5ad9e4bd49795ef3e01371a5e84084494823b8f93191d14a1c2b9d059be8169866222ab532b233f7a0aaf1613517cada50f71057d6880e118bdca0d8f6265ba1f8582faf97b1d04d8d85e26b1b22f3a501ab930aca7129a05aa7e8f4ab2d68110d0f9684cdac3af5332c753213deaa98a7f079eefce267cb69f7c67634c74eada140b30b3ed83a0d2f8ccf65e73d3259942d1c3dd5de409608612869a198e21c51598cb5b07d674daceb0df4a58640716e2e139fcfedf43da2de3a64a4ce6e7a8f672f3590a1a2cda3c2c4edcb1a885b09343439a15c62b11b26b44f4ad05935722fb4c98cdae873c9be541c207437ffbe2b7a80227b064572552f878e39f838734fde73ba21d8466f8d62c83321d354a10c3a6dbc81ae53c57bf03dee816b7de38c4144b98983e71d9d05fec4c08c53622a70f80c9b268928b659aa6cfd95fe6fa451d021789a9630a71f7c2b1d20e73624d3e7892591d9cae0654052e5caa40aa689c66b111f6e70ef4d98382a0e3123192eedaeb23de64a2f22e68081e533cb840c42db55de4a3e5646c9e0d7b3fb842f7a640dca6fee3abcbedb40f6ab311494961ee2e823189bf6d8aa91f46c333d9f384d3cd43ffbabfadefdf7a7e9a6f7baebf73a483ce68c775af34e846ae009318e83646edb920a31ecd4ff3bb0eaa9badfe6a1d26c66b86ee69e23c67abe4eef8638a6abac625605dda1229853cfc2c31fcc562179a6078db6a82a38e22c3cab143ecf1866d9fc41d5ce8cfbf1cef0eb67d989dc40836246e5e88331b479959983f4ec91d24061c0c88cb7f03c78a1b76e4f9303a2690f0308542bef979959e06f78a4152eca5f9899424b086320ad320025b893310c4c36aa0be34532d9f025a88b5e11e2b4b986b90c4e0511aa0e2c65d06de49d85bd1d7823cc819504f80b6327913e2b8b5aaa491829ec66d9b75ba5ddf5a6bb6ababc950d1046004fb04ffca53a79d8673d695468bb6cee9eed83d101e0cabce36add74ba5730ed7f04876e862fd07a2998c14a0bfe2c9d6d25bc8bb8873707e97197eaf78390a080744790bd61d5dece8360f156da94a3a61d225f68c92e2b0da4a59817bf7f81ddcd16f3a20d649f82cbd38e18637da7306f8968aca7de2c16072626fed7cfff047607001a7bdafcdb52cda29897aea74c59402564d11e9ea0edae44872ee60a8011867cc53fa38401594a3d38a32e060d84c0329b53e1d9a8190a9358f4863544efc945a1d737cd77f5c6e598605956b7c9211e4d8f880b2d8053511601d6289cef348e1f9727598a21a57f99e830d030a2db74ca7e0d1cfc621fc81a05087dedc0b7a3f4a302b63e5799e50d3d9de36534afd1d5e209d6756923916010f94fa44f079fd8f6e6f27645bb0abecf1eec911e1204f529db990528d435293524904cc5362a533d278ca267085ddd44ffcc2a0a023425dd41764b719cbe5b5c8989b40def1417caf775738ae660db10b4982064272a70aadeb92dc7720ca6dddf8625ab312058ced1922592bacd722d17566d4d8ef5212210a86c8c5910aaa06b43de1ea53aa011450c12e84bc58a1acf17b2a014a92eee2b8505288b880fcf8ddbfe7da413467a0a29635d3a6e4a9141bffafa63cdfb21d124df06b2aa82585ee1f178722d4de0931e88aeb9b0823ef4a4ad0bc04c97d02358404c8075ccb9bb71364352ac4a9223a55e009f548037d98f8c6f0d53715938b3fc607983a7990531605ff47322a511a7b7507eecf4889acbe8f2957dd5a2a53137b4ef3e920a2769834e23931f45f70321dc7a425b6b131a229a8c4d7c1087699c1814d751c7c806eb22fa120e07a3f7a59fdbf51c00494d1d9cc947b14ef3103aacfd51a0268469c76a628182e6b170dd8ce36f52b329f89543d85cf0d0c5b87ea24384f2242bcc35317d53e0a91c15d1c921657c05e1098611d2fda349760ac815b7c61d2da030e67aacd20358f54c255add543f22e5afee28bbeb969a661934590ecc1c3d8cd66633ed6207c7d8605a2c9d72bbf8fd6cb6652f37b51120e634c0a438938dfd68ddc941519e8545fea41fbcb2e65e77158f7bbce6c561a951b4ee6bbab3cdbb36ff2ff8e1048422aad5099f9617438ede1979361032887ff2ce2c2f28aa01f692116994dc05450f572ab435778731277000f48ae60288cb01609ca1ca60dbb48e35c1a85824934c9a7b8eede5408e3714b88fead02c511c162502bc4d0c8f33da4166c16acc1884336e123675460d171c33f20955c894bf05737a68e3081b5882f7441a2625daeef79ec96b244423a4f992628be30db48815a9dae2eecdab262f1f9c54df3603a419ed5fd8dd7e0353181c3035dfda84a06270e3db5c4e2a3ee695b2bf5391bb2008094dc4bf417547e0785c17ea56c1e7abbf5cafb40f9c428f8cb35370ab5092b38bd2b33cc88dbe85f7cfaff06771e4fe664cd89bda8f8a9e4695f4fbaa5b0bf148c6373dc77cebc0d8009c05031722303e7ff6e4f49660202ec20d6d6924d8b716364cc8ff60c837ad1086f484fcf64cf71f747bd0a72ab48b7c8ff61fc403adde3253fe6b6f8a726b20c9ab2b8efe03e6a22dcfe30f004b2cf5219f79fb881931be2dd5a5236b3bb3413e02548b28ad729b6e263c444c41d42d4162cf4e3c83e1c6985e84939a8c4c9f5995613fb353ea5f49acb808fb872f2f5d2382f451541b6b0eaa46057fae52c9d7ad15b2d2034ca8f82444098cd644d68c4d5003622b41cff80eaba989a54f1d9ee1be8515cd41caa27c85bd30875ef752e2e27d934f9f74d2d4eb94637968ef5a420a63676c83c942fe03f1dbd1b2aa608faa1a1093d9fde94e3e835ae1c40e29251a85e4ed40c2f5b7a2fcf62768650af7634c8153203024dd6d63c3d085c0a8ab720b66f022d1b9674dfc98e0ea5fdd854d48270b796c935102cc32ed0a389e79cb3fe3261537e741cfcb5209c5d4725161d2f4cb199737debea6b7497d5883567c00c967fa432b1fd511822a907ab5019e2d0289ce79d25561c95064a3e73bd54470bae829825cf1278dee01931314fb3ab93e573807ed7df3f655050ad51597e4d494aa55c08775be61c6c62ac7336454bad1bc8b9dcef3f6b8a88107f8c87dd0ac3c0d5372513f5887335905743d39cd66f20abde60bc0b96623dfec7c1d6c1962fa06495d9723946b1318609cbb7c87c1e952fe8f61beb352ab1f0b4b6cb14e970f48a730b69ff7be4afd9000572c6a83bf45168cb9948bebece7098e2b02e89b0e00141ca8e9e7517c3804c3f1d2233509100b5fb01138f8e50cde1414cb5d33fdaff786acd6f64854ff0d635bacff49aee8a62db2afdd05736e4ccd26ead9b99086bb046250c0de2a53b12a29e81b8f8241c33f737b506823c7259e9eb37ce7c50783ec24d858cf5503b8b044c962af1be529c79f2e1de78d64a728d02ec4e9360d7d69d5bbdd9a13e2a865e9cb6f9bf2c09a5bf98e7345747df37c335dea394143d2fec9091d3d3d237fdabbb4d21429ac60a0b83fa9db382dda7f0d444ecd5ac1009aec380b5a7b6264b9c4ad50b67575dac4a28e96686004f2371267be1cac849ac5b900cc51370d5d6aa11aab3d73dc102d91f329a5e94a60cca5740a9e02531d35e7231558b28fc6455d11b6b3e88dd9b8a59160dd646746125cdae8f19ccb41a47d33083e93073e67ab23d4a04f5c2f76d4b02340183720f52439083195a2964f8b661b7fd05146c1c6f783896e3b5cbf64a24aca407f3791dcf76103bf47343720029dbe239d897bb2d66d766a8ab1710ce4a1ca111d11e17ab3f29168f315d41338845bbe3ed30f5c75a0406c1d3755155a9860511bb05e8b5410dbd6836f0429687d10ae29b2171d0c2b8be71c06322ddee6cad1b6b6b3593a77a724184499a59a00198b83e1ea944ae574f00753d64191bb00d69db13db487946f03d97a9aee4b3ec6e105a0c771062349ee4d32acb30cadac76d5c68b00b26e956528f2d474a188a03489aa88744fd630f0c889b512bc993046919a97480e96ba2dd6525bfd567f95c5588e14d5c2c540393d7e11c9438e07e00141700610229138e94f92980321ba75df15de13b17d10e4d5947122e217d488590ba802142d1069ba847f060bfd6de1bcf10cce23a22515b86c6ca7f037aae88cecb0e252c5d76c113b07eb5a463ca95028861a0890bc3bd228cb77efd30b68fc8b134492b42b5a16b0b292a3ba2247d457844ba775fcefb05bdd7771fb79f40bb60f48de308a953c093a5b3986c06e0f0ca3785e0060295a59529e69007c7a688d815deb50230adfc90c3ed1df4a9f65e27c05a7272ba58f5a9165cfdf07119fcc228e0f5a32eda758fc690ef6a65b7b887b584bfbf54e7fb559202c7a6fc628c82cba006ea866cf288c7d4990c4ad44b8242bbf80f3d13658f4f1f8397349b61ae4dd616dc24d0fb3fd0df0e267c2409806439d14807a0ac4ebfb84803c3cd0d92b0ba2495fa4f8489ce8057625be66fd40cc99539c98f7849193aab82767fd2b4badcdf70d57dd86b0f670d89c24a97da7eaf510b6a73a95592b287824eb7cf21563f0f8630f767e80c3b04c202ee36580fee19fa80abc78dd28017a134093722db8b5aa122173ed4d1eb0765605bda2e82347487c78e065a20c52862cab7d059dff69c5f01ee140330163df3fbc36f989f9dfb39868fa608cd0dcf17b9e3bbe44b4bb31b790bfdee40d8f62d1c6a9e8674ab6d0bf11c151d4dc4808ced08fc03094f0edf5f73123f877967921b0529148db8f49180049836fed2ff509ab28b58de46010ecd1603680f0a687626ff97a5ffc01f63081d6063f69385b5c8b40c0c68c1efd500410a4292106afc743ae5b054936ccab21c7946f899678d57c8ea324d84228c2ca0b24b7fbf3354bc8d5697571ce0c47744492a2e906aaefa9bd2ac2084e205dd58e25d5427ae63f65c905c2b7c3c6abe82e8380334204dbdb66c32722f645b8d0e054d27abcd5773280234feeb8c7fd907c568dedd247c2bed67d547dd6fc58faa6486f50f4da612bbd9cd68c4d0b24ce2df10a2eecd4183753131eb191859768ecef0c32a5f9e26c8e85b068c9a922bfa9e216304d37c2c49984d16450e0d2584440f91edaa6ae7e6e7abb661f436758f067a9f0f43ca9b82c4edb5583aef9da02a6d0f520c3655db6916cfd8a3d8de73cb6618ae56ffdc2e16de5e4b3c86881d48934b73db6b47f144e2d42659034479b1b398fa2ad90ecf6bdf22c923de9e304070793b4d32ba7f4f05535aeb3a0af4fe3c90442323b331a5416dcbea2edc590ea359fbaa8982e73e28485e86401edc51b72560afee7c4d79f87d7e6e1703d360707fc01f17f8a23bc24e98d19d9f37fe1d19ddc2754123e878ba26547402e9367359d40ef916bc289f36a5b63bdf336a63f4e23f211a3cd0afa6763ce09237066874ccc77ddcdde214e406275aec9eecc7c85ecd666d54ff08a508af33c958cb1086f0b63ad85815eab4617d34a71f486d835602504ab82ba172b559755e6a200fe09e25d55b12d49ab4db4f4f5d134688b22e755a7f75d6316f4edcd88f87dcb8a6bcc9eaddd31d9508289271d64c956743aff3f6666f80b7774d44f5d6b35f10c31f47f6a420450c0ba0f235c80f6b5078dc5ea1abce0cb0144cf60f5634e6c24cdea5f4140b581448b6c70950995fbb5d96499dec3c12dd1260f26c81e98ce7a6dcc40398b9d1efc62c46908768c6ce60b04be82c2510f5411ac9898bcc04f469e842ab79b2abfc0aa369e9d993f37457a2adaedfcd84f52a127b854b038b174e6fabd668ec33cd8540aff6c73381732d54b5bcb6ceb2ef10336390e0747247032da9db6b275f196bf65f80d0d1eb6d06713bbab48bcef4cf78011abaca99c0df1f237ddf34a5e09a75fe1563e09425e2e4291d50bfcf6b85dd25fdd9d0174f9e654440e991296c8c395ac6bdf03d54827a437cdf8e61b7995adb6f1d5cacfe2faeaa368be7d37036b4d40fd91900ad7ea668fc0e614417bb16e62111cf5be8ccfd95a17d877a6c900fae797fa3b6162824adedf38dd59cfa949e797cf8b8bc5369bd3ab017d2cbb95fd6637117c08582a0cbc17ffefb5307a512ac322d9888ba36bf4d8928241cf69e0f866146accac95aa59a7e06115f5db1c732e7198d5ee86186709000915a5c78030966bd95c6e53e079ebff20f65f7bdadae2348880900c1229433ec0c5ce1f22c7302a32cbe860549dbb89890f917ac4141fdcb2900eb0f840f1bc1d8e083e5d9dbd3b38a12338345581e53068a07be6a1bc605617b87986124952ced9c7d199b4eaccb366dd4338b70248db90936bada8a417ae357cf54bd41e70fdce12f05eb40b47bb8bb066a3e7b3bafe7aa82e1c32f14ec3876d9f5e0b54e66fc603929bafeaa0799547265e800859f28a0671ab0c8588b015dfc2a4149d09dc8ed197e74a58fbd597ce28b43d56d8a126e508db508dc28b8da17a6880368dd884c0d1276cb9fdfaa549ba4096d30b58c41ad1338387b3479b791627ffcd9c542a65ba49232d7f94ef54d18c3fc60c29d0187e228387598f15337b64bef3f3133e8bc367a01df3e65efd14c8373c245195380ac2cb2b8760b8278470c4b1bfb75fcd9e523f5a69d9d962b4695b5f659afe6d24dbb26a0b67b857083f7f0f718a0110eb879fff0c79a80e5f72c13b64e8e206a4b36259d14fa31cf01a80617e5deb4ef092503ee1a01ad90e7115ef89ba7a92c55196d0509b6c6c15918d738fc1326da169974f6b0da7b77da8ca462181b27d75157b757490457220a7c7e93d31862e1230c578f8f11538a54148ac72338db3c11c6e494345f7bbd3ba0a486f151bb72402f641e321251c08b82a2bccfbd4d8d93204752583dddc8fb36dea1a80fda6693d38b3489ac43440f55b8337d5b3d8412ea35480cab494dd2cc3a8bfd913492750a35ea78a25bae3221590a8d5a8e054e53c9e926120c63daed779ca354b86dbdd5d09ed5fcabdfce30c9a53329e8517715ffb89d3a89a331d37ce70673158cc04e5b75f43be68d78ddf9e63b8b35435bc3812844648c36b4a836601cbad9bd754cc0f875158a627e48f6f316538fb309532b7fbc1a3051b2bc7c9bad1889397415ebdf556be1c587513ad8b04aa66e5dccfb36bc57e03853f52e73bf1b4aeedafadf7756fe07ce66fcc8a14cd8af03069a0e5e6135045f0f8830b3cd3e2e5f23b91378af1b2a22c5b4340fac8db4456a1b165d76975caf3d4c263abeff92b6ee9dd9cc379e8321258ce4322bee5b7bee0ec05357568db17dbf4df684a239ad0b678c40147e73faf5ddf2440e92142682ba82b54297ff48f1a8839dc62cd524a62e932e39d0eb2cc7db4254f210a70f80640df0aabcea7beedc496baa5f87d2a3be20124f456a1262990e93d1e850e0568ed0354f4128424504d600334278146ffc052846424e6ebb233bdb1c561709b182ec7a67e371eaefc4a09c650d7785c588455d3e5c6f2f0cf70799278a685419eda16fa0adc03883ecb9d71f53d3a353038b29fb48fd6c4c804b09e8be4de9f0749e18fc80065694ec1662cf44de97f2fe4fa9fd0cc32b04279c3b1a3bbb18924c98b524a55a7c5f4053bb65713d07dbcf74182e8fa8a7cb1291209a58deb47997b5fb5f281f6028515af21904215cab456e90c004aceac3e2a1757b8a8963d9a271a80e1856533c3844253679cc8f495dda6addbc98d8d8f0b1903b797e6f8d6736902e535e5d7084de8b98a98a265d60f091a0ff8f0415c654877ded29c9eeb95b9aa26abb6c7a21076ac8db903f358e94ce38b0adbe7f61714fa8c028340bcf92b80126181f30a082d88ac0a07c42fa4435ee0dee6fc6917dc0a0ad0db573c024d1ab868d72e876b0e428624964c952ef79fef55f85355821db4fe1e1136dacd26825abeef7971ec42509da3370ea5ff31971d5590e03de35096ccad299602a55047c8ab3485d41563196c5b4d3880ab605c73eb5eb8165ff5ee5473820e93e46d42cc71f700982a32ecf64ff1a565e58ae1a4649dfab17096718e6ff5df6dc059e9c65c4538072336a6ee1ab29d9138d017da1971335e8a71a433f6d13781bea4c80279d2656c0fae2b011198cfa13b0989e228fe2a8c29061d85155d69b51814235b03427276ee228c79a9ff8a3005e3260a942392a9b62ac89181456c40e9665a906e6ea42ea99d4a632c67f6c7958b10662b01541fa53b2e865f3641515d2a685d408db977496d8a610f8977abfbdd27d941c2add24edb67d0d86a7304cdb9637b75f1a054d228c600ff5466a4df03cf5a2b53c367050e8e7179be9e76e8946b9c3917d146c43a78af0756011af07245aa3d74c84752f9b0d08024c532b077221ee8e695d66e572a66042e65d47a505dca98578c8adb02f7d1c2a94c83f459d58f5ed936a9274805dc4ec5d29788edb8b6818d3108f0b73f733a623b08c15e176e8a40cc179e8371f04a55418fc081e5a9e9d2dec90b69cfb0ede5be668284e337c7a6cbc3209698fa366c5c413e3bc0b199c31409c729abef337ee7073b9b1e529d3ae0418054371e9da6979973be3c56b36e12c85374fe238865cfcb55e5f30ba80ef00d2bfc2c79022c8fcc31cb137c6a878f9969921d604ae98899e9d519c75d95171073f12adf00a2ae17d0940407ba4cc0cee2bc82ef564a87d5b0701df35e417cb9a113ced67f0af049aa5953c3aaa3bd40a5a8d56c8280c0b12d4f0a3ffaafae3503637c889eda78e7faebe83bb2b7c353ac04ad8f36c3d4e587c5ec1e0d63c2484b0aab8b297ccddaa1987d96be1209facf100166d11a7d17203d6950cf1cb3bef2ef2d4039120464f7f828f3f7bb99bf82d170ffc320f5f5bda400fc6a7f300e446033bc5f7ebbd4217d853f379fe1b197a3e1b3e02166f4d54394fd3a014b1ef6f3eeaed1cf8a538051b192df38f161a1afda14768b792c609e0b612c2826995414413f65cd5c6f827e922e8b8d5cdf5016dc62db320a212aac3d02a4c3cbd86b6a4381d27be74b4b718502e89886aa485784f20d6eb07a878886e2ced02a9f34d42016b8114d55eb0ce2efe413d6f0336ad4294fa6683acfcdb79520688fd7a2db9fcb9f3e430a24b3ef359467b858b3ac4aae79ec836c62184c2d666d2b1dc3ccc21db6dfcf566689f87e5872e7938524312c563545c8c7c187124f3dd1d4bbcccb0cb654d32efe785d4afa96f5a6ff9879ad1f93bea579049e46edab020ab9e30a9e8e9be68f76e2bc02c51b24328eb2a6d0a3bcce9e4625b0a9bab52b203b50fceeb063d898cf3184f400447443eda016f1874631edd5311134d726608a4523e091b7b8b7eedb22d9826274cf4700aef3f177196bfc1f92e1042be2439c77b4bbc1dee8e5ca28a69448a9014a1a9fd2d5d14b3103bd7cee397fa9d4e34f097f3491a49d50a5fe66e00e92942740839994ff9a88db1c00b8e28102f61d2e0a37e228424a33e8cb64024d34a8de220cfd2c51ba0091c2a227dc84714a60f0f9e4a0fc04cc852542216c5a756e33c914e18e3a297556ac19ad2d796e633de97daa53ed0d66ebb8153f8cd2fd01b197f9c8f3bf89805c694edf192fd134eb942f37820971df1bf096eddbe609563e7637891ef1ffd8c1f557afbaadc7de1cf4545647ce41e51444591b072431c4ec6cf094c2716c365ed2498a81a44f4f10314b15927dfea8c012dc089549f11800b76680a4dd946003ade35ac309a4c420a7517cb1399d5f8cfd91ce8e8d92dcd680b66e6dde5902a4f86df016007d489413689c72c2d4088f5535aadd953295be45b2edf2282593b14f9e77cdd4af04c2d7473b1d3b02ddf76169695ba6320271a1e0d33a00f50c024622c7f6010ece88e03e0e238af667af9d72582dd973e84bc5477593c5611cde8d79285b41c7096313bdb1431520acdae2c1368a2fa5fc7e2012f1d23d355c2bbd319471b77a22844af1566f668bd54b52b4500bd8887a037a843b747bd46dc97689bb32b00fdc518add482328b30461954db7edd1b158a3a2d97caa11059a1bf397ff81b971bd24e8ad96067edffbfa3093d7e9f34639eb5551f3d6e9bc1743d8d6f98a66c9311a14a86f761171f931200ce45d62c34341ff4f677a24b563309b3441b799c2d66b755ed84c571ac1a1747a8e2d460ae239814c142b6ef7d0f1328e11b620b956b5c5996372b9305d001ff4bbf6197fbe40f62d19396234191ad09cd27dcfa0a1410ece3a7355b4e59a6ec71da467360f0a80b9a47fb212dda2a3b2823822511289a69f3a48f6b913207d55a2735a07333f283287b4177c55d1a309be5287b23b94b4f30597ae7a843797dfc04d9d32f3826138f772116631bcdc1f0f252214c0be07a269d9f92e09071221d12e2f4df8e846d6265f78dcf95a07a6a66b2e054cb1f401ad7abdf81bc410532741465850b06d00a282c81561daac2a7945894d2e9ddb80ec7e84dab9c9444ec9a48aeb0bee07eecfb87b305762aefe45e613a62b2e1b5cd2125b945bf346aedb4f1dcb01ff20a70b9d55d445464330a0365ca78ef64c280c25e0f2dfd1f90e9b3dfb8255db412b2793cf91a914d66ee574d8c5481275f2ffc0bd20907ed232e40cf572f650f3b9b20df3c1da05cc511eb1cafc01f3460520d59540608d83bee506c9d49c5dae91f91cd4bf6af4e7ba7ca1913e0e3d22f3fed6f9fba77096a4bfcd09b47f26f1ff64785634666c682e0a2113af42955a70d68095e9567d8d65a7af37ef9d705ad91428222b88276c01a82e425c044cceb2e4d950d3109d1d1f0497bfa5308782de55e93648329c06aa7c4d0216142a4371d0ca5e10e08afc4eda47d343c37b63dd056e5202e3d397c0d212e66a5f1d50406d8c0eb047a8727d7aa0a6f5c24f27e3be894a262862960318f1e3b8ba8a44433f13932945e0108d3ff43439f5743501b415c76d031acc351ca63299f8987ed37e74d7b5365d9057d7fc72a5f8baaaf0b107b854887b09cf2a1c25a684211017a66506fdcacdf4067505129a0c93cdf33143f0cc6615644217bf6c9e21138174877b948dfba3fbef1ab9e99d5a1fdd4493b98040f186aedfef02e1be02e6ee091b958ce8cb0165eaf629331622a5c9705475faced7e17483e220dfa71cf2ae6b04bcf9ba76ad7f2fd8a07d23a8dac6a99ba8bfc929b403f86c64991af57d8c68d8c820645189038691069634454286100704555d647db9828d0007fe4409c9c35e1cb0fa283519dbd3bed45025b60a8e83c487025fca91d8e3e3d828d9a182ff331ebf1bf5a7244fe1edbbe172ea0af4ffa4445a91d168896e42b9f108de91866970c31b6d4d496e00d6f321c933781981aab303bddd364c6ee14978f6dfb448cb4882843516417bbcb6abb0d800d812d2bbdea9d3863af8d9e6af982d0f4e95a1d53ea25534bf23080f6077ca0a2579a5a810dd8615d6ed065d0981a13d10e0215410049c4f949dfc343736edaad8a42d443d0b0ef5b0a342564da2959dcae5d02014c8de1b16f15c0c4db6129e6bc1c1ae74efc756d3d74aa2eadf1236ba95ac256450dcbd78111bc9e38c0aa0c989f6c0d11169ce76ad9f1603eda75de1a7994705748b07e2fe33f707576c80983a3ab82d9235e63dcee29eace753bd8d2e766cb8966be84d9350bc082cb4f71db2e50c1912acf8766193de087a5cb51ae7897a423429b350e314fb57d0fdf3b7419d72c257df3587c919b6dec98ef14278a6c0eecdaebf830875e591f4aa98d9b15c99aff07071b1a98938496a230159b9bcb091b064d44276ac168268246ec19bd67d56453181b12e453ce92666d5ec0b18008d0f925f8188354108fa508834cfec3f84fb836fd40b3b6ca1d7915beb26c88ea44fc9c7de840d8d65cb149ac6bc1655923cb48c449a36e94c256ab6baad13070991588e4e61aba968630df9cf5783a7a0991a8593f0fc7f9fa01769feebb28e8dcdcc77b10a795b0650b956a94597120825018daaab3023cf6091bf7a9cb98ac96078f008731512a3caacf61e5f764d8a57acf0ee61661527361ead048be13be85ad95733129ab05e4d521a1da81f08107e2bfe8bd92533c9d8a756ecdf5b6b9619430b0fb5e4684d0260048bba35ef5daf623938731956b3eacf37ddcd8e268e3bc5351aa6b85367d7da0c0cf37fef0cac309182b6571fdb08775d887e75489865b6380c599551204a0a21e51e7cec8299a31180f4158b573c5cb6d3a8a3bd61f668c36f7337443d231cc60778b249ed011f3c5ac38142cb3e46fc604d40d33084180a7e4b128d0b5c89e25c439179318c088359b3777d2c4443159057c6c5043471bdde99710b04c10999538670a290935cbabc7a2129441b426c046d33421a35e9168b8d4b1b1c6ced3adaecf038539611e5a92bb4657cb937e775325d2e0b5953b448e7b9cbe586af04d7c1aa4442e8813ee399c5fa3a9f58ba7fdc6865bd27df63692294d041a158343aa41f4686de6c3e92630fd906b4c3b16cceb174e2ea7e58ebd7447d3a3d11dc5cff66820d6a0c7364092aa0be5bf0f8d6923bab94fe830956799e47c1c430c9bb6b6dfc9a649ec90baa78b2b359b288af9334259a3bd8c3437ed7c884fb94a0e5eb7ac86471d57f865326cb15618681198e001ac8822833ea83b2095fea41c8af99be51ad2c97308e88facd973845286a98a592e86f5b93f2a6bbcb72ccbe03c7c21009ed392c86cb1f97b056bad56aede49fe6489ad9dff61d489f59571c88fb64a6d7c2019ae3e980265cb43a44e8da2111a8258140c31e93c5d14503913a705a1c1e81edfa348e06d9c28b4dda02d98185868145cc43d72187082c388ace7e19ee9e2b30f4f18f25c9545830235b46ed5838ed0b2b8193b8058cc0bf5abe9b3733a735e2e61ad6a11e435ea32f77530455aa65c06be6d1c74b9e9d385d4be1e41856f62df0cff507ef5f99717c8c98ee151f72a828472a17e73b66c303054cab0965aca620beb0a43996cd91427081d585376c4f04f58e5f74387aca95daaeb2130dbc08acb4d9b81a4f98eeeed61c050bbc0e59d0a18f9927bc5170e403da59663b295e98a1797e5434df7d8cc39f785b7492fb31a65b4ef8b791f26acc252111837b448fef81b9bf0e6d24b9fd718abc11dc1bbb103a5643d45d504b467364907c7186df40c585352abf72519e8c73abc055a8e44e4d0681cd89dcad907254e84ce83b718a1b643a6b564004b9eb36b7340e834a08ad0caecb1f2576c400be6034647b248d2bd3f8a7c6b037034edf8fbeff6c3bd04f3e952fa3d0bd93673bc332b1abaa31b2ac2792fb742fbfc6d477c02204423ea1dd55eb3a0e9acedbed6f182dc80747f488a4d9cccd280f6355226e6e0473f3805489e77c3ac8b832401011f97b6283996d347e97f98d8d639e1142626365a3f186eca5da5a39712ac1ff366e63f74e8f5c4e13f40915f3e1fee2add369bb880ea668564c2d1748035a13c14f42f65c88732a26859a8fcae53f0d84c593fc8d9754df6639d539cb612b90e9ebcb9b0909caf258aa70fccddcefdfb7e12bbe4d9662936922e31bb927817be35362efaafff252f493f97580725810213b3af048ed0b1089729f1705dc2c1572a9060e1314454b57bad6f3505cbbea99fd98490fb88dbaa56ff2221f6faee10835f6e4584e8152668d1715677ec9c70bac658c2dbae6a9a8f01e6ba59b85df1cc63c0c893e04faf53af156a76bf77c707478cc177b71e5f90f9cb422f1428467b6a7c812ac44e81a38c9cdf192c85cb74c727bb016f9e40385c5114e99bc48d5cc0fa09cd763a9d80b5647ba643b0d85d1e288954c35328f1647bae4a7915db438f2f8e3f6d2aa88718ffcaa62af6cad51d435dff35f2d98c61ed1ee68399e8afdbb60aec14c7baa63ef8c38b03110d8fb44b1c5e9b293cd10f5bb0c7c3973f7ca907fdbd72ff83ff35011d0d1029a63110d1702f9d7e61827adddc5b1068d4a3879c252e494d88c7c0e1a77a7df12934e405e00f4e2145990404c44e284bed917c65a083982e55463d73ee058051ed2b5260c501812236ed8137268cfddf25e5ec40b74cacb8c2684c0df7a19fdd7df1346813994b61fb52800d5cdc878571b448da9320894a8269813e96b19fd3ae5a76a4990fa817caff7b9521724243f889eaac25cc377d6df64c9e501d8d44b30f71aba685f85b1ae9857a23d133cb47196b807d4ebb8766936c28bd58d5acad1ceb31cdb59a6826f25cf866d430c3b34af96d174df7a6dfc252cbb0dcf5fc15946a3b3253380be242e3e40079cd26d11c9e730ed48043047f81bca6523027f88a364ecd66b2c083a3dc3139907225dc66263da3513e49bef5ae844c9e4ba4abd3a2ef82f0c31bbbdff516f9d879bba8a8ce59133464d5043e1b1101a552c9f29661e6cc3031b478bd4e1bf7fac6b79e6c09125ce59988a7d1c1b7c278ac13b79f275fe53c8675a7509e6bd4ab87674206cedde3e9af682da4c76bc8b33292013c4ec41e5cf0e71b5becfb528190b58832fdcdee718987cb6ab679bff3d31809e48628a7269abf99e4ec376cdba18aadb642c990f12d3a017e0d26c617db836d2d0296a36cd4fc189050713c7bd7963750bf1059b73b1614aafae80bd0c6e2552abbfb352a34f2639499d226177b22adfa30429c14dd992354800c4b451a374f55490bd6d02d03663f3f0da91fb52c671634ceafb7b7f3e7499c3699c130f75afedc1ddee42e5940f73ce5463f96b857f0073f31476dcc42929adeeacf672f9325ba53c189fe2ebc162efc1a8478fdcd805796455cedf8ed31e87b932b751b006a33246cb2beb94ebe44a9257a5983fd1a9d9f317d3dd10bda11438323283335bc746ed4bb74f4e3bb98d559d000d490ec843cfbdd7587557d462f09ed0620f52c5c6fff7c54e4d593c3341f68405791c1e9b01ca233f029263ce65ea386b180f1e7a9cc5eee69154479bd017484f03269e4cd7f60226e2801227012f713d6abea192cfa6089383c856b4ccbc3c3e3845a507a5051dd9be7c885138d7a1dee1dd6afda4d84aec3b7fbc31c6232af11234b3a0c95c4e4a4e5cad9b56b8b325d69c257a037f38d293feb77a59c1102504f5040612f6a96cd6bc5ea000a626249aa85d0b0fd854c68217e9db8b6e0b1fe833d13f5fbbd6b0788e64961dca7c6a9a362aaa58d508313cc5fb368dbb57f48d9921633a92256bf49a13e4c00369f7dc4566a66f46732945a49baa75198abc50e170373ce3a381cb3bad4b040285654572e6c8213e2d2b8b4407160d4c17695a5d5d9d653b16a12e39ba490ef49ca9c075f2e01c701f725a5198095803ef4601ec5432026c8ed9d33dc38ba021458c24323bfc2345e006b5b029b0434f28e04280ed1443058f53228aaa314cfd1918c2abe80c4aae3f5582bef8576e4ca7a78912fdf9be2ac9d690852cd504e07e59c8faf35bca2a785417339674292cb5a19659a8a98ce9641c48c35dc831bf61d24b09c78c2b67abfade50ef405c288b036d6e4cac9c1271411af959235f602dc3e4e7e882406110f4e47555b44d04e788956e33439d81f33e5c318e1c156e148c10e003d6008641c26d0593cee1282e5acf7dcc4eccee4ed4b36b48ab3ed1b274046a83a5f102f808aa17062dec3324a1e1e6d415ee1e2257f3f93400cb5a6efc1bcfc517024fef04e0a95b3da94d40aab810f24943854a10dfb89b9cf9d0d10fb68a61085800ba14581100af53c3c326fb103c3c9ff012d91db2d44c31579f29f522175a470c89e8adc62bc1a57113839711395c94188003edf748e477550387d636b3f04debf1d65c1974b39c75202235b2ce9e5c5743148224632df292b88bae45c77ccde542c9ca16864ebfae169e04da5a365b1714c72460d4d4203e9385ac86be04c99b090bd2a071e28570b2f71a360324c0357adbd7fcebc8d65c1693c74956ea85bf6e7d823c0268c84ce6a57c7b48e23205e15bd2a68f43606ef4e8d3fc2a1fce594df950c95717af73c00c845b10e3e2fd1d168bedd362759654d113902de61c25859292b152529cfa200dfdaf6161dd96585be5a6dd9ec6e5decfcbbc5f78b99b18b9481fc8db9327b1a384991109d01a804f38756aba10baa0324c6487e339fe2b0db6acdc1096c1051cb303af0c8278869ad1439702bca63293634ddac1856f0147b082fe6332ecad08829f34a72cbcf969c5a24cb1dca073bde74e0cfd183631dbdb1870c0ffaa61d82f80de0c6c4d616a3117574a82809c06a4883ca63e0314342550efc395355540dd8bb8e49c844818784274bfd86465350a8987586625e41bcff984f6edbb440f1ceb42edf8c759aa184f3ee9fd0f06fa6769f98062978f7438d36e72e55d9ab190ef09b27037e477193157ae7b8cf9e808c3bfab668ec1acaf224cf21a41ae7068a34c2bc22a518129f7794f966369dbaf0984c3b4f88d070ca32d4b988c3a18d7bef087233d6ec03cf20d768d41c5e1eca012e434aa1412d08026a5de46c24cc370f8d387030657ef46d4e6744f342070c77771be046210bda3e075d27a4626f16e6a87afb45c95c524aa536f00ae2782aa0915cf5e3bb038159038a58ba5d1824cb04a237d7a94a842d3bd075a37770c7edf0dd11b20a17fb12c05a8223278d57788109a721b8052b0d3cbed9eac8c48e4ffc83e97bff6a9b184118e2c09169e79e16881127df462fbb4f581099a6031e7d8380b9f5499640dbb1f1a445d02d3e9450f96f9c8a5b43eb4e3205527da9fbc799eb5597bd8c761808add79c2e59d0a2161e02f60bcb96d2013f78d97a519f5be94a7827ddae8f6af915671ae50716b64098a2d9b0e309d7158b269248dafcf3666c47cb08d52854803e6e20ad4173e1d7a683d63b9b487204bce22a2f18a2ab87f0a44fc846a3e4c728c2691caa226263b1fcd6a821e279269d0e38ffd30d852947008a180bd1ab7e431a8fc83585c8d8968ee6fb2d6daa3f695837eb558064ce81d3e4f23362c7a30b8def0e1cad6b527ada0df038ed347461d956e7fcd60a7c98bd36b3021513a403c8148f181c03d8fc3f04cdd2b8f8e4124121c408f9f8cc945ef9169c0c8bf072c6826823242e077f2325d6dd0f5de852dd2af1fcfe123cefd014de7237839ef4ae393752bfc43bc6ef06a2339046561c81a5026660293186a703631fe24a76b13323b1509dcd7be0ae1cf6c6922f8013756e8d71108f02ac42473f21f80d70e710e3953f5f1690e22bbb3b8594837409e3eb7519699ede13d5ca6b0aa33765c198d4c0180b2cfbd743afc36777a346276e9fd27c0fc58e10dbdadf3cd45540204d718cd52f8d87313d44fe9b6e9993e519127981878a32203437695f21a5d421d01a8a5c68de7c23042494758ef5d344ca8c3d063d4cb9a570e736ec3e824f575e5d59093de70b271359c5491b5997d2825a5af0112b89a96897d93dccb15ecfeb0488b83d0f731cda2e920e388728daea48b29a32b1adf517079f3345fb296e47365df70aa9598a735e317e060f8a9eeb5c2bdaa2372c3bf5bb2fec4059703a62a0185f0cfa94cf734cd26ed49c06f7a2149d0e7eaa628f588e5d5afc74815b24c04d3e0d306e0ee0a4954f2290d94e7163d5722252b18561f1f5c1476d6b1d75d000c0cf8de0aef25ba24d976d7a7d128d4e2f9b1b41e6d54d5d5bf9397ceef6c311ae8b3a08fad0dcb013a1424a22a65bf7de311aa487bbdd42e7d12797fb46016fd41de6effc87ea790049b86cd186e31c56c448b8554c416482c96d423bf7e449250ff8f1f35d52e07b3188ea7379df6ec42bf30129fdbe06e08eee3481d8888e43ee481bca4363aa6800f53f4877e2ead080625b3e0bd0c71f16640996830d79e5340e91ca80a94e565d94bf4c8134504c581b878cf64f3e87f979ebf9c6a8858786e028568580422ba525584c8b24ea4711b217034fbbe97b3aa73824ddb74df7baaea1fb54b7cc6659acd7c2df81ee5b88c1e7e6b93c82fb04af84b5d583e3e4cb2ebb9a32191397f13b648140cb165fd0803256269bfe1d4020e0c01cdab84f30c5d2b11d43a57c7c3ef5747814508607ec20ca4645a1a0a3e2445d96ef6dc5f7ec5c7c73a9caf42252370b34617b92974017f9924b66764b4e4ecd2fb5814afb2dd413a8a9b12183ec19fed07b5d3839a2643229051139805de8821b962e301161321b1823398e7c3ad40cf155bbbe254af605d610aa6b9f2635b5c065c443d515fd49594053ac6d54ef8875631679637d65f12cbd9542812b57722090da030df62683e30095887a65d7f4e28219f3ed27bfefdab21a31b1665fe59101154698ba9327ee232453eabc5cf943d7e14e33ef703e4b00f891b0e62579567ea8df6063588001fc8edadb34c76cc8f49fbbdaf152e5f60e11285d13358869156283211fb0dd002962200707290494e6a6d07fd22cf3888e40b44d7f8222df9eef51ad11a94117a706c241cbf573f44f6fd502fc345b68cd12ed202b8b89d1072dcd76855a410c889923400d1f68ab640c0ce55e27d8f1e5dee76480523b45abf216863e158ace23579ac4fdbf069fab8476a3afc580859d170050d2d39086df7be33bda9d9881e7a788de0dcf7bea40905a6bca36bc2d04dc81d692a5e11a7cc3a6a7777731c40651a2b5da7acbcd00680c5d476a74321c9fff4ff3085b7eb36103cb0bff1e9b38d0e398e1f9d67c22288c1257ceb910d0f5942b26aa0e31b08bff4c1aad1cf151a952ad91ac6889b2a79e076ac00df04183578208305475e7453d6abf66c03ede7bd3a9bcbe386ec8b3e56e5b3643d97164a06a4e1e04ea7c6b945071fd73c4e12275384f4fbee39271612add7343edcd19048e3dbc72cf78b90bb2927d989274122064d39691326317055439506bf101c5c763070f87fc5cd8233b1c229fdd02a64bbf2792b86d864eccddd7c21a133846ad737deacbba40342702d3ffc1e8b83dd2c273e0cd7bd2cc7691dd290118a64c3965e1615df8bda12fb1218f3b5a0c9f0f79153f4e4590436f6311a08ee66044bd8b8a534c674385771d0d513fcef14b43098489126497774c0ff59ddd5de28df632ccb1a353b40ffa1709a40a073b303ddb557532422aa850e31949a768b4e7603cde96d075c87e492d291418bed49b96cf69e3c651f33247a5e9400c698bfb665b3d5a4addecee71c0057c84847a65a4b0af2f42660d02ce4a8a8c907484038ac40e43092aa4d050bddcb00afbbd70b253bac8233b753990da2223c17f2f03354f4778c8fd6d8d8b1f43c2b7c02e809c0ad65822e5b1688c1f70f0685d0c31742c2886793bb122ab3cc047594d387930b10adfb88ce10d84703ddb75f393dfaa24bc903643b889611c7b03d9b88faf7060b5c43ff8464a42514c8237f1d8fb11bc6c44ddc952f56954350945a02a4fd537c9caad27f120bc8e3278f91bf2b68b998991b1955f374dfadf87a978aacf0fa253743b735678f36bf52b1ff1783f92589e1394568e5c7ea924048eb632facbb07aff3456fa96ce301c4904173ec82c728007a2e7b9402e3ae81b1fe51e980cd41b5b83e079181389507cff283a610c1d4b0aa06641344933bcadf5f6248acdba454bb054066aa3aac8f40a4701a20ad8594a6fdd14a6db03b961347f407058cce7c18cdb50661b842a081ccd7fdc0de7a0f4428b37fb09ff1da08e912d083ab20f3e53fbaad162aed0f8bbff263bcfd34978d2a78e08efca093ac15d8011337bd7617c001d4b5c45253fd83edf9151f59b9b280dd531b3a560b21d2aa37c77c9d25b83a343b993740bc5e568e49f22546a8599d622874e276aae802166b2583a5b5503cef5ae8f1af4c6e5e1314be8ffdaa512705cf7008f849a45cb4c473585e6f892c06f620954f76a077b3c92cc62ca368f2de1847a8b96d7eee47ec584bd8514a43f8ffab21bc07b41104daeb82995c30ccba9a0e251d2a102168e36481dce2c0d163f09bf18284e080c6488505424ba5c256ad2ca04381c9334548b60574387598b96fd7f1d76954156f4dfc445d8b88ed585ffc2104c7858e5cabde0b8da691496b7bb735b9a594322519d9095c094609453b64fad3484b9e93274b261268594af776caad6e49f5bc77ded7bfec7c4dcb4442510f0da81f5e8061ca0b9313198511605831005330a165cc172699116392e4f900f0e1872ac44801922d26808225082856386fd4336008c0194c9c0126d7c7e027063450810214966e505a413a0d71c39c73ce1e6f68ae7274ace8c24486972a5670c40a9acab0a18c33c4389921e50c1472fd1ede50ad2408a28647ec124446d1d413793e8f8e183c5480058a29a35c7f07930a78c091eb5f20450aa6b0c01baa1b971c1480494111b97eec36f9114956cc39e704bda109801e39cc914222d725505084822272fd1d6fa8ea3821b50206b9be0e6fa85a219ca0c97133986fc7c832c60a79be8e37346f5217343806500031a41003863c1fbf5e4c4a00c316585280c20a098510355b2b7e07bb5b8c10d4e4f99f373473c894084396411846e4fa9f4385eea28ee45adf460d91ebdff0866a17f50421b07202a65cdf863754b7271d308ec895e96b784375b3628a1f2576f80835608a57a2162f9074783145982861b6f862875cbf07e88b2e72fd1f4082e4784375bb225a10ee00a36403cc152610c204b55c1fe60d551d1474395dd472858a92ebbfc010e5fa34b0121266b821072eb40c219f20620515ce1417f8708145174170a143aeffa242b48af7755dafa09ba6e2eb17242e50f8c224d7b7f186aa1135d4787122cf7719d9e450e143835ca3960aab61b8d428c68a14acb0a28b5cffbeac3023d7ff2b9ac8f5412c72c8f57bb01893eb03d5b8e4fa3fb21022d707b294450a725dd2e2895c1f842d74c8f52d90812d98727d225e6878bd6aad482899894133edcaf34a9729baa45053830e1722b0105197b8c872ad5aaae821572c5e86115dbcfbbaae976b56c18214797ef73a620bee36518124cf5f7d3184956b9b9e92c8138bce9309b9d65a6badb5d65a6badb566b92087ae297c9862cb0979fedd52459e4f5f3f905133a3531679ce295158b5d65aeb4e9e45042db1f7058ba2498597164734a2c832bbe1128563b7486293b76d73bdb098820595d78d422a494d97292b6437d784992b8b231b1750a0a9c2a866861566b67072452992a935d14c79e1621135c3840b2d2e453258acc0d262ea66f922ca66191343b785118b89122e8b266f04ab2b689d9621544f9c653a62b718ad66d32ef1586cf2c4633187f7554bb2802a9cc25861015a142d69197364015434a9b0002b8ea4a02149920287a2055cf1248514240be082f4399a4c6dfdb295564aab8e272e8d1c9762b872e472392ec5c0c50a3184912403d113198a7ab85549d365d174b020430e3218b1e446278ea0e59d2519a05892a1cb7dffc273bd9c80b224d39f5132998c05195680a48b4ed470845ad0c2ad39c18223eeab091597660af874594a607155392e359122ff52932b4ca83a4930189d48622ad31c645cbb45cce8da1c979a7851c495c971a9c91832382162e2ae725c7292828e14eecc71c9c9121b392ecd30460fa782eef60ebcd0c20d2fc8eeeeeeee186eb6736d312846cfb12f6b73fe25c494835105827e3ac3c9184252b6a726b8ddb382843d665cebb13e2342dd07ab5f2b16c27a0e17693deb8b70dfbd11211ae74cfeb45e7ebc2dfce2aecc7bcf61ef693ecad8ccdf6dd24209667e437ab2bf63a97463c60d6d035ac8fb8ef5716db749eb240eb7928c612bb7f3be48eb65dec345baafc1600d4c9c47642d066daf3c49c8128757929125966164f9f3732207392ca5c615d785a5918cc999bf1f8258cc602040637ce0032d5c448a90d6771f65ec65f007baaeebbec320f88005eeb7f0c5edf4dd87f1098b16b64464cc7bcff3dec320f80057996e7fe8442d2ca50616f7bd0fbb080bcfc718dc772c25077fc95509799f0ad75f853530a5d3978bc3885b6bb88e8d3c87e0affaf9e1dce0aade5ff5dc6c99a3acfdc8300ee563c70e9dad8ba1edec6b2a064510f42efa916c21f9124a3a5296b827cfa2e8d2684a1c76b307190b5142f16f0ba7466beed3126ace6613ffc148618ca498299b4573891f492319258732cad1e752554297e240f553314b563df7f50bc17f72d063f2064b407dfa1c547f0e749e2a2712e33e2a1c420d679e218ce0b92ec0e248bf10fc371a427d7ffbd14b802a048b00bc7909880e4aa9fac54c9b62507de95f8b3c3f93c8634bc41f9985962b57644d8350d67cec78bd646454d39f1479d8978831f95934451ee9a59f6a2a2b8e43764327f269244a9e1ff3c4b52831b2d6535ee38c58ff1870f800e4a5c28d1ebee1fe7020b2d25aa794524a29a594725229250754f5616cfaad1be2c2ac94db263729e595745220b449bc575c07e2df06af136ef430941b46a60dbaa393d2de6c383d95f3757c839e5a5cfb405cdaeda3106c73f78a9baacb7be50c0264851b9970dd95ef6d822c4109b61792bace1b6a2d0e7abb3bf548bd9dce0c8c9cb41c4d1a35256530dbb918db5dab238f1affc3c01e5f7212931d6a7af48064d2069bff74cddbe994d14a14145d249ddd1435e53eb2a1c254713159bef5861c07894eb20271b3d70f241079e4ab96b81209cba317686e28f9e3a2f93c8aca55ad7c48f58ae72742a41f1a41c24074caa111a41cc2fce6def5c1fc6dde8810cd07f3a22330999f79966c72c1904fb27cf9c47d4287923469e4a054d17db9c47dbc972f84e65d2ff33580e65db2fcf120dfb12bf9fd9c8a741a6e3515717dcdfb27858585b8de0617b179d7d77c456a9ee6af1897bebf74288fa202459372b0dae028635d83a3941bcd93311eb974d93d329c51a8bfd0a964f93964dc501ee121aa57492877a92807a5fb181c675cff469247625c9d12ae7f9d5df9b11eb9f263457271686404528aecb8a37040b961877941b9f2b54c18910c4b40e71fc8c854ca96c1acb8321ca1e6f72f00cdcb775acf644c65853e31e2b8012021414145351d4509239dc8a14b1d4d41426a922f9f38b9d374b8f2639634ea94ce9671b21286caac84f2c88a54b7d42487b28b4c924c5dee510cdab23c8a729fbcf84f902c2e618162924eb24ba52d619dd454a46b4992f2a89564902ea16a209157dca71fe93aa883918ecff151e76b212ec77795acbd5729c7176b2c71e5bbcf741a89ce21c7e74daee40298b4d3f94c68a1300ecea8e9f704b7a1ba269b24b9928d25f746de799d0f811e082e4ebcc0c504a83184e6cbaf6b2e80f939e6ebf86291ceefe0225284e4f81cb888fc4d8a101daff345e4e7f8fb51fee8e022fe3a709431d6b33ec71765ecfe90b1f96ef3fae834609f63ff7e278536bce12b13c1f11dfed5f7aaff1b4f3f4aafc6b79e88fc71190e1cda0f87afc7e1dfc021986de01a3804ca3fe48f7b7197cdcfc1a187c9f36138b434f00b872e26cfbfc1e1cb06879228cfafc1e1bb300d0ec1192c83c39efc6ae110c89540599d5f2beee1121163f3ab402d912796dc4991677e9ddee4e0fc39abf48e945575be28e4440e4eaf1263f39700b1e449c5865d8989eb8e83882b8dde5949465d6ef8b1ff2a03c04028f7a7179e73832f8932461bc1eebb1122d87d47c4ebb82279f52dc580b2524e282eea6c51c6de5e4e1d392bddbe5b750772b95b712b1528bd0d883ba3cbfd2d3d9244942a35262644a13f29729f965522134d2abb90ea48460a555fc6c9eede906398fc2f7706024b0e5545d51bf24ee6606fde90e3d8e28676ca4e5931d6be5ecf7943931e1539d81d11d20d3b5957f41a81aabeaf2bea64f4ad980fcaf673fb8ec83b99fb548fe2b1d9b9937545ee13ab4a36e52086bae12dba45ee538418395415d12197dbff70ba750d1c2e4d8e5bb68c206ba0fd48ca7268a537646be470433f72cae176d4e3847b73e5aafeb574433b7593c4a54734949472d8c972ffd1a447b9df33923f5b918cf51fe5de68b93ff490727b49b99f4611eb4b0e3d2bb228748b84b8e1565494fbb7221bb7a2dc5fa162cc63ac50381c52a1324c7e14f2280e937f24e6fe94e4e1bae34e8a314a96c450b6ed3b6a2a777f03a6d086ed94dc8eecd496d4b84706e972016b2f2d6ba5a824d418e6452fd418d64d2e112b640a3586f513b95063291c44107463c08893a21cc470e1c315412c49117534a3a2d4b049c0095210484b556c01430b162b284e0006892701133932753ed0b8187f36b757c6a8c521fd4ae9eb65ed638b438c514afa93cef8515c8cd14b8eef4a3551b76337998a5a26b65de692f27cf9752698fd5eb38836ec47fa21e0d0beea6df521752d8675509f6e1d1ac9f439efbc0ebed0089df282b813417ff9e188608f18f3ffc99e43c38716f24608f3733831ac83cff53fd74975b044e83179e27f9593ab56d56e5ceb762c4f86c635f3381fbd211a35df3eab5d754e62065ef4688841d5d5e3a36bdd97f9f845dc87f5f17be623cdc7ef27add44c34c2d6f28adf556206eda57bd87cd84b3ff149e0c2cf0cc949f48f42d481e8df474070086c890ae81efea01733d8d13dfc77d830176af310503b281a384d5e5e37b89b6c70e85e831baa9110e0b9c6042e9a9e4599cd198f95813e12f552e6b6820c6179386c31de6db647013d3dcd75d4d7611c7429f7b73874a2ecbfe1b0c5647f2f76646fa8c8e30ffb0eaa280639ecbbbb63b7fc09c31d06aa4a4375e1331c9791eaf999423019ea8e0f2304514970a92d2185822c4d542c2b54289c4890274427011347d4b6265078188e7208eee8f172648261492da1d229636cc5e860fc5983129b5015a24253889b1dad1bcb0acc4686f5aacd93a492c478fc8ebaf225c7ee22c76f2f3108a78d9cdae829c6be32a5ca94a51cbf9562108d8f4528e49824472639badebf9fc87cd84744eda489688841351f9f7af6145bd64443c808139f480ceafe87178f31c6da1621518aece4b129c420c7171283bcf701a44190dc9175a2df9cebcaaf0ee7a3dfece2099c4fe626314a5d3c71c3062c49eaca97dcef44b98bdc8f63e3833dec6dfc8d1b30182ceac6d719e7eb6c03ca0b8d8f7ef3937949889c1a9fd3942a35a4a7a51a9f8dcdc33ee7b9ce1bcab1b1b1b992f375a6f175863129d1f862c6e9dc45ce247751e4a9f968cd376b3e995b0437af2f03dd4e723f95dc35af4fe665dee66f6e6464648e6ebece355f679b36ea2691a7ff8bd945a7ccb532c098a1f9641f74d110329a06d5b35a19341f8bc5adbca119160bcfc88821e27d747a9fcc718ad6931721518adc2fd55bb2773f554b019dbd272191a7df931ff50264a7ded413000d72a62a4f0146b2ca474f4b47d6513f2ff7fb8edcefad93376fa8d6ce5de4e98f3987e8f63bd9e818153b2bad95ee94e0cae4c8d40253d1f572646a61881c2a6801e97e0b32b8a0baafeb7a41c9335a700188abca91a98523605a20daa1dd9a239392339894444d2f48321633a53b3f5c1a53e8d002450e9fcab53932295122ef38a14409dc40491253922472f83d3d4877f98fcb63fd4aaab9b13c57c751a970707ab23eefeb56d64655b3727134b6bb92eb64951b9098251ec113d063c68c19d31eebb65a57a6a37165a51c9d3ca95743bd9a1ad652afa6c6b40102410bf6006d30326e38694c98bcd1572f8f4e2b99ed7dfda42c20dc6c09c620dfe13e1debc8838100727f3880ec1d1b87e6ffc69cb37538295dca28a594aa18a5a4966e73d6bad139638c314629e5cab9961de99cb3fb557fce3a65f458364c5c8cf12565534a698cf1467635b8bc26add1c53bf2dc2e8a09e46084d9703d3796db6fc3ed0cb3e1469b29433a5d730c4ae30ced628c118740e594b1975295ce6c49c97594524aa99452ca158beb28a59452cab1ba19b59aad6e46a9ec94895bcf50cab9bae8451c4ae0ca6ec76f88ce53eaea62e49194524a29a594524a6d7c8cbe2ddc7ef9b4bb439d1c256aa5b5ba1cc442d4248fe058667904efa93c82e777106f5b0e558ed5089ee387f5e72aabb6ca7539e2d75a6bac35c6aeb6fa7632b5d63a536ba5b17155cea69b33da5a9fbb795557df7412a0945219a3d71fa2e30dcdc773ce9b5965f64c719a4637a51ae6e4258fe0596a86f4e5770f05e7e33c75725e37382e53029d9bc5e95577f7a455ca2e5772b9b252dc45b3abb8928a2b7174b0b3a1a1abf1c2d0b470984564ae6cda9a6290dd2d251829c1483926090d5232f13061e84c1266d231b015b88a3b779e71b13dc33a885fe414814ead9bc579e5f8bde2b8ef5b71ddcb7d54acf8ed31c8fecafb4ef515c144ba2d534d42e4f1b7dd4d718f74d11204c82c71cfa43062510f37ad9b5e03a4d00a379216aeb3e96a3c8fe5b2400d04164d6bc6a99b76901ef03a9879cadc1cbf08526e26d6cacdd0b4726a60d5d874d14b6edc9187bacd0d2701073f887315243ae174919b9a9a542a2f9c4b94d3ac553d7d57c96d82e5745064efa6d844ec2cd97d065a4e8dce02c10a883c37861a366cdc90403d3d3df267c571dfcabfb8f118470caa407afac7693247cca0a3ba87bf183b56756b50345f254d164af4a7adc53baa33e825620f7f2f5d8c7237214613f7e1d078dde0ccc701135bc1cba8f20512313098925049929464ca28989220fd1b4d773a414b122c2fe2a27325994cb66405160a52b49294548832836486d73243c76ab574726b47abd5f25a1f8fc8ad9f2d26246372eb6f95dc6afdaf00456e3d8824b7be87052772eb8156c8ad99536e254912a3cf67cff33c4f0531d93be2c4a4c2112ecb9373652e27733f392e8527994ba18bcc7939ca036042a10935b273fb28c656837664ea23533a001aaf6090c3a8a50bcd804ad126f2c9611fc9b49768403f66c994bebd24a420d37f2165fa1f0231327db0042032fd9e12aec8f481984a1893e903214acaf48310a520d3ff000428993299e045a69f01fa44729089c08ba21e32653ae14ba64c011f975799f33af9a9b2cd5cab93df96b9dbc9af669ab9998e867375359d8d8b66a6e686151ac9b00e6e3a37f68091d92ea6a38e7bccb9e1c69f2a748c1d63eceed8dd91c6ee27dca42f99f8f4affc22209bd33d467713b8f84831469d3927954e1d989c2955b6fb72e5538a6d94f5ba125b1d816a256537b74fd2f9d58f66d5eab33182f4adfcb939f5bd5a6abf036bdf1b52596c75b0364c4509d9c4001de3733b5f1402bf22345000e3884c68e7b92e020267e14b91d0055f040400e0e2284a28f616c8b80fd991675ec6bf70479699f91fee9fcccc476f68c6a6c647cf068d4fc7fff0170a3766fcd6b5dd0be686b2c88fcc8e5a375afff26f73ffa68efc6cae09c1fdf79ca9c30d5f5eebe2e0d3d40d250ddf7cbbbe717c7b43aee75c2e3a66cc182c3947a5aea5399af33b1ff70dfe0e88fb776cb8709f06ced30f07e77f382725c5e108343ffd6cd89367ce174aa770484f479de04a263188fecb071d72c5a1acc1667b4614f9c4084bda279b3868cdb8345ff3f6551f4a1a77b3a972a85e7a43aa8ae36b8e987138def960f279fccecf0f0653bdfc0dfc602adc001ebff330893de081779ee7c1876dd8039e079f0783df12fcac8c71bf5357cfad6256e178ca89f023cf77bdcb254254ca12873ff2c421b85ec7ff70ff5cafc3e70f77bc4d11b6ed13c100746772b223e24ad294f6297d42975c7243b90ffdde41c096a964b1581deb6547955a0afcb61b9c100115b42652fe944e93d2734fda6c311996fb6553948a8a4c4952b925131130e4502a5dc9dd3fa52b2146892b5f02259a6c9c4d173d0e74bcebfd73fd8effe1396f9f03df88d0dc79f085ec3c08f3e78932362516b20373ac0121e0f3e0223fb003a2101019eb0fb283ed86a5c03e0af1f0ce87bcf20f5fcd6ff5f60b3fabb0857d21d4bff987b45f97f375dfe3e3b103fbf09837d46117eb69bca1d5bbbca1ee5d1feb7574cf7a6ef5dd2865afcd9a94a2ae92d39383c0fe90f31ba1f3dc70e0f78f7b9a83c3435e19c7db1c3d1cffc3c31c78484fc6f1f83f2917e7693c87f3859db717a5b1610cf4641d1adfde100dd8f7309d8fded01724e7619653b9761e076f3938b4301cde1756816190fcfaf971a0f3f7fd0f7f7da10e6e3d8daff307e5869396fb713c1ed293bf9f1dfe6cfe3e188eef93dfde3f27bbefc3f1850d73dc0135caf4bf8f59ca9c9e6a505153535150b5272737f223a424a5a626a524243f8a9a729917f98ce6349f4d452935393dd5a0a06a4f4e4d4a3d6b5a1bf511525212521fb551d37a166b112a2a6c595117b56c2aaa07282af5e0f1f100b953e6fe87b7de88833c3c7a7c3c3e1e108bb0e5b033067a3287c320f987e3577d18d85e7a43f8fffb42780cebc0cbfe5ef6778c819e4c7108ad973df95b2663fd4684769e87061c108544e0de0151087c1eb8481110708fdf791e1fde1e9fe411b66ce7a3d08e0f63144f186b418840bc835fd25a7eadf7aff53c1f2cc88e8771d8015168078e3226b4f3300f5a68e75b6807f74cc65a166b29b5fec6e79f94eb5fc36dd4a851a3468d1a356a7c9d6dd8f8ba461b19b9769ea3f9c2ceafb7f9866e705e62b007e855bf3dad6559a353df5ce539387cc1f04603d315b698c3385f3842cd37ef72798d95351f63cd4b0cf6b83e04ba71bd7f353fbff0e6dd1baab979aebb1b8c3d8cc76a5c51f2d1915f71b05fe67ede34d4c29ee47ddde746df852b4b9afc89427d5434739fb02ff87c890ba0df82afa75c00fdb12fba00e4e7448d50f392dcdfb289b4b966881b4db511c881bff33e80b2a4d5ecef3407dbc886eb4d6a9fdcf9bc04c42758c88edd6886f2481a6530cb270787b8b20a491483a2fc96e1953e2e254dd27c892bb32ca72021d9194c49c6fa618e3bc98ac4342d4edd4a2d3b9a328a6a1a54cf6a71aae73df6c41028dc3ae8595681d093e50f395718033d5985c320d27654023c3066cc18a7bce1213de0ce178566974b8b415dee974631a8c34cf9f7909e3ca427cfef28077b4ae9facff61026ee2929cfe17243497bb2f3f40bb0b3f30dfefc064123f7a118e66fb37fcfef89a11c543d216950547ac8b2cbeca79def0989bdd41cd4a1a5fcc7c378acbfa71c3bd40ac9fd9e42eb3092d6500ef63794910e9f8018545219e5766f6dec6926e647fc3b3c947712dd34504a95a69cd035ed0195e74feb2428e28c07a294522a71f28422cf773a060633c6488113279c7084a48288a93a43539e566a92ea4b511493932f96cc4878400b3424478c9824c961f60025494c3308a1e40149a03a147a3607bfdc558e4b4a4e782d2d51a2134ea5ec7ee3c5f599776f68e667e48cfc5519575a4e46e6391919ee7ee190b7ffc3657dcb71975e7ae9a5dc8febe387b688f8e3c231c65898091a6c25bbccf159cfd18ef51ce775ed7160bf22c929e57c6eca29e5949999796e662674654a714711417a1c788e52463e6c2e711f9783bd4d23ff79e43e51fce72c923f52d692fcc9bbc830eee332647feb0d61e9bc8aab57e6b8c8bd27399555197766d673fe0aba00c8d2822c71228e3f4e8b5f0c8ab97b80a4bfc45362a6a46c6aca5a6855c6ad1a90b28fa48f309e6864f23a80cd23da7d2865f3894c5a7bedfde849a4c8e3f208c71137743146fe75e6412ae54a2499342391ac375d262af5d1d7aa54ef17be72eb0b6d9e42ba1269db5e25b1f538f0fc23f2f8d323f789b2b5979e3295dda5cb29739f302acd22f70925929c9262e4530e27d13c92fdeb064c4d9eb237921e20b047de97b419fcc0b2a7c9ad1543a14940ac9227f6e13d34df061e73202986a43aef9da424e33a50dc6071c3e8247ffec445a450db42f2e9c4b097833b40f901f184b8501123424032d61d6b4e515f28c150c5dd9d521a630c928daf74f9caf366712b0e87b8f0063fb3e31fab14eee7f0a504be72f74b8544189d6adb10d50417471814700722bb39a7063a87ffeee3ca9f41087bfe3669ca91a949d2528e4c3248e56792c14bae3f9da4bcf1722703e2529ef89b4febcfa7923b32790cd8de4a0fa828e01404073fd672e22b7b7f54124d94a69c90bd8833b2632b59e847dbf445fbff8cecafe39fe3378cdb0f5edfc8c8b4bf2ed78d6e5b75291b241f5e3c76d92af5188a5290fd75bc658c42e215c52aa1d65aeb96335a485070220c192238b2811ba0c6a0a054c2962db63cbde045f17483122c6242d081394185e7cc781d95540ebfc722795b69ad7495040d546011a3c313a229b6b43013c5161c9a98f7755df7c6315a48b1f0c2d30c4e2c4df1658b628a3050ee65e264c3840b2624a0b92e262652324e8e4c4c96e43075d2ba59d58aeb58565e4111003916c522abba325f6c2266895c7e8b1cff00d18bfebb86a5e3a4322a6b9b71cd0a04fe903f351f7ffb2800426dc8c416a0ab4d8c4c36400ad16022644c7d2ec47577bfeebc8e2e57adcb39e9e0ae643890b96d0ab7a7cc8c4a2929a986a456cb1b627dcbe3c0b3ea672c919679e71ff88f17452872fc05b8064282282006a9b647723b9b4569b55adf2d3cbd157655dd6a48b547b2b328dbaccea2047902c2cd7433ded0f6331e079eebd7d86c1988535ea441d105397eacd66a5ddbac962405f84f276d76f65ed4b575c6acb596da95da14990fa88773b95c3536b55be5b420cad9500f4c1b1b9b9bd7ecc95ed4b5eee17f5f2f3abda8e633236a2de946fecc7cf8caf672f78b4232ab4c693fab85284ebfcc95f9eca521f18412391a2d9623b1fc6bb1d6760a0009285cbe2c51b3a20769c50f38d81e2b434c996925994c76245ec9f406a554504a63971fa464fa3f4801b104105053aabc10c409327d20415ec8f43f10022aceef07223b65afed0be48354cd0d510aa4207e90c2f85025d79ff186aa0f5b7c209190e75f6f68ced083095a35381080872ab2f42025d767d9d04981c20309b9bee70dd59a274c5290d0a8c9218a19b9d6e7bca16a8520aa5959e014254b14151130b02c8f284620c5084ac8f3b74b850b5b6badb56576108369075a9e4fef9c53096fce39e7bcb88acfa18f5cc15cebd10db91e89c9f51f9c814987292865e40a14047777a0687941060c0bbe9ed490d84cc9987df2850a355a9c6c9e4c417125d560c181862317682470410897a32892ccb658418896a3308a8e987020e30425990bd6056e8b5703926ddbb62a68b40aa3cbc311551551dc2b872232a5db539ae569cb52dbb2406d59a2b62c531edbc026b668a7bd82a6ea525d2f2b64326673f86205da258a902e53b61cca6032a5a02205d385b26426048b1294274a8438d5a008a144c8144d509a8e087932fa9e90b4525a2d78e1d21c979a5ce1056dc9c9931811b7cb71c9490f3b29383102cc618b135c1a26a42d3a90f0e1dae4b8e4c48a0b922c39215a7232c61126883c168fd825b7c9eb89c72213513c167308c68e80628cb991e3120d496a947d4efeb42f3fa39634c924c6ecab24cdeca2ea644e30fb8b946c5f1eb98c1283aa945f3b7ded25f2d8a75fd71cac12833afbdbb73548a61dd94a22b2f5215b1db27d570e97ba8e6e2869921655ef82726592fb6c336f5f1a49c9e1502a65fb5151dcaa5379181e1585fa03e2ceef1cca999cd99ff6673e6924690edadfbedc9837498b3cf6372eaea3107d5648a28f6a87f6993cb40fd744f4a150449feeedbb0939b81231a8b3fd767ab7eff5972cf1a7919a3a83bed26664db28c8b6bfc816894bb6d6beffd05092c49f0cb808da3e12d97eec22b2d521cfc0c47ef55c4f6e3f573d06d477e170ade522875deba4b559becc7be00f0b222303f3e02dcd5f07ed47a1d7a5c130efa310cd47a10bf360e6657e06c33c0ca5e6a390eb6530ac6228331f856476d4a18a4130dbaff97a64ccbeeb039231fb349f53b5efbd878138686393ccf7818cd9bfd5daaf6f0107ed4365fb8d74a53390b3ee61df43ba32abb556ce3bdac9aea37226674d29fdd6b4b3283db3cf51caf2b2773e27d79a3d25f79cf4eaacd6fca6b56fbf8f74068dd43da2676d876d1711b4bffa7a8b085a29eb1ef66d7b1163f64390eddb318c5a397497451e6b27b6ff61b92b4b439a6f71c8765af943b61c9e13e987a7dd502737533fe9eeee4e8105df9698d88beb3fbb890d377a584a13f7799913dc59420e6bf2fceb8a41dbcfe96097a6fe58002e72ecc902a4e2b1bc96c8516210e33fd83f7d45c7fc6b5051397ed857f48f3ba970c8c3e250878d1cf65383919d8bee6de4d067339f55e069979b504ca7f9e43ee184623a4d2db3cb84c2a7c824242f90b4f09991cf7cd61598451e7f3148536492ac85f19f98524c420b2d4d579cb2cba7ecd24b76da4e336fba22bbf8ac27a7450e25174dd28718f39759f4500162f9255e55b23b11f24be4e95a3fe1d87a67350e7f83fe69619914f2c861010ac0fa2ede4b4b77e3150ed491436fca8e7978412412f672c3396b2d1e94a0d2c4b484d64158ac8779e081f71b863540f5dcdb8779c0c1a4c530676158c51e744f571836b1072a98c450ba0dc31c43517d14da3ebef7592b1de3eb25f8515122bb0612c8c106fa2181787ceaea73082eea7034a600f7f11d3948ac9229b5d4e577c45de51824a3d71e02bc2c710da4d537fd0f885bbf33dd24f716e3d55bfc41883807e3aa704ffe0fe346196b9a8392ca98162eead4af8fe867420bb551c4c07f03e346198b42dcd32e6da3c8e3bf857139b74fffe949e23e52616b44b123893cfed1480a8beb53fe3886dbd88bdb180a7dbd7c4274dd1f470b37eca7a7277f9232e9754a8c31c87c70c782e848efc84d73374d36b1ed3845e48686eb665cffb069e0911b85a2500824611207110a6b8ac8589b715be68ec3042476c05e2491d2332497888346dce80de1143a02f57b9c6d447051e795dc28e3d634e36f3fed4f4e2623e2e07cee05c38d1edee186718a88aa7fc619e777b9b218b42306adf2acfba99247befba28cad6cb2dcf856c61f1b0011f20108ee038bf88bdb0f10199b1f8292a88b7e7090fac89c54c62ed9670e88423f646cc65509d1845b69fd721c9c8d03c98d320b064ce2da1c999e3ce5f056c182e40913201597e6c8f424891a25d01daed3ea3ae5d65deb477fabdfbd75779d34470259b9d19c6a5f39decf6ce395e3edd0696d9ea55c51fadaaebd76a3af1c6f874e6b524bbb4b819b52caa77f03f7e8f89c73ce493ff6f4a89aa136ede89cb3937246c5182395b56ba594527a2da5946e1b1254c61869ddac8a524a6b8c91d6788c2f393b2be5fcea54553a372e0a183060c0cc31130c183060c08099748699632818304761cee0a24ed8b5235b172d99cd365b8ba88848a9e46aca71f2f1a4211670d07f840c38e81fb59a12e03e90dd1054d77943abef9e257b8040ff97f781fe4a5468f2a7993ce6b6afb41114775292060e42791deba35252126773582a57917163ed86317078c891c9e315a6274732b54adc1085c6ebd4c2930d275ca61b54d0a5eaac40083a850d4972e4fa53e74a0d5276e4fa968a5ceb2da2883bbf062436d01ca851134b84bcd06484059329a3291784b880549bd87ccc923282b833487408716768b8e0890f31302e54d9e1b6725c72018a1d2ff7e6b8e4c21535725c9aa184292a318733c69c79811873ea2f7d6e34b640e44972fb093339790f3d8b41396250cc114714b3f944b325f46d8c71c39ecd9ad6b2af8b965c99d0d3f7f1f229a5b321b87e1a36b636b926bb66b24cbe99db3af9b532cbcb5de6b84e06a5d8a8248df745392ad19400000001004315000028100c088402814828cd835db10f14800c7292427c5c369708b324c661144286180288018400038801305354e3007682e79eee7040c7e23542471cab3e8a5010746afc592d0001442be9be613fda755f3bff06fc67ae8bc9ca997de96f1d991bfee11f78851670b5b4b95668efef23aa28d84cff32f9e73389ff847f590a00db04c296af65c491abe3239626006d70d79e1b50b2a8e531e915c542949b08c52a423b61c2fda524c05831051a9755895e1a838004331485d7366eea40ea1053646b76a6b3398ffa7bdf548259fbb75d0a2641fe4b53720ccb00a8e2a12e7265874ac9fe1c9598a0014341e72b10ad74c411e5592742c7d0d6560d6e2bbf532d6e3b93e5cf47dc076b5e091e66f6ebc986754743fd6af4aef6ee3a306529b3e65cb17656e46bdba337a7172f212c7dc14a71782b98036879eeceedf35b87e932858369f19ae72d42f10e70a33e4b73084ebd9d2541774ed8808b5d2434c37bda30b660efdfadf39df20e4b9134759d5195464f7fb9e90642fa50bcfeadd576406b94d4941d98490e3b8a18b89e601e2ed493990b7f6bffedf0b768360bc28de26d84ef4c2c1b9172118272f62bf53f842898cdd30bf1538cacebbc17da9c752b2af19a22e18b48ae093c0f38d82df097090c7ee2c06ebb421126d1c4266c9b584e3c7f902d10fe7e96eaa9665ede7e0556184ef9038e40695d5a9bee647059f5c304ffb41a0a73d6cc0dab4cf8e8360a629cfb0b172ee198e0fd13b5ec739f7986cba0f06980554f3cc2a3e4ce8d29a624ccef6d7e09c0c5ebbbe055339800b6ce564edabd1fe63ccffb6a773d2d6812470681e7aa4a32e34cbc41a0e63582ace32718f8bf1771e47685e7db024b98c5a8beaf54ac4ac370b5782cf42ce8c0c190ae653f34ad15f5188a6168b383498461a09652f82a8a1aa1f530afbd18776ff8850cc83b11315bd5bb196e62e3475875ea6f3047d5822bc946de632dd8cd6fca0e287071e47bd136ad41b10b8496a6ae3876c5d8e381bd6dbb1a94ced56d323cf21353bf045b32e525b0e326964b11344d8aeaf6cd8ef23836171e31b7f7181a9ba0ec6525215513c42d561f3f0827d887c190d0652c7d110d751f2ed6c67f011590d1e39c94531a937ed345eba0b992ce21a48d4ebb9d0de364478d859e0dbda75473f819729f61af4905bd86cabb1f27206f3be97e06d9d1a73187a168b7bd811eadd8953b27b00c5eaee05d885db156a8ef6c4a801525026cbac229c472aef2aaafce5ff7a8e79abd1b6ac827e7e458f481e0b791cdb5c7f5f950f5398db7a4ac5ee6ecd3bf9a2e4992188fc8ba142ef93b5a0a7c7e357dea81219fc92fad06e6605d5d4d80d86ad2b506e218a887b4fa9197525f82253494a9a1008b27403dd97f83a2a960c9c57308bcec5825f0250c6b865ee6cc2648aa84018432a72efcd72da302166301ea4f56e9f2fc80f0006b6507f724242c13f630a8cb1277843665c572e9161bda2a4a65e24c5e3a7ad532b5a171a3e3d87cbe23531d81d99a4173d6b28ff6c42bf6eac3fb72bb9e3f0af4d2a42c1e84a9a0d1d271a7a0a2820c7211ea675bb18b356681e60a28be3fcb0dd198b81c2c7e2e1a9237deb891f2eedf971cade6de3593c824b140b528b206f9efcba4738f2be492128ee5401d147f2401103782899c198323182f9deffd9181ebed7962c5bd5e3f9f34fbf6646fcde6b006d3cb342c8618f122078e02c77d27119ff066b4918c31fd57e2130e7400fee29e9010d144ddb296c9d0665c9e3de67ccd0f513362b26ab5abbdcfc31ce12a2a91e98b3314671fc95f9df63b3690ea1759b18b9beb2b84fac899206d6a80ff66d73863c79ea7f39021fc9c8496fc360d121079a2fedbe07955b2751aa6e1516b55bd982b283d37eb7f959c43c9ce3d7b94f494eec30ab16d60d0efbc2cffa157921afe127cfe10a146fa8484968cfa0eefe665107151aad7b6bff957f9f0ec3779c898698d4fca929b73074ea4faf5c8a6a7171bd3263cf97a169e1908adeac7b3d95cfe1802fa74270a3034694de89423dc548ff858e8f22388b915badc1b04d8457c32b64b9051960a5d4ad684b31c2d8543afa5672e85b633537b399a97485d4468f74985ad28b5b624dbe2c011bc1599cb1b98061bd21eec7b63b66924c3254f187d00232fafeee16f3ca502a79ae6c4fbb6e25592463f7ee7eb145dbaa4d7d32ebe4b4548db2520e3682c79a7ee34470ecbdfb4b6755443935f664bb4907bf10c5275e485cd70559b01953e147c477f91bd24db739898e47a03d6e9afa1cd9355437f3a3d2b083c3a44addb3f8613544a052804c2e75674fdbcf836c4d3755fcedc7f2488e7a9e99fe1cc82de48ad6ac17a0a9a2ad38b03d2c57bceb38484b1678aa278d574415bd3e6d355d12f02dca12677f77775506d0f844e3b3a0f4c3cce3ad081b09c3e394db22955bbe392310cc33bc5e76903c2c46541748969f6655e1986febd04cf474b1ea758dec9030fcecc1dc1a3e4d967545d7a00ef220e1a127b326d585dba2f30da5da00b4181abc7c1e3f67a9a30b085434fcca8d848267ba21a7b1a5b7634663975ce777d0892c1cd96672a6b1a646c7ab01d863c832c9b47fc7478a0b3f07bd39e11d8da08cbb2787c4e2e6f903d2721e5ef59a2811fa7065e19d64e69e1f9a1440147a8ee0650083dc729a4b0d3609e47f7f829fa1a9b59b9b06fb672f996d43780981c3e3e300d247841f76494956d40f9529c791dfac074537bf78efd8e017a8e05b986019ea70b521a24024e68b525b16986d6d2f8379c909608178633e390fea3617618e8ce5b833088c111f6d03156e9f1f4d3059f58598669a8eb0aa278eb07425e9348a17472bbe5c27f5a8051a853dc0ccfd5c3f685f13ccd4c7efd7c0d1bd3f30cfcd2c234fc5f4a29d24b5f0de189f6d67d5a7f0a77c48e1b9ca785d1c02c3ac557cd2538f11ac84f54fdc5c9644c2dd0dcec2dd598292c602d2549c45282c581096f4b4a00d08cdf503b70b33e37b54fbdaf4b987b72da3501da27b5656979f7f0420c11b354d47d34f86cb3aebf5aa992819b0fae6e8298e599d5534b776bb1fb69b56639ddd2f2aefee859ef4c5b631a772e7e416d168dd080816f4fd409a194df7faed7765a51b58218501608f5029f9714501e090a281889343c139186a783736bc48c699aa58307a278acbb86e5b1f66a05c6510922d111a0f480a0693a2251a2d0b98b75edf55113c98962c939d21cd9dd11df61ad460643519705930224aa3114acfee91f2d9232e9c8889f12ac2aee97fe262a7299b7c18236e7650740e0ae8889418646be6239c3650c8de14c6d503763dfffb5a45c0222af22c67151eb9627e2686df42edb06e6ed1ccd30c81ab17f10b428b7dc6eb066b5548744dbc33f10c037b9857b166eee6ed0cd9098c744cbb8398241b38253d37584776b7a5f479db366551e4b586f720b7d00ead598bb921c4548f49d9368273500ad01c914a8456dd005a4346cdb2057ce80cec123fbe5bb3d48520c69b367b8e6d864ab81193fa1555ccc08780686a0370be40cf22acc428360e24f018b180e2a27e9598631272b0bff68a6b02531fb6da3a61530c5aa804575c7573c8709557022f7456c2b33629a26c076c98a98a8452242558c13416ad78b4adb2bd2b972a4d3358abf44f088dbf9111c7ec6fc9212c729bc88767724d99ccae0891bde99eae80c2589ee2b3a033d301d63b22c5e209172ea6b02edfd11af599309227ee974f38d9b77ce70a4bd642ef160ffd76e853d9c1fc7a27858ccacb653af36aa478d9a6f9cbf904d437de6771b70b761da17701c138f5f1a1d2dd0930c9e5c7a4e0356f1bd0e429b2144dcc345511955bb471514771f22a53bb5ca1a178b9229cec2760c5cfe549c60fc54b0ddb8f484553cf035eafe0c508b0837671ee314c11f1dbef267fe402024b97b32b40b954080a3e7323f2eb8e58cb71652bab0c1b246bb912190ee56422e3831b6f3a6698d4051b311192485e565aa53f2eb292848669909823d1852be4142869a19752359346daf96e7e49f85f2e152cadd4259339af1658d6e457bf0fd7cced16c8e686434a2fc5c0f89c62d589da28a96b452660a78d87aa8711faae79d2732592b2bd0b3a560cad54c9a9b086b9046972729f17d53d04f9d32a9fbe5d9d0d908c1782bba71f3f0f9749abda0edb1edce33b191de79aeeeb9ab4f6b7c7dbbf1932d9ccca879c11ede04d96dfcc4994ac73d4d8f5e5701ed1038133feef02f4c01a2c8d3d5b4e767a9b512c33c16dd7dd359f04802fa2e9903f2486ce933a53a9b84348997508e10fa63c50dc57d0accadc6004dd37791cffe308d2ec05ce42c60dc60b4484ece62c7a24c495346cc8ddd71984877f02bf4c338d4ef0a2d4ce2746bfc5a212aea38545c1937008cd03e20b4e81887a20b2d0a6a8a96870bdef54f5e943587d33c9eeca1511962ec300aabdd9167a2c59048797045a4b002a626c624e4da50a2635de6b619b2af32e3e0351635b254d4870607f183290cecd3271a8835433d7d10d129b194928d6a5c06bd2e94e589e4ca8ae13c03d9183a63394e3cb220787b8289aad9c6236d35b496023a27e85ed60a14c75328b3ddb69c7cd0ee4c4def6f1b7289089795a029bafe62f7ce585bd3cd1814cfd090cb0c560f7d370d91184594cd06fbea69afccbf1f1c20c31e15c1783c4b3c144da257695487eb793d0041d88b8f60186bfc9b607f2a2446999673216bd2cc7060ac49b079ca05bbe83f83a41dce6fce9afc7b24af4d55481f59de189fff0535159aad329a44a05626ebdbf1d94d18b2657037dec74412c9c000fa5ade20e3e01f49376b6fda09b8b5de67fdf22bc3e040e1efca4a25ebf80f3d8e03da89ea107709c74a49082e39f7944784c2a29479a83c742be426d4c95779174ed814c5b3a66b151ae1a52ddb3355aa2a22cdbff22ae7695c30090c2ffb9b46e33a0b1bd500d69169831b46cb5b9673d57c01c481fef575d16dd22ff5b558de252620cd732e68421f18fbd2c2e9a3624a981e56fb684f0e296392b0ef94ef77a13cc5b4a2eac92d805d51f3011019024b492496b40ea0a2a8b318473fb9a6221970200b4311c39cdffe6832770f85569c58da0be34673e2388156f074aa42edd75f3e468273306993682db5767d3711a0d14ba75655a4c64b9061bb831b6e5c2d0c58872fdab87baed90ef512c184250ebafeae305c60f020fb802c6a169db66651ab9d677a2c971c9b71531809c1c1d0bb6b59a74c33f30e2b32200b2328513dba98e30a73f4d20c3c18193dc02cb46e52684d3f209ae0d801de4b70167bc98f5ddf0885aa2e28ab142cfbc01c99be142caf3b3bd8e8b5c202df8b1ed4fe24aeaefd6b0420ce74816c33135f48233dc4a011ab8dc4cc13fdf762bbedf24257875e2e2d15818a38cb6617283417a8aa6c6a6c36642298cc7f1a17b65ce4902eb64bf247cf84abe02942ec4cbeed68e0b9cdecb995a32ec72683c4029fe4b2d82e946d458721ffa229c887196ddddcce7e20f42f24fb7f012c40c24526581348d93aa66c09d96d1e908df8643270af9e9fd8fd613e256b8b7ff340eef5bdc6597eeaf034621d3137ce4684aeb817bc76a051dcebf2691a296732082ccb6120336e69948ee703f4c50062ec8392a5adb464c004ec477fb56f457a5ae661068608d04ecbb556b577da687a7e67c73627c4c47aae74210fea689cbca5da2f031b5e06d625c1430ac87df6533504dc9f0c5370b383172d82d93511b9d759c7bf935d5230c213962a52a49bcf748044e59be91433e2ee556ff6685ab8fe3b938be2d57db7285684e1e0eae17fb4a2d6ad272da5b729a7c2517ab1e0f550c977ae955c16cb20bbbbbc5a26a2798f5e302ead4183265370dfbeb229a4ecf2398fcdac89e9634f4c60978681e5712a064bb8e2b07a43ccacf7ee08b4af294d8f986bcb304ff82a3204a0a487b72a8f45bd8aba3f8a2333137420ecc71c2d1692e24dee4c63ffef09b67e1dfbcb222422bebb4e1009cb8e2d1463a353529bcdd97bcebd6da2f11c86089e3f20c4a63341ce245ce4ab3de841b081865ded615e8354b45a77bbd8704905a06d50c1c17bf0bb072fa79fa07617bdb52f5d68ae777e033d8ae8be6829de3a20183ee72485f73b921cd56b5a6a3ba036a1e1c4c89355856c4d4b69133ada1997ee7915b0e6e73677a1b48bbefb015595566054de1ea727e2d6513f74c7d52107fd901691a4019422bf1809938bfcf6ec19ab951431acb030ab3dc52a071498643d027a1d41adfdb31dec49acefd200401d66f30435fd0a757f15e7340f89aeee55f573e065bbffc9b3d275ee4ed7862f610d47edc0997caa88ef533a590fc52f082a472957c27cdc09651811966a5ae676a1250a11f912080ede317449c7c84ddfe6659d5d5933c22814764fce43e42cae02fdf3340f4d0a601843d8cae7219a9acf4314d4a5cb93c2eddba4f9bbc9bbcc7effb373a88f34a5bf444435f11eb76f3934a4f404add9282378e3dc15b06c008f766fed68d1e15bcb27a976bd9fa54ea0201d986bb5317f40c31cade6f4b4c203aa967d2871214ee5765e88c8b4b6401faba2b4b2302c0e529385d90b4430c657b15531b57f4c8e3373b01c225065c016998af985014e5c5e8fe99893b300b96ed219b6b5468e4ef9a1f0b960456e299e138695be55514f1e6ae3bc8ef5f525b9884aed0674da427341e50163e1da6e72a2a2674d8678cd01634473defc80520b38ba14dca438d0617ae9642afba9ad3834305f7fd74cacc9ebafac2f4c74d3bd68ee6e827179f52d9725e6599d2f0d79b5668c53cc76a502e1223c729f20893b2cbc85ed5c02ace565c9c6e2d7161e9c96a6eb1ecdb01c9f549f260cd4884599906444059193ea5b27495be531a64c60a375d1caf782b9c6e87deee7a2be5754434dfe11dd10ad41374608285e23ed834a44e2442f4e804e885b556e7a41a3684600e6bd80bc9a21aba2f51024dd1f6491dde1fa6f198ccd8f5ce8fc56d8af9c6fc798adcb52e96572e90a051e1bf94dd36e4bf40d851920eb87be3ed26ac19d32effd0d51947f1956cbfeb13b0fea0e74630915d1f79bb786363b3637f9f3709f894cc5513391db01cce56e827f34738c26fbb06043b3cfc08152ba04e20bc4bafcb94bcc791e7560f054820f3226c58715957015472b5aeaa6bd0fe0a0be6b328e14e95a1ec28928d1d3dea3d85b07d9f2a685333acfd0f4e4f9800a911d2b83accca03ee80bade8909cafa71bc4a738b05d56839f7681b365070707c19a06900a71851ab10e5454f3ca17c793b41cb38e1a70bd96a2c38d9d84c3172032436bf3e81030d26d35c2b1de54350d7f18f24fc4c70898a8ffebb14d378a256defdfec1fbfba3e5bc54472d344fc1ef7b392fda337fb208aa4acba81da0b9e03f9d71871cc1142e40f09f57a2afe442b077d05dd98921afa5289630a4f699afb81ac776a99dcdbb919728241c8b28701b5eddb2aef8e245b88407bea83b0ea893442c61370021a03e5048ea818f2e90f81ed46c7cd0e25827071b00f4a01223fa40171e1642ac94c184e212486ce299b2988515b187ca3ad1c9f45d85bea3fd4705443952de219720fac7e540a77ca02d466980e21dd140d2927d38979029180f10de1c8fed5d368ccc6f401978475de5e12091c62d0149f64c615150fd58f30407cc2bb3923d8da06da6a8b5f52d1529eb9929b32843ea11c5c8baeec819c8afc5848763657f84f6dfa63fbf050b3fd82485e49c30e29d917c29f5946fff9e6b80897fce88e0688ca7e2b878ca1e44fb4b4905502d0458041ce4cd42f948099266302ece1ed26d686d49881806021ce9e148021c6b21d0293424136d047df2ab35930927f1f41c73f42e9bea61765b54af114dde4b3e26234ebf42bd1108696765729a8927443bb53311537c043102fa466c854a4a4955ae5800c7d479abab134b365e59e9c72e83c8c8ab35a728248464794c4858ad14339ce8d4cc57b142dda3497775dca2c1ba3412840d623bf28f6448b4c2ea5d1f283948cb54924b2d423df8761c4f5782c3b12487582925c3a10153ea7bd91708e9196f57115ee4bb4f19708bd23d391a472b981285e486766209210e2c518ca341884bc22cc84267e004cdfe15cac0d7a1062ced52d1d44e1117ca4230a6fb57bc33300f36e9906b476c724b6c13c72ffaa5528c0b5b45764c747b46715688ca847a7153213de367f2b0c7eba016e299455b69d31e82bade1a677fbc3b702fae29a5689e2c90351cd1ce808b12257a0a81e02d7aa7e65f88953bf44ab5bdb6dff66251830ca3f25bf540dbac8dc7b78cdd7e29378071b040de96448ca954ebfe545842dc40a712887f35eb0cd32fdb6489e6cfe715c0e903e8b212b7ef048abd9d66053fe02d60f5ae16d1233a2cd5519050a92992ce98aefb72c163e35ba664a259f6c17ff66fe19891cfcf0254ce9853a8f6884c068031b553d9eb990ec723dc06ceb9419689a14aef187d74ef71675614fb9d753af8feec9d97c89abab3ef8f450f55e13ac0e480edd2f0fc0e53e256809d011cf6b854a84eed9c432a33f348725593419df15eb381a2e730121a881abf7b29e600d6419a008340565bd7c866e6e10a1ccb6a9049ee9a94d1fa35c5c19720b6e25f54c61821879078a03ceeaedc6362bcd0898557b693f587a70971581fd0518dd6a83d2bf9cf1f05f2b30a5f0ba290f83849587a3b795c2d1e350b25e273b5953d8ae5c8858743b5a09d0a2e9d5f772ec8f1226703dd47ef160622d3fd2b5787f17963393184a243ee6fe0861e8d48cd1f97505d6701512ef41da07bb277bf79bb973027fefc9c8a34ce27aa50a99e207e54bb40970cf63c88c8104068a675d515910d24b777619dac0b09b022b7f8e6caf8ef2c2d7c91f476f89de58c90d4c611143a7f772ca2955eb543ff0d7ef5bec1d40cd2b6d4ef97926ae1b98d709ae9a819389e26445727323fc7664bdb1c8f5bad2ef90f1dc82ce6a4682b7a9effaf169adbcc3db015b357fbfa8f9e773061c9d42eb22cd795192f4da985833de501e775f14cd58af70cd2a2c1e71849602cf265dc8a5c654411f73d1dce6997c1c6a02633edfb26da42c6a7006728ab1068f80141fa8d5f9630084026b2524186b34ccf9e3fea705dde4606a797e1c3435a1fbfe8764e4e5ee211e55cd368926eca0437fe0040fe3bf12861dc21bc736891b23112631099456512cba94706e56f50e175c1153d36ed93c85ce8ba194161efb5f9269bdf86ffa35e626f65655e0e1bbd9becbf1853d6bed5685f3fe1b2a08c3be820f2853b5c9e83c66f28530d135641343e3b06498fde7e68de70837ca49b801d8a91e9c8164e23f964979e99766431b86ab2fb4785dac439876afc34491ae029c7548401ee818d7bdda9e00a795c303b679c9ff49ea7d00f5587c8faa59e6744926d643ad35d84e2f8a67ade9c476dcc8c19d56ad6ed1456ae4f0284815427c49f2277f59fb1faa29c6500f1baeefe9cd0df345e1a66bacd6adfec55cf2a2d2383816b48b6efa53ba673f121e915511ebf3ce246deea71578581a904a5ebe1692a8b71ea8fd9635ac82084625148c9fe6698191dd9556b745f8214b257c41a3962df35058ee57b6dac70ad887e76b6dbb35515cdb62c320d3b8f284cf94ba2e53c8f8a6cb4ce4a49b607125112ae54105d918f8b80670e8ac050a41562c3c73436c8a928bba6694c99dd5d1bad666879203eaadd31cb33292999a40d16d26befc6fdb6bc931622ea8fa0333843a397118a10892309441a83b82f84ae0fca6987d56972dd553229fe2ae1e48a80b6370d030bb131fbdfdc1f63437bca62fd35c4790ed5ddf0f1b3079231a9926c135072c58740cefedf2ad536922e3012d7482fc2ed5ae6a401c11aebcb0dd28a0149095ca1f27a8c13722c782698351ae6f7819686799733555a5b22029891d732a365be636474ae59cd31dad8c2972e42c6ab1a04b0a43a9bdcf1e6f1edc8420f10b7c2b189e148c60f05ca77d8ab184e0ae977fc8ebca2c4ccd8d7d03e8f46a4f7797ade07877c1459732a0898f62f1dc0476fbbe4421c6e7dafd02c2b577a4753979b583072ac9739b79ee952aaa965e31bc34a7ce2441f7605457739c0ecf54e8cb11c9604679b01faa0191869a98bd942d11ab993285655dbe54e2e920ed5038c202d18cb7a9baba3dbe66e2dd025ec5685e2738e3c10001ee0f0f548a71e4b47890280b675a028de6f66a8082cdf4a539cbdcf2959c1a2dc148efd55218fce24d410d401b64e5a43a2bb4bb8876c75828931a1b86da756dc2ecae9c4c4d22396129a76274c92f0df2e88753bef5b4ab8fa9a8ced53e2352c16b9b005ee3b5dc14855520f2f59fe210e0b82351c0517c1d8d16927c681b2c7bd1d42a6962093373ae9294fb7cf176cc3eefca7eb2ada239f4609bbae5bbab837ef2156d40c830203efcd2f0e289aab0a138ff68540dc0712551149b4586f631ba07d6f85004ea84b28c1cda635af7c3b231733798faee680fb909cf1c6f9322da48d17072fe433b64c60b256950312dd51b932087bea40ff0ae20ea468cad25d79fc0ed37b3b9305f7864b111ba58cc4ff7d23fbab72170baa458590fde830231d98caf2be60abacfa9150243bb86c0db1504281c9fb9679f33eb505ed5204a9fb7e5c8b56b3dd3788d328e2a9d15b445f9ff60a133f4c6c319fc1dbf886d6c98a2097041870447cdd7738b7c81424106fa275153a68206ee7f21d92da4153cd83c944f827cb786538db0334224640a096050075a01044200eb135ea712472cf3fbf96a67d9180f6ef247f0f89939e90b159fe1f3786adfdd6aeca0e092c224b8006b17751584ee068b5f5695e2966b684aef5b11aeb8a8fd2044275161210d9acfc8998ee926e7ca16cf38eee05303f39a2bca2f8e081954032bb78bcc3ab956a5b2060648fff8b5177eabbb704f1b5555b5de8b80ab10947cf52c1909c7784b8239e60d25455f35de3fd4ad6f6614d6fad5e596c59cf48c98787491785c7725a7fae719da7e6bcf43499db4b14a3120bb794bd99f31404a139f93b8e9ffe6e401b7e200b3e4c9f6d32310473021aeb814e8916c3ada7a5e82ce39246b052eb9219e1ff4d5a00e37140b96dd9d18fde32874334ae6ce2a22db0e31318acf98f351ab5b0e4637282483576f3906ac7c90a40d2a7fac82d52e208be812eb4e05fbc939496122560a7be667e82763b7d60d6150c7f09e10c14ac9c62cf551c45ce322e62fade950371aef2f37cebcb8e1ff09d302d90e9b85ea974e892d929f383ade43a8d7738129fb5388e388ce8a429783d6602f1c1c6cdb7128cb14afaac30283d87f5745aae4a74e5a72feb93da962033d6f4f49400fbe0ecd5bb40adce3f8fe41abec029d5eedae924d0da2df33912e07e09f40d9f998872bb06deca410f84f025f99f02af712e86d3e5e800f7a80da30889e99d62f5887d0410931c807f70ae12790ff941721df1fc85f92a6917c4653c41dc00c7af993400209ecc4d0e8e3c3d743aa198220e45935903411f05e4a033024f88724ea8178fba0eda71999c2c00f2bf73c888875bf17f96c824604cc1688c602e13361145d9ddf11cd5f1edb9e71f5ee2def31510c1578593b75c86c6585b6ae3f2e89d822507a1f7af736133c8d1ff6c9f89d3a09cd2e1ad8dd53a91b9970430699a8da912a6efc3c19d82fa8091e28d703c37d8d2293a3d6baea2445212e8cb1dde0ca857e13257ea888071729b78a170b6b6a805335edc1fe09946f59fd029ef046003dcdf9b02b2272d82721cbecd40d0a8ac8ae0b4e9f4c71c63076a3faa191aafbf096b33ea760bc74d5d78f0826b7b03657aab5b67d9f2636ccfcfcea5d9fe9b312e155a8d0971a78288f78b6a0bbd1384f40366383733f59657cd3151592a4d7a6132cd704c3f025224170cb3172fd66f49efce417cac2ea4f2882d651d3824f9a0320b493df7b410a38ad2722032a9921d06c655cc0c2b5bc8a6476b27e132c237d22d5880275f6a48444be5584281c0d76afacbd2820a766209cdadbb523838535cc5f7f69be0f34e59ebc9bb6ef9327a24e79b60b0105382b5b7ba673ffe1251c2995cbef1ee3d6efe8251ca5e5deafb2c9df5904a401033dbd0f3fb4d09a24b9d5f28b29ae396b4fd7100b6eb4d50e62d1af29f018930e0f5809082b0abd17f1eb8baaa0211f2bbf6cf1e2a51528b71bcac670d6df28e3618c8419dc63d4d5bda899e4bd4c004f13e7bc6f6b88e287de654a32417a8e273a90c588404653687716d675302d3f58513fb6b293e9a9d0c6c868a99a129e3627bcf25cb531d7a79d5aab4413871c8a9ea543d5bd16dd3b811b6886772a65c3bebc39d4e59378f1d63a2f67d48c6dea1f6cfa455dd8fbc38e939e2406d4e7130095f732b307d98aa190a4a32a92510592198e817211b43bf640b8b795b262a9a6274227d840f7605b33181316faf3b49fab71ea06b846f07f4a95baa2ea6e24a28c514dfa730b88da2c698641005672ee338672213a88f17441710a9858c9c64d6c7755689596291866eaf2d7c09c76433cadc46400fc522429928a004ad3f748042adfd9513c207c0208048295808ce7992216693e00b57b8c4bd24614389fb2c86feeb60dff532bfdedb74da93f268e4dbffbe3c0db41aa8ac39399b7d3b0b60d8879bebff4edf4cbc52e84ff592521a3dfc3b01e98d42e1cb57d1fd6de43e9f77534d6b2922a8ca937a0a6bcfa4c70be0000da13c8dabd6777bcdc073e0e08b95f3a3efec6cef025145dd1fa7493930c8561e121457a653ff46c225f1a56edc767b0e61e236c3529f19beba65e2362ccd2715117aab698829e1af7ca222bc91bc101aa9b0a5012ad6c9d4a35dbb130eeadaab51978c5cd5b04f0a4494fc432b1e485fe3c4b8c0d42060d5018f79b3642e4bb017fa604a00a25016e62f72ad335a8ca08516f516201b7d116571092a6413bfd03e3b1714d83eb6f2aceff06eea44745634c4d44826fbef2f7f372da0ee6bbc34cea1033ff82d7fa2a831f0067ed02f0f1a0d79cb26b4d0521da7259f234b25b8b5532c12659de38f719861703dc9b4cd3083324e0478259e42483a61d6a44e84fc3285bfbeb65c2c8e4960dd0feb02655f7418d81ccc99288abf0486318c8fa04c0d82cdb46adc20600e841e4168baa44f5bdfe615c66395e141f76d45e31599ccd9189afb234564a1a787c5e1395ec8cdf4a3a863cd134f55e49409e6f4446d3c18058fafd1295b2e19d0666b4fa2a7268932d307ef6a9cc86e1ad21da32942b1593c2c3462f9d1ffb36b2d88683ba6d7bc918b34cd5f1d412cddcc2f8228b34e1d96933c6c39bea970472655c53f977348e1412f7d0e124b478cecbd58f4a5063ce2059e9536ea47b0e43172c2a7c76a8e7bfb3d84327cd7ca671c957cb5c8b8d14530791061faa95277e8410eb3bcbc6f38daf0d97399e6467bddaa5c55dbd309a32e6fce3ad2e8940f2360dfc8f77b194d2381e55a9a4823a2ea3238041a4238e8bba966123f548a933c8501b10bc6d8639be1053a756c20e46310cbd5bb056ac50edf1ec389f8ec1c041074590c964a2122ade63a3c7857efae06971a23a26a0b18a4cd9c2e94cb146434a928b73a7aa6f71001ae4f61d0884ab43299117bb1626ba9b503f81a302380eb1c9275de3002749e05f112d29f5d476e25b9f97b905787cb7c70ea14f39909099a739c17f7b532216e5dda5a7156e2d907722b66c54ab23a69e0506039a33a18a3abaf859e19e1609d7b3115a4a4bcb6e690e459150b416a587202d30bb5395677059bfda741626babedf072474110c7b8a843a17a216ac57a9925142b179f89e537d0cd57f3739f9ffd50a6f4c02ac5d8443fddc2aaf53bd9f0fdb018c53db11a97405ea87b44b59c85014fdf0a398caf91c670cdb0805bcdf1e741ec4d36ab77e48531bd6afffbb96e6218f79d358a84e313c7bf4805660c38f76e0aeb106fee5197976f2f9cb0e6dc90e1c35fd09f9c68e5f8e3f45d8115ec6f8fff05f772c8dbb5c0d44f4d90ce03336d579bd68d22101ef72a9805e9c7b152bfaac0ea15c8f3ac21b1420c820d9dd580163fdd743b0ebe9a26bd0d6d2197296225e7b18f3e55d8d26538c5728edc98dd85567797aef9298e00f13c8e9bb80a2d956c944b42de90731553906f8f41be77af8ad36e7cc4a58f165b262e9b30466e1f817175d3a1d9dfc36ff68a0da3121ebb820ef72888c0331d0f06b1326d76979d477ad0438ee97710cc1c86a03a21ec778c4bb7b18fa9e7c854df32ed9658c0734da9507b8e628f4f3aed61101d928b65bdcc84cd9c70ed896190f58bfb1be0d489a4b52dc003987a0b4689642758041032aa394fa7b8e04b3011a01c11d020944b2cdab16776f0de19e3fe91e0df5af3b067f760e97245383f4d217e44a815cac4131dc5fedd83e89302d9d38c2997ed03f868d997f3a0083ba568c8ec8cd91082f34175f74c9c9b37df2627262079eed831048b345613f15c2ba1b20bbe40549874805d1635716205574248f2001195a9bd87dde611de01cccf96932a3f51475930903145ed5a5c481416fce8ae0eeda25e966034254c8624db7a5376772e735476cbb0ae36264e044f4ff53f3870f21a7199cd1764918a0c628b054e915e41fdf34d9aec399197827917b2bba39c0471ee85ad4c56d1aad9f2aabf35eecf0240817f197d0ca5844acee815619c6afc4df5c393e8c306f5f26aad7528f80321577e391950e62d8679be6e43120e186df31a8091719dd077c32d82a2f7a90a2ee14eeacd131aa6dec47637b1ea59f52ec76e63a9cc1200f666325d107dcb41239f72f0ced12982d2b6cea4f16bb6f4fbd6aed01f661c877a80610ee1007f5e1cbc83c9507725a9068bd3f8136bfaef15da0f27966aa17cf9afe610771f678c5f70f79d0bb5bfb4b693b9ffd0928c0297ed92f60f06563a4fe924757622554f32d673493135cf3a9ca668e7ec46e23ab4b01735857d18a6bf06981958d7ce7829fe0c12c34c646732fa1ce96141a65e5b99ec6ac4b8c131eb053b459b91ab9b09cd1a223bd789ae6ed3e1cf17e0b2c76b90d34f1cbcd5a002d8354ef18c01cc9dec8dce67e989119841dc514f427ec2d505453fe1d4a932f46bb1f8e7d9a65cb9fecc307362a5d7eafc3e4605e7b2364673ba95043a78cce173ac38c710b2620e06c5370120c15869299bc88b48eefe98278a83d35c46168fbdea497c34c613b31d4e9cdedd4628c4639bf2526fe1121a1277b03dff90605746b735eaeaef73fe08ef8e7e333bdc8e9c00c245840db48aa3beadf5538b3ae43f098870d05125cea03bd43f6cd4c055eb8547bc74ca32d6378bd0af58a846ac0965870a49fc74a7ca9f4369cfad9c21870220d76c42fd4eab9ac029dd336c0b6afec4f3a48c58551d04c6d15b9bf18fb9c2c2890ada1f046b88fec1391e17c60ff0bb9bedc271d6fac69610f17e8ea37fe74a176c82702f4cbb1c1ed44071421a6e296a1945363ceff1ebdb3943e72452c35ccf7765f378c4103cca698204c23884cb6048f4576c7cfd9de75667441d29740018de41d9e68004915b9a715dcb142480469f44b298bcad5d7f50a110472c98efa412de53ca818e04066d57415b8ca81013cdb59a006c157ea511efbdfcdc58d86cd3b2d4b96b209a53591cc17e3cb00762d5f62255618be056110092945a09310fe890d789b2c106b8b6c8abd6b54066246732f7e5575628795bf7d4e503358f93505bf1c30f136a43740e99322ce49bf9fd6ab4181a0f1fbb2755d8d993d3827107d6d6f8cdfd7691de9d8ee7f96212cc5df2af3bf437a2663719d788c39bb36a1d9a59a7b5d7ed4428581c248863bc71c33f3f8a9064a504f41af47904373c583e04b971b4996907e236289a21265b375a329fd217223815c2182639a4c54c03f038f01f2cf302b8c1df57710c4e41e58a8dbc444ee2cdc05aa4c168ff056099f7d1a9626c762e9992b459eb28c3f7db9ef9d4b63160422801b711f0413e0bb555a08336aaccb2aeedd111bb913ca118319b0061ca2bde5d6bb509adcceeaf11d2ed69fc6874cf7fd4021d8d7c8d7da0c3db868499997379cb22c41b8a7a408e7e5c1c5450835fa9e10d3cfd2fc9740245f260d4a0d2bd85cf0292cc27e7fe4858cddebd82137957308175dc04811a349e7f85f2c1ca58dfb9e04cc843b66042e0b40443f01f505360e79d0c0d0d7493ce4b4e7c2e00192cfd717b0b39e5ac2e940012072d8772106cfa90d9bc180a1c6dbb3ee876a19952000e51dc6450c02caa5ca0a11a12014e4239947749683af4f55352056abb144395d9df4d8bf67d4526925451039bdda0d1c744da5e4575145989108407320efdfe34a1165aa3c4608c0bd1a333bc68ea071363b64f8e228a9734727b43eb87df8410c0c1b05ea3eaddbcd7f53bd179817454c7ad158667143be6f7c025410386475aa646816fe4d30e6510abe49c585a5e9fc61e6262a17ce2b153c192ed05ac637a34db89f52a7180f9e0022be561ff29bd901111b5f22a98fd0ffb698270bdc19718833fa847c615f253be815c2570c5c9f87fb061cc63cc50741a52160eb2f6154dd41a74658a480420a1ddcd214acfa5f54f2dc0f108c4753a11d7a9b8641e7c9fe3744ed3d183a6b888fda6417d1f27c53cac96ec2cbf62c6268b240234e4283f07323b1a7617f726151f2e16c668a78cfc22507b1a164f81e2b0746cdce2d0db3c63f04f82456189b9469d6768dd409c370526c6d3fbcaf1af7d8dc7675b6bd63e05444eea6d5e41628db2035349dc171e3af02be77f81a6822d85c29bd5dea00b6d9e45f2afcd18f0622fd179c0f05209da3435174bb789ccebe4a627f11a95692eda3d858310b7a036d4a0eef435c74a5c9fa344a9cb37983b97343b4d2f50a5a42bb88854055d14828fd99e672694f309aa168d63b7a1e5b8441d5f799ae4fafb4c7945d8cca24d40999fd8981889073c7dc3bbca9b060a0ca2ed5c130920b3b0c03893bb8fbd3b5b9a79d92bd31cff9a8fe53deca91d0f3f5c3f7deb1221d58a894e0ff93eeabe670a146b2f18ac119a02b2c74edce75900eb87b2573e091034eb1c8daf9c7a131de8429320e6d3e9116e789e4a53fc619cea1e670b48305bcfb27d8dc05452e1f851c3978d076bcee70dfb8620318b1fe7c9fe3ac3305d737f7f51a738a500a392e7c8e0f2fd2dd7930e5392daaae1ff131cdc3800f1bbd40978907523751d460f1df51c0e8c8b51d9a0dab61b7f580489eeeac852ff6e8ffc6f468e8f9d780a6a650e9ebc3abe5d797e021a0f7597f2c6346252b7e8861070c1159adf9955993bd1eeb7d0ac155864f4094f9ac658dc85f2f32cd9eea110fb58512ceb393c8ae90aa9e31012d08ece5927312938ad0669c0a1230408bf6bafc395ff6d965c0aa9b0687a66639093cba3c5e8251e5f1fce2502da951447c43d5d1acf9985260f7a092b7a5b3e929375e635bd6a8829a1a1ceb4768a912f6249f6ec062a0ba9c0334b4b0017de97017fd2a7c2de60ae95f3be46460010ee4bce35e1f7ef15fbc0ba6970f8dc202fc1c3937fa6771c8185e65fa062b7e1b7f9a2ba3e7cb829abe5f5c3a527f60fecd62d271dddf1b7ea1c4ff19dc0dce809f26476b9f37b55b8f74012f2352683abb408053415a89e912a48b399f0b7df5a15cfb7436c94ccbe6d7ea3c94e2464e51a7e438a5ddba6f93ad9369884be0034d000a401ecd5b43a68ae644e7761f535b081cc420ec76b50165d94ae85a8e6da6892538117e41ed23cd75272cd98b6ac35925b03a17492c23875231abfc0f16f7b90c3b58f4f92d8b361d31696d704bf459abd1cbbc3aea66baab105f7b49b8bc79e7193aa07e696442f3a327c5d097f0bfb2f6cec225cd137807a8f87cba1b9fedcc816e1a0bdd6e2948f07e2b6fb0b71e3cb8886f37e0bea940119a8208ebb4d06106338d04081291cdd753b1eecdaa2270c16b2093c71d53aeff1494867d0c4bd4606785e24b7ac400301901f4209c54a911466b494b4fb183f347178c0747ff1dc6432d6b1093dbb36e292cae9e35fdae32ca9486c3c0b15441ccc27a90c062c03766fd07d77ae2ba283ea85f829e46c0231099d836da229458d5c700d3aa092d510b2b4ceba1ec11650b985ba1709604ca8804c3d3c18a4811fc433980e7d99df65515c5790f10410b8052080ef208b145ab7ea40214168461ea56d93d45a8426c30cc2699e812ec620d71cddbf31eb3974c35025e7acde5dc0ef5ac054a6e30841c805986cc1a42d1a501c0abb8e6934e1b56a40d70d0499de386a66287de65e84962c86645ecad5379ea2882449035d512649a6f6c88a91085de595f4c3b2ebab803d7323aa00c0a7358a47d83f2bb9094955494fe6853495c4e52133840988ab8a72b948578afcb2b209c126a344d56cfe56b2ae33b5b89f96c7d6aa35b788bd06ef573fb71a06075868e4744af71a4aa0b665a4fe79576219e41f2ddb8c77c498ad2167f11aca8c9376a0a199cffa877ba059bf4f461a1373a93c450293c3300103d08642af2b8c1c1ccfbe3fe4442194786d3ce3ca22015bc59259a5ff83908dcbaf623efd2c0a908191fff58aa355d48b44b1a82dce31b36a3abf6837882daf5047d4f5d546677f894b956242799e3a525b7ec3e825f4e003055567cbcee91b265e040d82581a7c19f7952547b41f157e5752f88bf60c5fe62ba56d0de6f45d130007c4de68ffc66af75ea5e89235a5f4b90a00647e79a3acbc9a0e33691e4cb1f18d0a7810442b49bfba1eb77450bc27fca89d6c3f2e631e3c2d040cca1f5579891fe1752f493555ccfb29e675fb7d5ada7e02c6d026d049a3534b342196b075e135ddc6078dcfdf4422e62400d5830233ab6b24105ab164512913578343620f3713d31a6f014b4850811dbeaf506122feb2041250a77720f52e746a6002ff074c9ee00829ad2ddcfcb8abe4b1d6d9832f9427673e9b8bc63de25ceab83be3aae9778918a1b82d6549683187c900bbe43571c4dc3673c480a3de7c9a8fff20bb320c84f2d20ae2bd98dc9cbd220330e27a78bcb1ba279b35e39ee18806cdfe7d7f6e3af5bb03401362a78d994227fd90ae65a5d63996280d857a8999b02e215da8d623fa12f3444ac98a94831d38b07e90f2cef0ec21ef7edb0e575a855ebb0a5aa777093bcf435601b82bca2b22949166a751e5ff79b046b76d730b000dcc8d75f56e7380e810bee5775d244a05f5b8c9568504f39dedff95b53ce24e68fa1e98af8cb77e5631e6dbff8f3deb62620b04ae1561aec71c8df9ce27bb7b7d64bc0b8de3c2d8525778fcfa4252e51ee6f1f366889365494a0853aac874dcde127c9941911b8606f46a9cd6a85dc027b7bc9cda337d5bfb04fa7bc193d2da402cb1e9c3f3d053b6301360a3806692be13812076fb9bb56f519ce815cca7aad43300992772fbabc34a7c2a30dcb375080de4403dc5a109d0ecc2f8de6f19c8b263398b839708952cd354ce68dcf2c6561110235908d65ef5eb404f77f97fb948b8c82108e41d4337ca53dfbe15f31db3ad487386435507d773e59f90a5e5a1a3651cd45b02773d8f71e4f8064efa3a902e815e1a0fd98446a1491f84d8df6c8e384115fa6d1ffa3265a184d3b98ccb21e885831ecddd4ec90663367c0620ab7810a0dcb19f101baeec58e171e4713603e69ef69c946a041c7cc1ecf088ea2956b55c8a9ff8b619d52cd08da70aed2f47a20fdafcb5d4f8912fdb914b328670ad64da9b0d335190d3399de7d611b9c6256b3bb128914c3ce18b34abefb09db252f2107fcd82efc9bc30190853c512156166a03f52a45922e9892f7c017e58bb3e8a7f1f538840317ab8bd931dd20cf69c64b4ab6af0cc35c56e786ead61cc8ccc8bb3c3c6bca3a0808ac03b8822f4747141704e0a783b94aaf7b16327a9787226d4f90786a832c5f2ef2e6b64111d9015073f4c8d67687a45db95651307ee1df2974ca5ace5481e1f1ea2bd6e4e41cd4036b4097fc8ca3467e030e785833f87654bb53570c28f61d52ec5aa93e37dc9f72ad8a2a30058319f8abeee5846d454267c231c982bd9a760a09dfb7824dd7ffec872384b01efb78653f9d84baa3e492c22a741d1b90fce50e79a877a28b80eb23061ef2c3b623cfcc0cb8a13c819cfe049b0b0d376bd0ddc1b8d2df6f249735a5754b6d16b42607981549d74e0538d751a716192777bd23357bf88fc3141d404bae76ddf7fbea845d6c0ab2472301c1a6d12274f3d662d91f3cb94222f8cc10e161063d204f11cc2152ae5a6116fc294cf47811e202a8296874577f8aa65c7d838c4cd9169052fe26a6382fa9f4ce52e92a659d41b6312bd1b50d2860b1c7dee8375c43ec40900223d9e35bdae127c9b459ed75dbe4f9caba443d2926514b6440a11362a04ee0cf73e407280e6d607f3e5c0646148228296f92815124cfd761bf584c90ee09ba17ae0d0cb94f414a65808a15360559c408fd0cc8bbfce46765eaaed9a4e726de66914211222d901d6fe45ce9b8162f1f60d9a90b31dd0d49972673afb202fed1ef043630776898c49ef67adab577e509a0a1a6f28f7e3d12288332ed022898c6716206176d8c9a61024a9e442adc7adb34ec8046447b1d0af89e0853c9491e871012940af7d30558190e150fba91bafea662a298bc726de678e109046cdf7ca2beaa64534234650af2c3b3f5e39580f8c8288adc95090e0a7bcfb26a7f1d3cc0c815e96e2a1c24bb699ab000e8a08753cbb53d45367116130238a4daea5d766deab0cf1b75376b3e3790f21a524a274bd517334da91689bf73e3cdda52854175b0e0d9058bc99101266105f0a24f29c8ac91420af246a264fb2ba2e0ca20e361414424f9a4529d1b34ddb9ce7af76f6c977df90edf25fe1517815c1e021de8c90025f64a97a0722d9afad4ab8766758d2f4461bfd50c9a91f8ceb27952a466a7f01bf4aebb3ee739ec2fd7e2d0f1eab3336d22b5a8dbdbe802362b14496f6a6c747ac0ae1dd13c1d084eb33d7fc1acfb77623477a5acb3db14f36572abe60141fbd4b56ac608bd45462f44d28fbf432647e2bb105443529eeacc5fec45b665f5009d400c76dd6d24a99a7a4ff2fff533bf9e931085aa92fc5c9ed7875032bb3d523bf8851b154cd57fcedfe464e908c1e984fafeff246af3f8082094a92b8d10302f001af04abaea3892e90d9085a04ce7c686dc64c9bae933ebadf1c556abf84f6d6b9fbeed692fa12c6e906264ffce27a9be730150c76ac24735756313f79fd5693f399bf50c823d5fdd8d7230d389afa31f829183f079f5e68822f1d0e891fc03b0bc384550475718205b5f5b1ca2d4b5c15acb571dd3d99de1e346147958693b1945054ede4800f78c357974c4168ce71a78703d63ec9996fdd4e911cc6cf43a941c14e66f2fbd3c0433497b45546e9e244fe17ba133022791eac05f9ae3d27fbbac14e401ef94321f6e0129fd0b936f4f125978873d5d6931e6fb576a4cba0c7834c7a451d8baaa5eb7149cf7d86c43fd55e3a9d311ea3b297f54193d741dfa7b5d024499735d23e4ed0a810018b7437735935ab0d0162850f01b1be88f6f6124892448377a29ce8e4149cace7186409e9cf99d90090b3d007d3a83ff4b652280772fb9f3f4eb0ac5888c06fe1d7745769d312fdb9ed0186a13b03b13d2e241a1e91ff2371de143e982ca0b4d99b74296e47b9671e3b7d90bf9858be611a5f1d433e0fbb5be5000c03405ca82d8034ad2afe97b6ab6421cb6877e2a8000308f393007a7bf13403087068176e16a16ad61a4b5cd0546a601961a263f922adfda6950131b301632e0ea696c1921c32e654b658285a2c267501907930620641a2a028dac050e0d103627a2a6a154304de401721c3443c231988cecf63b6b853ab5ad4c32b4a1086b4c8b1e013ba52f26c96c8254ac201f85eb73d53a56eda2e4b2caf05ca4c90a9ba417e111b6d17c84082370b7e0a0951594cdc1e211d33c11eefd09653f4028ce32b8697b8837bd56e216e73d71669328718c2499b453ee458745c462c386b8f3f129382e3a4c54217bb2156cdc131286d9b68bcc7af32731a8f03d4bf602a4fbf9e14869f0aac5dbd281de986bcd23c2e00a91599f302ff39f6947aab6a329011f584191a58637c7a44f105d6b39b41947686418dd33a36df60e621c91e2ea9e0558073f7a20385eb359ee016a9c697a17ecb78ca760da0b1330a7de30694c6518311058a8502e3862e507779d14149c4409ae8b10be32d70ba038201102ba80e295da21db101ad32a508796f5e0597a49c03c04f33f9c108268c861275bbb0e12a9d57425832dc42b682d64e3c9af3e157aa4408cc48d4f16a59a7f4860950172077d2b4693275be446499064f113846ef55aa755b06cac23d2179e3a951b528866f2872a4b5b599367e0c9625babf1919b3891836321e74c30a7701b2cc68608ba2ffbd986139883a47e937ab0972719d0813605bde34bd62e2106baafa345c03d6a07b8c8181ec883bdea7d6e5aac78414ae6402e608477a9f3d5d2f190f817d8be1a80f78d2a304e01ec631400b92f577d585967ac41f03511ffc78bb54a33cfc21317011a46c1b076f84cc4f06b42fae1fcfaa409ad5b93e5f6be0f0a60b02ea5024162856e3c263758b91287a95b1ff7d6ef443b6df104560a3fcd454f76fa30a5bbf6aec7afed8950e5e899ceb586b5f7e4f112f5abb49705059d4f2339a709d226c4e8ecf57e300d060b4e753e7571a0a71d7262c974dbd14d16f4995241fa9b9f8abc9305b81022f8747fd0ebfd7d49c6700535909afa36bcdee8cf1f8f5e757f4fe51c4d37b2d23a58abe1027bf70c445d4fe540143f0dac1bdd65477e96679efb045119353e2d8bbb87ebef4ccba05460030dec2f105e4ec8cbdb6237badf6c66c009d4d9c048aad6cf8274b5789c809dc8bf4c5b48141010977d55ed8a5c1c6b1cef254fe8837b1d0617638e48474d70cf38724d6d5e7442e4cb0185415aa02b463002d052bd4ee3f92b7641c1a33e609262611486e3caa3a7671e275e84e803b408c408f922470b1129c58a563941e8d4eb1784a614805c5a56e547b2637062a1cda4e737ccc41f1487874e0309c8c081dabffb52347eef0de26a73abb341f976f82ea82297612051a4f8240150ea5fa0b07c6521572b4cb827e4657c57e9e8e08362e68612514b75efc86b805af9cff34cf78d74f7f808d471506be09d0488297c168917044de93c68c2776e578ca6b1108d79429a8ff8c721a520192bfd560ceca52390b16d06c8b649aca0bd9e9f3cce24d2bf1fc6353aca13f95b1a555740685bf7fd9400615e76b6f64221419a31d5f762466068051630aa8fb144f2f15024c36d239a2efba2522a7d8ab84d563805cb19ddcd91c716b476c0f1538cbd034ad0fa73f29ea898652f712d060cbc2717a50dee8a4f2fbb86a350796e8676ae1da0737a83736090944627c91775baffeeed78a33f8478fa68e3cb496e129e41da07bd6fb2c41f17c7c24f803372c542a0ad7e46a7e0d0c753d95e60e8913ce694113eec95d280ad4b55488db6b71350fc6de4d477aee70885b8a22964602ac0351fd7aaae7c3a0f414239d150aecb50c07754bdb8c7e566470a58f38ae4f6b4eab52ea4ee2b6cdf970e2ee609179338d5ff506d46a52621fd44f5cb1550d507443dd1044e7f22bfade45b97690906d19f6788ce2a4383b64a791dbf395d0b1cf9076b1cda679908ee4a7ddc45a14b725101b386b17e493b0ccfdf647a01f5f115273c2eb9eb9aff8e20c7c2708091262211eddc41434516ff09dbc28c36fc11b19c6e44181070af03b30f8bf4fe41e69b27bc186871d360d27b5775f93b7fe8721b28ac5d814875eade81f8b656c69e00ed7eb1819534d1248ab61d52f84f1e004a72caf5c8df0846beaa4e8e599823d78eb383bdaf47f70ff92ba6b4fe16b352a57b370777ddc87ae43f2b9f7b3a8f5c39a96f3ef7bc82ba51620f89561aeeb936e806e0f3b426a4ef0626813dcc52aaafa76fc47a5c2b943ae8594d04366b1672b3486073d7e298e802078e9464a56fe77548afa6d78f996245e623568888b98e57b1a016f3f15d1cb17a2bef10cc2089fbe5a2203f6c505886104be9b8c6c5e7b3c3b18404d5ee37d254851d495a77ab348f0ff4755e0a38081f1fb738cb5864735903ab0523cfe065236f1e6a206dd86effeb3c58565744e24d26f813aad283c6dca20283303724143ef81dbd25b625c47fb7cb478cbdbf53d6391e4a6d6df4cea26900b91062025a850b70b0feb918ff7d43dcd06e9ac72b0e7bcb43f5db7e8be0200c2df9156ef91f7aae0f97fb8463080cadce9d4507be5cad2ab54adc68bc22e64fb3e3ae2ba58f99e8639fad36371b889e7f899d214da0418dbc3c4470b87552d8508a14b7f7201090f522b13a64b765e2c42ba472b5e0395681c8fa2b272207738c4439377e2832a87bb14b6fbe7d90342e9dc69a3d8051c6fdd602dd5d1f197f862a4023426643e8f0b3361c5ec2fb76cf0e47838fed41ff25114f917fe9d96bcbee1dcb3ec976a9e2e55e8ab01abb7afb39eba50a9506845e5ae168b13b2f55f0d9347482ed87540484d3856f17b81f7bf0f1edbc3eef5b7723b06311d84617dd769000aff6c2305767740a4a44260d4408d5ee0fe6d867ac9080b378cf41c80f1cd3aaf18fe8fcd5b15f203da2df112c103d3c4083f9f184b6ad1f22425b1f188cdccc215f8e0311a8fa202ecef048fa9f97471db630271fa81ced5d8f4788d350c0f1d85832c9e7611a63ba30e31db103e2140cc352ee720e4d6201c01a96173a37e3478a59d6a046d5afa24b3473f368af8bf0dac0ed40cc1f7ce3c1647ae998804bb353ac7f65881feb846f681650c856ae1a2c6cbe71190bbe14c0a948ae24765407b168ed6f25d85c22b7f10051d8aead42d4d915b70599c57662a8d5fba727034a91af6d705d432a5c688a0389ff69164787251c00f9217ef9fa4cf00c2536ceb1c039db86ca2ba39f1b63921b941e1d42c7e98f0e290db381b42d7ada32fb6f4b07127ddcc992e2f178a610d5acc487934e0578d07d1b0a5d9bffb75972851df09b982e8340f18705f0af40143c3db451d0821600952638b05900c965a2ad665938d625123f9ac3673cdedc791d7704c3f318a08aed37e99f7eb27bafc2bc49f93446b28ebf749c27dd644f8fe245a0d21ad06b16b3f185ccabd017d1bfee04c4ca16d92a3c2f8fefe16819161cbdbbbde145c0dfd72491f31d25c6b9fc661a5904589eb84a1c48aff52a360ddb3ce0dcc9cf64740d1ea822a08b2145aae2a0119063906e4ecc3706571d595ebb63ea13034012f21fddadc68769823642b863f9d33bc241523b221ad37dd29893b8c5161422e46ffdbf0e78800eadb9da845c5d5321bf1534213fd2430dee2aaa31910b88427e2c9a42eba7ebc52dab082a09142ae109f9f5bc392a22e4574a880323c0132228988b0dcd98faf8eb8698bb6160aaf3c5322c010bf6e1688bca65d5f791f5236bc930cf7abe3dd00096f3efbcbf8ffab1f34b365ec1dabd2b1f859c7c9f062b49ed5ec2009bf90f50ea4db1f983d5fec538dc358a2ce630a96d0040a8af12bbc73b971fafe33d98a6985f5def9b6c8c9cff80b847f6e144d0ccc5f0073f57bec3432e0bc4d7de3c05290edbdec0777bd31e7a83466405a3dec93b3ca3f9a7ced23c5961574568d370293ffe64c7bf559302ea6bd0fc16c7f59d372fca6ef20e24d26a65efbf96721e4889cb4e610520543158643f43b18598499627fa4acc8632bf9e5317994a99918f8c654d4bec62411b76f68680ade5500215d49acb8331cf63018d51fee0f665d01bc3cec895d05bb6cf8f0603014c2a1eca7afa0a2e282b7fdc63b094f293a1b6ca80026526290d98197b521e2b5175ad6723ec71cece2e6f650dbeac74610154c7c1fce1f0dcc713a6a426afe73692097042f0c1b9b7bc4c4e61bbefe2818a979ceb0404fe60a730e8b8df770815465b5d7ec234ef8aeec66d14ad2f25102170c8efacccff3a93946f7350143368afa0bc65de027db0db117978c073317d4838c9a5b140abfd68d9f1b136937a1edd86604f095f1cb094709e6b6ff66212ff29c0ac106d161bccb4aa66f383263bc66c924793909b3ec7e6fca29f9fcbbb7ebedd1cd0d5102f4146359f1cc362905d0faf32a863bcfc016927c1f6486b31193dd5ca7f3de37561ed4e18f14f39343c148c978b5be431c4349b511fd220e726eee022de4cdddff171d956914f188382e5c83b2317ca21266bc761aae7648b2d7a3243c29d96e3df7bcfcee559dea2f52f31654561fbfccab1390e4c2c6a436a66f9a453f8c2af2e647b577788ee386ae6984d68ebb3227edc559781751ea0320e1626b75aff587289ce04a6a1fe04acd230ac85f9ab58b9994a8732bc22c2e25434e69f88b34f68cd3e09a383c228647930c903f88fa2ca6b8fc7f5ae2cf46b99da9e6efd7c3855406ecd3ab83abdf8edcfda9283ccd95d17b4e500ede81967fcd7798c2dc9759894ed46ae3ac9c1dbf228ad8f15742c2b2efbfdb287e574822c51140eae7983d9958e40ca8f689de5361685c20eff44f0cf0923d8f5b6de6d28c2e0239e15dc55ecd392dc68f1063679df00b8178502d7943780674dcf5dcfcabbcbb2853615c4130b5706aec20b074d2a6058565c0b7a12cd0e6880fcc221f9d6c184ad336ae21d71f187bb0014d600f2f7ae93a764722d60ed8c96e12105a8acc09ca0daa53269ab0da10827c5d8ba322b2943953bad198cbd714bd93c06f0e017104a2c08a56cf5121755040e68aafc11ca5d2e8308d62a4d761ff257a5119a63a485624ca89162a01abf7f0227d8ca07dac6e54fc06b236e672345f07a03828daf27705a77964ba15db181912376b9765ddf9a2a0f62ec03df15083d9931fed94972de4bbaf6ca45018aaeb49502ae6ab41461c6b50e512be7bb36d590068a55045c1753e0c41d92d11e096329a5642b273ea91f7042794928ba2d92398d9365aca43b40c99ce0b2868983fdbb5e3e4091bc516848fb6fa85f6f2700a287d93874069b8600d101529681fa52890e537f331a79ac32b1930a41f18503666875a87be7b3ffb93266f78c182cebde965b2b114b67b21ee8bfdc3486d1cf380d17c52788ddb06dce4c298834dd14a032f791edf2b096f83022fc0d5420b69ebb9d739239f424090556cc8139082419ac90fc030f91dcfef2df8cefae62c01c9631f1a7706681e1139c7d25a2e6541cad2507ceb3c088487e8de6a0d60124f07f32d518c14e9f35c322793b89c74d60b6ac7988061044eff19ab982d0070f5134d2e1d5176be0e189470c26bb94e465eaa37f5564fdd6f2b75fa8449b964adbf95efda31a0786ddbb54e6337fe6a6a0a8cd8b5ad81cdc83b9e42899eda552da5c0fbe43b7bd40430ca4db196904acd59fc9dd6d2d9a1fcd896416c28677ba6b741f49e36cce89abb946c8b658402d3e71673f0deda41bc7fb6b6487bf446e944f04d7cdd1fe35d8c11f1136e2238274e338fdb982477072d596b83f0a4c56bc1a294ab6c403d4e2c93beb83bb2afa51d50f603704486579fca1a56c6823972cf7886c78bd09b99251d1dfe0684bbc1415c309ff09a348ea73477cd24f1fd2a87a78f2cb1003fde4e3b9786600eb4976e066c3f6418e25dc6ee42f9e587de0d233524e872570d07162edfa3770a0049c1c792098b8807e3f23b04569b2e5383648e10fb93b0c8d790e1a0b5bd85a7ac29d15fc5d77ed867f1310cc37fe6e37ca687a518c88dd5535345fb8b4719f20719615a0db182cb74f9576b3b8f1f8a439c04172a293f0c4eb34759cf7cb302e6ad0ad0527e05891b90c3b5122cf06957b37846e12038225f01bf6dfbe94e31b675c36c9b0c65c9db294c0154cad324a36b72413ee68af50c5e8f7076af3628b2230fd72bf44a721bf4cf9cddf65c027d642a18db7dddc0dbf1212ee4327657149c8b0189933b3a83a5da495d9fd6cb0a1c2f4520419ba8a26d79528d715f10335a0d4101d4f6da509a3c2e3cd32c0efef852114644575a7ed150c66813c0b7511e9463182517aa608fbdcab48d152bb9ddcb0494171f5253c86f8ff85cb1c905e4780910708422172cacc0962edd2f9d7b08e56756ef9d0965228d5d7c128c278b456c403c5756467cf3764a5987e0fd0693cf5cc8e75bca9ad130782410cab4d51691bf04f3b4130cb514f4de8b4e1acfb85ef18c3e689b2bbc07cbdd64ccb88fab44ba4cb9d7d7312ae0d4ebf8015fa4f9842c83268f2444bac6cb057717048eda2907d68174555cfc1c5bdd6069ec460da69962490f44e20041f43c23ac04964388e08d1c2db030734c10e6e84b17ad86bda100f678967dc6a9c8523469f1f362c7c1def807f5914d1397b254a21ca9d98dab989fadad6fb853412dfc3f63168e9e253ba61b39b2e3ce0165c5fe2de1730d6028030b144c3b3db810f773ce4f98dcb3ee5084a62ad2d076a12515d790197eee0cb0dadcd55cad4614d7d13468234b78b4d29625faab6768dc806c139011752dd36d096c57a4d9e4086915bb9d019673286ae37b02506283f13f2e272a31dad8dfb9eec4e2b48541ab683441fd5854d498757c5e0b0d2b3e31dd9b58a129671243444a72bb75e92fe7f32779320b042e8e1a908fea768a3c292e09320b415865054d17da8168015f0721de685faae1564ee66900744c59ed23d2a839c6cbf582341f8eb41120661b5363188a5ee9431a8bc29caac697344b6abe260a5d304be621a9a00cf171446d6d1de576bbb97864c7792b05161c7c34f455397eba9c509cac949bf71399bdb9ea97fb38277204bd22964ed90a0c0612060c56f7106af32da9f387ffe107d9d099771e509554c1fde9c36a4b4ecd4358bcebae2e29ed99f953e910b5891b7269cc9067f2c06a842afd18d157f86eac1be24a29c9c436ded017fa41a3a4adf75d5bb1cd61b35b2545070086dfb96b246bee7424b3323f379e3e398a527611ea627fa2423ee559034f58e01c67c1573971e42675fb637bbd0f9490d8d8b0a05275651c73d0f6ef35656b58fcfc8a09ba244e2a820769e1316ef82788093076d1e342e595f2b663217e4d2f3260cc6b1c74d844440bba0e4feb2798c03a0a0d045af4a16f65038739c54ee840df2fb9561d32cc8ff709213191d6631e313b95fff0de876c058e23f9d1eba5ffc141640f445c520680c96e8bf7431062921c5a5eb5f402efa7db7efb7998497e7c0caabbc6bfa51e7311ef277185fec4d8e91e0c8d8438e9c651be58019da1fdcbe97d1a046b9c0114488dd928618f71202672c33234fce51cb15f4a6b2733eedc8590044a72aa83fe8a3f79eb1e1bbbdbeb00bab7b2282e801957a693a4f4f0e5954cf88326ca59fb19c002656c12b5dd22e710cc14e72a947050735a09f168582d782d459c5424944948151263cf65b3ae5271bf377e11f235e82966524b1e9aeed22dc3ae3544ae660aae370792182f07d7767b4cc861193b79adf7c8716ae767b1760f958be5290b6fd1b48b0a12bc79be3a6e2330038bf7268d478f83cfefa3baef16f279a6717e616d586e693846fc49462ee5936632e0aa65b05834ea6fa78e6d4a9384bb905a1fe1b5d4fb6788a109113d9be875cdb7e33350e15d038d44b485c55f637f4cf50b8b6ac9769e400d78ff8f8a3ae6c7eba2a4be0763fc70e6350c6a1a2a64a18fb9c64cdfbd52428255502e0858de1a04ee3297136e9c5e588b50cda1cdac118452315f3282082e86b0c4be17073913718bb8346dd8756a47e59845d9901718d53a7b58ded58f442d2a6f1fb1d35b07fdb0cc2cddc5f0e64eebfa18b0b5e6ce5fe687cf1f030bf7c5146dd74c83b1ab7e104cf47ec0bf01947660ca532a778c02772a1cf4d23827d2b9dd3c79e0b9409d0d91cadfaca5056eca7204ef42ed44eb1b1e9443d81d17aee5f0fcc95723961d6d5f6389e86a1ca0b1ee7a30f463e39954207a5049280d2beffa264e30192325a59caeb9573eae34741cc635bdd2322b11cf113ad00461732dfa3458d5bf81200a66f9f79779c844e20845b8a8b7b67a0e64edafa20858c3567d3fe433a7954287fee03240d983a7cb93b46c5fc80ea253a622866a994c01d4bdfc3e235e551392b064b4a434e40d45950006c38b448ab40ae852f9b52c45604235dec6145f6a66bcd709c71396c903a196258591d6cd9bcb7b7bf70b0240106b47b5949f02490c1450002bbf9e312d0fcda6ce5b27e8a6a82192700054155ccf1a118a0d84c91603ba9075492bc4d380275e43651228582481a1a7ea22c35f20be435a52b664b1bbfaa8bbc86861e077f48c84fc8df7b8eee2f522c5b0e756df61474d3e13a95c6fc96e92aaf5da158307abecace3ca29e5b7066b2db049290491380a641f3bf5a87f12ac448a27990676571ffc4a4d4d1446a7bc8f25f5c5bd609ddd79ac0120dfd0520f4f3382e28dfeab834166f4bb27af544399c117df06450565394917c364a517a7a10c310d18f3bf7ebec3605b704ca42242be2532b00166d6a3534d74dbf16085d493c6852eff9a4b002f6098923ea8707857e1f08087b0e69cab39090569ab59df8e5f1035e4e4d21673831592b446a05eba947988535ba06b3ba45bccd54845d26d36a4a8acf5b6782e12a55d90e55fede0fe5f443b57e811114263c668e91806da972a6f6d3b8e2c66237699a1c5a1ca4396a91b1531d458adee2592247d21fa938709647b1b428d7ec9e783b807ae1f835a8327aefc9f4b359364f9d6fcbcb1c4870c0034c53b7dbd09452e449bd224f1854843f7d54000a7d288613225a6c66381050261fa72a2f9d7912240201ac735ebf77e5210e5befb703ca4a6300d7c0424639ed8d09e06c41ea3a21a87b3950392a2f783faac210726220b492b38f90b45b46e671223379a6e606cbe98bf66488c5223c665151290838b8850524ef971ab8d918c70948a62be09aadea955b105ae642224c6762c520cfc44a86db2908536e7ac4782fd7d2f4c4a151aee54982a1b4485cf6b84f88ccc5ad420dba073aa574da294dcc89232b951f5cbcd89abb9eee2a80c5a3ed2114c21e24537f47c7e52b0555d7db24b300e5acb4375ed269673149d42a5e1e64bd778792ebe96445ed33c946f00b0b1182dfe1aa888605d893c398d48892d0fe6016fa3de9665a7422503d10995676f1b4991112141d832422f23ce245c08198b451c7bf9d64b9989e4b5575dc3f70d5b95746a1b8fb26c7ca9490670908d724c3708996af3b9d664b044f7e4950210a2896d13e54a8c1f7d0e6e72c37b53195b11f30d1a620462c02c65470738455bddff7ffb1cd289338ee9cf3bffd169a7650f9ee481544605c7ce19063a87777f180eb77f908f230738a822bfc675d2a4ec8efe0d24fe7fb14a0ea168af7e41bdc822cd1cb72f73e212f3f3d8cffa62197a2795d2857d0aad8af5771079c68935f2727d1930466652cce0dd947aac90e430b2a3bfcce0326298cd480b9a03f2f721eeb4506f5df67403669ae6c7688576cb9dc4aaa0f7f05a11db6c48af522f74a69b506f740c4ff115f2cea0c5d2a93d8d591175445daab3c24e17e0da5b6dda585683494afb3c7423874a235a89de6ea4dacc0a3c48d6e06d83687faff2fcea016a2822cc25d578c75ac9f9b68ebb531307f8ba97f3645361c921369f25656f9b38e547bae0685105b34d0d0e8cd3879973c2ee6f1d99e63a669534d9fa7baadbc97ff89fa9f161ae23dab1cf8e388d355433c816fd73404dcc6901c10c6cef13b9b5bd7fbd03dfab0c3100e6e3dd88187d78b3922eaa803e0ecbe3e6c374cb85dac51f0233f075337912e82e1f884e4018ae22005352d4d3d159d81bd40a5ae914f07f26926a2af720f373be19c36239f2e9b50cbda9dfc7533208a04eae2cc68431bfa0418a0b976d3a204f71c2820c4ce2db4dfa5f66d2e966114c59d143841bc7e82ce9a2347ba002a6d5777fa5f8588185503bd70362e15a845d103230c240cddcb4d0edada2be960d095aff43f20d68f34271cc69cb4d40260c368066823d03b837e0f44c57a602ca1b70703dfdc05bf56f0ef9acd46bad10b58aaae821f91b114da682e1ad3556f4c28875c2e5a3bee0378c12624825c4cc305d20b162ccf76502e3450747f0795210099606813a90863cea75133814bae7767698a76c016faa3ab21b61c5498633d55466f2d990760c00fe48e7d64417e3a431358598fc88f0b78fa200165b40e5ebd40e35decaee6fc696ba546c929c72813384f43923a4138742a443c24c5dbaaf6184b6badb6dd32450eb05ae99032bc5b1fd2204ee4f1d149f836b084573b97cfbba9bdf45b42f81f25223bd427fa528e3718e6baca056bc083b6f89994c838f910eb9b2ef60e2c3f59f9f05b1c6442df0844cb906aa7aff0291659954c1dd510d9dc99bcb9576e21e297124d771adeab31478adee2e34f27e5352d8a43e8499e2f3b07c51babeb5ae7491b4183a66bd84a1b6b4d124e87456ea612a4da147ad49b5fde21ac34ce49c8ac1e364fccf0bb099a75af914e1f60166008fdef134e3999488461f9bb227825df4ad75ca08ec27ed200cd0419131ba6335bbeb9bd623b4d9ee96dcb4126f427e8a458567b7d36f13487dc140bb2587fca48162adc67cd0769bcc09d309aa6d09b23650adcfe97652e00d1e24f1d8d2105e0523ce3deba68e3eab113c5aa633637cf7b1ee413dc288de57f0588ed27cf8ba0732e5a90bcfef8419f1acf73f6318f7610ab07902dddbd5bf84a4068f5249e6d5f6fb9fce44c661759ea3ccc76c031e814453d05cb71461b84aeb028b6143ea3e0dd4231e614650d452a423b5a601db29685d05370e369f4939aaef07c8c1f73f17a51b5e82296cd0299904a098a7c97025eee55d14b29519e75f12f5dc09f4baf55bb8e58c4e061c80dcee40b8cbdcb662a596e9daea7bb60c8ac2a08053ea37f2584c6a1ef5e2e02133d8abe25848e94e61af5381192c627f54a121934a703ca941684582056b24a07f26597e77e2fbd1946fd6760593e9d316307dd13c1cc5cd5030680fd9fbbe537813459e0f28c39d16d1f823a43ee45b6a9ce15d5c0480fb04a5f9178aeb1adec2563e0008b56d2882464efbdb7945bca94640a5b08c60743081ce4f6734b7e06e248c2862e84f373dbd21591dbec59f971fca3e6421e61422e452bdd18390e23bba217535a0907ee2d6e8112ba4e646253fc3b3ed9a64c791225174c1d9138454e59698ea223ae742e713764a6302ea9b9f1a8874b32b211cb9187e32db9f52475d1e32ff6f1e12e1ab1cb3f1d2c6b05298505a41591c202ca5e7e2133b9118c9175771e7e88a6cb11e7684493021bc62746401af40634583445baccf58f5a6a8818298b28ee91cb754cce90c4cbb821af31847361c368148d5ac8cb183e2c09643e3afed286a28ff69b0d3c1ae25127c05ffb0858edab6133faed63a282cff63dfda33de649d93e123009f2d15e7a524640030e358c407b157c827cb4eff12d763ea48c407b29db47821afa4be0c3dd8ff6d1107dbc411c7f09619deab2b1cdd67991b91c5efbacd142b14bc7a13a2939be3d1c1e7bebf4598eff7bc45f375eba14563e51617fc3a38149f439c206907fe3dba321faf41f21295d59fdfca8e58b928ba568f4c5a42f1e6151f2d45ccd7c03b13457ab29af944fa43c41698d2bbfe5af66e6929116a52ba59371e5473594ba7858572f041bc6a335a6b02c1fc6a75fcaaf76257db61928bffa9939e77cb2f20060836d8761793a9ebb16747c7b355ef4d1e1235e2935afc36379dad778ec53738477a6cc873a2e657b2b106ff968ec816032cf1e83fdcc175aec6bea65bc50478c97144384f1629037642615f6a9172f05ca97f259be7c50e25c2925273f8787c36b216fa1bc76d2445de3f5136fc9674d91bf153971e5ef68b234f453895d20d05f03096e3c0ed1e7c627a58c5db279e5c735983a87bbf27b4a3bd12f0008f5122d574a19a5a419364390cef1252197622e3197984bac8bcc6426a5c8921ca9df94321da386e79814cbe15c5bf7896126a0946294821527d6ba5a514a674b4ab9c8cc94524add23bf5ff756c3a5cce73c654f2ffce60c1bf39f93e38db367bd724e2b95e0eeee320773777777c7648c4ba639991963c6d8c3306c323327ed650c0929e7a44fe56cda22e5e7be45d62cab5ce4dc82512e1bc7338b8f25e900c93927bb2acd2a9d53c75f2f74ab29c0376d01be2264bac7cde71727e63b1a0431df3cbbaa7508114782b5f94a31ba84ad52003fd8d465242cb818c3dacb485850017e81050d70c02209ab821dfac1b7018b28f216635800d104c3304c054b2ea840cb009ec06207af08e78a0757b2ca6abff21855bca06b34acd8c1698d1b97a051458aada2835395266b650bee06537448582cc9ace4dcb0e6aa9e7170552a555293ab4ada82b7204aa56a6eea6d6eea53a92b7eb8a92bb0dcd40d82e801a8e57e0123515f918070516fad407151cf483c705284aa904191aae474485600a1564a6b2e0dc0a5b40a1f5c6a45511534e01e84418333785812f2c1aa38d21281d0c419480c61791c39515153ac649538f2e5eae80834260c1555ee2cc09d54a471670db09ce69c734e71842b6bffc089271ac663871459dcf9727e8d72e7dbd51457eefc0771a494524a1b3421a59472dae008cf0656a42062fe9cd3872f9cd8d003305a70848b31e79c938389020c1c512461f5824d8b5a6b553d11860f9688a2972788d0030a0a96d4134d442726cb830f9d1051504634e1a29c9218628342d4219d407b22c479e1244b41940d0a317aa84318695014e107fa6429b341070c668582a21ac50e3c4c178448a25140c941c2ac86bcc5350a24807061082d5140e1c4851428457185095c2062298a1cb8f0c4c82ba2310304cfb03097ab141145006a14e1c40e4554013a29420758952280e0a40726b4748b992a135dacd0ca8526869e0041145104af6856b1b10d10b12b82cd0013e19db8a2497d31c41bc6a42c66d56312bb20c03e31bfc5206ef99be0faf340c4bf566bc31587ffba212f6d552c0c6dc41bbb1e3f319f51c1b9acdb4b09e2e8a834f6d3b3bfb8b69f2aa5a1bef03b893e33fd98e4d2db3310fcae54e6d3a25beeed1ef81e84f8e27acc47c45b3ee3840d75e8e09d7ea2973e19b1c5dfee1a18c6d3088ecb1db8ae354ad17ee35a6b0ff6b1ead724d8ad0df382284563217e7dea15c9324ae9ccbe30d2c7aa473f529b6ed9e37b5c62536c395060b19f397eb0fcac060cc0a0728512582831f9c497b10138f2ebc962fd5b7d5f911217507ef09dd8b7d773a77f8fdb2db8ee188350843aa5ff832ffdb02f94f383b0aaed60bfb9234e82d54b3df6d9e2a89b882f1a88a3f3d957d4a9e3e637bfe370b4f9126befda59c72cd6e834de19b99b2f1fabe37f968b3407da4d2ccc6524a3265a2db0db6524a3237880646485910e6a58ae09891a24a108260840440d8a3080218bb5d16a52032d60036ab0342f23f5604526c5d32a0c16e2ad9dc9c308e01300b0e38fc9bef10a41ba57b4303100f35e6830e29c80009efbb12b7edff82c9b8bbf79949977ce9106e30bcd205906c18285b1f4910e8e4dc3505046df12843484b0d20eb604218d20323a16383b7078ecc03140eb1aa051a8020b50d244af95ab594dd178de110eb361aad1b204be846c327a1a42b00ca94228c01d19315b380de756ab93d0a3b16d31cae8b1b52933efda36d4dddd1b7377d7399239b20747c75fd8ecee7677f7d8e5deb1bbb7d34c0d5863c0c72efe2ea6408169107b49b1ae07f65e986d15fb9ec6b288bd0cd2458be130c7d2c9132a08bbf87980dc18262d8679956a84cc74b78de3ce89329f64661bba7f5766cccc9699ebe4103ca1583ff5a6645469c1a114aa4255e8052e7e55a494b40dda066dc3c6c7e8d3261b9f37ea853acc1fdfa56b524a29a594d99c2fa59452babb3b7f3dd2203fe52ed863d8d4a2820df2cca47429a5942ea59492c1adde60e3b39452b2942e8fe0a72da69c5272ec32659729bbc8c984c904c90425cc39e53bc628bb4da13ad8f82e6990a997ecc8c86808a329514d4d72367199b2a949ce262e534e2967939499724a2e7236bd787c9fcc2e19e717b99c3c2544e614fb28eb9cdfe3d393b3004e6cbc731ac166e34dfbacca4c6699967dd56483dbf7f4f6d17e9edf13fc8d7a717e766adadcb6d7e6c7e9fde0bb3df5f8c6d82c03468d0d573d9be296ffc74e0c7bbb1f7fe727d303ebcf752bf2965d8fbdf97d27869d9f7db6413965fd2f321fa6d3b25ff96fb3004e3c09bddac70273d3cdb0d9036773c28a0f4337895fcb567e58c18dac0323a594527ea1119c989898ee7e791a2ffcf9617eb5aa55af52a9bed008cecf4f4c4c0ceb5931ac2fe6865dad98c8bc3c8dca0b8de0e0dca059794c66647e62626038bbe2602a0eb7aa41b5adf2d26112192691292dd5b5bf2249e4f5b76ebdf0486b1d0a853a9d78e774ea3adee93a7669cff18ee4b4df78476e9ba6f18ea66519bbb4fad2abecd29ef2cee9b59fbcc33ba9d75ef24ecef61ec63c18df68df9ff23076f59f3cca3c926ffab5774fb24b7be94d76699f3ac9937fca637679cbe7387bfacedf7e117f2939a97a4ec5a99ef30fc9e993d244f5aa6fe6552a99998ff9f92b7fc1fcacdf9e6b2108baf6b9af5e9124f2da47755f68bf7e6ec69389f12c8cb73a72398ee338958ae35e5eec498a8d6deab680edd95b42a53e0eef43c5e3e4ec943f9fb9c8a30c9b7d6cba217d4d03927d614552e443fa49d9a40cbdcff695808a7c2acdaa57de8af960ed7d8006dd47639b5f04491874432925a0529fcab645ee7ab40ffaee7fd2b447c27d32d05cfb1aa7d99e6e6603c8741880dbf27b1c8dc3636e75fbf3553d9fbea755a7af764e11ecb3eeadfdf6c278bbdf5e3e36ddd46f1b6fcdc9ab39e7f9957d65fd4217279f7947fbd3b7abbf507b1b54a47941454d57e39a93af79bd3d12fae10cb79381e6729f7aee1e502f12fa2128a27df745b4efad3f24f44329f39bfc90d0f65cd62bf3fdf621e13e294d645ee6e96f350e78ee7e707c4fcb78d55b13c717840da07a99afab42ca7ccdcfaffeaac121f3f337d5b7b7a341557fdfa00ae6653c1c3532ab97f998d43ccd3391f91baa9f7926aabff1d65f333fbfc6b3dc52fdca5f377ebe8ca7fa1e87f1c2005cd557a47bbea87ff14223ac74431074bbdf842eb32bf585ddd7bf3d5feb6dcfbae1d1784c561f0dda0a8c98811041508183283e3cf3d9adfe0d1e68d8fadb56fd45c3126ea3c947e66f883e43d48749908fcca67dbff4b46f85fafd80faf2bd5b81ded63efec9930dbe62bee3623cebad292718563e8b85af97952ee60019e2cd3ed60af5d23742af0c7eabd2152e98ecfcdeb6ea890436f617951824109b7cba3de6965797df0af5fac73e415d82d82d3ce5d842561ffbfa19e6dc7caed7bbc9427dac3275006d6a6aea72b125d46fc1a873642b5df9d987f5a994a1f7d1be12b4920f957eacf3d15fdb9b9f451d77730ef3fafb01fd45b0fa2d18e9b863a0e2df6c1d4485c7d073e707f57cf71c9fe3f6e750419eece9f69f7430337318743d36ddeed9f568f693b3fcf9b2b080db31b05658c0ed5f0037dd3032bbead1c468cda6c635d87111d6294fb27315e8b8f543a2e3d69f3eec4512996e8d4b1c79e29491a79550ac6ea1fc0ef2e137ec5898efdc39a0629f8fed6bc7c2fc2abd96e26e5e91ecf96a5e91ec63617ddd0b5d855b3f242adc1a65bb3c85a0af0cd87d8a7d2cd82eb6f86c5314b1b4165d6a72f02407368842ce03d2164b785ec7288f36c6529c174497df89cbafbafc85245801d9380496b1ec807451dc391e6ebf6d918b6ddcf95d3ba7b2e06d08ddb0044aee6cbadb77b92f8caf6ddf378c9fbd772c047953d3edf19edee16a1e17a1cfb77a45e8a3629016fb045b3fdbcc96ed2dd333bd6a55c3744cdb4ef54bc746f5a9bba6f9d67a6bae6bd3c63a76bcf1fd95c586a561fec81ff957917f26c67ce10ae60b2dbfea88e872eab35fb862b141842516a13e2e62ed86c74ad1ebc7589c915fc6d58ddc754f0bd51f6859776466e62fbef65d2337333773a41f63a4941e493243e7546c6a571ac4d260fc8c1081e185af71aea051609b99397baf34f8168b62ccb237d2491dbb3b7667f1e5b6fdb66ddb16330f89c77844e784418f02db4410613771fd91ba09de6116e294ed88066583ee99d751fa8829ae431147252d0baef30be79c7372546c3f7fa1ac517af4b420ba31f7eb12b26460e33b958d19bb825d91f259baf7732fb179bb7bccbbbbe3f01a77f71befac777799777787797777777777777777f71fe2eeeeee38eeeed57d453383a326d3b04a3794e54e5d4a05f3728325a3847583663523a324060c18d58b92ee74ea524a2cead4714a68ad7453a26595e6f84e89ccd1a111d939e4155b70f4cc112d34976a15ba3e31724c3774a62e10601fd7c23e397450d9857dcbef3a439d4bdd85bbfb7b8f9618f6ad79365b11b91ec52676e4e3dec2cf7077af2291d8f86347b99b73dd845fb6871bb95c356a3e8c6b5cd6cfcbc894f8f2acfad4c7eae476dc5ba6d6128bbe88568b78426dcfcf21022e53c746452ef126bef64cde8a356b27f1e6f9c75f9e14562391c7cd8845456e9dc8976826262277125b49a9a8b97051f21797c8d35ac49bf85a70616a2791a7978837d1ad75a2d84eb468a9f9c215eb0bbfc64931ed84da3acebea852b3fb81dd1b344966acbea4994fe67333623a1b7c4b1a75094530315b542fee728975a225525cc7c4c4e44e2ceaa4c4a553e294b86c182dd2a2a8480bed732dcec4a5c1a8466f914b6e8cbec68d436057fcb05d0bbba2141cdfd960f943d7d2ccccccfdee9fbb96226c8cfc6125aae9192628fe4a713aa7c38814bbfe1b31c8db3caefcdd3d41b1f4b18fcffda6715bf65aad5ddb3bebd495479388669986f5b66958779fcd8dfbc2eefbe91cbed147c3beb0d2d45b54ca9e1e75d2ba8ab179f577e93289bd4831c62aabd58a777a48b768582b76d9558d0d965ab1974fc32f03649ad6f508b2e21dd495cf383f1f9006654c0f36642c38b458f98eb1a0c4963ddbb24cc5230a214123a5947ed635f6fe713e730dd2efe94a64bdf31174315a44f7a675cbe03124b1ec924f3d1aafa65bf235cfa65bf2376fff56ec929b7b2cff90afd86028aba1c807fdc21a8a2ef5519ffea84f7fe3066b8cb49212b726c1aefab1eafb576badf463cb4ddb5b6bc79ae6f0dad12d6eb5f8c64fa0975ff4f945bf1dddf2c7be70bb61bdf38b4166a928bd1d382028b9e8fdfd2bedea9d8d4de6784dc5309a2465e87d28f6a5d0a04c815d724a4c0576c95700dfc8df648f14748c6cc8615c39a473c2958e01704de45100dfe4e01d156e5060f0a5c32ef9f3637c92d1afaf7d3603a25f310cab7269cae0d421a961c69578d4f9e062d1d6f9d8ae4f28ba3f82f5892311b5eb9e4f1fa134189f28b1db0c341c894e78a76ff7f18412b2dda35efb6c72bc63bbbc46c0adce7e339c86387d7dfab1566befe9749ac15f40521f0ca8cf4883324a2cf758f730af7ad99dbeb3dffd0cfe02bdd57d4d755b87717d7aa95e4e24e097eaebbe1b724e9f0eb77b1450b7bae77ff18274ab63c1ef0a7cd371dc6f282f88f5f8caedfef49da058ec27e8af15b8d53d19b7ebba0f756ef7ec5de7eabea79fd8fef89143eef6e985ffe40569b0fb976e736ee66562663e2b1353ebdfeebbfb5902d3bdeafbf156f7b103d260f72727acfcd87d4c2452fda97b98d3cbab4e1f0bc54259d4dbd3a34ef6843afdca5bdd9f76b0a7eec3d5ed7e86ced1713becbbeed9a6fb8ac4a2d9a726b82d08cb0daa61c615ad8b51b4eb9f7591c84776fd6b17857cd4eb12099273b7813962a3bf69596c162bef64cca33d6fd66b40b7e847af278506290fbb280a7c43a906d84561f016cd00dfd06ae90b7449763d2aa5f4638c315a22fea2f1894486b0f6535f14fa58402fac98b7cf3e2f1fe423559f7ad5c7024ad1c09280ea6154413ed9cbcbfc4b4d7d2f1f7b2b4e919bac1cc795b7e8c378ff42e7d00869907e7b400d6adf4f8354c6041de01d19ec0698c79f3e911778277bfa30748ea66d377adacb08d9909788f00eeae96780794e4f5f039cd33dfd0e700ef7f48b70cef6f441c02f1d6fd19fd1e1ccaefa272ff24dfdce7306eb731e65b0fee66d0cd6d7f1178a932e7df6fada47fbeb117dbff42fdfd0fae9b08bfe098adddc7ba1418abaf523c22e4a9ffd6fb59f7ad587e32dfa2f40f653f56365f5b38f95696fa495b4ac6a35ab0f7a8bfe69079b42e760b9d90734a5c1faf1eb740e4d143af97067add507ea9e7cb0b171b0f10ec7925f5cb71ca65b1e39de9827e31b7faec1d6b2d8adb9ca2ed724d75ff4954817684a833cc5c636dd200873a3f6608b5f37cb7ad8254176a11a1004e3c0cee96f4757ae87777a08107f116100ef58805d3c14e01bf9d2524ecb62b51b72d6f9f00360b1f236734efdf60360b1f5fde7e9bbed39a0d367bb49e9c7a2d91bd9329a51a6239dd3577e922c56de21424e4416fb285bf7eb9efbd67efbee7b0e48fbad3f963b7d23ade44ddb9b8f72ecc43a23421ab1c929de6ae951640fb51091d785c9859c49cb1253176ff563515af2566b51aa4a995151d2512735166ff5917614c588e3a4d745de92d2eb28a827442dd44344714888a94b149ae1486bf196901f18881c252d49e9e9e000e9a95bcc13194e7a9168860501f601bde5ffdef228e4adf68e784b7a44bc253d21de92de4f0d273d203838e9f5e4e0a4a7a383931e8eb72427bdff38e9adbc253d6bc349af7a4b7a2c80959dc14a8f0501f6a9de72d90918f216c8498f05243f18bcd51fe4b371ebb4590103e946302e237df1837cc55c19666e7c18dee98f30ec8a32d4db57cb627d75e58d4fead7803d7d09843dfd3e1616560d45379c6fa495e6d785d2f53fed6081a68438389d1373c4079106fd67f7d3e0ec6910c7877f83cd4bb6f0c6652d872318bff0bbf1f9dbbfb0ef3718afea200aa101668c31c6c8aef81e5bf16f7c8c4a4a428ef4e0064764ecd84c8012853ef56cda61981c815235246717135309619c77c5894f66b0f20810b656cac4c474e4c81115143664a623a4e61df9354b42a6c1e460436682b2bd98c15c8cf47041709d92ee74c56e27948a092818daaa6cb02133f15175e94920423a8bd44b1362528aeb1893224f04d231e945b506962337f222214474705216757a51ca89b261d5ae62d738fdc2f9be756c5ec13d874d32738c07f0c2c2740eb78a1dc6b0c63f295629b4610392152cb0e189248c80c40dabd665a4329eb8a7cb483cc8610d185bd1e83064b208c21658ace08c2cbef0342a30450926306083871900c194c4134af8b0050d2c43284388223ce1c5510f16a5140013765eb69203130428b2f5b2951c90a04866108159c961e86e97ad3461a3d7702d6104932266a0841437306a1fb41449741835a805549a8cb6a8428910d478c2a70a2a866842858c13302142900c94a0820aa32fcc6062061a407409c3084e4c21c4141d7079820344d0c0062e6010c590920fc188043734f1448726286109679cc14df082cb900cb890b16404dfe25a18074c8c8104222ba46c9101ced72c965042b542c4e4486594016385480757bb6c85080b256af31bb513467d74e5092b188a201714c1a8054760b1c610429e250442f0392aa3890a9a60804614a38612436432a4022f24242424b4c589932daf4587036891afc396e82de4d19b9c6c1c5fd873830d13bfc8c3374c126f94427fbb51250ebd0f76e304966f84e996cb1b3e08aff2eb7e649c445edf912de994dd4a3d61e3cf2fda25fcf894131b8db055ece5639827035d210addfeab55e6d56fbb014e56b5c7b4ac22f19034d03863256cf62b599379d4c3784828babbbdddbbbbe79c18f6cec4564694324eb0c619647421c50bc808828c1b968ca0ba8cb434c6cc65a4a5a4251a2c4959ea610c36c640c21867c85c461ae34ad218518cf182315430c6500e4968a265088b09c4c8811855c49881182e10e30731726023e632d20fb460f9c11596062eb8302c38228816fc608805b76b821117264b142c2bc0a2888b824205b7a302e5724610714f3f5c19228b10f7e53252184a9edc4e8d2b29c88282ab9d11c44d8101c44565f1c3f51b5cf1214b0f17ab82e8a68a78b8f40527b8d5881d6e0784934b2f23f960071dae652387db3135b95996a1bb8561828bf10087bbe180c99d575c5992e5869b4971c5862c4aee160329743b175c69cad2c69546b071ab1057d6c8d2846b2f23f5a0c915266459c2dddab8d2258b126e97c695247041421635ee16c6e730c3fa11ae5f4602030c30ae706a051ea51777f2004540480bbe04549f521a238db45aa01f20ef6d9a4cc005e6f1e71d5a7fae1a9c5f6570b0212bfdb4108109580003fcd2a94a3ab5cef97702fdb06b2ae18029ac341f5c71346b28c5408ad534987d2c1ae96fb4522c64d51f33d6844666c4e6a45fa433624f2316e7b4737e2c6c3ef6b1300c9b139bd954025cd5ac583435362be660cc38b2e2332a3a02408d0d8b66b5a259ad6c914dcdaf56384c4c61f31d0489c5528079f855bf21f4931f0e24f586b4101d6cb8ba14c0b1d915c22effd313db92597eac39b1297f4a39a5bf5c213e46619198babbab7588191860977c29dc775ab86f488331604f7f089117302f89bd1660579d31e98b47f128c3c17182e3afa885192bf94f6ad147b153e2508348d90c6c4c3aba72a59d81893c1790e11d0cb08b89020cde0879626bad4cb1c8a4e9c0daa0209ffea98951b3ac9ed0f276340fb0f22bc3aa579dc60acb3ef533d495f5ce2484628c1c86b591235e926e1561977c0ff08d740ebc61977c29db73de06ee68d088c300faeb87d6c73a1fddfd61970c13ab02bf56de927f02efcc977fc32f06230ff6f209b023f2642fbf00bc739379ae1df6d26f1579b0afc53749270800c3b00108615c6a0a51877fe8b44efc8142ff40c17da0905d9f73fa9c523be9d0d9d9c8ee0737d8fdf8f958f7c39b92310c8b18d6da09094a763f5cca1fddfdf0ee47164baf96c56675326d6cc3c1649bc7d91b564d567bad734194514a83505c4249c02ef923881995523fab948619581d1d22f3669d8feee228b294526efab1d7e3587772c262f3d3316fd8d42b6a566b96d55a9bb677cd6ad0715cd771b33d1edbaf6aa66d9f0e8ef6e9e0b827b3a4db9e28875d591b1362dbb8b94d8fe0abc5add9fecdbf524952cfa32e62b14b3e0ddf14619eac12bb5babf5c3191b1c21754bbe94ec352fab300d629c7bf18838c58b4b744b7e0b8cc06a8c825df2e3137ce351a278378c464652fb19fcc57df1894fbe51f7b410e9c355dcd0fa0d1e6558aee2b27bad350bc08d4bd5ca0b53f10bfb6cb8cda673e263fcc2dea625a57c7e9e99e117b317bf5bf2d3b055e9a773429d2b5f2fa49342e7c897cf2e798212df0ba5d58a5d2ef4db2b1f8877fcca1701bf70b8255fca9608e057c8d3b15f401d060dbf70bcc53b4da441f92f6ec97fe1c89ffad4bb55e7006159527ab9e1eaa66eb7425fec5babdd0a7db5f7a858edb3a206811a940f34a48508b04bbe8f1f2a4a1e15cb376a58b20d468679b19734d2ce86108d146157e5f100dfd49ae5f0e82e07a7c1fa59e61969100625360ee1a0842c8f06eb6799bcfd5e03d8557f93c9c1cae01749bdfda83fbd4f57bff690c2699dd6f568b2213788a37b97a74e2bc2afd3179d74ab7efd803882f635e1d6df28277383fd10e7662feeebc7a1531c7a20feb2df0a25843d7ddfea22a95d8f9f25a8afbb38d460fd9313566bc2adfa462c0961939e685eb5ded250d93357351dd8556313bea99cb7e4861208f970acec51570af771675f1c8252638dde12ee71781feea3a269bf753d32da0509ba5aac7555ad91f9b1b28f393ef9e2a2938e43cd796c0486ce89482292be8dfad3671f89bacf501fe8adfaa78cfb2efb581a4bd37ed3b22dd3b2f756cdbefa30b06ecdadb1e885093892b20f549f94b08dc958794cff64171bdb586b51d7b24beee8168f6ea598a7a5e29d137ce686f64acb3b280cfb0dbba134bf08f65549c106a5ddd1a0b49db303a6b31b3b20a306ab538e51c29678c7256c138505ddd3a3b03f7d2c20fb27d59f9e7dec07f954d4a7b0477d2c20d4a73ef5d150c30bea837ca27d98b7dfc37c8ca1de3eca4e89ac18a3576f4d4c27059dce99f1e8d3c6e8a96e276f47b7e6470f64d7041d4ba1c1d9e3adf9123b256193db61d32d8f6fe6a901dd9acf7f9a73887d0cf08e0cf602cce33f5fc810dea13fbb1e18a555767d213abcd3fd7c149887fbf93cac02338073b297101d1b73dee63983d86b1e6510fbccdb18c45e88bf5013fbfeaa576de67c9fdf29609f0ebba6ce9d18b6f2d6c4308c6215a3d869074b3f7c1bfa014d9961a60e8abc314ab13232d94894813b40558a6f60bac6dadbcf2cff7eb4f6406fe1780b356def4aaded1c2d126141bc96659a963d517dd243dc72a795ba67350832ab35cbaa740fc889069f68f5b34dab9fd1da5d295b966173238e1b639aa6a27ca06e7fab2817a7ad88b26939750bbaa7362ccca79efd25e3431f27a394f60edaa53c3ab90da6e63198ba55ced2e7b1e3045a6586629847867a67784786d5b5abae5d8f0dcbbebb8a79dbad35dda230dd52a5f8867efd2cbbe8cbd86035201de407e41dfbf45bcc7302e7dc20e0456774548b3a751c10c60246ad2bede8f73418843a901d0e82dd0ad86b0ff3aa1798afaa52ffb2bd9156da364e92b9f483d0d73c5ee2a688b2aac926d53e97a27dd65e0c49e4bffc9e35bc722fa2b6a8546d58743de910d100000020004315000020100c884302a1503420c8b2de1b14000e7398486654170aa491280762140541c6186000200018628c31c814155501adb64ad121e68267de05dc66414b0192b2c99904d78b62811c63441cefdd086fa45d23c4ea2dfddcce1af0e3e2b9ddea3d2e65bba93f07f1c59389cdc74e88304acf3892d9bd7ed743c5fea13601808dedfdb5ee3a724f371604e9c3c41cdd767795a0debb90827bb90a030e1cc1dfd6227666abe0f2476168b85d92c8105e546b2f625141bc561002aaaf419ad79bce52906ea36db067f465719d7696e225044a49a1944f2cdfe99b65d335ff143e31df13fd157505f6c8d55161dfe7a963941dcf6f1784367056b8ac3af7f5e79d56a67d2f31b99adab1b456bcbb4c903d55af6a1b08d975cc1839f970c4cb19b8540775c70c497c8614df17d3c1c6d92d3c4d6a393e768969844f72dd9378672fe1767d6bacf8ad3187b109d62936792a4714f90f447c46d1e2d714ab200cd582025ecea6c41c44b72991bca4827d5eec13d91c86d14333279be7f2495e5cfe611bd1395a911d89829d4138382b157cd759061644b49a4e62f79a67d563ed009d4439372611ab04b89027da8ae8fc1ff9e26894452a0dbbe52e816fa154a9015b680808e3231f1967445fa0b6cade0e21f5af7a2a63c6f04da34723774ec0661b00eae40fa42b09e00adc0c738033ff5aa9da41b66d1628c3473c368cb861967c284e6ed6536b5c83e56f0afa8ef4f7cddc1a78472bb7cafdc0cd1ea4be39889d426dfec678ad6e36fe4714fb0764e72713109a3682a42799ba8702b186ad25858f05f5a92721cae4265b49c34890713baeea52c19ed0afc09751e5e6ef9a776299d9a453d27c28b6d2355b92920b120a556c08a7573e95b04b50e1768a299818d10d1b0c6c9094a98d6413960a1f7d66434bbaebd38f3365d2a4e280a540453a1f4223920964ead75e64f80f9c31489363b51bec9705cc492215a5821e42ff28aa40794a95ec20868a655ee4de7e1ef6aa196f3308396955d43125fe9c24ee23ccea8dc5fb48a9b02aa67ae73fc284c2609569dd126f259e793a6f130c229054df76fe23a5da284618ad2a2dff2317080f55553d41afaeaa8f31e88530de31d809fa84afaa4060733b7d1bbf0812ed7d9a4f721a0b43615996bf1a0bcc6c2c9be85ea43187100ad1db7a5cd23cf6d3b7237ea15d8920f8b6e0e4e44545daeab9aaa6a6ed7c4376879ae63d2a103dd7c5635ee02e00a1ba53d33c3261721a8cbb66166c7dabed3e7250b2a2a6ef6d24b56610f6ce4092ec34e08ea0665aa101b9c74981c65fd001986baac1f9dbe5571fee8f6c82a53d14e384038a60c7307d62d8b99de4e9fd119b6f92533eb4f10b70844a04b294dccdb7e86271bc4248c5c9bdc46104c2ad0a53bc49089f8ed65274b951adbe23bca97a17d3c55b2a3bfe96cea8ff886e2bdee6f48d59ac02cb337148f8447e94a314117c400081ef43b57446fd8ef046e1073101741cf7fbaa0f1a426cf4d4f6d3ce32478e9123d9a85d65b97488fb2b10ff4fb208463a19fef524dc6d95a7f517e1247a350b3c63ca17cc9225685af1777215bb009538521557088e156155fde15749a2fb1fda51ca913828e380c705dd90b1e1dfc973a3131b1fb72a9273e09dd2385222a2e495d4d3a53572a27eaa1238cd2ff51cac5ffcc2c02dee07d88becb4ad30ba7c4c04939deefa2503d75407a767ec217e38393a0d2ecf49581f5c84d31163c62fd866c581093f60b2fae4815d615ccc74ee1cab7492e25400f5556ac244f267636c9c4c5a21172f42a8d6d8eec256c4133059d5c53e682798c6086212a1c7d55774db521a0de1cbf3f0492386efe7d1829b3be6d50b1989f68af2264c77d78fb6a38d6a9c27006900141c17f7879b60a74e64ee56fdce02cd8d5c25722d2792c9aa1029170c2cc372bced2357f48696905c5b53955c7a659029067ae3531fc7d7743cfc82d23c48c4f99892ea7d184ebd733a70c2ddc33752b2931059f67e41a0075b4a3d17bd2ff0d11279a121c0060dfc30b1a449f612e33d5811f7c63a03f81b633dad03f9c8c43144ba623cf0f60698c9ffc860781e4b6f04f7cdb33ef6831aa06f4f6824798609e339957358a4ed42f6d777928c205fa88959fe9dec7f8532db336203713f4f21d6f6122f9eb016717b46bb9d8860ae03f2d212f52006a300f31944876959f48a5ee16d91baab620d705407c31ceb3a6b42f00c255d2f3e828625d3895b49675f695e8b66fb98b5d05afd4ad7ffb9888231160f42447bf7eff30819e938f6a52fb9c04363bf97f4c5414a13b208bacdc17ead0456093b7fb2369a244f849654e9cfc340c471993d1e3ab3f4915eb311a22d0cece2fa919be8a2597a4427954bb9076a0ae69b0469c6f4ba1c2e318db7fe076d243429592c90a360e2dc889d999d7816b9a63b122e48508aa84dddbc4d8426aeeca2bee81e187d8e2ec0b9b82e9a2c9180968426a6bfdc06cee6d17abac1dde03a9d6af0e0874d72e753e993263b2599500d08d944ab5564051a10a0e536a7a935297c985d2d09317920b5ea574b1c6c8bec7e6d7fdb98a68581de0e38630036c1409bae8df108a9707885b5cf0841d69cd09abf9996b5e475633d494e1796c700dcbd74d64ce9220c66c32b3318a4b1d38094d572a390f65320c24d4888c4645a8f34dc7b6a50fee352779e8d90e9d5cdac20e0515835cb03a819cd61724debd0383ec328abe6512ca6ca1d684b5b019cce4d17ce46e18a5a67051498bf3c42745579427c8cbd19fca8535393bf3c0c30d98f04bb8a1c99dc07da1bd04d135b11285200767ed4373f87768a660c91e51fdb1a3351b43537b613c1fb9897b4daf87bf6fa10f414709dd2eb20176c1b52428f071c987b563edd726f7db9bee413acee6ab92da64de4e204dcc74dee9719701c95c6e4a7dcfd5af771184a209a9e80673226882b136a71e609ded70c7293ad46c8ff9c02da7c12cb3a4bc537060ac28867731a823c30cb62de804a186026e937c80e8d8880ba0be97ba24805564deccf2c3189b4708e543a4c873322aff11800904e9f33992cb4e7d1ab04dd12cd9fdbedcad5d25ba2a1880fd567ea77c9c6fb1f2bd2f8e183f6e3661f61095d5e898811624806b71b7404e5dc74b1721e353576697b85e24374b78019f0682b25c779f32b527ad3ab93c88f129b3e91629438eb288db08a33af8d489679378eec838c52b407733003a8d84b998d7aefb39b1c8ff18a370f083f6387460aa09c7450bc0f9ccffad59784491efb640622727de772c5c53b83bfeee59482dfd0fe0fbd8df278a45fe95bbd3494825c2edcab25571d2ff195b756811c65ebd0ee80dd600de274489d0478a5be74fd39a8710e7f8883209e926300270effe1fec05672780d41612633cb6420debb15c6145af01e1cf8c6086f233dff5925dc97cd2a89d2737e74b0da8c0486db67243089c587de1bee42ec1977d1d457f9b75e6d988f6918108c7903495e191c21938a52ca0c9a9206bc47c4eba798be72f9126bea561470f21dcf76cae091812455de57f9ae30636ed4780e78c80b3a0b51ac20e4ff375b4ccc4461e33a8f9757b5442becc0ae8e6b5b44f0baa99036063b7a01449fc12f37561d1b4abffaf89c7c334fdb47ec4c8583da031303907e0c0ebf2822dd5a76606d278a1290fb30e62c38198f6223336d8d446d4a84f0bbd96ea93fc0d692e291744ef4a5abe72ecf1c701ae35ea15f7ce98282df4617edc6792a00342b698b3cdc68cc68811bb8c9834dbe9e0e80d0cd163845b9deb2f766c63b035027ca22d3f1f1dcfadadb8f41baf53035988f851911e8388cdf2e14ecd1836bc77cb2d42ec19e2fb8b618f8c753a78ba803d726d770eaed0d2abec200ae69c04841c15acf60477b213035f72181ab0df77b1950886d7508753c4fdb715f84de16f81a75e871e4c8eef68aa3afcbca306f854f66fea2058670914c556f22e4a605436e3c7711a6f46d5689f0857025c46f40d7dcb1690d86eb0ed6ef3fbe5e0f2e675455277bfe33b09faea9cdbd144a8ecc79184ae29cf48108699f0b4546079e4ae536a8ba24518ebd7590fff3d5acab0ee3fd99e4785c854bbc5fe7da3f5e51385d2e4cc359dc6c2b5c6007cfbf530e4f6badedda96bac0a8dc5e65acc9060fcd1e884af99d102840b2889fc592780161a9fc48c9ec92d1135bebaeab1e128bb0e2f5a2754031f01f99813b925df2c51916e2e5916f54534ad849b34878346003e9e8ccf0862e789ad4622e0f787d25ddaa0d32b567dccc81cebf28d4d01ceb64e80ff753e06a17c926f695e315c8eeb067b0aac0b1c17e279a274126463c1405028fd83ae65490e83d07acb41b2bba5c77791d0e30cac1fce9593115b6c3ab017f630febcbdeb4ab1c8f6d07a11303b00a2588377ca35d5e3249d05b7df5b3a7f4e9480bca93a37c5f3fedb1c13033cc30977e65451a3fe467460782ea48f33c064d1c205bea5ffa8c8062a29a2e099410c688dfa9a8bc4f5a2351bf3bef231a131067799eb8701bc28dc269b4d1ed39974520347759ec502f5f64da015297d3d44a061e3980b1382529b252f07a8a5250cea15aa665fb39567afdb3d572caf7f4dcaf12c9544ae24cfd6e58c1665ac9e7e1c2ad8d443dbc5bb11c29cccaeee8ac42ead27cad770c705b238b0194f77d1224f4ff98a3f5be76679f43e25fa36fac564ba76f7031e90c2e784569c8108926ff075cb8cf2724ae60015922a80725d5ca5e1643a09baad146efb7067e9775a7280d3018a3669f97a2df154d33d91e2ff7915a4a843e9101efc20aaa1a6aea475f8218eb02e890ad1c1cb35d3128ec83d9fc7bd90fe5a986c6357f2f5c8ca05a42dbfa326ab7abd56140040ee4b61880aeb680b262f26d892d77bb8201cb17fd3ea5f8856001f3fbc730d37871d2ce69bb586c81023668967136e0855989f92d9fe7e00fc4115af2a153ff6fc50dda5ef69cb5ca4449242229cdd65bea42e6b98b1fe19b5fd97e2cca219806b17e256a29dd21abf2c5a85aceca816ff5a0c047b9a61ac37eafb0745fed28bddf11e121364a1cb3bbbc25dbde661380e0e1b560fe8ac69bab1dfe152fd34065008520c9b786783b260a71f5cc0c2e2ea9181258ac2ed1a304f87482e43e6bbe10be1f62f503d9bbd6555b413880ac444e5950c55824043303e24b9a14e88e01f039367aa38829dc22721d41e9087c55132be348e63d79760ed90aea7594db9092374ff8bdffc17dfe3f900f918b50be49a3baeca61c67aa5ee82a44e4a4b03eaa10d417c9352f79377dc556e52fdab60fb7b7f6633243665d9cbc2a5241b08105b2cb54003d221bae9b613dc03494c0766d500442ded0e59de875a6c587ee0a74f2aa900d62133981d7054a017527e1f0f2a06c24b0d09d98d9974d8d5509d26f0d3c5afae09ac6de5252536db18b420ccc31649a76401ba124e338af97d71bed34ac4c443d3a945ce163c4a5626061cdccea9b9bc5257d612abdf0c8eee23157458c013cb859286cad6913651d62ac3d81248287f84b32b7467f315b964d1334658a30c4f14179e5026ec2c30e555529436899953c9042b8a196a30443e8928434c31bb0efa6f29c2f8be7108532c9c2b2b46c512afe309045c5c359a7787ef4fdc5a47e10f4a8b7cb995cef769a4287ea4bcc0cbf885d16451964d9a120287346f660b332662cc7b009228c2e8ea9f60038f4fe8609930924b6b08bd84ec56ef9cfc564d605c0cc00e265e7cfe14cae009cb6834f16d93eccdeacdac6e7badadff84e6c80581f20f8e006e0920d64bed46d7fae47a365d89ad71888580282957c19cc55afc347edefecd2522ea26e9562f84e8a0efec7f898c7f05b82743f8330556be75c0a83a32f0a3606e18b3ff9f21b94ee15ae92f9909564b4a664fa72d8ae413223ee34f5d5b3235e659f265839a27af614ffa38a62d4b7908e526cd4e302122709f68f32f74c44a55ba2c2801b66cb199a2515823dcc7436d4ffd033d5e1fa0cf433b2288df9a147dc9467920a29652fcdb29c7d717dc576943ea90ae9b5a393d6b8586d1fc1477b670ff67fd09b5be4022b7abac859474bd8f8f3d572cabb9051ca547247b43489d339d63655bc6d135e4da61ebabc149f9e78da150e02c0babf5038f0984c13ac07120f20ee09c6ddd03f0793a6e622558c994e7350bb00e8c2b5289e6e87320ffcfb3a3651291984486dca9ec8c2026bfbcb4d17507c28e255828ff01373c36d96c97cedecc0f218558981e52cc73e11745d12c98d7911f1c725cce10339321080c68a01c3e2e169e025ac61e52c0dd4e40ed7c9ebe735ef9c229d8ea7602e75bb6746c2147b735a8f6dab5a5cce634f36a737250853531be9023fd4d6412b2ea58c16d055735928677d97ef181c51d170a405c87bdbd0303e102542a9d9cc2bcf71b7ad1510d99a6e46652c3b8814a911bd16ed4a7d54e68bf20e3139ada9727a4e9a9c38266f73f25e37616b53baa7d3700fa4b8be61852ea87ac2866267e524a5f6ee3bb7850cf10c827bafc0e323f1eb2172333df3fac7db189cf11534610db4d6878f9488975c1f807ca7f89490b55688ee625de50dd8f723baa37d137d2cb7308955aef02a312aab1c241566c843bf93971b4a0c204b0368881ca0f66b5b9315e03b1c21b1b9099cc267dbae4c880810c1b6b327dcf250596c7ee5b245b8f5413cc7ec90a515690fb5ed3d481a4ae6f921227349940c5eab5055d3ee976c2df0f28f139d11f79e009ac4356c279cf2cea91f97cb5259350bf1d01e415490060ea217f56038b5eb9720beac1f0a1580333275cc8321a48f3f56cf51a4cde698c6ee246ea9720e33741ce4896eea7e1064be8e5f1af395463b9f5f6c897e46a50beffc65d9de65f9a2089e92ab7563fe32447a76dfdc327447f73c7768904d29ea15a8fb875b887ea37cd48faedccec84b239330201baba091952963db90c6b647284ff145022c78827f368bcfa805afcbd21dad9bb5038dd18a525d890d66dadc241f6f5f9db575cb72d7aadf9226ab13f21ebb5db2531b4ecd5c5128f533a23b6a0f316bcb43b1c1c9cc7f9942b4ca940591cb9e30593dba7c433808c4f8be55c8ae40d9d65665e5e62aad402a16067347e61199a6ea79cc66469f814d222d5ff2bb7c9b8c57abe74cfc6b28ec2ff16e5f25f651950b8030be98a3471a2526937a7861824678cf2ce12598826f90418662e0bc08f16b98634d494152d84ac034164541dbe4fb9cadbe4fee6b7cc78068379dc47ea9c254c405103b6043ec3fead90926710f3308ff84f1136f67c325046fb54929860fef5f78814810def2b736ecf4905408dfa1edd3b5de549bf3f1a4386ed588914572373e39f97624c928c7d70ce2163b877e832e42e37d95662462f1a151f92456c6b1376575d94a073e27e88926e56164c9e080932201e22870aec4f0c82d17db623ebae21e5737c31a6b9d2e8f7100f88439071749e7fe15ae9019e9ae42a0f1fde6566a86c70c401fa9ff88bc6c56ce0d1291dd59ca6af157694989109ab1135f55a0bf4a48848a84f530fd807566601257b5c69e65a6a604c1629fd592047719a9ad3a62a03329ed2cc44008eded2255f5788fe8b5aaee082198d2b9a1adeabb68c2653b8f97e223ff0504209cfcc68b282f391956f3119447462097f8f0ba3f829baa8a2e753b3998e04b19eb69768d834492eb6ab304eebc55f81fbbcb106dc30f6c73a519118cc5c727402f623799cddace960f82405c571733987fd652151a3e2dc672a6c82c8cf00fb5b90d120ea5bc07bf90052ded24bc664dc2ab48b011b8342029f269b27c25ad618cacd1bedbb6b08d15389380628fcca383855077c9f05240b84a5fe97bb1953970691b0e8d2390119a1fb73ea4e2a7ee2fc69a47de3be80ed10cd7bfdbdb14595a4812c1b73cb5d5a5ba1c22541ff01ddcc21637b4c16039b5ded8fc5898f0fb2a61f8d2f8d4e7997da2c1a87d294411c4841c895063d1b18d2bc9b0dc7622c8eb38e4b6eaf2ef320c2212517cb0355b225730078d29efd51fa19501929e8404d46243906e2e2130820ee00bd1d849608dbca4c3a9e8af6c44e94aa48cf5654545e9ad7dec1694481feb72680fdbdbf95c7b5ada979c17bafb3627669d6b9fd619aabf60454228a10a99049276403b5ef266d8eb8c6d72d4ec508fa46480706267d42db00390a997cb11ce81dbfc0f933c6269fce244e40d07b56c8f0044ec12f8a0b04f841f5bd44e0d78ca9ee0f15fbfe1f46c4afc03960b83c37d2f39cb0d72e32b1f8f4bf484e0860a68f0d8b3c41ddf909ac4d5b3d7c4efc8b43203dcf66091b18d51d5571c1cc96ad2d143b0af68bfab3e5d4ee37684807983a730fc53308f6defe48bb53c323e088b6577647305e24fef966301e90a8069f6baaa0b8f8dfc62ca6e549205fa498f73f5a072a63fb44b9a65c2c9671d4e2449d69e6152e8a55413f93f1d45fbe3807240fa2aeb6f1dd3b012952a8cdea136be7f017723423763dd2e089475f1cc07836e2257cfe8d6d87ecb14bd347803ad37dc04bd4b2373e530869e3987d28e0b90da2b6e8d11e6cd13e2624a0435dc5918eed3963801269036d9865a6fcf642e7879d00c41e3acd8c1eb2aec691f6f09175953d90e4ea56c95e12cf98f2223f98da000b884322cb57388bde3d7d37f2fbdb1546cc2a629a8fb19cba1c6e8096f901986ef2c866c542a7d55a36a6bc989714cebf4e431d9d4e22ff30446d7fbc0309b5031b1c02f9d325d223f547373c4ca349e14e85d4a1b3088d1c495b2f512eb8cd15c2b4ea598736625782d600b2ae7656bd9342e3daa030e11f6b920538960f0433605e6d4c429c58e5b5ed7bfb49d1eeeb626c5adb2a3bd190add64b6f9dc3ae55e2967fb870b36e23b7c15a9ef35868843a7040a0d0fe2c8c274b532400074cd3e7589ca6efe4e1090bb5e41edef21799337a627531afbcdf552abeac42877bbb8dc9d4676f5168d0bfaad782f8cfd708ac2ead7ad138cb686279f1fdf58c2adee4230e4922ff48ab7bc062f5c6918f6a5c6de614613e9845d2f0077fa24f9d1216161a06adcaff4a07ec9fe0bf3a2c5fedf35b591b95d4d828f73c0e7f59cb747b99dd3433e5e7ae32e0ade2052abe4e2f48e1b05c76d2499cfcef01427b00d67698b8c733dc2d75cd9d3409a95111c306b748692c9fc74350d6799be551adaa09f1ad6dcda31f5e460f67e06d768e40982e7c212b64c78ce98542022fd9d591bd1baf2af3a3509b3535fe31ec1534b6aed8d163f98515298ab2701dc47f1f8e09304514bca92271f6bc8602f2344424b6b410bb69a5d25195bb6d069b038e72e7dffe79f1a0291759d5af51869fbba2e0cb6557f1cfae22d5c38aed8f33da45e3d6bcd4fcd23789dd5f9f932c35b64723ef8a8190638c965735fcb400bbad5954a73a8c234c889bb84260ab13d138fc0263bedbbc479fc2d424ead3a735ec5b0801198ddb80f317dfbe84185ef5f797d5d72b4f71128e641cab789ee105cfb0926a4e1a3f9baaf81ef7204ac1ab00b46fbc9842e6bdd997b8a37df849ed4f629028c36dab6c4a0e27780273135f619abf468d8ea3a25814a8018c095d7dfc0a0172efb7c644cbbb14db5f8333d0a7bbd70a52ad448f0384cd77a3f94c31ec258a0ce37115eeaa6b62fed5c8123453809a1c9fc806db38a1541d04aecbd593c6b1e741868764c5a2be5aa9cced303dc9beeb68e7fc96a24806f4d2f280266d6441fc0224b8013194bd13672fc08ace2cb7e9d8980e600b48298cd5a82536396eeb9763cedf26a5ed9196bd43d8256c3b3137ce8115dc16aac3b3da26bc2ab9bda444d3e8dca5723d26fe0cfff259dfce3ed4e52f89d622c00da53bec616d0ebab2a57679a4a8b308dbaa51107220787df9fc594871e687d0bb91b84a09f0f852e130c5298ceeab1d2296b741008acc500d2860ce0915269fd47af8f2426765090f88f0aa002935b708a26a24d69c34cd679875cf46442ae65fb0b6b7e0c85f7784c7bb4f5a60ab94e4a36b8ec67b913e3d663357bc76370accd64011443fa9075a9ca02e057292f0212b3ca46c4623f62b16dae1310cca836227de1f3f0ba40ebb4eb0a4ab6602343222920b151e9c4011810a15a07891df8552bf72e6f46af2dfd45d79c959d1c436dc6b9462fb146636125915027582c380ebf73ff2b8b15c4b21f5b99c20f88ddb81a26f132129d80aa81fc55bf1f29a6f0e4e5c65bf4a1d4a0fa451f7ef43b950a357809a10c1ed1501fb92e447ab44f63d4280ad6a45825d233d36c5c670900d0800ae3e0a84454517d6bc1f0b675e01689ffc156ec75ddfda8eb165d41ec411210803abe492e5762f10b9a44d2105d304dbab22281535e5ff570c6d6c398506803328abbc52fbf5767e510813d60c0bd1d8880f6a88b0fe38c8435d6314374378d2ea937b345e32dec1a0cfb0c64638ff8ec2e832bc99eadfc949ec1a97b43bb51da987e301e73ffda8b2ecfb60ff0470255263069a9afefd7c75e269c8d53f0dd46d6ccbb46069ba65888e6badaac8978a31f6724276962195df087fc6bc8469a4ae1790419e2309b486b830f7fe6c287e366db1c398976a79c60c23b7c94ed66e92f54bb61a1a0ebf6a041d011c6cab0994fd80df84d6118e080f072715ee03402bdc9398f078c494144629728edfbc61e26d6c7beea2568c0fccba7712839197799683e359db3710b91ed5f2cbc9e562fc4e1a2d9bf82e69c065b3b29ac139f03ad191fabdb544755e606c59807b6ce4ad4051decf223d840e4f63b7d0a1a32a79de8f01280c02a9217855d88b7bfdcb0f8416432b3539da890547adf85fc5cd367d9db1189e6c79a1bddb73b701b47c89d971c8749d7ddfc708959b6991af53cf421c9d88a5ff1a8c041cc7e94b926f561a859dd417c8d7ce169ccd495447322793499b97668088d899696344ad964cd94c471f80f97ea5642249afbae784d8003a07ee8f588253271f1eed3bd520722eb4fa2eea55059ea65e34572c60fa52489a4e31c029a0f034a1d449eb87d3284abd77858bf7910ea8d0cc0b7827a976b3908cda0a84d632c7ee4a5397c9856ee019f8f93fc0dbf3f374b2434789796b181d99859d3e78045f0898bbabeac292c9a545a931df70e40d3cc108ae2ea732bde581ec67f91cd860cf94218ed63941b1bbbdc4fd443a5b62dee7ebbf1f00a35d1737488a096ca649d118a1b4d3b9498adf1a3d0a72afaa62821639eb7643eccb5934e8354cde6328608204703abd6c8d5c2d26e460381bfacfdea6ca2abd188b4f1a98a5e8431f228e4e82658cb6f2909c49ae72832e8ebd09c1c2a0a588bf490349614ba270fdb94388ce49c8ccca1e2b500804140f54409267342c105bea3c6c787eead27322e8ca09703b7413cdcf6296ffb438c6ae1a6c4620cefe2dd0b16dd6d017ddce8457134e2f89d58c06608bbe299be9dd91f77c028813b6cd53f6debf885c5d45685a42d035744bf77cb128c4b0fb85749000dd0ca4671b26a9648fd3c8fbfafc1bfab8be9ebaee200227bc4810dd6aacd9fb6609ca331e54b5303b58838f39784477c80e04aeb1a1fd63e7fa6568a434d675854cfc190e922ff5583d0e4cf56d8a50700a5b1f67300b8f8fef2acf72005a950f12153f194cbaaea80d5385e9236b0fc53260aa582e69b4c3f9f5b0e6a47e28e780b556eb6774b1f75697f0c45228a60bd608a3285395b9f5c9770e8a5c11c8afbf79196a854a92a0f3e007ca8bf14964495ec65eebdfe97c592c509f57fab9624a345ee8168e76a430992fc7925a4275aac61493bf12f7ced7cd3a14aa7c8dc29266086ad7aa7ab69713dd9fa418fd0e5a43525fb1f68ee103df4f52a6d29277d51feeeea86fa830366bf0b8c6fa5571d881a7e82d4b7f487a654152f6d6029d504dcd60514232d84fc790a245899364d944207c8cfad8b9481c41508e11432bbb4052abc4912e8c4fa20d487d1964bec0be49224d60508e5afc2ac903e6389431276c6cf4656be7cd5b53049747e5bfa2b47d50cf5b4756c87dc9af30459dab09fa931fc9c6362339ad3c729ae13189ad538c8097f53267fa52eea1cd7e6b8dc4427c36849da699955b4abd18d3b10ec3623922d495487fd38e540871a69719a77935618f66795442194fede38075d3600e6df797f38ff491e753bd31af6c0f3cacea884c0b62d2e308534f9baa53cf4f3e5230a1dc6ab00acf66bdc6a44760ee75fee62a1767184c852534d5f02e11faca1429800d2da226c42b82e5fac89218067bee9365942ca8f9693285aaa98290a7ccd55451b4b16d0fc17a9f6376cacda42282939115732d20dfdb177005c20e7852a4b161d6e606ae2f88288556c41dd19c2656b546a0e1af15f5368f63290e3f7530684c80f0a0d5cca8c20d0c64a6723f8375283355aecd0230444f6f2a3a5b156c0707b2635d948ffe2859410fcb05738007aa0e41cd060ff20c26e70688999e52c7b1dba716ccce36dea3d70b3e32e43a5cf047de3b057cdc1870e24f15294408c659de16cbd937e9fd7e20de08df9f7b9efa7f44dd9472a8b6a1fb9a3ad6e63eb28fa6d7a82af61245a5d9cc21d594eb670bc70a733c7edf70a37de7d861b0943417285ad6b0f7c87899235a892bcf25a37fbc12012fe0d24d2c019271832ab12589d0d0de81914ef9d89d32619862bd7cbc405bd0881e34267976cb18c62d7f6fe6a72609ee8c08d197f801e1b472b1b870ca0515125e4bc0247be43bb959925b41055db1eaa204d6529dcb4368c5d004ccf7a58a350d93a81a91b4f866489865e0e490cef54ffbecafd5e6ed8d3bcacc32a2c7d08c9bc9f98ce0ebc6b93518c87e66046b0290723c5047bc773cd7751259e35ece5c880b04f85d4ce873736e648efe11bc1b823a07a0ff8c6cbf15387b4dfa0fd0cd4568450cf468d17d01dd01b417d705b3ceb5d86f4809e7e944382e532d99288ebe023480bdc17ba8673146075ff3031ec93ee9516c2a5800de32ec7604bd311ff959320b7dd88de83bc20d850d3425eda31286533a02cd628228c8e90348bf749ec40b5d1716c7e6d63e3a0be77621d86bc3b3a3d3ac1620065ef4e25130215a49ea8312c4681a2c548d8d9bd093366d3c923ceb86320b56ef84d29fb1691ea673a311a883e16bab6cbc5d5005e5311fff76d61b68bb4a32bddd3f9fba7c509e04fba8aee29d02ba40ffbd2c4266443877cba9dc18c1a6324baca6a6b54cc41b42d11647d8917a260fdb911d5872dab819f72ae6f3576c00e3169ba0b7a2dd06765bb67df0ada8a1eb412527444d27d3f13f384249ed547b85a70d8e6f00196d3e19b44dce1f5b152463de577952cb1238023a973ea392ed895683a8ff364ca5ac37c06fe436ec91c7e91363dc4b0fa194012ccf166557804f9c12774e1dcb6907d2f262a4b0dbacb21e3d984bb725b3e48298103c97ead4dc958e5925bef578d69df4cc37b97869977947d3f49ba071cacc65159dc11837e50daf12e5969b9a52ea613769a65137ab11bc0d15eccc7d7eb98c71173f5cd548f06eb0e8507ae04d7993b34407d389b0a046f24e192c012306b9ab5b4c9497985185430ba511aada683db869352a37e93948b2a7d75a2bc72588d54f13ec7b886a558258208e4005e7a0317a721a57fd6d2c4ac6bae4f1ecb93fd1dfd72debb8f7da4b986d26aa055d2d9e9a1b15c89c62097166f2cca5b3a133abcdd152d69d1f13ca0b4708c264c5d7a298aa5cce299e281ec9c95755b33a1c3d7a046d78c7eaa14df33458b642a6636cdfc0f925561f68c1cc7e868d2d31b1e5d318e419e28d73ec1a206cd694ce0cd4637956caeb778630bee52691252958def97a88f4c3aad9b29133b3502c73de0622b8cfd9e6539decdb3f356040796bd8c9de8ce0360969877b531636694a48b063b8f350a503e60104c598f2d42d2b6ffefed4f83e4086a7fa7690b095f867784e99130efc98a39f8cd322714603e072f41cc2b49570722375e631a8691cb32fac31c84d7a81b490326246e5f6ee53ff2cd0cc62ccee594191a781e3d5336d7e62414c70fdce8119c04507bc741436c56eed8131be3bf04142815e29f05287bf5d88dd0c1ff041428adf0500f7145225d5811a6a8be261118c32af7549e53f4b10727da65702dc41aef0918cbc8d75e20ce7e46c2fd7e7145e2efba17a3fd5e865964119a2785102c1a4fba30e0cb8ba128ef9c7a3c105fac94e51d1a8c47c90c093a298eec3b4afea3144832ea58b6392476b2121a8ffa50ca8d8bb0670db8b8bf39f59d71c67b6c1908f997201f05d1dd5320ef3fa889c07809a87005eacd497a6beb9e40754d45411cece865cba7ce6cab1d0f5c2bcc6dd14e7cc8d0fbcda8f446893948924e8e4ada01d87f81fe586edeab3fc61c06a3fe889ed179c01d18d5d18d2ca2ebf4de4266f1487235dcbec04c382e67b7b96ef263cbcd0f5b8f5eee165a5e307294f35ca0b6a470ecb238880379efcd3e57479b64b06c7436450b6dd38353709ed7addc9e61dca4a69349350ec90158c7056d52fdcf2165e2cd8ea06c8159dc20fb1aab5358f186f8496864aa2fb7f005b0e68e5ea7d58ad596be96ddb6ffa8d03e968b2c599ff49bff40ffe4a100dab2e0a9af88c8853470c0b0be4eb3e44d03decaf0c2b3e77f3586658099743074546596ddaa0f76fda9c4576f061cfcaba691d20d34551d89838c9f287169bdf7a4bf6bab4d845f295b1759ddf06cb72e0349af25df4e61f83407bf273295f84e712fdcfad67d4700ee6cfba8da7c724e0280c0e8b0bd5e9d94ea5ebe3e17a537ba9c0e50d67bb3fb4e4c16f50ea8b649a343cbe72b77dbd59ec588bf915790250202dd10f3b3f5a50972bfce46145a844fce93e7fc3a39bb8f5f264b404e0ce551ca7db9000ac3d471939e1d78d29affeb6fccac41e45d7d96559b1735cae8cf7c7e32787ac64f1ee0e073c0db24b015ab3b2a040c13fb4192420401a03bd87f85ab8ccbf80c0f825328c1db39c105b8d2c7ddbefa6b69cd34d5e75f828f543c76043dab30ea62387e6e257fb20e6dcc2e0c8590fd3bc6422653a582abae4dd1377c1cd6bf4a2fbf9833b99e91e1780bda61e32a47450ddf17b06d347aff92d1002e27f177b97751520827491b639036dbb483a0260741e109786e48b8c1938126b895de91ee94f073103aac2a3a58a092880cb739c1c63990bbb160442a3ea3c62822c92e1bbcf5ca26b114a958abc8eb6545beb3e93e13811d69f52c6a3098a336a024881eda852369d6f069c73edb6142c63e1d67607007b6d37ba494603064488c2daea01d2b542de8252cc39514caad8a98241538c42d959de569c1d2043992ca71ed59de90b91da4276a68a9517336254b041d202d4f04a74d3667d95d91e101d0cacdd3355fe86abbe8f112a05b31216225b114f63ebc9204d051d2d54657c80e661124a008d546542d2876804db8da86114099b19ad230811972a196bbc76e1bff4075832ac084ce91367c1114663bdd207c10dcd7173d7a546cb41df09517639272d6252f3816b5df0fca75b5c4f20c91f11d27b70b9d6685b97b46dea073b67f373cef11aee84a07dfcfa75c162efce3b33b576f50e490717440fc336493572651ed9e11b947e382ba64f94f246d31b51f82b02feaa8bc90c19b6e19edf9263c972dca1a4cce2958cc9eb47ac4e5b1d3955b8509952b50c7206e1c58b711a3b1b681f110d3e6d5afe66c8b57ec6d4bdb581cb1e2d6a07081b6d004bc7562e5ae4ebd8b034407c6818a6f6dccf49d99c77e7b2e19908c76bcf3ec12b3376af1385165842115ca7d4162644fc0db3354e80124b5c291206763fa8c50a3a19a0ff051e0d46afdc5e8ce608ab25032d760a1582320769b439a3f958fa294c79177a4f57c85992e6f3436e23cb9017e935732528fa7661829afa83372d51a7c63e1e946cf4c9e62260743436ea102941ae82cba04a9ea739b550998cc385506db268a9455c911e0a1099f755b167af95ca0b468a8913e7f6bd90809aa429c7d68c7104577883f1c22743ef09ec7395f5b533a8095be91b7c9ab6884769131081dfbf48ef8bbc4076e824f3ec51b460b1911a8cf3eb1dbf14b696738203d5ce2bba17f45fe1b4f14d02061486500a292c46becc7a688168df040c9ab1ee8fc6d4030c3e6f06e851cc54c881c14a82fea782b1315b22c7e3f0a1da2e5db0942dd642a7c7c9940b4f47485651d9a6fbc6cb86ed4f5f1d8d99ff922828c2ea50270ee3ea4c864cecdda00a894bdebdcce2c3901ed0dc601830aa276ea90198204146accfa749a74ac40d5b82a7b648d8c36d9abfa31146cdb284a60288bfcaababadf938f67b1bb223a1bfa81f0fbcbebd24daf4ba41751fdb6e4265c5a50b07ce69c4f2f3d96c35863d4445e188acf83e5cf76bc841596f86ae16f47e8f8cb1f57579c38b976e7a7386e8e6850cf778585e553a3945e61e54050923ad7eea7639ef52f26ce0d6766887c1f62202a1b850ca3c3b44a77cb04563b30ba57dbaf2e29ff450ebf5d60f83379c5af0e7e4ac01de828db26c4c2f5c11d7591706c756f2b6d1098b852de236e555494090d706fc11a3f4f9a2d115702c72bf211b3cbd8f052044e28f4f2f71c27711916715ad96cf035d1ed588efd2d17b20b02ced01a87272c05cfc99b9fdae190b3b92357cd5e4051d5b2bec459428e4cb0ede41e848a0a31aa809208bb531e032aa797e4824596da07f99972ff6d6c1b72c911655b01530221bb10c2b953e7a1c9bc808e751fafb34db23cb349ca12bb9f2833ab1ab5b1a51c2da99cf600917e6264060e10d2db35099904f932622ed48360ac334c7788c3528e668b152e9262d802cacf413df119443727ec3effea4468662fa6c6994d02159159f0220b73496906732ec3c4ca4c9bac873dc90c479ace8f52b71ccb7e549494666658251ccf8556c7adfcfdb9655ee704514a262c1076cd70929dce25c836573b069cc26986c3c855824339d761e4357d482f5640c956a733fe214b6c0357bc247fd8e9246eb9a256a20d5a10bf7b0deab4b241a92618c793f688a1f36b0c2160ea2b2d41f66678881af4dd91058ef52f662ad0a49d75da4073814c7c601c230ea59d524fb8f911e0a6546af7d6c7288c3da58aeac4e1abfb894811e212a2f9f541864d249c4332429e0c0667e463279474f277789105b5ba03273f0f319c7185205b18b4f073144a1ed081c9162ef18d71c652f74befe7313d130941952cf4312338f095163075c91785af6c8229f2c609c6909e6bbf117892f9794b67514f2c8254fdfa5786d5bf042b9922ce2ede45dfb6bcc6e440e2aeed12788c349eaadbd22cefc184c663b1b99ede3681cc52ae0d81cb51eaa92636f966ccca8c7177fb0a21faf331ec03e29eb4c3edcdd5bc2e7db0f29f2972668872cddeb0ddc7983c7f851cb6636bc52acf62d283689e1e66284e079ee6d3604f18f8125ff92b0dad007677a43ae102dc6c21b4cc31599e26d03c5b9ca1f1596a631cc2d92c13d014259da4ad2e027bdd2cf25b72dd0dab9e41995cef1085c2e8e1846f3154a4943ce33f92ba6d1a07ce238606c1c4d415fa78a046d7e0df504df6fd099d112f6c0c1f19b231d8c628f450668f1e31a7f2c61646b6c63c3fdc39cd49cd21853a22d40c66a6e5378e82de2ab0b477d1bf8d50f0081e4bf415caf82e04b895aa342b9e4c0cdec3ebefca5da545faa162628888333b7e32c0bdde0c1343f62ec2f9a2fc23a456eea48de8cb3a9a2a4085821f2c7f749c27d2144afc426878d64829245fc75edaefaa2dd0ffb94809272fcb776b2568c433069df6a5c64d440f06516539e2630cd50282cfbd9151695da13950349541d22b29fbf936778ca390a27727165319701ea0b8b8418f0b4184b4725cd3eedafeaea12984cca61a44b77dde4195dfc8a2b3a9ab1133bc251204e8e5a24da39f82f2c64ec3ba41a7e5ec7bf828440be64073bfd17448438419f0b6399282f2a2d4f335f9ed7d9f748b379c2faf344abcbd42e3a08c648a4205fdb037ea9dc48708675685c539e9c2b48b9dbd38ddb1d9033fc41311c35b9ade22a86dfb93415cada5646b6c67c3aa017ab5942c744be4a33edc089d7bc163c224b0452f3b7dd495a10eb1d480b7428d6dbdca88a504d368f6125ac15350d6440b60f3a0a10450858ca80f93b44da4c27a10973cd323ef86199932585250a6a3e3ab408c035814190e76cf93da062b5f087c8bfc22fb91edddf66ead5450e2ef9130068d78f485c86be9802553e8e34a70ad2545eca03885633881b85507c65bca2ab747b2992bd03624b6a5933bde7fab0482b16282171c76f6fd013b2c3daa3e7d5174bf8726d2c3f402cae5f240f5be729d8b024a95fb49ae6245f6f02dd49fa511b5de45ea6b4f0d8ff5d402193912b3f96999b4a7fcbed572147e0b02f1d3a78f1cfdd6300f93b9c4b18a64efc1b649cdc0cf873692ab249306c87e27d39c136c828b94163fbed501f392989ddd99c817503616ba9c453b3b8e4326516407572dece4e4c04dbcfe9bd76face522671f1318415ad5cd38fc7c1b1972199ab7e90fdd6bab8d63d3613af408f6acc76b054415c06050726e684ada91064409aa081d145761ec27d891df81e195b0e08e8640b68e126acc23247508b79e7e9cadc730a3f451abd483f01cb6e938758216c0f6b1d6e7b67d8a99acd8c1ef1dab92d2d14c977ecec03b872a74f7632a5e1e318749d923b9f8ddf0c0ecee5b9f0b1774b7b81e91b5fb1d59fcc13c0f16993cdb78a2457e657edd9541476670d447538be79fefe945f0fedfaf24b79b030ebb1a16536bd66b87b23a3f4d763b8291db1cdbe4ba366ccaeeb324d91b78a2f533bed512a63ce35b5b9320b643214a6f2c69c45535c7fd1a3bd0b3495a59e1ae8b2f85bf3a39f3efac8e3503fe01ace8c5fd565ff5cf207620c2a1899148d9ed0816bcdbb2ab8574fd54c84d97cd4442c0ee14e3dffa83e65df4b17607ef01dddcbcac52c88363913dcc2efa57b03a6637ec44a8c1e252214e728d915c0a7184b632a3e4f1a92b520c34488a45b57a27c2e98c991743e0fd8f916ca874364abaee5744c61f4024966ccc69482af7d817c4cdd00369285a7debf869fe646fd0903753b1440995a3bab952f91393f1401d6392fc643e213146ba6cc79940cdbdb35310d4d85382a2c1205e528bb24263e850170540fc1d071c4223b441f1eb8d783220681329d1c25b35480311bfad007d8e9b85425956c67b19e2bb4e62b0f6ec495595b37a96aa053d0b7dc54a2ae2223e1227e8fc3a16229e4f9a904103e2376898c56a6f7a032fb17cce48c298faaf103b433e7edb7f4222a671a47800d25d0fe2f39f7db4aa0119b7166a9cbcf1abe35869c52f801b803a759266018ae3c349ca05756310a64203f48905146a81f8a3d9cd0091a8fa7cf378525e350aef1ee3876289b166c10187b9126d8af1ce95b482fc95d47b29b11bf70efaebc56ae3378d2f9b3160907c76f80ed08d5ae32628efbe6f216cab582a0f693e8bbed4236c06aea5d886c3d4f7195033546dfc22d731aa95b038fdcedc79f827b4567e0de2d7f86c79e4fd0d491165bef6b031f83e2078847b52d21fa6fa6975f37cd0e50f3e25e893f704b6f06cba4e4846580f8e70590cc30660c596bd291935361fa23f00803277514e5f585709a71b88be0d5a2b051cc41a5135402416e307d5081959e85af76050b453227611f6d9ddd028dad8805a3c286405eb92f010a8654a7c03183c1bee9173a43bafc2b0cd34f7018360fbec1d0bd46f215fff365159de3047f461830b66b6527eea31baa317a4b27b24ab6013d434467cef156bd3416aea227b5386c11a18768f51f45ef9fb511cfb92dc127a24336c6045d3788951c6fb2c2ea14a73206b7c69d03a2991f563a9eec1fae7d42f793b61295862a7028bafee1b9d5546ff60d8350e56ba071b7bec964b30f4e0008ec1849bd088e1cb28b17366a110a6b39796408140386ffb8d4603830dd32cb4117f70da94ddce89ff8cc0fe4ae0b3355fb90b1c620fa6d85c3b8416e8cad051825ba064cf27979bed061b0132ada91b9f19259f83e5f419918b2f05695e690b64343c382988ebc69bffae31fa57d1b713111465030aacea73174d4f0873094cbc96817ef66cd4eb81c1eae442ebe8792b388a68062c039d16071cf244c4f191a07aeb5a30019c2c8d6d40facb5538438d0c749e5dbfe78487138767b40b47f499790075b8a5dff57c70bd67f03dc5f73a18871de36aecfd292bcb0a89b6c0e33ddbe35e6a541904c0b8eea67145f9cdb4fa0a79d7a0ce50d812bf579259679360cff1398fce1dc06c4599a00332959a472a319a46141cd07805c010c58555e054c56ea14b6087e11af74c0f6b99c82e26cf88199b95e03e0cfb2cbb193dd27233fff25e5bbfe64f9863c1118beb13239fdbe3500f77e4e02606e9c7d3191b6cfef270462df39dedf9430cd093d0f85ed3d0f22b457186870c036cdf239b5d177d05d79dd4a761af2ea46b3a50409e56da306422f4b0162347a7e79e9468c192c3ef773fe6412629dd9b6b0ebb48335deeaccf31a17bbc45df14f123bbb6deb3dbf854a3fb0037f6c88f7c3482bf8b4da1b802ff1b8322f64cf455f2d0b10f9ed120f1c93dac20d0838de107eb9bd1318a61f2dfd8936104efbac5f6ffb673562329227240b2ad50f65441ee15ec930dc74b087552f254753f9ecebe2d63bd77ce6ba9a6cababa926796bdfd5652976455dc1da7ad0ff873349ef3dd10a1a5e0b92b3a6014df06d80dfa692cc8407d163b5c0c9a8ace50d1bcf164636ba49b92b7d49931405be577114347cf1c4d85c2609126025ce2080783f01737879249de7aeaad6f33e091d54feb4c45d2830f88efa01fb32f0fcf67dc66ba98b2060acf504d0ea5df09eecc5a501ca7ec9901871d044145cceec8277a8728ea34483fc480f6cca223019c9c7261427a4b418e1d0ac40dfa170603193598cea1359b3291ce8085318a7cb67548aa1f1243db75719da8f17135064b984b9e5fa96ff056f638837e169bc103dc360a284c06d3e68e79a91ee5dc14ca97ba4242d3b7e09ce531fef4582ef2383131ff5803a36ba05e260984eab61be17d5607d49ce5f6db11c50e90df29def9b44e9e48227c5a6c8a1dd37b5cd59da7f40644fbfe795e5712064042e259063b3b834aa169f3cecdbc2c2fe16ed9d588942a664f6d17a9869f6532680f3b09fb420a875c225fff1fa2e26754b84fb4cc1ae54e4101457dce3d4b2c9177c78f5caba7b5498756bd6345b18f2d3b9a9a7e56289a368f4893b362ea98799b4a76a89cf3261cc1909ba5470d3e078872f1b7287a36c986d2113a29a50964d5c80bf87f3ca481cd6748a91c31e94e8ee8cfda615ab9249eff787bc615e014a11e75cae53f9f9573e8604bff5b880ef8499703463db5a71b093adf60981bfbfd00fbaa8c6690d0fc27b2aa75d58570bf3c1fd77912aba09dbff32274e73c575865d5bc73f9f1cdce8066f925ccd31049c777be01dad7452349408566f4c2810c13b95805792513d0236b4ba7e142e5c8c7dc26e0a628396c45861b614c5f8d627587adb6574d276e68b0f25bf0e7f10e439e80db2c8370813b4d2b4a03c6a5817767d5b328d1092ce45033748f570d0ca32b19d25fb6ac12c6f8b484a8e2ea509f4a11b7996c9e6d3abfe2f1d89ce3a2705f2a1adfeef2ec141f1f894bd5d5fb9b605bec011c4753b25c4f2d4c5d35cecd409ca6d290eaa52e416cb0d601918098a9a9dfea46cbe6e4ef61e67094b313f04448a5e0706d49ecccaf23d083dc8f4c8eaaffb8451746ce85851894960e7df687bd24babc959bb71084e93a0b575db7897628675b9139ab6a9c31bddd58c014a860a64103053ed333214a4e8f928459e521ba4bc1f1936e7e2c0ad3a4f35f5545288345ed1072a302aaa4bb422d041ae5518deaf10d869d939ae43d6bde03b25085483e13311288a27ab6e211079d9141ef89664ce6289cb2dc18097b444a5268fc230a81bbf4e01aedd925ce2d1e73a809f5c1bc6a33e91b28059632d1c7feac77df453a38b5357cdffa0d234d2a86ed0fc34d019e7ded9d87cd6dbd537b474d448af3cb74aa45ec1c6bd7c59a7078820f11e8bd9e8e8edeacb24372e4dde9451ae6649bf4abd605cd6730703437a2f36916deb13b8e8d183bb4d7c32da9720e9f1c23850663bda3d8c268471de84d892fbc28e8ee22e3369c196fbda6124cfab2c54e15440c4e27e4429bce9c4297287f8ebe939dea95324eeb981a14897bece61e35701393eb3ab659a3159ecb19e6d09dc0d2a630a58bdbbac1e0c536f2b6e604eea740054e425bf34f8895719a7be3a4117580571087f49088652af6450ec20fd209020693e31fe58b18eacda49b3b245dfb0ae0aeb0d1c0f036de53c8096582b813db096f3df7aa38da5803fe591c14166bee99118e227b2c81149322489a56a696ae3e550e23f88224c8db8ca0f048d246c9507ce33f901b859235a225e26bd0f35a74a6a7d85d95745ff6d45ea5aae51ba42f5fafea5d64f7201f905155e743d561cab3f0dc8d03398852b8e24a1e27558266a7a868de044c72d3d1d064775bc9de806a8eb7d2d735d70e37f34c33e4c1a08828f333130106f69c60f5648d4f4419bd6cccd57188417521dbd1ea4f0ae2257032749fe196667ffecbb3497ed10ad35873c44db037ff18123195fe916afebf28a5fb90d91c91c702b4c4f23e4e9fc6badbf1b678fe76ebc11333a6db4340e9da126dc84c2ca815ebeda2f7ecb7654df3d1958a7778ee2678bf64578ccb93cca228d770494f0d59ec89f1b2800756690429060430e77c0b88fd7c2214c559d44474b39fe269ae6fee2ef6a35d70b219cea7f04bcd11706e2cd1f56fa8ba8d399c0cce55b18a3e995882733ff0c63c5cf184f3a38a91a902b433dd3c2d3a0dc4c50e30d5f3f9bfd7f60519ffeea0ac96d4ab84c8b8f329161f4622656f01f7252006f24ca1f23d8ce243c4d070a30183f5e4f1779703a65a48da90553b6e410738c84db270eb129c82a62c26f812d9628a15f45a8427f6f0c14100e398c488633d2ac86151388697db61992fc4948da136040ecbc0eb76aa7aa07908b72cb2bcb719a54414c3755229bed338306bd4b5491fa29ee3a16525db5c1007033db451d5cf0da302052fbb766de86f3569af6e46ff0945858f9dd52f2a72b2966d7c54ca7131aeab8b67266b94d3871c513d52e3b532b00feac4bc93fc6399237d0ee6d69665b3af154bfa646a3a578b9f98f7d858893507adf1221b073f2e4c50b274ab280e1b26a169886e2bcd4893c15548f11c7cd830505306779061add04b1e5734bc67df84980acd74574cc205543f991f118d7284ccb4c4d9f54d1871a00c6268a48f9b59dd4ddd4bf8f861a3cbbcb5748660a8182c6cb3e1d6bdecb54f56c6933817ba12afd74b8e8ff631203fa294aa2091f40e4914355da6c1f0bac085388d17345766808ae5abfa59218950dd369c6e256870bfb5789f5e1d4a97340f9a962e4937aeeb0defd980e20598bdd68425518b018d37067d1c365bbc247730db31695240b2e2883c58a8edf1d9e37108b7c5f576fa2ac269b26883f541a7dffb25146bbae4fe3900301fcadcb8bc0a00b0fa316ff8a04a6c55209fb127da406f9674009c2e20db614114fc5743383c2122c52f27daa70ead2d5e00ed2a50de4b67d8542b08675fb39539adaee2338e8fe19846d9a18e6d9682122c8a0a622a5b835db000d3d8318a87a76d769d872a8ccf01072f5f71058770d1b1dd5cfc5a5af457e62cd01da88297319668c5a8645c102158c5e7dfad2932711789a44979ad7e7337a69b7dcc29c8b1279b4704172adcadf81ae682647fc684351ae5da0ecf6ee9d900cf8d7d29e7ceda7385b98b2c05f069991061bfb00b1c7dc668886954bffb0ec5a198982e4ec77869fc79da7d95b92cd901d8c36f6771a64ed81afc8fd0c0d8d5f79d4f919ecb4506badedaf8fda5e3a9de7f8dea577def88c1e0f1b0e14e08a47ff85c2a928f95663d6e2f5d1e9dcff9f001c1306db2243ab0c34ceb26432c877597602bb2e8bf463d1f9afa8e39de67b3b43f54e80bae59896dfc7569b043319cb53e379215b0c8c8b8a7c67f2e6698d72a343f303e715db24d77ad2acfe83d3fa1a65d1f94d719c8d4516b021ae0a488cbc52b862b54cc3b2a858be86a9437856a978eeecc851c8b0c0845cb597ce6b093dcebe0c05b375a5c823aa03e0449c9772e35bcc145c1bd7e095afdec8396017017895285b31782509314209e3c79ad3a9d80b0f7f54390d54ba0699d23e53fc0eb266fa7d777fa244a7dd9dfd7d9313f8cd3980076e5ab1e62c6aefd049aecb887ddf2323fe90df38b2bf76a36f305ee2b430ceb0b2879c63800ca9dca94a5b0dd6ff768d516224ee7d9fbe04d42ae16f8967182d55155f0125526c8cbb1f651ff2867c8f301621f65dc3334fe96b5ef49f9d913c71420533ac86d769173c2e9401ab74c243696de8db47068b7e0de56b63be10cab5a6f09f7740021fb9be99a60307a90676a65c5578f9963a4cc256829811fb2b45cd13085e33c2390f21f9cce78ace23cf2788527024e1041bdc1a430f6cdc57f36b617c1a45267990addfc14380ddb3fff321b0eb61bae24af2ad33de982b0901cf760ec0bdc59cf2a1859e9f0e4269c3e82c06c354ac7552230d26f955da5df27b1957e9570eb687b724da2ea8a377bd068ab26ca9cad6d50abd1d87be51c5d15186b4e0c898c7db513b0bdaf74df89b18fc88770e32e0263df00adfa8afab90f160e7f96c33e89d3533fbe0dfbf6e1d1e0799638472e75e01a2dadec7ff6d3dabd5fa79f3d5d3d74f1ae2ed72793031290757dbbc7136a782db59ff8c85370b2c08025dfb768a8d487797c1fb3b0568af71ac2cf2ce91822fcbebae6b7b154747d6cc2855262fc201294547a187f5db21858930fc157c57f9721204018aeef0bd79ec5d93271325891eb61f06fb70e6d4618abc0efceae62c20310a9063371e5cac44185c78c67254f531638d9c61ee5fa2a4f02dd8b8327c40b271ecc475f411375ed945f2bce50803d41f84bba76bb3e48fa814d610b83136a0589682a57d852f601a757fc627000fbc4d2e2e9035492d7aef38f07aa0b2ab0e8d444d6ca9f2c5b1f92253de07ffb0a17c7990a05665d1cd97f57c089001d3e627598132e8b9e963589234398950076b0876b316eec96c8148b8a2ff3fabccc76c488cdd72a928f6a53ed3a95296833ab8fec8b77be7f10bb1c14ee36c17ebf3108468039b7fdb468bf2f0c6982bfa45e09d88356659a47e95f2fb4952ad29dbc046ad40ea9aed96027fd6243fa4b0d4e2927b06ef2cb77bbf1ca0d66055146aadf8c967858499ee6c85ec3cddf1155c4970cdad5eaedf0afd1c9e7425871e5a1f0d03aed0f042a96b81bb19772dff69f012dbe5d5ef90d2c5052b493fd9c2d0f032d9ebfbc648d5c887cf90b1fd5c93647b4628a8da8d0bfabfc0cb568faf2926d741670a2bb384b7884da13df42a20b91ba8da822951014bda239c58d4e16a4e7d046c7082e741a1510b98c376c596d71227b03550f4cc767bf3c414bc2219c7e2a1cf049bd21cab406de78576d9be0c7aa08b2c24dbc34c752a6466671dd05bb9d0ee2a35b3d5b5828f3e0ce7b48de3f4021657c997e98db2004a823b5e7f98e5fa7d010d6422a023d7850f982512a0248837c6eff2322fb7b77b6862b712b3d094eb835559d72a14689b014ad943bcd3d978942bd1c5059ead3f763339184e10a9303b6b117e2de1eb3003d7064ebabfe52136cb6c999065ecee0380588c7743c848580967695f6c8e67a51a1286565e2a01fb69b104d74be196c30e4270505ef4491e8c8d0103116093c84414cd8697484bb6357465350c180a4eefc6a8399ef384bb39ab2e52f8ac410f13741d8c52f0d467a5462e4852c208ec49f65635abc2251f3a9d7975774e076aaf7971790b78aa394f1587b377075ef14d6ef3acd7c03a7b805f3151bb09847b4045c0a2921ce4e448fc1e0dc2c7833bc5cd4b76de9187654f65e78de35e0c3db36a464fff70f295b04c0fa2c4b03f660a5e875f1acafe4333cc07706290abc7fbae88ae1fa8cd454ee1150b6833e437fc7cf59c28dcf99d07123860aebf1e36f1d25f5c7f4dc3fc578f615dc25340ec59fc27b0c0a5d2b229a3bc7446e313534d9156ccf28a2dd7366ce439da125c124aa8cce12f62f49b57b190c0efedee7d9f49ec8f91013f863474e2df0cd8782d978db791f84b9027bc804916bea310002f7ec933a7f00d10af21d1176aef1a78249ff41ca4603b67ded5bb46ecb6c3e084b465e733cba989d90a92c839bedf72b51108d7bba586494b95c5d41906474ca12e87a9f88e102f0ec7d9ccb23027534bd71218b224a5fa1f21b21679bbaae9094339aa79ca68e9ce5e4cfe626d7cc98484f35db7ba5bc1409044b5e3f6888dce2e8709efd2283327ea95f037c4cfb2082e526e713fb8ebda8417eeab48e9d8c78fbfe74e0bc08129ddc3895533a2926798009eb2f19c884e2256983882467d876d1f6ac0bbf5027e24ef0dacc9967895c09a803e858799a4ad6e5a458b7cc3f73d68527f36ef3f2659d96c945ab4137241ae4280559092d502d739c30917b3263e7321b53bc2e15fe5fa9065ddc2b81d2bc11822fff8ff012c384fdbef667b7a3e8aa276126810f2237fa9a7b1c96c32b20937963729eb527ea326982b5017234ce330a12ebd1c93d05fc06decd5abc694d046e0a517950fe005ef86dc66238610be6716b67e54d9f427c845c9965a3146553794fc236b2b050d7aecc440a00c3d46c8a5149dbd902256a1b4b8073bf68521e6bc186c392b6864311d21bbbd72684415e21f0428891655e96eeae276a1958c8fa7ee031e2bba2d75ab525ae2d3100a20741823747d892c46835803c1dd38ef419474d9d0573d303239b10fd6c104891cb98554459d2ba41382241d86db4049638b1e9eef17edc3332e9038f51b15123f95134c575d2ac7e8921a48d38f7e5a9ad6d25c0793498c25347c80cc92f6de08be9c5092506dbc9a1c9a1e1646cbf7d21f9bba110741ecf26d9ede1aba247b7fb02fb8e80f773c6af5dd4807de848e57fa0fef3595cdf71010152e67c0b4988a28d431faf8a0ba01dd423e3e3d1030f29643fe17bb72f29a1dd80324a5f92ae414d907655e1d97f688b0140738afa5d1ea610b5a8d7348a508cfaeaf2bb8924e462b6516cf15a5f882b1df813803c4cbc0f1b821ff974364e3e312ee21c2a6d3fd0854d8e2e086979292a2dbf43d3ae08a016cfc384680f66cb64c97e8fef67917d2d3e34228ee539f47ce0595825361a264187504b2920c1182013784d06d418b89dafc1469d6f4fab5d8386e940ef11e46fe685cce87337929155f15006687de0d1b1bfd9f9487c4e77d623bbcb9c96da745ffe477dd362e7f6bbb7f37690c388529cab9ccbc577ef96cd0d32ec9981730cf47b2753fcc26407e27aa5b32a229ca3613c02806859097b75cc96c46655fbfa208aa902232821c2402e780cf933608b98f0f3c52f79b15ec196283f481d006bd43fce075e5618be101a717c810b2e073914c711549fe22c39bcf25f1d0b4c636ef92c2fc97651011b59f99c6a05157c7f7aab0fa1645cba9e7395dff61e13e6552bfbb2ef415f46d5ae71f0f7cfe65f3c5b39b5d34db04c5f166702b2fdce5f04c487ed3028accbf3da510c05cf104bcf5633ab4a44ea32df7814acbd72fefaa3f1da9fff62974d833c0b732ba563634168a75cbabd4b4785c762393ca75efeff7bf5150478509df5f8709fd330df110ca2198b5e4ce92fc5c613e4d41c21e79029de18c01ba130972557153d76b21c04b50d6e1a013158f5c47e8a1c77b0aa5ed18ecedc4a523a5d6b0aee7ea0a75572a0b54ad2cf96489577c86edf4e906b1913dea529cd7e9dda1310019c6dc51b4641a038ffd7924b80b9af6a12dc7f8ab09770612152004efca8a65395dd248c96aa4845452b884ae48ac74d7eca51b40e9bbca282e14a265938706d6b101dd7f5337d4b725c61e1a79e46a29303b78d512adfefeb0b1e7540df32f7e513b51def3dc7ce29176722ebd991ab5acf9d7617403c6985cef3601ee5496c7582ae4ec98593a5b746586fde02aac4a2065be599adbe20edb135e9d8ab13ac00add4fa50eb592d5630bfc4c2d325598bb97ee38956233d1eda5602601bdeeaaa8d2904764322021c3a64a144cdc2e8b76ca3b9825144e9ef8a5b9cb2ad281db08caad09dc51d19670b975128d5832d4a44ecab2dd4e392396eadfd322f9479c637e4fc10b55b2afc66e6de7ea30ddfb56bfed8d3e05e0a4d2751f49310c4419262269bc9a2ebf4f8bba0bb94d452fc60a2ed6bb0e578e4916ebbe0ff95ce265a115d4be44b6bf650b5a4eb94fa77341969b3c0ebcb07bb0c7821bc0709a93b4f2478454b741087a35cd07a13a685675ccc2eabc25d8ad3c7f5af234e7241f623b133afa9489f8453227b0a7a259d43721100714d2857f9615cb846ba363ef789bdee76bd438c40c7fea960a8bd55513033348699b7961cad63f6f42d66cc431e660e48bfc1adf43aa0dccd01e4b1ef20decfb4d5cd47021fdceea9e16328a0de9e33187ba04011bf12683b2ff85e8602730da0eeb96e5e1f2ce64112bcf9258ca1ec60093077f690daf1f0b4e95233a4d87c092d8bb758a856be895afdedce82e5148085f7e1aa71e279b8609cc1f89c8f2dfe7201868e754b57eca92a074b05ec878cd5db88ef975ce4906255b3199ffd16caf0fdd4c9cab70ae3872cd807b8bf4a364babb7acd6a989c89eeec35172fc47e398a85490a272b8feb983e69ba725f66a4d10556db21599d82805f6b186bec99ae06d8c068dcca64cd4d34b339b5e9076ab1a4fbd8d93d0a378cece535fb0b6fa581f66561d14b99b374f18c63063e1e7b7d1bb02ce395772621fda74625592a1509bfb7476f1089809039b7c2a26c2d64c320a5252f91e4414ba9061109f2860c2df52966398af4e4c210b6b269869702a909a19ba3482e066d50f8860673122de6304c1e9f974482078faffbbd3a24d040a2498cde2b5b943e20e430a95b7f307517543042edd7efaf93c99abbf5337fa85bcb7c53361c8bcf45bd1df499767c06e6b73ca5a7d4e06d6ccd0628eb48813e87d6a4cf519c1022b4dc4c5bb33bd6f5ca7d45509dffd2e6e76fcc88325810881ff9d11147fbc6a2a1b97fcfb9b5426003eae371b2992d95c0190ebc3bd6ef09e792f93e061a6ea994b32d401febadd90de2a5a6af4658c78c6298fb31bdcce8148b972d98b7cf0d3731e6d91f840062b4afac78bfb64b56e65b2de22231bbb313dfa49704fbf7cb077598514e25e43409fbc3b4e65db3b0c0ce5b835402e66bb09dd55a86ea57be1e582366d67ea58d365943c4cca29101ce70af911d6cc3cc204eb2fbcd07079a098c6fb99d632db6ed099b602002ae7000c4231c70832c87bf8b916facb0016104069e0297a228e4f24b1ffc9e37e8e604403ce491e4469cd0592bce9a5fcd2c1a960d2a96176952e6e6c7c53c950e2cc3efe17f4f8c0215f7f4c9d81c615e7c112cf37234325a9c6a041abee4f2e1483525ed7ec6f864c25985ac92e2b098f6ae42ee429a451305383627096bcf95018cb22cd9a1140ec78319de29c8b5211874042a1a2b6b2005f714d3c5688ad364ebf96c59216c32d325bd66b4149e8acf9817483c9f7264dee0ee6c17252fab5f1afb4e41f5d513ba4cd18c818c557174f6142668932111ae8886235d28355e8ffca886470becade505fa99472c03cdb64e9c76d20e2fcf77d002c69b653060cc13920b703afa67b36190193abfae2edea2b75ba65a8a94f147cb10c388915d2e620c813591476b40526bb0487d18e28d882d588be8c1b6c01b8c92b4423a80279b31e14408086d27f1795adaaee62273c4a2c09674948107482445230d40c2d51200613e4a7801e2d09158503d1c63e1913580a22056a8523239fd8b5eb7ad4d6688b65dd946c63c5e0343e9e207b744fe28390bebe69ab19bce1671db644f5c4d706294bfcf23b1045c0dae24a1b548f832d704c1171d668e046a29055765f64cd122c83349280207f713ec6d7e3362320df75136a5305a49103ba72b27f5820cc524a0783fcc92540a636b56512542300ffde127ca48755ca33eaeb4a3fcad21b18b0a6c2379d0b877c96e7020070fc623abe006cc2c593f76252b6698f12d8255385b3bfd9974e5664117d49c8de9b6cb9a59429c914a5072608a0075147ff084d17cc60fd434a95744317dfa1ba4da541305656914d0db60632d8fed8b42a74adb921786955adb4caab6ed3afb7386cf5c686d996964d76f5943d656b3626b253d844a20f1a8cb25bf64b6db5fa6d255772255752ca8f88a505d7552ec431eea87bee91b6ef65b41db17e7ba46e7941a26cc8532ecf3661d3d8909f7eeb51b2e1dfe84d0d4697c2cac299764c0505b9504389c5a729a62b2f08cce0cb7f8a89415f6a3042b9fd852745cd08fb5d7563172737642a9f6bacff1642080db28ff94db5213e710038c4184fa85f0485a4bb7aeaf54fda0c6020d611c6ebd3f3b95556bd12b3f14fe85437723e47b1bbbbbbbbd65a8f1c8b952ae254413535e54d0daa9973e7fa57ab34cd397343ae9a33e6c63f8a4b110a0b8ff598d8138cd8eb62c0b08e68e958869283e5862e0575e39c7136cd836f9443e7862e0525e78d508cc5f735cdc3d930778cd1ca78dd3b07c1e7cf8f0760c6cacfe353f3a8f5b6b1215715b1d61381244b57d8ec271b3b35c8c15b8a032d754d70f76fd67529068acfba633da64de9857c879f7a46664f0492d0a9725d2a2e71749a6efca3168a11ca56e5912d2dc50bf0e0cebe6106ee3c2a21def9448e14462b979fbb1d57b08f69d085441063fc1a638c532ed56044622b2c53c9e8a166fcb1f62012740130c813b99c6c1524a61b72951179b9cf6c56242bc29572d42ff71e0d36acd9d31d2bb0f46764ee699023a458f71d21c5584158314887f41212e359ffa21f9f6ebf75de11ffa3d677cff25e42fc488c7b7aa4f5befa55fcd949a0e76dacdcd3a220dd778414eb7ea394fb20acef28c6aac32207e97edba851358af1acefa8f531e867b46d528f04fc59efe560c31a3bef48f7f6e30a859e6fcdcf0bdfc5747ef835fb59c429cdb49f45fcd2af48ac5f18b38c9f64025a5e52cf97a5df7e475adfbd7722b4be9751f7bd84b0de9f553b8ff5ac2772cab1e91971cecac609e89eebd35823042e223e25d80b7024d4d0831bbff5b343aa1cbd19bdd67b566c5867b6630536fbf8f463adcd7dfb4e57146375a4cbfacc7b01b14fff957df721d2cd4ca81fae3e3ef55e402a53ddd667b1fbb1fafac9ee595ef79af44224ae5ab95c3dd0faa61835b0c165282aae5031755b9f3da07b1a56f6da15458f487f186fd6fd583deb930dfa0784fb2cf3340ef403fceb7b25b02dd66bdd1722c9d80de95fc9d0e76637a3f4b3c9f28a6419fd2c760ff0cb755e9178b9af886f5fe85e96d198d4d967c46fbf5c791ce8d73e1328fd5e3fb2ef05a4be7f28bb1fdc60483fd2be6d3912f4110cc443ce1e0dcaafe39c2cb93010f7fb833d5b4ae9359b93cae539e753c96c3ca8536c910a7318b558f0d6a128050a150af9cd11055888d983f8c523ba5ce9e5f613c1e011285879310822bedcfe1f18944188b90d24defa452706923f2c61bd58a575c569ea7664ea9aaa24314c5a2614eedc6e40ebfa73d26d8fdedefe8d0245d29a52b1796cee98bd6d904e4a4106ca420a6d308f301eea98655946a3dc3e6e7960b0ee7e10e0fa34ccdd12b1be8492a684ceb9a3259973ceb92599f354c164712749994e09d5a6821455ca93357162369973cec9056dd2e4882964d07ab0a658a1e7888642082396cc07ffe75da87ea54049ec3084154ed04045455e6102059dc354032b3141f650dcb007d4144e6ec84f5041526fc258a3a429a19452eaa50a2a6a157652a8e20e8d32e79c93ce4922abd0228a0a3ee78a2e0d0b090834349440872aaeb0e2c9ffbc79f39455b14605695dd6c75a3071bca0f64d1a1c59bd0cf5a6cc0dedcb293e9b72ce3748ace077ce39c12ede68916f787012f98d0d4b2cc8374a5254ddd0be1072e4c869aefad81aaed8a4e887941bebf0c840459fc6a0c76e16f16def039d75d7009f1d9dd545fc4a59f4fa31affc79e52743eeba0fa25e451ded2c078a28a5947215853806350f8dbff0bbfc610ed7c7618ccb40714d0458a8c793c043f139c64e36f3478e1cb24ce33ea7fb941d7cffaa6a610ed787c1ed8ed1234ecd28986fb33a76374f9613d162aea05b30a160cc2c2920e924e0d0691ea2d0c18e17477899410c1776a40e52bc10a5704297b0cc610799e509a5945236db9299a539e79c5bb42496ce6001536689cc218a3c38bab80e021531a490c50b7660708a414561270a298c608edededebc00792363e08a715737ba96ed0410f884c8df3c98b71a3dc6e81ee8910ad12a1f4941fbd51fadbe97907aa4fdeab35d100d29cb8b525b836050a305954be3b21a2d92ee2ae9fa1663e762a0d690b02110b96e8df8ae27567e3abc8ce66fd33b92c27cfa47d9d3ef25847efdfa1dc93efb65e1587741e6571d5614841dd6e06dfe6929a557ab11af3134e2d78b8df1ce90b261e50924ebd95aedd71e840e8df8cdbe703aa5596c31db7caef40b91aee6bd7bfdd4cbe873d461f4fba83639cce46fad785d57fb8d3fb0d9d3cf3edba4c746b0f68528d4a75e18847ff5fcbd7ec4d79efbedbbedbd82dbb7f7fc85aeeab18725dc50fe10d7d5e86fabee39efc8eb47f6abd5e7dacb8c662f1becbc7e5f7df4b28f9da669da735e1d12224529dfdcec7fd0cf68a5af7df54ca0affd466b5d7daf9708fedd1bf5733f02c7942cf15fd2cf7decf50be97f202265b4289b6a98628c5c966dcd5572488332f3a10648a2c776231e1b729c127c931cc75b59997dd7de40a615bc3be7ee3e65dcb05e4966862eab276a758f05451837c6b8c48a4be365f3e07169bcab2ba2e1fa0bba22d7054b66b817bc055dd10516fc8caec88219cf65ee8cf7e98a66f8bc8caec8a70032a264e0b83264bc1d7365442941726524a9f2fc262a05237ccca9f3863560d77d6d73bb28244144252d0d453279b258cde76a3f57d3c0b8e26a618401834c0e28305c30c01765044085c31ad7816ef8737de8ba7781e7ba175dae7fd142175baeef7822e9e9d28832521084d405cf972f7440a2a44649621142c4192584b83367694af6f0f1adab8b2b61b8cda52fe9db00ac10b9aa252e7d59b9b8e1d26f592323ae9ca9a109942e26903c68b182e32ba59492c55dea83c10d6ba4d962882a2855415cfaac05144f292d028719435c5ca5d1428d16488ae48f1a353c74c31a2b9872a91aa815dc61228b2f97feaa2ba23f7628163c0061a1e6d2d7ba22ea0202aa8b3ca410183c58e0e1e14a2d5f5ab42b9a63ae78e2ca9f49288f2b942e6582cea5575cb9d44529a55d0d3b6899e109a8289cc81d82684329a551892b5fca6a0410ad035cd9a4c7d2a89366050bc2dc71d59942698618a70bead059c167aaca823a5eac20038cd30c3b4e96629471f2b1d342cf1a19766e7062a798a69a62d82c515a524f3d45b0c022f364c57462a85a76d840e1ca24b1ecc459dac254e9ecdc5995e1ec78a18599daa044a960a856539c645254da0d4fe8d454f58185d9942121472853459960c14798da32a558a14700c324a1acc013471853e577902cb198a5762d599f79394a69e986f63fa62a9d3063c4065466c2c0943063260c336f02800233564099c90206eec1ff018518d3303ec3e6cd0842d0f17721fb50679e38d9accbfab8a82e33c1b441155b5f0077b03d337050c6763ec048605746dc60b2aea81e2c8dcb5067daa839a3050726b0f632141a261c0461e56528343074b0049a18b8a0a90216292555283478aebc1cc504889a467ec040524af9335e4687c085667cd149c6e7b4f919b9c5c9bfa393537b216850dadb5e062cd8beaf7e19fff28ff148b157ff8c7ff97794c5fd339a71e413a570623346082f8208f326d6de14368c4edcd5b2854bf3882f1166bc8c1732e37bcd8f21e435bf2016bccf736c8637c5f09a5ef367fc8bbecf7c19ffa2f463cc789fef288606868d5ba2d30caf4e4f460c3b592d5bc442710aa7f0001cc56418cd16da5aadebbb19a760a16efa81857aca9d78a8e7ed6f5fc86fb80d0b0d198e3ac00da35310cc73f226060aa3d395641836e5423e45471b100d19eb4d55ea80815efdc2a7e14dd2c730907ce9f235505ae2d132a9f565c78ac54ce632475d3e40fd3008e65da6c15a7da21030900bc52c0c237ac3fa421d6d40344f66fb42171f400b0a75c0408f7e215755d9f8452c0cd4ffa6898d71a83450a2ba336b20e221edca0fe10703a434c240f2a50c6a50d24062c3a0fb2fc45588104519a5121a141465f5a15f0d5783f25d4f49e23031c5263c24b770b95a8c04312221c85410128884800d150fc9205808749893161ee2d897332e3251472465494957f8d2cf3e99cd7eea713569529b94b4c4431dbf081ceefcfe0a033592fe72fb6392152b6e686fff2775a1a2a27092ee002e434901c3dd5afc2359eeeeeeee524af9bc926337aebb197d38fd6ea8a8a44b68b0692029212669a064e9ef7822e9f6c73b3db8ef8f639a47f7fd318a7d90109f044025e9c647bafd118a87b4efef383da85a055f6e57cf9eabb56ea1e5cbf52f32d1225abb4da5795471a1d677670083657deb8dcc4b89da87442974f3c85483a51f910f095bff82276c1899989a877c252c12b21ecd636e3884c7c8cee306394f3210fdc85402fd76b99828c779803ffd6a784149f0af3f6bade4b1f2c75c60bb3c86f5bdfc39e641c3fae5d743d362ff06d1e686b1ca8e226c18745b4f36fed956726cebb21cdf1144801e02906c226080201ed22efd1e3c945d4a298d94ee6890067957580b368fb075e9d3a78fa379bc44982fdf68befc5e6e24ffe534c8ebe16058f0fb06a9f7848dcf2d1ef2a74fdfe5424eb15d8bd78fecf267998f23ff1b82af20b1cb135c945f12ebb098041737ecf1ce64c3ef6e23b6b8758d06df07b38e0b6e5cc1b11cfbf0501d29b788237f4c6a9fd78fbeb7430f5caf21bb600561c35aabec8af43512af4767f9137511bffef93050af56b4bfc6cbe95397b3bbb3ec9d66efd9f7aaaf5a332dabd98a66b4468334fb5c0c445d48946905875da8d6af66f139ca961d65ec2867d3bebe84c423d947a4d8ecd7be5f46dad14b483752cc575fbfc8c45ffdfce339992753c97d822596b0239528a5948af912a6c91c33658b3995d426d9145a9d25b78261c527fa7cf4c1ee75d655cd42326c3ef5a6cf902bbbd3b4164d8a1224a14a73ce3971e0acc89aa024c86850a299d214154c4f64c8d8d089939da1d366632e5aa89374450c5f5889f23fadcb5074cad0792242894f09cbb97a5c574baa25852403fbf23753f431bf768add020ff9d7276020f95576fe0555ac43d9f935d34ba766f26abd9db8a71c2669d59572cb93ed0fa55383528bbbbbbb3bcd6a56b39a65533a15f13c72cb4c63a58cc997f225a5d3952fe593749a5912175bcfc8ec2ebf38c34b66f6cf04feea00bcd8500ea08b0debe54827ba942edb3f966ea4bb7b411a74b11c638f066316fbf10586e5675d6139cbc62a39dde8aa11b5029b1b81e460f969ac60f9392e2c3f8d26cbcf6961f95759587ece0acbbfb962b0fff1fb698d952fbf23f225443b32ebcbef6524e94bffa691cf77ed031d16654357c631d72ae9e9c95aaa6ecf9e3cd2aae8c3bf6ea25df050fc3a06a9c36dd9e4591303655318d6df2c75f0ac04d3091bcaa62a4a45a9eacd92a20fff4ec2427d03c3fc5b0505ddb8a18106afd70d69950c1937a455d90978c83f1b32a7e8237e5d259eccb2afc618a43583e5a0fb65d7bf12c2c84005eb3f4370ffa0c12f96a5571692672453ca9c65f2e7c7ccccccb17f91735e13e6471dfe8573ce497d4e77671f43bf3f32f3836c57aeeceb17cea79ff48d29fff44c4aeab37e6c3e98b579d0d9834ab13dc3fa4dfef931d07c39db063bb3e7ccb3f7ccdd9d4ee7a4b75fa553122e9862fde51070052a3fd9a046e99c93524a69651b73596599ad2bfbfa8594d2f7d64aa394e9277f8d95ae7243db1c1c1b3fb4769b2fa5d632217b7bf9f20db3eff5c35efa56725b99e1b2496da756b3596687317fdfd1b9fb0423d90581afc6401c65fdd8ddac3e0b8a3f4ebf6d153ffa8842d661433e30069b7d17f13c79eefc000085ed9e7b18a5dfd4697baf9e3b23f1fec8600525a8103e2019aca084822bb39a7943828e6ef6e4759ed39698c47dafd5b31e2976f45a716fd4fd5196d567c4fd113bac23f24ceb07540e04095a7536a66672c505b9da576ff34222770baa755bfde6199137669ccc3167556b500fb0d6ef2a653a72b7a05b3b22dfbc217eb72d7ba2ae73ca5b2f145d8146e1c6f899c07a9e3c4c3d3f645580e3b2428aa156d13452cb45a105153e3b1df87abe0adc215fec351b27a06a14a87d6b833de4a7860fb6bdea853528e0dae28324c5dee1c6d42b77a2a0246c2a777efce2d2276a10dc3afdab3744bbd37f2efd420adc213f57360f198ba2b4dca960cbe001f0fc2e1a6c28a5dc7e0078ff1978367078ee0ee53ad3f5a5eb9ffca8e5d29f1edc3bd32bc2527e7d27a7ef1efd1e4abf5b2cfa1db73dffdd322e7695b6d25418a8df29d392cfa5c914e91a4ad5604f6c2a3ce4b7eb58b9dc2da0bbf30e03f5bf1abae6f627e08654eaf6bf7ec40fe79ddb318e8cef5a2d8871e89ae843aa79b00b517bc5e9125df2f61933aef4e61b0094b131261719a582944be372940a4e57724f13cac6383ca4cddbf3126183e7e085800febe46f4a80beefe037f080707836dec3f130cffb0e827cde14d1ffcf87fce4c6c67fff46dfff3ced5efe7d363ea39feffb8838ac8170bcc77770bcf737bc0e1ec8037a22df9aa311c777248b3f8ef77e03ef7bd0bbf1409e8defc0fbe7c0037a98d7c17bde919f07df3da39fcf3e0eef083b6c83204a6c7c47b218d978d81fa9ff1db1f1c19ea877b8c1f13fdfbfc38d8dff0fedcbdfc8c6e378a3ff9fcfba977f22e0781b6f64e38d7ebeff1d58d8ef6f3c070ffbf083e6f7e1f81b9ff73bb0b040dfc16ff0e0051f4409d077f03fde911bfff33872bc87c3fb1c38be1b379e633f39fe7ba31bfff3396e7c471e8eef891c47920601781168e0e1d0e003f044fd9a36bca58ed9f8f794304140ccc62f75ec7f7a416cbcf446e0988d774f88c7fe6f784772fc8d07f28eb218e9f80ebe23254c121003faa518ebe0757c2fff7f8e1d2dc518d0bfdc3b92c528c7f7f2cff11cbbf1475efebd44d0f138de28c7df781ddf5116a3ff1cdf5107ff7f04f4369e63389ec8813c8ebd67c36387edc0638b7018d703485787d7081cc3f147fc1413c18d17018ee7588c85910948098eefc637af743061de5cf22607de065e00bc977c8efd7cf18af7925facf27dec30cfe398e6c5022eaf25468cbf315c3786651692317e2e7dffe4c243f6dbc7e743ebf3af2f74f97cf8e0f599532e7480eaae9fe2823a6e28a76e5be0c509b91bd2a513a40c2fdc71c3b914ce23b8c1cc7e1e3050f6d26b58f63b82b2677d2178b3ef6453b67d2bd9247d4269b02753f6cd2522ac9cefcde33d8e6d36bc23b6a28a229262287448a05012cb223f86b2c98697c1bf07802506eaa5a8a464632ff150d75ab2f1ff05a87e8a4f36bc230e63878ba6180a1d12306462598808804ad27d01f1f94929cdc3bf5794ba52f70ca5734d1c7fd31fa9629517e3c8407a75065a9c4e4ae716d65e8e6a52e786d6095ba700f3d849b54113343770b074095818bb41c1e21811c3096ec860b5cb514d7668e2060ea6d8ec7254132a514d5450a1d1ac71185882a8264b3026b65e8e6a920456452d2dddb0c5883d3fa0952c00781d830e9391d3b48ca39ccb67b2b495c7ae39f8f3355c96ebef18db0220ba6ece07ce4fad2e17e8deeede2e996bdb6e29a59452cae972d5eadeeedeedeeee1e4677daddddddddddddddddddddcc1e6b5633dbdd168c891cbf7ee2cb96d3a7f469a5f4e8eeee3eddbdcacccd63dbb81827c7e1abde9e3be7b43d5c10e7838dcfb1e9996105671f9d9eb4eecc2d2959b29bdc26b4d2404f4ae9eeeeb23a33334797524aca75c58172cb28375db2b816e703c7c6f7766666890597c5c697cccc2c8362581968dce5486d4a275a9b34b35c6b955a46679eace59e676a92524a3549bfd612cb0d4a2d8fbb9c4efb0b7c95ae71ee56f6d0430f31e41472dce568529335a3938ed46a46670f313030e2c96378d0608f1e2c7b7a68cca0b2517a936696de217aa259cbcdb362b78a110c8a42b109cf2acb326e3e07a475b32b5b4a6c7ca2bea2803b1bb7e23a6d55a90780411804ae7b0ccee7ba1ff2d20910b9b856d6b2140ef89e8bc613cfa6553775537f37b5f74c4bd497c8870ab7327cba46833e3e5dd17c9f0e8478b7ef91351109e103063491e8e3b2eb2a8081180f118765868c18b56948d597b2baaafd2a41f974b716c72f906881a10446125c1e2a3d75f2741191b461a590c38c242e2851a25e71dac2c275289f0f5d6acc8d5467dec42dc6a51b5f32c9a6282638dc5056915602bf3557b533ae1aff036a727eae5ae7ac9e03f31712b9fcee69316ace9e11d97dabf5acdf9cc7cf5baf46c36495a42b5c4cda925445ca690b978e49639a4774aae1b6969854030f456b5f0cd419e5be90b3cd43d3be6d83fe5af36885ace7063b20ddb79e763f58dffac2ee877faf1fff99c07d7d13581ff7b1731edfcd0bebbdb776f6e372fd5416c45663cb6f296b6b5ab665f57385f2fac88fb7b0fd527619374565839d7d3472552e06a795317e1f2748ea10db25730d1777f4b136f2701e91fb6958fbc795a53733478dc50c245faae988d4d1842de4fa823556b0ab973cf4bacc1139ac351937cdbb526a14b8f5d3a17b2217b2600e192f49b1fe10788abb27c81bdaebc3403e633608d1076d14f2164720982fe70bf22708f65a2f7b0c9ad81882526280c5ceefad2b821297ec09f2b6d2f471811c59f6072d4eb23069c70d665d7da275355ab027c8195d51f3990f78a559502de058210a777b9f79675cfeee4090cf711fe76d2bad66744abf1bc771ab8de32c6bd3a17ba1eeb3b55ad75f30eb16602f064d408860ac50b9fe12c808ee7c39878c97a41b9f8ee04a2963dd04bf61816a4900b458d762fde517065961c0c4fafbac33ae3ee7e0baea75f66f48d06dd939a0748d6ec6bba29ef7aec8468dfb0171ece8211bbcdb13750cbff5930de82be3eb0848323821c1020625b2a071136386ad7ef3647c2b2f94778be1c5cd0b8feecb7ecfc70ddacf6708775b9f0d3595c16deb3ce95b08384f3e6fab2f3cbad525b38e63441a6563fa723e9dd3887b992165432458fce49c1f90ee8847bb62e3e253c5f6f21b48bf34322f064454adc72552c788878a14173684b1248822b0a041bac10b2a52f0c49aa617a03883a51ef1a5890b4b5bd4489971e20e9427636a70a18a34f38212197c50228a175e52e044132c984c0933450a253985a5bb301126490e5cac5461d102945c420e1a178620c10ada784953c606364ddacc3047094c1d32588a50f183152956cc14d1650c92152469b2f06108252b5188711c783042c30209dc2041c39a3261d88b28787e386229890c45c43c0a9326f06899414e5211601070c50a2a2559e84c8142aa480f767218020571b248e14b12121f78c0f24445c78d12333411461b20f0a8417243145216195878d374c4164c501af00186285190c0f3660a05484c48be809344c40b4c5978d12189282827a0a0c9d243122e28beb800c60b4b9078e1e9d5449326a29ea2c890844422ce1b26a0f2cc80e4c9982e3d749b33499871a24c0a787e807204961f5334c8f004551518d0482e0ee7688a9492921b92ec70c5882fb248c1c1ce114f9c48228bb02c3081a50c1e20bcd040440d5257527053039425e4c82db379b0001c12acd0c59c1380791a824a082849ba8c31c2890c42fccc2084933a2e8019834706b1c110456c21e70b0c4b3c915a6201e29c3b1828488cfd183dba7497d2047fcbf9b894f2db06e0c532736c61e39fed7fdbd1e08423e55a96a282caf5e8cd0a8faef605c99ecfb4af861b1b1a91d7ff33af2bb259966599b31cab7d4fd8ec897ca57d6683953fe78330f61101d6317b0cdd98414d82c3e6134008cb178479ab77b780ecb320acfab357f6b5c197f6f433ff97f63103655fa5fac2cc5282534018b22ccb3249b92b9f5969a35936e79c349392f9d9a564965688bc0cfb6ab55a497777e9e2ea5dc732b3e5724193f53959f21429a59430068a43eee04f8d70ce39259d35f32624b394cccc524a29a5945232b313ac2ab6c15d00fed81579db62a27f5a16fb63fdceb2efe88f8ef891621c97fa6d2cfb5ef30ba284484c7298fc1c6e2cc7b4cf92e52542f6ab37ca7e95511965af7d47447e81a7ec439ee25864a21aeadb58babeb1bde56ba706e57b66ac7f3f295929656520fe4660398a853b37690496a35a48ba1a0f55285151b4cc1fb0a85158f82259e79c73ce6f86a72c2988422a4e9dd804e2ce29ef9c73ce282876aeac320aedb3d5afe25ca0bbbb2b9a9991797d6a9a94ac56967d8b2f708d4c6f0f071b5dabff5c1f1176f55eb4aa736a9aa6d1baad3c49572bc642c677df3801514c4e709e3ace5add7cf7b1ddd9fc699817ff6a6dfe8b962459b65e45f5301c193b4607a37750501838e4f72ef97e411359d34829074d3a77528e41ae17eefc1a3be6fc1a656c92940bc5e8c7675895b65d9f552297d218437214644727e8e4b084ba5952479ea15382254f986e08e2887af2e4facf1326782403eaeac78316cab6678ea8041ea25f2debf983feae42ba27ee7cae85826a341577c670fd2368d3f0100b2399f0e1fa00b4a13b9fd9cb9d5db24b3f01309003a8940278c0f5e79e3ba9f3263b20ad1f0a6c2baa553a23730deb276aae824be0a1eab12bd2ec4ac4bc91835d71e1c1f6e02786e2a9356ce2dc59410855f47294933437fca02dcc45d5103e709102869730f2045b7871030c29b0810c1236fc8025c825c50a2edc2c718309a27832473acd3373a20cf15b94133237e427285e739dd9506597a39c28f1840b0c6a2995a9e4b201e76e6c86c0c00e65bab860064e17282658f26632b1444e0d486aa05343d2172820020b1f9e9023656e804a933356a058b303179985c9d28a09262d38993f9f6813a529b436dbea7294132e576cccee85ae382465fff42bd2baac8fb54feca65d8e7202844cc1c7449b945ad5b41ab34863f4e862ea8dee4aaec7a406659426868ac2f247270957be7b174cb1fc52724bf68e6eb60c54b0fc910121307f80d4b1973cc39841068a3f35a4e5cf66d44bb03def60201b622cf3d768707a4b2943ee5a8e6777fc9658304ba794ba9c9e0e4456b2c75ab2f27988cf8df23959e98c69985d0ce4534379e7bd7bd8f8e7b3cc595096654fc199516a5d73d6ca94259d328b9369fc9a3d675ea0eda8044e974435e9d0d4000000404000e314000020100a07042281301c1ed2e55dee14000d7992466e5c9ccc635190c32808216388210410000801060898a9a1210e00686d76025bc4ea29c66fddb6a2625ef32650a17d51929d62e56f757534b5b9e7b69a46ae0504606dd7041902b0ed88c9166c7edae2f917205454e88abc5ac38df461653ceac3153de72f6d48708df4bdc8f5494b6e6f0e589b8d05e1a32f599c0eb4703af805f5e7ba19063c3213553b890b1e3413f8104878e29d927f86f3302866c12b308e9971a5f0e562c1f5372fe18aaf8e2e40d0dbf772add0bb868d736f6fa555237dfae0d3b0d09a62de1bf91eb8496d23c5ea087cc370f1123ee8c4dd3f680660bf4b8ff0ee53012625341a6c7d3a262ef00e23d8b42b83ae8125ec0359d869361d26e8998536ce15c471e4ef808d8917825f6f8af2787bd0dea78a4cc528c7fa1ccb668f297dba1a2587c1d45e900e0e1a3020ba5062eee10cb5761b9ebed3d6a6d0672b8cf10a10ad077cb63340cf968c0f51a2c04565806f62d25b8e8db508a6099e4a810b9d053c74d86a66cec8875fddde71ff2ec76a5123d2803790b430886eec3d83b57e1dd3db0386ad98029a74eef4ad1fd92abbf0e77a4fb6aa8ac22bc1126c21b4315717640458ab3492e4d0679644ed8d371a52efccc24715b1d5a908ab0ba4bd7c94ae6032833ea11ccf7efa2c004ba4fe30ee2a1138e6c6868bb86721ae3d088a38a0e8c49e63b24a275a614fa83877dba3a46d7bc2abe565ac528244b10bee5e192a90248b978777b897ca8af3bc37313b80edf8696418bf91832ddad7aa5055822c6eacdf814f3f9f9f701fc49ee5b94b70642832cb9b883f994f2a0050fd767cb1d6a7618dd35b7f49a74726bc5303fdda29631a435b091c33f32810c0cdf439f1d1c71e520e206f798ef2713ae4502fd133463345999c6d44b54c9753a4b943d2c1c56e8cd6e248e01d70bb887a2bb224da2517a133f668b32dddd59f4438ffe030bc036072a3eba9ab97fe5a621d31ff371be3ff8f3cda7b6446c797ae9ff1a3c0dd77c0202b91eb36f6048e8b42033d1e9ec14d211a7d430a44e1c9956731f50156ea6d7b70d1140bd850de7c9e4e5409e4519fcfad5aa90cf606e1bd68e2c8f49a2db2e94a3cd7ed0a1e52790bca95181efaab69a04bc593b91ce64548bd3343ebb108e882c57cf4be98d06fcdce6aba45422417e8d4c8a8cd9fd09dc95ac0e273bbf35c6a92830b3daf1e96bd7448a6a9691c5377bf6d35ab30331450c9926b19468c9792ef4b554f52349020eb109f6ea8c8540457cf80334706f511ab5419a891c01b27f28bac0e8878f851d49142885f64ffbe325bffc7080865f3c8d1ae5391de18526bce8b43d3422a994182d73656537c21ed39cf4a578c435568bd8dcd4d1d0d4b8aa0c8cab19efc2c352b5157125c28f771c5eb7b36f7ac89eb8ae6fd25f3b9b8feb4ce8c9861a7ca6a4a6121de7eb91ffc4aceb45df6e58e185c55316ff4898059a018caed30ba1dff6e5e5d2bc7489a09d323a2cee41a363990e621e5ae9236bf1e9d04c747c1cf713ba3a6c59d23ccee2e929b1fcefdb7445e8f12382c344a3bc2bb8fd904dc6bf3d66c6924d4c746884e0300298393c069081b51d59ed0efa673ae13ac788742757d91846fffe312d22b0b064a9269be70c4f592c0c6c0de52d8ced45ef73c43a5beb5b2ea119b026b36a1effb37f5a12692677ca2c5541421b230b03791102770890cdc7c70a662f1e82aaadd35591c2b36d7013a371e49e4de7d03423f2dae50a3b90e9179cef4ccac2c355af7b1e8bda115ff7f73a32137f52a4f59eb8c6910e1a1193ef2084d82c9d356319c934e1fdc96888d0be44f164cb03cc48e12cf2cb060b0a25a9e54b91e06353967ab779355891c3dc4d5630fd838d8540f72d9e2e0a637784e68821893b91e26c441476473e3c5608e067d75ecc04043e73b800a7dec75044d97c73f0e933ce1b83430b4a851104e11e1a6b2069e4a1c1a262103ca332bca7b0e252e9c66c4dfdf2ae04954229363bea1f88c183b70ecc34a2b2302d3ba0bb9a111c06ec5a2dca278802217fb0855a3ec8070b1176240771efdec29d0b4880d4371392a170cdf200faa9804599f96c90c7dc90260072255633517ff0fb5095a771c7c1dc51dc867956f830a24485d39f37050231e5fcb01e0449c1a2c49b1e4c41481a94a700c177649de02bde910e27fa12161399dfc84bcac462f9142f5b20ac5b29a50cfa442a04ba756843f75e50becbe095baebe4b075a1e2dedb473f07352a268f32e322013f4ac1caec9e9796843fbf014747798d3180c661dd459b583abc0c72ffa2403f583034288187c83ee4262581cfb45a1e6eff1c18333dc76a1b123518cfc9219879c90dc6413baf3b142cd965ecc06d11508dbb941cdb956c18dc67141a85ae572d5e9e7e78289497fb28733aa74b38115f72453178729f537c645adc677481290f448b568a5e7734bc7e30ed2fdb19473908e08ef89f0e4f7324e7f1c9dbb86166edac1bf0225e2ce3801a1ff61ed21c025793a38f267ec88b38328e908ff60cdcb51542c8d424ef440efdbd7eab13d3dd9c8ee930882250051dc5fdeeb70b2ac9eb2ef950bd21cbd697f544aa562c5bdc5e1dba3a89c20da006ab3577c6090257b5ab837564a2eb0faf6063c6d3c8dc238054fabd9e346ee26029deb5232d786cbcb997eb9f237288aab003a534f990912bad043603937329654630adb6c17aa52727e42b9e776e716128f077f3333990953b6d51d344adff581943dceb93a3455d0b577294bc41bec01d431489a681f505c165eef26738811023a4f2a564dd7112fdff8d491e168d15e8f3501af80b5d1e19542ba325cbacb755d2b8af9717eca17680e11b6e5bbbc04bd11a8ab82dda34442adbcf5f4268d1d0adccebda03c2d016e87eb329efce80ba8b5c80addcc018a646550acf33944ce4a8c0a3a99038ccccaa050f73980bb598976e5920d47792da8ba2a7cd5e94a0c0a583de184e8f67ebe25b5d8c4c9c1daa61cc4dd4a2529296f5dcf99226e21b98ab904fe9253dc4fe610150284f374145d57f6d9334f104b7a92c2783915f6d230b1f5a9a1bed31536ed668cea09d414b6979e37ad5864ea0e712b725889622f2d616fe61611e03ea358e44e46af0981ccf3b25a462220d52f354dedcecd5d3f03a2169f892820944e343bb2c9455ae24665a05401e39b6f7867f7a178a544c27a1c51888c447f7710021309b1bfd87fcac3a9ce0babc7d84fde08063f1a8fec58de1666fe375287476adf88e8d03a83309f524de2674618c0ed83842607ac8ddd1bdc5326ee942db97e00be6c4d95042884d9363ab6d3128274de8e879d587779b72e62bb96127844eaa1f1eabfd65292cdf32cd2ab58d76b040ed1b39d738a4f154fdd0ff34a2dc6a1cc05a078814b075386993addf53cb2284628838aead645c15605ada73443d9b002004d930980570187ca6d7f22dcdac215541e626cb6888e985c4e0487d8e2286a221a3dba8987177bbb57b52163203ce118a1ba0a546844fa000b36aace6391cc654bbc9427b45ab9a484d59b48a9a16ceb9f3a1f81479393486371a2e6703064575dce91a832a90fdd323954a60820eca2fa9b24497b94651915cdc36438809cec30630136b7514f7169a381f181c4f93c2dd854806273e69b96b74da636c1af04d7539f3d9206487aba6d63eb9d8ae9f70d8499cba1750f5d4e032e3b8593eb412a36e382715c64f066c874f6970150148bf9182abd3ad5ae846b1ed687ccda5cd9b96800ab09087052ed6927c1c444a5fbf287ace9521cf27cd561892629188c58425f26258a2ef57351e5cb1955a56500e640e0e2c6df48a5dcd24d071482bef17d1899fd9f5630b3c036568aec8d0f6169bab3b695ee320130083f9f82d6ef2201de83da92fff4a66424813cdb2031f3da18ae33a7dae28d83102a9a3f3ee06c46f1d44f83b7824e2201297183c742391ef22a5b2de7472c4a7d4c6f47b809f68723112eea94036ea46fa763d8dcde494fc82f2e0606f80e583749591a2012c1d5a04f4b26d545a89ef91098c5cc8c3ca2dc80be25d28405725a46c9d1aca405028b1073ef6d2f94ce8a7368451c9490afecad731e617e682989c002f0d1d848f63340ee072ceb5767fb370596f4c182cf6a836d63757370a8320ee74cddd8ad0cc46927e8f9e64fa6c1c4587df416f96d9798aae74089c3ec5f0d5075c18c3763dd516627cfbc9b2e8df870996cc7a99d3e5631b9c90b64589ed12f1cae303f8d52ca09da7c26865a5a6f66fff17bd45245a315f5df46e7aea209535e5e30643bbf9388dd477456aa2a00ad4951520f4d115801f0be5e62670a9ae6fbece34b9f6cf672ee60ee0ead44152a26a3fae43e9f4588daa9010d97c7e7b0bf3c0551b55029443d6c9c00135f0d7830a266522780fa1cb1b79ae7ea0e8b14f0b3592de045824b857558e5cff771bfc9ed31ffa6b159fdee0964a6b7a534b6b0b159793b1d0c9a9c9c24bce3e66eb8cfc4bce036ecec3fa88b40706299a6698f4bcda375b51ad53854a0fa537ef266b37e9f2a718818cb075aa6d7703b5cbd5458bb027c58d54ef05f187c1799177ba2f7ad38c5de65bb4ab96a161f6e46dba55fa8d2908fdecb5a622ec76821400a23fd771735cee9dbae0df327f24846f426b84857d027f7b4ce3b6d9994f29466127cf1f4eb852f1509791d416975cc5576f68b2eb0813cc6cf025f77fca11b365f6688aee19c7e856055fa5202cd01dd05447bd22088f42d0d06e808588bd35394526e023e74e1eaea32b282610c7bf4fa4fde324a374b4cde461b7709ce3560abd8592aee09afd0d3fb4efb25bd9ed5a9e2a1fbf9fdba4bef0af6d5f74db0c0dad119684f9471bb57e5bc0df284472af5bffdb98623d08c45fb29d7b905ec28e353409cd8fbfc3c5e4fede3a8f2af098c073ffe6156539dd7c65fcf1e12c3410a7b69a5a24bb20af60b6c707b49b1d4bbb89c50eaa91c077ebf3c5b5db1ddfeb78d4e993807f1a34f62e6d9a8f2e9a5307703304f2787700504a8f61b467cafe2e9c80e10e9e351242c40da681931aa1736100cb7196ee7802ed58efcdb7dd41c76a54f01c2d94b38880324dd9686a0e087aa8efc647adfb53b21cbe24ea0a625ee140d4762277ba9f4affdadb938622f6a288f3206ed7b1c76ca34e9a1bd000a2dd53a424b96558974dd1eb75991e051dcccbfa83e81a22bc40a3bbd472c7f623d0d60b2bac20c48e9aa52720def41c406505c0cd064784d4ad74339ecb926ae4736ac33d5bb5c0b0bf833f0eb4feed4409b5793b879ac4f9da3938101215fc84739c4a78e81adb26dcd612959e08f19fa68928b55d36a715156f08e084733ce8c686fef5281e8d9f97fd146aa81a5a45aee9a72c426c479f4820db4dfb9a3122447dd75a4c7f69679991247902e80793237fb42853bad956f94c7ae5f2edb4638fd7b510c6b962026e3567eb26543b35f9789aaa5002ae5f311cb1a5798d42ce1f1e9d7a4d77da7705bcced0fb37af384891a470d2a63b2886cafd7d0930148e77e1200c207264232e931cc4935afe50e61bc5f8d38c09d1d85ef1994803de4517fd0601e40dfbf91030bc49e8fb818036d03ea29bd0135d20b90ca8d39077286dc7f90851a79b598cd5d5dc711f95496864ba1418173c3f0eb74a8cc7e130896e7b99b03f705f4ff8b4fdeb26c31f59806cdcc4f09d3084044554d1b052c0691e9ba0c6c1b4bfef820ce8d3f4e81411cf9d89e47575a8247302fde9bd864c628585df5918f07b270d202a938c58d1c4f70591d6887ed0c4b07916c66c71019e86dfd08831d08593892a41db5499d17345dde2f39aacf439213882ae02dbef297dc7fbb94ccda52a80ad4d5ddccf2dc73997545b6c39b1f29a105e5e10cccc06d5ff79954632ac034cc395db74b5271e0f1184209474cb4155c88a8d409082df84124cb5ce45af10656ef70b8cd49a67c5ce923ad14983ac3ab530a710638c66434738e03299f25be424011d4e2dfa94a71b63270f9bc0db460325e402953f1517ab84eb0ab628451eb0a474d4bd8d755298d670e14699611b4ae1454a0485728536c6ee46aae4bdc678e7c2f59e23fed3a6c86f87874045d571135e438fc35bbe4a12504ac96ed4309d64ddc2d021b80eca9b2c676bf9de37164d61ed2ff9ba205fb23d285d59ad5d0632b8e0c45942c17430e20559ffa7d8d69a9a5a46ab4506d0ac0147fb1e53431788e8dbbb44e9df60394c6e47c624a28ed0c1da21f97549357b16d4c998457170f0e82628b4f1733696143322f196f7d3f978825f3a0b14478e1c94de9cd0d961e82478223a705421a3a5d47ed49a0e8a57025e6c765bbd8d2cb193acd09492e6042cde10aaa1b6024e226123908891b69ac0f24205934c1bcac72d011103744640462dac81c4ab7404de9170d629d1edd2a95801a962c4d7934f10c127abcd8c21c91217ecf666a99f4f8b9bc421d4a8832618a7a1b503557f67add8e4edb6f8155d174ae0848c8800d4e411d847ab6b084f4b49daffeb5f9c7af769a887245ea40b55bb2e3be2c643771686fc5f6ca27f4a5af6b7a091f7890d36c0ac8761e8bc047864dd09cc59abf21c638f29ee78088d75de7979b759d3800363d57fd7a01a939974a912d71d2834f8c9ca82b942d0fca5d849fa3e9cf23c12611a355fa22ff3397a44a9e86b1ca6d5281d6a67f4a7bd706a7939c17cfa753910e99b730ea713ae976c5701ecabcf3323d01c0a6fc1e85e60fc82fcd4d9b4779eb4464e9f33914289d3dc5521d1f331025d7f3d1ab5f88c2ee2ec72b274583d01fa3dadbf73d4fcbd3beb3e38fa7b962748761c07d104a4a62ac9fb2442fb691d83ada38153f65164de908146378ac870ed904ab8aa0f6f6608293dea14f68a10288766f70accaaa3d66ad4ec19ff266b4427a240bca7d17b27e74f9bbd272930f9ac40aa9ddf0ad088fe16fc3f41196c10d4563c53991a27453b4aea9d1eeb37d52fe06ac2328e6382daf5c60610638b0032159d5caec79adf47e861cb439d60c8231cf0ea05edc48382dc62a836c49c24b34a285cc5783613501101f630d5b545d679918955cec7aa40cafccf208044306029cbf03bbeb2c9b7dfe2aa3102a9dde11859c45a5daea4f2c6cfc506b9e63319cb90c4c90c58de303071196b7d363f9bc82e6c4c5882bc661b472fce6c836c3cb6952192a3fe74aa3f0e516aa24655c579e052b9b45dc74da903df117e411ce32b2c94cfcfb11249990ba9e4312fd84f8e114ba6c184ac91ea88b692d09c01401802ced0c6b2718d83c95ca4f38fd4f00c08f8b8e30f019c2799df2b953995fd32a314d6ae03395ddbc732c60c7d66f428297e5d802dec21a26d4e9881016d34ab9b28ed0012e3302c10a9902f2d70d0e6b4ff4ff8beaace306931d093f5cad0cd0a1657a5331484e71fda0bf5d5287b0424f25187a7b0514ecdf6699ec5f85147475e4993eadf79ffb8016c9e1ef16362041c063dd373590d14caf8fb191410e62c23bc4b6cb04f3105d4c70858b2a41cfa0128b3c5899822cb79d047b1687834dd053a242d1687d2854f8e9273f8ced39e82d10d9c0d10399ff4d7aaa75d8f08a38c108a80731d31b792108ac21e09396d5dcf1f9606bff035113687fcbc695ba3d19110d3e9fe9307600126e0e0000e166ce053633df90058e0337d882c84ce184cfc8b9b1c6385556258e1de4031c653609f20949136ec5d639b1da4d100bb5c48d4f42b17be11bd3559aa1146c16cd2e160d42354eb84c150c1839708c646b064e06e04d085e3261e23c9fd105ac363000bc60441416a299970701609e3b541b6e5045b107b3619c4f496a725b6112e0cd5c4d6ea1fd55f9585981df823acf7939d974c98097d9608abefff5d29fe1c88519b20c652780b632683313624f1f36ef5575cdf6dab54cf6afa1b1301d5c0f8c0bc2196142b04c115748494c20d2c702d84c80d51c4560d0aa5f7b1bb10deba8833a06540f547582209eb3643c356a914661d811667cf2aef048b29ddb45a09a8eb2e6e12968b9bcf40d8bae67d89df437d41a0d063d912dafd639fb7c393fbb0bbcaf1720c09821c9316f36a36d0f0a663bb546dd98e51dd4ed65cbf240064737662d252742b994fcef1bac11060e9b1c290b8570915c623ffd5506cbf02b1e8671323ee0d7b0764d906442a240c7e6bbc147dde0c391e2147dc9cc9a916006d545daa04109458fd3e39803a85ef6ecb8b98982f7d3cd52cb34b0aafea94c562ce26e04b53006a7ad2f0c1937eb7d065eef0fcad34ff6ae80dce7c0e72cf471d69565fe5eebe158a1e95d8d6160abc4782328e5e9ca8438aaf43843dd70690b44f02e5bc894cecdcf8c4dcaffd72dc6287285aba75e03adccd9ccfea2782c302f64be619a09af731410f72614b85407f9bc9f1ac5728f8a38c8fa30bc1f33bc05476c1d4355e235f9b198760f0174120c32e400ea5a9bae1044194e79f42bc9dc6282d2e8e503f4afff1d222d027d2e80386c9521a4737d9f19d5857926cd8f45f99b84e3d5ac10883664f674bfd5d3bed864f302e2e503f3b038ae0974c2707c42fef15a8ac56aaef21b2cbcfa5658b2aef3ba27c27df53e9742cefbc741f42f3c644318201bae7540184792af7e95e87f6b27a94631543330ffb8cc2a062a366a7ade77c166490ae2e0206073933d7ec2a9dbf4e15be9e5a5a9ccab5d2c6cccfdc984f89970ed49577a9669247fde50920b31aaea972d25a7849b893cf16a0ef41b7c8977a2aa18e7588bb5242327db9008d12f5a2a593e14616c0a634fe6731741c04ace10c3754acdc06faa11d3a61381fcfdca365e41eaa37f516550589e91fb1fa34a7f591fc42270d1f85d3e69f297d5a76795436ecf6e9a8968ec686b575e703e3ea8fe10164f7bf587dc96c6f50a4a71efc3c9e070b0e98ef6a8c512edc3071ebe8d16fd89781ae45e010cf24eee2958dc61fa5c30699af775235f552f849676978bfbee5c752242596185527380f1cd40e18c5bef5e599aa335ee04d6c77ddaec1fdc1217c5c127374b3b69c139fa19b83c7998edb182dda062e757e5be1a2b6cb6d36b44968cf1dca232a3658fd299216342724067762365cff57e14bc4d6470e5cc75ec7a09fbaa49be4dd76f3f9626dd92b3755a2d11eae9089a8733630ab481a9e3d461943bbf1bcb5dc750292c1b5ec0f6113bfc9baa8d258a6c03bd29536f62691fa4a200ca6e24767e9dd9b1ad5aca15a251c57f7d615d0ebc2a8943d256095a28835d16ad3d6e60f26411beadd4ce208558abdd529f4c7ef29ac36f8e0726e5050ab329d6b041ee84db56cd83b9b95191aeaeb2a3592edf681721d5c45f001295e8a89e9446e78ddbe23f5f15bfbfdf1df1bf63dbeeb431c906279563457126b2a7481c6b7ba0fb4f8d8e30ebeb88e82f2b350d47f1bd243cb4bbe416b786ef6d193922cd16c5548f5b3b2d5acb3245019201f383d3047974cbc8f3160fa7e107f0cd0177ea67a79ffffd0e25e42138a03e0924702976104cca260c79f78c17a53800b2ca0c6603f2841649a411ceb6adabd228ea97622a2c3ad195cf9e8c029d218e3f3731bd741c018807db42b59a44885b40772b0488cfb0f3712803bec5f1afee58f6237aa9904c4abfdd09d24c22889d924754512b6e435b4ec22753ac1fbcbc8692001bbf386268436ba12afc57710584f4420e4dbc04a2716c5adf49a14a1dbe201107b737c88a07b91b0ceabdcdd53676d5104c3203a71eb93ba402e26a8764bb4bf069d957c795c62751614a91dd2003fc8f5b0af86174049e2867867c0ac114f1e40ade705abcdfc8e4e1458719d1e199c2f184527313cb0da23ec01afa53a2006f37ccaa3d50acfd8e8416aa705d49e058b2b82da0d7bcb3e0420c824d8136306e6b0301e501f00987c21c1b9173ab946e931638c9d56a794cc0b3351da7a604ef8d4b9bc3e8c2a1b004cbd60f58f72b0163e43b16a9b00e6eef7b3c3fd2b886ace554ee41ed11e31fb3816d1ab1a07165855057baaf5aa13f38d629f88b5d0901eca002e4d80357814a437357e0468924dae70ac5293bc734790a18c7910041f99a8efd39f673198574bed10744bfbc0ea5471f8c1efffb4556db3fb618d91d041eeb057f1d436456205315eb75469365dcfe1fdc22009c15d8515306c4fa61db008c9ffd70aebe56504c6c38c8414f00ff9a0084ae8f928824609b259beda0bad48d33b408adc1a10a51175919fbcbbe057ce4041c7be2c0bcaf9d772ae05b48523a962cb06070210d94a0b2822f3fb030e21a08226702a2870a9cc637f8c2b26660cb699dec04f55ae4b8814a27825f01dd3e8134d082f07371799664842184afea0442f00927eb038ad4018e760c4f0b746e19d21ef70a8400d8fc89230e151207de4e0c7d5b32fee2f775c3063238dc205b52c208a860d296bbcc2d67d03eab7e588b25f271c87e37a6bea86e05026acee08d694655c62f6c043209a868c632602271383ea3e6952b08c0ce28ec010f2d62ea4de050004451e460cd4c4121cc09caf28ecffb41d74ff0bd3ee48ea25d71748504112df0bc35a1d6e9d9382c069e63c60d88289e837638596f57fa22da3e7ce906fb85202efef24356e0ad964f6b0be5b2452678af4e6fea45d28b4f8e138c77d1f4321ede96b3bd0f21223a1520d7b74b1ac60c4d1cdde05d054e47f8e01d2e44d0e8ce86ae1bcc030af4d554eafca9cb325fb2af676aa3d125db7b4c5571daf742bc7f80be3b8ca1474324332a4db35cbf6ccc7bfef3a1a7c51a38bd02f494230ff4baf7a6693f9aedb5bd1ea6f80d9ce8a17b35f51b079bff1e76291297ac81bd95aa39a708940fd4ec86f2afbb97b4dcb4baecb008dfeabf9ca52f1b4dda758fe388ac68f6589f2b6dad34554db2b74860ec5526bed67b95dbdf3e8ff87a953c324a24efa7870cf3d73025020270207bf7c4be9bc8a09e3130473def0563ea4353304c5d4c3383458a0100fddca114f74e378fa06145f5faec7c772e9fb9c1e78cef7cfff22e844b02ae7457b07691fa49cd106e03b1b42956ffb91929e3d0f00f2e2dd6ed5db63974e18ae3cd459d9268d4e20bc9edfa95124920783bb1d06c5d35c8191e6d455ff0a3a90d309c7c25c9af449d5ba745fa10609621a9817393973bf4b4ea75958faa39eb0715c35a2a3de6d6976a82a841ef45afdc1f24b06a6003e0b3a7da9e5c1b87f054aa09efbad578037dbdfdd7d2133abe925be59f9e7fd683be7362ca46e37923f00bc8768110ad85c387c76ecebd8875e97ec47421e6093631d09ba734fecceb8af3614fe20eb778d06cf6bf4ec1bc1f02dd391ce80fa96cd7877c0f501bdccfe8487ebc87788acc1445bd3204323f871f3fe0a28f130fdce254200b3f1a36d71a1b6147143f4a430517688f82f7f4e40fbe7bd29f864964b3017ffed1bf2f69766afc7aa372dea1c021aacb624dc06b12227e0cf1600802573ffa4e113e27fc16569bce7616da9da64681e4acf896d45eab051c55cb3de88c2cb8e9d6282e5dee3fd25c88caf75eaaed1ffe75a7682d4b3558aad1a1520d7dea7ec1c1b879e099c0e5a185628abdc28035ff34867a855011efba09fe7ec1e080aa2ffd5d9f194a16552e7c57a9605076d50b79975130283755b4f25df301368536e93dd48669b2aa2e80a990952cf42a7ac1366c946a5bdd02671a495eda21a8dd1bd54c67042028a4ffdd5ec2e3a2d943d0cc8cf2cda0d608090898c939a828457a5ca01774cb12a1963c265f8777caa62b39042c8176de6d63ca2903c0b6982f45375a44f85fd942077aaa7564584259b29ace640b53609d67e0306e757699d01b30ddc2cbb6323427db79cd07c9dd2fa0ec56820e00d1012a2953ebaa1e751ddd44e378c2ce97cc6281162e64a8be58d6c59ff8bb73e862a6be3165103c5c1a845825b93f1921ab432ab021d3e095307f8b836ae77ad62e8d090134a48f2b3edb08f891c2c8ac3ce18492006d6e84cb725f1164b5827fd6caace9b0150f1462d25939f0e075439df17b71f53bb5036e0ff77547a3ac59f3da65ebfb673b8662e88a2722511fbcca8e191896f5804562b1b7e383952858b71ade6407b42ff952f9d7cc09411e84b1f2ab9a3c634cfd258366146ef8a883208307b6091aa6de65c7f5607b780093bb429429694e34ee6fb8bf64b1ab45274a1bd1b5a11615b20a4b3c175cdec3a376f4c04bdd7213b9ebfd14192fa8cdcbd7a3ea0d67c706d650558684fbc0284ad0d5566a75b2b315ade236f36c82709b561e59ffa63f6808137d157fa0ae4754ad479947fd47510a4972bbc5523b73c47b8f505d24eb84e77856cade1702aef08b9d115b99f1246d271f1a2abcfc6c0af38b51a7779c500d74353352c98e825e3c710b8c5ec26940e8ed90de734da7abe28fd5b132874c107198c9decee9626a57083a30d822218f9b781c3395761b903be931b19132748c4372b1ccdc487d56629fd775baa481a7aed10005d5b1f63a023854295e80dbeee46a99df02216fb6a22142622d363292953843f5c5eaca138205bd5a4654c37bac8865ba2584b4b79ecbcf456b6132120425d71650c2324532b982a988e5361c95e114dbf0a8897a312296fbdbd10762e466e0c8f45623356ee903c45420ba12d390ab8780f76c14b08c64839bdf1f9caea41756170bc82022834ffff51cb1fda1e00942a8dce1ecabfb7bfb4785795e5c05f4f1d1a9a144e404683d85653b758200b984a151aa9339324d48b5c3dd1588bab30e335e9cc0bddc6f27f226ec17aa8e9b92cf9ce5999b46ed016f40124f8ae8aef2b85f93ba8cccefb7b1e7742967abc134e5774fff2477b997b570d74571037b490ccdd4d1110ff28401731b2ffb680d28f80a0606ad8aee68c7d3c60ae79a015d5a1aae5df92c74f45211a1fc8070411e78df07ed0e9c27f4e98f9833df9d906d7f2f150391639cef8a3994f0e968096395d9f00e1a2f4f358bbe7ed22d657ea8fd9da9c5edf21440480b606840ded9be0b0d5a4db98dd9d650381c2f1147fffda3d51e13c124e4dae72d5b59c9f4456148e026157f4516f95dc1261488878fabfe755a65c644f11b78018bbbc429fe1a79d861051fe5aa38fd7fec7e653e1728cf6ce847b8643c770853f19741fca90de277315a9a23ea1843d6c44aba2b46d67de4fa5ec1b037fe37d59586305b1835ec0414f4f1c867778154831f8311806b2b0d29923e96a77d19284fbf5518976084b19f54ffde40508fc8a4ea41f27d6817defe93125e9d3035ad687f8f460b5b9dd0616885a4eb540f02792f93b2cf547646315633c8427b9b80401b807da220a671fe0aa22da59bac88d90a7e8a513c9f809e80c84338faeb66b52ea111be9daea01168c49af7fd67691055f1ab15c384d0104715430f32ba7ce080f5bfa05509a95d0328738b1dfd9dc6771515c19a3f4db04f9a24911081e722198753a510133a3c05ce5e30ba983ea7585905940c8202938bc0c6835127306b394f6fcf1e5836b1d9d6eba112badd6f08db63c7a706932098e312f4f200695bad3751011e8b5dfe0053fae8ff2d5c354f62ddae0bfe2158813dfbd4029c2f5fceb959824e2c7ab18f513e7f293a023c162119761e2cc1520075b2f7be552faaa70c1bd6318f3bd6cb6fa64224671ed6acdcb2e996661373b44f065350d0a2e56fea12bc6cfe326ccc1b64513f117fa47e0ca8c583eb25bac76dd47801cafa753f195dd99408ea4dc582564b228fc505ad6cadb22bd22fb5ad412163039aaafebbf07b00c8798034aa81159635892c737653b082b9d2a723dcd4b5260f0d43c41df65bef89c507b2fc3764eaae98e0bf6000cb36f503c8a584d84b7d8c42eff3d05d8331a3126846b390f198d249139448f86f07e50dba15ca7ea3a86622f4408d191cf0dc4f854fd1b52c50c5128c41ee071e37f714eddd1450a64ff8fc6988dbc3b848c4a8684d02b12d87e0d0d3185344e053c89f974a4694c47d54d76cc352d74f86502ff871974f3bbfc27cfc3aa303161ee9a84880953b275a7a759cb7ec9155ede82be28c3f182dd9cdbdf315969d52b6eeefa3a27336c255951e4ebf8556a04b4322eb431a3afc0ccd49508551c24d43a7e1f3895aa0d8e9d72c15da0f8103dfa1f9fb1bedf88d1bc1d7d3f34bcda9f41e26d44b864cc0c2895525f362cd7257d11aae8680d33f56cc98adc548b47bba82430f7d01bd0a50ad9bc9d586eee2ef3620f95f3f37d7e8cac4ccdc17f7e91486dd5018ad3f2257dc475594eb5e8dce5c5408195d052b4d92fae966fafd851e458143ba19bc4e5d9cd3ccd3fc89894b62d96e0de96b8000b760de82d7733c8bfa25f4f5ae0373cf9f8eb2fea7903be204931727066744d0f933e2b24a5e48c262367379d49000b65c968560233a851a4208bf2f89a2d7e11a37478f588c26a1072a446c2d7f46e6700aed9cb9a358505b60d88963dced832ef99dd989eb43cb4a5a69b00b49e02e00a618c63349140041a62acdea08c18c214f9dce10243d69850e5eacefeeca4e4f87396b6aac652b748d72064faa23c275d668c018a685b44e12637cc7f90c2ad8f3236c70ade26fc4310d4435d7514bc708e1e54757325d7d8aca953c0b6a7b2eac1e91152480bd3d3943dd95f636483302483fa22c4e691279907af25fcc833e1688731da7a85a8bcf2c7090a236b755cbb7085f446214e6b36c02d1b9a228769ec69b85f69f8a525e90a7f9b94aeb73d7834b2cf0f84fcc801078361d228138ac043d59e54c240c269d16584950f60a08ff46ccbb1b128cce20305d8a334d2a4a65b3e6d6e646a90c0d4c4abd7f5516f0c0f5fbd7caf1435063799d972904554fff0f5c7aa9ea8127fae8be4b3c112bd8a34ffb2547310d00466f706e10f1cc4b050260d320d1bda3f8124149a0a96243261a6cf78196aab0f7b08b536c16eb65ae7c7f97ada7c938aedd41a0d52b06224b348f505606e53179a1dd9bc540fecff4032f7d7102ec0fe345bcd7229611654587ed28f09bbb5dc032aa1526347af6eae3d9aeac9835213132ea3e3e71b86100aab66b817c7cf7b921560b354514aab3ebed138d8180fc004557a97be9015ac00ed49c3437f9019daef2e95cdc99133519b845ddaec5a02cc0d6e994ad5b33d50d393f0fe505808140852e8a6af9ebfd1db2d8f074ad29180305f87083f9e3fb77ededa663ae9531ce44074bea3e3bdf23d635a0102a5df6415caf4e1d780e3f2f2c0c1ff3494a1102458aa9c58979b554c3a71471f4d0d80c408468633d02c7f645716aa5d1959f1aaa51fbd377584f0daa34f2c6415bae4c2ed42a748b78f5ebc08a905a8a6ea5d1d7d791f236dd456cf52c8d30bd136435afa94df4689094ba1663622d6f737c1ac5202b315143175a7b26ba1dcdbb276246d10659255848206d10a3d87664fb557821a1515b368efe5b2263500d8895464677d7864e4d51b0d3a26821d214f94fb027aad69b1b35159600de727f7846f7a2ff3993d9386b56fa38a8cda76d909d45686216d8535e544bb8d3e6e9597d12ea42db5481b8b925e322359f1717dd83a2d6cebe47a94ad52e3ad9d322c69487e8b444020eb8f3a96e1daddef17b5ae478711141b3120c994371518ac72f9dd302e1395c5ee75b641eb05ec81b84e74d0829c202616050fe320d6f31d53b0fc9969912cc9c205a17fbb0c502e1245f91bfa91d0b0a1b0b042a2049240c6f0356f88fdf56efd83f4b32155d66943f92a8f5d1383a159f3ee1482232f1da96e83c77deec7e7947fa771f9596029c118dde3f9f76b3fe12ff52c5adca917cd59a9b95577e3e132750644803baa609ed0cc25122a20c14531d5cae26ff8c9c0b2a0772401563d5218725924e8b27a081814dc80bbc88a414d2546cd768fc937e857224e3b7d00fb22d82c788bd8998b8f79877c225ee130bcd6fd231fe95bdb48c883332a4284ae83483b2f83299de1780da1abcf41c8884d21667371b046eb4e98109b23eaf2715fbc2d206ff444fa664609340c28131627d622484d4f2824d9eb0534f63706043f1f03f66969121c524c269ee5c3f1970a86f61cec1e69d561c6add3182a252d333924c06ed692e22988e9adc9253e19f8ccc2ac2416c1f0fc947435083d20bb44f3572388405a1335403245a03249bf73b5419100278f563b92674a81462ee0251e942f60671bcf733733064733606070cd9b69ce6f35b785c480c341841251c33e2379a75c6e4fda1d91964c53fb1a1b68bddccee1f266e2c5394385d9c4baf082a281d304863410c3ef16cdcd45f20a6479df3f1bac6024e42a10316e1e58f14fdf56c274fb99fc20f0c1129d9a8670534cb271cadfc0fef4a7ee4eb1fe46e50ff4730050d689249336a46572156d09922a23b5fc30d2f1469d9dd9d33080455665b330d9a9f71bc7a7eb21bb191aa3f3eefd0537754dcde2d0ec4a33fdc75cbefedac2190d502db251bdcdc9b73dcbf9e76a54fe4cbfb707691c92bb2681fd5ef0c22899335eff3d41b0c0463f25dbd0faadfc31258de222b7d70570bc73bf828aee036cb2ac783ad29076daeabfe694ae8812aaa522db506ce3da6debb5b78ddcf357cb72ac488b47b517edb7acc80f8cce616d4c4c25b125e6a21ee741d2ade25fe7837e475a32669b30400c560c3f0cc412a78052f5f82d1d1eb1bdf5eda234ab55f26c7ec586f93c95154039e4eb248f0d8e3b72e1cf5ba208d01a4305c929d8b6b420a9a64542a39442cbb2990856e9e8b61669bf196eb528f9cb8ceb1a7d17021dd34eb15b9541e290609c44e436b99faa512b6cf606f6225e6ffab94db941818f03106f200d426ded1512a1d6b8d814337e8c636ed341e813e5507de6fb8e7508c9e2f8290de405734b3af784298853d9b07cbf098f32f153c70db2a14464c4854aadc45641c744f322d0a4d02bf608d7469fc88e0585acd1b3691f2887cc02840f8217016e9cc87bc205c113a689cbc641b8ce797c32153de0ec7ad75a0e89f251fbd415fcebf7e88a39166ed834e141c34dc861e06c3447484f8f51fbe67cf46a80a6e73a4301504aa7872ab32139d9536a9c1f8e0c584121b2e200f30ea6739813f008904035976f26bbad8af4e090258cdc7aefdf712b207ad71de30c07d127bdc8dd9cf713ef009834628a1c57d510209122f0e2f41199e01a6543e58d6416e8c49f768ffe8223ec834de81bfab71db97c01922025bcb7e1cd3fea60e752d25b4879cbbbe5a6dfa00e32adbc043da5c62a6c1e810f7072d7b42877a81e354b6b4bf13e65ecc804e507b41eacfce632feab55774216b9bdd40445eaf151c457a924e5449daa02640dcff2699c13bf9a96901c0c84c82e1224976475619999179a253f823a06ca6b8fe11f9445d4c995a7e82bede49db3422cbe2bf9c7e81154f07767b193a41908f079432d409f8ccfc964a49d9fda188de07244aa9627807332a6b99bce9e95f700553fbabb053a09f11ab3c1d073c6ae4f4b2da197e6a1754d0381bd9a5d4d4fe45b3d555d4012089aeb442fcf05b978c65f8aeddc6d82a879cc2cd24a7d5aa45742f635be976874fcfb62aa4ede08b7a27b75a3b1768bca69e8fcb1683fb083e069a48e369840199025b737408d365c45645e40f6bf8a3f39d09c758743ba5dc4916431758d7b468d451686402d9a81a6a602c18911732069ab8b2f22d0988bf0ba9a33f728cf3112abff76aea06e2ec4b744bed12d1e50b099583c89aa5941c38c4203a54ff10903cec4f18aa84884402f82cdb473283f91bc5e60f7bd319db11ffc74c4bd3e31b6149be4d12309420e967814f4fb3f5daab019a58ecf89244bbdc20b131c2468e7dfa2a33f7436eff65a8ca751bd3dad736ff04ad0e9fd8ca6dcce3d46c9ac185d7cf8cfb397c4888f043f2cc48260a190bf1e5a6d75ecf5b3f5a31e5b67a34fde1297a8ea7a3a9644d79b98ba856e2d9a9f3e9f62a1f144b022f995242a243cea676e0c1e20553c7af32bc847d0a4376811d688a8effd19f15deef6473dad972884460fab3a1454f4bc82305e80261fb92752baf4971dd19b49de6082e32602183902ad2975d63885a2a40010114a8c999fcc934724e7ec68117872c18c60e60d9e3a15911537a47b5520ea6a1915d0f2b73583ec805bbff55a38422c85d2c1f70951cb793b76412a8390f1478c98e24ee1861bbe9a48907143972a0a85f48e6ce0bb729f4647429869ff33003f649638e700988613419a95e5f48f4e553e8b811e79001c0bbc73c2d291beef22c48a3ba271c604d55a5874fc1b98ebc42f029c04f3d6b6a7223bd2a50bcbb470c6c7541f50c69de850413ba77a7fcd6cf539371277d38b4ef6374aac44e46658e3aca566394ff7e2733e6b6656cb025b71bd694d236a0516e7b3dc12dcde00696df3eca092fbbe3123b9747c93eb179d925bf536ae37c009f6e021281fb2303a168d2115686ee366004201ed70e2405af6d00ee2b0c1065118684297e24119130ae6df79671a71e5e101264114c407e157f3a7e30bfe2a89b4ae619ca3e10de20c843316111df289d508400d1d550fcaf2501da33bb43adce25c7154c4a843c582c29b0dbb9a863c35a94651acb822edd59b1f8bd09ed5778880374cf56999150e204ad46f7f4ee903d6f548dbe00177afbaacb8255664bfcf4c9d12148bbe14fd65070bfde24b1f1434facc9fae1593faccf316e5d3f75693fe51620a249db1f6e191ef0eef924980e94fecbc6a32f9f66f3cf510d66d8b0e531876799eda5d40eca97e9858e2637d258805de1e43b69f0ada8d46a29eccf0da07e517624705d085cc35da3a92d6d489d04af97e2d748793a19fead36dc40124763b8469520b36e9c6d8fd9186d24e6acc52bb791c25e8be971989ba84408437b0a2f0577fd27ecfdb38af4e67f9144e27e6f9aff7f9e282f0b5188014976934a655a1e2307b2edc3d81e74a8fcfe617279e6bb0a3e8526856270efbf2a781b0dd96e3a1d471bc4519c60109e5d216250dbf9905e7537eaf9acf8f71afa86535e08e511179f3ba0024e2305921d855d9295dd3df55ea384f678a55da3779a00d31d262434f2f4b682a33d0c319472dbf56e9abebfedcffb06233e689f9d0f014f0e885bb767a2b8536fb5d85a92b924e01c2cfca852212bd0bddb1ae3528b3c4175a02c590e484bbc8467e500cf6051a0bcbb315f0902cca3f4942a33e2a026a522234fbe3ca064d539d617da5ff1f1a59f15ea18a386159ea1d4668c93d58f45a4185b57ac191adea4ccd4f4378516276c84ecc59395461e0b740e6fb4325384a04a50f48fcd15dd1f69e0244851dba66db4120e14a3b3089281b13433f57abe60137e0c9fc6c52043f9bd76b90086aeb4dbe7b850b750b6bb53ba8e1e71a052b7f08da5b816b91d9122a9abbf2da561d5f3e6209f26915f60c1190af367478b2fe33735775bace397878f302c3742ace2dcee927901cb09d1123cb7372f419ba7970809ea25c6979607d37d402e64485843c47cbf5c952185f33be943d0701a9d7a0a1b3a665bf9570d3d4c7d372f5bc9443714dec33ecc847a1da94dd54bfb9ca4be2c1f195122d0cc042de68739e9fcfe2d045770800829006da2e1041b09f8fb11f577f10297a6bf55584f48dc777ce34a415d1d01f0e264499a0880634f5b1aee639208017038c4765b6a0da523c217d4a6b3bc59fb99818dc5d73f576583fe6fd6300af76371115c07fe007808732198eab5e66190cfcebb22340e64d1bcb555bb2ad0305a2e6f97535d1b7bc2149895044d2332714a09289a698849876ef651b181bd3a42b7b20c7a4c72fb65a8b933458822c85b141234d1f591489a4a7ab9e3c0b4312257ac29eec498ced58894ed8b4e11fabfd5339dfd9e952de5e7de42234654438f3a2830ae8a3029db266f50420b654a228e1c4bbf38fe863bc6d98402760eda3f949441387086947b15b05aecd7a24c33bf2d24e57e5db58bf89c1820ba54cd73919c28fa28246ce977178f818903f31c585e8490f09c483fcb962dd9c4c7203472f7664b1e6892e3b12b7ee06cc9bb18e0b53ad2ece96bbb9eab356cb3dfcf8ac35aed3b0dad5ade14a34c80c53218ccffb769bae236dd8d1199c7ec7511a493615f3e6b4d68f9aea112b5f97e437714dc555f03b084070c744a9b8316e3f2c1d5615de61a7f219559cc7fe17d58206ed80ea9b57340be35960c2c50b25d6ac1c35f579fb2b506c91a8acf130b7db23626b4c2a3ef959e7d691d5c9eba7de5ba65bf4f0cac19165365ea57e921e6ac30fb1fa381667f2d526a38667cdc94155b1f994fdc6662b2a4f3e11b1a4a4e256e819e3afa069824df70a003e161ea426e868b7f49fff155ef02531b13d39907bc000dbc72806374a8ee4e6838baeb39f4aeab34645e838cbef0189c5f97c6f68305b59f2dc7f4e126141ee36b9eee6da581df7a7f840b92b9c171c6e11747f8f2ec31799debd884c75856865c5ef7f75cfd9eb31ec7af0df66a1857eaea47f80973c025c4e456a0845bb004e87370bd76dc530b8de7dd85c862bccd7e730aa94ee87f5c825e93faf2b1d9a4e1dda35079a6b6a763748735cfa19057d94b664c46438d3c5899520d9c6d3e17734840724376ec7809e4270fe686803ea53b143789f9f21fe6aada26aaa3e136c23167a7027a04a9daafc6e7b62a1b07d2bc08d12781e5f83f6cda491f5aa31c23dba13f6273a97b6f780955dcc43d8dc6d23279c337d3562bd250d61b168969b1ecd1f2f90add949a96345c541bcdf1ed967a3b381dcbdfbc336d8dd35f14aed206300f5eb50e65be2a77c1c38b791e6e0839452ba0805231b5df19166df0b99e2e5f8ed5857429e433864ddb549f1725e9110ab1e0737a62ab476d153bf3174d3d2505f1567d2b5b0ea20dfa332893dec5881655ebe236b199de5ab4f412f92d192b0e352a4c211b0f23c2a17a8300c947fb2404fd65db5fd68173b43c99077bfe45bed7c7736b3943017a314369fe560322ec9210c1ee710fb3f8bc597b6819a64858b49f503174eb37065ee96a684ab58dcb7ce4562c80d2c20c78ec256431fa245c30981911ffeb78d659312172e73d17f4397c0a83167f1ee526cd5d12d2d48ffde9d94803956597a386eac8d66c4389797f7f93c53f5d9f4c7c71dc2693aba15d5b95d0ad4816e4304f1c6439b59cadca720fa365ca897bd73a4e133653d93332a110ca1d68b58dc98d26561b362c11dbcec605de270e6889f95b4efa159b8b1edf8982c2661eb11c77a2d1a0380a85fe761d053c8a894028374fe60094cd18a869d87f9903b81824596ef0f920871bb9e1a60c7ee860beb741a1d36b189dadc644bd0a0f95cde7c45e51023580f81e4245cbfc28b23968da0937696ba007e8cf7e404173e6882d027af461ce72092301dc90e71123dc5c70d24299de0cf7b26d3b38a36c6adf0a165f0653bea90f8ceb27e02a43cd541ca43371cf6308bc1b9d8bcc0b2f7889aabeaf138a610a6833a8245fdffdd656418d0327f5c322a3425b6a5c2e1fe1a8189e90e3f02ebf9a77f348b03a6235eef5443e8942df8d3f63ea33afc37d2784167613abea4fb1465e8aaab51f71c51d35cd726a5d5117784c314695aebccf967bd185747154ce9557b457969f490deca1a805117365c47f75a3e4cab87bd1b00064ce7c2d975f9768a545bf77691152c4aa4bb6bfee0eddd98e790d7ba913738e99726cf2f09b8495e3531cded6d96d5119f5da971579f4dda5102c27e563d174d0f12a098139f7c469a135f4b790f350c4036fb67e6224cdcccfb2e9ff1a41f939cc68dc4c0b24070c92a7b8c42a2ab216107c94e621a2c74d3059e3409403ca2c737acbb0d29fefc69c9ff99e06a12ad9888928e4a2a353855d4e171f0b3816973812edbc6f7cceb6ec2a63628dcb2409d5fc385c40e9e01dd1ba6b10935222e416665957baa754260ded401f2c0e11b69cd174799a2c5090358413fdc14ddc0bac60219001fa97322f5b1bbad5a7e1caa8220fec2b222bcf1aefd143e52ed8b353152322891fd153c8d8b6bc8d7e45e49dc89f0df7de23110fcd8b99b28f4edd1497b5d679c769f0fa8bd3b8cb92a035d6788d1dc8ff7f72454812fa6219192a4818b7420e2361e0caf4493aa2bc1b751847d818a08e3f59b29940cbba99a4e6fce60252c70bed88724dee30da6ebd9c4795076d17fc94e8f61a62d300867ef4a76cfe433c1087e66a4a1bbb81d1ad209b579c442db285c11a1e89c66604012f0f6e7e142b5942a5b40909b38805d1b3c762327012a89444cfd78000201753625c86f108f5d180218827ccc1e6bdd402fa96e80a32bfb2b720b85ddb168384d38b36bdb31044fc9119dda0482bf2a575878d8989e3f4a1dc93b693c754cc5b27e065bdca5deb7c254f83b21fcbe680f0e817bd9a800c7c150603f59acbf7aa5c76a3c7f0053cc7889868dc4aa5d54fa216067671fefc11fa04511ab513d65f2db00605248b974250806022f163d1e3bf385a80ed5d2c60a8f2814662a1300adf85ca39dfbba1771ab2b3e534fee7e51129b90cf92fce697219cb7e20a06371a09d1f64e21f54c2537a3c5a0f3ed5398429db3d1591f8c7a66a4a2502bf4d0702a24db41e9df2deeda38fc0101d5eb3654f75c474afab54e8296b60087598967c1bb8a0db4056163b4403ce6dfb68e721f6db933e606d9effba251e7d056513117031906aa79307857c029ccd87c719b32a80afe7c8e2ecf5e2f60a729f28e230cd75a377a641bacbc2221f397c9fb62b9632806e7e33db7a5e5e3ef7d96ba2b3980dca335ff4a50cf36b51c8c90ab8208ae8359d18a71e7bb190ea7fb40594252ffc1c2b1d6b86840725eeaef5da16ec0cc337888ec628e9bf8f1f5c5b381bf2a267c11bf6c634f1be8115b8d6be52ca1618e5906942fbb6a682e4171d849af3c973d414a26ea3d87c23999bb80619bed6c4d5a2906b80fbe8a41af1127b2034ca403e109b7453c73ab467fc7e472f2f6aed293c96f3f1b32fd713b298e060781d8b564541be9c5c79d1475835006174b448490bd65fd0fb1ae60d0ae4633d5cd17a4d0d44d5492394478b9bc9c9650cf4698cf795f64fd40f035d1b72d30762f245c1c8be85f76981758001a1b160a56a812d5df257a297b7e12d084440a8f111bdfbd081a1e320a2e3f0a9f099d032304f77f58d13991b8c6b8ff822977b20c66910606412b7c5d5e8978b2d5b75f7295524db7679b4331c5827f0f81692cb710e20c7f3e20e754b34118defe53fbea9c3a0a65134387bf3cc9810a6ac7b3b3b15f1c8e88e1d068cbcba076324d732c9f6ecfdedf19214f41f85a78039f63da2447e1a9e35a0d8cbd2819cb6ac6b46ad11fe87aeb2641e9814386dde74ee5667180c26371843b569c1103c13b119397a269318f1502761a5ae0c3fc89299a914d3faae9fc95c38e1a5b7ce9083a4fa5bffb963b2770b900e90d587096a9d33981b179291c9af15036b1f9d385c6c85980cf9580c7b898534d88c16ecf5ea245ca0587959c2fc791376a38abb575a8b9b05cc155ce4572d474d35e323942ed289403210f45396309878a57b54065877f3aaa59758924373b47ee929b0830ab75c6fd1fd855e3a4321afbce4b84d15e2f71f01ec0c1610b53b7b3460988a6d9534da8c385188d57c88973db4d5eb8e2007b00f53ca35ecf0ad19cf7bdad3b2aa68cd03fe58737a6cfd2c8b370a70e9d4b433953ad1040def61a9caf1367f02698c694b840df28e078a0a4f581886121f860fbef28995036edc9e4b9cb8ea3f83811f9c8ec5a28252cb80d7e3cced9c82389e410c36002ecf25842cc523a58f7d046c2092c63006d8205537e3aeda22ecf2f4c479226cadd47aadd4985ebcc11e1129845d53fa3023800b358ce38257275ab3c8095ad61f6aefd20ce441560804247c8a7d73591cb1412b22952a29bd18485a0058b9742f6feccfb86de074f1d19f498a16b65ed6cba8210787ca0b3a9201cf8de5541bd599a62d519a33c32c0f7fbf329578322c6ecbde3b92d1be8241e2146d691d29e970832189746f968ac3bd79ce43a5c5558b3a42a743f3127a329c90389fcfc22d60777210f3ab6be73ef8cdfc5ee4b8a90f026b4d2b462446ab5dcf9aa05e37f1e24b47aecb77b63541228f78115283a615d40c8ac4a9d51dcfb567945cb12bd9ce47d1a0e05781d9d351ce863065d12f7096ba11d5133a0f119f930d5f15ed14fbc152a24f5e9b10ec10a1165e3b747c7ff5de5c21603d24676c3f9ffed4048ebd002e5f5427f5e40912f5799587348ca28512a2e4ab012dc66d7ab9221a55a8aa12c977ede77e96f7c0a01aa21c0c9c937e13d8e4466adccc35b4525ff01ac17669a98e31cecf9df6bc825b68b86a27c922104f28c0cbf1a6fd312577ba7f27aa1e1ed80621cfae46ec385706a295bde402cdcb8c0c6247b6f81b1d4cf222908218cf8fa275b4ce29dce03e956052bd1b3a4ae1061095015079d2fbb5c8cbc3be96041e4b60a5a019c8d71a2402301e86d0f5c67190bbd013ea90d94dc4e297211be06015864ba75001be8cfbe78bdc2f73a12279798fd56f9ee918e2f3e65b8cf84208160cd3c113446f2a6894c5136dc033353e9a87b38fd160392cf724a5b591ce8818824415d6bc50b40643d2f61541ecdff9e62e46085683614d9849c3aa6b02ead05f8b7c001b409d1da3ca7954f1acb8281bf3f53f62e7e2ea324247941a97063a2588578ede81f46ce3f59e7e44ce42ee6dad846c8216d000ba5c0a711828bad9fd60ed399fd703c8e9f2f1cf7380807fa462187080b71658cd17c653b152c22cc1bda870e98d68996194894d0867fc7f57a9abcd496cd36b19fe0dd1c54300a55d5451d5b67ea969735de4fd6fc3f06ad07a307ece0074b0d80bcbfafb30a8656a9965ab4c2d8f911430b11472ee5ef1b3a2c053e48db3e82f1d4139aff10d56d5a8d46de6f3e960a806d7202375a272ee99114935c15bbf286a875a28eff62b3f3bec83b765d03e5c8d01eaf1853a380226b842dd18c453ee0720c9a70b1b53a681200b232bea0d2d4db5a1ffe3c65ab15200c8d6bd963422bca72f4541397575b8d61c291b2e4ea95c3784e75c663ecbc073720efd1e8556112c3a9ab2871c408d1df32ed653cd58411034800b897666ab7dc16846defaa9026eae1811785025a33f9d3b33b6b0181335194a82b99a6bc4dfbe0fa3bc6788df9cf8e9c7d1f8098d14c5f365ce355561bb4e0820aa3cd2d786cc44688d226a715456ddbf4e483a555b1683af231b32662d7a1061ad6ef1bd4a8e9024522d9534eb88ae5863afc33461eb6f0696a74762e4b8fb742536ea0a1e01d1ebdf94165c2547d8342d67bd8ca87191f7f071ea412153557fbf0580a723b420121e101b24cedfe97b6d897b9be8de8779c482538a8f4cb047b5fcca13aff715880e1211e7119d2457d3484440eaf0830b6f845363f5e63fe4abd50b301bd0bf746eec90ea03a36ae540b6cc5c125732c9b7daa7878a72ffb84113aa157cae408c9e9928600db33d91108e465e146488c95636715048b86a9155a5dc4328a990c6cadd4d01bd30427de3b4deaf3ae5f659689b5240209b61523210d0576b4362804a554ba98c9f31b39bdc19e56e8cf1611006df99f7edc9ca8e94c54c40e235710a42bc200249d377c3b183f596c60fc03ddbdae6bb4af1480eae36c59c0d11b3d6e5a8e5ae7506ccbb7f884eb6b1e72d84f9695e96d5b481a5d9a4991ea5976db9a926527121e96ad9afd23210350286a5b32cecb68870ff1b9d56a2acf6d8a71c610234ffae297d84125b182a4809f7c6ca9b00821d46ec0a2d6a59a1a805b9aaf57b72544f40d54cfaf36ab7d55d295bef11044015d675abcb831a4046d00d29e109405c01527528f0f5bc821b0722875e8664976d7deaee2ba2b58d6b70f8320895593a5673944e3fbd7e4040b5811df184e069928ff23e309d149381e5b25440902429a8ce5ea394eec643549fc0512a8477ba48df6d564af9ada1fea8155507ce1e45d30be966853cc2b0806c5a76ec92d7e710ba1c0b9f855a59cc094699788e2a747a0971d2ed9a321fc63a22ff5dd6c29bbe21901344665628c55001f998806dab99d9efa692bdd340de9e19c814f4577ae7745bf441344d92320a4dcf8494061470d25e1e26612336677018e4e439c870022214ab11d9e71a9b1491ac1d0b376f0e30218c7e48d497af6c62cf4e7722e56030c11d26b1ca51434e4fe8003cba95407ce1c6438a6b2c99cd33bfde34b2682b3d4c2f8e60dd11e6881b8414182ec2bc9bbd06e579158f0ffcafef3610ecf0d59d4ada78161b2d5fc99548d3a236436b2e38d47f21fe34dd9d240ac85a9ec54d1c4890ced77124d27ae3555cf47cd5dfb1f0a4b83984cfe51d53a56f2a6210620ac6f9c9923950f70bc4c5738d2e4e9767a37a91714d969ad66b7bcf52c9012f15dc602ca95798c59159cba69cd29b6a4232cabeafc8c87be24dd1f02842204cc1332b8afa3f9e09e6cd160704e1ca7d1332b890a2151a94387b675564c52bf662bd6119952be8150c8c4a245ccb22e6db18a05fa121b2b577a94c71b08d7d021a5e64d54438cefc5d2ac4867c10868d68d6d0a4cb48a43084a06f19fdcde7472a84d23abe8bcdd37980eeba6b9ec6a356949159187d4b2b9d8a874312b3b9983514787b102add531e6a640e8b8fd240484b80f3d1ccc95b649b6c2750f4a6397d33c59367685e5cd2530310aa9bbec18ecab748f7545c5ed8c746b34b0102780954347e5650c6c1bfc23e66d7d9f438c32d4903e8326b927d26088b1317c2e3c1aa6db9f1673bab5fd7fbc9d45425b9e413f24b886105edbd1d23ae511b45384614a5ee7710e7b784205456d861425a6382390a0f73fe10770c4f3cf23f8206b410e9639784e632d79fe439db98e46193784ff8ba338d434ced3382bbd790a85ea122dbb1dfc9d474a566b455f2be3fd6d2f9bc6d3d6432eb437accbb4db6dd83271f07828e0857b9fdc64eb88cdf2faccb7dc00be0a17353eb1b0bb6c0d7695e6037abdfca41b88004fd3ad06f7f07ee9c8a1b602c7d306d4e9cb8f75fbbc24c9da340f406ef9b1647524a88c356a14e78072535bcabca2006086b852c903032639bc6a285982625f0b36d3440798101c8d4c531be35db8e9311c43509c378999a52c6415d666c69ca58f6ad912ccf5d91a80a9dbd3c92988222965ae047f8982a412fa9f5aab07407419dd5d0d4fe961da0b013b9fd9f08eea991e4fca2edcca85d4f43bbdf6c3fdbd478cdc2e7f9f9f177569d536f94b7f36dcae2b9a7e59164259bb533f59cc22f97604e83cc41da5c4ec16e15cc6940177352f08fb5345e4da6ac2e2f695bbe694587aed814e117d3737154e07f59bbae3080afc2ff6beac6988862d3700b57b5bce295d8d46816cda8808c4855c1da25c99a205d0a9cc344b69165b1fa50c2d32803cffb399f0b0e94806dedc32864bf1b10178c014815f553a6be84a2953e57de36fbdd9989fec704ec9c0895c4472f90b501d35e95092af866019481205e60e4cddfb82880e5448b9afe51e9d2dd4099be398fb7fda61ddae8610542c683fb0d3750e97e37c055e629b47e4d4e1f268a5700f133d81aec8acd014280bada48a6371d198b3a5920a1b5b2b88ebdb9d4e0f1b972045c21d34e1b542a1923c0a2102d3321ecafee4494e0960c4d798602ae3af5ed1c681fb8e85d4a26fb8119acf774214e61517eabc537fbec3ddf5613264e5ea3f8413ba8d24f8482b94c0109c662e50f42da86da41a2ac34e38bb7991bd6b0038180802e4f9b709feab5b9c444d09473d458e5ac30a6f9bb0cc4b00c24d32f79910ab8e9b36826b73102aa83a331f7d1c0ca8433602ebf935c217dfd29aca8f8e9ef1e3e9f5acf9bc7fa003cb1fe4548b54ace0a4b619b2c3f207c7428ab1803ca56613098d859c885a93dbb01d7a222607e27e71a15a34a7d1d1efe0655b818f044ddd4ec4c91ab49ab18d58a7970021f0c92e99c3a95705a797a99a8b536baa6f6d31d2a74c4c3db08217a00bf8026555a6f869285d79d8a7f8db9a67db608dc994a3d21e07bb7b333c2f423a5b31471a39506bc823b6310441d9f6448c9c7c8aae1bc391fff94419da3e5a6b4c5edd87df9c3fa25b8d4a7da86c44c2406099bbe6e80789c942450d325a5c147f1961ca74b7e688be7ff322616cf4706a9149c9fa114dc80d704df620c8a9b4ce483085fdf63d3154c533b645729c3783c6f4a95537a230c492068e61091298b3cc70e90431e26b516ac8ae634910e1c17f9ee734165f88f366a0552890a246f7abefbbe6438d93b8896be71db18e964ae0f49e9adfbc1e1b987f212148ee5e2ea746909d8f857a17580237b41957ddb2c0036b0db4741cebe30e42b5983f4ffb452ffa9797b00d55fd33f52b3100a274c909c30c60c0907a2ae864b145ca41031eed91fe779141baf7d871b6bf351d82c415f00c7c848f937b98eef20768782670cc962bc4f51bea2c23e3539c6377464acd12eeb6ebbc623713a3b4a22cdaf4c312dc9b3c90d4c1603d54f738e6f889b445d8001709ab1db64c24f057a4350e9de7772d559638ce3456a3852f8bd5f94d6cef422c1f2c40c6892cddff12edbd983d4475fa59bae4dc9b9b1ab9d6b6c3fed4046559700c194b81bd7ddfbb16e7ca2b620a2541c1a7e946a6b6c86036862804e4596db4e4fa6619def02764163512278680fbf40bd6f44485f771cac100d379773d58d2a4d2f21f2eafbd306f26ad52ef2a8abc0ba39fbb9b985f4d9415ce937181466a0a24070956e562bb9d00549fff52aeaa4f4c6cab434d017588775cd0737b551d7b7979ee32c12361a7512a60083907a5e6b97f061a2920a210a89b35750fcbe355fd58d7211e986633101849b9ebf53912cab3d50a61069fd3f58aa08d9f9c5ed69e3ba31563316d25d7ee6c62b7b629cd0fcac976e9bb0dcef4f69eda5992f81ecc1d90af0b5189bb99072377c2239560b8a9df3ac9f9b36f86fd2dc9e7c7b2f2ba82b671d29f11f15777b30544fadb41b1620f2787975a02f0e64d208d2bd3f9e54a00dcff19ba6fd77963227c654c7e358042a7ab72c24c1db6842ab300091f3cd7b37611b94d732b2bca6703fa096d23540b5b1a80f80cf3a2af3b8530e85e388093464a98c3e30fccd48a5fec468d683dea74504f645446f0ce029575e879d855acc68cba782af074fd4ea41169c71cc488d861d31ff20918374900346222eb083da3251ba1ae7cac6d26ca1fdb3d0ad3595f4acb0679855f6535f8d0d954e898eaed73f65b62367857daa729c5d315f223cc7502bc90fd1ef70f73df55cdc04875b5f8e387f2c1efa73f090e4320f31d8fb62833c5a052b2405ae5886314b98d6330309c621862634a6879816a73d67302b6a1a57f10c2f3e627b5f1790dc3f8b9f4025aeb03fdc30744dfdc8c979985b3a6096abd32cde191087125e03b9878f431e0be8e353a1804bbeebaa6723c9832fd53ae5ae10cecf0356a1c8d0ab70e720a280e69bbdfae01b3c7cea347ae04cafbac9e9b3cd707c8c099db3526c06803063b427eb46e4116df13d33c5e5b7df524dd1cc5bba20036f02bae5968a1d00223444030a4c06795c308c4a4e07ebde33e77b6cb6e745b30d681b967e9329debee0e531201ed67bba102646712e7e606ab87e4441a758046cea5487196e5741601cfe3abe995ee48e7f0323a400aabed1d0da27f5fa5fe013c84b437bef2de5de524a99648107790758079c16a78b2a85b0f3c8e5cf38ca2f7f06da5b083b77dbb87da305b88dcbc8a8ac0a5917f7e5df3e201db41657dbebffbe22a128d15db509dd44af71dfe2083df76a5b6de30664e7f56bedb5cd3dfe4dd4e2aa4389a8d7dfbdd6bf3312d14f645656b9fdff1117f7ab1cb0a4b24e54517295d359c1c5aa11f7b54bfdc27dad155685dcc57d0ed8c9ca70dffda4a437e6ef5125395ddf5330ff97c7215403221ceead444037f7f641b8f17762a9f735da9bce155815a2cfbd0c532028a80c51c392132f5078808b65220e15158cac0eb54aeb1b870c93710a0e51462459776b3dccca00be6bb50fde71eeaa268a289568ca2043e9044c7630aa72c3ab7a400a6a0b0bb3872311966a40410a3166ec127084c8709ac4c0e9616b72ffde264b708a6e1370cf599631683a24db15b20fa626094e042e2099b1d6daad5a6badb5731583765d7d5a6badfbd0a696035f83b6f427cec974f1fbad368b4319e816ad2fede27764cb0798810e43ab83e723c4ee2da7d4bed16f92506a69a594d25aeba6744b736da873debb8f6cee63d4086142d209a6086302154bdb96ac80e3c25695d4eee33d6729a4a0046f06b950a7547fc8fa4cb3b9c0dee5d68bb6e2eec6070469427f34a7fc6e361d757e90afd91e382be34a0a5338144ba8084848418924610ab25d6a319ef7939294d2df58bb8f783badd537a5d4d234de0c0ab95615f10cb5487e145a47657150b9f8a8ac2898fc69d3a10a0524f404184e609182e504242758151698105984329e0866a0c061db4184378345c90e3fc867cfd995dad67bceaed076f9addbe4cd2077771daba78d49c5785f46e57cb9daa8ffc97914ea518f42bda638b647a13e9c17d8478d2c3aa0bbf2c7fb9cf113cb0af6b8f77239f851a811bf833be8bfcec9f970dac8c911eb8362f7d543594a5a1232c7b536c77dfe8d02ad9cb9f74422211cc0078feda3e7a1bca172d2bcd74151169871460fbcf780fbcd1bcb567671bb973a3cfe39cf23aa763e14e83df765ab14c29c88f23c07776c5fb6bccf62d9da1985aae1799e8ee7fdac42a9f7428a73c0ef537af4b1cccfe5926c096d9abbce3fbbe7ef44cf9efd3f686d6fed2ebb73b873efbed6aecb9ca69e145df7572cf5f61e7ff77df5ca233beba0ddb443ecee9ede21768805a1fe160b945f1ed9de885fecaed3e9c65201a58f7d7f47cc93e59f12bd8ca78dd6f6cfdb7bd4c7b2b5bbf7442c96be87f6edf0f85d52dcf7eaec1bdebf5fe78bed63a980dd3d1ecb17bb0c3bdf41b737e269c3bfe5e9d04a74d93a7bceb064b1f9f1db2be2cf6287e9d74477ffc1d0eebec478d30f81288e0f8676ee511ed95dfe8ea81b4bdf2b6f042102dc1fd9734f8ae37ee7fd043b2f7fedf2571022b0d2c037f7aded818fdfc6dbe650acc59b41d7ea11989a78282929d19413b4ee50a95695c4de9d1aee96f2b2713470b809e71d1472973aa1f376da789f0a4a8de15ad55dea91ecd35a6bad8558d1a5cea1b5d69a47cd51a291e8d1aed11aad512154d786425dd34f18292d618b794c25944e3da1b98d1b794c24362567d2e1b123839214159817dcafafb5d6dcf64c2237ea0a88d8b4b6422f09b1bf94a209d5e05dc0021e67d3771e4f56a1eef36fba89498f2e94e8efc1e71e7feec6559d5f470ede1357f37356b31b577304d2fd4804cc1c8178d77bfcf9c16f9c235e79231ea70e71c5adca32a2f46ef14e4812d3d528436b0d7df1f07ff93fd1878e203527a774070999a6e1121065f99626d910c9a3d5c444294d559175a94ee5a42555268fd28406793c86643e688e011d7f4c0bea73a38e334c581d4ce872d264474d827ca1d168349a75ef8046a3d160f8da2062f48deb94db6a8e011d7fbc0478db797344db82bfd0b8cd5ad5082dcaf2a12f5e130a4aa275afa3e6ea893e74705059ee44e3acdd39a20c1502a29ee45bba7c411ae2369ad39ce65b10ee3dc80c528530a69c2b15d3e5c442640d12547a6cca7854a19a0f893e5e95cea882873a1fb0d3940db496506daacb4d95b99baacac6a9d1e5d090fa55001d0cd19a001c03cbd1cd2fde412229cbbfa85abaf9cd2938960e0d6dcc6d37af5cf0b056edd85a6bb5b539ebcf596da57a1363551fa86a65532b1b7777fbc6f278a0aa950ab55aadbb6d7776956c7a7baaa93d675962b14dc986440f499cc95e9c6c4f9b52957befbd4b565051955c41432ec10d97b3e72ccb122c66294e94ba74c512ccea50abb4ae5966594698a5304496a5166dbd211363062b495274dd73666506194cac1c69d174cf99953056b47c332b55665644d8df9eb32a5434616940228592cb4847820c461449153ac45c46e69922a6334ad018d1118aaa78b9280e3218417379db5745fa324a62e48a4384acc8e5e1af8a5e53f2a6e69a66a0bc70e49aa1612a7279de57c5a12e5d602eeffb2a1123b336c399970bcc2a83fbfef78f6c22463e22a4bc5c608c7c415489b9c05491087d45b1c2e4226284f322cb4d60ae490990f355b1e55290400a25971114938ba404407d55f41f8c2863e49a52e0f08393cbd349d1265f684c2e3752692da2d29f9545691554da747111f13756b481e332429b2081ebfaa82ce79264cb152e231e1043dbd048e4c2c0260531b88818018f704d4a00235b9926ae23faf572557105a4522a684f4cae3af2c0d92908168ea8d72cad793ffc0961387fb8ede2546a47ab5cf5f1f418d90955433c61cf900ff5f1ab52a97878787a7a7a7c7cc8300c7f7e7e58b0f0e6d461dcddddbda7c72728fcf9f1f171afeed5abbb7f2b5fcdfaf3e3eeeed5ddddab7b75afeeeeeeeeee5eddbdba7b75777777afeed5bdba577777afeed5bdba57f7ea5edd3f16fad361dc5bad5640aa162e5c00bd78f17a6f8aa238864463059ffbd00d5518a4220ad23060c4881123045285402a55a8dd9d060d1a34aead9486cf1f202090a885aa45a802c2d385d3161f510b17ef22c8d2f0c58b202272877beeeeeea5915dc7175f75afeed5ff5f14c571ac00dce1a001e80763c6a0313cc6ada0023003bb71108c1ad467850123460c13741d65c890419224eb63b166cca001127d90bf0d90c823d23a8cc78821838824594d44feac1b435313a9767cee2e43860c70877b24a88106f60542f0489205ee98f139050dc07aaf5e4a396c67ad7a33543342151d67801af82e8f78343e1d46d7a861e3d361b407000058f059e0b5565b1ed1416b7c356cd898b5d6fadd7bef68810ab444d7b1fc2cb0a0d50a400004f0b93b6500166f660e7fafa7503771b890677f8229b29543984f341fde5459f47ba2f0d74b6cc0e7a0748608c1ba298bfe8092f43bf0b40e4a27b5a3fa3014150f17180c96278b3a9329e44dee4293afd0e42838acc98f9a7a1e1084e6c3000e9aa8177ede6b308339f3260e03e283a99bacdc8180f766479f362e3562594c1e3e9f0f48843f04ba3836ae088802a9a031ac49112c870687866cfe0949f029d96afd7879ebfce416b4d431720702721890e7e2d36174e9b04d5f7c1fc5e10e03896aeac54e8a851a6db1c65edb1eea032a17a4d8710383f765091e304ca0dc69fbb73d136cefadf758079dc55830fbc2011833177c8863a685daaeb5ed3997466b6f81661b102bc10f3398d1326506850d67542d8011807d6dad6a5b6badfdbcf4b0b7b75b0b5af63685fdba98f12245972b33b60d2a427f1097372c8badb5d642e00a9b1fc8a0a04413319765b32d0d4cb605b7b5d65a1e2ab6657078c2d21d2c1026b3ade58264db143e0a13ac5b4f5b1888bb1ca7a5cc931f2d4c2b2d67aa84492cb4b489f93cfdac80e449cf9657b80294253c1a2835f9ac80258a2a4bad6705189cec24f18c8192d2f2a4224267470450b42a5401565973b6c2d3955814a31e578e909e662724151183cdb68c800ac7719c15d9374978928d9a8c683a0a941e28247179d223cbd1079cf4b0525466498f2ab42094f44821294d093db6182561420f2cb4190b5158e9b102d3cc032454e9a1a5287c41abc228ed527f8c1b748bb6ae93302a5d62c0f4cf9e332a60643ce99e3d6754ccb06054d4e87f2209a8c3708a538f2935fbafa9b899f23df941a414a51e5298286b4609fa1b018906f79cc59eecf2ff63574cc83ad4aab047512c07bde365c60934169344e33232aed0dc120d11f477264691d64acc2881b652b068d076cf59ec0d0d2ab11390446164333054d4b0e2031812642976d0b7948ef5edb5e38a7a083cfa57a4b4eead1ce9d7bab2550b92962d974b7ed15a90c8bea5f6eface307dfdefeaa7c071bceecb2b563fe1ded9ca356fd4e52b2cb9615cb1a45cf2abc2c6b183da8bf71c97fb9e0af2217d8afa8fbaa66289a7ab922dbbe46cbf1533ceaa840dba8af0dda69fb07e5f83a6b503a526ecf1e7c28da75fc1e47d125de5bbc567d774cb75b3b3e25e78341344d8ee20ff4898b34321ab52719ada98b15b4bb01dccb297b3bd60d1b59a35aa62a55d9b441ff0160740794a4a2149a8cae5f6270532af49e6002aecb7e1551dbc50aba3ed2a6235cd7e746d7b1ac62362d67354252b3983a2255a34b0be26a9a5fface1914a4edbbd47b622d82d1f769e6fe0eb97b08e5fe7e1db978701f73f17e13571b16b9747fc5cde954d11757746cdda0acfa198abea3f883067b80e01e95687f6a995021216be7914db4da7e22816117fefc9c08c32e0e7f16930cb9b8bbda461fdccf0d44bb70b9fd2a836e6fa305501b8b738a46b4bf6f969d214b0b33a8acb2cd3daed3fb184a5c6d5a4727f53535a6bec8ac20155d525965794f521cde7bdeac792c3fe86077bf4d173dd8368f8ea56ff781c49f925ee78d1d45a1b4fe3a16015344f54f3425b7c75f71fd0d2afd6a77c6983e904a47226030ad78abb58ea8ed270af5aa71857fe7552b20faf10864e755757b4fa5bf52fa459efa984c4ab45e478b7efd22740c2264d2666d7374e5fddc65def26800bab3d7a2c4609bfbec5b6d0aea8d1ae0711b29121f4850afc1efc421746fafc5557efdd3851257795cfda88ffa0df4d3478df48ba86812c87d8e48912829345ddcf66da8df60e77f679c95f58d1ba08ae8df54e26ad33c4f44f528a1e9caafebeac77dd42fc03ecfeba0358f3c63113dae7ed457fd4a35aeb6a7f4f5d3d7aa5f6d23eaf5afc447893b44c00051bd1e8910019380e94252040c109ed73e5f84877bfde1ab463faa2cee9fe7555ffa9112e1e845af373ea31efd6f8c3b5f448f2efbd28d94e019551c87f281a24828c93d4adc3eab258a46a2421388990a6b36f7f3a94c61082ecf8ccaecb27ed9dc6fff65af29c198ae3421e18c36f72ba751927beead134fec922e39b14b8a4604b235bba46b36d7c42e699bcd716d7e3a5192e3e85115a2322aa301ac292481356ad4a041b3b4942307498ae2cf8f8e4ecebb3651b2be57b10826b7a8b188116ac4930bcc7d9e31b210f5f55fe7f5a833fea847b2b2eadb2094bcf2c54214bc2ad3022bd22b94b4f6099daf104ab5a314d54a631b17c58a2e6952514c6f493c288eaac63e07f382fcf6194071786f3ffb0a98187c6fed27828077fe3eca1431d91215eab9625fe9cbb67eb4ad97d93607fc666390b66d6d4b8bbc310a25adee9cf407d3c6486563280ed302fb58a462a60dfb3489929ead688f8d3ea6d1db584e37442440c59a2362bf0405169c5ca8c713d079fb7a5ec0bd5d0179d44864b5414fd24b8f458cd027925ea8df19573bf8f572a5c6490980f5d7d4eb4f8d9f7ad4a7c62761db79c5b6938a6d8b448f4d1bd60a5dd2188d6d238dd531a6472221bc5e2e8d1a894840e9ebebe5d2634fd2ebf5727914ec803783c017a818b9ea1f71856f74fd20eb33463086852663a490418c511263f6449ec9e10972891c9a72600aeb9a5aaf67d4bd2426c98e729abb2426c9ae6e1a773767eec720d22fbe24263bbf2426c9eeba777edd2f8949aeb31f750ca85be0bac6fdf246d0b3f6c67dfe1af7c6102b070d1af7ea7b6fd4a8d1ba31c4dafe9406ae5af537b4bb6edbb75ddfdbdc1bf723508c8d7b8da449920b8a111463cb01b7a018338ef03c4a291d29c514079d94529fb5d66ae9acb5d64a29a553b4d6d25a6bad94529aad78d85acedacd5a6b6dadd6564c71688a81b51587b556bfd65a7b496b2dbec1db2e75ce568a73cf58abae14d56590a8d2c7b456d4fb9cf306aa06fd26a5d6526b29ad1445524aa9a6d4da9be7eb18dce1b8c3718763afbd1b9c6b3738ee70e824b79fdbfb06b67de72ab7d56b647b1b277beddde05cbbc1b1d7ed7577270be78b3783ca5a349d668a2f77b76ddb360e5f8e26f99005ee84dcc959f4e79eef50388efa255d12b2d0a95dbad3c59f76af7b3e750caee52cb89f1d03ee7a1b77fa28491fc438dbcf74733a42a716751aa24eee64371da12c74faeea7efbb7dcffc29fbbee918383939dd29a84a958b76d80ce99625f56b991b1a74dcd480044de726072414103ee47ce3061876fe9c6f78da59cc6c671c9eec8c030876fe560e5e86f4dc81e382366739ae862336670396cdd9a0c4e666376ca9810a8ebc51f30cf7c3be4928ec7b3f6c61df59d2153430792fc4c84c21d3b219b57e20cdae3448997de0f5b29d3d53c133d46070c0aa66681234c311a784b540481c60010509e90ad212dbb2b132c8b6d5019b5073c0a187292430d44ba749939cbfaf634cc0129a5031acf03169ca89e18b134f098cc106269d2a6452f4cd4ac830981743104b7009455d0c4d4eb825b21c83131dd8f2f8c304d92c8627273f9c182529e18712a6274b7e3429aa5204f362c20f136a33193eb0e4078c89862f84474d1586155186d8fa67519678c2e950ab42a638af598d8928706a90d2c40629566238d1a83d67525c8891468a0d240b5278505531d3ba33cfb7879a73ce7b875c107cd3af7f64eba074ee0952588ba3dbddb62be309adb3e70c863755681f353386d03c3ccc4881fe6ea061a4c13036c070820a309040bed1e0130d3ae584084767bc1cf900e648461e25bda0b4cb6f591454c7b9f33e300744695027b5a38259dae313feb05801b570e1e2c58b6305412f60c49041b266d0a86103002ffcf9eb59a4d37c727ad51cf6e44a5e3accab05ad00086000376edc20000ea19616d779a13e2f7f1d06bd5287404f7a2890e8e6b80e41206d732caaea8fe2708da4718a32a13f4a5677b7d65a6badb5d65a6bb3b515d7ca430577ef56ebc5b5561e2ab4bfb5d6da5ab3b5d65a8bc9b7d65e5aa9b5d65a7b456badb5d65a6b3d6bedb7b364684fafcb5e9775a8ebfa4368fffb41d0aef5a2eabdf782f7de7befe675f7d68c3bee5e226cadde7be79c2048543f08c869bdb7de5a6bad76c35d9e5d9e9823efbd97db26b74d8a290e71daa8b372b7dedfeebd3af587ca9c73cea04aaf7fd0aedb7b76ab7bb31f04590fdf6badddbe8fdb3008125dfbd9fba6cd41699dd40eea9b14476a47c5d393da51599e7b6f8f4f55f1f4f46c9e9d3d3fb5afcfadd5faf60189eefb7cb527fc6ab5f75a8b83c577efd517e8de163e55bc66399cbb0704614363f2e14ad5d6cefb7418c0c109b0b64934101f600eaa2a1929c551ca41699d8fc90829d0493d06a6902b391425264a2328cd5a5532528aa394da5165150f16cab55aad2664b2e867803a95f089762578c6d9743472a7692384c27b7cd8b051a9787eec1552850c096148085a488e1a9bb0874d0f143d60abd5f3c3a27ab9f3bacc429c17bc601ab88ac87fd808a9d56a405688650394f2daaa039ebbbb7b0577d8bb836ece49575d5544f6c694bd70baaefcd448b5f6de6de3a6e83ababbb5eeeeeeeebe7118679c73477d568cf3e79e73eebacef3be1a443be89c9808ccc9b19bee54965277cff33ecfc999f6fb9ca26a543b83b46a72dbe501411d46a374ddf56da523ce41a1505a6b9d21a21c3b7cdf0853a9d40e49546307dd2110dd2a1e90082889ae63a9bdd65a6badb5d65a6badb5d65a6bad7574f7fc5d1e8968c085abd6eed5bdbabb57f7eaaea3a3934aa5767676542a1511518f4f1895554e1e4a1d746010310407b90811471fde7f4e854dfa449f6a94299bc640a21ac3a2c1112f81c928c42177538de85315aa5b6a14c7a0569916d047a2f5cba68fc2941acb5386e90b3124697dfea49422a184e6508ae4068922d66ceab02f94525a4413344529a54eec1d4a2b0e4478e0043e74608511580e6cfaaaaeebba9f212165776fbbaeeb2a120f605ea0bd5cf9f1e440a2bce9edc3ed081cf6f6b38aed618448a20ad9cb8531fe376208a618bbac6184525396a61a0864455071c66e7f99d43cc984465f950f46cc0050c43d7387b8730aa625fbbebd976969df2150d8778811ec4b44d2bedf3af364df1fd27106cdbecf030d07f645d3b46f1a12f64dd3b46f115e5eba0a5da6cb2484d3ae2f5ebabc340dc6defd68632c44d3bb80440a2e4079c900b36d70e802b6865440a4400041800922ceb68fdbdcb80104961940f0b06d8e7d69afd76bcacd21892b650389364bc6daa06d6bb0586d34e55463ade5b66ff9d93f6ccb83990e98e101cbb64ff200c5b6df1a62b2edeb506261dbe721dbf67d70a01467db0fd203d3b6df8107b69df960c5b61fc20f4c2f2db0a23073b634c3b63320901cddb9b570a40993315a509064042c2cd828a4783c4ba66bdb33d8be3b3051e605ca832e75ebc60e4580a006aa103ec6586bad7fcc9a272d2c86e2e9c598374d80546113b226949a6072118e8953b452d3444d143557d46c69f1b22725b050856e62405816bbf23302a3950ce94aa8e689858ca9c86789a61f198d4acf12b05076022a3c163bb1e4041a27d4f8844e4e5025e1d44326cbbdf75e36489435c59459fac1060d0f48c491648e8a52aa906644eb423ba2acb9552119d9131dda0e19214cd03fa0e89222a326ca1346984ccd494e125d34193831d06210348180a6759862429b241d567852c44b87189ca461d241c624044cf88726b8a933a20967c491114959ebd044d1e606ebf006ca9dd8e2354a3fd630717a87244eac2a8c628a1f51584159739758ef1025ca089c76e80213c10e47454d453bc8a614a114bef036806025a9e0c3942f58b4dd7336850c8c351aef399b3233010833539a76a99f86294db08af4ef399b52c5192a55c2300446e0cffc16e4a19c4b3ff3d72f73e89f96b2c0df11756c624bac6298432bea5c50ce337bea7c39d3f89773884e447d8ef8bd8b3954ec6ce888e564431ff42270a744d47ba2fe1cd17b1d31e7679b0d72306d80ef2dd9d068a858ce1e76a9cb09c410bbd47ba6d99a875c3b3aa2426a9ee7c9947833a8cc479b7b8ffb12dc9c36f7a56a03c1dd9a6cee37cf0a4a72db6b73af83b2d8a5dd774baa4275ac5b52cd3f9f6e4dd306f7155ca14b2e4efe42716cb5fbdc7369e615b93493c5855c9c79c1b46eaa0836c7856073f768ab2579e8ddc7cf81fd5c57f46f5265713f5dd8c73d72e12d147719b11125b91f63dac77293825ff7a80ae1e7c27ba4b967818db82ed8c7d78fce09bd7db9955b93d1e67eab71b5cd4dee5dd034978f9ce2ce6883ef91b5770a4121d0f2a042add6745971ba3c178f0e9a73ce39e755b5bf01f9f4ed58040c10f2e9577b7f0392fc4b7e9109ae7ed8afbf01f9f48afef68fb8aab8ca9ff30884becd39e7d7599c5b67189651127c2ae2a2ca0261c4788d9594d0df971c6c73471c8c039f2ba238ba073f3f75af0d76b00d96dd1a8a83e7c1cf4e1487eac1cf6d288e9d073fd7288ed4839fc1cf4854686baa2c30a8829e9f3497d96036aa4239ccb1581592f1e0734719e87111e61e7c0c23638ce573628ee12d542897a12cf08fd0a08131967329682c679959a682b1cc91e3c68d5d724fe44ff0c7aae7ef1f71ad7a80d4bf2311303d4f64044289904fff882bc7385865818fc7b1c45a1c4b1dfe58862fc6f2398e5659e0932ec6926c31963fae46163fe1586a9fb1958f36f89e7f3c222e8395f01266c26828093ef9d365c5551e2fe043c6a7385815c2f8e9712dc44e1ad3b00c912345fcaa2cf0afc81d7134b0e48eb8a3a31cd59554361a734c2cf118d3250e017e55a1eda8b2c0bf9ffa9e3127bf6e4ca1679b0cb41da58e8e36f8d3458ad3c523e63594045f2566274a82bf23e636b94649f075c43c0525c1e76855686be26038d35e18a68b3608eb621b7cd8069f3baa421989b2c0cf6a9ad0fc52584667311b3cda60ce98c71fd4254397d1dc13f75431e69e9e36f89efd5a059817cc0d3e90fcd2d9bfecdeeb44164c3ae7c11e3963d206df836ec494fcde52f27b1dd43352ceb10450d71d73acb2c0ef9cd0b3ccb1a6a6a6a60d7eab0a3960b2c007a3d8e0e7b8a0e94ec494ec5e8b9692dda3c41c31487e308319043b2316748506fc493e08165901195b3f8b606e95df77fe551ea7cbe6d873a2714adbe8a82c1087f7f33d2cd6dfc0f74c4524603cbfe713ecc6361c5416f818fc2c967a83ba07fd3d8ce679b40dbe3375e07b97fc20cd20cd20cd60fef2c17798831a0cc1f73e1104c1ff3eaf29e1cda0922bbaad2ae4faf7a2269f2d5ed0dd779e534ed90f77d1a78f7a1df451e07ba223117d54163864dbdfe8304a823f4170aa9ad0df7bcfbd40eeb5c1df68756eb40dfe26ab186fb20dbe0b9a2e72a3ca025d1ca1ed57071feb2ab43df8215985eea3c632c49b2be262568d7eeb256cfa7acd765a43700cfc0625c329f404260620e00d42d8c107f3821bf49dd20c5c844d472edbdb0ea60dbf42039feca8c48c9a6962e9d08c00004010002316002020100a87c442911c87f24c14dd0314800d65823e665c401f88435192c34008628c21c4004280310418620c62caaa0ce82a27d81a1ac6c4d01061b5d9edcef91f453135597b57896b298b5c3fed1607cb8201197f8f5f0b436057a8d99f0bf9df4b0982c9d8a91fb0b66e869a4f43fa1ddd75ec69db3caf7c264881990dc6a65fcde8b6eb2fd58728b77d373a70d78b6ab8db30c1258e45eaa8e9bf1378e20dd0f39bce6dc57d9ee720f1fd0bf4ec72b18683abeb89f8ecbbde6857bfd033626cd43430c9911a89c73558d1ade1cc59ecade19f68984a28de704b00c86e1033c924462667a000997ad9e6656fff6e3aa9e4c3221c21951df47598f989bfb3a747f91fdfadc6fb8a6910d74f90fdc7cb2040a317413834047e19b9d105e45e54c75b7a7c8de76a4a3cb8b2b970206acbcca66c79930d6e481c8615bfce2dec4810865abf6c19f6a0c1c2a98c2f23c3bacbca2dcf7f44cf630c6ec94b5026011c8f8f599dfcac8e5c73b2d5dd7c505ab97fb19c9ee6409494fa22491750fde0fd9467f535c948f53a394e0a3fb5a51439a474a43ffe0da2f0fb6225beeae4d2bf425289b3d7d24d1411cd89049d38e1905cb5c1f58c5a7282a2aebb4b01a7f3cfcf1e91334257e1c78ba5ef2af4f6591ce6e3da3c315dd24bef1d6226f74ba6169fd7c5f7107c2e78bd0bcdf326af270dc269fa5692034440a399b139884ea4386b2e6f1818c921af102f9a4d4776d6e1dacb78121feb114e2da06156ece0b1294d364a6f9a945644790f06d10b189394897602912627164d109876a7d104b94a02d963b04c98adfe403829338635d40289c7bf7ec49b1885578e65310819420c4340b0223899f025f244a068b79d492266c9fcff60100068c9cf3b66322c9a0e59f6c641e7810e1f95b821c769ddc56ee1acf5984945cb94453feeafde9aceda418d5068c6ddd8431a9b2431330886538fb0b5f29e2cfb904739c92981f50ba5a8a09900e1cbf7ce45b53a3412d2199c79f3d73279ce7bf8c76555c28536b3115e28e1cf42d426884fdfa5657aa83f4506f8f2c20a335085c96c98e8c841e481cdc5f8e789bce4f5fed95e47bb40a8f0d98c2bb2805ee504f04ea7f02d6119965c5fda975c2f4f89a25a6b61b4201ea3aaa0a9a9f0ebb8dcf3e12b9c10b0b18ed82b3cbd7e99bd98d85ffcc22551f2a8ddb51e579d1c2839cee2b99994c7ff68af798c5550d55ebe53f2fd9c7cd7fafe95ef8b372883c2fd75f738a45ae9d2835979ec2e8472f86e2e9dc91b3027eb487aaf74b275c9bb45fd7fb93b914fc20c0163e2556e6820d3e87f737c6b4167f9950cd460687eaeefb159345275223f0d26ebc0348d4feccdb3c955e74251d4956a1198835f2549e842e1725db120c2984255cb18ae10a0ab8cacc0ecdedfc96e82e6954497942952d6861f9b4d9927fbbd06b62bae756af2d71b39b2891f5676abde8076e883d075b9e6df8bcb3856d6e558bfae510b77bbe76aba13ebbe1c8ea3c63af59f36c84d316917d238164dedec159226a108c8ba947c9c62a0c03014eb9c8d509ffa5bc05e41607c6c1dd535b1a1aefd55f6db9f96d7a3b4266d2eb39f69bc491c6705170635417c76f54703283816e6819c50faf07ce909da0355d728e3cee04d1a8a8918516c52d4e508ab22291c139e499378066fa2098da96ecea3b20d632bee696da3f856c7114bc00c7c795d6c50aea4a5f77c8e8668e51dd5269c2ef02b1c48dd6419f6cbbce964b8875173e27282c87f2e351353154d91a39bf7f803d6d3d51d912d98309669d2010b52e5839308a2069b02be7d3627223b48889c6cb50d6cfe56f7bba647e6faeee75aa747dae0c3b80d82a1b20897a22569a1aee7de6dec850fe285470d4650fd7ed5bff265cf56e3e0b7aa584af25fbca77bc576d8f8e320a50a595325a07b4a7cc9fe66bbf2e1dfc3683c6a8f769f2645cf49f1961276f04f5a9bc0cb9380747d1075107a28e7a5b6d071242cab1cb1e0ea9e3f70a6e4bbeba8fce40b75d97b52356f987ab24fa187d438aa92fc23803271ffe4d658dd7f9d4b4fdc4ccb8e39ef21b13ac9f116ea22bf08059a1029821fbea8ce91694f634fe3e20670d28b1805ac5bad5c52de942e18e92dd1950384b594cf9ee9db5fda8399a017dc3204bd8ca37c8f9393edebcd9fd887d35a00d06b25c7481116d85c65c45232f6495ad569fb53fd2efa112bea7534b0cb2d399b4caee03b8d601c6618d213f97632f991829a2d23a0e988a01184ada7b7d6a20ab748f24b75b7acc32c90d74eae950073858dab1ae2a47d95f83d02dd2e42ba19c89c073e8394857ff25a567ae5be5d255ebee0367125100a3d1895a5c4794c0679555eaf33c0f7fc85480836be9d8a913631f4c1fcd008e5d0d9df2ebc2d77959d0417b9eb31165b5bea1430194084028e88c6f2955dc9b9bf641c3a6a718d1c0ae442a5378d747440564a055d57a5e6b40dbd02471728212291f7bcaf2a6ccce8b9c4c47b03330427cdf29f9263bdc8cf8021c8cce00b28bdbf0af40bd2d25e6b0476e5eb662ee0ef846570cfd469589934a62d5cb8eb8ead95dd475c37147410608873b81452f58247c2bf07c80f241789faf967072df51961e9fc4b31927fa71ce272e5c8be516e761f8754f05d74b9bc8074ff31ac4eaf56e8985bdbd9640b994fef6feba9e81defd161185527321d3ea19f3cf19275ea7135a42da5955200b2a990c4d2e6625f8cb513ba220f1d67fd3fe90b28eda00bdba9fa161133374b3d69d9663794b9d60e423bb4f92222e3c011f1367d71055c4f9e49f007cd48ef0c6b59517c8d34f9cc2895bae29adca51953b4d75035c7e7b437a5619d2e369b310823f197b2411afae924ac4111d40eeab670594b05482e41417a181c89128e3595a6d09c0db69a6f9d5693a3fa789ae4c1265e64768fb4ba30d5f41b7e896c5857a57722bb24c2dc61bd42f36f78b5d972703c07644f72c2b0486cdbf9042177938bd4194fc2564851ce6342ad4d864df71fcc4d3c6755480c9a1ae1eff08b461d3404ab9a28d109975dd48eb47b23a73dc73228d20e0d6981777c23b0be501a1084d4f5cca8b54773e8c85fd612003940f499e88862c0e7e0b4bcf235a960fc5e925701b59e568474ba462a9ba301524dfe5bfea76bf83959878a77611885cb2265578cefea64cc9a2456ff9436bb136ffc38633df479f5325a5bd0587342436c0a3ac35da5f019d2f536810e36076dc04219cfb3ba8fd1fb3987d7c4782e7440bdb1b8eff106a67e52948bba04147c8cf4191cee1aec8d2df88cefc3ff8cbf5b353e4566453b35be6d199fbe7d073f90b35a7833bebb682133595a7c04631904f560f184f8b8240c30dd5a0e449e2db9cb5127c5a7ac0f1a70f57da79237af81cd81088e977d097e61138949481a1137b4de92edf28df8bb787c9102ce7befce19e382e5b761ad2d03d3b71b0887c05029fc4f7497ee6aef1cfb1ec7172feb4fa09eff82041ef92c32ec5de519defc68c2736e9c3edeee16bd2f794eebb42bf65dea99b110037f29017aafdcd7d91d036569bf32e00d8b7da8925ad6fe4eb49316fed23372c42983cae806b82a4f7cc58597a8f01fa84db9aefd1e48f975b892764ad8b1c3a7a9fcc9a27aece21e5883c40bbe52821d8c02b40981d6b65fefd7922ab78966632ebbfa852d3d724b27093a6b47824b0a6b0d88d2fadff1f5901abc0959139faf3fe39d1873be45e5fcb7ed134990172dedcad8e998158b2bbb5327e5a404d95953e1f51ecac2561767e18e45e2d0aae56a8c471d7aa7140f8086112591d9cce551e38f0ebd2fcee6ca0cf604986e4d1e98527302a132d103a27077a31d525c88986c67cf4a82ebf0bec430cc8f1ffe8f92a88c69672cc364ea2b5ae017ba9d74188f0c75acf22dea894fc5a1cdbcfd916491f296fa19c755201e98a3a190cc5ea987afaf44006da82152eac9ca23e503729f814944a78a604891d0a8d0729dcfafb6d58e98603c8bc8eed584fe24c37ad1b7c09658f982fb7cc585f4f8e7e637b3be7c20dc89702920252588efbbbb8bc3537f892a44958ce2719a15f1a5ee67e5b42068d8efbc16bebe6dc98000aeeb5cc297310fb464df60bbd49aa5265cc59c4134a009487c1c97aa94f53c318170df2f91110acbc96d6a289bf75376ea0ec600c3e52c5cab8c0743e878335034ec53ea79c790e617ed5ca5c6066885f1ec090f3255dee96b256f02e8f7d36fb9c1a476cdbfc23e3d68d2a31ab99fd207e6c60e5a74b6cadfcee3072e9a551c4118f3e28faf0be5bdebae8015ca5a829732ad95e599a9348ed881a992c356381a5b5f0df5aab843e029e0f24ef7e5b5861a0291b8d5a124feabd0d0c8591c19ccf10578d813cd4809e55b18f625f879221052f514ea5629ae02e7e80ff21f9e0f680cfd0aff1d1b821be69b73d9a34fc08cf43260813c0ff7a646541136e4394c5c584ee40feb07ec8f1def6592c9b624f73e47dd7542ead4b43b22b47c40ec1b8243ce508114229a823c2d9a8c8679a0c7133a05be73b83765153decb64c12d25895ede4ff315904d593edac9a0bfe41d5d958ca9c999c775e619e2af9d6acb34d2c7f521e11ebef4dcae296c28e304d14410664e6cc9ae8edd0bc5eb6d2c68114745428a0c29a69a5f0546b2d3d2caff559c304e074f7e5c6727ec59edd35216adbd139e32bb199d5837ed303550a2b5cb739ba2f977aeb92a304c20481c9b834e5a2bfc25bb7d9e547f640821ba1a82c5526abb8014898b0b5d051d9cb5f28a445e19b3a2dd950c01204f1c7669ae2131694420c44ed42cbfae04978fa5c57548acc773861715ab9c7f047d72420fa8adde633d1156d5497a5329dd6da7c2b340897cc030917c67c974e145fe28b04d0fa082f6270fcd7443a1dc948c9c297f66b40da218fa736c8e30d469189d5fc6675bfc5f9b5aeb027c3dfbe5df94c11f74226ba670c628ede39fc2e76321503e2a61a02915b098f6ed2584d053360e58a7a6a9c24cf8b7704ed642d943cbc97d07a992a1f5ecaebc20401e7a0203729716ba6b31ce7ebe75c618f998644fd9a890b581586d596240c7626a19eaf11c116e401875c37f5c7d880b10f92237f941e269710c77c07a5acc91de1e4728c5c4d89f07a235634e62f219f18efb3616102006a12c9f03cfadba5514d5a52ee8ef3e3bbde7c2c413a2146ce87e934659d4504f71b763f423ae176e6af2a606a6170f834f82346d632448cda60f28f874541cce70ef4136548a07498c113394e64a250902b398edb23d3a2aa588830149328f483ac398405be16f72fa02b0c214c2d2564c4f7338899174d7277ab2e2e81cb2aabb7624ba23bcdccb7adf73836718044eb753229d835c634dd9e04f1ed9c1623006d9359f2159fcffe3e4f5acbfe2e0f4a815a080ad0735a752a6b2d7b378c6ebbb439450d7d475eaba29ee3a90314475edc8248f814f88aa0701cfda12108a323942e5ffc5446ab97457776a384d1e7073b85d9e8717a91b9ddc1298f4221817d378a7f2f0ab05d3dfafdcacf15462448839c82a2020495d44204bc49a8fbff7f19000d8393c670b92f5e28f051a6fd336f00651d094e25b23325c330a803c32d013db104b79129954d725bc94b807d5cac913c61b84696ce5574615d9d5f6b5ee87ef037c3ae921a7477cfffb77e9e76986055f79479bf16e1c572bc58ae94842233149a8845a279ba6c67a8e6fd357c1cc47baa6257fbf55bea7c025873bdc1ab3374a3ec4df449837d3e841ce9a90055f78f5c05e9f5217e1437c09c05135d35e3da575ea1c2ff3f90c11cd0816a01ef9790c159b19dad415457589181ae8b42bc4c9235981e0d61ef29e3ddd9747826c5f97aecb0063e132857176e11030b302c1d3ffde49776f3082c3d30f3ebb62ffa322861e5a88b238c78d965621ce69d1dbfb1a5462893067f99452f2cebb646401be4550fca1e268fac4efdc31b1f9876d55bb6f6dbfdeefa8428cd0bf3a71e3ec12ca2d341395aff4e314650fe31dae67184ed130bf2417a7a1cd577cd71b3c796e75a0fd6ce479565d2f24cc1ce97aa276b2b9381644c5bbba4168c7f81147e60d5f0874ad8409424d7db856373147d036c6b2eba5c3a042d146cfacb30ec4663701fd2a437f2f8d155f3b8cac7947f0b226d3e25891cc7a5f2f7815e9ee68245c23ab9ad76683f27532dfef21efeb63b4288e29d8365011a4335e2385aebb80d558e598400ab5be7e43cd49d1be351aff492523477b50c6fcfedc8092456d50e2aa89245a932feef1c48415f849ec4259165bcb2e7e2b3ad2782b9998ad1a8f4eb0e1f2a959a2634d643e5ab4b0bf48136b810d205173c7799a3ce7b4319821926feb0c756d78f8486ffc99c46ee5756e4fdbbf15c894814c98a1d5346e95875166b22bfc010c74936f74dee4a177b3fe1402f60540d13c36700a8b44f7fab7e4106601b1f9e70a03c7b262ad83a9f9516a01e7c7f9b4ea9b7871970f8c35e35f08387770dc88e32bd6ec2ba924b91af8830fd36a33d0837be85db1113766167f79bbe241969656f848299f88218757d2509141648a25db98bab04bfe79e5543ffaa492f823fdf4adff6369b37bb93f6a607d73e534dea327536dbd957edbf298da5d7d9afedd156f54f96fe52eea7e407536053cf7129c1e982b8eab2538206b3e0ee40f81582cd970efd5644fad9cf72a46be60c6f537f10a6f688ff4a152227c333d81bda8244522ed828692b3362e1e1489da72b95e14c402478922307856c981ce5cca51772a8712f661d441d5126f9d7cc9b5d821395c7cccc1774633c06a7874c1227bbe284b8a0695582c685817749f7219364335e07068e9264512fb0ea7156be482e076aab1ff85b059a16a99732343d0c3f01b56b381724dbde96093f716025f098329b9dd7449f02d00a1cbaf7582f49e89a2c6347477834cc210947bc3bf93adf364c0e52b95a1c2fad7d8d46345a87b2d83e0b0c2ac7e6817bf6befb6f4552c4356f3ce9bab94e91c835c372da1abdb896fd86e1587567f86506b25ff1cc8137402724630d1a6e6795f0b0b5fd4c94b9a31a81a40913a5d5251b0a82f77c0dbf6f4375eed7e34d7499c9d65fc75fcdd5f839678c6918b015882cb57c4c74d030fd5e06c60661c23b3cb3e30aaee8bb3c66948f776a705da9db7dc6c89728a0716a7058c7ae127dee1c63d95b75324ab28f68ade867531222cbfbbbc7426fa6b245e29c0ce08c05cac466e8d6067f216604e72bc4d0319c93c69282a8325ee3973d5a56ff521dc0a62e33379ed36108e069c38247f2b42b3a1edae6fc9bb45f2648a3557e4280ff6b9209ab9c60821233e81312b5f372fe9de589d205a773c8043d33c16f8ef38462ed9c434624c213aacda13eaf674c20d75d712b4e992fbb2b3188ec9776a319147de8dae1b8140c1f7dad7d16869105dad409a0dd0ca53244bcccb90a397302fd208d6fc4decf8a9c147a7fa33800f5de467238164ea008916557b9a8f8a19c00bddf4aea3cb139f5f0743a83641b764917b8fa07d047d83609ebcbcc9ccdd4f3a4677b7e7acc37467afab81def59a09b8960694d199d0b62debb85613acab89ee5859b41e6f6eb891fce67b290289c0b33419841a70978da9e711a023a9da6941d5122660c84844ebb2b087c8eef95eb0e0ce5a8a1b9494f16e57d05c1e3a0fe13730f9c9fd6e026d1e4e5b68870ad0f832f5b8d42a691bc36db8f459f36c84bcbade7d19d89fecda504c6e64ca2ab9c710456130918899ea439ffdfd4728e2593fc80354cac56a517c6d0010ac764dd6818380ced161d6a89fee0ed9afe545dd749b6006ca89220c69f8f39daf7bde019eac750a129e1612de1c5b8b6da9e438898d667645789e12cfffb0dc5a1759c98fc5b02b387f833aa559310f062db5adc550a04de4ad05e7ebe46c7544d822dafdfb8782273c8a95c0a707babe9438f76fe5ab46335979328f0bf3cae9b4757b7f3ae364efcfd06a83122f0e81bd71a0cc22ed62e9614124125af12cefaef02cd343550b8860c38fb8ad0cd2d5f7029c1180c00007cec855a43d54abddb4ca207660846fcbf12cc9834c97b2318e2706fbb8e75c9efd4a124f2608bce7b5172e7dee2b8dd24d9cf1717a505051eb659b7ac3b0ec6990e77e3411ec89a86d7c106b21373d4f5ec6aa034e24f29f9b40a1575932636256fe1c2007a5a95db515d54f2351db807713e3ab3e81f29a723348b6e0a1cbd158d36a676f3c5da9f85ba3094f7bd70dbc2f824f4640bf6f4b210923f64c061ff99447daa9fab2d10cc8eac73d8cb2a398670bda1eee8234f747aa1650d2657da29ff8ddbb3ab44ca2bf9097bcfa1138391a68482f49b86cf19f345f81916a819ca3b504ecf853840ae4d56599edd4cfe61095c9bd03b69c75921c6f4009d04da38357c226212467928af5885c8aae60b3ee956e7f96046165fcc7196b2667d276980c4f8c51c143685fac6243829f00e7c90e63a59fd993261a1ded0b202e58c4c076be600272a364d229c1c9139b3426271fa5d889eb6522142d589a0420323c1e53559b27e161d9fe67d28ca80855b3debc04c6c906072584ff6c051f6c4e997c74838fb9d157e725d55ef61ab258aac8a18d1042a5cce74833b4e1151d3ecb39d9aa6db8a0dea13bda5854d8196b41c2b61cb0ab2c1d673d280615e84e2311557d97e542a09b359beabbfc494d45b810e82761c3c049487cfc648b7a9bc27c42e68fe89f2e23f3e28e4958e3083356b54fc04d1e5d70fbf034e0db313192aa688bf60defaa81a0889793e68daad33179b924af7ede6fb6f97d6a51b018e3af64dd9aff4879816b728e800f3acd2f2ef99feb4fa6f9860be97b95e9b9efed7ca5d0d3594ff73ae50f64bff6036a92be0c1097e08c24d1525bb1f1047e60946366840bc0c559c0c47716a2c79dfd095fbb35f54a57d0f2822777bc5a6e6783981ce89a55999814a31b83a05ef1f54f99910a78fdb2202088a369fecf5209531f46acb16648c03232b24b9e0245266e3d9d08268a59c394f58acc75b5a47fb5088b5f8b3d5a9602b4d6040a999a29864a860a4cdf84b6c3d26499c6ed6e2f71a74d1a7370a1057a65d25fa7bd34120cce69e954d4f7ed3b510d6c3f6e99fd4d24509bc9ab57929b742609acf035507574b0f619a431dba47ab9b9a61cb8b09f79c694c392ea1bc3271042e2c835aaf211dc2798c58ebbe15fc4b66be58859b631949c78cb0c22b42aba140d28a50dc5c55a8fa56705028dc1c044576a9546c45ecae16666dd4a4c8d28937d66d89d54a5fe386d07c18e924ddf712dd59f4563434b14bc406def999c7e9358ffdba647b9420f4e3191ca553327665ee7f68b0f6525774899f1fc840a49f5de96a5775990a1f9c7caca4e6c55e4af053c36b95a597f5c6a262c44ba364a551f21dc6d275ec05cf7a7b95d24ee44a02627283ed6e3e28202bb5c70d86bc83a7d8f32e2d15be65d53926eb1ac2031b0ff2c860f0ed23dce8ef2dbdcc3d588fade57de8514b067c257800fd300fd1e51f3ad58691b4d0bcbb549600970cdddda5acbbecdcc277e8f01bdf693869a4234b9a6cd216812172d705c8b69a73dc97c31ba0635f8d0811ba7b118b9aa855dc0584a02d2e5941ec1b749750255b3a9bd418ac5117c0c2f4e3b52f4092dc915ecf99af53d2aaddb94ba2d41b3bacf405d47bf7886fd3b5b0c5cc0582700453fb0a906e214cd35247840a12f344939f78e7ca5adc366d90088c01223f66be5110460cbe00f1e84a9128436c299f045500ecfee680d69fe14b27cd3418b273001b43cb65b4daac709bd4b4aea000e3ca8aaf95e8f60fae327734d716aa8d80930aec558bcc424bd2030cbfbd3707de433ba43c6122c014d63e052c08d0118f14980b576591afbc204ba19b80dc5d047fa7f7a3ff92e8df5da75460d85d2282bc0840f9b9f78f043fffcd317206558920c8f5295d520d88779f75436c0171eab692af86fa0ddc7744fe435522cca42fbfd081fea78fd7f6b984222219da97851bcec481d7845ce2f55f1c33bf6ed7517d949e698221c74a21d3728b3748a1f7ef0a3bb1cc917e656b37b2f55bf3061983a21d25889e4ec34a8784f6af12ae3d909eec0d58f730c2ce008d5d5f72eb9427eebd4423c0c8b61a725bc0f9a121b5b3a73c551f71bc72fa0cc90d2a21713052277e2eba3b5c253a8eda809aa671964a7426afdcad8e76005b37d918ab051f27dd13faf625cc0686923c385d7fc1408aec4fb0edd4a9db18c3d1b1023c818597fe145a0be2fc2d1e77be16de7bdd51e1ec88d2b0d859f3a9a1b85d51cbeb90c3961dfdba70952ba444ffaec9c233604976c382678fdadae4a0bae5b971fd85f5c6c6f38a1d56db282ebe3ef698c7e44b47212b48159a00c102e4165aca1e4ba0f781068753f56835cf8eeb97c578a92d0c020c2df5d9d29c532814b7e66c02d5d5ed9201c07843c4d8df519ccebcf0fa05b091b7b60a1884bebc103bae7a71251632517c153f982862cd855b904f70108efc6951b7cac2855c2bdf3a0658552a3f4336f473ca5bd12088fa3becbc8f05c1189d01012faafe0471c262f1af7382ed398134c41ea6a16958a786677efeddc8b77240862b30d07af6ade53dad1adb8d01fac102ae29db97e1d6357926311ee8a712f5b7f23d4353790a988357e13dcc119c5d9eae3d6696cf81ae24748401fa270dc3d6c5280c4403eec3d35125e8a8d3154f84a04d6751e00788a62af714ce12244a61ae16e8779fb231a014c71005fb58df9fb37cd0a916f49fae00ba77641461967b2b493babbf9a9d544d66dea97c3015f2cf04cc93c8dc239557d8503a3824878c788da8f7196781894b402fd8e2d89d30dee8a13eea62bc8bc843193e519dd70627b56a10a09372ca7a609d589c31e755900b0e11033a624200deacd23d58d5a261dcd289b5fec7714cc15907b986fffc9f48ffdc041b6707fe507a3b91d9cd15d74176e6963e2caaf777e0555c0f22c0747b275eadcc03560548278bb0d6ffdf9ed91624299f8f070246267dfc85eb161c05f6cbd9445c2fe0e17e2bf3425050ef35ba3cc0d74a55fe8d73b52feac3fe2106c18b0a8bbf02b207170cc541b61b829e8b958897ec7caab7dc79a17a886b5c2d0b27f69c29f458c6c64e24fc87ed1eeddee1b0ad699ae2fb6f79c664fbd7fa7bc9fe62077c1b5a20289650fb9fb9427790cb786720234980d00a435a097d1b7709e40f0e33b6180f2c56fd3eee3c7ab131772f57afc39e9d735edef9b9ee0d1ab9519fff1e7f6b24f685f8ed0ac01f199585590588babfcf077a6dd39c7b37ed85da08c9a4d42ec6112877cd25e793a398b763075bd44cd6aeb8a6d040612a5c7197da9221a1fad69d837c84b8d08fffecd18799f13383375fad267803dccdc461f53c2f255b9bfa28227f182224abdb06c128548006d3ccb0442d88ee898f13fa88fff7edbced3f26c8ccd568afdb6e481c9af5b21e4e11f79abe4da39c4ccb8961891f5492e5fbf0fb81f8df23f9fb3709714e4fe55b218cd035e6cd0371a5344e4ceba07f3404e20f6a0dfcfd232202345a7bc185cda2331b4e6f38f18db2b1ee94514234d9a4a412911a383a3149400793b7d64e4d511ceec5899d5b7d483185f47ffab1f1ccc61f70165c0f68e842e60eef4014fcb0bcaf6870bb9258804b4a2179b8f372818e90ea2cc5dd208031583ae89fe3e012c818ccc361b26e7a6a67643d47d7a06788f04383c665a7d71e424d22f206a7ddbe0e06169dd0e9c900ac2edb6fd67da36ace681ad3dedaa02b6f7306816e22891715ef946ca29b12a6a2f6a43b1684697740a0ca56261073dc15941cf8cefcd7760ed44d676eb9f6590d52a7a6ee80814fc43593a492d39cc9e6e80e9f85fba34751ab426703d33dc85ec262c751ca69326e6d4104192fd9280a1982f32af1bc0f43201a773374788be3f39d12ec031e11b7008733521a20f15f38e87b0be1ce2394f568bac2b3aef576a8b641267ac81f41506af7bdadb5a75ff18dd1c801b84128f733284e4ab2b7db5601eeb191b98ce5a23107cd0c7a01500f194df7c2627fc52a2b73fc2d955d66e14e6560e88447587054b8ba6dcea50fd3ecfd184388f7c6a960569cd2a4da0be53568e41a72a6e7645e1e6c435ff92804e925857e9f06622170510743db2afde1a329dd0953f27f5344afcde33181b6f010adc49c8710961b92dfa7cd2f55f2b0781808638e209c4c5cf5aaf94166fb1824bdb3c956444aa32379b79743b438972941aa811a527378863190c31bad24304fa9e4059e8afc284b980587683a8f14628b0711709aba9cc5af570870e22e45d3e84beddbcfc382d864324d9eb0409558bd437ca2219ade60228ba6b4b4ad13bc4de71568e492f8ee78f4b8ed1e2228bfb20e17920d8924446efa5e1a4f5c343ea31e9c0db8870fad37e4a1dc86f1618aac9a91a103d4f17bf8baf70a1ee61672b3faca04535b49093a155e38361cf78521dac54b70575ea75915667da5acf9e7ed2a453b015125aad9a8679137e696c4c947beaef0e6e84fab616ef04d8b0549e835aac8ebd9c76dd01dcf1024f2fe320a171f318be32efbda1c7582048bff3ec7196799b5b76d8f97df001e7cf9ab8bd33bd45bcfb73e6ec840bff3abb3979e4d2012ee291e3bca107030f2ad2043f0601ab1ff1be9c7654bca225b14730f6792d315542c73aead0c78bbe4433758363a00dbb3418007195bc1707b0bbb00861909a70cf1d5f3e51ae57bd428e793b071c1bb1240a7e3999ca9e9f57eec805de1c6d99dccf0a345e8c1e26780f32be0f849a3390619a89222be8219617044219a5e20f6c99c2024ace24db1f5b1c10a597fe6583de0d2657ad9024e0c14a2757f0ccda2329a077313a60aa4707da658e6abaed10fe94b078c625cac3c0e75b1be553179c424866f6e8a86e924f6c365abb22f4bd80d0b52d774ad757c231fe5e461f46312f310a37534873ba75162f6febf89aeb0ee1304bcd2f63284d9ed04417df1f9b5785b39b317c565096a7318abb0da0e264e77f2a28b41093569cea1a7b6001e557a9f540a3f26b094a110d0b94687924fd1dda3ab59a3c0138beb014a2ce2a39e3a88d69d98ac567cf394e31205bbfd3a7b406a928adcc6a604f19a30c1b06dc464d514f68029c2c7296a10ad178e9caa7e3202f2b58e3488a643e67b505017df4d11b81647b126653963c701e645cc112bf9015789b4ad71e6eb450e30a957ca0bc1cc935682503a6d51c79a933fc8c11ba37a1ee0941236093081394ad414e3bac384846cb7a72cf9b92e4e102b6fcaa288de03ca052c1aba40282f219f41b43b95d643d2cf0c2c5cfdb704b3208408f3fa3b50332e06d16ef4444bda02edb258ccf0cb8577824c6537389b8c4d0db14aec3299b84ba2165ff4bc4a6b98efe2ea6a38bc84b31f6b3194816b6bf93fa543a2711504cfc41b1c165641e77a718f310967289107bc231444d3c921540aee9b7ac8e3e9aca9b72ec18c266812a69834141bb9c70b4c99d911961e7ad05e7f28085839026f4f780f808fa168b9e236638584209aaecdb0eda555debbf4253825fb0bc3cc63502dc854cbf4341b9fcde7f2faf67070ea4b4b017ff10a8f45052e50d416b58a5303fc6c74556936682952093f31f119ae24bc07e7ddf3a6c2d591a85d96a0fee331cf5ab0ee6e9a30abbd45814d9790cca5fda88f586fa09568205a7ce078d04c46356dc96865a1a15c3cc279794593521988d615582e0c3940a9dc9f37927f0fcdd1aaae0960d36df722dfcfb02ee7ae0485231ae9af86801ce4a7e35f3d3c6b668218668eea9419d2581f1e52a3bab9aa42680e4b8ce2f06a98d7cd92b42ab46410e58d2443064c60e81200b38016aac53148acfc9ba6a9272c8fec5d3e852d0af0eacd018582a007be737edf271901d1ae642f903e319af0ac8eaab754e55de24a56b338ef2fea6a20af7e808ada3c78eff9ab3193f11a4a138078c66531b0053b691b7c939357bf3bd7373e9bb316934e6f00d13a0e488ff57a4a3f68da70e302ac956ab45028908d716a3b98bde9e9444f4a13241afdc42a457cfaf7e4f00be92e45b5864d6785102881d525fcef67c7d7348eb8a0da8e415f6f22cfa9e398cff8780d12af751a08ea5ffb8dfe8e747f2d6b883347f8fc3c7a29b2d9b66a59f3d3a8cb3649456bc206d5e301c822b7be430de4ae6a7ff214d937cef4718477e252fe6bab45aac5d1872ba20a0aa53f171158852e3e1959f2ad3bc7ba5b63cb3622a8f996dc66b5b71cac32dff3c363e1c8ec309d586e5e6e8144894325ea63836d56882ba1553a1c13e153ada13b49d0adc49accecaa10370d5bb43357c8bfb8045d6c4d105b339f76746d297d07ad8f177dad9f23596a6ec75b74f6fd692bc95a29fb3139bb200c550b553a8e371f7cd1b06b33f5e308729eee3f417dd08d3129c9bfab52d32af84e57d117aac2d839422a813d7f68471cd87d322d69c729aeab06aa3f56512d8e11d093bb84ac765670b94740e85e656168a3a6717d3aa9d2d818bcab86abc2f3869f0c563b12b048054d8369f16a08ead9d75ca920c769cc6a7315bbb4094ec817622497ff1865c86224f04f6646f899a13e8da9717133ee6c6bf830c3ab4fa90f328f55d8faf5f445620a9ec07261c74b05d34e11b4ba631f5cc14d5b1d5def056d978ac35a7500be44962c22d25fb878d333f6c13c3c9c5c811c220b83fe976fb2deaa8d980535cbdb565aadcc907ace4f6121fd6075cacbe7ced5bcac5c7cdf1b6f1230ad67771ff749bb994dd91c1f19eec37d33355ecf6ca51b49ca0baa9906de2c4f2dab6704f74d9e1965f9aac5ac4659a5792b1cf6ca60e818e8d23de1a43e95e88c41a995c90ecc0ec22a676548ca6ee6230a153217bda9dabc71c1ccdf6aa967b62d7a8753cb0dc78c835b3df663ce7d700be6d90b55f4037eee03ea2a1a910f2e511c1da3342727af92d5fd7ad83b86fb6d5119d2eb6c9358ac765528273b18d831c370f8c3e626966a13679469b41d4ae427b930049649565a17186a368ea679e37c0fd74c5f55eac3641ca1f2a0c125c37cdd22f12dd3639b7674cca3d0edcef440ee648690a69e96c9018d3c41f19ee6e6ef68dd1fa71c7fa7fd6a99cd04e8e66172e282bcf9245f4a6a8c0d5840b26c57bb6e243b95e9395c79be7bbb75c47571bb83f8fec0de3e0b37c33db2188838ab67a4d388893d58b061aba9f0721ec167f248cb02873e77d59752682733eb3436343aff1a76ae51487accb15b2c6220a157b3538bf8048c8b1834e9ed50a6415ef771dfeca20241815efb838331e59f63fbd2efc3dcf618755b0e8b6d4d0d6441c5dbfd969bc3adf3e1b8bfbf5fd4e212fefb093474ae278e7e9e46eee3231394815e0e962ddc696417a9503d8399310c459f1b1eda2485717544a50418b3174954d5b3c0c30284bd8ec86055be15949821fa22fdf7bf1db0f24ab324ff74b4c9e92b623f8f7ccf1759a534ed63a88a8595530909456e571d3bd3d7c6505b28203ed41b341a5ac67fc4fd82c184ad88bf903cc33adc8d0339008c1f2b8121d908195d2569e4a81f601a0d123f1b215905a717c9d23cb568716b0b7761acb2603fc95d4eb45543e14b4c295836cb48ec97b15f2411d5e1e285dd057e3636def3bc225ad757c23362aeceae3f7e819172297f8073457bf8c4996e52790c0ae5e361b6d563b74efb22c153de1cf75cc075856c86c40ce71bdc9a3211d19654fb457f06c64bd34d5e0cf30ce0b224a22328d74505ef2590199fa8e894d3b285f81384428932b30a644411ee61626db9ca111369c172c6fb3cfd09372b6c884a8e8cdf6c3c8b6c80800a9c42cfd4600bc0817079b61f72312d5cb32ab465a5dd8df1e6f13e8455eea31992ea86041188507e92cc6e7ac90383a25c88741d19fee86760804340439e724b4736e90879cc26e3117b1b96bc78a91e9ee562a0c48012c6f38f0950f8f2797ae61acd7cadbfbda4dd651b4fcf2ba87fab9b751353ad772027847e67d033c4aaaccc05daf4f8a736776bd890c8e123ef3905d055d072426127a717829ea04fd0b62204f25e12d48c57b13a8e7463309c83cc3c6d620413e95dae059ce24692a98291891b102fe314f1352e0fd91499aa48f2e8b444edcdaf736277db2d09f80c98adae794d534de325101097ba05d739bd7cd2899583b67977487a7a6f3e0f13e25682760dfba50295dd0fc82ad3095a76292056a66a8c6cfc802019a73819e0407164aa60b27603e3653a45c93407c691a982c9da0d40902a3d0f683523fa6cffe235e56b3f2f92d4c7f77860437d64e30a09f490a5d7c70f39db4b23c8c5a7f37ad5935b9913e2e755e8dc18477a03ffdb931bea03535e136922b1e514bb0bc97bcc948a79c063e274a61cdd7cf4dc9bc1431de207f08d7cf75c938148886c47e9e988c100232ada72d6928c3530ffd85eb6eb0aeadf4a9d7be23039a88d5eb8aa6707c88b0f5a9067ce15cc62905875cf42859b1ba74c2ddb36dad5c8622c229ddd1e4174f239031037e9a7a4e0a3947d359292c092387804039cad6321936c9a8cdc17d993634817d95c1a5e4bea3bb728e60da7da5ad46f1f1f5986542c84eb55328971250cbd649408bfe9896314693c84d021bc7da0666b257ca0b1cfa811478e5948dfd1703746c30cf117f848b40f01682bbf7f3fc7b5c5c16919a57aeea9e124a45418e148ed721ba7c582ca6d14d560a9c7c71ef32f8e4f9c9b7bc9fb99d9174c45bb238a879c75bbc79fc76704ceba7258af664177211ebfd66e11c8dfd2953b87e01181a17658ed3576d572ae5b42b7df366755637108d4b431dafba62aacddb46efa2bf82d4c48559c9347305823cd5b8324c56c923b13bca18b6e1ba1ad6347ae221035b9a83a5f27b9bc1ba2c65b8aca4d36d644d22381c4baaa8d0c462439652df63e96449b96923e70c15e43254aa89baa549e784beaab4e9061671b648d24efe7b3b5ea49d21ef40eb5e5059612b820e0b89730db20ac5825057eefb25b86cd2792eb3275ccce960f8b10b0bec0020dcc93610bcba5a0acb5e135c67f3978570fdd739a0fa6343b5a43a14d26e4ebbae0ad93553dc7a6695cade9f034fa3b15ebc838b587f78217897327d8124da372bad0143fc0f063d3a02f4548343df13c5240ad4f0e0a6ccf2875f2b7a18a4c90c333aade36b3596af339b287e90f6ec749941718019b0b6dc4ae7ac0b968741d6783ecafec30b8a87e042a6ea9f53630606a1a3c4d6a9bee5aaf1d4a979f41f68c40efb236b0fcb172ba10fde5289ddff7ed50b763c35e5075ecf6449414a15bd606d616024ddbab3454f940a64b958acd85684b40cda3b6c5245b38422d7a3a54a08083271120e65fcacf4b782415c2d21ff034011a120db5783589acc177dc67694864bed5713e4516db58264657a600d2bea9fecce391912439e7b2fc3c3161094e8639b46214edfac6aa178b49550007611456b0a17bee73c1b55cdb1ef155fb7b5d9ed605964c543d286719ca4438f2071480450bc0c1cd44bad920313ecd3f70f6f8d78f2a1c5b7bf2c4b23bffcc05828a5489ea0a4b66e8190eec9120e7381d824cefdf9792495bf7fe1a6ebeef23c6af070b56b5e54cde44b7e507115a8e4f8d1d4197fd2ab7b8795bf701831f1bf0df21b3beddd2f470765a529a23aa8c4a755cb3d898d39031065f6e2bdc9c60894f12d1b79e4f13d23e79502775abfd15d54a42c59c48306ef79fec4ba76b191f4f43733fca00514d6267367a057c42e5578bffff741ffcd0c79201cbc0ce3045baa9f4241fd14809d5f32bc178bc709b6c7949394efce4b93448408059bd4204ed5cd48608d7287e692e5e8488765302fa504ccd65269f541c01086142f4a715b6a1f2fb1bbac3366d91a6900b3750e681f015256201e1173a2e6dce48bb83c114cef2bfff8c564b3836dbb0b59148274c24a041edbbfc4ce336e9eb51391ab30afa1e62d47dc11db58aca8e872fa126d13eba3729681e5cbc4b10e5faf32a7e84b8362d155a7a16f27af5750e5d223e4a004870409f02d4255973875c533f4a78b0795e813334319a7a3a4f36880a86d46f99b76b1814803f3279343814d8f980e5513038ea0f0461d9ad718afa5dfe8554e34711a00621ea67414797fc1898a3979297a8bf04e502e119fcc73353deacb4c6e910ef8ae2826767fdac30b9f4fb26fe93cc1512a0c1c0f055335f1d1026ab425e80edbe137c42759755ccde3a703fa7f8fcffe0eb0f9219bb9d2d571f9219b23711231eb48d6421e3e6cd57e0b1fb99473c415df767618026d58a73cf1f90054c681c31988ba9d4a013e1a1cf8eb9015a32c62d117db16f3e3c7424b52d8f75bd54b99d9ffe024772c10125658e814bd67e8db2075b7f663cbeca7b8b96573eec67ecc241c2c5c7f3c1e837c2685b5e733cb21201712ed0d9be67702c5f56371444fb792f10d8df6cf3b28b55b950f98123055f97c039883102a48760b25efe6c70c94ec7a020b252fe3ef5e5ceed9877973f1d7d17ae1e301dcd44822c72128ebcf149f57163901081c0e5a47a23556d88ebf51fec757f38b87fe6f68cc34bd29bb36fdc77c19e01feb061c8437bba8ff1c1c9cb8fe77d6b0bf9dd933064f2804c4635e6c7f36afa1aad138523e0e8c15d5bdeabcd68ece9fe4c06a4c98df6918dfd657e030838eca2e080443832b0703d44e454e09d0acf0d0df27e43e3f9df0a003f05d7b49102d63849de00803d2527b285d1f4f9f87d2000647663846af838b513824ab2576ec9d8e7100f1745eb3ed4fb4796142245105818d008429ce040c2376fada931a490f225c7297c58f0076a00f88a2f8c422eab67fa75cbfc4ca40be363d91df1f92d49b7bef20114aab86d3fb4c91852e833d73e64c489e44fd2bbe810f1d534e20bd7cf9fb276d0a896f5f3ae506a84d5f2831ed0c9de277079d310440f9cc564430e97a541df9188fe49b9abd8a203561e77022b6dd1ff96ed6aa883aee6f3ee00e132fdbc8c4c4ed23b5e7a1f052a803b2f052106d87bcd8940e7e687cbef6e19a72005b30762d84e37c17fe121a8846dd9325ee8df5e60bd5f30c0d3225306ff6227f06d84218debe3ee16fee70f24a9d0097a4913ac309f619832ded87858522f90ae60a84e01f592455ca38ae13ffcedcf3f14b3868fed29e7c405264d143e6704477c6824aeb2cee72d7cb8a22f56c9922b4552777833fe331cd917635534929f51768dd055ba8ab053eaff83c8265dcae994cf52b99478b471e97ebafad6f13ee52f677925e28c1742cd36baf0e61d85658e080cf5c26b86363a5002a4d535da0407a29ac5b5f8b4d730632f76e0c8bdaaa4873602bf275606813f53a82552aa44eac4bfd1a17d4639dfd3b8189643f464803781640a224b54bb3f0cecc8acca6c7196be52cd47bba012201af5a42e40d19e6f014617aa3f6306d8673ffcf12ecfc3146676103c33d41775b420e199f9a5ccd9e69f56532ad56d2f09da9dea36f59206d65e1277618b9f978836342374875ff278439766893276c298271612655093bf8df19fa8f5bea42e94337b39792813be84f84f6d91a7c22f52a86eb9605871f3d1a975ea41f42895826568bc51c4233e059792c3e9daabd2439c0e343543bef3afec0dada24458e63e369c6e42d14784b4d3d0f1a801d9c1f4d8d9d0f1ef112d1e96f5a188f9be74c55f00b8cc31466bbc07405d72e5f0907bf6f05a29c7c60e0f497f7ab2342f9d14dd82bfd0a7ac63767b27d7cada2f29aa7c7afd3e4a0745bc14863c4e973e39679bb1d016d8fabe7fcc47665810663c078ba646cd126b2824b5f2448f194d765f22bf6116073980a984ee51966eca29ddebdeb045b9d09198af0f4b427180960e4429870cbff7054e9ac0118d663e7d352393243698f55b613d8e00965438fafb4e9f2756e80caf90243a48d2b064ad4d9c443c77e05024cf84a68bbbf69ef0241d7f04fa566cb719ec6c99005a9ee89032eaa23d93f105b0c1f4ef96d83d85998efe6918bec970e158f387015843fcbe7d9fea1e748ed5ca08c06868f6649363d86100074b6865f508af811c94247fd87462fca1b5c35763e21410c705c4b1e07cb081d632943e7c1e3d4f2edc22d698e85ac112592740e03c469a7fa8875be4d92dfdd008549ff60408f9866ec4c6e9128ba031c18285686c5ef6fe31f3ae5dba8b746b7d0ad7b2f7c629fa0312155759620ef37f4bc6c349c6b46087f91fa31a4fe51583f1138c7908a344b937d0c900f058aac9b2dcc12c4e90c093b5630f462fd1a8ba4cd5251a9d91f5982a24bb3f9e8b42a6f1d01401625535d7afd9938fd84ad2701a86c81ad463c49b869012f2c9655a7a22d8a786a2c1748dd295fc289ca0ab2fc0f80e16956048c51de8d5e1720a6b4c2ed0a824b55c7f1bdae0cf278d490f0abdb350c848b88992a9e456793c853bec5d0b3623d693f65e9d12d0af4fc2519e90b5e978d3c9ef0a28649c16f6609169ddcc044bc877e50e9102a04c0570080bc99533890e0c2fee72fc7bd9f1d2ab90d079dec7fb12b43a4a0579c7cd26be8cc968659dec653c3132d026391bd139d9f9efb71baeda2200c77539cacafd6dbc9d3ff142a6940b87212ed03900e936498010f77580b62b52e6e22a2b74dbdf09535fc7e1b2c33d454b61f7fbbe9254b46b58924427e0e36464f0985de1b153adb9ecaf06b007a8dc6c5ab86e7d76c266a428b10ca65faaa1127bfbbb8566f29a697fc3178a3a9938d202ae7480f78dc79fd8eea2d261a56c17d609baf4fd5195443db72a9902abd4f488354b660eb722ceee9cd58ad881485ede5314cf48955a82d44392d8b511dcd5d7a6726b4bcae7825808c704e8a880c892f607543711c43a360c747b486899a22a731940e582c34c6746e476873e5d6ff2463a1ad7a4d266b7bc4a63843701e25d4ba789ec91073fc655f2e08ecbb91f32aeb746a37e1eefa5347ec160912223fcedeee3175728a220d08b2c314cf54569da8cf3f39032f5eb6b275c849b066f0e0ca120c41da2907fdaef2ef627e0d12834cd2756422aff55bd4bb116f5f0dca276489db87550b1e38ed98bdafecd72869bfab0eaf7d114eef5e337bd7c10b9f2c69d6d764bc7906e421edb6ed9c9ff06f58e34bc004901784489a71e00ec1d90b72819d3b55f47922a8ccd7d4c9d4f7f36fa2a196cae1998c99a9d123743b57110d0e3fb0c744bf894287a627230b00a5cce0c0db8add0b690d1862101b07e219f1e4ea5eaf4ce0cc89f65f88f078c974926819cd36804291b8cf62b69056c365ddf717f0864b46f3c7ed7e614af9f295c9fc7fe0eb3e2101412cd38fd9ebf95a7750c7ad60683a26f876e69e33a03ee7887e4e2e6e54dd5d048244aba73607e410c7ec899cd2b7b50d51f75672e90c143c63f575e644b1d6913df6930e21529778b35ec01743d95c099c76f14ed0ee6ba80b127a25697f5fc885cf6f260d299884730fdbe5e7f4b51c270b9699b5dc5f4f8eedbc5fc85d33cd343d455c3daf0300a6dced162ca522b157b3d90c82354a9778ba9445d89ded62704278e52beef9f95e7ddd25aba3afc7e47f3ee878de91ee837acab605771f4f5b72c1afa40b76bb8635310d56a2bb07a99820bf686a34e5c8ccc230c8b4a9b91e46a096dd85e16ecc40e97e19f0959846e621a4b72e5dd38104012da037c10d8c2993a94960c2d60b3bc8d8a09de0ea1e7d2b40b26c2f1446e7bfe41aaaa1878e1da14fee86d9fe51b35c719e1de5215d78868229575f5fa3b849c6fea86a2a04f9acfe106ef670e0459d044fb510424b82d3fa3ed586a052cba5356949103e945cb4da21c0ccb16b6542c2c382ef5a2935a1e9fda1451ad5907f10486f4060afca2aa4aa0aff201a49b403454d7cb4d29802884986baa3ab96280878763b51e197ca4c659d704327dd8a966c10e969ce02af81411f5968e58490de59e730ef83dab89a3a123fec9cfc8af360e27a961345dbcba8f78e322cee41079ea15628b1c382e9f765e17aade169694353d0956d87b0387bf9494f6c3881d58ca9b9558f0bbb18082d4539b747675368823213d496807fcce060c3efaa8b5b5f6abd204ff9edff6c6a1c0584908cf483165a98865e785fd0a96556c27d708bda67b057929d6d4d6913e9d64e90838b92a86addd526a9fc0135d4c162f216c22c0779bf7b0e0086e014258185b8c2ca2ddbc29af6c7bb2f3f39fc5e77680c6e0604467256477245307b8367ea480bec0c23b86a35c937717f30d3dee87a46283e75f5f3f78c74071ea2764f53f66b893f123412be41b12f8efcbfab0f48dbbbdfbba5f899d0a27ba656ab3d4f16532372806f5197b9ee60dc89b69711f88997c8c745e08fc4d9eccef93a7cc31576ea9cd0670e2e017ac84a8b2aff23b3b240e54e27046cd469a25cd2128838e8bb54a218ab7a09e0aa409576d8de1d68c30228151b86ae33a215674cd059defcde2abd498c71fb1a9a2a1319776391cbe97163fa15232e47d7c6508756b25eb81563c12b17a3477eed186bba073571c1e766e54bc79ec58a23b7a7f8d611ff75b081659b59a4486ff4518f571a8123c6545d17b200b75618a7948a703b46f375837b33f3c810b53758ccb5a76b2859709bc23da25cac032c4153fe1f3e1643018b41e733f285adfd6c392ae62d444f98100b6d22b8c3240d21ace2d1f71870eaad62cc139208548d7739920055a703de7bb49818faac984b8874c9dad3e61bd2414f95ea476e43a4f2236a65511b430345e4eefca155816af2bc16229f41cef905677a5e786d64a790bd1b2ed61698b4f92a040fcd5a896a2de9349739d5b6ad2595e028ab0ea570b21ca1c754ce95a6dab084b4a70bf18f2c1953d698770708b1c42def9becdcca4c06f3357205460bfcd901a1a0979c4dab3bebed5bdf4bfb2b7dbc968adfa08879f3cbfc96a285521c4d8fc553eb31dd84afdf382b4f5a86dcb40db0adf644affa4387b996601d657647be5d384801ef5d841da65c3fc065cec94b333d5ad8a03b89db3ec1c4c21232bfa899cc079ff59df4fd78d7216386b88e462fa40c8ba7b4a30e61f0f0fc8cc4b73cca3fa4eda9a84de6444ec58e6e338ec1747fb15ec40afe8643d325e3fd6073017f0855ab0d994ff728fe242112aff087133da467a437f29c96d0f5a9c7d5f3e158a7c9384d10f4900f015dfb1096500586da61f10183b49188971c27e24aa620a564c2128c828653981fc5e9f034e52d9cfe6c6e222d50c0cde28d2206d19f723a42f88524e7b98b9832c45bc4194ee070df3d9081639f5b91a6811e9cd52025d2d259720de8c4529f311111d911e2c0a6bf4d5294e2841b819e5d11b043d4b0844f5d6d4708901197b5804f4756917588b02d97a18616f43e1f88658cfd8a863a01ec49bc3c8c833290876039da4b4f7d48d68bfa6140a6e4c22f6503d5b15a03bde980d07cf2cf6a4c6cff391abbd24d494e7201feac8c3c4668fcf9565a18622682be0884ad162589b06f95c288ed290569cc527e5425e31d2a5bec58b53cd7ef507840a4162e3be4184a3f496f75049f55a3491cf8b1a1ca017699fa910125cf93eeba2bddde17604152f9d368bd8cc334130f94bbc70f88b8ef9178332b48f8a4c38f60c21778c08efaafc4850e0b819e68b84106e6b1fb2c99cecf181dad6aadc3db7ee7a84efe5d0c9d42dfcd1696a2b7d0eab3ebb258768ed961395576d5e84c958ca2112623875b7d998185b5844901b6a1609297410753f91e9a94361cb1617ed19b9f8e272710a1ed0981102a04540872ce7765c9db290be76af87d0efb2b0aa928e85a54be50626b6181b9ba7526a7d45e4ae8aa828d91dd917ad32c319e5a0850abff1c4c8f2d690a0fa46d98c299e91131279ad55fefd96a6031b728589c8df4c3e097568eddf76c40a6cad256d3447942e4b0218958d2363b3c38ce5b6020df4ccfa1ce2fae3892b343aa84251d1ac480f4d218dc481b3d23c49036408269c428eb5c399b8696bc079662b0c143bc562840b41829860738d3ecaa3092aa46b772cbebcf497101da277be91c042174423a6a7538368083044c27453a7469c5b2ca7c35001de8b6ad60d2abc0b3015a1a2aea46b71af2f0509e61201830f9aad4919e75c0e9aebefbf58ebb7605e942c6b210cf9129b434b999f4074a66249975cab3c23e9dfb30db820d7e629bd8d14adfb0c32bb144d2d2fa066ba613c699c469b90ace8e6d38040815ea69e575b1bc4de766023fe3b2b842a1a7f77532539d992f880725cf03767168c21ce1f4698e0cf5888c255a297731c9756795c5cdea96f97e6f28a741e310dd0e841eeef5debeabc71081ae5cc119b4484b5ac841bf61cb3da27b553f1db4968ef64379c46149ae56159243910f9c30b14c9cd1e7196a4d5bf18950333e8c7622a82d9293bb4e615aed431b40d7cb30132fcc8b4fbc68b361599fdadd228fd701ca3f44a8538487150b9a543190867d0c3b91bceedfb0156f4b020500a02125aa8a7766662a41a32fcdd3bb21a58d94fb69470f37f7e43585b0bb5450c0c9da0b88e40df21e3a12e1b11f17e223ef98829854290c5eb9fe265f860c3ec79ebf78a6921dd743daebbaf151922babb4d43fb80164937be03651dad2692c996fb81bd8ef6cc7b697d9dc47b3282a07dccc95014ebd3749bc4fbdeef468de8a5e9653a27464b4fdade2cf8daa1acf0df840266549205902376a65f295018b5aab2637a5362076aff10cedfd0d788b503bd09413b83b476e06510b422d825c62c3870dfa33aa514e4e791f3b64e293f3451900e98c5807c9f844cd57607d89eebbe71a70b85197b206ee6c68f243809488281bd00ee03758334d8d1736c74e7de7c46c1227f22c4273ab16a1f4423211550f8e96b7265af7359478ac550937c86cd2ef4f3cc2cd6618451f9c5e8c01a879223118ff7e499711cdd1f0731b70fdd7b5739cd9897498f716738b038863a50b25f5c6150c27c5536615ebf828578a29d23adcf516f836e14772f93ee6bc694f69a845f91b3bd81333c97c907c8c885822518e06fd035ac09e05a7a3706d62aad3466c923578a5746c79041029f45f99f04d4fbd003bb6805de9a8576002867b3d4d4a6af0197734baadd2ea3e903aa492167acc9259f227f84116e45d529279031316f794cd529db374cb3e59917da750ead0f3b8e9b68de0436595ec1a214ff3d37dafcf1789c65998c049722c5a4d95db7065f97b69a7e5dd57ad1e6ab7f8af21d4d5f5dc042aee323187c7e4f1d82ce11472fbdb1e2f978027dbd16940dbdff59a1eca22147fc79cf57a163a8e877d8723423e27e6c6eb13fd7f68a5ecd9902e38d0b9e6866cb01c3bf9ab89bf3160ad2dff4e9bace58f929a8676e509a2bdaf6ff9fc27a8b78971447af602f70e669ac636e62d715b541017ae194964ae5c2dd7157a8572f2417c9b89af50bb9370e87986bdf96b52e2ad0469dd3db86bea32df576404da3f3c1f5a085f7be512b24e0045e11393be74f98d2660cd5c94589360f152af1cd8a46e4320d1e885204200994515879d601eaaac3fe8c518bbad5b893b481fa43261a3cf89e14b9e3b8a928055b8b53ceb6b5e86bbb20c76ae3d9cdd002078952ecd4b89acfc3465320d6c75a7cf815dde49c3cab279fe8624f8253f98cee7519998059f6d514bac27adba9fa7b99ea998a6ee41b3631b906c532d7be0fac9f2ed29c31c99e9f30edd132f8df2650e7aaa162d21e2c1e4e2e9ea81c6c085394f7b9d706adbf5d3f99a0147e9ac7e4070624bc6add5b9f21eea422ecc48ae550b27ff94322d174b4e4ece165ba3c02b75775a746ebd046b870be555d1b636acf23b9ba4a5d7fcc7c297fb6c17356de085ba43c28208d1aef0af1488cbf65337715646a7f23381f54024f8bb9bfe387c422362ce92af0e2e2de5ac1a84f75137cd7c66ea4052981ce51a173afa843b8e7dae610622f50c51756e567645d189fdf3a5fe0fafe1d2c56b11d531d62679f10c61df024814dfd95524789ccb24469ce1280760a9bd26fc0754a477b498fee0a81c53ff1cb26ecb5f33ddc867d0fd12a41c787a5c5919e0f25cbf221d67be6a12a4b05adf4dd869befa1ea453723a4dde1e3796f2aeb3418d33208b9e8923bbd6a925ae94504a98c48b7d6127a84cc4141a5161136bc54d0dc5069af5416d383a8d12783016990d706720707f85e8b4aa178a726328e9d5b003468caf3b8cce020dae4c7196cea937cd1170f3a4049db8906257e6808ea8e0cf16051474aa6d5db0df643121c991cbfa7c0291f5591b572f3e8fe36df0a72cb4b6d9e69db7f32a1b5fa21f30a99e7742c2ccb4a1c850e19cea7e11c394c178d5055d15c0b490cd15980b97082a1026438dfdf962f14b49edc5654f3ab2a972dbbd0125e324691b711cc37b9de1445ae644f02e4a618a70248b3232afbb332b58508b7068734f523b6cf7a49211e2be59f1cf3bb2ca8b77f891d76fd3fdd820ff9193a7a5bf067e39d55814119920b19a3a8fdf5434fcd901b8565257e60e404ee02617f3d3565bfcd90f0f16a3538d20c002a7ca78580f645b75514ea4bb5076de34a1a8e05657a89830f40db422bf921d38aa287489a3250d922ec8e620ab0414d48cafa09e28f54a24ba82ba30d37174ef53ab0249f3759cf608797cc14c1ff88be8ab0580b8639eb8fccbe8d4eb298cdd5c6bb4d6473024af389685110280d6b3a39d3395132b120482b223f1040bef91ba9a51f9fa287a49ba5a13fd591f6bd88d3743d68a7b9300a984e0790aba471a66ffec552c23ca2fa118d6a5b987910706cbdd69a486b7bef9da44c291e06fc063b06313186d8f7c3c418d42cc5c499c730f39eb731a9598a7df8b11e91490d8d09bc813071e63dec391613675ecff33c94f0610fbbd94c883d6ce6f5d82eb4338fc77e3e3fdf6d5e4e9d836a937312a15ce0d7796b42a75befe89cec797cd338f43c7e3d3c1ff2bc4b64eaacef1199d42cf57c90c8d43dbdc5d455624f55cf5550f3fc27edecdbce46a82f7fe622942acefc9bc6d7ff0c0afd7e2ace5c54caf23df84da38b09161969e8c811f5f36a967c9e3ea55f7f897ecd51a7599b4f4599a37f934e3427f4d3c9c70728eba7be8f8f9028b77ca65396a8a5c71f91871db25ece66f5f4fc4d6e053d8f413e7f9b4eb29757b336e98ea74e198c424bf5652293ecd52c057d159964a20cba62da216ae95148ac0a9fc7f06fb24d28cebc6f1a6515cff3c823ce3c51290b4379393983d223fd1ee9f37cd0579149cd6c845a79ceaf6d96ea07894c136ad6dae480ac07e5e5666dbe8fe5599b50558c21776380bd3883127bd8c744263533a5fa24f0584528de5698383ef85085c33139c9317c2cf561e26c89be12cfc77efe9134421ee7135c94d8628b34c418b05411479f8a33a5faf4617f9b57777a5ea278ac17b35ed4accdf7aceefc9b0775a78735e57856334bd2e64472f3363c6b722a7cc970b3a6356b73fe043cdea69607d4e452480bca44dff305439995eefa7759647fdf161b0c7b4e203ae1ad714ddabf67ffd30fdcb3b945ee98bbb9bbe7c82f94f6b21074a3adf0e94d2ef2e9799ed771e9ba07ddbdfb29eeaed5f99c1eceb1621bd778e6bc5c18267415f8caddf89c2012286bfce0d75b1d7716ecba06251465a2972ebf2392329162cfe3b96e0c24047ad51b912bff25b6d0f7babeeff55d650c14bfd7ab07cc049e87895866507a847d8f30d1a98e3cefe46316d86b01c76623dfc76c8f12b6e4238fa885069e11f672f45eeffa91d9c8f779ef1788e27e1df0142d6575326d3623231aedffc5535cd77b52dcaf0396a222287a9ec8ffe01b0aca2370ceb682f2211441d99c70b6d4bd7f27fe84e2d177bd7bcfe36ce9fbee3fb1136750bed777e26ca96302bffb9ec7506acddad6ace1a6d3cc9a35cb238fa0e5f1850b5fce3557de239226614888898a614191b34979141027e02c9146e825411b13fe98f902ce0e428408792e4fc1c9f2469d2ac8d00a3206196974e9fe66f7c7f31596a5e5237b240a595a0d4ac1acae7a66387966e5b1362734ab2bf975df045dcaf3bcc5988d62939a6058c3d8e7dff74d30ac611873a344886be8c8a51131024274a402b18542624262f2f2e87a8c6c7f934878a40271a344488c687e3c5fa50dbdfcbe3b12abc2fa0eecb67d31900868439a529425b3d6555859ddf7816f5b625465456330269ecb23ea9ec2604cbaeffbe6ac9d6d09cdf57d0814ab955e61dda615c6e4fbe073a254ad8dbec21ac6c21a86b1ef0b6b18c65c9fb4739c9ddd765fbbbb7bceecce53745dd775b3737767263289a6cfe95ee7eeecd2dd6b6766767707634176a89c6efb6e1d13cda75fdfc9cc36afbbdfbbe77ddff7cd2c14fa73b9ac9cd057adeefef96466ee84be3dcf33a27d9e47f43d337722d3917ddeed664fe8e3c3fc7d9e6ce3fbf9ece705d5cfab554be8d03c2be4791e1da5363e77f7aeebdc3bae1a92a9999e85c215d84c347b78320fbdea6ee544ea935851bcd9f9fc1f9d4fcff33c22666666cff33c8fdff3c0392de3d073cff33c9b91e7defff03acf3dac28d2ea63a2d9c373cf9bdecf6fafba572d71b7799eb7867d8ee7795ef53c4fc9ddf0bcce465d22129d7be7eedea921474ee75e97eb72eee5d49023a7f39c1a72e47877c7ebbcceddddbdf3ee4cfa72a7b6ad7aec2a3933cac509b5867b97f32ea7061b723af7ba5c97732fa7061b723acfa9c1861cefeef8634de66e29bdf33ed0f5a2151663201893ee8576b88e3aa52831295a563c55575655c5b2b4d0482c8915e4f323036b4ce94a46c124546df3925268e41ab906284868c7acc7078ba1c059ce63636267cd28a50965a5d34be2244e268331f15e36c4c34b7229f06f422e3f90ee8582e72098211e5d7dd14a5f3c916a1f4d79db73b7aa575de5790ece32b14e43d2e4c7608d85c5a4f301c164566214b22614d4cccd35726b4b6e493bb8de9a503cb69f62ed641b06eba86e2385a6d7f41a1e3c66104827336a424da85966464da8324ab3165454b7fcc5a5eda6bae53797f65475cb5e5cda54d52d63e0d2aea2bae52e2eedaaea96ad2e6d2baa5be6e2d2bea2bae52d2e6d2caa5bd6e2d2cea2bae52c2e6d2daa5bc6e2d2dea2bae52b2e6d2eacb8b4adaa5baebab4bba86eb98a4b1b03d52d535dda5e54b73c7569bfa96ed9cda5fd4575cb545cda6054b77c814b3b8cea96a7b8b4afaa5b96bab4c5a86e33b8b4c7a86e31b8b4c9a86e2fb8b4e154b74a2eed32aadb2497b619d56dd2a57d46756bc1a58d55ddda2eed0c54b74897361ad5edd1a5ad81ea16c9a59d46757be4d2de40755bc1a5cd81ea96824b5b8dead6c8a59d55ddc62eed35aa5bd8a5cd46c7e90ed4366ad835f937effa34d28fa113dbd857fe38f2a3714d5862608241022f5c89800b10b0f28016aa5071000b2ba830e5968294063060010a4800020e8082010a70020106608200a204000a00964a20610425114200e183271e74e08483264c36d060bc4b32c0e0022549922cb0211d2139520105468cbabbbbf316a28798b28d36ba8d968ab9bfd79d06f6647ef77d84950662281379defcbee979a02cc2b48e91684bb848ca2ff2dde9b74adfb93fb826ff06263523c9bc2542cf13c3252c9b4598d6305b9df0f6dd298e725ab594cdb2a54b9fddec9cbae1b9d7759f473f57d7d53bdfc117ece592b4a63d5c84f2e7cd8a48dd6354bee47c5a292c84d5a758aca7613c62b33b9f863c3ddf13fa589f9e1ff1c7072802a09f20a2202021219a50d08e1db51d42329904b21d43434386643c7810e131349b15cd78f4e831418f990f1f457cf480000223087cfcf861e40704ff14fc0f6b2bb02f8a47441b410448221089888e88220002040908519020b620408408b14048101a2d8926a4564b52a349208112096a43865c304402cfc31e17ea2194ef44ba6ebe5b90c890a20c8a883c4d2075e70445628a4c51640228a30b1815f912ca77235d47c59d6f04340a75a0c0df29303255010554472aa802c991aa232456201d5d6143c2c2025b164916689124690b2549b8b8408915061774910106185892811777c99bf17ea1c108c6061a84c16483ab264cc4e0a0c9184e3820a30327703ce8a08c271e98f1c1933340f8002b041032204208682889a0811194d22061840d94400207964a5003004b595000b04600a0b0112500710410458e0902e8c0004c6883000370e304026815e084370c5000385030c09c03a0400701078823010890430109f0c00214b0c58005ccd10006d491d2003a529062e796c207a6dcea50610adf5941051c0b2bc8e0001666a0e2001aaa50c9d242951a1ed082162b0fd802012b4e2e40c08608b870c395087079e14a1709bce0050609e0c004430e31306189e1cb9d4f9bb0e8806bda41061c981964e0818619c264a1414c0d59a0b4d4d0c3162d639cb690b1c1a9cc0d36f8c0e506335db8fce0a54b140e5e80c80187209e7210e2cbd3103a7c39b3830e4480d9010d0f6072617828424c1823a0c41cd103549a313da82133660265c820e1439935667c48e207331488faa1024044b1090208258408c2024308b1c49921da107186093444349143e34411b9278c28028a238c88224de50976e823604de6953db8b0af144770cb59344111232314547004c91192cd82a4244a2ec020832577d46003264d3870d281074f3e00210411944620a18425004009401401983000029c500003a07000042440010b604003a4a4709ba2c20a2c38804a95161e6005022e44e0ca0b12808129062c4d8d6b197a86a6a1b3740dada5b7b453dbd0373497eed25e1a87cea19ffa4bebd03b3498e6a1c3b49886ea1e7a4c93e932ed439be91f3aaa81e8205a881ea2cf34118da6735d441bd147749a58db400748e5f3db7e5df03ba5ff7bf01381aed755a8dc29ddbbc0fefe16865ba1fc39e9eb6fe1ed34d7fbee4b6cc1656f54ae07de1c67b3d96c369b4d4a29a59457fe7dd3a960a482d17c158c5430f2e20ca83be4f294196c5ccae35364bfdedfacb838658a3f089dc856faca96b31bec9fe24dca7ee3ae053333b333b3333333773a5871d74d766766eebae7ae73f7ced9d9b99b13092396396d8eff9c57d8b123478e159e2a030e381f04255127250fa919d58f35c5c645b174834428aba494a449299e73cea9a933da14c599a2443690352997b8a8c2284a92f644d82f9334d9a0d1cb967064514654196ba8c018e2f6b84cf585d4a53c332597f7f425fffb66dfd366cd639a3f452699d734be8d041ea9c87493f9df6cddc86307fe1229edecfb28b3e67dd3e8b2b34f6c81041e95442627de38e37f5d89b3251c55c3429b20fff54bf3ebf357f92f91a9aac55907dff77f4fc599d2eb674bae7fbdabc78631f95cbf444a291ceed266ee1903c5f073d92bb3e671d67cba220c2eb25c0f4a1e71185b9491483d4e0e430b2fcd9def69bdfc4a61dfd4afc6c5a57e98c8c4d474a52a8a94ad2b27597c05fb259e87f53ccf7be21253d087df34de786be61f7b1ea59db9381b413eeca1f407f7d6746e3fe801d9858aa83454fcd0c595623b95e924ae1cc2ef6f2bb6e02e1839a693cc4ef2b054124d029f3c37d9fd6e61d79bb027733a7d2394ba231df71ae7b3ace5f4cefbc029394059baba653d20112e92df31618532d1cda9cbf61dea896aa8eb346957234e727d4f100c4399ec1f8b6fe7248df62f9365792e97cb45eb64e2ca2a9db0b0b0b01a8b7e7d02c13094b9aa8bbe948934f4f2a97c1e6b2f264549ffc65b208c8a4d66ee36f5584fd3794a79c8befc59a59bef6f9c45c5f924aba4d4d02f99435686269c35d9535de22cf62c4536e2a409e34002512130e8a78707f6469c247b17dd41bbbf4929100c4399ec9f8a4de49589b3cdcb29ec2c4993bf43149508c127e992469c249b8986aae20a893723d7f5af9732834420d187b3c450bcc9c498787b176751795d4fad1117b9c4998b612eb1c9bc3723f7c65946f6d64d5cf9df7472dd1b67dd8c48ad6bc4d7ec90426d384141444e6611071c4f23953858a08ed448679d39b8913a9809e046da51255181a7917a5a4f1880a30b2a46262c3ed6185922c122d4441d4ae046ea22838e0dcc31d217951719a81a29f522aa0aa79156aa3590801a29ac8a306f9c461a0be3ccd59b3284c88d3484b2c269a43d555a5983971bda8cd4a74a2b6f0033d566a43f55daf6a2c50a6aa440555a2639068e54165b231316a6630c183a46352278995822c1c27696787a625433457653278bab910a0505350b0d6141868badb1a9b1b49c5274de2a7ffe94190265a2e99dc8d414b03343d588e5cb1a418944ca0b8437f082b3366b73fe1499769041c3184ea38bb44e62ea08ac91ce9601ea8ca7715a1e5b76179f349ff2146fde688db4e1ecf0343a9189026a6434b49863cdc85ea8c1658db4ab9de53034f084d548bdda5926b6620b17592396971458d3ca664b1dcf160b6c8d1dd8d9231ba6781afdfb0ec8a00b041f9c48af07c16f4e127611f9a078035d6e6f4d13c6840a757710743d88f4025dfcb2409dc4254e9d51c575f166b3048d3450c1e1264c8479b1dcef13dfd288e6cb3a21e850d0f1191599e01675443ca1cb433e095c354a8309e1711ba4c72d901fb7444111ec1087ecec7dfcf80181f511410f2033213c6a6e8786b89515b9dd51c4ad9091ea3688e5d1e74a96cc9a5ba0fb592512897c7aa4bafd71eb73ddf6b80def74cb93d434a9b36c01172555b754092f91ed16bc605a2592e6d6ddce25d52de0a20d24136fe2512d0e40f75707b5031813ff396b375b22bcd96c4be4b741a844d6a407b127e107defd810f08402108892053e2e1a30704ffc3462012050122a4469380c890a22213185160a40224478e6c481624495282c10519dc25e3061a30e1a089130f3a7802c207212889304209242c410140000410c504020ce0040314a08b1214aaad8b94038049c182ae262500cc6d0164a634a08c0af24b8187156a2b619ab48069720aacc9df166d6156a86efb884b59a86edb884b1d50dd76119752a96e3b776995eab6d15cda4275db445cfa80eab6cf5c6aa5baed212e854075db425cea4275db415c1a81eab681b8f44a75db5197be50ddf60f974aa0ba6d3397c250ddb60f973255b75de6d218aadb26732996eab6c75cda54dd760f9736aeba6da84b5b86eab6c55cda3354b71de6d2a6a1ba6d1e2eed2cd56d83b9b46ba86e7b874b5b4b75db3a5cda5baadbfe72693b55b7fd7469db50dd760e97f60dd56de3706973a96edbcba5dda5baed2e97b697eab6b95cda3854b77dc3a59d4375db365cda4fd56d3b5dda5faadbde7269eb50ddb6964b7b87eab66bb8b4c154b79de5d2e6a1ba6d1a2eed30d56dcf70698ba96e5b864b1baaba6ddca5dd4375cb772eed31d52dd7716993a96ef903977699ea96ed5cda3e54b74cc7a56da6bae53a97f60fd52dcf71694755b7bc75690351ddb2072eed20aa5b96e3d216a2bae5382eed21aa5ba673699fa96e79cea54d4475cb705cda68aa5b7ee3d2ce55b7ac75691751ddb21b97b611d52db771691f51dd72072eed34d52dcbb9b4d554b71ce7d29e4075cb6c5cda4854b7bcc6a5bda6bae5ac4b3b89ea96d5b8b42950dd32072eed0a54b7bc814b9b4d75cb695cda4a54b7ac814bdb02d52da371692f51dd72062eed36d52d635dda4c54b77cc6a5dd4475cb665cda4e54b75cc6a5fd4475cb702e6d28aa5b26e3d28ea2bae5312e6d29aa5b16e3d296aa6ef9ead29ea2bae5302eed0b54b70cc6a51d51964a18810a19618a4c0385301207ccc1085c94c025acac586a49c90acb8c0fe8a787ddfaf77137fbfddc7d7252c7493ccfef7152ecf93f4e82d5245a39097c7e588c87cebff99ddd9dde9ddf9dae3befbcd13b937c7ebe9c9c14fefc0ae3a4eee7f78909bfefc702f9d820a11db2211eb31e3e20f8c145f6878be48716c8060971917c98ddc145f2ab95d9211e5c24df6567b6870f2e82808b7e70513847e8a325046f70ef7c6e3e746df5d33365403f3d56640f40a1191158055cffd9e38621c82008be3b7844d63e6ce3e9b072b281067707ef6024215c936f346b42180909c968225da6f20682408420d8b91e04c5255cc27ea3b9c112ab1c6067c8807e7abeef6fb0ef6f3c975fc9f79c844e9077f0e922f2db0235e9a16fc810280c7f5ad18822b04cb907082491be9f3d74a875228f1c4ff4ef867bcfa7dff7cdceeb3a6f4eb71fd0f7c942300462d9114a248df6830c30743a5dd7d3764f2783a083a1ac7560083e4f36e19ca1ec3c91e6855606e8b5e7303dd91e967a267c61044366c92c999925b364e6efe9f1f1f9f9010262212166e6f7616666c92c9965b62b0b7213e4e60aefce095f96b649abc08856011f3d61da74226936224fe4d195bec2e16433f7a4311294ae4d6f9c6d2d1c15a564b9137410fcc0cfddfd7ae715d40ae378c3f7e5ba9e573d8f525707e5d2939dfce4d7e5acec48a4be0265cf798393389136f4ec2b27b0cec2ae14abb4b4aa566b85a083134b6ba2f181adf9665e4d2bf06fd3ca65acca772b086a69b95c1de875567ee12227a943c3d045dac0a38cc2418eb9f3a5d60eac500d23358e6bf3d34469219bbaa184e2ee88229c5ef0d1f7f367183c31506792c1488d9bb54e72a58b4c99a881099b6df9ba6ed7755d57ab7ce224d95ab62f5b29054b279836ff4a27e9ac2e327f5291c4c69dbce5e562d157f24a369cc6126d17e1f7b7bee24ec6401cce09c411e1fe65681cae03c12f8cf5f490c964412ebfd86f8f6060da1ce2a32b9236e9874a12063e4ac2b4f936499b4fc5bebaca5aea22f33bf1939fccc9237927903b5f5ec934241288c5664a4581bbb2ea4e09e7ce29dddcf99fd7754f9ba58c755d8fd2446aad247cd44eb491b469158b6959595ddd97e5d13949def93fd2ba6e8f9ab07b29a5c4698562ccb0e6b66d20b8288876828fca740f4c9bdf4e5dc79d6dc4937438a14c82651a4e5ac349f226739cd473e7576935919eb8487e895ae2f670e7d49a35d87d59394eaab0b55aab2b623271678e8be6b3010317c544ea37e600e74f2d31c29bcce5e60cf3ebb84eebce032ef4c8c3febeaf6777cb29a79c5e85334ff7e979288fe6f7ed5726692e0508ca99c691c031c8e5a90e7820ce9bb0839801e68d335371d87ce1431d5f48b065482e6808301b000347ca0d38fe812b26cd89ef14bc9812238e1c3b5e9cb1f3459d26a82b78b89ac30cac2b92703a6850307903079c115b561c78c2ebd47e38a18a34e83c3103163f40f91cb20f813a1abaaeebba2ba400e38501e99b31a8cc5c5dd7652a33767290800b194d703953070b0d0c31856f8121c7240386902cd8042142881c5f1c088c1a0f12c82185a03d5138b2b429734689ab37ee8120564c2068524b1a566feea06163ab8c91d11b2a36535084e0652a3650c83ca1cb546cd0b02923ed0ce94172d18137f241f9619b3754312e6f5c5bc891f2b6bc19837e3d2e538959e2d2cb5462d208d1c1853b45a46933831923d6c861f485c75185c5962f6e8ce12285865167bbec4ebb42cb04be2d5f842133a9c4784165460dd7130d5da6d222cc7d5da6d202897b7ba32e0ca3967d6c847e792a8e5518278b2f0edbcae16a035dd6b0628d71d28983c5957d5371a6be08c3e9727dd3f5bee9d20f7ceec1e3846f078931263227c021ecdb5dec56e13bb7fb5825e2c19e4c97074d2d806fe5bb9ee73df1264d37d9c47bffe941d3ad12abdd7f6114c19dc738893b30dbdd93bedb2332a4e63fad4c3e99dfc4e7ac83babcce05ba3b0cfcacf5afeb5ca0f884ef5cff2a3ca5bb4feb60cba9efbf8f69a44d63bf27ce963cef9bc6177fb5209bf0065e998f2542975113e1105c1c2da5a858d162bab245e3d0927ac24a85b3a54fc9c927ce48e0d1d634468d4c3d766ed6feb6992e428544514d59dd7e6e28cf93603a2bd75abdb50667e6de9a4e63cdb937994576612c293d89bcac89227a0a2594952c2325a3726fa454d59a36b989c4e3d8df663a89ac52a68bf4f3982ed233694796813374fb659544aa61f5d41c3797cdcdb1e6c471a9141d32edb3c7749996c2658143ceed3702967ca8040c1d28a95388460000002011400083160000200c0a070422911c04e248177d0714000f65783a64523215082391381848510c84530c432110c22086102619320ca2191b5000fe119b2f79ec20e98583a718e11691fa39be5f84663d1e7d5568c5c2e90c46c61fc91f537bfc407132d6e8571cb37ea078d81a85a8fe08d532b182b5998d69013d9c71789771c5122b9c17527f21b526f5b4e6b465ac4ddf395a537c3198b9296b86b57126b6c05b3df56f9fd74c84817f9900714e928259802658f5a4ff6f9541ad2b84f154e13037523a695e9f2754a5deec90a13f335a901a3be75bd0f54a724f810c69536d4d04212658b9171312f131e35024d4432e96ee8ad01704a843c4b94703ab55e0588fa1e8a38756627c2b9605bb7c5a310961b7cc6c6e78494891b7c06226f1df093469c07ccec0b16e634ce30f42f44c659e1ddcde70e4e3946f8b495d6ca37752f85236b222e838da1db0239b2390acde82ba8f431446d84acb558c7d2889d50fa64209075818a604537fd45d2ebd4fb748845fe54049309847ada679d79a6003ddfe933419ef93dae1aa41667381343252e3fb92ce76e1491e6b7bacc8b7393f999f6259c70412ffe03d4f3515225a586a51d6feedcf6c36189bee722c5839181998b5a1102b6e411e603f7f0e7af2cdada2827c62e90268448bf44887012665473272323d353ad65e207044bff3b76984da6a5584a625ec990dad613f477212189e01be8c37d06c80662b6d2ae4e82c990bd296c850da2f6ebd3ee6b3229660410843389ce78222091d201fcbce0ae0a4e77d2e330c15cbf9babef1e4c62485c2818e097c08ae0b90b40c5a02d552c262f557b55b37ad1f6289b77dcc81ec3d8c4dabfe0870de6b9757b0a7cf1ad492d8001ea9ce71494afe960758a8ec0a4d180334b21db9e049b1be4dac63e01bac7ad1c96e2154ca89d4d63d0775dad7ea88266ddd9921ff03235860c870cf35dc2553f666e9f5a1a359c544021f178fb55e4fb2af9a357fb5049bfd32d771df33cf334f0fed50f2e765404037058271620e6312754c01d375a736cb9e2d193ea3fb8e1776a82a5f2f9bc248828593022cb73661060810d2b1ed123fe001af3340a59303ba729ce3c9ca6ae97c74964e9d87fbcdeedaf805999ba5a38d6a3507d9a0d81f747e7a67b1cb4309e6bd7709f4b39176940d24ef7bb9c43cc8c240fcab1be31190c652caecbd84b3ef5f8c071c7f4338422e878481383f05dc987019d0232ac0856628df9a39c756dfa50b696367b4da19a9c14636c2d64af06ef86828383b3810c10601af19e6e9e9a52e632edd6502a897339fe73e4ff7f794598e517d9a8980d3bbf67959b26054563f2c0a90fd2e6caba6eeac97811db36c4df002c306c30c50868753b90adf445a62d87c71b5df90056e6506f3fa22b9642237c7920c04f2ad66a1dd70e87efc0e440b2b37748938e61686ebcf81f62ec716c6a449420785d09109fcab8211d1932523a47fb19207ff60e606f1f0ab329a71b13a23da45c5472f9a860dc1320e71daa154cae3e1ed0c26f0cb99c080e91e746fa3f99c4781d35c713a4b8270a16b712e3424e1e4ac969caf2951c96bac26d3189f5db3db9b95d41d4b0d9c465954e5a5a369035256e239b3153a57238a030dd19885b3501f60d45fa02b4cf21e2c4079808dbb9515fd47f4894987c633d1ad70c06862f42decc13071533f2c63a1c685854cfd38493f6bc9e5349b871eedb7e815f5e1520a3f31f6edce2280bc18f5922a8a170b152a5624664c55f1bd2a8c718590d154b03d03371f2f34837e1a926e954fb68cdeddc55ad52aea3d67062c13655f44d7978a1483347967030a30163b86a010ec9a4da939b3b4cf66d5ccd293de65caf3be3f1e2343a37fe7fdf05125815cac8b55ce2a431633010d481c5df059d2be3de0718ec276d215b6321612428f87c7c4617cf6b56222519974dfbee97734e52b1a3890401833e45f2f9fe06046cf8b81ddf2be892545fb65119d7887e95a257dc4ea2640d781c59ffa767c1e10be6fe4da8f33798c84612ffcfe3548d501d4bf576bbdefdbf43fe1f77dd4c718c8f755b895c6365ed1dbad8381a20db3ce8325f4787dae2bd8298b08eeeb50c4b25ad4e44e2d97f771f81680b6ff3c468f26eb627d0fba81bf34dff129a0facfccf1b71d8bf35acc5a75775c4a98692999208fb8f27e6ac4337016a171b90f9466e4f8e9ff72e6f9e3907eb2f29c5b817f8c01a5e11bfd238ffda9aaf282e86f70e87f6e79fb036d285b5403785e019e3e9f019ece2a6c0e59f8bb2016025be3ee1e29940560bead25b3b381d3919cdd91103a32f03a6a3546e8bcd9115f960cbfe89ac14a099df3f3e7f5f3a005f4808a3d26d2c1cdb5f0fe3a6122687a55a1293b2d72b80ba563dd0b9bbd481d32572cff070b09c5adb6e4aa242db23c49688e19cf0c895b090a37c160276410ed5723ad5afa835b9f3d1eb8a3df8691871fe4795b9968dbbb4decd698d8726e398ddb84634e415354c3b88d2d3459d23bf03de538685e1807535aaf341b2fb70919a90adfd36d82e1a88a089da0996d824e814511a080054bb6b2d4ac78215e90654433bbb1b1fe4ee9c2064ade943968e0d4cb630296560a2c4fca83eab3560828230277eac1d61224ac55eee676f298149e6772b53266bf0d6b47359bd004df6a2c534fccafec66bb18a65988d6ac84f566e2a40121162c11310c0cdfdd645f69d3c7701947a82116e12a8a94ffb363bf0e9b30fef63c3acdd4615e567fc8c6939fd8e3950f0fcb341080306317c8d97d6a716da352a5de4ff3794eaac4a0343a42fd5db26c349bcd80bd0124260d57952ddad545980fea828cc28f0265267960d8f89ae3bcde8151e1812e8c5510a2c4fd21aecd6e6dd6234482f83a5c1c0cd7ba0f6d7a506b8a4ce3a609b4628c9e8a2383943995df99fef146ce42a104db1061e4677dcda006328ee7bc2771d29ece5f8971c56300fdf1d3242868c845974b551ff2151a8cb280f0cc2075b8dc2d8a5806c370d115a20a9c546f3e9d998b7c90fe3306f558c6454d207ae51b0a514e579f05553c259a83217f068c3997434c6a307195a4ec17f5dcf04f137ce10297df7114f4bfbea3baf0b8f25042b84670a94d90bce6dafec1318c481d7d25e4b155181af2aab2b8ffb1f4551544d5fa5e16c8141d30cb5fb5a9dd00fab5f9bb37bc658009b62bdc7b0bfaf34b4a4e8fe239ead0968fab92a73050ab64d521b0e0a4656ec944bb35138a1a3e33f369f70980feb26c69a124c407824ebc035965d114ef6258d81175d3ba17756db5d69e5a0b427f039c7640f8541b8e12123d012837a8ad7e6955bc2bc56bc5826a4538bdbeb66114e104cdb134b3e0e15d7f58978b85f5c5d556515b54b7f52c67b338e3ae502e93c3257c0babe0b1ddd8e2bf86fe995403c4d8de3a876b92a8821afe47f812138cf3252ec9be34b3e0a6e07f48f6d1b881316c97b81dab215dacac75b80e40552c586dd1f2ef997adccf62932415d645de4ae27ee25beddad8a34e707de5948c2d559fd4b974a0644da4b64b54da45f6288aac52d9704c38cd364e5a46a25252cba012a1c8c4fae605165e241df31d11be60f7f0d71e3c981f2298dde1ae60495fb5b2165a7580d8f3b32f35a587e4034709081a38ce88be45f34b31dd85232b2b3059b10b22f9c2c50468882d0ae025615cc473d4a14a61076380000701b35e8285fa602dea80850502bef6ae6535645d258bae7d3e269941d8126a813bd24bac76d018d041a6b7258413a49f6b8a6c3611da3c7547a575a2825942539373cf188d96a5e0852e9db4acf01b52c2c29ced8f9f71bd360416c4fdfa9d4b3c152d0fb220e2b12f1bc376a197a4093fd558d2adc3e839fd6050f637276a83d7758df9a3b784cf2492ed6b2eea56d769ecf287db1c2e9a0f453c65c746985501fd6ce3498275c2bd57d2aa3ed12517af954593c6169b794c812c29515866aa7465b4569bdf04a80da439d6e9f6ba7a21edaf63a7130b0474ad4a822d850f8d1553549038f049aec7ad9245b456e8abf8c9eed2c716794a016348594e0fd48f3bbfcfe84bf82f65e1503cecb947d8cc1654278ffa7237b67d4aa97853d04bc9a736629e693d794f6e43711d02220c05c2dcd5fafa8640181540f2f49453ce9419b8dee6cfa512466834ee9e7bb937d3102a504ea62803561aa2084f4ce4ce52010c1a6ccca3b54f5015773f530ea1537f5060370584242c1d845e41a20c709e905e87bde3b6935e3b32c46a951ae533ec7a01a1e2873ab8550d5801471ed35795dced4aaa8c1c312a83251d4a84f276d6a94b9aae182e0b7ea72b184eddd286488663154d12e1697b806e8b8a90ea150c392164183e32afc05733ed8292420c87173ce394774ecd0b032405bdb7abe30529106f868e3a146836661cf7615241912fd2b9f5bfa5c1852cb3aaee8b87023f1555d2832d6db526a460982282923be9d598603f25cf6acb03a71b0d9387f1d655a223b3c47261a677d1808fff37cfb8ff39e7734d73ac7a073a90060cf4447920822c10f4e11f196de8277df9d4e20e761508747cf8d63e62461a431135d9fc4f2770f54674b64a7b003c8bab13812d367173c5d96d8395aab241f10efa96109f9d3cf674dc3474556c461dac57f00cdce6678bc99d842ceea7fb5dc89a35e9a0570ff5d9c91d7b64213f6dc672c1656100a7b3223516add60839287c3e6946b40535ae799aaf0df1a5d88dd611a8e4f5684458be52b6e15917d98e8cd1eccc47ba0cba9f1972d348dfb07c5fe0646e88685b68868bfbf3dc36b261c560ff497e5ddc71ba757a1d060dd0d3e9d8b3c9bbfbf354c460cb8c33d1571b47f97c1de8a959e53f3410a8836d7de7b6d3ddc6aee4d57c1df53fac4ba00de3bbd88ebdd9909f36636db9ec74b7f90abb9935e7296dc51834185b663a0cedf69ad03d0540a951c639bff9f224b25f876a4cb6a7964acab129a6853a2b54eb72897925b5844f589a897fafa40238d3708ab591f467fa2b952bc0e56f31b07efa71851e80e2b47ba2ad49a63265faf344b014756878b8f5fc3c91c0a09954687a2d7896c4eef4bd18951a43253d6cd1dc3ddc8a0c764afa8b65a00087fe415b70926a3a995b113ade89b8a59aaa42fbab28302538926a648caaafff58e306434092d04c233c7ebe0e789a0f1d6d9eae46dd8a6325f383d36e48ef4a6131a4fa204ccefaab8b87d7bb1c91ea08ae86229f364a39b68a58729e0ff2d352393412a547b192f4a14cb6668ae8f380e12d19325182a15add83bab2099e09f3e2dd5275f26c8bda0baa5a28b81721463728ef7cf2c90101d540b12c276948413f0474905d5e7206a8215c8d8e559865a082bbadf0d2bb8500fcba31f8d5139548bff03045de92c42236892de493e813caccb82980b031dfde3ae48ef6bc49c5c6332468f4b2105689c55132e645c9c88ea186d809a54b5742d11c32ae7fc6a0d3038ed887ab7be650817ca585c100af89aa50e25fdd3b7765e7cdde70ff32ed3c32330504e7cf6f983f369018d24fe3f7013cef17c845e5d8dacfa9ba812a72b5d9a0fbe3d9a5d47bcbf67b43d2065587e33508108bd4c53a00046b63ba709e6ecaa32afe5b6c87e03aec259f716a45406bfe56adc8f5e329f1a4b80701c4e9f6ac2a881d6dc126891634a0b77ddc18f214b347d6c692916b5dfbc8414e7c64399628e65162618b1e88169e013c8815d6ba21f9790854513cbe568f00ae14ec9bfa2442ceca8c6ad5c8824ace7f569bdcf5f2abe63745ab65149bc3fbe6c0b5785c116ef208ef017d02f5aff1636cecaa05e09e5bb522e0dbe0d1ed924dcbd121262bc9ad2a6a8748165ba15c542cbea0d5fe81d826c4d536879d1f5fe8584108012736c235bdf7927a704d5bb62d9d947a81058701ae58433890a51cf8e217fc312e5b169ded94efe6b6481aae83cd820cba68c406d27f90f8891cf5c13594748a56c67e26d5fde3d11f37910e98890daa9d4306f84d6c0e983429a0a2f5819c677b2e65b0c472a7778341b25f7f42443d38e0736f00be78c809c5e7c8ef3de915ad25268be88e178ae67bb5da915005260b9c24daaa28b84b517ca3a55d0cba7e0b3c546451c25141474f0aecfc3d10c0b37087c20f050ef77ea44268241aefcbbed82ed3d95871d01df3ac4ac58122415b720ca3115305c31c708ee8df707fa8422a20e721eb102814b232586ed9b606b307e9beb259e7f1aee62038e737fe2b99697c188aa42a7620bbe2fb6c04501a51b233698b5e5f515f191774ec0e9c028ccf5562ee5c4a4fa0f64fa85c186e9867beadd179e8124f63f7b85212459b236622de6454180ac475b59109057721a0826b8d51adaf8866fc028aff5616480418e4ed45694462b7eb5e963666ca921b3e5418354a3bd6ea488282b83b5826c9bdd8e2848176d789afd4f7e62b9fd17e2375203e3fd26366b70fca43bacac9b7d0560c57d5fb5fa7314162be7fb4ec70386974d8e037d22f7fa70e2a7df0b885d1e9a9233508e3012c0d230dae0d2baceaa3be857f4576969d40e57daf6240f54d9f37bdbab66bed2300e6574b711c8b703c6856961c039da44a855cddd5c8e8a7bffc5deddf1c7523400ecca6bfb749aa214f524530011cc91e4422f1e8584c75967d9ef5b8f416ef16af03942ada14299c3d4d70a59bcb40b460a92ad0b239513e7d48f4ebd974a4e5479d1eeb4178d41ee417c1d011242c4e17eacd8249955a4aa9f7540bc5b8e430fa8d8974bd850cd48171e1f5024996bebff4f338f0a024d13262ab3fe56807bfa9d4d5979a5f0b42ab3b04d4f19c474e80cf1fb2e8d9e612723babb57e66d6a326a0db6a6e18b48351d6fa394a71ca174aab190c94ae5d589f04c8f2d9a12b18f53150f2899e89ccb27ff91ddce3345afded292707144e392b581e4849ed43bfc051babf6463d175bc994434edaf9c6789fb9850028b8d74f431a033c8e6763268b4027f1c9108f6f218414ae1e1b1b762841fb748891b8adb421803f11c2bdb12c6a0d42e5d5de84396f6cf6d407092c3bf5c406b1bc1cecd31dd990da0f075eca171d3f0ada99d16145efc1e7da565301de1ed10a915d77e2ac5efe45430a240046b0145f58b75ccb4d98960109ad9263cfbdb598f08694c1011ac47e0fe1290b203faef9bab3fe1f9f82f6461ae1304f4bdaf6e6f949130f250fa0123b910dee0e62d0d3e0dc91a2cbae33d0efcf4c053d16d1233bc2091764ca7b85f29e8e14d2747d8564829f99310d28009c8836e39e3af0ce757ce1bf85c7238f822f29137dbcc37d3f0342207516ba79c607eb919e97a013257772e171eb84da9212a364dd1bf54d7d4e106c0c0be94eed395d5aa682a1f8b8b3421c6b59bfbf11b8bc8536d05bd80da44fd002548db3d605608e0bd664241cf19744d676dc5125effb00810ef332c47d0cb9484fd46715e40f9eb6817686a3084a3a40070ef204b109a11b326294815931e4c86c9ffaf6d0a314788d92c93b3bfca1a1e1733270a194cd274a336678e8833e0bc51b77e792cf4c0e9dc5cf643f2f5d03a93b9dc4025ef80f9443763b77f8ca09c5c5d2380f3f05d29c8c0df2d5a6e49ca33613865edc8527eaed125f0af8cd92d5c0556385ee6ee189796afb64d8190dc57d4f4b8b5f93dfe4d7c07231967daf15e21827c85cac6a977058356ecce0d1cd92f60b0700bac30df9b560702215826dd6d86f07270bdccfd475a912f6e0ec02a5935dfefb1138efd8a745008b223b1886fc8015d5718d48b860db05955b1074d92bafe5823d51344650853d86f4ff773c46aab645fae90714743de5361d050e21d11f9c4a5509ef60eabb162a204d1fb1243bfabd86b7ea3a1da566e56ae370b83acbed768aed29b3b18a45d1a87e23fcf0c550dbd663f581f34b0fd8c8c6e296c0ead83186c62840143cfb76bf7d3f2a70b7cf35b61311760ca9bd6311befacd0281ea17d8a0a6aedccf4d9255c4f529685987d37918fb2e5504d174c4ab5ed72a75d7e4a2d60677214b58f653f960525addcd7a4eb9603793e4305f5cbf7bf91e8b57e498a70834cfde331112142f6e9e51101fae817b11ae2584a8f8aa06213fb39b55027257e7c801c87fee64ba66701d1cef47e7840ece7d085265252be04958ecefe3dce6e3c29c0147d778536f985b43f85f163575d5e2b783386b5e0aea373ad7d5e83c5202e9562ef0a4de9107aa50f164eacba26e784292ea16198d270a14ed60506af0566f77b3cbb48a4f75689ec8861823376d0f7873af70016988a536dec84c6533fa55ef49e277c01b5982332113856c41fb8cba8b2817ea879a3d2f34aafcf73255dcac858771cd324fe60458d3e60f29dccdac5c9a0753a2f7a60238f6bd422853cb438690c929191a91a85529bf1992bf750c630517988ef7400ab85d6d35119be8259496369008aaa0a8af84db509b1c38abdc6d813bfba97c3f62237dc27d85030c719cce17a9fe97eea3e0a3824c159450c19dab45b7a648ebafc68328c6ac55149e703da4d0bad530f4ba28ce731296b5182553000642a9bac31709a7e39f3580e94fecafee734d05d04e2982173771a08f99ddd85506839e176ab380135759333bd4fdbb9ecccfcb4a15d5aca3ded4ee24cc6d3362e39ff3a6d688796644ebb9238a3e0b48dcbceb04d1bda2d2366d3964d99b4f4e4ed42e0554a73bb584008c9ec0a8610080903287e10718edbe0c30e7e045be3af47a372784dfba8f18ac8f5ab2491b254386530108451d5902513953d47bdc9bbed179cc8e0f0b93fef2a6ff01183a8691e15096efba1fd99295c12ef159145b4420cad3f90943d4b5aa76a425496a63f05041667c8d9a59e89d50b24ccd53d85d4c6fbc03fe4575584f00aef5b9ca2362312ef0a3858e5a32d9d23bc5971596cdf3864e1c5fcd43d2fdff24eb2b3b63fe861cc75f2240de001c5ca56e1321ca4e467f3c2197100ab2592dcdb213e71cb2a0d177842ff98c70ad309d398573abf47994866c8007b79d2fbc1dd24da88b7d24ce411dc6a516bf169c8103d6429ebe638fd4cd24905bae87a8c3d743dc87d753ec43e5d1e635f1d1f739faec7b04ff783b85fc763eca1e321f7aafb31f6e9f210fb75fb98fb743d847dba1fe2de3a1e631f1d8fb9a7ae8fb14797c7d8afeb63eea1e321ecd1f918f7eab21f232b13018c0ff4d0eb1bd5488fd614f64951d4babec83d3f222295dd63765f9518d14ec01105716d40ace3659687e27f6b6aea84a772aac0727bec47100faecf492ea65d926ed5d5d34ed6a6db3c7d1035a16591f08f09cedf0abad4fcaf5086b546781f4dbd3278d4fcde8f531596b3b992efc5a7113a7369a14bdb26a8f1080b1e13a7e803776389eb96edb7a8d40177bc3be0f28bf2a64bb690ae190fba85b10021f482bfbb1c7d1e955c6cd6ee17caeb2e8b397c6e45c76cf8528db8f74cbde4d551cb164c9c1eba3d480f7058fc597998bdcc36f5b5f1f04a179d2a70f1edae5dcf3dd3bea8c0868f66d3e7be57de0ac46f77715ef7cd07d4c5a3c988b398862534689d06408389a270b6369a66a5378745b9f2aa86fcb9c6db3b3eed580dc6552f875da3ed11740af332fffbe3fe7098a4f219cb45c93561d1fea806100b8dcc67ba65413e3b905b9899ffa7e3eb495d46ce782831787da586aa8c3b5cc5bf58b77ddff7d039818060df2198e9858cd961199b31124b978dcf4f11589bf4a3888301a14e342ca4bbd5e6b89611b5c353753fc7ae0c413d416477c31805bc64138fb9247c8502644c0224b99c80fdea37828e1253b892658b9591fe010fed1f42e8a28b7ac14301b45b7d7e246cf046cd92c6084827360b411402a22547a78ea63a80962b2fc60c69c3391d7c0b2429b534e48206b9f2581d805544da231ba2a4d9f702a23627c70210f08e0af8dc9214ae9063c5afa96249cd14f94fcbe6325049f256da42e24c9220fb7be600219ee5c06c7ee57f59463b657e9bd50cb1cfdab457ce9eb3cc12995a887d5980bc65c1ac51f99810909ce80ae88e04da59d6476b8a644bab221aa502734575203b12c05e791f6d33322dac0ac1140b981b5581ec8800fbe5f5e00dc869655544575a682e7405304702ed2deb83352045ab7513ad7201b9d04ba01d0b69b7bc8ed600292dd795e80a85e48056817e50a8bdf23a9a2624b6b86a82291422075a07da4321f6cacac19a23b3e5ba88a6a490dc2835d0e3802d6459650171444955fa3a7d1615d18eac89c0f4447185740ade65d4e17a8c4c2ff015ca101846e8a6819660be959e65af6038f59e7f1b01ac2ce956fdd8ea06471127a6a88b80edcdda7bbef0027f71ebb0925024c4dbaad36cba3b58df4f1dcb06cb06c2b15063d00a0e8403cb450380767aebe024c6ab4735241ed1e28059b191ef07077961cc4c64c9f79389b8103023b0e6fbcb212b08981735f3fd71909503990bac5c7e58c49623662246fe7f7964a50033b131f71f0e9942cca8a895eb0f87bc34664860c8f5cb455c123122b273fde5212b88988bdaf97e701095838c05a67cfe78648b11732256fe9f3cb2420823b135f71f839452c8acd8caf78781b830642432e4f5cf445e0a1811b373fde79095440c8b9af97e18888aa3cc45562ebf49b42937212762e4ff9747560a30131b73ffe19029c48c8a5ab9fe70c84b638604865cbf5cc4251123223bd75f1eb28288f9132767dd46b245882d72780e6efd77aad265eedb4b84e8dda55723b69d2280c62ef2fab0e90942f5fa2557039b7e24a8c62d71f158ec2d0274dcd2ab8f854e2040eb2ebc7258e83501b5de920b876d6f13a2e12cb89e58761221747ec9cbc34e4f11aae9975c2c58f42b813ab384abc566bf08d131165e5c2c7a81107a73e9d560a1af09a8774bae1c167b35b1349fe414cff74d81cc5ed8c9b94cd2f00a3a4efecc826e73f4b7dcd74007c30dc37521bb78fc429fa85e2f633221b82062d220578d080a74e9963b505627145cd330a94ac03ae50f81a5bb055c3cc7f0b5c399ad7d31c3518ea81363acd0ed1e6b43403afc3caae17c382a1f4ea7127f8c7c2a6de48e7f54e9e96341feebf8a60ea3573054867aa4f0838be382b17bbaa49258a856e19b386677426b161529f0c858dd04997d417a6d471caf11dc094e0e72d8f18dc4f5e67a3449ecfbf89cc04939b7397ae1565534992f018fa675bacaa3d3d2102954e6eeee8a6f6b21f9a4738aee0c796ce9bf98727e73ffc772758621db8d34bc8db42bbda0567ace869a44dc142b048c7ee9a8bb6834880fc80796165d6822176e2acc124224701275b491160458d4917ba6ff1137ef0cab2a2bc6d013b84e7e1e29c3163c961da8921edd5784d84a0f9efa0432db155379f1266fbadb50cb7e0e083f878aed2f9b14cc17c782585087ad4d33457711e1729f6084f1887d42fd4644d6a7ec756a363654d63c8ecd94676ce0e4bd1067169fa6fab643d27d224cdef44a908f2715c3ebbe35adc470a532f021433382ccf21f2968b384f46825ef81aaa80c2d3339b5efbf6b44ccb07a596eb14670e09adfe230569e9b5a8d16b1b8357d966c8ca6dfdf7baaab977e4397553c99d4c5650c0c6f1ba113e1723ec0853073d33113d9e97cae7e9b6e11d093bf854f3f6391702bb13ed261bd4bb968eb0a9b736cfc69b186dd11f2d60d349226e25112ec0d278f5bdcebfedc39492420eb69c221d67ae5c5bc2ec8de209df94549534b745f9531d56441e1926e3709cf2e87dd61356f6a6027539e119df563d19f63a28ba7af9e9ec79e3ecda97d00bb694adaa66a9f55ec31705632f84ca7c7d07dc6075e7754df867dafb4e8b095372e3c6db295cab859bda9df10201f79f6120107b8adc98be26969ffeb21611dfd7e7794a884ba5c9fc7fa3c75734a6104cfb910fe75c748e1fb1e59569a5267d9e96f054bb3cbab20b38882825bde1df1f4f62d6f61710444c18716487014c517a058f70fefe79e0be48034f5524de6989b7d50b7b65457bde92ff58026f60c18a9cd8c3a11390f64eaca5db8f721d5e137403cd7adb664bf5833941d4e73023ec694802400d29c5603c065d92ba5176ec2eceeca28c5c4265f84007eac27c14eb80a1d4acf4bca5e6cb240b16510cec5a468e64b4f7b7cbca46b7656195eb83658d7b25f7f179e6122c3fcf88ded36270086c9e67c47caf7c66c96e84799d51f25747716da79bb600eef7539d6c8fc9bcf4ea7c8d40c7a06b40d20390144f8f8f54b65f1306b07341e2b935b88592092318267b14509eebb573b5bb439a0cff63a25feae606a0096c4761b67a0cc28dc5d77043b944e5aa279045c217a39c17f93e0b6eb2481ca89c2e5c2b9794690fe9a8e821ed412200eb7684de10e8fc44de51706fdfd8a75b326ba1b32c53e083709700719baf45b5f30a653bfb002002cf8b21310db1773bc429442bee053a8a2dcf438768002021315fe01d43c7b50f26d4ada50a2e5d066ba12e27712f076df2f15685955586f194189b8c005c5a22e0eaf5a114677c1052c12b9c9d752666d3b99418d5ed2972214a32eb4991308974b6e11085db7873389a8598a6bb5d72bd447fee13dceeaa09a8a96a51326abb4d9e9c608217e9e9b8e2c6915c2445f4df18f6b43452faa6b3d77596e34caa3cf92ae04fbfc0227c91e9c087611612e5541b93943b1d3cac13819d66aa9b0130b8a7c55b2bced8f63134cb6076c2443a918f5dbd52f16b1958410250f56b4a0639b2e7ff484ebbf8f41d76058c20f76d70a1469d3165c51da10067bcf77c485ced737cb06d4f94d8eac408d2235c2ad2cdd3f4526fb7cf44d6a0090ed7bfca0319af4b2144ba13806a764a4e1fe907bb8193a4b4e1cfb13e0c5b85013683ddc08bac6748345c19d04a1d515e11d55e39d4b6f0b79e6077f73823bd2a506e076b73a60728409d622d6b775db016fbfb6fbe56ea58e550a825323d8b552dce3e70657add147d15b1fd00159a2967242fefa37d8022d17a0112e373a3201fc1dd32cca4edd3dd9dd8eb20037d12545596dc1818bbf09b676abcc273ca9b8a08d7761a8515add6582f9c7249f8a3145a461ec83d8ea4aec5adbb7035d8e66e8e619e20b379d7ae071d4838234b7362f981f389dc18afb8299f4ccd6b4c5f308149f2fcd89e888f64d6bd44adad62f3c7a5eac3ace4a7521248976edcb06a44083741105aff0f7c2a6fa71f3dea9287372a81f17e29991e3bfde777668a19ec16648c4fb21c21853aca3c045f3ede91acf102aaabcfff8c6a8ad62c1e612f9e741c102f8ebf1c02ad66fc5af60902877f279094e312954adc70c1ef7d12b855c2333d14c68732ab73a8c93e00c94565b1e755ccf4113c88e8d47c432f6006c326d8359a3093b6ebeb9512414233e72f400a1934174c2d82b1cbb4df7cc3315e84a12b50de71b60605ba6ff780859cd2fe063eb05afcb6e383537566752976787a58db6be0eb88d1884912e51950740c1bdc948cdc01bc7da5a585398132b9dbccc96ecad097c85656481f4042b28b8cbb2013d07064bc1199c3f74968034c15a14781f8086f0df43389122ab3ece51ae1b83874d59c99802d05b34d4fd06e9ad58cbf487a4e78d23d6ed0b4427e1ff0f47c2247cb90eca4688ccc5629b10dc830dc255a72dd65c0f40a508b24deb0e5008183dc1061ca6d06408b30208cc00e0bb5978aa5ef7adb862434e8f2d5fe71aa0a57063f4333408ec128244d7c05d253c78de38249c12bec676f1ed2aa71e350178565646338fbe1e155fe52679d282a68e05be44483e463701174012fcce3af8e56e49b4697ffa7df43feb06ee718a7d6e1615169634f3b109599fd70fe49dc9d55215585d637297b086ca92faebf359e77dea47703bbecb70a607f44332795f64e5c267f33db1e44420ec4703d661692d9b6e88d0e4f4ab55d9c0631885b1f44f36a2be002ffc01c89524ce544486aa82e15ac9884a7ef6ce2e5640d3bd334eac12905426b2b771418a99d924c55859504643e828a40d69de91d013a2d62c61fd5a958ee518aba792ffe79813305fab11fe67f9cd2250efaeddd98264b3064ab3db1873b13a0d124fb6a171b504f9b624cac3c6082ade878d6632a4f07950cd4ba95366c1bd42bf96ffd2d1b56b5483c140c0187a37edb84df314d538f61d0b4433a04b174b8390f5b641b25aa159e81835f8832ab19d3e679fc4914938ab897158bc2e2b309112ce6f69a542c36bf861df3c06ebdc296a5189fc2dc2eb5748110a7e749a4622e8018020d22aeac517a42912d56471d784d669a7c4386cf52d072fe5113700806d77b59cb2089786d9439212740acc868e6f7a02fe30c8f6ebe06bf52fa5328503819945d970a0f970e4355dbd9464a31f90a689e7cb3c7c3435ab31656d667077cc1060f53a6aae3a6d2a767cd050bd367b101b0041ad2fe2bb23f76944051e6df5a0c49a329167b4728f7ac7ab2f0386e825b3381c4ac68048d0410f03c65b004cd2b8395cc66472fd3513a05913f8e3ad944c8d45fc5ab1d12fc6b94c6ee8234cb56ea083fd6e7142793418a253a032874bc0a2efbd0753e6345a4b53d95403c4d40a9ac3c034d539e661ed2ff06688789e6ac1b8e76a4a3af3b940cde3c5fcc5f41526002e6975a8123c3647b0e1475a5390bbfa7c6899056bccb9ea0695e38366003dc56a62c658b1b4d990cd95554f7b33c472aed4437dc505eda3487b4e57dc61f3d7e9dfac66715275fbb41a68f965a002f3c221442f3864d53cf10a1bf5318344b1decb3999b39e1c06a8738a439429f5d83f966573582f050754ca212366ddb19e5784ab41ce278c187a92bb2a24b2bef50dfd582f2624fa81ec1206bc7fa2cebe5f5b1b66b1a3c424dc7228d77ed211341f90d6a7932a5c5f0b1330eec3d2f1ebdc1d78e9f3f69c31f0b268bc545794a4a76b19df37ef42d987c74a19752915a78aa64fc5c016a683ae0ca14d1a4e6582ba079f862e9df0d5635283e04e7c839350b4c1c60978050ae78f13b08fdf37e155b36fc2cf5c7e5d72eaecf2c7573e56ada22f4fa491fcf05ec4f820ceb571cb817193dbc07ca75871439caf688fc846a10be9cc03620a1525b233e2282f2598dc616370626d4b537b7e9d43de831acecff895768e953f037fe2432ac3b1bfdfd55d2d476ee491ec11d6b6c7b8e9e5276b0cc7df4758636ec743d2abeeb797a8b46682a7a4c9a1d8e9393c1185e84ed1686f9e385ce4c0ed0ff7cb518917906229432f61dd48563cf3e3b94cad4487eb08b1758e3660e914b6f351479e57199c33b1bfe4b56248e89240455c245eaae2554a54de941bb958890d4af51c8986951950fcd12254d4ba78b5ee41c400327ec2eb37701c591987d91333942a980f327a70055954f220f4b826cc79c099550309816de8e3c814557d16a1a73f0f8e8139a2135f69f187f25cd5bdc971a912ed28d0af229bd207792310f7bc3a9732f70c187d0cc55ce5545ca726d53fc00a9f3aec619bfba0ec4b4852776d589cd315b43e07c13311258379308c11b975a438e38cc50a814edb18ecec3f8f33119ec7c0bcce20074ea60cc3750b05683cea2f486ee98eb26d17515525c3ed37cbb10a12411dd1566cf6eb8f3681a0e351b388dd8303ac60f8931fb3ca71f561e695113b976156e7e9dc11311c9c118734e5b92341199d10957bf496ad371c10fa947cb48fe2d6436828a5581dca2c7668a4afe42c3c1bb23fcb258ae3ec47ebb44ef07a73a6f4f24cb9289648162ea6b51bd85329006461e921428c310cde6125d914906222753416601d453c686078cc6a5237b78736811659927c43a2b245e5374f0a0788eb5d63541644471a390e4e62b27812b3e2b84f2f05b049b358b0546aa1c30d68854227a2343ee3293d8deaa9019047d9f5d50b172545d6e8000cbc29a6d1cde162c1f4b91429e394314a4a3f54fd87744ee82304bcdd1d55e0310c830602c74fc92e094a8b27fdea2b286b8b09ddd78a89012d66c01c6a84545727629ebd5cd96c950c8548ed4fa73981c7039064185e4f28f72fb60e115b93835df6938d8040194dc447dd971a76f5d1a85ccbfb05b2d60a38a8fb47f22d95821d1b53d82c79402b872db5984a55352a181ab3a5e38e45e8091fb015db034184637304b5eac0b1e9c0386be3cad265ca6c67d25209915c6bb0dddc0893cdddad9027c4178527ba705b8abe9a95a4138a84a7487e95651db272e7b8dc344966e58530362580f8061088174a6ebafb67646227f9a89ad0f82ab479fd8afcf063536e726451460561b635465adc3835a05cf612ea1e97e9f91fbf8be34d6b264de119ea7d1dafa4b3a76062c1643e22ec7ea17bb374a24a893d0018b73c1a0d42aa9721b42912532ec8fca885b59014935543f0149716dc72f86014e2bb74badceaa228d5537bf72b538a27e52bed4351a8a06ea638797869c25b065924344010b811eec5cd9f60455e81943189e473542b34d9cb2ffb05c5097dc2cca887be639ed42a2f8634f496d784d2020eca218bf249298b9a82f3e69355ca54a44b0ad3f4cae5eab74e4900ff7c8261c38be5d2d3e7a44b79512daf3acd599e7779522c0087711be71959aaa4ef741648cf059c60800bdb9b96001bca50c8d385a9b02cd46485c3958f5bc850d3491d61927b524424114be8660ee055559ee32c4a6e488c37272409b062c52059cfec17f6356ecdc22eed08112cd85772c0e469c1683b673d84eff8c8a82db8c3faec2e5c6b62726044c6a8deb90dae917dde4d0b93311a13ee12bd904f179c601b49e826b0070dde35039fbfce2d4976b4018a876641e1f348c50ced53db4216c4895dbc05ffbbb54cc74222b11514a533676ed04b2b50315eb5fb75c139c84e5301a0aa9f21666ced5b425a0008fbfe21a3b60957fb1779e379e8d00ee2a85a8dbc45422ab71def9b92a992bc638932ee17a85f0f425f3ffdebeb9fd607501f3d35233f9e40f1c6fea8c56491babf5a2a0f8c08d2f54a98eaeff37dd8fc574a9a60212bb74ee8c4650a9e8b7e14d2040becdddf171b23c5ba7189aebb14ea0d7bdc5496e316b2c74a9dbc235d0706bd92a2e0ea0bdf48c5e33ea053a9e283e4c67f94722cc757b392ae2123124f03c88a52a8dab26b38cbac4347e1f70a5ff08e892f397159e57cbdd15d86cdb918dae75630d32c6724bf9c2af8563dae3b8325dd138534984194867173e7d9e8b906ac9e97667cefea387ae4a997dda43f1afb857c220128fdd3f12bbaaeb7fe0d1bad7ceb4b532f1456ccd5d7af3b8d66a74706cfebe986c183f3ee77fc7e3730f487a51cd8770d4ce115ef8ab2156baa07912d3ea1822d6ba0c5be7777bbff161ec792de29f2940e10ca0ca162708978450a12f97c1c5f5a631841d3e05991e4dedb4eec91a8aa641ced9f0d01ca8c4f991b117541c19fb4e8c141d9c30f37c0378f03f10a26adb849eeb21a16e2c6576954490c41ab7a6363461e7396a5439d208e1b099c1f9850383a402f010eb8a8fa566df863af97f8301e8359466eafb6df57c36a0e3ff90f9b214b6d4ff2879eda8058e7b6b3ed7c88c636c7e588b0a72948fe10a84f23923d37f1c172e21f138f3d23e34391fe85bf90bd22742bcb86a7d20767d161acc8db632593d707745b7e38be07e745ddcd3ebcc1563b587f208930f5bfa0e641ca8939052eb71e6fd74be343818e68a311b680a058c6bc4af003fe3a21d5082b94a5b6bf4edcd6eef6de7ca35cdbc4bd7f88da7bd1efcac9c223e6ebaf08d17d883cff270c85c95b6060c6e66cc99f99e2f92ae58f5a9e798a8e6f3eeafb0c6a06f9f86c7c009d53c6585e1e8e9de8edcd4efd4128a7f046077e353afe90489ff612808368f4e0200f6334b8f38309d2a4df7541515a9d65e727e8acbfb85b02942e15c2578bfbccf676820298cb6901b4b7e03b8322fc15ab49a73f19d7361d84ae9a9947637aea0dd511ad61c1040d2297b8b0a9ea71c7803adecd828267e2ea43451b878d47578444b3faf416521de3205af9788d1bf64bc580e0b9c16c165608bc544e9b16e6a13225b4d8ea58f2425772497ccb8f8a8871de6d405fd746fc0b15af0dd4bf8042b8b07bc8c5e1a656456bd1646676fe0c560a6e067c5413a9459a88919765fd2dd0f58b50a48e958381130d881243bb127fceabea4fa5a112aa0fc5ed6bbfd638da931efb6a48e439191f010489cd32b0b31e6b4553b5a41471a85a90ed1355afa5b41c2dbd525bf1ff945dbfc3b205467a2b602fb65ebf4c09d254b2eb2ea694e2f3b5b239550db193ea969d62659e1a015f258e648189217544a0af6422b02f0e5db2a1af4aaef5c0287f9cca3ae03ddb46d3f402c52f36a4ad3675affcfa6bfff69b7689f181aca4acbb683e19facc9fee7ceb50e32536e9db29495a41c93e668ad33101dbf7cca098506f49db7c2e4f928212cef2ac534c7bf489166ba002ba738997489e770c76dc963667b8e5524e33039abb17a6bb6980a6948faa82fd97bf5cb25cd1004f3525ccefd5cb046f2396e3557c24e83e7e1c664042d3762bbfc3fb39778568fc67363696be9c8a01b580b35cb6be6b46ab4ac055f09de5ceaad881403b310c8d3d7104356d3cb109e1e141c1f75de703225d15801fa85784251b785996ca76f9884c5d5602ecb3568499fa1ce9b54dc7ec49f8482d41755fc20d89175a1b06d362265af4b2c3d07ea82c5657c6da0b660b37a66ebe6536dcd66297cc53f8d3fa6bff62fdadcbb21be3c3e63654eaa9a0e2b8977b80bdbcfe23cdf57ab952d2cb61635e5e445ebe31c9efbd1b6ae534ce7ed0e5043b13d9b6d1a09e8e81080624ba036aa12e8657205816a703f9819b4295991c26757055b82fe96e34a8125773129a97aae8feecfc88f8292ae25cdefbe214013b2845fc8cebfcfdeea32ed63c334d3907afbb01306e789d8d7d99b24cbe15ff2bbfeddd6d6fb9a54c29c922086e0865082017ca6ae58c133178d272c44b5dedfbda311848f6b6b9e8125d97df43cf35227288f41bf8da1645475da04ffddbd8e28c81c7d54298955b468e2e548531dcee55c49121da6ac86cf9a967385538ddf13744fac97bfe86742f3f9def71ba0325b889def3a61722f15e14f6d7bd28947e9aad71b26eab3e1d3827f4f20bc2395f28fa49863cd024647660284412ea54daea36385220eaba3d65a06f9bb5a512eb8e0c2473d49caee99205c9648e92393eb329cf992e3f7533500ef3d93fc99d2e3a1dd4665ab7ac0134c61d1b48ca88707bf410c5ff52a97dac3505e99c6dcbc2ca58266d247f75570d1b48025d1cdf8080569427e59658e0b4180d9b3c67cf6666e7d9546b588b81c3375ad82fa6f1d7f193180c44a5c59101004c654db7f5b7b8591c1e1ef73baa9b099259fd97959033a328ac0fec77646f2628b9dc023e71109c059e71e743105272cf6d5828ec868db01899359b713f577ef2d04f959f9c59fc04abafc819ff8ebb7f60c7ee59875325671c0b3b36ecfaab9fbfe3286baab46f335a0c3ef9cb8eb25a8d9e4dae61ad86ac51000e173e15a6f1279bb66d75ab9ad69a0264cd08a4e8353e388a59de023ef97b9be13280f5ccdc09ee197425e48cff085c49cfef1cff10d8391d14f65ce7409390d073cf21098544fce47f822acb09f17777fbc08259cc624d5517c0bf87a0ea1a1ffd4f9a7d66c82c1f66b399aac539552db6142ca9e73d3df349a93837666656a3a767f5faeb703269d1574eae4f958e97f5c6d2413d9be9c857b779dce3038f0f9d5729c4853aaf65daa9e3083c422b5ba7aaaa565df3829c4e2693d99a52266b56e17d1e15b57893999987300a08d6b13f1208f6c9019fbc99d5203fe1f2fdf82e87cc52c352a7325953256d4ed77fae1ce5393e5bf7f975706135cdfd4315d871aaa6ca513d5389c4b0e3545def3b551368aaa66aaaa6aa67ce6da31bb349811030856f0b4b58774ee6fa6fe0f51bd747d77face17a3f7b8ab9953842278f23ece839d5bac123f449b0e9d0e8f4ecc63147b58f4f0c34f5079a5af4dda110d193422426ed4dfd2ea24762d24c1d9a82c43ce987c0c03c2914220a212ea19531df27c91975ab5b2d92625e5efbef9afadaf3e81aefb570f469d15bda57d1ad1730941ee2f8c94d3a2f6fd2417b17d29b86700ee9d954811717d2cbfc8d1f42fad18f42215ec2286a68caa17ef8fda4d01444e66fc8fc0dd390d29bbef4a6973931a149fb984f920313923ee685903e26441285f7484cdb9be8c39042d37c93f6a497425e3ee6634224a62d3c22c4e5edeb2073621ee687c43c4c28441442460ff342d810498fd7109851a803f3129a667844079963d2483fc4e54921920f63432c29dc2149ce2881483ff6c851bf7f7c1c2f735ec2284c397491ae2b187a1238fa17b0e56340978701edf367d291791d648ecc0fd1f1a5979f497b2dd4b9f144748451f49b1e3469323a748451d452e961625e8852e803cccbbc8ea7610c1da9949f489fe36352314ffa0ac07c8ed0a443223de7bcbc3ce7bc3c4ecb8032c7049ab4cff149726e80262d34e9c8bce97384486ebc298c22f4329f244706bc11de78ebf242dc08b9c7becc9b9e86314ca1f493cce871b8fce82b601f47f89cd3d2f29cd3f2387d03e49c12c8399a4f8b3e5068ab35764a04b64fa7564f7c7c644099d34f80a200d150ca74b956ab2d86543d9897ce051c81cfa305b4626f5d3d11d82993cbf694b15351ba46037275cd4be698f875790a50bf1cc6b039e9cb6f6e73e3d015767e3868e12849a861b87f90a7d9da7713460b6b9bfbed473b3f7485cd71a917b344429d16a77dd5f4cf470d400a3712a5e327be3182056d237cc32d1d1fdc299bc0ac27ecc3eaf7a4117e59f97cc8a7d35ba485f245ca481716d3cc9f22c847c822a4b803d37012f2347f9271e78f3d1ee0ce25eeec959c9993888ef7ccea03e4e0ceaa317d7af9f3a1b3d385d2756ee897433f9d6c61097bea17bdefee169658ed76ef3d0c98384211d6c8d9e9de7b9c762f7485e52b67a3be97ec83c7117647e97f5c61fb8e7f04071df7fb0d748267cbf9befff3c01e72fee6be7121ffe6fcdb3770bbf2f3b0449234fd149469112cc2f38aebea4427794c665b62666ecf9ba51668a9856935d6261565a0a1659a69bb49e9943c7b761bc8540bafe4824ffe2bbed144e7825132f0c99fae98891517ec6ae6e94dea69301918f5834f387ef2e7e71f16c73217923022d2a6df9f0212c55b704e7cbfc3944bc4515f58e78f8460bf173dbb80ac0bc552fae431d72808b67e5faddf8878bbd435dbc4a1022bafbf1372060792c69f03d9899c59b1941d587eb1ab45b0087694414138ea4706f19c6c92a1243a57ddfec0b130af3b719557349860a7044836ed47190433b9baf910c1ecc82eaf0bbb4edc6a8f90a6813a57ce0401ca1901c10601393b87a378b99968326bd7684f1f8a7f09641037461c91156ce415ec46a97ad4b585dd4b0bb6c4795de53927c79e0ca2f785447d4690489035da1741daf0bfdc201cf585bcf213dfd090a3ac3e1b561ca579c5e148a3c8c031af021c05ff6eca633013a4e8cf51f846879de0c5758e12057649200e9233fe0b0082c32e094479355d937aa6eb32744d498afe23e82251a23cf977d334a78b5f1cb4c353a4b08cb3308ccbb8feb39999081fcfeb28a5f46f13e19b10c81f8c92fce272fd475748189d1f84a36cc84d644b18fa7f3fd7bfaf8bdfe56217bbd8c5ae3943eda3075a3061c5cc54f479feb651f6fd4bd226f4fe37a4cdf6fe38a44d871a8938d8e1f99c96fa2210880d087be97b20101d18430f3f75600c9a46e4b26844e48d9bdaf61cd76d60e8a927fe10c029009e486f0b08d3b6c5dace748a42e16f5b0edb739b6df916b0879f5a207ecd012116fc8af9916bc2d5d1adbe725fad56950833f4959cf1245656fae48f659b59ec0e55729d2e99939d15faaa45f00b3b83e6939e554c2833ca9472a9346ba5ea993b9fa584e5ba159d1d3b76ece0e131c82f06bfcbb5a5a48445c2bebcab40def0c034cea335e5206f8b47e70bd8c20bec894b811debeb4ea9b0ae196b8a47977074165c7f1b8ead44e32ee4c95f148ec2d191f08523b331ba8baf3c9fc1556a25c562b158ab98d02530c0207a278fc11a8bd5181bb24530df43dc2b4080c2ef7243d85a46d78c9a6ac7f5ea4a0339e33f5facd335db0e5d511fcaa230a02ea067d02b940daaa24be40c738b4271d8dce9e9a427939e4b945cd8b65381da448df18d16ce17d3f8aa36c1286f62f60c8b15292f17eb89931d9e949c093a03c69b985de79bc9869c50b88217d765333bd2d89c6958b4202d0b4c8bf1c8ae6b53d0903447980212aebf26a5fb32b42854a05c9fd2555c2368506c21699e7869ab96e6735de3b9ae29b9fed40945ec481b2b8d6a257d2a83ca681579d343481a9736fc4e1c713d4b2fc1727dd450705f9e3f1fa2679791fd3c1a8b89c0c6f202f6cc0ae87ac26a273c2939e33d5de32a12f4895f66c1b2934509ffe84abc65ee0d83bdfa497743e928a95e754ff3901e86926667812618e562f009039589baf2267cc531ee420c867115f6829df00a8fa031a9318e8ba9982891418679475f854db0a34f1e7fcf022ba36bb66f959f8266cfa56ec50b7b498b211556b6d84b666490e1f341bf82a14bc123950b6d95ebc2b179683e1f737acc614e65fa18de041a8e9cc5570e7398c31cf672a2b19ea8a2ac9c256de83777c48b515e78043bb24f87a3c68489b4610e7d481cb7558d4ea64e288b52262d1ee0009f8fc91d61433ffa6ade2e015e48861df9d53b3cf3b7679f57cd91fa24e02c65748ac935808d4dad5cb7558e7d6e6cb0e1f3d1cf60e83678849f04dba55ac8307eb53845cef85356829ca1f1974a47da009133fea1efbbafe1aef13e0583c1d8a04f5a6451271e48574c542d32fbb89c30100d704f550d527ca3853a4c235f2e2951666c763161976f6695e9c574c2f5b7a3d077b2fbae3f1fdef77cbde62be8e52beaba14e8d21e47558daa6e89f278c979781a7e871c666147a157ddc27aa5c4dd2d5fc7fda4c6449e0ffbb0cf086c292dbaa2c819568b4e562219aba6344aa74ffc332c32cd8a56c67509a3726da0311a1b698c871dd887ad982f15d3b0158caa66f049759965be1f5f35c363aa9894ec302fac1599a73ca3adaf1665420f43c684529ea6640f71abb9ea15afa68cb957afd7063a00368103e0ed517d98c6a7aebc89ce039eccf181d1993741653e7c43af5032a81a3b88d1181176ac31719e214ffe57aecbe810b329a3311aa3311ae338cef27a7158633e6ef8814303aa1a5418d000a64ce88aaae6773978dc11b6bf7b80c7da75bec2af51aa0deb5fd018743fa9938f862b592e8d491b1ac81b5781f32b083fe0178d4925211b96569127ff3358577c281badeb7fe3a331baa24ca84aced02554e52df77196af441d27ac5ee97c10f87c841e02524640488e1ca51b5336653077984781bbc9f5ff42dcf6a4d23a6b5795c7311a9bb18eb1b792334e5a6445e91a1f2c77a4b11834a0eb515cca48633cd2980d3406d3112224021190c01026d2a9dee12cda0426c051e056c023810e9b389ccae7c3615b583956f61013581ad53c74e77a96399b5168e3fa0d6341d2f8944dd99465e954ef4cd994f5aa9d28d19e684e3426da121aa3313a2de09140a7d2a73e8d5ec6f5d74020b64b4326c8197f0f43d290402718748247cc810981d880b0b75f03df8f8fa76400039f8fee31f0ddb0018ffd4a8df5180e8b8df575bdc586e52b55aa8c5f2e98273dcfd7ab6b48307436d3d9dc5635caa0e4b6aad10d6c60871d38e03d619a1a30aa82c1277fd235382f83f5761ff018e4eb32391306182f47f912a220850f460002860e63c18e1bcff5a70cf3ce2fef48205865ac52ae7fcdb8ed5c4f8244d529d2865bd5e77edafbe58f866167bd4348328fd33e4c41609ef442609e04c3313f6d57c381a53b20cf40e9a798977921312f43fa98f015f348603e26e4d776dd95c819ff900d4b5fe6c0901e66474ad79048a153d0838298af098b79524cccf277463ce92812cc8f3a607e748143be9937240260ba2ff423bfbecfebfb422b612a1cc4b3dee1d8e78542a1d00dc58032679e2167a0c034565a9cf5c412d4a27720df18b0731a7422e649608967cb89d1d1d35f82b5e854a695299b58ee94855d8c5336d615533932cb9036f35b546034467ec2f577987b8cbbce578b77d553e627ff6db3b6549a29e01ac92214182599f674cd9436678c1a112b285122ac15661ad7a76cca2efdd219b6ca3629d3989cf1a755349e23ddad29e9896d9bb53446631cce57d7cc0f78fd810f7c3ebaff0008b63bd61583b5ae384bd7b077936a4cfab6d2a8988ff99898d077b2602983ca94981f3b8acc8f0de5c7aea2144a5f72653cc6619ef4386dfa422a2c87ac228135e6ef1cbf58d5ea9ab1c6ba46dac4aa8c8eded47aae2fb9ae299133313b8a77a43123357684e51fc7cf47f70cf62df2dd50c4ab31695345a2b88b578d711581c7af66d2356c480e43b6206da412d71b09cdab73951d6458fa9d5281b45102f341ec63054b96eb3b17e554a6a07ca5a451358a85c25ec51226d743273cb4f892332e73c6ba3a5dfe422a2c83da75f7771813541aa5b1ac8cdaeafa87bae60b8d05b70b79a3a940d2bca4cdca511e863cf97b95cdcfb0ee57aeaf1c267ac1fac52f7ef14b5ba2f5809a1239c32f7eb1e6d335a394dd2a5efe74898c56719d3eb94e9d30b91e8edb168ed6964ae1f8e27c98077bf849bcb1f58cbaaf28a3933ca476e58b573edf78299924470ea1bf0d39f1c8b2a7743e4ee3a07dd3d9abee884d5dfed20a6c897e0ed96db914a56346b340234643166854817aa92ca44003caf4b59ef4e7d26f4f874f87d0735fa2ff8e12620b815be8eb735ad564619c115bc28b007a72cf7a8d6ec198c67f0d46f50b5a72bb7e5ec0285e83595bc88d615a14e1ba37aed4a28858aa0a3da0d73369d3fd836eadd1335e836f7a0bd93ef056b7357dfe7068118ce7dc601c8644f54f0bd6b29dc8db31c5f2fd5ad2a1fcc896b562abd5f37d3bb2f48c3e9d1d20477d2f7afe0ffcbee58788de8abee5452fc4e8bf9739dd8e2c9665bfc586a6feef47a3377528e4fb914988e85d5ef42eef1f1250f67d53421381f502dbf2f6e76b2d282dba11972ca4729065f4489b225ad883f66ec4ebb612d2a6732cb7815aa444b032685821a2d05e88ef5de67743cb14e47b9717f2bd4b28fa51c8834c14dbe2359d2eda5e04f6cc82b3949cf1300d5b05599386b4b902ab5544af5a37c802a87bbaa7c56ab15a2c16abd25ab5aa51510bfd91f7e295a4b063b77ab688b69e9033cc05df74e7a0597d65d69f8f1fb88dbba3063c76ec96dc9e55351cdefc6ef88122081895180cc6400ea4c308931968c643b8fe332698cf476b74bb188fc1d1ed969cf1d98807505c219bceeb7ab7ba55c33d55cd62188bc1e2a1e26ddbea562577b3b85b0d04ee1e69b3d5c7e91fee14ec15109ab48089eb9d7cb990009231c38e3d6b713ff6ec093264d81b6652ecd82d192b36f41df5b6ba79ceef1f04fa869e65ac60719ac17ab7cab354dd52a92d354b5134ecd8d3f3b41edbefd0c28e3debe16184e597a965a5164b5857de915d2ef04fcb823bb0520b1400ddb17be413aa80a15185185c91c62c0cfd58ed8edde380f9a67e4a04cb2e25ac2bfc1ce0e0c777013812a8d25ab5aad11d3cbc17c460144bc127e7b055cd7a0106183e1f4e7c36f5fdbd9bda0146f5ef868f8bf09c3de72401bd4070923b9fc386d2270ffdd8be5ad8add08f1d9f79ce2a8a9e288aa11fcbac198fc18f492ff9e9663e853fa1fd58d3ed5b9aa2ea9e16bbc759b552cd6d9b556301847e66251dd81e703c27ed66f0669861003bccf218740bc851a29fc29ff0264e5d57a4fdd0431f6286c4aa67c794596805c85d9a6f6bd1bb87fc6c8b53c219084083c720b37eece4ae8659dda2fe85507ce11276ec1fc80e82acf1810c2e1612d53d7e6af10dab68705d1ae1fa11d28b9022b73c099574150d96b8feceac59f7a8ba877e17a56f6e5bd54416acc106cf0862b00bd6b317fc4967712cdee34e3c359b990bc04a7413492345b7a07b4a4cd8b17b1a4b8fae449485f60fcf6aba35a78d0d37ddd35724d713fab17ec7eeb99f0fde69d19f49cfd89028265d17bd121689eb3e32024b9b204775cb51cd6275688a95e1c28eacba11b116728a79b6b023a7e61dbbe7867eec773f1aae4c19836312d5e5b05946a4ecb64f11be100a1b2eb1b31f79f2174217b36ecdba35eb59cf7a26fa3c26020b3ac943898f3bc2f695339bc82bde829f90db4adab470b7d9b6711777fb99e26e1c645bdc9e4118d16b71631e39b33dc8861d79b56d630d77abd94a77fbcd13a174cdc8473090a3a436f211d2a6eff63ad28657726607470591286e224fdb6fa328fe7669a8847b98270b368a104a88f9de6f510899efbd10f4bfefb1bdd8b1778fd4d46db0de6e52272d6e4fc1f9dd90e476340c7b2ba8a5aefcb8c7e96a5ff44346159ca9db51cf3f5310d1b7bc10d1b788420f85881ec9e845db57b053d7c74fdb083485defbd07f4f2916ebdf813ca5c50dc84f9beb075310fadf5760bef740d8a62267b66ddbde51dfaac4f2d3c6ab16472e0bc4767ff8812ffdf938ed59c9630b8b74ac4ba2abf527581fecbb759ad77d921c1387eefe48bad1e6749bc71d6119a4b7d3bc0ddcee9cb7e3a8e6b579e336916cb1d6e77a61a0b0f46768da3c3485deb4bdbff726eefd33857ed382ccef7e0385ccef42d3f6dd8f9e0385f87ba12948f7a3376da1902eecbcff5ef4a370fb91fd9d963762cbe9de9a3621ecbf84435c46ef12f6f0537d99d37d3ff0252267ea87be203307c74ff5394e081cc562fb6b0f03dcfadf016e7d2f01b77e07ce204ff5bb8fd19033f537905e6945ced4dfea16f2acc7ad0f7e613bc525994db36cb381b828a52efaaa556693728a5344e80c01c2375ae801a619c2a81e7a60141bc127244b64194b649650a74d168d603c70fe188e3c2ecc8f31301004b8b29329996acd41b59eb5ced704a3ebac569133dd61bd22692a19ddcdfa457d7d95bd9a70f7578b3ea5c50a83e88458adfca38913153ed58c1a0b408d5533d86755cd60546da2731f39d344cef8aa09f729a4374ffdd1573d66a852a4c8eaac56a932bea957244d25430d2e083003d50463be6898af09868a7d26188c622b9a99d90ae62a92b9260a3ca078e01b2ddc767adbe19bfe5965dbb92f03f0d03656f8f020a170fdbb1b7e3118b4c6180c465533eacb06ea041a03e3c5d5a736a9aceb7c53a7a837a0c20b16cdae691e3ab52f477ea1c346702af472f118a42f35764375428dd526eaca058f5f3870119caa818a01f38bf9622bd8278619fa01105080d73140cbc1ab2b087835562adda82bd3fc15f00df73b3ca61ce8f37328ef568382aecc342cece8ab1b8fe3475f1d69aab3b3c353430e0ce9635ee6774a6fc4d672e5e72af015308defdcb1797aa7ee58122467585602f92567f097e12401466e8294338c3c46e9d3a173605ee60b327360428ee1c0696cbea84aa632d5ef4c5e0eda646575683ab0cf310e9236f20acb292642cef8f3b4c3fce46d04c16032495f3eda89e89dee22a88d2b4197675c85c388d9b1c48ef5a519616b65b1c871b26d8992a674a76738b1a88650ad27a7d888909f18c128ae019f425a830a86a4f1e7d3c8fb42cc4f5aa34104fde08cb0e394c9ac2cc112648481b094167de4d55abf90370a7d558a0bdef49955cc2753c9ec993c2e782eb80045cef877313b5b09247a882a9a54859a8a6735a4c08ebe72e2acda64861cca7c144a94ebaf49b9fe74caf59f54ae3f3fe153fc78f3256dba90289ec267be7cec8ab26c8b673eee4dc63aab41be92994f9f41981f6f9002cd8af9005680966fced9f2cd1781324703e54d329bdc80697ef46a0d3b726c7a9f17e220bb90354d24123128354dd3b417cfda29f31fc9b1561704cbdd915bab21f00d18d5293fadf8462c2dc17a5184920fec0e3855468b31366c6ae58a327250e81322fa165260b9cf851f3b3207f14de8fd17206d385104248ab330031802b286539eead405e2a798eb0bb30089b95cb1de36b07b408514bd7bb87fd08276c92460b1ead70e4f6a95e2d20784526d631ad157fac28c574cd3346db52af57da1a93c69979306ea29edd350ba74c36b1c8dc3d3869033fefefd4835c159f886959044a4ba2e08b6cec08e9d1a6bf87e882834f177434c1ceae488582b53bfe84dfcdef726ee5ec8f748a2e0f09174ff0185dcead5f51a1a8003878ef21c85833cf9ab80ac56da6ab5ea55af7ab581dceda4b6da9ac80a3aefade94f10887ae5bb12dfaa26411a825b928d598cae6bee2b5be48ceceeb691524ae914083ca4db946529a73085124be1eeeed259d0f168517a73523a5dca3927d542295896c297668b7336a1352de81514d4461b1bc7416183823627f51707390703776f7ab3062efca6014cf3af1467111dd53b6c481aef56725bbc020705695b6d1804050505f115244a6706a5256c12b7bb881a9d3a8ac2fae63d68b70abe9fa2605f73e3e88b9d080a92335e586e5f6d35c00112ae48238b531a965e99451a4069b45cceec20290976f4eef5cbec2c6ab123971718528c4ce9060e530e1d4e9d3a75da820b0f863b78bc000018c453000410c38c0c343d661800010840001a0a50538029e058a0aee1ff12cbd2a88ae5a17d3eaae631ee75faf4a6f7c3b4ed98eba5522db164395e776e9bd329f55992a5f652e9fea88a9d2df29c1e02b960f657540186cb3ba70004ab8424765cb67405c8c25cb1c11ded43a1c42500645141b1f5ca2cae2821801558d29559ccb2fcc006f998cd9e30843bafcc62b6e2e1413453b18d84f4765c39df0339e5a720fc14b33cc5101205c44fd2467bd92c2e1cbb899c2133909f805c40409bc370470682e18ed2757b8a3b843c39c75c3e72c683662d863c48693ea8da7cbe99a10d4c23830d4b48a147e6d3cd559b3229ccf09946c65260d40fed7b34ad292067e6ff8fc7a1b3bfc5b0a39c89dc125b2fdc125b2d51e41b8e314d174186c859bca6c05e8e6225e4c9bfc9f5af1a9dda8e97956bedda2c999bf358c6329ea99c30162bcdd34c38884e4da353abb5d6aac1180ce697f6fe42b6d0c21f39e392335d7bda5639308084f00d6f2199e594e6275866999fa858af07d6c42f734cdc3304ebfe1c964a1206b20556dc1948eee11bae82892ae48c5bd2466b438a5790a2f3c05d84ecc1c24d8265adbac00e445af487f5c81997c9aa4c26639946a7cffb5081b35ccc1823fdedfa04d1f0c0eda79e3592e3f617a1e198e3f673620c321cfb678edb438e5b84fb51738c392e573b346cff37c71c957eec815b84fe46bb508f8ee98eb6695be5667353db26a5734ef6baca6bfc748e5230bff3b6e91d0d694d84bc9fa0474790e4b24ff6c071bb73765c881d8aba6d200e9cba53dbd8351f5b05c769396e2b21ca7130fd83eb01e616e1e877dd2247e9e4be4e2e1c616e11e66ab8bd12db3f964a258fe3429a86c4a6bd168e20c96d0e9caef1164a19c3fc19a2da6fcf6d1bf5aa51285da8fbdf363727d7bdfde0c3f5bed2ed470edf9b55b0fdf6e78fde9bda08641bb7e547ee47a1f6ddc0859e0da9e8bb341cf9ebe7c3d6c295e511f9beef3322de5ac11ec4aebdee46b1866a35f1f620de16517d7912fdf72ddf712e2010f5ba70b3d62de4e2f238ce85b4d9e32a47f2c7df1ada14d87e17d3a86954f3b4f9385dc11be4cb4b696302222fcc0f09b8f56b38822417e6c7fa9b3743a12d7c6f9cb743a18f646248a4972191b649c281e36ffca8fd86e36571bcf613c717d1b690391ce1d6a27ded06285bb45f02678bf66540af45fb31a06dd13e098469d13e0c7843c660ff05c42167ecbb8039a468df04be3cd93b0a4b72c6fe078ee2b5e13712b945be1789be2e044405e5d4b45a1fc7ab374bfd31999365287466cdb7ca3c9d8990962307ad959b93a3b4bb2bad5b87638e4b7bc871399ee198e3ceafcf6d2d8eea9e8e39c2ea8190b675b4f98bd4ef2ced7e4ab939394a9fd66deba17427b7fde55c58027d4f9b97f26079b845ea87359cda15d80bb9c06e4f9dfbfe0a8a5db3859b87251c7fed37edfdb5d7e86b4184ec7f2ccb6d93deee4e6bad557bd7b4f75a04a2c2890bc356e3d1a8d0f7e9c51e4fe4008a99b1c68a7ed7021a36d40ae3fa8f1d3709a280ca7db9520b1410e176ecd51848d44b1bd2a54eff8926977ee743c8438bf4c31e5827240ac74fb46b76685e42d67440320c24cad61f590797527f7b433c7440d6701252a4cf3cb4fbcd93903552d2e71ea72cc5fab47eedf1a894751e929b03aaecd60f8542a15028140a8542a1901129bb45ea6f1f0ad5e7ed4bb11a7a9ee7799ef71d0e1ad0b045425fc48894dd51f4f53d6feb72e87efb31f4a328d4ea9cd5d6fa3538aabf3e10593726ecb6ddf0fd180a4729bba1914d41ec8f7e88fd91e887b4bc281c43ef3d8e87a311ed3d2058bedbcb9003badf3c50fb7e6cf543a1ef2ae87de759db6243ef51d0dfe285a31129bb5d38562e046ecff9d85aae7492c7e851b8270301511a82bdeaaeba542cb93917171c857211046b11fa11f56496140c73314dbfc0056b6182143016b77beaca0b1825d987909d0617c172cf546a5404172957a5b56a55a3dcd4298bc1cac10c542e18524f868911330315b74bd9e1e8c92ad30c54936a52a47a443c29508af5973255b03258ac8c1a56260d2bb386959fcb75f1662335048f8c10ecd8ad1d50b07c7e72c0aa3c907881b504005708828b284be041446bc62d2fe9a0c2cadbf24f856d9d524604c1375aad35a238a8540dd65639eb140d0800000008a3140000200c088703e2e18040a20782a83b14800b7c8c407462381608932887619451c6186310218400010118819991ea00be231d829503017ee523f858000fe934e6bd14408e7c2f48bddc293628b7668cfbd82a34b80ff58d0de48ee977fe6222a58955f1a59754c38eebc5b7c60c4662a7a14ad62fdf93d6308ca6c658645293f2e1525337a4beddceaa9adb35a60284b6dc3cd149aea25d23877de2a0634845399c9e97f975dc4b0745e765e4350209449ec12970fff12ca61edf472a8dec65c563683a5663114f67f615ea3374324360f9b59f8b8f8e925bc3c78bb47c48c70514cb1321624a297052b30ab69e8bf9920771daf3e3d61741ddc2d6721ec3530dd113343ddcdc17c4418420bbc44d145f2d1019cdca430c21da2ec409670f01d9bc8f5e1862ff62a1efcde85604ccf36a60e41eefb1c51bd54c743670b1d093a14bab127131ae1be6bfe2fbe49c2faeb40d62dc18686b29292347407b046573618310d36dbf6379cc9a889a3e08101d814272c68e62035168fbea7086647677a35f461cc0120f0aaa2516b38a08a94216a1af50e83a53f990341392ac1eeb63824c8f11cb85e3a110dd3b03e72638ff878994f9a18c49c83dbd9980321eb172f4b02603f81b42c38679fd8607d6870fe780c11b9d4f18fc5b767d9abc51d2150cb39f0e3c8242a02517254c14a400a37b0614c03a2fb04499aff770a17c9e0235fc2a6df0f0525d6e2803c56774c8da1946e0a8bfe47899b04b441d37e92841480725543b64421e69531b6530a15ff2762652e0cb33294d41af9495978a48187c25bdc248fb47135de44f3435d4c227b7496bfb3b49f8b91b530f7f94fa60080fa9f42be88c1634fda7345939f0c5e0d08937a205b297654355e34881e49438c8c72b0fed9ab6c76bbb98742a8f925367419bd2a3db73d43cf59b8e80988191b498d19aa73082fbe54b07f2f9e0e32917a77b9631facc2bb9a24b39108ec8df7f477e994587acb973b682fa487aecc635b9b891811f41140f41867221a81c41df8aa24bb1c86e2ba8055bfdcc9293845829ffba1d3fecc63b2256d91b01e1abb6f6483807e418ec476c25f1b2571906044e2e19d8f54dba583a52724682627cb320d59133c676da2aa6b3a56eb81983768bdcb47e34a4f07f9fc972a2cf1906dd924583587851246d84704f0e746604fb1ad19ea2f9db8a4bf2dd6689d9b0ad1320be5b037fe0e4bbc09d7a659b83cdf2c05c770c60ffe2f8509e77cdef3e6564aad574f657199f75f8f4d06bb80f0f7d322f778591aff05e6fa7cae2503f6b22a3a0e979cd644c86443a9e245a7946d7f13731c82a53c04771922436f8a7fb7e591329f7d456afe2dc3d73b92412e8c614fbde13a0a3ad0a82a6b2733b21132876a7effe14027633bb54b183c822787f1eff91a9628b3c1faf5ab44a14c909145a0ed886ea90d8e2d7e89577a32bd6c2b3dcd543749d839a898fc15cd9b29f1c933a0a48f5b354977e01ff0d5d021e76991e152b354721490af95754fba1039494ce715b34f82e2bf47ca3fafbf11f202f481d7dd457547616e1d373975cdc5a018ba09234bc86dc91d1da138f0752cdc0e5bf83d1397266496b774657556d41b2362e86ead366b7783911866bb7c7f441133891c063be15ca86d977bf421fdb963426a2d7bc8179ce6a77eaa924c3ccef1ca2dce60a6d93915a0ed72ef1efddd737d6e1ff3527a644940c341db1af6fa9ab93676ec97a9f2038332ab555336888815155d9dba9a1b8e03f3dc4e68fa81003aea8afb2fdb9608e48f8454736078d0debbc0be16a9e7101c7e6559e1e2f6b9793794426c564a0ac2612d2842ab453974678475b2391e0b8d9aaa26e1cd46f96de73e3a94bc92ae5d0f17d7e18528db4d444f2837f33d93a3f75e2e7796ed4b8031ee25d3c8f84bdc11a8298c821630fec94e76e46821cb1f975c3351414a4f9c48379ae87d2088ebe8b9116f9bc358cf770e9d8b61a4248a16d2909acad65c2b08a6c0dc60d53d09c59e838c4f0f1c716aab44e1dfd114f2294f033cb843edc6006664473d262991c1be3f261817066fab854d64570e23cdb817da47be1f9dac4394f8aa231d200dfacfd960e0f5cdcf96ae9efb0b07f703ca50b2d3b8b3d3cc0cdf194e84c676f3eca501150378d63684fcf4613bb32837a106310c9ef63e227e5c33872ca6ac421cb9e7c7d864352192a90a146e0314e5bd9f84ef527678a609848592d523c7d2c612ed23509920303599cd4a0ed794b0a5064d734c39e33bcebf80baa1be98636d1787e1b678d532253523036f182587722876f240745b41c385634f575ba32efc70fc162a1ef248ae23f403f5aa0a443145a76b9bdeaea3f92279f9c57af7c0d39d169f7a40b7159b1f0fb7108cee3fde500e851f6e57f394bcf7520c3da842c7755b8fa9b7d9b31eb787d7a2f5867d0b87433608c164c8ae5edf1c00de6db67c984ee2c6ff6c1a894f2230f96bc71ff709924c2d2ad0fd001e2c9ccdc735697168cb56063b188b193380ff0411ec4b4d77385895e7253b2371e3e4bb17667bdc9f226647bf9d0a59b1ea296860d9fe57fd70f696998460a8220749f2b08d28599825099efc44266468207b8df5eb5591dd4a00ec167e27ae536ae8e337931542554fac246b6f5120e0fc359051c998840edc109fdd81e56ce4ebbf9eacdfab1947f453bc034b3d2c27dc0928bd417a07cf5c79e225639a2f792a028de6885f75fccf8f71f70cc0b46c5f1d539a30d5e812f34afd09510aaff8fa4dddc0822549cab8e74126dea9a89aaeefb4f0307a083617586a0e2c508ddcd706162e22daf2ab62b13ce3d99d1fcd678e058821f5ac770d208c5078fefed67aabb894323331d3fbeb9b07ecba64aeea4e47918f6afd2256903897277bb1dc319c9cbc8e2ff6b700a2f99a7a9e10e4664525d6a821b690f3b41afca6d4b504aef3129a3e020a499760829b0086651e0fc8262e80ad8d7e8bd501481a2599666bc9a91f7e84f5184216793e54f10c1ac7e7214092632e603a6fa6c75c77fca21451731fcea82a5eacf26a0e2a06731e8f74cbdb7a77ba73a716ccdbd220cee1d266657f9aa96d9d1b9ab6b9a63ca7e56faacd8a19900d2edaec3fd32bab0fdc2280a4613f46d66ee7fe8523be24189fc9a1569d4d3db08e9d673e4eec35e862586773f2126ed00cfa240926df00a4e6471533fce91396e116474c3c0106f85291e72f4dc2f28163a115bac0c8f82dac4d72b96ef6141bc6c84a0271cc8ff009c38bb6affb12ae4df714c8d16bd880ff41bdb235e4f4f3679ed375198ca3c417d03bca9d68a9dcc8721a004479092519428c9942a80cf2d1fea4bf1359a0abcce9bc687105667e614333327cdc79f46d82a75f2b106c65700bc7c2fe13e1b3db829f677692fe3bb93ec59d188c96b38ab8cb27be0098ae03ba17e35966e73ff587cb7872717d8423254ed537054f98497aec21238c0916e15568d0a4bb2d348f4bebff37cd0254a121ba9dc099f059d1e26e829567f7ba94892b2e6bec56ed354a7c435f4dda696e12322fe789508ff8d59abae962ddfdc1f6d07a0a792b6c2498e492f0fc2a9c0e071981da593743964466e5aa8519997f355ff202c5b53f53c0df38af0b94cbf1f5f1c0a490c4e1e82d56c69188a612045c218c8ffbaa50dec70d26672b44ffe742e19a7f6fdd2663c58256965e953d367e1b1feec4362c335c1f6f5be3f2e2678df5accab67144e5f1d8c7316aa5637ef939915997b923018a2dafbf67e4c00f1ee5b0052d57454b90c204eea5b349de4f8658b1529760d362f51cbbac81f275aa24258a1f6119395a41b059d16923bb865a86be5d9b31bab77e9b0319eed1e88ee80cad6f9eccec4e0450aab8881e3b932d2cec9bab0c695fb0ddba3a238df61978543e1bd7e8555dbc85163f63d42f58e2762dcf7d05fc1e48984130eb09c6df7aa6f8547901942b079802dd79efc0763aa40d4a1290a9061effaa39b6b0db3d38ba62b4ed2e24677e4b5995d588bdaa417a52dba137a069e954c0bf9d893e5887a87478da10a3e5b2d238343c8975932ef3149c139ce0e52d704ed288279dd4108868e0c5fd4d408541643466f25bc4307535ed3bd21224e36b2f0a9c44563e81765c487668acf99733ca5eeafbd6ec2f03f4a4ab0da67e70c0404928832843367f7dd8dfa3e798cc300f067e646e4cb039be3f0b9ee84e2253a2689b4b233434dc537e5285d1385b58062363ae6ec809231eff1f258cf4839f6488b753c6b796ba50da9ee60ea084c936764dca09493d382871997036073667513e39f7cf84daa19e440b18cec99959a85441435bfd08fdf62e51b895b0c17d91247681ca79ede288f1590a41a93f991ad39882360c5ae27cdba6aaf167260429c5793cd5ccd9b1adef5016e64d981bbc6e58f84290e594de303dd5c45f7fe94c4ec6308e1ebc34decc3c5bc0fb7047d05e465b311ead1066bef6f532394d299dbb8d7171522e1b5cb450bfc9b4f0b8cf4afe402cf115c2c3d56f9f7dcb4428341ef1760e68a058af0e807bf42a18adef36a6c8dd3f63957416e08f4fba4f40351eaf5a009c1f3d50d7f9ed3e95fbae627b30ee21495855a4e512850cd9360bdd803bdf8449ddb9ec73db4f2c283f2ca602cb662438f6a1e227c3c135bcda7d698db6360d4bce879e81e189dcbdbd124c04d95ca03d5dc274f0f5011c68a5791f7a6c817d95d2bef506274f3b788c44a69464f2cb0000f3d7139b9ade9ef87659cf299f977d66c2ff1009b7178054ca3fa7db27ab62178c73cb38639ad5ac4b4f9c90c09897a5626750823fa3cc05a2537fd78f294db47a1f3a4a3f0df45231440ddcda4061a57807b421ec3c018ed87cd8fe48770cffb8825350f0ad8861e7a64cfcad27a32ea9370d26e0182db8f2db2c6582717a315adb60f48c00d2a966becb3e52383ca89c8012698d30286eae20cc8eea74f4011bb78e021da309366e49d047d1418eb580ae9b41bf260f1017d2f3f22b04061a0890d9da9683f7cf16a23336ad5010d61563857c890b1e267f11647ab93899b5f62151757564adcbc5202809efb6230cd1d19947b9fe6c38af442929805a357f3c9a3603cfbb996721101a13c5b2617512b416e7dec41999db32e8088502681933ed74ca29c75fe1f1352b4b94ff99731e1e4e79938ecda428228f29f160430a8aafdd90692c71b0bb976d944d1653f3c5dc8e567154bd8b756253e0e7580c0e6fed26b12003a7a8a13ac5ad76f45f67413fa26671ed93c0385c6df61b39bd6faaa109a0829e07143604f4801e5720458188e52959a5d7d6baca94f853d4c7065bb0bc0a789b60892ef01a8d6a6651be0a5803cbb28b5f3c3891b107ee99424b8d6069313d2ebbbc35e62c5805f0c60992c5e090ba1c32fd17e4d684ba47b851809d60113180c84adafaa24b7ec3bf08299051f3980117945db3ce2fd775d91574b1e4b3cb607a488eadcb387fc028e347cedd67e5163b6a34ab912383e3726ee01b85dd017e3ff17a06040f0357e474958ff9aa76a7c45c289b5ff245589b4f06287be8aaec2ca4b1c9c8f18a68c1954cb195a915f8b4cd8edde3fec5a9ec30fbb5496450144f8e2479b68e1e1ddc156405f45e28d0581eb1779d25c272096a1e856c190bc399c0e0c0d4d1bab1388b8fcc69e1997b1397ee0080afac909e7c045b2b181e6c6591e4ad92ca8fb6ca2e1602ba3d5881090048e7cf52791a5acf6ef22e49d18cca55ed2041ce1844555aeedc6334ab92b724d2f8962b5c264750a3c11f111af0fa4befeca44aa2c441726bdd51de875962ee9d673d212577e0494b893dc9f1f2076ea2b3af4555c2499f13d27d358884d030984c59edb302313af9b802246fc50a9664702315ed2c0bed99864461bae88b3e40d254a131a20a7e6ec4cb8081b6282a7be3a6e64c3ad1a0134be3d5dbed2c6932403b629b448fb656fc6562b68bd51bb069e162e09cd03dab434501b48d40481cdfcb9daabd62da73ce2a8a3bf20fc615f166713a7fc8be1ff082195134ae95c96247a4ba918f19fac2c26c4d5523ac3691aa5a5c132a133ad894579c03b3da7628cb296ffbc0f1c02b26d2fc7944e67915601c2d5184b79ab07f6119d595eea475d0968bb2046fa9fd0275a0c9cea1a1ca459abd0e309f723f2d4fcda9fcbf35c0287cd846c27b58bfb2df41090a7cdda70a508c473e33e733ede6fe8faaf627228bffe8085ed2f40a6936244ab85aef85c5bca5d02e0240350d4b564f65d6f27d3b9097fdd8260179d0eb1fbf33905a9dd9b693abe278436450cd299399907fe14032b97261eeffdbb20785798ad16774c7499abc7565588beeab1d9b62c6090d89c31a437633e82b1c861d126619c0d2537069f7f30edc2a2b3f89c9c0ef202035d7fda3d44ed9e65cb9b50b349766205ea63e24fc764c26ee49702dc836b427413b6eab33a95f77e0eba02375da3544ec7ec50997d46808267f42baa67ab44ed7d28aed67c781f8849c86f18abe4ef4c52612525d225ad2aa9335e7a7b6da33585bf1d6f4b592e853ea40d67ac1eb14af71ddc3b482026049b7146318c5b95051158bdcbe53a46329280273f235588625a9e726ff33c793ee859d082bd3151694f0f89028945fed400869570905306138e668650bff26a7f1b02a89654f0db86780258bce146720f3a14a80fc0cc503cfd3265ed272291cba4651cbdc0e4c3d54233b67a994ec94071e17b10fc9ad959c6863de13a3509103ece71197154f810b28aac71b810b982c5f69555cb0561bb3ea3b50ab5ddde324b7c4c19d2fb1972fa2a3b74e4a10354df1de5d49e35a79a8291ebf9b2fee7f6c67fa79058ee19ce729c060131d2af4fd861b510db865088016e153b3050a407f3d8754ac56616cd460a130f98c50babc8e9d575658cbed9d1d221d8b00651cc208058afa0b8310e85cc80a7ddb6eefaffc2e24f7a9bb7d886f1ab89ea8cd108b770f5b4ae139cacdff4a5753a2cb2a986ecd8d1a5672ae03184621fe4e08e210fdd2d3253ed57408c8d9f8a1727bf22ff4810deba87d44c8ebb40a3d7b1bb00640567909b401aeef254306c34412933e4a83fa0f614c494617c85cbd19061f9e8f1cc778453a8f749ef7c45c2ed2a362a8a9333c781fde05dcffc47a7280b957c8e75dec96b78878f9f2eafd40486538f0dc8a9e39944c785d1369c52172e5804b865177c45ed0e44dccb5bf2f1a5a6ab004e73e5fea79926bd0cea3a79e3a93618e933c9c954fa3a37395753930b1fafda91895b12c721f475c1efdab64105c874c130a5755b2daa8d82f4e39a095d9fa488902783718ef9ad1bb24e65ad1c63d9567cce61e87f6b5065a3e2929eb8d70e20aafa251934ecdde984a68e2f1955c91ea19b4e72a7d97c4eb0f85d5cf6bedbd8b12760395cc5b1f55fd90b9dc34f130ea2afad88ab917a9e66a61aaeffbb2946c79c6b23b161440637c1e1b19d7a79d489f52d347dad34711c3dcbc2a220054907b8b7999ef97cd1fc1b298a2a2864974c11208252cfa11882f9bb40d678d9d05956cf293512ee9c19b81fd6469576f48e8d1ef735fa2adda5059227711937d7cd7fd1b4f82fbc2b83b198759043ed751758359cd6da4fe4951974b66fa6156f5200164607de9b8bfb0fcbb561625f65e683200c49793afb5cd2f7ae0a9d5b3e1aea03873e66b1d68336534a935e6574c593d589b44017bd5c1442d51c1377a1cf7a020e84c220038eb037822945d2066bf8bf94383348cafd4ec43f15f965eba005dfa38c5f94d79dd0173418361c2652c4ca4014c6d0cda75a790a6c3b6c77e75d0c172c28e944d97def7b9bf023278373652ac0cd40851f94470874ec892d80e4205840504dd402b76863e2303983f840333d48bfc470146927868b43caa0f9fbe56af036f96627567684cf90fb8bc94437a27313e898aaf71de55d93720a664894be15e04e6b0692eaf21fa7ed279f8ecee91c9fd8f467f3010c54e58b8c3ee3cf83588622120379851b4cd506724796c55f7c137466dd480ac5c23d954a913415362dc572455ae454a2fa6092439b9c6a5632d84ad71aa96dc8574cd10d4143870757fda0eaa43bb958222f8d4f7e664e680ee7214a31420da84cb1fb486fae544dce469705c01619d9c14777395b892979c8276f4e656f3ff1923bf5f7da8e5dc75a76dd63ed1027b3b2512661992528744afbcb8384dc81d9c1addc6d13ca3b4ac70f33e04fb5c09c61d7fd5514c3109453fba7f7376747d22356e95a6c70e4c2d30e43d4f8e52825e83f163451592793fb1c708164ec42cd069b40ef372faab79aeb5d08e02aba14c8f89772c5c113ad6d4c635d718db58157d815a4a540b357f6686b29886a4aea35b1704d1423e1ea514af9eff46b03f7e40c146befa2e4a082743a62e493c56a59388c72c3b78637e7bbff6969afd01bf16b2fd8b8e0759dd5281b2d690b5445a2600ba3bb5515997af3c8d70a40faa3c1b6087b63274a8b953c88c22225637f931d6317e1d340abfe51dce3c8dd119ce2ef6c68a957288fa3e424ecdeaf16857b9189727cf047ea352b7e922e6735381b1a3e739024cfd994f4354e97e3df4227f08e61f1badbf04485f10658a42871c8f3df3f360f35c3990876a23d97f40af8fcad37b82ed38419ad85d44b05b3792b69bbf0a538118210d56c9d4333fa67820e086f4e56ad466e606c84f4cc9462702153efcefb9738e769f499ead904a0a266ec8e39eae699386d3d1a7e69c01345e61371e855938d850fdc6fedb294ae41bac012c82413f3f2f3488bda9195b8929b8c64d9e603e042d3824816f551d775c02bd3e042014622d81ea1455d3d8680b9020ef09b143345d436b4465d050acaa6bdd20dce09b342c0965c976d7e9c25811478f4935897280789c09e052abb292513246b7292ef97b1517837dbf7584d7826355e0c4c82ef4bd88cbedad2485170a98539c7c72774d0b2540bc64188c89bd4c05e44be5e66bfae8ca6d1844ac0aa6cfafcdb2c7ea527bb247fb12b663922516994b220407241a8c15df45c6b444b39f42ed0caa334e947ecea01e0e01b4999fc8495211cd2f03f5342e3dfdb1f314381cdca1b12f74ff7cb04bc79ed7b1a33347745c388eb07c760114d84ca408d1c2c6eeeb920ddd7f4b89be9c9c319e6a46769846ad9ba88646391849671d1224732c42b43c5fb2675a15156caf76d5d332685408b87f92245e70db3c999e57f8ee08e4cb6b836568e291631cb5a634c018363113cd8b3cfb6bdb6e542bc74b74e574cbcda6fea681b1b81da88f32a6412873c804b49388c3e136da31179c529214288ebf626180cbb29ada0c0e92697a3809ee35d5750a928a5a01ea0ac8e4f3cb615b002bca4909acedda0f333409b6a8ed4e3245f99bef2a4fc43a73bef2fb7e0e3d0e7347db10beec567b7f6d1a0d95bd9ca1bec8a6c67c091fb30685e80d6f9df290de3f41b8bbb3a4fec9566cea4745b2db16939930ce3bd515f85d5b054d88475c0bda4162217b89aeb02acd805495fae0b5827ca969cdcf76bd89480d51970bf68df2eea4a53d0febb1f76761c0d640586cc66e62c8f85144b571096a6c834dd44bbf2ecbc343ceee97e0570b77cbd8194313241a8d6f814684919f26ca4f9db682b84a6d591f1627ef86871d79e8a3d83d84f62c0c77325bb241a1b45e6580df458b80d45efcf3ea95b322aa279a250de6927100e51c66d354e259170e4afd76033d9977cdc7e34b2846d1e69aa3ba28c12c3b476e67b7b868fa73353a5cb2b228ed5ef9e33f85869e1186e487e5fe96b16a9b5a272eeee82fbc525cd913baa6052c51345e9e75a87570bb2189594c749b3f56d395d3c95392e2f4efd9452f95615b5ba1cb1786d18f655fb0ce96ea9525d003f817b37329f8c345bf4eddef37d1cf1bd719028622cfc2b325821db19f7ba70ca00ae07fb9e6ff0df889c884134f86796a680473bc76ad9a9b8b79ee7ce6b782d652615554de6fed04bc06d4c5f3bce8afa8171c83943ccd4938b296410aa9cd437b9fe052d0d9d2c078716da054ad7b430e9c20ed62cc48f3b0b12857949afb94e68e2d7779447ce82038a4fa8b21276704162160b5ae71a9a9836070e28496a2df502a8c404f5a13b6880185794ac46de8f1e1450c069cef43213e6d7d067b47545ed0a2bfaff20f11d798c13cb7c0ea1a29814602aca5d6a7d1116f97fb6bb8928afe32fc443b7e03448dcf4874939d2fb9dc54ec2be43f11509cc9bfc49e80cf4552cee392d321a1e4529a54b904ac7a88097663d64d949d90f9df9482f866cd027d9394816b029639427f392b781a70971c310ae20e3591e59c76dec0c768c1d3184d78891321861333118b865d15b14f08487316573211b30fe70bd8cf11715acd5278cf10a3bea32221d5269fefbda5878ebf5e45ddf2f580b1e0a1f19e3bceae34a3ca7159d3fbb125fee3b28405df5d8e0ae8d9c9cb25ea15711ee33faed09b2d18771b047053ee9db5f54c8451d475a20e5a17b14d88d1c8301264099a13358d0639bf96f600c542a812424348cb027f0497f9fe7b13e5732f99a389429bc1f11394787aa361f293dab8b93b52a80a819256b34183c7a99e35c1017c6de0eeb1da011d07809e703394b3ec37148ca8fea80e8692c19ee376cb7c6ed665c16753b526e43c32ef1f1bd40ace941cb2aaa1e66452427fea2ba8a75fab091612876a1e38d134474b6dc3ad6b9fd9b3f94809387112a9dd0ef897e794285fb66e4ac3eb393087579e873d8b645ca62407f0ab9d8095e7d9cbf75008d63c3906ddd2f69b194ad87279b308c445cc5a508f8a2c0227180d483053a945664be07935c3d10bb00bf16f8b34444fb3566a0bf04d0df57bae4bdaedb39497190f3556c51d19fd7353f41971cd0b7ecf145af628b364c841c6d99ded3d8eeaf3d12bedc1288585c112809dad72c3a1141ef622ca7cdcec4ad074305f961212c497d5e3da196ea4fa4b87e00ba79012d0914e225461a172ebf1c89cb49baccb27c31383893d463c2ebecbc5d4e4a2c3b54d0e38ac21b90c5e8a7dfd820d074d70f2dbc5bb1c3cf01c3d8153fcfe8e7768d801dc8904f5f2a77d0b8c91dea52209aec560413126372ae7adc81f0b6ae0d884be4298a59524ff80cadf0ed8b13804f9ee9a5376cc241d093b5d04bb43d01633c1c95082b975a7bd22f27878793d7bb7ab12a665cfc727bb4b30bceff3740683a3d17ab1a07db3e5aafece1ade5cbd1c6506e4cadcbf36a82c44c8714899b699bf1a88637b66f2c9a529b2c3875bf03b4d20e451bb4411dabaca8a03dc5cf031028da62da85bc971de37975f9f4d787cf24de9f236f7fa543f59c33b8da97a6c43227be219de69717263d78605fd8a262ef3bd56c1264c03834d1dff8cfd00fa49b9a18e46bb76633bb89227e609978cd67678572dcfab9405093fc8f92fa9fe8ea643ea110080bd1bb65c107c6d1516b5d8ec820abfb7948d8a91bccedd2219c6f6cb123ae369adf1cdaf44512d425acb64e3229acd232da5259af0681efec2a944ffefeb1cf80774e41f96e488f2f4e3d9e5252364cbefc358e8a3603e4eab29bb9362d37c3a0d0da615934e2d83c51fcd725a16454ca4389ea59efefb34277ab9ec678acd1c8bb2bd39db618a5816098fa94d3fd1207fd74b547ba1db55ae113048074599f588083968a4b4b85c0c84248c2454c3560c5910b455b2a3c58afd21f71767247e475082ab953b7e45f5524e9582465da05eebfbd6997e4f992b2f4f5a4db881c06c0a4d7d67bc1f89ea57781af84669ae280a0b005cb07592305274b13c07cd274cf89909791e90d7ba5677cbc703b3ac5b6a2bbc0458d9430b0f4e52040706b70b28e0a3d51edd8c334f4659e53ce3ea64c5818b52cf7a23b852634ae66282fed75974027c7a2fde03d72f33a0405017752da647fcc6586db16759d20ccd138705eb10887dc2b1bb9f8dde688f2e05b4cb06bf1f171dbcbd80c00711a3b064f5078878e0587d35072b00350cc416c71bffc577ba38cfe9e967af0126ee13f70d83e033a285c5d09334bca6c27ba71d4e8e328cd8a3824c3210daa9f7d9a57878468ee0bca12ef01190798b1a3ffc4228f214d449333111590d5e9085957caec71d2b76522de183abce98e283929bb0c3c32741a628e284cf4c42337dd292702d28e9a063b65193aa756b9b1eda37e107a249721fa66e0f566e236b042477cc24498bb8e115ecfca982a118f28991640a8502217a6d9af9a44964c5451f78eacb86ed401f0986db768170609ef253a239de19d036e78063c364888868eb4f10c2f1111fc102250502d7c51fe605aa6002b85e909bc48b02bcf7759d2e4d8c4208ae03b9c850bfd5234c496ca985aaeb6efd7a03c2916e8861f80379887ace64a8e5f198dbcc28c6d44d9c4ce7e529bc0ca89ee8dd376702f2b4a8b5c505884c3894d7888b7a620bd351be945398c0a85df348a5c5e1e617938bd8799b804ffcfcfb56b9466af445b4c140b745c255f69a6c79349a4c32a1cfd44aca486ff290fa160413f9fd7e16088e5304ff2428d209c36ddd3b0f651d636f423e24e3320371b8b03a12f390054ccade002aae2422e84fda2f007b3e7f929ceb911de67daabeded28e3995939401ab1ccfed6cb211e1fb8e9c187518c63524fef89127360f20a76c851bd7c98df9980922f9671015a120dd825ce4a4e670314d0d5923d733f64cbb2ae0954e47e2e5c9cdcb05f3f7a0d2b91111e5c1d641c654b3f12f543fbef36f58b32b80270d27e48eeb3d44c9987368972cd5938890eee583a4528daa243cb9d2ade01318176a1ec83400551d746c05257377d79dafe0e0d5e1dd9e0445983c947223a40ab65dcef9f373b469a651da47e74725e73d077ec8e6965439cee220736b156d504384abb47256734552ea69b7e3c8c6aefdfd39f684beedfd5addfe6a32c8563234d1c9b611a3e59682de7efc3f9cc9523a56e9d1beae5aacf5140103989c2fc295ad7a234caef29f09fb5e6c8108683702b27debefec2078b7bd8f30d63b3dbbe5a09ecc83aeafad2f8a7bca3eb97811049ac0b7062fe9bcfa42ee160a889461a9d8480ec0e816c7beda7fd032f960be7adeab3b9c8caec1322938b0c99fb87ac18e95726d3589a2bd914ee15614c8e2878ced75a91ccc2b267971f6206033d6c40842659c1870f6f464d0c4966ca4eafb8389cc5c979f83f208c3a8d5ea6a5ecf2902a3fdbdbe7d55b5e16bcd1e2d120c58a4eed2ff44c5a23af32a21eafe5274ae675bd202b1781b7b977bc458f31341cfd3a5fcdcfe4d16fcb1bbc71d36bb536bc0b41860ddba15cd57ac95a4ee5a80c2ac4628206b616561048263bf22c5ff297ba0589ea6596df46e90b8098d1606bbc0d8cb5c1374f38568380e479d9d93ec01835b218b3ed3b6017d4f146672bb16272407914d98677cbb03d85075738d915435a98ee133ada2bb344c77f03229762d1a06247fe9559683c493fbb80a63901fd4f30c9ced26ef9665f39a0cb7827a2303ac124d2cab1ad8efc8815588641a00990e4063113cc3ce6e48e056335808fff5bb61c61091b26e2409cc591941127f89df7548fd6e0a8fdbe87e72265b03bff47b848ce41d279cb386505bcb86e1512a82682e08f039accaaa3f1d821f8b8ea833e666c2bedb0bb1f41ce8b1f41b25704321c5dc7982e73cbee39979d9c8b07a7c1ad72f2455de5e6079621ff4397c95624d4c4e44f563a56fb85ce99d6693187bfcdbc7ef206e312f597f6ff9b4fd2ff8f234c3c873d66afc7ed2093f3fa336ca02f5d93830634cf50cec09e5dd3433f6de986aeab6a67dc425b6d72fd445846b305e31347b76ec7c9e7d26b0228e50bc1cadbb018cc32c211f40973cdf5a195e6c31f891c26d75cca0749b4b01b4d02309819702979098e58cca06f06ab16a7ed5b09ad5a57cb942e6bcc010428c38bce727881b00cfeeee52de91335e62b9b31c3c7e1ecf528d05502ba54c4e92d902da8bf41db79a83351565998c25dbedd2dd05c068fb2d08325c480095ba9da00320370fe0cdbbd9672169adeb1c5c013deb0c6f7824e796a75f927ba961c7da8c82de7a2a60552eb63cc2e7273ae33caa3e8c15def0aa967dab2c1235d47204ef60e8ac14ec39c36dc66272a09d368693d64e1a7d8548b501b9d64436f578cc0a91d7fa1ebcc0711a8a396a00f098dfc105b8fc4450ffc77fc1bcbfba6881b0130f04f78fa6199ca3e2ade1e464b2d54c3d9d36f3eb5f6c814e08db0ccc8a76b8fe0aeb85e22cc28c6b76c48ebc9e4c936c8cbfde80d50067932a70488eda1cc5418fdf0c1161aaa3389b47e3568e02efc6a8476a7a1c95edaf4cebc1693e46c57d739fe01def2e988f36c9a2d86089a8205a8f66a017ecd373c5fcf5c9a7677e5bb2564ef4187fd61ef230d5bedb92d1f3376296d6a1f738f8037c5425e256e2d54a054a277593a3698de56f2b439364c9a8ff736e74e993a741df893fd7fe589cb2097de9d4892cd0fcc27df14b77d7d6e2a4e32781d442a1e5c3ff5a487038e8bfe5c3aa06a8b44c53c4e3fea58e58c9d6d59e43f9c455e633b397bf3547d611b6e0fe3263b90fd73e503155d8135139d6ea9bceb09dfcbee6b68d518ef4833102308f05bdd9bf5e872d11da13a5cea7090c6215cac0eab1a07306d73731c952939fe242c1b2898b4ddc289d37ffe24a95ae4157389dc9d49c8b8f6244b2b55150a2b45e149a04005988b148c8820015d5117284e9fb3b9a927bfdbf90611f5345564eac866f1a22c833b2af3582bdbd6aaab9d683f4e45240ff7aa142329b53ecbe889501b987441c07c799f30585fe236fa0e10660b4c4bb9bc5daef4d047f0d4b9e69de3d5198d97343f303090520fd3832df14cf52cb583e3a6ae78a3e313981ecd011150c5f52546ab710b1d5064abd2d6e1a250b1e325e8dfd948e9b4635af7f821fb0f95e8ae8cdfdedee00b85f71430b41905ce800b2fd544a4f19afa98c9ba5019845343640308146e5ba8ddbe8db12775e516ccfaeef95d2ca8eb6b4b7c1fac90cb34a6f0fe87649fc06acc9d45139c4b2d0f4447a9589fca8468efd3465d36e01ae6c6a556df1417ce4da61366f3b4359e082c43f7832b46c64f52da8cd249d3c30163eb948199532daba5c35013f8ff5c0f246adba9cf79a03f0ef80038feb71211ee59bab93d725d11efff58b77a708bca225c6b40eb87bd468051d9028d183025c67c6b9b8e7c1907b8ed204326cc6ec953a385a8b13b532a145d74d78808b17360117c68bef7be55f3d8f9f1910d46be16b86c3f8d810235cee45e0945e111541407b8f3898c8ba21d73806765582bb4928b726323381f05acceeb205f0f65fe50fc70b016cdee488d459c4f6215b24f8e0f5e5dd2c0bab0a1b2adadddafbf6e10a9ff2ecb0478da5d73ee4691874bbf53af8dad139cfb2a97cc9f3b5be8edaf5602ae0f6b76fb911353420ad352d76693d5b827e4604f7d6ccc57556f35a9dd8a62b4676f062a3f9e7f764ca50b4bf98e15c5fed57208ec61e6aa52410365a32a65e31a3e93dede1b81ac0b921612618fcaf5137f0316924cb2ba91afbc3f088b3412f0ee0ea5c0f05c771a6c00f637c2ff4dae3abb303c073a4860960403e720c204e7f1fad39709caa27d337b3d8f572b435cdfee518661a1fab8e64c7aac1586b56135d92d04ee9f4d63bd516c0b8373841cfbc5d25c7e808eab1e4561e0a7a93ef7c731dabbdf6405a5f8b1bdd92a736942ca38c1eb349ee051910a27e10fa7b91ca5c36b8823aabef1560e81e719523f575bd954e8e7339e6bf5a9e8898e34a5044c255519bda83282a5114250c62d9ee4ddffa2f527888c3ac010c8901da01f4464842662a07192298eb8ceac5f06aacd9ec8c095d83b6fc4750ef763061710191f7f4c928bf0213922e17cfd6536e7b33fff955b81169749f1a24fb6bf3cfdf9b97156092585e526e78c3784ac03e78a6972122ec1f555c61ad2cff36ba604390421589e045a8d8b84c40507adf6880059aa38f437afa672929e0c3aab215180009ec6aac1beee8a69193f681eba3c8191780fbf1e20b8c2f1f62a7a115b98eb1e41499b8844d362555183c6e3eb8d203c692595ddf8712006d00f7be4fdc6b42c90b9d761a1a391f2804a517fbd31befa9c2c66238b965d8aaa705457bf331492926aa7fa412072f2a55d43b667c3572a716943480992cfbab67cba785ec4599643d24691e11e90ef8a5fe71226254ed1578e01342370d100a0b908d55a319bd39f03b5e4f8ef37e08f08132b861af9d7cfa2472173224eb4b1c38bccc3248538457c8053a530a8ae51adb598f00526cbfccc4c04a98af0306c82a11749d0aabbd1b6b792271985770eca1f4997a4ab0c6d8d5338515f50a40d8c8459d845a8b275a4ef9042e753d4f5db404563f4b071639572520742d9187634997d189dbfa0de42ef91b8763fbbb5b317d6ae28543b9d112125506f9e16be829ca297f0c1b3df5090a63763051609219350dfcf5fa94678abcd5be8ca8339671a17cd1c03ff374ae99f51bd1603a2fd9322b3e8e4cec10fa41f4cc756bc46f3a1ec5d0f301ed8ec67339f317579e66fb081581c28588ae07f415013c91c18fba3b17708d838dd92f789e061d1356d24ac02c0dd0d8d3ba44f89d5c8068e967ceb94889e3e9e87064450dbffd478324cf4270e13f38285755964f537853fc403042b298e68b8368f58259cd3b0ce9bb468b55bbcde7aa59f90b3b4629364c128dd11106a82541c474d47441defa9a7f9938c439b8a2997211d64c9642a607c27432e8c6ad1ddf089a30ec1f4509bc0c26b922cb9fd16c68cfa4cb6db18fe540352eaead0413a5469fbe9a65cda02c7ee85ee788a1fce28727b3a4e2eb0d320e1b568b040b8c4bb85aec536a096d12d0c2ad7adb391190e3a986aad53d1cc8640f6f42e1375e6a17673b6af547e90ec9d18f0c7e447f8109d111f3446b3c72ffbd4e54d4e95daf9d7b1e57a0a8dd50c5395789b2d50e58a0598d51e682997d6239a6235c1e3c1d0415cd0226df024a5d5a4c27010ab4488784b62d83eb2d66ff073183d34b028a101ff8c223eac5731199bdbb907c02cb845d3abd65008a26b44b5ac8195b12931140e5271483179949ecdbc3599abef333c0305bd4f7005d9bf6366428191c05f3399ce9027f82b5f5812e71a14ed8a77c4d0cd7a8b9084e4b95c997e3b4041a64a454ff9410ceba45f069ec5ac8f434dc2172e147c05c3c255a094cd68609be864c7187b95aa7c5ea772e295581975fdc8624c10bbb847b41986a1a18f6385930a229bda2ed9acf82e043662dbfb357c6bdc288f4f2f1dd9f77ca03ff6f0466b223fdd86e13ab702b56faa9a1d2124363f76b2e59dd73d62910cf27ca79b55bcd0c179e0a1eaaee09b3109ccfa525ba8aeb28c5c329b5aa16eb2b00aed95bf1f3d53ea74f7af1c0e152137d7690c7e59a8a7442e6505a20e556a8c6d0361215711ab69d2ecf02ca479a40cf4479b4d47c8e082d13e4a78a1fb250f3a00e2a7b03470a6e790f266847048d73c388ebbe0576a69a21867b50a56d3240b84eeb1050ce30f51df03facf240fc2737b75f20f8c1f0bfc00d985ab818a44c9c3b43c214af8684d774b88823520d9986bdc0030b48be3261e90744ad06447b62123fca6aea5c073ab635d7de7d15104668d5471c72af988a05676de3d14e2ff8b2dc947626ce4987912034bb469ec6d324f24d2e0ee99d784db08fe7cecf5b1a85aa0558531a752b115c0f2902828ced5ae075662a5715beb97eccaeccc5e2204c011cfcd0ff0a73b3f79512a78d65da5f2c448e41ef030c2b793a33b53233d8ca4caaed376b14f1439904c84c6a534eae9459c69948654fbe1094bcd09848c0bacbcc65289c9ad8b5acb7253c9e63d59047c13374ef98a28495f3dda9c917b0174726125df9f9242e258eb46ea183b467b38bd29773a06ae66edc46e5d3ebb7c30b5c5ba2d6980648505cb5769e37af64ecffc9c9fba4c3e1236ca722b7740ceb09f8f65017f45029694eb6ee9366a50715d36e507b1462eee151948956beb9ab25824611e58059e24bb288efb57bc0ae052fc7184c732962b75c3f599088e0b2145dee4b46cd260e50296eb3c4334b7da4757d30da6ee8a3dc04250ca2ec6158c4b39c02e002a30545b021a93569152362e60a4fce5e2b0adc835fb1fbc91e79c5d32e3bc4491944abefef218349fefe0615bb64ec6086ed398ab56074b98787878650a5d84ad8688c1a4815f5be54c546513d381a6e744de59086391621abc8062f4523f4a3ffe4e9355cbc8ba6455ca6044d64691c215b44bef2ef281d8a2074969fc411ce749b3f8de30346694764432f467d152053262610252f89f67f47204e3152dffa2b00d39b122377c1906cc63ad40f38e4bc1dc49a3606060517cb5453d0434545ea8e02870b859d2797991933fa02d40022fb9f4ec487b812a6b2c551656b6162d6ab1759e212cf2d42872a43e0e3ec372d24f37d0f7a0e6daf41a95c80bbff0e0bebe6488af7971bdee534414e3251f37d3ac799860e7f5fd4b5a093b152940a8b4bfd6a2994eeefb0eba935904531738fa657f40969208c7f7f2994a5ea02f7a8ec9d0dbc9c4cc21d7bdef28a753fb66440fd19f869495b0254b3fb5a76310280719d06aa424d6c656db36ca44013692b63648166333396415c843628f94abe19067717ddea9b9b8f4ec0f5173844da8ddb29f5b633229295c741d50b64d846a6919f798061e383158e3dcb2dc83e171a1a0db89c16000c92fb1e903789fbd75ee15ffdf1e465c35fff4364afbfaf0d82d4a5c1b64c31369c2a72b2f484b7b5c773234f066760f09a3b486519ec1804b14f5ff005528f40c6c140be2b27d7a87ad1cefee41d59701c540c5fe38cb3f408a28ae60fb1f489c352821e5f3eec3b983c7e84f070902cf36a5f0329dea20ce823e7d52cdd341faf293e4d3fca3b6b4f59a01d18d4fb3f0c8ecfbf3645782d48937c02f98d68dc1790220554f573186f890bc1748a4597574a351d09bbe7cbbb13eea38a36032b7520b900d31dfa05b628ef1c880c30eede5673e8ef87be4c7e1eb3e55b866e0a79dadd40aabd51e921593fb3eabad598bcd788bf04b2c9ab31c7e75d4e10c224a11e9cd4ba65f08e3cc21b01dc220f0f6f5ed04294f40b74bd946c45b5dcd841042204f87a0c9717aed7ab3d2a7e0cebbb3119690915dc9aca0012a7ef3130b7536e2e6ee58f192d5b0ac29fc0b596748b9540714395b971c789a720136ea2fa8be33155595b6d6d6772a90b580180f8ce6841253249b30222e85683a470d91e0ab8f4b70bc75d09e98134fd31aea8ec283a8f09de0a7ee68f366764003e7f76ae6ad0b9d7cd06599c9ef408b1dc97c52c82833c5317b09a78b3d65a375e56e4778f4b56e9b52f5b88efcba089e1a16d9a76bde1d1a2b4cbd5482a26a1aad7a7c7943776cf268f8881cfb7c09d80e120685fea9d962354b9b78278d0108992b8e7119a2e11882358e84b6a0ca60e7f372ea5a4288b6d51fb7484338e9e5e9c055cb4eb64f71eb6fa4a97ac46df0ef065bfd2fbc7191135d0314496d5d6f6d0d4ea9d15c51499160462b4659f6f67cb5066df22d47c1472e245c4bd146381a3aa6a735dc80d6eb96ee6631cd488ef3e049664085221c92cc74329a41edc4081a040d95204220ac4b61e7d42aa30e2a0e28860eaada6da05f6e0679e86e822c548782637334fabbe432b30e7dc3e7d8b0a66d3f8fb007fb6a188bf7533a02dd17b61429582066fa45ea122d13a8f2f60397ee7b184d750926b9fc28f8c4a6d27e452aa3b6a52f239f446ba9eb5507c1f3ab679afd9efd684f1845eed7a9da8cfdbba0bb96f3a2cac22efcad9e5dbf43cd628cc9c14b5ca529d572770bdd9507b3de01fc7ce78bd3b4d4fb9ca8ef177eb6d58ddd0812329467668e28bf966fb571cf5643c7faea3059afbc912ced37bbf97b964c2784b009f2928709a1825dcaf8ec062162887248ccd996342aec5b84102d5a6808ab3063cc6f35572a62e1d60575b5670401b140f2f797353a151969c240abcb8a20228c53ecca4065eb49f49c471e4955b3b000f70e0a4be99a45fd90938b70e66deacf1b407386deaf54dc40afcda5a6fc1ef220c125caa2b68bf22ebd66b1a2b164d048e9b91f043cd90b1eed4d2ef84b7a24dd553fc67acccf4008e53f6b294901957b12f5931b6ac2b113317a808dd0198c10ac51bce247a1bc793b8d6e96222823580bcf9d5e2f028cf6116b88784fa1477a281d1478ca9f35bcc636c3f7c5a281d85d147496f99f93eb35db15e8cc61b4d7efc7d2b79128fb569443dc1a64dc61fff640d6f6c766e0d12215657bfc6f62c4750b4505f78ac4e10a64a32fe686b7615b69c5c0ae92c0a22700f846c218276444d40cbb17ee03299ae07c477e25768117b896aad7714be3377a34a3d45c14dcb5d9f716d6a977369d902cbfce970841ffc4f5af51501f3884f3e731b1a35867322e79ccf47e886d3b51a4a280833144b7a5474a04d89b0964cd6901b48ca40eb973c2bf394752dc574425f982846bd873182f02d91024493f710e6c14c9b46c4a1c08594c76df400da0f2e01a8857bfb5a898582d00ae02214b6e6ef2c3454378e5c21489225b05db4ca9352e449d2f2eee4e5bdda8cc85296e8ac50286eae97a543630f61db0d222c49d757cc95473c494f063da26680a3b9513ee5830a644aad338cc6feaa84f8582bed672ae3304b0598e44f3afd3aeafce0814fcebc18e38b4355e87072329138114b99cbc3556467c8619dc2c96843a39cf5423fe3c5f823b705ca74b8e12b46a32ad5bb82ab18456b044bd7f221badef67c1bfa5b8b8486a96f32ee7604e8136b2d3b1463569c224fd5081938db3842338759a1e62d8975b51cb035efb896ecd57b988c9f98db4b8094598883b5774d6e1dee0ee66a529f4138a8b523d5f4f2ba775c60f15d6eae4df50db6d3903c251f32f6e456bfd982dc5d33e1a89a497b79b9dd5ab2b5bd0f546a63e5fd02c775124834fc825ca88a773ed052fb968884c1f2d86d71db0831c29fb86fc8f1f562babc4a55819fdc726bb5025497ff15f070fa2081c07cc1041d733fc712687c483b40499f62515e8b23b4cd6136430e1f8114dcf68937942d3cd88069bf34d093fd49edb71ae438b505b0db13f7fa85fc3c3f90a70a73c1eca04b893d396d5ce548a608b56aac3f461f3e1883c6b6145afade6c7d47486aff547ffa1e309464cd51cc8abe60b32dee670bf29f10c5d6c2f7b35403ccd563006149c698f491129f04805ab7acbd67fab2c9f2bbfe74ad21faa31db21bd92974f4ce58600d52c957c734a6dcfc42d1043c1063cac03d47808d022b21bcae03ecc905fab4299a5ea87db60ca89c75f3d0d2d6df536c884a9d4de24943b1010180183ff4cdecfdd7ff9241fd9a3f61ec02c3eec0649c7ef1927276019a35cc76d40bbae92d2ad134498f5c8291206df0f47f7da3b3add1c53e52f71b8b45a48358c0baf7b80207945d21a5a14b029f29c6c70cd2d0a7bdd7701ae00ec42ed4f9ca8876dbd0f5a4a714e3329225f7429d8bef3b3c99d5804b695a2a18d2241c265321802d0ca5994d49fd05fbc192997f3035d7f355d2f03040518916a39b8fff20832233f1f635301b10479b9428b107196bd671ad3c79b062bfbda63a66242763aea0c90d8f195331cf40e55788661425ecec129d767ab7dc62db50a9c6a84f3822163ddd55f8aaa6f9070b4fe71b9257f7fe4279840e06a57414ff1fd6f14506cb6ae73d3e82b04d2cf3a883b16f3c95d1a56046b6b242048c0990072ed398f2284a98ede53d156c3e4e8476ff4f68ec85cde5dca377c65af6f9b4c88d5821179841d8db743c4295d300d020c28541c4b5389179e5c20b3989843763fcc75f9d857f5e82e163c75dfe661eb532b4e5578532764f12ea9714994ca643d5d1029edc9baaf7000df0d511dac74852796846c1140f720ba2c0b81615470467bc1fbf81af2b421fa74f0a7b3097fa2f612a1c85999e5e965986774b936cebb08a1615d8f89af748212b572a633b99807a0de10d7eed93b476685f9203cf8e17dee0cb5b3badecc917645e8fe4fa6bfb31a59689760ed39378155441df9f182902d7939b78b6dc8a6cd193829dd2599a328ae56e931a69485f2420d909b73d9da4d84d927462421efa566b53b5228a0107d2fced97c73bee3e59fa9b712dd61f4a8197531e9224f95e5559bad5b270a3965b34fdf71ee4cb7b007dddc6e84e02a8610ddba6924b7a9ea6912e07d494c8523827b1894ba6bdb50e17f14c40cf92aa43dbd6a21fd9b01eb48157b87166b72d1c59ca7d6eb591d4bdb5a9bf96433a7e90c7bd05156dbb39fe5f73f3cea106fa4e084904765faae98450b1e28076b2fe166cc501fc13e348389cc00953170fb69d8d17ba463bd930a6b2fac7a720f214b0c0800f6303326f030a2d08d7d76b8d8702a916a057510362d2b2dd0484e9762b938f4b76836b77c4f7883706d9d1414ecff5c94e9cb8cf875985b3e26fa47e7ca633fcd83da2d6d97df527ce3ebc575aa9aef2890aa645f1ee506b4524da4930761a56a4536f3090473a97474df0f1ad17b5b879d385ba251ac5f9a5c64b7d929518719896832d579e0052db554ab831fff57550b9c2cbb03ab063933e05b1c0293f43044f9dbb623825ac23e12b1fc4e674f0098f1acfab472b1594b522515a416917106e990b8ce03c3c34091291c4e8a813ae3685f0ef1a98500f94308a1d756dae7cb2a5f6e658950c04e4c85a4be542a479914dc310bf9975cf4f322f5b52379eb11037ed306ebfa3655f630ce5fcc01e9f9c98166db4f5fe5e623db08419def7fa089d080841320d70aefef0bf2a4509736d4381f81925b79554291f956d85ec7962c30373e2240361958d5fedfd12f05eff5a5d06742e4678de72102b8c0549b00b2d21fe392bad65083ecc6012a327240bbb1ea6a11677274c30acb7e1e5af74a3b836b195388c735e1b1f97a0a6962a55d1d9f5c813896a7c0b1d1625af4bad9f9cd41c8c43d778dd373bf0f9e755d83c2a86a444a63c997ad0447c40f54632fd0670905deb60dba56a574d4649c51f7a314de94b19759f6c873753408ae49f71aa7b714329dc36bc202a6e976d140ff3529492b5c87246bd7fdf145c56e8408bde28f1f9d5e9feda39d0c5b952a073dff8627b7ea805f9d57b96121966ae616751879a27bc0cd9e7671d894aeb0148c3f6c6b94d53e09bc35b44753a9e4d775fb5eec89dd14b66cce6f65938355d55c0c5a3ed5d2d6ba02bf1defc8d29f330871c5200e9ff49c4ab5f0ff17159cc96ecfbca8520672cf8c917a4b5f3c632e862609f695b774ef219ff7bda407a08f15b0f75b81247dba1dc1dec5cb247d8dfebc17f17520556ada65aa06d490ad59fb8e6c49a7d9f7736fa4af0afe222686b141fa0158aea41ad1e8706f3fc1692bc32e2157c2a4e200350c06651d176e9f0fa304d0cc72a4931a550b6587f31c7c54ed61ce0e010efa047a58c50b2a57fe8b70e481b387e37da9976ac9850bf24c901ffce85072cf5865dac2a908f8b882047795b8bb78919c2607f8dc3c5a718887f70d08520dec343fb623efcb9f448c1550f07c1ba075b910c8f916b65450faaec150dc6c2d3cfa2b56776082c831e885cdcb5b69235d4497e65fab67bf4ff8839ed58004fdae901f09aa887e147bdfbf5d65c65b1999084ba0f27058139ea1e60c0d37cd68948194b7c0e8be9f585c64bc6c5100ce9b7a840c1b06714eb6f7f4e2cacf54326a33a49d6480b3742d5e3554f47ff171e187c51eb6aa0976ed496cdd87ed71aa3598c8c8691360e181bb2247e3dc863f5cad877b7a5eb97135b53dd7176d7dd3006d41744b58b861f889eb4468750ee73278ef2b21ca1d5950073c1e926da58250b7aa7d6478c4c784f0bed51d158f0ebe6fe26330b56c7d07eb6809de72265587175e8cb17e3ce22a05ba4500e543f3fce8e5685017928857d4793454fc5166c922f2cc327b8faa5825437b6d4addab460aee7116537f3cc418d47155f41b21132578edc403ea7f62d4e58567f007ff4c61ab1cb90d0cfaa09a8277a97e45a8b6e78b162a717221af6fb13d515431aaa4a5f1ae85296165a6ac3363d61740b3ab36e1edc34e8e73a022d724bdcd3a980ffab6e1de5e3e8e1ffa931f263d5af061e1e56a83d503791bfaf1c2711d1ee8280d94c371afa5baff85b174653d6c875dc2a9e609f4484b3cb16bf99bf630eea650ee31b31e9bac271579a3eca32efc7bf5eb044ea4589c080ec20ee8aadb75cb7bceb7cc6890a3647ab4a28236f08a7d7fd1d82f40b82a5c231143f6261e17307c6095423d57bbca23bcbc7c95d3a72d2c35b76f67af91ebff4e8dba568c277e13978078944ba48ed8b8e1758a17236f7c5dc809936a37e564f8c8d9d732019dc49802a5ac3cc039dc8c795acf1523141dce6eb6e9aa19fb4f72852806595ad795a88ecb557e5e7d3ec0b85d692981b1d577d4c1668b822efd5c3fadd2f4e82fa47e701e65100a11d776c292f27656edecd981e89245be302bfa8e5e81f18bdd1cba40a5ec19656b02e502fc18be709cc1699f1afc16ca9353829eadd98066242e4f88382bb879148e9da0dbe338fb7d083b6fbe66f3c816a3b20ff266889bec9a43932dc638867f85e655f1384c923de503f4c1af2afc68a0a1fbc2297da0d7ea3587929a9d2838c8e09a535aa4b76d6a054e267e3d9132bbcc60447606306dd0ced5c386721e0403edb3d8dc2b64d09f9a239d512b04e0d6b3814196502292a118021364eced07821573f4cf3da7d6ba94ac75a1087c6e7a78dcfe2f8b87741cf156de9d449a58e552df95b6cef5716428b37354f98f62023887376ab4824a55d27b8852181028d24bb517f0fcf60695aa465575b7d16adc11954f650496f52d24cdc67593ac11ca4291676f48fd23a342dd4b685da3332ab5f4991be8a6c6e89c9df6ca31467d49139b90cce8c4d4c06ea3b35ce854f143f1c03bb7596f7462b25f9c8262d49ad7dec663ab49a4ae0fb0991d0a35b6dccb1f2e7dd5be6ab4a33f302331fc2b2798cdde9b71596d2319478362bf842471b7ff025bed5e28ee6c509cf363e15bc4a846241b62a212c39e895aa38478592d1516ee7b5b95c1be73ec1f5a1285a9a09879cfb76eada1f960f23f59eafc89555a69a21babf1e5978eec61b5c6a626d0c22865e14d30c6b72ccfb049de909c0e5893bad84ba0b090495ebb2e3f0ab28a5ca4d2ca62b40030a16c9dffa9136916adea9c9d1ceae7038e2ad8d831a2734b87f0d7b2f6f4358785e9723027b308fb39385b8e4763e6e67fc9b5c8ab22928039684407cc98a5e7d37803e6f4c1acfed4bb1c37e5cc1963d3bf4c743c06633c68fc7dec89c9e05e61073b5a5d4e9398ddc8c7e0bddd5f35923c931b3d44372de55da5b9506a1ce657a24645f93b58010077e1688aa9cd85404b7c4c61971ca5ed1277364535c2de422f01e082a74240677c8e6289f66e51350561b86de8f2aa2441913567d00c7aefb18b124e53ebf057edda8d7f188b653e06ca313c8ad9d723b8fb32f1a27bedf5cbfb6e1186c367a54d0fd303d7bf0f4898164f49652ac71e0b38f0052b202d93218592a76a96372ce426b1b60e2959267c59fb08dacb26e24d24627e63b7bcf632f6d3d75a08efa32707fb9a2bced37b22e81c2d9d97434ce3a6a7b0ba434fcfd6017949e25e117db16365681a49d5cc4fd7363f520380e6a37518eed6759fa062b811c833dda6528674c0ab7c2be5159a62e8c2abede6270149c965c03a01456f581803ae4773547491a52c8b295504bf3492ffe5bb220a43e1cb6001fbc75948f49ce875b3e055c1fa2d496914d7f49e98e7a1678b2351aba528ff14a2252a6c753bffb0a044f76e813b84b0538fa1060144887e98976c9eebc5f89bd03a3b4e04b68088230889623d728f9096b1b29f97066974153456f15f5fe6aa040ad4df7ea73fe6d737acb29cd20e0b87b158d053836670a23b31c6fb27fc13df39f906dc7a7289fba17c1e3b5d0de847b97058c6799d409d78a93f441be76eb8d0719639cbe3bedabba07ed830c55704aa1054e6c08b7b26be44bf6345fd8bac2094d383bb1863e625c538050df18cdc375352f06e6665313d6789299d5692393fd1d468ffa8889a866fe5a6f9aab8288b89f68889896e146a7427c5037697a0eda944a3f4cdd0d98c308fa41d397ca52fbc7d653a02f5b2ea7f08e00831546719dd025a379c9fb87fa5f1eada8d64c1c3c8e43ff861a331588160a9cbf19027d095e9d4d98f8aa358f23ddd8b598aa89ae982d8431b853aaeb7ef0d859cc9d62c7e864e4f771efb18144d34e1df75abc740e2f84820a387f1042f21e3985d25cf354388f0b6632d949cb9caac901631b9318d1e92e7f5caa3842bb485ad398d724f584b910b4b985c743128953147c49ed9329acf4cecbc7ce2673590d9263be40a25f640c19d3df2d27d04cf07aa2b37eeb8a6cc08ca470b6385b85ba5be7a19702608ea502acddcd01486ffbfdfcd5de33045c3656954c9ba1be85f4f2060fc9faf57042bebbb7633e167f2eb34e47e293714a51a3341ad46252a6f6cd9ef476d3de991030cee72ddffcbc953b68367e287373fcdd7cbf2ac1b5a22d8847abe6ff29a7577d09f8bb8c0becb6fbb2220726fdf21c3deafb67ff65fe7ce1f4e3867c8a6a818eadc00965592df155e94bac654d4c3acc5cc779abc3dfac21c553dab8f72515a061bfde0dc546817b04189831efcb38affc2f17725eb6c234247ef9672a1e89c83791321e0852e196ae8094afa8fa1f5152949fa0f8bf1f9f465ca3a994d33f7b8c825872c8dd086d3b38f8d1f7874ed0166668a903049ff5ae76fd66343d5d093fbeea8e8036d7688ea6cac5bef200826943caed7f0811df30412c11fbca212782af7d19c8e913e45b6b7b46888e1997ba42e8592dc03d6e03f03078b42aedffd213bfcb1f592e4dc31725c33e68b7bf48057fea93548e7bc4f65debc9936efbbdd883e40e025bf7441c6e357b4669b008202f61ded44344c7d0c8b5e9c9068beb6537a4c2b2c3a7b49ecaed3414fc8d9a9609b38f623b0d14f1533e27264ffacff7aeea69e0339abde8a9b82e4564bcb9d90a9251a35217dce79afea1f3f4a311340915afa33a7d4a5bcf6d2feb0c2437cfe7d4a5f0d938dc078240d38a3d9b7fed5124ccac30d29f4b09ee7c0ce7ca317a3e1cc91623755f112d9fb510669083a3f20341fa0ea2ba4857485f7ebc7069755aa734ca69c14360906d9164d84c5fa62029db6416cf029f0e7862ad9d025cfb6d1f3f11bdda828310ce5ac863dadcb615b3b931a364b0d34d101d9134bababbbb8273b3e7f2b9d411f2702aa5b6208df89b9317e2d9541c42e2ee339f4988a49fd10310c3b3562775608f19145b66cb5c34046054878edecde7831cf2d8ba59f37467a525620f6c728434484dbbb32cca6cca8fafcda45078c2d3f2b3f27bba120fecf4dc575610d4229a0acc7f9498cca52a28105ceb7817f358f7db4582d9aaccddd98f5638ec5b1e45cbc7a86ba177178e9ac0d22d536df35e5d983e29fd24e654a1e196ccb60ea7c46efc720b94b39d0f86a0fe526a68b8cfb085d8c81f5f8dca0aa9efadc06858e3460e8e18cfd92f6dd29037017899a9b7cbe2a88f623383910d92d5c36579180ebdc884f95ca153a81e0e27eebfdb6315bf4bd2e9fe78202ef21cd02d1de1283fa8b8f07b76dd4012f09cf85d141978e4c8b0ca5c1ed57f5097f1e580b71a9ebd63bc907d847a4f233f60ec5aebfb7d059840d6d6ce2ecda2975ae2459ad4765098b69d0040c23db036a54ec11ff7cd04a06d50dfa6fe90dba93b3a37e47d1dc3feaa34047122dc329d769ecd06878ae8c1e912f0b9773e1fd2138ae19c70d3dcae23265eb67005e042d415aac143326cdb63c3e510632e5ad72a23fc46b8806810b02c1ae90c6054cd09b4521e61b7a606ce456d8ecea77d1064835b88ae6eeba25c81c943a21b365d98dcf830b070d590f74d5c20098ad43b984220bba0196bdfb4bc9beeaa1088ba4d307ce6675b8f1f3b96d9b940b82cab9f7f592673bcd4b6b4291ad1269c710d9b8b0abc45ec7ac7a85a2560a8f3494432a8bcec00a73fd92046ec9f35b22df7c065b2843bd1a0c3284c47b7c1d47437a70136e0f2040bfcb761f6f148eca114cc925b37fac8ec002a44d804f0c470fa8498ade2733158ab1b913c037c87cfb4b92292f790281b4609bdadee8b8c8fc23ebc390870bc157e6cf8e17303148b2a60591679d76f096378cba85a15f07bf357a085b825fae9737804adf64e537426242861c1eafc08e3eac8380ea3898d9d0a553f53374270a4be45a7fdc088d3f8923ff4f1779cdd4c2ee98926b855985b13d1fbcbfa42eacbfefcbdefcc464e88f56aefafd74314526e408b3b23a434c741706dbd60926e0db4f99b7d8b4b8b5e2e2e2757c9bef8567be60500096dd4ddd53b0be674f347f5d224f569a8cfeccaf9e80dc5125931b737873682cfc43a62704b91964ed8096c527255283c3fd0a6b8f6bcb963380a91e2b085e8dacb81e6577e54288f8be899b6b1f0c2a21672ff176dd1574d6bdc98ecc1848b122994e7a6edfc8d29d62d160c796fd3e5fe91772476be0d676724f5a27007862e0764ef792c8e8598a79ff4157d57e80756e57e29a5cd64ddc68ebd43255e6f8c3cdf60f548f9f98c719bd7d8cace6fb37df13903322e538eb19a8ce3e30a37e7341e7e8c511a27ef302814aff8ff40aa7f1e8d96f10cb4991815fa9ab3bcf7cfda0c03adb5e878ecd99f3f40d31812b34e8034a5bdb6dddee350734a9f6ca88648f63aea30722ba410a8cac1f7a58ba319b1c3b04887e23a85c2ac01397ccab62f1102c705ee10bb878d86b66198d5550233ff8f2ed7f3e22d48bd800cbb600a97fd1bc4adc162050330f7b1942b6f0b499d8e423d03e8faba442407dd8ee0872cdcbfc15e2733dbdbcfcf033160c6c9f6384915ddbc2284d5aa34f025ec1658b221e9efb94240b3ab170e633ad0eae39ee9ea6cc1898fc0ddc662319c322ef2d0cd714b8131420ea6c8cbb1c94a49cc877d88913a9a084567aec4fdc0138b601a8a7db0d8190964b867da451ad845da51ff99fa41493aa927000cd5132b0ab93bad88a3e68cb42430c0e3d5ec93bd91f43b260f983936f65cd1a9b4fb1a87ec8ba3b1ca85166cb9e1a4ed2a5d15690d94e798e1ed24f433dcae76d72e5537f6563046e38ce709ee8b2f600e89583feb492df6f42aa17bc3cebb99cf97832c72602813a5067296e434506ae063fd5fb0b731de2e367a8d130615c9ae86f884fc60e89ec8c8191740f861cbe56dcdf5c21256106951f651ecf6f1f34b57ff1171b763c1ad4ce936068752926c5a4845f048c35a9ed64adce76fb7a34ecb35d5a12c05987cb24718576a6f9b78268f6090c4e1a85c954b504bef34f75227f67b8b06801201887692945936b93851fae203fd54d1e11f7c24334c5c50e5f79cbc3d0dfe7a8be447031e4d00526ee6903409191faaf0e4cabe006547bb64060fb846a21a120d926fe4744941e8034d318e46087b91129173cd765d36917878797c0d15837fbda0fe2206d8ec71b265ddbe4bce9bbbe9387475ec2f2ef1302f060c48db264394fa80ef270c2c09da2d995d8a00a4a500051ecfd06d6ee59f16cf4c4308562021ba1091b63506e4d2496cb0f8274e49b96b4d120e815bd508efe975fc988638e3717a514a238e8b95ec5d6ccf0abeaaac310015763c49e49f7b1886b81f46e1534fbc28ca796b233e820b4fc0e6724bd8614055de6ec312dcd1760164ba2690ff3b61785755c603f7e8b15ccc2d7a5585124895786f69755437f6aa29c3eac0666c1699376c0b467a1e1ebf175c07b3ba2a2cd3f78910b9bb6a564d84c1e2782c73a5422c980b68d1e6dc016f0ef1156bcb41782453e4ca77b2ad3bc2bcc1596fd3ed4b0d6f16243c14186b4ca764df572edf2c3115afb12f730666ca0071549d07a2ca3ad2c20a1fbcccfdd1a6641edb2d294d819b292a5e40404a11af20b65ef638eefe150b9bf5bcc73317a6d60e410417724113a629a94f231d15aff1caf940f023b08456a3b4e3f780d08d93bdc58144dd3a3afc3df3f96adaa68d6a4a0005c1ccdf1163fceed06346eb3e622bf2d62388561442d825d597af6ae31726c156a8121ac9dd171d390e74603481852042909bf596f7187b76cf6666da36c899ba466965e802722751f6997754bc2f372a032a09985ad2428bbaaf6d81ee99c9bef3019c6582974926721629d4321dd289fa89383fca693a5d4bdc60555225f11566392ee259c6546bef9ac1e26b8241ad9846efcda391e160b99160b5157407f2e396e97bd163c9baf30a45c887cd6a4e1735a675374dcfd364c1ad5eb078d511a9bf493f7d54089d36915d74a6bbeda99c742e9f034b5e5340b4f5e791b6c987368b5ca6c019733258df6f9f3159d2a298a5c1f76e51f152558c1e5da90deaa4de403f20ee8f1ca479ad61394fc810817d97f9cf52eff06c2e037e3cf147f783ca5a20399350d48e50ed349958706f4bebe6b9db4175efa1016997b88b4b7f7628c6a42c62377d4ebe88c84f9401474afd9b490b760096ecd5d7fe9038a3eaebe08913395cad829669cfe05120df11339d3ffa978aed2e7ef211f05e2d26e420b6ca07f01322c7052a441b5aa24f1223c9a8def535b541e61b3f160ad3494afcd2f092de90a929eb9bab47a078009c9a53da0a2bd6527e73d000cc5431020f15bdd85ad5f5d2129ced3eb02d40804faa7fe885720d8dda80369d5edb5b5d45d553aaab4b34857914e13829bf64654c9261966fd63dbb691494363e6017c3f98d1b15e5edeece10d8040b4c6778c09a43c6e7e1fa7aaf3628aceed347072e5ff219fbbdb6eb9a59432a514520879087c087eea9ce0a6a445c1f6adb646981096deed8f665b8584a6f16f42d5c27611b2c216bab86d04f903a6f17fa24a61a908314125b1e812854419e1524b1eeca0422f5819fe42dc702e41d29420694f6cc892c6b54ad2685325699226699246a9a449daf4991e52b22e30d8d662adb6baaaab6d1549b5aeeacae3f16c9e0d06f33cdf30b7f1ab43678b1677f7f73c1b0cb66d2fe29a2f01a102583a37ae7a3e9dd753708dff9848645c86f4b819bddcb8eaf974de83de4396fd96224c11c66d2d13c6a50dd63a302947f877115edf3dabd94aa705defbafc0fb929c9070c9f361618844d28dccdec85a4bc39ab061c3fca3542ad7612b58d36030180834ea713344448406079124e9d30e188c460d56b163e4c8dd1d6bbfdad5adae358abcdbad180a75b1eb6a5723778cddeeeddddeddd55ae337e21a04704d02c09273e7849db9e1c80bda3a49b86646847b708c3f68731eaef3628c317a3778191ff24e172758c162dc7edfaaac51322b4c74efdf5418879947f7856cc5ed77cf732d2e75ad2e4d66e0df4bbe3086cbc46d3c149740385c86351249b09d4885091eb3f6cecfc8097b03267e2d73900ad3f803a15e71e71a7f990c060624ea71e3a00f0789d0502409d79c741c1cc956359e2063c7c8919bdb4837ec155dd1155d5b6f5bdc6273fb8e91232a2837d880ea27351d39778cd0aeebbaaeebba91c7e3f178b66ddb647c61258dab854bdbfb47572475ef1f5f481cece872c51797e8f58f57b024e152bf0cf3d35cb3b9a060d9c4262cae7b32f8ddbe245ce31f4534ae112e11e13ddce0bad7180cd40ea9ee1fe216a554a107a771b1ca42e5613c798d0ab1f5459683f20b232b028169fca7a822ba8ca476767676768cec189131992756d7bf8b1c63c766ee1be6888a27af79cd6bb4298d3436b7cf9b799363e40608f8602f1836e98d83a20c6d05d7f8b37f77715d8beb322e39b3d74d298d1ea5f1f361c2865e9bdfb2ce433d2c8049fe8ab02b768aeb2fb6c277380be622ca261e06cc654e664544b93e856bfc5532559b1ec60e5f1897d81f423737b55a4be9cd4d942b1959a462c7c8919b6541aaa591d28d6e3146598c41e246dbb62348dc464712f7bfa1530da54f5bfd12e0b569c252080a2023043861fb23c035fe096892049ac63f2626f46cdbb66dfd59c28a98b01bd3606ef464700ae4806b74b0b0bdd1205ce347e2916d6b7074c453b64ac739d8bde5e06f44b02d16f58a25c44a2592a12896bd8f8a0de5f57fc5183932f39c71048140111465deea6696f10517da8936ce628db19014f6891a0fae472aaec71d5c7fa70293dce5059760de7594f418a62937de755037a61b0f836ab2f12124ec0daedb8087507c018b715d72b1509c146b4770c913ad78ace2510947177089bebf6bd13b3c0b2ecd8f43c47c1881b81e6770ddb770ddade05db8ac068daf26f385f5da982fb432be6abfb0ba565416492d93c9643299cc23eaaed55c2a95ab93459290982c268bc9e88b40a2be5d8ba423227655aaa642f5e3944512334f8f3fd0c1ee641de8eab8fe11159b38a97a17772e1e995cef40d1e7274e0ff42010687aa0771b50bc71e32b358dff87f6c37ae383e223c2f5f7a468015ce3ae831376def0e6d33961411fde88e6ed3e9f8e54804471072deb9aace6aa2e14eabaeafdb82e8b3c445a27eae1b2b26871e102e3bafb8c2d01d11ea66c4597c4e80ed7fdc3d053d09c5630cdcca24e2eba2f9c604c19d7b84c76c5f0316ebb7a4b1458b82d8bb55a3b57aa4872ad56b550c85a97aa5659fc0212c51d8822155ce36f43f4825445528c4552e401d3f8fbcb5452ace213abf88395cb7cc7bd10f90b2e85eb1f12312b20f4285c7f1b9ec5f597ae488a3d341598c61f89d00d6c105f1012030b4078e0b0b0f2146b2e03c92c16f1e43b91343defef5dfc0ac699a0f7772bb8e4bdbf37a171b870a973a7fde876f7bc9bb255e34857f3983299ecce952c9298a5cb5ee9f23dd0099826922ec034fdcc85aac4f60c9a50b5e84700d3e8d802f37e25ca0f3d97305f38616044de4724eb47048e6630c10e07db656e13c618ae749ec700f7f1c3217ec4c1f3ce3870384cc1c1f3f1dbc46e33677ab673eff992f873df4cbf2989e7e337d3df8d0ea6d932a1feacc5c19f7b8f28b6e787c4f73cfdbcfe84e8bc203ee7118db866bec80b2b67cd01ba27830eb74e2bec0cf1bbc7803ff73fba796edbf462bf0621edcdccd03df743bae7be2453b64f12bffbb66f48f749fcbbfeda0bae9913fc4449b0f2c38f1f2b9d5e417e118b11c6cccc393723b0935fd8f62f5ac618ed17b20893524ae78c94ce58a3284e4ae383925239279d944e49279d91718a413f4797e3e278afdeff289fd2b05e19075d7a3f64cbfa23ca3869d41fffe3231cf4c3f81f3f5aa07f04618eebf30bdf47a5df0d78e76de639a2dfccfce49df7521d599df6ec4bb7377d79530e02731888d1859629877e620b2b35d394fe20b46ea279bcac38d8d14fc77d3892405ef711b5c79670a471fc8340f3f8b07c68cbba4262e66a24a46be63595758544fdbad63ce23b124dea4d6e5bddaa9452d64e344d94f2bb691e1df7e99eb0db13f673fd76338a10c491f02f1d9d73e33e54e4ad22b8a6ce561e0e085f5a1f99f191c6e9562f41eb86c25a1de9a6c56677773a2708052466abd5aa6e72dbea5665e5a4a7fb745252295b5cd33070358e5361239ae8ee09dbdd2f36ec58fdd04f9d0bf11c36b0f2b7207caba856d8a6545ee148ec0bf300c24a2965f7f1401f2ae26e75a249bd45d3cc20d038fd650b246efcb8ea2f34004dac5fd07b272f3fb8c2085b708107ea09d1017da148131f86289c564ed8b0b31076166ef4d30d0f3c73c2e9e674bae9d837ce830eeab43ac9205c43e794f5930506b0894dde924dfc1fef47e5368e9bdcdc56ab6debb62ae20b12b1e9745af9670389dce48992f4922378809972682780f083cbe4bd114d0a1203a05bb17d4d206f2b92dc89477151e1aaa8135127073f2cb6d5dddd5d02a6f130da861162300e8ee6e18fa3c7781fdedf112c4edb5fc138f1f4a526340ec7a5fa56d5ee9faead8a8d53a479f8cf4e32ff22dea73fedf1ccd37d8ee3681c1dccc37b1cf7f3f7e339734d7b4baea91f19e3c74353b85d889c71ce3a6b8c31566baddbb6515a2bf56a6dea0109bdc0355d802fd4c5ce90f7fe5c158d22c9fb66601a7fcf62b409a92159d56ab55a0d0912d0ac999bc66ddbe846bbd25ab7ba511a29cb91ec1163947d7a60437f5db7c1e23630b7992f63acb53d5c953b2cf921e435aeb919f5b8d9116385abc562514ae98e118ee3385624b1228d1cc755aed46bdc7024bf27d890778cd8e047dc66bee7312e51fdb07212259627386d8a5415ab2b602f07a5d36f918dcb5e047c87209114a35061d5eae08dcc11b13833839b629e394cc0e502b00da54c30e81fe2b83ce399bf654cb04d2bc1b4e536cd9af14516ad3355eb9c73ce39e79cb3d2a9044bb94b348dcf280cfafb84c2ce26521e45b3454534b138a8258c1dfdd01a2362138d11b949cbf5a781f58471e6a6985f718ca84d51c4b7e3e68d11b1a9ebbacf526b4c5f4f65c399e2b2d5ba6d33156933254f0e42e192d499a82e369c29192a6eb7613bd89952c2b2f763a6666a862849dc828242cd9443ecc1cb04c31022e86d94810fa8dbac3a6d0dd1ad6198a908fa60449115599755ad0dd56835b06e54b061b35256dcc609931a9592af48aaf1345054505166111316036b2ad11525b27c60c39992f9195fe39b9941e667fc4c7f43647ec6cc901a1ffa1a1f7adaece74b13c988362eac77e04c7f8dee97097d8d2f898d0f7d1200fc8cd7c144433403fa175e07538c6806d4d9108db62b581a2ff322206c979a9981c6c73c0664bec6338e1a49313ff318a8f1386afcccd7781c684c1c1f80c7c0cc0be0c3c1c6cf7c003e1ca60cb1f1338f03001e47674394d3e30a36e66bfcccf63a98a82bd208d1a1b0176d459a0304615de69dc6e733f38ee3dd0380884d315f030038be991a2aba7d3313a8bfbd0e37462aa22219dfa21bfccaf8220d9a074c970bf3531416b9309f13ad28aa60bef8648698bff14362fe068d2fe6852f89ccc739d113cd10b1292462538c884d34ba4fc83fb1a794dbd0bcc45a9c39e82e51110cae710114d7b7309b8856e825b8cf0d67ab8b619022184cd3c91b9b0a60cc142db2680e7adcf9c2ced4e748e31aff2e78c76bf1e435afc51d445664c52cd189e8839899a2551c74eaa22d185e60e387d479e0128dc6cac2c1808511f31d644b58a99adb8491b5a2d1bca4669309e54b644d9f59668a6be60ff38a0c08312ab028b10897933ba33839588b446898a9ae61a68a843cf3d08ce7be980f41b7f14d554ccb66b4ac4616685441e6eb2d66cc142d66be68b3fbccd46471892fbde16c4d29b11559363eb40c7470816052a4451a6f9cc8126388248e60d03fb23c92a612ce510c26aebfcf17d7b87baa65911669916524922218318c9d1d76967891365373ced93595a231900ed663f0325fccddd2255b9265ebd6db16b7d8deea768264b15eac178b158233078d98e233b789dff216d7fa28e30b1bca4e4996cb6da4152e6ddf754ad45056556ca122595226a7b82eafc89914b11536942c09939fef1e1472ff96b0de9a7ddff711c99688e5921549202859208b6e3af849bb9ab41acb8a8291d4bd7f1a003bb0d3e2d545f4642092011aa41467c9c0dac2b66ab74593ec275b9dcf7c06f35d0085b2e059afc2071cc00de34b1ee086f1d53188e8729b3006115bad2a31462b3706c125d6d7856507a713969382d6ba6ddbb67d88648049f1ca4bf67ac5dc30be5876854932ca8bf5928105856be2a64567b9fe339e39a145b9e2379e20595c23a3482b5255a0a638edc82bb8c6d5b1497f506cc8b4193edf3d0628066ca481c27e8e965198c65f52b92e81705d5eb9ee4956a758ed4a75aa53f3c5f9278339e7fc7c3e1f4ae9a4f3f3f97c4694d239e7ece1a4fe7c54bbd13c1ff77d2fc4396984b1c562b0d7bffe35e7dce6165f91f4953a6ccb84f29a5aaecf2ad79fce6df3bef9482a116167fdaad5da5048b6c00b03171b53602b08cbde6784f55fd5d5b6dabef0b7554c4d1516b799b05904977670295eff199bb348ea3e04044ba5ce28dcdc895ed8ed89839b152cf7d2003eb0fe7c398a2e5c6edb4c96db843175533eb5d84272d30da7943bc60d638b4b7de327aa41c77d463f380f88df6d9b1f3d2022bf51e55dc04d311505dbf160d914bf7faedc26fecbd5625d8f22d912c9c086b235a16cb096dbc46f7be2a02792810db727dbebfa6f5ad8fe30a656a9a852a55253958ae08d2c95c72a5315695cc776cb411f4f1c74c6e2e9e964aa3a6f015c5322029ddee7864bf1e30f31753363aa4a3855b2255b32bb4d617bf27d7860a76a968a3207a74a0a9646beb6205d30933298491acca4103369c42c0bd7bfb1c0a54d62c134fe52b8fef10bab85eb63cc18bcfcfb0726c9163321f55442515a45b43ad58598daa61853750256019b80d4af2879c0530906e36a3261c4759eaf364210aea726cc55515485a52fd9ba79d5dc6e5dffbe3245ac8a9815b1d754bda66aaaa6aad61d23071dec566db1cd0710a5b3810c0b103b302b56acc41839b2a7eb4fcf6284c42e0a892410308dff16d73fecb8e6b8c8c58e8989d93eb5562bf7484ecdea2aad9344d55250fe95db6a48786b8ee3aac771713aa83f346a95c2073a34a4c43123140242103233ac9db36eb14306084a2b5be48832bc1ff17bda281f3737a36ddb1a4581202f24923acfbbbf17b93d90cc2259d56a35e7768c7adcf8e01a121b4ab89b993bb2562a5b69531a69ec6ddbb656b54a35a9bb8d0eb791e2a43afb32bdcc39e7a451b66d71dba20cafb25d6bed6d1b218049120c491bdd91478475976b676727ba7622084a54241941a15ed2a6132e3d719b3058d2e4f6a11f782e993b4c9611ff18bf21dc9784fbeee3fe685c03055b7feb264dd6228df770e9c541ff3837340f5f010b1a87abbf936af73e2f22918424d2344e4ef390bdbc20c9f97c5efdf9ee4f0dc9e713424237d16914922f36d08a741922631ef5c8018670442dbad3f5e8d445b1d026940965512b5445a15c7f19455068c1429fd0551435a1729b761b896a169394db44978c0f36152342511445538d436920002736a42852acd5da900f360036a0a9bafa01846f7f32e5a05314d7f8e7bdbfd1c38b75d74db90dcb1bb24c004278661e4fcd43f3ec7864d7ff46b760352600950d23ab968a39d8b558cc16c164c5c513a594527631bbdd2dd7bbbb259df3935d8ff1c186a35b639ea73dd03be84b9283a9c240a02f097d2501fde73b8f6846f4cdf4c3b4e787783c0ff32561df9e7b1d4c3a7414f5c2151b6759708d47591436ac308a1a82dbc022699afc6bcc6d7a78b15dbb2145f96f35b7691a4579fd627ab0ed9a32a47bef4bd2d5ee874d53c21ed7e455589483ce84a6fa0b47280d8091c635f091c68d60c31fc51b273676fdab169817fb1702312708174ba5891bd22154081136e458ade2fa6701a3281445511445515477b2f1454a29fd7a924b112ee1b8fe466c9032923a402bc235fe9f13a4eccd49acc52d77cdbc702b553ce65dfcc51b07c1d2206ce855dc4626817a13ccfc2458c42e65e35227650ae34423e020ecd410d7047d4dd0b7e33c34f85e60e9875e60528ed320e0728ebf935a44a790d0a42291c697e8aecfbd39952291e44b348d37e1f3854e8450fd4287c29bb88d045fa2b71697a870a9fa1242446887a2bd096fb9b7ea87a2703b91edf5b75a9ed06f7b68ba66eb7113631e16e8c8c7a787a5dc9467e592dba8597fbe7ce504c4650f24b3fe39ce0c84d58a15e3220506e5b5a56585ae56d5bd7635eec9c27237fcee6bad5f3f97bc15144d226244af1a87da20d48496218108a126ec12fe7586374d7bd5ae48530433c7107694ebcf79b7f2e0fa3f5769d9f951fa021bb26c031cd000976ef00126f5e949f3f0f76853f801130c869a4707e1c67558f33084ebb18fad0ed6533fa127da4f6606b80083880cb65aad56abd5a8c7ca52e166a64c9bfb65737222a94a493b0f1555aef9175a96d69430c963c34858d029cf97e3a371b88f86e6e1fffcf2726898afd7e49253abf461060cfad3d0384780602b705919610818d7662c2b66f3dde3e68185a5b55a6ddbba3098f993827262f22122ab44b8f35c91b922658e48294084694d7a1b69dc00d37dc05988d0b972743dcb105ea24966334f3794b957ec8d1bb24cd6cfe7f3f9eae0e73381e8f92244081b322dbc61de22fd34f847e446caf20d43a0474185d828fecc32c4ebe9d787610a1b72145f50b9a22996fbeebbb9f9978076846bfc6d11d6238a5ef480f483b040c041e7bbcd7c60e10d13029e28fc206ed3d94c520cb20797e4fb1549001def36954b5bc771de8fcd0697526069304e7f29a856864b9e77cf15eb5d7bdd779fcfc7fbe1f916c944e172f6084bbb1f1a8d46a31df1bc1d0e8e1cece1e08d833e722010c4c16e193e88a1dc85a5091396955635143a1a7532274f9cf8cb10979c8e4236c0208dfafb236bbdf4a768042d9271ce398af3e5acdf10a7e98cb07ec31010d6e6c086f6ba7c8fef38fe7cc51084d840dfbdb0262463e380c1216c689c3e1de1022f5a57282143316d368aa25028140ab56d3b463d3a06338399ebe82736d4d0a76ec234fe12a3f333ccbbfba5773d3cb0a31b3f9e15e1d9ac8894348cb98a2d66d7218bb1d825bea2966d4606ef3ed7f87ff7c726f3b76ddbb61a5aafadc6c5a5261ac7f3fd963eccc370c586b1359f348ee7065c62e2243fbd22c953339aacc99d4892f1fe13758a246fd249999c359ba44bb6e42aca28cc87f2142a854aa15cb3fae578626722107ffe8caf03150eb5d08a40709a4e49711b260e63058b5a82e212f3c7631173f6d5da50282e81b9f10718d10cb771116f5785f58f556e186bddb6214245d87645527479a660ebfdcfe1a4c8c4458c1397300c5c8a33f8dcf0bdcb7db515bae185b0d8b88469fc7fa84e228a6bfc0a1bc6566c6d1f7a1f4ed38f058b6bf12d22601c04308f7e5983112e4d0528a0fbc25a3d5f6cc556addba8c7cd5cd950d2ac6256ad184abf9eb4165655a9ba9b9b1bbad55a6b7d52f7616dff4251b061e84a5291a8c4f5ef2118076c33df85e052ff4da4691ccf97a479f88d77439e7d5f6e92d4f79bfa85355f6e68641e709266c25f51dc2074872269014ce37fa35255954aa552a938ae0a19e260ff27369bcd763ce54fbc7054173f7913dae46a4339399184a4f6e068462c51a06c9f56713991e43f1a816008d6b39e71e9b3a104dbb7ebba6e3ebd61ab26f390e17ddfcfcb077d731e902888839e811d52a20474615b3509543525e9314cec31f48f4056a6e9ff421b2af7db047a04bd6d40e87b6c60d8ddb8efeeba0f924e61b1b5630efabb604abf0ecc2b895d389950e0f6e57432a14226985702533b9d4e261d1d2502a05513cc2b71c02c6482791d253cc6eb64820168827918dc98604c30cfbd6c900f0b9d05d3cc9eb0ef1a62d6a63b3d836189ddbe9909f408e6f7ac023341d0c2ed1cffe00e6f5a8f59382a41dfee5974d9f006c4041bf6cc51b3d96c369b51ea33cf65b67065d65167a08f500eeaddb6b1043b9271123b5713e7873bc21cd7a9cff67ecc2f6caddb3665a432da3ccd79d2d15698344b67d55552934fa78e0c82b147a3ffda5f47c4dddddd3d09e80bd8ca94c388c90f5ca629fd21c36230fcc0facf990830974b9fb95cea3a7dca9175ea0607a9128be408f5e00006232ceda0b0f23671d053ad1c2acb7c3a9d4ea73ef58952a7755268ec34b7711dfec5dfbdb8bbec642097fc9238ee03eb573b8f16d6efec9e5e3708d391449fa6c37d7d5a2455ee0b400dac90d98d1e5e2ccfaea36ed846c0208495c16934da9c3b68758c8e99274f99acc7b03559173f317a778a42987843d75b75606a24b5153bd7df690d6cd8ad4869b2586beb38a9487f09781e611fa0f72f82821b9c04fa680d0025712793ebfcc53b2b1e366458ea156b1efe138b834da4b0adfa9ed8119020ec1d9c808df81371555c9062080c3ad83c8a500029322292a06a8566383ed40aa5a4a452527630d26ad156abd5ad6e756bdb463d5a33a4b85caf1d2f586656645ec0b65497ab7dfb500f48e7e1b863c3762921c68a3cd142609c2456669f54deccfbf629926efa497ca2a2a208dd4a4214ed84b80385b5723da9f2ba5cd5e572b95c9c886fbb869cbc2e4e2754cb0a1396ceaa4aeac9e90482b20623c95a2e798de35dfffac562c1e5367d1b50b047e889f66f4fe9f6fdfddbf71ba91ff24e7df0648db06d65db28a5b6e3a55e05180a77fb3ed5ea606f2fbd1f5b1525c1bd0d6ea4af9123f2044b7d4ed70dc0a300bc8502c0c6a9410938416aa05d6c100c02c1a07f0d8d83a479f8105d584a382276da69d970c4c869e774629d4ea7d3e964b1405f1cc7c5da49b264151df9454a296badb5e6c076dfc1a049978508dafdcc5924e6863cabaf5a3b092781b5849429490e323b28d3e773e8b8b032791f6b50dc660726758d4b325c9a21272c7f58721d06256c18dbd5318fc13f0dfaa6312fd61cac5dc023c39b6133d948e30f33051b46d749a2b8a71c05b2dd30db33dff3f15c1679a28f8863acd185edbeb0b7442ef10a26512298c67fca941ef4e086edb272c376f911b1567bc2054ec80df4d6e73e569cc6fbebedaf5d4caccb89d30d252ad6ac58ff6e2bc27d72885061b91bf2dca813d7db65a579b812d207066740ad4820e42a0a39c42bd624ab5dd48adfd4502b4822a96b55d45c35b9721bae166bb1166b1e0fc735421c1c4284be6c00c1ccb4b1d056df4412dfb0870a0b739d524aa9bb870a6b61c842686665b15a739cac5c00b3b826a6c541eeebd4ed20ae7f6c988f9c9b90671fe010b007901841e33414be02140ac54385a5fc04834180328486122b024bc6daa4d06655615d094d11aa227473c38ab1582cd6b6c154890921726242c310a6e9f08cbd906ebd05724c08970470fd89c0000a283d19a6582a73847dfba9967e433ad50d79f6b2b6880d2152b013f0dcfe402e794c888cc562b198903977787c60e6a992826a321b89b551eff40cc1728c49705f9f37fa7f798aafe2adc899354a7b38cde69b1662778cb17687c57a37ac4dba35c6186387c5c65e71e9bbee8574b0729b9614a61ff6eae670e5d50e3329a3bf6f6d6795811d503db0d36bf0067430dbbd6ff5e7ffe5571539ab55575fb93c7592a97f90684929e58e9119dcc43dc3803e1eee330203f27ca4b0ec604ebfbe70f46166f678a011e89b3ba31d5be5975365fde837bf1d233b3827288b52564bd6a54aac61cda55d946bb53614fad128ca6ab5f61f0447a366f58b4b7c7d764faf1fc6a5fe1fa6b84634a10b76b3a351a86d121c3721063d58c294433c220c93e7bf54ca32d7d8c993274f8ff60cfdbef4abe7a39f1107dddfeebcad4bd8203b18b1b65a6b592c101073ceba4dea24c65837fff13f291d758a76c157b0204290e80ca100eff0175917589066120d1a39187683cfc042b854e3bae806e192288bdb42827cda415110075d6efd520bcc11fc4990ab7ad2c38b95715e95759356791031ec7c820dc1eba22dd81094f2fb85041171b121087acb240c0683c182ccb9c3888e8fd8100e7f99c383cc9e93ba8e3adbe9597be9d8a45cf274308d3febe8f1ea973c5dff26820d2e729a5ffdc9d51722ac97cb59a8e8b2e58637314358ce4205961bde64a982c9cd424595cb598ef0e2e2f6eb543f1e1ffad55bc4729dbda4384bac4a9f9acb7c65893de186bdf369653942ec86ed456e348c56a415aef7f0aa3199ae80df5aad0d85fe417074634508eb1e1020ec20783ae108c5d3cb4ece628425d8bc765eafd7ebf5dab6978582e338e68e7c24e9288119638c2e9c4c32af2383fb9890679f1f8c2304300ed83cdc13b14946c4261939ce711cc7fd370e17f27e8608f41f519bac884d9c2889cfc788941c9111c910292972fa3cd853dc225cffb000d73f2236f5dd3e326e9a216a1367454a723899bcef603ec7258ce805bb9e08072433433eefbdf7f97038d271a2d6e260f7f2e19182c5e16a975feed362a5b07cbd9f7201192118a3c56f00e8fd6788580121cef5070bc0a53700f3601b4a039530e8df34d063b8eeee340ee42ddce71f49344ce38f802b15641a0eee73d41d5686877624e420d708f1b8c5c2e3f1b0876745aac7e3f1783ea311087ebe1a699cd890675abc97306157b9eede00506fb9a410278512383f9847949b273a52ba05e5ca78fb31f67432b90c5ba4eb72201084020e1ab1e1c80d48224d7f441ee794d45ab972ed20b88542455253a9b5d6eac9c0846b9c874a7bd23a6e93059372228d8eeb6134185c0069d9c0862db3f12f12699630e908e7c3c3033bcaf3f5ab50a47893277de7dcaa7c00036193152690538324f4175360d05fcdc3fb0a4c03c5f5530db8d0b296a3aab85a2d6ba18e5454ff509d340a2593c96432d9a8879021a998d5687453c4061f3db8cb4b0b6d853a236c7f884bdea871280fd73fe4031b8eee0cb9949147233a1a8d46a3d1b66db5be55881c63c766668e0fc3e50771649a077fbfa00a1760a61c1820c41096609acf1f120117f1174435780cccd26336123bc03cfcdd3fac2132820d6faed74abf9b1b29c1ea25c618afb07d638c3146902cd65a6bad52f610620e7d90213970dcf80c001036e4fd33925e003f2f2b43d7fd8642a15088527a84945246dbfaadeb6eb08a31c65879d63ff817e19201a4c93ecc69f3d8a8a24b72e474bad1830ad606f0869f3f810e97fb7e90c83ad8fd9d534f06f91fbbddf2f35d07deeed37d1495a0c7f8bce78a9d119f8b9df48000e10bfaf682f0057d33de8f803ee8bdaf0718a0a75f1f06e663be1a693ceed99bc17ecc10fb319ff7cd44a0043dc609e483bee71aef0b6d002fa7c3fd70a28f8c7ad9ebb82f6c6c62fd7360439ef12039e06bf57ca1e8f6733f2bc27156c435d25899108ee71a219e837630a71466446415a6d66a63ce39a70e9348245598da9da304db452f3a78716fcf8b2ad61491b8754422e97dbc17aee99085ebefa8482272d2c18103a777a8748db805356534020000000033150000200c0a860322e17848260a728d0f14800c70884082583e1a48b32088611ca4903106210300060404400433ac14053a62ebe1d49c3cda53fe7eae98e3e643a94226bf60417b85a91e3e638bffe5089ea7e763537b32c3cd1b038c3ed906023efb9e63f336e064a8a5567cffe1dbc0c094a9331d30f3b69c1fa8766c68dcdec5dbf79c271ee0c74b7ffa91677432bea0e7760a7cc1827b9935fc8bf91b5072e98c86924426c89c405bcd9ec76dbe2abf8b798d697cbdc896c50bd10f35b60d3eeee2e3a9e6833d8c3fdc9099ab586de87e245e3dd53f5a77d96d350a46ee4a20ec638faca8d75b58722f4c9d5bf121a79728d4dbff51812d25abf793941e0c2a3e16ace0527081eb1399986398e835311a9d843c292e10d193638c719ef7cfd7f6db872fb15176b6af55a7c41259774b743e7789008dcd33fdb5aeee4c0821d8133f78bf84d086cce763fd2975d25249e98900256f18c87ec3ec65ba3796c66396f3d40cb3e4c9235fbd9c386e20e511a6494adf8484fec0ac303cc9fd5ce9de955f3b8c3d890d84fc9f49851918390692050b930aa0579e9ad95d4f67abac99a2156e8e93dccbeff8726d19dbe1f738d5e6ed7c81f89e00e560d99582d3511fd4eae2c29a878ccd80c50717ab9af4827868365075a7a67f4f981646cae036e7d550443e5b091d11a8dbb68935740a15ed5daab9b4b9c564bedf47b8c41e2b26eb9f4fab1ebcac8d42f858421a2789cb6abeafa8603cd6dc3ae1df90ee6c4ac2ee834dd2688f12d65da7c2fb57c0b5720d571f17e5b89b64333557a8403c519ee0153a81296d23cb115ec9cc8faeced45f44fabdbb023c37503c1bda0f244dd058a0871e98bbd4e82b4b63b49ad2739856ea36ca901473eb0b125b68361cadce9e8fb4c36654b25830f9d53498c9060d377291e29da82124d2f2f2b081a8f4863e004b0616448ec2caf20752c8ec48e23aab98bd5aa73d6e853ece07c8aa7c44562ff00118bb279768075bf267ac182846526963eace6824eb4f159bc13b95b78e24f09182615f1f6c8da9cedecf86c906d856644d1778143198c830b1d57beb955188d86a275864d4f5fa80a4c72e30164540087cc42ff9727c32262ea5fc500adfbdc04c1a29e92a9f226a78adad762c357c9c06f4635c001d00ce22e486d8041ce8b8063ddf6937531de8bfaac2e6631107123727a9dd72003ae25618b18b7d7895e442553a98a6bd0b626aaf43ebaf5713efdde821caccb1fc37d15039cdd5081133f990cdbd4572ba8199fae0fd85cb60836e247223e06cd98b1a2fa39f743322774f5ba60978abce004c9f467bb2bcd1b1d1f16bc0c0b1bfe4380594a60e8c292610bca3f87e4b333474b25b02f8c15541743895ff0d42318117dd915595f6cdc4236582b62383e04de142221d853474510a33e68c682aba033e40b58ebc76bbc6290c30773b02cf76fcd1178b3a2ee7f8e8f0fc7390ab6e209eaf8cda14e00a8d0df50fb9901c367b8d55d2671cb402de4f72c2e7e2287ffdb07044884a22902a1b17269804e76d75db1de3187ae9943411682352fa2fa673fb0b818702808f2048681c5785b1afda62f312e6b7b34085bd8e3327cd5a3516ea969af67a2efb5fa660a86b841d56073fecdc848b0ab4820cf1ba6a0518546fddc3aa69f5022edec05965ae0c3dc47e8a6e85fc73ac1b4e09da48ed3d53a56bc3688a032d9ee5c8a52c55e44998a3c187ff1d5957efcbe0dd644cfb9adfe76930432b7015d19ac16f64b416e9a0b4d35431c27fa157c4d6546b94469717208a9ef5e444089ea933e9cc3c016aa0d2d87cbb90bd340d5d291e694d283f019bfc4f428959d736f5b64ec3e26bd24ca3c5f47394d134b17a824e181534f7c4c7016c5975e862d77f7c17203f98155dc4bc3dde2706189ca8a0f30cdb6ff0b7ac52c187aacf776aca195613a013c29d387817a57895e7df921f24384c56e9bc980f61534848c2bf683912224128644ca8599eb24a5afe099b80a72846470d30e9667628615161a4e182ca3ecc012a5f463a792f424412cb371d5289c610963613d2c89bf147ab590b596ad168d09f959ef77dcd554a927e8557c038723f857f9a6fdf08cc80a557b738a86981da9b15998db868ef528d12ae19edf7e79192f43b0030a00dd35ae6422ac970c657fc6f05d16c62d285b56ae153ba5ebf61882126f6416070e6d6caafc044f1410cafcbec35560083280eb41ae14101b312a1f906d0803adeca8d56e3b55c8b7d59082f6f0967ca9c3013ee94d68236909f3592ba01c801f3066d9e8f53db3ee9917d641c7afabdcef143a35918ff1e0b3ee042966632845372cb10a3d3d7165f0781b38fc300e1a6855ff14a54d9e0b85da30c8e917554f58acb08ce62b3b4d0f0cc431fe514351b0dcfa6ee4a38048fa8e95ccc4ac6a6bf7187a708329cbbfb45a20400f766dc5fbe3f8bd6c01c11363d1d5403eb63a2e564ca500c7c5ab9e57b63b842a4ca541f88c27623994bd1223e82a53e4ffd467bf353255ec48c98d1304d65a0bc0bdafa55e8ddf9fa060cf497c2745543ad5855612761105814e8c8dfe28965282f1121502ac17f9e23fd242b5509870abb0cfc3cea02d28700aae84c53c47ee4bf9f48611b8c30af99f36881d16a91b2626e9fa5b2fe34126128a51b518ff14868931744b9cefdf7d89295be661474a494f2305e25a17568dc11611278160b9ea49ac7aa0c4f9d1ae59aebd032ee838a6fe11299727b97cb6f0f3891bbd69cb48535d2f68b8e28d7a7e27b8cf0b8d8e20ffa142478c06d8a0bc22ac0d7ba8b3afc491980c809b0eceec3fd899fc402108979470025ebe8a8a222f98194fb3e9316e16ef9effd147df3da0eba96289ae5ca9e362b9fedc74695958a965df4dad6b72fff8d05d4a4be04e5dcb923077c745526bae38d9d6774e4e7e63afd4922fb947b0572807c84f889a32f90fb771a682a9bdf5155a31e0986d469b0e69181d19624d0ae384985681c49ffcb2e141ea8440f30b0d206d9815d7ce97d04067f1001a7f9aa4cea198e2bc91f6310bbd35cb4658d06a1e560182dce36c20e3ce539d4aae733fbc4a2edb6a1cbf13e18d3c52332047fd2f87bb177025aaecd21a3d78b3f859ad953338f7d710c9944a348921805137c51cee1ebf3b7abd2bd2eff48bc6c2f9f3bdd0b32de2ab613313936e325cbe3749ffa7a6377a01532bf8303a282701f102eb21647f7cd7fe6703cf7f5a0f1fbb0a0ecce432b02d948eb1bb99f850eca8db8a04a2a42df4ae6d8640c26f51a6926fbbd67a5c6f408c0d2229665949e909f22b189f71e1b69207e6e162b7c085dc9b1f84c419878845a611d6c7e1500a61d132064056bb5329615241cd71aa62ee67df9700cf1ef5725fad2508f59f41c94cb7ab6362247b7d9b9914c94602e602c81e1e69b2746dfbcfa05eecd43af1277f83745bf2a2bcc8105fbea6e82656bc44a25427fafd91311c34049fbd38599b17536390b654c662572d6180f070a5e1a5eefbaee7b186f2a51d0f94eb86423f7049c09867a6ba0e8e831c963a0895b087d0a72382505fd857e22f100695c6b2db6d500b563e0d93810de15bc602be8ef3e3e953f6f5071a3cca42145469abf001a1d644e6470733ab6f9153268eb85fcedc950150ede8e01fb480db574c85a9a2f49e5df326c5bedc8ad6e28ef29d8fcf5f0188892809f680888a9dc93c51f3f66d98713b45e19ce33b05c4f11d7bb1fb015bdaf0629ffc8633b1a0fa3aa3800fa003a7f72e4a60588ec8743207b6f21cf9f96b0fe20cb33d3e5361989f0cfa05ee8ac497760cb21e4e253876ea6c690fc812946908b79e8a354dd2665f3415cfa23420fee8b62d1743447aab966c99df93050992cda4ba6125f713a111191a8220b177f575b71426f3c25aea18ea52b6e24be13f90de47ccec53fc53d328697ffa8601d6f804022c3cb68e97f047cc83abf819d3218d36ec4d9951ade708a04561c7eba07edc995ed404c7c1b7f174246a2509c5aaf325d3ab9e467377de354d7d1802a79804da159f2fbe2af88adc376df0046f15a7c6bd109475608da1dabd3043aaa2d9fd190f62e131fabe00eb1487c39431aff841da124546423ce668fe09ee5500fd5abaacf527d06e5dcc128e77c0f530ccaed2e9c9d4b0461c8cf5af3bbefff458dc6aa2eefefc4f69e522bff8463d0343815021966c9f5aee60e025d943f37baeca3e0421500081742a80c14d9ee95d07f88d88e62e88640803219da46a860f1d8aa905b12356aa842f2d8e4bd536e625d3488720018d8663186c8cad165e9f3e44f6b6ca5da5329850b9332056d0ecc5c1cc7eb691c14d2aa9344cae648988be775ed2a5a3767493f9ceebfb7e54268d5c133addbd8c83e93504f758d452df4d7387168061150bf8d1c2e562078c4ceb31eb453672d4e18c45a5c7d50b3d76f27351715f300aa0507b2293d88489abbce4edea2a30b1295927f983f4e9fce7b547a49b1f2dfa38a0bb207e1a3a12d89eac800d15deb523fcc44cedc16afbc04e160e26662b2da8f05d238d6b81fb8abbc5c40e5c7ab175242d54e20fa0f839d54b32ec3ba87ef63e407746998e6536fa169dfd074af8a30a42996c6bc6f155698dfcc3ae14b76c556f0c2e790ac3ed65101d3f746a62e45d12556afc0841c038a7f9226ac88bff4c9e13eaa2ced3719a7a6cdf72771d3d1420cf79af1945e6299988d9630305f06c1643bc4a42d090e97a460602988dd09d55a0f3338ddb4f6898290dc1bb69701dd35b1457e1a0170b56380b06e82a7e86aa2ad2dae523308089e49c7b499da12a5826b44556984b4ce04b44412848a638881b2275c58d00ccce7200eeb5771b9895f0ed4d841fda00b28ca8cfc8f9a3249ecdfc74398fcabc720b8d458b3557ed9fc641f213abf5a7856f49394d2cb8b1a3196f55fba01a9691fe8d5c96701cb0d89f20245c76fc4ca49b6467f2a233142aad5ea75b0b4599ee4a86bf48f546db9f59396f73a836c176ea6544b4a640d6c5cb5135c29dc7ca942ef01f1e9c93e1520c961a1333f1625e04057ffdd71386530417f1a5d7a7b6c91ca2c870f23eb8cce3d44f66492c44a8f353f836afba51fc3d64c372c2e8b473a721254bcf0bfceff3867966954f999a814c4a5d0b0b154b6a3aa5f83770e14de14c8e9c390d8410ff361d5f0838dfe306959254f62e48adc6112ae26527bb0062c12ba5efc99a6fa8fd341bca9730ca51a730e8086ddc4bfda344f91bda9685e9867f16964af46d2da38c4fa40aa814aa99d13749bf4ee6d155d113be71763d80333ccbe9ad85090b228552db7b5bc3851cca82e80705b740df45251a2b3321d7c5ddb0fc8e064c802ad6907067bfb6d3ea8bef3726c87bdbef9f5cca7f30d644a1f5919df5ca9f58d02dc53007b2c001b05008efd46bff4948e11197283f84526e333ea5949f592a79c9a487ee2deaf5aa02d976a25db0710717faf8b196f6df9939ea8f409defac02ec7e97fad3500286567a1fb4178d785b85619f23ef685ce551434af46516459e00fb1c5a1b88c1ed57300fec042a57f70f91aed1093ecc1f4af0d1e34f494adc503a0a40739769cc95808f2f1e88520ae5b3f5940995ba243a12576a917d1beee263657207e7ad94cd2b06b3068c011c60fb1ff9dfd2c07386d0aa3c7f461d382f9ebb705aa3db04f03b651ff004c518e63f7c65baeb517b0845d83523f57feb52cd31a0dc803ebcc521b34e3c58081033b77650efa62e9187d2291b314604b21df488e64f4990c8990a6f36a9367bc838d6719d2ac2d1a2d7d667b56511b37271a94e6ca3caac8e3a1d013a16037807ebcdfe18bfb9e0f6521cb87e4fe087283a3875e9e60b550a0ddaf9eb2b672bc3d3a708b915eb573e4eb36cd2b7774b21f8a3504794586673342f0e8b13d13e46855f2aae9493fb020bdb826bdef0cb42a5aba683fbe60c48146a1a77da9187a73b833550c8bb91060d970ac3d2ec5757b6353eac66c81f4a8e488fe6eb69e88086ca3633b5b3655a07c8dd2ce7cbbb26589b4b8f21d43d723f80f0c7cd8535217eae639934e30caa982bfab4a6c3b38fdf4e163110414168ae9a527a6605160885dc7199f8cc3d9afdf7a369f26742ea7df8486badc6bd765a2fccd717800d46c36962271b06b00dbcfe576984c58b5e7deebc5b0d24608a87799298632ef373118d225bed47cd647e298643587c0a430b01ca837c52e0788df4030c6b52b278bf8b5847f8498a9496aac1b54815cfb50c33e5f0581ddda77c7d95c38ef4d9ff0268aecdd02d7b164bdd7267b66e91f0cb5533ba3cd65840ae0303838d390668185f0f8b6235ce490134eaa7e76ee062c82999e666f30ad251cde26fa4aa5746ea2acbbc55ea1dd785d357099c6b0ee1686d9d4942aa96081d29bab3545acbbd2430500971ed86b1f0b2482530ed2ebf14f7bd4ad3ff94eca7831fb70062ce6731ed80454ba3bdf7475c4b919f13c491ccdf5669416d170e0c5793df76518e18c3772e91b5ef1c6095fd9d4bfe0d973b3666042df85b6e02f1973eceead3aa3502498508f7c082d764a1926122900a2c20f4366592563d1b55f972488987214d63853a6036339fc387248f9da2ef7214e7991c23a16b04431468d795e3c7b2da99257f678d231d8b89cc96a26a68c4b8d82a451fd8ad2c79546402bfa365c58f8bc31137fafdd03be7cef9c594a4b7f1ab17fbe233a665c1f071e5a3073c1cfea76b9b1252409bfb16ace86d006880d08f0a035f26e689f27a77e7430d41b77785b56d31c307d2c0b4b1a33d8ad0dcb042a2330947513140aae52d98ca629b717ece28ebc04fcd85c549ec66f63ed8dd952f1aeffc971f5469330250336e774f057afb15b224c07716bc2b2b33d1c490a67ac82f84147c54181cccd0fa76f5d3cac128c8dd781158ffb581ad87c8e0abda6771283d6995cc2e27a78bc28f9dcb6ab478a95f01047a55321a3ba8b0eda1db2d9e8eeef611a0e0501a83cd3b17567dd1888a1f0aa7b72c48fcdde05c13e5c909eed65b6fdaf0ffe305445d988476f4d518566aa03dde53ea7c45cd1d63cc700457b20e7999b1b04eec640c4e03c0250477b4c24b34513fbd7a58eae9ca9daa3edc905d52069886e01351a9d344404b52337532253dc27b3685bd71cc26f8626e9b1b1f8f0d51fcc83cabc8592099d3241cb7af063e03d71f688938becf2efc9160494a2ac78d5074e928382d6412719339a6e7b07234699d5147c1b8a985495bed6f49926844b2300a0c9f7aab8445de6796253f54205e446f2cd0113a8c3ebb7d3e57361f03cd71a21c2319efa7da6c0f3c417ae916fbacef331ca8196f8d104e594b55be7f9a9a11cea92feb65579f2faeb5d2d2895044ca2979969e3b65d790a0d0765aeeaf254e1370465677b6a2366602d1328664f23693de008ff39441030f1150733fadfeb6c38cff28519b02a145a4c6364d05a756b1e78670fbddedc947e13b6ae175fefd15a10564252275495f6b34c996bcfbb743644cbf89a69b252966f9f4047f5f21011a90f85bab824dd2974a5125f8bc6ad6b4a80090105e6f8342649fcd4da0d86ecd4984e72783f6d1e657e3da8b9cb43194c59fc429709e4e5023e8bb5c50e864d27a11a6d3742b5acadc4f23ae21456b9020ee694b7de5663a7a86670f34d5217f72564eae30bc40f510c2c60b081230adc5c2aacafd1e66f9568b5703f6446c504756ee69733fad92e9aa689823d0a23b97d46b99612182c95bafc9819ff1067cb41922db2dbd3ad5bfe33b48767a688aa33f393abf4933263989a7a564345d7c83d0548487b0e09649f112201c3ed470109dec0296820e1505b76575d44d4befff5dfd01c1fc49b010593470761108bde3a441c7c4fdaafb190b564170de9ab9df051e7bb0adcd9908e391dbad9f0ebf0477675ecdf311799e69b02ee8318bbd2a025c3c0edaac4a3592b79f62a4837314c11201a84f31e9e6d958e57866040a5f2bb06982cce859b06162e7a040a5b33f2be5623200756880a328c9a19ae924fb7c896f5496a60340abc3125eec47590fb92dd03ca44299e8716bea1d047d06acc421483b6e41fc46a4a745e158aca19797068758b833d51a5179d53d42138f6381735cbbb0c2b54426754a62afaa6fa98d0cb4625e0b03ca0a3d371cd2e6dd1ad6da6d2bdb297f5f13f597d5b7077ad97108d0e98ef8ad3321cb4370e8bf374dc67e3e00030171ee1601d2f95086414f8c70209dc1716a50219e35992df51d221d1fdc9a18a45e8b29ac81e5ca93d30d428216c45440b8cd926add5519b0054bb86a61b307c22323015401c2eb728da840ef047d4c2abf2996a60944aae607e0e052139c6d12df55f4e28505a16c0e7ac134af49afc298e4f7493270bfa502282bf2bd621c1aa9383e0a92d5858523b198cc46a14067c1e00d8de0b32ed1c531761786b9e130d373eb9fccc6eeb2eda5d8f75785b906bc1c05906e0097234487c6f9c1bce308eafa1615256edc6b6b87a2e70d81c86576634bba14ef1b2daf37a283a5f4cb18ae78e59b20362d53afe4710bcf560be1b0bd378dc7d95accdcd00d0c880634eeca564b4796895b914c7c506238ae3c395a68137a2195e000451995588909204a746379ac80f54b7755f9c430f7dac534b39ad28c28b99e8015638307e72450b8cb6d56c68b791582e86757a7cb90d014c783178b14b8bfe53fb1abf2a9111591961bd0131261c82275d1628a06024cd36e44e0e4fd5204894b6e17086a8c8ba36d0aeab3737556bc35642082d351adfa2270bc1faf0ba09d661ab6cf443caca1997093d787a97413cae68482221254ca6ad099580274db1646fc195b4b105cfed9fba2d3ccabff992b2e05a6aebb5d6ebc7ae40ddde56c86763007485ae72adbc1375be6925b889e3e02259bf24b1aa12b76f9ad65306490fd405938953c69b71f50d702e82e4c451a537200e3cc349f342daf3a4c3c7ccb0f15e616383124ba5de8ac5d60764c040ff56c415220aea6222300c018f7ca2ea3dc57d508c677b779fdfb326d77d64b7cc573d08f2d8d48e79cecd62f080f04b96580534a807432fe4e4f0433472e7030652a9a146b463f83bc36b46c41ffb48b1325c11a3538a845639d98c970e1445658a013a8d9a72ba36aedb034b621b89d757e460e32d771eb95782d6fc4a726c9accb93774b04d23430bc9e39b9c2181878fe1ceb632adbbf2df4fb39fc55a73fe78cf0a50a40791b2f94d84e756b7c29e6f7f8f0f4c44a87775423b21980066231bd3cb306d03a86ebfc70a38aeecec325b7f9e8c4ac1494ee261f2a283ad8a2970450d2cd5254dbefa371d98ba1b27bfa0284bfb1e4d069735b724225ad6d93912466c1a74f07cd9843d4c411fc05540c4777283635a8ba7322b10910fba436c84809e0c1cb773f53860351cace24da7f26f1aa05ae4c0a1a1cfca141c8d28c05a16ac2cb83404860d2702cccb54f37aa462a600e70546cd3e426f7ced4f257c8a47f406b1a0696ba3af516dd6e02e3d55df110ee1ad2539d3d21f7fdda85ead65d32d69791e0ab6b3be2b2a3f158a38b2d8a25ca8ceae350e29a573a3e1c28310a92bfd4cccaa5ca4602788c2936196e7b694c31001029e7e9131abc4e57be60c8d60d90dcb73f084ff7735d9b6b40e357f528f934cc700c8d873c4018b91b15293e280528bc1abbc95f361e5aa23a6df9f4c3ff3a5c6be857892e384b0889b5c844f5f0c63a698c04bff8571655ba39c0d6d98bb355d39b4eb75e43715665d93f7d840aac0539d367298834087915c53105f9282082bf20629b93072355d70ab1772d0069349560a1901db72e359a81848280cbeaaa41a6255212fc3c9d2c035f185fb6c3f0a95daac8bbc6b7baeb449199ca6e671f1116ff046fc4d2fe77d654cba12b157f2f217f4c9d24de8e021b95fc4bfea89bda26b27e2dff3956947cd72fe88c34e647c24f80a64f157eb3537a44f4560089e81ce39f4814c7c921a1fa0c5e85666555bdb78b272bde309a023bb70ec66ca3005144d6de55edea7ec7394d1b2eed9087eff331eb5bc5f92efde73c5b1449dfb5f8d822094a12624a6de71d2efba4699d16baf0571eeab785a885669f7a64c1b5cd34d43a2e27d12e5a726b993525c633796837224003c7b3602469c8e509304f21905f743b88340ed228bb1ec4ad54bbeb07e8216d08e31b295c69dc52ef205e8ebf038bed90be5f70c60bcda7782207f3db2650d2154d7d774ee622bc31ae36232a1563d1067b4eb0e46aac2fb174a39c0b066de4ad7ff88b6625f7d7db6823b3c919769223f5971942dd7d3c9ccfa912f38f6096ada99838387799883324dbd265f55e9f8b5d34c3cdc699464517ac47a02a9939a82aa997f3a37696f4fbd9555e4189f787f9f71d3d290864aac4a1cb046a33875f5b32e9096f6969c4948b49ef670d0c8add2dd394568bd98985bc772598d18c3eed9327befa440387134671d89c27a04348f8210988b37304870ae817268ea4b03bdab17d757423f12a239eab66631fbc9b8ab917fb0d2dc0b89ea17114f80e01006014f1608233a4fb1f11c9d1b76e4ec072518d98848fb2373b034060fe6b635dd7af23d1188d3873b11a5eefbc847ab0c8308b41eee2d3bcc5fb3cfffef4a800eeb7a48b65d6991fb630aa53788d49e3abe8faa94d78b2c4f9bbb2769d9501e8c5303be66cb20046b5d82acbe1496501670b2e05439b70e57ac25fb3f018d4d37cb45bf7c42f62aa5e1e221c6b9b28407e6e79f9cf6d1b358d8461b9ffa992b0d40fbb18c5de6aab2aa76e3808fac00e780342c661ad75270afe59adf06d299d15461ef5e081cd4e32aff98ba8f7e200124bb361f1b0116e66e6595819e6eafba9af907db765da91d61dda9a00156948626da81e037589f371aab04fdbe959b4f1fe0d267b311dc59689b8ced5e175a1906f194b21712770f3b31c4ef87a826ae9515df000e88dac119c99004266fc18303621595fb33b04b0b8f6306103924ff5d26540b087aa20df6d04adbb9c265cfb6d23e1de3c43204f48533b6d6a5e7df1827e8f8152c75f7bad60c0c4d3deb6b3de5392798ba377cf6824017190849b04e2e14d774ad42480797d80817db91e36a5f26841809fc418d6695c867d0464db2d4390f4260b4f1bbdde97802cae777bb192f8b093b3ebba2a865458d262b1a1ee4d826bed829bfb52369e4a13307990bffc22269b41963c938b646935bf28b90ab1cb32fb8f5ea03229c8ec4610733b4b09eb5997aecad42b68e119bff37536d9c6fc0e240892d488c8f4e26757f640d94d07fd7a4b49c7042f7aef009a8495af1b9aabaa32b69bfb611c4bb6f53958ee3812cf95c091b0f55220cd3c6d8539e3682de051b36c348243bb6f5636ccb51d3bb4ffdf525e36ca342d7c45c5e112ac40b02af57da520e2ff87c06fd575aa8bd9a2128b9902fe131d9c39a9c7fbf496b50aed58cea85b516ecd60996ff961d073b42bb01ea4b14b6b8608ed3901a7f29302bd825fb74f5054bd1660ecc654e76eaffa4cd88fa28caf6256ec32ceb2c60bc50c1544c0fad34d0a51effc2ed614630e777d126164013d729701fffa0cebd3f6ae55644dc6a0798e2a068fae33840a03f6468b5b22b60db75c6faf8c97e0f069fcbf8fdb60c9cc5879357f0188b68376da4d6a0cdeca36e729a63e0ce5f59de37e3017deb575791c57e9620fb2de96a7d8f21be0a7301939ec61819b8ae52abc17fac40ad022fb06304f71a40d20d7784408a4cfb10636de17817ff6fd862d7ac0bb7c2b640b5751078c4885f4fc28b803ce31b20267443e1251f6f248e0607299a9d912e73a18dda523911c36f840c7fd7064a245478505d30994d090c2291627f44552ce0bbc8a37008de2a018a38a4c55ec085301b5dd89d51ef0981523d72f9697d8158d3d030a48ca9aae08ac397545e443675925f75cf51cda139d3cf07e73397fb8dc520884214c292b43d343258314c471f4068682a463877f12c8581681ae56116198752e6735176004d5934c99e2f84f9072ee8d9d153a340fe80906c95d55a8c9cecc6d502255823e95d971ded6d2d6a1cea2f0496c37e609195b0d0e500bc15228be89f59c4d5599a4c5bc19e816d4f6067aff643c5edafa203832bb50cb18980704e560ad70f57546496b34f2d535c779a2a51596e8c7855c05c0e7cd9a8b916782abc2430131ffc773837f1e26bb650ebb846377daa39324f298956c08ff5cf0fbdf2a3307ce0213b375696e2be34ded5835a6a2d73825f79c92e74add5ab94393066d3821526a7c720e83f80a44abf231a3222f0dc7a4538d2f9141f7d5ca2be517827943108279dbb22385046c5ee71ddf7ea41e16b077016028f0a18f000f821cd83ed20afb1638be0296efb9865c816e6292d4416e165d8f4974c87aa9a1f9a49620494694ffb742a5ebe486c5777243355ccc4d942ee5aa512d80452427b384eb5f8dbfb2a53814d19ac1bb310b8e47e243e615d68d9bb18f33b6c59e6ea4cc0bc61a2a24672f23d9b1430d8953f7be0dba7805714cd2984c13f8a7f55932ecfb1ba008738515170f305854a408f2f25b2780a915575309959024221d8d7d3501a3538b604d2472a73282228eb7ab5f1f23ace15cd85aa28581173b847650731cad92b488f04a9cf1bddb329051e1c411aaca34467f5d1c6105c5cfc059d78f33ea865c8f3787b531c0603bb1c6374ef72adc8106690f86de4806481b7a199ffff55106eadf8e6a2349d3025e9c4d6bacad5b6c569f519f2e85771d601c0014c28c570c17659b9ea605010700789703e086fa669d91ca726bab52998ec221c9bc9bdb144304adff22d3738dd7328b4c19f76c16be3f76833fab29afaa5b0c0b15135d7c3a80421a150c48fa4f65792d9ecd9e14e7cbf07530f0122f58a1159e1bebabdc375861850d77449bbeb5ecdcad20f2b6b6619bb9e57520d187f8eee371a0f01c784d1491746686ce3415520922ff10b49cc4aeabb185a9f41c449129b99c51658acd5ee7418027c7feb40ba420ebbfa2e759895257576d9adae4119ca5949d7ca113a18a1e7aaa4a0f5574b0128fda51ac7f91453424e4550ec0b7f3643fc4f7ac1900803085384deb4e016f21bccc1007cebd66d52ecd217adfcc785d857f162877d41016f197b4e2f916689bc7d4b205625c4d348165d917dbb981816180bc59afeca4184c842973693b6568f36e477a801a8b4e6d6c81710167cffd5929afb761c2101acbf754f7a08583f33dde4978af1af3510046f0db6756684ebe3e1401d81c00e20f3185a863e216efba675453b202d28c62e3491d6eb63c7d37aa10035c85a7517795a330b154c7cb2f6083c136e9fbb25c36e6fb192d8fd5d55c7d99f40dcc6518a419cbe4e4bc143aa301705733a263402ace984fb039ceae69907c022f7219788edf9c91d30281ea6026149c39e2c208d6337e76289c62d7febb1dd5101338d03c489b6adc2a94708a556c6b3558e49ac548eb29cb428c0b584cdbb554f3473f478d86a071e67cb2857ac6331c1a99c89537623d4ade22a5b8bb3f58c066cdd5a78245b54d1b46c5508e9aa5ca480d6c0197c30ef4ec078d86c7c72b9b43f53201f6bcd62b5ab8d7b67b01078e005fb58ddf52c091c11fc63a04db4d4f5ba97598ba81cf668ca917e6bf1b021779ac9acc81b2ff42fb06b2cefd491adb7d616bac7d314212a8966e5c79f95bedf02304968fbb040c76d423533b96402fe7407535ae831ae5cc893342eaa58a7052658c43ccdf677c5b9a0a729ce66716d1e0e7138fd5713c02d3d3aaeaaf2430543c12610caceb5e667c3eefb43e9c4dde55220ecaf3df2d3e55fc660fdfd6d8f4a399394c1fad386e3a30945c406ddb95cce3faa541aa011b4fceddf4f4fa5eb8491ad0598cb7f35dab27f8f5d0179ee5c3ff96d970c92dbd67826be8b92a1012207fd80ccf0df4f7663abe11e21717fb20a130a4b7cc3f2a4ad0909836f3255c844d43e9eec2e91a5683962f89c0b8c5c7818e376b0dad205bdcce83f18f207c2e7fc4ea3f106ce02ae803ceefd8611c8b708b335f8c2fd637200cb678123037135e0f237e6f9f18b04e30947bc0f0ca60895407b3e65dbb7720ab08f8fa46332dfd0e26a4dece89b67229f4a44ce1228e404b179442133e9a6cf90af982dc87c8495ed2f55e69c3641533bb5c644f8dcb161f8373a87f8d90984853151252daf616c94e0576a0df0d7d8c8ac2c6df90eb70af87ba0ce497d35a1b0b9941beaa5f6f17d3866a3bf3eba92347c56e7195483a6b66dc4d504da7b010674f4197340b44bb4cdd871c20502fd1caea9b820fcd47c828afa01021027e538b49239b9a990ef86730c82a4a9372e956c04a92b2ad355c1b423d487c3cbc112a990e72f755b721057880a4690a8d1f92099757ba174d7d887f1d9db36756d051ededd04c9b5b5020f15eeda6e02d5bea38696fc277177cca7dafadfb0184ed207ad0f840e0d27bfa626e48ade790a78f6f0cd5a127e283a93ada77af832706bc1a66d87f73f7f70b26859093f05ca7baa3432cafd5a42981ace66c4bcec1404856bef94b15c0af397bada8e30fed61f41681050cf98c9bafc5f6b94d8e9adc8551a31608eb43ff9754dc6d389eb6c7eec194c6234735aaeb5ed3740c5712e0a2c4d550bbbd3820d8353c12a80089856d01d1f559bb8c3d85d141e571c7351aa84001f802004f06e06e0ee4f9418253194a80cae1f68f3ea80186a35893eac6c32be4dfe0731b4322014d8b666a58081ba67c209afea223cf0750746672e268094cb4b1464c4b47a6b3c00476b645250105717a8ed7b42809e35ba86b264e7fac5d704493bde4bcbea7f7852649929b2eb3f1633f1f9fbf8d7a208dbc9b3ab3dfb9b2449c00afcd03ba03660811f9b6bda0d92003dcef87b32e1856e5d1cabe9568beb8ca647d0d32f9e2b040f0c30ae8ba71c02914658d6b47e31d620e7e85d4c6e32acaa125dce72acaf3c3af1770bf1ec9a2c88cf20ab6be60c9a44b0a092432f54e4d31abd701d5515cad6f30f9a62f20a5b983f39fbc3aa7997b9e60fae514f7ca3a18312f396ea699b7362b25e2132c34c1657adbeed960afbb0aac991742f204aaccd7f80432ad058ac4062b68d5d16e8ff8953dc32adc059dc572baa1b7495a0cab58ea3eb14e0103ad282a56b91ed5d43d6e0f18f175c98e02eda980b864ce32368c60381a9912a4494a376d874de059734e878e1fe264c61451161c4d303c98339c530d2a5329245ee2f081964883cfe76601fecbd0e9cb22033e74cadb3dc5857a37c64b5e01c1d6f809f810563b38d000c7557613475297eafb8e2587192fb9cb048869ac64a48737d4971363c80a054806df784cc5ef30e083e9ac6eb8c1701b08495a832340270a4b604a9f2deedeadbf50a16f12aa03dfb2d8009028f3e9baf68732fc24a95b65feb3fd0c25e7b52b021a50c85c02830f7dd0729bfcb440fa7a471cd5ed864487bd068a0a766b5e15305934d2ab6caa449ebee6c51869a064dcdd418306763257289ca32e0a7be9a93e866f388cbd85fadb7d68f55e346b6f7fd5837b3a71ac6e245b39c1f1bf48b0937dcc0045d32a2c8f9ce0f4a29deccffb65d35af6994a24b743137efda26c028afc0482ca9034ef0005a8ba5ec7c450f486b84c65deab89d00acbcaffd3191026b01017b56bad2969c35f79fab7d7e6e555015ea9872557a551cf7636fa579cbaa67e1867991c93db45ec52ed559819a3701d6daf9c104dff2647451dcbdd51ce1f837a86dce8a7e6708abce62b85fd8d8130c8834c7446c571139b3df6a375b97a55556f7199c74787303703de9ceafc1880179a61a075f80761836b98ae5502c1dc9d860a244328d1a33229c3fd7b61fea92b8bac334cd36b971071f38aa87670dd178417e4ef29bab34e4a22685f57e1cb12c899605bef84494cb058b1458fd683b86517dfc6e142c603e48c6cdfe3813a65b8c8fec9fe86a0a80a20deeca116178e5ae71cbf4226aadb7098aa9a87265437d62583528de7ffb1d26c887e855de6befacfa6ad6ba55b26dd1dd27c238589144a2080d8a1dfb3979aa1c8e0e1691ad3911ba66b1ff705856e0d451f8720d2b05964fc6a11509ca76b3060107cadcb45fa3785c418348321caf7470dd5e52f756d89ef4642e241e06cc8626af2d0db5da0d76ba074c59a0f8496f45a1d55b61dc9abe52e250a34f6ec79cf353998e712c62e9e7782c24b68676813113269263bd5a36801f88c39dde6f7dfef6ea4c3972c4958fb0fe63b268dff39f7b3df2481ea4acf1bb5db76fba5c4981b7eab77db2c23de2b11e2ead7db601cd1b6f5e86b5e6c37418763555bc8458eff7e802b18335c72ce12fff68ee581e3e230af6a56bc0c9dc99921ebcbd1674bee1b902237f9ab70edb6b655e82cd7cba88b8e2849e2f15dd206721ad142984f65ef13f3f143e5d95c25e37c4672f3efd4393c0f26b5280dc330e74a704fd6421505f65a923a4b21c40668102f7b15dcd0f82938fad13c4747c591a3ee84725634186877219dbdebbbcb83b98118a4640fe93305b571e7e912112e63e1aab96867e438c612ff5b32b828b09dddc147db76cffe1dc8b6542d388948222c1956118a203456e37ba6178300fbf711de38c758531b004a227f1fdab53a826adf3e6329dbc6cc72f3ae57e566c502fce9aaaf7d8bd42dcbbcded253e6478429bf846db7c0185e590d391a1da5eadfcadfec21aabbbe9c8e97706c6eced6f8b1960f41e6086baa9c2f4385195b124df5825dab2e57afce2ce59628ef6df2f5c644de08071db5a1ffb3d061c3ba083c4c874bd9d040c87dce6d7039550afd7ba98da24f7fd96a07f44040639452d1e8407272a137c28233a650706cd554d37c5f44466dde0777c801ca7af1b3e29d38e2b32321cdf8f26d269a8c1dac3f5e95f0393d2fd0c1911bfcce84da0112d556df7b06748c14c66b1b2910840991a75184b669b0a83bbd65a4267d8cd65400e4adc7ec4c706a478ed4d5a7923e8bf68941a6f397b914aa2137daaf543c73f8415329a5277aa90f6a632d1bb41ba4a9da1bf741b9e640dea902c906f510af80843fb59f17dc8dee8f27c4b255c0338ae75918311eab7100518f536fc3f1927bb56e3b186a9526748c61f7cd768fa1ad1e4376c6dd8ae91391a8a7671ec8b446b47d8d92a27ae5aca8d20368870a731aa411a2ae493fd133ddfb2a2ddcee5a8b6ac3665a6fee0c64124f07339b24d4ae9ddcf0f46ec28c636ac7fa209fa4c8cfac5c4c0171df05e4d8c69b8516816b0df2b05862471f65a7612da344c479270a78cbbb54d6f496a2333024b818b0d21910658e6aad69ab2c816a8d9af76b2576b1447f613039a5757d131b2bfc37351642e3ae6a2b8b262d4160225f937190bc7bd8b774edd8eb2d12108597a1e5cb05213fdb0cf0e1458f16c74c71105ad80a14ed1680dad776e6213c2f7a65cfdca20cb7517802c7d30d25ced7df83a0dbce5212f427989db4edad7622bd731d9d74b62a318858e0586547395d772f5f7d0ab376bd6839b9c15be9c111181d0d5c3cd460a0d1273bbb818d07bb0ed2a9dba9cb59b0e839bc040bed2809aca1b069a83dfe6da1b1b800bce62747dd66a3d270991335d0a80f1fc555a6c52b458369db029071183296ed98990f888ae7240bf8ba8609111b01f64dd4c7f9df805b81a58abb8ef49be421820093b54efa715f42dafa6e71a205c7252caf80b72d2ae51c0c017d40218932150304b2bd548e4bb009aa5a968d182f9d4702f4141208695d230ae6f4c8f7cc28698d3b0672acf0df2c36e57500429b460cc89eec2846e14ccbb2c2d569121dbd8fac0f8f291c02e152e0f51889b02e88b8469ef16e37354f3f63fd038696aeb8ae5574334845ee23ab2ec48cd066aabfee7ba85ce307242abf794d2128608510bc60a4774da2c9074ca44623460ed6da1c958aa10a88deba50b1df4d783618d2a30186845edb4d44a24cf3aee719f0db34cba41ad30430db935c2f30d276f11e35b47b6315e82534d07f8b1696cfba72581e5fa3cba2e7befa8c8350d68315c7f758d87da0ca0bae484b910babc2f0153255da145dee6546d8431fe487998facc1e8a18b4b2017c0f8a993b08a4f853d645cfdd7f12294f6e65057717656971d2bdaf8846350e6a7b4db5565bb91a6f13eb8421f1bcdaba61f5da99fef77b9b8b6ab5dfdb0a1fcbd2624146e6c54efe99002a7d1ccef3a6a049aa79b3ac7a082c9a9f6702df0e8f1fd1c3e0907d13ca2d5c9943be4b5465b0a5922f69f1514bea4cee8368afdb09264d20a92b4b0d54a917b8554efaf87062f2e7c1441b8a6a44eea9fb61fa5d28b2dd1fd20e88281f1ff52d248786693efa499ebd7464b3738aced1f499222af7caebdc4e21c5893461621afffc7edb52f2f928747875a10593dc114836349b219122553d8acb0eccbb105349265538ff196a8cfe7293dfa81ba58ca7b7979ee3300d90c5393ac4a82c6276c57f930346deb7721b993b1bae09e47cd6272867e5b236920ac3efb9b9d6b41e29ec033a586071e5e53cb11e1a8293e1b49673624225597c49a63755c2f53f95e7c5d0857afb31ad163362c4f8535e2b2df837d64c0b6adaf017851baa167877f63733b6715993e3105d6b1a8f6202388fd5d6ce021ed6bd30f7c2145977e786dbbe968e032ee5929e65bb5f1f17ecbba8a587c29d69fe430780d30434db3f205e13022675a75dbb582c0ec8bbf00b37e157934cd16864af98080ce4789b24c40dde2d45692e51726f42e5f5c406deaf27b33c2e268dc7ae03f8d466f5a6043b814fe0a23cc85b7f6e69b6750e3b9352b34e78471f628ddee680be61a2cb8b371d324451181e54b00468eb1edd149feb6bd8b65621b6908b9d90dd1d76da5d212bb6afd6094a2398b2c54f41650082aa65511cbc89f8dd6b8ba81a187e288d75f120fc6581583d6502bb0b22bfa99cd1ee45bf31a3b9039587081cf28a91496152901d9fc965c954cc71dba5c4dafee67f7c0b2b62a1d7616999b27bcea58bcbbd55d7956ba41793854e4d2c59b51d6a9114142edcbe9a6e95cd0a1733b4c2ef80dfbe56c5e92d516edc68646f89ef271cd3667e0c3278c8f968e8020bbe7f51b65fe82cba0ba3104c48716d03d3975ff1046e6644b528c89f10624c1f99b4cc2936f268cb7defa05d49f15d5a82946c98a634d9004a315d50364ad980915a758b35ce3e91962e96199ede91d2cddd212f2f3b1efa465e0ed0626a5fea7652b4fd2aaec1183db649ee9504d5cc415a0f92aeac9a4afc1288341d8cf74a711984beb31a82fb605cbe77599fac0c08ab17929c6367ac54c9c547385518ce3a20ac03f700d97b38167f15e8011d92526a9d6391a57e297222d6a00a9be584d59ee91449bea91c52a80087942c66ef1ac5bbe7f38a3060b444552cc58a6b92fe4dc679542f84061ab27264ede6aafbfcbfc1d506da2b43970db032dafc7056419c003e7614b154482077ca1ddc54a0a446a8116a70f0c6c241ce2b43567bd8416fd29471544ae581740ce95a52983ec0963aa073755b73f0e5c12d796b05c6a7542d9e4e4d092fb8ee0dc89a8f5465a43e0a92c927bb32d16bd6055d2e2abe860eb4bbd652ef090dfbc52ec9fb3e86a069ea63fe3390ffba165177cb3768e7bc5e45d0b86b0aac27d6660426af993da77b80794d1532c1e20d8c52e89fdc370f8f1b087ab689df216285ad668f1a39985fdb8dae8e226e54ad723ac96f8fc4a731e0c4116c93fb97c2857ee985a8f60bfadbd82bf2b1d97387c0cfaba64beac91e72d525beff87935115581ab9afd1812f1e61b46d99ab2e9016caa2b6b11a6959d1212e80aadc5c1210bfca8c00d8ed767012d4cf8101a2b4654e10cac7342fee7fd869e8fc83ab7ae0caf55189511f266375b4aa66227e9f3b1f0fd37cf9ec2cd64d27e4c4eab78490d0cbd03ff96237872959b6b64e2893c80512470794b1855e63b1077d40bf835c93b11675d24ad6396a5327321c19543d11bec71cbb802aabfc55b9d09c7d83fca03ee3af414fda88a4eeaccbb3f4717e9aa7d07a0d19f38bfb3ef445c3f6a5d0c1d9d89a2caf7812fc36d1b1e10bc5a3a5435af6e2087c34e770c1d73fabc8f0fa908aa4e487a8d2f11887952008622ab8b8d9db8d981829c54e1a5de3eb3766d0f5955ab74aa2486067cc75f11b74d400329c23c07210bb1bbc39204f83c445de9875f9dcf94e0415271b5925eb32813035122555500f78f0c4a108d7e0a1b119c278920e1f3ba344c1fe250e56a577710377b4d08dbd7dc3491f4c3414bb9fc995fcbd34d7b408f36f3b10d8af513d3a85a62428f949586babd70c15b0630afe1db956d05ec8fd7ee4d4250df08ddf254e1c27ca192accafad9bf4c9301b62b7fab614d0667351334c92a6bf8d2db855b9d2031d371059e1569b4e3df408b4e5613642a6ed26e65c7aec3c2a86cb2533e29e80c08a3d03d0e653c927032393602a02797119d62c678870a7a13b6420bda1889804c73e930f65179fe8023f3fb1661e64d9e5479e247f7c123e9666c19e89b634c322d4d1b3fcc45c61c4b4574822967601096a83d870ac9ef98e82cec79c008de2e0b9c7afc95e86b226dfa81932ba175268f0d7e390951e40af0b057beeeb756f7983496ddf18228bef4b3a8549f943e88946ba49381da02aa1bd67d0d737194cf1dfbb93e36b5ed54887dbf908a353b65092afdda2d4021b575f5ea1d4f9cdd6e968a96ac3dd6a442343d90b1a28ba19d955bcfe338f40f59e53812f5be502e2a082e501c3a356200b47e7f0575bea4bdae77a9e385ea9c2b71817fb942971cd659b2c3b0f80b0342c7c3bba06adc8d75caa5d44209a8bfc55000053d43868041d71065ca61939017c00d16f9398704c052eba7e4869d72369334f29fc432587c5608ddb47bf5e6c08c3ce58bb1257b12973a89b3640110accf4f26a7fd0ae57ac5db02b7709a27f811117bb33669f1139e2043ac9d1286f288cae9696a75522e8f4177ff0b7456f6170892c7865a92ad49af6a85d11ad4bf6cee3d3e80c7388eb3666ebf4997812fe14e795f7515979f5f1ba3de1592c79288408862985bfe5c33a360dc963397a809ebdb21c20d9b681dec0590f8d98ed8d959ef9fc95de6adf19ba01d9049128f3f9f0e087f4c68d201c36b8aea07a3c7577aab068f79f64cd709551beb2165a277d2a2dbf89e653e512f0f4d905c6a5ec25242e220cf838f5382262481b6b298e32438221effd31cf0a49688e574e65e8c081fbf6a680fef754cd9135a16a4f932d23b9524dcb4831b9fc01e52c454a451759a82e776faea3a6f81cfb8373859bef442ed3dea358bafea3501b6b72a0b2886ab83a210b5378bd9563a7908d4d33622dc3e026d79e58f16a22bb7d46adc2a982cede2fcf5ce5ce7c5ab567c4b60d646a762345473296141bf0833e797eefe35713af32669b1cf9617f75c785300e2a092645bb62f23887c13089796a40954bae6426e1898b3c683bf623cc6ccc138445c5c52d1166cec802b0b6b51d52163e6d0c55989cda9f8d68270f9924a52e197cd38c0bba515d2a69a2002162dafbb5258a61581e9cda65163040cf55f7aff0117d6c2587ff5b3fa9660d53cd50b8880ee0ed3153ae91c591e6cc5d16243c1e06d45e827b377e988ad5d617af7f3f2686acf249ec0e5790615d0079dec7f9339106d3fc44463cac394108fc86c83fba3b85fa4c78aadf5b4d9ac8eb78d0c7291239e65a784091e77eaae8ba450a0a17daebee902daf1d1e460c992f8e5624583c8c651a130c14c188c585cdde36c00442dc29f50e5d3e0e88f6729336366920956986592ffc25e42d4fdd76fa9ef2b8475ec56471226869ee8be0e6bb0624db6c7808647cc9bd88740bfa19d9cd43c061ba4139afb3e22d814d546f9e82d338119bb9875a19294c034e881ab51a072e2888fe8f7a911a0f5dcd830bf4d6edec76b0ae66524519092b7ebda85eb38b432247f3874bfc0023f6c0876dd1476d29ea17c71804a813e80544fd0a3755dc2e37e8d951c8cf95d65192fc80bd21441814bfc249bc4d8802e361d36b86184da2697f3856c3dc4f0b86f1fcb8a33ba285d0a3acef6a23b7074aed1ed15a967d94339086ecdf896a7318f7fb32b864631b18d5761e2fdb6c79a1fb5c043d70de28d5f0e69dd533afac3b3d2d9cfbcdebb309b46388d649d0ed32d30d3dfc918eda088fa39f84c5694b88b6655ece8980b654ec3d47c0a0ec7b7797c0614011f7cf57f9c46cd8d2e90d9125404f92b9dbb36bd3d9f6a3abe9edd2a62e0e595a2fee7a2f67a344090120056b3023ae6a6991d7d20ffb0f38d673498f4cc5cf022c7565a716845f054fbf1573a6a733f54c324376112fc507c329feb837c31f671bd5ef970c28519b66b9fe09e83a5110bc14c031cdfbd4e39ff38a552342f369220e9cdd623f4143b3a4caa2b2a87c499f3fee9744d5d2cc85171825d988dc5bac0b26d8e7f43c25378d8827053bd88b5f06efa3854b530963439d108d92d87088a853e0a63b9743b495fa2d4ef797dd9f2cc3e6c1641d40f112b2d020c13da59676a122afbf6b6d50355ba901ed3384d743202178c557deca94dd20aa16223155b93e7a59eaa0416b6dc4ac8cacb3d39d1c28bb6b73638628266a42753149e3c30ab7f33b01daecd2bbf566f8aaf5a35892c3c9de83f20b90284115ad4f46b9913d9985f1e76757abd9e9c288cf1e45364a6d2623c0a04d031ed2017393f9f34923d5e79a5714ad7cb7404e3fa0a90ac881ee08f39967e6a5da7866d6b2075c293eebe7f4fcfc80c22599d12bc15f574955d7f49e75234d2dfc0536849a19d6f419eafd336789679cb96fb57ac9a6698539694b197593403daae72c162baad2a1988df8343b146402ae6e2a5f30e544cf5511fcf075ae834741acf68cd298ab3a07e7d8b53f040c677542af1221e261de84cd62d6c0956db0d92ef7830ccdc7928124ed20488644300a99f7f0cbf41513a84178b710afd5b47102ca794a3b267a01623308ae01d60f124d88c8af3695bbe8637250fe46f53ebf359ab6e501aaea643d3d597babc437b243ad1bed7c096d2b6e2f0308708c0dead206f7d6092974f0c23d10966b4dc501a03dfe0e842dd0d2418d35dfa0b7237ec43698110e8329a6b2777711535eb53dc2677383c901724df564a01939bdd9456b93aca7c0eb6117341533a9335a10e2d587c11d9916d7c4890c1537a439fbee851b0824b1f523bbff93b580126677f6ce02a9a5034fc9341b8fdc790f9ca2a24820ecffdd7f6036e4cb06bc0301a0f264d46e84621649ad65c7f24917f6dfead5d9385362a829d54c14829e16d559547f288d5a59081f4c9c6f511af5e94804f10d1fa4690ace573b82f10726297953db7c7d38523658c9d4546548acea36cc8d11238a88d8501462131b9d0010cbe488048b0f83d203107bad3d777c587608b6173c8c1d58dc88dcd2069ecd981f87cd9498196c40940ddb85601c467ddda3cd0ff014c7f779cdea0f039f28d37821ce4db6669195cf5f28741341cfdd84718b4d573dc633c42030ff1e7b8d11837ef70b52fa62d2b3c6a8df64e0cbddd91e5ca87173c32a531eb6a6215f9ac61d48bfda398750ff039ce903561f62ee75e1410b8c884f889bfd4ead3621b1dca902637cf3b4d54800eb58155c8869b7a5e1ac823c3ac40a191c894253979d0a7140ecde4f6c412ae707fdf858ca6c6de95db80205d1946460242d8887b9219c6eb1d67d94589dae015e2cf4f4709b5c0317b9513044a389110d22f70840aca7c8c6fbc81846e2a486b27304e1504033e9805bc41194f1b04a56cddaac691f9d612a6c95ab3d2b7dc4f25df31f5aa9349fae70ce2226f121556872a1c1c6333ce81a47de02dc5adf68e2973399991810d14dbdc2fbc6c022921383df5c03ad371fbd00b9bbb7e1ca6c63bb9205aef492813f654c2fe128c1e98c3c2843400218efcd34adce90f64614e752556d77eca1c9232ed2d7cadb928e330c1ed0f88ed9834ad62de3bd00eadc32e5b79aae44e1331b15ac08356eedf0e8e6132e820b28990b837246cb03f54e073feeda6a8ec4f9ecdbdae07921c6f0b06edb399f77895b33c843d434a86d538e0e8e37700c88110fd54dc2fc82c686866cd1c29ed427050751b2966acb056818da3e79b84c4e37b1779da3a101f812af231f792285c789f087941431225bfaa2512af3f4121791b987a83c5c46d2cea321ae16813056e4ffb6c9c6c346d79f5e9b9a7fa70368af5a7ce4f6b3feafa57d1e61684634e07221012e4a03787c7f8f3da6d3107bcf03eeaa47fa8d6b09b2e7549644bad5974b374dcc1da11221f81c22f9343cd58e43f229a3b188163606d4a0fe1826f6eb66d1b6d8a3ecb8e3c434d419af53681c842521ef7f29a3660489543b66d80a1370600e2ad5316f7d3278d376ed39d377a076e01b9e95d65da55c2cadd3a2b6a3ff7d40a3cb14545d08550b967e48da7a903fa632b90b5667ce5048ecf42957ab53b8b712f0dd68004351e2c5d89bbfc2261bbf76795d1388b034514ad4a335a4dfbc17b9f736232a470d5d6b9957d9f7abafc020523c74bdafa9b82f2d48554d56db16a04d0b26938d2d3d13e34cc3a1dcd9a0f138f7471973e050bbe26f6813ae1fed03ef91daea1cab3490a37d432f1af4dfa4304d1131ff4f30cacf53153142f348f7b51a2fc83e58279f0e8603393321d86b97ffa7cf21c8a1d198bb9a8ee3b51c65e4a4e0ab69b62f084410f38d9039920556751fe3391bf6560e2acb3151700311331b02fa54e44bfd4ba103afe621cf8fd1fa83d8c30184a15f6ad48fa9c29059867fbd5caf143a40282e1d819bf3c371bf31f62df1d80d9fbbc5cfb4a18db255c51b6d55a912bb0465a175c291ab13f838789ba549214caca259192ea38bffe43c84a87e4cd5d6498ae94ee3a0f99656d3a825b7172e9bb28150ccedecb77aca715c441b45772fe2ce4e7635ee4c8258274f3eaa94c482d097232a66b7611c8efbd80e5fa315218ab857ff5ae03ff33976c4ff92f1c4cebe43e993f7a5a9b8b2a132e1fda7dec957b3b9e98d36911829909a284ed6ef1240e02171b323e922212c78cf84ccd2453b092e928c9a04c22f7825300f5345c3dce8e5967856b084b851921a6257a68d933b101b9cd135fb12a159c190dd0a212960e72c8a0fe64bbe958025dd845d296a509a760702692575aad8f0bcdcd9eeb7c83c5557b134d999cf01ad47c7c1cd1ac7406af0a44d5fbdb9eda198aef9c41e77061506445e4813239e41dee6cadccaa221fc66f6ad87cb421821fbc9172d04793c0c2e044ec1be5d81e006e2016ce7339022b7d52b37b36e1e648a02f4400902e9d8ca229565d9d3ac04c1dd6a8d4368f302b789915aef24b76d1859947b9a551be34ef9b4386ecd5cba2712d3f4a9afb50bd2f2575a8d98e75b065c8e29328d89a1fc74f2aa6174ddb527eeed80e5fe83475d9a78fa525901e582a05e88f8ffb558438e32d68a341de6e95e6944d10ff97abf89cc5c4d4abb6a55898b09a9c46810b54548556ba7296aa31a80e5c4e2cec5dc58b0c8edfef3e41541b79bb844ca925901533c7c0ab303f9ca371879cb68ed6fb9b900476dfc175be1803f14848ebd88d6af69bfe28be8d0da3896e07d8b8d89f40f348b5ebf010698e92d44d4c2976962d9b576fe6f9bff1ba382a4a3da6a85c7f087df812800e3939c26b4c44de3a2bb4d0d5e5e6c5c09d76524318c2116037afd338985499b26049e7eab7853d6a773c3ebea01f73d010159351c9a303e11c1d5bb9970f18c4ff54d4b690a7107664d73770cf628b8606d13ebd6e4fe80549fd336e5c6808033a8e60d693f5dd3a339fb38a39654188a539d984cbee76662ed9d5d28a5f1a9a28120487f888d825f7c3541851a6220a2eb06c7380cb6733978ea5e41f0ce2abed93029a376c66fa7eeefa056d6f2fa00f025be678e5b4a25ecbbbef826495f01101205991592a7fb231f5e0726c00a4b618206d9a14843f4c30473b4e091d92f41f05ecc1269559fe17fa2bd7b881791513551312b058be92b229f7454c153776162f92579b10ca2b35ef37dc95e504b6d92a235462325f312a95953174a3c4bf8a9599ed4f44f49dafb0fbc49a3b2330b5db7588a792ec00d68677addcebf77680831cc74302c40f2b0268144b5f1970ab4839cf8d466ab3ab00547a45e1eb272519ce0a613866863b69f3e5d9f7b79eb9313dc04a09462c354c2845fad52e5406cc412b77a32c071caac2c8032840a4dac3fd01a6b4133c16e438ecf28812432b51ba02a939892bc84c087b54be99705e77a344021f2b70964c2dd445120323c85b4a2a16334c682fcb0869ac0503cced5b51fbc5abba6b528ab0bf139685f9b09f313c0e9db38cb3955d49eb5d72885d694d8ddd9e42531c6f11297e1f15ee92be370a967092a09eac04759681d0eae33e5043cdf7d2d4129f9c9fbb227270c4ad69db3b3e119002a3726783f273bc1abbcd469278ada4c7ff8acbc5a839aa52a33a8223b3a03bc80eda648a6584a220e504d435a9c014f2ec135faf78cbc7f488b532a868baa0f4536bd4323e7e27c1951e72c65d2b8563c311b676f7c2f7c6eeefa176085a6f08b62e03a23c131739e87bb9dac05c0448093a22dcb491b8389fe4c6c962ff8843ad0e76baa23a6ad6aaf29048e3174d3f71b5e1109852cce9b92a8c0b34a065d5b761f9baa13b335e95e8927483c95c7ae2baf97f59dd50d397c6714e0a5f1e3cfe4544057ede6ee94bd4e21b483ea50917d1aee469701008d0baade1b55e707eab23e929cce9f730ec14831c9be24dc7e071839714ea095568881caa5dadd42a370032a289fc0094731ba4946d479788ecfaaee55b4cd3466a5b4b2446b04a30d5a5503e8523d958afbd1ed1fc70f487a579d11d0816073daeaeddcf85cdb54b54f177e1bf3ddec7075ca8ef32f9735cf82658a145586bcda4a06ae26d61b84a59a0baec5ac897dc5663c81b3b54976e73c92d07f26fc98c78c733eabafb9559e843598ad76cbea68336c70786cdd7e7e0d680e7d9e73caaabc8b4f8d35dc60a19dff7373017d59b4106c569a2839c7b7b274308711c58e658dfc9ca9756cdff2c1716010df4eca49e2d1c2c7fa17ea110a979d7d046788ff86f57ee164fa2491787dd64a5e13a745eb60638c20017e013e04f3572fcd247b0d71abbea295dc36134b4c238b963a08b00e1a690a8374bb12be81ab11026a27aaeb533d117223a35d4ae0467a7dbbd0d5830d6917290a5870e6dc46f9d9398b5173b4dfecaef3bf7ec096ac4253961925d592ed75d9579b574a129bd1025492c08aa835ae96cbb461271215ad48cf5b208816e7cd0c847473feecf1d419cb6bba5d77fa4225a20288f5274846ae94f5987bdced38d756bd8c396fb245a9e2b4c1326cce16a120d8c30d3b086a2e8e6245a358ffdee715c32baff63fa01a32659414a0e24f2db0e2afaec89365f7c45e22b662f31240e7969723e008876479cada45ae650743cb4411d80eb74ade9202275304e624eb0fb12fa93f1586f671633907e4b713d4c05b5f2b1e70454916e45d71ac2cd380764527240489901ee3a00cea6088faa2c5c9a799ba93adc666e41ca3ab4839a53321a33195854748128a1596b4982f223b41f48d514b6a484eb9bfa92d5af769bed16671f625442b5b0f289d39e8449cbd8bf01226429c5f4407af4a07e75618135067374d6e9a2b37e064ed786e6c7dc20cd45bb73b95b794250689e8a557168277439e4bba35c842c1ede78a1f7944d74f4ff9ae151b6051612891b53a3d9f224672f8744d6d09f81d5308fb7ff59654588084f44158214eca054eedab94d6930b15f1452c727c5868f14e02c9d9945ac048439c393af89ab61a422ba67e4d8c07326c463a2c5712ed5caf34420296a3749ebc694fbff47f69753195e2489d98261da76dab7831f5e660aa1e21961104954140165593b5b0b6ec9525a337ac3d0722d0252cb0b1c9b958a0edff3471ef0d509ecb6ad68d5e602a17b00ea99fc3057236d18676cbb8bc5b6988a8d213d1d31634f94a952510fb1ee1b66ea3117cf65463fa42b44b96d303829acfb26b791b30780b87c1c7cbca3afcb97f18adf3176b79f003933d671d5cd48d57f049d8759b5475a5805373d53b003bd004790bb1a6403ac35a6fde7e9aa250ac2dcbb0f9872181ca2f6f1c11070a53a37cb88e2c4ceef08b0dcead10ed26f90dc3594b7d2b1089c4caad77fb6c505c9a141dabac72e3ca17234e5567c08f86138fd871a104dcc34b0bbae329470851487f16acba4b6da1f4f19e239a6d68dd7c7819706a2aa67e77a778db72d911a77801c443b404e6e7a1e91664bbfce3ea8562cc8ffce7700244308983df19e007043dd66d86cebf257cba8227f0993ae355c4c6a1e0dbd5552350495524e5bf594fa653073ca5a38b4ad6e20d5e1f4a4b44d54b95582d74da101c06625259ffc2020662b1738dde95a6c67301f8d887c32793101f74408a92a83c199492de55223dfca478067406586faf309c2ac2a15c450fb60265b27fb83df96ced792663d61f8eebfbf827e939bd0031499083c8e5274b13e6ace4991cf85dbd35621fe46219477bc996b6dd7163c85cd84c8f05ae9c7604e19a8b2cda641afe1a20db79a5b2d2c937c9f31686694670805cbd7f4251f99568fc1b9afbc6ca0fa401221a5e3bab4365240d75b84f64be0195a44da6d2e0ffb1cfa8416ddd994e15307f86225c777d8257ae54bde5e6c5ff8b89ef5e225f4b6c7f4c3bb50851cda62154d0081d34b91344ac33d72b0faeb2605e8bcb69a9135cafade1a1c540ac9e10193e95de6d58591a202918c7044bbf0bc59186736553289cb3397189df8a1b09dcab9378de24dd0434d46b2261e76f2173112026dde1483186ef7268294e8b91e649b36e83f3c92570b334e662233c272b9284a6c9a0da34bb670ab02f519863a3b50f874eb95648b740397870bcdce75b0d66e7d9ab763b04ef06cf5434c1b02c54c51e83a41d7cb7a9d1d3c516cff1fd371737211128e46a2a865fe1ed3aac76d689d9cf22711e879b8d76f1b889f61aa1df3b03dae0c35d49ce3027182ed32ff993444c8f63e4f4a8065c91ab8b0944e24e74cd1f1a244a05068b5a43b1497df6ebcc3a59956cd10b903fa5620b949dae2a184dd589154ac609f1aa9ef3fd0c3e687556e6c943a1975df244d8786240c59a2e490d50aa785bb0eb33545ad00aef11fc1db7b1a674874b5f89dda6e307a6eaac1cbe471c6dc279745dba6e8a982ad94f5e26ee00542ac96cded164bbc9b3539e4343606f84d19a41f76a3ad681a343eb32f7a67978a496b15f4914625ae662a173d536ee6f8a7ba9347d1131d7b96a3caa44533f5038ea67e9993a874bae31966bfbc794a1f35b9efc249a6076e639bee04c1ae6234f66cc53342b9c5983ec68e165797054adb636ae71c6ab6973b63997012747b504ee426a46230710528245f271b0b4b120e46e731cb9f978abf4be721391d0915c46a302ddf7b61c23fe34e0b7988840c8b06b22ae586443a2623be6a79cc08940bf004ef733a5b7a559fc8128986c71170c5e461334b812d87dc9ef3c46bc0bd667a144bc0dd7c26522db94ff204d942a10408f3107b8d3478f51018d0d7afad1116121361260f785beb5185b3a4a91fccd66e982ca89b438865f0d60913eeeb9df77b36e3e79738ea1517c888b6dd26d8fdecd1153dc527187c9a98d984552102752e695bc2c5ae18fe5ce73dc0f050e0d2db7c69fcc8c30b878821a3d9df2a4214869075ad75c1f6e5e0ba980e07f91e53ce10a5e8c18fba754a722f28a369765561456974c0dca108bb1f420f833acf526301b018564d33e1aede0335b000757192324e7df4f80e4c11e5b8d806e2040425b926c26f9be6b1e0ef5dfc70bd993339bd39033abca0237dbe6b2073d7327f314883b052f3831052c2626ed92561d50b1be74c5c423ea7df697540c6ed4e6f2737870ad91d6c80554a17ad37ab9727dbdb821662bfd23a2f8c3e8687e299c8a7125a75db559d9b4518a2b1003186fcd9838303260186ab2b0e44b35de5c8354a68ea69d174b7c33c10f3879709c5fee8c1dc22f6b0c07527011b0cbf6c4352b391f162e8c7424be55e855b27d3ff613db56e85525af830dd0233d1312a63e52be6f8946afc365f9e6903b23bee323535c3321f4124a404496318f6a1f94230d8452456d53ed43f322d04928d02ac565e5d5481699de8b7efa0ff5d9718a89e64d14f05716e3819b67ff4bc5548cd8d8ea9a94c313cd733c803f15c1bd8ae962af3a4d5c491f590f2b3b0ba26c925e48b1b9bcf8e7ca281091b0f5d9ad2f45b655a2e5ae9739edb51658c7a7cc788fda2c833646811f9fcb2792b89de42156a3c34c07a808879bf0abbd9b08343b8e08adee2a3795d5f24196e719dee7b220dca49eac557a741b2bf9512b46e665ae7061f0c9f4421f8ef5d9ffd215ccbd837fa15ac05195b6ee4699740a5d835912aa1d8ca58246b465e9462ba9de0f9fd555288f8e61863697560a366fb0d694f1a03c78ec79130a2bed12f4deb851f40d4eb27fd5c69b885484e8243138a5279c4547084ca1d9f1c6b56ca0d4e4e3c0b616485bd8fe6e5b8c237f8676ece24102ae885238456e04d8cf72437ec20acc47b707dd0d1c8338ddccce6a67f1e135eacb57e4515deac7cb2705b1352a16bbc9de10c2a059f1ac7a2156ae841cb41f6f0ed1af73cd166daba0c5d5fa8382a8fc877b746478b2564aae4ce2a998b097bc527b44b00ee4bbfa0a14c7b807adb4d31f070594c494fe1404537c644290abf3174fae852800e6502c6c04a0d00cdecb84e291c225fa40ddffd53f28bbf2bde3015acc0a03b4e6636ade4f4ed88909ec9f51b2c1028dc74fe40664a989f69b774ed56388b6cf2d7a3f5b657dc816b08766bc3c21724284400931a7a1d8fce0fb947cb0935985338ae90ba30e407b2bd5f5e85751f6be259a01b3fe64bfac0f2f96e92898496c51af5580638e45890a8b17957278a474adaf90835c9bbca65c39d6a5677425eebcbbf7d32c5eecc1c495440f1abaf550ab0347431c9b24ddf0f50ce24a2af3d964039cf782a7bb6ab822b85d6587576596251728953d92f6843d14c207528386942df47109f9d0a405a392e427f1f34b93d83490048db38bb81766e35cdccf2fb903cef71e4c9228559dd1aac0de7fd7658409169c468d382eeb512f87d7bd8e50015f74f207b1044e89644b87db3e0097a7ce2436cf90de5f043157bc942683d0f66c535623ff162b3ce598af3943f27d1114f9e67a466c5a08bea0d97c2cda582aee94e3f8a7f7c4802b7df4fa798b0da5b8378b03cb1927acd376467d1e78b860096683a713e7d3cd8416fda734ece30b6dd376644f1ce3f4dc0f77eba11b913ffcd52335d61891c368ce76e63454937007b63a96ac57e330687ddd2ae8a9fcaa6014849e70e9c0f981447e28d08eebac4c3b5838af4a321a535911f6c539b109705c06b051edcf5c4203a3c628891e741ec7285163099f07f471b47588adf898aba0b7189b80f525841d9f3e1093824b4372fbe738316a43342829b7b1e7374978610bba6d961b3ba4a74d5a504284693bc9d1ebb69854370d0b3a8771bc71dc02077982b8bb03338f600a3d2ebd98a57bf3d46ee7225c618f7ca9071c6a958740202610ea0282252df5d2c62a6651640c83fa8e37691165b359a786ec6858480678b62b8ab6f3431fe743cb023d9b1b8223acd86b169a164e2a62e3626b780cefe4a4088432df4a244be431ae422f1d586faa7bd221433e88866ce7aa7b42a71cb263301a120691d4b5ffcdfb6b8e3209762b3edff6286823aebc5451670792aaff5247b313378042c427aa37816efa2a06a0424f27254ac85ce13a04cc641403b8da44ae5ffabd82e7dcc195fc8ca23cb085482957119db777e14c787d28f82d83b243518e0dbe28ffa5eef745405e37b4973bb43c563124854b494ef58af000c2c182fd76f58a8743bd360478432d4b0ee97ce1c581aa660ff111e65f390cdee20420d8fa4d8d692076167e6b2ef772695fde4c3fd420b94ec04761bb129598ee707397da0518c054ce178a646ce0e9db289fc301f2b432acf1e1cc9ef5eb56448f598c72586fbf008c08e07fb047bb70de34e85f5dfa9706bdace478a0be5a82420becef94a4f1d94263de507bd21dac40648c4a681dbf628f8707d2d2c6dd401904ee01680ebe823304928cb0239193076fcb5efca1cdeb12fb1e109e8491e5207322adf4a044037f4a02818fe52abd813bb931c2509640afb1aa2cbbf218b08f98472092cf7fe0842444e10df19061e34523bfb51038ef29cf29ed18fac3f290d2f211e228a219e0ac384d3ee01cb628b6e210a684ec13b15ee16ff5c9e7c51e1d83657f30d74951381a2cf0f8e4bc458290ba4b340d7e659294937c05ab75c5ae3aa4a38f4a4a0fe0dd2497877a83702c83724a24de7152d459239ef472afe5ea9ef091b66012237412cca772d7b391d89aa8512c21ae54d3dd91f2bcb46691dd9d955ab171df60d861860137a5a5dfdd6fb4780b2ded6017d0b663bd29a4010cab8fd38eec114a37d01aff11406605486fb8ebd155f275015e2e3d893ad5220178f97addcfe06dad81826225888604530d0a16571cceb45635fb0c69f60ede6ff091074722a3d749cc99eaf08c522433b2a75c12532437de9053666968d66dbbfa4d54683e1635404e12b975997243d9012bf523ae51be174232da9065ed45c4886165434e87a136943bd4ccfb9c5e62aa2bb41ec270296fb4a2eb76f1091f79a2af8729b675caea7774babeef5a5b1dcbd7b88c31d83847edadc280a4e02942a1419c4f8511fff75e4ea13d3d40737ed88ba62cde622fab19725a98e018b5d51281eae636a7b547ea4941c26a8b7b5916fb648e274040c6ae13c35fdddd2f49874ddacda922a0ea56eaccaf1a2170c18847246c8feece050b4a1511da13d151f30e331880db300d0d58cef821c90d02df1e817e68b9bad148242f4e016169eedcf00ed03054e24275de95f013ff591ceafbc27a9beadda8c0f1adc4106a00e37b9f8c1d2334f097b9ef6602cfcb10f8c1347717a563c92f2ccc44654df86a0a25d84d364da7ec0d2008f677fdce7e454211244da0baedf597cd5a641a82bab1e959d71eee131a0b05682917d427db195b42fe63b504cd1fa98b8011218f17891852788d1763abc05c7980a2d88f1102a42db1d38804c9d72d7bf4f909c1ff1dd4cb3d2aa543927385e0c89624aaa6c6399ac10f597c568b01612dcc3a981d10989c4042141611b5fa3c7fa67b70b2e8e8016c41abe720ab3eef1ce57c2723de3a48133ab87f047f894f2cec8b5e4c5ad1d0dca54a8315f4e82f1f2d0d7c0c5f60f5c51ebd70bc013ff448b596848de3c2bbe96a42941b86466d643978dfc82667b19df25b491ac04907d66cb73498242f88c54871da6d1c592eb8b9d6d7b3f83276cc4df65ea541ce82d7b84cf2bd3e6a62a8631e3c9c48488183639399ff3e7a980ca4a0fa9f41b6ab69923725f33603a0d8bde144a4a5cd10b8ac21344be6d7045e58b680fa5fe684f3199361f5dec7ac2a331327f4fca6615ec591818d8a8af43a8c92436c72d8a884f11b9e414298e191192cfb6641ba2cf6f1a5f494e5207e14137e16e6d1f78a2c38e6d1cb44e36a214e0587323816448068b04e22538c1f68e712c42311904426712dc5c50e89908ca93707a60126b6e79cc09ece4992cab2288e226656ee3c83530971ea6196aab06bf41e4ba08ab1423559e22b009c4238335a44f64e6f68281d98762c296c8f5c0813904243140db2a3db11d191432cefd034dfd335ece71dde2303a9446d9339ac36e59e4a31af53722e19a8ea36dc0c34dee2912a276f1658ad823e42aab286e1273a11d804aaaa3c4be2dc36e8e5fc2378c64d1edd9f26d245bb1c0f7d9e3413eabdd20e0b171275eb208c94307a5320a9604b32af250a38ed1c1c7d103ab0051d1c0e781888440b77d1af5d99c32ae8ff3418677b3dc7ce24327f9a934f8c02843181090123229bf2c463f75bfba55dbad63689cba2df98a203017c8a03da1e8a6e5920da15a903d9abd27f458ea7769d7c31cc9d5666cf13012eb38c2e4c5d4817d6c9680af1660be02e4427ed4b600ce01cd600702076e890f040c95d081e7d3bf0c3b271a7f28ca15b4b4222a8027941dc6def2df79652a69402d206c606e40623d80c9ff1a2a4c1f519df2f6962824dedc334da87b3ff89c940c791b8153e3e05916bc120a89953cbb7a09e6680a26779fb32c0202da8a7960641508752097452ac766fa01f10380871b9efa47077e53d6fcd65480a19af7d3cf849c67becf96466762dd44df0c5869e665c0e5c79ef3ab9a28cf244ca64109d4376219bc005394a981692149200512e71fd59240d98013d246cf4b22ed64a47739f59f8f33ea3588b5d064856404f829dadc9f290460c262b7c6245d7d171978e8e8e8eebb8cee7222a78d286bbf58a80cd521bb2da9881804bfb47a24e6e57e089629ea8999aadc93387cc23e66a2a993ab3c8f567c9cc2559ab944f230a2b6a2f47d0abdbf990f1392f36a26f7911588410a719dff2334432b04922e1924abed8885a5ef4a3a9e049c97ae6e34d230adbaf434a6004bdba3db4a6bd4c617d786eeb5e0629e78712b0f839c958dd9ea823660be965c6b7bc10a7fa5dc04f31bc88f46223fad18fc09cd18b5e889337832451a4171b979ff12ea0444998ef67bccb8f482f3632fce8650047e0cbf72ee0cb07eee0a7183e0650aa402dbeaf3526634cac48592c2f56fba25d79d65c8245a14260ec86fe7850b7012d131b70bf1f31d109a0d440766a29419354e28f188f9458f16f0bed6f037de8d5953ab012a808c5668809569ca86e990c2543c9503294f6a22b64133551ddd665c4609c73b240f30769fae91af3266886283373ce39c70bf8a987840192127203c60f128689cd2143eee0c8144c4345e7a84cb3bd8c0178200b143b234d4088f18425cc9546bba7e3eaf63d5bf50dc8f340b16dd53d9de366b58a75cf0dc8f34031ae9331f2c701f246de0887e7755cdd34dab205700956b67a70fdb9d3a4681c5ce7182acc47f86ebbb7043e5066451ec23210827f304d739288fbd2f4da4cb60a010f9c59955158bf75369bcd4a386a6c6a7b3ee0669e3c7bc6672882952fbeb681de337366d4629dacaaaaca65bcc2ea936aa44875d59eca9acdcc524ad7bb58ba4206b7608204ec94f36385253b7dffa3a508d7a90ba7e0aa629a15d3f068095724565c7f1a46ac58955c7f8fe723c2ca97cbc31e3f1a94b0defb007def49972b7485ae303564479d02099575fd4d9508c35415c7f8fb083d28641a661a174161e57bccdc1dc182eefc5e76083de879f0fefbd16b94a88d22286c836255d2a2bf4d3925d80357afd65ac11e82a017879b99b99b6a929341d0db83f6cdddddac854421c0fecaea09b2bb3b6ba129d65b591eb6a42bc4a7461df1fe03736a4769d2885362eb0adc99a982179a68e11a4f7ceac0235e71a4492e61187f9974a9aaaabe5c66565555555555555555516aa393cd5062eceeeeeeeed6f89b58e107a8536c14628930c1211858d0dbcf612134b9623ce5fc5881bdd7e81c7c7f4e3188abf56f93f492c3143bf6583640840cecce2311f1ea71fa00baa28b2802907f38a658973bc198115eb8fee5e072ebd7e47ae86dec5b30476af1841e273be37802fdcafb57635f23d9db24d6a20848ee46e26b493d4a18d44702033b03c2bb01283361cc4c99b97182157bdcb6d0460947e720293839729c1c512c2547ac82a8e48e4c713299176b99ccca703c742c2cd3f8f4004e8f3aa43be372de6b37a42947ee69a0426a82650f6d3cacf2c2c94cb0672f9cf721ef43a00fd07f209f38506c1f4e0621c994370ab115e4389415e528479709e226ef1df40a446f973f0938b39d1ed6670f3be5212705ecc2b287b54acb98a60ad370d30939cae44892a3cf26784029bc4e89b1e29b92a6940c076d6a9694dfbd1ca24dede27ed324101c348c1ca261bc03454985acc17599c4f5e7da88ebae2a71a049204cb3bd8c42d6402661c428a1f0d6624c09639b9aa6699a8d871330409bb86bdc03304cb3b111b817390b1a5101e3c8710d23a76a46cf8a56b5f95a47032b8eae7f7f5add3628e69cf5980e12616549ca4d1bd5c0e66c369b513aa56bdba9b5568ed5e7176ddc7e1a2014d45bc11078c8347c2587ce128707d3c80bfad58e0ef49fa67d0fea3ad07fdd7b3c80fe034b2e60f0f1bdb7f2a1d7b4eaf2848f2336a15f791f1ece4bf7f7d2200f3cac84409046c312b603bd9f76d48034baf1e7750ffa1c30c7035d442f6cdb01345260020e6ffa060bbc600710217ba53886022c2b712dc9706432994c86c3712b51586245a68a8c47c8d76b072baca7dfe1322f9d16e395e08b7cbef385f8a10b560c7b8734596b297592cbf526055d602d8bc5816dd1c76d5bc48ae10da5290786f187b2e347cef9e3bd58dddddddddd1a7b5d8963dc9320b650ec77c55207c57a572c953a87a39690da154ba551693492a65269541a953a28d6eb4ab26989c545370fdac443c2748eaac48b0a2f287891058d2456641f70fc72a84f3f1de697c3b6b5ab3492168a5de176668b039bf5846b48eba458012feb3ad0c7f7476c3e90c197e9e3653e9f3a1a9858ffce8ea4cdc20633f3a862c16ed788d1bf09863bd6a5f88ffb78e83eac924fc9673e33b39dfea261fcfb5be7755ee7e7013257ecd8f6bd063e17ae30852f2440ab44f791b9a2ab1e7045dfe19058d9627948c308cb2c27e22a1f1f1f1f1f1ff7711f4a6d4891125aef689675234ddc7332c90bdd3a311cfaded8a0092638874ddff0da840a7ce246e65070a64acfbc9182cc27e8262716ab321addc4b46dfb949873f2e439e79c737a366a74873d86f4a9128bb2a3720c6c62e53fc7bc5eafd78e275c2e53b581654c8561fc64fa77b519b6b05f415ec781c03ae76c1b7c474cc704c3f7d0e50b567ee7c0ea70b95c2e970e4aeb1394520aaba1d2b44d6a018ef1f7c14d1501579ab6efda4198ca6d928d4d0678e81bfe1b49233961abc84710d989059886effb0e1ce3308e03d65ca06fd8d0bef1b0524d2bdda0d6ca951bd6b18e553b44126fc4f51ddf683d0449221c7e2ccb1c33478d7eece29a63099f1d2178bda28462b59b03e9a43ca5e372d2446b088b476584fbf9f9690ee4714c41946a744a3baf683d10685d292edab4c30b870215ae37ca9398be8567e1499c0aeb38a85e83ebaf5d61454fc16029580af6ad48b1e00fc8c30e8aedeed63fb27fec0f140bbaa2ad3d52a0fee43df7d607568914de37cc0391c026e714c7b1384f718caf9048adb8221e4ed95cf4aa6157a86c20e7ac54ba87e826ae607d0304a5e01c70e863150ce33f04ac75450f58a9fe7151e9d98fa760ab15acbbbb57a9558a4ba552a91df8467f123ce5291c351c637323831593b55ab162517660465e4f5a3c158562d91512984a07ba4e225defe2fa92eb9d27a58c59e9ee4ce34b347163b8ac8512ad4b3f20fa5623ddd717a7914bf295af6444f9bada532b5c7f2986bb46a3781595693a762cbc07bee21831d8e453ace46bc54dae285f2bf95ac9d7eab5922ff95ac917378bccd554ada68a065484693628388764b24295a039c6ef064a9ed56baa54413c0832e64260182f5e1edb9cc0e4ab5153c5327649596513922402c748270ee3bc85273df5b56af1fe0b710faa3e05c738e854348c7fa0e85c5c7f0f149b0572c370a0e85b340c2849c334283a0fdab7a002e63275c534dd73a01b218b24207f5fc95713962fa6e1a0742191c079b5d65a3f0f9c71988ee11bfd376a709027c31352c662b5645294c47660515cee84a65229562ae52c16d3905a002f4bbf45f9e4fab33c6c79d87ec475e2933e85c19a34119dd5800684218924830c2c2e1343abc2ca9f48b0288be52c96a649901315a534caf86476840f95580c0683c118c635d2d4fd5fea1fd9339773f68c695cab2995c23006a3c104eb75a100ae07be50d0f75ee841341861c546813c1fdc7befa37bd088abb31aefb9f701faee6342b0f48680e831ee4b9022bce7be08d0772fc4f51c071cc3c2728e7020112f35dd5f804f1df8e5d0af69df0d25d0c097991a5e72a0e2cbe6328515676ca0c252c043972aacc8aff9e9606f835270df2869b2a222b1a8158c4a4d0b6d947c73fb82f6b17b44b1941cb10aa2da49496696b254b2121c8114c0915c9f0234763e722b608f122c7dad6218e92cf447a91ab97188d82ad625aed1d5ae518ee338fae823a5212d1c368e23eb937294b526b19386173616a341c48aeca2410496b5d6da0edc073dfa4ec3a0513b49ac5fb1562c7058859dcee1afd1b7f01d18a094b8cb437f6f314d7771794a8c0a1c240a2bfe0522040926c6518bc562b1586cdb3e2ade88840ee69c7394b2f267a9d65a6b85511a6cdbc61bd76aed68c4b6246bb576349261e871746adbb66ddbf64d60bd84c3a07e7ad997b5f6e572d2398cd008b8b3c72f9670fd6b04b1628ffd55607b50cc61db8090638cb1c9a003db9f433f10728c31a64b5932bd2c2c10d26bb05d1c9345dff0b79e85f5cb333995ed970b4c59970b4c76066b2da53c76e0ece0d14ce03009ad262499210a2bfdc331eebed2beab2e97cbe572b96a0d39c6c56264345d78508494ac9e73cab64cf30dd1a6b053a206334918c169bf7de55eba7b0725a4d324edfad73f728cff04c230cc800d06d75f93c1047bfc306261457e694e58314442b550c21da3fc5ae7f49e98218e391acdf9df7279083db0e28ae17c71e4ee4d50a9ab80cbaf99948a85bd21f4c51c51ea9a0b344dd364386de29ee7f34fa93d3f0b81c9cbe7f423e485cf890bfb86bf0c0654775a9a0d8648c2ad9f6ea00d35e4b421e461406986366861e89a0c67db4036d05ad358638fc5ae7c2c76f43e3e432c7b48a5f4402bf60b5970a5863e10e775dad4b8bad53ac339f7858e8eead553c4e52d77e22bdf9931f7c9e33293089b5a8700d7df554ce357c63b274df64587d1a0c28aae13facb2eaacb50a699b22575244a0a6153cb078d04c380c050b6c9681e92249d837bef76fffcb5a3d572994f6b47abf59ab5c637411554bf1aad1608342b28b45102b9cb16bc6913d464f496d3371c023e30d23afdf07666a7ae93d137ea26b51a7434806fbce66888c6d1c98139559b54677c05418d3a3339335f135e33a3191d978e8e8e8e8e4e8dcc93f2ffebd7afd555841498b0a95500609af6de821558e50425d07859b15dcde3aa6258da4bbade76e530720414a0508da7feee9fbd916c38a6c64350b4a06c5718566cd503c18aed5251c0c3510f8ef177cd5c3eee72b98b028d637bed29f8737e3e33357da8cc586c4ad122d31fa4053327d1a3eaa9e5f9991f80aa27d18bdc1589fa7b5e791d4e830afb329faf769a3e3486478931c72933442d2d2f12b55811c9cef8185e943f35325e87c7a0b049fec8dbf2f2478c1faf63a377f99834b98c4a2ea492e85b5e9425ede98f07913f31d0ad21c00d3d2af46a446143ef695cb062c76231f9c3f2f67fa68c22653226757cfa0649362161a18fa7482c6154521cc6bf3562ad5432406fa9b10473c728188bc5c299b2ce82c5c62ed7eba78a131f1e1915d893e972b19cbd8df64cd65a0150afb02f3bf8a9e7edd7130b4a62e9e778ffe4e9974b1cf5cbc562695f353027477bef2b98537fe33ee783e11f0eccd940ff32419b8fbe8fe7fec5c67b1f9587dcbe971db4dfde4603e76bcf270ada54f0067c9960cd69eeb4abd52c579425ede258bb38d62e8eb58b631c6b17c75c1c3332d359348c7f17302e9a6ce18a6d00ca288cd194d18d6bb2d060cea94d6dd643f6e8a17953aea1693dfc9b2013baa78734795f396f7605fb89c350c07b7c13ecd8f53138c6ff658c9bf3a20f35c6e8e798264c2ae8f9cb53430d38703ccf66cfdc0d4f9a617e369bcdb6ad34da18a8b657cfd66a6d206f03792330ece5d3886f9de252ba09d7bf91f413b197741324987a4818d8900b6040032c532a75c9927a48538d8419d239980e81d5004b804bd3488ee6aa860a27c45aadd3adadd699d010b899678ca5bcb8675afbb57e53fa4cf3b9e8c0b287b7bf1b660d13895aad9da174d5755dd76a750e6460f7ce816a6534912c79691ed8ad195d8189bb89d40b266559135a2a9f222b554b65237beac669df01fe0007543d5a35fba902a98fc044e0d0f6105d9715d184b1a5ea3a15f75dabd59ab1999921f96db53c2fb451c251637313c4689b9b3948376b5ead6fc009c3d22bfe126c0f4bb84c831081dc9e2bcfea8bec73e3a107403058917d4413338f1e48600296916f5d70fdf3e1d90d0d7a3ac56efe36ee15146be07a6afd826cdbb681402216bb12fa40228fa5b3dc4a0d7d54d3346ddbb66d037d9625b4c20256cbad84be0ee46d9bd771356439c161ccb0d878c4939e9f202d6b2923d1ed92d87abb24768eba2476e4a387fe4305c463c58685ba4ad7c934130e5552878763b8ea412a18561c09c089ed7fd1a18ddcee1b49c10a366c94a6aebed8bc67bab9f4a582363f9a1246aca3c1081721d896151b562a51d8f852c31246f6c460ad82e5400a969955b0a947bb8ab83d706db3cd25af381a81110557e708a5a9abe09a15a18511d63ec2465bedd980c49088f580cd601b0c06b37198a60283e1a8117501d3509a864ab57886b08e5829d1d15028ed09b11a56a56106564a6dc60cd804d130c47ad81c48c52e20d4a4848dda3698f7844c715c5754adda0c2ce781225c6f2de8a80247e17ec181a4d62899ca63e9152b2af460b3fe8b1ce143e4ec2ae75447b35c34daf6038b6528160ac51282041344505c2c168bc5623729ce2d9526a289182580c39134351305d0a1936f7eaac05f7d05418d3a23e76c1a680bd8cb07f0e33e5ec563b399599624ab540a435913aad4101c1e33b8420aa8d38f298428a7232f3a740fae33abd5dad1c86b50bba669db65e7256150723d8c22bae8557cbabb3f3d42830e9bf564fb555f355eff9476b7bf49e7eb8ad7742758b00e7b85a26f996911bd7cc5341bd3d02b7a57182d87c2f22e5e1c9fba24f67b1dee45bcfe1436318df73c3b5e8d5c77306c42dff22ca496b724d13749868ccf01fd8c0fbd88b4f220d207e67c2ffa1cd0b71cb111bd8cf7d102ce781f22d0876d7087d0b7fc4de85bc09c2336325ef4392c3f03bc59791198635f460b89e54524fb73b290f83449dd8b3caea8bd0fa3db4998d10d1de83f1c159702865771186fc2add611fd0ae23e6da45ff7670ac3f8bbcecfebe7355ffef297bf5e2f4a619dbb38d15ad358e3e6315f5b2101d1b70438b4461a898063fc7de0186718ff2a5be0c1857dd19e6f09da6df01d2e235fbb3c62d7885d231d34287169db569dd8b68d37d6d1d9b0e083c372c79126eebad23182c5e101019a3044804d38339d23878930298e157c28ce26936dda56ab27f3dcc7758a389594942e9b996bb52ad5ab54b2566bb5eb54ba4a9afce42ad548baaa8e8690a1320abfbeccff022e13c40557ecd4a80512d515bb894de4281ca5e7f3930d1031ab0047b9f3c9ede7516b10ef4917061e848007490bd5e3aeb7bbcc640b516eb77f9352cd063318e18b91e3d8fac6711cc78dd2b46975d3ea07e27134053b63c269391648b6eeebcc04b94edb368eab756686eb487cbbd5753536375cf0388ee338862c47ecfc9186a156ad473dd28bd78139317401060f4e393fbe24a723f47ff40f923875dfbdf47b1ffa482ffd20b05bd50097ca94202b021359ebf3beeb041d232684cc9692d075d09282f3465b5f0c2bc060b7baeb5958b1a90ed977a3608f1d845220429060626cb55aad568be588bd92e79c94523ac1be9225537a75b80f0971ccd1684ed115ec775166bb5361a43f2158ff2aa511b3d9b8da09a2d2318262aac39fd020866d596c8918aa7e384cb1f28a55d37a36c36d7626ae5b212e45d899f67db9569487de1c116c8361d858adf2ab42c7cc35a3b3d96c369b699a3785d6fec31a0fd8a8e1ea2971bfe64282b1251afe5326d8fffbb55afbbf6d4e34959479aa092925ca9c5276777f1886f839e5fc98a1c4ec14fa1f9c173af6a52c0c36f528933e4c334720a314639453aeff94b210cf9c734e1114566c253bdf0e7d0abd46aa170c825ed10442dceebdf73ee47954649f293b1db8a78f4347677f3754903ddc260f13366945c85432954c25234919c704e118bf230f1ddcb1752453c9502e336532994ccaa48c521baa101566e6f97acd58c591a639271039ba48008462a76d28dc0588331ab94cd7754d0242ad10002c7c0d52781d417bdaae57932d3a47659acd077c75bb374fdfa82feff55ce7813ccff32a28b4d1d203d555c0652db450e57a2b9452daff9765e877f7d73c48e2ce27acbf46a2b4524d2b694a24a574d229c7719423cb15e5a85d51b21cb1e28f9e102bf68b254b96a2ab821b720c33b934338a5b02bfe24b77d0093b8ee3467f0325d0af51272f0d3e3703db301211ecf793d2eec8d6db4637ca1a1de0824eeeb4bb9bfea6c3f348d20abbc35d94d00ed4d11be86803450f78a0f73c1f40d77b1d1e690711ebbdc82e1bc01d38a858fade83409482a8b7c47a339ebd1e0741c3191b58fa6209071c331dd75f6d14b62389f5fa50e36a4fa5699e4a330c050c0683c16a361b25263caec1cd5c7b7e9cf8548945a9d5ebea0652a954dae488a86cf7ebadf5946cd2d4e329b1f28acf63b52bfef5ba4f88edeaa655971efbc2bd1097a3a105db2abe53f5e2513529b26a7d31b0948617581f47fa7d74efbd8f9707da8040ae46ecd82caa28d2b19b580f7f504f471949f97e19f1d281deca75001af66262d943ee786c93b4ef6feb1cfcc8a5dfa3722c2d4a6538ac97794a29a5da87615d00b022c452f0653e0b71bbd520cf65b48edb36baa2a0495ff30f87bebda2a4ed7a3f60130e2887f7aec97432dcff0c21081d8676fbab8f53a7a0a26f386b27d55af56a4541d106e0a35960c20bb60409cfc85a511e826046ca04a081384cacd8abd65e003f92c1b2700e467abb4e8ef19c458939e7a4829d1e7ee1c4b6cdd9715b2761484ab05c00f017b06d9536cbb65adb9b42a552ad60fec4c8ab88ab87a544a51a8d46230e65a92459a552188e46fc725273c3329834f95879d0b6f9b0ef3912b7c2bef73cac3c2805969910197299896aa70897993c3185eb29573292a61a2961ae699ad63f05abc4271a66e02a8e718d44a43ead907a95d349f002aa75fa315b4a749195ab52aaed4557a9422f690a7244abbff2dbb63dfd2ca001b9f31bec41fbf99ed7a058128d443fdfba4cd7812f3bd8173d0f2bcf028a56de87e86d079244dc8f6ef7deacd665e66b2ba4976949deaf3c0fa067017dd807fd0ae8c3c7ca7bcf27fe74a0afad9034ed7b04a3db81228fcb813e8c6ec3be9df4388ccb4d6e727b52c3c3fb95d0adedf3335bad34a67195f3381157f5ca55bd72550d35e0c06163a55ab9cb086aa55aa95e2a954ae594da50712e0315b8d979641e7329fef21ead824fac8fcbd09722f4a28c22633b5ab7c33457700ea984964e4bc75b5d41d933f668d1df0183892540d6006d8709f4df7b9236f5f03a4792040ad71fe481a27331db62e65968ee45928a846936ff828b2d3c0b6fe9788b894ec78a2988709cb7bcd571a18d128e9f6f8ac7de0aa594524a295da976f480d087422f0ac0d42d8771d7102e9f28a5945a92e85948f657487d0a919eb8fe4e5c06f837164d84437f569f4224543d8942274bb2608e039c70c13aadbc10276f85c4e30709b342e2615f447a11813f802ffd166c70073f853e04f62861502e43b94c119c63fa4801430ade5728740ea6f95e7419cf2079ac7c917d388ee338707379a8846392f40d241cd3e2987602dff07f3510f8868bdd9a7139c61b089b7634bfa783d7921a74d139ba7796b72f826edf986003692101a2eba8648eb87456b2110100000001131500001808060583e190583c2e9c27d51e14000c61804084643c1a09a4410cc350c820638c610600620000000c0c91a8005bc4b75540a5a7c72b1deac50145f8dc4928dd364c074088a269ee6dfe486a1253dfae4a91eb83683c7be2abe30ad51f973c7c268c732e8ff712c14d2b6159b51439e66fbb7210af987b982a00826bcbf7e28eb27efa429df022e047c4271939921063dd42feddf0f2719645a76fc4a962a073a7af70baa8efb54d55a27d93719c641f8c33029f90d5e58d35719c62d7755351f582d2e269d3d628a6a751ed31a34f8240329c070e9648c5802a045db863d9cf40445b0c278df7d0fdf26edd62eb90caf0a8a0a1d69d84882dfb62ffad8faa54d3307de15dd3a4cade71fc11a4c9fe03c21e02fac399b2997cc8a7581f30db8a441934184df0eba87bfd4c734b0a3500e2cabe6d7e2587073cea4c712c021a900e439abe8a36df876321bd5e1dd189f53fdb99d07b13dd5be66c12e3107a6e1fb13d2e9aaaa80a707b64848797408927c17d4278565c732853861a9b5652083c50acb269abb8d77f5325231858e3bc3604e0cc020993c41205628091b626e07eb718fa43c37cb4e2d484f935d82269a29d0c2698a6213032ec325c24380ceeee5160a8f8307a02a297966fc2f63df573a80452eec54da143bfcdeeaee51ec6f06c1c04c9b27c675ce3ce96b1300961209fab8aae6c6968d96a2ef24ed067633fb5349caf52c366b82f5361003c7d5dc4f209b46aec54ceae2bc361ea71d77f6779559c9b30548c205520b84d0c6c00b953d273ccface5a88ff49f5320dc4cf82f7e966b2d97d82ea59e90de3eb2284f69e12fd4aa8b8f3b63a290a242f139a2bb71e7fdaa2dde223820750fbce770f88ce7ffa4963bbdccd1923811cb1a3310872841489428b57cd37e8bca7095840ec583dc08bcfe2a9259390a06bf59c1972f6db1ef75c123888d24db519f82c9223ec9311750ca9039825b68a01cd4888bd54a844d70eb0c32e7366dc4dc32fa794fe4ffcb78259a2e16d2c1397a38ba90c9bf56a2f4060c24048bfb2304d1612f8b233594f77fc494f62a29c14623721828a1e4b5264090272e3efb2145450631bba11192cc5da390cab66c8041590d50a9bb19390378271977000321aa13ec11b9c6b5ef849f56990610c2891094cc57ed207da0b58ff2630cd3030a2343b72fbaf7863d5c02ef0fcce269c574fb398d5f37922212a71037e680942eaeabafaca5f8d22e00672ccf690b3efb177ede1c8e0241ea5f6cce84181921802f9e88ac7db43133771e6ffe87c5078b9880fab08ef7f586d6c305f763c9753bdc537bc200d22a1b3552a06463d1c1ecbc7b1418fef7470020eca9b44e47dd34f56179f4d3979cfc99ae87c3d50a586b4e45b9f38162737f84268c7f031aeef6deac37571ab87ffec269b6622c4b8ebe1bd9bcca9ecfc587f01d74ae4205fa715b832ff7a18ccd52caa79ffe0bf8ba31fa94da79c386099d32273c4b492d095d0dfad08c5dda8ec265996925e12f3dc56165fde30179e24c75ebb34ad4d869ea5e7d04d122e6278686dd34d01192f098e5f11178f25da24ebc534943b0c54da911116a0225c41a7bc2b1ddc80969369c512d042a0983f92fc084a8ba29eeb386d4fb08afa396330a5009b14ab80775aea993744276dcfe0824b6c90a3c105ed2385304f95525c3c7316f5e508b3e4d48265a737cc2b2427e2d809572cf6190871215ddb9b526e68599f27ca8c7e9acf43f30f9180aee798c64a33295cd17b24a643b62ea6ae59d78f71369b67de7a77c0fe88eeba008439a19e32ef7b88b2095c2187ed8a5bba01de81222a89bf6167a78184d1e358988615b41bf88485c56ab78871c2f25a76443eb9efef0bcbaf66041011f07b8b71797d5d82c0d326c4edb90a633f4dae21cfe522bce3b749ae2a19ea31fe08ea8cce9ca9d4c17157ee00b22931e61f68d277b4ef9a623f4cd7ec43c2958fa53a43b50fd4a3fc47bfe7a05a39e5be281b6f58110248ac2606f7c1e7848cb2b5a0e3ed9875d048c9e8e69af173ea71ff6910b7d4dcf9fadbf1661c5db3115cb9cb1aed85079706255abdc40c69d0d2810a2a1ab821f4dc5d2a141f3c4a74ffb7dc1fa3337a8e53db2777235f1bd902d7165f482f30ef08313bc4d8e827f8abf1df92fb57482ad4f85ccb67ff6254990853988f8e34c1ff7c7c2cc1a8553ceb6f714dced694df8c12b789fc51c671a3d56e358cc4ed05c3c08be80b4322d7fbb964c450acd41f24390420649b61876ca9e84b4dae0b44ec88662bcbd6627f1f9cb43fb4406e7a4231e6686635002426c5c5cbdc10157e2e0d03896c2b2a85d91c7ea3a2388270c6de417c82f0296b6ee7ad0a33c5175fe4bcf5c0488d85c42924c9f66caaa4b7cb5c3c486ce6c1e4f736f27c42c10810ea7e52b86ca09826317c1f4e791ffa7f970d753bde5b1d8890e2110aae3e960ae47bb62b05c7dce68304d4478e444d0b0021a16dc5c09e560d108b55f9a83fe4ca4263c25bfd1ba7a8cac88314099149e8b8a3302525ad21e6e171c4857023c0d22425e956613687fb84caf0e1085f04a5dcebd05aa14149bc9000f40ae7d0a979eac196b3ea77cc1e900472d196d9dc10322a43a4b63824b1a73cfcef9544ad41a9f541500cdd601ccdcb55239616fe2cf20805af10064c9a2c103eab785fb5afcaa0288287402fb8881a11ce68d4acd689fc170f0dd4df4605deb214bf82b78db0c5dab8a95077a0c43e074b61820de2d5b9a3d319331124705fb27d4dcb192e0403cc241667f512595d84bd36f1fffa60f0455f9ac022e3402552c38da214794ab8f1f925952569403a2769c7dcc4290de6d76a71702b2d5b8c26662793abadf79337f21efd7293cb1d57f27c43e1643278f7014493a6eadebec9eadca9c8c8337e69fa88b81601253b10fad222b24f717d93155181b51cca11ba3a945fb686b5e81fd95feb5aae1d18036c9d58c781f997df3afd81d4c32b8fd02498cbaaf172d0ee81b76bd4d4e8d6f2edfdabf747de81b5f0a0ee42841b7f50dd2e6ca5f682e69211372f3bd5ed3101ad0c3415cf89f4fa0b43f7049528721df18c30e6bee31e41b7dbd12ab574ed3128f7bf85a1e0603cc1611690fa946e93cd34c0464626e8f1875a506b2264317cb3a1d54fbadbd5b0b2117bb166dacf872c6f910da40765a58474ec680cd3a095990db68c06cce2be4490b31c7c2e5c63a1a61a95f9a026ffed3669fd2ff1cd5b14cd2c97b97c05d12208576bbb3aaab596342dd5653ba3892f73943305d99ed76676ab49765defd56c6444a20d36059d73a3467917a7462baaf8c7f5864bcc84a436553d58061691eb013920b0ff338f4af469f67cd8fc77dc77b15a3cc7abf038185113104ba82059b8b47076ee02b7c3a8a02af80e0917107c765d0a9a602cb280d28b8a1f1e37e678b1d6d1dffd1347b2433322eb500ac5c669a3f595cefd6e21c7fb2cec274ae8e60854636543912d940011857b03cb3c9e40071656d41a7ffa2b2c27ba5e45e73e406acb1722fd0611a45d56117ab5793c0b41d7b8e0cf58a768ba160db3ae9670ad342aac065a4e48b12a8648c3b2b0c52498e3aeff2091cb30f630f9f360f533113f92ddc8d77f4c7184a3950b9960661865dc046af7fd502e451a2c1ef5d02ee69f95a2810789004dd525e386936d41afd48aefc038aad78adb8fd03b3b28580a296c543deb10f79af3a1cb06670df1cd083434d9e1baaa7105aa1b1d3c737064b21ba962fbdea89fbb4af8d4d1ca8045700b6411cda7b616d1b7a5269eea20001de27a1d306c87eeef553e34df3cd071d078ea81ef12f9b34ada1905a9bef9e589e60554abb265bb1315d20c238760207929536f6e2ba2769cba4c786d75f9f3475e1b14ea60bb0ff131d7599c205b8dbfd92acb7325ccc4b069d0563f30ab50c54812c66f0a7b0eb7555c3105677d10c2d6fa795b06c9a2b6e73c23be86dbb3ba01fa702162eaeb905cba71a18041a138899b7efff68b82245aa071c00a3f2af2cf5a4f01fafb2a3b6ae0202394cf177e37d1c739a6998d2d3ec9e60706782ff3ff22d4de1f2133e0b22c6c04eb6085217a07002426f234f7207ca3bb5afcb0cfd66c61c8770aa7ccc0608c53c43e753cb300cbe0d3a1c2319b96fb9c510afea4040e9285c320bab877d9d0af1580c3498e6a16fb58566dbe542cd0fa7d945d8194518520194d2f26278ba18b76b31a4a74088c9c3c35060c85d9007a4707b6ee0bed053c580d2efcf5911213e2300b0b510058f2d040c9ddc2ce13f8e26063b40f862245ad3920a320fa5012ae625435b878fe65e367265e5e35bee89b3f612ba3fa1465a463c141c99e6da0250c96a9c2ff6ea8871bcdb4ae49448a0f033d9ed696bf24981cf9efe568edfda34912c0e3ffa3df8fffda3b699910c4d5c183d98d05bd8129e1f3d1e0fe7fc0d5c813375dcd1b51e0f4f8ef2ce5234a35051cc4c0c93ee922e7645c121ac28e2c7728129633860f383211aeb1c8311f3b9633dc0e506ec87ab243dc6acc4f05ed9f2da9423f54791deb92ee12e698f3857b4b0f7afb5e33307655cee87cf1c5757db9c25cce8c40aa0a45331790844606dc4d40a696465c4bf79c7d10dcab3f7b8425cf62826480cf5e108686b6f85d1f626e12a8107026d1d48d56b2407b22b34d4807efe83e83782bc1468dd24ca44f1aaa2856140929b19a24885d06161de4802df2f01c2f932fe726068cbf3dad0baf78b193bd59c0923acc2590b53ae4f915fc0a22d3657d085139dd3d52c45feb687c9a03392cf530bf92095889bc942763396398baf7a754b0e2539a16db4cc63adcf983900d2f5cf077192ea85ce7c3f94b202f32648055b102a69b500501ba4cf025ba411714cfe7fda756d812543b31f7920efd20968bc0911faa7c23a24ad8ee628eeee220337093e246d31362a87d746bdadba15ea2625f769d93753775056fa3cbdd5bc36b1d4198e8984b67a31d8727720d3f1351916ec303c58efe0a0e0bf235296b3c8fda15d69f8270263d26de3ce55556cb61a3c2829c4241695c6874c0e6700334c567df1a25fd379579364e3d563ce9375ea227f13f027e6332152580f80a6dcdce6bc34e17331ad311e9d76f43a1dd64e1bf8402b6066e5f9bc2ff61cc4e27e1469e01f9c3cba70112ee9a4fbaf73a47445e9913963d3c6846c16d3307af07dfb56d9d416324d3e933bc4644b67a61d4a1e4e9dc3d66a002207672bc3b66b8cdcb0e09af3d3b97ca754019f934b61541cbe73d7c109f3f9535cb97a5f75d5c086fc2b4ea0cbcea4bd2284b609d4a5718201e069000ddcec080ca97f8f7fee1dbd005cc8527250d8a4bc1179ac1d74aac425093c162aff0f22605059b7994783f3232f4dd96c5bb1fb3fd522d2da87bc2a28d94e5ae2ddb2a6c6004f2a51dd7c0c3e79640de48147ef7a0f70c3272916d86e3939c8d83e88328bd11127d56b487089a977093b4e2b676cad27f44dc3f4d28139a4d6d81f5a91342974a1ae1b8634396d64c634708e28e48d39df050ec23d228cdb2d45ba753eae3f81b3282838d9c1ebea26a5d07deaf56f2fdef6d014b4d7621d7c2797c1532378df8d1815ef922536e52b1305979a9a64bd3544a486f45c39afe07a8d4201407dc16d2407cc31b247babe960952c56986ee75c5c1ba142facd46615372e9e88a360f0165fbe01e8ea572300c86cbf3e8fd1499d999163cd8f620e21433a520b80fd80e1557214b7a644f09f897091a362adaba63c5071896bebd27e1c9268f425239540699e6807a2c23136c082aafc4c29502628f4bfdf396e852a62522538c0780d812d3fcff8991528c5ecc10800607df981879205463d8e9e2bcd776c3db3ea0bbfae2b431f324fa3d1636b51e554f219273ece699f8f8088bd9757390e4dcf481fd97414486aecc9ced42c6db6c2d68303366e8d51194e7c443f5a8d62783c4b7b21db259b9b52b777db591cca8e2bab424ef7abea063244517d78ac424b1466e8cabacdae2d665388a6f811e69abd342e1e9c55516ffb7cc5924670db1c26c9932a7316a88a4b515fd8e2ffbc378d2465811f892c80d743e5c6206b1c0c5b6cc23f0bc020d695d64582781fec69c308f352e89d3fc132cf19f0951766d7875d656172dfded480a15532c3c5c741836c39f99ab258fc55774a35042a3dc04b5c1d89fda1f0da289f1296ed453795248dd9542bb27281048bcd6aa8f192f41bb39c9ed3fcfcb65a3c28b4f09ea79940efb07de19203573248c986c7746c95a1574a9eec60078c0f6dbe5a4b7611390671d38d5f3181facbe02a09e7a3f2bf1c276aa60be6d1fba3124c272ca8c67701db7168dfe3f3f5f7faca407b43cfdca7d442b390eb595404e51726c8733da3fe70154f7a671bfa23d03c286b8aba37efb31977c4d3a4d6405463e2b504a896cfd76ba0f754f3e801bfe5a18633422fd50ba93c24302eda7f820584210c54ee037d37db88b24cc733511d1711e5530b02101f3fe70cc297d1f2977103e3cc33f7f50c7b830bfd2e485d5645ef17fa1e98d5dd430a7c3fcab0f546238c6c6003c2c48af161ceab17973e96f58e256782d8deee55172021c039455d71af78f6f4a9b93e73d8e665b94e993710bb710854c20aebdd021e6964d1909a875df30fc3755fa9b61e29443157998c09c1fa8546cf1202d8c01520d3e9a63a1c538de76203d87ad2f673fe4d0ad34d90d04fe7d4ccb14759e339d1e886cbe9c59c05f73c478dc5d6d05d822fd26e9b137b42a97756507811d77b89daebf9b18e7c6bcf0e0f08e1c4152bbe7860af7515db364fc86436f315ec87215318ad64aa9751d0ed9286bb5a9f1ba25b42cfdc8d62a58655d82f200e27269f6a43c8eed9a7483e24ab1c62b45ca509e9cb0b0f84819cd8c7556ed2b8ec83faaac3949d56c3a2a197c9f53970f2c6c37af0272c8839b2017316b4d38ace2151e9f3f47893dee1c6d61c31d24449c1bbe9a5d667f0f70ac9159b9171bf2e221524b470c24ea40bc16341e0fc172b6d3172488ff911e9b5875a0b2626029b8419d76aca1031f60e323054b5eb0e0c350642731f88030ad1faf64a6a4076f1f40e9ced772881da70437f16168fa5c3fe4c36cb3245a04e56b0d195c6b8760308052cf8931f62fe34ff28210bb876abad9210731bcb1e40f6e126a82ef504461f14788de7870662695b53b2b0f75b58e7c20f6ffdc1fe66e78fcbf6390f5c76d10b89b7fff00d43cc1e0ca9167c82522cb7a5e1c3b0b052235d5e96fc6c87f58310fd0873bd2e551b8a5e198cb2a27600f5057b94a474fda69a6dd8f10837b6ba578f561e94f135aa25d0b508a58bf7afd30cfdbdfa5544192f6c7adc0055db1bbc95499e227fd40b1ce63c443fa438ea2b72d7ba454c6f6120de5f48a79049f4a4426c5253981a4e0d382a4a965d3d0fb769ef5a5d6d3d6bdd28577a3499ae518c3f3dcfafb4ae3b2011dfda41d042bdc10b643f1b231d11659e6d8cc8ac395bcd948932e81da32772c416ad932f0db8c4ee1aec87cb988c880a31167c56777e7851648a451d7690089a01ae253995c2511792f4adf9ed67b40e60bc179c7b8f2bd51afc0c80f7631628cd6a126fa6c5d1c66d568db8d32f7f6e39da066f5c62b9c49389d1c9ce6a70c6a41293dafeb7d726cf24bf0d9240b0231d628690802d50238fb5d01a1958150c601a9821fcce1c9aa2593fc3f5ec53ec007c1cf1c7bf2fee13bf71a34e04a4ded34dee000cdcc9f7b51166e4a9a640c97ebd2d4b287525fc226a5e1959851825639227f7948b6e1cf3cdbea9398373f86c8cae16429426c462331cbbef3a338a57375479af28b6078e1aea62909b4dc30fc2bbc50843dec6f7f28942283bce03c0ea1e7992ee34982e713d0c6b11ffd5e5ca23296f27e97ad7714c8fa414dd3bae9c205fab2d077e6349da1e40f620ac725f145565b231df862fc9405c58b2d507a2b699935287b860ec7da0150b3bb93092925c232729ccc1251a0737b6a272b82a21a65cdee1f15974490e8eb826a6a9b57f0940895824659a0fd44fe73be6101b7ca86aa235dd3984a65b72cdb6d801499f1f4e14fbeb22827f4eaaa5327e2af6d472faf5ebd9c9d672d221c251d89fe619951cded6cec9788680e5ea9b617f5e667670adc80ebc230f4bb9b1aa1e26a73fa787c3699b244dffc88053cae09c51622d0954d96ca725a7f1f196d7e7c5af4da93afb134c6336dc4d471fe287701d580986d5e2f9e7ab6413380dba9aaa6b469e4f86bc82affec94353ef1344244873851ef8ff96aa537a6cda05c3f9dc0faadd387c0dfe1c07fc775a0f32dde5c4bdbec0c6e454a172de29f596ca138aa27f77549b24a193707289be392fb54524394f158abfccbf0920ae379963689ef1a3819932ece9c585de74d697bf0b5757cce238c27f5aa7c015500fc27b751339eac4655158f8029a87ee2dd524fb7cf48371d437d7507a664e835ea064c0183bf90ed0d504f11952aeb149d4405f6bb08594b491c4b891adc52251c800a6631b2f458bd3e2fffb06ad5b7ab5739deeaf65f9ca59d8fe8a1d5ce5c62a378036d148ccea23351231951cbb37e52f153997274782f33a1bff0120698706e508348484cd6ade31e378ee644040cf4b1e94a062cd992696a782938ac494395e597356e521cdee2aefe7b2915e2168411d38dd3ac38c72f4aec8651b5faa50c5d0ea5417b75fc5a042a38c5b3306c7ca817f50fadf12602d0cc1b904e747fe73ee15ca4f31d0ab20911ee0fbd66c972844050aadb1ed586fb23b6beeaeb80353f31dbe00c9f654189568ab15beb542b692d4c60d17a8c01b1a4df9440e41abcd118d3c27b627b78192eb976effc09f3f911fd4d55c4e0296fdd250d4dde80ded3991d61704414efc5de9a403852b6c4c42c6f015c8e75a6fdf09bc45070e70d189d42eda5b20aa431a3dd036e894e60523c20091c0696aa367fde0fe6e8011381c49b84a0a050c3c200f997b00248f1ee8dbb7c26d7c0a0928128f3aa103d40d548bb5bddd4b873a1a799e942d4e4d4fe9080804574149f75ec757915984aa798b75522da221fff0e37269123ced35d12b78ca6782db74e630458df7f4942fa7b910ebacb38ab4f45b572cb43322d9305d409c737b6929d0cd6fc84469134c3f432dc1f420e092a2b1e613a948f502a5165f781d7cb447d97c82a9ef0a94e57d827e051ead65655265a167b8eb4ba198fa2372913af11f12b88db454dee48893c4be33b40347ad01a57e2b2345dd483e1871a34e6c57d36af4b4dd15651891bfb71450902334c1fcf33e97c749e07cde87cd8adfe233f02b5ca7cf9746660fce1b5c0f09653979fec34b3bdbb86e56df7663cceb82c65b75bc31233df99d32ab520f4ac9154a587ea42f7fadc824479061fc095476e384c73722962c88c394968af26b1189abf3724bc4bfa296f4245741f68ecb71ec5325171fcaf44cb1fff56ba063234ad465741a2a11e6e5ea1818898bdb7d37517f6f16da61623641a4e26bd1d5588ab6a9c6129d5ca3a4259b943d295427dfa0b9baf551f6a42618c906118511675b49378daedf12abd77de1975a091e6a8ce98a170a3205b749710175d879859b457ec988bd89f2100eed0a9c02e95a0ce7bea68d5a18dc223b08d0fcbdc4eea3438e9cb310f72464992be3f5fd45ec6b3cbcc3a282cb0455f91d6e350169ea9045aae6e0b8c5dc190511845507f105caecd0092e6430c512dcdad69d622a6af8373511dc027a65af7e8f3d3f52f6ed97416f8717938837aad82dc000b797f739bdbfefc1e0c740a49b2e53df5339af86aba1bf116d34094ee24db37a0cb16eb06f6edbd451c7a5122d1688702a56c18352d5ea017838d9e8df9f33d8e51e9c80e7cd84253991de7f31b5990e44c210199b5066ec89b61d12af912333a253f76615b7eb899b29c8df7b591740f7c9162b20038a6342796fe99a66ec397519e602f352dad809548bb3a70403101c069c3cce76b40a202af446ef9145de16e0d33c646e9e36258f9a4ffdde85c3155603e59c6a07296a3b93e0003e552f96a6734f6e42742cf55cc1d425ca2de539ec2c4c9c7e123a77e40791534e0697e3d4482716dbb5963a051ee7e6e381d443ef62e132092387350e3cff79c22e028cd4dbd70b150cceaa2cf7449caa20c679576d15135eb22bed8a36f893c6c1848d96b89fada8850c29640b639fe6368975bfcf863c5d27b59422b181ab0ac18e04c57a1bdb2a98a9653d16f6e651853137c7a1b546088cf06530e1f0e17c0fa75bcb8f2f4ab6c29fc3dd81e5dea0a4492573e6c0a29f8c090ee7cb10eee41c28c7f79dfac954d95b8e7ba9302d7fa57f2698b0a6c496c77b483767e423778200c2fbb5615ea53b751e20daaf9964bd37bc787f61374cc20dd36482840401de24097d9610e2cebc2273eb99c866134a25ad804b24152d751a8596914c2c01c5dd76bd388121d2171205614445f8166625ff79ed11b425bb4f50de399f3fbc3011c78da96439c0a85db5ead0dbc568fad5763c127c3be6b2a8c2f0ca463acb8969fe8480f289acac064b8ce5c024abe6abe476391f258a051486d35ca14ed4988a94ad0285dfb60772d62a2611a811fd30002cc86a296752d1dbb7bb4c6a88cdd9a5c90e99d02d6cbfc97c64dbcba749028e38fa7fc33030f7f6e21ae7e680600cf4b2a765fcc4b740ab6229776e1561eaf196adb281bedc3f8d10efd1df336468af4f04438d94987b88993cbdf7f143143dc6636a7efdcc838e65213a1dd11adaaa4fe2810ea161a1d2cc154c3c5c28d8898a7b8b70344edcb82ba762bc9c8b35cde31d785c59a2122860b117b3078339018d3c176b6f07c8a5b2b41fbda86c63a48f74cafc3d0d99819debd46a724e17656abfb21d20355cd0572a5f85612ec6f2b58486c5ba80811a90b9c69ccc90cd754844a53e49a3f507ccd51bd2d8952de78ca474df3543913422312024012c670e880450cb97a18033a464d5356d57d2f51f2bfcda01e5368d8db7c89916c773d4341f75c51a447d420089b5e6d23bd5eff3f439e7471418c0d8b68deedaded2d4ba2dda0d0c995856196d7766ac0a1473f5cfd9ef411ae7ddffe3fb2e76633c5d17d9eef36a420a236e6ab7159dc573b5156b91d4ac6687978703fe02861ab9b808f7aac5c5bbdb137e334e4a046f370c4ff51088ace0bac5c232a250bdd29ac26e8d7db9fa65f93a12203996726986cd1c0a1458aa42b16e43ac1321a4a31a3964e5e0486aace2bdf936f2bda43402b080b083eb1200b0894a64820c37b312138815e59abd319d121456276f22973290f406c61c489fa920d75dc084cfd5c1aaf7660f238f0329f8132369431b5e48eb67c612a3e336cec069afd52994048045ac69c7065c410d6278171e99e096a04e2bd23213532b970656c5a06b75906b73b52f51c277d5d63e70c7a2e8ecaf58c89ddaeaad68e40855ddf76e04d3f1edbc88e4093d84b3cd0fe67e88b983fe444bbcd88ccef64c2930e1b7aaa5be2cc90122816d0cbf48f0fae630ff71d621fda69d62042e006ad96a3bbd0588a19e65d1c8712ac8b587909209bbf5f009fbcd2e0935628a4e999635aa1deac96b4abf7272905cca58102d8aa9a8f80943d735ac52faf6b816aa35e868306b9d1fa2b56249fd644a23f49c73f10cd6f089bffaa859ebd3a1694b8b21cb5b0ae9f9adc17c23db222d00e04afeb2b980db309fa90700bea810a590d23f809b03eff7d30962bda5fc942057595f79e2879df7eba0df55ca77453e84a7115b1dc9427e9c6dcf20c99dec66e6cc34962dfb81a60498a40b84aa7b3cc04b973cd63b323ed1a16121d826c572db5bf4e7cd8a8dc5748741eda69685280d8c3c416cc87a983181a29a292b201e32f40ac3aa6d035c82543e2955ba01d0f1cd355b3035a18b72bcaa9a3ef6544de4a3729542ab11aa9590ff1d2f1895159cd3d46c008c608b0c940abc6f6492ae342c7e8c19072e896e93516d749b646de09cc355385753472a71310e4e5627d4bcd9455855c3ce353cc0efeefc5f318af3f731d0d26f7dfa9f308f0fa942c003c2fa7217dde59b746926d44e33031d1ecff9574d0b9e7f2e13537b0d167e903fd5dbe050e1da40618e7382a709666bff057b39b5373a96c1d6aff5a10546234cf6eb08289cbe8606cf75eb41eeca09184bbbbe27c334ee08f0401505812346546cb03b08fb6affb5378624228be5211f1904987d4c6376b8191e58634fe8294ae97ac9091dbc67018bbe695492668c02dfc01819d98a25e3b26be8c11891250211e19d80764f69c64d241c49a7f5b5700efb0e0218dbeae2ab0d41c6959ec1f7eddafb5c19221e0e57944a892b6580b8de6eda32c49fae0e944e8d7adf8bd841afa058aa9c69b1a44988ca0aa1f2e2a4804334c581c96274917e99f3098ec0cc8ec29ab4709aac9ea68a7e9a5cace3f1725010ff310979cb3cbfd06e3de92119c9f3831fae082e3879909c50c02c71dfb2e83233e1b6f43c0cadb38f62cee202963846ceaf67fb202cb1e9674b19abb2ee12a57e757bd8707f150350b4a52988de0ff06ecbd83561cac68454511ee69cad9b752aaa851cd63787db35e6ea54122436a4c3564c2058640dafdb67a0202c522dc68d26233e1f0702e8150cf955c7fc1557cc1e90fe3bc52a5750d85ca24f9f83a0576994a75a300aec370eb29877b107c8e656df2e7b990534b0c1292b3c4f5037f08d9a7e265fabb8c0296534b59b1b364f8e6203173d67ac9805d1011661750192f3907cd633db59a5673125d4f7c323590187c9b0e0f043a307e83fb9b22722334912263c16177a9bb3dcdedfed2bcdf53c5d4c32c8522d75f76d74208fac5f80259d1300bb2730c86b93a029db926ea0f0ce83f45347923ba25ff2727ae66971c9c2941ad5dc4025428db6afc1e7b72d7befcc6db16976584a2c66ae1ad676f6245334bf4712074155800dc048cd340c0dfdce0585128d1d53e0fae1d68229c6d70542413b1f4e5a4b9815be942c537ebf901d98158dc970a00ef63e77de541172f7da53678bda02c0aecf2c121a40bb9a91bde2e84442177e397264971689c0240adbdeb4e83aa18eb0b9cf6b3986c8a39865e6949231710c6e720cd05a5d7dcba28e2ed5f934069ee0b70bbdde8c7fce7d2ea26825bd40b5b3a04d3b62c8708898637e613a22e4e351377c95e0851ea25d85b9276850852e80b2c69ace512cd2463493a0eb80b91f2532cbfd3f910c1b2357a468dba2e7dac6414ce295f36b5bce993ea757d450f78950c1040ae921e1838a881fa99ddb27b8de1647364cb323cc77febac0c4d9d400313027c21d205b02820e85a0942e9e4047d65b9597dc26e14b95e3ea1586740e113e218772e28c21cb8042bb68fad17caa1536f8c7f54d816a02ece6e7b0bd09d1c487f32e55d7c200e0d036b473f45ace576a1de4007b639f3fe2d7e8d09e63e6c04a0bb682cb80ca0918b71ff16ae4e94bd4b2aa600975ba444477c55798d321a177506d5861c9617458a73d153b67857c541f84aafe698cced7e09c6600205f03ee14646461c63527d70843c690844eb7178b591122f849cf1a5fec5bc0f07275d5d102a440e5912f2f75b8ac5c11e65328970e9fd410e55cbf4ee0850daa696581b943869be35e431a472951d7c145b5d6df832c61e5f1fb95428764f2f1f1b13582570f19f7f16b1c275a33a04b370ff5e202e7755483fa0db6bc41b142a431a7ce87cd09d41154022c7e36581e3cdcfb81cc9c20aa2f6a6e362c766f4aead5719f2c3fa95ef972bd1fe7a500acce6c360455cc1535692a8d34f030aabd67b52ce855d67e3457b2089e8d12f3468c05403f414f580b41a0e302c1b8894a695b939564858c5c7b7b7425e1f8eab981acd58be6d2094cc98668ceba21168e6b6bc94ff870de594f041490ac3cc09e5fc80c818c95563361e5fc42656ec689cf182e15e4ce3fd8dee70c89afde7c072f5dce544652145e04c2c9f1e31c02299390224b68c4b44c3a1bb5342cb491900d0983b70594c132176b9dcaa69271799715a01ccfd72b9e058d9c991578fa81aa50f541a55db386a327fa6af400548ce9c2fbf495b9ba520b5df6b4fcb5369862a0d4f43464379c3fad7a6802f75782c5bcc9863c5354536f0a4c090951511415cb22d7d20e8152576efd513260582c2a1d8754d61515fd25b0d4e9ed72380b9e2683e62a3a92c5efe570b8995fcc9d620c46f2c1578f3c7bb5ca4805b3fb7984e87ebd06062b5b5a68fa3ab60a0dae27c1b5851e337d0f9e01a1f2d2eee0d74e092ab282bd7ebfe2bdbf0d419a8e61c615fb8a819ec759d4c0d114f17306584c9ef46b16f7761a1f0ad8973714897e3ff8632595af32aa4839d6d7901e103a5f1ea6a5153602d2d416128e927fd978eec93a40d438e5bf6bfd535dd8a13673f5b880a9f5ca5734526a4118b2b53fe96f8779ee452b36d3b98b08cddc18e2ff0fa1366003ea20ad894b4bfc2d6d3c73f7d4ecf32850085c4b6a0d97e17ec7e2fbe12269bb3de3d8c8d0d135de8e9f97a5ba007f91399e1b30457beb9e1ed01d2c9dd9903ca645f022d28b1c29c4c80bf65b404c16531f3ef30cf5297c8cc81e1907bbf29c463fbd4b997fce4a5c21aea9c78e732ec67c3f551734bcd1388ddf7a5a7bb1c10878359805e605fb4fa3bb28aac12ebf267927fa7e776656d325f74ba29da5c2bf3e97413234efabcdb51d309c362bae4cb685612ba04ee50e7e9fac94df9445490117765325d9d9cc2536f9576f79e0b607495e3e5a75285a0adee504b1a78e65edca0862f2852d1fafa4888d098df513802120c26800c93d2d729a0722ac8b1d43bbdc9c61b8b0d61551c6b3ba6d9c76844f1da1399d459cad53f660a5b08f37bd7b5c60f60a85597d7f9bca4a7928ffe83e1faed5b40776eb974403f42477c91c664ab12a5b8625cd0605719f1cc82b9d18a2f100b279901ab9f66eca211f23d9c51155bb0d67bf434fb9a5e52bdb13c148a4b4f5068124bbb1fe18d3efb69612cc1b021d7e7859da69f8af25084406f289bee11e19bce8fcda2beaa94e02b552dc890cbd8068f04d6d759d916c1048bdef77a5fc3f3c1df687a8592b0246f0b476c820d7a01546fe5a2f0fe1d450eb551a492b65eba8840174351856bc4b4b1af8062263f8685cb81ecfcf3adc661ca2f34dc6089d3d173092c634266b9df1a8650e8912545984129363f4db3caebdc130a758209dc38777ad5e759d4039ac2e68a3149b1fedf082deb1b436284a6c557a6ea1b0a3ac7a10206e4d477efeccbfc3004ae888dc3eaff9ee62b8248c68e66b66701c0da40151b2c41fb02b3a19dd0c40b30199bb13db63e566fd6af3e3812f469ed23341e3c0972cc6e1ac0868ce0043cee79f89922b14d4b9d40a7b4c5ce184f5850d4a91de0e6b0497598062604245d60b27a37a27eb37faf6e801a04e89b12b080f74925705cc9107f29085bae3a8c5d368ac4e1a76bffd6169eb1f314100acfc4923303f72a1425e9643ffba82c3da63b90ed822eb405711f21ae1915f8816051ea837bd335bc10c6a3aee24e0fa4c1323cbfee3062e1b00516f60e50ea2758f725fef3a27ad16fbf95d7958cdc20b5e81a44aec8935f4d17116ab8f59a24c74a55746075848b991017651d3d1a6b9e6185e591c0bc206856988f2f522d132cf7428c2b45b678aaa38ca5d2e99a80303b95381b8d51450ee7d40c4f18cf6428a07aebaf22533ff86f8c43c192df3e659320c71826e4060e945c449d008113db777a856e9c89050adc8a96765a74fb13ff8853936d1d60d98d764d2d07c0dab6f795719c8283d048424295effe0711e12214489a970351b55644541beffaefc7da8c1a86fdde7deb735ca5fe3123ee03034be9bccd4ff3df460a9e8f31be0dfb49116a5c3dde10d9c955479994d3fbbdf7051aa94da6ab5c7391dcfd2903cdb350e40a2bde5710bf582eeef32128e3dbedd2b20204bccfdf0b57754169b31d72825dcfe0493f17206e6b06103c3091bcf693e0868183eb34f80f00763e06f04e2e60abf92f5f8103e288ab431266ec594e0d80c68dba450c6ecbdf7a0a84d4dcbb9bc5b21866364e0cbb6426993cfe5dc03a5e61a6d62805d8c2d8cc44f04b4e07fe2b97f0f608bf43d47b1678e0ba9f5095e5340d492a795a0d9de9c398a79a9caf105763ad6f0c56d492989fa088729fc1d62b2a31070f3a3e7c7926761884194ef95076370ec4ed8bb2abe0fffd4d1e8fdc7bbff8a912e552d42e44e91bc82fdf8878098f5eaf1bdd0d8e3f4684e13a778d0c5fc33ed7f048ca2f9a82e9460767949f6c0ee7fef146590ecc664b28e28c21e309a0b004974d44447fd1ff477ed9403e08f27864e4d354aafe3bd8152a89610276fb71342a507220493e94da9266d2e0ee186e0a492695111dd17793594b90709daecf592826fa480300293785fab87ad630c430163dd2871eeb1ca218d6d84d52d58e8ddd4a6d510444b90db32b562d4a5e76a84c281630cd80a3c41468757100ddde4d28bbcf1282038984171203c268c697741218ba0bcabdcd5f82fd377a1eac1e09f9524ff0d72a8d43e5265767cb79ee7b25946abde2ce090bd38f907ab823cbb3ae3255e9c1bd27c8f4173cfcd6ce1136e908828d1199cd6553dcbaa45cdbbddfae2ae13270a5e15b11dcfd28c9f040b76e1d189879138b807a116b00abbd1850f56db94a9b5cdfa1c294ac24811992e510c463a31d8dba010494d8b77700b186677e94fae402bb9f69ce396320c1412f65641314b73280580872468d9671b0c546ddb8b585ff76210e0e028a98132a098d1127b4bfd7b3488a129bf3111144fe1ad63e085f9e63c6b6022b35a557a34cbe8f8d82c58d6f015ad29d46d4eb607fcb4bc102e3cc726d153d3c0657d4171dfc39f3ad31962629769fb432aa010e43a5c30b4e71c0d8ba124355c5b2a111280037569a63eac424322c432758f7a007f0962720ecf5915ccfe1c01eaeaa75969ec2fe588e808da49ce8135f7d4c51d58ccf37a9b8538576afe6218bc020731ffbbbfd76160d546f570d58bb2a9131090dd2c31967d66a471c1ff64f939e38dc7b4df8150bf4be18b58721ad96a177f577c3b5eae7a398e30dc8297b476aca74ca8234b076a5afdf339381200b8279e92e98bdb4cf4c0aa48af634ed738210497e6d000eb99cbf1ae704071a6c431c9abc8997c23749070d53fe4fd53838acb06aaaa79aa46af9c564305718235a758add978bc65d315bc0d01e5a2511b4bb03c3715973a4666b9cea3cd9b783e86ac224443eb948c2baec8c99d18300410cc1713b92202ca86f88a2f877128a81abd3b08057c7c25ea57f2a1f8d9bb974ac05c7ab0aa100df66600ffbd3330ce099be1dcc11f846ba69ec08b0c96b6649ad5fd96e11634260ddfccbffc49665abc458943444d0b339c4c445e16e114a3b6ac5c498107ffca4cf541b30c684729c16adaa0dc2460b8aea248bf7e19843503b9fec3bf7f1c6284944f6fc63016e6890f07fe630427eb0cb471f191e238a43f194941738a0d19930e8da9a553d3c681673a832e013911cf85407f0c8dea55c91b47c01179f94cafa55f19480ca240c7b5b1f372b6e405bae9495d32e36b698dc2ecc60353d114f89ccf724414c2d28fe8cc36308a5e6d4116da7f59aed50d38ba103fad4bbdd6f183ebef044732439fe0963305ea5485022c199eb4241ebc6f89dfce44bcae8f927d67ee80467dd20e4ce5955bd51f62dddcb617d399c43ce46621f6e5dd0a94c8c1ad655b532a43bd8cfbfb783eeba32618e662ab00c0b80952688a45423558401a1bf07c8f67ccba199db609c3c1d31db6879fb919e477ed6e940020665062a84ccada77f96af76587e404ef4bf562ffeafc45a14dc8b18a4129dbdde07655c940c27cdbfac313d04e80b032574a434c66bf19f9017e715220ee28f478f748c72190e61638fd5e763b5fef74318115a0d85c9264f6708e002a50fb158144a4cfc23658848771d8fcebc522942405345dfe00f9b6e8c19f387b13470d6ee6907606300300008382d4686ef3de62517c93837f2f60fa9347d211f5b166fe4d7e531c05c57210b88b29e56c1405cd6c9b153e3f1bb5c5703329037b2b254be23223e68746dab50bc1f25ea87020f2d95c818f21b63f386f41c0fb0dd8254155b81dee18cad39426ca90e4a535cb9e76a27a089b2c3ae6cf104e69ab3a99e937f4ae18940efae603b8817676e4d600c46d4e427dccbfc714dd5589f65b645eb9ff71c70f9c0374304ab21d76e386d628081d98f0b3ba608ede102340af0e9b9ef8226a8a72730635b0921a25b3a0578a813cd9db37dfae130e929edbd48b44d6453c29f2bea4c53dc358e8ec9a1fab04808ac103f8cb8fd59aa9992f8c241449b5efd1852f7bf1c9eaa003074815f9bba43fbef249973c30fcd29e5d0de69a4f6b6ec6cc12950ebc85d7ace1d0ce29d7095a3ca01db64cf02123814f82fc5bcd5dec1eb06582886a34249bfca144872ce269aaf50ea4b7e71ece906d7ebafa2a2bf81646f4b66a203e1c8b44f7dc48f2b90f5e7f728b3ae5fb89b6b4bfe90c0d3fac21ce81545abfc3f0d7bbe37e209b71310c9f53bbf4236ca4acc586a8a70341397f12344cc97bcd25f6c3ceee685eb9a35c0fad93837ca52af8ea92d03221df9eed6ef563b23941f87a4fcdca2dbba87a8c2b9cbd05a106cbbca20e16f93d3c26415e84d970bc54ffe830c866ab4549a12c23ff84645817219f91fdc101048256ece05958c0ef86eb38eca2b55a28e397904a0f8e0b9f94d39d49c689367610fed215e50d17a051fc26f14b07743aba98a218a1434f2ef82cbc168d72bd658550cb8c2acd8f3fb02ef70a9367ab085d91a51e8e5c4f2424ab56f0f25382d4c11903956da35da763f89e4da7bf91a9cdfc7a54ba3cda529b7c04ad2bd23410288075ef88b13a9cc07e5c8ff4cdd79d476666c049753515aea4c54fbd2bbf28020b6935e6b3b93a106d310cf407818ef8aa6b31df4d6d190dc20094924513cf488e1610899310ae60cf9946737833a074822250cde9288cbacaba54b3e124c00da42ec163955197ddb58e9375c0907220c93d4113ee729e8d98f6a34ef8c6713609972666b4f84e10c7f575742e1f6f8d641d99524446eba86e9b3e9ccc180ff0ec3cb40c8f2d8ef8e520e180c289d3c4ef298d6f86e257cfecadd1e4638fd72f611339af20bcc7b98b2d71b55a6c2c468412487946907f476278c10500512a6bd8386e0ef261d9b9d3f4010487bc45823b1f36ea63f71a06bb1a772f14a09d9b8c40d250b6b8ebe87d0c7292b8890da226011b96addfd8a75a04621a851d368a0a1879e22436408d11e66e984338db2662e48130ed18ab9670d824c80b189bbb74fe25b90258a03ac816cd00a59566ed2e7149ddd341bed866deb076fa4d0ddc32c0952815a633eada34deda23925d3949eefd2c296d2b2b801d502933445214d79399fc2df95b7d6a311add180500d0eb98a965cdad919edcfb2b456c832560370607c0ea605330a6a25c63a700760279cc1f650df6bc8b7f85776dc6ecdce55b4bc62eeca940663ec638412300d0fea94ca33064cb8f4e022a5fc8ff9b1a71f15227ebfd770a511be21f2aed815b9d27814f88f35cd51a197ee29273714c2ac5ad8f75a898bb44cd8571aab8f69cb2af4db6676db7b2f530e362b1c3bd0fb67caa48cf5df5607e56f78083e7137aa97bd7da13e3824bd3c5e292c81d26f7d1078a29935e05f7503659aa56271c5262391d6c8e2a08fff86c22f6bab3f3c1efebbfe42cebd00433f555e517ed822fdbd34de4240b79b5c2dff9aac290c3857463180f21591ac197e218244125fde54dfe404420bae25f8d781e28c9ea74a8c2f746d428ceb70243577eb03f8321ff9abd41e8e6e7a8ba6b4a3bdee9168d2ccfa499303d53df271a15245f58e0ba28b94377ad046138e287479d9ff58721587043af7c89b03210cc1e8249f3dc5f45cec07861b08d7f8ae5bc094b0a52c2dff86cb1bcce9644477114ee9b257e4446ec63de1b488b155a8bd9955425f6be3699c5d2be7ee10a4ad426d2a693d7e0ffc0e38d2623cab55f28f05c5557c1da59896b38c07f89f141a4336c304cee5c289e9a05581b0ef813fa1cab7026978b74413d261b75f6114aa1d5124e8cea8cc4dacf091128675bad07d6f2b6b55ba182d57cb5eaecec5a39c6479873e0e37587fccb6d07bb0544437e2aa8f2e89a548e87d3329626438df0f8f8becba8e0c41f45a1e4fb5a5444eadab86d8156b6319654cd1feebb1289a09664828afeb4adf9a5839d01c85a238ee5be9a24569d7146f5c5a9678908c3c4941c72b6666e42a3e6f4db434198645b9686ecafe52a100316169ef4fed55e036ccdc099afcf298d73bea505df608abc60225504566aae2b83d2a221de7657579748efccb0a7c70ba4a6d0837cab4d58e46f9a48a07756b53a1a5ea0f7635516b907bb64881c5731e8fff60097fe4335b275e56df9d80d97a7739606a23efe4fa7fb2dcde63ea992acb3491851c1f94bcb67a42ef5f7844d293388b315223cf205409cb9680103af2b223dcded5b2fadbd0d339c75bf63b19042aa77869f1ce4d54932f4e9b7a864a1ad8fe889077cc0c885ab105d6b4ea87eadc70793f21aea5e5418915951d3e7f59783e9a48007b1dd3ef033c6140a6b41ec4c64f715c6343507d05b88110e024350867ce3f6ed080e90e6d7f9a480556a5a70c5d17ae4ffef9011f769c763cb0941087965d3ce4b4cecfa81dc37f6359e4dd370bbebdb1cc895dd53a3b36fec10320f4b16bb39f2204210ab0ed1d5fc01cd489023914de7aca27c8891dd700cf07bd28c9278511f3fc2b9da833aef11cfc46f4b3cfa147b2977b99930c98f359eab14c71b750b8efeef8265c868c0d15d5175e1f77dd22ec47efb08c44930ce860d12497ea7b9ee5b6354be91e6d0cd85ac4110cf0d84e60855c5cf5c3a60bf42c1034209c8508d516a49e360014f62be407adb5165fe7e6d215f9a21ca0f1957a92d3bf40bc6687b37e8b90347d3e854fc3d06146acc42bc6e512bbcc7b90fa3fb627d5e5a70a3048dad8ac721f5f3e11112cbdcee5c1afd6d1a31d0a81c91ac1d6a50eeefb966737e3ba4728275dd0a300573c37a22470f859ff5db3a31ba6fdf2e5522b143ed2283e5e8d29ae429c6b6b81b843118e67509bdb87739b1527c63491df3cd2916c69c5f7e09c7a7e6cca493eea08c77144706ab7e5f3e1aa8525d3cbcf2ee1111ceef895f16818b06901c588cb4f71680c411c0a6e7a811ee6e71b0f5096304e6b2469cb131c4ab174fbfdb82d0d9755612428bdb0f78f85fb7dc4565580a61ab107fd4cc0b9ac381a859a8dac21d182c00efc11d842834289f2c3f487b761447d55fc6732427612a66dcfc6af7321812de5277a3c681f7200016c0574a107f1c133dd68d1ce966ca0a5d99fe1ea1a77e61b0aecccfa1273a46036a1563639926abd89d323eac27de61ce714c9b840849937535767dbc79925dece58c574bf4244707e91aa2fb9f26c01bad21602bdc064682a30e3f5e5a48c978d2c0faccef90cb904fd1e7ff7b13b67adcd221e6fa6ba7aa63d05190598dcc8e0a1b60cf58ba335aeda1d983b6ebedf045d4e77d11c2270ebb158c2655ef7fce3cc45f39c0a83f3d0e4104d0c88b71cc45dc235aabfcc9cfa71c115a9161fa08a8f946c2d205c023740cf063e0041d98a91684f080c5fdb00cf4defd83cf92a813f93ea01e1e91e33d8fc60769c8274b254dca39fbc8c2f2aaea89d0225eecad34d1886bde1d9066959e853474a71ac6fbb44714026c3b5af802c9b977aeb83338d5479c9d7ec01c86bd357d04c80e4eeed0564ede4b2444a99d9121b8a823934c270906b48ba2d1b35e0057ec2608338c70db90b3c77e5548a002d88fca8b68d07136a647de9d2b94a0dd8f2323c60ff2185237ba07efd8d49b4bd809bcd0b22a864ab8a279cef7cbdb22e0ecacd8cddeb6fc8fe24d94f2788d91eae144874a53abca9e1c019351654663f5ecf40e1b256d13a3012fff19af4c119a5d0a3d256c87e05dff323f3d89af38b8b6975abc69d3a5cfd70a78bda5488d353a97bb20805262c3884eec00c098c9472a9c17661c273e05d409c1d7dd569a25d0f8e85859751609e6805a8079169517dbb8cf82671034c8ccdaf73f8107d191a524d23bf0853acc1ebc400305425371bddbc230e35675770b0c5c27e0d59e294db4032b0fac21c83bb971c4a57242b40c14d088f9b0b3f6fd422a951da18b66a38752273df0419a74579e1d18a82bb8d21628ae1dfc1744deef83163062281099fba4840177acb4f68e4bc97c249f4a98c1e778eb63309528785e6ab1bbf9f48f1f155b0fa44a76e764e2b678c4e3282e9d386ee6d5e844981fc0d068ce827e092c66d7016f28260cae6966b6f343a2721489a3113fc9cb5a7b18044a2b3c883c4a076c01cdc6853036d4eec04ad9737bd327a350df1fbda99a86da8c321057903c15f54c82eb040daf29d310666013fc8c473b3242f8643d1fea179e1a3539d4bbbeec7579da5f2d0b4a0057639d682b1cfb7fca089c02b9ff52a68242802b71292926a7c2206572a1903acb4f705704de9f6be532ceac4a4b30014691d0a94c12096d7d4659a86061fb91228c073ea4b0ac2364351573000dd3ae49964dbbd915341d3c548430916af268b05883b15a0cc851bb7e755a0325a0b12fbe7b2c725484e50ee3087ff80c2ab5730b91b5e5e2bea2d2823a40a68745e75d0810cc1a5e9bb5b35c1c13af83ed9a1dd43984659e4acb80bac93f55b2efd6721635a0b3713048288f2a61c5082af585a61c621046c37133df6877d84e79f9d1fac2490de14c798440701c6d41a00ea2996e1288467cf08a7825e88ae82358fe05293ae71f91ab4ec498c3a157c01910a31cc017e27af57f926f1b4a11273342924f7a0d08a48b707560adb1ce9ae81fe0cbca97900b771bb5619a88b133157fb585ebcf264f26e0246a29126c18cb7692456abdfb48c1a102aa54ebfd5ba3ad107faac1bbe07c8ac8c4fd217e29bf142ae7d386cb0ab4e560a9db9bbc25effbcbfb8f35df51ab5e76fb81790176a6b84331800015895b09e8d8caaf5a2fcdfc518a2bf8a31ab50ca30fd6a28ab11f96ffd8a1be471c34613e29ec673cbe802e17373373d5b069f4d8a8531066ce2687f0c32e0056c1c5fdebdf3d163b32da327c74d45f97d1cb31e73596d0f18a3a0622914df5d2716788b3831cdc3bc4459bb9943150ef090bfdd1742ac89b472388cf746ce7c6e6115e31964d4d22849c1e50edc4c42fe91cc4d7f2ef3f0f66898183514e2b07b72b73ef9a63313466878209f0b51901ed12442924d60512bd10562b500965b18e8487c73564df6a393e1ee64099c13b880d3004e31cd1d3bf0043a9d2bc52f6875d7bb22efb00e01bd10256817ee8c7d02935cdfb4925868c2e556ff0a0252c6f80b41e3ae221a845b2e0d07188a6b1f22f533a02f2f13bf2fb06b317577764c40f057808b3138e21f7b430cb79f800f16f899372296268643d6c8aa884beef196ac118b2355181c687341856628a9fbda1222b327c99420912bf62b838599195d1763c1f7be40aec21581be1a12b2d7e267811a8dc4e0cc3242973a15308c24732306a05da8320926255fec276050ae29782f8eb2aa510f1d55d30682edef929e6ccb5d0342f4411187407021b5b11b61a4922ef93e94ad1abda2fe548c4a81b79c3ff4dca1db0984345ee5a064d070d09c51b7a89a423e97e198c75d99d0363112df24cc35c051bd577e3e6f1c4dd0b8d66a5ced9ab0648c8dc6ee700d445c3c3e5f0368168ffcb0a13e21202bfd678a94891c7be25fbc7f8b9645fe645c8e2de4060a0e496f2e1627673064fcef19ce19e313c1fb3384740fe77dd6155804e46fac78b905d906ea28b7771ddfe2cfaba204eb48d65c1c33e2c93830940ae44e5abf50e0c50e67c7b6500dc981f7831cc7d421a9cad4119787c1bee501df5b3e419006a21ab5cfaec44c6b28bf082ac31865784d732ad407974a6b9cb3fc788702400bb233c8250dd636e4cfa559e73dfdff531f7d151aba75b13c93d007702759d4a0d5c7053d3c1f84a46dfeab5f1782e766fcf738eaeb2bce8edee1a08431aa0a2c5743ed412a0d68208398d503d0104080c86bc3cb4901448a1224f9d4a57e49cc5b06a2357ca97f13fc3067a72dbb559af7f347983854a28597e0640619c0faefe4e306aca40c6c836b92aa3935154a7d2387994c9eb371374eaa0f64c651ca76eed4d2683286ec2a6c648e9e4b1f69aa91fac2c083824a1381c583f369b8069c1df44e799f0ea0af8fafadcc2c0804db33a7fcb5e324d55f4271a63d70ba7ad9ddbc59e440316f1ea85ec74e62d1d5a9200d965145f1480d93791ab88bdc71cffb508d587e6636dbd7c447f5416f087196abf41e65823eb988cb9341a9c84549ce4856d4d35545fb1936c8003f2a637751f408daef9dca9bc63121197e57c1133520e770a5c19795d9019c9668a1546f7a318b4ce74a0c49e0f6363ea597e60c77db1c54e20c758c58ca1e30c27a22e4304be0207c5d18d246253521d122c008222248bdae69217f5bed647a6b6df7b43ed3740c8f2cb7d8239e0b0f2fd82253956b92ef29f123b034040c8c841b013f1cd23f3bb0b2e595d5862b560d52fc24342b656393e7fd5ec1220d0b97ace46011d61c31b2cbdc87626858552362dead4bb43feef46d346d8133c72f9f09786356bef5354ae1d1720be9e0e382cccb9215f514d52783d63749d5bcaf56d9287cd287230339627380ea20a0850b5cdc3c4cb413dbfb493f34c827670404e281f5d34b80e1cc129f2e0c99c6f3bcd475a38c10e261d22460f6c68e59c50c328ebc3e069d1bc1860c69d3bc3f7682eae08874ce7f8a7022e130158eb8488c5af85590e35a53dd5fdf54d716e3e7713a331151b25386de025b0eba5362f4b8297d66ad5e6f152e9808c7e921145ec445cc0712379bc324936161c6f5cc4cd0c353c12683aa6a424a502f3d78c42cad6a5f4b7db57d0c364e32b3e8d8cad4d01c6472a33f002257cfd7585af45a41084f932c2dfe8acb5b8822b79861729ee055ba89b96fb025d7a6de8bd9e6297dd89fd206e4fcc209c29ea187d8e496333d2789a8a6dc5e20ad045363805188fd2d43aaaf9aa99d066429916e81e1227977e065a6ae7f3b85ca5069a59dba92d96ab00fd17203a110f5b9892251fa731b8a8494c2818734315e364e8366f66f69170672ed93283a445207fc617244e11ce338ebd22d5d7bbc5b4138e2b9075055f91b5a28741a71c22d857e07865504197f63c5c58d9b4556aa6b742fd0109fef09250c4c4cbd037a2cf9a96dd8f731ac821c863a5130bf5e0f86f498f5d22554bf1552200f753ec72111150b412f80e2f55aacb764a9234525c2410545c445efff46ce2f2fba6d58c3fec874e694563b8350820d4af9fbbe6ba6d14c983b3b6716b8f26cf73457fc576ed54206e8eba3ba6b25b9cecf347d9a06edd2a3dae8ce97035fc246050206568ec815dede61d503cff6e9b7753604cc70bcf80404082eaed037f629d3fbbec371e7a5a077ca0d9b50c9d38ba11bb4ae6f28032af931d3e183e4421c39c91acf01a99d6236755b914b8655d29fb86c0e543569ffa995eefe52d2342bd951147cb1a4bcb4cd4d69d9d080aa7bd07b57e15a0e0ffd8f96dddb49381bd0ed88defc964af041207569e67777a449d6934dbbd359bbc45a4b8d218a8e9e6729fcb26169f74204145ab9e2e67f9ac7569aa9e70471d0c367925e9932c564828a2ea5c91f7aab2415285edd2d224671d34e54961a5b1200a25754824a3fb596b699cf108b538040bf2f00d43e8e158f36ce6a77079cf78978d94735332300a5c663e441ca07c7415f7582d52cc565fe08186c0939a0a53e596b62a424cc454683919280a218f8e21d8fca2e0a0b609bb035413167006be16a1cb9eae2f76893444efe8fa26e0bde54e54cf682a6f91a0d2faf7c4f31d9567972467452d2524982c6706b242d35989285902e7503c14fabe5aa442fef51a200eb495490e6a397dbfc4d594ab7fae388774a3a775a3da383e650521fb5936b40b0beb12c1b87bc088328785b80981f3510da3114697643df3d280fb6608b84ef69a7a5904d489334ef150a74ceae6ee6d704ee2e9baa7f4281fcb16f4bf315774f49ebca369678186912d871e9cd21f8dcf28eed11284eec6be42140e551dd3ec59e9344758468dde9e9ad00caef1f03e4827aec27e46586a51de2d26fc2e17c470ea67463130669f0c6d34224acc32458e2747edb44ed167f33fc2984ae8d0e75019673cce3130e6d4f549055ae941a0c6c464519221f73c728bf376bf0ee4e680da75e910f0f618b0a3c599f425f67acf2c31f6fdb95b38206ff8eab309ef755f3c2c31965131f290fa771851352125e7a795c08159c6dce4894d046aa1b6a2af7c19d8e8431c5ebf29997fcc22b7841d2039e5ea8c01195103574b2800ea16fd15388780eb1bfb2bb052062be6adf61884f043f871f9179681b40f2edfb9ae04d4f09fc1df56daf2a5c651f6c1655666822177b1f6c27b3a2bc5d5bb46476eac65e2be24e6095e162c138b6d46112c54c97c18cddd5069b836aadd18c1112eafc6ba8a044e07fec0b57f67cdcdd9cfa478890ddf61ac7b04c5abb47bb8525cdac4776185a100e74d6e95b92c1e8872adb07a329c9a7442f5ce4895dd1cfede15d6898a54b063d8d87274fe88b3149274b56f75f5f6bf5a6ca9999611bfb2e114742f96c52600b11a4a4491b09f046f5dd3a3e546f39d177f561aa35d884e49af9c0e2edf900849213edba22e252d5767ce0404f8bcc4f6c8d974214efd982463a94e769bc95ca00bdc88a5f62f5257e62f2f5f9f5d2faf8af85073b11a176cadae0c413e57cfdd23803405502679c8ce929c3fa88cdc3e0db896d470480b1cb1b4b47be284049fb79e04ed2d7396e927fd383716a55d6a9a1443fad25ae320a87ae51c06ff44a683636ef354d841ce451b79e8b5e896684c22ac6e54bbe6f73a01c8613cc80d52437bace8c0ff8b7576550ad2043223bf59fa7a000983d56c3a9eaa428b73e498832606a1cb8f3f7fc3825548527004fa224cd934c06a8d82ab3809418f8adaa2892395419acd0338386b6d1f99754625ce03630924dc74575cb37ea3b4151951fa9b6c1d3ae4bf689489fed48e86078b3827d37421ae1d146c4e9569bb0274419b72661c39b43376b9814ca79f2fa7b28ed9e04a3d498529413af3b51373c16d0ce9450d6b74137e12043045b530713d5106fabc45b8767b30c11ec4c51aa224aa6baaebd9950afe32c82ea597765fc020b567f681c7247275e748ef440dcc1c3463105d35b2ff94c83e1a73d2ce7d1fa0246bd20bbe58a97835d2830278bb9406767c80d8db5d849a0fa73d95e06636009b583e7a712e7627a64cfe5866efe2176890413af4637fa870f6340b501b0393a8c5e4a71c4e71f22a594f6eff9c74f26393e865cde5313fcb432bc99f000f1c1bc69b32aaa99c3fe0f1113f83ecd5a65915bc41db9799eb6b1b03d0fad3c6be77919ab232e9b6947d02ca6e3350f57b46f4d0068c0e319de44b7823b5cafc24385408f55a95057109fe411d2d77421af97c490198168677da6e59d8c469ec699bf93040b851a50d8f57923a2b36b25c5024b84f7225fd6a9c4c76f389dca07c64fc1987d8ced0694e106d58ebe39a001e471f1552b831b5f050da9dc19fb44171265e8f0e1696166579104b09bbd4ae41efde8ce8cf49f4f0a987f9120962c6123d25008387c6383b252b2d89435dc002017675f5efc51b486213c6608dc104da9ba66fb31cc044229fe9ac5b3db9efd0de219e511893b408d3798bb57215137641565d5ffd5095841b7a3ba6c4502fcea53910159284c2ff910612f2fdd2802d1441b242a0fab9d74a3fce6137ba649677933de48ab67d9ac2ccd62a31844db8db2016fe6753f2708e37237d8a9beb85c71e2d981327c33823a8d4207df0b0121a4c38badcc72f8f650ad8a3f1c964d61410a553c7fd5350e43063a438adfff5fc929b5e6e1d0215fb11cda94fd962a69645bfb0397f2dfd9c1a2267a56cf6c14234184b7365e5b880c9ee6fbd5f5e300502344afef7ff8add0203ca4c7e021f0901f5b79f2c99745496a16dffaf962d4bfe02aa3b4fbde7af311ada018384652168fea2579738ba3daec9a2a614fc1bc6bf633c5e4b5e9c4c29437885f927690cc55887aff87bfb58dfbe18f087a0d0789d4a454be9c37e733f6a39b1a14ddf0acc8c2d4c1db96b3a42511a01b3ab80dc2c50265493cbeab9534c8fd878902b7846b72cfb4434a63b67bc3ceba0ff42bb8c552572a560c04e9c8b76cbda952e23031faa0c39db5b158833d958451a9a6e320729f0b40c69dac259afac3ecfc852d05dc22c71425d543951a6f592337fa9c4d51b0306ecda6d23c7a6086fb5d96564dc0f717cac82b7679fda27bedb4d79c118f66cf64873e374023a55e29389186a0e1b8f64e5baa7f0894646129fe468ea9e0b39ca15be2343988e5a85344edd1fab58be7d8add79eb726a5d0544c836029d6374dbc85f93636cdd0fe783485e42d708c13344d208a9eb57a0a2bf368f9f1978efb3fd57501e5818debc2d3281d176a029379b0ef90b29adeeda8249654530ab16f489d43a6f8b04fca74d48d42d44752e2206c64e5185b3f8ef31667ab347b0f4908c8e963fe49d5cb246904ec6bdd574d016e0034701acc1c75c86c1c0435b9306473cf8a413794fb46840fa559adf12dcd7c3e09f86b7b17c912afeb705c395eddf457d125bc23d745674d3b7be5ba4ce3b9464b93095ca11bdc6a65e3e3470f2c834caf76ebb588c3fc654af43cfde26a0e0ff79626de3cd93f5f8f3ba5fc462c6a9928914ab0f2ba7ccd50d14d4e567e2e9b4e1e50f60c8151d11f962ec175609bab069b70f93b158618d416064138b4eae630bade7ca82bbbff0f54180b654ccde3528a950eb43bcd0cd30d5bc488fd5123ddb8b5dec0f395d73e67ea618a501f0a3d9a31d04332b92e9af355373c1913dd5a98013cf8f2637beff06462c9c5ccbec42e3237bdd7347374d9b3b16940be20ff8ca6119368087edb207bdcd6b00cd245d9447f9e92e19548e9fcf72ea2a8a6e1ecfce44af6864a9758b5eccae389e33e742f235ca10007ac79942f4cfd1e200320af1c6c6938516268d3823bb20f53b8a0282c6ef2d9a5dc3f0c9926685c3ec93d6a488a6de61b2b57271f7b05a997c20a87b080fdd8c8980c4e600c37845a4db55de43b023e646074816fb68e431660bdb72db70c661cee0888a9db65fe98b8a1d574b7950b5e9e51b24146007a2afb9a1a7c8352a10edd1fed170c93753848c0f70e708c21b552fbd9255ce646b2ea69e1fed9110a015633d987ca8fb6b99fec50402a5dc40d8a349a4800272867b9ae501fe9a94fcec2e7e86a209210d9bc2aab338720f9e28612ba1ecddd5cefb91c7dd5902f24937365028a879e11c17a227b2b38cbc6d2f79794ad52eab5d1a98ef13aaa3e22eab5db0c753e1ed856349e3a07ce6389722be9fb40077ecae38bc3b619617e61450d276646e5879c70c1d9ee90206960e470fe5da9fe925e8adb5672f3a7526665807f031d960c749d6cbc73a0ee040252e87ffaf36703e1bd29e1d540a824631f2418e7ffc8f7dd35c9327d0fe1373d08755ba1f290771fb65ea28bfa1d1ce24551743ea2f5f9e4f2aae934d870afae0dd1273bef0c2d6e85de10d9b59b601d7e3d0b1933de33ae204da73b335c366dc9b5188eeb059f7a5c6eb2228d41903c0859b0ead3d901868274fa4068e3c813d00033741ea73c915dc548822d584e368c0afb9c8fff7d4650ae2c2051dc60ee3eacb4e37afa2d870529cadb159d725835f05dcd2e56e16c2a14855a8ab285d8cc94af527e35605952cb29ed045ce53ce96d3012d2b87cb5dc9c5940688b2ca999417e43f13d350ccf33f13a8c24599a177032b13fb0a9828bef842959b645bb4be9843ce7bacfd24360f686807e329afd84902dcc15c93da620e0216276e80ad7e32891c526510d878fb53da51ef66f25e457e971c56158744e306d30ded17ba8cc9703f94bc2c663ca5456c99988df30cba8a9cee778d8051fa62589b34b0a5bfed80a7ac5e9f8b8067c0676581436a7046a87e95aa70532b8a9e64899a4a1d79d4598b914dad5df0276cbf5a0c0e6bf643dd2fb9ef53ea8e356417cbe71d05b1b81a51ae3ddb78995c1490f492ead7046b19c5fa0ce2c89175d9010c59bf2695a926bcf96ae79d347fbbb8319e9c2adb28752a6b9c1fcadad91d8d95b286b34326808aa6903dbd806fe6e7f1c1e899571b0bb4e22296cfd7412783b127b1c84d762e11ce0ff1d7a75752e198641454cba8d94192cda93586a6988c8ef74d97f6d083ff746376eaeb14ec5a1d6dda6ef4f261a08acc46032a2bf9b970e7aa8f6a6c4d7857d1fd2290477ee669400ed6f746a3e5572b849367d029632653ab6106db9fd2e639531b9e1098218a4f53473e2bb3d165b365b04f09cfcff0ca8b42ad7dbc66e93900b90020d984e76b5260a7658d0826120c8087850a135f7fe5fd8a30b2ae7b8c3d9fbcff4558b267097dc8eba576a2270ce990030638b75b7d462729830b5b57fa46638c6c00d6dcab8381c768a80131ec9b059b39beb66a77973a406c9339b06c4f376df92b49ef6904e741bbd04417a93b388909ab029f1c5db8a19525995b328138b99748ea71456e7a413241b33241d030ac8dd5d5c4014163707b1cd0e9bc5f1b25d7fbb7c3b7ccc80b57226f47da9cd3aa090ce9aed9b97c99f4b96b177130434f5f044b6c3c5a68bca0835bcf0b8df831fbb12c7e6b41f4dc96f61b6636cd2d67b4a5f804ff482464b9b244922654a29bf05cb05c2056384cf83cb4f3f94103246cb4b5d903f84b0582e44878a344992244992d5e52095e2a2bc4ea7d3898ad39f2845013eddcb896047740d85c169fcd3f79c565229a8405788ce7890f1adf0991b0aa3846bf6fa243ae6f35be130df666a2714bf82f37a2b5e892b89a6b1bfe4a7782856f0a753105bc4901fac7be61d03d608a5ba1d90130cc33014eade7befbda11008a6bcde542873280cc3300cebb1d1aaa27d62fed80fe5f90976a821b06359bcf7db7e58adddba17bb376443947da7c5d5c2d9b4f628ccab04d134bbd429ddb44dbb30f6bc1cba3f346ddab4bb5bebeead9b366ddaddadddebcffa4157b0caa7e3b615acf2e938ae082c8a6cbeb24c4921c6b53056f988e83be8de2c5f9cdb40a390f320366299ea609867bfee7eddb59b356a35ab59cd6a5ab679b125d42efd9ae6477d352d13d9bc5cd1ddb4e9fdffd13b2a84ebbaaed369752ff5bc9c413014baaeebba7c34cdd7ce78c576180dc57ea2e9780ff1835df5a0c0758566d14f7ea233a09675cd3753500582601af4e4613860f04070030e5f5ae6b305b5744c0aafe0eb743a9dacfd0f183a6763a6f9add6b71690795afe7c26d3667295b7bee5262c302569134d9224fdd60a0fa6be050c3de33770d3fb81ca0b5306372440cbf400402652355e04920973349fb80933796e3299a6699ae6bd366e4c9527582854ea89142567cf2a8a8a8b8a429521efba2e123c9dd05ff6b9f8d59b0f9a977edbe72821b0d85c847f9120830d5b506e830a19a03df4aa4fa1de7c93d237e99b660875a25028140a55832bad8ef58c45e061be011df33561cbd0ee017bae39d3ec206ce489f59145318e23a9ec1142ea9c929488d47154a94a6e855dacb550f073b57a900b374de747b221af72d2b1dddc60bfe1bad15123d040086b0e1e3a22d034ae23819e711e1ae673d7f9a85a6185518676e27ed241142fc2ebb9ab1fe4018badd27185d7573f198ce3388ee3385e570d1528a59229c584086aa754723a7215e148bc896badf59215c287b8f9f1136ec4061a7a16fb8b8ed263033e680002dbaf827e72afdb0e158197c475a7c6832ac0a107ff93826b16c130003d2a5da552a9542a595b23f4b182529369753b462514ef755d2c2ca15333b78897e4f2b067b25c696a0b1fa92762a75eb2104ff53cb8036aea181e5c3f2f91c254c634454d2693a99e40d44a92e5ebb85bb783cb5ade9ecea853601d554ca1d28131de386d0a3c23145b955facc0627ec9c2c6831d8e500a8ba1971a60f1caf0b2048b311c80d6af069a3d50433b096faeb986fb7da09d84f55bc15361d1eb3e22f067838733c24c099c4307f4491e29a295003f797af0d64b02f2b390b72b14c81b9ddc2852a6e83c89e61ee7799224499224795d35700f1126241126b2bbb3ceb22ccbb24c0a07ba6bc2e17b9b841d8e8cd20f94153e9ee79ea184a29b5ab2acd600411908d10297f375bde0fe8944958a2442d14d36b250c846e0ea50c500080eb836290cdb68e0b1ca5bfe1da41b6e735296d910bd6063ae80715374a4d48ea4b247888e3dd924b66489849fa91d59cdda56f762ec7939836028f494d2cb0bd15014fdf0421e0b13d833cbcb711f98a8cc510e904d896ad4ef420e908da734e2c9329090115594463c58f644e3687f9891bd241adb22ec108b72c933f2f7a8568764c707ed4841375c21e602cae1d9b1b292735cf2ca97b3925b720e4b9651bd3acda1147ace096b761790e6dcf6e9724e0ecf0e973ce3cb99915d3e1a38f728c725d3c026954fcce1f4c901d57da20dc78386f64da0472ea01ecd9872b30cd4061ad6cf0606b8645f0be8078519594dd33444f40c2dcb233a0b7a648950a435605d526849672e130df3f9f244d1636b60c78eb9453aa67e57eb6299acd0eebc35d99114beafd81213987e383f6c18586cf3e6871d473a8ee368473b86f5a654bec5c465cce639cebe8481c57e02e4ad00e28811977dd019f046017bc6403cb8669b5da39a96fd014a53f46819759a8d701f298fb68f86e71e61370f9af77bd22cc0f75e1f3548911162f5125493f07307de65027f7ed087b756268c18b65ae5ce977b2f4bafe15c86e3388ee39274ccbb736e39950e24e2bcc6008d002e947580451f3fa1180a49ce43d770dce751b4cce71b8ea4c1ef3e97a26368a87d5e96b4c440f9ef5d89810b748d972e43c3e5cf16f09a2d7fa6a169b416ab9de01a2e53eab8da0758995d1e1db76929cfeeffae95e3f8abf1d3e273d9e6bb5ce3931d3bfed4f87c3a6ed340b563ee83dc83e30a40d45a315b310b002af08cfdec9b409b66c8b699e301d05371aeab063686b5f6b2172e71e05e620232c4b2e7a74df3ee8e5d1d0e232b39fe23f41bc21a7a156ab889562d485ce9f02af40097452c671983720b9feeea64c0fc13b90e33500ee1e59f58f3f6ddf061be40349c7b98cf5912d8e66be19b45d1a783a11fccb24face177307c7819592982455178896c3c0c58233472411882e3382ee7cf4745c64ade01c8387ac99d5d724bd6998157be729917508f3619a097fc0179cba7e3e35df8cc92678058be1ef28887cbbef21a11b4cc679719f9472f2b36bfad805eb20c900a885f5436caadf4c8c572d957befa924701957000298c4acee7c301c85683114f96731875c7ccf8d1c67d7806b843140f057a84734b969157f2966f68596d2e777b6e9101bab7e5e215efa2c16081d24858ad3c2e832151ce9d8d037444e066078fea590574298c73ff29dd2772b350bc95c9dff01f7a8696a4a6e9c65155852af543dc756ef62cd2abddb19a200ca267e8fbd03768e5618817f40dc2a6f1ec45150d856ed131d46c9a6adaf19a9ac4445b49764c74a6a292784d6de2325d9324e19fa087a84dde888ac52f51533fd22fefada82615559b5cd7a97bc61d46afd174681ace344410f4d4343f6ed1338fe4838ca7919ec60424000441a01452f8f154d69e9aaaa8d1666c4ad3383d62ecf3c7d17fc450972601a24b744c915a83f0082a3c8d271dc6204174580149891105a110405c25741883a800054200319ec6244e2f40d2848d0ba66a661cb78e11bd70aaa083b516afb0788deb0842a967169ec01f09bc0061d82a40c2046038c2e03ab88ceb8c54e772d2785d1876eb652d86a9986853ead4bbbbbb3f4702ee8cfbc3cf0a8f7cd499d6ca82ec89946cb56a619c1287cef4c595d65aff71b390c2a2af2ed18d274ca9b121464bd42d9e26e76a85d918f4f0d0d3c3c3e8299c9e6ef5e0ac56ab9567c3f3562b4ddbc14385094f7cd05a6b075e7c20c1627e5a6b39841449962b2749cc23aa26282524e9799ee7d119aab3e309c93478244a0025144956b4472b27d414249df1d6553eab758d17293c57d4ac2a3ad3de2354499e4ca5195a928eab91349246d2485a95ab7255ae4ad34fd2248f409e48484574828c438a189d8c41c62145ac56abd5494de7399a3facc6d509c6d8349d3a72498ebc7704d8cb28f8ca66a8cea7d0353448cbd0d01d466ff452892c91259205631ae487d2905211a5911c499324c91a42a10c8436e083070d3d42a150284492a927caeb254ae45484f49ef968d7f38f86f9ac7d62163e97826635ab594dd334da1d734ba552e95dc1cab2fbddeef77341462095600123e89a175c4648d35c3d3c55ae3b991052a74898f530cc285f28c153e04678145fc251a46b4631cd9b8186f92c468f934422e517722691346d070f179c4a77777777f7aede1ca5949abc887690144c24f426ed0799fbf943b52ccbb22caf4b8b5ad6b2a2ce28b5274935ab939aaa4c52a9542a954aa572ee546aa351c4094a52a32fe2c4893412c1100b5f60b1969563a10a7cb5b004ae496a595b50026399c6f5ff6421dcd6a2ad90b5907a98cf57fb52a48ff42454288a42b196f553dfe2add042287eaafc944d7d2af5292f371312944ca3ebab9ed37c7d34bad68f8528b85d86e690f3c341f4aba74e0b267c7d622d3b500e38a168f30d34cc58ca06a7b0c32e7767013c8ee3986b78d5fa7d8afb5ec9c37cd674c01555cb0759080277e832b496652dcbb2ac652d65144192e5ca4912f388aa094a094692368796e18ed13e5c75d0198c1f80379017587452c454872ac791cb95640c2d43438004e1cffca030ef425ce603ed76bb9f9bf513310c046088816878ae76c7e7a6af78d4ef668a1b5409b502149193c934dda04c138599a6699aa679af8d1b53860e7ad5cda3c70668e8a6d775b917b829752170a61fcd1887563ce86ab55aad78d47aba529038d01ff61867173dd0a104477eb0d65a104c65daf54e5afeb30603b30985301b1d5c8a7a201f6d1c8805284001c228078866c5c84d9e032a48ce491afe7d3e2f290c0cf7f9690c8c41e1ccf12948be388d72806c4c588dfa5d28401023ee5df80ce47918c268bb3ee0365d7cb5b68f9447da47c39a2c86ad5adb9349a8ce94a73bdb7d9bdfb66ddbb6bcdda6792c14efed981c83f79d7b3d3f315a5a71ba7165813fbb3cc1a2ef34cd969f07fc7da287bfe5a7b67d94480fa639e557f48d2cfa86155e6891c2c165ada67d626bd9d5b0ec6adfaf5ce673876a012d4183bfa24318117eeba4a413cfb2e8e797bfdabe5c6ddfafb45c7e6bcbb9d5e556abdad0228b89f9b39853df2a0974bed9b42ca3f289f77e3e11e3eee33e31b75a1c07d6e898908d9b8ed9c1e3069cfaf9171f4c96144b88c565064b904abd7492f4141c71c4e98b68bef32e84e5092c9fd862884159348bfd84e513fb0cc72c7379cb2fb0e4cf083080ca27e3dcd2322d9fd8397896ccf26d94a394858585858525b3b8cc7099f1c98d9f252443c60aa8c683a18eb121dac183860771721e7427722ae2a907959832be33a2d66aabbd31cd9badffa7c09e6f9ae69fc98366c7d4d4fc0c5a86e6b57833090ad5738305169dc6cdd987606b6cd3b80adc0c30962fe87234b016b4125e1f17e13379f0632c0ec2cfbf45cba85208038b7ebeea513553b106d488f5390163d96607c9086db6f92b963021e2a6693eabd7da0e862bd3c00f7e06b984b7090f839d5074d62545665d4e9e60dfd9d1988096b33c03368388c27c773842e7953dcfbccec3bc83713ae974d2e9a4d349a7934e279d7daef499fdc8e4fb890f8f74b9bb63185823e482c4464130c46303363616134ada41d0dddd6ff0d07bf0114f8e0ffdeb4f131004c15a59250a957a2245c9d9b38aa2e2426b8cbff4b07bf66299a6699af61d0e2c7f56bbcc65b15bee99cacd8cf33a508f34500db31e7da77d8ecaa080b32f14ca26ca35bfd84d09bb37a29f348d4816ba41a15028146adb68183b1b60ac29d87996251d3a9aa63b555452809df4b913bf441be91bb507e572a6046dd206ba59f4f30ec1dd24539c43601d55879f5ace3e1a375aa641811e693466c83ce20c00b0e234d241013aa91110da8339caf2075a4223d9870348619ef6608eb6ac65d1f3c126fac66e6c422ba1c15f3dc10b25241fc10ea7ea4b4c4cd5a952a2a4c7799ed8799ee779de7b73aed06064b15aa723534c52be54e4c717c2627dba4fd77517cbaac0a2b35827d64a7edc7d3eb9eb3e9f87f38ae7173d29fd0cf9e17c92c56ab1582d91889e8e3a4f2ae564b59a8613f2319a46628d45cf9375b2ce2d735974161774c5e4c1a74d5c8687929e22ab7f1485d1314d2a53ca4404f53bf74fceea4c2c16cbc462b1582cd655a38edbaf149165fe2b58e5d371db0a56f9749c870516a909cb70b295560aadeeee6b93650765d9b5f9db319fafc96432994c9a76a2678f55e7b8accd19e833c34d7df4394ef843d163a1891e9f50f44e9c448e1a7ae17b87492c7a5ae81e9d494015de0cbc2d3cd149abf644777787e82a1402c19cdbfd5d6afb1158f4540a0473f6ae86e323cd5d9645279908d08d1e0b23984b8c48b14003dc581c0109967b7a1eb4f91b7d39b67d46ba8fcb9a566d1c908d38fbbcc44ce025f0767531d40bac7181dfc5d00fd21c4225a1b761437ed5cf895d951281205523b50245adb56bfb05fab073dc0812518c2320da1045463c9e37202ea8afcb36713a91669323aa22292eca9e7b3a7912272fc36ac7dd8ed3a8f0b8ae0b39bf3714047a825fd0a61c271dfcf9860ff866b7375eb361b96e9f13d1beee98eb52b400bb0ccb1758ee220446084ae8c6bca75396ed18adb5d65a8b596b2db6c1808a2a701676a1e88516eb70703178775805d8731294babf2021438c9aa6691a160aa5f22766cffb7080bb58a5a0ca543e1df7659fdb5d16168c3d2f6710bcda6adb6a846c885eb0710087459bedde2d9ceeacd2dddd38c0526ec011515102a8e85bd7759f8822c5eade7bfb2f765d1fe63baff3c511a81110059cb45062b4b9d0e2240fd2329f7750e94174538ecbbc7773c4eedbb13bf7ee6022d1cd329ed389349b1c5115497151f69c4e2b1b9b3055390cc36c9aa63ba92801b2d1d3eb7ef423d2ab4c0394427802cbf7c222a27f813c9d5ab4568e0a6b4dd3344dd334e94c2804821e78c19013312851a31c200ad0190269c4e3194808053838473c5a1645d67e8f553162cff1bd9c7212e6eeee5cb6a00fb492d0039eb9bc7508702f412215f61224ca5026ec25488ce193e4c665ff9c84611223b9ce45b6c8d8ba2772022c3ee9650042dba3cb2d1fa8613cbb69e24fc5cd2fecbedc315e2a7925f16710be8b5baed7855d6e5a1b1bebeeeeee9fb5d7755dd7957178c92e19072fcfc88fe401a4977170c92f198719d9cba4fd8e86e7195eb24b9ec1cb335e5cf2f5edf0f20cda61cfe4cd57758c0a26f6e5931be80554432b096dcf0f7cb8c16867809e04b9cc7814280c7bc44bf2c12dba00038a49924f3ec992f1019062b3eeee16ffaabb6d60791865fd70d68f1387fe1d8e6a2ba537887af402ce0f51005a851911dd116a02d0ea23882602167fa2a1070fd66a87b53f4c2c168bc5fa11b2f1e1a1564abfd5067cd46bafa555cb5b7655bb691976dd5aedadf6baf7da22863ce6ee8e5dcc8235b831fcffde7b5d49ca2ccbb2b274f7a157d7d5ba4a4f728328fa0676c3d6ec701922f80f2e0dbab0fb6291893c51c64cc6fa86a57d03cb9e9b66a84a7778749e0daf2357b0539e1efef59fcb4d5c64714583249779d702085307a1cfb235e2943c4cd96a95996e19966559869ab68347c912c4da16ceea455d6932f03ea30116166d1b823ddb8cd3575f7d5191c5b0ac0a1004310631884bd6087a4813d137b24c81f01f7c6479211c08acc9215d7f307a66253fdda125238b0f454616df0832726733686615460d819587fc900a9272313e2b327056c99fccb29245af594dcea8edf0b18a505dcb5a11f075752c2cdb2643fb8cc8f872808cbabc8525217564393b060c7c84f0b38a11c2cf1f28210dd2cfa23b40216475cc93a592e82caf94eabe3f39f102d327e7833db0582c168b755d66ad6fadbd027fbc2458ec966a478808870b85df753c5b48bbce092c86b4f0e66f5c81b3f01ec1622806ec490cfebd3871d3c239b55aad560b27f582eeae5d5b2ab5f4a2f4ba905094124a92a5cd46391b6b6da8c03603010720ec254a9284b69b408f74ae6cb3d8ad1728e0ed72047001fe2e5aa1a396ece0e43d95dba8a0ff1f23def702fc9f93f02181c56ed510a769bc898ea9a23405141d128ca309383bea055224a2244992f43728176bca759ef8576e93fa5c8434112962e3505e08bd2e6e09ced12a702a7c65d246d3b4ce33d6c960f34b16d8412fa105d56e0277dd6bad09ca186a6e7930ec0e077e50088c57f0bc9bb13cc3fdb04c02338e89c88ba05bad6e75abd53af2048f154eeb3ccfd3da8eb91e93acadd553700267d75ed9fd687099bf9db59496e06cb55aad160e86854129a553f0f6ff5448ad6507b81a241403096fadfd2ec77605c6e1f770db82f3dcaec09e4bdcc39bf252447e9ee779f6b8ae2c0aa6b5d65a6badb5ed3838f7629c7316e1d0999c5bb9955bb9955bb975b3ad954195123aa71c209c12e48887cb40424490608d78b454a9d4342dd664537466465e59242bdb4412d65e3bd6644ba59bada6597b5df6ba2856b14aaf6bdbecc57da41691eb889ea12814132a1e1b2459b52c8acedc286090c242c5c406255bea9824d7674976b4452c59b2da05f9fdecc97e3ee29456ddaa944e415114652d2a8bac9106515e439f582788405116c5836dc1684b2b9bea9826e7ca96e8ca96ac2dd9922dd9922dd91286995e13ce28986952cfaf188f42dd7b31db11d1fd4cf4f32c014785f0ff47f03c8ce1192108660e1411423734b4dc619685e84cf9284eb4ccbce9cb0b9ffda8042e3b5ce9900000000001c31500001808088442a17048342295b7b23b14000d537442825e3e1b4a849124c9d114a30c328621028801408000c150892c2ddcd8ab999bd4543d9452a28be475855e6441efdf869a9c7eed04c8b24feead4d784bb5e98b0eb34645472f2c0aa6a5cb1e45c9525fbb94b0cb7f78143c9c394150da8d0fb07dd83214b014cbdbc732b77ac29132d9d931009fcd1a086ecfd58179be1a213dc83a3c45775a5bed482df17138484987626691f016730dcdd1047c6aaf9a6b54421a328c5e79562cbae5dd98150d7d18fea05a2d2282bd9da99e3b53515262afbb684310daadc2e33b5bc08160d9c1027a6e280a71d70e120f70c43c48f97d58de2761878cdd226f85980c9a9e93cd6f7ac287d8fa75c023f002e713c5b76ffd48b5ffd85062a200daaaf1e0711042b3a8126bc2c5640cf13778af074eaa29f81fd4e241a49165a50e9f539d751e5c906d684d935cf592db0d35020ed627c712ad834431c04ac64a13f7a72aebb6809974d250527c7c41f7f427597b5cab489f7c2631f34e88cf3ae4f42baf87af76ece5576c5bf0b49233e4484a6fcbc4d814942b2a9c99f313cd00d97a5482901a68aba5dcd569425b9da6c8512bda8a04f531448d64143806f621a0b7ea653ebb21fd1296bf63551cb58412662de6bc64232558eab703222b9166339d28217a538498d8c55f451fd8d1af76ed151e5d86b2ff932ffe56c4c59b4e0610aa12840804debdf5af4577aec948eb8fa239b7f47fc6f92f4f9f5753b41d7a0a1d653f6c4625c01e609eb15af69727ed58b9a14b2cf2b0ef42e4cfc1312b79cc6a86359867f18b96bee4cca441a7ba31888c4c3ca6784cd22a999a54e6700f6238f43715d7c03c7c9345e2eda3e80047325f9ecb2c8e083dc48427a206966e39142e75cc1cc5a3263331e9ca51639ae5996b82cc04e58f19ea44f3245cccf0ed4bf25b31ba3103ad980316cb264fe8bcc08934ceac860f64eefba4d5969aa5c18c1d7948e8424d9199def6ab0d9a1152cc7d11fc038e70364293e7f79e80d20d7d2f823710d1549f325459250e8157debd11b931e82c9662e4fd850176a1b6be3b6c7d3bdd29ac108fd47ef07dbf639aa9fb076792061d28b6dd82aa88d417f2c7bfd63498e3a1a9818731ccbb2b967def42f8bdac8fdd6c19703c113ab6ce9e2e2236205166a3a77f4f01d4239b635650920adc89154b0d635a23c631fe10436de96ce08af2ca3065bfa1789a704e79d6c8cde9cd1a1923d65fa01e373bf629356c1448bb36329877ad885d023c5b4dcac57a10659764699d3e2f7c2c3b128553b6913ac4f039d903de3a342227c0212916db8893c904313ff3b5a92336f6b21242afb37af5c72bfe908db124646186a3fb597694b6930f12dff4a139fbeebd09a57638f42aa3d5c33258f8565a29fa0915866561aa8e2da28c3761ad2884407cef8e13bbf490ecd17d5fb1be6bb9366b8fcf79418540bb930dae190e431c90dc9d81a97ff9f5328331a95eef4b243f6efc1e13c8af4d453ac47c1cad32f35a98afbdb4f5303d54434b73a45991f9ea0be154d7fe48a9634fc5f5104fba3e8209aa723ade8f9ca96ca9c2bb4515f1128312160e910cb9249ee7b93b105d91834d27c25afd9ec80ca7d056b381a4309e6d30d258f92cf6059eefefa98d76324424840a08b839113193246ca59d296b6819e6765a3480dd053fa0bfa54a876a85bb1bbe512c7c105c9499d51b7c7bffb46680060d06dfc18a9dd4b735a6366b41a07c84124ea9415ba8dbfc6fd31553f857b0423e67d215f3b52a113bf8348f4c407199978f5ceb1a625dfbbd65e570d5485b6eaf5a58597b2103a2b9092ac094289327835c09a18acdb282964aad6e6e65be42ab39e767c9349b9fc89f96a4599b857c6ccb6f70d3b8f260017f8b9bc50ffaaaf705bec76082e2d52cf42ce46832193772ed946c0d51f87d935ec9517a2a1dd450760926bd0cf24167426b79cab2e456a44583a3a89de85bada3cb5f48f567504b26db24e66c26e00e01529e90cb19c29375688e776d1f7397e4739dbe1ca563ad4db46de9be29db682dc5068e5d8b1f1c944094d5fbfc52d08e9886a38b2983efb848a4ba6dd9e4ab1a3c6b5bbb713cc4bb07a21f13f378c9546447b40b9d5c3764295d85368a96637e3cab8bdab6fbe0f8bec7eda54bc205bc2d75953d535cf8a21d3b812a47eb8c76f51bfea362c45f6c87640d79d2bdbe392530fd0d22233d802a058b8040a129f1b7f229457e0ed2aee6a385afdf7cb0fe457e1d320bb0a90068c6b9f39cddd8cb0c7740ce68d61e094cd3358ed99fd5293a661d13250147381caf90ac58f5bb87ef6cbb67aab2e7156537593eab8f1db81a2d33f5d87d3d1046075ac7858f336c9e4f54b03065585d57298ea3d6a206b5db8de10eaf2ea6159684182c1bd447c407aa4024149f67dc69612c511fe0413d63cafbe347419a88e88922a901567780a0174a07ed577dd760886414ad95eab06d2f1b01adc55728ba22793c45c155e69bd6a40fdec8068107cb9e9e97518ffaec2545a5c3074c384f3bf7e14c80a691c69f272ff146ad861432a21a8cda43a420e1f26bd1357e29e1bd50630cda39d6c6d58ae035c4ad6120430a39c3d2d08b882463e094073fab43fb2905a94cf4a50e6eca31c4b0493857b59174a84afff3f07af2e9371857c2c53c523a93d180eb59cfc3e1b56f35a5ae0e377840be332dcf11f26041cd29ca979a20e84c27b2ed612a9d4bc3f00f5c4fdd6e8fd801bfa3c0ebf2f7e28c4f406a5f472558a880c2568dc266fe86f51c9b9e22e62df55e1b0ed003f5ab3533641e6f0d7842bd1b0776a6cdd7d63b21185e4f96ef84ccff8611ee89aca01daff09e941101c89de345514507acf9635494e88e928b08ee93bd14951b00416b84d0eb61b10c1d2f73087724c45d8cdb374b85bb35c2dc3fecd85a230860b1093419bfd64dbf293284571adfb27bf951c70ac945e9a5f6b54e7917e025f16b1204595ea3fedae44f196ee001af608028511be53e9f613e9c844dda4741ef94333cdbef91f0f16ba810baa132ae82c70597166c28dcfe3f0d9f3bc01f78f62ea20a0d6358d00e15e32783a88277903fd06f672cfbd3acecf5d7f29bab88ea85b3498e12827d984e489f360583fbdaaecfa6a6714aa7d21c8f23334f281ba678af6733400fe9d11e5e25d0d1ad1497716a3587a749076624f81178015bf47cf0106e6818b828ba65a2a287ab12398b605e17f4e97576db31c709d88e8a5585b6a1bb512e628e73731fa8d754677cf6ccd9e9950874337f880ad364826df075a46ee444ec74cea06b678132e273041644d8966a8f6368390a121b8528966239f2ec58e798a9fa2b0a8b5e04d9488fce37bce285e27b803237a5c45d382ffafbb3a0be967a2b126917e219cb090a06de48a5adfd84f623b681742e860197dabc1f004109972f51651c495bb32c0c4a9b93eb99a331cfe8cfdcd4df2eb753ba9c34715886ea2f07814b2a7e633e0bf1fd49e6585e0e07e68dfd1051ac0d928acc8a21dfc91331f8472792227d6d9102c800f8f1fa35dacf9cf3a57122b5ccfb86489c049f2c6d27908adbdc1d3aaa36f1d4157d311ff0003f7c2baa78e4f4864161eef5ca101c97eee9caebae37532cbc6b034f320ee1780e3c626cab2df674bc978ddeb801cf5232133ff18bd52044ccd9ff0912177a1cb610924c1ba15a2d31a35b428223c89d55a2227e2dbcc2afe5c4f835d8e658dd92403a8730d87b9f851d70f8a6668efb1aa091b3ae5b9cb75b99173432730ce5e2e6ed66c972bd3085200b8bcfc257aa77cc964f483df88c311b89b2d19606fa6dc3b7953ecfdca90afcb81924870dfba42edeabda6b7898530583e060c6f27164cb67b094a2e3f60838c84afd8e95a5af628822719b919b1f0eca7a68046ae16bc17940aba218da2401ef4ae03d0ccd018e2808d15a6b5a0718b24e6d4014e303f3826ced880eff2f206ebfbaabfaf8588c3c2e623f2f51bf1cea41e7e4c7a13974921a5f42b346b01759346773c6e704e313999fc3e198dabd2bfc8935e5307991f1253156dc2a2c9c516f3b0d8e8871bac88740b3f477356c2160cdfa29863121b5d6dcb917415dd70bf50691224a16809f09b104b0ffaa0e0652c28484274c198be2d0e36a948f873f36c03133caf074bab48916619934d17319b20367ace9ced6e2505ceee5aacaba9241ac2fa4d5a228ce9a7192fa2b7b2de3581f858b11ee84746dc640b9d31403ccbc34194c1d59d2360cd937da21bf0e4267503cdb2e8872c9554f209116b6c60c7d0b15f0e2c2f854088734cf646a874709f241d82ca9c0e6c1ec9d90be5d7cfba4761d64520719a94ead7fed8f17e085c0dd17115862d334067be0b9b8e4242daf953f16b8c9cf1108a940b2788d7138ce0e281b20a4353b231744df56a1cb8c97af32eece387a3340096528af1d160b6bc4d2a1354adc74e520ccf3cac1841c0662c122b12ae26ace04351ca9657dffd66dbc59bc0434e5e4ea63b1a0a49fc7bc3034b8c780fe46a50c7f8c90dd1794df8c3233b61750f470c31005a899268ac887146ad8c11e16df532592c70cab5459eb768ac26a80808ad4d0e3711c2acfdb21a80982b5ee83354b2846450aa51cf16c798327094c5ffa98a28266571992ee3929c06432fe51fa14ef291452e77c8c32e18de83a195d1cedcb151281454849890fb730ab7642ea139bcd3c9a329a11fc002aef36f923ecb0c664112d8a557dfda6eb513d98fb5b6e2254d1364a788197055c422ca8fc1fc917d038c170dd88d7a7bd2d9a1909e1dff0695f854635fc37cf9f03a066a6f8674b48ec80253b1b33eee4cdbc32e8cf5c7663b06942f0103495ef4181f79c00b83240b5d7fb635d31a19db5ad1533d78ad5eb7f5031e779ffbfd13b46aa49088b8004b6d7ae3a15b8644931ba55e535998fc83cc252b1d1ae62eb713e6f94c492a2d9fd6103a2b9d77e78fa7588ca6c6b4c91b26a3ec974954fcdbd4d362ec644b2bd68a7bd4ac95343f73d898f26ee3c1c358fa5bc574b24ba47094304be79cb4da72e345b8f33678a04129f6652e3971ff22047e77706ed21b38e44113a1f8a347582dbc64bc4dcdab25067118a38c438ed952a323a9f83a3c375d5c5d75544f0432c7d416fa6d0919403ca69756272566d0fe9052e013dbc2b5287add033ac768518eeeadcb7faf3ec4b9abeb7538e2b836f5530a2fe436a1314ba486c41bbebebc2fe6ac95e0f6d7e9ad1065c1793100f255eafbc923186230c96ba9f2afe0f8555935a54a542bc54c457755002bd6c969ae0006003a0f2b73782b98681d45941b3c5d1f512126f253e1a1a03f249e0a9b59d8c169ba8ce5caf66cb16c74fec5e74c46097c3baf86302acaf298b6434926e9d337b30e5247364e981e2250fc4d6f2a4e5af3a59ebc51bd6382e86f35831ad7ca7ba4b6a95c1cfe746325ab5569f2561fe53156622ebf91d08a3c377fb6425bd6b710613937c0436c4ec796cc1c23c032cb9fd4d8ceaf6122abc18169e34e098331872e81a7caddb273a6af965b56efa1bed756313a8f8ee8647f24909599c4a2196542a0f96685b840e6daf1848b4b1fd9393409d76229d402340a325f6b1ce00297cc4680b79fbdad7c8bbd341cae7c95b92c1024ec2eb6f4976f669099f0a4177828589554a1e6dc8d9f47b9164aff79661a1e11fdd1e310d2933b224bb08ff5a86250b04c7f6653a0ab6ac9b1e93b9fea3ed7b3ac9c76112fc240ca3b78319fc140efdf24770d3f4306d6a33b1a49bba76db61c478be52fa4cf948fc51a6a587b9a27288a5ce44d857c3f047db005c13235bb79019d1fc894db43914083c9c90a2cbe950491fc22a5fee2805ca40da169e83fc1e6b82f5e459abb1dd35e1d6600c3b4d00f973c77325b9405b270ff2f55ebf047aacd26df7c2139698ec5bebb31c9037c183f299d70e35e8340072f4256dfa56d93a14b9c851f40e9b94b89790533a4a808beae3c3b83d08d105656073183c34f863b8f48768bebe057e55cb5be67a04f0e2d7ea5a99725de18ccb1477a74fc87ccba2915a16a593d49cc52687254b5ada3b15f61f9cb721bf1cf4bad678c2cc2b343893f9f0cdfb6566b0a14623f601185b809f957855b7a6f7a6a6544aa9456d3e85da043b47521f0b60e7505b0c335a73b5fe20d9301c668a083dcc442d65dedcd77386ef481be2c844d4ae8cda706627c9598a6854c3c77a6e04647e320dd7e887a1b8466f188c363fc31f9fc94170fe55b210d6b80741c1fc746c954dd99d836399066470e0fd938428a7e1780e1f4be40426d1f529d7626584d6934be930bb74393fe6801716ea34919cd327c5849e2da71ee143b1b134cfbf12ba1cb60ad5a49a9d80e72c4de474537bb21d27e0612532e872e90de18a426b08ead20b64bb0086325754a02e74b95cb9a45dba963f10b630d5a15a6e4c09581100aa30b33093c4098fa8214362fb676e06ea4cff1310b4d694b05b760519e5570fb1e522ff088cd2aa1cb3731797f6df1503a4bc0a7857f9086747f6de797278dc203b5f463c6b8133217dde9d1fa50e6cb98ae2b6902e727512fa332c0735b6dcefb3e6144c3756e2c69835c79b29fc05a15140271d86d973cbbd5005eaee72d9724048ca0b05bf6839488fa1e5002d715a943d2820a65f1a58e2352b55fb99f9b5f4e492a12e4c3b0ba06648350b0f240e0ac455abecb750644ec9064f7ce55073192d2ece691d217c3507705d6c7c61524719e9ca3222f1b8475912e4ee159ceb3b798abe032330945a539458135c6ab713e67c7184f0260702683d2a8e4c0b13ec223d61b9bd01823603a25724e2d174eb60681b8bde11de521de241c8dff470f782bf55abf5128ee0d3902cb002c74c5cae10e76271d6d81fd0475ecf48e551628ac9485143393589e85878c1374c6f24e2247285330b599a56670381222b54e57d3c1f38eb00e8dbe5e7046f808477978bf0ada4a7bb7be62b1c9aae02bb7934731c3be8c964805f3287b46a2ee216e66d914b916b130d063616a6eb1fa44d004bdabbe70860859c9675f89fd5978a6a7d601a8a0c6c1fae93419903aa3839e24ed43ecd44a80df420364e7ebe333a295f95328ae6f9a3fcd908345ad595069bd72be9058b281f33e45bf519c5f1714fd39b82e473a66ae4098982c6b7cbbfb7b30d949e94624affc426e449b924d16cb8827a640bc76e18b170fa8cb6548eba488d5fcd4de90ebf8437427ecb2906e65b061d303a29e66dc3fb44902e69ab1836a86207e35841309926d3dcf3a3e6b5908c1f1521f4fb9c34bcdc203d796a8b0be8a385b92addd0a36b247beeb210c0eb8ebc2f48770fd299055864fcc5fed0f37d0b0c8992b6c29d2c3af021779503ed1aa89ed2c3095c43e2860e50491439c463b290a7138035f51df94be4576dfed226544362c6aba12f8b37837952208bb260300e83525abbcba683344af6f7c164c19a472ebe85f68c54264fa30114a9f12395e88ef9966dd6f0ab5d1d3bfea1e8f8e36196769a885f841a27980dd7f47c0ccadf58434f82ca748728cb91a525f466290c34993462d9a71e7a47f0e5bc3b2303be19588d96e1b89b8ce24a114da4334ece8466647034c093855ac8539f141cfd72e89a0de523cd72f2cc65bc1ce9997dcb7291c4424ac7c4e6c2e1a5982d36c6279fc3cf437dd149b5971db863aa414fcd673c84b159828a1e9fc66412c90a70036c181425d6697b84515b11df40700f9d857f1026f711c71ee06e94e86c476ce1cc6cd1b81cb8c69ff05a416933e06d6d3cd18761cee8060dac5cbdde9d9f067b4d622f39e8d3f4c221f343f8c1ecaf2c6d5b9287ce4dd85e43a99148aadbcba7ad20fcbca2908c27a0742b821aa2fa84ba5df63795b17ab7a7598b87fb9b728bac19576f6d9a940b44b964924d82e765fa33ad33924e0db10393f5c19985a36f3c0faa42717072f9e2d9a34e048004167c9dd0ed4f679cc12158d91466e08be8b694994c934acd82e7d452a9cc2b4408b084341a74443863cb6713d2d378627414eeac776fbb3cd793d76ab71abfefdfca86eda324276ce779afd6bb02eb8a6a8f232c0452273f73bad5729877460ebdf9a663ccc4f27e7f559375d252d37a0519b1fff036b77863480d83f0a2b529506d045422c94951b18b221a269be1d92846dc607d0da70589fcbe88f9785c6e60c62f37a940f889d7875266054b07a596cab30915957ea5f201e44ac5595279d2cc7e1565572481f395b24b2bb880e722e3d738a166a4f213a06ba7dd128e1580a5893eab2614b3fbf6d6db963071358cd20bb2547aadf04537d228fc6d1a72baaf4ec38c905c04a770f0b236433df01584455731321862ca75d60ee4b5fd7f5ac9ed4380ac7d113bfda8f65de0d5918109bcf810a3b401340879b4f4e9571b2f5c1c9fb1a71fec99ee75747f6b6efe9a9460c001ecdd2a5ea819ac85a6e5dd8886937de53d55d81ee982bb5d682b3c0e921d8ddc94d7122499950407d112d03d8c9310e6cb3f58e7d31a90e47a6d7a729fbc425871ab26da749446f36b70c8838088a34c3dbf02748996daf5b45adcf85e57a88049c85b9e71f2147ca5dc3a8d2e6a1f84b68bea956fa149ecbd97b822d50913f41f6902d4c58823d9e3ffd03a911d04ea493690bd0d4065028fddd9c3088edd487b5887f8588cbf8b70d88a160cb90aa62536b6334c5dc5001b3e4485dfd4b8de407b1537299bcf6c36492d7b03762e3122d59d2fa0a610e8d4067a0bcb816417b52b34ef41807b65fbe7545ad51f11a82b4a395f498bd87b90915b19d7e1ecba217de937d3f68f85b0e23ca03438a623811b25505499286c3d6bce49b3bb4fc701d5f45d9cbe229b17ec6b901214eea6c5bc2b28e8695a111e9c8e25360b9dd536b29193ee9683ec8101c3d02276b85d7bedc25022a0ee8a5feebe209e568e868b66ab75febe44352b82e8b6155517c89c4442f265a25c38466a0fcba093ca29c4779b4a8886083a39ce39c6d6c836476ca3d497a43c7fc9621e5eee377f5b295b9aa40c0592f460388ba129eb18683a861b447f8085f42df80a4e58f068ed7df32af9078671d953e839581a456cca4d034aa923db9bd42b0723c46198a237c0d18cd6a6481e08edab6fa438b4743cb2474486e4b8035ad6fcc4530c03068da1c04d62c472b823f2f11d7a5e6007c84b2a93c9699e6696c29ae5d0a4937b17d0f4344053f481183087c2bab8fdc5b87fa62873cd53811d911e598ef194408376707b8946030aaf60689f790ee014b8de67dee1ddccfd149e469a9644a4a996b2f8a7a8d8ac4437209bc8df37ecf054c9fdcf2eb5c7cf87d2e477a9a540286e6d821e898f2ff22d5bf63bdf879db340f9dab9ddcf984a0d53f1bdae520ce76cbd19d5efcdf1ed36dfc1259aa6c202494bc4e02f60db1e8cac542162ad04efc1b77b4db45a73604514e3519e65224ac4ca4fcc4156680bc0a7804ba455c8f673070a4c3fe00b02d5ebf47598a635a18099c18da48e3c5fea6421b6711e9d211b437ac2ad60ada11d3968a7e729f6850e60a06c13cf2db1d17cbb4ffe5905a41f877bf454a24909238447cb2ff6f60497aec953c884b35fa82c4496896c765fecfce8a2e94f8d4fa7160d356a1538a0d87e80538f9f0185c38342c8e0b5a7553432ef4185b06dbde434887f6b2f9dee6d0e8bc59a458d226ed979d86a342b2b331133b72d4e89dcc152da2a6952ca3f67ca0b678d34b77dfca91654d5b45e3177b9f2888c8760ad99e99bf2a21a05dec8a82e574b1cb8aec136f8d9c58e1d921cbed385fd5ca04cd98b20440415916e30f2d38c211a2d4bb96e82191f7085c495f65fc599f30628ed0426078bb8fc2a81eb485213d6b9b8e3ef5ebe8d3dd2d2575a11597e50e26f7d32a4c50a9fc762a95f55d07b189f4039af12e4f3afb5d1bf642568007b70d96e9e93433b306dbf826f8340fe257444366729197fca98c1422e64943d46fa4f36db1e819c3096b59e9b24ac4e0e4940943f6599fabeefebdc6d14eb9165edb05f330ac5b6c0e27d17fa953c96a2fe53908b8d88c4cf76a18a8fac1641b9172768f611a7d3c904c7d0dd0a0f7f732dcf78983d1f7debefca4b0695f1c3955a3ea6bd61561a01286eaa89249ce4329ddcdb6bc11835d5b09b4072d14d2b1f75addb4a908b39fc3ba179c6c23d89688d4a374e8fc4a44bd846400cd0e57fe0ac52c3da024e82b94c3cdb084685e028256418cd8d57430617c9c14884bf877dd871c74ae9498bcbfb9173d38722f84602a18575e2f4185f5bdf44c44ae305019471ea44061d52c08564f9259d3d73d328a636ebe6132ccb7e8d6b6841c1aa00c93f177c4a270b52e3579bacb8c37d4badaaabfaa7758afdc594a2d09cdc02f66d89f03c780e3a001b8847aaa2831d97c6cffdefaff8eecde42659dce336870af544a12a0b1d58af12e5886ea048a275e4a25cc01c98d8b66f2fa2b5c774aa18528259788466897d323c54ad109d97f05763c8e2e1c2ccb7bf7fc0ce200cf596c6b8e28ba55666e0eab2e3fdfb6d5ad00dbd1ea6773fc56cea76d806a02ee0d8cdb632039137e56347d69e18b46715a5f4809f6200ebeeb8dff6f6838acfeb260657f58ecc0e0c5e6a6884bba4126c68485ba31f37343ac04e89e863c893f129f3e92a14acb844976f6175c01472b24c56d270116c7a72306c070b99b18822656006d40253b1a7428fc5b990b717c568bd05cc7b11f1c13b9c2907ce224bd32ec89aa9c891999dd2526d9addf0734c78465186b42accea6778b5f06dbc507b27886c515172a13e991a915e7c7f95d181b30d20a864f7b7c3d8614c6dda8e1475be097025355c8a49d671d9b46723200eb0a9658b803676ffd2d575841afd2336ebfc1f53ef211d4043bcb34fe88c06aa0c446706b8b3b2283671eb71f9568e39e1215ff4372088d534564fd3322fd25554710dc387b47f44bc4fab8e1b6c707e9980d9ff268ea72f0e2afb2f0bb70090cd1b0c94b3d61031d64e7d23110b6a846b26cec0b5560eba1d02c03e54320f19aecf6aac4aac952585b8601800acb40271a67fe84c7c317b3214d8ab36dc2728a8135a780278e48de7475467bb39e9b7343ec8f8d08ae9532e8800400c6089778d57e0a12a7cacd0a7dc5b5f880e7854073ff2109118c9dc04445ac2f0e7ff78b4a559008c6ee1b841a88c8d906b00ee5acf97466ec723495241af1ecaa9fe5b87a7427bb9356bf05137f80345092d3fc339b623a826e6d2f28213c83dd1b95032fe22bc7f2687b2c88380ac3ec67ef491b7dc6013b1ed877371b72028ab0a506ee4d36250438335c1f7f3cef52435f7f7ec7495431a1644f5863e4d1a4250d60aa2b5536cfaf7de04c51abd4de05ab98d7869d64df4242174ccda7d22ac7322379e4d19305697244e5124495030241406573cd425cc4ee9bd4d57c033f277ffaae35fa1096f3d96e4eaf36dd302bbcb80f64ea5c4a7978dcb8de06b1824c9cef5a7c72d741f6609c0c572b2a65953f24f71cd42bfc7d29a019767b7b5885e1485bc31a8ae30148020032cf944c79cfd3b100d76ff7c99ad6641b161f3432ca1d70c5db91064f52f86f977e71b5b5f489167e5ec635081fe9584180a5dd21855e35b95e53ad4f8d6b372ab4a9a39a6abfa482a2d19256d5f63b837c4f3188bad3785f4514d00a699850f37fcf53708d2a0c06915286d87adf1f5bf8d355063c7d605860e73e537f15f4806dc6bc08d1093b77a6025beb459624ed64e539ed31e95e8ca5ca440c5c5193455a43012b7885cad713249cb47ce2846c0283a0c490e6880c57beaeecdd5103e99851790f4011afa5f325e235877a5a0bfa96d38f535810f688019782d41a36f358e4265466a0468721eecd40f8193352c4f6aa20432475984255634430b6663022534997844970e1ef01bc69f4f63f2814369d2f745cd6dbb6089657f6eec3d081850cd36827b73b8fc911951ad2b761d81dad57462aedb1102365ad864f5b7559b924e0c086b05d425cf2bf8d36d99cb56add289899f8a7153e27c6a266128fbb88abbb8ee24c4f8f455645177cb1c9fafb5a549f95cfe69970c3f9771e732adce75ec10b16acb8b716e0f22481889b92b846525942a72ba408a11c751b59aa81c7504123e09a4162c554dfc0911ba01c62e4434b542c78fbc35f576520ec9d2115e4e4d5a7369eb72326276a29a5856c96ce2a86635025b70f0ae0d84f92d5a8442bd4d8c797b71d9f2df80ec33f2f8f36823d38a39bf37b77ad434e09704aab409291dfda2a8c01775820ab32aa38020b453c68d7bf2b9dca0401d51182d56bae2b78bfb411ec771ef526ed0323de1777690c47c9a20566f74d9e86f03b33a4613e80c45f1e0c341fa127e0e68d36d3a2846c5b4e944a2da195f784658acea1e9134be42dbea2206f4555bf142020aa876930b272764283095da7fe21ff07c27489516d9dd280c12ab7184c3c55df743977c669580e6703b0f98bf269730b4036ffd27bb32df59e6c8f996f539c046f880a99f85b2602917da7059fe718875de643ffce75a9400f7363574b9cbd3223d7d718cb7d2ec3f3e9fd3582dc4180ea186ede1831c07fe295a4c5288239fbae18ed1d11deab78d40c1217e6bb663fb742dbfd620eb36950a025045a1296f1c8cc58d69f6e99aa882101be12048017edc87860a4a9e53dfab05ab783ae6ea58b237d364f0e1e9c5871484c94ec394d6cbf4da1d54ddf72c08ddfb9f4f0ff393c5ad830534777bb1d4f4cde55f3659a8837f459e5d729ae3184105352b3e69d8fe44131ce64e0cec3808685f7ac4fcd40684646025b97caefd5464f295e8f4e682a74202773e3b15dc9c2cd7eee918a94fc7c454ec9b789291769d0d32ead9070df8284ecf81f0cf91b56f2e79871bb100839c550e1cae97a346dd6c62afdf4edd7e810bd910e48f717c159f6765a5bd2f3d601cf63427009544f0fa6abed4c316d45f10e50afdd501d33afa9e105a6e4a01e5d6c9dcdf274e015b7f9f008a259974f18b9e4d54aab50c16ad33831590e00c96f9d6af6e3c016f14a652b44a327787dbe7d3ac986e84da957cb4aff994b940a922e81ba2eac0b8f4be962e3ba8d554dff58e8b897ec40d0c8f4a680cb11a228bf3105e32617b096bf67b319b64cac1d4c2f74445371329a21c6d403f65529de61419a800c9b0247fcd3cd8f96dbbdcafd730657e651519359cc2bb5b08d98f819158ca983629e2239cc82d13e46631162cfbe46262b1c7dada16f2c36d9b0c520ba9059e92a496226f9736c94c39d266c98530f51b2ae9cc0f3a633011e2d730a6a22563b272306dc398d5e52ac580310883bb690595d3957542b4ed3a711dd780585f5982df60fa42d3aece90db5fadd375f12e988d65ee80b90083614da7298a8bc39ff1d9d918a65f16ddfe890ee74db420f8964ac240c120e59d942b0eff6f9271b56b23cba943efd4d439fc41da041294b8f263f3ecfc14298cce3b6b22fc6365067143721287443c2b33ecc42ac551b30a3f5f00a75410a94698a6f9a87829e8de2d9c6b2564e118ada4c223d8f44b0cbf3f934942d02a4d4d0511a99c530618cf69b5c9359f826644addd9270d0675e06fc5d1ec8bc2e480762caf181a47ab0dc13a95462d5cfb93c3150bce59c9231b7ed5034e647ef9d4b21b960d0d6a898bc528551ce0fc3a027ffbc48bb6de3167f2a0a4e9b34498774b8f03c032f46591f1e5b8e8900d1bc41b404b1b756afce3c3136dfda82d4cc1a5cd7b319a2b314c33790d4b9ac47c22083743da1428b803910891909bc200536fc5ce2b45df325e0facb0864356634716975bbaa784408a06219900948d87d54e7208ae128e29792e4ea8fd560097330308a7e8ef08225629fd84ca6f0a92e96c2178e5184aba21dcf7669ea49a2722e93145ea34e90816ca4306fbc759641a882854e50d0a4173a06cb1d40bbb4af82e278933a5030d220bbb944100b3824a05507ad6dae71861c8d38cd42d68c62c7ec37be50611861950bf32661865fba32df5c787b951a86f8d775ca81cf594534503a4d36b76e495251a8855801b0d734579b0f8bf4725a16508503bd16f46afbbfd8a31fad25ee017ce43b32651569f5b26d3bd9e4db1354910b8cf51e40040709681115b9a05d8c582ada63aa48b8ad53f186f716852eddd94ca08a40aaceefb10a4515d9930277a5f10a19cadad1eb3e9f5930cbcb2879ce29c67a6e08fffc77968e454646ceac21dfa2704590027552259af8edfbf5c4bba74853c5abea7f7b512872d2c338a0bed9bd85de5a6b982f51d10903e204afdd090ac23e4391db273bfa630750ce748308c5428873b92858a481e771b90ace55c5de1c6ecd36def60e73e43491c9f4c787889a619d90a82298e3b0969a88a03ac0cedc5344acbfae1d3d1b2313084a2da8f87ba1d6f82dc2de50c66cb43c0291c5c8cc0e7ec135534e3e26ed040915622d22c39f7dcc0a26f137b6daf65acb96aa95ff2b50a6c7ffe7f2ab0954ed6e923f1809f872857f44e54c264ed7732d28376d9d4a1a055007fa10fb923b28d259eafc47a89e9756ed4357708067e0f6de7c969dd6200818def3bf5c421dab786992512a3939f467494737d48d0c0d9c79a78b7af3c2252418475ee41573d6fc47c9a1801ec50ca36862b1fe984b313fc083b0d0f9be92476e760416115bbd4bd87da197e1cd83874cda87c0797136c5e0a578e55d7e71d8aab97ffaf2286a834db791270e217c0d9fd5f706db36e8f24d481e821f17ef33dd99797e3674fa560b28a1f38af89d1312020f17f146a36c1d23dad75c23992c613a9124d3e5545cfbd3051b91e55db54b1b40bd43a3f8fd9493dd1e3d5f6e48ed5a29bf429a614671f9bdf410479363bf5e4a7ffeb86d3e013628fcf8baba271c94e1bf6e1f16da3d772b39d75304b746201a950052c08f9e7d04828e86847d186aa1335601ad0204e836bd51adb0345e22508484b6cd3cdc9c0950916e318c33b0e74e7f0de57678c8e712b1c49ba01e7238a15686ecc70963a38636c5631ec7df0b59b85d593f2817ecd8a432bf6cc6caf9a9729d7ec93af35ea781b68ae94b550133c35b7da0c8e0f8f4b0b2ea4eea7dba7d39568f2acbf999916ef77f3d9de4ed73c38969bd3675a10c04c9c8902210671f88711973785f216f81b6be9043ca3144faa6767232edb5f6bfa7963ca3d1c9f3d5d81b28ae4d5cef5e22275433f5b4ad917a7f211bd0cb08b0ca7663a2153329423139edd4443597871300b00019ab45225fcc210cff1104660cf48339124969758af975e8a5e7cda4d37e4603270c6985d5facb53334a215b520a61802926db529552da0df2c0a1d2463628b36857e1e1d94813e212d98c984013b66246fff8bb094a59fe4bab6ae159bdc900ad4268d2cc7465d74fa49cbe73cf8261180d1c5b71f0ed938ae74eeba71cdc19a4a5dc0028ec16553fba0bedba34897f73a25aac2a8b7c79fe2a7e287395812d434c3bdbab6b0a81441d6495ff76dfbb55319af7c93cb4b6e9257b095d2c008844dacb0825f6022fde172b29b9cafb5e2b7e622c789d22eb73eb78461043a173e2b933e28fa50ab224578da1dbd6fba8d219dcb0fc7d15a2c1acefdc32a07dae30f0177b15265ec84bf153da268cc814640642cf9224341849e9cdae095a95328ca43743242924f9c1c61e924c9b38e117ad34c2d78e90c5fef223119947ffae2460a98ffda68add68482ef2a462689a320894dc1ca21505b993ede407aa849cda1d073298c966dcbfd935170382c4d38eb7a72f533645919c6a71a48628a91a831da57ec9c9b76225a00a47dd99ff7b93046e2f6240cddbba497e997091b103c0e144aa447641422b238adde70758c94311ed098c55ea92b407569cb365a8563e40474d7fa959dab10cf06a34392bab28821b43e00614fed85356c3a1aaa55bcf61260f3bb43e93d97394845a885e55a0be27988c2c48ae0737fd175fb38598c3463c62018ea43c5272098b73249ef7c4befe1448e86384c893accd48514f76da6e391d2a91e7cf669eeed8971b22cf808d0e20213299cff0f6f5860071ef14a431ca3c91dcbd96acf168933efaceee2b411c3a05215e3f23725e70a63ca02945f908726579ca97a85d468d75945e883d9106a53dd93265564431b5747abe0e48d0cf96782189dd99e06ae73e4fd26982f595c6ecb4dbf3507304c1a5511a345be9049f10fe5390c22dd52d2aacef0235a87d1c5cd7a8da2825b12d2e9bc54b91b6dd1857f9c7963d8b58f6c18e05c073a3224ca1d117885691f9458d3f292e54dc6fbe542aa982b9746f1788e8e0a318e7b0e24c458a7d2389646df1fa468e8a4f0c275e5949e5535237c57a8ba7699316ceb1d407f199ac0893679398916ef827ee280f3b6e8c027bc8029ae130d35de8f4ea640802cbb44baf51c8c9ad262720902dd02424ff5abf0ae738b83fc0df9eb46d4e17ea60b3b887a273aab899a30a0827ca0f79e0b8bc65829edfe9119cc504a7c2a691d102e74efc41000a039c7ffd13d6267262f82b4c85cd8986f274dee642e56e0009e97e887ca37137e0213be7c5c6d16f771ecd40f15f69e95ecfa5234cfee5a426aae4cacba826f1fd7bff17633cd826251768fb6b06f0dd44f50c599497a60015dee1f1ba9c3c16d5e3b7cdbed23ad6d7bac65004cf4f9613977563f6aca206135003781de7617aa53590432a14a841c10fa17d3b7009dd31b0150058d569b0d735430ca428c4a00e98d7fcda98d9afad85c07966b9d0374b0c38453bbc5c36c331a3337a0bbf23730783a31f860fd166eeec27db2eb80834f5b84625e345207427fc3d84633141c661b4489054fe0969d61408c6162f6f513c8160516b1aea89ed26516407d5605e6ecf9d77044c1da1e63ba72d49aa19b49f1e4844c70e0e212ff9467ea48b4a5d0760521d03af1081b83fb61ff05c664034c70e4a230ef8236b00ae89c80e62a7051c911d088c2abd88ec8033522a68fb87db47dbb1d03ea16cf1c1c9273d99ee14c80ed27da91d1d341b0c78d00e1e36723b956a09d4ee7f2eaf586707aa9b7e44ba615d7763ddc85fb3dea804fda11ffe45b0703aae2efd40b27e5fd01f7096f3dd9575d2325397c3e0e09bfc5308ac41b21ed448b10a32823f2614ec147dd003f97857224a761bfd7c87fc01271dbefd6b8b63831746132883b6c432b878881d462f8c64b907fe23e234ae8611747aac8dbed0dad83849d648f99cd6b6ea21ce5483bfa8c6a4504021b72de308264694001579647cb42e6a66a07277a56b8ee3a4c50e6cd4a65961d14a67a7b84fe0c7313e5fa2c4c43d738380d8ae5e23938b361a3eeb4d770fe657e9a198e0c4a623c17e337d63e9961024365682ca5634ae910e8e0d2d495300ef162c670b78368f3027a87398e2d36f36948c8313008f4e062b85f7bdc560c9a66e1c91ca9de004f7b1cb38ac571f9a81c587204b47bda16e60b7d644c7fee79f66f2eab53edcd93b3bcfeae6db6dbbb63c21704fb048c8723129d2c40539d862e6f37db337df49d1412695eaf3ed515111b2a4aa0fec3dd429d127b449eb60a25a9100f1466ddf30d9c76611a89e3672b56fffc605839890635541cfa5362da2ea532a34eba9b10c9629fa14ef1bc0be4dada92d00906f811ae3028297e1fd156f84fe4f0303bc77217be53897629b1142cd96c9601f57e6725472c0b3716c2110e1b20824cdce5ec02640fe2bedafaad274dad4bf6aa8d662b45ac47cacd2d74ba53d8bfcf92957966010e825a1826df349107d95feec3cff04a335d96639134e7dba1606dde82c2ae0dd9b81d1651c37ff369601587a6c53fd5cc8c38adadfa2ba43c934c129e653658372f6ddb0df3e33544caf26268642839cec97119ea1bf82efad609ffc42b8dd46d474136a7986eae95973f7acc1acb8195a70d0b3596d931c24857839abe40c452e390bcf33e975a4e4255d487f5f173e56bc05db85fb98a7c8069d4bae97a950ad51fa6cdd788a20e4d2afa788191c2303977955ba1939e5c8594670690a369f956d10cd266b1409f0ef1b30592a01fa2a6b777e25149d24de86446102c11584e61e26f9d37b09f735eeaf68607831bbb16075211d896c5d27cdba5f99482edd822f44042ae570fa249e19f50563e056e41cb4f019935c04ce1f478ad260569b721afe497856c61af92923c2edf16028cf65477fce56724b13ffc3d30991e89b29f101831d715e00ef4a304bcfa1a5277a94de16cddf5eae3c5fd9ff562312777e04f4ed4ea5243179fe5f44fa3e64f26df2d1df59d4f73bf14b82f2cde5bb9c5109faf9eef0893137224a2066a6f9aea3358f664f383d1b8b8cf3856f80334d3958283f2d7bd74902b28f1c888b74948448ca1be95efef9a2ad0a18c57fc9759c6fb907c41c89d2672f12fc26b90b376562288e5549e95aa1ca447a245d842f19fcd5197fc771bef00cdb6c4a56854b006f6448cddc84be01382325e85930ceb74c852ffe206421747bc45ec856dee7e67c952a78c255b9e6a0f2bf3248e97935a65cabbeda869884bc533a94c7dfad72406aa24c37c80c23419fee28364b0837c7d2156c3a50b051e2d1d49cef66b9d7152509b126bbc7d7801b27c515311175deaa8384a0ec2e3e715aaaac39ed95d7de7ad128ff1273bee0d7cb3fcee70f3819dbdaa1339b41e995efaeeb5a6a65e7015e6ba1becf7bdff67973b606b5c936b6245d26d0af47fc746b53a9b4fb3b30ee36fe48970febe382db3d734ece3f7d43d0c59299686871cec6cf7e09401317d21087284f384c9d9ce30092db0c5fff2bed078e0343c35b3011fd138de43dd35567822f7a422476a558a381c231bed794f72028b43eafe664ae5394d4db411e5203c5da6a32658a444b4fc0ef4f3c6f661e73f1cb09048b1557c8fe6fd5885c8b7122b8ab494329495597b1e504df7e399e50ab21de2ff8e0f6b6c60d792816273432c029b431e8b17a111a7a3c1e759903671cb8e1ab0d924d294be502495e23b5efc941ca720e1e0bbf2f56352ef8262f0a62f6ed87d4629e4fe09893b676221de295afca56bc3247eef64e262c9377cceafec2ca688dd873c4be3db2abdc246a28c7cba2de78675d7361d76c840a651eecc0a2db9ab4e2244c53ed942c3e2a3ac6910a710b39cddca4ea2e5c52b2bc660c2568a3c7583060b5a38bf4c7f3ea190ba8dbfd33db917b13c1bb45217b54019f53085be7e1de821d500a90c64173c43782a49adf5f5181c80dbb7f41c1f9c9e13468140635d8cad9f0486bd0a10b1ff21bbdae377caa8d3e764b8b2439b38708578e672382de17b17a20b7a7fbae90e62080a143073a48098fe0e26727a84741ecfc5bb473b59c71fdb84f3caf7b6d1ccd13c68f599f8fe40efe26871f641fd3c2ebda29736131809387d833420d65c2360541096242876668f6344351d469ddd307640f6d2f58d48586c90cfc69340d7758d3ce9a0d9f2775e0835a6781b6806871a5ab7151f2190168919d21b95ef3ae8bdfb28251d50d6650cb079bc6e248c427e34a7366e71cf048584a90a9f7d185180076d3325f4f2c30c1be23a48385e12c2b3af8196e47a183e085eb1f02237317a52f875576d062d2f0e9b0603350312819bb6bf690ceb789319728b7c045ceac783437796f38b75bda27b9a3d581a34c3c5d6f41b982b017a991b3147c019656e02af822f1b05843ba721b4ab531ac04c383c3385c0a76160c84901a579db7bf4604a20da31ba8ef88886a123fdcf95048fc3a184234c59a17da25ede799bfaf9fa3b7e6687dcd87048d5e92ff39a229655c0d55aac3bcc07fa3684e2a357854d0e4bb529d7af08128fab5def92e858b01e2dfe27552b3cfd03c25cff80c77490782cb0d657141537ce0da170c5315a42671bad21b472d2c6ecebf0deca0596b2af5111695b398143c4c717b1bebb7ecdffdb65c62a81d5af918cf6f0d58cbd655da5c82effdb54960ff63364658aace409f922f2671e66358953347a58481570d3c0edd9a9e0a52174873424555dc29ddf8b9a5594e1751004d016ca8570be745feb7ec6b0a47cb2cc79e63af02d06865af1ffe14576f562e536f7437cb70010b23ffe718649584d0507b4f63714be9047529e808aafb75de0441980c5035a980530c3e38886fe823c110e0b10f54a8cccbae674f1126abe51666a91d97814a6970a32d39ad5f76d83a3319d1865bd14fa2fae908edff19564b2f6d5632d9de0e7c0cd10471a0196629a5d61e99b09c4664f07a8320faba24164460e1fb4e263a4d2c8cc356f51a08189f95e2589db59ce086e884e30f5dd3e3902ef04d632fc530cc4479f8f166a3b8d6634c17c6684b2b1ee3a6c5b7eea07082fa4c2199d5279de8567161f48145f7de3af54e94f47ebf4c066ad4a57abcac1730896e23cad79aa8b5f8b57e4aca258fe438d6495649024f0e308b376744b5c662a6d1ee95c2bfdfff2197d486988608a2aba9efd1733342e704ec0c42c80ca78f4b6a4572106ead8947bcc8c01735d465d4c4b77ed1974cff9f3e28a04137b0144b52a7efbcd81192855b8c5f3b9ad29f195de871c4926d4a1aea5c176ebb4abc54bcbaf0563bdcc26bba15a58f9adf699d4e5d53c500d6f55aaff687dc70b78e0e3f762e240385cdd1521f13bf5c2d2ebcce37baf8b5118e905d15180be43f3d831c7c91ba7674ead6eaef5a18be999e09bd51f94156683f40afe48e87b1d1a77b02247268374ceef84bd1cbb8313c75d3819655e66fc6f8d522b94a518cf068e4c9f76d72ecd5910c8354f7d71bb194007723d16be13bf81c37961c4f67cfe9b853ca7d350806a6f1c758c47573fac9da52d46d6675e361f698d66588260358a4d918f59edc01488f30e8ac6ed7b5af8951aaa062771fd4dea41deca000c2eade0a404df79f19a2427905b1cde9fc72b0ada361dd9147a168e2fa1aa46304affa74a4e6cb3a68d3b9db3d248189a350a6735677869ca4783bd1ac7663545ce792b1dca963bca87db8578b52405837412154a84929027719f8f84c62a2933f452f0ea85131a970369b24df26211f74cbcf37f263ac32a22e128d80981151c435e4a24bda86b105873e3d8e660a8cc23e1698e8a146a5005f2054e9db911faa763fcbbc0205c86ddd23e47e91a76895bdf3f63192ce712ecf12c6d714cba02ad6b7c3810066ceac7da83c1ca30b34386e61e18797161fc774163e909b6252152b557bf6bc036f319dd7537f88b4358a241754aade0d866bf689c6749151dd9ebef34216107da9ae61a0ddeca5d3eba7ae18e4c9653d14865a645973e5546bb530b430039486bf4b420373355953677a5f0500acc856c816a874d831f3ed858e9c1e7d3e2a00f06b475f363a053e6b846945b6b53e8f0de71eb37a10d49252acf174a529d4298db602619e057e041044331ad64caadc7b737898a93849bcd1ddd96e9f81bc6d207dca8e6f1f187a0437101c8d9175d6ad37057a3fe6a7346500126370d06b8c1ec92aa651ea3b7cd17ec37912f542132d448c3024f53e8d6d47ea321e98914b0a7c394be700125276e155e7256f3c532c0fd5d1a10ac14401137a776be45ab08127d4b805f9c83506636f4bd64732f9875ff0bf5a69e0fd42935c062cbbb816107e9ec3ec6bd5688e4a9b414ce28fb04b31f0be7b5fc48b80d194ed333673a6b9a938a8260dbda2567726e1664fdf581fa5c24a0ac561909f5f2f03d204133b2a38548f56e1d750368dbd4dc1cf3dbe246384de6122e22e2e022202fc7b060ab5494bdd6a67f48383b21941f18d376fb4099998f7139dd6aa271573919b47466c16d0cad8580d00cbfbe55fcbe13a0efef82ecf1bec2592070cd366d9f721db3d9b48315ec9205aa262e35f2868c4e33b21a0f1dd016ca436160d6690116086294f17ac4c9e3f402dc04c940d509be40d54e10893d8e96a77eccd6ffe7a6b268d4a1a22a34d1d939fd67f525cb21b8d4c60a09a22470112fedfcaf8deed7e963597e885c8b099843db0a8bcb386dda43c9f9961da5e68b1582d985830e6bb5fc285a07c92c27c4a4c537983cf5d6e10c1dec23d657b5d612ef17650efa05711ef6c6f124bf152e856edd78b2a0f1f0a43c88f71e1f15f40102d9bde3a49a798fc85eb214e8a0f72cbd3b256d3b5180a832cd500359f97edc354cdb90466611ac6418ecc6e40c4f40df5eb56f5052e000346f7505e3fce4d5d18ee27e30114a96b17d02b1dac569fb06f4cb33b85f8352438b93d9d4116ac376a3d92d93ea66c69c5440945f74a43ee38cbc3a5c2dc35f2632d6ec78ef6479916d47f768b0fdf3715e402be01bb07b85e62bb1d8b466868a89e6a83a8f1bfaa8a2775453530e4122c9f16df2ef24f77b7a986ec2975ddda3678406e06d0ba7c525d6610e773c83d623979fe9cd23b00d67f9ce87f1cf2dabe327792bd225d9b3ee225fe9de7427f6c3651910958ffd13cde55eb05d90af8af11211745872a595e0ad787b6837055ff53b5881ffba8833a332ae57b13721815a47540549e03a9d38b6b89aa6a0071439a390c3414399cc49d0a27e33259696df06ad09fecf329718eae36f58219823d8f816a0764efde7542999aed9ece46319b876ec8219c4bc8a79750b6e87d02623e293a63b038c50656abf90098de7d1667f0569a36a5cb19f7f2baa481ea8f53570bb0d744bf5ec6716f07c0a00ee32d835a428ef30e3071c9b278fc19c4089c1fd1abda374c939dab7ef38083315b8ad9676fc40765a22c727640fb24635b86da0e5b9ee8faeaa1811c5152154195d1808354ad9da2a5ef80348786b0c482c7aa44d81f39781ac2e9e87616504ab229033de9bbd63ae5ae105b9f282ac1e82a3251e03b2955191efed4060db9455710c618f92f4f57f512e5eb8aed02d74d71bcb11c0ec2e9c4e299a8ca72c600954829c32ea7f67f7bd1e1e1f9a9844d9614cd8f5b78da4bc455638707a299050ccdab95ad4d2a91bd9f81288f09225871a358af4ae09ddee54edb7801bd51ee01350924555abd312590b106755314e86b4c6e4daf14fef4d4cc60e7f21827bb45d5612456206a2bc5059010ab52ba3ab568fe9a8e1ff303c4ad6a6ab38de4887af7107b9cd2408d83e154295cf57da5500f0beea88d89854d180389837902504e11ecb1f53270de9d5811ac4224c074463702917194b2c822b8149a4293c1f02c82a688b49a1eb55594927b0d051c06893720d4749e07a417403918c18c2becad3010a9badc01605b18c1e5e37b22c4f508af01ab719701eabc0a7489c5bdd0ad4e9e55a01bdfd295ad85128f288092325df61963020247e7816491842902553f9e4b05668a367639fc7cef98cc6afd0164891bdea9c9c25d386c084dfd13651c7ea16022db893f873e5072e599b55d627f513aa95bd8b1bea372c32893199219199753993a484ef18e8af7930c893b91c5206f254dbd28514a14ff14064365eb1449bfd23165e47cc87593a6a84134292963c87a0bbc8f3e0c12a3ea649bf499b87ec60845c44adea44009bdb273fc21d236ab7e01e0ddc267def96c6474096a8afe1a49f55a2da00a0f30a3fee65047db0d4da64ab1153a01b7c9f7943c0b8e30df564b149b7685a617388d5e2fe1564ab3733c899ab642e91da185d424122d84bf410f2e9e79064e70497869ef50fb9b9d8f463114c403296c93cdcef97415ce4c0a0c5a635d388547f47043c623f3abff46f9c14ab9b5f1c3357198c456d2019a69f448e52f532d8ed2dc474a96522ca1656f0a770f6626abb5384c437ee3857b212b3b88aad59e6b565d8d47b6b79543ad7e0716d3cab2907f512b9069d5c7de3785e068ce7b312d1cdab4765348d658fa6f539a5d8a7306517c8d37113868461bdad4b1f32a7919ad25eec686b1f8f84cea836856a94a07456a51ab56fc96a91be026c15dc5d9c07521a50582f99244e8750f9b6d575e83373dfc4e2605ab5812c920b9a643133ebb282cea0334f71e5437d98da1eddc0f50f36ee7cc78966c6aed662be312c8b1ea311f8ecbd349051c66e0229b54b499a77402f6a3a3f4149e629eb977c58ef0c0ff87f14c8a904ba219f297141efddad5617102021c61a5053ebae4d9c9e40123ca730744ca68a357114b0661da17092f2491aae12c8b9c8aa72d3327723554fa5f75c7a5be608e7a5674ff534e9e82607355abfed7b234eda1a6d6ab9ad3c35814553acb070dc85d0ceb806dcb0f69f71ceb8ce3878d708cc6b848e8319275b90b83a1a72790c117f7ccca65503c5ca178121a89eb47cbf575a7b7388b6143e8d551cf57c085590ad40f9ef56f84945457140875b882166d4a9d22e92a5779fc880595cdb47265f5ed1241bed76eeda69e00eb5d61b272bf792ca393bf73cf5b3a51646851fd5af9f3937981de9abfc75a7aee959e9e2eaebabb5dcd0316e0a7b13ec0a24e67e94cac4375acfccc01cc7496e80738c5d8f5fdc40381d05b8bda1e770541f9630ba1907cce783756c9803efd43f977c8903f59cddec74cd972bea9de860aa5c18db6e4b47e591b265d91ec7d476950bb3fdfd64c8022746c7366a060b451c0b502c9a9e3df5d74035bccfc9d16058f2ef1ec2f5c15c3f966850826cd7aef05814aa51a7918285702938a996dd0c643647b384d4f9bbfb85f70209d4bc1cb9a7b4100c31d220203b88d597acc1d6060e7b986c1b111f9d22a211640b5b8c0e4bb40482e09242ad3c80d6435d2445decffc44d03d8053028147f33ceab88dcab9b78592cf697893df46af231629a8c8b6436090e95ef88b66167b75c7b6fe7ee52edfe56fe2c97e78aaf11c57d9dde7d9c52627399313d27427df2885488164dbbd231b56761df171e81648c14f158de379333e83f9b7e1501e67ae5bdc9330f4d46fe320cb49d2b055d2a18ebbc4dcb008b9e41f1a72d54027cba7687e4419b658e4049b5726eb240e060cf599b58bc06966d5941c8ba904ff337ae8fbfe85fa838065b99e0a049a0b38d3bc5086095ee0d5a298d364eeddb1cd93fc380ae6764d2e8503201393b6284ad90ab557367dae61bd08b604d2bcf81979fd4a643b68764d4c387022799fc7be21083eedc7134f643312578f9fb2d2e6fb1598eca608ff3cf9a49ffd41ed1e525077a9b4f889692d4310515a4cf2c3b2379fd533fb12cd74ed248703ebd74bd544da6b8bfb83f97f39236b916760f5df44423479df36bc6c53c5d4f03c054ac0a85d9076444e85026ef7c4888c483b2c46c010350563f5381bc58b5af76f2698b0f52702a805f1a3cb9a7c5e423186f93532c686d54d9f39c0122c357dc5b01cf2b88b2eb8bc7e18576a30d46308c46075267bf043ad3aa4606f8ecdb15f860c69d4133c03e42c1775ee5ce4c748640e859a20502e97df08009733f5f1e93a8a8a959bacf20cd4b46ce47768cfb268248e00873d8e7e0ec1380948c087419910717c2a385fbe93b5e206d8106729c2b7e6e74896a472c953f9a2f80c82f2f285d1725c272ea735aa949244fc9f7005d2a43d23faac8e788dbe840436f21df1a5c0e11e16159ffeeeb6ecd8850d21ea0187c41cf27aec5783b2c379a04cb65a6c209337ecfcc5433d6df6ee600156b2043b2536eaf00aceb28ccd3a6b86ac0f73e8c9f407950221f1f77768339590766c967c617fc651e0ba92e2dd006d0818e1cd3df05a29d5cb5a5e5da07c69a3617f4bd035874718f4e7b437e20ed2904258405d2d3b1addd8f0c3e92507970281cff5b1618db7ad3a6405a868fa739c2969ea9864d8f1081d203b6628d50f6ceb2c26422fd5920974cb92dc8153227eb4e482bf12802fc626fba616b9cb30cf3f087ebec37f8033ab4bce2cd1e0c5e5886cbdf6fbff038784f9179ac2800608f0175a8ff5745361ba60d3835273e0d1eee2d1437e2f7f8fff57be2baf823d78bf19be36b1a485b84031c8075097e19a71e8ae80882dd92b507b67152011f2d3c737bda76db5d1d316f01efaf9e0ed0fa11738f5e8808e08adeca6ef235aad17838e0bd7cbf4b15c53ec793286025f6fff9fe46ec95637e1e095e5dbb8f8a115ca14baa15e373c5816d386c00acc88dd1a3e472e4692b1960d082ad5e8a062a953ae6d220529e4ca67f17df9bc69cc2903db8e41ee9d7ae41d63d56bc0dfe076790456f18bb160b7fce44df852d73d6067791eae69d6e9b06c0d8739433d988f0fc769f31e2c298b9ad0db0dfc0aff0a22f6b3c008d09c906b081c7131a886c5941c2d9e79c7db5303ec6e504c7b7e3cf24d28bfec4aa12247af47f3d5171d5d9dac0fec0387de709201f235138832da48a3432ed961ea72a140420a48a820c8676a9449b62e2cad85f63f64e779ae341898cb722d6b5242a1cbc49b43c43022c895d864a772881ac87423db7a94fff7df2d763957156e1b0dc2431f6545b9603ae4ab2838e0a5d7e42780ed6121e4548ff02959e8ac6581eb102060fc1bf5738c7321d4e5477cc73ca51388e1603e00fd6580b4064561c004a4796ce7c0f0d561e416a1a48ba26a5542456b4f46a38490c4cc5dc4f826fef82c8eeab2aee892b78dc7de1363c86b019d5d4817f067eb7a8722575ded07e5397b727081a1c00e90388dc9ba09c21fd16b23ff72a1fdaddb012e174579ea4290d4709511fe0572de5df6f1beba6054d3e8dddd03342e817ca82384b9c487db5ca88a4e1b73fdf500dcde5d47a419419212a025ed51b6e64cb18310994d6c36a95b7b8e1cdcfa4a27d917ac8f10247e40a03f56497894bc86cb3345863c7ff0dacc3f602c0275e46e4c58d126a59e171544622cdf587776645ce89df8007a086d9f1b786b7c50040e4bceab847c2d9fac05bea40a6848f6ffebee3fd053e30aa6803d0e9136b22639d1bca08785ae8fda927a81959ea3b13c84b7081afe73e1215c97bfee0458580d59453c853ccdab3447149d260a9f93f960aca9e5949f7a2ca6f7aeec4c8769a2f4738d79c632bcc2ea737ef4482cffd457f0658d4ca8874a7140ff6e76d9dc05616ca4ce82bd05b7402c3e18103f7e21b1db179a121a823043777d7ac698a8fda28be778df475629d4e64572b4ca5061656040ba3ba79cc3299c14aabe35baa2b457232c12d150a4e9e0d3b7a4fefea3e3815fe300a090e6c6be04e8fa511f7d9a65112d3a48e35fb97a03077500ca39f10dc249dfaa7ce67edd05501135aaf30cd2935eb06de6f204149e60d41099dc619a236b7d20634209c318138f3216a69a1c9770e4c3bc0999b33f23c2be1ac6cc98e1f48b4da194ca9106f257c8449dcbbdc525b7023261ba145fc4e616b1f0094559b7942176c7824f3969be8c09eba3fe4310ff76dd5ef39b018990db75217b3ed7577815e1b96ec5c271f1bb8d6ff53d859ea0c980335217e24324d4ac1fd5581b6483f199990c6dff014f0a6c7b46ffd0fdc1d035ab33d595930dad53607e5c8dc585e72aa45b8863fd60f0e78af06e43efed81b58131b6b27d4b4fbc55899a3031d56ec65d89c91a1060a27c1604690d718705a8e83f9457e7f9e6c1d0803e0fe7bf2e049fe9f8fd2464b9b249152ca94646205860574053e45755a29469c4cbd47d662f9567a29a59ebd3806eb9ab8f7d251278aab64431b924e4c8200b14aac6889946559966559965f7ef9e5975f3ecd975f7ef9e5975fbe17923165d08636b440acf811c9164869436bc3907c7d98ef67aa4aaee4743a9d4e65792a4fe5a93c95211b96655896362c4b1af35f9665f9e597effd7f59e2adafa0c39e96b021b640ca29eee28fe1644bb2a17b5d156c68c37074981a86a10d6d68c397ab34030c2349154e766fce7064dba8497cfed7a84d4eca904206bb94fdc514a394d20bf3b277af4ab9d9a3fb18dbae05c7fcf0eb303fdcd590922dfe8b81b2cf2ac8bf0b5444d3ba4e52fb730139ae6c67cf9bab0a5384385d40eb77398b4c8395bc3ce62ddc2bf71572104eee63c67ac4324692363732aca6871e3ae9c4c483003129119d084729c7711cb771383f080cfcd1673961ba143e7fd6e74a702cda86ff681adc4731c5077a0ffc89b6d1393c98a61a8073f5351318cd976841b37eb40d9f4247ac29dfe3f09e3db9f3e03931beb66aa7b40d1ad2144c01838ebc851df3cefd4ee5b40d6fa552ad9c0b7b8bd56a1b1ca6651bf66d4a350d7fae33a59c06d399985eb7ba065ef87c4c42071eb23c4aa93dd8ac1437e23ab57d380ab930f470348249c3075238bda0280f3dec74b84dcbb0e63167f4f0416524b9234a29a594524a299d18e7fcf9fc87a80ae35528346766df20d8a30898a04b8f7895af4d05640a2a910f2158562511f742784c2d51e76f673b140d8ad22d681628ea0485eae137fcf862b22ccb2e1a97c025a2a64b494b391d3a0c8d6a5d0b0fdadfd741cb287d8c7eefc0ec077af8eb073ebdd8fe3862c6d829a0e60a35572857425128cce650da36f8b9cea6c53eadb97ef02b07ca3f1c4551144551144551144551d4bda19110c2d0462770220bd4a3380c3a800043eebf66cb081530c678bb19085317f1da22d9f5393add17395618c3b21a343964e4755dd73499ae1d95da1db9acd4e693bfbd817df695ce15ad533401840483115a26922a884832917600294b20330048e43967dc073d938934994c26934966fb81c1efee340a72fab6ab967620d6e1f9f953dad54c8d408de3691545892a0999836a728de3d829cf7d918c580a96923d675b1ea198353899550c804963219988ef68d2c4a2f965d883e1513c67005a64fa9d8f233e4670a6625471f538f2e0466c15c2e73bda862b691ade4614610a57620425aa2528811c57230e57e24a708cd738625ca372857d52366181a1cab1a9944629f574321319ec548c4aff1472a5c9b142ad344dd33c1d4f87b1bdae9661d85e375585154d581bb76d1cdeb86de3f0c66d1b87b7546adb36ef25b4790c1bd0cb5ace89dc14881e33f84fc76b0ba2584a3d218212726a624a8245b1568cf1171983583948a886babc94fcda637d755d0dc5ba0ed234b80e420a1264073a08d28320a828c49448ae3872ce5967d5461a83c5b410af61ced42fb848824344c6a1f8ca552a8ce17f956a6555f831ecf157ac93a12efecd64013e84593d0a93cf875059b6e322759d7380ab542a95b52fab8d8806843585aa6e0200838cdd8729ba6e7713864c43eee59c73d79b5a2f16dcebe14904217e3deddbddb79b826677d3a7d76317c83eb6f4de7befe6850c7eea67738cfd3f14b04891bd2f640a81ac83c5f5ae8b8b32e79cd66698bd40c8321b17727d50a60273e5f3b7995d4c61b6ecf0813cec1235c3de5a6b6d6685ec9915727337441081ee8af495d1227fdb9b9a1f993e58e307ac0f9c2b1afcf3214992bcf7254496aebeae797154a4722453cdf615b95ab739b8901ebc5f6b9cf93db6eceb532f5d7fbb52069adb83738b1c201ea20e39508b38ec8b21438e5c6ee370d4dc9426ea9b3d67164eaaf03172a2078d691199fbcbe15199938cfa9426fea02e8c658a1013dc6863e17ae374fc9024cbb4d30fee614964ee734b877bfe3fb8e7bf83a344d091e70fc2f3f9c10683e32ce0f9dcd22a2a313a1ff957de07d7e5ce07d739c863f437b017f58ac6ac742ddac7985ad72201ee57de022a1fa30bc2a3d279ae9ba20e688a3ca029ea96c3b4683fc4a7817f611886611a913a52977aea247d726fac25ed63b8df3aedb5ecbb3522d5544b602d655ced50cb6853ac9a6a69eb8a700fd6d2a804aa715243b4711c4712d7d295611a892d79d552b6d512564b9ab6615ab66d9bb665dbb665188661d80f2e9381190c06ff055367b2a9c5bc625ad1812ec568b46dde8bbbd490c18126071ca81986d4596bd79e329ece26e3b99bcdba2b32fdb985429a0efed3fbb7d66b1c845a6bad1d155e593aacdd42097b10dc6a6152eb6412932426afebbaae2773c64a53355902164d7247c3224f56aba80da60daec89ce93cd7f512260d876927d3f3f7c6a2499324258720ddbb45b65ae410414c1b4d360de736ad5bd98f16d96a915ce7d2c1c241692b8c1285c17a7466bcd036b6566bed5cabd4225b64a9d52a795a2d4f89eb783cad96c78343c7699082b5d68a22c639ff6b1fcd8de7a8a190e7e5bef7da720b916ba9c1307cc21fc6e00cb75dc015007cce8217e459679d2dfdf88710cdcf067bfc36d8e3164c9ba29aadc51275c1c0206a218af7be84481d23cc39efbc5bb4966d91e99555f22c45d0319e0c2928c20b9fff006cd04ff17ded9b86c5b6cec7111f5b37b51d884ba6a0b4b71c8ef97518688b4ceb4f6e87ecc33bf4e31bb82ba239e77d5eeeb82ead7c42a7d3e9e42cc34a0c337c5e064b0c510534452b20950c6a510cdd8f23d8b3fc87a5e333c8685106b98805f42023842812e3697c72376a10f9597e05d4b2a2f247f011ed8710cd009aa24f86719b8e180ed01a484706c845193445db16e33d5088ca5017acab9d9597e163f818cf926198eb1ccc029a2dd218191eb33c986374169dc7d1438dcd8d0f397ed001844aee782787ec743a9dd99d4ea7d3c99f19c3097905bf4a1325a81edab74c974c6d24c98925460c31805838cee3a1c901877b3dd4d8b877d33fe800c2bdc65ea094524a29a594d29e349c93e5edd7183f6bf8a60058e0a35a648a669195b7b1a295ce8710a2958f010ab1688a3416509f20758396c89770210c51a4f9114c9c208a348a8223986089b47a04a9092d91664f80d30529d2ee14486a8012691793205c4125d230145054220d2b021454a148cb84ec201469dac602ea2751842b91b66d2ca036c2881ba4441ab7b180ba0826544889b44eee84c0f3d5074d26478c1889b4012cc94f54bee24c1bfea4690871184c9c3397e7b36a25cb790b3b4f2912d3e355b5c49c73deaeb6e68c2fe68c279f4a075622a52ac4fd49dba8389346eec0ca8433e173fb2bee7d6c2892413b6ab9401a4259d1987ca3380c062fcb8e62bc2562ce99edc5061a3a7634b16ca29e8ec807da519de29eab3a1d8b4bf2d90e94d9280d69d8aa3046476d10af87e1741ad2b08661188634b4f6e596d955be18eff5ec23ce0ec018a6d9bdb53692318f5266cccffe721ce013350b8f34d4c54e549da84a1b06398274a152428ebb5add253587aa0b25b0c6d5683bc085942f56f77a09d5f03c29c158ad30cef93fc7531ca190e7652e2efe8f13f185141c0018e48f98c15ead1ca673accd716f6e3169f48a0aab1386e9c8844194e3c175e05f57dd884bbe21efb460ce894321defa8155b9bac77814dfb0cd5952664c06006cafac7316f690738693e9e2bff2e273af24968f72fe7c7c544953e54bc8988b9e9488d9b91467b9776d6dc14d0dc3bf303d475123a68bbf14244aa22a70161593b88f95847125a9703e82290916c5b6d65a6e7bae7beae2e07f18ee66c2b611ad1e6dc3a604d75eab65afaa3db40e773219c6183f6e1b3a7a4c7c84c76ed6a6a6ea7a11258aa2288aa11a353659d45ae76ceca2fa824c5f730fab9cd18b0cce32026ee4ebef20d41b5399c1df8ee3388ee3bda1910bafebbae6e4ba0f253f9e4fae992e2e8009865c43155ef3e347f6d8ffd87ad42175116f6ac81a92478fa68bff689251c85f230ab98507017cfdb4fb40ff7f5dcb0eff5d8fb300041924d1852d82a08552047687795c2928a145175f37a7838c530627596373e303d7d1e2aa21a37531e7acb356ecf3c7d35a7b85464411a191a99a53b5aa610d925347505a1508ebaa54dddd1da2636fdb5328582ba20926910f219a481729d191d9796780e763541ea4513e5a2ad1122d8d4a9a66b79e344e04db69d756b56dabda76752ad59c69fab967ebfcf643883815ee8710691d100da94b8b4dfecee78e862d990a25659fb3ced3d11235512a326847d9db133dd9d169644fa3d3a87b85655bd6cd3963b37fb9fb58afb88d82f4823aaaac7553d5c861ee4a4557aa95ea5af14156a9ee6d194f5cd7cde5836a03d7755d97733756e4f999f63df86fa0ff5cef85643006d1e4e01e8e1e40a522c66661f39bd5d92ecf59af8b5bde139ff4095bdec4a1987ecac950fc496a625fd2232715e0d3beb62dd01b8dba4755f491f8d51a02716f840251263455494dc345da8d843811235ae7237519cd1944883ef2d168f4987a4569d6a2c874a6711e001dd3316811fc64568b22bb1645ee0ffc7c9f6cfbcc19ef743a38e2e7b3849a2a52f173823cb96e063d3a4b02eb01d7389c0e2bc8af850cce164e0cdac59190728044132d22c960e8a3223645418aa2288a62ad9d1f4c1ba0c8f33b5a9669dfb1d66e3ee6143e602214f9b85e080f293a72fb7998f6825a76b83f5da5bad7407ee0c7ba2258868b4075f48218d4757e1d9b0ef795abe97c77b8ac6e71f30495fdd168341addfb121a6952e8a9892909a694528a67eba3df4adc05479c2871a2c497cf3452d20ca5826b65328daa9c24a4911494131393c9f484c94d3e254ac95af156cbd432511c984c485a2d8bb24b9a4c319551587346e5e20a5df8dc3e685354455d4ca696a9656ab54c36c8df72fdfcaed4d5c42f158d59b99e78a916148fe9b28a19e3a4542a954a792e219fd4674fd4c55b4e52d99469cea04adc85e2c0549a4a53d932b5544b98584c399b4ca4c96432994cd70a0c20b40199265babaad86d0cc3b02539866f080df15f7f6d13a9a1e44dc6f2f939038446413748327d709236ee79dfc74fe96dadc66168cc0f3537b4cb51ae7a24ee28cbb2eceeeed6b29a39a736b5d63410f7f5c7075bde344dd3b4a7716d730fc7d542d74a69571965c6d7b5d61e31abfdda3d8d5f208caf8b3a6a9cac3975ec1089f0002214d262e9506121d77b6f8dc145fe0ffcd0f4efc075bf7ecdbd5173df723740e045188d829e73ceee401503584b336248dc7b2fbeeebdf7d2eb567bafb5b8de8de534978c4c4a46ec88030799d2c8ac7d77c39165a426c284c9e19ef5c04c5d92df3bfa7e942cfe74b807f381e9f9f370ef0324989e5b92ed20006814a0a8708232423a31b15fe9052253831ee10371f83a1a2c0f6f71407f1ef26bd9c1437efd1ef2ebaebf1607f4d7b2c34f2490beeeecf7a41225914824128944ba3744ea187177afdd3d8634a626c9c964c4f675f12b810411a2dd6ab28cb465abbf6f4eeedf98e47104e2030022b93ef83a34c78300be3a031872f6f87176cf823a3e903e109faf3e8d8fa036b9974b2cd94734943503b5d4faf9d86cdf22feb40de4b5d85246c6fbe914db2abb2bc5ad467bb04b353fb00efbabcbba161bfc6204feebd1bd5a06711797d265d865d865d865d865f8a2a2827d9354f6aac3322ccbb22c4b6b5fca184ca04e27146b0a9395125594d493d309632ce5ca329f33442efc099281c4b4f8449c928373c8669c7d76b91b7d5a32636a6af83e594c27a793a8042b419d5a6082214af04dd2de93b34e18630cabb141dd40ca1778d049e183dab60ddbb0ee31fe1504e6fe911a615b66b047d5da17952f68d6da69a7c51d1b5bd3384204492484d664145d3f2f900f7e4377b70f5b6be0e694c1900acc21b7f75e5c20bd7365ec7018eb27db699526398d27c8f56ac2ca8a528a1208655fc103d58884095648018491342a41c2c1109c71f45e42b4c385765d2fb9862d642b10d7556bbd735a4f167d5517c624d684d32a8a1255123207d5e48ea3aa474a2bbd51a96846158db9c944594959b8062bf2a42e2fc83a8fba786800a50cf6489be8aa010e64e0a655f6e80a3d5aad3b8ee3385e17e785cbc0b6dee762528fc6e76f593e9a61b08690b135c3bec4f45cc084a906a37164afeb3611482881d3a3ab464db9412142134f89d290bbd3a0b134e744067b341aed20f7e0e19edf7882f60ba533d4d3c57638c820a6251d6cdbb6b57a6cdbb66d1dcffbcf4a6470b63e70b2b2fd56aba58282eadce4a03a76c86053065b01711c4d0e387aa8b1b9f121c70f9ece6a73ec8566ad9d765ed8cf8bf1cda670041654221f42d0a0c24a1465ef43081a56c84288487b2166167250240aa2ad6fb801c4e1f3cfe14f83835540a72b420efd10419d3316490f69daadea97698c942fb81ba150180a3b0c851348c04d08019309d38a9950088970088e1234eebd84b073a75a6f46a50b46f7de1fe4fc00ca144d9053349102711f3781918c93e8fa16283214597c6837eeeea57a363e3428a24b850fc0f8c0a9f201111d50aedbc950971c2e0d122a3e70b27a3055d5da9266e4de712ccb1d9f4ff9293f234ea271eebdf7ca9052d3552c91b14506272902df8164361473890c2394423bc4aa6d35ddddf1a9187bf083e149624badb5f45a0ce49f18a3753cdcc5ffab3b505fcdb99da3c0052600030d683b546050426b5c8d754790107c11da71af2b546305061a8b00df9c12c5dd2f56b7680c0de70c28c6d7a45a0330258bd9635bafebba2e989c6f5c1de8e3bb78f8879f86ee91420a84843b0a7d5c3a4ce91e48451cdbcf4e87fbad0b627b6e7bae1b421404f79d0c743e3f7d95ce67117e5af2803a201ca2f6da6f8f3d2b88edb5b7400bee2ca0fdd65151744ca7d0162d411a8496301948e4e11fedeffa9a6538e6873b1ede3474984ab32830bfe811cd4a352bc5eae80f2125d13d275e16de161e0ed6c562b1582c16c65e8e62db5a6a69cb58eb323273a63ffc7692572783657e166152978f0a191742768cfd6554f6de1a342a5582d184c5643585848275a94bd376bd2297ba346738b144c74fcba7a2fd8794431f7dca074d29b207f1cdaec8a539d3ff83bbb80fee6203039386ff8c06e60c77ee060ff7ae07bb74030cec9b9c0e334aa67d373bae86325513bb8175ad2a92c12ea14aa55229c707f740aa9b39c33327d5cea6811f43a1b2ac860c0e3452604c92d9c74c4336b96d5c6419b68d66ce601f27e348c6e29250f03fee36f206c69d4291ddc6fd9cdd3daebfd129cd094992645ed180a13a94a8c5c0d62776d0597d630c04cfd71d0e3232d78533a4858316929648e2e93c264a85802924896173860c0ea4e6311821b5bb975d204e51d92d2832385b94bed8da897da23f421f6d3320cd39ebac5a5649b4da11574b29a55893b06fd29ce990c6ef0fac94c126913e0f88b130580eb30a50c06c8a0893c6942839547cce240559cbb08d524924128944b2f6a563857befbc531445ea7934a48d628be258924183eca1b73c9bf8b1ff74596eac2124113853b7033ff5ebae676022b78c26641f0d046708a70cb7484b5ad2520cdd7075a077832dcb39430cb2add124947a61e2a16c5032039939ef17e2de776df45b88d77879b1c1061a3eb16c510740684d42b69f13294d454585eb171d667ef4c39cc8ff953cc0f96cb0fff8b3b7d1402dfdddca402dddd574495dfc4b20ccdba1bbea5e0b71af4527f93ff1092a7ca08fa6286f5996652903671cc7d32a8a1255123207d5641ce99c18ff77163295a9e99691e971cefcf56559d6cf910cf6785d374a669fcaa8840e7a64c968040008405000831500001800068442a16040302094cdc37a14000d577a3e805e401c48644916c46008a2903186190288018000019299425500c62fcfd202af25a3cede476926b63f2a54c0fc9206d818fc040159cf0a4f6aefc8f07baa91e60ed749d43d2b2075d1742bcd3bb80fa7945c6b03d62f00a582e2d7e383ff660afdfb19659cc6015385c8a50af437bcd7837524f7e494b6b04eac50e64b6d2bfa44c81c8133c6b6b6e73ced05c1a6c24ed55d67c4e793a6a61b9a093193f59769678b8a9688a82c81b1de893b294755e589a146de29e630e9526c64172833fec060d2124d7fa25cf39990a45895a744f0967917d7c338afacc4d92b9f99e2f1427963fb6d07bbcace34e5bdacc0d7fa2d28aa0865e6ecdeb93f5f49cd395c52ea42f19c6a8e88b23f8427b54f294d5953230599815eff254d31dc54b89aec8180f0f077c75465fd946cec4cf1462a94c23935156412b78c13bc3b0c5dceab9ada90ac8ab60db5aefa194ea07c7797fe8537843952fd2c2fcfa626275d37e31da9e4ccf61327d44dabe18340429de298001def786b431a7ce3484191777928e9314ced8158c3ea8e836b83a3ac51886c4e8d470523b04b12c0e8dc2eeecde12a9cc60f7b6ebff74d1b80f0527e7c2677df060728f3455f7a41ec225d68c8fd8579603fa9d283b0b1c37a7e5568a19c1b3586e2830b848d02e99c4dddaca53fc844f1a25075c611859fc4e19206d7511e6e3c024201e88d02ca61076ff5210056b04017c2c22a71ee401e494bd4f7f985fefd0eaa0e459f1b64a5c723c562ea632c58793adabfded5ef5542a57da5716045e64bec5801589356b54dd105eae52abd44ac0d454f65a70cfcdc7553f8844295d833232334965459bcb2bb59dc359b221cc1e6541b33ba8bdd2994ec0e1b34f1aef368c5baf83d472599f6467bc44adc24ae290a6d0b8a8ac76bf3d42a0fc9f668b18bc958f481124b044e07f00618073b1925e58c15c162a9b830bd28f0e61c41b2629abac63472c652f087b0676e3b2d85f830ffd62ac5e22d87846f368b9a357c2249ce95e10fb9080af3a98176a217ff4d3d00a6e4bbc6cb81877c78e0356ccfb560260c3eac23fa2d9664fd88a11e08fdedd7cb6ec92ccd8b522a1d41008cc3c232bb64545679de519890597285d53b2671ffdbcb0dea41897c74d49caf1341b38640217cdb816821984cd168191389176c94cfeb9e17e068949687149e541f57a3f69ca67466f159c142a3af050b1549b8978345f8029501125fd07cc395a18741557f54496a191439f69def98e4ebd420bd64219404b58f3c91af362336cffa5c9eb0e42640bec22829b772c9eede40fd4903d2869df7778f7ecedd5774f4b100e746776a2fdde95ca10a3feb74a75ed01bccbd4516c332926999679fa8951155178a92b9c692a0ff1e3bb012689f538ffd11e70539114f90a13b0da116f6e3c87c8860ea453c91c4518a98921d5c6162d4972b74900ae45fcd8753cc61c5370c9d9e11ded5ad755fe3d0732817759f78221afb637ec7c57e3993e30fd67becbf4ae2db726811d0ddede8ec02734a435f2b192e4c8217c0ec681c956644890b25453d4b6bb720b1302676c43213334d24ad2b627409b327d1bec1ee3f06fcf0cf2811089ca9eb4326473277dc27d2523161c5617e954bca0df6c2a9654d1c3c4ac6719087917a382dc680231256c87b822c5a9e22c8c20d0035e542af3649632d6a533983ce8842a5e13b6482689a6641792c004890c1f19842737664dcdc43b4dc79fe6452dad542b3c48a7ddbf4fd26ec1b3b4be258c89f982de888620b28d7aefb09c469da0dda58e9282b58092d48f7e06fb59753701b436c87777b513159b43ce6aa785751a5b7a13fda5703f485c159a418ef0c1cb98b569e23eb32ad5bd151c5a4f75409c3e966de62d24c66c405825a5caf780d4868827bbb2efe6ef83e3543fada09ee8acd91b92a9b0a426799ed9a2b40bab7a9abc3c5db38cf3fe312c2424237a6ed5cd106d07b71e64f297a4626c30f4afbe78e2325258c0087b639dc9683ef76edc4dde00a1a83b73fcea0e18651830b82d60940785535eb302f5506faa736f3df332b86579a9a462e8880934aefc18270b8aa317628cbd245e60eadf848b366b064eceb7d199d023bf3f16100a7b4247af47512ca48b29a84359047501d7014628363ab2d0c0942d7f851b8cfc5375d3a63dad817a3b8aea4733952284530a9b16fab7827aa40cb3a437d74f5925b64dc5c9ea6ebba9a5c47bc79474b1cbaac42e3d8e96c8eac84225f706d16e113eed66a1b265f84ee753c6521a063f4b09ca749bb03cf7caa3a76c7f69e8b1f1fcfb99dcedbb67f5d3ad68d2390b39759ea25be59ba325308a46b3023a49c08630957c34f3ae33cb446b0b5c9b5e9526978bf55862dcc9afef56b072f2ead393efa23f8971793aca9c6213419acf51fad0cb2b6c17993ac0a27d94827d9e38e3a8725b527595ca7014cc4496c734fe964eddba4eec1b5bc25595712997d68405793ecb08b2cbaaf88c396dc02201d1baad3daa4ceaf59775714fa3cbd06802c95878d0f3431633bf964fe213e9cf56e1cbea79aff2313b56781c3b8dbf7e13aaa0849bfba71b82643208a9f129918a03d868e86987b589aa90fcfec0b7c3c4e4bb58691cb1b64593e2d5d923d4ad3175c5cc0f5173beb928e59f8bb6840abb9f8eafc30ed483b8fdd9d245afff0c2ae7cefa90b6d35de87e1e1ee29d862cd3885499143fb27d970e85722c5c52cbb3ebf78ad6d0e58a9cc5f238cb86d06ddc69ee718b9286c15cb7108c70aba2b3e70a0a23568e62a0943c745060c106e314443933b9a5e18adcfb1ad909891b11eb136071dc8c5507267982866d613a0ad7388ab6c2bc329d5ed826cfc012a76f19c9a3988218e8294067e7c5dbed236ceeb30619a490a95906d44546fb7fd59222729df91a7a510eebbdd71ac0972b124012f970c39b4159582f8f0850e028d6fb234f1244a00d7d4bc171e21ce81ca925d2e2afbd23d4a56eb6a5023d7193fb1546da43da033abc5fb0f2f2bc3f54290fa6d20c8bf6047381407fad8c58c7a92af6d5b0514ae1e97e74670e750a1d0d976830681fcc6e292f87305515ba69d0a87b0f01dd8436d5a65b0d7dddd1c1a4353ac00f33e38446398d9f5744f1deaeeb4e2e164462740eb0d351137a8b3e5d63c9362a538e21928fed3076071d05c96d070ceac4705076eed0a1f266c64a22826c8cd4769cee50b3a53303fc4a5bbd4e210c0d2645487d0fcbd224d63d8a5eb6be5ea590c1893c18a1f5a3c2c68d287343f743bd6d904ea00c0804893ecf34395237e5372b59889ba0c9d1fa2e302d13c2e94a021aa63df3faa3e80bf1eba8f27120e25845ae8358c62998a84a232c5b43d11a4f275bf5aea2ec8c1bab9842ea2614ec2ee030530a52ba9101eab409698f939c7e1206d1aa3199288e860906bf12451fb6a486c9a6c6bbf9bd75fc25246bb6136bbcf4719ebd4212226e7b97ec531c84c31f66904e631171ed4d55c2d0380c682eb5f7b8a837f3b0ccc8dca8ee36381b7f5cc9267ddc4051203afcde2315f9d4f4612538025cee70a924a19ca6620035decde70fbf2ccede068b1ddb27b9edb09e66d03ed5aece4938ac49a8edee848260b224bfd6e7962d97ee60f805d230c8d1dd279911c49c01ba0f7a87b04075784ed76de05730e121c45a9043a483b9a235152f0964b3a2878527cb45ebce69eac7dbb2ad2021d8d3e9178189e9f73a0b671b28a2384421a7aaad69ed778b30e8e077e63a7173e756f25240fbe40ad1e69156ef56843d75efa0742a11efea63ae4d076295fbe6fa481a0417f63b7b1e5eed1b5628f40e3c78686b2a31587c1be4a591bfa28626150f583d0bde714e8ab8c753552a4f6ab55f77010a542cba9d000980deb78a603befa003f74ba29b677d53a72da7eff92310c0428693d5ee7fbe99b32f5d4b6fe01c78a5563afef4336d82e1e2214e2db9389226a958270abe5b7264cf60315f5925058e87bef5637502b2313265f7a7d268c1bb5ada9008fd6c01da67c69980ee4761022d52020a4e2eea3a905fc41783f12068291f85803b834847ab62adc49313ed6f52c87ae37eb8f5204fdb9699cbcd6887dba04511866690ac16199ab8cc8c7b0827dd10608367e664cd177b3758d2d1d3a2bb8e7ae8e62175da57d71b007a244438b459b49ebd2aa139882d373b52acb6852b1bf4fe480a6e351d3a6847ecc255e889ab30701322702ac1f31cbfaa16fdf60526665a88cd1ba9414600720906d97969b2ec9cc8dbea16bb005cdd17ffaa4b2eca181e279fabd82e3734013b7c862760ce382162675d95269bc90612924f1fab6e883ac02697a89a58d4e89f567a4373ebd3bd0bbd279d5d20bde63ff24dc10cb2fe235f7a5c3735f09c3529571ca80b6c9dd9b078fa726bf31555992765c38c5c6480a32da180196656575e9bc1044b9758bce811411fcf7d2f79b2632ba4bb46e4f9c1a949e19f07684770459bc3546a743fc4daed6874e254238b7ec78120514ad4e40858343c960ac18ba84ba168b793da5daa2748615689ed5e1f8d6b3a669b61bb2c0755e472892b60e97f16e0e08557544c43e12ebeff854cdf4a867343e8c32c570b8549879b5f3e323d5efce881b44d5298a23f8d1ad7e026c2df0c58fdbad2a1d1f57286163fd8faf0414fbd9f38be581b686316684b50c2a5fa6a54e2b41462f21a7d503f3aac04aac67c142060662c32aa1273d0dc0f8fb4499947ea15c2d629b125694007e3aabaaad95c1c475def01edc2243c1529e16f86c6bee1cbbcb233b38414f8f800c2ed772f81ea9a89a67405e8db86dce67ac1e266818a31a1dae67ae4f0053697d01f3190302d1de03d40a878cd722ffaaa693647f36581f2b4427c7070a17948e194be98e3eee1b62663f5c03944cd88462c01711e99d943610a93f2351147d0e02f2633a9074efd3295c7f0fb9b3c6c1994c911950e2c2e8170e98f0c85656dcbf80bcda19af36be7951e1d634b7c154a5861e3df779586ad4ac54c34c42e00323b66e1c3f6ce7ae6688a4b7aea13d433bbc017a036ec43ceb13e264e04141a93ddb719f9b3c41cce03eccdadfb9e8ba4ab95dfbd3712054ee8c846cab5f936aab94c202c0a2949f90c9bbb9937ddca230fbdfb10a35fd5b6082487f18eadaa173926a8324017ccf81574eaad92f8ddc70d4585b75e34e82c643d70dae5a300e26ab120bebb5a83c12917324849b9b8f1c5bdf154af4ac99435cb445eb02128417149b91c116081bda59aaf18140ef2bc55207ae028835fa038ca06210bbfb11ac43c50a7657c687a24508b74ed3a693f1a174261006d245692616a5fa3618ef21138a308bc0b946efdd1d8fda64d15e9256b637ad0ef58f0bd7e7a0982f7ef138555a04b57c695924fbc242f4c9acb15afa8416d5284f21424517312199d119dde35c73bd8625e7a54d7d266eeedbadf38b837c9fee88fda35690429069b43fea9282e84d47117a465160878ac87ac67df5bbfa8345a3f86fee172bfff0c0a2184e9914a09b02dc3d5e5f9aca70c70c4db170e68cfa030975008de8a28e2e9b124792d0606fcce338d5e11dc1121afcb516011f4167a9f56430654665c78ce470fa902e53ae49807a477eb9e6fb4e03beea5c03eaa5423744e0ac17d1abc548ce8989854726e764ac4b1c109c490064d77c6ab5d3f9302370a9143a13824f53e13288f9d5b1da91cef3031d3074afd2dbb3cf4a6d7e9c13bae16a0e108297d07e69e248354e6d527f76d9a617475cb14085ac564085c80276dc57d4b6a41031a3feab429dc45e61b3d19abc7582622174e7f81d6337aa97ca672a64d5562679c80d9dccfcb8abb6397cd7f8527c7fbeb5af40ae8e64b4a278b23dfcd0bbb6030ae95940ed9d30f21281fb21abed9ee79ef14c78dceda006d7b6100514d9da46ca952809d4d42f8ae722c9dcbec8f9365eaf22820ac3b907dd7ccff00b164bb74fc3fb8e26ce70e49d8fbd5289cbc4e3198515d2e80ec75eb1f728a6cee7a557ac117f0cd8f189c55134ffe12cd95da9976b9dc750c1b48cbb805f6e6a0e8e928d4cb407eb2da462aa9deb1c0ea86a0a5fbdf9528b5af05241d8519c745ebf80d00271df256a2969e7c6a0be9c8ee8b8bba52ae4f9b12b2c1f7e1f6c6cce1241beec981d945d15d3557a61078c11cf3f229e81f670ab09f7aad3bbc055fb5d2d3d16912d8ab7013d1db96cfcea00e5d10f5c5201aea9dd38d4823f563839eb0e60c862e46d7f228f75acde2270c01685e8994854fe83ee3fbfe42350f4bec63245bdddc83de2640a841670c1e67965d181850347ce1118bc8e69f630e7036f374b6f382e7a6f5d065098bc505d3bf725c4128a4ef7e2a06facb1878d8d2db970ba5d807040a4a4e7d5ebfaf993092dc170719ebfe63cea2f025a64dba471b110192343732696ddef6f38e5fa7e8eac7115758fb971745998cf4e0736073a719a537b3e16d89e999ee0f0f244052adff6fdf2cb6a84b37b71ced93c9915bcaa99f23b01f674cea1600da4f57ed62664d4f120e8da4a1843612b108688b5938086ef15b7e460a15bbc0978e60c7a087725c212cdbb0a062b5d93e5d12792641c4f3bbcc128c8e6ed5ccc0851778ba5861aa36a35bc8fbc4caf891a2fbba244a7d76994c7e7fd1f2cf449613c71b72f16ba0029fb0191e93b45261e64a74832ae3c1ffd1982e5ecb7157024bafd75c1e5812ddc30591b09fd2e72e95fcf45b060c5a2ce914234cb65d1acf974629f98f274ed0903602a73a6391f4f6bf702b8cbd9a18a4ac92b075260403941e4fd136500848f702375294b3b47ef6e78e0499b33e4deb9cb658714d2219cbb93f422d78fa87f17553eba3224871548bf275b646056b6514009fb8a6032fc0e9409750f177df7f303987bdd75cccbab3789b56c794ec7f85e6fb232c4de00a4cd6e03d8ea308b25dc3dee58fa5762708185c82bf88278b96d8e305c9453c5b830103dcee14fd87d1f76da413cb7a8cd30f2e878fa02eae90bac34967f7120a7379d0e0f528db0382a694bfeec64f772e546346ff0170137718ec7d2a33bb4acf155a03f18eac426612784d1ef466f4a258081482589ad54204e5997f3e92c2c1b08190ac78fd80aff472a37ba9731cf50f6c9f56afe1109ad1af84d10bf5ce63ff9c609860912d3b6c06ee193d855de9ef3b41049b757f5ab942cdc0df858e16139267780795e100e04503cf320a1fe06e3a451719a441e40cb9d0e78dad4095f62094a27b7b437232895bc338f5ba0b4938f4cdc406942722940c851e5cfdfc4b93691bd718fd4eaddb71d050fb1c37cce18dc3617782509c7175bd3625fd4b0d9e89606eb7fb2b00a0ed54839804260142eb72a4477fa1c331734e13798affb63729d209edbf4a50c4ca900a98bb4a0f358291ab785632966ba14b57b92ad73e04fcb49e963d07fa266dd8a050cb1d6a95eaa7c18a8c225502e5cf8d4bcccf453f5998aa177e5a5a53130ebfce7c7d7e76ee4c6668412a52a044d6fc987dcc133df96880185254081c738184688e7dfa69fade9376b03d5929143d3e480eb9d3d79c42865384e3f24eca2294d5a8dd6947442cd3ee8c2400cf18127261846273c3362956c65b11a2ad147e658006d611c68fd564d557e73e988ed819ad84c9b5454455adb63458d31bb7d121ecbde3e5e98476d89485bf05aea48ac27112c4c6e1dc68435c69467617ef027f76b7f65ea0cbb34b5c44ca0d8096aeae18832b541f515cb59bb64f6a08f7d8b69314824368fd2f081db48c7ef890c3b7799afa9d2c18201ab1b2202f2e42dc8f672e13f0c9a2be755776c3772181490b98ebe09baaca3d6f4b3a64e93c74803dd2084e34369709fb7f5949c739727af81c1707ff4cdb8dadfc8c00814ae121090c48a9af2bc882e73eb046c1e26b042c46dc3143ad20a78110a0704374546e3884b8640a5b06b57ae60e9600b574d4838281571522703b89a54526930c68a26c3c23f53946535c1d93cb7dc2e21bc772fdb4ab067aa14da4b2719be6416b92c707a27622c55e204b34085a743f77148d6a88972d6c7c3d7b4ba2600a1f59f2b959927ced9917331e3b8e5cf5fb76bf1b2cb3108bee4ff0d0a7d01a882d87e30140c61af2d3212c971f7ce678084e8eef547e0bc65ca54431a0534cff6a4826982847f2b6a8662669b9e48064c6a7e7ae0cc52ae6655518ba5765a7d2c57f4a7c3850dbdb2e5b8f657820f72dc215e34dd75d49c42454afa42b118ba87e195d562e56c346d57d4c2da3f5b094fd492da6b96b41737a2e632500f8d0f67f53414132b3bbf1f17b861de0dbad2b0f1299f402e538ac08b200b901d054222e08f4828b6e9062094cb11f3578de02b4a15db227bd9a111416762043725c2d7a533a518608a0a00ed3608a1cef5b5b0cada9dd7f4474236a6561362710bf3350565a1d7dcef49c17775e161c3a720fdadeb4f45696ab530aad3fd4e190227d0cab95f0ef8553b98f6875b514696f119c0a18c7083336aa63912d980b351313ecc3f66c27a4e61462ee23a0a7303be455d81a6b5140fafcc1a71acb192b30a80b3dbb29d737b67a2238e9c7d8fe553028ebc9726f6391c81a88f33a83dc9e372dae6cfd756ac34336d1cec50485f887843086f90d11b187d6fe9e68062a59a86e14f646e01156911e652cf54cce7ec15d98bcb68b0402c29ef3f8b2ae24669698192896d29aea2beaed1fff9c63aab186ebd93b4557db92ba813bdc8edddcbec07112583da2333f62e6bb075bc12c88f7c87366230b7ee84a3351ffe7952f8c03a0affd90a56745a28dd8f3cabd53a33b7d0e5029fc6af0a8f8168b1fd4364b1c5e2ab7fc0a94a2b4bcc7904e3bb3f58d139c2dc8c5e92a334222ec27a10a1cee06c18a2bdefbde33645c916d10ecef3e38994ec8a8494c2aabe934a70cda2b8f31e1649082252aebc457394d42cbfd0983ff21625ae9a7004aea66aa6c3b6b70817d1bffa9e810ea5599461276c1afc51694fb9357da37fd556fd96de313731a7e92ae2446715380694f9f8f3a652b8f5cda16db33442f575c54c7f42ef0da60bbc456c8a5323a8c410d10b8a134a49954a0c1a6d7cbebe91463756f0a526aa7b369438a8a6f9c8110a68433f6c35097176ffa01fb59786f85ef4b2694518afa5813668c5222faffad826b97ec92c8b0739989096e970b366a2b2fefde632f3e199cf492833be9a54411d9d78c536022ce502ac1deeabbd86a49682275b51ba0e797c18fe92708457230ce35cac66d4701dd7ffcdeb316fe4522f27a3e099653808749ad7d87c7518fa02fee83c10e1067057961f5a4725127e11a210ff99d0f9065629632f4fc0e82ca94107886a2f40a37731c45cf22c160962f3d59129142d642263f280a9571fd1127679786cfc51b9ff4b9f25f0d3da7b2294c84f3024376cc0932069de82f3141823447403e555fa6e04d6511272cf0d2228c98a2749be9ccd2c0292d060e4f2012278271f012e2c32b652360bf6e4701fe2b84adce4a3078dcafc8cb8474215346293afe42439bf02cc3d0da7d92979283d6c8f1370c68f286441858c61fb266776eac6f8dcf2c36755a16f7801bdbc9bac2d4492e6ebf76efe88df38f0d3b87e8c4a3999236902fe525b2db33e36e666635380697508fb3aea391b8978c6aa6585a3b3201beed8a582167e97a90ac9dbc75a6513902f9cc9c80216ab400eaf1e7433c306129641faec246b2826fb711a2b0f5a0d7da4bf0a249e5c8ebdec98ba026563e0b81ee30ba846e42d01be1374026089fa1e58a7f3900c1d04e11d15307900b4c784a3fda73cc3bd8ea1eb81bb8b232613c6fe6344627336b5431e11041e9b5dcfca3955c858101333642a04fb71b99172035d8070b5315a918c639043305f214c083388abd9900203e62c52c9c8c3382c276492b971746b4427865414beca5f1e2610ed5a27e9aa4b453083c5652ef161cc04f1d49c3b6ff5f40444af49ebc71e15de033589e68c5b982e1beab107b85793864a084eb38182c94d0ca268cb4d59c54d93be162f6fa4025baf51eca72dade4d00684ccb84fdee4d5d6ee3e7c85cdf13bc1ec699dfa0018a6e3bced9868757a34e860bb38475586ccb3177fda5b6fdc859b5600580b531706db503f2bf55a7416289fed1c5e877baa0f37871be7c09c0d0ef7b7ceb1301920bd49902f834f0ffabaa2611f08230fb4d2e7fe6e075c0a1765e753b899b6bb6a5c5a779754369f7e4ed93ba8bb5bd4cf7194b9580d8ddb60ce85da4e0bfb63faccb5d4778750f2eedb84bf80c3616f1f8542bea8fdbc22eb978e4a030819d8d705c0c4aaf12ba3f1abe4ea7316bb8a3f94d0e694fce11cf6c935530474e099db288d3fe924b72e99f64aefc04314e637a57454e930b43060d314e992b58148d3fea48cf94663b07f71ab7f1255924d261158c472bf0dcc1630a642afbd931ab7d19d340c9344aa8f4daa86be9bb031a1ba52f730573028c50dd95fe8e7447d39832e3cc2d5aa740196573a802b4b9b70313b173f0d503bad24f0fbd689e06a401fb7771ef79d80d88dc4bd36bd243a4dc198646261176094746ad89fa779a74dbfd518c23d95335cc6b5299e0d6467788452cd09106a3bf62e537faca88892f228851266b1c6343248853388634e9a733005a145c6d46ce45f1921a120864c79a9e5c4ff0a26603e681341c3bb25bbc16fcce7fd242a904b81271a8b07c4c75da1fc39925d9baaaa8575286035fe5d24571ae28c0af6abb6c96171e6bfd9c4ab18527bb2d018c6efac2764855d70044cc800b9ede4b6b7ec38ef28738a21933bcaa25d105dedc687e8b8189650eec4f5d1db1dd32ff0ecaaabcd16caf445bf47d88261c2f869394c525e10a758e30999ea6eae752dc4917c6d1deec71265482f4626aff6845f47be3527caceb76c1153b235776be5e6d2a4a4ae63c21000d750c548477260fe0891eb1b9e9f1bff3bd9de9ad952f1b4fc3cb47d12d2dd4da2e585fa095755e56e2c9b6aa40573bfbbc6f1a0a34871e358454c5c9faa2908d4b50bb10f403f1e381b35c9ebb39d00aaf58374b4917498563dd488e341b7c0aea43de8f160de4cb5c2a2883998f6668a48e9e21d96d04f8f07311f34efa84c37040e92591d9feb84c23f32a7d7ec04f35872b51a8438a9753ce82171e73e0000f2d7beb6f236f2ccac0f548b1b06b4a254df3508dbf3e37aad35604118eb601c0f4a2296ed132268a9b805be5a5851cb76970ee3385bcffde98f07bd7a968552abe772ed5dcbd45946dcc5f6cd302ed6b8570a31f7d356e323f12f77e24141f7fe9c76d60dbaac3683013e17af1318282172ea45f93eda84fb338432f4cc5d488ccfaac24c6eb676c8be18f9a2ddad0c7c02bd7f118d7d49c669fad6fae566fca49f9d9adb279d4b1e6333ff0610e1e253d632e83437d359ecec21d3de502f2fe217923327761663d93a7c80ca6c2ce5993b71088ad7652650605e8d982beb012c9159c317839d61d64a9be669f10e85d98d582c2c36af66b5bdcadf66ea4de334682d559be1555348dfe01e8ca584793797476d32eba446061bc900a050bbd3676c3974934f974a10d3dafe8630fec658cb812e00bf7b81586474b0e25f3309bb501781506e676da8086f2924888cbb7ffbbbb9df2333ae069acbc92f069e56e916ba901b6e9d82067a262d6700f085dc5a802dbe0e99697723cccc2e77526c26f15b34efd455aa20374acc9b6114c6a64e36809af56de7f30e72e3c1b27f92f1ffa46ab97bd003f0ea39ee3e75263c072aa9b567572814d68a0671fe698c82ec6003f6dee5943232214b76d49e39151f3069e13c8a378baf3b4d1d08e0fcd63e09b8c333703669bb65250270f60642dcc63b0eb78bec38955cac4db336834fdda5a419e9352c371372cec4f59928b60ee5fa87080c7458df60f82162acd52c531970f0e3a8394d1fd6ad860397afc96c67c400ca3f73f869f892af4b1a046ee5ab321e1f65b650a76e2ab4ef6ad459b23600150b76b800d6d88d3b36ef11dbb710024165580b33fbc125f073b4e6462a4d2b1a58f4ac0315a3b5709f7624e63a3dd1ba468739dc167c3887b3a9ddbc456e12853aaf91cfc9e2a2259cacf0c5794b6ed9f3962840f5418c6b772cf0724b6d6e3c3da2ac764d404d5e308313cd56ee1f455c5f79738b885cd5da77ad4a3e8e2b33995aef7c456d0a7f5792f19c840e0fd9b67810e2f52d7f503870003c3cf4d7508d50e2b6c55e5b93849ec62b24bc929aa822e139c9b57a6df12b4818a15622cf075f32e9eb4952e6bb9510c109e4608ff6931025cd02bd96d534175587fc4d3e281ec845f68dc2aa060e94465c30bac79c2f8bab7cd2b096e3b2ba66c8f9b18ea34813e2e172bd55d47ef937194cef571262da86eb9ab5b32d1a317e196499a093181a8ffea317296b928df806c3f6b39d64ad6fb8fc8a78b16b1da3275f280f95b13021ea4740c91bc8dad97ad2fbb7b6b2787745aa5a46106c7edae4f062cf1254e8435b535f477dc9f150e5488c20d88f0473199146c83405bc8b5507814d8811dd049c4430bdffba1dc7727fd5a25d5cc547b4b36d52a4b4cb2354dce4100ccad5b59eeb53617671a9c6bedb62c6ebd901743dd9061a73286ca38b830deffa0166562a5b61fccbba29db1056722ee328bc16b77912e85b3d807c41b0d4956d295c6698e070645823240216abce102d212b3ce13a24ba87c5ddd6ccbcf6238743943edcecfd462828d7c2ebd3be4af91e738c60686a334beae5266079160d4aaea3cf278132342a6909cec0090eac12b2eb41bace7b602b7f4e23a7eef8ced7ce0996b3b9891bb51ad6f799985d6850c0225452228c159eec8056261ef2ad00d85bd12ae2b9c5e2642bb2606f092c714ee3c9cf04ae1e4af59d0e0213975ff9c2de2281884701a08c2806e3e876851db50256794bbe278afce23a36d95b7474a3dc80a0f7b6f64ba8d3e82f15fe2ed04432f147e9180561ee837ffec21a1a13bf5e304d39bfbcc6b0c4aaa71b315a4a0f7cd437b090cc280ec7d860fc7e6ee35315ebb906138ec05457f98b36530933e1d391c424943dec3701995ac57221b8a2fcc90063d8a8d12402561abfe833083b300c6bef70d9d53d1b79eef8ad7a91b95e7f1d810071fc6dc24d44f18e345bd4882f30d4d186a09aa3cc945293c33cdd60448b93432aaf85651dade7d436ba4f0e77a8bd038eddbabe862f7818be49d9816b3316eb08e47aaa6ad756fcda54138663c37475ef6184ac38125863b4066babe67e3759b7523fd551df3073cdaa10e6aac83c075303947d746e0bbe7c0d73ff5844b7d15a45e834601395651ff71c1fc2da244af3131809795685e3d11297b38f7b5ccb9b5663cc2aeea5664959d8d65bf9daddb1b2ddcdd87b2895b241a80e22b326d3563c80e56a769087346bbb94a9fd96c989f158fbe28377ea9f6b55684b4fbabbd525399ee01a086e08ff5483f33ecd062dc32b54220315d99098901ca28b3403e0f89a07a893d67278a02bd865366f915f1dda5a7b2c71227c5314a1bc59e8dfb2aafcf87e07433d25c54e9b95a526067e95fc40b6a6fe4bdfeb78f4606c5ac4ce2d1becc716dc2c987fb919d7cdc25d395f2b73d54c4cb6fae1351b928086be2694bbb87e244bfbfe15362386db0016d7ab02864a3ca2aacce0e98015cbefeb3a773e7ffeacc296df5c579b3d0f07052a1b9f48aee5f12ebda73f79b95031bd48f223c4b638f8d4eee87fb1ff9cfb295f2977cf8f269f9f93109573ff27ce7e99b8bb31d79d9ed602181829515d135df8744601e8179f043a86fd6cc057b06fef9af34ba30168e46e553c5b3e0fcdf9921db8a1a419f6c7babd0edad46408b9b270e863a32cf896b3a1ac7f25992fdde4579bf7e95ee1cb161aeaff8af849c32ef86009005829537f0a521ca54ca16886c3fba55476b23068d7a24bff95c6660dacea458e1006fcc4197d6b8bbd20b77f6b14bdfa37c5c495a6c5b0374f8bab7622b56039fdab072030eda1ce03d6a1330e0d33e0046d4a70667c273a5c61af4f0b193b896c5fb99a68b26f0a7b442001ab48c9e5fc04464eeb386d3947db3e14a2c38c7a25186e75330c5a816c210b3c65b43a6001f358bc0c6d8406832c32ad5a3002fc53b65f49ba405240e31b314f5aa1fa4dc9ee366fa7e0c4b4c5680672a4b709da37d2c9448ac1b8a317428590dc47429ae841e25d718682497dadc2044cdc57540a0c67e6c0f96033ad7ed71b48bc524a129c0a4e0735a397cf0e51e5765f56279c2a72d0dd1d59247f8abc64ca5d0519a691b710c8d5516f27b1cc6930e99243c1971c65d27414a9c34579269eb13b7ebc4e999ee7cdc39dcd3ba037af9eda42ddbcd9d882d2e816f85b6e7bea388e452b75b7841dd859f25219d08f479dde74cec802e86c81624cc7229137d0979b3ee3342fb7f949b9ae9fe467489b7b756bed08764e7221da1abec729c513bd42693470f36f84c8205b9e793515d70b8618bd0398b6ec7599ecd8c4995e481770d848d81b6a1d1384646521155f28833fba10cef0e603ab91a2ee3e0e0116b3a7685f474d41122cc947b33199d6262ad28b06047715a68ef2c94b329b38ce8d9e428e894c0197cc66e05ef783eaece0221a2437bf60084749f82e3fccf9e779e129c49f8ec1624134fae6852f8bebb967691ab790385e6c3aa4a35b88b9fb1c6d167b26a43e6d10c3ead4a79a5eaf8fb68903bfc5165311637b894a16a9c34eb438170f69ca781d6885d6a09bbf8bf0dab88a38a34f75d0f427d33b3d2a796156bc3929b30efbc1c1e06e226cebf61795a52104f5f95d470534bd0bc28bd19457a79a6649c7625820554d2ab695f80ca0c708f0cfd0e7d59515b81bfd9b0758fbd2b9d7bc1b59e764466416c7ac1cfa87320eafb891d73ac579d984a3a6d41d82e671ffff9221b7c57edcf2187bb0b2db16e0e445722a1dca40c78154dbcf7536cb2527d87f4d3b56604b63640b6b3978bd10af4ce70d0608c4c66c8d6260c8c802e26c4df85291528d6a0c5097424aa31a680df5ec9a41070144f342d30c5d8a4148283cfe5fd74a4a5be354baba5e725078d7d563fb4939b36681cba48a2ff5ba201ae93347547f696b5820b66e436c0c2731d895458878eda0d6d6e440d60ae1dd3a5d34e824b9cfa96dcd69e8329237df73cc6d8be2becee34b5d7edc53c9503a44138031a9be24f935b8e3defe95a8abb6540499a91385d55545dee69321fc1272d08e8ff0b8fe0a17e653f0254aa1f7fa368c6d4d5de4f29dfa8ff4eb976df7b8f554a571b34aa371be258ac0bd21078f044ab5154c9aa10ed56668627ce492905a42feedbcf234443820730740fb1ca9993869643364dbf4b4c6ff4d92bee72e44fcbeccb7f731f85d23195fe8015f0b5816b8290fb37505751b8868c5365152005b97705289c65e7315b5b69f76193c4f6dbb69fec77be5f305bf426917c0249982589d14a620c0685b22191c4a6365d17b8f92b7d3ca72c35f42ce9af50f627b0029ade625b103523a367a696014f20ded1862fe0e33ccb19092868b48ebb4ad43969951db46390c6940d8c8e80d74367915a7780044ac04df259b3db4c7dbeba6e6f56dc61edc215b69b283639aa5981de21d14b8a62599a0e59538442442b2235b090a1c5989d4687fcc93b9eb0c22ce8207fb6a0389ba0870738319a125d6326332756fe54bfd33f9973c0c35e91490469c317536ad3d848c878711dc1e52952ee89b32f939596fadcbd27145407313520dbb32e13c5229e35f9b857cd3d4604a8bbe63cd4cfe9838774a5d3a72063812d1b66e9fbf1591ea449964d4fd451e92f5daa1a5fd6e6e48a21a4814247ee3ed4e763511169e06f9c197fe00ab271b234f27f993c0c8011d1401b3c3db4754ce7a3ec6be141f91efc5a2de8ddbba7804f99987ac9cc8da33607dc479de58405843f2ae6ebc8533efd97282c32de3de3e835afa38d634d1aa87d76a9ebb0801e421959959cc34efa54b3cca73927fcf653b56de764a20564f4feea9a7a7c2603fb351a5934b33c0a278a49cf94a7822a52b35c4d70899e6d1a0e7b384553f4416387808b62bf3766ce0eedf077fa364347b031024044b52034989abc811bf4126830bd443ef833e5d92f47b1ba082a6232e46cd3ed6dde994c71ab76827e18550cee24921d0f157683a711cd58ef8afd3b27bca1f97afdec1eeb2ee009d895ea9dcbbc99c3657c7328ddb9258c1a347edf58200739425ab30d9c1ae1ff84bf291e616f5b16198da5f74ef9c1aaf881ab447b549e7ab1486cbefe1ca097e79ef0b868d7a501c64c178a7756f7547286aa0dedad17b2bf1c4d8608588dea2b765f6074d57e54dc95030e1a8601650ac749c9395b254de1a79833ee153339322bf006b01e7a43a4b15441857e808593d775c7a85587b56937194434a089469f741a236258cb04a3d5dfea4079e9fcb2711a1dfe68c3bc22d150fb499aaf431056d65079029454728b086999864e2e7fd1f968f72d37ce77601a727dd182715de56e0b79fbc661e66c289b0ad74037a5e536958fc95eaf2c36ea6504917550966164b5ebe2e668ce265135ac4b5c949f9fccaddee62f3d9e5905a91abf536bcc029b51dbd38f8e01cdd3c414975086a2124e23ccbb19818680b2d896a750999a9b34b826e45b95977c74981cd0fa9a64bda3fcd5ca96042f914493b8c8172f4e6558594275025dbc90509b84c5580809d22cbc017b9f923bd1c71b845a928bd91ebe31a50af6e3a9f38d24086e0f3d00688c88cf8b4ad315b6202ee7cb25674e5823ee5f269c7c281bcb207ac90438a56ec79a5cdbce21861819b798c98d326da3c06644a4870ed890150fa72fc24067cc40d02d856ce689b315b63d1c92a1619fbd9022bee6a94e88c25209fe68446a40b229005f70a4e45b509bc78fcabf4b0995ccd9e46d9058ee6e0acf26349a9dc36324903bfc6001880736541f402c5d40350d0e331b822d84ecf43295e28bc7ccebd75d1542a6b137bee79bf053816251ce6ce89883b1918e6f879f1a51324801c53b3edfeb759a41730f928ce53d70eb3a01608931723075b9030503eb38297d38f3b20a444952e7c4054a0971ba4cf911ccc8f3abfa0dff23fd170e0cf0ddab25f5ce308178be9678a6f7aaee4b1ff45bbffd4e1fd85d9fe92cbebb576bf28851cff3924e5ebff27c1fd184c37d64fe767f71f492abaaf7fc96ac23cb479ad16c5bdb4019f4c84fdf17fd2ef546278f4c27f937a7c1bf053772a53be0e7886572a930ba9a9cc55be9190a61a23b51486c4e6c901dc17a6eb4e7626fa93a7051e22163f8d34ff8cb2f151fe5879fc8ec3f09fffd50510b52cdad8cad8f5d317614a36048f3391ce800c49bd09bb91e90f47091f96fd59c1a5a7dbf47ef884e14bcc756f59815555016fa8cc0dc3fdf9de32af5f64ee14ff57aee0a26cefd51abfea360c8984add837d82b450da5cb2cea355f7fe47b63fb196b8bfec78be2756ce9eec2cae5802662eca7f98303388efdb864fd1640f6273d59cf1f32e1d81fcdfeb30e66b9cd800f5a33eb1f75849a41fe574d2c5f0d4e0cfbbbd35186c238f2a7f064367c10594038be2e51cc942037a7c5f2b1a6866239920277ba1a65e7a60c4a23912883c430c41a09d8e62aa53795cea2cfb61624c836fb879a9574ac9ef68bb7fd00b5f3ca7f94766e9c9a7e225502fab91b99e0b8ed631c1ceb84ddb012b8e93836fbf77fbd56001b1f8b18b61a43872df64e6a94bb90c13e32541878b96ffcad4a8ea41924ef319c0a65e6e068b7317be246a2d1744682bed142da46dfcf04fc1656829a733f092bec40124f0027eea838a5409fb4e1fc1b8e1fc245973fa6c53fb9a9661b0c0f9e1ff7536a92488d09ffa3c1b5a1f0a4cc7fe88ab0960d04b17b4d8f79bde38f0b2b24f1ddf30a3fd75b853651b4e73da1c840a1f0fee536fbabb213292ae7b1ed40dad312024338167b60528609f15457ae7b6941e8ab01302c72f6db288ce77f7161eb35dcf89bc84d70d36ee6cdd0ebf1c6ed077bf5caba58ea497aff1d9b3bc4cc9802a7b4ccf2f125482d6e0d1c6c4cd9b43fcae01f82c0562a8fc3be3fc2e534e939fcf2aeaf4aff5b0cb7aaea70a5c1480de76e00c2b35b3a205851ff1265490d126f0df0614250de7dd3bee49b0b39e673f2e4a62cbd0dea0e1f0421cc345f63afc80dbc51d1901135342f71aa1d2d5862118c869d050726d2ee09fef3e1384078a916accdbdc7fac1bc9e4042fa26fb9fd80551a75c67bc8b28c6db6072d070f54dbbfcf7bc826fd172fb42ca356cb4c4a9761107e0214e95cc482d756a342d66b4b6618d976349b25b263d2ec79b53c7d439cb1bbf8a68d0bb0ad484246f111a908c2be6adf43360fc50e093c311deeaec42e0c5818aafccc6c815d3244d88999daa4257510c34b43eeebb2a608b674e2b3bc4c38ccf2659fd923c0ebda89cb2d767b7eb9dc727a4899a605b7f5722bb8882b280ae252dfe0a925dd002ea5db098bdc349b2d9a57d21210eb44c4f9961c7f89d136fb095f9e0cf685788fed0e2202cf27780eeac7aedecbd2568d63627683ace9f5b6c69d8391a6a2b61d18c967a9c050ebc93950dbff161e59cd6f843de895d14f302161659c0018c49e36cb7bd39bb81a8eeabbc06540c5933db06a557a904a3f9c56d9d0786e4118e1b4b53eca846ae0212f930d863b8738af580067a3ee5b6c6ef1a64c1c27215324817ae8ae9edd62d0af33f031de707c06431e6e2de6f00edd47958444e5e7e7de3da0349f4670a6aa1f38ed2300ac7aa606d111080b2695042cc0f434870bd3bf710cb5255fb557f71ac6957f04684ad49ac63ae19987e04fd0981f4930dfafa236c39610edfc18040e8fbd7afe72dfe70a04597c197fb41d998c8449dfef34e650562586b6d09a1cf9610559311b157de674834e0b051f0faa74b196d14074e5d78b5ac6fb2286efa45a9a1d1189ed4573fec3323dd514af5d0f5de4e4affc147d2af527a69fce7b5376a90a4d86678ca62e56f5cb250fe3796fe02ae90c4865677b9874c40ace6bc288daf987dea8c841964cc37dabac6b5885e52234433dd6b00525f3dfea2beebc068bff9f6fba915231bc603b87d7a227dece951b89e2c2057babef5c57559efa59b2f7677e183d53e718e978ca1df9137daf4359a884937b40596392364a48fa217255d2180817fea2d3eee3352533e66b8c561aac40a0fb33c0044280c7059dc11cd783222a481a0cc3cd85d985b13ac204b790a357a845fd1502e9ff07ad4c1b51885e1a2a374748c00af687576219f57b49e8521f7e46055b6d1ca53111570d217560686c50c58615b78c04c1e217d69cf5d5966d6a6fc20a0b5ad2d5c541750704a9a072628af962b36de467b563026438d740341e4d359759609402c1935ad60eaf6f99d06b02f8781af16b0cd381e06f58745309c882490170d5f546aa9221348173440a0a8f755f335ddfb2680a1b6975d95fee57ae0990c4714abcf53f2bf79bd03e8a8e8b09489dc474a958b10e62c9992caa0a5e96853d7f2c490eff718273224720119c453ae3cf13bb8b151dae622a1406ec6d2b00913c26423f6d3ad5f16ea1f1940363aaae7c989742b8530d7ebfc07c6cbee8dfb1d7911c91f1b18e570dec5182dde7846af6ffd200231b46629dc0b791cc674c0568c9e4ea6b8f86fe1ec81ef7ce5dfd505b78d36764fbd5d17cc2e357f95e38e885c71d3c954f17dd6209ca7d83975b9fbeed37dfc13966708922ff9e13ceed8baf7881c543790cb87f5d2f80643703c35c84351cec52905dffe9d645f08bede8eac7b3377119700dcbecfe8850b0d07b1a710a90190b66965916bace8ed1d34855886ae2e0b0ec88e0f50f61730a87d8a062fcd810156c26a3611373454756110286255659fe2515023ac282c454b70f80fe0629c2a5353b1802d35e33e619e4e4c06ff81420ba10bb08d31ba1ba20e252ee90ae239cbeed027d4af5f06cb7482ccf30bc254b19ebcacad8b580c44206ed0ececa6682e8f7ad65e77a43c663103b3dc23cfdcda54e48de000473fe9d6cecec9bf20dcd05e7b32b3ac8b71d522029809e537f61b796e185dc8d63e700f03c07e997dda89672a6e4f6eaf9058f0fb884ad6a6d3b8bef13f6fb76da64c2c90233b4455769bb06b6f3b7a91dfd753c986ff6157f6a7e653d96fd135e32b39aadab067499268eb8650fec8dc0261412928500763aaab80224dd009827262106a2c900a6273845a42bd3d07c5cf9f21138a82eb3b7b2be3b7c264ce752edaa35d423eb374da021dc2b617f189aaec1a368202bf297fbb6011ff5c0157e65cda8750b7e3adf2536720b8cb94419bca4a4ac02de87fc9cc95c1f64df8f02ea3a05e8fc19f85d63cfda3c9257231064f9979553bf202ed6582d1dcf75d8ab168ecaa3860cbc08f1f188a72fc24754cbd51fb5fac4f5bda78107b25d034480fa370074ce14c2f98e31a6340372b6a85b2dd7d1c10b65872871a8c89bb5b6d6291f1ee8c83118fcf4c6876d2cf246139e3c829f00c64132065c272dd26f5ac72cece4568586c93564d70375fc7d510ddb0e60f86a1f0adf11ff636dc17f622a87faa8226278d1085022421aaee77122a234588227dc00dbbe8c6066c38cee9bda5a9e93c8593b917ca05a114fc24e03e5775ec6095f9036aadd1e386bb00e4272617657d12658342e3426bed1b2278c556ded8d22ac052084371a8558bc01933caa1188ad95843ae00892f1479aca1f8e0c1f94654fe6a8e966fb04ad38c19ae7398c83f0b65d8d28d656215ab5ec576a3b6082e9125ca956472edcd6a12502db97b0bba51b225cbfc2e490961f3f3fed7880fa08b59aa3175cb616083a360c89279efbf5166a5c151d8d7993aa16adc259eaaef8c919bb1881ac1a2c0896bcaa74d5b72bb06706a6ecabaf198a65b58b72209d2378b9bea7a78b779289c2ec7254d75c3544a945e6d79cb32f42bb8a4f0b00434151163a3a4d593cbbc6426d369380a067151dc55c3c44113fe7b1e8da9612806087161ec3c75d206ea41feefa987d925e7de71b401fa6bbade669e311a6bbcef828393d468030486ad9d006611fa4aabdbb8abe9ba4aaeb9847216303cda8076fbd851cb33055574b202204a0c3c2c487b8dcd06b37c294674a88fc99b45c6469ab17d8585d2d106407b08eda019babdd79f360aab765d93f407d2f46c367854779e40c571bb6e430fe9930201960b2bfc186d20616637db5bc8be8be444fb7c952140884398ca66b1e00319a88c4f6b36451b2c899e79d3f4a8c5d5eafd4156c28b2ea970200342957ad083f86c10fb690b3b74464bbdfee0b301fbe685ddd420971c29401349a7a5e61a7472f3e2e6d676ef9ebbe86ee1cb50fde19a33ea892cc59b4222cad73022a4a917b128a7bd6b60622979bb85b2556ad77a382574afbcf15e4265c35577811ee9148a5989c3870370ef610b364f21914145a5122ddeb6f87950dd87e3c3ab69dc869e68481ae43b4fd0b36868e06a391770e823e6ba442b8b8453d80d2c7088bd6aa039971a91da348582c4e51fa7eaaa558a09d72d0cb232b291826038f626a44e4d0463751c787c021f6f0ad2dae04ca9cd1aa4d2466026c8622499ef7346d7fccbe7490f2d9df3442051dac959c7d784330b081fd3736317d2cb69ccfaa5f43e554e348a07c7a095eb4101278aae78b6991289f71e3a2357e21c2fc2e0e793a206011848c528fb79c64f53f219d9a6c5b81a1650908ffaa9dc7e9d7ac07c647aa10b15a8fd9c31be18ce4aca22f00499341521cf195c24a79cd18a2afb1a332650ab7802d8d1fe5baf5f42471019d50091b5ff0592954c789db462c19ac469a16ee40393b308b58e6f35104913ac56f5bf340e7a4d0dbae945c56cb7e0e1d9566febe127029447e204513d76f5fdc4a470be7917d9e972f0e2afd2430052b0a9f4a2dd4669f739f8edf1917bcea9c4ed0079fc420fc25f83879e54cfe90781411004cc33676ef018a598ccd600acbe051a3e1093ea39ee4d1fef0718bf9540164a5be16a1d48fb887b09f902676f91636cc4d9ee83a4ce38032c88dfa74628a7b1251732d4ccee2cd4ccb1ccdd36111bcee342406ea787c79ce397508e5fb2503af2b040a824356724e30a502179bb8c6a336c3371d4fc658935d4181f5c1a793c8f89eb10cf53ba5385894efc739781f1cc15a12f910dd0cb5fe0430a9ad4ad9e4569ac71aef71079484e34082b2df19e760f2a821145f0f7048d6f4146aa939984803a547d77255fac21e1c2ac6665f153400335f81a60630f396225f3e2c7465cfd14e6760b20e9d6edaf1bc6705f4cc8cf575e2c511dcfb48c8297d7e946c67bdbdffa73a0207c97c0ab0e262cb5a7c0b733a4bd8f326439cd44a81de72989a25a3d10853f3fbfdd1650b725039e2c57eac1650f9b10cc4473dc3a04ae8f4c4dfbb087ea1c1308b2e0fd02625aa1b514395d709b3aecae19c0c31c5d4c0283126e959efa474bac198aec8a1a6c53052b04857520e8bdbce80c7f34beab607e660bca8bb97ff423e8d1dc7537b1ffb2fe2a115d15c5bdd556cb94fe4475856c94c9e296ec990a240a0d331fd0536d0722cebe172aac5afaf47f25e366b9718e26bf79987f9202a9c829c20745c35d6c2e852c984ab202a6d556dcbc04de62c74ea6ab2212241bafdce3345136362a721c1c5c2019b6718b0a7e736e5acc00333d014d7ad6a56d040afde47d361f19c93947b37c97e24d47655da3327a9f594e46c9f43c58991718232130a546c03cc0c81ac3b12e2361222ec87ae6b40b951a126ee409e849cf57c5d66555399ebe38e0b95e8b2020b79363c5fbdf73e0ddf2d0f86165ed56acad1c83ffb6a1192ce5941e28d3270113c5100fa50a1ce683430c4301531c6e7b1de339a102f4b0e6b6db4f23b16b3fae68f4426b151783312d294e22f330dcedba593582e6af622e1fa298f97de5d2f9be1593b7786e33ecdc2a6ea96b036d7e882fd37e39681fef2b7c0ca90f464bff5e2a1933be6d8547859e972b94957afd0d0695ed206fe0b75fab9f04cc21ec242fb26c8c09024949feba9f5daf4012e25b6deeb4f25ef105f223b0c7d71fbfca6d43f5663f25c5ccf86c15670b0434c33bd77cf31563bbc19106981a6557d2e29088d67c468b07edd397aa99b1a6424687092c6ae79045722b4215280d7ac5f07cb1180fa0c91404b1ffd0c0dfcf0b41d1dfbdb1dc6e2575ad61f401f1f71827f541f9cac859a20b6e699450c1813eea20903b01bb8c29c003e2a4fee05091ca637f9e1fad0ed0227512a2a36bd7e63aa3f66b388bb52611d2e246c9cb591142f0373b06edf2c9fd78b7978f6783a1a7f090bf18bc5fad13bccfdc237bbf7cf2512431cc7c5430f313f0fd7d4884598b9cb2f8632099dfa4836a4a99296f0b8a347754e78c3e08ca9216a0423366b4e3067e76da5298f5aca1827f1d4bad6f03fa2db26c1c2349f32c0ba210bbcf835823c8c420880a0c8ab1b4824158d21b1cccb65b6167bd85c21dda6e3e50bdbccd0888b10144041e8674e3036d6a178b31d18ec327ee9a943954383479c93fc21a8fb543065b21654a118c13cc0f2c34c7889a633f4904931d5ae0d2405b973605be9cd9fadd91536d990f5974d7970239065addd0db7ac20f966b58c0cdc9f814d94e6ad31580f27424b7189e1d3b7976e03d52238ff767396e1c4f92966747657b04611fd9d63fe2e0811485ce948c095fa66ae95417786cb8a647c6074dde996e4f96d5200fa995f495de60bbd321bfd2ae379cda28fc82506666f15e7672fcf57298bd7c4b90f62d4ff8c2c8ba5ff6d285714264c6a6821324f29d794f9c40a74abb0bad2045c327a35cac25c12a6f60f64e035be2b0b92fa2ba60f9e53e183229f99d0b399168174bee85f673526349e43440e568724b72eb475515c35feecc644627f181723da4c27aea1a9d9eac2474b71bd45675dc744506bfab468cf8a0dca0b2b01c517c37ccef474b744b3baf5cc8658444a1c87c849f550b08a2612abaacbfee646a1bfb6a41759b1a0b44c0de52b38311ac897d613c277d5efcd100404515274294d4e93d9c1da388c9def1ba4b5fb66c18451f11eff5a5e4b25f62257a84f12aca9a08de493aadde18408532f7646c6bc75071a17773b2963e8863a8ad04f10f1cbb87670858c9773ef6195aadc4062e7f996f310e0f8a4d4aec1a157e4fba89a2646b5ecd292a75c1106a549dd58f3835089d5b00e074a822f49b00a10753b5bfff2b12d574d4e31daa9faf8a9c348e65b610a61d3f22769962ab6d4a853f17c532a59a29e36f93ad5c9db2ecae4ecb64e59a838b57e599d04719246a41ccf12cd3995bc7766229488946189c972b15ce6cc5924798dd989f05db20731dd2d2cd1f20a94c1063bb75b9e4832d7e84d6a1a4de7573e5673f217bab3320e470ad88db461ef4a5d85e6f00d782b8db003be0c420cccb532a782b8608a898f451c96fb7b92f3129d7a458547f8a928e373db2134f70a36f491a42164b162ccf162496645d1d8d9c4158ff59da588825aede81c172a59ac534ddf4a873793b168c1aecc059d8b5dabe17a2b865b1d32d0dd6ff646195ce9e5f07f684ff2394ce5f6e7f449b19574bf66df92abb02ace7520c1a4444789ee12abb63d8116024c62c7abb87e640e79ef8f18d96462fb8dd631a940e300c2ca9ade500ba0aae6152183d96bb6403537878613454272ed7867e19cf02678358373aecf4b66571b0f83f850b5a7c652e674ec8269e56348e02be54ccb12db18cd69b23767e7269993eeb8d8c5279645ca11ccccd3d2fd1da18d5afcb718d96750f6bc590184d1854c8ed27ae2798207ab7c2a3ae29907e1eff4235f7ebe8040cb990a7051891757bbc9593d08699c76d50ea016a7aaad68870448f34eefd45660204fa03d463e1c6409860edebcc3a27cb4b508ae45cf2d2acfec0d91f51c4fbee0c1bfa261c1c47d152ea3a7aff3a8f065a3e6b192892faf85a03efc5b5d6336941fdf590607ef8acec096c22f39fba11a00f6672fb08ddd888788025d740039f11758817a72c055ec1889daaadd3e73baf9df1625a0891e750604b302069619fc0036ed6c53ee25bacf4ca31e1fadd68c77ae542f70dab8826b00ed10106a541f99b5f54a5fbabebd0d991ffaa927fa78d560add28fe08fbd66145349c2e2591ee4aeb1dc256fd4ab7352f44310240b89fd3bfdb9d289fc9d437379c36058bf256ac3a63483b4e20bef9252c8484766c03d68ae5acc436ba65662a80b8ccb08de04fdb449cae5ce49c4319e60f5ce8fb4972ab858ad2e602af963f92054348b38249379e1ffabb2df5b9c220cb702f92a7062177efe5bcab725c5e53943dfba69f310f71d429d497d96ce300ff1bb5f551f305fb3492fca17de04273f2bac0194baeb838bb000d2556aebe7d40c31255ac6536af19b34a8a3a040718dc58f167d3d16551c87a017f87647a27562ef4830a5c0430b673508dd90c55260248731939ebc4bdd7abbfe6e5e081fe0009e79371a580ddf7e0717cb251225f42b267a87b675ddad92aaa3753463bfd230b8ca0421a22e61f57bc33b0d0fa1a2524643ea262cf51251f016a331f29ddb3d8749606f57fb6584b77cf6b90f742b7c7b4bd0c0161db3b173bc0ba93a8f554e5f09caef6ac7e5139112a92528ea04e0be8099047c3e640f55db57bd8969c0b7b5a99feefff4d60fb8fad07eb142c21c519129a669825994fcd0acec5602c9d0bd3b59470f8b43d8c0b730eb4b5dfdef619b2c01729bf2d596bc9125db0774e08a742d1a6cbc53f436bc485b1ba429198ba7cb428f9bb216e6efd17b20676218157afef3f81ed0734e471f72d90250c17a8dd75614ed910756f64d01f59be57060ab23c484d8f196327c48346d1929ad0cea5c8ef4e918a2584c625ad33ceff46f8952f4d7607562ffeeeee633de6d7a2fa421ef27a980da427cff4d45f5962c1a70d6b89b8ad58852aab47af230092d01cd60c98321b4aeddf0027da7273549306515d914fbc4ce5cd25c43ccb5ed24379381801557f19e1d7d2e14ee0e256d89ab0a422915727d16cde6dd128d9d03d11b51d5c69d7135534ca6aeed8e5b05881fadbeadafa458af811c91757f29e1cff2a0c5e12ee76c5f5ad9e80c4d7798fea8bee7b8a3dca2e66e4ae3980c203378dcd52172416d9855a0d0038099755298a1ae0f0b86acc3392c5eaf17c71bde76a3e32f3963e470ae263c00438661afe371977a22442d9b837b99251b354b08784bfbc3e21cfc5134a2c68a94bfc1b8027819581db89aa3763b775864d27c978fdc8746b1ea82581d063026a2ca293561a8b1c0decf1076a18aa4fcbe73837f959b7ca8216874b03103fe1a43ec22340878ea697d5d770bb87ecabf7d56bace48c11aff6a9b08309cb341b7539e3c707fde42f72c2166a044824360e24638c31065ad1b82692c0521a8871ed51ac032dcce313f6510e0a4160ce97a3deaa8e68eb900d87b7c2726ef86962376a7e36186fb9175607a6aacd97b7c406c367e85bf8f9a6355da3e167b538bec7bd541f191b1d2a0176dc2c4832138bd293b54c93dededf8410af2f72e0e37c4e033f3f8980617a9e2f4341798a5ab15a798d1660d4028dee75adee4d0af74649470a228571e9d94b594981a70a84916add37da6026c110e9bfda93072314f35a18f956a49698bb5fd8d12cb481e51ae5dbef7b5e53222c570be1f9955b4fc03d825b4061e195b5ffb07b9eeb3a750f40e848b817fdf24694282d0e17fd0fb860811cbc7565c19fb4592545b8fd971aaaa25aa548479b6a5babdaab57ac836201179f129a1333f085388eb3986fefd370dc56b88265e303ed6c0c0c53fdf443d9502d28835768ec33cb110f7d9dd9ad3f01c39d6585f8cf0b5d2aafd279c421edad415dc82ca599c8ac29caf5d6435e8cc7f9a52ed4cf5ec25372c543812469789f28fd8d8d96072da1262fc9b892ca149acd93dc58f18fdcfdf45728238554a64821c356a1ec56461efa9926f4a5e509b7715c341f4eb15237c60eb4c7e2b41340a0450b4d1c0173e7dec2bf4c0a7248206192cc37916e860be965b151587766cdd785fa005d0b20b2357bf7717ef8e240c619e8aa01c627b16de979b3d56cb8191983cdddac1893911d6b4dddee1591f6a01126d65a492b11b074415e2c07590dc3f015c1182b92323d2ef456a1abe24fdcbc12cc4f17b7734be358be389844cb19537b0527a42cc7a685c4bef8e4fa2991f0bebb45d38ba4f3f44cb51b3cead60c764fb02760c51dee1e32a3bb671f67585322e23e86173630c0413383e7335d77df0a96d0ad6a6161aa14a4d4815a3d7f6cb74c4337da781761fa92a77e06934966e735eee1f9340410151d5de5f0247908ac95e60d67ecd78b9e0794afda2c47ccf319ba1389eddd974eb73c23cc763b884de374cefb9060dfa5ffd031ecee0b1119600ce1d2d069986bb1f099c0b036b1962c54a5ab693fe44675bd83850079da9fab5f3a073af9cbc2154779793ab22e7225a272f7dc599386f61ea1a2c148142d37150ace6786b2ce67caf2fb90ff46b99a616fe544088d99d43549b818211980f5e374d0aadc7b38ba7162aea7e785548cbda4ae85fffdbf7afbbba4384c634bdf9f97d2f1f5bd540689501364980a0e47b9e9985154f91ff9407dc2437901b9ae648fa30021fc638ec2af60846bf3123027f2bd07e508da449c3a90701a4536e0781313defe21cb11482933251afbc50b9164c01b75f9327e7ba850d458f7c5c27632f825c9c4585885ba9f735b8ba78c2b56740d6abd9460069c5d02aa71814c5e4010424e079c48509fbd9d87254ec77dc26891177202d14e2a3955c91b46bf28b6354fcc4f99f5ea70983f81cd73b51d11e662407fa4dede007b5248697cfc3e000c01690dfe98597fe37b1b3714289b67ac7dc86f7b77db724b99929429b704f104a80477766dec707148a66463078f7b677776efbd3317a14c9d973017b219ac07180960383c2d2ae2869033e9d3592ae188e082f2c4154c18d8d431ddba278a3c9564aa2ae6d9e83bf2672af98ee9c7dab1c88b8a6e2fbae8e26c36b3b163cb8cc717fa640bed2189fab0042d82c160303aa444049986408623f758866c48b52dd2e7ad20b1e5615e248aa2488b6614082ba5052a5a24d222f145175d74d1451769a92864a2289b55516294a468ca501351f48fc5542a95ab602caed2297acc251689f090cc61be63c23277e46c478e335a98f9656eb1b9154571c8771e3063642fcb2de2d0c762624c8c4df1b38781cdd0bfc83136c4846886bea585c5863445c533994c9c899fcca0cc4cac391363be3391c4c498288aa29128c3c58484a702134a2975efee9eb5d65a6b952d1a5488ff7f57ffcc1f49a3aa2bc4fa081b0c8e87367c17a539b42d2f586bdbb61582a769e34c6c7928d03b27744ff70a3f9a2a5dc75f69cbdaa75572b6eb68784ab8ee5adf29ae11030c8c90439c40ee20ab136c249bcc1df9d32f2cb37665eb8f9b95c8de9de04116a315118ac9623c82c631a8729c4c867f51fc9ab5be93a5a4f87f7f1434e0ac22ee50074ef0f6f696e0cd04df91220a5246ad76fb82c2ad0341e4d7a13e35a48c897bba19a48f7529a791283d294ad143a186784513bc6bed6b04293d51fcc21c9b0a10701cc7b54c06050f1ebe834307997ee5e13bd68e76a45f47aa82ab64085f69c4b2d609a5b4d24a7311f1f0f0601ef9f1d42aab9492561a004a29a53cdd0326b96bd624ed56332cb72ddf0e494e8e0b735a9f0b3826332546b20ad1134e26eb3b6b36c8646fefd4913b3566f774cd09b87ae2ca2cfdaae47e7e5923fa407804d594a83c0de2314390134ab2224231598c870cea710cca525a60c4590b8ad6de60b797b5d676b693373b4d34786a08811ea0943efd1e57e62a3105d2fd218b92d2ffff27219aa064d908c857a8f29ee5d7e56971dc45e9b04baa586d569bcd66b3d96c369a1a96bc49cfa3fc87abc96469562b666a561af8f1bcf6214d9d37ddf6530d431d3434185ce9a8c1290f955aa1c306a3b00dcb632b3e8cc23658e8c3365f0aa750df89357540d64b524cc3bd3c13cde42c57ebdb8fde99424cb57dffcb9891dbade5184b7932cb32c7fc566b6db5542adab79a41a81431f43385101942fce1808f98ec47ce600508db8f47a94f4a653fa5f724b11050f020f4336f3a1f23ab905c1784c8f94ad22709e9c3e15aa4c6aa8ff69161a45cda409eb8d29ab5dd51a5846f79625aa4a2294dd38fb5f7ce94e40d759bb7a1a10a0299705da247f754fc61be4bcb98d84bc798a69fd7bcbd8cb1623725a48c892b8845ca903f73e63b3f676f4428d631a69096f144f20ce336d80de6dc57a5acfa7f251ffa2e3ea890ffdbed769bb79c6ab3b1c1c3906e45c62f5fab1175cd03305d92268d0680182e872b8f2d1fc4b1a4945f721314aad631a495ae8d58c669abd97258695d6925b55cb45aad56abd57a28c116a19f0c988e1065fe020c068b113d293214246b2294e46130a2144e5c180e3da59340c81e2409ba2f1216ac1949605316a1425c6bffe9ffd75abf4787bd300c433725d9bbeab24dee2a56fd731c676bc51c0854bbd51213ee31752c36b1660fdf913f9d65e1e8b85c978d2f9c0dcae1ce22011934f0be1328e528e59842a5505c9696c6a5a571694ca152282e4b634d0a75023f8f86e6d6d0d0ccb05e1ac8174b9c425dd673014f2770e9f34ee0d2d2d228e5f875266b39eec32fb3349868e84629471a6818979696c6a551ca51ca71695c1a53a8148acbd2d29842a5505c96c6142a85e2b234a650291497a531854aa1b82c8d5746fb2914ed5228da9530b3657574e9c7142a855a1a1dd3cc9f203dddfde481cd719eed4cde079e64ee0c4d0dea53a9e04085940d7e1256b9111752a9542a582c52ccdb54e23a2bab54e645dc572b10a8c32b160b9b3b02949cb9f3e2967164b1484d2c361b0b162c6e5aa06646d3e8c511a9765373a52865b1c614681fc78202064ee81e19cc005be31cede346264c1daf7b4c4832d913a427376496127b0002cdac58b0bebf333317df197c71cd6a0e8140175c32651f8805fc64bc930dfc6c349b5846486ee44648b22732d993f9a4a8a8850b9a24e24c9e96a32f5e80408fa977b893f867d8b13a09674448e845786791196bd10204aad8c50743465e94aa03b9924996ed6028896c68064dd88b172fc2ee9e53b646463264b676db368b5c33cc0dc607e01750e9839c6fcb38e6c4f87c25b1cc70da6208c08454abc9c4298aa128abd0aa003819df0c10acab6b309e369231dec4681f37cae91e0efb282b5a2289aa11677d947d93f1998cb229b359abd59a0102673b292e91424c868666cc980142081505a51e4ab2888c8100020821d0a0b63361894b450821844063004ff3311c36680cc05fc9279938138b339c414e3880afbb71efeaecec804016ef002db15baea44fdfd626423718ebe880401dd6010f309be7abe1c9dd417e98e3cc3b753adcb58f8fb64da69b4df6954c9c297d1ec98bff0f7b0ef8f89db4f371a0e2effc8162ffda7bed94d96030189030317c12872d2e32cc185923e50e294676982c5b0ac9a59a8d1c6292a584c1602afc4813c84c79132365bf66912ca51c132583f8a70e8f0e08b3ece15e62c29a3a17e46e8988dc81e92a625ef85a9029b661bc84f1c0b54982041b488211207b2d7bdb9eb20ccaae8a8d62d692373f4431029bfcf8bd10f48061c10d6eb8410c90c01811213f9e3539263ffea993b22c14a16acdd60c144262160a304240226c322aacf049999d5a8879451481320c31898e636d20d2d13c2676ca6ce2346de2f494dac4e9fcba2a13b0400465488d881f375800a302e48614252b416644b622455b84dc18628c727362c80d22642a03c06e298068cd56b843162a4a64b1f242873b933b4a96248cc8d2c48b24ae4cd08b1bdc0bc307395c9a1b865c9413183029306e23123e8c4938398c4e44318a995d530b3831dc0fc90c1fb8dd8a830b19ee69042c48fac105946b734749a221d55e80b99f3b4a12521049361843dc2f7794a42c2ec4242fb0580e6aeac8562a779425885a4bb557acc8d1e10f733f2b28ae3ff247822b747c0a8e2dfe562958560a8f6b4dc04b695ae155c5dc63ffa16354faf3f3d8621d8f2d4b87e31af3a79f748c8a6b4caeca99d18aca95b439dd87f45e36b7784c3127574aa10a787dc34287fb0aadb53fdc0659426e63990f4ecab55163864bdc76f7fb7d79369e9826a960f1be6e60b8af36aae1ee3e6407dbf4e1f50d0b22aec4ed3f6e83aca1dbb8c674406a8c104c62c07de34d164bc96d2ca7c441e99dd6ce1e8e54a526b9a2f55818cf6a59ed58c2ccb087013e4bdca3d216441b524a2965ad392d5aa7848b2eca66559424299a32d464766777766777164700c8159f26122560820387d8d0223ffeec0e47bcd793c58911a70991217f8b204fe495ad159f6007140c65b9c188177e2af5916badb5d65aeb10b533042830a29fbfa3180c29c84e72e068f2eb60554858d48f2d41669828aaf9e1e57a31a2f92166888c6d26489153134a170621b00922991b867cb4930e413c116c126412bf22221d0e2f364f052d58946ac871da8816645758883511c3d145e9080814457028156d40c85864051c369821c1d104ed28880788e0606244e11544a31caad150b8d9b25feb2c094c98e4aa88b1706d6cb872051e82d89e1283a231bb700205930683c18e44d1a202203da50363a6307eb860ee285aa49440cb0b5c50b95cee285a6090e40a102d4ad0aee78ea265091b19b4b828cd224c6389e90ac79de30548dcb152e852d0414787254ee1c76311e6be46c8d415e4ad706cf10a67e998d8aed0f1987ad8b9cab231438a9d5563b278b82f55b70a5f61a68e5f2d17a5dcd13da92c318feee964171e93b58345140fa878078bc96dec49a1e93d4154eb110cf4e8b0ffb77c3c010d645abf8ae90a02f36b4fffc71d6b8489232d0f0eadf4516b25eaf6705f570292a7016d93ba8bdce188dc12db2c16af6fa4cf8ce57eefb4923f3468cc90051357e220b7cbafcf7d5af54f25e276585610d79faf62ae9a2a06575cfd5692c94a12cdb062d34a369961ed1d8e533b9b7cb2c4e7cf4be6563672a1262e4dee23159a6489714c706b721fa9600308ee4cee2315845ad0eecd7da4028c862bd325044209181db81f0c421e6e97fbc8470b4224d73b7231c68710889323372a37dc0f071832a8618ae8c3f5721ff958c104b7e63ef211a44596cbe53ef201a3b9a37499511bb8a74295c245a3e570d16834174d872b878b46a3f9d72e1a2d878b46a3b9683abe3f4551dfd5c7db88aa48d009aeb9e3a2d1683453cb90728a0aca08e1c4613b2146feb8bd132de68477174d872b872b29c9f522486fe58438e10c1180c879e9fd2d9ba391ed235bc68cc9d45d8a31ae5f07a8073febb2b56b1b756e34da08a12bfc2b8e42152b57925cef2e9a0e570e575be92b9df4d2c33027c4e9300c3f10fc4cb7939886735c2fbdd58a31001f12bfa40b395c345a0e178d4673d174b872b868341a0d575351f46decc499aa2f9c2a3c2fec7a2fbd7be68781a9232fd05d5e49ac42a680219f913c94deddfdffff9d524a29a50fb8930419ffd34b7777772ce54bf9ff4effab1e67b2de75d6c479edeeeff56b777fa7a07cc09ffeffc6656c58ab52d15a2b5d514a4998f7b3dc8dbbbb4b4ce9af7000e4631266f84e030306954c316d3c95f3a8fceaee6e4b6baabf97af1cbeb057ba12bfbefca29452fa2ffbcaa189dc8ca59452eaf8e5b793c194524b29a59452ea81df077a4aaf97e9a8dbeeb7335fb9ee6a739a46536b28eabdc8eefbfa724f2f956259777ac78425ab9b6567284daba69452fa1ed8e0e1779f3add3dfd294f6d7116f5594ae9d3cad98e524aebed0429d64c1d8952eae269e5a03ecbd9f99da9ffd27f7de03bd269becc75758cc63cad0f3cc9dc194a29d8dd327786a60675bf199aff477dcdff7f8f4acd7c3b5d4decdda2391da331a594524a2b4d0d8a529a9ad286e2d777b516d72a3357ffdf4bf1fd972b255dbd5ab955df2b85faff5e2bebaee28a1072ba7b6581edb66ece4963ce3973e6f4e9d3e7df64efeeeeeec972ef6a55f2bc39a7e339fd954657ed7dce9f52cee67467bde69c73ce39a75fd895787e375783faffff9cf596ad7f73fa4c25c5e0e7513c29ab398eda315f393a865677ec4febe931f8d9dbe3563a660cc5e17dead0ee6581ec983eeddaf5ed9873bc9646e7bd3e98d8c3691f1242fbf4c6f3f0347926fbada6d723db9b2debbb9084d0860b870e04341696bda9b9412c83db08374f4b75c3803bab9532bae72439cb8194b21e012d63e21bd646a57d35dbf8367aeac84a95bca3bf338060a6cad10c3c7490bf07c9165b9e34501d2141fa37412808216a803283211b55e194ffffc3c712875454ca01c8145b8aa4859b2e2f9e40e7883f715552c5c95083235fc28f5a95e106a2039951f10521b38420e28b1c3822dbbe8d1995109bdd2391680890a932c00c63d783aa735a29ad1fc470054a7e61a32b5492ae8c30aa5c8162063e821a1331519d1626ae787de3b5c91b00305c4e04dcea9e1499c33cddd399c31cc7610018b935b738dc608bbb2cf0e3e66648cb6a750cae95396bf220777409c575b524667f28e7ab1a8ec3e15fcd9793c3129ce1309e3a26dce18a2f873d0f3768b2d85911086fa898a24409b2ad768616638badef38b6f8face636bb15d7140826bb1c53756646c599cfde1b0b5788696e369cd90c3ab2188ec0e5ead153f9ee1062270936db7188fb0a0851147a0c0d8e182b98f8ef03074bfdc4747720899dccf7d7464098e0bae29f7d1111a5ccce0080b2e5e7069c2e080e0aab0d4702dd20b1a2ee782704603c2881035301244d1f5dc47465210c28c14d9c8600829bd69fa899a4f2557396a6f3a4085bee3d66788ec58eeffff2d27f32f4ff3ffffffffddddbfcf1de3ffffffdf29a51eedee6e53edb8eeb6ffffefee9cbbc9762050e37777f76aa25e77ffd7dd0efa9ffeffdf72efee52e6ffbfbbbbfddd5be604de53072e78b2bbbbbbbbbbbbdb57afeeb2ad34cf39a7bbcf5ebd26276d87e7ffeac5a2cf9a33ecd56b26b7cb4e8b27bbddddfd3bb7dddddd4d83a3ddeeee4ebbdf7ac773ff5957a652777759a9bbbbf707fa40ebfca4ac5fdf7274e94afcd57274cc3fada6af6368fda1e72a1dc3d9aecafaf5e95793f7b4f6f7e0385a9fe3f073958e01673f06fe7e4f9ececffda9d71e88b912d3ff80f5ffb49a68db5775d8a1b5d60567bbdad3e451295d5c29c1126696ac3779ef792b5f1779deac6eb735752bef0329066fc870ac578ffc2c1a13ca4df8815352d938373ff89dba6350fa2c21acb5f7fe0dc2ca580b42ae500d342ee46082792f147102222345e6c8080c34b2966bf04f1d1f138d23188e589c2c13832c834f2090cc494b11f80211ca046c054e6ae311413c61e445952317828e8488744cf95b1c872d132d20c95c01321d8283231686402ff2a53c62416c2d31edac90447eccc18d122b1ce5aaca15ab00816a132b5635638523f9710a10e8392b5768e48a6d40a07a332b12829d5c71aad66a73ad15dffa43ae18d72752a6a88043814095e21a2247e4a7921fd38040bf82232235cc1459f98b45e6a60643029938a8d4ec5de88e1136400013170000180c0886c3218118071339cebb0f14000f4c783e5c5c2ea48bc3a1481003290a63200662180620c410630c610632a8ea08003bb571b1b5d28e6ab92538f456f188d0a93bc2db4e411b91dfb7b4856b84d6e480282c8c87b213b860ecba34d473f9b735bbfbba91cb070e27583551eb3da938d2af070a9dea78e31d45ae15bdbc4b472bb0e231cad3c4b1669cb35b5de25156c939cf179ee56638e10ccfecb6a85f71ca3d35163060397332c2b945e7be8b899d412132c255c1f44537de41fec2f8aa318cf66b4d8990abdc9bc6ec35b76dde7ac80fdc36a53e9d587abf2f40e01e3970815631636c60ab0a1156e1d4f90f3bb46b4f06b1e5cd616eefa50cb268764122a6fa3d35d117befc737f84ea8b99db2c2b047e2f1df8604004e03229edcb7c70a7f661750314339dbff564fcc0e9c9a593a0df6f6882278635bc7ad7f242e6bc7fc8b650b4fe4b03d8bbb5a6a20cca8cfb705ff2d320d58f8250d4e15356a630b331527f21d78f0b09784f078618b31f88b506c5d3532bf9d045cc023420f54490e05374bdc7cf38d30032a38b8a40b693497c3e92b0198836f00edbe47f10128bc16a040f0755a466aa3360164d6d76a313e9451b5ccb67e7a33897cb09c30f710a3d73ad16548444cb207e87903444ee736dbef78077cd2f9adc6e9d6d3ed530e1a06a5634c4d4df1e24d7ea482951f4d033d2e2eddd15e64dead564dc9fccfab1fb39bc2e9c18b095510f64e817100070a66a3277ae0f11241a121fcb9dae7e9388d17891a97f1a180b4c38bd1126224e5b5f1888cf60c1c7c3f6c8dc5c77bfd82b912b1b53194cbb4a049af1ed9f20d4ed3bb4663028c538739f5839151ced87d66d92105c67058f6ac9c9f2c29ff0bdaeac1800e4455c7948ca6556a6abf7ff36e1a195a095bbf4daa9614e0733785d569c6900a7456c9b1520e50b3485d0470d4c4ffc09715281b1227c1a850f98634100e43b1cb279f1ef91e2a0a7458833734d31443a01cf2af6409bd49ccda11d47271ae8d5ea0e2b1df7e0d5b9e358ec19838921dcbd474a9b967b9494b4fd6918deead420956a79bbc567fa0f0f1736d0a0c00ab61bdd61fb4dae7c1c1dd7fc32c05bbc67e5e4884425336fc30b1fd3be75c93ed4f1222ea65960f253ca2846d2c0f4646142dee13f2dd0a4587877bb31b64186d40d1983b70503c40c61f9fc363133d3bacfb6e5a6d6eb386ba0c6d09ed88f1daf1222abdd5a1fc352bcd6c50ff4dc9dd38c2c09b5f1fe02413f3673684382a1dd44ecf827e7fcc956669913b75a0ae217bc764f8efb0a243667170f49b01417109bedef089cab7c89c627a9f7a7cd11493dd61ad6b4764e46300cc5ac8eb08f3b448148dd44a212f5437073bf71efff21b519d91fcd1729f4fc7684d2824d965102782691e37a21667a4a99c44e0950d4c7229be3e1faf9d1031a03029070b454a7e32543bef3069c915bac9b164dfd320658d64a7573dd8875b1376feb28e972e1266fee6bc5c75bfb808279c340a64e7c16e01d88954b8a5e38e6411fe04adcb71e5b9b20d20001d2c3b3489378b067930d9d171d2166ce7b6059692dbc6b14fdcd90ab2ada6cbdb63c0177a94aa6ddeb62a3139d49bfbca036d955ca5d145d29616274e6074620e8250891af575cbf83f6842ecb19b97a1e8c4e684d010dde9685cccdc21916e103fb0570e37e309a892693f9af27a698227b21cf152c854629ca0b34cc9a11a5b82278cab805ef2a2fb4aef2a3de8f203ab31848683d4360d87393e574bdef9e349ee0a42f7c1e13f0e620d005ee4364af3269467a237b46bda1e525da1d3df2495a4cea457a4b00acf0597da411502a56c7e9d3cc79a3f1d3096b2ee52a95aee1640880977a17450e8546d5ea6f0808b0c8f95c8815b50ab034236e2203c299503373d5acb9164df38d7cf932b14842ef4986b174a9b2e4b87f125d2a8ffd96d5517aecdc05c5158177925257489e9d90bc2cd2d0a534717bd0b2e8f6d4444f5464642f883294c9468ce820cab0c1d66012be6bdc7e47b871e7ee9bd53748e9f69a75a1895a1ead7aaf550686e75ceb529576945f464141c587a4cb3204891a18e16122232c485208b71c93edd5ec52daa140c96dd4f0b30dc7845f615e34a5a515e7cdbca068b8b1767bd0a3711205ba86ee9d3466d51622022cf2c34f7df617bf6e2ff800f9fe85e708b8417cc4e8943b75ab91e2f0117867ceb8c38142b34a2ceb90167101b8de441a66b77fd6e96f6f826d3006654bd33989cc79ed8582d574a764fff390e2e84a2aaecc89409f8dd638dbc1c3b482b9ef0f0c0505acaa620c53473345c9b22df075a3130245c7c962263f3b1911645aacfab545a1874317034359eccc515a07d2021d340647c1d7cb00555ec111cf988773c93042087c75c7cc67b354c5d2b3325c5cc260cf05280f81744499cb1bbf2556b89e3866e94f2fdcd9a112cffc07d51feccca81b3640ba1224109303432c4625229f5e8d87c620118894ce78261509d979c5a7b1285229d5c3cef4e688978c40558fd2b6350d0c15359c7e58d328a8326c6eca1802fce969b51ac6fea73488359b3c192ff8a0ad9862a79f2cc9463e5957bc0008058878fb6f122f4dbf4b649f0e61cfa6520f9df547ed9cece0d4cdfc2ca233347ec2337e9d0bb503c31b3c221c7080c828dd2b5439409105a0ebf1bfce1bc31032d85921c6443319c7b75c39cbd2729c9d510ddd074fcf1c422cd6adf9c80a7c3f4b091a699d1e5bec8e3bfab5201c630048b68043c31f3b66df91beae21889687aed006a7d745e82c50943cdbdeac4f3ebbb56b81c14922723d8f1e163ab24604b350a78632168fc3e30a1ff41d0073f52684cb9f089405181267286c475c016b33f306eda46ac0481d4fb529ed3c348860a38e373818b30e32f97b8bb124a366264276c5c5dd345b9b1fbede1ee200dcac8e02e2fee8dc9537f0c9e715fd6f6554e15ff007cbcee52bc1cc1e088283179a5adc121dc5e240b593518c8defdc1a41a12ab8ca33f19833ad77a39d0d80c2326f993ad123838a40757650782cd4660d175a2ec5195c84a008d94d24a6c4280f6eaee278807d2de44401291366e1ffb067dce109852b637b10695bd1226258241652a93be4959341cb5131f29e57c7ff4410f8551044860963b1130428e467c38a12858bd45a9504565d01f6ba0c901a2b90acd357d4e0094e1bc5e08ee580141088ea9d41bfd321ee81d8b055e759ea417e1ef71aa9f3eaa2a9d730b028a003cb997062e0cce01c6842ca64c94693d1a10ef3d0ced97afc87991acf5cff9fc785f2a13632caa1976a054221de0d73272465f2ca3eaf7f2be160f23a6a61b8a24b5b6b2283ea1ea3bf40f20c75d8de09753543dc80730bb09e6df2515280c52e3a4d0861da1ccf871f04f18a15cd8b00ed03fa5da1a527bf617a5d1e071bcb33e5f66559a8dd31801770a34e0acd35cc1882befbfb885f41bac8ce8e1bb42b18c16b5d28ac308b12d90a1c20f01c405c8e8f5c38a8e3b09bb614d92bfa3cd07b5f55ee47073e6601423ae0d1cf349a2eb1a6c3bd4043e8b156d764571a3b1eba08519a686bc0ba4ec4af54ade220078b463b1cfa3f5051854869fce246e841b834ec0683c589fb5bec071f3642bb2188dae89042bb2cae7c6f76cd42d1054316ae824c36750f883dd77d3341c56f9837ffa0c86500de8a297ab5621bc4162ac042093f2349de0dbabe6e93cc8a00978113b173012111db42b857dd4c08cadcf8a9f89443f87e9a8e5f46a5a5ea26e805729e8b8fd2053a2b84de1f9794b99dbc9c076c166d61b321b68ac2ebae1343e839d7436e9b27873c26988b3b56eed102b978223ede406d382c081d5458164edaab6c6da1407a417eb380dee5721459c8e641d68a30319bd2ab71c5487b42b07d183e763c98001f6d913e2f1a2aa269bbf0a462c8c10ac7825ded2b39c74d3e2f344cb2922bc3f61506bff128bbbab4e6a70257b0a5b5cfe7ca82c838bcdd291a078233da2638400e2b1a1a1e592ca09c584dcda74a0fd30ed18dc5121838921208750de5ebb6442a13e42127b3881d997329bc0223bcdfa30a29430c59b430f5731578275dc7a8b4e50e2e77d0838cf293122a9a680197c2e03cb49b35d70cccfc043cb5876e4176adb22f4905d8bdc2cef495263851d456c36deeb236c50b70cf770b28380ec20c2f6b9f9e4108759127261bf535613e42e2a3767e140bbbfc703f919427277591623c6141a4f50583eefd812f158edd7841af0d01316d1bc3e8631a501883b7328caf40244e9f005bf83c6a7512a4f51ee94de344602d96f05140fd769a3c351ed80d9bd7030dce6b9c0e913988a4f0b2db1dcc8feed0dd28253b91e57d19330738ee82f9cc5890617a47ad0bd3d229c701749d200d2eb8063867707fb3466ae9a72e7bec7df4e2baf69bf5f6ea0de4072a8c3280e008afe7617cf388fded2bc9411892cf9532963c4d4f0a888f743a98f44ecb7fffba61941e9989f0002fc604a06a4c00562c8462ae47d433040f0d1c90891ce0491294c4c5c985bca61919ba3127adfa366db86b6ef2433397c862ede49572217db7d3b21596af8996ab62446dd79eee7239097707ae3610d7deb6435c9bff8e6d565eb652904d46badbc1f11e393ad78ecd64033bb1594921301dc629dc529fa815df6601c1eb1fcd4439dd9a409a59c8bfba4990940a014137357311a94a5e73ee3ff18abe813048a9e0d483926f9bd68e3d9e5490b35140b0bb5e31ceba77ff8396bca321f38b385b902f493c3a5b5bf7e9afcbce86fa1e7cc3e297270f6a68952e9fabd6377b0e946901ad59895ef6c34dc4aa56a899aedf3fdc0d14d616fb08a5ecc89d2b333764c38a07faa87460d064f557512717902b37029eba00d92a44844a117b8374d5153c65346b3e22ebdfd1c20bcdf030ae244c1770ddeea3a86995bebcc219107f897339b5269ec8e7cd46c53fc1b0521b36f3d7c94db3e2675aa9ff644b4d8ec76b75d84f9f4ac72cde4a7c4e034b2107a7081866484a7da76656423629593a7ca81c1e9481d0d41967e3cfe00b44451936e111ce5ac1c2c64bffe56c05688cbd1a1be7a025db666b0d717ceac7e7f6dd15347ba1ea26f8d3221e68764904a5d1ac3792eec48b8d652a0e3fa21a4eb727717638d83d3043b3e7f4f32560439dd21b809d36ec81af37ed0b55d1b89a757f790d1a397846d84d5b46696a3bd6a6d12bd68ff7851486b0db7e628759e7e297239afcd881d99ed3234e4a13de9a74441b28c7691985d35ef3d06ea8b388adcfb0ceec20831ada9eb4e3f7e7e6dd1af5c29b9897ae57dba4e4714a3a5d391bb49c7672602934d403e6b483f49099910ee98983ab6eb252ba4267ebe742cd6cbde53de7b04cce4cfbb227c4dfe72f2d82c7d65eae4a7e3ec8cbe9afd0f97d1a2f56a1130635012ae1222b38d216204882ca83aa80131625dff0abc2feef24b82846646a9ea9ab2af577da7a720dec7a8afc11a70798c9fcd702503e217f133a1557a6aeaad46f3f2ddab59050e300515f8e4c9bb06a4644579f066dff9407293003862807bcb2caa5d618349f7bd81299fdfde77c1043f7777e7fd5a0ced70cadf7cbce5588020a266fc1fd8f6c8ecd0f50d3d056eced0e95c5e6c00d8b4dd0448adacaf0e69b5c067607177c61748bb37c4ad6f02eb6560146352417555edb29d5d1616a799ee5c9fd5c092ef97f9bc92646a9ccae6bd7324de5812d5d5ab96a29b07aaf86a66ff0b7e4dee6fc8d26a1e92e2e317991507a1bd22201fd552177d1e7addfcac2d686330095d4be520e15e94cd0903b5b9b1bbfa03edd82dc1871c9d12e8ca1d6a4e6fd337b3a64db7fe896f2bd2a525899bbd62b6770816c21cf0389502280194cd9f91c39083da68306f8567e6f70adfe37e4097225d8a57b8edafce75be8cc88dfb0c4118ddc5df05c32d406edc8777dd0e459e4995d5d1a5020e38fcc2c3047f134023aed8328e7293dd1658bee72a3771b40f9036cf06c8c644b5e5334dea15f3e991bcf92ed441ed4081e5a1c1fb9396dc9da999c6caa5bf237ad0468633090dadcd9b6eb53a635d3d92e8f572c9cedf4076e34601040c3797fd1b619c115b48003698612dd274322a0ba5e6385c499cdcd04d7d96aa41bcc5d958f03677d18e9e0c858c7925b4147a189c9909f24b62f1855db48278587d726238e474971208fd2f23102564415f1621c31b5ae7020799b151f659769a7e71f01baaed61567404a65dd1c2ea7adc9550aeffff50292fc1161f0b104cb161ea6a704aad0f460b244c109e476304daa3c2b3898c466e574bb6b08a5308e64c2e194f3d050d85b81ab4219fb074758b2bfb847047d8dd29fa7ae882752bc52570e8990732144fc371222dc249cc9acbcd1613cdba85355700d5243166ae8c798de09341d44041bfc8e87e8087af7ba75aa7d51eafb78e09bd1740d58138b81f0242c2cb2db38fa7da17951fa94523bdada8a641129395ab908721a50721ef5482c106fae070630c3e84126f2e7026e2cac916608e2c9cde19bd584076d4e58ebaf2fa0e416a30058348322c48336798a719bb8ef477d4d5a8106d437baca5adcfb22ef9486b7ed95a21fd11f5de0d52d56ef777b0f86faff9bd024772878758fe0ecd4b4906ba855af06d2cf03f74d715a82999b355edca070660d5f4ffc81d5cab272f1f3f803edb52dc222d207bef40e0751e28314f23996faf2f444c7ae53d6193bb016f9082fe5f724f903710fef65f740aebd792c13a7a45a1fa03e6da6f334c5258f63d4cc66950ff56003518be09529c1f3f94197486ea0ff39e67c4cac99904fc4d0baab43c29e2546c9c013d50cae6f2c49f67a528cf309083eb0c44671c5fe34541f08090dfbbc6544a650d1c8db8d30eca03e0eae5f27e2cacd570311733551af20eb8701fb885d50e9838f154f1daae4482deaa00119cfd741fa0953135681e38c633b0c35c4b419830863958d5e895f8f14616d5c7565d4fb6404727bc2606092825728b4865a8bbc548f55954552e1aa0a4f03930a48f64f8bbf242355c8c5f7a288b81d4ca01020f1799afa9cbd4f76d8d52651e10956d0aa1720528924bcad9e97446a1dcc2ecccd12493477e6994fcd031647126bcba93b6d09f80ef115d1b8f5652778a10ac4639454dfabc77057ac09f3d69b3e2998a63d0b5baa4fd16cb1d8cfb8f9f8fc496ed431aa7a5748605d6f74eb7c71aa5eac94662cd0a61da51a3eb3240a173821c467965f81154a185713cc1d94427a9284467750a918345fd158c7e1b30910b84dc5b65d2f8053252b006cc75785abd70b4263d62a80233ab8922ae393216a9d8a5674c12ba44111b3ccddf2a7b02e22f6ca7e5ac943ad686f0a4cd76c5a826fe61fc45a676c129b8383021bc4396917a802602b4271f407ab4978046c92c1ff39692fc6bee0e29c46987074c60de4820260195e75091838e8dec05f427593b0dac948a1d76c265f0489d4f68ceea602d18ce71506212d51f864e60c1ce9f77eda39433b7bbf1a0b2f280cd9d524392b4ff0222fe8a62acdd6b36c2c2a64c07b946ad2567cc2ced2d0d9e1e3d6a56184c10712988631762b92f137d3fced2037807fe6d21ec72827813d073f828a6c3b5bc573dd7a60d265e64dfb7476ccc961827600a891e58027bb7b8f044dc299d4bb0435902ef03b31fe65845581c192be887c17cbaf7e9ca897c250127141ba4c5f76c0a3c422999b39b7b52a923d0870078d55058891277ca6b3504582dafc146418a77c22c0dd1cf99741164506a70169f1855135760b3494832766a53947b33bb40d7f656b8caa6138c4e628ed40169c036c4e3e2386ee31e0ae55852b5a769c78a6e2a9f7dbb6141765b01f0502955608a78a7098ae16e75b19590c20a47359c6a144545628fc390d1c8596b8eef54ebf11ec2ed84b0fcf34e784a65d72f7a592c16cc4b693e6e14f0868417130192a5cf2323aae6c51d7cfe710678f8c87cd61d4e2be5c98af5311809b45be29f67eb5e30d6c9c6a02c5b5b6f09395b5510454600f6b673a21b2a9c50f177cafc02a20c63e23d06e898f8fab8fe0a693c8d3eeb800d390efedeed045a871110b5cdd158c8f0a0b82adc973fbf98096d0ac01dd8183ac7572251fbdf2f2c71a8306ecccba9b430027e74ad1f5eb755152d5bfbfd4ca1aed3e11d7b10e117f51da6662015f828b08a8c16f1168356d79a7a3d2611fe5bc0f0876129fa873f06c5dd54c6776ce2060883d41e41ca63c08141eae13a884e2059ff6a73ad63e01c127a0da3a4587e5b54ba8ba97465b216b997b6dc1bba316196c69d9352b10bdd0f94684bb6e95670ccaf6d5002d915ec185c8a6def1dd1194dbb7cf828ab5f0ac445b23a6a9e87e407069a6a5a8b06eb13084c5ba02448556f6680497be2e7fcac45743a4c492b69bf053a5aea7e28ca77b1cbb7a63c40ff29d5230f1470f22c315df38e99a6556ae4e8b6e8c39c366a8a96fb8bb7a8a264ba12d7e3d0bd689b4d5c3e2267adf52fed6ac145b50640ea6ce06b3e63245a4fe4cdba64454b0e182a5e640d50f39cfbe1e064a5e0d24fb759b7741350c8eedf6a4f64bfa7007bcec918f2b203c8dd2c7f4285f9a37b3e49a604ae703eaff2c1c59de380583e815a07585faa489448890bca0092ca8ac6fd9f9b93a9ce647b864f8213447ae2d045cef5568e5fe7c0229e33a56bff2d42bb75d1fd3acd9cf60ea464fa8aeacbcac788d094219caa9bdaa04904b148401fe2f010c0b5698a438ce50c7861b0096fca4aa3f871702dfae180c8249f25abacb8655612d1449efb10dc98bc1665c2fe33ee7f92500c2ddc59418083a4343a996525c044ee8edc0be298bd13f28be8561fa401dd0ad150530afd1108238889e9f5fa64f25a71bdb08e8d108e82888683d8ff8baf82b4f1709109e927612d95719e402c2ba0b1b9fcd1fc5bd9fd9cdb18600a626bbce6dcd9653a84b08eec2191420524f6678dcba412d422656b898eeedea9d2f2748d57c88b655cc3c31ed054a4dace0a71d7b4fd51bae7752b81311d65d42b7ede5e3c477845f7a06bffbca4b41e4305ae1e4c428ab7341e03af9a0b6ce8ee438b39ce09edf48025528c338b8c807b737080003a0884764b972dbac5fd8a1d196b480f6d5f1d9356a206a25ef7dd5504b46973418090c0265c30072b3ee3344a6f18d3d19cab8df175db8b3890d88d12cf097ea8e2f1543c1a0c06946f556b3c23781df1af9ac3fe2dbf781e09c614a10697c1096444f7e0da43015471de4d2bad04eae4493ca8a56f6cb8a4cb78c10fa4b138451702ef10394d19d2319a05412bb045efbe89a76e72b7dd9a4c0de56367ed8b2fd1605c9e5a4b7b5291a8cc992d2e2810246c8f747b0de7f7992395cf66dc180ea9e23cc14f5a55945c28e822cf1b58e4c7240b78b22e4e0973cd5726ce60fe5d20eba9fb791a036c29aeb8dfb13e22ea42418b06fbd28566c9746891fa8b388e8791c48c000b868181b06d06376ab1824ab776fc08c60a26c537d49d1f65d2d4f6830af29ef5b1672d0ef57c33f3fe19588efa84b41b71ebcdc6c474af7db3907f989235fe93297d39a50c528769dca711ea614a4f773951a3d3f6e1040ee7df39d6e922ccde0e0a8ca9aa25e29e03e8601a4956f4eb518588b727f21bae53405946812e8c89a32e8abbf7452a6697e78132994a47d69f6901f05dca76e278121f5dc4ab2f9228fa2865b47e93564094e39546ba2dcfa9920b84e2c40ce523f8f5dcfd91b25729b4fdae63ea89c7b241e1c596999fcfc03575cbd39fb0695d55d8e5152a58a1597eb72981311e4b9dfa368b8ca33c78977b54d9d3fb7525162866409962bd7ea34e201131f8f6c21e9a518e98fa4668f1dbe156cca4771886439bb43d4567ddfb34cb9dc318f3731c24f82df80305d1be941461806a6670859c3fbd984d197d53f253b32045021f40091dda44c0175408d80b235f82b40d708bf5420b6e1612c43bbc614c394a8056e818b82a0fb0645e94fc71411c6067aba2cb208c3b420e1cad140f112112a978947b8202353855158fca35bb8a2c201fa1ad233f64e8891cf0cdcf6a73b12da81546f188eab38bc602850d241a538514f839d01ca624d5a7b05496136a5a4a80fbf105a3303ded484e83f2e380605eb4162826e800fba03e24ee8082a33e003748787b10d426d980a89f21af41144c7d85cdb6af8d15a60a5819d31658226128875c0aed86a401109c64a37565b310a4306ba89614ad0d895cc682a52dbc0663ee9845e66e46c5ee09542140406e70a9c80b40bc92905961153b742fd27cc0358f9d22044411da85bc8dc264052b8d13e3be0bb9fff5be8dac7006c4165f30f349692ce70d6723161e0a716c2bdd6c29cfeac2468d3c8e81bec420dbc1f82849155b98a6500cb1db90c2a13fb8237578424368653da4bcde9f95bbae556858a874b739ae2ad6631a0955ca9247614b59e083784a6d777e5f555f9b1a0490f623a95dc651ff8f81ad093c29d208a832dd5def508bc040bb41d2775fc1bb80365efcc7c14911094c1865c6b9ebb71b58c02552149d21db10dc93f09043d9186996c0af98f3ad3a06713c45bc3abab2590e318aee9e0db39a807566fab1a0e17d8a0062d935d08d59882462120599e6eb945e714a131af453fa70f40584ae71d703197676c62b09afc5bec59786a72db2657265298f89d7f3d5d029461480913ed70caf81d9f31cae1495513ba0b39451a8591dfa0ceadb989d6036971f24c2f75d36ace8b8f298413dc459b9a7c74ec1f372833b21cf0f88500d4d61d930d0de98472abf6859082f9beab109e7b9aa24f80f67e2d273ff3660ac2ac265a037409cf83bb723840be1dd83a47b5946a12a07a3128448876cd0d456554e4a2c6d211009aceb63e86c0137ce21cbccc84c23d59131265004aade2336d2fedc26a1c2a9cc5aa272fe0cf5f33d27a067eba590e0b719e646292547faab164bd0c24d5a85bbeeae1d4ad72155f40cd2c72248bb0a10bcc284379c751a96a5acb54b027bdadb032b2ca0a54aca352b6b0921913fa0e7a0618c5602ebd6ccae333feb8fdc343f2d42f3aba3f411d69d26c3c5004bf99b3bf535877cb58f101d43d93daa296a94bbc8820a61569671d65e61979eac9f2a86a2ddaeb501fab41dd543c742da0e4527f04000a1c48feb4d10bccc079c4b36344416ea0948dd4bd7f0fdbd682094910ff468616913c01b2b07850e81eb873128683e889d785589c3c0dafbe61b2661f1415db244c18cfac9e30e86774b2b19e0508f3d687b4ca86613442315c42f1244817a7eb07aebadb654b3433e886534dd3839a6da9a58dda0e68b37dce520c9ee66b8fcb8ce6361c8bdf81d950821ff46cc7b40f97ea5d537e578859378357420f722a3420676ad204a8d619948677d93085936cf314a8f5bf93fe690c4740af777268f6ec35f0427b45f3cfa6da2374ca57a03ccf6df6b1103388cddb623884acdc4de051515d958878757e1125044ef03c9ae121ed95b353b00efcad93c6dde70bf12a077e7e227f01e2b5ebc00d0e0bb59617434cd40503490e9ac80f32265cfd297efbe64712357d5027a021f05a6c62081a890012bdad8a8b123b1a38ff706c0caaa50290c5bab95294c4c5c1731831ce932ee11a59f9ef712a080343d08c527263286cbc0975a4965e23d37c4b519cc4a05873e0376443fbfdb0e5d645fab842e6dfd6950389cd7cafb99818b709ba10eab352f3946bdfbf381cfa0e644df698a535ebbb98124d42d12d2b86dc702eb21902186cc0ff88688cbe46f1be0263ca1762029b85414ded2ff7010dfad105920f77cab0d7f4cda2c2630fda6d469ec3b904dd1ac5de0e623cf6a8cb46c709016d0dac2e5bebf3c0e458bd6203578dd9ee112a57b4915149866d828525105ba01ceb9f21da4d9c828f28eb8e0f5f877eb362c0bda0c6d117727344c2367b10ee3951aac6dd98d2759328f0ca6a99e7ac7d20f361fca569d8982d4044a028dce1d28bcf18d548cbccd50900298914726dd6b47200331cb7cc8b519061206db8b39a0ff8b8e6a261f9134f23cdd44adfa5e30b2c7fff9da6e6c6bcf86996a6535df054f3b58029f557e624d0d08bb1a8ddc1f195a23f4ec51c69cd7ff81d4257ee3d52abd59dbf4e6233611d14d001dd26cd1b077ee3c5f24f278b34e8fe201b830da21be340fcfa2d17f39924e569830e31f6d2239f7a09e3b9ee2990148656787baab78b42418742346653ee38dae9da3e2d363052ff6d704c7f667852f25862069e583fadbb1690df6e4be619764b1ae7202e9b583683ddec99b2fa1395e3271676b68df627c67d35d2774a0cbd91f65a021fa06aa6aa0059e3d9407e79ec8fa2941acaa6cf0ff4af99226a0dec8ebf9f3cdd36b099eebc434b9b235a519367d2d220fc2b96e065d4a348fea9e6cd2523c3a56c2721aaf8f51bc270345bae13c2da86b3838b8e2c7d708473d1f2729958c95aa65143a597c9d2e4b74eb82a6ed184147de7dc465c2e2a8a84ff5a0003d1e1fdf17d1458932a442ad009a2719a2bb145b3baf82c39858b5ec5ce8674599fff3828a48a3ceab0df7e9d86ff04d6fa7cb6e6bc031c2aca0b7310d090d812372faad95c09b55f28162f13835933c3b46a998600e87588ede951b513b77a1f43af5ffb1eef6801f4b93aa00e362310a02ba26d2dced2754a19156d7d7c246cbc939a6aca78ace58983c035390836758af77463f8f9fc349cfcc27964549dac7778a658361d84137ffa7d46c4518caa2dfbe738f7629abf505936121feb49babd1ac44ce3275a63fcba3c4c6b0d3fa99a5baa69dea226ee5b6d4fc5a92af6a5e52e97b135e54dc89b7cbb690a93b5dfe92df775bcceec12e88bd322e63e7200d213a0a9f3c4e92d950d63051b65ed3f42132fba7ccb62d39d5800f28158c2eed121b3a17fa7d1326584537390aeaae48f5d97cf95d3f584fa274acfbbae2a5ecfaf0ba6748afcfbedefc419b62b55e2efb49dbaa3770d25ff99d2aa31c67ddc7d5ecd8f38f42369502c3874e2d7beddc255163b98374571302598979ad8b2ff1524934dcd8f226717a37680e0765d434671d730f546be0b3f91a17a78e6f5c7c707297950a768ab7014d39b0986a31bb9feb2d403567f2fe252cfb926c1d6a20eb6f6e74ff8c11c7a06dc871c0fbae4c48bf59aab81557ea515661f236a4e8dbb07b38f824ffd068b786666002b7fb9b477d457a063a059f9a0e0110363897dac74d98adc34b3fd1d8621b28f992117bf00979eb09749c50e2774db1efe390935726f1a32da030e7f717da7b12b3c375490a6741e9edb81045d994a0a677bb87afaa66c4446bf55d68a1a11fa55249e671dcf97cd664ae851cef4552332eb1c76c1af25d796f495ea10cd49ca740a95dcc4e6e3b49e899053d542bc8ffb8441d335228fa17ed3b5251a8fc3590ce1fbdbe02135e831b30becb6438614d5fda70e74e730777331f43efc9a63348eb8fd7662b0d97386d002d8e06cac2354ce136bb82b5619ccc9f85df17f630c783912085bd1e259c69657249d72835db4005ce694427d4f0d5be59491c8ec4cf0112acdbb5b8b60da1ebafdb377af7097684703747f45f63db5f62e0c5403b497349d457a7dfc1f1d72dca54b40d38fc69f86c24f35f13891fee5b466f6821b4a0472dee3333a8c5d5ca5d1f0c18ade7cdb6932d9c75eb56ba342911760349dbcaf84a98dff196a9ddafc2b8ad6d784da52b1461198fcb2e318f71580bd217b628e8a119fba34b2219504d3b0ae0d39a41da448a85f2474f35462498f85321aee754e204d2e05bbc1e0d4fb167c85dd27583ac18f7ef81268bd220bf2b682b4e41fab524c55a78048aafc06934f9c83d641afa8c4e42ce80445762e5b3db843cc681229fe5b7330b50797105598237cac3a4235c397d81e0b6d22f4cb7c9c9cb341c1e7b494b9cd48a263b27f1cf1945ef0b44f4fc8c95ebe4fa0e6dbcf663b1693d8cb636dc7d2c4ad5ebef3c35b861db3d257c945e2ae536ac32de15ecd5369b8f1a254ebc5f18df7026cd7913706734a377ccf79617bf81b85f0e36776db11cb956dba76101a033d42e33945ad70fb7f1c6be0b9af58444c7f1943c54b3d485ea095feec24b208885e10dc260a5050c889fb21b68280aa67910063523b8ef200ffb1397296039e22b5c9b46cf1f48a88ee9c9200de0e1b98b8733922c72f7dc615f62d6456927a7517836f64c98143990bba0905980acd21df42b0658eef1e6b7c8602fb0ae65f72397c0b0b47f8474aa43fb564808c64df422d25e81d93bea0e04b646d644ff8161af25227172ae54ae674649a25f30a5ff42a92536c7c678a1df5e1ecc35700fda37599f7ac4afe7c2b81062b6a375f4e224d3764e1cb93812caf44508c5b4a2eb3eff02245f3803acc018e2ea94da9d17afe89b067f96b5195943c3392b8816f3b08e9e1c0b7ae7669409062000491bd344303f805133b314417c91969a181a339cda45a721404d16ded7dec0ae7e9bd9cd9ea9810c70a2ca6267fd602c27e4119779102d1e3cf98c9188111f8f60b4a85c14be980a14f31873567713f6821f3c5d52c5c9779472fbc0cf6374a3a5bc44d91f8ca6c85e8d6d7afb5f9bf7d65302460a01790d50f644db96bb809cac62b42952e0d7917983166df5396d739d6e871045e369403c518413586d8b27c3d7f350b24a83100c70dc3859455671e3679a5c9c092e15cba6012882170131a7022fcc1b739cd1051c018e1975274a46664e82583d57798dcd4183ee2fa6080dcc7de6f987979db612c6ff4cf1d89b76100b7abafa91a42d19151886ce07ffc557a49f9dc49229a889725da0bd10d60dd5246977b581e74772a33774b06ab9dc8e941efe114ed7581c616b66f0ba3a4dc05ddf48aa372d0cbb53c1c44596bcb14637f5e1121a48b14b569e6b86bca70bd01fb4a3a7277eeec368450d309fbe98ca9ff87b2c237d25d5a4149de38a5be1bb17e7fe666a068ff156d7b5b0cad2fef355fc14fce04e2496317f82db4727209120c0f3a2fd8be93e0775cc3c6a6761b8b75e37f99d4be23496e5708b92267735723443da89f0f96ce450360b9d31d1dfddf48055efd76941f07f9413e86d71c5d1df2e1324cd89dfe84028ff0c4be4a36d4b16eeb364f7efc2af51be8d1d7eb080bbe41c20a01be827d098f00f0bd1c52add0b82dee12b137753a71baaf8c30fd14ab81f33402690723e747e894b0f4a3c1d3b3050ef9f669c3d1da11215de238dcdc12381d8c65b2eddca014bea6d73dedbf0c232ec5e8ef5e4e786cb44a6d4631a99322d9ffe0c3495c196a35df44d56506448f1f1739d5dca53b8fccb3819e7f17f7673368c8b8048ecd4e3a6daa1072d75ed32d8bebc5c49199163c3394d926e24feb7106fdcfcea91599fefdef18d524e90337a92d9ad1586545c1dae1c3df6e040dce7f55a9f488e0011a36fe9460f8528fbb3e177127de898afd0beb6927c13fbb4dfaa78617b8ea234574b7b905bdb0f974feacf00be10cc7e0cc0f803c0e1e94031d30b37793cf72027675505ce053c3d537ce5b74c9b55aa9a2fade9faefd027c2c60d293d13ef0d7728680a70fc8f419041c9470b558396ecd2ceb572221bb3a7598339d2abcf1001bb59edba032a8446954d5f42db8fb5485f68c626c60feeb555da8df251182fd708ea05b834582f7de84012ceb40dd15bddf0ad3f86fe4de17585fdc5d952832b995316fc396fbaea639da06c606029311fd9251797a4ae870d137d329d2ee8f33bc59fa6c755f87be7e3382409c479c7889bbbb6760040889297f1a065469ebd96313e988aea3372293bd89b626fdd375a25b1fceecae324cdcd6a708b53ec4574252eda425044b255c78d5e21a3a1c29daeb68605c8be2fa4dee0a026d1f80ee24e01bda2eded0d8e1c07c0a3ee8e80784dc22b846d7be67f81b618a4ce7e186beb9f5fa6c8daefe87c2fdf58691319f0850ba2df97e62411f732b3e714a85c46dda776c869ac65caf1a74d493f55c7b1d8ee15f0be33e784fe7174b34ed3beeafd92026445b84eb7979050c0b2d16733cab7a2e6202459d61f9053746283a111294e593fa454c66749a7254c150bf507565f0ebea91417d6d20187ec42f5fb7ccd77290f46294acbfc67564d0242569c3090c90deb09200ec87d86ae83b83830960f96b5248e930bf753a9d5c19f59a135b686b42df5bcbe7ee9a9ef299b086c72ac82829e8ebbf5620d777324a5f94cb1cd57afcdb4fa9e3c3c4a1432aa1a3d826d92a3671cec4262104b8d070bb7e7a515061383133f906604a072141fc965bf98de1ff341069658013977dff786461b3b13621f27d4b181adde0072d163b1572d8ea08d1529612230a3b9d39a8c5d83ffafc6e30576b7796377da74a31a985dae10b0c7b4dc9954b3f7ec3313325c60ac4920dc5d19d93e5798189f7575938d7a4f2635e635e7220fbedf9f0356e053f4bb4866636ddab29d13b2eb767d71fa6115cc85b492ef5d7452ec4399c670f00f9b453dfac690d1ec00080b69305949de57acc2a1326e66a0078191fa68ea255b76a1357456f5a125187b6831afda8209ca31716a06d782e4ad4f3988e0dd2202ac6e31c3916edb999be0a17fd97a38e4e61c7348bdae9dc7102de2b788bbcaa36ba7221d3bf5a2d1afef867a39a1f64d59218628dcf9f8048c942aea766a318d86c902d31226baf33cfeef09248a0a4998054edb0e98c3a98b87d8f3abeac04db5751e6d1cb6ec93d4d36d65553700c782fbe25bf44f9dbfea94ac227ce586521aba6ade6c4a9c9be4b4f467ecd151f997c7c6255b09abb95098f0fc4f56233168f9a833ecfe0c7faa7aabfbd7573ca46f6c23d2d11dc0c51c61b56557cfbc10501d05e19c860f34f810ed214e774dc3c2be45e1a44f2c7e9538a622f821861c4c92fdf5281a7d0377a7e139d3cb9584b921176745999a5aa9a9e465fddec800fbc3e54c417d6402f525e9e157cfcdb6e864f83c8e0e96dbe54efb2ee0434485c96aa3d5fa1420cf0ad78ecd2943af11bf5f72269e5a64c879bd9c4f005dd5465c47dc4d13c66428042f9dfcfff61865a926a5032501cc4853f90b9a23ac75a23a943e7d85c51330a2585b43077489602a60a3fa82bd0edf6f46811adb5ae829102279f4b526ea99bf0796b48bc5902521da50b95aa89dd91b9288d9e00dfd2b804f6cdc9b08394273464b033bb8b2013b6cb7e47f8807f129bde855d2b7242c147e66dba936f735c1123a79efc6c44c03a0470310fc0497e2e17f857cc340802095bb10fb857ff1057868c6239c054f1d058889b50cee6fdcca11aae53a6f454c2d15ab7a16039702a2e43539b60ca68e562c13d4ecb565d95fae5c29e3e669565dfaea37b7c5b4b84de44580c17af18278e7a6d9b564c406857746e4d42560c0a1102d2eb069cdc01d2ef5330a2bed9bff252b9e887d284d57c89b52b6633969511723e48d627ecdf26279c93d6cf263bbe7370aad25d10db31d38beecc824152bfe11d6a713da9df7eab0f1b4c452e8d0e8a320efaa6d49947fd82dc34b389601d0f8aca9db686f6c8ac376f4445b705330038ab7efe3b3a9714812b6b242b2d6a22eb23bfe6e3a613f33d4ce3f3be2ee00e02662aad313031e3f790969fa4276d7329de32355f77a9ca9b50061391b232cf0782c946aa684b99c7170e367e1a72a3284ac33751d8c95f28a4081fee4c86a0d97a094c73947a6e435ccc81e3ec3d0e3498ea5ae586069f8193e63aeed1282f523b0bd5f0c93c7fe16aeb05f70100d4789a3fb2b036ed7f7f602abb478e5d63bd3381b612b61ef7fc1bfb3589f88bdf40dcbcc6bc95aafba8ed05296e40080ba6b79d4664630a21b62934cebac5fc73a197b0eab7b3976cc8d5674d457b5109bd70c71089cdbed50f0490628bd5c47bb45effb36341cb17bb19b7ba3986e3966b0207411479311b02189672e33c4148e5bb06593f37659d196eaf9b2142ca8dd5bba539dc77131e50dc29426f873db21e1f0e15c8e35fa2eefea8f6dc979c0e09cd9d7272c8ae678b90411c97e9d6b004e12a37adf668e349f52189c83554c356ba386542ab7ab81651ca45e6afb8c3453819b252625e595c5e317f2aeca3133081de114e370c3322baa1fed60877745f37d21382fa7a0f95641d60570eaf0f9930cd14ae03840c1caa08d3a07b4cf0a9a0d34770daa393c75bd769a76580d35d2e68fddc7404d4b65c3dda54a7b7e3854c1fa3fb9aa0b7eb14f9979e8aa4d0f7c1a16b1d6cff434f2c52382ba795289d40f41c3c2f31b977e86fae8310e71fb58672871e92bad782850aa25dbed375eebc901dd0bd9e9273c087d2de24a51434bdcc92629f2ae19aa69d0bce0fbdae98c03ee5e261950b4afd1c7215b119783a68bad0fd1dee23391f8dcb35394899a352832d2754275e99746677801ee5c2cf3329fe89f3123193ce52efd31056cb003d2743615bf3e7515b3013cef3f388569f47bbd5a0b6862bd3530ec34707f4fa14360337feae21899e100355deb735777fad88089ba3b921627a53d82ab89795e033ed0ef0e879598a3163d4229eb86145e8dba7db6b9e4e05069928cebc7cc49d2b02f4b2793d0a6da10c5945366e2aede6a1639678e3ed7f2a4ec1da5b8c26a78124960b70b461f82419ab75ec0703ac0286f14549c844e937920c94347d20e4582e45057d2b3e5baa3a51c9a8fc05407ebc2c5d420ff8823ee160f294323bd0005052d25ac938e05f4ec0eed7d3b4c9e087c834c02433fbb084f2531c414bac8920b45ea75f52a61698ce6e747bd2e018a7d3c1cd1b42f96f3dece717295958d84b46d8d75a4952ea4a176ee9a87696e369ecc53d7465e4d3c759aed2957f5303b0db25675c0d4b889ccfc4f9150b5d96eb16b49f61794aa2a97dfb397fd3b6662766a209ac593ed08b00d88eb0131bc97179612d911ef00fa49e646b6152a13ef69f3fdb0d424df27496b44cbc3dedc3ccc7e25c96d8aa710bdf2bc45e85182e202c5b0d23dd65820dd6a65506376152d389a5e282c8b898527b317256bdaa3be98da98527c510afd369920d9b616058b2377b9e361af7dd85bca3d35331dc9d373fce60bff7e704678fa1e3737c79383e8c19f8760554dbf43148110a6068817e9c8ac81272e3fd2acf50bc1e0320018bdecf258d48852e9ecad58cc3faee4befc393bbfa76b18d66c07621dadcc78002ef3a9180594bc2267664547444fbbceaf8a019d7c22080086c4748b5a020d90209b3acc2686b1fe14c1b02518bf891fec37823f95658a15cd83ccc8a023c71c5e8fb9021d9a3cce4aa48622bf9793f56e2b3f7f9a6dd006e803ff2d534bddefcc3b24b60f90a508330be8390941d8cac27a1ff39bdddb2dca07253eb8ff74d3db78837cc02e3d3faa41e01012153f2b15df2423cb48332bd4c7625ed2436a61d91f1a0389906dd6a74a40193f4d051fa7a61472c5e709eef74f16f307e42fae827197d22412ad6fb9c9a40d09a9c7d49495b5ce2836ae9f82ff8dd452bd35b004e658a2818f1c32e2bbc345f80ebc54afd32aeb33708444fd8fc6c9b43f312991b83a8c4c7d1189bbc83fa131559bbcb69c8415099c3b2a14737596183e465433ee60189fc42c772a1175990792aaf00c7cad7ce604002ecadc5f73c650910570eaa49a7d602c6ab799a87a256888310fa8ae2ece0fbe026a68c2ac6fb40bdeec8a921ed3dcd3d9af03528a866778a11da18b06f8f85a2596f2170f572a6f939559cadd9e7ed33c225c55e4ba9aef80072a550f0d85931ac021f68703657f73e8925f81cd779fdf4184bb4cccdf667bf03cf4eeea24c54f08f4d158faa2574ddbb9edee98cea195ab7460685eec506f6df97d14d5e5989c054ef428c2b462661433cc2c7be41a546b09c3636792310c300da66e4776a4848b3a3ea6ffa0ac9896aa4d0f77bcfc15eff57bbe5ea37a0981d1d07131ef6c333740e199a41d0e12ff0c49f0f7117b31c49ac91ee9d33b161bba92904026b665d63083c07905ee95d9ffb49b1df8e4f7ee3b92280ed13a3084bd324e3dc161787a73ad8b36eea42d90f332cd30c8d10c34e65790d59095946f013a3f630b81ada24c075bc00b0837c9c54bb275ccf57a2ec947789d0b1c6595bb7f79399f1e9dc68d804a486c19be164a450b431381d82331abccbcd0cafa1934206e502f76478904c492be848b6559c3e8b2fe65f947c2edd8f245bd6115afd0974873134f6730b0cc74f359f06047671ffc500f65432ead4bea9f1fa6ebd86604267d1e5b5766e3d77d3c526e3cc2ec9c21425b53390a0398d49fb9a3775f4b65039932a8a6d3673ac46ebe94706b4c004eadf4bc158309a55493ab94997836fa9ad91e698ae1372c7228d282db805765cd2d761d81debd680de4b9ec23d23381fc5fe2f6017c49a453a361590a574c1ef05349642d9d65a687eb19753a73634726d6c6b9f10b21bd984ecbdb70c2d0cfd0bff0b3f6032f4832df2bc5c22ca9f792d6543552d8bb86a6d1131a33c3f7f1dc5196c49f4381bac5505fb655f2f419221d6ca13758d257b20ab394964a711b8081c64bf7894e7446113c6a978ec29d3b19ba2299ae26d748d797a244489f2a42d5a94679c731e80c7bd899e2623571368557cd441d7a0af8936284ed163294cbb32201e8e38a45cafdd20e92c8955927b49ee222d6f56eafdcc7643b416d073f373d3c40dd0cd913893dafe9ba01b2b1a47e92d858f5047a12877ad75b38158c2c0b65c2b55f5a9db49557d2604fb5a0f6bf58c72dfe3c9df25942b568fd1a071c05c8fd5a0716cafd86b3d35d2ab520684a650d028aa0b88c419fb7ada13674eafa74bc41994d7d37aca449c49793dfd8933a8d7d3261a87ca1571a519712d1b34f683c69ee819eef518111a23922b17afc75a72f5f22c0b8c091de2d124b25167d80e723da6b31d4da07924ceacbcebb118cf27933ca317752d4b94eba7075a264d9dc6c1f2fa0b49ae3256fd9514672ea1ebe7899fd3a07b814dc1374790eb6554d4ca95e8df0594ebb54efdef01f38b85dcc3fcf73017f2a7606eaafb7d0e2b75531d4e0d913acc6f52877977188c63a4c33730bf18e71ee6387fcac2dcd4bdb7e56f4bd5a5c373f9c815cceb67d2ced52357a9771db6a96ba54a3eae5aee928a4172d5826512963128855baaea4d295526a91c72404a8734e381e9ba2c48aaea3defaf35283b3aca3cefef3a6b934a317e7e7e689665493229294b4a4a2a259592ea4fa9a816957e4a3fa59f52515151517dc62357f54866414212a6eb8e783cefef2c4f2614399b954f9d23d95913922bad2555f55b140d9d904d91ab1572ddae1081c6a3b53423882bd792ab3aa426916bab12b1db85b91615e5fa7ad4bd5e13925bc0745dd7e10f3a212857a05c3b5bbf6942b9a6d8934c922b6d8bc6d1597ac1d28de56a465da36e3d5255b52cb4aa5ec80b3a50f793ebe6936b85c2e681a7a1d322570d0bcec684371e93a954043724106f411b904c6a1cdbd1651a7a26d1383a5756b5495a4faeda0c727df58255c97832dbc18c27d7dbbb3511bdfad3dd80a2578f72b723770b8a5e7dcadd9044af1e7537a1e8d5abdc2d49d7a8cf82e44a631255f543b2242c916e893311942b52bd8bab194955fdcbd58e90e4aafe48557d96594b91727de947ae341ea9aaef4ea59f5c9f25c915e72355f54036a8133a51fdcae596e091ab0d8b56d5d77a14fc65f5167fd603b98e20d713fe626496bb1d758daa823fee27d7734cc499ce75abe79a88ab529254d5734b74492c92a423b9bee3366ec95614bd7a0d73ade8d59f56ae2f25d52ecbb623d376546f326d474f6c59fc9492eaa611895ed57aa257bf618d277af52f4ab09fc693ebb72ce20ca713673eee4ce2ccc9ebb7a3380373fd8614678eb6d8a2d892e2ccb77121d7d7ed9f6694eb2bd251d5c9b51ee57a4d6e2cb7ef8d24b9aaaf3ff0465ed15b1d912d25e55aa170dfd29d6ccb1fd3552e54d235e051f0291726814217228141f0db91cc326bbbeef73c996d3bcaf5a5a25a2acaf5a62b03af1387dcb8c18a7d7048a6b883121fe80452de8e51146c7300948323792b8294b7225b446f834582608ebe59165b5f6c31814724c9f0ddf83b62a7a79094b767f77bfe6011a0e86dd656275a52b5bdef44125d230bf6834ef01849e2d99ce0c9db9dd0525a0e84f2f6147cdcc95f4e90a64dc1246f5f53a19337888b00e56d4950be4e04925c4123494418c915f6ed441cc915fd56d2a610ca17fe9a8a9dbc61970823a9da0e8d24c915114852b57d7b864eece40d7f3d4552de9cd0c91bc5441c49d57623db59833b20cadbbf9ec2c8897fd9edbf0e3601245792b51d36112457a9d3210b07b250be1d1a218a33a56f4694e4ed42793bcab552b5fd74b72b5bfcd9bc39d1ca1beea2b71d1a6999f0971519e5ed2da191a3bcbd6336ec033a81a49de8641a59a963293c91a604c237c2f0235ee396586e0a0bf3b557ed55833c1fe409ca54889ef6348e93d3d31750251a473d7d71faac7519c995fca4cf6ed02b2d336233204091b32430394b22ce9c4e9ff1c41994d36744e24ccae9b39e6c8938833a55397dc644e358393db5226657d82cba2df2b6c3247a43323d6dc995d6d276d094073d633a3dfd41af5a4e4f85e4cac5e929915cbd9c9ec2a0bba90d323d9624579c516986e5dd3791ec0ed5f18aded1cf34f4449a498da33b3d768421c5192c08f3912bec545382cf5fd0a5c08ea0b3cb039916b58830a1be0ef31ed8535848ead853c72e04e6d79b958a4f3d8775dd54bcb98e3d1ec39ec238a963c781f9f51cd6062455dbafa0bc3d75b7c36cd7eabace0ec643afa41b34be974c5199a6be09c10ef3ed02362c8f5a7336da6ba2f6c286e5baf066458457645b1692730086533cb06387390cbeb9708ac7f5eba9a7369b882fb072202758019295fa0f9d5b7295d5d20e4cd7791e1090d692ab52c9f3feaeab317c7c7c7cb22c3b3a7a719b554292484825a4125245aa48b59515c995d6aa3ed5a7b68a327d6947ae342359020a92463b3b3b9ef7779db519cb679291dc82e46a23922aca41d12afa188513b82932e5ac68b2ed6c44db5644e4026f45529564bbe58ce4aa32d18670465a129c1167945ddcd45fae4b0b22b532bd6674fa2d081555f4305d672dfea0138c720065fa43a3db82327da32e391bed84324464a5b62c1a47e76c2bda582ed7ddada86b703c5245b72a5a451fe401fda7f3c994ebc95453421aae458745a65b1632e5764a2520d8526dcd23997240dc4fa6af8913a971d04f2d7a55ea6b080624579155da7822fe361764faea851717b24a3b59666d0793e33f98fc957632fde9724c448f1ee5723fd1a34fb95c1397038a1e3dea7247a247af72b9a0e8d1af5c0e49d7a02f01c9d5b64554d16f4cb22136898ec8b9254a4672b51d19657a17772b922afa97bb191dc9959665d61e65faea2357db4ed6551f94ea93e94b48f42d5bd419dd5b2921657a969b237af4db8e5c955a454fe92dfe32fad3b6f375cf317277b3ae4157e80b10673ad31f20ae2a9254d1d3d3e4e8aea0d47b6735ee8aca1d61ce287af4970afb71464831227596957a95654f6c3ed4e5f45b12d1a33cd1a3af78db891efd8b12ecb693e9b738637a8933dc29bd8d33df29fe3828ba38f37156c8f4b4fedb8a32bd762457f49a915cb55c6a49996a4699debe3790e48a266a3e72055b156fe415bdd511d98a94a912ee5bba93f5c974e542251712a1842012940b83e0912cb3b6eba4cc3e9a5c5b729565d6766d65fad295b9f9891e7c0a17e1728d883a436edc913756a44adfb8ff88adaf93bec8f3f2bd04630b26f9a0501e42b22e5c845461f9cb4af7061f58a49964753a9e7afaab8958ffeef3ae234a72fd1556d46f22d57fad848672d77e51aa1d20a7eeb4aa9ea7a7d24a3fe34fd7a89f9c022a4ed56f18d65bba76bfeb80c9254e074cae18967047a1954f7450628e1f151376cb3d04b404871f9be51e025242f412af99bd64d90b98652fb21798bd642fb2972c7b41b199bd64d98b245a96d1f6608430d2a04993096343d4b3ac258c11c6a15894936453f41941932200810a2ec880154190239011723c2ace7017908e8ece0e58f15d5c75cb1700e42f86c68daf93c896596cd14cb27c7f7088849192892920921bf9834994c025566401990c49a20a199790b1892311494cf2452553c85f5ce26345969734e0c97288065ac8f2afc110b2bc375483a3dc4336384296af714196078207135ac8f20c18628249962f020a59de88a11b504107073ad8c10119788a14a1c7a788157e868a8021cba11f21211ea2560e9214191d21dd20cbc3241da441e7063b3b60034f1347e85122cb1e3282f4e30411509010114f963dc483a32c1f731667be4d28cbc34d28b7d014497913da8c907dc82dce40a20cb7242cb825c9cdd9c4d2155d37e4a36404435f68f101419e9fddec253ea8424a9e73ce39b119f97a2791628a198c60ce20075d0a2613544d6610021990010c43621c010c2be8cce0838ef95aa8eb1c1d637a65a52ac295f686456e4455bc8c19ac4cd7d10091e55b75bd74bb7c1591e5bdabaf0d64e95d38124b298cb6157d4cb4020c482527d80069044d8fb410c1bdc71ccfa41fcc16d0132da0f1424b0648f9eba22cdf56c877f2323159ee2119b4e8921944accd3da4441bb2b027b987944022a2b50f52061209d1d09841a38b4ee491349a9877727970b62f8aca646ed0d050e5c8724ead7236d8c466092bcd8e863703001e958d9a7925bb2eecba2edaa815b9494faa68eebc24362f99f3a4f39a5794534e39a5fc94726612ce89ea1848e3ec0bc503adbbe17c9c53e2ec0abfb0e7f40ff62cf19ac8c3cd715baa240f7c81b9b806b9d3ed9cd23f8c2c78c9d95ca39b86e250a877af22ea5a09439e9724f6933c197ef630e529a89f6e6f0afe687c3437b9beca7a6f78cbfd6ec03f68e496dfdbfbdde4fb12f7433ce18f473edddecf3ee5f56a29d7e68616f615e67a697fc8a0ee77bacb4b9c8dc51d3d18bcfde28e5e0bee6ba50d5fd421d75d43dd0f66ad391d50e59eeef77cd2ecfda1b37dca9b4b4067abf2cbdaffd0d9e21b5a5815fcd5bb1c723aea5b6eba2aaf9129d74f56b939b906c1320a63d086cb6b644dcca2075d60df6398eb05e622c22c05c71f8b4ff01dccaced3aea792cff6458fed1d44896bbf2973b391b95a7ee4bf4506f7977bf9cdcb1e49614fce5e494dbfb5d057f30ab1cd67acf9391913c9247aae0abca57aea9ace0c0aafcebb2ca6ba2e491f0baaed35df0e9d8e926fc7557777b597e6a39ca9b1302f5165c6fe7eea2ce72bf6794157bb8726d4565e5d637a75281e8ac828360d93e7241c03469d26447ccf51980b9e21fa8af5c9827b74326a75ce5167f5008eafc24f920f51c411e2323a12322a496fdd745349efd2793abfd47a3723fd34f6f6e07e4c9292af88b4056b9bd9f4cb6aff77b9675d7fa1aa972835c59e5cdd9a86020327f30a3700564720afe78d87b7abd28879c0dca4fb723109d2d0e7205d181264d9ab4b209ff90c9278f3b72b5b5aa1ec6e4faac56fc6df8078ddec997e7933c196f71d5aca933800891e8d1ba71487b6534cb7ace47c80361e7c060c4e090d135e2bf1b06e0411bf1dff38c4cf4228acbcd43015d63e60d442f7f32594787862a80479228a3a5101262e180d8409c8155682f62a199340a3212ba060c401b2cbc7681c007a00d5647d7f824ccf276864d58e8c3920f9093e1a14fcb962c0c90abb74adecba48c5d7d5d04d334e4e7f5a169c09ad2101861bc380c6491bbade8259d64cbba3b2667de0b68b210ca0c4050a28b88a8c602b2bc5ac5e50b0359decba65c49d69793eb9039148221b9331c02c18ea9e5fdc6e98ed3f22a6a8e38c8954ddaf12100ca2fb987424064fefa25244bce46bab80c41cadf3cda8288792ec10940c81f3542434b6e2e2e93682a89392ac87e14c800f2a674c43cbf247a51069616d1d60c269e3f716662d1aaf8a905518e474747aaa0e17137d6c5c54546a665c4564427c3eeeaa0582d5707c5822b6fce86e535918888517ec94d44e40845355607ca53de33af93cde97dc234283b02731345afe584cab18b7abd29775ead6be5a812cab5e6b0cb9b8118c4f591a2c750ee970198a27807cae5738d440902e445c6404ea6c03cccb31586951c3434f9c80704a6264d9a346175d7b0ad7427d09d4dcc9fe8c5cec7757aac00353f727af2751ff39d2f98fb28a78460e5698b0201d1a0d40e7a79fa2eebb2dc51a0959be5a02a0e3081d8586ee7959b659cda51b37ccd12a78290f312a778dcb4a8d103666b12b576928841d18b4838ed2d1807e3f0d8812c97df3c858720e103cb05a7e40e64a5a4cb7ba4eee272212d4fe11e2e7ff90b16d2dd05a724e6f17297964b2002f448f42e6db95c5a44e5b4fd2146b9a7907b68c808326d512671c6f4f82c1b621f4fab50c9f1b4886a91291639d22ce47829ff6c892691bce48f59447d81fd2890c4418c381267604bc21389bff664689e9d1e3b6dc9a352504e6f59a76d207996154c5530968251f00957fc7979074dde2890c9740a34b31c2dae004dc6f00784e9a6933b91442f4a1ce40a424893264d88323689b6162acbbfe4eddf34ca4e232f4faa745c789e6e9f1fc53f68b2f60dcfa049247a1113faf901029a447312116597372bf566c9899d3ebbe6d2b9d6d27579777a7bb43c75ec0ed1f2141662e4c6e5dd85a4de22790c4961173c892065f9d91afa409119904a1b9d5402b59800922bdaa29802490a84bd33a53ea4c4a91d9ffd494c7fa80f6542ca679c8da42d7903135b391e43527739057249e13b7bee1c123df9895dacc17e3979ee64996d55bce46c36708420fc60c8a5933ab21f6d115d7174b493450f529cf926912de2cc377f72fce6901c5b4b620f2ed48a38f3fdf801eaf191ab26b2f3f95da7adc6e1e3c215b82e9168651a85c6d157e44f6e91618b1a5d17863c9047082ef77161d85272e991a872b9cd921253a02a02daa2401f355a82f6f4c096cb5b6e4abe59ddb1a3388321c955cbe3b124aa4377e4cac5e3290fedd1828d3ab9b38b7be94855fccb9d4952159fba13e9ce23a98a7771646dee2110a4217ff3c8e5c2ee9690000422f3088910bacbeea624a62dba641ec5792404ce884b0c3a310afbc122b8127df041073b20db927be80936e427bcf044d14f4b99a3dcba054e2a3430d04498c92e92ba89acf8c88a184708aba50a3b02464c0a24403188e1f3a29c72ce396717a44ca459a994d1186126e79c534a1b39e9906c49447461d817ae2fcc78c10848ac0470de0c6d2e51d367ad22a3226dbbce5a795df68ca7dcd825239c48d7135910b66801d0132420b2001e9f2764d08382a427828082999d4048aa3a7b228a152c00053a3a80b0a311bb30ec8261041b2377145d10438c28788105387cc1b2e48ec20b4e14c10b51743d48b62e22cecc13cef4dba355d76d77215f4ac0130e6e1a84105a6b6d2dc1ec1f4d861d764cabb2cfd4d8ededd10768b3961ac34abf6e91d28fcd2eb863348d3078cdeb9a9e91adb9878e78e10a360bf9fbbc47433b524516062928967be8881443ae9f9c88a717b439693721bd539e2c90a3ac79e2c800cdd81ba50239c6d32cdf37058165898174938c6118e711391ecec72c6e51d7755d5729f7d01126e4cf7ee640eb188de330d5fe936b13b91ee59a45f42a508c9f628b5c638c3046182352aeef187f72358254df874690ea8d8f317e12212455f1f144141101e39d58bb76ec12512455da8910922b228e2e2b9e42286b4652a5d5973eb803a1acfdeb298ab276680429b3b68d2065edb0891fb98a2c4d3bdc01529ce922ed2db5778cc61323cd82968a5a9cf893ff41e24b9992513e428886ec59ae509f90c77e599e2f8a6ce7963458e9237bb06338a2feb514ada20ca5100a6aa01f9f9d3c44d1bd018a1ef4bab33cbe6b39cae52b7749a9682a97e5a8fb37be69c2450a1d54d20a5286c75f4be1c828432914ada0e46c22c65e2335ecf60d2df02704e8f04280ae8bb3b988446fb6cb8d30d83eca31c60bd2a3e84d9a459eb185099bfde0cc277a970a3b1f77ae9e222bdfff681010504f0aeb89de757474a4f21ea9428332dd30d91367a60cf2bcf4996ad0e9c1ce90680434d48321ac83f29c8941974b0cf58027cf5324712615e7e74ff23cecb1fd8ede8ec9d9348ef826287a2a1772f488521a74a9fc3aba7cf2853f382b6f79c461b9cba3cbcf4fea86e5a9b33ce5027de42a75d31df5eea86bdd6de95a30cf9d4279c68e3a73fe221267523e3f8be40a7578048d60512b7fb2a7855a9ff168ca783f0a75a1905431498a1e44f28e1e3475324fe2ef47ec9c1cdf728593c3ea4caa7eba46aa8fbd71aa69d668d30873f744c513eea993933ae79c73ce39e7fc49e5b84f8e3b0fb99a5a51a61742a4492f26d19bd717a06a7e9221cfa9863c4faedd0c64393e1edd0b8bae31df42996af205b2a767c87eb2470ae105b5679d73ca25c823a0b460e1b116b6445bc1c2a2b460338de62963906da52c3be1cf5a3c4dcfb2ad44b169858db95e42d1ab6e1983691b0796a79c22cfd951fc93373092a704c2fcaccf354543d14e9ae998e9167f59663793e9d7e46caa50d64a375561adb5e22f55aa37dd1f4c2653ad37d592092b99ea4b2b7d714a3b764dbb66d26ebda95ec3b2c725639e0c8f7a8f05746f298058de52106917666ddb36ce86fe6272ed3a6a1c58f40a8b42abe667e93acaf39ad46e56deb9a0def21edd5db09095776fc1428cd40b5139eacdeade2a1d16d2bdfbca85a0aef2d4102a47fd46e5a8577cb3f2ee3571720a6816cb4d5514eadd6f50ef5edf756fc1382bef8ea302391b1e2c3db98347b10465f815a10caf72a5206299577951854df140bdf3d7513092e208a7490194e18b6047c1285f4771267efecae2428a3312338a1e93c696604558ab8fe2104552055f449cf95a8a534ebba6dd5a51b8130c046257748d79b1d6c5965cac08b3e2c87e368af81167e8e7a4415235b9cfedf3725e6e1167b417f5b4788876bea983b5bac6445dc594325d339d6ebad8d7a9d33b0b01caa6db37a7c3def49a78ba3f98ac35996e4d266b712a6551ac111a8a351d25eaa06b74b6a65b5392d5546852ca8961589bd2de9673ce0bc34e4bd74412e38c104278b718e3c4d98cd7fdb2aef15d2288914a794929e3c4536230cee092f18a18931ec4b0c65f863b5ed7bc660e8573083b646a88fb983300ff86cf9be1af8d72f6989b828f390ebfe1a68688390e4f41dc23e638a47adc7018bfe1301e00690a6cf6d835b128893d429484a0241c654882dc43486802094b202189dc4354d8dc43541cc9dcfd66e64e4caf91abd4498c13ec611cef2a5c93c9958753a51838558a8153252144259309008f817164ee6537ca30e336fe9d9c7236275807b762e3a6e065bc5933ae77d57bc4b80c2c44c663c8c0292e47c815d0ac4b55ec72581e4ec19b19f70eefd5780c8ce3bdc671627c460a4b46885291540ade06be078dabaec238399a164fbc9be262dc1477ee9e7715c6b171ef3800788cd410337ad4788af3aeba9018329ec3eaa1bacc65b0100f5fe0467519188706d748553c1052156be29b65037f4d44031b816f1600b0e44ede31b2779b756760bf4e7a76f29314134e0d112386eade853c06eee10951e14b95097bb8a5ea6ba19cce1a0e17c6ad81fb74b38cba7d1d9e0809477213292122520224747294fbc18c029fe12c86848a868ec024cbe41e12fac94976b60d951ae21e873be01e079c72003e8c18f77e13e31ec63172729c141703a7e05537321ee331304e8ac3a92166dc7b8f19f7b0102337315ee342bccfc03d6a7cc6676021315e03374b069671151662e466c655172203f7505dc6656021dc55b8a5ea44939c0da732e3a64c4fa5a49868524c29de4a0af7d44a8a938153284f7137329e4aa1c898b1825354b65b141977064ea53cb57273192bf886c655dc4da5527a7087d67ec6950144e66a514ad69eb0acfc2b87f98ba7522e9ef297a79e4ab9dc9452397794afd0a4c0bcc09f8c8b14cff47217930aa7b8d368c12915942fc8cc283fa9bc26e664538a3bb9a9d4cab9a71cfee4326e6a050f41e3aaa738dc8306567dc6653c95827bccb88ccfc0324e375d2032a79c64dc96aad355b7a5ca05d2dda4499336649413cae995b34139eafe885ebc4518c1c28cf21f7e1cc2c37b82bf994d87af917106e5b18838739a97717892c15dd51c96891ec763c82f55dc077540c8e16f66eed7e1551d3e3b39bceac2132c13bdb73d577ac3ebba258883cc2026c7711cc75d1cfe70327778af77b7ccf418387572d5735826ece1d4094e0de13dc66fbcc7f8c963601c2327f826c65518e75275f21827b89fc3aa91f0367781d0264d9a30d921b3c680dbac184064def007b38681c8128e000eb759306eb35ea260bf1f5d84b3ec628fb9cdbae136ab46cedb196313b78ecd81892fa630e79cf2f04a29a5d426ca35ed1aca0f9a0c3921b0b6fde446cee6243e27cbfb3d4720c39fee372f67da6e6f4e8637c8b55ddeef46deb8fb6d599b3ae8350c51d77e733e85c59cf3f31e0d19334fe25544eb984d1e420861a4810d3112610bc96b0def4723c39b60609b551f5f83c87c437e850afb6d25d4077f75ce68640cbf9382fd3cf983468e9797c9e2cc87cab285a609e0ef11ec0735001c28cfc3b8d6dc8e79183fb9dfc9eb8366a4e4ec7508c04bb502086c9251ac853fd6da1b376e683a04e03afc6ace2687d7c4d5ccea5773363ba04ea893a6556b6db53f9c6c3c8a1eacb54a1e0cbb2857351b7c92c706361ee5ebd5daa4dcde949b6e54123d28f385630bc79947f8d3456022ef50842345189277f80e45207245cee11fcca72da0489273780e876289c66dd8c9c9c93cb9e289cc41871c0e374e07cc395cb3a75faf185669d0e9302c2c29296f8e8525e584c36e6861535236cbd9d4aae3f4eeb5c26a391da7eff0eb62ab9f6ee7541030ef70987738ccfd72f20edf2108017c00ef68accc8e9765f6b7efe674c01ff071bc26621be5ea75b8343434a75c3dca8534a7f9e9d809e6155fd7f59caf9ee535b2068919f52d25057f39574acad53511b25cabbf2e7b5d36d6b68c8a5a45464618923b77468216b2c88d043128b1b9912006241ba783dec5bbeb020a26906c81040a22499664fa08c5904c1fa13852842d327d11aec8f4ddcda27797d45de28c193366cc10d23366cc98819343e35c0aa72cea299f05a203e6ec33628d15eb5cb81cc31f8d8cc538eb23516b0591282a6971f6cab37036b57e72db9bd38e72533a520ee328efcc72181e5eca711e5e13b59ae73c95c2373cbc06e3f4701e8ee3c3733c87d5dc3d0f37957a7eb372dc54aaa62663213d9c87d7f4e003e4ee9b7b711eee759d2e9657cc741a4f0162937d06d681e3dd4df7b3f93fdd171708ccdd258763ec4edffd59fddf1d1e77f45e70b76121e8bbcb7b81c03c00fc65c771940b738c9d0a6773e10f275f3511757a146743af410e08cc28f745cab19b8284c728d357242208657a8c08addc435464d1e51ea2624946a293e9afe48911830346a9e6c3cdf11e2e0f7f716146b930972ecc382ef67b61fc8147cf72dc87332007f6e10c38bd876bcd5d3cecadf7874cb6f774c19a68aff77ae5250f0e31317e612ef0310c8ea84b4e08fa8a534160c791a88b44b65a8b5142501f6e0e0bbb9d5fdcce289defeddcc3cd61e1fbe5644a2477eeac71a86b184cb9306f712665666b32bd394a4da68bcba2579f72ba66ef1724460e01f51988d9877dc7fdd0ddbe539e723acca89bb2efe13039ce728be4c029fb1ce7e1ddfd307cc2457ac0298b83d8e1399c073cc4880e01780e3cc4c8ea489c656426b56387e7f01d9ec335952d09b98790f864c8518cba30290b36559f9383d8e102b800b00fd4777867980a2287d757ec03f51cde7905f3950e872ff8fb75b82e8e934f98b529b76731d9145b537016bddabde2d3399b0be6a77b17f7833987a37678773f9cbc3abd1f4ed621871b24e61c0e7383c4bc03e4ecb31baf34cfe24c3dcd2f997ce3f6ded0c2d2fcb3f906cd339a67375ee284a04d9a3469922bcea277e3cde9a0c137be9ad921e8c6953a5c49240057e670e51237b01ffcc99a83c9723dfd84bf99ed3526ecb79ddeb9721c67632d863f1ccc9b433dce543c3b16da75428d03e61bb096aea32c375a8efd861baedf7043938b93afb7d88b0178fa0933a5e4a4d4c81b302cbbc7c0313860f853adc51fcc57095bfffff3bedb5e3140aa2aabd61b99a80651db80af7129b4c4ca4f39fe753cfcb331efb8f2e1313e6058d403e601b754c9e0962ad5ed8cba9d61b4beae0ffbd34fa7afac5cc3b69595affe74235ff57e1727db5f29c71abb3f6e4ec10180364eaf91315f077361c6a48fcab1a75c4d6e30299ccd75dd0f02d95ee56ad7c500cc11c8f55f677a733627db25194bb92b1a2a25db265945454545e55f17d9fb5d08e4facb9aae1f379b7000a00decf67e3a646c0543a213ee681a07ecf0977df52cf8eb360065fc05fe3299bbc0aadfc62127848d9fbc466600dc0f66188745366ee3326c5c067fd606fe7ed0c83030004ee3aad74800dcce32ee97a9eed7e51af79347c8f0312ef793f8078d3c4fe3defd6e320da824d2b81dbdfe2582fd32ad844f5698c8f525ab72ad7f78390bcb3597bb84fde2d191f77cdd775c1d2673c1b227c76500c49e823fef84bfabe0afc38a60dc895e123d70080b49f9e61ec2c292ecb3454fa6711817dac9306e3fb3761921a9878c4044fa40266c66b6ec277af51867637116bd8a82ae016fed0a13ec2751903f591467ba43974395c34b14340ed36947bc9049103077f466ae4fb95ffde99033611df51626058939eaa864e070cfb396d6fbf066e5780fefc1c37dc0427c380fef010b3172e3c379b8901e9ee3cd8ab906b7a813756024459d24265107e64f0dd1c373bc470f38a6870f98077c1ef9e3917fc8641818dcd13b86f119340e23c62d0ccfbbaad398110307d59bb39171a1920b7188f1c3e06f66986b37f9f47a0de35a6eba761e9ec3aa16a786c8711e7e93e33c601c23f638f526c779380ff039700ecbe2efd69eee07f329e5d5d6cd743f1ab962d36ba4e9dea7dccf7b7342c0b88df9e9de70c983533b6246ddc56b24ccc5e1302e048231f7cbf2fd2f0e312e447299d4e8a35e666badf5516f2d13ad96256a1129c100ccf65048e8542f54ffc12024156340668b81402660abc2d60b201344ad97291c11b95ca2be1e5efa1ce5b0208c98b75cc1bce52ae532390ebb28d79f52ea4d3030f04793eb636230c69e973fc92393e30e19e1486e79e9da4ffe82c48cba8b766df3af18ee9cae35077d603e1df6f02ff3e19fcdf65f97ed3f2fdb5fae78b87dcd6da972711f6e0fb7e50f997cfaaf9d2ef409900bf330fd29817a0c63c8266c0893b040c35aa3721ffa68783519da099aca18ae0bbbae09afa0417a45a88529b33c23af2967f462cf75daae9479e9631e3bdc6e68c1a68248f1318fca9d3b7f13a776a0327654c660cf77ce1d67e4f624547ca09480b00d4dc02e7a51ecca4209dc381ad6de9c19c811a3f89a1cc8f130cec8432886ace5a5564d9c6530c3160d34b863769baeac35ccdd3967420dccc8b25d2a6345d60c4347168c11b200e7ea406bb96261d89583102c56ce3c0246b80520682c0d6e57abe6c90296456fb3b0db2c0d76bc6eb396d0b03967841ed0e68457072694110e41c37266a075a9bd920660074e3ae7829009373c10d4829d12a020091f104187e107f69a584e11248c0cdda00966e831ba90a022092d4ef043a63bc3fe6e83fc793468aa60227f2dd44986aaa8414f14296862086a1882c492b28972cf1b720f1535c9a6dc434548f688d6cf826d2e421e493e45d306441b0eb48ef920cfbcaeebca2ad69bede6dcf29cef0b4462b7c8764d6e14723371a66aa5f78138409c89327af2317a387048f476a8dd400b11fbc9e46db2c06e7126420ce9135ac764119569e24a9b8176552a22ee207f4d09c87798d422077104b4b09f0c16f693c9376b15bde806f2754920aa906692269d60e155b4b0f0f29b110b85748dbe0a182c3c8c314231681d53935b080c36fe9a71c618048b54d0b41c67e39c0c71ebe8d93d67f7ecd9b3bbc140318c6254ca8961584b2963f2bca83c867b46b694f328e8c7a6402b652f3dbbc41fbc32eca39f5d83bd59edf89af304cbf097fda2b77ebb27d72ef7c9c38c524c7f7439fba4d8857f1421ef8f2ec74b25d06fdae18d2da4e672230c9b768880f8fe341c44faa83888ec472d621ffdc1893fd87de1ce4060ee5f37b5e37afdf5fad227f6d19fcfae5d3aebb33b2b9e1b86951e5f7a8d2c5d98afc38abfaa5dfaed66af77be5ed330bd2d61fc75af8f2032777fe384886fec235ed334fc553c4bf7a15dc336cf4db5cd73977ec22032633e801c41af8f2012bb3ee23b5f1708fce4769c726ae21df1fdb8a3cb5fd32a4a7d9230768650681d330fa1fc25040b1f2f2bb0e53819e9a8699a8f2247c8b08cfcba20b88edd4706642ec281eb9d254e0eeb7ae471e1ebf04e213a595e0f8194980611ade116b90f9108a069dd0ddf57b0dd4162ec9eda15ec372584337a57b0d9a78410c2ac218470369411e9ef8285ffbaecf22f71e66edddd36ebeedeb2190498bd0f2184597e9ba8f932e7dc8106000fc06bb0514353386ae5aeb1d316bd993b0a3028c95df41aff784772fccb148260458e570840a0d1488185ef4d87488370caee6e8bd45300e5eeee9e32c6c4c4995496b7eeee9630b08d7d3492fc804681902a78db51d84290b2a09021514da7e4869c0228ab846b5860371067e2e171e20c4df4e05519e64f468706f302da8364d9342746310cf380c019bdc982655858f089d82b04bb5261ad15ab87174837418a335be0479ef9b94d21996cc43c4bf70d7f7f07b18d9d8795e422205b44f4e6514e60e790a833758032c59148e4c178628c31c6083725b496f029f2018fbd11208d0862fb4e6cc27e91076a10a7b2c76738882d0e49d1cad8e3cc1cce66346275a2146c7cfd23ce00cc1f95d8b586339e60df6e77967f453a1669640552d0c91057405a0106223a39620dbb189cf9ab3ee283cc8c1dc38c88de3c8d94c2d94a944c1d29298c3a3b3c30893c672a08cc47846fae07cb3b8c8343852776585d77712012419041100bc7e5382fc7717178533b4acfb961b99188bb474e47841c8f96d36b5a844f5d23f4dbdd3175c4eb031ede5410a5c797bea3744af107e3212704c70096a7ae414e01cd6a79730e60790ae31c216f4094c44abd7b0660ce5e23e32d3dbba597ba8b0341c03385100bc7053ea2a7904410cb883cce0b3ee286198258296ca474177c04968424969193b7b038c1ce97ae4c5db973c89d3bd12b510cd57d6641e6cc6cef446f422b8c66963ac212267e8aec767b7a9a379623715647ab112caba8e8cd883f1a18362961bf78abc3be747abf0c578046f6615fbabd56baf1f4f06218a5cfb0434b33faef26c30ad0c8df4df69e9f3a53c7b4448c31e66843b1c89361fbed1661f9d54165b97de441a1c1da4f163c87b07c0e89de1cd29de52831c218233c1dde53778dc0b35c24ce32f2d2b19c8bdc351695b7ca63562a7dbb5f4ede340cfb0f9db1e76478bfe7084c085f57de5c94a5d39b93e30d72e5151c0068e3462ebd466eb7a3b7650ee51c8eaf9147dd6fe6146c44f47ed0c8273c77a6ce144a7189fad947457cde99608b4e9e7df6dcc4990b70df80c8d8558185cfb2d935127e592ec272ecfea0c93106b119fb01224d9cc93e9fa3c6932b16dcd1cb6663a16b58a16dcc6b175ad135e6e98557942e3cca227a33ab6d141fe329463f9a4c6113585932eca92c8bf391888b900e3e4b92f2bc4f134079c6a03ce77c4667a6ac429e3a7205ad88aaf979052c8ade8482fda68ecffc9ccd3242cb9167b24e0dd041823e72c5806cde8839fff2010bff02027b09a5441b628071f61704cb7d7923ebb79d114e4a31ce112cfa2b23555146aa6472c7c3932a1739b0f0510a1ce4f8971cb0e2e5fce7498f860b27d44352d0c9b1832c2329f0e4af5b9c8e148a90bfb6425eb38b332f71669281c6cb08905808641e73ef319fa20530b862152e256cc8c109ae147477cbeeee8edd0db1abb1eb6aec9a92045a1c4084534a29a794524a6d421bb023b431e5001ada90d25a137607703d8bd08694529a8080958680ad602963ec18e337f3978ad0463fe6993b69638ccc119ff2d76519638e97dd0def17f3c92c718cf29ab223c45840712e8c5ed725e31111bb9a482831d8a4dbc69cb31fe1bcb00bfb81c5ae26f3921884524278c2a08497d863298310c3221614e3a6e85637567adfa3efcee240b32ba6db867ca984bf23f2d5860cc32efafe840328c5b671c1d8309e408c4e194f5e34db221c006cfa2a4b598c56c26a29533cc4c2d7c489d9b8a49098e7b5d2f6755b561bc7d4b87b6a95fc76a53c59205fb133407326af439a4ba73b2c964b1848d79047e44b7275bb22044e402206186c29f7901236d0ec25bbae086722f4e0fb82d73c84104208af39bd6c622f110ee03ac5b2972c6799e72c4d6cd2db7962d9cb8bab94c15206e11bb3557cce2cc9946491bf4e52a4a40a29e418e42b265f57138e902f254ae4ebc615745032f97bc9c1841e26846109567ceacc107476094755c8df96278aa80479526188880b4b90420c2588c00a4ab8c2da2fcbb009f644470be2764a5089f800cfb682522b49ab415675404b4f604e10ca86884c70cdcc4e91822ea080080f04996147676768880d44e814a5d44026cac490813de58e420c6c802164b5dc518ce104558ce1055f18c30c66f4d82c771463c88137033ba99079ee28c850c5fcc001e2cc67a3a60040a0ae6ba27e20c08413af28818040a402851f9ca025a60d489aa8ec0401c8058c249028a11205105c40052888a09288a0029311b2eeeef6011172f7690ac0a33f1b675266314fb0f0fd3883e7212b7a727fbe5c97854b50824496f88978868e872965747663d735a38c538832c2d2a905c20b4613b412b029ffcd39273c651f6d62fd9a98d1eb98bcaed933a31836e7ecee19e48ad3057d2161e1e375cd6be294c472c4a2db66d9b265777777675052011e368700982057a4b4616268c30b754ddc3171e144cd84104a3803f003fb6519a8a58456286163bc1a9b33b330c88e3dc3b28cca2ca318c33d6397638c1d73288c4dc4d87dec7336f6c67e659916331a23ce6a60d1314a6cbbc61170ce6641291cc9f663c71c081f20e535e735fb03ddfd5386724af99a78c1fce564f9a923c2a3c81d68b2bb5bcaeeeeee8e51cad9b108730c5d843093ff72329c17964062d89c58b7a9d492177627463367dfae715d18c5e6293ebc2e2f6bd8d735fb7a4d2c7537be2044b141264f39a544a26577c309610732ece779fd82311a6918bb21ceb07c733912c227c42cce5ca62ccec0381ba722ee5cca16e83296d1f79db3c212cce095e1a71c81d4eaadf5b262b54a292fe7155a8c51da07d5a880200806913065f6f929bb67e4bff92cc64b39e79c73ceb89aac289b905168cef63c19191a1aa12c7dc83e64d3eeabbb9f050dbeb24bce970cd3c83db3d2bf790c675225e72f0c772b20a96ca8a46d15753d8d3a4533224000008314002030140c8804a3d1682ccc13416e3e14000e839a4878569cc9b32c88614a19630c30400400404000406466da00c0c436764055e2e393cb7f7b1169696b44dc3d53a14306f060f1015863a2d2a8e5843b9ffb1ffa8beda4fb023d1822bb81e7dbad20d60725548b59eca7d708e201e3b7810fef2df099633a29698dbd2f1ced3defba012367169b549e4d97c89dfa747367248fa832b38c5672abb70631dd62fb7c83312f247bb3001a6313212385d857242d53ca162676cccbb2253d5c5e75268c23f738cc0ed896aff9c3a49f7b4211fea5dd17ebcc4092a25af46e284d62ad80824a16614b6585ce602bf3971b0c8d51f74d0ae44034f3aea9ecf41d62ad7d8108397e3277bb31d0a1a1c8771c6c2e2776520bd4328b64ca5ef0d7aa8ffc40782d4e095f26371739baa153e4cfb722c8e14744f4cdca76cbb26eb4068f060f03935dc6e692010fe6aeb8bd090278383d7c603c47eaefa506271e63afa1838ed54ae28f32a6b9064e12c10b0b4488bc7adead5c92720472bc2420b75c37d19f2ffee648bed2445cf523c8b70c463f768135a76089b243a87348f645980f14e377a9cea930fc076a81638f0d0d90086e1d54713a8721f80ec81b7ef4c39bda043b160e3243110d5424069a3480936c7a4d954cd512e0642e15543b42337a6cbdbd8b8d281116e2510fd134f9c9f57848ecf632a45b81b40d5be89fc85854397205ee5d25fad1ae49424ceda8258c431a9d380e047c84615b261da9fd3d61af153243bf0aaeacf3c6b18225e8c690dcbe4416ffce18c230257e1d8cb20e0c3e1fe18fcd421986c2399fa401b3eca1ef13d0293c667ddf4869e7858bd67ef8bded878b01c03731c2ff9be456fff7694ddff5f69e73e8a185cc91e73b8fe6a044ca8a7ca55efd7ea8f8961e72e36431c4611d3fada9655befdc3426b1c299cb0487195a16511d77fe3b83ac56a9502e95d8a00ccdb971c23c1dd65823ac470d3418a6fba0523b28c099062a9e98893b143d5a8e1e9b0f2478495e81b84ac1722cf92302aa9269b36de546705a4bd228249def296ff7fad39edfa4317049cde183030ff9e1a30389330ed683ca5bf18a1f1c5d1c51b32529375d164e4ff80208ecc4ac8f2b3bb7c4420fe3904d931f937293c371d5466e0aa0e34b00d6acfb4e0c58f7f476a28e13f9884e0cdee4b5eb55c5564bcfcb3edad5367a6a5797caf0956978b41005a2bba3275d275c5d210e7f919a800383ae15989db5bb8c2f32433a440bc8ecf88a61e53b75b7ed8a01c2e4df9c4062b1c2b6fc48f701d70a8a87e34aa2014adbc13e7245a93bb0c2a6f41108779272ec56c733252b48e88ecfc0c57e7b989db3a5ccef273b165d5394961537143bd85c1bef0447647df647a9ddb3eccdf51f021bd69a5c4814db14a2df1b3986e44abc4dd8c2e9efc10bad6e0edc875bbff776c47ef0070354dec4310362cd23194b7037e939e3c6dcfda97aec58aeadeccc3afdc97f380e9a284383367c04da44e6f14a2f6f6046bf80c9598f35331f1a0e5113ba8603c2f863028743239e6fb643fff67e8948545b549626777658a5a50cc544b4e1639e4888859c598a9279f210a81dcaa528ceb010399ad36a9dcb4dbe86e2dfb9b1ba3c9169a3ca4ff1c4d288165573338e200b8e6140be97975c0a4a01d11b881ba1b0688513c657481b947a58c256a855e657015c8437fe54329753b0f14ca5dc2466031d3003d348ac380c969c5bb6fb579cc23582874b87202068b76b906831396ed9352a33632cb81dccef939a2960823a5a8714fcf13ef3f4521445ad7fa4d496d3dc4efb1c7837a2f9ce1089147a7b683113975e80d235164f2c02992f69340182eee9aea434f4c5b3f53f804bc98b2c1e60c6d0c98471c064e711a9cfcb5111fe990de1f10265f67d28cd21a74a4a6384d49e53e43b191a7e781f1d301129f523d964b37957d8e146ac0e673fcd126e69718fd540016b8314dee79fbe432bc35daa3f3078388aad066ec691bd0709840ed1d930e67738a2b355b4cfda2a494a9cfd26c6463e17aafcb5815b5b48ac8445e24d5f1cfda0b9ef3777c0f24a2f58cab0e4e63fade0cb9d2cb593caa285d84c4125bd80a1c6aa230d2c06e34a2c78fd74dac4733a1287122223f3ce7351cdb88d925b0edf02711ad3fa38f264d2f039299df95d04dcc0b17dcd11f1149f079538689e70eee6257ba8ba85532a9afb879e765bf9c64522a7f5aec4cbd9d8bfb1689f41dea405ad318c868aa5391754a78f8d35b47db53e8ecf87fec02ea293550fef24dc6e0cfc11e93b502d626c3afb2e1600409277155fd28a69bfbf18f2a09f67cdf609f61f8a3c890673951edb4f23d78920cdb9e08b157b1c168f45ad2f8cd73931a232c2b62d5c4cc108516dce83a4bb3260006e8848136064b63b69c5e8fcb9e3afbd61d9fc4623bd2487b5ab41c337246d643af851320faeae845109fdc4600f216fe429290eddaf6aeb6a8648754188c8b3ecd379e96ded7132c80288aec874cd785721725ca353b4903ba297d656f76292b5fa5f4f9be561c4de639a6aa0054f89af41cb7b338048ac33e7d8ba371515f93c4badaaa7d9222ab3d6dd2906da792a2769d17164a3ee090c88c545a3694bceecf4ab1e484acc4d232c63fcee98c29cb492f40352662cc0147d07b2580a19392e91a0dbe45ca52a0252f0d1fe4c976749a875e91a9824a4ad32df1acb490a2958b2b571c5061bb57b32665108f383c4a7d463fe69469322de535b6436ec0d31348b52283e2ac868bebdecc72e517c662ea1420e2ea0be47f8df233a9dac41e771df965e986cd3557f8ccfc73defadacb0c835c53fdd2f0fd4b802e585a67f29d292d08f49912853080132ccac600496561df98a1fdb943f1a53bbf403c907e392a3125bcba002b1581853775d46ad8a84d89b9d0c527f885ff7a1bed4a09bbae225512e8eaf4bac07d6eaf3db807dfa8edbe84036738d189bbb8e0b83da99150ec914475556ce094223ddfa764366ea61d81ccf668c9a81564ad7c843654ef76b2ed5bd8407d61621d86023f2d32b1d5b38784d4179f1db676e9f6f96f97882a70cd33e84d51da044ce43897a29e8010191dd76e3020d94cd3bd912696a4b2cbf873a099050f5050e1ed2443331784cd4da9d0dd116b5681b20de4ce509416829d191ad8f178d8b6a168a1a68f7a0a9d509b59d6cd9d732b57451ba7d59caa22d1c723552f4246b63fd06a5267695481dfad90f4a1646f68917cb45692155f4558c59cb9e6f1612902f88593d046c10915ce832aa5a74a0cf22c141fd5a75c91e608260d02db438b5ae0c2d453b39ad74716da369e58239773c59fabd7cd27ba89d90a52ffa0c3b4624ae55a9d99aa145307dc6f5675e79e3558f1f8cb09086e9e61f976c3f7f9de8331061585d02b49e0961dc181c4d8e048c0529375f4f8395255be020b88ef7e248a757fbddd76ee806ed79c8533fb2f1f8b00f0b42b8e62e22726a128239fbe2fe73ffad1d0478714e044a519fdaf4e4706b801dc16332d2184bc824e9041b6a6734ce7585cf52fdeaaa9498aa3fa449102819f0005b45b9f6667f4fdc400bdeb3c8d4f96a8144484ac9b8192bc7c2446cc3f370c42559c58fd2304f901cbc2cccd89bd5c4db98ccf407e05db64ab4c2176dc67ff2738b667f9706aaf55578e3980512f3c94e1c83fdfc096ca7b00ada08c4b9a7f2981dd8c3f3f323baa278672bc14f3d8d8db265e2b4c21815a78f7d19a500fc55762e262930f4b3128e2a7327b23dfb944c190fd25da2a7a6c2f28809f1e7390bb4a77614e8d9dd4d11a9c4412a6b06f6829beb8d73898743c877264b60ec764d5fda11561eb330f0fa6fc9e29f3c61444838a18f3b7b598987bf87f83cf8a0c707d900fdb815b7b5302e2e0a0eeb5c011cfe26a6475f8608b5ece79af3ea01222d31b9b3853f20b4569878eb9b01778bb180136b8917c6130551a2ff655fc189b32c56875b63e8277465b8259b2dc8a724bfdcb9645eecdb04da3c7ff11aabfe6a5f52026c4cf1978ef96c9f31a56c230882b9a2feeca8b39a187e28e1294e2f79aae36e05dd3865255b32214d4ec03e3f435724c100194febe1452bdf276310313add860c512a98e9de30fcd174571a482cbf73c78e5c08b4f9c49db2bd84bae61967dc7b7e7e807d9ebcf9af37e5c1af22459ebc14c8b2108d54b7fc92f0e415b25d293d9c98c921088d0726366e4762d5c16b62a72d3e058764ec60809aa67c49937576038955367aac259476368557b563c9b418ccb7055523bb40ce828e8ce4ae77d8b8e9acaf4cca4f081361b3b084f4f935099791bfd4b1751eec0aa0d1a604816ccb54923ba5798186c0690090ad100ccab762315eb4d0ee2fa776a9d09933c891169770441c83ec869ed7745f9ddb91d665069cce12176d12dbcca8b1048224a5119d499cd0082e0b24f74a6d6cc4640ef820b6ab6836cd6d374099f74421d39336dd554ac6c59a6328d5c4ebfddf7e5a1aa4db59b44912b25d2b1637e8a185ac0fbbb455a2cc51c3d06059d4d3bc9b72b419c3527e85cfd0ea43c1963f023d279efb094fa4a962a75a8812a24bf50fd36234b848566b364e135b53b81e8ac88f11e3898cccc3ce692e2bad8a36dde9b7d61a6aee17a0ad9ee49284a283424f4d9130023ed4858813e0347fb41d54f96f7986dfbf09875b67ab3ed2a2c0b4e485391f37334b7280ca971cc8638e572d60e46b5fe4ba501c40d5b777741f316c6fd5c56704405ea6bc294616ff95d3ebfbed3fdcb256d56946b6c04e058d732bdcbbf14c4b2b5465581d70a26202dc5d50b49f321dbb18bdfa69a0c0de8227873ee9b67647f8a3a7bf381295f219414cf7021d7b633b7f95b04f43857ce6998beab7ed66e02ce435f378ca9afc2d9c97267c9289dc9ab53f92b4fe7b2b506ce9f49b073d8bde4c82496ea32c16c043674bd820f5acb061b56b0edb407cfc93423bd161eebcc7be83b4127e1fd4609651aef10343abe834f1f164a3c949275cbd854ab9e294e231946bf9d5bf170a3ee72466d9a6562d7656d0525d3209ec8d839fa8834a3af685010b97a062ff3ae0c1a80a111424f8828620829497fef4953551fe52ee21f884a3fd53dee6fa17644471f1c59f7f7e01f228baeba95416b67d24cc975394a85685ec21e576c7446fbe1a4a701dea87f7381a10a929fe6a751e80f5d7a679b56dcd61db6553bd133de15a585c1cfc32babc38e429df36b9cb1b3e93c10911a0ac7665edbb2de33cfa3b101312a0c8b11e14b129197ff0eb9f121a617c44a805c17b44943f5cbcf4c288a5aa32ee2a5080ff8b943be8d6e0a68ab835a78f68c279e43712e350c34c502ccadbb24419da5c3bf22bad29696e0f3854772d50f7968b9ca069a2728d3905fa00329552a684e4e9e5f82d55121c1604db0ee006f1e0de516ab1d70bc0ea68472480b58334b11f2ad7a5ccb5a4032ba260cb212eaef0be6ddf4c95e11c346772740989e9faadc8f8c11876f861e512b894d47a39921ab4c6460c6e8bebc09354fc804cf9b59bdad3cb94ce9085005eac5cf49533945096844f8a1113b7b4af69ee9e75f7d0099d6e75bbdd39c240ebbd0213f73a4df4cbee7052df8dc2cc845f49324955886d000e51c89c8bd9a78c043fd1e48941fa253a1d8fdf06e00d28860ba268e24d049d96a813972283575024338d4c151e95c2a4510653434596a7d1d305581dfb7be6c5115b9a93152c70f647244f735a9298faddff763b70bbfa4d5901bea34bef9f4219f634fa35a93533000336dd0d99116e4d3a23880084464052a2ec469d321ece80db6bab330ab185b32a876637306004978b49bb01081d3a58ec5c3b2f9b3b5db17a1a4d2e9c9236631b8624cd2862fa1c324217ef737e0e46cd79c08f67b5f39db24580b8716bfb096bc790fe1b02ac658c8a3c33cf064d2ccb50905963826c6850e3a4117db24ee9b2bcd77171dab1bb4aaf63ca9847a0e70cdfdf3baeebb801c617eb6e7dee919eeebb194b238fc8fb64705eaf05ca0e8fc4f56510939759682aa7d5277b968c89e172ec3aeb851a16f05bc67c4a6715cd10a8db04dc5deb3457c757a65c8a3c667c5e3ba3c81ceb5036cc17bae2a9324c055af9e7234664c3a306f5450f3162a074bc5048b6fd7663501951c48015fc300a7a97dd7ceb05a8045ad549414d086536d6ba4fb877585d07e99f7106a36e009c422fb77d27df07a1e93137268b1bc8a23be72272f76ffb3a32bac5248c7ff8fc7ad3d146e27950700e3c2c59aa1f5c4a0d083d68187521a83e36b473cd5005f19b35e6edc23ccf897a4c3fb93f859dfcd1f0f26cc45a40859488c8c4a11ed9aad3d6cac4c0c156f8332cd24c01b1bde76017e945da6bd1e13db405e8aa020319a69b4826017a36a4afa4a00d51b3705cd4f3022e88353c8a5d0dd1fff6dfe234ac3abf1e9156411e34def8a982a74b9911909931e5338cedc5342f31e5e251b9739911c2954798b5140edcdbc72c2db3d9802807395a921921326292c6bbef0c4572a97bb5d7cdd5049a2294788648820a1fc9bf143429fb3379fd88c2ca5be114100b2711ae057f598cf75df8ef1c38865325177b55648a1d15b7a5252d04c2edfb49079b9a3b4046b1e1f87ce526545a018a071aeee55747c87919e11237180b5ec6e02e41a2e5f77de1d43155514b9942ebbbcb2ed59a8105e89fafcd38858f8ae336a7afce90c14e8eaa874e40b64c03afca87c1cdedb4261c9ee486e88f09527e4c24feb0924caaa40831326cb457a85856eb55d282780f5b939baf9264cedbcc435326cbbc3b661ee33848592d6acd4d983c34e06a7b5bbdb4af61f1caf746cca12648d9da68786846bbd6e645615496d98242df15a44904dbc2e4721600338967605d57fa23233cd0d4af629f0ec9e6ab90fd4a7412817a33467402b8be0161a62288bb95c135cd23402a11918fd250f3d6d0904f1569af6a54cbe389600542e2fe0b3707eba68556a9c522e8fdfbb31b6c46212d765977a0286045fafb3918d38c38267b0e3a972d811264401cdf3586844982dd59af7e0058031992c77450b0250bd7e5764320797d48a0f3f36030e45c1a21e8be1cf44814d080a7b15e267236f4740a927d028e4ed0c97bfecee88272bd827341c703b1d4c857cca9fbd3d10a96b3e6f9fb1421e1eeec91d1cb30cca8506c120dc7a6c616e370138a02f2ded6940db5c1e92941c416c2f2af7d50ae5381fa970c8a0a44edc0fabaafba29f38c902a34f9f523050d944688ac0dc001df3ae96a435fa621cd81a601965f2985b798ad3243c70765eb68f7f67e1584400b5d9e2729315e714ac75b0a0b32c194a75a3e9c0f0ee1acbdaedd40214324409db721cbff86ca6887b90b800e7860b9ada5e3cc499096a954134f4e765f6a58b33abc4dfa1e0221b46fdcd08adfe3953393f16a8a2a117cc52bf1d0f428209ba78b10481c55803e95cd3ec79099771ed3507e7b5f6551fad4b49f0f7a32c71a052187ee35c347df512ebc40bb7ae94a2389137dc510b694233b731af2a00bd1507e07c441d9c2f219b02e923ed5e5b7a1bd0b544e226474274ec8f35a85130468320076bc9ce0c26dd033b154356d9bb33d46c556988a5ec27b97de18f4915751cd18aba8dee4a73eb172742819871c40f9495d1121f4becdffbdb6617d1abc3152254489d6236309efce375519506ab2fb31c2448b9b32d91efeaf93b80251915292cfbd573dd5ad75726989e843cf9e3934eab3bbd7adc7087be75d787647cd70a5b69ca3337b327dfc7ad5569e23663ca5ee844b7480054b31fe72b96387aca843c0b59334d599441dd664685b56bb9f4db46997cd731d2da3b0089ff13c11b09f98e135666da5306a248611337788444c59ac0aa5c614035ff50f8272cf339acba389309006f8c477867590bf080bddf6bfa523027ab143c68498b11728afac88f684ccd8e04806c91d9312fe8411b6a4937d8102715a0bc838a49f1d0e0b9e27c047a1cbda06fefe380392fa1fbfedc21a0651d93b20c81a1111a7ed0c44cbcb8247cb703e575646a5241a663350e5ee0191bb9167a60cb71321cd7211ea634b735db159bb5b420b343d4073a333091046c703e45a5e43fb48b98fbfba7761d74205c68f733cc25bd16db1a4d633a9cc89d0cbcf3058f4b58e231da5fe8de53672689e03de0ea79afc6538a62d36d729c33c58ae66712493a44c9799d4016ca6e249e2dded55ee119f8d43ad1be5920f725668c8f96465db3a1c9ae67468b8fa006363d6d2fee85ed9eef9a113896a08d35bcf3783d121589757eee278b94e71e4e94e41bac9c0e7bb844740e3abcb211ad2060947948086da401d702af6dbdbbb18bee1375ac4b2f088909309677193aa468fc3125f3d6170a1afd7fb1e551afbbfde8f61d0b71d00ac1fe932c08d62382c1f97af39417f7cea6981b125f2f3b5f7369060f67adf4dfabd5ef3da7d1eaffc64e3178c0df4958c132b673b7e24dcff797937f72567034de94f3318ba0f66c241df9003c2f09f9f71b71f7974fcfb6e2459dafe8f2135090fc1cc1468b449dfc2e5b67d926c0af2be9a478da0005d53f3fe8fa8356d2470d01185b0e45067293aa82fae2ce8445117af79631f84375d7b4f9f4e7855a9457d3181c3d0a6c13dab0a1a41c8aac32bf5c41864cb436c97e7c739d749b124941dd404c9af42f60f4da322fa05dc5aac283f9fa612567b80a664763787b9f1761fa06ba811147deac1207d0c6ef943c33d29e9a031bc144b10b32e30cef6b7d5c7ee4a1762825590e05f6b3afd1212f95458bbc1a545cd66ba2adc3b9a55a2aa3557770c610c6d051239da83d277001dbbf035b77c7ce6da840f04d43030a3f065eb5f74a0f70b78f2bdc7bc9785c5b1369c9115d790cbbd8cad3d8a23d1efc6db23b8faeeec31c15489bee096ec7381f8729e6886be1837658b80a135c4ee7ffa6d06c6fa304994bf8ba9c4e7edc8f50836ce1e1c3d386a1c6edfb9ee95e5b1bb52dafa58f7be9ce5b0e882195125a53c4e33e795eefb3352da1c20872bec804997f96659f111ad109435b95773eb88bea1c124c4516ee559e7c581d9b1143ec5ee10f32c519e3227882d82c4e74250d429c23b044616e0a70cdfb63b616562f6231c37bb53a4e4206c21ff4d9d1c431a65963fdd765f068daaa97d009778b37dbe92fb13bb0e3ebab4821769b56b7347a32d1f0631672ecdadb753719d6a17949fade60ec0f1d942bf35eb5fdef580bbda9218bdde66519d8fadae2c4f36ff389b5e8bc79e67846aa4faaad24854e95a55e654cc4c4da3720feec8dca98950ba6598e9ccacaa09a404d52945f55248e082d366abd210594afaf5101cbda6b4ea4abf6e3efbece722946f79c2de62772118514a3e5dbff9af6a22b5ddd7b152eec6709d74341278a99637a94e297d4cfb40c8e954064fc3810ab0ea6e5a2b8d77f6fafbadcbf05385f82396fd07a0441b857f752c696a469414dc1f63fc4589df6a018d5cb6a54bdb3c59219ffb3570172b02fdd65bddba677814fb1dd29ae243a7f060bcb25b7725286bb3745eb7dd95be95bd7c47c1aac839153f2e480b0b1ff021e8f4a4cf3b34e5978093a48691fc83150f2e875c0cecbb37f6381de797e0ef00dc8b883d25f9dc24a2a034e05e45682bdd7fb3c865a501f63e52af077a3ba2501270af23b49502945e25c95c44e2a72753a338155f4aa2825b35ff71efd112e499212e3b7a81d00693c768c07a13e736bff9b4e35dc5accc57a344bdbb776326d4fc8e870aba582bb7f1ce91ec7993d40247a9e4491a9ef95a5853dd4551a860f1c5b5611e2d6adbc2aa5c6d152e5a546a87f6a3e5a5e0221b658e30cfdcd11b9d873690b833c345182c771b0a14f357ec53a0d611649b93af066f6ee3a460943cdb21f3724219771f1977b5f3846750f6424112a885cb10b10f48ae93756d470ee3ed54ed81ce8cae6c3877db6641b0643689973367125ef406f696eb58c67dac6f1c1767d4eaa82d0f4d35236c998b3c0bbec8301ef4871eac90dd66739b2c73936dd6eec169479eff6d8632302955ef3e01e20193c49115aa31773c625a1eab0d69c01fdbddcf56b6f6a5a799d34d86438af30624bee30d6aa1498514d436a73927182302d2d4fa426634492cdff758548690817c85aedda916b44073f881d0e1ce6aab2e7aeda4cbf24cc324c61569186dad962ec6de7bd095ff7b0c8d52f1b64c9686f70c73a2e3c6e0e29c283811b93e76f4579878bf19a1a65cac5cbf1dfbd53ce1621dcac6b4bf1b5143a90661d14ea2287de3c97d1066a4d405e76c20e8d9ce6a622ac9ee42d232c60d2ea1f672a24414458366e6e12abcdd3918c01d7e70aaf05ae487994a5b84fb97becabc2e6a564d1c0638516c384cb27f148d497f208142b358366adeaaaf229e360b28779999395849d5fafcd6d89d96b05267ada1ed91f9bfac8985d396d38f464407d46017dcb4867d7c5d1ec491d235a0d1c65233840b9f8bdbd237097aae9ada2618c883db74dffb7b2f6d0adb19c76492332b6e8c80d8f3aa6c582595464cc0ec84dbab790bfb1de86cb54bbe24e0ad25b19ea57cac39504a65daa8791c17b2d3479dd373ea8e9f0b7ae0388da35b29953c9d46cf08c263d24e66a91195dc748a67ab3ddc1d1b565acd2a55109b199c553d040501ec9c3c101470bcf49e45f9b40088dd22a0a40c2895f8e28d58871779e145562689e9ba8893931faad1ef3b13fbe728d11f0dbdab3d898ca387ad000b817166914a4dc89800c81c1b526ffc2901db2bfc615a645ea6d5d49ef7728900978c9ea1a02ff31545255ee28100e4ec7053a2631e946ea050df4559fdda37ba1b8ede8ceb446ee7e24ef41a7d0c7caf81b65b67262f7842ac705fe36f803b11032c3f4eabd191de8bbddecc64624c49bb0c098b73095799d5c69b0394f3f884c4be4bad81d892de9e56b8562be2ef4d20ef440d028a9b13a11cd8fa6fb9661524895dd322739c762bc7c13ab66d7912566709ab1f522c406dc6e3b54549b86a7b29fea9bc4604bde5d1b4330817d0d7647be40bfd5030cc6601df002854190fd186ec7ceb90976503dc76238653577055a4ad1034198b32d30302fc9bb6560af0705596ca06b4b6107c5003c225bccd708edb6cfb9e84defdef90357990be679485cb9f112cbbad0c9513d166aa8184818749807b2665e0a08234a81b8d6ae0558c251e684d3c0897d532f09a40b1eedf699639a1558f6c75e3761350465114369edecdb9e04f2304527a68183230327280e61d2ca7b0619efa07e98b133fa0b860a735a04619e2002e9e9a71732bc51bde299beb151d4d5b692711a827bbddd1e43592bfe844ef243065056e7f40ab7760504c6adb9d899c2a601eb2e44dbd635c1b051a8ffbfb1dd8e9029c9d15a4fea5b74c905cd615ad9f8643a9af046d59dd90121092022c0aabacf2a7dbcc82250f451e008646d51f0c655a6c86796016fd266ff7b2ce86f13fe6154a4665c7333fb049d6f7f9198ebfc7936e74340d74c3e745e6149eb31988b046f38e522b2422a6bd154ae0fe6e8beb7aa4c816bd11436a59956eb134426ec501ea9e631aca105dc86c151c0aefac881e0009149a496091be97713360f935337768040b217d9c407828882dd48091779884a834ca2999c031ff13681a1884d3a90819ede08e4b07c8fc5747144c598106e02d7064fe5207613864ab49276eb27a370b197348bd556d9881174626102866a2f862d6d6c10004f2ca0887d7f17f17343074c401d78ac8fd3f47ab55cb19f757a18b1e33b324dc8894f0307fae4575108d37cd7a08aa9c157df624a116a225a1aebb0fa04b38009a9cecdb28d20df2629a4346f2b48fca22bb65573bd28652d02e4709b663d7923586ff586f131a1c22f29f5303db8e5828011dab99bfeab744e816123123b66bc96e9e64e758f805bb202dfda12395c902566c5f506626f0e2cb038a9c7676817dc72e1bf196942893112c3468e306cfe1c0ed432497c921c6b7db911b823b7ccf7becaf0d1dfa618accec50ac6fcbf48b2e01bd3d781a7a8d21bdbe54c612ed8c8e433ca4b7f8a5328d1dd9098ccc63801d171a0f131db61dc46878a0923693c19f599dbb418fc85c7c73b7e04e3ccf5786dce0e06e084541d9e1db27adddf10bade7a791511e0481bcbf048da90aa09bc7bd019eeb51748644e9e6a923a363e1bf5c6b537775d5e722058fe6402a725e61fcc89a6ff6e379864cafcac0c03a844438d9aab5a97792ef193d7d5023d706b9a5b96c865dd4873b350e3322886d3a512bab9f27c9fa91b3b870e26647d52626ba26b332c17a22be9d4add760e3375d028fd10e7a6df2c416119c56830b8e0fc91c6235d39a162a047cb692e56eddf63d7138b0e26aa3fc693d7562be780c432dd54fdd8e18a57146bda6c30623b846f82737d685b2f42141c03edea0f4924e55a45bbbb07031c9778047e62463849dc3a2d25f5666a2b502a1d3593ff07f04bed875ce1105562492876d59193e79ca4135c387d005cc5e758faf234c8128f740c35ddb4903e5cbd69150b9e5e145019b7448c24b12aa0b60c1c0461fd0db1ba51eb4a1b81aced55864b569fe754dc6418de827663bffe0b38aab8cb0b75ffba296d84bb6ee7a90d16ff3ec936fe547877db655672288790f5b9290ed4f9285947453ee85522da2e033a6f5ccd52c828803d458c84647e09ca9e66c564d46b10c85666900bc5149bc5c6468341b3f737987aa0b7dddd2804e9f5d1f0318d910a14dfc9202141cfd79a95aed2fbe82631584c8a44af2584d8ffb2a56c90fc9f1d97abd66fb02bbe384d339fb7baa271dd04cd23f7ffb3faaa7bc34843f0f4353f21d3748eb401ce0e795831e0db60024f47884def0f5cc6054fd99906ef4c46fb872ddf55d3043f4e6c23b3ad847f8232f7555efc0fe5232d7dc7c5b3dac37be54f9781e030b2d2cc1f41ca1674b116127a7ddc50febe8eb00f1d14298791c65381eba2fb49474523e2ec5307a31bc65171ce3f098087a030a79a70cb844e88d1213cfa58e54904d86d2e0222075148152a7d1a2efa27041789aa4f8eabfcb5a43f2f905a6b01200967a61d15f0ab11cc558d388fd40ed4206662ee52f41930ecf87a0680223f8ee0b1f197e21e1666d19f6f4d5b871c07a92ffb8b8e90e30c16213d6a8d2a4367b5d207fbcebec1842e5dd7ce59a9a3f330ace7e29428cb660a06b3436b169ec66b92bff32f7368608a25a3ed7f16ec4ae410111a226c0194e7aa3a98e7fcd3ac238f6f34f5fa5711c37b6081281de0ab688ee667448fdad44ee54da9cf6a0f6f94cd0ed114f019b5bf2f9e6ed21622f3699a4437136159275c437736875adc7654931be3bb41d2e753407be0984e923fca1b4b086a5912c24b104e922c7ab58fbc374a12d75be1bd2a48285b00365bc3692314369eb1d71b85c42f09d747ee0054c0d9eece12f11596e86443013058c81843033b96f2031e8c2c39611d1f6fb4d384109fed2869b4f5313da0b9800b41ec60907fc9894170486fcdeb5835960ee31fe63a0d21e3f8b1539e938f775b1fed43ca09b0d6f9056384ce40d992008ab0bc44696cdcb99d24ab7bc031b40ef7ad59c7bc93c47bbdfcee8f5643c0504dfa659d794fe8f8643a0bc2f3ac9bb99091af3b0ae2784e23489f71d22f42a09e46b4de865befa2a346de0bc882642231c1f76cbf6094c23002be31429d12222de7450de501cd8b2af9202750593bd38b6c35e2cd25fe67819bd523ca87e5c24b7e517b746a23016f645ec988393a89f1122b62fe4862bceb1dea888c825bd5960d4029831af2b9aa1075c647291690fba5f82dfaf332ed2540c9bf3471b8744ff79882d22b5ae7d318d70192b5a12c4460d46cf1af641a69975b7ff07d41ef2a65ff1e51f36512af357c16c414055e5b3eef7b5268ad64650e4fd8c19f4f2fa3a842d1f2cdf0a49005f5d0350141f9490a5e2061dd03eda013346af81468b1855f1b9718020d0d158973a41b38b270e3ced6b50c4ff42bb78b4a3361b121ccfef2472d4c32592a2a3f4155dc202a8e784b661a1a28d224714853636900f10c37a31deb824732a3d56aa2daf861ed337d7cc89e54b648a78666279d903d475f7d0520561a4a2e1c25ca1a11ee59543d90ee4b80aefbd9d776187edf7bc35b3df1efd2f0c8410d5bca2536c75bbf08f976efdbebdef84cbcdc40916b9d38f1a13f32ce1f679fddd7c24f5f926c1a2c0a5ff8f7a657eb0bd21f1a10a0adf82e62abd75d057b0960a08773cdd04168a40ea6b9c8cb1bce3f101b1fd1fffa4dfcb610905f8279b9d2ab87f2db75757ec60c93937bba28a78d31753e536f04f9e84d6c7411adf693c26ded1a4451d08afb978bbbb2bc12591f520a5d2bbf1aefba606285c479ebe5acecba1f008bc9b4afb3503739bf1e6bec461d55dfda827fb3195dab61702d48c505b88925e2cea0fd0bc86eb7bafd2f480562bfed37fbc92b2e2e3904b07100f8da40494087a968425c85bc743e011b137129fedd7752a66f1dcbbd975e6fb14a047ebad528168f98d9491fa140717bc20dbeeda0c0aa4ee2d0498b2e017e21fcc06c5a8dd63dd56c7d93ac939832189bb20e7816c0e0b3d93e7dd3cd51c79cc27397565fc31cdb4ee34ef9365c2812c341c70e4d312e0076d0154dcee2682a1ae2aa9db1881acd181954a2e1743661cb7a3f524a855c6f05e4629578c357612aad2b23bf8b8e9151b14a008d4cdf7d5574d330706ef158b04f941249c139d7b2aea6e5bfa16d649055a7706e8b883c0d64a3331d08a65e56dac6fd367ea257f84ae464b3fcef0555aacfddd6f6fe9dc5a2c28f2b0382069deaf7cae4c14dde2a4af2540967ab8fed78d2d377d70084c2266d135b61410e1463f212360c290d1c7c4792f721b046033d6ed7acf193405b973818af0ec7e026c80d0a28b9fdc09ea73bfb0d0fecdc347d193d107513115d520c0739fd34ae7525dd795d0a4d6a8bd189042303c3944dc2c3bb913d40a918722ad5f08777bfa45e90125afe3160a9cbc067918fff1847147324879610249f643f88086295a6d7ef1b1ba0c6999f4fe41f2ff9b88d54445b94f58b066d56d405b452b3b38c8f51fea981f4a21da56426c1cbd71cba4dcf362e432481e4bcd317b5bd0a67db67b0a559dcc252b459ea1847dced7c8c8b153e3abe034f70e2b29f708783ac9c8dc073240f49a7609662801c9f89c48d55772ba037be420b47a473329e21e033021ae4da13729695a7bb563f6c277ced9bc63e305ea3bc2b1f9c0e7227099230376bbad2091e51edec9e4dbf0d2854f8e75aaadf13a5adb51a95bc19cf53f1d13716084390d1e765f3d4b296079700bfd7647086e9e01d4e35223a303b9a10fc061962bb29667f31261e7d6d00ab8b044ca292b91ddedfbe6877d260096660e23c6b8c41e883763b636c71d787c019b3eee8665c401178af18a6403dcba1c8b6fdbeec3a0fee6b2878bc03f30a82ea62d2cd60c46524e445205c68cce4a49f145e2aea07be1e7e38e41ccf7e091cddc2ab0f708b9d210c8bfc2af7b4d9b7ab67205b53667c8e7f9274e2eaa3c5e4cff1b6d637b802f189ab81b1528f912e0f398a6e9b6dcfbf2cf968822d95a69b1bb98f6bec02f8b2d4dd4e5ea78dd4648d4e9835c2c5d6ea6e57a633033fef44eb478a056a485c7523348583bfc549673c39768c9e0c51649f6792be29a213655f54ee2df44610776c42223ebf6eccc4fa6eb7f122fab17d82b85941859e556ec947e5af5ed293382485a5dd2e8fe0e34edb3e6a0804e92f23a4bf9892102abd65310a2d65b1fb7147d5a29155deae468fb8e470296c6a9c55f04b2dc6d42ebf8d65e6ceafbb42cdb526f7b1af844b7185234b687b6fdb64a530c5d63b60b062db1a9b2445b6d424f563f784e0ec58c70ccfc6c6ce04c70d2d7e48767ce7ac6989679918446171e50791dcaa69a6125c580e5dcc4ca1bce6eda84dc3539c95f658c37075b16f6843b3f5d306cbeacd5ee5c14906f58afd327cde5c43d5cbf7530ac0d510b206ff85da670d5ccd1b4c40365287bd48fd8ea07d33241f7da4b68fb0b4a4c55f481f24118143c7a52c5ffb219cfdab489303321cd0dda1232eea3ad777538a4a7ceefe8b01e7e10681d51bbb283ced841ed0d0df633d40fb174de1397c9b193c0e638828e82e2c9a20f59329ca4287d61feddd39fc3e476e4d5800cf9fe1f2ec61ee4313409af24d344c38c57c74b3ca3c0d0dab0ae4e8b91c5266605797626ebad7ab3238d4a7411d554c2c233d6c96f0cd15fff9c548cb6d38a7633359294a4c2d203297e0fe8dbb2392385632e915ae5d230d9eaa57ae5ab014b4519fa4aaa953795f9385ff9459b05b7f88f98cce6821e3532f27edf388d9d98fceb5b92c3276c4cd6567375a29dabb52a75da242b7a314809d267959b263df1e6f373b9c77d8861c95569c9e9797aa02b7883e0027637e1051995b20aa1e33db0bfa6a0c679088da269cde95258d64874164f02fe2a716bc6805bb2b5ae2e85f77508f28d09e5a493fc7c4b4ff811d0b205399221532c678a4fa0b95df74ac3c736a31654306a9dcb4e9465a15adc26c57442449a8c83319efdecf6d74592421c0860b68aaf60728912ece1ab8daa507e32caa501484fed83c9a5689c2de2ec0af973caf23d907b403a64b6b1f3c21c2e12fb07afb69b89aa6b81718dd20eb03e086e3cd27a9b64ae9b033b693fc4e1592387b1aaf070269e2f716cc694246582a7a7056c2ff46985a397770620a0b33c81ca8c908f889cefde81ab7830d7162800443649c13961076ba66bf3ceb306c45cd5848f8b6bcb6495f7013fd3e84baad8350d369f53c105739851238a9f0c3bf7e03cde451595445093100e63c376aff60a216a5d51c43ec80bf1fce647237a7f22bf19c83199388be072c3982af0259066fe01afaff41a285f6d990bf365f5546d455ef7ee8aa0c71fc95033068d0fb35e36278a00701525151b92729f20716ebca2a2a91aa73b8b3d7cf1095874df4b74ecbbc19523818c603877fec492228aaabcb49cb0c5f8256267b76647002d288a0d23591f620067e987c241bcd08ce276a073e21b3acf61331edbec6bd129c2e7ea3df45be5808109dde59d1baa6456912390779193ef5177f0ff4e977552ad603758a1373dd6b5094a626b55de0f336a058e390abd183a49cca8a965afc770cd8279be614b1ab9d1e3cf458490030411c2e1f2f04edaf1ff1a33d3e08961988b2ce49c2b0636a076cec93e6b2c90520cdedac14c4a87443ac5cdb189a42223ee196d21483486df2a64820c35ebd7b680b1b7f6a3d5dbb5756a7ae7d79a82ff82ff440b4ca9afcc1ed44dab63bffc93a3ec0d87697d2290374b472749fc19a42ea1db1dabfc41cd085fe6d52a1a557e8ba08e9d01c7db50515c2a32e3c5891a7ddae9223edb98edaedaa01915330d2b84e2e27a2b90ab1098c149a81b60b522e094923e158d9fee0f9b099a281d6ab3e9445ef4f4fd062624736cc9e620d256743c62c0d387c348c2fb60453d74f1a8a6164cc68b81077cb0d29d4d2846ae974afff20a6eb3df9e464f66c47b4bea18301c2eac14adcd9bc56eb6a12c7b6bf45c0acffab7a7b1fe70d69f6ae5093143d7dd900ca021ae1c97700ffd1b16617b151e8fcb7ed40af480580c9cb4a4c92c69b383cb88edf652feebd84bcf52b30df8dc58f485d0e74a8b2fc908bb69f2271a9412c9077331f537765b857be7d6ff48e0601b03755eaf7e5d2434077215131d55aa3a361ced31dd9f7d055e6cd589a5a48a2d43b0121f94b6a8bcb8353526b8c16ad83f7501a2df9bb237f25fd05bd8449b295775fbb8b6abe34f0ea66038f385c2451ea912db50134c77dd904a41ab0c54c1173434742c690ed384ad073ddc05144870c08ce31fdfb5b86ac5fee2f9e49a276613da33d682a862fa272236ec96c36015446e356d7adf5fdc197d757b7d7258eac95d90481973785b6471ac802f726e17dc824a5860dfd60f98f253aa6bc8376c71bbf88e14fcff5f1b0f473a03229308410fec36fd004946a8b6808219e95c75fc36e9dea6cc242d4d2f076e3c17bb0e7602a8012afe9450778acec6ecbe39d865b8e73ae85f9f3c448e0d7d5ca2bba4fadaed67cba29b985e22a2442daad690380f751dadc931ac9285a6054fd05e01a8ed801b0db90749644673383fb808a8adbdda51000bb08c522fc5768e8566b4280b0408c36d9a6d12ef4bc0a31095babf53c64793a3b794eac375708c3769ca46786bcc2d90d8b9e2f8dfe18453cd24f1a495fab30be5130fa2e4a5b1d22256f43176c4a58028ff12eaeec070eb35abc8cbb5beea7a0d3e390864b13ccf18d74debecb0f12e4db6b1b2608da8bcf3c4a6c85eef8db244b691d5732cf20f0d8aab1f5b8aedbf4777b4f4801a491f40ac7f5735ad60c42689138cb2d648ca60fd67548933c869d5f640aa29fc533c928911e99d375fd8a7ebe28309376500c48970cd28c020c7f3a338fe889e1766b276c6d175e090263edb794ac371a1a22b9adda656a7d3e3712cf20dd1413999b0ac37f0ffd3227c40d136a78c74592ad3d91e89becd842ab08a3d0b16d0ea230c5994ac0bc829477fc6e8891778006c50533b83a68542d30c36aa914408f9190ee72b73379b1ee6cdb9d8db26cb50233137ab3cbaacd0736f5ac3995804fe4949bc0dad0e1108f96f39c07e1b63081908ebbf9576235bfddb489460bfb94217d09d176947e8d50dbf4dca0fff975254a3b275b297b6930b2d04d9115e6865208cf1097a0cd1c41df0f8ab22434398a976fe8872c37d4171202fdd969cd66959ff487001c41dc62dbabef24b40a14e733f824bd4bc401cf0961aee662ce2c08e7e71dde9da5e7fcb0464ecd7c7b652403d712bb9200dc6c4b9dc80308b91101febeeb9d0ee049af0670d0205307a09d386e1016ff3e0247e563addfe562bf8f1b589a605cc713459c887111c1a6bded18b0a33eb3db58dec051796312261028fd66af69e9dfb822439c9a0274162d61a1a4c72cf0b2208404114838055e53ff0fdb86856d3c6e4a28365056c9d28b19ae8fcdbad74e631bfe053aad406170f9692583f09452b3f298dc7cf2158b4429cedcf38619e620dd60723a056213103904f39505fe4b956ac0e7e4b4ce3735d2db55e2400d27d5201fd2bc24cf45d2bd8878a0838383450e6d2826565c180a2cfc76cce131ca61a05bbb11fbdcb8bbd67e40d668ad90fd0a11ac827a5d07cfa0555d2b59b8f2f2b4f90e833ff119da56c87108581c2d9b5a7a03c70c39e1005f18f72845be647e7cb63c6bdd8d58bced3ed7df99f355492a32ebe824ffe90b33e8bf8eea189c64038f7870ef9bd1aa9fee79a43dfb806d11e55e24610dd3e0a635a2d039fc55e449d1b62f22c1662f619891f0ab0ea2ff76653c17985fd1b25881be516a66449f1832ee84562b63bf104f8d327b1e56bab6d6e14ee347a64b5bdcb23cf1695d7c85ea181e875c570ab971cdf2c80a15fb49758caf03da703063f19abdb6ce6e4356fd1c131efc9b2cf1d3dedf968608391ced7b96ee6d071c6a8e38ae76cafc922c3dd99b7eb5c25da32c8892435d119e8533fc3bf65ebd02015b7013b82996ec98d18793a6c43625f0ff004762ab4df9c04e83b4768ba6df27a8b407f5149907819b5344f0e4a76c433ef88880293866ff353ff4f4b5f35789694ea52008808e815026fa225f0cd3636f65846fed129935f410b495045dedc4e2a706cdc292203e0e4188a27690bea5f86f22b7b5a857cee935cafb9dd50ab51b32c6ab96a104271a12afacbd795976718602098b257abaed5d77d1eef63650f2db3e2f815b83a359f31a74486571335467c4640d5257d731515bb0ded783e589fb8559e3d7f17f455115860d679930fd5984411c39c2df51e320acfb8c2b8249f0f80501558bc9d97e4edc268de6f98b60905f169ead6517061521999f68d1e38c251ebca319e222f8e48f084eacc6ce695f847925a83d2db6b75ac221d6e092e7338a703e66f5ec3a3a4930e1ec40a7805b167b1256af10622fcf44b11dd191f4f145ec0ac5fec7c99845beed924138ae664b56fe112ca3ff0a2212a043651ad8b579f32710de1f11856dfc12c6be0f817fc81a0e9e22ebc75806103faf3bc26ab1854291ba3ac0649a8aadb04452e18456af98617a3fd3538f315e060fd875bef2fd0f58f8c2bc0d134e14f71637cf880c955edad8e8b611258b9d0895cf2304e46380b28b1c598043e82dd57e44ad06bc617f78ad02b205c9a0d292e0ccb903a834989365267713e54690647ea678a0e30f8545f43420a700b965204ef96330855644eee547351a9153ddae1175c5c3a49615261002a89a3c7d28061d6870254bbfcf8c4d3f232c3b458bbdf452b4ee34f22b3848515cbb8621ae96977019660a2a566dd107b2e69d8f88b8b63a3a3db4c384833cb5dec81772ed77ad64ba5d2f574c2cd0f344976ed58deba32a0351fd15b151547da3ee939f239e01c7243e77da80e84b77ec27a9ade9e837aece030d51662288da86781348da2cf9e4d91fb1cf94d77fa9d82927fd506624718b8cb4168d64f1be9d785abca0fae1fe967e8bece4a59e416fe8fcb06e2a6bf560d9025878d4ec80b8a1a000f3460ca67d4d0f389276b674c70b4222c1c22c5bfbde3e51faf73e1d4e1457d17d911bd02e2e9d96571a739353a1a39d22247711eb86f688155a2726035b1db07adfebdec94af9755e8a48273d18c61d81e6f03afe27cf96adcada8b8e2422475af05eacefd52e17a3dda8c655a41d9b8300f1e6f94d59a743ec7ba5245af0517981f8d9e6f8baf8b86a0c82fbff35b84fc993861b390bdae49f3c4c73768f4106be0437f20d00e5b95a3cf611821aafbafa8c73e48c18a7012c4d20f6cbe87e71f5c6fab6e44556e79f75b643915068073f7a1d94604f7226ad60633ed37bd4ab16e4415a7b80d7903b448f9031760807a180d55a7f1854579b5272b11957aaf1612d02750c1b0ed4a59f6a0260da01bdc294e39416814845ccda753801cba9e02968325471d550cc2b2ef8c1fa0fc8700126355ab49b98e12c2b160ad383e59a00692599dc3a35e8b9b4467ad4f78591a0b8fae470077f55f1cc14fa24a682c61762b12d9f60de3c89110c2408f3a83c4a2a9c87d0f3f111a71d5a3c35fcd49da62b08414d6d34a28878cb2b1a625dae9d88dbb12b6385b1ce48e25584b561963cc230d98d1c2b6e31505279a1f0bf4dd9f44a5b50108abce5b4903110cb5afcd61c89c91f2ac4be423f7de443819a9b38cef2b501ad2a42123a943856ca3e66f327b4030242bd9639c4901443a99f54c2c08d768bd4bf755ebeca478b2aeae56c936ac081d81c97b3081a4a48d49978870f31f7f774a63f0102348cd06621a8465bc756d11f7edafaee277d33e7f2c35ac543f025e1db35138430a298f796cb30bf208294ef18e18ed8286233e477bd1e21438a8088e5b88fc664bdfefad96158f5f8db305babc207f361aa787711edcd0040de5d2b60d38652981e2f1346fcb1af0412113ce3b1340489010c7f373f1b4218e146858c017ebd9305f3524de2fcaeccdaa814c7dc88b7202fcdf6aa417745bc1ed88fe3e2bbbffdfeae08fc3196641dbc86d76238e96d608cc0444359d7de34765505d899cc0d538bf10ebcdeb8d945abe5a1ce710b676394190b80a76b7f6aa56ba74f2a7c86a6157ce8890a5dfbe0a62ae28ae7347f0bd44d3b67461b7d85a6739215ee65c9f304f1d64d34baed53a40b135f1080c599e30fd504f462631e69f743e766f7a8514ded78feabb6ca781789b2eb83ebe056286d212175c9e08c0a5f1991b14825c501f3e7b8b59e242a11768ce43902711722b383c7e61e150d4eb1dc3d7b99dd14b1565b2f98f16e59e3f6d3506d1ae3c1e4f6b310da49462744bf307a6b1d361ab45835316aa96ba83fe8e2e5d5e2d0b836b396ea62de9b5f83e1f76cc1fb7adc9c30983ee0fed148a50489cbd1435bb0d69d2d8ee98754690fdec4ad02506ebc30f255bb96c281e1dad74ed2a0ac3a477f597d961b2c7bd0627a42fc11d00d04e5b59c950ca94d00b64bc0945fb3dc5e58241b4c3c13b846b61424c6c86c751c55749fbc48a2545b6375a0b09f5ad8927359de381f0c4b1294a50d8cc73ba487915775640afe2c3cc45a7f060cb1ae44b2931ac78a2e5539b3ba79184d775a5fae5df3d433e32c312df49171bb3c7ff1206a515562446b7a5a0b399d22247ef327a5487927fccb78aee4d40fa26f7c0757bccb233d8e2af61d588da5a51f5a00f32ca59af193844e11787f86d1fc968412c1af469bc8cccbfd775580dd646523d2fb63ba43e608908e372216bac21f61bf3a359da80ccc9fb3689d5fb058f793d2bbde05e6f1339677b48a606868818db2ed04fea2c00fc730f03586e116eba4642c404df12f6a09879ecd5d620c7af27ddd8e5b72a3790c48239ab4155e147d0c26085b1068686cf17b89e8f62156670e25f1d1b0234a2c8c2dd532606c3f2b8940066deadf66f56a922b8c9cf60da212194af4316cd4536a0de98198bfc9388cc1b7c5cbdeda337e1ba003451175a7b390a11d9e4040894ad34eadf3117b6b3861d7ce0de1be0a2404a40f7de0cad44410e0f892868a392ec0090cfa482ffe87de1dba5402550fa24fb045ed2efbc9461e05b52ae48b35f65185464cf6b462e063f4f2c6063dffeb4848b50b52a0a14105f89f0aad3dac2c35466ca7af93e8723ffdf0271dfc73506cdfdd8320a28137221b7fcd608d32ac486c2909c1ac125b05ae3f11281019adde1c1329cb3df59849f502c88f1bf3b9dbe218cad2299ee2b5cdb14715068dd52809b001e98740673798fcbb4569e6b557a9f6daf4cd90b4b55f6e151cc2fd4b9646e97136b108fde318263696076ee49074466211ef7c49c13ae075e9d83845ead3cc005304d81fb57cdfe489f2594235f94478622c8bf8383ff6074a8daa64024811500fc192f8c1a925a5db63022e2821d52dc8c53e41c5134433693b7f67e0429f6d0a93422a684846bfe3e797c3dab31cbd0e6dbe76f6788711d335c3bdc070c8ad0cb4ab20c22102d82989e9baed187e4e21df4b23ad043d91383869311668ebba68721586a097fa504823f94abf8bee2c60787b4e58ed29486b2290f005ea5b32ab0df41b648a4b8a214457f8d006b68db587d26a474d55acfe7bc8bb9a795e2c101aa468001dccf1271148b7818fd7728108d3e94ab8d42ce7ea4e38e1e8429e985c08a5b5e2112f514f35ba2f6efcbc52b672a1a6a1d8bb31a3353bbd89267469059d30207dde74a498105c20be18992bc0304053b1c45aecb1e82d652fd88a57e6d875f16ef84f3e627ec84d1179fa9cf46e212ecb9728c4938b398fa82b1517b33f1940bb31e13ee69adc6d9b1ac1c4de8c40caeb788364dc5bdef3805cb6e2ceef695627b21b443c45613a8bb2f2ed5a0f2efbfc2aa0d428179a308318c8c7e5daabbcc30e7849bfcc11c8db56ea1583f0de3d322cba806c9249c5542b2c9877ede25c23475ab5727d3543e7cb7a2037b9356ac95ce4e7ba518385a5bb1a752e9abc41d73b3f36286c2f0599d3e9f23895ad4c5c4b442811bb430c470e634d0751a5c5eb0b4eba30f482f35b67acb748c7a7df179c043f661a2a1531e30fa6a7ce50f9c2162097b0b3d168abbec56b36d2889ab4a8af033097c9aa15182f3f3baf6508bb669e14dc0fc26055f4699522b318cb6fed27025105446dd3c9e62d3d796934d0fca083ef3e54123992cd5be88cb6aa4acb41ef6c20e394e220d368258ebcac7cde12b2a3231441db814d82110e9a078cd9c3afa8e7cb06bbecff84ddaba5e21d0a20f66938dbe31edf9d6f0b28b8bb04167eca3b2ffe703a230e8ddbd4bcbe576903cb061a907e2d01feb3220be336bf9640965e64a2d71ab29ed35b0bbd0db739a0c3a686f44b2d3c4538836f9bd32c4921b8867036bf9e0df731b46458c0b24100ab9daba55026c39d8ea78eab8951f7248d77b119191f91efa5d7b698de7e209089d1b452f538762a8b6936a7da646460e43d49d78424a3848790bb9c2b9bf641b1dfcdd9ab695301333bec634e610165af8860cff21e80f301cc0053cb26351fb4160aca6e99fc9c1717d635396a8052fa97029219c6570642c519ca55d40883f1a49b1b94e42d44da967bd7983474dae43a0d9c106ab6957f7bd7e98f919cd87e29f46b4394ae40724246f783dde38bf5b286fdaeca31981fc047567e7f6e353833e548ba1ee51ecb2cc6f575737568f203bbd412c8642d36a14c10347b3843afe9079b67adf8bfbd888633c531ce524aa8c423e4c106a287a733c2dc49892493e29a394ca38c289136c34fc730d6400d66e56f5befb615a3cf83910b1a35f704e7aa114a843f0bcd23fad81276504b8f31298046e11e91332dca33890239c82d924439f258e00e3f4e621b6ea4a645fa1c451a0383913cf7b9f070861a92b4b935b6a513803dd7e65ccb44ae88611f7af66ec68ce1afc575f9241afb39943550afd4e8cb67ca4e6c5cecb86de427b290579a207cc7d8252b27062dffab384ba338d4cd6fc2573f02d4a0421a51ed218b19f2f0fe3d81eb819f81b4c6b735a31edbc59ebe5eba4b081cd151ea943052f72e036b76b07a70455ea383f291b6904f1f9eab6c793b645c73635108abf378a9571fa6a505de763ec58f9195c4264ba895c09c3be0bee4644b65b029d48f3c48c16859f04251fd2bc24d5d83fede56fb05d8ea311abfb620e56b13fba59fef5f22c98189d3a94cbed22b0bf73396d9ed71a986d07bb75e56f6eb4955ac218b1243c205ddc5b6085da66fec5fbc41953b27b7b151e7317c3e6f9fa9006f0f2f89cb53092486577244abfa99065e705a39e4ae5e2f9182626c9c67fb0c3c17870e6a3a9514a789e90dfdf977865d22ce65218a910214de41a7894766e9d41b80aed92573c0c12e1da0fd1592a78bcf60f9f4fb0c5fa1cdfbc792f78f9714ce6cdd692c76e05684158b827c2fa26f03a9dcace322b61a76180d8e91063a8807731bf46b5a4f05db19373b88d85e5ca06893277c57bf2c1d781aadafe2256b599f2103d19a9ebc7c75c42aba47e25e31883f138f7e5b214a3bd32ac84c41ea1c60dd43cf2aa7402729e7f6cd68bbd47e26426a839c8aac6faef6552267d0d7894bf5e95618a6d7bdffe5c5ede31515e1b4ed325f5f1550f8886138ca405ef357bdc79bc0115462404d615762d78913c8f9292bc007c9f9abde4ff78a6cb6f0d2640ad4407602afd3825f50af05fa65db34e8c8ebe1460f776ec01e512e4b64f62b81b3367c1572b7489416368859071baafbce97d325940304d6f58029294e354f9e03f4d03a42e4cd0e30b88c61d9399fa25f076f9bf896f4733a986d1d9a63d17e26705f1a681a36bf5b9452383fa018bc48d01a3f9d8344204c989ad191c1b6ab65993d57ffca0e9e931f34883821825e8378a74e824e48ac94607602f5f2659d35d41682c275c861afa189b898101bec6e1470a82a16ace3c95a03e878d8b76c6dad9578668fea2d0952778403f082a683c5df519c0a2580eb9dde228e0284cf84c2936d3777395d49a13dc50d38bacacdf6cf2003d309196c92c17983a179777451abc874331d342e02c115ea9147a565968cd52749918105793517e9ca35af49a18626843b6f1a63fb087e3b4133f55cf2ff79ad6b113f40e04420aa1cdc447c6b3700b6041acbc2a17fa81712445a07f070db66f4edb4408a8e499fa7cd821908bab367cdbe5f235e3023c588d872f3f415ffe0c461d2faec735a339303c63e74f57088cb89775dee600eaf9a53e6c9608dc9a8467d8bb5b4eebd69c2ed440927c45337184dbc906963f627a13f13185898faf4443dd558ba30fdcdc4a6144333206c945d7db4d5d1460596c31b2c00e435d6326e95e239394fd8e9bf411cdadcf6f9899195b0898ae37cc25720ad74042788885fd66da309c6b13a0d73bc0678f0afc6b0a71fd7414e880da125f5158d23fb53567db4a42141a121e7b2326c50d3ab3ba34bcd2032ebd41237c056575d26c85c426a8ffb575054ea3173f1c6833fe8582a10a269bbaca3d082ca1a52e5553c871f0f82522deb356c416e88538dad8e1b4b912c176413573507db238d1a725533db557c2285d9304e2b7d19ff4c4d24fdb48cbd9b6f19d9194ce5c3424431c0fdd583ab25305ae6cf6b25811edf0f01f7224df67873b293930dade3e3672055224dd0f9ed455cfdaf041268c8f49a7beb51de0aff27365b215a241d71f41174257eeaafa0a68beab5a9ddab171a9db0721ac17aaa90242b6943c394feda96730f8f85312d12c27a7a88e8049486f162b3ce5b02111db0fe360ca416d27796f25d9d2e78ed1e5ebbc0a20b4083120865fe96daf9125256bcc530a291541fe87c72eab57e15c9643d2a61e4db5cebaca8f52f9a85a0c78a40ecafd1526a927f18039af82075cf1bc0bba98f7a00402c1b803f4c574f6d40db76cee91506d0da21402298b2bef6187aee3b85f7e153386580b82914a4b3c69cecdbf1a108c70174d933c9db0f54bdf8f546b514fafbf0512de6d20d780a002bda80a5726055bf8a28603219d862fd92910297e4ed0227a3437e42ae3f78e8e6e22345dcd87ea404d0e29f6818bc82dd1da4109e030465210fca758bf462a474af16fab51128c5cdd66808b9e038eed7cc7ef9956a00bb082b1c4685bd314aa0ba2492cbd7d10bea7099d18d1d6a377bbf1ea4db13656f600611c12a84cbbed41d571aeb9934036ed40536fcf813125b2ebd953e25bbd2a45cb7a24ee5f6622e8ab37465619bd178175df9b7c95ac64367378c3cd0140069f1a771f53c1aee04e1f055038c566b91b95c7ca22655c860f06904f68b7e18261c129e2dc1066f3675361e66e7d2ab46b72a4c2a377378da04199aff21e80ab70b35db6770582ba46656d2137222a01a683821b72e589c8aa7f49217a74c628dd2017a48bdf83b2f3462487b2606f2f47b1eadd7abaa23616f5adebf77a39d3d323d14938ee88ceaeae23e1b4c9b3e3ce1f472632f5b5f39d76c946a67980a135458b3ccb1c9509dad37b522981d514b41b4c131642b50067fcc118d524317d9e9bb442ce396a86e5b428b6a40b0e6e9e857065be7c103a77ddd2532889c1354383e1f9c160afe5590bf756109964587b7c9f3d92666eb9783551511848096b8d8fd30f17c9642c58cee5c0d6de55c522906e7c45d0939454bb44fbb47348db92fb76d51b2e1d7b32abbbea0d179d49ee3913f8165dd8456d2ba61a5000ca6596b3b9f3723523e0a7d82bfd26912cdcd8870dafa7bdf5b480290a8a735d157012a965415686d529745418d21602241402b778291d72f7fc183896d77ee248d3fb8b61648c9c9a8e19492e106ae93b0cfc4306970a53bdf9059f151b3001eea9ef41e1eb894adaaf54a13bc734fa0e6f6112685be9f080717db0122d99faa1fe97e05fc1c57e6cc110280d9ab0dbd8ca86243f9fbcd6dc56ad9e77625902dcbfd7b5a172cda669cae253cbb95c3c97c18ec3584f70c4491f6d34a2c55bca6d25d7c99801f94e7661c0a03320031466d9e9adba4e1981b7261dd844d109016712db57388c1d35d1b999928634f84d5ca611cac5e53d2e2039b2d2c38a0d3ca438a9502964a21ed1ea5ed8c044b3e022a4aa02858f29d365559f77471675ef0755b25c5bf04c2f9f3b1f39d920e1b498a3f92020833c2755565baf15e96b2b266a118d40b39653c63033d31ba1db30d33b5f2e9237b2156a46bbb2c74619f06619a53ea93bd56ff1fe58669fe7511c7a990ecc1a5ed2bc9e6d45f87e5196d40abc44772e37ff7f101988e8a9ebb9ad25949d694f124c02153cc6970290d864c9fc72aeff8e522298c4cd489ce25e2789c1921c1422806bb805c617c8981f9af5fe0e4e9b95e6e60fa69beb19c5fca08a0bea187eed957d3893e77b59a91fc97d7290f6c23b4e17520f9430e3a898c7259cf76caa23d06ccf16d36e0c7900dcf3f1c0c6916d425e20f7bba9b45a1d09dd87e58bb85a621ba8d74406a767ab59f97334fb7bcfe113e966f57ea87b90f6ee7bf4ab3660f2436c764f87e32bd6a4a5f978fc6e9ca350260a4c1d4d1dcbcc7c059ff8de670c2a5faf08f5cea1d9ad4f931ad45fceaab8ca38719bf8ae152e302a7e03c453bb3338d02c226c1776fbe8e696def33cc9a8c9000c6b680a5d20055aeb5f01948480f2206be62f668247ddf0c7424ca4916f324aeea057e551b0629edc70cbb75059d714fa3f5d707c4602e27bae0361062da7c6e503e62ff3c30b0d6ea39534d6c23d2f89dc49d6488392305f499865b2e861f20775641888c38bac092a1338f422c4e159c1e1473ff24bd214cd639a5fbf5823bbf3537745755101814a4cde63c0db6511d4390fce353cfa12700aeef529f39da47d3d78b77d7ffc08871e5061d166580596586a33022568e0a95cf7736f611b39b315357615d4a3f6f86211409cb0cc64ffb35760123cffa28be2fd724596afd020fd0606d159ef9d5720fefe5273afe00cc8b7ed5865a5d488980d67ef2092d5bf331f2fe24071b68d51254e57c1881dbef8b4bcddfb4553081196965fffc1b05b39562a191b41e6ad1551f9afb575c21efbe8616e516d9092005a6084a09459996d80416e9ceed1877dec58fb910045aa73407dda5e58104366d3712aa903d766c352b84e78676f30cbfd99547d47ca08c2c8728e5f2ae3df8a8312186375af03aa99dba2af73c21f87ad675dd8798a7c997a65a3ae958d01b217cc4b98bb08a3abbcb77237092036d9bc43092367d4d0aaa905cc39e28e11a72a5045123d211ca54d98e669240dff4883fa2de45daf38c752977fa811c1b55609c8309b3550a367079c1f6fc8ed12bbe2dc4c9553470a59d54d323420762f84022764669e2bc16e67d0bfbf723c270ade8cbae829256ae252aae31387d096eeb18989067ff40ecf6d9be0342960df95780ef81a84951b907fcdbd61bb4d88492c590411462e5e1639a5fa251c03212ab94e4778ebcd48ad5df206b2fb4fc4033833b9034bd6ead0673578d551691020256fac43fb95aa6ae497134ca08e7d3518057f3261387cbddbc1bbffe402c25564a7f2af46154124ac783318364a6f22f293e3a82528bf3c284babc3d064f142758ea9d291de32f62ce0008ade2de0a150a61c86b011430a144588b7ddee8ac29f9fc767a89615593c7cef9a3e02db71efb96210ffc816057bdfab5fd9edfed807ffb3cd84a99ed564ecd5acbf159a7662b9e83f401c9489b65f88d28c97b3be308c3119a80eb23877cf281eb11a6be5e765cefa15ac4bcc8dacf47d8e483877609393d19f95c9a51932adea06a1ae4cb3c0ca4867dcd8dc487acf0cc0296852ec641da9d7c90adc81ef02981c82e33da89718949f34877cbe0787bd80b8a88a08b0d6c8e100a911a79aabc524e257890db7cb927f4305ab2683ec9d378019880afa570ae15f6a52e8e170567c9a91092fbc294a7bf71f5278c5f99b46b78713fcf3b9810f421acdb2b014059e9cc5627e96660bb8ceb2dd172f412e4f6c614bbded915c71ce29d7d6eea3da7cd166caf4ce413a5f66002edf6b1d496bde3823c26117940b9bf5f4949d6ec4df19310a57bc43a6ca03b020a28f1bebdeb251d164edd02169a57247af1122ca37431883469aa901e50aee0b69745fa977c45a9477c1f45acba96be8bf42a68e4a48ac6ea275dc87d5647e2e4bb616ba6f4586aa3a8fb3deaa7b8f082cc90e104a1611654166541f14826d253082135ead870a659606b596b8adf85934b0b079d1e7a348209359f8d168b15d0ef918bb3dafe822f91a9131008bf9bc199f3a422af1388d5291f23219001a3f3d5c5e109fc8d4ad62a06c63db3ec3d1f1c59bd61ca52b8e4f8c4840570a2e9e26665b3d8541ce32b34a5b12d9c425857fa1bec75d78721537a4ef452ddc253b79c087a28c5bf338978dc89be8f76cd64a252ef0e7d46944b1d89efa1889ca5c81d67d660f50610fbe71ad304d29f313fdfd898071a4e0562cc3b3a65c72a4341a59bfd92fe192d481032e53482eff4d94565562c8f75638a1ba60594a8156699ae300377c18afbfb4f302ea6b3963704a0410b565f7c971d249255883432743e5001523f663eed6660591eaf966200fc61e1b87911d4c7522d50ec5ec51c15578cf50f497b113e1da18444d50dc7e723f0363c352ed6ca3406f959e0f064827e72f9c80f923804a3555826927e11609a0eb522669404843073b098ae0603a2041c38a2f4214289217cef2c63ae05beaa00c4b77f7620750c01f1003331775706336f448ad32d03974ea0ed155c22e2c0b50284ac48930d58be043c9628b8faf8661d782ea9437cf7dc50820019faa5d8c33e845ac65b064d007cdf0aef93ccf25126d3827b7c60497cbfaccd4530e981976044008879c184324b2281f396bea0b15df5aef0a80ab4a74398e3110515c98a8de0caba4a43eb33607c9a1bd0776134292c039741b8e986de6cb8a66aac1ae7bb9472fd97d61c6d8b84070b1ff7096d4fdc52e66d17ea857ec064a1b77821eaba5c7cf25dfe8dd9188c7a70f327a4386953898c50515c5e07b0c8d6e20e0c40e749b414ca0dc4d78f302122a0d60d41c0d7fca4c5f851b06e55b183f78e629b3fd93c1677a707fec77251ee896bc52d6e13432c95e54ac7eedab5a6d4f4bf009343e6a283a80128dc03b60d32c47e69ffbe15c3cf1539a46079ebd954b9b07ca34564f3051b25957b3fd9c146a43835ec85a59bdbece417978db00e2d86ecd767eb9db491a3f74740fa159f871790ca84094bfe32f747885cd831b693d2e63e75e963254a313fd01e86d5d29436397bf7d69a7825a8084a5d17d7fa6f130b642e0e61f86b346e15fcbdc868f4409d046959b16662eb78ad806784ba3bd0f467acc909bef00a7acd467eadebe651ced6543c8d98538731381fd0a23cf0ee97170830e471309294933439d001954d21c72ce443210703b0805e073ab3bba107a02a78b87e77fc3c2384222163c35a4c46582b2448b3c808c48c862f1a2b4ccec42968704a2a951d320da2c846537b0fa1b5d854da779109d7dff4dc555ae775322a750112438bc976a3628945153369e17c1264478f227c0020a773c6437c7999e6764911aa5d8527567ff4cbdf0143e23842f5ad3c06050cd6c4de29450a9f2e0c9d248992872fcc9ed18dff47fc32527d0b6fe889e000d54e71ce5cee2188dbf37fc98f2e3d34873d09982e4011564bac12adb29f0e5790b47d2812800808aeb89821365525dcbf8bab4ff7737a89f634a93bc80acda41e358e611a024f02399520cdcd11d4486c0908e134c2f3c7c18f93a5bda9d6c5e54118598f93131d9bf4631b0cc424b11f043741ed6204a8c51808115b9e4b4dee2d50a31608f1414c3fc4de83a5c7f1687cea2eba3b92782603bfa73a54bc78fdcc752537c60b0f5f1fbfb4e05140588636e9918dad3f48d75ce931a02f39d4fe0b39a7d0eaa3e224ef825cb7d94d25380e1fb87fc36c760f3f6e6b62e2d52d4d1682f8e6684f49df629902262fd646ba2a011e70d14f3ac26fd3767c10709ee101153500b44e422a69cf21b5381c6f4b15f724317092d9fd7af23e6e97258ee7220020fde3f01c465d26bd888663966848540372608437fcb966a8d309a7d75858d131e72f173b6b85f1a27c80a88328a1577b68d201f1a77bd0dc113b061d544978f06dfb4fe840c2d8981148d625c85dd069744b55b65908bc32a4211568bbb20af3525d106d0ac97a0ec3f7f27349087e89dc5ed7d33f3bbfb5711904b085b6cb3e3346e0c901a641874a6de568564285939a19e317c7b4aef66b744d7ab0163911b1e911d7bd2f5ccefd9fb12a298240ef6a837f32714e3709c6e08ca9216c981c9b1a582984dcf0059310c09c55cda92ef77a329a8e86fcf0f744767a6c4616726bab748c5a6fc32f1a05e45a1d84bcd20c2f0275849aa97ad4d3f0451d0ce73a04a45016f760d36643afb9eba6d021ecb6e53b912cbef11d1f590bac099b3e98e8fecf332bc009c7ce15185796d3d2f8acdcbff84a3b11cc89eec9296df8fed6a231a8f41651a673f14540c1190baf6382354de66ea56ab686d7361281573ff327442f7a085429844b800364a662ebf996a6a3b21769b9cfcbaf41806d7cadc138b57b44a2abdc60a731d9c54a6c8323fcf279c2fa13c5c8e8b97896cf3c54be90ed130ec753757511ae6917b772dafe94f521461d63cedba8242e1105f28a261632a30db863d7e29e7835f3b71168cc8405ae199b538d9e3a5d89d16fcb1a87dada4f29fbb52b717efa0930a636924b530aa4958d20941a66ef0564d55ac8f667807bee96e083e2d6bdeff0d73807fb2e08a6df6154fdc05ebf09dd2603a9a387f5d6f0d146676f5274409f10e064f8d88f54a6cfffe2e69b994d5d69757d57c921983d16a7d0806cbef21ebad0b1f139466ad8b0c0cb8a54e53c3080c616b026149cf39c3acc29e759d0fd6b54dd428ccd182614944a9b71adcdfbd3e8a95944f79b1337050feed6ce72dcd4fc6b36bac51a05083d62406d0d4e83041632a9e84d3d0ac4ff393059f7224bf0ad5a4cfcffba0ae9660ae3f350354522aa3394da4ce1383faa6723b8063c93262a593a590d76d00f38c149e69925b9ce9ab15c3b5e88f94a3980050ba82eea1d90ff8530787e72564125ad5d6c9a2347d4d1b1bf6fb712fb96bcf2c20116c762b69449118546829be48b8d980e1e04712d4c90136832f2fc03bb9ace910446bde292d37e0ba2e228b80ab527406c460004db2e108a23374d737147b1cbc22532b8cac84b1bfa9a55ad8cf29fb11e04b18f9834763279aacc000b88650c893678a5d1f4ffd844f7ae79e04a33a1db7b89e7a4cded60883703d4c430ba44c7990f59b5e0e1537afb6caadc2d649b18b19c4b236314192e3ab8c5c62f007341ce35a86d809756859fa14afa6184296f65a4c262b14b9bfef9795860986eee85d52d25ce6ad99cd48be425b91d3b6a18236b5a6b327532a26d2c2d8387a338169fb882cf145b7e8434bcb7d829f22f5e954caa9e86a1d7bf1240feced94f80e00eef078feb402f008ff05ccb20eb1a74bd61089d92b519064ff5c2176cf5be544a2bfa705ba62bafda1c7b1551cb4460bf0cc233e2570b2068cad2dbcf92b112521ee8eec2d9703da3b49fc33fab217ffbe97573271b867b7e6649fcfeab4d359ac0e8b3d37b2eb1bdf4fc38e5a8e7734bc6e282b34fd6d292528a9d92740468988f9a2be8e47a838d538a0cb08b914e8dacb50c9e996e91cd9e63aa1a8df0533a60289ac23c577340e6bb490676a1f1c23fc8ca6f918049f05425f7d0d240f1562f8e05ab891bff85ce6fe67b7f265e1903d2e44d3c9a030cf1b61d62ac6cb6ea29283319b65cd3d1ed99ee9b00fa0bcef7f83076494857a1aabb73d8c8dab2bc97452e2a10a1d158fec1bd1babf8a836dbc0924759b67a1168ddea7ad06b73e05390e8c92b79703e6def143f28365b62e3c1e0416281da2b8f3c9b3adcc2398b0a7a4a75d9617a8fa5a27a5e9123056b1adf2fee36342c98329970c01b0e9271b4012653d4355ee0552f0809c288fc352b8b71bb2b2adfb224fe3c3fee3f6c9c236781fa8f452c1a3da1aa50fd8072aec2af1fc479ead2505224c0e7cdb00cf9712e282d964a6202fb6732c77bc425620b1a36a5f60bdcab24b99a5a453c5882783a1fd8aeaab85865842dd03ee87b3b4b7713b105ea4618ac3563234e2c2d2a249f7bc63e75ab93663b324ebca3775ce4bd3c11e40754d45fa24be8fecf73240d0208a446324c7f5e86c2a00a735a9c8e21dacfa4acc9673f33bdab6cd97cae66492989a4cd1f0a2431e58098515a1068a1bb703539672337e9b7b25ec0747737630c6e2b259967efd4b77d44d2a92621a25865b3900657cab83bd399557bbe6711c0fb0acadeb930f7a42feefb9e21efded7a2329fb1a00fb0288e463824882efc21cbcf93f671aa687e990f23191ce25f9a4d0d5f7eee8292bcd4fc031a1b9cbb0774cd2b8a792bb8d81fb55485873b875183e5f91ebfe9f1de1d8da605fa615e3b372b6d5dda3e70451c252c39d4605fed1b0f3bcd993005a2c398c6d0efb2ffb421cc4424563dfb530c7aef1292627ee24226c6bdfd1ace879a94f379bce15e2bc50071bda35c0beed005b269478aa892aa8f605c5556668fa685f7212ba0523881d261005b245b6301cbb132a45b636bc5c8d50876738073908aef2e92dbe881853e0b9785630679571d900bce84b8fb6459efe8c6d6227a2f5be9b8a0e274e6a57b143f3e52a8a3a1b36916575a00c0a35e238e3ff4bedd51fc1a80c7fb01e11d04d4898ca07fa9d641695cbfc0b7bb94595b05fe9fec182ab259ed771f4027a3a892bef38142c92eff302e5b4a3ac9c1a95d339ccc253766ebb29b6595d32d1190e23cebc5338cf91b4f4dd9c92bcc677d392690519712713cac72f3df2161f4e22e4c153ec8dee26b4918bf30b507460a93cc3d6cad0c40eadf6808ba37e80d93e31904f568edde2130984a123d19d58b59f02915942a54364e8540560fa6d9aa95025d3adcad447d05a09b665f02d8c2e319ed1f66f8b2e2b11392574e8d83405cff9a54b74fd2da9ed6e0497a530078d8d7832c59e37c813f6f4da8da2cabe89ed55647de9f912af9cad9100f4f1321cb856191e57051196aa9b54c0061c834be101a4d700ec16528155c16388913860ded7c62500b6098be0589ae1ceae8de53123dae708cbd9c8a88e96472e009737a1ec6464fcc28fe1983d8abc58f5fdd9353065bdc79ff750beee71b2cd45fdd3251b223a95403726ce4635c5fd20764ebee9e2b7e725afa457513bddae2d8468a907ebe1cddf204c831422bcb07c36d046cc0512e1861adfe67af02477513763bc8bb4de4f2ae3e04a201c9af37c4b8bed3e8200297795510a0f166b4bf301b8020615120a8ea242ee1da6ab60bc6373cb32da51fb3ce051013a7e20c519cf821b7d1c36ca1a0f9854d52f799b0454b5da006bf3b83c0853995887f6443a7e545b4c29495ee0ba8bb50f403ca75b35f61bfc0948c9e033afba4edbeb576322d49035e4cb60d113b40370cc87a35328e1f26f8dc18cd70cfbbe0abf6a81f1b0ec1760e683c2bdfcfa11bd40c2eca814f4b4195819a985203b41fbc98377f1a11fe498e21485ef200af052d2d8b7c40c87c8784e0799bbbdeab5de79328f8803f8d942a24e83e190ea1a0912fc950220ec17daf0d5928d2e8e2de07f2e076f778b1ff6a955f2d1602aa5cc1dfd30c3b70d0a7f74d2826c6792a6e211535bf946a4f50c684ffa188600acf10f00886014d881610f3c6b3df2c8a5a265dd2c45a7ff7482790ad62542af58fb8b339b82fdc171b28269acb4108e886842f7121ae47cc9fc472f4536506478e182201dc3e246496513c05920a3cdf6a8c3b47d794420671e592b93cc258b73e9a5d6901607523af77118d2b7e2294e2d6ab989cc80754359177bff28ea4341483e3b57779c4b80f5a041ff07ce5fcd9bb9a2ae1b624f6aa544e11c5c55f7dd8aaf1321fd9aa92e7640038545b24f0240d7257de4f2f4b45a08cb26eac54fbe52e2a0267256c1be98f6e7a6eaa3eaa5794d21e19b3c6341bab8871dcbaa5ad1b32916f152f7d5baea0c0a9feb50745a268ddd70a3c1f73e4bd7ff1362d55147c2a3926f92a4815db9c9eb1610fd357702d3e74cfa28889a6f3f38caa7962f5670a3fecaf725d0048036cc1b83b6f84aaaf44d5e9cf6ca300d393b03c517c4c08aef9924e26e7d02e9386fcef14c4ae018c91982f1944a7e5127acb6681bf4401d975fc666314642efbb7793b047d45b3dace5cbae92cf46b1e97428653ec23c06f208f1a4ef13cf8aab69e3bbd257e4e27dba671f5e128a4b47c1e876d192209c353f721ce48a55374b98a697036105457ce1e8ff732d5d74b10d1e68235647a86ad785262ecbeac5342b20025229dc4e2735552f37da064f36a573e0913993afbb98938dd4c7473cf71b0384a630c3d4ec85d486b69525095815f4797375f97a045fdefb535c683e5ad08cfe3c47a0b75792b35b6ad427f71e0106e2229a3d83aa9f22ae155bd75f84151f5e24a0c2352dab48a3d577cf0bdeda6d7cb8712e48a1d3e25b677ea3742f15953e39ab5621f869ea1896f6536c472d79b2369e70f98ceff2065cec129d1b389417b2deb536bfb04dcd088d04189d9765182e11e4354930af9a80361d397c6cf92f6d7427f2bff27f8ba94accfaa5e84384e45954d02ddec3a6504ef07a1c45f4268660d2645b258d2734d885acde6e204a8412f7d7fe25a0c3ae57bcaa747e6c8aa9225a71fecde5da8cb98ae17192df28ae7a6db30ba2bb1fa92bc051a1995e8d1c3d98c474af51a6ef010fc9ec1956400d1b6908a5c5dcd5c408d9b3e20807497ca37247f17e1cfdcb7d8e44556b32a898eb0d2782bfbb90d68e41147ba2170536329f531930daaecd543012cc48752177aaec84110dad6809c969a0ab6968e323e658b64538ff43c5af121e4f66399092acf200a25d932ff59a43dcd2da420c5ee72d4f52ec56e1904afdd7a87efe2431635a7ebd7d81ec5cb836eff8d4b22634ebf11d1438f91bee07fb1c30031d8da24c10b1f6b66d48c638d48e67af9887f7e59985698c2c1bfe48ef0281115a153d84c6e21bcff24231662374f7202c00ac6bff57307432222bcde5d86c73597deb83cdfd73bf2f3961308d2f4eb625b46d16b88ab83264a2861f3291a4929636885286d79bf9f45395462169ff6bd53e898f54509b38cf24570c6dd89601a5028cbe528a85639bf8ff0a53995cdbf18bb8fb9d9c9abf96699cdb4e7b067f82b8e34fbb7f095c98ffca8afd10c29b1b217a6d91e8417c1f5fba6725bbe3895db33948763429a69526454262779520cf05ffd78a07fa8a8997174a8d7b2fc34d6b39c6cafed1a6032546b2f1de727b0385932851a133acbf694ec0fc4ec9565958f480b698c81431924924c8075e56a52d00327c80ec2e2193cabcc2810b7bbf0b46e5bf39b1ddf653195a5aefa3ee4081a218e582349bec13c5b46ad910a7d036f0d2c608d9ac8feab9b1ce781818404bfba61f81394d4367bc8bc61157d70ec7d878e63982905418ab0692e4c5f7b3ab76fd588231ed030e1003665fce7c9c2440c421d28381098730341048d10df3dabf079eaa304eace1e3fe012408a8f29a844371f2546350d8787b435ccd2d014aca1c78bc6cbad243829972abe2acdcea7b9a88b50a90f0c9204ca3a3f9b54305e15251d2f4e10ee00105140cae798342b731c20e6cc34bb3318e4204cdc37d1219c52bc53e32e6bf45c1d6ef7bbe8e72ef9c5aedf5f175380a7e0a7b76ee2daf2633ffb1fb5d61a2184904df6de7b932de50e8b09f50879093a76447829c34abad4df71a1b6bdfbee7c3ce553cb49a485c4d6ac23a5dd58e7d27295dfa695d2c26b52db287b55a0c24b4ba11dd5d3c70a1cc147820fcf43e0a5edcdf3f8a95d5611f2d05a92c2c753e9b1d4e381b492f4159e02b9b582f156cddd0ade4a83cd3945c05f9af5a7813e699f4b2d3ed3a8c33c092df3e47aa6b33aad33061fe11a186637994da6042b5ad57ec587a7dae4c7f311ae41a789aad475d94d66b2b48d89dcf86815c12f55871612d5e5f391119c3a0fe91647e95ca93918ef7602eebeb614c00ba73b8fa8575a3ec233e28e9eb1dae384946766867c0821bcbbce92df380d65b80bb84e8b340839c2853cf2e31ae03a2dd2904492a4ddaa77fd94ff68521ef41ed2a4bc89963d1569b187b4ec65d8652d8d8692acb29b0cfcd46828c9aa8ddb0c15a9cd4458c729d650e41ee03a2dd600c59d9556dae2df774cd354a7572c4a7e529cbeea58ad187dadf49556a230abd66318f68a5ea66f6573bc5bbfe396aa53f7fcd6b7ec2c036f33e8cc269c52b1d38b321057a71bcbb4c7caf4ad761bc1cccdac4cdfead22c3be9938267503b49bb89b2963ca8b53a6b8297e9da5bdb6dd2b6acab8defecc79cc8f7d24a3974dc7877658e77a5fc7437b66f46c7d5c48dd79f0faeb1a9e05171b7fee38a7dd837c5ddfa327d9fcdf16e05dee1906dd3759795e9cb402e7cce909c1ed7c791325c2347a8b8f559d7d5707349d769a1869a4bc475b6aa26b95f5a084228d3577e93e9cb3a7c63bfbec9f46415518a76abe227c8ca742389ff1c141fd2b2a712edc4ac4cdfea17fc68eea97832bb595606deca6e3355738d845a276d899f145c637ad73bd98dff7ae2a0be67a18c7d302f2d3ce941b6bdbcbc88ee494d569a0e8fa8eec6777b52f00c2992e12c2cc2dde9687821bd6071fb431851069a961868a8b91de35e2e5b4f0a9e01df96b48e67c4ce69125b13908267f061ded4076ea82dc136431863a4674ae5bb80e82b5031c59cbe7022909997dff42d93d12a2290c9ee00dc0cd856c6ab38d3fa2e0878ed0f190c048ce816f8800b8580b77e93b9eb2e743193e670e640518afde1e6707f78404c11ea6762a445448e4a55ee0cf9012f2f0a50c0010e308001c48861125304f8aa995c3145a0947efa1543692da0ee021953044ae9d4640ca515614a51b9d6cfdc3a2b40a51501b7e4e670b2a29fa1b9d2222287b3b9d4258199cbf67240f44dec6572e9756bfdd3b6573f5dde1a44ad94b224105d19d17d175e5a674d60fa344331c4c1b932af00557eb25d4d32c249dba619c82a98a6699ace9adc80fbc31f62f8128a6086c69a8991f605326603ee0f31fc1c55f0f1509d132287fb0333f363d4f813ac9ad42298a1b9cc7f98b9932581bc3cf19df5f6304d30e7348114dce95369c9b315cfa14e878756470d2d24c12666fa4c8c8c26b1bb5bd6701241f79ecbf4cce384702217c4cb229b9ac47d4575ce654ed932f574df6ff52ad05da7e6e9f5e445b7b0186eaebb035527a33a2250b00ffec13554d00f3815fa1938157a47b121bb56e85486acef3c2fa106d9a5e71f4e08be94b570e9c656b8945a0c844b7dcc39abae4d75cb38c508e1d48c641068157a4a9b5ec80f1f4da2d7c09079c005419dceb52e4ccfd1b44960bfee62fa7152f641698f26d14fed0c593cd38be890517aec331f41cb8e20fc657514015a0d5cc7ac8ecbea704f65ce79aa59f3d59a16a5d6034d7a344ec79a34dd12a71dd1cb534a27ba750ffa48e50fe7b4b07bdcd114fb08d5c9d8200edf11f56a1e47e34c6a524da2f4f5d9da55280d1910da40b67fcb6cd3d849e99be6d1679405f254e8619a690cfd43424fb18bf89029b9747b492e3d1cf2bc30d22dd4693dbd4e4d4e29f54c5804de5c3ae9bbb9f4959a4f85fe69d953a1879ae8a9d09b6893ce3a7f69f31f8fe65e367f63de8b59866c13510ca3306ad679012fe478417331152cb954dd668a1c7ba5300c135d27041704355f313bb56da6c8a5aa3bab88eb54a5aeb345c465ad4d245ae9167a4ba3a7d194de3d7af7281de2d2b7109c10f323ea1555a94af5e9b3e7f24e2f7a2ef0f47f2ef1f44e06b5258863d5dc210f4a2ec24316830484281a31b5a007d388424c13f2a185ea64e4dc290bb91eefd655ef66f77d7242c0fb4ec47bef04702e0e36e03e06dc17c47dcfe43e00dc67d94d64a4b216c6bdf36f9959f408e77c56e0684632f89c17979f2bc3e5e7905cbf1e5f9048111ed2e35ed60ed1c2903dcb86121d7041bc1787bcf7624e6deb89ba392e989979729fd84ddccccc3c396747fdda3dd78e63cd9d5cbb5caf2ebbb3a364a2995a109a967d4b4269c85ae7e160d527cc364e9c7a629ce959b4dbeca94367c85c8c8b111dc62b08974018df45f4e36b8c81a3edb586302699a6254d4c44da857d94c18b57d32d930a215674a4792e289f40d773c13e659f4ea6e863127dbe9160684ca0d893394db2ccc2e920ede3d1324ccbd8c7ebee49e9e3b6acb6a69ad8497ac7b6ce4e770f19dff10238d954dd69c954d9b7d397b6a72aa38c30d65c33b3331dc08b9664d32184104238279c304208219c7114a58c324231c421977a9d46a0915330890b823adc91a7e4cea55eaca9e91ecf051a792ad4df42ec64390423b46f48b432a091e7f26c0bb13513031d136d87ce90bd9f5f0cf509c873a999a7ac8881c5a50e874cf7e1034845f5782a14f55aeb057074700c768e611733a9ed15b9d4331f59e50ae31ecf856b322ac9dd5e1697fa16e3526f0875f7843c15eaa3e995ad4ebde7ac5665555cc08943aa1d911db2fe4549447ee4f832ed443b32329ae071340e9c5ef7112adfc9d82e0136c943f63ee7cbdee31ed5c99833cb442492dd92b8eeeebe1cf05b13d79500c68ddf5c892fa59452da1cf1d0be491619b2bbdd5996adb339e0379e9fe0f9f33a37d9e688b68a79685d57ce4d2a6a0c1940844e15709de8ba3bcb9672d46f73babb140bde75ccc4bc4f1a0ade567dfe5944c8cbbff7ce6326e6591ef1d2da1cf5f3b4d2dcad135a27de794c3c9dc77be76179ccc41db0a110133f644c34f15c98a0a1a1a18979114e27b92625e14cfa906dcee630ce750faa33f143b6b91dd73aba855d66bfbfc88405f8466bd287ccc5af34c93d3a19d3734316930bd1104e854f9dc91eb2cdd9189102c3f770ced7a811b1125ff39f4d7d1238566eb4a51c2578b78a79772716c8eb9889919f1a0a24b449b0c0051c439d92f23c6662a4e5414f599bc31d6aeeba24aa90a7d23a213fe57c2a6e9e873c0fcbc3b91dd30cec6906dcb35b9a5b022679c8a66f434c5f70823a0f4a5178eaee53c678ea4eccf39053ca3f1727ea79cc57ea939eb24e886278501e4578a74e9d1664a8b9eea4151ad75d67053920e16e435cf739499fece49e6bd2fbbddbb19c991a3a4146172f8b5b6badd5a2281af593e607e3ffcb06e09515c02d0c158089dd4401b01f0b00eb9e8ac766189db46bd7ae15acb0768b6aadd5568b1ebe52971b393639363936d5fa5551d70d9753b1b0bc1f2c34301ae5480d75cb54bd4a2dbb5375486aad20842548d1ee18a7b690d8fa130d2bfebaec46e34ea7d1f2cacaca8a5cc12c16cac2ac4c7737cbb402a5bc68c83f4b5978c67c979597646141b2524f69b362e9ee66e996eea9592696ee696261a1c162b195841f4e55732be6ee943912102b9f78c6f4e97d0a22247ab5a294ba8bd2aa02d1ead4c5c235e8998256f56a2e33d50d1942082194333a569e15a5ab57b8465f3e2a5e0d0bd790f45bf56a6efc3b535b5919aa31335fd75ba33159d0b2406dc3a151978f26a14f771faef07b7cb565e1191264a93de1834ea8ddd8e2d6361ad5b23c9c5b2baaaa2aeab22c2bcfd2eaa726312d7b1af5e0a4b3e724bd748ba7b2ac58c896524ae9a5692c5ca3521c1a0e8d998f43a301594633099128cb58b06fd395daadbee1d0ee75eb553b66a75544f5d2559d890987c6c2352a16295fcda558409c5285ac8644f594361dd36af66aa5babb4bcf61dd1485d123161234ce4718db96a8bfc768ab943951e6449913658d3ddd58cf3955c6c6be31927a49cd292fd337cef909e7fc46cd39678cf36c23f5b6a32cc6c738e3678c51d6cc2a227bb4a5f83e96d58f8da00fe89ad70a8e6a8abf2a05e37d9abb549c2ea5c5f864acaf4eb5eb53939f346a8a320b75cbe4b9982d8651ec5b758f700de9b196aae766ca96e267e5395f238561169bf198465f2fe1bcccaaf5ebba6c7579dd3a52bd562ad218638c54468871c80f92758a846e57d2d2ba3e4a2995e9a795b049e97ba04b3ab1293d10fb7cda0f175981507ea319258910c9a244dd234261212917a136acc88d9752f2b425b8527d2845dbce0a60f26c41d28eb0538f171149e9067aa4de4da9ea7c85c9b7950bb2156832993604a2eac330cdddf71e469908fc68d63d1a251bc61378720506af8c5eb172e14bb5ad5c4a29ccf215977abc62e5621486612b37567cc5153ca3c8159f06d9fc6024cea2cfce9a7557df9cf3692549cdf9de8cefdd94e731921bef1a47d5f3ea9ed11161dc79cc3d5807c6c94868b41b23b91047157b8a71be15aaa3cc8bc798df8fe71e71ce0dd25b9667de5aa93a1ef4899e950944d98f87d2fe71eb93ef69dddcc33ad43ad3e8439ac9611e47abedf989ca62769b17cbdb52dffae4272c8eeace3ead4c7b3a97d0273ff1e8e1ff04d79887559502c5a58fd6ca86a3baf3950a14179e9fe02778063cbd9b2c222823b9b1bbbb9b9f704ea5415ed47e40b8755ce6f279153f6ba55af5eaacc167d7ab95ce39674b47cd4967654a62d562947e7b8fdf7bd396e8e51f333b66c7dc4a4ab33bc64e7237c8dd3af190b6e74d6f9a3d379eb302d2f617fde8a7cdae6e9e33b38aa87f9885446631bb351fe33a59cc6e1311f101b9954af9de7befc9091ff7ab43ed7d4e69f110be5bfabaaff4ade273d17359992bcf4e2cbb2d5755256de97d1e5a7093794d6de93d9ecf5c8138cdd9afaab375c49c56e6e153aa533844d36d954de66d0dc5859fd4a51193dda69b792a1211752e8557c62be77465b453934ec2c046ea145267855aa5966559f3b22c4ad9baae43eb0cfa654153c8e51a78e3704056dddabebbbbbb9b7ebea76a62a25b9e8c333afa6eeaf3247237cf77dc86efd04dd38ae6e79942e014d28e7294f51ebfc7eff17bfc1ebfc7efb1c5a798628a233333b3075a5df3de0512cd996522d14f72d3c6d6ac7704d7e8b365091172e555ea7ba07cd7bc3f51762be75c1ecdf6b242bac86e22510d8c5ff6358f40d5428206008f26867d34a314bb89ee0d18769ba2074fec76131bb2b05f1ef72fcac8ad1446dadd3d3d0cfb2c0ff3207f7336cfe5a4b81c0bb3a869514a29a59452b6045fa78d4c4c9c764d9b9999395e1a2b3373f3cacacb8b356796894494d23b1a798675782db9754d4ecd9b2e5a727be7e565658544fa45229b1a7ac578ad57913a7422086fe6cc3291e82791acc9a2afec4be419f1f24eca29e4f24b615b040f4f1a89f3c96c2871a35b26750fd5e6a9d4e2c3acf79e2c83f51e175c43deb2ae495e58cfca64175ce3559627f3489c0b99f93db6cf96ac1765195e5bf2467221bd90d68bf4d1923837de4d69a5945276216f2417f23d89433fa7e71df3c82e7806851e2a2f4b4a0fac26cdad966559f559d985e42274bdd2ea5bfbc81b97853b6597142e0b7c4ce329a595d6bb801b0e87830387c3d970f0bb56b4acec42be3e8ae14ed3651738ac57d5d25092c9bccbd6e329ae5bdc8d8a419b2e08cb6e382afb8feca25ad985bca1288973a9eca24172e8864b2abcb9151b028563a577ef9d53cf7f5a7d6b111412bd6b464723e9c3bc2e59eec15ffd0b5eb7b80696754f697d5ef0e6c250bbb08ff7deb39856dfeb7e481a7a51c2bee3c2aef53d245ce39d21172d6d0936f6076fe235e1cd8defeeee8df6d5dfe8db625c7e2a2d9e73378cf3b9b1270fbf7ddc9cd9f4ae5b7fdf3e13b0e0af3f6d93168376649d1fbf02f35c9800d96d9e844cde7b87dc5262c72c24aafbdefc09d9130bb26d652008e5256937b62c24aa176f621f5bd0c776651141e1925b2fcbd2dcbdaecbc2de2bccf368d4533494c3d044cf34ebafdee83757bac5b554a77ee34dd2c248278293c4aacbebba2e6c9bbfbe4d7b7d5155d7b7fa78c77cbd32b3a8522b032fd49933cbaeeb882a0f7f695beb5c6923136f92dbf578fa94cbcac06bbdfad63a5993ae8b50b46915515fb2ecc6d242a2bab555ae4fd1c774fd621f978cef2fb846a56c7fc133e2a9bb14de5c9652cafe62023500d45063bdfb55dd4d0821105d920de19ca0fcfc24c3b76d094aadebb3db34a3e3c94f0f08a6df711ff5b7756f93a16ca94f29d8ef51946b50b24fa98d3c039ee7a7f7fedee127aef13e515611d79fa557e419f3c15fd78476bb7cafb1e3a59abbad3ded96364f69f5308fe12b16ea96e90ece73ad9555bdb37cbc0c12d2469e5155761423d7909ff8f23719bed4e9ab0c4f94ddf84579fa39e37defef4d37421b79c6342bb676706e75e51b0890cb80ebb4c8c9b93936b7eadc8a91ba05bbdb2a4dc675cfe5130f92daa354422a0ff3a44cb756aadf21733c87b4b2a5ea155ea67abdce5bdf7a2bd6b9f0cc966fe5d2062d8d3301a4096ed7a195ff3c5db9efce3585bdedb6322d66335b6a8b9d5af6b273a26c891e7bc4a2b3327dafd3697339aeaad44ed465c1486f455885444d621d76d25c0ecc8370e6e2ea08960d25982eb163b343a90d256c2c6103f3a866d95882f9d948b6a1848d259ecddbb96c6309e78212ddce8d9bcb8930220d455bc2c689c634547d137a31e13c2efa862fb4f1369488dbb38194cd8d87d82314dd0b8675228b6233fb44300eba732e29f7c08f34f75234e754606822cd3914cd0a7dece6965c07e5e3355919081d7fbad1561978a91c78e797e01ad596a8c74b59c953b6c46c97e01953daedd95cf825780684f444e3188e71391386a6a16827f555279a8b715611f2c6a47ab495c4c99cf37f11908f274368517b79efbd373fedb3598287801ab486eaaa63c69899e185e4b2484c2df2138ca45b26f64bc3ecf604126624d331f313dd62c53be62835772d4dfebd62f05570c9a4e4ed7412bed142cb489a04374612cf4822c0976f51f17d6e59b6da9c4b76cbdae2c7ca2c1a4a32ca6e38aa63f4932ca2343f59eaeebab3d8af63f05ab6749d9a0065f9090b09689f885cd578a8c51810c2d09d8f318220744bf1f4f58134cfabb35be8734b93af22c300e89e3f90e6aa568920ed5611743275a94d6d0bddea52543c11dc7f8c849f58b13abbc29c3a538b43f8c89c9375260fe123131fe1217c44c431d6737933097c8475f888cc3c5acde465f62eb3bfc73a7c8412429f01c9fd755a980188b49b8e2b0fb5f8c8333339de9544c8770e6691982c22a2140fe63d8b88eb9876317f72374277e11f64cd4d47d4fbaa3c41111101e1291bd23806f4f164d6eb890fb2d23cbcef482e8cebb458c3cdad5db17504bc59b74c0babd0ab96c6a734ca5611f49174a389365b251e4513b54afc8926d2d88a003d4c8bb4ec28dae7279ae7261ae8ceb2d131a1c3f46348737385c6cb1059bf5d43cdc23c07e3676e9cecadae2d783ee29bc23361159655c73ec7b0eb309d61d9371010184d4e5044175f8472628279387461d7f99e0c7b523c299e1493a7e5116c460708f41110100602496c4405c8032b9383b42dbb333a3010f611100c34b2c204948140d64d4037b138039d848e594f9126d831cc9a3c115e9e5899be26761bc10cc86eecf9c763b70cb33e0a320a320a320a22bb90145a369bd1615d24fa645d348964f33bae1545a2ec5366d15b728a2815616589b42dbb333a325196890ef344d63d5ae9ba1361229147748fe5c13cbf2eca3c22eb338540d84766a27fa6d004fa605384817272d2cacb8591797e896c36a14c2793c9149a40d3679a98dd5ea8cb5f5ae419565529bbc5d339e374c2940b90f0e1f20caed3420d3ad4e62a7b7daa7f3e9f4ff5cfa7faa7b4e3560e7e3e55755dd54c223f339b62ad5ebdcfe792f968db435ac9fa609f4ff5d0e7a1ea0a1da631cbfac7e43af60df4ab7a8acc747d3e17945f36f4ead7517e7255199cbe5d329a5899be21bb8d6086b37b32bb615745f908cfa86c35817aa9dde0a71df5e564a54d77b7a7adf3118fa7c23cd83dec394c57fca9be65af144eef1a7af560a7af0f81348b89d48f87eb2dcb6e15d3f3113ec247f888a7631fde7bef82d2c4e4105af09649e419cfc48462138a4d2836a1d8e430cf84faa595aa4965627299fca2aeea323953186326c7ce33a35ec913c72106f1873d9c31c6159f5cafb71bf35d87401fbe2c1b6334615a85b6b9fdde7befa395765cceb24358c157d91c0539f53ecfaa2aabaaec302fa3fec9ac2ca3fec9fea1b8faf0abe715f594f8f9565596c2aa8fdd463093d9cdb215351f84675074daedd94cc2a036ec6eabab99032dcbb2ce3956b52cceb12ccbb2a6c9aaa6eb569d769379599c1b13c9344cb3a65b293ae3115166b504c37b5d4d3a51e9289da6191dce4d229452eaa615ce393ae77fcad2fe7f68c54e3c16c824bb2cfaf19c602093cca2a28732d18927f33cfbc75e009fd089282546004e3240010820630056d3ec05100000c418a5c010a19c9884401f4f865d96a2368ab2b72506ebbdf8de93efddb27bce49ca7e9772644589f608c89b2494721bbda845f958db6e4cad572debfddad7dbc29e61575b95a5941565184b25d5c9d8e4902b1fe37c71aa26a7e88c804c37cd382335aa99736e9bb555dddab4d3dd2625575e29491a4717e9faf74edb6e2949a6c72f489051107931c403849275a0942d25948f75ec1631ebd5f58fe6c91c13b12a2b33ddf8884c131fe1237c848fbc19aca118aa93e15c7c6e92da96f5ad264739237411c6cb4ff67db20bba6137ecd449e6a4ddac539b8fed198a1180ca6e2f77fb6d945025cf360fe65c7913bbb1911bb21b34c23817e7cad0419f76e301dcde39171937b9732e0438ca9d7329c061dc3917037c74e75c4807c09d7351b900b4ab68cea99034e7540ca039a752008d009a0c6d009a732a34d66af6767b11800d80dd56006063d88d34b229763b8c13bb65d26e32f186401fdbf3a2a124c36ca52c2b13af7ca5a56a29ea4dd196f897d9271b79c6cbd1baea4839c2a9463574e74eafee46b4cd0df5a699eaa9d4e254e465afaa5274aa8f8054232b4a9647a6e88c80700d792aee163aac3c9e438b3d20bb356466666666b615f4bc5b551c5951bd149d1190917cd53b8f82c05797e90ac05752db463537ba2ce6cc3291e834f2a50504692a7a0fad5292409af7f2b2b24222fd2251c35190471fa722293a232055b5c518d55c8ad1ed2585741439650c1b2ab194524a91ddfef8c44a28e508e7d6f7deabec28c808c8950c318ddea35d5bd36c538a4e671195d018d12d0e833459641195641acf4b69a594a320ef103eca51104ea9ee24f940e33215792ad3776e74725e8cace0dce91b691a05c1d145ba089592e4522c95f0744c8329f25c5e5e565648a4ffbd225de4cac72925c98d1f05e11ad528488a0e458d6a2e9552ca51109e11290c16c01ce86f301a2826d7096b17a5924a2aa9a4dba36207a554bee9bb3e293e94e2c6243f7b554aacda9275aa4abb4d333ae4e994a7534e17a573cae953b7eb74bea24c594a299595a5205ba51d51af9f522291234fa594a1cffaeba297b624dfb4d69005514aa90455b55237b75a1425a594525217a6657f9ae89386726872aacd7fb4ea308fe935a5e0cb96e4add75f405c29b8c6f478b2e702efb1f3b624e9abb4e4ab86843feb67c5d54528da89963d15eb269a7b139b8f508a7aebd53ba7fa641151d15bab9452fe52534e33577f70d059d41fb7625d31106fad9ab5a664a1d40a83a2a09cd45b2214eba2fa030546b5aab3a5faaea1ae3e205cfbe4dbfbf17edc780ba556ebd937bde94daf288e5267c16a5d1e26fbfcb81b8c1f0c639232ba07868d012346c5f5c634b2392976cb60882c4bd3b585ee81a45d920042870ff7e528e124348ce4e86c6ec7ed0c41e3c25c8065a016641e22783e63f880a8f02613a010e80f211316989c7caa38f9c4a3bc8080bc6051018145cb20d231ace8101513a572cf855ff88557483b4c8456906d2f1df8cb4530be906d6e679b2d5b63314563d144da8aa6e2c19051c6944f18b2cded4c266c5e919d9d9d1d6793813354d72d20e09a5b724dca0c10226d0c847b308d119c1b9b259d835f6c08c1a02c6c707ffa30a9f74932a4c87381e767c43c827a99b21092f38a02dc8c68ffe04351649b33f2e8f38820c988b647b819efcfbe7c608d2fa09b1797ac5578850ad214fce31a3152396803ca03a47f16869bf1a89d3da2c27d771f9d6c7337324dd818c1373733f4630a79136fe0cd0d0d7838a10d372419cc822ce00c8921babce3bdd7ec5ebcb6995b430b49b08999e11ddcac04fe64c1c60b1b0e831736d188112336e69c73259b73ce25d9e68c349c18e6b9c8bf1bf15ce2d4a47849e106373827cab83b50b58103b5d908597643348d72908958d0e366a11930b89febb4888318ee969dfd40732bd6f39660222db9a19384910a2fe0bcd8438f1d3b9c010b254062e00fee0a10ee26a2e26ea41fdcedc5077773373db89b335273210fe690e40863f0811a7e78010c7007406c01270a353c74e2202466c60a19c4600d72c8c210a2c08798feca737140164cb8828a9c1a306108317d98e7e2b2480213867421061af0200e31fd249e4b0370e690072c6001061e57c4f46f3c9713242982199450410728d82066016668c10ac0608517841c89699a1ab0a1f298020b7d40430de620071b0401ea8006150f78140951c105e6023f5c4970411772a0c8208410562c8021b0e10311f480033a08d98153bc81055317d04007287ee7c590374494ebb61892852147ac232c794ec417fc0b23a0ebb470c313575eb7c50ad0206326c0096e3e8639b8a49e89a20f3588c3139840042531dd5ce4478c3ba02e60430b824c3d842e4c95138e0061f0890f52204245c1092c48a18ae9c4c4872d64b006eaf270dfdb62055fd09b2d64a0c5c5aedb420660b8db23139c1093c7f3aafb1e1a7e6be59efddea3a6a99d13629a64047ed93a17c47b6bbf273778af708a3312018f609a3bc5799eb2ceca591096005238e9a4df22c7c8fc4967e4182b8cd2cd2b34855ae184fc4acfb175ee21a122a5749a788a16ceaef4add14fd3ca95f255ad1ee35df50d58d6bd95a90bd051d43c45e79dfb085ce8357c6fea8f60e6be379d9a93faa43ea94f869714457d4ed4e690f023988980a31cab505db760539fa51342462744844e08f89c10af9d101d3fd996e2dfe361bad497af5193324608a7e93d7e87dd7756e92398b96da72907b67b4208fbd49b7a85da564d337bf6ebdbbbccfede2fbd57e122c699e57869638c18dba955e62d6db38aa8eeb42e68bdad0850b768ada89de27c73be4f32c6279af46467154c4f359e29cd2eb5f2360601949055eeef2eda8f217bef34de9ce83451ca73b6caf44b736fabcf2c3b3569a23e69f212c6e8966a3759750b94d24ef2512b8114627ea377b3275875eca2ce3cb4e009363195b7d0831076626a7b010b3cecc4d477e30220f888a953d59a13030e919c980a6f728cc4d4a8e343484c95556bfc041688a013536790214284c4545ab5c641a6f0e326a65278d0c3196a626abd830c68626af5052700e126a65a794092b3862038e811533124b44127a6665a0cb1424e4cf564810b3631f553b5e6bcb8998226a6829c165f6c01276687b3f9820b45980d40d440c36f48034d4d4c3da95ae32d7440849d988a52b5e69a015b75793603f85554b5e6eca0879d9a980a0346ca1e84a0849a981dcc6f8b2f906c2949dc9ce634cda9e15ffca4c1c7865372dbd81c86c1e54c935d420c48e6bc1b433127cb4f0069244f0a32f01132b4ce9c93aad7ab463dad3e0b4cd32f7b91ea75bd08774bd7e3294b630ed9e888ebf157a5286de6520d87e8f69f83da4ce5ce030d29e504a6cf1731cddca76def0ddfe3cc7d7de13403998d70b4e69c73e64a6d939fe4ccb52c3ca30ff366203318a4e4ee9b514d93e4a8a649324e31c2a74c2949ea08488ace2808cf98a0803e608f2689609a373759f328c84dd60f2699baba4b5578aa82b7e0f3d139345e562aad52578081c015229c627c36a1132f25949406a9c944f808e49c0b2f2584d7263dd122eb4ffc28a1d40236dc82d4c403d224f98034493eca4ea2fae9956c244f8aa86dccfda2ad502756b0818140b7307c40ac90f5b739ab2cc2806ff0049b98429a24af9c429ac472c736032b1c3bdd542ada6d42f17c5e2bebceb998fcc2ee5c16f26837dee46c5e5e565648a45f14e5e434e6076f6c9a34591a4a3297a3c3c2c833a669c7754d3ad922eb6f2ee746073c70f5c443ce0322815427c34db74e2554f284099d9311c94af5b07aa5dea2d349ba05b6924afe5d1a914664196495a253ed280864db5e344e1b699cf6c2088744312e83d4469450a7510d16190e918c6f1612c5b80cd228c8888a0b3b67cb7a07270bdd8b6e89d148b2ce190599e01458c029eefb2848fdd64fe1ad73ee1b05494972273bc99caa868a846d86847aca66b148b68944a225504de8609ac82df154f6f7eecea8e9188b2615b1e1226cd32d946d4e90c09f70daf8848cde15814bc838068cd38a1bb94811510c1263d1840e915bc233a285d0081769e281de3645951136f2399f8b3cebe122f7c5ccf29b78d9465a42e592385d5a6861861e1701b7623b3144acd396641bcab22454d5215b4a9b4dc88cb879cf72f78aac240fef46ba150908ea43740b5579c0e9e0a828bee421314417c5385320090dd850e2c69350272718418124346043891b57647c8da040121ab0a1c40dd2151941011b3148403c7743e2233ce4c8345d73e2234ca4d6a16e40dc6a52cc7242f0090f323e5b19275d46b790ba25f2890fea542dc39f40218b775d9dd4647cdf7b7ffc77f73ec4fbdf65bc9055ada4ec66f654445374d2da39391bda11093f587eb0fc60f9c192fd6081d60f4a29b590702b8ee507d532c7f2c3ad38961f593896898bba0d7199c66516c9c233e02bbe86442864a2e2d5f4147ca75d99f6fdb83c979fd8677dcfe9a39068cec3d0685c886252cd53ebf3232d0586283bc9e2c287404d1dfc74ab2dd17a59c712cf52ab65b12cb52508a939f9d637ba0846ca484bd1606432226522f8dca3bb65dcd8dda218f7bdf7bae70f961f2c3f587eb0fc6031695018429f8f941f2ba5b41b12d30eac91b2a1e828a69dd0a5168a62da9976604d4781c1f816822de4ce77145d73e79d9bd36e32b0c77bc6ce06c87e3c8f6e240cb66082348f0fbd6452d2504c3b4d82dbb46355d5b403806f5900be4d013cda8dfa27c6e33633d03d02b0ae55b2bf56e918768e2c8c771a31bb85328c3ee512dd3997d14f465a8ae61c0ccd39156a1d907b75cdbd6e62b7ac85dc4b64e35d6d431fd1bdac876828c940f29ecb5eff7c3cf71105d7b82edb51cc40bb73a3e019f1991619098470aebcf08c2078b9a568b32a605437a25ba205e214689715d22f12c5ea155622238ad0b8c7c76e248f15b1c52eadded2a8b3cdebaaaad8f5155fe306c4b672e38b6dd3366dd336f3d166a20e35e93d74abb6b91baa19b96feedc28764e1a060ce0a4653370c22e5dce74cc22e1727c480d2ae16e748b3ccc8e69999439ef6e9a7ea35ba08f2b35c7d3b4d24b4cb6d25f58ad3393929d243ad38d0eae11a8a42d98643dc9d4880000800001011315000030100c07c482e18838d2242da40f1480108ca24c6e5219e86192e49032c6184200c00000008000a0499800d2c3730312345a41b15206bdacb84e2b1f029f9d8e502d16e8f9e56813233a05d7e6f9c8d82ba6c3e017cb304a39141e336b391e3ddd9753d16ac99b135d53d35460e5c9d4d87b49be59f3cf10ed73be8c02e28ca0009158e6ca59f306c35e552500df3a93e1c977f46aa097b1a5348ae8a5e15e4a59949d7195efba0dd2cb502fe35239603ae269b8975ed2594ab2ef2d008884cd6708c21b7349537e83bd1c95ea4ef8508c587121c40d06bda614777b35ece57c29eb573b2c5e39897c65f0be5698e40cf11f4ba729757a4f707b57fe60cc5f42ac8155b70a1bcd4f752e784364612e9dbd2ee8d85b85346723e0c502221ecd1a6545dcc4740916040c75e432cc1fa2a228b470daca363049e2b5eda6fe879a78d3d6a3fbc1f772b2e3fb96440008a3a1e05798919dfc836ec0dbf9099c5f7304ccd142332e09710fbe421ee299b6b2a787e60fe0b62745cd66ded1553cf41f83ead56b0085dc86fccbe95a63f976d03e45f5507290051ea7a0d17d6f510b7137c179850bcf9744392e16f609f4f113e7141b2825be0f6e829c0e67d2815a1fb71f4c3ee57ef1363fec21af9ee9d7a7b6d93a30e144dd6a10435c921a13e479a70816f50b96d948fbfdd0cad29629c63fbb808a064ddbdf5f285252857c0103cf4bee02e3e05b67950c87a2b964cfebb9679fe16ae67807ac185d35eb265023b73ee520530cd95a3631b4bd85714d8561ca9b12607418649635443037306056badf3fc80a1d5e3c3c0078ba0e3d15fd830d039a2fcbbf9e7d065dc1c5f025fbb4045110b3b2e64faf077a995daa30a2f52860410493caf1dcf1a4b24c0f2fd148402567e9d823f9f392cb5db961b7ba388d1db9ca20289d2acb5e88b4ca0a881d2991ae4af7049156f359edf613591c5a28c7bec4d3b288a4d61a952cf63fda3c8104abe4893c2ceb4fc686df7cb84eca5422aee0c8fa1c05d50f2fceab26e40f8b6377951a2cb9e685760ffde5b4acf2fe30e3b506f89273b1dfdb8b2cfc03ce8bca3411943aba21d2045e22d41b7e5aa0722241431ca49a19f0a838fe78f4e4f0bf36fcacb4c416a46d7dfde6f65da9eeda75dceb65207ac21f8f8bfa61fe3b77e0a7493eb84190b88db1c52d340a04e0e25ac283a97f2c1b7da38d628323c35b96800c3baebe1380615ee94a0fc25f33e4b11332a53a01cb821c571da192c18775f599c7ac55dc161352378efe0869e2e10cffa43352cd755a583d4dd4188519a1f0495b647eed4bc186d94c371dfa188a1eb1a270d48c9c74efb4d43c21abfd81c3e981af146730ad2f8cb3bed16782e0f6608bc291bf6e8a58f35953fff4a149032b1aabd3688b405e0e5c3fa4bf3eb96b82f11fdef11189bc4e1331c4feef2b1ef1f467307b6fe095ae9b2755e5699579148032accc1eace3054df08fb56ec6b645ca7969382e45667f8d4372954896f0597ea72e32fe50145e8e18510798543664576976520555ccf44354df9154622a337427fa80c5e87a45350a18341ab2c459d9ddc171639e7d8f68c5d270a4a383210ea2eba3920708022f04a45507e3ce61341110aa9f30bb3707b0574b77bd27ac4c45ebd2c8b61a3a38b619e9b341faf3aaf399c3a9f212d899f9b7e2f534d9add4469b2d4397f9fcc2df99f8d799f5fac7bf69492a66143b80fb9616bd314cba11f48afed0edba3232e68e8d2654e0df4516a7563708b9ebbd10d82c95c8c6fb9498461f3d2788b6c8ad74f0991dece840e90c8ca46a2b7ff8d68d1d677ba7b2a66d72254b86cb1a32085e0569978c770c00683ea85c39c16c55ee0a3515d9205d1235f2e3ba75020a4da072d90618259b4d7a1e890e422efb3347f49c5e8e8239361b2477cd28fdb5f587d166b5266edd8212a32799f223b098268f14b9b7200cb1a85daa48ac8a3a71b9a75bcdd01f440feb3e3782f5ce66033418ff16bac6d312e97451f4747c996334c916b367156090431c4b8c2b2fb195f87f1e6cdc62e9c61122cecea36d525a24918ca0b7343756ba003ed99fafb9fe3cbefef7e92577c8e7a8c85d4733942deaee85588154a31fab1ab585f3c21115af0370eccc6c6fc6e146ebfb2b1f9a5e4737741cf39488af3744a43f4195bd5f81751919f4f66e505253d3e04db13c7ff0d63c163f94ba0517bbb3e38763a508fb4cadb14016e28a617072b3ae29b6a9bccb0f60f24370d93b3a0b6ee37ccbc89e3cafe9d451b4c7e7605930f74fbadc11f612e7d37f5a0db49dce6412738859d31f05f8209661263e7e54025df2030ecee406a058066334f82125c5802d9205d7354af750c9c109bfa4dc64e19751268b37bf527bc1906bfe9bc3d06bf3d8b14bf785554b3e46d68062de684c403f4adb71fe0bd69a27507af91a600a1ec38dccfae80295a8f3bfe4f707e300e7885c37ece307b775ce7f38d6a42e998411f4422ab2b2bd8c39ea86368432b41ef1e6c3b0aa2a6442b76a8afea7ff39ec1c3cb3f81e24c3ee05b46c55850828a1a8e269139d5fcf2d0ee87eadb8cbcaa3e9c67218c33bd048a66c2a3cb318ad97befab4cc05f10ee07f709c54c9f8782aafe31c65ebc5e590c4437cedec9a31d59afcec40f02ccd9f9e29125d747ed2cb669d3d5ba2414e19583b4854aaed797e51e6a543ab7fc63cb80897c30c4961ff42ca5df99d769fb69106937cf32018654bb3d24a3a8e2671fa130fa8d3ea44ee706f2f85d1319f93a5f075510c5d11793b9c88596802c48532cccc51abc5db62f942d97a18f20428fb7dd7fc9b44f85f3e026beae560246c11571579f0cb7da5dece975c8bd20fba98bd6cea45ab3e31a6abf5fd59a4f3fbae4108c0720d99b3e6b399e42d3ac95e2e52eff661b8905a17c150ba6e1f359edc0b2b2ef6e98ce0b7808329e33797f4562ba2cb85f3424afa7a1be43903d96a54700df5880870ccd76b970c9b06ae137ad74e4156fd40b53aae42cf87e9b7b1365d510b5a0b735d151e08ec28ab9c30378235f119eb2437b09e932d4e016ab76908d933da0aec442c204e436a8d48ff3521fa9a2bf3021ca717e0b019ec7fad46b3207bffa64cee6d83064633ebd431ab94adf8b14717dc2086faaf831bb0e6f25c05c811e53d1087f85c97bf250422572a9efe9fae2a2af9b709db20eec9f498f344422a77bb7f2fd0595e966f64609892e1880d675f0a0ba59006d55eb64600bdae4e503ae2a45ca9a196806256def0213130050c4d15e5a8823b9b5bc4509cef0f39c2e74cdfb5a953038f3bff437e456f8119ac619d45af52abfd48fbfc27497c1c2d73feb9c7edba0173ab8e2b47cd9665cda557b0dfacebbc4ea7032f2bf57f69694c91d64d787dfd0991956ee2be1a645e5f0b4b30148b3b01bc406b8551784a18c2efb3dc86af943fa3b40f78e046a6cecf5e0355a4ebfb90fa7b14244316d28f43c3b3ba143123281a61f822a484f79769dcbd1bd6c76e1ac8f4b5d8e2d439528d707f395c1ecc1a7715a6b431a0f6fd89b8aa0285801ac875a99989f3e2e7630c6914e8b082b4233a3636039949dc5c6cb574d3c5847f4590c821897d207a116f7e4f5c14846aa8ff8f186e47bd9a49c3d44c43515eb3f34ddbfa99bf462ed95f17a3496673bd525367fa8b70bf479ebe4b8847c4af3c799001a4304c0eb826607120e46d61bc52f1275161e2cc84fe91f48bb50ee3dee807373d20fa3a6c5a5a4b755fcf8f031d34c5af19905c962426b0f99072ac3fcaffd9b390335e242adac4fe9f60981daa5f3921fb0c8761169d05782e3343b4ab290f4289ea918a6f512f9b020e804f3a64b13b4c64db384924f591d52b773d28ac046a4b81df9d24d3ca3a23cb7499db77b4e339174cbfc628b6b46ece7eda2b026adaae926031f03d30dd64b475fa820c29cebf8f4c75bb0d94a73b0c96f8c40d15247f7491a7be9162c0aade681b0f2675951e43f1e496cca6e8ce236e1eeb535a58fc1075a399e692e36eea60eed8880765871c2b78697a233f7aa17a975df36dcb7c5bab1e905893c32a41dad72b8f09152d65e20bab56584448164c2478015161904c0191d7cced300c98b438b6f5c857c2207e066a3f5fc04fa07aff49c18e77e59248211845faaccea9263f58edfefaa4b511f98d2ac3e502315d39d0d6d67f72ccbca371c88e5f429ca63ec39548e20325d98951df6eae992fa9adc8037347c20c2aecf5dfd0f06a1f653a3fa82e318e7760eef521a67816050212cc15852623b4964d531e1318c4d4411d2468aedea76da63ed7f4a1f7dcd03028917654f9edc46e39a778ebae52991488043cfff9b716e5ae7b0a0218a41fc1d4329bc5f32af06dd2ce15ec3f1c3e6f541139986693891042874d6ad25bf9525e41d2273b1006e84514238141b1074775a74b98abdc71490c0c96f0ce7e8a938a355502dc7c29f206ee7461b7c847a87e4b5b77d77d88d1e0828f312522104725f0f311b662f0c176668966fd7c82d3ac63cb53f82902e16d3a3195e8fe41337e651e58b285ba66ad358dc4ed77a68e2ce34a06b09875abb4be97c3ef01b0e5536b8648f90d6d0081614c4328a988772313eb8ff5612170f15480b0fd826f0723aa141902173dde1128721aa7cf33453a5cfbc6ed941cd8f0b5c5d7410d76ee17a8d5112bd03bef2bb716f59bf27374d171192df29fdc65ae70874c5df4adeb7fa87f625f569c996b84e04faee6c017b8e0d927b7bf127c728aa2a6060755cb5d0e930e4350dc0fc21408da19b7130075f2a5419b86dcebdd1b1382d89b4a943bd46f0640e8d17ab7d0168b277a3d3aaa453fc3aa47c7e31a92de240718a50670e3fb01824386c019a4a04684f448b01de0901da054a557027f3851d17924a53b73edc52ad55493505e017320f83e6e57eb514b2a72cdf470d0e8ee173875194acd0ed91d65770e2172c6312a4cd7d15b20a3eab600bd928077d55aaf086caca49a0dd68a6d5b2d564aa0c38dbdb28815c3402e313f96ac8f40316b28e9d428aad7c7e44807800269b2435f3da5ec446610ae5eb39051838018fb9a7e8ec0a74bb6381171fd99de482c8e8bb5e3edb65d8f157c01e2f2965cd70c164611664a6d01d43afd9b4a1b5469c90645ea739e132281ceb3308508b039ed6e2db4f9e7e97bbd36599010c313f36c18a1bd3518489fe8ae1e28b16e980a8f786260ea37dc95c889fb59f58230482a16d738cd50b009efe79ab0b5fc2212502000e8bad67b3fcdd3926a04ada27444ae69dbc4debeff9758405f25504dc685f2a69b28b6e0050b62e6667c0c114a18acd5e0d830f3493f35ea05fccb27b7cd8332be79d87868df6fe6d6ac5f255f3a69107ccb28143ec2f2213025fb087a74412df4024e4c2fe0901f01148d6d84d96a9d4b03fd8654db33bdb15d77236ebd135b254aa86f529fc3372977d447afaf24377da1134b8a58d8a6a52c69b20f7c2c2182fba4414587e05a7cd21cd26518a018a9f7ea2a78d789abdad06c7b9bcb2a845863b23bf39b82d4ffaf616022fc3ed961f46d62ff0a575f32a915ed9f7705c22b5227ced444d5d1758c7c52f870cb6e65dee78d959aa5aa40d87bb33abf9cbdf601aad9a8336b7e922e0c3dfad325ce257842ccba8f26dab31312ea462abb91594b73595ca7bca129d9958710a7ac3f3fd121a9cb53f69c913c4876fb3f2fe92f17addbe494252198f2fdf09767feb85353ebccb79f67ad39910fc6fbe93e0c7cb73ee5500a3c81db5f208430f95512820f053ac814f2cab2cf7aae262a89db970da58d807d0d5cf5a287cabc1d4b7f3769383c4cddb6a314b69ecc2be9f35d7d305a4bd94b887dfdd121abaa897616f7e5a537461ef11b979c2823349b10db0361c86c0d0ac92ea4489b2ccc7c1522dd42463179151fbeb184ad8263283acebecc87c8f58833c1ede47913be93583a57755fd16edc611a4075f16d029b7af9e4af2550022b33236145d0ecd5aaddd409a5097626aad88a2133bf1f3ef6272979fda28c3ceb9a14919e78d9028b79e444bf07b3f615309a7d4cac2406ca7daacb996461a54f06f959d153cc60c25e46d3d09170276baf4b39d37d9a18f092b8c1ab2f99cfe9dae28c4534d36eb1b953354bc992ef2c4b501ad0512c372a0b89d3382340043118f43e502913c247a3cf02982b2ad82d1876d31edcd3fa09f9905b283119a088f0ac4ba4a5b5fe53bfefc108693d132be8234385feebff6ed6bda88ef6d1a4a4710fb9a6798240d3e33f1b385c20a221ecd9edae3e407547d2181987e22587dc4e1f6a8a4d7cef68dcaf04b0b697fd8a1068bbe667c56d10e9141ee5974215e6a08c1083546377ba5f7228f6b5121e54b7862f9af677897cd92ba8de74677a39f26b6cc83a8479e39f644258a1f808fb8ba7aad54dc90eeaaf9cc9213cd13a6c09f443e83c19bc43e82c197443e83c191c331d30524d2bf03efb7937f4afea8216701096a422c5f4cd105e286bf982bfaf0a454f1755f80653392c82371c7ba7c163418f4616ca4fd7c8d2b5e0b8d5cfab4e83d27389b3651acb6766d21b20530166f91033bf316e71779152568b555c6c4a4d0a6aa75c9cd7dcc8bde3a8c09949d0b95c2849b6c13ed9a560c7f62e04456389952d5576bd5e11ed6609a8bd8f3ebf647e19f1fce29182be444f0dec69fcfa249a98338194d9f2d5f14aa70d6df21b9d74d01a3b1e11c71368282949ac53352cd82d2a43dd679e8694edd19b5b1b77940b9c9d0bb321b8ecc01a3768b365d814848465def84783350d6ec3a1073279a687e3a01a63e6933206489a3bfb9ec7326d28dbe684ada197e82d452a984ae58ca7eae62510f0508735743adafe18d4077144cfd6957f9176ff49d8023fcdfc2b28b14cede2c456cb06787f55ec16d74c3876cac91c13bab853545e13781d1fa4130e689905739b0becc649365f083e5cbe9bd255bc3ed4b005b17ff569d6ba7dada8bd8cc6ba005737e80f5df14c4a282d7757648a3b07e40af85c732cf4b3ab5b4a2ee2d5c46c74e06bddb50dfcf5be7de0133dc7e565b21d68e8e23560f59ceed05acec5735a5950adc4ecdfabb0b2b1d1f468aed0427ce672d3e0a5c6ae31c1329ca09e4e4e6b0c50ca2135a1f30e2d5083b96c20d74d77755e20cdb04b62fdc48ae20cd2bf7307ed21f7bdf0e9602ded2cc6899dcf376b526c40e159f60ad2109f435256876f564766bb38ac6dc0446e6fc5cf83e2ff6918286461c95aa67e7dabf5ba979ddfd96338af4e739c29a2be051a7b05991e25049c9c7d155318e9c421fcec1fac42d5f971767e8f6cc39fcbe5b00620136ef80f94511af7751eb228d2e00202856f4d2913df71c2abf2b02cbabaebbe4108a4da91f1ac506d9728768ff47c0a59a735c1afab2b85b4d6ffeb4d96eb6de8349c4848a83ee7a0eeda83c12f9a0e51a6d5955295552e0688c3e5655502162c54bcb685f156777a7f095f5b0ed9a5aba2e4966ec49b6ed9329c7dd1bd33adefc20ebb6d174d9ed86126aca5162e8861ea7f21b43a53cd64c12935286c1a6d3a0edf584b278e91165c2af27c4d5cfe13247cef088fa19ebb9b50ed04c225fddc2bafa4012e7a1e30530cd95ca51d52f6513d54ee1838a5069839b2625d84043f5649f3a6af7d8def5442e3f22a6f41c1d776ce8f259e206ae69d5327c537abd30eb971eb7479f3e754f42b0b31c2710e382856b6af1c9987e3caa24a75a08d8af8799ad2882e0931abde72cd90bf8f0d4eddb138e6ca4c0de55121e1da4eae8bb616ae1ad94dc3a00de1989e995d837d5d67b839b6a0bc13384460bd7c06b0a3eaa00030fa81d8d27b4d2b617068aa1291a557f7750b6e626459caec50c25ba8b9c96f5beecc4a30bd570029621e0bb26f52245b513efc320b1ede1ac634f2ae668b9a175cde54ebbe1209ddffe70ff52d1f9cb14290b645f21799ade1c7a018eebf2e330562a6726949a215d2a532bbf5b918b52ab648cce52337f403998e82438cd2040e92dc85f363d977e8dcf2c958b263bee7dd0fe6b3cd9728c423e79388aa498be4665a7256f2bb8323143ff55532a6e9073900daea1255a6ec75a12a3a072d8bb05b311dfccb10859eda8be5868e18340c3c3846dc6cce73215a131b95602d3c3a1a7fcdd0d9129a8a396657908bff311b4d940e36e603b18eae4733e61430aff35ce431e5000456d0da32073dbd3a5c5b9a402b9a515d99291b1843edc6cc49c9e8e6cd232b8242361dd65002f0941254297863bd19a8cf32e2035559444a3dd0838f8c6585f31eb0f67fd5cc9c0f7bdc0f264bda06ffa52e00c5345309fbc5065e26b3fe190a74f106886807dca3963c0d84da793e31d630c16cdbf49b53495f996dd68e5e5d11f2027b73ae3dc8cdd33b191786b9dd4b84e4471eb4fcae60b347a0ad9ff1ba45e3e02ba8301a445724e772d51e7d5459d2538650ae414561f0a6d1f8e5eaf03ef2bad5ab0d8566e45287f81fcd9fc78c065a27a8a05000224e2dea7515e42cd053402c2fa2ed74762cc48e02b2568f24ac15ab297f4a0f5fbb2580507678b903f571e2315b450626fe4f28795c2143739e7c61a54cdc714a9a82d5f4187ff3154612a522ca7bbcd2649d92723d1141d2ab115ea2f4492ac6210e05100d510c05a18507f983ffa91c8bbd63e898d70e87d5f7f97f90c44a345339f319d56df9611eb4e7e915c893a88af4295817fe6db3c8d33f4c327dacc2c08b8ec1b9cbd3c8ed079cf6388aa815d269dc144770194a4ac0f91a2eeb59ae1caad7033151ce527bc4ac6a28834283ed82dd8fcbcc656121bbcb6e1b5aeb92188ca9adb400ca769b23485c65545ed95a55bb81561d316c1eb9f6cc5315e935defe7ae12dc33fa1495d6c01cd52bfd77c78fc681b67613be429ce6d53116922bc3d0234328b792563a74b2a614fb2421f43f3c5ffa356963199ef295415cbdf9a2811d60e93c105ed23dd070420e4b6e667409a3184532b622d27ac28498080e418835a30b5479b8fb0de499b1fc80e3f42e4b54d0bf3fd1851ec1fe92ead42d50c6ab54e781de310568ed41399df2d478643c5aa11249371596d77a86b31124ac6c944dd0a9145cd2b67ec0cfdf8e3b69a7bfdcc66512ddb7e657df1683888331d44e65ab17fddafaf5cfbb3e0362cce8bc66dfbcab67eba0536e88d5a0abdb266d2347b029cfc273afb498cf8954bd3f210acbedaf9a21ebdf72103b52a7b7102504d52dd0d20cf7944082bca52a74ff4b55907c48927b79e1e5f38d70b9de48bf0181ec28ac1b95139a3cd13dc57faaacd3c18eb45598612ae8e36153699b1b528ac8c5665b3e397c84137afbf5b479dc22d6ae84770f1b67c6c3262596178cb709cc26667f1d2caac1608772b6930cd23c3e800e084f019ddbb0d8de12532b1bc08406077a32bc451f22406e59fb055cf7dc0a16861d9dc680db19d93ac1ac0d1c74272821a1a6a53cad80c6c8e223f45b2f1e09ea79bc24b2ba4b56c8a7ea8213e341884445b15c6bfcf7e8166684a94f80b0ebdb59dae8faa5222aa6812877da3c7f4790375901c5ed0d1b152f42422e581ee58ca20c0ffddcb60c7b3795d5c989e6c647983237cddfca4fbd6f5c94fe79ac2633d15c057018713766cc4e3dc64048fe79894a30b3c0221f92d95477994c32ab724a9e1c66fa68e73433a2a89e1d08c712d56da68ff1512b5e5a36dd0c2414dbb39750751c7ab3b59902dfdde7664544d0fdb8d02d002d4917b4c39a0ff0301b600f2e0d501ec6111a7be9a7ec61bfbd5af0b8a9e9e28ac8c3d508d174a21b74f80f24708a9ce52aa104caa2e4fa38827c09908589d607e2928d8716b9675f6ff2efa00eeb1ce04719a4e7d834dd37edc8709f84aecdfe374250a82d3b488273a633f390a38a0c51f1e7d5d515b04d8095d866b8ef06e96691cd690a6825ae92dcfdda05cd5354d49f2a01c6ea20e0f28dd50b4c118fb1cf2957e00608d7ea0839ba8c27fef304fb29f36ca40695c98defae48e8ae7467566a2233fab24ad5df834bae363d6bce1b4639b3abe7e984a9a687dcc63a7987ac4d0506a516f41b2cd84bbe7c66d942f650e936242f191d79ba72cab6c7e970524c73d63ee116fad4d8b1f8658c3c48871bbbaec31d1e4059be678d3f4c8b40ef9d0e3e17776068636b7a39dbec899ced2a8773a28e91210b236b4f2be6c5368a730c016eb8cf649561b694aa72bf5f58ae1c63c798027938e3dab525819e7f44c609e1c5fe0a32f6069ce9982c973e5224f921f25388760d4ca0d8ce0386266351ca0e3c4222db7e72ea55638ad22c74a57d53ce6ba3a5f5e1a46005932785d0851ea1a6f1e537d145eaa609d0ca03530981532fd0a04a6b4309b85281156c6e700e536682fb7daa26e40533394de5fb501527aa918671ce1e3a1227b975064bf41ee59a31bcdbc37082012d470e5ebf79d9586f6a2274acf5bf8b24ce15e78395740a66f87024500d82048beeb7e720f354cbcf8941be0cc2c609d510e0ad3157120ca590ebaa437218e8dc088c4a431911caf014226ed4ee2ae1836329a9c0905cf736a96e72532009a9e612dea2f3a501a9c2064d6e1ed02464a7382bea98ad208a8193a2360aa30fadb820fc416fcff7f4782dca64ce1ea177968d1c20d0276ee573dd98f8e2528b313efb26a0c05f7df49b4229ce9dca97766166abbc567ca1ff8c73a75c54dcc3f231ca8eb99049fedb3a21a9d00ca4dc0fac401105520cf6c3364a5e223e46f7343e8e27f8f8016607185a54d8e957af3a3732c018026fb0522dbd01913cef0f0c9cd6e0a53fb63e2417420d97210f664d205e212860b38e7c8d58a8bda31226d781812307822ed8546bdb7870333d9c2383a7d624b704f3ebac798e364dd5cfed960d770fd34088628fef15362ed32bfe6dbf53d62052300c9ab35b1d73fd6aa19888d212f6aa1012eef5624e31c69c387c6be3154fe218c5903586961eeaa296f170b295e23317a1503d4adfcf20348f2317c32469765476976644e05e2a9b63be363aeda872c73205c90b7a8d312dd8fa2439ad0c4c04c77239dfbbb5fa6f311327c6d241dbbd428bc707c72e41f77ad2907c2351fd02f47029f6eba31e737d182f8f46fee73aa2f7db0e6d130745d3dcc2576e6c8bd9c37eff853a285e36d41ec7dc7f7964311f80cebcb5266db0a48a9bf2e7632aecdc86e6a04124e890d03647c9b029fb3cf4a5fcffd2e4fd379a34bcc5e2cc52f392ea8a7620acd8f4c548ed52e5d9bbf4ab4794117c60c38a30022fe932861f62a3cc432d0cea07f8c5e2524a84d37a232e870a81cc3522f29df0b94de6c83547a34f7182069419fcc6e8c5b0718dba8d8e1eecba729f4b29b58d198854d8db401cfea8905fb30883efab5863cd17430d61c3807cf4e03cd75f5cc39705436e1139a0509401349ef7e2a3560a585d8e0fde8733fc16dd98163e834cf2b4e34f151dad2f09e25fcff4439469ae678e730f2c6402762e4d184804b07c88d7df017e47957423064aef54ce6a30d2577917cca79c116997f237c82e34c1c91f152cb25e6cd8dc38fed27922ef985ac604065c5ae5bd2dbbb0513ae2b6c80f001d71ab882d24e294f55abd4b4c4fd31c8960b97cf4226d1531baa897cc01584aabbb7a85c5ec55703922db3fdd7f5dae9480501d8b66ddf6c81bb8e81be69387c1c85d61221685b82db91822d8e8532b2c77bb4bad02760bca09a9ea341e8699b22c8d1986bf1813ee90c1029613c5bc3462e42a47102a7ebe14f4100c7815f4aa70290e683a88d9f4c1040ce706487ffdfa86210373d926256ccb3cc506d44b9bfaf77d0a81ec20f87c744691588ee7ef1c42f3bd44c755d4661369b6c45f7c2cf8812bca8e69aa0d7d3f817efe793e07bf904109af190f093bfffeaba66b6f0f3b51867ed1af86f649b7eec5fd7861e1acb9660b422d91bdad796afeb441837f9d7182c8b670c4b546031591220706ca905003592156d8afa63f8c1dc1fac676ba257bd55bd3212643bb95a1162f9143c9c436be3701ab6d8bc496e0fbb9dc94828c5885e8d045aeb3884cd08a1ef822224387d02a8960d33eb872668ff53e340d99ba243ebf8db5726dfa52f3b9521d1451daeaa2cef858c1a27b5c092c2f22ef74091d58ec1ffa1968b316d5b597cf909e5a5c9b1d9eab2aaae491ad87cbeabbabe42eeaef7a48a3762abe8f13d63867405ae6ff25d6f6ef0dd729be01391a8b52f16cdacd4a5d24e9f798a5db48eae76435583b5d7e44bee70b68eaa78631fb0cdf66e21e510721bf7918d8b85c944c4746b6b4688cda0da913201f1302df90623809ec4d2e64fd50d71124f4a04e420da47b44a02ed2e1d723c0f9c7a960426cbe15662b15219f88ef463b5359f0394580d1ad666442bcc4c69d3fad2939eae3bd6ec245157118d53c6c845caac80d328412355e1c33bb8f66e4b7dfb549773cb76a260f36505222af4d58cb58b3ac47295096a29d644526b538c819a148cf6464c5d3d54e2209e5b4b688f62a8fb3a0b3922e1d3b945a5eedb3369d76d731f536107d0402f9d5f60d11ae067bd0a6df04ed9f8f8d216cd290a7b23f43e2d6524f4d17e94f7bedf803c2497fa202ddfd76b2271e9ef3b791ba4a014330465f6d9be68b70560a3c4a5c7a19d45f8c2782d61a5cdfd738eb4fa202ef37737d0c1ec1ac51afbe203940c978aba6dec1832918074ac7289bec5573ab937697f42af6cf25065fc532d983f61034dcdc45b5543cf1345f472090e212ba49f2e82ffc54f442d2629f02eef88b44c48bf18702dd69fbc61621fea404615f888584f6f007afda443d564245029d55c8ec31f9409fad5a27c2c54e8666bf571209f3f532becabe16b7ca0fb34f48ef9c36ab7d5ca3a1ef4eb5abdbf41da1351d1b39b7d3f16807995a501eda3bf6b110a5bec5739615df0078663d3e53a73efec50a1519f015b315be005f9ae2474be305a4bb1d2eef89c83c4280d5b7aa87eff4c36459684c37a1bd4a66659a5da343dd9cd9dda9507b917c3b11ed9c4411d299675e9b27134570a92b9b68ebac8c3fa0f8f166dc68193da21f3780464fd6267b634982ca6da1513fe03021b4c6eb1237235489af974a600430d3706a45c3b0e34199961d209e5442c85ac62802b516d9734571ac34c5d6afc40db8a7b88f156c329647d4d0ea01ad1d4b166cdbc04fb8de59e43e4f25577c05b6fbd28cefcaef16dab5645fe08ac572f7068ad2359fc9c3bb51886b640782344e7921bad8f9922001cf0a751fe15f7caa441948b870e2ce9fd6d6c803ff7892bffabb0c0f48854287916cbb0ae71890aceb0d73cf0b1a03af6ce96363e9ec46959a53869cb954d2fc5d068a90359cfd7fdc86c278b1d574657e0ca4685661fa81d58744301358e60c3a228626c4ac8894569892d2a8f1c71cde57a94b2a8a88cb9a2c06822539e1aad35d062096f816e3384b92b5f5433af902a2bfd7f97b02c7733c8f8f8af1e5f8d773326fc20d775ce34e9047c4915a725607157aff08f514d1184ce6928abb6b0d39fb6b569d1391fb68c83ecab34a7ebb76438db59d2ff6e567a48af888f60c27b70c4067b20f75bc7a07b5cbd1655a768de92ee5b2f5476bdd815a9736158c7c757bf4618d1d18d2efc32fda7f3d083354ccef84b7a4d1c457e9c5e9ff01fc0c2e7cab690277c5cc5e72d4ba03bae321472a89d2c54ae3cda7195a51b6168718c23ee689d4cef1d765eae266bf5426386a6af8486e9ed0b79bd66577de2b69bcf3a6b7ba12fd672773a6b6986d7fed0724e3862ce34c2ff2239903a2498ebe2e652b172a2e8a6314608ab7d5ea9137fa3223efbe7c04a4f23f0cd36a6f0529952c25951c458a83ba0b9de9f4b178258286bca029108e013362bf2312ecaa5b12a5a5c3b6a1f1f774d3dd4e54072f2f65d0e5945ff792e05173e191624b230da7a20aaaa514f8e0df5c213766980d38ed5102568916a877417dd5041c950fc5c88ee898473a10a0a342f74fa4e832326caa2a8f16a200dabd671098a36a9091d5f269ea23c23cd26702cfeab76dd86335730682f54d146dfde1c2ae92cd4863e4301edc51afa9e8d7190e5fbca1580053e714542b7248f632153d4954d3aa1c597c923b4252aa841bc45d124dc4c8d54bb48e89f3d48c649d31a1a988146fa72f4cbd7253f081336d624b14fc2b62b9cde8696bf36d480e1cf62a30424946c06aa51d93d0953aaaf543db15766020e4888014e6aed9eb87084cf80596cd3aeaaa910baf60ff55cb086c1a1a5ee91961627cc45889c3990fa842c6c08c76dcb2b9115bf0dd4c34f420ef6d927f971df43817d7dfaa2a75679756ab9a990e22f7054d1915b6ace483c12ae09b4e09964cb7724e92c928c988ceb063b1c6da6ca599e3aa1c064a6f00d6c3f39d6837e5b787400ed8fae4067552115681e3ed505d4c7fec01b0cc4aac186c962d010ba1a5c972e7c57430a228a4487497f6759152bcac87570e4644657bb559898d272fb04c9cd314c8700ad6c4917290cb58d34ba989896b5164291dba324f5af2339d7d9b825f4748446e6680f0db4e3a7d83f3175ee6a077c94801272c3452e7c679b2bc9e28a87560ed884f349675409aec70d5c53fec3f980bba6dc8e681d8d05b8c42ccae51a204e7b7cd8cf38542abe43fcd733cbc172a0473edf11aebf7be106b8c0f9e5bf52f14ff80f83a164a677c2cfb016ac30ae28eb4fc8a7a5b2401a50ac882b223f300e0b22f5fed4d99b887805e581814265d7e730bf1e57f41d3865bf842b4ae80632e004b1b8a21bde42c776efe64be3ac68d5aa043283e1390b21aa759d57d89e817e7454f95ed76bebbad9426114963eb162c92344b3041bf729bdbdcacac07e714d16b775d8839cb432bd9541e6873ee7236e47ee27bf6dd0c43cd01b40703b0b03bb243323aa34df40d213a0c3fa8caf5fbb6d3d04bec6e5ed2f1b2754e5be961b5e98c658fdea053561f7358a5367fc9f73f93161d0d49807b20100dca7da6ddde64a9e0f15e92b523f687a8745b8f11ece51d37dbd11a094008ef235c8326c7bf09e262759ce755f7936878ed6b82fa57367a53e243977509a0602b59b14786bd0f2ee2b1c909cd7bbf1f597e9fd55a8d5b6d87f07b8602006c55dc2cb3c3c03c1ada255117dadf5064f43401ece3c8c2eb69684aaf34914793ee5dbe7bf3a5875349daa6264b55f9e3f88ccfe567b68d3220942e3e3d54327fbc9329de1bde43c4f4fda2f5045f51ae7528ea79fc7be871c32a01da368e929bf31b7756ca174b7203fffbdc1cc0fa5eb7ff71f066530c5b8154af1960e66db0514d0cc0734bc5e7ff0d94ce19b97f4441d793e9eb496f480c4b29d7d2e4efb862c5ef4d877e6795e77cc39e6314a42e160ef51d3f16e90f75a9648c8a4e075135e78eb9de38db2fbff5cd203ffd4ae2304fd3777117fd4154d618579dfd2955f5c88beae3f5c26b892f6bdfd811c974c7210b4dc83260a5d4fea4566b5d6371b1fc2fea5a15c801a16fdf275e17014ba65113c77d79f7ca0ec170507da136006220a24dbc23949bd644c8a1b7d26fd40c507a0a328d8454b55596046ed3305bfc22f81ed4bc3cd88fe8b0c00265f7d2d83979bba4f33be987f2af6e6d2201521a06d3e805e1c7147237b309d9eb7ac0e9e93f0b833146d6798f3c8f2c5ec742b43a2413de78b9051f8aa22b1608f247d979663670a3e2ca54d187ece2734413d79c79e2e74bdaa9dfedfe2b9304bf46d5d3830a99edb9f9ef76a086cb06816d66abbe79bf711dff5bdb72d2fd4dd7dc2de0fe88b06dcbfb614d035904d50cc3ad799f7ddde0a75c7e081c8165c29b608d816840a5ff49e66aeb68a6f482b429358a8e00ead8364a78a441074e3183a9da92466674210cf93093c7ae62a175530abe7337c0cc21b152b199ff471071dbc1fe31d150089ff7338206fd9954711fcdfaeffdb37242fa757fbb192430e1559069b8f39dcab3945eb75b5cafc46c88fdb865366efe5fa516581bdf7301817f3f55e68c47ffa60a4ab336aa57e114bfb83d2e6fbddc5a1d9dc9e67f16a2fa66886cfb6a1b5ebb5ae8abd8a1e6824b08b3a98f9e9f8f2c2c20c8264f0a24334cf3380909082e4e9df7df4f387f4137cad1aefa28da7f78bcfe3964005225450bc522464aa646cb295ec863c1b06645cf590de5b344612639d96b73c30250e6ad691eb214b07b5859f253456c9fe757bc9f38b6d63a7d5341774dffc1d004359e88987ece05989ee9ec2b33dbd732a7ad35a267fecec16de07d321e16241ded0ba67d34b99a11bcafc577cb49ecc3d241f57eff58ee8059e57afeecfd766232a2c18cb06cf22d5323ad3708c77bc7fcabaeaf94a2ab3ef4f58d7626dd513931f69379479b74f081543ffbed38db4a61bca6c9c1cab30e5e6c07833f8067b3c92e216c23318d7cf677662da646b1ec91e9459d7367c03a59ca7f5abf317cdb67840fbb78ae39ea6920d49aa93449568a431686c9c8028ce29489ef977c7b90fd78fc58f09b7403f4c0430075998a41b7aa7d18dd335c3ca0a47d74f109bb9524aab6a57cc019ccc563bdbdc5d00ca097cda449f2e92f0459271939484aee83fd1d51f6a7519fc03945818aaf835a1ab726faed994b6b223a8341ef1d09085513872a2055b86d2eb9d20e986cc9a2724309838967271942bc78c68c6fcdb55953fd9da2e6c97d3791d3c1942967fd82424b6f3fd4a8db88c4f948f385fcc69934d1a195091a518b0c7e8fb35c60ced3fcc545edc9929c1a8ca165f59cfffd509ca85ee81d6aa3ecfda056b69b9defd0a19795dfd83c0498831763c29c8316912a55e45495cfd1d2118a2b6efc117b744ee1f29711a1c754660e570429c1829a5f8869344634a9d6b05b6c9644b61aa8e94985ea5ef59ef5a67b54f60a4c450b850a1b22391df37626ba5c8e2c0f2e9ffa14055513b47266fcbc83b94153b366864662646f9068d32575fdd73bf699ae90a7b20723672180492b2ea3525861ca0e6014cf98a8c9040a470aece911cf3192737d042ec851c9665b7f8bcd830040162c594b354234f1a1604ddf8c3105712f4703310f4bf5efed90af739c85e4bb8f55328f4cef5f9fc3bd2c11b6a751bdc96705af171f228c473e2bed244103ebc02682c633b3188389cf8b8896f0c847b2e3e460dc3aa4ca80f52812517fd9375231178dc73c78a44ba8873b038d030154c9faaecebd50f1da10f1eb7f69729554025e88dfe9c1cbd137ca9e1ea4aba164863ae4b34dce136292abb78c072ad94a57dc5beed0dd8d6401f7b527722b7175316165f47b75c0b072f16ccd768e90c8c45278e3a91000192abcffb4d21e8189852f8a3f949282b618cb34ad5253d7342799692c3daa5ad070bc006508fe1af4b74ae7bdeb856141f93e514cabc3c834c5ca1b8755e0b7d5ce1c6ad6b07df4c2a0e8982fe91c5c2a67643c3c3dd2549a573bc14c6b22f87631dc01d4d3a049bb68de86f5a04339d5a7c9b8ccd68c7013629a2f850cecfeecdda5f4ea36449785c0c2a5bf95b437e873a5ee7fe6eba6436eca1ae50c1b03f47d49325d2dc40efe8592cddb7b3e1b45e3026aed7aaf5e48da524b0ecc9ae23bdaf29edb767f5a26a331bbff04ddb492f8a64e2a83836d9ce136910aa436c85b94161d95e43e1658323f45ee2229960d7e7dbcdb3c42ca206850f93c62a62295a7c638900b107c37de0811c8840042eb0a412ebf9ddcd37589a3303a31c373e45d79aea6dc1f39aa54dea88066f3fc6dd96fdbfb359b780441d6cc55042f562ca2bfae51fc854271595351e4b8f33ff5676e362a6125ae6948933698b9b1a720353fa6efb4b1b62b3edf9da12240a9a401b47de63744d0c05f070c83f0246c98b92ba58facce6bdc873f1e23817988da1232887cb26115f65fdf03f32a551faae98d1beb2bcff64390b57d82b671887820715b043cf87728889ea8040e0cbc17f8e1f03d6c2eac5d8c1de7ef8d98e645d383be6c835f03bb300d58f102d629c47a85f8777999ce2c34da9fbeefd3659993b93fca6d8013323d4b37ef0934e7a53bed3251e78b36af1fc4d48fe3b09f6747c1cb5d60336e998aa2fc593a0a9041593b79d419f394d8eaeb4cc6981c2c7d80660134120b7091decfa710bc3ba99cb92b8f0a090e39aedfbdfd6ddef42ac24c5027b50a1c4a8c2720a8e51ef90518832ed4de913b6375c3306b80a7c065e1844f8faecb5ea5f55ff1e08977741f0e9e2fde7be3c0108e5afa93a3c70ad72163d4ea97027c3ff2aea2f120837f41f616b48473b79df8cdd0eb8892adc2f1b17f37980e326e1ddd21085693bde25b9a3ddedc0c0551af368b0b88f98893468708738500c1b15f5ace015796b38cba948e7f533bf719c3ae414f95c39a3841e5430f93524c3e38f2996fbe93ade8dee9fefe89b8e1e2576d135137f33e5d669ee9a56b9c61e05e97e61da3db18ae7bdf239fa96c8b4e114b97f02f430d02f87ff370f51fb3890ce9ffc9a58242d990a93e9584d2e98e2d9a473ea70b91fe43818552c6c2cc5c1a2b02ea354b27cf2f990ab36b0da8b51076e7c3b1639c7e81852c255b705b96df329941dc49ab8fd475f2549125ed27d70f306edd9a4eff8b79109506d9ba3709bea94fd03fa4b7924dfe3399aa09bc7342eae1109cbb5bbe4002ce11e2b38fad73f2abc6c86a7c521a1ee8cea929789d39ef76624e300ccbf422325fe99b69efb38c3fd420881c02eb3e4d43d3ccd6814721fbfdcd914a02f2de106320e7ada640e9668a54b5e32480f1a9da13411272aafcf202d6f3c96578c111cd4acc894319b27b94ae244d70ac21740f12432251071ffde208ba19e4986e01066f70ea7f67d8c8cc78c34f0378884362379fd124ce5d979152fc22b2cf4aa630d5bec3020204574dc975f7a1df3fbd243b0e934c6e535231434304ac4a291f5a2f4e725b0648b40ba8d9843cbd87c4f471980dcd35b18bf4e09c9e21852be7de289303183e7575f38444f058ecc4cc6a491cbb3bc0c2b875da9c8c05d66ddb09e4c2d8b5cf6efb1dd3dfef0768ebed33a9c4bc832b2239d0f10062cda84041b8882da16a23d1237cd4f70288638cd85330e97aaa90800b84f05eda706f012cc442f3dc3beaf8088b1f3145ab54c3f9a9558b23bd2f1ea4883bb64e8ee5f38cd25f566ff570988b7fedcf25c69701b1f48c7d96c2518754996f0fee2ea6f9971363fcf3cb7378add722801b5fb731e0a197e04203720dff274eb5ae75a8b91be936d5c2e0d1d78acc6c16dca9174097a85ec3108a03f1034f1aa45e0022651d678d0cd18a04a84d43d068ee2566f452325662d274c2e67900a85ecc0476f1f052f9fb2913a393f3f2c0a6118939084c9b9936134881acf77de67f3cc4b41210d4c984aaa7c0b5384c8ed97d9e61be5ad2df0e889c072f84ce64a5b875e32a7ee0403d843dd2f56613555f9919d3078cbad61f059770655b5644dfe3c05bbaab56b21c2a20bd15dc85fa56e6517bd343097f6bef4bf8e710adeeef020051eeeba7c10983d8e82d474774b052cc0484504a1a831a1a9888c3f7128251b95363f4b4c07d5a7e89ee555e1bf2b1c4e9c9b030715e849369e8c3956d9c654779ac06f07c1ec3879fc76a401a7f8aead108521dea63a252fc0a453387c2b2e8a5d9c267c538ace8fb66dfa9b21c6dcfce0f9cad69f2f56c2e15c35ada0937078fa86d689bf132f599c3ca34e4256875ce3745cc8bc7434581f9e66683e999369f5c75930980e367d99dc0340184c6771f4d0e5a3f9c7fa2d382716d2298c3e6029208c50b3e73b32dd4a0af8dc71ae9e85fa03c8ec81c56cf369eeb60faf7f9c04f8a053511dc943332018372729fd368840b925e73777a7f177018ae09ca095ac19eb84cc52153fb4a61384cc11059f62a2728a827c5ce390b280cce72ced14df6eb3bf2edd9892b42a384dab92a9e47f84ada74a2588d73a0e0d645885a81a08881bc6da7b89c48a16f042e18b07c12069ac97be7bd49740a5e61e918843d7ccdb41b41073670b798a61e7ec5126b60bf443b768dec77cea7f0ee89f921f4e20807f58037d0a9bacda27c10c385889bac0a0838a6ee245f69be64b956a368963d4ee034bae244579a5023b7f34a1c9eb63416204ea38a52d240a0d89d6e9ecfe38661209181a88109daf73e3e0d1107921c9f235e0c34f12db0134e090bb0b7c3f579e3e05caf931f31d3ce2b905ecb39131737f3facf5b26f4fe7e6b8c04c84a014864cac690f9189fcfd25db6f85b133717b483263b4d36e59f34b3a3d9103922c5cea5349db183e881c3ba97d0e5091b27a9eea32c62ff8bde0d526c633fd08dfe2a581d9f764ea896c77acdf4e0790d942b9aa8993ddb8e923e2d6f5c4109361aa00579472ddada7bb82390c0b8eb651ff5b1bd2604fdfe5acb6c4767ed8e057c01f34ae6772e8e8b23fab3cfc661474624fe477f4960cfeb884fa353983725b2b2b4e94af1a2c939f8f24ab8ee7ff36adcaf33b46eaeb29427c57c49ee3431a027b635433ee893538a2054db5641063858146a0f7f1ef039f70c0ecff47e31ffc53da337a302aec793ec52a2c510cbd57f2521645fb87a4fde6ec2af2958d13d462747a9a5a86fa9fa0b99fef00f829820fb3a38f2a23c4e38a468be9683ea87429b9abadc38c4427cbeff6b5ce6db791fb633f888a2ce1501360d121a3529f1e5e54070a837c9ca44efdf4667d6a7f2f235001345ca372c6eae26857ebe89973aff7b17edaa29d05af280ef3bc3bcd378f90d5cf7ada6955dc5ea75070af06b4b788d39f9b05668e3413db5eb15073d03f8afbf43871f6e0214f9b525d374b9f72c3bdeb6266c88c5c92779b2a27397014158985ccaa84069a064347df1c1846fac4d170318f5be1e5eb56cc61985bf5243c3885e1cc1449ab69f31c9c261a61dd8a2f0559c7bcf9fad0cb5ec185b3c944aeba2a9dd3b8cd83d838d3ac641599a72c89034ed58d113348ab64adb0d26554cfdd40986c7bf7e1f8ba2db85d7d5d3cdc49ac9e2f989ed13136a3f01a7ae460786347a082a347a9454141c718000c458a4c5febaa45a8ee4bc9eac82f31602a47c8c13d94be9e9cd3e741c71a6e2a90687082ff2ab3fbb5cfb09d95ed954fc2b02dbfe41ea90b06cac6a69dc218ce6c6193eabe4af365c0271ebde7746d47474c385541a698baff630a7e91322a6e425241a5686d6f0a21333fe0a7e87e1dcae29fac9545859b7e91c23ad042df0ffec48b03290a4f78f080e59e3011f13c1325dcb1af514eb19cf4159741b4befe620486377f5f8b0994d19991199a327825d5ffa319f852522c45e21683e32bae8e1304b46964562f0944755d64b45b8e8ada1ae86993fc95d9aa179bef28e74c30e2983628a85a36f4796315f514894bc0fa33971c25bc3fdef25ce4c605d72fce41993991b62f35b74d2c6c9d91c3463a35b3f384a57b6468f05c63e06b711cb34ce621d7e989ed212026c83a9d71d8fe6a211d7205f428ec50ae048e60651cb8b776b75a160b2ec371b34fca664348b5151b1b52a8fcb352f4dd2c264aa13c0f973c62a28d39e031ca875c1f0413cb27369a495baf622b519082946dd111f3256a5a4b316768a9d8da1b9e6606e279436b4db6781f676e2e51f0035944b83b796c4f4150cbd94664161873cfe8a8be4d6443f0aa7e048a08ce2f736e23df47550245c63c91bf0c590b3832c9acab6dae5ee7bf3f210175e4c9461f75c4ab93d425fc63387697c7d70ee5d9a38abbe986a676bed2fe4d2750fec5607418ebd0cd82e36d93108b9a8e55c6ae0fe34dc6bda482e31799fc2b095e332279b7dae068c8af9b01069cc3d1bcd928c1e3c815fe98e4077aefb810e66aa40c471f68375e60f8a496ba81edb5692b8575d7abc8c0b5a955dbe6ed69c7781343a891a59671f48287f466ec02e3bfdf9527290d00b46bdd9f84dbf3b703e55b97b9f72bde3b1dae0f419fa40c0468fb727e090600ed503a0162b711f23daab2d8027c75801e12355686594cb943441b088dce43c582f53177a4f119e97c02e9a3e6155204199c80832fef00b046fafc8b1bf4f56088ee6077f6e73981ad5a235adc22d79ca70b5bb6c3918956e8d27aa39cfa1876680302a58584627516896c36a2666db867f12074276f985ba1267a696603ab7aa25b2920b204c621ae740d77a143b0a95a6d264aacc87014a0610c1e92a695daedc44192b7c893b4c16aacbbb9e12c9c3f3e722d617f70f4a5af6df88003aa8f037645b70c9ad9802e788223153dbc3b115ef5ebc6eb1f2536bc8c9fc190fd565150ef4b2f46b79c4ff7aa256d8943c626631a738fa1eb6e7327a281b7a4bbb53fc340ffd8ab47298b52547a04aaee5d1106c81a9e9fb1b8ffc16d8a40d0bc83650e42485daeccdf108f363a983322a55c7a5133483af8609d56860e1ef5c78fb75e05636a90a3641366dfa081d836a88e440416cef8875504a3c5147ce98cbe05528a1e8e229b611e3bb1a28341e47a196931ae1fbdbba9a92ceca542caf4db7ad4a7dc99a58ab8aa0d24bc1429162bccdeb9cb30530b9e5cd3ad4d1618cb29fe3ac0726c71ffd4b19cbf61e1c8e3d0867340e6ba57340502207589c7dc237a0409bf1b1fc6520b372e4e6965bd5def18342a8e8c907d2abffe08e5860ba01de9a2edfed52cf2432975a3eff07bf3679b34e872138993a4e5310a2b1fe5bd48bdcc481a8bb4143389bd73a7e7816290b33b9b609301ea29380fe44ce498a671954ee342e55ddaddf8d2cb15c4a3ed82ba5c5165854dac4f7fc82f82c37cd77022438a517fd01628f515e4c872ae25d8af1d07310ae848c36651d834fe4d1fb8f68fdcd2acc2bb2fdc9bead38713e9668b520900f531cdfd272ccbbb1b72e6e677aa66bd538c4f82eb3a1d5d349febd312dc577458ff8d397c3d4883e21c8f6a30cc0918db0f457900cd49523b067973d599de240ec2cc72c0b7ecf83c1ec4e1daeae01773b839fbfa7a38d521d75e18d08799eb645917a6aa95e548e0d19e0fc516b1f962da368b9dc33585f4c6243334d721d31ec8e8066cc924152fb52297aa3099da70d27aea79f482559c47d273d07673e0be0178d90800bd57e979095688ce5380d063713cbdbcc91afd044aa169e49483034fa0b83effb162071b9575b18da5aa792310210cacd5f06b5431d8392813aaeff41b4b52c0d1b8a0fea18b84e0ef542aee8dcb2272744eb6d44849e0a1724120f39c9ce3f5e92ce72f78cd9ded2eb0368dd331e215a619e55ba41ba651779ad324bd55b3eec020df8b3c5bb073854edc765d2cb7b8626506cdec7ceaa61338df51e137d69575a91e9d3f9a3a1bec997cea751080ca195b19483ebe8ed1ecfc2c7af3ac0b33693f105a12def795cfe64f0adaf2c2a8cc04194d6c88351fe077f0d10c8af5cbe57d912203632bdbce89cba418dfe6a2876b287bc5fa8dbb21ea4bc167b19c92973eca612694972d2f9321ab8b50269c3ebef7b3310204942ab828a2087744b445c042eb69383b451454232fca11d333426ee9d234af604529e7899048f6a2490ed8a78faae832d20ce3f3b31cd1c08eb816c8654822aa6a7e53bae05db4ce2894667a07189ece585c684b0d66ea7f3ee24a565012eb132af3686c9cd777fc9deae96c5347e292e239509e9a0943ee7e2a677d0bede172fa9ecc2f940e8038c7e04583c1887021f6a8910b9bd40bc557f6c729dc6df6b170075f0e2df1a62225ee0ea55fcad3aefe7c68a56f73408fc718f0d5a28aa3ddb8252e50fbd6986788d8015e365dedb90db8873317fc472f6530aac84d869974a0cc69be77959a3529836ab2cd5ad13be0b7b99949ccdbc320cc3b07df37f784c5e57f4a1fe5f38e3758cde0743e07c7e419d97a26c9f2d0503eb98aba575e723ab0c22f97c7320c950f9f2ca8af1208814f391d1beb29463fa68e3e5fbfd035b745477ec012c51cc8269d638689392a2ba7e658bc84c5bc8d0a0841843e79795bd323fbf68259b0fdda0c5c952736c6f6ac133f5ae4d5472c9e3893ff81473ba53904506a34640e3f6b5cf32719923eb8200607aacbb821d97fc6bc2abe0956773d1f6c6713ae40cc0a806360e838d5bd9d515ad6424a6ff3fd04618fe36bbe06ff8ea461fa7589abaff81ee8d4507eb1abbaeb771043d2989acebad62c6be310eba069e57f0269b5dae895e26498b4b14bae39fab4625f31739611f7b6024a43316c223fb158db6b13b87b21166b7d5e6f7606c61a433d7d64a173c696a39ee79f5b3b777e20ee67e9f01fd73e2ce587bf0f6d52c77e4ec22b69481134d07dbd952217d3e835dd220d595cfa7c24768dd74c8a79e63391d055e1aa9e8ef04d0291fc594d8cbaa596df39fceb4bed1ea6a474f4550e15e377f9c21a6eb31a97c682377ecaa5882fee86de1b854dca73b0d9233923796d3364fc3c743ff62d94201cf0f30c5cd2370a7454d73489db1c13c76c5c0ba8d18984cc1515e005d13aa83f23a670fb62650a576f4c9c298357085dd8d321660a689aaef300024440ed6206be6c034dd1b63f2bfd5e192e592d1793308e2d64096bc57736e9b4433424fcea1120f7b0f90cc1c30d507e6215a81913301e22eb1cea93af7fee4621a287a00c2588208ec024d5196ad82f34a8317457292fda110b7fa235a7525e758365f56738fd180d5e1b48a784203e0554f30521a834e837f2bbf44a7db6b275be2e2d185d9dc7ed7cde7478ea523ec1b987edacea7de36a9728a8642729adaa35c093f22ea7aee4427e7afaefdbc27487d06fb836ca7c7944ee6d0041cfbfc9634ab6e2ade2c13a823cf00d603569a3d0c8d8b251688860e9e225e300ee3b5699d0fc69afd6e6a95292304ac4152311359429853824dc5703824eb29309d5781aba626ed73337dbe189629248a99f28482f4157be885429c89292ba5ab7b718c098b47bbd57928c8c0a1261602ea3e866d3113354b00127b2d4d123dba66bbc728f7902440a3d89e9c4a0ce39b2aadf6df50dca410d5ef42e3c19fba01650c2b1c3be5c5ada3aacaa78f06c4f06d6b4bb887818dba24ef35e48df618b74d70cd5f32216b07d68eb88c621a148fca0df027b9234fd6a8dfdd5c31ad44d622353730af211761c518058c379ee52fe5e48250e885ae5d70175d768f9b13e40e4067bd75632c2ac3dded4587d9575b98bca7506061b4d270827ac7fd859cb75b3ace21890ba35a4d0a69e4738beb255d10192f64bc13ca2042393fba8398b28c59ef80245036699da8fb95228e29f682fa549c4637b8f2276250e923faa944e10f947d7a4a821be89943fb9f1911fdc38b430a3f3077a4bbf5a8617e34f2299886a9441466cb2af85765f404c837861ddacaca14c32e31e5e5bdcaefa9186fd774bba26863fb2b8becf7253ab7ba019541520c81b154fc9fadbda940f61cf42169cb27ce500d9f0edb6f3dd8217e0af71b662e4426d2b3b0d7f37e935a94d17b8a4186ed544d6879ed22edabf946bc3a5f4535807f7670c007d52bd4f466468d947138ed1a847187adeec84108a1ece2b01e184840a2a24b07fe60cb0de83dc76a4cf99e3f4fa5b4a34b340fe9312bf305363bb8d97ed4973302585c773d97e4374d0e0d5fbf208d716a77d44d31497f5e80893fc2d044d48f91821264968f2d9f6f19a2a3be8421f4acaf6459b455e5d76610e443ca66f9fcc659b4304133d5414a8bb875452984ebbc5e703328864bea78478a5ed3897a296b0037d90d151db3f79c19c64f14adfe8539a5463e2548236085075002cfdc6a543719dcab52f9c71a67015aa0a0a295e5838965a109e79b2975158e2567c0cfd11e51fa9d3ae9aadf2e1f9bfc76a0d4968ae2ca1a2f90fcea9689578b8db443086fc17ac09e411eb0c25aca4ea9c58fbe81ae7c62763d02026f1c53048fae4135e7c500b5ebee3e599e29a735abcf5e8ae43be4878be066f9c37f96c727c65f7d525ddd273555e0abdaad4a84dde0f3c27ef099b872421b1dd1dc53879270dadb699adf06ad37c533aba059963a73dba4ac9517ee9e526a47282ce14e9de4fb2195fcdbafd5cd7be8fc008c198ea2f2c02536cbfa0c7d9a80280ac6fdf6743a28956477bdd874a1ab2ddc4a6b850ae75d5eb9a10a5a891562f62eb6d888b38cfe879a45efce135cf1397d004fc36a1a1809255c85c55140a610900de2a9898dc490df6e01c189d5dcf7338e2851455dbab25d94fe88def9aabef3334ee4a1abef1bd011b49c733da7bf1cb1111038889c82039cacea16ce858002eeed57ab682055cb78a514846c29a8387c863e49e31bc7c0aa4e1b83e52e1e40de5e3eb52fa43d61756022e82b26091c2c5171746ff28b227f119080392384a55764e35f50f1518384cdde3844d90da4b99eb4541679e6d063eebd9b7d75885fa6d73442a94be876ff3c8c45e67a70e9947993a756a445589a46a3e06bad3434a89519f71d3ea63c8694c583420b4d498e899637102b137e0e30f6c2ee779584a9655a31bbfb3838ed5f0184bcc8e1d74282245bf4eeca888ed708ecd35316a74bb29979996000f90a6e2e68d6bc75d689453be5b9d13ee6d87bc647f52fe98c51c1950dbbc015e70be8b4f80bcc82da7d5bd0dd9d29f4726a793bd6735c9e56c7f3b233e490325c5ee23a66441abc2bf3968f4e712f115a154576090055a53250d52f0d16536cedeb34e389112a25b1a0cdd0cde0eb101a37dec22546157704f411a7ed00175555aead7f0da932532adf7e8512a3dd3f5bd911eec74a1a2acf8fc699c43cbe7c54490b88d3ec4f4cfd08759d58c27b380d05f28b6d215afdc7f79a745dbe3553145e300bb017dd2f0a1d8bca99b2a939229c68cae867de88d8a17dc0d26ffd6440fb183516b384fcfdbacf27e2d0e6fd05822c886f872764ede37ac1005405f918b590dcc916089fbdbfe3266f7d0e8c453fb74308a736ca14e46e04e1645ada74309ea90cdf9e45fcc2b6b80fc9e420dc8cb90ab4f4f83655345fee478cd8b020f500be19eaa8ee3e741fadc57a06f81caa08204d5f98356eb94bfe786eec711683f89a047d04a6ea3217566f3f25efc9ffc39362e0dd5e37260139f13dceb699153cb33a16bb246ae99c2cc2ff6223b309ce204384bb946a06ff82c4ecdcf5b5ab0e8cef268af51d8d4b9a1a46565785c5095071e48fafa3875e2c297a01b04b6b8e2bb56a7bde31d60462e1e4e28e4dd360b762b694e17c96ea150339b417c6dc70166bca55cc2639a71105366537b56d93a81d1594215b46fd0d13c12b099813f5a176849660b686f5464995050581da69763eae0871cee2dd0576b62808af46b46120d2d572d049c2cc2cb90cad13860adc57c5160bf3c1265ec28f1741a5631ade49c354cb05fb8bcc2bda74e625c93a4e5a35d52f48de12b4475bd4da1e824207d520942d1a684f4ed9e6ae958faac307fcc16013c9075bdc351d7daab10d728efef4dd1675b94c4d0aab920afcf7b86461da940a8856be0485a6dc2ea3d33e246d8003948d2f0a5d439b7a8727988d3471fe89616c69bdd9ed831eb19ed9a95be1d2d77d6970ced3557cc0ccb5a0def14171f84d2e50cbd4d6c0f36782089041fbe7af430408d1579ee94a443aed3cc782a96dd1e8e6c6460b261026f4d41bba4f93a14a18af6ffe9830f2cfc341330626685c17b10c592e7e4df47027db918bfbfebbbfacccce51177392acc77ff964e1698a88166408d18600a6220f3b47a97665e2d451a2e6a65eefb6826fe9fe88d7585177357aa4cc9e27d7791bd1763f26ca6db1fb0d1b837a3304ab5965ecb647f706d71f6272977bcb7ceb8e2354db9ed5bdde25f2ae531848fd707f8f0ba7adf7f099c7a454dee7f856ea1c31b06d606ff97d6e2454cd366cd78bf04c6f22c8b29e440f9017c2d9181253d76ad9017c0c7c9f233f404c97081e913b8310e937c9eafa57d53b85d07447a81866fb17855c8ab2b8781466df59733d46a4cdb4472d77cc675bd2f074d9dc80d7b06319f14f4462c48cb6ae27dc625ea8f2e8ec99e9fa95c14a7b0619f82a1da97092b2b1527b77aaa7bf222358e4012f0cf65def2a460ce3213e77513c405587614418f68e66e824874803311df3f999ab7f404f03c9230cbb6773e8c8c4cac9ca0c603ad1c453633f902161170f784ba0f3313e311d89620cfd1fe5c73967012b81de8d1860266bc739139895e911f38ad04664f7ced96d4394630d4d28d23e40a6acfad442ef3044ea219504bf1380b1202b044e85027ba2f150b832165e90958ca59940ccd552aa7a092cc85eedf8077c908482494faa30d0da37ba05d8f12d69a0e0d1da25a451dd4720f0d25ac863a3d486be908a1d5a17e35a8aa0c89a0903af060da1efa8ec99c4065d726a22766620feebe9e9e74d7fb2ff917a279ab3e45ba2f5906f8d55d518fc00370f112a370edbf3f8713fae71fb2b2621786c335d14a49f338e75b99d6e514616244ec244a66d9e25dc3794f4b5bb52f92e42a309af3aab04ee66987a8391e746df02c286aa1005ffc7c1371e1cafc120eff56494869567b6502166b0293b089f3066603b042679cf92fffae2ac317fe16bf4b87841b23a1767558bc9ccc70c8d050ec6ddded14b9683fd28a783198366175befd99302c25a4e886b5f2de63122df975a704e1bfbf5b97696f6905cdd1b2b2035814663c59039b23ad60a8788b6816a7891e68bf12f2e299deb4b401d151425bbb06b4005d4f8c6d187399adf053163131fbd744851aa9deb41bf1b199cb9db9051d13350d9f75daf35e52bcd8a79fe505fafc960f09e54f554b3c2c55f68eeee845e9c28506c543c7fdbd95211e55300f1aa41c2f45dfbf337b2167c36313e879aa8ba21ea14e38da60284fb37e68eb58fd5b57a2ac205976344fb91e0bcf15d446bb32d85f4f70dbb6df52428289a5172134c28f8cadebe84984a4a6c82017f34a780611531e3e1a491fea603c54cac59924069ef6cfb800d201266c45eccd40e93adca972449c8a6672337b2c254f6e1e1adb857845b4d3322c6376406da41e0c174a002a13a5f0738123ea8218a1ca670d87f1658d0ac263764c48e23c3065d564619e05a68632052688c552a6f15e77cfc9f0652cd214f9c0044463460fd4b562031d842da2e40f58584614e896d0cedec8a69f4021aab53b942b259b39e8c42ee3782165f4dc43acb1332fc580fa39df89b55ea0f931238a557c5b6d88ec93291ef0eb721ac7d7bb33c74443644606699822e74b32c1de3fdf2530cf44cf1d12cdf76460be0cf8877a50c075648b7303eb9d911e959511b026ef40f213b1cf5f56fb3bdc44b66a49e4d627ce35e7e52704db1a19fac9350fdaf64c6acfde2c9b0633e8ae8b6be2ff36b27ac43cac1cb2515220e5a87738972f325824fd22e6b7f8885c69e3042b7191d61b004364d89ed3ea65a1ae340944628a3e6f90c4aa194bb33730354d7711b16ac8cd7fc94c1b6589b151e3e0d00d25346654c9103b9de31166288828e105e0ee7b56dd7121542ec7c0345e9014309f9e5f86dcf932b7892805d856171551ab8840559f3e7f0d24c63d900163cce1f65a5873bdcc6546902a1957a68bc1407b7e52e90cf120ca87e05d41831339834858ef80db09fac0f7ac672761bc048554d8f1f9d4a03020d27b1af27e3b2b2f981af778ed674edc8165745239177d9613f72d6e5d65875ad5085121fa920b6769a6824485b8a73352f602ebb4ba36c452beb37c52f0f6a8d9fef3778f531d8ca712100bd19d36bf71ba14dcf2c63060af9c3d73e58dd67da15029d97a6cbbc746a84e8600913b1320f4c86dcbf278dde947f9052c4a865af96426c0ab3629ea190de96cddf59552e86e151a200c5ce043535fe10adaee7a537a9cb3ebfeefcd0befee356719664f43f6fce44de17865fa44b430739caea90d49129b841e6b9840faaf7bf319c6b0ae631c28764c719196f3454a2619ff5db129616ccd6ea684491e83e2ecaf26d1a0c00203de21af9cef4509b79961b0c3836889ffaaa7eba7fe8588d691276461612240477e115add24f036438bbfa9d2aea987000cde69bb6a72fb2dadf4aa66ebe3892cd88c34840f1e11f6d92a55d90306dc44d5f2fa5810f5f76c848d2d07ad793b66137a1a9026e338aa0c29d87e1efa095773fc58f4324c506bcad8c3401bffab14a606f67dc5bcebe14af28a5d94c2cc2a112a4ff156a2a901394fed2889d0b1f2474792900dc0ce619cb9fc681d01065484a8e21402476a1cbb48ba9e7ff611d969f839b9259aad21b3cf08f6522a2a72fe6e246a9c41bb2c6a20f28341712ac448b9ff69a352bf0331921932c4be73e90d063f543644a96f76724abb6fc1c6ccb2a141a03b506fcb8ac4cf929ed51c084ef662903b9704100540b55a488356dc7f082e1f0975556658e008b1b15f165c7619778e5c9d8b582468fbedf8b4473b4b4f175aa128ac47f7ed8b81530b81123820f09c5524d56b03ac7826fa3df532f89b110c4fd818382b1dc74dfb5421dc351e03231a3ae21aa500a68583a769dc4c29e6254e13616fbb41c9315f02c13eaece9710cef714357ae4ae8ea14a27adb6332847ac19a906a97fe12f91d367911a4c470d6c7ab1e4778b3d915a93454a7e4e59bedee0e5e5a14dfff5a3cb6208798fe22ae54a128867ac81f9a4f452808111459881a02789f266b838cc1fe7be3ce8569d2d8385fa19449c9884342249dc2d6985199a74dfeded4ba3ad0724308d5bded47e74ddf8cb78c1d4ef631c1fb143cc168d1df724e697bb7da3b8bd3afd3dceaa3cb29d1c2aeaaea024ae9b9f529628010f8afc4130f8e3287688b8d9f2a823af8a18cdb4819bff816ed0fa95048cb5c6ea13fe848b468883c41a09f17e37cd1d018437d1df080f507c0c9822cec49734568e78bdd684bef6b8df10cde6d803aa8e45593e8bec39a1e80127a239975046f1b19c6c6702a0e8070eae103cfbd5515faca38f097102d22b09525c7a8aff0054f77eebf5fce9ba00a226cff44384d256d2cbf646760b0a421ecc25aa96d1647649435ce747df8fe1933272f44139f2d2f37cc721e013015885add011d0208d0795c3607bba3fe1127e3a603539c272d62708d52d2f37d880e3726b0ac55b7406c1aca5038d450eaffcb31c88b71e91104bfe90190e03c50dc485131f160bcf75811115f3f57a3c12c12fbcad9a25685004f81b3e4e422a6101282afc7c3028b86197dd7dea1a61b5cf1575e243dca003a50930cbc517e7d8538606233c32ba37f7d8928aeeb5d7320c237255054409c105c2f75c3c60a36d7cb7a1632b3353cfdab53d43d73773e9c36c5efc847e0a819f2a340fc9e49907797d535e31c8a651ba480af015c98600346e04a2a97aaddcc2aa47af70d8f828b86f11e1cd4712dc4c46edaada701d62dd9924d7bccdef6d304be2a85656e12e175b76a6f353d8f4c6b0a941f2351f619c5e5740e5d9aa1dccf99a2b68c5d50f3ba8d167840e3faaf3b1dc4d0ff979d8e2b090ec916845229d18725b06f597b2f2359fd06593c9057f1e8a8cc475c0e4a3fe745919b721ccca99b888e444423c3a1494559e9345a2a901ed7096db378b5127447a31c518acb7fccb20ea53b613575a3b078b9b5eaf24b045f4ab110d996eeda9ec15d1b7bf89f4d548347849549ef480ef0ce0bf7ccab20411531220a20d3d77d876e105e02cebf2f129e906106912fa1be7f020c99e30d35fcba4a8b5ad342a228531d257ee47b67bc45398437b6826913abd58048399c490e033b07a917075800359e6f8949060e180a35b7d82574152a1e9e7998391f6ddb5b157003f17ad50c40d580ae3fd5a320b30da6a9d5213ecae2fe8215f7d0a26acb85f0c772d5603438a9d0edc7cf420c67bdac978d75ae132a08e518c77315800db2fa4c52871450a2f44eb94d70eb3eba47c7a28e910077db28a2ce404a29a9130c61bdd24cfa06ab6df2d688d7bf5a5a8859098313a4f057165e8dfb73fb9abe802cc8690a9141a6e5dffb2f508720ba006915f471ec2acdce5aaac040438f094b0ac947786e4360e5135cd229fe811520863d45128bccb41413d17d32f97689a1189964a009e0caedd78e264a2341b0481e5ac9414665501bc386e657c79b371a736205b770a67ef25174176893a0c9f2709e14363252a92ec2e10918a8bdda5dd8db433e0241a3cf1062f064b534a8c2668dd5b7dd4b2c1878a9f9c510b55d2c990f819d43e438d8e03c75ad39ddab498ca308bada6d51537826ca755870444b49bda83c51b4edf6283850d3465a46b7a5918f296c0b5040934dd76874f376a83ac3bb74860ba07ac7a00b267edf2638dbb85cf293a281fe73d058af830aef9d87c0170fa790c7bec95eee2595f64471993171aba06dc0180a89a2e29ddb6674d1054186e82d20d4f8164e164b078ed49772b78080cd5b955ae49f7bcdae20e45320a4be59874e7f591a37b46303563e99274777271316c9006d44737d20811ad68ff462d7cab94318d33e936fa4cb07d1a5c3d490c72e7e411e18f0647400decf95bc540b27702313aaa60c21c28c608930ebf40d629b7608a4fdd962fe78017f0fb0bd367ff2b258af13bc4d1e0b7d625c90d0213cfeaa9331ca72a1bc70c910deb150c0f5574d10324b6c5b09156eca40582bec81027ca271e4b21d1af2a4585b337042a3ca8b8cc7da9d808051db1ee8aa1c938894e54e2d9e2563d4a59d467e2a8a357293884617148c947c58a7464ccc57470228e7deef8e70146530069aa49fa23975f9055aa6b735a27ce4bdb0783afa24a4adfa493e1543ce6a567aff2226ff3b6541860ca091171a33b7ca9f4df459e8903ff845a9fa4d87243810d57d7ffada6bb8f24d247cbb36db877df7bda28ead8721b3ddfe147185140a8e9980631625bcfd8e55893d103a32e0adbc6877e28a3aac50bc0e86c65378cd44e0d240f4b78566d92f83eb0992e984cbc4f286aaacca4d6dfae22dfbc0f543425fbc903478dbab3343fdff9a94336f154c06127f43db2c87d3b0aa52e3b20639132b56bf01d871ef98df05966afe3cb60c99d0b4c785c53480e5c6d5bd3ed563525cab0e6ef924a158f2eff7a27323fe8def7763762a41d9b92c8f5eece23cb596efb5683a44bd490cabeed49e87ffac64e245a3e310e50547fa21a2922e67ea2dcbb9df44828a9af319771338945b16816bc0cc70a2d8ebc166d4ede75ed2028af56841e943da9e4d0d5dbc7520bae4c0de828773fb53cfa4ac80d920e1ef9db2216fbe7dfd15777833e24c35e9f8a8d13bf9e11e8427e5d2c791662e4c930a6f22f54e68538cb310bf2f85de14b76f7d2072a4af3fb17c5f4d2184ab904ffaf6353615e06ecf0c4c83efa8fbf4bcc6e40328abaf1403c7885a5dab800f2083f4e7a0d415bcb8101e82050a2f3329c82a3ea96e98888330d5e9dad3926189c03e8cea2f5283ef6a17c8dd2e40c8b328a12981b867596143b095abb6203141e85c623d48a67ca8107ddb7b3278d4aa0d9e2618f7d9edcfebc594d5c8045e8e49668895f1b1f54feb519a6d3d2cccc4e8238bdc354c92db4078995de9c5ce02d49f608516c7c4b4537a2986db55369f12b72b4a5114139829fcbf10f08b777b494fbe7b5fc6c8c11f4758cec291b9905d25ed3863514fc80470f0f82f129bfe88c28fcec4333686526f389f26cea3a525fe887cea7cd5d4a70d44d076711067639138539097b5af5161f67cde416e75ce085285474e485d1d59e2d003bd3c778a78b023c7b1387097dfdbcfd6625f16e435748c521ea2a4f452de0dcc18bde63533bb837fd213265c38f953ff8fdee911abbbfdaef2df8f5de6bd9f76f56fd72f1f2398b9aecac4e32e2f79541ffdef3105fd5b296705b925af93376a68972bd882ee81e3f4ffbf60c1aeca6743664f0ee5133fb38472a6c39f6317835e28ace65e7474e99465bc0c235819ab61ae6445fffd0159f2bf11da187ed9c9cc87fe10b67319fa6bfdf630602860985ad4784a18c9a36ff3f52a61ae28b21c9feb6a81e05085eee3d8c6cb9cf893636e5d1fc44664b55f1005f35b4eb9ec3ab93c4a6c01926c85899d17a437b7c7bd745fa2d36775c80597d43845884747f52720bbd4d9ab2129b120fb5d222908af7c17c68453bef0544646bc9b52492b6d7851837ddb1982d42c6a8a0a735b8a6ad9ee4e346bec0143619dfc1194860ad9861a8d5a477558952781b84481085e6be1765160240e26137f9fc3ab7fa7f745cbd8e9f97561953a9c02f9fc2d6a3c264e2096479106d956a69088c54d11655246cd93e12d4b810c33d930d9524926309874200a913b1c2755119f3461b39fdafd86d726887a8b0218c84cd78b716a55e10ff5764990d0870a58df4a8ac99ce847e9d9b5a61d1eace785f2280f460481a9501aff644c28660c685ed057d65f7465504273d4eaa5b3864439f23b1cad7489e5dff0cd567244eb0e49c23141047e6336dd1046bef4ff17e949168a21df1453a62a2101b179e2fa1acbd5474548fab837d4704d8328ac181b4b37f4e491b9d6c6821578fab7807c8d78642eb09535ddf778dbf7e3c1a8174a7fd3c86f90547da5004f371dc794a1f6cbae88effca0a6dc590d4c3f4b31c245e40e4d401aeb20609b41528bb8af929ba5bc4003972594b65571548e264a9af1f6eeb5c8722654d6d81b4a61aca5af2c42d5db597b8126a1276dc860342662f8228c1a0cd2f13c0709c6b8ec68e46f894ef28c775ee049d4daa253d8c78c2c2f77adaa869f68bf5c2b83826c181ae59b73954f131f4c3651d7ed0bb09d51f5893565574741a1c9e64394510d9c233b5128a06a76382daca3e18b778f8be8b2febfe94ed782f5baf445c41acb8a73a65ea98d8820823beac0a50afd1252a8641fcb8800c2e0694e185069929b9f8366450920591506b440b95ff6d931b818f6c8539c125a3032e493343c194fd5a506e7de9717fe8b43888d657d63bdfcee3bd19c3d021238c803899fa60d24e33b5c8263c434c44336a63a5b11713e9d748be5db1de927c7603757e127e912abd8fd42d3f9b6ddcb4ad2327462d1964cca8bbaa28ebc296a5de7746bdf516e82bf3065dc0c165457d3845258a6d819350495b2d3e3c303961eb8ff2460d9794845e5494f731c49940134a64fd5dd0a92958eb91ca13814d0c4ed78ec0004a581231438b66aa117291228abe2f585ccbf634e63516db435671f803ac3157005de9908284cf932fe373d18a8f0e0277312d5c806b4ab5f665153399fa7e1278f3d5a8eea451ed8c5d7d85fd4da9d70af5762b763caf15a1c0e7dae15779c285ceda55081391b4e35e224862593d219ec2d1835022a713958b709ccebbef3bd89e9d6111c6fe3a2e5c8d04e9485227396fe34fbacc317cb738451562f292a12ae42b106f7cccc1369990b95e5a84dad998e1c953177a40076759502d224128c2b9ea43c984ed1dfc09b6693dc2b4f1b8e399cb3dc1ceac3dbfad642a883023cea85913966618ae1648a35d4f4b292833e63449e42401fde88ccfb66100d462c9cee7ff211da2c09d26e453b499919936ee3493627ad430b2d9667016a6e94a65cc4c46a7d90235cf4a585bd552abcc841925b3b8b30b46e3cb7649c738f38228efd0747e7ebac029992b97c52654b26fd0cccae9e896c936025c1ccb9c4005ad5213ad1afcb13e97a60681bc50c0519bcf70a1c1dd77590a2b86788459a47f16a97909f78dc73770ae5de15a8364bcbc3e35b84a64e5e26d857daf71dbdfb5a55985579027e7a793593e393a33e22aa6959641305eb1227f43937b592886c52abd4eb294dfd22475b98e3a5b431ac678a2bb10f9967a02625250431087e5d13e4333f6c00add16799dd0f2943a1a68b032e7804d4ba4cdca1c2df58d927e122987c62091c633e33f5b133dda892b25ce8bfbfbb7520e77b4b6205a5cb0e79342f928f65420329477b4eb85c1d876368726573bd84d7d16c08351f793f935eb6fcc615d6d4472e7f6285f9d1727c6ef889301f3839c179a7da1159cf1ad7d3d4d576215e8152ec6fba631113dc69c03dcb95e9f8a06ea97f596dc9ce39658803a35eeda7880deda2eca4d81560dc0b80f943842fa54e81d78b2eb8285ee472368c3908e6c29a822421d6e434c8dac773a62e8237d5a99d2dff0fb1a805a08bc23bf9c737d53199a02e7f9ab6728433720fdefef05a79659f9798e8436fc634998a6bf77a0921268dec01a29e0ad821600066705cda322b0942b6a5fde571a61e0678d80ebd0908230757f6142129ac318509f59d04041249c505055e7d62698c7006d610f7572f6cb1a025124cff694152481f4a062a7435eb76759c4c8c60b0906bb8230e1addc5c45fa645e1b0543e1711ffbbec4468b207ac1ab1a20b54ff0e79a9412044469ed2fb5e1179d92e24be3dca770398f794f6f5ede03d79a7108771b1d886fa7a997b41cfddf52237610ebe1c029a8f89bb211554a488387298a3c21fb0d40c96909558920b2d0339e9ba249019ae390ffeb99b7cdab6d33a392de967400f7f024888ecbf1115b3033c948c52663a79e840bcb3cfbb1d20d631fd4ff50db0f3a745bf8a98c6edf330c72eae6f61b93ac97b93d6f55359ddaf76d3eda3906ae0e6ce2465a0818cb55ef155389602bc8e4757075e18b7269a264b321c00ef58f274f599a77127041754133c5891c58ca66c9e671e7f465a4bf7c027dd1f34182d9fb153816570f6f40e602cb2250039e5999424c738698471cde073df46a8173569c6db0076323ee137b5164db77c1a7acc560d77c9177401520fafa9413bac9e23154b274c6023399fcb301cf901099bfe0dda78f8fc8db75765d3a857e4bfc5cb79295cb13a7a8c654250f9d6cdbc7a326bc416eca002cbceb30f4529d9dc36d7ca90788174e5ff26af9a45764c0a9655f8d8dc8458c4b2af0a92d75f33a511554ad465cb3ecdfbd45eb2a5e91a2b74f51cd9983bf92bc2d32b3d4e034bab0479304e559e908305fa7add04b986404372cb39033d9a164695ddcabc884f72108b6d30239d4f8434e53527a7aa8a0c6536b86029bffb9228473df97462d41de9d5838d02f364d2240f2ff5046d4c611860c8bffecc35ba2550a2c447d14185bc09ae28bd59f2cd1969ae4d8ae010feab22a71f5dc4d3cdc5d424ef1393799ed5ba1f23e33cc93cece99bea6fb93f950b377abee6c80505f0cfd23acbaf5117fd54e5634e5bfa0a1f1208eee8dcd096706ac748005d7c64ae940dc7d72fcb849f142ca3fe936e72c84d4d521aea97dcbba3a74a099a8a8cffe0f45b52b09391413883eb161da7c38efe86989974d059c9f842a7b144f0e168349fc571ec2553d549f0de50ff478fe5ea0a24a0f106700e8f56ce22daeed89574087f7fa6b634673eb54980022c68aed03209c423a4cb53a81baec9e4b44f91bdae3006fcf61c9d661e4d9b342cb5a614a10a2d8fefd473c3ad56da07179083be44aca65bd15d999173aa933c1daa50258991f1cab2efa04cbaaa273c5584b6113f6016b8bdf79b8ce3e62fd28d540fcee28bdc640725b7ca1448d8fcb7571e30e91f5a6ba0b23fdded28858400f370178722f5fb70538ccf202e6beb26d81293227c3b651650ba9d8f2f3d61acade1fea2463b38971bae63298a6cb2ef4e84d9c8f21b2680d495cf2dae338cfcebcb3150ca238e2fa34173d5c6409bacf91eae00dc8c4a906e06f86a41ba571961e79634d0ddf4887f9f96d291e3da9aeac649266762213a347c88db941d3519249f661915bc24e82fcb0faabb0bb5ac30f83ca7f1e826e9828e7cc1bf480e710d66cdaf124e1bf8a214556ee67518131e4e7bd54527d50c3e1535cfc449e8f5a45768845093c60e9b9628c233c92bfc01747eb181a64d165776d0b22ee13418fbb1a172986b3a20a24dbe1165a637ad3e990f32a50ca8818493c062e1eb4c40a562ace5e5657cbe31424ec248815330ea675cc0a291ccdca40fcb5831a5b7fe5f2af6a52bad90ec2113d19569fdcc3ab1f045427462610610cc211b83e8cc62a3274dda7c07faecfe5a94a41686afca89513f265207470495e741b9aeae82690ea82c3c57d854766f0b825037652e87902eb676a0cff77cce229bbf0f5f9fed23aad95b55c13677ded6cf1be087137880e06bd0cf71b8ed39ea289a2329a98f48bc4c44d4c54061da9f21dbcb5776ae3bb7a4b7c0baae9ec94ece7ec8a7a8b80a19afea7da117b641964c94b9667717175754ac80d26002f3736e1482db3fc840a5da348cffe13308c22a43f4a11dcb9d0800af0b7c20d4640ec0b6cc2342af6490cf3017a6e518a198f1f2290b11ec50db7512961467c6fa9ec01e841a5b84fd1ae0abfa5e6992eff181899e142a1662e529f7a4b96c2aa121b97540aba5ef9cdd14346ad3c166c7bd18946a2fd8a2e6feea7e5238a2821dc0059b6078120b2f8cc6dfb65de819224593a4dd40cfa811a210f8073836ae4e1da694e72e342718e7dd355256323c11ba386bacc59ca502e401e3257f9c2e060ae047dcc4ab0325a6088708a6433a7fc00e2f0beb063b7578b381001a8a711a98a05dd2b591ecb05b464a073482db1fa11b49d2c68cf71825668a3ad87e2521673128cb306a815548d5e1ecbe66c0f74e426c55e1ecb0e12b48ad5e5b1a44b4280f10aea59d290e27059595e92088cd523fa2db62275624fff44773993fcd116cbde923274c58b0ab45e32c1546c034a158e6a5b30145ea503da7996d0e7ad34be5c6d5bb8cb595a4b27a6ce13003b19ce0bd01fe258834645ce122ae22511c5161d628bdcf0f801e33eb7bbde4ff37afda35e0ad72ffe8f269670e1bee17d834a7ffce22d3bc8de64cbbda54c29a5cd0683068306346421ef166a21168a21c68f6b70842ab696953f9439628c514a295329a47260be0ec373c498715877c55239b8fa67a91cecbf37dbdc5870dc8cb9c17f3e5755d908abfdb6b586bca687e8526ef69c150041507e45a66d69c2f9f29fdc04bed56ab558469eb59b80f06b3748e56b3fd3600ac71c2a9a48434b46488db33538f252fbe79459651a354e3885a8475388adc110f08f5262526ea80f3b15b4dfde533b50bf7d9cdae19f6ac76c55ffd9aafefd981fff07aa92406307744a6e668750ca47fde6eebb6973325bb1ea550c72a8e1004328575709872f0870021cb2f0828315e011c5ea2ae1d0831516ae6931238cc023acfc35e933c3a44fac4a9f137b29a7c4a4f499c9a86931238cc02b50c6cb74333191b24c37f33134326464cfbf71291c8ce2994c866518f3fa67c2180e4de09fd96233d8a20eafd96e2fc60c931ebb3119dbdd1d6bd9b1bbbdbb1ba63bef86114497c8ed5162324ab92b252633e93353aec6a1c2f82ea5e4dc93acb56024c7c86566d8cc30cf231789612d774a96f24377f795bbcdf5203d62188661f10a0cc364c4aec062160cc33029b12f24e65fb29c207ac430b98aa9ebc36b5ac76b5631468c433df699e3ac0ef73634d98732aa8e4dfe7ecaeb788b07c4f3693048bbc03630b02768ed058bfc30293b66e6f828c6e9be7df8f00192c2e19f0f12d02984da3239ce2eb2931455c8b0218a183728e151d49095e090430d798897542e7ac9e0f0030350a81728dde0440d99b5a2640316a6a2c5450e95f9aad790c3912d9d53dd38eb423ba523575653829c4471648601c81083f8d188961aa7a89186276a0cdaa1a3e1ec2a73d5abc94436654a1c894804612938e245415eb34a35b4acdcf080238c948ce0e086071021a186b0ea55cd902a90f88113c8c30fb6a042b3ba493ff052032b66c8c28a1c9470c08245d27ca6a25543a6a255b51857a8ff8186f3851c5c23d001ae1c1274d21a657cda3851a671c08fdfa790a08cfa55fd313f7bd487ab1b9c4a617ef6d9e4388e7bcfe9e7487051ae598eece79c9aa6693eab99fdcc661735962dd89666052e622b9b73c3c9d628616b74cd987b394aedd63e7b02c2809a00cdd7be0423ba757e3f4d7ae6633f7f9a842a28a002592cf505ffe2a5fac854f91a90f50522a06e7ed38b64585307f303bbc5c2d23ef38f88e6a9fce5664212babdf64a4680e021b2ad2781f918ca53f9d7333f95fcf9cbc23e55f62c60d9639f4a3ef6cb6221fbf9f3fbc1c01030f0e243abc5ca3e955c96f6bdd7f8d70196401419b1921c3c3d0a68c8454c1e1134ec22958bee927f0c8df3d3386110f971510a09aac373baaee1efd81917a7d59d3776c7766e5eed0760c188d3eddedcedd47d77fb6777f7c605c70884b8bbb179cec8cc3122f18845cfc89c25c6e8ede8e87883ab78459432fa17af702eb6a5a9dba0968d3916a4ec97de8e8ed7fc40c71b5cc5ff188966dcd238f1080c74a87119290284225b106088224e45b21079d2220376a40b1aaca06c60ca860021635ad18e6e51036ab5c56be3820a27451732c0be90a2b58ae08ea4b80a2353617e4f5061607e12a930325ca9300f238503a62b402af51bced4144d4d7d4c7d11414da5beb86a4aa9e5434dfdae004449b16d3475b3a9dbc7eda79724eaf67448ddbedbbc58aadb838db3495f0c31c56e4e0c153930e20b220b787531c4c58bae4ccd9ea8d9cf8c0b126c7183c5f12e1543610b2432cb22801a54fff954c593106d26af0ac39c161796640386618cdd4036d920a59472092c5a920829a1c54b1750aca0b224d960050d590d65862c0d01ac494fad17528d3f6fc4f5828b11473380410e929c205a22882d3ecf2cae1ae4e83a0d72185feaac68e8344eccbe307e3a4cb5205269542f6848690c61894ee188af56ab55e428c9155c38f1e2cac11169440a58b0558c5695115de934ceb2fc38c3801ad2744982290b23598a6accc2881a93862003e84ee6933927457129129465d1b8e0500336b9f81024776c119471118286a804e362c411d762cb0e2dba48cae504397486147772692207def1d4e4353bb9b84068c7109c3a224e4a337c914a32801758d440802050545da519ba0ce0896e759566d0a244b5ba4a3334e598814969062230f0efcf565d6f6babaea7daefbad5c398e83ed7908584d846ccaf8c671e388732f3f22fe77c42f40b79a96e128c2fe4275c2ca5eaf093da8c04887d3c8277016d905543a061ece91ccec2d33898098e3cb1836e6d79c2b21fa76c19f6d987e9c4784171c207c21996a19ef63bbd2bd49199b7f21ac6da6582262ac60082d228652c21b1262645998c22d98c61034f4e46f2d46234d1260a45050d5d028409456d44201ddd8004885ed24c9c4a07b93fb953739566299aaf30169723b99117c98d932104c4d0122a538e9870a9182ba80161f63c0df2c8fefb917d3c7aaef037ab071acefa537ff007e9d8c91a769e4a61fe8e6facc5afa381b6a22520e2487b47b96b8ff25ef02f3a2f1470a0d03930dfbfc5878109b30f39f662e488607edbfcd9f3ce87931f11a5edccac82f13ffcb587e1af3dc752f3b28f44f40b7a16cc47157fdd4cd7f46b2ba09d1419541745426713db3eaf4725102f3e225ea1eb6d6d6173767936085a4c4822b3f79af92808ec6f0efe52330cc3b22fdcefc06f7ce9c110eb8ed8bf2c22d4af07a3baa7ea9fb5bf393d100790e62b4b284ab08c208a96111c69ea8fd9dfb93144897bf8de434b69410d57df030dbb97122d980d70659a8ef10f7908480fb53044c92eaf0024862f08d78486eb14841fc837d72de58361c762a93868832cd4eaba2a46432ca12449c9fd8341dad81bfe06ec092ff8172f3bba55e5d780bde1da5e07ba4a4f05fff2e5cb97da5f03168c2ab8cb4f676f646dd6a921585986d2b0d3b0659f61588c40acd1b1280463be61e9004850595789071c146009edba4a3cdca0c30250578987293c18d5f07b8ba416282dedb3e4ea4a1e1ed7d5de4041c36deae17352da750f32ce32b8cfcfcf21013a122833aaf23659a0c7d688cf41a8dd3132a7a47b60d82cb4649bf99918638c3ed1fda36ec4b634616caaf2b7ce3e9084adc19f01fa518bd2b2d758d3b44c7bceb0d8621b581c32c5e2f45377b1a07c192f624af582f2851a477bf911091071282e699cf9dd854ccbf862c47c5e44bf229814d71f7b0b68b8efad103d1d373c14741a94361290e0cc1cf584f9974dcd999cc9005b835f3ec83632b7c1179996b49961a856a2cdec51dacc50dacc309436330cb54ccb0ff3a9df1ec59307745d4f4f4f4f4f4f4f99cc27e38bf18594c27ca1cdc77ddb3741d000314954326d8d661b315a557e7cb23596ee8dcb9f93d2ae7bf94bd953f9924a83724a95ccbb9bcdcfbc179c911a1c82ae8dec896d645f346a2eb6067ff1c9de7c91696fe44b25745a098c14c6bd029aa0303fe3e5c72316274eb1a07c39e38b465acc17dad0afd5cd2902a500e8ba19f12bcc179960d080c6f7efcf8be44fda715e94050909090909098983268a6addfc4c3ec641937e554013348c46ad2a3f1a21b9965e4c325b63a590b115e4394258f2a390e74496fca8240e7d2c8a4d362858edc7dffd5412908f3d0b8e7d404d7a16c85f7eaafd88b25f96fc653dfb11ed9837a605195e0bfa91f7145846c0c2753ab51ba10723f480668b369a9d95820842d0d09f78dcc96b4ad8010dbb656f1844a914e60ed8c5485cb4af255751d1f217c3021abad04e7b933b6571269dd76aa99b406f2667a55ec8012b21d085804cda096d8dd49c9476dd3fd8af55bffa1583d7c407e235d1998aa6a061bfac6c4e347a6d0dfe4e0ba5c2688a1a7f33f21c9a45a3ae24ac3cc799bc26fe9c9476dd3f08ae564c948a100410cfe9d7cbc90926323de938adb2803abfea406aa30353e3529eb3d8a1a01da0159b6c036324f6263e17f109ce4a7ddc44ddc87368c450a1fe5da917a35517fbe411f5efcf3e9992c0b252bf291eee1909fb908b7060518a28d3aa5cb492de6bf7b51d0150824d6e1ba7fa63f4f7e132fe7cb84ac21094fbc17ddcc17d5c887dfc2020e5730a07c7d40a42eac6defc622a05d48ed3e9612e770648ccc004b57db8da334002864bf5a76385d7c2b362df2f5f074f8fd4549ae6c3473e906725bc447e10b94d44aa0c2212878e704b57ba90daccbc165a537056290456aafc1b8db33ea8f25dd8c1098b949ec2812282c68a83bd0c8e2ca502f6330d764464fe3958056eb509c12a69c815133190f27584a046d91648849478108251015d25212521185816ba94e2b81e68ff2a0931a9d8af6f18c6428da3d2fe3114e771d8dca67c2e224a3db37e98c5bd4c0ac7fc28edbec33e1b50b56a50feb2745637b06f6713e1075a7fa0d8739dcf2d240d2a69500a3528352bf46bc8462b603a66a6d0551212aad86fbcc92859888d3c466a50be00aa501551ea97c53dcae3e9d1625640e3b1044d793dbc463ee7f98842c36daa12ca12757e412410277655f9233f062168b84ffb1d7d7808fb9529283666a6d99b1eb21f7d4e4add6d36a45bb338dcddbf0f40b32bd95a93d2676262626abd8c3c92e80ae1fcffff9f3c74f0c0e44f791a696664907037604c1d9fe051860e51c60892cd33e3d91926a349a49991e1646a68e641c9b0e2bb86a780523495d93c681f3a7662f849229c4f413334b403ddb523c4483bce42317ef3056f194e345c97cf8ecbe572b9a490cf8da741973b0d6e41c3e9e2a01df6666f76d9fc4960760d13dbb922ac18f4536d1f4f8ca7458f025ac40202e35371d1def4c3c0dc97d0033ae39391c105c5d88a3e1e0eaa28d58e50fbed53ede0df9e5f48155277baeba672506fcbc2c9c582f196a5793cd4200a6c834bb027f433d1fcb888461ba59a4341d3aa54f854fecc04658287ca5f68233343433a8310ff1ef9df4f93fd7afce3f7f32384a948912d32d4a7715a1c0edebdc0daa06117b64610ae2964ebc2deecd3b08d0bec09fbfb855bc57ef1eb582816317fa78f7eaacf1f3e5ba54a75ff1117ebae52e5a8ca67cfbe3b85a3b32cee0fecb97e347aae8ee8578e4a49daf863588f9f3f9583ab0f4c642a476318b61206dd1b8975330deeeeeeee763b4de7d90467409b06411bcf46468ee338ce9b7783a1eae158f2632592df0f0858d46b38994a01e3662a076bac699a37350fb5715b2a7aa738b5198cf0054fe3540ecea3810395ca81550e47f584ea0ed55d9f7dbdbe70a91c5db9bd49e5f0caed8d4771c0487130606052746fd893c1d1e58895c66c31a8989898182d26262666c6c46431588c8c311ed3311c7302bfd8c50cf38446514336e2a1d496cd07206088edb3623cab8028a80ba7a7713819266e18b8f2689c6f4229527f884151a4d07c94204f9e2821c5b69cb9a0092b4840b97b70c5513061b27df49db3bbce93a06d65a3fb1d25879928534cf041fb2c15666ef6d2ddfbab06757a1ba73b7e389d7a7737b66f9ba0057dbd48e04e9d3a75ead4977a0f9c301a5a52d42038a345431eda6716d72747c4a7c9556471c2a589cb15c58997902926f880fa2c158ef254c3a74bd35788c0fbeb4a297d77e3faee7e3dfb294a09ba27ed966977386b0e58e065e7c60c1d68b82e1d3a74789d0e28ee2e82b8bbbbbb5f5ca675ce3f7ae156d9dd940b3c4629dbc4141d9d0f596989a61a3e85120528dddddd1d73772cce06fdf7c6bb2c8d954d83fbdd206f3bb3733132d6e43b8e8a7083b92ba0fc73ce39a70735f161cb8a4667db2fbf95ced3689075a46c273c8912585c477614bfbb45a8abe4a40b126c9c34dc3e3831738f06f97d70f5f1e17e0f1f3e5cd0d91b7eae09ede183db6edab4695d274772024ce5e70f003f9290cd86ab2c450a17b91e9888a0438cbcb8ffc18ccf04e59e8d836dd7e5f4fef7864a7bd8a250d73d46667e0c8b8286ab1d5f89c2737431f624185058517612d0fdd5f2036b4728818222e2729520690853e4c95a8aa281aeb866239818824a08945c69024915282aa65eb3cfde8c42775775f7bb068ba06b43164e51fc553d9fe09947d2753c14c5a1788009e7c46c2b3498f1a8c094ca39ee72a171f6092447955f5d7a340eadccad2a4f20d95243bea18519ba204de1691c6e41e5dff53929f51b0d12cd5fc5b430c405f2573ea2418dd3534087a2108edc5d623724e6ee2ee5638ca57030f67247e28e64ce22a8ff7f838d6152a83f867dc488e66373ce1857f06a7a31755d47b4f1f4c47f148ac79c3c3c5e3d0db644794124cfc67ddb1666af6def1e797ad9164476332fc398962512ebe1c2d7e8f9e8ae9bf3eb71a3c5850665d6c37d9cab2138d3a0fc3ace5eca28d4ff553bfcb3cf9a48fbe8691a57d983cb85246bec065b92b610996982020a8dd31f3d09e5e6666460cd38a9410d6d666823115aad5641336040436e794f31826469fe6ab55aedcdaaae68cc0f5ce980ab07e90c82814fc4cb781a3df6b71f44dd403f05ada599a56e0a0dbb89441123468e8a1c0d79e9ffd93ec8d6e0efe7c0720ed0a2096d203e9e0f8fc70edbe8a0da3bd9cf148e2835ef9be1ec695a8a086ac842362d90866090bd41816d70604fe86f4d0b7a544316a2c12d579cdc9091274f505f3c61c26408131f5e0d2ecc0be8d72a3289155ee136c5cdc1beae03f766569bc69993c77baffb81c61ccf1e11f68bbd7fcd7a57b520e857285f549957a50d7612342b92e4430d509200a19ad0cb01326308fd979ca67d7326314b46d33c63b5d7467be34e360b1115435328b122d4519efc3d07e6b51ffddb6bbfed0dba7dabd0bf793d1af427a18924a954654b65a52a5dbad544b0df9e7a305ebb521e6aa2e69ca86ddb8c3a339a935277327ad0c948a7ce18427b66764e7376dd3f08ae56298f480af56d3427a55df7afad3414aa7bd25ce9289bb398b795ea0d45f57602c6f64e1fc6c873603c877eeb35309e4e08528db37d1e6e40088c944783609bf6dded2814aa67f6bd4429c95dbaa687e74c963b35e8de46d597696ffadd488605fdfcdb8c21df0f9056cb63f3c72bbc6373de6b74f0146b65bef6bc537fec07c16ac45e9b3888b26d93d8172e5663cc767fdf3518d03ae3ea5cd1073dcca3c10e2af47ba401bf0f08743ffcb8bfa908ec77e353edf874b8c2d54cbc22290906f47f65a48c0da5734257980ebf12164b4cc2201ec64fa741ef874c413ea603f641bc4af9d9476fbf5583197ff9f2e54b10af1c0601f9e964afe3ada47f343a67fe8d95ce8e4605c90674ca84fcdd395b4d7ff71678b6c68ceacf23dc205477ff95e76c1f2b995f94b287c7c7f341a1fd27a8d28b51a5b74b489654e96d7553bb37b2ab52feacf263ca5ddce3c6dcf3afb6c68ccaafb33524144e705f0cdbb75e83fa580814a2a4fee8f7d98c6be6bf59669dab95484a438ab817b2f5fc0889939b3b78da41465da52737d84149294992ec411ba4f7424370b9719669869716297a9c148df3ae55f73abc867f7d76097c2574a7174e15fc4b5d50872ff9cb2964ca2ed5ddddf1812f35fc69d5fdb80abae8d75daa1493d1578809932f5f5adedddddd9ff6a8466d956e8f8d5a4dfbeeb92f1eca300c23e6a373c2501a44560d45436b9e0685a0daf7a980a23e5c0939aa3fb42851da2729d79e5909b51feb2fd4deb5cdb25578f36db291826a2d8b14b8fa2f14285d52529ea4a43cd12289249ee8d2c4d2d258c9501aee2bba0e349c759bb33f206ba571c23929dd710ffc81f21361cfafcad1771f6c20e857a41454f9cde840c30eabc293199c407a6bcca8b1f6f311b5e75c7d50fa7df40616e7c7829da385ac038a66a95dcd7c41ff8e42b9621f0cf10b7d364b8dcf9a4fa4b1eb86b74383d0b75016640213c8c8ec1eb19732945232b38c531e716c7759d37380809a00654f81eca380fcaa3032fae7de70f4ecb47f2b30b30068a0fb11e8df0f05546a0876529c862a55765f3c7834b83c1adce769707757fe569b06b7f31aafd9d75e40bfbf705d2b2846825acccc2c9955df2da9115d59798286da5f1bb1f7d0f323a4e74748ef7e1c04ef0a614dec8b086145212cd5673df1e5f7133beff66e2a5c5b2af69bc4e54a9dbfbf836b2583515ad92589b667ec53fdf601a558d83ef5a90f889b5fa722807d60830cce5feda7170aa9a1f6a86deaeaeeee0b3f2108e67ae033f13fb29fff237b08ec6826557e33a9f2db3ef3683a677b17ba66f6d042c37dcd6c57270e7a784a32425d2514e4b0bcdb02ca7533b804e599066bd720af409edf1f00196cc006a7084f34039bd3c2fad81a5cf931b038d10651962455dea6ba5d2a7f0536c747f658d095e734c19e2bf77eac2d53d897419ca05ca5122ace2dd719f639b22f7bc9adfcdc23f27cba867b40df091535d4910197989d6de1bdd2c44e287f051a87824ddc6ecb8f908406a8fda1571b209f90f168b0f7b5e2b1e113da3d1a0dead8b08d04ec09fd08c09e5731a0a83a27a5fc3d8f5461bfac9edec67e56ece3b9c1b59253583e494a8202a5698b1042114350ba326504fdbbbbaca40b13264aaa50a7d6feb033298defef1f90d7ac8a05195f7ecbda9517d2ee419d9d16dd8dceb4caa2a32d550175a13852523718fe905570a4a4822e5e18cc0b6630fe19ce9959b675f78f20723d7cecb7e6b01f52ca7010ab7f22848c0f50647c80a2a4944489a5244bfe5c8f80c2748416a10a0ae84f038b653f9ffd1f3e8ba5fafbecfba781c552fb53450985727f74f78695800285092682824aa8abb484117fc1822d4f9858efa44b19dd37c77f7fd0cdd05ed6c90cea1ae999f891e5ade195df8ddce539f15f04959f1da9b22fb514d82f5569d506d45daa61a8ee60e4853f7b8b880921a0404dfa81e2cf0f9bc90b1afafcecbde51ee439254041819af4641f1f0828eb57f64c2a16a26abf9f263dd8c7e79ef8fdfc3451f1c49fc0b2a2b601c58f5bcf3df60179150bfef1e30784bdefa77e59d293f1531fd0fc38bf75a075e2b2f1901050931eedb1073202d23e36f21a1ed201ea63232ed8a7da4f4581f9d8f764af7d3f4df6b517c2eaaf67fea878522f84c57d13150f50931ed473a5c04f10b3b09f8159d803a154fbc1d0335ffb7e7a780dff0496e5e3e535ec427db84da9cfc96bb2af5b5e93cd07dabe16c21ea5f1872d5419fb80fce7a7daeccb1e7b1632ec81e4671f0b1890d7ecd74b610fa528e0ef4abc865b3ee42da45ef29ceea55e1687def21a969605fd1a7aab72f7521f35c84b6dd420ff26a49b0a4d71bd76f77f47c78d0697bf30e2a8fa57c6f622e8462d647e75ec34b87fa5f2f7f2224992224746a4808179b98290b06dc9d6d86609d6060a3bbf79a8a75e5f714531c462c2a47154dc6fddfe432ec291eacd9be12f64241aff16c2c9698855a4c4941a32d2875cc4b193cd0940d3efa618bf947ecca7da3ee637a0dba8440ff6243154340300000aa314000020100c064422b150389e29d2247b14000a73864c805234944643591224318c428818420c308400600c101a1ada2600cfb45151019851a3f4ca9642e3cbc9e9e230c410bddca5922151eb6290133dc8040710390dfa9d5f2ee64468b80d7bbbf9b50c6a5f8db9db7bc1bcdb5b99a8ab1417d21efb8839dd7eef530afce31d0b0d48aba7fd31c98afc201dd4ad87cf68586ae87da0b73c0051f036bce1c9f10bb9d9ca75d0a548cfc2f04b21e143bb30cbabc158702965b9984b789d054b0fe099a6802f25f61e62abbcaea765bbdc3e6cbcc26b135de1d173f2bf57e8fc0fd30dfe28c902297d465cd2c6071cc1711a58b54da2af8dcd35eff98c8983a40eba74339db4b835795e46ed6de5bf7680a20161c46f356cd46ec6ace0e307005ee094ef35939c959e05e7502ed346a00d948eb664b84ea0fd76602d2c4df7c0c0e85432409a2e4ff07d3efac79862deae4ae6633d0f8980bedf9e1d6d4f3f185b9bec6e75b0a20ac2292149b94301ff592d1152560019f0896466a9bc403f9a3a599624420d5414ebcbb4642be8297680a4394efa251dbb909bfb856a465519e302c1d58c73c26f1cd072508acb8e4f6c0de5976b654bc022c0bf059154ec50115f6a43a7fe5d5c351cc885a1c8a568492fc6688f56678ff62d926b0cd78a9db13e0437b320f81ae1533d5d9f233600ba074ebb18800db48727f031e718641a090c8c3051827e9e649f29567b897d1d03dbb5b1bda355624c9becb19a793a07af76844b36b903f3eed7d13bc65189837eb28f5a6ff06a5ae00af533206d9a0b10e636db6ea0df0585da3706480490c8821838ff5b65cac4af862f75ab69fb17df951268f7ace8eca935978cab224883c18b96d60c48f77c51cbd60b0d162becee62040fe7e8f281813cd569aee986fa4a2abdcd66322ea8891ed04b10f5ace5e7bdad198ddafd2634dac9fcbd12c25e01de377a1280ad60b6d78717668c492518025b6363f5666e28537bc547ce8f0b61814684452c48ad1b77e60a60f81578d9710730185fcd868486b2977a5b38bab058db322207ff6c23a064ce0472356f9fb72e5d0769ce2d49b3690636f2adb9e9937e03fbeef0207049a13003c28153d0762e5eb25e380af784f8966daf63e9870d52d2787c02fac858b4c47d6105379ae144801d963e187d1cddfca6730e8bdc4a816e28752552286f7d182c57429cfdc376ef3d673a9e8e43080a275dba9512541f6c2c8bdb9bc08e030479e366609660917d9dc516f60c1694faba7be3b49eed38f89322139d6bbf14204b46ed3c8c0477000be3d3e3a8529c1983019e5b87b7362baee849a4e7bc9e5798c2979f3909288113db26d78ad91258c30585b7fd390efa1a9ae0ca5af938e648e1a06021a961ea51a796e5c29a0eda16a72c8b298aad7af5badfb3dba5fd94f5da8ee00ea751e8675a6180e552e03d9f01cd087876a7bcaa3266fc40f652ff305dc5cb87b22b7f6f32c7d0a69ed5d56baf6924935c72733ec42e7a5342072a5e0c7977645060151b4ebf484ef608807c17baae0cce493f20bd65f20f3d00cd5857f5584a05863760897937d0b04f8ea5d85cf263fe26341e0d0896fcd69d58dbb6ff00514d4cb0ad7cdeaa005bde134efe0862e7ae15dfab73e63d81905206e6363ba1a3475e15bc721bc90e92568481d88b641266488435d6d3dde216eed4fdf199359e04c5ae0dbdf5c3a50de72f2f452cba382b003b584abaab53659757b51f6ccebe345a9ee57ac148f806c0f7a4b386e32a765651a45d14b7405708a8d8c7114db2b1e13cca7e9ed21d465256c6c1a6d0143bbdb3600287fac95b9d13b4cc504ab3e4e5d97d5fd028ae8a92557592f2012afe7990db1b9ee1bca96ede4d3c4bbdec8c979ce08569112729833c93d108d91b7e0212ff9f2e484f03d297edf234fbb4f1833a5628b9a1add0ce938bf5b50b1399b8341c17ef00520c6a10bcdb878826bac303a286e00be168961dcc83115f5d1eff3521c85569ea590cb4da5eff5c2ccff80546487f15e8d6dedf2a81c60aa8e103466dc75bf52c691f731dbbeb9044ffe12da857b36d23007136e7b24f1d3db28db579c0470ca200692ac2a3bbb79abf16aa53d97c3d4b2dcff097bf68563d9dd21be546e641da3c60689db8417fa5fc6aead8d4484fea1c55ab3a2b24b851752fcd4e74ce7473d036ef03a3df503c2947de0983e82648e7c9685b37f8d246ec786157689df0362f91b6887737efb7f4acea4707cae198add6e9e176aae46c956fd416bdf44e8d4ef0c1415bd8e305e8c3b52603186061dac74f078d86f0cefad81b6524dc9e89cde71bcf73ad0db24c805349958a17922452adefce8cdddaa0eb1f553b006e6d8825a41760241c8f45e6805cf5aebb45000187fc99f2c91879aedafd7a8eaeab9db19ae57a65fb9ace508b8253a279d96fdd3dfdcc9ddfa85e0a7fb57112417d9e741625f337ce2a6569362e8f87e159952ccf799783c830d5199443e66c30e0fc95acb1b431a30ae451a56c94e98039899d9f2ea24598cc7b3f6d363a5b6248425be3c213165574a24b8c867a3dc4b43643410a826568a0e1891131b22f42006136368ad666e5d429a28e46473f157e98374559d29223b4a5314c4072e1a8b0080c1c1073a484f09504e8f30264ab5c5c1edc05d8e2f9fbcd4b86be5b6774dbf3bf3c10c51705cde0ccfe45f19c056288801e5768da31fb4d112fb4a0a827164d33526bad5c4a38d966731f36d792f3636c50267a092c5f52150b665896f1941f2d2a6aea15033805ea2465fafdfa94d2450d4acfb45e6c91bc48c2807b50da164cca7d848e8211656ab39d15febff3678c214ae11201b4beb9ae8b5f075b4e17a0b08f1ecef2dc53624743e20eaf33da156f216532f64a4dfda7e2f98be5241c96426e9a4dfc500dce2f202e276d59ed82002528555407d9a8b216d7db0e4c79621cc76579e6cc7ac2e8ddbdf2c1aaf8f263f0002b0410a50cdda8eaee3fe413c1d5ff6c8b55666e6de9947c21b952d650f112abb16af715c9b3525a59415724ac4fd808fdef64de05d91f43a10a36da60d2e847e3694f82686c174a00c84de0207f9ba02d20b1e73dd60fd645372245041c0be35f37a46cc77b83240f882950a6912e1be9de86b355c99d87d880cbeea3426c06ec937927c65f4edf5ed52dc224a3d280714ecb67490569c92db668bb78c7ce42e41e16f28089611362e5ae459d1f105d11947dee239fcc91350de0e42946e0f202308a80445749decebaa4a7c13f634acf318b3b0a74327ef2f7890a40162969c29ed09152804966498ada0e7cc6c2d3c6345647c6f8ec41093c7e30b0022ae3111e80efeae238fd5eb426c3c2cf35f81fcf370fb93155754fe8763d7d32880ce330beefeacc904feb219491e1845e96a950e2523a9e6f5229aa90132d071b8b1e9cba1e29d56fdaca15c0b940731a31176ffcf6f9c3ace50877b5f3e2eead764a2b447442af776799df1092c83f7bf9b561783321e18ff9c82fa043b7067a06a1a7cbeb3a534bf9424a8cd89ebfd375ddab26c0a932800dd0000d53d4ccafc649d8ebcce4a54fad78fbed1f4616ca5b16100def77b8107b14ab05fcfac65f23d81abd3e9a2ce180e3c10bf60352d6b1b460857c52c3210baee9715b94b39ecd3c97e95f5e3dd2d7babb1e6c4eec7fed7c6fa55d5c8b5d615314ef9e36c6c34427e5a7e8fafbc99343030489ba20af84c7c3fae2cff247a234afa1f9592ded8203578b1f80eff5c3a07be6d3c0a94131fb4c10571c8379d0d422eb743a993848abd36accd3ed0629e262279b3950f20061cf3d99d8afb7c0918cf1a045cbb793b1608205c52ab32cb6a89361319f3bbd6b1fb330595552ae19954b7fca5433bc9e413ef398e41ac00e08c220f8b58eea709e4b90f8829b4287a2c770dedff1278a6ec97d1900e3c3e84e35db91841e11c3693f44e867f51c2188968a1a79d24aa8e41ea4ec0b9e6c9080922e1b75d8d3f00dd21cb468cc23204180659cfe07812fa7b171800367e00fae60590120ffba117dbaff94556f5f7dbdf5fd126e3cb1fef00c8893c4f60138f6863fc4278f089e247add297f86dcf4aa5fb0156196639901e183074eda75efe1c40b5a87f289f2e6a21c6213a8083369a1f835b74d6c8741eff024e7f1852c0ed554881327236c7b04e18d495a8e99cb22289207518d8d36e1b0f98522f4b376f0b30cdb1bb326f77ebd949bf354d052a1e52dabb3d0846d2f12ec85e44b32afdc50b8ef43e4661ee4f8189009517b98033f8fba3e58155fa77605fef0a0a04d5ae9224236d0c5228c695a48a67eb09d0c5a998bd281da0115c50d438f19331b42e51210506418e64064d11d10700c5bf87aed1e270e2b65052e98ddb6caa101c20b1a9f57c880435a6cf0a3ffea5c83694f2bdf6caf32e183a2b344ed92d9a54fbbc2d605a78edbc3bfa02b57da010fd9725b69f3863d0a28fa9f17ae66acfe5240d072e48bef1426f375c6d6875d0ce3c044eec0daf61dcab54aa04cd44f97fba40d4d88f30ccd08f26d24db57050c640687a9a199ccb1c2fe058325a4b9b43b3860c6abb49a6f85d4f6751f25fde8eef2241c31844c4bc36a2fa1f3e5867929f37684c1d14abf247ff53b1edc8507c0ff76464b68db3c12c3b5889fcfec2a47d0a35185a9615f8c8be63c95a7aff06f8f3318e5a2141746d8d41363251c386f09f4daa7a391f2e4c1a82583f4cbc73e9d5de5271786e73b2e277bb63d4a27633c902c7810eca6c2db134463b2ef38b21749c708e2d949157d97a645b792db50f23e0969064bba67dcb2ceb7d5aae4dbd60aa3097d979d361341146a7d343b42e51dbbebaa4eadbc21ce0f2815097cb0508c9e3ed6d8fb692d466bfc1edf1b2758e11f00acb397041d9f910aa401c476738145d8e2da15821c87acc3d95589fd293beca5bb9fcd1eea4cbec0db7de335e5fb5249b89a1688d9c40dd884bda315a1e55bfcc5bfb9370a17476765596d25dda2f889b8bc66868d209fc8638e415f0cada750ea232c6a793aa64e38c0d49a7412fe9948f31f867a0e98c026ba719eecd328b73c1f954f0b63085971cc1823be1ef9148c41c38777fa21b1ed374064aac6778a8a9e3dab4755928af5025237ff98b89661366bd702e2f70be7d67a4608828d86154271f9349b02751e0030a4711abdb4799d80bc9b34527baa6a58de8a1bff5e7fcbb71c505560c6e70bcf5c58642045794b7e85fccf924caf469f2e83dbd4dce90588ead3606c12afd46c1b6587ac8aecef66136370eaac6fbf9142d344448c9fa7f29a44320d8f3655847c06feef6d65a2a26bdc25cb2081eb7f98bb3f9feab89c6bee2c457c315e5d4b0c80c5e94b94ce295a31990d6a18b9b4fa550c82f1490a437ce3c90f3ddaca51e892411f7a4eb3648cd332b2f81a68b7a60a87879997d748887d5dd3a6a6578a98e8bc3eeb78b9e40544a4dfbbe2903ec7bc879f51b00e33e5b59defa5adc3aa39e668aadfc08b42daa5209dab4b9dba5d98c6a3f12e00a3c5b455d1506e110050a15f135e06790fb49cc5259a9eb616e600c4d27a279d6ec272f4b73b5a4f8165c2350523a1e0aa69ffff57e54147216c7ee780a0072513c430a29a1ec5ca7cf4ebb8e4595162380a0f77861316828ca47374cd61e3393b36e40f7c9e5111ac710ccf0bd6b0815061820adee301768752f0a759cb219343416fabc042357fd143dda81774a865dba41904349eda712121c5ded7ef5fcd131952c3d016775cb5b61593625aba23d041568d31cc7d4132f8fd644770a4a5fc9deb28d073f225a8ee7534701ba3d240c7a6605cbfd8155b27fc6799a4e1239e29cf110db55ce5e20b38c33b3adb49987dac8a5b877f7d067cbf721d7073879347270a528733971e87ef318d3386de7a77a87a212648f2fd6778de2988508bd9f4f2d99ef9a3f461229aa1d3aeb46581ec0f2ffe2ceb08878dd9c599a5b25fd13a57e698c332135f3707b5b9d64a0da08eae159bc9d605b683f6802bcfef7fab771cad584cae63fdbf9521db0e066432d8e7f17685e5f6fe92abe10fdde2151d0d2321c2a8148d10ff4138f63fb9b1c862c5f2afa6221472d14db06f9770028c9007c426e18658c24f6f4cbd06fbd1a5182c920c6cc4a2a2631fa4ddabd21e786ccb3215e87f24dc2c3159c693afca6416cb3168fb200029b728a23549635c8a8943b69ea89a113f57a448ac548c1dfbc747c8b75d888451a660a47e1c9278fbc5dbab6e843918d788c66ec634af895925d9522e9a060463197d5d80613841827bcc22d714c8643ddd12991da40d766314511d420743b59187815e1b06e63caacb6f673a944b781ae9d95cce0a684611d70e86428c421fa6134e00efab882b4e6e468b2018f6d59be986a198225ad01d20ae21a42d6006f5cd7ae06f67edd443201c274e99e0099bbc47c022ba01c6d8969872a5183c58397558e008ada6ee4ea3e4b80d9285eb390111fbd2a773d5e487d08a0748b5848240edae13de2db1dd3f0a7d01d8a962217a92003db2046e706ad27962f9f8fbc141aef1caaad7de40778b8b757bca3c4d340befdd50aed43b7ac2e1613bb337982884b47f75ba9ea8fdcb1968f939b2ab90f8ddedbbe17d3f345dca2921695425eba9d61b48702f17395e32d24263299ea9a35de8c49fd6796ce5342f1a2d26300d90dc789e3679f4109e7d0fa9963be5cb048786ff5559c5596e040290f1eb6ad1f4df3f93a01dbcf4855514919c2cfb174cc4f03a1aa3dc2a1dede485b16ac3a9749b9420761dd2d70ceaf04f92daff4be6f03834cc0eff37b38177a171804000d5573e2b4ce5998fb296070879f9f4571e61c2ed78598559c5b217c3c7866ce52e5998655c679f43ecfbcd055ac48a81af2ba7d151d295eeb933caa58c9f508c9f86ed675ba20b5cc9e7352208f939a103ca2f164e658dd01fcca7524e5e963ec9ae2517be1f69ba3ccbe8df1f65e565197ff9552f1428b0ee1445f01177f28af03ca6f3434c120f04154990a0f91f1090e9a323f0bae498b91dca3f14eeca78f210d96303343ef4235037418a9f7fdd72cccfe850a6e4c47ca23e4eeb9b6cee1f83647781cce9277b4f41dc6b7793e2b27c4c8c186688da8d4ee1f81f44245a10577440406ed496edfb96ca23c636f75489334a81ea58c438270d60dc8b61b5fa2f0dc756fe9593e623bfcb8ccd5516c906f9672c51cd31cc452606ac662ac1fe2d65c18798f40895d1016b98bad17745099d8645c16016153c11673d1cc2d05d083b08061e66a5a25ca6c07bf782200aa82f36d6e282f4d6bb036938e819df0247060f27a08e80b0fb048c7aea39ef25874af96b7cca606731d2315ef4ffae78280016124b092bf0e00c6d94c98ffc9a9ee11b02bfa403c2a9896375a501464eefe342d519541cb33d807b0ac8036d5f6ccf85f09cd8f79ade7570b7bce9641edd35654c38c3cec3b67022c40152d6d00e8d340025f95bd64b0132ab2925c7929cc692b5fec458a2fdc3817b24876958cfcac8cd4d3afcf79de99652ae9719763e3c4ca6947711581a25062f8bab47dfc0b20c171db507f67df9a22f28ac5ef24552535891fe7fbea4ef15242b2c4ef31df3494dcd720c088bd2cca466198a44f03c2f4dcd9a782b3434cb044e5aaa0f7512ef3dd32d9537e098b55dd33c18de4f24f73f7af927bba3c7915e1fc93a38a5b9f9e993c1c9a17b257b86bf3077a547bdc40fc9474384b0b50d912523add5ecb54ab1fc1e48b7154a381f5203b700616d3a34796103d0c49eaa2864ec9e4b7203bb0a3a100bba733d18e1155619b09619ce756db14e6384d23c2b4764a401285b2cf1bfb32888a1077d0de9f047a9cad26f6fdda22c65b947c521f247367586ba46ce3a5c1ca6091c677e56a5e0b9af919179a6cf9366722dc779ad4458d5adf40f6f0671a3f78d54a2631a6abaa489f9603a82f4b31bf70738f1e64bace59ceae66640f46b6e3f25dc051177594cddb84063883d6c6e3ab2aa3e2f5bddb894a16b25c95970a630e6f2fe42b5c1f11901273876310693ca6e19ed77602a3842f1b2069c01bcb13b16ed80d0342bf3de0b05662dcd10da1f5092a38c5894639a0db6a0e62058b00c53abd56811f7cecf3927fbaac1804853a478ee0d1b52121110e52ab8f2ba79771c864de8a3526f5430ba4ee8decdb81d60bdea0181b56d91a6ffbbb4bf0442f1926b3e8e1e15e71005e8f1a20e27e35ea76cc162497604dfd9d0ed3b542ba8ead33dcd7b830e3ce1b12fd1f0353f1f45eea6d62a423502b9b364ffa6b92dc05bfd3ab1e0deaa79a2e42ecffc9d57647f1f3e01ff4a3a7499f8d863ec7a0593a0ea4d94efdfc5fbc38c996df201f174f0b32ff6e8af22446eb9c2002d4a7b30ac8c6c5d77052bba7ca078af639a39f8a470870e57bacdf51018363cff4233633e5490930d98ae9471750e915d0a456e7f60a3f6c646933333c5eec11aa345457c8517bbd13acadd4bc2773c03ec7652e80425c68e8ab97a357ee35312ed0474bdb5a1539cb22d8e10a87c70eefd8b9f26994e8b3a14fadf39450459e4ad2136e58c5bdd44a861db1d8892c10e1523774f8321dca50264f6fb5fc4578418546975e68332dc42d884e5965be52bc45272020dde40fa65a0f67add422dd70d25d3ed54a14c5618b7ad30beaeaf6d54b9cc6631ba176a3e7ae4c6d4f70a8e92f2b40493da63b7c37a98861d90f29a462a30f5e2c4a7ab9699b0d104b1aa8279e87831e51ee3bfafc1d3358cb9ea7e3694269954ec3e8705d42703d664722059c47c1bf837894ed15e247e23dd745e3d8397cb728204482bdb5c614de4692adef7110af2958dde7144e0d5fa696fed12354895d931765794e2e4c16eb89b5b2265444bd152914f62cea7c9330dcb37f9a0e32aa0a9ff651562924fee39bc24eef1e5189f7bf3efb3fa6ff27c0009890f2db33c298a22c0cf419e576a058afefc0a8fb3f9078cf344c58f8b063a683f833d2d2fbb057f0982b88a87d2b3155cc32223d1893dda8a207f46777e33bae1f58b6973325fb613fe57e8001bff08f219cdb20a9314de5bbea6225b1e781aaf796cef47c3e826c414ca71f0f85da08b7fdce7fcd7613823103f6a17f4a38a87e144163fd8aa5bfb6230ee684210bd70f3867ee4154505b74f00c62aba397d81e42bb492dbbe697de556dd1d33df7de63ad51e02ef888121854a850f93d9a222629591e19ffe309bdb1b9b9fd3eb25dc1df0e8491c19cd6ff40f2b2da332896c368eaaf581c83e8bffbe8416c86c6a6c34de30966865e927895d4b46b931f826cc11b94828737435dafa4f9558dd97b6fb0dbdeb7b71397c106de0b04edc022cf3dafde623e9f5481a6b7502697268fd49fbaa4fdce873991b553d36616614ad0a0ed37805522e4af6a952c397cb54294f4480fd3a06655c15d88f1f8c2612bc696f874a128f4ff25468d726ece0a80468e08a2bb16f9ec681365690b7461db8ffb4445adddfe973b19bad28fef28d812069e3c9a07c36698ac665c9a4f77c57fb7670f9279a8b6fb030739b53fadd4622d9323fe0254f06bb74c23982b0e2525db8b8d0a370cbb062570a2aa64c15210ba0bf5a19052dcb86ec6cd2936e927a013a45fb6914a3318c59041ef1d5ac6aa08b39c0d5bfb5cbc28ac0e7da844b0ccf03db957900f1952c60ccb413f53d801c15bec5d7678fc9f496bbf36cd20cd8068dba111307d93428b1ce410b3e2cdfdf5493d43a0b3a6881b0ee411a0b16e3a4e65350cdbbec972903ef25d18c8470b3b594044766593d887b211aebff1cfe9e2f022a56ec4fd6573a04938c9cc554569f3e8e8d391a8defc44bb3098a63dcc9595f43d73da9e0f41554fc9d7ddc23f1ca6bdfc60cd411ba7d8bc2c4ad05beb66e6eac530df71d59287b7ab247e52d74d27a61ce98a4aed40ade9eaafdc282a8e967e24043f299b5ef2705b5ca5c1b34707c75976d2b0a18b785b3388f8b6ca340a7d3d8a5f7ca1e40e7a370d48066eb8a0a36ec4fd854aabe6bdc8f7431bf22cf31fdc79192a4003ef0bf40c2c3f4bea46609293d7b0e3eb8065a5d3cef62c4dcf053f12c1a857e65bd9458605bbaf99a93b9d987accd003841a32cb60351735979967cfade3c2a8a69aea7e9a0788b2e779272ce4e359d2e5359086f79aa56417b722f375b3477af4d2fa2df811a15ca84e2a2bb0445a452e70df980a8746d658a442415ade9985ee34159d30fbe95817cd58b9b27c87a077b011e5e8b68602533556d20e4d9d057a46a697d79882911c99949fb1d659f521dc3263bcf4453dab3ff94c7d8b12bd81d17e22a144ddfa6b250d6efd11fc906b511fb5541083bff7c71929575ecd4d830a8eebf03a136ece40351c6b89c2ad14dd6413031c7ec8c5407604f4c3821b032b280914ee9be88a57ca953ba77e3c83884e912425ad2d8a15a73b8307a72e696634bfc6b74c8da6498bc9153e8aa15383694d045f325b0765c1ab48ee88558366e18d46010a3bfd5d7595f9944e2e0f39b2e02a2190069f3396bc04d66ea8ee1aa719047a175048150cc4ed6b8745d147f1b6dd266e896c5ea1e93b357087837864f3e88fdc98d4ece64b14a19736483e664f945cb0e38eaf0cb50af122620833064ff7fb307b6f06a494eb6056b4ab3ab84dd8698672deb25c390b0171d19ee6623e4f47b0a9e64b8e36fb3b1abc2210fdba6f3c94138f66720a85be5e025eeacfcb80d75eaa9918bbebd914f998b9c3ef581b465445cec8541b96ec4d9299156730a0bca221fb38421ad31a90bdee4b4a215fe8171f531900830a7209a245b3a6b8d02854410b06289263718fc6d08a492ee5b6a506985ed8138b7e00000c6f4b7ded35e6fcea4f614b8916161a588355a063061c242c0a5cac779cf927a3f88ded44efd55f142eb8be316649e3b7fdb029a23d638abb0f68358877423c6caed110e57a76a051c287f51b87000acad0344270991de63991ab3ade4738ecdf6bbf48eac914086397b7032f61f483d83f535ec19a4e4a5eb3bf6dcd1efc4ac50de291e9a8d2d3f99f6b812b855ef641ad9ac86a9ac6336929de869c9ca0b6e2bbccd1204dbcdbc8dfa470f4742d783a92513f4a0cfd22731ef3b0afd14fbf05b5329be949ee4e3fd5d76c59b5804127cc1ef2c7ddeba106b33858f2f62305dc5b75951c395f3bf0f1cd4d54251db70a39fc6b631065f70d83454f6b5517ea49055b491cb0ca561b16d0ad241057f02007426e8148d9d4669cc47f38e1948816e8457152d3b59a74b315772f5a11ae842ecd978b150c20f336f6226bf0367513ac98201ba2465b9e4745740f3e46a920a75cc052b69d0d41a42c46695c76210d748f57e889dd77c0706ccb7d629a946a12c7c21d560b273618333411114ac4e933bae43c063e86672463710b8b57b155f96b2a8fe0b7614e0f6cc8bc9d76a268328a94b5afdfd134d42c35e6e35c506cf7fecd518e311063c929cee93f701844925b80503fd58513058e7308ba981b98fe5e23ded26918d81137923b5dda76430e5eb6e7cbf53161a160770f28601873fc0c73dd7b843142ca56ea388d8673a8ce160f4908c60ef0b49d2489f225c84f3c208c0a8e719e201801c19205156fbcf6926a8d60cf69d1bd28f65f80893a0535b4023c25d713ce9be17e4c839505320c18c2835237fdb6cc5e124c766e659a17637c6dad1bac49ad6919c8b7a1318c687513f876386da7e77a99c001be4499642d9922ebd3a302cbaee4cf7f5a9bd322e5c5ac58ab0fd250d4ce4987bb213ab5c3e75f07fe0d540b6ebaa8b0f92bc6d84770def2ce2c1c905e4e545118fd15383b8c7ace311869db2e6d3ea488509ac157328547c8cd2e50c88fccc0340c00948899be0306ee576999e31c0607ce173cea4853fb765f444a0d753e47f63d7ea6be648c1bfcc60308abe7d862c68a1a90a87bec1b8b15ebe16128d346b6dfb03a1a7ba90477a3d81a3bf434a4790a5137e1c7ed64304d75e026467be2423121e955a87dfe82fedce59aa263baa83a1da5f83c2dcb51292e5fad1cf24a93472c58160964f139e3242db3f75ffe4eda4825c1bde6ebd7aa2b503bbaad18072ef1aee2932cb7c7d854b2f4499ecac1a921e40c8ffb8e6fff2450d2e3430eb9018f8c69f2dc707513cf522d5734d2acb91f150a9ea542e5daed3449809f8e76723154e3d0c76ee4a656d07df3bb452bfd872a270db53e5439706fb6ff2dd88c435bb113ca57f2ffe70d9fdcfdc41340ad89803bbaec959dfe9a32d8138f176793b6040cd4c54aea0a34525f18efc6523faa6b3ec7e4b39eb491441f6ca53e69613d3418002d92f9ece907b50f1a8e3fe1c77368d05887293770bc692b02640d9e6f36d47c00217f4d199fadf75066f2fac4bd3a836de016d9b669cbb31c41d05a5c8a00b9d69dd8d1c94ac1092b891b7aaaf62c2e7d22766841a3bebdf390fa669791f24559dedff72c32fcc9c97bb9b8b13445a2d4f76c6971daa55c2284c41851629b88fb113f138a6a5ff521b34a845eed5b2904e8f5ee2a2fffa07007ad637fa7519ca5a315b536c909aced28a720d6f2ad62ea4c7b48399d27a32a20bce288e8ea3228ce97967043e7e1e06295159ab85d7d805e01e86f312834bbdffa02a5ff2601a7b2459ea9f030eff49df6d3962580917863f2c89e32c66d68b940c424f894113aeebb6d3e857c71540ae5c9a0dd96c491c43295946ad467902f196103d54a33523529cbabb8b82c04a875546749a7a975320d0c201ee704dcb30e91890573fe6348a0d0f37f79badebef3cd1306fc4dbb7db1744aa045eceb66fa66bb2e171efacb4e1f0758fc77b6aadc819b1a7d94d389600384b3eb4946870b5f9680c28e6734d8111ab4dcab7e56511d375aec6001fa7dcea48c3236dbbb80836ab9b6b5b75f4ec4eebe3d2e8d64c5ead9e6c0e6d98c89f911de90ff118bb6e2039f0dbb55bc820fbf9b436071ae80ae64edf5d8cb85fdb7b7a0e21f02d24df941da8c5010b5a27db34681e704391d3b4d771d7ccb471a303a258955cf4cca0146459c903d21854a91373d06101419ee3b52976496710448db0ed5f4b557f670ed2cf5a763b790e18ee88a8e2aa86e03a07d564a394ca4d8c98bcb2f2c48b9903d9344c4432a9dca485a15277155bd38a0aa3dd34f3b5ec24580b8cea5c45b2469ab8ba6cfefe3e7e639082aec95ca0ecd616ab9774b83a099c0632b4fdc2a23210486dd1b6889c4825e3285e0672f88f93a3481ed4e0545b85fc4ca31a2c584a2b4b0fc27dafdc337ef604c5dccebf69acfd0763c6d1b26c665c0616dee4dee5d2d7b80cd332cf84586382824072d7c3a9a71cf02f3406b7f11cc2004822701108c4636aebf80d2f07e55b38331b4e496fc05eb00ae9370d33720da213a1dde48dc65c94eab29da2268a1eee83c5d6801fbf24152fec6033c4fc55bd14b56223d5c48da0e78559c6f6bb31bd2fed400bfd6da3311e96839a62a3527a9e1f616124751451bf8e77561693f0da1f0bd30dc005aadeec77d4070778f469ea4ba277d38c871a2b8312f4100bdc2dd7831bff46ad639741fb1eeb69bd2130c3a20afc0ea4867f04c80d112909834799749b25333a238a434fe1b9a947110d260bcbd01e80b69dfd408101180aa984e0a5f99f2f6e240e3e8584ab53766170d13100b7c95960d0cd37ba3c85cf48055414c57ff8f94df64caa0d624d36eac6d059f11ef76cff51755531ae9dc235fe0b3a55e3a852e73481159dfb1471ef6ea06f97cdbf859045ff4dd7a89c2014b4d4925b96179739f0724fcb445bc3296afdb13024f9eb54be0712671d94acbddd88a4d27850b90c1ac76a4c255a2aa611ca2d16c58a383f73f86c62b61ef2be35864f9bcaf1a08b9b89a7605b93e6edd011875e7ca6587ef6cd2305199a5c4030c6341e559ff7d4cc87a87085e69dc70c230ca46327e718189dcdee089f7f72581b61e314c3b376c3072d8e75de464420a86f7d64d8245916e3d71fa7b1021f1bb1815baf4f4ceb1eb87a8dc136f17a8130228f559f4637a94295e9d951f1e3ac48c1a9cdd9c26bd407b6ca7efb78206285b7b76eaf6518d2decee7d6895b3d28e6e2873a896b0bdb5b18aa822e68aa9d91f1f5a70c489d5e3b7dd6815adb6482a3e8861cfc9610c1569f967607e0b4685861e6cab162d4ccc78072c72fc86e6325a66168442461eb5dabe01979f07bfaf80db00b0109274506f0476588f7c150d3c6a377c2aff2e08a7b316efc89ddde2b8bbdf26ed0876f478fe224aaf9ff74e094834deb59bcd3bea1e64c7bffd439d85ba11a930c5d53843701821a772254eca30a85cd43052341e72d19c7ce1a6fb91ca23fd3001ab526c1b9c0ed518857d18bbc1e2caa0b0b3a35e6a7853ef283f2ae9a2572d1ceca57b3169313aab34cd6a8d920c2625458cac4d57df04e48bfc16f8b974e178909535e26b965ae2ca251c881a995c6fb6206cee30a49dfb6e91ef822780527430c5ade57c0572e37f43d6a86d6a62211061c1b27cfd824320963cea0bfdc0e6123ef780afe27f4c0b5a3472cae09a9d1bfd5855ca6c6b0e214f2a2dd0104b027b518356adfb12dbfda7772f26ce6f9c98f0e9d8ef19fa4ff34677a3d233c41c0fa424bfa7e34261c39156d7b7328100db7fd1f3242db62172e3371b2735ca7d4e67d2f3b98e04a0fd74b9fc3f555e3ccbd3d024bb482758fd9acf44c8d88bf73105031e162bdb9ef7c2f9c08ae7f771c32bd945a0550e17c00f8c66835e9d7970f9850eafd31107efc955143254bcc9a67ba36013176080185504228c705d0a4e6c7c8f2c1792c79163206efa5deab2ab97b2986c4d8248e0b1368ef585981b11045edb837d014d0110c64d9eb5d79cde7192ad70d86c1460063f80abbd69c944ec1ac1758e2bd239bc49429300054f4bb0368d0cb30213b36d14c4386892fe4d10f19d30e075074f12a507714a853bbadf4f633ee90dfe85fd7939185afc14ce7621cc87e178ac6b9622e61fc09d29554bb5186f23447d8a35e9d36f64b2a0390381677a4dabc577b6dfcd942772eec29d51e1ff351e4cb792a234694da0a8fa4804d0892f4ef24cfadcb93c767484cb52b912a7d4eb8417e19406fa99414cdb410836a53841fe26ef0fc68933af2a73170c7fed35108f01157df71dd1156ccaf6f7540613e8af39196dda838705d60f1ed1a1bd8ad7178cdfe6e9ddbafc2e61650aaec18fcec6193ef860ba31bc6af99761f5dcde3972e45ac3efb17952a1d7be1792b06dc11928f60b54d856538f41ddb6d57e24f53c19983b8518c97e321720cbacda5702f6cb194e83d0a5a3d2342d262767bb78535b96134196546d064c927bb91b50e7e708f01948ba39158744333d441b68cbcb14416788596ce5a2307638d3b4ee925298dfac9d96475727a8e26a7b950ca53242e88bef7dc34be4461c703953cc3c2fece071e8d8bd5a74ede4b271735394725c28f45a3671dfc136fbbd19b37e834663a272b8421fbfc15fbcef3d18a2cc8bb4e9d9e8cad0f9cc75450148377a698bb949a43fa5067270e6c3f55a5f79da08b0c4a9d0fe4445f82b37497837ead2dece261244d4535e765c6f9ba336fdecbd48c75966d0ebd207289dd62fbd4f0e531fa24bfc4791d8ed5174367a2e16a43840c6aad41e3b93e09691f9923283d1115a2b451efe8a527e8f89b1401dde5161eb0a87f676894ba5145f7bc8b2a2a08e69b940d3b9ddafa9659ff272874f1562a2619bd04f93fde11c9648779c592dea291be88ae8da90c6cba989d911d0ab05250118e56e56395f634c2e4d45bd78d936dbd70100252e56966554caa6e51021d605bd76edffd8f61fcd03606f2318f9a408d03a2605117e2591a326f317dff9872b30104391a0a4ad3f54cfa769f4974168cba0f82ba17041a8135633708a68b665e357e0ad0cd7b1caf45e84ab0f1736336d08a7808109210fc5ad9814c1796bd82db351a3e704b5f123fe9f8f9d30d5685d16f01ea9ad21ca79c214d0bccc97cd9556e2e8d94295bee1131524afcd01346b482f17e3be3be5a9d02c7d4dd9b9fbfd6edd86f65da6905c3f45425b8582ae89049297d46363b7f0a544f80f14905fb306bc7fee6ac69ae40e1b24d9811f9661cec821e7df5ef5110f88d312ac5339a50a1c4e35251d94e0befb8967fa2b906744b832845ece13c1f8e630e1c281789b60ef09e9c5fcdef4e543f042d3c058b5a066772b240b5d1506d45adb3e8293d7b247ef76b1372fcb1ae35a16e24884204d62882f78d7bff054301027a5c6bf0cede50987f0b8e7b1fd76fcf72a8055e76bc52bc814310eb2c9f999e1aedb3766178cb05e81ca38dec64576cc80228e00f1422688ae1e7f56c4f6718692f1d7309f99cb2215b997012bed94b2f08d6a9f4b47e77452d50f50557b6e63d4f524439d35f7a054332857595f77b1ef290b42d4c31c1d2d039c69592b8c671bf57275e5bd72fdb56129954bd5fffedbd64402d9cf0e23060656f70b0d0a31e8ffaed2aa6c2a1482662c8f46b11d682a1835811d0095f85b0f3d30bac7b6784c122056d0058ef4406852339d8141a44eb7a01e4b50240b0b16812758d7e2ebf621afd8508bf8f427f4deb467574e1e9faea1cfb38ad45f19932eea0d97cfe312ba4295a37efdd62152ac47677754d0fd71ca9f355274246f366a1e88cbca7970d74e65d9b7ab5c1aa2ee4e950486a4f71ef0f992546bd326236bd1ef11a692f364788936bbd5a039b8e11df8b082a80737d102d88a92b79a8a151c155a0ca04d2baecdc0102699bb10e6b6d978714ad7d07e092e3a5768598071c76e04e187e285c6fc08caf8563864e91b4cfc884e532966049efb50323ebc8984d8a1170f37ff312af579f545ead37cc8eef25a52796100ada781da94c7b4d5a0a8bfdc1d6f7d0abebf4af09c70a1f9e22c4953a05fcfde3b16e7394e2a0ceca462ced744272ebf47d16d6063e87f730a673312fe1f10412eeb2a74f33410c6302bb4c1b86c2ff362fec2ba113538167cef340bf120a35ca65f74bb98ccc92f6e60efaaadd78fd5ac41432769266e7d7591269e4da2aec889a4a51cfb69ed5f66dab4d38ce39d8b380f7d09d17aaf2d36f0b264225e733581848528bd415474cf066a03503ce3e72bdacd10bb94a5ca304b455c1f0317c3fdb9ef75d7857cc4797b1b37cb05a32d05b4a75bf579ff595025390933fba4df0c9c4e6f3e9aaa423a7233167d7d7037cdab0f6a872c4cca02e35669bb252485dc0fc066d5d13b038ea9a4eab91cc5cd1db21615d00ff7f742a6b8a0df1712a3a74eeb38da4dea4a9104f95f3fe8291e355ac9f4355d4426fa57ac340ec7860043d027ce62a152d54d94e5ed143590344f664f1c0dbdc7b9ad532c277d76fabd94ff85b7f5feecfc3ff9fbcc84c294b073903ed3d098c2cdff1b0f2ee17f474af20bbca5084ddfe2301f0b7152df30e6ed3d7f2bd1c49bb320050c40f7c4edf9666fa385b458bf0a6a55b34d45f03fef9f72c2dfba39890428084c51a675c2507dbd760ac82d69119bda9fb7a35d321ee3bf12c589ec01ed324e05f8fb22f0271660972e363313a355f469b380e495de7dd510fc099ad40ebb169dc1a2112be88a0693cf86dbf47e9b81d2444dd845ebcd236e35e9833c08ea364c4c96456e44108264cba85fed342afc882eec2a273e9b83c65c97a8bba1f51ee2f4aef6f07a96c79933ca373ad08592b8f3f8fae7dc427b363bd8974e287cfc41c8aa62f666bb993d92da350fc1a0284524364fec375f98b9a757959ec0934a34bcedac2181d2ce390f79635b6fe0d3a2f65e471f65e1a99a702a5572b3ed3bb71cccea078c5aa020ca489d505e833c853dd155989f9dbd5619f621c579412d7641ebee51c2c548886a8a9ad18a9b4d4eb102e780effe4f8c28ee094a08a60fe4b35be16eb9c17dddfa30c24e98124429bdc06a4ed71919ffecdec856cba0ab2130739d9ae3267be008f4189a38a1d5370496e39c27d8ba01e975b55e9cc7518654c1867193d074a24b9416df456504780acafaf9a033ead10aa18bbc673d4a562199d32c08f0932fec0c81b941d8dbca840abe1babc68698ca2456953b45abbf823aa18a85a35e66eb7307c9cd4f29c29c4ca55f7f9588a885b963415adb22d0f033fb980398171b15afd2c52a8aab933c2805e32837eef01dba94012a18901e9ab0bdcca10eec7da348bd220fdafd8f35ff27f4a41deb57d04cf8b7fc12b4492f401b96724808ac44f263c40bdd650e945862a2ee90f556bfdfc540c4d43e49581460b48ec146ee9676156feda2a23390a03ec5afc990409e48bde24d00e1167e04f8a2c204211599d378c41cf0dcab8364ea75885520ab190cff3b324c7deaf16b367c17862338a1a4955f87da0a1b5da4ec1cb545d060458dd60fce281012f13e719dbc4dac4ca7f052881123ad6d4136d22e2a4225d85bb8333e9d1d124eb6b15870f57f63664961bb2ec86cc7967940f8c92d04191f2f9ea3ec4f963038bb4c36245e089815874517aa53cb20c050d6cbe57380a65d74b3ed83778602f3e53190c7b21819cb1f2168aaf869a5c57a997d9d7cb2b4df98b2cdba73d1d7742bcecb5beb234b00b6a79415ac596378e61a5953c801c87f467669b355b31bd347aadd7f4f1984ddf9d1de95bab69f2393dc4526db9d8feb911a9dfa805b462cdd825a8eb15864bed323e516fa2581880094463fca5e5f43467cbf4102874c0f228bcd3009dd05c77860c34767e7b038b35034bd705967ea781efa816e9aa97c7e93b4bc933ae9d418ceae0cf2cf42e9ca82b4a8b87045d11dd2c244d9eac00ad4897b9b83cbffaf46f91ee00092e0e6cc7cb533ff384374e05609e9192176f7665f938007fefa9750ecc4ec186d6fbb355a974dda7d9d97d1e9d7ae666b5020951f2d56f9885b9cdffb2f1b59d39743aca9ca6cf1988369e5aa6a0f065b1b7b9a1a81e4f0b77c88a87d709f94af9f15ee23b1c25a28cfc944f4b3dbab1f336883d86380bceef246126f0228e6ea8aa1be83992b91d59e2188a3c0ff1ea6a68b0b12480bd52720e6f2e681cae1ad9b98fcd03eb5d2eeac5888d50a71aee9f0387e09b1467ac24b69ff0364f3ee7e531d0f846cf37a4e36d98e1a7b608feaadce873cb6ef24852cb88043934c3bcf28c6488edee100de2c38af1f8b795f207a57fc5be0f8f81bb0326b8c3f738bed181126f103f20dc8c0f4a7a796fe89858e1da184e7676a2a38f639eddfb9b628e8cca1eb313b10946cd78b4c102aeef53e503119cd142093924846a51e32adf82e51019d4b27598c28b9fad71e743d7d2356dc984a544a7e32a79f4664a7a367933fcc25e818369f672734d7083dc68c3ce667afc8f179fc3876d49948142ce28a279a32a48241ee59ef957c14ddb92d327130f14c315945690c21021db1b571411cd8ae6ae61b042c046020301e89080f673ee0eaf721152d4386ec1de5f08ff8893f0b6bf3a665050703231bbbb9e516f057b56040e1e68e139d38c7c00321b648ee97ea8cd62c2263cef0f4da533140f399631fe3b8b081252414d8a7af9ee09989d1845f4c719c0f031a18f96ec086c1b0383b143e7336a910ca8183b112cca18f2f0e35da8d8382f89ef22a47070cf204c7a981b4288f697d4be99e7e6658652ea6ce4d0f91b2827e0ef18fa6531fb66ed5991218f0fae7fc27967abef1868c4aba354227d4cd408b1fa01921f6650983ea133ebe354c19e694450ae93f9b2ca7297bb4e736d8fc3b50868eaef9bd86ee2ef58ccee62917978b3d4d00dda33a215dfba9328c029b31f5f4e8df522ad3cc211e7e0a988c803ffbc812ea5f0173a2a4f907742e408498191b5a6c767cb377870df4755281d5a3ff99ecac9841baf88b09b45ed6ce5c976e4ef7c7f56c162b9098620b922a20c464d1cbe20d34e637f8624deb2269184e9bcd345b88c40dfa5918a5e4798206cb82739e0b1c4e9cb4106039e3b1017129288eb15a09aa50cc0375c12e31fe941cb6918121a2a1fa188ee45caa997066e256c02c333b3edaaac90b8199ce11c06dc62f18a3a51042957146f2cdeb179c8171f73f41a2694d6e65a79c41a2333e3686486137b1906099395164c3d845c06a30d2d3ea12eff1a6da440f7dd34ff895753bccf3130122cfd22873a4daeeb90732cb568ef8771f321d1c6086049765efe875f10bb7839d43b7dc974a4e3ea6b063d171a9128e4e983633dd20f04e2cd64584654b45e938d7ea7f08e527e2bf0f0a5b462db45d89adb7fc61c553ace880d2ca49fd869164402117ac28dc87c5868bab71fc203b84f8210798e72de57ea7de9585ee7f910f4e9ad053bde203cf43640496f3c73443fe4a8915d2196053ac175500e940f8e8dd487e84b5bd9e107b8ab4ad30ef8d69632cdf039e433032622b0c835fefafa45d4f0c41b2233e474107d20b50485da81339fbd20911d59cc7d9dd4bc2e0b47f3b1b3ad42374c0ad08d40626c076a1ab66491377d3627a17b984ac04d13aeab1bd4e43040060903d391c4fef60a458438d7c2fe63ae70b38fb8840c2329f2fa43aa03e562df0e1629abe281f63cc7c2d8a5ac7b075a3c2f162aeb086d9e54b0c5ef6134a5b4db468dc77fc1103a92570c44e648ed4131cc630c4f03e4b2f9b7511420cb4c00a0689964a070dfde5cf9a82357cd17fd6f574e4a26db6d8d8a78142fd45b5575a21447925d7a6256415202a0ccec04bac5a174a843b202522cbaf25dd125310a691bf6c04f83ae6c306b34e697b8337761eb6eff4347c62e9d035e1aa30be9b033ba311bf85cb1494fb70c6f23fe780f72db9c6c8b4f8c8093c516fb6ccb982db2eef75239e5579868dd55643c2ebe773d84b4c3b53feecd86b8ae70ed562cd9e7b4339fda497f2dd35cc8c30014654864a1f5c7568bb31f1c20ed38c947d63a1b34706d01dca23543b96eecd5038aec014d893afdb0179a73e5d7ce0287213013b71d6a2b2d3d3329482c9903bfb397f17d6b511895c0311840c6a055b445ea38c1154184fc64d8596a1db590d56b5b888fb9ddff83c083977a57560821c66414ec6de11f15a98a0b3d853df49d1c8a56026226e7e1e9bb3c3b716dfb864773c06cbd39c5009d5abaf5509955651b9f246d50f64f422887de398f92aa79f5428d4be9006bca81856210b7781ae9bc5ac9cf2de3a22b4197e57a2fe3619569283b606b446cf0422d314058f148917e9dad43265a326e590a0451a8a35dd9443269d858a0666f34150da644c64d8e4745211d013009fe4f04dfecfd7447e695f12454ef5b7c9f3350ff8ed0761ad73cb4871e2d22e6e749ceedd5321ce6012cb316ebaa05c9dbf9d5234a75315ed5df280c1ca916e121cdc236871361097716fa3b35a660fd5729bedbd139ce34db8ab29c177fc656fcff5af6d21c7c4995cbe4e99c75a567332bd842c339971b63faca21959943f7d2ce2e374286bb8d06a5582151850c71fcb441b50bbd256f34a23fa2283909e6f34559605293d39015f52c82df23ed39f8c37e6c15c94554f3a5e7d4c593e806aea1ebbcdd266584dd9148645c521261dd70a108b051d936a9207bb082cad684bcf30d9f36355c13169c3c38a279e2e257dce65d7f8c60ea11daedbbec5ef894adf4044260f15a2a9e4a725805d866121f171f384e666ac0169d0e0d5afa616e58f48c693263559aabfe6ab7dcd9632b481c40770b702346b6148b74b6324cad32b362398b7dafbdbc8633364186bdc20d0881ad338f73b1644a663a7f3c290a1f93f7e0adfeaa1ffb3efcc3c20006b3a4b31cd1f5c3c01db6d2ad1f418e6e78a400cc1e7b2cca06f8a391b1dd1d9bd35de692a06888d1dfab601c37808991d79a6cc5e87bf936bfc090865ec7afd4dd335e745523f8803abb9623d54a3a666d00d9b0353910b30f8afcb836ff7c5ec5247425984460b62f64caa234755d85970bed17782fc7c089a19a544a880b1eebc14c363ee50a9114844eb83cbc19fa662c39d58ef1bd82f1ac7ec5b29feaa1ccd3fcf197647048c8c9791f38d39c58d454cd7b694fe84eb67c3357336af946ea23b48f154c5512c21b227e63bcfeaaa04ae87e6027a380a5b706a22cf853a51cee12fe72e7ca7ec50de09676fd36926998d16d33f539dc1fb59e43a824411497b2ceecd1aef0615a597ccf57709ed27e7ae885e49a9ae992c9b5aa05165db4b1315bb997b4906519bddf1c8976ecfa6af7c9b7895272c2c0972203eb4d96247568634aaa85fa93e304e375341dad0e19d94eba0e1774f4095f3fd82a187049ed8a52ab1ad9422dbf3d9b6a70ff0dc527b19dce49b7ec49a0b0ccdee5d529a4062de19ee1bbd980e6adf3122d599344ee2371b4d7f15435a527b09d4ea3a1f8ac5d5b2df9a01390486a5df421a7405f806e4de1de8f46cc9e137a1b0b7c83ac53e4205ce2e7206cc2bdf39b2164c3734dadcbf2b669fc5b15bdcf4ddba31871963fe457857ddbb8ff884d7b0399ef0c604df356decc4cd68717bc29fb78fc66a11b367c1efdbca11f4bae259a1da674f5be6580650ed7b5455aa49f3ff325f9b370acd31bd359165fdbeaa13f31a64340ae631efcad27eda700bb479aada4654d2a88d7884547ed4fdb5849e2e5974fd7a768d6a740140efb3b52115675ed730c5f8110a6ddb6b884370eff78261cf9ff8ea048b47e2765d21126777f38f549c67b10e06cd870b4475aa9394c50913435a7bbde803d26413ba908cec5550b3f0e1d341e7bc88c15eb715925e66b3840b8d2dfa65045699c60b7c4265fcfe59e7868e6958174d90177620f6e7429744d95f4310320332dde683bc5576d9917c72a4441db07827895c9a80f83b5c9bcdddf1529cad3d7b6d385da6ced6ca54c41058c45fd624ca45acea8d4e8d397a9a743ec6b38943659431c2204d7d180684136a4a3d61b446400d9de162ad632f6cd01b48ee56e0e3b8043fc1a37f38115268ec2e3712c7a263e27853b6bcf28006b4b2f05d8e945242de1f9ac69dc96b5ecb6a9b6cce2cb9e61f4647f9065079f8eeb26b1842f4f26ff31aec8c9d601e2f3d42346b2e39ea550ac438a2292fc4b852f1ae43c67b25e30f101f8baf9b6004e0604e3dd88f067c0fc3f2f39d960f1f20ed173674409b6597102a9f6e14363f963d7ba694e21e574b397d501c89d7cdc1f17017300a9605e662759916c8c513243f786bdc6c5204578db07ca09f7a6e7ddb5418a5838a4d07a3f3b98d9a127cb75865edced017030ccf23633533455c63ca68b6dc2b046797c117199f4e8bcb78bf24c970e1d95f25bb29436478e14b1d6f353603f5a1c09302dc040ec7051fd5d2b67b6cd6a2ce3404f7c8cbb61b1a4a4fb27722638ac0de2f52f20839cd40b3de6c194cfda7bd0927c9e03beaf13d8912f0ebd7323831102f814646ae0df83f72b5f92ca8b0a7d021975bba41e1a6d6aed817e976c5c7f1409fff3f569fd91be85c415e1233be46844f648ade9e60a445b7c6d9819d9272ba4547a88299e87fc0ca23f61050bfabc553cab042091b71d813459328534bdea20cf55de19499be78a1febd20ca5220d90ba80724205c2c79ed0648b31e4cb357f3083fa2d40baba705261d07e24f0bd49c01862379b867cab68c5e68189aca39acaa01de8a864375e2b4d643b1dd73f92a55d94232e36b7525cd921f989896137314a742c402658590ca1f0a0e8233745b5e90ce628aa07c5a839a1ea6cf5dcc0191129022a06384e9852b5d289094baad6e35af4bc7a79237e26b34095e11491c9dae86468baaeb2026433efa38bb1f884db536bfb337e3fb9c42bbf53653fd3fabb9a3a1192f0ddfb08c1dc13283062428c3a3acd7921d1bfe0b6f3680bbd727feea3a06b1d00fbfd81df8670de150805510ccee254adaddb9eeec820375158e95d349fcdb57d77e37e4bf8f220cc7789e8532f33a24195e72af28cb4dfcdc7f96ae906317410faa957a77335271f67a11f06c64853ae19f0eeffd284e11a3f514a12ba681028b22825913816cdf8eb59b488ef06bcb118444a79a668ec6d396171eb9ef03adc121b9cd3436c92c4334e4d811fa59e2882eee1264f48c86a20db05fe693b61a38a0f410cef60bee734978f2bf38972c021f04e14b240ac9fde60399aa406a1245f39b5ebdf993fb3bb137f2c3941327e7b91b54d9ed6b1c59c95eb0d8dbb473b1442af4afa8689b7c958dc4579ecb2cf36253635fac682f28801f37312373147c8e7779b52d9f63bbb70ba58372cbe43411137285a3e783f150ed9b68ad4ecca9123cdb5c1e59551637ef484616d76ddc85151c5a912a0bbfe7e7c98e98b0293f86c5b4709fd1db455d6afd9aaf89c8b1fee09ccfea4daa25df9816a3a06ce1f80a24bfd1eb9f039112771467575622a342819afa2460a1b0d6f80d6ec42ca711d76cefea5fedef7727f85ccc09486a58b542f52e823ac2f34922bcb3135cae97bf69cda06853935ad638d55046894c0dad57c8f44807ea144a074e2fea4a0b8c8cc2dd3ebaf9f4fb863c1de4cb0dd7203adfab18cda6e65c0da14d9b9d0e1b0e6b7e483c19aae9ebdc9caced6937cebd1e2e512479c062a11acf774e7bb8a379535eb9505f4b5e16e2ada1ce92a2e8927067448e958180bc76652dcd4975ed48bb37d425d0811d8f41b25fa6ea00c45dddd20db14b7cf3c0d10b38a6ffcfa429bde32419a6bb69fbcb70fe5f800ed529241314479b428dd0549fafdf3a55ddce521ec03939955a18cb8cda2f3550b4f3d9b53d8e8a9c22150335973beb48fd8d26a367290cabee0094be097d3410a44fb28c9a21b164bad8ebfc8045bd09eec244d3ca8e49cf60f0d8a9e8d05811d07b2cffd7533e5e0e8f2c4631802dd9cdcee2736a142c7198337cb6e919eaf5b8337efc1449a1266d3369992436fb8ab8225c76594dd5e5ebdb0b5962bd9ef4f9e3718ecc1cdeb9f53b494b5497188efd2aadd2dd8fb192bc5bf14b1de22dc4341ad54312fb0eed0f902fce205d55055fefa45cdff123d7e2bade04c8853db7f4195c8c10804eb691d704b96dc6cb93259a23ae23e1c5f1a76ae9fa9de69996040c94646b9c9579f414b935d84d6bc0f70ff037780dbf987c23faa0535b898b4642d5145ae1b472ed5b9f57ed04ad3bdbceb2665a8a3fe6bd527d34913685aa5640361f52e00364de62be7c500b1adcaa92310b880306f5c376d7ae82f1b050342f28a2b54e28e491142604a43c98ab360ff070e6f457e51a37de4537e097b0e3ea2ac501a63f8cf97f3d6db198f673c10e29d21c688c2898b4488903c6f5ac3c4737b1d5004d137a86bb15c0fcd090ba5e42219d026f8d8f2848d31d0f90dac423cb75ba7ecd3700d6065c7b81d237c277d8062459df83d1eab50ba865b15c117983f8e9c40212d6de1e76b189b0ee108691d763d20229c0051247c30dddbdb3a14e4d69e09f914384fd5550b55de90a502db8f81df3d0bf33e8148e0a265373f93b1af792de3b69363f11912d170b07665bc849aba574dafeaed60a471255597ea544f6d5172aa43d3333d9f1bb68f9d8eedad3b741a2bd21f0ce6ab06e6c947b91a090b85c764231a898e7c315a9e1768b9eca83827b92aae43a771b7d1d66c8e844d36540d67adb50e19a89c9184ce7c7ecf955e7caf1f454d75044957d54d498daeeb0dcaa3ab0a5e6f103f6027b0c8e21e30b7279f06c19cc071b98746b8d2c0e149461372b430b0d3286262156154ed6916f0c8a4820dc8562399e971d4a12e89690dab4c8d28b30be170f9c8a534cd91d4277492fa45996083b9333e9078508c3ccb690c3fd423d7fe2bf594c8489e44fc929831bc199a219406885ed5f00b362aa15cc7604a17a38e85dc3dfd092c68038516f208fc452fb4fa88c9176d2af8557dd242c7682faa8da1e730168afdfcce77760de5780615726b025590d92da3051193d013699a5203571153e4be4ef16717c6367b61fba8014a301210526258d14ed6b73d60a365c5338bc60486c92a44ce103f55260c9896a069dcbfeb0e3834e41e494cc4870481e025d732e05faf0936884af1e69632bc16adc7ab70a7f955f7f82adccdf3e84e4249cb3559a1ea8dab4f732a3ead954279720de66a152dd7e6ac718381f84c28b42e64625a040b6de681fa543084d732d64a774653e33588cc9c8e92788725bc928bf4a90d122c4fda2046def513f6aed96d24c363b31deeaa2cd0bbc2167315301aea8a320778ac59a6a9a866a0638f6b2c8192d2d19a5d957bf8cf51036553ac474634416e14bd66324d9e6db380c7b057ba5813acc65b48ce8abcbff74ad0e233938cfcab854af10e0873a8710a5924ba4bcd07b5f1b90a7f8608608215062251e48cdd1f3eddcbb7280ee1bbb873418a12688109dd342dca4a07739e5999988e8883d13f5fc8d3b1dd2203092a449ba500cb1e25a554558d81b0393fc46d260452ee645fdd18eb3c7b0c68dbb87034c38feb61d85b762e9f87362208afec3748291114f64d3fe7a06b7608a0668c95249a704ce32d88c3886c76b025e4e3b11aafc2c40fabe81eeaa142cbfc79506bf292848d26c5e3b8c034e9af8980fe60761cba49c8d53c8c5f3d691a35178c40929a58c0d32889ad674f36d3234154e44f33907290c90e67076c4e8ca7e7dd1250c9e2e1eb010840bd276c6ee25e3da6eae7d8e1910a4ac7a42841ac1545b3d5c144488e3277f99fc201f129bd3aae73bd3c85c32a327f013e3b6ce40ade0819eb831147800b5745047a04b2cb07ea5e461e95d7dae88c499aed0488a1e563fab5da784f92fb4751da51349df5320a9b459125efa30e5ebe0efa55e7152583ab3dfee44685da5a8cc04cb91cb817cf3ee65e22762dbb152cf4dac949c56d4857616d74877b790f203f576995f85e31eb8c94910113758fb0bf671002fff176e06c4a3e2a58bcadb903932b727ab31e3482a386a774d6af6b778b552e07c6d9660d3b416e1c9134f9441b6e1b43a30ccf40a611b868808bb9c5447720081ed9a43df1485b5182e9071175e05cd65352936e2eb53ed2267e83b7e964d6eabeeec399c6432f30104b1e09bfee48ca514cb383164318ef063c8b881b35921c0135a310ca4f22284b9d0aeae16b1c9ee16094eb5e5e827442296f7c533e65235b1e511b31dda7646a4fd2cc1d1b67aa716c32bd896979f4d1fac53426d34d45a756ca25c1b518fda847a511fc3c9ecb3a9b42e2eadacc210eb8599e19f4f7b71e05d21e576a73ab0fdb4b829ea4a3853ccbcaa897b277cb0bca209963dd77517e430be48aaf020228cbc70d6c42e926bc850bd313ba0f424619ec67d990cb5442a7a206aabad19580e64ae12a219e0ca366872c991e41b408d628ef858c208d6796989061538f736ea05dd600ef095cb8007e170176a6e3edb78b7df27f077cfaf778e219d27712610e2151542d873817cf377b25d266fd765de3b05891f83808c194bad871ff5d45514c4fa0db8cc5d87d5c6402e4c2c163e2709475783a8056e3182549847f899c41c6ff5f3d442a536488b12f76e84bfcd1ec95405c08a73c7183189d17a1f125915473b3a9998480d7256d0298de110854bdc8a498258f9a03fba0c802ca2ba1e8e3501c871c4a4a950314006d923ab12a39c5178bc049f02283dd1035382e900d8adc81581cfbe1b4dfb75853c010a30c3257eae070c0979180961061806d0918cf31643ec2d77a29679caaef3ea9854ea33fd34426e8abde615240e4cc01a3e8d4800837f290f31def003304643b8642447915c5a66adc13263b727f2a8b82eb516d0a4f4d16cc305648d9cdaba7e3a5a1d3e9a96e5bad58275cd3bf81d1f91f8c35bc1813e8a94531dfb3aef671bc5624212697c1d25ead2da6e586968fcbf5b9d7fb3337a9cee9400a5732ab2e353e42438e4a44e818b7c48891aad87199c76e57b38213e1739a0d46ee5987504810d96eb5a42a589e717cf832e2a675aa776471aa3e4d6625c12affdb37eeb0711babcceef5a72e38f4848e6b650e7eb20dbe6197bd18eb2def3e16d0ceeb0bdca7cefe194f6884e907172b2f1eac18ac62970b06f62d56a2e169cf025c13ca1c27dbbd109cb2f0862b9394a981ea112c46d054a8ac48587dd59b92c145da9d122203ee412871598332254ce4300794d219981f13db341aef8c36167da1fb862583088ed70d89e03c38e4e65fb44ebd12046ba4e5df539b5c887801f22c47e611b477d61c55b2ddd94557ec9fc865137c2b0fa1959d6e2ad0cfe4213932858a6cc4941509f2f20193f7a940768f59e400f4231657471762e4217a1253a47bb7ba45e0859c082553995305ea5937d36047e1040e5be54c21c578315b553fb39729932c57ec87abb44a538dd2e9a14229a96f0c866eba8a520e0c84ed6cfa1e57e8f81a8db4a70b4bdaa495dfa1c976e8b5c92f2e59ed30d0bcf266809abc7857e795951c06d80deec776d4067d1553fbb6a4c696e46ef9b54b54bde4e9bf90bb68a952c89d99cc7cfcb00b050be0c9a476993c2c49e4ae5a54505ac4c3104e6fb995cdaa591658c4ce02b5921d121247224a14591598328279848a05daefc1a5290e0741df15efeca455b86bd5e615f233006b94086960c088b3c70bd6f972c89f3bc2e79fb93368a3e7579bb074880d0b51cba2a4658cf83c6a6fdd6d3fd54974297738b2d58be18c9e120174661d656a45fc2ef974def5c93a7b2db5e24481fb46771bdb1b8c1e1d858b84bbd984ac13cab19dd8846c14c643a83721093c112dacbaf87a74ab723909423cbff584dc07fbad16f981dd60d81178bd76d23566bbf766e77518cdd350a0199ebb6bc32dfc695e379843f0dd301d3d046e35a51fd7ed9ba4fe1e9b9bb5689899ebff137120af01a2d1dc68e08bf4e179c9418883ab82112a553cd506e339a1b48782c8891071796755dc713eea0c20a3855a69a2cd55aeb93ef5d442b139f8e69dd1f2dc78a4ceee2b0c9cf0df3ae1e9bb61ec21ffaa567fcb8d50dc174e343eb0ca3718312bf16888edd414a3ddb1d644087ed395d10767031ed792e0cd591a55028828a257d4abc7656ca2fe3ea0dc6ac609176ceb92fd629c28ee0179723ab8d174c821cda48a91a6c317e178626e53f16fb20b2742b8dd73800df074756eadda83af3009abcc19eff36b80488a527ae8bf5719a12c3b0bd4e5a98b5dfc04ccc31c23df32888ec1fc197d26f2e484c5519114dee59529425b481906e0cf3239858a986c0d9e93cbf28773a4e1f133444ca12b9b934ba6515239551f4dca89652285c2993d0f4360547b30f20e64548a4ebf2ca44dc0b481faeb59ea5a6111907b4ed2d1fe84148189c8b1a8d84a4cee667e06b1e2d2ff677de30e70923ef554f175e9011a373be6e99d8a8299c13db923e3f2f234d8477eacd8ac47a9c56b97478431c0f7993065177a1a1522cfb1c88c1e47b9a0d8b4eea8b9a75d35432957c5fc4f9a5b15d6b68d0af483c50d3e97809f782f9bdfc025c575ad7759546656dd39357d050de280852c6a4d3f57e037866f9838ae595f637d4bb5fed90a38fee1c4fc12baf6db24e83f6b11756a203904db334b6df046451ff267de3bf2138b61bdc5269b538a7e9895e59411a84bf147ae9e98cfd0cd221f1c40e9fa7383372a6f3ca5f88d43eba34290879dc7a9f69cfe3cc2459b819b61c627243bf58aa5c6a7267ac78cf042f46853d9b06c963057574fc01a8db60a3dd886596705cd280d543274e3b8f8e96623c1ac5c866ee533667f6497c312ad9ea725ab6f53478579fd871235a96181e1193637de84e80dbfc105e7572cee58b0188d7f1f6ef51f24e8dbcacc0232833af1324339273ae3eec10f8b0cde8cdeffc02c572ec49010786319d463ef2513f320146103ba3b82745c308eba9a0ea58266fa7dc08955740b5c605360b57c5f66d484a679b773ec9080c49501be08c87ebe00cd3cc310ec0ae14caa4340134e3a5eef9b4c21c36e17e94fcf1adce9e476981795bd72a191e668c416d2f6eeb6e59652a62465070740072e07dc0bd93a300572e1e675c7820a9d5bdeadd42b485ba43cdd82ee47b89784eea7d7fdf723755048f7938988119095cc0947cf74aae15538971cd6420eeb7b2794d3ef9813d40cf8a98b8a5cd9d6a71df5baec791c28a43eed7674617361f251c3c6b2a4c54e9a2e29b0ea59df48848afc5bb693c3dac9c9a9072ff221284f5072e64bba27a8b09d5af4204a1d095585448c88a8fed5bd9cadbe71f8ea42229a9733ecb416815aac34208f1f0e7b5195eaf10761fd2afc593deb71c8fa9fd56af5abb0c8ccaf1e874054cf0a8918adc21f11bf2a0c419553b0e3708ad11ef49d195d56a864e2985505c6de6254ff2510cb8d64b26e92a6d3ac67b319d5e32d79227dface5c25c03b64c6913bf325173c36d2152cc1489f1476c8dc60e44e3f09f43da99346bb81bda5b33ff6960fec2d43e099bd1f7b4be88386df8e077c3b663e410acc7ad58f8dd43497a8dd36afbf11bc67f2d175a9cf7e273bcff3b13d15f2fd043b157a4beef7b8e46e2cb9bf91c03413ad7f0a5d216f2473cea80b55d4471794b885d39e9528da09a8ad74522389c136a42e22f0d8484923914ab57aeba386af1f8d444b809b16cea816bbdffe8065142bc98f51921f230f4722d60799628c688236fb2912b29e1516f99945b4223faa67fdcccf204da4a22b27122ba4edc17cfcab1f659415385ce118831d366b4d4b93c98fbcc621817d24d67e8eb65a92a4cff6d652912d14965a1acea4a31a064f30004699fd14c8811df772468374cf853f463ff73b4ecefc7e2a1cef0d2507ba30806176286228b8be9cd95a6e6ad16e16c98643e05196152061f6adb5d6edbfdc9398f80226db2ddb9f5062748fcd91fdb9c5beb4d6328151629dca23fb938afd293f13e48e0d9f6a9db485912dd26802bfe8b3dec88c035fd47bfa9ef7ac9008ebbdf0e7fbd41399f93ef53faa13f2ffb4de7b16f8d33debbb1939e03a0626a2f91fef67be051a01e99ef53cbc87267cf92b9933a3d77d2034effdf673d6fe3fadeffe8608f09ee681784ff3fe3f461e02e9bef53fded3fc8fd116026985f77f68ae7d1a5ab017befc573ff3ddccea675e4030eba9ea59e05773813a2ad44c1fc7182c6746f4f3fee52110eebdd4e3f881c493c91cd61cf8833a288158f6a7e028ba9864b2679e91ad2b145d4b29e58b0a9e7276695a7eb100a7e020a5e38c1aea9e9f4535e35c629ab59e13706ebeca27305db8508a23539dce01ff7ff53466cc7819353443381c314e38ba38d77fe128aac21a8e327f854953fa1196118eef5f138e622b1c63b5a69921d6100e41f07e27794f083812e64f461613bb7c275d1b7a0e0040d9b4ec34da2686aebb4d7bb282c060b87bf794ee02edee6edadd9476373872b9a7cf321f8539d9d32d09dc6b4b8f05842be12f5dadeeb59dbadcab7d4aeb04ed579a6f485d2d23309db2c55a3f2d787e8e9672a72f0d3fe70d2cc051eb53aef56f0872ad4a3674c9f5ab7cc09b7190476f8a1a92100293e96fb8a3a8354a84926ca5b243aade106292e9d35aab8e1836685143982a5c706156eb0fb9d62f5799ab0cb629d36f117ed46a24268628a2706109125866b516e54ac549ae55860f4383951c198431e7c40cb6e97aa93db208da9466004388f67042b24c5a70a62a044393a002212a65c9dcbe6cf9c2a54eab4403112f6c36c460c06022d7748928c05e9659b278829100e36499258ba83c62f10ba727b4e5cab20516368f3fa5dca19fa3ed14789c436456b42642f6bceabb4f2993974c2514322d273c00859fe0cca950f6e7a3e33ef5db4b0fb73c9ceaa1bba2543887b660224666aa4fed806b1e9f0b7fa8c2c0e31ca20d557082a13a4818ad4d010593964c8fc8f4471911f4e7049a4095fe03ba47cab7639280c9c2e9697ecef5ad222929bb97dd6f985ce9095276e9495b949e38653adbc79c20384cf2a0603b09da4cbe6829207bf27c7f14b05fcfc969d1715accd101d71f71b2879fd3a23f8665ff6d0478944e3eba7e8c752e830a5dfea20c4a64f7d9b783c69716dbdb3e2549292500b2a5d6565ba96f1b121dc8a38b07198490ed1e065078c4221358059afb45e802dcefcfe19fe2c8602f13ebedaf17bba1a45f0fbad1c740fba04438ff948729d057e1e62977eaf75824fbb70d652cf4b721d1fd5a6b7783a0088f3733d92a53a6d054c184a60a2636d890840d33ccf032a74ed1841f1434814e2174ce06cc16a5218ec5feb5041e63fff37380346cfe30014ea5607c4808f12121440baa0d50903aca1160a2d86dabb556bbd99e5270dd68583efd1b06aee1ab7fe60e63e10d392738f64474cace19043583dc45e089007beae7e7108147093f80cc94edbad40c21070f1083ad0dc4116d282be18710f865503850ae6c99504a41c19f9b5639a7a4f425f09dcb90c8becde958770cbb2b05430d47fc426db7dd22d28be12f29ed6ed99422e1eea8975d38b4c56e2a84a951151b266fc3e4cbbe9b531a7ad98244f75e4b3d69690949949f63ca29a7cc3165eb88f6e30d30bfeed4dda5b4a981c17db395baa49c51c3454e96b5c67a6d6d12d34b73299de15adea52cdcd195a7faa8089f8a8ae0addcdd6ba5b5d64a3b9c929293f24ab939b53555069d73f21081ce10bd6d72a6f794524a29a5f2f502e1480d1b192175e10eb9dcefdee1f8fa1173987c569eb456bbdd71ec57e5afcb0e4af9d4e5e3ebe6da79524a29274d4a19a502a594524aa9d7b5524a29a5d4fb542b3cf37d18a86cac5c18a75836683c0ef42eee928b044770aca19452997f83b97524a594524aed6d5fdde01aacd68c67696ad4dc8a5975061c5399db6a64cc70d9bc6478121c0130c3456dbaecdfdd6c90fd3b9c9ef18d5073879edc49659f8181cb868deb06a76ce4fe8e86c7b97eec1feb3bed9ae9775d336db029a5e1b5b9a95123fb77e1cdcdab064e8b8667737f0700ef29695455cd8df355708ac320a536b36bbe8865595d4d4777772ef07c1fa59452ca9aeab5e54949b3497a299da192b2384aa9ace102bb7baae54eb5dbbdabcdd6ceabb4aabcefb374deacc32465e7eea56ef348c9b1ee8c47371a8f4a4a6dabd64cf95eb3943b94565aa90c8f4aea140a6af4dae5dde6a975ba3ba6b9bb53bfb91100986aab9d9133f65f5f0f6c29ad332d1a302d9a992ff6267405405691810c6a6bdd2a4e8d960694564ab3b0f645ed97d60be31b3b43ab9db9c27ea9f7d28e56cb62b1baae7bd985b24c9932b6be36d059bd6e7476543796565ba68cad3602b8e1d9dc8875ee9303ae034a847670894c892385060e0f2c0d1c0f78aaad0183f3e2e9a1d5bfbe2c0daf96563b64da4a3ff09922c5cde3a4fb767476a7d56e38346eb27c7a656d819c84d940a062cefaf007c3968c97326a70c277587f4d173c7ffc57e80a4000bcdb354d19c8c820834f47fd1a35d4abb5bfd91a2d0dea8b34d4bab442e3d3e12ab0228c7e58b45e18c33cd8a7637b1b1b78392d977f5eac4c242c66789b6c6021a12934a9e0a821f349cd04d54766470644f4be173f1daa176ddcc0deeabf1e7d801956d779988ad7f94af50d994fbcce0415c8c88070a9af0d7456af1b9d1dd58d8ea7a323bb4957d66e98306f34ce104e269b4835cfa46945c804a1c9ad5e304436b9e4709c5b643602b8e1d9dc8875310e3a0eb80e3af0a4f4b9dd003c297d068063436a1107081a383cb034703ce0a9b6c609e7c5e3d1ec49ad25266ff5822ac5faea9161b9483497c93e1df46545b2cb200cd9b639adb488a58f9037fa93903bf5034f4a1f1f4f4a9f248c5d5eeb8dcfeb66a63978824e0eb23f75f798f489c91d9f14088cf1c54e0bb3c316960d3c44d1f4d0d492e202950f543a1fb2b84b5d23aa218fddd460f21cfb293799281fb8c8aca7cd63e38a79f5d28725eeeb28f900743be83a294de48e0c2e410f2f5c2994789062edd74f8725c00e284852da61092d1b614daf88be3a8871b5e8a04408997e875d2efa2e7a058728a40ce145174e66f4c5e6a9405cb152e562af66091e4516f20eb976453a28c1c9f4ef3645173599fe96938229327d6a37a5211e32a55fb9e4e420e50465327ddafda083031513e4e042a6ef395ea26891691352e0f18a3a50d8cc8cc04d578c1607585b68a0c4e0021e9a8122f4c28a5e160f4c544d2c612a4e3e033411b5520af29c545328e94690f44141810ba9273c2859b8c07550bab80618c1500a4a18306c475c1420254c51624862002786a2387161a916258a0b062041541422036ca145a1c2845e2e3480134b51987819c53aa6650b316cb8803924b028304cc1ad2cb33cf9a08114d826cb2c4f48884cf08c2cb33c49914197272d32e0e2290ae604af90d4a8823f2736ca60d6908d21f08a080d76c038a9c613be4c3570c03335510b5e6599258c164c61c0a8410553174416b08d013606e69e9c00df285b4809e307518c510d1bb097651631546a1c65119374a41613b3c5d6a493392924302bcbaf5f18065f292fe82370bf510a5aa048ee14787bfbeaf7d519039d4722790b715a746f5197c5c4609fc205c4eda2058a5db885f108c214b1618c714be90616dcf09447fcb48ad32d98eb965d0577376f5c33601fa9c733a6e011a7bebf9b9e6cd93e02cdfea927f8738e965212a9beb08884a285312420a168610c5af6fb94e3387ffad465a8c54240e58e87f52be55e8242dc3bd9bd48909ffa1da9972ab8a42f4122cf41dfee0d456858e7f841ca0f600b12794f411cdde37d7f2ce6b21c6070151ef2289b7498b8269d2b0afcae16a58bc0c3257379cffd9bb70db4a0dc3102cb5047eec8afdd5feacd5d0098b08ef4997912f1a72111fa5eb885dfe22482df3f4ad5a9c019e5b04afb81c4b1c3eae3f7503aacfaff784f7f23a13ff5fd2922aaf0e77bff2dcc6971ae5ef58f55ac577d10d6ab56ff839f151209b2fa51fd785618fb9574588ed81830f4ab53a039a32c14a1e14f051c169ee0b0eafd98d7f6347c6d21fd1ffc1ed2573da534a4d1b0c8cf1c72587dffafbe9c85627b30df7b2b68ad8f630c7d7f2449e87b9fc4ffa3ef3d10ffef5b416daaf0b5d5d716fec0db130caec0090536cd61f53f1003e5fa9c18dc42f5c330b8fe6c9a4935e9db42da6227bfaefb0de43e9532d21f391b7881c7f76a10264196c9aa26ecca4d690946269d4128773f16c99386eb8144e40b8018439b11790551153902055ea2663fdeab422233cb0e3800cd8a7020050c41b38e6a0ffae5ac88e442862a42332fec0f9433d79125a425ba24c70b4573b2d2626f791d61e9deed515e774322ef91bcc76ba4f99130f34c016d246107da88c2374273e98ffb7602c3a979c6761abdc8a19a67742427538b1a3b6a49f38ccd25b7182ec678ffd5c3bff63bd27b91910374a11f8563913cbfa1644079826d942f28c3d85244cb0e1a41e28129cae40df49745cd33bfc7b778911fcdcde57228177605d1bc47ce80f2903bbd8ed80277c2118b39798f87c31ffc1f0e5ff3677e5e53f541583346e16bb258af7a396b16e84e0ee5b0ef3d509643d603112322ac377afdf03e0172a6fa9aa74335e0091f58019e54cdab7e944d56d0680eebd5ff1801c1ef7dff83c339e4b0a079e4b0315ff89a0b50bdf740beaf098918f9f41088eabdf766dec88c45f35f8df747e6ccfb1f9a703239ac5f334ce187745802e4cc0487f51fa9be6936318da999504d5fcdffb0bcc7615007d15ef3ab79ef83d4d484aff935ac67853fdf7be16b862f04785ff341bcaf097f8c80cc7ccdffbcfc695eb23e48cd4cf872ef83cc3ccd4cf8f29ff19ee669bcd6d3d474d00254bea505ba934371636067f2a5169de64beee454d4475d94db432ceed44950ee1464f5a3fad53b943b2d60155220a73477f2aa38aacd3ea7e9984b290f94339af43ad2d1dc21e9eebed7e5925e28a5f5012dbabbb7839fc3eea639e48efc98ecc529bb3bec965da48d7877347436c7032692e6a18f3f2665b247ce205ad417292515593e0a0e90208490a70e52ac40210cd5202ba58aec3fa5587fea604576b17e92303ec78657c8b54c762bddddaa6e7882dca92fbde46a240716f8a3c97348183dfac0989412e63547f697b0fa29b0eb8fecdd7fc20b914db7392f40261ab4e840c68b306ea8412cb14d7aa82f7ece5b31b48527bc58b550c33cc1b48277d1c2e54038c3ad6d58704ba48262b663b3d25a690d16f097a59210458c21c4942d4244b13428e91c2658760068be27440bb93eed5ae9bf8e00aa1845193d512962d43f2f9411982deef9a51941e132bb7928eddcfd7a97a500168a64f76e760a159986f386e3ddc2117b8bbea82c042656b64d22348f540a6229cf20bce45492d29424733ad7c4a9e3155869bd7a3dfabfb3877bf94f2499c8ad9e5ee656cfd4864cee06b2c78625348c3e0d65c3c601f08c2040691ecf80a4995640fa804e7d053a54c20f1e4bc6abc210155aa443c20a04812f7d2f2e2a32a514082fd1a73fc42954ade17e377b7bf9e9f0d1dbc66ba7bd96c6451e71be933623df9f136886938684472fdfd4bd9f0af1a4f590835d54ffd6d0fefd8e227de41d7bc98ffdc40b7b68389f72bd3fadf4fd09a57f7669f55d5cb66071519953eea7eed3200273299003abc0f38fa84dbe3f6fdfa121fcf23bf768173cb7344f33d5ecbd6f5fcb3ce21291efbd6f8ff09422af0b04d8887b276dfbbbddedd29f9f2c5f51facc21b973ffbe0264cf8442c29898b46891573ef8401441d0c686c5baf77edf7b7f9cb4fcd7730d6dddb5928142fa90c3a40fcd15cc60aeb5d65a9fbb628bc0435ee7dd6ae55ef0e762c04745cde3ea82bd86a5fe0643f64c2aa496fa4c4c5ab4c82b1f7c208a20686353b93c674deed4af453e44471f9252e4ceec41faf4d76642dea85f8b724861ca9d8aa536538bf53beca5166b95d4932e0bba1916a64c64cf0974932dcd5b30ff205b876cbb06dc2b8041a579f05bd6db6f26b2c785ba903ddee430e943b34dd9af35acfe3e0b9c605ab48f417b05db22b037794f7dfb2e54e59d496a81d2619b17712f7031e0f9d43c76c4f387a25412b875381572c73a90f441923e7e8473c081b43890088236360e2404044e026f368923913bf6534dc224789c4dfdf3de24202bdb5a3b9de40e7d9aef4f02a3e030fbf762ecae1702ead30f529f86488064b3995772672d18c038bfc8f6bbc8f683d0a709914c27c9817d25773613a2e0d65a2640b98588b2fdb63fc14481ddc99b869ab2c5d247faf474923bf6b3ece91a562061f69998b468b922af7cf0812882a08d0d8b85b36dfb6058fb36083c7ad9be0365fbd6eb99a00cae0041d320041843d90801f6c068f0846590400c836fa8d0f0430f5e7e1012bbf80106b10abc7a0116633420c236592a159d40dc01d764a954c484175434d001b3b2542ae2c1865091932745405ac4d8a0c953161867a944d4e5044426d8801605080606930649b863a286126c97304289e8034c4440d8d082b72c958878f0614eff6eab57473a6520696459abc9f293e5947f6c88dcc98084f573b1828c269cbdae7379333a9547af87adfd6ca5ab16f67ce5ad4a2ddeb6b0042379ba2a75f9f43a4ecea7a33e6bce1c8b43591c48a704e1d69aa0a6bc413d29592d55cd274ef73c2a6578d2a3299b1befbe365b43c16d9b7f73f391306987636978423a413940381d9d8e3f59432303261e0600e87e8f36a98c192e9b9b215907fe8694fef0b1e6914a4a185dc9743f980274c515474041c152a58244169dd36ab7f196b0b91893f993c78e7443cafbde5bc311d370e6de5bfc28b7f42479073ef83043972fb490c169e61ea81285cdeeee2d031e316e9c7657f3c8dcb31ebabd79db7bd2f3878c6a356d4e9f41d3c1f1a64079f8903d43648f321b903d3c96e691251a1275df605765e50196d0b0f9dc93710ee540bda039e49ec354c90c85f69caf21dcca5269862a799c41b98416271641997beefdab7fb7c999021d227de66637ba6ddb6fdbc681258077ca56cda0a01fd913812119909e0464cf1087bd181ce571b3d611ab40f9b5825c0a747a9dfb5bc2b7433e51f70d689ed8090d6891c70c2b37067fc63a416207d528d149efe92b5f94116a7532bbbabb7177775f8735a6d9eb6eefeeeef65abcdeddded6058da4694de48efc9512980cd49353d214ea204a29add45b4a995c1aa294e21a9e2f7f8eb329cba7b44b8bf273b4982b78caaf1183e5d734a5d49b524a699d492df250f711e0299b5221f49fe60ddb2f3b245264e645ba6750e041b55aedffbf56abd5fe3f6989e9ffbf56abfdff77d004a189f4b964ba073b3d413575116e72393d54510bcdf0ffff1f4c172e5b269625115ca97708171dfd44ba40ddd3b55aade8a8a8a88b7e740d045dab0975a935d56ab55aad56abd56a35ef367501438518649eaaa8d56ab55aad56ab79d01fe9ec745aed76b9546767f267e77d95e33e15bd57b5f26da3ab5837f32cd9e4a2d39680986a28788f283a2c0578662061f55960fd16f22ad21b8a0cc8f6eb513beccda709824b9ffbf26717f9134b6c224d2bcdc3bdfc7ea6a79b9ac9752fc62ed72ff912153ca548ce225a4fa12cb97f9c4eb2fcc9448230b1cc1bfd92e674c57c856aaadb50ba85baa4165241518193502fa95a28053912d63d0b94123697963a550bf92a945d03f2fd2976affa7a7c4e85aacf87ab64119e3fd4505aeceea1bb86b6e22aea1bb078ae610bf50bf5ea1b81fa1337fc277b0f9617f39e6d06b56a0cb0102247510b5ce00596284f3851c4154e30e960adb5b6061f5cf08219c22ca104083ac9cb096e1212a37ac55092263b34f1a4cb17274e6c5189b842cabdc208870eb2b8e00b185b64698111f5083741e1c15eb1c24cd312375ce1845382c3115a94184af08014461d02045c962748ee155090d45a6b2d81184b0606ebe58a1a85972c668a349822040690c00287114c11a6c9163329a7796e9e33c9262182cad01412601ebae01b9e10a203415d82a3329f3d8284e65176c90e45a61ae571d226d05c32859c8825398fd34af6a67214953d004d8628ba72e5ca0c2cccfc633c4e9072e4055f4039e1441033ff19348b3cc9aa8e58248bad73e0459dd8d6850a76d8c24b91951cb4f8a18bebc08154134a6e70128515485e70e1160890baf9f385807bbbe97dbf79de77537f5f08d86c98fa1b522e05e688b5d31008f75dd87e432e1c63a1c3a6ffcf7dfbc3fd06e4be0d8170bf85f8b3c02c937f8ce8ffd84ffd6cdfbd7cff5e08b09f7a20f653a10864fb2e7c8771e06bfe1b995d30f5f7132067ae90080e2be46cfbf153b65333fbdbd726a6a47207676fc0097247be6784e7c75a942f71748fe01987dc915fbfc0217da8b4b96a0007e562381a70824c862079b6cfb593390c662061dcadc9a59e2c3dc81c09997ad0352a1a19302053ee3db04648a63f45ee7b0399faf07cbfbf03784e4d231407acc608ee4d11ab570f87f4a13e3bd596872cb616ac68e10bfd23a4ca6502b422b43801921034e952a72c81258621ea0b2e822f5c1430d45a6b350192db4215a0285a40e22f7c0ece52e9a8288f1fbb5e0800093cbefccf1175ac064af866a97434248e81b72c958e6a68131c31b9329924b11c1df132c6568778ca5d964a4378916189b5d6da301f688d59d244eb8b314b527004e83b526bad958b0e1c145c8078a116610598302ab8428c099284f1c3064518474a66b8ae219686b052a543681e2ee575aa6f05f290b0fb52ca5b94650f7658d3af3209bb72c9089228e3910273ae091cf852bc2fbf1ed4f3f6f8debb59db63ae902b5117b61ad65a74a28eccfed441c47aa7bf43e6eda6783856ddbece3cae3ccf48f6c231a65219c92a8cf11bc9d88570bf92592564f51cf72bafae563be4abbe7d16a8d330fb1514b9d48b62f35831d63cdc5bee6e7fef9c77daaeed169dfd252592f288979440ca7666bd68cac9f62f879f7e3a2c70819953bf43666ebbb5077f5c0a9c92b2147024a9a3af7a5538aa8c641a8e3ade774672178eb1affbcf48fe7aacc6ef57a191bc0a475927a4be4a0a517d55a55eb5a382ae56ef90d95efbd95a6b3710079939ef9d3766a1aab413cf5a67752ab3ba2783a58023b516a50bc7f2c764217c9db34e20e89c73fad359ebac4dab774e3178fc1ca7230e32b5d6d818f27ee02bc834ba3fdf2bad94d254caa6524f53a930265bede05d1f62caa72395024900755cf04542cd1ee851fb77bbee479c5a4cfdbdf47e4e419939b0befcb617f5177d9a690c31269c20bc796013d162313451327b05f929229d88528299117d2f7c059929303935cde67b2ee6a6eb240a542a0bdccaf2bbf995febf101aa02fa8b46df844b5643403000010042315000028100c87442271402498e8c22a7b14000b778e3e76582c1a089324c7511404418c318618400820001162184254441c0020516c50695ce0c5a843790fb716cc6ea340f88e8727bec67ff44a591a7d4a03d016515bb9882701a944cf604088a9ac816dfba7aebf6559b1951f11acd87833f99578132e8b05894b0396bc3c14fda0d81c251ae4becb11b87d6df5b3b22abcb0321eda997ccf0553aecfa2271e5203fbbb049c307d2ddd77ffdd1754039b8b1d2194851b34e639d94b16d7181c34417a142328fc0043766f574ed165c011b932275e5aa5044c2139dae091585a8e578a2d1336986dcef41d8a343a2909289e66c6fb733806b46ba64a4123c90e2d87cd49fc6e03bb9a0322f89e04b7144b6a4664b0e235b373cc9dbcc5d5e9d5fdb4c8ed074022da12347a37f75f2637d809ff5aaa49c1207e4dc0ff36febd6f733da9323a466254a02390fdc1ab8de241832d2227f81dc8894000d566a6afda11e08d31ac4a34960b097c22b653a056ec1c9659af2a2c92d5f952168d6d5986da6a861728ed695f7c6c98bcca5dc585b20d0518e6d7556507c6ccb4a8aa2567ab12d6b5f718f3e062835b4ed068940d137ff25650e8471bd865f927fa139ddf988d7bf7af073e58f322439d70bf6c7bf81e675361d6008a9efe03c9fedd952df8c548de5ae9927cff1c0ede0e57e01cc7b5c0299081af12e7d99a239d46af049179cd0789f2245cb38d45bf2015786f07f8e1a444d1c025635f92dbaa09a50a1de1fdeaf02ea3ad1850150a7045f6e050e9a2af0c47432503e80f843ffd477f8bce44b7a47a9ec4daced89ff4d5e0b1d45d5fc0c678f68eacf4b061d677700538d4a1cf10b87e09d69bf802f9217aecc4edf23d0ace0ceaaac108501a41eb2d5ddc178b53990499e3bd7b804cf315ddc373618138c396eb1b57f2049a2483ff4f90f0322dd830fd256ff00c58a483c6fca47dc0dd3bae6e76f484a42bfe1f7a8d396f725d1c9a65527795a846f3d77abd5516e4b1c1648c87d555170d21316494f28c21cc8b32eba08697c5b3e01b72b4be9a80c61682a1861b4266e0a026a09947b0c78eed58bd7b674232d34f3ee7f09b099ec69e57efc7cd1c6a0907d1d0b3299df18240ac9ed79905926e52d70c2b08e8326c3dfcc503b8f21d7e589eb1260be009d1fce33223a67972f8dac80c55953eb04bfdd6b53088b008f77a3471df79b12c164b189474cf3efd82833d3db6ed2baccfd79e45b364011f2ad740de9a8407745e2a8d7de6218527b48a82ad9e5979aadf0ad542059db4e4eb07f06d3b5a0f0311f719d8e85d7f1080d21523f8b68c310950d4300d1814d8d7b32127e42d4b5553eac0874ad205acbfd234958e4a54458423f57cba213eea0326806bfd7a5852a4fbbad310b100e89ac16834a27c0019f7406e91b94b91acd056b1e086f0326a5e1fb51a64bbeaa56bafaed72b597588ecc825c9f9f1d18b473276ebdea571e43284a417ffad8d58c788d207cdded3c885732d27e8ba41f2c5a5d03f1765913064f5e9dbd73d28da823dbdf12dc756b1106e6793d369c239adfb7dd33f786536e4189798ec9f9ca3cc61a638e4a5ca04083d6548ef7bb3e1bab03d208abb117d6662c2e16ed826661bd35e3aae3426342d13bb8e7c569d7d719931ec6d173374fb16bcecf9b05870168fe1508c6eda0ae5867260ed3cbeaa26c4815302c657c4915a5af1328cdcde240804e073031cba83425810c6b8013a379c53b30b17e3527a427b35937213b184d4132e9948f5438601f71afc250e2c874f691eafb9a1a88252063870888bc49ccd9e60c85ba3651afc0b875d9e0f69f5910d01be54d96a5bc92d9dbe7c20450de810987c49b1ec4a39c71e988f3981762514d65a19446c0ed02a648abf4b43e7910c4ee677dba24382c020ccfe9f8c90c81c12fb4c9a675a66fe31cda40caf4f4698734d3d8d1041ab89e910b5c700634d049f665aeb12d6fe1c771995dd38518b11a01f0fa0a3ebd4437b8733e2628aff07aca3fa00d3b69c9fbcd178bae094903a04265bbc15b8b67701065c920096c17a21aa166da90b79884decf2d5da173f54201b7919997b71bb9ab38089fdfebe4cf5c88ad97802c2ca559d966c49b1caa2065e3bd18cc909659a7da867a78ab4566445521db5e200f5698186f40151831a1404ba072a1cdf1ba4319e93458fa4db89c9679a2b4bbd895ba381cf36372b3dc9bb3500e6dd863033007b6874ab93f0e2d795e650d0d768e9eb827b654bc44a5cd6b404ad7d646d3ca8917eb37868c8174fff3eb4372d147d9d1ff188e24667fa9ee0968a7366c10e2c4cbef1adc26085982e28947bbb1c907cbf8363e5a3c4a5daddcf20f8e96edd4c5060ce4b4d5102f4dbe2cfdc6b523f559a4b7b4df7592db24bb0fe1038d4813adcaa8b42055120979520b5ca66ce2dfb229f4be442cd08941a1591ad89622a79db854716fe4a94c5faf8e129cf94de03152c15338c9c68a0790bde558927e4fb6f28b26fd68c55a61a1a8906bd6302deedc17b2b996b196186e54a13cd3d7c2c407a9e284ec8dc8add9b6fd1049892eaa0451f25be43898ef8d567697653862cc6bc1628fa5672906520457d2bc90a4df8c8ba0d041aad13904562a33b797b1bf06b63a33bb5d9602cab57fa57b4c9c729ec3691d09be1f09415dad8cf364190135a7898d270a88c1c200671b8e1c767971b892a02dca774301a69c277232c08c7739d1a0ea04b05e44f67a84c08d1900afa40c3b704c9b44bbef45b57eec3fe374b339c86451ce7300f0d58c0c92cfc22850e9c33d3806099e4dea1b02589b7d8a2713799084703ca7f4e5ac476d70fdc7a6979f264d810747871fca9237808392b5bb4ca721f4c99a7a82070c65e7794c4f35bc4bd1aa66fe42e76a6de91544f5e6a63d7d77c9e190d0f5c00c91a5bfe856dd3770c5e8b44cc5a6b145eb348b578e9e2c74c6b9abf4e4c67c473c8e91b57f595d54aa527c8cf69020f5c195dc4f3cb351a8ff6c5aa761bc46aa706c7a7d915363791a760476223edf30a6ce021b82aa8a7c3aa20603d009d37640a3d91e0e26b0c0eac4d5165ac679aa2884cec34761f38dbdd1dd2ec38dc3a31b4fb88812546426777cd3f8d24dd4840b301bd1f0f7945cdc8efbe6fbaf30fcb3f6033def57b0520255f1126e4605fb55d2544a1d3df0f77c64eef6a49dc46d89373c6989547b44d06925186815edd9cfb0197be2352f67b36f602d480af3c620ed5a206320a36b1529ce2d9b5038061bf04e5de27b0eda846f6623c4a221fd76652cd83572c9f2391b4600e92d9e3729e0c005af50d79013f851d7c98144ec7d9733a329861b046ae76e4d228129d8d46fcfeeea1580586971509264008285f2bf81c6dff8e9b3efba76dfd97bfa8fa2400e1b00a77a75e2278d42852a52d3037e8897d437e1afad513ba13b24821b0f604640821f003706f93e6f1936c0802995ec54d54ed25b18e5a9e4877c72b7e3f9b2e61f8041f3cdbdb019f7f89a0e1af3fbbf76140ce2bd03b89e16b36e0d52b1704d020a129bb7339b01ac4398c614162c0be8b55792b8cba87f93bb602ef2ae0f499b9191132c82edaf336c43a81e142947d037e217cbd06c9256fb496e75d4cb0a48cedd2cd3ce05691a418996e99ef4734b59626ff43a8677616ff544f830985dc9ff8e4668a5ccda95efe497fce8b890172f6ed0d254119ae31f69affe67fe8251055c84bcc7c1cefdfc6a007dc9049518ae28c4e559c733f96acb5e5ce305cff268f0e234be81045a5e9ec4530f2ebd060c813ccc892bf88d395782fcbbea4ecf2f9e95f91d090f47b2c9d1ca48058de4fdcf60ce8ade0916b2cfe757f6feb9d2bbd9f2f66990ff89d46eb16f31827665765909e4c5d2dc21d2706cc273b9e69fca00db21273ed7c06effd3a3de8ebe8506f5c5626f40927f7b3061172ea62c176c449fcbd5c9f41e30c4f8818017b9ef54531cf731415c7e875f283e601d9d3234f4fec46018ab325e6e9fb0dd8bd58f741a7f7832f8bdb81eff1b8e30bb2d955ac7c7643ea6d1a99779963d6a4d811629cc1975b20f9af9eac32c67eb9b67ee3e18d48ba7c6d8cf7ffdfeee7fba579caf0ccaa756587dd14e6aa8e49e8d79062aefa82e723228a2aac0da806056f60346e1502c376fbc55e1c7223245e9af5b09312f819981dacfbc1105996e88f27b6d2a260f2b6ab12d9b3386f80d78b0951874f8cf63133dc63bb2ebbcd76fc2e8805d8592c5685ca52edbd920df6a4b53718ed2d791f9005e00d1ef679edcd3ebf1719f808bc9c700ec06ace9efbc31d32fe3d033560c3a88cef583e0e2ca13b574c13fb9b297b9d74effd5a4640313abc947bcb48235e330b49da7720cf9fcccd159616af63ab41f5bd0478a8a602d9bc9345c76a1f2c768d0070bc2df3b9762e401c97e8bb2f1d1c6d19058897f729c16fc1cd33a788d110cef8c3e94e29b425e7496b49b33c89321d1774aed8e672925c966d7903d63a11b64323fd284267a03a1d5fa3925e58e785e51b5fd4f6fdad90eb72e7815b31d30f266b8c2f81da0d6983ab8c9e22618eadae426de7031deda7ca17804ef99e46544911d6a769f6ac7b23e441bc96319da1c8323726704782029267afd29635a373c2f97ff12136392efd0528bd44f635503d2b6a7aa3dcb7de011973d9547c7339209d46e40af776701ef86499c7628ba36146d1e12433b552fe630ac42bbe30669a6e871ef3d47e93023ee883ff5300c865707eec9ee9a041f818cd4188046798c741828b83c06ba351ac3a22b6c5d6e905d38ae6e37c40f0eab05871c69e0fcd2a190eb4311cc53219b458caef1b33cf7a71a8e80fba422cc9bf201ee431f7a0b6e77f411ca25b0cb22cdec8c9042bf84d26ef4de0256ec37278ac8722e894c203f5feb8ca2797a02f9d3845afbc130b3d9ec75f427cdd27f7d0449b07dd2a95f73c79ce9038fb03d2af94f4745114b63693ed184d4443adc1c26e7d5f5dc574b4ad1ef626146834229e5c9ed63185b7ee020c950d1ca715589b0a95cb61a2637b8bda10120a0335f616e821e4c2882b6a2bd8fa25bfa88924d14d7e2292e54eb4f7233ace3da17fa6f32ecdf3bf3a4794438f3b9f832e73723d0d393d5596eb48501bfcd3280e11c7b70a73b08927a43c4f28ae722771f07780a46e16bab5c3c937e23d8b55397514cb3a5cce89459f55b72b4088b91caab967ce8fb007747b3f38a3377c599d1fbfda1d4a7ae65002face8fdaca2b083553e87de3f5896e02805156e0d71ee5944fcc450d3938016013a8d0d709d97fa9948ef9207cf2e620eb3f2bedb7529e3901a75719cc88517f5102c4614d6253d2bd9d7f3ef1a96687742503a0360a71dc85094c76be8cda0590c17f0f5856b6c8e9c04d9816f8fa53b6c8394a6c2933e093dfc453042707d7f22f6aa79bc4e0d6fe2cdb17eef233ebdeb8570bbf30a952a69d90df002c1a0480005cb8a6a12713e7de42142ff5848b3fb7ca268e3e2a3b68876b6393064d894b7326f428824b0042c324be77ff4145704f90978a4a6277695ee7ac18805e2ed13220491286fd1039bf11f3b1610a83beb79f175b014af4ffe380c305f36abd6dad40e8e3339f4190bad78145bff7d0ab424c4e3724da2d3e20512c82eeb3075a5f26fed40f12974b1644aafa62b1e97558fc072255533367b0746defebbaf63cd091f31e5b1f0a80e5c22c4c34e1ad68eb0da9b6e09e9df2e4b0e440f8f10812b210395bc14e20474f06d54d54ad78ff27565432b1ac53dd1d64e3d7e9ab7f50875a3c330e759e76a60d351619070e8a1437715316f07157f0333351c899deb20820ea568cb675b71c669325247b479216461feabc23f2797848c42651ab3f4976d9de701e71d5df03180524ae6cb38a5c900f8924638d9271e1d4aee733217a2220062ef1ac5a6bbc00a09b7bcef4cb41146fd734fe0021f35c9287fadb76cde9ecbf00a4f8639982f1e20e837f12e970b55d1111850b10aa575306cc6c88937e41ec789a0f81da85df593a2c7ee0d7185a37efc0d726ef8a8daaa73a9f1b88eda2da7a2624a503349499692a1686c99990affad100801a63f31e2500cb32ed3edd8d85746625b5d94d4225971d6647cdbe4bd87d8bee7d788c2e16218e7a432810dcd67bc6a09266c149f3522282231aa885afc0dc742dbf2a0a645f40e10621d709016b6dc03aa38218ff47f8449dac78300e3abdc899cc161122be8f82e7d86395571c8db99ddfd8a92c2efcd3c0e9c084420cc8b2aa7d4846800d08002259b3ac371590cf50b80d50abae4dadc004c6fc387daf60d0f73e51a4ebbdebd725381e06c13d8afaad461d4bdac6dfb347e95fed32dd84707d07f32ca6244b938a2df10461f6158af9b71d3a2c966fd4e73dea272950b851a9846c02d36ca02d0b4c621e92e8d7300e05c198977b57e34aa1f504a2cca1f7609b776c55828c90c35a604d1f1af9e1e7815fd1dc88852026cd817d3d94a7aae6155ff30f6903703fc50f9ff9eb9f32fe84aae19c40179be7c0e0d927fcceee2312394cab7500696461cd20543469c71922e4c15f3a2840d5407946cb9c13cca6667b9bba34ef5563a2fd26b26adcee9eaad40c506deb068c87c28a25ce4ac7193fb1981c7e03c56ccd12bf8caafe96371002092766ba901f13c105ddae20b71281e304e62c78ef5ff360869eb60b84e979baf76ded16721e89990fef9757c959cf270fdc9007a92d0fe5860f744b21dede2b55f9f9facbed30acb4f27b4623add5a6a1bd44b4074fd531a3e8219129ecc41d81e185dc3b0467fb2b3193cbf141a8f30baccbd51f155ca065344ce171ace7d4a80585a11a6c073a9e3282a338de6fd909d7a9acd2c9685806d11f6b7a7ec843690d08de4758ca4c016eba7dc9475158dd36f00990189bd5b07a54f0256b2b59f1f41a361447127df7aa29af93fbbb1ca757b2b221c4ff0b4c464656ba560250a491a1fddf9344815e6eb19820539aeb0d3aa57ad05424a6be231b5624c67f5a3d28fc18442dd08512d909714dc8874a1c284a204882677679e184128b933d9e96c3a196bc83fc902ed313405ad257de8eaf0c1ef734314afa34d22b70319cdc419cebc698275ad5a7e0f7d0e3ea6634d65714e8183f24c12e8eed247cc5246843acba50287daac8dca394037d93e7e675890364fade86dcf4d2f603b7ed025608e698181ade8fe74a183a460f203a03301d1b6d687b5cd9af18f1266299f29bbebd3d393958679948eff17f5d158dd1457c15827d625e69fb0d0994f255bec67998c9ed902f3154110514b6f2984cf68f5cb7832b6808238148056ba51412fb86afb38d65140d111bf3e1a0d6b5febb1cf09dee7ff98f06b893b65b05a6871d696b1708848668c8621a0858a0aea2316c3a20e1e9346114f45f2c7ab3ba6529b167b7dcbb1f478316de045103127f2830ecd763d92e2372dc5be9d1daba5e0b5dbec18763e8256adcd88c32cf1623a6f54e8bcb99bad1a52ad75c164fdde84c114f6257022b386c038193941422130a3cfdfeb95c416c53b25ebe54777de417e7a79865af4a207329b3f84f366d0a115a3919490b917ef14941df2c6f27cd04f0aa8106e628e92c76b386abf9a35580c325f239808d13633d2359c95f935634f16862110ff3dbb64eda2e78e8b3408b41df9a142ab8e066798910c42a2e2c86f778a7c4c39fe0536876d398fb901f780516ee6f9ef91c001a22391747873aa6dd654e70d66690494da4be7e5a218b911f7c3464ffff6ab5c1ab906105991e9bedc147b01a25e2358f3e3eaa9f7e2cc1d9d06870495f6b00a236a19e17dc205baece553bc2f94b87286bdb113a4d907e11fb802b7fe0dc3748c6ec3ad468e4b3ed691b59714e2f8d78834f7b986706e245d9c5cb9c345d7bb1f6a1137232585459feff0a33b540bbe0f55bb801b70a63ee3423c03db16171fb19821ea326dfe2b6b058de401699069f75d3126a97a81111305bd5b336deb48c95a6c1d60130176523c1a6c1cc658912e047c54912e0da4c0514ba39f9e0cc0d06cb97dd6c367b37d6e4b20fd160b7ab8bf4bdd8fd4ac9920bc6742307d7814a0d64e44f9b0270d492111e8cfc43606b7f509b96ef2e9b1afea5e5b380a4cdbac54b35c5d77e0a53649601884b4e9dcc7265c8e498f944bd43268364c9c57306939b6bc52edec8054d740555c151c6b366a55014353356c4151efe858e441b2afb29f095e4aeeca0a9ac9c91e50b5616c75e515fb0bf67b528f3938124b02d10729b82cb0f88bec6836f3e3548a278eaaf5e6adce52bb018bc2404dcdbe27e4ffbebb16e9d44cbeeddea399ee4662674a28a2ed795576d3dcabc62044de2618792823058d130f0f1509dba8dc43108731970f7dbd05fa5ff17b93b6ce01e5141f63c36b34d11cc29939564804b9cf98fa249f296e16bbef9e1a791500b98c12d3713c4ac9a4cc2366519616a1fa155cd1146e33d8d35c111406c1e900cdfeda1e2bda1602fdb3479910d1254cce02482bdac45fe2188feecdf23492ab460f7765237debf4fa8f7440004671866716f439c3939aee079ae57790828fa349bb9d263a6fc53de1aa65f7c8343b6522fc59a19dea1efc54fd92e99485c3eaa8bc392d5adbe0d2c742cc64d08d007bdd0274dfbd7e4e16846dd8fbc017e984929526eda1fe97c23e71d8381d45648df37bf40dc991d9685a022fcb2430f73a8d1b755e18dc7a335c21f25552c127023ff180623d74fb8dd498c7c0c53986b17384ca4e0b1a67b96217030aa7a24d6c39dc41cb2103491bc06e0cbf638e0967e4d8961d5c5371bf354eb9a6a7529db08ae3ecd5bf60a94215cac55d92a107263f92e2aa3d2f82b120e70737471560f7b132f5d6195b82ceb476ec9a9f86afcdde12742402a5c9b227e582e934e83f8e04cc31d5072c195b491a00ec454738a82b297e36e1b334539516d6f86356708a0ad91bd17a3e2861af30ca145f18c7c98b646ad40de9478993f9918c256e14240ec424ff2200c2c2e9ee77c057e3cedbc9139e2b92032df6e4764cfc00cb65aae672077e2949452beb6341536ef657f79690eaac86aa564a4ee53279d7d0d1bed754bcbc7a245f24a7af98b0d3441b6a1916bf360ebf583cb3968289a564e3bee2055ff209388f81058b852e52243c1c49d7bb9553d1a7b649ce25e6c4a9c335932c39808f37026ea32410be16d7f918bf9d65221268f8996c39bfde4374b02342db7ac237feef2f9ce33994f69f633361f40cd70a9e6550e6c6c6336706df0950c26c55525a9f44bab34ea4a8afd1217c9e9d685badc00a47e5b009e6b15417f3c4591815b1640ce00f2774ba163c94449cef009505adc4ba48b53c434ba1431f1f818916286484b6ccdeb4f76c9259676a760d0f2e9c4695fecdbcd208b0e41e48ed125ea7a28d9be7c7e9565f5e8c921f2bce7c1fd8b03702d2831c039864015159fe7ea8654fde8f5fbce48815bf8955762b616da05f0e0887f2bb9f543611ccf635d94c3a751da38183f71316d079c35b84d7a9e628466c05a04bdc88d3979fdc3fd67c08e578541e6bade0e3161a7a8670a9d294c18f38dff319506a02f9bc8ad80b10d6cce1fb2e68f083b03d83cc02ed8640bd439808fa0e902e9b1bdb51c9db84a41534b246d4e5c3ca981264787129b1164a8d6456cd57d4187f155311b785b7f2de477dd1147a6c114e445221a6ce380101048e1501d44f5898136505f6841ce181730c0e68bda980f9804f9de6be6d8c04235a470124c35c3275b6b78017d61b3f6b8d436161f09091c9ae7db61feec1a27c02bbef8b141dfb41c8e1e268841a225e1fdc6237f6fc61461c18aa4161d06920ffe6744c87bb25d75a6308343d378e13eb44ee71f45f81b16a12a7f16725efc8cb585028e814547180d708f0a2c98d102e0f5a93cd99ac08949ecfc28accfe672a8b9dc0b44fcbad8fd1d461c76a4785d57f3b04ad5f994c4f8a3f60829484df216ec4399961eedee8111bb59b041e9232cb2e5993bd508f2dd3f02caedecf428c0189316ba87f4fa5819c0a9da6a8396dcf9874c9bc4455af3a5263274cea2697faf1f10784c703a3a8079ccc0f26eba12497080e1e73aeb9a8f995a63cfe5a93ead4e1d7ed9921c0c21b78af7ed9d9d516ac8d00726dc9d5c7922829ff2654d64ac3df22dc3e9f27f78b38f3f6a80d63e1ddfeeb59c42768c893168edc5435f85e6a135ca7173c5eb892df138fe7099c7c8829fe128ef49ab75ebc582d3fec530e87b688670d2b3f51a59b437289555cd744da04f10a7100f8080d8d13915d5029a6771b63da6340bc2c2b6cf1e7d87b1df146480e60fd6c53be531c02d7f31a4c50820e5319c61b4931c81b57f89064d8a1f5351c85386f431286892426ea9e95c099215f68c23817d38315e580e39936975036569855f2024f0f339652b4c51e85c75740ae3d9fcb882437dcec022489ba24b4e6993678fd4c85cb6493e69b4ac192fd4f77afdd3e09c51749d9b93a75d90c5ece0c6bd9eaa3a37a5612c74ba301f3eff94add6417bd2204c220f90dfd1c927d606604f87ca2dea73556214ddea13d917414d96099315a11c3d302b2b70961aa432ee50dc165bbba49cf4481907e71258e3e5a0734d19c2f008206abe48c8ba99067c19fadaae9a0899c96abd2ba3ab69162c91a215d5da5881e614f751c69a74c5d8ec0541a44321a03bf1bd64ee391915cd5038d361e37ab2792ac553080363067d2aca54e2722fc544d63423332d8bf308cc08e3d08c33f425ce8e346d51f7e7d94c3cc1d1b7fa20bc3b0b457824f69f89454ab1e1384c73e0e64fd7801919cef697dd5c24df6385aa71062d7e3a04952949e71af3804bd0b3d400cd8cf99a4d4460f3a05c4942d15477e4143f86439ee2be46286be9d90229b1d7881036e0aa80270a7e47164d5b8fea73ffa9d7657d06ce2b364414c833e08604ae54943f49c1c4f7c89eb0bad3a643f4a7bbbd4814726d02b7bc29f6731ed9445a2060d0938178914b0beae8f7e099838356247ea574a3f9f003858424155aa5ec5f318ac715bd218531d2626e1eae94eb99eeb1660916db862ed52f8fbcb132f1c00e3c1376729e78b6e1d22f022392c56cb67974b5fb413a1cb33ca5dad651c35be0ac24951fbaf1c6a8b0e93eb9714428b36af1a3c5f6995523ff86ca18ada0e1b6cff976b0cbbdae3162c284d0474c76219d3142c86f1a0f83351ba91fc487d863d747aaf3b0da2d72cec230043712e2913822629c2d91c62a5338c173fd7228d4769866bf789bc1e85307bc81fa78b8c26c24921b415ef83f2ab4875353c289c387a9e613003ceca0ce96edc0430c8f7db88a81a27067ae909a2b2cd2c8b292459df237653802698c8097552afb80ca8ce6c782812248c4bba259aed93063395176eba28cc17072f5e74c42e4ea9081ee104b169026353b9b976470f4c72e6fddd8f1e26616b5677e805d3d2ac71d51f895544efb86f0793ff0c8b699ea4626fbb2336039e2fc34ca11fbf1aea1ff22461fd60bf7d86bcd6f8f9ae30059de7583b8205e240f05a6f43b61d1a23545c3d531801e60f5c0794345072ea829a7f5084003caa1404b90c9ed84876b9c9a7499308a3e0241a4715f779a33cd5f302756b22df7357fb1a26bab7bf02d2aeea1db427a784fe1a9d878806a85b1fa13fb44416ebdd04746a4245ac7b06423ad9b21e193d5bb0ebd73db5fe8db522014398110173f5bb90341e0d9d38ac4f0990d553be5215d6da88cc15fb5145562bd38466a40dc320aa757493a26e4bd8cc12c5200f4c601ec99795c9210c916bb8941452bfca5eab46cfa7f1f174f4044db3d25c38b3349c5675e004c4d29afccbaf0a363e9610678b55dff82947071db794d94d186aedb80f6ae695c2194f3c7cae24d635670899d996977f0b4e3d4ef54190f2e9eabecb611d8e7836551dbc35009415cbba97e50b5a864094a55b5f20bb49b14d7e15f64cdb496bd219f8267b205aa024299fc8a5df6a7f3f7e94a12bb25854e64b6e281aa943854dc3c31a41a6450c3e1207acf8af9259f792fa99350769d58b6df6dbd1522e7596b6677b66a3f76be6f030e06acecf5f68d8a40c61d20a2bb01c22d8006f467573cc7b600f9bf859aadcf1efbaf2efe1590c3541f316428d05647046be359a52d6328fecd3f17c93f8f6847b2314e552497b4d47dfa1d5c91966a806d1636ae7df95be10bb5dc3d0c5b3aa1157770cd26017947593dd2910fbee83996c68d2ae693c959c7a732c8f3d5e9298123b66665c4cc6e24cf14022179dca80aa8292124653762758480566c448f9079840b2df1d0b4a2b50f644264db1291326804cd95baa7bd0cfa24e649777d366b895cafd16a72b0208812025fa3752c8e5bc6818f5c8ebda7fdd367d7bcf3558e3c7e05de9430f1e5672e579c507279ade88a4b255c3e593f410732bdacd11f0d6ee8464b71c0023ef79ea2a7a406dd5e1be32e97c074e302f73b5d99661b0c36efeb5c721d8297b8b55f92ffe75640d633ccd3a44a2c29f639a6361e462db691cf6b32444d6fbb6c88dc21d63b8db2cda2f012b7b46bbe2b253f4a63e8d0f5c2f637fad48f901167ef8aed89c56b4e7bd5f515c605b809ff0f41fa93b1eb003b85f02ae24deb775aff054ae1a8ef034aded829c7a1f7ccb718d58ff522da9f9393dbd1f130bb1dd5cdb6ee61ac3822826f9f61a678d6f207f3edf9538b5b9014848674b5071b07307b715b5b6f93c871ff56b835457f63366df690e1f806609675b9b6db0af3571b0f04420ca7bff53cb0fb062a0c609fc715ebcad55fcd9497e4e898693c690d7153509f252df84b7e9df9533c8d022876f93c39e017aaf07be45c268241c50b613a830b550dd77e7c9180ee43bdaa5916cd223c1566e96044b96e8053556bcdbe2a5c3b903eea33055eb817d7c36aae87d795f168247f29f19c195617b2e40cd6179ec4bbc577f86cd74d46fc1dea01a4f31f0cf07a7851a87fe99d472bf65330d376ea7cd3a0fe1071380d19a87dfe20e16e7399a10ef84f1560b71b9edc08d4e2d22a6f78a89e383b1edec4140b84191317eeb39055ebe31782fec44a591466b45583cec81642b3a89a6c32881be04c743bd5483b7d9757e0cbc73c3eea1d1155f35bef6af62a5a15b48e58589cf7502dc6432c0da755dbb264a596e9ff0ea95e14e0b0562b6367b27442107acd36a6b2ada732efbb8346349a3cc2b148f8e5894cddd2dd0fa54087e800e8bcb067228008870660f41e9614fc98335cbaf334c659e3fc72b442c32f9f9e23840b860f11fa5bdc2bc99beeecb108f33be8fc359cfb807a56b5b661fa08b1cfd2ad182b3eb3d0f32cf68dc0ba23bff5e92fbcb72b133c1497be6a76886014ef1fdd09dcdd1d58094a7ca599d2c0fd99e351d9695d3dc6462e97fce7b823665d7c94140b893c51efc054de99e2fbd28fdd14b80515a91c98254199a9724b94c61a4d07c3cb19c8ebabe8e3d1fe79488b4652dfe9c0a491f67094c6b0602ac27d3e8e59a6bae0f71147c1d6da1bdbf6cb046d5f7d33d07d225e9cde1d3c43c479ec8428192e2563bee28ff769ab87badaea5a9cb2478527cca698fae8bbf59ac49fb63b13e712d1bc2591f2ab0abdaa9d3e11d44521deb07cd5705d453de9339dc37128e7ed3a52a8e8f8ae270ff56b3e9aa27b596aad85546b9f393cd7e05cf82d89f46848855f0d46a375b08c35fa8b37023f32bb39202bc853543d2b26be7e00dc077438d16d4adb5ca5d8cc13ebc0edabedeaab6fb9229a9cb80e6a4ca9af6df21142f93cd4773821d122a916530cc504f05a0e789dc6b5c4d45c0c3c587944c4178e3685b2c496e4f523d2499a8ae5741501b48646f4bc853c7d6ea48356c717311763a3b16e1e8119f78e78835681cfcc4a1d88308e58659ced19cdcaf467c6383ed4f522cd1292b6d920e4a3e8890df8d0a0beb3729cb54ff02580747172ed7e1a816d35123e32e336c26cb58e608705a78c6346d54f105dd93b8849f1a13da4733d8a46a65b54d2af3399994de7fdd3a6f05446c6a8ddc0e9908c15dc15cacfe11061ac56d0501bd066dd121a212dbc5dc880b641977c325fdfc57cc020f8ca1555e45de688208b5d3533425e706c52bed8dc4a47d5dd7c4bdab9348f25d3cc78f00d0dd9dcc4eba2c731b3f2a7f822858930eb8f8a5b24c8ae664f3b736fa65a9d7cb44e9d7f287492555acfd877544a9b8e83dfd1c2c53a7eb1f48fdb7adf90333a803b247492f5212cb69552b7c4e185fe58c45a12eeb2975309a4cebbad6fdcbe1b25d57aefe23ac25cf7464591e8abb495b4a31bb4ba73d06e290a70055cbda55a452ad41dd15add8b1ce9f8b1158bbf5a5d183bd5280393ccd0e3a42d1779d5113d01cb1539dad3aa061b9e30093babc42a2127fe57d97063f7798610b747f02c495b55f1f4a03a38c706466cdaf789eebf82cca093071bea8499e16fa2c9b57c964415419fa240267b33a50031f358b83de58360e209e0bfb43fc69979a47a799f8a5ba91165129a9f4f59e2d30d61fd6787640ae1cead847455f7d5bc35eb7f29ca7441ad3964ea300e5ef3ebba2bac00ad87d1886c8cc178bf4a13493b29332a54fd01f60405551afc63ec6f9fdff2b4a75c95d80d8d12210192192c6ba568b2d93069b8a8956c10b867d5e442e179426a2b88b213bba8cc037687aa499e0aece384522a29388718309f40d5f82942e2a0f337296f3328f75551eb46ae97225411cba07b7e4892d3331b9a570f154ee6cd99293d4e7d8525187915f47af3c6b395a6270a009b0b258fe00cb0dad6f668d1b71a335a551ec3530321c3ab8a04085574af56c120880866cb76a6587993ef5350a9642e810d2f4f32cd927575943b6834ea1785424080958a12a918d49d2a6be5c1a84d987a5fb3aaf54d8c51aa439b29aeb7d901c9d0107f42bfd1833a87713aaa22570a4f5f96103419c0771f667c33e5c4d444c38406c5562f7b2c145ef83ed86bf8913f5f070f3e0d047c5fd9106249524af2ce9f384926023a33fdf978a4c6df9a03533b524166c5c83334bf89bf4937ff87a76981b750070583c7919014c5835284a422a9cb317cc2fd629746718f3726df44b1c9198542d805a578327e6f1fd548ec43063922866ab83303c1ff9275319f10729824c1fc63a4a9d9b0c89040c238079202a7d0ffb2b69250761152e280c439a2248a70ea2cf3a5a03139a7509e1e3f7f59b602039686a616b4f5f9205e13084a0715fc243b0b8f36239c78bcb3f5fa7867569fc3fdf250e8246dfd16d9601105480788348641dbda7250e6dd392e8bace96ae0ec21c21ed4497eea5da02408dc13a9481db8e0f166cf55f9f01bdea053be32c85d1fa2fe860ee6aa0b9eebb58d8812cff4b624c8a3c1dd43d41e7dae38e94ff6df8298a7fc060102b0fc378415461f26c79721b8faa27f2bffc059f603501228034c3430c865ca3def301c5462462777aaaecb279418d7a7b7d688cc9574cc3ca50094ce3e4393100c51ba8d026eb1888cc988c6d6cf36d056b4e181798d92b630d43b25cf3f02195aa0d4883828a997c9616601e8cceecbff89af12881a496fce5ca5c218c76cd7511add428eba094f587f0fd928198e559c1583e3cff159b1f98af8d2851ee7191ed3bcbabf45348451c26d16910c2932fed577670a07b4308c53ea14ccda7d415d5498c8ace6a6460bad15ac5dcf1967edd2f14610e57630a089742f8a9b13e549ede8ec60dcc72d5360c1dcb25d3ebca23e3467b8830cb08ebfdc9d125fa27c2e588d08f20d2a06632d94224626f56c524e9368bac3a26b52c9a6855d47d72bf04ec0044f52131a431468949f7251b3589fc7549a89b931e9df73ca42375802ee658763eeb567e0698187ebe5f7c15843b7ef6c2f9b375341ef43cf77afd621ba74be86555d996fe834d8c11c1c753d0789ba37669a0996e68b59e35ddf31edf2b04c571cf2433c5727a0b32b1d159f77c60e29e556bb81bdfa3b6b3346f88a0d55a532364d5fecba74da018e715d91c9128acae6558a70269fc85e1d904b20b0aef87fb5b1566f1123053c9dbe3db0d4933a239d53170007ee0119558b90cf63d961f5c15c6992ef22c95b022134317106c7d0c56a1036ac8e167b40d2a44a4c62cdadd5d3c77b74a513b7401f98c682b8b020215be21cd5111ef0ae699946c75f7bd5577ae152761ad8ba03fc9f2f097e265a379b954e2fca6575a2e60391e2e84fd160447724e743ba2571b85126a1c76fe514a1c3de9a781b2da681954eb25662ac8715f2e5069ee3752e78185ef019f9cdd6da7c7dc8a6723ec04ff8f543f441d2661d9a808dd914b8cf7530e1d6de2d74c6a9de31cc5036250388ee29851f0e85112066f01a04fe1ee405592049484b0793105390ab6a5dc7168210d89269914eca8fbfea500c077f28a69ce1a366a38fad2093d0cb276f721ffe4c59c1ba6126fa8ee071af515b3de22bc96b8dcf6a4ade847a01d7e4135292718a2d2e0add0384d7a79d3e56cfd25428d885e55ffae1f5556d0ee2f5587346c220e8003bf1a7916dc3ca915a4c8e71b2bc18b5edb5e934e8625bd7c7c29189d842c36baca0a7c06b4c1ba8f1a3579306f4caf559f016df8a5c5ae96a902a7c040f91e3c5fb9848df1702bf09e365c663061b02fd199f3d162d24164533affc4c8f6f8bd8bdf3b6c901e2178e43a27301a1c5902a1eaf1fb4f85e50cb5a2c85b4c02d20a4a5bb47b118072765f140e32963e40f5557b9222666ce8641b8bba8e5c592efdf3d38c801c5aaeba71183ee74c05d48de3da28b21bf531eac910008b6a7c3baba9e60560f036c32c16e6bfd7deb9d16896f5369d2579dea22aa2186a3869be52b3f636f34ce97c0de6d9601f8bc195c59288260b8228852f382aee59d7b668a1b763ddc3134f99113ba7afaeeefaf674025afb1db183dce84c915782a9228ca94834e4cc1f265b5fedb66504aa60d1cd82d548a830e8dea02aca52ec47fed7cd5d296e693c9f7ecfc67a80cb721aa7419db0ecdc2c08d6ec1401428972bdccc593043a5aac9bf824b70ca074d7f63aacf9ba6e72c9a248f78753b509971f0aaf4e79f386e7c1811e8699d46217d87a31d101becc98f8f8c757e510dcf7826f05b43f969dc401b814051845c36cff8c00fd1331ef31efb95bfa324c0cca391282cf75258585e3346cd91b21fe5da95ab2caf814a6784396b7469f4acbcb3603d5af5bcfb15c84f3bf4c6754d404feae786d2f176fe7783effc714a0f7910cba6f00bf6bc4ca2da359ed3734c5f28aa257b84e3a0776281ce2d7902a65c96457c311c89041abf41fc61ecf7647f0712bed908daa0021b0700b1b0c68bd720bef443c118f0baf120fe7c5fa213b5d23c017cff4692b9dd95df857295628c29cdad5c08cba7ce5ba53514834d74a4b660f9f1fbccca1e83e10e89ec0578c0959668fb436e51189053dc331d42a1607c679e4090c6017192103872cfb02f300d47343d7ea10117fea905532dd6b61630d189c501325e06b64d53602cad5aa31e1fbc82c2c8682fa16dc798b0c73216b402a849b564a524e27e620e0855102f3b91be8434700030511c12e84a21d75dd77f95f2f05e1de40da2578d4875106cc53a0864cd573bdac96f65b36950b00ab1cb04bb2e6edeb1f6c4ba90ee0cf09bc347dd955379566a24c742f3ce626edf6fb38ef185be73ece603ca1d3466676a39d692c74f04436c495d269b866c09281724f914bfcf6d0d5be93159e8abc18a72206403c0b50e5f271d7d66a047584f1039157516e222941ec506620025c6981f4a6585424d87751a7eb762dce3a2ea1207608e229a3c72abdfeb845a2660b28ad6393c294f5bdd3ba0527022580a06847c71c7d98f16454a88799e90a43006d69ddcbafdc6a5eca74524bff1002a2ca910682fcf5d991e3173ef3fe1fdbbdde64fe6b2a70ceacfccfde4aae81e146ae88426669d7a754ef29fb52315b25c48e2d84d65d9bc3109bf3c852af1a87189891c9e3260989ce8920dec1fcc835c959d088fa1b5483dec5a5fb397cd273d77942f7389a656c00fec3284ca0fddedb3ad337e8476c8b0eddcef591187079ba00a7a14e1405ffce9f50580e6aae652b7f12c1fc4d514c239bf2a95559663cc0fc85be6c58b023bac754404064b778205e52d894c70d6b31377ad734e5081d885060aca23e4dccd3403c9d535b8557279fed82c690aa12818c3edbb64742ad8f092b6046be6148ccaf2e0363c7d85330eee4afa7237802a6820221863e51e3fd6f9f5c3989db6ebebad5017e3490e1b5a9cba19b40dc91b49f9e914a4ee728e6a3213f33e1c94be3eaa61781603e32ba41ad8a4c5afd5cb9a9f3579e4eccb60e4f8a7739b1432a81a63fc3b5258f0e791d65c7cc97c94bbece688256fe0bfcca66596bc717d75338d89925da126fc4b43e28b0c02d6999af0c197f7ad3d15c4738aba0092ae1952f60891813147fc45c8589f74aecbba9a0827c72bf14fc06253b1971f276c75eb4c1536cc634eb220177d8035cca3cab29519b34cb2d55100f7498bc1f79da8533100e7cb7e9a87b35b5099955e5b47300d6a5fbc880aa020b7096a66bfd34ec19ac5467fdb43552b501ff4a4de5548a9385aa40b3edbcddd0c6fe3702c6137d40cc2dc28974692ea53048e60ba53e777e7ef3f4ab010c57b996431925994d498b6d43c15960162296a1ccd0a4499506644b98dec60f25e7e3ddf029ff067f1b499d4537ee4627537e3b3adc02a3f46cb0588341f116ab5d428cfcd860aa4c1e0d8ab668e0e01471d9597e7e0c7674e242dff7bf02a161d79303d0267d6ffe279dae4e8d0a9be9d9e6c057c8678b79b8afa40e9ea24387e2a217f6c2c4a1e94933567a43242d3a9856c3eb5a175d445abc9a8d09a7b5c586b22d8d0f535a88b6c64b46b4c2f7206ae474fe843962056d41c58a70fa86dbc83c875a6ec6f9cd9a879084ed19a3999aff795a996c9c084b75b33e88f5f374fa92e1e03193c6e45a02ffac065d5ae1bd7d62232c4dca862914b2aeb724f14f498f11c528cdb68b16bc46482c5e5b3d13634d3ec8e67c3e552b4664fbc3cfc123082dab5e560c1aff0aebcb9f4b6ec8dcf5b1b231baa59c32b5578ccc54b109e43e7160ea7207805dd334c63cf758e04362c69d093c1b68c6ac2de3bd707cfcd8da8066765ea5bdea941139c0db092dfffea54808cef8a232694177ab17d7f5c97289389347b83cb33dc72ef157b298766295c4111f8ff25f8f1b38ba00af6074eaee86b17e192db2932c3165eddaa1ee46d355232ee844456497ba8e103a5f6e611e7fe72f900798b31747a88eb6800d09bbf09d34bad628dc7a1b00e1f81ad874619b5f1a8852aa0aef05fbb5b5990e672ab6332d7a0fa638c14a0a0ee024549459dad99a125d15af537c977802974e4944d1000633b8fb48cff0945154ee69047c5c1050e0aac8a581f89c3447aa458d114fb42239cfbd132abdffb87b08e1e15f3354e4944f12f4e85037394897ca3d0a512183ffca8cbf1092e98d3abcff28f118a3a72a0c4f6ee83db674a0647e513684846c39db1c6d4f061eb3b6a147be006d31a3eae30bd86082595a0f136d6993e0cc071c81c9b9112528383107648ce3aaddb6e7be075ba1f4012c601a514368008dd85b2ea506d8719b805112227ff171b2a6c4aeb290d42437939b2442592cdfe67a48ea5227388026a0b21283a8f870281d2fe97973738efb8c499aaf3ded77fb2819a82efade8eba95330e93e2405fb3b11cb012bbbe3a2197220eb55d9de7d63af674dbc1064eb63ea6facc21cb214a1fdf9d8e96f05c7946005ff67294a825168fc654335ba99bb381fcc4216b76da9ecf19989e7097df11dd7e6a4915717d7f8b2d924c35506e02318ee603c74061c9fcc813ca58c1af09d21c4ba754f020bc4609b728edcf2d7c2e4a3248dd5405719c9e98df80bbb63c40c8e8bb99c233688a6dda36e873189e228c9574d37099dbe2dbb8f318b86af32aae615e67e46d290b256fd26a398d4fca71691f8ca390b9c7af221f079b2e02e1072bea6b05be4bcf656704ff2a980bef27498ebccfe51e289a86fb44a365419be647ebd08c180e2d256aadab69c3080a499f9030dd9b96f0779117d964c0c8d0936c88f132cfeee15365592e46daa6f1f5c884264af51f876c169010a69dae6fbc3382f048de9bf61338b73f4ab5f02d2ca32f84f91c45a7ef25a34a9d4daf98d36a26a1592cfc366999369f75d1fda2262695fff49a071a14afa7bbbe72385e9be60a9b21df9e09f435e88c09b615185c564375cb2d1433dda29d0a9cd5eca59376dbe4ee6b4f467e2296a18aa4f81ac02213296990b283a1d36c579e8dfb8f846e4330d139c598fd94cfa777f3081f2bfca1cda029e63da1b946f213f9da70cc6e29afe479444673a73ba945a1802fd87f7f299165abd1499c79da2684f26c901677ea1e453cf7a39e774b128905eee31cc996dbcc17c040c9359a9f1bac8dd063436c783b225e722f0312bf93730a1f5cfe16de425ed030b6065e85f8fdd6d7734ec1981d52205fffe8daf6d5224b8137bc05981ea3109d4fca1be0c810e99307cfb8a4e131f1ab357b785ae0425e8868d71724b79f5a71cfe61b8cfdd9327de5a1431b1bc77fdc804339982b96e026d4ce8ad1f53cc98001121e2f5225b4fb84a22469deab4fd2780d8e76b3703d467a465634aa08f79f01e34000bcf3f011b009c28b42c75a45111ca824441c147a89a7cdde1b242cac3e6443f8c2dbe8a20151f4962d6f62364420d65fff06d5f034d2da4b2746818b286b09ea7bed83fc13b256318e407c81384a7063382ca1f8bf6bc4e48517fe08c6297b06d0cbd6019414690a29fd30cd9c4342854e4b67f347c861beedf4b02b3d424cc0ac0a4b78410952cf11b4f2166f90f053cbf38fd104af7161ee364fefd5211423bbab5449cc5e7f035f2a2b1bbb313e110d0ee4d1b6b58d44c1c3de90c0fd0beb54ad1dc064b6b787c8ecce420137a3ed3417039de9765813397e3cd8bdb4d0ff0a2b7d9a6ca0031f1950fd96f9012f35f4973175c656443217d9fc8dabcbb458d9f112ec68a7987a40ca812b17fc5bd67f6ca70d89df2fd88d6cbe9f44f2f924f355860541ae6f936a94a6fc65b636c0516b10c5029573b44d9a529926d1ee84abaf68daacb8d4af046de4cb1747fb7a8b9d23052118b534a81f5bf6078bac85b7666cdd3e3f5294c198a43cf0e9edc3279d288bbafca0caa7ba44c50d432ffa00869c0d3e1a859c73bef7c9142888e02a6188e5aef0f762b117949480a72aef49fd642f2a7a2bcccd4a20a0080b249aa1608392fdcb81c6249d32d21e83c2d04c65f5542243e052756976bf7243471efcbace1a614f4ea7ed4de64a7ccf7a5900212a51db03b34be2f41b563ddd989fd079a732d01569dbf802727ab65aec2fcac2df7a1b7cfd05749930a77644d4d55c136fe5aa47a2dee5d9004a373c0136c9e2385c666fd415dc1a4e4d3f0061d313db364f6294f60f2f0ee974bb3662bf87ed336634c4531f8b3d051a9fce19790eb924b833ebe8c045ad312ee7825df5b41d45516708d2f9ed2831f2ca1739cdccffa17a318e66b94011c975c1721d8b63593596450b23d0e9b6cfcbb9ea1c1285abb561b1cfbb96417c8b4f922f6ab7f2b0ebd44b9c3ea59178d252b384387f44bbe22e05b00ca713dc5e67367f98fb8cea641c7e803d7493737022d1ac1da6b41f48839342eabb19687401211ce6f77d45871e878f9d2c18b1b837d4611640dba82e3fcb827ea1bbc432541546c2bda81a01879bb2fcff748711895c0ce8d47338c150faec01c85fd8f7b8707f37707474b9fc378479f3f5b93e593102acce44de8a84fb2c62c350716904f1ebe596ff6874ce5cc800a16f6af99dde74494af689128fa7b4b0ea2d2b986504cdc4f64ee8a74462ac3b51824b59751aa3191bbb0e70af3a16a865604df4356e8b3157aa03b55f192b45f72d95fc552c96e033a827c6f5937da44d6b16c78c5d43824603f495ce7d309ac420e9061dab768eafa78c0f7bc4cf3e57ef391bfdbf7e74aaeabaa204e8152e0061a6c2a71be0e8aefa0fc6aade9e994bb5439b7ba8de53456d61a84d1d59c867e50ea969df1cebd011d801e9a3139ed3bc2d0524f99c2234461e22545a408d539650b381defaa4f9a35106aaf89c038acce3623a0e9d0eeb3d3471246e03720a60b6c748f3368de922502319eb716bd260d74cd03a364c97f76bcb97c70018dfe9e26f6982703eb3e3ce831deb0a2dacc92b5d8576e358da850fb49e084a202fdf5d97f57dd4d145e48068c85a0fa46ab1836e560827aa32802c61544700fb0e0a97206ee0dfbd650645e99c0a686fc7b5f2d9fd95f693ff61dd34ff208cd4f3c26eed4a6267dbf574c3f60e4aa680e741271bdc522d21eaa5ac46fdce781b6c9a380766e3ec3372c75f2c57014a42735d20ebdc007bc41dcbcc3a808ddbdd24f99af727c1e42b24d9d9505b3973164e6068ffdd8d172f8b1204a558a34d38a743e5cb88797a53bc4de440feec468754caf0e2c341679e6840dcb677103d87f343db996a5996a20e60af6355c6392af3b5c90ab0fb180c9a20960d0e07efe1df80c242382306ef787514f4c186bdb9a514f76cb587dd556e793f36878c9c6e725db870ec5e7c5d06244cc3e4e6d1c64bde84779756cddd44f66391dd7077618d51985ca45cb8010681b75d7112f4e423a217bbc403c3e4432225afb5356ab59f57109fb3ebdcc919dba08b8e073123a789de7669b91a50dbdbc5b866b23c783198f4ba3b555c71f2ec457c5974f358a687724f111532c3d9ecf3c947ba5c1476720ff0fcea5ee031a0f282ef3e319450b873705f0a12d108442166e4494f4d82f2d89c3288c7b7ac5177037682bf9d0e0f404071f1f2dd1848fc8df11f88a1f5be4b4fc098161b2233d17c92acb861e5e78b83d2add5c58dfe591193fcf52413ef7bc7fc3102feb4c87d8f0ccdf64177626125f357470ee2766f9f806e8ca4bd79e2311136ccc045bad10ab2d691de1cc6399433e4b1f77b2eb47f3f39d32a98f2c96a93b0303c96fd8a2d8ac0a37825f07da91a8d6a7a270f0d43717402620713050032c60267abfd294e53a0a9150eb65445455e32e6ed4dbbae7858be4d8841e28ae254101843e3f887e369e7668ac383c38924ea08a1eea05051ac7d754e87324c60e805bc12766e826a6c7082d65a9e4be20c7ebb4511f6a642345129e55156d5d57442846aa5c83387d82cbcd64664cb737a171cb1b6ce97b174e37166aaded1ef2358695be1c94ccdb91495f6692e1114ea21bc920e6380fd997057788767c30cfbef53b81a9f2289eca0a4804b048f7150b844cc93e49c3911e6257f9a3ac2ffbd669a9064ff28556c04215c11ee35d110c7b555b0ddddf12078f2ec44d6d95e3d0686098a6848f3c6a78b55845443ba6b16ddfeef16f8c6015328ed8d5c29ec088ecca5dea9d0cd9b68fa4200671e553c2955620e651ed6fb4efef1846a70878639ca22622116f5fb8b7617b348dd54b60fe3901027444abc18337eec0936a3a880b01a74cd97ee2be11d8972df3af7b2f2d384b22fc6ff64264f698a3d0ead5aa4e63d913c93f13bedb0ba8a5f7af021e9db01fc76c5e4fe944ab6b7964baeebdb6dd3c056e20b70615296a5061126ee1c6f4947ce69e08c4b99c4aeaed95e91b99aa0336c2ead865cd1e507aba16d2f88fc695a47ddf946fe0c19f511602d98ffe48b51ffd11f3353dcd139bedd76969ce674cc201d681c2ebb7fc5ca21e4bc53864de4ef0f96c8c1513cd01e26982aea0bec34c8791f0677bad5ef617643321e164506ee4c16cea4a2070bf64b42002c34866ab19360ddc3557576f9add31d6feff031b49eb6e490922dca732e7a368e6b131910092f574b5573760ec0cf788a16a40dcb9bcb91e2e1bf455e3e310563d0fc8997bf67b76f03b109e46e862bc27da116018ca32887896bda74a05ea5c111daee99100b11665670fa740cddd8278ca3f9eaa110385b60f11db22479dc7c6cc85421681278dd2200b23cbfa8fe618d275137688d335111b2823082cb66c37f76104be175c918f9fdcd86cfc34cd8065524ddc8e741195f7a9785b8b791d996dde34e1ca109887ee18def52ecfb4df4b759483442f4d0112a357e35321dd1f325e5e30eb3c97d8230d1e84c5ac2295f54f12608c78391ae7a69f18c647cafbc20832684d3c06f12e8443291b8072c4c99e1b8cfc1720bd5e19beaff324ae5f874cd0af84ce694d00dd84bf894122bf6a53199d2ea8057db37e14e9a057be7a7dc82a36a8b50bb9243e9c61b9843cb8867c50ef273781088640da142595f80997db8b485275eae9ca772ded5f9fd685ff701d2ed3314de89634274ffb921639ae2b3b2e6dffd395ba0d77c0722060bd2d94ca323b937e2b8056ac6dcf2afa5ff00be75fb102e9e7c947066001e80c7a519c3503a727a3df72ca339f8db338a7a6e2b974d68b5e4a2f75cd283571ed90916b2c619b09e3c9f4102e74fc9b479171f05676c35ee8b2dcac462c8c223fa371d7b3c1fdbe4b04d20ec20075bf13ce68a7d1fe73526ea8cd829f32d79e0b1eea0ec7b7afa64fc135f9c8cc002d2e2a90351102231ec9a2962fb96d8d8f4891e2246ff9130197047cad5adca757c74437d21bc9c805a837a4b62636b5149e15c02e26ebf6ac42383ca48cb489a5c08820deb1bf9a309e0111b3d42ea3cdf09b581346ea12c69a80cf47e7b427fe0d5c641ed1e23538b315cd23d267931875064a8f537965db436f47269a60d6b6e1464596d9179e3c82ef4851c9738c34062613ded7d4cb8298c60c9f7a96f4b109bd75dbb9e2620a1a9e8954c3c68034ff67fedb3adcea225a5d96a5e03dbc81934f53406b997f454af585772711a7b3cc9d3535f66c80d2c12cc3e3d1813975321fde4493058b784769d137e9ce2a4a66e511f2c327e93334e40cc3ea1fc6f03c2aea330ceb0dc3e340073948f5d991cbdb0d2715dde35a11949f013bbf3d4d90749db3b2849907eb584edf1cbd951787be304de7989053b9e384ef79278f749267ab4c62c946a260eb9e66fa223992b44e564ec5c45edc0355c3c0370a9524e4429ed68c90cc30d123bf40994bd14c25a754510940cf2ee9c14630de2b52efb7a3b8c7781f2ec77c2193a8a0941e37eca682b2fd337c74a4bb12a0f1477758d9759f3f07424488568e1ae9c706c6b08ba1b8b868a9146b9667f85e41b9b7664fb433d04d67ff32bcc966df0d2044ffe35ef26bc05ce25917c1b492822e9d4de0064696d9df6bd8b280e1dfb7610715349ed3ff83da7639edb2896ddb0ac3be364a6369e6449a0ec7fa322a11b6d66a83c205378391d746bf7e12bbb1c7aa988fe8c08be9a0d793ef08a8f3ba56f32589d7a2303d665c4568616a7e477a2975a1fecc8ae785e61ef148056da1dcbaae4f2bdb96d37bbe87894292dde0eec39c426a342190168a17698ca7ad319cee9ee5f26a0c174174588ba1b61959206ed371367f505759d6481572db2376a285d09ad2f92378797e10881072dc8463f4a2e00d85778c46642a0895f65b071b1803995420fbc871ad2a698552a936d99e7ee844b306c450cb021fd073532159225a4ce29179c47c506b9429cb28dca138eed324291084cbabc2c4b3c724d1977d463dcfdd439435132eff08401140358b5e7abcf00decccd5df4012ac2530a2990c6455664eb910c589bd951c91a8c30c7eeddd785993d835d1000f87fa1f046c26fe7a034b14f9beb1b1e89d68662092e629c149e423a359f7659696f27457496e3e2289c1d84321e371dcad42e5ea4cef279ab217b78dd2df8eaa6586f08b01a9ac77b200206e079f6ce107464f3568d586e9375f2d383a182132f99ac15ed8c2d15b75cc4e6c8c3148e6e3699c12251a83eea859cbb988ae03ddadcbce8b49d04372a319a446eefa6ee2da51c6342519363918aa982b18bc5700a4a5f8a5c1a77f38bdfc67fdb089df5380de99ad73fd168234569e22957c80e214b52f698cc912a0fa817c4ac45cf732251e4ac8180c015ddf2728f271a0bb63b40ede29c23487e5dd5d7f3b75836693ac86ce070aa6def697a6b13062f5e6d6a0822a5143a14b28e60ad60ac30236ef71e96dcde405d68b3071941b79c0f70cae1905a78e7ef9c73c4c167743621afff5112adb2488ab866e35af5acbd6bb9e0fd2f8174feb0e3e3dca055ba9429705d6c668d8ed95c88561c50b84e8fe5c69720d7eed3a83237963f461e8627a0b1ff92d492842afb7a14eb436686ce71e43df5b7fbe7c4948ceefd6dda4509c836d46d619f25690c2b8cc1f2a69a20456607c8da030ae6f9508d85508c22eebec9e79505504022674a2a9ffa6bb1e54724326aa91d41883c53648b077be2e9e5864a4d5b5bf29bbb283db8a055020c877aa42b08a957206b8b547f34b5b692aeed043340a3332b229d6824575088f84851072afbb648f8a0ed3ab031f3134107ca614880eab57acb8ff718a2d23b6b9b0354f76a1479fe00fc676a4659bbb04e03bebc62c60ad9f56b6d4b5e4c959e430947d35a1f75638c200c71644cb4485941e2756dc00f9df4ea80f8ab3f86af5a7eba9481116bca9b7d24b74cebae16955fd8ec94c1d3a2d957684188824674aada8da4f87a5d10683a8860a50e1e9834c6ff5544dae0ab7be4280a713ace8cf28b405f8ec4269767a74649e12ef07b5a19050d218c530d6c29ea1b18d2133cdab7f0fa43661851b6da737116e2dbc401d208b2453be61488c657218efc64e520466f1c21892710ebad3dd9b8983ae22c92ab7004ecfbd06933ad5d31d3094b0131825b10499aac357cf4908b374efd10c81b5fc939b6c5a3107a2ad3b5f4084ba4cdc24d13e43c57e2ede44e60a3bab70e71f709c4c0a8dabd805adc12c73a9b01e9ba9f4aebbfb478677b6f6460bb135297dce487435f47bea5e4532e3182be4d2ddd3f2f06440a93005e4abd6009eaf521242c6fdcd1f2eebb3d5dc853f591835907b414ad9b16287f084e8fb174dc7a696d70fcf195d4a511c921dc79e00659595b6550edff40083121a4c8ab8ab7875be3813e1c34e63877092b656882cc976f995a34cf117cc30e30844707e4164eb91bdff4c166e262b7dc128b2e87620fe26d3fca32a9bad41a95d1df3ce2881bf832416b5d6692a128c3dfa0a1b31136f669b4dee3ecd1a7363749172cffe0787cb6870a47e700f2259d43515406836181ad5776a8ff3cf5915910f586ad919cf875cfc3d35a3f814e21c9f4d921956a29a1ecbcb9033b54a817eb0725fb3931db55a027a8fadd840dd7e0e0c691a67e4c1adb61efacca5ce07ea28bfcfe5112d0e6c896e5687b236ae1f0c949f0c89ed968f07fe49297b2680fe27428de7411d3932f50a040d2dd4bd89d172d49ff3fbf993857eb30d0e19763662ee4f6add3b37e273ce65ca70b2693b88e508b53031ac7e5290d114abdd77432c2f8a7636ea85a2061337365374d623ce9812dea8d0fafc9a660aed715a386562846ed94a55f3868e0a151c186032f68fe65d038118f1b94e1f8fc3ce80f73af3ea65834d8556f459e9929086e65b19a44cd97081fd46071e82c841a3dcf2d76c1b23160f54ab5cd0912bfa3a23af989069580d7429db033ccbd7d5a3f4fb66f258b8dfabf16d52cbf992472b16cb8a193456e3c322d5f736984f7daa2fd5b0e63d3c191796954326321b8e94766e9644a5f79b3ca394bc1801a8abbf45241e6a3b0c36dc59030af7e385591bff1a54e6b622f888d034051a56c7c3d650ae8431f405021ab8949c8e750bfcfed5b3f1bf58e174e9e43f295198121a2fafe3eef746207baa44d77e3eac97d47a901e7cdb769e9144942a282cb7682351a744af2b8ed865d21ac51a3c331b63ea5ea00b35610ca36a66e9cfaa87dcb546d6e31684bf197221d16141a81455d64e628a8677efd0479cc0c47ba845def81d7e79d86864652099ac6b02ea28c40d10aa366aec23a5326b71b5e1fd66d78b813324008538efe2fff289e51c461a7cb218a7c80f1ba0869f89334564c112251219d7d849c944d57614ae5bb442268e24c9bc069058122b911e703b2c24c3e24dd65bc8bb350eaa8aadfeada52920d779f13e11b2687d73ab80044965e09268789a6c090969da3b0cada66c38bbf1995368660b35a83d7ae06a7b843ce2bece287ff51a02abdc6b378b9d65199ef0a72d20219495fad061224d241165a544a7d7df22322786e74f59199b682bb014a98c170b1a39e52e9ee645184637613838b0bcee7f792d0e03dbf492390f1790c107a2174864ccc009e34995b029074e0da5cb58f52282adbb03e9028c0abd48f6d5b7c9f01f98617ada1e93d0d45a79a2718f3630cb1e3c51846d0612e8c08560b304fcf4031ae96694572056b24a07159d088bb37ab4d38d0835da51aa8c37e30f5a145d4c1f95f6dc3df41cdc7f89ef4d0f2b5a280998d1ba9eb8412d2862063671b8617458bcf0b2f0f1ec2812f7f2761e7dfffffffd4ff6265bee2d654a29052f0909092809ce0c050a43f9ccf8d1c44ddcf4352971539307e1f0516742ac3d6a5dbd42724b7dd9fc3ca8fbeffba17383d983d887bd51c70d75eab85023759a5d17e2f0e1e0416ce54aa7ea1eb041aa30871d390e0a6a3f053d67ffcb2891992425e1cde6264dd3b4ef4971d4cb90a3529b73e0d8337e4ca0107165e241dd771772538bad43863772541f018ddcc568d62eb1ee9ba29a9aa2a2fa888df6a14bd045f40aa494b2e3af518110203cedf9c9987f64f899305e3d5445e1ad90c4a9cb4c9849163281ea611f1c877084a96cd42d1ac2b6d57449ed1f4965186973936ef5373b912d6cd01622691af600e23853bc71998a780884890e15deb8501cf3e9130927c0a30f5b1c72802465044a9850b932f11828bc02ff0e063434a82b266aefa0f6e43250672797b75f9ed05f000b51a52376c09efe3eea3e2266ef9fb94e0cade383c7beb277cfe33a23d5afff0e9d11eb80e13430e71b9c4c46dc83ad48791ba31fbd1a43e37c37bffbe272409de73136a845b01d436aa2d322d890b29d76f2203e8a2151a49c6e9edff9ca2a223b5fa444f3f0ecfc0fcfb42cdbf99dffd1debefa1d2794f27213c2d884decb6f0731d3afbff7dc04695e32135be11edc85d9c94a759b1f3d1e48ddc426ec02d6bc0b1b74816df917e9d80eac2316557301998f79ffde52cb473794783c3ff2110f0fe223a46ec23d3ac63aba466a529d06dc15c720d6b907af8e7ef758cb239a221ec4aabdfd349ff0003ecf67df4478be48892cdc164130ccbec33103b59f871ef96ad7d91d3ee1e19fc7860f79c2a29eff4109d80b75e67b106aa05977f5fbde2a88664f5bf43e87178a586887e71b7cc2f319f8c4033c3c3c3cdfe11310f0b0f33c08ad6a084f58d4423b3fb3aa31e11111c3802fe048a3e24153537c446b67db59366512de8a65124edf18b1947e32cc69f2be7e75de56bf9cfd59fb6ff8851e73ee5e1385c73e4516575cb982a972303844fce910c77149781c87f38437e79cf3bd07daf371e1ae8be33ec5fdc671e17844acdc770dc4f5d4719d3490882b8d3571dded5f778f66d6b1dddd9e725f9d205efbbd7638f650a9171cd7dd3dbbbbbbbb7fdcdd6def6e6fdeedac05dc7577777757ce4ca3a9996974cb34ba750bd26dbec875edb7c1d1aca7af48a5964d2a25d5b2d9867b1bee6db8b7118458d6d3e7ba66f681d9febb1b11397f58477f90ae1b4d6faba3b5acb8f087cae933ebfef17136862e071151e1bbe1e9f413e9d40bb4ce6696cd9612bc5977c90a2c5450c192153c5802133db0024a18fe194b4e9bee54ae7a93744529cfa49487aee88af2d015a53c5c4ab2945366736bee91fdbae473727e952bf0ef6b85ea95ab0ca0ca5f06d23ced3dee91bd7c0d7cb1454dd334eda56a487b5074e2699ffd1845f65ef5e4d4fe47b714c3355442fa7f34ab8064ffeb929f8121eb604de1f5e414eb90bf2579628b5286485e5213545ac1e447e218c21be9d10976000589082780810410d60b2a595260858923a27cb1031e1891a9209531690172d5a985b15a9af2059309aad49e9e7c8a55952750a8f2fbc91be9aaab02085594f09c78fc19674870ce3a42c2ca61d3ce5e526609ca30c489976551781e645dfefead0d2a00b7fc671886ef0faa3f0c3cfe1838d411c22f9f439db9350b695a74cf62e08d2c030c91610daf9f25a5f8f9fea19c3635ad8a25740828a92a8c84d08670da88c888d0429b9060c6b42a6210845400115332294700e19222796b992081028a60f2a2601a9c24555125081a58f0e4ada50ff3c34fffb444e07575ab40614104d4173e8881020ea0b4a0700694127c48c38725218c98c263eed147abf6be1d3e2cd981ded7a2949ffd942c9d78f2b746e1c9ae6357bbe31eeb7d2357395d7ede1a9ef410c90c8e81100294cabf2f50d58f2ac74ea344a5cb4a6e97fe3a92fde55355551f1d5413eb9c90a830421dc594142a3fd4714775679f3ae006f81085038fdd3d03def2e7a4e598f66662726a402ca5c494b4af557b47b5796ef1cbbbcccbbbee2f594a29a53fbbcbe94bf8944b48ba03954ae876ce5156ded1d7dd7d6ed7ed673ea1a2f639e62d89f1111bcd1a780cfeb64422146a9af1f33da97ec8321f7461fc90c4053318021b48b0f1c4162ef025234820fc208d0dc30a2c54502e62204608360c2a94e820a2a80111413461c51629e613543cc992bc20aac9921c3861841c2009c9d45da2e2045418415997ec52aeaffb0fbbfbe24ccdc9a5e694bed3478b3b7d939967ef99d30f86393316acf4f9b548a9d47c6a99bb53f7fc05e27f546366667a84d37eaa25993d70f70b7082f0f8a1b668f16056ee53cdd9c185bd9d7c7f22d520c1db976afcd775b787fc662224b23e89e92c8085e6b3d00c8f3aa1f93a4160aa3f4cf5704c529f7c6086d45beeee04b1723034cbb22ca39452174cf1831a34955fa3495514c10a57f9b31774107e90aafc13092a5e9040c2a0326f7142e5a5f27753073989caef7d4a4cb85229156b4e9516540e42f570fc9a9fd54032955fb008c112a1a5e2a3eed2882bdd3d0c222485a12eb8a89d85d3c3f1abac22bcad445cc765ea2534163ff6c5aafc3204d9170aebe2e7df262a767fc0b053f97128eaa0258201c870aa2cb20462142af38e0dc76d5a29a1b0af7d16703b65f33c4de129970bed7aa8bd7358973fcbe51ea52c5072994b1135449f2975dc2ce28e9fce615ffcfe1c730846731e86d7bf54c695daaa20371e185eff4ef94d4260d2fd532e9722597cab8ff53378fde1f91cfe04d90d77f8ec87496818d2a2ff0c59a101dc86212d36ebb4dca1c8779ae8209ea45dca12abeeee51aaf7cb65225c8a649101240da4e3d3891cd471b354c7411d774bf528ef70e4a3f61f4a0668a0558281d2376178232321212121f506161529215f7e92da034c93d0f65c4759ddacdab1fa4383194461243662a7f6b3d20a414a93d52285c6af528e924003a1f954cb32b044a7a713b51f072ee30ac99be1c849d38974007054e0c9dfd1416c44b776ecf0f161b1562b24a4f1e767859eb61a74eb679b81f7c3492218346852e43b25fa026fa49e5786a7d33a5078d5078f1778238fca38108166566c70813ea7d2c7f3993eddd215e73b28579cd3bb5d91f967bef2ce8afcddb6f85795c70fc2aabf6ae367086e7f59e342559875f9b30e73577f1fa52ed5ff874850007649698b231dc5a4388247f5e75e985e411d3b987ded7bdf60e99ccbcc54db750791cc5dba31f306aeb7e6fc49837431b8658d3f9472f85e1adeaf385948f0ba21b4940fb2094b5a1c5551a3cb4c428225253021e1042dda50c284c2092d2fe20c1b6981b0c61426a2a8c0093880811a501c21460649218f5220b865ddb4c06b4e22c47df645a9df3e891038c45beea4d39fd2e94f7d12a1a26d2bcab62442dbe72d29bc19c6accbe927e4de7b19702a9e0c9d0af94984f8a5b7fc930811f156f69fb7fc95b0818b94b0617e1616cd90c32cd4b1613eff0c89784b4847fefc2a3cf0c75b2ea512549f413f713390992a85711b55d5811ca8937d12213ff256160e31ea286fd14f228484236909a698d00a29c14f7f8557c286feedc322ef3bea578876a863c3f6fd423a94071dbd6a8067ba4c7e5405a5a19199c42d7370e3c3dfeff6d53bdfbcaf9d97cf6a480fd3d81e9cc43ae4b7e49dffd13796ec8093665b37b887f6f2c66fa0b7e28d1b52c78d1bcf205df1c68760b7e28d1b3f321255bebcf1db3be8db167e6f7f23e4a31691963012d3be364a299ddbb67d2f1fe6c79eeafe3d4339ccaf6b1f78836907eccaf2c6cbf0e5f7dc06eebce83fc6e0f871ab39defd7380384016c89f55bef1db86cfe0b8559877d5d08d23620dbf55423c90c5194c2085cf71770394350c290cd34ef4f1ce55439de893fd8f097ec4e1286dce29a17ce328979a1af33b6683c6872aa79cd27307bddef847b51d1e77779e2b4c13e4df9161864fa337f0805ba90c2e77c04feb181cc51df0d33cb06b71d5373031adfe7b8e763facba626228bf116e9449fc49c2d37e14abfc61256cd00c0bc5c43d6e60daca5032dcc03d90b00ef9854c4c5ac73c2a21abffe8af7e5534d0ff543330c8460cf5c37a16615ee4466b78e34679686407362ada818d36b063207701398a8bfc22483c1b703ceb7f3497c4f484373253124331ed0b2ccaf13afcac67a1acbb01f67c08fe6f20f845907096653e335343f33b7f464ce13371fc80be1fbfc7ce8fac9aa5006edbf2df0177758caeeaffb5aa8b200aa0d370870682edd0ff5441e8f3f71f1ba578c0aeeef473be2807eb7960d1ff582c168bc562e560e5c0f139fc7b5efe4bf0250f0c4ec334790dfcf9687a402f07b8dee209e98a86b09d704c5257211bb528657e5d270cc7233caa7f4ec838e11716edd0a227026f6428661e3e70f0f8f1791f8e276fe48e7117495ff33c1c373734a43720f736e098a3662111d780e357679e061cbfce844c0c9594c450ab4ff4f91199f5e327036f6c26947a1eeba9b34050a6004419b2775d53aa5c2c90b048da97d6473eb4230dcf816c6c5f3eb89192edc147ac43be6cb922d3d03de297c2470e76c7806ce5a4265a9453fee769cf494ef695bdfc1e9c6f633ceba16a39384cbf37fb1f9e038e3d95be0d387e5b48c4ffa37b3ea9276435a7a38537a5140f62b54f95b3d2df2143d1fd354d63f99016ddb5a11d2dca9faeb9bb873f5be53bd287513d3a3b365fd26cd3380c9a492dd39848c76ab93e4052feffac06daf9d5cbf769209d97dbd39ac7f9554991b23ae44f1b2d6782f46dc0ec73b80de4ca03eee2e01c58ad9353d37eaa86e6c3681c27b32c530dd1988c66dc38e0c8aa33f593079cbfaa4dc3c18356dd62a9fd9864f5dca986769e53694f44fe88e3959f06e7573c5b0f76621d47583943783684445a944f1f07dc791e50e7370e6c8b9df8e504ae727670f1564fe5ea6f562b1cd8487e28b32ccbb44cd3f833d6f8358d5552ee93012a0fa932540efd351b671bed1d84edaa866cfe47f37378e4a7fa3bc80fa312e2cf6f03f2fb88dfc762411d83693f323bf1b8789a114e72a285fc1ba83dceaf6a933a60afc0de01fba88d5a94eb922fd286ff9ee7a438e1f4c0e3183bedcb5f7e0fd8f32cb474c594aa42dda5154851b9addb01717e05faeb8039dc64555936e7bbf365b2cf62f3733ef54c56cee7fce855999c9f116bced36cdbca79991cd8cd4b1b4e4543f60cdafcaa82646ff33f5a03190a47d1ba09a640afad0292fab9deca09c7ef8bc0d7e12f4a22d4f33cf43c0b656fd3036adf420c0ad13e87172a7fcf73cf3bf8c4033d3d3d3dcf604f58d442e0ab6af8079f9be0290d84f3f2198a9ced2585e12a6f8f6d75f07b9f037e20b860e76d215d1787e31766fc37515a942f6d42761272528bf23d203cd6d802aa1afe8bacec9798bf87bfba813776ac911a481ef991d956edc11ab0e61d1c8754c93d0d38f654999f01c7afca844e440c038e34f5a563dc3b0c3f5a943fa481fc191c7f5b4d49234be5a74758425653d230a332cb9a00af0784e28d0dc55b3f2dca90478b92478bf25755030bf5bc149ddea1c435cf6c0cdef632b353daa66c383938211b754e185bc22276c596ca2a205d659ea1b8ff088e58aafeebb4c9cc99f7fe6ba06d669b993321cf99f7dfb6779006fc544333cfcd8032328fa45dc667405957b56d1f64fef632a091e9acc463a175550ca9f79e9b41e6b7e75a45c3cccb84d1a5cafccc732fdd107dc9fe12a4f9560991f9992a622ca937ea56f1a24b65150d313133e10e560dd53cf7a3e5fcf6b481669e478b3099df3126a93ed4f0c69f6a0426138e49aacc1bf991097db4e8dcecb6f7a09c9cf047ba8536e07c4ef8ab67214ed6e0502465e35024bbe4c436b0e655aa20335ff332a08f167f1a0816c3ccd73ce772e7737e2392b3b393f3ae1ada5435c83af3379f6364e66b5e82453aaf63130a89f91c5ea8db1725115a3d0fab3066cbf92de765c0271ec8c9c9c9f94d27272cc2791d24b26e7f13fe34d04c081b3949a68aa126dcf1f38337728c97c87cccc77ccdc7bc3fd79c7fdf8facbaad827e7ce647afdbd374d00f6ff96fbf3d6f0f1c5887ff168e5e8bdfe69f8dcd8dccff6822fe3fef8f03ccfbab54bfc950c65f158587f3dc736c5f32ef3ddf0703859301b932946d731970ebb675db16ea6c32bf2dfe688ec96ca11099f0090f398ff31b119c2f52629bf99c9c997094f96d7b9c4d666395ccacab4200ceefb66270de655ee61b88261cb91ad95edac8af79ce8dd8fcf7d90e329ffd28f3313fee506dfe03479bcf6a6ade553170ddb6c604a9091db0dec27919d026ac0147991f699d792eeb6a40f93423d79b99979151e2c1e3e74746e6c7af9bcc8f5fdddcabf9ec6dfe47df803a78bd9101475a657edbfe876b5566bf81317f038e3bd49ab789a9b951d1c01ced6ac0ad34b44e70e498ffe13bd418702b0ee88123ad5be8fd0f9fdac3c4469a3a3214aa316d3101de7a8f92670403f2bf6eb8aa21bbad9543926d1a94825b16b7a5a0042f539c52cffb3ef932e54ab7e422b74899e5eb0ca5d3944e9eb73a54cd19c6d055a98e1fe52d8b7d71d9987642e08d1b535454038d2f53b234d0f86969a07163aa3d76571a287b62f251c4a819aa8de8981326df82249bd857ffec0bcac95bfd947adef7fd8fe2d6a5c5dea2dadbd868a1eef3e8a0d4c684d5d1bf5bd4133ce9d44057f665d4405b7770f124d4cf23f7a02dca5bfd0fe54bf2984c244a3deffbfe9d6a3fb7eafe76344de8b180fc37c5bf0522bc19760eb620024f470759b587c1544d8ba241be16eac0d57584f0ccc0a548b8ca306aebe2a481b693500dd42e7582c8ea2fab87230f955f9e6c946852a71195e792cad3a8b2ec52f925970ef22950b349f5291ea4925d5cc988698937d59e46ccde5289246d4a9bbcd5b4c9839e9a9a68136daa4dab872a25dad48637f2539353652b4e3c51a88164f7d41bdf206da5b3ca999fa90a4a5b825b964ef6fe498424a89365ae436937dfb351aaf245529d3fb27cd449b5ec4a9d2f756ae8976f43bf0c8b9490df9f8536f06f61913f7fd315ae5ff66c9dce699cfefaa66dbaa6bb1ba669beb3de3ad55c77adea97e6df51dba7368bbf797e67e6fb69ea841dc30947efe36791b986959861f8a90bc78f0bc74f85a398312b51de58d2ea8423ddcf09470f271cbffd2f1cff261cc51fa7ba5f53d4e2fe0ca8e44f1ee4635b1b138e9e172ac184235585e3b79f0a9fb670e45179763c6969387e9b71d4e7ad56d5aed439e182b4a7bf7150aa53bfaa2156b7b4dfc06596377abd55b603c1f9737dc415c5d868a4557214b7bb9ee4634be14029a17400bc99a4c2913a73a92c95a5b2549651aa75777777a7f8658adbb66da3f9946af324ea4d5a8c62448b1eb6931671a078631f31a9de40485bab9e38e19653e0ad15509c547f19c340befa56d92d10e13943e1b6527fee2e4005b8adce5e554f5587ee45d60eaac2b4aa838caa7b8191b158e5cd6181e7deb5db17112b551db2ca441cabb0172ad3e0c52a7f17846395b35851c6165354e134c594de6f75ccf046f1f3544e58562d761005a255baf2c21bc5da3cc43a859452765048291bc8a594325c4e825b393993383314fe2c41b946b8ed9b167838bb5c6a531651a0c8236994e549090a6fe24ca2b858699212a597f491962c549438899b7489aaeb5477fcf0f30e6d28a9d4556a719f1966c7cfba606ab4f0ba37088e1a2dbcfda902c2e14ae9d239ced65b9bfcb232548bfb315ab89549b2499728272b4d4871245fb2450b1315279dd4b12e579ca634c1463012d491cd42061c33bc6d91856386275b5cf6177e0e351970db6266f9d2259581d7ff5503b9bae4cf26540cbefa28a954964aa354aa4fbc9c107851bc7eaef25bdc178cab873eab7e1fe22a5fd65042cda50b78231542142a8b5027242317eafabd1ed491a6f60f03e1858bc39d91b4e2806cdd17d755128208f56b71eb2a095184fac1248cdba83bd7c5ebe290430242d65d1a820f940d982d57aa4a8bd1db252d4cf503de2e699952f7bdee929626aa6a5ffb75a3b1e2cfebeeee761590cca0d2dedd2e8174b8b29a823e5a641e73abd7f3b36d7bf6766f21799bb144b62a866c6edbf6ccdffdd9d63c5ae4267125b81567055e566f3450fbd75715e466e4d4151ce3e12d5ecac3a5cf9e3b3f6564c183673b319596c9679095131b4827487728d6fd4f12fa78a27d87eff3f2c9d127a43d4df7ff4085b4e7ae8f635d387a14d6c7d18f90f6fce00e407b6625d57d1819fde009bdb000e077615be0ffe11352c2c31f2db6774b2e8389b68294272ddf500401aaf2b3b80ca4ca3e95df3d26f73625b8e94ae4b2ac331f16173ed78c460b3d18161b48cc5695b3a7bf0dd45ef7aa81565e85611fd5a3f9eaaeb81f9f25d6293baa1ae232eeb79c4c35a48519a53506ae0dd31da65823c193a31f5cc8074d48891c64edd8910f42b481bcbbb329a56caedcdd9bfdc0c1ecacb265f6655916ee77077252ba37e7a3d56ce53ebbfdb33a5d35b47582dad66db3f74bcf54190fa6bed4ee70eb56372584558b528aedc3ca2ab7fb6ae7491b48ae9cd23d7bd726336bd9d7dd8d18522ed7ace1f1eec32491695cf71b854152a5675a8aeb568479f985d9a1c2bcbc84635195a9aeadeac027319c16e92fa970d795fa1457e9ceaa2031af814f62429a6eb1af74b986cfcc27959abb160f975da35a66c8eb9a5f7d672699596acb9ae653669a9639e179443eb9be2b3aa774399dd022ad780c6e2baa72c8b60ef0949ad4b215674a07b0579c9916b2104443f2533b32cd69449f9fde8d61607efb1770577c81d9647d79f9ed25942d6ee1762ad949e66457aebb751aa4924abd279ec6592853d02e8986fce7d3ba99f4a9fbacee4e93bdee2fdbff684d0b97abd3cee2f187da04b9669dacfb72594e4a29cd6896895e5716b981e68622abefa3ccda4b3976ab59e972911b5a24227be0655cb783ebc6d5cb8f3430d91371a7f09f488b383fc8324ddb3939aacd15a57ce75f51be0682caef330bbb06da90dae0fab90685c00ced48c36306c2758802631ca1e145e5f746fab4051043489a4ab324e5fbf134ead9ccb299c2104f4d68e1d2930b6a963dcdb3b2503c42cc5b9b83079e5677e989c9138fd65d7a427a3afaa8189c94babf340426754324fdb520ad90a1c4b63e4aeb05174d57f895eacb65471db70b0b7564231e3275e4265a38195577271b388d01060bc05063290c2a6218218695165c811a03690c294b6364c182022c3ec0f224d9f8f934279c0f599665d90a7c985d202105d2873356f0124402b6d8c1144b68d962898dd9832ed030a2ebe20c1904f1a403277cc185ca954bc8c1199feb8b20787c51459defb3088d104fa4c106133c28411421c9832526b2a480f036005ec0a0cef9d40ba73abd061a797ecbc593359c40a50d17b0e08a286e708457c45bfced130b31d3acbaf081a6caa75e0aaafcaf8b23bcb6041e1f7fd44a0e9614a1c9833cf6ac5071842c82948c045e84a869e58a10bd058a9c568850e7e4a57286940851bab54d50283c2a5f166fa55b5b47b16a44b8d2add57858e16202056c69e298f8f85341044870e2815753b70a12889043081e4ddd2a4868238527783275ab4015b1c5f3ea56814a4205243c9bba55a09c48c251168e36d0ee4eb99276f777f713ac245169beb06e953482f018143985952080becaf9ca853ab27cc61d3ebc1b9b65b700403a2f6123b46052fd084b5a3061c907598c60698b2b517680c552d3117c588ad20265898b2e4b3f687a028436765f9303759f7a0e7e021bae00c2084f0841064ba060022107180801064f28d9c11231777767ef27ea589cb999b9a04e46537706132a68a209f729f5f424babb3bbf3b3716777777ee31a4bbbbbb73636131199e497777777632ba63edeeeece3f72952419eeeeeecefd947999bbbbbbb337167777e777e726a3c570f666777777eedaa18e907ef9cd8e458ed14c6e021f00d14c58b25041d1b4250b2c47685a38220b348ed020d1a8e623e6880a0c8ef8b842fd8a91f6d405c9080b946c09131b40c1b263c6c90766ecb822fbcacfcf2b9c94317184169c2406d44684932ba0b04829a744c2fc058371449447c42f852e66e0c591ef8a966dcb931b68c142fd4877a4f3b94182cb123e57322fbb728575e8a93924e12da8c00041451954904c0ef2138cb043243d91f4184c2f4da62481090b1289448ef15333832e68f073659d382e74c0649db0f0a05e83c838b1a2081e481c898fb16259414412ab7d67e993f3baf63cc2922112e62b417e0ae9e124dc9ff3eeb7ba5b802b929e01d7aa96cf2a24bebe2a863252eaee5ab7ad92322cafc1254a149410c75d837c4512672409ed165cb4408a10e74a9a100613a11e609a8a580444f08249a8879ea4c98458194a423d30b310dc72309d8163513582a4ab94a10e5d8d64e1760db8658dac04a9f6732fa04fb73e160aab56fd4b0f1cabe3a543d104de56191aa13c84b755d0b852fd99634cb1260fe21897e618972eefda208e3df9a0b168f4732c8d6d6d9555352cd30f269efc6d823128a02835d2902a20502b14e1414dfe4c6e4a8e9a5889518ca981baef29b6f34fe6a7b1ca1c638a516951c95127c18561e49852ed576a208d4a03f9c7a05a037d939c83f3e52526a67350a3a99c0de7600ac7c14ec74155652d1ccbd262c782b0313354313cf1782f61b8dd8aea493918199b1c1e8e3d79100cdc77dd4f57a7027df625bf5f876b7b541874e8f87170fbf96129030c04e851a7eca01fdeea1ce4aafb9299191a9a2536526c6c6e6e8e7096c0c1c9c901627583d5cece8e6a28e5cf4d9e4c535982fcf3532ee5d6e01f0487b891831b2c160b05265010455185292a743e5a3ca8475d1d66703e9d8f6a48ea00e2ef9896da9307937442878e9f9f12b0a003163a1654433eb1206c10c7a0bcf5145bc22e618376ebb65a50d2826a8895ac88f0465612eb3a72ec8963593a8895c4941cc5b6d46625310f3aaafd635d4d04c1108e5d1d57c92b22a80b520d718c4b8b354835432a1c532f3ff32d16c531a8a6e71897ee59462584fbee635441e677e1287f863a466648b7452da8a35ab64ab1a8065a185aecef5440becad0a6053eba3ae3820bfbdaea23e48dab34e4070c36c4a0fa09a87d71c7a55e542f324625c47fbe2743ca4a90b8f6d105c7ffb5b8a4c509f3b79e9c224afdbae0bc1b5329ed53dfa9972afa23f7dbcf7129493f05d2163b74da1d1a91df8554be67dbcb37229fa6a47cfa0deaa0d5961ae552dd214db19462b917eea6b6712a9898999aa6a01ab685648716611f0e4131fce392bf2135ec36e5e8403118a98eeb86e873373743eccbbb2fc78a1ac87f6a6fa4a808c9b64313c996524a0f2606a42faa0e4c894016c80631605b41439c01433c08b62d1cc6f0a0dc45a08c064e4c7184ca7fa31ae2a5eeb9a8a3e361705ab75a655a7c299e9d8eeb7878523ed85628a26763aafece5907ad76b4c52692d57f2553d9fdbf00814603c1182e641686345fccafbb71e3c68b239dd4e958ac1c2b48da71e0d869121bec720c29c263ff1cacc5c3e15540629cbc71a1c623ab9a3d677cb4c41f899452ce0e0520bc8eb1f68cc4466ccc190d278aaaa199f14b0ec7917f59cb40e92ffd0325bb8208542891dee7d1789e2b498c2580a18329a260d22cc836c0860b90886289255af083904b41a35377498b1234b8c815aca0026ffcea2fec0bf6420cef3f807dc10600c3fbbbb02f980b42efef635f301f3fdebfc7be603d06f0feaf7dc15e3ede5f00fb8209e005f4f283f6050b5a97ff8ef707da170c685dfe2cbc7f00f6050bc0bafc7fde7fc7be603bd6e5bfc20bd682cffbb3b02f180b29bc3f00f6050380389ff5f375ec0ba6635dfee1fbafb02fd80aebf2fff777ed0be65a973fcffbfbec0be6b32effd5fbabb02f980aebf2cf79ff14f6054b615dfedffbb7f6056bedbab49779ed51d8170c8575f97bef9f635fb01cebf27f797f1cfb82e158977ff7feac7dc158ebfa1bfb82dd58973f7dff705fb0705dfed9fb83fb8281ebf2eff7ff7dc19e58e02bf016086770fc0c7af9821101e97dc18864f8ecf705230af2f4f705232ac0a77e7bc188847cf7dcfb67fb821119e05f7e5f30a219de7b98178c88e6b77dc1880ef0365ff3fea97dc18868f8ef6f5e30a2219ff338efdfed0b4654c3af5e07468480e7f99df77fd9178c2801ffdff3fe30fb8211d9f0e183efefed0b4694e3597fe3fd63f6052352c08b8fc2fbcbec0b46b4804fe15bef3fb32f181103dee755787f9a7dc18888fc0aef7aff9a7dc1881af03fafe3fd6df6052372c0b3f00078ff9b7dc1881ef03bbe0518118f07fa00bc3fcebe604437fceb05f0fe39fb821141e07d7c8ff7d7d9178c28023f8077e1fd57fb821149e07ffc0befbfb32f18519117fafafe3cfb82114de0617802bc7fcfbe604414f8187ee861bf2e7ea20a843f2800f6ac8b7f0220cfbaf88b803bebe29700180150675dfc100073d6c57f0388b32efe078037ebe2770068b32efe068035ebe22702d2ac8b9f01e0ccbaf81700caac8b5f0160ccbaf86d0061d6c59f00f0655dfc080055ebe2af011c0272ebe2a7014cad8bff00e0b62efe19406d5dfc0600b375f10b010bf041405f17bf0c60af8b1f08180319957fe885ca0f0396ca4f8001547ea1312a7f75a1f2ffb852f95f18801895df851e95df471895bfc7abf2bfc0a8fc021040e5e701eeba824220a7ca1f0070d7b5036c21a8f2b3e045e5070050e5ffe9a2f2eb00775d2b80bb2e17b8ebf2015500f953f841e56fb150f9452e2a3f0ae0ae2b07b8ebc2118e9eeb06b8eb0ac15d1708be0f2a7f0fc8cfa345e5df59e9e46051f971c0ef8aca7f9342e5b7b1a2f2d7b4765d33618cb7c4ff52a5f2ab72ecba381c1b48a9a8fcda8dca9f4dc1df2b0558d1c8525b485ba95c79090d2a756bff04fae5e98c2c6a4edda53396a8bce1d45709e9a91c7e8ff3e4350b96089113cb51f2a4d440dafbacfe924a3765e164e5e94af54d4b6d0f37264a3def7b9ffe3e4aa78731f4fcaf4b675ca9feefc4240a977538aa746a71eba26d51393b24f8be2d6a6e514d9452598ea234715c9840d13d1941c9625f502f46e0d07fa1b28b67f4ed6f514c4c4c4c4c01a3daa884cdf66441ec10d1080000004100c3140020200c0a084422a16028186ac2b07d14000b869e4876589889c32088519431061942080284106008010081a99a1a00f9eefc6488e4606f630c03b257007b312e2ae872714fe80b28e7b11eb8ee7de9fb51e6850079288c27a492b5b8cc4e0ca9bb0b089bf9989a40cb37aedd12c2b831f3c51c25944241c49080b87349c3b3196f2756f4dcb3d198e2ca92cdcb0142ec2e6d9a85122e32f3b3add43d7208e66a346218d43b459036438066a326c6bac0197ec1a6520c2e8177586b9871c38a360c139b33ce1da8864f61120b5b3b2150ee3873c08193f2949d54853ac1095ba41d9b08498ec8f5c1727ac9b53ed393922d0d19372d94e093d1a003614dee8426b5a2ee7a151f27bbd18d3abe4f18e3775fdab7e6c3dc5ab68d610427098dfca8290f6bf87df1fcb8c19f3f5470c50ab5092b3b91e26fa60887da360a52be693d1415a9a9b4578876e59f20d6152da1bb68e17b847b02ea233e1e05fc33147ad4899561697016d0ae5421736d6450c4788038dbb671df1df7d0d45864706ea12ba9637766a89a6a626c072680ae5847f7d2207541c40690e54b32c3cc0b56d824ac5852bb0c6dd0736b673ff1903c942b182afb33ac6d1d7227518eaec7d4f2b6ab2111680e17dbaf76c521d58dc44ed52898414bc34162d8b886d5c17be1d1913a10845f76c69d02de0d37eaddc0b966968e51ece602eb2b6e5dbff1c89d1626cbc02416219ac459e80dff86eeb4ea4f6f02732c4450bb31ad8aa620a833d6f18fc5c2978f15c36c5d7352b7a285ca6c824933d01bdbc28bafe835b5eaee12535a7d930c75ec6812c69bad96de61e4e0e1b876a6dc03d3df6eca4951ba959ebc6004dbdd9e79529d42d151f3ea4da13185da3ededf38b31a153b2c55a8484415f44dea024194b51e77933876df34bd09b06f0266e5f9cd888db8d8d18c699f7cef8b5f12a243ab83e1628118d96012cbac3b7782f5f40b66e9ce58a3252305acb9042a757bac77fcb6d75c5614eebb15e9259538483d592525bd64ca04f641689c43cfe2d1bf972a108a94a555e3fce690c34696ead7ea767bcd61f08ca4236318919832d3828f02b1e4949dfab3a0afaff772198230a1883356505b8c45802ffe1a54d5ec1362a726f07c98c54585572940f8c54fec247823cea4e16379e0e25f19ac40c7eb86c431d1572332914274e5def29ca91e70cbfd20513ca6683ad34c2ab64ada6bc3620a8784f67158cb47fc9a3f3aecc7bfdb47d4d2c5644e5225f7222dedf70bae09f1c2fc1432f051c83cc8c3dfad0f45f5387988768efa311aa88f10c9fc5a2ef726851c20bc7d2e4405d39d057c3428967017d04d98284bb7992f1d85ca071555b65112092d52c7015e4ea5d53aa95f27498b5e20bc9be0d88f82633bbe908aab84ec82d91f496ca4ab16bcd68b6f144e1c46158a7a2d738b6dd9b98489e7b655454444b1f9b044b46977e7d1ad5f477e067e20dd9a6c3d0e364303e33c65a092e9b6f6ebec860a55f0f9de0ce7ad3c54d0f9513bbc754574b23e9f157b04780733720161060a77eef886c4380d5a3f33540ecc2dec2015f41035ce00ceef0460bacb043f6b0f7c27b84403513f0761dab0b0231a8b42dc3889983274a0b525c4c89a73b6982dc600d5be0f354aee607f9493b4f784887eae02474533726db297d12ea77290102158c9c490613faf2a861e75309ed024bbe9b9c708a79da204ef695ff0df6a74b70c0871ffd65672ac0b7eb68920e9c031a3f461bb613aba7b1a2fa8b41d178002e5b3ccedca14dd865f281f9078b7f50a41029688ea389303ba059932d58a0663e663b275a8e13b1df3f734a14bb94ba7c33799d75beaca92a1d490897e38b2cfd64b4c0b788475362577926fe0921033a0949b9a22bd037894db162657dae80072abbe851d93bfc3e781b07cbc2a50271165bfc27848db09f3f223c43227d07a412d214578360a672b0f9b31c2d5ff4f74c88baa2ab6f141d1521525f4e08e9bbfa201c89bbcdaf1495af381bf3069f145271671a8191b17422b12292a9a961087402df67bf614660199c9896d6f5775e7c567213d4c8ae730cc42221055ff0f402c902a7e6c875859e566414a9f56b5a1745a81122f0dc94265205b401c0ad2b8ecbe818f4854ef8ab42a0b53f3cdb4a84f755b75126cad72242d20e5c6337928e939902c24af9872da09704a499f320f751f0b2fbd832427214c3cc2118de8bc28425fe1f2d1690077c6a9a7f59ca0a2bf3172667840627f53dfea699579ff59f7928f3cb946c22cea9afbb0032008baa6b79ff8a00c3667ccbd3fa1c8282c180202ae064f858e3156109c5c79c3778102f78806289ae434d4485e0b5f18a4f82bfb40c60191411f01d0631d3dc190ece090fae56035c442ead1f6edd0803ee7b953be3077b134dd02c65c163b7cfabe11b0569d68b37dcfe6a7e7699d667d91793561df202c12728d18db0161e2bcf0e4dbdc8ad74542cd6c3f0811f2b1a6f2a40ff9c8073ef5299ff98cf1b1b468aa088745c1f8ce38da69915dd4134eefef453338ede853eef7f2941f48609b454c5cfbef3eaf2ea21db2db879d8f3c39f2b4c2cde497c9b1e84f28535a747aad42ebb249aa31c0cf213141734a510a31e002355e1d03902fae08c22ec0859df5a90c8073145ce05762c85fc8c7cde1aa96b76eb26fd81eb773abd2523acb6dfce923ec8f9bd96a727931cdb5b724e6266825226795d48c2d19d543270c2ce777f91524644dc32433de2f6153f60fcde82e95c01c379056fd61f34ca02cffa4a67381aa0f09d244b816e08168f404fc930f2c1f9783abf0d22a948bdeb9b8376189f24e6331993837c1b232eb140589a9429fa5e4928e627a964c2c97ef85d6d8a3dba18156d06b42cf0345ee8e120062bbe6888c552adce5bd291efc0618875532201727df082ca2faa11dd46262c9a2070ee5de89189b5fa9a6d55bdad20d79369f620b1da05ccb366b3a37aa27dedf55a4b4c6b25305015b29b0323438fa08b07666eff1ce21d398e0e83e00cd09196ccd0ee46de74a5f1cb01059ac03a3c9a4a082b65a3c8988b9eda1108ec9403b9c5de265d8efcf13a387866484fac43407a92385077f9b643d9a386149b17b7a477750ba42c7c2a9a77b3e4b79e61569902ed7d4173cdfe2b54a31dd6b819805667954e5a4751965ac08bf9e713af8aa85f2d6f8ac1b78bb0b1ba07cd5bf08527f1a4d294733337e618f0bb259f96bd7f2ee4a32a4e4295146287d4664c82e39a09588b403a7178b9aa8b91e14ba3511707660c2c703e306c29d199f0d9d953e5ff625c53fc03eda24dc81d0b30d537178b1908e52b171b9009f3652a26dafc8a5cad5ef8a7f1be640c8037b92294ce3a51dee1eba66188313b5ce45d8c357697b607e24e153a4d91788afda8fcbc09c134498c4e589928bec3770c5a6ecdfdb99bc73cdf0a911f4ecb0e75e5338b93557f2f16b1987ac160e414943238aafd7bfd936b1792adb65449ad6ac6bac2bd8fd289a0211dd0b4ad78ce3258a55d24560973b48cf55e5051b32432411b9d0d4b0e67e4ed9f9937aff6861c7d2d0352edd2cdd760ccabce0e70198442eca8df812948d8481bc978264c5aa9f26f5c01f5aac7d9cc563c09d4e1b70bce7c3b1dd66e1c26163b609890aa0f800ee322bc22f85875be551597deb6eb7094ec0a4eeb1945136be237bd1e009d463c8ee43488352c73cdd96773a7dac0f8bea55cba42d0794b64a73d2e980816cfbaed781c6aa06b51f659dae1c81ed85d36e153b57da9885a525ee9120489b23c276bea0e895b697f72c31ed88575475858a9e42afa2afa1d7d053e8d442676106e7c8ce4888eeb66149ed97d5def5d62b09c077714ab6dfacad816f1a1af166b6b2014583d5bd93f5339524035472d1d75871a9e68b6a83a67598284b1a2d176ccd7d9f5456268e12e4a6adc6789792ca3b01f29e3dc857202a600d6d55de5c07273a86788e8ee1edf582cb9fbde07caa546e9005ac7f1c9138edd6cde523194bb7fe258c6fd7f4a6ea0d409ddf62d6a53ff0b4c264b89dd520ebb6dcb16594959b863d38ae118b060b7cf4c1e62949e437cfc78e46c6ef89393f728459fa8ae08e4acbe62651f2c31c513b49db553df2185d9f12e23d6127341507a0a3dfe6e74889c072681955c5cbd2e4b0ea8e0430ba57e159d157ed6a156f3ea0d2edbbb2ad3181b4dcdbaddc3870acb39f0cd9c57024c4870ce01f883ca9b2315b980860476fd4dd4cdc5284bec02e60e95ac48fbd56ba5e71c16b9364bab8ff688f16d61625e28b77db4f7f17612f7b76e3798f95c03e7fdb8a4cad466a36e2f0a3f52cd2251e51432f6a69521846afa678f18f0b8e9b6923b4bb1188d79885401e82fa8090cd7d53d2a6144687172a51e6572f10cd5d1b0a13c8b9e2be11aed72f650471983eba2f1535dc0b3d3841947673bebc42caf1a57723072c3f0a6fb4e8ca8ead49941efa7f452b5128c1d8a4bd58be2174a2349c0b0d4811bc5c51b1898b932ec9d5e91f43e6be20130cdd2a1a675084e75c10e0ab6b8c2c1f6320bed5bc0ca2ae320472303e345f7d0639511a2df7f264cbe7d245bcbc99f16ba304817cb57daeaf0d8508a4e8f431f7b351465902aa68fa5c8c919d158e6a1410bcc3cc8ff50773182a89cf92e5dad4bd7ef09b1b875d11bd1dcff16f2c3b5907dc0918b1a86e341d3cbd05ee0ada485d2cae7e3a288b879e77c1db3cd04210bb2c88f55864b6304f44879f79473446bfcbdb62ecf68020e12d51b7e2398502e8f3f006454534f913778b33cfb280ff6af11da4a0190a74ff92c27e8a93da8379e5c5c9a0b9c617aa27d7590946cef564bffffd3df3daf8689e29810456a6243810e2f2421cf3c708223711eb42710fbdad89b38cf2ebdc78dc3d188c833fe8b4626611c9c373a4b3c08bbc0b51e2bc13a76047a8f26bee0332f72d3889a34d1969809c242d351106c2b97b1b23cc816693fbff5e52b49ddbe2abc5793a96f369318ac083a04b383fa7148e83e71a0fe972a368d910649f01642683b900a7d4abb50013c72ac97b46d3611471f6a87d3ed7b7204c68729557d883469f62a96e911e89752c798a0f2853ea596b982b4ae8dd6d92cf90ad3cfc796d05965b52a91f2e65a1167079043ea34ee9ddd40a19d7e7dfdef008e5a3b0745bbe6439af15b14b07872aed34d68bad17eb2c3f4fe27223222870c5f48a7f0066cbf9136387d53d7f6ce32b8afe50e4fad9a09c812ab3f0770e1e01fd465b212956ad9325abdcf3278a81633b8fdef3841db66daa5d6eb4a26a418697355f3cc448bbe0bb88c6d37b2c31f0aeae963eb307395c065d7321c459dc167b20b9882f47e5c56b8b84f6a8659c993b13b5a5c0bf7cf641ebdfe3a0fafbbc07bc76636ea1c0579078bb17eec81cf79402e009d68279e7506d4d13dc0556c98dfb0ccf3c855cf16ac55626658f47055e77125a488234a347f862ac785f936de0c2b22459cf21cb8b26d8da80ab62aebbaeffa78a291f15562c46bcf813fd75ec18f7a7db6044e4f70b4eb3707de20844f64408e98b385adb9a131800987ecda89bcecaa57ce810c9e6aad1575fc8296bab0e82e36b5ce272f8a36288a8b36603e951befa3c4578390ba6dedc086f83a2fd7181c8c3b3f17384b050b49a0ee78f606c83280616e6a0e7d01e8ed0d48312662f6a6933e2ff3bf89b2d7fb194ee0f1c374ab69334e65ef795d9d483ae21a57115084892259cfaa7a995e7ce72d8869667ca217b9399047bee81d797f5f5b4d657372620d8ea2f08460e0c1f57c4c78c182b517e6ac1fca82f6f5e736feae807622129a7f861df57a09c9b5bcd35a866bf7ba81f2080f8db1456d397d0ece0d0cd29825a5c344ca7ae9436abd65e2f0c290580ffde2ac9a7339faef3a930fa94703e1df9b91f4708eb086f1e44fdef492ea567e4f360596f88547a110caf8be7614b347c9f11a1b503794bb29ba52cf13e4094075ab0609e0922a7a35540baf0fc774517278eb801e7616336123d375bc427dca20a6673344a030dad468aae6260323a6ba75065b192d897c8049da355bae2c6e72a98c6d6a637c26009113c63a9390fd61cec4f583d2141a9654b2f176a6c257885d3a3d0044db333eb8f8a5f60f680fdce7e72e702ac750ce9b221047c73de797910218ca882e3a21c5b78fdbf7254d489a291d089a001e171d6e1fbc2897b33f193e3463ccbfef423bbc8cc2156d37e6fc46577a1132a1bc2147c55d8b320fb3c9093a92af11a55370945e2168345c6173c748868676e4f359c901e68214bb43c883d94cd7e793cabf9469c024c5ce585c0085f6734adc0eac894f07303377bf4a1fb731db83346691a8e100eaae3ff91a7862f5805ec70a94f268f6c4b4b942e933ecbf6406988f7acb525320cbb83f08dd27df781403de7095d192e9833f77f49ef347b4e57d79862a600c790a5ee528edb0dcd28be1b930e9aa98a96e3527adf0afbcaf029dd8d9909650ffff17a6ed426861c53d0901ca0f56fd995fc04008a7eb5a290c5e235a4de9f5cc5a7767743eaebc0b36cec8ccb52c71ef68625c43253dfd6cd2a12170cca252f22b3ea541cb23bddf0db0065a3fbf3527d97e009a4d3f4693b3e5afeca161e0b8dca863691ac7364ff9bdbef4820134278bdb1287da0da96987f3028e000bd93145b071a3582fb980d2d11b57b5a7f063523dc6dc418188916a990f023b01ec20d1ab4d1a31d2be8c791276c027eebaebfc6e8c83154e4e34618e63e71bf851bd8091b8e20f6aacbf9031069ea5ba6e9fb3574ab74a3c760d72340d2004040516f60e1c4fe96b7b61dc0a665013c2259e69954130e24a600814d502fccf0656d7d697d818afd17ab08558deeb858a242f58851649c3644b12144d55cd3cbec6a5cf4a6098aefcdbcd0bb6df5252a9a5e5faf3e9695d7cebf410327438924e63f3670631bdd0e432434e0959037c2f8a085447e563b3bc141bfc14b3d95f66cc0cf1ac0f1d7658426e743742504d482462496d63d8c567a5ca3473b1ae488ac243d002540ca44d1c8817fd53b41231731fbd8f1c36eca96727aa78b6c256dc5182638b420e7787dbc626d2b03493a9d27ff280718d0f376bbf4ac6e576101885938487dd9a3be01d7b92e1559aa9e2c2d53f02f91cbfa691a0009618a2297326c819b2c350636d6938ef01404a53c90b4a13d05a548a402c87d8b4d4aeed19f0405989cadf35f52538fb08f31e29f30a1f2e17a226628b4c57a0fcece4bb0caca823db2ea24306f25a54cb41831502f5d94c510dfb8c1313ba0063b85282bc10edbb50401ddc3b3a3b78799412f20d3af55ff7e300c06d37a8469944c8c79f4187411f24dae4ea3736ce0ce41049476a877264d28e853add467063c518011a45e89baa3d09ebb847059b9a0c68148071253fd7a0844b3bc830f27b3a937e3ac187cd8e8f3d9b3fc559d6105d540dacfd4c69bdce8180a8ef7211e3c2731cf0a6ea0d29f3df8ce2089551465a32a1fec8e9724caf9f6b5960f9729b56d51c26c0e3e64321589bd428e8c7338b346e4ac22bb360fcb9b89cca0be226ba870e6c32146304991433a06a222bba8e49882772dfe9541997fcfaff30468faadef75533e3845f7cdc7970e458855567e68bbd289b5349ab79cd19567fc0baa93894b2d18c4188e445c66d1d323de802cb1a11a251acd0d9498e2dd446fc4d0c3032b15ff8c1301fa474c37f7a646c9fb12883b3450af86a2132bc6de7f53fb7c01f90797ee618ec40642e1386c2a33ae710d2ed616c613b77393be485e45a6bd065a55ba4b0bd7febacafa65477fafc0855ce0cb18124666cae0b747b40243f07f6c4b6b26e82563d7528a60fb4158d90d82cb7f13fa94e5c4c7a7f3fbf7ee5ca1bb43f4aefae641446f00a160fe3a54671802e122e632010f52208b5529271b60780c328eaa1e11dcba426f9abef358139aadad7193bcbaf2216e9a8679489d75c6ccb5766e7355a5144c388eeb661d3a1b138623075a7ee66800709e73bbcb430d2ba48830cd610c225f002e4bba40621c78b67dbce47bb925eb7d9258d548bb593c4a2cb1d0d1034e53d4e79f0979b90b795defe507eaf8a6c6f9347a97a86c1fa5035042ac1abacba4341db92d34b8b80cd509ad49feb9c9bd6e4bab7db0086288d704be611d42ec8b1db752cb0b84fcb012f0080f68cbd8b8fd6c008c7aaab58195b592725b2bdf0faa4fec9a63ba0bd26d171deac847696d21ebfab550aca17c8c3b5f64982c980c5cb230a0ffa18bd6e545e63dbe763b1f8a748a9676e61565b1e0870538a29d9a8bfa545879e246441969c4b8935e6614b1bdbf9b2d45e388413cfaea829489c4b3c8b83462c5c2e2b6d4d35aa9e8bc5baa7436fa5e8a9e88637e4022c06f06bc5106e48834a5f8b07400c5ebbec14d4817d86add15679174dc482a01463ad6387765bc3b23d5dac8b73578e6f88714638316d26c6c540c8eee972d4a22acd04483ed7d9ece3c62fb5b26596004fda624c29ea860bb85ebc04677dd4b37b3e9befb1c8bad691081b349b2695c2d6ccf483824651b259604bebfebaef1b5710226c97dcf9e0803fcec037709fd757545eef2ebc770698043b3bb7fb08b120876bb88e4cd6d678dafb77e31f606a0dec0b4d71635500deb1650f55eca1baac3961b0a85cb556d80466369c2952ab1d9af45676986df38c404ab0304d4950f0e7469758ea3f4ecae494704e07f55ee1c0e5f74ed884d1a6020efddf5ada633448315490f69801958faffd25bc17e1d8e00702d1909e01b6e929a79626b5e0f25d62b3b4a41ecaace592cd78b07d17c818d903e64d50ff820dcf75528e8773b7f77c27c2feb6fd4937be255664ed8e79cdc1e4d50de3ad3a456356b6b550d66c17bf9a01a5cc836b08dd355efd7914c136a56b960754f56021268a710b282c072a260f5cc2c506b3d223465acaf051036f1962ca6ecd3b769c40233ea1388c8e27e6fbff44390389a7af60c7c71f252f993f11aab8b1e74818753eff1c0966c55a6fcf94ac237d3469258e753b08f6de14361d6ce50e58d27634ed840f71496cea0793b80a128414c4fbbfdde27ffa2897c94d63edf2fff9d097c4cb3bddeb5ef7bccff66824fd3b6d7fbe6ff34d9f83038ed2b5c8e81d8156c1687d78e18db2f055087f08043287082b4b061b5bd211d224a1d4eedd93f867b88c25fff88dccf87bdc74cc690d08fa162d9b86ab650cd8a6ae295561a750aa2a13070de131b7a55913ec3dff0f4188bf5748954bea5c7e4eacf81bc17a6a184ac632bea9420901a2d108d1a380d6cbfae33d0342e108d1928512e9f7469a0695c20180df06dcf58d73d1080327a3e3291d77ed418a963466fabe6b6d758370e63f6fe3dec28ad829ebac279bd8c6d838de95e91f98e020ecee5719a5246ba97b8f7572dae58fd0b6993affcdd6a43e983f20c54533237d132dc12ee4f8c8838502e70080346b24d697d433ef0b55d11624f2e4cb651eab2c1fc2896ef6ec0010ba55a8e820383439e3c5f42c6a43dd4362c87a3cf1e50bd6bcb3e9194e6f164c1642129d684a497584ad71ba09d1dd0635a7260ee724078c2b558bc5be82d2b4fbd86175717a993dd06fd12db08085ed7467c0bcd872cdadab301ff6e27b9101f2a6ff4d8ba8704312e2095f504569e87822476261e61e809778629cdb5dc55219a2946ea6374db0376b3f2904cc5dd2b501d07ab80087841334518dfc75f985c4a834ba4dd6e863e4345e3ed98341e5fa42f858b3294dcb80bad954b8aa8c0fe13c8c8f704239f5d4df293ea6c28240240bc637b9070d9bc2d7d02e32cd697d1d8521d3d2e34ce46e7003c6ab88e7a6af2a4021ee0c984c62ed3b927ffbfb26e8c8d6971193d2f1c5c01adc17e631cb359c73015735a3eae6f2c519198ad9f42f9a1217a964898cf6f7ce9c45534497db944d5e99358580c2d5b566e2cec050a3c48bbb12c1a8ca3e9235e13d1444a80031b9d362b287a3143e6c86eeff4eebf207160a949dfbf273bb6a794cc701077326da9c3e2fbc208d63348daea79e08a8a040b10d38cc7da4ac492b6e893325bc6b0d534dcc3a4a6188c6eb580b53f505fdd798893acffd0b1deab8767c53cda342641ebf82986382ab531eeb875fac03c1e8f4418d170eeaa7df3247727f04c7c3dfb3598a3c33e50ded0bfeeda9c0b0f87a0bf7d06ef03c474ba2048c0a2353b7c85b00ea7937bfc48d442c7b85f3e65037fcf2415c7d38d447983fde0323c115b45020b2800c2f1b14df26f9f93c250f0fee7c367c9fa13984509933d653f685a040d3ebbd8f0f91e6ab89c003b579254c758270fb2e7984c0d1c0239059a8e74dfadef16ef2864d250b6d7e33c902b11491d77ca0ae35b33e0a07762ad06037a1244103429a3c19c023c42c5c0412101076d6988d04ae24d6129fc7301cb7021fda98d6733f9d6d968d4ef1c53554ed63959b5c0dbbc4f4430a9a1a288bfd6a98319824ea338414b42bebb84a2a42dedba3793234f828336d83c8c991d386084a60598a4ef671d412e5343367a5daadbf539272c0c67f323e6aab78d833b378144ccce6264d4ba0f5f65343588ad626f51f6360087e93cc8e82986456f20173ab4fe75cfe6ee70c9178ed24c21561a7415c89cecbf6e3d16a51063a45aef0cf534dc7ce32ac8de356cc528a5aa0fb6ee16542842adb549669c141a50ab2d4008777827f1cb8a4c106e3e4a37eac07689cc40192e910d71d5d96b911abb75033b6b91a8ad824ce7c6331af35805076d3fb0aeb20da0432deaa61779b3fa0d5eada633e1ab410bc4e8ac05a809e4e4b90f3005ac1fc6e0d34e9154ebb6bfa300022b28ed9be72e3105bc3640b19595810076f3aab0314817979526feb6eebf7868aca4ad49b1649a85f228a1a7804f22551fe9da5510095888600f7d6abc7303420398f1682242ce20f2b7dfbffd5eb6c215bafa7ba553f71866b0eb4e47d257a1810166960450917f769c6582443126c0241185f7372e1cdb77ee061c6c2ac43a0344b65f6387e83084712137e20fc2a233360ddb18b0cfd10acbcd5ad41080985d9b5cab7965c2ab27a3f0529d9dd6d07c1d6bf0d0be707b5d69c683b6eeb6e6a9e8515e42a6efb75a62fab2361f235fde9080fc7d07895a7d65edf77d9f1fe52ce12c9de1a0570ad7716088da90b9fe7a8527999325dc9bfaa9078197efa95af252bd39ef924e96d1ca8e0fe7cff1d12270d396d09d1b160e9560181973cc70561c65eb6cf8ffe3ad236ee7061c4f02cc90089e10906300fe4c173cca890436a8ae6899cb0eeaa6c99a0fab25716e367ea814df9f2ab2ca5abadf652d70cc591416564165e9860da457d370fbe2f69d219ba14f6d74e2c4cb42892c0853e1dbb028cb49694f4f633eebfc65a14fd4374d5a34733e288e3ab314e922e33104f9bce6d137edf119de8ad5fe4bfcc22b3dbe2d78f515b9f098a768d2c032bb5041026579e46e422dd6640d27d802f2b92bc125b9df5b2bb9fa36a640290f1d4caf2fac484cc06236dd14edc48ae1c028f7a0513cb8c233a0b153021607eaca4c94290d816884008f728fa85c6a321b595b8b4fee7701465e0a887b34ce401ba93d93866cd9ba41c12c99b1ad32d7919df403ee2cd9e4d40491a4c55eefaf5108299b0ccba5c24e16eff6cd88c545eb8166c8374c73588d8104aef40806354900dbbf26c25ab866665f424995a0fd5cc1b83ea7ed4b9c24426875f90e66e8763a71a01e15c60261a70648579dc3bc55228bc8c31903350a34c1fb31e389824bdde9e6fd45e876931f54e988aadb434e8553a6fec51d1d14df848f5b39525bbffeb45ff408293a6fe090607702248804fb3b8400f2ea2ca89af7211fcae41bf2676c57a254770fc9db0cfdb4551fd382cc2628738f898aca16996fbcc76380bfa17438fc9bbe21123c81d5e0fef80c11398cf91c94905d6a8a2a1ded801294f22383fff60e2556c549845dd1848b96539a212f87941a02d6e15c16f6ebf063f91ba840c4372152220b171057ca18ac6e01805915087965ec6c621196193a9a1e285012e516de7da1591166c516f4d79e720d1fb2578fcf25941304843cec79647009dfafdcd3be4d29b1b2ea47c4b8181d8a641f01297f052c66c56f9dc7a0ae8a8547dd9efbf6ad1c9f356986143b7ed6504582b7010423805e114f7142bf1351a616184518425104a67591dc9a5b04de73d42d29021b34230f542f38442d10a1fd4b8b9c080290bb801f7a4689d90cd6d851aebd95457c39e807a13caf6ded826a85a815ddbec8100fc9228e3ee8588126510eaf84bad0015083c719c3956636e2d921b4f2df56f48b029de0b48ac5b236228fe083e031fe0f925b77bd26c6a6053d0077be8c6658728823a3c2b925ad82a628f658fa5f44b3ef8b478b8d51c85152c9e2587ac7f9a7be4b101a953900cbe45b246b0e34cb082187843a752793cb002710253fb2f7aa45e8da27edcb3ddb313f7b094c9f54055d2b61f501351a5ab2aadb0b61cc80ef314ff4ab783a0ae16ec626265eadf8c5d71f89a7b2a936209dd8a3e5779903480b159f6ab0451580983fd2e747f12d1c00943483d09296da2690cd5bc545f152fbcc022861901a4da55b5c74bf25bb09feea946bb3bf94c7b7a96c5e554963638e40e088649163bd41b05988490131af951b80ac287f9aec86ca7a8faaaf0e88ee6ae090972ab5d8b3134aed521416ff4fd5d64871494439f1009b53c67e7f9181dbd68d047b05e64c0b5a89446eb7471a00abc6c6db9c0cd4f8c10b8d1fe93c5da0d2f0c9a59ce4f5bacb0a6840fa2a669ce1425d6b7f3577b6fdbdf667a98581e89de94c9cac8139eda2ef1d769421fc202d869e6edfbe74a0629158034f404437731a2144f4e03293122851c5941a96f9da3a57e59aaada82cf5ae4e13266b0db0b2704ce674a03cc99b0829bd35d88577db9ffa83b2d461955140624d8cee3723c44ec857eafce821e9d5585f6c414d0f90103cd1aac26e5b0b08c69d0068e5b59df96efb50fe27f9ea3aaf8ef4366f8c884d573b9e7dc819239dafc88a3911cd3294966490a404cfea21b803953fa5398b60796bbd86a070dbf95b2b32b75d43485c8c0b8e7cc67cc4d6ba6cc4ff5989701e00d5968a4856bcac825b8b51e9533d53f583990c3ebdbc70f0404a5c05fc604ab73b245eefc092023d94a49998255c4c165313985e2651f1d8cd9789b77cd9ec772ff2e047c46aa9819c5ab461cb052f1e04f5e8764b08dc882b70d2c90241527f53de8520f1b451df0a5de2b1ac3fc70d58d9de121a887ed0be9f4181363c6ed3d346a863cc3faec6b19e476d29e134b7485430c7bbec1949173d0dc1500c2c8df96c029a073ffbc2e9654b9785beb439d203f0b96abd44a63c551a581acc088cf3d18d8e010e72c18a0a8a238a865b74fd47940afeb524338775c8ad9f0bc41c15d5fc473d6331dc94462db9443c20eada804fa2dc046ab03c1d8a7472e72574f924b621099fcd72407464f46100c1aaf40ef39ae23eb92ce237d2ec071ae441c2c7f43e8cf9dc4cdfa1b16336c57a53d474cffb718e75941df08670953fbeb05dddf38fcc6432f2dff5ac80f736077746014b75f77f1188a117bc5c42a69d59b33f76761e6bd636ac5467b723eaddae97917767a9b4764afd5ae239f9d65e9a5ce982e6357564dc40fae15b92764b568aca946ec6685957f99140b098d067c8149877d329fed61511c444c684c67664ff362fa85b76b99fa97cd155e3e94e0ebcf8b22805e28cbdd8b3c8da2d47106e2cda75a6fae759f71e6b6c33dbae16a6f3c947123311dfa6717883eb2e38f24867825917cc6aab32c8a65881e22c628a0d51d2f6aa66897b70e4afa400389ad59c31c530f7153e5a69a6fe296117d8d9d60775020f4234d1be5f5fb11ad42fd944ada0be201e1c1f9d32d42c4c9c496f5d3e10afc540f02913590433038fff6f909f74ef61af49719e4e273a0cfed51127490d9c8831cf2e251ae621c9b3cc53e8192f969047d187e9225b439058ef584b3ec937d948644022d099179b862fcacdbf88cb4c385baec8d5470ae8a43e8b2d04f843cb829c759f153ff2f2962ce4b48d0ca6f8c3187436daaac1f8e763b3acb93c5d36b28ed82571a1dc23e18d28a75fbbd163c5ae51e9ab74736fede2b11c86ea792d4b8efef8d806ed0723ba9b9471a527a34ab574044a22ae4e5e23d36a213f221ae8f0f7c3993b5c3999c2b7b859e0386e4065195d9a1816ad0905950254be89e7b597604c60b385e1d40832e8b90d5782d6bd600de17050476f1492aaad58c33d5c34686b9fc297aef57d8d7a29affcd6801e8539980ab61dfc8cd6474e18ac0123c6794459210dc763d8d939564974fdc4e1e48de9e6a72059717bfdf76939bc52c2ce104d519e4be91691e8377f29ef45c7d4ffe8edd2d9673cb86eb25619bbc9e40df2d2dd678427283b4e69b4c43ac9c8905b8ba26113031ff381999cd07eef14b27e39cdb7c614bb39a7e1e2a7e831f4af16da5dd432424bf35aad0a28da8de7039738c735a39c71f0d3a02a754b3501563311d701d5224692fb40fd43466b81d53748a6f55ae58cd1165ae2016523d31a5a7242e8ed5a11f162ae0ebd0f94940eee4a909620d4154121c507b01d38a9093cbfc3d1324407c5eb5927f4b349ace513f906fa2af708d88f9dff4ce2e78e4fbd4a9d7ec3e2510e63eaa1227e220f6b9ecf112188bd95d213b5e19e6d9fdf6be208c62a989a31dce9c06cfe66e2db644597fca6763d424ee4896f7d69772295ce5413d14525c0b9929e7f8c39b3c742f8d2bc5796080dce4187661fbe67215c35a0a610fb03da8374b0ee051102b9a3b72ec66798750f3c7bd6c874da0eeb750085235baf4a645800c479a2347ac2db015a27e236485496027de882f1a8b8a32150bcc60280231d6522e1d20651d591f0d4cc1c0ce23bb0194a414f4186120d5bc6de6d8140c1e2a8b297624fe124c18056b02c346988291be19e089615a37e2a1b21b1ca01e6c1694345f49196f3dde3785cfd9876f8bd8b5566bf0dc185cd26a5b1faaa41d8e248cf76aa3ab737bd5d1d83182d7456b747cbead6c4494aaaf93848c4d7a80695d5ef46ce766a58fc5ab40b44f5ed5d46d83a280a243f1a93bd0938acb7c0a5b2d1e26770c65bfd2e2a149c30e4fed0ae649fc5fbe306a77fb243e2b01a399639eaa23922e23ea13990daa8922522c6c3e94ac880d8707606971fccdd7aff6bc30fec947b141df86c838c9c2392b68d30ac6d8d3bb8e0a86b319d0d2502ba6832efcc8a5600cd1586d31bcba10ee66080482abd4fb46ea4d3678279c018a7399db99f81233c39e5f0723cd8897ccf467c89260c326b9c41e77136af26a3b972cc61874bf8c4fa6cf02c11df424022a0f00776400c49c1b0ff8c630361b9e3067239549e250dad5be14361bc20242bc0c7886190eddaf7e3e3a9d63ce6734457035d02ca859beb04cd0841361c4b39a754a9d33376287d90647e625951b1eac1c5e64b520509b9dac34e5fd657322142fa119fe1e7eaa9dac1a44e9daf93cfee98e6398c1f4366f82c4899f97c3d4f1b8b627244bcf13c62399a1387a8bdb82c350258edcb5509b7db00ef35082bc9c1cebfcba4d5aa3ef6c79442b4ba809915b16bd346708c16a02a95b220dea6abd819ef265d0e83cef9ecfca8e5b6a6467c9b167d18524e2bbd37ae05fb700aeb5a7f74f5d9bda2f1d686b3a54a254926db7b17427f85de99e442425fd723a0d5f8359d604533f0bbf99a0762767fd89941ddd922539469150aa4753110acd9bd52c35672de04d291c1dd17a0c4d01086ac0afd947be46c289eb8480c0f9ce12f16b9ee07b50d135fb404270aab9db50cb3a73bbbc1df7f4b29db6b432dcb6e3c1b1d261f091e5eb83ad5fa6703565567b9143a2d0fcb79a1835801ca29764144a769da946381d69d642d555e26c064ece57ecd1d6c11f049a8215fe35545e453868bfb9158f693c03f737840bda1e72d66fbbababfc4a3b0280634c47279a531fed08030e50186f01b0d37590781ac41eedf8c50ed160954606e26334f8bb34f8afb3e953390b213b55e6c436f557667f7e87d0e53f09d0a51a2efe167df0b362f9ecf0b4587864b594739c805c61428054fc40127b4a89661979808036c7a8794548fcf7ba76c9d6dea1990fcc159177ce34ce0fa9ec2b6598cfbb2c8325a2c49321714be67ace643539e70efa3998992c2f235c4de5c6e6be17dc873345f5ab9f75d6891de7dea3e3c7d89dd2d13d6d03ddf6ee1fd6763cb8a4014623ca256af7e7f9dcaecaa487e82cf163191d5d54322ac0673119351ca4380ea6d909dcdc5e6953246b0bcdae7a38a2ccb5ecba7521fc310656e6359d155cd6d976689036dad860a40a57f2be2c6e1928871182acb859667c3edc337d3a1a25ffb5f5f0a732c4a2f0adeded62abe871816273726c26d89226a3fe4c612ba633c329a9f491ca92faacfbe3d5a5d399a7e0dbc03af97d0408725fe74458e2a2db05d05c96b03e33d064ee358ba03e3df399962640acb927cfdeffe993716d7e125269755ab1bf500ae9624dd1c881bd8b74bf0b0c49959460af1adc7da2206d70dd8dfdac74415e3fd1011d9dae8b89ff0e04ff1344b303aad860aa2d248b78749548d467b78509e4f92c3e61a2480a36486c41fc9c3491cf10418d6fc9e82c97ea2e9819bd63c79b0614c82a4c1ff79746ceb4b65fea2baa6f7686a7b9389dd0ec2647332a50de73ac11c459aea451b949579cef6fe9af50ff37c36fc8f61ab484d6907c34fb994e3f918e54264e03064761fa1e317abd33d2b74684a310fa876e97630a6667f9e23ea211e4b1c6a494d5a74aee9a7bba607d657d74f96b735e3b0f5e21f920bbffe78866833c079e538ed7f96624d4bf98b94a9c9b549321ebf313e745555bb4d53b4e7cb48fee313b72e64b6acd240d2277fa2e695b538e76ce6cfbcddd61ffec232977ccbabe80df1c229ce2189856afee5741cd37dc6b6df01599f1ae6fb789f14e9ea2b3871271f96141b69cf7aa061dcc0d2b46eb76eb46e71980f9f0d53484a1fe71f77dbc9a09476abec4e3ca089196f10850298cb63502ae7878718503bb50bac7ce8586a7ce6e712e63f225a52cb6e6012da9a3aa43b4c30ad819c30d789abcb9177a902bc9a40c9c079ce4db71c2dc429f3196468a3a7f13570ac5b7e7d63cb1992d602c7822ce274d23c17de3656245b06c6e4733f265ed5f361a9431d276f45f8dfa79953be544377398733a8b3e8f2904452a6b159cc54082a3907786326d1eb864b0bc618e5ba79ea8df84e37a42ca9aaad10ee11249b1a759817247da7351d3c9c7b640560c619f23b30ff08e68b9b18ae2d94053f5e9309478f00a222c973ec9279ab53c7bdc16eecb89f53e3aa9cb38444403ef52d35c69be5a828417190d2fc315e30299b6991193736754659db25f16763ed5e8c37b0c0ceff456c5570a2a578741565b825d17486694306f2b6848069c7f3c19ffae0ae87664bb0bc9c22c23257741b33c5b8186fa8a03125861777e034ddb2e8b2424f73cc9725b118dcfb0065ab64f47061b7d7f45323b8d55aaf60e193cce5c104249b33513480b0f65bb87803a4131f839917c738295de37cfa3b1548e584f4198c055ecf23b2c837321d9c861d1ec439d0a0b9b85d8a268251248f1c329dccbc1c2f0272abc01f145e784d1ac05c1461688a5e2ca8a14ae494fbd187f847c62446fa52a20c97be9415c4beaa69a7b8d17f145fceb25db941fa401173bf618cfa47366b00249b193202eab9f1e80339d2159859333634f1c1a667265a6653b75356c8cb6f4400a6b884e60ec1dafb7aaa79f09320b3053e8d3559b0d68e7cc4d785febb8c0cf02eb979aac9e07f12566fcc2a9cc8423c1bb77f95c4af60bb1fe945222d1207a64a44c07bf8561c7122faf80dc0c12d1dcd9d99beb7b2cf80dce237424df2f13e63f1671430d224003877c21cc7b4e25e5501c36fa1d11056b5b866a958f3b9e20b43f03d5047c11547958962f6e96447e5d1308d720fa774ff67314f8cf7c973667e5c673fa08f6960d3f243d74f777ad2036253e2d492ade5322851df4407180a00189368b1b858417c7207a728befefdae6bed13226e41506232d28e1bfceeefa4949b4dadb720cc85424259ad5e858d8f267c43c23c660fdfe4566745b6addb15be8c03240ccf353dcf0fabf7b7947200e9a52dd02fec5a454a152f00b10e24aa89810ea6707ee057d41c883a5893dc933a704e1134cfdb2768f3131234582a2a0975357c0e1a5c058a6114e0ae5b06ae7c1267ec5da3da86ff8ac861ccc7791ae7cd5fba1fea287c2137456a51030fd60f62db750358c84bb708195afb69116ed09ae13f963bccda18a2dbc52403ee6fd48f4f3403619a30305de273ee783dcf033d69b5d2daa58fb56832d3f28ac9d869357489602e0e52dc5804bc5dc684abd43efd682e71759858a0020c05c8b5188538817cba910d721867af7dde666e9a1b6209a54fb5d8c3f4656a96c96ae3830ef2a2c41b8da17e75e60a8745f52bc5934eeff30685508be9ac185c7d9c4979de4b33cfb266993eb579f21eaa0018725473368a422f0272d8f090c677c8099493c16915ab8f45e7f88370afa20bc714c21b6d028e51b34b3607ebbb00005c5d4157a7b1f4908a9487937e2de75b33805eeb6817b144d4c3b9626f313cacff3f7971a439a41df490b29e3ecb84461b3b8fed352aef0880cb1b16cafd3d618d5e63fa4f09e7cab2f5399cdabadaee92e29e144bb0b62416b958cc0ac4dfa72dfcac18c97cbb75069d0f38149f81a7be1a98a713ab792b4eaacdbc7d6ecfd19d6f722386246f1e08d84b97fe53724865afd5408f2248b721480a9663fa88a55418b77df3e1cf26d4f9cc126fbc937992084745797e5a44d326d393dae0e221bb09a19c4aa23cf8a30a8d30b54e4fdf678cb82df834773af527fdeaa13df84fa2308fd1196985623f755911e1e0853ea2d9abd7d469d82a8cb63836592715f5d8520755b9832430a508fb476aca2d9da8c1bb0c30fa3981b39e7834a456e13de0f19d37c91a241125f35a34077f4b328638098294d7303092435529df441595886c160ff2f558b5e0c342b514f32736a6079c11eaec23e986f50ec9ac4d70f91a71c5587758089e17ca857412b5832f899641d6aedf3209cba44b3082a7ed574b3a5dfbf5369ba002554fdcdffc5b1f1507e686fe3d06d6271f780781a386f472400fdd5abbe9ba9747e9322c562b18333406326806f2dd31636407a19246d2f219e56435c7a72bd52d4ce3aef8002f82e000e5ba7694a17744ace5c107913036adfe8237d697354714d5519b848ef6e39b13a9886f333fc674d44ca5f130d57c2798dd83a30c5f3336d995cfeb58a4af7570a735fea9d66e846693ea2c75a102b63a0ae7b953c51cadb05fcabdf84d4520b692a74b840d10b099f327fd283559775c30cdb82fa93f2762f95ab04584ed72f7d829971a3cb955de0a06046020828b9d32d6de7130ba5068031eb9f055ecb91fee8c58eed8e9f9cb88356450c620ae8eda543c53666c267d839389c32b7caf1e9dff661cd3b842b6e8b64af819de752954e643369b669861a0d29bc5d8242a9caa64d9ffe1bea94687fb2169b6d3a82219d95652f8e78f95ac2b41a57b2c9a5512e7322b526640345b9200ee8f54ea0a91278ab849e943ebb390e30b95f1c6ab3f0c3a9eb7df16b3d6d570e1f99c7e10ec65fb2edf08a97cfe6acfdf911ce92127d0eb97d7050100cbc2a853b91281f56da2a38b8ea14f4f9fc3439ede7d2c1dfec4cc9e5320c4ef15aa351b02bdca1831248d2042bff9656cd2dcd4e619fd11a66586a88d9fc3447f437094efaba76577e1eab4e17c4a56860c9f989925b8d6e052f294a2c8e4a0d4bb535b82aaebd60e5f55279a1d314ad4b18d9b10387bc3694eba406d60f2085258c387575991f709dfba278ec8db674d2e9ea12fc2bd0c30d742977bfbee9920c24e452f4d45169ad7996779d2b2a956a3caa932b94452f3894a59da1c15c3bcf77ec6353ca6e3381eb121492cabe9156b8586d8b5582b33bc53b03c8c6821626f0185873fbc58bbdfb31072a9e4f410fe1dd8d542f735eadd3981eaf29d6ac3dd1323010c1b94e258d27c215c088933f81352bac5decdbafeb3abb243100f18c609e140d776682e93b57efc5db13c1df64417fe89c6b7704b08fb51eeaaf66830c823e02938845456ec538a1c0ebf8f91e6fb31596c57c49140c6370d7c153563b69f935d05ac705b60ffac7108d45f33ba436f4beb6f436563d80341fcfc3cac5e266cf8d564b9a1d1c897947115b09968a23d4e30b707d0f195c3060d6b71d4b367bfb7656258a767185f9f7f69d5bd825384d7132cb5b87fbfe28bffae397fe5c4ef46051e00dd07a9b72cd06f573dacc0b426b42981049a287dd7bd0b1d41988cc027699e6ab550e8fcb445759a09890773e4fc8541236f1bdeedcc7932b8944e19235aa3c65956fbcc78ec85c6b0fafc92d61fab74eb0b2d2ddce333f4289e91791a55070cb537a64b55f53cd558af5dcce24ae30b68b73e6429842836f4b35f9eec1f0df48beeb6fdf5ff79508e830683c8007c230a51dcbce0ce2c22291080850ae5a9a1349436b0ee3ecfea689bd1b3c16993fafe59a850a2c034f02a9a71f66873529b74ca318c8834986125f6795bff498e8a91e726fff3ef0aa3ef3b5fd028d23ccbf3e07a125f8537812b694e828edfd95878b0606db73339a4979422b5d64f42a01a68e1b8a16641203f01443b33fbac2a062a9f0b65270cb3e106c84f502b823cbaec2dc26ed0a8b698afb39c685ecea60e51c35794af2092ef085c1df74ccc3cfe188c4466b7a37e0f1d310e1b438e10b2c7eac248594d3a8adf9c1a68796375ce682d452d32f7091dbdad105a5581c8364013d542e0b3a0e528987c3c8d1d8fdb4693f216eb4ffd5db84e4be09f79b95f1d6bf87b529fbeb3d1327ee4560823ff7fa6c4b1202ba1e4e4794ec472e7bac40a610c451814a77b2d950146816617410804133272a09902bdc75aca7378202e100fe28eedcf19d1d13477206a85dec5cb72b95bea22189ce04608a39f02a6b45dfd3e68b8532076d7e7f6a90de6fbb242999b890612b057398dc0271a9d0be9c0ef2c88129d0fdfaecaa383f197bd731ba37ea5738ced3abe820f9e804b1b588368dbed309e1a62c0a5a3c71af07cc2039b7f183fe40351d6d82a0785e085ad31593ef885a53358b990c4cc2c650462a05a3e963a63ff2b5decd164022e07046205611a485f3704ad821078410afa13af1205963767094a23233c585473f1002b79e8c1f22d12f241c48078826c36a492b0438a81c414541668aabc1c3066eb36da4a77bf7234336dddc5402a03bf0fe3503de1faacbbedc358ce719212712d5dae67c294508a4cf7092648e738be41a9c4a48eda2e4f386c187c851255390630029e21a92caad7d54c9cf048138355a38408d1397e6eb4e35e259c95e271b815d682e7f2afe741052d1b70c0876809277e067c62211a0f3a2921b2312e4bcf4f7aa44d0b57ec3e1c8fdad372e13a94aa9366747c52554bf7427f72c586b8191145480a7cbafa2c47862783a41a0fb2ccb1f0036d0e9faf5e5631a35668beab75c36fd5d6e827f05a6a1e5c714f3c493870194d35993218a55790d5af757520d484c97bd9640bd7422bb1d42cb2bbace5c6f5fda69840d95b4d4e097e657fcded22a9d297fd92507d8c0ec7cd9371b704133acd1db8bc6c3e1ab79f15e985a12349ed035bd02ea14fb539fe51233565156a9bd6df98626cedfbc934b9257554472cb75e6b35c986a20b89d41e4039d0f2615345015fd335187337d48d43a2b32094087f2466356aa54b0099104f0a95f92591cae96be5761dacc8c28909ea7b0296ee4dd651d9557177c6fbac2b549f9904303f652d8e808b85051b3078e68c524dcb64f4b61805f697948f24076667af59af26696e0e4cb59581497f9828fe61a48e547c60f782a4fc67e84fc9ac9784d42beb0881345060d544fe5fc3cb0d2846e732b3240caffcc60ea17429cfc9277e6515170afc4b9ac4b4f9c93f81b8e556af197bd5bb16186b690bc9f1cba7d2a683ea5deab15d10ba4d09c66fbea91a03c2242a4e1a0733c2e029695a8fd10b18ae74ef255c72a1a9ee9fd161d3677f3bc4a2ce9afb6233e0b9f9aa8b98325776be92498eebf9b2e4e443bca3af48c4c57ec280a68872e52edc9e76caffbc2d55b7033161617880cebb31241eeac80211643066b0cc04e1724e48dbb60d545db168a55e778f902971ec6de93db1eb56600532ac31f28fe4133ee902d2a88c2947907d036080d4da0d35ec811ea84148a98645f7ff9716672dae14c0c0561d895b5c17f13ca97eb0120987c4c9dc08d0e10bc86194f71e7ff8b5df8b82253d08de99f58bde72b765908d4ba5cf493dc15783cc46e435e4bd14a02de3bb1425861674c903a3e884e8d703294f68ed54e7e3ea95038b2cde679e91d7b25c33007a41acaf75ea2f0dd5f75ff8393fdfde6bbe300d66a95618ee036f975ac47e8c9f4313544e0f3d2a4287b82b1473368304d21d1914f5a92e2a94a3382fb9d29008950c7f70a5106f36c910d7d2acda31404a656d92f8186f27fea35910a43e22fb2377254bef888ad473091d7b51150ac99efba4b1b6c0c7889c86c27c9cead524481344710a20e5beb252cc380479677690bd9d633141a8dcb3b9285304f43d390b62e37018900e3b512871303f60e8d9cd1fb6c054b902417934e01f3660b0cc8886ceeba4d13cd7b6bc7951553ae520068b110bc1cce3711aeb01750c8c1f31de6ac7280320135f0fb5197c40f603a46648e7325d45e3df7e21cfda9d63226d3d5cc2d810556150fcd89d32747e1913d0237eafa57ab7646a15e1d0f7e928f19cdabaf10c089226bf661f41da524553c29379b763949f8585561392d604de93a0194701a7ad4824779aab316b452c3d61245832b28f67b7116d684d5ee495a49b4676028abb3cd18a0d1a44946aa0764ab0d592f2967c8090f4c7b3ea737598e7771a59d6fb5527eede2bbccab8231304b8eece0870966648aae87450c12a457ca021e76a7c0419551e9a6da4b6cb07fa53cebfab1d381b577207849e829fc85b26aef139d3f02ff6a55276b53ff6826125340c15fddf869757b7585e72b217e43af610c81729876da5020a81615e65d4b392e02228d2d498c301d7be3350919efc6283a51ec580136abd7373844676835c2acb575b4de25ea6cbc245bb31f394a93e39cb53c4cb9312431f58c4b12cf24b3e399844f02935c1b5cf0747ef74256084628427e5dacd6a29898b1d746b20bdb7e6cbd32a00d5f02139d3b679195c22f74a63dae4774b4def34752af12978848b33aa92f47632375db472f05732adfbb2ae4520e6246081dc2c79275daee00d32d8bb1176d7ddff20700b2ce913e8df552e4c8610ccaf03bcc734c04bc3888264fddad1985a9cf389d56df72e3335e08b265773dbb273742697d6ca04f3c1e7411aebe8d56b428ef06e3324026ef17147e11dc17f68635cde19596806996c501f4b60b3f6fcdf898db9086ccbcc8e37d87040534d3ce3276956c96184a04f2026327c26ac9b083ad4fa37749affce8c434f65de20c83ef62926750628674c9c2d8d32fc49e3bb0461f0486580582636ca890fa84f202e097926584d4e496a7f3da8f5a34dfa565df0236b5a8aaa117f773d2dc3899d9382e9bf5cf07c429431fc1392ff3460ea28353cf8b53ae210911b72b0f70ae623fcfda213b4d5ec7de833dd1b9c8af60948a422b7a9c1ced2a3151c3a4ecf657f91ecf41bf5a825611c50f691d1ad49444dc6a8496de21b2568062f6dc6f7dc9662eb9d3302d316282b22483012d595be867f40f69feb582b32339b2b97590a509722cee918e409e8daa367d5851d12e23264d3f6d073d81c25bca2c2f94993046b571b3a636ca93cee0db21a27a90836831d103b22f772a06ff5b61e4e074f00c0cfb8fb15f108585960cff591eb750f91365622f60a6788b460b253117e2a5055228272f06c436e470d56ba961789dbd2ace13c127e3ab9e600728ed4acc51c0e04ac2dce67312a7cc8903b5c0eb3da16b12a7f1dc40d53f6242491a940f0990cf035cf4113c12edeaa9533426627fa8207a78af9751f09e4c9c6fd8bb039bff2382a027b890b7ca3dae0ed1755c202088e88e1ce8384a66e6a9a4b46261e0de6f02b91e7e38a373aad6ec7dc9b08cd31b0bca184963eccfdc0f5f321c9a6760b933a73cc00cd59c16ef1b35506899fe830f02f00a2894948c36f934d6a7ce046558d0fc992182c9ef8322cf96d38da0e2cb9561306a7b8c48ac0f55a54d48aca0ef32a8dfbde94b278398bc645e2379dd76ac6981b2a4dcbb994c313d167d6468a27a8aec462031529d543d7be62aac314d036c2dc252b905344a8e278ee4ee181f3097f7edc64d289b148e67b3e3593ba59eb2adb7ed728d464a9cd06de9a410c2d2fc9bf31dd4897ebc9ead42ae4a8d201cbb50d207a7429acfa30347616bca4f497715a2523bbe2f1cc1ec0631885ee1352fd4e5885a511f85674a423598de4df2edc6b9e956f1ed77426b83825d444e461ec128b58d25176648b07f82e6269aade0b346da6781fde324a19c029e543fdc5382f8557046ec60be8e9fce64b36d20f9180f1ffb07bd2878f522628e6fe996ebd7cae35a713813d0b2a6e49b4b0d2fada5f9a6645342ac876b2aca5ea064fd13d9652b007a365370ff13f0bbbe7dd010fd7b294d091ef0de48e30193faf6d0c55e54d3b9adc1310ca826e64261755e4d5034e56a1f0d1aa13fe38c5af61105430fb29a09188e6bd23ab2b66819a5c20155f7d1fa011de8d6240e76808a8888ceac6e5bd67035e9711a26a117d67c5c14762f3f94c1e0ded36adadf680b3a5b28325331c9ffde477699d0f03dfb813176b8c4e7627959d1d877e474a738993f46137ba1a297b2534e75e4f908256ceb87792aff2a1f11ffe99ec0eff0e2aa0e4262715de4b79833d3eaf5226abc5846bd6cf061bfd9583b216cf248abaf1fd6070913a3f707ebb26ab86cb0b130871b2f616e8f5141378eaee594765b8cf649f80af3fab7be17aeeb753aa64ef8ec49cd32fc20dd5d1dd96c27d3cdbb581712da6fce8f987986f0deb441f3ee751339d439cfefff96560b11842f861d5354ef28079106adf7eb8088560e4a6b22074a849a588d5c75042203972e32b2da77bac9585fb23d7db17d42a57bc60b7d08054d953e64554de39a04b9c24d7945ef22d7cad9ddb5856b03aa400feed5aabf09d1bd60d06f089516383c311f7f78ac4c6acdb68c419e039a6150bec2e09046c2e4995f5bce65dd36209c3f4e2cc8054292f9f009e5f2288da554c560d8d024430d22d8b762a16d92cc457243442adf25d219a0fc89b78a293fdefeccc77ebd918629a947aceb601de8258fd35ac83b5fe228a0f328dad21b3e75efaade1081ec3b5a89e615f2489d72261356f43faeb0c0e02484623fae1d293cf55210752c0ddc40265ec0bf90613c142577b0c4e5faac573d715287bd2c21454401ceecb45722f1c144530374ea460de1e6fe0c3c3f2b3c9af1ecae2f2cbc3f5ecc10060a080a26bbeb23e1aed0ddea806df98cfb8fca05e9257e1139cbe7210e427d6af98b5ff0494bd4edffa01ce8d848efc1e34498b8a9e38e8c0d05e87fdecd368ef87d9845d2b0b34706cbd67c2331d22548aa410e88f2a7596d4e2df9d59d165b2bf4fa973386d8a6a7d15144cccc38a436554e0399a74a854e375d2faae9c987d99cb436e7da3ba0bba64821e390098571d8c2fa65af64681e570432bcee2003ca1c0b2d0115e87648948fd30b6ec6edc1c05bceb6d4154fca13da8b4df9208be4c1f33933efb2556f88715ff7facd00044defd8cb25a65c1abbeeb4656146fab35f054ce56ec50f1ba0c512c52aa02e5688c5069e12fd97d802895d459cdfa4c06c71552f75c1fef78f1b2e5911ec819a004ce2ad3c5548b49d52e09e9dba0907351bc4ba62911eff30bd56a95358cd3fa08f3e9201e85069855657eaf5834d5d52544248e08d387d6f75c58d4c02e2c021044fdacbe06eb5750c4fa1d18631af2d6ed49d79098774d4f6ce2a6e44d3d27709e05331b8de89c57a76cc1c0336b35a70cc4ccf58047d204dc7a456bfba0041eaa8e35992ece03f2982f97fd0c5d640d1c550c4f8b8ca071dab771364b32407a967741b9ad744972005361f29fad3d36a36e7b7f476bb8b6a6db48e02beba9fd4b2415b71216ce091ec825515da0251456ca44f4a2cfbc00edd6507a2f44890ddfa91d96c234117f87014388b171ada3bbec39e8109a96cb0a1bbd6c9e2bb6223b64762e0be0961b7e1805e82993ff9dd181f5d13e5ecf52d67a763cea9859603f27b97bc54ce62abdd9d1e18a107147be07c8b53ef2488c208ba23abd504c51ee8796b24ccda856bcdc24cd2a82524bf032d74b502d14588dd3b4ff3b7e1bba334393ed6984406714eb0a67192dbee2ba5783f6b2c20ccc7f4d0f6651059c548f99e65287da537d0a7ef056d5be81031ccc2229290053dd106232a517d40013143ed9c40a191dd8dcacdbcd7a85c0ff0342ec32f8e2f65a8ad45ae53bbe4500cb9ff783cee8bef7060d1c202354082fc8ef0e8fda0b09f29144210ab08386d30d85872c603442a19fcea13d58b25af1abc43e1440c2c1671eaee9c9b8fdfcfe78e896a0d3a08917826db2d815eaf9751caee6230466f25fe00ec3f7aa2ea033698f68b830e702c170068b51c3b80270ed68831eafe352a5bc4d5f0608808bf2300d4517f620976234e846852c629bf5c1f0e836280f836d336e0e674de2d8e953ca58ec744fa4ce0f6166a6519d8881c37d3ad4babda1f1f06dc81746a0ba281d85ff7815c1d35d417fc7a2997f8120d1df0ef67dd5ad22ad6668aec5ad5a2d240254a74faf79d9f90ddec61d0f426bb1828868af387edf13a3e87e0409092e6564f80370591b80a078a2479aa6024a7223417141dfe94ac6b0ba87632773762db9676db70b390f0a2df2f63501c39195c3ac463f1aaeb0e3c05111217505fe3d4077b33904d06fdf348aabef16d160fe25bec9db8b6501ae4cd50fc9d038904b932fba76e68eac89a9e675226da5bf35e16426a86ba2398cdb5df39730c6697d5b0062cc12f256bd78e83b295be22181ec09280621f256113204f8e34c49acb713f850b5649eeb55d9580aca660db1ce28743ca196f7b26afc5b7b6b6a67ed140e4713c37b3197010166d05a53d27f5ad16b86491753d3e2f391a48359c400a1fb9a7403735323392e539aaca2e5cebb545a5a32017433245e5de1248a1d6955e09edd3114a9a459482f356280aa2bb98fae329aa57f85ab969fc55c11798291d3cf59b994dc555557b5795c9b32ca1b9442d99c19210aeec001daa39d94893d6a1e29a831f34a613c8da73600255999931d4c7390e64ad457f9b602e980400572e39cd1b88e5c9f052fd930db7b13e5016718384b370cf7edd755ea75a73c736436a19d03215fca359f663c7af0f16818115b9033cecb7072ea4560f21de6d44a0ec1abd6d9f3af81404a78d6bb666b5912105d921fc65febcc9f272db2d30002b616d477eb18e714866b6e41890aee1f3b4061dfe4b3d23905a67a98a882833acf6261ae426d0b4344795e1748248bc3e89ad48af708e13caba08cdee3d3b94dbdface923b309005973861868e7c9295c34521e0906ad854e2a1419a4e5b10278da3345fe65ee728c81a61dd348e2e3604680285d2402085b52009a98e1d4e61f75e17eef53aaf407bff9bed5b07b891a7030d7eec01a3c3289d749a6adec6f78b930b702fbd3a8bb0c081cb2e11cefa88c0c0c3e7bf036aaa59cfe626990dfd3e85ecff37a0786e25060b91933a266451ca90d03c73b174c7f00641e2999a7034344cee08fa7d44043dc7db3676a00a5dd586c4e6fddaaef302db3694e511aaf3302ab443525587ead793bf474c7d3b6e4a3192351e83275319fa6cd042f962cf82aadfded240c0aa2dd9da2a41d60c8ae75441575a7d1e638a797aa512c20ec2acd380c3e77d321660ef8eacfa32a4514a28fb601e8f87f357eb7d82cd167c45c4969a3af2a0b6055d046d866c5d9ad8920eb90e64d7a2d4734713c0915f12444d9782e768aadebabe43373fc139fbd605c4dcc64ed934c2e1130b2ee25bba24fcdb1d1f5559a55543bec4c60fa68ca079ddc53e9dd7e4f447052dcefc61ac20f8c0da7de6dffd62328740522d9e5e7e8cfb119f86009c0a0c72dec3a757ca64c64495d9d16a1550a3d74bfac7aab8221bfb6163d45b0a53009fba81f259d09fcff4a9ee3f30b4303afd87a23789bf1a63ecaae08578d09190ba5f68de5efe8c920a922c40373c139ad601506b2f4bc60e2129b78826b17ac5d954de1ac14f899a603bcfa2ca18742b3d84a66e23da145b4a3c42ff5c773a62226c8d6a8caeb50a208908222b2f601ef3c37599a48fd505bab6b90436c5cb2189641dc542603de80c1bee938e68a0c28b48e6857c5daa05ce25c4c84b589b3137b280c1df8adb5f4417a497947f05217dc888f684f6873793d12caf19dbed38cc967e408b900d239694a982ec62b7abccaefeed39feeab13aff90550e70e6e5b97c2f664e8b09d49df95b3729ac67ae5086f03261c99ce9893e7b7c1972822bb50a06395a4319405ec18a632b53ba958dc67ede821bb0ab7d7b36afc78a9722eb3cf92ce6619701f55333e845b22969d4a896b8fc739ff61ab78b66242f3041400e8045eabfbc66ef5c4082b2c0990b31073c1332480aad092714d30d082c5ca6a74b9128c413c873726cd010ca3ad4dccd442fb8551afc2fae2ddb82a5622677245814dd6c37342af69c895da809f94c4db3ebe4232ee2198ff218b17b8fb9cfb36747aafddcc32a02d91ce5baac5fec92107d7f2a891baa738867a0987a92f8c2407c8f54d74a05e5310a2da05643be465f374e90f8324638d86e7aad04f8e78e58532eb0b1baa849183849482f33c2660a9de2f5e50b245b417ba2ea706a8ce83c55543fecfaa8816301d98f187670088c9d9566cacc2941a490b470867085ee2161786d1177d07f673e2c0a8651d868a4ba2b976e0f2f8dd06f2281c2291b6a92844ac498f929687965d36d896adb294ce9c643d0435c05979d56d159ada4a441f6101b69b8bdbc114102f63bac7ab7ae99d62f3237d4fc4fefa46e8e44b0f64c847cde1ccd209f135b8c391c4a441bd2c2813888f62b903e46e6d6ea1f835818cbef94196323d420f5a89ba871b54982115415735db870d14297cf51d844e6c059e8fe85d6edeb533217611da6b19487b3aa91d41a006447654a4eac0a0f286982eaa3f11d1ef4c4309ab532bdd7202a4fe76543a2c481d450055fceb279621e445a4324b4a30cb73bdc817d6e160dcd84ce914226b992d106e291267e4824eee77c0a9d465bcc8caddcacc50b1b2642c8448e5a8a0f826ee8300e2e0890ba7c84500c22a0380a81c759852983bc24683ab87be4814ef38e2ed55a3e00400c3b505dd96238d178af20e18b40a622bf6974d7a3a95c01157dff989f8d2e2c9e75312064cbb8963ef7f34ecc400730d482c3e4a9aea1ba95ac8dfd0a264c43694b03542e04e9034f85736c5a14a6741115453c244c33d87d9829f102fd9f2db3546e166b3fdb1ac0d4e7f49e0e7cdc3e97e9b0a9d2b968ebc04edb20bfba1da7b56e33d8a51345864119e91be84653069dd51201cf312abf895271e2a37311dfb3a9d16df93f669992c6dc4b208a62254e84a5b65377632bf89ff7083db0ff0c332e9064eaa61b39512645774d0e229333b48aa845b4f4eb8f93b8e69b360db8639100db2b50ea1d4b0c76b8b33c4b620cb528162665dbb716581c7759d6c9d2cd8d631b594a474a9e21bc65c048c9f6716cd4b9aaae4472329f49a7b9bf8c5b7dabb606cf6e47930f0df472f604a2a62e03118d07ddc31746b9aed06d79387de62e286343f00bb5fd56bb02373d25c62eb8b9cd082689b50bcd40aae1b9782406b789695b073c271d4ee38b94de5c7d178b8ffeff91d46eea483089603f37804bf83962e656543c2324689a71d63423f88fc2dca29e319045b90c6050f19b74da2b9845723bbf2924208d8b65874a13aab6805db757a398497e9a31ce5f011d3076e49c6a271ae7dd147418b061602753ca51e6a2f7f4e366c2ff4b992719d174bf3c21dc1b286800a6724b51b1097d1912d3ca829da55ea9850e2fc19aab11bd571fb743fed9285790b98402741110da9e3899321f0647c243d5cc2c7605286a4475108446b0d46c2aa985409b083117aaec0fad22d080401735358cd4554decc2d09f7640b8ca7a719db6b129f72d298b6381d24f5ec83e4e9e861300fb2d6c00f34fd4d78b619383106c3aa5093274bebdfc230b8255bf44c49c6952e1414594f66af4361cf24e1734a3e36553297e296496af7d8db57c5af27243b959d51f20c7eb4654a0ea2593852adb900b5c90c17d03aa8bd8e6b9954ed47653882157d694853eea42c8cf59535e1a81f7c0567c45441d5a9b4fdd7bcc2ffee32106a88bf1921d66970313ee34652c7b2db0c41fdeae14d3c0b93832d497bb572bc004cbccbbbe704d10f2c7018fbc2aa4b7ff151e6eebd78d87c010c88c11c2e799e9273ecebcd3b2b4d7eb3be350bc15a8ee8d648cc49115a99c775ad63349caca8e261316b716a94b99dcc9b00a72dcb9cd8448e470cf5d90e9c5a6883390eaa4bdd84baa353b949145c98e6ec8b979ae700ebb7c70b0a2f9a86cd3d94bc6188fe4719afcd7394afae850b8b8a57c1ed6ae883a520084b317667408b69296bf68a92c51e0b00910f11c1a066361ab7cc40d01d83cdcfbad8efa6400c7b2b7df9cb84b6049808a74d860d18fa8f347274977067aac57790114c6ab32bc74e8039b06ef158cc5ab529e8b57b9147575f9885846d0b365ebd0e54b3d60171f0bfe7ffc2445f5c610b8d00cbce00e62b43e14dc55d2a32d63f795746c0d9c84a09d19b0720b6522e490447cb42b4619c3fc12d125e0484836180364b16ef6dea3403c74d9f92d93028102134dbfecc0f17c46a4d6317c5cfa69ff395aa84bef9d4ca8e4a81fe5b9e387e8663083d098f69e666294620842b804e0b10d7e5f0fdb2c30e7b19c5d7ce9b0bd54f5f5c4ef25fd8e83d22dd6201cf5008a0957ef1610e8868eff4b34382ffd76a5ad167718a0970451fba13a108728a21e040e8ddb9159e2a4d66990f6de00e9f4a49da729bc994f32fd21ad4379c602b13edc5ece29cd88c161fb53e7b5bf221fbfd1544b408cdfb9b69738c0f444c38af947f04eef7224ae440e85ce81132ed20fd6a08b7e7c7a22d5d731145bc1af3cba061f15d7ae04e76aa435bbf6d5a20cd051450cb00d054926f86e832992c800f82a449879454bf11d111e40907e146d6827956a0d0b635c054e844b43b8b8155af0a47b7c1b92f9c177f588afa6a15e7f2c318bea4268bbc11af70604ec9be9f6165d827ad68d513cfdeb919beebfa6f2b92d8692d4abd2c7e1c109e6c2513504cbbb6cadb2dd4f45504b77f8fa26581d6a7f5d18250f8289d705dc1d8bc2d46c8c8b56a3b6ec5ba60bfe37b8cfdab5e744706f4fadc4814ebcca050dce2a784d8fcc6c994d4854f977394212aaa46f915d9825b331f921ec956e6e5a679d0b0e4a4a8e3b80ba385d93016fcb835f415f4ae21fa8685c8df21ccd8c4ad0d20dc4cebad442240f415d6839c597d10f8117d059e0533017288bd86258f90a03710924b1558970333af678dce4bf782cddc68e6c849612a8cf84d7ee199579676266a383fa729b69c1bb3fcc99e42506f55293193accfea1ca602199655b13b20cea5849144a48da0d2bb7bee180f0043134c7496603a65a4863ee49c0d77b724096229b309eb6101274202e2accda05f0c0c3b034b533330c6ce3b3149bec663e2ff0ae78e2264739ed3f79bd4c06cfc0f54cd25f655cc5b857b92be8cc846f037fc398a656843de93789529a672d354ef5053ca722c71a628119cdbc89539adbc011256f4ef4013751bf94622331b18755b188e32079e0059158dbf2fca29fa918ef5096a93e27ea1f9e7b9586fb0db0e4ce103fa71927c5a30523a4a1b557ba611d5181049f87b5d3578efeb15ed62555c45c95e739dc48d5d89b1a19346eee9649720ce20be71ddf7cecec471e3790bac553678cbb848f0a2321d57063ac65bad11fd0fea13d44d12222989482291bd690760084e07b0073203ca3f72503621eac4f840c0f5af379d7e867b4ecfa8437fe6d613874d1a6b4a8fcc7d55fa56383125ac63d7755dd5d0dbaf32f63663d6630880bbbaf247949a2e65159675adb56fb19bdc7f89497bcdbe0848763d92f65fe7eeb57addae6bd332eb177fd8d192f0b2d30b76e207f8e362e211f4b1711fe311d1043bc62b95abdcd33c8d5ee2499df89c0d2c184a1d4b7b9e3427b566fea491a86661463b9256423ac16234e0e803a683e455310fd8ea0f9fa828ca96ef590587e2cfeaa3ac2af0a5a2c71b04499ae9ef859d197961cbaf681bb1b634e92f866dfa6d03024aa53e6f67db7fbfb36ddb7e1b4a42b76cda4c992933fdd7ed4c675baddb5002bcaa3c0b226902acb7d62f86bdb5d6643299be6867d96fdbdffc75fb9a724f19f8b587d956b4b79c846e0dfb2cdffc25d998cdd56399fe35bf7e90846e5d61e79c55aa6acecf3dbab027dc39d20eb955b9cf97347b8e30aebbb95ba77c2eb54a1527f6c42e480487e4c79f28a0860c410d77ce96b342183d9797c72eacdcf4da2aab8fd10cb3f7fbea150e81e14f5da137037d78402f7e0761849ed72a6bf4aa2a75304429c03ef62d60df02b744ca92eb97d4c72e0da50e108c5eaa8a1504160a61bb7d485eeae89fba62c04203e0eda6674c8c7388e34044fa56ab351d0825c4e8b3aa755befd65b5e1c41ce96357abfb3054bfd59f875cb4737d473c884518694f51656cbb2f4071f47f4e1d5e73edcc1e8b6657dc51b96bb68647d9dd6b2acafea4c22ddc15495e538ea43fdc1b774a53f77aaa30a28a594d2ea734faf023a84d0e974a702858218ba6c8f12dfa330c07a9475fa8c925616ad547644e05aa5cec49565d518e3ccb9d785510cc32686490cc3302c6298631886c11a42955583449905b99910d72a8d6adad4344d46d7340de20bc3988d0eaf0b5bb7d6420cb3f6664259a669dbb64d6bc7971046cf9ac3abf9b6c19979c41056964bd5d0b48728c8cf77fc32b05d6f189b4c27df3dddcc8c27b42324ee1cc8d309e108899b833cf06b788c31c618b5bbfc19a7749f4b61be54b992f4c481982fb0d003949a77ce2b39135ed9baeb6c6c6c6c6ca2436d03521fbb00906a3acddc1e39a7ac8935355e032fed83e1749a99e1381a9a1a35f0c6aa689de130e73457484d07d40175b1734a4353a3464d4dd779363637372854aa67e762685574deb821a35f35375046ef6cf00dbe12dab0216dd88836bcda6442379910148ee3407983729c6843dec09104ae40638ef9835be24ef5ecd41c570e1682bd317f292171dbab23f3349d6d07e6c163c29b96f15c1e8b5d3c3cd5e27158a37d45fb5e871107077338703e5b08b5ce9183f32204f403c210421041870ecfe33a211d6ec70e1e3bdbcece8eb6b3b3b3b363632313ba2184c3a3f615ddb89109898f234772dfc91ca1fc5c177de7e0703964f41c392aed33a7391c73721cd29cf5a633a11c5cce0de17a16e48a0075441dae83e66428cc9d5474284f803d4f4767c70e1e3b3f42266421beaa954a553435654c792a958226ed2bd2e1e9e87411da910989bb0708f2b83c78ec509e29a3f3c0aa7d4b3c2f13e256277ed43a3b78ecf08c20a343c8b343661d3dc6e831422b7ff59bd0dda7363f222866f3d8b163270bc22313a23c3221d72828f8bb0e631129401581e107459e11a4ec4164fbc748d4790a0001c884049009c11eefa9da5734c2087284e8b0070a85d4beb1a3fe260081262c8c6e44c6996c77aa69a414d248e98c926a0de91473ce49941373a0110906d260a7f657043f3772a31d03f6f73d938804c03be0f8dfc78813638c313a86151cb2ed1923b7a5ac01f5e855abf5b562dc75305715dd441ec444d8092184dc7dea5cdcaa28a5d4a97b2c62d239e5c3af43f2ab6ac99773da9b1e2f289d93cea9ef3c83fec420c5bf0ef94f2d44df7277cb5d4f49abdc3306ab307f5214a8be728931a4958ba4f690fc1bebc72b095b0f777c5a7754e2db7591d4aef4d07c9737d88ffe0d135809dd951ea25aeaa1f83ec3d2972caa940942861bb24c1720e1e1080e2e3e40022f5d0081cb126362161d30e388279acc3082262418244a62e0a5035998a84103369c71045a31a241135f9e30592209337e904598264884e942c90c2906121b4c0cd185164590994195320a10850c252652c2fcd0c5bc922305195e6c20081f34d1032ae6480f61c0600b285ad0031371cb912d3e948004484835f8820c19908104298d1ea028230c2c494954073598628a256a80d2039931908821a3054300010aa32f35d030530922c8a032c4132c64446024c34042811684e940131fc22c9d012684230f0021cb18198ac082e6e0c8ac02c9092ef0618b3159a0e10130a81847ca78312687107421811552fcc0831eca50d9e28b333e3066098914400105151a7ca1e18b1c03893efa000f9618d3449829611cb1028e61860e70b86203165ba8902408c3064e0091a481249070010e8a8408c1126472c032441857c0e8c10f860801164258018510f308119668a207625809410c6e9853e091a9a50b078a30438a1f68918418f40bdc12850cb4b84203661c81c313934a1a5794a0063878f24412497c8104822993c609c68ce0cb1667502fb48031c313204ce0050751ba4092c2165d6cd081862852ac50a5235420819409242d30b1850a25c48069a265a6801a01c41622e822461844d01b1c992f80687c6982e54910aabc400772043f88c00654b4c00a0e5ba2e002890c63c268228827be58c2872d907c51c20834c290c18a3368a05e60900ca2f040822bccc0e1055a403252762042074fdca0c50cb309241698e2041fd042891b82b0c2060f38e38805629630a3075e680d669214e3e3012caa4841175e84d1046a11812aaca46183204ee00507ba1c296201106980b105193a5cc981d200491454ac30da010f1ae08013740631d00004305ca0000733a2805e661648341bcc20450b30a688810697b802314a3ce921cc0d42d02d47240f31474c3027619eecf81188f8e323c6075a01e365c7773137a72c29fa7895e84379b0e3fb951c36ddc1beb82cd378043525c09f2bf952f46ef08071fc3ff635f237778d370229c6ecbbebe6ed7e6642366f6176f3a7fca5f6e96f7296bf157676cadd679928b5b38f9910f69f27d5f8a4d3d1c912f87327950cb8c6bf67416abec607c90aa3973d8ef74eff383e6234faab1be37e467f7e0c247f6ee898e1682875362db5335dbddf6c31a569ab54e9d93769ff883ab08a8d118b91d43f5ec7bf7127db7abfb9827dc79bea3f586503f9d20fff127d28fd1ca3edc41dc7cc97c2c09f4cf912944792ab9faa554b4a86061a68f0bfe63412e44646bea05de92fc85a7de7db9e31188707f8835a64f6e3ba6ac5b8ebfe33b8af8d999023f1439628367d0c6a195221f14396a44d6b75749d4201eae931e14dcb2e10166310474675fb58f153138cdb6b6fb529b0efeddefb4375fb6aefb5d785b3bd32cc6a1b5c98fe946127d3fd7ab7ec3bc3aa1635acfd479fa86947f7de7befbc990d2e2c135a68616484e52f67635fa3cf497f7557bc4df0f49346e3199a65cf68d9333371e6bf222af42b38a33fece929fb36e5df387f38f6fd4dcb593ee2a9093108adb5461b5fa6ea4e297dabb2acaa48ce105007faa81fdfc6c6042f6c19718e990026ed6cdbf3e3c76967d3b2aba4b28ab01859b08108b344f73697aac9356832cc33d955057d4929a592beeb9ee8590f923a99f0965d8b51ea4facfcf564dde34fa24f7d8933b5397fecd9137f8252330aa5d78362a27814291d8a01dad388cbf9fe010155203cf9032552f4a9a46c229bc826724acf8532f341d18b3ef2639229e22cd0d81f8dae2a897434f6855536d4017fd5dbc7feabd087c7587f75d39aceb47fb00aa55a89dcf46da5844aecf38954cece0edeb4eca6a20d49db6e94e6bb1b98053f2adf649bef724d9e791aeeb5c76ff115b072fac32793ddeae55996657fbb1aefbad310d6e81aba66dbe38f3f61968d31bc7dbf313621faf8e3a4fdf2a7fef7fb7b47bad8b24a4846f5dd49f4e2e32ff873244792187c17945c3fd951cc4cbc5d2cccb9aeeba29452eb9a3e57a67f5dd7755d9665ed58182db263557fe5b96bbe95b66103feeac4138a65f1a665106e1d1d1cdda6bf0eebecf8f6b2177677f6ad738b9d1ccfc39b96edd8186d88db9b18d9d37f75c67f382fc1f4f4b993def8b7b75b166d599f3fddb608ecb2bfa2287b28fb216b6b96bdbdaeebd22eec56edf433f1badfeb2f7bfb417263d7ea0f76b96e3fbcdf4bf06d6b6233b7aca75996595d62972e996bf9da7a5d6da30b2875826203287655a16f3f888691b5828454a7acb08e38dbb9ed9d9bde5d7f443b1bff7d7b73c5827d63ec462f76545bfbd6d3fc153931e9aa318d556ce6b4693965d921eb33df9b56e464574af5e6dc78fde7f4f70259a0ce71883afe34436aed3b86d94c04bb14814a50c9a1d276a0ee081503fcb90f108d1d7d9cd817e4cf49fe489de851fa0d903f3fe857ab8ad3afc3315be2aad6f710faf5c3080c922a92dadf0ad8dbafd25fead2f067ef5bce74fe746cacba2e4d94b3aba34d8ba436d54377a691fd4d5dde4e4eb5d65afb35887dec83e2951fab3999f6d95c4e4311100c981e860c98be060762d24b1c88e9fd6bc41bccde94ddca4bdb2d7fddf79f8e1dbf7e50bc377b98ddec7abfd7fb755defba48ce1eb2bad3385a89dc4355d71b3d9ca26558014365432b631ced6e432b6178b095c43d4497ecfa18e68e25895b49c5f2f5156696c6dfc470ccc61ec3bece8de9fa7e6928a7d4fa44a20fce1ce74ddbb7b6be045e637c35a6ab43d7f9f0671ff5634d4718c40509680fa5cecf20cb940122e5f31fe265caec21a222ae89fca1c0102475744aeac82aedf7dc00266d7a85942c530648a6c542f7a3eba856bde97b0f8aa345c5b3735b6b71b6cdc9d976d3b2ebcd222cfa0e8a78d82da59f43297efa3350fe70dc9f3ecb50d9cf3c0472d24b5099462960db6666def4309cf457f73dfdf60d98d1a687d9111666fea497c02a75502b68da3b10fca8161c087e07f2612d053564c8fed5307a1d04a2d5e8ddaff9734ef4b0d4a9aaf937ce237eecf40001459d1f55918df10808298c5fa94fe833dfbfca775da55f5c8ae042c49e1f87442e457079b267915b5d8e31613552814dbfd60af557814df557813d047fd2eb3fa81dd312de2118e052041727732b997beaa8efe5520a5ba38f125a2bfdfdf6b1d0a36f037d6af4a27dfa1df4713d7d6bb3fc18f6f13be8436eecafdc411e0c67571fa1577dfc0cea602c75aabf740779aaf9357fdcd657578da315ee64dbcddbf53fe2d88ef9176dd75f7d0a03ecfb8ab8225225e697ce96f988efcf8fe60f196387434d9dcda638e83bfc0972273b48fed4a50fe370ff00d4891f97e24ffd751b092edd80392a9f1f595d58e8357f3dbbdebb2fb72fcebe39fbeeecfbd04b80dee36a9b4d99c6efd94353c61868918d3108ea55911d2058d29e32fbaa7ddc5cfb8b3dcdfe16524a7736f6f52bac6aec87b2afb6b705c06f59c11e33219f655b5dcf8454ffed6c0f8abe594b29a5d4dddd35f6d907492dfba699fe7636fdc135fee0bfd4c24cc80ef601d285ca9a73caf8517a9c310a71c2aededddd7166c09f57e9cf4a0404747a82458716649f69ee37cbde5ffe9c2a9abfd4dea6eb6ffbfb35b9060dd6327ab5ab65d909706b7a9e4cb46e5a6541e4cf24d5964f935465ca94a95ba45160d93ef6f7d29b7d633e5950fd97fa56d8b362d8c2ae6a55f267554d19b140daf32bfbf153a832feb9422985a8953f98a4e5e200891fb69450e290b976717dd79f7cfa36cbd7d72cf74df6dadb2b13d22eedf2781dd92ef616dbaedd58f6f5c8d5b2b77f3d561fbb6c9642d1ce78e2ae1f5f72ee9c007ec88203cd2ba41894b6f555cf18eceed42184103e9c4257fe9ec89db8932ddd0996f9d39a724a6e4ba93f4b3ef6d8755ddcf5f2edcc84a4f4cafa2a3e5137777443767029824b94524cd70594caf17672e4ad79a6653ae5ae6fe5c5393b2998049403276e3bef964d6ffaed31beb2148ab6696b307ad7d34a7bc8331fee9f3fafbd30fa571577974f63956d0352749154b1d839abdcaeb1733bcb72db78b1b86fb2bfd7da6bb32cfbbe70df6cafefb7d033a1cb923f6fb8bb7c1a7386e05029e38b0a0afd59299d4bd38bd8bcdc9f385d010da3870202fee6290575fc973adc45cb6e129654df924f89e694ef50cca5c3132cf214e914f1e33b3440922d9fce4dbf4ef74ca88a2f33d1dd3152ea35fac49f918a20ec0ef4240584b7c3e3e04c20993157aa16dc450a47f33635ffe10db91f1f38ffbd0f8eff727c6efce7f9d8f0f15f6a93f01fd01f798f12508ff2d13dd41f4dbe79541ec0771967d764283e5fc3fee4018c1166cb975c0daacb10cbb6e1aedb6578c5e608cc4a972115b562dc75ff51ee141809fcd5db6558a52607e049c841de4716f2353200fe270b3d4e4efd08f9c6f7641c2f808cfa2e432936978f709fa4ee1e6f6fe4111e47ee795416001553173813a2b12e244319244779030000a1548690077a3d2cea863702576f97e10b6480058d11f8abf8d264eebb0ca3e09ee66d6cb8e7381bee761926e54cc1dfecb20178e872016cba7499009c179adbe51fdb5e81bfd9e5126ac5b8ebbedf9e974a755efc6420df651fbb264339c0d3fc7c8d03bc09f9e7ed4f36e10f90a3bc0700d2d5f0b0cb24d828e3f4f183628c31c6184f32292949fb1a244934c4f8f8a2143d2f38d9bb401e2d5227cb95ec4e64a70279b4d871e6e3cc7f914b0277f9a72607d4edb20f764597078071d1e59eec5f5023f3fc4fde8f93771e95773c8eacf35d16c0b6c947509fa46e1e6f3d5e2e43086b32843b43b8c3c3eab143271f8190c7480c465be7adbc34b9a6e66dd4bc083535b3cb01202133f1307100f149f857c2dfec3200382df876394526ea24e16feadbe51e9c14396e974718d3651e202575e37cd0c3e8e1b88142039b508f7a9ae7dea9a03ebebd910971a8f7501c0ad3649bdce51d9312feaa08b7cb3c1e6254f6c9f931972eefb071464dd6f135b2f73f39e8bbacb3ad4d969b8b3128bba703d3641b32b5cbdeded2c58bd29718268e893a34206335c1dfdc71bbac236b419745e87208d6043cb7cb395b086a72085f23e7fc4f2ec0e3e41c7f23e74765111e47d60f9409f05dceb1b90cc5000678287f88483fc238e9e8e82893f025641f06c84c7ebe8607f2a3a5c02772f44bd857a91c513942bdcd8b40f3dc5b2f13b2d129146d08211a9506f60821e7e402641873e89c6d641f99840ca30e01725cea94701712f28ff7914b781bf9df06d5657d9552b7cbb9ea80bf0a80dbe5d7b2a58b8f4c4206ca2ec665b07bc40f923f36cca872254bd4898f83237b97e879c9aed4651c1b33a2cb37503de8b95db6b19fcb507c7c7e7072cf002e0e6a7b54ced91598f3b95d46d964a75293bd4af42a19e06f76f96673198acfd7644fca5e253b8d8fa692e2fec6e6f43701f93354e213aff245208e4c2327b3e35f2f351379141fcb1f66d4a926f2285be2e003d296a7a29ea8821166694ce1a5ac17766528d80f134e889ec49e524a31aa6d76c01fc678d04257b042ca0b51ca082c44ba1c5ea864a4e3f0822c2ebc07c2d09903936de32f5982e1143938b1bfbba39414fa70337607b168f8ef5f1915c4fef5715f9f659402f0df4761ba05540bdbbfb5305b80c6850cae040474420a2c044d89492bafcca4262fa015b000da3ffe05f9037f85e853450cfa1416b629d1c7faf8275c81572ad00f79cd8b5ed575bd705d4a1ba752de77152e2d0002712a4878bbf4bd9db0c5f8180f49f71ee194c14058941fa720825dd522123dba2fe56cc72943e528c658f557d5917429ef9d4424104fa6e094c147181416bacf0b6b8555b6a29765458b69d9a11069d179a5624aa6a495722c4e2eb6edb2b0edb3b7d9b6592793c5f055ad4bd334ad5a18ce9f3fc9b4ecca6a666536bb19ceb62ccbb2ccd2d6c3fa4b6dfe24ea443762c7d732a49457bbced2d5aa1615d2487fbe67fec6dbf64b6dfb0169f64d883f2e7cd5277bfb37b20d1c8ee66d7297617d5b6766ac9a1a341cde5c309994f0df87d643ed743a65990e7aabeb8e968e8e96e2d1921b19c9997884d5ac565c6b05dab5d628d68575524a5f51ae2cde188f8ee2516675448a4947b5bad157603086611886bab1e930a3a5a4384d3e20a57af294d5ac2e549fbfddc88d6a8862845305555512f6769c28874a49a717c9d94352d7eb2e830d4af287685403e6a287f990b2f43fbc292a7fc6915ffefcd31e3b7a07218ebf6f3f3b8e6cca1e7573e2a287bab9c16167ef4a5fc6ec48b323102612c986a92a33cd859d6938af945856e997bc6a8555b6cd43f2e7e7568c16b6ac9a1f7af531a62f3d87c86da94091c4c0202c9fbe3771a320729bc0ba419c49b5a6c41f0752ab13d1a3bec5b7446f8a11d047dc4044ef6807ccd5ffb0172f4b4a5e941cc90b4e5735aa732324952b55934ac2a95143662643af8c3f85d4a0017620f3514ea267e56ac99c713a207a2c78730758e6d0e03ed4d0e2f5442fce1d60a951190b90e7caf6db4490092bd4f0823fa8e4829c3ef4ff5fce6a42f4cac0df4bcf8464cc9965e01863ec2612d6d34bb8fe8b615bab3fab8a12bc8f5c9f046fe83dbef70b277afed8855595aed14b5082f711ff24d84bf0aff9bd878c1173c1f2adafe40c5275c35bd66412d630849694f2f3f0756537d72a3a05014e10f0e747f15f70ab7a3742637f750355f94a4a5dba402db54a596b4de29bbe53aa5128e8214b13d144d1f34ff544cfdf03d890f9c5b0e5c3f0dd0f03177861cbff70f67cf9f2cadff5f5ad4f6eebb3b6f51f8e6519b176d543d6e70f495165af28f0bd37a386dcecefc38dfd25effd7bfd7529662de6ee1935c4fffa4b0f5dae3fd410b92f6d7da67fd56b7d51b9ed3f5fc2e8535541719b3b16e176d17477f23bc659ab1860df9dd481690499b3aa26c8137fa681e15bb9304cd74b88dcf5bfd63a2bc9559dc6950a70bc4c948e3ccf842b4c9ec0401bfb947b54b06f1b23d0864040de8ed6dfff07bb482995e613f80baf9eb63f99b2c72eec8d0a78e5aa441e992d5fd62c5a556cd2c2209b69ff75d8572630aa7ed1ae4bb0ab2a1c24ed6f2b2f1d117affabfaea11df56ad2c9d84eeaad2a82173573f77a551d517ed4a9aa8c636f08728eac80f6242068e91df754bd004a5c90ae2334e8d023ff6d2eaa21731fb1447c88d5e9cd782f46de5740aa13f67177db069adfe7ca3866c77ecad22387b08c782546afa714e3b5fc87ca1ca55747ab155f96bfcd7c33b495984bac936b9abc10eb05d7f5516d837edb1e7d7d05f0f0e68467fdda95613ceaec5a84bd9337f3ec6b5fde7799ef6f171601830b6d01e422bb6a6e1145ad52c4dd31e4aed09007f807e401ffe9aa601c91f1f03ea646000fde748408e84bbee3d0f2905b4c3401e1b4db0fc48136d0c7270c54a1949fb53fa7120622cd5d002fff5d6d78f5fef95b1871b7ec4aca1f9f3ba3c436168be1cf26c5af353520790528efef75fb73b1b9a69b4faad0aecbbb24c58cbaec5ae8a54c99f52dbb6ec2d8b94dbb66d407b4b6d58fef89498923f15f0b2457eb0e2aed3df7b29280668cb16ec5db4681460ff20e9ef4a385e680cf9c79f6f59b7aa1c8582fc0f921615e3c4b6d9d08a182d56651109817b88481691f38a9842d42e54a99e05384c7221b53dd79e9f702935addf58ef4f5d000442c48958fda0777d1b46ee6e79162fc1dd5d471b46d4a9ae286d1245208417e12cf01e368cac1feac3a969fa23c2b099da1ff6433a1b811afb548a2815c4faaaf5148498fe52d0339dec9fb2b7699e55d70ad999a960952953a68b3a7115020ba10f9a65fb4b27b6bf10b7f24892fa4aac4f808c53d704500d210fa5b9fa2b53c15f3e66c4dad588b52f25c3cb7677f9f43d53c13315dc359e720bf74be812eb2d4705630f3314b09dc1c03d0462ca309c9e72798994164e6ffad44e459f4ca36af4a2ebfa033b03957ddf042e34157d3ea20b81cc3c0ca6e79e9e7ee6e90ba9fe7a9aaf8710d2ba29b5374c600ceb88ecac8361060629dbc3c0bd897bd3b3c069d36f9a85199d653bef299bae7d135054a72e30f884a01c3a60cc5941c41bebadeabcbc04140ab24c19e9310cd6706e8771155777568fd07b58d1670583af84e0b437e2eed6ebbaae58aff40266072f607630da514341b1b0952923b7f5371b71abadb9bd4e6d0c2c25be5bbf7080ba14e7e1bf1dbeac2afb002a239611e99e87c059b763d1726256915f6613e93edfca34c3cc1a328dc8c801998618dba4628819620b104cb144c548ee6049054488913421bc4288a5a32962f4306f589268c0a008b7d86cbde3b0fde368e5555565966559395525bbeaa165d5b7189df5a77f16a4c26e5996cc558e87392bc3ac5b68699f910f87cb84dc5dad2bf3b72324a4da15f2f8756596f541715a9635bfaa2a2d334f4fc86642e49e9047e6af0add29f3778570853c75b31bb66ddb766ddbb6d56db3b66aa373935bdc7ceba12589515828a79c724a09a3124cacc0ee417624ac2884b0a3049a907ad47a208f9d99901f417c4ba9a1f65279d4244621843da7860a42bfb9e1a441023838314e4a679269c54208218410429f16662d66ede4ec09fd5a95fb5b1e71a2ac2aab56abaa2a286755559555ab5511e14c5ab9af555b4eab5955ab56abf20a7ea5616559b55e17d600cfbb989d93e60f0bd19cd13c2177ed866ddbd44429eba729ee13ce39a7432ef037b53cb21ea594525ed100cf23d275d67e73cf39bdf2ceb2e6acaa5a41f841b128494cbab88b5d183666f81ca574469c1c58777777d71046f85083b156021b67430821c4d9fe9002a6d40fd8e3eda43e1d5c08db2bae77c7d66cb14ce796dbdbe9e142a8a263621633995278f372204ffd4c77367794dbca9b6977c7dfdeacbdf6f7b34c639a96f9be7342c8536145afc82b433420a89431b32102a4e2a8f44e164435656648002000000043140000200c08868442c168381c1266557714800b7e8a4280661fc99328885118a48c31c6184200000000006064a6888300b99eb317ce9b79d38f19cad04833b1c8ff42131b0f32e2999964f16c2d046d4001a38a3cc705b6129d10be10723156099b1938d7682629f39bb180e7a4e2d2c460f8679ab9eb21c7e75aa9be2a3f29444e4abc12c4357b7388ca019d6f0536016a4a6b6a2df0c952d5a04727c83bca2fd4a46791d9583fc0bef459748f5aad60cbd08f630ecd1bf3e78deb71fdb9b88a488d7e56a2c31190e808912d857b89b2e5e7d88d13b607671d0055a047d0112ab65471546478f726fce304b44115771db376c2d161300de24a17c947635f8a08372eea69f7d63874235ba36f11928c8102e2d83a0b319f9605352a11ff330506663565ea2b0683e34b8480c03cc22410f2d752dffdc6a6e75f2e6f0acaa709710f641e7ac302d9b58d64e7809009f169525f499a0da11d9b2ebb1ed9804287635a3f1c7fd481be2d9f546e5f1eeed2beb031d6df1e17c534c4397884520f796cefa41de72489ac8634ded1b5de383636fb96fdb0e943039b398741ec865e9bab63873014c78a6c603fdca883ac74969aa6a9d9aa8ecc840eb18fc64f4cf1ca82469952bc2518cb80b12c5570c69b39e85843c18d4e585596eed365c90fe498579799d9993620c0be9121b5367b38a0d0c3cb5acd341f42a2ac1ce50681c68b60529cea113c53de7244aa31abf5f90f9768e2fee773a0046135783a1854059838adfb0205d481ef69461b784496b4b469c571a4a847451729075da4a38519b77ef07792f65f062481d79fcd0e6703ef3f37c09551b96d0539db6e832cb1a875d2e681078783515811b70ad77d8d1c89cf7c22937c662279d179915282dfd73d2a3b7cf2202943bbb2cb2d4b13f2c5c28ea5780ec033a514246f39863c5c889e1318a98d2dafadfde32a76da48dc84f0bcc13cb4493feb4847a22f2efba30b6cc1a396d9d191460f314011d98a13f76eeff5602776c6f393fe2e448e64b5a75daf31342dc435ad5cf831aedcc2d644f924464787187b896bf375023fc83fde3f1ac645db2f41ffbf09793fb3cfa13b42e9ab0613846b824c7fe87e11b8dc976e4e808d1f310cb5cc9e4bd099dc2dd6c06940dc3c1135fe64fd42cc3d414af1711f4c34796904ed35fbdcfa61bfc93bfea10bfce4db3894c6441230c26656b60c249f3234bae73084f2544aa2db933c51a9e337513258dfa1885f5b5bed36ffbbc3a1a7e7016dcbddc085219ad490efb794af754e34e9ad505975b89d5fac7fdd887ab1b4f8c858c755db5901678ca35840a0b4119e2298d90d5393773172fbb46fd5a961e0d688d0cec930db2eca6a999b6e071aff0beb04c9ee8c0c95f3c12e13583607e022c73030038396d0e61a889e38bd82727b4a2abf82ac1534fa47d426f855a50ec8e4e2f12a1be4f82ce814035c0d9b00139bb218e4da797e182b160f42a3f7fb8c20078fba474de297aace375ceb374e32a84ac996179336d731afd90a967f98c835f1e143de1950dd86014171af2ede66eca02362cc1ea2846a090c39b9403360a76b24283074e9a305f78815c42882e18fe63cd3603647af0bcc5c1e49ce8af8cf5e9cc29fb524a9fb6e77d5ad42601d58caeee6a8a032ce33af7fc2af9e3ff3aad83fbeec2a253ae6dd00c5a444f39b125891f9f81be0596e0ca29a5e9e95435912a4390fc43289bf5c651cc768ebcac84453f2e569ce56dca979aa0c914f794878f3a62793b09037c8f0658015a81bdbd83e4578e37efbf969f1f71b770750dbb8f8b92c23d1226c15ce474955c8a92c7717c31513180820ecd1ec0d0ab7877d414dea391fa0aa6571ddb028d2725bdbc5958877aa48b31983b30f7d51d3057e84db622004103688962f0acebc063ea55b806c152345e113a8fd096f542a52b1960f9c9f573a08aa1d73c2656778a9c46df9ef4924c5241a6f9575a5929fa0c09dce942cc689dd8712d33a3dcecd4a6294e70a0b718872d5d055c893ace9e74008cc0ed83dfb78020b32bec9ef40f9312bc424b0cd0772891755d5a5f789cfa31d18a39210624270c0857d85b2d561c35eced9e04186a19499dca5c2f7cee1b4ce2c1aa34269bb43f8872143c18010635beec9acb090f30051de30754d3f0e70bda9b70e090dbf10d02a1c7f87089890d07b41056c2e479760010c25e9d9e62725b9cbd0dce946b56b63a577e44c0c64f7206fdc814cea47594d266ab37e84946bf68585542299070162c63e4509677e0c98a657df302f302e024c32be17f36483ff47a3c53f933550398108b945cc74b75abaa3cbf72c5a99a5a29c1d57a49246667e07d462b05820099f7d848fa7df70dfb4ef5a8f4a5a3dfdeeb72041b424d620f94a2146e79f187527959348c3c09d41d7521253eb3ccdf8b1a7fd8197393eb51e5d3ac04c6f35a66a584b3a33edb06bb365e1d271761049e28a04105ba24588ee70c2501b55507d45dd9361ed1cd26738746092115dbc9e52783e22590543ef18ab2b15ba1038bb0da8329f99b7f505a6ca8c99f0774f1ed00ad4a120a05295abd4408bab8c22a04956d845c58de8fcb3e42d46657d10a96026dee86c581157c78ce6fe1af30ccc2c4209d449ca8ed025b0b2a8828a945d3b900da61609c544e00af4a0bc2fd827862831347c7983c0b6acb92d0a1a6badf0ed93b7fb35a9a54c916bac9767a5bb71088229472fef590d51dcf300154363d3e6f33a833838d8c0c5e05866571441ba315915654d5a51374998c18d2ded47066e692b6ec8aa9c6226f6b9effe8709b402ef81f9251552bc7f7d59498d17df6c8642fa5422ef28569e8909afc4484b1ee3c83149ffc643cf156f24e1662a998da5dbd83b63a15ff86e8dae5e0785f67c39c0daf205cbcbdab1ee3ad37ae23eb915083bcb36e4ab4add7f87fbd8b71946f600a6d4c15037a06bbf43c00b6813888eaf5af34d9372e6578394ba6a64ea6bef3f9acabd656a7ca6f238775e2895965412c414f7b75fc8790ab7a9f3ed5a2c8945523b866653396dcec6bced98392eedef3e3cd05bdec2b655577d1c431e6061be6cf25fa43591aed0a4db74db8fce72deeb19d48a7d12eaf49db8f5f983d23369a8fa23a3231fd638dc21f2d2fe3ec882b6a660085d3bf9810e5573212b061a03d0dd510882336cf1c57474cb50d0b29e912cf67edf09817affeba1cf1349cd885e5bebea78c52b8fabdb94173621213cd04802a45466a12d5c880f21365115fa914a46d68ecbe500aa537b818d82ba52567b1693ff9af1e5e152645a3131b42bbe5bb9eddba2dbcb67c62df50caa35210c1946c439ba983a79b2a947d5481b646ac4e25ffbdf552f94a9a67d78d9012e7c2489e89d861a81362ce9832a7315f1c17cc2154816d4c606d58538f178d0f528d3f70c47c9654b586895304d42c6d77a800d2e0734b0b9d184b89328d207056a4aa52828b35f72b626fe6e626331b166665148dcea3e85f6721e9206d083212db6773841ca4fb34a442cae70e4927d3053187a3761842005ece952c91b224f74399dc3895b2e7cc32079b63a73d256b987a4001df2606a269115481a06b8286947efe218a7940cf896e017c2c8eca100f805dbe6afb6962205fbc03ed798c61f8a42f0d423bf2145f8c70f32b3bae9e46735e195a4327cfd05712b6221138d9cf86a76bae042b4a5960e389bede0d80d5341f6c129fc9318b13308a012c77fc6ad0f0d95853e0a77b262c970e43631ba11465658b176e0e6450d70a08754bbd8b4c17396aa1f0d25ec1fa5d591a37d72fd9eca151a8d4b8801c78ac45912942d8c9e5a4b46f030f055fde15f343d7d1e6fe103e639254b342ecba69e5048ab0d605b88f7d32df24ef3869de4cb0268a0e4fc9b544e651846934d8703235f9d604e4cdfd15e6b4552e15f44265cf4229449ca9430523fd125da087ebbacfce07959863acb9a9f946c4f5b91106167cb1f4aed74661a4d46c48d5da407058b6b02e64568fe1cc45433f51a7a74615a83ca4ff58e4f070796cb4ce4c553b88a59205c1517d1c72e607e47e09a4d009ad335a5bb846d843e41ea5c4f0cb9f1c36de3dad3c67b9fc335a37e9a711222997e0d20c6e13b19f04a08718b8bb8c77da05e25088d777661c588beebaa10ebbf6e49807b008215a6aee5031f8613a160f99f6310140c9b29794b6b8ce91b8d4cff4ade7a970193267470b55853777f6fdd953d74ecff7d8a001f26bca7761041c83f35ce36697f865811ac6b5aca776b035307a75432d9a3b72a17fafe854d44aba7fd5243203faea55ad071e492b27403a554d725bcf3aaa2905dcb9d1bef3cf64685d80fce310de97b92be5bc2c3d55a51d4973fd60e5e9c3600f6cae970c2f58830511fd5ceb40ba50a0e270286e6067a65d21e7638e193afe5b3a99b59f42a9546245ae2dc99773b291b7d85fc537bdc3ccb8c9ce9825ad4f8a18c2eccf386f476d9702e623ba28083251ef712fa083ed3598b4c90182d71612d14465182f8b0fd49048a27a5e091747e47682cd8e6197fe0366e25378ff345d531e34ea3b761aa39b545cab93d7d040c2a1e254e1d9e3a68906140dc237eca1aeba020c6ab7ead9295ba8509164571b5ffb10c1fb6e3aa333140f870f647125b07c5d17a18e801c8aa3d6044ebe3718f2c41daed1568968ca540a3d78733678246244a2b841949669e0d2751322adda0e6a0ab14785bbccd4151e89dc61111695fbd254b1d5a33b146c9742f811e2e30ae5e63b370ac12a2f51aea517b17c23adfd0b20c0bf6911b6dfc23b3709d2f3a59c59f1b6dbf40920d05432013844032d413c07543425033e30ee4551583b62f611913e2e1de56c4cf1f6ed9f5aef7bfe7bbdfff3d3b25de134d3e1bf7e79df8433831ca605975b997e295c38692004043d0c0fda08e9269abd52aad7ae03256eb66053fee125cfabb04131bc56aa00ebe350d23629807ab4e9e93e5afcb42fcb72f5c0c545e2935ef17d650ddca3b1719cc9118fcfd62dfac7664cdfb0dfcf43403b69de86320102014c4fb0391134d6463c8045df4c8f118411a308a2fb4fcb97190a35d6a83c199195ee22dfa342ba317817db3e4c6595828065c4c700e9e3d37fcfc888d7ad85a30108e776347a70386a9281160f82c1faaecabd40d6c02a81b2345c122dc7644cadbc3cf8a23114216312da2e0a3a3274f0d942a73e553cef868a0dab8a4cb1795d54ad5aeea151b603f65af270c2ce2dfba6725056b667f2e2745661415e6a5a26b69037f9527bf9e5da708e688c5be8a6cece7e94ae555095ea2dad49c0f30628ff1c6edc840b1c5a4b3f857b8a573e35de19717efc83796a945825b099d376eac2cb9dcf76a4c687396ad4fe18d260db60a976ab88ef6080b33941433723fb3d94971571a61804115efae603ae575b619e005e72f04722f589b30c64777dcf4d79763fe9c06500464a26aedefd2075f0c007e64fd8b3200c634b369cd22a3dc916e7f2ee4791474a33106c0c8fca1b4d70fa81e42ff989e520362f7b0ea08bc9a0ac1f420e0ed93f06b2a99efaee2893e45452fdb27d1c8df09ae6daf40936a894bd4880d933b9648ddc14084b5a4b3c3845c1b9381b831329ef1445e1b13bf46df20888263197b88666f2c0443e08aa31ef3289e3764efa6ffd91ccfb9abb5e4ceac0a71ff0637368a71e6d315c7260f435dead76b200ee3db34d02c05a8c01a231d4833b4800748787a364938383374a19c602ebffe0e4c8d497e682708440d0c114aa444f93c83e03b2a3ddb3d387319482fc62a35bb93242726aea90882c53aebf01fe8191d93280eb6ca556aa07b20e088b78a880c24bafede68da0b3f9da7e5e91b32f19b32d839d2036f1373512b90e7d20384f1300c8d23835382d70ab7a1b49b048f96c62fce14e5caba34df71d8591580454c4931acc9e1019b6f512848ba03fac040fa15eb0fb372369828e71b5e5763dc6921b32904060ae63f7c2032d783465e2c1ce4d013c10d5c4ca996033c374527560dc0981bf81a2688abda9421d62e30c371a99871544cd46fbb822c2c444b5f10a8096e92f74d12bc56c28eec9fa581c4e97062e0de451bd807edfd68f5b234f880a90b46841b08fc2cc4924204e8eeb1015df476961d99cc607b1cb1271f2c55f1c3d22208306cf99f3bbbdf8c29cf04e3c57cb047346c9273645e07e726be99d0f7206bca2a0ba926f227573628e295b2dbb725d939932429412b5d9868c73579eb1f24e3da71b8a47baa6cff968723b0c330ec95019cf7c1268c658d2975af238653650ca84f80dc5c86969ce1fce802c5f84e63158ce087ac4053b31d11374fccdbcfc6b0a4693293693908683faf26ee7f000ba79551fdcb208adb2be718f16ea802b1845462cadc7be00c8905a4cbdf8b07f2a8b900e0b9807660e760b25fbaf890b772cca97150b3b518616ccb0d02361d86341226fc53a51787348aa1d75771a7de437cca05c38f75608b73c71f1112d81e90b457a3b61544d7a928310091871ba15ce31815084dd0e590bd1d192b1a29bc187623768576eac95178635e9ebe757c92518a368fe143d9bb6920278dc3360568a352359977ccd59112072c3a91857464c4952a885f30ad5cae12a4100173cb1600b552a63dc0883f5422e26cd4ac6bd044d1e63b1ca9c6d1471c60bc40040771e74fb11a4b23f2d4988069c9bd276e7773b437f4f69fdeefae14a5a9924e28a063d97b42fd843e81ae8c34557a988ec6a210719cf8ffdb5a9f9e918d6694e64fccdb915fcf98b62af8b40b79c6cbc2bdaaa6e4fb1c8a957c0efc09c61ba19093b07a1516a96e858fba65a05e7cf16a70f117cb6487704a865c6bf6edd606cb5eb6344be7f72a2ccfe9fae24ec93b1b3e5810146af75e0653b343b2a8897fd398041c9eb9f2f537a96e27446ce9efe98e1bd72439299cfc46d5da6bd520aecb4bbae01212b0c7672a600ea6295f4db58a5b9c363d3ff7349a0317f5986a70e9bc52f0edfadc2a0f61c0596d3c50413f50f56c4e4c51849ef403496a1f22c474d0812a73b586921840095c093351bfd29d4bdaaa840c94052670729b7b40064a28609ce26f3362c4b603d6360be79ec1b67847b69510db70378f9230d845b106f5550753ec23b87c8591766494d8fb0552a5e4a437bc62cea06ad937d72aed82a790a9d03a589ced49829fa30dab9a35e96e6ad6fa1e9f8f9f021fc7ccb921ea6ee43700092038f24a1e337dba370d90b869ff2ae9d307f92d1fb7be35ba88efb60f0bc7b32986fae188ae5c990c747092ac447850645950ff40acf7b93f4210d0090f33b9e946821a1d5c3fa50f20d2e443d8933ba42b6be7d9b8a6d3858467c05e47570f207ffe1546bb581f7fb3e790d838adca144639bbed26a8e629ba878ced081b9aca610b25a2f001626ebc0eef8823620ef356405cb6ef2dda04dcbb9968dfde6a8d82d50ae1b6d153c28bd3d4580ee532e802c3ff26f42013bad61da5c54ab3ba5a6d42ca5e482813c52473c6913d35f3e376ec87015947394d749c4b9c952ec50a00d13668b62656a7cf703b2a5ed0e628dc196e80e35b317e8d1b07edf7cc1f17f6403319c6115b3a28e01c6757dd4509495c6d60b61d83fe3e921ba351caf987b34bbdc5eddadc457f8dc06de9f2a19f8971bb7517f2459166947dcc8d516e5897956f5bb0b02d13686d46bc44459cd770a20d7e7c120ef074212e6a1b6d41d62af94aeb428f4f4659960c88ecabe4ce91cc9744b8caa596e6808e89c9264577dedc235dfc2e66b8ded6c14aac8e166e0a37ecfd4b9b83b12a52256a4322b04359070c7b87ec62bbf0153ae810ff32777f3e6fc552af48fe6da06b8ab2ec8370322dcd5bfced48497122a8462e0d97c7d401267cc8fa43df07e3c35bd7d7db9afe734bae24029ee0f27a69cc7dd5b41dd0904c73dd33898f49ce93ce0985b73cc1db77e66f112ac2db6c3cca9b49dbeb188fbf2c66ce01665839d984c87793e28c1199f2ce136b82f3af6e4de777e1e3600ee06ef1b56497220ecd0edf2ee279fc5bf24ec9dc835c4d55adbac878ad7485e442dc44776bb797ad79ff97aa0b763a72cd57f766af9ff69ebff227ef453661417b9d33c53f39f7265c8db667a99ce3c5bfb46e3df1fc0bf70b88cf2fdb59950ecd9eb1b7d0043be8a7d956d175e6116aed345f9ed2ca638f9d27bf2558aa7d8a7b840ad65f87d4d5c0481553dc9f81ba16ed72fbd7d053a7b5897a6f46d1f94e27a675be71ebd5635f1962c2a6027a97c44fc68488a1c14dc378bd1cc3d1b43757f4ae40b2c9bb5853f0a0efacb75b13c8aa0400672655c46f05012db3faeebfd2e088db1b738af8d0bf1fffeb628fc38a694b0fcb91cc544b7c32a4f60e024dc5540bbfb81024ef48f48392ad571e17da4eb69201c47cf5c37b87df77f9865eabdcb2d44c70049b4faf25d220b5eb80ec617041750e855f4e6687628bcfbb9e2d741d68b61ec99b0101206dbaac3821073092c0e1e00ddc3457048335214471d16b07da708ccd965efd74ae15c5135d172c4d36f9b1be0c47b9698abfe904614cf3a451c92d1ae2b0d5a405c8a86f2ffc54b82689b2b0bc7f06df474055da040295a87235304419a86fc42f93c77d8fa26f1913df8d8885a5ccda66f2a322958d80fea2a472f6a333c8ab66761b5e7d776527224c0185361e0ff97d92a8c89641190a0d6e2bf100e1d733154fefc33f8e0ceb84d06c8d5388c831a512508c03593a29fc360c5332222cb47d3f76395a54c96585b46014d66816d6302c31f03888f08e6fef60e99c347083d686c53e66014b2d7f3aa6a40faa7066cb0941e9fb0fc1e45105d6661cff1df877487dd0405b62e07650c704de170a2c4350780848148270b78d4ee5dbaccfb8a3239655fc7b1f97c5e4d79314af7156063ed6abd8eb567db3d7bc64e0efecb94ee6bd6a8da21c8824d6a8199b6c3d3043b27e8322ccf665126cf77e56be0f99b3e618cc69211fe20144a515b5f5aa9f992b7ffb08b36fd95e7aa3948024d7d424042529062b4fa592370b355b3a743591f9916b06aab3c03760093de1c6d4aa12e17cbded4f4ad9969d1eac0f06d09fb04e4c3868323b3d3b247a144d795a936f5a9fd5ef8e0cdce71d4b68891fe4ae046f192f3a241aaaf4ac6a8c16624679701a439ccd1c58a24fb31045d3f84914bc5aed122851dc2ec9874e6d3c0f0ce4a1a4bc05a9b532161dffb13fe440406735406507259db663d2d1fbed42a564a445eacf62a7cbebe791c0723e718177c0eba5d032e1e7a681ef1293f6f9f9f9fb9e74bbbc24eb88a70576d1a05913d5606564a3214d94198050946bb09dfb8657a872c2274d67b2b9446c74a6e9932d4f91a1ae45108d83d450ee37037e0413f360a6660bfb34fbc11f74b4cb2c08007cb5f64975889cdd83df3af1c046ca98eeee5a6d61757272902b513c50c2660b4522956a22c7ca859b1f2f19563ebcca604d34ca5a279fe6fb14338d2cebaa445b95c36a16132f750a5b38c7119fa504d877dc61f228076e56deb965cd3c1e0a119211cd4046c72f6467375d0697a549ca60617b29d60f5f5492e6d24b1dfc7659ac44e24f69f49758507994ee1cd5c3203c2ef441026c3e04a4e54c4bd04c8b18c8f294d41142f20a1372e27e07c48897ec7b87c69ad28608c46eab274141075611c9d38140c4c60f4567a4a086028eb46c3780ae886344e649a076edea083c1ea3f66a7a9bdb8e21aaeba4947bd3cbc2dde0764b28a744d622873cda3f88b65a454288633935a1addb590eda51d04548d0cfe976f2a8bab00c66f16775034b13ed9e9aa509a3ab4524c5fb58b305782cd345a4eaebd31076fb6fdace4916229def1c7cb8029da8eb939d63f4fad2000152b351edf71249f0b2d17eaa263f137a89ad82c5a94a9a388441bbf20f4f2a40694d54b500853cfb16d1982e2c94ba1e7b2e1c0365cf71c696b4d6c848b5e3240fb7a071f52a9ed934c56d2479fe0abd077eacd60bd4dbf16d451b6842411c9fdd3374184ea5215e4ad16c20da9cff2a005f3036617eeb33b50718bbccb46648ba5e2f6a16914d6f9ad2867f612b81d50cb9e5d63acc04891900a0e311ca91a9ea5d606838622777f96688a41a97c7fd095d36030ea05c5840a13ac48c9a6d45dcea7a1af0349f8981ac4b6e17c616e70d19f8a5e73454c56f498c8f2a29552c826c0066228a5008ad43bcf2395da2980ab23dcd8b49732ffbc7773004e1466c8b189040c3e7f24bcdf8ab8452df50699f1457bd4988ce941aa1e09d2cc8d30c99d8cf6ac6554f2a53f00842131021c14ba6ad314ddd5c912f12d7173401e16f04c0580875b3e2f45851d03f7296ca22ac7a4aefe6e919b5ae5c49f7c0d394b66b17f73ef2ca732b269e4c17324fad68ce4e6d4cf5339f92c9ec33a61afc76a573073b9c661845a32b661e746474c17fa574f194da3a76733f944acb529b7d0255ca7ae62d01b9b7405cf08e6d87f48277b60830e4fc70f893a247a080211ebe3f12e8e3465f4970f8a25adfd4531368b847d81a59200693ab4bf6bba2157ea81f2fa4a4aa8b672282035ff49e7511867a59b150da0cdd51e9ff4f52d4efbfd24b85086ef4994d6f0a495c1493bf36e8bc3a1a818f0cfdee61b623c248884acbfdf6c4500c074121a98e1682b0f7a1b366a72e5ba45101bbe0175e32e8b134bc5ed847435a7d06ce14023d7dcd0bbf2cb716b88404a286476b8419e888ee6beb0df853519b89ca7ff6c0366936caf064b1c5f1b2f8c09457d92ae1a8146142001bc60dfc05023f9127aba0f7a2b8dfe5e64ee53f29c31e3893a12ada442423e190911514cd040f2493563837a0aba541012651739564d2cc57b86a75661aac4a202eda38991f8e126cf74099a4222ed67d22b3e8a06baaac3b294d91379deb6fb5ec77c179b72631cd62460902ac8f34eaff4c40c199c0f6e75e68638724bded50f8aecd1183f4ff6d92455a4009857d2b41778faea65b53a570489eecf07d94b68013c3b296a59ccc39d12e420e02668a4873157e32de3645bfcd77663916e80bb8c92e8751f4a03a071f14efa11c01e42332838c8bd8d3c3aa7f10fa705f2ed537867f7101c6f82257a0b7552da28b4473ce54408706adc18fbbf7049ca5b4b511f4d1bd43438c1d6abb963db66cc378caad73431b1e2de595eda73bcaf5e2ff872fa6313c2bf673621b85ea366e315ca38520d318445b05b7c4585f40dd4beda6ff7847d50b6cc0b575d391bf8f083c291d288568bbccc0da89a001d5038900a4ca35f3978b4dfd4e064cc0945b2532c3e799ce52e36b46ee4cbd6f36b1a1a6da315d56a3b8948e87b6340333117ba62e80db55389517e5eff846210f3b77159c687b3f68bceb817181695fd23fe7470d719566c0c62e5cd69141c97c4f538d56420bda604900f48525179c7d4cd514206515d5722697c14c80674af95f37f6acffa2317ac376533e787db1009c49bb48f6acee3ac2370b2780630aa56862cec984b12d12abff05637521266f7da6ff97781e1c869f7298e320c31c30b296c064aeb5356b6d7d5ffec855bf7eea41e4bf1936b5de845e2975dd16a0dbfd44d16e8354ec76341f35ed6d7f795e4739b9eb7c8a8864d390286deedb7ebf8f97fa790713599f9890a2034044a77dbed0aa84b375589e644650632983e7a4e5d035c56ff29f2ce79181ef32bb67c888c23d85c9c360328cf5126c309aaa97f041f04bdd4c3be3efce04eba510afd55f7e9c2293300cbcf32d1d082dfbb7953b114517b3d2d16fb6f3a4b31295a96fef3606c1468a376fe36ec12cd93771112feb1a81588e040faced0ce8f08f3b3931211b40556d5e4b279831a0c0db6d3f612cb82e252c0487c6a115aa42983a06b41c2e8d05c44b5943163e80b2cfe5e70fbba3aa8633cdf268bc435cc422be60673819c46d75897712e1bf83ae2fe7c282eabc82e3a3588ff984eb3ee7afd448e789758a6f6776c4a27eb335c529d164741b4cd6a2a4c669fb64b7be6a7b83684be167ce376914fbeccbcc322577810241b0aae424b64580d86e1632be5d05d3a6b7ddecf61649b06b3ca4e44b0126fd6d1b87882acc47b88a7f95748efa7480c80c0a2f11a03d19d5e34b9d4aa29e482164df30021f1e272f7972b44aadaf7b9297af02f96a4eaf303a743829c675c4b71d777f6f0f4632914e31edef7a3226fc32af38816dd7e971f5c4a32170517f4941d766176a1014161807efc3a8a23e5e42a1d6eb8b1714835eed4de404eeca1349e3f077e09da66f80edfa9d8b82b15acc897ca200ec1d68948387bfbdc01e44b4e13a768dbb7e3102b7aaf6369f5f280a2d45e14bc35638d994c44c7c71b0006ab9097a1db7ce419150debfe9798ba93364c2dcfb0f909f71855f232360e6be38bc597790cda887e00278f5792fb53cc61773040782e728d0be49021ff68ba4b69f574b9d7d4c6184564d08850dc2788a90d14755538412278b9fc8b340a11ca64ab3edb14d95701c206ae1ee36d5d621e744d4394a99eb0002bc498d67e81faed6dc91c69f8f5089a68c8b01551edd80acf0437a7e2b6936387179b38e1fcc1d4ea94515abe0d3fded7b171ba5a72fea1fe1a1201947383f525cd41639ef787c5bcd22bff0982949d6ea7c152a990b94ba60c4fa0ea054102d1ebcae1d0da2af52b94424e229e2a69ccef43a11ce71ce7f8b19e05eeb1e03438afff407d122c93d430d8d15a5dc3c8d269fe104ffa09d0c7d6a1640ffd7aadcba9e5897d966f91efd580ee2a3757ea53955a5509b3d65bae22210e0c0bf9520f11295a53a4f573197218f5cb167b5cbc71b8ea2a3c3e3103dea21c81f6c0237150b7b75db481321fa39c116886ef957f7b328ae5f74bbddca7b6f5344fd09b1db1c69f820d503f49e6a7cb8810ea6ad87ff290511670a4738b6a0203b9eecdb116d4d80646f747c8d97ba311c0764581fdf98ac28c76210d4593d81b3d23be4a37ddbddee67e5470bacff2761551f00ef5d47aca184cbcecf6296137e0e37df8b38bb9187a6345336572a1cc34096771ae6a8698a68e5d18ae3d965cb178f347f606939b260e2acede0644bbee10189ef89abe5b0d7fca0d4020ee4dc8338d09b4d6e17ed2aa611683e6f5420f1da711cc5f5c9c86423e28253eda9832dd8db6233bd6e4e7c468f6cd7c1c03f8d53889e816f44629373553a1cdab5fdc48851c0d0b5c778784fa7e5c85a3e7e72d91815f6ae51423bb985ba596b1dd28ea32d9a9d344300bf54e75bd76bcc1d3762b57bd9ea6b90afc445764abd8fa4f3adeac6a892ccc91b7ae8b56ef599b0df8bb4e384470be7ca10f866194b55b90d12451406af1d447b14c8762f36b13e5ed937500a9463462e16ee2be628df16d1ff7855a8274010fc8e577c65a7e8cd6f2b66a4a2c848b5b6ef99ca7967ad81ebbb46ac16fffb7b235b008b1f89e20e396955e050c60086021833c3fce2d4e36b95a4d8faa574d76c6fd147a1e04702d3da269938fbc92185de559ee53dd0db0fb96eac950f01b39454317cf454fbc75ebd1d7190f5bdb394bf997386fe177aabb6f60b4d9a5b99d464f8c8b10fadd7fd029483e1562dd22da7874694d392103f92dd7ddbd4d89e804e6133495d48b0a240c97b9978362fcdb18ceeb8f39c64d4bfe4c58e6f7c7b69d73ca406102966e160743cd8be15649d6408e4aef42162eac6fffc0de44425cf5debe7de5534a3154e50e3a6a789d965242217c8b464480a4ac41050e96404913441f9b13611192773423fafdbaf6885252aa3b019bf35f325d3a5ee30295fa524e52ceb100b5df4bdc087e6ee439b2b2186581e0c7049abbd701e89fe00bda5c7c2795d30026b24a464b5e654bdb507e9c9c482a6654e759c998c8206b71f0a9627f59b0344f532792b55cb50f4f796c2b345b33940be328a27e1bccd1298d3ea569848652542a314acd926cc951e4229d1ddc4b2f728c58248f6258b153986ab68417fb008fd6d3a58e3a8d47249d5b055cac118afd30adba1a9e04107d04b5534c5cb77565ae04958ad52a709af8af0cff5ee8b715ce97c08f821627b17537d6008684f31de2a6c09c05655ed87d315a36394669f10dfb07d09f1008fd87721af0aaec924d82c8d246d20ef3f158aea12687d5c5f4d6cc1e4ecfab57b42e610cf870a9433781468f94fe50171b35da3fa1cf93e5a3ab3e8faebddfb1d566becaa841e0980581981fa32c9f8e6db4dd169d66052cb9d4925373727953a7fb8f19b14d0bbe703c93588e3ff35054398b27302b087d03fae6d92bcd29596840ce942a8edab1fcf4be58f3e3db8b52d91b243a8620390ae971908242bf7fbd5360faeb19c70baca3e96f3a8497acc08cf6df4992888edc6c67244a065169c118df4dcc6be1d44068c39e26693fb64f8df61f56c6c079e551f4ba93fe60842be5f61e015a21e43884f6caee37f962d3540c8d0bba43112d012f6d567f98d9fccdf602d123a249270ce2f59eae36b2a1f6bfcfbca9d9f7f44b13f70612366125fd13472b1a63708cd7477e3eea693d7e4eab06f3b971e2e519b2220189c406680668f9e4be10558ff819b58bc65fdc7587de86440f1623c3bc5baee3efed42207283b67a2e2f0f4f705de34ffdcff4fc5903d9aa358c3af4ded11743fbe071db7bd98bb9ce40f9d18300be2af0bc2341437e4b240728bfc42d66fea9e9ca77024c2bb671dd142047885293192ff78f48fa582763f3428cab6d6fe9770e90833b1480c914f88d0d77ed73aa3cabd85e101a9d2e8f109ef134893b4c840c15c06298c97d590831b28be4cf20710f89d3bbb012ac1459616d50b2b4c221b7f363283ddaecc9690e66a419c52fd5e84c228681300844ba7925655bf98d010006feb986361864ac1cbedc63a4139ad3a031f8181a0981e6a182dc7ab6cff79496b8aa4aaecb2b209f2ade21197413dbe7c7fb9a5eff83807657af64416300935704677b44408a6415d523fd027dcdee8a19bf969c0cebb8a776348c7bb627022a84d873bda054c5a23c1ecf3224ab2719fbb9740e6fda9626cf229d4ee323809d66c30e1fc9a3bb683c0494ef7f95fb2b060258a7218056d589be600bd8fe28a151581751915c782674e6ffb623294023b30bfe1d5f9ed3ea15c0d94fe2527148a4b1fc5458522480363b82d0aa9334a239eaebffa5eee83c0175c09fadf69180ebbda91cfb6d3c67effd3a7db74d6f53fac42699e8ed4024b1e85e3ec528ce53c772d9e483b249c64757696a3a389ab7562028a671ec60ec8329305ec4a2456d9eed294f0146aece6a59b79713c3db73ea49bcf0722ff33eb034147ffc5a175387e5032a4451d9947ed8652fc01f44e7f967de58c7c94f525712e92286d57397a9b19f63419089c836b68ae9e0004c2eda55dd4c82c4859d3ece3e6ade01a78b614ac3fa4ce12fefb52a0f552221b57a08184f021f71456ce7cb3371d6d94cb69e04f2ade483c7091360807caeff8e204a482e4181d2011410c78ebbeff126703844f5ad32e4aeca8b4eb4e3f305b8106bda8bc873773b81e7941168834604a42571596ea245852fab2bb293819247f52353bd9ab2450510f710eb540cfee3b50952eac8f0ccd3ded4089d4e2603691e8658832932946beb7c4a02d4db161cb8752858a2ef236c78f807a5cebc38b05435b6b3c5616304469a9c9fd245612bdd066308232e09a94485708497472ef2498e0e875d6707a68e35ab7f4a22633cd4b8f69f1128ffdca5c0624375e6e8003c0191e48d08f0cb659b0d08f7ab0950aa1e20c897c561a85630a3e2b18f8d37bb1f2a756ff8c927198d2d24584099fbbdaabfaac20274306087bc629269af20666dd7f3524c1bdfb8a1e49541d1df30b53459ce64e6cff92050837fd4fd93cab47c80e3b431b941267eb4cec6720534528d3628a6360f53144610fc4aed0fe1f931a7ecc9a39c2cb7ca603651d14c1c2aca9bf0c61069486d708ae2882f931f39ce619a5e70cc87e6368cf38ed97131c1145c8492ddef5676bd56a0cf659716638c7e7e8df1f1ea5047a45f8e5bb1b6045085504f13f518d410c7892e1a36909c9034fbaf63db1e0896337418c11c686d71d142bf4b445ceea2daaa91fc95fa69610f6a1950f02ac5db4582b9e472fc78f5eb896880b4a74e762342b0145927d6b5f9a6e43c6683226958b02e0fbbec4f0f3c96353da961fcfd1c39f9c368c2b0cc4797cca794037f9bc06da7db7bac34d203a9c06788649db16d1bff2077ecc6220bca84de9b2c183159f0ee2b5280ba525bfff3df986157965284d1890b3287ddbc664736a5ca7eb25e2afee50a940fb7e3a882586f6677914fa48ed865154753c7c524d63b785d374d4f53bac436b9c1760f55723be6d3f9d46604cdf32bf76160427b338587d8c0f46e06d367530f2ab4bf0b2e69b446f177bc75da7185d47f5dcf6ddee7350275e65fe0d8470bc5623a3ad6a0d0902bc21b06dd8e2a3abf905cd410de32925bc716a3d86267c4c04115a21a0d20dbaa7d3e96df04d8c7598f0e8fa15d2b742657e1b6fcd4fe14d2fc39f9aa6219ded46e4d3fd876e02e4aa7e41f148cb7181b6168123b44401b1188c7ebe7271d44ecf7c42c8b6eb529970e9557c202b4a553411367ddc656f85a06cb8214a4b20f7e934128af6dba4fcb8c23dd2a5bb15385262aee30602d13390e5f7af58d0bdb01366b3675cdde503164b9a75975b1d96addf1bf02bf2be712c259b7f3c498e77cf011663fe9e65bc98eed0a458490d2e621fe1cd87008a190b09c2f50e68e5167de078331734608aa5a3efe994d0b832370f99bf3e3a67a894707f98a2faec0dd8cd48a24d98f059a23774f793bd33bb9f79972a0059bb340a6489185a0db6c74e76dfc0e08e0621e56143b0e0f1a6c2298c1d76a3114510528f2e08d901b68d07772067170a05718fde10d2476743bc06d8e34a317ab2f4bfa927a1507e1ea18ac89de1e33d2c3f6da054d2c3ffe0291dda8851f1019d864128ced7bada9239d9f0afcd9b753e2c442eca4bd223bdcc490b42dee88b6f63edf4c94a4505bf8da54c080402746b217832c831e3d587e0256be3a82a3a9c2906f22298e1e395d5cb4131c5ba91657135918464e1e18386f3cac20591d5f9b8d1f0e89904fd163f3fcb631a1cd77bc082e6599a3f2a4f4c8e26954c7600a3c504fc05f7d167290b1774faaf86c05394b553805b7c4d70865c09230a43aafecfb1272fd4284419521b5319b03a598629a005e26c8fae3a9eeea510c3d761b0f53cfe1b4061d42431f48c474b70475b4abff4b99820163596fb38a4d8af197ba6832b606c1c939608cf1db118e3cea5670e4d1e0fb2471cdae58a626f4038a684686c68ae64a9f2bd74759f0f6f91ae0add030864421f0afc8def56f58f3131ebb02e99bd18327ceb0c3e7ca22c08296a5318ba31a0868a3e80bd4ef9ef8f5a63f2951efc71bce656b1f891b766f0adf619698aff31d5d11f02ce4e93022296c7abba33da3e83aecd24d791d24642ce0f88fb6e8c8286ffb887d22452b4fc262d5a6e915c72cd94c424cc98771194ba74587d8984f42dd0f5d2cc1a7e4b32d7ab0893de63ba3a20e54b266352b800dc2de80fbdf33598a68cb54ab9f38722c78ae0b425ea6ff031fd51e7fd0b27dab233ebe41e1b892f606ceabe49ba1cbc6edc9d2fb98f7b57e78297d3d6e086af4466bedacb4ca09776a12ec2b6949265a49bd4b6ddce37d9bb20aaf44b7e1284f9c4f5276ff8aacb3453fc2d2ba51c9a8e0c50e4fdc278ea5d839140e527de30726e8089e3c0c5c9f7d4b3887ecc5618b53da00b078a5e2bcb0c50884356eeb5594a1c595c8f74fcce6632446fab8dbd306c6d2aa59f99a01a35126919bcae6cd7a825a2cda0b3c2e542f41155747998f13e54c38f75d9e281205c4796e2ed31a08154c99653bafaf9d7502c69dbd0d3c3a740f3ddcd3b3e5f2786ce0c42f10d8e951c2d6e255c02157a94093cf4c4619b6651c44f3046cd0a2bd31ce0bd40f8cb2151dab3e13ff7acf1d2c3b82198abfbb861422461d3066cafbfe77cc489a52d3981f62a5c766ff6c2e29f3349c48c1c0ce748e2255e85d70c4161cad2c8acd3528889158ca05248b5e30d933badb774cc585c0a187a4b5424c9b15a322e0c5a6320ddd445eab8881fc7e34a7d751bf20a0a4f8fa6c05b55ac0e7eba2cfea298f5b3939383c24ead1a2c83bb5346958a5fce57c0e299b4fa4fa5601c06b96cba86185dd0bc115e215c7c7c9fc1d78d242f5cba099283619e7cf8aff8ee5f82ddcbbca6d33b869528fa4e3e598ca5d14b6251ae4ee0dfdab080d825abd6d08b91c28cc34cad010a3e6867c87c0cd14d00f2546d75e027765f88ac9e3dbde2858920ec6c6575ea103006f19955c3ac3cb5a8d41d33b8475bf17f05853d1b8c09a1b25784a66307fe7289388ab84bc438a46262f1aece39e3ca57bb1b2abf550d063334a1546812ea9d6c8a205e0e45b6ed2f6e115819a419c9578b1f2d1afe888c4b79b666937ec56d2b3144bd26adf100e41172bdaa1eedb2df12ad8006a66659574c006c344702e24b54fe09a3fd7d2a8a87ee3c55161467c80e26554455d2a0f0e4a4910c4bdecca2485707658734a6fd64483de1a901caa2984be1d108d66a9b5d04f6c2326b6cf8511ffd7a049959e3383604068a3e375f950850e356f5d2573e5f77a4ba2c04d04ea1b20eda36b88ea044f3706abbd37840b5f3787ef14094179129b54c283393fac80db3fd317a2b8f553f6459a7419d8b158589f04c5c70260255a1425be6a2de5dacd88961fd5ce8f49f278627b4831eeb3a6d735688b7a34124f935e03c60714db3ae3fcfdad8c8dc6ba4ac3cb206fbb7c4cd9310bfc0786f1b5e815b5360c49ee7e8d26f933823f05675343d7b9d53e90602b2aac9034a94c55c2e3210a6a9b6344e6d2ee78dc77e31fe38ce639f862654488cf0bb548eb32fc4be9631583c6440edc3327843cd80c96d27cca7cce4094cb60e3a701f845b02e9c91fd189dd9ea29c9e1e832d15084b59fb9bb9b213a02b70cd81c4328748b9a8893cd0bb1f845845ee25b9c1854f0166512cad266bf874687e298b8f42785c2ea1014d25e3b6565a9116941be22b8cf919327970bd956cdcd3dfd226c80e573079116ca1b223adca1264ab3962ddb96d0693942c0f33dedb2c9a662b9e3d0577c2f5b220892cf439be7c91b9fc89c270bd4f781bda9c1b97a8d5e650afa681abd0bbbd8c4c49641c0912a1a558a51243ff8a13fd7a1e7d0090ecca7eca19726b082de1cbf65a747b14a77889e1e998d769fa068f6341dfc433f85a41a086898f67007a9579ba3c29b5a899892221078faae6fcea923cf067a7d4484c1e2a412fd7ca898cb1eac519c3e00cbd8721b4ebb99dee40857a36c4db9ca470f830d13b5893788356ad0da70be61b0cee21197c6e74555fb5dfda90320a4284ab13daf28b1517552f50773421aaea469b2233b54bb2e7a78f526cfcc56c4d6040394b9ff8fdd1640d3b2bdfa797e66c22d2b29a155a2c25037ca0961dde875ee5f547602c66c609e8f6e53138cf986d80c78d0a5e8117ac743617797dd99970920daf81ab550f6e0a4f1caa0fbe1ac3f52733169a8c3a4bb8d684958a8c5029be4e821443a8f4145a04993ac4e20948642a2f35b6bd54d477f704a8ff3c7417b142b2b2e8591a6c0cec5c2de996ccb2c042d81e137e5b8c945e4cc536e678b11cd5c32be80e86d2f1ef04c897dca657ab54e4e4defdc425742c0dfef9383cb34c07b4ee36c5bdbeea8b84b0d25f58a3593b61d9a80eac846d89e50d9518ceec09ebfe11ac102547149125790c512d72ad759a36de382e7843f62758ea980317358daccb191184678632b5b5f1bac28e31ba1ac35605bdecbd9940ef1c7f252e73736057c43dc3cf543f284a4252f5fec770561443c5da8fe24e2481dca7fd0d38770cdc8299ea63a3066033626d8ad96b5393541a5b873aab6b5d53f8e911d02469ffa36e8c19df658716d0bf2500f3513811b7613eeb49488d02bb490858e2e017b17c4156be46083bb2b4e77d57cd1a716d0c249cc14ee368cc830c5698020ba66d958a59d0c1ace285f2072852158d12a856e9f31df085ba114c9a721e81447a0e2b93744e94117352b29283f8b541a1bd92f38221b339b3cefe0855819315b104d4cce7cc5881ba176991c26f5b3fb845416b30e7a3b2bdc139da18904c0161c42ee339cddaed9e88933bbf4c101925da4cad0ff333ee312e0decd57242d5f2e6655aa4b6afbe36b2ac9914e7bec2dbe0f8cc107113b5576f3c8a4ff7431110e59b26352e81c378118fbd9f70c1da569b62c64c8c1fe47da2deb45d10192a234d5089fc44406ad98596d915e87864573f2ff3f8484888c8fc5bda034b998a2b71450891b70c2257aff1a81f5ccc61aed5099702ec7b60f12985228763492c18322534ab3afb11105febb0a3f394a5b1e19540ec401127a76b16707273e39e80888231e373ec35a71c711626beb21d2bb07e413926454ad3d4cd8dc9381fff122a73fdb05d54b0b02e4aacf22321e2ff19a7e6bb9015f6293708a953d7c16b245a55e17c946cf08476fe4a0899f6dab3c14f7a6b284cea90375b9f3b7724dc781498297a398cbe274eebb29077002e863af2cba0c82e31562ac05ad5dc2c86331251ed000a1c830984e3c777df04c6dc178ad935e7683a4ad43294d01f6edb9e1797fa15c9191f88f6185992dec6ba7ff961be0c6400014790b17d50850406bcb019b0d0eb628f40a3b4366d68d7fb4d0cc4aa706bfc3c8f4030915b645af3957f1b469189de2cecb86f5236e225db0696937b7d3d56216548358530a994fa7fdf2276ecbebdcc42493b4fcd2fa5c71d9d6f166aa0b225b37e8031f67da4fc222164e2ba52deba42aba3566fe4a6105214376d70d1751f284ca131d213cd879792fd7ff1f2e9b1aa9f896b8924ef7a8e45dcd8fd97b9e92032850cbc3ff76d330dc40b45d46bb3543bf69f051c598e303b568a0e1e53a2c5542e8ca32f4336011e36a9805ea5fd446744a4e5ea5f55bb7a773899e43ea13aaedc9b53bc1a1073c3fa01dd01da8830228726146c8de594f386a4c920009602807bce027bbab820c95335269406bf82e703fac04638089b38fb446ea734d1d178fde7f1c4e940d2b52a8e68145445cc38fd55968e9fa3d8feb9b676d080750fa7b698947971088f12aad99ddcf41f1a5c93ab9ac679edfe8850c6ea425ac39c72ce2b5b814bb3adfb2f347c3e54fe9adccb4411f083338eddfde64ba2012d45a5058ff1255163dd71e4fa4ae4da46faaba14297ea9efd2e6baa41a1edb9508e3b67e3bc6e8975c0a29a2fb28fba630cad14773154d2b1abba50cbf7ea2108d8e0a8ed1593cb19c506dcb42ee260114e6bdf8821146933cf7f5e3272b13dafda40c0169a98218d85eb79672bb1b2e1cd0853262e944ebd01d21602514a05e342248d4b08e22eb43f8546b4dc430cf8a8450393ecb1a31b9d0dd8a2a61da570f94897f5beba7a462c9f5367805e043f235782e7e537c70eb4531f3d3c3f1a39be4fdaed6ead26b1ed761e539c2230655b14eebc7541b234a14370994e7d6c78af4d41ba467807ce2218c4d82fc1087a332d35d99b1176ef3696835e2e40cb4c6b7ebe60edb414cc331e8be095a3ded7e6bf81c5d0dcedda76d04c2aa7a1a0688db4fa6aa98f3c45d61210d974c0046fcb5d11904094c6700b73d1c48cc20eef16ca8dbbc0181e88d532d18ce1a2f74b5296c1c58ce32491535290fe453d41413d9449d0cc5fcaa79b88d8423569cccfbafa3f502d44b056b689911612580699422ccc95022fd7ab5919c285284bfecbff3a04e50b7614d971b542b4c4dff36a5de5de251090899c8a99b8b2ae10121dc2224a5ae9e0e84338545802c060681e0ce5893465ee1d2f38a883f85865d4b43c2a240dde6857ed476803c60dbf578d8dd1fd2f32172a9d02b3f94c95ae5f2cdf2b065452615b8d343d91a915516b930730a58147428dfa48064e4a99fe91d550bdd03812a92333e4b3a78649d1f69b484fcca9a5371c6367682e864194a4ca01d90eb79ceb9a35e34e9a58adbc528268d2e1580fc197a89b6b03086588fd187cc073898f437bbc669cb128e7b7cdfff8544cf517f2b833cbe1233a9aedc78a7b9f446d3530d1decceb6e3c1e8f5d6419cf025d289d75badd0f3e18987558f50b526cafbaf35a6d913aea65b16db180a0cde4c0ba6b463730267efd591483f515730fdd8c224950870f1049d3a0e0f1a4c4d2efc3f125466c1b318c394d0f134d919c289fb21801438fb85ee4eac5a561d20cddb2c176733a4b502370d1ea802202948f44349da347a7280f466c96c5242756b710a85d2e88f0251ef70031e3e85342a0a960badb1a693dedda6da76e02d2c0993235a9367a14e38902903663f6b3bc7033108de9c170f68418c40a3f3b9c90254f7f4372858715776d3905f7e2cc4db9dc83f2c9242f4d73e5cca68273ea8a5e9048cfb50091944f7987974b0d8d9a89fa6c8e81456c4f5e1240e889f36a26f526b753ff549a7b483c486407d719eee4855cf8e3d6da9b7c4ad92e2cac7612ef1ff41f5264711a8a797d6d424921319638aa1652a77a4c109a607182f9a0b22b2baff5bde75c20b990697cd60068bedf5463181ca2f820fba50599e12eaa09f97cea7f7768f1526c4dd39c5158167182c1fe9b82373865a29ae35e1d76956ba68ee21428da18121c268ddc9d38a2364d695a13250d139ac47f5619fb8afcf04fda0ad034cd5971e1d06038e18f431e4c07c3953f792d40e26c10cdfa523be712bbb263cf089c353df708a60d1e13ca5ad964ddc07e979b15b208471676a0bccce2238b2d5e6710a175d31eec4688a80c66056ff03fcd10fc64244551556fc2059a7c5058d4124fd39a2cb493a13309f1e49d3200f6e0436e0fca00fab147443b7fde8158bff75a24eb7c0acfb4cd047acbab1735b61a0eb6d61fd81f9e606a0cbf94a624f30649adacf1c742fafc048c25c8e1c08543b105bd7f576b6d38834673d34abd69007f76c311c4e902bf80464f914ae5acea4a4fdf20c1a67a41e6153e3c87ce422ad85c3595663c108bbac3323f791d275387bcfab21666c357078436d9a9f9ed2208320f0cb6322c32125ea1673579a3eaa76a8f0e726f9bf2c0bea7abf25aeef491fe22e674ecbce9a7831453771a1614cb67187d419a1ecbf8ffb5c108b085d29867d7e9846d4eb6653d4d99dffe46352533ea85e793fd39386e312ce8fb5cfda468903db37e52c3f3281d889acd0f2df8afcd19bc29b4d8aec5f156941432d679804700c38d9c07941fd17d5b43139f192effe95247eba61647b77caac4fa21584ef124b0bc24dbcac1adb18bbbe264dabaf339650dfd09f5c0f647915c15be98b165a8007e345274b0ae5a72883141e56722762750415c47d6f36ce0b0da24ceb7274ca6e1be5ce260322b2a698ba737bf7050fbebabf344a09f1cbdce722e7f566af567dccb030fcd51c9f667ce744f900b22ff84fbd3e87dac73e0035517eb2668a14726758cbd9710c944b9d51502442be1db150591dbe1b078e3d7dc020030b32feeb27c281600c928b3a36fb04ed2d077d4dab56e97e9c2707a14553b158d1692640ee8345c3fb2fea77d25e4a10ff8c2cf10df133e888d6e9f23f14d0aaa2bb7731cd578bc5bf541232736022dd9d0c7530b13059465556cdd0e5a4fcbf2bb92021e21ce9f4da86dc3a93f3eaca39dec791e11a7e6ca4f37594f36b1619221e90b7df84ddc509b920a610b61095788677c95f09b4867f988e7e3b10405f19ec6f21aa120c26cba98f05faa090a50d14288d5c6542fedc914db46f093dd0df6c61bdcfc5dea94ec72e647af7951e8e884701024296417074d71daf1f269bb7f6f077f76a3bd82f272c6849719cf20ef8b0bf9bbf56f7ce92ca2c17f0a217082387668bcfb0cf272312155d8a28a45d3fc9edd1e733fdfe1168a96657cce44b4f891be357f75dc98f826b4d4590925efaac589ded1edde8a25c4021928b612cbfd7c1bf46a7ac8f8813818112eee90017cd8a2d46436b6ff39e169a8869f69773f653ecc2032f57f0725745c9b986ee452a7f064284628f32599d3a88c429bd34edd4a489b5334f1f4dcfea84badfdf5311ed0cfb48d2bf4a9535b3c4a5b648e8085896783845bcd0f53bb3eee625082e383883a4d0ea9b3912c088eebde91def124e4cc76f808787156a62fd041cccd82c79c2d1ba2401661d22787125542e19655782b635b1a6f99cf2503d7f5fda5ea6e2a73b6bd5d291fd5e916418aa11fa78c2c68cb6036805224e562df0ac5ea32d3972450f5bddde9f68b7087328f846fb8b6f81b2ffc41fa3e1ff6e3e52a2ba4fc56eb8904b832a4dd51a31799e4aed56acde29f080e65f6388dafc04e0929ef018234fe9183c41b0d41694070ee547e18e77758a574b98518e1b5fc037d11a307ffc2af06c03dd84d68cafd7129c1dc855b0d829fc163d15bfa82fb13a929e3223e52b0a8b46e29e0a57618064d62d5bb301258ff190132728490fbb4aa5a45e741eead75352ad2bce7dcce531e20b9e5cd2c3390f1d2517139fff5d6b422e383ce9457571c8a1732fc160e02aa97816230adae105acac57345bb50959dc3f1635194c467068c4c05d4aee649594727181f8d8492e73862f5cd477f5e1823532ea548c301e85d372a72c968620546494e902e25d7922ba2029b7177cb8296b58e8dfcd16662e0d878c5b33c769df460822a6dc6e9a63612c2d439aee9bcb48bd332f99d1df7314a01f25e35cde0d85e89150e2039b0ad69fda9642c5095cc09f7d8ed98bd01caaac4bdbdb18e4c8deb0ad1b632198053546057cacdeee19b503e455d424c111852c65b1cd87dc726b9c5e6218ce5340d446150fd3e2317479120f978ee9bf60420033c040dc3d667e92885c6ddf33763c4dd15164c7803158194ce2194e88e94be3548225ddc1bf29625cc0eaf498d97ea822cf1dd7e31e67ba4d51a94dc327d85c4cfbf17340b8d99b73248d3c3cdc82442b338271a0fa01897b4489cc5a2defc5b8bcc00842c9cb252a60a03588e1294016b3da55fe866df3509cc94dccf0a410ce99e40afa883971bef29944c619217975196ac4a78166343d899bc0e76f87842695320178999c0ebd8a241f39fefafc583f2255973ce4c27776c36dd1bd7d78d1105686950561e9dca5d172ef96908c6ad7ec6f563fe314eead35309ccc54f983a0b06aa39f68ff588a982c3e1b23b03d4a7732a8324e2ee50ff43f98b3158373e9b470794c9b9bc1704340c05ed490fd03050e0a1f91a2d4831d4db04ae16db5b0117f6e5d81a463f52230ff8bf52e6797281089b4015a381fcdf019783c291b55965fee711f1437089a28d338722e070e8e3e71909b1196ace829e45fad7365dd8270f4d4e6a327725a1635b33b8c4a6734da08322cd7397cc6749342883c7ff136f0932286e41a5120fb12c2d1d18b23500c608bda0029e41bc0d6a038452a12d34641c28c6d42193ba12bebea464eb1fc5a00ba6df921f415ada821f2c65d87a254dda1f4a0fb3c9448e3e8fc4fa2e8f39ad8d20a91f7bb65f7c4ef62417feb761913fd68860b20978da8e4133a3ceb95e2388ad1a5bd10388a795d9ae8ed3cb979763f42b912a411cbaf22842607bd27417fb7a200e9f61133d1b4402183267f99bc0369f42580d9e90819d762e5d5b5ad0182cc1acd4f243f5fa1de20bcb921d5845667c453ad3649be0316e54d17adc98bea1bd4fc46a303c2ad18d1a81f721512be64fbadd6ae367c52d6c91f663cbc41ff79aef786ca552ba820e01280617a83059db9430f2cf0e9550dbc890f0af1309cead5e0f209b0a329b5f315a0c8df0408780d40f1c876dd6d7d973ad67818a0734da102ac34aea624e3bd6a54815e895cfe6f32a9f460d07b186c59cbe7b0ffa7b91aad31ba977733b4f2fc3bdb758fcb9c31eda20f69c346368bf1713100dad1932d0cbea5cd8ec141e68b5dd5e8c5d1205eb830dc4a78fabc0b80ab1698ce5fbc7e96abbb2a629b74db6488ff4559050c79b54da564e5305e21b10f799cf54b5e347b432ac60021ba63e53d0ada9e384dc2cac71afc203e2f180773c5a510030765143a7319a2bcd86513238227c10ab23ad11fa8f5628e27a555d548309017f61a685ba17ea7f5add012607a1e9ba3ce8f4999a6f12526628f7ae8ce0e709867e7c550d026ba9728a9d71a31cc110cc202d9dd4a61f091cb7852730fcb24b491e079198a4913b26c2b0f32deba055cbaba8b184b8f4f5a90900e8942e464ae1e00b52ac29f3e40b7704d9d074ed8dc368e08d46954a53ce64e0f3d99daeecf4af88acbfccb9a7441958494d18cd0e3f3f2bb704815b1c595937852a7a26c058665297d195f41b1cf3fa17a3e7f66213741450116e043025b6a56c1fb837c641b00cfc259b17f47b367be1fb6949582a0a0f8631f2bb1fa63f3b6f089b3fae8acf289451ec8bc2f7c832b5c29e415efe905d3aa3527b5761b4473b480e00e244d990100e3e88632491ee5d7bbdfcd69adc481a15098e42cf36694f5efe0882b005c5a41903f0f7f905296c1d3787e00282f164c2aaab966a788770263f095adfb748fc416d63043e45fe3aebf159352364de4c8e6275039b1d6a2e124280a03f5e6d26529d855f7aec10f8ce40afa49054606028dc465c9af853b3145dd61d875a0958a521a18a55f6d286e60d160b83f2cdc27b4235e650714ad9c7d00e0c347e8071a1927b880596e745354b28e82ffacfffc3a59a006e236586cbc74f1fe1d3cbece560e7ed8fd1fbf98f0313d3c1c0d9d34115e38d07e831a8f87f01b695cfa39ec8c3ca4908de6abad71b9e3b9c9c24e05358589708d413934ab1bac654ed9e05c96f3ee89b8070c022d6d61a6f7c55592e8c7565323c1bb0fc3e30781b485090d0757decc9ac2b3d09a1894aa213b74d18b588cf2f935528e8053a94f940fed7a03042a109eb702513fd9b57d467c1a27f27d9586d09c337e34fff214cd8cdc6449dc220d581ae25e2990d8e407a66026338b64a1842d57c292d466e305ecbb87580584136b28b3b8b95f573e723498e2a4b4313e0fc3754fc9b31bdbe7e1237f30aba8b6c72b66f3cd4bcbe2e01ed602c88bf20da00410fee28c37def869f5d06563cb232bd8d245f06db57deed11d1f59849f6b2ec8cb075e4b9c76c48b8ed47a1818884d306f2734aa8389c5cd28fa0dde97c13cec3c18cfad30cccb11ed9e213c79b95124ff1297b308cde9ab4ca097a0fad72e480b95d06784e572752ec1d31e0297326dad5725a8c0de32ccb4ba5d6f2af6f0e0aad26e0df52a25eb01b054c8e62e68081bb606633821f73d84c2d02df1dbbe98a22d453113448873f34e875d329e502a743acdd0e6f3f492f5874dab25d3c9a13b9237ffcaafbed1e071443c3b9eaee983586663964538018b2e12053a147584fb5fdbf5e51bea540c912f75cc332acb2884c1a3cd192b5026abe19e01724d8c9429b6450c14f4dbe59556c33d4e324345c14af86cf84c17bc26d1971752687f1f535065cdca38b7ca3a9b9ff6986b4f4335f0d1bdcaf6bba96482bb56384b727f5ea82b01ab96f86ce556012c03f06e4fac0e551a417dd43e9488869d55929df97404c29da98ac4c1d4952b0b25294a3ad89faad5438026399620ce36103497885a595b3bd4a0d526a04480dddcb233f07464ccd0b1e362a01365516deecde5ee9e28b329e2179504515b1930b6107a437a3d65a866f206708c879d88682c74b673f2f1dd99d24908c68604ffd81056a82a2b315b8abdc828a735b3e5a6e3dfe66e1917a628017a0f2856fe3002574b3c5d46d5f5b48a971a9063f1ae592d4f456f7411544c103d578b3d82be3a40c90fabead4c326b2e359ef86d07bcb7fa31ffa43b6bc96dc750aa05c4e7b3b4a4448457d806f9c8df1a5b0f53833425ae384e48bf565ebb45d119414456350dc8bf033dcc31663b2e0fdf523d8406bafa2010b2002b82823f15af74a3ad7c4c3e87fa4e03b63ba54465d5af2ee74988cbb32ee29365ff2083e21d85da37efc84024e6b51b541f8103a3f4eab92172f527d12360c74691acd3e77f1106849b51ff7f19a932900b531201875b719acc448b6b792741020ab055262581e78213a028d13c3d901123aa2c8ab098271374096ce0cd3ce108ee55f5e145c1f2a60157851359b7e5308ef576f08e3e9227233e868220ccfdf6ede96b6344f3541a141d1044791d8f08c30773a50df496cd3c4ef93e014870475091b88e36373e984e89994c5641430ba57bd7553582bc42ab21b88e179354b733c2e6ccac454b283ef2f350e02b0cd887fcb8bd9e8cdc70ccbe8024598fc03ead9a6019e866a41590211ea6a1efedbb79433e6020d83d6f690624e7ed401d6e90384a1e85d993cea9f83c199e16e462be0f383150186cb789dbe08f7999985d028b0b328708fb49bac9309f99266a1977b450abf80e5e59990f0690ebe02916cb5b2817982e40bb403afa0cba3a58f81ec7827f61484fdcf27d1169e597f2252c2188ddb984c8f4f0e8514f1fd90aff02e878401efe45b92dfb6ad4a317454776067fba0ed215a01e9238790280f33bc782272746de898dcdd85436a30814318ea9410618f35cc4877c7cad245548eee207d8225919594a024b8afba397e6d689bf4c15e87501e923e810d234799b415bc869a13fe4b1fa4deb99234ac7f561894783a90e447654df6faf1d98a9877f61a148aa1b2ba37d2624babcc86cd63497393681e70c67bcc959407ccc0600dc1f20f8770953a4f87a539206941ca30f24cb7e9d6062f8ebac08d840688818dc5ca9f98054a25af61310817e05765198eeb76e7c86487bca056f582fd1a39671f70657f1f59ba5622000e5443f6a3c442eb4315b6787451321f0e5efca5d19bdf9d0897b2e71d4c2a7095c2a13bd7591e1f55e7614d9deda69170ed7d1111f2c6216d41e8ed050bee96c7cfdac1318086c2915b955bec2821441269a2ed0cb5b114a82a416a5e61e2a3aac6a16af8a4854c242cde70c27ac553f4e939288e5e85c7a1ff319e5ad040750a69ecd917cc87418fea409359f757f46a28160fa2b457e99fdfdfa8ea7070adfac3eb5edd733c55e929ac8e364470da2ce336b8db6f55f3276d9da172c8a1f397bf59640307c2679060b7a183a6c1f4f1a5ecd19bc74c82cb7883ac9baad7a4c11cae903186766c33632bd780681f34bc6991c0233310b9e8ef70f6a946bd1186ad330b1668bebdd19cf5861fb06f4078f5fdc8df7d4893f208ed8be7a0050a5fd52f76337581ad61b9a967105e5cea8336efb52cdb3127e0d3152d4d1df52a94cf12a23076da06c5f63c9e725a5c23fdbced24fc4c8af680a56196dac59d8f2095124c0ee825b54ffc3348b5587e075a6fd6aad72b19a40aee821deb17be888fb4c0b3eedc467dc76481e01a506422bc1f2d977a98160923d3a75300934132e2184b80ba5d93b93fa5fac96db9eb4e43057bed1654203c2f3f2baf095fd335e970772e52e69418d363974d82829674ba0a3c064fa407695fcf82f1e691b6297df41d5312b8e67adbf4b2404e9b66e747cd3aa25abb9243111ee1f82fe560dbb4bbce29d9689f5f6da7e59f96dc046a7f7d85633b7210e684a767bfadc5ccd200f5649d0985229296971d5acad8fffdc2c1afe5b8627a8437e16f754dcda395d1f49ab7189988e3bd9fd26618659a50ac43b1bae60fc304e0c02ae1868cf7834bdaee9a5c7fd0cc6096eea06d17e85ec997dab35352eb77af98236451ca72018a5aab42ae11b02fe99ed0bab03222c82022e1bc0b2b0267f7572e3f8c4f6038e82642b023feafb046d7fd3c116be8fd7da587881c0ffc5ee8796613ddf4f82df91398587a629961ffd65da28963ed2eb25fe33962bb4b86b97849f434d415a21b473e6f0c9257ece9bd99ccc9cbc2c04da63a093a1cd6840617407ee5ad5a84dd4296d09ed2c52ac376e4de4303a4e3bc4aed34aa1aab836386f7a6658c3494b9f2dced9d9a96186b89c889d20a985266bb8aeed13de92a03e903cf33d54597003e49fb427172f988ce4aefff334f84f213eb76a2ca398b8c1135fa2d70f74f5e52a695f9d974c3dd69f8c473f5094dad7fc92e092cb212374182639e71cf99ad2fd4c9b97dacfe3e367ac6c5438de6169c7cd4dec5551bfb1e06be18cfa45b146b08bd3f47a6f105e54496f5301cc358aebb03d6ea5f7919a293cc41fa6c4305a75379eeee9080c2b402cfdeded5802e1c7d0e3dae484279350e1fadb6206e12b8c2be0041666ccfa3a927bc0267371cc849cacefc73b5ad89295708113ec15cf83efc0e76ee14d252ce68b2072b25b8814c2533c9b387e344dbaa3aed98fa6e886061ba3afaa42c566ea84e61d7ee0a9e1c3dc5eb74a5d818ac0356675715d48b9492ea2f7f18be5317227d7e0bd8163eb2cd7cf018379712ba3e23d47afb61c270c7c4cd25321da3669df270526dcff1a489fe1db9f313f8abf0e3dec5e7ebf5b01693499b011fa95eee93e3b471506597d4f4ff37e0d2bc9f1dcfe9b259baacc350f4d6ed4bf7b891f6d5500441bb3aa3a9fe7c8ba49faece7be1489c463664797ce8e279a4464162c22706888345828fe810774e4f6b38885810a1146e84e845bdac08a3eb5f35fb9916c73b0b1ef4e3e4f4948d3272169977558048fd795f11c6519b8b63e2d05d354d24fed29f7be3fe302cda5e24c7a52ea7d27b4f7988375d48b66d4dd0fa5da3efc27b562ac95d895ce51ffa7715566af1cd69f7336572ba4a7c5280a15f3373635aae65f5201b6b676c86dc124d85f164dc759da2d5fa4efbbbc5dd1c4f91413854a15876e3f748d2715f98068581421b25f8ae29ad78c69c4416895bdca8c46e03aca97be88e2ae480449c2640df963708162b07b6200297c07c29f051de67d817d3988db1a879ac6167ddadc78b4e61b831095e4e309a7082784d6afff3ea16ae3f35cfd7ff1288067ebfcb284b4f07c61e6feb52750c79a069a27398f4759838e6117290eb37fdce596cce00814bdf6fbdcce3af8466de3fb9da66b9d2cf9e09bc2983c0071f28ce3c67513f8ce561ac395ae40a1d7740c6545c667f94ab2301b361dd6925445b4c558d02a2b7503d6b185a5026001f7ea6fcde53a88e6f9be701b5280b5fa57a98abbc6578cbbbbfe9183ffa71d3de546698ff0a3dd8c85e19de7ec210443d5e84c0f774fb699c25ee8c20210c51c28f001037289f6d27f312f5f45fea8006b110f44c100fed60034a299e4b9b35f216d905418041a35b26140e196fd72a06c77f61cfce704164598b54f27b9959723aba4b8b2205a086141877b223f1cdbaf25f172565ff9b7cf077cad9163b10a1b68809ffa3fde074fe796546bdd39fd8138c5c8ebb503d69595cd968995113450f87beac4c6fe2b66251029a43adc74ccb5d5fe21993315e3434ed13f2d8e81ed7dd60e76fe52f347feb704f036470c720c28b965fc656808c2393c0b6a1b517977d54d17dadf34befb4fa25ea6c7cc0a12a4cc00aae7a174b443e483ea686f414049286f627a3aca5589e935fee235c3518f8f0c7c20d2319e8667dabdac051178d4a4cbfe77ae67cd2ab45aee7dd200ea90920efd30717f8bc367b01ffaa0a6bd3c0d8b8bf8550dd81ecd80ab3d1f4a248d2a527386223317471fca78cf6b619ce7e36f052882ec1dcd68b212727de8dbf39bc9c3ed0226a910bbc6ffe5097b28a229220a6382841d653707fbe69e03f6bbf280dc353d887b88a093c403629fbebf695022c720887bfa65772233ba09f782c8942a90cb1dfbd635c59390297111e4e646334a669c5e573f2d1ea120eceadc5da012b584acd9237927a702ea2f97d172d4f8fd0f900e6a1874800bed1ddfd771d644e99ee54c58c5e32b500b8787e6fde59eca28b8c114d14f834c74a1f8546fc3d3bc745ab9b423b369353d0b73a2b9c9c1ba3839b3b91a557fa7173fd8ee029698c59f0de3bfdd4fe0f70714aad4d743b6657f1ef7fe7f1e794d2b33d068a89a04d8051c858f90b14f647c5b1214e25ee8b68ebc1ca8edb6e386dd18ce9b45fae10e1111317114aebf4051acc57b53fb8812c68a8d00deb63f130f05896aaa38bbcf6f563aae9004a3eacf21874f74a4120aee8380ad2e459c28a3ada04ca520391d832e2d83e5de943b110679f3a096407dd3de0ddf802b0d98d3c1d82ddfbff4e7f3dfaf23a888e3b917ec717028395ce7372a8f05382201ba57d1fdc37126be791e2f22feb0777d4663a940561a2c457d575fa57cd5596c99abb3825d187dcc7e42cce4e7f2f644b6636cefb479f7f9012b7f0622ce5fc033c7fe846f076808a11918afb3e4355d223abb820c741f03375303b94613a16f926beb660ff832f46db3677982ac8967af136b65a8f5dad46a506485e3cf24ba69791961704161eed3413c376975940a190d2152e5b7f3cc7660bd0667adb8063a90d72da36ea8017647547bf07ae857f5b36d55e23656493ce74b4059a409a71b054a8bed97acc3966334cac1f98706240204f14b433e3d261a7b1d08a6d73309bc9fae4a3451991395ba6e565816f8ce4e752c7072d440a41fe121295398de1760865b178e10515e3cc3608139eb711df2fc9543a2287791342a4ee66ce97d4967d1b200f549fdb3a43479195541896cb4bf5aee709e8116c93d40eb0ff3b53519bdd9818bdecf69e7ce0013a4115715ea341b746a4c0260d3faedca6b0b89a3a55c7080054764d4e7147c5193e900b7400735e040c24e0f2023266b9f33d4d9d4a037b742a07ed223ff4640d512125c2484caf71e5439097ec2555377a3780e788f266d9058c33919017843aec43710e98f204fceafa42f6c1d528df1e2ce2084958060c6f078a4db164b9a4eb7c3e2ca2bb35ffdf49b0df7740f21f0c80e5ec59367c2d524524d09f32529eb4d3af8dc0f08e65b2277c3f2fabe23f3d347d8e3598a110d1c66860b2147e1e851a07270c4b052460c32cf4a3381f13e79b39ffe91962da126af94c9bff80196f0fef0a525eb4ec9f2b63b76bc2ebd3df1e4a3467fc88c12cdb62a24ff01b80eed8303564a02463c0989b52f669f70c37045823d470b8602fad1a40681e7b23effc6241df80b61be2369bf1580f260a414b5c87343b26e8c16fd510187904bf8fad3f5b1bc2ce905416e0aee0f64988f6269250882cf9dc832ef83b4cd21e035f7ed89c11d351c9a2971f5517c515e88eefa5aa76d65b90c5752550bf9bdfd0e3ba634129c3c5af487f11fcd480d82db932caf0fe43defa070a5a4a099ee2ee033054002cf5a4cfa7c97ee8eb9afddfc0dcbefe3a8f56a9ca3027669060524e79a6ba13c3eedc87cfb051346b8146fda85329ff6765a983fe848ddeae3048aa8911f322580e5a7dc36fec224d47f15a324e43fd4a091c691c28f79828c20461c14040ad48a9059305816a503f2460d49daa6f7c11696a91b2ace1fdec5ca0e4acd0c517debf1827a4a560c79d233ee01188823eb83f6e31fbf2120e805054e012fb5b9acd453c1ec3e0a9eb42c2fd3a71e37b3d6882464efbdb7dc524a99520a8b082f0924096ce3f2fc31b04de9b7ff0e70f90bc03f7adcd45c8699a91835a697edb1593aed47abe99f085a208c03472317d2f3921b3f15395a4d0f84505115284d8458b2fabc6b6a22c4922ba4be4b57eada51f1539ef76f32d5486986d5ee5372499fe3ee84d25a4da67fcf4947619af8a4e824eb2d7f01763cc4ee6b14dcf859eb8b17b232277cd1aca2254a04ab387b6444a21b185525ba0437c227c2aa91c58151d3464ebb1fb7286732a83097871899263e864512f5e3160fdd4d24953f3b69647b0bb0ca83a898c528dcf848fa870f3407c6f1c18403e3c88f5f01b6a11fff48ffd070ae8f6f3da83a0263e81f9aa5ed237299fdb88b25da48025e82bdf8f448fc4863afe0466ec59f338051a7d5712fc608551bd3f931c79d71fef5eccd39d342340c596760589504a2f8474ffb4892823dda477c2c7ef4e6523c299aeaab3577592d05553d3c444444695c807dc48f11034c13ffe32624619a780315d5488f7bf157c840cd3ec5d33f7ada477c9389bbe809e34699e44d5a4e44a41425da673dd9fb50b9381ecdcacfdeb9202de9921d4c7630e91d4baabfe60e37e8313a6c814629c76fe6523691e88d3e3f66dac7052ea55fdac1a4b27b2ef6f3b9f4b5b8757d7dc7924a5feba6ef14c228e3c31bede773c568dd7a2f97872e9699a664bf24977eff88c1ab24fbd5113733837b300693bbf7fd70778037da1c6d54fe90cc5c223272965984069fe5535aab49723ef25316f56b2b577e3de54a2b526c2f91d85bfaeda5cbcbef2aa5ce887bf2b7aea7bb0148922c27b9674e015c6c270de49efc1b3ba8d98fbe81b6e81e67d93dadc76db8bff131ff5118d5cb5fcc46476d2fb76deb71373bbfeb46b40d712eb9d0d8f60ff9b1c42a0a51f1a9cdbb17fbfd42ec42edc267c906e055fff0cbb4bb18e5d1f81ee338d3f4e7a1d806e2a061fe75d94fba8feb2fdb3ea4c52105a5683cc331fe57af9cec45469960a49561e662988d8e9a636818e39c6aa8cf3d03483ffafed6ba5652259d6a7079ee8bc0fce874aac1e5b9e781f9d1f3fdb970dbece95443e9b9e789f9d1dbc02b2641949efb20627ed45c90530da577f922257bdabe74d28ac4d8d3668b909494eabf3c4ffd17eb43457b1f97afaf591e5289b91d262ed6870a8fcb97de06ae2fcf3e80eb5dbef4fe3563b8d8afdf6342e8676fe4eb217e3d3414961196e632073d7a7818bd26f486a2853806b7213dbfce0c6e537a664e7d8c35f22fcf8d9cd47594daf5144a6bcdf8923aee8ede2c7bbea2b615292addec575df8650d5cc76dfae9477b3ad5b03df745467f03b3ff6e341eb7f93ed3b4ec6f80c0cfe93182047e0faeba2114037c1d0dcb201c753f2bee7f5640dc8395079eed39bbd9c02b1db8d91c1f007c5e610e2d1595dda3d67fba64cca5131a1fa065646ea497c43ecbaeebea2e4a4d1ea40db1c3184e5b2adbf90c2fef27949ea394de5c8a7d89d48d3a4dcbb22cf34dcbbe3b2dc3fec22c4b8f269899f9e740154a6b894f4eea4e3e7a9ed1e85fac0fcc8fdee7e549ffb3d26a77f25277727f97fa31d607e6ebfbbc7ce98847edec8e1849a24e08388126a1ee94591e785e1ee679b081573cd8007f87ec6de095cbc3581f9f9f950e1456cd66f918cb2b98b7a71d983f65d6065ec1d8d4cac57ed7e7c08924759dbe5f3ebdd4f1aa76bc2219711b6ae3cf41c85e45738a452a85d16b4af786c20a2b806c5a86d16b426f247c8c1bcbc8fc78771037373032333d6a6a328c5e53baf715db651b8625faa70ca334cba80ccc81af33037c1ef833f0bd0ca3b445c506ee132efccdaf0c5719d79ff20cd72c62422b92a71d86fea477f9d38ecb9f6ce855e94f455625fb3fab51fd6a3fd2e8e75e2fb3a4d27f7e8c310c7b86b0ca0863f04415517c981bcb9847be6919f642ae2d5bb6b461c31f39055c9ba9d248230e3448c37d1b3532a1c243df1d601bac2bf935eea8fd66bebfac0bed3c11f981757ba0173e7f462e7cc242725e14cb8000c9b0ebc7bdc1000244757343afd93df03c39af99197a4d197fdc3b8ad643d492c633d767d9c32c756fe0f82ccb328f8bd13c26bdc6dce8bb23d92fb5731996f83f6ff4fdf57af8a774a07f8fc949cdcda8f99db94392bab687a8eb2b0ba1761bb98ca2d55c9e7fa9a82a7bff1e32c398dd8b3fc81fe87dc557493f320e7c5a03d9356b3e1b172e818728855dfd7ab8d1e7c69883da3f82bf74bbaedf5ca575596cfb23a8d35205f81a1ae754524ba7bb41a51fd4b1419dc1a57db40f7f26ea217aa21d9d19dca6a18ef6e1ef704bce0d4ced238afafdf7a0dec0f3c1ee3bbb8da8c76d3ec67283c9543dc6de41c9ad37c4a836fe31b84d9b4c2c1d45d4af9a58cbb2c630da5996cdcf4e32649f39d76599f6cc651af69d61df18f69de6f33dc1e73bbf7f7e6f9bd6652719605b1ffc9da35b859164c4fe90c48e59ea4efba56e64c991be1b6354fc517c2ec6974c13396b993ac22b47fa909b3b8e2285a8332ea4d18778e10ffe23bd8928d643ac228a262e57a106d1355dae420da1abcd8d8aad52b799736a327e47db9f034f50a532cb94aa3019a6653d1cd146a4116544ac1e5f613a355373a660ed69222d510888a2101951374ac9d0b40c4208bfd4d58e348241240bf58854fffa7e97eeeb9503b9209ac6715cf771cc6d9ab669ad75d3ab75f7f5fb302f0e3ecc8bc3f51a65ce24e3dee6d20b8eefdb2503b45f920b3fd2adc298e6d53db7e95cbfd60da7c5410608c60584fa994c8f6ddd551847f56bf412aa9af6d95fdda7b19643ea6acfdca5659ed1ab65dd977d0ea98b437c987539a4e86557517825a8661442088f5c11218cdf18c42086c53859e8daaea750c5fdf526a822fd75913a18ca6d5e8d4bd858857d9c5807e328f818db4a837a59ca2b2c5169d462a4f3e1351f9e88f80cfca2664f214c72a1fde80ce0d7f06f866118b6759122b9f39b5252f4895009245ae7cba9e5f2155f984194032713b58c101a11bdce06f5bbb9fe566aaabbbb7b8ada2374990bf2566ebc1ed7ad855951112c9229698b8c7c84ddd9a0fa5ba9fe1f17b5cdf7503c0f591f640955a1d5242474bf2b0b64614ea06ab2202a42b9110b9a72b5645e7c980b6efcefcab2a36981a80883fa4d4dcb8d3f599355194ed68dbf234ac52edfc089d01d627141f108dd3ca8a87ff9d2c0f7cfcb57d608e2c6e7314585cf438a28acd13dd078e693555a1746eea018d48f85fa892a04b1b4206c412bb55fc0838aca97af44410dac9abcd48dac0285a75c58c19310b0d0a47a603e6149994058e124f017ac2c454d60c284279a587914ae7f8de222faa9824514222d2d289cb8c2440635c0019117b3ce14c040e3410aaa002209fa53646967a235c512ba5e6e4d31c6c52eb7a638632ee1964401cae2074638e2889567e00b368a5a555650831c1011456bb68c52469413aabcdc12a2410b51849c702688105b422a9861e880460f5cc084222ba2285d6e0941112a020241ce979fb4d5e553f9358b269c70a5fc198f16492a09130d88e81a0c31c485156674b9e58328acc009723425891478a1c5d009b24c2088b450e2074b10f14bb2c052ad27435c78b9f5a404330373085f620330890c942e7c53ca035888f0f324063e40620c278e085263b25670834b0bf9de9c5860190209126230c61a5b8c20011663e0e00acb77b0022e8eb82e27818b9607d0b8dae5561223b8dfa7a4179ab713ef543a33ec2469263ced7ce3369f0a08dc72bf4dc53852de7a250a625c1cb77a97ddee76e8eb4c033f36a1ea406666da1d8bb40bcb5ef38dcab95ddedc15aa5cb4bf6ee6d2c138ea9ad736394d9bdc77525cdea53341d4f59adc3431aa76dd8cb445bddc26fbeb395bddbbae87a155e15ff1da6a37ca344d7bcda52bfda8ab245bca3ea3ddd7d787783f1dae666d0ff1cacf58e8d22b73c9a28114d757dcc69d57d362db42125d8fb7ba0d13d10008d7b3d0d80b26975b5d10e072ab0b2b3709f44117f785cb2d1f44b9def6d98cc63b2e8344bd5e931886595ae5c605e1614acba8f32737445eec35312a256dc184228e0b82618df110ef11243dbc9ef934db360c8b379bd0b351f6b48cfad55c49ad8431044693fbe3728b4807d7c3b2e36661e53211164358586935717d5c6ef580755b5f64b9dcd222a8a585932bc513222b54b4b0b4a6bc6cda18350ca1fbc2e556184eee4d182c30b4c46ced3736a216308a89a2a0445554a22b626eefb8deed002304444caec96db8d583347a6006902cba3352ccffa2890b80cbad2f987891c665e172cb0b2d2ebb4d0a5b771fc52e04eec428655a7344cd6e6efa0271ef86c76fb0ed65b820482ec65650d3e5bacb7a7c3b172b3d89e3b867ae318c7bf7b8cff127759fcc2565309051b1efdf3ccfbd1e34602f93511257847d003e54faa998d83d17b82018666a1c9c69078b5aff8b97f437489f034725faa4cced4d7f4f8f7b2cc3dd998c2646cd300c4686de7284edb84d66638744cd7ec4da9e3ef6c17041280b580f0cc7eef180e168c85e875b3d381a6e68b820a4ef3b391ab2b7372f7041483fbb9fcb6e43b296723464efbd4cfb7976db60ed788897c3be2dcf65a6c1be2fb533f0509da7dcc51e062c286b815ddc90ecdb520cc37e623c37dc10eceed434e99e59f36c2b526e3c2da37e3757cec76c534a7fa326f784f808a1769895916f4fa0d27bfd8672f98bcb2d2eaab804b8182764be96795e4d0dd6a3e6eb713d0ec37e731b18c69118751bd2f76f15a62717a473903ad2b7979e392f35fd18a71f439b922aa97e8d89e962ca9db9dce24204976296ed377aca29a0ca50a737eb4edcf315261405d1ec4906ece963f6c4bd3d8d9eef0e90f41a471fa3db69f4a3cf3a24a30eb39799863ed67d29ecfb3199d49d76c6bd1bdde584c56245b1cababe64308acda069d42dd63031f9268ce2abc3c51346f1fd523a5c848069f83601aed61aa715f5b071d7a02f5fab8fd9cfbb18d0165d5c9acbad212d9752a1e532110d9e98d6b032a58563ebcea75926c72c05d2217194fcee8eb8279f0764c02b81189151b8f275a070bfed7e3c04733f1ef28e6ef176908cfe00714fbee7d5d4f4a87949d57817e769cdcdefb6c0b443e2cdecc634da0f89b7ed28fbfeaf9b1bb245f7346d76231b8d44b20f8485961c0dfdec5e4c74d3cc8ccc0ccccf984c2d34c60d696cbbb98921062031b8e4c47013b99b1e82ddccc29045fd642ec66dabca588692b80db786547025bf9becf4e830ce842ac85e3ee16adaa6a3c3c3f333f04851fb4bcfdc901dcbe39eff484ec7bdc805e9b1359765432c3704dee6283b8e4c4c83bd10ee694cf7c120e9993d86eddcfee6b9d9f76b5927432404e532d11466dc7a99688a1f5cc62244e6621b0c523037a4da9feb0f611827db8128f9dd3b5095044ec93458866118cfed1bae7342faa74c872475373bd37ddb4bf7f5f880dcbc97bac9d1d951f18ab254cc758ee55e8d769ecb9739a2cadcdeb62d320efb371df9b8a75ddb75514a63865858a8c609d13ef3ba947bf27574a0b6b4a9aede6064324ee9fd4d2c73b7d7816930f5291629a5305968a42f94d2178a75497cba6b56a963a6d9dc2ba27e48646ea5dab6e1c802d4dcce713a9c2c09e2b849c905e976db4cdc93929503ca57596e531b4afb111d90b88d3b71c20549c283ea0f01b7d97a6e30e2dbb68dbe63b8197e6ee7b8cde8e5c7e03693dbb01386b59452a68a6c6580e8ca211fb7e116104eae7c20a8dcaf9bf8fac9958fb5c7dbf2c1930bc3e5561845578b1c91fad4a9b3cbb847b226a6c9b2ecb70e89877141bacb76ae7fc605f1a75dbc3cd63b24a9cb29ba318e0fc72a258c922fdb7e321c1723ff8b49e29efcce89ea2f547207cc40cb1642b707781b0890107045a5b2986480b52a314e015a6700310d544df93e3ff0184e3061ade2fbfc302d15f83e3fec006a22c54a5a2afe3f9c90e0240aab68a9f4ff00bba7bbbbbb9bbb25d3305b33d0284263e85ebe8246153097afa041e52a216a30714a64326e702ff6304dfc1e8269e23ccd06729b18372b56b86a004f3aca57a144acb287b8f163931bb5dc6fbb3d04e3cc797ba02ad54050c580f9f282ed610c685517d1a8f8b451f1dbfbacd5a1c3e57e0de4d240d9fd98a853b8299864ab6a1c151f4866858b8a1df5e9b86dc3eed9fbd11437451d5a82151d2c97c8029b3aba74a3971df71adc5c84b809aabc99fe3143fb881fdd541fa6bc51c9ce10493656fbf9e5712fba1ced304d7c6e8afaf2a4d7a24296444a4d6a924f44462f474f6464a52881f997973d2fef43455a986abfd14bfb1d692ec8e8e50b67bf929c82c0ca932f6af6f1055a7c0d08e3909e5f875f98e8729c139e7bf0fb53504260c6174330e2076aace07f12f5a331a298c62f147d9900d195f642dcaca85ccfa50845bdfea3345c598681a88078afeb3dcb32bfae8797e501cbb21f825c9d0f7e31c62c8c9b7b1d8ca3b668cd6235fb88b3df79c8fc877b79f7d34b86a88078331ffc660c337b2f8e645cc805d9a1b708f99f7b7549ee9675356754aa79ffdcee7a80dde44b72e35f5434fefe1d30a8dcddcfedab76221dff41a02a56b447f85b33619c7e93cc9bd6e034cc30fda076c15ad498f821c0aa9fe123ee39048c98a0f6efc06afc68e0be76b5fbe8b23a308d3c03890b8d647152effc9ca2a09f73e67c966b8822212834212673ce391909ac1b4ca9c22ac210583810ac882d253cb04407a49452b2e042621221a594920821823099fc1586c604a65d6e2da16289152d084153de4c1ce932839bab73d30e4302872e5fc07ae372134b5b7ad069b55a2d1aa79cf26504f5e316141d776fb54c5e8eec3c881a39a1b5d3cbe436476f416186f0d84eafe8b4af1ea259d0dec1851f04a1402743bd03cd6196805e58e1f5c743bd1399a81f0f0545c9d181aaeb31acbbb9baa11eeaa1dee954efd01d541fdf990981cc936ec26468c883a8388b70b0019310a310810842601d6c00160d018628832068af92f04fd22ff767ff588beb115a71dd9db280b852807e35ad8ca3dcb4c5c4ee43d0428b085fe8dbbf75ed4c4878c1002ffc0a9a8fb84b957697339c5e466e1eb033840bffc3b94270e749c5d2156239c2de11a8a233720eaa2a1ed94280050b96200e64a1290e409287c1e9bb3c2149bbcb091c1a01bd2f6aa47fb4b310ee13d158a224d1f2b11653d5125f9878423e8d523fd6f24510be0c909a9afbb1961b8ce4403a618cd02e1731b61e333230148608cce5089cc9d832f68b0e3e2ebaf0a111f814c6ce2463cbd84d90b15fa8162ddc6be92088824f9b35a596244ed4efdf29a5f0af7f9d2450e55df65dac549ac4b3e2a5303d519bd0a2458b162d579a802549171742f8b109beac8d10cd43a935fd27a07f6054a856415a523fd306eaaa53b7673810b921f4d239a590decd795d7f79b2ebe109cd34a99fc908f5335d77db6148d4bf9f77eb90c4620713a66a6194f34a82d218e5bc28966914ab584629866159a66d1bc78d46a49ac154227db58d1bf568b4916238eeb71189546ba9e4e2f2f2020313c3059111a2a670828a0387c9044d2693a9965c60626e4478c36fdce81ba5928bcbcb0b0c4ccc8d1b26538e6b4aea5293ea1b3f986e4c381c070c4c4ccc8d1b3870984c3972e8d0b163c78d1c384c394c39749cfa9443c74966470a9d5d6106002cb4e0a15ce0d1a3c7d643cbb01e3d7a6c3aea0623733ac9ec484185ffaeb3768515666600c0020b2db4e07928940b2ef020f1e0c163c483070f1e3c78c8ec4861c78e1454f8ef3a0bbdad9d2315989dd9f9bfeb989979054e86a8020b2d78330480112c1851ddb6d082e7a1502ef0e841e303a3970f1f53fa88d07db40f1f3e5868c1f35028175c7081078f1eb2478f165af03c940b3c7ad0a0e49cd2615c30419927d5ad0b1c91be3d54b0525fa83406a18931c2b8bd20534475eb23150001c0d030d0d0d0f464a1a57604d0c44e53ea2f4e48b576f0f3217c1d05b043085d4c103d14fbb8ac89c7b46cda0183eaf282faf1d0ced0d0d05016cda1472882ee7403bc4cd47ccf347acdb75150cc8d989f4153b02a49146c23ffc6cbcf1903f3da283ee98b8c1e3e09be125237e1dec9dfad40d4dcf120889a2e7467cfefc4a8cdf1475f04fe8b55427af823ab840a0fc9d257c2308524ea07d52fbd074155a985588539d1f532ae1a9182b05041198c93cd3bdfb340d5280860f0c0abf028a8b7c0b55c9aa983e3a70e5425390255386c433171769b1ed3311c754d0451f349dd7cd9589ce5ded472a73371d4f4a02677ce6c0605050505ddb933fa2015044d79a9228a4934499fe32f5d5771b1429a329af34b9dd441fd3cc8a3b8cd97bae236e1361f5fa9122528ca6a742364481097c6c3863e83e80d44314ae97f24034c21dcdda1b7684d027ff83b3d146015cc04d826fe02d440b1d70fb91a78355f8b3fa80b80377e0ff6a633b88ce8232957bcaccfcfea212a7632399dcc41a5514606be3ff48f1f87a40fe66b0f5f4a7dcd1d0c5631f7847f869771e79c139226e9739c3422b19063c1e17090a036fc18db4ec76243afea9b60ec72523b5055faf93f02aa71554f9973d6e0ab6a5b0b44cdce594f1c08aabcca8c325f9b92cd06a276e55560155877ea8477be5b619c22a88a6fe241c5825671a7c6927d24a53404898afcacba055156b8379f347fd4cd975bd4cfabdc6e5d2b6ef3e1b8f3e7d756dcf9f3bd8a3fb7c324cec7c13393bad684c65544612da6ba4d216610f793b935f08a096ac70b2adff95feac6eb239f986f7c24442e50a2b8aa003db46125b2da38c2fdda89e6644b116df3c16c3d4666701b6825063789fbc9780aaa4ca6f89aa9b6a03244c123d0623fffea4e6e79307bf213cff51462d863cf2b2a4fa8ee04edce1188e215c686ecc4a81db12098213333d727b5bb9b075a4e0c53d2bf28d54107a8468d393ab80d5f2918041c85d99c2c4e90a81bbdd839295e161887e03e10830b4c8c1f18218740c500aa720835a057d890c17db97c850d297774f90a1b4a18418b9bec4c321d17449d7115fd7adcf8f165fe8377dac89e64f624c3bb83cf9d94ba0d845235339e4926422168d7f5542c503108e96bbfbd4eab590db400d6d095fa555335fdbbfdbc5a83c6f0bb9b7b98377b0cc3be31cf7e0713f54b5d8ca35c71ef9cf5f961c57d8e675d0f06395035ceea78a0726bda689d00d1d9d318994da5eb73520b600da5628c99fde0cbd3c8fa6ccdd1906d4fe9e8475d6454fc4c067a3f9f9b3d5b0fc69a5e4a0b0002e6e3a108bbcc651513ae818529a7f90f4fcb8fefe3a7d89dda9e6a80ef5f24beb44aac1225fed002271afa29e9a1dd81a8f927cdbb93664f0c900f5fd3ac0f0bfe0ff195a4a43d69f37f569b3d6996071ef9dbfc9fd5cf8acad0cb27fd977a5e916c0e0e2719fae989483ffdb6bc92160d2a27227de9f7a5f644447bfad4e2405fb35709159ef8a45772d2de2a89f674fd4923d9d365315b44c6f7374285f6c045d7536b011f08e143fbc3e8e147ab0344cd6738e1977d0ff05e492ef673aa21f28c3ebeb4cd8a3edba44ada096cdb411035e7d7a021834ef4e5a9066a79e49f78e25fb6ab448e75bf168aa27e9095e33a6a8450b975f97e70680a54f52a0668c40e75997250bfd8795abc15523cb1c216bb16b819cc6cb16301005bec00a095a07e94852d7633a4245ad862b74266a57e3176b6f542282b958b6e527f1b680713ea4763a782f6248a4381414d1cd58437c1638bdd8e2b3b29177d618b9d8c1604510d3482fa511f5bec4edd35907b4fdc6b92da62a7e3ceae9d74fdc45103a0976d2088f214aeb4295cc805811c6f95d413d032cd4308614321681042c834d43d18a78410324fe9ef90f414fecd5950598a00ca08a10cf54233cac0fae95fe1cd1ea39cd7d636878e085c18c39ecc21810bbf3df93206870bbf2b506fec16a31c07cc3108eb978fd3598860ea7672351216da404848a1ad0d40b91f674983d7b8bddd0b060927eef5f17a0ac4bd9c105da6a93a228b4d76a3e988a10bb3d48f7d84509531704be2800563a8e1a40652ca78c14a422c57c600ca957fd9082f74d03ae204373ebd62239ab1e2c65cf9b4827165cb881604910f85145bdbcc98429314dcf8728bfad53bd345577e943b50a144171f8001129240c51cc2054f5c90c78d50dc184d26d642064a6c37625f98da848171c42cc2093542965611683c2122cb101ae04887208a39d064060d40d1c0086628424803538c98818829401b116f6ad305e594725e39c38b39e7dc1202477114da84a3e496275cba90e58c222967248104356bc2fd6a3555982b50d0407d31a668c1779b1391f9e3c49dcf949a482e9f753a5c10051fcb16edf7e180848beecbe700affc972e076c2bba97d5b1a4c2fbc5e81c14a8327dcc8dff2a0e1badd501deecbf1b1c94dea875c441b9f061daa81f5774e18f9ec3c238d9c3e7cc701b9787cf157164b88d6ba5aebe465f7244b08ff2230dd84986f8d8c7c7e1e5a37cb12719220ef24b2f5febad748a5648e9e5cba7f44b5db51f0feea8e3a12f673fbad9af9ab6a2cb5a6f10420cc67252380a72562e8c10890a3f76f386b6606e6ae5b2501ac08c4a9d97e7cb37995e4e8c820f63fc669c1e1a4aa9fe1fe58860d76d756bea72300d7cd9e948d598dca66e9c05f5fbd44d0e8cd2f6d0d9f5cf201bb186eec052fd53eeeeee5f5cf525821604868a6a6aa3eae0bfa30815a3d79411fa476b819fab83ef284265efafd4abc0a1bf01f601ff45d32e3f64557740882ea264546a5ffe6b630cbb54aa948c6114fc43328e0a17c20ba1055282cbead800023f16b2018a0f3fe38244185a2be300c13832e3867037da8c4a85973929d9cbeb7229d910fa57f7f98df2e3d55bb48b034c03bfb92119957a5d08c4ae99465a23eec5ef537c5e65ef2ca86e84280650b2930cf179e2cf3dfd4cc3256a0ee318b98171e2c36f00ff507259f500154ca6528961b43e4c036b9ca480b88d3b61038cbae3dfe3957d270b1620687606b59f4f1e1f5a254a564aa215b2e255bf9351bb655ba8e3a2b2ad1d79a87340c6500ef291c02d5baeb4523cb2582b0821dcb265e55bb6a8608d1ca4c86732ae14fe359aebe118e0459ac96032c84083e4af792785452507ffa692833fbb3d11e9ebfdec3fdd16883aa51b5f23c9e719165c22e5b4532d7d7ada81d60451713eb5a79db9a24fbf57d4e12ba1d683a808df2f7b0351d1e1f717f16739e40eab10c10c6a53aa5258f2990196158b48cb8af6546415fbfbfbe10f90f084751b0a1ad30cd1a8db44d88d80160eb2dc1c97afb0c1ba4360528426975b4320e372d1e5161945b74504a13b4b77be469acf3ebda2fed12dad19a36234da229ee7d8bb5dd2abeca36522c40156d803f52a7b13f6d967ba41035a1d889ad9d78c7105d9a11f29fc48bf5f89126a7b6ac66022fafe45a875ab258916fad7f350eb4385c7bfffb2ed478022d03750901decb52c5a9f5e65dc8e0645a3cfd9688b5809a201f58abe6619a278a0f56122c40156f4817a757db44b7a757d0d1995fef5f1a137478fa31ddabbd5a27fd9d30eed55967daf32eb43c5065e3151424549665b6360581a0b44c167ff22bd62615da06e50e7e5161144440c0115911842689d9999bd2111124a184550462a0b8d67be54b4ff10de80c6348a20d283a296106d04418408c804d41aa24882c0d426178470c6d0d51982e8679ec0c48d975b432cb9dfa7588fb385c6333836f84acbae8ecbb0e6d0f12e36bbbd020f6a578710be91d9f5ecb8c745468ada4a670f59da9caf75bde209ffe32e43adf3558a21b7a062d6870a8ff6d9fb68f2a177d2c8e415e43c2baa7dbfd8d14752b677ae48af36ebf3c34ab3b36b2b3d05a2e0639d110aa9b568d4af592c0a37eb2c6d0438b6ff622ebffc41ff9b5c4b9bbd69cde2308dffe7c275fe991856997ef20fb9b10d86b33d7f896db4cc740056298027c09effd55120870ab00f7f78861ad79f70dbfb180ad75f07721bd75b88eb708b3567d4ecd93d6e48f6dab5c50ede03f00eaeeb34ca3921432e4ec80e8d456e988d8614512087f6e17f791a5a9e70fd75988debef5bb2e06b00ce0895c2fdeca400d115f7b2930254571ceb466b290bea47a9e6970d349ee12e8fa3e27677e20a4854f8adc58f85e4e7b88c3d76c73d2e6af721349ef99ca734b3ec295da59b7094f7138ef215a8807d785b7fbe5c109bebdbe569e116f5f3a12952aef721efc26da277e143dd41f01f541e5a87fee14a149181eebef19477468eb837c56de077fcfe243a40d5cc525d4d5a0a520a1da6aab8d7c409fecf8516f62a767de7f714f7fa79056d80ab695700aa2d5d8ad1ec2b01b48a95c8821b072251e5f54fa42083fab5909523f0fd752000a7e00d3318eef90d91bed31eb9611a2f2280d70057974d419415586ab75a0868470e2a7f771087304608234c615829478e48c1a85f4f99d21e1dbe13582ea0ae7b8eeb5fe93a659c78994bdcd59c91011e3a08104ce3ef4c7383f3fd9b4c371815f5bbbb3bfe8fec2b0dcd538c43baf33dc639ed29cae799ab537c25d7f34b5b43ff7c1e2554a2e5e1bf5e49ffbc94cc1d58d47e36991c06acb5e646a0ea9a454a8c1cb9809309b08a7610377e43a14d8b2660e4889306e2099742f1208a7d84d11c88da99445ad48f8b8cb8cd478f40c06dba280235bfe61bc7385ff64def97fd67d967178bf1bd0dc6a4c5987dcc3629a1d45ed3ec69085fcdd69c51a9f63f37a319ed7a80f7b25d8415282cb8b204145ba4b18a9e8a25d0e0ddfa87774b54e9efb90725848e43fc1901bca07aeec1a4b5a834ae9574410c15d1000000004314000020100a86c421b1602c1e95cbc2de0114000a8ba0447e529849a32008529842c610600c2080803100032033325b05e52e2b6ac05478bf273b1cc876a547901d81ba146452eef7bc0a948c6ed2dea62f4c9784d52eed030910924114fa59e0de1db03f7be46813a14b0483308478a33da0abdd88f57483bd0929767d894aa9d6a74997a1d1efdf20adb8322ff4b9f2db32957ea26f2727881446ec4a0366981f409e52a16cba27c834a97f45c2e8d2266ddfe35961756aafff73427e714d0be498bc34156c2712b34a8c19376272f61ccbb04846d1069abe353cffe41950d37bd97b971f906e73df893077e4af3278a08a15d69543ee8a40d25557e39c1447ae6f27d6fa8623ded381c5db8e2645ed5362123465a0da19a66d735cd0901b33250d21d3a40c601e869e3a9b7e400274800cf624a71aa248b5af2ddda3104e8262d332a7d4534f1d695510b5435dfecd623eec499ca60a19a64b673854e7e8b8351a8c1ecb49f3ec57b9cfcf6218b3142dc652067ee3018ee3f99e4bef8a6bd3149fd698085d9b26191563177429c4204ba735ae485996d66c534f02d8bea5a5f99ebf4df263bed1a7ea3db6ddcb779e5035886f68f9ad01e7e6a411591c11a285e9d386e9b95fe3849b3b131296689b60c7047fd6af67300e35afd4afdc8b1af0cc57d479aaa5bcc3b0495a6ebb1a4dee9fbba915a5e6b9a7b7a83e7ca942115f6a4c7636452c6cce9b2d3121d0873852869c1acea97138132297234ad9f71a7036ed93dce4a3d06876107bdddea8a2497b7a103032e60360b535ed44e2f434a3f286c8db3040a7724415e7855431fd9d2550691537de0b6b9ac00cf6060fe1dd1914837aff900d09e14cbf9b34ce17ad80f44f4cc1c7bfecd97eb4ccf1e734863287b6749e0a597b3217d375eea163f35075453223b0ad0463ef194305c5e3ae9d5d9bfb431a0b34fc277c567413af7ad0516f20f7526b81b41c14f0285746e9244f91818bdf417f1fd299aaa46dce45ec861de8b6ecc22c1f9bb604676a3429910131b10bb7fb15b3b27f806bcdd4dc9918ab7e5c3bcc653d35625686ca8a00dae6407f0e3abf8444079703b0dc69b0aebec2138ed0e46f218f23ed8fe9337ccc1d3174d3f22d25120b373ebfb55aaa7927d9e457a984be67937bd4400b158c16a7582a3285187737220644d1674744542e2491a3f12e3e2c208b0c3e6a31cdfdb8db67d6d421b0db3c3561a3deaa9b3300382a6375739025324c9f99e14007fa8255f8a775a6cd7b7ba1ed1430fd3e025ffae9914960d95dc21732ccbe6af91a7d969d7422103823d92b876a54989366723d29155b1b652a4d0713868417e99da2230a84b62b9ffa95df646bf920c6f5a5a209df264518f62e82fd04b877ec21c699d05a8fedd529a529a45777e211905de71d67dad532308ec29c450f9704cfef7205cc1d5b01fabdb8235f93c6b229c1b0ecd0316a9a37c2b468962deb93a00266bd779b973fb622e9455b014f10e126fde906222f62ed50787f916944f8b3c326ebb16f97c21ef00c3a881b683c196137cd1dd2181a9b61d00cade4d67256ab6c36811520dc8e8f598eac6b96bfa24807d0a526715456ad9dd5392e45d278fa52db44729ca53d27f8c746fdd628e7680b1ac639c0b4479ea1832dddffe9310f7cafcde26b1ce83ac0aba9fefde4f20bdd4a697e5ad76b4658292129e9aa3cf3a3c5e64808cd35d65d1ff1831043dfbf4e30da8f62e853e320e2ebaf6b4074bdadb306c5de1c6450baf472f59d1f579acf9cb60c940737e52f9529d9d54980e634f15b3732c56dc00204014a399d294e26a748e514f939650c25c9e4833f71bd3294c0602053a530d90abefd540f85f5e707211f476e865f51315c05ff6706376ac6b493cfbca1cd613bb8704c1c043003fc100f0b0c98d6ebde89a300ac9ff3cb4d9e179da4e307e3564725bfa5470c94d164049b5828ff385689b94f3f892c0f8a4de70594968634ea62f95b603392e10f934458131cc2829fb71b91d5ab7cc5ba8fdb090bcd6462d6d6b7112c209022d00c703b61ca3f70769d25dd7ec93c91aa1a3aa08b1fc2618dcf4bfcc7614dd6968549b6da939444e589d17260dca0d9defb984bd573771752bbdcbdeb245fa129d25aabf4e755a6d2ffe4a6070ead45120df5eb2ecee3b1116a37fd61d0bde355392efaed4f917cc8029f80aca4a44bab6f04824e7de31648a6a7f1bf33665d0fc8547a8d0cd6d5ce61e82974da717122f615cef89cda9cd04100a3f4fa2fc88e870b2d10d7a4ae535e15adaf48b6941a754789547fa6a793c16fcf49acc54866c396dd637c756f06d458caaf97b08192b89a957564ace6fff982c7a17a2e31c0e4393eaa2db87b007be308595948fcc286d4990731ef073d9cb7a3785dc08ee79f883b352a2f98d000978449cf4f78fd59966e09315f00af3fc7f9b408bbfd74fc03ae091c8e1ac83859f9efa1d1f78f1e10db9accba489de6a94231165a627439d5e81455eb98cfd404f86fe605e103c650133029087cbd2aef653dce4d82f41264787f5bc94d02006231e10dfb31802e9d52fb3d96277c0add8219480b9ec5f6b7a21d674f51b46d88d0354a802d758d6d430bdc4d9050da6b49b7f4b30ac0b552c13985651d3339aa0c309281050b4ac8cfea394236cc0da609e7e904b2159195e69959fe560b265d7682d212ed3065409136028c1381a0a6296e0b1c7c812f55d46b07b0fea6a3c346c3c451c03b24b260f8fef63aa97325c0246fd5ef1209533ef63d32cfcb5606d063cbed33632929786041d91eb23c1b6ce4f8538f013c0cd045ac3a21e793ff965ec171bd0a0537e42b9642b34c221238dac2a9bb72073ef45cf5f82a757abd14608ea9922b491ad78ba3bb9fc7405c6077e5c40193d65d9d5ff7e750ee19d2e361655c4e5dcdc4e5f09357e400891556e5ecb61c11823acb329a7ae7e864e5c6c8c23d75f74b9b5ab47844fd31d2aa0307b2dbee95a1d4c53cd2df7de0159c3283ed866c9232018ee686577dd0adadac3724742074a9a5f37a41fab171400a989cfd0c878270414f62927f78d20875e2028ce5acd0a66db899a666ba5aa361de96b9da3f26c5baa4cecb276a4e5b79978f005b3c25b44f3cb2edaca9b53fb08fc9e1084eab2d037ceca42956fec090a18a154383bed00d6a7d03ccde11317107e09c2351e99af5eea103af304365ec0f94b65a30f7d228c5bf72a36afd4c24c1efc99419f3a0c0fe5db33dd2358f60f78a87c52402324e1dac9722d94b5dc62f7543d5a09e03dc74cb3ddabe0228c297a720f54a740a663202a68d0e04d1367602598a391c92ff047c8d34f4446f95dc3bb60e0e5b86a49ce615ff5df72bb576a04f11d13b706aac6f23f4c81e540d31e5c0cbed861ed42e1b2a948a2111fe46a8535d1f168f9de235ca0c1807d8cc2dd870b0705520a0fc511a2fe5d442972e5d88a1ff98e7965ac31ca22d3ad1ab59b99b12703389017d6c1ccfe2df1abfa3da2c94e9e106b9bd9b64d112e9a703b389c8b3c9170ffd7ba022d5255ba58b040b37b066a79a934e97b8c14e99cc02c64d5de8051d715edc14114f4821039c4a7103750fab7d994471a32b9934018ed0c73b1637cbdbfa5ac8276e26179f5a8826018534ef1b93aaf04b00ea485c9d93accbeb24b6854fa6703077bfafdc655c67cc7565c94a39ce94922c5d64e5ba933bbdc1727df3818fda7d1cca32445f04abcf1be64a10f3d70a47026c02201fa35cec8c5692b88ea2ebcb178a51568d406e51b3a418c592a246fad460028ac0519be451405a592bdd065125d37818f459c1679adefb20963759cda88b6f52afdc628c9f1bdda3471d5b02344d492da4a48a83befa10ef1b8944782701b3c16360662ebbdaa6564130847ac848941251c5b41301c42a43d12e0c6f3defb328aaf8287fc051846758c866c5420f50325ce3a81b8c038780f6f81f11b83250b1ed750f0d2732f332abab1f854f780c34ec4af3db42b048ad3377828785e680d43e4da611d579e97de4b8b2c7c7f8e4caefea89e321b5fe868c617e68dc3ad178bacdac9c04a71a9f4f6e0005d1755752d04bb84a239b5ff2cd50865596899ca1cf3e5fe2238ad0d87e60f9316e150c8d873b08f7a08cc86576b2c79d5acc09b8adb4009c6528a54041815ad12a0042d1a7f5e3dda474554576152339e9ad1faf866494b9dadb37ced17c7c4d0ed7c8ada71d3202246697637015ce7c71d3bd6f1325abf119eaf2fef79e2c15c9fa7325a1a638fbd5028136684fc7e76092aa31c3f3550255fc22c9f1c89817236b743bfb573262098a462b22a4596de23e096f79956d744e9026aecdd18fe05af37ca753c6136534ab3de739f19054798923a4bedc1984f5e92e18a090ade04e7c8e8d41171e32d38e41514000bf47d3b57a52a07405eda8a9ae0bb5f2f329467fb89c10164176e2df92ac84c578c384f9bed282e0b07e3e5a46505e2d0f8e6ef0afda3d054e7e86908b1be913253285f1115b4e9931aa0f7c11823936d2cac2d05857e0b4bc0f558f2523a93f02dca963fb21a3eac080c54cda404946b72f727cc8fc6990d624b86f2d92ebc50d73eff95e6f3d6b9774a943e9c42f027669b6198a80fac4bcbb12b6df2fa333fa408dcbaef4016c3860b18274903728e23079b04d026968e5a3a07282190b052f0fcfc130e0d433a8eb485c318bf20e6d88ab6205521991bbe3adb22fe3c67eb88676c5fad8b9b3eb4a4fd405bdeade12df9fe364edc2114449d2620257ae4c46d534e2a41e5d09707e814995c550c7038d47c7249db13c408c308f782bf85f55d97f897e37a61e813f28e73824720d7517e9a17cd5320f8edb31ed5980ce787d3bc4c490b1067caf6c20448176731f37f323cc8a1457c04f7f26043a3f9d1b8d814e3c62b7c454b36d158da504addd16639b109602714fa48cbd1059970e4b7a2d4cd4e07ea37dfdf7373e1a18fc6a9d5b31497adb38cdf641a8837ae31fa397d79a303731f4ead154cc48aca45f0bffcd6d64684dd7dbaf5c7b87b6a04aaf049400d48c1a297bdf2621252653aa9e6638a24d39c06fbcf75ec2f87d72b15614142d422db44cfeeb8a15c73890a5b597664220c451cb0524579543bff334149951337c918623c4bd5a4b4f2327843ce48cfd78e24b716f18e2dea98361256ef990714f5db2f767dc5343ffb88ebe8f7861d07c9bbcdb843b66bc696174c421a27bb6e98b1b114fb515bec98854ac57d9040de57e57130816135731b67295d3923796e16aad075132da0947464dd314b4b2646d4e2ff48089b4fea90f0461d6b4cbfd8100e64523a02773ce0a3fab64e6955b2dc76ed4eb52e7231cb328c714bbe4ffc9caec215593343167eca95d80de326b113b0f1352add7b31d634103b7100469be7264a03e31a9e18a44eb0432510b6ebc53bad80323f531a8108c7accc67da2c573fce609ee4bd78cce03f17c6e24a31f37f2594b5d1ba0d4b573b1a4341635da765363f2840c748850c44c5abe535e0cafafa284cdd83facf9ea77ab4312dcfbe7079430c314c669efc11fa9730d3cafdb5a700bf5c1dc58443b76d2f1ef700f9935a57321f382e44e6b92c3b700ccfa43e6b9c4caafe48b19672062a126e26f3092168983a888cceb61799fdcf46b207075d13fdce31ac66a0905b6bc7fe3626d19a51b461b3a6887be4c55ef37f25a431daaedbdba49a0b14e9bc0ce4314c2dde4a2f7e8db506d6655a851ee2f2c350a78a5046b25dbfc326643802370ece24a4e445d7deb158c71a9bcbb02e6e180c00fb9eb3859c676d9bfb5e6415e93505454adaed082eb2810f364a619b37a69daad3431e0cc185d5fd28294b4e01ceda81d819f527f032771e97324cc8c94387aa6b67617ad4cfacf611eca215866d6355b14a6013cb2dea0c203d685bb19a6ee84517863e21c9c4ffbe2eada1ddad0430cffc8b49a6f118ebc3cf73bf846dca495129d3be32f45be73d2b126c500fd6e4fac65e5f9c7ae21d922970e43fdb2c32d507c99e60b2d67a5efcc1125302bf2dfc6217123bfe65005d9055ca9659c0fe162ac480015502b28d1fa26fa605e6a1c409096c70e0027cd9d214c8d5c82b8ebcb06cc80c05ad7e8e18cef5974e9f07032f889f8656e7a8187146f73911058b549f4d673f378e07be6dcf456db0f6168fd51fb2916d6081703634833e860f601737315c6ba830b14a3212b7e30d3b7d9b7dd7ab340037b2a9a60715e175acd103c6067dce1cb6e65127357410a9f6bc4dab2980c3bb30a911623d193d1be1162243e4447ad47455db0e3eb7e036f1ee08bc825ff2f43deed1791a4c9484525e3f4feeb73cf85a3cab289544c588f452e74beb10ab51bde1d8cdf162cbe246fd0a4958dd75713cccb3ccf6027f71144df0263c46fd652f25a15c81fec0739798a593dcdddd56db573a0aebc1556b45da8f826de5bcbf40b3760f6c8ae86c870c034fd72dc27333da0c62e04b858c1b0bc2e04896a7edb1bc9716f065f681e97724d7b653f8952b3ef9dab0fcdc95709064aa66041c19153ddb10183132ef5700c1068ec7669da674b56f3ee2111fb33e8d02ee44f8fa49353011cb0ecb865e52d0116480b9673d063f9ca93d33cc429259b33dc6ceac1d09e8bd113e80957871b327b9da37c9f1a6eb120b46ff416836669400c587eb62c3762c35744c22494c6c97c0447ca67b6e60c7ce7aa7444046e40849b290bc8b37208c395251a26e843977eb2fdf64a9cff1aad991be60d5a17d1f74248813c77863f37420065dc4886ff338bd69cdb46346e2d5107b014f6ab990e7169760cb9b9aa42a19f6428ec8fc640a8978b1564427dfae96f63602dd2646a14efb2b1bc191d364bb0c8ef68f1301de8e2edd98260a2a98034a2606dc383fb36e208524e19d799c18a913ab05ddce6e89915e29a26df4be867b87379d22af9b0b60d54a7799782ca38a1f390f5da577087def7a70b9b501729ca66a08e7770fa695aa0e0789415d7e6dc901ca6b75d769a52a86bb42d8de711fb3b83c4931f07638bccbc113c2b5de1126a45d0ef455427a22d79c5c22876cec885b88e80e40f6dbe3cb852057e0610bee421d5b02ff0f956a1a027ce331d581190187118a7988e594c16429df372c76704bd81951bcbc5e109fc56336b71d4caf960808092b06d3bf7301b4839954998e16f5924996e79cb5290c81b96a34d6e067f894cadafcbd2e2ca672cf96352e6064ab72cd3c7be6aaf35a9896b76ce81ec1afa927a30c2562bf7982aae4fde3ee893ae1b137d9c01cf19621788ceda0073592ea93b9dbcd4eb9f8697bd6716c6b3a9cb9efe994f1924f8306c34680f71042b0804f4dc0f600721efdea709803520bb08bea44d79e0aae25534d2e58ab3e564ed356b5a7c4bae03fb3e8683e0e507b37f7e9f1008ff0384017ea1ff45f2d46b725c10ac1b457e8c0dbc245d3b630eeaa26e7177c92c0d3b02c7a364e3ff33bb0db938f51de7e8e5933ad8ccb64613246d12899651599b2d35dda96e99a179bb4b15acb1a3508d1f7b556408691c5f169123abd9c66237443d96f9d4d148bf5c199cf283b5db45b0e7183e884c43865c7f3567d2f7e937198ef87a6266697e3daee88a68e2d374eb7b4636acc77b9e7a8f3cdb7a42fb43b1d531b2011d8aab383c5ed3def1059c01245a681efb10486e94570191376bdc20d663188519153dd063dfab66bbd551373c0672a3d9487d029634cabc5876413b4c7785c51e8ae70dc11f050e3b1a64d02a683094124bf966628b8e068390a815f7f79358729a59ce44697a9c11d2a43a5cf1572287d3835614391608cbefedccfcc730b17c489ce03c5d9954f2f7a24a11236a346feb1d3f3250204fa0eed4e8e8f45bc8e8ba9b10aaf9cb8c086137a75589328d3657bcd94055b85de9971b430508f1f824e7b8fd1e969caa6acc22aa23fb2e904044239d1779a0bccc0f6525acb21e760b624ffe5a645388865b7c5db599d6096723f1e0a11a8109a734ee1f21e84058bce70a939426bee69a1026111c802593595c890746b195c18cd42acf43986405f82389520df7eb85605b81c3d5b4516d771f22a6a89824fb651eba0a60b624c77ccd50a1249b6ad692183f25917c96e447cb60122b5b911c774692130347d8deddf74d9281a088b929047ffd07fddc94f6f59c8a249a7d0137f038bc0c7ddb825a6351636ab377a54217c148ea145fc2545f779b4181b8403fa22a92389685c9a8542682534c26bf4a1847962bfddd31fabba703c94619ad48e4c982d5e71643cef7e1259a7109d9e5a7d281a0ebc4347120a4244192985f3a5ca245e0f8ae86700ed946f00a02599e9bdd1d5dd3be72c471b0f3074a15461616ad8f8048508354b727220b2959104d84b80185b159302858c7a139d2b17d0a32c9594f4207ade5b51ca0e30eb67400cc8e85b50a5f28feef9b5be983bb277e40c09fa40520957205cbd7f3e993383b518d2eac89c107aa03299086ab82c94002590122d423526b08d0e6fdae4b4b2c7ed2b83da3428fcd8984030eb5bfa2a093a9203b6954ea25cc151d73c182564d38022c89a43f236e86995f82b923dc5ebdb0402826c969d42fca782b95fe01ed3379a50239446922317e67563af0033351bf28416815746cebc787ba436d66ba23a3d0308a2346ee384b94bb48ba865127928b9d220b48c97efdca93fae75e1433df6f9802bcd50becb760302e69177285054448c61ee5f76b9d570f9066220534a11d86deeb35f7ee18b9d82a82e06721c1dff1fe039cf0faad120edb8aeddaae051777bf11aa57be06d74615d0772cd1783234d8fdadb3743057f6422e6ad6125ed2292834d149219f63e05f7cf258f0c8f5e5148409116ab83d7f731cf02a553f81887b3903eb4fab00aeebeca8cd29366ecb3c91580ea15adb83aa02e02e07184df61ef5f15985e1ac6db9d976616ac85673fb471c2eab814a2d23c4f83549c1ce7c0d8dfbe67789fa2b0b09ddbb01ec673e709059306244e9db32a7cd0f4f9f2cd4658c68a1fdd8e6010e1d026bce66ab451045e18f861c0735c3a60d6f2bb220111e90223f5125166e66282b72db65c4f4f581eaaa7f2b79fa23e0a33bfb27f561346031fb0812a2a1c6155af99e406a80c33baaff903441c69d96f0a3904c56c7e35ea3901fe440d13c6716121fa25d7e49448a88157eba6f650d50e02ad89d0e8dc02ab765488e049a1e701afb1061de9b4390aa12428020f21bd9a0a23f1b73bcd8de32a4d076c4ed6d0edc60c52fbc1cb0c4cc7850f1d10755faed489edc0b925868d0981540afbe5ba5487bc28d6ded347b0d0a0137d4732e5cf996161685018e3c7d2b900330f2667e9a82bbd074b4ce64bb85e6f2f3e28785e6b0e2b99a4785c3bf403eca880bc787965b5079e63f5025e4c4b3cb3ca4d7dd53b4c30c0322b1370ce06fcf188aa477bdda064c09cd63ccca775558d45c9d127542f3b63134b80f82a53b75091b826dd0d446feaf9e97eafbdff7b7eabd33366b5310103c652141d439f577892f5d0eb154eecc3ade4eb8ad9c26eb314d8ea205e12d17077b752e21292bd69a9e3a5ba9b2a786956328eac0d35432dcab5b009d03d35f7a34fe39c42fa9e49d5763a734e8b0d3f9539d1bd0c3288bd6c26c4d6a7c202286ccf43145d921d206c7206234557603067703d32c5257bef912083ccfbaac09bb3846417adb8dd823a047e19bb001633d6591ff6565caea25db2121e10ab7a7b25ce6b28b05b821eb41b5d1ad819fe036fa53e8817df628b4b7681810fa8dd9f2eeb6197ee37f12dabe44791c2063ce607c874e79683fd441243d54144fd2a40475b772154dbb2be2330df1fff6db04ffb82c39c8bce50ccead992c56c6f09dc36197221b7fef7ea2bea65b7206237d32430578188a4ca203f7b1ba94b012d16bcb1485d754912f53749c8d760aecc70fd7f8a35b346a6efb530aca2d1a7bfafc3404170650429b07f09f72140ae078798680edd8071c8995d0cb9796a691907975b6ba8168d7e9973c287397dfa401e8ad47bade0eeab76fa47a102e707c147d960aa870f99f494c4143e571ccbdf32f79cf6134187b3170f5618ce44d5be290d31d9a19738319a00e66d451987c1acb5bf2445fb02b5771193aecdcb086e1281e6c15c8c13c7f8b9bddf936d8ba73c41335b3807f493a4e581e8b7f8b066c6405e0562d0f9737014be10faf2b3f36dd0fac95ff0022083e00425cb96bef9b6477dca946fe1466c367662aac4e83c2708b9ebc283e4de28d92316d62d335ea005fd531c96dc34d10e36af8e8b3d8b6a7912a30377c8ef8d0b7fa914b95ade0855ef8457a313ad35bac31ad48059abda2ffb4895598d1ebc2bf805627f193a4b2bc61f2280fb75aa655b0e427a14a0e79abd5ff2344c24478885eedfffd461216c5627c268089e8b5a16a0e5a41027543e0fa31480c212ec46cda9dd4797edad82c9fbf385825214d9331362824742a45970dfe90b3a21d9ea124ec478f86011689493cfec428f9e78477c42340c207ccfd9088861f9897815185850117b8fa94e9ca4b8684746816948d47485dd40708bba1c218e0da74801c05b190e0412c20eded8216d4871d6a4b869e65a1ae2462f1ff983a71b757c8c42a8dd375130c6a32121d22bda395dceadca9d2bbbf0677efb43fa0ded84bb0f40aba920317f0436b35f96c0d76ac9be17bb87886d4c745cfd416c4e1b10aa370ee30174a760ea796a2d265af60eae5f6a6245bf28a8c3be279d4a1781e7e1893158f133ebf1d0d7460817f465765a0e545c8d61b0d2547cc023e62ad2ee741798cdaa218bd753b6595c6076c8d1f0da89589ec7b07f3671d098a0479b70040fbf3437cbbb81b60433434f2795328a0e15f58aa42a61654e29332cfe10d6f25efc1b5d40c0520b94344d9e463a501b9393e6cec1fef15a55807dc181bd0b75faf9d0324c9c216ff41813fd617de51c93583ec0123982bf6c1749db84152357d2ea545087d48b89a033f4f54a8372d4ab56ce3b1ac450d81f69053ef372d379a1c7a0540ac7a24114711e08f85ead3870bd92d92db746df7cd7ba24bc6ac19eb8e1a08caffdeca19502f4c90337077113f211db1055bc0dde8fbd8d5be02aed29fc06962e91aac9dd3d8858374f40dc06db90f9a9e588e6f6fd90e20b9063e57183cb544e726ec5a94a02839308859f2cc3a609639647c80e81b0ce86376bb8b1085080f705295010f1d3e3912634f6b603d158acba7abf7a9f815e919988e16aa9020e927da69bd4adeea9afc705d5735e81e5adbf47643e88f9629a46b3971299bedbf2e05d1c0ec7d4e7c3219b256500df450d9042c2b2db7fbcaca9fbf282514d673b03514b03c706504d281c1fb9af02419aae94e1dae558341596867ad56a2e0d3a98169499b61e9c095be1ca01b09be153adc014935b2ad29b40ccb45f52c2aa089d9288f5409038acb2094078045267b96bc6596e498ff730a34f6a4fa6e2c3828de795c649a2866029e009382fec8a0d92e90da0cda6c2b84992ff75dc7be6b8e5f39be7c1ad5c4fd4ac5704bde5a2a0ed77edb3cc47779770ff44ac4f7be53e739bd0799a420f46217d1e6a9fcf876111c796708b2a508b37088aaa3975ca087a05ae7c259fcec9d83c3153d1f8f5eea9f17a8d6412950c45c04e91c8738793e8e21832d7a2a2b52755452dd17b53b1520d6868a7119f94b56ec3bf9ec27754e1f8ec30ee56eca4bdc4009a60f05d3618ebec78d266e4da293bbec28b05999fa9cbc8abf8c2a95634d1996c1745d8654e0580d41d48765eef94679790861f22f428872c64180283038dc0c741573e28686907140df7d5e299e6c5b8a2ef05ac1ffd1950ec858bf4780ccca2bdf06b42c20c51df415976a08fdb1dedb0fd7d021e3e1c4b2dcc5d5b5d77a24e671260a4237b29981e0a5c43558a1b56e636f97ff5169966a2ef2282a1ee075b6e00431a936e130e9a820a8d9ae34191240e7102eb753c8263f83afcfe1624b4065d3cdebf60c476c1b852155f6f8fcc3b2bafc64d9382d1cbd11d6e59c36f1272adc52ce743aa3c3d9c96b99be2f071e2704b32291bd74a80c2ca9ea9a26e25475cd658f864678efffe1ff456d9276370dc58315333e6096c1f3ffc30a1c4159a64c8591a4b997c24c3a32ce7c1bdfe84eaec3a07d0b7b6fbb8e374864cb49bfc3ade10d9babc5fafe88279ba5ddb805325d88d7bda0e4f229cf694e2a2187f894df0fe1465bfa997d8e10c66a35ccac58dedf0d3a0010b24e6a2c12867e83edcad13cf7663ce119bb8d48c0c977e3319fce73a6d3fb6e9b10a7fd9893e3d5d236c9cb03b0b0ed3680404075212903fbb664189ab5b9d3c5489349dd64a81dba502dffb2f9ffe2e171b36f98eabe52934f7d58d4a893da075e9cfb61d7f80d53668f764bbdbb2a4b0822df2df6beb583810601d36bc0c180e3683e3cbee52cb4ded0e4587c4e86d9237709c9e2c93c93e133136c4e3188bf25b8665368e489bd87c0e407faf38ed0f8f526d25d073aee60fd01071defb843bc933eaa99d4c1a5bff5a650ec5143ecd050a360f9e75cddb7f711fbba8ffbd2df4e509f0d2614d0ef85602262ee633725de6d102101ef6d8080c0fcbea500ea1fee0f981b53ffd6f77b735b2a5522b212d201f8a7fbd79081ce748a4f95779db0aef77eedc3afe6fba12fe1a6414c2f0ee5133726827bd1dd2b788f759cddf5b215878a2cb3aad2d420a6574352d733d389c02d19788e4bb9958bde5cb86dfcad33c24d195d56728b15614ea4a4553e290606bc8f0103ff3eef8b7dabbf1cdeb0db1273f37de761261bd966393628017534dccc4f3204588bfbc37760dc4a263944fc2b35a787acc4f6c6e912c458a08ce238ca2e104b6f8d7e7d43dc03bf26e3ace8ea7170e18a55256cd694ab3f32a3d838351ec47add1443284f26f0f13656efdeb73ebb211cb726b28c5f1c6519193212b8dbc8042873525d136da06596a3f4cfed931766ee502e6a1968b5ad5fae3e23f41aa4eb59ddbdaf57b75606eac5d2d8cc14adc5be57b75738d49248e2e611e8e5d88034b2aff775df9f7d36f132dc2cd385089ac4e61c331d30d995144211ff08eb37b85f8aba618c6d3102971f07c0dda6646de24e3f92fa323a029611f0b4f9a5ccd6e14c1429e4e10948eeae99f1b40e460a17e29cad00a11d88f1a4fe7055e0131a7b2091cebd4d2cf1e0c08264fe128cfa2f95a2910b37d4ac2c86ea313e45e02bd49226c3ca7c0e8e3573b788deb087f94337b62c134b12a3291de3d35848974f950ec4d77822c8d0fbacfb3f814104fae5c3c46bdf0907617a07deb27bafb0cf0e82f2a103c0daa6897e9e017d988b103842dd3c258b4b5e7a99f77ce3da4985507132212e56558983ea53e23c52ddb4fb149daf24cdee054df3e3f3863e40450f2ff1d892306f934d98e029fa929fbc799010883e7f5968d932bae83613f008043a3abee220b67315e0bb8c6a89941de6d4b90e1664d88cbbf4c9a78eabe9ee1809fcd8b87cd61e8caa50b663e6b308921fe509f29bde6deeabf577cff906883ec57eddca4d29d5bc4ac010ac5ce2a14ece0fb834e5e18b7b26ac6bd475119cf9f0421625071817b146c5a0e751b952fac022192f78050370ea198513055291067c37b37dc3b51042815482f41dd007a5b7c80fff57c941e1d7b1f01f0b196257e536541f3a29a0723e718a19a188aabe6a20d630a466cac365d1f95be9484035d100b48b29e141afa16df78d8e3f0f1c9e534489be19d2ee6389811e86207e601fe5afa1824d730b7665c0ba55958d68f7af6a7eab9ed802cbc32163a2418fef7276d2eed8210729d0829d63b1e5a5222f55dd2b392a8da58b34754f3606cc2b723a5a3ece8f6389baf21c95d91d4743e1666643ccc15969fd5141714b30a9a73d647f7dd4b4b6ea2380b9b2a33cfe202e2ce94f615782dfe4f4a61c0868be12d6debaf37318f32d3c59812542576e32ce58b4aab773f2d24ea282acd03fed7e727bb407b2da611f8c1f59ab4ac8cb515a1cd96a39871e5bf0cd7237665f2b6e7e3a805839e3bbf2aac6808e8b0434eea516d7255366e13754cd3cb6439b6dd03a5d1f62aec269ac1cf670d4bfcbc297d4418e823e8d63f2a256a142a31c327f3e6ee40368f465e07255484f8068a1a46f5401c78a17b8a4c0c39402de3cbd71cb294f91c5a3d578fbed0207a363d35a8bf7fd5ebee5f7f3f5737adcd4c7d390e04a24b87137b36a74dd91ec62d197570a677cb4bf4b626056b51ac28632302761c27f6e8f4814ba7f88ff15fabd97bb8a4f9872cacc1e970f12b118f235aabd43fa8d5a5fcd509abb03d46c26b300ad24488c9291aa43a086e0180947840f0f5775e8c9b4e5af33e4897857875e22c35de80b0d249c0faa929f73ab4541fffa518549d436de8caf339b1f3a6456b35eb199cb6a05c82fa9e96b09a68f930e635e204ee9038e280094a783348feba6158fd8b41e3f8c66ea32e071e066c0b9d048caeccf5be097367484985fc5a843d5b0f001cec072df05745d47957a68a1121b0a7f94ff3d732f865b7789400641ff9a545f2cff2a750601a287c880f6b7276af38c4c7582acf5f41461df3fd5d2295e864e97a1533bd725654f94291737eeab7629ba4750f9118e8b131fd0682a253097fd78fe4267b9662cd7acbeebc82318abb7886688a835b18fc85b4ec91ab0207a4afa13f6ad192c09808fa5ceda217b17b0c8d5e7e20ccc7088ded78678f66300a2a982ea80f0534290694b16ca53e9b8e389480e7c2e2d733dc9e8a2378b7a3157a0bae8233885003ad7ebbd501fc96aa7cfdc08fe688c06dd261dac840704b43542b9e5389422846d5d27d1950c6a40e800f03b07900fd9c6564cd312480bc880d428ba8fe4812475a41c3f301b4af8f19ac43d371687a11cda8fdc87d4868f43c9991e94a9b53c51fd67b82d559c24451933eb22e9dede2e0946d9c61cebf2bfe09d94eee6f6e410c595d2e770fab529b2a2f9607b9b2280e320d7d6b5ca1cd6dcf9850aa300c5714f4dfcb6320332552bb45667b9aaad4118faad946cb3bfa1c3daee34b99756c7eb681d116e418fc697e0eda2d872b2962701caba7942467c097f3bba2068860181e3f935d04292c19816a4d46bb1dbeccede72b1b00474b1de160731dbb3029763efaf1dc8c5c6a2ea43e15118896686563705eb7c364d9d800912f1447dbe0a5ffd5fd92234eeb1e312132d7f1c7cbc80065252137e7f842598d870d800fe2c540285449058c09ed2f1b7f5171e8ad80d18000caf9840ed78eef4e07914efc0b3829fac53f21ce40d17bbd8c2ffc9ace978909d5dc244d55fe03bdc9e1844696b4f4d801ae426b0ce8a86ad3e16ad7fb324039be10bd8162d933b09ae8d9520be283a500dc9353fe8898b70bde56d449fc091389ae4cbe96d2c5ca8aa791f838d40dcce19b56ef6c92d88d79b0c9e7ae87de26d47cb540487b80ec513c7793d904038891068f0cf1698819376fa8b352ce56adbec09293568bdb4e099a58c716ae617b69c78c731a7038166a43fca05c2eaef19caa4c425053f957a47826175372a125220fdd58ab6f0d02b12da8b56926e04ec4a8edfe9fed0321aadbdd501a94019c2bb96fdea3ba2645d1197841b2add2fc5d5c837b3334714ebfdd60ea9b6d021fe7fd468cf58edd8c6ed1a90222c9874f3de69a26b74d322bbc8942abdd1fa622934526a0746b69b2483032bc7a789fb33bc94d826ae63769222b50a0a67be7f804ed819eb556fb35bd260cb2857a50cef32164bc6c414a78a1504bf3bd08059109cf57e9611f55934e1179387266f1ec47477506e326f7dab99261d568f659379fbee0cf38ae81c0cf988e250466cd033a70de8dcfbefa0c58c2ef707448cf1aef0b7be9415b08e1df101fcbe2b49d1ba5e1c22db2983ed9aae7a6502af143a479cad1c3221750fe03ebc24057c111f856b36dcc3b01f127bc283c69a54ea4b423767d06d7a535f4c51d648eaaf655343005c03e4713733f39715467c129436c627b526306527dc8d965421ee4921310441ebcf07dcee60d92a87e0f3c11f1d436681e176f8e81a56f78b20e07608a114653800c8241194c14217bedb270d0a85e61eb993a29576cf77cdaa2c79a4c9b5026e1172d0a30a9c57bd3236a770190846d0be519b60d058300a18481c07cc0d342691179e2ca010aead2ea8dabbb103a2a14b3fc8aea0f9944f75cc2bc48cecbcd0d254f89fd473f2f19ba1c40cd0e2d26a1b6d98fc92e3d585aab68d2b38ff988f7a16c9064f9bc968137ed1c73452c009a228b8ac0d224352ca052617734cd24c610cb1492250854adcf81469a4701f8d0d02efc3e944dff76e86efeedb3d8c8e9124b42fe6c81a946841abc3490341ba4ddebc39dde83466e3918398f393d66f256664bd89cb8b57eb423e425035bcbb8486a8715445c80fcd6c8dbd335465fd4530abb41f6105525ab9d83cfe30324fece74f59123eee3fef4f25fb25a9189124b55256fabe9365ef435e16b48bb78e1b75fa6a0ee1e4c383514446b4d72e95c2e36555a3cad7001210e4fe7a67c36cd58cde12a657b1402ead77dcc32db34c1dd34a3628c9b518e5c2695c437d9d3e60792960a7ed673411feaa3cd07209dde7e6c0156283eef6dda6bb9aad4c57dd05c1792c6b607825ab0b47e4a9e0c267748328477920056abde478941f12b0a8a38fe9bb5f695b84b82e80f24a74f13e96b1a8e0b255022170b2eafe70d3ce53f65c9afc4fb8348b90598a9617b66591634b525657e3e24b08710c06c5969bfc6ce0d24ade764b4c2d857ef0db3e50b5196f1c5f1a827ceb5c7cfbce58ab28d37ab1dc9ef3a0c8ecf252b806c684fd4f75b18279bf812e51e4350b4069c9e2ae22dd8f181cad06258a8c4543d88f35bcb540c95e13347d560a0ab67a5a1c135ccc23c490c7fa3c3e934020ab6b3652fb1c6b5c819c514b582ad5a66c4194ee2a5f4edde7d824fb48e8371ca629667b84219cc30b0fff6759aac367349d83ef960ab6865acb24a6a7cc7eca1e8452012f01dd20d2b467ad84f033c7881c262fbdf4e60c071c112a9962102c8002d3aee0bf2378ca56f129da8bbc9c049330010936b92dc63e9f5a21b998630eb5882e2b8bd01517c0daaea8af9aa2b2e54dfb53ad7852c95c3b32a3a46cfb7cae9fd5340afb0171f63ae53744da451796bc30f3aba41f2bb99bcca0099fad28ab7a813f54677c639b4bc962b5a9b77b24e0852a29e4a0b720a926bae75202b9f2a44d811e7c823b77d8219fab97015e10d8e549aec085aa7927df32b558c02fc4db9c5c7b81f5f3d9a29663c3cdaac81f0f1c167405ef2e8e2abba60a463ddf80d1337c55d133038429ab6217a81908f5b9b9b23517b964a37fe090ef1efec62ea9bf2a14fc7e18ef3165dedd63d3f1bb2c64b925b6bbf51fadd04de5836e74ceb5cd57e2cc8a741b6d6e24145e159d1a852235366816b938492f085d98bedfa8a955d58c7cd73d9ae548a7e5ce6d0c8b7291548280e3263408800231299049872da1e42116a7bfb7afe14097301e2d0926116728eef9db938d209d888921b8aaa12993f6570e728081aeb50bda2c6ffb5fe9e9d98e33bc7df9d952a43e140d6a3b06e76220b769733d9b81bc6c2c7b74e81c33fa1b31e0dede5df22b7c189e1848f704dc7051eb83334187b2112a43bc0628f802356719b0186d1caf84238b2af6cf719e61a27f640b3ecb06d33611f929f6145050c43ef8718cb099ee9ce4ce008bdfaeac68b59ebf36024393e5cf679ed5271f8d53539eb40c316ede5009962324cb1fd7a8beefc1ed507d97174cd7e7cc5a839f133dc90d0fb171a8200c3fb463651dd033dab012b34415ed13a3afcfc25ddaa462d1420c6a86410be35776359938bf3ba99cd195eaaef4709754cd6000c7f0e0ab62f49a92117f2ba9c603ea143f24ddf45069b263db0fdfa2f09697ecdcb2da448f9d53e0c029e068e539b8c2dfaf76d382f9ea8d95304f57c5f0171224e85262052032ea7c2f1a0083bc4822480eb55e2e09b504a5a8bff361e3378c38fe8c20151cc774a8af9558cfcde866f858e6459fd3608b1e13db70ad36cce5298769b27a8bbece2c99dc99f91089a506e36e42d6baa71d396735f3cb2abb0b3d6250b83c0528c2b412ba00be3c95ce0c83e50ef944f05dcb454bbf7c60ab8ecc2ae452d0beb89693385bda25e7f87e19a0cade78ebc2ccc1416076092a42edc1b52ad8f1bfeea5ffc8f91a245cfba7bc607184bdfee0033361d118fe7371b061395de9c712bcc649b4a95dbec8a29ed3339f83d5418484730916d243f7a3237476e588dc8a5f6c72e2d4aa88b29159d5fef08291ef07444d588221a63af7992fac83ca35409e09c5bc23a72ad641221eaaa098c90cf6383dd0219c6fa8a6efc24cead13fe4999642619c61f7858b484264fa17ae8da2ad05e86f1a50b537f1fc47ed14579342f1db00b1b85d2cb9cb5b53590a0878cd7423866961ad5b776c6828fb819d78f5a2ff62792a53e8ce6d2ecc94a3a1cfa635b8bf9a5a3fb9e61888299662ae648047151c46e303264c517b5447c3a095479edcb64c65b76cc7c5d98b6d191797d9e9605b5b4606f379761fc669ecb196aa76c9165a8e80e2c021b36605729d9243c2b6f0419fb9ee1a905cf82adee5bf298d297156f1e6d943dc70a3fc40c0e07c59e9a9866f6b3119c7549b05bfc0c1bd3e652f4751b8d56cdd52060976b4db19fa827e895efb7698ecabbd038c7456e29de81e1e061f65075f4c8244b8d4c004943b266402deab8b2921e55c63c5fd751782cd0201c78070b68a5ae6a2b21d9fcfe3ec6ed896f4f3386c4365fefde1b6de95fd9c185238b206ef6e10ea6dbeb4e1f9ec4686d6c5270dbcdef08ed73bd33053e2f84df5ce6bbe5d4678c07df817f9e0f9a9baf629483209dfda20577bc5192c09c89238e8c98437e22a4b46692f003028a86f8fbd06f251d6ffc0e9207daa01de51c1563843888744a73d8b61a505fc576d26265e4c5b5d232b2308676189173f20d26127276e98d40b9a3ede0050b2d7c381b7c157555bb16cb36a9498630a6aea33564bb99bee57596f0a92de5f3da3f8801801161375ac220c96acd41537540fc6397098681da7fc45d7b1cb940959f229320d70ac517ead4c59077e46e9abc8acb386008f2474e8adfc9162cce5f7375c3e001e84f69498b4c7b67b64e62996e033e9ab04d7b439a04e8ede2510431b08729ece639c07ec3c9e5d7dc4ec82668e08e1b7f5d6489bd065e8fba125f505301951b0a985c7f66cff44cb49c7b62346c0301fad1a61c6db6f5b09ef16ecabb60a83a24398412f43843ca833f1f363f847155d5daa59c17728f8146b315d12ca8c238eed165b0fb01141b9bd65f0e6730a32b3b6aef82aeb4be9f14b993e7041b327fe9f6142ac7fd3654c271005873a6a41d4a404bfbe05b77c3d94abb1003e7a9daa5f6e52bbdba3eadc26aaa4fa43be755a6634d5cbbc6bcc87668b4245d8076af5defee1593d2e71e0ae0b269b18ce6051a6758669b4eb5a6276d280d7f870245c3b185c92166d43d6f026050714332d109fac8ffd3ea3ca511740b6d1d0e4d47ca048761a79946aaf5177c8335c3a622133500d192f412973a8160e464548a00e3261c8ab390fb2474ea18dd716bd9b062261e7e8f6e99249642e9aa618289a3fd9f6d1b925067b0580ef27587ef3fb9f23db0bd91b3c166d1be5e962b9320261cdb07a8f9df29fc698078a46b9c200ad9cfa94077537d8b1aba332f3c361bd0f8fed83762cec4838dfb2d861f48f1dc41636994641975336f0a5e667964951c40aa070a4812bf1fdbb01d8d0b73b37c3886837a06522a61d8f396e4509eeb0d238bfacf05ee7afec6b713a22be268a8944d7f0f0a44e3480e922bc0dadf40caa44542c5a2759e4331a975ab10f9094f1b56bf23cbf8d15386ff9d0c5c47c6e0cae4ffe5046c4da0ed542ebd3f8869fc872b1b71a866add2f050d7eab7d293eca5a86b072db2db7283091c218eb969220fd5a3f3d18253a23c0852fba7520593d00fbb7b03a783bbfacfea0b50c41d4549a5c3582f0299f0d5bfd0f8724f06fee71eaacc90724271b09b482a3ac4ab3f14b5c4b356a0b87bb83c55bb812130ac77fff131eed246bbb5575c355c241787232eb2c85c09079d01d20f1dc3f04f58df90becabe2ae8e4dcbb450b3e207d64be969a4fd136a2f8a051240f38a6f65210a14e3ce9eb575fb0774b838a8341fe90959a29a80a892f5e86a819bcc41120162f2248a13da26ef90785f240bd398039063e7d748fd124885cd07ba3536280ba47baff810a84a828e146b5219bfd08dd867ac61d67a3a85ae181f3be78e15d4eb74737bf7f79c168cd836de2c8bfb2e21d4fcb049bfcc594607c425de3ac4bb1459e07dc1e28f1e02e547007825fa1328034e7c2e18d03be8b713b4f21a24cd132e14ed7cce02537fcfe7696eb7b95b8ae96c0b6a8e003c9948517fb617be3ea21f5678336078c62c68b460a88541e235890a90b7282ebeac89c44b700d3f1513da84e1c9b8fa5819f5e6dc916acca2723db81fb1557659c8ccbe2b9e35b997347c4b5dd168d0ef4113a60c1e1216114d7d60585ad20f651c76abaf908d6da96c20017f26f6702ae133d2cc70bc65c43bf1e65f697d2e916007cc0c68de275055c71b0b4a35dfd21fb7d64633a04db23f49d20bc194fa4f72c4a5e08be701d0f325a0b1340e3ace5c979606571cfe042cffa80ca1eb922d362d1b968f8750f1b95d200c45e9ddf396b9f5563d7b47a1f1fe7db27d31bb6f415b9de9f32950ff0b443e35bb8d974d1fbe2029657c4334db586e8c440021b1119a8c87a3430e6e8d05978a21ffbb56347c176834fb1eec0da0a6807743e814e575f3a1173d6e11409af62e7ed7d160ea76499b3849dd551ba9ebab0a1e4f7e89da279e443c112569c9ef0848e9b88ff4b72ffef58c9eebbf0073fa80e4f38892c6fddf1e9ed68a44ec631e06fbc12cc8106d7ce3c89172f5fc37e65608550fbee699a7e788cb95e0fae5c8a36ac4f47a6207fd557fdaf3cf465ddf79da157652a67562f66683668990200c75138a4a210e439aa8a249e804423807e69890ac91c33febcbb36a947dd85a2106f22361e9d93bdf7a259a9c8f9b4e0818e432ebf4ff1290b31e282237ccf5cb17f42b8cc57b9e2d73db46c8d8175dcac384283f3d841c802af8ab43051b9c9ffac2148e8da667ca6a2b10ba1ff67ee4062bfc5fa66ae5d7a542dc197bd3bac25055683d719bbec07689aaa7099c3c00558745e7c1cfa938e05ceba3d5ee9cf2e0b7048750045011b9133d8d63ab2fcc076c41fa26bce3caabfcb5949af058951d9ac53c22c838d68a4221c28b3180d38f1c8b46f3cd4d9f3eac2b941dc718d40916f8311380703518ed721c1f042d7d18268f6e2239166001ad003339fbea7c3fe9c9a203a50e3433c8fe633879918975a8cc8a5774411bd959f69d573b791a2360bdb8c1ec23f7d5e0427b7e6780199c81530dc8d21c12f14a024f03e220a49b4204c88012c48397306736c16394d090c096fc0d75f5f1347e128077abdc0002016900d5fd6ff70e360b6ffcc1f2bf3c03b6d5e1496cc58e8ad0d6e162847c1eb282b5909620187e6800a636038096b4d4cd9f097b8d2903e531bf674e9fb159e34b1b1050521639f9d50f083fa5128c1ea11cff78f022f2b9c6729a1732e93258cab6f88502f6d7a222ca9ff41cb81703726a7f250193ce455ca70f3f918388562e19ae35e374d6aebd4763d226caa9bb28915577eafecde1ba5f28b47caeff0fa4ac42608066a6ecf646ea4231001d0386743d6462769f81f71967deba1117282c90958d42bc335215a451f3fda094e5c6a927a10c10312188944cf17a4706c05ed4e344c37d3f3e0b50e6f3abf3138f3b5e2401c84e10de31f09754988793241dbc580e656837604d156e3d5abd96dbd549e9d31c04b4ec33de7418904545294910ed2f60c70d3aaf74325f38e853b9a4beb41ee442ba49525fee31c2d4e4f2acd9913a7d584a13e6d793818b80202ed4571a626e7bc3e4e9fdcd3f44521049f63bad551b6c733f37545abb4dc07668a2f51e99f35d4338de4638588c64d0a17619c96f6920884441bf7d563c744681108fa96223862ae7bdc4dbf46eb1c4adaa31a40ec489ebed66921e3d51de1a31dc6d9aab594dcf94eedee1ec71ca4196edc8a7674ec33129bd56a616339bb502113e16c4e888b5ea4aba1c5520051dba1b438f691deacf598c3c9151c28df5961fb637feea239dd576582a441787b5f364c864fca9c4aa1679fef747ae3f488fc032921a30be9ec244585a2564eb7065fc6150d1b621b4026013e5abf5d057b53190f08bf10723373487a637355cecb13dfb6e29313157f248c62d3dc2308daf3f254f06f3f70238e671e7d9440d80e6a8da5ab67db447d51c519121fa412fa23d011afff5f8c5c0be0fab2b2da751d0d16adaa32cad97e7b753a7ba0111ce340114b38145c66af14b10f34d600a5782e15790f72b7abe4b214d040f843e9472679fdc9df40679c7da2177a6b9cbd087dac25433e9e7e549df68eda3221fea3f30d081914b7c9a204986c846db6ee2941661e60874f8b752afa147b6440c53965c1aa75d3dd457c9afd7dc687829e85c384efce6a6b0988789320372eddad3e957c20b7c03ea4e777219d0b636442f2477f060dd6385946d2df75bb47cf4f7afaeecf1e57b74ed179e9aa025a29e435fe1e7f7b7c3e8aad1e88c6d608bbb49bf0c07457f9820d8c9efde01ef6e8e72a8441f6f4b98e02c9a527ba270bb0f922788b11bfdf93fae5645dac0a6352bf39ef36729244a0245acf3ebedaa4089bef9a624ab56d18f03e602f773d8e6280ba3635f55db76e6a00fe2bf95e564bd954f1403dd3be0cd9ec81f185298d9ca704de1ea97f7694a973b1f5899bd67f12efe210fdc0a670cd4689cd43ed8e56383b01b84074205f22f47b8933f5954018ac8d98e61c6573372978f177938c1e446a80284e0c28d23103adfaff4b27d24329d1d685ad791b957dbfb0fdce9d208adeb927d9874aef367c0004c13ae0a86fc3fa18497c5fd8ef2d92efc176feb21ddddb02510e9888070ae8a517ddbc7742bee4b3a1a03a03caffdea17fb3c886de783adbd0750e388d207babb1e7de82bd80ce0bed9e949fae977dabbfe6614a4e881d0a51289672a5ead9a75dd32238660e11b59c81e48df6a45407582d593f8b3f34f79fe26aa040c5ce6849287c31384326c94edbb2744472929b3412b4b084b996b987afcb18c24b8ae4e0550b5c0eda1110111a011ea270c18852e70fbc38b29722e43fb0db79bd7736b1f0fc519e3ce6da2acb6f297bf610f9a7bdc60bc318ca762af290043af03e799962b1018f497b132476b87515dbebb7eb14cb1c28e587c58cbce3d33d66e9c90ed6b768795c83542308246582d4333971824364a40ec7c3990e73bc0b9f5d4ae59145dec0809fadc3e0160475703aeca8a9088530bb7c95d4aff4b6ed6c9d421240b7b815248c823f03397c85e2a41c29a94eb3eae0232027c3d82c9265dc480afa612fd78159eff068e81da3acd5a3aecc341431e9d86a610f2bb8e056707cb3b9f044aea4178e78bc7f0d062b51a77b41e6932f133d11075b4861ec78b9c2ed8994e2921597a07a488468ce35fa15ee4a5115a5197f62a66329a7aed8962d0e4fca1e4e3e7f92286fb70cc29f96fe414f43975e9f1379323f2cdabd545c5e6b6391afd1f2b30ea39c5affb62ee9f89ef15277f7dfa2ad35921f0b3d2b7b87df6922f8dc7c4524ed7bf955a554bd5ab27b7e761078daed1dc56bff2b50643c44ee6a54dfb705469fcc90078438e330365e89996268fa47d9792841c7b09f9df2ec14074af02cb26b708c85943cd94364167e06231e4ba6c31e46213787ccb67e6fe47cc5e3b6bdae95d6741660898ca966bfdc8b434b673385ee702dc321c93e04563cf5cf29e95e190411f7f1519b0a3ce6339759d0753b56028f702dd83be8c7db0893ab19dd503967060607b7425838271edb2cd2dc74dc530760483186771ffd0c9bec7bb086c4f420bed030b5424c1f781f3f9582156f54cd54a8eea0371bf2a39fbb647a9be623314c706390d2f1cf636b883c543c08ac7ba16aa77ad9a4667b93cbd15769a0cea2c7e92d600cce287f8b73c11497ae1f04b54a482e9a7573cf022c468a2a8b5b02a714cff14ac4c7bbf127c0fcfd47f23f367226806faaf7682b73b178e13429154edb5e544c88fe63f4305ff4baf5e7b95d0e53c4885053be32053ae00db3f4f052d4a30b05486a1dba7c1ae2aab3bf215078dc9a632442f09d8b08e9b7f5106eb72db85f72d7c493624505abd09c32e6cd49689cd0273510ff736057bb1d80471be62c7fc1507c11ffe57ee1147eb8d3e2cc2dfb8a3b6006d9fdf8bf67270535d3ea01fbc055c7c3640ffdce3a9cf878b0b057517d7fa73ce7f752a731676979663b6321f93e6c32cb615ef3c3b2892e16a83b1dc250f8902d28ff9163647a1b2531d843be84368ddd9ae942aa1e1a912462172cca85e91c173703a90ac2d0a65266a1828639a47313bc239eabe79412b44e96743a004299cc418c7a733d5c1d5940475d0898c96274632f5811cdcefdf03ec5b1b852b89a350eee2b0eba0ba749e872bcee48b8991781ed4cc5cdc4c5c7942ff8429f3cb818307b3e9e359cd3dadcc8dbcb3e0de0dc05b0bb814d065ceda06f2d07149510131107202279bdc7e3bd2677d51211f5865da3c003ba4948e5160db9404d879ef3d19f6b420c1613f10a2dd068d0424b18b7dcc36535aebf215aba4ec8246fb4c833dce17c83a06d4a1d00960bb1c51af5d1c57e5fe237a8d665197b2e42c956f8a1b262871ca2e791d1a93918ac3450284f2b143197a36e303a9bf624a6d445384457a4cd8bcbecc86e83af04a31bc05723f1e6cc7728360f2905e2586fe02952dc6489e33859abb869df56b6d6a46ed518a7bc9d221e58ee918444c0168e74ba8ea96415cd1390a56837a2b41a6a975af0c830bfca70a4f25c644a5c83016fc3b945f40b67c40d4328fba9aa4e6b26acfb27bc3c9ebcd8efa820d133cfb260101ea5b83ae634200bec90366eab172bb63cf01838b26d2004aee5829c1fcc1269863bf9ac7836d1f74f9e9ec631a9513d2012752c610043966be300618ed5aaae82b05e2ab33c1e0297fec247169bce5b62ffac3614721f4818abd7943eb6e6064fe2eddaf09c2f31572e26ae93bb008b7ae3518346548de1db3676bacd119f3c2d2c9d35e3794bc0d95d788b785627f69534b48c82b073d39ec9621821f0221cfb998d3fd1329e0586fa1909553da42c83af706365a35db5cffaad4e830bb5c71f77ce9920a4c7029a406f6bc022fd1a3faf7784e6987b8927d4e5e382254cb38320fc2b8f2a7491b29a5f33eca1e84fc073936b6b53356a808934396c738ba00034b8786e6a8868512d614efe9b33c8682ae3e56efc4832a1b4cdc178b0f865e3eba0e0721ce80209ee56f076ea7b622c39981a0243e5cd7e378d8832637f13891a76ab7fe9d8a5774e5bae530dc96bb60eb81ca340177e51a562a3657b6d51286eaf050dffa9845889ec3a9bed78fae9c0dca40e52b31e04cd752aa97c6779f0990afc40e679fa3d10ede239c3203bf17464b0366172fcc7647c72ac7097f79a822194c0e52fec351e9664096ab892be59ef697786a5d9e1d91c768037d13b4a289767bd53f241c499a13f13baed4b7a09f44ae5b8f2d4c74f1e259f9fcfb987e33772e1f0424e9d0470486df547e10617e8a08f1fdc641a83eb4aedbff2a2f9f4aadf9f6d1503719b862958c42b4f1218674b47929b136aaf84cff9de88cd10954e2ef9481715f941bc38a8db262635971e359a3b1acdd6856d148d63696151bcdaa1bc39a8d67e546b0cac659531b07b152091549c06cac0213b78e07aef383d4f5e0753e409d1e5cd783d6f9c07572d0ba1fbc0e7cc05be707af7bf8ecd479e30e1231514c69cada36dbfd902c8f19a6143d8bea66a014fd10a01debdd63e222e0f2b33930c137ba7be763440d00c7b83d756c89cc02a6060450c696804245d50ea43794cf10b03db03f21ea3c7a1b122e464cadf9ff1400fdbcdf1c9e4f7f043b4da142a898c56af9454af79d857782f7636e58b4c8b5d41536c325002a5be2f4f864bfccf4ca293b152b5c1a72cc1344e64996d167ff1cd4d774134e2176a01ea4f9f678f3e5ccbd83e3343782d9f1c87dd193828aca06ad874e712f65d9629f929ca9d8fbb16ef6b4d4cf2b12e84e91ba20281cc94adfe7db2bbd21509e6e482e004853fbe03f5a0e422173e95133eaca3d979fa0336f0477d478651a9a9011f48aa8b053e1a00a1cb6cade011b03225cee9cc0227675e31d3e16e0fa102c2568f577d0a1613cc4643c24d0417a7e0b52f1d038a361f0722c10271326eebcde6236144bd1f5518c3dea46eedc3869e90942de91ca9bc2ed7ff2428c6e79380a3b1abfeae5dc9a358887f90cb10188e164e4148d8b97594a01b85f6815e2a483f19577335e4d91bf6f784e9e0ed0d97dad7bcd7f0ea53afb6be6717f8e79b6e86161245a8be2811a339dfcac83db3d8e4720baff9e0d5936b79c75713ce2c68b5ef389cfe8fd9d683dc112fbfeb2fa5415c74e265e4e9fee8663093eb4b48bd4f681800a6d6191d17351c52af7ffc8d2164001a9c4b20d9be4781ccfcb0a8ef2a54725d3b9e00568bc4a818cd38dd5005abf2ddc3ea2b79378aa0f48424ad9e0e7a09c8064d2a20c111afa95d31ba7113aaccc9067e6c8549d640dd307d79c53727e6e1ba9b854a3204901ec76db04eee47e17540c092c4eb500ad60e228b8a0c6d5c8c0ee9b59526b2128e1c33253624181c06b51f0a09fad74efdba1c2121eea0239cc40e9130028bedc45ec61d975ec288fd0e7d2eb5af9274a0d6f91227d9b1ccab9d0154845b639b524ee5b5a91b2eaedfba29c82af83a616b9d31e6d8efc9dfa4d2513e8ded4da8b17248e2e08687ba6189f3a96ad847a26de7f3659b89c77fb972868e1fb547e080edb9c42edbbec1f422f7d46eab45d1ab5713b516ad95a22875906ee8c2857806c4ee33823fefcd92df0cb8658ebd950de6b58be2a1cf4fe9aa827f06622307924c1bb4b1b3b59e7b8d5ddfec7322387820884cfd860174813567db4bd4027a91a49a7801504782f37806426f0e19f74a4f59c2004627c1df0ee635b2fa6b20f3a82dd761ebad1272e58cb1d82f7965c320a0306b3d767ece37077d5cbba293078d32f357f0eb0b78ed9cdcd921b5346ed56fbc43e18918e8c772875ac17d3b30658a5c90c0621b22d2e4ae82b8648c146bcf836673f5eea9ba5896051416d84268f7c3a5f941d615f3107d763a26d914c1b6f04e59fd8b3e88967ad9f94fd6f455cb8b571ef473f5ac9a1e44b760e4f1ff15ad2ade6ee0104a53a98af813119d4aac13624fd6a466195bf6505f6941f25d7730bcc2e08bd0c15343665c2fb2fbcefedeb848994b6b0a927819873583a27c61a18cf353aadd06d2426825469a1b4c5fa3851e66d82aaa6783efd75b88738e2ca7156e209f8d75355572d5a65dcc617122f112493ed94f263033ca4e908ae38046c3d8f2bcce0ff1ae2c1b26db62f4db4e2dfe08cfde841dfd2ded1a96b6c15ba2ddd00ec5f49d6ae60496bb8b36806469ed07a2391d2d692bf3bafe08aaa1b2a5053eddd499dcdc9b323ce93997db5123135a8bde95285883f3e3d0c5ed3856a52fb79ef0cdd06914d52fc9c1c00bfc16a9e9248b2ee7f85ef7a1c1775a02010043139f828239b722178474a97876c431302dd573bc85f357eedbe6d71a89c2d07ff155c62688582d18cd591e351b3faa04b17b142f16ade4a93bc6c1f01c2b542baa8d92b59e11a88d34d0b6f47a7665e9174f7bce57e62ef90f7c939f2a87266f01b400788defaffd5aa46631ef391bcfa234ed8179a9a4b8a74d19bfbfb87a122abee14fc047e65700e2a3c0c3c3185edcbe20c65318278bc1b90824ab1de9a32e1f805afb2c00c0a156c5fed2dc98601534e145903518c896071d02e65a9f7e9453e39d1914a78d11dc2ff8390272e0225a32f75dfc239f938ca5ca29e568e99ab18a301071436ed07f672e29ea893fc1aa78368964a99b69a4820fa1ecd2b5e983c4b1685dce83d01b301c3a146ba672a763dcaacc96bc7be4ee0d54dbb35bb0c4a23f5be655dbc8e61ab15f9cce82e750673df208d78ac87fe3a149802951309f5e7b247ac1eae45d263e8905bba61a51ab771d43c2d23ffc00bb0a4fe9a8b39083b31dae12da68c408b61a00328687fa847cd8afb8eacd465fbf054acb2c674c2c244ad50b4af5afc9c1f666eb836545297a7060bfa7da8bb58300e76e4d31e5224361e4b5e45ea3b3ea172586f46a95901462ee15ba77bbcaa867aebb39b656523f6ddeaa9b6a9dcff8c12433380fa617996c1774ee499986c7e6b141e37f1d095f2e102cbdc4b06b655d5e03de7990250d3f478879ed20eaf74d9285be921c1f3472befd87120eb9a968e5c6c3bb772ec329a369376795351a89aa0d294f16eebf4bb50418448a7156dc385a5d5f5f8abdbea74f13ab2681ad49d4a7bb4d4d406c0f2fc3f0993b498cefd1991b1f75c0858f96c2e49d0124aa0e2ddbfd5b0c5995db19e23d02a222db6978cd045304c266c623fb5e4311df671f70dbacc03fa16ba58007701f6374fce0ca1d8772065779e287442a30ee2bad4e088c503d85d2a4e71d2d666d170dfeaa851a38188c399344ff04fc168f902e9bb9237e159d3b48b5c5f609f147826967f05526b550f06d5c2cdb200126a350ea6b54c84bb185876399602e85fe9a3124f04dedc454e2e8ffdac819505c6d02ac0949ef4f739ddc36733a6f7520846d4dd4aa8e618977a2343b2253d0f7e617bddc79f12b0404b6f6ff839bfcb00498ba49afc0b3f7c4001da0e7556418c0bb4d5cb3771c88573a6a4c8457abfeccec09215eea152c4831c1a024f20897660fad3ea0d2f678e9dd897f73fe8ce398babf8a87a00650a148de1e07dd59ca1433c15ce95cc18e65dd55a26f0776e51225ab8584abf80752f44ee6e67c046a10e37360e196fede0fe1038b5d2b6d1243d8a5a6a5b8168deeb0204172cd553ff26861d5187dd0b1848a192f7efc1e73453ba348e7f788f76449696fba56f674bf9d59818f58251325a4c5f10aa2812544c2fc806a14b370b9c446abf86adc2c98dd83ece888a78a8ec7407895cdd33d8cea1b9cae92e7f9f4e8fec81232064bb7ad8c37f6258d6dfd0a760ea4f4a695d734fb841b760789b1ad3c8e7934e5b36cca5bdd53dfacd927a3abad36fd7122722d0c614e4d282b711281a1ea444275157265c6b7714f6276876c5d34c8ff3ee5243f800eef3a1132cd46067b39caffaea48409536fdf4b09acc148472185d6ce77904be2a7f25e24f500527b43beb0f0efa1a4bfacc3c92bfc63a8b3f9c06759b2fca2297b65984dfd4ea47c62db8a08395a99193686375e2f4ab81257828e1069e95f4fa12261ae6dfe55896bf1c4321eb401a6945c98e234b312fddbc527e367ec353fa9facefaa4af46d8d4eb4376dcb97dbb74f90efdac7656938555eb49b47ee78eb81456501ca11d6df07c29f19acf71c75d9868d3f6a346642aee4d1701963eefec5ca446370d11d7eccdc098220779e359418cde4527a0115c1b0b9d3d22800da87bca60184d04a21ca40015b01a21813115420377200e3284805600befbbf0506e61c9408ecac7873d75c36d222463fed21a0935694e159c31293b6adac5e28375b6e8d8f8ddfdd317467251da698b8429c019584e7e1e3c7b62b590d1ef9396658ea5ce3080697b2d0352da18366454938e8e7b6967489d75e70628bf7e02cc134956693a4e575156549ba2d2607108bb34fe78186fe4723941d11132a799e7b6a6e3042dbd6070a80ee9d048e7ddaf19ac4bccc37984909e4801772b7cd1491def5bbbe905e4424bd8e6bcc19a2d7e7cedb793d75efdf173b0b17ab4944fd7cd7143fb786b67a3d1cf298177c0cb21e82c95fd0ed039786c5240eb9f2b504e5262eac612f9872fd781eab41004a72efe87ea9dd45fdc17085569afdae8fa4fdd1cecb5e8685b544831171aa89cdfe478ecf517f6e464fde1987cb019fa3ef4e83f6a9909f7f879723085aea16a374a2f0ffac47c5980711b12f8676fea2e752ae33252f626df3419e7e358a62054335d447329bc43860836af0f8b7154d2c5dc0ad05d2af46092d0959208cdb6d5bd24554fb94bc2a9dc4960d70bdd673d6e03d6d1d4964735fec078158822b7cfe44e99ff5e8249854b11a047c18971a49cce6b061069b02de621dd49060e9d8ef2749dfbc1a3d10ae775dc7d9ace45612950fff995d29b26de8c6bf1196d4bb52e9126201d4878155131f45d15c2d58207387d6fa28ab1772a5aed866f4bb46bc1f16894589191d423379b27b37a5ab4f389a49a72f728264c92eefd8c04afa85e8d1ceca4eebd7a1664b48ba68ae99cf01ad4b861cc533b83bf6d4d679bf2e113030fb2be0079832fcd115a8324bd1a7fc6a9b45723520371f3595b21c95c067040465a69e7387bf96265d710d6cdb4e089e35add86284b09d46b6f3a5d4f7c7aca86ec6913dac00b17b00975ad8b34c07a2deba83bd630542f280b22d768e9fcf7c2a4a3a8b86da35da3e3836a0b71281fb687de0e6abb54440b77b599e58f4eb163433985b5543dc9dd784d428a667a5787a3f0a11c931011e137c607dc3fa9f04ad95f6279ba63957cd576add37bb27cc52c0c127e25f7ad67ea4db2bd43fa52ac3582e159d726c0d432201ddee8e36eee44a53ffac797a95d683a9b42488f2c1896a773af7aed21c287b678045a99f16f724b3dcbbed1ea075726b40069a31a8bccf178e12cdb11dc3aa47f494ec00015095e3b0c586b79c46f0ad7ce1dc362f518d8ec4c628270417c565b6d3f996acb7fbfad6f81739681280f7d3f2e1ab93fad83c74e070dc4e099c6db654e9a0f7811ffbac96e853990481c833092b788cc624b0ecc0557a529b771573f664216a4462a5af7e7fe9f904ea42112e75f4fbab958a2439ae0b624141d1eb5a4e5cee7a38934fc4b9483c8a1adbe2bb51933a21adc42b1b0245cb42825fb5c6000574f7d0f86d41695d05aec3f6308fbd3b3de47f17e23446f2914cb83c462437a373c8522be3422a96532bb254e3fb80dc74cfa5f5af7075db85e8868f85b72e5a9c114e8bcc5af3d9680992312a0cb90fef5e29c0faa58fc72c973c56e8e24169171399cee3359587e7ca71a225d0932131587174f4419ad995d433e24abb8b156eeb38de9515448ca6a820aa9beca337b53cd666b6503e5b01b08925dae3d91ed9f878895ec6097885c2e795acb11913d93a80ad2ac4e2fa2a3ca209197db4887a4909664feddd79f97454ed22c4a02d9ce0181c4d466acb828b794773819e80f5837f6fb957202ab6410559a32a97643e40b927981b7578af8f4799f19178353ed0a3b41067743e46ce76ce3befb6bee86c7d80f32595c76e7ea33d4ec87d8b19c6fa9b1a2d3f69858db9490e2400ccb71d7fa66cff7c1e669711abb1bdfde74c14fac9d04a126caf9bb5ab5a84110d5ae16007d46b15fcdb46c0046a1cd50048dbed5effe502b5858fce71fb8d55686e6e59ae21f0477f687e6f33a4df7e6805a1e4176049bc7f297544ea8aa4915b5a6730e8b2c3b2d200ff0b2054baa6d26cb46cf87ddae15aaab8cadd5c1e2d835adc7c0fcd81e42a6a72698d10d889395e4d6deba38a0bc77ff9442122033e0e247bfdb225b825f2e1891dc57b958e57a83b61083da7d1829d235e5983cdb253ae86b32bc4998c9b00a664e471163d80cd352c8066528ff34c1943a4a07322bc8176b3a9946988d3f494fe5769b9d97ebb447a4b5f60ca80073ddb6e324b8fc73c959131f1b5823bfbb85c27c4ec7a25e4f846b7e09afc532386c77ef36a5ec108e9a999c0c768acb0b653be3451f989dc2b83e0dff812bb86fcdd4050be19bf70b5ea9553e10b134f642f1b6ef2fc1ef0aae8dd16782ccec29588fb37b4fcc3c58b6ca06a948dcd88198ded0ad67cb45410e06554f9856e51880f1911bf3abe0c4c27663c7f8480754587990f23e01ff25920c8996177d652a2b8900a8c28d61f7ec89caadbd843d1ba7792b878a7dd119ecf37845ec9267988c0f3947af2ff6e118355ec3aa62bf6878c5bb41015b3491e361c151fe2bd4ba6162b93872f4cbacc439507509a418f1fd05a9aa2e77d5fecab00554874638061349471f2a0b9bf4058dadf5fdc8aa236e62641a90c2e5ee315c377f9f52d83811a97721bda5ea6a326d397cdbd17be3a81e4371ab8e59dcadbbc9ac94da1c50e4b605d5431338ce34ed9aefe851cf15f8e029c702dc1ba355f5deb0068dba91eb2c3054b42ded2ac2ba15bdb3baa42259ee1cc50844f020022deb1c72c3e7233dae06c19eb4dc0623c3b9fc9b05e6c4cb1f4cdaa33bfd033fd46904275c5bb873064f6cee7a422bc8569699f39adb506341c6f0fee328f35d9a966e9320e4ecdecb3045ad2ae246714b103951e55031a610bd89be241bd8921cec4307d29da4e6e03e89087a28d7af8a104ec1c0795db59ed657c459ad73a005ef9759977a213b95ccf20959895148d969c988825a75e0da30e990573797d1a16adbc6372ba1ea149dd10078a1cf817db91b32639e9b7e4e0d3d8080818c1dc44ac37572aa5308ebbbb9f804c85136290c71bbb7c17123bd4e8c8922294f4a664ab15241ca9dbc1fb0e06850c7d4208f48d1b3b3baef8bc2a6cab91ee60a7a8e9424968532d1af40e6c872afcc7ab75fc08e27784f13dd4f729502566a138a723137cf026ccf275cbd4c05bd7f5ec14e24ea7fc0679e5acaa1d7b01a783325b9b1c5cdc12e30045a1c4a8b5328376b252d93c700544bf93c224d4a29661e074e05b80c997ccc3f869b27287a575ff300205abf720dda05442179b15cc62f4c1e7099b664b095743ac5d4e0953298ff7526950c1a3c728750fb93a77abf94e66defd6d214ae0bdb7a0fd7085bfb850bee5407f3c9dc1a973cd8a0607edc5e042939c59c49009bd84e2e5886427c7b26864e1e51e53b7a2be111be3855370a067c22f7a65af10c5c7307db899944755676ea0fc52a3511c8c302bf6c2342369bef5b265b11fec35b850233673cd70a0491b91a777a3ef3d634db61532158695964da4958c9b01b66e08b0dd44eb26c744535939c9865445c80b2f6421efe5d75299afd016c8bc60aa1783bfc0a356ac17477b2a85da4622d3cbbd496feb5089a7ec7b81db171f97277dc24ba4fd3fc464d603c1f49ec032d08e742accd7542671d5914c163af8208d1aabc614d264623ee8ca4c44359921a77f9ba92928d607815b8c6461f983db426d1d0616b5b333ae1dcd392977ecf181d5258d5550d4b6b39bfdf14596b19bb7b6d77283fcae385eb5f1e2363cd66987f2774e6a3daa64230328d1a8ad6a8accc6095110dc05d4a52b4cf7d8d6aa7e849c51ba043e0a823f57295f66caf769555c3e24df49850f388912941b94648012d5be687e576d3f5bb586c56654f748b48b26195e3f52f18088fb0beb82affb87d7aa0df53fa49ca086edaa16b4614b543d3d6652bfec05f654725d0b9124c03d278efbee4e6ecb80875e511a58ecdfd7128ce61744963c6e8f07ac783cea320dadf749066dbcc391fac9a9bdb651ff26838c9747b7cd4876ee717610ee7865ffb4f6cc853bbb5f6db5fc7f0b89d4af15500f2db06518de6939a10af925da60cd3b235f128bd1aab5e070d1b7643cc7e2b885be673d3c39ef359c6bac70c3104a3b5f03f3121537c78fd7a8aa6a4cb3fa77af704301b0c87f2fa5224f87711772e62e2e9747e52b7edbed1d8849e03facdde91592090ba9216032cb8aa12f76621d24bcbe42f40247e91562ede15ca60e0e73cf36e34eeec7d0b972db486382914e00c06b540a96a4a1a15754213f8e9aeafbf78ae17fca807ffbad584ce691ddd116db74608a126b4b2bb7b07280925093009878f64c89faf29e986c70870a36f874d293c75f83515bdc9e1e9000e7f0570780e85c363130e2fc4e4f0480270f82543874761c9e189821c8270d8a1cf811c5e861f87aff201d209494a9492f0806489c861cf7d1c7af097971e58f038fcfce8613db487df083f720880f31c3ea6913f5fbe3efc76aee3d0bbcee12597095485c69c1c5ecbd20978890471e4a60568a5c75a784e6e5280bec94d50b4d2634f7e23372140b372d301b4d2632a7c959b0aa0b9dc64a4951e23facd4d457a2be0262ce040501e04010f3ac08914e0468c7e24173d96b50d4977412731403bb9f4177b082edc85ddc48501e80fc8052231fd0de0db85105cb811222e0429e0420b88b5a0939c72dc8fc845d82100fdedc75010daa1a9e0a024a7adbf1d990206a03f146e08f73bbef7d69f0917da424f7242417fae8b1082fe727e11427892d30efded0bc004fd091d85c75c3872895c2341416e420b408f692a5c0b26c472dc480b381ed482b621c9e94f720aa23f13de447fb10fe024402181059d9400edd4d2dffd072c18394c53b92c10799213131602a03f120ec402d01116989c080b1f0480c883b0a0bf20076ae1421040c2139de484a33fd881682af73bf2c308d049582204c87fe0273f9ee404a43f58d6c2d07f682a9789fe7edc25b0030101e92fe84234150e48c8939c86f4d7fa124d05ffd0df8d7fa0a9e01f1f007d48c8979c04168280e80ff600682af7c87d0284c89320fa0b3a094f8290f0242798fe700ee4c9cd8d3c810179921311fd053913fd91f0007cc80186aca0930ca09db0feeec7e33e3495bbc29127399db082fe863c4853e1562012e463051efae37e64051e4a8e9cc80afa23f2a027ff29c010157492938dfe4ef85ed74712fd057d08921f1f9a0a7ee9cfc77b682af8f524a720fd21c99ef4d0542ea7bf1e370992070505fd04fde027e9a1bfd579f4e0f1242724fa0b7a929f23f990158c9c707d5c252a10d19f910f5181c89027399da03f9b2b51c187fe587fa9e0e3f524a723fa23f213f437e44a5e82510929e8240268274d7ff73a9acadd4d295c9e148ca460444b21058d3ba2a96429e8efc88da8f022994ab69b8a4a20d29fbe0e4de5f2e828c11629c2a3a960ad3f9eebd054b036a2bf9aa97075ae692ad7d61bd1543823468a1829f224271d3afabbb73a749ee454f567e4b5c8ed4bd054b2dd9402bd3c97a3c21dd11f7d099a0a77a4842739a9f4a73a47c4a33fee5a53c1db8624271efd24a7a9bf239ffa2be1f442b3c90004800293000c2d394149122426e4111ecb247c67a00b65213f9247780999841bc9402f92859c481ee14332095af2205ff0411948c3ff64215a02215ff041f2085a2ac917bc0879e72164efb00c0290ec13634efe23effc83ecdd47ee79650f64ccc97be49df3c8de79320074ce5566cc894e72ba863bb47422efe826a2eb648f4877398ebcdd9553cfc9d97172f7d46fe48c95b7e39cfa2a6757e50ecba6a5969466cab4dc225fd066bb021ab648b36bce48a0c4022936cd330b0d5034d833db1d408006061269861b2b0390fa059b1095741312923f14683bec0bbab7d6c3251f30952f68067fd08c9671eade9b5f73f6744d3f037fd04c14970b6321f9032bb53686242fc8577dde00a65f50845d1152cceaba6378010afc052dc52520848290148989813c3546f8bdb0a504a955fe1092af0d52f943deec801ffb87760ff5845b9b457bcacc9eca1e94e5a0fa2053b21d81b9a98439828881bfd4a68f6bb6fc96af6ce71e10d1ec18e26e9ac0b65dca6ddb9036ddb659bfd42715e035e814358928d6180a5f767d9124b33f986606f8bb512aa828a5690618459f484a67a500d296b3d62921851b9452eb5c00471d0586cfb2419ecf428966d494d97996edc44bc9a4e4de235fe9710f7a1ceeec42d2831ee7b81e3d1ef34e8f2cdbe9d123fb0ef723eea8c175e7316731c6b8f3e57ce736eeec1c764a72ce390046edcf4a972d25e731ea2931c61863ddf9dc3bfaab42b9d89d9093f3c5ed8212235f59b7f32c6f3b99ae54c897dca94b70c8ce65a6eb17f9c2227f2a14cf79e83800bcebe438941cbeea3bba3e411f8946be7ee0b2736c789e9d9d9d9d9d9d9dd73452e0fdd5331b03816f7cdffa3bfeb9f60ede3bf9dfdd3bfad3baaa4103f757d15435f2e78cfc992ad04e908a334e789eebb8771c5dec7b2ef00936fa3b9975a83594af9defe84a867cc119a94a3fc121fa3baf3c348ffe5c00d03af487f74a477f7773ab1c38f42757aae3ad755dca75eb82bf8894f445fe7c53693fc91ff8e40509b67c85923fdff2144afee0b83c15237f762e4fa326508fcb53a50924cbdcdcb8f987f78d9b9bdfc854e97a87fe5cad7f5309c7639764e7b0a34ad04747850257285d01b0843f59e6c975f91e19caec3b5996c1b98efe2ece3dfd619ce728f345de8bb1cbf5bf5e30d817b423ec7068fde5dcba32dc391fc5c91fbd915a608837b2120c9407d7a78f93576518625797b8fdd5a5a56f9a994a53c96acda30bfea81255923f4b6ab63c851b045ac86c18bb376960798bc42375aec02f36ba2ecd6a96d5d71398db10ca026178d8c00221d8df6f012b78883ca899b7c0123d7c07353c3019ac70c8384f63e424a464245314d04d0c7e8b2d180c16020c0683c160416021c0603098bc71ef4ddd7befbd5ca4f04e209b9818f8a548908bb1fcf9512395d3470a31126675030c03fd91baf7a6eebdf75eeea6eebdf7b2388ec25b44ead01d1e3e71fe7407f2f011a2037de25d96c3ab584b1583e7689c18771004304c1783d3255f31062790168313281583f7d4c01d776f90345832cd73624ccdb4ffba344e182c082c04181c026130d85dd96b56579140aceaa81d63d448afb592dea803d35257e75e1daf6b5ecccad5f8aaf47256522ee3a0bea01fb0204499e783abf38ce7d2ecf5c5f3ea51af65b97368b7f66a2909fba0c72bfbd537bbbeb4529adddfab8704cdd4eba5c15ef5be226dddea7a65daccd125a95aa5b5d2d77dbd347d656e9787069db2fe7eb384d43f1d7c09947d2645ed6618a31986d938a29860a8992a023c85fd9285a065032d6e9cc0dfcd1004db4b93e1533ed205914bc041261ba1b9c88aa399881accd13c643765d764d37da6f458762d27ddf058a6951ebbba6a164733097bc7061996c803f4d1b265cb1c76803ef2ad2a7006caf121ff08c1e1c6bc82ec1c8ee61176d0c01fcd22d01c420e8e66187ef2389a4168858066203b8dcf179b067f540747f30f56991d1ccd1f442e5aced1ece30648981182b504d30d24da28022bd910a1b45a228a85c40d2a2598f0129d1250ab2d565880e34246b505aa025b5354b7050515d09ad0705bc4e021c504d4b685064b978bb6a58d1e889a404a82071fca1041c46529892474205ac24c1250e84094849a24ae50524a02044a444c984922cc0f444d5c72d0783f2c1de3964b2703d2019e293c2df98a3aa49981926ad75a5b2e18b561176994f100e9062f2c69d6e841039cddd0a305b8ab800722c02c0cf808036b4df4e0a00a2270b621183442e0031a5bd88059649090907e40a38c1eec5cdab08b36c6ecd71768a4410234d668c9c10e5128a17936c4062184104208218410420821841042324284f1a201df15f830836dc07c8044163d5650851558db100a8926a640c28331f0dd100a09227ac4e06c43282480f0629352ca29a58c5766526ee84329fda69c73d25aa994724a29ad94126fb9240a4a4965a694ca2ba794320535958b0a0c2184104269ed9c41a6a5f605a370a296ffece79cf39bb79452fa694b27a573ce7fd99cb37ed63ae7dc2a47d3e06c2aa7def47b0d01ebfee69c733e421ff8cd39a79caf5965a5130758d21aebbdf495c4a5524e6ae9cd6c5d73e9a51ad2bbe6d24b335bcfd09bd9bac6d6ec525bb333d5da9aada1d7d635975e7ae60cbd6b2ebd34b3f50cbd99ad6b6e5cc9ae94935abc532f6c5a6d16630d5b461abbd45a6d4699905e88823489b42012e88b16a01fb01e286f8f84e5095165d2c2c46491b0543da318242c99ec329753a9a65f28e87649ee6f778284d9ddeea2180245a6881441905ad3bd1913d2182631910b8d484abc540ba1cfbc4d9a8b71b4914b47648184d2768408761602117e80f6c7ed6a64d7236e30220c1641881140ec29718c1016f4579669b892704a08e12319f8c988a495b5d6c2664f7cace2880d5ce18123a8382266add2b6d65a6b6d50d06b7f2da62c76e02f36e5590113103d6bf0878384d9456c20db6a8004f3bb94527a420c3050031a41d040882e4a88c97410812f6c43c51a5bdbf2fc8837ec2f4811f8655b11ec0f8eb19ab59dfd6177aca5d6c62d2dd8918a6db394d9568b6d7fb1eb2f180cb67d90d01222226eb0ed9f403962db4779c1896d2f0306ae40d262859219db42282268c0a4650c9722b874b9b22d84ca0017f6622f62b6b50fb2d65a6b7b64e1c0bd9d076012a679f1210363260566071ba6cbf562881a0629c352460e148bc572831fe6089e6a96a40bc826504f7488206213305db238819b8c008d7429fd8075288049d8186a18e1011835baa8a106163d30c0aa0dc1a8d1850f23f0b6211835c078f980ef8660d4308303141217c8d9100c1b5b94b0fd6bc5bb6950a2440d0b54755cc76198109109a2cc182595627e3b929b3eca5e77b6b4cdf96d9bd56e75d65a6bfdab6e72665de04fa7428922034483f47a990934ed0f50505faa8e1ff0847042a8a5c45077f698c2b4f6846b179a98ce16a4b2473e35c67ce268ac8184a54b9d99cd7296d15c245f2ca4745c8a4af0009882aa81e16397c472306315a0062ffa090cb56f179217f38040ad0c7fec06c47d7e3e19c357e5bed12f587be27927962e775838252ff504f89387eae2cb9697f67572437461c134a286e002239a2070902a03268d96c833bb21b9ab974a5d0e490b16c102a1845862df6cb92447d8f2d5a60ea14fe5eeb32cd24aebebddb093ef5e9044c70f986e4be40fd556432d44f312263045a26b75419f3a035c796409f3f5d556faeb512d1443227bea5363e827b00b0ef194c46d2b19ae0e6838d82e1b0eb5d65a71b85d34e104c5e102693629a5843e74d2092f6574c176e50f84b0a0bf30b51267905286b1d18927d0937618a38f59529b669a3e92f747f38b503bc618e352f67b6917a3d0de69aa539a462c942e118d80116ac9eca187b14d6608a554c9cec84a4ce84911282fd4280c0485444949f7d99388b6db0c6348287dead2141b78098cc92529ef0b6212c6e6375967ada78f59c4e244c6a6ebb387c296b4826c7d1ae36a4d2a8416586ed8794eece78de636bfcd6f9e91fc7699394f49dc5edc72c3edc566f6e2535e6ccb5ebcd985a49682cd9eec3113ddec37cb32884642b1411e2fc2a889ea679452a289346c90e766cf3aedc5ec475ed41e91ea29c098eadd8faa38191d6557fdc8a9283beabaa77495edddab7057dd4a0cc660ec1ec6b6d82591303c8049dc148624f644a90bc978b3176b517ee23951a9b25ffb5de9a3ecde8bda48fbf58c52d9bbd76d4bcdab5c5a50c6068e422505c1a691524a29ad144a144a29a5dacca44501a5748b0d0689b4a1b673d8c01149860914af1ad883da73729f7ad17d4a577182af72e474a43a3db2bf87faa83ef5953e9a3b5be0d5aba84e6f57ba4ad59e13d5e9bd4d17a9b4b73dd1d7a752afaa2b94fad41ebc7609460536f8f0840e19f822765f349f7dce5bed44b347fb4b0d342640230c24ccd02296a9741558ff647595a96151474e45a9cf1fd17b2d2a0663993e4a69cf89fdbd077591fd7d51fdfc07d33ebb14622c3b9c324e95071f4351f6d4bbc77db3077fe4b4fd28f5ec474e9d2ea2bf3fca9efacc1ed41ea73df84d17ddd31f51ed412d03d2ecd17428616ff336ed39493dbb51ea993efa117dea463706b5a6ec4252bbd7525a5cb249d4ad84edda29942812804bb5ca5a291492b5d6babdd65a5fb3a7ea084d69eb6b985a19207f3eefcbf142945a6bbd078740b873c021f465e8195bd40287c82a1026f100a39658041c120f213c03a11816ece18bc0a86311d0876660533172cc025f458cb1650c228b0cb6bc05e08b7e0661c6968f715f1d97a00fb5400db67c84102ecdb02712d22c6385495a3a2595b27e61938254920542e3820637a8ae6c61c5997a450d2ee98cae0733d6e4d0829a43adb5561dd81c542c11a8ba484d81820c18d134031bd460072796a05e10517be0806a731b4205f1441a323d5833704825692b488091c314293893431850c82eaeb85c9c01020b986146cd813e08a61fa99b1d90b698316306f26c6e674ebeb07cb9cac03c32e6e00d4d087da2a69312d9aefc81504a2965bdf9fbb561841d84a2505c00cdea05a00b019248427290fcf9a07c8970f856ecf0ae7d781da1c3bf051e067c012f177e2800f6c307d97148857cc18770f822120eff04e8f02c08393c94270c48c0a144913f5f130b4f1d3621e0d961910f3924f2a0c320ff6152618b017e806f874d2b3c75f83515e0d9e157a484c34732f2e723a2e5927cc11b397cfc42fe7c418a1c3611bd0bb596912f49867cd517409f7a9d31f017a3caecfa28a64c7d952f1813c9c817c021364731d58ab59672d9edb35b2dc3feee69d7823e57c7a5ae49a6a5dcdb674357dbfbec6aef664deceff1afc6f28573fce14f71ed7b3b2538f8114b37021438fb1797602a95c588c56adae52e6ec121705b6db16427e4ec387f566f5b8ae3ba221ccfa1ab38d9c3fd45d9df1457a577ca989a65bc21fb794d4a8fe178130e3d0395426892147243adec35529ccd53cde56c9e69e4eb862ad51b7942654185c6d96dfbc23d51a8ce091597d516ccc4d26283e50610ada51b1e709472925c9ccd1509c71a39d4786974bcec50a1a3cb0e343f93cd6832005086e70b1e647a8cf120ea25a6078b0f9ca05c7e9cf1e16c9e6640d802630a418b085846e06ca64004e16c9e68e4ebe69b10cee6f9245f37f7d10dd93cfb06c4470f10204080bc80dcb46ed8701b10203e3e5aea0201726f06044896d90a8290114408418810988f8f8f90880195a6bfc9ad9c314b6391af8a5bae1db18c9005c3583d527d646aed2cbb37cb62a09bde52aca97c6dd9cd0f80adadfa223457f4aac8d55b6a7596c5b8713b460d73b4e02f46c52f7ef922ab8f59764fafbecf3ebba1d68e5f84dd907d2a73406b47fd4dd9574f69ed98e9f845fe7cb41b8a5fa895765a0a63a6fa64d0da3bfad1fd4531bbe6c8f214d8bef1cb06a486fa4a757d9623d1ce6e65c36e5edd85eae805880f187f3a9773bd76762ecb2df9aacf9104cefe71bb56224bb2f80241807b491623803e707f5b48de7b513856ca6eefe918357beaeb925e8a51f255731481618cd5323beacfb5a18f1af88b513baa4645b5a40757789c00771b42618145191f5c808a170a7085c203344d6460c134051630b0580a8389ef2d9376564a2b9d954a2979a49411afe6953f334a29a994f272369093569b5ff313c279b3219c509b32c29b26ab66361a279a29291a0821a438986dc468a6d2362a3d234633319aa9b48d4acfa06a6cb322118295ceee55ede479e6b287e59bbb2648d7b8eebd37621a35b95811850ffb834f104a0c0a9ca298d00006d03cc1c36cf292e68a2536231ac8ac5146cd0d67ac985cae5043857ca0a905300853060c282618b7032ce056adbde280167b75ba5a750087bdbaaa4bb282647457acb1bb735d920e7eb9820a1d5a3069a82b7088612079e1e588e8851559d02ff78c0d60993744696ddc0d285dc08b316b54c181304e40c5ec54625b0e6cb16d6a5b6badad628b12363de5a1034f154d6c7b2d151cb0d65a6be9611535dcab04864958e67401cac53e09319d18a14622c91eb8bf8ba948c1929323a27898c1131531d062861717164a2a8049188f1164a0870bae1b82d98193193b90c247961d6851811d98000736c2e031c271411457ccac59139b37aab7d393a79e8c41d86403c71c5faf18c5e002671b424df18507434031850a5c504a0821a432d628bb9871ce99d93a29a5349b76d639699c73427875d8a69c137eca0c3999e79748e78c311ecacb28c116e99c93b26084c1e61ad8a54c770a241ec86092228d1cc6589a820725319ea600a20230589ac2081ec6809ac2e90632a26a193268c0e4400d17186f08260d1df6771cd2c08018692cb1031ce016b918bb18a59c52c6edf5028128683e79f5f888446e76fd5dd3734a2112ba604bf92412612182208fef16391a6f2bc7c2b3a8c50d053b5c601965193c378c0c782106195e908f686478216628398a9297d4a7f6b844b36060cfc33875f001dfc727e910e2834f332249b821d004726965308d724e55d376d919c9d8762dc7a739b9cc1c9880f69cb71b45b24408004a48d769bba9018ea773de9c118b04c9d79c954a092f9c3d70aad9828a405953546ed5d4da2a9b3ec218dbb2d67cec7a043cb6ba2b48fe84005fa99ccaf52134c15be60440583369a780ba014e6d0825451a3e8690e28c1460ec6e432829a2aeb665f4a9d4a65d0c8120dc5259769ba57eb56b7a48ee2f6e6fbb67543f9f42d5d333aaa74fa16a7b6f964d2b76862967187b6b96e1cc663c5b4afc7876566bfd3785a7d6ecf6fe8b99f6ae1e82f2f54de1d936d6db5cab151ba70e5bab556374b58838216a2bf5dda44024983e9e9eba4610a3f6b25bed65ba08ce9e59290f4f4f4f7018f18293524a338dab9e93ca182319b576a952d62e55c71438be4a69350c744f3d05b6e52c82ca1b1b3480e1c3e0cb426b0d1c2fa7f0ec18e3149e3dcf13a714f16903f7e9be60eb42afb843b463522960e9b63293598aabf976d92929678c12069b3adff13cf2e7b6648fdc1c2e93590d60f9ec35f37cc600d77f98eeda35a148481084792a9b64b2ce6d730432ae7602d011bee6ab6c4277f6fbabb1968f99eeb867e4991ab73c805fd76d60082505d37e9a3fd58b33a4e8210db659e3c504c6673e4872c1fefec396367cd0805b5b6489c2cb52144cecef4544e103de36848a62a9e5a262a622a5f44a09e79c73ce39e79494d26cd23969add44e29e5945266f2be6094c6ef7b5dae9b35b0f8e037ac5f26ad36bba9596bb5f225ef8f311827ca7fb5555fabdef5663745b53937eebe6ed00666760c7233b17bea267bfaddd45aebeb9ba7347f5987ba5258bbd93247a98b7694d65af5a5db96c6255f50c5adba24f05072f3aa99bfc9755be0c8d9dca8ad16a594bb9956df0843d549c9cd0be16d3229dc340a21b4787230c0f1355575f637e7cce8cdaebdf207de4a7180a5a473ceade3b0d8b009f8e5527265e008e19cab2cd35524105b21b1c6524b2b0dd4d635965a5a69a0b6ae5943abad960c5bd7d0325450c69a32ce9401633c53c6cc4119136a4d171c37395a5bb5569bdda036752e417e7435501d2c9ab05bb2cbe2a214a4c496ff766cf81d48246e8920c821104875a82ec378263b60e7248ba57494d9338613714b4c8a4848a94deb5210c5002865b48debd2f2a203c5a170bb255b077b71af1712a15be7352d15bb244d97e7aa150eee82b9588129dcb0eebee068c375e1c2850b17c8850b9c40383155c76d5a54266f78c117632c9960192424a41b9aa0816b820ce73406eb092b5450a4e0c6179dc4b277ec8fdbdd6119bb3bedba64815b74832203504f8800a7f9b89db556e044164e423441868b04504d54e00699a14698113c098162424c1c038a1928818118bc98c0024b21934503496819620359d81c3c80a4451f3a5cd26c7afabb04984d8f97b0c1a63c6c8051118c0ecfa64d6b36e5e2b4e95d9fafcd8383314a9c804c53139b5eea005320586bd3245ab029941248b82e165603aee86a0527581981034427040e2f749cb1c9a085458e0dba2daea441a3b855dc242db57ad84a80a30dac04ce96ae0d17746d58e1eac1b526cbb6869a6e0d3170769093a56bc30cbb83141b2758ad41828b46b5668bba0136d4746bba986bd4c099a00d1b562658b3858574c5f502098acc0b1acae03594e8aee83ac688d41536c4d8aea051c65e69b9726c08e5010f4499800607f07737f528de7e105949795c3b50603829fe04d20e537fb2e93fd134edddbfdea5bdab6f2c51460aae6042aae2a4e9abedb38bb9632e45027be8efdd6c96dd669aa76a29b019b5411e3c81644c27661ef9caced26480716c18e80e627d09910bbf68d3de3dbd8c3ff29c5c6de419d9cf6b8fba48fb165347927b11eb9cb6124be922ed5d113ed71dffeaa26df39cb05e5fc47ad5de2dd2eeddec292df0b32cfbcd32d6b58d9b167792ce98bd5eead888754d7b29dd2435fe91bdda357d548fb5778db6b3b41f79f746dab973fa086bef6a8f88c5fa1136d2479c3ed2ced2de2d621deb2a4ef7ea22acd266cabbe7aef69cb058f89c3eda8eb7eddebd11fe9177b551f7edddb7145665a20c65f664d7f20bac55f62ecb328c6941b3278b58e42b68674b4c536a4d1be4d956abcbaf3a1b1b9bd56ae5c47b77239823078e635d25e7385ec57b8e5b8949d8c91f79efa0777ca39cab728e737ed4ddf3debdc87b77a98b72367c2bab17b5be7afceac6b1ae62f31bafd2bd752b31a9d25558c7afb2baca4a0c77c72fea8e2f8f6daed255726ef32adebb5b896115cc717c231c57e9a39ce3c8398e1f79cf7118935d124f7b91ebaa17e9b46ef491cd6f9ca5bbb7eea55e74f3d58bd4c70c7a4ca27dbcbf70cb6faae7dc087f471fe91c3f47e7f847aeab72b2c77de730e6658fd39e13d7736ea4f31d9de7e8fc4875d7a63d4d3b715d7503b8b4a7dd4aec06d0d19e86bde39cbbeed2559c8a5cf77415a873e81ced11b9726238349e3d32277b9a2b7b5afcedbf6718cb89da8b5bd7dd888573d63b9cb37e74e369ddcdbb1be1b08ef38e759c1fad7e93d24e6ebeba016eb497ba95d50d80a359bfd15560dd9c1e91cdbda2ee30c624de7eebb2973a8cd9642f05e4053d4ef51f8c2a923d267adb2cc53ebf90a1e46c639927ba70810e9e32ed31a1b7b75a4ad534c6b2a2c9951a2222e882572f3902cf675789dcf544f2e7a2205ff0dbf69b67fe22929ddbf6cd6e5151db3fd8e6dab66ddb52598bb0bd7d9e955d13c8357bb61c047decef9c73cb36752d9522ca02a77e9ba4e007a1b8772b9b1cb77803db1cdf86e3baaee362ace3ce3a8c6d94734d208eebba23d6b9c318c775b7d968c7499b89595fddc8c6e62b7de4347fb4ba11be8d66d91cff88f5d5eaac17adcefad445f8361792b24b01c66e646f7e35e75cadceba951b87bf916d329c3dacbca93a4e952f9e3d54c70af0fcf743bfbbc10e4ac2a8e9a106259a6a30c40e492d08030653982fa830636fb03a2d68c286304a6394400aab0318558c1b5610b5c54c0d586ae982dbc2cb952bb6060f2805f50086085090051a25184115b11578e0863ddf72cdc3a0b2508305582e13e6e2ea722faf27a8a079844e912ff839e18c9762ec72fd305850107d3a3229a5b4a5c33329a57386286f1adbd4b822f88b4bccc7bcbe809171e67e3a1066308319dc6aadb5d65a2d7de40e63174e7f3344cd3066b395da0f8aa182c6d7b64f368535b395521d635652334869b3c43441676064bcbef818971809effa8179a0b4caa0517391b09274c5a5a02818547c8a5d2297c804d1ecbab4e76193d8658628a6e8c40c63b643910f0a92d0ce9066cf25f055efc248544c970be309e565cf29e1ebc99e8f51863d9fb62836388120661d679bbc719f6bab609324ffa26cd5b56c73b86995feb46df3450922e7375025e0b259cff1b2332a809be7bce626dc59df62d62eb3bd0e13e09bef759800cfdb6b9afeac703902d31b8ad175d80920eaefe611a05b4a8edbfbcb62e521eddc6db04d86d0475bb1ec6a65594aa46c36e3d8617baa57aced3677dc37eedd6137b45dcbdf0c43da59f66a18ae24d25c71336c284aa4ac0eb7262db4812f6c7889a4bfbb39dd1d4b24782eaf20d02449ebddeaac434fc926a5dedac36f96bd4dc45ad4f4070314a41d03dd10b0f138373404ac705e8e81f5ce1b92c27dbbd552585f5d062dcb2d7f43aac3a8bf21d576a1b9cdb0a36c2599d35f448236f90171d35f44da753b772119f12a07cd9774c1107713d5211c426f1f5065775f6596a6b79a9eb03a2b4b4969ebc5572fde3ea5691886b6738f7a6875d6ed3f2b48dbea08d06df5d097b6d552bac32ee7782a47a4cdda3547a42de5e639cfa199b09e73d90ddd3ca789eada6dd6f2eab06372f39c9c9c7d31d055f68658bf39eb25c002c505991d332e764d6e76d4526c0452dde0ec7532de2a8abfc16e88fbea36774d589f8da68756d71a2b472fb3dee5cff5c9b0216641c882c75c406d94bbc9ac1bfd459b3f6ce1d6c9707299953f2b4838ee23d2bc4b37c9defde6f84ede6e471638daa7e2eebac76fac5fad752931d0ddfa77e3f0f6563f40eeb8d29fdcab6f5cfea06c2e8a2a43f1b27795cd0b5bbbbd97a1fc91e190b5ba71564b47206e9ae48b9bdbdc856412ee5df6ecf190ea9e3debabcf72df70b657e5d50ae7eeaa5577782e7fac7faa73d7b2c7d24338f7549e6a75f89caf3cd5f16d6eb377e3ddedb79c7c739cdc3ac769f9e639593bce8da6a9f4d0cd3d8ebbea387b9c1e627d75789ce377d95bbd3bf7cd266fc7695d95bfb8556f1d276fbfb1d19f95bd1d6e57e5ef861eb2b9b7fa567a8875acfad665acca71af6e337e97ed6d32ebb01b6261fd5959e91828d7e5edb06312c28e31c429735b1d01b9777400efba430b1c0fe7032c7c96e385663774b3e373e40e33738bdcfe5a0f889b76f837e32ea5303563fc1253293d3f9d9de97ba189e92facdcec4ed8ae6961da94ac54a7dd085d707c2a03c2f12c098eacc304584a03b27fc39de9b8458b7c55fa6d76f84d38eeb5c7b93d0ece715c38872e1d97628775131c1db1e4fc669c692109378e2cc9903d395a96e1e814570bfa581d976aa10ea894ec98a972182a44230000000000d315002018100c09450271300e26b2aae90e14000e7796406c5099cc634990c4300a82206300208600630820840064942aa20301946a70aa9e4126bc9c963159da9b7e6a1782514880c1f32f774369bf5052c700b13e82cb399fa52af277b9f0d0be18b0876bd0d1747de2522fd6b50b1a412694da9fa8f606ba690ea60c289d1859eb9757a711b4702dc182bd32a8bf5bd29f58d408607197163c02efa19be661da82597e4de90c15f52dd204b4dccbb155ba44abc55220889d5250d440e75fd6a27da565ef7305e2b31502c2c19f7d8e10781857526cbfaac8d603e3898e6577258a12a014bf51ec1e95ca69561c00a6ddc5a608be431d301860c8ed0e9787ed2406c2825bd58d10a623630f58985a1801676ad2893ee0aa3da594a1d5b4b63cb02e826708cd6cc5497ba94dd1aca2a9dd5dc3dd3ebba452329d7322174473a262d4a6f510c7818802106581ca7f830da512e18d059778e5591a15aaba37ea057c195b8ab37314dfd9e7993adb4ab593cd51fa3c57d402a5b839d5d9a2492cc4404247883acc4420601484f3c3e32509d7f4d2c15b8db31be32fde779d62291ad12f7c08e24d5c84601ac8688684c6819f30fa0b6392a7619a5db3e8078d97710c0c3f517aaa1510eb405bf551329e288bc2ff9056fa88ca11fb7dab724506fdd8fe764cae53dbd5447e6898da82e3324e87191b76b3fc09ad65d42f271fab29f0b484c439502f24c003fc647a0bcd03911591df42bd02406a38b8924d50c05a00f190840ea00711de30518268952e84556ec3413eb6469482923289a966e568a7dc7aeeefdf159baad3f4b8d5e50bde370db8674310eb77e59379d9a708bb8de482ce0fe90e0eb41e9f366bb18a15b55730320d591aada7180f4708bae3668f6e0678307549de09f455d8c0b6cf12b9aef5a36b5c8e461425e7cbc1261836083b22a328e9ccd70fe11aa5da44b57014ebf535d503372bfe085f122aa26f168e462e048cae005da3d801e6ecdc0fb443e894dad8b204ee41b52f491bf611459364f61598c5ed6f56646f12232159dd37a3f51e093754c77ff125fb62f8a9a21c86073ed636a8306528554bca2a25db1278d603e83e8a7aec0f352028a6792710d15705a2807129821033548d75ec52cbbf0c7ccb466dd056e40db05d28c9066c9710da27c56ab21b26747771cb5fd6ec1d4afd5824a310322a27c516079ad0322ba7f33d30d6d017b8c47e0c635085a7904eef619e2333c063995a1f648d4af8bfed8937aadd61cfc061820cffc6434bb763c34a9469f74464d9b11014c0d82b88f6c3275a18af3fa2b053eccbc81f34f059cf05e5fa10286f6b4eb64fc53d442c55ac87a72a648b153e835fb66d132b61ad1adae757066402db534052afde3751c88106a10cadcb854350d5eb2d0a31d335feffcc303e46f324740124ff816f40f4aa2d945578ddda388c22a4bde6fe2942a09285cfac04877883264e0de93c8f505097a28be9c0ff28e7fc4ac6c8de4b23292e46474a3888090fa88f0a431e962ca8fc4829dff3d90755f0dba504acb0ff256e4643d7c8a090b63591f5d81e4bfd6b04b0041be29c93317113bbcfbb55815384933a3291b2f08297ed41297e88123c8732329ddbfc545915e56a7550fe97e658ced39b28d4099f935b00b0f5e443b20b0a2cc3f68fb1b4e3f42936b35be4836c0877ab9486a0f5081f347c59d72f0940ec17b1deb8107098a7392149972c8dacab1655b33e7f99a6adb094374b432c0d9298a45e82d9906443b380d9063ac1eb9a80363b5814adcbca7232f47d5cc19b98fc726f8ae8f807244683456a59b43994f645e2bd28e0f4ad7926517f646f4998c70d332c7a56300922c1a5e3797e0729fb9d1f8aff46695713a835c773c1451123e07a2a04b04f943c88c9ee0b687c1d6aeef18e71e4a3338505bf8109201099ad46bbf7c2837dda08a6ab02508bf0b3d5a1ae930a8ef2c208a23340ed3ad630e46967a73cb17c45e148f5c1c9a9cf4065f9ec912ac3f9ee6d11830f61eef026e2365957a02f820415dafe6ae30738c188b31204a109a36d95b03b2215fab322183f36f5934ba754137d1b0ef3c4afa1758f90fde37819d434b84c74011bf0d6f5883406a5742946e507d3c0e5bf204f09860bd9d2f2ce7326f982be1383b0d8438443745c9eaf514c7ac95db1d521151327b6c8d50063ba8a5882fc7372186824a45a886b788000e2db420d801a31c8fa1d76bdf45f84ed82478a347920e89bf93e1e4fd84ae03e90aa43c32a7c4da37295fdb19269b69b50c0fd179dfbb1f403f33e297b3bb1df44318c470ba591bb154592d76eef54e5b91e54aca063f1505188fefac26c045bd6d53a82b32c5d48e6995950a0a5161dc71ae261c9772400637b1f6bd1d3a8c2c58516d98523e787c62e072c7f421e206552147ba2a8286f120fc768f8504ddd869b109e1929ce61a2c7315011398dc6b07cf5f9f80647806d0d56fc90fcbd6967940dee1e67a841c703847d26dfc27babc58244805092230346d4b6c81c9086625d49577f793a64fda86db5fabc144d5e47de5513b60eeccdee0159c5786939a7224193acaebdcdb915b3ee50f2c71720b3158c7fd940343018f99931b24577086451b9856323104308868701b0ea87945cba099cb6e789a1fe861cb7a2336cef279581d2e13c1b889714b908074e2028857e04be0ba074e77f7edfc0d4022476e3c2034199af049d206ea9da807521d3741495685d4d77ce4dea8ce1854cfa64592bbce8da72af921d075f4fae73a15beb57742f29773af7c84e298ce4496ff7ad524ad47b96d3d46057b4db5d3de2bdcfae76123ad6456fe4feff92161ec38e7d4374d572448732fdc518acc302e148ce9b98951f47d092036369e59f3264b60ee3423cdba8bd1c846425ba95cbeea80b7e3e296b0badd524bb8dc4152fc7fa8e9f95434e31c54367177a2960930e2259e490e9b3b03298787912c0303d3b43585bd119e2686ab6899081d3b6bfcf5bd4d23a10d760879f7d574848956e50e411c831b2e5e698828b9b6171471dbbcc1f9f03ab5c84d9d5f7760b13797d82e5755b71f7017e7d60118f914b1926e8ef11c59286b602ac20fadb597cd132fc4c40c4594d3b7acc7e809402db5066463e7e5d48c3d8bc678e8519807a0a8bce6ad127a1e24d452d4ca096b373390265b2954b8ccc8d4b974fc361668f8b47580d2ea00c879dc83e700c917592d8b91167bb59981a29817f69b9ee6a806ff97ca8c821180d09468c0106221275b05042773577f7eb8fef0f3fbf7d489b68645de51e027a1a9b1c9ccf2616f613e654ecc0082bc844467a52a42f2c00c1ca99479ca6fc297dc4a235140cf204ed843676cac14cc7e770000d6fdc65404f03142fc37d715e945950341ad1c755fbbbff3702e311cb9450f23aef20dcedde5e52e6f02e4bd2fbe7fb0c6882ae633e0626e0f7838fee265aba10dae4817d1eb425c0d77b1a57846c2cb6c15bc93833a7cc250ee94debe58db9a8c0a6d83a014bac2555eef64801b548eb46f7ba60d2993688d86fa79cabecaa0721be4eb4f521df625cf7fc864c188afe2baafcd7e99e3e17730515795485e99253cb35f7b10075c878086ea5b58931bc33e6260535fcf1cea904697353ee7a66969854768685b721c4c67e97f950891cf6631f34016c8004ffa45ff2561350747fec1604d219e3b26e26de4a9882b55494af9706cad4a88f73e1f9574cfcb85db1e183137ccafa14d378701222c9be12f621639bc9480cdea0707a13208a91ac6b208c9d1f20981a3163c3da617b87db3a526af0625acd3331a73e0960c6110ac8fa81d80baa82b6b5480ca1353523f79f5f314ea0a8334e2814371860230a1ed33d3a98efb10b4a7211e67aa1c5b9908ba1ca54c57c877c42d66992ca5e601bb3ad8c34c9d8305b6554552cf3de2f6f15a1ade069c88f83355cf4a839a04d83c11e395888b6275f71518ff1a0679768233cf483ddb2bb3d3e326e6166defd884c6c16b208ed2eeb989469529213ce01b111826905af023fb7fd6146df1277b309dc28e102203045ce2080d9cd81677fe30b1731997effb4fe988f0b34f96c8920c117e18485cd32eaaa74c1c1f84dbeea2cf5096d66ab9cfde0e65cb24c82554e9165476e9d4b935570cb0adf7fe7c15951bc03c26e12295d8da204220424782647902575b6560e172535669f034f5664e8d9389e9d93f58bfeb290c4b5d39084f924bdb2456a75b7dd6a568be9531f0a69d372011842c20567a6f1ac496c63ae4f9b48bb1d285a412afc67284a350d5bfe18eb049f43268b998e6980155e8f231c49a5bd4e8d4e8aea4b2ea83d252c156b08c973bffc8b4498bc9ab33bd5b0fdd317e160b031bc5753b6950367b082867da2d11a5d34c5355dd642993c54b5feacc3b1448cc72f0ad4df1159ea12335115e7bafb71bb0d76af44c9bedc2070fcbbfb36fcb88ab86684929e1053925736ad2e88011c731b293e0fa73c202fab60adec36dae4cbf0252d6c46ea76cbf8d2f59122ba5c01057595955ae2d9acc87ab821c423460fb5be1034fe9aeec9561adc80d95387d82b0e924c6ac4af58f31a20541537ea762b2fab57422b684ca95204188d823d2f7c9c900e0ea445863343847e43520938bab887d81538e8eb936bc4aae9b8a9be5f18cec1ad2cf1a767b2030d5f1d85bdcfce8bb6d21806c9e7f0b9f77b86be6f633d91ae2ece2e826ac4c4e31b875e25e476b2ea89894b6805d349bb99d41c1108cc84af010bea728dc5768a9dc8a3d8f977299e0f76b3ee3bb3d4bd727c569fbd855e410b93475dab51d61e42a40753011e3190d58b53e4f93b6d64fc384e2ecd106f16fc0afcdfdd70849d953a037b60e9eafaed1768446fb468287d11f4bb42601f8e3e41841ac58826c6189acda3fd0a70c9d7792f368791418c8e39e4e26eefadf0ed092784ceef4799be02370b075032c3179aa66558db0a2397d5c8a87b50ac49661d149e32ebeda5b0edc419afa4c63ba19e6f71eb3a1c2d2b41d5a4e357eca4c3974bf00fd300ff66722210ebaa53fc0bbb239ebc3b778e603c36337ef5deba04ca0c9d388a917907ef7539abd1cc32f9cc4f364449ba01e3b7c11dec29590ead6254c078bc35cc368b88a9074775575c4b084718b99af999e57599e1c1f619a740200aa6caadff73508544da8729054cda08f6c93ec723ef55e7a737fc8d35c10d6d8ba7930755c01014d1a930faf638b151c41a4d93a982645264c9c6d1a5dbe08b4d937bb900e7acb4ed2560731fe6bda0f946cadcbf4708fc2e0a2b097b0f259ccbe97460416baf5dae28bce08d04050bfa6cf886cd577e3604a42af3c3507587519e0ba5dc98eae3c1e5e95925a5de75320d65ddf7163de8d203d00d12cb197c4e1093638564893641620fb3bffdefc88119cb59b7a4790a5cfaa07e76f1d71716da62bd8c250e082ea9470df3d462259532917fc00b5fa8d9f9eefc917de2483cdf2fb5f6a34c0fcc3bd0030b7fb0661486fb452f6c508376b55ea3c0512aabe3cd9e37676dad4f1119aa4cb12574eaddb5ab31a4f0caa7bb268493cbdaa9e4ccca2e1082565e55b6a3d7cb33282a6a14e64b95a7c4272c5a6e43b1213e360e6ed236d447082d97eafa04a58645371a6990a78a2fda40f12ad972e1e5d6aa9f0cd3498b93096ac25c89594dd6f262e006f185e3a0e811ce53433dc0cdfb887b3856044e7f3e105b0f046f564fb1c9cd8820c381dc287e714e0cf0e14289fa7db38944ac40bb9a15b285aff73643e5a83edceb5cbeda46b44503638bd7fdced406e75a00f1012f6742c4395dbb78d0b62073c117525475de6c41a9979a659ff6062790c6cc20101e43f59026320631cc26855fdd56bcfb43692f9e85e9390b9e10d720b74c523304a049f6d5a25595cbf820c52a41c5b683af0081a627181712e3597b6d002a123d6a054bfa532eaddaa1f9c2fa43297d1db09da529ba77277c7fec848d095121bcc1fe0ea2e4d71b7804deeb1194f65b3a3defbdeb6ba0e8d7be27454f985105883d9f19c009c25ed2f558c8552055d4fc4e7beb8673e5feb4354df06238859f31b5cd5ca2c1985e9d7dfd6ea5e4cc78b32783196a30052813c453d402707c9dd481d22d66a6c454cd65959d106b3961c1fc04838219b66f3a9c0d745895f849a2d68e8bdd2768ab90099289e4ff875b174dd9d0aab3844220aed91f4ce8c8165a5cdf54cdd40ff0b4b683708b18ea4264ec849a159cf2500a62320c49be9d0908afc423d8ccf40f6f16927c7c9e029ca1e27ca8963c20a8f15eb27cdc542102bde92dc18bcab89fd3cd6689f698741cb659681d73beef9a44d50cabcd6ebdb92a88f164b6352d93b05d236848089ee5f1a07598f33279907e2c1b9fc55bb73df30945321dad79a141916b23296d07b9d2170dddccf9fa146534333a9985739dd620fb3bb3c88c2822f7bf4133e9fcb6cb960605fcc06cde5e5b94186d045bdccd248845f7298358e49832b4a3ce2c0238747aca0e883ae39af6f4963ab029ef3d78814e6dff8220f5ba41be765470fecd3209ba44955d00eccd9fb924ac175967c264c6a445f8fc5ef76651d571fc7e4ff731f0a564b408835144d87b883d7aa3f24750404c06d78c9f065b57a7033331883a26fa1d3de69aa65f453f6f732b623b23a21d6cf1fd9a35ed77b144ccc2cb0ae39a7b744f8eb4856303818e5c82d2018bf22a3cf078e359753930dea267f48e4e9d3db1d5002706c724e494e462ad471f7eccc734b7dd02a384c4f402b662ff33c35be93c06889897d57657e7977dfce32a0f6d02ce3adc77b34af9f22263ab404f88d1d9573e6a1f1b1607927e81fd79a4d1e8ddbf3a2c8e1a92f8141a22c1a22554bc88ba5be93af5a54ab608d1fd8cf2ef04404b5f8aff50c82779c3e0e68150535ce4e04bd97aa28541bee0e214e14a204708356bc113cda25fee0b1e50b9c00645de6cf6ae8c113225a3274a6303b34aa7408b8e5c5e35c5cc80f10f24cdaa6c87f18b4774405a24e301b712408287d9e48a72daa726d863790fb46dc3543c8b7cb7a3a1394b70afe84867141a6594d2d36040908abb65d6eb539929de81e27217e49450411fba87c8a175df0beb3f81c64559f45532bae17626adfa837056872bc8f51628726817274d0c17b6703aa0c559397133aed1a7b12bb1d6029112b79cf5a26529069098aef22f0331c814159d4bae0cc91db603c5bb0e3481b479f951c67a06f075937d19a4e2d4cfc7fc2e604f59c383a88f1775fbae93a4c22c529a4581a0ceec498a782cd6bc6991f4c08133f7b14a866a7b13861ca448d738ab9a8e60a2015dbc2db192097bb1d87df887f85cfc2d462cb6e1c54ca4090374bb12e91d331c83da119cc96c8dabdd859546c3c69be55a6dd44924ddc6941e219f2880595749d9d70f9ead23a6838d09a5c48cfb800aefa1d9978105d344d13eab180a0e578018e103145a6445950fa86728cdc7c59ad19a7030f34b25f27f7fa0651f8b667db49140dc1596c6fb969a6fd2b99d1ca04e3b380172b77aa7262d428127937d0d8dee9f40ca82450a11a197bf7c4b5db0b1bac56f7f28d629cde4694893d7ae88b29a01bd2a2944a8afd05fbc0adcd3c32e58047433ac6449c48f360bd922d28f180b8c88629b92a95bb77501769136a98b3f2a12eeee8e53804cd74351a6b2264c2444a240b25c9330580d820ef0cbd726f6e1749f6faf279e37d1663976312a4b485f1e18abe117d2ecdf162a1d116b18975bc27b6e806df24cbc0618574485beab41b8dabcfa9e0530fe93d20147ea837038e13072a496be7c23ef60a074574888d979b6a690a4cdd5b0f67921f20177ad9b782d86ac95c2b4acd297fd34d84e81be44750f0503ae406e6d909e9d0f46a9009484942124af377600b4a3037f68948f14240406a38f501aa1257f4936d3a0736288938b80a2110c6a2ad1ffe29445959a62ccfa95f719235e4cd94c83a863f75098cfee97cf6c7d5b0dad3759b35db5ed8128cd0322919e2fab0b7d08ecc302708b99df12232d1c824344fe3670a0c21534f89e0bdd2f7740dc2ec794de2728657cf141e0456cbe33026be29b8e544f6a4e655bc6506cf9e9540d80a3b92bfc78729e5523c8f06d4a942d72fb1bf7cd3451a01c0904e7980ee45956d23cfae32144941d336cd397e37d509e6124216742ec8548210b9a6c9cece18671639c6d704f63bb6565f7690f7678721a690af9fc2987d0548595fe30dcecabf6192d83c78f8189260caee6bb691d9e5e1c388a1a4089baf06861450ecbcfb02efdf7cbf0a8ceca30eb394388959f2f2375cf46470a9dd6671e306563d697e89c7b427bfa7897273292062565a4b0a3f85fe76a015cf2ed6e4e33417aec98284e41dd54f517c69324cd5892b9cf478a8440b876e37d2b6bdf2d6833e91cc56071aff6dd82038f0a88bc643f7a64bc074518f8e200719813b7a096f4e435f632ac612c6657a4ebe74d23aadc5b5134dd160a474a1a0034352b1daedddd909917d152ceb277cfa11a74973f15c2803abc6d71fe45cf1a879adc8824246f99a8c3e00db7b29c5e1b3058b7878fa9db805fe1da0f2aab3e50f687b5aac7023f69c779d41110cdfb54beaad3db9b7e139be161eb7b381debd203c96d3b71dd5820e229067e67bc44cee6f5416fe2043f70a46a9ce7a88a4a41b037f501374cb9968144aa8606ab69096c65d3b6a6d6d9f9bc845e2d09011a0961b64011f35a737b46b4679b68599013b56e54b5e277eaa384b819ffdebce902a64b8d002c094124e91e1f17d4b2bc8c20d23510b6c9817e45ba782a94284683fd4dc18e474dbb636981adead3251354106dfa25c1ce77da0c0130ad39f1977d592629e1125846d6a56336a105d8fb58d1ceb641ae013772cd3ecaf0b48acbc4bd3ff9b70565709f5989960397a587c4812e8cee5d01e27ece34f5b16bc86c37312ef8cd7307aef4e6b6b66ba1fed527dfd5ae2d9c77f3ae61c6378422f422e9ccc85541904d391dc4fb7a0153baf24700fb94965adf575e1596c694a3d3a381c6d2db755f630c490017e9a4ddbc505b0bae695a6d42dd2a492782cb59c5d5bf5922af9b5f3202120c53b3eea8c9f2021b0dcc3c943e1ddd0e843a7d9a8ee0ced8dc87a668f6deebfa164db991e1b032fb462585933b6751032b651960d0f2d026eaf07565ffa40cb8b25ed15fe84544923eb111e29d2efaffdbabfee193a5dcbcc5c30def70bd7f585d37cadf6f2a8897b0a5ace33f8302b64b284f547a59ea976592065a23280b35f2780231becc3198b035913e200e6e78f38a3ef9ae646877463bb09c326537937639be1320652dbbc72cf2b645055171bc9601cbdcbaa70eadbf64c54fde5f78c42f17d03351f529b2f3cc55b1bb9466889013c6074210694ba8c3944ddfa8722929e668ab163625f7cf03d3a61078fa779879d71879d9f35c1829c26e55e83adf9cb255ff4bb0ff7fa237899ddbeb2b71fdf93fe59de5f6418793464477730cf1faeff90781cd1ae17df7ed625dc71e3b4256547fd8586a819b0f26dc7f49adbe7f09fd7260259d5845b50c43071a2b2346410b054df91ca0b1ef371adba99a9d556be71a95ad49980d26185a8c6ae7808a999a34bb9375b423557648d6ec92d1a68e8aeb3152257fd3573477ef62471562ccc4474707c056d612e2f1863fffd44a0685990876b1a7c1a6c68fe27a1127102413b432700ea794187c6f042718ff6f6c0d5a532514fb5c83aeae80856d64f0d5c169e4119e56cff1e2f878855bf37df1a5fc22cc0c6c98a933efb111054a7538bc1c32976eaff63040876a34e60eb67e442f4c126442abdbc764ca1673e9a8a45a766e22c30224a2c41bc9a86b10d6d741837914eadb275ada129bf185b0b9c46eb35afaa91f133d5428d1fc0d251ea7e8e1e9db869301e22078810d9a459a8974cb822724f588298a4b0b25c69fd6888c80b94df1086714c726af358b756f1fc6360b469571aea4556fd16e8a2760a2507d92a05d2eb7f6e35e9ff1ded52175d7b163de694a05307c59ab70071fc791c81f0b8ff979e164e3d0e1bd3fae6d44677e4024273174119f23bc87abf1189d5a34ce780d754415e295301f711fdb51c80dac45cbb17f40095220a97a113c50aea2e979e044aa17161b2ae9289f408cea251fc0a36f84174c70dac9d7d4aadc4c3ed722e7c2ab3362e7e02751cc9f7145af7821fa032d5b3aba9e7fc3fa4e358501eda77ef6138ae9ff0e0d1cf1bfa184a350a85b5fe1e75293357819acdd3afd62137e1ff59077f0301d32eb45ce0f98d46a4d46727b091a64f9a798093b97b92eab9c249b00ad3674e842c58fa9329f1378297a8a75f1f41961ba3d9ebaa2ac65354d92af723edb1140eeb2b557117f04bff12fee3807f9545998edb4cd24ae37ffddda6a15eb750be48630bf0147a7c0f44ed909d96bddf04658fe009600344284124d14327f0724732cd16d26d9c4271653faf0646318bd4c9198fd8a7014341a0a8bb02aca93268aac4b99da82c7e5587e2cd51c45214cb7229cf9b43ebd20c72bfb14f73978d1a42c23e4a19434d1052cfadd44c3b45122ae519d2023d8bc19f61fed2f1cae851b94ee122aed9c09884b283524967ef9e0f80fda203827de0562e198c2e1f0936a1d0dc14c53e4a64e2de08e5cd384e32caf568e4954579c32a2fc83b6aad4adaa702b0e8d64ff2ba18973ee6d49a35f3c805393b71a1c8a3429d2ffdb623b166801409c82f00374482053462dd309db084a50b73f5ee42de462c1d4faa12d3fac9ea167dcc4b7803e2315bcca7a75154f5fe1802291b5eeb94647c580b297d485e8cf17d47b74dc55632fe149cc098dafa4a8c577c0d713db689610726ef5bb95f3e4ab1efff63516dd143f51ae6766f91c826df31f9a80e75b5ee97bea1e04d80677ea5ab55e08fe736d5854746185d1adc0ad50f3867058a3439d6d38618d85018251831d37007818056263c73993f21dad787ed20e203e3673dff800071902fdcc2089529557c86a5b842e7ef5c7f84022a1149122a8e17a059c551802f07cc64bfb93b69b116c247f26b3e6bc5107af75af33383b87313d3ebe6753b6d0682f12a4ad0b79b4e23a8760a8bf7d03fb4cc6e65c954fd578471313115ce7e2d22898892ea363828f9f1a5980509bc78ce5d32e048a0d86533c881edb05847e2e922fb12283e738f69c17d79229f848e923062247ab94b8d17ed6012d258695917cbcdd64a1913aace0b10a61c6c947798f42ec81d84f2860390d48ae9bcc269998f2c79e66ec5bea917a849283c3c705851b90043dc9c784b8e5762c99b03c4696258c194991e64ea43ae20dc996f1f86f9db8e670e56054d126a679c15eb911fc048700299b6ef609c9719e946381850d8129079a058ceb6252aa2322b2dc797415e196663f7cd40c5edb42583769dd8a547a4a6cf0a1d7f169796506bb6cc9eb1e6ab4badeb91d4335e6a9f680cdc4060b9e2fbdb87fe950ccb19928ca1742f99aacbb63c2c0a64d7698f654745d34155ccfa44c18c3a841390b6d38c97c0c2cd0510582ecc6bc3ad17b26ca34a2fb215b1a6e112ed79508be134e09ecf97a84ea5fd9eea2c610e4aa977da1cd9011207630af99bc0ebfb13da475e51a8b92bd80ca80017c1adb0b53d66d009cf97e8598139cc072c423441a75501f6f45ad4c1afdc6b7c18ede322a5adecca571632a7ca23e259d0b3aa7f207305f23a17e45d4a20deb6e684c9dde60ed83b6e121ce158c704ac6ee642644befb05667a4b5fc4c01dd37e4d476adfb6ebaa138e93648f17f90c963a989aae13e793d29e1488050d44ad528692c5ef8444193c4687d984ea17efedf49cd452264ff6916c5c33a5306323adc1f4859fc30df9c4a09dff6eae7240a7ed4d6885fcb63ee56e975eb90915dbe883501308aa36b0eaefed2da7d5f5e7f10e37c544df6f643a7611c9947346b59d71cc60c46a1d5d6c41d450dbb410cbf79f17e202c43788d89b275d838352962450fad12b2348404315c347c994688b95544b9f85be0cb3ebea3d961b99681a5b9bd13c329aa23d0a6d2ca80551323394e1fc76ef27787d0eb4b89606161cdb68e5f538a41c7528bd9b525624db2efa1dd18be7f14e7e858cab45b2306e726789d1776b30347fe90d023495b923b74d674739702c9b9901cbcb9a4e7d3fbde930d6ef6c1775597df630baaf859171dc51d1d93fad28e8a452ff234801a9ed7d2cc2b1684453bb99527263b9a8c11d654ebaa5c4d098099e49332a29dfb6113293932dc50d822bd19af295c1fa8265262dfa0a5973e750e33ecd037f8c76538ad373ad9f63b9e40e4dff4b85e67f79660e27ed1e45d10d710ff4b92e0e0a887bc72c915e5fdbe6a5f2648ac95f163a8edd061bad1be3234b27308c611b91043d99acb0f64ef6f6ad04d56bbc4e779248775e1bc128fe0118e33d3d6a44c0efa759e7d857a77b3436f6f03d455a29286c20e6d9162cab9a5a0d24d1c31fc7db8964be9f4fb6d7435a0f2d165ba0f50010af81e0527a6170e738ac0d3f3c1ffc77b2cd750627a768cd1200c86597fc8d7671a41f1fe151ea7437534874612c25cbe5a29bf7192c5670b807de735659a91990d10938e0cb80e9a3ec6d5f53401f3670b9f3c2cc31ed0e6cc1311a0eea6989f92c9e8ce89ae97073a0e84194827076ed2e7e2e540485e5c5cd3c68ba16aab9b28346116a97cf101c815257cb0012250658c798a256a8e8144d3eb0da89423721cb0a25132217968207a076f8e0d40dc0607358a35963e6e74ca502187012cd8405efe5070064d9f403f79cf56f66a0c0225a7c7a1fa97d0ef6563bb6a42564e43539fc60006a717829a05547a3b5af02c43cfbd40f196ccd5c9e4f037042c46439e805dbeffaa7ad0b5d3018f2f3dc72ec8521e1c5fb974b90503d5c76b1a80404695f710028f69e1f87d34deda7b7b28058a70ad53a5f74eb429c44a52f046500f5fc9eba780256c6961d14835dfaab413b21fde04a2800dfe89096bbc878648bb6880680d9047479687fceb201b1d57c0fba429aa967c96bef8e6f0ad7136670656078e3ee3fbb4d65cb0af1d4f2a0a1b85fe8e0862267f748390d8d45eb1d16e0fa3631fbe0933de0894d3c9697e5ada267cb8c050bf80906ee0cf5fafe4517cb801a1951b17088ec2512afa0f4be5ace3ae61c343c627f78e354bb97cd0629d1a6e924571008a63440fad7d5f69cb6378a2f3f456d2ec4f617f5e8ba8504dd783b650c988e5b49585048602970a1b5642a00089405d71ca4f7b40f617bbf36185784fe04ec4d9b5a83ec0fc96e1c76868e8ffae0e7532d6b7402e5864dc77eadbe9275e403bbea8ada205f2550c50880e9b0342851c5c53007b7d8520847fa489c60b3541aa4c35a7856af6496392486cb14bf83a271a3b693f742bf102c2635b03c601cd23efa33646f5fd9b46fa5e44204727ccbb221934309c111ff8758719693b6ba2e89d12a3ab6dbc7b243f4bf6c8cbbb5d257d00e40f3e1fa92a9b8e9e46493256de71d1c9163922ab0b2a7d0a9f5cc9a8ae335d000918099ca029229a66de2aa651815c8428bfa1b94782f799057549f606fb8ad3d58cf1a37065da603094eea4653bc593ba402fbd7b20a3366c246981378dd291aa7b44442e78bfc2084ffdf38ee82b8d612c7f020babeac14ce5e3ba1fb707dc997652417564188382db5c25be645a6d41b16e817488c1a5120fd866885048788d075226599c81c1df626c59c5add1e5fc1962baf239237f6430c0fcbf97ca33111ab99144d798e18e664d5f8e852eb48d85396620b0f19cbfd12e0d867aec51d53cb01a81382e184fe12b8e90f7ba568dde8721f7abfc30ff322cf097b48b8eb1ebfd004fa5aa9600184ba35118a8cad1620626be68c7bb9592970a2785759d6e86b80a0d39ba9ae6823d4c810a286bc913df00452d023c6e0eb2f04453fd514b413683505c662c005bfdd9be192be185add7af4524807145492000d2ba8426b0bfd8f5de09834e0ea1f36cc59e79388f0c9b695fc273858ae101d3bfd2464bb53854f9a82418dc01f374dbdfbc5655b938c3da8dd6a0a0ef05a0bdf496a7d24a919181709c5ea58313e4f5aa1c1cdfcc38d146b4d784bc92e95c47a132b7023b613795646873fdbce42803e05d0cd01a9456a3144b18020e76e185d232c6edac45aa67b135c050526101ef051fbfca73b21c0a02cc5a9516dd0e04ea10ba00e3fa2e674c251450122b9f63a24cd1f262344f9350647719dd8fa4c24eccbaef7c4768a043cae22a5bf7a659198f14ad7cb7533818cac48bb6fab60a25723aae79572e14d3ce7bb56f96c38ccd7074a86c5743ee6ce79036e66c7dcf7e119d5cb789e85bb10dcab84b35900e4d40d66c1c34c4f2f821968b3923fd314bdeb55be17aca04245380daf4d6624866311133b40e4993951ab5630f9940e8ae0502f8d62678ed5278773c3faf6fdf7721aa02af6bd7a7f8cf2f71adf29087aa8b6b77715765826b9f7cfa73934d1c16c50e8f9101de132a073a7b3fdb97ba216314bef4a53bb8dd17bb72050d5e4a7951a176a57d6a9b4eb16bf4aa854210b7496ab3a461c29ca6bed739e81f59e071b53447c40be7ac99bb6d166de1f99e073f392a193bdaa7eba1ef3f7f5bbe1c9e92e063a8a816e8ea3f3ff2e70ae67c4dedde7c332d6b01fa00b3e8c7b168791d82b8be9a15d6118cee92786ac1743914515100db77d168a298b3034ea20e4a4d5d024a470048598a83695829585f56d995eaf536ee0dd0089fb9c28184563dc83f1436837b83d7cc3729c3e9e10f26fab415d501835712bc23eda7a28c49a63a5d07d202aa2f6fdb1da32dc38bd24110eb63c8102e11d5ed1280b7ec3f741b7da61e7eddd1b044a973750132aee7b96c2d839e0d3d41d506bc4e2cac0d001170cfab6e2aa593b166b19b9140a4f472f02a51d0b38e31165294dc7a274ea029eec584048b2332d551be042a60c59cc91149f59b46162de580a2dd2e2e7d225f60a215f7b96b89968390c1496c075d1015fd598842b8612607d07212a77294fcb0a3ff122eff95e06a5a208bfd4a6a91622d34518b43f5c869335d82009033422fa0383c9a47c21a2f00a80dd96aa3545e1928fc0e47935e2305da3e61e48ca4d281fbb197c0b91c43cc1696ad70d51197847dad972cf9238f10ae36552ea649d176f99f987554654a6b24eeb74515a2e909a2718594f4a3b47b385aadc00ea8c6ea49e2c2f67233f66e14014592f92cb2851836052b393df3df7690ff668a34144b2fe60917fd8ac04d177c3669d12cff0be796aec9e94ccac0f0e024803def499f42c8edcca491055984141164ad60c442a9e344b62ed9d559095745ba141602fad45d1b0562d185e6bb8421ea34106620010d574da18ccfae55d55f6a596afff2b474b8ad57793be1d888fe579e4eb6047f7356e68ee30c442403bba346bcb94d66620b8d24ab0382e818fc5de51b503c6f8485959aef311a511dccb861f546a9f074dc0ed2028b02a44805efd094051c4a1e01d221b7bbac524a60cac58667888073bf6c4803e037cacbd4736bb1759db6fe3ba8d48298e8adee76750e04a2a4cc2c3bb2292f3122468a9b4774e4e91d088fb524ebfd01d29e094cde2009fc7ad0ea755dc1048f53dedb49bcb3d8a128f61c8f6ee56d8ec2355bc5af5e0672a9a0f41349dc3a8abea87c0d9deb8693180736adcdd35e07e75c98941db24d6fcf50bfa9a76055313fd48a8b6f5e2ff3312b9c492c18ce7a02f6cae990e7705512441fd1ce32c511127be3b5c6041b76d3bdb196affeb68a85dd6ae813cfc7ffdd8488ca98227914a2bfd21edd4e80e091bc8c3258540f3a9055edc0321476f940732778d9108ed69971c0c79a603cc46a5530199d638aacf3edd41c8d3714e6d4489f31aaabf52fa0a0d64743e87ee2e5d8f9b1b56365dda61731c39f528f477c2d232d62c25ea7bca7eff94333e9cc22a9c127b9d5e46fda01fe421fe41d7fa742c5a847bd351e9681830a2cfc31ef7dfda2d8583bfa70189164754ed51e284893471c2c27277a98016d5a330930fa3247ceddd90474f273a09f331eb38d09bd3152b1c44f5fba780c90be1682f4315836691597fb9f43e8b3b35240b80bee139034eaf42672b3b19014954e1f04ce9552d915dff0a9492a37f6f5337d4df34fe112d808bdf25ebc349c6f39ecc1cb74af5d09c193d11b3c798f07254258fa60231e6994b609c0a8688d707ed82bbc1f2c86ccfe380785bab3864d9de6eecb703f2119491ac75ece3b445323b0eb24c81b6890d5212664a8eebf31639d2250ba96994685805762541f83e458e8f92d603c08ca8c17ed410f4807b06a68ca6243052dba3d3e6a6e4d7ac1160f9e83a6c4a9653e718fd8a0d5d0200db4f5695ed3ce9fe5d74f104b509e917a9f537191e36ed33b7b42100bf2eea794d8289967b95e7e37d79be102ad82ae718160f1afa88c76ae2b6a617f7e93bc7db88e16793d2ae0b400860a8fc6eda1952c21931799da4d7475828ac4e29daef9c261d69c4e04aecb4340959db8705445ec0069dec6130f1d18a097102dab338325abc82800d14818e9924d351f44f95773d04f1646dbffc889a88bcd28467d6eff739443b8823d9a4cfee693a96fdc15da5a0c17566ff6dd7400ef44e48088d52ffca79ce68e2e23d73458f65bf9467956c74bd504377b207b4e605bbda5c8e6750d7b5d7dff79339e0895e314a3b7bf063d8aed54dbac394b6efdaf477768559e5241082529a7a1208e2fd5ba55d85e992ce90d1c5e44a2b47a97ee4887b4ca778d408fe721d0f9354bd53cf17c969f398a76e721a2f92e0b68e4fb10891dcf060102b484532149295345eefbd4759c0db7d8cf3dc88c6fc34e8193e93abba1622f89840f2d21f72ba4ca447e0b6f73222a32e56b5193ae4f2d2771120d0228072d7218ce77ee8197fc01397ed084346eac458ede2413e411fbc95b91e78da772091de8592d8b4356d43e81d1aa8651e463c219a120e154abab04ae4568b463f104b57296a9bfb39100d49c28cf1c719806fe0d79ee3d5e32619b9fb41c510fe83ec729f218884e7bd181eea68f415fa688c4bb37316878ac5c4c0d666a5d8135a3d6c827a00e2b9433325ceb6d7c0696d8feb1c950fb150ed54a0dda88a563a84df2b5e37ea0b678cab460c84411f1538d0dd65a3ec5de4cf0c3ca8b487da1445c6e1a02b97982a719828c486b34c59ec0c16f140e44813e2c38e1da7f54dcc7c878281ab1492167021ffe40a9b5c56715f051bf525becc89bd97e543a5120f916aed56d69019d8c1d703d1a19ae38f1791c4da632f9d0632d000ebae327488bf46391d0abc3c1e98131e95823c083dae1a30860761062cd2d3cb9c60463cbcb67953f019077f52419e385f410119636220194d6d1c48cdbb04d933a1e591ee8f3df70d33a92841a57383610298606a421e121aec7e35f1222585328614fadf7674414e635b36459bac721c5660a70d9293f4fd64d9ab6265e7a4de49c205353c8f20327dfc910d531231da5a562a7e28ae1192545238bfad2e616d6892532dbc09034984b5905956b2f2794bbeedfc3678764ec104f8507feffc79df90c00d614914a394954f45575f67fe4d6a2f823bd476d5ce8d252b55175f698d725bada9424d08b47413abf077ec37c03dcb1da1303b8d97e9f1a1c227a51101f6d75921294074fd37370a0d65541fb9fa4977de1d542b98d94742d08211b97a483ff63e4976a36f51ddd197a4e865ec7c3bece84d88fb3659873e5a2cb75a18288204276614bf7b516fefc2300761dbf29007e1dd84db6a51d5d02dd5ffe26010f2ccf569eef88e331d4d6e9b1b6d3be695215ab6540b5335691721decb8bc1301c0e4eb547d008dfb916c49debf5e3d5c77a5b97cf756459c5bcde33af75a1c9f5b432ee3a79e77d36bc140971d256e6a211d5fa17644a06cbd00e7ab791aeb804920d8410b5b79b4e90cb72a6b29aca3cef5576e268f812d75bc973f604e2b6b4e5327194ce84fb7b284d560589a67898df7d06f950f1300de90fd34dc1d247abc374610ae63c971482350d986ce88f111571a4165661b91800b72be620aad9fefc82310034053811a08f0f0aed56a4d8f9206f11514d1f0ac16db842515c04fc15be54b047ff9dcad7be144dc765ff566908f0044293ae69f991733cbe38652dc66fcc9413af71f4924e36add19a907756c38b3b5b801a561953994a3befbc082d13941a26ccd8e97b8fbe9d9edc7f1e827b0ae786657d97e9fd106d8f91e6897ce945c81932437b85ad516698b6b1447bd8a5298cb87cbe8616e69017a2314f53ba2af6c6ecc00fcf9a0da605a1dd220f483d14593ff533bb2e92cee4746b4a833330c9cd3bc648982e176d41d0e965ec1318ea6809e1c64c63417f87983714d25950d26e0e56fdbb8550baade8448fa485cb2d804b12016be1d9af962fc6f715af9cd141fc0b58f3912f599ee1f8de42053688b0274cfdc207c413296a3a768088e1f4e0bf6e45a89133932e978dbf5e3a42f71781a845be8b484f40114be285a63da57b6e9815bfb9062dd707f8e1716f7b527550b807bf6285b2707e9df9de14134906eaa0e7409bf4043f20671926abfc3d6b417d748df1cc1be5c688fcc91349a224196e9421aaeea1bc67845c58843e1a92e37fc903ef76e52f1b59018f1e4b1178ca28170a622bad08664350191cf3f90500244dd754327407f8ed1b3629dc3e8c7a2ef525899a8ce551972cb290ce4b0006b9d45e44375039b0c194d1ae29381500e3e8c51d3fa971bf48daf18c8e8b9d0763b8f141961e80aa2f1ca4ea20fafba97909c669fa4fa9db27cece60092e3ac5c7349985d925baf86db82c86295973d183cea0611eb57b4f9170d6a4a8f066230dbbe15ef97d14f22308496b62b16423af6c4e3d32d97539c01735a25548d0ddb49119a6989196bc81f372e2ede51187d0f1d1c7789f744de4a87de6d0112206268057e7fb982681acdeb6511bf2da70ec3a391f1542228c3a23b9025c158b83e00f03da1bb9e3ec6c2fe0989c519a9e9e21b4f0d8e07d2367d1d5aedafb38e87be4f808da21ff13417f8be908181af67ab3b51c8c121e8174e13af04fe85b93d4651f541f6db745b3718883726a25cf9a0d9283d1e1e8d598d121859e8b509da3e64aa798ad086e29028806c2de4438d3775a060943d0af375bab9284e3c3a131d981c8cc3785faeae24622e5f87b06fc83119f3f728400fc7d790e70572dfe909d11013218ec3e5ba52884bed9803797f11496292f73d795ee0206ed23225b5af1a9e8c722cb081cbc97a68a450d455e382fd2755aff05da5bab498c90abba44b3f03fe9257a4cbbee1291429d986f105d3d8aa736bdc624444740e3c744576994fbdbc7a1bf949ba99eb113cb3d90ecada91d6b758fbf07fe79c56ced06962a3d51181d8e47ff0a66bea5d4fa7f3d4d9a7681b99b497e57af3c437def6360a44f3ab9c8b7d3ba0f21d73eb093336846324a120e47f6537f532a8e7a328f53a9a8be269318f31ceaa25304a0c4db6e0ceda2d2dbc504cd174ba6f864510e628557f13643afd593c2e382339ce3c6fa24c00221250cf1a9e441a35af53ea6de4d014e37a7cf5484ac45156453a4c5565e74369115fd505e2ff7fe00a296712612d3e9714a6a38dc4bef3c308f0a62f5095588d2957f362fe5f440d820d94040e628174b28450f781771ab234b03a282a0b93b325fb5ab4456a32e071ed83b539efc35413e5734fb527a73e4e094407fb8e1dfe2cb5d7e6e1e4335291f5fce22afd1661185211a5fb0f6497f331f7ae3b061313dc274763de4ed1e7b9feee9adc1d319fb2874ac162d99cb4ca56852017cdff765021a388689ce8ac35b771b5acedc6f12e8d377a3fb10c126e1277a503af594d6976c6d010158ab9d8023ad011742b7e035bf9c29d113912b514ac01eef3d90a27845a4094ccf63c8cce7c3abd5aea13aa45a18bdc4aeb6fb72e489b2285cb736f556e712a6e1088f977bd27c953385a4f1db7cb1a9dfe14774f3b4dde20bae242d388c90b4ea63fd515330ada29a4b0968873d14cd98d0cc88d20a67f0444dd8e9486b0a957cad909885705deb791089b40e5e8dc4f21d830e9aac746c093d2f1923db53ee725f22da3ed90e0121ccd0a42e169586e0fca06c3e4e9fe7dea2c232496c625f6011b3b6bf8e21d2bb61aaf28f7fa3afc03b7e15e5bc40f858d723d1367e65871d8b25b8b917d8009084e69032c0a618e7b5b218928b01027b4cc8786dd2f4d6a846cd8e22f585d985e0b8300710396beeaad4cf252a27576f1459bbc3e4eb7c5f48cc44c1d5c1adc721c538c18a432293a310e9eaea5a1d0736bef4bb64c6dec56fcc2a0d75ea141722fd2f8f9ef1e15b70ba4d678fdb167356613e4e264181fef776b60a605a39629ba7c8675c456fcb211203b34ea72deba70129d399bc749c0411c7722cffaf43359c113454c07c5dbfe846660e374bdf84f022919ac66f9b73c3defb6db5849235548dbd857cea9e3005a31a13ec81c7a08e14398ee23f54beff73d8a8eaa0d7a658ea1142b219033070b373bfab879f5e5ee9a9dbdee9060d68f5e04742a65365c23685db4d5ada6785ccf70c96c185eaa177e407236e27bb4d29b011a76be2f323601103524a6678d4f88df32e5855b3fbe59f0b24ec285e85576fc6538a7e9cc75534e70816766729c05198fddb6e9d6c248d4cbb5492f10f8df502029027823f808dbe6b42df07953648d789633607a5f1d9eb5e97c54abb1c8f74f1fc14279eedf9fa96a2090083751e866af3c9ee394fbc464bb5a5922e65722ab1523502c0e88077f7700861148da0410fabbaf00ada3c88eec1de7667cf5be321f77838ba587be1414e9ff540ee4b1829d38d06930369fe3b3cb206157aacdbc26e446bc21a14d49034627bb476c9b620091431ea590dda99280725277e81532655e7f1e910470eb26b33373e3ea08d0c76b4424ecb0d79d78734df5934b548574b5944310697556b7bac23071ad838482192c44f7cf829c1a449d4d4661ca3a6862350ba1078e63619c5198378cc83fc1df7168f3d4b775a923c1287dccb4b8dd6280c5294ba3e59dc04adb14f461c1e5b7de9ea9aecd4550b85b681b66c6b637d8a607142f2d69ed1c25d6ae0c5525b48a63a13ad597303b451be63ba66eedc75ccccd40765fd9fa853a81e63e83292c2de8c6d6f415d8e6bb7d6c4bc070eb9a456a2cb5a6f59c532aab890a2f9dba868a1125817231eaff7e59268376eb51a2dc920d182d176897d5847b49c217f0322e22c0aa6ce63d67110bd3944a982125e1216505da7bd61cf036a548197034a7cc58612ea387a6a9dc9fbe14908908f40c06775da13e384ac2ad72e404435b387fe52237082e8e708d87f29def0e6f44f89e9eb85f3560d5661139281699a8f970408881320c1402bff78c3569c9484e13398030a464d8d764f6f70d137c341d5fd111397a9cc0deb4fff2a253a6148fe01ee9676ff1ad3db52c073537e72b869c4d247ee53461987b433d50c176b1b26bf032b8384944290d5fd8f6c7e9c1e90b91b1c15bda0eaec5dd6aa08b9ec32852c60ad47c4a7c846d6954e88da3586bcc26f1271e90888266f54b54e3bfe31c02a88a50aecdca378cd81c02189c8d46436ebb4cc9695347d33536bacb80d6666b0cef3f3a3a16a4b631d197ba1212aa92164d61aaf3bfafa3bf7e691dc2363f121db81e95c49c8f84ba8e26f54ebb543bc7fcc78bfd3e83d550b30b7e55f90b1864aea21c4babebeb3a09013c37a02b193d15697104f8bc56fe431f655eff81170712f0f269812600215c68cc1ea446548186c3c86c43504cbec79a8a53aece6c9d683f5de3c5fd588ae52096e3d9c1efab578fa0aa902257546d73420bf6b84954d7b8d9997e9f204279dca44742bdbe9e2ec542e69ea41927bea276dc091dc6409ce3dbcc514944961ca08e5be73918f6431acbedbbe3ee1443345a5142eac0cd457fa51671150092f3f9137f83866abe5e8a28079b36e8cf042c122d723f56d7808ecf26b1e2c3ac53a12c3247b13a13d5c0790baaa883e8e696b799b23ba58ead308890676b35c466106442e3201418618589c805b7fc2fc2a6b023d8471dcd6c083c3627c90964ef5313771e414cf48cf4afe6b6508a99202bc95eae717769e52ed24452c267b6a789329e351aade4756027b14949a248aedc23aebc17afea089924e913c6830cceff3a670460ba0fd0d1cc6b96e0cb33ea64ceb5b3ade67303547b7330c0d9ceeb6b2f71e6d60cdb2081982483567da1a1a7f0a413b4c6e720035550eaf902f34594addac78826b09b7196952eb72d924782296790d6734e0e3c29b414224564ecf307fbc8466b70ff638238977e6b2b1bd0b60dd0f1949adbaedce51d778e3a92296f96cbc047007c457de78093d0c6072d4e61430cc89167fc073d2dec81923acec6e5a896ece6307b7c4a62c542b4995f3e16a81c6fcc70b58c596c3e1879fed05c1e3639ae61504c48ecaea18e2f255ff09ce0568f1ad9ed298022b27980fa9da7b5d2a84e4be56dbc6f3813bb443892bfb698da902a9d7b3f85a86b9e6f34172e6be038704ad825e8636cf341e9a5b1c34d53b7636be62e2cc3168fdcf99c6d84544fc280918134a5a6b35fc3b817da4dd28711ccba44864cc4f74445278c841be91d4f8f8a6594a5104199e97575938dfe086bfa4616b742409f06f7df7da00ca54b986fbd20bed4bc881050d7b5c62a9633a778d15ff959b2832aa5c5565a55c5d456193ff6befde5f7be58d0054210fd9579ca64ab835cb1784ef6028df3a65c236c56ab5e960e1d8b1c7f2503f5cc5ac4b25e58ad55b452917a9f62d98e94d1fd58e2eb2a52f9269c14826cfd2e983657f83c1536b25cffe5e5c753aaf6a6ceea7c94302949ed7fdaee5feacc2f5d4a5659dd673d9fb45b948e8c36a65b1aa1f2b90c94a973938bc185a87d8563f9a9b3beceef98d3b4013b31f4fd9c92c6cb61bc65617aaecd4f5ad3dbd49bf93520a77cd506e41c28226aa6dc16f4c25bacd16bc889161b9ec39446b1e88e9d65b1dec8d575c568511d748d1b2e3e977b3fc91f2fc1190f0473601628284d2814dd681197b9a5244f379ef286722203997978cf5f830391ad1acaca6b910b28a3a894f807d02b30bdbc8a2bfa649b1a0b10956c055311facdd3d5ade6339834c085543ca553d7008103f43b58cc96a4211f35f0de33033d0e73d8c490e83334d4e7c8b0a8dd8bf686f4cd26e79d191a6675ed85f72c52b4a86ebda19cd6ed01692fa9263ae453462e331961b2a862ddb0b79a3341cc727fa27d4202a0bc98cbbe698848b58a0ea195ebcbcd2b58df7e628b69615ccde5aaa14f8eb9869769614c64e55355e7a64fccd48cedfa2076831a0c0420d736ad59ed31a65e55e15255729e2eacf88ec62f0cb69476ecec16a814f705452c5ad6902f72f8e3559a838fafb4acf35d5edb502b9e5f2c5c1517b616a1eb76c0f9d6240b76bdc5ebb0ea3b40145eae000fa9d42412fd3788d9e7817185b930a6eef4895c61c37fe7cc2e3f8c71e5575cd44662e08381f314e3251e35a24a680b97af984818d3eb8871967ff84c0409c89a1c70511bc3455379204aa6f2764eef76652335ad9a6d1632d782d3b955081dc72f9e2e0a8bdc2a42c95954d9f7935d0598f104aac431b09d73b58cac47e2a36e4e207fab3773390cd83e50fd37c0bcfb587ac99b6f30375e980a840260b203b79a7afa81823cf02287609d4fcfa146e6ac642b4158149f7a4cca0557a69d3ff5f733edf3e06849a0845742e0e11432d494b6bd65f3689b2a67d531ab7c5e82fb70ad2340e30f2c49a264496d0759224323f63a1895a43fa3c44c83073d943648fd414b3f74f10e46f770adb19f5470b7da00abf5ced32b191e3decac62d7a97463ca5244b40f915955df7fb20f662431676038156331062ee94e1347c5ca0f181a65049218917bc12405e33cd2fe3ee1f468e0726b0c409af029bf214d61dc0c0cdc26955765397c77a12336224854e61959e735b0be7b501e4271f0f19b2d6b570a8cfed4093062293ec7c2c18e22c7cadfb4e2fd62f5179b32e60a376cab08319b6a348108d2a9deabbfc07b9526d75a32ad05085d9c3a9b9f47e6e0410948793a11c96bfad2820c6dbf540a60620bc0efc338dc9522be3e8574c422f7b0f4bc128fcd30dc95fda6a4a73a5c2887620e5192a54339703d79b369117d603c9e1a83d924215c7aa3eb12bed2a3bab202ec14067168507376f9fa2dc4cd435fe57559a934d0d50e8574f5b811ea881ab579de04b4d46a55539326847741eb13dc4b400b5cdc55367a0dbd46060ace696a280f638c7956480fda1a8bd91dc95607f2bc00c2247b9a5eaf478eca8d1d8bbb312f9de172b5b6f120ff914c48eb7de349a497f4461a02f9b5e5eb43457bd2240ba523ca04570121b817d007aeeba11c17e756ddf3364bff5da359806c705dbdf8cee8b7e48dd417426de632d27fd140a53aeaa62293c5dccc2d8b6f4a9ac9b0734216294d209a58a788fa18c75ebfdee7a92268f108b12f4c1b83c9d5be8fa4e346877075b41d51a7c6c5e9c548b83740995a87fca255ee89ace12a6493c9486eb7dccc9b02d67f53b846bb05afc56b1a064d86080258d6175492516995a19a634c37707721209125d6362cdd600adfea90bad40b0eb3ec7f8d9eae2488f5a86915a29d3447e7597478530a2f086ceb2d4f582cd676467804fe98e280c381dcb2932889578ce6a8990d57710ad256a6c8398ee412649c14fc7fdd1a0f3a848d482d2e3b770e00d5eed8f26a12f044b8e57578687d4c93bfb88df125fc104de081b3a5b801e26e2edfb4da4e3257fd7d10515a2ef164ff78c627ba5380f704c3bc0c093d9b32cf024487e491f4d2beb110b8aeb8ce70db4006c240088f4d1a60e414a84d851501418b21eb542bee74d24fd3a6b7524b19491ca8724ecee9ba9b96b3f26d3e4776ffb93148c45d493a789d83de6bebee24461de41e017039d1980b67eca5c8489f14a4a75d2221bdb6d479b6bc9814e5b312d2aa485700190a83e0df213fafff6e158e50cb05c570f29026ad700ab9508df1c1c10abd505952ca0e6c0c6bfe4ae3b9ad7d5110f11325650a41cc050c28e90ce815c0ef7fb4b1e327a5cc2fe24afdf46e4db41517350372b77381c69b8d00a98b4ccc451814feefa516035f0b2a91bde09db77ca8057d4266b2951775955dd0541cb34d74a4db39d35de5f9291f4655c29a7babac2dacdad8cd37033b9cae554f05da38a321b205ec0a0805b85edf40966b9acf362ebb585c58bf467e4d5025f30dc1d52b997acaf80358711f41c4ee3da5c7df582e4d87029229471c5d06ac69cb5f138558ad1a91b2c7e5d5308856642827eb4799233ac8a58048dd2ee3a254a5a13250328a35ad75c505aa6ed342d53d0ae11b2e91b32f97aab90b0318a6be6742987eae3fda663b44812b8c924db6c539a63e74c1573b22b4b52dbb1d3a6e64c2a07248e0af5c7f446470615049e9f6cfadb2989eca66c4bd6877cdc4286f4d0eee92ec82a8185af5203ee8368730dbb0b9257afeca0a34aeed888672ca74b2900ff8204b8cad939d07df4c215732b11d4fddbf0fcecd2f41bc3028674bad903e4b0e2e362dbfbcbe471771de895363937a63cb5086678e3594e7c09cc29ae4bfaf2137e086d5d4c28a20aff0e24777f5c5aa0587a5a3e82fc009a9a873a1f3a06fba77772716228f1c7c5abd2a1cf38de455ae8bc2c5b08e9fd2e469c4ed41bc27de3caa3f82ce00d8f2d76089ba66d4f00d13d0c5c57287aa6b9fa1f758f8b76caa09b662964fbd719488ef500197caff04c751384a5a83e4efc7693bc727862c04231f9d1ae15b38e70cc710ce6d4521bf6e4e7b89c0b369aeb0c8c6afe240b22d814b5738936b6495f33b5f5cd4327e264a1acc9f7d2080038694af34dfeb4a69146e8b872917c6b5ad755e4338b3a191fd6ab53f465e571d18f5e8cc22f4633afb92d01a8408247e5449f3a41eb66c959bbb9ef416a0a790503f534801a95e41dcf8284b2ed1d91f2a4bf174d264a33ac7d82b188006c8510b0e394fe60b84f352c8f67029bdd15829bb65413f0a650ce13170c7a52d61ce26cb36679ddcecbfd34ca9a934bc5a801ffaa8b62f78a2add22ce17015ad01b06cfbd1c5c1c00ee4b7129ed0a142394784535ebb24f2028d9632d3743c63429e5aa5ec2f0bf5939632a569cc31faec9c0f962678c64615f7eede107911a7feec98884f94160e2527541c728e620f708b647d54d96e3c4e55e85807783df46bf7b5a9640f71c8ca1b7f35d369817625ec824efb6f64aaeb98a11f3d5197ceaf3f2129720536761d3ef4e0aa6e9cd6201b5393e55ea6570849cef792461ea65303edd41e202d279555107e2a8a81e135be8acef324c421d19335d1f63f7b612623b46a182a1d38e9f8e0475800e010f586ced1dc8dbb49751023ba70c27e4a7ac8c6cfc045b65fe54d7842b9eec6768d024670413c49eda57d780e3a745bf8fed7490fe974737a943cdbb9a1c7e309fc31148f1bd0aab135e5adf6e856af3383e1d7113491751638caf37f2ea9de3917e680c77fd255c9d938a0080bc9560bd889ffdecaaaffe432e4bc8adf53b0f99015756ce4c5e435094c5742e751526a59c212e9042967502a53cac0e541ac50fef424e7b19bdfde79c4d13d53e467892c91edf86c69446a5940ce242a0bdb726ca17945544e3979aa8591a090c27534cffe9a5aae4bf50b39ce66468bdfc7601e2ef9d47ea32f63e3a91c22fa4960332565401c977c98674d675c281ad91842aade990dd7325360aba6a77b60ccdf76beea285c15a22233906ce039b524d730af213b3c5139355e8572ec045d2392c05f0a1c82440b3df97b515d912de448646f8bc63a710dd7c05481eae14e120a25ce9490f7000d134fa9cf3e5904c7203137e5c6b7999d073950166a931c06454014f521c3c5280395cdf99786d0877f51f800b92774853c0f30059c8423771ea84e06bfba1610257ca8c989aa7ecd5e73def82b538d2986bacb6b0f458c067934cd29ad551ea422742c3e12924a7fbcf1165beaaa039cb5fbf55eb05c819ce95f3f4ec4b11d2c3655b856b53e106607f9e9c8cdefdcc88cece54b01c5f81b46146cd146b7826c7d9ad93fb9c186cb46bb7e258e5e83a905348171b0b0824e7aa121c53384844e542dbca334b7310436c0fe4d1365a2301e78f79c900881e124fe20130319dbce3d4c68b73290677495eedba5caa53f8ce246a400412f5f1f4be8b6f13f6e575c1571d5ff6c274a8064591eec224ca81146ad772d3603b10db186d22c6839597d89e6436fb28c4d8677010e7458a65651f928567d4dad0d28e1c4728f056dcdf30cea688d929f439986247e173c685394b07bf4b8d26e916874af3e0a6e650a39e2a3fe4a9816016d5aef84e3cd99ce6c1e93d7d56238f53ad78dc0f4c386a5c21d45b243902607a9cf730e4183518bd2d968ac6ca82b2fb67dfb2f46bde8478e5c10a5cd7167221e392f8478ffac9787408f35c64a3009e904186b1179b94d571a5e01d790dc684eec09828914f9c05facc660b003d1b98b9301d722f6198d040377f0b3e8000081676ccc0add35383e5b2adf3d3f8a461109e678e97697666712eac072090eb94511f3f97e905186cd32eaae0e6f196db5c1123437884480b2ccdbb95694bdc0feaf1f2391f020c9457b02d1939c83b386759c47316d2cdbe8c765f2e25df05995f8a3eae348d3c5609642417015d966aa991974ea66ff3176d08d00849d88808a21028a8b6ea396daa24d99330cd1d3f390db7ca46c0b810be0420ac4957a0e14a453225320501ca580345cb71b3b567b3e487cd764cf441fec6246e89b129004182b374ea4df53bdd15f4227dc88ae00c8216c184d75ea8aa4a5d604b0c4403ef80f42c7152bff9ba9a22d30e34487bf0033c21a29d6407154ad64e87e13cd4e2c0c6d0ee0d59421f678252840cc19723ab54803f1adb66eb9aa56465c079887d09101e62b23c4e513242dba9994dbeb241ca16fa8888617b6cac2258d507a4bbd8e428dbb8ec71d341c186257cc8c0a657392a239c9e2c15771d14e83fb8818757704d07e07887e106b37587cdf9dc269a007a31d5fab398ccd4a09549ab9718156a892aa82a26327347da40a065ea5838f4b208fcaa462f29f718d981253f769071ded21064702f49697ecee70965466d56f6c4e961b2317f379f657ef561a6d86b05535726e79cb89a6582ba228f29402913164dea1580003ea5c17e0bcf44a26d21dc189f1b5996351ebe9a9ba9eb08675d167208e1f7b8f3c655b05a67468ea6c7d8612ba0b7a7cdbcecf735568394b404c0266f480d1800e7935342272d346405ed2077d4b16529302cf50be2c4150a112bf770068914ed6ee2cb73b4ce68c80e087a31dcf01f53857ee3f04bd3d6385bb3fe4eb47aa682a9633f299ab45504fa30bba25a20181ea50365fee81889443650d29450a0329046b6fd2852c97fd42a8d6288a74733fb5daa8a5734901a1e8b0e40602a430d721efe6d486786ce9b85da93d80e8c0c12dcc87f72017ed466d1482344eebdb72444ee2da54c3205c10a8a091d0a1308cba1b64dc68eb0d714614bf5ab5e0e1d6dc5835a870e09fab0e97dfbee9de3d9e45bc132489ce3d1114e7ff1e30f3cbf6f228fe7348ed55471ba8b0716decca75e101c250c8f1d613e9bf0c40b7a03968518443cc142842221b0603efca00584161fb21bb020818926c876c08204ce5dd4576ee07e61b1015bdcaf971280d00de1d323134b99ea51a7582855d0475d3ae9bb4164e7c60e2ca3b1ffe43ff91f6a471dbd02d4e69c6d4347af002f4ee3a04038effb86fee6e104b1aa94ac815299e4df8c73f6c0b1c8e284216459c214ed20adcaa6ac3d928512848a7670430dbe60507c5005113ae860adb5292f70b6b8b1e8a2c5931f9e40825112580f561c618c0c0c1d72d87a10460cb8f8c1890d37f01026c9078717d20b0b24271886611804a6c082071f2051f485123e8412b0c801ab825a11461068b0c10b0f7c40a58b2864c000dbc1042fa31f54f811f358688ac30eb04ad3a04783fb25c50e1842083d5ea14165ce89655224ad70bfa4d062d24aa552c954a158cf81af570babfc8395d3b66d474cc22affe2b61991b84b8da37fdb5880f13b8f368ebe24b0fd7a15d5c0fd7a49c14f8750bd94e2c704710047588afb2545ab251936e84816fc19c5129cec203d4631466683fb15450fa2c892443d5284a31d18010544c85206abb528acb8200b31b4a0610c1aa81811a910e560ce395357c06c81c208cbb75108a9748840c83be79499084dc909657b944c53652050d1875e06fbe1959e11986300810ae718700f7c7989a832c5aec1fbf4522b36abf2e71bd65885e3c5994e5e3a70b53419a3d795a42cda0ca0d71357b61cbd9ef882014df11b29425ec54aabd5e1e4e81568e9538161bb10c202a0e8af4087e025136ec0b1044b107e94525a81a7115172f4e6681cf355c0b3051c6dfa663ee7cd1c3f5234d5e444707cf8cd99b95e20220752be1062092928c0747882f900290618866158bca1e70d3ef002888d0745d002872776488253ce2909a0c3133e5a580cf7cb892710c087971361d897135f946007a9c54910aa1356387105899a52428c51ba7faa7eea2e31eca252cf11720501fdc4169824a4a32d46af22a221215710d08fb7c024211d6d317a15110d09b982807e602b499118601812a407cf0ecef72a4be30d6d9f2224c51525080ad0931f9fd90a03e64b9217a42e475cb66831caf2ba526485a8cad0142129ae284150809ec81fe9235b6130982f495e90ba1ce1c8c5bf2d5a8cb2bcae442b18be4cac2273641cf20f36a055a9e7c0c7b056a95494cec7eed481339940972b68d950f6370ea861d80fb3a8ab2ab61fd288f147e3801826bf3bdfc02adbdd0dbd3b5651a9e774e350bbe9dd6982edf78657409fc6d1bf42c2c6306cfc9294f26569963efb5aea52e931997d2c956ec81c52874ddff44b69a5c280b319638c47266e2fc678638c5147e3d0c2c647c2aeaad8f89d819f4ffdc78170673103b2f1e96331fbf9547ed693bd7c2a367751cc30ece6ecbd2ccbaebb32170aea9bbe34c677ea39100ad1385429f78edd1da571c0d9ded0c334170cdfc6dbf48dce417333f5af1b626424f12fb61552cae8d0217c6c4305dc10e78831c6d88ee35f3f0d42ad34424f7637f41a6608a3c396d808c4f16626d82f74e853d8c611e3f412c4be144208a97cf83146195d89fa594a8fa671cc19259533f60d7a851b8d634e2d621fb10845504d1822125eb1ddf276479af50eeef9a7083d6b658c56222cd5186354417728638cd325ab0eee170f5e30f5dec6bdf8589884e49f8dbb5f292a65b15008ba7ed502d4bef49ce784fb2ecbb86f4c9534617141b8cb7de699ae474d5ce9b94c268bf52007f227d8ff86e14ffcc77390fc5d2e21fef96b5e10ff6acd1238ceb3d38b306dfd80f1561218a4a4a38c3e7dfaddf46b2398443d4aa9c948c42068235cbab9c91026d246f46256544158f82ecd4d3ba8f33bde28638c32668865308319cc604e49cc331235ffba6191c7de4e187032e45f3b0cfe3915eb5ce94df533cee6f937717f1edba1e4246e9e38afbc99480a639e39a9fd6da3b54b395b7677cbee38e38c33ce39e7752fa2e085ad31d41b05a41217f221d991e5ae78033f85969d0f1f7ef11c5984096a7002777f1912496929ff2004821204e4aef81e04545d5268201272f9073f432ad65d10097ae9241c5da631e2cca49765524a8a69190644624d85b9749ffdc5a750662944c77cc8d938e263790458becbc611fd739904991cdc638cd15d42f7ee6e82c148c559d3e590869e02f78b065ce0eaf0ab77a418ca17f2414b4a8fbdbc13a09fbd054a8ffd122a42b02ffd92ece92fb9b01597d09ba16083758c29814ce800c40f2bfb2aa9bfcf9f71f698b3747bbc9218af0a089b2110108540f1e6aa58603304a28fbdd35f82fd5c42450866fa55fc25f4aee21582bde9b1a75fba4bec53ec210a4eb858a5a75fa262601727de380b99a784092c3a4a428845456585092c42c207894505aa9480503c080bbd4277bd826b627705e75dc1f916c03e7b0bcca793e54a661006146fbc95c4bfde02817a2b02b56861f8ace935cb814c4bb877b7673de7a440d8ecda60118a63d21b8391a3fe61de0e367f37b20da689db8e4c857d8ac2ed471388336c8ada36d555f686e794aae9bbe3f4ad058861961dd91eb44ac76d9af7e8b0e96df60d91fdd1f7cc945d29318d086dc77322c690a27cdf7c4079537e63a39277020725580d840314d6b6040e9660d5504103ab9e208955e32809563f4ed4a971031a585dc50962c0eae7893a359a3062d598020c568d2935b0fa87449d1a47f8b0da280545ac0a861358fd45a2ce0c0e5ab07a0c560f01074fb03a0b568b210410c2b0fae11735b01a0e5181c2ea87425147c715376cc16aec0a56aad503ab1f16451d069c008a17ac6e2e4760351080c06ae824071eb4b01640469820b0ba817004563f4c8a3adb1090b8c2ea8760a2ce0c2bac2d095b7c60f5fb4fd4b1f185d50cd0a18b1eb0bab9d081d5efaea8d34f6869620b963ff1a18b18b01a5e8185cb15ac7e278a3afdc4d018276075562507ac7e7f459d28041a7c7e585dc760758d82d5375a2005ab511083d5ef495127e78814b0fa1d4cd4a1b185d5363becf06383430c58fd3128ea94acb04e5a94f8c26acd05464060592b395ec1dd6ad5d062e598c5b672d412434551abd58223b072ec82f918b5688062c4cad1cb5cc2a8d5e202042bc72f9468a8d54a4207568e61729c50d46ab5845859fa401a865a3e43706165f964fad06ab55e50042b4b2825318c5a2d22a22cc1a8d5ba8207569652a018b2610b2c7260653905cb51ab25e50656965588306ab55e70042b4b2b520747add6105958595e916206ac2cb35001d46a4d798295a5162cad566b4a1656965c8060d46a4d9982956517295cadd6942fac2cbd4cd751ab65840f2bcb2f9507ad56ab0824585986d14ad06ab582d0032b4f9f1db45aad2d64b0f27c5284abd532e28795271428865a2d2a62b0f28c82671449ad16951e58794ac15d049ce794c8c59b121105064ab8683084137e68a1e086ee07a024474a10a3862dc2d0a2450e48c440e1435f09e20b134e98f8011440604f4a0003ac8a124cb181e27e31618299c54998cc09866118564312a4644e5c590d4a603e3870e2653a6902f37182c5886c87ac86221f319ca8c009941a56e083854fd0124e843003ccc70a4c8ae924cb747202d30ab61ab0d87c98e0848b520d5e309f273024d42838273d30dd60d2a20626947cbe2825ed207d8c40392182fa184d2758780d48d2490d309f32680d5a322735d48004adc2e704196a092c2b4a90c29ad86ea0e2414703550fba329ea072123334c1dd2ae6e177f7f5bbdb737aedebed89790ecb486c68da69d358d0fe64e266688257a8efb1df3dc779317772777eccf7982e1288ebd7fa266f65bfbe49061f9a0fee63bed63aa36e35dee4c1168ed15a78f3f29199af41e36df733347e06024d5adb68fccccc8c47bf763450dbf7a3be7e4dea6b6f3dba4a205dcc57cd43691705adcd3694d679784b79a798467d95b79bdc6bfdf579220daf717b9ef5aa97a1501268bcd2ea6bf5b91b33540e50a8dafde9f439a6c66bd7decb4570e5acbdb948cd6ff6e65ae365319f9d22865dd73dcab35ce5b6cf9eabd63eea3b7b5731319d8dd96e8fc6797b24367077572894e5663c279379ab1e8d536f3fe6372f23b181eb5d71cfd53ae3ae667cf6b5a3a152a95434ee6a46ad3277d575d90ad5dce937191999e7644ed9766580486c60ae7bae7b8ecb2bd423b1814fbfea90d8c0a9bbdab2df32eeb355f7dc67dff6ae50b747e355ea7b621ef5a998477d4ccca3503116d5bfdd1e8d4fd77e67bf7b6bbfb3dde59ef372132b57546ee308198d1da9ccb22c9b32fbeac9995189bdc96b20d208c43e661018f612a9c2cd07166594df110806047b1963bcd9b1863bf8c1f027180c1f8b3ee6bb40b38b4120d35b09e978e3f21e8138462410c326d4593ff328eef0b67137f372924c71a642666e8d6c7bcdeb18176e5ea279a8d7bc7e2de6ceb83083aa713bde7030cb31cca83f83665e04647ec6631e90eeb7afd0ab368a4a6fa3c8bfd69b9b208998fee6ad7cdc9e52c43d343e2b7de5beeb7adbb29ff1ead665decc0cdd4cf79907b9ef381fdd73d9e28c898ffa5a7df8dbc60477db37ae5f2fc775377fb769db6b703b3dfc158faec77ee32311c2c6a1799ce7318ec5dc9c3df7ad3dfc2c4391163e126b66840946b1e038f38c408ccab8ef47fd8c77243e5cf9b83ddc779fdd1eedb7245d9793601822f5aebb5948d73d4fecbc0c5bd87ef5320cb82d77336cd9af50832dcbd16c07ea65f5f2aba2f5ecb79b5da8f1db6f31a713fccdd3eefc2ce2d376a7e6f59c2e1288b35fa14ede0af5d99fbe5e197c74cf3deafad85efb2cbb4822ceec0fce6e0ffc7ed8c21a677b647eab3f63860b333366cc98e1d1cf4af565bed2e7b22cbb19e2da5e861477ab9ff92cfb1a5e6f40b6ea75f576bf09e12ae4bcecfb67b84a5da8b1c9788d6be94b5fc38b1b10fadc975ca871b99bc84526b2373d8f73dcc3af35e67b8e445ce37af5b2ee6b78b546eddf8a6054cc968f64db8eed396f957d97f1787685d4d8be765ec6715cee3ea37efbeaad3ad40af5fd32df7d7dce5b718ffa98cfbcd5e9ed675f65bcd4b6552ff5325e4dd5cc6ddb738ff2561beafb538f7a6b7ffb1ae3699ff2627ee5cda43c2d06e5c9004f57c8ea57ddd7f899cfdd1552e36736eb75bff2666a78339f79dd5b2feb4d488deee6263337dbb4ed884724e2ec22716c4309ac09eb0fdf48f64fb1d62a4ae501bff4b5e4d54e48295b21f6d27853aa9f6d6ff2b8bb7d7ccdeb29bde94f57156f4cda735ef6dc9b5cb0b7238d3726edf6946af7b5a4752e9cdebee6753727c10f79e2f6d27ee769dfdd476d092e7d73df516e2e9c6ec79b8ebb99892dddedf4ad952a8f92e7037ec97a27ea82fd936f3c4a3f6b12acbdbda5ef2fd19c04e7fe23910eb14bb0212a0ff826887de6955694529e689ade95b4a7268ee39ef332cd4df0f63909debc9ef9be65bf651a7dd373f44d4f4ddac5b6db33bf6eb908cef47b66dbb671a026d99163c0d8e72218bb997e73df83bde9b96bfa8e01675fbf1d7bb941a0095e75a6ef4ccf5d53fbb7dd9b99e04bc4e9a58e3d5d7afb21967510f7b6a374794cf8990ff8f3963c8857a65ff9b8d8c49e2d51b7a75f79401ff4e13b2e79f9c31a863f4d25ada4693717f91efaf135edb5dbfed5eea10fff74fa7af2b20a9fe8d7a89dbc06d2bdfd9277bab9082e55d5e312f7f2e310acbdfdb8eda8df7d8c77ba9989bd72f3e19876d7f431a51ae371f5e2ca83be0ff854f3aac903a27dcd9007fd69fa7ab3e91e91a5928f1e4cf001302feeeedeb50df6ad7910620d279401e2f852cacffec88633d99836bf62a68961d83542b33b03f63360d894917a0c43a876dd104a2803c4fef1b13fb2612cd29f5e9c7f8d6417890772130c679833cc4772c2f3ce00ffc826af1107c3c33ef6d682ec913e9f05bf0d53c8472046e23fbd8629e0e8829a24a043f718a59c13c3324a4bfe512ad15dde4ca394d11d4aad524a391ffacc4d5a18c2f70f2787841f3ddec0813b50d4b699c1ca145c7ab9054a165cfad209c3557c8927865b32188768290e15c5a1a11987e250f63568b08da314fffca97fdefed92d72cb9d9fe116eca85780d86112eec691a11658bef4f6a48704627a73fba77dd98d4798946a8306b604553318d2b6adca703fd5fc53f506e5b7bb175092a8e76834bd83fb5337b04fffc3c154985723088bfdbc99f6883a4d55f87495746f6f4b2e36f76b02bd820c1070fc20bd02f522bea3e0c5f2f817ff42b1f187449d8e415e9166c8e67e0d7905c1b4927743a757a08ec10d48f6f2d634c1e62638cb6872608564d96d9b1e4651d5793bb17ae9e75f5954c1a67f6c2ad53719cd943e9f4aa58770276e3b7947339db49d9d5ec171e95f88c173e2973e01ad43e34de96d13a552a9542addf0aff4d0abf793a7496347ed665aa2115e161e9bea4308e3f6a3093679d2bf4ea202de6067bc7400d4ada3d34d5c83de0f65613f3d21de6261df5e66821b89c4f3573ea00b4f29c4b2b039e52502c3bc8e37f1a64b8c3addf166d88addb171b401ac804f98e8934e13db13bf33ee221d8630c49006dcb73d24b2850ef767f92b1f262caf912b300ece5d06ceb1210bacabc0426323b00cdc6d44766f2c50ed8533cd6b074838ab6854dac9f60a2aff3aa8de48c10e58c01becc45db443165f149c35ec7d053eb1f0e59d510bc0c58d1e0a40040bbfb5da36392bc231c26d488e4038027928f0f78490d2c60f16c3cfaa9fa803595a8df30a8e39d8c679fecca9ddf9439b5a193f3e1d22a9a492528993c373b2a7b452fa254c353b7fe68fcd8ab227f03d258000d0c5ca79058531ec7c41f8f13dc720a04f02277e4ae5396129009ed8f70fca1f2fc31ffb36b039fec420cff1279e13dfdf15853c67e58de1ff60f8ffe339fefd8a51081a57d81efdaf69349988afa5accd9b70b08fbb0847881a7c7181e784dde9d18f43a343759fc63fcf47bc4a1370de91b2fd5f08ebb9ecbcaf9ac2e66f35bf1ff03b6e780eec07ecdf63becb2fdaf9f58d63c9e2c252088c8b0984e5f4a1819d3f77b600f0c4629fa7f7640a24dc2f2c493970bfb02881bd28ea74f424cff9e239d8536aad4af59f7dc99330c6f82c60eed1344deef14449e341b192c6e873ba43b1be333d27fbb983f30c4df24ee6e520b2133774d8c05624e227bd817d9c77200b6cb6d8230f2a9511a4607f9a96299bf96d039bba0afe190faa3f4d0a435e09c8a6704bf1830d70c0f862849f1d0ca8c7173b3a3a0b3684fd69686cbadbc6a87245a11f8233b0425f7441cc2dc1660c0828eac496c4801a03c2802612306582c568c0fed15f13a971fc7847d88c8393031ba24540571c1afa420bf64f55e9621176e872211f7215fcbd1b6c76210b7d0862e853a80c611f5a41c83f77a1298dc387a20f6c8e4371c8e3908d2245684a169b5bb168b6aa0f95f9363c0bfbb611b28408c75fc5c07a44fc359fc8c23b44deffc1c691cd2aa3e789371907e70b2bd83f878a46472aeae0dcffc1661cac81f7c17616341ae8151a0f11e239f05a213635a968a9c0b8b0bf121ff9363c8b3e0621ccd8c4300cc3ee4ce22a804912755a9c183653087443f5b1e16790abe02fbde97215fceb0d6c76232ec95aac1575e43bd21637b253c8735e5f68f11c0f8a32c5b93061734cc2fed5b760376a1cfe52e6f862e1169cdd08c98de4c55aa61cd86c53d95114b66dca46f3a1225f95020c6cee97e5c2661ba5b0d94ee1fd1042082184dd72f6ecd9b33b1e51c28521e7c3a3961747d83f55e408f491574629a38c52ce38b18a6118065d49112fc93dd247b08beb7ceba936212da24256db7047b0340cce30c8837e9e508d0642894285f5f716ce306805bfbf0a2f84b03fcf8e0bfbebf0e207b3dc85c0b9afc8ff6c3a1fd82d7ed34714c926a97a00e65b7ed33c55fcf80d069c80f19c24f8e0238e6790b874c2400d40b5f2c40a352712c28d31d0b2e2397d8597271870f2c573561870f225eaa0be7bd95ff2eb8eac8b78a20e8e6f363552d8fc7f6505406c38be6f3e6a188c79c4255fb00e143086cd4712c2fd42e27271bf9088f0c72094e765e9674b64e1e72560b07fe45183b1db8d63debc2409e725449888bfe21005fc3a0d540034c2a25aa9c23305431d104ac110429d4fde07f0d44461db891b965094420bb500c718a54c410a9e7640eba85a075509f46bb0bc9988bf70bcb4461296c61b252c8da276d47126e8c0e5d55852f033c452d2647c91c31616930d08b6a0f809623181b003b56d4cc0840c8e581546814511ab8d78a084114b053c60e16209c065049722569d5ddc40882156c54cb04412ab662ae04110ab9b8002c688d542b0c16788d55f04e1082256ad5af4025084a41b92584a04d022a208442c260bf029c20fabb1f0c20623d6f7050a767f1bcfa1c1fed45ff3af2d27e5552202564361042d7e586de3d1871a1920060f492c252a5811c6118b09aab5c5ae8448251e049630462c29fd8bb025bc42861118575094167c253d7a83a10130a4b67130411404ce1667095f46081d32a1b60d2d515a8247248636efe0ece8f01f9e03b1a79023855ec133d3f7a75cb31ff4e4767568b7fdc6e10374f88d27a015d0381e4feb04ec65af5083fbb71da58f1efd92e7a3e1675007bc3ba723acebf09b1d1a50b025d4b6f96edadd6472ffee99be898ffb73e933897dfaac61449f4253e9a6fc939a855f189ba9dfc08fa988fdaa1a47b4c266faddf0bfb468e2a07ae907ce340e7fb8b1007deb4ef9d7051715f42256b547d7a9ef697ed3ad00c8650c9c1d6741f8b0858ba3219c298e9716a17295ab5ce54fe3153aec57173dc0ac975b64caed11f6039d8a4aa148e44352e89314abe1353affef31f9ec43ef51a9f9f69c9a87b0a606974a5a95534c2e29bd42728a7f9e9442fec1cb43c2d2974a39353fdf8f681d2ba7749caa79a5506e304afc9b9fc4bf195f06c1d02b34d6817d53f3b34b566a3ea7bce6b367993e64e3fd08147eba3fbd816d024f14bae0599ace05db76d8a8f16abeb71d36fed53cf4e4973c16e04b1ef03188829721aeb936fed53c8fd7ac6116b96e1c352fb51a221f721b5658d3ffc0ade636163ff38691c34ed6c6dbf879e239415127c99c70e298b88753382821b5ee39350fe1eae1b683e6874005603fdf850c748ee9e767ea7db618d5f0516ff2627ff0318f0686ff1d84355e8e2f74d212c6e3317daac18d855a9a621a1212f2a12a442f37f22d6ec5b3b8162f9a9e93b10fe2dfec998209492121cc83b8cbc67536de8643b75274c58750f81a358ff6d8781920aef9209e93e10fcfa1475a2ad745f1cf6a5306c9201b33b0335ef5331f5f0a094921cfc15ec7fbf9b268d69f2fab78ce7605cfcf320b9e443d1febac149242728a1c62c1b124a2c9a7f1d987e8cb8b04d2fbc33f1b354f1040838d5d3cc7c6cf8f4851e73f3718e9aa79d37ccf931b0268b05906f504f16ffe11887ff4c8284151c75d320837f42af3a14729b1e9e62518fb98e4dffcf7e217bf99d9e7f6dcf322cfadf837df5d13859b29ae71570d2f435ce3b18d4786b11bc348b1f0572ff333de87b46cf19c159793ed52cfe334980fcda743f2ae543ff3b9c1dc0008d702f73e047f760b1c1288e5cdda97bed478c653d1f088f89fe1c9789e0c30c6cb5ae643783eca93211b1267d723eafc9ddca659f8df377de502c8416d1b79b2fe4de95311b083cd90c8debe90c8b30f3de939ab7879c4b76f79649915a845eb582f30169180319965df798d136fb0a7168b5f33af6b11cdf4321eec9bf8292fc683561a75ca2ea47a94274f39822296619909c7bfcce413630cb55608f405679969ca2eaf2c528be422bb74af2b451c07adbd48208fcc73b02dba544aab86f2a015ffb0c77995ca62367c605ffbcc7f8eecc582fc27eac43cf60ec573a2788efcec841d798e7dd4634018f3208c611fe36547d4b60c83b3f7417a82b39f5070f676fbd104a3ecad5de7446cf0ca08c4a74722717c231d8ea887017591407cba19da7bc3bf18fcc39e03fe61db860027367b286ce5348c728d655f3b9e7c3c27d3bab6297af705ffb0a71a3d71dfafa32f3843220989b85849ca6d74c57330ac888655cc84758fa5fc9b10a848d4517552c47060c0bef4190da1b044cbaaba5ff58924f19cf8f57dd4afb5d65ac3e0fa4d1fd6d7d115dc5e70bfb2d8822bdce6170c3f460d7a595ec1b008e248875863187d2638bb32a9be464fa9571657f0fc1dcf51f1788e7ffd1e5555359aaa5458efe15fedbe81945ef93d6afdb6a9b707b6b0252e300327607051c51554848912813862055bbad8628b11c48041104198c11170f02148881daaf051c1ccc28814203184215cc9d283186300318f3065052a3f4f4822cc183208c2420339ace04b0f2508eaa10a96021370f92268e04cb1e822bfd287d269d432460361c4a8ff0c4db089b5d05af0bcab161cf3386e8241cf72e79c73ce3c21c5f36146fbaee4cb246cc00afb3766d89c73ce09348e7e991c6c8c3d5b7ad1b7146d79d11d5e5b68c1bd4516dc2d01937c93d79b04a8cc62fc58a2b494655966929b04fc8bab52896634e062bcaa69b6b5afdad7faa79b93606fab45f0f6f6330ff59d1773aa7faa9f699bb6755e6e125304f7ccd3e87e86c6cf68cf3df184c6411a35be964a5f4fbf51ede6ed2bd7695fbdda6d0fe16b5f3da871a7aafd8909e65ef37c701f03bfbb99d6705caddc37d6b6ef3cce9ba1098ef99819dcc77c6f5a0ff68dbf4298bfc678272fd73fc9a03a542a95abed6eae1f83656ca5b1b57e198665d8845de68427e88261199c451815a51a9e0076d12ab53f60aa2382e1cdf32b0438ede38cf7d4335ed75d8d4b693210aa64361a3366d7fd065f06de4c24055393d31ece7ad2b4874422707af8dc77deeaf4f5bb6fd7b4d5e9725fbf3e470477b0a630f7f0747bb6ef52b8bb4452d86e3eb6db2a1a57ce9832a914d4b40e7615c22e057f42a85d6ec26e42082184282f13c1288a632c8e89f91a73baa70ff51c17f3a76fcad9989f93a6f0bc99480ae56522a98efb69396ea6344efbe9715caac3f3bbc97199480a6b29f88021449a0834006207a926020d80a08208462919341428385ec16928500e10a1409161a0a86cae58e9bfa3809e9ed669321675b279a1fbc8c5a44cf6a22e774f73ce79bbdae1a8a2a15aea53aa97b1aa4f69dfbda6c99c7ed6ee352436b47a35cd7efdd879b9ebba17509db742fde93b4a354d5ba16efdd39fbe3ef7272fbff0029e9afdaebb446cb8db23a379446cba4bc4065b1999542ad5a53aad6adfa53aadaafe55afa2e9ba17e87ff5476783e21ef539aa5da5ba99ce90b9a993bdf99ffb19af370834e11ede6c83b99b89d8745df7bd751de7e5176c1e431a0c6930f7f6db7354d86bff50a9d4ccc39f79eba1663e26e6a2ec678feaba1770ccc7c8e7bc8c7acdcb2f74977be8698ff24e27cfb166d3a3bd041a732fd8f080386b3df51df7d6a3b14d2662038548f53b48e0e265b383042ea4a0368bcddff78551a1e70fdba1cc44d052e7b7077fe51f83707c611eaf3202e1f6a3f8ddb862db8e9ed3ddfdca7ee9d0fd219c4d6210c4ad1939b09926486e2d10a778800a683ca1d7dc10ac651b9a54d6f0b77f98b3f67f088637737f02623f88d6533ffec7496562a6bdfd55894ff1e5576dc710863fc385e1cb7cad70d6c80186cd45f0f63602333ef5aab740e055f5d4b75f3dece373430061ee51588668e1f9337cc4fcecde7e05f290831ce42007b99f0fb98ff172fd917aed2377737cb1f36bb511e87ebef6a7b7945abbeaae8fd3d56e1e82e773f5b31e10a7b2fd989be38be5b8db937aedf6a09e7b1eafdef4b2bd443e9c7a5444cd78f8dccd7d258d7b2462998733785cc6cb4cf0b4d23f0e73af453c2390ebdbe7aef4efe625986adbb6c3f44022de2260aff48ffb7ab59be11f893be66b0fad7fde839dd087faa64fc2404c587beb0dc1311fb71da6b6c11c4d4e61eedb5f7a2af5b6dbe9db3d4f4c7d90edff69a0642490d46dffe03545c0f4d56463eccb08944c9ec5e202171ce1fa36428ff3f210cc7d47934c8cea3feaccd030c9a86e7eccd1b83350375b999b8ab9f99f279e3c2410c73ce765d467eff6ebd7efcd879579ce47cabe7c6e0886da43eff4315ef78e4f5e465d5a737dedf49c37c487c675cf133b2f33c1f56a57fe87b7979b29fee4264343b0840800220b128edf48208e3e64acb0f99f86d34082111536f70bfe8e0e1e8feeaf84a3ce6df1bbd83d957aebedae92be4d58a62bb59b8fc4a01804c4bb441e2250e4e19b51a21882168c833b8a214809426afb90d5316f9f55a800ee4d5b3ee2700b6c6bc184bbede7c642f7f4e186e439d86f2fb71d5b103679361fe79c318495dc4ff9dbb7a4bf3ddc1e6e011500f1b67ddc766c4d7694baae7b16fcbbcdabcf794db0c435cb3cacfa54a27e663fc76664b1b4c78f209e331fbe0e16f59aeb344b7a2688e49f8ccf04671e12962859706309a20b5e006e2c41bc009f1e7af9886f9edc76188165e0550bf1edb310dfcaac04fb5b8ffbdebc0976ef8863eec22d600a5a15ac3d4fdcfc88ac127fde8629cc9743fe491f700b9882fcb943b5b63d932acdbe09ae52cb3e635a9db89a30186b0eff6edc10e0868773d338fc336f003005ff8a6d3c20748f374e4f320cfb52c55a0bf1b3cf10630fe3bde11f8df76b1c3c7c45b388aa695aadb53ed5a6cc5efb1a3dcd93d9c758327daeaf51fab546198e64b8745be832307d235d06ceb2c46e8271082c3070cdd77cb09f0f1f43081bba3c1b9e05855e76c8c6095df1ca30f11109b1d80c5d2f03c42e1d58f80cf879d67cfff7b781d3e61bba54345909be242fd8ff864dd9e4584db12f9b2d24427e5e6343a737a85654060e4e5216ec9f2388902e3aea9bde2524e47209b9a440178ecbe58a49415651d8243c858868ad120c12f2cf8d8ca04ba607d005849424c465250616167e4c7a127bbc7afc78c0d10fd5878af419b5ee1e416c606310608f30ba60ff1f3f640f56f3cfa6781e8025684a1ff578ede8c83255a881cd36017de34f593b8dc38da4c80115acc900230e6ca62d486db05118f44d5f4d1e863ffefdfc98624d0aac114fbc811f05fb13ec57608741a28e6d1fb48ee91d628161d185ad7ec1332c77c9a8a5fa968c116cc1a4cc08368ca01bd9435711ccb3e159548a24b1f1163902049b3ba9086c9e8f3aa55799262cd18bdd8e37f9db51a4099a8b0a55183e54a3a13bfe2561338d5b6cf7079f8c2a5d38fb910c024202fdd01f22ea47447e9421911ff99117ecef5ddc0b9574c88f2891fcf123223ff2a3272da5ab8bfa72c7e3c934efb8c7d71be1fc38f39ab08ab2ec2ae97893a3500c6ac28a40334dd878e54b62c1ae2c8a3744576cde21a23f06e5080494654747961865099b77e27b32409c77faeb5ec2365204f2276ca6389a3472c7be4082a14a10215d62908b0ceb4f630516c628d115831ac74c1376826142e3f0998414dd592e5bf18a85374b2224dc464141f3c8bf56bffc7bfcc05a3d5430ae42eb09d6679eb0f0711cccab83b0bfc4c971251e6d61f3e73f5bd5874abf0dcf2abde69f7b3c5e0f256ca6168a221838c00323f6b1deff82fc117cb0bf0dcf03b4dc20826a7ea38ab7fba879e2d314451f30700073ec042d4ac148c2fe5acb070a1582dda14f6c862df8b303ed1476b6eabbe969bd41a2e9be406fbb7fd65f94f0cc9dbb53bbb0721a1164084c0f2ed8ff07929730ad658b43a65716a424239b61bcc2e608b47df7a7ec43d14571910a84bd077424ec434343eefae2d50822253dc6c1acb77e1cc883a0f52050bc29e2b697ab8bcd146b911221e43f2fa129561c0a7c1b9e657a77a1a12c363798f8039bfd487beeb3bfb68fe1155f18c78289ade8f0667be1960c07c38144b6e1e6258937fe9a67823ff1860b9b29a65a12f58c09306058e9a29ce8f57062b7c4de1277b06048699982fec91dd8577545f6937039d0904b4a15a3e8452b6c7697bba45004fac1591205cd90416404042a8c3081135b8001f4431345c020c51006fb13f9b2a323862265d84cb514582f5d5aa6a0f4b420db02a42d43bcbcb0ff0b54a3e932c43f57c11f853036d3a8c5669ad48497216fad228c9708ec9b35b10d88ee8e2a15fc197498e7f3b90afe363c8b4a868fe3dfd71e4e7ffefae2dcc0f95c857e1eafd16b6bbd0461ff94a4f24694c1c1e6ef7e9fc5e68ff6f7e9b8b9af7cd8480b0fca947ec1fe5a9047f129290bbff2d830f66a1cfe364a80b9a20e46146fdca3640f92ed4921a2a82387a4101196539e0c2baf0c722914848786a43c9248f4775446b082fd69bea688a894ad7585452d11d10c0000003314000020140c094422c168241a536565fb14000c93a248745418894990c32084900100100208000400000088c8dccc00bcbdf5763e6c71bb01c0cee3087158de58af47fafc668d52d7008b50c11dcace6ef541765362c9b20775709e16b79d1e2c8ca5fd113d585aec72495f95a9b562d016689e6e6d922c58f60627a7e5170064bc88a92bc017595dde71fa0805c6b5fb5338e888b1ce8242a363b4dae59a0cfe25656837fa81860b6dd383116b0544ae81af24ce2a5bafdc4a8308f5741483cc46319365a504c6be0e5bc65d12d3daf2fef471af7f1d91f59de9ef45639e56ab34d38417e9494cb508995c9744f9449a6a3909af86e09a94982a0690e6e8c28aecbde3bebd5e8a9f54cf5e83dd01c61ec3403c0ffb3dd6b1f750f5ee416ece557ddd8b94243f0ce583c9acad7be96f3249ed9cde7e4707c222cec723d0bb89738586c6904206add0bd82ba833c64027d57388382da5eaa8340c150adb0b3b07b53699df40684ea7d299eabbe986c4f7d87a0c0d05cfd008b8dfd5d85f6025c6f6ceec522e21b48633100332bdbe1040cc59c693db8a589a7dde0c756affd0bc1a6fe293560497f05b6f2870427302c2298b9c9dff58a90c1563f85691b21146af1fb6b6c2f7efd8b5ffc321fdbef62171035ff6cc51c694ac1af619b8a96c0c9d84000fe920376504d4acee79413ba024f8d9cb34fbcf2da288c33154ff8e28d18176376dd4c91a0c3e4969c85ceb9476e2574164263d7395f3a9fa3a7686eb5a79cada1dfdb82fffc84143d670e47677262eaff857ff6532badcede4f8f3ebc0f8aff899d6f4b46d11c8a819d0b96ed2a7898ad8b98a6f6965b06fe988f04efaa17a1520d020ef7cd69d983ee35621a4ae0d0f997742e366d3ec9c6179193ee51c54aff24ad0479472076105a47773dce41488748fb927472b8eeb82ceb988796236ab76dd152ce500cf95dd060a41556d6e03ff9323729cca2f563c671790b4c4335e868ba18d520c042d23214509e5718dd5e9a6a6b566be52c1852624b4e9a721df7cf094106ed7919aff9f1f071f06bc886188a6388081bdeec4f752ebeb2879cff93dc511e1fc92ad38a3511a483f971918cde9ce732846bc7cfb927631b868a5571f61539dbb0e13f71470ae4ccf9b0769b06edf3311b561bd0f337c9982c39a77680f6fd7483b10eaf55a387369f519d6ea5769b0fa9a5b6b0216d737e565404e89a7a8625a9343e25f753b87941f2bf64f38148b0bc512cafd94440e136ff4c6901e8a8ee9e1fa149503e02fb40d9b1476046497a64fea740bf5c9bffb48fa9756d1e32328ccf4af0be8cae66c24fe16e3ec865fc28df801d155298633e82815a526e2f779ffa687a0ee25a9f2f15c9ac3c0e7a65266f3467a6292e638f1ed0745895ba109b088a6074749acffad589c30ac95b854add954e3dd31e384c874ce359f09ea159a19feac963e71c660c392a1873510eea586f5504ccbb0b53fa2c2ab43d3cfd85a7aaeeab5785cf7715c6534b1c23af9b221e53910aa53b475a07810bb32476b1e6001b5817cb5c9b17c719a6736118962623323c10e32a486a24fb9cffd444c3397fca3a52cd70f0bea66e38cd39b1fbb6e22acfabcc9ba169e7e45f97be0e9711d0337f89ef6340cd72ab1e21e29e81a0feeda5064feb16dfb9bd6b128c405ac79fa01c6955df03f79cfea36f52f7079ee5b4d1ec9d50f149f1c0f6dd86658f832d60e088d234e3860a66423b3b9be9cb60d3d3a2451371c8bcbd32bde333731b619d906a8d341f009362dab908ec4980607b93d3a94001305c7d0bd5822e959bbcd2868fb30ff3b0dc88b74dc559a8f328fec37a1f39bbd06da88a34b31fb030b1e5cffe26e22af74b5d1e45a689c9811d4c58aec15de935c61d40463f52c8bf9b1330fd0eac0cdcb096a69c0766b745a810a546f79e91cb3ec8fcdebc8985e2f7bd05cfd34ffa1b62dd312df30fdaebddefe3d495d032dfc4496398498b61ae80bc23e89e1fcb58aee1052ad20d5aa1cbd0993abb70fb21fbb735605ade496bc58b80d1963aa41803105e44a49e782b48602fb8a26697874799b3c4a71bcd2988d8187643241c341a6166f07156f5b41303f713d488e69abe29b2d413bf940df1e7f56c14a7959dacb56df8c9b85dfd5e4b14cee8cf8a565d3f9fa11d3bb9457a3a782f12227d3ecec7b87b706c9135bad7415c47a48bfc682326992b439349301585ac1023033cbaf14586a7dffa9d1d17bdb975cd7517aa8c37410cde23c208f28783a0972997a22f00fea8051609f5ad58e1d8b47ad8ddfe1c64ecd3a6a119e75e2a1004b24f15a613991bf90411eb3461a34ee642dd4a4b9ed255e4ba05c64ae9c8368c2ab8c2c39bc6c44bca8aade4cce4235c2391de27b35db5d113c27251a382e4336fe64804d8ad5b13f161233d29a034d2257cea0ea8664e0d4b7662530a2d4a582a7de90239e8486faf5dd8453ef9911ba36eaad8631a5131319769c4b0473e57bb92252b6490c1cac908bb23adc8a9e830e91abf3a24576d7ebe5d8213d8ff942ac1c5c074a675942782b2ddaa1cd08605b3952af210c2069706d119893499d79bcdf9018dc59d72754d0e398e86f3015909e70d13423543ef2782ac923817c9059df621323f33ea39e7e5b3c4c1f614dceb990ca6ba30722ad6a7e183dff41f72ada08cb49426c0531f8c37a211b13d1b5ce424212267ab333a4fd7c9819b40bcda9d15516624df0a46ad4ce98e0e5eb5c8dc636fd0d2d58ca86c3ed73cae0d307d72b8d703922c848148d41040c6e4e7a1f48192f34f5ae45f79215c30df061682a07c3fe089f2b3b6754cf4635c5d0cc6c70501098278e8de5d29c8f0c6381827bdca12bbf22d84217eff3f7404ba81ae8afdcb818c64d9bcdcadd521281e284adbf2cfd9904696189b8e11a370e9c2fcdec7b08287d4a26ebb8f0b349121f2b9165ab0602a2a0ddd4bea3ba918c5381042bbba75d0cab849d66b1851677f2d80ec9ddb9d3ea4980451ca5baf77e003fb8a0177d2a4505e439f5cafd53ff5dd2073e1150bbf84366d79bde689588fee9ac52ce67a67446aadf09bc379cb106d1ad60b902956fbcdf3537e9309dab6be41b135e86034955f0a108bc753fdb584fbe2dc2fda70029cada1a8dea300b986ab35f26a03ca6853829c84cc864ca023c3d26752c1a2db0ad89a52ad9e3932a3f0a7bcfb5faf5c7b9a7772b708dbc86b2f6a66e626975a836d85f676f12e4c2227ed68311f29d812191224f0517526739bbd30cbef7de5570093dded20bfa76daef648e194abccde1b8f0eeb6ca48371f62f25929023190d0981f7dfa946767405ac0cd840bef0dff841cbfec4fbee1913b65405b751e6c103957595c9b2024437de8685632aefd073825c6127d4bfabebb3fd24dd3337302a9e1d7e48a7968fefa6b015bea15f37216868eccb46c37faa5b790bcad875b6c98d3dbd9fea891a711a50f77f2399c0d73ec528490bc1894b0eef3582d5f9f12c76f80e10ace64b4fb189b09d1f3f8b397d0ba0dcdad2f84c257ebe2313d3b7e9b9d2e98cb80ab06d8a60738f2ac258a52747b5fd0050167f5efb745223e512fa089e6d2736205c34939f8592a478e94feeddae146de36c96159713b85f8fe6eb75611f3a376884071362c27414d16bce21c536c7b2d54e2cb5d5c94401091f497d088d88202bf9409590b3f67545145e7816932e7e7afe262efa42c874d4c136b94fd46ec2d565ca8acea621ed3843e4d59bc831c4d1d554254fcd417ea6d0224f7ae40fc02d4fa587f37bfc605eccd990f8d1f80e95a4f83a15f4d466f92c87b1372befcf7ff9bc278038f2aa00aef575bd28045660ed57fa98d93bada5a14562397179cbbb048769f7bf888d8a8b7fc6266551d8c396b35b82a09c8514edb8854076b8b7c82f29a46b59812da87560ea2b934f9cb062b0390be579df51869a11aed68f06cf40fefb0d1e9db875a9edf413178213c8e717a8967780e5fad8be9c27926ad3438e85bf8d98780fbdb35937a0b571d0b755f010c483c97a16fa67730cff49e55b38bd615560da418c0c73fde0cd06657b1b78e2b86ac45efe18ac892bfe01d0b2829039f75b80f66c172ed913235c221dd7b2ce73546a9adbad89667b466603168f529acb08d2b6b12bef714af6437797072a58277e402ea2c9eb7a64c4ae5912053ecde75f5497957eb98f90a0fde91251e4dcc12f089d4366610ceb8178baa6ed38ee129f83955949ff37ea7dae43456ae2d875a9b26a37ee2f69a4c97c3fd42873c5ab026d9dac78af3337a77ea0b9a094913b0ebb9c840379795e061cd88b5ad0229745c40b9cd824967801d4b75f53b1c6574b57fddee2d9209c9646391f643c939b48fc4c2ed03753fae15da873183239cc943d60dd7e61e1446f4b66b653b7cff29f481b0398b82c434e919aab04f2fd21f67c097f71fc3651b5cf772545614f3860ef8251b76da66e435a68c1a913b511d39a9c2f141d4c9f44dade3c4f0cfd2c4bba2443248d45bf242836440dace00c92c106c5597cede299364e5248cda8a02c2179dfe03365ece914bb80090b7e3367a80c1b9452014cf7dc5a1171cc0af17df33f618557aeb01ef14a7d529cf190da28fddd1270d7f1bc4773cfe665b1f1d9020c4a35e32c55df1d934b98c8879ac9d51943632a67b0b84c77791f813833f906deabee4894c1793655dda5fdb72ff2778aa0828ffad5c6b2d03eab0210fc18f385bd319d0b8a4df5d6af1907a2030e497c9e104110740d0423410c5c126983222aee559ca093b5063894843abf799b46d943b2018431edf113a6e6b9d47c30aab91e2760ef7be455040c9596e9637a9d061dc1bef7ac06a4bf5c0c05155a5bc5532863557ba890bdd350f3b81406a042de67f8b320cfd50d4a1f6e1c478ec2ddfe7e48f3e1f222d4ca2313b09b39b4cc57256056d8e760bee7cab961b9f4ca7fcb20148cd7dc0e39a87e6169ccfab6b10537702ecd7e60c503edaf671d5796319db236c3b9432b92b621053912bf51fae699800d6045c8a5c0569c5664c6959a02cd172dc55e5e0a7c29fcdb450d10658528b28e9089972695584dee4c89f1602616bc765c57aae40662de3f6a216e8218678c9c6f90718cbfb40625b2f377c262e862a71af1afb431405ffee10f17d790b14c686fde64bca4455eb755a360353a81c1c3f3700b7c40bcea213a0a2aadad071fe16a37ad5e8aa28295b4aeaac4891f26fb2d1f2266da1f73181df6ad8cd76c2d89512e12cd2a498dfb0539f8146072f58376138006177472f901a3a5e885b5da22c7798e0f82b50fc3ecf53486e84f3a499d3444211220ddf027e64418f8b1d603227ab351e564a486f436cfc6e5f4ca98c73670dacea3042df1a217343a6d97d8693a6b115b9a7810af3b4432b8b167804a005135895d1bbace946ba8bbc199f6b0dc2c333ecd2e054db3b4bccb5f0a380a776c05ca1dfd8bcf55984a9785581c2bfd0ea79666325038ad436b5a627dac2281defd6a352ea7bf1848033b59653e9986eeb4b173e72edaf015fe94d1406bed8af9b3185a865c34005460c67463b42ba4143181da0d584219a0fb6d0c2b44d3a469deb7e3e45793663c837ab29514723eed0c88d734ed1f3960a04536a3b023b5021db3f3b05acc76b3194242a157414c37920258153cc9aa3cb1a9209224ae19f7d222015d37313c76662a8ded9bbfcdd564ded0c693d2b7f637ec601481ab1bd8a30158782528c3ee190db110d5244089ea11d5b34e3fb196a644ec97acf61598ff7e57dbae3ad27c79be08f85e0b4add6e2d845863c3a4e5e361ce03f3f60b32d08d97d19efb6f12aa4b6608aa2bde3353591ea0e855429ce829d50d9fe7dd02c5d4544472976b9db3b89073241d899236e1c7f021f2a31f5b1bb927d6097b502c62ddbe9f438f32302f572774ecdcf25a5e62bb7f72762781c8e28f32d056642de8fe2168be17e77b96c26bb44da5088487794177ec8fd1e3fb03ae797705db034ddaa6c421e090be3f82f805f5a721520e54922cfc4584c2da675761f0b26c800dce8528ef89ab60d8e6c962edc2e8ad3b455051ac350795f55b49e896243569540e38620f58ae9318c82513425a4e5a0b00c880c8df865bdcdabec578c65d31a7766013a319adcd5e08a7c393744c04124ec55506e093e8a10bc81a1022c500660c2b6e11372a852aaa75eec54f694ca101136df74a591fd3076d63eb7068186711e3fa62100f8cd583044dff7717b4382a46fa60ec84ebd6de0e5ab58759a5dae8e6dd82be47991b72e33195b76a49ce5549be04329ce0a5edb4f4a441f481a1fb6630befcddb44646bc7ceeb49e0c1051c04fbf9d1afa108e789b22ab61284549c6d3b43fc8abdb64d5ebdfd64b1e2239513ffbe136deea5e41cb046ce5bcceb09d949d60cb224562673e000e60f28f86ff0f64a4eddcce9b1a63e58a0d2dcb24f89df13202f5baadf62964d6fa3a6bd092b9b516efc9c2361d403355beb7a7e7606420f968f991fb279cf536cdab4310324fe01685f5cddf0882e10a6b5d463668a61ab986bf8d5cd9876a11f6f290dea7f2980ce94485ab250dabe997c02a473e399000cfabe4ae5eff783f6d281e4f57e909983c250cd3d1722b0d6c915d326b4ad57744ab6fa07adb81b578de5fb8d3cbaefaa4525a8e84a9900f2ee7a4ac2f2fea36137964de3ef2a524a30aea830f4680ac29618ec860567873e82c30bb54cda869e676758b27a360bfc99bba472b505dacae2a0c2b882413f8d737fc882b57f46e09950adec77578cbfc8c4a9facc280644d60b5d49be8ffe70479d610f50422f813aff62050c56b53f3dd860dc6ee6d37aca382b673f09aacb9a195ff80ccf6fabf93b6ea79212a0dc37c4c7db5cf95978d429977b152adf80ca9956d07cf80fd6fd3a69762ae4fd39d382e540e837d5d019ce8299178db971b7dbadf2b5a39fa342607eee133fd1ed9dd0414efe3c47acadee50ba6205f69ef82524c4cbe793cd73bfc6628b010a9344d626f85076b62e41afa88b7811101f6bc02d89171c79145ed83bc87328ae8886ef772a31fa4270d487c97f6290dce582056b4e3da0b60e666a9f9251ae843591c79cf7f4d519e76baf6b7171a90326ded473e6465785bb40eaec41834773adfc90ce8ae00d76ab1da3f16433b84799c26d1b9090e65ec19bc7dfae3d403b1a8f3592b02196102399580887b4117cf8a324fcc894e4fa4adbb83737a66e5157d0e130254fc2c0d673bc48b34fe46af4cfcd35f8ea6f8981a7afc32f3c592dd2a37cb879a89a1b5a54d112b29fa26e5ebc95d10bf443a691e3c1d91040fb3e5477f8a8559acd64c14d970bb17bf02b04184d426a068df17a5e31605307bdd85f1542510732b20ff8dcf39427563d9bd147c5b2e0acb2e8535a6ffeb11680671d62126e5baf9784993ad0f50d23f98aa56755585898e16d2623230f584acee1b1ce551b3e32e007a2124c4afca4970c48f646725aa770f7976c07d0a817a2d7d778fc4f5261374483b5cb6eb46ce340b68cbed4ad9ad300a86d5ae59da4132340a3514911f7deb56f4fcbfc88c940eae3893ddac04d0990c616f49d262b7bee75c2269121d9ff9bed49af63703d6238d4263f59163e2fdac8cf598e628f80b320517ed7ef93036e27b3526daa65530063bb707cb7eb5a018f28151b090f053804486272ac002764ca34e2a907dd091755b4823136a525b5c78e90f95b171cf8890524394912bcf068447ad868f0d4444d1474e4e3bcae03968dbd503c16797d0542aafcbdfc5201e26465560c2bdcebd33d0eff34536670327786342855c6c64e7d224eb00091a5fe8f9631c4f575754657f51eb2a4ef2ee780a0c2ffb13400af9484d3b86642649d00740a26fd0c0ad8d9c26f56f9b83bafa44f4fb7e0f6d93bd4b7d034f95636ca01566e60fd584c4fd6676afb592e8cc86f6c016c4b6d2c31ebb4d99a524dd063a7791de64f796854799a825ebeb537c10c91b49023c77594625d2d36d3e06365977e2863f29f8f1e6254879550f54a9b90a64650716bebadc514d06e3384380c9003b5290e42989d4f28ccb4bd733a0e5dd88e08edaf1aabb02aa09ad3ac24835b8043bc7c580bb9d061971e4fb334402ac542b17b54a641c47696988f3ebcf9c33980bb39c73d005b3b78619454ff35e1ca763443958441a1153820de26035086c0c012e787e797f40351738703850a22f1cc198a37ecb3a07e2b310ae52b57db03ee03fb27602d6530dcc1955b696b2ff0c27646af7e21fdcc14589c2f3caadfc98b273c9574b1931991549cff4dc5b657a51419e2b19135b07caba82a574c092313188015574fe605c3e05a275603f3ad7a0d50955e3e29df7ad8d91a28372297fa11a1f45249fd7e173d467f7fed9aa699621c1509875e2096c5ba815507c32d33a99ed598f5a14830fb4d4ab9fc1392cf97043e6ee82adc5a8eaa91af973b7dd19381a2fa81957b4f921144d9ddd6bd61cc4f18f432e932fff65d4088f85827cb1c5e2d34ba0267f52593aba9deb026229772971d928ca843aa407f027137dff4b13eba0ebedd147e23ba8a02c03eb65a3dada1bb84205450a7a9aa25d92ef5854d54e783fc2fc445dbd04c1fcb8bc73c85579c12bb271be3861fe027c39fe5b1cf080a4d79cc7c5eac2e896aca823fd8eb09e6b0704c1a086b54d9f463540e0191e000b74442d695a1799a4e3b955ebabca6b3034eca7b6c2b3bec138022a5b25c8011c6f5646c81c3fa844b9891597dc6539987ee13ba184238f0f2857348218eb79455fbdd88e500d5ddd53a9ba62c881504abe4cae3303daf32d9113bdd196a02077b24c919c2015beaab82960ec3b37578aa4de809a62bf3e8f7c63ba898bf0c9f3cc26914bfa0479830785cd17578ddf70306b4422bdc151047fa90386e8089386adb24b6b8d15572c7fbf01026c5af2de7eb0c2b5adb36c25b9c74dcdbb061b131e38e0bef894f90c3a36634e02851212fbbbf8ec840a06893e00d5fecd986cb7f6fd24c5c473fe7f5f66fe582c52e2b7f23935f19bd81dd0961016839db5f21991a58d70f893517121404f8a5622d4c0a074c7bf6f150d3b373626a8c88f27d5308e6d48b7594147a1f16f88f3c8f173a5c8e4390190d6cbeadac1e8cacf980becd5e9ba39d3568cd8381cecfab96dfa91b10b7b58bc89da82659afc7990b1e3e97f1e035e92449c97c5f6e7acbde920b652f89f785207e0b56b92c8db69d80064817bf5ba94a32227b959755ed526e7e05e98581bf23b9e3a8368b03508ca034e49005c802cd4490aeffa99db98a33bb15fbe8f836ef5752b14eb7078a1c43225c3d2fff9882b6895c4a3bab235804e6cde26d6bc41bcc6bf0cd72d33aabf709645b3829428d9a39512b1e76570af0822f8d8f878203b8346174a61638943c54c4a2ead1b40e4320211ab1d5bf0a0868346d582dac8986ca3b9011243c7f85a851a72cefc6d019c5a82893f5b84222c512f5763c73473ff3577a9274791eb64f101b1953e0c22ee1e30b918d815690c3836e779d011d1bce269ba653c4985b5b3e2ef479d2418b081064647d19b84aae48b889e39c943316fbfcf47ae349b2417dcc27eefc2be900cdf9719d693be3695664a53771fef8654bdcea05cc41004c378fa12a2cddf50ac98f43265e3e29328cd8a2222dedd8cb2d0a96563ba029b66468c7b8a56c1f02579af90519a0d2ba936922a254094ca8edd7ca4a7ea9f0807a074ad8dd9aa03098d469fb5df074d6da54fb92968572c024cb923006c87a22fd81fa59c499a3d0e773b4e68bebcc2455756380fe895e78b014f0545d3af69778e40ae15ddd4532fd3460808262ac9d30ea1994c422785cca3e629e9867ecc1855d80d799056ec73326366399e8656c693811c8ce653f1c07756c8ef1097d3b9496f1196e0a00eb7924743b2b8152a4953d11e42ad9abfd39499f4be64a833dd70daddab9d88e0ea1d46c50d83da768497574e74563eca8bf5777be0ac635f584cd65b9e9cdd064778df888e18d70f92a1574a904ba7209a61ec8e9ea2db350b2b764bbb78114e8c4c40d7828399a097ceda9615b2177c239981cc333a1eb5e75b14eee591eb486f87e41559658fbc9f3a83840a72f79dc19c5f30544c80212c36cd7c633c6064d527e3144907f87083da6c76fef47c49d85837bf7872fb5a3fb68ffce50649e11ff3632f2130de24fcd2029101c40a517b8de2ad59ac9dc7b5e85b514e8b47b281ee310278a5a4085633c5b16905ddea50fdbc8009913260e8065655320de44257f6bb86c6424acbbac0fe8839895d4fafaac62234d33125b83082864c40749d805fe81e70d4e9f6b67c1574e82d0ae40a8a829adfbe0100cf122ab347a56239a0052a5d1c1734141c1667fe9ec8817d2f6d7c62db5e3b1726eb44de3624e9d25907093d8080c56b1dc81858b6a36d8fe49cbba0291713b03cf99ee70030e00f3468eb2a1231d51f18abea950ded7b3a59c28714476b38e7f8507281315cced5d2130ed5ce20b5bd82004b3836f98b6e3172d971ffc5ea0b3276e6ca3bf135c9dcf49da0fa7336b68ba399708c3bb89836b104fb69aab8a9e3c431f21453c013b37a21d25ed2f3aade04b71e1133b36184bafe1d4c119642d280d81677c5867ac4d418fe2358ba8296ee4a8f0f2cfc9e9226823c2a5b36b1558fa19723d5f3723dd3cb2a57ac4cf185e93a7d91bfc4d4833c9b0cef64222965c9577134b3f5399c2e4bf5a6d3b74143bb20a04530371fa8612cc16e2c26a279553eb061504b860e838d85807a4fe685fb0b8ebf112014bee891d7786eed29ae0d6ce127cda968be7faab74a86761d727fbbbfe483630b54e1771a59fcfe7076762c00f27475f9d9bccd1456e65f64c9f7952f3b8cf5a61025961ab9c2f28f9322e95daf5de131c27f026755bee9f136230595c0e9302a85d1115ac6a8e785b4db2cc06b5e55282b81c720ccd3a5c6a4aa2a7c3661de3428b94782e36cf5e2dfee4b58777f38496da8ffa05483c22cc126d4c74af752cb35d22981a3147767685e515267b275320877ab5d69c66c8240274233c04771b59d4a9faf952ffd8b48a894e59087d8609285a738dd3faa752c2ac53a195dccddc5e6d4c9089507e2a2bcce59310c2b7ca15aad2e35b2f302936a0207ccf1da58143e4705601ee50c6919e430d6b039d4d79805614ee1db5ac9f2e5e0efc603a3e9b17fe90b60f11a781038b1eba91949c04bd49e4ac62d60a8fd374d287c649abe7780482399d05147a29113b7fe23bd5d3f9723fcd6adf25d5771480b111e7cdc95e4f538d7d6b66a95fd994256aa795bdd59e7f3d59990a36392c19baa6b0f77280f13c45eb0d73dcf1da213e1883256b7c71a00e8480605c0241f5e8065ce15200203d55bd87f360ad5b640e84aa34120614a0a5e456fa942aadab667848337d5cc9a117f10897d8cff7164300889796776de4267586dc7f647be27308af6aaa74147b31955c9ed5d6d528db089511ee643ee422b8c68ba8daa06c463f175a3639439fea3f1eb5a9422d6da4430b93bc9ccfd78c1868216c745cc6a865a148185d86a13040cf8f4029e99fe8511e69d52d974ced5d8737f24b5410529c3de2ef4221231098eb8fd4f42dc92ab85b027a300803647f805bd1838a707658a76f8955a7300dcd55539970dac5466345b9b697397dfe73dd8a9c33b6ddc8d7c5a0e74c6964fd7bfca9ecf1da92efeae0fc0c31f741c1ee1771a9ffd78f0c66d009e1e0e7260029fad0b242cb3417ddb607f20b204b8b8396f25249e18c6839e4e9a76d36fecbc7f813ade0ac4b68074a615c0c2d47d98bf12b1824e6187dd0a27bef1b0ba43e257624aed64c7ebb66f6539246cde441526fbcea52afa6485049cdb7901db46aabb57e6862af9bc93c51a4e104384a9e8f41c5b49dd40afbdda07daf023f8fd1dcc294bbbb709b8a42dfb07e46a6696e7f6cfcbff343aa54e471e2ebbac8a2144f74fc6b1e3cc56a71f2f34c48193d6c9fdb8d6b1f21c27ca74adfb502a81c0916ac79861090cf068b4a77cc07dafd7b65d18973d3b414b658d644a66ceeb914b1502de40dbc9e8d4fec7f214c11a8bc0c940fb79fb2b3bec90e0bd644d0b0510df7e7c26e759e8e867d657bdddc42a5b3b713154b64b94e0037a05f18e46afc587760a6c5bdd26b50d74253749ca59afbaf37c90bb74a37bf2ea46b37a9b0418f7bdead514d04fef50eb1159b99dc9e881869e071350f4fc21ab915a0da775ae5f61d4c7e2c5b3bd94cd5d39678b511b3c546af275cbf2691c692c7d48fc04ea632842c16549e632d3f907199d84f9caaad40bca46b809f3482870ffbea262fdac543f19d0fb176238dd863018a7a4ba2bb6b025eda03a8561e7b3f54ef0520ab683e32ed13c4e085d00845bc5580747a032c29db7e65d5e65dee425cbfa1a170810b5d1b0bd6b21ec90bb9f60c6b2e1653dc07df8b1205419eb7d174dc09d8b0a76111db1d50996385d3605e85885d6464ce1a0136eab729452b5adebae5addf0e1aa1a8bd61388ebf57a69f3a1d59fd4a2305cef3d814beb1dfc705656c003a0fba8b0ccb9e49e5bc2ce10dd2c0289b9509bc925e431229628ac0f26e2a95f59aaa7e188a2ab64b8a4114a8212c8caa7032220c07f5ea13a4e4870f92f2f35083443bacc68ee1c94b5f9f0ce784b5b2b6673be54a913ef889be364e1e2313464cab6738f7a797714894c3f48a1374212714a6ba2cef2251871ddae1abc1196146178b10a6eeb7fcd5a0186cd9475960133ec2e0e235c485ccf1672f6b4d0c9869979a50bd02574c77e36da2eb2c85cce22f3a4cd6eb00d1191c93b3c1c0af0be5965158e153c873ff8086b5a05a2d06a3a59502a2b98b7f7f12025e72fe85cc511f0396fef2053f9875a4752e429c7a11097edb0c56e0b0cf8eeca88ca796bb13311fc6cdf3a198697e5aabe7192e1f666cc86c08cfdaec702a506d8c46e03ee9f3513c9a8002f3786cb5b4fa83234bf2671c32b65745007c99c02a8827da804deaabc33ff29c39a25aaf6e7ce24510e7c0522cf222ecf793d018e19b4aa639e930b9df07879c1b9891306daf1eacd6b0eb9164df44d6e2e033de5e70173815c310626679f2b20bba7f03d0f1830856d970acc922a04f4147456881ca0116703379429663df745db1ec3c4bf370390bb00a9d8df5317f1f0dbf458629d0ae7ebd3c1632f39fde2ac412eda147d296acbf6f681de73b78c47ccd037090f51511208eb6d026e499af8196f6467ab17c0ede3c3f50b8e01f1d626d49e354c5e4d8ff9ee839061b92079a77b9d6f0372be558dc652c0f9e6a788591f64aa93abb59cbc014459c423250e06835f90e624b73f5f5d1a1e84c37852edb3c26c1f4abd664981f2d5852c9e377039bf01de7918687c8d901eb3a1ce1984db8f968303ee066b6a912c51c046fa7832df2b660ab588bba9737dd33d6c15a9bb5da75246c86520a8a0490148e078ce25f25b674cc05fdd1514ba073408252f414eb4d3c791845aec4424770b5a4513cac9d416a67be8e075b3dc5925ce50d663e6108c4eba29144adc2b08eba0becd5a3d2f5e36683e7aac4f453150863669c5072472bd230db33d06bcbe0e6a8eb602257c4f10cfe03e3f712c238125c587ab72bcab0cba1f21a2ece27431d3eb12c760eeddca04f41bc741179c3f211619fccd0ae50379275b4cbfc4ba6570262bdd8ba3face03a6e65d2fc6bd9c1fc4856a279de8a0c5e2ea58304b7af1b95a5ba6c2098a0debe67315acf99def2bd16adf079cc9532fe3f50be04123445ad2cf72e855eb36ee3ee3af5c958ea6d4436f397bf106727bb8dacaa0a0a044cb31f456a3eaa7e2c96800a3c59b6c3255ee8326545d956c9119c2a0a697168e06acf621abc8f4b81b49d40f25a1df010bbb1c423a37e0425cad5b2012ca5d4e3a0a33809535c4a730221ab583f478be52edc5f740adda94558c02a05cb0b56c88690e729bc549b94abcd6c2c8625ad95f31bcb22ecbcc88543bec00ab75aa7dcb1060bbb0577317bd8cdfd0a967d2654543e5eaa9f33aae3e901b2e4f1a97f7d374918f86ed083bd42c5a4b996d2739b7b038bdb46bcacaaf79bec7578ab39d64ad25f73f65773e859e2256f225d23fd74ee4ba350e12455b79330890dca9b7f42e8415bcf418991255b2df27434257b5f79831b7f2c19d930405c1f06b5e75a503e0e68398b1b0023e21f1923b34a75a1abc787cab4f2f71cef7d63915d4f5603d508159e12379ec711291f68b16399504dd2b1467a97397febe16770c0f2be84e1070bd7fe1e8e1e5ed91ad8d01ff3d9fda9380a4549d1ff03ee8e7199adaa900de48ba7e9716dbc5037dfe3dfcdf8287e58d41ba0b0974b342d201c991870db7aa1025987f13d1684998c2349877e37d0870fe0afc7187a0cfd23a91313c4e80ca78d029708da936e8b639735f06ff3dfa3dd4c0b88b836295347dbfe18db44f88f48083edcb1bf8d9b6e631c8c024199ec4a742276a570b12d7f6d9817a641605708c51d556f44f2410425b799f17c9d0e5598c89c75c86465e1a7c2fa137e45e7eead8a23058afcbe34ae1faa1797524ea3d5ba8cff0d807ddd6186280c5d452a60ba9286dafaac4d5462a6ce0accb9c7f2d9a2dff6f8e655d24213e46593ae40cbf59ba930203b0494d8590ceb027611ec73cb4af2da26f59d3158d51b921011e0513376b951babe06657128980117b3236b8a07e1b6339518c4bcf802621f2935e2541ea1510604ed5568a79db93029ed547322b7f491d814cc97b3dd98e957eb595f210c2a6291a01afd7f7322ca4547445fdbf605986ebdf5c1737dac4e0a070ba15efcddb901c24df73ee1cabaec7fdabbea8dbb7a21962ba70d042eb7eaec4d3d6a4b2c4ee8f17a12b7ae6057ae1d8b7f5bc74eec9501c25193300e1281f59df0099b98d44595c0d89e54c7ca284127fd416fabe89988564e4b669135d83f9ffc5c8b83049e2f0e8bbc813640e5953558df94f3dc627e1da26a1230f6caa37681bf48c951748b61e686c7fe2737a89680c6356ea4ab9fe392bcebd71d6439764467b3cb33758bdb38ed7e5dcd9d5ec9826a81febe0f5160696ee01bd105f2d693e5b3607a3e60b370fc8213065ff51080ab42c1bde746d3be6cf51ff385a9cc6b6a5ebc25eb4f82265ad6debfa04af85cc0433e6bd81dc9b841c618844604312de794c2c35a40b00d44f295f7fefc42d6245c853c9700dfccabaa12921e732e3bdc3316ae6d2288b5229ab0994ae45208d8106fc81be67429b16e51b58c2526a201e87b0c3b592b449df3c577d71f8a04539b6db1bfadd365e8e1ea1f857b310a7269f8ffca013ce0040e0a94402d721d2dcb7144842230d8ae5ba6096a0ef0e385442a06cc3a4fc189cb29ba7c73f01bcbe4578f2220fa8e9d8eaf2b3b30e44bd345fec1f4465cd4174d36df8286691f7de610c68036a1da277f4f4b6f0dbb9af096e28143741fab33a66dd42986d0325d7db58f8f3f1aec37532b5fe98f4f6712b05132edbd7a267ed2b61497fc5326fba562c135c5e3a7cf6e69b5c79d986fdeb5b5cc4dbd2f2feb682ffaf7b3db72c769a910c38a84479df4e6f44c7d965313fdfec9d38de5ab865afc6bc2f54d61391c793408189158dfa73e1326506638c269a3432c8a6a5e419617a7703406c8ecfd28090fa106b6fcd4aa0b694e2fbd71660f3c158f1ed1a71dc6308ce5e20e554460b5b6aa556be16f32fe045605dad9f9284a2bed96c0b38a5d4e3dbfe65669253d5ab09fe7c3f11499a8fa8946ecd4a614920d903daf0240cc858b286627860772bca0fe65cebf983a77b7e30b505f9f441a956f3a6dcdcff57d8185c2a02b625ac154d9a0c6bff61ae24d1e922c4356b5ee11a261034ab120f1c0d57b0e0ff17637de91264cbe05dd5bfba08aa52305499d3d066024c992e30ead93c16a2518dfa4021fb0642e0840e5b278d7b83130246640a831e05218332971a8f3a54f534eb50d421b7d53b055a6bd8278b46dfe7e5815cdeb6246eed85dd5fd7f696b988dd8e956345175dd20d87a952fa740653813bf5d6b61fe75d4f22cf02fe8b6f174f40e3fc6f5bffb2c390a69b8d8d4c2b57eceb9ff0cd485ef96ba1806728d714ecfaea3ae799b20a9005f3097fc71a5a35e225b8ac8732a79ccf701472f36e3f4e4eea47516d88cc64c1392908e19247c44fb364024b8d423a8f38c2fe59c47dfa238253a0e6d79301d9d776320b921936acfba6ca962596d9dd78b0988a802556666f5433b3e2f6eac21af4ca7913798aca6e28fc208aa14527d3073a77d12624deb7b20815ab75e1813c3519c1c5c37520a20b6aca1725d068671cb5ad166759d49905853a92e6c2490f2178feb5e8e7a215abdd4c4bffc239804416b105be934b40e1541934858416ea4cdddfa126f4038608496fe05cc14880c9686f4cd2e5c720282f44893c8b1ce3d65a78dc57b40bdaaaa452873b9cfdf65f872bc031a2c0c70191469a362e29b856639a90f17f131393ffeaed7f7d5e60bc82c8f7b1aee3fb798511eabb05e370cace1a987e43fda50243f5f5e7af26244a28e05f3d530fe25feb942c82d3d846b453a21f02f8003ea688a09eb4cda85f5db4a84b92a203adb2b91fcc1e838beeda0a61bd634bb4d7f3e07d81e70ae5ec0bc7e19f46d14da93fc3aac6270c213b42de1dfcc2214826946a151df09f67e61465d77b315040663a254059eaa2b2f7442217898c44e916ff6930ae8632d576d72680681391edc4aa1070d28a6984ffdb07fae5fe720a84e655682fccca14b3b95fe45e3534d2dfc9a8c33af0f85e9efbeea970d7ea6cd67615b3cbd1ae6fa268b3e944a1b257542308ecb256693482c425a72058f851080a90de600d140c17db9529c5f1ebf25624bcc4fdf21632702463ad80557089eb12bb31f885231372294c6b89be502b9fb67970b0e1342eec0f2e79a978e3ece3da45e3710375d4574a7d37b7ea180012f55c028287d3f196292b8f15cc730148c01b945c159079253081b0469f3f173969d10373e8fa3e1551be375b6c7130257dd0f0c64454faae33038fc283da2728131ddf19092ed4cca205bf64b58efde89a5f2588c80813b57c5eb62935436ab33317d14326d6d1a8ea568fdc2b6ed08ac46ac01a7c6947b5bf12d025d82b0d09a51d51b3706b8029b2db32204e8c14c5419f7277aa22177da15b9548ef6a1a1021c546c0a7ac5a55624793ba578bd21dc25a704f71dfbb7e00f0bd3a4d66f2052810cc522e9e6e2fd1fba9b82b60aee239697fb49c3c5d70552d7f5d19bea8bfabc0fc2b0ddfc9af698f200d9eb9c36c24743ffa456f09b23e9e5ed53aad1d7fd0580d173ec03e6ffc819e846c1b48e11ab6f5affda2359252d08c1d6e85e3b1ad4d54b987a3972609006169090f3a2ecd76e36025e1a63e586f9e7e3ac7056dee318a69c14d43dc3e8d10b3979b0a1ea2d5d295e1158f018553ae51bfdc0dce2773b4b5b40dccc626ecfa4315d5ceff2a04fc9bc5cd17155260dbbfb6aa4d8464d7290cc72a8b6dcbedff33af42ced74343d79ea8b0ccceb708278046f390b0e4de0174542c8b348f49e974dcc7fd668a3b60584681de095c4bb2459a0b4137e64a3c14254d453902b02a538db34632bfe1b01ce2aa901669e666a5a729a3418750525523195c1a880397813006ad911821c16b8a8036775d650b3b4171eeab7e2bb31cab766b05f649c881a2ed1e7c75c14f31b14128c4612a88809e212672cebd6e33722be749fc0802a2327d7cc5754ca6c4ae7da09a891c1f0fe58f9b454bd4e0a51af5e2ecef75f772b497423c4c649c12251070f9ca16cfa0738aa38968f569de57e21d23ca41e19f2575b819719b570ba228fd43bdef991de75303c8ba5293209598e9ff6b0f526ef6fa43060f12746dc9a07c8b2a5280506d2d499218a00488ce0e065ad8863dfed034bec077bfafc3161ae11163f774c9ec139cb2d5645518a76590f1bce60bbdac2d1cc4fda17e58ddb79968b07490841ff343cedf3ac1a30457350b99fdbc68e947c76eb6f11f1a6e0a5b5ae848e1bffe2dd4089d334657116ca14ffe2eb60d124ad10c009a9bb07005b3bf789ff688a22326909cefa80538a43198460683a9e4471b1a53bbd8866e349b387703335543677d9a4a54b02491062ca06643ad4a2f7f24b43dff7cf10b6143ad67cb1b94057efa53d0176317d56de1f6e281d8f69de842ac03dabc5548a7c980f8c8a21f6b3078814fee14aefba6045c5be1901993d17487fc98217755fbb946c6c411329b65a23f1beaa30c486814c43435bb9c359369b3f51b18f1a93f5dda68888037f42830fad7cd6950c6a76caa3eb0aa1553ec0154a844d5500273429f0f5099cf157084e086401591b370bbb965798bbedcf959deb54a07e91ed9a85943f7859369dedd6d94f3d6544386eebbd81189de3d5d7e804ecb2743fc9e2b1f55d390ecbf8923ef407b8891b187cad86a4859bbad945a5bd79d6a2c3dc5ea744dfea26af63501160e57cdb707b757c2b6f7aacad8fa76e36279837d4b118865a31125bf98e4772b2bbb74a397d1cd0ff53edfd81498facdab178efc1abe1d446f8d81074f6ed8e0e4011c36de9f15ea607049fe08e1a3d5e9edc7474ad2407c1d239ff4b0bc3678446e9122070ce4adeac8f87b5959189db3099b04c8eae02728c87591119273e30f7a02d5666093552ff32f35887b66daf533ab82c2988d4c1c6a5beea8a25a66d08982e842b471793713aa60b48e69c0a2a0e8e3d844e287e928afad1e0182897b98cd191355c60525231682082aec10d9edbf30243d076d89a3a9b10b9852f603b6d3bf65bddfc6dc7a9759f11715cca376b9d9d5e6fcc3b32da6b6e8e06ee65eca0102b82ff39f059c6891caab193a428729acd9d4bfc23b7c215c7358ca0f72994cad2a3636f244bd555399c8fc738cd689127fc465ae80fb0c14a23cffe05e490e4b2a671236d64b7f8bff6dc556664665174097238b61ba621725dfd58a6fa1c5b75df1d7ae3a10174c558603399f5c3ae3c0f5bf15a307d5c0cda1fee964db22b8f89768254c97efc8142d324820a856b1a00ab6aa4cea9bf90a75c5fd2f4e896c198a65b3b502722d5f5f333c066deeaf55da4814831026585a4bc27922c3f4b2b3c3ebad9fee018d5c454a7f491b09093edc15283e58d3f1f864030d00374f745f38ddfe4c28dca9943c9f5b9c3c368f8cfefd411a186a12862e4cc28f79b594c44b30f5b3dc42bfda773ee74147176acf1a2b1589a03243cda07a9508a02c9fa7d1b3b9b79c96b5c9f2d3f5ca7f88896a7f1f652eb43708d58a65b5b9db09456f17639dda832f0996f46c15450a8c404dad293f53a5b51b5201d72fd818bb8cd000d4da8c724eedbe4c84fa14f136676a679252d945d2c7e3f3349f7a94f9beea5afb334d341079e415b691620f70f04e0dd33a974800f6a81b68fec66b060fb7b80f2a6be16d0c484d78c2c88a129cb2caa3d7324d82d42a70328bf8262d72271a79dbdca2c1827292b4bf4c19cac9dd90b150d1179c4299c13a65226bf94e2a25cb5e0a44f2739f49c932539721a425806efac7923b20e85931eb52ebe109ff57261b7e026e0ed79eb54fba47504a94089da386ee0975c206cd5335497ded0352ee7b17aabefd6c059277c127397212d0a2619d6a3ac15409bf529a1bd7708ac51218b7ba7a4385c83bef7c2e2c74c0d823e220c930507d243a2b9a5c2bb944ce17ea5a0bac841d96af901769472de5ceb2dcea0230b7cdb19d76207f06815eaf8a6e9cb98ff004b23bd05db6ccebb87bcce47052c18104589be143e2523e7c3231874ab8ebe862ef4aa39405b87d512e4a113588aa315e72a3ca3ff692951a38e7532738c4f7de9f903bcf4b49ec12115caa2bbc765a706b32d9c3a7fda899315ac053a1b9f0882519ae659be9325bcbdcb0a5e8f781b88442d7cf0515fd7350887d2243e28e51352adac496b8d3f80a11d5de5f029b6c847d5929c985d20ba849ec6b29ff50af08b7f8936806281d2142383a45c1193efeb21f025b3581d5c3e2c2a025cb32e862812469bbe785cc8f4c9f5f33e0bf19827380f193fa5b4149fd2cd0c3da989d7130f4090fdb16813f460681c915ddec339f0ba91b75e50556fec47a8f3ff98d20132a371b23747458c783c76b3cbb991d5b0d65124b21711f927a5ad4ec04092a709b53c5057dc5d2996b50ec75a134e08ea11372f79ff577607c973cc4f8ae6a659d700b81d517a2e4c8ae86ae58e270cfd8730007945a36e2a961a9ad62e910a2f49302f72e37b5da2b96509ed24e34108def9699c60d764a048f4b15bd8c6dc5929832bbc255281f0d1b565b3437d8d5f79c38daa64e202f1e2486b6a225d84b6fc8cd10c6a1cfda57615c56b56493c513528e1db1f698336880c37dc6f28f003734293ec044363692ae938aacb95063b6e836df1a150ae99e4b620500ffc6f02d3c2c5ab76eca4e4f5f49be4b62b9d8429f7b119dbbe8f5eaf32378c6c974d08dfc816a3adebea48ef26c87f6a5a31ef4baa4ad2c5711bd842fc4f35b5b6bef928bc034772e29d7d12fd3b3b61cc39288ce02fa2aac3f5384d7795d09e161981bdd706ac5975169f5564fcde8730c8e9e333aac2f6b34c82a265896aa1f0cffa2c3922661c287dc70d38775a6102b0362580cf630bead344a7151c89a78b38b32e14e69e7e9225651d4d5e029c1e3ca3dcf644d57ba33a68bfeaa1a0774624230c57310576a5763e20461afe83d0aa7633b40abe05035e1227f91f23e20b2d2fb35738ee96a9b25cc1f2b4d5fe9e0b721c03c2658362cced80d794c73fe4987831793fe54fd9e8ceec69cc7f42d1ce401f9ff3ffc46a6b4a3def35dde3169f11afe2f511bd129bd3b98cb50a044d759978ccf24a5b2c36735f4a8d1ee949d3a7d2b2e6fa6e04c773cd9a9095188c777ba2faf365212be1a127917c8890e51b967bffb5d795ea2530742b83ebcaef03f0583b5a4b25dacaa5b0ec576bc8c8d130cb625a00fbdb9280863228a6175d163175dc7bfdad5c8e0f0d3bbda73b69623b29362c63783b428f3d7922a5199dde691332522cc5aef067f2f08f5a50081e761c4f2913036f95efd24994adbcbcfcc7b67f2e954c773e824d3d95f8aca04cf5e52bc671387778f56ceb445f671dd88f421e7c434b4977ab40ee5f5608f9b7ea08b23f2d870dc6266c3605194e09a9d6b9501d488008e1395ded04f2c8d0d744e23bfa87052621fedd6fbaaaca5ae1b386dcd2261468c7f76b2c289ea6e72743e3fb43f8019dbdf0b4504878d7b69528f44718ae4cc75d8038a4c2327acdeb3c6a6c59245c59c82f2120b46149093f23a21709d6c33e8c9df6fd25c110e7e4e248f7e96b1f914a6a53c431b0aee78db8ee06874dd6367473594005f6165249a87e8d5393cc1e1ea229a97170b82a5690513ded5b63712d0ce5d205d0024198f17a228d8f22bcec62a047a6fcbda1963d5d21832ed074457f62a30bd8f21aa1aad4284515117ff9607538eb51d51cc531d0fcf88ca9900bd28a07ef11551a114a2e691d76822e62a6c6274c8a57a33513f4f08d815fa6d6c4e69cb8c0fab17a8f8ddfe12c19a92aa13223213437ab2c1a8d60f4445a4017f2939598dd3a66ec29b695e89d79a5856594134095eca45f19823022a4c667b5791d28359c317c523090091f4f931db7c78421370ea8a7db22efe62cf1d571faad191899379ede566f02ca7d0c66215a9af4e8d8eaee682e4107545233283eb5fce62ce0149a554abaaf182ce649a4127df77911efb59e15b5ae5a98f481bb0ef1c99a95952def60ed376b73996903c459aaccae7f91011a30d6f7012136000dab754e0069367732339d2facc095341c57c88862ad0834fe7ac044dadd64adac246ff99569a120d11ade2f87666c8f801278075d0789038a7f70966c77d3e2eca2ddfb93524ba5b7b7fcfe082e6f3d6886b3b42137225509d7239e61520e98c0231562b6c1a48bfaa595b97eae369d9fb867b9d2b518b5ad89d95010aa1603afb5131a64f2c77524ef052659fb2d28398a995494ced22d049a22e76ea742391895acdd52ad57392c0b5295ea0f3188329614e748262de426f468b782a8be5fb653de30e49a986083863d93a004bce83179b28551d2d637f9ac90c28eeb637138b582491f55ebae4caf8fb76fc4614bfa141f62bb84b3e2ebef9aa8cd8b3479500e4b8c90ee94c49e8568d48f70a97f810b3f92a302e475bd530d5287bed51551d208922e93ae7dff3826546786d01ed0187a239ec9b8ad563622c4d849a7e5f74b7e1f2d1d13eecc1f5eab5ec6931d34ae1cf0649b4cf3086e85c48e6af65d3a2c97ce4b90c21c1048dbd9f76d47f3d77303a7d4d4228ed7329207655185cbd133a401dd27d6dd8936dae049bc6fb4a43a661700d94cd8ff9fd2a72a5a1037d5388662543abbf6855c2bcce3ff48a120208fa26491d12899d0c65882f715ee6e93f28bed2679a5aa7559b3ff172a82a96b1282f642d40fe5ed612e91680c93aad62000baf15bde39069cea3150173aef8acdd9c97784c6adcf93b5dfff574021eea110f85e88b446ac170a5293dd211ab2b12ed2aaec3114f309c2b4c13e883d91327ff45e8421ea22af943ddb9c214dc95a94a7624dc40faa378d1ec3c755e25c30329de3398ebe0327ee892ceff16b5adee16afc3806f8b6bd25739f23907521c06e90320bee22d3f754551f31555653323a9220070efa5242b183fb3365a7d8fdfbf8a95a3138cc71673b2432bb2b54ff07c12656c1bc10066a6a0120fd437e91ded602f30e13510dc192d657d4e6244189caeba892edda033d490732f8fd562c1f85dbe9310cd7df7b12cec8c21cdd4c9995de536055cabe1913b4d1d991e154b977a19512cdd7427b68c9d53bb59be4fca63a8b4d4fe9ea2714ca07cebbe733bf3550b1a9f62105ed08e7e1b329fb0a9c4cb7ff26a9470a29cbfb283543aef326aae261555569bc0efac348f9677f67df59455ac5755720b9d611d1b571db4b1086d59b8ca443fd1eda4197616bc8657e180ac710c3d8f21ba56cbecdbdfc4336755fc215f552fd1693e37cced71a8f242d3328456d0ad64f2ced4c33dcb08fe1dfb71fac591a99c151c8fc9017b175a21a04aea05ce380dcf14aab1ecf42e4487a57329b73f176c9b87ec17108fecd0480ede99343ca5caad7826f11d8903cf63b10a1b5c2b917ce04001eee1a7fe8aea78e1c32d0cee443b16e6d785111ea8813960469687e1eddc4d8b979be86295df22e6918045cf3e533e9f93a100ac582e3bab559e8bec866405f9d55369ed8768448232a148336d1253f6d3debffa81da1069eb1de59a8d955f8e00ea8168ea8658c16fd9ed04892e7af2f523cefff25de209e56e9c3b4c448e7e32afb5718778d306a9bcf61e6677aadfd604f15d433a567671d63f43019e94204a550f4b356273e5133f0aa468623bd8944236aab1c5e19c449aeee9c620bd057218a9c6cab1e1bf80d0e8a6ebe48a0eb35204d2e951bb9c752c71acc3259d5567c8f82babdeff387965e59caf39c86c53ef1f400480067edb79b70f388417f09ee989d306f35f8297908629f13459d2094d4c45f252fe6eb9212cb8d77960191d71191c03fdd7aad5d5aa7afa696e89c99d120179732aa8a3e6ac933abfae0606fa0e078f761c1f09f6b0cc64f0f8e167545bb22232e773fd268bf9695837f8ff3fc90e0741b4fa71a50cff68d9e460e851089ca1cf66640038782fd8c80738ec6dc1855f74d0cccabb4d84e5f2af83a98a44f2075759b5c23745d0e33b730cebb73ab9ed8c8c6e0bdeb4996982b13694d66d6bd341074e5b7e56a1466e559807ba23c1a2e2373b6540f3facc01aba690805205a8f11bfc74d4b2701688baabf495d5b87938f0baa45fbac3736f024880386b85009edba18285e07a8b46d7355d246a1bf846934a464a611e3a230512fff853999127e3ef347f88b0c563627502fc3de9b11a4a80e01e14fa6f1673c824eefc0b7d3f1aa1a8549365a63a95ce8bf166b8c8ad282a1d9a5b06f0132059517569a5a8a2f7b534f822c7315536b0a88a930a3137892a6a06c86d2522e013a4eac9832fa08ac24e462417e40a005208eb86b691d58f4c8bc82b1b75fc6d07b48f56854db1fe061ea5617dc1cdf3417179056abf928eef12563a60360d563c9daf969cb9737a525cd4d974592f16594a4375480c2eb41978b41e03fb48a88c0f2daecb9376ef900d4d47a7dbf7d1e3f7e917f889a3b2b4710e595d386b1e1a0680de56021a9487e3f9c613075d839febf48c5c3744ec44fe6552b11a6fd6fc485290145bd831a9497570557013b7067486df6ac1862cdca39fef15d500b7e28c2595923735bcf4e3ab95509bd45e52a4bc3b9ffc38d25d3e27854a3ca8e4e26a4866eed9c6e3604fba0db65a6c91029707e0067163fb52f72abf86deda90c7de18237fd17660ba92fc427806ed598d4a3359729750abf70645f8d3cdc5634d60eb338bc2281bd3710fbccab1247cd3eb7963c9c46820da15cf66e1fd5cefd9ff7f562f4a72aaa15abda7be8dfda8435662052e36ce1d1f4fef6c1111650267f682402ef3f9b97737c5af86db8c0c246ac0fc99c43995eb8cb5b4353079463af2a90b865c2041a111916ce0830b195f0a00874912f3b771f39631836bd29057b5ad4dc71ab517c396cad82e9d31d86a691e82afabab71340dfdab0b2677ae437ce823723902a3015b00edd6639b1d36f669c67934554ed04e7beacdb6372fcccea641ca77779ebebdd308ef4c764ab61f71b27caf467fdcadb2d36bee49f103c4df75571b4257c2cea459c15a2bd52f2c2bd7df629ae20c9ab853f25890be9dae831087d2161291cccc48d6bfd4143d19bad2922a20ac0316f29b277dac55017002327b050ba836131257ab0db76aad90d2a17183901b325c6cab9861d6b593aa447dd3ca07ad9e4658af4c51d782308b1f83e892b04c749ecfae959d68f12150cf309f90e8247eaa7234b68025fa40728422af00ce7d27ae78c646ddb0083eff09f72450b0cafac998427f618cd1715ff8c18ce3205cc47652c2c993568a40345f5eb72779b0004fd86eb2c1764525afe3d44d1e5ef3add97eb3ace00e9347afae4cd5f9511c33f807362e6f855098d3aa38bd3d26128dbd8df44bd80fd95ebc643f328a62e1a2f2421642a0c95a7eac4e85ebc41002fa5cfd2f7b40de7aac2c749dc7b65400625748c463add68c337ea116fe396e13ee1ab6ca65108bc8427dc1ee73983942f75538651245b4b6ec931413bb4c13a6bd514943fd65dbd27171183f6aa2127c8449369d30b6f87c8142f7be3fbcf1ead38ddf0c6157b0df3f852c1172663c8de5823db9b351429a6b176687428203e0315bfde28dec6596b15ed03be9c8b32fa4616dc8424a332aa680f9b53a1502df5ddb13bee822c83ae4fcc2f332e1d3a57fd18a5892b6d5b8639bb5f1813560d19240c3b4810faa1d8bcabeae02916d83b83474920a261d51bb09a8b871359647d173a61cecc81336c525edb5dc713e8d37046da3dc58687a93811c13bc4c0ebaedc5868ae89f85b59da4b02d751af3e7986556a75119e41753d10ba14a1116a127c62bb841a668642b763eaf92c03386436e1be91e3d10e6f83106a446e96a338e2ea698738bf6b8b34349e56c0dbd9d4994f4633ba2962f0cafa978140ad480dd24c8a521004c373f3797faeb90e1e9ca4b3e4b5c470a39ceff250c59248714e0d0d6e8381986fce4c510c3008725e6c1e6b04fa9dfcf572e5f1a120721472dc512c74d2ae082a4942fcb8dca9d6b3ee1764a0f3c4c212a8e30260f3d098575b04fae84a67f97e5fe89ea784a0279fe26f784848a2e4212b492bd6e24bd4cd5c14d0cc2a3616202fadbc1622e94bdd5d7c85f9b88c9a0e8ad273f0591c67ed80e626bb1e9135e137a0a49ecf77ff6cd310973c6500ff9bad5b5787b15e49179987fa88f077fd12e32e9c04cc217b587fbec7ec833d02a131e705868ae52eaa64d83aab95dd497c681b0d2c7e502d371e776cb27038fa7b094286daafbadb96429dea74f4d8e8f8e3df30e72dc6120509f172383d77d235c20373565d566be43eb966f8121fe23cfa3824e1ced9bb1091631be68f79f7a0670446479b462817c02eb3c32172d057557dc3172b12dd868099eebafcea2e6e738d213108a4faa0a7c30d39f5e9552e42477fc0704b9314b14cfdfdbfd6854d00c2d4b08cf33c52caea7c264433c68c41c6e80c51911c70e93f9c6d338f50d19965bbbb6e6a37167c0d70f2669d5d2feb413f7245f90ab387378d8bee0dc4a55fcb5760f235d0687b51b9e2a66cdb9a2b76520dadc3b58b101da6092bdefd940ede63791edcdd93bc71e82ba79c579e631e0760cba65f32417d977cd339660b4ad3698cba07bf385b0235fe192739f05167bc26f23b33cb891db00ab20dda7aae49d8eafb080829705033f324db6eeed74e327b98c736e8d91ee9c98ada6c686ac3e406771b287264a3d0e53095b835c723a4dade439d8e51682676ff8860b94499e1407dc1d523e8640b7751b2a0fd376cc50730cdb65551bd5f64dec28f7179538337346787c68e0ee50a240d563758714b06ec483e2486885d943d65ed65292434b69ad965271caf1f97d62380345602075bc3a41419cfebd0d88a681c4342b9b7753a3634fc19d561ec449598621227c032591e40ca818edcc5dfb3aa86450c4416c3c92a20f48bfd1421a9b208f56ca8cf8e58278c6077a3f10ba37413eb63783aaa089b217e755692f1b0d1e6d7bc4455153b60d3e8bb420968181183967b4b942fe0db86b1de6a7c1ac63dc9c4227a5ce1001950b6bd1c65cc8ad1e67e3d664a06bdbcf820e3b473cb09567a9d6f93bd24be0880165ecfbe206fcc5fa3e2affa5b682337a7c16c377e9c65500a7119f621220c68d46d55e57975a25b5ed45184d7c6e1cd95432db77667cf31cc57f6f2600494c1fe51be80184222db904ed2861a59e36fb1ace1ae02c068250ef9f4e51c2c235fb2f94b3b00f7f1802f962578723dcede3ef035980bccb6d38133c7936b2e3eb49ce18c9f2c0965ea153260e59136ab627c154b84c9403d45cc58e882e1cddc92add480a26ce86b8372848c3ac8aaa2e1c320220b52a8eb08eae20ae68aac994e2a88824a5eb791afe1447d443447c15852a16dac0f102f6ec6cc8832924983f4070cd22551e7ebee86646707b7e3b1d7fb08b816a7f77a68d9e28ff43d3264efe0497057676198446de33f15baecce729ccf3cbc77415a5d64af8d3a0dfac6d0c042ec1c811d7ec4e973049dc6a0eb9e0b04c91ad08fc7e0f4c08df76e33986c8da0c7a97eabb891fd996ff3f02e104b5dbdf9b7831c0326b8c48956b200d8b95ea7fb30d3f345a0447c39a3a5db56a664420fbd6e4896346f4888653f7d72e813cf8174b385feb65679c62f0f95d0fd3f88c4601165cc71f47d43f3b801a4ead5876e08903b2f523ac97733e25e5f5e74b757406165397a0045ee3b3eccf8513ad704a08c732c921aa775c7e36c469d20e97947026abd1778493790367dae22b9d70e04cc6ccb76b5faca3e5d6844e0bc50b7c2c910c53cc872cc5c010874379875a83cf0abfc92e2c4a57b644fa0ab02ee43c7e8cc328c8ef1cdc04bba8a29c0ead76d08963d5d089c0dc31aa4968d634f6dc803b7f7ca42fe10656ecbfef6f7f98faf7024a6d5f5049d3d16a97e23f366243806c58119d2b20188d0a9a209512ecde8fb4d6a1cc3b3c12ea4980a19eb3ccb7b53ad52209856cb5ccf01b73e420ec7e213f24e47bd9198f4509f9548ed496250749f49dad347e4043f6dce0b9735826d4a554fc043be7dd4d722b61aa64c7ecb3fca0eb59da5ebec9664b5c3dc4755d2647e4d41aaa9aebe9753804e38b5028096f964fed9a5c86c68c642a3b04a10df45d79cdc1a4641170e84b07d7067b07efd713c11c3c08bf401cb185ac46b4d4f50d69c5426f180f5ea342d7cc38e214d112c21a5b2affcaab8970480308cd3289c468b83a4f5b8ed822e2c2bdadb072aa5e8e3c1f399ca90d1de3f4cd9a00d474ac42e05295c43e51cf4320db89f78a8a59caef7f688d9a20f42d20065d9a19d3db3c9555da4680ae3562a7e2b612b7d7df47d5e447ba40e166d365a9a4b61cadbf99f16c3b75793b43751df1fd5547fe2fe44ee3824e85a54f30d0fc0f0fbaabd656a87a197625e1724737c18017109960a68f042148cf554615361fb46909140dd9ba5e0165279211b909a8d0eae444f9a099751d0cd60cdfe4b604c26a05850d247587165af630eb2688a9bb0e86a23aa7370408c4fe739bae2f1a63ac329eb81d076a8ea3f57c124d6355b44f35a2875cf390cf4f62a8bd1ddc14b825e4bb18a4fa236a949cd991efbd6651885f9b0caccf96c8e504be8cd53c722ab890870816744bd8ba37b25b59503a92ea64c13b296168c9d25de7104601d726e6559e9eb466d1777e1f79945b63eabf6ea511694d22d915b4313b9fdcb34ad9c2d9d5d450fcccbf82d1431bd991f9dfc05cd95d4fd877473e8b5614859ece1d79584ee30ded1e5676c76802679b69fd6cf9c9413feeeec8fff29c5b3dda9972977bd7c096cfc83ea76475790f04a3de4657ae229b7c225facc13ff83a6599aac01e25a8697d487febba0df9bff4339440462996045cff0245aa5c7d69b0dd04af41d0e238f92be64bfb4624970907077d2029330b864ea99315d9811858d58ab58a404bbf5485161a1612bc47e6a0676e451fbd209cfd024146d456f39139b50cb5f7df726be05da8b039bb246307542316fdea80fd80ceea02d0657ce2028021626f9abe05757bc020cd009a7a764ae70e82a917ad45cd24a193cb048c8d14be9445b63b7dd5f2fc084f309d1bd9b19d093e7b09b5cc927894594ab1a683e0d2ff510e7c414e30036c95d308c5a04f88036606ade109e013e48019612b30097e425c282368b5db048b031f201798095b0193f00be24099410b7002fe0638206686e358edf16bec1afd8d4ee3b7f16df4373a8dddc66ee3dfd869f48dbe46af1adb0dfa763665178128689b75b0dde187220af485a14dd085f09939e78cc17a9c3c7e66ce21635a4e93d39722d798395b0f13e3e7c89d330d967372fa9cb863c6d1324f18be47ee9861b60c13e677e2983246cb71d2fc19b8c6ccd162e6e4287d2e73206724d8f2a17f52f89a38c6ccd1629e18fee1e0486e091b4853770f65f1f97be0ce8c9375aa895121d573ef05cd9e76df3799ba14a82dfc5053b97d38009eacdad67af1c8c53e1d92883a5ffad380b420cbf229f56115f427e85bc3d2c87f7d1f293b507bbccd5667587ee23b807c1d60933f0325954c18f2ef11e725acc54a522370da8206f7435630a88d778e2b60aab653263f866d7211eb82031e018b8231098cdc8962bf5d82a539c7ddaf7494f2972a24d1c04eb3012101a0f3043e2959d42764987ee2f5d1e11afa4ed1dece78d48118c83b8991810f822692371e713fe66b6b2912e3e04a4fadeb90544590d3e7d18581f773e1e56f61641acc0573de12d6af6a0d78a59f4037767d25e33606496cdad5a83d215993fc27270dd6d22ba7927499afe0f3aa77af935be6fa7680965757c7cbc57c42cee0ae6c7833cc01d6b72eaaae5180aa3e852b02c973a85c017094b6d0f05bea797a0247fefa4cc50a5dce370471b0d9f6d465b6d002af78089e6aaba1dd5c56279b79c0357350f8a2eee06d2084140c4b1d805eb670f6d3bc43f1e54d8b391898aae102146e2d10e5e1cea833c895fff409fab2b184d9802e5cee2483544d2aa06975b483180a0f4fcb2928ee5ab0c1c775182f8ba15a882bed8c18f54483fac31f9bfd65d67c7d3c56f49194a88e742f9a92bb641c829a6608558975106ddbb6dda87a00ac68a70d12e5e061d2ee0674031d836b29db95eaf35676312dae63eab2f5d48c9ab1840e06969e395d80ab766927e3475ee314249585a80749d980c51c2e0f1d74d63b9e6835859f47ede59bb2c80814173d427d94d84741945cbb4f7d1a2a3bfd01fa4b512d40e1e36dd8c0c05554378ca76f78865f39458902a6f3071bb5fdbe7cd5f86e2511f5b77b2efe85eff72f5f65ac020091516dd31002f052b539222fa2f84127c6ad675d7b505b1621173952d648942805f969d7e4a3f8bd9cdf338b5f42f5f39a80e50345f3dc5504707b78ba7328379374be3c4344101f7b24e78ca78f6002977546776606c0e46db31b84757f95e10959184830e28ce5ab43250401e3b022f7340dda7db83bbd4d4e81456f6f82d1240da1cb35d077cc185d141b1c7ceb204581407cc6a52c813b618a04c7f806caa8df3d09b04a1d1ba57154867fe95318446e12aafb1b0d77cdc0236af234f7724a65454ab80c144f3775c92cc15006975936124b1c7cd40cd6c4ee3013195452dfa1341cb50228cfae44bd87812e15a0e514f5ca09eaef397dbcb84a35220d686f59b634be8810cc306c03170223c95054b99b8f33c1b14b20233cb3df430a868cb390c1ec34d3644fbd6bdfaa555697d7cb5cb24726dee2f5ada3c4da530ee15a693ed31aea1bf3a5e937adcfb1d68ee301ef9e76773c0674bad3d45a00adafc360214523581fecdf8464a40481cb22d541abd2e40b8ef0c6b2c64f2bd60ac3953522a148321ec7ff786a5cbdda9b39a791e09dce1fdcefbc08bd787c5aaaa79ca9602cd6e0f78b959163bef69198ac99e8cd7f9a8db0ac46df5df67960829408e2fd72ab370d786214cd223374d57cddaa898df01f7dcab64767004b9bc907a697cf090f10447c46b41506910d18ba78b4920dc3924dceff96b412bf23688150348a0d51cffd1d413b1aca1f142394e3afcb46b02a8682fdc88b15ac753d5b6f2163a9e5b1c492861478723700fab7aef7f9c3c7859627491a888feef4943067df6c42040a9ba8c93acdbde4bd03921b6278d39fa69e13dad6ecba0b7464c6b70ec34b93451b05f4c4600098e8b600b4681109a2c7340ebed12e62b301a936349fb7dbf7b0e4e7cd1ba284a71797a4dec830c731b37cb65adfc25e8fe8b27221b9debbc7084b026a1d9f53b0cdfa5fbc066d5d2d97311b27c8ae715085a4cd42080cee17c742163be37a9c00495978f9124df0075a2d8d77b3b575c96568a64a454c20c733bdaa3108d771c809e8d4a577d566f6e071f2e68ce086978f01af5eb88f80c990bd1154f98c607d6944bf6ca5984a8f4298491569d55942a62734836f07b17303723ceb98880cd87f3e0a82094cab9e73f7973ce217cff33510839860ea318856af9d58f16ad101c2a22d722e456f77c5b5dd27c5e22230a384496832e095297502587130b11645065f821443d336a74f310b4add2b779c18afe515f513abb14f1aef373d4f20532f612bce97183c56da0fddb77ece6313092d8fa1fc45bed5fdef6f0dd3b68b82024aaf760625dcff38a9a49c8c74d0cf8e1e574ec4e614a12a01905863b599398275b16f12e3d997c5049550196a893bb4a2ec5f5b6eff2bdea83b986d1e7a94145cc256979b5f956bcd9767498fa281c368248c8ec512a762d76aaef4ce388c4527d7c7b7fef2f5ca4de219be1f66189ec17409a0818efec1e3b1dd77c15a05bc63d0031041439487a46bd48e9d05a352734f3ba923d262d0af8a69ba2b4c1cc58055932742516311aba55c8b8196837e8622e1fabcf89002496e234019ed7deb3440a910209f8917e2ed152aaa86ed67618de87795fbfa3cc32e15aef20c1c46cd599cff0b030dabd2daef1df37ad186a75e2ce2addce2ae876876c3c0b823b30515f434243ce88baa3b9b881a669ab4a75630a774227a03a866ecd04f178be1122d2e99b5e00d5e0d343620347ad070e341209ea49d52914ce47111c809b802aacb43b88bfbb9a568211df44629f642ab2401623ba348d3ca60ed8e619005e1b9290b08e04b2ac7213aa7440a30e903847d6eecd39f7580eda3ee9dce0ec220c05fb2c51dd34725acc8ce054226c6426e942f7f53f24efc8b1a4605fb4e6b86cb4d6adcaa8bf53a68512b1814773b77557d8f6f32caa4370198910a00f99e9382967342e214d981edbdd03d83b91513fdf576152a2e7e70500c63f1ee95962dfded85fa3047526fd03398c5abcf3a3e1f02dcee7dd60d1d2d0884dd6631de69037414374dc5667e1180790e46449e10b637610e78e4600cfcfd6c7e1207300b0e1dd01c7a1923269269c866f8b6f075f3d5ac56904516f9ee9c5937a2002e66a0ff7c9c4c18bc9950c9a489974581e781d2779628435592d6f4b6b998259e910b67df459514a19f9445601155232449e94d723a6f9b92ab9d6727430c99c6282bc79944a43f2aa476efabe79332e9baff489d26676a4ef05f07964008361b60cfb10114efd31a910886d93d72ec3605762f722b55f26eb894e91db206d060ee4ea8e9238b974c465cea1c14a6fb06543629ee1165a34ce39bb1ebb617e96d80219105c3d35e3ddd246e2357a1c893e4f660cea2398632d567c28883d2e87d99baba04af7bf04542c24b2058ce767cc05232b48ab9d61eece5868b05533401dc2ec8bd7682de66ab711e89848bad04f56732803b2c3fd76080f299e7224e05b0a9fad8fc250c608b29be452ed8d8b6306556acf9f389a1270836a04413609a1d1ab6e1dd5d9d235057235390fad6faf8e21fa6fa9fabaff3fe86ae8e16ede74fbbaa32f73ad664d9c495ac98eea0e279ae266b9be1be102fd47c6f91570a91938ee7d3b926e76de3f3d7ac2ace80d4cb2d12a735769c2113368f28b44ffce9649faedaa6b98b7469a2fd3b5664608d66d6154ce85e4330bbcf3abf9d513c40d3d9da9801482019abb7fcef4e749ca616b0978a982d3e9c866bc8dd77f93f510422bbf4a5d47502761436d0d6e951822e396c3ea2f8e02b8c2b14d00bbae9f9cfb97c9d4492166458be0e3ca2e31a663b048bafc4cab50015039f67ffc3ebab48fd1ad68fc7106b4f36dd6f69e6b75264fc11c5b0c791de3ce77f9ba2b7239feaf4a87f7afc0bcced94842ff29d82192cf7daf49dcb93c2028b56ca886177fd293591807e18384558ddb900316b04ec3d623754250071fb8b6af2a4d87d18fedfdffb807cd408cc80f380030762e57a05d375b465a73f5fc798c68f54b4e809a4cc519618f7af1a468e126ba73d1a07906addcbfac2296f0d03de079189249d13c7377b9968a38ee2e0a2f6413a0c8df57327b2ba920f7595cafdb8fa79b58ec9a0ab4f5604d95ad1f697ff02e33c5f76e615fe588546fe0645ba6d1af5b6ca803184912f778b0db503c8e9b44ab8f98089b7294ef0ac01a60a4f1187a34fe4262909f561fde3d3ed1e6a5283c15b9964b198444d106e50998092d80a547d492e12f41a0c023fdc80ee111c946dc629081a3cda10a3b1e85c424ad107a4f6dcb99cd9811e6b225721990f41add00ea8b35b85387e09132f7e05e3a0ac49c9362a2a56b63d10f7c511b4c20250e9cde6d876114608e5d05eef5fef43caa203487dc81f6b805f97c80a0569e57b159e399600614a1ecb84c013930e49a7f4a9294f5c0b51e682cdb43676ce46f299b344ff2a8ec0b8dea68df35a3b10d5a5498a561a9f73f8679c7ad9fbf213321144d11ce971fdb9001581a8274a7003bb88259a7079903779b33dfc77dd5e2c4e247448261498158c4a6e2cb858a29cffdf2449b5f66584447d9a0381680f490accacac57bcf6c0ea0815357ef47bf7427a59d0de069675a8c7772d4ca9b6f6489b1efec5849c33ab7fc125cb50656e7ebecf740ba9ce542581c42a27bb853517d283e0160e445096a44577905f4b75056f4f86ee24e71e7a26fcd67309053f7a27cfd25d5e25cb7e152b2593e52db08390b96e867ab683cb6bdd64de1fe0f3d226486d5260e21c98a3c1fdecc460aa4589c9c7e2fe5c773c307d0a126c0ac11a4eb59e36214f7d9cf588b5da94fb963e3ac5e1690bfb9e794fa207e2a37bb1c92da3e0c611e02c73c9800c8abb6e3e96e9914e449d4ef6a1f531cb9c04b8bb0254dc6b99b33726e85d243dbe6afe96bddbb7364db8f90ddc2c55c56b7a8787cec8f99c2fb3016637cce6cd02d4c8edc45dab90e92470757516ab626ba967a81fd753c543e85630b70d7af61653abef4c880877201e7686d60c24773bc15c9a8fd5b4a2539bf3570ed1794069d31a044eaf39408a99bed0502aa6de4356809527399936eb60edc68b1be4921f5bbbd45c5ed17540886310de573e9024bcd290724386bf95602391dbf5c2a3b3c232d9d38a67422c80dd2a46f04b2baacd4b89c52986a90a5bb3a5e41740331f57e28f4acf81b60769c1e4302319c427dd1856f10704a8457ea09a50d7dad41ea1b7cbfbd428f3c7c2baffe07804800bcf8aa33785bcc42c8c910bb16217f466c237eeb8c9650d606240dd641329c2c4a641807a5cf4af5c070b261e4e3b3db17af687e2f946db01cef665837df1cb5ae982815b0e072ccc5eb6ea4820a553460be7344177a439c41d4eff30d84d8c69b360cf1dcc3c571b4b37b685ad6af7e7f30a7cdd4bedbac884bbd3b2d6eb4dc4d68779abbd387eba064596bba0c1765171b336d0e6c69a4be90223d0bb6f287b517b76b7e1ab65690b6e9cb6c2a71722ee3be095a78b3d4187e205c97e13b2f9212549ad7ff06a72ff5d0c7755feefe737097489ec6006b38a3414c8a8993f9f10bf5a5a0e9904edfc24584a06992e6de2c6738370cce0ee463df57aa4b77a59aff5ac9ed54fe137e5c6bd996ee8e6546481d5baf16fce1b74e3bdb6f7ee656f0aba21de6b1c017d88b85367f14f656a5b0bffda996dd180b98fd1d16854290a47847a04939bc1f9f6081828da7351104faf120eb2338274de14f873d7a7a969857b31957f1e323c82184b50b6abafc0b446889e2712db8579f42dbc752eec51e7a1bec5de6a4b7a27f678dceb3128d7b4a897e49e75b84352cf4e3d9374aba3be893def7c9f20fddde25ec7a05edd92f6b385b3eee562c585c2df2cdf2f41fed0d2abe7c2e2fab2e297e15907cbc50aa3f4e3d27d89c21f593eae1bf0acf657c5ab7a710dc14a736771e5cdaf90c060a5ce37dc5c518c66dec5952957538a577ba599cfd2cc4bb43206654af16aacd442b19a798b2bb9a8a614efed95373ea3999768259a942dc5bbb1727b60cd0e45375756db89b7f24a54fcbff2e8e8028d9f433f905edb8b0363fd5fffb90dddfe47466656dcf036f171a95ec15a377913e0e325eb8a5517b4dfeb54330b3c9e039aa3aa855b3052aa9a74ea14ae59b46ba470d6de23dbb6e9045638eb56919daa4b16edde55cadd2669b5a2c429a0621a8c1a841fc2100400529608e28a8c5c5e1ea20781e27fafd7eef55e12c6bc4fe8b1caae37b3b6d612a2d636217b6fb977e508e4071a08d3d5c93063a6f70428304134ade323c3ab90c4aa726154ac90fd26ee31c4ca02051e68c07c56a746a353a650a1d2f05a5792fd581347f922b613726c3134193e653493647590c28e0c9f2309f6abadec35b9d845c49f179b712d57aab7c34b21c37755ccc8d0c464f8ed03351226c3a334f003a10e0a199e7b7951c67824938ee24ffc9155bef803039213421d9ce10f6764bf0afbf1726e86af49d8afaa60049d3c8112447342863745f9a123c3bbd6b0e172d4ab65b75981ab11486402f6e8d33bd12cdbddc0cad938f1c795e1332e70b82a71e28fd59125fe1870f3848f8ee943391ec77aa932f48c92c68606f6822a00e00a226fb644596baae596ba6a3031352bc4d4acd082878367430cac5981082d980183e3f52b9b34934930482f47469c0d334a22986143110e36dc60030c830866942237ff2cacd79ea4b36389b2560abb59aa5a666a1f1d4c6c73de4c2980f6726d75777777b79431a3b3bb71f79d73c6ee8ec51e514a306c7c3f4208514db8bdadd37bbc72856d430c55518ecfa109413e44987ca802473ccaf111c9a7a5813a56ee5e72779883dcbdeb7e1022773f80e1451047b0c1858f4f8cdb5879dba8e46dbb0d236f3ea4206f3e4d70d00417b090b5c336d0c828ed912965824fa64cb041a66d84e00a0ebc9a1fb478811442190abde4243cc9f2564a964b4862038d2ce5490a1940b104a38d04d5b26125890c18174c36583093e784a922724ae8000e9450865d2204f2473a401cea92e59328813ab03f293e225ff211952f0db046fe88c421007cc93560f2d540470d24a5b5ad96ecb70e2c0cac819747f2104e2c7b0b14d8efc740a687e99b4803b38940e4696d8f1e666eb6daa3ad4ce7040206eac82f85dcba17038170b1c7bc95f1e6c09a590f03759460a58c929bff4e799e7042ab65ed5773e2ec0e869929868135b1f4ec26f172b64d579c2c5cd77ca90d3b7f8900b9d820cc566126550b2c0e7d13719ba065467b1ece0c4f3ce95c99613fd86bb5b6d592b03c3529365894bf24b0cab550a7b521ad8a7603ad8a5625571f6d48aba255f1628311484720df92675865fed9dbfb0f8349b0b6d24674a8854025a5437428d3a436a24374482bc106fb86cea6d995f436195ac928d7734a49a59452ca7e46339a6536349b369d4d2af31349da858c8c12d205ec66dfd992ba9c4497a3f6baeb4f292b97cd3eec322a3749adc8ee6e39a7fcbc72ba9e645795bcd3f2ba1c7541a6eb8926a59cb3bd98156d104688124883d50f5e6dfd633769ab924e94ecd47c581626d8278cd504ec8985524694230e803a992764e8c29ae8cd63aa0242424242a236dc0013e20398902a3f5202a56f909090909022ac616541ac0c3c071c5720f08aa214a1a13caf0cb1c168a9a252b56aba9d97e23830fd8a515830b231a2318ab245571e08a2427a91e50d07884068769a8e3d22cd545f1401348f4304d234d3b2c841ab2a14d4920cbc52d5aa66da81fda051a402eacca01ca240b1816ed92dbb65b76c198dae1a72c2e0c0e0ab6f61366258179113904498bf885d432ebd7bde943833d6dd46466b7777d3ae5bb7d464679dcdaa6514d367df289d31993541d5ea49fcd25efc8095313c0476ce395bad5ae984dddcc056fbe6dd314653de24d7a5bc4d4e526ec21a9ae54de3b2ca69336f25aecabc99b8d2c699386eeb60e84f53a96ea7086be067d10661a6076b20aedac4301bcdfe699aa6497a654728259bb3c8890decc0892644232072e287a8893432972191134300b52a154750031809694058102106900d8608030968074700418a15238080046486116140e9170c324018525a5fb4b42108815404243c1a08f10321bc60d2da322412628bb90358cb22592003322db0801416f0895181dd32240a228d9717b6cb902888a3208c0e932bb89665190f8ca094d23190fa05c748aa3efd823ffd82325158ae06444dc0c0082b55e4cb5b020bae341124234413506cab89272e2840c05a9adc618433a0384a810704bbca70e88c19c83439a307af32ce08e3c76e190e9d518433d238425249ea17f4f9017232001b3ca12f1f9200449524606087164970923d20609905329a65b406233489126589295714418a200769e4e00a1499cd3ab0840c5c800552e4600b1870f144e3828b2856605944404de45386444045e4cf7e8600a21184d08f1292f82162090330f1092288688827800c1044510928853434308411c4f8c072190ea521254a1a53764c61b30c87d2c8c11a6914e5c0b41ae3bd980426d7971a6918e1215c202221a55f5068a84a919101c038a23979280d339a20c1450f9080381a52034a9e191201c144fe9e63d6c46b85268d6a9a0788d08630541a011199107caa0a8aa05682b42b8698568a321a1021855045be604dd2c18f5012183f0308824f52108418c0109eb4981cb1321c4ae3072220903a0ed4564ece6896d11d32b02c19121921450b234600c508275e51ac29432223909899c2d60c898c18228527b6942191113e438a48aa7d038980e0402bc321352c906986444c9068b083ec8b21542a08c328096900d10408d90b8260d37082524a29f5a10645449c0f42d0807641240411911055e45586444240c9c0061be76bd958637422e67863582d43a21f1f3c614b39466984221aa29f2d887e84b2cd90e82708099d26c020065460618231b6b002762101318020c6154a88220650aae813642ea0598d28d410418449b135c321353a60a5e529cc73d639e9a42d3bc3f0cacf4b7b764398bb7be291a452ca86f204310a6a6db568b4c9df12aa840f980fa9048da22961e3db4a53ebc369b610c6186b8510c28cd65a82b07ea74bf7eb2157ac69a734f6a8a7771a654a9f4d082b841aaeb7f488187494db89510c3a3a12231e5589c10e9c32d4ca964a14231e9dec91ccc596b5167f47e8e930ebd262eded6157e4845b495aadc3c0e9dd5bdd3bddfe74b256d5a24552743b9db6abeec7ad4edf4eaadbedf6ba52e75028570a0f3992ed37fc1dc991db56aa956d657bc2495a79d5b276b3276b397bb2f69e77ef301175d90531ad5e254e8bea36a53aac3dfad445fd645d4e27a96ab5be242d7b249fbe42a14ef4747a94cac4fae9aeceba4356ad2399a6ae32e1242d4a8f504a69abd59242c3913c7f4f4669e960043a20410ebe324116ade4562283f84bc2e5c943f6194f5e6997b48361e2af475e6d6e938bf2ae3e6f5744c5624d6b97b4ecbcfac7d2e4ed3798b5c7169c211e8b1aebdca7655d6a3bcad5e567afaaf638777ade263a224a86441e18ca5ff421f2405046d939ef1019a1c6d98b8a1135c48b11e3af872c53af29db4b7872fd112526539b5a08cc8d75e0c94b622e9d967209efe4d4c07ef028d325ddf23e7d9276a1e5f44794f0b0b9b0d8b05fcd137a347449cb9123fb459fe8d2e28297b476a54a9c167c8408fb4558e4431bdb108158ad830748d954aaa1d6fa392b2cbd5edbd0b0255c85fd603089e3daaee5489caae10a68e7b473d8b51d265fa68a2319b0c6749369337da7ab15ebe1b767fae5c242261cd668d8e9d4116ce9dbe72b761160bedeb56123f334ccbb4c97788a02c0189379790ee7c897e93058637a16b604435ac24876d305c2f4cd74984dcf3293e9f14a1384d2643242c34ddba0098394601f83ba54a511cf8305b5621739d6b05f0705b9b8b05f0cb22252d12e98b029c3a69214b2aec0d22089bf28f412d4d2500029cd32186986a70eb209547b2624871cd8c6a15a39270b8311c2881f10a3917cc9d2e3c53da55f42edc94fb902cba47fe44b66b87ddab30200f892bf425a6e47ca1848338c0e2437f87a81a82fc5580f733d84b53edeac529afd19968f6cd8cf47218993235ff252ca8db2b0618d5a48ee0c657767a8a83d9823cb1af68b463627e7cb89460be897bcec97b4f25808b6d7b0972b38326219adc07eb625676715daa8264cc861613f2e77e9f51c9c0a308a9beab36f56577d536587dc94df917d6b3abdda7b755557a6306c0f6733645f222f12ed11ef40b3499b37562b1c93a53a0baa57f2f3f26ed9252b6a3ad4bcc34b86404fd62e85c0ace1782a6fbab2934175d3a5c49bfdbc29752d85e54d513b2755df698be52546a2ba09236149bc446615ebf5dd11c95818ce4b16d4ea73b58a744523356997a7ab29f134e16fd219e349c618a3f693fcbc277b59f1d7d3b7f6a68baacd5a5d1586556ada94f3b0a39485e55feaf5a6b7ee57b36bda670c167f3d64fb1bf47e4a3255f19055acc3c669b1f0973afdeaadab24abf08704667b61961d0c31b3e01d2646328964d7aeb14e57afa7f67e3c6493bce9daab6eaa3e753b6b37c99bba260cafd99fbe25d2528c7a4569d9e3915ccfdd8f875c67d681874c7db2e9d9e9ac37ddd2dd81568c645ec21bbd7a02deb8c2de6897da391b141081c01ca3ea281c2916d1135572e9b36bd7ab7a765195e5d46ae138b1a42a4b6618c95ccd194a574d24594eaedf2267fa2667e3c0588643eb829c2e0fbb19703cdedc48bd85e5b55e53ffc63c0ed527fe9278a8cf241eea15856754c9d093f56c62d7eaf328d5b3ba425d7595eaa2eab74e6567611b346c868ba48ec2486ebc9378291c6f1029ddc02d38585866df49eb1b3559e6e3a90b72fa9ca8393f3360513f1d09d667f774d6955d90d33728a5f4b2d56a7559575d5befa26ae16869c1d1ab54b7565917ee7476d865a5a350cf6cc8cdc27acbadc471b93cb1d81676d9e17ea96f6f8c2423523a0a6543669d85bf245e5f55cd70afaeaa73bd5f0a73a89f90d4a3bad6faac7ad945827ae7c6daed4e579851f7f452a705d990b598a9e6d26737a41a3520a7744bae971cf339ee4b75f984e4093d221460828a10a0e4d96f400e8e6ef96122216501e3d1e4c05e2cab8f40caf4ce6d6b1df6e7e4e453a491461a69a43021763ba158261c2d2d2d1c374c2cac154a754ae570ddd01423aba5595690f59de6b8b3dc387545b673f61c8d34d24823a5f6f4598e7e3b6d1cc77d5ea69c7d8a3e753f2179bbbd9f907cb2dc763f7a14b641c352ccb19c4b92f3519c2427af72b8961ba7e75a386e64f6593764ce701a2267e04ef614638ca79310976ef58d863de72454876c9556d1234eddbea45870b4b80ea5cb057fb505e3b8815930cbf330ea2f3939a7d3e92424c7c60606a3a1f13c1898974c5329fcb5562f2f17b645a55eae4afd2585ba769b3a9d8464d555f84b92934f9fa7f77e51084d5d6b734e54488ea739a79c534ea6393d14010911410e0c511221c892e505dd7769d71064e8eeb6e96e9a39a5940d9bedcdf92d838778d2b477f9c93cbbbba59415ce43082184b0b1b0f15edf05c3cc9d638edddd52ca150864a073ce49e3e1acad496e42fc13b249a79c3383c910692279341952da40e69473ce09e19c734ee9cd39672c452a71b43e73ce6932ad86a8539bb46693d60c0b4d8cacd6ac0d3a6bd6c69c342b514a33adce4cab6d64339b5ae50e4b39d59229a514cf9969b50d9ad579941dd92c7bbc966571ceda4696c11e95873933b9711c47b336b6d66446a306bd5a67d775259ab55192198d1995117758946a7d9c8f5aa4996cc3e462b29964367358b0df4c6c3dda68464a6fd9c41efdee121482d58ce4c4d2cc5a3768d846ce49b312a534d36a96d1c09bec309842f7ade3b49c1cac65da2cc5eea62cd8a314a7a6695d106d562a618fd852cbb21964ce88c879ad0b5232c98de3b8ada78cad96a6698f734ec945d3a4997c74c969c51efdd3a90b92fd3425ee6460e3b78823ac291d962e21ac29cd5caa9ac45940df007bf47510d0b2a1c8b4997c5c6c6cd94b86bff0a646afff132b371b6736246a398116db0c7e3a289c0c32ece0940c5b9e6d6db0f863b9521619ce3bb3ec5ad66d935ca9656545412c323c4b140875e0155d90599a32058532540520c3774d562e4c6929b2df64e9b490210bbc01c4494b0cfb554d27c7aaed08800c9fc30cfbd5b354605d707060c07593e1cb0a554c3fc86153e558c0bef999f4c7cca6f853653287f98e0bbb212fece1af872cf3ff5b22730a7fcc85d94b017f3d5906c73ce6dec3dc2f49fe51b81f0ff99f401267c624fe4e6f18dfcb7b7859f2c539173604fb4da01374c49e03c809bdaec7ae4b629a14a357fad52f2161a7933b86cdfe4da01a76dec265574300ee32e2c2f333e6891828238a50cc85f7c476e4cb85b9850b63aedb933f28d4ba9f975db838e251cb40c3e2a359dd8fa5fab4271348ca513cfab8fcc5a389c50368fe68527f60ca03a894dffa7ad2480b6d5c402816007c172e8cb57061acb5b0df4b3e19a2d003d703a45f58e2b90f757ae4fcfd55582925cc95dc3832347d03899c90c102bcf92a111368e43e0b5e041998729321511257f2869af09008102726c199675c02c4c185fd9a8ceda6331d8d3c3d2aed75dffee263589c21911256e4cdf428c4952ec5e50a4bcf4deed18af6e6e9277b6dd0b0d9b9974ea58e08fd40760de17014eac9aaabcd60061f108aad2e8cd13c69c8d917d608eaf4c82854de2e045ff35ba954aa37a7944da1de57ec65b5e74a61183aa7acf4913f4aacf0a489932c63f3a97b031068dc1b7ca4dc1b7cac189aae2954faa6bbe89b2c0bdbbdd59948891f64d8d1c0e43a3460fbaa734176dee83edf4e248eca9a7092770343c982f1178b2e5ff47da13cb5d0679b08590e91e74e9f2ebbec27eec6a1f66646e9bbcacc0a2091124ff2cc3f4646454748502091123e793e5e699c199b2c325857a816167633c8dc425c5724fa904df1ca7663924b8cafbf7081f5fa2f096c521c457fd13730cf2efa665af1a34a1256a41c8a55da9b309b88058c859cf626dea13312fbeef371a86fece72395be41f53773f7ee1bdc9a333d93c25dee54a8bd69cf5dfb93b53826a16ebcd2836c833a3023319dfbec21cfdce1ac339dbe9ba1c3a58b8496cec52f2ea1444edd3324f45c9472b3675be4b8c89db647a510d93e01a3bc3d0bb97604b0c66422c385c0a13cdfa44a864419a0424786444d8272ed23230b54f90132f1404a224a1bcdd9423e453f5434d013560bf58d8b0623edcd33c9f3f0eb22f2bc227f2ff98b4938cc17cd204b398b57fac5a47f7a3b71d95dd4549a79bead50c9f33d7718f9f8f8f8008990b022e94a9e9ffcc9f3891a59e44f2a416f2a6531a96fbe5894e75d70664279beae31b90d76a54b848abec97a32ddaced8264575dd9d1771d91e893296e9f7825265161a54fdf449f3c332b4d46dfc8f94eea9b2c0679beaf40ae0b925123f8ea8250a23272868d1660cac1a8e298c48124141fa9c2da2e86ce3ac4256425b0665e754b5ff0f0d0de844935f47970a804c10ba1580f2a2d84388a4d91447b16a970451f1f2aa21441e30fa7ba5b1a484231d8d1eff4eac258a4a26f287629311b294f3aa393f6e6a3933cfb531e8711c2f089b5d1eb0914d4c9269d51e66b03e3d1c06ce8945067c6676159edc5576d6641676c1b9d53ceee081f4307598cd0dded75c37477b794db0fdb766d64f2e7f23e578d5b6c9b9492b694b3bb2184d0e64e09d31ebc9452ce9b29e74d7777378b1933c6d8ddd9c60454d671db35a5a8a9b4954cdbc695b614544d2ab1a026163060177a3108c8b22cbb1a8dc0d4b030418ffef480d2cd4c3b64f938e98dd09b593725b69c34d36ac984ca3a6ecbc1d40d39caa6a8a9b4954cdbc699784add90996ab5d412947153c1958eb449b5504a0f4367162fa66cd8700ef98a93cd9291c5524bd544e965e9565abb212518fb5431a52c5a76ba32bdeede012b7f704a6b0651b4a1812a489841132bb17eeddb1f7253a2223090e5a7a4d99338ad28899092270df294b80cfbb5e2df3712a7c2e25b0bdb928d484404144ae1b4b48b4c7f8fa8064e64d99c19064174812c88886022cf4b0ba02053fa6eb528a5525079decce844885ad9204c865289f8eef21b0a36b0dde3eb86c21636c3b300e35ea49cf234f9949d7e3a9d8ef4cd119a4ef58f85b3faaabbb0bdee50da0c7f47724881adffea4dff2250ed2b9111488c3f59266529e31131b4296fb0a87d72761c72b0a1872f720f4639fb7a28ca59b7d3ed451fedddd50195d39d7d3be12fbba6e57acd27d767328ad4eb5d40e3a4b425fd2acd9c83c499b1aaa484848d4eee18361e0730723f8d1f2107e1d45bd256354dabb5d67af85a6bad75d62adf46d0d7d80a81e54037c9357badf3d5f45a7acd6a56b39a55ad6ab5e5663a91984b47b0116eb29eca6aba34d55a6bbdec82546bbaa9be845bedd563262c4ccdc0d0b22c05b046fb27a59c00f6d00e1b6a34b0466b6523ed69dfe917d92f067511500766ed3d05d499d71ead0415c52fa25133c9da95aca19135326ab536d33ad6ab430c30974e24e61aa98841b046bbe90856e62f0611a9efbeae7e4fee21f55ddf93af4bf3b2a6bd6a1046c3483a886eb141984f46d3112c15ed49813a11eab4f27c4ba9711e28fe54b15f0775901179be89140ee4393b0479ce22604d0e1bd8f8af6310ac99e768e267add606750e1227fecc39e747137fe6eb5733fc593a23eea22b869d8f41918ac681735a213527991af66bb5e47560fb72f6105bced6b8cb69ca696fe65285cdcc46515f7e1ac9043ca49f33c2181ff19c734a29613361d30eafd596f059649b854b1a47083d7d292332893fdae96dc0a16ff2b6c939bb66edb65cd23b3932c86e4913994c91357cdf896ac27e1d254e429327a641c80de050922349bcf6280d0f4f90c6dfe4c951f69671a5ef745f54910cc7d64aaf9f37498b7cf759fa2cdadd8dbf1b3fd21569ccca6296eab3342e229f1599d73eb34f9361f5794de2c8c5db1fcf0ca8f75177a1f010ab952c863cb95fe5b56f99bc0adba061255ed2a60c23493deb159e384bb57cd62d9bd5b1e3e4ba23ab2fb9ee2c4efcc943daada4edbb7fa4af927e92f6e83b3db26b181b84b19fcc9f39334a5bbe4f46c8cb1b833cfdb431c3fe30899bd5b2b5c4818e4a803929aab0cedbb9ca93ce3927b637d0c70b0ff207f3923f1a14963841a66f7d8c4c899cb8608923583c60bf7a030d00654a89342094e9bb05b1408a52831f2b404145d91414f06056cbe5d01c019b1865589eb9c670c6457c10d30ea14f64d84e296c1499cfcf1623955fcd0da5644d395bc258fb2666b03dd93489ad5619bbb9da77e609a314aadf44ba34e37433510961686c5e28945f6cf4924a29a59cb2b1946163d19523c8404198ad8b36f1944a7db04e75ab92f1b46948d8fa79170cac2aa652e7696197a7700bf3a45218d2ae95da5c6e4fac757b622e41628ed7aab575f26a0efc822d8cb52e8c9d7e52dd0f16a9345cbd7e49a011068a98f205ebdc65bdf6cde95bbc2c1dfe7aa06caad4bd9cfa25cedf72ed0ac75d375c5c386e6017015a7f790d2ecf812bd07297bfe00a4c61a4e52eaf80ebade85dbe4e77b956be4e6fb956be526fddd6679363927853a943a39cc22cf5f67eb08b7c8268a4bed35beaaa3ebb20aa14fe6656d9afaeebf4d5a395eb3adde52f6f5dd7e9ade7b8ebba4e775dc75baeeb8461e051866df9098f462f9f393e1fab481c1d9fad4f97d5a7eab3e5fafc093a72bcdc54aae59eee725bf7e3c9dde95d27bb225a0ad7f6ba4fd995ce42dde20f1a65fb6d83028bf2e9f5d6bbbc27d6faea56e2b854980626aeb7bc75971b69a9cee3c2aeee3d312117ccd3825ddd7c4f2c5691afd9c29607106c8ebffc84f3205ff3e799c288cb5d7739cf144674bc75285f2d98670a232d3f01f3b4dc8579a630e27a0be639825d304f118f10f99ad7818f84fec12328a70bb6f2650f632d2acde578e9c9b09c041659d4d5eae95b6cb53cde8a0c893cf0247f3143220f00e59e58cbbcebb6bcf59e584fcce5f6c4b6e65cb727d6727b622e37c65a2db885e3c9f4ee96be23bfea1a1291588dc3044c78518426a18c13e209e50d7cae0fd7cac9adcbe43241800f251f175a38fc76f9eac462219cbc850b633344171197eb30bb9eb9767cde35249b3962279c4a29a594524a4c23941c3972e4b8cc711d394eaf0e281ddf6989c216f69b4a344eec32c05c73d8ed38cd611784c7bb570c8cca9c1e7917c9eb3043188cb91f4da6df646ee797fb45a21f2b59c7656afa08befab5f6511fa150a878b4d91c7641fe83dbd1d65a9965be513222520c71f3ae87e311ac690e8af63428da13584393b2271914f40ad499aad3a45bbb6dcf41c57eda2aa35264bd7bf8f3be09e4e32426665d0c32cb7ca35b0e1bd8d3e351f7f2ed41b01f4d0282afbecd5b2d2929a4d0accbdc06aa3505e1514ac66ea35c03f50d8c3906b79356a2bd0934a5f44d6645df08411deffd7905d489691c9f4050a7f51f38fe1d078e7f15c7eb61c4a142f2d527cfe5e3f92ddc85ebf2811f50c3ce33761969c1c7835c88cb078e22860be02f3cbf2796b1eb0139a74236f807feeab1900bc3bdab09c19a7eccd5ae006279e1c2f018ee0b177235ef76197605e8580c587bf2c2d5a0c070352903fbd52a4faa6d5581817d92dbc763de9f494420548dd803ea7c22017500a09d51b54308849e71d8d5b07397911682e0d9c27b62d06524ff85bf8079048067c62ecf054304c0231b30108a452658098af5c4a6d88f266dde8579fb71e58f1f3e6e43711bca1be7eb5a46284ffda4f336b95f16eee30279ccd5b97763a645126702c9d71911084da26a4420da13ed45b4e88cdcdf00c0c2ed27b0a60fe43614edf575f2044aea6b4f2290ee40ecd11a4d6a0ec09a06c085f9476fa081daeb9e026b1abbbce7bb30f3a556f40afd88a9942dec179de0098499b03782e0a4f0afa270085bf986cbbf13f28df714a813f3be975957e15f0bb30efb887cf559ef22fac6c7bdb7cdfb50e7c76ffed9dc7d857f35775d8f7f9d4f873918763504e02e1862f69ecfb34d198a65ecc289793e0f2c0c1c14c5f25d784fcce64ed81eec5bbe3cf18735520014eba19e119be2df7e7d7c8b3f5a602294f3a54b10e113f37161daa38771fda211c80d5e017f35f7c03117079c041c913834fda277a187c1f4f42908d15f8c8f54c05fabf56da3dc0b7f31bfa4e7c93cc7635744c765300c33af8bc47bcc3bb286e63cbcdb93775c9867ee47e3c275d510801674f02314e371e13db06640149b42e29c980b329f919efcc743963bbc1999dfefb4f43c997fe6c2bce3fe97b95f4fbebfdf2287a5bc0f791e314efa464a1e972a7c02f9b8845d7eda5ce2efc72790c4d389777aefe27bcf3b4cde07cde58ecbd3ae08bd5764e63318c21a19f997779a5f7632d47ce6927aafe3d79534a7f7bcd72576edb8bccce975edf8cce90e3c44e6339f91c1de77a4773f99a3e0d57ca729cdf13d8ca4e6331809ecde775ae62e91d9bbbc5f0d7ec1fe7747e4de7bec86c03054012f91d9c7e93dfcd97c3ff01299799cfae0c1437a7f7d7b5def255f3352e6de5f3ce469cca5f45e9799c11f95afe9c48977893f27f2530999c71c097aa737862686493e8f6fb18361067f3d7966077ee11d352c9acfdc14bcef348ffbd5fc7afdf5172eec155ecf778a9ac3ae480d7efd15f37b1849cc656264be2369ee4e7ba7a7f9eb13eaecf8ebf599d35cd8deccebf51e3dfef1f83ff31d77c29a99d7ef7d7b5d24b07b19995318fe7ac8b0abf099fbd93c739afb29c934df71d83731f7b1037f4bf28ebf5e7321ac79e18f27bf7ce04f490c0ff975d8373f6e73087562eec3067f3ce41daff98f0bdbabc19f92fcf2813f9ebc037f3c4ef39ac75c086b6af0b724bff0c7e333df9134f757a1e630c37ae01d62e621e36124f432f732f432ff8efbc55c739afbf19067bccf5c9ad7dc1d34f7df71fb93b907bb22afcfdcbb1f0fd9fbeb4258e37de6e55d99ef38cc01a13ba8846d979435eb149140000000000315000020100a064462a140249808bbae7614000e7a9c466a509c0bc44112c328ca20640c308410020800c6001821a21901b02068fa44c7b603af965f4e0776b3e92439e6ee0df3f9043d0ba2916888a315e6157695466e2f4a16c7177c6a1cc7c508d3950d20e02481be892c2660f0d4ef419fdcc044cbfececdc2511a38f95a2efe2825a2e2ed5e42266650b9a123184c883a5985dc84eabce2305f065d9498a27bbbecd7322f50b349a2c92f2e591998dae67ebbb9fb5a98498346369818f17daa06af4ea3b9e2e3ee3d1b2f9240adec3664146f8bd6d5695ba63485d14ca530f552cb4c6fd15505d5be01c3bc03cfcb9d94e9788e729dc460dc8fa174560f28a237f4f44b96f716055e376589a1544f00059fc8abca1c349aba8ee5ec02610197c6f217aa1735a350b00aaec080e719a366ddabdaa7ae547d03a1419b5dbbe2c0c01dbe09070e1549ecf5f3cb5f819cfee76ae6d199e2757e38286671c5e0f6022810b3f32a86e6c3fc6a4e144ec95162ec4bb6bf2f85ef10518b454c17488d565d4221ff5f65c52cacfc016970e7b3140ee3e25f4865c7613e5397cdeb5c4db0ada37f9bc309934cb518f52ed3ff0146e557ae35776bd7e3deb7018a63c34eb7b4c1ac7d263d249e85fd60c762948d33bbcb8ce8946607312a51685b29f83b344c73b300da18ef91478956b4fd6377205d8c0a72dfca52cdc7fe47a5e80415542a9c802c4f94d679a9716dc78ae3f102c1653b2adea0e183061f3752ed685a1d88e2bc25dcdd565f0f2a1b81964c5adaeb4266dbe492c2269614ed8c80b0fae7642654bd006fefae302f749c2c96a2d873b9cc1786fc2e482c6326fd0c2e36e18618dda6b4aca0597a408a3f78815d2e9a00590a481de70484cb6f8d6ba4d0023d6b0c22a14d5876ee92f73c7b703c28d6ba62c4a9beeee1ec4a3e06d529de1c31e19c342dd879ccda2ff965a389b5c5ca79d834cb5fff56df25967095f1ff54a70d53b8983f5dd55de7cadaa146e8916d286eb4833f1d1a123bf24e39e5db4936262573fc28fad5369a46bc26dec02a0621ee02b1e13f7fa5896ecf3609c4c8ca5fa93695324f7e2d138916cf3f7d57d070ff0a99d71ada9e80ac8b9cb366cc399dbf0b8ceab1f55c7befb385a686eec46363e992967f1ea8416963410bf4f09ca5601112f1a0fa208e779d3f869a4493b048be70835bd1f6d6480cdbf0f75a27dfd58dc3f32ada23a9bd1c5b1ef15fa442cf400bedf203dd468bcb41c911a33837d941f71a418a2a96d3daa7e235fd29e71ed169f77381e86b1b2a5cd05fdc0896966360e1c621ea85e291ed2810410e98916b25f054327e2d7a5ed50d5fe8f5f3b5a93e52d6a6ae004b79c272918756ecef181daeb60355d748f975a7bad227bcb252f700e5cfaf9ecdb6d44b17144c155e2ef25f6eefd6fa166ab5fbec9900b17b4a9ac4313f0ef70c1dbe26a7469db161806c76f41e4f67550a101151da22ceac33fdfacc80fa9db3da808ad6479a05af102851e1e1b840c572ebee8f06eae4a6fc0eb4ac65f7c93144d98fec669b216d1b140b1f974ab36eac82fd0e1247b03bf2492b7237e6b7412b64f566d79c3ea4ddcd0e41013ee3a51d10af92f9a2771245d1d39a4c4a1b8858dbd38402f77427f1fdaeec5575d6dd6c5fca8f9a092f760863dd6e2b4af47e5f8e6050396de8972cd01d24d2bf9aaf82f4b1f9b9df5f217a21f4a22244b7f4f0745a3d5250e36ba70c5a592832e39960c15793debebce9dbd577826d60755aad603239865e8e4cf58851cc09ba5c8e6a2581ae77dfc27739f94d0cefd8e5c96bc4e10c3b253c4881138c0f4baf9f441e5c16a46130a90125618ecbb923494a6c27ad25e0d7c683706c3b9fb186370fb4c813e580ab3da85243d3b67d57b34da8f1b473b214806209dd225ecb78840b1c15cc7bffc78cdaaa108af8ed90e48e9fd1c7e6184bd1d328bc8153ca03bdbf3c2de2c50b60b5d06ec36080295bdf9c1c01fba71ffc84c06f67102f109780e0aa37f88b7c4e105db150b328c4fb0daaad6fad8368cc0b38a96dec46ec059bafcf0c56faf5d683a41226754a5ea094c6e05dd889b59bbd0a39aa8f870af1af6c19575a83f98d013097e00f1dd8c1745a6588e4b5c8fe8cda2b5d1e092a7923c62308bb7b14e8c8de31908216d89caf7a5524a030100143944549212aef0b9a0dfbbd7503f78003bde88f82d2ab8b55c555989d7efbf6b3ffbeeb98e8cb23644c0b5b9bcd2baddf06f1798d07b484cc6a6ea7f38099f162393b6867c91dc844cf7ab68e200dda96fa22b835aee20a135a349fc91522e4939b0a2248fff9b982f7720d0c4a1ba5a004eafa3c4f48776cb6027ad64715d879259c02660d2cc768cc4157d78e33a7c460b270cc8e5f372459b094dd6f9e36dd99101d1a3ad0370806813ea517d860dbd89995a2ef3aabafcaebcb0b56e293ced9d4aeddeec9d84856c52779acc58e39ee0d223debf7141e440ea059713e20bccb8e292bc0f0b51252ec61ca8663581f1280f81c245ec1cc7977418b4216fc57e90596fd21f8ea9c76d72921bf110a570a7d5aeeca8678de1425c72a6fefc3c7d526a557aec32801de6effab5d326c49950e16a56915182b2bb77ae37a2995ba27d7a5e7dc4cebf07804ca590e512e6f0497cbc8bddf992f8351c5013befde93cf3ce1ec75530b77946a453ba6882e26f13fd280c6d2c2a9b941e8ece17a4150f12d0a7861796e4461283eb77ceccfff0c3e4119dedc9785e19bb5b232c3f750967d825c20509f3090bd2b8d5592d298de1bcaba35a86998f366f484e85c9f790aa64e1161b65e314c551255ee1cc975f91cce014173bf4f6393e67b681cfee8571c3c82fbe7287665489203ede7879c856ac0ff738e169c68c47ae597811fc1a97fdc31c8ecbe470604d8c944003875809ad1f07001d117d4bf41857a92e9257b229b5f0e3318f1ec45b2ac903f3a4572d2136d37d2e0ba93bed7949f719b95d5fc55e45163092bd1fb729e78a9157299862fccb460d12b24c9edd78512a725aaafaeac63dec0cb282bd0e7fac500d7a9d9357058482800c6f5de20d0034bcb860b7e178165ecd39d90e1765bb39e3f82694a29ea30589469ee19a8a3af6d06a1edcdb574b8238b7d1b60a1ee3b0b02a37db4bd5f068d36cb2bc070cba265c377010af6c8669addee3d1d16a836e666a2a1e439307d036be7ed505033db9dbb60918a99360b296529bf9fff472ff87dd20283145cb3452e47d540833cb244291429029c3e71855ee3cbce409e16553e1baed26a875957b7270910a90ec1e021015e6b8836af1e01bcf6db986d23800d2c0b5edd2f9467b2e5fee8fbfc4393f15b763e8d2d9b0d1c5d9d9223cbdf06ca6dfaba08dfae1b9bda5d35eef451dd920383a6190570c17d03d64a1362ade067b8e975165d3825983264cca8a17eaba7dddc8a3b0b63bf1cd0cfd4e28f2b275a0ca6650d1f999e4bd822715c4148cbec4da862377a0679ecc77bb38b5ca3082c4446d5af019ad54a74526c3de0b0d1d637f3ea900bd0f1e776e66b57f97a80a3eff39d57c45393bd6a2ced250544e518cf086299b6b5f296d4130c9d9e80b9ec55a27cbc8680cf4eb0b55512489908ad637368b41bcf4ca700ee22c84696a5b73ab419913e9b3b64d6ec5603fa793f7fb4d24cbc5ada6f707b029f8e87f8f574cd1590b0880296d27446cbd45a38f532e4abb51621b40aefe864adc4ca0be1ec84581f2b0aaaa036e86ca1876bba5a30556e50981cc483375721f0149ca674291607e98ef668094e200ac068fa3bad6e6d046d1ea357ff37f743fbd16408c3e68d238162ad19d0b4273742a2c842520815cc3954bb9b2e0eee5ec8ff8a2eb86463dff758d25182754e764cbac00124b950667135b4e9a5f1b66eccf693dc8e3cc8ba6709a8ff8c5668af6bd15ad455dfb8b1cdcd295c4ca9937db9b1215cc07faf0660a4d0e9ab56fc481a6482e17492508167ca0041b812cd62b4eb015061120f2f775a1fca969af74d37da81a5bae7b8a318a6260329924495bc617cab2942392823ef4bfecb5de493a6162049225133400d3c10c815a0824a8b937de7fe7e836878d9a13bc5cce7a15dd2228e04148aae1b38382cfd7fe21a922fbf24e79515d01fedc6d677c81caf82c76976e8a55cdc40054dec6a240019cb5180393b207e357076f05cde25057d3fffb7181a273dc280cbc4260b7cc1356b76e249b759197ef93f1e3638f7488af8b46adf11bcc19887e8fddf322673de1c7bec2eedf12f31b9ad0a3003836452c572b3202562c8e82cde5071ce422396c10ebb4a6168742ac850a430f8affab0182c4130484d95e7d199a629c8c8cd73062c2661c4d143bd218c32e2cfe99f07923d6adced813c0afb41d836ef6d6e6acc09d375a63a1607f0340feaee97def24ed69acacfecde6dedae6eaed93b15677143ae87da89da0792582d9437a7c23be25ffdcaab5985ae65e26fdc262683c3c0fb892519ac27549a32d5271919e4676313224ac2755b6132e280956ccf6c6d6a6ebf2072cde802a3b5b0008372c4b6edd349408e74106f5b222d6136f5d6add4cab9350698107c1e026f8ffba6c4e75f070fa84cdc2ddb1bfcefe156932bddcda48abb79370f3b92079d35c9c4fa50d24a344705b227ff5a50d2e83c26088526e5f36745e30d1200a407a0147b889d7f05cf5825fcfb53c4c60e3388327a471fe42f91dc4441349dbe205e76e9c993feffc43864c591212b019558eac49692fb46949e6242d63bc8cd57b5e14d0170c27c569abd1cf025bf65a62b86f5a6340b4f03e265fe14ea937e51715f4314ab830239220c504c15615a643da015781eee3cea4a943c7f57b05a409245b93a1985ba856c0b50ec4779fb3e55425d4fa3defd0c5bddac73977a50543c5913b672ee039fc54e3dea3524caf3b7373dd9a4aabb657a603293b800b6e87d7b38dfabb00eeff5f6b28af5907621c89e5d82b6401403aef75a7de21a7538cd39dbb3c69b81cd452e8bb1e510025d04e7358296edad4e258137a2146c36a58731a9a9bfffaa915b4cf3cd06329012428317524330812978d47acfdc3359eb2400a5898668ce2807413fc3ee2570359c260d4c487debdd38bf9f07174f7206f49e23dbf6fd5a8490f8bb90ebb97af23ba7373ca85e8fe1ef1c3ea5b5df1b44952b850b67d9f68b83fbfc760b40357cd3eec28f634b25ab5c358176170cbb1c00bf74d4bd3f7f3e1ddee2417c77bcb0267d55db500e398c3cf6a05407ef0d03c9b04243cae235c4d319a76cb26e007aa40bf2122abe7e70c1a1d3c308a2c523a7fb4d1a351664e9106cc2674255f7df56e8256ce2dfb03e8079dd127b5c9c36502a9562f163dc76840d594785978433128b9b3eb835c2992650d156763670139fcc2768e9c2cc7056c7cdc09d8928f7b26ce7904024cd3a34c558df59368c563f7c917235182931c448d523f278dda44f0a3712a8a5ddb87809d2d72478bf5d1b466ae39f83c648959e191173d04e6f42705d6a281a6bfaba156598f58034359d6746261e1439e3865130eb07452cf218b39cc5334857ceb5bbd33c2e35e7090149de3bfa9c5340b9b646e817ed0c5bcd0f36a3cf1650b2490ffe9ac2ff05cacd54208e80ad08e9dec17358093dc7eed1192173e62b593286525b865a646b09266633336c06bcc7ca48504094d935cbb6290e1436ef075fa26db2902265f3d46b92937d2c7e1474cc8a4b5771279cc7a595153baf967ea9a8b50c84b2eb41e30e1dc1d4d99da4a5a8ad4c1caaebcd144ccb6b3846a04079b56e73e02e8b15d7d334d8f40a4aceade42e3333d32de4a2310b14f1a3b91cc6007b42f46b25a11f4839db46db88075cf5c01dae1af370580c601577ba06bdca77e43b2059a15269ab2c48f70d7d137361cdfaa5fa53c8e6292006722b63822fa0ca3b6be7dadf4b1d3600c70a77fbd24867202940b735c04813dbee21bb1b94d696eff9d7b9bf0168938580eabd7b2ad8911de8949511bd92071db1d0dd4c80f95b09e78f637f06d112a11ec9fae82a42150d667b55a4c03d8d61cca31ec1899993ae5a66bde19ed685e210fbdadfabde97ff5e01991a81661a227ba94641bf9da21095867aa1579a468f3fc187dc2c7023a7074548ac090f68cd1926d27aaad2ba709d2ecaf13d015787526ca3e474016e9aae3c5950c2d581805a1202cf5ff54e16a601d422065c4baba8ded193d825f3e9ed84230d7643e5b94505a732336ca7756ad47549f7cd06185dbf17312c0a285cc36d290c8e545dd9f63d9a7ee87b7f3d761b781cdb58abae61ddc99c5202d804353ba22c53cfb79066e2b4efa603fa5edce43bb5f4beafd3f0469f996ba161a78c893b2c28f5da804f84c0fd615941028714f3133c6692220fef1436ce0fce5d98a6aa38c16cc18a850f2680326aee68ba3f8d9d53b6cbd69167b98b7bd35e992d88e27813980f32218a3a66a0ecbcbc959db9661bb004c98596bfb91a61358d87a099b3aed0c4551a5cb42041149f101aa2e471818f793feef44f1174d7555b69179b7571af375ab28ac8f8f1a33e31c41335a8a18d8cfee8373c7845d5cdd3975e7350c6224ccebd87c3e70a81ddcc184c8ad6114159be29ce518285bdeb5808fde507751b31807335727d4fe4cd95cdfc384f6a4d002ec60278dd209405be92d4259a7e38229f3d50980c40e8e9141d44e2592c092d11d63f0360fc83232b6c074b7fafa265ea498d337ae6ff9349807e688668b5399d690292931c86fa51e33ca3202a9a77d68a9cb326945c967376a97c3d3938ddeb0952a492eb3b16652d2d473af01a4b0027858bcf837a424f989081634bbf7a11b2aac11f86eb03d31526ffac2b82dce9352e8b5ba1f26aa6d477b641a59ae62d7af011feb251191a627382a4c3e2ae2d8c05c1279439ec1793e4081db183ff46569500a9157f3970f642a37dc79bef4ad142533d43a0a24d75ebf7fca1d8ea65b1438dac006a74e6d7a8b238c64e000e846e63f7bd08eeb1f1d3e4ed812c710205c106fbf6a7e2d29d03ee7cbfbbbf11415fa5430fcddc99a95bb947153dfb373db0c0723c299d7cb3673fc7db6e4ce7a3d0076f7044b6c39667bf435dc5cc346d94bcbb05960045cb5ecd32d12a29dc4f984ab8c199ab97e51f77203b22bec7795071792321d3603af129574be29994e96186e7df2f5c57c9cf9d4cafa34e47054261e2bcc6069f094e5c1e1b1e2cde17e780ba90d113b305d8d9bb5913f7f3e489c0ba1d9868f3b816b835bf242ae9be4818099c2c8e6dc6b17e31235949ca12b9242b1a05323da125b2816710255280f0cfa6b768463bc4017b82305bb6815095324ccf5750c2d829552d0c38527c00874bbb6d6a6a4ddc8a65392d3bfb1eb6418be3c7ef0b464bb3d42bd982cefba7cbef003e4cb4c0e1ee01cdda7a0298854012a826b4c9b61cd1d4d0351a0415a8d810ac275c3e365563d8004ee64455b13e044e1c8951f8b3d8f37b0fd057da759ecfbac0a724e6abe63d01c662df91d42b8d1ca2bebc0b9b601fbc0194f49a7ad2fe5fe1a9136b0abc104bf2a2056b15ac89bfa2ad91e0ea53b116dadc4b4467d3b6bfbeca01e9172618f2654c615f4c61523f7e0a7ef9ac5b0430cec126d63c7af3ef0e0173d0c333437380175f68f096ede2f42b81672d3fe4156ee26a6c8dd4f241eae5b2f2a2f7cd8e544cde8813cf45fb96031970ce7632bd0820f2bc77deb00477a490eb47e2701d83456b2a08c4cf3158c455c8e217301ad11101fca9c80a1ae0da80ce68928b70d70a6caf878bdad5736d36a9793d0c8f605101aa4818caeb61806dc6ccae7bfef71affe90c02583e6873a1e1ea05319952a6561f8631750ba03c55f83698dce94ae5a8c7cadc299fac95d54e738e22b0e88ce40a330619c44a720c1ba2a844a07d6655dbe62e5f40fac450ec50d9f22404c3b624275373288065db41ee54921d27e4be57e1c0d8bb01471d7149a452a8d0d5a731ad11eaa1c37652203c88d2bb84f393f3f59898e48518f0ef98d9d6e260fcc29323efacd3cd5c17d137939c20b20040f1328dd98e33579f201d04339ff4017c95d243df3aef5e566f25224311cc6cb073d92f584e4ed94d0265b53a1e0208a6a4c996a3b8fea42134c42a6bbe74b4545c31142fc9ff13a08b0c0535486eb5928b4681b92482015c14ee9ff7d10791b55cb8725a024ae8c38d0f449205d38a198195d0fe63bd72f6f408b626126c93dc5353752e1a3c5c591e9efcd33efc937b4f128f48f9228457932695c3cd7f27004fab7508d0b2d3d539684e268f4f5906eb96337fb7aed9a89eb2c1537f7471053b9bc4a0bfcbe488d6f75fea79d30dd1793b00480d8e8c3da3e32acb1269a1c0f7930e89043dff22ce348f45a3d783b239c8e22967a544f998fc0b06275fdd92a0ea1a06e1f21bcdb68dd1f2f782f8db3df79af89214f2aaf20ac040f0f7cd3389357ed953df2767015479284956ff6900536feb77524a738ff418aea4c04cab2bd0d7ee14d356ebbc9ec07f2a7dd0a7e1ab7a65bc64d1bb2b0863835b08420669ea0407c0e3d37ce99d460e0030475e0ba4a470a07903d82981787ed991c565652e4c4ed6c156852f305a749e46c241e848fcde89608f68b823d08013dfb81342acf7ddba874dcc144c10d8257d24a3bcc8d2a936d7978bf89f6efc4db972e6321b9060a8a59c671348d3d9a2fb6b1a6a7da31c5a58bf38e01245e297108d0db23e060e19538eaeafdaaf8cee62f1acde682e041baad57d55b2f4f10773af3bc098c0772e45f4e15f8f4aa3a7714cc5c6b3ab22fb5bbaa1b16c11549421803cbd486009e5bc2734f3059c39479b6e7a9cee54d42fa56b67af70513dc55fd823c2b29111ac96247c4b531478a82d7102555114ad92185ac0b1cced4b084bcb20c5cb8f27bf85738d7d99ec2da8f5596cb5f5c5d250148adbc8634a17e68fef426b25f9797a85c2c6d9e175e96258af56656bff9e820110769060218a383f7f02a9cb56a5200b0b11893dac17a4d84dd2de42143ed0807201cc328d97c1645d4a0c6ea9ab7b8f7e9c09d3f13b00ea9c812a860af5fa1641b171e032aff3c8755b956a38a8f074a79a3d3571a1dcf10450f997c4c3a46c4485dfc52ab59b1216b5428e5322824b24012066cf2c857f044744156537ee79ed3fc9a2f94a8b18b1e160bab61e43df306368d30594f25867e4b220ea4640e11a64fad3fc3f94b537fd1dfc1ce268d1ebc5be5762a03de9a10058c137f913ace1121e1d11de1c1139693aff947e1fe9d734244b0459dfa2efc19e1fdae752d6c53c2fbb945cee06b882210c5db15c132d2f1f619bab4a734b56319aaaa1ff10bc0821d6634c969b0b29fa06432f2422d1dfd82edb74e482df624d0721a88b846d28d9296402eab19d0ad6f0a9ddedba7221dad00c84c33f1d16715f9842b007c8dd7231cf75310cabd2ce5d3dfda69daa7ffa79ca9f23c1a64eb1b66c070b6f1c7b9a909d4cb830c9582c6169dc100b188f1fbae69450c80a319533d4f2efa2e1803250bfbc6ffa61216af8dda157763ea727ef9b7a5173e8a884f39ed250ffb64153316130021725882b3752a37169df64b16c88ffa1b9cf1b784302eff90f006f02beb80e0bbd760fca8a2cd6a235d85d9dae54e384484f1220ae12851b8514e69d9e6bf1a475a8adca139b8fd7443aeec2dfab20ff7c1aee889e572817ded39fdc9229e66cddd4fa8cff1c7bdc5e58c43f37f9b6b7ccc79831451c53b3e7a17a810a42d1b8322da0bd8df1aa3e12ef8f305b1dbf8fc5a4c54c7b360b3de04785e97f1e9202b361949b3c8aefb3f1da0ed894a45e254bf20d0fe716ad321ef782fd02bdbebf3f25b27b1a66fcaae62683f6c8cb951222c03274dfa0736ed5a7e117b321e3fe16a034c40517aa4386d4e1898d8b725f298a99cc4eac79074bc99c1ec31a638c52eeb8ac6f48ccfb245831c612e81b733585c6c0cca2be6507d9f05e52e7087c64e004a065c845a7d724e786a5f7528abd56808b91b04f644d54aa09e1e5970ccbc44a36616af429b902b2c0e3e461a6cd492c69f0367446c9da40c3b09370b00f9c72048bae54d2b5300c5753c87389a2b65f81d636b5f9836751b7e34be400bc29df146bf35e8f78be6ef5219699d05886a315acd0c828cc63de998ca30c6fcdb285d05322c03e7d7f7a2a33ac2d7f53d2f063ab9a360511e721d47aec7e6ecd3ca3ffe28ed76bc7ddb89e0a64d3be9073b22654564acd9f9505e75c2728533a9671a127113cc9256b1557a26b7173d614d5144f6f66157bc2ec287672b10b1d3122f1f028a79bf6d4e74c7d426d2dca4e086de05401b0a9c8ad7f15202132b1761682ec6ab4a0264ad49e5c74146d40d4b92880173d9576243f578c641d63289eeeb22eb9e12eea32552d3ec026e0e7d89035155b8d5e47d7b607eb2fb2bfca7fea2c81ec52f1182cb1421d057a9079a420ea274a93c7b6fafdd1d7dcc886e4c8c3479515faf485a07082d5780992f524a3a674ff01523ac6010c453978eeb06417df9c72f486cfe88e826ff24ba2738c39d218b395ee884b772e14aef9bd615bee5cc1e9e8cfa7b4b627367733e755c44ed69e2321107fb97c68b39bbd40e54bced37b16655271c9a6d575e433156ab80f9e2be2d1d1f55ad959d87dd4bbb1c33140088f927382363dfa681710517d850a1171589da3b5df4b691404f733070420c7e9bce730cedbec68f032d352d868a815ed7ac43ee5b26e0189f62aa67989423d8c0bb8240398fef46469b9d016e3642f6a976ebacc63a2b6de541638d8ee5ee16777f78a7b62ceee5e71277752faf85a78c82f9b38a0274678964e9f056eb843e9bd5b06c50c8236c71f19ee0dccb07b2cd311affb8b83b2e4bd9a8ca6385a1494726986aaa51c2959e791f1720263a2e3e1fa9b26be238ebcd524fef1443a5874a3bf36369b7dca90f3969d70b869a6bd68374844880e5fb849704073d16c0877b35539f94e6cde74b351828d87eb4ad8dcda85cb84bdf3d8623fc6f8ec2a623c2df5025c666ef5c637f70b8ea471bad36d909d54676cfe99e56e500bd3c984463082ce1c58dc80aa7e577ec9ca3f9c73a0582f13ab5a86cef34fadd02fe0935667515a2a17bd6146f0fe878faf163c0172ff17f46eecc87192d23e961fb4b711da473a2166ffb21d49cb647a42bcb09d98cad42f628f4bddd12d09fc8731322b9f45c7700ca2897761e81f98d03823e4344bae104143574ca3ecef34f0d048f48ccf5889e0898df0527f9032b0a4d62c094f23fe38c78c9d0b9ff6a31c6a73cbb06f09e16d81b28e34703d869cd234532a4f90a91968560edf633de1ef7edfdc02157632debf0c71108713b30e143dac30d5ba72a55027b9721b7b04818d46dbacd8c6c9b2c53e3ab73f1fb2232118f5e5e35353bcf59647de5e41347aa9da68356a6df0ea20a50f236bbafc3eeb0421e2832e6de699a4287569517aab185b5dfa1502a5ddf6324e3428c90e97cc5c69f8ada10252c49671b277a116af7666f6c649570e6933f58211d8d7f50b91fbcdc36b0b84c89f42a7e47d68d612920c454053d4b48bea5f94481fe43476c61add25f8ea9ac634726360dba16e49f002fa64b70db2232dff0e020c940c441e659b9eaca5478fbe3345b2afc961b1418f01f84c61c24fff478abc556c8cf9182e7421ed7d5ba3e353770f60fe5d15c4839f671ed2c816194d64a588a24871a59e6a168a314b9d714db9310d6f99573a27c7a17be654ac317ecc9fd6ef11cf828a71c7b4e7f62d3398a759ea00f9dd82d94928da72cb5fab68da11c2d21e64d23f96d1834e0d0ce8f97aea3f24cd735903bbeae0cbc36d66e3ea96e7f028d066361c306aa914119015c316ab6617c0f932d881a518cc22c708cdb77157b96dea6e8935dd7243c271045fbee044ff88336d1059092067ea51342678ba9ad0c285de1165632fda90d50e1b7209c69d3c275404d3ba2177f6eb63e3fd5daa5fee0a7bc8ee9a1a54ba4d7eb2358a2048f8019c16bcd7578bbfa48488b59b8b87a188a0ced4358bcec8d86599d973899c02c7e16357dcee2d54f7aeeedef295685a3d2aa2bdd8dbb4eb3293755c3f02b6f76166dfa9d94157874a8c48243785c590303d3334a8841abb29f287841b2fa7cbc9ba280de3883246a91ddedd47c67b9d013424a055c0c24f28cfe6cdcaeccd56c5c744b812cc1ca50a6c27086ca610830def270da47468cbfb4c4d1f4260f208d36324787cdbf27a666009f760f4d36de1d9466dfec6052f80508e3fd17981a349b40e06ef05400250df235f6a9134a257937875bc11bd55f52dea1ccd927c51489ed4e8948ca5b86a8315e913a95b2492e725a029d69a13cb646b044006c93233672c9a586a803109b6d0d208cbf8def49d2897b03ed20428c4e2b803fa0cfa37d435d31aecb57737e76e4bb76679e4c7435d2302f77eac817650f735f069730d6cf2cc64c052ece401cd22eb4e24f2f122a6f0c16f56b95802d516a68a017cfe9d50d00fe396885d529006ccb32864d1a8b3041c149d3514a1800609f430caa7e3775e45f629b8a8b882a0a627b9a791ddd3376b499bc5edd20c2f73b3963876665434767afa375e1535699096bd9b63b31e43a846fccc5a99446c9259ae26cd2ab6d046f1b40f136ad4c9085c20323959c775c575ff12a1a7f3ea338f991e65822458955ac38f9bba68e34235e02db4c68dc73a70ccf4cff33636b02f34ac21025084bf42048332e1641a4c6b4b11caf4187808d510174c997261fdd06a5a8a0443051edcfa0428f94c32db3e7f89fb140aaaec66e2d2af45d4f7ef224d488cc488d17a16615d986c76682840f6b08bc44e66c60e8b0c75e4710ac75354783c554631a8a3e0607e090157e3a476069d920ce3440351e175b960f5d542362d11cc6f10be071d1b4ea4de1bc554402dd9c9af70e7265479ddee7aa94c8b97b7c424e666ce5235b519c478fb450819519c2f1f1054351eaf23edb19092dc5b96b674fe8ea22622481730f3d955183b5b66d663753c19d3182b72cb8344e63ddf0d4da719d3cbe513d6f13ca766982648ac48b669072157753abd61ff11ccaca0922c0fb28790db39378143cde2983d44479fb9c2fe50e87e83f11078ddd623f0694bd13708784bbfbfc46b46c17e0b5fe87c210bf3034dc3d1a9439ce9004019c138be7d6f8ec5bc010b4aa7cf01ecadcde2477964810d87c52c609a25bcc1001c0af10525f9654138dbdaf25b24a58a22219bcb2640f26cc4a507aa8024ab04de3a07f34032307e1b8d11224080724be7bf4cf855ae2a95e5a6701ad534149e920bff64a31043b415cbfa7470091a8105091065a4af8c7dcbe489b2de24858aaa9245d8b287309ef17f86b1e570cdcfb01c7ec82a2f61767d1c94e47c5f0bb2c7510063b63298a1e8457e5ac23ba3726d85015ae83ecbd60b1415e9c94dc46db990325e022cca6de063ea9215a053389d7b49b35276a49325269d0f94e96862821ca4c655977251fcd7fdf6ba895842c782c2c89addc2f89a5026bdbb9f48e23d942273621210fc2d5046a07c156120e6b04926d83b593e53469c61cb736e6259f95fc6576fc78722a05a1382c7be2df1fbc8bbf7f7237d0c775af540046d53ec7247c0de200c70150f7e7fd5127390442da085bf0367af3ee2f89142a561ea05d4775c4463d821d92ddcf3bdbe60cc776d97339e25dee77bbed116f4da22da6a26e30bdfaf36318b528263d71a2d07f1db31e18421c5d6a1f02c6ae4c4ee723489cee042e21ceae0c3b1da6acf9b2d3298ddb0335d2f75bd9593006f22131c6fcd30f57411c00f4fb95a6665edf27979c6e911387be4322767c88844b431f1120b5fcc595fcff81e5c92a3d1f8a1a64df3e82722c2530a8f817d481d231590ffacd4420607df6ca37fad9196846ea5b51d62f8a9637c40ad852b5eabc1aaabe12a23988161c0ff30bb25851089d8641f11efd7e8e7146030800a750badb7e4ae18910b9c796c1039d656c806fb38b0a3477da4940519c800c473b1dabf5a9957cf236f97eee1fdc002700779783379cb577c7cb3b030cb9e56dc00fb7e7c265317d7e96533ba78c01b4544c1e7171b9758e2a465950559ac6e902574571b553e8c0033933e7750bab6e785ad66290e0699cecd744f86cc1e3a28a2a3f6e6abb557cec7613cc49c33ff13a82b127e2865005563a44dce0a2a114d523f24f03f4b372ca2c40f5203f5858a9d3bc6e97b31600ec94898bd3bbc39d76dc8ee513aa7588ee3b152790822963a341285685c9c8cd095020c74ff3d1378ff9ec6e52d80947f723b438012d01590c2d897dfd5cb778ee793dc5ca7d7e6e44da4c0d9eb2f5a5d6f376081cff9ba45175677dab13255a6c0498c3320b53353ad4c9a4e18972ab2831e9ad7b4608657b1add140a8aafa3d4c923c04558e1e30e362219faa771a5592e01c68b0aa4149cd86873a8805a38f69008f1747e6ca3b6589d5ca524ae4ab40971520ad293c96e01ef592188f2ab977f674cd1917d30e911411ef7aeddd3732fe53c5bc49b72d90cbc09f3fd116e11438e061500c0abf121c5926f3b7f209d86f4dea6553c5692fa0b903074b9829b1ba0d1261ce7a7bd9fbcfe69521f1208d3d313130b21bb95cd5965dbbf549d39079973a237cc393218359cac66a14eb71c414d47587a4d8ee6930bdad911a3a2e72beba01293ae3781f4fa442d440edaf36a75999aa8373e565d65488e42e2f536031caf9e90a05aff63a621e4ae4e7c41aa42561db13cc50b353bc08d8b1625d33e46e6325b3da2058a3b98eee5e0566518a793842f2665cac31f32e523728a45c62cd19012c11b8877d84a0a6cdb0e74dafb450ae330a2317d174bfccfeae03fd09aa4e264f31df64d28dda21578ca331bf8738682faea06b7d45fc36bad03f6d4f0dbfd44d0697933e5577d1ba6e4537a7e6b7a89979c155b01bd8dce5867e287c4c988ea7d748e11df60c9c40e5bca48794f546268fa8ed0b068b197badd674c75db7d335694736869e557a6591859039cf1bcdb35a2f3239c29c3818457d8308a623260e56370861c5d1f91b7cffb07648ed4b3b5d51c3115e4771725986c33512f16735a5d6da85a040cacb2e84147c8af0e3c800fc722864991ad49259432d75e0d729a9587a681ef98d242b4db20bd95df1f3c079d4c98c05958f809cb3fe9d9e487c68edece4c489b6f2aa1887976fade781c44236e1d71de5cf59a64bc8f483384264f9ca1697dc5454c027811d29ae28618af915b62b2ac280d8ae5705d879103afbf99e0a86bf24f0ce7798d61d7efe5ec7a26cc95d0264e6f24300c491c89cbb52b144d873072fa34270c1aa25d3d7b4e2c4520587d9ed06126198fbe09fc6d32ff3f786ad29a4094c015270f10fb78cd4c4646cf851f903162e8608b69dad535a02040e933b1676c64d675c64f21e70428cbe12c06be37838bca025caf060850b217c46efc0f86c9a8478af759f71cd7ec9d5b51060d755c63e8e1761fc1feeff4ef3c93108d3666900ca89856d0c8170045850dda988b1122a50e50e5dd94cf39a98d28dc133457a230414d86c91824a4423498512fa1e9dbfb1dd1467eafcee2078e5189c4c2c131d3c7046eb1777f574ca7660a84a895625edcd856bf7adc61de7ee5806d28a399d54310f33a8acfd6ffcaa719fe13949f2b2af71f2df36043920a3abe327558193ae4022f196e26dd89ba84ca78d7b29e2487bed123cf01f32e9b392731cd0af8bc7735e481f670ee52c263362a687f60ec00dcf561020bc3fccb073de519e08e5c297242d6bf7f9ee3fa434ed0269f7ebab64e0a69c72e693e48204ff1f3e55ad59cacaa88f7cbd226fe11796217ea9eeceb06205cce67c2d12bb05ca7c6eb97d46c743d4a6bb44e4b891c5c32144610dff67612ead7ba95aa5e9f25d5b32b3c55197abb85bae026bf2a252ac28b79eecfe6096b08acf263def8fae9e39902b6412e5bcb3d5026d78d6c28a9823fea6f3b4c30f3b862c6c9ff50686b06c23d6f72f17a17d3735a0a602976856b086f53ca34353ae9e4bb0cc4e4a153e318ff55ca27c7219f7ac44db6f2906d88f3fdc382f4711c6f40dcf25a4e4dac4d6f6b1c809546c234352bd805bd5cb222e031824e07913aadccfb5737630d89c8c78d0ff87abdca010fa12ef0bfb431563d8f582bcb6c3a809af0d6f8a492e84056787288dc7f5f1c3e50b0ed1847cb7ae2ac003027e04d88047720076c46f32e11259137838f68bfc98c55637617a6f6b4aa570c0f182852f01be59242f525a1eb54f922e38695a99fef75a40c7f1140b6eb9665d30c3dff2a948882ac700c83669d8c2cfea21fda7fc2582829ebe923a810a8627b7cc7962eacb7048f2051344e2e656e357d4992d095e60ea0a52e7b4039e535bece44a7b42830b45468752db46c0ffc7250a71b8c26581bc3c89f9f4ede2b8843847a8e64e504352f117f026a5561340765a7901a71221d02bb3fd4c64a65c6200c408ae71e751b924bb92d5482375264e8ef531ae536cffc09c4d2fb3bdd1a2d727e8aa5c428ff47f69eeeb22b65820d1980ea8acf7b479e20747cb207c2a3b64ee49982346200461b8280b5bd1b0f9510214348582ad93b821f6dd69451c41356c323e9b75ea2b891071e1a858beb05e6e7658101b743013801297888ff787758c0adf91a5c3b4767ae292d5673c75a418c07ad272457b4f4740e103e9778bd15bd741511526fe62e28f6e0965b50a829bda3d69a6d4bdf318987a8fa66c76cd228d2e540dd5c4741a095d5b02a417fb22d7ca4292f544243fea08c42567772c28485b514a16c2077a4b9c81df7a8c57497e01cc5291fe29e05f2a640a03262920b5f2870432dde660c4d99f10a2f94b640c972c8c5cf27d4053062884958f496638de088513398203964fe12b51a08ead32600d9e02e4f50b177073b25152e53c876add683812f6ba6cc44c87807939f048baa221d200735c02acdd02c58579a01e5e3bef97306c4b79bc58eab3cb12b513791822ef92b84211318c1566f553f41fd7108b495d87a9eac0817ab9a34f3fd7f1bf94f14ee0a834d0c01340af85086367af9b47d4f0b0c8a784edf9dd8de2e697a7363d4d925402b43ad4ab08a619f6317e666d504fe06a2622f3fe449cb6cff387e49f59c641d7aae56cd5025246d896958951e4ff1ac80e83f35623d37ed0c517612be47d96f1005b21e4b680a6d6dd17492587add0c634f0e67488e10c5edb30abf2eb7015daa3fbccb94d8d81973174ff86760dd78b47bfc3a957977c3a7b39ce558e2353183a50bf9eb72802f3de1c06a7504608a4cdc8f8bedc3291ec6152cbb71603d0155684e29a02d218925086c97ea100e5c280458e9193dd949230f5d4d081fac95e9d0d62a7916e8e271dca11e41485e6aead38da1b65766d3d312fdceeb28c6fcda1142a1f7025697f436fe71919bc47b9e6e91723b6096b4833510431143cabbbc579500ac15ac664ab0d6cf3b43ca2a8d4c086d50b9ac8ba65b5caa0300aa23851c8498471038afa8eed0f78acc1dcae0ff851e530ae22cd32580d46c1d26a3e09deb6ec72aef402e643f63fe0e6054d0c8f413c9ba1a209ce7cac41a742244785f58f30f40edc3180cd98d8136d3e816aa4209d2d46d9edaf44736f1bee359fc8ad2f47913fa4c76dbe689f5d301211b528c0e8b364a790039c228828df3eb460a982b5093f1f47e5f4090a0dd1150440a2f26b63562c8da0f0dfaf186c8b7f7868189726584eb56f0171e1d4556d209b84360e97fd7240caf04885e2746215c7a09b157a0cc8cee307bd4db08ddb649dc1e8f27e3b81bf5d42865ff07fe31f1f4230c11f4d1bd065f1f9f2de940594cebae04412500906897d400312b36f1145f372e23175646693bfede6891f9816c10c2cc122219fa35877b28bf9b92db5f4d77f1639cd12fe69dab78f3b7c52b83b6131daddbf204978b220001590833aa0744408b3b1092c12aec207b35fb49b5dd9eab616093d709a4280510b3bd5b3eb02d8b5d5d75507a33d981cee236ded3df00a67e0bb2b56512b5e0e8b848a81e34547675a0bc88bde54bacd38062981339358f5d3b43e02a5e6bc004813b2c5baedf4449b2f7ccaa56c40889d1656f214b62d79feb3638b936315857dc31aeb6f794a93765dbe470179e4f1e17f0f452540dff1538b0409bdfa08197c51a92418c91c86c097f50b3b4981e293893b82a8fcd4c1e3a693c057f480f40ebbb8e0e5bbe72e07cf8d70d1966672560c79b9362312ac623b859f7c7e6fc3c680808d344c82946e1390c3f94d234451a936954db84095cad206d7a781a89c8fd6b49dba45fc32e09d5705717a8d1970f22cf3b772f4e87bcf483af6f25200a0b6e6bb250ccff9fc4c03aba70351910fd5b42dd293e999c0d3d514a698b27fbd689a62411a68d10255931f0bf940fbb62746ff45ee238a300d02fb11e2b85266122237119c0c4b37f18021f015eb2d6fbd6e58cd3de1ad5d8156c4692fd01132389a3df001b6200eee64b43dadb077fc8b0f8ce3d714cc26a764f8b7a3d8c30ea04bef18ab8f6189687f6e589ecab22b9f0349a61ff695555571a8de1eb2cc07ee3729e393f83c4171030d7321b65e80ea3cf71a1c7740b20400ba51c1ad3b3f122a4f83cbb1021d1846ca5c059477d89feb81d55dc74d389b4b7abd76d56cae32a9506c1198a8fe2a168ce27069d16a0105b19b226404ca39010dfa5da48e4b2ee27545c288d96ed643118a6b0916e459889d14a2105bd6ee75c65dc7c80b072f989e913ee159625eb230b2b30bdcf153092654f06693475ff7bb51d5906b3f9ecb74cc67f46047f98baa8b44a7542ddf776988762a6d66abfbc8dbeff6c3314eaeb9ac9d12971d92a689d58e8fdb0f93b395008433ffc876fa4ab638b874e8d404867da41955b068b59f990f265bfce2466899584d147bb16b96aa568e3ff2efec3ca73a8d27db14142d66577e4a0cf79dd634281bdbc9199e303b374ec928beae8d162c9c81ee96ddd9e5ac6ab35f88c46fb0753e7658b17253ad62039c6f843b0dad17eccc6b7bdbe6e7b8e5ed9a717b9c8c673e9370d21903f4921b85e982afe49396e5bc35686eb499d99cedb4becbfab9442baa14d200f6e8e7977a6db458b86eb143a55772a9a13856371a73b8fc846622ddd61d4cb4fb18d4546665692c91697b9719f22e9ee7d08119f76dd3267b8b9337e836e31894a07bc41ff9a84739a00f8cf91b51504ce4d6e538a214566a8ff2dfd355433fb7f1732c358a4409d01441dca68362e22a257ba5d723f7cd04669c09b47633c8b25ba49d3bb5f0b8f233ebb875999759aa1908368a33d068415394fa2e3481119c00506dd26e8b4e1c6d3177e4c3e75e14193bf933887d798a02219676bd71429bc1d10e176765f71cadd8e0fa549daab12193139a263aba7f24beb642d7b1002c13a759c30840978c4ba097c62331d61b281db82f2eeb29b92c0992cab28bfa9be1d52ce87ec06ef848f6943b6d2b7f872d19753adfbbbe95037d2d4d057872be6993884a612d24eac7fd854ea7e87548e05b595085942e0281cde4194c39d88d7f681dab247acf6d412885346f4707ccf2c5dcdf7bedf5dd002c03001d3bb957538db3ea7fbc793ba2cd37482bbbc3466aaf1344226fa3799515777dc0225e1fbb54e22d921ec33654102ac1e8a2db9b7bb4f745c317ea8bb296889d75e19692f86b76ee90bb266d89a2c09fb02520a1c581378226878b1474f50660f78c3eeaf3c260a4b3f30c2c23f38a1ae8a8ac7ca9222b417a796a021582890c817ef97ab63fbe5466255a0c3f1b684f8fccc7e6eb3c47a435fb8d1cc817421557bd5331a2862134aa7dabdf5e72c4f595aaba36d8447ba617722ec28f9484738ce3f3ff8c3543c9df0120c37f9d303181550530b6d36cd7ba0b719dc0eb47ad0c4c9f2cbfc1aef7b96e80d215f4426b34f6b2ffe33bc2a1ec878d5cedbca4060e06db58c129fb3798ca7ee07dee4914667374f93813afff3f772b6249bd649b781d2941f049832e3771fda8c5fe1d0c891c991778f63a7747cb88b3e05b8b884828885440f2cb7ab021d330bc4d4d0d96ba6e470bf7318e6e6157e1b3addeb2eab7e6b2299743fad1cfcf03fdf026213ce48fac7f91693d229e64f103702f3b1b62a033a274b08c765d8c3791dd4db8f04022f28f57ef4cd9bc71616db93d1d07d7207d3cfa3c83a873491095ba3acbe5da1a05d3cc29f254508ceb6476a4a1bdceeff6ae9f9f31450afa97f31667e1004329f9a9666ede9e806caa4ca1efb2351f7a1af6b378ba37507111c56c2e4d76b5aedcf944b9b6bb6fb793aeb088513836d475a20fc2ef361a1280e1079d5747027a5efc5d623941d4fd8d3579cb17dc42f13e538f5645608cf85ee5895c195325e247e5824e31ec5f83fd6d5b868036bcbf4f2c24be4100cde8178eaa66a034a357c9a1920bc1d70b238ed62e33cf0e37f42a49b8b2f9752433711d94a3e6ecf9dcea9b7c8d4ddb7a7c0058ae04bc61bfbf7a05bc7acfc1823d559be4dd06428cae8cd392eacb5896f2a6a4100b3012a8ae9f4f400cd1c88a02c68eb916021891d882afe5e623dc27fe1abd55d1c30f901e40019d65bc9325c10ada4dfddf4b59dc19731f2a0aae1385ac302c8f5f65aa73bf7a4582881b3d27b467d367ca2530b40864f471b57d13809a7dbeee4461033bc99819a926a084b735c70369f9e61dd347c97a931fa5167c3d51700d7bd79ad25681cf152ab9668dc982309076927a1f85e08e6f551a3f89c10add3602bb2f5e19a12321a0f204f7bb8d8ac5a3be4eabaf01cee1f687a29b7fe33e84436926005e4dc311ab8a44e581cef9c88638d5fb11274d8b1ef1e6e9064f409ac0b992d31e99d324031e09a1cd0f82855921d2d2881f0f5a4207748d10a403a04d465c008856251c91b0f3af112b6afba0332f621c6d25934a273234057948019dad33947af6d962e226857644bba37e0c03151cd3aa38293aa3ace9e2a9bdd16469e5634a4fd987f30612ae45d6a067edf7e51e56f10016a4752208096722e8fe6dcac78f34a975250869359c87ca8eca7d230b01e659769afe11d4fcbe4c964269734cd7da3598e3fa6f42303b19c450f983d1b973ecf774aff043864df2a2d2b31d160b93824443e4907bd8ca5c3f07c7912101510bf13b734a58d86887ea95b5b9baa27c38c487151b07d3381db4c56fe65b49abc9cc76db119fca588c8ece62a6aa28b00273867918bffb8796bcf3f0884287d8d7830e4c3662ac45c1823f34c4e5bfe6a98184e378fa9153b1665be9e9f0fbc610e34754aaf5c86a9f03798e2487d0c61a53413e0dc849a0080dce9ec665a04b63152adf34737d9d3e6d4c582d593dfe9c2d3ace9634555f339d3ee17ae4e295224c6f1643bd99d04eb33ac7416d041e9a496491c241ea668a806534e925a33f199a5cbf83392f4deffa37239ca8e07c7f171358415640ea4683fb80cfde05c6f30f3e8f9d2f15b8c3a0e0ca3b73f95bda3705858bcb4947686fc51c2fae87bee5726642c8b1a8154990961250b6cee87f9eb1a32b3657cb5ceff8048d9bfdd0a502ca5271c0c56e3b237a782496ba620809eb15efa34e0afe524a1a0b56db7c58d066974df6c1dd24a23b4a0a48bff6a10592abc339644316129611fe9c49c0f751c44f88195fff0353d69731c0a444e4aa0f7ee350e1fa18f12e575978e25beee04c2b042a0ea206ef51fbf0cc707e40a904a83e2a19c8904d3d8bea774934f138f720bd88871ced7fe8a1811a68d8df742a608849aab7fd90b95eb8a6970698430a3c720e05a2fd3b174cb6345ccbff282d7da3356d364ea437f859c959b9d10639221c6133c94dd357d45080d9645b1e804ac5fc24e751e624dd233eafd9a4bd46e45faac314e108ec77b940bca00be9caad4671107c91883dc79078ed9d242214bd5696484d8786fa7bb4b303263b8953a8686a2c1f07f9e306d844a4afe6c245fac954f8d667a09b1026cd8d67af985fbc8f969efa19f0b666fca97553a335a06e12df3b583f21b8583a9a79e8372e1a40c805c70fa402573beb132faae42ca492d80fc1257952c24ff524025716a9dd5461c89af73c9bbcec7ecdb7b990a85489c9d9960c7bccd8e745b880cc709e7687027cbf02702dab7260000df6df86ca3314693964a7147de667560bb3db2656ccb2cd1b7493220a9488305617330ccad252db6eb094b581c2a69c06cd773c3c611a33ce5bfbda061378a657d1f6c31a2bd1f6092ee403156ffbb3334cd49a67e47a5ac9b8b751f2592ebe512c3da705d99840d246dae7bce7d8154c2f35f9483d0e0939d1b4ced99b6a47afbd99a0d3c4a6036e7af1de05b4f4d2d1b32cb7c45d198aad21d3fc484e2c0307b89b8628dee366ba2e96b0c2a1f580a38255c17192792aba80badf7e978e30e42b346e8cc93474df35e2d0319199c0200e2f0c168ad9803aa83408ffd9c458802d226956c41913c17490fc5003e00e67925de798b0cda8d5eed7431f7535d0f1d0f052c5841f13396998c49a48a37dd00a4dd03a4d29561f9b311f436fa1e5740ec6ba106f1631d0e652278cac462e6afe78082a2d18d0109b926633c410153e54eb9beb1a1ad23cf6f1e577ac8f1b3546bea0c6d0d01cdb98cde4e356ca1cc2d0d0f897978c6d1a91fd319ed55d914169d32c343406945bf228ba2ffe2c0c7ca5f888b19f9742435946d01731c45944dc6d2c665625f0d6587ad2de39716aa244fccb73369412bcad4ccec3237ad9d0aa63009eee0c9b5a0f334eb31d73e415be6455d70917d28b6dd632fa973a1a9c63df2d45ecf2c4a921ffbd9b4235e441a568c8abe79927889bcf7e7ee417b2f23d3c98eaf770ea9cae755ab006cfada004af056026f401e18ab5b5937f0034641c92b417f59c627ccf2539f2bf5c4064767ab39ed6c01dcdf8594085856064481b430f8b2cc1c3d167ec61f22cbba8890d5f17baf63bcbdc505776797c635953dfb126624cc7a9ddffd784963de0f4f3b300ae5728085a8e8f2c2ef9618598eeb1b8daf5b31e6eb67eaa46f1521e250257b1e3188758b78a21f7f1144943b7e4aee978bfb1dc28a95ea03b6321a0a0e6b5f53cd1d10527d4176cf93525085e97427478cb5ee6abb5d6238462df3a2041b9aec01d99f20219a8f00b011409ca22a7909f239b1da1580b6edf8571af32ef8874c0a3780302b83c41f7ed57e30c5828bcf4bd04b8d9b293a31cbdf05516803134eecbc0df37d697382baaf8c66f9cb06621c23da3ccd14edd41428af45302d0a0ca353fd16f30f8f318c0fce43a6bdfee2c330f8ff0a80b91f3273e37b6484baf67a1a3945a394b944f3d165931ea5a4858c560800af2eedd82e9c3812977377c6c6339bad9351e3877cb5864743b44befb2a1a98708016247f8174b1413d82efe76f887630f660ac3039f4101458cd25b5c6a05e7bf31024c78d36f3ca53b5c91f672a964956960c775e80228c81e73171e5ca34c2cfb15d98f78b270a93b82191a0ac5482d882884c4eab21ee7f5a3f3158fcf6df191a93ce10bab165b5cd33eef7b3675ff2b2262ac5f2fc9dfaa9f5846e0a1c8eb23ffda8e459860426f1ee2c49a5f4d14b62d80d48d6400e6a42fef3c77862148924258fdb7fa4b0be1dffcf25cc8ba475c7d3a78f051c20350c4be802608ca1d4fe8afa390bf45e03bb94846786e42e03ccc7b7f842cca21545df417eba1e7469273ae38c4e2117f81f772d1acb24c4acf0c707a600654b21d96f9ee1cd82577d650c46b2fc243fc8a92e9c4377f25c8256b981afc17281a2d977578fd6e7e5ad2d5567b2464996508dea7544578541efe250c95fddbf958a373b80c57eb1d04fcb13b14cfb3b5b8bdf76e83713d1a4c320406af41fff1ee23e906deaa6c0041f1d9adf5fb1bb56237f607866b7345c6557bc119774f7dddd676aa0b1efa4a5ba8f10d7b6741c67311991d239e2318db93651af27e587d3f5604f5a0675d24b71e931b531c7245fe27d248d5d5ae8a9a64d62b341b039f90ad395a99f7f014a3daf22f6b0d24c1a38686a055dcf94b2d8572cf6d382444cd31ea8af32c1bb011a98b8b69df8cd243511310e481bfdffdf59dc9b4013ce32b72a29497f527c0be9b2393813e7b55cc0f5a1d3f9037950fbc53af8b0c185c411f5f880e0deaa51287a719fa18b82bfec6be3c1af461ac16f981cfe7ca86c60f8110e3ea286fc0da5dcf294058f73e5e3620cf552eb074a766e2b054493d12341c299afac1e4037213af44a8eb785b2b4bcf3e081c49b9685d3e7e0c0b7d9f4bb50861e4e97a83216cd1ead54f12f894667136d05abcaf09dbab318ff0904bc0368e3805ac82d7df57b57a76ad048f8a5c11c11c23048cb810c0515f63d8e3b960a64fc4d09f8a491301f0eab4cfcc505a86df794c892c095af8148d84b48d4635bf7405dd5ae9f35fba1de27fc18209f8b924e0d9be53e172ae94aca3bb26dc07f650c12614c479b1163dda2d08625c1881272be2a36c6e63b6412a67d85d9452e5a126c1f46d699b1b1753ea684cdc30a4777a4e520ff0610cfc484ff24f4d8c29219f33f660737182befa6e623963890ff53ca1e3689e9a0509311ac7ad383a17f95a6343234573f5c62970c9c3fb948fea27f995fffc4e162b55896ed0526e9fe392c077da2082c68fef00e433168ae3ae8fb6109908f0ac9b6fcd409d415e45375f0e6213e16533d626911fb7b5360c10e328c04423a2ab6c106f94ae59e35bea3903e235a29f4f10cd642ba33b09bc7a88e4903ab6157cac5b255e161c43f252ea6d26453128a18f4cfdf3b68e8e7cd61abba085f7c9d99c20f4b9116992f3eb36331f599f6845c4a8825e3d8002c33164799075bc1f9f3373ff49771179ab89fc7aa56cc60af0d474390761114c38d71870fed3fb9462bd63c736966c78b45263d7d26e17981df2e1baaba9dda2b24d2bd5591fcf2892f9984d128084fca1475467d2d1da996848948c52f8e7de949b9e03cc46612ed03d1dae1c704d17998771ce7af4ef8fd1188882497323dae7136732c6332217bea2bf2481ea3b4e8a7cc0596333c5a5a274e3f9e29096d3ab72b3fa231d8415b2d65cf362ea20bf43e3141e62f7c3312b119d79d365a93cde83494ef914afe180b7178864592e3fb6dfbeec3fac5b8bf680ab6edf411335a55e939597d0692c0bee89402384896516361ed06470a3e325e9d9a4279a1cfaf0652067a2d413f0471c4220c5cf8e8291fe8369f6ec633c9f7d9477b07aa6a7a10bd1da89565df506f25718716080a2a123678bd00e1557fce34d72793f8d1885c05f00ced1cea309cc8f0c69e8003675230a0ff32a552f8270cf5dc81afbb35ecb5274655790030677e8dc96474a0ba38e6661b573a873f4b0d7d4f75b9cca52cf29005222cc8211e194710899691afbc33017e9d5948c1cad5592f100ef1c603d7aab6c80e486f191795922c5cd3ba61dfe254adb74e76fff95c03f6d2e423a9c62298941ba8c1a2a42a1411005f574783983b6fb1ec8758318f35588fa83e8dcb112ad1558838ee0693c06bcbb1a7ee5461f882c292bc6f415ef068a64a889c1802f195fb3db1526494436142a5d3d894e606c4c33917721928001a5ec743fd55714cd366a71774b0df8211fde296c86eb9d174b1347cd51ed7fa73309e72edd4a93e3502cf31b1bd00a71e6a0af65dc8e4030e7d4a6ee469a0c67a9bcb2e24873e972ddbea021d65ee11a67cd296a4e365a0fd13e1ded90f219181ad20ea7d3ee2b6e773fee709439beefd52efff063776bf24e57f1cb9e92edb54a8bf904b46f70efacf844c4415ecc7413bf5711a41c1e12f9044b6a882f900facd184f0abae86dfd84de4169f4c508162492d16d858ffee2df7f7c4bc6ab0b45874c700c4636e706a283db902128d610e55e1dd3c41c694bdc988cdbb592f6445495c64cff52e3e37235c3de85204f6775c82a0810f39b0968993e4b7b70480754cccf08ce92770d60062cd4fd68c0dda4472beaf89fb2cbae928892146d4643b8fa367f503ccbc0301a83c190aca98f5c878fac41bd25c9149e0cb4c2b6247dcf99f859d307ed7bd2af9f3988b1079b751967558c42ef424eeeee43e5439a00d16aa98f9a727c699f033a7cb2618be02738c043186eea545597050865c060f5b0a154e494974daaaf0daa8ac4c2cfe1dda1074565c41412902e8546d9e752d4c352a0b4b5d1f4ac3292b687153ad9fcc93b2c3035532c26a96b2a7e69c5f982dbfdc2655cb3cd1f889a2a08bbf883980fc41b454b33572fdbd6d9ef24cb080a03f8bb92a150f9a53c12f4b393051e0516cf233cb9583abe0d26385acbd5bc1e9c67755112a739b9534f4c00c9a55c6aef0621824767528b11e3c066c9adb185efe676fb347c2a860ebe8e58af54d300a9b304d17b38fa486ba36bb7b61cdd655091451bf55d2143e1ec897cb62a5d4a3456361824e428517f5af97a77f69e580dfe6b82842fb89d0c49bd3c5f36922ea7c890c55de4faf2e7c02282c159d5734bdc4c45a84f292c721f2f36461b9d5ef6c107663cf30426d4d1f3160d3f86e101dc7f1642b7b092e4c2ad91c7b8408935282197bf974c975d201e95b79634e5c33864eb958273f781cbc5c6a11fccbd9a306e048c6d425338965f9728ef992a969720747325af16b48884f3a87aaf8d438ac9bd906e97b82954ace7e2ba378db35179f949bb09854e7705fa1fda630ce122e880b457c68670172050ea4ca02f2be8743135c77d9e740bd26194ea78885476dd6ee42125b30eea87102aeb10c3841073436a7d1402c69206f4f0802f9baa8bd67f39d0c27de9db3a5089818a9d2fddb2c348a2232dbf7daab43bf5cfe4d2f73f32a2ed5c5fd29e4a36a25c8eb6d07421c6de2a7f485b98f9baa861050cd7ccbb541368f73668e93208312138a528bb2336d5671a429747517c712e452e0cac4c18d9503b5718c8d97d6eb3a5b8e5923ba106039bf4dde3ebf2a01879ee3b668d9e11ca3851ae06bbcc74802c2393c0c7996907342b4ef20606cb52a39cf591b3fe72d67a34fb3f681de528ed701f8a40e0359db72be58f12c337f8bc7135ad3f3f15385a6148d2255d7a1e5af3c5387d494c6b97772bc9a7b974c46576719db46d5e8ba12e6c15fb3e4294428170d9e4866331eca07f2872ae69ecccc33456ac6855200a7173fb0aa4fa828066652ac1827ea4810e6d5daa94aa30e134cb9c841e6d85d961c2d86c38019dac91487936f77e5658c3234b54addab6502b8870e91df3b38a00b3e41417f55f49b9a956ebc265b5722dd684eeadb3ba279d0222b78964d9e685846ea4a80ef7d10f575c8ce21b51a29e81aed1a935341ad84cf4ea799281e035fb2f6e81ea599950509d224b8ff372d1997cef86eae563bb0a3d887a737045930675553f9200e48c9dac89d21c1a154bbbf54cb9703f29899032d6516e9dcaaca79c75d2dc761816fbb6875ab6c22fb79b9917451dd6bf9ebf0ca8ed822489056a3befa110cd8f5c95225395cbf0157bead8683939f48131e5e76ee3528a20d8323ae619288b8d77dea8b9964b1c1b92d2c49c459614ad50190616339b8281391a5b6bee82f2f8d098c9bbbcd9947caf711366e345c126f6a2ed9852eea45175ce0c0c07d089e70b7d8ccd3fba09f3363311ed55904aeeddef3f00dc3352bd18e24c433ec658dbd02f3a013893885adf7b96b4e1d899bcc91e0806790dd4978d8c59db5d075f40e5744f576255bd7816d8217cfb58c35e4a16f4b1018d528fb3468b5f4163969c78f45a0d2d5a32a281fe9c492870eea5cbc3d9d5f151380e46a383add0c5cf1ea679fd026ee68f42dbbc53d973cdcd530cb381aaa910daee7822e423564a6a757f0160745033b92854ed4133523be8d1683d369b6d980fc590606b3ec3a3b201e6fa64b4b1e02d3d2097291e0f54b4a059519a7b1a4347a3cb4e288f8125d357dae726638b95d4136e5222ed1cfd3fd5c62329f48a0fea5a1553a2b14cba21f55dc969ad86f1d28246d2945c719acf5c590412df332ee4e4fa1dbf6075c46b058f1cd5d873c67333c12c91662062fe879d6aa0e4fe330ef1ed06c92e7a9ed6f74d8d704b79fd37f9343e76962267ce9b014d51604557c3cf429fb43690f4891801fbb738a2901c86a64fbbb08825c7986052786786dd5d414c32861f2ae4ec2e0d0438c6a146764632a3dd25e4e3791f9b3d1e35ae7f4ad6cd17f0e004475781549a9c971954390f3ef0b7d310ded069e6896381ee5dd8183f9d69adce7f222380d918fd70435e5088df77150ac73d820c7d353b6a9f9a8d2a53793063242643a17385555e0eb9163b147fc9be7e208fbed7406b155c4336afc7dcc7a671a89b7daebb6a8574a95e3af8d73b7ddd688df10d0c284a0afe6a64299ffca6beda704ccbd12b24b6176eb888cecf2219c7b0c04b495b334cbb7fb2d82648229856f71419bc62f494d51b6dc9937caa690394fe598f3182d11fbb527e12b1a8017aa004f64ad4dd2f9721d88a2493abf35f94b65d025107d0d1c7a466a08e90ceac13eabee18337a8869507fd1146680ae5c66162aaf64134957c7592c485eec09a15fb31a56a6ce48c54daaf542b2878383b7687b2e63a7f411eb805d1700bfc56d072c9a651448073615590c70eaacdfe158067512cc79e6e3261eb13d55663b82b65b18282c8508dadc1b36776b196890e47cda38bd521ef61fbcaf201f432218df35bbdca3be2280dca04fbc575b5fec3bfb4e45505e6f4e03dc5a2295f2cfdd929619c3a80a92d3ca9e799cdcc6ddc2bba412c43c568aefe0909857824b6176f8728627b898e99b1683b124c65c82bed4cf8e5f177a87ad263861a0ace1efa56d171ec5c5bab5a7d70b4b1e609e70733ff1a72eaf1516202ebbb2517c1ca2a5c44f9f7e72282127882a977cf64faec89a235592698c73bf6dd6afda36cc6f5a9f31220518e956b3d3ced3f35157854c4925006d22bb394e17ac656609acf1f69b0e8c6ca12d63b580cf1336f733e34ce992a713ecfa3184e506c80be221e500c383112008750e509a883d70e0875aefb2029db9689a9048ab9bd9a1cf5955682669f85e164ece85ea1b3296e62edd4f22654fb26bb4758339b66f242faf49621dbb644b5a6399ca344099d9fa48e17aa0bbabc6f9f2c6dd1147dbfc777f53df961eed49ddf126583585efe9023743f14232b1347a183dd76f3a45c5b0afd89896bb12d5b702ebd31061572c10f894cde200b654171027b610c193ebd95e82565b5538bc00f69769f409b40709f969a6530d984e5ea22e0f689912b09cd6f796954e15e4e91fe09a862fb2d35fc38a34dbaa978511e74d0a26011b121c07ff2dc1172a23ddbb6303233f2ab7cd7749f54ff2723673d173225d4daaa092af0b4158830397061b2b27565e1ea7fe2dd456994f32f5690afee26390ebdf8dd67776d0b53b4634bf81e2fe734580efaf1b8e96d5dfcfc51862e12273270167a2c6a8ddf808f4f8459eeb8c0dabf70f39e347a38a943e073375aa435a8b6e69bfbee85320a82952e32d3839f56d31d647bde2c52422dbbcafd0d416c3a80956b1a946dee704085f4266b0a1ba43d043a1a1f9a5a305c15351d9981a263f758ddd46dd4f5f7dad545b35ba6d52a844a7eed36a439734fa8ac5fbd02c2071e99211c00030b60ca0084d1bdee9c330ad85c00f4001e6d6bf884261f632ffa08f661bf35db96969a889051237bcbbd777309f508dd0914239d623b3a15fde41487a563d4bbbd77637b775defcdfd60c3180cb3d664fa51287aaf77d7bb1b932fd5c5342a7805bf53d74d267b8750f94ad783e8dbe5556f5dfeba3eba4eba56377bbb6b92e3e17615c3ac35997e14eabadbbbed9aecee8750c117c7a651c138f6e7d5d464abf295df2136bc826d64fb9b7cddb03b664366d1adf683e817ee21fa867b8c7ebdbb3e62f2752ac2a347f96b4423a625d2e57997bd2eefca7e5d5e67afeb9862ec2a81bdbe1bdac8df65c3dc1f3f44895e5f595c9c9edecaf920f27e9663ef2af8266fdfede554e075f1a86511fd3159723ea8264ab668717c1aec42e52d4ec3f2abb46039b6d1295a5bb438cdca555ae0d8a92156b00a8ee91475717f69bcbe2a6f7ae9d977baa961ee53b2c6a12a571c62217682435b42e17bbded1db9fbb7b82c5fb9f82ad7050f5c02eacaf18750b98b3777cab1727c1a95bb68f17b9a16bf2dee7f4fc3f216bf1f82a5850b7c67963ec48902c22e30feca695c541bfe62081bf00b5c6f8bdfe4ebe2aa1cdf95c764adebb29925d75d931c0faa99f9af7c0896ab5c72a71cffca698e574ec382af72161617c72c180fe102636c3b557f9323e7ddff342dce72f1f1102d70c4bfc9337366aea8dcc7c8d0468ec72ca52da9948f3e28d38629603fd891ac795506512a67c823396229bbf664a98a41a9a44962cbc21695d4c28ecc17f9932f4b620a841ab139383ed6c7e463c24c39312726873227f644ccc7fed4253144512cc2d86861478290c8217246fd15521b65117246edc6b2882145f4758f75aa5fdc920c6c1879ec44a15e8c883076a6e2651147f0a1961563376e1cfab8f9aa17e56a5cafdd5267bef4adeb32311cf3b22ade262a66091c8da1666248bba34b84b13145fb6e188e9457cf7c8142cea0aff5f2c93ee8a504ebbf7e4171f9cc970b27d72b93e0e8d4fcb513031b4a9e70e664b90509836921ca5c4a228c4a7ece0de4190653155450c8533221cfcf9c1a8f1d003aa6145812c8224c66b060c8510b39c6cb0d48184c6a000231d65c3df345093e3972b561244f8ee461e1c99949266a5e20b64c0c3420a6e6656a0a24301561e506e6bc0c19b173c74513368463a7a475b16da2304f368526b5276b9e4dbdac96b4256d29a93c6273e1505a7d35166874935c6f0f36543860202f8bc82132070859e3001ea6c6b14d727cfc04b2a625130bb547faf03411190a9b086661d60ed9b2e843968caa92216710892a18a88e3d2e35b0d1464b53c5228df1891d72bc8b0c1adabc0559289040598749fbd8a64c122986c35196d8ba381eb24ce32dae064663a634d6fbe2a519d85035b34d548c14065bc91c311f1a04ad5eabd7b5ee9e3cf29440a84b72d7b48f8d3eefe8e3a3ca99528b3e78a6009d74e80f5d45ff4c54979460c3b884dcc74153f3c3290575c91591bb7265b3edb4ab8b4c14bdbcb1f796bb2524d8f0cafdf71160e3b2648dfa0dcf5ba169337368d533a07553c919d9eb89e4199fc81816976057765d5886165556bd33131533511876591aa31ad81e206a7834cc77611bf3a5071c9fbdb65374eef0d2325ce24be8b3ba648c7b56ef4dce64c81821cdbae488645765a2228d5844cd5bf333da8ae1409bf8bbd7b78b75915c37ec93899cf1913e1fd2d19641d2bff9e678c832094f2cd4209d7b64428166e9922e0ac644d1e310e110f349c0cca523368c3d618c0d1c34350ee01181eddee9b7399a30a589053943e659ba71a6b267573681bc11e95abce88626f0d68899043e720ba8f524d2eb4938147dfb453a463a7612965c0244b71fdd2f8b6eb2f5b66fdc76356d6241a289f96419a536537b943342df5ef1862d19437be56af4e9294743db4626194389ac6a9a96b56b59bd1addb6ed35e837bc4d99b357ad46f6d0bdc9968c512d2a35496f1039ecd3c890af44af9b8659c7cea4f36e44f6b0d570e863b368bc62be2293f9729d0b5dd355f944d5dce1512ed1c0d277cc0e13e67b3daa5e1527cc37fb56c819327296e87eefac6bd866e4bbe876e02b26422126f23562f6f0c9be07760f63b7b7b8b83d729c8f2d6f585a2163843eaff8a25e28f039e964cddb7028a42787dbad87b887369abb6fe7ba6fddf6cad5e87e3d05e85444ce88b9eacc977a9f1cba538a89a2efeef49928ea44a697c3514262c319c47ddbeef6942be6cbfcf614a0f912f3f6530ae40c99b7538ec66671b766d775af31dfede4add3e6c6d5d8cedd9bec63eee40c9f522063580fdd30e58a6c552443e89e2c57b161ac6a6dde2bc6236f8fa21bfec81b0ebd87a4b6d96fdebf7ffde1d0a6e6efdeb4d8fbbc57aec6e71d7be7c8193163de2947c3ebc9db75514dd4f579fbd25b6fec920dacf53a7f5d47c06579de64e27d2ef1de24f0bc7b21fbd0354dfbbeef35e6bfdf84cf1ef7cea6660e6fa11a44262e59f34298bc4acea00fa9f6cc85661a96216364af9d3bcb4e391a19932caf092b31f19c3d7b942167c89cb964a793f220afbd86ccb2d06916bac936335f58868c41e911edba76dd38b1df647ac3f3c8b15e77562c63a63b4ed98436fb864172bce24d8eb74920634c26b9df37979439a031a714b0886152461f9b2c420e298249e3585da04523489c884a62f013951811224f6c3373054cd9c694b3bc917202bf028d2db438b5162da24c8b162c2c518685656525caacaca8a844191595d329ca9c4e2929512625050525caa0a0984c51c6643a3989322727a55294299548a42843228d4623108c32202812451991e8fb3e6ba38cb59e17653cafeba24cd7715c94e1b8d8b26db165ca685a9645992c0b85a24c28145b30ecbaa2cc75595694b1ac5aa34cad94764799ee39a3cc9cb145467e5ede486909c104e8a70a287c789e48b2736489247472884821b26c92c515414b9848f12325ce1563d12bd30341a91461bce28a44b04215991e063d55ec9e2aadd78609c33353071a4329c5e982166d3062c3f853310cc37eb09f8e91e590962c8324905c226be426251356e630027b2387c8481f1313132fb52ceb9a40b5704c471965bcf53246a5ca22d7371614f3d496b1ce6add1c88b0b5946d620e43582343b055080707e41a56a1cf4c17d4bf19b1218dd9b558b3cc14c7999219111acd71c26c745249295529820d9f290a063da57f31bd2813e7fc8dd6f5a17b93fbdad48c61193246fdbc8dcd46422ddaaa181e949cd5a2966559169667a439d0faf29a942b298388af51e6ccce224f6b725f0338808359b81034777a16d19158c43c859846e44924cf2b7c4cd49c3c988e649922c655cb25661892a3e4287e01e3666640cec9040b47004a120435ec4451038b0832d87440822c18c18a220b491822054cb010638cb2bb37dad87c99b13b7637a594524a29a5ddb17b069452ee1b1762f63eceabf21095efbd76ef09bd61e9cd25a0fb09c342144d0e4bf701239f4e3b03418ce9ef3d39dd996949890a9ec8f5f4c5fbe4f4c43be9520b96d2574a573979a9f4caed78711baed9303f14141b504c9fe92b5d7b71633295dc0fd4647aca0d6f72cab5de5ea484348b5cc0604fdebdc9f4dad47cc232640cd3eb0d5db2e9338d82e3448d6ebadf090e61e412e9c689d2bed1b649ee878d3b0fa1dc9de3be594b0303857af1827a1e087ae1e87b41a905bd172f6e9c29cf761a0d8c9b0dd7d36b2fb237fcf0c5855c476fefa581a59f3032f7d0bd4b031bfa80912b07235399b78d865b16343747727cc46e2002d01460dc4004202964f93b6d3802f7892c49c581a3ac54ca48230b8a3e8d52cef328776f87ca4f1aa651c19ee6554e3bc7d1c44e698f9daa9a17d6ea795ead9ee9a4e4795da779ef3c4eeb3c1c59f4d1e93c3a55ff712a1de90453b92715fbbaee386155a72a27e26ab5a2d368441ad9fa5315590e3c5591e5b8d355c141a3824fafb59eab1d47e34315444db3548612c98aa84a011d38ca4e4e4a99157db5d6d1886447b556558761d69a4c3f0ab579efea3f6b2b0571d0cdf36a15e1eddfbfaf6a15f7b0b5b31da992ba3a1a8d46231044a12794aabe4ff483eaeea24f54323d364c4a8aa9d4892cc5a12a77e0c88a4ea3118ac99e946c6647d64a1c22eb6d9b8a7b941908c2714284aef34cf5dc8e2ef33653ceb28c4412812392c7ed40b9a97298060577f7c07b60e76d998769baaeeb700ff01e281a91482452d7913acfb3342654d7ed309d8b3b4c184b795e77af13bd870dcda277dde8ddb59ee79148249287c3eff624d2f72e7e64459fe9a298ce75dbbbebdd5e2e9ecb36d3fd4183824d9c0da5d96a48f32b83165f70a2ce3ee3911b87d9bdec72cf3e10147d190e4dd144b1567bab918636b0db87ad4ba4087e66a6b86061b32cfb68a3413c5b20e5a1e5ded9ad9769efd7d6b2eeee5dcbb2cc46f4ce3dd3a8956937d432dc63c4d1daa36c949548e006825a9659dc0dea43c85d7b86411f344cbdacaf69a73c3051df1eb8043c466c27e7dd4d33ddb3a311062d16711d531c87439baa51aa510f8737dcbd1ab0dcdb02325b9f5a9665f778e4d6de97bbb5a91aa59a46b9db542eb4a959c371cba1909ce1117978641b7a808b92c06176a2b089e298f0de81af4cd64e940ef41e078410e00d6d3278d1fbd8d75bee3a3777cab2efa60b66a2ce70e33851deb6813eb64d2385b4921dcd9ef49d466f9525adb7eed4022d32053cef74020fe2eddfd6ed7d8fe0d69d4de313f813f8ed21963f7c02b18d981dcc0e3ec3e8f7ed8636fd6dddf67e05718fd2777d6cb97bdfd062afd495e8bb114923913a52e77d218fd49fd733f4ee3b9d2fa47fdf356aff99b87bdb4874b2ef814dddb9ab8936d2a5fdafffbdf187c1cdebbc5028cb361105414a3abde1e85696652f1da4b7fb4e0f7e7be9f6f840cabdbba29344371c611f5bceb230fbbe7df47636d93b78698f0ed35b1f9ba5f7e8bd631f9b8fad1d00848d9cc1143370828f2d5b40ce608a194c71b0061a8eac659444414ae9290ebb6f9556db3d76c39470889146f560b5f5ab187b2aaf44aa28f444d64fbb77dabee3f495ef60b94a47e9874f1b5ec1342cf766da0bb7c74feb28d52a3e59ed9eecbf6b9dc9844f169f729cbe729a130d8eace1d0541fee2152a9448f128552cff3a84769ad59adb56ef68638b2cd40100c5119040f5ef35431df907f1cf81e7d2822d2e9f41da313dea182a3cf36adb7793872d7fd2405e524238128267b52b299c892ac5da17693f1047e74d2cabfbb912ecde923f0e65059b122ec43a5cdaddb56aeca4f9765bb3954f0cabd4bc382e35576e52aa759c12a98e684bdae533e545127c7f88855953c8f69a284e0bb2190fc715c08a4e32cb5f6d64e6d3be9edb449a7a5531c8a9a36f77a8ee34eda28e57ef8fe59ee5e73f6961702c99cd55eefa9c3373e0d73d7b8ca719d394cb9136cd35c49b471b7d72c672d77bb71da6739daf566efbacce4c9effb6e770bea903d72a14dc665dfd7894c9dac36fc914db7131c3de68ee3344dcbce5dd2b37b83c4ddeb6e3df07e3814619bf6ba73b7bb373ecc7157f36c9ac33e7e009196069ab76d17512cdbcff4774578b30dfe7b87b189a216f7681bd2bc61edde64f086417253d228c3dad4186b1c961e47f4deadb2ebd63fd87ff8068edea3f5bebd36959c0fdb1b9fb4f749c3372cb5d76ebbcff2287e05efe11ee06ddf90721ee79d50d268e4f5d6dbd6ed7d1bf5d6bd511c1beb9e7dc320b96d08247b07ad873b7a20f9fb4c77789b280f7cf70f6313e5dd7bf779d47adf4bd2edfb3cba7db4f33c9499eebede28d5b6cdebee9dd2eddaefdeb0946a1dd5a6b6d13e958f2d1f6fc1d0df39cd0b92696d8be93bea7d97dcbd5b9a17cf8536f3f346948eecebedb52e37ca9d62fa10f743fda9fb0e949fbe43e529b7f76e48b377eaf0a7f23d05d7efd486dde3f70bb4debd4b6b101fbc537cea4ecfbdd60e9f72a0fc741a144ca375df431a3dd605c9b5b35564b75b7ac38aed462da5d45afa7978d451da7d1ea51ca59bd5a4773311a5d7d2c7a3ece88f6ed6729f9974fbe928df7142d9918225f8a8a23693bb386b6d7c7d77b96bf6ca0e7ff5745b7fba33fd598cbbaddeb54fedc41dfcc87e23909bd67ea6439b69efccc2bba1c69dba7fb728f7f41a84e68463a3fcbbddcd91627f7a6bd7c65d8dcbc20c64edf494d39c3c1a149c929e6ac319b50182591a24bf344fa8d0dcd5b0de369bfd13b5bd5eee9e69db7ec8b09da82d7236d43b098d28c64a1ae91193747bf60377efd96bd753876f70d8c391defb7743eef16ddf1eaeb7eaf71f3ed4537cb2a7f7bedd933df72db3f806777a79db876da8e5e11ea25bdca3f4ed3393bb9496b651a4a40a8259b6d18df39e7d0343d9768fc361dd6a287ba8de1f39d4358cc966fa0478b8628ab9eef7ae7613b5a139f49885425b88f36e2fc7891eefdd74b14e79f8a10dcd75bbc6dd1ee063ce32108719c8e049f76e77431eb9b321eea6100e79944e4a38fc91edbf874c3814dd8b273547ebb32c3bf8ed8a0ede1ba21fd9e3eed9789b6d34ecb7cbfdb8c16d59e761198e1385c30cd02c7328c3a1cf4c798ae3478e8f41fcb0a4f949f1d988c9d7f7e9d58ae5f0d73729babeebf2baaeeb3c7c5dc44d527a123d985db71dd2932befdde34a7ea661bccbdb6818ee339d92ef6e4da77870d8466c628a4159ca08b4e19015b6626ca27866628c266dd22ba48dd094c5b044aed6afc6a185758e8480340c860384a988cde9db68982cd8503ee1c08ee1207285fd66c523ca707986308f4ecd03e9d4bc69e60cb1c252175fb0615cd2d6112bc051a2042272e844d949a58ff53a64be504bbb8f9462be08315fb29989f326a6200b64976098b526535c92b5deb488fd0822c2480f84423f3a4513c0c5a01f5186cbf4fa44a04e94b1995e2a1165ae402f8970090399864c1fca3164fa5076215329852ca74842a65c963ac814479fa892a9f5b09deb126fd7bc3f36943a2af43b3586c416fafb043b846297f499424829268afee8a09a3cc844a9a53a211d580b63132575e4a9951b1882944c21043948304308da27b70fd044054d54c7051b4aa06eaeaf4929e62402a353856c222ccd3e5186abc2a2204e27664e9461c9fdd93d6530719d3913d55fc9819539081334dcb507d98e8368b72692b9c444293175e6763bae260f620b1aacfcb952d83851f28a896a30581994fb73620924a1882d7d202020a03ec6e44661dbe78a4109a494b25b36edee96e10e748724ace1880db15c84d6311912369446480dc417cb23c8f4024654c7c49656c2368e4e755eb9810d634c0d13638d189638606b20a22e53080af632c1cae7d0215afc0e98e4013be325cfcf2000888619000160dca0ba005a1ee3383cf5500eb18e43df7ed8f8882d3387088a066c3873a65c71df68e623ca649f8fc9e1f34088ac8cb758394bca63343d4610dfc5637f8fb1616450a772b827ed3a3cae64dc93864f3964dce53a72b80e39dc25870bb1e1321e5777795c692ff029870e77b90e1deee2722101b80e38764ac33a5caec375c0425077390521b3f65006659f993208c705c6f8c696f95f20266a3e07193776aa050e51319d9a2c388c91658c1cca1d142a0587bf099770682d883fdce1cc0396c31b96475c1061b55010190efb584e177ca8b0c72bb30a071c7050a954383cae04704f9dbaa7c6a71ca994000420800bb1e1a947ec12464b4b0b0cd461a00ee34202f096c7d50df7249fc3e32ac63d497cca11e332aee386e7808500e0375c0616c2040d007ec385d8f0183711a0c88c1d260a35800be3dae8d4fc0d57755b6e8c8bc3cd38dcb852ddb88a71e3ea861b572d37ae60dcb8ba6ecc03a83153b7621bc6c400f4c53200ae0dd7c5ec7a9eb042cf135638810f0c0cc330152a8cb1d1c34f8c12248001a358ec819d748a9e3040a6101335491e2801b69a31cadcc0fb94353632c6943f0d037e8a5aca020cf96a7b289bc8f31207d200db470fe5111964c5555d49ae069781c4969969c17af77bbd46c05bd7c8106c057ebb427440b5fa3ec4aec003a1848a9f6c3373c5272c6f39c87ca9a71cdbed4fa1930e4ff4791e0d730a611ddbedb7db875227f450e684b045c2d67b8b74f7547fb3e2eea9e209d07057756abec34150d8e3805cc38a09eb71b53d443d5b6df71e9aac0d441932e4c981f842447cc9260b268e9c6123a708220c378fc8b3a661e28a1447b2c995a27bade65db9d3a91bc496f9eb4a2457267165114964a2e63c42c3b29133e4084a105b72e8ca5c8361d95a12a2c2528cc3c6445d2558f9309b1a0c8a80c4bbf6ffdc4a7c7a1082247890a80286945236866158cba6dd2d553ba07aa28ffccf44c5989999989923e4e4d0443125f24420030e7549b17a58d775cd5f3c72c7eb560f375986cfd20b368c3e34354772297354a2842a72d330810d314a4513ba20a2020c83a80917801f20428250980daaa0e9a805d49452411c74c5c1c1c189a886b1d13895cab91a89c086126789d9b4e79c162a478e20c6e80b4b1c598279491c216cf8bf982d67cf77b71665a62414d8a9c4199ee059459410d8b9842a46a0b3924300421474565189108ec0596975a3777ae0034728b2babaf0269f6e5839cc40c630264a7e07497a70566d25923f471ffa5b3fc1d573f55cb5d65a6bad97cfce5cd26dcd25b5a7f6486176b7a58a735e2eb55eb556975a65b89c40c7d2c9eda3840db71c755a4746ebb44ef5913b5902c9242a974deec82676e4ce04ca5d8449c4c2049a4016106850ae3ad3886d64a241330434880659249e39ada83ca8a1cd933ba886a7791ed03ccd9306ec48c67ea8851db17ab6755380381ab3bbad6e7aeb5eef3bdb6a2b05a84522c8735a279d2b03d9c2575b97b6e0a433516dd5478aa38c98b47a685c92650e8fb8e4013c2a0f2be877af29b6cc6b328a177cc0a7872592f8acb4ee001337f05969549ec00849f05969b5e7a70547569ae5042c663064a55d5bcf112772561ab60149c1b3d2b0adde134d0d129940e4089e55454d948cd911988004243b44622044d5348a61d8abc51e0a65c65e6f45f7fb41cb189edb7ca83ececc8a6d990b43f7369bd1639ac5a4155c8dcad19076648a17ddce46928c5d912790b442c6980f61184603bbee0eac0402ca92abd41a4111180cc13ccc091802e8ad6953656b94a996dd588f6d974e1486d5502814ba7693af4b2e4e1476b2b01d65d942a0efeef3406fccd7e79b9614b8ac895dd69453f6b02c7cbaf1d97362cbb22c8c7aa19a2fd3b22ccb0aaf8ba27f81cd2dcfda018cca4a9bd2534a69a5b23695b9be6fadb5ce1ad82badb4d25a2b0cb6a2d9a78956276dd95d67d3ee39273eddb8c2be2c8cfe4d9b36a530daaa1f9bca6682165f8419a5216d02413fe9a7920833833a456fd109058c760eae4b42b752a0dc9f411d6d6d7a051b5e38b92b9389eaba24b6583d9dea3fd7ead3934319106c587d8e4c597daa143f5950cb8699ab89335f84c064bd62a6ba02a9c0ca26dd6448ee954ca9414d727f0275aa59a2b0a16c02947b7234b02c9bcc972c70e68bfcb5a467db208727d8303619126402992ff4dd6f988a598093a392152891c3d81395a8e08a091357f3a1b5d1b9c646c300d17303096c189bf467a6cc01d23038ab6e15489181f851d3ef1f551d42a8923a3c35353535353535353a74e8d0515353535353a343870e1d3a746056a5d9d1a91361d69af00e150cc334b15335db6aad12c73376fb7d1688873db1a7fba14caa4e81a3930eb2a87ce5a39b43e58461262ae4a715dee4ec212b6485db0f1277214da35aadb5fe264b59bf6119f2862ed946c3ad6eb20cccd95e10dd90268baec53967ca34cddb9626839fe91f7b4513a5eb94bf5e1a581819c60c7dc0f0ace63616154a555cb0bc45fccc2469dcb56ea41c05ff403ab7e3bb218cfc7ddff77ddc51fe5d7b7139eb9e4e180573971edf1e46b724fadda2f42b2cd7acedbb28f8a44bce87ef28f8548f727bd23dd57b2761584a4a4acaa9621a1bfe22936e714893519e82439a9c72edc5a9742747a3f4931394d3afd3c9c9bbaee34eb89393128e13757f724d076fc82393aec9cd0613a613755fdcb89ae97b4f9484618c4e718fd12deeb18272c38a7dc0f856e88a0b96d7d3b75061c1a1cc2a57b1d7ac8ab5a00f16ab60dc8df644a09d74eb0257fd66e572c36962fc86cbdf80c3553808ea383c48001ee3372b19f7242fe337ab1c6e8e1bae3acd0d575d5ea5ba0c1c0400573d880dbfe13738a860c4b8a1c58a2013c523771afb25777cb082dc30bfcafb53672a315fb61e1934711ae6f44e61d189325c6619819d79f495539ffebd61b0563daade84ca4c54131657465ae982275dd19da09b8e8e4e5ca5c4d549461d2327df3c651fddf4947bcab08ed14d271d2b4ff9ca532eef094bee58197c7a2ec98e53ee12ecfc699fb9a46156c209c40903e28a470d5e7918814ee4e36af430fed8187de561ecc98d250f228c5e4294913bb12507730711e6e43d97903170d00521c288c087272b57f248cec8c953ae3c5d693252fae476581c9c21fd8e28ebbf574c599749b4f46918d23b943ff2d3a7979033641216b7b85e4c5967b12e77a24c8bd8622d911b473c7ba6ce9da73b713ad54fb9132574d3a87f72a576a513dd133b05fad0c82e87b49cd149e4feaa74e38a74e36a74e30abc713564a56558eec49639244485c5b0c4012277e2e6dd78d343a2c51761c5e9e6e9d69945bc775c983af3257a98c5a2a9105b0a39c345c628437c41437ce9236223117b07b17d109f105f342a2219e48cedfd1e4394e1de2d3a0bd974c58b1332cb3b0849e40c1a19830511a6a65344fa501841471379896cc402b1a53fd7106180c89cb9634413816147c4549f67e6d24397f4eb82b7aee8d45ef2ecec441996fce5fe7c22a50925a223cad01314f9294293d026624b9feea89cbb31b69cde82e5ca23220c7d424c519f4e5135649ac54579764dff9158fcfc5891a3613ab9d65ba649148c541a5deb596777727a826e42a49b944824d98488dc44a0373a6dc22b92441699454091682659913c512672356acfd979a73b0d439774aa0a3983662163ace02574872ed9c9134f21f7357be71222aaffdd898488f291319634cc8c22a65a362137cd625291db9b40b1a53f9f0841c1ca1e9eef4b1a66a3594cda853c0aa91ae47da88fcc51395d79651228a66b335140666a1ee7e7e4fa285d1ca4db0316728604628cd9303444be9025164d64938804223662cb3c7861c8ef456c51cdd4fc8c99491a8bd8d2e76e8f21b6f4b7db6490319e1051fdec3615b1a910532e72464bf1e284dcef20c4543f0ba62b4c468022a67710db07b19188a8be75fb8888eaeb6822d3c8199d8405d146ce6822b7409f202ff1851cce1d34c4598688eab7b8f3e72ec9730d3175c41772bf81763ad56f2372bf89c8fd9d9db91345c5595983145738d13384123d4284a1064e044d9c23e008e28c789f997693b10a9ad812438018bb042b63da0c7194fbbac194410b42ad71b307c7449d64287778b7a7e9fe5dfef3b0e9662531695d550c8aa14924cf1c2c3bc2863268c917ecc4456822163f57f830a9a24891bec4b00c8aab89716856c8162a08c68499420499a4496654c3c8d5270e0cbc3083b4287a767777239948827690c8254c41f2874221c2483004fd04fdfc502c836415f45dfb72466155da54042919423e620a9f385f26206f83e509d8d82c7d105bfa920731c684913b9bcd0f28acf56a447bc368414131413287f1c746187d76421782854e8f248b7c801c95d820284b4e869e5d8876ec4242cf4e7368c7ae23f4ec9403bb761dd835eda1ec210d9be46dc8277cc427b2fc4c1256eed022cb5f3631280605d13c658eb56a1017689d97143a4a7c8ee8d66182c57efdc77cc18c881a4684636a9a0830b84294a1292da74882fc81d40167a36144efc7663981053fba10d149affff9a9a9694084912d883f3a37b042981042bae8f5a7799ad1c1d3902e7ae44e13e718611007618206bce83a48d8060e0262535c31a1cdcc17d90f0390e56d8833a40a628c3e6647a4cbbf8832f4f2a4fec804da9823d8981d6acc6067cd34733eb4c8da8fb78ef4a4adb15d5e9e6e5ce217b1e593f235629638a4f9b352fbd4dedd6f363314ea50167746091595914e8e99b19161a973d56031ac424dd4d502454d1d6f762fc8c15982176ee0830023da6d4c549f6a60c3c86363c665c26857990608f20307a771e41ef33e54f9d2b02f582c54354723b47d08eeb153563643eb4395352c6b59166ca4481c59e28989ead9d34f744ff360938707db393251488e44196ed607f1aa77337faad66bc534df411ce47b909b5588cad67462727227b6f4e3ec79e4889c3187e496526b91687411e95e0983251c4464799774633ad5a3eb7913d38c46d6da8b623ef06a7434127d640a32ba27bf5b9ff634e02df80f07b1074728d14d28d3e88ab0195baae4ce13d4fd79c57ce18932a1dcda73b6b68d63aa8285827c820aa89f9bb9e9c3710f4598c9d3a966429e3b1665b673aaccf950a5f05899b54f80f61139033322cf6f9916ac7422893cbf5db3b6d8309e05bfef8237766f573e617907af63748b858c22f81dbc8eef2016c2040de9e085c44e7d3ac07fffb01012963dab4943ca9f3cb17c42c3fa137d9f083cf7378c45babd90d2c15ba4dbd3906e71e920b60d630fdea29cc803ef8db090efa35b17f54b570eb10dde3b0de85d748969be8fb010fb9177d169bc8b70ec14f8efa11c228b8097c6298777f034de413c318dfd085f744ff3403ad51360405c7d380810f866f5b8b28f40a25be3ddcad8192957ce876885acb5d44f9e7a2ae4fe09a70f0ff3dcb7abcd356459a3bb7c8dce872a6fdbb34b6dd2d0b013b7cb5f903b3b4d828096fcf8c81596f1e4892d54d81096488e4c54b73c92fb579ddb4ce9014e963b71623151f44d66d0bc42c76727f3c1ce8e8f4f94b93f55e820622193cc9c91800a9c30740089945276cba6ddddd285ba58392af14952440335c6ce52e4f0b109108ddd48f639e79c73ce89a53c92e7e90d6dae4c7ffdba82f0ba2f735e71e412ec2573a80ce51902a28930f597e71ab2c441c835e41313d8d02501d488f6992977e6cbd430dd7246392a392f68864fb28fd91158ab4a9e248c01a358b52eed9a9846bb8957d42bb6a0601cb1e13699ac0d7d62d544c584aaec32511d635355a1abc246faaa186ddb3ab3b6b7296742b034dfd0acd6758955666aab1709565e073c2d833e7264a29640f54347e20c2901349b0911466aa18166048a2d68b0611fc97d7aa49768249d2476f7916f8fab1cddb7ebe8be5d88761d9c90ed9926638c38fc68ecec9eb28796cb30131ba23cd61128a04082fe749289a2bd83a491ec20a1d612b1859a7e620b7d88084b7f58320dbd4c2189f4ed2c456ca1a71963622d1c62cd23c40ca080a20a3aa0a2044248a1831c244890f8f4b060618dd0914148924c89058e13990faccca14492f9c0ce1c4a2448649034431259be590645999bfb210f6820f73128260a934155d8cbe201425225164d8276ac8462a2ba39196465108c9e257a7a7a7a321250613be7f280165f348f5944eacc588247a684953953c26648d896417c8999a8b89a1151fdda84c7090b6c189b0c6131c1ac41c87a6b96481aeb03e9543f74b1192d83886aab890ad5c407045fa062b0d288c61796437070e490a6e8a8740d97b4450b158d0000000083140000180c0a874342b170409ce7e2201f14000d8ba64a7e4c18c9a32087510819440c21860000000019001860b48d00628607bdae1afb40ffb997e327352707c5c08a31b0c6489061496de9c085263ce1ee420974353c2c846dddaa2970138d3b18366d0fbc58a44e6f2755c2a99ec67717316a3968f5c2933e9d4a07a9509afff7b4d8c0072a1b06556ee68ca99b16357f77514715f4bfcec3981594865e7c74ef684e03124c027f2215aada7ad1c71c1d48158e77b03cd4fc2c5f47fca94f3a3b503a6f068dc3a14d770f61384a75e19e8350a5c96202734b3c1e23cd2563cc4858b4615c368ac40e99074db6073aff19db91e526668a4a7072c223bd6307be64872dc40879a83d1ef899ce4bfb6b618c07a9be7434516b19268ca6a26f768c7541a9fac9b1395b16992975874c8297f399d93b2bc8546075c5dbb88b3094877b48c6ce5ebea85d175ac86f32b60086657cb4dcd95ba2c0381608813623b9345baa6bd30eb626d18c83cbb42ec939fb0fc90d535306a2a58205c5c511a1a5091ca9ab75e852704458f038fae6d9bf35f610fee3ae4ea846008f79f9c09d96d92200c10fc09486385854f58036b3fb8e650b2e1ed5cad70fd2614b82dbc82c225195a5c037425d75298ec532e34b3dac180fb07c17d37d9907ced04282a239fb43ab9e2727a1fd3026154f51b048ac9ef95c035f76782d4c597402d3a9d849ae540749b0f83a42a8d6d90c57e6d61464600f2a1f95574651e19dcef569e2995595c1efee95463d23adb4e1dc57d1a2de6e3917041a4677841e0d66b28ba49bc5175b51c8543c4b9f85fe166ed878dea8ab12655e8557bcb1afac9ea702558d69e64262721add1b9c861405efbf33ff7f7cd13d4cfeb2c795ac646914ad58a8ccfb8d15c581c7a7d40f073cfec2318cba85327ba13f730c068da5b202a9aaedfd3268591242487227c417d30b10c7591f0ac52c27b329bf3c217d124c148ae91b67433e3bc51c1ed8744d2289ab68817f53249a9e0e9ac40a71fd9266f51df2eee8a13697d6c8a226179a17e5640b9319b345329d930945838988a6d2b1c567ec232cec72f6ac0c7b9341589517e5ae624c635207b53de01dee30683cff6824f9fd8cb44cc91d345bed990a0c8dc0dd2545aece2162ac058f5990a5906c64d92d397ac8303eb0409117a34282b1c09f12035caa5027c31cfcc875681038f0e955ea9954fa93db312722d52529df8292fbf3ffac215e7c5615f635306ccabe11c97c65045162946e63c9057a3898b4e1b6782efa7c93a4ec95be695438ab73b740398d623f8d223a59820819336974526c021c28877a57bd7797f39353d1fff4350d8e1cae2164946f6acf09304d23b83869f0d0486503b7be90e812aecb82af5ca668363cd0a569900275cfce2ab009d49336b63c6a101e24932b70b82e239e16bfb2795cf7261e54dc7b53e49cced8d0e171a252640fcef3768e04be57755ddf9627e0143dcf5d536e1254271380209a0b18a602e8cf3f9c4d1efe12272acd42c954ed60825dca15a191e53d4a3a7385ba96ff26766a173d0794cc660df0fe26156fc029fab5ccb1262bedae6bde670e071cc74896c7f38fac9d5682547ace9172d764fed0504e834d9629ca27d0201d5c920e42c882c6485eba7d9224c6940e44220959dabb4a007a6eafea42f4b7b83cbf27bfc8705919388248198133531ffadcdcbcd56f9abc1822647196122029a7be49bc44c70f27d12df41546118e97af53939bf11e1cebe8340b1782d65e3b5875633abf152357bd10787073e6740d6ec3974ff5b4e4dc5fbab4a08ad7b963ee6b44f8f9e2fab6e2a120ac1ee66b8079d50d8bbeecea329d7f8f4cb4abcc816d77d9a3a407f0c786f636303394212d41cb90aab2a242cbebac4f6fb8b68f862c64f8995485fb4d4c566c09c6c928cbf6f4f4f775522e4f0671122003f418dfadeb5849d0ce0d775d16b852b500ceea511325f59d408aac02c3eaa75fdcd034ee001821539ffc9d73c79918ed6f981176616bd19494521bdc5834977e4466b16a8493e390eb3dfc251ff5ba78d2f1fed70467b114f50dfeb7969c8561238d03f4af733406d58a33b385cdba9062060e76790c65d4e4a652e92536c0249c3bbdf6cf9bcfbeb8a2317257cf0f0bf72bf651548189d29d586198623adbdb8329db4b4102da44fc92c9101fa8d40734f338817a3873031052d18e4e31cc6ed614822e4ee1fd7330e85259e1f6bbc3a49d534e4ab8b95882641385884e24e4cbf0f57a1551935c6c313910e4d24d957e54cd17522035c54238519f022732cf265b1fdbba166561e8f75912cc26d2389e762fc476b5eba6aaefb2cc6d87fb7742ab16807a8024882462a0b81f5f003e853a8af3760165530ca366954ac30601bb138321b6091c7ab1771b82736818a41113157ac72e446cdc877a21884d9e049dcf0a1c8ec227d75518a4baf78bfe9c60c8a51da32312b2bd00247b101c3a0c83b8677373f1b5cb4c527dce9df333139d690971ba4d263c570d21d7e14e22f70ad95a12d3710215d0c4ee0bcd3f475c2aa0550934d93feafd5052ab294d3885f34e0a33c1aff9f8442fcde8b8288da1144f2dc59cbd148f0ec47199e49684cc99fbf0f9d34cb47e86d0b42124188a2a9608fc1412ea4dfdcaad5a170a62428036542499f762a7368ea7b4ef9929636000e27ab13cde83b4c686b35f60d700d9b92b8c81684a068730b64dc00007587f7651d9316341b6707be0d78ca87c031bc46f60ff0a6a3feb8fb1b15d0a8b3024a0c20cfa818ffd458f5f386dd84e71df6b8561f86bfa9162384394a0fa5191dad7fa402ceb7eaee813d7e708a166012100651a13c761f4fa95355414d5b00e99116730ed2ec688d398b195c8691212905c3604e4b3fc171aa9098f86281bb1f2fcc8b79bbeb33a876a292bd1615ffe92f6875e6bd4e92e30350bdb859a6a6c30ed5ba14932b74540e29e3393735ca1805634b22e24f749396948c614e402f7221eb8341db72904d642b8e90f9d81b4c19e7dc8c6b9044f37755eaf65836cb796b7d5b2f7a57e79e95bbf458694503e43010ecb7e15194e2e5e8abc69d410e9bdeb47da050e3cfebed7bb28d6a0eda9dab852211e3f1c8aa61d4a6b87ecc2c0cc3d96200e086cb06a057355030262593a3aa3aaf06dcc05c091e4115da068d87211e456e48e8670f61537cffda54792aaa078bdb8a58f804948875811c155f0c5c6bf021162b14b78f017ac709d942374e81675084201990bc04711fa700eae9bb4139622beb02511e0368077aeed34718113eebaf255c0cd9e84897a0216f8f0d80439c42ce08b641eb4609550c1a8dc839c583a72b46c259c364135cbf95519ba2e6c07bb1126cdebd02c29e35c413bc09f3d2ab7192bd49dd8322d8c41e4246273c63db828c8c4a871590115d0c47233742654423fc0565dd3f745f59ad2800020eba82f74049a69fbd2ae31f4719b8744594a360c7053e621819262fee971119ecaee6417c5a2e40c984f8ba0511ce55283841a48ffdadde6034f69a0481da5356d607128adf1239e86c8060ae2ff24a230a3cf6869e0c7a1dacf47a5d7b523542d17349c9f5512b4ed8d9273d23684e6c953651c35a4aec9ad9d232fb29ec33f26213e242950131f49b21870cf5d13e943f498db245856e3e20631ef9e438027d07f35b8f216871591b2498410ff4cc5c2a49fd6cf44928ecd20a970d1716e4e060066dbe4465c5475512cac110eaa9ccaee36bcce727f7571b18b702ffcbf630e2d839008d363dbbb8c04113077513cfb21d616b9e8a57975615e217b90194a05f6e20959125dcc43562ac889ef8f15d87f61d35acbce8484a008f1b3addbec08bafcf7c5252c390ea0102a4d8ad9286f1c33b23890c77628a1f1ede59e88e41df705e8c7800435e2a33026f0c481eb8fe318112365407d4361c47c2fbc6afc9ff2a0eab5312528a3a090eb894341c9e9a56c496620f617703bd1dcd92d05b2eafc65caabc42ce362a121fd3c8fbe59c3263abf8dd979c0a25ae772e1bfb55a153566093f4dede7ecceeb347b07d90ffa5c990a1226ef0398666faea318efe3bed2be149e85db476684276718c4247e04f7970af61adecefbf5d971c639af891e5e079eb98e8e3eb27a8d1f540e37fb815a27205661236db8425d2c749e1a7fb1edad2cd9a9ce07ef05633e0be4c8018196f2f738e1f1db99e8a3f21655858e9309016c50007f9f38593bb32b81286d4ee4771a64bbc1b59e259b467a845383acd5d51a285d68e63e56ad0c58c1819459aca1af798c53cf05bf7bbea429fb01759ecf44521394c9f69b1c1edab754c8f752eb80942976c1f2ab15ba9f956d347d036d9fe6b1674dadfdf48c127bb2807d227612c209d8c370eb07c5297abfb93d671c7969004f132f4c01a378483b45fbe74bc9dede740bbc3a04be43c19e2f6baa03f662661e28360803f26592a234abb6c8852f19dbb10db288c75f89ced5494a66da9eb9e44d4bfde85244d64702f1182a9b867d22b0cd8690b37ecc1ea0532650ffcc3a03556e49cb0ebd03d5851b10fa199244231dff624088c4a9f6d8f622b14646a672e4c6fba9ea28bb27e6734922b49398f15d37ec9b6ef7f94916abb4df63e5c802d0ef67289a5d157aab46832c5f281bebba26b1228b94baf9bf8f8240c3088c86c6f089da104c7106f78a3d13aa6918e5811cc74508c82ee0b472d6e55421d64ac22350535c4465ee37cb3d1d062ad3bd5e960ccfd89905b62684f9f063c239536c8f370964f866e7715ad2dde7f03729dfb203a4849d3320dfe406bada3363f2fe0e9de3d9a1928d06932471682d598db0abd0e28bff96ffa41b966c8b162e335c1ffafe241bedbdcaceee3f695c9bfe9381f35cac15b0c38a578a0da2760fbf93f4eabd2d1ed969caa04709f57dc274de1b35120e30cc68825660a31e2a5134f749b2c25fa4273d21a51c7a8aec8987a9780c1a4c69440a37745bf360220bd646fd06a710c3f9be977b793ed9a10a89372cfcbca3175001505b4f2dfdb4dd6c00fdfc623299202b95e1492935e4eef5241c75919cbe1d62ef7d49dcd22beda0da22c26db1a98c3250207254f40e2ca469788abe28e60541c6219357e7cb6058521b352756f68d28c883f70310fd94e01178ca2925848cc952f7b55fe3f5b2e00592b4a3e900032d46d1bbd40262c05a78ba3619e26aed24bc4b681ff97e0515e909e26fbb8270a4c9d97829df39d22f1036834d31a7b06a9a398f6018cca5a04df981a30ebc332ee016527a101bf9f537ac8ef50cb5bdf83d409fb1a96d72608da24b9993687560c33879029266a53b397bd6621856e510280ae19da8dffb1da775a51857979aee9c1b2812f7287873e8fe6b8fb3f95dbb15d233a1f3ed0a818ff671c3a395ffd9f954674800c3af45f2d94b8443dcc1f3104766083b71082aeef22f40e8abab5208af1f79ebcc19fb841b0f3c0ebe5ca636a3edf111e79404ff15cdb62c9cfc5a3d39d76d5f8297a13fc11d8da2e40ddacdb6f4ec0b52bcf098d0b544405b1b243131c23c0d48e9fc7e7c5febaa2a2875a8e5c421045d6dadf5b904944468f5a0fa93a71b5c41dad3fe19fda92723bb4dbd86e4f7f3429e66fb326470daa4d047c3eb23c1abdeb3bf91a16b67e0fe3ec023f76fc99c329c2ba05d75812b313250beb0c05a049c04dd74f9222db0bc29c14a2fddf76a60012114f913498285289cf7f96149c1e66bf8ec66201a80ba7b479d478ac6fe18aeaeb96a692bf36547c78ba32e97f59eb52fdcdc7f1cbfcd9677cb896fac05b718cdd3492336468dac9da2705b63d08ebb5cc8a9e52983358621c5c3ab6701d80e7480fb10966b6a3624d1a1bf94f7862477df7abf62af5965f984398216d8f4930e3c6160c0b9c9bbb771c5306be56a5a10b07973207dbb42afdca03c045241077a37151104085067f980f54cc70544b16187d45231a718018fc8131182f3c429908e426e7b8ead2439d1d4761b4d516dc89a71b6cee25ca89b3083944b19268bb4b51a2c3d49c020d9b1d1592297553a38cca8644e2231983fc526bd7f9a15cb36888f5f08a6a8e21df4c959b1f6b6784d8dbf7479821c47c27917dd804b79cfc96986ec1df61b8bd04d7637ab6b39469a773acce47ac4d34f072c54466c978692a1e3c85a7975f90fbba33cc38b257638f1069cca3b47ca15406fba6af7e8ed127de7f3074331f232fdb18f12a86e29e7f86ed692c710c6ff74e07ee390268572264eb0df42e504801827821136c2c20b0cd670e9e92b5fb730ecf6c6abe183253916875f1c37c40f035befa1050afda8adb3fc28458aa113810f2922229e229f08c299d4a8a8ce0ce1866d1a0dcfe7bb27f21acf24c43033d295ea8fa097b94c83f3816eb19045e6b1b39ea906d251993d53d55480db661563b1ec6b0cb36d03e55a016cdb89292da34e08e179e2eb28818d95ef5b881cd035c3dc559934b9d8bafe4bde69b865cbd9be2ad4ca7fb234274d95a2b7325618d106e54d8b4ee736132f0e0854727c15be2794b37c0414a54d89e60cda5c7189f213a28025646c555f2640759771a6526538db1c2a41c3f95e6e231ec59e6e0c64a3f9d590bd703af29e7546f8c8a4d680e8bc4a756e0557bd719aa0a885a4583d9b1653f6e01fc9e65b225c12333a3709809e431f8327cca9efb862253c64ca2683773eb78eaacfc1c07bbf07cb0b987f0bd82f2ae3ed7ccd3493b4151d7748fb640b8998478008edc8ea9c746ffa54703f235349a9f040e6866b1989113d064d0bafea15a88625ea7d09c923a8940b74944c73bbbdc9ca18a3ce4ca53102436e24a8bdbcbf7e2b9ebe3a9db201bacfee7a7fc532af8e73a1d7f5917f63b52bf9c36cb5da2c5f4c8d90218b390ad98579f7f661c2afc818015eefd707c9386629e8f4147ceef50e4a4a9de77a574980947bd891146c0f88fb6636c1025c3491ad48395fc1104b4f819124354c26347a5b03230cf0c0eaf54269bfbfbc7cf3ec23480a6a9909c1727db3c0392993c332c9efef8565624840dfcdfef80681c5d803009cd2abc50e0d33a5c120ef1b93ae73756945ecc203967f25598a90c845c2049f5fb8da3bb952187946c820faaa7bdd84f24bcf7d6b3cb630d2302aaf334651025875b762363f3f0d896c0fa43bca885c1725411b32619c47927d7cbecd6e16e4bc216bb0c6e8fd85fcbd9786abfa4fad9f0803ec11dada9a971a319c17e5f4266ac96251ee9a30beb28ce8d6cdfbf7c25596a48ed12112499580306b3425989eb52ac3439b96916467af337b6d6831de854af6e81884c80e187b9975ae6ad9cb9779fb310ffe17c9b2efdca7a835cc3532883e36c444b9909910108a65e033abbb7b050431f5ce27049108b97f4a3d8b5efe5e2fdaaf340429b8e4c548468f1bb312b12832c1b8dae39db1a438b51bb4ef492594664aee7e694c278b8a7b77de51fabd9fd938265dd22289ad49c5d340939f9e950a7e0d14473c7448eb6bc8ad2131223cdce37689592259ed4e6dd0e32e8a6344f88928ff66a1f5136c9c4b980693be0de0af7fa5cb0919523d55078a17eb8efe8f6b5b1965d35f5ef237eb8e46e2336cd437c57c892393259f241705b7bee0dca7b7a80cd073ae8aaea5d8a6dc89dde5109be94a2ed11b35b4d8cda6f8d61c055492e92517aaaec534ed527c0fefb482c8a2288de0330d6ea5094ad2838aede9c554859fa0c22623d18fd120166e49ecf970e63a1357ef70a7c830a8ca64cba4009d94639b29e6c95e3925f522bcac6b91b4d5ca75ed383f4dfb5063cb3bef9ca551454c044d29f618100a7ec1a78ab22582ee6472829230fbfde79cd54fbe284d893b5e5e2742b60f43b91528f91a1da137d64a4a9585be30b30e8f77eb1be799d8097a8990c6ec04c9d6f7f1f99e8678cc4f21a26f519aa3c3a16091fb9470f882a95957ef828c85c6415925cdb80b4b08b706e25dfacac1748d2cf3d77f96db445be5aacfd4ca5e755606e2601f042c45787794452f91d7a91bb4f4c5e42861d542b75ec6a10017436a247dcaaffaf751cc0389bbe19a40991c3c33ae166f0617d0ee1a013c49d5c4159f1f068f1456acbf894413f8330b027bc5845d93c84a5666e2943cd39b0f4dca5e87e38cbc53aa5e904d6ef70bb21f0b6b2b3244161b990d808a66826d04eb60f50da8d071d7f47e4a5c3fd2d9f7b4e1e0d8c0bf8a96dd7bf4b0d6c10ec1fd0aa18f524d97939b2749e4be3edd4a603afbd8be4312467102d1c5ef0f2107d514bfe681f5d5b64702bf0d4aa00643d1e13b30512ab8e61400949639ce12063e6dd402666e3ec437a24aae3e864835d4fc6fb38542625f60d5ab26bda6d1806e4b70f85b872819906c3a43f5a23c084962958adfdc1eb9a84ac70d4ad690306ac0c33ca56a76d1bad5bae88feec72256ce13578550e1a6b895ad964073ea0c2d571aced296e48c92e1fb358af3f810656a0da77c47a681eabe1c36069b4c7b0299b0faef33ff828eb1761f7f75e2a9cfe921d882ee73d262050d383335e924e0588ec6265f1b380b7b3957ef0c9ba0af2a51729a0d16484c9263d7c2a8e960707416ace6a1f31450c8263c3531beb2ceade9f05737180cc2723685368ae1302cd054111ad9e82bfb0ac0054c1946e81c720c8f83345d749d29562debdc520d595dff094aecaa3b71307017e3665c50f5044747a4116feeb6af777ac391d60735d04739d6b6ffe42fe970f01aedf0d308d965cd4bcae1083d6b1fe7428e8fe6e6003702571e299da69044f30f6547cc7565be556c517e9680ad87fbd339b48bdd93afa66873ca2f0e3355801d64d7659a050b7704fe37acbb43a237bafa8f573facad95cd93aeb74e850ef383f866db75ea8672014e23d2a1f217e984d2e8c1ccf85dfcd9837ad3143d3931867fd56d91f118d5888c04267a79cc408890d8739bafd490bc3021d5738497ebb59952fb28d18627692b51125a36858dd7f121ba7b8e2533fc70e9959c0188057b24c853c5597cbc484fcc741d16d30d35a69abf55471ec44cef6a7c28e04134105488fa66a503d20253310828d89aedd00137d1c24a6b311603104696dfce1ab0d69729d676acc8613b8c2e718e601debcec66092ee4727784e94355b6701d42de6e0605d90a983c6635a74dd9027bc5f57b969863efc1f6328fbbb1c8f2f1237382bdf13c01b1a68729fddb928b73a23e579b9d60d9f6f6a13348218f7bd20819e9a9293ff7c2489e4a69f98f77b12eea61bb57df466960334794f41589fc96261f28e7cb3c807808f18adf09c4fd3e6dde7500590712400b0d3cdf905a24b1c5bcf9a63134aeb1827d71debd7cc6dab84187c7e3c379847de41a796567144b00893d94b39df68efb733746b0f8028fa83640f158ed8b40bda609cb7f53436cd55f9499a6daf7662cdcad645e721b0813e3f4bdcb57fbeee427ae10ad2e9ac1e852588cc261603e8b984f5e4e94f3e97f9d8b4f6bb44d2b0ff7e9f93468a8612a24f0da13b94874f1a0096eb23763961a99116ce80cd3cd1ffcefc8ca8b33100cdca2f4b0091a4c05800a4a60914debae28d5707451154a325447d4cdfd6eebb9488225ec69cdc962f93b7e1f952ef5c6ae8f8326df8e362d6c1ea2efffd7579681734c23b637824883943b588a757eef5de1181081c07495c7c3ac01092c0baf4d648a54adf7ae347ff9c6359c213b49a81a795f64a14400b5a8fbdca5333b80122195d96709f8c9e3042c2063c8587e3ce9fcd1c3ad0527144b2d99c95cb1966b8c2648c9c11c1a30ad7c5bc46dd168aca9d4527466b2862991613f10fc96b8b225b8bf5a0c941e0a7a47ac3b19fa0f02b888ae93c6afb0729b52c1a3bb038a8daa25b73695d8ca4a960331a65267c0c13ccfa0f234134ae8696bc7846e37c744859718411a7658fecebf6c371d1ba114dc6d7e19e92e2bc772bbcf076fefa78ed5bf916c7431490e0c49801d2eeacf3d8e9f6bbbd2673c7836c4db574eaa25fa0626b82085ebeb8721e98dada40716b49bc47c77ceb46a40c6ed8e575e75c3f96c3fc186e328050d687e57fea9586bdefb1f29b32fba775fd5f7b2bff3f5f109d243b5f45516ea67de051c45719003d14b7940c4f5ae016faeaa00e7d9cf900309370f48f76973a6d41817cee190c4377c18e37639673936e865ca289b498a64f1457e8ad064151e87ddbea0268786fb15624ff1b5027fcedb9a1c7919856653a2fcd76f5286965c6b5c284691e94edbc98fd19cb81d92b1f11f9202464084cd3a645814e0d0568396e02ca72e64709c0fe891678b2a280d78ec435b5e65cf80faf27b67b31ac960098bc9f1539f5ddd76829adc8c84f529230d97a601ff8831dadf23a927826b169b4fa450678ae6876a81b05168381c21ead4a65a8475b657e12fed4eb040ce8e4358621e61cbfd79762478f948c4c98d90867d244b10dd3dd917c844f111f683a39922a2ed0c5665b1f40c17c21c4f5e34d4ec1202892fd68cdc390336fc7635f067121eeff5894d696bb22615b9ba04b08aaae208e70056b5cfb3c7f037176951f0b42719a1871ae12635ec3faa2740cdf1022a6c7597d0d09f243a19b2320c4e4d94d6e1bde4af0e310ec4796b178af415b80ba4780f045db65a3b8484f65556d88be28624466a675d6ed55844afc335a9b6a35c14d5218257b5bdb2bb28f481e5055307a4792da78ebfa5e5bb1b8aa77c55aeb27c515ce57739dd690ed3b77348150467808aeb64dc2252d8a8cd83222b599a173363ae9f40c1a48ec84e93c0009f2acad20fbf9295d03cc597e28ccb27022f861b8e0d895b28d597ead89ad120a269cfcf71567f984c1e29071bd6e4a46f00bf793a16770f899f2c5080287495df92945972b825897c96237b2d42beffe64621e9068ea3d83d6b75930d57d40ea0fc88dd22e5d0571ad8c181ebc05d0ffd8c7aaea29eadd323c57b405ca2c5045ff53aa3b12870b42ad5af5c69376ed4c128213ae66dbd9ec3d4444fad85a7075513bd444e0f8a7250dc0a3741f2b4af687ca3251e9acea3a5687e4748f00a06e4e490e088e40ce20b732a3e7ebeafec9a6711db1fcec9333660171aab313aabd25696dbdc6443e820a5f1ae43a3a9f411b321e0ce47ed072aed688ba36cc6052fc231427974a93e83d91d25ae8d9322abfbec95a875df97fcf56bc679ac7f062584d3f244d6e28bfaaecdd78c8674e88b55c2c97730906c3ed27d1cb97a1566b9f0f00ebdaec9019df914719de311e1a98010012b04db445539417487928dda744d5a72daa1986921f625d0282e94fb8e671d8bcb76380a8d475df23d3d60e7bc83e2361d860be83427592e15864c454eafee81397d5a601ec49a5fb5e79ba18722bdfd556f7978c0afc7bd15d0b01b2c0bea72af2383dafdb9f2729b2d976b71f28e8ba86b8c6e0bac7febf26b6e2550aa2810f2efb39781981ce990dcae355caef5f80f62824cf324f901c8011b404c708997a1ae19864e3f11e050959ec6b15aca471b17fd62a46ea7625f4e1b01cbe02b3a511a185960a715306f3b6658e3fb2fec74a1da54ae2a8132977bcf40759617e3094457ad059f36a2446e1f4745ca015cec3ad6f1af4c7c899d258af398b07c484b9040e249e0ec07d0b7cc1a4a405824a1e440cec3814da7af763aa0c42854161e3979b5d74de5a4449886ff8c36e22859072351901993ca87422fe7ffe4193164b185346dd82b89a2f7ccf60237c3bc52241cf475fa2c073fd639869f07a89406f75bf97f1d6a2726f1f6e065e382f700c4b13977b02e26542bf9608668310175ed9f69ef3004ab41a3994319a9fceb397b3eded87c3942df923a102e913e92d9ade92d6e39c7a95b3de92eb6192d5d3e2a9e81df30a9b807a51a4ee4e89afef2438bd146341b5b0c21b863b821b0a6ef4c36d104ea08f9d294e93059400bc9e9fa5387ec31f9f1b81a21e331fa0b96054e9c554f867dc40223af5cba662018930334467196fa3ff599e0dab1522c9beacf43387b0cc8b34f033627e81439d3f8fa5ee3729c3f57ff6e937d0952150ef75c728b3e6d67bd9843482cf48f9aa1d004c24977b56aa024f7518ec7d283e83ba8f7e511f9c0f50baf1368d791f805f52e6b8822f98d9bf91bee8832ec104233cc007700b6cd8d8852a9efcc9ff5bc8fef8d4041207a7795af176472fb0da6aa9a85f8502b70b41c2fb70ea286a1424e1c84537da386753c3c2a96666c521defd8be2082768e90f1109e677c4c836819cc88d2ae85cf5431a0872ecba05c421d0c3ab473c0e2c0f7c63a9fc55839a70b0b93e8e51c25432ef1032f8eac30e43e3aeb7df872e8a87feb378637f7104746ba3b77e1a6b3304ce5aa88ba42d8532bf064a911589b8eb8436b988a4bcf4174a2066e714d105a6cc8ece993df3a6d91551d858603c853e88fe1ad59cb64540e0ff0cf53da6258c87e5862b8d8edd13fc1e4713fd16f2aba3bbd356af62574d3c0139fc55559a6066d908551a34bbc576e5cb688cbff6acd00cf49cbe1adb5c4397335d40b0071e247c903eacc0e4a68ca3afb62e4c4a650d775fd5e4a4dab5ee27883ddaab2a1c0b5dd7d1998270e37ffed63f220eb1c6d9915a78c5cd2b3954c3bf0bfae030874e8fa17b5bb59c141046e4c724c2e20daef4c82e1306484923990bf7185a4bb8c2ec5595accc2e99605b865ba8dd15e0891b57da8264a71ab3fc267e1a32d2054d1817993d6b596656c65eb635a8cd0c2bd255b381dd6ca1d73753f20b59178cd1bda4d6a612fa15fa6f62419f5ca5947a5f666d32d790e8d60cd064373654ad147a47f401cc37d800311dbbd2ae7dcfddd1f2f8d95de384d6efdefc55e3dfff87a111195be01a1b59166d8016ac40695e8b50e30e34f1ab02338901ae5f101f011bea97f90386dffbef87a6de33b64b6a6db42cdb134d1505cea1aba117bff7927b6d32c4d5720c8182f699c1d3cd073a68e6ceeac122c96d1eff180ece1303b0ed8c254501da78a1c2f1abebf8e9124fe81bfb08c84469c200b715eef0d7ffc7871ef4ead83eaeb961d9602769c104f6d37284f4a7a5a9cf13596bb34fde37151e8df0f21970a3be913f52fcf9fffed0943e4fc59275d2291a87cbf3e71738b359d5b7230754b68a4ce85709ebcdc98f4e6f52e66a02f01daaf1d512a416a375982d138696206c89f31568c8fad663078d804bd53f657de6c7287effff7de84aa063fbb8e646cb808da45d12384e6b84d0adef87febcf70c9891195d08d4e6bf58d1a21ae983a01ff8a0c4b1a59970aa892722f4976139dd51304fd15b291e05178d4a63ac3655845f23bcce9e1ed9eec8026ec74da1fc3cc12fdf59df342160e1829fe90286226758fdf284e4d6e286b1201e2161c1a7534bfee7885062c1960d390de077d0ac3d5895d8b43af33c4287b34e5fa7841ba73722972d566db4d9fd28276ae28c3bef3236fcb4c6c7466057fcbb73ab9b2ff250df546101e259bef6b91b9bf6fe354b45660c5b05c36bf9e78c7a9c7269cd1ffc7648b285d729240b1c183bfe36c9dd348def7b44bceaff1f1b43ba1e94878722fd1744e24097a2bd67a760ecb8f31b1d506085028f63562b716581ccce300ed36f9b87d78498bce9cc63ad17c58cc6da32bda4a63132ea3cec0de5534c770c4facdccfb73f97245a2700929129aa3e774656b18ce47f44f5ab4a89ca086265493e1f45abdce1cb727cbdfe9dcf90da5484788a7b330c3d97d01c7bb8d4e051b6a87159079599b4f04b15e7f08ca4bc7cb2128d018c2ef5ddf4517ae5f35f05bfee049eff138a9bb8a717997c62ceb77d91cb9b1f517c0a5aee084af9f7e22b586e210597d33a5d392217f97e95cbf30337dba09ff5555631668a87ece94740ed49f475b82647209a400fba51baa9e12efa475ad5a456359be11f2f9e7180a61dc9943d5ca236b5f9b4cbf7fae8be445cb1d189c310ae8275e6b24696d1ba5a9b2c12ea22bb10d80438bc79af361c4066492b3d4ee12ae3b1d03dad55f78803a395e30573ccaa678c54cc6aa92cceb5d90d5a90d4063f7794f3d111c8a413178bbe4bdec5937e668268f2b3dc8fd5065313795b6a25167e3aec9de7c9bd1d1b10b817bc2c34c375fc71f479d3051bf6b562e013f9b0f99c2f67a2278e4cb75a7d2be0c2075fc8b5f33857a54ff423922f4acd89f8ce79e3ba4fdceed7719ab91fc361b647b79dd671175dac08b2e50f0968619bac5659c591e35c8f6d3e9a935b8ab8d5d82e0f57bdf9fef91b9fd6c0ed88a20571c619c9e5eaeb955db03e94eb2b1a435876d38f8f767c4d0366ffe8963e8154ab042fc94f4001a5397150b12e169d154930ca42980c41945b31025369fc40b839c275517cf4268e84104cbb83a5e85478d22181537209e56d0d37fe4af4d162ae371aaf6f63991f8233d77dd2a9d2d66c9391cf7f2af6f972b82237780303ff0acd31be0bd5eb1f37711868d430f000d4a338330d917ddbc8f059abdb0581005e13fb645f1af29088ddb3457d2259898c1fc54c83b94bf2faeec7ccb34f40df3511c88549703c0b361a7b0c3261dadd19f960e093f01abc235983152a9e14ca00e06d9cd19220170d90b8fad7e87b68a6b64611dce2ecf05d54ad871dd4684cb57330e5c0a8b0350e1610fbc593125a338fe97949ab427fb3ae97f33d08e12e4d3b74302481691c06fdd3ef1ea631b3cd7ad9ea1dc2689081e4f609959359d9c5c4ce268ec23f967cb2ef53af6f9d04029f05f7fda2d4a22646cc26cdafe5c0892012fa0a6753407fd671c1a59d73890f6481300c04121e5ab354448bb34d9870c1dc3d69c75abe124010becf4b456b444d0921ce64463ab12e933c971a930ccfa1678c86b9f7ed2eec857214f463b43989008a5c165b0c24cdfeb7e1860990b7135fb8319a7ccfd9a1104ff3ad6b5d1aa3ad51a734781d1e33dfa5cdd0bdbee9b26999a3f776fac94cd39ae19914e0aaa0744eb422e9b98d84ad5fc7b47da148c88926c916331a1d51c616352cd4111ba3f3d77949347b844432210af0d53efeb1ffa78d30b3820a08ec9913c171207c56a86a4e3ff364a4e8774fcb1c30c85fcab65f4ee540469e1e85b4f03a167940cb6099cbcd566e6197dd6f7110bf087ba164479c5fc203fd6c4661836b4dec8808a7597d8ccd43dc086d3c0fc87885546b21b1c58df4b1409267f82b4c196d93924b4bc5885fcd17d97b2b751791fe64fa21c1044c9b905737769917efcaf510a2b257a2ed5d197f536a009bebfd82c5a3c754fa5f365daf2a075875d92c9df63dad3f2c8dd977803a46c7363eb45dffeeedd59bd2fab8dea7a949aec59f3fcdb58610d1ea35957e249b3ffcf39491e4cf50b9fcb75815cfa3a6d7b6eebfc61e8b226b054f1347bff07d3c681fda6709ebb1f54b5a2eaa087c7a48ae7bacdd8a6012b8e866ad8e25b5d271d11a369462582a686938b79162ca33b8581242c40f8e623d274b6b030808daddf556787808c28148457a3f4195d5bdd4c60d8ab9d2add4fb49c1d5ee8630813ddd8d4ccac2bcb5d479db2f8d0756acac1e4b84db705d3fc54d0f78c16d343ea84d8e4f6dbfe64bd6ef6982dc2814e19dea1e8b442784e86bccf37118343ae302e95a26005381d43b4846e5873e1c815c18f456270f1c5ea4fb9601d01bffe4718745561397f3e132f14df213d42bae99ed8b13c4bea4fdbe026ab82c345d19429aaae2c00f78dafa8a9799486c4faf4f487c91b1f4f0de324b9f580989d0631f4f74cf6d63f9dd727a94a94db30029a06770f11be353ff5e71d3635d47592fdfb1bed6d1cb940eebca73c01c0ae17d7b9b803c7f9ed8b1e333aa6deb53a72b804843a31d10fb74375d378843ede6186da44ee83be5e8de78db31e58a1180f0f7957d4e21e9c7e5ef046f6e2bb291205ca674ea22915aa36ce9faf73a856b63e010be1d8e9f5924f6668d2f144c3f3f35614dc808101c0793877b3f53a7f8b96c29d2c7281f45450c27a3b6f62ff30bc51d3667f1aaccd18179dabeff0746117c47247a905a4b564c4bd5d1e5cfd2b9667fe657498026ac77dfc22df02398351d82f4982f7741a6fc9fc790bd3116b210c020d7941f7404a4733e950453dd04a8e9bbe48f3c2eb3ed7def4be2f172fc69eb551431ab0e9b530c4eefa7b5d5e42f7f26c7973f3eee7dd5c65becfe7387d9b2ee3f6fa907d19c2df3ec01401debfecb1d266eec8283ab30401d17d462a667df3e920ffe9af30e2259ed8967a65921988c96850e4786da3f083449086d50c02b9593441bb72ba7c1b66455dfeecaa1c79f021e3ec84c1b580d928a2e3901fcc05c2f43eca28fbc12c3c8a33a0ab94ea3e53f52e270c024494b9010c2fc945c5e11b3641c7112420be99459fdc2cc9fdb7336bd5e13882d6c2954819a04a05861710c4eef55992a978acbef0e040a0408c836dbdbe4061a366c36fc6068e8bfd9c7ec889af00e72a81109b0482ccf257b660ee7b5d20f85fa8abc3333ad29e1a70f8d2221f6989fed0a4951e3d663374e388ce0dff6fe171507e53cf8a8fd3c85db761ed251fb599d7d7515a657a0e2400f20497fe7f51fbd8ff9b1b8c67c145738ca58c57b2cf38506a243370a3c71fa9d5637a337855a8b78cf610613c94c419447526ffe84d9226c102d2f29daba57a780f1831d10264baac003bf984b3f47f18e37c37cd455781d7b3a6de687af9dcf18ba8086f1228f9c49dd73818793ff97715ba3dc67693fde399a8c6f0ba498ba420cf2420135214cfdd89011f710fdd80af0f288f4061b62dd24175967fc9045203f01e7ef298092ee97d64b8db07fbc335acde38ea0e201f83185a3efa0d87aece6dc907365070ee5dc5e0e7c5034e9fea5a804ef7eece687d6f088e7adee2275b2b888bcf0d510b3ca98d550fcfb7811306d8c9bacdbe6109c66355965bcf819a678996009a8e8a10b41176a1e368bbef54ff0a128b2e8c2d4a52b31243704101d71beac519ac3f10aead0e9ec6a5e4550fad8c54151f35e5e4b5bba53fcb20abc57c413a2607db266d6b5be8511dd1ee54bf7c83313acfa2c606305f0c701fafd50b58188ffbe11c71f50875c5e9201f520aa7f0b6a9dada402fbf6dc3548b15317fed91cbea69c8fa3a1f426e206c5bbb3f7506002824bbc2b834f28bbbc6689fedb14d6e14ecafa532d10b8f328a1cecb28402affe93b5664f5302a76bd085d124d656b6ccab8b85d1ba21c287d68a1284777de096be4ca8f89e98e4ed65404f5293f966d179300708f97c7c2ab961d5e1202afb389445add2318ae66b554b8d91332a782e4e49e6a473fb79e8466b34e16d8f3e4864a31e53b98840a65cb50c01c8030c7952966618d8a530cfbeb29764056f2c168efc00774512338169e582347617ba91bebdf60ac71b8371066e1b3f5573d3c5df5641f25b57fcd4b572f8a5a9c9b5fcc57ba58778653173d5fdf71aa87e8a8e7e8a4baa0002246c176efbc41d425ba12db094b8e64c706865931268c3330fad6effc93ae9e662d2b99ce5b51c1fa265005f25f15346528ba902fd30a0e3066b24608d677a850a14373b904e1c38412ac7fe332266b245e166301a26db75baff4a675aacfea15b9c64cb9add13b2ceb5b588cb3e7732a8546d53321002a384013319da24ae808d02b761033a6ee1fc57d430c54b4a0f8689c344e9ee9e5a383a832c54ae5e65dab3a16fc56f200970aef5687587c055e35a41d4af49dca9425950c7b03fa04eb8a665c34c1802e257ef3df5b002ee01a709584ce6e7c230ddade4691b50622da9d0f65cb4bcaba29b902a2da60eae7a467a9e71ce51632795416d279872e267b26bbe98396b9ea6760459fe23eb1af443f024f4b47fa5a0b4ec2766ba9e5ac8701ab033b536335917aa21024947d324c04057eb812753f8b77fcb0b0d490cbdd5c487ff0c1b916fb1620e4d6353873bc7775b755626f827352dd3dfcca7057d05e3db356f8519708239e63ed51035742498451878dc444eccd3ab5775b6450cda8742de5f380548f5e769fdab294ab3b4356fd91fbf0a8bc10c2e7ad3e2e55c92156d046af0571d9da3b28d460074e35478c0df8d7902e12012f8c61454b8399232538206deefedf20b0ef564a4eb504bc379ff4fbf80e38d5fd03f37a8cfc3b853f7546c879aaa6d21c99c9962555dddca49cb2715cc86f28fc868ee023dfc724bc89f075d7af1d81ee88bf4fada419e2e6c016fcc5073369c186f4427417e150e2879c86112c13f61bcb9a97368832af2a9e06f182ad3556bd93b434f03e8bc507dd231d7d56de8a0de4474318f9b7a613ddb4357ee4b37be0333d0f2c8d747ec185dfe7f69b9084d5b6b53910b01a74caedcddf79bffd3f035cb1d900a38739d7f45b29e9f7418fa07353f7a3ff07025d3fbdba2621b6c7fa0569e0a74292fa6d792af2b25f804176651387b73b181ef324aae94168a8f4f81d318f06f9ffd617de22821bd28d1b9017c6e7bdf0ececfbf18fdfd7bcffaf2bbadb522714c3d67e7aa12bd20299c20507abc7e2867ca6b39d0c253ab94ad8ccc02688fc7a887f802fe5f5a8c284a3be640362fa8af2c55a9ab38e923c4246603c39521a751d04930b043ad947b5022617dd64ffd8982c3dc153abce2937d5c544994cd69a2d54302e69e97a99e9f42ee2003dee33c67545c6b4e57b6284fff0344c7dea9bad1f014b335d7d39e2a240b6183e17dc9a0db7c17ba5cdfce15eb1f0733d059251e390353211db06071e909907af6ffec9e52e02eba2def6e40e340cdd5cf4dbdb1aa9c3c257cb22930936376b16ef0507963a54570e8f02c7e0525e83430889f33ecfb024ac44cca9937a9bb746a8ccf279e5d7f748d8558a774aa7e3f2803f86b0715d49345fb36724d06de20f8b0606a18451c662bd35601dce441b86ef1907022ae11a7c87341f62d063e5dfd6be5dd1225101f5412d340003248945c9fb9df7fa265e84679e600a1abb15c0dfbf45c050331f43675d8ba296ff00530754fe991a46137c216298df5706588bfa80fb779ba20a3f1f94c14f5832e6ee1afe190a1708c773796878f9cfd4b5a6f6ef531a31ce784f5d531c9ae1cee7c6b611d17f328e69953199c48cf902f5a206865b5ebff55090c66710084e8f0372f80c51419a77d12b388c00cc840428809a6b83050a2a660e3e5367badc8572db4ee333beeedb0139d72e103c9d9a03ed7bbdca9d9a06f9a17d4eb714cee7ed62df778772e8a81896454a1cc8f58c34e3fb74a60b19691bbfa238691c624279862ce434b8ac9e786734e2fcdd8acfc085860cf992eef37b5def6085accdf6f284a824984cfa1600ea710114949735afee0951e9fde04f09761c5a8a6e9064ab8f69bbeebecc5ceb070af858e9837c0747e4511542e21cdfc99556051ac94fa44ec01cb2e14b5641e5e33a229dc858246dce5339f74ec46d44a5e1dc8b45cb5b51e075c6b40d395126c5883bb87a4c62cb13c2b7d9a25a47afd0de4122c6d15cb4578539fa51a43f1cf6721c770f8fa4e41db134df9fd1af59a4b74cadc5c3420aadf2e02024780ddd1e320a1af632b002aac3635e75000feaf41b5758cf62eba81783bb7a3dae55af842964ef46d1747390d3f1c80e774e42f53fc760e7da7f097fc231942e06a91deaa8b8d18316e7d610a1aa27a73a58cd04381e30222c033c3205769b4be4de8545371a9fc4b2626b4b165fa08a7d0262ef024a1724e06077f1b54c6c4ee5dd9dc6f3e6a22b17df717326f689cd014fbf1e14800f14e1d4b04225288265d303ad3a167951599b43ae2d0b0a111cbebefd654926a4774eec0d3b97288167f6bb27e0b96a0f82faa8b2e13d0d4b8301b16aada9478cb8539d3da5797ecd60206f6295dac05a809b266ee39965116d346fc8aa4e2ea0964d18336412a14d1fefc763590d972acfd70e39900602942324b7ffb2142a1abc461dd50717125d06b953373f88bb64c6f72c4d5ba14a740d68733a11c9a22f386979ceeed2653c5d5b13b61b8fb3c67560a0a187b443e4fe5aadf7dd9dac47390c4437569894e779df2b5a7f57d6a4ba449ffd8ac9438e11fd3c88bd327378b4dc410fd3c2358455f1a9bec3241dae84b3ad5b6e847c370b47fe2d82feb88387887595ed99bbc1ce21dea7b189e98eee403315b48fdc1dbbeae94f72c9cce4d8a6bcf420c57bcc13265fdc9ab4954f52aedab343de063298f3ae6d2cdfb5ddba3ea6e27dfbc07cd807f2f2b817e6cea1f9be30dc53ed779730973c903925c69df96ee75ad45c9e01bebddd27b72995eaf50f70be3c6eaa85dfaa490ec1aed418531475c64231c2eb5fbb9b070da55a27191703502c632eff3fb6e8fdea0fbd0866568105b609b9806587af021b0e1f2f827354369587d78b5e7a7db95e2146887f4d4ce1022c670f269a2abead3b17199fbaf0ceed703e06d6e56891d6ab642782e7708917fc252d0aa663a8c3e63d8b5deb91f1b548e56c0aa7192e82826544aba0bc698e10cb4e9d1c0eaf9bbdea40ceef248bf896f25b40c7fa8b762d7572d197f4ee686e018bbd064f29f2dbffa32ed90726d183e05cfd02a84767336a1fd7c23663815093f2ead7620cb68f0760f0c2d736c9fa79322dd66f5068770fd7eaff4da3a96c887ab32f2d00454efd616e464a4f3518491fc0c24d2dbe0acca9aed5fdec2becd6910dde915dd61956685374929a46573a2dc0beeb81cf90f799a1adcd1324f547d5650978a904bc17270f8dfe584f6d3e8c67a959eb2ab7b952bab14511e45922a05547d128d1ec686145c53c3ddeea28a36bf219e18a95800f6231f833e113c0c832e21bd480e6a906eae3cfa060f8831e3856be25caacc7f8e24f9d68a18cc001d85722fa9463ac83eb58945d778c474bc1713df6a0b7a00877ca660ce3842b31f2540288ecd9f263d6d4c5d7739a968edb4aa81f7b945079434c09050ccfb2052492239d0e509590c731dc1ed1596780c94cbce128954f3ed4b5f2f16a280d1c5a0ed3ef01dd9ca46cd0d9930076c4a36f95d15e4734f7473e9918935b63e9529d79562f4fd9876127d95bb6c3400fa97d2b756856e6ca37a645c25da54aa1d785afad3c78f5763e4af2e1b038c02e3e62dfa549d230206f6db499367703bd96645701b1b82f2dfbb5fedad839168767f3ccde49841f8d901be51c2369ffc9b86a7b57b58ea47aae7996c80ac16beb51ec75a8fbe390a5f1d566c03947540a3ab66f7c0578ab3c5cb3d1b599cdc572d6e425665c1122b3cb52f82073c679d6f8ba987c3c56c3aa3d9258e10eb988f595fadaf7a8fb714486cc032fb53824922ec3d72f13613dec139e19c86297f5b1b80066a3e7c6b3d875ce6bdd0a5e4f59512ae4fb5a830971e8ac84609f2ebb0a9ac0c1dfd7d7e475497d42c11a7040ecb5b9e4f9087bcdf643fadce4ed61e6c17aca59ea785bd7f4f6f9dc3eb675bda3269cbffc212d62b0920a4a947815ba31b22a7068032609208024fd1420b86fe928dcfc91cdd7b306f70a6c75397da793aa2eb240ac0499aaba8ed640acf6ca08ba70110fda1c68839b2796014ca340555d0ea0bff51a555d06b2453cecfd1c3fe040457be16ebc0d967fcd158437846a4e39a6d094294b018a2e3ac7d59e105ce86035f05c938f245ee1d730298cdbd17c55a4213fa39ea93efaaf8e0212750ebd525b97b5eb24dbaee862cc7de73d9020c85bc78cacd74e6aaf500ccbb657a9aeeee9819824ef74f6558fb850570dba49f38a33510ed58d40eb5ed181a347e8815abf4e7caf5707b9257554d49f0f950c76b047664e448687285da13d0eb8850b5fde767208872ab4b66770be68a5594929f497c64a65d2658f007b70c17ad8e28ca3949400a8b369b57b595bd78a5a56e12c58fe22ab7b8a15bfbc2cb33c4fc03beb67cf206bd86809d09b7d98e669c3eb26ba485f8d5f794d0046fc360aa4bfab02a50c129b7b390b56414eea376526659e01b9f1b923965c25b1404c98bec2b654576aa70b30214b4c6070acb71ff7e8dbf145b0a912a829940c6ce1cb2e8da003060d50118ba6de5946039661342c445000c91f4bdaa7a343befae0498e685e8c4be658eea3c939322d3177452eb0c1c2b39a13045d07f336216b33435c79aba815469009191840ffb406c6fb583434b3643d5d921c4fc5408de8e16430590a4e6681532cc1690ca642ddfc79104b053e859b4645a9f9a1fe4b27c7f07532d8588b512e0124326405eecd1c5377b22619c6e24f29877f6f36a0fcac1a7a3f8eca614067a55ef421497d32506443d5b1052f479eb164e908172796206286dd576049671a6ace25607663c262a0a2c06cc0246b4a1853f26e53f00c5ee4e3cbf2940560545c9ffdfb9c60f2daa22d0b2ac7977676e6ce159411b00147a51a42c7f7b4c9e12cb0109921cac212b9ccabb2e7b10e17f570e5f2b2d95a9e511a246461eea60dd847b70645d1eb8536d01bc0816dfbe3317f74785ced5409624fbd81c53c49520c4f0ac13555498f97c38c1eeea51ebd20fb2c126875911478662a22565d50071a8b85e560781b0b50a49783212c8c65af3650693129bafbab400a3790e71d65a16d62a00cb3e64cc0b36f22d665a666f73e28f8accc080cb490b026795f3ac4a49995f6f364a2c1c8e4d2ab97c1d2faf12f4ab92d8e6b6e35e2512f70c67ea236a05c55a67ce2e34ba708f324a21ef50609567d910cf2d0079b6418a758138f755a1e848d198b9c1a763f244436aa7305272fc5a708df17fd982ea707dc08045b9a2cbbf2a8a9f3adad3bae3046e42ac0654b468366e2e88943cf33a363f29e51c17639f5cfecd9916623415abfc58928428978cfcdffac16600477796885a2e3eb75ede17cbd1ee4d2923df5be3e94dea46e47261ff987827c74050368e6acd15aeaf17b8301f60905ecbf50274d27dcfd40cec0fd189d9ec6fbc1c61d593ca8701bc32de304eea365189ad683195fb42b7c62562c4391b33eb960df11aacd957d11955671a741af1fcd1834ba832506110a51453387c7d68f049905550120684db6093fc2e376ab92726e944272f92c54ed9f108de09ee8b8fc8762bb926c04e459b476d88fb4b9304216b5c2f738def7589368849250026f14ba30e2073c2b83d8f75f702318a42ee8aff18fc6306c6f73e39f63671bdba214045c8c508936db83d8ef349b7b12aff222a08295618f1222b40a2f30b3626fb3930ec2c8445f902d689761d7b71bf445006e3c723c1a40a505198e6285807898478a5d12763015ba061bea3341ab38faf0fabc9b8b1232c2ac4c5a37da047491907e910ff3d82c66e3320283ade7b87a0fe48aca078952b4af222fa48e8a59577c7a9d49e0a814ea0b82d1db792e2a218c9a7dd92f69d556eac2a221d430443c53dfb9e766a07821edd4bb66400be005511986dc2ded50aa9fa4f5a4c466ed5ab0ae34a944f40a6c06d4862b848df4b893deabd4920a8f9a0dd99345a665923bf33790b35ce07b64b2e8f3f6d2aba5f7e75345fec4630164643cdc5f41fdbfeef62384947023cc67375febb2cc75e5c83c21424a5fe24a15b511f98f2981306628ee197baf77d7830c298e7df2744913a80056b9d040aff9af96896fdaa437b7bb82f64e0ce61aa06c4b6634c180a6b0da2d68caf2c1e633ee9cf704655f48721eaf962494d7cae0e7fe1cb00eb4d298c873bc6d761568e661fee4fa06638fc86d5cf25a607ed1d53a40109cfad398325e70afdc48ce9bbb6f093890f3967cc2f6d9b111b4689651dc209879d00e4e68538da47a9f067d0a11f1a0ea353cb93a4d182e363d673b0d3a59ef13ec08ab6c4898f8496a8a09f49e9cb728284b1844df4629c47d09c90991d20069dedf75f889fba4704c0963e27c7f9a4f6a7d06162dc6ac10cbc07f4fd0e0200f34cf26bc24d782d0a026e0e30e8445e9390da239ff4f00486f162c45b86da762ba40fa36896bd03b190a50a06b04d0a05ecc379014f4937e6c98429938084b45ef3bd2efe03d19879148692429ee40dc9bb4e476511231e930bf054e5474032daef52d61adde609f9a84973890783277862188dbb69a8613969f551f6228e2714fdc8df000ec92a988d1882d865c8ccf1ec69b034d2cef704ec510890e845d099d999060f0e96b5a10e88d556060745c338533eb7d0b838116e555818cf009735b56cbaa26ee2cf2b4a897de93cbdb0d1453af75f5abd20e2619e15369dee064b555ea1113a1e28c6e516ef465823c781c9b073600539376c9db1a829b1b0e4db98fea241a1d81857194f9f0e6e52a1b9b09c49f46c8b08623865ac8042a58eb62ad996032f86fa38c95a5951aaa39674f83f82cdae16468429e2c3282c63ddac98c38666b076d704dcb4bb72bbdaed64356a8c56000b5453fc80ad84021dea26fbc9483b0bbb0f993408918de67402877a453224672508dd36972c00a78adb2004df56ecef3139d80ea9c2c4099563738dc152b7eede3413f938d53a7c22acd1db359070d4795498c8cb2330a5ae21e56ec9d6d66dc590ff49c0441879826cdf5d3f5d89c8e8710f178e135bc1ac8cb24de7fd23d350c2eda84b01a6214a746efcf0dae462686219c3805ff66494ec4d69cfeb7e608d87619f9f3d5ccbf019e5d228c6a1d9a15d902b60dd4a6d72ebd7a0c0eb6710e5ab013f0613faa090afa930898c25cc09528b44030d2af60de0c8834a51370f849cfded966dbaa61bf04d1b1d4fe485f1122b3e6252513bddbbc5a3b5264c759a49941f7d7e214586ca401cc9d4d4dd1bfaaf32350fc6486bb0d22d0e8851afa22dcd0dba8f3d0aa60190169b0980216f34252187a5bad08b7b19e2a2f8e59233f69cdbe925a2e8bbfa635071575e30fffd6d2ad0dfc864ee86d9bb14513cc8202aaad7f82120d525b00626a906b7d852c41a6df1c17bf1073e109232e30f3db957d7e7566b7de694d0c673754298e8765a9d97976f44cb0773e0566740a1e0489d405219a1ba19fc5cc9f8abef8b7423862d2779b8e9abb19b109dec7b5079a253609afbbaba7879a03da8612221f0c8a2c4c3d204cc7ab3a8f1965374388a9f7a94c1f2cf022b3fb5cff8c0a35d7ff0a51d2f902a295714621d819aa7fa60c1ed787dea6ccb3961541a26769039be9e9ee6b7c0f1469ed8030f9d64f63094c4ef0502b20f1fa9c513e65e66829b1e7936b18e9eb9f7c180c80b025c96d49c22feae016a16650a5b099f2c0ce4db4bf5b86c97467cd37345403060825cd333909978d93e57a066ad6899acd09c5c517565202812342cda186889f80391cd41a0799d657388749462d9ce25138161b505fee4938ba17770d04c3b689eecd51159dc0c02d0962a71e14e0426f04eef5ff5b22d4d1cb5546b1a58b26c0e2c2b5bfdbe0992c51cbd7767600fbad66e6c5dd182681fe9c613d68f31fc00bb00b39c681e411b2396d125b852c2e0adc29c04541354499feedd46329229b3ffd412ebc37646075fae66368eba2e237ba646c61e11b8e14e1fcf9abb1495bb614d1d45390ae4414ccc2c991e6b0a1c6fc20eb4e2c3dce8db1998ceeb607321183e4ddab3ec49bd2e94de4965f43aa0de3b6eb7632f8ec3d8ccfeaa33a502f7a015d0b79681ca9caaba5ad6d182d27101bf14b5b0786cca9a3ba025ef9e5ff4df7598d0462ed92cd46a0bab22793148cca27b85b1ccb470b05261f0c3a39c4406de6fef7f6386116ff5b0b5b56b752fc8ef18194ec4d799fcd0c0c6f61ba22ebb374a01783dee533df1095cb6e91d775bf1442c8ad7ac447768f39eafd2058ab8e91e2604e0bf82e4405b386e92bff7bbc11f1dcfeccb2ba3470ee5fb8b9ef2f550dec104b991c325075686ae4284589c69a07ff948f9d9e4ca544022a7f6279ccb2ed352ad4bae9e6e65e9bf34e6de1941209e93bd3507acfdab285d961d473b7b16d9d76fa0a5adb04373962582c83098551d512a6baddfd23ea4181e401647b799c38dbb9604072d617edb9601cd7bfb6dc912f96f8f19de6a01e4f61806d986737a7983be290e3b417a45dbabc81dd355f38084812ee0e2fb66159fa67aa63efbbf157044ea181062b9a85d29d26de6301ede0d1c096a9ac16141850e834bc6905c6ce17e12c0096a28514463fc576be070ac037da5e30454d24369620b5dbd3b4ed6829d3060bd02b5a9fbe10b18e49eb926d1f2f6cbc714cf49c2a0046c6da30713d62f44dd7d71e53bd98a6a86e1f446c325bcbe1290d598476aa1a3426b62a7e248559bfa573d03268f9d2a8fda8138efdf7b7276ec25a51ed1567265414af8631dca963e38631e93df9e6a5be373a672760ac6cc6d5f30662dd7de7dc7a75784fc02bb9fb157bc3b76d9027bdd5e195905163149d795bbe36538c00287f67406191a462c6db3b3d62740d181fb6a7697ddce33c50bbe574df0c624085791db6a8fc3a482f651fb613f562eae59d23f3dab0dd2cca8ceae4cdfead67e6bd9b05a2c82b832e1bfd75c1cad825e7d2550b274276740889b18e5819529a98047cf3e73cced1232151afda058c6cfe5bdbcd6a230c3ec0a63b332769a30124806b1ab73b7945091b308a3c8c7c452a6ee2526d2d1d043518282cd1dece5b1a778d978ae3a1eab2b96352fb124704ff7a6e20f3da9a1a6123f4e984d33e9517949eb17da46825c5a83e4215446dae239a015512a7851632a3a202f0ea8f8b186f4356c6bfbdd05a87e0e67e78f713334998990ddedf2000d18093088372202e96f22ce482ac68c11d239c5076952be9548dfe642824905b24ceb58d49995ae0214b655630b44473c0db8b6f4f15160d936cb6179b43ddf380acaef00875f0de106c747a3c88d419fba00135e3dd579a71346f4ce33cc2d7a9dc1f1c88d2d585d74a98a63945003fc6f9ff471db7ca582098327f2217c099843bf4ad2f82e12542fd4b2900289a3aadf1e81c745db4f50aea27a196057d68c0e7d450f4ff72a460a512ca60a06bfbb4ebfd1d844c5a9c1c5180177d86d40efd6ffdb47310cd96230babd22ba62e5fcb879e39193a1abfafbab906f6b0c14fe2ed9f2f15e76bd84d2e078da2b61cb13023e941725d6ca63446eb370ef99098d3ebc9c6b2e5605a3aa24434ba202be22bfc9c10ab01f110e0c7dec7ee0673a087703cda24e68b33df6592c62b3aa55013a49306423b269b8db14ea2ed051c839ac3c5b57bd01d717cc04aadee10dd53e30040728e723185b9bc9c9b813fca7b9d8f0e06b665d1add0390f369e31f0034e6b7f93746407abddd81e54e05c8167e6ee2c21a5751680d6d64ce0925647a50ae00c9434c80bc34224273bd40722b3680fe26ed73a990f20bdfadd20612340a0f8dec82262bdd910175c53c457f4534a14351a4134c213965c8c5fbe9e776fc1abb9c9072af9616d2e934928cc6b4d46ce86f6bfeeeaf059e3c6c67b726f3ec960a74dea364ef2e607e276bc10512b6add49da43e47de84f6a1686cae93442a740109a251e70463d79bff20f6a8eb31238b7ad0fcb0f425a1bcbe6a83e1afd11173e365ab8b9191c1b70eb3fb979fe8a4f0c959993c44e68789a89f52b2bd3cf4d5a19bf85575a9c97f5f66adcdd24416185ee936c340738f68e3b4cf3b06ffa69a74a4d62b966b18387311fb786d6361b53a2d4ecb7e759011afa0b6ee86103548f48017d5d3028df992e21bd264b033edbb0a1f2f70f6323eca4a85fc15e5fac1d3d047fc5e762490255ebd8c0683976a6f2e6451325d6e08bbb8340a3cb871b26693c6796e268d926e5d54fa1a8ed277989e1fd86b892e152a106b2d6417e4566540040ab1ed099818015dcee788d7ffa55610d63a31507e66397adddcc35d9f9a82ba9ba4b5b1e0731d63fb1126b521fa7d758c329226f029ef6df9ea5ef787b25ffd3843cebec7b7111714e06501f4740d5ca2e61a24ea7f7b42d9ea8b1b38a8e001d706326a6015e3dd6f77604c0a19c532661406c4905a5cd5d616c483c7d1363dcbc505463f97652e08ba17326a1e807e3055e7b2d1ec775d54f668799a9523cdf8d2590f9bfeea4de6c2cf8c3a17334da1891d57a9c42d8185e425343f41a0ba3b98ab9a4d003e5d0a4f0109c5f04c6dc48a289cd85078539ef429297428f5061c65325528335a7b20c867f0ff38dc455b583bf1f4d83815e2a96eb34c966af1677e36d9bd6dc5ffa9314df9a1c9e5963ff85f431f4734ce22d3a8be55e9ac810016573ac31dee82810be687d8c0d45d439ff32c7eee9c34a55f6f20d116701b08a3d48aa2b99feb410a90abcfb328ee655b6f6f2de5335e95069c10613c8bdd392d08e77f629cf6d068b19e4146e5d443b48153285d633f9d1f1484c2dc75e62c4923b74f392afe401f215d81d285dc9aa5b8bfb6f43eb2633613a4539e1e5dccab933bd410f11c14853c60d2f73537a9821d230d7ea0c52f75ec64ce11d51cd4f19a5509c462d18f153d5a56a8f9d867827d80eceb02e7d439177a5d3071049762228f7d6d6af60f1ed7b4829a2d053e1e683cb2ba12ab57227f894ead70204618ebe08d246c52d11cb70f15e698302ef7b87ce31c5aad0a9090aad6589160caaceb93e8299f27e5715459967f98e577a20052c2bc96f5e5a4df606b05dbe961d90a68a71a55c31d8903d489d7078f8a9c683ed70d4352430478fc095c57ef58fb322560a1501ad71b809046a763b8c70a43cb983ff2a7a1415c6b07e991cbbc1a7445e0798aa6859735428ef049363016f4d988105cd61e3c9908c4676badc587b6bd1a26105057fdbd1e699b9a9b1f2b12c526c2ee279ca7dacaa990049e7ad63f0c53a9914b010164382c2f9687fef7b7af337077beb9d640a1c9d9cade1a972b1ab129efcb9fc912a52078d03f98cf330c7afb64cadfab2b93477788b52f3184fdb6bb6f712f86982c7c1eb2803a4362f3cd218e2a7ee80c7633ddb64b087092351ae700b38b5465a057e2e8efdddfd47a2e367af903a313d9a8df4dfe203169e7c0df1f881c4ad6ffe63d2a1610ab2b147b47bb9af58d3deac4771d64a9eb6e89e0b2cf20e683958653a18e49dcd69c16b36cd39034700d28eae67b15f05356c3f3d701ba69e71bbd9ce66395f1915f92039c0135790dcd492719bde78cfaecde9c6a93803e4116c52c67bae250e605796c7b9cb141cdbb8c85aea434e8388f9a84357182de00297eb62e88917fe1b4b5bab71fd839918ef48d6022a5cecbb706b92b56e1c9fbe49b9405a88a2584340c594bdc492908ba41e7479c479df6d3f6b75ff88a4b86ea6976868d66378587596ce93faa25c66a837795c27511c4cb3f6fde6091614f4cd567bab274c4993448de70f80044f008c76ce7174d4f85625fb4dea6f5b45a28eee96122f2d3fcc4ab02663687036bc3d9608e9ccbdb0773dade8cf4e911884d9f1572870cb913caa319ab42c45804a468414f3d40113d1bb04950bbe93bf4ad809274c76ca502b3c0e5db2172b3e172e239b5d163c60f578dd934100b62fbf009bc3d78bb31b4673d7213a46da687271ecac2d04d388653094dad14c81c147f4a49ee060f38b5173b4128b3474eb3a81bfb1f5846c48801ac068f0e3225a38184c1cba33f91a0a09c61160974ed849622312589a5c7da85de78be4768e1363ed1fe5218a26ba0905f6dd7fc7b111f050e9408adf80bd00f8deafae2bac60f19ea04b664fdfd9a24b9ffeadb0ce78db4a043b8f05a8f178adb10ba2232e30bd5d7cd80999be2cdb89ae1b18450a5a40a13373c2f232e3774c9166dca8eeff625504c8d4d5458d2da0db043a5cb190829cf5d090b3a5262a181075bd071610f1a3febc70b38e6858714508a25c712b8ca430709e191e5605c79c82abe3f5dcd156b99dfd7c53c0da886e979373e141cf8977ef2fd280df6c977a83838d08558e78546c3d0a93c57338aeac138c06c26172fc4fe4b38497eec5f834caa65fe4d5145b3048fec4f812c0f66b37ea11d201dd0029783b900fb21c0327cdd166a1e1c307c84364d012ee39fe04059112dde1ddf05454a7871238e45e69645dd649bdc921f060a2de7b68a8d7015350333450a3e5cd57187e5228cc9b7de5ca5d428bd61c9fe64abcf1b43bc9e335a76f3885a0ad906476904b1dac781fa9db05a3d1391be2daaad449cabdc483a79350e909847990eca042c538c4c83b74ea1063a47112b6af25ecc3add7cc6dfa563adbbadfc63e619d9b063a8098a819cdc45194f70bfbdf7901696fc0daf5e407784c158506d370564b1a814a4a7182e2509ea139beac8e44beaccfbde739b6cb3ecc6019af8ac6d115095cb56f039bc2202868f644755abc9c21e60cf48b503e853d535f417c9171757d716ebfc30520cb4648437f9ca3963b04c7da77e5fbf4f7b63ebe14bd113afee73dad00557547482ba7a5e0c46efd5af86e44ddc5332bb1403684b537f1ad9d7da06d00ce803b1934548378d1d06cec777b0befe9257e14d327260ff3201fafdc7444f5b15c546e9d55363a75302ef6b184358c678c7f6afea592d017abff6a67ae1df8083e75667a423cf2033b83678f2051d98edd63f56d3c5953351871486e5bca5df552b7f888f53e2d087ef1457f5813ee8529386587a762adf2c802f0767cda402916ed9586f5f8bceee5f433adfe7ac1119f656aa0ccc4cb046a18348c61471f7986b2e84af3353c60215037d5b1386b6cd95c5ab37d215353bdd321fda85c6637626f86294838f21b3bcd6bab439a592784fb83247345c51894b8fc83f39b5db053107132ee40090799cdc41900f577d627062243fb0f6ac2fdd021a828b48402c46ecf090dd94978fe11bde4d40d0f00405340280df5cde43f1ed2a025657a744e71c22d25e04bc201ddd20beb8eaf32b30f690d6fa9a1a02cc626704e8b32138ddbda631c9e9620d70a3a3e83280dfb1487bcb16c3ce81a810e1611dbe675f6555d123ae87217a27095f18b47b8cb58dd44e551eb4b54623c4282a69b88d54fc6e04e0a13d51dc9461a938f931f7fde18031bef785aa16e3278e13cf3dcb712a447432570cd78707ac98661033c6fb22fd44e6adce7fe30a1e308020506c5c73d25e0fe75814dd831155cd7dde542a6a4f1287dbf9e1ea79ea3dea1e78fc8d4f20c79ba0747d187d5a132024535fe86334aa388f762eca0e4ce498d978576f0307419e74c45998ae3e73ddefb84de251dd37bcfebb579b95ce8664d7056260f5bca0e3768d01b607c11321a2cf2187c2ee4f318c7ccf4dcde900b3c659727747d2ec528f3c9c13e76208f4020778d0a3c0c51e16dbe653346ec71d8cee71630ed69b4097b9f78cbabd0bb05581b8956dc853cb9a3707edc49227656060c34fe3f60b994f6e487e1b1047f105487e8898b30f3ea1cc2bc8e78922f2ae82126b7d7af5596316c4180f1e0a1bedcda9be7e82dec06e8db6f148030b832239c6b5c629afa53225ef50b4f02cd8bc965270fc814b67f08b76dccac8d8f2650da286d6845e603a608a455455ce66a02919faa01612c9f7535ba7fef713309fad078f2e157740116d02f17f14be0f4677fc5931c5d9de49b251811fa03ddcc4c5165136e41bae56a1d3ffe83bfc800a263dc6082fe2ecfe15ef5353567a824dc3703a6afa96dd0fa395758603a064dcfc72d8c4149eded1841efe314a01e9914faec47084272cade2bcefb0ef7b4d30ea927be12b1551d2849b4664d955a381db40757e530d7b4a3f6f86d33073ba4333bdb907fa2a3a63568b217bcd641b8fe6ced657055b31fb9f037f706094d1df567f60f39850a936d0ca4c81aa4532e987c72d30f9e59ba57406f7dcd1b84017affe2f9af56ceeb8fd45a3d9bbc98545a65bbe48712be9fe960c63a25ab6a6999b54bbac29e8f1f6315dc302541a060a3715bb2f91a949cb510e48ea8bcf4cc1c21768bd3d2a12d40d01489c910140c869bb9897594601820ed3f3feccd57b7b3c503c0d930f1182b1b1a38aefe13ac206cf910035ee7e4df6755cd09454b5b642d3a21a09040d6a0c42cb6721cd91a8c894b148c232ac224a41f697940ecb4aa220f73b001efddeacbb642d4f56053e67b2e4c560e7377047809fb85ac622c794c212b85f435ad6c4884e132f22c474768b73fa5ca69d763c78ae74192ab88f7c8f67f8df8ae381ced2769d67e23f6ed00d86473c003609de6d07a3d884a32f9ccf8684652eabb3dc08e0b99f7e7f5b31b83d2aadc707f536eb9e97f682e46509262337d1e5e17c627f8ded0148b15a6764708b00601e4615de3027212e4321abfa73c622d85317c88e5d9584614f4cf23680892c1c506c11a0908e7e6b793ca49191ac00038a7775e6ef0cd5f3e3c7314ebb16ea6698dba5e947cc808b48b300d7462956f6a3aa1a49db126d2a612b3022592ca24638010569d2e69d43843621e8ab09f4cbbb66b8734fe2d8df62807f20e17b21502f9052a101e44c87e5263a40021faf533bfbe313974b7661dc2598055c55c0c83a660f9b1417aeafd372fed6a202344e15e4c8e87fb0cca3869165ef0f36ad7170f57c09cfe824b671be17166e360dd040317fa65b6bbba50083b3d782d8eb48692d646700f431793e29d38ae8df0238a4f5625113e79b98e672130b4b8a0196464627e1edfda6146ec714f906083b54bfdecccd65223ccbd07fd86870cb4656acb541007021a7bea23027c6be2f6e0d4f91d98766f042fef26998b50f52a2674205d22396a59eaf2b8c71649a8e056189c0deebcc5f405355333ae3419941c74c0561774b0b14007ba9079ff9f40c4df4d788a094bac77f5fadb24aab7074d76622e47f28370887aea4c6d1dc87d523b365252d9efb86fc16fa321900616046ad41515e26c6f49d3dd3f560ccf85aefc5c48a04320019bb596fb3c901c766bfc86757f4725176382b301b2d360d9d120c16180ec60b07436b371757f4e824d53132653417600e15775de6f1c8e6f36dd5f1e21f71d0f50dd116a059edb803cb789d03c7b431c0b003ee483bcaaeb971b072382b703b2d761d9d331c1d67a3577078b18789a7b940f17a643429b7192d3c649b89951286f1275db9475d55cd21a6796a68ecb0fb5776e0e1eaf9f2a76d808360ef6b3c51f76bf1d768c7e56df7d677018c76ff10aaec414d58fd882dda02c230ea249ae1e0d1b886cf1cd93d5318c4d7607b577164c8b95a819e4487e99da2763e6a6d55f4159f66f186092d80f93a8dd4e9d8022f075cc01b56d424518bf05fed964fd8423cae7dc25478968f5ae8448bf6134b1d89731a69c3ba730d505987a8fae1ee1759272f6c5fd112b33a4ebca4e122ff67475f659e562a66c84ef9a6de37436a4acd1aeb5725f8c6831416cabcf8c4ca3aaf2814fb5a7a10e6f94a77789d01d2b71fc9396b3b6d38003be261f7290be1ca2c1018f432c0951a0f1f2a9e1208558e4563227be3c332da12d26bdac7ce02edc29c34da7344600448b04c3ba059dddacf0be424eab0ef2cab9e4800eddd247571cb18ff6d07d1d4a2c01363d6a8d19872d748e29cf9f26b3800310769dda6ac68319c5263ab0711c9a0fbba4d271334baa9739e4ce4450e38b4517d572498585b54613a9b5ff94ddc35c1ce11d13f3eb60332962022318de3141c39d000b03d84039d9c00ba60b676d997d99223eaeb3fda3f2ff36f3e0df25c58e4976536b8b3771073a59215f4aba9c2e3e1713bf6a659c9a06ae6220794de25fde6555341e00aadbc806e442260faac721d9bacdd710e887402d99dde28284ac60dc583f0d36f1fb2aa43b8dba23581ddcda33e26dc5ae09ad356da98940681fe519bcec25aef6132e54087f437ab9d941695d9c92adf9d8d904e15246bf2d9cca2146a29b7738f71bbbaf0f9a44aa8564d0e717afefe421fae29a841dd9b1b7f6baf1c1e688d7ed23f47c0c8ce5189acdef3422ce7c58b01177494da04dd31ce7ad3ccb9f78ee1e125d2db4414f5d15c7f09ef036ef9f2e94a0f80c402f0264d44fd9226a56f229ca6c576e2ff71cb86441f1c689b06b8ceb0dd7caf85b82e3a9efbafacf34591dd73d689fec463a1f11f2a85349c7294bc99a6585c1032db728cc08691d99dc5888f081601243ef02484e938af4ed38bbd6bf0d349e425800a2e424b602088d67c8a9970b158dadbbd2779026fe1e877e036eeef79ed82f258ad63cfe5006174ec9da4fe9d22c0883be069b9d20eb127308d31a363b7844aa5c3c3e405bfa6ac2dc5ffa74be0c58ed62f1650208b0cde362cb9708578904628c80315bb0d521f79dcbafd177527cc690c702d9a16a193d0b655f8c3ab92bb22843650dc74167205404713f6ea64fa2d0b70e8a0098af23d6ede4dbe26fd38a02189654ed8c523fb3d12bcd3665345a071c6159594a84044726ca62012d9547fd9c5be115c85b1b8984ce0713415773c0a51b8e135aa4dfa95568959eddaa3266dacbd5fd106935e285a894a9d7d0a9e5ac28d63aa3a4ab7470b29b6e1cadb0a3111127f96ded162420edb795c13991dcbd3971ada597a0a4f0e895dfe518776e1e9b61939fddb0123ba5d75501ba1983b5711894ad4a4ed17edcf5fa9a442930c2630f23d80068ef6645b7724e9c3b0f1318a0ae8bc3a7c02672281edc9b16d630fd4b1d84de422a783b7ffcd758d0b04c684b4854a09b2f0a39470f2555c858ad088f6d57cd03a9e676896510c15cbb44f2aed2ac5138c3522b54cba4dd5a53bd5a02f34d40d61f254723b05557b0e291faae9aa6fc056b6473601f8a882a261567979df3b08421dffb10ca6296e84fea9f1c5bd3ec6d011bbb6f4924a73f39c3ad1cdb011847ae3e9b9842aa654f4edcdff8a63e17280597e2b2aa5ffc7a0d8ba7b45c68dff41ecc6fd99c4a75f56b616d9109c7b86978b11ac9d8c1a4e4ae280b9712024aabab8b22f5f56304ea6ab78e6a3b029d5c0a2e61234463fe89506b44a4af04fa3874c86f6ebd0ca96fabb25ad8fa0311906e00fdc65c0785d9e9a3bd053c9952e5a34ffcc5d298a35cec3cb9562123a026e8f1e4e0c3839cb042afd09d35ecfb25c61f332521359aa7f6c9240f9cf03e82d731f9f5f487c6352aac0314d3021adc555f5d30522a9b0ef1e28426524ca8ab663d160dba674e3ce9ae2b07ef3299aa251505685066cfd96aac86c2e88aebf742986220d1c260a817bd5a040af1300a49a1878ac3a36ee8c6b98ea1cfd6ec45363dc2c3881006697befbdf79629a514f205ec05b305bcc1aa6f321d9658d2df45ec244bd23699d3b4134d6c46b5ed88924d4868a64454341372f992679b6c33da8e6c421b0c969c6626d995bf139e127a45f589e44e9cc9694e482152cb9d546772a6ea3f7da6cc6936b02bff15c3f3292bb72935e47b20a7acfa2f973306ed4f6304a095181512d28c9413261f274c4cfe32605e5c5a586241b11382fc6867fca9e850f00d7f15d19fd819234b125144900aaa3f92ea7f1a8d6a5852044be2b02bff203e1dc6ae8010fa41142f96e462497f1fee7994a652ff2c9a5ab650a5a9e536096911c36215344755152ad86847a5763763a2ad7e63d0d94d6b9f20744b4edcbd930e688082c61c6b7287059e1d9511d485c9c3d3625848335f3e561f17cc916e8ed067128428c8d177baf9afb95bdce11102ed6f74706c1e4f3c7ade77ba59c0d25800696767fc4b7e17d072a1b8ff8e1dde9722eed0817a17e6a7fc875ae1e32aef8cb747d5c3538b5bed10d4b7f1503c3d288ebb33de7fe20b5c5346158d0951a211954f51e93fb15869a7917d5040594dfae7657ff81e3e2bfc920c59dd0a47f73c4a6b18ee0afcf04330f47ed206cbc349b2bc479c26fd6f9af45edaf116bad5dedddd9ca404a9cafcd73e511f9c73776fef2697d58ac024454535a31e5a8c2c94b6265ca9d53c218618328218b80bde82c80b3943414008f1d9284dd6090e2d6029bd89113257152aa8cf76581097299c4d66a276c9486c1f765de735c88a60f275953b13a9b00d3336628193daeda4b6f3a6b03577cae4c632c54293debec6a063f7919055a8a0fdfb251b19ed380dbf7896aacc7369ce1d168e2a3f4fb7542a03d134bc03ad856e6dd211231fbe823f5ad2911ad42621a63a7b5ddcca335fb10de81ed5ad658afff58d41474fa401dd2f3d5a0c18f5ccdcf4fd295dd0929b18caacc9229f752b759310e8696fbeff6e44108340552b8c0c60dfe6994273543e4785a69df1dfc6f042cef33cefdbd7f721d8034e15f0555a54a15248814853535393d89432a2787ae2a94997265331bf339f128e5fa94923b1318d59bd99a8de43781f48d44bda08e56d14f6964975eeb899c29c4fb8ac03dda4a127d5b9981e4dce9f58932a1650fe79b58ca4d2f78b240ecdefdffa8790eacfea968f9e4769ec4b560a9e47e79093ba4944c84ea89b34645439e6349e47692af54fd24a52c7d6f4cf3f000fa9738917069dd58529764cf56b117b8ee4bc8c2edfd3f2348a501dd7e1f710ec979f2c3465748a1a9f54c00a1d17957548175dbe4c81638f60046d7997f7173ba6ba89fdfcf21268f99751022ebfbeea98eacbdbb467e4e56d34cdcb4f16d2ab493a6be46efc948b4aa37e98e73c75ddc19973ce39e79c5d4bd7c9a5630a99f3873961384ea3e226a7a2f2a8ef49993eb5e5e72e94da32ba5646fe11631c63ffc6719a14d88523c37666e280f3e7fc9df96541b7295a4165a6d71c0d7474c7409949e8349acd8ececef8cb0826b0d32c74eb7bcfa18367a88e584644cd41d5391acdc66667fcc313db6c0daa04c28279192fe3431aa2ea05044710550fa8f1345ef532f2a8f134543cfe47f8df9d5919550b68797e7e525fe8ba81ae726056fe960752b728288bbd33a597e2400a1be0cad230ff29fa80c9e543661a7f5ffe357e5ffd32aa1ef05fe379fcd7a8f13d22fcd7f8ff1f7b667c8dadf1e2f3f81fc71ed5ff8b63cf911cd5ffd813f335c67df1c85166c840cb55a25e8aa6c02e28aa964b5bf958f8de4029632c4fd1b2352c5ff2104e78e28c0354dd405751dfa807bba0a7ca309f82498daa1552fff22aa4c66d52060ccc7bfff240eaf730e2e732f234b9b38526b75a001c3bd37f125d402cc460e0892181681d4ebaffd00474541ae2be4610f71523ee4bf579b5db978e163398a63fe1cab8b8870c031428309cbeffb3719ae7fe24daf8f046da7b11d2041b279430734300374455f8ac07f21280a80a6f7c8e00fec6877fe3c6b36ea0f040588fc2e7b01e85f1c71199ff71c2b364c61cd6a3f0288c3fc86701b9210001bc0922eb4f106f3c29a2f0366666666cbc09e30ff26dfc8f137ee68198f0404af89cd597f0fe2500e04d187f98f000f81f25fcea81bcc22e4124c09b200ee04f10713c2916e00b20aa38a44d0735e941429b6c9b55ffada85ba51fcdba45f4bd1f756bcaa6d256444b9739cc5d4602ba7de92815168a0afb44856dd2fb243b653dc6e6e019ec0d7f0fdfbfa3fcb04b363be34db6a66b939d014717ab9e8630b963d89533d9b08e01f1a7a880d5324505cc886398f7d33086a1612a1eafc2f97124e75ba972def3bcb1c7cb540dc0717df58d2e5f398f8b391e4beea1fa566bb8bdcb33a5eca9dcc8403be33f846e23ce78d3e4d65b1647449894c44ec0036cc9107c9061a526fe61d09295941aa502d7fd2a003c9abc05ac96bc05ac763e36af79881562613db4ccc51b610d1bb94993fecb83eacff15619a5c29c356c14f79d08564f754c0bb5ec09a526bd95fa951c1c42a78f4749c29559d7d1a36b7be609db85fe685ee84f6ca157fe9c98819df1f7440cec0d1783765defa679c2eff9fef43de02f9367e4ebf7449d1ebdf2f0f4dff784a7ff9e47f8fde9797c7f3a7d189efa1576037a5c5512b45c261e4ea77b50605b0758d27fd4b133fe9b8b72bd35e91f9c1c96252519aaaeba494900a102cb8e81205210c3d49834e121d03b2684fbc42488ea34c94c63e11a93a6a1cf29292a2a2caf32aafa577e5f3d2e9f32aabae579a47ccbcaab7c4fcba7742ce3ae8cb4855d46d95c79069ffdb949b7fc9be55bbee507cb188295f108f840640da482a3a3de1f48ff27d3e4c3ac44600625163908d5e2096419d3f1855ef17b3bbe62cec72783944f797e1ce620670939978ce4a4b20e8bc5c2c961d96091a806a3bc4e932c43b8bea27aa139ae7d7aa9c7acf2ebec740ba6f2c340e303d68c80a23207618450e5e7e1b6f6a1f2f3ac80b29af4b1355bb97befbd6edc1718841a89fdabfb7e1b4dd37127f6e895b7d3640fd761b58d6e7d651f652eb8011353122d60814cc935a51a55e2d7f62f5f4197669064ca508ffc31095ab23c2f06ceccecceeeeeccccececec3f4d9ef8c1120c8911c90d03bcd9d9bddb1b8b203e3423f18a27962cf919829f109205121a9ccc40d4440a2366109a2205480cad00c80a211560b14b8ea2348164288a228de44807283baa7852e4480bb890a17a90c44fcd071b92340144d4440e4ba408c202206a53d8c086fe6a30248aa1708a2234253f0005315bf2e467c8cf2c92439325419014418208dd6700ad0ea9adf833ede73add15ad6eec93d965504f3407da64044db5251d74380ac2c2892896242931028a2555bc000914330025d54cb08276e2892f7a7bb777bbb7677bf7328e6ea5e65094a41cfcd0474562dbdbbbbddbbdddddbbbbdbbb35318a6c3e0b850c507a77b4df6cbfa3cd26de6c37dddac6f279e38dc51bb6d971c3db363b37f34df3f6f6762729a9010fa1fc2bc3738c7065ca19e379a9909190883890ff78fe333dd2c53db72ef315f820f8a7ef091fd503fe49f56d2dda6256dd8af1c7798977b12b7f1e5487e24cdc97ce44c55951b36e0d6d6b386762695a881d52ab25252d13063000498ae28c192b2bfec38d2de33e3128d7909a74ee3bd4f729323561df09c249b757211cc1ef1b4bf0bdd1d5a40df8a50b649b19edefe19272cf7395bf837ade7bef5ef7fffda2bb54f7dee55a66eeef7b15b67e63f879cfdf89464eefd234a7e79ebacb5bd79237c30a8ca458b2e1081b64d58b8a8a8a3847c7b89bba514ea44612edbe8845ff6920224affd91a21b666bb695f9b118d8b84d0b29b98748be35105ee69547d5fe2201d107eb79db8686b9886add815c91dbb2b716d0c42b92fb9569980231379230d328fe6b1c744b4d3149a327e9616626139da1a2eb6e5c5b638116cebabc1c8b652dedf89587f626b54dedfa1e01cf4fdbd880dc261dd8773e4185547a2820a040f41aa7f87d151fa07953573691167fc27a69e13617e135f9e672d24d4b3ad49a9eecf21a12b5f6e34baf2e55caa48ec846f30f9ca7ffaf00d26a698524c89c608ad168b3565b2297317b896dbb4c9362396d191c21431e651a2ea19899db0f44c8414a1991251d14c28e64b9ecd52336488f0cec4d433522966f2e11bfe2c4c3ea713cf58624eb3c9b4d81c9b11cb268bc5e60c605e5e3ad641ddf4662e2d2cbf4b61be9a49aaff36c36e22d84d86b186a5d964be82718ecd886ff8a302375f10a8d731d833099529e6e8ecd6a07e64e2d6a854ffee616bb83fd6e4b83bd3429a746ff6a91ca549e6906833b1c307f2f2e7ffbe0fc71fdd7f3fc0f776290a98042cf749d79f9f213fddf7ec13efc1b11b7f80db0528dbe709a862f04fcf237cd4d8e385370c68d7bebc574ee7ec8c3f4369928beaf705dd4616e99afdd5e0ce35aeb112d7d6a64316d25ce3a52e7f546ee4d919ff79946a22a209e7740bf8fe5493ffdc6ca265ea26f5a38afbd2b5286e544057766d0d379ebe24ba8d3adea6d3f188a3d3e9743a4de634e93839364dbab7747af8f42ec8118de3d435a2a0dd77834968d7349bad112b5d9f5f14fc83782f7f478d3a76e6fb8276638a8e59501f62b6851cc303cafd4682ef32ddeacacd61836ff89f3e9c4d22c0726d8db73cd82e7c7d5d8532154b20d57b70c7b9abea6f8373acf646006ad7502453314dcae040b9ed8659d8db5077aed2d5bc28540a503420f1135b33674a093e12c40e218bdfebe8967faab7a6605229a430bbcbc36c9ee3f2309ff3f2a95f9787791e2e0f33aa1ae665bc7c6a54f5ef4b064c932c1fb67c384fa78d85d296959516951696efb422d2d08517683d3d7f8f2728941304ec8c3f8b07d81b2fe28dab6b580f964e53fc0441c50e3f4d3c7952cbf4459d7329e5f5c8d3e4e4f131392e683fe9abb94caf81e5d91a5ae7b7b035f3a7d75d6f403dbf471e2ca83fe934fb9aaedaa3aa7ef5b301e5c61f996c3184a157feac49a2a29e3957f967673ac811184d5b88f301d1b32e6a1943018202e463c856bed334bfb8891d08317bbd66b32e6a23f07fb6860b4f10083f3c8da2e72b70f4888a7ad69dc0cee76ce65238773559d433577fd9b36de49f9db9b1a9cd3c31361c20f8295ed0a288ac269bec221a6d224d21f307e6d3640a3893b6046d9395dea4f96a095a4ed884398d7bde37c392aa3fe581bc548d5219fba76ca31a34977a3661d2644d9cb48954fd5fcc1535bbe8710a9593c6cfbf094d19bf50b7d8f368bb029f859a94cda029e38d43a5c0db269b42e537e9a22f35e94bdd2a276c0a75ab74da1c72a739ad5b4bb37103fda269a949f70eb717e107da2f739a09e3718eb359396130d50ae1cf9f4d544e581b9934186dc2605ddd7fc07ca1c6d904035afa12adbad3684bb425da52f5e762b4745a1735e9bf421335d9335a1bd168b4179a2bb139788cbde17f7a7f37da1a5e62094eb21d25cace84e015d33b5117b05998000a190cdaed395bc35da38c9c9e51356c666666661e4b162ac1064110fcd0719af410f652056511fc0ec3adb9707442b25b5d99b9bb9b6178a1f3b156734078cae9953f07e27c238b6cb21b6348b7626b38eecb0e22e18ca3d3834611fabdf7fdd327f4f91ec461f38bc27c4f0ce2bdbc07c520decb27c8bf7c82d01b042867eafcdd0294ab98ebe58de2aee602c6948ff499d0d92dc5e41ca8baefeebe49cb5630123ca33d763847dbe0c2f6dd871cc74d1f581141dcdcedc4af9ab3dddd45207377779fcd9b37770af72278639962eab123b2d0a47f221d83065a0c4124690a307c50aa0205330cc1233aeb3e0f5fdd75980ff90816b63531b1ad99df0b09e14043aa7b91128e6d0dd7fd37c28c72dd976a1b550ff01e7c1ede83e1e63d0f301c55db6824e5f7857af067b5cd27e6b0263b51b571ddf6df7be1d8037aafdac69c6f546d1f760f82dda8da461cd49f3ce63029e8fcd261dd7ffd4593ee305fc5be316837313549035af64c24a2a5c3981cd6ad6d8288a0ea5dfd55a40d87910e837143ecca1f89fe61a2a9899734a9c32322adaf4e25082ac776c67f08e5d1c685e49afdc1a07bb7fbe60d04b76d034fa80de4f0b481fc1bd8830a4f1f828f7a1ee0a3c653f827149fb6499511dd9ef3d8fbf80b41af1b59a4cb0b9237293e1cdf0a9acc24740393d0fe44564fd9944da139d4db9753c6c44253c6513e1aa8ff2e69d26bfe7dcfa32379edfb5ab7befdbe5df9f721287aad919a5ce2481f7f1c92d71ca95bb56e95dbac2ee99697be4b886aea085aa66ec881fa6fdd7ea8032c00aaccac270d9b51032d1bd6b186354109429747fe1f304292c5308ed6675d9e688114a5f881996b35a41d983908a9492d46c3a0297ada2f29cecfdd5d13d0fd263a8201121c363b6e6ca80eb45c9a0dcb85c4ee76b1a9269ba363aac95e124ed0316c32202de1e8baaeeb7e7b9bf6bc6e75dec6799bd76ddce62c1da5efaf29d5f28774ccccbfd3e4bfd840cb55e57fb18167078784176c8d8778f8197a9be69a060e9479773218364d34238872111045b3a259110c4533a2d98ca8685624039a01cd8488848a8c8478a1fc30d18edab9e4ff264926654eef6f83ecd6d25411ba35cd02e367cf903fb5710cf364a2fbada496293545f4f62be02a2a189ffcd80e0aca485d32d2f6cc48ed791cdb5533d2116db78924c4b4a36ef5126666e6187b50d8083f0943b48f5033d0466ada8f44747fc4f184d6401032d2cea800a41af1cfd95e3efe08c1cbe5aba01951cf84d0b2675dd4b396b5d00d347eb2240041f702e1ab66f469aa49268e8a08b978d3e4eeee6e775721039224e1323bb7cf8deb3ce5846e935bf3e6ed9f8668ca555325fc50c20f5984ac9202ea620141f73dcf474f8905eaa72eebbd238e4e4d87524a757056cca9f5eb34b7e1e8f8ceea0f4b70bbbb2c8fba76b89c481d22bf9c39915f8baaf9208a154504a55c43d4c7225e607676677766676676afb150e369a106456c8a2390581f77a03d7a23120b78109e7f1e8f3c9367a7c9de259eff7f1e5fda6181679b3b7ec33bcdd3c26c6b1ff3a9d41441d496b400fa82871f9808a5bea281f2c840b3f907887fd88128a514e88789fc004d77ff719afe2939c63ecc3eb13f516f7e6143bb45a98bb5a36f5650a9dcbbf8a09df84d6e2e624c933d95e09fbf7371b04df7418fdece30b703fdcbf4b82bc474ebd4a44d7b0cc29529d746b7b6da58afd5bf2fd336627ab5a586a0278f8996d45bd24fb57ca1ceb9a47f7b759630d4fe90674bddfee6474656b7df19cd48a86eaebad18c92d4cd6773fb6fefd2adf9dbafcc06841b76288285926b89134860f1ecb25ed014093950fe0ee830ab22072fe020ddddcccdcceb62c540ad20e2c4122d6214394110314c6f6b31e0d06464440f458ab868a2429644142a9210516406126858f213daa0ee7b1c6b1e19c6318eb14f73cd6db1e6d9692a22abc74393a7d960fe556c51cb57826e1ee7799dd771dbb6b9bb4fe6cda793208313473b14b5348184431bbe4e23d34bee73415dac19ebc00721eeeeccce1b4f66f776253da91d014612417c3cd07dcf4ba57e64f0191181388c37784ec42b8a9408519188a00454f480c81731081a628914454a569820091a9e7c5794a0464407198d99d99d7dbabb7378c40a21b57caf1ee1dbf438cfebbc8edbb66d7edfdc260f68b948373898210521995008a1b933cfc9734e9f93594da8b8412d7fb21b75ed535a9951f76f7cd53a9aec6e1860e4d3344ecbe9d636418488466d76f473bd719a6daf69a71a8711ef594f4bc5a880f21351e0088d2882848e5e30272087a39a0d6666f73ccf73f6e9ee9b3405915aa650e09344b4248908a8d5b56c752d5b2dce61b444cc0aa35aa66642c4c2f6be267a33bdfbbbd605922ddc87de6cc9bd8f364d36c77936dd363b96f3fe9bee5f990e4258eb13ed0889d6b414122d6c9e328aa0f375b68679bb14de4057b98b421c3d7030b033fe2a4ae8f6b3396cf20183073eb035cba1c0564bcaf3a8bcd3dffa79bf4223639b9a7cc8f89e948c6779fe1e984f7d8f8c7f7916984f7d0ecc987a1e735ed8c5260533aef7b7fc0b035bd39e4baa49199514d1d5e46260c579a2a48d20d06d3261b174f7245c19274c4b5b33756bbde7b8aedbbca02ed70a3a4d2ccc1d1a45481a8dc6756291d3eccbc325a7f17cc543cb223cedd74bbdb494fa9c7bdd9d4be19e6b7323f3cfcec0bc1bb613ed0889464b21d174b6c6bddfddb1fb1c705c5fa57ce5cb5b4a1982da8ddce6b51729c272eef771db6cd431f55b3be543433ea55c4019b56c847bda743f25e81c3d1fb706749f3584ced5a4ab28a1deced20f1ffefe3b3c5b73aafe18d89aad66becbd4251c4d93d35b4d3f2a45a69629d269fee43852622d656a7f7f9abca8142e3745b9746846020000d3140000180c06058321915840281c2d7a7c14000d77984c724c98c9c37112e3484a29638821860000000000002a008188010df3fbc11416fc68e1939ba6d428385c039439c70c73e9987a22a438b8b27cbd3f0ec0bd2f1f22d8870c8ec036211322c125d046ffcdb2a9682ecfd7848c359941eb62eef31ea41a0e9b0f22ecc16e6be837be6b49fb4794791d7be959424e05a7ff7975cca5eda5be88d77d379a1029b74aa3b986c2e6ab6052ce5e6d389b260c3852d523674af5e37e735140f685ce331425c46ef50458db95b84efe0fd76345e183d6e036bb9725c2e9d85b0a114569fb336e135508a236340e3d4f9a84c68fbdd4043d39cba62339f6697f30b53d7440adc6af45b42a9ef1c7c420053c20e4b87035ae92f842a1980ca84a01b4146da423fa5704e52259f53c57affb6d941f5ca24f58482a051301dc8b753579501af73808271fa9c0c101de25bb1341a86e21ba0df56907a5f7602ee0ca6f61158c2e1171b889bbbf67d5decbf929e574fd363e6f34f6f033bbe9d0ce6ae59df543f4b5ad0f5cd38eacd8882e9400b8ca96134338d0060ed4598b28d11be4b90407759965c2031640a104adb086765c590900c830007eaff2eb4dc9aeebf581a4dffdbfdee4d2df10eac3c7f2dfd6ff44e8faa60729746b02a1964910da4b533abb72d90939ff7afb5f288cbc1bb2c8744846e69967ea7750d08d87093ea4586b234ac576fb484b9a2b67f5000dc66bcd8d11629fbb0868d2861964711ed1c7088495f57397de8d4a309629a1d60c37c9111f46aaa07df2f60a789a99d601eb52b2b87eaf928d464531cc7e616f056b63c4425681df837349e18ffe0665ae9fb253da2fd7f7ebedaa7053793adc4433f30d94d30558bcc756befd89445c16753f59b64fa10fed62d23c4cbcca728eb1fe505c37a1dc8238ce0b6547026e71310fe90e73d0c730e421eabe4ca58f290e85bfc7207c840ef50cf28491560f6d9d8b4b19799f6d4d1cbf5296b7227dec1b4af47bf42b52f3b52852821db91d977c65dbb011e82fbb65e80d1b6b753668d8185d961abc42463504faf64a8ccd40d40da7a643e0f953cf77738fae3b8450dc3f88744e504222eaa0c036d13661f55d427d44c2b46cbcc2279e9fe2d9e92494a6bbe651ccd1d5af1906c83f25a542fbbecf013867219334dfd11a35417be63b4c2bf85e68ec4c21eacfdefc7902456f12be23e185e177779c3f78d9a0803cfbd8dcace043d3c748d10c6cd1fd9f34b0f55003326deb343fe8dae37e632b0803713f5b0473dcfe09f2d421053924c2eb654f1ef0897079a1909bec637fee543fec201de7c8caa3ee2046af66d309015204a001c4ea0f83865337b95722c98f149e227d09154853184e3657e2f15662ebeb3477047081690e22c62b91c12207ffbd4acc6a948974686815598c5a787fb28dac43ee0d361bbb0d3e945ac7c283e46ad40634900dc1350ff6f9fac9c285b9595ad7f4c281831772578369256fdbae12c7c01f0a86554d72faead2dc2d584589a82fd6b3739ae140685a093398e39ab6da2d3aef3bdac73a219bce352b3ed8a71a64741e8d500f011dd18ea3b2a211737d0d6b9ceeec07f7f58ab2a8bfd17484cf7484d9f9edc399df64d1d8329e6ee816adedde4596aa0ed2ce129ee8ddda18fd757f64023078535d1c109bf4eace3aba2b11e824656104e1bbbc0ad1c7206f3255041dcac6317b8cb230a81ad5ad58c196afa96b160506dc04acec05d3350029c34483d9b8990c9d32aa309adc08f7b491adcf3e47a45ad764f6024d1ec84dc78dfcf17038cd800167c74bd155f586ee9a626db3ec90c8a89cc5695601388dc52059cdb936c6c1281642042e301eb352ddb5d6a7af88ca9bd6fe37567816108c9eab58f80525ddec093759e7e9ed9d5f14954a5b9ff4cb93ecea763485445cd3ea8ca0ae5853ba7ef3fef741627d1fde17d3ebdcbf5f48bf031a2b2f27bd360a4b536e4efe4ddae0ee84e3b314c11f98ae123c429195f4fdede52e9e6a7f7c0d867ae158eeda51c704bb242a5548f92743812c2124edcae58c59b47cb3a6eb33ae1aa8f309153c7bfdc3c07f5f9129f16ecca4e9616d0ae86298611aaf0939b63e3e16cc5811e971dd290654c5506ea473c3c5a1a21434f1e1dd52a42951a9923228637f30edce85fb6e42209967c60848484b5e8db2a00c53ab31863cece850edf8ca16109583c72b0dab416c0f9bda585e091ca3829c40c0b9432203828fc4b3c3db08ff4093bc61e8b02a22a75de6e7e51fe8a4ebc88b6217ca056c90ba61ac70fa9372bfad57a9b937d3aa3fd8088df73cc272f4bd6b96fc5aaa1dbe4a61720dc781225d90b673814afa0ce53140d51ef58cdf15e7533dac44f8d8388fa25a89b94f235a44935f5e75ab342908252fe5c5c0ce5700a8009cb8061de7a99d0d6a56df6dc767e725ef29978d1e62294b940a6fe088b1b9f29f3097127957996fdc654a89a6e4bfbcd04593f2ff565b9e794030d3d9d95ed54703e7a0e4762f4a05f10174f4889fe5126645622b1e311eeef268d62ed3555e1c38529359b29cde014619720c59c0d2e8f7a17493a140ac916335708ba24eaaddf103ad728f2e7a10b77b5c9a737b76026b17acfe11e6f6779852dbf661d56762db623cbf2b8050ffa974d53dad7aaf9566b919b7774c0efb0212a41dfa65e84c27bd2b5ca9ae31ad02a34c7a00db57b5b2d81f113350a10b5fbe747f8a51f74e02ab8b3a6238e47a8b877c9c63a66219e6b0e440e35ef9379467501042493ab185098993d052fb57bc502c01e9f2110c4618c72c4678947ffaf2022cc0d86cc64634f68bbefd1b0fe8a74ebadeb1013db6b19a730a24f0409a85c80e5759d0f8d225288e0430afb41c6f48c8d9bcc1d6acee1673388346128589a3eb01e6be135d13eb4c042ad5b1fee5fc7b8407ad6a8ac11778a785105e0da8ccf8b86155908791f15f11c93e4dfc2c9617e4a0b246a3cdaf7d7c8e31800d588090eaaabc54490705670e4f726ad1caacb41120cf8062cbf3e00433aa2855a9cd4c72aaaaf9d85c6f015d7001194346ce6a9411a74fc147068890c2715d6fcd06e5d36cdb157f6e206062edce0499fc2468dad53d73bce1016f06a5d534cb4b9b227ef86445db74de10271c7a88a014c344e6911240ee78b4c1621e1b1f7a87964034a6f5bb9a7b5e8d3a7931a1f53a748209e099b771964db42dfa653315290639f3503b06347def8d35d579c9e0a6fbb0978615f728d1eeae36e6da27f945cc05c8b29d2b0752b68c04880232cef1f964e66818f6d1a2a044030d23928af467772af42de8023e2f503a37bc2972e2ebbe8cbabb05f130a2f4db22b6f4452ba201ea3c16d9737a898de8413056b292d1e93e18b393624f1b6af4982db65dc955ed442d92fb2d4c44295480a50f4a6763c960f656ab6d82737c5b4764317905846e2967fd1fa144fe84e828ef904740b2d3509a15568fce0733469a0af51749040467c9d8abdf3ec86678da489a86c51ccdf20d9bba9c9a10a21493acefb89f4124a01ae9b5c36f2a21321b192b18c7910a134bbcb8a326d3a6ce1a65a4385aaf1b0b5c1a8130b925c92fdf42c98eacce4d8930dfd0459bac17a15c2275d1f0e214bb9edd2b674fb403bc41598286f41ec868fb086ebf84341617d1c820cd5301caa7eaa34906278e4d4f4011aff783948690480b6e30b670a58955b24a82f8e8a940a2cb59dd548066e16a04ba71cb36f78394bfcd1453f49415dc2855ecfdb4f42d28849ed44c9f51e23a53a25ee003a6d933cd0781ef91d7573db0f63ccf87cf0ca26be8495eb1e06d104eea5a87fab87cd974c1e626a745ef4685de7261b4496e5a49462d3b67568ca821d8f2f65c9577f6fd594ef10a414bcfaab2d96d556a4f8fffd00cf0b3fe662de8b9d178203e0c6d861d95f4c70e8913a75b38f60ab00e3abb0aaf7b5de3629a8d448297982a21e20a019fac1db5e10a33ba3c00cc301a40af6cc506deaccb982b981ffb64f97a769b91b4de11acc8ffd8694e505899da04900f87f65d37986004c1222a81ef06be634aef422a700bf783c7b3500b2398d3ebd6024a70cd67e3ed21f1c2c27668981d293d9b0ad3c9b59fa1a2b38b32c5e18ce0e5b9ea9491807c5caf9b7405f5e6a292ab53334be0f03f8d750f7f82848de80519b34628e62fbdd9585c772fc459802705df60fe0831dc399ac70db73c6a3e542e320c9fcbb80dc4a3e29a010ba6b5bf5f4cebccc06ac54f1c33407e39a45507e2cfb3bd5dc2e451e36d7e1b5950cc3640840e89bc45c0174c59fe31a22486cf2b62dec2fe1a9ac65ee04c82ae9c3946d6ddac1a8053ec01a19a5cfab8afca6f3e0a7f9bfdc03c568a5f12d166e503659993e619d3b145f11c7254b8497961848cc62c47714d43cf7f7b84e643dd1c2c363b73383393b10324bad722fc410b60c6d38b44f009ac425500636acf2d180b34acb4c72b3b0a31f2be2eada2c785284742f21b81938ca1a52f7e3dd27412434d316947f9c2457ba69b9c5ac75cbb81ced1dbd0fa701a19c76d3942fa5e381f2c0c4f274cec4458c4987c3d8903458e47314903657956fa29af88a22cc49cc08ac33a9678c5f318d4074689d12b56c90daa79cb65e40025946822e6103e18c8e9b8910e7a59fd788c31fc30d51a9734eab0f25e9090e6a26eecbe8da317d7d8a8b99a60e2dbf698ef4bb406d391976a69f7478027dd3d5db0ca93dfcddee1b8892a4ec8eb8ec66fcfee2593efa570ee13fa7d26b7d7b6145899bb7c0ee822460ab2aeda0b219cbe3d678f6e3de043e3d76d1fd1d233497dabeaf31cc47d48591677c3033e7b0fde8047fcfd1d12415b3dee8d6f06199ec55c04904dee665234f66f59ebf7ba840220fe5cbab3637f4ffa9e8a9c59da675b25001136619807f7a5acd1319ff2ce814bcbb01c3928bbc425c40caa166eafb78278b116879c98756018b8bc8a94922f4a8eeddcc4c5c9c3de741deb995dcd50da11232bf78e242916cba97ea1fcb7ef479d55209e6c48913ad83c8f110446f7a1166c44bf180f1bbaaa104158ebbad2ea2a4236f44e658f9821884ff8f90cfee783d4a161c9a0d12386d3e27fd98a27448748ec9612ba99a66258300558863ae49d1ee342b9be15b06d4a4de1246d8adebb57ab312861647ed5ba9034fd49c4971346cddb1b47501dca38fd799ac04c0cf62a765cf339815d62915197eba18fc5959dba18e731419e55eb081613cb29db10f4769ed8c796a4fa5a2f6df7708aa7ad6b831efadc91b9013ad3c4c976ac2c3894096a117aba338b63d1c61ab4e049606b054989c751eb89c4bbff00a5aff4339b6c29e421e330014253f3358bc34926ada2b54bd483e12a9800e9297e91bff59abe75cf90fa2c53779cb012fd061b25e5ce26370ea529c42a5ee0bb88961a61f4d3dfeefee4c03480c6870bcc18e42830b5bf7028eaca7d9f6c3d50378d47c0f7b354ddd9a260e9ea118e411b644082e9a92994e94e665bca2d0670db440672c70b0027a8ac42ac6437f400cd99dccb6c74089f382f314dc486a2f5e3af36915360642600f87070e3bba4c06c6840b2179d1a1003ee9842fecaabe753511835db8df110998418100b370cb6921e37949d9771b3111c2dc74a86fe276d68619a752f332201ecbf315138f377eef90c7761d78c6e268ac2b2b9a9299ca316065f6a1b75defab79ff4aaa3d1722a406b0fae6686b6ba8ec1a621f58644ac702182bf5d9b274b863c83c7c608df188a437eb6823b2049135837851a590c1c728e5b745788eb0f4b5808975b31701f4941b40ec6e8133b80ee02a69e14e7bfc1149fdfd10a62a70fed82cce1e707e32ac6a11f59ba9ded4e25796024b1c729b0e9eb1a9e5f28f7e29ea2a7f2c37474df742470a44ddf75e180414b8a10290cfde0d97237c67395e942d233be269c6410554e2dcc5ab313af66b608c2aeb861556b383ae4629ce308778595f389b974b24f5dfb9cb8ac9019650f80ed15f94c67dd048a858f6ebbfd8551b393bbfd275495eaf6c4229bdc9bc885bad1a13312a8c6e73cfb003b18d924c02af490f4e6c86d3b8cc83b8f493a2f89a22380a5d9dea0e47a68d950b697ea7bb8f1a69878b6d870f14b99c446cc67265c2c78152dd674e4ad9ecb4c76b62b77c0d0f58655eb1b1a0144150a7926bad4a49c39b6ef8af0218c05affa96b0e31ef895f9ea0a684c072c18cdb7c3a6d833230eb9a91712fee75167242de473aa6e5b35e6782982ab068a5984aa944c7e9c85bb1ed8cbb05e5d140e0736fe3b4b774fb72428d714a7dadcc9b5779e9b7ac2c22d0e35471af7d2a3ec91bc49b5b58ed3c85e415407df96cf8fb67634b8b35901f299d421f8d7da1041f09580b2babb23fb6e8c93b16c1ce6eae2c60720211843db7a5f857c7b1ac0a4a00515a138934bbf88774314ade4b2beb1ed3f14dbbe19792a026dadd2c3451881689e228cd57800950e378647520f372b9d0b866d953312e1b8e9248cc12951b4cfe3e32940a3911efe1773dc191f220a154fef99848803cb8784cb8d8c882c8bc7c15244251553d13167e196446b554053df7da43d06042e5ccb7a30b12a6e9eaa6a988c2f6748dadea5c6ee8313657fe6ec84d5567cce4b24ccd760fe080f4f465aad8147d8fe1a317803c6d2aaa841f9e8e85983d9dd9ef13ea14b104248559a6d4a790ec178f57fbbeb14601983daeaef49c06b9658d34599b596b548d7d161482392eec030769aea81d1efc9b2a77fe0ce54394775e9b9fd4aafbd44db4cc79e8787c862e9e8b3c794e9200521abc41c4952d3ce651949685340ffa65219bfdb6d9801c26f58c9eb53353e5d4d085720e2d445b17d90551572648b6941544171101571e88216b74b077384a465568801c5e6ff331bdb352f2e97a7539c446949560dd1516b092caa3603a946218a40b8d9fe36957196de6869564abf3f47aa19e833ef78e90ab46e1590a113ee67c415576651f3e91e76481e1919c35b12d2a56755b8e5a44127162cc2abddb09e09052529cc6cdeb3e58cc03cd0e14fab33d0afa33a93e170c6a0466fa51114a1f5f3768c1f23e4815cec692d8662a0bcf136a623067a60f1c9093d6ef8cec51d4b3f65217b9a9b6f8a5ae6598004943279803464c95fcad3a3a000fc1f9fbf002f3d26873b4406d6224c261085d7fc4c1ba9088031e5b0e5f1f483d7a1a4ea80da9083a8d0bd1e397ac591886f855b2de7c375f0703b5398e0b0a3440b19f3cbfae6e09f492f4328067205035224b6a4fbc44233d78f18cb43a13970949f5834c4f40b1495b8e548efa78bfaf29955bdf5746e76aca1ac9f4b15e4184ecff0a35fede4c1649c6993bb28136b88929f0dc17260419ded7ec09d8d1e19b4037e48f57d2de6ca730eef147de7ee11336b3594356a2ab2657f2b1e720e69663d3fe36882c5ce9daa39a988880b0d1ad0fb73f660e15f6ee0e6cabbf6624669964f2d1207bbca2bedc8997697472588cfb9bf14f993adec19cd118909c29f31416200e89b843e74d79d0df5c610ac3c5f6ee48889c7e1fb01490cb1a8256ff117f837ed28fc761e81049eea7c45f6d130b29c52a065645100c4b000bd424249490a1d37b6443dd88601288d22f39b894501d4c1c8b64495c2df3d39d7efb21540f646cd9be060fc7c389e023752955d64bf53cf5da8b7fdf5552194bde3925aec7d23d1b46b814bd0ab50e82da3e777a1d6b278b71ae994a0ab2e140e14b0659979a56c2d45149dc04231f9b1dfd1942fc36a95864d4fd4da580e187c3b36e400fa8a70f193fdf727ffad0d95248bc1e2ce03ec11d5d8d3e1e32f331c047cbcf737da00bc35460330e473ef7166fbfc75dbcf387be533b00e9615d86a430fba7663af55690463d2f715a8715333b1ef6aa2e0170c4d2e3e912df3221e861128efbc10eea160079bd593ec5bf4af22eb648c8b45bee1b2da6796d50512177a8bf1025f1a4da4a144a5d8341bc40d4d8837327f36fed53eb1eb9e690396539f699cf9ac483cecec692175d619371b974e3ae5de58e2dc082219fcf1f033664986f15bfdf4941b48686c549143012e42f6123f4d8be8c19c510f3b506a1b68c1eb5437dba20277c11d94bd94a671b2f0f071c5b43c3c1dc2005007022e4c1b5c45564005f5bbf67b8a09eb06ef25b36b38945730f1d903fa8445295c71b9a706212cd02cc04c9a7ba73025dfcab49faa1776fd64cc2419501071d20d37c7b001b5b0a4b21955be26d966eb4a731ad37a0b9c8cddc0cc27def34a06a00cc8edc2d982c4eed931cc894cbb3f4cff3933b7151b71dd7f132a195ab05f9e420f3f6ee5a6bd2a189231887b9bd3b46f3a7b8864df31537c50ca7500f13692fd427ddb1453dbf872dc35c36a7f88bb69668d8672b9b48c3bef9e99e9a5e1e4d2101ae32bd079e144039325070725f0609aef3c552d03e5fa17041e3825db33711064a59eb06c2f9b7e36834bc715a4dde151641832f11efee1219db1419c1000fd6f221b4ba9f31cbad579905c6ea7dbf142c9da8950065a69819f99dc4862146bd2ce2d48c2a83d5ce0b1ba64d7baac7fc7a87254fd1ee0f762e0aa608338393eec2424f6fc169a6a6969a6a682f07a08006a03c797c0ec2392813cf6e75fda81bf6c3c93e844b61747a1c5d72b3bcc9982ef8af861e5299fca2ad589ec227ca8b1030b63a8e83e4bfdecc7abcd1864be4b3b45267731dcbe4e8d9752413689f2e1c50f009f438fb9ceb0e39296c816c124d44caa09bd2161db0f8911a094c1b487471575d552dbe1272e996a0660d2daa49fb03dff9b1a1252ecaa70971217b4533a0e7e59d8f07871c69f8ffc3399cd04af434440a8c30ffade43a287eb3117bc9116626f4c1005652d28f5468ffbf4f4a3f17290e27b06c1a0f4a37760122d4c283be28dcb9febd4b063c1a5343f134177e152e99bfda3fa150d25c15b6a18c69b8a46c953026731f4721853fceac236415266e898b105db0a3121b4d682d90f44770eee7901096b539863155e3e28b8fe235e5d474b4b0529f0611f060379a3e4f37d8e0eb397c932a01ed678d9018242fe6eb4b5d36be482b957c0d11e7ac0cd842261417289430d12c5d081227d207facc291cf02d22a75087ec0d9cbce192600c32fc62c5ed331a8fc5e463dba0d4a0711a1545b4a50acb31a43f95991d9b746d162c099f7fd8f1221224a8c416fb84a589056def4002369655ae7708909b982b4e7487b6d6089d9d0a9f7bf113468af5364ff749831a6c407cf782f2f99927150d93ca91b8587ef3223f2693fcba2074ac93fcdcadb7e89ac564c4d16a83975526e813790aef8851ac4b6adff1e1c41f84ba7bdfafa026da3e291802810610d78c34329825710724dd9a5367515d5813ed59b5c8959392ed4c462537da136ee3695e2495a737e5ebb0508d569ef820045d693719f549346aa410975f73a1b2f062bdb21da90df7b420a55a0b5948b63133ca6bc586d5c4117d8f1877f3f13832b0ed040127ab0873a05d20fc06e857fd5d43400a8e6623e5253e0a8ef4d85087bde4fd177122448e6abcf6803832ea731aa1c95fdc574f60328eee7304754f625f27681a37264897c84df8cdcdff3eccaeb95051646187ef0b716d64b5043189928a3710ff9e2763b91841ce42b10489414382b319a65ceb0a66571213ca6d2f546df4fe7d8668188389035e5b435d5d719a83d8c44079dfabd196f61fc60c26fddf345c7214cb6cdc80050a7a1212e9cf3032be97285421af45af006c4a28bf674e487d13653d141eaec1b729fc085faf2d6c6f3dd8769078bbe3aa86fb8b4258f6a344a2cec17070dfc698413a5db57ab9ed6ae9850e6b8e460b6d6c08853d287950af928d296fe4bdc7ef793f811a326f74a23eee7166fdb83971e174f140f2d243c9f98a33cb8edbc18faf98fe2637196898be92a3c94aed3de3b5a30d233ff5b4a22600a5b91174c649e5e956cf66becf2f9b741132ee7f8391ca5ef43d3f45f96f64a00b50b9e087589e5c15c2f42d57e0a95e1ccfb449a032b6f75a196323eaddcc37f24cb75a1060dffbec358a00b0fcecea97d04d09a50a578f14c92c71ae60ea816c4b8daa0e43a61c9ecb19c2b557b2637445416e91b1ac0d7c6bedf4cf8298e1d57dbbeb25a25e3321c2af5803a4d1a14e3921e4dd681ce948d9ac27b82a43f6f23119b0de4ba31108cf02ec93c53038a7163f2539588f0262eaee92e66ed030cf299fa03dd74a3f0571c66b5e76f95ba204de9d3b955354f53bc7509dad7f8e7169de94c534c0cbc3bca43e853482439b71d5c67a01bd0d036d65d76e5fe1854d0e923985cc151abed7e4ae614cb4f20aa988924f0b0bd7ec597629640001583632bd388419db9e27947ba6e93427fc3082de2dd24a2546cf3b7024f9eed1b42df7a56b588c888e88816a1dcd43df2b0793856693bcf97abd227ed703653ece68694f8d2c07495ec490c722d02331b960d59c9c0fc7ae2b55c4da7ed976918924dbef442795c2cc922f031c995bdc79173a291c0e13663f964288efb98d839289a615f8ca700101a22c7ba52934bec4f54fac46b11f955469bc14bed644e40184d12a353945c624a4c04f2f8ec36ba18b1024f4b01698c1e5f644983216ce2e3af9a85e2206a5c683c013c1e9c06ef95eff5ae4180e3c9c923eaeb40e7b57180702cefb6d368de9b81f2f6cea761bc7a4f55fa60eacb19229e5026e68e0892a7dff4183db1ba46ce0bcc19e0fb994accd11e87915984c1a508e2c3487fcf240e721b0cf35b1439a51757118b1c1481c0014e1c9d78d7303623d9ee86291ff9589d9a82c7e2d28b14955a7e526b096a8b96df6bd71e7dfdbe6a0968bd9d283bc7b347c39854eff2d18407e77e6d4365230ae1fe5e3e35ed5f49ee1e0b8465004dc2d0a3a559615b511ecb0c0d88487908ad1b29f9969cbe2935093c030dfebaca2454ee37322224fde714d23e4262e08f87f0e063a0bc9fb98fc9e2aae10812e2202da38311851983a9d9c901408159b6ddd435b87e4d29698496557762d9e16bef2b71260ea3d507a08db74187c1e8270dcda32bd8a543a47b1705df594dc0d1cff4e0e3146a0fc3b0778d912e4c8e34b56ce63e880558e2fe6263a4f5a80e963e5b0ddabd64c5bb73a6cdfac1bc260447d243aeada03a64d8dd5f094a2636bf62421f8c25d20b4b26d913508c9591420143211f55929e5b539882047029dd47d7fecd7809fc434b1c3fb8a4a940e9115d2081023f38bc76faf1dcfa48b46b1e20e6915c18ebf4328ca5f54858fc2f4816df280569903e81e6f68db31796c12d24223487aa2a178297a14e4ea390fe483bb8660f24e962dcf05687402390042d30a2c35818fa9de9316b3c79627400688daccb7d23d96e6fff42d7f63dc560675861cc35f5260f6236c922dad351e69ccc0756f8ad67b6e323a13faed0408200a0fbc0dc3780b22e7677cd3b232237aac64f862f33f64084ae221348546aa298c789a0d17c1caf968d3a262a08ee9bdd7653747dcdedddc3dfbd79be29c625f919f786811dd8ea280a060b0631b69941e50b0085f1dedd87ba954b097f5cf7304b2cf63160351c78fa4ad3396b4e58de021136df7c042e51440bf92556677cd630be115e14070e272fa0ae6a6f9fec24bca37cda4a40ddc37c9c204abfa6af193787b6718eaff64c0ebe362171dceaaddc6cdb3b88d92613cecb7c832ab7ce503aee2a0a64ded8c213b91ef35556b84f542792f9b5321002197392f180ee9a78cd21cd98333c305eb1f53372745721af6deb742a1aa622e7dc1964e377dbb9d319ce33e94d11a5d211964bfccaa969003e1d533537ea2e52d46f98bf8917f57247a4fea49c91f6711361f04bcbb320dcc97ccc81c84eccf956111de122cf40fed5e855841a1b4546af842fcf0a4d1645c3fb9505e887ccaa61389e4825dbb23ba77335633c895b41279e46279e02b18e96f566753825a7fe98c2c62fb9527edde51e43ae85263a1ba44096ee8135f2030c7efaf90720e8a7cf44a58953d3d6903d7390dd5dabd20ee242877b680de21fb206a75bf68834759d4a87d31dc2719b513ef806da4519b6271411bfe9afc5b8f7b1b6a0cb0ff02895ef908066ef2609a9730be939a87ce91cbb273e7c442f724593e24b06a9ed26d1ab4a009e67cb218ca5798f35c26ca5b71a2b065c7f52070d5f5f6ce7389f3654b49e888bc081df70334bf20e2c7b2276c07dfc2dd78e0fe6f260f2205c60f882f14180ff33400f5b9badac39d18f13ae12cb7654028ba0d88209d560d02dece6ca161f31249b7282b149b84d973de88b45044d236c23861fd6bc3884989f8a667dd7226a65b7419a1be57b9f50d40c73006ea32745945deaa4a7cd2cbbcddfe101ca46c72129a295db4c91f21895fd63dd4bb9cdaabecd500475633b252144fc64b93ccd6815bbfa47e73833511ac69fd38559a9a08fe6bf67e6197f2fffe35d51b0dca43059e8e54d206b7ff5fed050e71462428f233764f9cd883067ff7477984140b0acdcabc8ed97b6c001a6bb506571f5989e750145cd7182f8217b1e786469987e171b8dfb6007d39873a0c2088b278a1957d20d06073cf723b09c01008c52f118d5886749f0625dd612e5681f8ffe209a0124e6d0df07a9aca39029feb3de5840e3e61754e319a7628628ecc7312b19ab64cba12aa665c25348be574315432a751907eee057ccb1655b97718795713db1a76ae05bbeccb77395c9318e8fb539bc6790098a7190775ebfeec78ad4fca06954db4a18275fda419023f85bfd0a2fa705d335333ade35d661170fc0898a8f0759d3b2bd097a2080119db3d99493a626e3d0c5e23e797225824442b5935c28e2223020639764006eef088eb8f57a9d129859025f2e1d5cf16ecb8a6bc6141866192d813765ec3c215f2cad6c0de3cf190d94da301551af1847db2e0796aad32049ac830c25a10fe15fabe4fc74af39f9285fc19d6be8a76e55b190609581c8d070d0f708b8230bd269ea6c1f2d7e9681009511c0c9d4394a90b6548963b35399633bbb9b054a6b36c44f28cbb1860b1fcf65998b991c981b528b27147e7000001ef66a784360cf301a1bea9274bd7ca70baa9ecd245bf84fc825de6e579c629aee8f7ec0e867b30f742cdc2e45a9edc8c890b5075a8bb4e7c2df6c7e4a4159c989d941d59451bb67695a00dc60474134656936efaeaff697739af4a57f9272a9a6aeb2507b4b536be4643ec2189681bde910fe99dca4479c6681937a77e4f1432261a064efdde8819d950b5b00a4bcb1a4eb5b1a696ea43aca006ae917eb0ff5922430eb1f772f6b97ca30a578b4ec97585ba188025fa12ada180e563e3387a2e2d875e6db425555f31b79dccb1475a24fed96d52316aaf6a5cd932f85cb107c7f9824835859b396ce9302b104951c9c736befb0704ce8f5947894f4bd204065d4faa47f212dedf21149a602d261954d7cc52a896e81d7b578b381f9c0bd7694ccc7bbe1806537cd04354c002680976308d4f637af2d4d988beee962611a905c8319e3d360445f3741e7977eda96c0678465a7e08f367be9924f75bfbdafcf078b89221532a0bf145e6b5b9454498d4b35c2f993a346b39bfe603c57969a17083a49d724b09b6e2b92adf050c6e40aa8f90d38b5bfe7a3385764c05096cec3adb24e6e29fed5199b2966911bb70dfd6801ba3288ad9728f00ebc8aa431ee24fcde9f0457780e88a57a7872734befd65c282c5385c13cf1b7e20dea9f92ea843e015f2553c27a8697e0de286f011d7de2e5f185bba452553ee65bf820a04309bd75dcfd79eae5366060f09a9836a0723c4e9beaafd9f59ef4c2adcafaf8d0446cb7f84699a7efff1d32411d807e771e2e36b7156613f36246b20cc94ab67013d613b6b38cc7799445b435f1f8c0ad3e8d2a051575de7f44e10f00f320e664aeff8d9fc5f4ef2eac3b64a6db536b18d5e135d0dcd30746fd57fbbfd3c5c26fd1062e76e9594cb2dcc424cc8145025b79eff727ddfc86b24e3bce3161eb5a3471a1a60a12333c7c49496ea072ebee09659f78f7ad3dcc29bb7dabd0b1a4233f623519f32b6604c9feb7391181b2f6d304024dab983b833dccafd795e83f1817a87d00618d12008456ff77f440214ba82fc88143e5ff403c07a82a3fb39ddc2544d7d45ed2ffc267034b3a60291be53480a266152a8a30c16789d4f90217a19f7e5b4fcbbed992bc6b064bf494888e01b2b91d04d362478b4f6a653f4b7bba916655cbbebc8bac47745b9a16e34e4c709b8b05ecc39eadb585c847a02bcc593d964e63c423e414ead3113cada04b6fd6db9fec86f951f3a7b51193ffd63c8f81dc27106535c05c26c0efa52303114f43ac43a16f757a8a9e67f4d85f1f5cddbc190dc4f58f6f6333e0fc847ae275addf512e75c638f9bbef4461a0be4eece9566d315e9aca6265c8ae288fdfcf474c3e4fe3023cf1d4fb04e1222aabb1920cc118b7f489da1a1994649d644482b00764d04064ba7d23f054957b666d8012eae22e413485bc2a8d3ee75fe0a8bb80582f50ddd3f5d6751d44c63f76344f6d220aa133df4de18d86b5df2fe00c6a2fdd0233b1849b5a644dd3fd3b3d137f1a4597402c6faa9fac072a9f9c55106cfa8f4a36d3be334585445be77da1e8c072b20d8da1d464ec5053cd0b562f183cc8d8f1a1363aac40acef5dac70985f2842507870bf9dda91dced1a5c2c9a74eb0be67ae46e4ef49945208933da97b2a177ba5525859524f4c394295cf25fde937e1b4c02441d128a4345dfc4eab765004af4a97e88343dfd3e93521815972bacb25ca5b7226feaa98986cb2a1d4fa6634f04232adfc92cebb9f66b7bd5373c4a37c323f3301fd88708c52a01201ea30e6194ff2109128b318dae1af48bf420f4afa8e5b18236acd8717fbb265a7a72bfe1c631906c4b794e707b5ba2f94d243709a599da6e28724bf5a080b193315f9bb3f268f3f0a2980aadeceb715d091d83b5f048556b8c65adebea09ce19c6a526c00f44f18cd92a7ddb288bde3c681a513c099031b9af5da8f94388e36d31097f1dd3c128b3a88d941841c5afeb9cd295752766d3e85353ca02e3fada6f6c785d63f715bbc51cf480b4861a3feb90280b8b1276aab953290e8c30654f6fd3d11a6dcd1891dc51e6a51e5859a49c7e1fff56aa0fe80d9eb4d97cfca6490ba583f388702a3a41b23787d1b4d6aa81546e6eff9cb013a3fcbcb115e3fe7e068d8b21c420dd364283e4f1a36bc68e9139476546dfda5982cf3aa138181aaeb04f7d2b2c49276e90ed4d438214bd8d471df05f61c706ceae72ce548023919a98f7fa3169d0d467f982154a0f714c1f7eb547a58e98b57f43c4c5f33f6ca381024499232988d1b58b565ab2435e6cb9c35f25b1209669968f2f587d4a73aad5eb9144dcce87c224f6e78cc906b9525cb50775048c6a36263452d3af46260b8f72a15036431a1e6830fec2f8393935609f21f119798baa429fc4a201fd789dbc597fdc1691e8114849d2451cf3772b9178fe2712b59c9745ea4b8cb103924d3b7db400e3d9ca1bfb912daa478008c21c2345ef9aface9f66000388bfadf97ac2bdd523d7da6f48f88e828bd30a5bf9e5751a096e5de821a194d30a48c430c34da648eaa5bcd34ea197d40854be0e927de87d5e3a93a8519f5b56e7a978271d207eca08f90e0197bff84adb1db5c48c1c899540f2066795462ab2d86a01870eebea1f67cf280b03ba5ba14b0ef7cc42ee7083aa1f373386abc9160c50262d10f93556daca5a180bd1213564d6bac95973d0c9519c0d8559b38a2b334645a554f2d563069e7b72e3d3bb89a89ddda15b31c30c07c373e39b5e674863b72ee4e1fdb42265febc6c16a9df5f2f8819fbf7666f967007effc78408f46fe50f619187349984d3f59c7ed3b46f27bce1d9711ce83e43a6f657d05a47f3f5f4ca39da20a6a947ebeae524270ef6fa44fa952dce8f527eb6bc8818319e73b05a063723d673108e2820b30285d8f83eb689e2eccf6ebcf2ddd63e313bcfb3e9497f814993a251420e79efef3004cd65f44cd16ee16c4777dd44abd9eb8301342b3d53ff223973471d2ec5d2720810eb5e1da51ff9d583e1deac8d97aa84da47e315f31942480d10b8e3f7aecbd0b160a11084df7b12259a0d99ddb6e606f1439551202b33c5bec2016815ed70de6b3009901855c7a3e90c515a8662bdf4827a25ee665ca178c151ab0ef90f464e42b9cfc00fe404e2629087cdb0b57210d77238fbba36f7d53bf5d302580c34805f77a32ffaaa06df248fcd36edf08567e5c99286b0ada10138bb8db9ec1e7917e5303eac1d3b7165aac99d97095718f43d1e1e21f961c8604360639657878cb8d2a988cc6fe14b4838471e93d9b5f4000d6bd16096b047656f0a3c4a6fca24b4ecfc37a1e300fa3b13b437eb3db4112ae99913a82eb91ef2dcebc719aaefc0e0301a1d18c76b31ac25d20d8837c504df01834f6a7a03d248e4bef6deb4e409a82fdbf2cc780073f4c748503d9a881cec4005eac9951d46c53b792d3c6c732168496899510370765aaa9ab611210a35bd7bfe0938d4a685ed62e1ac8a09346973a91a5a8c939eb7243388edb0db00673a351b1733beaef82776b4b567812666fe0114ee0617e2340b0d4f0d4358080c7a09a961f25cfdcc539ef61621ad90036cc9e15c009aeb6b252452f3e5d3f5d3094ef5c23e793a22fe130e3898f7632c0278ad9cf005ac99510a95d48a03afdc33be9ff03cc32c40af01b2832ea1f51d181c14b186f5d155afefd093e31f94be008be92bc005260751aa4d83009b20357749053ec65d5aaf98f595c1f185807ea0ef0ae10572e683284cf7f4dbd88b4def0249eb4f946a578153bd4866ff0ffd085d7d6768c37c65732b9355e67356034f17a9079541f243c462b031951cae285944be967cb6148781a62b03fa70a7d4dbc7a1a755c2b4d1e90b0d47d97c14c3272af5edfa3a6fa87f3c0a80879883c2a4cdc96c406b3e461c7281a6c26a1ea040428e70d2e74caa11eebbac4639f8decc0edcb82ce7b68c590a9b4dda57744bde3d7cdc9e3c274ec5b73db9642d10e3f47cccb4614017154daaa39991191e2f27352180cb10faf3cceab4dcfb10e5234038d9f61c4e06106ec690af1cec6921170f0c623e4e04337a7be0ef7a0ee1230da2734211c5da6a171237cee84686c0ce14fc6553531cacd0e40bf13ab6a12841f6833d39481fde961702522b84588f44be52b874fe68e5fbb461d716d7a9b49a923f02208ab0f55920afcede5c53f3ee39e083c9e01b997d9a6e0f04021e8678dc8f6a3648f8783e7734c6cdc9b8d7dd10a4e79435b3fe402733bdbd137db27667a7266538da8747be52d6c9909721f9b9a13bbeae1c7b014423a14f18913403b0b1bbd2ec02f1485641a9286ebafd37018471432318e0871b29d78683042d07c6bac0a083c06014dee087606b92c2e9219759d7bf85673c7a398e72682dcf681f29cb410db429081be670285e91eb4ac28c70d5a9504eb9a502f6382b9f6f77c250808b97d1f5071b1015d55002acf2f9f3a09681050c58f9b70deefb7addfc0d6b02057f60426e21f6236cedf3d9f54eba4a743f5e29d1139ea8ab6681c02bc728848c03e290bbca49dd959b82ad7defebb7c9d789696dc215215cd533684f108c8d24b2016cf0f39e0636645258475393cad28f784371b83c5c3f7772a4a9f7dab0d241694cafde3a086230b668b27391220a6af86bd93c4264d9a1b676c320626ab14187f51caf9a374a9961ad5064400834365d01f6f43405128d12353200506d6d3c3bcbc184c7defe8346c67f45a8fb83b1b679cec59a613f762c992583b577be8082fca01b3b230b4546301e02e3b389326ca9eac9abcec968451540b68171a2ec2b6751f8218e2063df99b88ef45c76447dc1c6a6bdc886ee0fb7322578d10acb78ed89f2a9a06890b80ad54e0b48a6dc8ac41c04c35019f06a57bda24a7866e461ccf5055832ca556880bd87a418065eabbd111f1f6006dce20aa55efcda4387ba4ab633d31679eed833ea693f82c6c6fb7a9b826fea1b3d3d1655b442cc4fc08eed39567688d5a10178a766d115b21a1f2e59dd32e0f7c6a0d7c4f06de1659aa683b09962e31a863dc582b2946a1de2fbf5c5aa4d01df379e4a7b78ada380cdd52ada2fd737e043546c941d1407e156953a217cf7f7711aee380bf4a8c6bdd176efec9010a614074131beec2b52718696f623212a6c01d18ed0e23c6b8bbd3fd63e531999baf804e971e1de1e4dbb439446c59f9da1b097a2a95ee30bd0b2c7bde9a7d03c46d8e6ab01d7a59e5267704fe8d4668b5c82fac7192d91a60d547b123dc8a8255ab43672412f15b0f1db95f0479fc2297017f4336fcbd9faf54bc5495a22de5b0d359c6c33e872ce0fac226d0a1fd042d0c6448282955b92cbefe9844d7cca12b4a19167461109b20552387d25d3a7ade56ebb79441d50497b923651be1551c5eac20100e88492a54bbcf211861b0e41204c5336671b5123d2317e45489b45abc79fe55e0b5b9972d21f0199994d979e8111caf33ae07d13a15853f88aee26a0460522a3f9615954e35aa7a3d9fd064951b960a9a15914e2965286d56639043f5d4dd29a364dd73414d0643a9550dbdc940e351ede8c5bb304db3158c28dec2445cb016896227985d7588fb225afad8707771f0d78967d2eb09f6efe21c8c648cc8867914ce68e5590d396c74bfdfd20fe8aa53cffa8b14b3091d466139a97c82b6caefc0ff078818a18f8ec208c20c84fb214b2fae3a80d2ca189b181d6f2a9850b5d79920069086930a0298df90a71ab052d52fb2507164f3c1b7518c71fbaf4ed5e12d4dfdbee5b0199f4b7d0cf1e237352891439c9aab3852ddd268f52ce00464f03b6443aa40e4fef5838f2f5e90f3dc05d52d8a9e2d7d279615e5eb321260fd19105023763005ca06728d2bbb5620abdd05a5cf0cc8c0c24d8ff86d5ad0223b3b243bb50ca3fab25bcdc39760c04d8f2a0bf814f19c68119b5c8f0fdaa0bf7aa75274cfc30b1672c1810a7c42a0f9ecb7c8db58786ee6a34b91f792ae2f7a19085890aa653f182c844819aeb445b387341bfeb21129731307fafed2e5177b3664611e9b805e242207a2090498f8d3b41f7b4f03083e21dcb35713fc0759fa9ff6f67319c2f6377a7a95895e2fa699737cc390bf02d15be435e2ef4eb4678aec76e69264b880090eb241c04488c94ee9beaaa43e3f6c40d6a8d637a048477170818d598394c88540d07c155a3b1baed30fd878edf0828862e5dfc9147aebb178beaedc8e3919e2b33e5493cfb79f8c39267c8a8ed757a6bbdf93bd0d2ba6e643d076fe1fbb5746bdbe755cb2a665cdfad65fc150f7058cb38e7190095fa859f9d51abddc858cd2c86330b585d53f80d717af937454e00f735ecadfc0ceff89c2a0d9e196ce794d25e8d72a55f18bdde7fc8ce1943b08721bf63789929805d48a3ab52276c320006f6ac1e0db86b8703ba61986ee8950a2ed1be38a44defb2db55393de7aa1486a1e02cfacec5d1465a63007fffc92c5af01fab2c09451eee72fdea689806842475a2a518a982df3aa8fe691cab893add33330d7e8a17e1f3223a4b0c414303b7415d90fcad51693565eb4b3fe26812a25befeef5f5b73fbdfe182f12bd73c77061949c82517d30cc1e22a49dc6198c4211af38838ea5b622e5515568b297eb1eeb2639e09582b1a3704b2d636335d85e916e08a53b1f8f175155a098cc6330e6307c7466f8e790833be31ad2bda23d935b7fa5f68d74062a8f1a18c9fb7b0f80392ad1f78cb5b40f61a7220fb79caf4b80eee42b0f6c17df375e0b4fc9a7100af0c9c976cd33a37189c9ae39d853e4e9603537be8c69a6eeff1522f4238208c0beb20ff3d474a47196b0433ff0bbe60ec47e1157bda2b753de0dd8e0784761f146a80949dd0bbf526b87879ad9320635b3bd9df280018b3dc1c65f7c848dde78b2eb20e7981a9ae7ff7a91f2a8f5ba347680408afa6b659749bb26c9c48f72665d097445d2e7e4dff201c0ff010f48fdc207837713eea25af7f158f73eb610e53462024c2c9fbe75ccea90d13bc7f1e448c4b36cfdea86c11641f7e1922d00f2f9e1d4864c91d40607c97ac8e2d8031ebc7f5ba288f5e1337dfcfeaf0d3d5a12019c0fe4c76371811ca54069801aba595d32d7538ae73707f3612655ff26c88d4cb4d8831d5afc0833563d4ac2039d2ea97363434801717a8769013c11ac0ed6ae54b034d4eb059503c60106a7e21fc60c36b599fc90ef24603d84dafd0c4176e38c3ce392f7fc0060960cda47953c2990e57ae671ee8378368294ee13b3d59439897668c106385d9decae81d59278fad64d85ca62f08228ceecdb38bdb70f3f1197f809e9a9a30a6234447e6526f07bb7d535471cc348e7012623b308df1ac4816a19b529cc81bbba2c438872b0da2afca4d3496a304c56ad403a6070ffbad1d02dca4c3b05d90f54f1d82f20c09a1e05c7bb094bbe282e06ff6fb91bd5801229eaba3c1d71676d787b026f485785e9c17993d7ee3a27d53736e86c814a754cd144ffe2283c1359bdbb09422b45c6d29f592cb46da5285927d6546b4554da13cc65ee1fa22740161bdfedea00c7466b2643f0c3005eaa968856b49ad193fdd639bfa7d1e3674c91c92b9aac2bb01bb26b0a6225e6da9ee4f4ba6dd4d75d593ce761abf0998d6bd6cd2d9d54eaced69b7423d572862cb204970875912faaf2350f53e0a0d36e159c77a6f91603a4902b2519dd0d93b259571604dbf3d55765335dc752fc94b23599b08b050c2ae0b5448f0f97dba61425e89fe7d4f692c3e57d0838fca8777e9b514e9af1ea5b4e804608a616539000d469c7d6b33ae34f0f729c360f204f912944639c82ad312c4f31eedbfee0e00b7834297eed56e23388fe37b328d8ae927b4a8efe216611d8d97d8b835619f899c9581c2e427ec6915b2d2a4ebcfeeb0bfe9f9801f4c0275d10144548d8bd19c13e712d93349e3141c881a715d76259441ce4a2195ef70f99707f86991fe35b0a36561d9b11ca3fc497f68c613581ac83b8cdcc3dd9d1bc6eeffe13dddf0f6b95c135c8fbae10b46ba6fd8b91b427cbf79dce92b382b5142dc4469d28a65ec97e83913bea81ead74abf065ad254eb6d3bebeef60a89f5a25cf5d00bbd09ad583fdd17e7a6f186f8179df9d91919469b5c82e9045c8dcc59aca244253ace5f99963aa04ec1b91588041aa28566a89a220a3da54eeaeb5f889d87c1d75796bd00f5437b901c80da7cceefa3254802e8e2d62050984cf78fad896c8178049d84be4b4e1d6a1960795bec6df3e9be32b2c7ae6d80041627b398600d9eb6b7d3a25f718d40b5f18f5384d27782b4de90bc418b5c232b1614e4520b9fa6b47f5700806530b95a170921e1a53c4fdcba30f9aad59ac8b28710208d5860a529e1a1ae1208dac52ccc0dacdba22230d71b63b32c857fb1ebfe939d5fad572ae0828836a8d9fbfee7b880cd3a4278e5a3463b394849e731c6ddc8387ab43e443f6beac6971730dc49f700632d089cdc7e00deb0593caf0ec76d05df7b192f73f23184d42b4f7ecff851adc419d75c077eeac863014de8a58f9fb325ca546421fbad6c62aeebb8959f51e7a1ac9d29b9910ada085c85d5f1837e1b57ae24b4b93f5df9997cc73e04aa36d6a07e0d550ae270cdb3be37da01e4af63c3931343b912cb7debbabadb7b522d5541c335ef0a5d0c712d1e5d38de53f40df5585322467df7845f810c68c76dda9da9fb5a6b7d92fd5b00e9019dd88f5579c552ff95aa491df6dd2fc09cad9ee085f17ec652a4a385ee91732c0557620bde0b8b1a71d7e6103a8e90330d7f82aa9872d9202a630e76ae9c1737d1b658b124a124bb19e866afe9ccc25ee99b0e00a8214fc37ddd4600b27354bb58b332c9d6d78e690052955f4abb8cd7492714c59e1f1a848db44fc601834a812196c18189c36c1fa60299c464e1b246264be61436c4dbe08be8f782c1d1383b32b84e6fdcb922d348c2f6273c4c8ee0524989d1646388fabdbdc56fda910ed365a5cc8694d6bb314956ae4b39dbf999381cd7623de15b9becbede367f99c1db1d373769d30db38909b162e4339603e18e8fbb08d1b416463454ba419d9045a350749ce859abc5cfa94364b688a6939f88b325fb96301319cdaf405354b9cd62f8dd2c2fb9b90189a60bf9ab11f3108cdb9bf0fb5d7537f67aeead166028662ca07aac57fb86704e0d83217d64bd7fad12acf24dfecd197e25ac6bf9cbf0cc65c9bf148ec0cf7fbfd71dd6ab89358a7510a4f014879568fbc7aa6c6391c19f026879578348a59c7d35786fccac19f0bcc5a7d790080459e0b63d50606a551bce726cbcae2c01949aa3d35d47a5aa461d5301fd0b5069c03439c8304e5e76258283ffaa5b01f32505105f740db868ff561dbb0a45b2e2b72362250481d05083b10862da728fb926bb0b8ddc35de8cf87d4169562febd1a524e9b533ad75b41e59e1c8a7c1cba1c9fdcd16eada51cd5d86769b9073a9c442e7acff625a7dd10ade5c05fd17f1079c86334459d97ef4ba29007a5b4a64d804ce84b38bdcd8e02c61d1f793d50c60c7578cdcef582ae4dcca74625ab6eba6a2828937c2f9e6ea9a56ada503ae4d900b0d4ff84f7b01c51972891285f8676c659227ae3861b0efac63ca07668524c3264ca2d3ae502a0a342f60856bdc840d4a0d81f4018378b36c0cf7805f6b52405f25a29b75417622f25eff1ea2337f286acbae0bfe368d0c4580b941eb97328ad4623d743de60613f6cc48f248905479e7467abc63b1d0d232c3cbdd3cc0b05a7d93b49cb47e1bc20b05e4be2c807df9dcc11ce60e8f85c6d8b2cad1e9fd424b5e65078c778c41de4972bb1136dd8acd57fcc7f5f62d57585eb39d43d807705ddf6e0b25bbbf0ad35e417c713aaa90d1e75a3e9e411b583137f019b9453bf00d948ec7e9b88bf6e193e0dd3f8286bec8f32ef8ac2c8588858bab1c984295b7a44a0f43be0a3bdd44cca25f2c6e344304047e94ca94bef7d4567f71e1cf7660284223276374c01ac173b543217e095f77ed05cacff9b88752b540c300f6ab1612f8c8009e470f8e2b7f3853244fe9342640b5705949f58279d57d5a41e2b73ed2942981cbd13e6aaedcb0cb56dfdac427700bfabe36698f757af72b8ad9ba5a25eed143c1bfddaf0f0a1ad613d3857212e6a152a0bd13934a7f9fd567a5230516468f2deb78fac82a90909ce81073f2ec935291c3f09b3279609283f490fc758819e09d92de5f3ebce545a35d564e576f67f45158da1d4646f8701bca2063db25cb02a087817da464a2a48102e3877e02aeea535fd4da1651a1dd565618dbda9c8de4c4fd77c41818a9050688b2578ccfd1b4e23c17de61ac40ed2d61167afcbae548a807fb67c010eb26b9fbb4ce3791f228e70e06b48e8bb44086dc5f4072352bdcd1456582f206bb3c078d09787ab047ef547eb688f07e30c7dc30870156bbe07a2cb6f716758274632606f0089d4ecf3d73658cb392a0528a80f19779e538d600323ecab97a0e33e9a25d1817902306e131759c315b6857453287c0c46930b4e80f0a7fe4ee082359b21a222a1bc1d4566762e9dec8f5dae24eec8ce4ae9b51d492a20e4f923aeec1b74660d4b5afa370eeadd288e63943c6c25806f60a77137546ffba9091b0ccaf2093b6ea353abbb5cd03c56ca06b08a4743232cebe01817d8f75aba377972b0c7386814b35c940b5b1243e75dbe67d8f434ec204223092f4630adc1ebce4f276c2604c0b9785106138d63df3fe69681d790d1a054d1c18d4ecc2583cf7fcb087d5311404e5a8a51613f932174ff93e1bce5580b80abc9a025281091d46340a90c63a0ae926344f531487a6dcd78a6df4dd7fb8b419a6c9d2ca3031aa8c28b5d30ccba9a0ec5922617dcc1ebb501475a21a6bc70491b98de1303dedf668a235f05dc25451a34654439ef38be72d3c3d1e6cea9a2f3a7aa3040a48afb45665745254483e64dd3bf0f21a021ef8af767beef6fd2dade7b6fb9b794324919670867084508159ce241391f8a15cce82573d99e4721215b0e69f32116492f872d7b82b1e7130e23ec1d8a6c5761cfa71cc4d0c721fdf555917376396f38e47c05a91b08220404bba6b0ebcd3b8edbf88d0d39a72b67a753994e4d3913131393eaad16325f2b538dd5e9baf47495313131c9549ccd76739bdf820809827f88030df156557df97d9021df97f3f6e921425c3349fa97ced70f5299aa4e22e71f80ccd720383f3ecda87134b665dbbfdb6c1accd7f82017e69036af43dedc5811799099f7b75a8c968b5aa53a3950c5189732ba36d197ffcca9413a362f833ada88be74727490ea2269527551261a5d8e32bac4b54a91677454ce5fd6589783973dc3ecf944a508ce133991d3245a8489ced3292f60a638207b782bcc2115e044b3ba847e7e5aadff71ccb97e2ee70d290781726f8abb6a1512fa11125acaf4b9a319cf01e3e75a2232d7653ae21163cc43a61b7f0d32027feaefbcc7737e9f66e1b0ff8d7bead2e777d8b1483211bfc683c4af9f45b2c75d78f44e63f14642f0e78526267470ba56561a0511b267ed7ee62df339dd6920d3b6e72cf2965bcd04ffc45fe441d8b1952157ad80f1ea3ce6398e35dc98c70935720005f53eb814972e5251f2a42f4f33c98125792b47d3189d51fd64cf9f547bcbee40d2d122cd9921cb38214f5697e9aa22fe39ef794e0aeef0f01dcd2ae2f99de7016f686e6e6e685c2c0ae1b5f3532ce2f91d6dc4d22c9adf791e2328453ccf7a239da74639cfa3270d196408c9d344a6bb88e76368232834cf13e38f5e34ba88f53bbfa3ffe8455af03b3f027ded3c110bf45797b3a6ce4011eb6368a30de60b5797ebdc6258f05acfea22f5cfbfe6d14444a014fdf3b09ec86b9e8fa17d54d78e26b2f33c12d8d19345b2a69e3734f3e666ded0cc247b3e157948223188fc5bc062c578124a510ccda32d2089b0b4479f8764d17c8c9faf1d9245a3591aec3ceb79f07c0ccdd2af2745f67cfa379466521a1a4a3b3a6f665ee88d66d2190d79c81d92488c67e9fcce4b20e7799ec863901a5717cdb348223abfa369e88d2e799e88cec77831e7795e7422a6b5e753932e36bdd5a0185fb67462b030d68941d2a51f37dd79fa19c88393a44b3436714c2239cffa1d92f544747ee7a994148ed40d95da8941ea3c0f99f3536469a0f33bcf43e777348f9c67fda4d134a665c89c8369ce5325a7b11c927271973f5905cce2d0db796fb5bc2585e3230da494c65458d08fd14af3294c1b3594066b01be699ea563ee3987687e262fd190b5c8f4a15f436dd414260dfa744697350434d3a7359cc77ffab331975c75d618095c69a8a555afb5d65a6bad4cc250e7ea129fc5037cf1411edf8f19c4efb94ed0ddedc67a02196f501a9235dc55b1cba02293d60aa91a214170e74136220265e6dd841944542f3083beba6acab905de4e65a02eac204e2f5e359ab56386e6c397919973cefcf44b9a65e67c99cf794e994c43ffc7ff0f22c4c8c78cd498a5b254760e09aa770c31761e78cf3d11d5ab5eb45b728e4934876ab63f03e690b7c73999f0a5cd73439e7bbe549ab5237c9f2a529470c4fc11e44021777b4fc1a713739cc8fd383a753a6e6f15f520f248407ec99c95166a6cde62e17f244b0310d4b3ba42fdbdf72c1edf7bffe9210ca82ec7f97ecfe25ef5472f4fb32e0f56f7e37bd5177dafc29d2e12fffb4f1b893a08a82f77b923a77dc31d44e5ac95dcd37d1fecc8f287d57d24abd33c42cdc21f3eab7bf18f5e1e8b8766618d9fd5e9a2f0456d04a57ba30ed447af8f7ef0b3af2e77f4e81de09da20ed6dcf66a16fd72d6e8d482fecfa169abd17e708cb9734f054a5d8f53e720f27c23a55b7da54c1f865cce5a8f1e07b0f9b2c70e4bb78dced555675ceefb9cc39f9a6c2197bf417f7fa10c727e19199907694d4d4d4dcd1fed1a7286863cdaf4ee194d004a23d778aba7ccf8f14f31973d1867f0bd05eaef896a06d2336d8fbbfcfd26230579bed338e7654ed81caa2d6ff5a81c9e400298600c24597cd5ff84b5a6227790020075eba706f9f8eabcfdfcfcd4e3fc580522c6d179297871dc79a794cd49085b4af0e18a06e8a65fedfb73d0cd2f69be317dfc9843d5fd68535dd20c4c1acd8714752a66d55bf365a871a0f11de87bef6b70e06e0f480edc8d835f4562ee31891f879c791539f3204896cf7a500764913a640e39a74b66becdd3901eccb77970d2d8d0d0d0dc7d43f33536244d8d98f34a8be2ab4abcc3f99ffe911cc99ed68c1a42b46cab8c29b91cc739c739de382e019c3f7b621ea3b573a649ce9ded4fbb4c707eaffb3c4f5928b6dd976ad26cc81cf222ef3feff1732c0dc0ff5e02a09680a785b86f85dce99f96c692e7972d1c5626cb0586a6249854d75a16955cfefc30115fd4f993a2b69ad0aa7c9a55b40d0ed9355c7df82a92a5c1eac3950e593c444a021977ad1f8c1bd4e58e0d64dc5de7e9ec2debdb3748c351fcbeea12c8b824cf5de6397acbdb75531aa30375da03efe9db0781fcd8ded34969800f9640c6fd7df71fdda08f93d2a8ef7df7def4967f4c545fe341aaef6ab8cbe61a845ff6c7834106152239bd14402e5d6c5ba06a332c539c644f9230c59468d294182f37292e5136da6ccf396d92284e2f66d25470d3443c6923793e408a0c14e61bc9656b0aa1e5219583214186ccdb10212121a1214184dc820819626b90213ecac965bb73e9600a5187c900ce9eee098b11d410eae004f9870f4c6f4a18e7dc026343cd98261f64c4f063e70e358c7bbed39eb40c310224ea8fa48f15b9be9d35deca795e1921c8f4e73741f10ceafe665fe5eaa2df91a3eb31d32c3cc9db3be77c3badb5f6ae6ec77d3727be42ba7d6fc79153dc2f338932c05bb5ce5ae7acf34e3be79c13055b6bcdd95b638db7eaf8f3738d07e15725725d9b75d639c588961662a081d6724d87f1e8f3bb3927ee640c81a95bd1b39221b79eb4e2c7176cc894882cc3c8c4fe018de145a64bb116688c2679e09772d9836589d326632b4c1a2cd86c537559186c069b59d8ccc26616366b22d3a99bb764907053ed404377bd12b99cb01ed56dfb615b9876a5f6a9f745f7056e194d7269c19e2419e791abd5a095e6a654535c4d9ce26ae11457fba6b8da9411d989da2e51b9be6572a0259b85b1a5551f32dd5cbc55bb484d9fca79315dd56a6e0a964beb54dbdcd4ae5c6d3b3950978a85ab511bb689d2a8514c1b1d501af5837e385057a5e610f7775251a718b7e428efc2843113a5b3aa5845f705fedc4cc06d39240e26666afad8bc55bf036ae37a3169d4af5bdc755740c5ae17cce865d7e7b22b4db25daaab3ebdddacd3cd3addacd3cd3add5ce64cd68969774c9cf8476fad2e52a95e0cc53ff2de282c0a8f9ea8c2c96892cb7a67170bd4add52099b7b65ddf3a554bab41aa9fd9da76a95b0dfabeb6987e698ce524298c731ec77f0ec95bb5e438981279979cffd1e66033651e3919b5b134673427cb7a756ddcb57132991179cee6d75dd65b9d554e565df53137e33e094e46c1e92d3b9bb1e8ab9e65ad18beea45ad7a2222c521cbfb30b47615eaf9b29a45f59c3ef44b9f85abaff9c51aab41add68f63ceadd6ff38ca623333dae803afef3fbae5ce2e964b232f945f6bcba49591d689b4b4a66d6dbd55579daa4cada58f8d49bb3e0ce39cc7f16b9d7948642a65f3960c156c5cadf39514f58605164eb3d96c369b952e639a61ecb719bdcde8cd6b22d74a4295aa37f0e7f392e99e52f5bb881b8cb01a3366cc181ef665b5d1075eb53e91db29de72a95d7acd69d5e518e73c8e4bbee42191ad936df296bf0c156c939d551a387b26b561bf14aa957e65a25f659b665b6bd3a60f54ad2e511a54575d0a3975ea692157cfa272d6d65a39ae723adb8c33ce18678ec6808ceb679cbdf5b80616ca00d32c0d6f418328eae1ada794524a73b05f605c6606549c39cb71d776ff5383ba9f5151c85f98c277b2eb2182a00782df78f366f8148681873bfa8f45b6df3d7d20de83e39ceb541ffe286c485211a50e7e08f93c1c9233dcf579b88b6cfd430e7c70e01fd698607ad8aebbbb5f5b0f9bb5e500ce1e5b6f489caf64f717e4549d4af5e148a9be8eeb4221e28d215ef27df1c3f70fe242fcc12eab9e95f3ea698c6e0cb627f1f126c47f08927f5ea998e03ce87ddd6c776fb5c01dfcea5578ab5ea5cb957ebd77e28dcee66b74996b3eab9e136fa85636dd156f745e87f3385ff3e5940b7a5651ceeb60b104ccec959375f6911cc9eff90d828f7d71c89f19def2bf194c1f7f2297e55b3b20eef27a83780b4847d627aa371e791dd7dae5c774f95b5bf30c360fda58a82120f84bb9fb297aff1b74a321b025d9932948494aa0f55fcadc7758b2f7436ebee36ede717e14ed88f3f6e7fc71fb569348320e79430e7120f0e983460e3491863890ccd33772a0fcf4296cf5d4ea00bfe671805fa3774de99f679ee68866c65b2b585efd78ab355d572586de83159c02998c86d050197d42a750249a4495c0ea59b0bdcf14e73c8e38eff842c89e0d91db904614667443da10551b4cf66c8e9c1a3284088aef518daf9cc0492bb520644a0477bb373724ce0be15485c1781b57c8be3343e2663dd7e49d913aab1625a34dac69a9537aef0d0a83cb65655df5bfbdb07bcaaa7f854f5f2b4bf3d5caeb05b32390ea8bfeca3aad766ddf03b5c5813820de739ef5dccee37c0f9d1f9fc82f4b039ccf791ee3b334119d1f3f47138152a4f3a376223b8fa367d5e58ebd7a276985cda119384f593c5fe218ef896f693528869ea00ec9b279d61fbd7648968d6669b0f3395fb4f3396f63a38b749ea58d705ee78dc6dff9a3978d4c4d6263e3394c55a66b4f4a28bd0c8dcd8ddd25072a2f9102aed24d427fd6d97c5961369a01f4d26a9095c9b0954d1ffa443e3ece4f7107d2f8384f6b8ca624b0d826270762fdd4a035879f0c84223082e390d7e62f17072aadcc81eca67fa11c683e1d9164d3bf3507cae9726f5436fd3b75c5781012387b3ac2894d9f43f2200b6637ca81e653d4d4a67f6d35887bd1a7b1b19c549303952ec6c9814abb64e640e58dd1689c9403b176143d7120aeb669cd61534e0b962807e28ad8f471988ca903eaf6bf4b3568d4383fab0b07e779d02f548680e16818cee3e80fb09e48901fb50f0dc57ece8f5f4e2928464a694eb39ae7894c7ffc9c37c27919231aab2efaf9f3e3905df2ac2e1afd95ca1c9279fa76c981689efe172b6f2c77311b2edea22fc5066fac065d8cc7f1ffc67c767963565f30a60fd78593f2168da64f38313b2b12c86772c76d2ca7ef359ce791f3e3f3c0799636ba800c45886961d444a014e16896e76896eb1d398ff3b24f72c891ac49d5451f87f4c0c8b6c9ca2a8c3e7943f6a79756832617382449a18b2d7ca952993e14ca8322df9a7e21e72faf8d78634647fed5e3c8bffaa36d146e9ab7214bba6d7e454e9fa2dbd4c8bc2569c8b267cb4cd7add9ba4f9f1317d0da20186dd583dd8ab55a55195285d5a4ca549ba6d42ab5484db2b2dea9de82adda5161d5b54556ddfc7d7b4356a4ebb72679ab223d0e79539d36fd90bc01c91268979706e4d1c7974683705d6115e6f75eec15565dd32515c67947362e002720931927f1cf880f6230dea6cc271bc46cfbf84bbaa452894856eae9862a9b520c367dea54836848e0cd061836c5917a9a12934da1af12718c2fd972308740a03bffe775b853ad38ac9736fe6d2d7e2297799aa791c9e48aecd103f45820f87d0ffa3528cda6bfc34707f48178902a03fa440da00f7e5ec5f11b8421db3e91ab2cd8f38efe434bb69a49d2bf66de897c86ac71ab500d2b43de0c6a58fde32d2a02ede77afda9d57fc22f403c5b38f006c1687b886badb5d65a23300218a72a3e052cc24c728f1d38640ce8272b70a9a21e357aece851eb9173ce3d7614d57614f5b876470f216fd11d0ed48383a639a3d4a9d42541869111b8121a62d304304c774ab3830e5ee825a4160cc8c129a54e67b0369b0d2ac80bb974a6ed4c381f26a4b0810a909cb0c194131352174b4b92a49e9cb04198272b389cb04117b35188bbe7930964bbcc2f83821fff01402dcfecf954430e4872def3a9862700ec90ed9e4f3530d16252c30930b89271f67caa610a0cb8c8377b3ed5f005064932b7e753939809983e882376d9da4118f1f3d4e4896df77c0a22cb2effc72211c2fe8d6e416f357e684de6533f326a432693c96432994c269b426cfb348a6ec102de2aeab8dc17725c48e61a2e871c076ad61742d1eebd31bac51cba975cedcff3eea55b68b227f7094b8a9555d9cf8fec3ec634aaa3356a1b41d4dfafb26bf316a63525b9fcd9f6e7caa2b2f7fe6595714f3c8846d9264ed31ad6b449466bd40ba5516b5fac179096a907f8c1efc41cf8efddab2c32f7fed88696cb9ffd8910d0811ffc4f977401f8131d007e1962f2febda4ebf89e7b1c9fd6e57d0ed08d3f141fff10f086d8032f28d6f01f070aeb1377d9fd5383ec4bacb24f844c65a0906f8da38597546dcfd21a9555998ccb94d04daac3b4265bf27e6e8cb36c2cab7d8c7f7e644b41e66d6aea66696dde682d28e7f5bd87dfc3182fc59498c61d5a1bdf5bf6b7a53177d55fc232d90f77d5bfe1f903ab3f4e5e1559a65ee89615d4cae742d16899e63d7eec698ae57b0c7edf7bdf73ef69968efd7d9a45e49f8f92ec3dfe3cefaff7f83d7ddfbb98c5d95a17f97e496b58bc55033ffa6546a35e6acd05067df095a0bbdc4235c87b8abb77a1246a63ca31c65dcedc385eed49d3870a2565406dcca7424d55897d1afad21cca40d51cc7dd0feac05df62decde3fda97e671a636a6aeb6193d6748dc7f1f57bf7a5939be520412fc05e5fe07f7ce9996dabea44f92cc913c4f644ee7b195efbd77ca5bd6ae7e7a6f6b49dd9747ae7e5457fd624e306bbdc7d883e9a0df7d9df2d6b4b86e6135551bfeb2da7c78ec8703d9c718df171f3f381590c59fa28efb62102bd47c78cb86399bb6e9be242bc7fdeeaf76240c2359de6dbc7b64c873a685a9fe7b5f6dd8615ee891d5b38fbfa55c4e29cf61db83d5a095d6512191579fdfbfac369b03ddb78e64ab5230aca914b539aca43fdcc81ca298f3481c7acb72da6132b070da5e68f394a907f7bfefc41cf7f183dfd1ac412b30c4a2f844fe7da28ec9e56e100c6d9e72a981a3fdbd0881fbf8a9a8e33ed6e2026e0e6ecbbca72120fef7a07843f51e693748c57b498f043db2a45946836086035446fc4b8a1f79ffc7c6ef91e5b83d2aaab4e7912009641499e417c9efc03359b7b8cb3ede3e6a107e85d5e642f6245d4596b97924b8b915595f1ea9da5ecbc95671b54d1ffb9f44c67fdf6e9b44436de88420b5648bcd1f35e847a806e159fe1ab3dfb5843cfbfd2f91ab6ddb47807d960eeef1c330fe74b5551bd374d9c738e7d1be85652772e96050cecb3e2659dfe3cb912cee3bf10698b3bdc061298b124eb25011c5cbcea72a4fb6ad71e40f3773577d1aae65dbe8f597dc559f09cb72d3e8447ad9f57b68c81ea334be8d1d40c5ecea91de86c1e61006495ec4811c68d33fda251583e9944c88c3ede202e866e5c07fff6a0a06a4625e3dd645f9655e461be53f7aad7e54d74c4bd39ee20d16d644afd5e7bf1ac80cf3020a609b95c36efc7663cdd241a996dabe02fb2939932b301e2b8703e87b9a95833bdaa0b8017dfdd08dddc8f4194012d89edebada6394467d0b033d00571ce6489e54679ee4451ce6b0fb778b035d2ae5405cbcd6a25d5a752ad77f3a356d2dda6ad1a9d61cb25ce89649a5f6dc6293e026f0290d0acbcd974c706ebeefb857fdd7618efbef31c73ddd9c7deebf27a737ad0762fc16bff79da8c33ed656b3ec7bbf7aef891c7b7777f0c3813e6ff5f777d4a05c5d36e5b4edfb7bda68511a57e61be040f9ef9713887dff670ef9ecfb42431910cda1f0a7456dcca8d1337cd34c99e11bffcccbc8e819a51fa6192b34cd4aa1cdc29739343708923e4a72f8f8c507c3f0c110fc50b374ec30fc4007a859e0afb417053e7e9508be07fe07824f37087a9e66853f37793189c36421fde3adabbbc8dc7f24ad79ab7b16dd5ed73dc61df54269d06f927151497775e8fa56037463fae007a0d8179f7b20d6f34992bd0faa4f926c27deb9e3c8f34466d9b7f9a317cedb7c11cedb682328f68d6c6eb4d1cd8fac1cb583a37df7557220d5dff1ef5359cddf2f2993cd25aa2e990761dae821c481c4eeef1b7d39b9d818ecfb3fe690b53113640e797f7f86f418a5e1e37ed09c5b5cc5c53dd277d7819c7843fc2b36e0886e8fa43ba434fa3e6a1c94f4874d5f6a5fbf403cb12f9d4d1fea4630893fe78f5e2c5c84f339361ae7b12e1a6ff4f8472f1cfd63247fd8903e4a32cf13f95fadeabadd83fa4afefbde5ba129b92626eae0bec43177900f381fdeb24fa5541e328194feec1e634d044a91c703130175d6ef1d49ceeab2d406f63ecffbc00982a27e0f5561a87aac33febe7b173ff171f7de73f841a29f5951f75e0b3f58ce19f73d66e9a2efb1ce2208740c8bfbf74816a73350e4fde769efe70b7b9df6e93345d007d0079606dd7bcff2c7f8e3f19e03651d8a1070cdcae16fdf6e513adaac1d451cf4b01ab841a78f156ff8f4b15fc5397dacb621fd942c9313cda93ab5e122581da6ab5aa71ea65c72b53974041735c5944b677272a6ebe4f489495cc935683aae39bcb7de516881426f81d3a77ed9ed59bfbc78bbb73440434f674b52f04aee3607e806627549b707f641a863c64c4ac3f334b65ef747bbd3737bdace21ea85d38ec95032379dd2b0356ec67536fc219716466f30ff0a951f67cd894bf7062edddb126c093687a610484df5a32a54857258ccdf22b92d525d04a424d8f4a9e1835f86f75683426d911cd4585bd86d5b98087259a796a6e6d017d446ddc22d714b4a21207912ae23389dd93974c31e11e492dee88dda006361b0593dfc341310cc5d7299bc75932c29792b367d02fe2ab29b2ddffe73d2f007a56a10385dfe17cc8266fbe3f8dd89053092bbf7bfa26692f42f03fcfd02a0904b260678efbda73a039656010510720e4a5597006c752a80ab0a352b87dde1db1dea92c89ef2d6fd21d2c5b8ebfee7449dbc75df0024ade2aefbb8bbe0ad063d2875bf8252fbeeaa2b92b7eed7f70fc37a54c369094b407f70b5e78d1a5bcc9e9e16a5e0ea468d2db2f59608d62099caa4b6cba0613a08e298f3fd0390867bcea8135c89392ed8ab8a63ce39e7cc60cf05503de7a474ce39ab1017b470410b75bb2e8d4400674f896f168c07e125eef2b762ac1824a4224536bd4a5769d3db2eeb33b164cf2b7b3ed9a43678410bb509492e31d4f6c7500e549b1c33d5251f6e870ab515e32d5f926d937bc923b67a1e554a4df8be7e5fbfafa0934cbf76b5ab5df7714fff821af205dfbbaeebba0f042b58a93bfeeebdeefbeec16f00bb7bf0ebc00ffcbefff48c06e85abf0026f4743c20d9b2c17724501b7c47e26583efcf3de824d39f18e76c671096c9d9b30024e4fb17d490edf7e0b76a1087f3c61bfc48b2bcdec23fb14c8fb7bee79eee398740a0377fd101fee0ddf4afce7101cde10f3e48410a6a0ed00dea06685b0013aaeee1ee131c0c91448230823852041058182145f45b1533850eb31d5e68f2058a2ec9085937ed0e6f29ed700205296870d2430f598410a22b2106ef488f274640a2c5114d767842c2b64f63bac0965841142f4628cd89050c2b4788e08624685c3a101851041fb1e285d203591246872637dca0e48a0ea966457ea56cff61c8fe5697970b058630010f5d58a01dd1a41ba126e5dd0bc05050124b0285a8147422d46c58cbec06a428aec49a88a10ba196053c62830d2928a288305844d0a183d576a0a1476a0f15a69890984a32c6c68da94de198e4a815ac0842052b5d48490aa31685b3b956259a8070fbe2851794623a7814b518564990a00b1451b4c46ee0c0f0e891da0bb7c9044249102980e1e94b1556c4d490d85a922a27a93e4e8044851121d030821b56501be132a9553bc50aaec4906a21c4f0851630d65afb46f28eb1ba8ce45aa9d0ca83521213145844b0a289175abe702e7b1b8a5411831329a418a946b4508195b4446103432e47ccc930048e0d159640291155c44a186e4acbecbb4942a0a549971f6eb089208c161cbe7b24532d33d0e4249e444b0ba1529416116c8ae06265e566518497ceb3057ab86551c11547c2e8418a1364594060c20aa618020bd74511537858977e6b0a9e84cd051b0951bc38c20ba78222b6747ea4c90b51b531379cb083162338304520e1719574627b62427241072e382e94ace005081f76f832858a6fe96e96215172052d0460a2102326054530e52bf32506206e43d41053b205e74b59e856d17a1223c4174d285061845b1125a86ef55202272a2588218c089c1663b02b390b3cf4c0c50909214449ca62e64802bef80085890995159c705fa400b9903282929426b65ca14316599210a960295964b952801e9414e1f4830a9880e15464a1225ac942823c44eb10173c48d990c20f27c8e2258b920c9309446df1c4c6b43404150e6a0c9e4018148288d5848872e3ae80020372d34a1434ae64950081ec3df244834c0aa72b2a4ca9370c111ea1800e26e410a50a132454b1026aca4a890424a258c941845b9410705da0986aee110a85e403b27e641a51220cd314314f5e64a9022ac67147fede7baf9523271494e0e10729da123a380aa0eebd7763d764cdf4f1b7b73c87ae8bf92c3f846003942861603b7054101106c621635cab942423584871e10a264b8480438117d78fd401200166892c4e8cc0040e5c88d862ccd3c7c6841a5875a18b2b218922060726aaf8a2045db829a0f08e14e96207134819610a14332998e8c00c332c524ed084165d4411757d3a81029715580821ea85239c1411b4d06a21e20a771d8718931980b08202a530382888a8c2390d242c9488e24452d416dc13449400ac4cb630c97828e10712e08965b6f260d10483ad8a2a49ccc09d40866dddda975f212289c68a063688a0092648c022b6026e0b11307ca9fb8c0bf188148d5b6a654631d101d97be413620431b0c005942c60a858640939772b26e894acb5d6e71347381183a2e9052a489c280178a402575491c2298523312e4d2c11876841c458487001092e464040e3981822d6b9d2bdf37ebe3fdeff5b45b02f09dfbdf7823268c073885e24c0ccb09e5c266b7d66a1457d7757cade840acfe7e47499afb676de5c75b02a7cc102172938114105596c4fe0287984791de167937a6489ceda24e53273bf4302b660e2842b5d84601ab344ad6b22e2e7a8290c122eac70c10a09342580105725c83e5f2da8d5cc5aa11a84adb5b26de8c49624820f25f060c5cb72496cfbd4c3b65fc381eeb6ba0432330bb9c4e37629e0cc1864e1436b86002c11802f0090426b842727d70bd99312002926188491710c00a892710b4764156a7882b9a2a4208a36050094c8aa3d9f68494cd17ab8e04bb6d9f38956c3055eb2b7e7138d89b62484ecaab0f29f3dd9b7df696387d2a0efd5bebfbfcf397d7be9e307d874cef93bd4460694067d9ae9fc0ce8d379c3891c4fdd8e727eabb575fe4cbc13a3d571f7bd960e28dee8eecd1cd97117efe018db6d0f95ecf70904573dac7a08c1cfc3d539bfe00ff7feb8add53bceddbde3bcd65abdd66a2bb5ee6ea79d6228aa9c521a1c9deeee95d269c15a6badd6ef9c40f54bbc7184506a963cdddd67786b3e56d27577da8eebf0c59ef5be0a86d37edf773f50bc4f2c8ded8fc505d8aa050c7113f3fbf9de5d3a5b5dc6a1f50f745eede52e77b9ce76b8ce983fc36e5a71f8f8b10801bb3d2ff41e04c3190ee4aeba4419e41aa48090da30402b4a559d70b57abb7aa562ae4106982e7f4fa62b69585d6b9986331ca8c39d9d1ef550208e16dfde67163fa420b3fdc71f9670cec7ec72e5cb9e60268df98c12c1fd73eab4e79411255d3869f20313a957d41662d3a84d8308c116df3a90d0161f146f882a104cdae18be2e73065faa26a47fc4fbcb152893a0376871ac89cdaa01e91f87e9635fbbb32667fdf13104aec0f0b94fd3d166f7ce3f6be136f7806c0f80a958da5367e4ebc8127d00092c00cf083086a96d96c5b5cf36dafa8b7432b0a12c25851e40606edd2d0848f4000c60e2e3c4d75310b02091fb6503a6bcab3f594a504b1fbc41bdd986d1f639ff2230863892b6ea0e104501c314495afc3d16996945cfeb7ae053b3ac4b6fd6c9f7c90b22dce6073dce7a80d04051412dca0786284941767c4e6fec781b8afc1bd02e6e68e0ccddd75dba78722db5a2156d7ca0c3be9e8e704166d267c2a98855114f1ba9802b338018371fac29410ab12c6065cdc02a103a8942df6553d0b11d90800000023150000201008054462a160349846caae7614000c7f9e487c54968ac324c861148490310410420801000000034668688a036d8f710c200871e6e8a3c2fd99197feb07902cce524cafefd92f2272bcd65401e29fd44a851cbb690287d35105a34ced90c269de87d3e0be909d91bb5fa76f679e3117fbba73089e70420eb9c6e748c104ff861eb8ca2b4f51f3746c329ef600837f76a8b554fa6763110b7f4471eb75b4a8843c73b2372d6fdd865942867896a2d7345abc89abcce20d52a29b6128bcd81db8c86442338ec234cbcb83b6365398fedf854fbe56c2e36f1919cbee65ed49651a84b2d2021950d80245c84933fb059f494e33b854c2b17733857119ffdc11fbc0a82805171c16ef79e67f9e508bcab21dab9bd39fdd34e89117ceffcda36a55d60a51af47ead2570ff91d6970548457d049f538d49fe984f55a7448217630bbba23ee863dafa605f916929aa8c9556831477fd2d67fdf87a78a645e24d71f83fecb1ac1c4fba479b8a441b2ac63d25686ba86877030a0368225201cc865e5de05230a1a5068326f85ae1db86da83a1b84dd5ac66478224ce7c46b31354232fc493dd4a20283fbc2611d6b827ceac5992aaec1f014a6f3c4fac1a279c44aa76864b811808fe53ba7669060cb5a591fdaa5d1593f25a4afaa50e077acc90d1386c54e71e81d4c865a2e3e2766196e6a177c2e6d2aaf5f70402d7349561510db41f2151cf10e2a10b00f33e25c212184dd76d121bf4b8884e12590f16df0aefe8a6304f006d9693b9f6a73f5e80ae38a013717e144eefb77d3705dc29d984dc27a6cec0ee48796056bfceaedec36043ca5fb045d17d472061b98c4feb21138944c98a089c2cbababfa11c4827a16999e6a3b28b1b1342e0f87680392c503abef768825b451e818765757622b81ab5e217f49e4da8db92182f14121fd66bca5ac8726f3ef89b8b271097bb930e24d9eef729921c195378342661f36e92d849eecd130e7c7bc327653beaeae6f6845db752730f65a72a78fc49768ce0112f6225a100578128f489e79d1885b9f7bb00c1b0dd565cc633936e97440197f426c243ffb76c482e9826d733c0e3646f34fc6c67222cd93bfca368aac23def846c5728ec3b129774bec3e2e4d0b649c49368dda9644eb1b9b48bab135c1de477c18ee7221620335ab8a53350ca6867735764d98a5c238056f043fb133de1817074fa4834fa1297161212ad602aa122bd622b67531cce05f4db869de9249d103eff160a216b67be296351a73c72c2a1db5fd7291fd89b5a4b3db780b28a22dd354c8dd48c5db792cd964413b400e6d521bedb08fa8aaa006b988ed2eeb0ce3d13c48f88270cdf57794d4a4a5b0d35288332432c4e6de9dd37e9323f52637ea01d43b4b4eb213b224211288d915d5192c82341d15eb23c94591665a3b6007d41f901e3af918fd4be2e548e833b5c30ab5d456b8b90d58a1b11e01e170cbf870024d1e774975734acc1670c4f50496653161b7488c5c799244005f420b4e0585e7466a0e51aa980f59e468eb3f42eb36006b50aa93726482f5569b95312cd5489087d340e3b00d339839f8f88fdad053e7ea13d8c9031b388dc5256f34874541a37b04672d2944331b4eb190e0f665ffe24aba897b774ab1170dd37eb49c12b83ebee61887d905bca2fee42a7b5dd869d9725bc5f8cb96d3af5258c4e73e9759806f80bfeb254a4e61cac8551876fba88335ab9cac5c2ff0bf3d0910cb132763f313fe432b2d06c76b958cc9398e9a4694c911cb32c6168567e2874c415115d534da8e3d836bf9cfd92f7521fbe09297a3dea4465612352a76cbabc51d4e78925eda8a98a86291416b539ad401209a7864744a5a3013003c65e2ff738b6528d7b8c404ff0637ab0bb45f3b8a9e4cc9f0593981256370007c416025a8ca3f3a329754d015ea4ddc946922488b0ad28efa94a8f453a8f6657241b86640952350a862d412d01c35369833306f0889643f44a46a468659483a5d84ef7f2aa297eb72a8aa46c5f7b63215e395ed2948e3d05cd5e3f2a5ef6bc28208d2186b29fe9fd4dc77494f40476dfa33314401025e02da5111fe31baf7155acc08219680ddb1a54409f1b2510cc6ee9d202e3322841bf706824106fd43652eb3dcb47c204d3ec8b905d7b1b2f2031b13439eb7f887acf40d21a70fb149fa59c93cdf295a897643186806b32be00f7cf31ce2bc369c9d4f35356bb187f51c289e4b6474b5e2062c6114f8d95e69919c89b352f09514d282bc828404c2ca8e753cc7b705dc7b2471c693b62e9420a6b981402c1aba62eaf8b115d1f53d468bfafb6d517d1d9d6a2501e571d509a3eff881e11de5b7463775a0002d7093b55b24ca71acdc3be4b8f474a6fa64bd24b98a5aaf99027aec3e0ef6c4b7833c81ab30b7edea79ecc071c2ea37038870a228971b9311b9ea2ee7910f26f5bcf29655e4b85e89031b5f81381fb279dcaf80242a0bee71db5506125d5285062f242725ef1e2c30c78abdd8e7ee8b2d374a91e0243d59c7acd54f46d2be44c0b7b27cdf1ac684158ab40c58e6df6a2a3ead8469e3163be4178ecfbb53a6a43b7a4c895246d9109f0e0e58dbb7e596217ab5804742e86c292728964f7de04dacde186db7f09d2522751f7e021596fb0d4f8da8e5df75449f170178618fb34b60a24c43586d6217183e275d9325f509aa248e94c9cdfd6c1c2bd2cd3ed692ff0da7f923146fdb2b39ed7a835aa9d174a672adec0d8936cd7222935e0cf19db4465f0c99efc30418fe5b15f8eaee5f9021154af5e8e330ee0c2983fb1c0d811c7a2bf6f744ba0d54b9108a13630dac02bab1af867a87a5d53402da985c12b9dc7c25b8eb96024a864b3988bd2f87b917d19737f8f2a2ef98a492d7085fcb34a90e9e09657061d901522b85e4c391ed0c8f083c7774841bf96ef6b9fbd4aa248e42c3b4cc206d5d55040124989029dfa364c8432141044e462f9f74329e06031bc263f792c4d88203bb0841ddbe2c6815fe09d01c2ddc6de133771c4a536bb97abc1efe29c7b970dbeb30e7e6d6a9d2a88b8419afa7c9d33be8092e32fbe897cb11a2308360a465724d24b35cb3e7cc167193fd0383b9144214092eb5964e8ab220d5c4385d6a15620befc840c3f1b6035aba29d32d11e33a14a7dff61bb931429c3931f29a994b444999a4e25a0114ae58f5e406f947acf1e494796846c1c816dda64d6729874de08856120e43a2db6120af832ead6bc970323e358fa5358b5cd50e8f194f9e7af149fa7be620ed466a74607c66621292f0adfe88aa038abad0086538bc9038ac66c41fd1fa31be8901988b219a5bbe18615a36fd6940097221161d7a7175c87f99b85e48026aa096c8454404da8972d8bacc0ca388c5d64e5ebfc9d16b448c68be0e1d2ba9ba2e3080a23f41623edfe3af551659af70b9524380efdce306a16f46e3c409195f195e23ed018fada946f30f6e871461c0b732d16f356898f072022ead6b864ec4b721944d809c6ad3728e469e0f375b4ebe0d1ac2e1f8b82f02de4f023fb37ca2466ebb0a0c5c7da2603bb893e566dfd6dfdc1d79dc6d2f4e07e197703dc5501e476e68ce57bf2a323faf2f08a6f86933d638ade6fd8b8b7e6e78b81f78169ed0ce8a3578a6676e518006674c169481a8b30be9c541519ae1972961e2bd8b729810dcafab18b580cccde253f6bf5386fc86c44e95071b47f632f19fda19f2e805858b8973bb426539dabc414195394169b693549c9c2074ac903f0245e0da0ccbe553bf07821208bd1f823940bb08e175d42a68ab2e4faa8588291332694c31cd210ee6e3a96ddd2c4d5ab7384009e6ad5a6c93ac577593037626fa88e7cf92672d52d324c5d6c96088c37a6f5fb5bf00b9543cbcced90e4f84904da2de0d46f60ed4415fcd54b8fdcd0435a8d16d5d4010808fafaf9cccbb657014bdfd651e5adc7c5cd75d514c1ef36529c6844ae9b31ccf9a7c4b6efb3670e11f2b53307f0923963de024dd8fb8180deead008069c27b10d299a84d9ed1be6d545e560681688a0bfa8a030a1995f838ad76724519024bb1c4a5f9af344f037cc6f48859c9c2f44d5925e65f0d48aadede6812dd5f3b77e6f90d6a38c5fd0cf474da55c7b768a6b0673454eb32731f17f4f8140ca2846a640c6d2855876d4fcf9b4df27197b167e251837b7e175aeb37d945980bcbcfbb3ab07b350ee533fa234d0c4bb22ba140fd9458b1fb63029c5ea1f7a37416250e553d6b7eac8f53d4c79154134c91088c5c19ea7276df25e519326bcbcff9c10e93a2a7ae5d11b3d7a2bfed0ab03cbd130a10391f87b1e8562519ef4f5a46be5e5f2ec1af93ecd8ec786104230bf4db371927ab3b0323c7cc853adbdd7bdc025f08887230ad5144f855e8afa121cf0a919b6ed61a1ca4d66ee6ad03a9374ae0b46a0d7932b700191a815394efcebea5e5ab7b783845ed7a72fd88c9d95d35b9fecb0080d36a5f92c8102a6182c0380aa1d61f3d956818174caf9f764c8d22b1e5039bf27844b9d4ae4d8a808e08a984a384dd3c99ab9a21b4bb9ca29dbd2fb3aefaf0ed3e4117221cb8d9a42c72da1640e6a6667c0195d92b54d14b802c961e4f31e7b3138245b85f9872d36624cc29f254d617616ffb5dc348850184f162305622343c7b1061ca69db8baaba312471aa376df5d78327dc650704987df93577c0fe832e32dd2a6cd5b60208433173a7d41bfc98947a727c0c726d2d70278b9899ca3be51271d4a9eea132d2b7be91693bf8d17347ca2a34ce481958b9c06589e7329b3192160634a420ae91a14d62b01c01622994da47ac829b4159762d5a45c456d9f5984fe08070a2136323d20831b3f76c492176975158148989f1064b25d2e6c660a7f8d6fc1c8d2cf8986483a2963671cf3b628752e265acbd42669eecbb6217dc5acc344920aeae4ebcaf63252835efca55c6054d883699aab499087d42c14f46c96f0f94825e0519e47aa38d7816db244346fd4cff28e9ddf5fcdd81cb525e7457deb5f4de4af15bf410d113111ac6b87b493ea43948c2422d4417143680a5f56073cec1b177d12aef3381a84079785f35c8b02da768ef2673233a18baf719a00f6b6049e897a377604f01b510a3d668df8e7ee6952464396a451223ca0429eaf7efa3d25e267ca04e83dcf350563cac94046e59a8e3679fab9f16b02813ae0d4e98d5f65743389611621f6051afdec4f697f6fae3367c7fdce313fa19100c6d596c33937d06ad2a875b2a60d3bcc90433ee06e44162ac5ac41c02f6f96e2ca1e12958893104793125b10c0d891577ff0c18c6c4a21573dc159cb788c67d8862f73604cb5d50e75421a14a4147d62f6e2f0f92bf7051aa4721a58e208e31c49037979472d1557514b7483534a21550498bcc95d88d3f813d5127d82d786263e77939904e550d76f72e8b74aab3607942d9eed3e7317b1e98e5ab5cc178e1f1d01757f49bc3c20cb1ebc546d66004f303a981333ab505889b7320a029cc63fc3a337750a876c65bac26e82976778493a943deec4bc73c1ebec2c284e57a6ac0cddfa6c3c5ea55ac1c7dfcce9b7a5780e535be8331b323dd7286d8ee090ba4c6c9cde7d2996a38a0698cfe9813f3cab6610d4f974c964e41d85233a6c8b8c05b673fe67eb04fc31f19fb36f503d51f3679933c137fa0d0290590c18b32620155b1023c388808b9de48df91d1dd87780b6c70426da691d7834550a2016158e3256354f58a082b0afa78671a0423808744059d32990555fa91b6b6cb80be5d9a7b599c764450ac90a4608ab145f80308809adf3dbec8c2d6146286d9c7dcbaf1b07da8e11384f603eba4859326d156ff6ece92da8c6e406bd3456bfb315eeb386146708298679951248c8caa31dc0370559407c32e899673b52922a7dce31d52d7d35f0291768461d326722c9074c9b64618b4f26f1c57f17bc0a5607e0294850ce26dad78e7820a866f2973e5852f79bf3cc5d1449a39beba67408b5a42c50c91d054e8ac9df40b01e2d20e212a97d4259f0a88808cd0defa879ef11c3e4ec28e9acdf3667fdb86658a08cd11da9e81610f86ec02125e81c733a6b3491dbc0eae111b6050e594da4dc28310bc0875902ec4e1b692ad73e984bbe83a601910115b6cb3a4ac1dcd080d3951b941d55914e9293b33aa02730b2c232694da072e341105a699575cf523ab14044927cc8c714c0e0c26da6352e627c76670cb35b876cdb04c24ab7584d07cf6041830cd0578f380df3d9e0e0586d2fa8a1c029703d6429317e6cf0dd08ebba8cf243c9646fb3b6969c97a233816686b72088732ab2044209b1550a309dce7722289f3bf8aef8a248de09c7fbacc20e7fb775ec535b86bc58565426e5df5ecefd09d29564aa41a5f707f28be0d1531fa17c41e87dbe76a74b2ce9627eb190a5a8a62f6bc2c3aedb778378f18b5593477a45942b6da568eb2615c47f417cd0a954b3bcd884f680e96754dd20e0b0577dbdf039682875c0c6ebe801c5821a776197d26f9f5337b4a9c217155afe6708ac80d55f9c155915b68ab8730983025824f3d712fb8d787780d6b4f7219c21b77e2e5e32579e63026066d29e68d3baf8e919e54e1800edbcd8e104662f69437f217d768c58e7d09e35a09c23748251392646e1b56183cedf2b3a44ed73d74c160e6f473f2f002757b0d4a79527641cf89fac7b4202e9c9b4ef1f1e0cfc59b3ce9d87cb3fddb6c8be1ee52c5b2166d063fa64b8913abe39e4e27288cece83d183dd9fc8a1c6150dc0045f2eef76a206b60c154580e39f4c5ae5bed93c282a60795905a2aab3c9fad55e4960a30db96ab82d4d91c3f93a36168d8b5c703c2abfc37c69a455de463d2c31596cdbd768bab16d060810665c9df288da236319d763910b5cbc549f4a88e04ba39041d55be33d1490bec296e9bd74f3d27f90f8c8a7e595693367a8ab7cd19bafa03a9e0b1b1072df9014ff2d91874516160e8035439da747e4e3a1de180509e3660d077dba87dfc185a2a0d8d4b01d84ffc279123ee7fd619cd553baeb6348341f391c6431515367cbfba8a4f9abfe1d345a283880dd26cc29bfb4e76e2fa68c3eca4518f1d4594a1e6d2edd7c8245ca90735dfac80dbacf99f58d063a23f884913f47516a157106a122cc9de5d63460ecf0f6565380e9610da6117378590ac4ae726d276a69beaf2fde81b8b0c2326492769a6fa57af0a62ab5105bdbe3a765b8dfdde93f5f89c762a933463b412497cb0c4659c673050e12bbea44ddc5e50432b690c37801dba76c973f096484723726ea2c1d3e1730f2ce7a06631a4b0f7083a5261888fc691d3843f29f631781f6d4e475490e8111fa08a3c28963a52ced91484441c16a5a5c1e1abd0a7254b621fc7c7c847e975947de3318d52667add960d7c1a659e347eddcf6d36a3c27edc22a853d734311198959d471b20d6ac00148c48828c90ea6cd7142b83a520e522fb7f72c89722581cc1de63af0b058ff6dcb86ee09f0237fffd486cdbd1e4ee818f93c1f8c99b44632beb11f998a9d16e79930cba43d691729bee0379dbaa8058d4dbcf45eb7500e267dd3e88bc696bbfb1e2feb6c60a022a1d7826ed24ea77de0038578e82809238578e88b5a93720dca22235a9134919e0ea949a0f3db03283b7b0bd468f07b143f56f1e0073ea7fdba5d85088ba210db497ab80fd7c68dbe409c4c1853467d0acbb5cc21a442d49cfd634d1118a251884becd7c27f63b002b979b1939cd9ca0937248ca431020190e2fe7b3d10090ec1a88b3117c7a1b0ea28bb3a391fd641c980ca5d0fe5c4c90ad02a7915a9758d28d42538e3fccbe117adc740e3acad8caeba6a4a6d3414a39850c749b4d2703bc6b244b5d830c4ac647e4b6ad74d7a785acc28ae0d5d85e1e90d589e64755ba6b9c6ede53ab4a89b84b358cb82a794bc2f8faed871e0a7685e059ecf1618de400afa4526b43ed5de41d767c6b827cca2792d3112f5b5481cf86527919ff69b064935feede3e023882b2361c2548e8a9c66ba34e0975bbdf2a5285366762239fc725335b4cbc2412d1018e336c0a484834e7598d1eb147cd7c46257178b34012aa80f1a9c306c6d63d4ab613357f47090fd42844e596a824340b3680489457bf366085cae375733d30b61e4c64f4ec6d833d1050e120badd0259df9f1154de9865955b733738c8996aaa1cbe091966e68cbf13be8d9ca71ce17606a9738bffebd651c38ba6b23410425d1bb2b8eb91db957a96409c33729636345d9281c1b320651f8427e4a8a97282cdc9d57a2d5ad34e1e579622e9e48504aced3c1bb9c804e5d513cceebf69c67b6e6efb63c9ab0bde412b46a6e884a53f98cc2d817215302c7f5e804bbb9d7434f551f287c419635faec5497de2bb7fd319265ea31cf8bcac5f4b9fc52d5c4c60dbac2cc244045cad802b64d0310881ae4b811c7d70e70a167a31275e12185fb901f18dd254b79863a352ed85dc4a1dada9c7143b189710bf845c32f5685f872cc34921775abfb74f17a32f85fa7ad99a78e0d9f502dc5b479043d531d84060c454ee33697f3fb87785225091e6460b48997a89bde974e202e57caf7a6e4d84b555de206508655deaecb02956bc52a50b9dbd102b73a2818ce8d5b24fe28e77c10969539505d5c5a9571139882a2d2b9697496914335120c26fc917c3ccbdb1d4fa08a19f9755be20e44409fd7fa6a699198aae6314c2be68c8d17bfcd156043347c335804541a288e0ed573177f9fe336755b2394de9bd9a6046ad8fffc825de8b6507774001fa222dc46ca404ee0f9ee05b032bd900183d587158c4aa81c80c5be47099911e36dd6e1ff21e6cb36c258895fd616744426461f9c441e03bc8920a1df2034eff5de2a8f6b554c61a437f4cdb552a04d6f62b2672f23dfec82c71e57c5c2ac5d60812d353b92df92b41877bc289423a6485b3cfdad755210248ab7324ecffae6f0a30feb9b26d0a4f1749dbe10464de05c20fe8e58861585010b50c6ef69d84438265a80005aa960ea610d425c433f4fe86dafa281860e27b5921346eef35596eed0a3372324e98a3620e684096b49b32cfddddd03423b14ec50847c12480153070b4923fedebe2e9e74e29af29fab189b643ddd67b3af9915286c13e69bd5c8da852425db90ef02af3eff628d3513b8b90203de9c28b7e4a2f191b4bfafc65efa0db26fe78dc60ed5a95403f28f231fb05006b75ceac5f1cdde6d47c73058480bb8dd6ea5d0527854994cb35d99842b634249b01e9360aad971fb54123f267a2cb78c90bc9394f516543d0fe79d5bcb447dee6d13ceee72470dff088ec66e67981b40b50bd90f499452480d9be1220c53d80eae6d5b1c362f4bb8e3efcfecedb5fd621671e7c32dadc987abc3a51eeeff0b86c8097eedc06210d4177a959c6aabaf0ade1332c7abc33e267d1f9cdf0af150d87347b4804e15fee3bde26bb4760f58f5efd6733b47b7e21b781886eda52f2d20914c871bc9aed6842218a47ecaad0932cdc6f4a71a44166db01f873b5db97ec511448ae13d7a18bea9c14ba4a004400d9b9f4bcd59110ed1b8ce6c0ecd195dab4ec9fa98da84adc0c37dcd0ed9cf2f82625d90449a78d34d09bc7346f579e981ba7d21c29aa748f7815a5aa8c7ff6a16fd90ea4169237690982119dffdc63bc501fa4095650e8f6590e6c34fdded79eabf797beaa983ad183515369235cae4312ca01e3920db2a510c17c298fee689afb94f7f54fd79f05a8a46f8ff3364549f1a21af4224c9bad5faee050c4fbcccb16b4c832243f7a493e8a71c5c26e910bfe08dd322572a85ea26b5efa2d630fcabc8b54bbe817a72fc6981e0ba285f55524ba2113cb19a009b68905cfbcd6757fb75f569f14f297cb6ead4c7402c301262282a644081a97b35e0305febc42eb70e132c9c7e2c6e9fe1db2865f796e8c4fab8062c16a3e8ac69a2749359f68c465a1a42dd3de2de2af20e473c0d85f6df729b69cda455f6784f4eeda12e1f8008b436405e5877b2d2b2cc2f71e1822049cd117660f86032783d892b040bc52111dee68b8fd05950adb710b8dacf4b0f3a732622a22d18eb841f0df15a70365c4242142dd0670cf257db043cd878cfb6898d3a49bc27fe859db052d47840f8e4f406bd7ecc599a93533928910dbcd45f871475cf839fc643b470fc8b1c89805e226b100ac2fa986710e1720266d7148d2a55bdc0e70f8a2f10c19320f05a9ece04d13631599749bd954b9813ab11960385a9e55e04090c57b4a4a0e4318aed26a947aeee94e360a72ed4eecee06e7b04b24722d5c4808e647bc6e8e1dbeb3d5c5b7a4d842c4b7c2e0ec7ebe99137fb0c238238fd34aee8b21c45a280055da9204c670a5df10901c8fb228b38ec71427dabc1e54c8b2601ca0d9e4fb6198b08385011c97e4f74d6f9811b17237cf0d2db135a188c99ad81736fbcfffccfefe6771f2a60e98ee426292b92543abb636b1a2a8244106f2ad7721fcd19ca66171c6ed70322bf162a705ce8eb7a95a2d0494881ce5907cc328df0a155cfc453c0057f405c10240ed393e898e8e507e2e443f663fb4508a9679e70984d6dc598c4f1d603b1e5e8605d70a05849059e6dd6caeedcd5dbb051008a81fac4513f74891331e5bb94150bd0dc55a87e6d5f33f7740e051604109564b8b49c4b9c518b726aa45f460c9aa33efc2335ac8573edeb3ddceab85a4a604684ea15248d4ac273359b83f45032c7ebfc7d0d568557d261ac2ff01a42f0f3467583837a6011a62634323b59b107957daf7f68fe3fe947fa2ce34e591dd3491890c1ee75aebb9ba3d90c1674814bf8a5af169e4bf4e00f1e732a78c9b0cbe05bd01b8a704d76777bee7c267219ecc7b5affe90b02bc7e09dac7f4d1975c9a8e7f6e66351112cd80bf16e44ba13011d2a0f5d011badb03789366574cc8f0e60d29ad93cc40f58360eaaf619adb6df42c4d4e0e6639b429647c9f0c4672a5ddcf7c7204dbb1b0d568e08ca0dee4b5acf3ad746a0a13f6f1600d64216c34e69a920616891de81e515d884b80f49f9f44aca97859e79b42d3535a1f2007e83cb70d247be91eb3aab89ca9b357b0ee84f90c9fe3717480185bfb7da6c27256c1fef6607b6346257df31eb705155d27bd341647421e26667dc98482ecaa88ec4c50c84451a0e4952df50c7c540a09bb490ed13b0fd6686bc1e92833c1806c67007f1fba87f10c49a20604eb996cf479a8543bb003111baf87b8c4316620f360d492dc26f11c4101cc274653dc155cb045f9f9bb90f17b9bdc54aa84ede78f9837e4816ba654c213634e5c55b8e5a0538baa4a51584f98b4b14186a70563ae4965a812a6d290fe2d6b02b933d012e6e06d53a259777a86a5c8d40c496c8b6851db7a7be7505d74193d4e86cfde7b5b4b147634eacbd96f0ecf8373e702119357b6dbe10e43a51887a52d75007cf1ac203c162f802f77465d6ff9257339231577b7e4388390b6a2bcd42aa2a7b27d1462df3dffd00f92cb372ee8592cde9fc4db6ab044308a4709dd38d85bf319b090fb53011ddea6dd4d703322f8014f7dfd7c9e42de6a98490dc36919be84b09625ff99593a81607a21ff99d80ac5de1086828d5e10a45d500f5de27d91c041e03dd348c7adeb04aa11efa968eebae1012c79cea5ea6de996c4f8b2038dd579f4afc71873c4fd9dc4302854a26e5557b5e2cbc0047f9249f269fdfae83388af2e5909f79b1b2df13b2f104f998009dd206140519f8267f284c80fb535634f289f81a81d49e7c2713afdbbc5ecee769c2e26919a5142cee8669879c467d6fee0089960d84af82489f0878742b61a9ecf9de3201609bf453f482a6171d10d7516f0e4c93aa75d629435b71a352f47e9f08e2d6c29838e9e23ffcae87cd3b70886581210558242de111134bb7584a2ab3f9d168f18af167059eb2a2af2a461f300c7518b33c56159d0d978a51a1a096389fda3df65dfaf59488c862d2be5762e1a34bbd652ef43a7355b50fa7e47f5b306c6c6816d98fe1b0f0c6f2e12f981850268419ed92ae5ef29308c7377ebb7d1c9ccc2cac28edd3cebed585c3fdffaad14f085b43e78ec5b1274e910dd72e11720a054e4c117d8c4954d120703aab5f42ec2c8fa5ae455e170f61d2e59e288d21721822ba9a6d0684f015e36a60cdf80ec50d28b6dde4d572dcc6263cdf015e40d50374952f8f8adcb41e8760b9de7213825123db2978483f9d22c1106894c9e850c4759a8dd4d592cf8e3d2b964b1ae1a2caf9571701cc2eac65e1130bea9d9dabec0c4498f4300c721491b3aaf95f3d54cd2a2342750c8beb5c35c289d96e61ecdf84d482237a4c233eb4c9bdc5005b92afd41fe9d2bbf6fafbf3b38cce7250f592fbc52c90adfedd5fc4a2ad5d734c3858a0a06d553a0de4f8586b8655321dd980652a9bfb8f1586fa61d3332d3948e1b5738ec7a2a73542aeb36efd8ce806025c2a29fc21db88138ab40109526f1514ff1ab0d35d9f323265f4bb09d59769acaa6288da21b3f188e0c62f4c7307ab466a6daa1518cf375a9b037de6743700142c23d9f1f11a01a78ed735104ce10a45f78ded08cee6d4e0cfe7d5d8af32e258b43c82de6ec22cb43aaf3ec33ba4b19752d4f8e5c04245623e415e17b55149b63f5db00889d8a451e158ba3e867290d6816f33ea5f10d54bbfdce0a6cef939db5c694646f85b0752aa6428a2d05d2ed8395e3d3d476692809a7b044f0d4fc417fa741c5f837c609934722c0bdf518599a7509ca75b4ab4539c6cafe484a9e9610bcaee2d9c17fb872aa0c23d26b1fce5c043a0d174a5c90c33e3e50fcbc34438f15fe7c3b95b637922d2cd4448680ba54a3407d3827765301f7a00b8b15280a3a4fe5327a6579c31b81c737b192c0d0eed19486bbdf532d4adc63d10e6e79683d98583213522c0c08c1528b88f51dc9529eaa5fc2e1959a28385a5d6a48d25e891d8b033f378695c0eb15523f02267cdd728b3fd08bb42fa3d8f07df9cb5aefceca2cdd1a106ec25185b75891e00761a78160dddbe03e88d7597fe0613a6e3486c341343f789d68f7f2e372aa7688e9bae12136183f6f7a92bdeb67e881bcc38582ae26fac4f8a9329fd751e93331d5fec6c7ac725098341daaf34b37480f8e9cec1f59c9095d9fd1354461578aaccf2843249973c4c9ddc9bafb33ce3e0c4d098bd079ea8861a7f15a019aebe7c62b0d4e242679592eafc949b84f04d7e16b89b3da40c83d607c218d2c374e74023a7f44bea6810e14126926710e5929b84f8cac7fd6b59fe2c936915d7f633cab9d400fe0cb3d2a1b57a5b34e332395453172e9e05708d5e5b5bcc39c1321df7f071236380fde011dd26270500aa519b2731c398a384696c81d06a3022a6293b5f7587d5ef3112ecd748bb950237f4937be3b3eb206aff085485e5dba38446925ef4ba835a196b0cc0126f1aabd4b4c07823a12ce845452d334582f6bfcb6a1ba5ba612b9175990eef9606d6f47ae4661e97868a89b4c845f1bf950d4f98fbb51b1ce3305a5f893dffb315c4cf95900a0c2567a19913708405c0042e8dff254dfa600fd76c4a242dbdfd08cc06eee27fc3a2f10048996755b18b67ff419c1fdc04abe3c3341c06922e83da7997058857f7e79cc50b16018f309056fb7118caf53fa5061bffd8bfb35e9dc2ee93e73f235c1ed48742350619c3e99fe321c4f96731288234f801a076e7ad65a7944519f70dd3d4314b5a5c41c1113889da04f23db4c87915e0124e066ff9ece35104f7217cd2ae131abd9d335ee583b9abd66b0b56719e0bfc242c98ce6cb8ec78bbea43f6a8667b20ec84037fc787dfadc9c0d986f8d9c4d4339d0ed4bf86fe7e543297569c08a71e928705bee168d2412a016dad6c2b5345a51df60f9288080b07b844ffd9cd41998a9c7c7ee05d5e44d0807880acab4b39cfaa44157fac4a0e6ed29938a01fc586b3cb3a32de53117aaf55b650441a8b4bc2eb179fcc87e1f50105c177cacd33b9751a96315a4869ea779257f4cc9a4865b53ae60249db0242ac9d83e408940675e22347357426d14a74b68d1a18c788e30f35c0706a322bda97f2069b571e51f28e3c3c59588d3dd24a8de4c11af3a939e9a0e60248eb9b323fd064f08e88cec5647d2b14dbf99473c914cf2952982388d60b3dec2124b78d2119210d326fae7e2e4988ee40ea7f138925e3e3ae884849fa290f00b62965ae31db3c2bf13d05daf792a7f51302da1e2719613bee873331678720367c6ef152b7b34243a406848074f16ec6a3eb40a7588a6c8e1f4de399b899eb807402fcda1fff38b1580eed0f5fc0b47da9d526e8a4031939f4801d10a3ae242a67e66c24116065849233e116d74c9d8033354908aae13a73cf137c6ec0b40fe464836345c17628dd7f586bdb45a4d6c2c86e67286234e548e46628595d2e87ea12ede97cf2c652e54d99da85b20c150309211c38717f31e3517a0ed4f294d9055bd3459e755e2bd1bbc1cc462fe1198a287a27165ba3c6c10a59050f91b319454f80e72f39cde84183979a6102333f9c403b698f9d3fc01c2449bc8a46d3a469210553b227898ae1036e5ce814b4cc73c8a0d6b412a79d769b31f7a1fc869570f75c4212e5dbb6be6b4dee99c5b4b656376f925d079fcb70605cb936096d0beb93639e115ee4810b271ca59cd55edd4737e761eaa7ed90a1118621542011abbd9e36ff4a9964eee76ce62b6b17f7b1dff4eec31a7bbcb40c497ec175a81dce25e7008932facbd713030ce1f75030ddf17db37a56af37c0ff28ca8c1503f526384ee4a2e04fb1081efa6fa34d1586de26c6d6f43a99b08e0737523f9d47a0bbe20070cb23cf04eff3294f8c88df955c18ea2cc91b263a9fc7026c568a586b4c1ae78e6482ee154d1d10c95170db63f19dae95cf08871d959dc56f2837cfa66a9c8b1d61f012480822d831165960c4ff9b9681c566472bc60f5fa520199de14d2bdedb624cca0a9c8d24ea21c26832b9353a48729affc205c5fe039b9b1ef52f305038bd97bd4b13cd7b3a247be40148927f1ed4c194408965c0927b7a2966d17a4a178459b16c01ebfc4fd4ae043cf7754bba6a131c080c9a2151799a1870da1b79da09a2583f4b3147255ee66c6e23ff9454b20ea6726b97f6614c139a376a49cb8fee32ed2441c0b4154da16ad885eeb4e2631b5da6c40d44ad475baeac7f2ace1e7901f2c465957f8fd929f2df134c6ec3b0b7e953e5d0ca45a1a2a43480033b8811c3bee2f2b29f62ba5f36f4c293299c9bedf24fde4b11b4ea9e83001976ddf4b760a07f4097bebe0a562f666018eecc31bf67823dce1c02d7ac28096283a0d6342a8f4a237a3e154bc52d98c381082ebe1fc17aa6169ebe3af05d0aa35b2cfb4efbc6a88c006471847dd72bc4c3057498b1c79f5da2db6386586ea2451bcd500983385aa7233440fb126f352068b06e1b50b477579e7e81743a56f2398a65494fbfe15409f4bdb829b69e255c26102675c3ddb8c3d1cf0805ce6f86251602252c366eb4d3397767b4a0a2279118b4a1e9b34336dd679666ae3ad9f98ce959b3ee4180904ee9a40c511b808025b18f71c55d43457b8340358e3e8aeaf2a28093633020d404780d89b28eb12bb999098cd4091f2cc2dca0bbc39e5bb07be190c7bb2889264dba7bd013601dca77ed76debcb6039a9be329c2257d709fa004095c8666ba67aafc0e63f8e0f0efea3718ce2a1c7255f20f8e4bbb7459f480e4b1c9e7e910f99625da242c64180a8a3c153519882b1b836ba19244e1170eb94c5b3145507bbef996412b10a206c648b716e1fe9c085d9f7e660f2e21b1d1fb58ecdf8721cc9bb318067f9468a91efaeed88462584c340ccfbd9608ff46fe2ce7f6b39030bb6ab2f1ed2dd3fb61794d48131a17e09b664753e3f60bfe441597d0125773e75ebcde6f526b98c0e0ec3d59a406d51da3d1222ddb3940f674c98bece6fc8d38531f23f876e598b65307b66eb3fc1d23f21eaf406b6467dedc07dfd10ff36d0849b4262dbb95a172c39251b2c00ba2d109e0e603eb0addaa1a21dcbec670d05917ebb2ac95195d35d0008fffbbc0047fdf8cec334e9df575d3ce6cda5ead503da56e489d90255e0e872d8b0db0635adec800fbcef58138c39c353144012007337fc63f38dfe4e82723ed0b09287ea2ba6bedbe05ddd4257429aa3304fd764c3b47282fe00bdcadf3f37b730193c343da06c80b9015536be084d3e341ce2b225e037c9a0bea2ddb76ef97d57cec9d7f2601802571fcf5b4143aa35183cab80c6b7ce62fa25c557e61009e72ce9fa5898c6578572fbbeb6591cdf5de9bbbd48ce89f1d0389d5ae22d465b6759d17258453577aeaafb9c6750bc155a29f144b6cb68d014c39ec1e4099d201726271e95ef2e811eb047bb971c1688fa4e8d6eae1350c50dd60c3e296c1a2287e4aa14e3de4c5de840284a9638806f3d0206871856b14f8bd48747bf1ebb7cd5283b09c94208b37a71c4cd38144230744ff9d15054e5484f795c05c7d25774bcc89fee821d095070db7592ddf18a84aded3b59169696daf60c997a2a8ad2847cf72bbdbd0232f73882f8ccabe44aa102d2fc61efda093fd21e7b57180796032d3c35e0d684fa1b1e0bf83fe84eab8d3f865ca56b54128c8328f77cc936bcb65cef8ae279fa62085d34877578e0c5837b374dd106fbfa35f955c40bb5a5f87ac93d3c1ede77b8ed85368e9c14ada827a898b2b900ccb1c7876884e3d3f5186c5f31ddb8d2e9393e277034bff8a0f91dccdb00047d80aa1feacccf1e1749e5940c5c99f85eb4ac1c0256abbfb4264d07a77bb1f7f3d7ec6a6883b937fdee1f027cbaeb4220d814e06b66373768c580f988796132ff93b7e9f2f318133d286e78773f058d0a8d5b46fab4ce94720adc581fde2927fe0ce443577c50a7aedf8fe75e21d98eeed93567833bf8c878b5b088131128a7456b593c21c7f0b8c80dd0012cd65dbbcf6d3f132e097081f1cb04c9385e1444cdd836e2f019702fc856a3ed1ad0188af8637f830320670f0b331342fead9091f1edc33c5128bd259d9a8ba89b95afd984e6411f7a2ff19440657edf17c8fd071a119901de9faccd44b07bc9197b5e8d72bf587a583cbcfc096054f0f78f0f43686ff9e7e95457fded4ae03359cf0db5a98844e7a5b563096f92f0c96c463209a51cde59c5ad4e3180628b7a2912e50362801433629dea3d3a9e41438301fecdb9150bed31b03751d5861f604f33697378c396867988f925953a2994b93cad955f42f8f07048a81f58d47f3ed931cdccd80ae8ad54d063ea0a0c5d3d19edfc72d777276cb652d90b307f2d1021edf9212542e54b1dd7c3d2efd90f81831d8801b68850b3471536298481492ed68d2940a140f8a2b4796cf91d151eef277e2eb0302c64212eba6fa5c0b38a1570f309a2f79e4372d09dff4ca193339f9686c635a7cac4929764d1fbd1a1d09b29416b72a7d3bd0e82567842730562cdc7e6ea759d79eed85b0ef9591761af20ca076700e276985e25fb12ec662f0db86c6e801504857202d6b50facb149a6f445449ce14f62480ae455eafac6bd218873b10daaf77fedf387a71751418bd7a9df80501829ebf41b4edbac835a7a602b1cb334798d0bc7f30eff00acd3502fbf153a91bd66e37a372ed3e58ba073d2a06dd1fb539cffa98ba07cfeee901e1ec28e121069922a5152793c5cf9fc180804a0e726a8a308167989cc9654b3805762c194a693acf66c3c0e34e7dbad11850f1bb6fe1d408364c4cb10f4f3760b686adabb460bf486844bd2e67eb69a10eeed5599f1bfe41e11a59110451a1e914d6dd4e0832ccada086a36bf620eecec50993f63be23304cf04a0cbe725a35afd39abc971d049b65e0c4fb18f285d382256260eccfe056b839b8fdd70b2237f7b060b32932aecfa2600452425b2009bcff687be3814dfee877b012bafcd10b851a9e44fc1fb135408e9b1f9f3a910bd0c1dd88c9496b45fad4dfdd97bc51e86265d863cbc328b8ed8c18e511264f894d4c668d178b5d86811e6bb726677296265f5e494fa9a15fe058628c06f87eaf3bf88e0c2036eac63e0c91ef34f50b0595bbf4072f7224f83664695867ae1bb678015bab4035e2e109b949ecc6d66a341839dc7a25d8fc84bd3c739b90a1be9e3ff519571d624952e46bafff3ef676debc25ecaf377055694cacbfe9acfa7703c946da7a0383ea2eea88cef330c855f7dd8a107b31e45619a4a9c5b15cdadfdd01a5891c289b70372b6837a44517e56066bfaa205928739260e66cd3fa8e6925b1b1fbce097d3e0aa33eab0b7c5ea4c07e422449081da0cf1771b30515bc63cdbe82635e3025713ffc0f56d6d1900748c3244887d7c68d9b94da5cea781599621673046ff43f8ecbb14e53ea300921b16e5b03deec222193f52b3b325dd9ad631c6e933cb147d83cbb355ced395242260f66e87059cb03568922dd0c4d4b20ddd90f3594e823af705800bc4b4ca4b46ed4544e9f3ec5dd01fcf74e28426042237b53c373fb7ab2ba08d7784b2ee8dd6548c3a4f1234ae6b8e3d630b52e61ab4f8b031c349f124caea74ad91ba16e1ce341bda6abde7b1ddddcbad2762666762cca329da72292876967ee170366736cda2a40e9e3c2b3cb523d29a7c4bdab1b8e130d5b493ec3d7dea6a920dbbeb0e51a5c55a6605b7125c8d0ce45db87ed89582803e338efff9c748e30ba310384fcfe1af1f7268c5fc5f7d3cc280d6c903cfeed9d66110a02af41a2661128815f02498b1d48b0d8a9eea79a54ddea152b552b4e566889b5003f6069e273e49ae9437ae5d59ec71ec0cb865a135693202bc8c646a88256a0cf712a8b1d3bd57345b148cd4e53171af122dcda5d0af1529621a0edb20baad2accbde7b45ed1f02433195802c0af09bd866e6fa8fdf9e2ad2690dac35c5dda13f24f37e2b91a3b890bb53814b40e32a963771978f5264235dc7f63fa82be5645ec084dfd211f57c5693ddd32ef309ce0526e2d226a56f498962c4b7bbc054e8170d8f8855dcff4ac4245d64811a2e580dd662575bb33a2d72973736a41bf55830ad71148335787eec433525e068093e8cfb2bafc5b9691c3ce2397dec4b1402682a8103b420be13475ad71ba9f8a9aed8d94f7f7f5061dcf987eb2d3c592253378fc14d0a3aa0d10ca2cbc59fc1345ede6b5e8b9aec74194974712a33a46c853cdb52d61c6343c784ac5e6f77be5263de5ee219e9bc670346edf8677756635c386ab22913393c9fc2ee7916b130897a55e2b9d322a1185212556c16b92e45ca748e0eb0b0422e27503822524a7c9d23db635a49ddca2b07864f2ab00c0cc0c97e212e2d35db65102fb97fbfbf135a6cd391078f7fb81c85cf2b7f7c9e36b0d39161ce7f4b5ec9a63392126d2f3b1085a10500c2cc860c8b24ab5c14b996467aa3c1a45cb8f48e3d9935ec51df6973fbbeae3ff0bd2bd1f2d42386d183c2ae4d966796a6773e4da07b73b34881c627ecac67a7bf3ed229ed927582558d1c75fbe91588ab00b2c160de0a456df45981f73dc98e95be68899d8d13c7acf6017f02802fcb034e168560d1404c5ff9eec40adcd935180b9c33ffce7eadbf6e571d741dfd3c0e92403a109ff6ec9b7f70535c6df2c1e60537fb3fafe6c99ca6c8b0acd6c2b6756b410630d3b060b3a58d0937a9b042b6daace8bc39a0f87d122c38209d96553ca6c745d74c6ea889b7dfc20508033b81c9aa083a0c93874038905878fbd84463427651a32acda86d16cd58f92e2bdd0d97a46866ebd28cf428accd2463049144cff229dbf3368a6867c3c2b90efd2dd5199fa10f547bbbe1e50e0951c1bfd086d5947976d04c2fe4977fe644319cfea2112622176327f4e129714d5dbe33cf8bcb95237a7e510e1668fb7f5c51023ae3db2b8ba49ddf9f0eca1efec402cda3c2d054fd682187693578281022468b3a6a604adfcb66b6a718344fd0d5a1ae03173449ab78a1cc829980e0f6dc5d91f8a07d1ea32c31b1ad785205e776638523f788c314347c11957bbbd80b374ef397e5d3a31a105daf920493b9bb3beb701ac738bf906b4fd03ccdbe4b09f923e5551f7241f43381cb5a27602af570624c14db3774dbf9747fd800249c192e67e9e63aac9cac9e48057fdf155a577fc35bf9134317099b64b85a8a5744ba9b440872ef8e6d7abf35acbd04861c2d59b53496632119b0007a89312cfb97cff6d4f57b2fe35793677261abe12b459edeb996f74c30650a0792119ef9700adf973c00b3537aaafe3c9b448707c100b30a24d3f012d414f36cc291b6af2ff94cdb0f76cb6ea6b129f466b702ccad0fd6d8b99835ed8e6cb3b481426c2bb11e57e117fe590091c4229924c34ef030c1144dacab32ef4fa807afda534eafe958be8892b6e843f0deeba39d2f711851a4aa693feea33fc437744459e105712a2160bc5eb054c35c35b424fe6c8760c4bc8f925ff452b7913b4e416749ee48386d36e3e6e9af29d9feafe3ccf9cf42194b2d24774e9da182a59e14268bc9eb81cc2dc0cfc232d5362afd0da1862d12b0264a62c0a9ccce61648967759e0359ca7f0ddc99e25682de13876c551f23cf4336f5d88716008f9526af3f14a99e1514872ca14569d290c9d9f36f655040820b1255fa0e97b3f80e17417959aac383f8ff197638a47624a058f704e1439c13fdab94e900836fa3911a13328a2347507b0cb4ff63c686a122e23b8ba0ee8cc21966aa22db1b92d58c913aa876ca8a9dd60853a4dcfb760ccc9e50e710b4339eb01bdd9344f2a30cefa95317b017965bf447100b703a07451085657c8df483679788121d675c2820ae1070248b46284036925edb7a06132ce2a943e111b585123be604d8ecc0b1777af93a540bc5f9e1e7cdc5799c65d7423fc518a862fdc698c5be32bac20fbb1d0f2dc41d011d034319fe02c2154e064cbd204fc0860cfed60aae55a8ba9230780f31f8a9631c9acf94ee20730242f7b695fca20b002086a4e04a300166947cb6d7812f754e5a83d5c76faff07a0072b7500c7b125e2e2ef15f6bf2498f4b7812d2e83149ea24a50185e09ffe5078f9da944a60a391365c0ffa96e6055388fdcda03eba851ac03e5d736651ade4aa54529d0612cbadc5c4351d28a5b7dcdd890d0dcaeb5ad2d8b048e1cc8a4466c149b577aaed0a357792bb1c26419fc750c681e2b5024b1392ddbbc84b1c501fa5c622ca68a4ecbd62740e5f96c65387846bee10b14382e177af167c12bbd6756a15e64826bcd3df531406022af9e179ac496343dbedd3cb7532cd23c6f3f429a5ed1d0400f42eafddcd34a6776372416486b5009cba5ff45a70c3104aa7eb6a54d66ac272bb83c786e4e3364e1e786f57083013d89721fa7e3a192e1b8633c40cdde621615498ff3c5f1793b704a27bc6f36580e6a2a5c1799ecf3b2fb1c6a516ac4a12c45c7ef87f19bfc750cfffea3b64c34e9d7fa75c8b7219b70d92c6cab6c13311850d18eefceae8df35d00642f3a7f79a8de06605c93fc23a9ac45a419e4308a89382675c9e79aa7833d9f52f895b26b0309938a7af24520e061c984d3276a136b5d094532d36d452537325942e7e7ff4c6ceaafa25ffebdcfe1b5b0cd7e1fa084ab3d18713cc8295edb83c64abe890725728f5459c01f9d46595013c41fa752db2c6aa6653bdcd0552384ccf118ab95d9d232c06df5ef249132161791026c1500853755faf0fa6caaf099d90526e7859863de08dcca5a5139b1ee568ae9d9e7f0093971d883122b9900b382b612094d05ab9746b332212b5aa40d02003f66694b1a77f52af97420a43f2df22bb29a4111a4912bac8fc8bbc4251de925f96561df87e38875e6b9fe69c1053c7c3019993f1ee0ee11504cc0f19810b0a43ab4fc2b76e71bd1fac498cec8a12509e856d6011e9881304cb23cbae4f84b5aab03de24141a7fea7da0389e61e37497a878addc42b5c8dfff061727bf557c580472b3351ace57e162b1d731f1125c5ffef82e93bf7bf6b7b4de5491f39cf1de74aae13d6e1f1fe30235a49080046aef913925db858efa3012f9244bac90106393713e26cb871b5f27171c1dea719074323faa6a5d900409e28500b0f057a3b576d045196118a634446f23802d29acb8dba93922543e846f905a3e5b1470d30c9d95b808c913a6ae0178d0cf0d8481e2cc478250ca4b44e333e3d6c0dfd7bb8cdbf49be5833198708041afa67e2bab4ab2779fe9c34c4b5f5f3be759a5eb9d9ba8d50671bb81231317858daf82ded9663245d1dfcb4164ce8d738f02ac146cc7d008c1fdb7f54c0f4803a0dbe5f8c3e605f2c547b89fe3339864ed1b2da0bd991ced317c40376c5f2a354e4627881315db417eb20a85c08d1bd5c4c6955273084307a62b9e42af0f55262e54c5981a6e110df34e8e1fa0b9904628adecf83e4fbb2866f491c85f8106aa1030aa96146296543ec21e02945003fba134e46f6e25f393dd0a6612aa102cc58965e9550e48c389c1c04a326470cebedb427e442f8b848df82a8c5d55eed0d1446c6e5d6e0ccdfa2d219019ad3d400fde350d690cf04d083844adefe5069c7066b0ea24854ef3f5853328d7bb2ea735b370eccec5f0f777a467c98f040c423fc4c3df237ee8b1e9cee6c859c860c0297667809ffc0b73af9815b01384834fcb56d3e782ce11ac9124345a3d34fc22ff7e0c3149d66f7bc8feb9743bb54405ea88902011cfd4ed07ee19c6b8e6de6dcb08e2e86427e5e675dff052652af14d7af936f032bbbe508c54fd4dfbd16921e7b1c1471e96f3a521b911e32a5888b5a4083be97a3ab843a1dacc19e60f161fd84c10fe94c9952829da6979a83ff25faea14df6f6d5c876f3103a8a93deb51a3834c9da185355a8b189585f7c71ca082295ba29e326e60b9d08ec5386deca533a03b903541380cdf616135e348c6b09a5cd4a074d7c2e3d794e44a0c4ab8537f3f53b12ad2c2a996c5ec0a1b6d9a25d920a782bbf2120262a71f816a66ffdfedf5e5dbb868d37517607e880d697720a0119077b19df4e9e8709a177bd25508b00c6c79d4b1c1ca7f607aa7b18e6f93e40c8ac9d8705bd9deec9626cd4aa06491416430f1e204177b62d1bd18e9fefa0da4d6e032f785851dcfdd3e2a2058b1811aaa976c30a067d9accbe4504abb7c2a39492889fe518dc529cfb481320dc05a785cdac3fd225936716b149620ce252d079b0f14a947bd7efa17868935b6483516f7653fe108e5c1da91db5a58a3aaf920ecfacdaec9cb997d80860e966fbc4c2cdc52f41fd3dcf8bfd70fc4a28f99128b1b5e0d412c91fd59a9deea1d9b5170e92e1ba12064847d72f9ed9a58874ce906252c6679851b735cdd5f3f17e35e0ec6117e83b9718e85bcfb608428bf03df02e2950a07e80844403f21b8b0e6ce349c5ca2c260aaa780949050d2d9fb90f890e0d33b4a8263b0f3ff72d06ad34072f3567c85a6e301d5b374d9a78a94e5a3523735e3f9c2db0734aa7c7c5787879687b485f1fce35966c742b896aee8f0361d98ff221b58f0746a0bb4ff1443b1f21eb26e1503187f52903a842a278d3cccffe3328876cada9acb5f109f1679265c39ff58a2f5f63162117f63556389d00b7ddb9cfe0346c62344ac0c9f130294d0178425920cf8c5623e09bd387d8f1d1bc4a7df18f8f38f20680ce2734505fccf2d7e13a0fa68ae5f7f949346bce6896c992cff605197740913534fc38ab50d679c92bdd04e6682ffb8581c3de476b13bed9e72cc7807e6ae6a0f0fca5cd9a03086c1a5971991a7e0a074c128727a4fb8e63240df8d4e87ba2077b50ca0c1de5ca6b4a09fd2d16022aad1342b0dfcdf706d13d433db3f592bb467f911fdd09969d1fcb4fbe4ac156a4227dfba8999642bbb7239cc6bc02d4e1cc5ffa47642171b9807bc2e62027befda4cf5d0b994e36b110580f40ae0b8505bac989738219bc3deefb4fb85037482687f8f196ceed78ef556907987d952b042016046c57e36e47e9d5e4c71d5fb7bd2327809e4663d75a402c9b6d89857d2d07375273e3142dbfbd1c4c658fc64e2436ad5d8a714d9a626b8c6a26c3ca5cd8b7bc75162e5ea63a1644e4365ed6b16107b8462e7186586b0b2c43f526f0656c544b70f5e59e809462b1e00853cae8bb57e39c55abe0e2435c53bf68c0dd39e3bcf6da3250f4ad1d47f1e9813b22b109c9f2ca6b06b362f8a0c11774cd90321561ea5633ae94828698a5b87a338a0a1ee58aa9e3759a68ebce835ab5bffab1cb564a704c220e8f451d02ca511d3a61071e59d7a638c6eba218fd186a9abe7144e21dc862d71cc14fbe19898710ec7321917cb26df10994c2d4980eae0c9c512cfaa4068fed52d0c57c9dc3fe75b51f333210839a37ff90e8508bbe215d7fd50cc4c90daa00e10194fb660fb38b9a85be80afd5232edba10ab7c98dd8e5ca4eaa6fb7828b529711682b92e5c980bc877a83b9d7f0b848181110e370bee7ad434463f90c954705032d56c0b86305b83f61642daec190941c8b687f46d70c0c0cb4d627c0af930844189d3df40c1ac2c5e0f19e598bea0d6405cbb41e35fd850f26bec7aae8873c883ba0fe69c268839681796ad11c090e891f6cedb944fb4118092d8b4397dce87fb4755b1ac74217d12597496c41370c4653df24c3df308c2c9605efcaa3e8fb55250eef97ba85bac046d6aa82d249fdd348bae7b1f6e658b8072a2aae3dc22920e419e656810cf8f80398bd4dbabc4df89b4b46328524ca43edc03677156dc05a215d2c504db8b28e876a1797ae0b57de85f634151a8bde6002948ae1f540fe4d5f53186fd979992d4bd95c2fffdaa0d02596e17b439ab392ae0f50e6bec0af1ca9668890801d5dd1c9fa69c06ef9fb04b9b0260e8279bf93f1fb0de27d60b0c0d07329f986aa78e6660a8d0785903e7bd71fa74fa1ad87a952a22ca0c215d035817ef038624b5445a326d84ca96a7486058add15801596906b1d78381b9092f504db0008628d32fbca6471359d445a248c0019ff3b8f6157b7b45c14c76ef673a927ac5e425c0a2cb71cabda5b2f323aa037543ffe1c84c1833ac61c016bbcda50b219bca8b2d210914e2e6aaf5483c943fd4e7c68228b26bf751cb466667a66529cc4c19812a8306a25776f5ceb24a601baca327cbaf470a61955474f3d9ae0aa8fedef83fd62d1d1e843a835c3b993a00f09faeadf015cb28d01b01fe5ca49864b667ba0cf0ceea8439d39cb2a9ce71f7b3a5a9b2774141bfd68baacae775d48ba40cfdeb1d1018f77ec090a44ca5f4672f8a1b6110421edbf5014dac6469e980d3e207f43e25353c1a7d4d010974e145a7cb8a5e05d7d79c7f4eeaacd410dab582adff9b9ef8ffadda3682651dbd8ee69d60464070908bec020c622cc00cee15aed981698ca7d413b83b78fc1a2e6c85bc11787374342b243093d26301476c94e671d7008c7f076dfc7d5ea5d765cc4e82b0fc85ffc8c6637909142f4f3fc943170002ee6e4ffb8a3807970aad74a2aa0faa7a3bdfe4c925757497aee9bd3c1ed174e3aa9ef62d6c8df963d0272545e3a19aca824d7ebb9e22d1818f67e98d5dc3e1b3c45fbbaf8a5e053db3fa77af51d975a22ab70321bfc40af2e3828542f4236adb4755552c9437ce84d39332588d33eae8fe5119eccca99d74962a3c9f09ee67eea15f24e58fdeb5536db348f335c67ddcf19dfe848cb1bf5f2be87f5641032816376f9dd3a983822463898a9ac4fa1c88294747715251561bed2933c08be3c006f05a7b3fce71c179e9fc1bb1de53392ba96d0080179c058c441b9e370714526fe5a01dd1ceb43fdbd75efa6324d799955a4cf9441f50c25e456c6436f95fb183851782552a4e3ba51d2597225b8f2b84dbd01ab4bebb18a49712ea84e86297c4be4e394a3d88646bc8da23d09373a2534fdec05540253bb9435ce0f9c1ee444de9557f880d3bc81ae16dee47085dec4022057b0d3cba15569f542939f58609cdacadf9d5055a9b123be4fdde7985a8a4eee0d37032769527587fa1e09cc7122c8323d1c7ca23a98dd63e970031d8545288d6224c9cc034e60731a9212a89fc6704e67f4ff3638366168abe7c5300fa1dc6b95f56e079cc9b97595750031d8d346244a5619031e924c5eaa8ab82200032737621f4e5684fa19c40de1ac18cc6d416ca42dc59e52b07e1a69f3dca3ce63ba55a4c1a3cc761148f83808c62b840b9252ec3fbb2060b9e7c75f6d94891722056d24771bdc8806d3c5124153b2e07ccbb16b02987c0e0ad70a29b48ddba6a3201ed15f4c055ce2b9e3feb5a7ff082215587c9d891c9c02c2eae1c5efd8625c6535ec57be13465f5d4b5acff1945f0a7a11b0e41546b1e8f3a3ae85fff4ec1c89c2dd44398d04e7689464ce2c631929b4652c9e7645684a0857d6c2cbbe2363496c88a5d05050fc76d488ba7f953959cccb5ce8e062c203df001a542cd5e3ec4fe97eb15619a9149022c9ad71b9de36a3a5d257a0d3ccca684666a703ded560df69a53ba546dbade63093831c306d04bf0ab91ef00f96e22edc0e9745ee374693ea38e7c9a66f766d395be678036af3520d65e817f05c7a9e4b5ee15decfdd2c40e6a75789c112a2cd02da7e7aaa27b57b50626a8f989630b47550d39efbdf17d08da5e066039e2be80c51a0221853d0d840fea4e5d0a818de468b455d1a99b98b69ba89c118ff0bdcb250fbfaef760e3eb3098b92512e0cbf2cd32052281727ce17a1d55824fe953029190dab087eb179c5981c1e785a2400bcacfb5fd63025983d3841b2123e21b529bcae8f9162068db42e1280053dae8de583b3b713f2f78738df67188b7a01f40f0918fa1e51ddf85757ce251e903bb996d87ae4c845a420e6333e0a3c292521190d1318d4840446166d1241f4a837b47179ea554e144991857037225cbcb44e782a9c08d58f166da0e7dddd3b05ff2f7cb5ca53e445a3e6c06a1e3e03c7912619a8418606b4b5a6d8df274ce2aad313d5ac4305024352e9219046a0ec8ff68f6d7ce075490b9bd41fc9940f8a73fadf6577a4742f08d0524f72500d944e950ec1a7b45d2bdd46df4bedae251dbfd53c97a4d3c746de80214c46675522ca77fcbb13500597ad629d5625f9c2ea4c38d1334225b00ec20b9127dc4a15d65f129f4f95668768b51b02341df16c8430c163878ea13ed24b683b993732cc35b987c78e2bd25f1fc6ee4e152bd489baebe997a247076d380c092acecb79c91213a29ab370776cd2c3b3871204cd156e2ce3ff091fd31b65cfb824f14f400f6d9bea7894b8e2744e60b68cc0c05084c024e1cf64867a132f7429c9411fd0c9e225f4828bd9f523bdf3f5a8c21b09dbb8f4770bde3a488928ebdf0ff3d2c300bf6f4eccc94c5b1e4551ef25d144cdf78fbab3fbf37f01160ecaf91b153e6a9322575c082c4eefcfcb41f606eacbc87e9ad7022a0916ca782ba8a2bf4f408d135a1605c85ba1e211a78747ffbd1ba7c36589bb7cd801166efcf55a8e2e9e24bc6cbcfbf1ede191a777fb4a6a67a39bb92498b8d06b287882c859d668a7757716572334aa19f4624fbd7dfb3cd42a34f6f12da60c850a21a2e9998bf4256b961200e57621dff71ff89860ab8f1388b889e6836fd22f120d9f42e08f4999454c0523038e056404d989a3dbee88cb59a57efd7c5bd3827bc85a0bc8c02953ed0d7dcaa108c115a48ebe00c19ac6a0b7daac1b1468d6d8252e93f061bc381523fbea0f248d4de1945c704fd01510d54aceebfe791ff1a44852613dbeddecf2e7fdff1e981cfad3c615f65ec413546c787497f2dadcb2022a6a2aa463bdef07ce1f303b06c8413357261f8d662a71d344737504ba69b8be1ea8252e6838ca8936dcd7041dfd6a646d1cc0b12436f3c89a7feaf8f57fcd34f860017798b1cfb22395d2f2836dd02988ac37178f82e5c543a98b8982e59a0729168c866526ce53173da0b9300e6c17ce85a74032f052a82142b290911a0edb93bade04baa6e061678107143823fbfdbf3c06c0dd0de671ab6567cc3ee3a657e7ecf6d10acfe4ecb89f9698307623643468e0ecb86d9d9b18db9ebd3272d674b43d42784367f2454b3759ca6bfdb30a9001d8b0ec48ed1378d40a33524c3a81de9f331833c8a3e9cee970f651dbfce88b26ed8f6a4240fa2907529e11a44d4b35483310d25e3c006f2d7d51432acc216d43a4de48db275262f925ac1669a7e6e9317fac48f5a8c60620767062e78bc4d93c3fbd5dd47a332512d2fda7447f76a7badc77f862f290ee4b89804280f591a6badc290d3e128c46b681e69d71c3ed2a3b6cc4c530e0479309d6287b692e08529de31d830126afb4a4652a8aeb3303d3547d00b93b661edc1b226d6d79a7761fd1e43264a090a0d8e9cca3454ff74b8257714e299ed3c6f5b6648b9c26bc78ff09121b9a485282e4c5c2b240129ac0819c8532b0b9cca4472c180836f7285d09386581375779cb546d4c8ac6043b3255ab1035c1d6e07647f453130bbe2aaf04e5b7bf49d0cd6474c25a75c67901a23870ae77c32e61af3ffea207f8749100649c3cc054c36691d4127bfc7ad18e1f018ed75f0609001dd83e41d6e2ac66964c248eccb2802c378c9252146c8674ac23b81fc13b0089add0152a1141a6472370df94ae57e54ab1c3a26c55f24a1dd0ccd360e794f0a63987a08d50ffb84a76d3689ac1211ea5d8917dedd6abf81476dd0ac1c7017aee6abc9ee1a41e6f5edd0b89b9d99983857dc32914abc1e20c29c2c2882c21923c947c980e43393500716ba97ec56ce33fd4d01240988636baaf5aca649f87fe606606fecb888b6a7c62dc99688774d648982ce1cbaaa3282467050963b31d95c29f55d427d4166b142f767a695ee5be116bfc9455f405c12a1c3e7dbb14564f94c73dc0a7af0aba2b89c172b2fe5f9c059c0a51fa792481ba1fb6f97faed51fa1e1857eafa818290875f34d876e2a17e5adcc7bebc7b01b941b9934c2e4942e9064d836f976444ebff2dd6c52f44fd9132f4a4a0ab659f052f5b80e6696d217568dea33b7a90302d011384961f7809b0238ed7e26a28492676e4368ee5548a3f5be6b7c3f4342ade03db48e82bb247dc95b52638fc38802881d0c2691f2801d0be3a66700ca06e22070d3987ff4d44db8f952832f0a74e43f77b7874bb68fa67435fffb3a5d98b034ae7766999e8c9563366a70d86110deff01dc7cfae0391be8ebad2fe2d816a06daa82ad16587e43c91dcad74b672541420d12c50f82f737de005ad7235436f404e87ae5b3ac732a92a34bfa3c20f267fd4c7ab07947232044ed716b52a0eee6bbd8388d7e4d15c904a4ac17bc8a7c57032a9e8e8a5c30ceba4a078e335cf0db5a938e3233d9e16a503d741755da53cb2b848c86c25199e1245000f93043ed0b8ca80200a4d1cd64118869789d5e90f2b393c38faf9492f91051c29563e737b8d7c5c704dd199696ecbdf796524a299394015b080f081b083dddfbbb0e92e54cac171d082fc24500c43d3192e533573280171d8acfb0e33fc3cc2ba48f17e79700bc38bdd079718ab17b78f8e26c5abd38b570f0e2740200fef30889f313080d369831481685caef5a062fcea0a9e47b518a91645422baa2362f4ad98e17e5171e456408b2077fcd8b52a900dbc50e7436cd8b9f6366bba86dff1cc9aa4168760028e60680a22e0104c59bad251901c5cc0050d40b004700c51b8d480450cc0a00455dc4a5116dfa2180e2cdce50208062fe0014b50730a84d3f01a078b3f3880050cc4440516f10146f68363a0028660380a22e402b1b6dfa4240f106cf7c4031f780a226c06bb6e9077961261628661e50d41d30316dfa406694857f80621e00286a01f88cfe8ecf70cb0728e60080a2d631c2ad4dbf87d175ba30baa20f0050cc1c80a25e81a2ca69d30fc7bc012866ad0128ea1c5054c1367d1c50bcb92ebaa26f3300c51b15088a2afd81a2a6ff44f90614b3e6018a7a0798b46d40f16657998cb26c0d28dea87480a24ae70045adaa59e300e9cd162d28de6c51dbe055ba1615499d99a40227a67136b3e19bd31a5112b09766d4d42475269426457369ad198cc2742a8f5dc49c9868100d923ad9680924d86ba63edf5c37cb3564a1ecca2d2525a9c314862ce6e4464652c777b7e1177e022761240c0b1386c32d5c05136121ec72fa7c6f4e6540c96257264c8a3109690ae5ebba3f5ca32b044605a9336363665032398620757cdfcf375791ac601a93449c2d933a54db5c446c2fbccc10238707fbf9ae9f6f4d4657f445215949636df91d8d4d8fa40eb67c20b2c73f67b1ba7b752315f46e7bce0eda5fb6a9267b2d49007b326dad71b66912d4a63ff67c516362e747142005d8b283098a40f6fc1f76fec654c294404ca94bd3d6db9f7ef69c2a5a7a983d147776a84a2582ccf6f7917252ea5cabe420875486568656665b9e200fb1b0b267ce13a6e7d3393fb9bb3d6d0e91aef9405461026da74892ec49bd6c31f7b03939e80955ab5beab9d30f7e53eae1299cc4113ba16def4bf92738cb47bf50d65aa5fa705321fa725aedc559dbb8ce4bc9e899ba6bd2e66cd0dcc081812a878e1a9b1d3c6efec3ff9bcba0cbc02b92b327160f2d0e4e475f3467ad73cc3407e3ae38a741e7f3714c74e57405e6a17d24586ca645cec5b99c27c8593e6f61b287522ec63ddd7baf067f2f0777f5f7de7bc3bf37c8fdbff7de7bf3f7b22ef8f7de7bbfbf97e7dafcbdf7de9abfb783cbe3efbdf7eef87b815cd5df7befc5e0effd7175fcbdf7de1c7fef002ecddf7befb5f1f70ae0e2f87befbd37fede9d5be3efbdf76e7faf8ffbf3cabd7706e0de7befd5b95368e6affe7befbd3deebdf75eeeeffd1000f7de8b736feaefbdf73e8de7c1cd206783202c9e0e80fc188000767c0440a7070034c0d10087831b2b1dc20d34c8c1c9c0c6df70cfdd703540efdbb8edb79b8d9bb1e96ab66db3d96ab64df3e0766cdb66b3d56c9b8c6ac360db3ea523874c25c340e6d1641be3138d8d0e870e372e8d8d17a7c376d452b9361db455c964797399ccc7a7f18e3e6ee08ca6b5f6cbe59a1dd49f6f0ee394cdeedd14f7b6e7eddf896526f9d898eca1201c398bd26a85d62b58b687f3de572ad2a702ad5eac55b6f7b548f684ef7d6d39ab6697e76d6086f4a9473c2f5234b677f35e7dbd9e5e605e514ecec3a30ae953953a78915ad1247b3e2467d5714c1ac791e98af37c11d2a73e0179911a01933d35ef3d1d725695c968d191f754c87974bcf7148af4b1403f5ea452b6b703ca59d6356b79ef609cc7e6bd772fa48f3d7ad1c1d81e064bceb2af57d3cbcbeb69741e1c4e481fab248017fd0925d993e3e52c3b8eb011c953bdf7ae83f4b14f3b2f3a0fdbb3f19e9579900f156d2fbff7538cf4b940d3879d63c2c89e1b31675d974be68272cdb6477385f4b94701f0debbafd7547a2dbd9ab647dffb7984f4b94ad387d5797126b1bd1aeffd2472d61dc76934bebc991e2fce29dbbb4fb23000785186b1bdcf80f4c140f3bddf80f36cefbd0ab207bb64ae984882b3b0cbfb22d2071f4d1ff6a5922702d9de6b04e7d1defb10644f7def43e749bd062f6eb0bd773c7ae08b747b9f237db0128eec71161ee9cabee7fd943ef869fab0ef79af81e78197b4ab8bb2b8eaca711d4d1fce0acf1165715cd5ae97d2f4e15e1d286dbbc964a36c94d5a7e9c30101a94fdb7e9551d6368e553656d9586540d3677bfa01e4a2acedf572bd5c2fd7d1f4d9940670f4a2accdf57abd94a6cf762400a56d35996c948db2a7e9b301edd8a76ddfca284b1b472b1bad6cb432a0e9a33df900725196f67ab95eae97eb68fa684a01387a5196e67abd5e4ad3473bd2511a292bcb64a36c948ddbfe7d9a3e1a508ffbb4ed5f1965e571bcb2f1cac62b039a3ef90900402ecacaaf97ebe57ab98ea64f56e2e0e84559d9e57abdb2ebb5ed63a5e9938fa6064a948573d65886c76d1fe7e39e9ea64f069a3e641508c8b7889ff653d9531995ad40fb3494d98083715634c801713200410ec659d9f6736e523c38ee75f3dc6bdbc7f9bc97714037321e321907b4ed6750d3e9189f6a461de3f8b4ed833b389beda554b3bd746c2fa56dffc360c3b1b9b6230c36178ecde56daeed68dbff1c9a4ab6016120c321f3641bd0b67f6323e7f1c9c6e88d797cdaf679dcc034da4ba96ba954faa5b4edefb8976aae23cda5b934d7d1b66f53c3ce649906d4b564b22cd380b6fd1a239dc7a73c8ee3530d9ed68b872fa5acc197d2b6cf693a2a9d4cbeb3ebc8b52d78b4eda738a11c3e95641968fa6c2328aa545a63d9ce40db2ae98f7b926de3f4e1be481ff66d4bc5c5b4903a761bb77d95f7816f1930a78aae9b6cbb93f3881369c79c47ecfcf3ef590478fb53698e7329ecaa8baeecbb5c1f3817fbb8274ee6a22c0e8cafec87d996737151dbbab898d4b13ba78a1637d9b6bfc99c07cc06c5c168ce5a4b0ec90d50b995742e666dd6aae7643e2ac3e2dc323f559a0c78019c1054903deeeeeebec3f3c0531f0a1e62145cd91c6c990505aecde974f373ceba757fcf885377b73216dbf5f3a79d6ea79b1e50fb1944e1c62b32c116e6b6e005a48cba165fb88eb65402e8aa3fc5acd0b10279c57568e31ab4f15331d2474a1fdbe3a761644ff7acc9346663c7130a8cea098bb429f519ff6cd2a09851e0e1d2fbe0c88f15ea7419e5219612fcd1cd8fee681af7daa6713bb6d7669ba79b1fcd5aab7e0cf7dfdc600acae7e119a9ef3b3c112a131393b7349d4c934903b9209d134dcc06e3c73b4c745535d734cdb70f3450771ee6fc6108c4cbc60fa429c706cd01e92efe1adffcaa5d8105bd2b2075f05340fac05545592af80abf93e8318fd9e0094e210cbc22149c4473c83d83f7834c1fb9e3e1bcdbf4a9efb258de728c93e1f08a5cb07a78856cb3baedfa2777cd5da76d910034dcf950f594898eae83c81eb977360d695efc1b3830f091db878ebec2ab1b9a46e5ab95d672784634217494d19ebe9812e9c8b4f1774f71784552600b73cb8062eabbd75ee519d1de035b981ef3108b1e63fad1ea400decb4ea7d0b75e74722b3f3dfe764073e7d8e7e3a609a82f0f45e58464270b8f3a1ca5b9476ef19a1413ce4412226991327041584ae38cd30464a2e0b8235d1592daae17970f347e319c9e01c0245ff4964348de88bf7690b48c82b72535c900e7ff8af52693de60ca1ef4feeb989bf7b7102b16b7846f2fbcf18394f77f49af144c8294febf09b4393a8a3e338e6ef5e14f2c022fe4da26f0ee550c18f82d4c1eeae4e88b350a02bac04b524674d58ca1fc0182d4e214b97a40e7efad151882f31fc9f23c7cc0c550167d7b450b73f1299edf9b387ce231ae1c04ef4d745b6f7231c38893238877c0a4d21222e7bceb30905ffa43c6a26a78c02a5ed15f1dec116e607f953ef9e7f4e7d2ccced81297846dd0764fbc40eb60bf18cbc4f9c401c89418c266c638c9fc39db79c07c85d1a7822e4f7bf224773d65a2ce4be14e877812d878aa64b7404e188068f70e0053cc4cfc2dc1b98c28f17f0a3e03cdb63cca47ddcf6e579c5c6184856d24baa9f4c94557de5df650d631ce2e778ecd3af7e8d4f888c64eefc3b94e54fbec20f66e3a8240fb33106daf88336fe1f4fb4e87f57a8fee4f8bd22146c61ee0c6ab0fb68cd15c6390b37701247ac42bbfefd797fbed350d09eef5836fd1c22cd45a9e09df781ddf9a5f529377c737cd022654d222faa26bd9484ecc9811e217dbce88acf5c70e127e8338a44dff31630ffda22f3535e91f917949bc23aafc8b49bca7bc11e1e52b08e30412959142657f591b4a45de98823750059813ee2ffbb3ae82fe93fc41dcc41e8191235729ea35a93f66d39cc75643ed2f695fbfaee84ece9be7e37df03c5e9b2bd790b98bf7945e63b9483f1996c86690c6afa741a8bf5a0459fedd8b3524f531f7df21585499a7a5f39d59aaae87f28f34b1ff19887f55998dbbd7828ba93c3809c4774a75dab4bf6c8fa4960c6a830bbfe0f18b4e84eeee43c1e73e9d291a8bb23d125aa44b160f19c4daf7858fdb9499f3cac150884e915c8c34a6352a7be10fa087d16a62355094495c89eedb9dfb6bf9fc881e274d9e6b0e8ef3207e361fd9c1e747d975587c252a9087675905e79ba3f63333663dda657a897c7b111298cc2fcc859da8d5ce25abffead4857a44ea558bc68fad023a48ffa957af926d592a64a49489dfaa2e7b0eb733f5c5aa43198f388f4efd72fb2c78f50ff90c8587fa7a1a23ddf7d3ea5e01cba9da8556eae503757bd4e596d8a7a719e1e3416be4ad39f4f9f648fa493562ed5f99e9e8775856a77f6d01271a0ed3b149c3c48ee0fd164687c04c9058f50f07ead427f86eab306ab874c3879438b9656cb3af3429cc77e0c170ce11b2104e7a9b1e007b66efb41926d2b0d61841c9f5c51f76ad4ae4f6badb2d65a6b7561df2c575cd9a21ce5df0fc179ee8fa0a239abaefc6ab0fe21839e2d0fa5644a638271cf33342e4a29e5e74c8f3e2769473f107ce30fa2895cad0e4a991227845215826c0d04d96208fb88fc4e0c617bd769da431b5fe34f903df9ad9e2f93f26eec39ffbe8d1a9fd42627f3a53e51a5f70557989f0a415436dc092dd0b267befdd944f638cf7dfb53c8ce2b2205c50f6c79c54e20e731c1431baa54f2e7bb6690ec9992fbb7765224e9c3bea863b59c55f3d321849e20d878686d9289f4f22d9d394fed0312a5f3d39f4f73e6e867bf15ac7d77495fcd7befbdf7de9ad4791ff8bef7de7b6ff579e9bda9fb61ce5a7b185e9bc569698ba1d427482badb4f2f39772b6b4f33895f1192dd55a6babadb5d61c1974adb5d65aadad44b22db35811dbf6a5d7df62be9fdcd509cb96599cae6cf7a6b8ed59634f1d53f64c4daeecdb6cc73c45db97dbbf8ad98413ee0ff10bde90e9743138b9e5146999c50a2b5b66b1e26873dea9bce8a6a524d1a7a391d9bc907c2041640f1a6364687fe44abcb1e9933da93742d40ccac3a54d43038af5b52bd0ae11db63173127262c5b6b50a4443bca630a4316734aa540d199f6eb8924249895dd75a0e8aeed5406942cf6b461528c4948dbc6e1d89819942c67242b98c6a42b9bb625dad93b5b4be36850dba62f725bbe4f9e3ee8ac51482964382b1572456b8c8c8a85d64d4f0149f6bd56a98e7bd22dbd9a4445b28238e0c5da43035eac4294f0623d8204aff1fed40cc942c1c88bf50506bc589b2ce0c51ac3086ea30ac99a2e115ea42950c08b748b222f521684e0ba08c99a30105ea422f8e0456a020f5ea45912f02285512892359d10f0229d81c88b94ca7e91f630c4535e4896b70ef0a28b618017dd8c02bc485f1022e38464f9cbe745d7d2f3a2574180173d05415ef42725193209c7599208be3a68d0f225b883657b9c33d775dc10cfa2de19c43f846e1e601ad504a7f6947e62b735aa499f6e339d765ad5d1e049aba6b1d91857156caad56854a1a6654da3d99a06567b6f9d9fa837e6b4ac69793e0627c6396bda764268a9bdd652fa73ce3927a53f2db5d75a6bafb5d7da69a7b5d65ad05f033bfb48926ed68871d2d68a55e8c40e23a163bad79b820e4ed061462bd44de3e10d2d7a6aa973bf86ff2a95be5ef40929489d8b8287f77384d0a2def763f7fe910d50d6101530207d24307ddcbf7f5fcf21b13dafd8f367128b9b4ff82183963d74df9f462fd0dfee6f1c0625dd91308d9fa6e0f7526b41fc9c6704532a653b9b42e5e94634d28b1bdb8dcfc627e5782754eeb49c2ad5dc4013385054a924b881db089a90672c386b8cb2868c4e20fd46e0e40f710a66106824db390f96c9f9358953923d9e13b22725f3a91428cefaf2b8fa929d95ef7747cd987bb31b38db665af5219ded189d51564542ca39d248cee37bfe48c791ce2315e6e1b45291ea8bce26fe5918a82807b3e7ff8041d797f36cf4079a1fe22c1af071e813f5329b3c9c4b37b468fa1cfd56a8938629da602df270cea4ce3c729f85b96945aa583aea3cb282a92c903d5eeabdedbd979f98023d707b2a6e4f917eeefb5cf7fdc61487c1b2ed3737fe56f02d526a86ec997b3e3d63b26a8caee6cb6098d5c99e55863d5bf8452057f34db0e767d9136a4f2e629dedf99c5791a4cefc9c299a22c5f6fcf91628569f8a64aeb9fde63c582e6da24e1e4eca654fba14c659f3e9cc79f03f8eca662676f3e992cfa7239d383634b469be3365cf033ac58abf89e59b41334916c6de2966e447328ac8cac67fb7ed3e12996d04ff055b98437cfb8ed0df6610c8c29c51b2a788b3985a8ae9a455fe10a775d3bf20dd860ce930eec08b69e8af02de2dd01e73ccaef4936e7ff6f0d07bbafdfc193567d307c8f4d1e56c247828e5061bbf9633b246eee654fd9c01458d6303c59f3efa3529545f8e5c6dafa596bd3d069f137b7b1c05d8dbdfe8607b379201c59c02b78de6dbecdbf87e6c3ccd097b1349d81b68e3e5a64988d8b06103c4913a1b48f372d31f9a772109119ab79686e6b7ef7aa0b5294d0992880d319c2158c1eefb8979cb9ff4ab88b3e55b210090b187b0fce9729e2b7fb65c30611b412a09d9b549ea95c8a46472a8294b6b194957a9d4cb4c29864bcf820553f5c019811f28394ad1b4ebcb3ae794b5f370669a5b3fda39f6c22581edfa72f7d8f291d81739b03d10891cb37fb0f162fdfca1f31cf13ef450a652a9542a053ee8396947322b985f4ad943aca037f39904afbef74368fdf29e013d1089f6168362b69e0732bf713232325aa60192c8e55effc63da765b4d6325afb8ef33eb1f380842593c88dbff1338f648ed95ab6f748e698a5ed3da62fa33f2d23da2da3c1f7d0f33c24f6c5d4b740f70f1dc0f172d71a8fe3fb99791792903003fe2469251909b9806078a145e4c7850f8c59420561948186983144665e49122233a0129abf910291d8f7b81f66de7e343640570e22336093999f81ed0cce7c3333df05e8ca312801a9e3afe399408b322684fd99b742ea27765063066ca222a264e66b804d32112599aee42b513203bab055d1e1081b6658220a23221f904e2f314a9a9eaa4843082292aee42ba9f14a667e03945503d47425df41155dc997719926ae8a2a3bdd25655df79a4384ae4fc2f431926082ac596bf7604a1d9f2931498d8273ed39b779f9dccd6bda8df69b767301674d28bac23f299eb3e6757c8e573d063c387edea0791b357ece6821431210c20824384f9680b3fee7a7e03c746aee19d1344d48065050380370c240700a7da0e6f1353cbea6be6351b4555b66c1026873b5dbf1dd3cfdb4ffeffbfafdf0f6ef979ffbee771ff7ded77dead3fa6bd4781a9ac7f1dd507d187c8e4ff53abe1cef9f8eaff9fc6d3eb96f7e884f580db6d9f1e1afde0778dffc64a2fff8e70ed0a6061473ed6abe9b9f3b87082d4ea809f5a263d9f8455762e3175d48083fb7e39b9b47cddb7cf58cf0f89aa73cbee639ef7688c0e36b7e88efa8f95a987bc7cfa8300e14f443fcc6774dfd0bc8953f940cc5d9b2e1f1edf8b2873b3ebb6d3e0fb4afc1afe39b613cc49fe3f39687f8559f0379881f83cf5d9f077988ffc6e79fdbf89cc88b3ed79fcb7cfec2ef7d0ef32b8e743fc792e44a1e8efff9927b53f61975721eed77fdaeee5aed27b5cd1e11623577affed683ec5aa767a4babbdb26772ffee4b328143c6d99a5cb95dde5685b6b6f68d1d803c1e737a3b0bfcf3c0cc63b34e7bc71536e5cbf9a9c358c71ee7276d0339d87136a688a21595a9ab131c638632fc8e59e833b7586fb09e53cf9797c267bccf846b0a0c433aa89640322d84c318de5f01d408420a58a27050900e931e29e160eb0852440f2e0dfbc6eeff0fb9187dc73dcbddeb4b7f7b6efb64f4db1b2e4bd8cf6a7d3fbd4adb53a6ded3ded53e3ce4fff62b132b494df6508b33508abe831d163a2c75a942ed5d73b897414a2ae1e3836236da21e1803c18c93e331ee7307e278453658d5caa55efcce73ca4459381ed3590d6d882a1ca03f81220d0a6f809906f428edab0ef41507e617f3c679de7beab74d6fdb96da5e664b6daa6ddb3ec8b66ddbb66ddb96e319d9ba6ddb64b6ef3aaf03c189e319d9c0143cc45ffe21ce851dfdb66f6e0dcc86e99c4c9db75c4dbed7a14a148baf9cbe90ee47a9481ff8c8431c16491dfcda4789fcc96555360633141415a39449f6d0ad514da323dd9af653fad071fac0afbd967a24323bf5be395013b2a54880edfd8e8cf7da93f1bcd17b21cec3bde7b3bd2780f43e88ece9def33ccf7bcff3641ef48c78329ee7698fcd3c967951bfcc8d5764060cad56eb094d4113aebce5091213bef6de7b2f045c509a26fbb95694ac3c011440464b3c26d65a6b9178f295041365021261804c482208ef689a1a3f4b96905ba8c0b0d87105587111490912ca4c7ea680090a98c8518a1f7cedbdf7de8b63df7befd52266b6efbd59b4e82c29a8416ef1824836114608cacb154ecc96f0e0f262975c5faacca62934686826f304370dc99e5cb9f7de19165f49a5a526ed01332f610d1dc0884571c117504d88dc1df67dfd65df961833049530c43873d7f5a2a8cac507345cd5015c185da24a45174552b80294305151f282285230a94c38456152b493f1c516638ccf10a22b99f3cf0d32dae414443023308808dab6018c600c125f9c44b0022074706d3238e9321be221bee00756748318980f485ca1a105dc1623c238d9a10902a8254304c929c080c26310e957d1146ce402261915d43003bef6de7baf962186f6bdf706cd24b0d8333a202bc5ce40492042a889097624c3740415453a218b714eb0b04e8860ad566b0a26da320a588bc849511dfaf071feef903bd18c4411bb4e6890c517638c3186a2096a639cc31014add2345adf5087cea801430e5880d1058a9f31c65011c20816dc07ec300baaa207275c15474f3efc4802821611484cbc10b1c5cf84c2618c1fba128ee0a18624ae1841144ae6932e320419618c711090af649091a021adea7e5250c49210c8926a80a24395a11b4034f9f071fea160cce42e859f31c61896e42b393239d905c062a110205c3c18e1258b2b58680f5d8af0ba94c0a5559a46eba5314362965c308c31660213b431eea1cb82461951b41f262e6ac03180382f78428b2d404c61e50b26921859c0822c323198145d2c90c08c20ca920ce30e328da32516482d58985c18635ce5c3c7f9b7415aba5cb10d6d0da0220811784d00e921a78d68cc9596eb0b580e38074b74022a122a87555e2b4843a9e80730309528f785289632e00a59138ca5d8bb64ee501c7c1e91341fe7c3c7f9af42450bed89b5d63e31f2957c6598465225fd183ee009d34e102fb90aa127202a85dd42d3b8269a2685144d4dbeb811205e55a986a46ee9873a8009d7bdf732f1d68e945454996d8cb355420b0d8c1d722ca850c2894b8a6108638c9d6839c00917187ff2f4848a1374618124c61833f9f0713ea84b0a0aa32219c688fa82a50c5c44654c92a6699af653e42b7964e50a16a5127e9670184230f1e104569c0cc1840c430a1745d65abb84155fc92b198b56522dbd91259ac29d2ad689fe9a74efbd5a64f0b2ef3dc2044a206901c58c9f228c83982c9a946c0892df5a6bc388f94acaa08a84316bb98a4009e12064c87eb44ad3688d6fd0966d015ddc34848238e8d1c245d40ba2a2c002862e36fc28a1699aa66521e42b4964f482212519c9624ceac249ebe2cb931fe20e5cc64c7162cc099e98627471e24a54f5408c28ad20664c5c60a96734a518d074efbdef23048d9a664ad012941d6ed0678c3176f94a0a11d9225b18fd1845c8b0a488a84b1337bc649210d22a4da3f505bf20995165a8082943d8862dc61a59b640810e5f77982e8b2f166ab55a31608c31aeb131c618bbb6dc2283d0c6d8b593c060a225017df83817e3ba25cb0d395ca37ad444ca3d62ce643269c8702706891d8b2fc618638c316e32c2cac6d80a4d0ba4705ca0c829622fbc6028081fc4bc8892b840c92243030e5aa4f03065054a44f105d7d02445152f0a0d5cb23c63a4186717bcc08698983fc6788c2669204dbc61fb846b63998d31187a581b20c62593f029484c1c146f26c5698b202fb27851a362c0d7de7bef55c0102d4dd3d2c3089a3811c2e8c7059806241f2a52102e269911cc7849914c235dc96cbb9c819dcc2636bc144394b404132a4ba4ac00cb11627cc1642597a525268c317e828bf6908d1248100514315aa0d81002a3218c5a60b92c1947284a10dba161009391d5b20570400206298a20818bc98aa19264c91353d3981fbe98155130b6502e387cf838ff4d59c420858849c489549c1882660b4b2d529c5ab70a30568c2a3b425b6a88a2822f4870c49319943c29f1a24a9221588b0a0e60ca408a61280bce0b4d0372460f49d0e0c44a165e10c153367e5db497e8a00417b2041e8ceebd178acc86dc321446b68778b652f45333a8035e18b5ec4d4fb49c8a1f2daec75c98e84ae6265aa569b4963286a6699a56e42b7964e50a16a5a5052cd19414dbf7feb8039a8062b2538788f94a0e21cb4340e921662a225a4f842bf42efb4ef184114245f98bcc842f505a0b0b70d0863e7c9c7f210fdf7bef76b7eb5234dd1054a150b282a12a6ff1c518638ca950a2b531c63570d1fd6895a6d11a1301652230553af8c08f1645428c71c4c513938c222b5eb884a317141c143f0049e1d2c36c630c055f7b535130ba925bbb4b2d8021480a1aed892c964cd932cbe2880722a2789112810a2334ae931ff287f7091a829aa2e81283c18910d5034baf2d34887105891db00c398c51daa116162be2e1c3c7f9bf181812a30727c0b852051522b7a6b12f0e44eec5185b0d4fa182102c696202511469e143154fb4cc10021aa6e4f0650fedc841e34806335e512ef8c20823820033b524b19480a21ec000d3da320b8356d1524516a9a0d1e28b855aad56138c31c63936c618e32d474c1be36cc7259a949d291cb64f348d8a29464f90f8ac598c313ec3e52b299489f403ce3052bd6be609eed2e2a29950303ea93c542e4442331d232ccab862044c284144858b0e2e4e8a1aa880e2046bb55a4fae6482b2e2c485c9101eea92a4699a06f395444a1a999c620f1843e62bb9c52049617297178a6820818c66354dd3b4fb9ab7354dd3b42366be922d971091d16d00122f8ac3fdd162888240f518638c957c2597624dbe9256ab2110415aa569b4c68106dc84039525fa6b0c3a1830851394fd7b31de72451a1bdf8dc128c4c4ec96518a14a37ad444886dcb134948f91d7c826af1c594a9d56a51c118638c319eb22d9bc288da188b2562d8b62d4cd0821ae38b26a204e1189ad208639bc64499819e41b110143f685439da4952e4840f3f28e170839527a6174c70d102a858abd58a6225153324410521021fa298587c31c61863df18638cb36c1ac18f56691a4d0375103e4041b1a40b204d9f08f239676cadcdf9adb5d6da28a85c42d4ace51222ca259061a47d9a267fce5a105cd62a1a560933d8943a672e64bc76cef96d9ca7dbf91f27eb336625a0d17a2c72b6399909d56ab5a4dc2bb534d1b4ef6c37b9768d2c6440fd7815a3195e4489bd60c51437c81dc408e386fb2f7b780e60c6921b6078418b491573563486071b3235b2e4a22b99996427185ba9513c11e48442b9f77eb7afdf7b6b2d4307269e68594195344a40e46229fb76a1b20315306b5dc539bfdbb8fe9481914559ed7b1cbff2c5108d0e3f186f50687124b3c86634bc38bf7d7db981bf2ffe8ca1f8e2858d9f461546c9c66fe3aca0c5a864cc184a7b9aa1a90100000000c3160000380c0a064442599ea6499cf5f614800e6596526c50369d86b224485214c510a3000000000000208800829073b4a3001789589a955a9b67b75d52f2f90cb9b4157bcdd2c16ce3e00a990a31292226c739bbbd9835c20adcc6afa81012339a8037382fdd96c7f08b065c974661e27fbe4b6b2600cee951ed6d1c1c96205aba8556c83643ff8511e10c604d7a368661f77c56de01b4617dc87331da1eb9a09829d58134a28a237f74e4ca32aae8855cf3505a7cdce072f6b98838a5d6a375f2bcd63d2c995f60756cdabdf3d30f62ae806482f2e47fd2d0745d633cb442a43114e0ec4bcc81e0860d4f474b978ff8c71f480d3364e88b3bd2e3bab5e32faf69da18b2cec5c44bcde9d49c5e95099b68a5a701e4e6baa1272bbd14fd1e7692dd4b3bc99b8fba5a9c3c8ef5ec7a01abc045be1013800ec5369816f763a72e882d5cfc32bef807ff526d70d39601b99b926465357582a9a4acccd80c1d7c0992d1a68bfeae692239d12300edae46f6ab7ca0bb8d1ee94cc9925dc131abc92cd9dec37e20944b2766e17a123882e69f99e7e34aec1c67275835854084012c2b051d2f3360ef867c98be957e8668364aea0a8a4054494ac64b56fa0f73e6a633ffda7d13a6344c142b028d257aab535d8cb8d4842da4457587a87159ef481344ed96963c519c67e13addafd15397b633a8a08a34b8f0ee2193806ba69aa9f7974550bfd34c867623b14251abaf7165845690ee8d4aafecc1a9c3d4cd2b5efef7c82a8e9adb37d6935613b7d97c1ec4c8d7f0a486aca200fb12e3a03bdf243d0cdf6b57e274dfbed096f8dcbef9150e58292f0d7dbc28fd5854239db3400c10d241b4e16e47881bf28b7815201a36e38c706f89ee09557e7df2edca497860a1da903fe008c478d59a620c59a6ba01f05cdb7e18c0433643bd3feed737260631c9e07d006b5bf63808adbc7b45b7ef99aa0e5083a693e85183120e430db11e3e40907ea4618c467097b8074336ebf41d69d1874b8927312a54df0b10ad126090f2f62175a70ecad31d6ac2c30535e3e16a8ddc8948978012eaa875417b41318f703dd749607b67573e026a9e06665b282c4fcf8e299fcdc5c45993af3108d9f7046db141719d84240fcfdd6f09f3a1a9efe7603bb0744354e9c20019d4e9f5e7bbd596b9fc885e9e5e36f9fc8a9ab77be6c6a20d05a692df0b1253154b6dd6d245696b2afc72accb89cbd07d6ff1f4797a35be9f69ef5debc61b848a4c37341738aa729631219d35f0b839edb3b0d2e07523ea99bcfbd208cffd0ea2b9bbc9596451aa0dfebd42dc888495b4cf1db9a9ad6a0e38963ac28bc74e88861bd68a7ff8fdefaa4de013b5af3d5f2a02ba7e43255b2022065fa05515dbfce106f54db4f28bb7552e39881a4cd65865cf8fb0c5a1b38338633248e159842ba32e12268214f8a898b87fe3865ee5ce60ae80503f560080caf3abfc94ba346cc56d73435b2a66abcc7a44d1a1cead361b9838097d26b0ae0d8c8740f7ee85655c34356d631faccad21663422484ed4a4f5fde07582a2273f61178228dcaaca68055963f8e756ca37296d4b64cd06f6f05d172a7ed8de872a3081d11de94aa91798c980f1db1d6cada53246d0cfb194a6f62a0241bc0ed903f193288c7ca587c734b5be99d57619649cbe0f4c3333ece7b34b58c9b4e27f03f5b974ab95e9c89218d10b1ec4939bd4884953396d851d368ddfb7f13f340bf227dfd1741bb8fe5af4f3ff34e5bc49884ab6eabc5254af84399184029c3e5f463426b2c7c695e5542f3876a1597d79e0ffdff15a0a423cfd07b6eaa2756eb4bff2e7696b6fcd01092b9f5ecfa9e43cfed87ea1b0cacaf0b63910f719b5b5424e60ef026281698ffadf90c95bd336d7509676ee0250887152e543fdf4d0ce9b2835c2d8cd087e5b6e6f6c4aec81e1da1d356a58ede46d3f906226a0e856db6ae22c73f686e3f4bef09d476619aa291edf112a6db9eee92f22dd96cbfd36e35c315f161f0b46d324b0f879bcf98adfd8eafdeedccf22c164249ad8ac917983e181f9f3915ee441bfb6ad680c9b45468589bec364fc56c0eba51a864602bb0cc7161784c4224ae9089b7914eac4ac63d78e58190f1d462b80638e0bda7f4b5021af785c2d7805526776e6f60c979ce3d52ab4432dd815e35e4a0c4f94a25c16741d6911b6896fa3168042e74e8193560fbee3803a5716ef90d40c94fa423e994f9704d4563e9c484af9c5410bd89c4d94e3b1fb3f4e0493d562533e9b8a33f7032a42b646c56afb7b85d5e98267d4435a07e53b1f84a77321bca95925b94017437c005b39a486aa3415ab3194553288c478e017678604c213751d05da19359bc4f7ace368bbda50b60392e5b505e63b35aa39bd0d7fb811f2ee6a0c232cabdff2f178969e5c7fb3bcc63d0a5266cc2af47b925b1fb997088f34e21bfdd7ad8fbe7c0d518a52d68a54158b8e57d8d853c9d7b41062a71f60a10af8796db6cf4f0a7be6c8f36d4c199cdead3c01f747b2f2b830947dd087a8940e63b820f4ce65f5fc8b2e2d0bee4bdf30ab550cee9425a937136018405d5ff245d7ad8c077f4018cddfffab03b49e78750b570a49f50eafc38f9a12d526bcc88b46059a4c234ad28fdc13291b2e6cc106f6b4f5aaab4a50201a6842fb2bb6ded57dd6ee06b5567a8dcec04917816a71c1be608e44bc836ebe3958ce016c3f9857777952765a778f18874585ff4b64e6cd430e319939db0887f30c11ff5528158b08e1792ea4cca196046a792fddd925973b6012e7c3441355007fdabc21890579fc706e18737901ff960b62c22d05b65ccaba1d3d969ead8a4af0cb4bf12d6815562c66d6f3e13f2c54fb4b6ed0eb74c3e5a099abf3f3833b79d176563c9530bb040f2cc04db2c351e6f998d0e69b4396c79639d498a5b2dd351e306d8dc62280b9547fb6615e3cd399f5be9f2f5b9f7dacaa9699a7068a0361a48466a4c4cb17476da2f5f47ea44a089e4849c94e3d0d3597d7afce5193179f4ebbbb45296fb3e1e059b747c7340866b8e310673e1d5fb4ba63b2911115dbfdc595574b2fe99dd5e26680acdceb8e799a95c836ab0d95ad4b2b908c03427d96985045c49a4cab93d0f609765b4210d4aac2933e935c8d6fdefbff8a8f753c03b457ea60173cf748bafc448e1b4ebc7fd64d839dd7e7d5143dd05c1b32ee9e004b6c6aa99d00dab977667a1bbfb1ea856257b7c49ebf00824f1aaaabdf5c31433ff30f503f9635521c3bb9f506bac5431512b0520d378e0aaf4d90fd8022f72d806b13fb283189e59191a3e6c091c75543cd6cc87466b42842c45c1c1e8d0e71e93536a3fdb4d1b8b998dba6df603ce243cf117eeb6817d587d830e262b6721cd541856c8a0347fd4edc940436c308b33634fb4e99ac15d2d5f6931fd3c1c26026f0389838850355c373e04e41188ef2f9f49cbb6951ebca41ed52f8cccb902750158c937424795facc0867281b3268eea29f3f3addad0ebbc8840512141a9eeead73452003ab6759f4d68225221076ec90ec1668c69650907f7fd8ee3af136ebf9e2b6681b7dc42191c1b33dc6e669eaff2a3ed0897a3808ae34541d35f771594ed5b8ee6fb618a21cd6c3ea4006d6783b9f9a58fe76802bc45df94f7f5b16767e193a3a9af43cd6f0968d75e8a554bc605cb00399ff6365158bfead1b489b280ebcd5ed20869684e1de3128354aea070461f9c7c2c9634ce70001e97bce1f355eaf9948ac0330701751cbe76efb18859098b1b79ed39d974a016a58dde34f0e0c39ddf12b8ecbf8d7b40d7697e1a5424b94d7f062f46e184889e7d4be6b0fcf064bceb37c45909a679d3826a0cb8936ac8b26a0e45bfb0a4d22689f3ba237fdc72e41bfa32b6346ee399f6e9bc1350b6a9ab827f80fd3cb6f2004a13f6e02d567ced5c13344306bbdc43686c24447436d390603ebdac40a0a44c8f375e1f30a5d02be287f97b02fddc2aaf64b879c398ed31eac6f900ba8ab2528816cc480fb4799602d3fdd8caad6684898ecd63211cbee13733b51ce5fa9c21b97bcbf4ab59b7497cc855e666d904deab169136a81c1b34a455218a8a74388eaa106eb05050d23cd599c0b1b91e4e39464527a120f8fe91a612f693794c90321131a6bd42e4f0c08955e7f4d9a2db9112b83faa8317cd39fac2cb94910b282f74cb23dce4143925841abad3ca5f1dfc098e2165c3b5abd46d07339e8b1737cd4f7ab84d3250058e04982db947a6231d2ba7698b342020aac7fae284ddbffd43d9e816a87a3de6dcd0aff6ce0db0546392b5fa8b0e9542b6e5fd3c944ef5eb794f6aaa5a3589b13d059de1d2c71b6ac6cf0c0c5d72449d795f81883f62dd32c97bd8ee704a7d4367a014b589bed95bab6110b91a8cf39d23635d994c7c8780437ffbd4de259f678358f3e0e2738adeb753e3396dafd4ae5a3b16f2f30894027dff081cbe640300bf93240ec925b0bf86a6efac326311762adb8167f316cc358293a39821bfe952bbd81b4b6c2dbb51619cd0d6106261c7323a41ca0dd5667c1d9c7f535caab2da27f06787de384f4ecfba96b6fdf15f4ab0e08eac7fa3f6cc713b323c7361bac2e96280bec301cea58518c880e4db1cb61e9ab1488abf04ca7c0920c8aa3bd7984ce1587e6660f2b0dfa42bb0c5fb48eb78037d22bf0aece3dbd3f15c8e073605e04e427f4de09dea961ea93b2a98966c2607436d0a554619bc09bd9de262669ced06e1a2b0cc061ee782dd2e0f851c74df6467246127020c73703a1ea980fef963cbb9048867fd464892f49cec9a1f5ff783117af870030c73c4c37f5d99daae862c3cfa322734c74e760c8ee0c65b4ea7fa5e74934166754317c63ea0746c6d434b585eebf15dc4697ed53f178dbdc65a672853f4a6ac12e7745b94e53429f3bfae444d989a450304f4a46ebc9c491fe7c812dd6c941342b3196dc54fdcc9385e2939da9fd842a90cdd0fbd9348eece2503f9592ab468a8f864f6d41ef735e0380ac00e84e11d9ffc7ccbb16b35a83b6cf030a042873c97dea4a22394a1ea657ccff9d20b2515e133c75741df6091256d2c206b8510ac14ede483a066513e26213128eb84d4a4ca4d9442134063bdfeb22e64e774ed8f77b9be1d70e9bf37454037eeb53ad91c6bc1df58342348ce0ded737d76df5a3976edf231b2df75e38dd26e397f2634bd9acec8cad7dfdb84fe4ba971430111f13619fcc9344e55fcd7619dcfb4b427a1da24e10cab86da1785560eb19d8fee95ea9766f3e71c0b87c4b20ecc1f8e572973cb560d5d3920b951670218f17694b82b30506aa73890abcc8b7300384bf6b258932d1f9205c1494e44d981b7922db02a1742397cd3d8f66379bca28a990a2f9e387fb25e16daebe96fed5a79e729851c9eec0ddb79f4b673b6a9f4ad2239a17ffb39897185b4c266d39778f7bfd4d4ca694d5c2c3b4fc2dad44465aea4297525e633446c6a57e5f781983ee1021776ae690926950c4d6095ab7620b78ebc894d4f8a1339f109a79956ea64caa05acfddfd52c5b2af1488ecdcb0b9982297219c97846e9c14f7c6b2d561059da05febffc313935ab09b6e3d3e6fa69bfd06cb36ca4add2df7e66cd8d5bcb0a6f1801d39b5eae9b1bb218558193518f763a38c698a702df231f8ead8bc9d6ac55cb495488b18386c6ae39b58433ab9114de31ff5d1f4b7a727aa9979e19c75de1cd68f2e01e3e4f3f3fcc104e5361fb654c0c2c0aae4c202f308f370da17e580750c075e1c2cbcaf86101a7eeb99380aca3a15c0465b9375b20b912bf46c4ce9dc691f34cb057d28f458be7b3b25956b8b0669d234d2497ed1122ec3cb84aca600a4111ddd0d13073647b901ffa5becb4a8e354435a900668c127176ca3d40301cfbef411a268e7218f7cf23e50af1e0e4e3a2b2c323d3a4312265cae7a124d426a09dfc6099eb50a25099703c7da69b261dea18cbb6455e2a6cb76d283ddaab5328afa244588b52ad98df7ae3200e1558b00b1cc95fe3fd5ea8b94ac8b1f98ddc17581c780394bd45db415e705914c22efecce660f107bdb2ddbe618168781ddd0142890026c3ce6f55278c0c5911d3e6f9a371e5332f3c5d8c707217a9529d742266b51f8cf069f3471b78feb66cd8cd56ad1e78a873e44ebacae533e0ebbdd7e118d785a67286133ca6023264953a19ad6a215bb961b7b1350058e43a35e6b22619893a16e45c8e0575ecc3cdc1aff031900e37c9b8fca8b1265fe15bddfdeaca365ace8cf540189f30de1ac62cf4688007a52a0ca3199eb76b6180ec718053d6cf00b116c20b9203de1ad8cec04d0b8dffd63b28aa32f945bf6703b0140272cf50939a40f93d342d2cf143c3c1d38d573d0599cf963fafafbe1672d7587ab6cd9b7ee641f038dd12cb9b93f94cf7541290d1ddb0258159ec7105697facfea65202212d3f4c6549731712c6186ddd32888562aae5a3aabc151fdb64ca73b84b9180b1a753e9da5ae2eec31adc1440d814ac76b75d0510f7251a633234d0801f812dfb00b73bf1608c6471e0a127edb5fd6cc403d0c30cccb13f2a7918a6428428e11f40c90b3bd7121d717a5032e5791c714fa966e258c99c8c529489ff5606aa729cbd7f865836680a8edcd0ff887766e65ebaf81f215009b466ca1295afd3626468d7bddff5af49e8840da54e7901691308b91e6619afbac60f621595c6c7ac3ec7ddb00343c1e4824653b9c6aab38b30c052236b0a39910517957c391bc801fbbd8505b9294fc0c9682c53537112b056537dbf7bf2e6d752d349a679f0a021585863183dc88d5f02faed84cfeeda1fcbbe2899e2cb38098b5d815d408bb03d3b8b5a6052841f0eac59b9914e783ad42ca8577caed35c3eb485be09b41c43d08fe53acc92b763a1fed1f8a453de2556860c3b521adf769252364068ed6b730789fa7a0a66fdc475a61e1a93c0b8046e4b6bd11aa54726faa50ad02f00936eea76063a81c2a22e2350dea6d6d595fa30916d7f1e80ab7fe82c21336cbc289e3c5e08a6c3f2e0d7426ab58e2b6b953040a60bd8376710b639f5408dcbcdd1ecdb3501a3564ff3beee3a5d7ba0a6758bca2dc81231e0233217306e6283071a93875903b49955ecead296480b233d18fd9e90dc1c06dc621c1e1e37c8f6d7bea717f5a48cebeec09a7cf974ad3eebdf259f9731ffcfc732855afe4217b9190d7473185ec11bcb2e56a42cf4d57932b7a5fbe4fe0900e4e1c0afd7ddd3886e4564d801d9bc023ac63181ee9f913a0c6e013c317474340e8fce98d6e10837a197e636f782fed760c1d21bcbc473f8a18e12243a15aa9bc69124080c97095f74544a121e55fcf38c4de25e97d7f9ac047f3927910d770dcf00d0df614e5d3aa0537dc4b695525805eb702025228c0cadd5f7bec55e0aa4b6b64d4ab14c6e41ed9771f07b6fa135764ab2585988bcee7ff00f6d4451ff7f4ea4578244f5a848d496cd4ee1dc9d14d4b6109be34551b7d781d92058c43b8f700ce53611df569fcc1f615479b6b31dac4a62899897dbd3d066095a32aefaea928bbd9ba5e367406dd8359b9ed0c1a8c15ace934c229b0e8f0281671b316184261549211124506650e851c03036b1ea8c858c428de10ae205115a1cee0a593d4e37ef14eef1582caf7a8a96c52291772ba28a51b5df6ed897527aa45ee514a6641fd3afeb29a390247d9ff9a3b79599ec9b38301b50ca505f8e5bc9db5a69397e36a663db6524bfe555e9ee726bffce6352a8877ee90a17ed44da4f4028892ddb5b9ac9073635a79c7a04a62ba8d3a196654e07ee37fecd2d94a4bcc10c81923b420d8bb30aa0cab28f384d2e4433214219d123c72474c1e8cf8203d9ae303a22a5d0c7c8a29cccd9e357e8711dce9d01628d59a5f1019a24c518c02865ae754996d5877c66ee5b9b8128314485706ce39a8a59f62198088ff674c9db5304feadefddc6a53ff5753855e1743124bae4215dc1df5786896e80330cd8205ba3921a1f7800b16049dd546ba937b7b1b06e0d92f4e496c2e5cfe16eb2de57032cdaa0c92840d04e98c5c58a05821d4d29b6370000bc76c7bc9a303d4aa336b6798b0dd11480d6916f8c164502c38a598bec0a4560a6b7ed4dd5fe37343951320dced3e2af9c44665ab358d9dd4466d9abc90e06b32119e3ef818a82536be931c2c1bee502b4ec8d0293c78c5e78707d370b2495f52c60149a5de65c05897e1db3aa52fae888a47bea337539644803957c5ecc7969473270fe7ca7022678b75e6447e7753b296166e8c0c0288aa5dcbae9718ef1003f5b850bc0e09af26ace3b98d6c94e5e6c3b31724f87091d8f9ff2d1892afdcf6f28f44b0a4fb1cb96e8f9671c840c20a00eabf249c1b2e4719f03d3ff9216616e617eee4eeea27dad466666b181fbd87a26a0b9acfb943b803a80ca30125506721cabfe288e8defa16688228a8e977273afbeb419d400ce206614c5052b257976b522809d31aad04771a44ef1235c3eb4022135ba7393fb944ee74f767f899b392605a93a7b2c1d6ed32ab85699afa22194e6c840b680acd09170a72357f7716cd9ea73dfe565a6146de9384834c24d60a176df7ff0e7b1fd2a2d7272a7bbb47936f108cf377e0c157eb8ae1a27278d613ae701dca901e1a567bbf152845744d0c5df4e370013e3dff3c91aa8d91088d25a3adff19d341e0423d33117bacd8ac2d3b7c5ec8576c8e6008227ca1bc9c645bfb190970d69b54b11993041dfefeaaba69ef6f988e71f4512502626b07201d364c84d56c90fc3f619a9b24b00f2d19eef175fb6c7c9db3703019e000c10c5ea0be93c3699fc5b94a283ba89e13f2b5a8afb2a3af5853d5070ffe843a1e9caabd8f2e3509da9316711ce0d398dcc19c3ad271ad5cc3475b5c11107f739ef604550391d957d662fe4a50e34726a73fbe47120a1e247b545e8def59e39976046985750e79abf9b4c5477cd508286e1011c309539850ba6c9520bc109b265b6302a0b170a0bc35f4f59ac0244cd82370abafd888d4ec14f56e502d53ef24c03f30793c010daaada864dc5aa204bd4a30b7b243ff1152533a945bc4836f89cf813e10fef31c07b5b7d0dacd43ccbea61880c8bc35c5f0b4f5e59630a8f1aeb822febd9320285119d72f15ffbaeb11a4324331e2ce0a9347af1e9101209f680e00fabaa0b4645a4e04ceab5e373e54acaff45d57717d929c69cc0c14fa55abbbe0312b563e829e464aaf3d6635ca1cd3b2d2de4a39e79efe3f55b847fd947c36a13447afd162213f1edc32e61caed08ba90a20b5e6da074b74a2621a0eb29e6541db16ab48534b772cad16738898bdfd3e842b890a6369c79b399f730a6507b4087d1c5eaffa76078fa59623b9aa813db321c290032deab6718561743f2eeb1853ed0315344a00ab4a423c8cecd8d146b1535ed25c0a9ac2d15cc31142e7ed4cb4151a6dee7a89c36cdc3dda901a771b5d23654f2fb767d2819d72d20c37795e493fbef4f9add9c6aaaf9103a6c179633a0c10d7b176b265ad039a53e0e4cc37057a2aec74f6061e2b3ec963f18aa9b0c74327c9c80a1afbf5cc133c2e890ade8ec574fe1001752d77b49e83ae1982e88f0516961b742e0d5044a4aa62db5eee43b47da06be9839efbb397d0bdd8a174e4ddd364e5a42d54b15dab354199c7058f06817686f30a907385fbb0ae55d72e44564ae994bf3d9782d1483d2180633772d4bc2d742488b5eaaa7fba0027c3460ad5b487be964988395d633cc33a7f3f94eeefb22db89413618f0502b875abe51a563b8950aefb3f57c4bd270ef316d762b7d7ab329e93a19a5dfd506ec63d200ae5a63d6e02d985611481c64ba2613238deddfe28f1eb0b2956934395b06c5a61c1bdfeb857736a3fefab01756d581a4cd30b136f763f6cdf2569456f4e04439f20fe90c1485031132a9552fcad82a3899445923a5edee0307867e96ec2e7541d3b5897b0de141b01703a312e0c2a543d9e50df7434fde4857117dd54a436845d57a7ead81f89e54ed113903277db3bdc2a233deda93ad255f118748b2ff4c9b8e190aeec224a0b0ec9c8e3aca01beca1f3435584fcb9dc421c808410f17601c3c4622cc40e481dcbedd3a18f464794159791bfd98168fcf5e7c9a2fe2a88cd955f853d0a690e4b53095c1b83fe1f72d918431b218f9f94f900bc624bf65f6e2a5180f95bc259802aabbf352c83a2a2cf586b3d4cd2a5282cbc2c96aa5f402954d7db7224c611a442429c39d33b0b6c4f57f077afb388b46d666d8a9290cddf9df890856b65c71ef967e03d316ba7301edeb98276a745b202e45e1eecc6ca12e68804626431a4de55328eae00dc317887a8d28a234beb74a7cd19650fd06da3ae4d006c0e850f09eec6d2d5549e46f0180a21e6ce0c8d997e327787723caf2e3e346b23a9d309f0f16e370ff47e43580429d0888ba247ea44a034f1a7a9588982170130c0c03edc250f928f669734fb7c1e90bebab943662ee26c2344357632e7aacd0cfa5d47c512edbdfb70cdd173aac1e70427c81f74970889e79110b91d80c3d6440f49bae8b98afa20a27ca8fefa46baa837ff42a94f952f15342798e8ff3f9fc773899dc29a00336144d71bf7854ba10142ee4060303af38e77a3e5d3b0832ee792228e2c707b03edb572382678c336979426834a5d1b55298f91296e9cdec3e6cefecd6c0f43d9308b54ab9c5719fc0d9062eca721844d4f6eb6fea7e959d720a06d48100c4620a3274615178b1b4a4d7b53d713f0139b51a02cb46c1488b587051cbcd70cad1674047382e9234828ff8b460d5e15e194498abe6e51143f6516eb11859e6c8e8e8dbee3c976ec28e408d81c9a5d390a75e5a5e2b06fbfee77d4d380ece5015ff9e0d4b9154d3160f627fe2bd847fd1298ec42eefb6d029e91cd7f2060f08270a56b6c04bcac807ea68e3537b2c4d0f1d4ae41d0370089554c840b586e59cadc9594dfe893119d0acaf1bfc1f65b83392ccdf8f1b847644c04b4d6ad7fd5d4ce17989dac28253b6e2f3232883f2d8ccc30581a68a5c21c1b10bb1c93a8936a55fbea790fb8aa7a1b8a5f31c1e39e040d2c5b457b67c3bf7e4870f4677f4a19c58fdd3a6c82da9929b4ad5484d34d8219db0c9a4cbf5a606232c40a137fb50e0c2c85488b1430c5b39f72674c12276e356a270cdfbec9fa705fa874653a611ed8e245c7ccf69f9d1454faa1c3dd1d6a94397de4c79d2f0902f3a5262a16274edffcc87b631b3948cd4cc2947542cae2104d86ac5f896115c782a00ee4a89177da304ea6ee50ba0aad21c80c5c34f787ca23e1003fba884d2625b3a4bee767a0f10b2747ca10dc81777d797312079718ec227476d6550449152b3daa840d252f6f5b10e82f25bc9c17ed61e1bce605eb46ce8758abe9f0dd3911b66b212f1390d0506d2c11857bab27525b5272a5a4b95a0357ae5f0d2c4a8350b2e8042d740519fbc09de395c010ee6b4e22755e694ad5df71b09b033b765aa3b22d527eecec48cfb47e39097770a09141e36e45d12736344e32ad630361134448d3b126e990337d485f2279f0fca461c0fad128f530fc15f8b923ce006407785fdfe2a06d5f4194e016d6f3c57cd5ac661ec10a9a6a8f7ed9f69272d136376b182fdf60abc24f497116f4d572a1b636f9eb7e026ef184484e35a07eab254173a83fbe147312c6106a646d9ece9d448e440045e1def6f90b74466721029b409240a849e98ae9646342e3ad809641e74ff6c92eab02023255043d1f0e0e67168acb766272c1506bc14e2164b4ca7101ca09d33b6c3361ce6c2592526bd50990ee93235853096f573040e6daa11fe543f02558e453fee17af8c669890778e7b2c887b3ca9af3f419347050bdd07088063703ba12c100239ed2e9d56c1ee7d00a832a0b5713e5c0f2f45fd227757aafdd15938fde3bc5b417880b7e49c89eeb35a46a68d94468f7249077eb55d1b9a39db78d86de774aed3e3e7b574e61363daffb5bbfcc7dbb1f049b3aa9e1949dd566bf9d31c9ebfecf8b1c2fd5a66253f62d554c874cddf04a4e879f30f0428b6b9990b36d68f1314dbf3d1070c1ea71745330e60fbeb19ce7f9583de72b7cbb76d79b00991072505162a4c4eb6d70444ca030b88416de653057f9f1c6e1df800e13ef3d456c6ce9eaf825faaad51d7031f2a06f51d23f475e89a6621ed9c2d6a9c38998ac10d1fe344e90a106e41843d1176883a62d35dd119102fc7428eb9baccfe4a786dc0364c5920501a97e176d7316969f876f6739c4cbfab26e1251cf021cb1dc48bfaebc7c2a1661e27221c05315d1f41220f8c2835dd4bcc4b78567bea10c8ba4090021bc650db5232ac295337e7d40d43896e5d733f5fd8d95117c707a8d5c662681f4f3bb9bfc143d06a2d248108fef9506536d4cd3980881ca12035c8604f2f32161027ce85c4f7507f0873e2e7ae53bef9467828504d4e3675b22909502c79bc87394d48cb7bb209d60dfa89234ebd8eb3772631f80f01c85790db234ef58808313f398ae307c6f045a3f1ebab74bc9d09339e7770b569e744937bc2763cc4d6636d3bc682573ef2b996d96fd699dc7cda1c7c6ee590c6366666636cca774c5156e6813420e5816906a7a41701fa0dca1419b42d543b2695d649ff95f67ba61807f370200635939b47dd3c23f4ce54b1e5c2e155e9496418e240f31c7b2462efbb7579e897432e4a1664e357374a246216ab4426f97827616306dff2c7153692b3a8f433e40dd47ac317836e98ab48158b0daf241273c5b1998eff9c2728ba6575a8de0d4e5506ed77fa35c0cb61e4f899c3888eb247b615ef28ad104820a97474f8d5b2c430c2560f68b9e27b8411fe0fb85496d3c5b508c7828d3c3a536412607372ee4ff630671c31047b250640e72ed825d012aacabc7950998774a54156f78dafe42781fb9a8ba666fb8c4ae8a4f6de0e43508321dbb0e0a243669528246c8cfe34f41c9e1f11295c46ed444e1825b8a076b0ef8376b6d321ae0bae95d83ee51f95336304fe008fd93f82b58b36cbd5f822dff0c78d63cf1eb2d40e9d4aa7f365aa688f6072bb28f2643fa25816012b3d328a1f24e638e4c016f43e31856744d88586d60ab2c6072f897fdc64f9f3bb1850bf400f4a378dc6bc46833dc661b1e586a0a869c826b54d5dfb1d7c2c9f2a21bdc44792ae0dbfd8ac8095f69b919d4f58060a2e88f988ab14871c0cd63f0efd42403d717cdac51f26443a69e1933386744ec296d79562831dddbcfa541b5726ce8eb1b64cf3ad9b35cce735e90ee36865fd75c97e4abe6aec350fd9e043835ab4b026857d392d8b09cb41d5a4cc9a35346f52abd1d989a9bb0d62829e6cc4ae43ad77095de51a73fdb46afae55203c86bc182cf1e1f22981e93851ee4e88e1b9a7ca2ca8d1faf6a9576bf6547b6e63d40f188b1c52c48ac51d7c215ba3fc77c5070790c41a409a2541979493c9320b851a44767df89a11912e661a85acc95f226283ae522b6ae63592fb868b6b74a622d80e80e96d4d9663eb78ed2a40b2d824a4acd9540db9a81c3831b0a06aaef679994d5f2e10c4d71264b7d41e3d9c67035789034f91c479d5410df09ac25deab0238987124a67770e26ce4a54e42352334fb10c9ccf3a7d0b00c34f3a2e75fcf1e00e52678fc3210c6228fb8e510a0318075a6c0082fa8b8ce72ed2a54fd8904734f199c77ec18d217a34d1a225c2e8c682a60599f0aa0af2649974306992da04e251559a4e7382f1fd3d8a4ac6370e46c20abaf6099d2d84016e9f2e7ad6b5cff332fbf7abeed81798cfefdf44f2cc700e6f62ccd5a2006922ff8461dd0d892630f3709aae03f3b36daa95e53b4b196ceb21ad33f81ef200c2e57b83664e44279bc9f4fdfeb5e6fc92f408298ebfc144551ef4304019ba7b583111f9c30ec66082de8bc081b90ac28d273857f285e61aa311ee13b676c4961a28d3f102b41be004d188ebbd6ff37feafa90bec272ceb2e1ec33a0099770c744416d9e5341c1ccfc1a588b6a8ed54fa65e78466ec6995df8db07a0d1e44a801adf1a85fb14b19d30befa0cd0bda14357ce81536c29445604f3775831e43504717de0ae52972832c8820d74611b1a2bf0ea6483dcad463f2d0adb56d6a45b6c4281e32c715e9709edbfa7089629dc2dcf70b4949632ef32dd77f1245a60c9c48b161fad4c855ede1c7ac0604324da651c362a776bde28fabcdbd30c5108855420f776d40b10c9d5dae25c1ba3889f8cdcd54dac626df8064bdf56e765519d82faf2bd9124f804a58b2fb0fec80bda3bc0ec798848dae2621a1339a1bc2802a628f5259cf00ac225f6ab086b6786278532562970502f380d49b3954da170581d96b3bd1f79d34afe3886eb49d9ff2287f1b5f94691089d46fa9587799e8718ee1789515ea918f42f8b11bbed6a10451ebda1d12be87cc40b3582971c9596df7827b1a32fd45f3d479967ddc34d3370f8e12b0c43f5e6d49a87b2596c20a6f47a4612bee31ac43da2f96ec073ac1da3bc56b731fd774d3f822008ed3969df670b48d7223108c3451d193ff1dfc9de0cb000642a8f188b5550118ea17c95b21f68507e705d6d125a078929fbccef65f63d16e3516e1cbc4e80aa894dd00c2afd388199ab9de1d1e7352d889c8b6d3a637453d0ed844ee3a01ba52fa16a147783217a2029cbe4680d14fa0b5ed0d8058073c18ae9851a603d9fd02b8912ccee92526e75f0c42d89309de25c7a3dcf6c3801de6550e908c58537a9e41864310661c61e94672375efcee09389dcdc5f66717c86c744ee8d50940f54c85f2fc9a8ee2824d22b362c507ca4e9b7dbaeb0c80864c58a0d3358310b769153eb580d47913127db0287d75c1e84dda7f1e8adb3c457e010231e23be631a2b806724ca20ed9581920fd857b32ecaeb0a1006774e1816b983bbb29cd0351c6d1373aa10e8923ae9fc94ed108a52fabc4b0c5f1544c59c2e0d4447e50e5db7d8d07024d204d597e0e7ff9a4fc7a9e5b64c171939c21005819fd344ed80529addbcd97083213b8783a0d35328a51954c085bd1fb7416e21e4ec2218a2423634bdcd969cf5a593beb36ce4cb7b0354c6c42d0e4a870332fb0a4b6da29f5fc4bb5ff2a6c355ba6825addfeb60bc0c287e97e6c951cb19be1371a419e5bac0b9bfc3f28690afc62aefd0e9f18b04dfad528291efee35fb8b4c66fb9bf954b9118f2ce1e1276385855ae0a2267e9f8e665b2c6cee23a5d7b4b9a7d82409c1b1cd2fb1af8f0ca03442e0761c8f4e3ea25e86a46174ee1ebbe84613534f50e4e1ad2b01645d9122082cf84d589198c717c228fff447c95be7b5e558a85e52a2e94c55eea20ead7fb64e565d32b50b7c110bf4b8505bc2f1e07d76b363472db05391359fb902cb97dc265c6cd52727704e6134297bcd346f58d4218dda55f9da213796d347867a569c5105a40ec6f7ccf046aee230a361082874e115264bc25a270cfde7a198052a3c24a6e8e4f40cdfa66e9cc0abc9410fe492b4cf60af66437d5897896d8e5592e86cfc651ba058f8c87d90d4cbf6270f1e46aeeb1c4a3e77847b169b27c339a87b91d66507156f3e87a6331ad5e4e22bc89e3b6416739dff4813680a8d0fd9bf9ff9b52191dd985754f271d4cd9f21becb512408d8531304d380c80da54ab02291326d27c6defc6aa9ed1cbdc51c05cce30238fe112e2f3cc326877d6aeb472b3bea01b9909edf4fc2c16a54d9bde746590652bc643185dc07f3f15347bb2acda8dfe1cae8b9c914d4a3c8e767840c7ffbf0381df9c55ede8ab9298aa18428d5feb46410f388b846ab63da3c946309471f5b0a982686a5988502d6e8f1dc74c08e7ff1bd320bb46e1780776109c2208eb1417dffb95a487597740497726180bbda7dbb831979ea8a51b44d70800022a8b4223fc942327e7c5754739388a7f76d4c397805dd72887c8738756ad7ee74939376a8d49d7cacf858bf0a08a5aa64ca0caf348114b309066c67a6868255465d31554efb2bc42748dd9e69484573a032d9a0c880a90e3e12ad2dcef8ad970d8b04e3eb8bd48e347a658a892c73dca1c9748488ad029173fb7d58b753ba7dc33005f4e8d3e5f3e9e442da81be733bebc9733b95d55ece240f71e891c79a64bb23a8647f0778a845858ff85ea61aa4a926cd5bbbb9d924e76017843c0e35186f0aeb8ae12098c4caf06b2a5331d83006b47d39f6bb84f5add049e7b46c744ead670ffd58a2ccf22a618671783c387bb810662f1957d4e079e2de68f1c421a6ee779019008a2e39814b144a6578d579e121819799201ae4679499b01213c686037a6c28c35d5a1b686f1a623c89191a19358ec71bef62f6a04c4fb52b9e1399aeca00d993a5677e3d460200cc2372ef0dfb911543e44165453f1f23f6b787dd77772c9734a80443aee6a300da711e271c9d70960df3ba5f5817321da3d434d6c1313365f709340946ebd6b458a061b6eb73eb91459b00ffdc300bcbca89a16068d22d3e910624543c395fa9b28416518f4acb060ecf8cda51427c5f15b8a63f1c0262cee1e8a7ed95d7264b5f43170ada9ef062fa6a3f3bbe428ac9b42bab5bb8c6fdff66310611422fb262f6c5e1c9bea9a2b3a45c6b1dd8ebaab36ccca1a99d75f82fe1d1bce7c1c283ccc3db84842ccaa34245d895d7f67fb379cfd6613226ee557a368cd37974f8eed68e8be29321ec737d5b524c1e73a25f64f1589679b2ad1318d6dd2d825d27e5abf1dfb89b8587bc288d5015315f5a54e28041e73749d522a92db017d75e1475979b91da947d821b21723f3db9295671fae647e0309611ebea3620bb31d7f0aebd4655df43ac39515daa521dc14f926a59a5177e338b6f2a38a4459581af11b73f58b9cb2e7b05797343ed813bab7d56b4576d41f3bde6e27f6b9ebcbad079f650b66dda0518f4b2c86db180d81e8b5eaac61da76509846def8b79d8b64b12a4e735c16af521cec3ad27b15cf44e82c46012ead88bf4485c5b60fa94110f14b54f12c21c3ad3e7114782a589e79025562d2ef3a5d33cdaf40ced8c094d900824663e18ae84263c07840536c220a17d5c43a6e85fd2cc1462bf1bc690f48c273bb01b4505a8da50464523e594c34734edd07e31e8f93163955c7ca056a1f223d174272c59725bf2a6ca33084abbb765355a19f9839c3d6d87f7ff2acc55a79552b52211d1acc5017429875516336307e3fa8fbbe2cee7a817f2c1aae411eeef09ac0a4ef27b4ffa4cf3a58ddf9037fe984af5d78ada657a779e3eb3ee20aa910e1ab38681b03e1ec76e7613af7ef9ba47350265158fb494687f2cc55bb6a2d67e0b53a334bf585243e13af5c2452c5e0d2eb6aea38cd91c4275ac567f8ac729d47c61335cdb6bbc4317e9679436e5bba709654e49d96763063ffc58282a4fe1883e4a7046913bd508344e2e4a2bf6f6b62f6ab44c099d8948988d3c214179389f9bf2d43a42c5ac20ebba0d56865a292f1ff3c93d178df2ad6b311021af1c2d871aeaf2ea40b1768c0ff3cab3da038b08a45e1019ff77699096470c044a48d727bcb7182193401574558fe58b61b56c7ba3585a3f4400c4720206ab3b0570e881637a0ecb05885458c5cfd7929e13ca5a14951b18982e92945e1579a6ae36884547c38e0ce353195ec03e374e07f65dbde85b2e8f174a0603a579e2132496c52466d1125b30e68afe2d96b546135cfedb8adf98e8e882f2d5245939ca446df3985394ef080dc324ab9eb061d30873734f572e922d3abb2faa370f4fa27d59b1362fe09a9494d1c1f015120e0219a610c8a582b160178c142eda3d49e2f3e44eda428ffdb546b7af3b13aae2e80822c3752c041a3932aa0bf2e94492939a6cf95961328a6450beae94aa1bf0b5effb754c98cc1cf492edca4859c5f4b26fea5cabc9692c8dab6308091691426542c378737db809e28da8cc34a699b75427a76e2f2e6493c1958a35fb75665b9efa57297b107468c987c12031b34fd6b47084b59f553e9833bd42c957faa1125fd1981e644fb2e738d30dad10c473949dcbfe1d3855c3f0a32a2268b990c7443ce0c3817ffe1d0df8c74428ef154f0f904af5799954bcf60d85137fccf076e31124610f80cfc1131cf42225945f469c878573509c0702920cbd0e1ee56ea69a7d490580e0aee677ef78eb05020282e97c7b2548856e825c67f0c35fb6185c23da3777acc48636401519f28c93c93b875e8c09ca28d092315189624c1deebe190746df0fe88082551b308c6303a3417ed682c9f1716b94adc72b80787f0697a4062e6f891b0701a7a1ea5e5bd66c9baeac354de33ac65e0d995e98d91fc98d79c350e29659dd93c7c1ffaa83c0dd5e70312da0137d228abc1f074fdc606caea84071a0d1cb0869d705137135746e68c05260471ee501b31bdb490e6cb7d6a170a87274cf30003d1262642c2372fde55b048486d2dcacaecf8cb280fcb530a3074bfba8b8e823ba41600428cf91605641879aa3d1622b5be95b98751166a27011f6debb0803dda2305dc5c3120986da95839ed014d2fbbbc92466991ab207ffea54f17231cb2fac40d9682c2e1df2f48417e11feb2679f20465b474202cba751a60afe8651da7f84d118e50b4d38f275aecdf2ef7fd89022e3968074dbd00d14e23ac3c533a82c1daeec2ad0a729c0ab65c5c0375c0234fa9d0cfe4b4a89c8e3e20cc522b18c73c56d62638b19cbc7f3a0b5d03b108fd1b912e0c5458b14f3cb6bc49cf6158a035a77cd38af323618f5c08311d77298ea4324f08266a6c561c0a0188136c4f47b16ea3603648432f45764eec0b090b0ba6fe03bdb057e1474e6cb369fa78a2ee484a13aa2bcb88452d7977b4fdca0d28829ed07f09d3177fba494240d11bf0433cbaded6ace02fd2d0e7303f0634a00ed92a6377e48b14a0483d560bb94ab55a4adaf5a34e2338483cb14440e9f8c9b172d52d59f31aee3f7b73f1f714048fe511e496c8550e06a700ade3b983a0b426d5cf4f6e29746893c8717946c88f5249005a271637ead003df1b2cd41f41b08dae54c00d2d4d8f42a3b100aed136485b260844d9927126265f7a1f05d301499df38d4a003d27a22e4ab0f382b559f66a87624ded8aa84e7e623730f26da39f20075f6231852f8f1004b1cc032395bfc362aa9c067209ff305ef02d2c6644579a9664781a903c2a45621d8ed70839ab84f3e9a952293fa5e82915dabf83f07de1ce2652e0b5506cfb20f1d0648ea33180f477c69e451a7b58856312409bea0f6e2274e65dae8420d8bdc060adceb745251edc9fa068ae23006276eb161b7098a5432fc9d81e81b0ca498b24820e5b53704462a4a655c0146540ca16009db6b9c5af9b52d23b05752d548596c60834878fe92e070fbb5daac7ada19a6564d4fcde160b013f20ab5786d29ccb5d05ec4d1a10368c2ca362b935d5b2aeb19ca10120f34403efbf78b6f425070ecfe16ddbb993dcb5519adc999da7da9a7e3dde8c2cb9dea2fe843377c538dac473e6c06f812ec5314376ce44fd69b872ce849ae27273c392a5965a03674da7580a229b73263d4b7f48bbff652cbb78b6e69cf9e0ccc71363af5a23a084c6effd28e59ed35dd98ecca00e2c3f866a7a692665d3f89899d95e8ec7657a7955f4498dc06e62a4687655cd99c119b468e804b0307ac311e44aa3b007e680098e1f1f24d4c8dd9fafb3676a45212d7986a5e2cd3310c09ec7daaccac3590c8a3677ce9c10e20801d47294dc0d74da03b6fc0bf6bc22e6cf576d1ce6d25c9cff9c2369dcbc99732c72dfbeb7fe361fbfe4b2c4a11f8fc51ff74f4e4cb51c428c0133020a005bc4e94807c27f921a0c8e82b8154fb2df29c56aa6ced8abfa6439b8d02d55d3fb5b4f381eba5f3f536573c7060849301f96ab71dd6cb24f59ee5103ecb2c426803595d3ede6979f9161a9d7c2aee61463857b7f79da891926eda4b73b87d794c9c6be9f449ff19141cf65b29d4e93b39c77cd4358dbc4cf6b4d517cc52b61382f91ab660195aa7a87a4a28926b3237ac437c7b9502c91e17bcde2a847e4d19b0a63f603da66dbd57ac7c11c74ae130db409f89e336cc4d86bfcb63bb3f062b255c54cd7d6356656dbb3c77997c32b36b126343eebcf0ebe2762ddcbfa9ed03166a55144d630970207dbacdc3d072172b9feac02046e8ee44ad24d3caccd7fe2491cf998c16061c78819e45952a040e1d1a17e56e8661cdd292a7b6ca1c27a475d1e0850512388f29063d46ac41f2347ad28ca1f4129104685b2703c07dbe18553a8225655971bf1fa2185b414fd12ab568ea40af9831639ac47532a31c62e9f2c967eeb4290c44de9ceb868fac83a15e4b8e5125163c4bcf6fb7ef57554d65dac7da1c15a03a4a981c476086c201919aadb3931f35609cafd067ca20fbf98879713a0c87fcb299d710060c274cd3da7f0a4f16611cfd6873c429e504474b25c65cee202c2779de260b7c42d202609125f4a9b6ffff35b97f2c3c21fc08ad1523dbdf64bbf65a937ae8729371c039b59a97c0ece0c438c2efecfcfaa941f96e0802a464ab1f4fa931d03ce0c07040e0c03f33da93132116f6ecc5dfb9fdf9a94272ce8802a9414324f47e6c14617fea724e5cad009c310fa03073f5e46c3d218a9c7cd5f5c94adf2d12f26023c70f70131e2c2be3e2a3b8f73a62123ff1f9871f9e7be742747d5f8825d5c29868c6a5745ba28a55d7aba30fe2caab02f4ab7638107e4cd629aca00a87abe0b5b81a86ee00b7f5d577a38b2581b1ef34ab46c47e3c62a4fef2cc02977549bb691b55619ea0d80c6ec46814a159aae992d400ad34ca3f5d719ce9b97f61a0186be9ac02700494bf4e0acb11ab2cb4d833b76060475e49600c3a30df85d221107f8c3ab0e485e02f41907a1be6cc627e90974060c54c7928dc3b5b07c040823864549c52d100acd95263c0f78405389802256be0f66d36d0a78a608d909b4236a5f59ff1382fbd2b7fd5c5bb6d1586dd9399ec46b415f6330b2bd9b83c2ee55dc045759223f394a1974dcca9bf72ce36ab44f7f0671f894c47b836c4819b6fc47916d9d20de9a727d8342682d80d22e2ec2ef56afef612ccd31eaff59cb6c451c5c2985c50e836b65d1e22e05513bcf83be4f9d9cb44fa54859d38007174068f305513f2e00d43cab5abfb108c16e39c41f6cd19d2ac91970867433db8e17be369ee72671a052ae556f8466aae55ae132d5b939b7c2848a6c884fc50cba5ff01a7e942355f825d8340bf000b202c2be17de0cf0bebac8b3f6dedd69c0640a7a8118a823210440739526302cb9c47706f50dce50934dcf4e5cff93c5e8b9043c6d9d9c18ee15e8431b4f12753f2557882fa29fb4e80d72d8db86877e4e17bb135f62fbb3fcf9539b0307ba32d1fdb14d561a464dde926e626261c9c63608303157b68143136213dbb413fb2d347e396f2f048c2be045b059c60dbb255171dce29e764bf8e961d12c4efd4d83365eeee69ca83635d7bdf185ca86af01fe972df4ee0ebf7fe2c84a98cd2d52994131ffbd34b39d8e5beb60312312743946e10dbfbd44c0aec1412fe265fd0a65743c52366c4989bdde2dba576777ce939bd132a6128cba211076a45c070b5f918942b7d5e0b9cfec2695eb6f8ef4cb36f98ffc4faef6a661a64787d9a9f2299d0f4003d375a29d527259a539292337adf7e389415ba9f73135e8416c589ce0477dd9bc5bfa92d3808fb5bcef1284ee3082c53efd4b3bfd2feeb99817637b8857f14a80dab7840ed30ab4584c087887806940d1e3d8e86201e9e97112cc9c7c110ccfda590bd825742733f437b9888f068a70fc23efbdb950930ab81f9992c341c47b73638827e0ee4cece8e3a1ad1a63414e0e854143102ea69dfd75ca1741496ef2bc9408541e1ad6f6e58b93217a3ca8541dbf4585e282b4b277e7154b9b805c43b33bee0468a650914f324feffca65063107d770b5f3e87fc931550b2918af665cc91d180b2f51910b969b2ac50562c16996c9fa0a160c22a19ad734759747afe6cce98b9ce9807b07601fd8fb05c51f4c55f46f2fc31bff122e712e51f4d5e25cf58b52d07e6a9e51cf1c1c28803d4a330bbc883a08d3840fbedbad55a1f0e4a89c25f42cdc8cf3a4a862eda5c0cdea2ba741e00c31f218c94231c64d475ff24038b85a09dbab0692342cb30685a5618a3eb5fa322e613b896aa3b90c9a57ac0ed2adcf4ac64a71132223222e5904a8cb391f1e1f297da3287cc276a054a4fa0cb2e24a1166f2be2ead1d03a55b84e49dd283c387f91c0528166dacc5231263173c48c6ddadc01f0e8aaa79dd00a2ac9c05ba432c2057ca72b9d51fda11038fbaf546d558ef17465bd32fa4dc1484e63006ae48b113c60c6a5f0a440af934349506a7b57e6c002493214713ef762b9b0c4dbe15f63115aaef3af893abb55591063996f41fce58167b74a303b0d5df7780d12d558ddd0ea7696b8bcb509b78e1bd8fa9302a5ba3943fa28d9ed46234e8c621174829e22c835e3fd34515e5e55d2b65a4075235543d58028c4c74a64f28491cb38ee45617fee85984c42776829f537c0be85b234974f6a3e8e7d613c0015b817eabf2ba2fdb93262bcd16f443c0ede0078550b362e849f31c4d7a3fe6420b6182177634c1dd57f3aa998a01651bfe33b0b81fc7a18c48515870c1a1ae88b4158b41b5244f89dd9b5159237a8123be905a36e8b40e5a1705ce35fb74df9f8919e1132b2fcef2458f05171082a0327818affad2fa20d0bce082b0305728a008c03a5990a1ba9b6bcea0792889c1d62c2890f4042330bc71a81c19be930b9faae1356287928f2351867c811ec40c82fcfe44ab8e5f94901edab3889cdeea6c9aeecfd5506af9e849d2f79cc765952fd6cbe79d9588ae3bb33b1ad6df653e0e9086d6a3dcb84fb6f2dc9a8637996684518f43e2672995b6424c0936a95b3e3dde2669353de7a2d140923018a452efc8e2f4c1fb4b6bfbea0150c701788c07600cc1442f73586dda14054a97bc2fa5b881d9014d8aa4971fa487da281e617020c0b68d4e2c48bb381965b63c5e0a695d5c58d0671cc496e430c787104e6fba2fe6f29d34f3b1fde236579f52ff44ce57f7149d8bdb7ed3f7870428ada5055c921dbb3b466adb907ec6a1b6a990aab0d7ac04e6e6ce6df49eabdaf7ca43b7b1913ca94e8dd1b9401205aa90c4b36822b391329320c9539478c1e071d77e8d011071c3becd8a10e1c39e668393038d7adef08be63c71d39748443c71d3be8808372e0d6a1fc3bccf22a388987f307072ede4a5f8e4df8e2cf5ee527baad4a855f9b59b98f7a82d4cf1756ae2955f16756096d056c75e1f012c1cb93e7d6e271044171d4e2a96d09c04545d23084a3152c267480532930ccaeac78766da09b0aa9159733ed4c092e7bc4e6c2054a1d9f2aca981eee39afa134596875af74774bb8d4ce720b594e5a73d6ac07eeca4a08adcd9a61915b49bb949519663cf0165b85b26ad61963ccd6d451d69d31cb8cbbb1325a2bb366bae456d22e656586190fbcc556a1ac9a75c618b33575947567cc32e36eac8cd6caac992eb95525a83c1a7048b33a1bcc50f975c53ea2710484f4fac26b5b69d5943d2a3f105ecc467fb9e913c3669e39254a7be09664257a951e954720ca3c00de277ae9b976411645e567df59ff5acd045eda356aacd8969bedb64b6c9a756dc5b4ee968bb56cd1ac552bd6fa719451d959f12542f0dae8d7a73b4ae59b87b22701e66a075f90242c2ec653ae791a9d224235fa72ca738273ee7261339b12c2afd6d63092bc9806aee44e00356d86b3772f6569b2e05219310f0872115a38928b10d4dae0e17ef053494dc4c5886c94901ec8213a50ba90a0b39afb1aca722edb1c801b9daa513fce47ab708f90c2aed1380f51b362a19c408cef9e9874b45376036ec1a2f11288c07ebde8948b57a4d2de8c5c019e18db290e1cd28e50123eb0fc2364b962b8745071da645ccc002b69a070c48e080d49601f1cefeff5fac933fcef1073cbb7b33b9a77ed0a83d1eaa8b6316622d4bde0cb1f03153adb0b1f4cf6cfc55d3e9c019112e55b38ac7e79ad21a95f930bcb651c0bbb24528c474aeb49eb00b2ec22fa981d676968a07b14c55cb54728d31e6d130142b838df9ffe0773354ea5816e40c96f5bddb6d0eacab6daa465ec3e911d378ef8d33a746ba2439badbf72dc0dfd020e980ef23496df2a0345b899921f73fe8cd7604be6751d89ece9a2f97bc57929c985633c7c3203f1b5fa81a056f8b952d0cfa80fb73c93a50e626335b61c70c40fc813ac5c1b1b8e68552e05e2e974b36c32add18447b2d2e177b1d14f05f3ed31042e8eb8dd186027b4ac3e2e4edf0d7bb2b2b84f45a5b51d9d1b0f0511bdecb33a88fca40ccf9bd601d2772319555e43858ae9794786a41c85f1ace26db60e4a73da45eca6d70ec50188855603818ee005100c3cef92308b8db5b70f11eb1e0f0ca0dc758992a783c8119affe8b87871040649c5902be2e2e4c860ff47b3f0e1e63bd38d9c40720139bcde1ded593f2bff48333edd8895abc437d7825be87b1976c0d15a813d4f22055293035db3e895038c1c31e143361404714eb59e27aa7813d3d2085f61e18c174c2260689f317ab3d912e5f0dda6e9bbd8985d3401a4059e4e5ba0f496c36008773ef3e4d3e2d298947882372b6b5cba7ee2a2709ee1b1a3672fb4a5a1bb124d4240fc64afbaaca3f0365acc1f8b03ffb723ddf4431bf3e7430b97092c2f7dc31876c101f114c3abc90f4d2f6252ce98149ecc5ed8acd140defa71dd636ca1455be13cb5e9cababc4def79878e231e33ff1d7fa33cdf18befab12f3191839a49dc568e4f3883ebf7b90d9b0b1d63a8b95c5b5b20afb6fa3b0cec9e160773cad9030412343bfb00b5ef33206a03e5d03b661e7ed88613431b2040eed0aefc126997e7336ee124fcdc86cd858e31d45caeadd590575bfd5d0b764fcb8839e56c0c02099a9dcdc16e83f6c40b35061946a6290149643b4578388b4ecfcba63701696bd947ea762ee6861bde1a765ad8f5272463887f756cb8b3e67dd97a5715df29a0e0c5d339a223f3d9d26af0b3b6bdfc5cf5be095eba5a84dd27928140ca026cfdb1065040138038fcc890b0cfb159dff8d76385cef2e880b7091d06da2810da3b9d68e30f4be18b8deb236b5d16918c458422d83c68bd878e9410a2fe3a4a76ca4709e7c62febdd8608748864c5e6a8c87f49e25193061881e7701527d99d4083633f1749b4a6d86baa84ce8bb79a48577b58b8f866b283c566230a3840b26a7150ace4181b8320e06c07bfc1b181cc8439aa33ab0cb9a890238d60a96bab9e0a1d157a152a09f17e4ea331a96ed1e031046450025809b16a26ec3c780a4eb9485aee49549e7a9d2bc505cd06de08b3dd0ee7d9a474bbead9791b69b68b64d0a23dde3b17d9a16bacf56ba96d857b561310c53ac43dae331958f2b2476ba0e0428ce6f3417c19b8258a512175b58fe9c75f79909bbd4ecbf0033479461f63dd19fe943dc395a0d6e5ca99c2b98f5ce3ce4f9f3a60da3ac0696bc84f04634c159501c5515f4eb2b7ec8b0e3091a739a0b8259673959de6cced29d11c561575e34588dba84c7a76665f90decdbc1f9d8d841705ff4a688e0d231ad4ecd6a1e8466744b1b4240a1c2f1fc4cb86e19a0ee2943d448a0a297ea7b27a9e96da16edb608246a4d5d3c1fc8a7a5349aa46163423eb0e15fa8554454d60846a3cf767a340fcd2f0b35b456c44282894150461685fea1cef0ea0f316fdadb6373b7e882e835b7025a236544b84e753d6a8c1abf1a60940dbf970be16f8cf7b5a67139d8adec08bf3dc1d4afdcce654b21a3ddc8fc4a9741cf9c29322507b176991554ef53a71ba7c6068f1acad74da87a6a47856174631ed7e5a20bb122055ba38eaf43fd69856bbe98d2fba8cb8cf45e649a08c3f767101cc014c46aceb07561c4c04a67a4017995aba4c3790325af2b52cddb4fd990aed8464981ccbe9c4483ffc9f121c579047ae82c9282ab67f83394ca0b2b013c5d010af4dc355770ce61750acc7240ae50e5c6eed0e64552951712ddec19e28f9907ee30612477136af386f61118d7dfb570de7c4c681f1a17cbcbe442e5640ae7627934cce10ecae6a60ee50db66f3adb8ae077af16e826774a726fff1d310353034603bbd91abffc38f9013491ad3faddd27ebb42e0a815ab25104e6c8c69b1841e3436badaf84d45b4a84c494f20daea8dca3b05a62389b43522d3ea7e8c72edbeceeee454bf596369d4f280845d13751c939e77c216eb5eb2cdcbae49c732e06d55b3a241a8fc8f7504cfeeb160114632a525e623a322358a5206f51e99218d7dbbfd8b1ddb54dab893df7fe5ddbb45a4b654282836539f671ec9680792cbb1a42d13ccf6ac2076868beef8e919022393c2e4f366b46f1e60d14026e7c26b97d77f7a757bda5c121d108e2e9f84074adebb60994b4ee09aead75dbaf623173aa4ac22c2feb68df5f4d0a526fa9101152fe208959b2b4c64d388e3c5e3d40215b8df242415b3846476f06a7d06050573713ac8cdad0ca2006f3ffbf4b918434501865e6bddb3e0b93c403bb9510dd21c32b47a7a9882964f4a5e4a4fa78d7366def7ffd9b2ddf61fcd4bebfceffff1fa0cd9cdb2521c396bd01f5c13313beaa6214a198f8ca5131817ccfeeee6e347c71eebcb603b1a123693161a59c734671aab7f409fd0f28d1dc1bfc0f29c3e4c7bc20cbf250396041e15ddbb4bd8bf07b767777a42c249c7b4fe7eebe33a1213a24472867e3c50b628e0b0a510dbe6727e672391c77777777f7202e45a2f1c4b907714076e7f9406f79efd96b3877770722aab7d4a839001db5f6271c9c3bace52ac6d58f7a25963688745f6cd88d301d399ad1e5ee633f50398ee4cca880445afa1191437d91c15ddbb4bd995eb6e9459a59bbd62517311185a42a2c9f2a1f8f9755c60739b2e433f4734b5c9beb2ae97a9a58aca666347d718506a31f2490e5a91f8447166fd9658097845112381fedd004dd4d582da26b83dd12d7e6ba0e5cd15a6b2049f5962a974de7f30408d42b70cf2f7067edc0427304deb54ddbfb573282918f530b41bc1c434b30a6b2dcfcfeffffcc1d1edc7fd631710861884168bdce05efdaa6edbdc232a237e3e07b76b5c5dddddddd3d884b959ac4e02f75bab2345592daad2e40b28ebc5ddbb49deeffbc5c89b95c4e664d9734904b66d48b5fce90231f4515d8e8124b2fbb62964400843a939503ab36b304cd8a22b49195861d58402c79027ee1220691e97892e1f3b58b8699392504b24d5b62ef481ccdf07bee6469ebdaa22accc2ad8fdd1a6e11b78c5bc72de456d24ddc8133eeee24c434ce4ee3c01a27d6383425232e30d6955b3ac4c6c7552583a9b5d33484daf9704319c673e7fdb825aecd759943ddcb4fadb5f6edd45b0ac734451e10bea09c73ce334cf5964a5165759dcc889dd0ec5ae5114f1844e697ecc17a1159660c3073d0d3c00a9c94183c3830ef4e366ab1b5c23291c31eafdfb3bbbb3ff5732765815dd05299624083d05411244c4d5490cce869986293dfffffff6f2cc5b890915b41d972c29b8246997fe55aa429168b3cc5a0a25091a88854646a560928eb55aa9135dbd1b0f1003272411f71244e73ec4312791ec1c4258466bc51878ff7eceeee4c285e9c7bf03dbbbbbbd18c13e7ce33519fef7a4bdb15b7c4b5b9ee9531c32c1c81abec98e80549015271e532637be0947307823005757757712084ef7d72090af311c3062a2689e297fdff3fb9ded20c9c7573ce43e0213bdffb493e7c21592e930b38764ca86b6b63e085e6d18326b2851d764b5c9beb12d9620f98bec1f596b659a78265310192e20aa9eae7ad1a3e65bc8899c96514a2ac788ebef0aa6924516e4c315b286dc9744f392ec664f411926819ffabbef5c4c9b80e69261ed30a0cd06ddf3f5725d55baa5c660e554dd7b9e00c9390fd40babbf3dc6cb5536f291ceb132b1aad81447cc35313fefff7b9998318d0dcfef37c81102dc6863a44468071d9d440aeb04a61ec040f32c566e728c4696cd8d5530ca291330386870ab6e20945e5e55f07f724393c1e8ec35ff332a5ab088bd34d915a109d56bf3361c38eac6b6bc788ebbfecdf2d716d25399bb43d77f7a41a1a9c3b2cab81758ddc32153b7ef944249d6822a1313d323306486fa6f4815f1519083da22dd39d791585553c4392a8de5263e6903cd60280ea322cd3911bea04800a0bf2518304588f1d3771115186020d4ec0e44a70d960690153ae40116080686c5ac134d9fc40be2c19309bd1110ad205f463e1844b8b1b0818f05a19723c423da990fd789249326aad354b53bda5ce27d4034b34e701f8aeed0666d2bdf5e9a6640a366af6fbffefad37c6a06cd8cd1ab6407ae4e55cf9cdb8d0ca11c118cd84181ab934c1a69c463f6c58cb2b513d97ee3f7548f4dd5f49885c386106754b9cedf45a1ea9c771a8b523f366b311ddc731eb04a3f9822175aa01cf02459c197ebb756678d7366d6fd7efd95b9d5b92ea2d55b61d5a96bb16c248840550aa0b860dd205fe2970fe42585860435081e637b10e25730f28c011fc6cd7a312523e7777f7b8c697c117ad91c5c1f596b62f3976b76b9bb61d8ed16affff4f51aab774d9747e90f2f4921b37d3188b6ac75a66c2ac20502df4cceccc3499c68c18728c6e896b735d1d31b3fcffbf2a12a45e51ad183b6ac4dc58a135c3bc514fa6920afc98a4bcb4442075df33d5e399a6ac66b4d65af7bcde13f49ea1f710bdc7e83d47ef417a4f32c984738f73ebeefe434abda554595d1d7e88ed3a54c15fc10a6cddf663fdcb56eb0bf4c05ddbb4bd31ac00b0ccf994002061c7468526a08f538c98b8c5c56d75feff0fa2516f699127731014e4debc0ea9e248584700f4065207e44a41e8e05ddbb4bde1155c7777e5abded2e0507fa0246a1d858348486444c72b16d1b2820aa8a753cc8a698d94a8962bbbb6697bc7231d4c0c77aa663a1440129dd24e13834ec693526f29555617089e18887717ab42761d08817db4708a95814b24b0495334e9da7819feb9b2a45c87a477fb1e4e617f20dd3cd98e3094268aac64ec70295dd860b1462c60a5bb3b8f8010402eaa601848979ca1e342e28576f4d3f56c555fee9085d46dfea924274bc6ffff1ed3d38e16ce3de21bbbdefbff07aecc2fa432e92b6c967da0146559d98b09d22763cf2f6719e6d3028d4c0c7725aa9bb66b9b5693edfd2b091203014530203a3e2b462ec213575f31173cd08d207aabb0686c5878666830069deeeeb323142b091316954284104aa68965450015cdf32034eeee4094989669c8e9be6bdf1febc50a6ea74eb3ac36dca25f1a172e71906a5e1362553f532b869e47096a6473584efcff2fd930e2fe338fe5a9649885d906a7d74bc3221ca524c1403183a973f4a29725fbffb96877241ca872ea0f1ff9c99261bd8481129f2d0f832e25a70b9c17e371a5015de6b480493acbd199cbe55ed4232e352221750c03ba25aecd75571ebc382ebe50311383d34c8704616c0af0a0e9d803babb2fed1071eebed4a38cdbf7d7f9ff7fb9ded256657bff40adb536a2516f6991274808841191bb3bef3dbbbb7b8ee77a4b5bf6cbab60a092fb3c1c5945474bae1b37b61e05ebdc1ded162c2a63d3966dd7a089caa45c6f01adbb7b902cf596767d0822b683e30c7e84e4eb5d5b12a77b5cdfa3891584172873f88efc766dd3f6ce3f150709a2a88c651485081d9c98c705ef02f72fc40264bde00b3c7ce01da11a3122d6236b66246548887ae755ed150b295750e7b925aecd7575bcff7f9f32c33107e36628e377241bf13462e7844c0ac2c5b8c2ed8861924437a42899c2ffff41f70e46a83becb2cb215459c3eeb8c6cc96505fce395329d55bba6c3a9fd00da8a239126954acec11ab2073a28627a6451126299e8f4ccc8acc8e64d218f62ca2a43453d48f180e50345b9ed25b2533072be5762e38c32939e79c6984d45b4a84c494a2cada40d38562b66b9bb6b78f75e69fce9945a2e085988ddde9ce5b89ab556ec5d7dbb54ddb7b85317410b582cc9325684515c49a31a26cd95d31c3da646d7a01bbb65d5e6979b1c97b4871746df091c8c26e896b737d85b58e987a4b77704c53f413238fbb5e79afd65ab740546fa9f1884c9eb4a074770756d07ece39db20a9b794294595d505828dd8575044f8ffaf9273ce5f39f596bec121d178f4f08564c36e9e96d4647c75199e81f550656042dac84e988830861eeba64caf67be20d9a35733f6a3455cabd81234bbb6697b8f2d6742d7bfde0ebe829bc54ce8738189e853b3719622f524d51d0a115b2a502d18797e85d602ebad0fa452503174f0f8111183215b412648cc1fb690d1e77f001b2e7d252f7cceaba98feeeebe96242aa6ded21ddc012cceee6e8d3578f0d414ba2619a880089d1c3b7ec2c246a4d0a79b212c95f79edddd1deb897307aaaf6b6389818ac26f23e9aea991109adc340d64793430674eef6c802e6bccffbf12972a091571ff4a9c056a9bb36fd7366d6f1a361e324052d21328b97b81ba7e5804edc080250cd103244aa80495a36a840a6631035385d09d64ace4ae6351d82e8851c70fa8488015b7c4b5b9ee53f0c52d716d6ed4f94e09470c39edfc8f58002f5b6eba444cc6bbb6697b67e07b76777727a025cebdc689c84aa70ed912aa3ac4a54ba1e7cf4ce5bde02a0b6c05a99899a13fc2c8604442a849e83e29022c5feecacbab97e37d6c6624651b881cddf3d1901808804a1e42b2b0cc6617785cbf614909b7bef7ecad9635b5866bd820789221634a450c142c1b303f6a52b462ffff19dec761e2d803b61d8ca2be357d6a0d7b5390f2a8342d9e46c5009d00331a041c41c3401cca12b9a6031480070d8890bc7888d02c1286c6c180480c0086024100180c0602018140202c8a817096ca62b805e646dbf3bd2e64f64fe4f004eb23ff0e699e3247d24b954b5088a40ecaa685d8258c2a3eb4ecfe165df5168aaf83b3641af522ff11f7da40c0a2cca02c46d2efcdcc6a1175a1daa9d70e2a750166303407f0b645b3fc83a82b1e4899184fb508d9357cefb368f6496662150a6f7e785018b411225e0e15f5890e2d8d344d1149d7f438da024305fadde9c440f6f31737f3568755bb2fe44eb98682b9ed86ba66f98ab4db8a862750ed262b4417862278d552ef5a1ee7874ac9ff53c3143d4951a8845e4981874b338fdb6644167f70da9070c9695afbc2886b3fb9fd8c545bb6e2da3c96ac914c53d3127a343d6fafc05c2dd69c4ce918a410957a367bd3c9241d7c1218cba1ccb4637b47115544336634360960ff0568b26be93a44a1a2d9f59b24d4d66695674084fdbd875a244b6e018a06c2095e2dd4ab8dc170c6fe37ba6bdbdbc1e8b152458909ab6d0d8a7ecbca1261aef5c8f04b1c3068a2b35b45a74db7b089e3dd78d2b48c6259262ccc5d1dad46da316394643eedd95a6410cd1e4fcd54fca74e1f6da5b8bb3250970d8d5cf4a4a195d4643556a2188b7bfd5559311193eef6871797c02bc2de8bc8834292b0731e924411129b9ca537a6ceae2e270dec2c56a22b96e463f562dd90156539a401cfda7ebb33e130c2b32ef4653f2413e0940f5b53b2c237b31677e5fd1a5ba367da9a8f52f3ba211cd0a86acd14aa4534574928ada454c5c91726a7a4cca5c9e68a194a25bdd2e1ff64f5ce58c21b4eaffe332f7dd0630929ead5abf96f88b98f4712bb4d25861070fa396f6c3f228aa769d1bef21e9e5f3ddee813d442ae61f6e8103da1331e1fa9e78d1736369bc1bb1251fb2176cbdf28b32e55ade7e471266c81a4dd06282c44d1aff58c1627dfb68302d90a51f44d311893b313a8307cd6fb1475788acc3457c7b4fd399f0880683d5c650b11cd9d249aff68132935cc4ed966f320996adad48ae4546194780947df9a4e180080b6741118468028346c2867ff5c8cb30a14e029c09e75d282ea573c268928293a1f81872649162790292cfc5167a458b89fc6f0fd6686b2a058e8390076939b60b212624324df61daca31cfee89ab31cb4570f2e01b2296c28f8a721385c653b853610477395dfa84c8bd4a47210579b988cb39a3a2c755a18cc1bd73f346275a053857a265e82b441021e8be4a6224d4d03359ca241f3d7c14887a47646ca2c411139ff02fd8d3318ad02125e321b6237dee5811d423c8a9a8510786e2f6c731a10a01da3d9a5796898a7ca49222908098242196da2adf276c85713d91c840af479be33dd44a5eb0e00c6ea0f66ace750ee1db88d1ed6f050d177a8d4d6c6088a44ce2b2a490949a94b4b2a527de01514f2f3a3301a46b8f2609de770961ca6bc649fd8aa25cdac6883f60893904bfeddb91fe918bdfef655f3c8e517069c457ef5e66a650f89c7757ddf4bd252eb8bdd1c0613bdade3b21caf239f0030c93d7aa99a04305b4a17589908f2b2fac28b7981b3ae9e762258d3f881e1aec599b58884cf5cc63bdb190a6736247b98d5cb9fcd2610614e180a3298dde507fbebd8deb27dc403ec12cd41cf3fd70695f9192f71dfbf654235c6e90353490315fd6c832bde9802d0853ba985356e9db8af0566a27858c95138b2db3035d7c7a3a75dba3b647d4c995ce53d2c5b4d91dc8ada94cac2660ae2d76e22ba90317ed99b94c64fd6e32287beb800bfca12f1d2a2218ad7d1db5ca5d3b15a28269a39f7cad6b19a4b66f7e239af7f89607a8a796e0afd971daf7767d82898e207218b3bc828092d7b227d770933e0ce683636435391513bab68dad1389cd709cfd3c40e78d02454f3c51050c97d335cb8a9278364abbbcaba81e076ad8261218b934eccc8c43dff240fab4aabaa8d5c6efdba7c461f7be65bb39cdccadab8549a5b52571d0cb81fd05257f52bc54670ca95a3f890155712e5ed711a3a30a6ddb7ff721dc7d60c33917ccde9916c22a0b4b04d0af2cf4d33c4e6e10b9e3dce44cc35e553ab56df458700e79afb0220d95858da2acac25d9fec14943b4a5a1fff0bfb26a4dd03fd1d0b18b36fe593955a56e6c0b044fb29e7ae1b62415fe0edb3738f03ddadf16285f195300a2f2ee76deb793c71105df2539ba939f5340d31ede2c94a4d025862142884f01f8b598b61adddd5408ab29283531086f384921340b5dd1eb61b37b68394340723ded2dae6516f96f14f7a417ab1907ef8f3d8d002807e59119c18490349de164614cf2a4d018c482df8a54fadff007d1eb27bd59e53fb0f0af55089995afded158869ab33264e0d9ec7bf7a2abfb5b2d0686615895ea0b03f339048da25177b97eb715f87d1b76e542097067eb609b821735cd01863e577b6046e6f6ab46eeb5e81bd192f6d9586dbe510289c53f0e68887b0360b42f22b057fc969c9d4526900a413ef5d1d03f08bfb46d36078b080c9db4de95ca92f557d53820708327b4cadc8b2892f67e0abd2f158e8c14f38877a6bc6b9c4f3276af85a56859bc1a55a67fd2bec14f8b438d341d5082006a530ba240c0df3f9c5422ddddca3933d61c22138dde9184ceb36a9cbf6fb9b5fef5e3ad28aea8c716ac000560b91113db931cc3057892624067762b3a6a9b0941a5a9c4c7ef8dc2f56ac24df912b4161ccd44b5ed0e896a88f89219c826ce8f87bde318ea29d098ca286bce340d0a3ea2b03a4dc786543814d0d7e91bbd61729a3651746d1fa26c0c61c4f34605d342b5c3ac9f66ac3c78bad3d11a88b7ddb2cd19c17b8dce701d600e729e31978ed283e17bd13975471a3cc41380f61b06e6398572c969e0a97c8f0e0e1ffc1e75de3e8ad321dc86edf9aa0ae222941c5ad99472e7b497fce170223b142e1d9d710f84883ec8f038cf50403ac8bfa5be213df902c8c309038fd2b8b108378a2f35bfbd9111e646f33569271d6b03fb15ccd2e646434a6bd7d12267fb3cff5d27d2e98334a71d985bcbabddebd1423a96cd10e7887b26343f81de4cabc0c072afa62e8c7cb05c6aeed6bd1769bf89179a93f1a6cc33976e5b49095f86a79dc57f7cd358d583071d03968b122cb8d09a05fc47adec2af778011f7c542ec21fc2ac4299c316b9b112b064e15331e6cac305901a167eefdd1defcaf6816b685ba73f8b3cbecee1f82bdd2b89654323073de92f0279361ddc40b851462f2bef68bf7c6c788e0b05565c089e13c681f12679ab7ef9ba9ff896d673ef47529ac26095eec0fe4123407ab6faa661484aebaa842b7201eb6dfb528c06d760a637fbae62f1d3bc14d90c54b814df5806b6921652d24ab8e6143eada637b0cd72fa85511925f5d25b5019bc983960a35c72aef5d1235a162eea205db3afa039ad0d2e604bf6e5f0aa5b5d8b602d54635bd41d408a472636a64855314e8282d9b4bbf573350423b6d42693728633f2df5fdea4fc962764d5f80192fe48a89bfd148273cb239add4821e82f9643abd542ec7632e379c8cea7bebd05e38fb9e072b7da4c52bf436734d31fba1ec750e77db919b597124bdfa5214b2c8ecdf37ee72fc8b79da76f87306ec947a989a549238b1e6af86b9577b39aa4d7f14d3acf0d74b922082611b0eba19c2f31d3580b2505c2c15283ef06b9132264df40f716c2c541561bac5bf141f24d724920545cceede2df3f42b39d5aa37946d4b5ed98f05e1fb0e30bee5a504e93f4498f0e111d342b496ad2f4e1f4ff849c982949182dbad8a5fa4c9801369e0f9fc5680685ac05030ad4c4241faf9eb0a0e17b0b25f92004ccf3f3f5ac6bc5d61ab9e36c80a5b3397a18d538253934ee2c38bc13353534c1106bfafb9dd6d6cb96c74f050444df427c6c0ae8fd0b2952172f5f981c4cd6d23cbd4789c704e0d6f09bc12e080065c7516174ac9d4a35b5035767f959234c16fa345d631204cd16fc04323b557bc95c8ccc5953e3abab2b23c449f7f2660da91a9d322a49ce4fb65751590f6623eef1398865d64680c9156e7910a949b7f4048c7010441292de7129ce5036b030541da3d701c3fce1c711ef2e44b0e8046bca4094dea630ed38264db66adaafdbbaa81a47cc44616ce544e1419ac089e82c1be936dd76769eece46f515713c1f02649320b810a4062230def881095b3f4937fe3b0e8c6dbe3ca7818253db8ed10c6d2eedaeeb1e1ab8f3be1ff8f77209a1a6e5c21860d572e8f0300173fc6c2a2347e456ba045efc66b1abf12b2d76760aba0ec363e19c1a04e44733a3ea7bc8ad605d8e9725d0d49fc0c25d494e0344c50b616f398b342edb8a1894229c75ecd67eb20a2d7687cae4c330d13435ee91c21c74aa81054cfd8170ac3306e3fb097327717c6a0cbdc5de3d921fd7ef7a56c2e839d01a4b883fe558446cf79df45115ae624eb16c1960813dd326f3cbf8594aa969d497a53f2030e1964eae09908068ffb047e7ffa76a9d4564c424e87b4d7e412f2dad17699342f204022683428ec9295c3ca27e0ddb0c2f6e11701e9cd20969fd2f6c41a10902704f0ac8769bd1a50d1832c23d4aa888bf3c5d6bcda86761f92709fcb0c60c26f20e1ab9a0955c7a446dc5a28e9e1e371b81126b7c9588b349d0642927c3c733ce462d4b41fa86eddb5f655e5ae883ce7799b4156466c33b15f4e0f4cdd142eae599fabe121b1cd09c5fbf759726074cb015a22dc7dd33f7e1dd1ee44b9b9999cf6b7afe8b95e1331cbc12822a14a6b76411b37f65ffbff06f45ad9a19d4f7b3dc8c2ab945aed68f4d6a80bdddb98040e8fabf9ea2e33939ed1f67f68f7711d1ba6b4090d8e1ad131584f88aa1b87b0d1794ae4e9eeffae272335d065c65885e150a41a0983350131a91f648dd8ed9750e39c62b72e532def6ea17e0bffd88aaa70451cede72200c471d3a5002c5e0f6fa24180d1471824492dbdde688bfa41fb4029e138da56dfeb755a3dc949d459cbbc5a31c0aaebfade577cb36c49acf1af887f6679dee08e0e36bfa2d64938bd418cebe8c1067b011ea30f7dbd88d884d65e340493895c03c835bc0ba48e1c402839721a7e20a414691b12ef4ca1ba04fa31f71ea6a357967a29919bc7e7fe5edb61ba0eb613ccec12d7779d4cf61a6380d3dec324e84529b72deeb6bf2ae9b31323744956ab86e8d7bc0a41493c245c0ca0ad88cfea98bf6a97582645ff908de9a238ceb9c1415b68eadeb2f8ab10624f72c6559612ba7fc1323dfeb15bea4a857a0f56ec1353ced24deb1172eefe871e56575a0bc12c3d50b90bca175f125b9a3da0bf442cd46a68e06a86a547ffe5c17cfa11b977d968e4a02fbd41d07ff9e4d54d866965af5736f497b826c02f6d9d3e1e9cbd13fab1286ad5c4965502d99cf32622d97a200ac8f446cd0a477a938c0c13c9703a4dd57eecbcbc742fa1944608a93744ce7051996ee05a4239cfda769080fa6744df4ff541f1d08e784b07876778caeb692bb9e788673d9b17bf49fc5dac918d70377f1c6c587b8ba233cc12320a303b55b31d2b34a115e96a5eafc5da000152a60c3294c4817b3077df6cf4f3b60db39983e05f13e7c66e084105d287db7ba5328224be105f81cf482361c6d34632366b58484e8d25ec257da93ff08871708b6dd9ea121730964a1b8ca3e88d1063efb192853e840942130a0669a653da7d2df75522648aa574bd3a29257594a111c53b7421b60b088916063356ccf5d84fd6432eebe295c3552803800da55593b9f378fc8ffe76e2371bd072d3193c91b24adf9ec7b751630309762c578eec13a747e353128e64daa4a423933ef1e90a451973330c923cdfee5d7712d0ac9426173e69ed3e21c4b2cbc82d5fda3292b2d9a5799380030ab75cc9f823bcabdfd466e66a4f8ef611c1c7583d93470ed5c1d64217769222423cf278a34bffe5e1c513f0fcd84146f85880f415c520cab65967690184994116c5216fc10793b5c5c415896083570e317e7d7ddeb3719e333f36ca82805985976c9e4b50f355f9f194840ac79abd232f7d7d32f9a6efbedbc36b661bb782ecabbfd9ce877b745b38e77ce3e681d601763490bacf0a99d8c177aee097ed7eec233aea98077c7ac2b979e385d474805fab5aa88bcd8a964ecd4957841b07361c04408e03909bdb6cf698070725ba56209131b194cb54404694c68f6c36a071ecc559dc54e5b87c7c05264aa022c965107a3ae4cb2f0c72bd56eb113534f7590d4dcfb928264ebfa28f548becd638e42fdd4cd72429abde6d3bf20c95220a5c6a481da5ea48ed54da96b1e8577c332d98bc5a6b6d73267ca80bce40cd021d4447ae98300aa46b93c2d31a2d2246d8c45411993f7cf9215b7624817d8779a3c2db8328d10db2204bbc787500fdbc5ebc9bcd71e28038a7da15324eedd453fb6a54e921c908e3694405019695788ecca1f3b46b20bf381b859732ddd868edd1a7237ade96a05a5cf12e093438f1ef7f3b33b5cc7436835a6db765cbb14643b682b0ff9dcd4e4ef8dbf1af4097881b93880b6456dad962ab116944774b732b3f1c45a0520ab8514a0e5700cf36510c31584b061f1836b699c521ffdce1e99c0e89b5dcfea76c532f34f0f92d811440128a9bdfe03e2f4d24683d7fe8f183977ea8f2d944bf544358e0918041d55223dc07b43685381e118c6a3ba1a4fa713d1c2a4a3a97839c994f20c3013ebdddb59b65bb0011f6051f586c24eaf547bac67197cbfb47e939d5e9b7dcde3c5d4ea20b1f7c060741c76b732e4bda7ff60d96ed4821e2b971b86bba909b6939b71f8608eb662e62914c0d4b538c147a086beee2d3dafe979001300568cf821027573870356c330c6e036badeb53df3d357b97f1c7f9d7d370119e77913b12bcd79dd62c0785216292327e3a6880af7b6d7807929cd3342eba9283427020456e980d1d643f21d73522a0cb0ad4482931688409d813baeeb6ee34f654c2cf8cbe81eab8dfbae134ec60deb1b3ead5e5eecfc8cf8c804c49a988d5bffa77054d3b6fbe58bd941948de3e200e1fc316a70edbb85925d469c5e2897465d7c3e5c010646c36e09f1593e3f51735adc6bee6f9fa557169481a8695dbadd30c6f2dee99e6513c54376ede961d573c1f3cb16db9bb8185c3b908e91bb54be0225aa6d51efe24857de5891a7709bc9eee680c756461af9753bbfb475daf342e6ebcc6fb748536c58fd5b7dae3d2d8abdae2af7f8a116bdad5cb00d4a8215a349bd917ff1d450cae6d3b975f40630270d820fb983a30d602c66dad4f0c1cad0ec3e58a3315bfe59bba5018e46b956bbba8c77281aa25360bc431f499e549e8f928ba59527ac5cdcf39c07d3042e86e39d6a6ae8ef0f70a08d1fde3f7ec1bf6f66b9dad0cf8ae90428b8444168a90c0580156220318e5bf2e8b5e70e2bc6ece9cb04fa0bbfae26f607b804aa5736168509658c609c86a0cce70ae5daa08b14feab81e48c9bb3c59c1b0f62ddf690d91db364ea3286cf219e29f0f005579897d1bd9a8a1c97a41b800825c44be6c736fe3bd7047d0a2bf3124d921b7254bfb9d5993a440c0954d4c6ec53301b96e1735344e1c425a189ef90c7293dca0312772189f5c57ca07e950dd954a766d5779a191922e19966174075facb6308c5726174b9510075770f7915f3b06f658ce2a5c7b60d86ebd88574fc55cc1c02b4ac3b4320748eeea6132dca3dc6f5ac179fe71d6bec523be767cd55a9f31f6289966041c98313d957871d67fd236e24cb549ee258f7fe3171f1b09ce4b8f4d696fea15fcd087c75248bc919c76e974643797a90056c9b6398a340bacebab26a790fac76ceec942170938318fd9cc51d85817315db8580f5e34d1a00c1af6d05a5cf14e76f8e84477425ec910a3dac1e6c098c1f147c127e95b3f4bad2e01dec8c35931dce2c7bee53598b6bde7973071a0a6955d9cb9972566a80cec26f0e8ed81e7951a4efbb575ad6610a864e58422045f40d1f252564ec548b5546695e831bfa1fde8b6c5f919eeb6c8ac9e52753d0a6c678609984a35f7b19cad9d965d343a2b71f34dc84040834cc3d4e2fcce21788c995a6662684ed2a4be0e04f9acac09651a0754727b7dbd694d2a53536b13af58c89bc8f73397012d16edaf370a3bdcf4a5328e898a1626611c9f10af07b53c1deda04033e1b81101daadf8ff6df92ee534e5c8c3bbaa20b44253fabea55b28d22d2391346911816d0d8cc248a2140fbfcd8f2d0fe273d0e462acfff6244408d2c5b54e63afe35b07bf388cf2d7daa7fdc18785549b578d659486d0fb1a18d1809526af40485a1a19d2139346285d54edaf80860de4d4ccaf17529a48a69c4e4122edc0994dcfb1a90fc88ee511332d5337d4508933a67a95d0dd4a23250ba18c0c1235783de853b9011daf25daaafb7eb80b34e1f5a26cc4d3512b453fa11825a5bebc5a20d0910fe038ad381bcfdd14243566610fb1b5a3352cfa6b5be72c1704e45c9c956ee598be33456ae20458f125abc95b84b80240f89842a3138b5ccc9822bf3179799844872390fb597743cd1aa9d2b44d4759fd48e2d162ed7fb41c0bc62122f892364cd6b72927fc6061d4cf26958e1ce168a1919b6d894173dc8d85412268b0d379eba4528ce085e30955786f12d57962fe451c2d523d51cb9baca878322bb7e297b4bd87e43b7f6801cc12245a7bb8d7cfd9ac8ccc44795035eed38ad0ed080afb4c3c5485a52ee4f5e620ba11240addda1868e97c3bdcd8a83282330f2597b97030d425ff6092396afb002591b82fd07892c47c68bf3f62e4081783b7a4d67486693a735b34f2eee3b1746714ed4125aceacc82d19c9b343f54535b23619509c2cd0420c1cb025183376bac82afa30eea09ab0c9ed51a644b4ae5e5a863a63f7654adb046b295240b0e88616e53cf40aa602bb033d908a5444e5a8b78adc19930591c01946db369fc3f6850c874119197ed6d45624fb2a56890498be906d1ce405ab12ff4ac82ce1e23002c7a366128a2f758c59bb3d6bd306e7f4e8efbbd3d244b555c0a5192564390d69a9711fafb7b379ee72889f1f74cf58806bdfde655bc93d6b07831bf75cb4c71033198f2e8058f188a297a486a32835104968cf14f3d755d9bdecec0f94035c8679618a245f22d81a0461ad0e024054dbe3016f1fcf2a879ac891fd5106608ffbdcf3d11f45ccee091dda2cb13f2aa44f20f1ab83914b7b0aa2ba97b88d928a4b8cc0290f25af77e643422aae968415e4bce91a313487745c8d3c0505cef3be10c1f036a336c9eecdb3d4a6cc3d0e51cc8d0cd274f0e7242fe5f3139b383ff3f9515d9c97c000128f1522679070c725ee9fc00736a3c7441915bbe9c3c6ea7bc38e42b1d2d6f17717519c1b81961ffe69773e700549e494b90ff1e010340a9d813f2eb6df8887acda12fb90000e0e505db744670a3208f4c0e7e920f7ce35834657f4ab8c0184cab5bf9f0232a88ac0051e5798174988806eeea46858b5ed68cae539ea970199c98b3207671c4c270452761cfdc2c7119139629d19215776be4531642195195a6abfe50b50abe36bd26ac4e9b72536890598f4ccea5b5bb026c21ba3eef3f14875f10b37d783f7120bfae425dce01f2323a04515a6c9ee957f91dea4811d11b7f188ff5a272d89bc507bc2eee213e03917cd2c8c8fc27cf6ac5fc6c89dd8b629ece1f6a754feb15d595bf71bd6efc3947040427ae02fdada3d6579900ad5d668c16804ea9309ec56a54bd70502cd7a8a787833923604a3565d7b5fe2ff4e17e53cae04ec8f7a783b4fff7744826a42f38d87fc31c88239e828ea71d96cec115c0fd62a611daf325c5b310ee38c6d306358bfd6c1c6943b37aa6463083c843acf5a56c29e8ad8a2b92972d33d2ad507858986e49f9d4b578d2d02bfd5bdf320bb32fca19adcfdf3a1ceb49f3b1066b5af0165e8d449fe64a3e206270fbea7495ee99a4abc3eed2d591e4067a1a5a47655eadf13388bdf7305abf9385585130b1136a61b853b6e18118905c18629862ef04ce1f9139aeec31c2b763aacea6371067789a8f58c305fef29f4f6f8cdc789e5d7755120169e858363d8ea82ed17da2381a03ba9a4d94a8ec3bad452f278bfffb40a2879205aef144deaaebe4bcf2ca13d15911cc682f0a0cbe43c023f742dad5ea478ab83d98aeb6ec983b57c0011f22db7a8cb99f2782e7d055ec5ee8288f62d240627c95521277bfe3d2e121ceb296baed42a2fbc8819e343492a39730a110977e7d1d8054f6fad40ed6dac8c31eddda1205b087243335b323b969591237811282f7e83e6639672f9ad9fc553c37d93d946de542851b058b3020664141533e76557d75ca8fc5b0ef810ec596079e81d1460f3091bbda7a7c1efa3aed6003185ca144617f87e3a7ede9f743e6e977786f6fe53b4fea9a3619261d783f946a13c3fb9114856aa06f18b8c3be73ed7e2866370ab06f7979a58c16f7af20ed76d2d6f91b0af738cc5264d6f2c6a786aafc1c01d598bb8fa84f6a7e454d0348b8bcd41f3ae1f7b028337faf664209272fc7ec0611a8172dcc269acd9f607e26acfa24ff16d8e772dfda4283a63a73a372ef55226ad58d19144a526ecbc8419646485e042106076b482d87c9648963d2368ade1b834caa97552b417043cb900136df1c242e3f7eb56d79ebaf56bdeb6e235090f971905b7f2e182548929e025697e2edb295f3bc57b63a16f9efa707c277dccbe9ba94087596c46f533dbbd05ce3a6d605f37f1d3358776ffa50d7228d4a5237d2ed02d77a834fcfa7fc44c1102aad4e244a6cf26724aeb15dda5981cf8177623f249b8b763ba4edad621d119c4b017ada842f58708e25a503f6a3b75858eda21f429f7aa0d80f9444ec0bf5ab0cac03d0c682caa3257748598db2963fb767ee8393d7c5f631bbd4b858b236b4ce4599c576b9d8801cc9dff2af3ee5d67be05e4a5137c263b04d64f6278490f802c202f70230305157835e2d6174702ea87ecc213b267e7070ca091e77e6726d5951d6937555e6f6ff3bb8c59c9f73ce990712a59949a5071e4b9b07a42e674ae95cb0544f6722bdc59c1e8e471d4986c04cced04526e48825672ccb4430a78fffff4d2ebe953f97b541d6065a98a7aaaaaaaaed8ffaaaae1106d0e6eb05aa295befc88f891210a6745865536c5cac54244229692549abb2964c2477944a301972b960cdb411c2216c8a563645f5c99868533389a46555d814b635eae3ff3f972e83e0eaad3f9a6be0040fae9a96872786232aa3a3af1ac618bfb428cd7c717a8084bad60e4d702bca7ab2ae555b582d0d8d20451897f91ad2605eee91f9d329019cf1d8da8367b0bbbb3dd1bec88a61e179c191fa6c01ecf88211c312e300ab0cc74156db5b51d693751dfbd1fdf021d5c2939c2140d61b74621372a222a25122e1c2ebe3c6a6a6f6bbbbbb3ba010629cfea829943526f115326b5dd30865d5b2a2ac27ebfab076002a143a70eb122f898b369b6dc5995c318b5043c9169d5bd489546fac0a9bc2b2972003ab13461ea28c32ae13c8140d63580153dddd352bca7ab2aeed29b5b505c4465c3f13292ea09e8fe3865b7777d48aa42e2b63570dae271ab8da08a9c4058e4b77b7cb6229182d3696dc9278dceec32e5ab66c5ddbffbb2af84ed830fd9284c0d29a5e52841d2fe5ad47940f175dd45392f4c678ac7954ddeef64c8110774337f6ffb18e28cd44263d60298b584b9c333fb843b537e79d4bce39b6d6387d0504f3ce30012e3039f4aa0ce994450301de8ab29eac2b0f2de608012128bbaaa1a94645950c4de4d443b49112e0032a9d4bb76b8ac061bb9935c3929715653d5957fc12d6c7ffff9e77e57fa744f0007d88ab592308930ddf970d163234323864f87f14c39c7d5a5c162e276fd99aa41bcc38b6c0a2cd669b494da72041e0180faebbdb4ddf0c052676400b30785cb07462109da8ce14c12dafeeee0f3d1dc61b5a93b6ff33862494663e19ad368861eb1f72b1f2e7bc588c802220b960ed9011290a6a1933b8009821769ab090d8de7f96ccb53bac509ab9d5e56187d82c67360d4d76dddd6d18aa4c69a6fabf10d562fbff4c4294660e3308a662ce6fc953b2f7df4ca621b6a47a6b8d6cb8f16289dd97354f0e8ec063d0e45804b3a2ac27ebca7b9b73ce594594661e91496587aca5ad8396eea58fffff3860702bbf0e1b2d8b8cd40c59fa4b793897125d379272c920998f1b215217f15694f5645d77217cb6a6b6e4a5bbbbc99029cd549f32fd60e175bfacf8733ea91411e3f0ff8150261ac4feff0dc00986d588592e07dd750c09142f70090d21239a351853b06c77f7900fa5993f6088a160568f6aae45edbe6aa4436966cff7fb6004bc0ac618639c5bbabbd95a46a1f63be3ee6e2b94666ee10e325d286ce35815368565310c4ecdb1350cb41a5be3bbb229dffd4a5ab56555d81496c5ed77523d9f6d5d328258b2c26b7ef9ffe70280e9f6ec51eeb8c4136d58fdffcbb91473d3b7b19aff1fcb86d24c5d4f032cdf8f76769e865c923bd2610fb2fb6079fbf8ff0f2534b4f2b7b8d6ddad944c69a69abee4803cb37703c09b84ec171b390caf16c22ba89996a797946606cf30ee8131c637cabe59368ead71748dd36b1c5fe3fc1a0728ecf7ee09fc727158f7d41dc7b52a97aed89d7b61cf20dd7ad2cbba36f310b6a42d8ec8e7f032e7bf55f3ff2be9509ad9f3fd3c5002fe3f6f841a5b6245239c747c332335f5cffdff3d6c28cdd4f57cbf103d805db37a8b31c61877dbf296addd880132ba8e242ec618d718a234b378442695226a2c557c9382e2c17b51067892b62bca7ab2aebc921b43160c94d01325b613b2947104caf7bb3141614609163bac5e7e3ace760ad8fbea1b3b4598c7aab0292c5b03005e77378ec40b39dc4995245b7c97430a9cab1897c5fdeeee6ea916dd4a37c61d6731631fbe106be707a29e9a33bb73e6f1ed8992b1f4f1ff9f461289e4089b9e8eb6d7474b12a511bba99c730e16519a794426952182cb1aa7deeec52da705590b8104494f46ab7bf06efd3d4f91c32c901b784f8b218e9a8c68a05c0cd13a8f3b7eb83a154c62329634f4248f43ad087684305ce398f0f471146488cd022bc8f811923b2292c3e3a846850efbff6f3184d24ce1f0438be2ff322b9ab44769113c76e1fcffc16a5cdc92654a33d5d95bb25c4f36e89eeabc227470569fab730855da6cb69a99596c912872dd66deff77e8c043aaeb5f1e6753532f898a2bc8c66206f9b1e85249810848d68b209e196228fa884ecc540437f0d2d5e9e888712372bb3efeff5f97338e8cad29221a6c36ba860d163d97a5006ecc38ba526c94f08731cf3d41f410b6d8a4694811efc61e308dfc5a862472445a384ee494d0f755c345219b825d5cd50659dc11917839a71042af8e28cd4482b84afe38ae942b3760e5baf45dced852300f0613cd6870bb181508993671ec97ec7e79250930206408955717d111e70c4eb930f2faffaa224a338f1eaa9039dc345ce668055f1ebeb12a6c0acb0a91f4b4d7dad16f7a8325af407a5891347700cdaaa189d1825a6e93156528cdc4b52f4e4f0816406d8117071a8c31c638aa09001a7528aba5458f251c48bd4444aa5154342d3ad478a4fd7f18d44be97be9eec6f86d0b3f2576ba96c8ae545c8105d970c09df8cd7a5cb09afbff9584509a5994f464eca0648577aaaaaaaa8a337bdf5a95daff1f86446966324cb9862d51c46cac984e45ac57b3402522449e488f9501820792373d2db723c1125a472d406ee648ea5815368565956adb4ef0eed4d0b8015b21c319b60454b26abfbbbb7b92509af96414d16385c2b6dd3e3b2990a2ce8ab29eac6b1490b21cd54562e66c81a7d301f3824caba8c914d21b3143c0d6d4955113d412a8e753c4cb811398b2b46010f278f05801416163fe08fe3fcf645194c143bedc691221bbe15b427edd30b6d2afd8c7477d39c77004df6e47621cb16952ca51559574238a2498d24cf56f4dafa4f9012a996b915d581536856571cbaab029ecdb4c5d6663d1c7ffff1cb61c22872e6723e4520c1d442f1518bc69acdc7835e49d36ec9e2716d030a88fffff01ef8fcecc06d23d4368853c3e4470158cbb0257e002a6c88b5b8fedc724e34176cd7a39d7c7ffffa82572e5ffdbffff3bf5509ae9fb013308a7a0caff12e0e4c379f14ceec9399b90455ecbaab029acfae69cb38f10a59943b5b81e51902c083e49959a8bae2ca6254b6e68d316f5e043974b956a50ef58153685655b75c74077edbcf08562c44c52d4901f27b058b28bc2b12a6c0acbe217dd993986b86555d814961debd66505807a594f742bca7ab29a6bdb8404d4ddad43820bb025b29b2043390da51563657adf740b57c3d5fad121ca41e4f6c562654a33d5decdff5fc48d62ce3fbefdbf4c12a5994a0f64966f115343195239bcd8755dbabbdd54f9b7c45bb0375bc21155228eea85ffffff15302436214a3387c52332790f6cca9fe4869f7ae07e1cd0bcf58aff8c4ca78fffffc5b0b295ffdd4035030fd44c6f85f24d81f5758e7530b662d724fd09d6201fe3a2635a35e6fc254df00e05feae0257316163238af8308a0103ceab86fa529aa97eb744db59dbc7ff7f61cea80957336e37398ad408eaa7e12e9733054d3e2762f070a167c609d70425263c7182f39bf5b8bcb5d4f8432cd5b5efb74f27889baf1baf28ebc9bace786b075e60415c42e38e19352a88cbb3bb73a8cb8b0b7c90a674c4909161a49b8080b93881bf9b09d9ef37c36abf33eeeefe4262115fc995ac8acc07b79393d14cfd55d653dec0dadb060e54b5fdff1fb72c0df81e9596924f3227118d12274a4b48079f8527231f1c28ca48109934f460d0c1579ffbff91921ae9a9918c8d64d5485b8dd4d548628d54968998ec54555555754bdda563099408c20092540449662868464cc786d5ddff3f0a08a5994220a28a5694af92165e50f514b946388854a72ea6c80cfa04b595ba6ef75b53fb9d7177b71630a599eadbeeee96b7cfee99931cc298d8e8b101b4aa6deb1a65a4ca5bce2df43ae59c734e12a599ca10394b9baee7fbff16530a08885b1c331bb639e79c5f21946616059020a8946ddee6c40079131a68040d0371248cd31863001400070c6a94b4787808311687c5c1a0480c0484026130180c040040a140182c0a82d068aeaad17800a9362d9b755087dcf8d252e1468564365bc5a23186ea284aae53a43daa469d13895e5178dc5b2b30f6db8c5b8ee015ec4a8f461befdb5090b46549080152a5f5c6c1719314fdc262e01b2f7cb846499bac1e5a5c8b5520cd8497002e3e611b62c8d6356057eee490bdaea48a573a2d93e28830caef4f278d631235b561e51fce0ed32674e53f5140a552cb7c0d081c6388e0e53a5effafcadb1e9bb7b2e6da0865e6d8d3fa48fdd8523785e51f71ea1a27128cee1c5d7b313a04ba1da2b55f9be41aedc5ca165f290751cf0590cd0d4fda27e79761ae35b87b26687011cf31c28ad9d213cb6344da3e3f74b2b3537c3c235bd069f7683ed20d21d4b088ed678cb5fa2a8b5490f1b7bf61818323e03ed030f223019ad1775bb4d2eb1c7b0de8168040063925f1d163b92332dc60a11e8fa243ab93c61fa4c3ddd46864a2d2308e8fc72fba05207a5cff261d3ce57fd7f153a8add024690786bf3c69bd9e960280f148bfdd347b2c4e8917a4c69317fbdbe47b65fe1897ca90aaf0343c7295c6066ae25db25a775eb1efc34f746a1c8bcbd876ca1a2cda61554b1aa040360a9311f60f9eb859eb6db7cc115a2a1a8a6941cbebcaf6f168f51d1ad31f04589a44a940b87979d0107e60ec29246a8ccc420d8fcb4e3ea5c9ad027bd880c3948e6b7159b4a8a37f4cd55e4911a95427b5d31f4d2e256e27bced380dd8d246cc8829b585e8f429a07f12dd81c90e4aef2cc53877cd856996c1478ea4231f03dbb9cde6d8de7297ec59b50b45e032049ebd53d24e41279dbfbc974d67b9938564044581bd86a8ef7d6cdc08a29870510420372809173a9fafcedcec4568d34eab4f895b5024e18176a716b453bba8d17c4aa98641e0ade052f9272f7b879a028af2df79c3a056ba73b0dcc0ab1dbfbb3ecf2a45e574a9214208b332241ef642a9b3a700c8f059c4882a07b51c53c0c4c948530ddf491f68f199e39d885c90506d27e3097393314eaf300cd5dc2bdd5d2f3e57a48999a80f83cbf9c362786b75587cc2c110b6df4957d4e698db840b57f3990871489c67d1bb9709db69cb1d53706044b9c4beb1b1adb6a704c44f90b01ba8672f30afb5ce0a199edd47bcb8e111e93344e453b4d2244f262a824b6193cf528a7639103ac8b8f2a9bf48c2343523120f50b2cd953e8adc09dcbbc14975bd51999a584ada2ca8feb61edad30207495f2dceb01a900e462e6039dc910c907c662c6d15f58cd2e8df6e596b650c42f533dd0d81d1c710855ed25db4128e89420b34545bcd5aa3e8d1c07cf9370547599d25c9fc3535068d70d49bf3178596ab1957e640d3c1c9ec30ab08cf814acf3bb05ca64ff945703c097977487372036f25de21d993cf004ee36e30c75fe07c8e237a9a01dbb35788df885ddc18fa956f48bfe02d9850367135038276ad7ac5cfdd5aeaa97facc69751b681d13c3fcf0f0452f8018b861cc8eb837bdb995d8d04681627cc7ad38588b3024e671d9ad1941ac2b52ff4a94ff9ec9119c77e26f4f16b2da84947bae7c04807e6d7896a4eb000169b8967d635e87bccbb421fdf28d726084833494e7f3dd1f6aa5243fb3ac63ed839d08a4bcdc162b3595e9d5ebf4459d602799f8bfbd5586690b47ed354eab043c80b429f3b4ca1c0e42edb66179c066fac66bde8536217815d0c77fee1a07f688191d7f3b54a0584cb0b21db23e415fe7ad635084a5e405de7c966d880a6746f69be38a57145cbc3f38f131b5b21e5bdf0498a9e2bb907d5aa73183bbd5e33089b4311958b675a65fc285b577710993c27a5231644a4e96ae90a487ee4b5f25548fd8b6d66f546e5ebf31b5b22d90630e8e9505e9479a09a0b589aeb9267946fb14fe12e4b5bf927e8662b339ca794bcebe7924a432b6b215078fc811dd261095648c9f765445061abd52791a68bd1ae7496b0272b2185476bc1989a6dba82cd1e6595ba632c3c2539e2cc4490fddc1983701b68a2790f7ff8a7b6f3504497669a34a6b5df26ac7103b1be2d273be43d06cfe8503da2511ac3b9093abf2e5a23c034eb8629158e83605bf704cea275cba29d285a1868e5d6848115adca25851956892766d702a8f8df05f238a80c2a402072804596d9116cd5da5f2fe3f834001ac6bb2609735f829a9729c75aace0d836b8065dbf5c8df4e036076bdbc0eec5596eb839aa9f283ab945c837d4e89aec18423f40e74d8cf50c31ff70db8457a528c77a3e540551713c65e776e3272224a12720a9b9e434ddd663f81bf7a6ee289d5fc5c00297698a96aba2e380fea4cb7fcbfc438db3b037c9112df4f081eff2c5bf86c1c8ca5e279d4970ce10ac5b53260a9c2fefd849d37021d84db480268086fe18e2e5b029c72a3852341224da2635397b4231b8fbd5efb58bf18e9920bae9463a4980e06a20cf274249c6005bbcfe7ce8dd18c24732cdb4ac1bf83d11b657c55bf551d7b85cc81352d7a7c53e89bb842e069f8b2c124a6af601f9b69bcef5e6740a0ce981c6c7714b376ac1ed6ca411b2609a0d4efddf50162a9a18c4213803c0ed22e26c04dce7b9032a2229a2c083b04df26a7f4c657e471792b80fb7e08dcdd5298ea718e1805344b41e197f95e6edea21c20a06b24c5f28922d3d784a3db550c565354db5df30631d0935751bcf76da87e0e9a011f121f4da51412404c9d533ea8f1b47c2a766ba97b8751079d3abc46d9e1afb396038767c27d5737cd76cfd19027c2517ace2a75d1f2d7da2387c4a568d9c5904311c9fef21808ab53b5830728ccff0c1a15829d66e8dc39a93d514b76b89e25e9d555cb043cb9d3a0e67a77c8ef637ae9e59c1be788c702cc609d62950454a5d9c578b09ceda25e6bf646822edea84c8e59816c400a69df0178f8bda50a373bfb87b442a100764cf27fa86762ddb6f1c5aa225543fb481039a155063cef8bb3ff2e592becc16f12b8f93d10ca7bd85a644beaf8ce26b7b20a3ca57a6780188e2f69f9d7eab8449aa11e447e81c35a2a2c7771cdb03c1d4f793c81db60088efa7525a972757d5d2eb1776feba731f8830be46903f5f1411464cfc60e10d96c79d2503bd6befe426afa55f50d91c298ecb41f2cc0f06dd7fb4a868b05a8a9ef77b074e8fba84274b104ce035f24e70b17b13a5dddc4b3ae4afb24ae106e84657b1ff6c49fa936154f1752f996f6ea52be089cb271a71c6f783c5997e91a0552fac57f96413a7df75af1f4f8a66abe2e4135ccbc3d8d8d6fca963f9f533e50307e01cc56d5bdf4feacc7a481144a30d3c7c55c6ab2a5c9ac38d13b0cb04ecc556f816d52db834b498546e7824764d532fc61e67023e4d178d2707147b0092326e06550ccb01985b2a541513973be2ddf77c2c98099f8990cf5b973b5286c9544566989fb38bc5247f2d5a1236d6beb0cb0f494e67b19d4dac0e73700df4781b55eaba76e47bf70066d446295b25417f50d0814e4b3530c7024fddb0944d737fced13c0d7468f2a1cae92ba079f3a212f1c0875d95c9f86399f8bc530c36c8a3da191668369fd1b569cb210f73b09820cf233bbe24d37d370caad446009b2e668b8ebf147e6fee3cac7025d0a2ead12c4e9e4d048658313f79ad5a630264c542096ffffcafc920a94d3ded10ef4a19a576f6e80444ec71712de5894aaf9d467b4a712365766e2347beb00c4b98017a2fd4955a3ab7285639dd392b77110c9c1c1b2d12b20a6db882c00473798ffe2d7b3ab67acdf9581ac9484a5000817f0b57a48b9ece8473f75d62ed4be91653391c90280e4be7b125992693464a0a244222174232851f3db684db69d822b1b8c75f36af99cd6babc646e9796a2a2e1551f9862c5df57abbcc8b6873a900b24ce7abea3c9078941bc4807f073d624a9514c8b2146fb8ed92221633ea1b5444cd927035792ae8e2c22a206cac941f7da4616aa0e32cb533bcfc704b421a5a676a2e93b0505c95b550d4c544e00267dbb6f72c08576e2350e1f469f3e98483749111862081de8772d4b56e7a49e96a234abe8eeea3e116b5c24746bdaef6b7d3493275a86ddaff53e254618fb4a19fb07b06e30ae735850e508df75c9f19de0ef29821309f3215956c124129bf0e4098b807229ce8086074b272accd3281655a57a501733a1da0399981da2ec635447792f9e548724a19f09c3fdbb2b78e747385b38d78a995c203d80254b4994420fdd4e01a32d812921ecc8b9c7d183ddad7d23d1f49477977a961f6ed52ee658fc49db2bead3b00d4445d8cb97b6af667936aac950647d04f7dec399d5bb2afa1d386b5c29a41583892242fac073acee0c7740470225faea9d56a87c6215b60c2c7ca9734afe6bf501ef2b16a25b8c64276475862d9a795aa5e5e334442f9a253d1aa5a930b88a83970585559b7ee75ca8a2fe119d85d7b8249c9ca40a079b1b873568e1b5551ca752b629be3e06ff7eb0faa4b490e11f169d15fc97c4dfa13f10fa031ae24e8080137e5b3690ed6ab8a7ecf87c7577fc2d8ad055c8214e103cc8920ec43fadb5e332b4e05261adaa0a2413b37b1ff24c2b81ef63b9bdad754cd1737d28904bb5670779d624f5bc1068cd0801e6001b8f0306d27a5957f15f60d1f3b443ec1b2d9802710178009518133e01a4fa23257e2302ff1ddff9750f6fc024a837c6af43b46a17aecf7bcc0a5d980bf7e91c36b631cab928e7652a3e5395289179e82399b02c978398c4e4796c2efa5a6cf4fd704ede56334970e33d447d6146bdbcdfeef36d319c0d40d3cadfe267a4bc688acbc30c9fc52689aaf22b132504da6328d3d259c692c8614c6f114c2136c651b2849ed57681cbce5d7ec868975d13f3b22e2bcb18c369a150158f43c588f848518a1c3503f525491444e881a8696f16b519af71ccdd9780c76e973e452fa293057a143030a0b4e6bd895823cf05a6dc82cb945740fa2e2e85dd61fcea1f0fa746c15674086a9839964f7382b9a0ffe83fa03816808fe035a21d6a7ca3675d7b1bb7c16eadd24b159d2d42ac8cfdc552389d1980bfda9ecf64ea08426a51851b7d24f05511f813af01fb2d15ec806d423e72ed88249a22b5c3428e0e51b9d1d6b3b180430513c2b90157403a533edc59300a243854217afd93138c291a9d91c861a66597f1c0a75708ce831bfe516e8a23f9eead92a0086d518d7da90b5e096652efffb956a443fa022a07dfd5aca652255b4bbfa4d6e7bb38778a0ecf9441718c9fd6e352c12b38112bb9575bfd5bafbfcc2bee0b8d36cbdaf4d8a279fb2f2c4688d7900602553a9d939afb7bded4c3c37a5d2fb7bd02ea964bd9a69a5c263234759936adf5f51f07cd2cdaeb75dd5ff76b4902adfc34dfa2ba87d1eb5bf0bb7d3aa9ea8308dc0156b5ee68c85c67cb39352aa13391e126a2308ca6a4d75e5110b0e9ae17cb0c50de55f41dc61f0cd5ca6e79715841d7bc10692cd9881858bc51c94e9229bf1992361e2e2bf02015ba3a9b1cb2f04f2ec93a44956c4af931d28f597f0c6ca20278ea4d5888d53971a147aea2cfa9aba84a0e82fed663cc3f65ee777e3e125eddc757ae872a6b7dc2560d07201882badc1fd2affe4b7775ef80d45ff2feb7cb12a9121ddd54807c1587bea92c8ec3bb40d154828071b6e417fbd5511957577aacf68db03863d4776bac2cafffeb84b7b9182131aabddc536355432bcf0f696a9aa126619489e1c2f837e56090a568740e2d036e1ad58ddc669f3eaeb25d781b0ca4a2309e5ffb034be64faccff6797b8a4fc845ba4f183033f1ab983220670ffc2231999ae83ffadb834c048dbb4d7fa5ccc3bd941084927cabb0c7ae5e10f7fdcd441232723b42a53c91c716cfd60bd1371d858f58b717353649911cd818916db570b2c071169ebb7653c9c590d438783eaab595ea2702f0ce7b618fb342fd2e44324ab75a30a4e6d5d41dd8d926328e96f99d871e4a368dbdd0b747bb8532b48be47f39a153c69b843532a8e93d7af5d643312d8e5b5a0c4a25f42219acb58f2a70185476e6e08384c5c6f49b77819579789c11ad80cc72568b6c9e9ca2dce874dabd6bba4de1b94886a49fec7cc5493534bee914bb968949d4e9831174104147cb09ecb22bc02aa12f5f38d0b5f26f0053046901c43f244c279943ba89b11e1f1c79b935d56d06f6bef29bb9aceaebb31884f5044cf3a013a91e663af156b02a26eb6f83c8cfabb0ba9646523a610d6fc98009973a394195fe11da460de87f866fe5573402d801d4f263ff9965a7280e28bbac1fb3a91532b116666f1014580a361bcd554368c3ca907f8255487d14c049ee329f36d1c965f0fb871c65390ce44bb2842a5678922df7aadef656b933226b0e5f49f1a87934e662483344f5b90192273be59a78d50521104002e0bb4d70665d566e48f7448cc15fea67bc89905dd64d560c9edc3772d7fd0fe79f04e1e16e153b4c198788d4958a48c5bf6907e77553abddebc4d936111042d296b4a8cc2094514829e441437056cd6627d8e24d3402595c750e0d6bdaccf61142b9eb40d792fc1263427c75c4804ff7a085386eb563553020d5a2d91f2b938c451d0e1e46a5e477f6fdf6230c4eafbddeb1e129692601de45a6c59a2902133e5f6a230745579e1ee33275b7949954beb877d6213b8f9e936bb6029697b62096eca3b90903a67767f7171721fad61db7398a629fc90e9e847224950390feb0d01424edb4a4e35b638e838aa595cb525d25225e5b021ec60f55019655ea536c72983be55d13bd4e96587c7ed40f3b4f505f0768d27f3233e3826a779b6da96ea128905ab30ae2dd9a9274f0d2b6d99e1d25bba5b08dce4414451e17b3fa4c6859ba8c4afc647dbedc69b6fc8b76d45f0224eba4fd5c40dba0b542bf719df330757f504b21a1aad7f1d401e2a5f83d2c6d2def10155855e5956be4409a483234b7c34f32756ffc36f090c50992fe760a384fe8b8b759e16c0e65b8ce2469aad7cc75aa3126b186eb7be7f494dac0d612e66872435f5ae50ab6971f21e6e8e22a4029135a61b7984c9f5f7c4e18a0410c3badf31e95fac7cfa3d154312c3a4682813c11462febbe9dab5402b434a4f05e49f052ff85a0d4f45bb7192106f45466f5b20c3c70847587331ac70b6d0f4bb56230fa6e66c50038cfb4624fac635162f6fb87cc1cec3726c00722b670a1efea68938df478446285c70505ffceb73aa508ecd914b96c4918556a269d38f495a8251b2b0be5adb91bfec46f849b060967363139e2d45f127b22b0f8439cea55ae51766bb3b92cfca3f4180bc8a1ef7f548bd86c4cc348b8544cb3db6ee5cffaf1929356d9288cb80407e847082711ea1d233184ae64a78a6a46ae59a4510ca760ab19d7d4e0ca3059ec005c0e864e5589b654150b015d2d314b519eafcc0b80c768df4e54836a95ea3ba878b035b0a05ac8826c5306e3659bd8126f5a261dae5b3851472b3cb8e118dab83603a556232584de2ab5d5cfac6d967120e469e0a249c2a84e7c2aa22391db067a3a275dc2a092b81d30f0fdcbc34faaf08bda5adca3346ec6454a80d366e899c2f7cadb8f4033ef6801f4ce7505fb9f5e9297932d4b73ff34b69074d344473edea2d1c4925f5c2b53c8436839466313c99b431c4a191275219dcca464842fd4a8352f4bc28ca6816e5564428a218d1396eb5f4fb77fd978bb38af36fb6e95a205e34cd60cfdc31d8d3139427e3707d7c278e09dc9b8a779e465269b1a008776d0858f259f86cb188578370b958c4bb8b76a169daeffc32533e6dd1c2de30d379e0bfb66a448b0f3060859cf1e061ee028af35bf19db4166d67846564c600ee1b32ed8ce2f2804d5646aad46f6f8777af521c4dd6a0bb77abcdb88c74ed3e9aa987517d220108b1a922ebe9bdd0598967c5d22003d678dfb92172a40a02c641e9346e75e2312cd96e7ac7691062d74ed5b2011007f14b9d6efae4e95b3ac039efac873f07191ce29a3a77aac664ae8246c66268b102f78c60330563d7a56c026214a8af6f2dab6207bcfa4f7edba740107d51fc2eb52afa264c2ddfe1827d1a1d7748cb4445b2f45b4bf953239bf7e60864399f74f5d24c1561c1176b8b564bfe086fe44fe158da0ec3a66ebcda0345bf73b5dc84a4698bc1c3b5bf5181f49cf3c818972d1d5b58802a4b7314b83be9d99ec89bb4015c386d96e87e99b3ced7eee25b9a7a22986e0843d77aef9caca912b5d0c88f55061380ea1529cdf84a222eff2e5864596c8cd9124646384f0cff8ce257aa2eee2dea3c3845c968e7b1c794963ab7e0908141a0d262886a99bb5fb9cc2ecd67c5574ffb4e7cef3e257c8aacc503f343ca9f02380b6e2726d982d1591818417dfa0ecca45fde0ed31a94b84e858415e52e22f5b9766a6daa8ec656da88c25c5c79700a6b9a107647fbfc45bd95232a2bdf60db1fb6bdf309bdb0ccccad1f11efb5dc1711d7cc3edc0caf0ca1909cc8283cf2a0042aec2a386cf7460c95d20944ab34213b702d777f31df7d266c3b12e63de1627802154a13eca949db37729d8d5ea9638b3e2706bcfdc5f20f2f822940ccc657083040464a9f93aba30924406880913b6f50cc7c28ca331d8d1d02f0e1def8a485517c0419fc11c39e23914fad2699e57923472d7145fd0f06afc16b379d8bb8a69cd0273254d470cf70030ce3fa02f1baa7213f2c34953515ac2e0ec98348f36fca03d91e8697cfd74614cfccd74c7e4798cb3d28bfc961136e2ac71ea2aebacb81bbb0829a0c07f672be14b67c03d8d9f31f0e2f079bc71de86b8b96a0d04e057fda6d35a7bdb49db820fd89817a7a9a28a5521eaa2d59858b96cb65a380b589ad04836f715661cd76a7eb5fa6c9f6d0a2d59634a960fc11538d24812af0cd6afc0d4b093f1801b6d001c24ce443e21590c524d303000805b45bdbb0e03447da1360ac21ad213a6027bc06c3e10587b4e26967b33564b96b530a58e6a5a8051ca179fbb5547e2e57a0d43287cd018565b6b75ee05ae07b8016948de2c4ca919a920991e025149b093a8c2fdc9ff78c7cc9406c25da988d7193b2bbe45cd379fed3a6ca7c322b05e4bbe017f1f2fdd3b2dff9d641e99a3d566d6134550196467e4326a20f8d1b090b8009c3633d80e776b2179d52854d461e1a8929d1e5096dc8743cdd1cfe7ca31ae14ad046ca127e58a3097e81c1dc164bf8e17d4829cd8de665a551a0b1de6c4d5e094ea71ee568863068de987841de2327e4b70d7b87d846fc1e05d1939bca72c93146344659f71332aa2166e1ff3417077cb28429b10d1c22da1a2ffe7c673ce6ce8fa1e66f22b1e28fbe63a915a507b095a8aab9bd16244cd369d39451dbb5db4145a583f48c2d2a7365424c4e77ad4999163905a029b4ae74f9a8720f310a7d4012a44aec6af7f92c71f53f5e9a9d92bb8329157068338468af607b2e87c5e3a455459dc62ecd7a00fc3a18cfc04b5e46c352bd863720b26a9369ca080274574e2766041555b8cc21515907e700c78a89e0f619a757e999b034773ba04951e3f7b70ddb668d91c8c0698815e16abb03d03ca9813aabf069f6ea2a1583eb4ef5288d43257e704ae86f80404db7822f605e894c5997382d6adf78ad1d86fec29589e76e887142ca7caf53df09fd1390b84365b0ba89bebaa1aca0fc5510538954961b137296bcfa152db73535223a74a4ebbf9d25eb30c726e450229ec2bf5a7d06026856c6281c0a62e534a486ea8f80fd2ccf5356a8e50f54fd2677db304d5220fbfcd29548d41b91a9971e84594ee5bef6577bec7127915eb0d24ac0c82b0d207e565d5b130d4bf2f1b50230b381934c79ef5cea9f8ec62c7344b17c91b3fbbf0f85753f70ebdd44d7629623304549ba870cb87bac1b9343d114855e38c4d9ada3d145541990aeb857eab77e2f70e400ff1a8bb12a429b3647749339e20a044d26bd685974d10be43e38266dc96e9c543670040e9443eda9b180d4a8a7d95f7d05ead92a4ac3d23723de7658e57e3d5693a945dac2d5962852c65fe9eb597ea9b06b3395f5d1132bc756c03a1b5da16d1e87705053bd9ce85a9d73412ee9aab365298c5cc7b0dc456282424934ed9746e084c088c62ce9467c5b0b06338b6dd9215b763372b481fafbabba955234d57fb24d9e93bd9e06afca8855a6b057f998b3e58f3d5902dd95cca4b81591a7bd3c8ce56b2915d27dcd51679cf75d43925efb841a040bf357f0d0cc86abcafe2456937483f7d5b58ddd0dac538fa3fe2342ae65669f760a288f9f523bbfd1e24dbc76b196aa82164a287a63a2970b881454a346aaa0dec14f963cc01bf266e801828d44584e73d3b94320fcaa79691c947539aca8a24bf6d9c9c693876e5f9cf7cc807051bb4d4c21b53bd5da722bf7f58a36b964d03786e4f165e04faf96b68a74e0f09a628f297cd9c348275c32d1dfe014aedbd76714faef4d670835d13d71bc14a868a5249d5c94c8f9a698afdca73b3a0b8808c4ed165f9a5552a9774963112457136bc5be16f2c70510dacccbe8a8dae3b917518f6af4a772d573e18b947a8af60a601823eb85ca0d83d429d7ef7cd186b6f555203a9729c067e422178ac293c12158962c6c03842f021ac12960562090d1930e50943f6b202ede345de177ed4ed3b36f485cc931ca5eb7bb0efe1be5bfa857a41381c089f7c31ab085328e55aaff6455cd76bdf74cec0ad2a706c47222ff2aaa7de08e034f88fdb6c9265b4a29a59452060f064f05e8056fe9929f4ff23cb951a8526506cdb77ea32d50a8e2350d30dfd61da62ebc4e6eb2a432f6d8715dc1376b49e5eba796c7d66bcfadaed88e55d08d22ac42d349a0c736271980c342bbea6cad5334dfac3fef37eb37fdeb60db21ea53fca9befb767e4c807dfa1011ccc62113037aede8fa0875db05e091c8f49f3a6680e9c8fa10127c18e2c310128c48301f307d82286c1c8eeed8b4b3761405c2911d7958a2322a53b59487274e4960c4a06f735d8ad7a4d2086b243e3b0ce86bff94e8db98f08baaa331e85f578200d37ba309f37712d276eb49d5f1f873c276eb2e36055d40a03abee3766fbe0c76bbddf62bc6a027d0d7fee3f9f5f99e6f9f3fde68c2f6ebb5ea288c18291ee79194c18cbf30e6d66ef7495777dd5eaf1e23e98e3f4a736b0783578192086b6b558cf17d0d773a9fafa3752c924915063d12104186e973249201a62318d62740ffdb19323d86b5adb5adddee2b1d79b0c9a0153fcd39270913ac3e4511767392f0ed38518281528abf5efdc784f9b583bd734ad5ed18e38e247debdfce6ece4942f50e6e9500d46f55013c6ab09b9384e954ed2f65e1ef18c3c4d7516b521b7c2c4c9f63d57427a4d2d26eb77bd2a25d8f419fc4fea3a4a9ff589faf02d3579faf441f8fa41d2140abbd38574defaedbbba1edbb51706e551d7a77ea2675e10d476ce709c9b945e2276047d269cfdcc20ea356af7834c13a75183a9f9f801d61d09fec30ee6e97f4934713b05787f143fdb6dbed7e97f809e01146dded766fc71feab8dd6ef7e16eb7fb3a9223d8ed76afa34ffed011c9d365afab4f479d12fd3b924c33fec77a0c134f3da9baf730ee6eb75b7bdd8f1d91ee6e8e2aa04e794cb79a3e7d822e9474da6eb77b3a6a65ea79a14a7053880b7bc2f0525532bc33be513c69664aa03ca7f9d9c0565a6b6dc38dbe23377e7829a648c1a1e6879427301a3b8ae8e0338315192bb12d3562778ee7799e1785a40a4d1db4a2744520ea4bc8961874e4889385871a58a21c3132b342738351394dd92f1b5821f6c2b2318354a13903121c7632c49adb9c1ee004e3032d8d0b61d6c4a0e5c0522bd346cc6c5d34ba72be72846c25192a6709c207f69ef09495f6d219e79c73c6b203c9e76c85735416d09c78a05f727659da861cb6160491fa9a210c980db46e121b1ee440eed54a6b1cd3ca87d5102bac2dc46bba57ceb060396d3569af1abb26298a95f31496016d870796730e2ffbb53515f62009db4c59e51ea2c072ce5987a5b185c5c985fc14e2c29e708642e4d01e58df7703dfd05c36ec9448648e95225934cc98c1e2a4c79490106c8cf1d8820108a719dcbc91e2c3cd072288e3a508950f1fd28cdca0851b865c2a62419a3cc881dca93cc881bccae9172cb2b04748539c1c3abc6e13d61286858700a283e389d5cd063872ce19890ecfcb996a4e7d4d894d8d4d954d9d4da5e170ca0ab03a6269c8c25b6bad776455a1b9c585e5c50eb01c84f7de7bef1d984ae692cab173e50712ec39799003b9df9cd6749855c656396e6c9db5d65aeb2f3055689299e10c7ca179d72d87f94e799ee7a1d1aa42334ad797d818129ab29c6f50ac0de5fb0a2cecd13da692d868c0f2c2ce9a929c73b6a17b6435e51db308980f6b15e2c29e30d4509e3170669f51f3a4425cd813864fb58a191f39e79c438be79c330e1daad0d48ae2bdc0d1e520543583f2eaaaf185c52c3b612a32990a4f210085302d64de10b1b1a6254d142968b059ce535e235a588295c52a889919179048299b52662fa4d0bc6c28d953daa3d20526c4ca856bb5ad9688588098f51045446dcdeac992303435ed2b2aecb1c156b69293af88e11757c6c23651c07ec0e94aeb41447b29e1f182f274b5d65aefc0aad004436676811d5a0ec218638cb1a8d010c5a13144091324374c512f4faed8b8e0799e6725aa0a4d1d3e866029c9ca19b0b2b51583b132ca0e0d8d0894a3acaf7cc656b87a54b3a979430e0f7220c7e15b67adb5d63e7455a1f9855ff820860b71614f185a5d1e39e73cc40acd9d33946faf365f61cf5c43c32c076160a69cb9b141a8a992b3508554716ab86b1ae7d9b5eb8aead2f1f365ea862e555e806839d124376429bb6d74641bb6daf03ccff3920ca942134b49d6169705926091e2f9408b0c8e140e4da684d1209f3d0ce41d75f5ef08f28497f0eff2b8bf8bb3847ff4e6e23cba5f0924eb493dbe9d82483dbe470bbe5081e98d88befd13fd8ea4ad033109e7b89194f1382fe15714a3fc48bc263cca8f4437218e4b23584186fbac215c17dde623d82b562775fc7a093149c9fe2091257cc745d1667331898fff884a1d8ce4895157bfc7af9b709b97487ee867a965f7624fdfc7c7250fdcdd472499dec74571e90317dd821d9081c293f8b8530a177d042b8a4f7ee45e41f71e9164fa1eff7c4423a2bbb8820c0f5c1c771eb8e8e2e89404b799dec5a524db93e0760a1fbf8e134d8ce4f5126f93d1236adf587cf2230fc62437e711939464bc55a2494936075d474c02475dd55510c484369f7f9de66c5e08a4aede4692c9366d22dee24cb28ee9cbc84cb8aad3e6b3bba7064d1ffa219c4fde7e7efed56fd4d5cf9f4f1029467decd99132fe3ce72ae2cff3a8ab3bfee9781ec9251df1c98f76becc32bd9e5d0b923ff1b3fcd2f2dffc6c37193b62f6273f5a41860374c61de83ba3d3ac21507ff2a31d9f479febaaf6efbbe988f3288b641593643d2e25a5907dfb1e5d909d8e3094ac2efca414b26bd7a30bb2ef11468cf0a92e7c3a82a54bffadade9eacf1a34f7d6596bd1a2e0a6aec1f036ab8a15412b705668c807ea4655450e22fa6e5a67d7d1c93fd6ab1d9a12e2b04854837c070d4109f5a894e2b5ef3197dd8eb80d4d09adcd37ef8f7e2ac8a3cd75f5ef6d86b7f91fcfed5655648cb348a9125dfbc93e9f29b7b5bbd8a31e993e8be4d267d72299fdf6e59b8bebd86d48b7ce4db36f6dc2fc3cdee8cf1984abcfb086b5d65a6b69a5420ce5a0eabed65df53beae8db71522f59ca1e849f6596acf7448d5d5749ecd5f55d7a6facd61393741583fe2dd9e6f644281156f0a044b0ad306b089e7b62f5db6f9deff883649dcaa84cbbd6b16fd4d5ab49fa4e3576edd397e8d2d31208aea75e7dfbd6d37c5bd259cf2fd0e9acdb3c46f53ccae0698ceab6915cca696f73bc455db535ebea6b9cd3da27282655a78e41a48ce9d674c4baaac017d709d66cc23c81aedda7a01e93aae7d18278d7a01f3cce5183f926d0f4eb79c612b02f62d4c7a3b52246ca4bd8b5679164fa3c7f89f41c8fba6ab361512242e8ddee0f21f6bb2f98cb9ec5189e5f18d975da2df644a5f95a544af176d4d5af172ca12255c7e30573d9b32761d7d5cfe3985d571ffb1ec9ea7be9eb579c382425ac0ebea83d385bebf883547dfcc17e6fd97707209ce59b45cf27b8f1c6f72798a95ff10af200f8595ea97aadb7f92dbd90bcf5ebdfce0ffd10da2add493afbd775f5c9eda0ef7182255ca4ebb691b479f5adab6ff36f484a9874c78b048a4950d8595b5b5bc339bb4dc4f7e623ec55242fb9f4365b92aeeaea83180841ffaa67723bde23597fbb57a3fc0824ef7f1ee547d701e0b8346b08365f9a3584ea37af3e82ad22769b98dd139564bc9d3f95c4a9777cd30066429bd5818b5e502e2d67470146418e0b6fd43eb2d38d58bfe3dd7fefbd3731d6628c2fa698e28bf1a423a65c18638c31c634bfd9b1d65a6b6dc5855f3dc6feea2754b7e35093903ecf4f33326d8ad0a8b1153c61da396647d2da91720de120fc7e9657a27e3a53a5b71cbe09e4d488d953aeb20a98a75c4fd1beb4a2e4c96dc5aca472f6d689e8eeff05e5fae9942ad13525a0a74e9d9e5ed15987b2c2595ad1a4a6547ebaf59b751c96b78ec2ab8a59768179eb406fb73c9559a4442d28012ae5afd73bd67a6bf5112a9d32595c914e9945601d95f653b7930575ccc23781b8688b4a43389d1a992c54a04e4f8dcca2e92c54a05366d1747a5576ed9e725d3de59a2c5498b3684af93b2a5d9172cda239d4249ccfc34565b2983f47af348474050a55b24c21aa02852a5c66d07cb2443f4b32459e0ca7e0286e4d787f966488fc0b7808cdfd2213e47159645ad0f2659f40b4524b2bad14db4aebc5bbde6a2badd4d24a2bfd284833ddd4a3975e5ae9a596567aa9a5955a5a69a5d8565a2fdef5aee096c5695500e66c0ac0dc5cc19caf259c787bcbb9bd53800230972911270073db04604e8f71a19edb24a802fded2498b3360198f35a94a08e718d4004604ed7b1253c2200735e084ebc70020463521f70d3e38131617a3ae0c5c381314e466c80c4cf72cc11129ea701132898f8598eb132918131347830b07381bdd366a72ab7b0402de1a54405c05c1633a5870245ec1056e5cd981a54da1ccd29b4ab5e7c5759c504ae18279f04c05cae62b04a44404c90db096272801e04c09cb63c7378906e6b6e1e3601cc691f311f17602ed7305478aa4ac881396db5741e400298cb340c92f0280c91efc3f4b079de083a57228491d2a9724003be2b068039af0518c209ab588039afc502324f199e2a05b0b02bb6f7c5236d2ac09ca76d08604e7bde170f84045c30595870002330a72f98ad112c30402d401198cb3e477c88866e7845000d064810cf95e709008c140fa90390d33784131e01c008e6740827bc126d0827243da7c9f4785ed61f8039ed0198cb148c44f103e6b415a2cab6b967f34e8039af0af1e46301e8f916e96b1325c09cae42e0f020096a16b807869e9e1c2484d8f1d8353b79841d03c32f60b71d9d9fe5172a3a399d45ca93c57305f2fc2cbf64f1dc2e7804ac9a5305b6d9b6ed67f98588cdfba263c4fe3263a785ae56cd56a9c837a3e52b15f866b47c47941961c190a0ae6739fc4892033956a094399a5368d35ba5955a5a6da5957e14a4996eead14b2fadf4524b2bbdb4e8467b80436442ec57af303ac28e9b2cf06ba2a220be7a5151d50f2222fdb3f4d2f57a0f81391d6a855a679de990171bb6b641606e5b2f572ee8bda900c0dca640800ca06616f20bdad620fbd9f8de3aad12a2ebd7165e6ab8c06f9de2aa21604ed32d04e6b4cf522c2dbff53a03055dd41643d54bf3be14b616f583efbbddbecf368bf22cca0e7af0ad9847da5180df9a2fff5c151de4b107abe63ec102b7efd58fbdb5ceb3487b9e45791669c7b328cfa2ec9e0952bf76b7534509d0451e730394313f67d0fde1d303e6b405261b1cb85c59d2a6c6d8d9d19c42db00604eff6774805b058180b9ed731655d0057d17d6d6eab9972fb5950073b69c776f60b75b09f8a9d7122d7c6ac8155ef580b94d02cce9902be41971afc29cb61f0f5fd501c19caea012f08ade28a8048401cccda2ecf7bb4734053df75e679db5cf599447bbb5beaab3287b9d457a1665a7b328fb9c457ab4d9abb61f90efa330dcc03c6e16dd3cfecf52ccc757c73f4bb12a1a0474819f7a16e70b600e8435e1ed672906f533df9eba531f6bbe17d605eda12092756c2faeb859441d378ba6634ad388cd781223e769ff2cc3da8461664d62c29cc48499691213d6252c882c61c2a4a6be84b130f5258c888d2f6147a4bedc301e5e07a91b0bb69f651891c79561476a7801ea92c5a52bec8bd70416c407e6a50bd7ee82a509acece2e6757e965dca3ce946f889cb962d624358b0167bbc25eda98aa5a23df8597249c24506babf608489ddb834e9b02a3a5c747e965c5830c285446e7b5c452969e28252a48b930b45ea7a9003b933c1ee41976aa77af47ba9950b632ebca1f1264d7339a27180727707b23f4b2e3c9e74231c85b9e8a0c1858a06d63f4b2e505a3603669415fba8dc5256966a4358f0254be811e40895314ca444d16052394790148c703831c61ed2788c4bb0a7c74d47f88e55762ec1d6bcf7b30423b36686accc6d8354e0b2f9da7bed9b28cbc6a986383acb9e565c7aefed962599bf77a82b9ca11df3cecfb22cdff6b32cbb7096212a5f52004291b5660c884a0a463e346839928291d84d6bbf9e97b35d6d133ded83745d7b76d6a09bf6fc529c8fe3dcc7e636dbbdf4ba09b789267c489be3fc2ee96a7d1f138e73138e53e1a3a5ebe95f2d5baec7b7b82392f7775c4724975ec74391a42a489453412848823ce15e0e020c10609ff6db7bb494bdcd7b2ee882e9716e13afcf6bc9d0b7dbdc8adfc5b93b6e0cd96755412fd5d9b75e7dee7105ed19037a9c56c80a51eab75e2919ec2d3390d82d631f080a7806ec50ef06fa8e488fbe0956a082db77443aadd075901e55318b7e459be79b45ed37f18a3de3d4592c5b763e2470bf7c73c6b85c732ac0569a53bbd3715352ae613d39c5f0b55bc7dbbf4058fdbb4048ddfaa66ef6d55c830098945d19fb414a199b1c46787864d4a2e6b8409416465504d60f31305c3655a9e6e3ef258a23809fa5da1335264ee4c08173f3195182e7629c091d520f4ee40e7676b5811fa4f8f974ac93f0504c5b7b6830a76a3787aac6d69c1d95cba91116e1b4d3ce9aae579a9a1a358d0cca2b2dcceb428b2a02088e0808341f3888aecc1e7eccc131258e952c376c1c695d706940e0f8a8d1dc1ac104fc00132210e44035688407a469a5657d0e923c693425f4668892064395b42a1fa8421ca449ed90e694b5c624ce12375c663a1041c31054c2d42096c3161c80e8c1ca13ae1bf8a0c4890d9868e98065c30d34fc10e222368478089220497c34b1dad2c1c3d4912828729ca4c8d975a53d6944e4240edacf6aadb571cede5a3b9f9c5b2b6e1dbd1de28c11051152a15cf587195fa3ccf8ea1fbe5dbf275010c484d6ad6b3047b30e9b3c59648c9da4d41bafabc0f4d7317d4ae10c29e3383db9b3d0b6beda6aadcef4aa6489b2d8fc6d60cedb1ef69c8e6412f6ebf9d314c479dcb388fabcde2c7a018b152b40568cea0aac2535d850264c0d607b6ac43d6dbd600398dd0b6bcedce00104b5e3e7aa488d16d09c9419b3c58685001421bcdcdc20062e3548dd70e81143181c9c68b88216150000881b6ac7d5e981c90e438c64e192b59f7a44e07186eccc17133332ec1aba0612235fa4685185d94e24ce3491e18c5a94263a84d84d3ce8931f3eb2667003856ac8a6915d20a64717acb118ca7cd93376806ca95135264a8d72ef78ec10e04401b91a0325e4c99a138979099b51840d5f566a1e600992034c4790afb1393c3e48116707355b7478128395b41461b45a984104d85a0c7276a000f2c4860f1d48ba1479128707cfd65690355a3b9098ed008747070d086288ac356072680305ad040a2e4dc2d8a80147899c227cde802468c264cc4a0e6590b81971f6a19064ca922142e03809d2c684561a1498304b98e4c06687b766876d6681c34406316084f8f2658699587821082a24438ad090a5e644106b80f08222c30d333748c3234cd3c30b674d5ac072068d0e9117998eb4af1b67dea8e932a22b4195cbd75a271065b26d88737c31c645583dbe3d38b28a7111548f2fc618bbc9222a52c4eebe41b3d68d9bc73f4b37664f863e2767a218c29c513833f33c22ced0c0d24d0d1c4432e833448c8800c10043d566ce72d04424c428c4b90715a0c9bd2900110539d33ad3218a5e7b56c313d190eb40672c9cdd78d28d6adc13fe555ab3ac01332b239a5aa39bf4106bc8f29a34529b524aa9d2b54e73e02caa3e8baa4fb1cdee6910ce928dd9d7323f59cc59e267c926cbe38a588326c566061b29363b9ed8f48022b2c126091b18b2d7b7066dc6962ad9ecd03f4b363d2e9ee19b75d61e280be41ac1b71cbe9f659b27f98631c6e2cc5d2bb6016b436593f859b621a36de46c9d1fa4fdd6ebdb919cc1dfa68fa8ca66d1c5b4379e9f659a1920ee6799a64a47fc59a639b293a6eb71651a227c843f4b35509ee7d93e355b267e966ab04010d4d9517306809fa51a2c5fcf8d8afeb334737adc8d8a297e966633f4dc48902041a2c7a73413c34d16277e96663e3cce6ff3fbdebe1d4915be7a6abe59777e6e21b3f138141f02ba6e10451901114519a1f1a25243d3c44a0d8d112b35ff59a2817af0675916a70c08b5322a6a6550d4ca70a05686835a190d6a65576a287e966536ca769ad2595429d2dded763b8cabcd741a00dfb7f43f7cbb31ea67a46b2bb53adaf588ddd65a9f1819ed8c88661035116146040d22a7b5126564b839738ce600cda13866bec8ccc0b9beefbdf7cd073fcb3354fefe2ccffcc0e5242fc1676a88ba6ece5c455937e31918a2dc9c6921cacd191e4e6e7269268d99312b381c1aae88331ceeececccec86eb41650ee4d8882dc847960324ab01e776ef55632b23d3579aeebd60ca64b995f172a30c9781722b73a5ac24f3fafcb3249bf2a41b8140d839555427edb573ce592b8cfabafab5c5bdf5ad935557bf7eb55043656988208208a3216ae08166082238d00c71e3bf9fe5103b3321ca6895917143e68ccc1632568e944132c496d9c5f7de2f7fc9b4fede92cc87bf40d59691d1e17702949ba8c4eb9a2a6a1022c50810000000c3170000200c0608244194244110835cf8011400095a924e543228a10444a130140c8581280a621808821004621004a3108603599e4379006f08f625a52f948835d9a91cf4e87e805b4d6f4df601d52d4ec33bcf3d2d3cb2ef4fa9b91f13e453a7286cfd7e8fdac537da566343a7491b1cc373e4b609d88932d64caf92684f9c1034e1d0a249b244afb01cd0faaf5e890543d89466a24a107bf452882f2f7655c21e63184f5a1211b5b7854fde71975188f7dfe82e7626de93b73ab7205a3e88db850b9ed2700fe052e8227d43892d0f9e0a890df5a8292dfcdd9c319a368becad0b221fe3d33c420a543c767b20ddfe5c764e092255a42feed35312f4bf3144d892cb770951a74d32648585e0da50e8ab43760ca2e7ce409b4da4706f4b8b06731862c939f45b5a5aa71b3e8867c6f5ac641db00cd6ce4e1b222e12dfebf302f45ee3e2d9ac0cc6ad6d7d4ab823b4b06a5fdef9ad22c8adee66d72d36d8ad786c782661414b245898c148db51440d011450f010ee8398131faff942ec5208f7e5ef2d1ccc21a262a20cb37a500841a3031097ad9f31483a2882124a7d18101198160045036a53e505710e7a3168e594c5dd7f64d1398afcdb0fdc23f5ba786357702997c9c5ddcd6d5dcffdded81ddeca7bbaf9bbbd6df7bfdf1bbb83b7724f377f376eeb7ef77b63eef0566e626a3f0e16c337fbaf1ffa39f18cee60f6c5246bbd2b22f01f85bfecebee5079eafebd571fff9aad298321a14848d670ec65890022d8cf7d4f62be8c0b3618661945fbb2a405c71d2c938fe25d95ba286e9d4f2331374906c68f5be2daa789e8528d054e053941107c14d5c678e05c3ead5a18ec07b3ec040230766313faa8069b20c3fa51d5e9eb27931cdba7796860e2f58cbbe8ecf560470f7310e3cdeb508a3d87de9a87aeca0e102834ab3e11b9296e0a3a9b298010e095f65f4ba53c2c2cf8600b69d2458dc159b07a96aba364a935308e1d0268d2e6f9bf189eb43cb4d61926a1c0073c3823cb6b7d376abd199cb3532de9876240c4c7401d9f8193c9b93ddd927f3807637c0cb07eb8e2639ddfd99c5bd22df986b29663626a20a30ec9ce7826e278953010f7f2300f5556f619473fcf97638470f9d64532c1b2e142876991e660667e5105037cfacab5670b835c418e009bd1fea3aadbc60b0118995b0b90fcd06d804e1eaa2005c4baa5e0ec0a7ea697e289f063f44252ae61e92d5069a44c3f3c0f2c192f8fa8edb0c10b20be72a73b5ef8c765af2c9941fc0a8db7d2d321d8214fe2c548ba653ec3cf7c25dba3ce1155eb54748f790bec9e87d2d11f41e83414c1a35d1a17c99e47ac3bac0a1b3757a0258a3aad9b1f0551b16ddf8498b0eb2715daee53d45e4772fb3dd9fee4368de2ea00fd45bf955bd2b4dca24bbd71975b113df686187fad43635358bf0fe976cb36f3cf86a3de28fc03cc35f141da38c9e6450a1f4081578f3f04301d68b7d5a2800960bb15471668714417bdb2039d7b2d2789f11ef588a2381ec7f122d7eebb1c3157087cf1e884ef48f14b7a032a4e607fb04706330273e0dabb033e68fa6725ebf0962426a4d04df6f55eab0db04f1c406dfefe849aba0c61ea22ffb307c79d75940ece2f8cea004b9826b80a72533d1a795a3244c3fcfe326120233d650a87a3b4900d8ee18e7b59a8f7ad35e513acafcdf96cc1a5283cea440c46a326f85bc4d489b05d0de1db35b54b76e4b31bc76b159c3f7ff51e1660e9e326cfea4e88e5e376b2152df47b7386967e694e57f1775a680f416f8bb63f83ae6219b423a1f5739e91e0f645fb4b84a2fba1039459fea0e70a618044bd52f66be2a7c6b2e2f8b9adf6b2a3fe8a3f2f3a7ed93d59eb3fa9eb137a246f69f18415f05ff76397fecb955367a248a54519099e6bbd81a0b21fa5270b0b85ff6e7d993e6f75b7f67aabd8f9ed41485f237278ade530d445bff18c75b85ff7fc2f9de27be68f78e665fae71601be0dccdb7ee91117f18fc46ee142690239f18177ad42eb53847210a930bff3a0d56d72547cd9fc03a30dbfb49dabf82fc7bed842ea0f92e9d0f90d41b320c2e329d4690cf682e8ae47866d63c0c00bcb07237ebe1a216ed20cd2e9570d3efa86ff968e2f962fa923e2b15671157ab70c8fdbaf07bb229e33c2d132922d035f529dfe292dfb8956c26c632bbc66fcb21e435e7409d3d27da04ed2aa043ab300a267fedcf7a96ee874bdf6ab1125ef22a33c8a36cdfaee5bc0305c94604e52f2a4da592a6a59c1f4ca2c583f0c87459c07c344b4a48de1dcfd77d0f08c464f42ae4e62002637cf83a0eccf104d644ec545c9582047d19b027738d00f2ed5510b93c27be604146f3d95e207f8dca8eed6cee43786779a57831fd86de207a99c683182ee11bb6e1acff97e521abdf913497f063ca2e616dddb6a2bfac013dc1a982ee576bf4565af01f4cefe7412c19ad9dd912fe737913110040c4de2d85ab80db42ccab34779d582a09e8522a70104c8d88d12751c020224d481dc7651f0bec65e745638835c539c06a90165d6def00526e1a641277fe3d17aad37c3229f345c2deee09107fd449ff066d17201a95e194cfcbed970359ecbd949f8a39a83fcebfc10b75d046603bbf8b83f6dade6e002116f63d82f71f9c3784abc1c3f2a252faeff43631d7818bfc486e057c117587713b4b5b11c6989e10ae4f565520ef49d5811721a5e2dc8033c90f31299b78b83721a3a39dff6dd94a441374635b66b3ca41fa23fc105447415c0e561e45bc2755ea714402b7e2e1bb4dd55a6866781b6dcf21d705221bc764621fdc95844111d660191010ae80eb3170c3a47aefae9de817d421010892f9af0af0086fe30884891fce2f9f14f4f8cea0edc00bc96bf4a3e32704131b6aba03055e1135c25687890a19cca4b8b63f20e7842048daa4534863b80e3d39a73c03d12f49e8f265262a256f36651730e8c4872ff782a1200097d92203e24e1156ff35937922443dfac89d4b6ec80164959c4373830417aaf5db1ad5df4b2561695b89b5dae157857c1f0b69635107800919e869959546152853edc074579282773d03043e8f9ae5d1e5f4163700e5441bb6fe4c9de9a663dd70d03e7b0915db4a6fa5c582c71db6b0a0948c4ab3b4bdf01d249a5cb5b38dec3da6f88c9e2186f95c3ad0425ffece3e04e380fcc166865285e67d20f4c6e4c4addf7c05fc1061f13841a2982ea494a8354d27827790d5589d111516ae393bd6087192577c8d57dc9969cb9825debdfdb12fe70b5134ea01b85b7f14ac0bdacc11adfefd5baa661de989898fc06e6592c00e29700e0b32ed9bde950fc166dda832ad7ba4331239a281e25a61337fd5feb61900e036470920aca9e2d67c2aad8cf4727f4e613dd3f02c7f802c19318df719d543fe6d3517ac181a6a347b4ebb2f9507367149ff3895d96740aedadbdbd2f44066c564a8036f731457f571b4c0fc706b476c29b772ab2aba1e8f62f9d43a716fc497b602f17cb4708555721f80ca5a46a001f17dde0fdb01c7b771e1f02d3cb73b833f601f8d5d108c43fc9479017dcf36f242a7c4ff8639b13585c9cff49e3d24fab2e47253d7f39cc9a57ad2546d5c53fc6f9516a2b14f1c31f832dee4196f1edc98d6402c949e3a1e7cee8bde2880bce3a582f209ff609c7f1e285ba2c60c6886b82d9b8558c6120451aeb7bc254adc7afa8ea87ac48a52f2b949173130f1eb32d208a10671872194d3cfd8d79fec66015f1ac3f384426c6396eb9eef3144e459c539b68e9fe79d8f0295c504e78d58a5e0377d513000b0d28dc92b67c765a906168544ce77bcdc3b5ccf06758c27c1641103c5b86f0f5483424d44cf614894a71e02801eb2fa757bbd5c0a661d40047c8752ad9087c3aa2e42ea5990d41a63efe3f40e15f44b2258e3adca0c7beb74b35873a70395eebb7b138ceef7e8c02f9e26536f1c4ee7234bf51034ce338f65c04fdc1c57f08f0a72a884b225e09833430f5d2565566edbf0f28f8d7a16097e31711f9859e7c9815c63f17e6f5e4c8b3ba1ffa74e64f602ce27fd408967b8d11264c8b0f283fbcaf3a4751981e45d2ede514808974f1599df6516e71ad2c8a0ede32a0350d1abdd6d238d537594124f384f6dc7bad8172ea026a376b5d4ce47b144f6b861249c80af9949c9807850ac769d6c7d57bb00960185c899fb79acd3dcd896756d5842fbd2937c437d3413d517f06d79df13d9d0a82a310577f76115cb7ce8f183a0488c29acfbb2270c5dbb873d63e651fc08a0a2ea50b9300430fa204f4d527c93bc13a78f8b181254304aec9919c15a1b27cf16e80fb954c301857b01e7f438effe7bd7d9f1d9b77dc9d3fe6ff58269d59e5df21f3bcf57f2cdbc4cc43bba61feaec9b6e5e360ecd3baec94f689ca7ff694bae5b1042127e91897e3842438bfb517b16cb48c9194b7d7785cd6354bc336d879323a2355751bf1d66eff21d7393752a040f2103d5635c1a8ae0c790ac8de30878b64ef22a5bbdc5fe652e9668de6c7744b258d21e2e7e9bcd199effd4858c2956fb182175e66c579809dc0b0abeac765d1b637818b6f4f32510c672220788b7a25b4e2bdd2e430f69d893881d4ca51d274e36514a3550436939f2a9c0bd90ec3c53fc63664a4c3038714a5ef0667cbf5b318b15ca28eca3c3c5cf206da3273f211cbf2e9f8dbf5bfc651ef5021145594a72cda05965ba0d5800692a8b220f104db55839af75c0ceaf16f4d499025f029762a8276271c3413a21ced80f784aed850c286ed166c69744c0bec04b0088202dd3f3cbe6a7ddab11bd9e4919eb08510d48d32638a3c97d01eccf7aff0d902d7935616a35392fd9ff1ff6c3d0b05bc362c63d466f0f087015abe01b838d07bb5ccda1a6c653a6d3d912098b8414c63a9bc263260a889259724a895f4623008181bee3bb060e97fb40e9a1c45f92876d28281e855352949800f2f41410638338aed668bff597401d05a4b7132d88b4494adea600b8e511fa8103bc62143fffc39ba50b69a32f1811fd4f05c3fb3f45e312e8aa374494acbdd5ebed202d3e5ad841e35568e081091c1ecca26a4fe73c74c644301117a3faf662b3dd8dd49055d05b1691958eef1a7cb3e95455d8712745743d274070b67654a0f242ba1e3c5b7d94333aeb1886256290e8b0383326fac8cf4d4f6855316046979c9c705c4f6d8f85dcae983952f8a5a37466220985a8db6c9c79c81308fb1603f8afa5f631d01bb6b1ed0910498fe369f44cfad48fa7338dbecfff4aacf5b4af05bb3ab2bd779c46c139175c39b00d03844580f37bbd0c3974170a42ff3a77781003b754ffcd2c21e82f7461f0522396e0d3592ed83ab75a7ddabd88ed66f08e5b48c6124963897a829c8792771e13b8ae9eb53f7d2d3b05188705678a5f4a432cb55a7674f5cf23d34994b377aea71cb9c5b570cdb2c60fd22103c79908875410eda70aa732f0b9f997feb18761c70c9362f0d8ba5294b580f534d3925f35b0e7d3d9a1250d7f05830894ef2e45d06db64355b1fe17b0a388429ad61e864c93b72a1ee94bb5c48c02941a578ef17a81c0c965375f333dce1d7af1b58a1899265dfd8d866268ed80ddb500f320475d38a06459d4879be976e8fda7f657846a8e822293dedb4e89a93c7a965e48e21b58df6d4f019480120112204db01082eadea8d3587279845f21e9fca1ff6ce0425adda94afe106f2b3089ef95e088eff329b3d69b1e68ae5c78836af78e2af38f7bf3cebbffc7ec138bb4cba69fb5fc3354438a394d721b5b2bb9218d98409ca79adeb7a8ad271abdc95e036a34de06410d5187e480ef7719a17902fdcc970e27903b58d10171efdb31067d98f72605021ca9ba6fe8316d59588e410edc4e21a5fd58687113823b94f49b6f8e67d06391640dde45ece52890794aeef96927d9c6f826c584b880d8b25c0702b888558a8896dbad6135035c70c2826bdce0f8aaee34beba7df23a229628c9487c9b814fbb34384bc14e0f9d1a8b25519c376017ea0522201a9002ec2f0ba8b1d98903c7ce8387106e0485fc0bf9a25dc0c3028ee35e65d53e7a0348892393e599761292b3b4589d231998ce01d7d70813711dd4e8543bdd9c631aa84d51462dd4c85dece81a6baf4791c0f0c74fd7673827b6e8068f4313b96f4006b7061fbd7e7bba679b3c6cf1a3984d9d318b2faeadc4db92dbda621a0f8fec3b8887fb4332ab5dc64aac7d0e9521cd3a3370c6867b3cc5846a21a1649a76734af2ba22a2852ce15b0ab53b6022d3f6f5c2c5d52f6d20299302e737ab6aa4f788b785c19fdf6284ab9bdec3d55ca22c9039e83a809b2cae9ca098013dbf489fc3b7e8a3fd68ad92a2a47ba26e478d49f9c55c0427a9e2bc970e8c80465b30ae794be41c338289ab6102a4d108059e03500fc88a65b5307f4efff528134fc3bf3ed0b3f6dda14faa9a5218e194d6d2beeef229795c8534313b9be8a29e56f3d0363cef8814a31a13b873615f56cc4c4ca0cca8013ea56a1632e52de396aa9dab565b15538221aeabdd3094e22f9554fb6d84e3e2d39fbefd30adc835b8d0b9b7b96ac0ae194b4e2612a466f1078bcc59e50bf17330882382284735daf404e55a5e710cb2f92e8e21269efacbfc9171798ed1a39ad5bc23a59613ede5be283750d64b3eb62cc2b49e8201a62a2a3f255668d4f75585913144c832becdccf64a1dba7494d94716e6f695e73185f0529457e7c967493cfa6fb365c50df115c33aef0eee52c07ffe50384a208a9662347cce083e28c48bd4ba36a9f185f4ef91a3bd85fbf97b6e1c96b10f9f59ce03468035899bd61f9753503a4d74105935ea8c2cbd206ddb97d5a1fc1f4d4558041f427a0c8219aef47a1e05109dceb79e3eccf4090d4520f599ed31346247c5aee8e09fcfdf8b4d84cbce98bf32def5a5c2c455c672ce30c1f31f2d3fdc21e5be3bd85555052d991f125e4509826ab9ff190ed51c7b959d636456f20111a569fa29075d44c58df552227a5bc2815abbca36f535b5ef380d14ac95826688542774e5b7bdd7f8675e6a0e4e9ba85368164a4f508023811c835df08c85fdf908c4f5f5c099ea6225c7a1380914a33c65c5287e392993afc3eab4a91baf82c4947ce631d09648998f534f2d2780d4f4e8b2464cdabdc7a5ad5995735a44b080ab6b82ddab0f4f36466665929a9ff34565ff06159becd0f696d7ac246c750e36d7a6f7854b9df58ca295dad97a2066af1ca4c706b341b8cd5d90c5c5dd3f85042707b66e47405c19ec9a37a7e9fce015207a761c932f11cc6aad441341f29dd6b6833d490d4c6437cd36947fabf40cf55ca5888498d774033adab5d92e766807896948cb1dd4029d3278370a24e8e28c8884117195254238d531f6ddf04d634f8239745707938c581eb95658582266563928be82482a1f22c86c4994a14c3c12a01d70d827703e090b0caabe13f34cee6e1bf6ce3747ed3835589e7b1a0e4d0207e3bf03baee1a4f88b1cd9b9f229ba0f47be70e85258a6759d36da31e27c1f017480295217f2a5f6af7b026246696c99a7f8bf49122ceaed3c258514ee00fba085f6a2bee10d69f2574c27be352b5389b1f7e1232c055d8fc5ecba40e36785dba784f59696f39dfaeb18fe6e738f2896b3849a3122cf36292fb9ffa108ee0a140b0a5ed177c06939de4ef1db3b6708707d16b094ef7ec3059a454d8d443da37d1ab0fee2bf4c0cd2c31cc6a667840df26d690dc65c78e14d956605ba29beef803b87e2bd550e101b0706cacf77832b56674d5bc429d5b9fea85a919e12f74d64ab2851f54821b9f84789b187dafcd813a4d73f9bb7a869ae048d031eacb48bee13485b910d88268bf068d753ed9b9bf62d2d2bbe9229186e82b3a24a3aa10cdf4045484ba4fd0b0c96119535c5835fbe17d821fa3e76e016d5a67755f9cf9d98010b50c95ebda505f2754c3ee2d7b38bc398dfa81e9d4b0dbb3a38c0cf2659db4ac786a216fabb604639ca7833e7d39011c742a12c7dee8cc14a3faf8b43d2a29a69c6bc3e1eeba9ac3469e4dc672b360dab155c84db6df08ef16b263f2ef264bc56092583431252e1bcd0c7e4b8b689535d6cfebadd0eb04998ab505d93e3c2713d4f74ba7fa12f429b674c3f3fd2b11369df79667840c09d0c53259edb1b9e99ed7c9da418b9ce3346b03b80d8d5ed68d36745c12339eb03bfa0f9f47581dcc67393091525429b08842a16d53c57211e606cfc9d2c64923932bb2560885f8ed8149b09ffc2837ac7f654ea49ea2a03fc457fa3c98e78cb4113763aa6cf85a6559f4842269fa96b19026a33e0a93f7fc4f42405eaae74f2f9b3de7582af42db3ad747c5ee6a72b28e36ad50bdb5ce98d2f9043b6e42de9954e15c8f4f1686ee2652273b9ec7527b800e4b2279dc242d4c653b7b8934d6bad3cb56fedfe5ff560f2328160d4ea4f30704026f3974125939392d595cc7624476ae9f66ee1997cf0d3e395e67d5baa43af7dbf596bb3380341314081a3d82a0726006ccc1f4c5ab85f70585d7eb402f2a13535776807fde8cec59141882cb347633c198cd31e6a60664c4e749f18443fcfcdbedd0e52c4d1276440c47d8ab9dfba245bce3681f83f552db95d4e9e95883d007032b2638ac1cc972e6a796f4852c5f3b69b7eaa6ebb26ca62b54bb181533fd6ee5ad331cdb711227589ad63b43254284f79fe478acdef813cd26b0f86b3b093325f2412e1bf68589c401080d833fb97113e741d0e427dda0af9721742e2ee89badd512d9effdb9cf8bbccc1db77103974f07ac4512ff07e39473de93d8593be55cdf78c88fc91575aca8e387dd5c46f152ea3fd14ca040e65e1d7e48e74716ec3a4df6ea7604b5dba0eabf15017cf221eb846a7e9545953906e3d868f366017f08f76d53dbdc550363c6ac6f57e19cb34d19acaa6e8adf510967a1ecfcfc00bfa2667a78103d3be1fccef7d0a61a66ebe4294c7563633f99eb50c38c4dee1f1fb9558e67478e072efbc257a454a716cb1fb7bb1b9905c2c55a9ce7e0f0110fd853793a1c8082feed4e65462e3b9e49eec511e0da82171b0baec5ec8073600c5abec0d3e1d482a71f73c42cfafa8f1dd0d63fa3ed483dc1b8a87698eee95e6b9323dbdb1f2bbde05f48d0d9c7e4e596e30da07422f458b3cdb8827b41d9c9d3089bd1cf1e9dcfa429c6dd5c2f8fe5f20d151761a73267e07ae3df15aa6ec951b0f65d69db94139fb9c76ac6ca00db0893dc880b8fe81e8e1f900787f90596aa97dd72acdcb00ef67438046e38096398c53349fb95a6919efac8d03d14c41b183205d5568cf86f7e36ad2ae17ebdd3abe6ddb00ab775047d3cc8807c21cff016025ff9f145ea3b2352e975cddd8bae00f08a0c123ec9cf2f31ac75f45d30ffcf9b5d65d7e7e338ff5f400e80b71ba45eda3f60be08d71c484c8268b26ebeef92ffb3baeccbbdbe0355bd5fab1220f29c08eaa40c96a8aeb53fcdf4ac84585dc372e95eb1e6060ab8c6730b4ec04d05690966679167e79862304a8fb717f4d05432a9ab6082f7d35e8646cdb3eb69a31bba7868870b7871ed44826d4fe400f1c5c43537f80173a49a1088f8abdbf171f16e41368d3dfbb8c7b1cce0a85232440e33dbae5f61fd7ddd17ebaafef2e5e06617880c0054e000b2030fbb5ba4a02073aca13eab276c1e874c376e2a2b76dc7d4923d6905b65245297165d41a55e95c30e623c0b2d8815b5c9986bcd801706f6b9261fa9b0d26c1ccdb8e283639600a9d283cc8e4bbc4b9564ef7df332bc4f9905797a26e05e5cd7af38fd655260c32ff83a26ef1746433d23dc61259ec835af1322767f06703f2e6548d858321e4ac4939f8c2c77bba97c91677b3607b231d0c774fcaeb9394dfdf96c9a899341421c90f4e14758c8a9a048d125aff20669fed7b0d474bd44caf74b52a336a671c493db06c7c92a8c1f379fbe2a8382bfac44d244fec0e84cc0d61a7c30798093b78c538158017f884aa3347092aa17ea04a0e3151fc40fa48543b01e00b85d8a71f6aefba7a299a49827fbe147f9ade3a0f052cec21a0b5de196599f4fda1f050e6633fbf267be3c63d997e12c00f9c84e322d22c6b94a226a31dff4823267a748cc81eb9ed25589685e524f629c5c69ea8a3fde99e60f68bc92739b5c6e76c6dc1adb3bbd7ecab4897e753241a8098f1c2b97dba8bdb0adca96f9ea5e608509c4d44dfcb60eb73f40254aa577c1bcec7eb270f9d07b4e5dde04b9cfb41df49120dac2efeddf431725c44bcabc887a98bc5d5bfb7e873e442bb1dee089a00b955970bcec608d2851637fa5d0c11c88b5727c11fd2becd04cbd493c666b77eef24949390b5df72ce0fadc30eccd3027f67a2363fecbd4285becabbba57688e361fc1822ab6d1046d67ec5c44ee785a1eeac309296fcfe39b33c275e7e502372bea67d7e4b8899a573198e0194ff835b19bdfc91ed42687dff87fcce6997338d735b1c019194e72f9efc8aa4a2141f45371790d2a6a4c9b5b9dfddd693f556cf28ebf758ea235268d712fd9a9f1ee0e7efbc51f7ea49e15ff3cc3a138c00ddd0559e6c9d93f649937516b4d630667a2fdc345c1bb1c1d218e5bb96f924271bfc2c359e6089f136c68330eec2d46c3afdea2ef18aaf9c35f5b12281635f88c8a6ba24b62dba60682027f826b3117365b3ac7b37da680a707a9f955f98fa417fdb1589e36a471c4b7c7f66f9cf73a619885618466a4db06782bacf095a564ee030959ea4a0c4e18f861e0a61c657c2afa8ee3a97b98e67bcca8bff16d76769cfddc16d37d163f74691e65e79b790ba4d3624cd1f3afa601b50cd72a52067077d31a24145638b9269ffae4ff9564d2388d3f63f917a95d3407850b3cc464737060a4bc6bd79937ce2498fc21a0f56963bcc95b34e578621b9246604a6eab683367bde78ecb4edb56aff1e3f11b71cc3d5bc75279babf9bf726c96ee469eb290793d075066e2142fe4d03c09f9e4da606077404f91b0382baebe1513a7c9e78fbdc9fe4d53bdd50ba2e3d8e2ad7039e61d23928247b29ed788c7fbbbf2748197c4dd173c5576c6443d630ba44f702429924ff2751666fe2d34e266eb3234fe3e9714c66aa2bfc16cd54bf02b827a6f16e41a67bb87c8a87ffe9233faaf8c79274eb1ba74fe85fbeb8c2619975e415e60df508bfe7e8c8f4cc26e34f0367aa1070505f414fd9897f828378f7993f380e0f7b2d672dd2fc5df4cf200607f63d5a4b0caf05a001a9932a53b00a8370802fa03830a95e4ce2578f304746103c5d9b605f0d6bc60793165b214dc65e507c31dee160af1f207ca541d98cc67776368152f50ca6fb84ab326283d00f220a93e7c931e615feff41bdc56f73a73be4ee12531407f25887fbe693b0a4b6aab279ee25858e139838c1ab0404cd9407597d45c833aefaa2771760946c16f1e3879a9b4f29dd8c95d8e713f11a3a49406bbd7d6d828e0bfc98bd62cf1d1bf8e36c4725fe19941dc9937e89e32f0cdba3b8946c7da54063260ed9df879c827c92a529796c412ab0c889999c50729d28f042e0112978e7811e2a5c42fffec9c68668d577d89fb78229fdd5d97238cb33259b33c700f8ca52a25209ec3e10c742618e219105eb9bb6735cf6f1fddbab1bfd0c8b05c6e9484ce5599a819779a6b81c386730a87e39ff12023df3427bb02522f80456bb8e2c7bb6d948b8810d8a80acba3a34ccfeff4e31bd1c2671b0e555abbe6b498deb0a0d7d07ea096fef8ce84e4986eb8b7004141642c85faae28193616c2fe7b76b9de48db86600b8981c5f6ef747df083382c2f47aef577cb4346547b7f43ac0f41a7372332f2f3234289daea64c95c769769895c7340c38596cc9f5e31bdb5526d9bb24917b5ce6d354c4186632a9d6299e0c8618b5a2fe36eec5e37f2801fd6d84e7ca462385a1ac58f486fc1c9d1832ebbbadc41a3fd0f82503eccf798388548c348210cf69f4dabcb6f3e70c332acdf7a5b54a022e98a81fadca535bc7440f4a6e2aa361d68e3cfc733f679cfb2120f07f63cdadf67f5bf748a71d19d664e555f76a3c7e4614c97555f232439625ab73c0d8feab4f534fa91354acc90dccfa886b3e71e3454a0ad183c0a486c219122dad0c7488d17c88f88f53d65ceab9834eaf8166a982539b69b8bb82bf0c3b30e9a2e61ca91202752277e207319a35691a4f347bf1d6fc56190a3e43cb59e447c41e2aaa2054d3fffcff323a42789e3f6218a4c4162bd1e574121b69294acb375c5da3b70b8b010bd8a629ae64054ed83d63802813bc8af9782146efca9565d7d32ef466cce972964afc1b6e1190708292ed48ba12085844f6b3a9d5a31b0b2641271378b5e5d216568db0e19c735932d15ddf7ec63e91a59de4e3797dcc628ddbbffa20af29563e64e78313e4f2656c6f57ac414730df464ff451e603e5bd2d2b8bc6ba62eb3b2e1e710f20faf2a0c232f4f4c2053dafdb14bd6e4fac6b212e81d23f510a4755e8dab7e8f543303a1494be1ed505b366f28eb0a93c476f02605e0ca082c928a91057112c11d02a602c7a37623cbd4d599da03434f1884fba8701b9323c642f0dcf983e39b5f682e014eca3ab06d1734aeb415a714dca15188dd88b473b5b06c985cc481f4e96ffc4d54f1774d75ffa9a7d58fa7bc53d5bc7fc4e465a9d8e105900ad074a46ad711521a3d4d5bf278acea9a887ef789d962b17d91dc082783d0314f67e4952dc5e0cd507af0d98cc92ad91f6e2d1b14b395c70f22494713ae3cb38ed9e6028e859bb379fddde512295d8220cd7d16968dc3d7db1e81f6575a7a63988839cca6ad3857f1198b2054c4a775f756d2c3fd60b922ab2b34fd3065cb487ca5b0f19a0ec198a45fb2b4b9efaf8dc82a260a741c148f22acb2aa7b6ef0909eed5f7292bdba20195ed81014e2582511b88c7c30b7a0334216555d712149fa73c7cf034a6363c3fe1e18687ccc22fa64160ea2165933a4a7a09033a2a4f71945ac18e70aa68be25493f37e19d9e2adbda64a9b3db6314c47461ef63c69d024d5d1c9bf5ed32a350ef6787b722bd483e131a1eee097f49508e45fc519b2c0780bc95f0caab77379de21097aeab9e2cfc5720f47fca642370e17de94b0830632b1ad3a28c98c76776a019d3708d0a6cf276777330385716cc014e04894fdf4c0b52cee7d30bda21a1dd22d4756847447b57975ad120205f30ccc1900a5a0b9b439e46428b168c2c05350ee1c4061e7c4de9284aaca9b0e2ad5f19fa3314c26ee93a79b560a91645ed7a6774a2af802865dcd6eb6ed4621f96ede841c6434803764c3bcd27cbf92ff7ea5b76d77a40246c274cd83f37d7f3ed70481a387e1fd6deb790004dea14dcb062f3f5975943ea801e12887f364a76a8a0ece689b2c1d589ad60fbf6b1e3105bcf3b111cbfa39f6b01af73e4cc51f2162631e79202452c805423938ff5c1685e26ba5e0882f6d9e1eff0c465a5f571f3a181a20dcaacb49078dfdb9bc9aaf63d25d46c9cc683c352e40d1e19a929bccdc4a6b5125d2a7929477d57756cbdc1e9a3e72655b5029aede61c395a29f566ee33bbe66b0217b04c2597e4cd0218b436abbbe6d024238d08e4cecda778d6e3e50a5754689d3bb4aa8668f1bfa0a101dcec9edbeb730c6dbc1e83264ab96b6ee75f7ca748c876f72e3bb75f053c879497bde09092dd653237a79515b4d856563482a13e969ccc9d37a8627d7227fa78a7f8a17939bf92574aede9e7658a80dae2439228f95b2e3e2d26e29583da6b80d3cdd3d23f025729a2322dcea557d531583ba9112dc0e061bda12a9dc07b420d6f7d9ed44e0f2eb8eef8fe34a96180e4ff7a3d51a365a2cdf8add5a7569f0e01a2398c1413d3bce43134b0a43db1dc09133ca6b7b4813b7bbe553aee80265f375a0e8b0219bc710255e9c52199b9de6547aa4da874aea2d5ed1552e3cd13102ec625861fd9c0d25c9b7908440c582613b4a061d8b19611b0b25eb7f1623a2584ee9f5a64fb9165047756f7edfdefb841b41844546f918e694e2c46b278f30801e370da56434239909ede42d6f5b61276759949c9f9ba347ac972de480de83d49d55b2c10749f41da528f1750508b263a956a5d3d3b35417881bd9044a97dbca8c956d3719e38f34a9767082a2eb3a299381ee90cb65485f76c76848bf3f47ba156fb9f9f2f0c869f163b54f84583fc84e74c1df5f0e5c708afaf324d24dc20ab3f8d0516f30df92f869c4a4100e318abba7540d5ca3bac2424cca56c053f0a5837810712f9462fa8ae765a5139f4005d0b460f318aabff72a651fd6b4700946804265eac12461b7703386c04fc3e4904925df87d8d7e35e9bebd192d8fc1c04854d81f9b94d4c3d2196e4498f85067fb1f97fe7030262991fbefd912909e6041cb7def18c7e8ae85aece40418ccee42cb1f1843327d60f1ff419316584a2a67771480e89334cbd1a6b33fafe419f10d55dc34c597003f2ba75043be6a725f83aed41ca91c8d709fe780145f4201a76a54bda0cbd22030350e89a3116da16a6a6ea7b763eee767206d057dd10216c39bc1a51dba4d007b23dbf2cd6325c8ad3aa82d6cae6a5287806447dc143273f500998b85400cc27b350c27c262e0e38f0a50033e5e4a9cc95587d6098fb1d2bcc104df3f1f5e8989c8dae1a48540532045e742987591e2bad48df05036d30f35ef3aee01c10d983dbeaa944ce5f3406a141ed59d5fcb1561397129c48323a965dcabd89127ee444b01ecc83aef5a2466dd2fdc4b59a5ca3abe50477f42bd881bb9abef562a7761f9eab54c04f6af33eeef4bf240d158b0488c4c19376ddad59c4c01e8d59bb022824539e7a00c67a8ebc97a3aa9f582c3ddfede0fb47db07f297a6340f0a8e9651bf416fd3ccabfc12f69ffa81ab0cb04817cb566ea7c6f2e98109d278cc6552492bbc8b6aa67b885e9f38694f970f7504cc35091f20a97ec324b8e912aa3af3d03701c0137826aac74c548dd55a05c75323a0f950efd6d29717d2ee7414725c5da0660791248fee222cd0b0c02dfb61597e0ee9e51b809586e01c1390c60564554f1b9d0f1ac86a74fa59834d0ccbf386a0cc6cf644fd97b4e795990e53a5cca75f7a86dff1ef33b8deb8be9290a7da49805921d3ad50db89d1a7b3ad1c7ae03e2ce9642f69993c144cd8e5965c9ada5c5360098dcb008d84bfb33e10002b73616e34cfc076ec826d8cc02879b42f6fc94e501723797dbb816627ad5c48f7e352794feda0cf77fc1c65e4dd6e4010efa061c09d56e7ed6e8e114676b50a14abbad14cf2a664127b9199d42c543e5ef4692b9ff5821da9f9b6891bc74880d07a0953d504ea3b5d27fcf6d062d1ecd58f495a60458a4061ee440dfb8713b34cb5065eb18392851c69c52420a5b0615ec80c0ad1e54b7297504273aae6a4c434b2b83ebca82f80870862959315e11292a5a8414c99d6b1bdbc7ae66ac0468a39ac90342357159307b1580b2883b6ff98b4e0caf6f8816f94e94a0390493629de113ee80cc56670866aba0ff70c43057b0921eaaa3a313cd090d8862b60ac688c16f2731051c1844eaa168335b6e884fbe093f1ddc0c707b714d750aa4909a91523a8cd9504a2e3a20c0bbffb753ba4212cbd3c3e0e0ebdb6a6be818a149e114c5fbaa9831ed830d80196a00c4be9ab8d4e2f3c75266288936cfd0b27fe1adfcf500f4d2cd736165e80b765598297a57f28ae75e54480c96d3b4a39206f9f900f993909c28697b553601a150d33fd921d36425483618e00d07b953ca7a5eec7b9e0cbf187b6be811a159d0d30bf94e3e03f209d3066dabc90a71c92ab37bf6c8b1726ebf32adaa58b8a9ec1ae9d3b0d5df3a504f2df55f72c2c4c7d24405b55f81430a339b74e40e4ba5adb599ac60b1934a785ddd2b7a8140896571ad23c2038464c8f087d4d3c81902a14b282695f3027ca72add8388c5d21b8257243d666eee44ddb1b36afb76c05361bbba9087ec58f3a9f5a993fe467d2b8fd674a66cf05264c09a52ed99342145c9615b3b6e2eb85a6e2ef96fbff37f0286fe2189325627e8d9338f4805d089edf52626d7be864964406fbd39ac2ba5e3125592a7a338d4a5808e9b3961de474701222ed554ad18bea31b4286b9d2f2f2a19e34f0f71ce6136cfa5849f05032660014660e830cd012dc1445ffe19ff4c158de1216496446ded1793c296578baebe6c4d2e07faf117cb0a52250589914f2da356311186b53bb6bc2a36f2e43cdbec8114489f9e1e128f0eaeed7b59a531fd45e91f423b7a26e3960ce64cdff14e48898f806366b1ffb6a00468e6095034103464d86b589ff2bca59d92467f245fdfc69b034df08ef5f56445abd9f50cbdc19e80f0e55c6dcfc6d19ac5bb598e5183ec69d7767f5666599eb295c1ac3bdf2e735d86f094955746592928aad4b2ef59cf815107248a3cb00d62141aaae8f9c3a84322e25f1802030ad5114f6b4c7f20ae4d70e1ca57b2725e2ea060e5a49fca82bad1a209e6508bbfe56607cd1ea35ceba58d02c45c036ab4ae8e5710400b398d01e149323e741ce94282775c1cbe664cd0cf78bc3d3e3038041707c220e96dc40f08e2196ce00e3a9c734bb965461c169cfc91795d7063c9ad56944c24484b530c129166c027bda4edbdf7de7b4b29a59401be0976083808ac9353cd9e9740ed11037f37087ed23e38a1f688859f341d4faa3d62e2ef1663e2276d76729eb1573e727cdea8f6b0f7d159a07a778e3c69201557b7b97570cbac0320ffb02ecb3f9975200bde9083fcf31d1dd6330e1c30182c76e3062c0683e1c071a29a33396e1c7ae7ac4c26dd9df31da24a61e10db7cf5d2e3dabcc759ac970bb7c5cb7d9e7f52a16b9ceba158b62076db893797ec5a29d83532c729da8c6ec9c7356ba52d9f890509c89e26c0bc5ccfa4c0c75e48c0306c3913fc5a29c43aa830bc3ccba8ed0d20c7e9073ce3860301c19fc3399e40330b3fe41fef9160efe65d669fe60b00ff67ddf7798e5e0cbac73907f7e7078457b9bb778f0d3e79c33ec200c070c8623e74c2679107a99750fb2d65cb805688bec9e58c4fd070cc5a22d003828166dffb1c71d2091615907aa1067d63bc0f63f99836fc9b1bacdb1caace7c85ae726a918ece02a1c6b0f1c27ed66c57cceb3a393734a4feb653528ca681f168002a954e32880cdadb3f278904e4ac18f7aad95ea8626996c1bda27cf99e78c757bbef29c2e9d3d653cf94746ea107aed613faeb85015ce5d69f5e34f0d975e6350e0ac330093a513b0b4e795bccc4f9a5417327016c648bffa9cb3e452250a971780a85d239aea140e58a26d57e2eec46927416c192597a62d658ce145470f863e51b1b0ac3d4b2c2eb858cec04e9b0cdc0e9d6a082f54e28b022c431688aa3d4b1cc850b5b09a325e61853eb488c81197d031b353f278d153e61003570e3678814eb0a8180d3e586cf070830b8e4d887360f297847e224727c50f7705076b3ad8ba0000f8464606101a1ee810e703271d51661b0c415c0d3bbe1c84b804a0fb22002ecc00c6f0d89e00c14c8f2a7c64f183734300170c6d6200c16414603bc30059d608c1298814224e866d032dcb01b61f10e079293242842d09214c8c5066081509e0b020c28502362f16805fc0806d8c226518d9c106475c20817bd28028231a1cb0617980c743095d906c4440e00813c444800909909980190aa0a9c01516d0e2026d30f045065ea0013cc606c8e0001a1de021ce094e1e8842c2705443920d870ff48002e60202292525588204d298149e4832130215a8508a60e362052f580869ee61043d8841821ed4e8818cae5645ca55f2a20564e28416319b3e5c4ac3e446dcb3d402a7052d3770e173d2c483a1e9b3c1c9d3f224e5c40828259e744f4091224a29650553c0a062c60b3eac11e504860f86186e90c1d352456a06303424514315ae89ce4a1457bc2a6cd8e2062fb0747b963f8081c30f64e4f0031a3a401967a9c5cd9ee50f37e8b2dc273b6c5378d8828593a187eb458b962a5f36a53ed8227eb8466ca94870b967800869a64fc6596e11b3c970d72d68367d1844b9e58a2e21cd5bd4d02e34080a04e542b7d01fa80f540bed81f24077a059a80e34078a03c5426fa036d02bd40aad81d24067a055a80c34060a038da22f502a740a9542a35028f40985a24ee81375a24db4097581b64099969828d5483002165610810a21484a0169891210a0f0812447a4074ee8000736a0810c60e00216a800052620810898000124253cc001460d20e18891220c588002882460c80842442842c001689b284808062800902102fcf0d103041e03104000847604cd747ce001908c7ae1e1059c75f686da4a3fc51e33d35d457a2a7a818728b168993f188b0ba071e88efa5b430a640b412eecb20a75c80debcc8bf16e3224aa219e43ddab8cdcce11118990606fc799c443ee3eda6e0f762156a1ee6e88bdd4aa30d64becee463bd61280d4623dafee7acc5927903a91e81eaaefe69c75d634f0efbd07f1ad9a5cdad75e9188266dfd148be6d0b5b4d2004e2a83fd5662a9b2377c70ac3db66bbf7dd364bd1e527f74496ae935d3e6c7062a6e6c90e2829b2b6b5ca141b7512d464bc616639581b5026705576220b45d79c10ee32fe3eaf1c5f89663b86779858b2cae5841668a1d5dae33ee4721acd4cba8ac041ab88ce5952c54289c999a2b51b212ae4071569e5869e25aa8426f996ddb368ee3b819a2700933581967e08267909a82c50c59f00c3e34c1a2099e5d51420d6f3a2c35c0296ba001951916698466694a19d29431b86056451a5f182a2aba1b64f8029732bc6046c5ce9e659518ca2a4d68b629a680e14dd40da228226c6226862e3786219a988941871007331886336270010733314811c312294881d099281bb80045d40c9e405146a9894243e687bc67f608c65946899965d4112fa0a1d2c5940b9a28f1829b5bbef0c54c89d59e651497282d4d4b44e5f0b4448e3dcb176cb0c73dcb17c0a863c488f91123342ba2a4c2460b9b4549c58a8dca1927a0d4134ebe259c7861ba22382fdb0f984a96a74bc586272a559ea85079a2d2c2940e0d318a31a24c61c113a9295c40494db1028a14b76739e54c9d22834ba3a9062e70cad2a40627dc7456baf8c270b20418181833c41a6160d0c50c42d4104319058fb809d700d54df9d24319a58c32ca9319988e8627700aa6e012b426103540e9f095529e3c990921658d2dee594ea161fb01bc1dbdb7c746ea78fa88fb778bf591fd7634bf4ca4de9b9b7ea9a3f489d9974a67523321b66a032f332f5e84bc04f113a5890da0b0186f9cbc3650d438e902a58aa72e50cc38e952425162df3d4b284868f9d12234fb014a540950ac64114b285252663ef4f024ea095409e504ca05a129a2c062b6eb02ca666bc289a7c56dc249699b70c255819b70e2546736a431830e0a0264bc18a18316694c896206275f6ca5131840cd6ed061fcf62c9d14b1c96719597b964e76d8e438db611cf72c9dc0d093c6c66620b15a40fdbcaec92c90fdb474d746210b641f8b19e14ec2a43560d2b8739ac802d96fd6dace02cd1891796d2fa5df178469f12908f668fede7655f6c5fd7473e380f6a5145cf66bcfd2ccd49c7a0b1313a918fd3c08f3415f8c731fdd237a3c841eeb9008fdd535b479fbbcdeb269265228cc4b5da1d8fcdcb121b7f4a1ae22117a1bda9c635b6246dbe79ea209760d18555aa06d5a69b440f3320b546f81de8c732bb92cd06c02aa8fc58c6c2761d236dd8049dbbee96981e683da4c9a1bdce68ad0951fa1d1f5cbf0b57736c30c66348c76cff22906fff9f62c9f58507d289fb4944e47ccaadcfb11cc0ed33483a17c9262877b964f4ccc62289fc438cd6098c9308bfa89aa325b69207419bef85219b93d4ba72f9bfc65253871f9716ab2f19e65d31a9bbcd0396e0665e4b8321568a229a149cd78abbce9a87432b8b965d3174db328a3d093262eb4000b16512e3811830b61a4b890830d38f62c5b88b3557b962dc8c00550dc38712a5b58d33473ead9b36ce189eded59b6204605b3a69f0a52f108f7edf786650b58c0883922617ba76bdd9ca25d1dadeb1ed3bad7365a775288a375277f4cd188a3795bb6dbbc853b087ebab644fbdee51aed4e8d7cba0947eb6a4e4b340e09f77a63f2aa71c47326db66d26efc4ae90de396b137af88771dee1b99549f54ad97c9ba79e0ec8d966ec62ca60a764ece324a28c7594c0fd2a9a9da23e73c94a3534aee47b0d336932fede9efdc3724d6078d7a3da6c91e982647f1ae1ead8647df05e05b4d0ec1c7b714883dd59eb524ec1bf73a1ed9f37c251609373ee5a2c6bf34d9b3d2a2ee71e9284bef82e915be4a14427fe469f122c523eef44bdbbb7af48ada8b21ceb2c1106fa1b5cff67787d9de7e3c6108c1675d27733959759ab7d71efb23aac7bb73a86bbd63fd870d7f58efc7eed9e48fa8f0944671e948a1ed79da1bb577f26e6f0c0a8414dab85672bae101c27888814f4feb022e3dbe97c9a4290b747b7ac6118ef72d098f1d9e4cc2638f5f9d14da5c7832a9b2b26bcee83b5148f8a35193747b07cf8945c21dbe262d816ebd3cb6f79516f51d8302093599c2d2f60e7e78db1d0b1b1f9cb4623c04dc9cbe63d01d9b5e72d17341c20b12511b5c81734271c76f1229081e6311c4182b31d97e1f625dfb8e7f2f88cfc4fbbde045503f714a4cbe5fcdf43d7ca89b9eb8dfc7eca1ba8334226bb311fc2f7bb9c9aac39f342e370942c3bf796efa096b16ee2d65f2d11a46100e73da16f62ca90ebfa1b3377416c71c42c281e3c61c42ba71c3c61c42b261c3670e21f9f8c4e610522c069b434830d86b0e21bd5eae39abef9943483d3d3c73088967ce26d2ce8ece1c42d299b339849493d39a4348ad166b0e21b190c639ab5fcd21a4d54a358790542a710e218962388790c2212410fce610d2f7797308c9f3ba39abdfcecd21248edbe610d2b6e1398484e76c22dd399b48d6d6398454e7acbeced91c42a273ceeab7d85b3d972c135a2f1990f6501adbb40c23a53c224b50963065182c5041584a1388d977cff2082c6ae89a103b6dca54bb9174ed490b9f73257c235a6dfb146b58d76ad3027137028fb7df1b8ddffe4d7d17f5953252bde36e24fe1ee37f376a1d1ffcb273f0463adf3e6a1ae57d3c4b8fbab675bac6b9f97eefc11b71bf1775cd5e4aa5299a9a94d178d55bbac6b4bdf51d4dbfe0e7e85a4bd754ba268affde690bd4a48cba83e7748dbbea2c4db35820fb51ff7ea56baa4fd76a9f91dab4405246dd435d0375add3a3127b0bc46481acb54016ce54944bc8cd97f11d1a8e66d9e074a3cb0da74734347c995a4bb4764d5c3f46a130b2ae9b6166ad79c86d7aa2d56eba6bb69bc8d24cf72b3709427b95814dc50e956fc38656a25fda75b777b0c87b2b9374b7be4cae003ba973925a391232b7f7bbc57b393956ef02e77e8864f4c76ea4e32f5d938addc675bc6643d79ad0383429fc987efdfb0d55dfe1519de7b121aa87573d3c19d3aebb32a94335c904c9e8f51b37fa5f52484636fefaf5d44a2f5d7bdd86a66a5e474b82ec7c09dd54c78680e739781eddfaf89dbc84b28680d7d1b24dc5a2159c39be79e99abd05b24216c88e87d16c82a6af9b60e33cda8c9306804f1accd69e4e833df65786caf1d89b6c5091083d08c66207ab88a4c9462cf69812130e0efb8d14f05eda266042f2d2359fbf0ecbb5251aec20169134bdcea483c360afacc4c4e730cde4f31bbfa19b9e72fcf598911c87fa39ec93168bdd464c3fe5386c72f0bbab26b98b26e8e72cd1a658046faa49d53d4d2f8e5a05bb49d93eaa43b62dfe836cc402d97b909158207ba06c010b642fcb2758207b00acb0c90e72931507fa29c7f57f72932034a51c9a05ad270dcc643da23ac96eee5527596ac58a15fa653fe5c75eb7b0db5328ebe3c6ed6fafcae48d5cbbf1a77c92aad9b406fb537eedf5a77c1baf0cf5bacf6119ea751bbf91a15e875dcc47cf50af4f0bf43c693e36b2a7c9240bd32a6c9bfe95a5ae5e1264537dd47d4e6daac6fa88b9b946d0a8b0ed7ab049109acf6d1c6bf0b0dff807e5a39570fce1b43e9e9a04a1e1b88f37e79c739ae08c09ae6cf086b73e9458a0d1fed833688791a46194582ce46e4f937649907dfd84e3361e821aa9eefa271c879df44e52355b3fe1f8eb9f263d9d6477d52a6cbbd35b7068166e5c93dde8d22a6ce44403b393898d87607da48fb6c07eb7bc8ef1abbe2ad3066f9e531b9659989985d7412a1a79e94e24616a4fb1c8c84b57a65d7596782a2c0a905ee602b3eb7dd4d8f53f3a8ed8f561ddc048bab6cb045e76bd8fcc882abb5ec79d4dc0596957e3f515895dbf6202d273f182a67a7963a5a0ae141a2c55c6568513d309f1c6abf20d21a682437c117e613262d75fcca22ab3fa0267acc222224ccb4d0e1167e858d929228c104f11687a8a28c38505cccb8912c08ad8f58f95e04c47c4aebf4f09c2a835f4e9421b3f6a0f72b442c75a6537ca6c602465bb04f366d7eba8a51729bbe6791e26b8d689ea14774e547b6253f43951c5b1e553b1e8a7a27ef7916635f9b32b185234c1449b4cfbf5a0a89174fdbcdee6f8eb53467afb23fbf0a7f6f8d1640e9d446853fd7ad594295f8b5a8d4d93aaacca6ed8c011e28073239f60238c18cce7f3d1590255460fcb6ebcfdfa2b1bf5f0b83a179a9e3c02cf199d9c1d6e47ebe49ccc1a5b5b4b68e4a1125778a563d31f51067ee10d35fd7c3aceb3455e38e293f786db8eb3cb41555e8461776b10942028425a64e0ac207532762fb08b19651735e6ec83336fc410420f82b5522b82dd5593b2a2dae96b6fc539a76d0a620d4ae9bd58b62ad65a8bf1ec02a68b178a8f29bddd5c24e8e244e57400679d73d24fd102f1660d1067765dc2da0fcc998228c38c2f3679d7ec3341a02008306a04a125082c4148d9358816bc33d000c128206e00820b07486a054a0a65a24db5246befbdae15949cc0711cd759dbdd7bb9241aa8b2eedafbce765df729165924dd08f75a6befbd5cc6c70aa8b28a801f171299cc474703fbfa24f1193b34ac74a3eb32a11f96c88e60c422b11a489254abb4494a558d098261245dbbf688e1df8df127ad8393e3f6bb37009cf4d931ee7773dc274d567bc4badfdd019de4b163deeff6bc4f9a071fa1f6887dbffbfb3e691c042a9fd5b1988f5c958d7cf46bf57195c4354e98365a693289cbba88f6bc975e3af2d49c12a441900983cc1764d69464d4b852261921c818b15d5546a689edaa32b1c85a7b2f1932656243ec26992ccdaff1043ece4beea990797baa91867c46363ed212da66e38b30db10d96a4fd28d978cd9b0bda4c3be22cc36334c2e3d41c6cbd681e76da6db6791abc95a4c84f94d54afb8648526175a60fad1bcd54b6a9bb47bda1e829c18a3904481c1519b4e61242b6c1c45a144d1a8bb46a98413a5182e3c6e18bbde314a3258ca27a22e1c7a4b409b435caaf0eb36a702d44981602057870c757e18bd93a110581098f1de2704a8d36a08f5aeeff8e0b66d0fa8174f42bda897b6973fef987ba7667b79d6183cc1b4b977f4c87e567104a23d750a74177520f8caf6ae6c9f936fde41b3b9c788dc73bf9af41e3b5704e3e9ba91bcf79c80b3b95b27dcecf017f69b7b6ef34a2ce285178570dfb99777deca4721197e7c4e0cf6b95d1deb5d8ec1fefa3c7830c788848f3d7c4cc7605a84a5adf31891d7592cfbed3ef73cefafac7357d6794f2655f7ce938f5a9fdc773419de7b8efef169e523eff9cb3fc2107bfa7b9753a0db1efcee0782ffe07c48df93b60dbaf9bc10b87692eb66701ba3c3fb262ea03b772b825095ddcfa9ee0a38c1dfed6e9be5acb55673f4cecae3c7be97ead4dfe625d4356773da30f15d749b4353cabbb5dafbf42ac5f976d786f0dde25ff6203123f93d29f528f5b890e46e3f02026a8feef63aacb5a30b88917e7a5d9559fb201fc6b9ef9c33341c131a384ab0eec2a9156a0f4ad636f7a9497b255c469a5465f5e1deb24e95e12b49f2a1da5565415323793457034ea2f10b51982f15e3cd23d41e56d7eef3642b1079199520203c3d39d11059fb638739d4138dcb5569d78dae5fb68530bef435c90f5a65ceea83881875aaac5231922ed797129cf48571962880736febb69dfb100410cd213a87e8a6dc11d7e31289454b689ceb537be053fced604eeede7ab77db1f16344ba6fd779eb5b26bb93db5597ecbed3d444191b0c37b1a8f52e8ff7597d3b9d7346ce59158b6c45c0779a5c0a7f445184a5cdba4fed71bbdf1355f137c75acff9d4f94efe740e5e47c75a8f11112fd2a0e60c00d9dd9e2727b3deca222c6dd6988f56faaa7c24ea4d35a867956ddbc34dc7865c2626bbbb734328d3eee8c6224c279898c02e2fa17190964c384c4c3ba9b5bfdba4707f4a7687b424c84edab687e43d692b6281dd1dc792209b8a0958d2c16972bbdd0e7ae208447b4e1de417525184edf76ee19cd15f1e73ce48d52e1a3267f44ce8924eed986e410f0c2d78ee734f8fbcf7265dfc39758380b43a317e155e0cedb55693dcf157994719e3eade571f3fc5d57857f7aeaeaa0b37f86111b8d77349b8d6a515b55c942870a3ea3ad5491d7cef05bf5d95799431aa1e923faaab4ab27baaf4e78c3eecf2b418d3d1b600ac32da755dd7755cd775f73ecf3b97bd9fcd6df73c6fdbb67b9bee964cf0cb28fce08cc24fd0970dcacf4f10f7feb85c5d5c231365302106136b9a687ae1327166cb6a8f0e0926c4f8a8341b54c558c97ba918708a609c251352f62ca7b26c01ec598e19635b4003dd2d0aaa629c5546492a72b4e6a02de3fded829a18f161468c6a8f4b373ebd32268c7d5f05382b7ee7bd83733749adb5f66a926a926e22916f7376ee3bc7b845b4cb0a1861eb98b36a64e69187919c6fc8a9a6ebc6f122b993562a543fef18368b9a33f6f72c91107382375b68cff20447ec316bf6077b964ba0d92092da63e277dd3924f748a2ba3c7af7529d1e46fa2934d74c09d41e36ebd833828cab7707df913e36fe23d341f270ed1177e1a827d7a9ee6a65ad95816098b9306fdbc3fbb56dde0eebc422efa0e775a0d779777361b661ae9b2a1106d9a031e3fdc765cfda5a67fd06054f2541bd7d77c73a3cdd778e431d34c448ca36d639c639cf39fe8f90475fb14887874727e11dde8a444810274e1c347bdcc93af9ee9cf0adf0ac1c7ecc2178fb2ed424f80d1cab6cb4e1528bda34c0ef344f3a3339dd6cfab3a9fdbe8368d22c99710eea2f1f71ef4e43f2d3a4d0feeaf45d587d8c3e58b2fa31cfbcaa3a1eea805f69f23b47bac60dea2fcf4d392d748500ed838c18c77b9ff60ad9b9432b1689a08eeb6777dbb98fe3b88dd3dd9220e3eebafcd34d9ab78d9261789ac9ef902e0972ebbd7462d5f689d0ed4de669f267671f8ed3a40edde8ddbe713f8fef62c662d1d6d11fe4ea0ebf1fd60785a25142412818e7c711bad1e5e2b237c5bbe81673901023495baff5ad30d41d9875aa6cc35e4ee2dadb7de80873733ae9120912274e9ced537bdc5d6b5556c7aeca683767219971aca2111c0d1262acf73e394c8ff8d1d212850c3f5c497eb424e19b8483b4586a6d9b5b83b4586a2bbddba7a236b80255d9674feba3155ec79539906afbed765b47df6da758f2d177269b66d2ee0da476bb1dbb23d0de6f47e03f91ac7583a326936ad781dbbdf86a122fd536ba6d14cb26f1bb7c64cf64df7c91d07724adb249bc7145c8baadcef656e049efdba6491a3541bd6992d3db266ef6dda59bce57c786d0df22f4f74828140572bf697b49d6712764fb15b7eeb2f4d86d611e3539d708e93eb1c803c13cf7ddc07cafddc11db72ee67095bd1359ba21494aaa6412ed3e7d74c9ac43a15020780d39d71cdd6fa7dfde515c414a528dbbe3cf7b2b4f0b744cce351b7f03bb7fc7502810aac9ef9eb7e1cd6df7debbb94d53287407be4ce892aa8e2640b3c34d4f8fb388b1b241d8b34cc24d125fb66ccf3209a89dc4d4067778fa338758dc75f66c82b067013a0ea60ff0cbf5f54862309705c1d0f33c2fcc47dfec3c2f0c5fa75f62f751f8af087da7c9babf8727aa96ce2cacb53f569aee8fb4567bfa2824bd935693176f4b35262b6cbc052c30e24c9318b65c41850aa2a8ae6c19d326aa0d531a4be8a06123031b19b059019b2deebd3b76c0dacc0e1a54e082cb1316fc0046091a3a2a90ec1a356bdc4cb1a9114cdc7174d924467aed9a3454054e382976c884146c9a9cac0973860b35d600b1c11cf6bc55765411da820b7c31be343466b0823245b0f1024a67868d102d3653556ce0650d2d35f0618935d6e852c0e6068e0d0f52ba30b808830ba61d3e44c1268d931d336cb1c69a1748b005182da0d831650b279aec88d2dab3dcc2cc1650ec90b203ca0e2818632c05638ca1f0ec596a41841634dc90051a38642186942cd66481e60325c66ffa510b5b4ab1e73f5862ac65137bfe0331e5127b7e0e81dc86b76ddbb66ddbb66d4bb3831b1a7cd12587285eca8889aa200c4e64740146142d5aa03853c375bcb8811733dabc81c1541764ca266219258506db0a153da8c1e50657b0a07b028b3328166840b14135c801c6932e6caca81203369d1358783143100d9ac61a039d2d60d8d1640cf72cd350b1c3290d133b9ed29c200d1120ad61677de0d1de90ce3bbbe9cd29ce1f0f4ca0fa273218b6d0aef5549f80c6483fc27d9d3540cc780e11374e614f7b2bb52a72697f01ebd7b4b36799064b53cf9e651a2718c400c60e4e51bcf083161460d1c5c3020cd4fd7f8ebb26ac7c3c3ce1eeb550601175b1a8d2744518ab2b6c3083156058b105973328b5e18a356964a046898506563c61c598d91560e4809ced79f2677b9003888ceea827b3d783f4117f58a07a12ef793a8118a13bea4998b38a4fde15c8790653b7da24655a9190c3364281502cdd28f3e06eb25b6d72b4fa225183825bbac5943161a140e814dd515fdba841ad78a18939ab27690ebb0a608d917e09bd00d61845a05e689b393465b95aeb69166b7b90581f9f40f6554d0f8ea9c3fee620538725daf653db32794d6698f16346694596b20a2382c8b0e2cb0b3228abb0a18a2a33801304a78a261ce26af18558bdb43359497a7f846f871c71f7fee17f07bf4ce3eced1fdd2038456b6b772ea9ba6ad326d58da92882e0e7ed4f18aa549fafaa9cc71849a14d647d048931f250a9c2f0f334488cff541f736aae3e7e3eac218c9ff3a178f04eaad811f1e08d446da4a2713658c322234da29126279553a8418dc6ab1d0cc02ff6f6895590666fc74b58c80d0fce1ac2ea24f74d2cb27d92f6e2b7af32d4694b35b216c24ad76aab33991584fa5941507de299c32e5bf1f75ec81d4caa966e5cee5ec5a2ee134f1d23b22f96520565ec1558ef9e3cca18aba55badf5081fbfca2024c4a6eb84c04e889b29352bbaea446e953b0be4ddb31b05d7de7412d70ec573a73a488b91fcc1719947191dd771b6be8a4482bcb12a166b6419b13ebe356aaa5b44c273b1d811d77b1ee3deddf66425969411eb3a571a5f93325a5d47d75a5fbd9673d57b726c7b4f6cd3b123e3ed1fc07a8f566a29f5dc7ed2d46cf19c5256ea79ec48cec79bd0eac95259c9953b0be4fde6696f6cb2a302873dbb14c447815ca8d2b5255a4d8aae9668abe9810ef62cd164d968ca6c3176c47ebc093d67bdb33e4656cc84d65937caf99863d43a4bd728ad4524dc44234daaaf94d493747347ec5937a1e7741cb7d7723e6a23d6afaeb5ced25624121ad9f9dc463cd7e9b119dfb36e643f3e288b31545a29f55ce52adaa4c0abb292ea3b42c4779abc9be3b873bb23855c737a625e42396f87ef9680d968751dae735920bc6d22110fae08e20a9ee7c87890314e1a8f96aadfd14a5246aaafae94f3d675b492eaab9332234d4e393ad6b5748c7352e9d8f758a78d8c629f3ec2fa65fdc61e30de7ebc655d95c757d15b8dbac611d9952ae9824b5e650d31c5384193b7674945952e69446931861a4c6a58d03101a6c9146fa6704393b2076b56aa9f349b4948816eac5370c0a435717772128456cf9da8727702c598fdf6bd1a71ffea3d13ea3d5da3b4dad43d8c1df13414a5c58e78ba064569debb1b7d6288892cd07d988dcc32e211a11ad4867b585f754d2a76a49ebb513da76b52f635ababeef4b440429b6bca75db0dd27a410c8149030f52bc55cede493b9513fd6afd1ac0e518be9320340feb23f49e56a29fb42526daa7d8804933526feb9644c5720488c65a29a6aa684489c93df7cdbaf0d138cd74bfbd8e31fa983edaa69598e0dfd18ed955335d3d2d90c5126693e3cd6ca9519aa7c4a4bbf70028136a5094d6bd09a57577a13b372f53a7a5ee39cd8426556b914d934b8e92e0578d023ebd127c7a229b56825f7f330a98da59af6a098d8302be1a85fa7a2676c9d6aa86d01938e0f77df105f7867b33a788b271e61451db942cdb942c47490cd034b5efed99333a4abe8d82019aa6b635f3e628c9ed42aa33b366571a7669146e17524c5b8893c14c83022e87a926db9ea51456ca28b49465c204855146b1840b416bca3371ce9c31069c3f9df5517f4fba40fa39c1b97acd3529a31004b591ea56d7a858f538f220e3fed34d55d7a48c787acef3daceeb29fdbd11fdbda7f7b491eb56d7ec5dda8805e24e754dc802713cba46a46bb740dc7534f855a86b52de6be1bf7bda687515f7d5e9a90e754d8905aaba46413d2d10ada7f5352923faaa6b4c54d72cf0da78d6d428c4faa44d375ceb9346395ab8cac9352923f02d5da334f0abb3b252f8ef635692320a35cd6295c2f368b3d29a7eb1403cc818b5524d8a8449c3df3e699f56c237d2544f53c0250553fb0fa0344c9b3fdc6ac9560e88a02dd86cb185150f82f7de7befbda0787aef1559e238de0b8aa2a8fa15c14f111441711445d678a20a8ee3388ee338b258add63bebe3f5d6c156265da35095b56eebf82f5d65e32852718e5a3cce2c16164f1031c6d8a51297c2f054960f2af1b67e652bacc9d541bccae291ead4b5319e26c05658a54ae2529565b8ecfb9db28c15d74ed8c3b3dae8576519a8bd7a584271c6bedf61a9be8a81ab71c43ac6d23150c7b491cf61f778cca408ae34fec6fa8db10eae56ac1b815ad4e409fb7e458ae756392a955e81fa28fcea44555cfd86e1d5a46ba7876747b55aa956ab202edebc11bf3a61b36e3beb03f6f1e098b15ea9ce3acea1e8da57f599335bfaa56577257189b48442cd06bf7a3de7ad1c4d8e2f55a71abf164bcce40946ad7c04a65de3a793b8c4d545708a4531f1ac079cf4a8542cb7953d4d32a56844000080048315002028140c88c40181482c4dc32c647e14800c8b904c6244154a22418ee32088814806a1184388318600630840082169ca46001418201158a2d577ecb846043cf14c7c438620918a066b84dc99b810adb5b4fd522608dab7562a9c3434482543684e2ae184f7720053e0fa25bbb099c630e86ea9e253d24e22cf2a5520fb06205a3b3072bea38bdff101383d761ce609962d3c15cb319fbe29163cfd34a72b841244ec1567e48281e58e78dedd6962e25c31f5d174f9984e107ef44b6d9597dfa579430bb81e119fd5e5ca611fb0048bdd20c01136cd6520cd1d5d2f7680965d00fc0dfb2ea7be4641e0fde1537a128c00e5a801cf37a823355a3366cfdfa1373c326ca6440f74b9ecdb7a7fa4cff0adf64c808f8d8e820dea28595f6a786a5019bdef8f85b0dd0b91d8ad6f2c15b117104d2e9800f1e4f54331d4ee7fb973f5dc16bb608988c721082dcb6c09157725a4d5ada288a1613cfad6864e2b49e5e6d61884974da39bb45d670f293140fde30e14e353c9c228dc34b30adb9e86469172b7108f28f01d92d6ad27832cf6c4bd1e7f026f380318cfa1f1ea6857111a25e84f96a5eb799640d1836f89bda1c3b99c4b4a188072fd52e4225eb2eebb54dba0bb4e5771a738717151e011e3e7822503212650da0f504e75b55b6d0672bf9b9e38684aa5d5780923fc4ffda11e942e141755b0d4c3db57c73aff42cbf65bddf4d9ea6226e14a93d5401862af123730a1d581ab5ab4540e6fa0a46e2ba4cf3e1ae5568aef82369a1505e04df7939b6f3a3a9ddcf5bba07370867aabb44bf228e799f37739456c8c5472c455441d03e73192f4679c9155d22dbad5bdc11690208bc3a31a55c2672d0bda8157957f8dbb8e178c961c6e803e4b1fa72f35a52243dfba9e6142873785c9643631bd9acd931d0598658d2f025e1ae4e36e6c6bf4afe3e3c0b4654e3361da24632834b6c04764095429bbad47d91ab1e92b163266d15577fb439948d46f0e6b55dce9a6c32dcde79c6521aed209409fe2bcdefc98b4cbdbc5b23c4cdadc6d1c3b8faa7ba3e387c6b1bf7f1193d675f9479417aca3d35c84145139815c5943950309f8b5767069cf5a6c828a2cfe29f5dec48261ed07d416bbb18931b4a70c3b98aa20924623461b92225dc71330e809ec9e420a6cc45abab3d36c9055b31d17ce2e59a1ec4f3fef3fdcddfbe8e784dc7a03318a3bc16f6965cebcabadc0f45734ba6c0a7828239a31d0d5e29b2c83ca48cbdf03a5c1c0dcc573cb47f073baec4a8579203466d78788264cdb333e353061ef40b0976bcf502fc2a95a18cd877b695c574000df50501315e9905e2a0e0d0eea9d868795231005a79b337d7e4c7bfc50de8315579b2ef2cf93c9feea000cf22632f0a3bdd2f7e7db25ac11a9f2b9520e7561eb6794217968bd43d754cae53266c9ba7a84cbe3e823e2d4ef9df0afa1d6b7fc9dc719a6933bc89a3ea6075dd69131473f3a15c54f8a59726ac4cd86ab6833b4c77f4a57ddd59ccacd97f8e81f52dae0a6feb685684bd4f798dd99eb0b492d364b22304da8f2fd6a6f342a3ee8cf2ce8959f6078cc9615537dae85888671579d11596cd552a32ee40982e86110cdbbd534333e5b4b0d4dd821d83fdb99434342e0d6a4220a5d991cd6ed3f1bd32355552cf0455d48161ec5eca4394e0d91c76b41c0f5e86cc5cc808471854f70a0e83bbc80176579824aaa1c05a121d56bbc80abfd80d42829f246270492f334d6590c0213fc29e1630bb7ae9d2169dca6a4e62624281c0c7cf88ab8d0c95b4e12ddcf76474c59a4cd54918fd262a38a67a4ff30278d39a4cc396b0773f2845a70d8dc884a7a4e94ab1f1699e044458b466b8f7926b6762e5cd44647bc7977d9600407386f4764cdd8d3fc5a6141246a6c1916c75713d117a8088d5c0911d5589e13a9f905d9691728878f3716430500992e97c55ecf3aaf48426eadf3816bd7015f66d469fc836fa6dbc8bc9763f9b34929ee790370359082575303bedae2ef7100ea15307c9124af2b487f4c36988dce6dc4c1110ff6cae36a44dfab582f39bae77ed0bffda5875a43d70b39df6e266c190b6664732cd8a59dd5cf99af8d22c50550ded7e01e467aec7174e46f0eb511706ec5bbc1dda545b963ae2be154d4867522a3ff6c4bde7ef707051ab4777b96d2e3796cc529c578f57007023492e112defce4105e17836e5dd9faf2392c9e152b364d24f29196bb3eadb04e26c8b120eb458e02d5570e745c8989a860cf2bae2d7c527fcd78fffa2a0ae4a0cbcc3e5dd689b4b15e4395bc90a990fe72c06c49d8d57a74a838c16f50a422c023201350c8e2eac4c65ee0b6ea579a611859be6da66b3a8ebe99366857c2cac83d6303b83a1beb989b578e79c505a0896315688a114a406c372fd4a36d40ad32c24d29f665b90db211a8b7311484049be543d6035c27f10754b1bfe2ef3dcc57923dab2819ce3c2de0002208ccce3a89c81f3ee8d88645bc2c3bfaefddb1c286269d6aaba2966de2f305a97b90c64bb67ac0e0933cc4cdd861d27f8c54df2e4d775d8d5ab7e48b825ac5aed491819c0a74168230109b7220a1ce231a59dc2c77149997806544932a2f59d4eeaeb15bbb1d4f064949f863e4ee640babec0d969ed3c9f51a1721015759d104e9b6f83bdd3f2f4eb557ccb8e9ed8afa8e211a48defde0c42e55bc44ad102611e5104370eca4a2b28bd1216debc0a0bb659fe6a8d4b40f2813c9aea27cf7c006386f049395bd50c77a0a83fcafbfc79600953a9626dd3ddf0e30ae36e7827ce8aa93aec9a146e04ce289177cb2acd8d38aa80d2e7da2f6e6bc688c46ff53b5494260bc9ac10ae3b9ad4bbdc0c2ee156e737dbead3861741e58f12b03402c1bda38ac4b9ae58b312b468cf7e03e7a3d0769724fd7a24c2b6c37026158e80ff0fcb65a31cb1031f471dce4f2719bd4cb56773b013326b18cc40e1b982a374ecf29673c75364da7445a747e6e2bdf2428c8e7137bc5ee923c60526eaac1d7d89e77c983a9469a43299272fa4662f8786957720f988e1e49071002f79c4536547ab25f50cd969ff643cb502b64b0a7e70668926d1b86d0193416498134febbeb97d571d89e79fcaf044908a129f82e9b0e8911ec39bb08061abe4e224f8487ff0394b22f2f46de9619bcb45042bdcd3396b0be0d337dfd1433444775e6c17be52892cd9d65428db381c5879bbcd9699cc0f16190c1ecdf217d3761136ca413878bb2a874f2c5a5ede8c4c1940aaa66946eac4789163c02dfa94e3245aad9b124897c8a9098e444e66e1aa93990f1437d62b2d95c62fbaca8615e7c0ee5ac76887f2eb9865b3dd9b837adf70c69bc516efa27db985e04bfc04944d5f1d81d6b270ad73cd80b2a72c4f96c243ad7c57014fced287e3055560118a88f8c4f07c266528686030f458f58dad06d2e6e5040b411213eaccd3a820bf7d82fb109b8af28b7b86361445f0488940b6f7ca48fb0fe93c716b6c3eb253888830e42681674514552f80242fb5d08dd0752374e9eae6ac68387d7296c62ad4340909c900f0c25bc84b6fa74884b502787256d75a6a74d780ea423fe456ae079f0da730ab43b0970d0e55514f9acadb6596ce46841effc8a0494421cd83d84da3d50952ddb4e9f3dd2cd4f7d13def6a6960b3948dfad36577137c589cf2240a95f60928676ac26b6f03c1a0771839728595c2bcd42e0c4b0d205f38d120e534a69e4dc4df04eab0a3160a6b81b598f620b8313bd4c660ca0480f4a5313b02c324a169b45a5bbf506fbd36b269bccfca182c9edd56139f1bb574c15569ffee7097b54de6cd526308db0392cdb9dcbe6586a013d4d54b0d800c1143cda812d4014206724322e23089c2cbb3dae362557b0d5fbe0db21a4b8cd069a6bb435313c514b6cf1bc984758b9054a5b741be6a67da1e3f13d3b2657633b216297ee20db866a7ca34ad6c9a011286b180d5e89d450854d21e6e26f06008ed6b486bd635e50f6a71a04467871806996965195405b05586ca45337cc5245210597b0770d6c9fb087f0841c896650604d4a5be54db55b682d726e0045e34888c6cb3f44ed21b78e5b7583dfabcf1ebc46138f6b59a30f816ce2ec9288f7d129591f422e1264018696828b2e5498d2738403ed8f280b071c74dafeb0ab27a5bc22b70c1214301cd0abe059225c572581aebe581c7b71ecf2cc7481f0acbdcd3e8ca0f02c829f2a9312c5bbf4573783cdee6df8aa512db5e418c11b9840b975859a9ac3bb962c74f92abac648af97556b435159b6f83a4cd98651fcf45633b749aaefe26ad15ba565d3b922c927538524f2e0686e6be7e3879f6a88436a4b798bdebaf461c335563335d7ec4d4ef810b524d4c883cddd8c1656fe480be8f63bb03569ddddfaf0c2880dc1d17530d9a832a5bef9ff729aa5cbe59c75372846a79ba3102d47cba719215c25652ee3ac264fc1b496ae668d85cb7083d4ddd77c79280f4f5cbc19c5c1dd39b1f92d3acb8d83d865927e873aea288941e04f51946ccce313c62eee9cfc26130ee18bfcc5a02f4ea19eb3c0f2f4f740366abb1fc199d0a603c90fef13c3b3ede3d4422b714dbc150ca278f50a2f7132e0b6c3d68dabcb3af5e2bd93ddd8c013ccb24e3e36cb483ab1f5fad65ae8a24a135aeb4516cc29349de02b2334fc42e92ad9e106674ce3d4d0287d80293b2897382b954f8f040e67279e4c1f116b6b5b777b51e2a0bc80438dd2406c2da00cb04dbaa2c3e9ea9cb7315022fe5bb03025fc85e4b853e5fd49f7ae0f83f83addf427630dafd03c5ab391734f255510e166c3adec2325989a51810d715e031979de12e289ec574c04536aa56cac1a1bbab7de94b918738cf103b427c5751e95da33b2a760d41148c411674cf6a0542954a44307ebd4bdc65a653af4f80165e60df92850dec7a84588959b929f99fc9432ea48df5a593fce625d3e2ef433243ece4230cc3d015d578b41f183e8d07f1e999aa09e8beaf6827df7cd003e430b11a8a89b2097b84662b77b1a24840537f1906c183a34f7e997689597db11e924fbe94c22e443fafcdf2050040fad4d9d8ff043643a136fadb908f8edede1bd265b7cfbd621eb9676d934917726cbad248a53a33caa5bcbede8cc2901136bcd575940e08b076b0ce328b516f9d496a21ec416c93249275e8db92d267eddf462c26b36caff620ec9ca86b48fdb624a6fa65c2b86b59345be98ef171ddc0927b6c6e009c42d0a32b06721b798e82b8ae916435f93fd95bcee28a8b64cbf8602d11f48bb5bf78f10e331701629ec2117bf278912667d8c628fbde47f0ded3068d6a5c4c575e7b581e920ab8341bd7c0d6c424059a490321185c70336757bdfe1df9c4ff85f4e2d9cc6dcd82c54375667da9f20bac3464386d65da88e5a709231f7115be4eec5059ae911adb158b5084448aa5fe3426e4a2e492af50ab050a146e7b89ded71c1d5938fe3fef170b84c08ddf2648905bd214df6dd94398980d2c6ef7ca0e8f35a3b4d2c78dd6b95fa31a7d9fabb35f215dfadcdef395905b1389aa782d163e49626fcbb8b8187ad981b3800d331f2a384198b98d4c6e095db5e76c31e0949667014c98efdc882d184551cc1af8f608d2c18aab8063491f0aa27206ddc796aa0891218c583e67588164e6ab23f3c18160c143ff67638d85bdfca873ea2b7a95262648cdb8a3b2cf64c0db22fb8c848d455c6f575d2e9af63795b44b10eb773e293757b14c4ed552517f9baacfe780932df19c18251201ef67d5a56666e4fe7e06c3d622fcf2c6331fbbe5790353ad622191c3145436e43e8b33b8c45c09db93f80b77b23ec31f25f6148c466f41e4d270b04d4c0beba7f6a7788f79c9e7f1b4ebcb06634dbdb20ca283ca8a8596b28053e56bdd4723943562e542c2c88d73689e50b096d78c74f04390e3eb10f54097a91ec29928a9207902b73b037408594195ae0b9658f3cac217002e5ab71e5b448a32175e4cd164e355f11ccd26441964dbc3db7eccfb27bbb65679789a10ec109edbc60a2e8793c842d7b0c0a2219ec550c8d9d04381a431a41e1d4e5c7f9f9f192be4b2f188a223bf0f9f109396eaf57f0c55a5e063b34114cc9df22d8ae2ddb49e0c360079a10bcbe59624c96bb6202a50bf6428c4363d516ca8b33e750614ca58e1bb72717060220fa2d2cf3a2d4e72de9173a9eab61f8f464a1e56edc1e1d0280a2f7594edd1abe3c6ee2c8ed41938d4feb4d50574d49b1168e2bff7d74d76e790016faba103c30900278d58d5ec2327bd980dca03c7211585f13d78f06df3a7f54beac95a8fed34d49db5a080ef5d4242236ef2208d0fa9e731b29c10e500972d776aa751db54ed50a79e232b253bc4652656eb74386e08c5931e016adbb3d46d735d1172eeec6ce23b15a4de1c318861f5621cefd514f798118b52766f7245f93bf269f7a6eb501837f38134c470a4fc591c54d0fcaad7984f767351bafcf222cc1916d1dec2bfa0f188b2e62586309ee71268d6e4a2c8307f44485419bd5e3e36a4737425d4ffa2078ec0366bc60bfa4e0d5179b05de949651af3ff17f593a07442d2f407353f0dea9154f620deb7927b5854cacb40566bf67c592d5896012f2b8395ee4cee2f65a723ed0c776a90c2c24ca457be678511ec67cd7a19410112c472848eac9d020e5bf6c0d88b2606806722dafcb323325703efb9b44ce6045132c3a5cf93b355d1d4762dd8cfb590df85e192b8d675d8e54b99446e915ab480321634036277003cbe49fe2606f5366955787a23ab085a59306b1f3d3aff61b5ea80a2095ba25936e8b6822253b0155ae797262ebbbf49253568035cf625925c07087d8494088f983d8a3b5ce31749d8e42ea0a01b4892c48a6224a17904ca21798c4ccb9f12b3cc39b9d8f756701c0d66aba6c60043a012b81f6bfe850ed993211a5df900b6a8f473d762b778680b7a049ff91ab97d520cf4924d5b01d6bb42c4466d4d2c63725f83ade648f3bea9aab74f96b58812ba31876f8d054f32d07ceb63c4127955087ed1b53b5f6b4d753d274d1790d3795b994a860002f97f369ce6042a8c434111caf0ec191b8b171e505abe32883419ca136ed99d77e3429cab897d5b2a46f77dddcf15e81e4c292fb8130e094a1b13158b4d315fac1cb56784f8bfa8cc4bac21793adfeaf43e3b72f3b458d8d81066fd9d636d490f4b733ef160cadc8d890592727e5fa6a393977b0c57696511bcb776ebd7f976f322db442186ce6fdded6097697a686535d09cd98c0f6a95f6885d761fc7f1f105d377f85584261f750591f1f94249139367836d1387dd25ec235c1d700ecfe4fadf44e911faf1df6bc5e12eae2961050a609f99fa4c3f66239bb980a2a492b5a64eae164324b5d7761b49aea69c8adb37d832e7032d0a041d402201e718e29fbfe3eb633185995e1e848da250b7686500c132733b40c41605077dece7c5fcebf10c1b83976f9dc912cba624a08b64cecf20df211a3490bed2f093edde5fc6d7e4957d9c74f09dc88626a6136bc3cc55f607909a1e9c5af38d4d9ef16ed6d92e2600ee55d130872db418fcbbe23a8d37bd4bac0e688f2d669858b4f5c258a5af97b26e5440b13f7ce517a9eedb4d6ad897403979644596a1d306f02a277e1c9c76d89e9353c1d7045ab90ab6197a0647a433a6c21218795b57bd0d2427962d057c1b013c2b03772ed100242fe40818444c4398293ffd87422fa2c9f83548d9169e9e7b578569a05c89b245e22f59f4c55758e942fee57c42db33d874993d496263fdd146162674343e5852e65136b23b98a46ab0c7a907e3c8d175e76fadf384bc60993bdd784fe919f585f6d105c39b299b80f39cf4a4b44decc421371c1db23d7cc5ba70043f73b4501a47e6e43e9cdd6853c182e69d57b657a370a80ce30ec4d81dba877bf005bade09d84f4e4ce24c3d47d180b4a1551b4eadab3ca8c0bcd111501c5ef9ffceb915adf368f364c0f4abc48e0e32325eda3e139b904a2b01293a88c8bf8da8b50b2bfe4da64433c66b50a7ba47d56882b81ebb733b8a4aa5d16ae8de623485ada78d5c87b2e001c783b8b84cb4b0c2b8964e985f04e0218c4e8da8fd5576ad25735fe70371e065101982f061e9197b75a5fb87bcf7871479166c1eb420f96985aec42e9f56c01acb10d7c091a188caaa7758449fef068c420ab9fa7299618899fc4d3a0dcd00b8d7537f400839e8e28408d55f9a99fe6d0050a5de6dcba337544c108e2ae71e883e3aee780b812f88edddf472073c377c5452969952a537e5de6372da1c2689c18fe038a472ab5e742ad61c7d4606b30d98d7ed0843d7ad4690f489d901fc333a893a3e8fa676dfb51494a94665d6e2f3f213b8c04f516704a216dd47c6cf2162085dacf9c06376fe60f9e688d35e4c97c8d20386185cc48f556f1cd395c7f3a492cc2e136959a4f05cb0be84708ac56e1aba75129f1456d874b74f1788db8c8d1da72183a5b24519d0264eb7a8ea863d96b2603c76e592c25a71cc0d22b7d0c142d047a867e43d4fe21b39216dd2a2d4ca4c8cba9f5a6bffb1799bcfbe3e6e5e41dab005d285e6a744d39e0425250b4528244b80eda2ddc4d2686dcdf4120d9027b14bb70ea2e03c32a68d71cb586feb421fb806ff089b90a9eb5a809b96ad9aa8ece3166c7f415c2dad714fa14c6f344155b567ebfeba835b82bbb06578a17959ec13189e44807354fd6eac811dc674ac748c28b3b6b20eec3a97d42d948f8456814512f440eb16344d79c5dacc02f9e5859afe90e051e8cfaca03218c6695845968181991f5869fd9d57fe7ecd53f5cb2c7141dd5ce9d9646954306d07efea29fce502535de8953d5ed681b1872241eb7589ca5c79f0d42ceb08794c56b7ddb869f75fcc8a28245ce3198d4875b4739b02ad28384c65b8c2740e43914d591d6eb064ce622f95feb007592082472c02a5b44ac2da580f5a4ca73485803f4e814ce40b65b71586231de1829694535980c02ba85566b9e54bfc34a81277f0bbef96e4df60546382f8a1dad379549136133da853646684e36bd9993cb6692668313700ebdedd683a65babf356e9436bcc67e513221dcafe34a092d113bb55223644eca266129da8990c3140bae2af9f6a452e28b04298d988139225646683112c64be939c2f7e5335eb328e265744103723aa3f30c0c4e1e1c929a6c96290177cd4e06d2b44a671c0d8b45357cd1134d57d81acd39bd0ebf62f3765038bf621448d10b0bdbd858b1073cab64a6c1bf7c39ed10ec1ad36c946bb09c71a66fae7a9fb5a013aa2f45e4c18d3a3e76d9ca9c381eac3325ec67f2eaadff6c01ce2fc0ee08744a34d447fd0a52467a8382d2ec745f8bc75cc57ccf53196000aa4f6da565cd8faa726d0f6dee6264bbe46400bdfd2f6ec23f1972ecdafe860d76f397bd135d3a74349767d5d7155dba54c409c15e6e3c71212225a1b0f2039acbaf8883a7d6bc0e09903a2b2bf518ea391e897e4426c454dc570946fa7440bb3ccb62e9225eed0ba25b35508f137627d46114fe3afb1091d85f7e5c1a2d50ea3d4993aeaeee101d7289e74b8b40716756b27ae4c9aa5e3519d059e303878a9cf7054222af30afdbe743ad99f53058152c24586b06133f836c6e16c0cadcb9ef41fe50ad84f7829f216e08a0aa25a3828c992d92da3c72a1eac348f9a136935711fc83913dbef91e4121f4037749868819045d4c97e9c8df1b348e8ad724dc0f160f7e9ac50b7546b45fe6d72cd91b88b1180b80e7e87b261c17cb4c3fd172dbf078f71cf1cd60114342dae02810f2c390f9cb920451438b811c93b68fb0d8dc2878833930d27172ab53ed1f6176e1bb20513727b5ae557f3adc57edf1b7800238aa988699de524570809d18397ba7ce4b30944a91490846d550d0ae8a762cdce2df65f7915191bd2f275245026867150a18a51223585513afafcf13481921fb3f61dddc35b4e5454d6d11762f2306e4c7e0838f05542207a1254bba376137115c24bb7a4399ee50e2f7dcc668fb888ae82f37e2d594ed567387abdaf284d7ae0e4e1c56fd75d8f5b24eadd3a94f107b02f02d37c226f679348cf3d3c0d7a42491d21d3f98171e75e0c208a47ab4b4a32050c3317757a4cca29fd0d8c7c672e94eb02c85696f8d985d79c8b21f624c31febab3153aabe275b1729248435e6438b9f7a8e2e424d5754d69b44fc73afa6b1d23a7f19617c1b5a7130a902a267740a5f7e5da973969ca48afa4516e34ce2e1ad9b4f52a6ef197030bf27663a34d673fa6ced37fc82a044af5f214149c2dcf99b10834866512b16db42ea2f93ed44376f88b0844583791ca04057545e66cbad13d2384b5ee738c47546b64c9c765ac6cbb626e52401da95f315a172a284a8e4eca5aaab700d9fbe90101144068ca09940837a1a5ab9f3f494801776bf5a9750230c98be899d98b6d8587f7e5e2915a161287170c9689b7da15d99a01a9a3eee6422deaefdc265cd65504531114fd30d800225efd3311195d0975b448cbf4928931ff70f1fc1fbfffb33f8937e2c85c0a241868329d434182959e123b420dc5004580d120fcd3c8eda29a1e98ce03e780881d06124c559fe0ba64058d82ebb831732dca707e54ee41bedf0d2f02823877d015d998818a3a63cc845f6afc6c82023ed95d8b90925c6f7a18edd906da0a5cab62c85ae9c04c209225948b3c4bcc5c53ebbd872fb3bf281e5b00721f9dd2344f0c0b5a344f0916d02e4ed0ce40a38e6af79f156083b35e1116223d5eadc98ccc9b3816b88257e6187a0372709f26f9f2256fe744b7a49885e41b434e0264b441aa1a98f0e4d915d89856d1af9179298a7654851da3b642d9cc4131c9a52efba9e1683e772a793e4d2edea95b20eeb2883d212fa42b4795be85e1580f71ee2eff8458e3ff2a1b49fcc1a0b4da76e04ea6ca9b90507104d5bcbcf62318b2877c62c24cc11eda5c1dc9c1179412a4054b4f2028193a67096d2095256e9589791fbe1edd128ecb805beb82a37639c6b0c243b351f93b0fb0a892061eeaace2fc0256c84ef0032e3a8951d27d1550897b07a21f5e2ed31e671fa198c76d5bf12ce1ff6f109c79cc48f8015af107f77f2b862a9a3a12e48e4a6b4bcb74968be534688958bf6cbf2f694aba2c0e50068d2dbc9978acaf21e33c2534a48ed044735b1a57fb75a2abc68a317f0beb8e799e3aecccc5198f7b39cfc1f330c66583ffa7c3f66122d40c11cdbe53818a97856daa051e6e2b03d221f9fec210822bc8705fe26e20c0433a41ea1082d8dcd79e0adb1b623c78257aeeb6c79abdfa428b436ed940c64f0e40ce61e20dcf14895f1c0cdd80a7ee22a09d3fa91316219b1ac3725c7559b18873be6c730d65e7e2ac1c2ea6c228920531adcd85e66daa411384fc76f780ed06837b331607c8d0d1e18d6363e6e5075eeba2212e985b6af168ecd439794c183d38cfa6b4142e04a877886f80a578642a1a55ee1573d5ffd61d682ab2cc57dbfeb234915dcd13bd5ed6a2d50a8022a2eabfb118135086a627c27016b1ca17475ec31fab5c73188e67bd9c1b540984bd4a70e46a62917a4500f0b5d058c995c74d5ff2676f897d837e0cfc11fa8a408061feac5a8724dce74cc41c91eab62a48e2be25195d66a7f841ab135a56a5bc9e64ce161c3c156cd9bf707055bde6cb889c28afab349a93832303c08b6f82b7a7572d80a0c0efb6edf9123a3c5a9513b53b13eebc8bfa5da07b2a5a72f7391849e472820bae446d0808d5bbe4613d4d8b1c98adc4e6206d90840ee460170a9cd28ebf41cc126b54227adda5dd26adba8fd874dde316852a6f27d7441a2a3221fe46ebd5f780f21d77abe0916a086e4968c964efa937f2f2486e014c3094dfaf186054babc7864af938623ed3c0be3602dd20e37b11bf5ec588c1e1e82707d9bdc922baab90d8ba78c96e0e45556a40425d639e3e4550ef3c0698e541046c47b85917b521ebc18c8398516c0da2ad35f0ffda6fb9abaf0b8b6f5ff2636e5b5086537a6f512a2137c5b209bb94f7b092102067e52426d02d87622c3a86d81fdcaa9b571fae25c5f50aa7f2111859b9bb6b200f838fe36486b935eaf20611f27381665a97afa43920ce3013b62f0160dbb52e64dc9bd31675567b4c8b97419fbcc1bbd80e342847de462911897583dc57898ddf4fe3f9d2fe6fb75187b1021ac35c2c10e4dcb3a5cc2b15911e1812b921d79b3e9d1f39d5833c9de929d0d06ac6c9b71bb0caea6ba90cec9f5b3977d83fcd0412289c800703161cf40b7c89740f7fe1f03e226d97a30474f851d402a88cbad3e16de4079f22e0baef28003cd11a8a82b8f489fd9df0c9c713fbc348202e48f3e24bfd4e8094701159a97f35d378c4f926f535a11c251c7372b0bc6b527fded5c4f3de11d6a120bc44f0525858d99ee036aa3062ef140ab6355000098c37c14e167713f485c28ed1be2e35df98fccc84805f15b74c262beb35c9c148656086a540e2b027f491b6e08ba409b070ca0386940c1cace44f2b4e4a81e4d38b404506adc094402b6beb329c24e578844e0b0913398bfa3741f64e371abf3bc30b737e6cdf1da88eae298f859b5d5937d55cd680bda55811f9f24731b4997168de5e896c1dc83032712044ce90294a54a5d703d40ba73523021214e29292fe4217c53ef313c286c0861ad0b424b76606c183cbfb0af1cfe945b0d117e58ad30bde837887171dd11801ec082574f8331e29ca3bd752582cf4e6559644af3808580304f0c2fd5343b69c9807705fc98db600d52c9cee43af5de176495ac7ee9abb7886f31a1ec5ee60321c4096bd7fa513d82d4de573ad6d91fb1dc8a57ca4d24509d20997618879a585f0a06ee639ca66ffa8a78e5fabed09cfc2042f5ab71e2960df79936d1346a4a1631ce44993bcc6fb53bd2efd5770853b081dfa6944879d822abc8984a6bf1d8b4b190663a001dfbb4dbd2379ec5c21a0b1309713c0a63ce8e0db91079c46525d0f27434e3be2dd52b9dbac8d9dbc5a0dd613d5a3fbf8bd279679fb019ba519fc7704766bc44d997eb98efce58bf02a2481eb83ddd23259b14a9a92bcfa0af23e4739264df77238e2c25a0a26c3fb564b2a6aafdbb514917559c7376c07c62c5aa3da013b9a001b7f19b0db3e6d8fc0727cfcd26be6929f721f204e40dbddfa2ffa45797b08f870f6a3f0ca2738507d98be8a80c2ffe0b8a07cfd80dabbb48b9b9ff9b7eb8ef113973307e8e2c5a50f261097d54c0e435f6418148c733281cc31bfc9ea53484ead400d4462927cc812c5bf2aa664bee315ee6b409900cc7e71baa640079768b7aeed6f41f22efed0792493dba054f7e43088ee9332c1887af7d4e2f5edb9511294f70c4a1f4ff82e3b5fdd0bb3666d92fb93b65590c7b068cfa378dfe5e653f8be329e0f86d60775bbe4fb665c6a20be4d0c995f0e1fa060d1328802fb0846baaaa88fc736bc6d73986c94f9234ec521ddf2fd9fc32aa851b38adcb575cca8b61bede1e81bc531ef2f194972bd712e8a3b807bda5175799b2f9693261316525a824f99b7b0bb95483213ad8c153ed0da04c353399c26f23cf90aaa2c0da4db07c1272c4db047acb8e39636a9dc0d61e5d2bb3b4ce8321146e823a6692c3609fc0b23ad060024c7c8301d99a05f7c418fd2202aeb1724ad969fa5ac66761b1a4e0dc0bdca8d5133f2a5275c4f6fa6362f428a18149a8e551fd49a90263665d82c88363c201e451f79906f5047ecdd1409f959c288f19a5170cd15a8265ae0e43a8c27f52811da31f5a4292ae01acb9e990e333976597e3324061ae3c258069a8bc1696b102014b27dc607222621c7c2c37d3a0185c555903dfcd69c12cf7768ca6643a59887417ff0cd9bf90fe8d3cdf14ae074e56cec4eabc51e723c51a35008124d0a4234a4240003701b3ea2d9639847f2b468a3324650f5a537071f70f05c3ade0bb071beb12c6cb7ca73b24bf4d5e6f4910a8225e62eafe155730cede643cd362247d42b5a200e23f7b6fe46492f911b01f910c05456c0b210c0e697e4a4ad432094599e1268a7088cc5ba9a6ef48b039e418ae512d86e96e86e7e50608d3bda774984e2faa22e2703fb2d1991c2a48c4da7b80dc28d722d83c3369a57ebd8a96c5d88deb65e40812872d43cdbc2707d0f70c67063f06c878010e11b7bb58f863cf545b81818dcb14d6171edb7e4a2f48ebc81d9d1113d95ac78be7aa964b53b0630e680307c42bc2c1fefe16b8e0e748685d9719368dd1b7ae53774e14bc11cb22e6152ceff88ec343e56462344d79785e60bad0a081ff058caac14814e8596b977c7f924260b502170e58d0a3c387b50861e4c615735f69da69301e48159cc60fb238ee2b378e51c2537781bc32d492a0f57aed8e12f296e20da5a001e9094b0e312dec8d0bcba0a60a0efe6044ad3a94553266559d86c1a3a8e4b451fc065aa24995ae40c5ee381ed82a580d983d865d83b9997bdd02c570f0a9879d1158ec059c0010569831e78eb3c482a77f2c1c8fcf227f0d7128a0ac460fa1c634e4e1d84cea337ae915392ca23d252d320cb611511d0c9febd06795c54e744f2c00227959a67029f340f8e8bc51837478f2c4aa5503a03774e03926419530cc3c372fb8249a7a2e5370b22cef2110712c2ba151dd6279a91a8386cbb435bd9392f22c508304b79fc0ee10b5497b4a28594bb697f01c4caedb62fc3f3be271b1c092dab0a4b8a3d9ef890bae7284a0a562b115c5ae8d86bb508878059ab170ac245ac291c1406a79c1edc7d233d20298fba9837868a42848540f6ebd8a06b04a1e91b07f4900a619d8012c4d1145edad88f1c07d49214dc5b75b516aff81fb237d2849890b5b3a711bc7c661e9635d44cb550ea8b883289ae737bcdc871e76dce438d2c415080d4eb8841c9ea3d72ba0d5a493f81574e256bffa7ae6848ac0e75aa0e0248ff4a28af4b49396290600a29a8dc88f4f47ebe2cfe06a2e2049a02f4e45dc4bb1801dc005347cb83ee469e32bd301f2144978a5a9018a0b6494e7cb6bc4918437cc82fdbde1139989cf7856ffa11e400901b009f45d150d020f5e74c15a2fd1a4f830e650225c31604b74afe17d662a568173590ba710edf45edd4769a5f42a709901a0d9132789c77a19069ae3f5e19aa37ab47cb5ee3a3047a6c36059a88e5012621737dd004639218ebb134c45bcf09da211e6dd592972bf3f44894be9ce4607014e4536dfb343898762436cc712947905cd7e8e725151bad273cc7e134bb53544855bd903e19b4e200cfbe4527594d5733937b02e171e311b2e2cdb5bc0989af370095f9cd03221dd0bbbc9933ac862f377dda023ebbe4d1e940875814da93074d84437018cad2abc1a484a26a187d5c126c2fdfe460b46c89976b02857e086aea2e61fba5eed0e4c4c0f1e843524aad3723f5d55180701bb94b87229e9753fee6d56b03e8966697e69b605b941574ecf0a733941d86861c16cf935a2a514f88ef40a870088789e811a43462a81e5c85208021086f6ca5fda9837051ecf0a337cb68ea3c136762b654279c8c54e590041f968e371b802038e36d8c5df3570141671b5cfeadbaffd7fb269b428db56d31174064b119508b971e7917ce6fb5497195a05ce5ac8975bdc266732b280638fe83968a65c7e88e62e97e0a9a17fa728824ca35084c55d88da80d7872caa71d091774707b2a8d3e93a2292db0151f5734402f0008db4a08b7c2d8be43859c006a62f0e7fb6537f6ac8fc43d09b88f122d7c5432d06460b30c2001e340fbb4a0f323c02b8b412a447181a116844e1e24e11887971cfba6638ed6d1bb13062e85f8623e6f6936415eea6fd264d9a8e3551712ef325d48560567a96c6ffe962d00f5a059d7bf33da7fe2279eeebde8433b00ad5f810d7fb625e6b8ff205ecc732434406f51a5b86a62a33b3c35a974df0d0e128f47e5d49c0d2b856718bc9a30451d148c5f51e77425fd6a1c37b0d797cd6cc029a8ddedc020518f7c2c102a34a8b416dd85aa7bfeeea400ec3eb47405c0d611547925780f6a67cf6ec26464b70d8927a20d1078cdc01909d1be1d52a77707355b61ade20b1177861d8ccd01c435d3910b85906c78e6879c02288d5921ee560d7906f18223ac797160110207082dbf5ba5faa0203332d3eac7534ef46162a25742a440b13a882838b091e2ef1eaa0ec1c6286eb15f18c57b346dcb058d93a085ca04d7b24460d148395efeb08993bb5a67c800752d4f2067a00798f67453a921e5672101ac6f0b3dcc15e6cbd97f3dbfefbe76e5e2768d2272ce7782ed67188bb5158f769771b323e4fe838e566a508e3f8bdc84d1475b449da6266a665c098e195ed38c7d54408c954e59b78ae1c0f2528d779380f6a502cac503f89c02f5d1f46a8c56867e5526b0e604dbc1a2195c980d61831ff9831b8647e20130c5f3c900259e15d1da5695fc89aa2a0b1f3302c3adb87f068f868465af72d2c264a28dea23538cbfdacae66038d525c9e6f5b4b1dcae87ae93635308431adb0f887b8494c650b89f5b33d25ce5f5604dcd87cfc4448ec077ce6a152d0c954489c8be4909575b0052fd516c5305cdd9e01d9294878a4e01ed1cc185dcd43f318c79cbce8d09f47e57e1da7fb1741035de0dd0e2f4fe8dcc184da5b4977dcb84f94bf6e3c5c1ad2850e413d075c1f59b376cb0bf9c3ba1aad0fb4870945c70a4f376f67e24b04a640d0c4504fe5f13a292a32e54df1e396046768c641766cd5bff9ba508fec52e54ae1b05a46090004a69995b01d1a754d3c2e4a5b686f413b5a92e2b62130f37f1dff12d29e9861366a1d379a08bcafe3b7665457c56508e642bce2c202256dcf200cd7488f3348fddfd88529b0ab754fa9dc12a5b545355fbfbc65dd206635f5f44009a1a6a4bfb99b0d5990f33dd67207c2f14b220012cebec72616ae02f60420ca6a6b861e9613d4413977f618590e4f5849a82a09657ecb51477076b1826a6861c21755b9e25111417c13aff1d109f3611102f514487b92700d19115c9d0a24593dea658c53a061cdebcc2f93445565d83a1eb2d8fb8e5a300a98af15b30618237416ea8f41902f6f9b496d31e8d6c24cd8f5e9e305e0141dba80c835802e8fd4a86ce0517b2b90ead1103ddf455a75e664aa3c13046eb53eae20d1f0ec2171e531245e571c25da123dc77e1b06063bc2d890c870fb227839556505a418331498820de7f84d0a7eadfc689f63095fcd0a9b96d80e20a96e04d79f109f59daa0dc831b4f31667a84feca68d2c9bcd0ec2df902f0232e05dfd8f80668f0d8863105171586d83e878d3ae9e87562e2e0be0df4a7540bb1f62d22b28975dcb143384c9ffb7051a7377fa848816a1a6165216f299ac9f0eb3c13379a9a65a37c29a08f7b5ca60e703388040ab2e0f9f9edecac72f4450fa95024c13532a6b7679e1444e62fff8a9f4382d198aa1c0e4e7d0913fd01dcaaeacec8411fe98e9772d2690ea211e8733b68e596295fe0bd30851e067f3d5df139d10e04f95182923dbc7a04d6c17de6a5f6c18103cb4ba19d0a5262f85faba3e9aca6cb5fd9b4d3ffb29ab2b45afad0a6d0124b57629d0c0b1e2b71e859fe745e9254dc898a282beed28c409adc68705812561e10ff93b62177f3a71620b2aebec38c32ef21cc0a76591b5d1a22674d002a87203ea46e237119404ed6ce8ae1b39b961bb33f6eea42d498ace063f1a1f2bc32a123d865c1ba36d0234e472b9a5f7bc7d37d40cd10b08742de7d0923ddad89ea3a503b838009acc9561881044293aae5cbbd4a19190f23c064e97eed720669da61f7c0752ac2a2cc7886e6ab17453299409a888059330ee742b6322734b89791f550bb40bb6cd9998ad51508bf2f47e660dc2b3f922d45e58b98c0528ede4515d416b711c7f4a12d30532aab07aed285075441aca0b029060f93b363fdd34e704ff392c5d88ebbdd9c93f34a06842252d65b63b1ec5ae10c3b6d76c35bd25596fe83515d2423c979cb01c199d2c657ffa60acd52b6dbbb5b5cd6a560155c79efee534d6b158c0a41811e292878cb2f01e257b200730a05c943054e2ae1726ab063ea6780f3838782f41d8cae302566cfe669a47ee88af71320e551e06332a6bd73038e08f24f55b0a01425b1a2ef917f3217846ef50b2dfc6ebfbd9f105c66568c5e5c7551da47065e5d659cd92415b739257a668c958701491ec3bb4266a9061dfbe8be448852f25d525debec0316cba2cb325d1fe09dadcbed33122953fed9b0fed4bf6319f810f45f6b1f58a5ab2dee38b6e777161af5b8ea60008d75154d7d7354ecbbb6b7d4de39877797def1affd8b7ed4d9f0fbf43e43c7bfb116b76ac33932fc27d4107fbc987e42fb8597d8c4bf8cb26af6418f2102cb245a9792760bbdecab359e43905034e646a4affada4f844e4536d3c411e9623ab415aee61f215922b004d98c18e71b74ec2cd92d58433287efbb9700ad523cf24aece6b30d8965c4aef6cc41972859d0be6473f5ed05f476295ee252b5ce5fceb6c932d3e2bf695ba16716b8507a87db37545359747b77426a969d7f10f2ca4d06fbc73bd752949be25b6a56f1829087827b2d715ae272b90d5bc20adf6475fe1aaec9cd74790df01e540f8fd3e93ed9441a09eaeb52c73dff7b71b3849b84ff893a0391c9f0085702b11f16aaba8ff0e3604fb50835b140af2a742b352179c89bfc0d4e013a98bc0d816f7d9c93f37b40b68d3b12835b3d1a759f50c8fece6258b5b11e2bf0bd0f9cf160c5d162b51755ee79d26218120c03909aee1633c3757a3eb91cb3a401a387ff8b24c18f6d31f6c92abff1b7969bbc4ad2d5b7d45c2cea1cfa1e622935c00539f6475204b12173b017887401e4be0ae67061c874d90b05495e68ea178adfcd1328ff7c08f01d61e4c389f1efbb1b462ff8e205bfe74f3c75362fdb5626e9f389455ae4caf39b00088fed13b1c3d43644eeb04f070d57e6b620a6c7cfebb6016c0897f4bd7568dc50cf105fbc9311391ed6bc3f9a93467a151c353986e030228e5d69068b9111dec876a91f4afc5c1c13ad3b90537f38bd235e915eb0b85168199f58b4c6474504bc562771f0833b41a632658b5bee28f371ca848f1636b73fd8dbe0fa1bcd66814b7843ed042d6743ebdd707dcdcd39bcb330774a616614f698a01f2617d236e2c8891d4860373f1917d3d4de40dbaa2076d69394033d8dfdb6caf37d1f53a030955cec1be6fc131cbe153cae0df6cc8646e5cf289d96c4e7f07bf035d10702e39742cb724f55299ab3509009c42934188929030682a837e984135e84bb8b08a481a948e63016477d93a377adc2a804cb3278fac40387bd6677205f9b3221b2ae8cbbd43ea3965b58c78be4f09c71ebaffc76e524aff609021074ef9b09a274b7ba62d601d05a50c31869fb23315d6c5d195aeee1171e15d20454eb4da390970222290fe81b0c9c795ab15a48da09ec80edf7067dcd1e488677b3546db280fac43a7d1cf00af8e7ba02538e711329d1623d925bff89b63829990a1e878c38994d687d46700e2417f18d087ea7ea9698c432e3101b16a0a4377a356ff10838e9c358a38ffae21e36865b199a215b9b2332bd7c920603e27367724c6c2df1292a9b13160282cfdda2518838b1098ab036b4f3b0b1a2fedb939fb6f3e4e5f01317da779f0e780538334fca2ea1bac5821f8385ec1f2ec32b19140c1c6b02860fdde082e5e446c2c08060bda3223c2e8b8d5d2648744dc435d2287c97e31d4ecc088fc2ce16bf4c05a8acd1d59071494793b80ee41d44432de6b8480b664df153a8020e6c140d182561b8dff0edae34acd750c18336bf9edbabff981c2ab4ef9b67d6e16ec5975b93575221e1a8054c811da5dda424dcb9a37c873d6d48c7b3c885fb395892390fb84e3a38fa142fe24f0ac0d434ab14c25072d2de9cad4943718fe1b228592bdd69964c789abf7390c69c1842ae08ec325b6422017d5d23bc69484b70815219f3077b966253dcf87cb17250b4ef00c621b25e1f5cd123c30f04a99516a831e3ade53cb3f224cefcb900d5207e26797d4a44844451c0d9b067485f68ac6fcc03db623dc99bd6395cde143f0d2a3744eeb1597435ff1ad5bd2b50e8a5ed591b170b35415414ce271129aabfe8fa9558876ee508f92e94eabcd2d755986d5793dbe36ef83b1117a7264cc4d741aa6135894c5f5cf751d4965fd55e3b2e17060d33f45a91583d9f08278e4adb71e0e26e864e4e079a064d3583d96871cb60053f611c0284eea9bbaae7202df7cb3bb10e8565aa827c05b307209c890f73dcb4ea156dce923d578fa16fd5db779597454264772701fe9e9f858758750102f7da75791a8a1294e206e0658efd6c26e8965652d4892b7514c70ac83f0b998d7024ea34d515ab93f85c82ee17e43d01f5b7c48cb13b687d1ca54b1640141200800a2f56911a8b8aa82853069ddaf66012cc3f6c1f8a340bff67ecccf45bfd6a9f031f6f37f87d6d963e7f2e93c557fd3f7b3b28f4a672c4ea00829584abfacc4efc1c1ff78574e5b02a8bf9ff229bf1e50bcdde0603e3cafdddb374f19a5dbf4cdc7ec994745951d71ef4f39946dedf04b483c4e8d9e39da357bfef40ee0914dafe5369b2ebef0cf18d4535cf0ee296ee3bfc6bbe9040f71cc361bb368b68e1f3b38a5d0f35b357d1f3e7a54792d9b1af5a84f94d3616e5b9c577d42072adf36e96d8ce1f56151f7845991e93cd2a5f023c42e254cad1be990a36396d0200a21116a7afbf10c308dcb202fe115a264f12084b31b32e1988e892556aa14b68c83d657cb9fd74d80fac6266c19b17aeccd579c48551421065f59f90b0eed6e51a8d381e3ebd2a5fac1e93edcc52ba959fe687dd0bf15a62d271691236bfa5614a2d4bd80a91900b91bce59b227ef8b8fbc2bb3c221b1c9d4afe1926d7fcb90a5193671d2c0bc7f0f9fade4a8c742d8e9a8252056f937a771d53cc75ae83824c404fdefb0dd6dd4ba93acb1c6fcce3b7e0770705d6cbf30c9cdd9a02a6158afa4d92043ac6a2b05dae97856fd0008e417d6345036829d8cd37be6322a20bca1cff5ae6fd08c525c8fda31a151fb1661080cdea5858a99f3a5e25552b194d025048441370bfd12bd187aea4f08d8d340512e61a7823723b48770c7fda02cd45101b1c276799180bc375a6910383f93b60e62789c3407e587322ff0258d8c25b5ca3800a59d5d61655c919dc3ec1d2cc3de6da90a483b23c0587dd8060f586c72d02525b67f6d3857eae8e80ff881660026376734314003ea52e3a3654ea6b3c8b0877266d6239421926fbd2d53dd87ab40df0c19e466c623534acfcdd593eb5f72af43b400712ab7799aadb6f6da7e95c97310aa43d9451097f077dd6cb601b6a4724410864696511d90ae181b71b23af5bb7a2d0129da6d43ab643a93748c6768b5f5e91128106a363e5e020342a7661e8455dacf528d5b876b59a711597cd26110a0175b8daecfc4c646b395342debe32b4028e4567b4d7533b4a5bd22a7eb48187b1257b53e49356e85058dbe0f02a1d4fd1508319bf588ad3e4bc77a04565a3e27353784a3469f0752ed1bc76b49bae667e9d6edc955c53ddca6e9bae21e56dd0f4128f38ce76875ebe3238044eb5668d0e0f730a5b9e1bc2ea5d57e226dba3dbbaedd866cb4ae57aec32633d5fd150cb1ddb84aa5b77d4a686f0a071abf1ea6b5b74e579274ed0fcf408150d359f55be735298df6b3274069c3edd1752e7d732bad8f8f808110b3d94fe1aae62789d6edc95522bdede3293020746ab6eef674ad7219d6738b9e31a7cfe01564f314eda535a6bd02c2d161d4a8d18df93344becfbec845f89fd4a3acb91f32e5f7ebdc5e2709f29f0cadf0bc7dfc0759f5707a7bf03fd90a0cd1dfdef8db5bcfff255bd090fee84dd827c9da6ed737f704e010c4e42ec41f7b77e82f320b1ea2d33fc92a70487f7ba7fff4be1f8028cdbe30b103431fbaa0ab7fbd88c6004fb9fd4af5e0c1be917cc85d73fc4163f909542ba244883507167566c96021699e0754e8d9fc0f5edac2c2d4046b4a192a2681e1430b18d11d2454965df7dbebe52e9c90fa5e0de7efa61f5576fc316a36a17166d84f674080348781eae1d840772156751734d28d90cfd8bdc2cc791f874dfd7afcb572f7bfa39b94fefa9aacbeb2e6d8b11fed667f56b07329463e306c5efff263ac4fe08deb47c963970032bb1160c1d422119b9ad2497ed8a37e040fb983ff5af2b1fb7f80bfabed6dc251e13015dd984f99769bbaed1ec83c941d7e880834c29eee585be12f8bab6978e8f5ee0ea216f27e9daff5079901da399756d327c192f6a48365d869880322e18c572370fe3a0f724bf22fb7abc157fefada15473cf55fca1653a56a0464150784821d6fba4b0b0cad77b38815423bde72eaa24b5eb38a67eb1549061309d134405e7b9fd1d8a27af697beebc6dd580c624a21d7acc42a5624a22ce972f16a4a7c9c401b73501a611e07f285f42747c157ae801b8a2de289924794f86779ce4725a62d91aead5948dccbdd752219682119b45bba2d0c6854b04919639c40074ab1907f894662c258b4c6268f2ce49acc42c9cd23a5c674591dcb8bf0476481d205f8dcc8e8d0f3c89f0c2b5fd1619be27f8accff0745c3f9525a6865b10885b359a4d42f1ad2ad821a1a64818cd3109047398d50be4a96e37b6f5e9262853ee67371d1f8fbfa4e4d0ea4c31cfe6bc85dee47fc7439fa2b78ce2803172d9c5b5962665b162b28422cb943cf1b5b9e78b5baef3a517a9a7dfb372b6a7b8685c5ef8fbfbb21e8000d7be14c738ccb66a631ef7584aaaf4860d0740bfe0fc21dde115d4374e8d5da5d58e96d319179a91168d93c79f1d621bae6d1b792bd9f90812053ca4ed4a90502cc2536cb80008f171fddf736df8a7a62986899c8fd7cad17028b0ba10d08dab614602261ae2b98f31d3f4c47d68de2abc3eb6a4656437eca6b205bea11d53e8142ae95be95b3da8cd18ef76ddec7cb851b7622d506a87aee324bf452277d932e4b7f795e41d434ef7058459d3d0eb7ec64ee8d4954ea0c4ac23a448a20d5794dc2c380d9bb3cd28561104a0bffa92c15436b516a7d459e265472d9540c1c849657fea4b25db5f08d377f8b0a1dda891a8f582d5a585bac33aef7026b17b73e43d8e1f540d990ebb9fdaa4399a9aa65fd4b406bef88025aa7a191dd2554c88ab39a93b696307d3656fa01474cdaa63fa00e04ef19ca1d428e60e2daaafec19b7b505fef43dc6731a6c9e3b8bde6953f40b7678c6970b825e62efd77987ac940706b21f5bc8b4e69a19b717a4bfc659f88d2579cf97781dbb9134bb82dd5ff48752606c331234c1a71bf3adb953b59c563fc752e0fb1e3982f83d01845e654fca040e700e69b193ec82fe3e38eb87f456078e88d5390f0b20cdee94b3ec55c343ff114e57efe2fc35ef73a7d62848ff7dd405f6c6b769349df166b12aea1338054674c358903cf8d8b48bf737394b7f744544d2feac34c37e72c9f5a59bc2d557d23962623bc62fd3efb72b6140e3abf81fb865ca7368ed6d46a7a85f224ee5e9f6884aa0d9164302b3f80510360bf169168ec1a5d8b38bcf19e32d84fcea3be09d1bfc7d701b3fb9ed1d0fd27d9286928501bfca543ef61ed20a1d4bb7631c925b9ac320c5830d2cd81df5f8e8e0bedbcee622a6ffd305a70de82a53b876859b3abebc5c901de69fea666d7e609a0891b7ff40f467483b87bd967f0c6cdab30795e5d715d559c9fb8fd95b789651ab5318a1b92cdd0db88b63736c3f12e009631799c125d08ea60d12ea03e806a438a3557d2fecc8dd28524555f863638676a8df56ec46e602e7122d517ab4cc876155f1fd07a70f50f71cf09a230f5c8864f3f8cd2325a822734953e5bd85aa3ed48d4772516d543e691a7c759ffbbc39d0375e4972e4a8173d7edb990a66e54fc1212888ba79c1581d7bd47e255084917dbf56f8d26058f929534ffb48ac8c558bfa9a8b9db2722c52df3085e1ac2a87660af55c4d5685d8c849c8634ea0cffda4d91ee1674e2018cab89a554a6f8a7d974ecb45ac30513631651af6660f9f45c7b1919bdcbe29fe62980ea659c28e58ef0f6440d2a59b764c30901cbf4afa9d72b9ed69d275310f02fecc99548619ba50de5d1252ceae54035be7437774dbeb7799901171b54ad0d6540098b1667e450fff2ab2e125998ee843e44e9a6ccbaf69aa42a7108ca0ad3db02fd1ce0ee376ed3d0300b7fcb421f11af3330032b4d9a7f22415f3cfc5761776e880566d7ebb32956d0d9a33534d1c3e326b9faa744da12c1a491fcaa50fb5271250a65ea70eb7a5d67523797dc0ac05b7593602a46634c29781b6325e36949c508e8af3e76d84f534a897ab4ec3c124abaa0e367cdf1d06b97780efac5528e477e88ba1b87ff8cc8901babf44e0b19d6f2f0e551212d12a7c995638ed8f86457e555586b1d89ded3506731853815a27fe195ab9dd25788de93121aeed1eb4bf8172ee91990d8b7c9b3dcc48e6ae6456b68386e3c91761d6bd0d43371515b4134bd8a59886633ffac12576c5f3a09d7424de3ffbbbf4793efc90446aaf3684ce09a50e73430aab60a5e6b588c99da6fbe825c7c9ff45449df600b20bf77ee9d039497d61e2d8ab6ed6ba045828635d4907ec5c7f580701c3049b80284817bcca4506eb12cc7b54e97e483c21ebacd67e2ca1b565e119d49c6e84813ffe9da21faddc3c4099a151cbc04f76f22472703e6ec03f218c3e5722c66d3e0988b2f49108a13f32f5445de7e68b10373eba433ddda86816c6bf2acdd40bc07d9cc31f4fb93e4f2c4c0c41d57daaa55b40b8eda685bfa3be43ef755d875e4150870f48268eeffa84e9f725ac38360fe6c036d711cd0d67cbf85522a2c3788fa84d802e485ecf7b241858c48097f055432227d19ad9b1d8a0046ff16f668c50a9961e4d69fe24f2eb0f0d8a215c99e104c785c4f26970e14a788c8ae84f3b3cf111f016610b17805e6bfc7509dbe5111a3f69f7281235e1e91ec8acd47d273ceb0b05740cde61c2c8120f0f110808bd24ac59d11dc3821aca83849cbf868118bf0d7d3f98633f4a407cc4ed7a43d3ead57bdaff4eb2e5bfeb932fba6af2f7bdd0bd4c60deb887a738fcc0dca647b4959d1ec76ed97f1e0b72edd5a63924e0119a1e0bda7706e1122d0d72c3b7e0802876cfca8714a0dd5fde04bdc655a8b8a984e063bb256ea33c6caf8017a2d3b6193a868f6494b506c944c51740c6e1fbc69be9c859daf3f16d2584e374f35fe6b8521828f6023f939c68842198291ec47d82d13c9355a837cb49d75475a6af0c81b1bdeb81c42ddd4b186ccea18a89ee699bf8c0bf77c626501dadd9e59c4ed5aa4d9b03b8e89121bc67dd8dd84ba0f3c18014b75d6a7d5f0eb0981546063bceb4fce36e1d4a869a4d35e10d9cf3b03a7ffce7e8dd94c45f83688e1fe1a4f8f0b3712865e2ebf17064cdc3dec5709d3a1a1b2b2426f4846fe4c19e7791b9b3116a2cdc3cd84cafc528a3bdd167a63b372f9721a46aef15663c7e7615d6023ec6f3a368c1c24661a128d0fb5f5ef0a34fb7a68339d0ad5a78bd3a1fcf0561291ada4fd1269709e02076abe9eb848fffce711b73ddd557f01aab18e773dd45825174035165107bdaba788abae04bd3b2bd56fe1b4107deff3e2e1eea6bfbbfac25e1a277c02d4eaa3f9788a02750468e6b09a77083a6a047e7945c50f979a82f21c49b4c537359682549599251ce618b011554411cfb1b244d6980da2a69a45ef3d1c0a708003e96e4de7e4796bc52ab1ea4a2104a50955d5cb989469db0f2d83230a7024c2be5108e72a04fdeefeddf883ebd6abbd52f757d6d7f25e51b7bf181ed348a838d86c4a54d208a2944326018cbb01fa3b254459404c31930e92f708f42ca7254892a7b48a3d650cda9c609b016bd8103ecf350f35795247f6966aa65ff1929d68ec07e65df15ee9a609331e46676e78508e2bf29a6564abb910a462d88c01fb43d329d436d964efbda59432c9e20370049f04f603021aca40426d32bede257ec8f48ab60a81aa4157d2ccdd0d10cb4ceff566b5e6b9905716cad83dc0e0d21ad9839858a2bf436640a637cf6a0d93600bf996dbeb02ac4405742d234975e5402412c50464504480cc5a6b2d50ad592f7119888307d50e90fb8e186480ec430681206dc82fa0404005f88024401e12db5af08548ab2e0d13bae60908b556fd839229dfe8960a7c7001103ac0554bca21df9c3dafcec16bb797dd25d28a446b06b6edec1b570f6cb01561db85a264ebe11066b6b62052a135b97a259feab4d43c2500fec1ed5e85cc64ece02c6b00aaf7822ea2d60a921c3292946f690ae2fa976908fae1af59e2065204c1592e69e41bf8a0830efa0c04414b53d15e10ec85e400f2e74852d8735fb200638c31c618037170dbf75a7b814a24510014cc8081174a5a59df2f94958562c837bfcdca0ae2b6c13718d61e5435e811fdb7a1efa5ea1544b523093b8ec837f06df50ac2da0dd5a94e6bf7f42fcdda06e1738c3fecded841f7f97dd8bf8afdd6b63e0bafab9318804c6f05d885dc640d35087f4023e4a96e8bea3178aadb92915dc8f549be799341ac576f1200f9067ead20da0b05d666da4276a8d6acd76acd0a69c9e550adb5bbde0cb97a45412cd19f33fdc412fd3fd95620529f8c2485c9183e02a86660cba50c456c2e324f7568bc7ee8a90e0dd5d3f184f74563165169108d8038d3695175104559a64e3d3104aad72b8ba73d05b22003183ef5b61d54b7ee8d2558fcf55bab5bc763071b209ff644d7eb4ff5c1edd55adbc482f6f5c8ca245f5a85aaf20c8492db6fa05fcf17935c2298e905bfc51f13f4afd3a00dd7e96f28d17fc90bbe4978d0cafdb70bbec1bf8086d000742f19e47311155ffb915f4692aa640c1f416b0637f0bb56514abdec81d8b5efaed59a50967cabd5a8b5f65e8bb3d50bf16624297b9d00e2aca621f25497060a83270106d5da750e252e3b10870aa2faad59c1187b4c53d1df1ec473ab1848ba3456df34aae3f27a3a5692b41949aa5a37d1dfe51ef770a5b49a91a4aec7f01152d891a4c09a81e79e974b39c8a3217646926a135da29f66fa095d095d39096146bfa4f0252a84995013dc2b70184c82de0a21279e8cb434835e07fefab58d2ff83cd208ff256f69895c8b73a5dd80fedeea1863bf98620dbe1066d62a5fb71a84e2e4b6d6f6f5be77f6445f3fb982f06eb55ab93a7decb5d6ea4454441a7433dc5a9b0999c94f240a29c9ed336befe7e16bab9610bf21e857129156db7d868b16d2ebce84fd6df6f5ccd6d75ab5745a37bc445aa5f840d2e5caaa2b57c78b935c560a4ecef4c89a5241770296d41a158ebc11d3e58c1126a9ac5c162e86317cabdd2ed06d696eadc5350bb0a4990293142142c40319c4636a6510ac54e30585cb26c34c149d39c296d284174a2d28a6aeea6f6e53a273e46f6559eae810512acb058cd1a93107690e4ec69c11c2847861533170d812dc0d9382b3615c706594b892c2bc4018be5e589a7d61cd10bfccc890993ab2b9954b1232ee4165c303abb284bd345d1b52eeac031b9351c360e0c8b026187276883aaef7f585f565c65a7bf157d8979a6fe6799ef7816036c090c2300c65229810b01e198c0aac0a98979f9f9f9f9cc1bcc0d0808dc1d0a04183860a464adb86e9086b12755f3f92bea270412878aafb52c266cd1bdb13dc8604dc068f0fdc26076e03f5c2060449f925c3502e99626392e304cb41222748154521ded49299941997b032615c1efeb4441b95af1f5f510f546b12f8baf1851bfb1b18f65427078d9c3272ba7cfbac922410c3e8cc419a830b81ad6a8a4d08d5234629cf17374ecc4c098788e001cdd5e71b449f398b111b3e18dd8782e0504dd0af510f35eaa1c120d9c7212888839d7d08b3f73c030db924914762cba5ab0a98fc817cab0501790909faf1d5f38826623fbdb42bab6bd745064806441b946c6b926f05b8304010e95714ba2e23d6b69567344457ab37f26c9c2ffade888271f6edb55b7c5104dec8fb69916948445addaf83f5da10883626b97d66abb61f41a5add65a35bcf55aed07272c6a42d040c66b3d14eb060b55cee6d14d74535a67ae0d9493ba99727572907b01038f4a8e474e890c3b27d8f9e5ac9c2470b016c02a1255c74a8f330e948f25a7184e30e70372755e9c7abaebca19811c97ad3337766e39c99cb95c97b37339b293f8e4a29c5f7250289e3a6387c971a08d0b4d6294f92145eca90d1593932cd725b40e387dd4545184349533399aa74d2873684ecac6022f098a4e812125e5f4713a97b301b90cb9761246bfb8fa044aa795a8988db38d20c06a03a60db927670b66fa85b3079133b73586926918b4502f37627a085503e6dc71622585c8751634e700725761de50aede42d784625f504c60319090ece82221e754e46c3a676ec9d94072d4ca996b1c58ce3267063406addd99cb79d13bfd8467860a678e3e69ab14ec60da0585734c379d39386067aeb3c6ce386797e91044a95c600024272dcc7d9d09c869c9d19d9cce33078637540addd3317427fdd24b754c8f8c335752acb042a72e1b3bba8a9e51e3e53c72fe6893739c233820c849bb7214a4f172774e30c7845ea15927f5b2878d99d30590294cce496cce49ab206970d234f77e3afe63932729cca163e3d52442209783a2d12ed66439c3a8983387651627eacc39e1ce38556270985c4c9933c2c3f19ee73645de9b31c2db21a7458d156c4128141b9053a4ae9433945983f554b726ca8f4adaaf9a286974e50d3b1998e2844c580dd6a8e60c53995b03e4c7a7ba354ed5b6ad55e55936954d69aadcbb01351580dd76c0c2097df29708e3ae569e7a74f0a07a49f5d587a27c78fe8d627d6dcf6f50ad559f40b68e1b9344d7f189ebd80357670f63a9b57660bdbaadb492ade5d75dbcddd7fb3a514731c1370dc4175f4cded103f286ffda296289fef60c3654d043105c41677fbdd28a0278b4c88ee8e08c5b688a02ee9ab01cd1d9d938c106d49c393638f8032fbabaec50129ba366840d97bcdd7a20fb74c794f5796a58d1c082038569c9c834e5851bcc0404000e2822c606cc489a1f7242aaa75b421213d3d10c0b4e06a61647f7de3434dc5c11588d45dd71fa121287ea6a6c097735c2d0b0b9710fda3181799fa73ba62a7ff35274022956a8844a035a98e244513903c74992375e5ebba5b1dd52938d4d8804a703e378e06c06e2ca1a720e8fe07078876b4ab271ed70562f80a73bdc1010bced374afd5a15d501df5ffdf12561bb9ba4fd2c6e5737698ea041a3956fb91a5df022b0d0a0dbc988b3938164cb1af2435503638cf1ed70ea94f1c0b5f8763392703632bb195ffee7e96ec694a4322cd8c6d8c6ec94bcc031afafcea12d50e37e754aeb0fdfda1fa291ad1be991d65a6dd0c7ba881abe25e9102e5bbb530af3e2d39d52162b0fa8c96262430718205be10a6d90feb651f29eee94aadceeeeeeeeeea63a6a8d3e25bbd80a6206d415a8167d6fbd679f375a073fafe14802246bed4d4713f5ad8bdfccd2ec539b01032e7a3cdd291d79513692189293c3d184b54e7a60146cf28215db7bdb12b5c57e6ba7b576f3a8607baddd293d398db0d65a6bed968c9fee9470947894722b28c9f091f7fcc5bb24342f7bba4b129382dbb2dc005b0ecaa15d1294b74f77494efe56ba90e46fa5ed04a932861f729ecca35c799524c3b694d4428d18d51b130757a9848c102fb61956d23ce14155fa50a19c03048a470a37cc183196cc3c315e64b1c3244e0a6aca2e664a295faeb0983daeecb460735ac1dc09f92ac17684a3ac8241030e0ca43978ca3a5f43d8985c593274e52e0b10296617931f53259714be4c305fd0b10389a9dc428a619a90472aac44a16b4c2b29cc912d245009d3c47603898985c8181fb67387c9e6a849c99152a9678f39858e0dca4d0b68ca27634aac2e495e306f58915162a351c47c8342c906aadc72be98612be1858b72471d17e0b84893a38c090396c9c295f20428659e25a610236282bc4082798e50da71812bb5b838ba72078a9c2f6bcca918cc2c30b61830d8784429a5aa4c24165018d2a3470c39b0d43099762d605c19b3f5009b6a23e74c29b6339b6831974c797281c891b284a812cc092f9c6cb83d364cab3d493ef4849993c5ab44b265961163eee11255a58472528a49a2470acc0fb127734dce84a1474639a6a785ad8598e9e4cd132e3364a47809b3624679526651c551c13403a424c3c336c20d332acf525925cf0b5b0e3b5de2984e6a82982959f8f294432c5e7278651aa932674c955ea24a273e969e909c34c696cc2263472ae0090266f2e82acf6cd99ec6bcf0a2bb6233e1aa6c5255de99ba1365d2f16112792a879c65f02cd5c073545ae1319bc06c4d5d65922daf315abc9463ae985e57260955361c53e59e28b3ca8789e7a954e1b4b9b8b364cbdd392aade03901ccacd2d5c25679648cb98217dc9512cd559baa335368a24c2c1f2d9e6c36ce387696ca37768ecc1c1e9b0e302a5d495b25d718938917b3ce95d2cd559b2a38536ba24c2b3ecaaea71a67589d251b8c3a475478cc12c04c2e5d2590ad126c8c69c38ba974a5c85549a76aca1495a812061f604fa69bd34c23b654fe103b3212ed9e5bdae798835aabe5bf0736509d12580d3a005aa7510fd5ab530fd53fccc822ec065f39f8aa0297b556bbc90e94be1d010d61bd7a394443d0c7b7471b4c82bcbae184e71774a72d4f773ad63c039eee743071acf7a7bb1d475ebc333be219fe661eed5e0ad0f71cf44b43d06f4f71ab8ba8200e7aef44fdea462060f1f445d4149e57cfbda741f6e2f0edba672f066b2de8b1cc2fedfb482b0b41ecd73dff2ee897e679d05f32c85aaa3abbbf5eabfcf5ddd31a11cf9a7641152b8a3eeb4645df5b3ca2c0d6abd75eff76704c7167753c71ab4822530fedf4b675f2b9f7f50718d93a55cad741bfde3444e8177c9987b4a29f6fda4d067afd79f8b6980e281db86ff7c6beddf37afa2c76fcf8023cdded40fa158d45b0bd968d24ba7be620f8854dde46bf79ee5303123978947a18fda3c93c9016f41f088e34702cd16e5d846d1c1a5ddc0dfa6d44e1b9d7f5236ab7fe91e2877b3cd1ee919e75fa91e8d0e4adc353a71f60e4a29f6feaa1dbc11c93173d0791a72f6281c23d7d1c632ff3d0739079d143bff9f82d8721a9211465b4998fa1e8216de6e38c06bdcc43da8d05d8e1cdfb79a3fb335f711f03cd380061fc83431f1f692038a290b948fed06e78863d4f45d1087e74a3eb9ee823f369998ba3e8230d4d84a4e83ea2288e331765b4cf7de8480b7a198dbacf38862842b0d6c21926c70e2e7bc1040791a7cfe28b3ace53fc2376a03416df23099ae74d54bdfd03699edfebb74a3159d46f9d08457bb56c70407d88a7bb11b27ee714e694f51d3aa8e8dbe79ed7cfbb794ec7122568d1a2858bf7c8d9075ceea7ee7d14d3a0ef403d50f77639afbf4ec35aefb8c147269bb94fb563079ecf3ae4b16d588915fd9f7f7091653ec296ff5c7683cc5ff1861ddc121b464a650ecedce83ea52e8e764421238dfa3b45ff8cbcb5254bb47b200d6560fd3c046927bc15fdb3b65cd7ed05afcfb07b8e6d4561dd5e9a11ee6641b463ef89e0f5d89b4c1396af1e3279bb3b897cf5d0c51fd0fb1b4d84dd32df50d44fdde87e7bd1cf37e9e2471a759346fd94bc591abd61577122bf08d543ff42af5eff0b7d457fe8962422896827784abb854e43af2e86e058f4f32250eae1665358f2d63145f5108734a3fefa3d82e486131f1149547d453f2eeabf41441a74fdc3f5757077425d10cc65592b55dcbf95290ef04d0e0de0c38d6cade592df62aa00a7d4baf776f616c4758f8487e997a2c5ea15df4a5e3a14f68d1eb0f803807e1d743c9e188bdaa81a358aea449804bd2c22ad208aaedf0bfe54500687d935cdb94d5d1f3edd35698979156a674349d3111b53029023a804cc983162e8146d800093160000180c08060422912446a250dba90f1480076ba22a604026a74a0261280a83180662188461000040300060100041188ac11854fc696ae111e10cd10d4adfbc73855d6c5157cacdaeffab8fd83d5225acdcdc68de077071832af5d2cdfabc90ca8e2f7f8c1279d154a6e33d9d7558cb590c132fc61a6a10901aa598ce0cc1f20c0ccb2640a9b6728ea42ba70bcbbf03fe651d285140cc105c091dacb07029941d5b2f1e33e25c8b669a1dfc76c6e6ac9840555cf00274f7292556e0b781ee185bca54e5e5559c2475ee2120161b6c648dd381deb760b75ee8d7b9eb6ad3e9a0d46f6f4b2f1cdee79429846ae5f5beaf9532df5bc74d7d3181fd89326ed2c0238d99ba2300cf3b1c8729d8563f61c925b8630918259a2b756a1069717ea2177a486599dad36d5b4743f8d45b02eccf814f269d4916990b8615c0150c1805a6a9cbe9367557b336dc615b3773ee07ec408d282987cdd98a249f89508a3bed15e2d030f1b7c65196289326d671f83f4c878ac08855dd3d15145450e449dcc2e46af93e83280e881ac1020d2cc3a45c96ef1714c1c9b4ce3e988238eb9141308294b8486afc40e39761217f810a6ce77e0eb6bd62d73bb0bb5e9de13343551cfaf0293203fc8ed9a3d3179c0fceae403a7d1ed6aba382889dd9c365da81c119667005bb2a6264678abb08f9c4f86c8377f47c0c10759f5adbf434f2d7a12ac4ba24b2c88528832468a6dd6d7603127a32bc643541a8053100f150193110540072110aa43d4d604e40eb86f280a677056eea8d98a8091b66bf68ac3d5bbfce958c2bc68ddf448399cb0e2736b27cbf2c090e7c3447f81609b8fa5236dd012b3b2e7092ec10984d1d100d3e4cbdfacc0a81f6b55b82fa622ad9fc8283971daacc47ac8a602cd2167804472ce35b200b78f860d56d07c8bf43cc5c36226dfe2e8603a3e007e1fd3ddf6f00b8a51595b6d6bcc8f6586c39f7f950014c6acf1d7ee5dfc5b21e7639f2faca85c490d85232a757185e1379443f4766b451b3978dcf28afe121259f5a428cfb9b8a05fd9dfa241b6f91df05bff4a848e24d692ed71484f1d701dbb6240c88174a71a434aad0b97aa508af2a69839902773466c15aa40a7e8ca38ec26fb4e9a88d3ef3543582d0950342440f1d07de537c36772a18692039a61e08724eca09b2e20a6bfe040f4c0cd8cc82f0e68cb5b8cb10f300857a0de483f3e358fafb7ca02f03c68657034be02aaf97dc25cbe11021142143908d6ff1517bbb0380726303058f17ea7a30baba110dafee98c9a78d0289b6ab0c425e5b131539ea8010898a0ccfda467de2acc708de1b9305bed8fee4859cc9b998872d7c3d40427dc96ccc69cd5f37ea15ab0c19ba41203c47439b7dca07b3e5849fd63221580870aa7f1b82d297dd3be103b97117fe3305da99aaa26500d855c84a1bd93cf9264cf7434dea47152942bc4684482632cc0d2a8da1d03804eb8638036b6985d590f6dc058877b64e9487a434499188dbbc8fcb1a8e226da167e70e075fbdf7c048916f998a9f18a96f32da0bf692e218396ae83dc27fa11a79077bb774e01c0526fdeb10ac2d3c9d01d230d23de224ab256f81d95cae2704eb805268241aef58b49834cfce937034b68750c284ef428d3655ebf60466ff44253edeae876ef04a7308533c65f3a2191604fe675815ef8ad254ae12403751020103a396798a16f409de6c177c113fb874b652893761f5aa8c06d4372431fc9c64b4394024a02263f7e842c4a28b36ec22ea43c877f561526c6d4223e0ccaaf6eab4b6d5a12e11af82674ebcdb3e5889b5ee4eb65b828ceb40c77c219825f6613dcff4ea2a0e0d16d1664db8be1aac149300db70f56bd3b7d8406a156b11107e30d626d10ad5fbb75bcca7bcd41f140fb3421d85e5103ea54945bd62d5749a7a8423c7360205160497a5d01cf00bbbb96cf0a03812aefa0931a6d6917321ea4591dd0b0511ecbff0c3d2f7b4c230ffb2ffff626608b94c6fa52a698bdb39feb55787a42fef2ae9f537adb7d501c308a5bdb9b989a9fe6b57f3b49ae23be0e1cf1cb01ddfce3921218a2dc91280dbd7b89357640a411a0b9f52a181364e7790298afa9213bde4ea2c53f71d4bec974d3e234dc72a791404ec71b1136e8c7613bdad0a6905f138d70b201d85633374c3f9731ef882e7a96f0939c5238d25c881cd8050ea74cf1a34c6df452c976e59fd4e40d99066c1ae90d6dd1352bc401759f0e42a8abf4adf5b6a0631ad27b030c6507a22a05c8971705026832d817aac4ea137775eebf5199d576358929b989bd6673f3287903d8509ff64299b3ef273ef634a9e55c20e7dc0bc7199779071513d208746ea4342d61b51297f052a79c589f141f6b48fb5885a62609d4de4aba808de60b2bea5497b2599df07bd949890766321d1fd058489cf8c6007b19694f57d25fcdb1a1742cb12875d87a381db63700d6f236b9dc7a2cee5c5e7f93ff13bb92852ce48c39ae53f3c1440ce67f81a7744082950d5d7e2a51a26fc946437189e0e40de978a6ab75ee422d02036efbb1b301b35b071867907547f39288f674ea7a324062ae8cd21a2f510d52a46d6ffa945b3991c109be2ec2d87bf52471c8f25b4c44964ac3864e66c12b6cc4b21198f8ddf31b33da55434e883a41ac67fa30a872049cd9cd3d8845b4de12aa4f25ff08c414f9e142d4bf7ccb32ec6b4830a392ee26f58b33cfd46426017271918797bed8d8f4bdd09569ce95cd8224c843345b67e4d1bc6643d39e1813bfeb1d249249c56e445d599ba67895a07c43fa7164723078dd1d15c1e9a6bdb2759666c06d0a48b9cd36ebcc7aaf844dbe2783cbcbaf3826f36b7bd63331829a9e5a261044adf67f1cc81a01331cc06ee82b106540203e234cc3e20d36c6c69d5ac98c930b87f633efcf8945b08d4a0c9a8a43c37d30eb00a4dbd7b61a925a5ac77bd835c4cd7e3ed79bf85bafc44ec9d609af201db1c6bccc60ce906ad489d9e9674a526cab6d1c23321b4c097054f62f65244e6c0c7b559d85a45a8030ea72505a4fdb5ad856177eaa9005d99a8b42a9885af4d202eb0bd029488a5019b0f31abac32b9376389d784f8d4375dec91a61cba3fda995a65e3d32adacb5ad33e1819f843c288e2cbc0dda463c69b0653cd530da60a1c59792b1ecf899bfa7a9d8fedbca79893e047a31e925855e70ef875be3d1eb4294eefcc6d1b38b092d854eb75036bc24bb5475a066ae517b0c869eb91caa8a24442f68899ac572c3a81c16eb4921d276afb4ce383f741f8086f2210e3f8797928702898b743a53c5b4d59d8141c420e221db033f651634ecf23cd438eddd1b3d9517f1ad3301b7d514b68d7876e6fb230092a1b5eab1f8c9dc942364eee66e1732b188514d13dfde661ae15713916075a818e245c694e46fbad8a73e80f074caee4ec23398a30f83b0e76731f2524a8e1893fbbb38cee05a0f159ff844817bb5e22801c6eb222bdd7b528476a6c1920008cdabace58c39f5035be4f5afcd1a294a801a6f74d42b20aea1440aad492cfe492a60c503f4eff54473b9a1bc1aededc5249cdc72c7d5bfa1440ab67ca8f334ea48c93f7616a9d4b9514357a2e55b32c4f63e698d22d754b10bfb81c97966f138222445ae6959923db6693ccd474dfa64767cdc863f20f2cd1682b1a82f03e652a9c5f080e660ff75e4549610143e1e35dfa75d5bdb1c1bd982873057734dbb6530894b52e6c9912c8b9d45182d89d03208974d0b8749a1099cd4a3c3317a5c8bed30eebaedb7dac9ef639f070642bd6013df7b5cd1e3b71685bb1297cc5853610912173779da50f3f5dc400d1b30f55208ef98e7e401fc5044421fefff76913d8779e11e93bfa38059a96cb755ef7878214bfa9d04526be9866ec0d1c136fdad2da0d20b60e4d5c5d73f35dd8684354afb2b4a44994f63846f3a0d3b5222caa64010772638ce33f271a7f03b649bd8ae339b5a2c18fa4f87398839c87ec7cb41dcda1fece1a80ef23c340593706486b07b1e18d80f827ceda83351477e8ed76d2375135df7626abda085130034f22880e054d6f84f18528d81c5adb32cd85275d11758c30ae049c7225ee1e0311d04db57dd4af396edd1bbd36be9ed649f12c82e1065393afe5f314c8db69cc2b4f8811ca4e63115843018ebdd64a6879c86fd4db45b5e452dd8c3b58490081cd7b08c9d02482c9c20c3d9bcdd15723bdef419732b5c417dbb7f9f63d471a287a80b8563e2c19692e596e5038abe631affae8b8866e02085530f33dc65ef6031ba69c318ee631cbc0515c723d82113835ef1d998c264b4909698caf265a53630897a2a495c5c716d5d8c542e4233ad5d542eb18eb04f0d00791e082b988b574bc3f128f9704ead41cd77a9426844103a98167d44d97ace544a52243e87c04e2d94d18cb89f0754e6b9a69b19b7c7bf4c58b3a37589bae0e8c8ac3e927a4b25190d030b0bed72c0ba734409e3b68097504ae3b1f8a48ae31e323b0fe4e2a4d7027d0ac60acda63a2df71d58b61c418ba08b0ae987e774c204c8ec405126d35e30b300a838fb01444b75f7fda0e2838a3c749c6b9c8cf04dd644420a278bd85a41d38b81376fd08300f263e09622509dde9e1d751835efe0d7d56559986d160d432021dd8df83f3e41e32a9058c54e60994f8cb24978289123316b9679a738c056994a4ca0b0aef3fac7013f7057a4bcedc14765d0ef7dc85013dec130b174e3c13e266cfe592d74496f1decd8598c8db4763abcf863c878c6cf804f4642f306171163a8ee22ac2d1ccbc1474467f20f508bdd7098f732c60c8f41b303fc6113cc74afdb0d8a8af79ca06ded9c1497f6b439bef2e08d0d36a6a03fc0888bd6574017c5c9bb845aa2723878d0857f7ad68bb4a9695c7ad8f94695d6d7e4a38e520ac37b7c7c1862419144a37df5c014bf1a6af88d38b2293b0b2792c52a82f7c90081a0dc47cb1760521b1e52259aa8452a46de052d92140987b1349d1c58dd979e0254d7f54ce0c772c3dc9f5364f3fdb2c89de47b390fe3c268438b06a516647b3aba8e9959401ed982bb16b901f496afa453e5d6b32bf9171c93f48cbb3a2acb7c85c2ff7989c71b011db0e9b6d61b62acf96b3f08619e84fb1a0a5cc13ce9aac1bbddef4f8ce4f285c1b975271cb9c422f93cd869019817d34a5eaa920faf2d4b24df84e4a0bfa8670911c6f3a2c42d6d46c198a021dbbde9d046e1451c5dd60c5b5bd27344f4a80799f54c7526c7f4b2746ce44bd45cbeeac757c26fb9660a3587353c0aad0633aee0e4964d08e25def8072bfe54b420b050d282a0c3aa32fb2b721e96d09bd1f9c48d66cf325de169cda579b32f3ce64c525c773d0f79d54b7e17e0b17eeb4ab505d72114f250d61c5645b96e7f612524deeeab54c6a0bd71bd661864fb08baa6e2a078df09d9c683f13249f2655f4d018a83bdf4562e57874e1e34323d47a49e67cfd546deda6ff61d1130b1da5694c65723f416081d3b6d89f2a7cfb4b0adcb46f6e4fff19877ae73cad32226ed0458479ac746d41a3c22b45f1dd622856eb457ea1e4a0d283276fa9d91131b4e4e02072a17cf9c2b6e5d4953de16e98e34052b61c3edc5efdd4a22989aef84692c3eec2171efac1a43d46c4804b506a4cc3f20efd2e1a8383cd313bdecb2840899b0c49d74f753eacd82fc3f1cb1ee71cc385a84837be940b9a5b16da816af903ffcece73d13f6e993c40d7e9b9190d917046adddec151a39315bc2f9550c75f0c34d67d42321da1b751fcdac4c4de99b907358a19584069f280ed63ae55f3dc88eb41bc1a75abc9490c5a8227dbd6a79ee2d481c4fdc1989d3c4f751a0ae5e14b9dbf4ed15de08ef136c73e517c36a28ff34f6b5e3a4c940e6f5c07990eaab578b23c8faf347eb4acc01177534c20596a8792fa5661ee71b96d0dc71419a2fa94b71f0641058859fc6273452ecaaae827f53be886484806f7d5764fe0c38ced1bec1763a8c6390b2563ab10bd05a245b66c9af386e6ca65d23ca45d8f8ace2fd12423e62e9152fee9079d7cae11ee0c46404c63f16ba36d3269c5da10f215e23e5cc3bb1bc26bedc6c97e77903caa6f4933c07629f00943aa6f11a79c443bef83a0bb521de7439a2e061cc09ba5d8739a15a5196e7d58c9a45f178c59dd9162efa08e0837dd0d3ffdb3f8dc0755158a8ebd9e7d482e1221b461966a1536855eb9333e1abc4a5ad08e5771fbab8389c10e5d13fe08f5502abc93735d579031df138cfd4655067855640cd072c7ac4b62673cfed9288feaa01a539c6e1c2d0783946eae08666b6efcf5ec235a83213855303ca62777f8a70dbd9588bd7b2c55f4702e113a1442f600ebf58410a41a685c4d8a99dcf81693aeb1d7fe9647aac1cc74fcd4f9d3a40047c4d99f562a720041283d6325943605fd7c171ddfa2bc8c82cb737beeb172c02b4ceb2bc618e63a86bd1c3d95cfc93ace95110ba10286d1830410fd85efc91f18a9cad9363b419e3a28f62fbedd09c648f5bd30095a7ac99ec7fe4e9ff83502a40a6581eedcf45a2d591bb0bfdba27f6b2f27486ce5a095102841bca9e1c09043f23b71d2aaec20dff05c63c222536acb6efa65c672062b23fb3f3f334108f6d36e2ec5fe0df601ebc87ec3a22a4944b6e0f34256bd081cb00673e0c6fbb9f47ef25965bb915629560cad0a42028f607b5905aff98c05a7e2ae8f57249a5040da96205f343ce035a89809c780b81e47aa17243d11083460aef2f99e399fca13aca616b53b2f895e37aa65efbf75a30277b8834fc5e873149fd7c666d2a6fa281806736265093ef5ee44c3ac18a198bd0e7b582d253388ec7a630ec1ee924b60721f0b48eec1851c2826900b68815eda050274b4f6dcc7c6dfd4f1e0cea735a12bddbab9a9b3d977907889782200a117f75b1b8778b816075e543571dbe5c96d11384bce3f971772bab81c616dbee9f29a78acfa3c5848b5837bad7bf192660a403dc88ac997c4e941c812dd4889402a349e02ee3f56ad1108e8c824c58f2b8ec01582ec8239e720f0ad16f10b5e14249364631e181e626ab5b04dfba6caec86a3f05f5027171c62acf2e1c2246e4487d39655f218faf1519a4b6aac9eeb5a2b1de13c2a8591c234a89fe2e04891ede138eb07296704451920370205497dab2db1055167210c6345eee3aef8045d2997921ef2a240d9fa8540b8c183c1ca36f44d5e8dee48a79a20a5a314609e767159808b47a4c2e2c0389ac983be28ada68a5be3db009d2e233058a0e9eb887203061f5b0b1a567ae91bebb30cd7a3dcc9b86782b838fc2509ad9ad6cec539bacb0970f19ee0d66d95ff0a3e7de0ac74037b8f9e73046e59f95bdb146499a176687bbc6f582dcfa49013d47a66a0a1a393c10cc2efe68854a1e66d3a25c4cf8854409dac16c0d82a44de006faf8133b9c1bf2ac77aacc6809707416b037a94ace98895a81e7f3b815a2d03bf08f6be3cb2a5799fe1d46fea7c61dbf9f74fd86549e24c686199fef9275c49ecae94726cc59238b2ada8beca99b882ed9c6b0b6ae9829ff8401b8d28edc4d3996bb17f209fb07f446e0821b00f1dd3b00c78c214c09d2aa3d769ee8539a846b1dce63d83e93ff6ef37b4e932b9fbdf5c5c807d02bb9f9e26ce61c44a3184f2754d61a5e791cf609813e68d498541ccac3e5a4fdc134e8fd5062fc075cecfead1b846be30f65e15a95619fdd85a11fff33eb5866150176209bb004aa0070190cb867409e6b01e969569b10aa9d9846f32209423de08368ec038fdd46d29217c11b408c08b003499c526c21eb776ae72370830e2c7ca05178141f819de12234413218bdf0fb2ede2e903ee0610af440143f41b30411f18e7609abf9dcdbec32dfaeed8cced654efe54bb2e828a2330709f3d3816703eab66bd880b27ac724df5108de342f9d13936c40a84de311a120854373bc648049c15579f3dbaf3e13ebcd62e703e6de78f8816e64f1a9712e2fd08ca699f1a6a17899f9a438b78ef5b01c1672e762ef253aa0e43f7332c07ade707610e2808fd8b66c29c115f81fa6a47418e0effaef627bf0e43fc33b5250594d6bb759fdde53eab960702882158e43f747456e07faedc73417faeedeadde2fcc039822196112f840888310501c1a7b97370d11fc372d02e458c7c2ca89bdd92d667b0bd25e8020d2ef19e789ff5ad0f4231021f041b44244255841e842322c5113f0df3cf9a2d0dcad266854014e4f35e0e63f629ddce20d4053a8ebb6875e9afbafbb2959bb2deb13039ee79d52811bb11e088c0207c76e05820f8d49aa54400fd7f5cdb2e9e3e076b2c849fa4db59c43bc4388269884d107f10e940203e0283fdd96c592128a09a7411a6407022d409c102e1134327b4f8437351237e1055bb5e7e6a49ebd6880b7d29979b870bdf02cbe7c78d22744238431c8b3081d08cf823d485c00f016c0486c8cf789d15621f89a344ac4e88a0cf4f7b4b4704a04f3e436f17a84f96ab4680475c422884f808210e61265c248dcfbf8b17741c250cc048264bd21dc6c2874edbf6574b3446a74892fd915f8731fa99de9e0262d16d5c68ffb88f352df89f8a70319da245d6c7f706a31118bacf263a16909f55e383501881f808b598fd17ec2673f70e2de957be63c06e932b281f8c5383c010e88a37ed6c8422e21191e35f3721fce4c2816a7fbeb462207420441719e14054a1502c84b70d32a2c5c347e39e42d4e9074dc999136707f904ba154658f14aff68b140cbb5ef2bfcd06e2915d1e2ff09b6abe2f919b7092116205e208e20cc204c1b01d4f7c96afb84a0c2f4415a3739ea936d6d1174b1baabd20bfbc7eb680c4a1f49bb5b5fe573afc98b70401446e023b622a0405040ec8350cf8816a34fcc5608840294d4900db0df68a55fbafc8c65cc02f5e43cc54d5b118213f14e0446d18706ce0a854fad43477c10aa362268fe73e0e221c48df093f823b841019416a6b9e1c4533e99561501200816970f953a6a9d3e15d92e923f2bd70da13804e0d715c924b9f94598223623a6b7be0844f976408d98a1dd828db8618c9cdfd743e1db1d73004ead119cc5fad16eac880ac4dd0807083128be19027a4b71258e1ff68759f0d66c994b3d18a323dcd18cd3fd406a67b04d11f9a6ba33f81fe1004676014597d65c88155df8aa582d9ccbe104f904bbc208e5106e448a44b460fad41c6a08f17db5444ceec813217e562e078218818f48d60f8a7db20ea351f273e5ba1157fc73551fef001839445f18f8bcd0e4a3d14d5431848ef013f80fd7883081ce1ad144b06810c67adc093bca4985829345bda9710f90e7d9e0b721cb61c9668aeb3366c6c2656071dfc0de396942d496aa8534524d91816a37785ea7b69e1632d42c2a8082f6c12f6023922e74ba70508c6c3d8dad4b0feabbd3855e720de3e7c033df67e219269e088187828f76292525dba9f2f02273c4fa3e72acaeb1f17d5d06ac51f942519e752883cc8c104a31ca6dbfb0d0ad27056cce2c74281dbabf04b164849eea78bb0d563a40e03af63efddf22790ec5ae19baeae5f51c9ac1cae8a9668937d4fa622593724a7cb20ba44965d7a2e73f0a75174dc61806c13c60756e02f90537bc085b4b5bc2f02329b9850037f6e4f22f9af210dde652fa91af1d5f35a972d2c2f29b3ff152ca6208cd670a662f7f9ce18c40877aab9f5a23f04a4b4e3013263b42d0be41490b6735c105bb62f59e221a3f7835e5a12cf6781d742643e8c94c4581d6ed1df6022d46aa8a570c44372f61d8473c759e464480cca582c34cab1a7341215aa45f418e9bd52486a3e347a6ead63944e7dac11010cc17783a52b2d843d0c56cc17d75cbf045e3773eb550c54a4d7b3ce7ca5f0c3b308e821abed32e312b104f1977cb2ae372e25ac8bb2bab65c1bfb128b14497321398e3de11c120044641a31b42fef43dff86cfe76fd9cebd7f39cb4d455496019d4b289f5da96e04d061397a11243a1cef3080f5e1a9c1e15da744691101c4795341f017cbe057837d3978085c2387a53d0c06528895e1e428fbea0f59518a7c0d26fb2674c41453445fe7e7beff06ea43a92e791cf61584c5adecaae6922ff22125a0eefe3fd6b102809473f59ee10b63178a7b177b358d86db064f65b867ca6e8bb5002a921841ead9742607aa4d03f7a8aa58b66c18066cb0e22572294f6ee02d45289e277b0ec81e36d3d36863c9566473e18689a202f15a576003f8ac00f240d1b0fdb6bcd0857a6e7b86eaa8d277b99e50c9399fd48a1cdaa10b63c57506a039eef84e4e922c267376159c66abaaffdbb186b6d4f691e88e01096f081af7add35a6ab5d5bd259549e9672d48f7b1c8be26f6de0cd6fd3eba1220dac1b416752d3a4b46ac7713fa55b3ee3f300c2df0f08eb5992ed682098e77128e86de69978032b80a0f26b909b04c3b546eef6e727836a12daea981ff85d0f6d3e385c7668be1d2e422a859ec0e921e7dd6bccf144ec4fa805fdd037905708732b7f5560810661d6678496bbdc706201ecd62c2b1a02302605dad60517727a20679aadc708d1023db52dac037851174654d5aee99c8c1c633e150c39a0cf06aa4dc5e2cb630f4d492616b288f3ba90cb38dbae6ccbdf9e038dee09d196ee63df81b309f644277cac67154cf54da4eb71fcd3c1f5789e73a8f9eb08d579e2b5bef842f0330ccaf29662ffa74787ce4fd8f1e29dc46133088960ec5cc62528f4dd0109f5720d5fb88d13a3415c1be9241085cc1bbb6c5e7e13cbcc1d5e340e91037b99f62001dc4288ca577e6586e5fc4861e3290fbc9baf51d78ef7f46720e16bd840c451e99ddcf172f27a7b35c6fef2e3c82535c9aa6f9d92384ecd130a49f6454c16060525529f68ee21c439ea9b1193f8a5cf7af1d0a09f4516a905b138283c05ec1c03aa4ed05d274155bbf5d5b7fad5b1ad1218788d6646543b7cf5d2b85cd366c53ce0e4fd7287047c7b2bf1d2a79e956f0af1d99ae73c92dbdee061f71e4f661646b1c8003c4cb356b5f25524ea1fe5ea07196b40c2221dd2abc4158059b9122be16b618c64d05a763106bc07882ddd768ce7bc0650ae0baacdb227a7514bffb8336b7783c048603927abc8201cc87214e24a7b0cffba678726b8d4dbbd590124811b1427c05fc205d37730a9914140ddf0cbf8573b05753da8b48dff27b6f7c7dd5679db2f1dddae9c7af267c218c62fb9a7729c846e2cf29a0f11140ecd658b5ac6e36b8ae8490ddb84a07214e22824bc1da031cb9fe4549ea348434b81c43c153a70ce1cbd08a8bab1c8cfe4b0c052d17ea21f209e1118525c287ae01a8dda41e658d16e8f4c015ba3fe94002a376311e901a18d15a199ab6e16c874089284775ce7594970a56836ca23ce68963c9fec58e96590a81b41b37f17486065e0e3f472549d33683d2ed59d6f968e08e311ed075b19590760400b20c23aca3d19ce1aeb9e844a68602b4515855e5f1b24ecb1c2d9aba5dae071fe50b4863e3e9723be9fcdcb60da082c68f3d7758b48333eff20fa89bc0aa46db3fe58df6a19f7d1d46ed781522c7cc0abfeed35736572dbb640fdb980e604b4ae5c6aedd02051adae4d77c9e3ee6985cd7542c50bed2aa89658e9f3522d7f47baa55e9f13ef23cd98b79447f27f1192ef038aa2bd2cbb70ff4c05e27dd8c645dea92bd070675cda7d7ad8bc0c2e3ce6193428815ebd698c6700de565f4ce8adb9812373582755924d124f5a8dcb9d81b2f44eb2acbb6d73c2b272bf310bb9e67acbdc84f8a6c260a85344ae80e3850c9a0cd28d2225f0bee6bb3023adf19fe1c224c16d3ad0c238b5472fe0cd50963c0140e592e9a25ae7489ac89c109492c885595f80b538264b2b04b96e74b57b9fade342c1c9b6b18c9149665edd7698c8e446493459060e618d80a9b8d6619a0b3c8b2d621f75c598de46004895960b53f09fa9d8cfc9d3d110f0deb8ec5521d51953ef6f5be2b1a2dcda9a5c4cd3452bf3f13190916eb4a5e1d104f1506bdc8e8cf770ae7a2bc2d6ac5b79eacbf4c6c05c45bae4f447058d4e1fc6e79769f0624ce03b8d267841ac2d2fa3bc7c7c62e1a634660ca15eacf306932c5462a6005e08aaacdb4fa24818b911629ce0294f3d5026c1f53a640ce2ecf177011235d0b119c3f957e07f1b41189a265c89d11c904ea69bf9c3ca62ad86a17d9ca1adac34d2da70756712e90dad7a4a66fd6bebd694088b53e77b6ac51a9396af2356a77c5e67a6c53abec0a6f8a0526332dd61942a4cad5bf7a9a8ab88aa5e5fdd9bec59f9a216d90162e1d64b5742ecff9ec71695a1ce4067978eecdfbe9e02a47137370bf21b931963b21002dcd74eb6d227419154b8ef69a70eaf013eeda25eae5e2137cb7e56337b32615bea5c5745d20da9013f264ee084af59f841b34cd65228fd7226611f2827be14ed1b0f5c84c56f3af448be1e6facb0a9dbfe1dd1c8160d56824dc12f5177861c7496cf06868e4106d7e14eedad125f216b6712818fe4ce48606fcb79d6f31a56606384d57d5429394dcde3da5c62259d35262b5c5e6f89c7694e03dff1adbd75687d829a46809679488135536786406d6dc269d4c0734f04f1478fbb1cf39facdc42bf87cb10450acd1c9fed1f5a10e56be65eeb4ccb19f4036de83023047dcc2de521a38ab690c6499c113c0751a8b09c73539c16030e9689d52bdbd0cd8c4e96cef17b8b7fbfed1f6bcbf61f9384aa415394febcc05b1522051710e4647fc027e8be625a2133c691e48a309e6f03fd81af657d13a0b243f5a37715b31d9af8d0e45d98122c0d1cc3f5371bcf25ab655a3ba0aafe770f0ea8d463f18220a522b0827f52685695ffb9d470335e896d8501313ed174b78bf98b3ca6bf0a0f1bd34a814c4153ecbc4db6f4835aa1302b2e41f15cf1d5604b730cc687f502f73dd15a84d490b298c8e9d77cf0d3e78199f63084760959e1ec7fe0158ce978e22e99714840f1f425ca2e3fadcd58b268eba294b89f5a6e799b71dc4a42b47376527678ec25558a98ce88fa1d0db7341bfcfcbb94e1c520e5c608e6417a4ac1dd03d6c6143097460de87e9c5a3d880b770a294140ccd9b4fd4ab73b3b4b31833a4350974b438378614420676ed08f09d0db01b8360f6d51a721ceb74d91bbadccd14c122612c3388942f7272515262179a08aa4d9ce0353c46ea7aa09c4609b7d4704106941a5687148bba795e1ffb2cf3e922ac1f3fcc448131cf3f9cb21b157d88fd6bdb633b8e216e4b3f3e6680d689f11124a8fdf78506f78682978c367354e234dc8dba6ac475e7ee6fb3c9878e62f9bf09bed28b7899423c99758fa49ef322b0851fbe24ebfd2314095c9ef755895b25439a5f7db90a99521d1e7947db30efe84c51b5983329e62f99dfac60327fea0790031d69e1f3fcbbd2b0598b9c6f2d318f26476e15627e26b0d6b27ad027a50a832a892f1abb7cf21d4bf9a6fcfa05a62fd4da6aad5ded61ddddf405196a55fc552a782d2ed840a065d0a925a1b626cc83f3d737e9d09f6af0f678609f301efd750755af7efdf383003f0d10df0322547aaa1cd2c0c19598951da7d35d1abeb3ad4b720f1dac250075b6fa5010573fb8efd79ce826e9c97cd73d78f088f4a13efca13751095d35d816235ac5ec0a324277032bc9034422e354cfb62c12218546c9896316b456f2473f0647aeb29aed0463389d1d98c92518d3081c49116005ed6d9011d45049c448fd8bcb6fdcdf94545df8683984a5c762654b391d629b68ab236b62d1c3a00e42eb673a2ffb48b0b36bba70dd2b1789431130b0bf3837e302d1dcc45c5bab0aac1204fa193ba45410380b4d3a958292b819245d14f8bac8c1917fc8f55059c341d6c367a511eee873a983b4bf2a3ed17923ad32d6439de64465637772e31287f89281863798f56d40659be0800f7dd3d79b10b9e5de726f296592329909670ac40937dba261d3d9a871dbdaf463fa7ed51fb5404a80e39c9a0a95e63eca79fa33cb856439389ee6e070a19c108643c7d400c7c775e792b0bf5973a1d152ed32d7856e48ef175e172d154887a8a492baaaeb615a6e1ced3beb51c93796f6c1b723576553ae0acd48f92dc65866abef77a16743997db9c71fe21be771f4bcd13734a8c72633aeca0bfff74fcb3876b1ab5cf66ef7d5a6a676f797d3b2ee3babf1d55ed85d3b2ae1b2749c032fb0fcec72bdcc433c39dd75e5b58069f4fa4e00ef9c57ca4efeb679dfcdb0e3244fd7fd94fa765dd8fda5434a1d97e4f518d49cbe2e7aafa79267be9cdfcddf5b379299aba352959f8bdecbdc79b7bbf7c7bb3fdfb230b54a2ec27200e923c92453503b842d6b59d66cec1fb54ab6b72114fb5b28af8b339425a994f2503a36896dfaf82505cc7621ce7b99658a6b997d0eb69549d9f7bd135bd68e58b3b3d0b6fc62cb9a14135b695f7de9f75866297dc251dc7648f7f73f5d44bfb189b8dddd9dda9724fa729571cfbd7beeee491c4f8b85116be6951bf1d453a5d0b55472e4fae34de2deea3185238a084ae26e085a0d8b02fd79eccfc3f982d18e76958523a02240ae6ee0b6dbc6e3658c87de997d12e028ad8aa82b1c7fb6cb8ff8228791fc9c1cdf2d6d8cf3321fc14ec04fea66521f078efbdee3f81aeaf891d2211e5248cf04627300ed6b0f6bdf56ac4039394d71266772da9a356ace9c3143864cd69831565bb64c59b102e5e43465324da64953523ac104139628519204638c43115ca445d2470810fc187e8b35d00fe06b6323aecd0487a00892c867cf2417381bdfb81096443e42a08b509268680ab94c336b9a914478d31d379bfe5ce342f8a9cfa6ef4c2e347bfa3e85be4349229c4d71a8f3de4b99d6f94ee31d7aa6af8b5d8ed7d1b2c739f04bb13e0f7d25cf9440ead7d7d1b3e994da5f28a54ff534ce773ae7b117a4c726df0f0e7e9806f1fbe3e8cf458cb5f7327bb77ab5de2d7313d7b6619023c0515a1d7121efe90dcceb91eb73817af4a94dffa547579edea6672e6e3a08c8082af777e40757bc53eb48c2c65ff147edc45f7bb0be17e4a005506e22225025bb3e09bb86586afcd3d34d7ef6fd9824badbb669201dc0ad7abf69ece9b1674cb26baddb05e201dc9eabffd551c9c63e5b126ddb5699cf86c02f7fc7f8af27a1c0b93d97f52bcd37942ee2f00ea15dbcafe0ed019c7b0e000fe0bb726f38d2edddafc3b3da1b5ec18fc318e5a143d208080bb83dbd1fb5c2dddb755dd775f75eaeebbadbddaeab33ee76dde5e4b66db7ebae54aab5c9acca29a59cdf757a84edae0b9dd039b76ddb6ed7dd6dab72e3b83be6a36e0451f472c582e50ea516fb47abb848ab1938366a6b9b61cbbbc3964584d9f793b90817e2efb3fab571feac9e3a838da9576aeb66b9ed726fa7784526c6e9c938c40fe7f85c39477ef0f1ec9bde6f3bdfd41fd6f9808800a5e8e6429ede42a4ba9dcc36738a0daff4f164432892f77263f19b1a96419e6fead92b16f3f14dbdf33c3f32d2f7d5c551a9aed9536fb0373dd6bdbd72527e309481cfc4beeb651bf77c5617b1a16ce328f97472d1a56fe4a31c7a6175d1f33104785dfcf14d1d04fbc13ff473f2910d83f4641b4e282b59407f11007065cb9c645db4e19c62c322cffbedc73905cc36248239f8a65602802418c8373509f9c88623d44c70912e01ba027a930c3030b5d28e137000bea9a1b8487f837c64c314c40db0074c216d0a7df4779aae80738f730aeef931978e8436f87c88b988ef219eef878f259f7ca423c88faf271ff5841ce8f80048073f40be00007d2b74908bf806e1e7f3f2277dbcc701f8bc2ccb49de6fa1ccfb9c2d807ce4e120200fbe39c585f2910df1feac96b522cad838d90bab6be3a16f4e11ca453ca33cc4f7379fe81be5232f9c4f2ed6a2a6eca9736c1ce483b2c7a27c64c3f9f461cff3ba7cefbd2e6eb337ea0d0e85e1f01c311d3ab2f9c18ed9ec0339a55c5b28a54f372a39256d3f43d9f6397be72967cb8ca4fbcfc52bb95bef379d7400e586dd0db65724833d36996d5b6d682483edf5f3e873ba0bc79fb16773494625db86b231ec579bc1a65af6499ffab216b5c496fbb5a996b5a82eb675b18636c97e8eac1151266733813fafdbbfbafe74004309e0323b8ab69d8816ec9752ddf5939c86b2ed9fb3e9d109e1092ed207c12631919ef084e65f7e1550ca70e3a8d4512ff67ece106807d0e5d2b15d86e30cd39ce459eff364cedbdfb8017e4cfadc07f5cbf5f77efaf3eccbbc05491f294baf69d57cdf7d1bbfc077bf7428632a298fddb8db79f8cbae1778e30607862327474c878e6cb6a3c7eb10003b1f978106df75218efbc73df9e8761f11b9bbf0473632dbb46953e41e4fdd7803f1e3348f8f1fdfa6710f8fb8810619ec0020d4dfd3c36d3db6d29e1e9fb2a7a703977db9c61e93a4fc15e692e75d15419c01222a0832fb67cb5a10426c22b0f68f2d6b4358ed4aa368a896a5965a6aa9a569aca5340dd572217a75110896ba58b508404080a3acb9bb13b9aa56abeac90ad3f660cb9a1058423c6d71cb5a106bb610521bef4f6be317de91b01b2fbfc78774bc0b71372fed4bbb727264fa755dd7510976ddf7e1f8d98121930fa46946f052ad3ddf8ef2ea7b6b434b6d38564aada59b7dca86e65f615b6ae948d5d8d1777e3a522dcefbd1c30f36a433431a8e124be7836c33e7b7e1927b414bd350ab471c618e5c84bea568a6106badd61e73502dfb99fee4be28165a5d2b1ca19ddde7e40871507a63c9a537cc09c710c78d90a6a94fd65a6badb539a4692690ede96fda7e1185e91b389a7bfbb582e04534cd8759a25813699a2c5114459aa6880e2da13cf563348d28ce5c9caf43bff32c916662a2199a86a6a15a340d0ed7314505c22116fb99b9bb3b9d651ce642561508876a95ea17f5b19444cf66cf9768b6dcb20675b5e57355f6ac01d1b4e78fdcf7c409b767524e09640338fedeef42236c4774a17dbf3a827ae4be43c23df732042592a64a6f3b241989fc2d23a13fff0b6b0d61341fc90956fbd3e624fa3017a23f5f3cef3bcffb8e8aca0a4b92d9f275bacb5926dd7b45bad0b3dd7f437c6fdbb99fb9c8e52e0ef7b9396673dbe6c2a03520f76e2b13ee6f112ebca18ccb479c6f6ba3ecdf2a5137d0e770b8dadcdf1a7dfb03d5b6bfe16cddd6c1703a9c9b0a62b910dd32270f99569b0bc7efd6fb7e7383c97dfb37b2915ce4de2bb77411fbfef417dbf901818892254d52609af214e5bb3ece61f317f5753776df9fc97dceb7ff26960bd56c64d67ea8dd5ce4825f918d7a9d925ad7b47a5599f2016acb5a0f637aa8b2ab13285c9b366dd66c7f2272bb162ceb102c1dcdf61f97760f4ddbff89cd7623f2e73be1b6943e54c909b7bbcd3db513c9f61e72db943f7fc2c07bb276ae7cabba03399807529a10a6cdc659898d4327af36fbca073dded21ff57b68199e5f0ffddfa34708f5df8308fde043207abc0cc77ad41e3ab1f04b1554b024152ba2bcb2658a45318abdcc0890463a51ae0bb1904a4ec223c849d8564145159f638d2063abf3bb15d8aff81c8b4cc2ec672f33093b7e470826ff949c646dc8491667e160430d5938d54083163748f7e123d320a50454a4f80c4ea6ea471650ba0ffbf6addea26ac76730339a9492006168b31f4880b39f99843034991f10022d480d58440218275537f0f8d9f3d060709e879ee160d72f6daecd0eed248dc81dbfe3f10e4d250c4d4a0250e1f13ba8ecf8998f2410e677682a3c2210829492003cc21b763c952aa49404a062444a49801da14ccaec67dfc6c63cf4ec77e85908267361b513fb1d58da198cfd6a3f87b222b2cd76fd93d1dbf997b62b63133871bd077a6cfb733a11c196d7f0678c35dda2d1b0c676db77854fb0abc853a0d16834fb3a44dc477d223323b28d8d671a8cb5386cec0a9fe4d8f604d73de882a33c3e831732cc204399186488e18ba6189ac080a10986305e80e105332ebce082184c2e30e5246cf318519cc9a3d88c8216d044692127657c06c7340bd3d34c775dd179aceb8e6969a413429946ce42b664d0622f298fb3e0b49c9471194bb1d7797b060296684b46b12f721dac6b2ccc4ecbd8a66085366da05161b6c1861950d8d84a23823342a005822d47432a85ad355c05521f58430d5f81d4133562e06aa43c100336ce022925363068810b3048c3054c69bc604d9417acb1f90527a8b1066317e4a46c711aaecf2ecd819b972e6e4d778d73dfe04c1758db829c84b385c1065c9fed962bbc925b7057c06ca43490df6e99915258dad98575dd32e7f08aa43c210b7252b63929631b831524e5c9af66056aa8b00215ac01c31bbdf04650548086ddca001b18f0362e205d44c3db54000d2d9a05fe7b54e0c31ea1a43fb472923d232759cc0205ce48d3820969cc8852821968982620e98ffc92fe40930217249082325ef8644b76fcec6558864309ec0867a1741f2e970be1327292252327590c0304c838d314813328888104148c21c303a4fbc82fddc71862cce00031cc48698099306818210c306a6000185fd8b0802fca4c5140192f46a0c48b2ea824a08b13dc808013d49c0e20290fe5a99900872492f2982087241370a18301b8d8a20a922dc890e0089912ec60a4045af050002db2782a9245560f44b2b0f001098b2b7e18225d0cafb0c24261399a5600414410578488b212d3d583ec635ac410f6b1cb155abdb4b17f33898b5c4ee262953792b8583f3f969e8b888bf57318640338b3c84778661181e509b847daa6cdb67f84cc45f0cc9a581c1185c511565c215584155255606131a20a24b0b2646b8ff86a7051918f6eb25c4d8184540d381f7531b3f1e3e029e4141629a6a28022892c502431e6092590b84f28e18418a025c0515e0de990479ccc540d60ddae2b464b2e62df158eaeb7a1554eca3fdfa56b189cbc25090fc2e423bb05cc124a80c94776892f390967259af8c2449516aaaa26bce4249ca996e8c00b972e3929632e4cfc74e9c2252765cc0490172ef9e8a60b5513df9d01a832c02472640ae926101da101260d47c9c5ae9ffde64e2154f9e8c64b133909e72a2e1d34f185899c9431123924529efa92a80322457d44fad4072285a414698844f2d42e57fecc3dde2b8998c049782e919332ee126609305bbc7860b5258c962aca23e6d69f5aac94f8e2123de8eb111b1209a201ac5b9c4a88c945ece770cc8f13ce2472120693434cc37c3a74acc8330637ae7141b3658dcbcf96352e519b880bd95dff48123a54d5d068b42b46b54af2c831170b242379e4bec20bc923f7d863d39f4ae423bac19619ebf81cfa4a8c0b9badc196352a339b4b95edd4a963225ba2327d22d3c79aa34bf9a955c66fb5efa598c662635aca5e17f3c774900f4486985319091124f21056b224b1fc9d452227d9c7e1a8c2c63fb34c2c5336768242f1053d05fd008ee2ae37378f6f6ebec8a16c8c839e408bb5d537373fd68d83c3e69ddf7600a9ee1c7ed207f64875db10c7fe9c19c97503f637b42ce7717cce37a166ef6c596b628a7d5d283fd012201d123de7cfd13623c1be1f2fd6370bcee320995bae8b593e9f8b29d68735ed410178e3c3cdd7b7f928e79b89251f49e558943dc2da27827c005f145db48fc371ee6f1e9191e0178dc8491c11172a221fe530c4633eabed7e72f71756176995d207052570d3ef2410fad3888cc4ed22425991fcf4732829a51308293eab7d06f181fa4aec5fb1cf3199b18ee9ab6ca372127e1b32718d4bdb85e9148fafe424fc396492dffe242227616bb3ae547c56fb967428f6ddc7f2db70d6a8a030cdfab0e6f6cc45ece330874cb6d436912d59bbfe91fa236c41b3b7ffbe9f511909fd2f1c71759ac5949b9d572af519e552aa45946a7b39bd7d150a5581ae50976075ae20065886a1dab2f6456b6f59fb5266d620a0a67489d6aacaec9b2d6b5557bb56455535a5a5caf6a3a5cd05cbd98ec82389ec960db1db7fe3426b61dbfbab412fa007023501facf5bb70fc93c8c19e1b7fafb5c8cb9582b8db65ddf4ecd8b43eefbdd9eb761e15e8a73595db57b8abda7f4066dfaa6b0c7ed95e81eb9f7be53a2bbf33617c445a0312067eff6e4f442ee6efaeb026a27773f99edab9da4b0bde7f413babdedfd7571ebac68b4cde94ba3ed4ddffd4387ae91bd4c8e65e3fe93443226f37dcf876d39cebacd761b4b1fefaaae9bfede76a48f2d22bb33dc913e1ee4a27fa7f78f2b70bcdd7cfaf631ddec36b739e76c739e73dd7bd76dd37a0145b5767d92e1d844d4a25a55d48698aa597112291a9aa6a6a5a996c416d5a25a5ab82ca022c0ed81b4803a404180e38c7dcee7bc0e3438723cfdb94676adec1817ea1e470eec6b3ef26ecd431cf6f6efbdcf7d3afefe876327e771fc38db76ecd9361cbde7381d1d9d7abf3ed2fd1a5a3dd6d9cb8c64ff5bda9e92ef998ea635176948cbb8e8e1c08123680d98f3f5bb2e27279cdbd373e7a836775716def8aa618fb7afd356eb6c998ddc8761acc70aa4051c41a70ac79f8df3e07f2106bf778c3d1fe5d70b17d1ac713cd6399f8b1f0dc7d9867d125a5354ec211374100c5f5f445f300b1ba2f3301b8ebfeda8e4f53daf8eb36d4358ce17b98e7d986b79ff3dd6361f61dc853223dc778fc47df74bfb8af9f3b7bfaf7adcc11ac77ff5084966d55c04847deda7759ac0b685ed1b8e4db6cd099bc070be623ba630e2502a0e6c00c759b3db47abaaa8a4a4a29e9eaa5029540a6ddada62a3a585a64c991a16d6555515959454d4d3531597e252bc696b8b8d96169a32d686b34c6de642937b202be0c855e1aa38f7e442e0d3e7a05ce8f5f4392b2e14c549f9d08ea7cf6d71aeca85acb8312e247bfadc1587e5423a4fffdeb7d6eb3acf52ab54b74c532c2b058d8bb4aaa96bea194f5b9a65b2525ca48fa3ed14eba46d156d9fb485d2d68ab65156ca4e511e5ab35476ca46d9a91b3b65a5ac0e930b8df639269db1fb6dcb458ec907d07ef73a9e4917724cb4b9e51c937352c66c66e88666d35243e96f6c249113a5592e44b35cb45496ca5a55b9500efbf629968b54774fb35cc866bde895d534cb458a44b13ab9eda5575859b6b3d862706ab9d06d92443852aaf8539453f995a37136be459ba8942af48946512a498463775c0ca0ff5d402d0c3261a009238c23aa6a5848dd1a16524e566a5838cd1c2cb91ba08b332b5844050d415f5a3b6b5830edda021a3a0c7c7a16f5c349a3dffd5e6e8519acac8dadadf2256baed7ebf57aab9dd03b34b73b739ab5da61ae2d9c5b538d9a352019e7a91fc6983d4edafdda69b1c3a8daf527533e723dc2c0dd645ba7ae5f7fb5cb0b5fa112359cf6a68a4fd87604f3169e3102c7993545d6aeb0b287a851a5bb3e91aa242ed465b1eb2b39c1ae3fcd58b9c806f4e6a472201880f42f1d22522489fcc798f8b3a590696602a93f694276f8ba726d210ec71f3353881751858cb1ebbbf628e953a79a1f57388aff0a69369c520ebd201a45996d8cb070415580726fdc1a1c7e21d32b1c63e36fe12882e1f853db4cb5a16a63d5e6ca6ad74b27d977e9fb8552896e1cd2bba51ac923ad607369924736491ee9ffb968b9ac8dc071aa715aad6d31edfa738d0fb9118dc9df3fc7d992726a8f5bd6b0ac6c9d1080b286e5b41d8b861a16d38e6d55b165cbda14517b0cb2eb1455f608ee3973af428bc27adc6546eccfa5cf6b54646d599b226b0aaa8d5fdf77b511388a9bca5a154f43d4883e08b4d61a7e77cbedefb63d2ce643f7b77fdfd95617b9589fe621419840b9e70701c185ec66b939e75f0bfe136c848282762af5f7e2a12cc0192c1d6389a574f7395f4ae91ece2c5f414fe058eb9cd405c614b425352cab3b9d56bb71f7ebe494f73a4d9973765d54b54229a59e67a9acb516e3acefe3388efbbe8d8bfc755dd7e56ccbb83e8c3176b9ae18af2fe79c5fafda9519afd70b046b5756b5ab346ee7f2170bae685bd6a4b032b4652d0aad8d5ff2af5700ac4541668f5f83c114ef9cd86e345f7e10bb85a00dafd89f3a0853d31bd970c779fc03c179fc7d0c326100e957bda38348008e4182b87b6d02120314e6fbc25550068dac7966bbd33403f40538cea63d2ad15d5fbefb9979668c33f30c972a141403388a206c2db0cf19fe7d5bb775e307acd8b4eb2cedbe7694d6bdc56d928edaabc5dabc7da79bdea8fb4d2fe0d5c58a28cd493a7bb56cfa553bf1dd81a57da5690046f73b2da3f7a3bcda307a611add67ba51b8f04b9ba512b828befaf241d8cc797e5c4cb3ebff0405b9d2f446f7cafd2d518c19b3062881ac00679248a604bc4f7feefbfe600dc7d8f62dc4740efdfc70027c62b713dac6cc9e396c10e6b37eb1988d7d92886e0973ebb9c8cc61d7a7da89ef273c7607969ee4d8f49d706ddab4b195e6a68f42a561559b072cbdcc24cc9fef5b14dc753da514767fdbb6bad5eae0cc1d080b1cc59f2049c4fdac2ff6d46a7d73f73bf7fcd86ff6b9cce51acaea2f6da4397fec21449440e68f3baa3f28615b76bfbe33f1eeca9a5f5e6e47fa6c9bbd57ec09b29bb538a410bfe36b6ff7b7c7d4e3aaacbb6a99fdedeb6452dff7116cd372dbdfd76df58ef4d9dccbbc50d43d4175c666dfcf07619228ffec7e736baf75ab5d2e5babb4dea7e7acc6661e7e0745d49834dbfb3eff700204bd2f3ba0d2c097f9e8fa3bf7d51f77daead82ff2a3ee6dd697bb1bf7f6feacefdfe9edafcecfe9fbdee7eebdef5d7bf937ddbdebfa786edbf6485ce8bf415142ba9ff3ff1803e6f979168b6dddcfffe12eb75dfc713636abb7ded8ec9f009df4d8575e7fa5c91abdbee7c7a9f72f50cb94bcfe755f19ca12a3d7cf4c41ad02f85830205dcc21942546e0cfec7f5d1a757afcc2d07bcf3dbf9e87c1ba3b5d77d01aef7bbff7f3ffb6cafdccfe557a2d0a2b1bdcb20605d5c6fe79cc4519c39b8fdf96b1ed5560100b402547ba1eba9c471658ee602969bd2ed211047dfb21e667d69d0f7fceeb2e0491793fb7bd3f2ee49bf7d4eb3acfebbcce9b92a86e32fc7333b1ef9f93b9f187d508feedc96b7f8939d7f7957c63efbbcf7fe622fe4b7b7ad37f8c01f1df4fd7a357de5e7e9288e32a955523db738fb47d0d47285fb5b908c65daef5b1add4529713baeb7363de4e5d36fca4cffc4ffa383ed38d9956b3d2799f60da9452f167df1e637b661b561ab0dca9d1f98db5c89da87242ca2e722149eba54377da5cebf4af19c9d29ebae62217fdc56c51316b36a55fca88d932375be9f41eae710321dd02cb2ac64a90f9b9e3ce29a55dd7751ded3a988b414f200a75dbcf5ad84be8c7c5faf25f624cccd39cbbfecc56ead405ba58ab0c226b566a86dca8fe111772d0b6b1c3ecf1ab5f3f4912dce5234abd7c4429ce479466bc6d5c7e24b97328733d931cc402902eed51696357e7d1a58d75936f7be11c832949fd0ffb7da2b6bdd6c07cb1a75fdac5bb74a1944efaa4c7eec0d2ddf32f165de817b098569a26e8f2055017d8bc617bfa056bd33bdee77ebc746f5eaf745456570da7d432f9db1f3901de97db6545c227a3127d3752e48677055fdadcc9c0f8e5e3a01ec0b1eeca84993d6bd8b2b685cdde9ed2bff47195d6c68fe3ea9edea7f46fa6d8d3f9257e9c70ecc01275590abed2ddafc79c4fc9ad808293d9667faf74f78dc7292022bd2ac80c084686f3f6f38391e184b58c04c80d7f0952011846963ff63a1f46e7eb62ffc2b139e76c6dce16678cb1cd365b6bede36cbd4c11e369cb9a99293355b6179a1a8b93858333b6cab8cbd4d660cb5a97a68d7f83dd54fb22b5ede32f4fdb7e96b2edbbd850d176065bd69ab0b263574e4ca8d9221a34b52f5b1bcb28282d5adbd2aaad9da9b2367efba5ca6ae3cf531bbfc501bb954f63fc1e95876cfb3106f595181157a28a30c28b741f477491e222290f16aa2c4d4817916042d21f534b24b145521e25b4685142bac8c316aa2a2e5c7838420a4b961f9000337dca9503ebd20587892e436cce63ea60e4a4d585f3765b2d613739385ebcb876c8593e243e15745e1a61397d4ae743269da598c69ec467b502965cef3e6424dff9259e5f7e25745c3ada6aba16595db0776b5d39195655758343e501972e60c48409e3250c145658803dac26d0e36f70aa5cd92a1f59d88d0d9580b09d93f3312ddd0358cc9f90d3ab0be775b434b25ac26e7270c488717db9e9dec5e423d71739d6d13257a8048412d32ac87eba4c014b46b247802c64922ded9856c092512cacee817d9a91dcf390b9f3c7ea7477e96c9996ee8195696984a54b31abac2ed74b23aced0e02421de18804172b4c1f49a295489ffa405bee85015d01ceb0deb6cc0f98dde0389820128459920990463a0a582241152e545c3eef522527611d7212b631adf3ee057bd5e75f72093dfeffc3303ab8870ee33ae4e0841039e0f0441038388d0142d21f3fb2d30d5040f90d4e258a0f7e4c8192fe90e2ff6325488944f645fe2309b0c7cf5c421835324f20043541628045b2843051be00a1b5d63d5ee6231d5991bf0e424a22f4837fa8306af43f54086a663eaa40987f0da5ff5f66134230a343203e94edd0e37b7c91b761a4a3230b7bfcebab305162efc29291ce13a11f40219d90244aec75429912ae8d91ec9ba064239d5f3292329d708afc61d404fe1e8e75bb7b38e7f436f0fc9f17631fbb41412e607777f79d9e0f8c92b57b827e071673ecee2478232c71f1e8494a915b6badb5d6a7136d322961c0c5265068d4cc336e45e4c80ee0085e38ceb6f759793e9a735a2f66f3519d765aaf42d1683423b46a5a66b4d00819414b8d04729565b1f5e1c393d9291380df0c7b5b5a4ea0d6534c8b494b4a9bfb094d2c52f4979edda6d068b422d068010d19d0a04234021a3b481fb94751ab055a6a481eb9399e3d33cff65c25a536d3b96d8865e7b6e196e88564382bcee30e05e55330169ee246bc9f4ebe23d2cf0a8fd70eb97ebc9e8fd9a69d52f9c80b4f00facc50f4846ba7f62b1fe3ad61e546bc87e2a27b3e6545a1fa6ccc7d77737ee5629a315b5e6d5933c3873464368f2d6b66d036fe3e97669a3fb5008d3389ed3fb148b712f233ac7462dc6fe68f227948e83fa19b89fd8c94df4ece1bbf90fb201d402617b0ff5bda198e998100fec044cff3685704841446d8534f2bd1ed791b80ed26b0ddbdd5f94bb3c6a6d9b26730d93362c860db4f73c6a6d1923cd2676357d46bcf252490d7e77aeb0a7d0ae509a758a1e16e755535c6cc1ebfed55533e45737733cdd2c52d33aa064a1771b76409a766842556b5ed242c51c3850e5506e8fdfc51a281ca5ce8563e46cb6ace3d4e2d2b9f34b5340a75e70f77958e4a3292b5f6b367ea0e2561560922c58726564f0f0dcdd0dc02bb7704c31915f533f18e985f31900235e5d12268115e28a5cfe7539268066d012671d15fa96a2ef4e69c33045d7a4a398ffb7bd46ef2cd4d697b3fe73767c6a15bb9e86fe4542e84b587dcc87dbb4f8d0ee5504d5c68eec7e1940b4df7749369bd30888b6eed2cd2b6ce992409c544927c01bd1fe5d591efb116c5fc9ee7fd8b5380dedf1dd145ff3078c10f9ccd84c6ebc16658a0f75d6ce66fa3820459017aef85452e42d9f6c799883dabe9ecf909fa71d1843ddf5a1be4a2f778fa0407aa021ce7cf2817921a251a6b59b027d6533799f9d4389c9eae7b84fdcf1e61890b49179241c41fe95947326c596accd4d04c810ad22148871a1a5a2d055653413904e11054058a4a5a535881f3524bc194329acaa8613f399dc97232d85032d46c4a51b0b5291952bbfed46345e228a594ba4615b6529d61a5563018e953ca29ba65ad0c2c9bb3e252ce329c8ef02d6b655839ad428e81e5639469e26063bad96f62ac2579e49a2bc616c814932279e4185434d8755a33341a2d8b917c9b8f26077bf470b4ef36dcdcbe2f06bccfc43ecd48740d9540a1fa371bf1df68341acde650060b4b8951b5b777a92d6b669a6c186cb4dca37fe6be0ea003c1796c102d37b2db7eec8d4017b228a8b2ed1ff1a16a44b7fd98b51f34b3f63d23b9e1f6b022efffb9906ff14f6efb757ff5eb827800ef57ed142b5502c980f49164fd5bad24b25512552a89a84b22aff5c8927c77a79bc317c6188763936f672028c0b933d52f7757f8737bf5c35f6c02f1ffbeef8b4d21b319def63b0912e19a91e8c63fbb8ee7f7481f7fa035c0ed69917b7abc3edb9fd317e62edfb66cdba6e461918f1ede6aa5a5096c4d1318f6a7be54270a58eecc2fe809b41873e198c2fe0b8e31eb2485cd8d2bece984dbdcb67163fd5a819e00e757f965297db52bce34f92e8dd66ddbb6edb73265f6b66dffdb6f1fb4d5f6c664fc1932b15f5fd8c9acf4058fbbde538ee3386e72f3bfc97d94523ddf6afad16f7ebb8eb0c96d8ee3b86ddb40342ca5da7e2b18d097f6f69b06b29a77537d7bc0f3fd3fd8fc7009969503e36ad39f4d341a7403385429f79de6ae7451d226b00f89d79cbeb052466b8b6d6cbfea4274e24e02d938fab8bae4b63d92dc8ad89f219322f3392ebc2bec51896e2e8d335bd6d638eded83b6aca531b5a50bc9dfe8f66c48916c3849369e646cab4d60347db1820d5650c8c64e2551ec2deea4101d1248fd106c01942e521ffa55fa50243982520a906e7a933e4b9b7e5b248fac926324962423cbc82fce482934c217670c7153341acd8b9114b265b6954a81c1a80206141851604c491ec964fecc74d2bf014ba01580b2e6c5d31ec52dfa9549840f41ad54216c101b101cd4fda1f3c1eb013f7d3ce41d5c2478550175b891c30d0e384eb01b7050c919418e29311b74d4a043834cca6c861d32f088a147d3c3a05f085d0000d34e940c5ad060b2b0c1a4896d78daf0c1c68fad9e357cd4e0200640d8fcc0a0833402f002a035412ef0a005422c18521364051fa880080d215a209c21803403302304344529d865189141803322a0e0688c2162209921124611300af085913247bc40d285014e90544b6282037081802d124046490914a0c502b26040d6085834e00a0758f1002c12aa80001511984202574ba49840142540418131263c5101272c802f6085811032308051004c4068224403441bf88003414e1872228482071d085202f240009e74f0819f14808080835d1f87c067d7c722e881f243051fbbfe0f5e816749dcb40d360b1ab49041941d2600b810bea061f8a61e31f09061c70c3329321a746ad061436c4a8e11e450c17103cc0907879b1c6ee800567991c06577701ebeb99f640fd9872f88cab77fc0501e107606918fb60f62012cb28209f105d1bedeb007a80d38deda15185bd6b8300387a53976fd118888ef471275df105d6c15c5ba58b3ebf704a92f42ad9dc069d75aaf50fbf381da502fa4587ae5036aa3678b3d3f364a04da026908c406387f02bd001cc51e203640a02d10280d50ea88202a178b67a9486c53d7fb6c46a2405be07c117ce886d579b65742895a6dcb176c59db62cdc69e91eaa661934fa422901ae0267d828c00fdcca7e59b5abebb059979b5658d8c947de9508fbbf291e8a2ddf26d81f948144ba02575d8b2a605995d14debac45767be5214a108680d90fb1da02cc051dc3bd37ead61b5e18e0b855a8cd9b2a6c5963db5d8b2966566772085dc90407a7e2491c51bef0f943e1f13f9888614480d900958133954312e3a5d66db66a9171e55559f8f6c28f6c400b4f38bf82504b353c36860258af1e144cf131c8cf9812200510449217415640a222a40a86200584556185d21021643b2886451002d8e94c00064926c8180cb8512132ca036c2091cd00509d38b08dc324bbe28010c13c2b080190c88318ed104051b387302192894a1948227685230230469a09cb182160d8d1654c0b48217d434b14086164871410d6ba6bc804a1a4e30c8814d9518eca0c6d31a3e6c41b111441b56661b2268512c18d18254942c4c532e28f1c216189868a28aa18b0c5533809162458313358cb1218a295723a0820ad60d573865e1a0450e6474e0a24a8d045dec508607309eccf430860f677e28030a0d106982d012420556d40cd10222d65c49238a4d116a18b175c46c438a86a5852c4c48bc30d594840c4a48d152c396294b5061c2a9891ca8aa70d9a1cb93171faaa0be0401c64a1822aca2c418e184d41359c64c41a144145ba460e28a6a8a2e5454550106cbca0a27ae1883451459575950a1055609ae2093b585165c90e9e1221fd1508c02e93bad76e36ee7e12fbb5ee08d1b1c188e9cd90e1e3d5e8700d8c940830d441e1f3f7a7c3800f2d3410080823c101a0af201911010043080108ab6110144381a8244a448018c1c416280a42407404002942860010c18a1010e78000910888004964ca0040a9850010b5c000319189934d1c00638708213143aa0e481271f480104211001141556589ab4c9c26c6146994cd385f9c2846136cd18a60c73862965d2306b9836cc29730493cabc613a4d1c660e538759659260ee3079984fb387e9c3fc61424d206610538869650e3189985766d42c621a21e71153cae5c40719fb0b03f2024a3046a9ad3e04969f9535b140b3e79c73ca39017caf5b05a4eca854ecd8e992562a9a110000001a5315000020140c088542a1509806622ee61e14800d86963e6644950844511aa3389261209a310600838c013023403052c200e2bc81b72899f9e7cd6fd94238a6202ef7316b79e56720c2df59f15d4193a3422d7f5c7fd6e72626066d5eae68c65d70846f165ad570db2644f984d2952ba1f4e0e586ca8c371cdb51c4f6f77d77b6616085a60f624cdc9e0dc6c06f929dc35e5b3213203a3748268a061ebb5924a00487d941f763060fc7d3ba613e0a555fbdfda1f2bd8633d15304cf0398122568d56bd3afce8293915f1dd891b9ec8eb639b13be4a9e945db66fa17a9b3bf4a9e6e04f2513a4d8b368716b6dc3fe600652f29387dbfab43bdd8c698d5a0cb29b303139af69d500d30ed69d5a0f3cc21a78306b43c8ee9eb42301d35d3f111b422b84340c03e52ce0800c03c5d1aadd1103ca840326ad89d14edae928aeddf06655fd8054580bf7fab038a354b85f296a9918481b5f28945d9dffa1c53f85bee953d5863a4601803ee49643661d45e041ff98531af7f5613b878f8d5d52e46a3e2865da20efa1102ca37fa4a4e02cec7d743f9475a8dcaf0e14e6fc3f4db09727d561b4b742c5a77bbacda1dc07866b5a3a32988de12b71c0b9b48dea7830b802e65f00dfc029ad1f82f5f7feda24371e01287d1db568c0255e39d00983baff1e7269ddd2618c5bb79c63a748679a82debd2c32c79b9ac3f2f755277b8fc2ef4a11311a5c8c0e2a0560a138217524d607c503ee1a4d9efd70ebc87c96655eb7040837e8ab04919e28a6c623b96b0b54313a53227d26357a8ceeaf5450a9699dc13a7d5bc691205c0716e5b6d42ba580d86ab6232670c5c8d1a06b48a0d346af5be24a4a5440b3e93278e1f1e152bdd4b198efd6f8cf45f53ad1aa69852b31c8d4881c38146c0b028b21603bf50a8f0cab564026c09f369ab3b91f7c962e517675f7c23f9f3c44b7c1574b2e6799a58d462330eab8c25dc34d6a9b2698575f9c379eccea95d9b3ee5c14cc0d3cdc078604a30c0aa075d0437c5c5d3c50504ef991ea190c5ee833435e538c176c606662cd193aacf527ee6cf4fe6a2852ff3d025d218a75801f149a5484cc8d703f25ccad9a19947fc4b56dc33cc3a60a2d88f42a0375cb49e7f4a18c5f6ddcde3c3b479fb9832bbde149554f6f0d7f1ebe1f3a32f3d722c90bfbfc9f25d95ee9fae61af2f68cf847731a078558e5f9858d84fc4628356dab389a8d38cb6d26c8fcf6311a43a8de227c46dba0d34b1daec67cb564618ecf124c93eff025b4955a2d3f738915c805796a65e3a6e0a7674ea6a89bcadd5a89b3be58fcd3d69de8f59370814fb7ea02cf7f1b00694e9b31fd00fa807b07d10fa4e9f0447df7e93033f2e0c3c36365993e1da49fba9173ab4eec947c54e6f0f2c184d855e9725ebf48be60afa19053011a43a0353fabd99c4b43676c5610a55ea1bfa35ba5325a750f315f2f13d06b113cc67a18a0d547a5e85b02a76b01b1da7c35eb0871af840d7b91f2c60b1ad80e5351a6a526f49115c3726f0ede861559fc900d65b5c675bd0e548220760d68d41e127c26a11e1aebe2c9628fb2735676dcd6e423d1303d46a2bcee761964ca712c3ef1b58f8b12b74fe0bbec74da5bfeef9416f90c34d028ddbef4c31a6a5a39e36fc311c9eebc68dc756398c1eac625070c1acfa8dfbd737ca4d600064b03ade029a51040f6d7efb1ad94241d662dca5776f80f9518c19b154c8b064b1b87fba8981f0bb3ba40fae8dd4c0c74c2c4c2baccd0d9c97446dc773b4d53cc9a60d72383a78acc78d4e598702bccc80dbe41e740e60ade3c209b57cfcee2a097d661a57f9788353118e2e6944f81a68a750c34635025b81047c3433d011b8ff9a7c262bc1f383761486ba3ad7cee4ffbac6ce04e405e454e9695045dcf28706c5fbfca4e6948cf41cb353b9d15b983130a1099f708e693f8e1add347df153023559984cee54ec537866262c8088731f39283370ef6c9cc245d2e4962e3f09646518c53bc3b61188f5c044e45e91260fcf9b7bcf6a06df0c35902601bbbcba0a0da24986afbd612de7f7720a8f1cd5145cc91358ddb0bef95df19f6dbeb02135836a1f405dcf40b85aa04e6ed39e907429dec5e1cdea3472e0b7ce4938b47012a8d0755857b8e80ad7632944e3f322ab2a7fb2d56160a9f3e1358f8584c399bdd8b103afc04ef958704f4eb73bc1ff53b89a2782dcf13efebe0ca17bf3957df2466148761880605472c82f24f3dc662575c9cd8dca3e65f68a713321d67177de77e55a965644712ee600d17a760e724c5da0da5d41a5a799449b9370c1281ff496e14dde1f5069be1799405be2554b72d4bbb836a9e5b06d31288b464ebc5a6c76569269ff06e52a409b6dca59a37fc7a102e8d9c359d447614a880409e8c5fbc5bc7c3c46536103e650ea0e0856a5aac4a5e1147c4fa46e9d7eec862e76ba00664cd79aa1944130162196065f914d019bcd1b55a00d5cc12efc0b9f1fbcbf48790ecd4c5ac4ebe89ad469c4bfa4479794b8114f1cd04db45ac61aece26bd43c51e1156b4c3c499faecec5349a97c9304edb9dfb44140dce5b07cbdfa8abe5fa7869ff93e38e09b24467a9970d43c77e62aa7d2020c6e613183d3ef7a630ef56446851267735e272af9fee95ec17c39b3e589b41ae20461847f342c8d22c4b3c28d6d1d723fc7d3c8d9ea04365266ca2f6dfaeeba9662e91c82d820bc157e222f70abe265dd3ffbb236990c8fe485e66860d81286a402ab310950e700f60b1f4ac262a068b292b3e3cd558bdf128a0643590b7aee983c538408edc7dccb1a8ca5791f9ba5212e63b6a3bf20e51151184497ada94a07f3c1c2b4d0aaa2422b4217cc9e1cf4eaa66a8bce14cd267bd6e60d13fc343f221852206855b9f71d7428936b37c0411d29eb12fe61da96cc93b5c4bfee701c875e6e79f4629f42ed817c38abfdeb87d6be6d82af3a342e3ed8b8b341776b23c5edc42fc1e9db4de12ce3d93c9159b9f6b6465e2de74a3d496662d0290d8d11efbc0a94a7da4b84d63838671389ae4aee356740782152d9565383ea3a22288649ef2b7045c9c5db760e1b05e9e112b6e4b50e0e9fdfa2cb9e79d7204a42778af7b0fbc061e16aa4ff9ec28dd39a248feb2d4dea6e84cb2b5c6d4ce19a453c1dc51d0f40c3065f7913f7323b0e79122243e8f6e481cc5058fbcaad7960870327cf722ff0947a260c8e5f71c7f72670f7883d64ec1a2fc45c88a2199f84e7c5f49be80d1c1392259b7580268f38fd5c41b06d0202454ba5e79d5f6a98bb412b6639ff57020da4b30d7a13a7b6d0d86685a55a629e10c940a8d2f58ba06403abafdb4e916c697dec40ad9b6c2c336a1676e88c0f00c65e445ba53614c3064658fe9c261781e36afccd7884808c97604bf4d485be8198d19e4ba3134092864bcaeb80f29cea2973c17e3d7873cdbfe54dd48ad7d311002e1c9365e0cd7bf4ed9d94620f6528c4c2cf3ffdc6dc82efc1a9a1cedcdfc230ca473f5fd7e83ff62834d36791115cba1e96a503af9d223eeca1c8ecc9f830348963098ab221fadf84c87c2a765a45570d4eb50d7f830a65ba0157e842a23a8f9abd0c9ff73348735d3ec253cac949a5eb40bb5ff23568191d0b6c02e2c8925eb976758de3e6fa4d73c2b409207bdcd096c1f76a6e06b30217eb22772b8ad39bcc819f97f91124240384ed84733141a4b54d5b96759bc4e90b3312c9b7771e8f535c8986d931a0a9b365b101920956af64c19f449ebcd678bcc7efc5c73a92197f5fa5629227d883c4dced4b2fbeb1ae49de71f6d6ff052c815d537f8b087b7c6b0cdd914b88cc104a10355ac69f383d3e3d0456c1cb22d3772a750b3c1626011eb3e36191baf8de2c459599cefecf5b8602917f15c8eb3596847b2f1f21b58b882995023a5e7cac2767dce1a8f9f16a5e8d95b8445b4e949ba84932f0b90cd15320fe3d237dfde415e6ab17d9f3a3afce9cca17b5ae3a2e706c0faecee3750f125cc104d5e95a16e6e481da1c7991cb9127801e43c3e0ee0280ed0e1f276d93da76783df2d360f54962d0ca421812e4269b2fe3b43f86debc6bbda8179326201011e62e6fc5fcf48ff268e83ec4cb3b1f44a6e0e4080bb7c3f1bea8720e7620f45f87cf35dd3e05e80f357f87f70db8b095b917b38df685a4bcf59a4686a3847828a571d019550116edcb2812f7e9a04f0d6267380542503804048a6ffd2b3b8f38c05db4ae9f462cc0da1a963ba847b0df8a0ec775f7817b3a7a24b86c7e57a0a4a10f19d9509415089557846623d1ddf938e1d72242743114aeb0e4c23afe0d7e56638747dea688a4431ae834284bda82f6cd161c620f6601ddff296d6c65c4a754e09f5ca02692d57705c944924b46aef351ee4bc2ca11bfae85e06c1dc4c59de534875e42a14c499a5a0a55f213adf2d4da62c1ecd55e0dafe9756133e86b3a038b7ecd0db0f32de7e3af94eeaa0d57653adf62e131b085c757585a7e0ba0be4a25a8a23df3de50d8dad786cef7142b2bdfb24d4b1fc205f8a450ed9e858d3f8240ae1972f3e906080f852a926bf25315031950d0f1f66720499f819ff31da02143f676a5318a04470038a9f3288bbadff3e00e028cbc9d1077b201bbe547be96ff810736205ce889171f2749ad182003d0ec8c8ee2a82b395a41b3e8c90b48a937605a0d90a3b8da2b575fc5496e2a24e04a4c2c8465d34c99ee11cd1c7d09b3b97dc65837547222c25eb5f8a9b87095da22870a2b1475c9306f7134a6a23e3893a42f69249cbdd3bbf97a0cd4bda7b64a8366971b6c215494d6fb841a4116a5b0f9c7c68cea28f1c973ff276566544fef557c389b13846d89d572094f8786e6d19858c4538697eed2dd0be18d2a14d581137ac04a21fef59a7697c6b37abdfd733a1c7f5c9aaa69d9e1e69b6b494ed95eb4f1a577683e94b9bb1631cb101284c2c4c9bc394824e1c3354a8215b9f512147e5bfdb71c723a7bdc60361c0aa41c676d00a2b7db66e93eaedc344839167dcbae76bb076d3d1beb383d8b5406233dcf0439af87f7e86bd73d42bd63bd86d47c2c4d8c7eb223f86c400b902ec0d55e3bb744eb2fbf8da782a49dced8137292f34623b034ed6c9da3588b1ec938cdd0b894407e1f811d5ad5d61b84ce5f9bdb8007cfb3520d857f9765f62d8ddda5f0973a9ac2cd0839190a434e5a62c7f67ac52560c47e2a2edbe56f6fa0660d8652691841966f69d88856ac048b93d1538e7ec7012eaf9d3af9cb716a1c13c9e20caf1c152ca5465a1be6df36b7b05d738c009f4dd60d720526d772375b058457cee3825413b021663aeb17fcc26b7759bd0f5e1f1022d7623bc47ef50c86ea07c5453cc44fb9452deb8c11ee8c535b52901513b031101c28efa7dd0fe62b3d53bb53f5a6a8a5ed680413c2e722bea9e935c6919fdbda6d5ed4bb8f9b891f8f045b60e77ad31dce4fd096011ec46a95f1ac1b7ffcf39184ddd4eb7bd82e49af45c054f568bc5f22626c512e397ddc93e023e7379d357ceb73a6447e5bea952f206553409643bc7bf357ff60860de76e6716306127e580df9e25ce2524115b0681bb4344bbeb76f49f7591f842b1ed698e26b480a78f6f0c5eee593ff2c5a8114018755abcfc5bbd4f11b3bbc5590882f8cb82c1e00f614db9e6b7773ffe1cb84d0141a6fcb85cd7101dddf7c211de38c05756ebd0b378884e28de1ee3101cf398010e5242a1f2d58fc81bdb937e76c28689e0c743ed976bf23de71ecdd6f501adf74ba9f0e4f00849b98371b3e330e49506bc13a37c5520a5bd2c3b78c3c9ff1ece4dbb7c87c99747627de3941dd0703a1c4c19c42503ecf6edabe189d8f2ba864fa9f052585fbd82fb9d863217421de006d2200b4377880232a199ba26e7c39627cb2fd2d1e16c81f96a5b044878a8319ec215b2182df011612e3bd1e3283997fd82725181bad0e48d7867d3ebf3312aaffc0fc9b8d48f61afbb590b197aec69df6c0968b90ccd11e45a560ec4aec522bd5440cd43d3542b37aac71db94355e8cea6583edeb465684b99178d30e2de6de19a45224584c49cd531e2d81d3bb95fa97569d2a56c39e38c9e89e46ce130e6de1867ffb2e5803f9e8805734a705a699666bcc0ade326b6236cbbb056289950494bdd34f0d2f6ec26f88ad67fcfe028954557a8a939c42484bfde452b644e63fd2dd404e6e172088810431525f89f7026e8f6bea92a27af34032955d78060083fd40e44bdd507c011c4e68eecf9a13ff40031b102d60a7abee8bba18a57076e3a3c9b070b03a6ca59684ef810ade6638bde1bb863bedc0a1da6a8278429f41e138a6766ae122f2ff014864d3d6ca0e17293d5baf400209a633227e9e824737576cace05089c6daba68a0600d23d4d72150bcedd66a158e2265a85445830b5532a67e9f000436edf6ec90a8d2a3554a1426c38ffc750418dcdcb5db40c1241bab2a1181c2b5cc243f4740e146aba55d3856f2a44a050928486596fa3f0a0ed9b058b242e1a54eab6b910102148cc87f67d0d0ed55ab050444d2bcae8e142a58c948f07100176f5bdb5a8522250e2aead060c1744689df83c0c0369b9d351c5ae2b0b212092344638cbedfe67840e0551151c5ee111b76faca9262327e697e398f59fe54fe1c3e196f39bf24df2cef2cff0c9f14bf9ca7bcf0c709e9d4a03d30f960487ac69389d2e20bb9cb21c9f288f2346fc273096c34f946438d861c0d3f183e26cc20b47168e390034347c38f84180a371c6e18e260f8d8f0815046a18d430d861c0d7f53031fbb670c01afa0760822d2aae28824b125904b23b04874faa18979ddf2aa5a31b7b392c7c9f1d406517e9ed0ca538ed43bd82b53a4e23010c6a8ed15ac7b5abdd874b0f7fa61cf9a1d267dc9025c633c5bc5855a93cb13c5bfbed84e9eb51c0ebb85fcb41ba980c40457629d764d09904b9800ddd7060ec6456c11384b3c06f4d4da0a22b942d551fabc8ebb6e6d0e7accd4168b6449114bfcab358eecdb0c0ab0a07af0e4c840a755152d8af1cc9fa9fafbe5040d191a1d04ee13664c132867aa8f079898dcfdc1db1e0f60d73aa542b75e860d72dc2df1020cd8652a6ec310cb6f143ee63b848df5ab40b254ffad4f3f2a33bfe7d88a3251630d93be51cb333c6ddf6edcc6a862c3e450da8ac972a306e23b74e8275117fad9d89b00420337c66ad01104f15bbb5824a4c4c608089730b4b0c9228075365139fcd0b9bedb38704fe1c7c28c4e80c7965d3751e6746038fc9409cbf32ee2f0d3fc911bc6da33ed04c7155800a800929c3763ea3f2deed7fc97fdd3f61686b14126a995f074949e622156fca4c88333ae414d1fc8e6b0ef9b1cafa978458d0174dadb79805743ad61ae0c068c5d1e7ed40f1767232ed7067f004ea14575b20ca0f04898a73b789e1301394b4b8b91989f38d36f6bf33725890bae0ee3994de5f87c60b6b68fc5df2999055468af7e00003c1a99682e2bf05b6ebb6815f67f27ad51976159dadeb8de690cf2635a9f8ea67d5ce848369a1941e00e2f815c8bc6199effc3c70b000b79917fb1a6107ee7e2489cf11d8446fa1ce223eb29698b484d47468e3f4a828ef49aa9277dbde84b6f0fe436f6d95bffaf40d592a58887b1c746a660df8a694d0ac050a9d01e1b38cc73e80b7ddf7aceac7c0139de9883ffbd9ed2ed57bcd4b91e6f24b0aaf15a6dc124b271a91371c862575ab3fe10f4b963211fbe877e7f6f09ee3d72fc000ee43370d138c5291ed3cbe4c73a4c3acb9c75145916cc3fa9ae775443c177584949c0c7940090525dda2343e937ddda34467563d703d0e30d8ee3a0b80e8af320380c80d360dc0dc671a0b8078a7b001c0cc06d70dc06e274509c07c539001c06e16e304e03e33a50dc03e2a807556c9ee940dead9fbb2b051206fc950220a85b2067c91b5b9d3e5fd100384de83a9aab7509608f2a5b54479e5d0aa28d47495720adc03482d00844151c5da0b402290aa6100c45306ac1a805a4149032055cf4c2921dab2f2af9fb202f833649ceaa9749ff7cc89351331927b5c5e4bf3df264a899d079cd12a97f8fb655fd42c9ff4ff9996849e4a8ba90f4e7b3fcac9a9338525924f9eb915f56ed449dab2c90fcf4c88b5d6b52c7b58b257e3ce0c5d42ed969d565a9bf0f7860684aeeacbea8ecef87bc0c342438575f26fdf7515e468d091cd42d93fff6cc2ba306d20a26e0a4b298fcb7679e0c35933aaf5920f5ef313fa686099deb2e4bfcf2c41b5b534287b5cb657f1fe1c3a42dd1a1ea52b9ff0f79b36892e4a87291fcdfa7bc4cb51371525948faef395f46cd491dd72c90f8f5cc1f5b63a2ce5597b5b487f05f3957a2e2bc3a19115fb4094434904e5513d1fcc07e9720f58bce8c56f56f9a127a3291a103a03fa19da6a481d1b667639227cb94274dcc3f9200ff85a5f316e5e2547fc8d7a281cadb2300401732b8b42a4f28d77d30731258b5b647e1639700835fd4683b59d99a50a28e5a8506e2133753073b8654689ed0dddd208f95f159f51caf8abda5cbad49180528bb7ff87bbe4df882a563ce084310b3b3e8b85d559ae817785064dfb9bee6ccc6f8b3965aac4f4fc5ffacc6b04bf82d5949d24ddc836a211d4a8bc5d6aefb50f9e3c058a910a429a5a5ebee014e60014fd62ca0e52b2657c051464d83270715eff06f62ed35be36f0d5b534eea3217368f4729b88ab505f70d821e26754b37f5e46241b30bceed67a401bacd48bbb2eab814d5601040e0123b3b23009e84f17a6931a59688cc6b20992d08210e88561646c34968f540c471894321444c60e53ff66f4caee184ed262684ae6ecb0bb45065b056c77c16eba925a13b11b582562521fbf94873d619eeab70531e24714acbe94356bb91915c0fac77c542a93b488724726e8d6fd9e1a49216a7631e54a89d4da83b200cc283bd4d5731180441606e3d7cc53ed285a6580eb22dc7c814bb801b0fd45c3a95f1d881aeb23c85cbb818bdc1267304b300fa1931a04c97265ced83100e11f14e58db4695f7c1c8f9154906181106d9835d501370c59e9948660ad847152050f176cf790761b770943a707586b658461134187b090bc5d141d0b490e01861f715c3c5723190abfe31685d8a2f60e098b82359419927ef08cfa9f1257c6a0c43e8a1e375b7a460c2084c5c8c69dc9315dc12a80a46e5eafe083b67a826015c0ad9d26a66fbdbc2035ae9cfb0c37387c1bf329c9827c7fe557029b6add91d76d70298e123ac84fcb22ea6ba6504c237bb872d875024238bcaffdc7da628e79d15d5b93620315093de0b9da45ab349c77125e0025d3e14192ffdddbd78c992ea16c449f177c70db36dc6ba90540e44ad7eaec08310b4eed30a3df91ac1d72625c5f80f1e1c68b700c63bc1472d7fac01e7b1e1e1d5cab15ea14c89bb6cd3f14a7e2074df41270dad480aecb3004e0041681fd598297d441085ee3104b634c22c2b123744afdcc0a9ad4e782f5e7edf042f138c6300dbcac674bcb1c1aea0445cdeaf7af129f5d9a11807c3999068be878336933957d889cd239c8a5d84869ac317676cb639f552d1ce6872a78a7c307e1282e4992e07d906961041e389fb11d767dc59235451f269a33781a175ca40d697112c4019f772636dbc157e396be82e5d36fd8ce34036fc3d0e34f1824c5206f6c0f7ad4623cff513d1ae8b997598678ee300ce48f30125de34e5ec7ffe71ad0a71b18c0b9a297fe0db5c3cd66c66b940823ca88e66194138a1c93d979f5c5497187c71a826d3789886a1e6723ba6f82a1c11cef5893ca3373868fe4625ca454c482d179f2d961b4724a0d372e84e8e47f24c2c08c32569dc1d69dc6e9f000d4939d93d25b814ed3b709af4f6e2132bbf7ee55157c27c9fe51f9bc8efea51a8bb93aec27ab903a9c277db20b8dde60a96d149e539fade01961f450c9521889251468649401d99e3538308dcd721a4092ff426d1dc4e52de7a05a902bb29c8e6cb4c6b400021a4be749c5117954894d4bc38d041c2e025eb89df769703c125d2fb449d3b9f825e20e0aa4cf309d77cbdef8d99d7d19fc8be3c44b942beab9e2dacc55fe5dcaa3cbe4402a6a29edeb54433a59c542172be559018c2b2cb972441f3078cf0f478c4b3ea98573cc54b9d39c7a69c7eb702ecd5ba52deaa59da39c4e6db914b92693c743facf9cab8481815aac13f7d9bd8cd655aaccf55654a1fa188a5167a1dd5c7da4214970dd09aeb67dbd7e6de7b9cc72a491c6d0e443f655c03fb3c9d27585162f326ca2bea75cba0676aaa1f7ec5661c3f898862cba4d0d6390992b98d475930ad4219128f516753fdec5e9f9a3261f88c3d61bf3ad11795fa5fd8d13bbed798f3ae5c2ab572aade07c861c7199fe5de3e246d6bcd7685c0c16cbee624bf8358efe934a0443d68be4c9be174e730b76c0261e92439a23dd8562f1e627c3a72067ace8fcc744fa54ffcf29a98ac765225a52e5c64f165bd3171e2256103ea07815e661cafc66621aafb037eb88be171d14db7b3fbf322f2ee8ccfc69e40cbb637fa6716fa5601922da82231017411fd955f291ff5b60f9e6aca056464fbc8ec546f582eca333af1de4f8be44004f5f49e37d8629824a304d5736822ee25d045c4a955c42cc154e267ae73830f73fb4400ca8662a96ccac90561a3651a6c19723c0b279ec9a6b10c600ffcf6f08802846d2358ec19624f369fe6c05a4f5c5fc70f1737911e444edfa7d1b5c79630f852ee8593381c1572ce2a01bfcca1b7d0d2760d39e25463f917a216217f95b7480f8c35d1ea78fcf2399c6e8cc74486bd29a4ec7aab838a5ddd2da8336f85dcdca54ef3e71ad737f8dfb02eb7cd92a0bd07e861bf766d8a1f898cfba50b5f9da3215224b78b2073b94e35e81557b6a420bec86ac754f18d25a279c6753a20e9cd8619cec2ed7c5e40f3fd0d91deb6d5b695c30839cef5afe5694ee893bd8c98ee16837e97ff8619d76678fae59038a343171f6e48881a14ded0777b8f31d467ddf88c7396e3c28b5a0c0b1130bdd357d4f57543cd76bce087367ddb90aac059bb9f7ad5b7b7864e05dcc0912e5e001fbb32cdae097e6f7dcb8f8ee8abc40d111d336e43d78600ceb1654c6ee7a2cf58a117111ee599ca301f8e51d390694c6852b1258436e3b04ae4dc61b19948414e7769919d74d76466c0c5f085272b4aa891b5615a2158fe04e2c0bcf9947c9d22d1e7c08a194ed6516285f5b22743f1b3219755b0ef53d2de56c62207b9865319c97aa71205eb1a538f1dcc31bf7d1acd549d4316199187d44f553d8f4ca4b7b37148ab57383e182e2fb1df4660fc5acf0218b81df436baee404be7a864e0456bec5e71e16de4a6da1e46a3db9692ae2cf9b3eef3be15c99c5d270273c2822b3fa32a769a55b1bcfe708df6e2bbf6b0da0711953c2b8f0c994d21a2b6a0141ecbdb3461eed3f8ec0d40fc4cd5f41728ed510676822062f80bd7b6b3303fc0b6abd4c540ebe99cbf3cae1c2406285d4d2d8e196fe5e32471e96ad92874b4617179ffb70175fc3faab89ba677b890e7b54eecd7b940de69829815257d0d18828705ecee8d1e1ada10e3c4ca96662d1987e879c69c5fa8ea3006da4a3acc9ad13b02cf6c9d79cb094ebcfd47826f4afcdbc443e6cba6e952acd52dcb85b5600fed6f06139ad32680b4552d272c4125d6f87e8d6a10aa2aeaaf64b83f21fbd4bf11b7135ae9e7160db71e7c092d171641ddd2210aeb44428167e1b1f474942481d646991391424788d606f8a12e0544bb11edf5e53413b556bda8acf173b43bf0a80d59a65678c5435a356746fe56990bdab94a57142e3d472623cfc1d71cbf21da931ac49dc89c53c667928155a945770d529bf1335a7869bc80588461ff00e7a4b7be008851b841f29c2050a0aed7bf5d9c8fafcc42baa72df290ad1460150a57c55699ea612977c7200c375657df1acdef3a79437858f541ac3d2dc8702ede5f19fab9913ab0983f77e0f4c5c0a7842fe316c6094f303cc84707f018ddb5fab080a201762fe80d824b7be5e314a8c0d071e20ea23f470162765fb29875637c24725d919eb3c9748025432e9d100f2ff89c44757c2511ec3fa40c4f2fd3f78bb6640696eddff515e7e79e9d2d2877505dc676e6d8f20375f7c126739b5d634082f53d68a29b1929f526bf701604dca66981f86a23af3e80e264f2750cb38731807453791178c54456fca431418662feddc54784cc29a18e5942485f609b4250c7a439553b91b4ac79397712b8c711e8dce38a6243495c741de7f4ee93b3022dd89e03c0ea25463854c1c6c11c6be59a07833e5b21d671838420c4a23d66dec9149024d0425e86718e431fa799261669ae56685a03c2b8c21199162637f1effbd81bc538df7cb13e204bd2470b042f2371b542231ff1db1437efbb1a7fa53dcb25a1213c96ce28db42568cca10984c75a1a8a2c27176572da05cf340fff011c729c307ce33bfdbde6d7b315393024c2282bc5ddfc46814a12d41760ec5f96df39c397d54adac182132eb666aa8766ae9f5f680128105f99410ec7f50859c95f61c5269a63cde2bdd02361958a9f1ba3bbdbe5197113579560252aff0a57119a638af448cdc2ea2fb021e9dd586c6b519b73d974044fc3d1619f30f3e28219e3efa78f54990ab470b95bb035c8e01a31421766157e5544f5c5d615890fa801adda41379d19b1c5a3cfcbe92128644abd19ba41779d93590e6492c8e906e7454805da0cd2679ebeaff1f941a278e3e34cd2cb76918dd238faf48b943d4eec898381394a41bc3328518124d8b3a954089d52c9088ae045065fd54058d9bb60516b11a76bc3c24389a0901f80747210278c631e381384edd8308b1c7f866b30852a49bcb74411cf8fb2c1901729206b55b077c3ea573a6ff8d26e6c808942ccb88c0222670bf09b3df601cd76e344a20d53feb9f016cd34d9394412dabbd1bd7fecc750dc6aef2a40230633318f76019b0e4c89b19a17d3c63b6c73b3ef5532f67942bb4a7f4a16c16a0d60c412b311501d0d51f3a5a0aa8d692539b87eceaded56ac132e2acf2d9738ec4944a45a44c40a1eabf38f1149be81277190664186be65eb12f61b027c3d3a0c842819830d212ab7635d58c976c14784e414aae792c9d283302a792809b2c02e5db13afd385c2e6625d9f79c07d58f6093bb4653d3d0c172a51c94a49c0756a9b7f8533499bbf3384a925b153d06169c6e02367f8d4ae0facbc659ffd89c3121efbee219445f61dd6eb9a057e199a973627288034013206dafa58b194eaf76090d801219e77cd01df6a1504d5f57ea58d351fdc20be8a9ad5ed4f2ec78320c47a754ee8b0ec4617a3935ac5139d188a575c3fc01f52334350e14d65891dfdd2615658da107bb3b5ef1bd177828c96c3d9df2cafd10487eca304507ef4ef022b157d4e2830eaab088f6e5151082f7f43b0dd0a983acd8d50862058db0c3fcc3fd5863bd0c7578144eb4e42431c2c4d6f435ce6f795adf34e919c367bae02b5241a32e451c8db62e7a1bf275780b85e7e0b8996a0127d920398a6ba60531d7ce9cab62e3d12c634f9cfb015cf027364827718d40891abec56e83e4ab53bcdd1da577583d8b2ead4380bba0ac41e8b817b310254a56957e14255320257693c97fa87266b1aaca70d561cb397bfda00dee61f0488c7e01295375aec4fa7483532aef4c6d1750f8b9e2f56d1816fb3aa83ec428485c02f2170249b52d0c24bd5854c8e3a46a92ba5410b709952e55c3673f58c7d166d5bc0bd547b7a17347ae9cae38e121b5c4f182a955e286be5ffec09f1032a063c7d48784518df844fbf46cda52aa51b1591514f4ebc0e1260219c372d5cde8c922ef99c5e5f5517c62d73d0591626287b34cd1ccaa6d35159c0ffa71b16e8eb13bc702168e33ba90185c946dde7a7a08c46f79d731d7a6a0ed0c1201094fba0f53faa6155be8053aa1b1a7dd85cf350332a20084b86c4482af597e17e3b3920fa2cce942185c9721636dec466f6f93660b7464c04f861d9aeea848617792ccaa7c5d520381bb1d360d38b2794b1ec9d9aaa44094ca90593e9ef3d7f15a0601477cb852a8294bcd4545727e98fa5d138ecf05f86c6f8723e886985c09fa9d6491975fa00577529f7b638f6d086ad051e3d952f215771cb4abab01758fa1711ee5313270165c8bcfdeb63c8317068d9268b0f7f6f22108a16bf38f02697b873309489b038c6c40280bd28bbaca0486a5adfc90dd0017ae736ab3bb0bdd306b8003c5d413fa2809168bc451b6b61c310af53f64b4d784bb43c532ee440d5409dd25c0303b4402ed7b67b94231a0a1d86afbf5398a48a2c024ce33238e93e27673f3526e87da6bcf9a16e991ad0d13c81611f4d5650d77366a17de0462de76d02b82db9357e23171c269c65dc92b215864b5b4e91ae6dc23e0bc3643a90103b06dc0c450083bf1767b2875fa0c7c3e264b91e3a5c0bd58727282493c2f87f4af126172e39992b8c331ed13e5048e8fb5eaa3efb74032fdd44e007273274d3662ab306bf8bebdff987815ab0156a6b8ffa0da1c7b79b86ab5192fd30972b1c2032eb2db38e0809c44c76538fe910e1cd1a4e785ac2f5150a0752d584b2bc658c8a01b524d339a4bb970bb5b6ed43e82e986a1fc048f9e2a491cd9bf2c78b08ef486aa02e3917217c8b671c7b45f8c8ba700299ed127c3d0fd3f12e8bffb7c03647876fa96552241c9eed3782df6315a43252a4508f12aa200cc080c7e3bb8ee8b8810b8def132f395fecee287250f6e7bef415ff10119afcbfa5ecd1be4add1ab234fa30b8fe5350c13d8c17908e6728bae1ba4fc1e1c072b8c94f42faf58efbbee59de4348271fac73e1dfe7d33ee46b7558f5c4d20bdfbcee3e82cb9f1618256d501a2e0e9a81b8d8f0d23c04dd23fb6990c884b9e026590f985b7b1d31d6cad366394c811a88fc52544147dafcf8a57bc068a26949fc0c26256b9fbc7c3f503fb9630a2dad9f2802495eb3b898cdc6aaf95c1c70bf3630edd9352a5faafdd8b59c78aa4de6a33af874ed960854ddad1495940564679810673f13cae7f9cf014664823e5d40ffd8fd6cf0f053f6d4fdb8a938c9b6784c72659ae660422036e822910a608c50087de16aa0243f3dc44511681a42bb82d9d608ec7b59f56da555af749f0f1a524d28e4610ecc15817b9f4da8370a5f98e3ae3911b0fb12bb3fb1ceb70323214537a74f0516aa8726468636e5302645dbec7b1fbec1f0006efc71af65f4c54d84bcf7b33f71ce6e233ee13c743f169279595000270ca94757f4f6a3f2f23f636b40bd317fecec2243fbad6db8ffb3dbf1a50eaba69872cfe5c2e5eeb6c29d2637faaf37aed8639fd19f8bdc51f9b1341b98ea34baeab3ea9689dbb02572dcfefea6efe9eba3ef0eee9d9c569472deca19cd46b1040753a4967e43b4e22243ace19a2c03e5d295f28df8435253d1f85abfd4dee5736a31ab10e155d39544596d82845fdf3aff5fd01e9a4ab2d3409ee730406192df3f389f53caed958614f1bc1e69361012689030b0ee149b52d508ffd7c21905d1a9050709f6efb9c53884318428c4858d1e84bfd64d10f1d6a601f320ef75dc27ef429e329a90884e58fba1f772fc0f5dd7c210f3bbdc3ef860851841fb59f2a26d249150129dffdcc5ba1fefeb7fa19dbc64b08bd8e4410b456ddc15b59862bfec4086dcafdf66f97f538585d9ade8b4735f8d084c9407525491555cc01a798c5cfd8d6915b653ba1421e095f9f36d36d9d54314e842dfd3e4845af1670a8b444320281f2100b47dcaa3cd6e88f414716585a23bcabe91afdc83aa9fbb4d781f7cf4a93931f424cf7ad41d3f7e9f84dc064ce7c3166bee18272e8d234196161b5f3700aa67c2cfc2473bf94adbbaca9a9a5e6d2768fa521fb6359db929bbe74eff0906b27e5654c0aa82ce381dfde34df49cb993be982fd633df33106db0ea409144e9d8af5099a657d6b3e7048111ef0a8856b7028255e375a118f55aa4c75ac2b912aa09fc79e8ffc4b9ec686b2b7c5022ba457fb392bab81e9ddf9cf7919a07dbda1f41c7ee671f4473deafdcfb01f24ec3d74992d38883508a31784d8eb87d995275d791bf8975626935584fb06f55c3fee2769115e970228f6b7f4d1c2c6428dcdee9083a82026346f49d6f21927895cdea9b7d85d8e79b9e044d38d72e1c925f6e1fbd26958299c4a6c2c84205cbd03a12a57526383052cc0c24c90f62dbacbcad10c44de686572c99702552f8b1b37a9bee72d7f2e7e20aecdd73046c95c818e66b66e6142a8523679822434c4221076fb208746202dc2f8abe131d096881c366668de033d150164ad7ba27ba1a3ae5eaaff0b994323413ab7198536fcbedf2ff627a7cac37bc1dbf624eae3f8b7e8a8805fd163144ff5b9356c87e379eec00102d365a4fe0fbfa650f0708340a39020d484505342dddb0f04658d637f106642c9b55fc6f26933468a51049b8ad3e59b05efe56c047357d553e9f428c4b44dac044f4485e5c85f42a394f96532c795071021e297290f51724e578b741882ca0d4832bbb48dc05ff6f542601d9414462538b97e3df592c901a4e754b7e4a4a76ffce0dfefcff0d54264302a83fff43d7ebade7d0930f5fb038945503c360d5bc206ff5ee18369bc7aed8c57d61fd12f04fe29c8a97c14bf6b434223105a7694cc71d795c315f3e37c936a20cfcc42600d05e0d0b936a2059ad9eab76815c6a938bba286fad6544e463b660065cf476d600ff0fe641cf3e1ecab36ab16e30892ee8ad8c18a724b8759a7b718ce9ae94728b2dbdad7198cc3ad36fbd4eeb8a1855e14d989888b6d65b34be223d6a152d3757723621886ba44e380f2ed0082086126310996e82ec742d3d52045bea78dbd8dc37c5303e583f18db09f8e075586237f08860662393fadc0fbc06afc49a57ad50a7fe9e7e9163566ff93850b988f75a1294bc1ac150be1f449883bc9590124193dd51410463aa68afe6c1b29f4509c660f86e75dffdaacb14f93188543f695f0ac49d11380853c84e2edf88bd83ed99dbf9012f91cc251a6856a240eabc5755dedbb71e30b8330b80b41a75f5c40bbf3929b7e99a8ed341cbe01bc1f824b8c0701e3c00795581cfc9bfd4b5f7de1ce533f9e60106009a1748f5e1798be5b3184dfd696dbd4249aa32e6846cb5817da8280d221e03288b93eadeafd768f4b0d2055ef2a32369e6f71eff7d8db951a88c2c2cec294842de1a62ac0a7c47ecc6f3c1f9eb1337935dab2fbb8e63637cfe067084ef586d862e2e9a48d1875f9b2df6bafa846d10810d6ed6422d8e3f3115f8168597f6f0d7d87bdbc378da3db6a32d2d162502a0f9eb7940ddcf532ab6182f57b7bb62317ec7cdb6ec463d6c802747482951a55bb7842247aa2e847af215ea81e93afb1095cd315b839e4073a97f717f9cf251374fc192ac190390c66451f1769f2b895a63fe6092ef8f8d9ec09a793857ac85684e0df92bfcb6a8fe11b69577b23b0addf2a6cb3806c7c643397de7228e16ff1b4fd8f3722d5450027b652fc7c48804c71ed239b773c48b72594db00576172d345b50f287abc1284ab52c28b818b1906429594439344c6a4e5585a229b40c51565751d52e659851fa1ab908d237e649808be24ea99b0405e8ababe1ca765307343531db4187335b096c689d668d582edac7047878825840ed90a00f8ef6681dd759f34462992265498c09e99f4f779829895e02041b45d43b525bcafaf8e600b33fa5b4f8012059284992b1bc86bbbea350597a498abdf3effd8d31ec6852fed4bde085665bef81ba98b8c9777450c473bc033efc8b7ce275868d1ab9517abbd105b9fc2504db4476eb44203a62aa451ff06f8c38697b7aebc416cf000bf6c17772f27122ab280afed2a2a81705c7fa12754d087973f7011c960e17fa3cf4df062a10882a923b035edc3893ba9d2a77126bf1ceab18b383c53e3f0f81b0caca25b0ec797312a8d57c1e02c096979b40d9e7e625a06c2b6884f990776698c2a391b8cdc50feb8773050fe4c8112f919d25b08647e972010000d2fe751f230b254e1c2e7162f552154ee96b0376523227ae1e8f3602405e3d0d0769043b35c76ec6ca95d9603ba2f8214038b98df3d3e65502f5f3105616d003b5d22e35a91563fd0b2630fbe67a14cc1c130240859c88d04e194c0db43c3cf40cb408ac76ee7e8a761c38c3e90272c712d1254a4f63d9dbb663c7e4b6b7c6ddcb6f9b45779f73fb6b748f1469e55c5970d2fe586e601b75ade37bebdae9fcde5cff7ed381d00f5c40d1876f5b79051c8c040aca7324ca7f83646348f48c1467e15e19cc18e4470b2d4c73acf9d72df9e3c8a329b951ae0da3d18da13229b40197f94e5aac6a58577c7be682b66955bec4f2a0c8aafa07ddf5efcdaec4f35b793f17395d791780630cbf6fc5fa5f1e5d15bc7baacac304111f49b7140c2733d98166e027945feae464ab690e085f60a729981d8975e68c2cdd5fec1ce27e964d8f7b5d8a0a9c23eea98c323b36287297a4bf55a67a6133a7ca4dfdb90d42c8b54f4a11f9e76e4ab3858a3828a9bddcdaa6552b7b64c2571f0464898c05b6f6a6b747b4b5d30c156e5abf1f956f6e26914dd8bb0f42cd8eee6e9c5f2529d9e66e27a61d76e4e0ed4a0196af29ba99ea099453bc7f5529d9676ed2d80f9f270feffd5505c99fbb99627f78de6cf8f75728257fd66d9ab2878c7c52597a1a983d52152027aabe702fde2ee6f65f1539d9e5b7a5da9dabb705fe7c1489e497bf9568f7bc9e1d78f3a12493bffc4652fb734d6fd84d8111480a44b634bf8ba2c214061c08c5c742db378beef95f8636321fff326c5b249c0efa1d682aff0dd9557bfac0739484e920afce4f1c01147ed3661e81413e6fc4c584a48ac7a54e9d3986cc87a81ae2edde442ea4632ce4d0a6929f5856a6af0f56eddbcd9ae7b8a668129fcd2d23a8c9bb8da47d4d16ae987099d1db582c719f7b87d02c20cce0ee19113ca715f61e08b0510174da6ff7d18f7956db67fb09d7987d98aad837189535117a8637c0f15d67009477cb2d6ae2c22e01c5d8d892456885dd30340a6e327eb032138d28c4fe50594e6c164419638a1d74c7afd4132b53b7772d8aeea61ce16159c97867544ace78921f2c1b4168eaf6506cd9514622a98eed4cd1ed3ad55269e2382448742a1c7689484db4c011bafad42dc73d5d64f93b03097a0f9ae5886745283616a789d2f1e5e0d88852acdfbb920581066f3d28336ba02dd0887a79f7ef3b2b055f9bada3d5541f4fc7cb708a3a03c112c19c3d868c525e4f9063946c7740195b7ca0a9e2ae213f4d720ea413b9a2e3abaae397ab33b06eaace27b8c3dadd840cbf5ba6d0dd6f19d8038ca627b818e3fb9d4951c7da80ed3bcfbc046910776a87389f367dfcf3b24c0c1312f8973796161ee298349cd2b5175cbab35f89372a179fcf2ddb5b798ec6f9f4f6f389c69bfe99a1394020321916afa277a4bbdf2486bd5f17d1babf8ebd59f4f2f8bb3bb7719ea973d7edf2d2871e6c796df39fa54cb17aa50e6a6872d4eb9eb8e8e8be4a6a24cb590ab1c5e517c7b18625a022387c174d22b6618d2f582ba289852224c9fc69cc3207ea084305cf5fc1301672e2a809331fa7ffefa04523a67b6fc43bd5b5319c4ec73d10cf44d74efacaf7939a755aa0a683a1aa5db6388174242194fb1bbc69602a23241a81cfe574e5245c556dbda03eac64ece5a5b19461225e3006aed1ea401a5137c07a50dde03604889e7880917cd7da84df57e9e70b41bb5e2007180489fa8bc11e4e8b5a407ed72ae35b15c0df7a0d3ae4a532504696e61ba6f9bda0166b2b8d7fa49ffdde1eb2414418e1df07ccba396dca45d2cbdcfd72ad66c54a74f9c87d2d8fa006e1ced177a8645d7b171f1ca95789a8413de4536d70eaa16e1c24cb1066947de1f78a3bc61c60d43a7a259c3fac2c88ab1acd05b89d7fca604c872103ecd34120ade48c48ec79b0f934c07e47b167163ea3fd0e07587cac6b01923b6ab899ad380eef4221f5b25d736e590f5e6754b8b526b3f3ef2b0f3607c3ff3e8ce465b32826e98132d27039d605b9dd9879f12692667763020c2f10976797c03cd1d17181f0966b1c28e3ecc02f21e5ed3c5e06af70544eb89ad87c4528a41142a17f64ebf53c37b596fe249c24c05a82089e627cfd0833878b6f0e42fa30154bad0e8e60d86d917d300a5813967eede2371672ac81cecfedb7fb665c905b4b82749747712bae563e28afd0464039f17d232d581f42e2d11d6fbb86ecd97e88c260eed466455c4509de45dff9b147036af7b13f2224bc02d95d399279ac5e4be329a76bbef6cacf5135988aad925d772a87af5a5edb0b4afd1250ad4a816ddfb33502d620cdf8af3cd12a99bac594440b70b449a5b9cc554a2d346a41f6a66573fddd506da59e98bcda9f3fdfa4f2e0f296c427f320db8f664ea695a14c39e5fc6ac557e06d6082f62a185e6b46028ca4cd3f5bfead0113ad9f5c5481ded52605c2253d698e7ea006260b63377ee59c46c62e8f88c628c09bbe306fe2d2976946b51eb0c6ff710e08db721f1385101ac163a52149970892eea45666c2b78e941ee62e6ec7d9a66828620fe053b607fb5bb1634a5f4a4890028f14b8b4a835ab988ccf2159ce73eea956810dcc221f6c88cc43f70a8724c6c748123940cc95ab39ad8d70e47aed04d303f3aa07d87e83528fa008618157ef03ed4dda648927cadc3ffcc0407f0b8d66c80a6b158115659586a5ecd8b68814d246a9fc4674578da4299a27eb1618fdb5bd4fa8d188b84116d4e4df7fad24b449f54d696d5344f4a3230b29e5db91af1d6f2c6249146b1f09444db30add7f8e88ff56644e834ca313fb7cb25e394af3bce32102f9b362d73aaa16b9cde0d4357fb3e5161b0b4ddb7c351321cd904626be85561da98739397c20339ec68289b5e58b83e0467a0ce8274e6832eb6e43650224bd2974c867ffcdad9440dca8c9fddbdc8b6fa701206ae776e55908691fd9a2842dd136482f139e1afbb4272de27e0b43df896a91e0fe088a4b2c2bf2d33f11c30c68a19e196cd3ca97331f742249d85e8a35c6fd685f28fa3e66afd842b8e28865035e071ccb12f62dc7100ef6952c032e1178f8db0958fd2b20fdce1b5ad8c04d65410646bef32a6b28709b5d8166e302a6e8e97742e158b353e26b0e9adbd6c951dcb4fb6155f24c93f5eccc17bf9f20b8751ada81f2a1d1d2489ecc3df5e8bb7c288fe91be75c22d6a4b3a33e58de84f6bc2b01a637ce05cab51484ed0a166bb966cc23ee56431200c1366c6f18f4fe64a382aeb357c7c9647c9340881f6ad95b6fb66b2d3e33e6aa1cb041ae66024b0e03ddb2fb17ee287be38977d06c267ec9025bdfcf9f347bb9f1490f2e492c788910cbf486a1b5e87588f4b4d6c31d4f140be49b8d0c689adb3adee6a82e89e9ef1b96b3f00a4b3a81853186140aa4cc599f61882a55ab5cb8a818acdb15a6e6037f85eb0e411a85ab26da0f994c16cacddbd562dd5d9c11b64e4d297bc70cf39c32c39e5b514f30442637573a2d5d5bc45367c74168e68a45e830e72b165b66288af8e19d8d2465a255c7d966a3ae693f92f20269ca9588f4ee5130c91f8cdb4820d022723fdf40b916b2d1c06cb2c9e5708e35c726644dfa2b90fe627befbb83b30a5a59cb119d0f6c663d7b69dd5b4bc410ce5050685387610f70f983f45e5d798b0bc6845c6998d7888412bf77967103b6e399099a41feb3166a887ae81b1b4e649341c9c9e51e6d6108f3bf4ad754598ff0821673ab777f87f861a09fa27096be0ee0b098377719fc331bce8bbfe02ce9e2b24f767a56402ab456f37e0eb21200575135286288306fc9c9dd4a10ef4572a7341cbeabf61d6d5ddaad70eaee1a23ddab6ef09b9c4d1d61807439282c37c798894d692030d16e8e98b9e18e1e7a28934b626b4d105e22494ce31642600badea37e18b34866762aba2c8f9dc4fe641e99c38045b426d6e40069e08378cdeb32d0abdaebe8851edf018661d4da4f875ba70ca42fe75b48cb0edb58e8952f04d181e5ab76413902e26fd1a0e93382b2e87da7a5aa1402b94ff02cc9d2a13d9b56b65feca5f5089f74598e42a5c91869c25c432482fa14f218b60dabb5f6bdfdec9cd2df10ad2d750570728fb69e244ef6734612630215ea29ae0c0c02fade927e1fd177ef2892b95fe9392f13550ffbe0900c9a6f7603e2cb13a77f70ff1fed2b4f4efc2396cd00dcc344809d8ac9f9b0517ef6b9839430a1ee307687a72e588fe8a926da4aff893636ca2f8ce337c7d8e30e7650868e440edf1e1f1a0efa9810626673372228e1350ab8179ea03b9dfa94e9995b3a3b6121418b48af77a4a2c087fdb014601fe736ae80cc26181978f466b221967a2ef7351d60c7878c2353c8141a334c6ce5521b8efc5a15a0b31e6152ebffb9dd5c3ccd35a5cc4d39979a1db6c296a171b5b4e62a97000ddc05b6f0a9e93590eaf8d09468900a2ba254a9dc21c8579009c427b485e96406b5ec6fe0d6ddfb165eb5714aa64036b1d99db667b8e66cc196c64827d6d967c693c8bbc9015506b93b05b317b76ec0eed64889fe618dff9aecbda004915fe88aaea7d9d06b4332245e660571f4fafe4da9e84d32261fb3216bf3c2355cfb18396844186eb8f55827beec0530812aed470a68ab3fb9442c9de9ef26b3a405bca2ad5e97e4c352b7c45cb32e890c498bf870804eb68013a0b3f8ee810182e4aad47d1d65213d3ce905b966726392ce69cf3955198bada521cdaebd77d538b3659c6ea428ad96d7918ee5b3d5d50289add16e33378f28653fda087a527ea08f33e8a1b7241a3e6770f32f0d9b78acab098f8eb1529553e88daa6d68619337a1651ec27659ee9683f36db19086fa73be70e04548dc10235a9a29c18c0fa48d6c04dc134dede492089619a1824587f1a9526397483f13dabae8f2b2172496b66b8c5d860d940216313742f3323a9e3c2b03e59dc7ececc679c49407b056d6c3806b9c8d0123913b6510314e2de36953533ae63e76a5d125dea4da9c5ea42e9f3a9e25599d958b216c2e035c13f04b2615aec119dcc812a6b17b1ee4a09340acb1846521fd056a366c9c7479b49c20731f886a56c21e0796d5ee19c0b6fa4547fd89d653c56e0cb5f3847c15b29eb1b9863982d94f20be6507823e57ac3e92ce3b10a5f7ec1398200a1fb9555ac267625f9868d5301a25969ae2a8d7bc0d19fe4fd8730eb7923e30c06e8fdacf345d2bfda7adfa2cc5652b8abd4ebef29f3190c3dc36f85248876fa2a3387d544ff00f64df30534458aa0381028baed1fda57a5b329ca2a628a30e834c6f7e09af9178fa2eaf2fc49348dceed4f9417717142be3019b94ea5a9feb4b2fd082e1b099ff8350ccbb653b8370b4f9ea4ee77cc01a4c9f4894453645944cd7019e4a09f647024e843e4a0a06f1b639470944c3e9b5f40e28d49858f86dbebc5db762bea9cddafd721e09b8762adbc1eb3f8c0a026636e1a943874b406804ade017f5fdf223a380321c5c98618afb3364209ac28d130f3471e4628473d2a3b23d6329bb11e2da10068d1d6b8374bb4f740919bd7f8cfd9a5de82236dd38332acc8e4440060f8be477c7209a745a4665b8bef4d7629a18813d7bd24ccbfdfed9021c0227a7fd1ac755f644eb46bc9d18c8ee4da56d2abe4595594fa68b54c09502fd59694cbae8dd33929c59fdf546020954bc63472b26f28b3e321f306c91256be8c02a7cdc99e6b43d350fba9d24df29fc8a93030f93776590a357e49ae0d03c40b5b300e499e90a2022cb8743b9e1af3834b633c8a8a6320f34c5d9ca0f298c6a17738ced89c2b4a1f826a3aaa76fa93303a979e3a04250ae7b211cebd2d262fb4533c9796e56d465e1a4f4db56c5027ecfec255186b21e31b5991f615164b2682d3706374d6caa680423159fea37b475770ec849f0534c9a516922989aeb2e61bd965ded4210a2b28e7fb21effe49ce119ac426e181f848bdf3fe6435bcdc9e114b5a6b0576adc87231ba064798107f311f292bde1e62a0f6f79c33bc7f61820d90d4ad01180a4a7096845f8ac9c2dd09c941843688cf3168216ea6e8bd5203853f6f6b5ab978950258a90b2bcb637ec844f9a96e8a0dc4ef08e4327e8e4a2ad493e1560b2da6645792b08d316c40bcc6c2f442727679eefd8fad4c19adb18e07f8bd74537f06eb9530330c0b992a91e0d95d6e62b3acb7be3bfa50ea9d83ad1d00a0b594a813ff505bd01f55381d386c0978bc589cb4028ecd7519d91144ca24b8b7e699c1975c5d5193d697d0240a382051943c2444c1a58f5867f364cb8927cb55b475c0b445f5a91821b175456bad3a4617d4e09ef4ef756179c06611eb4a49cdc0be82efeaaf5cdac56d3fa1507e285155d40212bb8af207b8bfde03c4956795f187a121cef8ae1750865d4e1914bca7710927441933b80c68ed16cd388ec6c8a132573a1e7abba1ad17e9f8e0b9a93c9d0db11e406c7983c42e394f500bf20bed9cfd781c9d57c2c3798e3b52722cbf8e603cd82c3b7692b38a02f42ad838a2ff4930ca63b8fe6cf37a56d9a48cf5878d23ef04178bab4855283bde449035a147bb4d108017f1aa777a72272aa7c389fc5a8db2170fd820991d8c5070834a17edf818828da398614b6aafc9629e4bab6c1547801a826dccdb79f78ccb38b8eec2872c5bc7d6a605831bb2c67a8c8052a65a19eb049a63606a0c1c2b67d82685dbfa6a9b3ab76dacf611c516c62fcfc8de84825393a6fb9d42ee38d3fc3c53da8557769b5a98c6528c5a50146c61927cea5089e9c4d596b529e74ddd3d9d13cee41bb744db68b753894486c6e897c2f5392e1baf303290dd72a530b8192e81c25c66cce8dbb0ba02196aa855bab08ab92774f8cd8262c3ef04f0658f30bf56142999d0f75997a18ace4045b5d1da4716ec64a3b3725e6f252df4134b2e405b41a06d669ed010ad204fb250861bddffe08f5486b81a01d95c5e5dbb01022cb05094c981e73a5c9cb5e9e5d03df8d04cb8129731b93d2f8f9916c2303010f75a17ba8f54f05a5508380159ff21084cda8d5a55e31ea9784b666c4306e82a2115d19f9ed4979b3774acdabfe5bfaff6874fb5c24e1053b42fd5892a9bffe38901c8ba110319cfeb39a03f769b67aca52710c7e88fd75738e1aaf37ed04b2b295326cadaaa6dd7346e530ef88484b1bfa769f50f672b3b82feb7d11e946dbe8c050cecb6ae9a441109ea7a17d7ca98694e4dd149d1e292123e28b31055f320b0f4dfee8d3e6e2520ff74245e1bfe344a8572c5b441172adc12a8f33b393bbeed8c0f7e2624b7bcb57b7da8a1f53fa5c8b599b7778bd85cb1e8b90cd987dea1170cc900c5e80db6450acc99fa4c8dabb159ceb1140a9d2aac75468400494e733781294e93b38273324cd3b9260fc7053b0ba0c69d78bcd230467a9a4ddc5d8c06fba4da7e2e4983aa63498dd69c04a5feae7a93f49c8d25bf83205a2cc0f6fd176d32656387f33c83ce4eb96275f532358b9e45bf6a2acfedf0147f17a310f8549884c3bfd80eb110ec8ce29cf41272220fe960ab2c59964e20a9d4d2186b84b560c40167412c0758c51503fd0ba4d3ba864308742c75b05dde782ef0432ac37deb30add13dac0e6021705649e82d2cb8e30b23143e04c643738ac69cdda9cd97f97cbfa1baae2b076fa78c3feccd32962bf2e9339893301b94f30fcb31e8564afb067320608bf2fc177624ceb4a1612a5ded969beec3a10e00b84db5e8180579da0abd40dcace57de8705d80ba468a365ccd0c8c29397fc10a58101a0b71c4524f8b8e3e85d6b720f08507542e71b29501daea58601a2f8c9ec9971cd07b38a18f3f2bc68ff3b6c7532d9de6c7f0a9a69637f427f2bc787eb8ff8e40bdcc9bf18ffad53c3610eba57a53abff388e44db0da5db1367e80b51fa709a30323c59ea2e0c4bb7c9b4ab335648d49149bd827e9d13fc7e7c977aa5d38dda79c7b9cb600b73a115989527e80f9759ce975fc544bf5e00454763bdbe29c47f2cc7d97aea1368fc44c3fc5d9115372a9e429188985911900c62fb737308e290cec5dd26a37a1d835414d620f17167cd15dfaf2c91c911c26ad604b79195bf96023745a32cff21320bd862cd4b8c382d92e8d1bd75e120e27905ee7e1748507fcd0fbd8cc395c46c641b4d881e1f4a7380a7abf9d64528ae1a0bd256c41baab928948172a5ea1c50862454782b70148575c771211488a495cc77e969c74b4fafe09711c6d7093ce993e1f190e4bc9645352354517c34bb8ca173ac6b7f6e91e56d16a6ed899020cdf524af9e93dd333b09b48764117ce6eceafa4a4a13702538e667f8b04e99ada0b28902161f7edd4f0aa5d13be4582d4553e409425091f71ea261820277a1f3b392e5e7bf6857c3aa5ea07f07211f31203572012654e67223fa74f351475492bfca950119b4a456c0d9a1938a56181b6ece68e5cb76509a6ff4ed0a797d5b55536190baab4be3855014104c153e9e00853e9596f5ba6e5fb6ba70a6c279c878609faa35744aeffcbcf3e230a0f945db6c042ca2fec4ba59e8eec387f50a2dbf815c10a229a659ec44b63b3f866979d17c97ab4d34f0e0819ae8f627bc5a03216b7c1738513aa8905c8763ea82fbf15d4a5b2e543a30a913880f81702e8531d9f9e3ba5a2aba1cc21a1c1201f9d6d3984d6be271c24567791a708e638ec311843eaa32c93b89971d0d0b948d414899597b7808d7360cd5cb10eac20be53dbfa3ccf25984240e3accb26ef25a55a3d4e40f3f83b978032d24a33a07c78b3973eb5ef8f7dd4c63c8c2fcad8cead4589323cdd8ad1a4ad21b9d087f2154d954c959ecf3377c2e3665e9adda15310fc12d38cc20ab519f7ae365737c9b6ff33512a8f82efc44c9b171fa73f85b7fb8241ad0a4d545ddd6c606cc18e70006e4e0d534815d66d7911161ba83add966935550e28e1d90841a64e494143b21c5ce32e664a6487df95e2aa585fc49fb8cbf32bc2be86957dab37855bd4c99c45799a1d39835a02f47f4bacb97aaaf3b6f19a82816636a2f80bea9e4942e18087dbe52b1ec92a8663ac03fecb21747e7b0d2da2cd2e9bb80cb5840c0c27960a577b4cda60e26dd948b112d9ae621d81e2a56f2ee0aae1a270146179a6593246baad2a5fe5e65d097c66e90eef4d04a0c2d4ca9f9863fcc54ac54d9065758b8609d958a5c65b7c49de60847170dce5e6edd6590943189853c27ea5d049dbb21ec523d46b5e0d14a8f528ad5c750ce35580c3a61b4a6e6ff68a6326b9677178fa67e84c60b15998e339492af73ad20005e7b0ae34c7e1146d7ecaf4874fa4ce97dc541b26aac593349b32020a8a6ba57cf188cf3607170fb101f86b4310221b0c20fa290d96e9c42111a286e64490c3dc45f2e8dbe1546fbede92aef6a51fa8480fa5b21785f59571792ad4ca1d7d6b3a5117b5d20c728eaf627ddaa20034a08e10b6538096375847427270c646ea1a797ba6880a0dd36e5d00490904022f9643f0b8fffb53ccc01546726428cfde3313d66d98468914c75230fbd51e112d38cc50a9c27e6da079282c651f9eacf760e907890bdd26f1838a9ff1915e20fd357008b0a166934f8bc4f83d8a71adb96ec23c6c25d95812a28d8924e8981bad767ae1e8e426b1af0dcb24f7306a4617d25598e3188525caef981c4dada077696cb01c2d3edaa301844ab3270a22addf3e2a8b59927e4faefe39db21ea7c8973ffe32ccfa1a245895c513d8f329c6a5d25126eb5e215f53554dfe601e5032be3b7725907d4c5b9c3f96995f5970c6a2e353535626c7e3d8a60b4f402efd6183a6cfee9139c5724dfe2bd5ea8e9c70c3f52b2e91a23bdea04675ff7651c06dcbc35215e797ab1df2a2ca2fd6f0c9aeed0700e01f5127578e807c3cc1d86d41f79f355b0124e0af71bef97f0b3bcbd17cb0078548b656eee0daa5cff37c2298827e7bd3b03050798b2517c069ca36c644bc8816d6c0875cab47d22fe0549d0bf73fbcfc44bd25020e502b0ddd32022b065a4b9f7daf4964d86fd3d8584856001e6c480cbb400da434149dc4b71f6b0d234497f840b36ff8c1da4638dad478c276773ce83ec65074ffc67ff750c22b449b4c40c040cb4966e9b7e56456f3cff643a34a5a135f976773473fdc3e159be69b6b87604e4b04385f9abe759f4b7a5859ce79531880c78018b2277113bec70c9fe48693c53402fd4c1f8cb8c4a371be0c46cb074312b2a2a1f601608bc56d09eb47c9010a0a0cb428865d521ff17b757f6c9cd25a9cfb45d0cd751bb4676869d73aced65105a1606d94c9a023731cf4c01e9dcfabb27a059678697ffd48937d81dad0bfa98590f977e248b39d17aa2d9fe2edabf51a2283f8583e17bdb0241992fd89a9007f0a923ba675320a45daf41b7f0b00490a107115362aa16f1570b9acefbf06645a41a3c868774c7249ae2b77375c94e93c4e9e2a29d8c43a60af2c1f9c3dfaa6a234810105db3795ede906dfc19b7e9eda26225e4eed4ce43e4a4e9d41a71556f507e78b55c018e6a6f6997e3051bda142d4b84c530181b93920c8e258c4991ecbcc8a06eea8408184b88def4fa906f59d2eb096532ea2ebac20f4cb79efae1814086516486f35de3459bdeeb8d9f12d5e95a08141638128c063bf2b52d044773427ef5dee7360270b4a507bc36d37c8881f5a86a99ef3abf51594c9bf079f391b59464c96fc2319644a5be30a1c009ef9c205b86ac903c21c11092daba944714d2d997e18b2e3502a7ab56661501c4c7cd210678f42b6ede3005168d7b55e3c5e70f8770243f9e1be5d489b090f30d9ec24a31eba25be0524fbe5b710306395bb930f7bd3ed4ffe0e2091be6495f4a40ed063bb2bd5ee21193836273069e9a82744d37c9d31b8d10d8b9eb4a87d6c8fa47b579519c85ff500f592df69984d18d40ecf2f3f593aae4493f84c69a6f3b6f09b4dd9bd81d3ea079234cf6ee08281cb5de7c6f1894c5d35757d598535a83aacf5352f447abbb3ed109c1d3601122ef7f6645bee1426f1039aebefb95737363a73999fde4655c093872dbfded9053c4752d364edc0a1be8668eb8278004bc7fada6e1f286512219da67f59ed3ee4562b4c3ebeed8c9522d32db4374feeb79dd4f9256ab82104d13267d0d5d05ad4de6c21ee569f310158116189063624cf8f687d8da80d2ecfcfb5390b568c8c941bd30b7d7d2ff1899dd792138601cda5046576ce52b82a112745d6948cde41b28493571dfa3b80b0ee792468bb009e9c9ea63c6397242dce0cc71666c25deb59e1b4f54ce16c610670b4343bdc5acf74c36b32b5a06f3e6cee020e8493935cadda985761b567cd3b394ea7a1a9c19cc3aa64fcf817e4a96a7e7fb7c81f6860d66213cf187bbc0dcacb429deaafdda5757a41d02918a122103cdaf0aa6558747fa4c32569e9ae490913828e10be0e6956c7e1317ce5e46823f9df6f275cd7eddde90f2ecc1ad638928e4f89d38beb5d7d661d44c3c099e22d5af1cacb7ceb3584d095a579600af5e8dcbe0aa166024377debdf49cc82cf03e2b786fea659f3e58c55917ee649b530caa93a3dc78abca9d0addce4d17bf5e74f34719e58249863107751ab8e964acc9749b568e811bc71c35d67faa6e904397d57165c09491ffb2bc69f7394c9923e83cd39337bdf7982a9ddb7adc1603da7e9b98b91a98d0f960386fe0dee54f01f3e8ff3af17690af8728ecbf7ad2baff247d9c9cedb8e07e04c7663cebdfe8f47d200e119d7a1b43d2e482ea8d9425f27fa32746a3a5cdf86fdb034d182e5ed8d6de535a8a16f27f361ffc852287fc1f8ea312737d35a0dac8ffbdd4b044ca5b84f3299c900854576290dbc266c830baa9966ae14b4f81260163ca8e9d0020bae9b32ee93aa93fbbb7a628e23782bc002c1846b87ccee32199a208c6bd33bc004c92a42a56640fafb6ddd2f34567dc5d606f117c5a6895aaa5708cc2909a761bf988c6e033be00accc897f2f0000c5a326d0771ec4008c649b4f25501305f7e2e6312c142ee4b47615bbd649479bc2be7d8572f091acf1358309109d61e47651738a4ff4d5d619cf0e6ae30989833ef2377ef907159caf89663eff8703c112fe00f10e93fcfe834addce21061a032aee988c762ecf6cc2a1014b9bdda2f7739425e0bc25a0818b2860b16ea7377aef064ad52e85f7ea3398ab808374d3e4efd9028028085ab7be4002b38a09266691bd868718505c36c11e13478ddc35c7b5691b09300c92ba25368fbe11030a2bb832adc916840d5069d92514c3cf50ca1b6083100c88e92b7cbf84051a7877033a754c4391a4d0316dc0f41308a79af4606db8bfa7aa8b00ee274b2b149cae04722bb7e0c701a088bff2d44399191901c69a394b2988f32b7e0e20fd1d0fa8c3772ea557c39c9e8fb553327e8bda430fa2aa3be8334156dbdddf01ad0ea21eb0b9a107e84070f1be01080394058be145811efe5a5d1502fd37c9082b369c40dccae3244282c08241d092872f204ae3aaf7b21fec48530f7b981f29875cf7d4a0ad91ed091ccd8266c4e45b93acfc37d34e81965d356ed50aa22bc7de55b26f49642248bdb744605deb38162d30fc2a0b24502df0c95d031fb3455f60964987088fc992d80bcc52f55c66421d3603ff9eb1555ee0e9ca4117ebff8adea3c2959c1ced5f0cb049613168fedb8aeead139b57284a1e5a62b56fa88ccdc54ca34fe7675d0bafb6831fac4f96869a717b688fe82f71f4117b846d1fd80eb692cff378cd599ddd4a3445fb898502a00abca687187461f2d34d8fc08ab5449785e395947e2ea5d6a817860afba6cfbf0723d100163adac3282cbbddde96d95cfeff4e85da9b4b6965bccdc1ce203c10f64a5fe6dc33a09439cbc5eafaf7d104c26d36912fbe55a7eb02aa0f260e9dd0f763d44753836033309bff29aab095be0fc70d944f494f170621abf4184f908d78fd945d44df28171fe43d50bbfc9cbfe7a7b45478e3d8c0712c9e6f8e509a5610cda471e6dbc1ebda75aa44ba0821262b16f9fe71f2a616940556b7d116ec7c0bbcb560be6c24add594b559094e0234e9302a6cfe8e38954615a3195ea7cdea8b36929506b793e50df0d3b98a89a6d823d0ea8123815e8f297250d33ac85ce0df2e55f01c338c547450abd85542382cec31a165241a29098ddae78129196488981e1b5e9995df3699f264e3470feab7656598ca52ae1ea71c6cd1c3f60491026bc4dc2bab9bd929fa5bfd00c1c893f6b7b4f04bcdcd808a619776144c89e308fd88681cfb309bd2dd7203da1ddeb6c08abdc051ad34e3adfc86539bee35d5a423ae301396736da34189d96982adbad2b031ece7ce3939af8dc4de689fd2aeb2e8a9ac6a2230034d6bacac920588eaad65a4ec5eb0e39891bec00a59b7cb957a0a49cfd0e2cb21fa43bf2ed23174592a41d33667ef5ddef028e86f899cbcd5b78873cfa6cf27c1a918cde36ff40d0ddf646c9f7d339e1a0ac0040ec096e5a64adad5b347fe40038bfcdf95d9a99d003dd8eb5185e0d9a7b31dd4decc53af4d2856649e62ea74e2f2ef373aee9d7cc15889473d724d809b2bbd45f7ae7a179ff682f29aafee556925d1c3080bb65f3db3388195d04958d674fef3b5528284231c95991735aa5d1b64f9300c8980c749d1cab0dee9f89b621299be67cc407da1e058d21e7df217ac41e2a559de995930b10342bcac95d02c7c2f981011cf505592aa2f9f4bcd1cb3028d1b2a82a79215daef167014ca99949638b3ca5903dc073ea50f486c695a35b43b98b6600a7cea88aa6ead88248013b7a48320c1bb1fbaadf40177dbfa50d2f32ac8f04bb85c2942e50f9a6a0e43d208616f32c26b98bed6a34ed8da24071facf8e199ca173602a6c233a40268bf9c9b4db9ac16d0580789f42cbd13331945f54bfb092aa2279e4f032e92cfabd04805a670ba89eb29349e015f36d34d3c9aff267f90f45db44489d070f39fc787de66dda5e6c32f371451af3c6a68417f6de7a4ff622ab3367dc64acf039266559628ea6a73935560b439d2750418678b18588647ec1c7a3cc4307c32f55ebf73480f7b5c43fb56d8c49871374d85a4f38c0fa2df8234b5056efb854814551030ed6a3a0c581bee38d40832fccd155c40f303b5048c0ca7d3e988fb81d36345d58afe29c46e29194d64174fa891b4bb45f88b4f249bce8df5e3f3b0b70b13abdff33aa42b5203fbcd31b4fd40c2ba096db2770a1216e814f9125e91b7f27c3c1b2fc7f3bc27de05dfcdcae5bd40eff33c98e7b52cf87a78e0e7b140cfc987e3adbe0bbcd5eafbc00ff49c7c9f17e4bbf16cbc17f8799e07f3887c37ae9507be40cff38aa8f703c90bca6a0396b7f26c3cf087676303c3f922e402af08e87d5feb73bd1756308463c5f33eeff33c2fc85d9ef30fc8786abca8e1d44b0f6778e185870c1174a96328b1458c303c8a47030ed5c3041eaa4ac09f7aa04287271e9c454b0f61d43855a229587ab8a00456ea9021273dfc107252751c5124868e91229e1baf89181e0a9accd6ecd084a6c0c7991c1704cd88f880c2100203d121241bf000b40606339a8301f5b1e33cf000e333560c4ec132bec17d5386b72c77ca8c6a7b8bbb23f98b9b1e87a1741eebaea5ac65e626832daa5c362e1b50c7ab89cb66452427ca84a69e974febe66504e6ecf0c09c960cd4ad74562298a6cc0d552d1d9fcb0604d22162a444d50d979c1204d261c2472484b505f45a50185015054b9258e6036f402c3f27a802c10948bc30c163c1440ca698f081415641398f470a6b832faa5a2d0c9ccc54b0e30885551513005005c4a64b4e8f9c1e2250a920f7d2376e5441d936556632842e9b950e4b0a54a105135ea08e16116fc71213ccf0c106c905ccbcde18808e0f873545743901affc17264ee0c1f2b9a172a3410b041089cbc90952f450e504235eb0201c32a3436259e18a2a9cb38643191d253b3c2e43564f5c409a9870e372d2ba79e958152d555911b9a1e246d58d6b4907ea65458812951b1b2b3950251429aa7c80404d6e5a301e3aad1b1ca59e1084a8ba7959b980c642156b15b2bc9696d60deb490b8994239cd3e6c8cb4aebc6054427c9c608d864e3a3c7100d825880e4b5c195270d4cb8798dae253a4b8a969c5c39a02b080bb66a6293031ea1535c36ae1b50887505349ae2b184ac7858160cd9bcc89513a5273f5e3716d8149980d48443f4f2018d80465a465421d10126b136605d6063041cb223c98ac80955a390b7aaad82c0156b849e10046005be4b28e1051a269630028815c89040a5a56a4fce4200e1030fb86c8172c2d2811598d1121f0d322842412c1cb0660d1198c181940c7a820071f110012e8610428b1207302215e4e0b4aea0220149103104103ff4c0830e5b7c8e088d38dc1005b5660e13d03e58430d05c8b028c30bb626e5c98d8b88186028b2c9409a2288803969e2a307cecdcb35440802a000e1cb2c4d1d2c5151da21002a0060270c26552ca1841164ba70296af2a30738012f4ac0454a942636ae56132924c0091f6a10000b231460871d5ec8d224e5c9911c4f8e551cdf1ba01aaac6770668c627062b0c4f031f06be0bd87c9163816f029f043e2abe26564b7c526018cf8887c18b086b880e90cfc75781d743e4c1daf9442f04757c38ac9bcfe6f5fa5c2ed6077a2bcf6bb9ce8e9590951555a00b5520961612eab9da302114a28ac5ca7169a00113c01e92803e728038a2b5421588c5c6829b213fca54d978013f585d80b35b392f2a2fa3d6cdeac98f1bd61655af28aa7a8060824dd14a073672c0a862ada962e504b94055ceeeb1a2cae6878ecff58425c49ae14c053840067002d842d50e9e1d3cab2051e8f5435c73026b8397910bca6ac70aca0b88aa155312a1d64d0b89aa154e2b0808849364e5813920d00d15d6062e2fad20ad9b56989c1e393d6c7ed858e0f2b242c109ad202b264e60655195411810476519100161393b5a22e8ad825648a0538e8fd6e7eae212e26a6255a0c36be1ac5860ce6ab5fa56aed56b657333045cb9b6b8767638dd88ae96cb5bb940225008f45e4056424cb0c1807544950e244c105ba09057d209201827f4c8a8af498119194833012f2090c569838a0106a5230ea214e1bb861a4247c21d2fd7970fb234ade9c20214c0e085211b0630e50630302081072800014b0c21346a50c3b95fd0b90d12a8e1c0171670c001c8941002081f7890a5a90a150c2ef061011553905942891248901119822f063ac0f080185cd59a149cc00d10bef8b040c4c003b0588002100045132f04400559085eba64695a9240175844a00a1bac6c4003127880031460851040b080421912a8c1c0170ab0820a27cc547db9a424eaa829830c3020e0802912f0441131b8f0a52c0410be7459527a02fb117524d0050474c02106335f86d0c11215a527301d3aa097418604bad0028b2912f0441345c8100033603a588af204564494e46747047d4d196478400b0860e1802912d0441132c4100017cc8c210303020742e31c7260b9b243470786c0d0020b1e7419f2c10516765449d8ac59a5e043014b8e2f8ed711a0115f11df109e922fc96ac81b7182561baca805187c433c21ae202b20de0fd08767810d8f9d9d5508eec8d111ea04c9018233e446b471bd5aaeafd562b1569e0771bf09739870779dc73769755f9b73425f62d2de9388bbe7ca31e32d0f73b884394d618ecf0ff5a1f44babafa5dad26de9935daeb9de485a2dc57103878d10870cf719b55dfb371dfda151a0509f2850e8b5b6a7e13c11e214e13ea3354cb5a57b4703338ee397252bad2043eeac273dac23ee8e818738465ce7f1e6dfbbe684f20641100457abefdbc0431b316cb8b876eb2f29ddf852fdb8a43f3e4fe613ebd046091b1bbcbcbf2bd5a1873654e5fdd0868a0d1135690d539a712dd7ade9db6ecd371b50e731df48dce3849eb4a6b639c1dd2f70b719e185e6b5596b7fe61f77f771f7235e194e92502934e79344e2c489919322274d9c307142e4648913254e92381972e2c4c8c8a8c8a88911132322a325464a8c92180d193929322a2a2a6a52c4a488a868499192a2244543454e9a1835296ad2a4099326444d963451d2244993a1264e9818312962d284091326444c963051c224099321264e888c888a889a10312122225a42a4842809d110919325464b8a963459c26409d192254b942c49b26468891325464a8a943451c244099192254a942849a2644889932446498a923449c2240951922549942449926428899321a3a1a2a126434c868886960c29194a323434441ba21a7a7717e2a10b059d47dc798536aef6199360dc5d03f0cb0d90cf8d0f0994043a02a54037403723504a2975f70cdcdd88bb17717790c7354461ae9c1908c29606b27077201eb69698cd28ec066836a3b05d7b93d29bd9cfbed96ca674d7def4995158f9a6ad355da34489c2a3ff46ee8aebe1095b3b2a0a74e408dd95962f45da5b732d2777582bc8dda9872d1da7daee9346a9b67fdbfb64d2e19cf151c83ac37d1cbb70779787ac07b072f01f0a050a4de243ff34eb149dfdc54d335ae9c574f6e393ab59774f18ec62bb735966b47cd3ac240cee3e86e01cac9355bafff8e4b735bbef974b9bfc30b42c0270f72e1e8273b8cd82977bf9b88c6b66c5d1a4a52c45ee2ee4ee50dcdd030fc1289d474de6ea641c0a2a2d0edafa62d2dd7b90b9923966b3471847913966a424c9eb7ae4ee3cdccd7a6eb296bfc9cf51296d6bb349b76dbb8f45933976b39df82a6ddcb47192ed5ad3d61cb5db399cd2cdb2317eda5f65dfdfdaea8bc9bbe4ee629871ce4a9ff1862a7fe7ce9c3192fea9fa584a6bd6a9bb9431951ac6d9e97e95ecee501eae80e83cd2acb933a65577dfe1ee3a3cc7dd71dcfdc693c1ddb778f899e177fb16e0ee14a6cb7b621aad61ba4f4cf196e93c0e8d49c62441faf78e26b4448911f94e68a513274a9a90e42709c26172a49db76d6dbbbcb5b57d16c2b65cc951a84a89a02d4ee871f82644a340a1fe7a1955e0e1477d5b33a7e96cd7d98e879e09dccd1a840f0fbd27dcbd87871e0f1e25bfbed6dd75dc5963bcadf358629c10f953a1e7c191bb5be0a107e4eef74d0a853e8d96e6896f9afe506d7d28d597d7d29fc2b8cfb25cd56977b771ff4a77ffe1a107ea3c6e5cade5ee9e679cf3d35b69f9379ffb33bdd8eebaefe3decce12cddb6e64d6dd7ee5d7119d768fd5aee3ec443a77277d1439fc1dd75d8ace59fc274b72d597395d479247f4aa6ab5988cc5a88c4a6598576b9669a35dd9db502ddfde3c0cc17073840151b1f6890f8410004c63875c08aa89a0488d0c831957c784060e4050fdd6b8cc3061134992a006163d4f35380245ef000b2c2b8cae924231c843983c20faf0a00a98a02c7d15455c57c05c48c0d38384467bcfc4b013473c6a3291085600d2b72de3e622c1ac63525c5a25e071e134355d5b3955a3d521db4284b0a4b6b4bb5a817fb5c3ddb8bd9ec10426506779c1d3478d4eb99c1a3cef3ad78563c5b8a45bd954dec6bf56c2f06ae89b1c01e2a7c7bb1af870a6f35021c213492080defcc19907ed4e3317306ec49420c07522b2ac6d174c159519c55cf151c5f8959992f332798e07df403bd038020084a7dd4061c33f55156cf5e51d62ae6d1970be49162f5881913b3e9916a51d69415eba32fa957cf981808ae562b70058220b85a510ff43ceaac2270dcf3bc559817b5b1a1ceca1ba4ada98fb6280be411f32326f5f578cce31163c58bbdc4ac3cda23664cccd52305d28fb65a3d62563129a71ea3ae7365471538424000234408e9f5c011a265b5e3078f7a3d3e780a30b3a26652203a62d13154ad332dea3a5bfc8845cb90a9aa02573cad9e174f1488b3c307779ccfd3f1c0a945573cad568be2b8ce961665f5ac7870583a4e2e123ebaead2a2df9702092dea1d7d2e176db97a52c869b5563c600a24b0a87704521c9c1565f18c2173c43a5af5a4907304d2291f4bc989453d58d1564f0aded18e35472cb0a70aef49c13bfa7aaaf04a687d200e38ab2216d107429042c1a1be2ad2e1f9705639383cded1475f3d551ff5e88bc78f7272a88deb06a4371ffdcc8034877a3c24d850efb3d1a17e94c343a6aaaa8a27050e8e56f486c7cc19ef684553e0e008a4ae23440b487568d4ea0887474787dee0d033ded10df5e88bc74c0ec5f17848b8a19e8d4749c0a15f4ec8a323848a8bb6a45c3d43ec3edaea1962b7a21cbccce0d033d48c0e4d811edd504fa7a76a453deaf59859d19b9e2a3329d0231dbad2f940878784125614a7a76a4575b4e844ade877c363c373c63bb2a1383c6652e0e00887e27c3c3a2b1d25215a40aa433f58d11cfae23163d385cc914e0f0994841b4a020eebc686278524c470106b51ef88888d508caf89b57aa486d87d74889d102d20fd68d48a7af4e5b4a274f5b5a4c0214a23fca84789ca086d70848000867aaf55d19831257c74454bf02848c72411e5a22d3ac6cc197a14654359b48417bda13e26057ac4ea1953428bdef49ca1472b6ad353c2ab87455d3d25b43ccaea29d3f26e06aa8858c3c31a1b5060460df0f33ed002de1c4f78ab700e1318f17e2001fe00636172629f8ff7792b2f470e325e46af550370d05cc941220e35719cc1c6cf8e385a8863863872880388389088e309c7f158211c73b88ee741ab1d8671a461f580ab1c560e1c5a787050c08343031e1c6378dee781ac9507470e7000010712703c010715703000fc5a28a0f15eafd70f9082a0d11b676220f8860dde8a7e3a5756d4a3204501e7fbc21ef405d27ae0f84794055116a0e7795e16edad413dd08d39dc308197e6042dc4da30b3a24f629ea6046956ab96c33cf6a238613eeaf2c0d8176bf57c445d84ad9694778515868c7f06d820063aac0d11ecf8d183c562adbc1ed08d1871d88f188b4533f0271e63d1230ef3d80cfee36de4f06a0388b00d247eb4f1046d638a232c26aaaaaa8e38720aea286d10c3017df5e908332616c67b85f9562c23282cb0e5020816b96898bd22dac24c122b6a26057ad4ea49e33908e6803daf2f8ad6ebd57ab53c25cf080a96b0c7aac56ab58c5a2ed7b76ad196165e822c3e1c56192008b672a07c3facb0022c232deab2f96c40b0f5d2c971b9e84b47e7f4540163383d2c0f02691530e6eab90163208e0dbde159c16c28ebd371b128c8638487d2d0c5fa7c86582c106c5110a42d90b66e22f0b2f93e9beffba80d0fd0f7b32a8aad98b810ebfbc0d647592da0cf670512f9a409d18055d8785ecf8fefb55af5f8b4284be7f54d59794e73404f07a43d3c90b27ae440f3796872c0f93e96fbc4b4cbf5ad58ac6fc5ea01f2201608aebef0c2c603592005592c10044116c862816a8a7c7c7c5e2478c3f5234c8f21b1154841230e6319b91c06d2211f209f17359263c40b968d0dcb8605d2cf676504054b0fb0e56ab568185713578b7a8cc562fde09902335005c3ca87e903af7c14a42c5087e5e2a161fcf359c57af8be8f8705027d31acbcd56a459b4cf97c5661c80611abd54b07908d19b13044b195b7c3615e0f140f5a515087c3be1ea75032107ab18fbedef83e8ac3e209c3a11378432c4a8d7846565febd3f97ac2901a617d4394d230fcc162b1a6f8a0184531f2fdb0582c168b0d13c4582c8ae5e5c35a7d3e465f8fd1e7c3a25f0fb562c4a250b084445f93152513db1ff5629f7b01c6c7a24ebf8f05ae562003beeff3becffb28a53f28b85a7dabefbb22c4f1becf410aead06a79200be4a2f5b558dff7b1be6fd5fabeaff57d9f1cacef6bb1582c568b25c4cae359ad56463cf40182e2b55a2d6aa4b55ab5807c82a8cfe7e3d35ac901ba3e10044110044190822008822005bfeffb7ad0ac84f0224aa91b3c0c7d4386b0f156dfe7f91a3455aed60742f1a0d52a8afb185d11be62af1e9607b97a7a8c7c3ee12aa4d4c8ea034d80b302f220162bc87d5854c7a31e0f088a7a7c3f2cca3a0ac3f8c45814e88302bebcd52a67f5d1950b9ee785a18a85d92b4fe7079c149c112312f33428382386f3e540f97e563a4a203562a4275c03430812690152d0e7c7eaeb6179d0e7638445874e80650804b1d547575f48519066e5a373586fe01039200c12c359812b6781617c629ed7628134e61ae2412bfa83e54d88b8cf7745e831d0d56a40f8d12ff662391112e147411e9607b128cecb49c00409a220011a17f298072420c27f5ce90b12033d88f5d9bc1810b25e37397ef4830436c45674489832622b8a839383b3001d1c34e1ab47478e8e520ece0e1d0a087758118a6115e18e4845c863678a1e3c1250410f292ca8208ad0870508087ff870203f5e41807c3150489055ec3544089121de054430b8c0631fc580877a3d3c457a3e234532300245e8c5400d32008f68f0f239f2c5c01f9f55ec85e4c7db0089c73ea00dbc20a06f0c3a40e8c5c098d0f8443824648030c9902b49e22d51f2112d71225c31216aa20993a22646454e8c604e9ec0a03c8902454a140ea4f8140e9a884d398a211d252129255151aa42c54a95252b1d2c79573ac07285094b13539626a72c9f16a7272d504f51505ba2b86c61225c75e1e241172f1e7ce0e5cb07207c01034208606421cc647546ab24ad24bfacbdad76b3b9bd85b152619630a570e6c5e19bc36272598cce5b9f7bead44d51e9765422ecbc114418330209634a20814c0955644ca83ac18432279829838299145000400a6700f0a9706605155858a105165c6821002ebc10802560782106186488410032cc20001a66a8810625c2d5006af06c1800016cf86e20000e37ac72c041871c7c071d0ab0c3c743017ae0c1871e7ef0c181f82108203c2182580d210411437811441851847784112b248ef0d89704125e6ca544125fcc975022897015f3623945433e6f091d252442228e083feac57c3e9f1f5f8f9107093124844e15108cb55820cb83be1e222342cf0b42e7eb212a22043d26dcf38cf08ef090f092f09af098f868921a38fe799fe7b570c6f3bcef5b83873572f82e80e31f0e0f26741a6081173b40b0461b62190b66f011f3346a8c50b3001e70887e48a3c61b3e9e6d0905c230ce819847b178e1feada07c3e1e1aa02120cf89ce84de43c641f0cbf11ea0c762008ee779dff7b97b40e0e0b80750f0267f8ad69f9bae5660b028baa3d1f2f77dfd39f7d98792368737adf4feebf6d3a8edda5cae32d1716b6bd299942828331cce358afbdba6da34edd6f42f7e1cd6f47525e9b574eb334c0d07d9e8c6970a1fd1719731ee7141d7d2b7e56a562ed8897e356bd7d2d75697ffb63149f712cdf46934ffd45ffa19d73eff6d97b7d27b526d29eeb78529456569a263f925d574a4589b95026dfd369b44c7dcecf7493ed5cfe88e46f743d1b17cad9fc439476dd73e8dfe6c4bf1d64774aee27c68ddb5a49a8efaf1a63a579980f2dff6dbf0a6fbcc99f547ffded1e8b52399716de3283a262de56a9ea4ddb9e668bd96ee7b6292acd434cf1afd5b147d5c6d223f07f5ba7e26ef128ed2e57f1395128e7a1c46dae6596b9f7471d3be2746aafd13ceffc1c54da6a5629a158965dd1d8b874dd870f7809cb9e97a7b7d52bc29fd1237a13005d3665398a64c99e264cad0142153804cf9e1394c7f32f0a1302a447f32a01428030acb610a9401f5a1140644c17cf912931c8c117290c58f4f95c1a8b62deac98baf0c06d3175b2dfb8c492d2b7feb33ef7bca7027ce5e8b334ffd7947deb7e5c7bd6cbfde16473bb5145969a6596b325b5050d0eb7acbc9ec364f9c4c26cb057d142ca25c41f1a67f96b4527a4f4a0d308002a8a1608a7c09a354b9e3bb7b29ba7b29d9a6fa1b46c1e2ee3f40bb97f259dd70d3d5ca0d06236dfefbb6fc657d2bdaeabf5f85fc1cd49317375d6c757e2e545152e4df90b4bb7ff1d06805771f8f9268ad96a5fc8cb73e738e7eaef4a7bcd527ffbd16484bd5cf694c5e4c526d6f9bfe131393fe9bae56e8f8e3f3b72d82d574dfcff944a2e3c6f8893e3131d1ad7f572cb4525d6f95eef24f298d74eba7d1bfedfcdaca68f9a59dbab6dec86be9d37de4e6bf45651c6eb4a3c6fdb61953fd53f5d26ba9ae375ac3389bb8aaadac56dfa465593fef5e4af6b65b6f24cee69c2cd737495892bbfff01006e44e6174574af1a6bb975aade1c4012ec3597cca82aeadfb9e1817f4a6547e5cc6e1642b94c18510c5c3221e34ce2f02b55dbb332fa6657d93e24df5b634baef5bd2d66aa7a6e5ef9abd69af8bbb971e36190a8baa0c2d094109bdf5b6a9d66fee6ea5fa71b9ea13dba9cf4ebb3e6de7fa379d475a509322dc71145f8a420e6348795cbd34474f283a8fe49231c9b82488fc1293413f65779db2f94b99aea65965baca6ee4ae34d99319d77010ce864cc6b853d89798a46fd2ea8dac9fef49cbcfbfef5398596ffe7dad69da9a7e5bcdc972f56fb2fd5a4b917f2365fbc45f6272bfa6b2b5cf3999695aa97d7fea65b76d7532a9ec01c0ddab42a14c0aee1e0965aaaacca040c68cbb37a68cbb7782bb47668c092578644a70f748f0c6d8b48a0cb183851934eeee1d8800cb147fd99866452a2de692df342bd2b5369bb3ecfb534a2f55b7be5bb6ae26762a5fead6276d62ec84c34d17db8d999ebcd59a666c6b6bfeb6b72eb66b37e662a72e4692ca15f74fb5bc9fbff64fa5696f4836cbf43647fdae1b6a6b8b25ffc6a7187bb76cd356d9e5df1b63c93826ad73364abfddf7c44736abdf5671f6b3537ef32cff4642d9b8fcd36ebf2531d46ee7ac49e5a74c8ca4f55f1c2e6f7d322b6933deda5ad14fdeafd5b2ecf24f6152dba4ddb67fad79b3e096707942e94dd6bc2f46dae553d7cf513557de9aab3c0df74cfa7f6f8bc361a41bf951a625ef89917664cd24c67d92dde6c94415b55febd8be8fdb55a728ad9fb4e55dcad99bcef8b6cf580e57b7a6d5a3ddb6a6dd5ba05aee1e0b74f756eedee7ee9ebbb7668e14b83b0ae658b366cd9a356bd6ac51e3811d41d07490f3317a18a2e6cb1743fec5131e8a5c98809167c1030748d8a20a0f9c8a2b2e807326e70c6f22072a649600c37d882132f8788115ba0e4faab0010d32ee0250535ac00010b0e22c2469428a32f3e0279c004a04d504463e8213aa31608059c0b59a2b72222079c271162c6dc1ba800baffd60860d178dfa4c86a812c86069c13fe04009ba0000f9866f21a10d1548f0a83be5a4e87abcb8c2af2ce14060e072e15598406181230120e1484e85788c0039073750a0044172e44f9caa8457c4c086177571802a3868428d139da06404272035e0496c38a091a901f27184391e20440a667c032a5e90522543e93f1a4041e29e1eae0102e698a3042430c28d5c1eab2f4178c07ba86c81238384294ea704e141024202fc020fc439c2a8220a1fd2c2075cfe00250fc281344e24b1011138102a1ec482a478c07d3c008c27b528a15b30fb3e1d01c2f00a74c24c816abe701e5d5c4089129070f8ce0f3e2230fb32858b2a8469962815e021990330a143180df88e146e74745140e83a70a0330001c305ae630510358cbcc0798e1a45e028536081aa1c243b6f728348154e0e618e38be38c36f1ca0c119392d3e54ddf400f5883aaa729b33150880074b44f8eb0d165a0841115ff82ba705193d607155b94810a2f000244bd0b8a83ee019c00808d0b85c5ef870ca41cd94960e26151376e4d0b49c3cd933709929ac0d14e172058f31ce3a018513725ab8d0b0848c24d1c1ce195560184dc810430b4f54813588a1c38e1826d0805a2e0f4e4a48e26068010d60304064ca8a0238802047192e61ac6c6040991fb43853b5a26db9917a5a4357421828e9e18703f44b811a2c3ab88d876f0237a87066800c0f9f106c48e0cc000c345f095ec430c48c32fcf3a2230401d040c1ec6392c20f09b373f6f5f8f184c36ecd14cf04407851c314aaf23090e3816c2799e24d41b48117b10d26bc1ebc0f5c5314a8f2ce7c30479014270c79627e7cd08d71a5cafbd265ca122c47428f8a042cf000a80683b784840278f174c6cce3712ae10c1a95175ed8442a031c68000f9e636004cc8d4995ab910304264dc8aa7c0c2a940082891639ff620b0c0930a2829167214512d59305d4a9d800174e78651879134d883184a7a5ca87180241075128e0e43a3479000910350b9fe1234de004151f67e18887324cdcc08497711d893064051e7c042b535ea43ce1e33a056084b89d271c572400739041430e5e63c283924106239fedc0438d15de08fd83333c2a2a6aa2f02d67beac41abf0c29d44708df0050b16f02b5130a9c1880788bc8a94595e8304261ce9450501243471c63910cdc05de08a0e7fd2a584264c200081177d41c68925e220c389ced470e4869b1c3c096e07270b8ce1858f2600f1443411c2375880181d3cd57082ff6c808010c4f18305ae01154e5c51039a13dc4836c1cc65c108efe9c00d0fa080c790d3a4d66e882e0bbfe00b03dc2813a5880f81010423527c013d481844620fe8d1c0812c4981104769ba0f2d1ae04511111db78096002f661b08f10a76c4c143842058701e13e08085a33796f84e1020284981dd719105256bbc1a21f0d0040490b141d7c37700600425eca0a1e33a7230233785180db88e021c608610379e3c670d288268c30584e76c500230644fe538ba015356d834f11b2ca6b2448004a5df14b111b1c11983dba83087084a700002fe82c30711e05c89c25f622680a68808d6b88b0d170604504214eeda6df1441ab013b8eb75844d50ad89b776805541f24410de82f58862c464c7591bc8c0136b5838c1596592fc80033480b384b0c0608ae6c241319676590c2b1c1c404e052b601839a8a50d204168910107431a84a4b921f45505766e5d2c5184af08a0040514e078e02b9a15b80700b1c65742277c51f2a307ffd630d930841948f8370136b42461060eff8650b23519f1c23f3217541942c60dfe79e172850f4e49f8c7448b36c41cac7f3dbce814c1f1807b2610634a097e28710f034a413ca00136f7a6085341014258e25e0f443d7007d8c1bd332b40a14651927b62b8ac50822139ee7d31c104213ca8dca3e20606d248d9b9b7e407d7132ad8dce3d14ca0f161cabdb08b0a42ca18c33d9782c50c104bdc49f00309317a38701f230d921305d871ff029b2957f0e29ec5049608634607ee54b08145911cda706fe20164f0e8c47d0825286e7c2b771d685ef8a2729fa1c72b19e1e1ce02540c60b2acdccbf85072021408e13e4297d3070514e1be73e4b0dd10c51d37f40517ae22dc6b54907152821ddc674558808b5c18f70f4a108ad0e4343194c6b9a66fe6ee4e1e1e4912439f949f4f9bad5d4c525ae94fd51bd594d58618fe73f34aa0e1f6e4062ddc2b4107a327554adcbc12361005ec880b508fcc4f1a2713f821c22303001b8ce038c0cd2333471646432870c6ab72c1a9484a4f149e09517e349931208d77828f344cc8a489c22bc34345c4030ad47866ac0d4bd07031f25090438d1230aca01e00cea049d6fcf0f25688319184522b9e0b47c218e240c9140f06285e42217441e3d1b0c3692c41270bef865a55900eb8303c1ed8b00244c5088c3c22e60863030ee8f0c273228d131239a060f0ac20c11b4882e03cf022a0059a1f27ba2a0f8c149ce1e3010f67786e6c21e288ce89303eb10a2b1ad470459a6fc9076832388006a2f840a02167c4026fdcbe17901051c4043aac7c1170322bc2834e5901f142448f122546ab290578c1a305ca6c8506541a9aa372063cc930c2e0052726583d51682e50e28334ac36c4f048a09aa2777fe562abf543d9f2ac17e3ecd32aedd66cb35d2b75f34fe12af96bfad61c7571d3eec6a4aed5f76fe4b6529fdb45eda87076fa421f23ee9ee3ee373e3d7c42164280c14f664139c2dd3d0f244001267e80f1450ceefe79c93d1df1458f352570f78f081f8a70e231451645eeee0dc101047a7a9082e2e6ee1e07327cc902830a3a50e1ee1e0356642ad0810544b8b9fb0780223d0af0810cb4d8c2dd3d032841731d2a84f0e1eeab3646108213483880002370f70f8b120765b070848513b8fbaa8217bc50630036504101776fdd40c31b9e05602998e2ee1e196dc430668e017ca0c8dd411c662e02dcb6080386fb97f8067d89afbbbb77640677271281f727376871b331ce3a8f34a12ff115ba96e6ee42a8c119552fafaaca84aa9b2af7aa2affaa7c55e52a54390b2cb8dfb88dbb9f715fa1ca6daaaa4ca8aa62a1cabdaa6a85aa133c03203cf0bcd048933baedda52f6bb6e5b66c5db1d86def9208d6acd75251da99b95c4cd79b53d4932ca71f57ef266b0e87916030f24b4c4e2959724a91e5cf51559c14894d5a36ebc65b4655715415475a7d6dde542f7b5d6952942c39a1e477a2aa387ca3703d61f23b69184c8ac4e69194f273399bb786e174d541c9e2218f936ff273c624ddf86a4a6b96526de9dfa2e893992c63d9acfc9bb6d7d668fed2b4e791cea3de16674ddc6fbd73f79e7c6bec846b776997f1aee5ad4c66cd9fcb62565cc64de5ae3ae9f7829b16e0ee57dc1decc06d75531b8662d179dcd7e2dc9271282809f9608ce813a3f38b14287a579dfe6b32bdab39adab89742377cd699ba47535c92efb66b3afabc2dd3ff010031a4c7bd2928c49462541b8766dd09b95d4d766b5beedeaa4f36856b382e0e105398ce37864bf09b4eb671bddb8898e9a9ab59a27695692deb6d551dbb5e53de9e74abf0bccfca9376555df9a374ce7f1a6ebeda6eb8dc81bee3be39ba618ef9bae370a7b9d7fea33752f7f2abffea7ff4c85d4160783e9fb5278c3605a9fbb6ea8276f35ebd1c556e7e702e5ee4b56623c5eb9b4df34ab130e43fdc54d51ee4ea99090d0ef9ab7a69d5a88bc3b1d94cb5517b4ffbea5e1b008414c868476dbe69a2bffa6ebed9e30d8ce38973bb5ec7135d7de5633de1a067b9cddf7cce1101e9cee4a7fd7ada9bb2be94479380448e7b1bcbfa9fe89bb87e0a1102afcb7fe2bcbbfad0c87734d467e4e0b913f25b4847c1a49d6222325342743b4a2225a7d2323263f4454abfdd00f91b526444e6aff4542e4d6671612c283bbc7dc5d5f6c37f9a692c6b926779fc241fed24ee1ec54774d72f72858e8f772f7206b0441836ac8c756a39a624a58b264397a52aaa214d5b4b47454a569e9290a8a4629452165c182654bd393d21295a5a826a5a4251a85a4d4c151162c31a5a724a5a8d89312d3962625232a515b94909a94b66851828a5282226d7edcd9e4c424b6f4381b8514b5e57136aa7c11ac59f3dfaff226cd6acbc5766dde504e4c47417ab8fb17da9465292a86b484c5c92906e40cf72f146989ca12929213545094074a514d59908044e08af23779e69fb2dbe26cce012102481021901cdcfdc78782f94295a2625c969e9c1e67a37ed7242af74dd97d53f6bfdfcce1307997cc4ada8b6f252f6e622a6d16772f2a2d2e6d16dbb5f9cb9c653f5496fc5359f297f59fbec44cb61af534dcd964ab51eedee4dd9dc9ef1dedeaf095ba5b6c35ca76edb515f7d9dd85ca2f31f965ddd5dd898c6a7b8bbb8fb6baff342b17775f72b1dd56e39cbb2b49e2eee5b5523f427717bab8c9ddc7a0c761285f4d0940081b511039c8c5b54309a50deec2401537ac983272af0cbe19c0121470971b3c2c8d300a59e36b44e0ac1a58285aa2c600f7a810d70ea008ff704f96e860e7e3ac1a560d58a20113677d6044d20898a8fb8f0b090b1b487197e2428b2d6a80e15e03c8c0e044cb8cbb801c81450f736471d0660133c81630e5ae14b280018438d6784b0b4e8d92c51a7757fd36151ef817022b63970a38714781115560a6a4c1a24dba18038a35ee728205c90394299c65860f330fcd8f7b44f83ca9e056e12b262d7878a1fac1bf29381d1c805be32dddc203a6ec74be9240129a55d593af9c9ca6907440046fe13083064e90c08b7f5128b102ce41c45d28d0204415961ddcb546083954913373ff2288395ef8a183b7841ce940070f4c38d0b559b3be0856ef13ef2a160ec29dedaea66ae62e9ccb4107a378eee2c12b59a2e7f21d1ecb024f87f7520216f96a06f002d7f77ab1c095e7b98f6ff4cf3d2d20079ebb9eb07c47cb73cff3582ca20fe773cff3569f15cf73799fe7ad5a227cdee7b93e0b9e3cafe5b9fcf374c6d5d76af9dc500ffc3e10060ff4bcefc66be3f36c9ce581dfea3d2fcce779df6bcb27e4f3be16f87921f83caff5795a3c1f9eb7fa5e0d60ad5c3a3c0b3caf82d7cad3c1fbbcd6e779df4ae67de08d0dd197e34979792c23ab289eebfbc024ab9beff37e7634f1460093e05059397d0efa178407ae7c40d6e7ad9c45833786e79f8761e89e7ba0f7796a78af0f89e77dacef5b79de124fc87b7939dff77d2d24efc8f7819f37b41a7180bc34f07476827c03f0581f100ff4569e7f4e1fbdf140ff6ccbe9cc7361e53c0df07c0a16dfca6b79a0cbd3f156367cdf0bb6f28c56def781f4cbb1f1f1589ee71a02936030e47d37add7e7f23c98f77d1b58b53c9b0f5c4d793ddfe979ab20cf06e7f35e2eef9bf25df05df01159f9cabd1d5eebfb3c1d305c811f90effbbc9607d63c1b231e0eebf3589e8e2221df920bc7e6f368af1cd7e769f15e9eb7fa3cd0b3e211f99c7c433e9ccff5b1bc95f7791fcee702bd156be50543421f3a3a3d425146868bb212b8285be3e26c878bb31f2ece885c9c39b9389bb938d3b9380b808bb302b83863c2c5191a1767127071c60117676a5c9ca5c0c56a818bb5888b55898b758a8bf58a8bb58b8b9574b16a176b0a2ed6195cac44b8589970b1a2213244489070274890ef0499e23b41b804a9b9fbcb46c78d8e17d0452f392e7af9e1a297222e7a0172d10bcc452f4a2e7a6972d18b072e7aa9b9e885ca452f29b8e8e50517bd10c0452f45b8e8050acfd90152e53b4074be03a405df013283ef00e9c1778024e1ee3a383b3b7038e3210e34788803101ee280000f7170000e5e7888431a0f7108818739b88739ec789843110f7320f230072477b7e0a54347c7cd6b473cc377c414f8ce8e10dfd929e23b3b3fbeb353e43b3b567c67678befecd07c67e7face4e09beb3d382efece0e03b3b41f8cece12beb32385efec2cc077762010eee84143141ed2e0000f69c88087348ce1210d6b7848031c1ed6c0f2b0061e1ed650c4c31a843cace1098e0d8f9d1c23dfc9a9e23b394fbe93637d2727fb4e8e19dfc91980efe420b1c3fdc78d0e1e54f4e10a226a4972510b948b5a4c17b59871518b005cd4a2838b5a9a70518b025cd4420117b594e1a296375c7ceae1e213918b4f4a2e3e7171f1e9baf884828b4f33b8f834848b4f50b8f874858b4f5db8f8f4858b4f1e70f129042e42b180fca88085325c64210e175bd871b105212eb630e4ee366278d3bac9e942878b5dfc70b18b222e7641e46217555ceca28b8b5d8071b10b9b8b5d8871b18b115cece28c8b5d04c0c52e04e062173ab8d8c5102e76f1848b5d38c0dd71c257053b3b3a628e4e8886150fd180f2100dd24334b68768a0e0211a347888c60e1ea2818487683ce1211a5a788806181ea231020fd158e3611a403c4c63030fd328f2308d240fd370f2308d99876988f1300d321ea6c1828769d8e0611a41789846123737e08e1cf10411b87842095c3c01052e9671b95886878b658ab85826c8c532442e963172b10c928b659e5c2c535d2c835d2ca373b14c1917cbb4e0621902b858c687968f17a8f0f0050778f8c2161ebe90c6c317d670771d3d6e76585e7c8765f31d96f61d16007c872583efb076f01d16112f177a78e802110f5df0f1d085200f5d60e212b7b8e2e216d5c52d762e6e11838b5bd8e0e21641b8b845133a3737ad1c7104352e8e00878b633e17c7ec70718c0f17c7f0b83806c8c5314d5c1c93e4e2982617c740b938c68b8b63662e8e315d1c73bae3d8bc76a060e03b5090f80e1426be0325e63b503af01d285b7c070a18df81f2ee9ec3c383c4a07818b3e261ac8b87b1301ec600e061ac079b1cb101402e3680898b0d90e26203aab8d8002d2e36008cbbbf5eee363a44295070510a175c9462002e4a510017a510c245299670518a2a5c94620b77efe13f2a403dac00918715e8c0c30ad05e2b8400063a81266a8490a8d2dc3059c08f9a963339f8f0717777cd016483bbfb8e009039e3ee3e840511d2ac59f317ffd7644a5e15544c910029bc6789300ad77934cd271100841705029e78e2092928b2701b28c078d0dbbc83b874a1a3edce92b22c3d2939c994aac49eb24441c9a2b06c8951514a82c1727a0645df56a5ee792369f974dcd12815a48d9bcad7fdfda7ba6bd2efba93cadfb8a98673c58271d4a6fa7d3152598f5c8c15039c711f67abf9960bd25e020c50827b5036cd7a84136fb048276208aa4eff547070e215549af646864d04e167d0d6b5f6fba4d9bc77accab479e8ee3226d494f8dec0040c486552f91b731162d49a56690d676ca7eaed69f4379eda3a67fe6dfcd9d17cca5c6b146baae968bb16db2ea6e34dd75b8c8eb7df980bc6517a772b1314b08912403ce58b60931841120a4802091f7f7ce88f0fd57d936f20092cf507210f930091c000121170172a4205891328d535a764c9d2eb5a0ddb5e23f1b27da6478441614fd69c3ec201eef4c17ca1528228982ff4733bbce9085ccd1141c4d027f74d992c4a141a8587de376532234e30a274f73781a8b6541bf1b9eb46fd9a6a7be2923eade633661bffbea675679a95ea53dfbfaff18da4f9c45ee8a8f5b96baeb95c4582a4eafe888e9a8a036d8b10f1fa4244263384156f8c33caef04429c719caeba7de456a41b1d6d9646f1aeb67c1cce527de44d5a8dca475493d8bc5f969696718d6a4bafc566ad7c3deafcfb7135d71cdddd8ad35b6c43667dadaf7d7dca64667d0dd31ac774e44869beadd1bf6d5cc6356d561a366db47c6d29dd991796c3597a71d67d6eb7ebc5b7f699ea2bdd6133f7a5ae369dc7cf35f7390783e9f0b087325f62a4f16963a67f23a9e829e69f70ed2ee174d5c1606f9e2566c2e9dac5490b1e8c0c50001378a9c10ca7ae9fb7b7410188dc33093b5c2067964a36ab754f5599469df1262b95aa6449794f9a3f77a464c9eea5663f3f148802f9f8d4f006944867d6b2acf4568ac9f29bb45963b849f754308eca5fe62cb52cfbcca2df66bceb52fef23e958d9b1e67a374180bc6510b88000e2f57c2d96a22c51e57c93f0af34fbba5376d0d0349e58abb91f78fe898df29a9ee9a34669cb33b7fee5a264a55cdaa318e665cabb4fc769c5d3ba3aa4c33fd3a6ce6cfe54e6a566cbf466d75df27f3e7e8ae4ed726e56878d35dae96b4b5bf4fc3367f8eeeaa3992d534fff6b7a829c6f2cbdfd18d6f4977369374fcb2e234dd35d1ec74972ed6f59fee16a47f7a2a4e4e9fcf5d719f9d76d54ae9706c9a066848a26599818c7a8ae12242d10c4453ae8305dcbd04016cffa41040e841210313ee3a94e18cbb8fe6b87ba90de8ac5cfa2b33fab7dd4b5d1dae7a26bb78762373fd6bf5a92dad371d2d77eea57e4aef5e4a1fd15d5e53dbadb79bdda4c5e18bc937f3974bbfab25b1d631dcb4fb2b7af75770d391f085ecb69b6f32a7e285efbe08f83315aa2fef520070eeeec2085c9861bc27ce9d52189bf46f51b18d9bf25f5d968d9b5e6fb94b1b37edfbb7e2f23bf5f0705a00c2dd6b184f27ca9701c0177a9f4f28d3ac485a7f2ecb98cbe1b3f37d2e2fa80a095fc28211cf8a15b058817a73783f4c48e107ef042e0498c0a4822f881745dc9dc943326276e009cfa8f11d15ceb91d6766d079a491e0eebbbf62cbb12b4e001881cee3eea58478f67ddcc50e819cced92d02a0072e4fed1545e1a6b5091d200510dc69d50be241e122582d82d594fe94dd41543b482827a3da55a6fbfb39056688c0e9aa23c778688647ccc657f7170663b2b979f2b4f1cdb3c43819add63ee746f2a97eeabe8c6a07bd2dd71b12aedd258d9d6edb76c93867a5ac24954529eae97fe3a9d2c432b36a5bca64af710ee7dc3d339695b9eaea2edb1cd5cb76b2d28410689bd54964b25c95e92a339fbc95a495517e69a768eefec3dde570f72cee1dc0ddbdf2e9cebc5d4ba38672756b9cddbb97ca670e67ab49fee75afeda8bf09f6bdae284486d7125249570848434db0b220c770aa871b754ee4454b80709c15d47c8033d9c2f4410a3830d170fc7784182061437e93c925fe26bf3f642afc7aec434fd4487cda751d9b57a6b6c45ffb53b87fb6b83e89fd7ee8b49fa64b773b820bacbfb3ecd616cd28bddc930c3dd87689428b403236cd179cc7fef99938c433058fefd391725ca58335fbb7b30fc7801f40260d2ea3e71ee9e0b345a4b0884225819afcd591d9a703747a5b82a76402cb14b9231fdfc53621c257d28addc36eb38e04e3ca4327f9f544b5419bc08f65a9c2e8cddb6a6d54de1ee473cd4cd4033aee9a0cf3937743aa478380543fe6b71e114964c472ae114f8fbcce130199e599c30c0f699dbe76ddbf0c41285d2b1fc6c4f3164a5b5b367cf41cb3769f53ea53b8c4dc5487321ec5b19f43947a376583fb35208676148c9994217a1612029108549a114262b479ba5e9253da4fc325752db9202510e804c327320cc298cb66b6fba9a9a0295f7c435aaf3088389a180bb0f7928c6875dcde69fa278973785c9caf277ae1bca842abf0c736ae8d717e7f4677a6d7e27aaca14f526ad72b15daaca346afa5275eff6b5414f5e0ca4f7f91b4f51dbddbba0fc4e6599abee88eaa7af51f356fb7c5e5b6d957ea678c360b32ff4fe943e546f186cf685d69668d55495a96add5fa02cbabf663dc53ccd585baaef53d9d6274e464fbc3be98977b4b644476db505ba4b74d457874baa3759f359526d6fa5c553555345e5bf96e969d44772b8dffb75ae9a4f3513ba757db33e0e88c99b395ca5aa7f8b2a4d7cd39f6f514723544ffebd9fd64375bdb6f6e514ce395c7d5d6f495f947136a3d54ed1fd1a898e6fd2acae5f2be9d36cddfbc891fc37f235dd996625ed2d57b30defdc2e93b4ee23f7c926bf56ab3463bac9cfe19b5f9f41789f95de5cc5ed5e0af61b5f4a85755174bcf8a4b66bdfb4371d9de959141df5dd42c75d29ce34cb5bc35f31b9ab398a3f1f79dd6f7b8ad15074ac7457d376ada6f5efec5a3a9b7d9efd0001f9cc806274fcfcb7b2fed677898e3a6c6a9bab4c3aff14a6dbeeaae9cecce1acde96026d5d73f4c97e4dcbaf541fd9f5b681663f3ef7cd71465f9ffb48173a0e01dd2c74dcf5c949abb634f7a4b65d8bc39b3e8de24df5e53df1d3ec0bd5968299cdbee89d79b3bc2dd75d7eb6fa934cb392b4bc95a4e3c5b9f3fc530c05ca1848cf28ae7ede3a77d25be9aeff64263f3f8eb45be36babb6a4362013e34d6b65d097e6597b7de65fa23b3357a52eb68bb354fd2e9f9d4cf3a4f2e79f5a9276d569e37b2b92d67fb7ed72234f268ca3a06a56b25d9cbb986645d2911bdf74ae5e28dbb55a5b268ba39de5ebb069c6366e32cd4a6586048ed27d9502c0c26cc954107931ed6ec9556762ec443ba1f643e5306eca65d16fe2271363a7fcf72e99183b955fe6aafba7ff4ca5bcf82848489b18e7645756c4dd7375e3ab7154b9a4dfc64c249d5faa6e285db352b984a3cad7b5cf4eba66a55a96adef92be18a966a5bb74d3d584cab896e5499b9d70bf9732a6a243305f5c6b96f54b0a547ea61bd3fc5219ebb014894da120770ff2108c096688decf61301f086c80c001770aabbb96753f8e0251204f05af052077cf04ad71eeca721aea6302fb86dcdd8b8720d8b8eb208d735748ef70b833a944dafd95fdfa9f742e577550faa9fcb2fc1975cd5220f2b9d0f281cafad98c6ddca4f56fcc0587f7ad3727d94fe92a1ba01ce10708f88008779fe2432fb696eed8aec4347acf1a0dbab8c9abc20f2c70f29e3526e3505090c5e9aafbf1d9ed1cee65da8e5264dde3a62af5e36a9396959fe57118aac4b88a69c6ca9fb24ffe53e885ee6836363637e5d32ff46f5136363637509cb68c5a9b39a08c6bdb6aac6b19db9c8b518fc9582bd578974b5c6652a432d6ed6f2abfbc273e82f2a7989acb923f97abb9cb8ce27ddb676c46cb5bebeb2850c654e858e9eb59fe5d8bcd3e9f24599fa65fe3dba6ba52a0197eda54df34fb5d7176eba75a7fb6e19cab74bf8de24d4d5b6bb4a4ed72d5fac98d2fcdbfab49ffb6819e3c4b9c73b6ea28ad5fdad22c4b8bdb9937db896ba7d5f47537b2daaebdd7da3e46475aded7146f2a85a93eb22dbd91f6a6a3f9776df757ac94bf2b69cddc9984e361d400a27451d89dc27c288551dbb54f140c857d110a1acb2fff2f7d1add55dcd66798b2d2181defeea5e8678cd3b66b6fae5dbec44d4bb67a0475a604a81c42dda066545bdd5f0a446d9546f1be96525a3e982f3487180aa0868271f75a70f758f054f0ce3ccde1ee545b2cda568fe8135ba595b6746f044f51dc7d2c5f0c1d6d954677556756baf589bb3f656d5b5baaff12d37490b6559ab6260cb6b53565da5669b2500b07dcb4b5b4f8a6a395da2e696dd8a4ee4ee5a11610f49f5adb1bf99beefababe99a34fee3fedf33965f5fdda97b4326182d5d21264ccb852a730ac1494131a77ff1a7d6262ca389c530e7ed3678e3e313189c185061e308ef93535eb7971497514e3306ed76ca92dd04c8ad109857c2e359cb1a69feb0e67b159ebef9a1f775e253a6e6c6afa38bb6979ad548c8e7a679cfbbc7b29aacb8cc9cf949626bee593ee72c5d99cb35d6bd66a1836654f310f7b5aa555006480bb931e4a796ac279c8e484dbd8d8dc8c363636373f423a484893b80cbab6e284b01d474de2524808a6737bdf7cc0e82f1487b73e916c6c6c6e64b251976f9a1569772b13c6519576ea5dce2c31ce44da412643132770318be2b5a23796c0506584a2260f9dc8b0883263de00983061019fa6ffbc946f9af68aee9bcadfb5cc6df9122369fd2479425dc9c07dec000519e7acb4e4610740b8fbb8a3d1a7d15b499a3f47f7ad36d729fa1723996645723262020535da4ab754c551bc69fe32637aed896995eaf095da965eaceb3fd191fc9cae26f925ddedfc399c73b467575cc6548c9868dd435633f7595b6ac4040a8a96bfebb559fde5a88fecaf426949e34be1a96be9ad24d575d712df74b5b275b6b5adbf34b10fcdfd4be128131d7127ce5255dca8b5ae340ad31ad02b7ca4175bfab7d9e7dbc6b5bb14dbf8d29aa53fa50f7d6d562a749c295972cea8b67436a3bbde36c59a96678ea263f9f773b97aa3e7d6b586e96c369b7dc61b8a8e5b63933e0d47f579db369bcd9b3e8d9aa6d5515bb31b5f8aa9feee8bc99abd7fdb74f6faa4afa3354b5f9f957c1a9dfd507d79978060b0289f19fddc6ed37adbfaf36f6bdec8fdb51ac6694b6dd74a9135efb77953fdb6fb536f5a930cda379b35a9d661d3ac54db5b141df17d129bf46d95b45bdb6b71f9f797a6190b578765b8fa5bb65fcb642fc3e5a9c5ec828725b744312110438628a614f3843940613f35a5bf335e1f10718ebb2ba1484a13e72a798af95246fe9490f65ef8bce4bc38e2fe49e02ab96f8b532a31d2d23be17e5b3a56303318ac66fe43d1516b4bf749a3d79eb65cb1e05dee44a8e498b3b129af54cd6fbb18cbb63636f8a62b6f2d1f77dad0efa269362a574ab1aeafabcddbb4b5963bf5b6f6da6de96c47abd1ad31d216b6eadd54fddb94121dcb92ea4a6b56a73f8916d1389395de1387c364d54d615aa51b5f3a96bb343fef4c2b423569f9fbcdba2b2d2fd171091de9a8848e48744c4261356c4d13933f4f5e7c295069653e375d6f96becd9b522448487bf1c617e39020a1556fa01d8dfe6d9bf55a9bb555b35631cd8a83aad5cea37de66e3bf749a65962a42f31924e2aa3f69bf6d625632afbccdd25a827d2e67094d2b5f586849b4cf3a4929431d25d2aa38ca84431d94eda27956fef273d796bdef7cf7aa352da1667b1ecfc53ff7497c6a0cf3224d76314121a4537a0982ee1ad31be91b4ac345a4fa09d69c713ef6805dae7111d3d8831c1604d929cf5f3b64d929c545b1caed666651d957c316eaba9d051ef74c6f93fd0549569e3a6376975e7f26fe4894dd36ea81d15cee52795d7e20e76a619d37d13aedc0ff5e6f9a4fba62e11e8c2c4c58d25d3ac485495699c5155a6d99b38e7cf69aaca649ab111cbd3165aa919baba6bb6d18bafc6fd97678ddedf516aa3f8a6fbdc6ee39deddff47d1cddddfae689f74f05fdc5d4acb67e50fedcb9f357f26be69969d2121285a2e2b4b4dfcbc64d3bd37cb25d5b2ad55d7395ab63e56f6db16cdca471bf97ccbcc5767f5b4dfb2ee58dedaa99d336a9c4b84a979bae56dea455bc9fcbc64db17a64bbf6fe94adb27153d393ce4f83884e1470c2c2c90ad1e9004e41884e40388d426140b4e695e194e41fbeaec99f326d4def6b6b5632eb59da29bba54cfa7878728011b35bf29e98fee2268c9f9c3c3756529e54053c046207aba52786942859a2ab4fe512d59757ebc72200bcdf5e5bde4ad3b67cf3dcf771b7caaec56176a65965bfebd49bffa4ddf8caf67d9ceca7acedb3dd52a4952acb2ad32736cfdc99497dcfb26233777e8949d3aca4aee5ef1b79e2299cade50b097959f0c690103b7811820086b20c93c8c8156478f192c6dd573a579c33775f5d40e5f396086854a102549859d02447142d2531a85a491c54dd88415e8460c498c921012bb45c85850e0cee5e1099fb1702980f0423d18a189d472128f2a764e5c3609f3129cb69a95c10ee06d5f29458850cf7efcb0b5e9a0fbc78f075f9b8b8fbce7a678854ae70779dc71c2e7285ea8b4d133b5524291263d9d6cc4195b996b76ea8df780a069371d992a4757512937444241744a4a224aaca34eeee9333faaff3efccfc5398de4a81b0366374acd5b08931d2182445d61cb575a5328e3b73df273fd37aa3d5762d05caddaac36128bc2feec4587e632ea369ebbe4f75b6625e5ff3ac690d54c33be3dbd36af8b18c6535ebcfaee17d1fb7c97a95c68a4dbcabfaa7ccd7b8bfed29bccdbdad492b7ddad20446f3d4a6b9edcfb63e7a8ae21cce4e4e331a4433a64f6633255dbb945b572c331a4461fb7158b775f97febb67fd3edcc52b7b3d570c6a4d604a81a233f7931b9b48fdc99121d69f7a4f71655ab54a540f43fd7be56f5eea528d0d7ea54141def163afef8e830962326282bb8a97cbc9f0beefc8b9b366ec24ddf943f97db62bb387779d24ea5d04b551a853d65797a52a2484b55b260d1e57d9ba938e1b5c0840c77a7957a669830e1f92032a1721f77884ca08ca5a5e9884473e0fcb9dc8fb8484445d21a221094b84844dd475bcee0640cdee2c5c50d606711387c8002359ec64712fb16a21206f82c6a466b67eee24e5d6ad3d6fd49356f2a25b39a6f052a3feed41723e576aff5934acf830ae28fa954774dc2b51b8583192949a2c3d72693ddf6cbf096555d92ffb6aaf5e364b693f6a59192244a5eaa9a34a94a922fd584891299927ad3b29baeb7276f95edd732bc652ffb8bb7a6e1f0d6e1f2b36cd7de94e96afbafc9f096ed9bcdbeae6299b632d9d5e19bae3799b6d9acaff799bbf677ddd56ed3ac3625667d9dc4acab28442053e7f149f9629ee81367b2e2e80cccd47dd99722b39b9cfe9d619f7341bb52b6db7667cc24139150e12fdb6d9b71988a93c9b6ae35b3de8cf74fe1cedfd5f6e697f7d45b5b538aac264de6791f8ffbb883e0e29137968c4a928c4341578733eda6ebcd027b3582149aaba44cf7d71b722f26664085bb5746ce4a337a8f84a399eb538ca340206ac085fb78b1d5a7495ebb6ded4247ba2bdd57d4c0080d78a074a741d5b559adc1cc5dd4c0c8dd450d76dc8dc62441e44f9966c569318359064614c8dd5b2e66e043cc20c7c81cee0e44a94a0ba39534e3702e178d84e114a6716f7ef95f9ad8caae8fcb8fcb1fa5fb262e195f9bee9e99aa32514d475cfea8f14b4caf05c2f94530cd9a7ba99adba273a76cc4fbc7a7bcf8c876edc65c46fd1b4f9176df6ab3365a77b4ba77ddd5f2e223a47147ea237f73a73e127480714675a54a546250b9f33449aaa740e5c5e443d1b1e24ead2bdd99db92f76f2533a6650efa916dfd3459c5b92d74fcd9da874add9a736790a634466177898ee5ffc65c70d3ee5626fd1b73d1612cd509476dc9475b9f4cb92d4f4875d7a4df98cbb54fe2902cc4214cb88f60be50994c1c8254fe99b740fd99a5b4d5a9441282852b45e97ded587e06434eee9b95866daef7494da1e89804a6ab619db18e3ead029d5a5bd95012ba35c6d1bd8792e0bda3d53069f7e36edbe659fe9959f5adb3fb393ca3b7da5d7310de417a2c2d3d62620c4ad9f2a76cfeb2b4d45a4b8fc44cb3e69f52a29d9afe8d02d5dc89b38ddec8aa44c7dc897395af3d0e9354dbfa64cdbafc396cea2fcf7b8e26c63d8fab3629dd436bf8cc659c4d8c73380cde26363176c251da76b1f9473bd33ca9ec6e0c1f4969fbb471d3c64db66b77386ffd49394bdeb8e97e2ecbc64d5a5355a6b7dd7a2bd24f552b1b37e9c7494a493b53eb37f33b55bb7375422abf2c632355652233fe146e5756243a8e74e3a6b734729ce9f09dcd92d00da89e0599f54baa93222b8db630eaba504d9d5edf6cd60ac651e597a749be49de2d4f92a73e731aa8fcfc3977d7f1c8f880eb74eeaee4ee11f1395ac3f4f5e7b28cd9ae2d317995caa5fcef4413738c728af889ffe29c9576db9af5d4a2c3587ed7a412e32a5787ef953769758b8e5155266d2bcf0ea82ad3c576d7a76d71164b69bea50283ed70ed2e5dc0bde8b1637f3ee428815781cf480aa23802538457c3cd8a1e2b7820031e2b42308128f643957fe57629bffc9c549a59ca2f6d75aa59497c69e1ee321955c56da1aa4cf9a576b72ad1f1a7ea0dcc175a67b66b67d91ac95ffba4fb53dbde13d37d62a09d59b37488e20db4a3d15d6ff868fc9a95f23be951876f9ace76eeccb42ab35d4b9ee5e719fd4a9f6649a819f95cca9ffd6c9631ae6adbadfdbe35d7d336bbeddc97b2202a85cec61f97aaf9e54ccbca1ff3dbcac70235a34033aa2fcdba7194ded7c2664034887e99594c858e3b7aed98f1e7988c3f3324549569e6437f664832a632f3a1fabeeeefd03834164d69bbefefcc7a2bb5edda7246e2fbd987eadca969add5309899ee2f0c96df566f6522ef4ed36b75a59d9896b6e66ee489d3959ae6ebea4d3fd151df9ad374ac396ffd379bbdd5fc407a06863e91d9ae7dd9971985697d32d18262f59e3f3e54e34e325bb2ccb8ee68b4ea6ebaceccf2d65b26f363a6a1a1da121d1f97bb7de4d6ac54f3436121fba3675787674d3e74d4d966697496df29e3a81998d9ec4b352dce6a3ae6af2d95745baa976efabef92595324f1ad561d3dcd12ad53847a544c719989a95806810ad59e9cb8ce6ff817dcd4f6756a29c9492645e9eb244412d59998d60661a067bb2d6f2ef2ff9e9583e59f3e7ba2d05ca785b0da5c337a09a9f966fd6fc3b303318cc342bd2eccbce2c7f77ebcedcba62a1637e5cc6545b186c46afc59f31131d731547b59dfdf88c3f3e7af75230d8cc9a399ceeb30dfa4c77401bdf5aed4b4ccb16e86737dbbd946c36f536efcfda666b3fd75c5a3033aafab76dd26ad0c537577137bd3381b22df76b8bc3e4c89cfb795abdb7ea7cae74a16399abed33c5b56b7be84ff9a70fbe14a8b65471b61587c3e4d32ea6e99e1e5abe6956bdff6efda4cd5b7fa6f9c4359d0f35eb8d3e79f3e77ea7ebd3cf47767fdb9a92cf65898e527467d22c494bbdbb313ae2ecc524d5fbd69c1d6fba6ea88d9b8eb2ad4e171fe9cf98093749ddf386244562f36ec14d547054cd4ab52ce58d416ddc74dbb94fdae95b7350b66b97be1e95f5c834ed0d496ba41da2c13ae38d6b29d57f061a7a4655d6569f0175869433a09861821faa2d50f93ef43735a30a3372a035bf19db8c29666c60061133723405d259e3dca5b4fc0d822940d082ebf0a59a4661b319bd5689cb1254931215a5a4b17e2e57631778088271acf9a16a7ed96b58195a5051861265cc8037fda1d7d26b6906e60f0fcb3075191c543347a3902187b364cc408600dc69143214400d05f3e5068b6fc903bed50616a020021068a08a3044f02b7a946002d896d21381980fc75c838b18b450830b2118101ae2bd207073772c4b3bb2da1e8bf6606e029dc7a0a08b4d0fc5f02f314e3ffed24e6d2757e3815b7805b4d8c0e3e113f0882ef00037c3cbc019303ace4330a090dd6ae66460fc70778f7a4b55996a98aa32d1c7d9a8f167b773b8af9bce481a4493cc7ce88c9cd16dbfd27476cae66d5a5aa5e1a7291dc58eea789652a4cd34ac292e631ae776e6d6d5047a9cdd77fc793d9b51bc4fbcf301ba38bf083b6d6f7f6aa1e3c5f4d637cb5b6bdef7b7a53a6c858eb9936e7c81a4347dda8d7cf25a283afee84cc378d3abebf8a3f5a1e553d91ad5554777399cf73531ee326b4a12d434a33b13a87c2dbb78f69a6afb43e5890bad741fb9b8f431cd1cdde55a5e4cbea65abf794ffaa479e6708e3e4ddbfb9f3f476b96ce1e87a16654ffec72cd3887fb283aeeaa14b6fdbeb6cea066411e64e91245c7d9d08ce27dab69d68bf5dfc8faf966a1e3cc4a549526252cb2a82625a62a4a50b2a62c4b51485b9ea2a09ab220c99e94929ab6e44e5ac3d706e52fefd9656b5babe1a02f6bdeb4d297aab88b2d6d22eb7daaad3573b94a475dbea9c3572ae85a4c06557a234f4df56b9ccb676e97abce3f55566d2b694dda99a3fa27e862fadabced97b2a60fd5f6b3aed476b1997fea75f84681b48949bc71ced16bbdc49870f937dd19dfb43eb125ad5971b492f74fb376aded5a9386ab369a712d2a4b138d82dac274920f458bd01109926cda7d4f3a54feced479d7abcbf2c5f01d61e9cc34add48c664c672f55f7ec46e29cd24780717fadea9bae99a4444d4e5ab3f4c95f4cd4e4bc9138f72ba92caa9800cf6c637363535fa5a7d5239d475aa5790165bc56e8d20c6e0850e1ae67d44966bb7647d6d296b792b227ef89efe729d997b25bdf24711859696526c639d9ef3771969517d3eac5b4f24f99aee6f2b6adeeff7f5773b2fd78bfb63299aef965f8ea376935ff35cfa96b65fbb5ace697e9fecacacff96b5fcaca8b699f6fba6e2dcbd5cf52a495d2329969da1b89c3a4ec5a9c6c67e672350246221024022fba7ba9f2a730131721e0228b17c1227151e5eeb66bafc597c2fe9e19c685c94554adbe49f16ee2a2888b0cdc9dee5e6a579aeb9b483a8f19870bb798dac26e11e5f4a6eb4db6efe79aa354e31c85edd7b98a93d9aebd67ce651caeac32fd26794fd97e9dc5e97ea58d33677c60c226c783591a16a248410a6e3e8454d0c3bae361a51ed6261e56260f2b081e56ec612de36125808795080feb023cac1c0022a651e4621a486980e0621a615c4c63bbfb4db86347f8c4ccc327c278f804091e3e0183874f14c0c3278cf0f0890478f8c21a1e3e8185874f7ce1e1136578f884093c3c802706c1710de1808b43d47071c81a1789ec7091088f8b4490b84824c945224f2e1209c14522d8452227b848c4051789e4e02211215c249200178960e1229108ecd0218e61c6c5316470718c1c5c1c430817d30071710c03b83846152e8e81c5cb25fee02efef072f1071e2efed0e3e20f485cfca1c8c51facb8f8c316177fa8fd70baf80309eeae73a4021dae1c20e2ceccc51d9c8b3b542eee14c0c51d225cdca9c2c51d0ab8b8f301177748e0e20e1c2ef270177954e0220f1e17798c2ef280b9c8e38a8b3c40709107ce451e23b8c86306177900e1220f285ce491051031bce262287331a472771d56887cc7ca91ef5861f21d2b5c7cc74ae93b56c4b8bb8ef08b91875fa878f8258bbbf31095d81e2a01000f95b8c143258cf05009273c54e20a0f959880874a6cc04325d2f0500939dcfd95138a3aca7051c7085cdce1b9b843c7c51d405cdca1818b3b9ab8bb053f441cb68b38ace0220e3bb88803142ee2e0007767f9d849dabe93b4f39d24169272707642f0c07742a8be1302ce7742d0f94e0825f84e082af84e0835f84e083cf84e0840f84e0848f84e0850dcd8bc5ca22836b9808b4dca70b189085c6c22878b45372e16edb85854c4c5a220178b602e165d71b10804178b6e2e16e55c2c1ac1c5a2165c2c22808b453fb858a4848b450a70b188022e167dc0c522365c34f27604a8c4a804f06da5999a744c213333321009000000931200304024180bc70312c16c9e86ab0314800372a67290521d8aa32849415219c20c0100000000000020981a02a5295a29c7c8d19282f33e01009b2b0265b1c6599152c7f19b177053c43555c0be14b1bf9c5fad8f1a6dea9b6020874aa802915f6811ad2cc131fe698eb2978a280d50a1ef92c4c70a46efe9525d1ac4e81034e235d5337003df3edf50b9ecdfb7fc52a17fd7f4086af45247e6af2f529cfe8e3149cd0db26bd903b6e488f4226b19e01250a3e9a2110a17132954b2729741f1642cb47251420a7add4522169a8e46a998080f4d3c70dbf53a0f36dd9ed1c8f0df14b9ebe397d2791b889d13924dd5ccfa3023e766edd6e5f676fa397900b4def30b070fc2477525eed178d3873c083c285cff25a8309d66ea8a70a0cda27360f908165e7a25aea728e39205ebc3182a9f66d740cdc774053a8fc169e04bd9730ba7ae045646414bb46fa4aab1c482844d99f544e36a4259f867339763fe05898bb9a20a5658329e25a8d15c75f9d01add8f8653879714a8aad6362c8b67c86edf38147d9b817573d83271a1192d830a836cba0e6fdba0230b64a236790111a66f81a5c10025160bf3b1c71baafe84642c27f24d5aa5b18e4a0d42349f848a78a8c5202d01ccbbc8193300d990b5dc5ef3a30dc7d2f8c94a8fb15806e435a5310fe768b2f226898bc334e1510b7a2d8832e01c3ad6a8577600183ffc3dc1aea0c6f80a6ea843ac6c2739af045a022eb631c4714cee200558fa4f71291f4ddc0f7c327e2b8899b4e1cdb3329b898df6cd3ab645eba7a92d95ec51ede37df57e99b801ed5c31978614f484bdf085cf2f9379c6af1120197c6cb5b66bd50fa4cc28e8bc48e48d19411a1545469298215a4902f6e8280b71a16f6ca743e3034371caff99d6ba142902ea7ea1086f52c049b0fc08ee310b25951c59f3b14f485ccc16fa5ff90fd808deeb3a1a08e59eac455db4ab051a1e400c887887a0bfb39a36a4e01da402a3c3a68f6a2efb681b4cecb44ec43a9ebffccc0595e83e47958328886dd0ee15aacd5d04cdba3deaab3a039a5dc52f0f519ce93515b2dc2513e911cc68f85a50fbc8fbb01215320a1f629f5a82146128b840010e72d8235bf803c66df8c2769a9b6728b807c3c830ccbce0954bc0b7a6c81a5479db1e6b6a1c1ab2054f8ddc9786ef7c608c6aac64feca2dc8299441eac3ec4006dee5b87a887605520c18f44abb9208536d5ff3a004775f8087e46a35398e9c9bd99239b8dd985493b305d7bc1dfc36a84680bb83712ea93ac6e77dc19b02843dc5658d2a48c3656cdee3e633a8652e69e51d218c104c913ba5229253f3b5f86fe348a62ac219d79ee8eb07221073e3671eb8a0e38b5b1be86b644f52ea8aede9db96da275c18687ce2625a8eaca81e37b2c1ea06d007a0eb32feb6e9ee7fa8ca87d8a12286ba76c42793dcb7842e72b0161d36b22fad1f79c893a5cfee9a3e5bdf39de13c96632e5a4dfa92d310fe400b42884e1bf8bc7d66877da31a00477e4e039009220603780f1dde98ee7f890819d0022e88a8ae4203cc01a21c1e19541288e058e01e7a016e78c7dd60600bce0e888096c3a436ff7ebe513b3f3a7cb90bd6b244751329e56b5e45f5930781b959a41239a2796118cb91708a260dafb8158d5bc416e3d8ad1a2e8eb9a4695851d5583db437d32eea0098e4efff1c8538d7b75b7b179ec8909901f5c4bd627463240404c14ac09ebb49dfa20f840094e90038886fcde1a7fcc3dc8ba721a727db6f1d62f32803a81673d13991381179cc736790a9155f57f35e2266486d9f7b1bca38de118a5c8eb9098f3c9de038e6af2da766307d48efae1b6e13f86d6ab2585d86dc3276cc781c28cf08a2ec7501620d10b0e2bd1bda1f7c9ec93531806e46d096a4521a3ef7fb00b084a6549150e5f2463db5db0e6da1a1c2d439b35f1c7b7062be2c5a844adeace61be787c05ebca890a291224cf00fbb807edcb6cb25f99ee38d82d40af621a3611f1685c7b2af18bb111591b4158ef6f0f334cb1e205439a8a88f00483b1421695df54f7fe4cb310dbc651faf56b3c6089113f87bc71e8011b0eb24cfeaa13e71c87319ac45469861e8ef0c66dd44eba1cdf368ef2a4a7fb53fee63260d5f839034786a9388b3d635b10174966d94df2d14686954b5e877c1bc53e2f96db90f8712979933cd632179ffc90b229d0bea9c5472297da974a00976f8de68b1da90c550fa7f98ae401e0ad26dfc0612ffaa83cd97de3432c98f8e8840dacedcd49d146b2de82944d8bb8ba11a581bc28573335f946148b0fd5aa7bb9162d183911dadefa20fdbb894b5578203e7524ca6d1869e0296931f0cce558f1431c54de2ce131f31149e4445db510120612a2a2ff227d3811547ca8f1aa21936d55b39b3ce73b42347802869f2287c36e9bd5e58c0b13e5473b2f08361a2e82357501170d42d7b3ad97a92ccde36342c70b4b442b8d351931650afa7340eb4088a34907604de457bc0bd4e77e585d152591cf9b366dc3dd46ba6b64777e2fdfc3e35e71e1b7cecf0730156c9c5c9ab70412c47c1be40be0da9e5dd53f4e357538d3d62570ada641da09a5f071f27c4aa9ee8f295d61b1aa2025a69a62468270ceb18b6def77924bdd6e97ee68d29cddea60a01cf9f529e77fa3498499ccc9dc7059a0bfa99d54fbb93fd9316266891df4f2429432624cb5530787f41792c433c1f2100fa8165b49d4ae8fe60fde9ed641140538a16078310cd2bfc96f5447273741d92b0eba157fccc94a8c22483acc34d222f5df2324e2dea7fb0367e354e8d76565969d2cb84dc91aee3a58d28ecdba617dddd6fe34f64deb4455192ec92b02fc8d757f90caed2538567840a37ab042fd0f91d221e0c22ee41025aa123f5a0a6703855edf03e4ec821f2a06ad04872b03046f0edc776d0035040448dcc7f29277cbb9ad186731b0ef6556859b9da7b5ae7651af672ec568a241597883a16f9a553d031a5426d87534edd71fc0d8c1de4be2c5630cb4781a1f40c770a345bc2e9d3cd23cc154028cf0f34efb0e84beebae9ff904d60ef88098bb49138caf8d30e15e2e8cbf035939a9ca1c1607bb793ad8efb3221f86c5a0e80f85e9ce1b5b36f69acfcb5890cee7b7043de4cd954f378b5c218a1a5cbd28585f7f7b527d303f92c92a5e2b7ca3496ff9cd2fa7fe91b3db65acdcc4df634e483fd35507818e0848bf0c69dde68d580e176f59aa23de20f1021f2eede580327e2963e955e0b57ee1b1bdf42cd74bba8149927cc17d43a69e12d0ae8ae63b550780397835ddaa317434d1eb604918909ad781231636d1140c1b3cfa67530fe761ef9b2a98ea0cde1c51f1769845824feccf0f9fd54f12871cfd878f0780fbf1d60f4ef606df7e0d4e16a36d6439c62a20a8a5cd7014a828e69af6a1800089a0671460b23438acd3796f99864be1b682eb30f5d50e90922a99a35049934a92238c8dce91cff51de3dfd934b783d8ccd7be13bd319551841047369a7d41671cedac2ff1c6da4b0f8922d4a394d442de5fa35c64cc6f8fcd322245a92cfe6a7b5b7fb10e81ef4e587994876fc98b3a2fb7c02863ec020dcd82ecde9650599cf0ff767e152b40d0b1cfd91b5007911aa8834467ab8048eae88771918c100245fb8d2c3a0e110169673a9b96c3fde2ffb930d2019db13224023e8a27f168e14d59166c8183710ead7229bc7a3e6b3f780111488e81897b0aae6c6116883ffc1192c84e6890a92148352b2c5d35466abe150e5bae7c34ccf91726ca13cb2e3f1e1c13af06f3aa7260736b08ef9fec83988549592c14a872ec98e69e05c11ef3e9564a6d90516ad899bd304b88d8b75a480ccd2e1c7a1cfaddc05a3f5ae3427d6079b01ffe349e67495e128abcf630ba66324532bacd578adb9cd99086dc15994f4d1f79a4bd85eb9359e6b0df118198f55d99d841e54f7fca270b7f48e927acf9a7daf36a5cac2ea0974045d4811f97e21a61c4d30177074876aad26316a95eaa476beaad9678bb64a326752da93c346c878c071edbd15f39636dc7da9621a8a6ca71973297c94afa2e5580bb27f5a3e5794f6be7c5dfa2a1f71477a0c5b8da0461d4183de6800c3724a4f023fbdfd1c70b9b7acee58d28cc2ec9c81996e13ad4e09c771ed94503fbe24b97f30215c7f1cc25315cc01b6d1e7bb2b28b8c6994561c4ba5fa605b35f358a0518b14159140c06934cfe69cbf0b86730630812fa559818e81608a55a02af318528ae8fe3d5c405708c9329bd78c36b00e3a2b45b31c4982513dedd629875a026822fc2e54697ac53b636ddb016519463e1924ba9640b02c1e42eace837c8b3892a7c0200d97d54de3ea9cf1c38d6e8b5695142abdafca574dce53086fdaaeb4bbf67690fe105df6319a1aa8d3acacf51c58a8b279faa6e7688613f822b87717733da453eb9ca6b52861d13699bc35d3a4de762515c1f0098218d6b8c82793f899343cde6823fa06f12a2bafe42185f8897a7b8e0f944cd0e510f3486504eb821cebbc22068640a3160d34c41f0976cbb3e710bf957012a322cf32e4e8ce574d1e4a2a404484d2f3ab355d144a703781422113c92238adfef0f48c356e9017455a77464921212c1cf496a528c66333d3c7804435da3d4b6fd8dc197fdb3b2f43908700d5f619ed33ae395a9925361f82a7df1bd1e8b154139c61cd3e8651d57b2cc48ea71a1411f29084c65ca89fff7661d0d2ad61a6de140e82448ca7997c066fec0eaa7b58387d7108a8c1bfafbf54e8bd1299e2608764891abf95f6eb6f7d5c31bff1a49c216b7ce8aa00773547cb592c57ed96ef8320ea846810ca8a5ef45e64ce028663af4fb2586f22cd354a86750cd9bee1b345ecb213e36ca183b3b538b928a54cddf5484742e46e920ccc42438580f71cb8cc9410e884c1e95191796b0ff55f07024941630d6ed2450a90c5cb8d3dbfc41b8e38126148635e3460901c2e9d2e3dcf22f3029c1b01a6fa8305524dbc8b9f52c3a87ceaeb3ed09a74221ecbd19fcc6e4c21d92855e936a902aa6a724cc817c08e5668ac0c7c10c2bc4278d8e57ab0c28094ba3ac20bb5692f4b7fb35c30aad25a952ebac0fd084cdc29a499d1b74dc2472f23089ea343465a12094566a9ae347fd5f38a78b31b62ad89ead323a884983d1bcd97328fc3d08081662bcbf0c021e7594624140058de19c854f9fc248aa069123e25218884fa4905dc1018f523c8e05c9ccc241f143a17b214d4a7099d7cd4b381c603b9cc94895d58970422c182626dc44197544388cd6b92a9b0612f683084a53b114c91094b2e5dedb992f2bf4de0005bf6deb0f0bdc143b9757f720778c692b474cb985d1022d2d5e7c9b639911bec47134fb36a3160449ff29823dec41d34c035476cc4406363d480de3e12220242b653421a15a5ed521f3d89e95da810e47e87a356a9c0837f19d72c092b2912b33a7bbfef0c6064828675706d9994ab3ae7dc235d9cafb852356300e951c8b662b1719382a78dd900856fb9d6b6a59557f38f7516ad15465e469e3ebb1707c97bfb060e48e71b39e2f5173985b27242d4c12a00d755a50f7336bd665bc125e8a756faede236086949c3d5ec911ea6a0b6dfd16e4fb2e4050686c38764a35275a0506f65a920254c173243675610d5b5e36404ec005e93e43a3482972e500fc9ffe124a0133690f08182e830069ca1310c66bf2dd407e8160034ec5098caab0c84a1297dd45d59d388093e9710b6fef01ec03556c745ebb103c72a805f058bad4de22772743a34a6d4c4d3b8a1116c7d4c06229ff4a5fb9d7e28dae5b4915c2268c0b9e8632a5bbe78913f569325ad9b329b6410a3cac52b8927548bdc64d00652796ede21570f3f2fc5c0cee8c0a2c2a3f90b051a11d7812b1d30b1bf8d544e381978b903f008cdbf3a0d13008bd1d80b0bc40cca00ee27d2fdf56a677b1bb9e757483e09a2df3de737c18286dd7813efba4d9525309b54d313ca9fc01779acf153291250225212a284eb69e5b521a11fa2ac38458338725ba40ba2dd9d7b7ecaf0efedeed42c03007458ca80f6185db70bf1d59b0faf906fd07a821fdddd4189a1cd360cf76fe4d8a46823d16726e2715b68c6c74f826cbec7b893f2ea5f48abf73109c2dd111cedb1816510885154d17d0935d62bde262898d45c5c1265a7bca14ac19d5d1a5ea251592828a246d17f9d90cb99ab4626480cc614a8812004f88ebe8849a830f6c29d754bfa2c732f14f3557f4060c2a4308b93536304d19626656af1622784cf0e78ee96bef16c465465c3d315e9055dfd49497c12f6228da394f56d95bc0f0644260eabdc14bf0a7ea4142b713113541880880405656484c378b00ea9bce23bfe17c4116ac23c0d8be3ae81138d83a533d45f311d0a10f8df7813dd3ddedae798d7243deb938ff57541e3625036effd654d855f8e459b31b83bcd6b08a6f3946899e113c9bd1f119318063906d6e2f0b3ae2bc9246e436073a20b3061e223cba9c51d8d7f7ef76e02e01e14faed7d77e2cc5652496d850520e96f86ccd3df7a03a946416e15e72de9fd83dc605516721de3767af2f9e518067105f4f87bb4572a7bf328fd390f1ed2b3ee51894deddc090188a1bcc2f363b2bb2f3d307254c0d8243056c789c395dcb1562c8b4addb53942970d4da5fae223045644c5fc6dd31697d648f048362059b5da8dfc6f652754ada18af960e30de9ca989bbd83c97d301256b37c92ee23bec863d9a7f9b07e28fc754c189a50bada349e56f95e326d75066dde76b855e16f792ed8c0178aa992cbbf7d0b14494d7e85ee60e9d05615d92c0e4b16f6ec8d9afdf610bf311c0a31f23db028c46296de47355db2653b57562f35a8e905c19c9e126a43a0e07adf810a5d66a12c9623ebec70a1d69a5ece3edab920b6360099a9df2f2088a1dbd2c8e0dcc9d0fd5e5949ad024d0d82c402a506ab8fc4d25290c5f20548818bf6683d12761d566adc07b26a6231f39ec1c5b1e26af7d594b17313101fa188fe718b009677dae0c8d53ae3e04142da5f6d3bd4076c54f3775c792d073cd84da7d7025903c5b908cac3456dc8c4a7c60a16af90159fd6ea02f0744b2bc702f1ea05646683a67648c7981da39354f4d258abc3aaee0abb458e39352c0e449282c57033d189305ab766fe04f79c35e02dba19dfb56fc3e6a75b3315c59430baa303324c0ccd8317482ca5842d23251bcbff6fe5b585b8762c5b906cbd60b16e0ca0ad2a837b7fdd86047b63e1b239fe2267bd4b61eddd6bd0da6d5c830a82d1035d1a30d9fe73cac1120e86f1fe8948764b9e73985de4b542c8ba75b98b275ca4878ca9025d8a3b40299849f41b7f01dd3f456c11fc919721b9c06c0dde4b6d9a22ad7abd2da1e99729bb383bef075e74ee91e41f76695a467f1596ca92900b6d3064edd5d3f33a7805935c02d98ea99efc922966d0d2d7bd6bf87fda18937316d5e5b7c73ef4978c34e4ad41b96fd4beba2507296ead3a778bfef7e1b4e1be6dc856dc3d9102304dfe139a2fa8680e5de887d3cac6418ab65f51f03b3e7c47f57320e3ea2a16d5b660270189cd275fea9d6386a7d5bf491c161bc28ec2f3663446fd0e4601e832257b976a08b631a4df8c9c4e6278c44038ab781e02ab1a43a91bfc2e6760464cea889a51f1970bbc07588a9908a7af5807963306ad4a57cbe6864d5d85e2598b9c8e5223e1ca1a1522f338edd78842ebc824699e8697f1f708252b028dfc4c7165e8a56b0f31da73ab71eb160569f8a5b71d8c101e79754d5d26230d80ab5d7a005ecdfdcca1c203b7f977dac8744901efc6069cb10674d2884baf10111157f76a10c3f623bff044a0d7f271b4d4e12c3590628806a43f3c2a88a87f84e9949369ccdbaf68a879740cf6f38e18b8e5247558f734a9ac52ae94d6fa159bcf5a3d193898c4349298146b8ade3cee785f7691027815cfa0690f7751e856517e1ad27ecb6fc44c4f48ccb1e6fe45b00b556f1f8170afddc15f31bca3cf78a23d8569ec74f8b2022f0700f7a62328a11945163121b79decdc5156ddb05e50e01c3aef189b61d1157386d4049098b5b94f04cb4046038145a6ac3fc1d144bf9877d9da82955461304ef72d9cc75ed5d81013f92461907c8979e11cb2f301250e0d1a05dbd317312b6711f344571ec461a3f7455af61413600390b751107c90c1f1affb88c68d3481071dec063cbca549223b6d34a1631fe1d2ee644cde5af17a06352bfe0fde4d7b2da0e3daf5ee2fa07f196c15974897bff81d61e435d489b27d2af986be6e297564e520c167617d323e94d1c6de1e859962a4b32074a63d8c8046fba8e3eccbde89c8c65a8fce93abf3db8bf2dd82a68f38744cd98c663a6c1e643b47b8ced4e8382f4f9232ef24801e6f0be047252cda4abb5d78bbba4145b2c6dd810fba1ebdd891ac38ecb7441ef62625bc5c86ef723965af821210f2ac7783e20ccff44fc0d60b258be4f3078499040b033918504eccfd647ca4f410a1c7eddc080e3421f4711933928d5af62f489dc09087393bdff1431ae54943b3d88a8882e9b1292ccbc506384b963915fc1610a1ecd41d36504b7cb8a14cd5ddbcedc487fc7600ee88249eea728ac6b8b78fe187aa05e8ed8216f9d107452a1cc0f05c0483074c5ff949301f9fe3c3942b6b80cd55ae5e1c7e18401809a5ce6b1ff47737540aadb96f106f7691e931709ebda44839852ef403659d1583f5390e5cf83280e0be6ee837d6b7566c919c95696ad77aeb91301587bab76b564f3088cea47caa69794445f5c2f74e4d1a034c3661a32ec8270d2d45b7d9bccc53204e57eb8874b932d0c4933535106e0e10593c95e13bbf42342209fe73ada9948470098aedf8ed810e607adfa7a7b37a25a17ec5dacf3fa4525d3f1a029b7ac08b64068cbcded6d7266ee2a550e8df04a1561e3ea2b1d9c0e7f1782f3838bb49441119ed74bfa7702e54a88354999ddd77af9325e073465fd0628a627dc0159a7391da9c322a6b15d802af107069d5ef217338767259adb17310c4cdaf9913d82d395dc7e827fcd0d27c493887247a9cc9a157c373f8ad9d76adfb503c03e44a795f499b823f7ff69b9c479c23a7f11f776436915cf735b96f07b2d63a52d7bc834f581db62c36a43346d7d4218025550935872da447019bc06232b34c1f710005988010cdec2eea65df145025e2c6b55c50b77a467b91901ad36056b203372be10fc682d64d7bc7fef30cfc0c0f9d20dbc14aef366739e3f9677c6e3c00c371de4e70dfff9369a230c530db6293c4fdfabd16895bfd64b44f703e9eefd42f8d3f165140316b529244787d0f11e321e4d840af54b0e2de66df5ed6eeff61b9825894d7dab9e9ef31b2a5b3afdbbd81e9ea7517154725317107ae2b530838061a34f0c546a1325cda3658f00fd7858e0c3a476796434d16f057d1188cc3dcc9b0341b253a19a2e8965e3f06f61ca9d3e8c9ee6c084ff5180536f3052eb38543076843e08fee0d3f62bb2d7bd3baeb27528bda8f39c927b2b2bfffcb9df83c96302ed5e82c4342e42cd9f734b8318aa69be2e9e201d47d78779840c179c8ec37a76d750e09dfe7093674021b4f55b708cff85a11388254d88830d4e6d1e8f63ac43b2610f483ad4bf95309d47e3583825bd10104a1ba3b5ce830e8c5808930ea274284c0941304ab6509c64e90f9aa8d8382d3c3bdc88ea2fd9486e38c93772665d353d2426e32e69778abbe50a342174d4b25ec58bfe550f375ca786e6c8f3d5661a1339ad41c227f8ca18f476bc457257d6ad6b6a9fcc564c6dd50aab81b2962ff5e3d034e1447059d00c4fa5d4936ca004aedec2fa03259317e05daa1e5d04b1f77063a4b314a937c0d04426cd991dc05f68dc37f99f151c6cd4ff7fab42ada690df2332724dc168ce9c5d33b6ef201b358b764b37c51f6ffaf884695caca94f556087d26e82fff1ba2434af893b9052aea6fd1d37f9cc490268535e6064f07b7682dc8f709b5bbab8867e940df8fadd940a8865de41ed839a63290e35dd577dc2a89ac58824e8cb9ce37a64243fe5447b68ec3a56a96202232e77196bee9f3a8db17f7b3854dcb871440b43855bce9d6e1fdc9485f59b2b33f8a8e1f8a8706aba83fc199317b8fbacb1f8b278c6a92558ab7b3c005a71c22fcdc766d71416c50e7e5905a02b4b90ed6350f22cd012007738df16b0d473c31b4676dc1f4a837d15993193be17cded6e673922f043bd2b455ce4807ee8e2ad64cbc45693182239433a83d44a8648120c66247c7a530c23b4c478600878b14c74686193f5382e921ed7c1d5b97d1bda96c1e2e852b5be654df1ff2a3ea0a84efee9c70b20909697a2e2a6ebb2200de5e82e8600b5b39261e9ac22b41706a02b0d371c344ed7e665bcf55dbfba1a78df805f1431059b499fcc9bbe0d835470bcc224458a89bdaefb2592531535f31b61ae10904f533be9890e0995d93590fbb4cf5dad08d3c16cf2a8c5b3eaf51b19e3b66331f55a2321e1158f985955991b794ce182d52a4fc450bf54ef735eb7c7d84f256979b6cb24b527877cc01ed758ae28850e3c514fbd9848f15135ae7feb59470336d008348c7b7f293611f6d3c439060297ae29016f12085057487388e8dc333b500d59a0cff3f2f3cf5ebcd07ab7414cabdf15406dd99e22c629cea505742622f121a21d679a40b559143edd23041c6866d33df1a1460b79853ee318666455ca8abe60c2a606b3a13f3be46d15d59ccbe4db48fbcd19ba517b3e9dec272fa6b200154117274da09a43d9ccc93730626ef9a6a40b89449660c03f8285bd03f16652884116e02b876ed62e183f5789a580763efd60fa8feb4aa6ada391b93c868089b4658c8a795fca8e4df7c20a5ec6bce3e63953df8a0a5b2593690d0f061fb9b0e8a13d4626ba5ce5f5a2a5a120c4e9f601762068c31295ac0c2206e6090f3758f163171fef3f1842c8e48ad21118ed83fca30a53388987fd77bfbf0461ca7be92fe4c77ddd27d3e381a87a8b3f93258be15e43481f43975df687af40b743d6c5e87766cec57a0d946a6f46fe9118f6cdeb68fd1650bc74948325fdb5de23901345b6d7958747c262e79249b88b3c423d5eabf767a3fcd6ed80060c1759707d24323f41fab51405edac29ed4ee103bf90c494c6ea381954a2f0c268f69aa394808472e0f5c64bccdecc7b87e8fc34a6cb81ea685140ace3d0eb36cb8f4692786da468aa84572b06655b1d5d488f4c35c958def19a31f7a3aea1fe5e3376f8238b79656b54a668808c7f043e42d4209f85a9d95029702e5ddc9f5ec38e4b09f8baa002809855e6c21c15883c8da6b95acbb99023ed0276975052022109844d216872252b3bf3a2dbac0b2d46deb08fad4fbde0d980c740147fa22b8dc88c8ac26ea98bae55e6215caad98a84646105982c15b879c83c5b6b6a8faaa4010f0e11244c8125e10aa0d6aa80d2b0c5bed83b554f813bf95ed2e54e6fd845ac6106b32c1921e06274280897936a262bf86f8634b023982f166a00a54fb4c3a23609327e2c02c00021fc19350fed96e111fa48912fb2a250b2f0f6c850fd698ef14c7ee9e764451e50ffdf2e17cf86cb1d4bfd56929639387e1459e3f3e4dcf0c2e0f7af233b0c90ce49f0a3d8b26b8ae43c4e2ddc4e59e3f9f27c759afc68c58956d3172c0967a3413a231430834807f85f734459b8552b33b45096803bcad2a1e04079fff6984667d0581ba0221032500c173da12f621606b564b00e5f5f57fa64cf0ed0c350b1357aacff45c35925f29476bb652b8fb606104c07245fa575a5fd00801a03361673721af9f2a0031cc4510a6488b910d985fe560062a65f8833277ecd0a9baeddec616cc5dd8334fd1778dcf4f9b66e71e69c96510f3cf0ee848dbd73d327092640187220e451df2da4c531a1022848296cedf69fcd50f6bab042bab36c68ddd0843d2612ee7c92fb517ac3b1ab37fb99c57d4bcc02d743d985380921cd4d8b0a78063026c720af59e05192c973d36463a5561db939423df2fc5999e89e863538df06fdd8a42fba346be291ba10b16b35a14ca539db0b4a0317a9527aafc05c147bf80f0f39387bd40b950c6b94e84216867f98e2c59e5b53e8c42141a0eb32016e1cb866a25451a0e33444f390a87babfbbc1784f54092a41685c0e78eb9d8413eacc21ffda28365bab4dc0023aa64e725a35d1698995aab2f268dc0f870650701717151bd860b4f962273e50936f6f2bc733a8091e4583d93da75b194b724b78146057eacac9c35f93df546c7e14bf3dde6464f85859d046faa98a437e8a136e10d0df5b95365939fe61038ca788038162c12053a60223e8cec033d986c11e02b17a56e5ed2d9cd7e6d0b45fefefe86a21af0a9f0e50e7afc8ecbafbf25c53f88264ceb4650790abc7b6a5f7cfdc8ed53643e16586f4125ca3cf7af1c88a9acc19e2951c78d72f2af47020c5b600cd8fac92f8a22cf9a270caadc88d1663a63e3289f304f3dffc26f1ee79af36ee4d6d5a5f05fe6d75ff65473d26386727715ca204da7eecc2453a25101afc33901258cea2a825223368c13db01a4135105899ee2a4a0c0b41b70cf2e4cc974c380401f49c30ea7b270aa0d922db7c72ef27ffd4c8bf2a10c9958422f7cbc2a7b94d0439d6a17157284c1e6725a338d38c1ffa4f6ceaba03c1774dbe7a3e0bc283c9b2e44c94ca1d16f1b7ed85cb1abb280b6bd82efb3020671b682e071e8cff8f38afdc68ed8470712fea2b908eab5433159f53314f28869f8751afb970bee9bb769db23d8f3b06a392e7bf5050adcf841c8ac1f702fe0ffc97e3a8ea77879e991765c2a20d19628dc69f6782c1d23ccd5f75ce1c8d81b4694894d56f30613ede0cb37262ffd109043945b9e6facc2238a5cc47f61ffa5ccd73988fa83629c6cac00cc7fb35728253405745c91a11380e476bd01618540e8443c3301ce825ee418ddcef67f09d70bc24f44b826f41db1c6698cfd4b8a046bea4450f0891565ec3b7efc550d01cbc8f300cd2dd9ce388b43a3fe94b0e85816c4b92f8c33f2b762a8521d962dece5df34e99a3ae686aea1f168d201c65c5cd273962ef517ad83c8cf6ea259c75b28299f3d9e9088847dd365240d0c0734b6bd91231d00b3f5776475ce9265a86ca4c13d747597ccc9dd0da897b14a3c5e46227d697a7ec1597d018b325e8fc93e14d1a2de9f4ff22066d7ebb8318713e3f4fd977f9ffe94b8c5305c8a2014fa309fa3f7e410d48b954e88fcff301ae85e1ba3e855458789d3b197495d069dcbe936324c46a5cf12507e2bebfcb98329c3f8f941bd2d8878f47438c00d5f879db370569e0f13740d3e1d798fde52a9a633af5e5168ffc88013dd2af1d397cc8408672a1ada8905a9d0d8f43412eff5a893fdb6a653d768199ff98020dda889ee9081c043db59e09e789622ac08b99c87d03407fa4bb6de3a7579a5d756014a710d591b516df34501135fa3195bcc6d6b198c86e44f54ba4738af34003c85ea49be5f70bc3f1c95296b6e049b8b88b7650b11665de106ceb6fb48ba77e81f07e0bd69a1a30f4d12278900db6d4cbed0a950addbfed611966407a91e0b7461c88332bb0225c2905ea7d2552b2727ac332268ad8038afd2d9685fa15bc6257f25c9423e3aa1c4b4b05db0f7b122599f2d4aa430957accbbe8a243246c78082da2211fa9073155e6e3e1bbd688058941d131c0e71cca84b24d41a9ce4476a2e290b02bb835e86f4cebe786a56429d71619dee7a0c04930af14e4bc4bf45dbb8b19e0a057e0d17469b566f450afb6dad06ad08cde0dd414c67b49bdd490925af05e1d5c08ca5d518957ce39354cfe2975a2803b6e949bd61ab16cd77db6a76c1579dac5e7a0c17165f7641277bf11b86ed5705bb66f1d53b2f40fbce1f4b3e55f8b0817c7125e4618c93b9469e8609fa82ae220e50c32b4322f0ace8bc800167498234a4633da9a717111f984f0719b405b6fe8a3ea66b181591baf4e6dd4187ee0555fddb0100484252ea8c8711728073c74dd47b45748edd9c0cb6ba028f79cab38d5c97d478745e73ee64b9a52c52ab1657a56c75cd312a9df548e5fb084b7db3585e0f0756415cae17b10c3060eb91889d66d84eb365c658c16d856991a33b2d26cae66a0e1367528297d40c93e77ec10425463c878289141df2661ad374005698e2544870302e0655a32f45007fa69cb3499964240c02658fe3735d4907a8cbfd17daa80e3afd991cad35748cd248ed4a973d3c74e716215833df7f4d130283269a48c9e662669c56e964be558c0d2d7bdddbd4734880e341acc2a2a2fc26f1bff619414ece0b6c247799d31bc6ad8cd7af42013d99eaf4cf7341580c909502559ebeed4463233df5606c6e8e169c3edf2a5410cbb0d13b5ebf02ddb823d7abf05e932e93dbee65c6aebef06db71442b8c1c4b0f1ac6098d32d017a05d3cdd837f7ffb335020fe2db8d0fd4073af82a3e40213b5421f9700fa50f747603a8e5fb7f82da1ed61cdf3d6a57bf1efe4e01f40ced79ba5426ce7ab5bd84aa783ff5623453afbf19028615c9610b09e4813b39bc662dde8d5f5f504cf7cf2b459a7f30c2ac81272904cd1ebe600ab92c0bd12b2883eb7164b4d505b2f27838b1d66398d9a10eeb9bd212736d555abae41bb441885f7a070a6ec6b68a951b37bc7f63eada6ffe696e460ced8d05e2bd6ec8a8186409e63b762c2f236e9e9c2b717e319d33a5c6c172d0e36b2baf0c677b868039bef68558d0ea381006215d09174a586def6f309a5d7d209f3ffe1685938043bcc2ca972ec7d4a1e418e706da36a1dbafdd4cbbc0b187b936af694f44962c38d8ffb3a8386dd50b44ac7dc2c7298cbf2219d94cd18019df32477eeb8f7b62dfe99f268b8616e7e7577db0c83b93f2771025280e86ecdd9ac94e940e99141778a53deeef584b99a7eda0e0def498a4bb9670fe149a97305e9c804055025720ca0821082141bec4589030148c093200d5ccd98e49409ce910b29b1821af64a1d473c42e17fa713576cb7a620ee32b3fdcfe3c3bb38c93090c77370547a5e330f368bcbe8d46e54918085506bd7c6ec03db423a7d60fe0db740796d211ecb51d640aaf1da43a753c7ba4d14666da9bdccee1b4379881776473bd994f762fcb4f211d0af5a3599f657408536ffafe15f666b3a4db544744e70f22fd63f446d3a375a77782aa2fae9c93db612bbf508e0212dc1025fd5b93f5865bdfb7fc48b7193399ce21dc97e1b3e839f39cb72d824337b1c926643e87ce835b1cff22dceb4a87874dc790df12b5bbd334f5a471b3ed70c9d44fc673156b727256c3ee0b961977936f068878fde313fb0e1e54bce3085ce62ab3cb4037bb3d57f51def027acc4c8ec5a2863d1fcededb5b38b2d94c322176edb135fbbc966f13fe2aa3e4eb4497df8684f75affc4765eb386969828de234775d3d9f203c60f81829f41cd66a1f1e523f53c702a5fe2e4d9ca33d022fd2bc09d2a517772cf978f3edb8b107ce37a00cd5fa31b67e7bc7a6a6fc1cf48fed7470b5d70d4c68fe4dde08dd4533bbfbc28bd725c3176663e1319e90792078e6b7b6672cf569f8bcd5603749053dec3b0c60ba9861f38cef523974018f38c52f7d7c3e2fae39151f5eedc5efc1b78916ff651cff13cce3da58b657774e2408ee20a74431bcd75ada8c51236e86e0c89f600d62fc1c0d81e6b983b8147fb5592c3401f0ec6a17be3969ba705785e6890c735dda9791b9d3115ebdbb77e13f13e5cd81345aed5d5d9a75ddf41ee8e17c48f9ef3c7e98c8ea57e24983283b868b6d44683ca8bc3bb347f4f6964887dbaf1aad183f2038ea4f6972ef2db371a470c8f40700ba284ed93dc75b7541c299694cdc976beab9b422a9b147a1cdeb716a26bead34ffaa7655379532637ce0aa6ef5905f9a5b229947a10c60d5efe6b51c0a5913cd40376036200da732f84f50952d1b507adbbfa489f58d49275e084cc165a62d3e3f7753a1dd2baab16536116cf8d9a73be3957498c471f407656b3e80a730a3ce280b2fe5f88242cf636e176dab073e0fae4e3e0eff8cc41b097b8225706a2ed68b355626c1c26f2f16bee2dd8f55080fdbfcab60d43d0a5683984e2d05eb88a773ec27d18bdaf3606f1d6eb9bfaf4ed973b40ce05236283cc90e50c98f0f94ce62675f0bef5d522441e03c0633a3e59aa681778b8c14672e7714886313a55ffd32dd99f9b9e7155d99a052b48b7441117581af151929d5c380ae0fa916596c716889514e6925ca2c2d739df4d4d8186d4daefef2d5add409eb980ff670166d286ffbcedff087ef85a2971f3bbf96ba315f9d50bac0c7fee089107e0cfa54abcfd8bd2092baff6ed93b621204b8edf9f3b39adde28cfca3bdcf40b2d06de1b1efe726a4d878171acd11f96c5f4d46ab9da20ec2040fc42a6d3d8ed1a7fe9821dedf2929e3c10f5590510699a2328a046d545c71d1b8aa9a953c046a8bca0115c3daf4707caaaede9c552515ecfb40e7690a7bcd10d70b435545248b87b957a74d8c4f1320c7bfdba0453ee320ad832b2a9b3d9da7215b584ad709469a89ad272663efb4b54c9b1ede8cd8977e7d9e3f581caac677e4189f441ea59f96bea003f8f7a5b22838e38786d7fab8b2a40c8ea29801637f7915ee6731535eee47acf4a88646274d3280edbb8e27706b9c92550e848662ec0daff0b1c5b274765223d376061f2328c817b6bc51c1115e809ed41e1edcd1e63c2c2a14dade148688a15b881ce83df9b4ab7d0149a3c8e2305f02fa4f8ef2e1b5492a8f97a0f82728a0c7bbf0cbfa8dcb4882658b950fda246fd78f77bbe7f1e626492f60ab6da1a02192bd4c814121cdf169af1247bcb0e4eb57280993ffad34213398eef69e547ce6a5fbb643fc658b618eabf3898b37d7a729168068c786049b9ababbbd4d964eb22adc3db0c78c514eb6360402687ac02ad42644b14d07da89f1d07f101f3a42ef5f5e02025ee41e4bbf0031ef283323d2257b3ef8eb6ab03c2eac79a2387126d5b9f4b1c8448131c169cbf3d9f814e5881991d6981750d7dbb5df57ed15da96d5b333a8a1f44d56b0b4325556c44f56111a95e193b7c713f21de7bf791cd82f09e0118b0d22132ff9a6ab406a27c82afc57678c33f41a36c019c952e4a8e0696a769bc0416667b7a839d7501864fa622639e645c764a1b9609f371cb2e18e59fbcbfe1f94b2c1dd66e62038a4188247a2d3d999fc99414b307c83d8121a80364597fb125187484e676fd793e69f9b9f93b480353b8335d7f7d8d3af8319e5ec694568e83799b2ffa553f2b8dd2ba1606a53bbab07a16b2d99431355320e81004f18c73d851504a19764c0fb9e00d860ef5ba38051f6cf9c5e3a9155b5e5fd240463cec544eeaa23f5a3b033faf9c05f2efe42f78ca17c3d63ee769c74b0dbcc8cbb10b199d4b250dcb15e1a6aa0f0772da15690f08097ef239b7492d8a1f698debb94e03cdb3c28369afa037149f8ef4bbba748bf3c47542a97cc6bee1f9182dc959e4a1d1f6cad492c8ea5a8509a0360984b01ccdd6650f8e56328c6b3ffc0c0da123e321fa79e017fd1ed75d84c446df576aa241e70dfaf73dcf8708e62bfb84ae18661daad9d9689e9c2aaf5c341b8a861072cd961f8024576615767c78f70bc63139d4f19f86e7adc0ec0c2339821441b74b9af4de5c1a93056140e5c3bac990e692a75b941b36a33404a069ef2a30db953041beed4c08f4da8046696574d7e16ec20792e87c885034ebc1c272bf7f70b5b3dbf0b6f4aec9b742f9ff569765300c9489192f01a9236798e51abeac7c5cc7aaccf4cfd142ccfdfb37ee800330ab03fe8e5df92b2c2ebf768a463040f8d234759f75f0f96b6555e161be384229cd65450961bcbc5f3ba0196283b1a2b7bc33312d396ef2466b515fdd3ae713bc7a5ea1a7b3f7e8657112109210fec788e5fd705799f8fa0b286c401ac754392d14e3eada14edf87ec881d5fc1e90e3b097a272a56001773c1b111003232aa31d44491e80d1424dc182998d79c3387b1d98e63fb804d8ac59a41b97261b2ca373ca784ab9186e9ac0cb6cffef6e283384d810e68afa646a1d528ded0da549e686940755a125b2f6488ef2a3a1d16628a31ba48a9d2788c519956598e695a983fc583011f136f64794719e7e11407e40d0c7362048550f8f2c49e4130373a2016ebda8eade3804f73c874fcd4d53a686ac7e2545c803340477034d120ef7b369b245221e924194d7f587bf62db6767f45df9613ff3363510db929f8aad83befc91e50335bb13dc8c3bc3baed84e10eed5e8d20c8d099ea33e7a79819e237ea46cb6c090cb120e8c62a59a9bafc45c69843d3f0e7cd6e22779455f2fef10f2a367abc72631e57084d20956a8217c0e0a97792105d830b85038883b0ed2d4886650afa0582e16b677c4267ba46bc3571ed7491a2ccb28621892af552c4425ad933d443e878ceaa7d1b70e16e0ddb0c78c10c63602d804ca208e3b348a987e44d36aae5f02c0a4a505ee5ca9d8c8bd5c631ba0ef37f4383aac774b6031d0fc0baac33b83e95b1854fdd261e0c45cf297ad9c931e4c04100a25b4289dc5959fd48ccbd4f426e0f8f7abbe16d32399f0a1c6325d3b6cda2ce53c73bb7f641542b0ef8bd7cbcb39c4c81831c987d7ab380b40e91813c83c561f098c6fcf7f0e97614476c763f0950a7448324e155e76d4c6f4b685583befd23575fb8684434e26c0edc04eedbfe6a8bf2e9a3b1307f29230005559742689b1aa1d6414d5b422e0c86332a9c5a857c6f1a8c5b911c85018d6b36ec6b5c83aa75f9a90c6c7c025e88d7081adceca07ab1613cb4611869e1d622f3b33d0a5d166fc4c3b4f7b42ec46b76c1e34305359f33268d43b3db5ff2800889d8f43b90380b22f19c07c7c38f199d859f41e470d4501c5e29c51dc9153b76964a2cf7c054d360e472260a363f5dc859ef68eddd7e9ec5c1cb127396a517be0b18370ee509558817a622035347efdb1935ea02cb9e654e1c1ecb08eafa78e2287c60c9f4a946be43f7d14adf8c303315983a86d7f4e4c1588a48064eb030cc05c2b66eaa3af6aa51b8f32e92f2e6b5fc55a2ead9fa72138e2c547cc18ab96ddf1794fd0bfe13306123fe54476b906d5423cd05f0dd72b90841afaa149861801cd2cfc0895d1bf2d0a2e65453a2d2ced8682e78e3d8a76255da354846227be9eb18a98b346ad7266fad0b70c9a1f7cca00484c79cbc383ae39c3c4aa1ea7992ae0e40b0c94718b95171c0c9c00273eaa24f81aa11a341e6210048bcaccb07b8c73b735ee8dbd0796706df803e2a5bb8b825991cc2dd4da543c110b6ddce16dcd2b81411f4f1a3b5f5dd1e5f7e8613e14dccdb32c215230d72a949b41e5eed74c1e2f330bc812cdb81211a5bc16b02d97a547bb3d5dc3b1521f1c308cc071144d6d6195c460c0080c54f561fdf70a5607f8a7090edab11e2dbdc60dad6d8ba940bd975438dbc1b6c0b595d1538ebae52b3860f569ea786b75cb7e4067754b2479d1ccbf9886dc2da6cfa6080d14efa3dd4a952d6fef83643ec807b383a9d65540e7f0252f8456e1095527ce440565152fdc2bae1bf1da201a4d2905021e33ac0692e300ce5acdc4cd316cf9296506fc0c3d1d9d1d0c9f936d9c0bbe7515309ce20411ef4ab77dfad988c38d96c74b1f0efab33fcabf7eba317c929b71704171e13a108714b4f45e09cc02406f961697747667d5db00adf870a115ad4366c96bd72bd009f6a1a194d205efe8adabda68d96a526d1d242856f28d6ba70b9fb7142f2a15ca1201ba572d6f61da6836c2071535accc9ffbbff37b71e5ee5e5edee8c941ec8482ce04f4c7fdafab9978fd0763d32888e9803641e18e78dafc0f89791dd08f4764140ddd6cf2855cd408c6dbbff117ecd2e82c05444805e57f703ac8222720321cbb4743efe56a76ccecfc226e4b287f75a0e58209f974a5da6a4e5d0eac8c74957dac09960ebb102b2b05ed6738adb4c24c14b24ecc2da436df960103bdc5d2d39e5d032fe162d205b7761cd49e966b83118f74bbf010cef8286790db3396ee24ade17720f2e2670b2083e1a069941b48de58a1872ab0116929b5bf1fcc9cbdfcb1d61f22a0634d91f48c056184f9c99a92d47c840469d32dca7c0bae9572c9d082604c761f0af30c1a45689de4dd4482653162324454e0d226407e1b8ec08d57ea9abe7eb3af3d5768c1967e091af01da1edb98c8ee581a6a2d3434328b5aecb9c66bff89b60d988ff9fbc9488e514e828fa6fcf1cf219f3d49ccf2805c09be8fe3a3da368f5319d4d16725733f812ee8b187621449b8a1ddacd7039d180022692aa95ada864c78994f97fc7220eb1ee1c762dc9e4910859c5d5fe7e133bab771740a063f9f71ac03d32c138b13d2872d569b58393b6cc5734201ca4a6820739df66ff041944444f4bee59ac051a33610f49f9ec1e857b61eea2ef39eb73425aaa73a69ff871be0d795f9efa9d52b7a8d40dd1cb2609f1e3b718c531f416de2d142ebede5edeedf53586e907aef3a54b333579c6b677ee1f6ea111aa616ac05d6892c9234c4f348de55abe4633f1ffd9fe1b55b59599d2f0eadef785cb0d419779b2e652d1ed6bdef3a9d57e2f4b98672ad88a3583a936e38580fc2dcd0ef8311f79ebf607c8d611d9554674dad6c27e3c05d8d318a1ce1f73188d9d327b10fce1a709c2368b6e1049d0bed51e2913dbc2de7d348a777449a5256cd1158d40ffbdbacd222073591f572df5f1ef1239254b17e50f0b7b0e88fef73a091f732733cc95d69d319241a70422e1a6525239ec872f0bf670b7337f5f785c8efad9636a666cc0046902eae8eb0e95d6e40f2c023d5a405726a77d1a7ad3ee9d6344ab4b48c2bd441c1b83410bcbf7da3a89fe3a26bdb079c9199a4067c9616c411af9cd97309b7122ef1e1fc7590268bda4837bff7b4e4d9a403b0cf78bd09ea63d5ae74df5954ea57d6d403cde7415d33cf365f921354f7243645b9de900480f177f983df3e6949b79668679552d1870c5968ab79bf751b1337c6eae8089ba8262367c4867d8ebcbfc3dc470f673408da133b67ea51bb93ea11abccd5f9e7735312454f604c2a692578ad619e8ba324dc5e849a3c8ab93c8466f0ff22caf67bb8b03f743af0e5830747c67be40c9c28ea584d52f69a869dc0dc14516a9a2b679244b5b70c1f186443f3257b6d146be78958decf68aa1b9d2de88bc908746c2d65a31a02dfa4ce02af64e38a295d83ecc49834e2f22df173c107213cd41f95e3e0453a6d536599b52f11941ef547b181e6136390ea8e8d4824321dac7fe143211af79e4903846767ee2f2d6a78433fb2af5ae29c8c930d2f16616d569d7824dbec08bb7108e9c621d364af3dd3f56bc7afaa03ecc232c0371532dc033fdda996a5d51069875623798dc94b4273ce6192e3a60bedbb3c0c7f2d487656edde1a2cad5a8ff7d34b27dfc579c8de7448bd2621ee1e47ac6d82d6b8eb90576477c209541c4f6067e7a0aafc1ac90035032d82dafe55f29264b4863ca5ccfd383357acc6b9a4c69792b4e14e30aa08f03854509e9040e5b1fc12e42ac6fa66c32324352195138f0880d86dd83d12b0852c75b5249b91bc339a5ed86f98330d064e11b449be44439a0529b556b76ad7c3405388971cbb083eeadd1b07b67c9fc660e852260e7c6bb686a2c095cd023a630a5bcf38a51cc86ac7920d3adbaf9c99a44b61033fb25e53366c67f1856aa6bd5c574c1b55f43f0df311df44b9e567a0350bc32fd8699f1ec5d2fc03600a2dce51917278f94f71e429fa273662ab5fc250af96b6854d99075458e225c1cc0d2fd5435e2126fca7aa6ba7ebf8e1a74f628ef71b98487bc11d4a3810492a14f09a113e6d191630d1eb4d44ba4d8380844e851e4881f208d21176c3d91509148958c6b5187b140afe3a8ce1380ad66c40f17b1b82a876c7d71cccf2b04b588de6fda296e84a71bad0c228d08ddd778a89bf075ed3326795343d7e0c046479c779d8013d217e559709f2d51a02024be9d861ef1bb072e973c62274c02aa6b6a49502537e90913c0e17c504fd6e6a586209b0452e6fb48ba97ff8cb162aade0f0d8542de017ad79d37979ca42e03c8ae5bc556faf2510661216cd6338e474481656d039c04d48eab98a09f7ab2c479718bbb6624236832defbe44cc122f5c36c1a178c1245b09c7bbe06ac468f18b085fd9fe21ab917d72c92f31e4a5be48bd2490750084eb4ed06a9a9d07ec6bcb4e34a903f013e7f429f25f71451659edd2f975ad0de32e05ae3364bc0979a6aafcba2692a36c79ea44041ec40d0059d5cf73466e174610de40335c9ad48167a0f7512d97a2b6cb335ee992d83b639c01d9a8fb01a9cb9598629172829f8989f70a365bddbb97d44aa990c5947a9ac07e671eefe3c8682017850d75f460afda209e387084ad8e11ed5cac5ec3eea25bd24443ae1869b030d6f41a6ff573048a19f45c463c7d122ec16bd7e32631af79e16359464da9380e800373dd4e5e844a5634bfa129e015663540ddfd8b4da3c5b9ca7f01131e151dba39a208cc56adad9cc70182205ceb56b9046ace1e2d6a244d548cfac5aece3a0077990b9915d3778b7f2441380f9509fcd89f0af3dfe54d44d05a31198716265637d75b4c5088be49930b08333469253f64dc15ccb9b995e15433d0087eb869b9e8d4dedd2bde5d1bb4eecea9b20c740f8c190f076dfafea718a3667afd9d8d9e2b2ce668ff6d346685a307d44584684982ba84cc3e8a213e005922b7777c50d8d3720fcc5649d9219d065e98d212ed5e989d10f96a7aa5921a5ca3f362c30c89d805e81f96922686377e4bcc5435b82ca75f3587758440af27687a408a0b6a15625424a9ed21c1ef58c7162d7a703bf5e36c061111adfee4c099351537b5d2a355cf4be00cb17832a03893de4543931d1f6d3ebcef84ef61f5f609ff7fb9f85d2793a787df83604b88b2ff7c6ad2f6598abd4d39596271b930005e23f602d042e4fdfbcef9174fe0c2b061f35472db3e97f10ab09a1f4dc3565231b48d39e51e002e5cd644a08e18db7a3be901b6f52571dfea13e6c7cfe179b52f8ef6423e3919905bd5e04daf08827c9bd13f6e1488cd0a04cfcd850443d5389c4ba251a3c06365e48cdc1ea5c2af27d262eab393745d1592c211b8dac952a930c3a2e24707537033da6f8d6c2e8fb07f3b0b0e6caf8aea09c8a105a8b99c2575ad3a1a4d7667e7344c1e6bc1d247c65f8e9770fe0c7a9951b645167c9cd1604abce2fcfb9b30dc15cd9993d5ece2203dc7a0a8666ce3dfe87c2ac8a6fa67a684e012f0a9bcc5ff25cb334f4fa9a76ad3514ff5114664557a32bc55643851f21ef644b0992d4f28e533b3ca43f8758cb88ea7f989d96dd91b965ef639ba60f14b892ab6de99bfba5c437dee44a6b5c485be7cc82d4a8ca08bb0de5c7987122a566589bc661e62792fdfc168c0fcbdacee2add9789f2e5ebc0ae92d0a235ecc7873ce1b40bcd74d1c3b6d5598aa0ed5b5f5007e293e395f2c2921a6e7f446a836155e4cafd075b2f3cfc60ea07893ef8eb40b9d59309b0601e3df57c2909340ce8089e9ed2325deffa7b310cab43ece4e4d0d27c773c10075228ab59e7f8182965abd0dcb6b652050b2d365e7b1d05f649b19ce7b62f47ff95a31d052ad4ee397b2cca64e292c6cb37dacb1aeb0f547ef44c998b4b1d7980441e01b7446e164fc643aee74afe9a6c5a8e69a0a34f1829750167641f1749da69f44809109a2a4c5d0312929049f38f9929fb7191ad69eb2da3e357757973d86362bc6c9511a347ce88106bce57e35c4dfe9a33d5d76a1a6d7cb74766356456b7a15a66b7d2e483647cb594768c48402704e09206985cd644a3e61a51bdf1c1f451a55910932e37e1d11e7e3d60929d96a29f93c7c382870d29deaf4666f9c6498becb1dca7a0f03a63998377b82d587aead568928700b483b71483f53a2543c1eed3b9b4bafaf2d45e10ff99da3943aa77ee5c2e1a84a6f0578654ed576171308d00096ab25c93f244ba3104662f94298bcb6a7fc1bbd7f3bcac219b574146ef64bcf0cd1feac4b74bc276102781f62ca04eb2c6d085012e0d06a2c2412ab1be32698f304d73b0a533c64920d8b33530b65e16fce14b4d615f1bfb236fab226c9c90b9448490db19bfe5d529963885ff0938a175e112d72c6c4327cef3f44702cdd7f83ba63ddea8565b001d6a23fe51eef3471f828f5568faed064ab3e2390c86882b5a6ee8782f0ec6c37d98acf968e5bd613da46c36bbcf9f9f0707590a19c28a509b27a8bfbdfb730032a64bbba14471ebea2c398a506612b785922597ee76139034cee2ff3e74a4d1eeb471cdd7f43068bad806a26c8d86412362793c7823e1e500fc4495399b0011a6988eb76f3be8047a849951eb253ab9bc2767db88e5cad1100b1fe331b68968198a2e033211de0c804009f95c409a4e13210a30cf12d725bae62b6f6ca513b8e7d9ca8eb0cb9b646bb178847fbd01b44b4fe1e6b15b0ab7c2962874d5fdf70a455f03d2e4454163e89b20e6a1d8033870e1e177cc6d856457c3624dd76227bf6b5d4f0c2243dadbc11235dd58a95a272903af1ee6671b636d8f47e90bc36d5c6e070d9067e7d81d504f566d040c3f193aee5b48195fc5910ecc4926d1ebb121c2d451d3e99a2386a69104bb5c15171ddad5caf8e09d7a051b33c1db342cc4ec3d81e59ff6f19983a44ca609a48d86a053d635ea49fd90b0bbe55be786c28205cbdaee73e6ed957f8e93b8f2a9209555609d3346a1b044750221bf76b3d1c10504ab8aabb28c0aeb89fbacce4dbc40afc27e0b3b9e86a3913cbcc9bdd343045e0ce24e72c6f52b6852683fd9a61533e5994c107ca65a0d81d4ee5322c0f7168e4c2b32eba8a7fd783eec9ef68d7f20950595988bf1f857bbd3e184aca1ee2e7f5f71fd30a1075e59ef1103345a8835806dfc6a8a246dd79b7551c26a9b16a7628dc65ea8c930abfc7a7cf04f1e475664e1373cfe6ca0396d4c750d114081f22d33d7981eb536e60ed2d3ea5fa72cd046c5fb110fddfb14466f0d8c35b2658eb69742505e7d1d4161f1d4c1b9af6e373fc984baa35f81392bbfb56c201ae31e2b6fe2a22680802d51d9ef98e7ea830eefa07667879bc93921d4bf4f117fe0d328d1586ad150b58685eaaf61e704a2fc6ec854d19ac27a9d236d88a1fc310c23255187c88a5e2f7684a02098fa671d68a592e2096466c5b87191efc3163193123feeaaac57b4fbb7d78352b01f5ddf29563946a45c325c74c70e2af45167619af91d10088f0699695decdea4b41d43be14e0b0fc95c150e122d56dcb90ced5efa1ec926bcb5eb55287c855f215bf7dd0159a7cfffab8b8f897ecef49764928f3792c5e100c8a3fa3044361276a2b987f57624a509b2a119c12a678d25791101d182af43f1d44749055116496fd0ad78598cfd78bb185d0a5b02fa302f3559a5f952b9ca6e639da088792470e04ce9d76c431d94f0633b7c9893c3a72e01289b8a7573e60cec814bd2e2a553eb1728a9f629cc9324f93f7e440a11a39a437bf3ed1ed4ae684904cdc4fa3546e583adcd55d48a8d0ad228e4eca534dfa0cfe35dddc7b817f9a01d9da79ea45cc49915a2935d6226b7e0b99417c55e29c070eca6c394f3994026fdffe5382a05ef7e348dfa260c790b9549d5ee560c7a83c1ef7f413a46885df2a15f14d81d83d4fa51c709d5ddd62a34b6a8d531e04c48920e525e98875cf188e8c800876a3186d081472c3e3c5180eb3f8b1dfe1b2bd18f271262211601f7b0a7ed0620c5dfdfcb981ba7a33b8b10af6995bf36b19889b9938a460659e2454ab63e1e0d423ad0d984d4d3c0874aba21623d301d12f606fc3296c427dd4aa1bd6dce71c0f8b31b1c2be774382b8db3f518ffa883b97da0dadd6dfee0a647ffaf1e89515ca8a6adeee729e35a3a3b43cbd4826397b4b7b627e41ca18eede322eada538090e4b924104dfa67909d1a4d4941ac4448d911e21c6068c8d094fe3359bc8a26ed66962bf618c6c93f9177d5f93527789b89ba3d68a9e1b467d3616e26af40dcf7e8e71e6fd1b88aa1340ebea1c0e8ed4400f13904be8e90cb4af01bb7be6b8843da9f878948cd705f2db20ff131ed1cfa4695d6352b78a2d9bee47e45f19a30cbfeeb561853cbb0ccd307a5c95c9824001f9e74ca4cbe5ea9448f3ad32cba1087603c3627ec1c11ce2d2a8692240d650735dd67199fcf92bfac80b38d535d4c40063632bb63ce185a3c5aa992b7a28b761f0683010d3a3c058a07d8dfcdf7e9714c9c639cfb4de8a8ee7bf4efff8c5fa8ded4d8d5bf9e00efe6edc2ffc87b886a071b6e65465192361c76ef37897f7eeb2d13fc3ed6fb02bf2b9b7dc973d4a5239cef90bf5e60fbf5d6fe601963d3d39fc92451d5fccfe61d8873bb2ec8e386d2752f7d3d64f3f01b90f6a8b13ec9fdef7842f7d7efeabb2e6dc7a2279860fb434b71fb32eb73f0f199ed35e4832a03f1409b38eb0b76df9f885aa7d741f777b24feac604b4dee56aa9412461c5784182c78129ade4459596463f7f53eac7e8eb8b15c2747a3dd1a848c6a5ce7974a54d6ded7b739ada83925ab0a1c7cf2cd2a1871513c77fc80f8ac1c8b12ee425bbbeaad522e8670902a42a597f04db17ce3394e0a1423ec869dc9de9f790da539d84cdf51c4b98c5803a02c6141d1b42c2fd896e4cdc9b9fc2854f5d67da69304f70bc3c4dbafa5354a0b896c60de031a0fceb8d8c1c2bebb7ebf1feb0d549dcaac1ed75ab7e4e6ca0842ba63a95fb17ec222dc836f37171981b6b15f6680a67bbead684b46ab654d35843f036b0a51c24a73d0ad34104b473c59cd7f6083cf94079024b5f18d50b9515dcfb63b4440189659718bd1e288348ecc7df7d80a9e8f65a8611049fd9d8ce67e831283a524fa8893c9d0d907c05fb8ea777c015a1b707008dc17fd1c2646fe9e85df960eb1e8c00e6dec6603ad15190860e1f1cf1f61a332f2df442b7a48e59fb5914ac307fcccd938ab4eceafd708bf0fde6d8017ac1d2e00cdc3c240978173f357d0f80847edc43d1498c1368e8251fe4ad885606a0078415e17dd3dd3ffd23d1385c5a02fea1688941bd1dd7e547001ae785bed2b7d02b117b4b665ce3306a37af2017009b41e56ea7b23e8a7c0f8ec0766a690f0d8dfb105156ef8042413af70ae21d1e75bbc6cf8df1017a144d736241c745774935344ee98b7c964c2e522ef4d2f71f7560704a505b14fb90e5b1fabe63b43a17c3c242dd49c46135d9cc8e1e118d401b64410fdb89d04b851870846e68db8351733ae52d52bac06fdb52efc13c8ff37187a755dabfc3018f6efc4fa1fd06aaf544f0f2333083af3dc6f335a957824187d3988ba57e605e43f68deca71724add64be8cfb852099ef7382343ffc9fd367e881f3b0aaf60f7c4d7d5c7b38f687be5027a7d675b827e4aab8856986bf77858fb039a83dc9cc4b10d281a01b026aa81cfd5ba674039c9cc6301ea3a7f32a4f28bfb059bc530d5ffaf4192a0a9eb0fd1137380a195ee1f3ffe308843862a6bf97e4a071f00791d7e7ad63f83ec2b22e3ddf41437fe96f1f32068481d3108e6b47849641eb7dc8e969a596fa374e1919517129d9d0d6d78056508c39e0df6b9eeb2982c2d98033193aeea3ebb47d7ca0663caddb4dcbd63fa8f6fe4e64427bf2ffdafdf4b89bc2625f4e9fb31e01e894321a337e3cd977aa1dc8fc5fe3fba2d504dff188d77e898ab92cf566377ff837fb106b8bf986c903053ec44f263acd65661423d30c33821061f32e594110ff80588f33315b06f6bb1893c25fc0a031cd0cbd3370f532c5572e06dd70ce7f3e9a1f0595c6ec10f5de3b2de433dd9c99e5cf1c6ee3762f30dca011f14be2629f56322724fb5f885e3785914a1bcbd017bef734471916ea0fc99cf8fe21fd6120ba7fcc1466ed1d7d570ae0e4e5dbc7602331de15d9ecdc821d65d79da065a8744cd47f37eec88287f9a1f3e8db1fcc7c88913afafdc66298b6f126fd1bee2fc3b0735baaaba2a324d607ba179b396e830e238d8d884e8d7bc018e1d731170dd447bca53a2b5a7bbaa8fbcee62bfaac97eaa96156d67395cd5140e54c2d2a2af27a7e2441758528d65e5ec7de319fcdab11ed2500de72962e81ea3d297683045dfcd54856393162bd9fd2a25313003e10d822011f3d30df0994a752c692c6233f60b2c8b3c0529bfcc878685bdfd1aeb6028971a0b16b86dfd40f6f34943cb19d1230e2ee8be4b346748377b4dab8de3a2456c22e06e0c0c72a54207fb0896fd02538d305e53d20b0ec8b7c8d506cfbace3c43a778f9e60b076ee3f6769cb070ddb077bce84044b24d69a8bdfd91738f48b62d1cdac43f20f7f56ec55794515eed0367dbac7d45f2afb47c04491455d41c5be1b78c410bdf11d4ad2a53b4c854f9b111babb072697727aa0ce0fb907f1d500f8436b57fae4511fb63eb9f84bb327cbdc939476cc577c11e6b4fb6c02f5c122e541003e2feef34b763424b3f66b9600dccd931e38d2475b0edb929f1288515b644abc3327e2136944d835814498caf7553c3f724d4c102922a772db3536562766d0b06b8453ad78d139d426ea5f882d11d24bdd728e1432ed77c77f3fd55b740c29187dd6f2a12d94a1a9f096f5cdbd00d754f24a23efa621338ff70c9aca84e4c8673e6c20c630d3e315c223fdf15ec03d1968a67b7593d1fd6676c5b586a40352270eef173badbc307a6be314255c84414134650469a960f0978dd09b711ad5d1dfedadfe916503e9c7d2b31b170012a7192916e54d541ac5ca57a4571c5aecdb0894084d0b95fd4673c630f7b5d55681174526928b8a9159fecd90c6d8e50c5f2285c1a10de52e5a3b8d273e075bdf8eeaceb75da6ad8120a90e252d9880271d9b61469d08c8fa02b4ce64a3379c1bbfe1fba817488ae9373e425e0f1974a9147efa9ead2d4105662eda3737edea4c9a63d17a8e567aac50897a0ecd72906c28dfc2e41a5df5dd61c17d7f6a60604fad402c8cab492d26f551585c095ce39c17e0fc598a3140f34aaeb6e486b2ac5d458a2f1cd1c54b503d34623dee4b93c9dc02fcfbab72f621cb0e90256ad537da4a4afdb38ef218922723d1551b8bd581027ac79c5578695975b096d0200d48b0ff1aa6548badd57fadbc6b60648a4f8f4ac2699d98343e7dd3d0632c2a0cf7a6b3813a0e21e8eccab9cda6a91dc71c3c42f3c2f564d9a998b107b93211ba01ef7abe7bd8e991bb15daa16745427db628ce4cb393599bde748c1731260609985c0e1b3dfa512a42f051a7510dc3e26a387ecdf236569ea6cb1b469a72336489fa5e9576c3d93fbb9d7e4b44216865aa75afd9c27c3fa497910acb90d1e36d3e958fad04fc2a4f33e25148ae183579c210fa8bed9aed5661943f52dbb362e25b036aee90ea2a23918228e466e645d878670c81e4b1f2669c1cacb0585afb9db3b64b7ca2f948a48d8204fbfdb2994e1e87a7d2a8a7bc49e553f844b86cefb2a414972f2a95b6b7dd650b18298de4122a258414464a5df39e162e4cd4d4de5aa5340605813ebe403367d7f157a93793f595f53aca8e2a4d7ee4074012a02cb5323a417d8eeb494eb0cc17033de1d08d65c4a4f9ddde4cbf28698699cc94163a910f1de124935da8a45019f05898fd4fc183863573b1f0d67572e29f8b97d6f2472f8b711b7cf173ad7f50d3a810e506a2ff93e270281a5ef40dde744ca6ca4b71e811c76d801457935455bdcd5f855c3d29d2a7d3b9d78b5c48b88df16362af28d795643c440b4be67229f7ba80c4bb4881cd168195b3a196bf2e5531c60d60dc6793fcf3866d691cf8cae131be330ac3c01830c027ccb88d1527d6425fc37f48b27e8afc958e277c4113910103c7d274b61f22b4a04f7b0c975385979fb794c20dc9b2ca5f8cd90be81aafc8448fe77ab128f429740b51918408ce4b4f3c05423e52a0347bf44ef670812a7a9c1a9bb93ad9854f1a5af49d0bff294229004042f513808a35fdc15d07533ec9f551ab32623d2dbfbe12029110a141e179e4d2efbbbd6e4212fa114b78ee6ad47d68534d195204d625040354bcfc573e98073d8bdf549a6d3485c382716bcfe1ac369898258ab299ab2c1d1ec2f3d79482903038841b46663e68ca4d6a4789f0ba25598530bdafd0783c2bbcff336a54a995f8f67d092546acc1150fcdc49b036bda364c1042cd0a88a19b9aa890001178a8b63edc7b773707e26ffca4bebd34c8cde6362a4afdf31eefb46bd79db3f49ade0b7a5a9e134ec2c5d0104d3848607b04818f2d42c7f07749c4d571d550c5023442af41bbb78512eab19f8f7674a126fd20467509033cd228564d178596f90d0c55260a20e153a6f5c6da7e1cdcdbc5a192200bfb1715311b7ce8f379c802673459710d3d2682b1ace45be05d605fed6552f7f77a6de7f6eeab7da6e7ce8f78a69d67a3fc478bfe9d7fece3df9f8a13e95e0cd73cbd49b3e371230cde67bf52a34799f978baf0b5ab12e5ffe67c2f027a8cb8d59e610e749a61eafd76f165cfc1649ef71f48647b7061e1669de833b89d8b5cf95e3384ec121a1b741b13b7430ed13e5307ac34fa1fdde82ed4f34bb3164292f40a57dba8d8a34fb76816e746a2c1b85197dc3081273fa7d23c6d296ab2218b76353f59388064cca506d4d95e486ebadab684e182f53a84d851e0facac28a7863c485c039e862ef22a152e74c17b250a9a10db26196755c0938a193ffbcc80e62d93468ea1998481dc5062a3d89ec69a2ebd7e3cc9d739145bad5a421896eb7db228eba4d5869885a212a382dec9184fb2bb12f43d4a175414e6eaf22f5c77ddb3488e539a920284462ead356142bcf3041cddcbeb22cf6a8f9c5261f769f22b5cee8f77b3a0580b44a8f0a72ffdf1251abc5ed4926ffc98851e150de81ff441238c21a8bae46611d933c3fbb94fc198502fc0d639b45bb04137fee531b1cf9ea4898b52fe3ac9f0ef9004831cd34490ba5b957070f302c8fcb117076f67ae27d846de2ebf9bd1ac7aba319d39f3b362c774a666225f58652f7dbede7de62e5d5d0858dbd5c90be6aa5dda51889584019af8a9ffdf3023f686ce85acf977a6e2cae426bfbc516c432f6cfe1bcaae638808e992e1bb47b3fcab9b00c75a8173c74f589730f38f4d6087b5440f413f12ca5612e095125ac4b300e424008c16f6fe99898d129bf596bd25b17060ffb061b3b00d5c43454bb21f063c7b03d062cbcd6be4ea4a86b8deab28c7bfeb87be73c8a8959890216864a6e34a58ee9ce02d5999ffc9decef9cfffefba7afaf3d92b490efd4a83c3483591a2748b66228cbdd1e7420fd46f1d26b0d8dbd83601e15a5d76087b89fddd684d211da4fc42a611ddac22dcb3cf6d6fea51dfc20f0cd3c3eba248ea9967fd84c248ef6b434b16e0b50447329e1ed4ad799b1a0e1e89dd434cecae57d6949670520c67c9398ec2220a77efa2e1893420bfd9afb9d50b2d8e5173236941285a45670292538054ee4fdc9effbad56206d54a78ff9d12916ea0f1a44f8169e209a0a3ee0fa71fa1153ab5ac683770a1d48e59da4ff59a8feb9c364f7a99d25aa57cbfa45b76a010cede3ec6219965f0550bc1443f8c6010a8298339343a0da30712d740cbc81d24fd937674451a1993bbd75aea251f0b43e76a4333aff96ac2b34df28ec2a9fcba1d5a87884ee61645558b3074812f4f53b0884ec568f5d8517cd01cade1e903f784c6e78869bce9e2093253f6407452ec3ecd9657efd68191d648124c2392f0597022c91ff866a946a2a1fe2cf37d61b99ec177a85efc6cda315b1b3050bd70186123a8d1695e5d927ac39733d84024910da51c09a72aaf082471980db826fa885d6aafad98aa269a65cab6b0b2f992940c0fc6fc1fb6ba1e1d79ca2bb15c0580bff044d6076722dcf7f8cf5fba79411e6319ac2686fa8d19298074af70b8bf8a241076ce88b093cdc27699b66127a3031be25b6d82f0543a5006033d02d5678a3870635f651351448ad649e90092cbbca12fa80262b42a9c2903454e30d56a1aa586d20f2494347a5738cbb5aecca0d6b226a7be8a595337738b70ffbf523f47c815732b0c02682e417c3bd3f3d5984cc04daee30c98b5d497234c12f598c79040054874b3ea8e01d91b50080a498c2025cb49d590dd207a6ccc7fe26321814095f28518458d7016925b4b39413882ea1fb1b2580e58fa7e69bd549e1b0a1648440ede71cca2d68f0fba44283f498c3c5acbbd885a187888050677f87728a62493270134ee7ab158ea1da10e82f243908e25e8908885a2746b8e448d3a02c9c3d0026f36780c81cf052efd83b1c9ffff815fad11688a308ce38e52c632eb9e4cb59c26e22e8a01d8cba3148c1f7af3ba17597cd95a3c9b78865fdff3ad553d340782be52f929d83f6f4d6044ae82731e51ddc9ef43bab1e4ae9a22bdae4e98c7bd3de47efaf2dfb54719408e404da99e5c834b5f3b8183fa553fefccb5f2bfaca3a4e5338a7810b4fc1d0f97a13403c7599a607bc7bd121beb627c83fd37283487df756ea7d38b8db9fe57ba7f9ebcc6cab3ecc7d70127a0f0e80a111b4309ee7a6118085cd548e8b797bfe2c4570a9ebdbfc2c44cc8852f45d3f7a7e6778b553f42a24c6c299b5fd959a111cb9c97b73c23a13dd7e22b980c645e40cdbe23d0ed56dbc18b95d03ae0e698162182c9f6ba881a4849681c3ada9fb6c906e7eecfa39e3d43780852e42121622f84235255fb3f7db690db4c3d1fb8d86e8b8c4eed38e05864fac37d926c0a29c4aaee6f316e37bdedaf8eb1fd3d957b9d0a24197a3539bc9bc76bb314bcff9e84db94bfded0c195e28254e6042e6a563e8bd12793134a919d48d012fce325c9ba590aaef758e70bd43757a60dc65a6a9138987008bc14718be687ba42bdb10654a00bc4a644f65e00a61238e892a70ee5d56d89b1345789a6f7e5000c82e5f9763385e62e1cb0864e1a2e89a7be3073adf92c4e8854a1b336c8c5414a768f3ab4a9eba29eeaefc1f221ef954297e597ba3615d3e3792a332ac892a0899fb259039fba984484cd435c1d8bb01d4bd3e63581dc277440eb28be7f1966315312ed1500e9b07a39ba5e6ad63ffc146f1fcda7aecb8645d2a29a7a4128243041bc1bef1618de2f9746160f47c28f6852e466e2558ec0f27607e9ace220347b6ee85d443c8865c1c782c88f2cee8556f6d37b7a1de249d3a3c52ad60b4c7fc83fb3124a6c7da2a1ecc2e9a1f0a17a5568287e0404c60c9b108cb381325606fc88043352579b5e3c11e84478e8ee72d9cd989e4ff6e3aa1234f7d01b6cdc03fc7c7e1ce17bee16878301c8d2de0f64de9bedddfa953e85570aabc00df6c5e8844c4ae92b82b2001c16a60d582f960e73514e19a1d7d97fb1cc82ebeb0acac2c1960c8144a571076c28caaed6cbf89f803a96254ee3588fb6c5169025046de87a04460be98b2f22da176a460464229239549091ff904654dac96fee466eccb16247b4bf75b0e4fd1feac1a67548d055705e75f82921562f76c5d3fe9823c76702c33a8b0dde91b4e9de9541882b6dccb1d4bc1fc8bca7d7bbf5e930f94caca5a7d455be1ff3a05b3d3b61d6c5736b9ee8451623d443bf977f558f425bd6b9ffd58cabef15e5f6e491df483d4e1fa493d5415ddfe79e1de7b0a0030038ce77a30822d93ff8605625319f6d363f81d237836a2ea2eef526c53c92d7a43100f0184195fe42e0636c0d9e7dcba9f69d905094cf23510a63003a30d874f7aa670566d89ecfadfd4622987d5e4ba5223828cbb662ec7c2fbc881f427c354261d028688879378dd18a404fd82784e63b983968f3a8af2d76ac10c66f8c8cf336bcb4292ae1bcf565d0ac5760f324bf0a78084515c034b32c8d71fa53f670d0cd479c43f16aabce9febb19edab9b60eed8de41cd5989a2ffbb5bbf1e32936f805e0e3845abeadb68c05ab6b8205c860b73f76572c45da3cccf247d89249ba60d3c72cde0ab3ff9ee1b025b3948adbc6e02179905eb6365be80ee3954ef3a69bdebee464ad0f1a49584dbc88c496ce703b18436259bee9eeba3596a13ffbdc222103788ab1d322749016a8879f4840f52841f018f87f09efa5dcecd0e34932d73fb92ba4b9580261d82c63eca0c56b0f1a6d25d01b70f661fc0f7d7ddc84f14d0addddb46d8cda3aaf5c2a47152135280cdc9bc45dcaccd52714e209f848bf84d1a43c37b8fdf9f834334f65e4ffd1f6f18bd3a0479d008bd179c9ef18c8baca0a2dd1e4d79d85858304dd3b61aa7d5a69b3910780cb355f8d01f33e13f4335a95a21a6729857bf1b2397d9e2ad270739354cf8c06a3137d6fb74b872ec25ed45534881cd5818e044ebf5b540210913abd90a0b29c28b69d1856f01e01eaea889ac3f67e058ea8d83d5395e3ca98a89da33eec58ef9ae016956c3c913a647632e87bc54810966d85442c0050325cc681b9122a2fc5b842860491fec9eb277a0dd3ad7958d2ecf91bca76bc0374bea8a1caef412040258e846f2ae86aae6edf2c96c7f6df29b9bbf1fe39249cc791304b4b8a886adcf68af96bf853c6df5a1c3d48289ea36041113896880178dcfaf39f10422de08a79af3272a95621dbe26231e5e5e9101d632291231d127fcc4481f6238640038bd525ba5dc0e18b57661569bee11712711cd0bc4a70394f55207e7464c7caad5888e680baffcb2810c7d1e95314e322b2dea7e024132565c788b293162ed4b586cf327b7c1f3684161d21186f0e3b87f160517884e9f2c9340a8f485ffef92129024c15cfc82226102e74560bd567fe2559b679526acf95f6b8a75a0601b81a91dc6382f59f912bb40211609d5d8cc201b177e7695dd2a8b5bc5fa7c19cd92c024107dce6a004d031f217d68d86fe4685c850abc2fd0f34a6b6bcc654317b0b125be78b863494ddbf6519b245f927a4673869f7213c1458717c6fda6b884862e74b6523de7af15634197513fa2b4706da848ab6bf8280de1fcf7dca7d7b127075bea38de190a33d7812ad6bb09f05dd500f75fa9a1d3eb76a31262f690d359d82e437d4b752c4c904872144c847f0c7cec02ceef18bbcffea4f7b79a966c6b566bd0d5278d48ba69a2e8e286422d8df8647a78755e743e06bbc19cec962033237b3134de52fbdb470be8c6f2de7fbcb0619fd679b4cffd9c6f830bf0f2fbe19fbedf0d8f9ce85187fe26d90b3af33de3c6b7d269c1a6b474088596834cedd819c29566442d925481809a5c6b4177062179634d4c22ae52cf659f28ce0b52d79ead2b242e74c2d7e4e30ed7839639a4222cd41b90e166592ca0659449e6a6634cdd459b24487757dd2c21c77f6df3ec3bf3ec2e2d387b0f9f7115e3e834e4b58fb3143e564a689acb3926e42062ad3d4a4e6b0a455dd81427059aeb8977ce9f16e3f1b51cca924d96924abacd385d081cc1435d724ac695957a0094ad3d1b2ab3ee3d0bace40117453a2998db18a357bca834bb394230b9b63192b760725a0399956ba348d452cd889640fe592d9a7794a91810db18c25bb831c109c4c539ac94ad6e92454a033859a6b0c2b5ad725d004d954b4540293ccf7109555739550b61be7fe4f4eba0919a84c53939ac3925675070a4137194d6c8e9558b333a880e69434a791acb24e17424750e3bc1aae00be0f86ccf0699a5264b349ac63c9ba5aa0074dcd764f39ded9f7248184fbc73575917b750977d619b7d44d6eaa8722a0a8ad3148bbdcdf5cafebdcab4bb8b3ceb8a56e7253dde1ca2a742c21ee650c6ecc1c6e9299b89629b83393887f64ade69b7fd83bc9e214912771c7ffc63f1eafe0086b5bc28f082e5ec50dff10d7e8bc8fdbfd092eb178074f585b126e05b0b615f7fe437ca3f33aeefb27e147a3e5a5f873c6c4fbb8ed5ff089897770cb7fe10743dee166c6ecd51490eddb390e4ace73c4fc1f6b8b8cf9827d49277cf18a93cdd959fde0010f925ded112bd606cf3044294d4948489649e5bb3486bd5ab11fa6877828302ed88ef0f3ea1012a9c89bf504ec3ac41e20554768931d9f4d430457f5ee328c897c4a044650281a3c60d1a0effd447f59460569a605c6fde96aa58ba462391e6281c55872fa8c1e5ea89deb10c3b21e8a41a09d52a1cb95c9bbc2b30fef0a0cde320669cee13566186bed2ec1385ac7208d6cdfebe27d0fee09dfa9e73bd6594a84a343f41b6369736d6285a7da9cb5c97bd1a77b56ddfeea0293658c6a7ab8a822d0d928237b5376fcb37fbec6101e2be50f2ce0dc4e9a390a62ec6793e8a3ddb5a52f6a04e5a24611d3526c86af495d23553a16564e4c47e0896825eff448b00616d7d47323d4cd928b92ca942754a2e3fa3e0025dc855e2cf29b51aba72f549d44c1f737bb9c2c5594a073c1673a9dc41777b3bea2e5b7e01f8cffc4dc2344dd65b5ee655a95a741f750c53c74b722e582528b03d3637f78f320a84c0eea6dfabc1b095776570d3546aa9f695138ee05aaac3ef8427320ea2d225e279fd407b68ba4e017de3b4957a877b1080c2f051a1c6ba5551048cb481dc419bc348900023f1a70d2ce4fdf84b8eb9df6510e60c22b079c4043120119812f49dc52926d915c2b2169114b646ed29e0609211eccead0076a416c002b2fa01770a7fa0615ac3aaa63748516f17f0adb09130a980a3838aeca18bc5dd77954a53e9a51be0a3df0aebace697fbfda8fe68b65ccbacddefdcf601da65de797beb5423e9a2f5d12d2757eed2c530fbcf9ffcc5b2bd4986cf0458d243576d4f8d11da57ddc9ae961c6872e70edd264ac65ecb3df0c2b8d308d6fbcb75b9155cc26b355893443a58b31a7f3a75599fb99e50db65579976c55da23a4afb6a85a6aab12098d1b62d2194d74b7b3a60b86c12a366798babdd9ea9756b42a9192eec3eeed06fbf0bdddcca042c50c0194e18332c69421a68c31baa3cfac4b57e1f06dfed9ec3ebf024ae424d10f61cf6bcd6a7f6cd17b20d2acf6a7525b43a544d622fdb5b224a5f740a42246943c8c2fd1bf50cefdbf507873ad94087bb652229c2dcad67befa3f7655f53de2532fb2db942352c3dfae3ca9268a88665139c9344951261fc9277c9d1e37fa1cfe10fbd4747f86d32fac2e13b9917dba39cfba29cfba2f09de0fc4993626c33d89add274a193fce0edd3fb22f2cef926cbd24fc7368ca6f72add03c4ac2e13bc9b92fc24119cb218cbf7e964393063d96b5a30c1acdbfd1bf59223bab2e9b4c5e5aa593ca3e4868565d299b31a234be343dc2bcd336627889a1274615e3bb6f6ee8cdf5fde6c669955aa14fe9055e3aea2cc16c86e52c4d01d304992e3bc4a0bd048004448aa040aed140fb73fbecca9f245bf993e623e54fba7d76c5f09dc4f06dd70a11a28ab7cfb7cfaed8c2dfbf13bf471eebf44be7579cc5940747acb2455569be4d6687f287def7dd3ebb3c9974abfc921e83eefaa4ee3d5ca64386b8cfac0bc98f85e0a77456892b4539ee1ebcb9f3786d4c4d95272b8d6294b31c83f2e806e247aad62659fb82b1f0537ce34cdaa4200d13001105036cd077809d04e80691d66b2608d513021d700446741b33ad0292259e4075d37ce145172a480117345b74d3a0a09b468b6e9a2c684e1047b5b30faa61d9048661f6fe1518be478ac49c556cce07fae8eba32fdc17b93efa8a2f843ffa8a1fcd9686b3f3a3af88ef7f1fcdbef4b42693f53f2b3f5ca9a4b53cca2c27bef199f7387bb1f7bf5dcad670ae4275f39344f8a32fffe86b562a44530023badba34cb24799cc479af4e768d21f2c148045cda4b14963b02b76982efa933dca247b9489962068a2e0497f729228d664927a3497feb119efe370a6b59fb53cdad9ec2988f112ae56a852a248672beb37f9615b338dc63e866996f28abd334cbdb791c6eced66675428769dad46422e2197f8c8eb0fe524518cb7af9428563b9b579542799486278d75391daed6c3980a7761ecda8c3f0fbdc7577ed867f6976492c7db673af384132350a17193fe5cfae596645bf4f466d86799edfdb2bdfdf43cfbc13e10663328619e857d36cb608fedfd037cf46fb5b7cf343f9616e67d1f8865f47eb0fc2118bb59fc38f9d10cfb8bfb0afb68fe6886658fd29e1863bee7655b675eb0712237be11e360ef8fc49cd50d4bdc8a0600432278234b1a5d9233b11fb8af3d941450a7a8544a7c94782a37292acf7e88c5ba4679f643eeee754a023c3729cc0727c0a287133a28f199bdd7c3014a7a9c086c7ad64b05783c691f61643c651713f9f06841135dfaa7ad364de41e22a0e32b25f169f38148691b1080b9e1e5026a1db580bcc7739336089e0d088874cb1b0e1675b4f083046614e008160d267cd122c7e647005f0837365b70c084b0e504590a5bba1b8709299070d1dd3925a460730c2055595c701b264ac0305aa5b78ba6b100cd966e1a22ba692ad0365e9eb0f1d28566886e1a217ed050a0bb939e38f9e40df67f73ec9344fff7f533dfc6cb0f9a0948404b10516880c8d29386668a8d07c8687ce377cbc9dbcdc603583acaa4cc03f3fe9267f1916e1b0ff858628d25ccb0b7bf36cb2a78b3ab70ae7f731a5fc2a73d4fcaf08d5dc2609b2e46d41061d3c547171edd3518982140960b588002361d38e24193a569f5d2257abf9f1fbe6dca58b6d5d2283163eae59957ad27417bffcaaadafa184bcf28ff0d53fe244182eeeed136493c914406922022891cba553e74f4721f7af272157695d2276f3e7fda6e9a1fba697ce8a6e981c70e56a620510211708056e5e5d1f11586f114a1cd87cdef217600432e3cd02aad34276f2e7f89cef8e89623061b24b2c81fd84356c59cd4d0028d378de7d96c4cba6964d348a16e9a8ea6a3194233e48e3b66ea98a923d2c0984b4bf3a4774b867e5a4d805a4b908a94b098a167ae68a1674a3053848117bef131b06383012246f4002f790a158d80626344ca8801747b9e04a2c99beb3d2a976455f23c095495b22dc22e4f0281718110ba3d49b34f4fb3c0025eb28d05ac74871ead66d95f1a14e463b3050536450c4002a12e79fe937830c5f773efb323f452eab1d9d2753b2be64be6d36832f648f7eb7ea8f32ca15099ff4ecf7e2aec2e7894e577954e11558e8e5feb39ee954a8cd155465c55c4553a523ac73b478a449dd0cbe5e4142982e9928d5d70becce7774790384dc63c2f85ca4f63b1304dfeac1ccb661da6c9d80a77f73fef6bb6ae4be938919cbcc5146c885341b912254e45c789b84d4a8755f865f4e6bfd9a7b9ccb79b95fd7579b514c85df06871b95a8bc32ae5833c5d94165152e7337bffa3defdafca5c65be5f6e89bbe091266319fcfad96796055bd1db6398df57cda75e71a6f826674c93d42b8d5641b779ba3068e9954b8f1dc3e2cc7b8fca8c3b9afce96832469331a79e57fad3d616ce49222966b2348bf58a81b8a6052e4d0071c40608158008a1fb87357e28e3872b9c855fe52c95b3f04c1126f460b32442f704faa186279512c5ae739bbd028e8b704e12e516ce0ee57e5617bec92b8f28ac064c0f45b8c70e363c84a184fba2595d93c6aa92cd606bd29f1a356c32571750c436991dca49a218b1e3ccfa5f52fce8ebfe0cf2585f56a98dde9c52c96b515e2d0dfa4993622871d7ca68fede869dfefc5969cc257e3a6325ced4c7424984729f955f9f954b998a27e6d743cfe3a4313086278da9306ad298e39b9c7bdcf288dd7966754c3df0b63c1e4df993946d912d9af68856e9a43fd916dd97df24dba21e3c39393aee13c3ed630a9a7b1f533cadbee7c99aac7da030df8af3dc3e671eac4489cfacebc4af16e623063ea414c9968c295048531540492fbb5628683e4ed65ef36ff25ff3ff8218bb80b0007145cfc66e21bec91a715f73046de8367b9c2fa34b5caa0a72f9b5b90b65337b7f26a3332afb0f4fcfd6bc3ace0e29d5bf62af90bd7f004ab4642f0dc2b6a85ef0677aa085a204ca0844402a105298a94112e166670e9b8f88951e423e5cf727bfc62ac015dc2cf2c4be9ea8c8128bb50a4960135e0943c77110c98300c346038a40e3072c68f01044b541b865279834e90493725fe494e86e3bc1242c617682303b416c27886d722f2cdd17d809da092675c4b15902c35953fed03c66a15736d9d780b08cfd9d5958aef6ce6095c232f8d1d8a4b10a56f0b35d9a15fcfc6110c39e36c12a39992bbdd50bc696a82761dfbf0d276b19276b394676565d33a3991146ba5608e36ae9fd59959e26f1cce893447866942b7efc1403fd3dc232cba099d1acd449f8836b5836c1ffa13cb2b1d6edd259b541322664ab980bbafb15b127976e577aad385f56d0339551efcb2df1886f3878cabf1ff83689cb5582367806eddfe82be2c7587e61ac52da0eb1cbc91b8bd5e1ce67f3bd866593297f3c0f149af2c706d9972daa4a3c623550f090000f20a850d12828b589ca2b7cdb6781800089b406fa570b622c73ef3b3a0e8379cc347604c9ec2f52473149c1d2f8c6088038df558332e078d5ba4d37a0d1dd9efde48dc562b16ef0a1bbc3b7b13e4b0f577973f7096e40e1da283ef8f4ec105170ba6f368946abfc759e1c0cd66082e2a5bb3114af4d5088f4943f4d4f74e81e8a0003e2928d659ccfa7343dd8bca691297de9e6a1bb7fd4ecd0d77e4a44aaa5d8a3b61b58270cf6d59a5e4768495ce01b33786b05212347072963420729734337120bdd311060862071e4749038987490388ae8207184d141e2f0410721a342072103d441c8c43a0899b08390e1d241c8c0e0841d4e50e60520657c20c34577f78905091c7577cfac208709a94b371bdd3c4052f57c7af38f0d5658ca03e887470f8f1d1e383c5ae0d1c3061e4348613ad28126073caa697c230e903146471adfd83e42cf65cd50778d931a665e5034c50d13d11bbdfa51d3240662d8877fe65f7bef1213518d690807d350adbbdb5947aaeebd9965ac1e7a354c3f21b92664e66bf8a1060538eb6f569641eb2a9f954aba8469f78c0b78504dbc709171d5c01586fdfbb96a7ecfa4a08689104f7525b9869ce50ad2a48d2667b8ca95a8f09b9a2ca0490e98fafd0f66330ccbfbb6080826466c214573400b031020f4c5fd25a3d09b42551a87358a889f1e59729b91c85064f57364c97d566e6429dab01cf258adcf8f859eb13caa2e5bf4483533f0aed29323a69878a2e8eec7e050d1ec322c18acc8c89584b4e4098cb56488204c3c4033409a0ed30c3998173cfa015b70b48ed841d2d5b06cd2b9aa4a243afe8519e59df4bafa1bcd4f73339b42c5ffc20cebaecd3229d4d1e80cacbdbceb7c663d3a69d30c5cba632a7c1bcac39cbca96cf689abd2ac4a33ef936c110c06833d71721f6662195d0a7ef56f13c66306043128306104129c2146890148e8ee26a10914ec8c5087aaf66303b2813811471620345657c1e5fc280304c48d12ccd8c93b2fdb4ece4e096d4aa2834e12268915d9da0bbbb71b976e8cdf839992f81026c9dda624af243da624deddb0ff599210baedac5258994ea3c3f49b9064e179d24b7723d9d2f829121dbcee4672d4b09acc7e4876ba1b89b70c74846f83c9904577c3bc744faaa5730d93618a0c418d6132a01acb4042f791326d3a32e648982360ba1b049a235b1a85361df1da6d9ef2a77b660440aebdd787139311312623b51e130c5a4c4654353324311999b2d35d93a4bb064977cd901a196a33b12504a2bbe6484e4ebebf54adecfb3a10ae9874dec03762d8fd0f859a15e8fc2fe4e972f2f682f7782a276f2fe0584179f4658ee356ba2f3362ac718236e56409ab94535a2ff43c82d276d718e9ae2952b30202681c60abcd1a0659acf91567f4d3167552d65da3d35d9353b3020d269c283d2606b18c68648a44daffb332ec1566938f930f85860634ab15cc8c11868a0a26a0d04256c159a6339f71c244a4892e6284fa1a3338009165a6015f835313bbbbeb98d4c033dffb98f45a33249850748861425d81da407763eaff85d883504add5d429b502d136ac7934b30130a05d3cd0f6ec62ce9ae5975d7a84c37b91a22606682689a134c336e74175d9051230210244c3144c9d7621a23189248a1ba6b8644dbb5b3b6810986ee5ead5635a91d7f9cab5ae1948f849348dd3528a1992a6880e8aeb999b688c55a329b19d51f2c6b4add9dff8ec3e057de7cfe632c7576925c480db1751454d44262626ac1089bc9c3e8d6c00c2a7423a04d0ed44d5cb7cfaef7645509bb8432b64df21d3c985800405b9af464ba323d2a6a12667a41138da9b1a4cdb4615ac1d5b44a6158d2ee2f59cfb348b95ef08756a9490535babb67f4e8a9a4d1bfbfa4bae04fbc602c85fa2b3f7c14b1ff1205f151b4c9646c7efebee5319625cecfb628d779a3fe5ece8f3178c516cdb7d9a5478a8149f395804ca09da5d32a55455a7c57e56e3949e431e7bf791ae1b8acc7209757e93d8d4fef7fab188823c6a0f739ad3732398b81385219c5f247ca6287531e9555d0ca3c59cb56455b1eebcdcce538532131100bf186c082863b31b299e66c8b5ec816853619cd493130e85a21a4a934b11dca180c9a8fb3b32e71e52451adff592d8a14799c55aa92bcae73f993e2ce31add2d9cc76dd0d0a70f7f1e9915906b9ca553e3d3d375a4bfa5c89bb7b029cc5727f8f7e372bb3b5eb3ac7995610574b1f29cc7452d00afd25eab5be7f27b44ae50761dba6005c117f263118fbd05501f0e96e619bcc0eb9db6432cb20a41e21f868757748e7675b27fda9c940fba4522793fef43099806820aec29bdd6b62e8ae81a1bb7fd4bcd05de342778d90a7f73e36fd48c3f4434c37f1e79c248aa177a9b4510ff765fb6149636e3d70da5b4d8637e36237a31eca6aff71e7f829feeb441eefcd4c1db9ab1ce5d1654d76c14fc96375cd9f7549c69e87e9129df2e6821936c4d4800e1edd261e6faab181a9264b4fe08c3156b0032d32d0404031470a4c8ce916c8d1ad563830d8473ffaaaff199966b484d79a663ad34c4e77e70fdd3523c2aca09ddd6861f3e7fcd8ed592cd887733636ab343592a96d30750ddd5d73c95af2a62680a97fd8dc5183ee2cbfbf77c880ba03053bcab4b3e67dcb627df8654a76987ae98232d0ae6677a9d2d5ec2e09213da961d94408e9c9acffe5c79e2761303a96ba8215fc8109dd1736a78c81ffb0fc55603110c3e694b1fa3110c33205adc84147cfc7a00b06ebaef1ee1a166a56e8eea0dab291e34877c7db67174c8e0c0bc118f63c09837d36db94a1dddd3d58404607dd99e2ccd01ca9834c945e7a2f16a320ac200e3a6ce2e002840d1c62e048011c5c781873ef32818e0247149b37c6d8bce145775fa11856c359c3d94d33821ace6e9a1674370f1a30fab3d9bede4801cba1ee4ed9b80fbc9ca8b3cad1dbea671247a08bb638d2dd5fa957a91b5984a159018f14ca0d2c1550c2c68d26ce7a18cc0d1368957ef1af6ca34c1b61b8bd6d08c10adf066b0347168bd5060bed4a3c7c1becb3d9bfef41998edf835c0fbebbdb59e13bc9580ed9d7910dcb97cd608b8d31366c4051f36a69fda1f09de424910f8eecbb2b499756ab95151c9c9b95457a85de98cc15276b38e270382124331c41e22c6751e956ce7a59d491c103a5c47469cad5fd259b3d59ff734ffeede6674f36e95c7a3e458e20d1d1c1e97156b543808e907c88bf52e851ea819fe5ac9f69ec7e92a8a893429318fc2bab367cdb12a2169290db5cde689cefd8560cda5a845d46b3bc59f7d9cd6db6f869357c2753fe446c33fea84ccac775df0d8cb9ca2679ccf5b1771fcdb8eb3ae74911711e1fe244fc7f0643b912ff6886793ca98f6694cc93329812cfef05b2e658d65a37a875772833fe25ba74418cbb01aa5b0d31dd1dd5006352234babd175c720d74b8d02d8000536f8bae36ae63db66e031ad22813d3e09246678bd2d8498384ee1abca03b66fc93c57a4d178bf59a140d2bd028020d20344ae8e28c069cd1a4fb263fe31be954761ec6fe2f36430733aa98d113432fa9a8e8f3ecbd32ae28434b370dc6a08107688044031766d0c50c8e98419319b4c860838c1b9021c4adb34a79a3cd64182143003220a33b7615c459cf6bc94028063fe81203a518dcc480070cbec0e00a0ca674c72e7f52b53486de4dd6ce57f1bd4bd5f762fc9bac281fe244fc314b7ae0edda99981b88d932eb1864c6d0c018750c14b410c38a5183182fbc00062ff0d21df14f1cc69818861017d8e18211b800e602a216906941981674a005575ad0d3dd75b250de640dd358579562e881a10318303062981c84f1120601617aba3bce2cadd24a6fb95222167cc10213b0a0b6c3821f2f1bab5bb522be76b602225680c30a4e5f50f1c5952fba2f72ba6705595dbd600c96a5175478a14377ec028e2e9ee822d645933854a0c4ec3a0f63f86bf95a9f5915ac5240c7ac52958214c890821e5c8ce1820a131755b828c0166d6c91832db66c81650b225bc8ce579d12c79dafba148e2b8f187f55e2b2ce572ce944fc73222e9335cf37e9995679f31bce411b82b3cffb9acb0367df85f86bd9ad2c625a05673eb3d251d04201102da2d0226b21d3c294451d597c208b2759d49c004c77c7d0fb259cc398bd559c8dff379bbf0fcf5c33eb5dbe16e75d8797c47ce5b7ba3f3b410a587081851558502c6cae40e38a1eae306245195680b1225b81002b7e7477e4e1f9190dbd5ca3516f5639b35765894c5001135013d06002ef8eddfc2bdf9385b55502232588a18a31aa6041153854714345185460a142670a1a4ce1c4143e4c219b42680aefee88333ff7a95ac6d94b7bcf6619b4c4abc4602dbfac93996669bd18abc39fcc94ba32066f61959daf705f3b5fddce579dd792628a9b36492184149f143048f123768f71dff92a1a01918008cc1d603e00468299218a39a24041146114a88eab0378c6f6f318fcef91f3ac32b647cec23f2d51f6308d753ff826bcd91bfbf953fe6cfd60896716090a16406105144140f103858f66c58ece269db4f3afe5fc18cb59c10bce1cd3a0fb572abd557a9b128669e8d303c3f46fb2ee80d893372afbe9c957adef7da55e75cd87fd05bf6b9f10d3fd04982770e82cc312f704932748e858cbb8af2d27c2b49ce812e67c4ee951aec526277e447c932b6d89c0daacf23c1164114127829d10a4417b8f2ed12b65f83d09e4c362bd90e8809e040a4100be60f1654b47fcc5a6892f9ac02156fb8519d6ddafb6891540c00508bc74c77ac1f94ca0c104154cd4989031410213ed65092f4a5e927c208d0f68f9c0079078604cab5498e68f41f198315805b4a177f706b4a12b794c752c5606bbbf494c5561eebdd92f61fbb5fb3356c37f672ebde2e497e481214b9cb1c40596b86189205dbce8027639d279f2afdb7a1fcb9faef3997d7545d1debff2d11aae76fe2cfe0533385bc90e88a0036007541c0883035dbafb63b055942914be3435511c686103606c4089fe9f3ea361ac2a2951a6af28d17a43031dd000150d0809230908242193084006ecc88017549b32a083acfd05bfdb79540ae5bef355adf3d52ba793208dc66e12d3e9f3bddbf92ae20b7e5d18f358e990213163ebd9d69021ce6252e42a426a418917fce487b9c4c1e5082e52b81c718981cb09486cd11d59ab18e66432f749ceea609dafa2c73a8a298cc2b1eb280da5f7352c6b9efc6b67d585c40e1226f4116558dd1161aad1115f3e893b02cb6c1d71a46593ca28988401336618d0a11d031d064830c2099311b9eeba6b8196c4dc1bb113bb9fbf7cc2b7791e8ddd3f9a5616be932348409f77d223fb271790e2025cbabb94ce055edf4dae7fabf6d2a5d8ed316ec51e8a05e6b0c0062cd000a13659a0c723b6b30815148183a9889dee8ea197a3e1db6220063d0f193264487c1bdeb2c496275b86b6f46c4161cb08dd6d439b88a882082b2622a274f4bc1bde0cc6727d1abb1410caa873d94f2bd3f1554cfd12c595c5b214f6e10ee55d2a49c7575d95b2cebba4eea795012539110742194d2b23e2c7ac550133ba3b7a1e755281fc49157040a6b7bf6f93444b7c5e8f65edc3f8de252cd62b8336acc13e9a8730a3bb7b882fdd1d6d6b88af3b0e41430f4142377e1a0dc42d21c07816220bceaca09d499c10b58f662148c0996fa280151d3f9a294081ee6e1f97ab6a58d62630c50488304de0278044026848c0879b1b9f59a44abbae8bf597b494400b125a685a9e680940104f04d10501640a0208103e00c20820baee9066b686e4eeac95cf6c87e4441ce9a379fe95ade21092d26a6725f45272e93c1fcd3e5d75edcc57b1be4d667c63abf3e0befadf2c931f3cf003961f8e7ca0c387287ce0a28afe61eb01f52066043d2ca0075b0f4d786883071af000040f3c3cf0d8e14b775d90afdcbb944e902ba6ba6c6b18fb68065d915669b7fa68f66b69961964b9c294e5872c48225046048488404f0472202006021a80c01104801ea0830708f180dc035ed0a14c77f833bd506241beeab08a85a77ca45890af62a7b37a75917a38d3eea3af2af35c255375afce67b6eb522f9451e7acd057b2ced67095f6368a3ffad12057d77d20ad6159eb7c15f363ef64a5511ca40314930e26981c40c6015238408b036c1dbb178bf5ea3c097aec5e9d7bec96c48c1fc4b8c562bd3a49143f9a5755d6b06ce283fa68f6ac8df9e0994f0f6c7ab6015da84c9132c5a3cdda56038e7832c5612a53eedb09622953fc6baf06c4d0dd3e2c960fcdb1f4574f4fcefe789e9455068460c262069621b034e978bd550c6dfe5aced622e9b46901255840071640846901411d73727254393a4ec493b2cea1f82aa6507eb3b7eb72c0410e3030e570450e52e4f0c59483027208731082d3e32efc04f957235f455fc5940ecac8573e3dee4284c1c2b7d909a63adcd70ee5b996c1d674bd600ad0a2805a6d29c08454f6280de513871e74475c0b073034f6c2414b96d3438943cd16e1e009a0a39b4902b848008d558ab39e5b9b31f5982901347cb5a62b7774479f57cfcca88811f04a92e98a90e98a4d38453833852818e47ac8f5e44ce88ea99cbca1a848c12b9a8b5da7137f62597339fe3ac7d9a3b43a3df4268df14c1aabd4840b010e02381bae8437565de7333be94a4ed312104b499608005e017a31af62f628cd56e9ade1eca53da67e671ec154764186c274f304bace57b1abf4365b5d5ccdacdb1a8e564bf3f568ae14b472ff684c0b6fe9ee9609d3206bcb8483442aafcdb92ff2f161b15e3d336b7b72f6a767d6b258af6b80394c564670adcc4c565c307d68983e2e114b5a938138d5a13c530fbd6ae3f43c8aa357be4f2bb321a6379c307f55be124cd60a4b3bcada376d2dff4d16258dcebaae5aff9877e473bf47f64f303662b1eeb5f72e995557c6f6a392536452296224f7b39aea923beea0a9b2a68a88dfb5428d89c796377c10be0dd642cd1d4a4b92d400d15df3c38f0661e8dafbd3d6f8d05dd3c30e5e6c65ba25ee8b90b051f1c4649bb1d5335376305549e3892a5d5a70531559054a15a52a475568f09945b9a9ca0f13f582461163a62dd8ea59fbd5d430d558505b8195dfb54238578a69f227633a24677d5a6b6684636052f8d1179e19e17fa1fa776674ff0393aafcae15c26096d70ad95975cd02b10afacc118fe9e98158e2686de12fddd486bb8c2f99abc4fd3aaa4229e912c694ca149594291e5b7707532ff2fbc38a98eef82d2b56b0745bc9e9c6d40a09dd31ffed6693be32f10b135ddf97eef881b64c77fc9bc5368b68bb3e591ababbadc7a46a46fd124ff5ab4e6e6522a6556a8b3730b1bb69e9be4589d35d372f4c4789ab96b63c2f318fc86687ad4b77bc56565b29980a63d6b6d45502cdfe7e458c803e3e3e9e47633e3d30d80dacf4a32fd4d0a8fd5073d57e9c685bd0be8ef16fde7558ba2a762c56a5d403679947e2c752f68a5e77adc7229176518765eb32066747713c6fa6c40cca2c8607e3c33732679065c6a60859c812203b8a5385bdcffee73659c436c7f94b3c761e3f36b33e640828cbc9592d0c56e9adf321d1b152ec8cd817f914c786ba5b4e40da709249240c9d19dd0aba06743bf16b39e71a8de29ae4a924949187d54eb759e2d0c3f8c30a23a33baa42ef334c0758d7ed081083001f2a6c5031e244c501548ca880709a42c51423a604650c4ec7d2f3d31e3683c1baff507edfa11c5bdc4fd0dfdacb5791a8eb62a68ee3ccfa077a0dfbeb24650a293e48497280340e20e60058ba63175699aa602765561b4351e966b5b14e8ad2077673b66e98e30631372871bac1deb0a372168eaba1d410ca08e9069b28248852eb686b38e950ea800206142f1eb5b9cd2ea3352cbd2416eb0514fd890e9ec8e0c9104f983c01a234859255a2e1c5c6ab03afdaeb3f2976b453d54fcd28956a531dea97ecad73cf935ffd512c963c253520099624c209490d24213a7bb6aac0d47cd9b5286fb9bc6fc318365c4002363cc08604d87073b281c7f48c705fa472564a07657474e5c87674c4480d232e464418796d8b7c151fcb5c88a1f799568afb7a8b70d10aa7a21188a42092111960684888a125a7a11f273f383961c3c91627312721505183d7ddf1710d37a337cfb6e2fb9fe36a7f2af5d5575bc39e9337efbce54358d7c80e61e5bfe1cef3680dbfadf2ab0d61d662f9d5d0f1da2c9333294b723dd11d5d374dd4e86e277d6a72812651dcfef82aa670b514e52f17916b88c56a6599cc52962a42659abb89f86d762d9838c164034c8c60626322c4440421129c847c6867392b7c9b1773b5d22848bbf26775f92aa630c558a23cf452ab47422d096249cf92108272109446d0114114089205bd10040290088066402afc74f19325623b31c5adee7ff26b47ee3185f2bfd253b44a510e942a4219396bbaaf52a8228c3262dde8cb5771465d55a58ca7d6c9fbdd953815be56deff501efd985a55b47e68b14e34a841030db918daecf8bb1139cbd62b71ab866596f7bd9f451a086080384e06f0a2bb23954e26c56c2dd24feaf2a1f2be8f8f8f0fae96e2f91f88e55d5229cc47f64f7c70765298cfbc4aca9c947451f275b712a39e32dded3d61bae74bf76cf57c7dffb6ea53cf4e0f093c5cdc2ccf96ce8fa94cc9c99b94293c94a784ee8ee04d4d965bac2d5897d259a507b91cdfbc268db1765077b6582674f70c635a3370d19156e90c5c66f8669801b553a63b565a74dad9d9a911edd0b0e3dd3149991693248916ac94e48bf3948486a64bd5d6561215e2949f93b78f666cbf8be408121264104386214e3230408624477870440547b074a78c3e85a967bb1667248d93112f46964e466a30c2a373f29693b7979111124ca7969344119f8ad08a149d8ac4a0b3860e17295fcd54ad3772aa38e53020474999138e08706e3845701cbf07ded96a76dfca5ac6740ac524e203a2778a1f495849715a6d49e95cd00ae1292deef5d57ab5b39bf29b74b56c631e58eb4995864a081592eae644040d22491099272241447a9c8684b1be9ad56dc67578d29faec3f6fb4963d77a0e7f2ebd4306302675c40e29a0532ac829d5a82650b7136aa85340734a2020a0506256f7f91483173160200629a7184e80410630100183100c51062f2c717a819e5e18800b57b8705d4841081742721d23b61983acbf85a00d6bb8d847f387b3074e6ab3ad97d230665b48727239dc8c938fe15abc8a13b90a2cb880852a58c8e1c402664185130b21ac208315905881ae50e4a44299930a635410e2a482014e2af038a510c529052c93c67c858d7c15696c0524023911ff4101d9225fbd8a948c806041ae1f589416d18ce2e0dc6f347f96651464f44279ea076584fb4a8432024afda08cdcf3242b0534692b56c1ce278d5589420e28c8503889713ac18b13ae9c4e507242905301d028c00f0590a7020831010c139030a1869309dda99f1f6cc46d264aa11ca3dc71a655fa9eb5316ff9cabbb04a15add2e92aa992b44a63b5140fa088d3001230801f804e10344e415e700ad24310a5ee1ea14f41989c4a80a3041594a0815309b8127c4e25fc3865713a8de0a4c3c97512c134839309042713ccc46363039b246c8a4e36454e2490712241cb4fac208dfdf8ca5771e20c5a8bcb11488a28d6f26330dbef6b407c15511e4595c293c67eae5d79b77258b514c368d2d2669593c66a93c662fe389aabfedad9941d15c0152701e4208029027861041d9c465062040498fa34c2d0083f4e22e440042544304284a153087474a7a238113f72224e44b39e1371229451ea0865e4310acac8a3e749998ee7c9d4518ac869d6431911a18c5247ae4a619413392b7c2711678ac336e34c6f3e3db3525b085d8a42c062429f42e86208a8b645a7008411becd6f96a8f74e70005800401a27004cd1dd51fa0400d909003e27206b9c8050e004c4e7042484c6ff85299d94a7308892d19b20975f2b3f96547f84a71f379c7ee89c4088e3044218107c388110e50402cac719271f5dba5993fae79dabba99d1745d8bc293c2c2b7b158387c2791669b6387c3b775298c0adf96947b1f47f9b1e7a1ea635b3bf9e858ebd4434c0bf34f3db8749f7ae45ee8538f26ddddaf530f7ae251a61b9f787ce9eea6a0bde1c4034b6ec9fec9a9a6cca9a689534ded54f34204fa19cdf2963fe9447381eeb6a14f34359c66e838cdbc4089cef2874439eb0f8919bcd663759d0f8956362476b74e9f664e38351ba7fec0a96ba7ee4ebdc21d34b8838a076de8aa2cfbfcd3edcb3d8a551f9db922ce14cb1a823f77cc3b64b0238eeeaee0fd3c7365f9df5f3bc0d881a5630ae51fe8f3df8e9d2057cb8e3ac2d4d1a58e1a4e1d293a78e8a8f9c11a3fc8c00ff00f52c8f5e947657f577f319da3ce3134470b73f890e38b1c5de488d2bdaab64a582d0b95b1658a9489a1cc0f3260c848323b644088e38b3872dd9d42a95878e56e957c1559dd6757b63aabc37d6af51705cbd622dd4c2f18bb51ef6b705c01c703e0288203d5ddb9cbfdacd2dfc06f746fbcdee87963006f88e0031df860091fbcdca8c30d319dc28e33e523b98eff04f92a56231df0f619676bebff0ab9c1a5adc56eb11b51badd50a10d32ba636afe12f5da48a20d0474c7d412f550f331283fcf37f5faf8b8aa86650f8ae881941eacc00619dd5fcbcec6b371840767f0c0071eecece08c1d04b183243a2843075a3a666b6f8d783aee2b6c66fcb10ef7b5bee761a69fe57de5e4e4e8d895ecda9f495ca3d1da4b9b5d95a53f6d4a9a7f2d873e3d19d3a1d0a7681665099b486428d2440724748fe99883ae65ca010362a5d6e69bbdb31cece4e0c71a62566b6869adf175afd1e4263fdfd648a12785753878a34b9cbfd461896be1e02885c201ea961913a67b4c97a431b93eeaee380635e6479c4937b0a23baf79a8e413d7fba0756a4600002028931000003020140bc62332b16c3ee9fd14800162b856b05ea1cae33cc821648c21c400000000000000461b004c9b09795e5624098c79f82d8f62a2db4da8cb9481beb753c7d4fbcc0011a86a0442ea728843db6e9ea28405b06369186c3adf84313ee780fc84c70dd954eafb8e16a8e4389d4f1da75f9f31a087b47002345f0c1bbfd168222388e873103723ea63ca1e609a996a569696e0a3f60d3a08c7bed33fe313dbb6cab83436bbd4b8b9952d20984220b5d43340e31c519134d91a32b30f0eb627444280abb4106955136c0a1596c7216f454370f684804fcbee4ac7a7682ffe0d3458cc1405c2943dbd27b3325f10f50d2f5aaea27f2d3b72e4adc5bd847ac9a6dfdc7c27041afe45d39257f942171e1a335331236d26797a7863c63e830af8a3e51cb410fa0ac629fd6f4a50cbcc48f437a1c08bf086cf3f58163f9cb40e3945e635adbc8350628b3b7b34966a4b2e01bf64ec1854e4a8e051e4bfe512fcdcced905a14b317ac6a40f5b2cbc00f80c190bd944b6bb630ba7f03118c8a4273cbd22cc8f9e6087bddb30ebf7a6b6ffc12d590f83dab58de83b9a3782789ca57a4e0fcc97bf2ede6fe54c018879a7dbac20827cd0cd8d97e935d4be20f409bf7581c2daa8bb29a1107bf20d7880db2d0f844e075c715f4eb36a621ebe4123aa00c065e400bfb096963198a776e239c4eb1efe551a5c82b0586a3f708b58a917a50281ed99a5f7e1e5e2939348ff6fafd01272f01f80fb315c175e3f219e0aa4f88ee3094e8e263e282c9b577ecb4e8a2d9d0496310523ed58a01061e5acee370cf5a07ae2c32029e430d5fa0cccd63b5089984902d232b511ff0eaf841fa90869876aab058e62c0870d821609caac5541eabbb8e42da37086971958920080bab497b46cb2b4b68a49260ff7573c5c9d89804227f2851aecad918ffea473d6ccc8985ab2d3797b4b868f9e5d6af03906a6027d77b8d71126b606831de9d2036ef4ddb4d2f4ffb2f3f5b5226bc027741a26d284f4251ada6b49e69f9378b523f9c12ad2b5288de3218940d0866c2fdf16cc6fae6b6c14deb20bab4735188e10b5dd66fba11123e901c016571bc6568980c9d96d06a1d3181a3a6ccff54eb129946118158fd231f2becaa3123e968f6fdf47ba0de7d2268d9c0903346c1cfa6df49ba212a7a520e919f5ba354cdf68a17b40caccfa6e7e030248d47b152b4c69a91594906cfc4e562844088dc11cdc1e242e82275d6da4b42adad609c6b36451a54abf7dcaf966c0c7a723f476e66e78f61e497f9bc700caf7dba1933ad0f604b6da4287284346d1e129fabafdbdd33bbb7a329a50bd8a029ca7472185f82b83e1e5e16d4a754b7fa3b3ce5f9761c0c7563518d204319a113d57f10c8c30d5714727695f60e7b8e66bf57a61b0b7b0f366c1791d5fbf3ee4fff9cb26c000aff73799c37f20ff7f192b7afd4edf03f814d57e358a5c53422421669ca69ce5550840b8475b1044c1d1c204a61b61af3ea82cf487e90bb4684b045599e9afb1bd551b2f6f1571255d2bea727d2cdeb2bc96045e717d3c866a2afe57b57e43f018e35c37a231220d8c2e3b74a2fd2bae8cc5cdf5f295a3ed21bbdb415af746739cd5a5afc47932751450118c85348455d352f8d61138536c4f6eac0829a3d7c85ae1dce77e8477a2180176e6dff21007ec543ba808a8ee9014fcc8953ae3228ded0297b856cb8babf4bccbcc238e5646c06c88061f154331648b9e51a5db10e04805b189393d592bf228a6764f3fc123a1f6bcb6a5c7a0b6aa078491fd3a65b78381de1687be99254ec0937347f9568980e70652b0fef6901549db3442fda73af177c3e07d5a6beee297f16a4fd0f942a58447b84fd2f8c1d8f721c126c6815bc626cb88eb2d44e92f2cb828aeb9b5f82120e6f7c5ac12ca49461ae18b69806e75305cddd443ebc832826b65fcbbc911366466fd5b8621399cadf3f5e59c04d0bb89475b7ef43ff753e0867516c47ccd4881d1921591c9c102a82371eec375745867e51916fa99dc2ef666257c8d665e05d9098a59788388babe2adf32103ec6661d728f40c7aecc52defd20a2a640107d9963c0abfe1497f9ddebf50b0fb74a79d9f683d5b094760ca50883e0b9f6ac473e7b17535ffd1b6f0e1991841f89a8253278c4953d68994ffff131bc4d17f5f3db01e74df94e0bbb7a21b7821b5d8ab51ce288be1a8cb9a6b6943d252bdc6535a151e6693030f5faecccc008e18042b78f14b4b082db325c21f58a5c85e18248ea611059641e32f41414727278122948112a198d05d14091e53d46074dc24c7ff3b3e3ef8246c8067eb55505655ce1ed4c27ba980a17d52fb6f41352802a9bb3c69248706bbc8f96cc7b5fa03c1686a9b24f82ca13a9491cb727baa70f123f5fe141e90cedd2b2e3c112cf6c86699d24c829ab3ccbe3a4d93b891e8bb57e3dab70076e508882aba4b2e0a0235922f89926e5a59bd352b65343e8c3e2682fab57b36cc73b18141ad8d69ac1149820456831eabc3dd96f787b163b4c5c1e7c5c4e4219c14760a74eddce611a3a7be33fd005236b1242c368becff57eaee707e31740bd999bc1afeabf6508294894d57b3eb7aac9f5222ab1b1b314731cbc2b38a914c266d9ac507850da4109eae721ae79ee1b2c222c20306ab08e04d7af322ee1382f3520da815a522ba98b7a3b82eb643363bfbbb2d4492da5980923f37153be92be0074007448856a67883d0044b52c135dd7ec6804f2777ab7eb848773adafab7f2eb8b143ea30cc60c772ed3d6c97f38ac89cecf23fc41b975080550e7b8bcfb0937643968804a77d67a1757e6d004bf122554b31cfae2e3e7afcd5226e5487be980deeb679523c47f5b715ff96c69ec96f56c5cf357b06ba36c2afc30df38c1f57417ec03ed40913d6c490e4cc3f70e21b20913007c888aa63a7b39bd732ca1be9c8482b151b7c2ccd78c1db6c7483d6c10f7bff1dfe091db70b4b1903695e0f56dcbadf44e072d3dd6262732ab6d0f75264f3d6c7e05533f3988b9599cbe3c4638ff120ee6d0859ad23d1295689043680d7892691fd6444ccea62717e8e11849571c493b2dab75e7b62c3cab656430ec5b6bdbc1363b7a5cefa14ce5a4abc73a2ba451b333158e906ce1129bd9bcdd1e5b524a8d35d43c6f44c633564bd7a2ec710cbe008e89f9baaf3cdd27b83306d68ff1f85598f4139212a1daf97a94d0c12b4d0a4fdfffdcb86cb3a4dfdc78911fdf2d661057422739c62781c2d0985d66a70e40df538c2566ad29af2c75c90aab6aa4f1b2244e2bd1ac5f9f17f857f34fa692e8a921e8bd5762c74db758e23555ffcd8c17acb49e3f6fa41d3ed2bc42b8b2711fc59d0f4b787416f30f18b05db9edd3faad4db22f21e400818c6ac4ff389c9acbf67242b0da4652ac9968d101d96e2a158fcfb819c7d6a0a44607448150a16d1b5a7c3d4f53b09f2174d7f1978512d3f7b1bbb8d3d73c28d627108b76cc98403cf6e52374c0b976c54001eb770d821c59586c4c3829a9bdc765c410316401df09282195f8e2cfa066a3bd912201e8423121ce11b38fcd9853d39991269b63d2d0920cbc629603b68639c8175808f3244162b5fbce135e046104264f16a336f290bf9b339e6ea99f0e9176d0c3eeb11635cab78bb81d04db82ccf3ceef2fc97ddefb3b8ea30bcff557f003c8a464687bac1ed9e4d251cdea62dfd71a2151bca4a6bad751afd2e36fb60d4f8f69409acb329b5e58a8418242130bcc76cd4ef2eca071dd23d1c053a50a6594057e1567f4ada5be2ada1f1e206c39f7b7f80402e3da83c594d87ccce9f0fb74a39f900aaf5d8c2db75db186d4a3f6a4394f80193d7139710b50a59bf463d213963afcdb5058605e454f6891ba1d6c6a3e1d1046b47b83537a0c44f3405e81cada1bd7dace161a3ed676abcfc717ce3ea444b70c194899ccc8d3854d2d2f9445f1b2d4b0aa98da0b0c6236c8a88d1e7b0d38222057aa45e223f3927064629209da4cea3e7c5c1478817588b20202c5d1bed513a0af75aab15e24d40c8ade1cc8c066517a460d5c65d137eedfca6f9a86449a200ec12cd4ca441a724e24eed47e21c414093faae6a9c8b6ad50eb7f297d646637a810cf7f92c0d3f2cf7e8a0df0b1aa7bf03aed13bae12fe6a58bf1947ece4c780918b3b7a926125b1610b56ffa57dca835baf1b3f54c82f33aebdb9f1f0c9840c83e52b87304bda75842a3f11007bef835e8c4fa3358910e54d7c1208fc2c750bdad7e36d0bb3d2a8d60a5defedfee6fa28ccf95d430085705e03f890b5b2aa871387b1e1ec19123a6fdb1e0d3c319625fb7829b804d957a0fbb5f7f926de6ac6ba994a8e857e5e8cadc3885a4a0f57d32e040511fc3dbd86a160765cc251aa9188b267a5225cf23b20ab391cf41a126d70b0867016a3122e3f971308aa40eb913be0750d3522f03e6f0642f503202a4706f973392abeb9f161b5da47a2d82eb47ba57f4110ff5fe78366feeec71fc9fb21195d8ff39228cf55f4c0c2fafc778e1ed5e6151ca79d71ef52ae0a87f77015615658d9f4915d35a5f28d2778bf43ced6c6415981bdaae586ad182137c92b1b65ddfc9cc0ca2fcce039fa81e6e5c9adaa7dee49f4996d696204042871355126cf3d6c6cbda28c33d91fc311d9b870b6ebacca7a96d6d9467e0d4b366a03c535c1b76c11418917969af3d5040807b4588f90868a4b018feeaf743cccf0e72001013396fd42102340639c8353a9381b9a2797224dc589ab87e6d184386814651341e8aa616742e8972eb20194156d2c6a0001100648761083c26fd712a3c8e0a9167a70c6b8da4bb00f1b7ec03d352d2a3ef1399cd5cf3e9d24f970f773aab39edd1e9f6f572203d272b2a361977c81856a0291e106a298399c6b6c342f1d661690a3fd7a4bb6d46c180a99ef183c254330c2e1b0398110e7f797ff868dc323c632afd0924f195c86965285315e907e92c7603535db1ef2a7ebbe6b31266df92478a7d9547246af57067757511f6340dc8e2819414add8970566ef7b231e9a860fece979579836775f3f27af5b0a106eed61c579fe37cf4e40fe09e5d1b3b9dc9dd8a344b3baf93b51b6622b4b94309f6710c0fa7a756a2aa4d90102e4cb31b21b89e5a24a8bc4a5b73bec3a56bdfb82101f80263a25b70f83068dd8d1e64cc98e63b3aeeccb78d6c9e3878b62a9c41abda9f32e3164453e971e6acdf7f29f55818e7dc5cf8d5c1a7bdb1896fba395692a30dee4dfdc509a42c658e854c1bdba8dddd1aec59e43b8a2c0a6d0fb8865c98d269cd04b978eefcf844f76d99251a999e2df85daa8352343c13bb49a0d681ea4c513829e8131c939ffa81a34aec3aae07773adadb9660b79ee1f063b8783e5f4a833a405deb1f5a7991f87d0bd131d8f8168600ddbd0058c3eb0017b83c1b05588996f8ca7ce0562878a9a8266d889a03b5cf0f2fb63be78735f9544ab4cec7ac59c16dda5e488b185b62bd528e9e19ffd4a52e5530df56d0f0a407be88f4de93a16986c0ed4e3e6433a8158e71c20d23c0bbfc107c79526e4557e05e681754b0571f8e8a5f7b03bdcad40d29fffbd6ea56a614b41cea3942dd73d6ba7c347ea9471caa6ebf7d7ab348fcb822b808929cf9dad5741f9556a6c73af196d4bf904222325a45a9e906976e93fe647b8d8adf09e5861e510a821b54adc469d9d3eac678229742a0312da848336eb6db59955c445d3f376f6874057af80d7dec8bcd18ccf140fd6b0831148c775656fd9fdea9c539f62adbfb21f3bd978e3926f4e64fdfc806779260b650e6d6cd0a0d534449e428a18620c98e070876f9a3daa5e5c5c8363ef1b6f4bacd049ae85b09707c1a5eccb2b26e5deb424523ca53d307b01855b4b62f416e2513eb84ef45f6a4f2f4d6d03edbbba4b05b8501561907c58cf8b106d6e2ef3b4bc1605a2ab601c36be51116a617b2c2eeed06e44027bc7b1f4cb023e3aa5de92803782256b75c5a0c7e9e30ae6c0812ece56e00b160d1a47562fbfd22db64204ea2657f713ff995cfec26972b056dd41671e17bd693c81feafbad81f73022d5f5e955f13a1de1a6b2f4fc4b975d0b1b21b4a213e4c67850abd75569df51a3c516d11577f2ba23d320b1b2e852469f02d762ef1684d514a8de7c7c7c4068f2475c52d2058964a6fe272491712c254e183cc293667605b9afe4c7151d6b79d3c4eb2f493e2fcb1afe4753e212fea6daf32293597b00035c4a0d08268ed71fb9deb84b5e5dceb2680026cedff5902994a259c44373cf7b15ec29e0893994772278f74280f5068023a0ee7b7b03393f0ef95f6acf13b6f4f3d3543124c867e5ea070a4fd85fdb7ff604b8ed13bc6a747abb9843f55de90478b66a21861d86efae0a8e53d0fb17fa9d055afe7a9e2c8c3f007c874fb6814077fc863f66f4141a48600ff7236b7602666a599ada2d22e6e0a968c803153acc98959906c20e6aab31be32554f4719a89b1a2680d12e9dbec0255699f3ea87214c999ce0c86fb1a23d7dd6d40c225bd449265bf500f6d6a01d7b50883eb7e4d32e0e657b50e39e36e1118274435dbbd6b30ce624509cd7894cb910836478f7fb2a15611ca792178d57f78984459496e97894409337b6fba4fa1d807f21a1d930801ec10608091e3fd4768f5f4b513906c11c750b15e72d4e6c55ecc9f87ce9f4100a29ed52fc29d936356e72a82f24966ea33213db9ed55439f90e7fc720c516a68e6a740d0c08cbc83f5aca4259cba5b2adce4b1fb689ebfab5ac149ca9957a6c801dd93b64fc75b4fa8f9ac3930145710c63f3e78ca0fbffd058d447b941815fc38ae182c1ae539b253bdf1d3b43829bce332157d12743c76e2a30826cb9de44a8415097f876b6822c22d094d35618045444bc515aa0ca28f3f14c24f0fdc1e2d3e0f6d1410f37c88e1057c35d679908e8f50e40c91e350472d468d34590ebd05a11b88587b8da6c8a2be46370a9bef2b2d42cb30e87904b5f6e345d335336bdac3f37781f8abfe29f85b9a4cb8d450a78cdda1be4a18b3f31908f2ce85805a0365dc7677793ab8d68b8c7af6438afbd211ff9109f0d322ed630f462f2f954b63d2408c0b2847113a23af6d434d0e746ff24299f9eeb1647e1fa4da63849f01379673dcec6875d470d284a24f9db9ff890a47b1fe0b55755fa998a6b32b88102dbc84f4c5796e7fd2334b6f15335baced92ff0a4cf253a6676c71983cf893eaabd3e93cfefadf56fa4da19a0bedcc46fc5b479fa7180307071475cc03d73cb686f2de4c9fcfb87d14d1cc2bf4d99fcca7b2a3127d1d0738579b8651cc32e3778afd3c698c10c4c1bf2d64a1e41fd4445b1cd10ad427cfd91d0630578aeab84ab548d00ad4a03d85d04edc9f4e8def432ad47ec3ef7ac9d20a44623a4aa3b87cc3a07b14fb0c07d313d02d1bfc704e01999139813ef1586549aabc0c1f984d3cf7df11b3e19ce06f67e36d4093110303fbf0f4668d8dce7a512ad94bc7f6008c98c3b6c036a0c7cb68d983a3c39a7e0d0d63c8a0ee75c70da24dd1d3484904804ad6e1755c92148c51b8d9e90cb9289cfad2180275a015408d83bc7ce12207bc48a61d1b9519839e081390850d51319855e220c5df296c52f1fdb01e25db2d048866b58de962590ce337cf77f986eac751857f1392a64f79707a3ad42bf6f1186414c7514cc5a5e2394c4ce8ecafa598f4f0740b7e06c982da5f7f6c866e4f12b07db104ff3f264504643826048cf83355b0323765b9330293837056746fa9cfbfc423e7eb7e96d0b784e53679e557bd2e24199f122a1c849de09a72aac58c58a18d1f44e116fa914c129b325f1c2e01cd00a4462d68f2d5765cd794064c37e278b90f351057fc2beec202c946bab29efd3693ca8fc6904c81a8ef85de40e3c3811ac6442ded8051ec73b2c8660982b8b271145b8d68477764362962880474e9765358c39295a8fbbfaa0c74cc2a2081888d02b6f094f9d72a9f722fb1ed0731a00ae9d41937ce9a420b850066b515d6a96e209c03433857fce0c51bed1a1cc8e8aadb4a60c999b8a894e7e351f5d470c74a5ce15c1617dacf4cd5ad97d738365df496fcabcf37998b52604ac87304a0ad52b5ed19b720b66216f59c48e8ecf3dbe906b1b271826f2c5872c55778ba6cd9a6ea1a42fab8ab515d39f6c23ad107179594ed09078d4ba3c8c7e104adc9881fce03190c68cad30b398d88c24f4f801f46252b6f12343084f844ada05d13e1b33c220904e913a2dc63d3dee26f15c4cf27533a8fa7f8e30d3da3950f93229f4da06748b773c07c24bd0d59a0ea502741418a980389a52ae296a3380219457e9493b46400291732e3fe7ca259a25e8c58aa173ef66abf170eeb4bf1f2e591b4952182315a9860a117c97c3ea7fce33a1a9334d1b7d7862fefdff14f68c0f1606f2c51524bfec7d4297fcb2d3100d1a7aedae7e05333a16448d3a7f0b4829c60b330e672dcb38c356804b8790c478ef74f61fb96e3c90cd01c19f0ae0a6094b7c3089c6e1cc396573d8c0783d561b072a6a3bac71d8a391e69be35442737055691335886d5bf2b264b7771892920140ab9c1ba4d7fd81c6f561e8e1344662c1769566d87101806eb9e1b9e5eb21a7b92c2e5ddc85d98b758e8935a37dc86dfb6ec9f460f80feb6ec7c2c5b929fd6faa2d7d692377f3b8e4bb33c898942c2c23e3a332f2796e88e22f720af86f8650e7710ecaccdc5879bad1d5e46c41ff96ede80bc0b69e64d3dd167915d149ae8e7c3bf8cb5fb0911f2794ce0bc68237a881231990eb324cbddb9b575f86cda0678d39c0aa054a20b2521396f363198fd1acffcff81585cc255115c2fbff2e3bc218afc2b4da254ae2d337c4e1faefb532ee53d8065161495441ac587cfd391717979bfc274924a39e4dcd2554ba1533a888da1cb52a47ea8e082b4a18b86e48cfe3acc54d2f4c64c82d22429eba88b1e9e663304b95df3e22509666551bf971dd3cb5fc054a82b2e0212600e549b476e726010037c6615cab92849bd247046008e9dca88207ddc5cc36310cc88bb284d0836a66cd231340f36f3ff6fce1e5bbd74991ae4364442d04635e6a9d8fcfa52a4425ae7c82df238a8be7e63217ffd6ac3104c988e2894caf1a12450da86b29599c4fb17097e4f9a8e7ee167d164d1990ad117a08469aedaabac3c919c301b5babc1fef4e135382f3849deb3aba67a045319c5b2d61d8427f138d741b8179bd8f6d36e73e4baabfef963e208ff419e65143c7a028394e227c53502eec93591522dae8726ecc0d5baa77e73f5ac39707c27fb1004ae16bf10fc565d31d4eb381c6cbeaea0e4f3b99ab9f6e30cb455a017996c0fa7ae1eace7dd644f8f6ed2e9d9171461403977559dea507539d8157e52a698a82dc88ff53678c3d0af010a441bb0107dfbdf19ba403412bb3a41bfb7468b6258a77f2d1e18434b97e09385bb74e90b03abf7a9a029d780ba2cf0efa68700a99343152dfa6adad5123f70c3d9eb4c85e3ecff798e54045b3dfe0fffe428bf31c611ab08257d3a75b296d2da6d7b94c2af28ebe1fa78b4cfa6a24762cee53b29b85cf10f3c42b401846fa1d800e6e549afc70e9884237f520da7a68a0bbf765872b57f26bf29425e10c0770dd192a94944d50d6a0654bd62e3513bcfa1648e339abcda083f8a522e3ddb0b4113c5b8e955f267a5065b64eed0c24c983cedf3a36e101afa0ddec9e7a2c607abab235b13f794d2bbe88e585dcd959693a93cb0118f8392a649b26f16ef1f93232a1fd95c522c740393310ec318d5a37dc382fc3ad1fe3fdb64cf44a71053f0ee0cb60b57efb47a683eec6ef4abff5fd41d8b055abc79364e2b960376d26ac7300ee4ea583dbfad6f2784362e8a9489aef24c4ab262b63df667dda2ae6d9c3eed0ff5a445d4f031369dabc861db4a74b0c5124f082e864e3531d483c780c009c2a66de1d0be999ad2deaa746500c2ba2e4c797ed4f57376ab2d2ba017af1ccdc1ff343105e4450dc9172bcaf03966a065669982f443f3cc572154758637eb7b02f3c396249699b59a461a74df339b3f967b1269d1f5551e78c8a40bcea004cd0fd216664868867f29524eb75efd9bb71e0f08f9f21314803697c583e9384ff5da01b7692606cb6c51356ab399e3a41130e782422102cfc5a095c3a918b06b1e2869730590f408d5d478367f5721e368d54e75d727594a778ac4a3e188a44305f0c611cf1c7ba90b0f284f52a245ac0e5667b3bfe9bfa32d077124189fca04c8fb7fecdb48c99d96d497c1fff7945c48c2a13a9c4ff0885a25d5fb843e7f80e21b7234335141d008ea78c3df60959c8467398beb19c997128ed67b122def88facf5c52f86f7aa1c65ef017ee387344a35c6e2bafccd256f35d5ca195cc43e88deec202bce1fcde419fd59d3abf12067be1d67d06c07adc87eff05ef3ebed3fa5b9621f0f3cba6b19d26009952968f7596f313dcdcc0ca6a423578720a1d430582ac2908a7bd295c8992353bb14a092dbddc4a2b9c6264a4dfcbf26f0e8fa4bd8d64e97081c7d1477365f97f79f950df70e9aae1224462120cb029c635b8cfb39b3653f13f795e3f2094937cfeffca4ccc8c09b8334ffaa1ae8750024ad9fb89fdded472fbc6ab425f20e96dd2aea6acb4f2d15830889a0531cf9dde2bcd713eacc09648bb7609be257521d59f2532de6198ee98cdecefde9a1751916c76494ad36020b95c6b8e0a83680eb4f44297ce15fdb21c6be70a170f6b3813f63e1eb0ddf1b239e80e95c1fc49f25672cfc0d90f849fdde9c3bf090e6da84e30e999500ee7337e691d71eff4df09e30c3bb222a55c1850df266e1f74c2af02c69bfc06c2f46d8656ab10151600a57737588e18e3ddaf228643cbcbe77487e3b9b5f14d4c8464e7e980d406efb926d465f5cd51d1c1dbbf12fc3d8cd4e43cc8eb91bb477679b47d7288e8f0c3ecbc2e52df50d882682b8911f6c5a05b27bae01988015d59ce3c3e032ba4ef2d7c2d9514318021404a9cb6475c559344fb1450b148bd8de464b2ac2e2b89cbb4b7ef079424a5b4705817ec876363262181dda41d8001a071411987a54eee566b1608ba445bad5ea61eb6fcfbb219dbc2c5ddb90a2c502cc9370288ac84e3e6d54ed60c690f1198778c213f9466fe8dfd7037a13744da90bca11633cb1e4c23776b90383b5ec52abac49558aa2e7c74d1bdbfc58e39531fb0c312705d4ead7e6f1ecf709a7173a8ab4e97cfe5ebf2644cd4738a9dba58f948316fc4dfa301186144c8a801ca7f889ef6091c811d108ddb5721b4684acad063fbf0a28c1a1b467ead6d45d7077af719219078843f3885a8e711b4be17123ace7a5f845b8fcb4da74ae5c847ca4dd3e49b65eb28877f1c89731902bef392605724ae61300a5f573f53a74f6bd9ee47ba098e2656df384a4a1a1d9b04df1d000a89ab229448d7bc6e62f9a399e30c89476593f5116b67484c6396dbfa9c863342f2af43cb0ed016852c07a1a6e94290fd82a901d2f25d0940a4df13a90a82d156a0c6db95e7ac70e622b829da16f6aa1e3d6d50d491d735a308de22af118d610934a3aa71189c5c3579f7d256755cc6379cabc02e5941225aa76a406fd3bfff7040381c01ba0fdd52395cc6567860ae2c47b31d62e9637471072611c886201253897644fbafbeee42b7512381d3bcb76d63bb465cf8b52c0bb3138ec68f3da34ef533edee7fd38877fe1dab45308da756d0a0d4dcf636d6cd5c09c61114a4cc28548121e9775b72e9fbc6ea6baf2ebf2e11a650ace454382e9ee75185a79e4e1e8cccb83f9987b3ffd946391d1724dee56532b60fc5e9c304dcffc8fd6d7c2638e87b8e0c89f227f28962fa9aba2ec7daeb5fc9a309d00ab4e8e0c4ab70bef20077f5d47794cef872f463a70f1c2cd99f50c6a176febcc741c9c1751e9892c73bf40be3b5471fb7d01fcb22397aae5dc2eb5f86bfb5d1ae5c85a384c362d1f9e58ad5840321e911bee8c449bd489bd07ff9c7442defde6ab9827fe8b20c4b105750d5be9fef585193b761faffb2a82ce4b535f8fe30c77b2ebf178dde7f0e484a4be4231e00a31dd514806505c1c46b3e6882fc31305073cf71b490a1e6eca614fee30c70485a97d470e1711e7759fedae6a25efc99d7a2c7dffe2fc7ffcd17be07d5ed178b2611152c82c69181fc751cf77d3b7fe8a93c7a9c04df6d7f1dd3d3e7fb2da6077bc637cf9b7c880e21e1cca517f0817da785997eb97c99bc08adccfa5610b78653c20a12888404470bfc896d4d03d2d9cf6396434126dd326a97733998f11e82ba88c456a1a897bf512aad9fca3500dcb2528128101a395e63b5bc6ffd24c069b8f5efc7de3710f5f458f48e279e9daf8011b60936c982df8733c5c275fec22222a7101f5c22c985ad1b890ba73a82fbaa697952833745d871d710b49bc5730a4189c90c14dbdcc1a0a49c3535a6cba9d394480ab004d2eed7acac25ec33c0eb3cecd59e98d8db11a1d84c15b03779ec0bcaabfbe04939211e48fc40244dd0499c3e075753e6159e0df8b6cdcdf595fde2531b193e435fe0fea9aec993d7f0b6d492c656904fc509f847516510e3109bef7f93e5c441845250ab4cb3b3d2cafb7c0d1ddc6990f33f94abd402c1755ea57a2996e1b774e5ab144b454742140c7243f8fbcaac21a2308baaf947f5971ded9372e0203b26b53c379d6fcc30e86de854320554b32e889bac7396b40561e7276de5c79d3c871f33878f984af500d76cc73229b39f2f6604cd5035d96dc396174d9d854461042fd80edd7ba511eb84f5bce64f05efebe22d3d274f4947b5a12a2002174ca5ccdcfbfff1064f612a49116e17d06b2332b310899e8e04c2ebc55e15523d411f7bc3249eab2d3d0ebb311dd86f9fcc5fe81ea109f8bdd57aa0030de1cfb89eb916cf9bd81e786e29bf627709d335a20bdfcb332a7bac7b5bed134b90e9e094ab1c2c654c7898a5ead3c352b313095b55ec91cde6aec6ba0d4643a65266cb22668dd9b8cd8bb333c8765ecad3a86bca291deeca48a15d7b24ebfa96954f699d41d1b624b03f55322c605b0d3f8f3066b79e53d2c43315e5cd14b0d8411a5378a7afc745c5974799a74e13c5500e2831a0e7353d91b0fb64a2e4f7cc3ca6d29f06f489827243b0299d19665a3fbc1e1a4e3922a7b83177e5cf012601c6c4bc460676ef8dbcc47ce367e4dcb2efd596f0ba98d688e198fdcf488c94f6fef27d368a419684b3aa1024f7c7c0d884aeae60f66996b998fa8b29dd71e46d7a2c6944426dcba89e9447d30afe444f69924c9cbf9986577d0b9929710928da1cff9a8b85cefdb79c1b21bb12c513119bf9694884c8eaefba81dd4893c93e0d97bb8b70ab0843e0d36a24d17747a7e7dc26db9bb61316f94113af354b21e46b608e7d6a57b3aaadb3402f68b8151cc740aa0eb10ee8da1e3003bfb004cf0132ed0289fe784d7c8af65ebce7d86ce1e12d438219b85389e7c67d727c9be6683a12871975e3785ed7ea43f3275a3b78b08c8ff8bd2cf4c2d3dbec2f39544dc726493ab3ef2536f313e6a77bb6d623d8e8e1b925035b53a66b429ef77111b22d3f9db082c9e24d5953a28d818bd47ff27b602ff680bf42f98461e12166a841f4e1227f83a97d0ba85f057257243dfec59ff6e8a16238188228f8d416be52ed655cb0f5b8d7ac508023a981cf238d385a66eb102647e6e2185bae0c4568c1efb09af0cea464cc2673eb0dd5263e473529a6b66e643a68bcbd5a15a0d817ca9a10997debfbc8bcd4bd0ccece7fed8b3d5ef0492dfe676c7bfbea2b52e792204566c96c39154b4a62f2c031cd0761a21ac84a8a9c694d53251b544315675a40e9b407f4807b8dcb229be9a54fdb4eb0a8e58ef6e83f64e60c5397ff2e62448ca933009c111b85c53b247456a8e0cfeb56f9f0f31db62cfddcb24cf35a54951a7724a5dea6139df116a6102239d2d6b2734e0b9762b3e0d11cbdb7ecdf3ddc8262504ac39e0fc33de223abea3e3bffff553220fd7c449afe7c0d88a5218d28b0ae7c597da4638503d427903917faaf9df0df0b526bbef8ca7427706644f8fa3e83610206378234a5605d68ab44f7549f7bc04bd3786f33948b60d25225c619da3fad7b76b38cde5bb24ffbc6f36eee4d57117037851707b10fd5b2345c786340c2156fb785fd1a24277bfb9a82f52be0c448104281cdf1dd4fc44bbc0ea535b918cebccba708f78637e2fc23e67849ca2da7055977fa38c8fbf2420b3d68df73d47f10afa87dcbe4315d8fca16bc62ec365f7ef8584d43be3cbadba65bbd11889596c90d8765e5f632487329acfd2192b74c4bbce73869f68da7e364b1d87cdeccc48547ee376fd39bfed6791e9df7fb7618b71342c74d4cf9706f9a4cc4c7daf4c59e5159eabde73f4db0c164ad5b5986d614bf8263ac728375e2a722c1db8ea82cb74aa312b5d85b66ea1fdf6bf2a6c8e781303f51d0d89995364e86d46b70f72baff39b3e822933376bfd511ff4fbd877ded65a629cb433e8cb1b313333bc0336ea17069d8c460c9dd54fbe16ecb198563e4aca3f98ed055befe295a58bfe05018819cb973a327ddc44c0536c1a8aa511ca8694549dcce404661bdbd14f915df2c6ebe782e73ef988a31b96a025608d021882b68244c841ef86217ed97146e3d59e84dbc61290cb312d4c20271f55f2a8931a4b1491113401ab6cbbb94e8a0151282eaf34eb423b31bff23745a752e1aed2fc57568ce222b8efec217ab499146b3741eb9433107137326b8c5d3411ce467fcb4fbad9971f5cab0334025f33a6d5eac4baf8caa953fd20418f3a08296ed1e9c69ad84f9f726c98465cfc0c440a3ea6b57fc0f8daf160dc557219709cab043432a9dc01de9f8bf4f551a1ab22d802d66793c887da0c079b3a2daf731cbaeb58cb648c6e350b3118bcb80b5be4fd7f2dee182271e7935c0466aad2d31a12f339c0d52029b5108d0702b9ee9aad2c1d0cda345408f90cdd2d60ae9bccceff4f921a8a323d3d5ac6af26e61db8cf7e8a0d8f00c907cedd730ed13a3182ed2871c1c9a2e49d40f5454eb101e9cd94b7a805120bd0e948d34bacac14f96d8e3dc6a262f3278cf3fdcb0e460618db29ce2df78e39e83d9c7773b3ee3f8e24f4e985e78ba3e307972d9fc277b0687365208ea4c1d38285bf1b1ebd6c386055c20145d8e6060d297c67962d049aca38828fcfe5bc8786c74cc5c2c21c0424839984708b7170cb78577aed3690e62041bd03748448f749d169cac92db3e24de44a5bf5f108c379599d42cd898235c907357437c2367167ca841ca4652ef32556f590475586821270b2cbded67fc0786905064d14c580608bce0255d33f21d2949fcf1de07eeb63ac3ecf8b9e8d632fe5baa3997d7c2233a9c37bce1c3052990fe6ec4f1a84fb037fb17a2f0ff6e90efdb52eaf07a0aedb8f3facda4d59fc12611e01a90f71f3657abdc33b0f7d3d02e772f32cc8037ee3935d6b32935830d69c034a1877bb73da3844a5e4a9f5fc6cc12692ba6eecae7c89bccdb462dc7c81edb0a65a97ab965777f8dd6bbed9d734cb36d2493d64012589bd6f529b157c893c866343f17bc87021110dac1ccdc560f257483b5a9f42ce5645f4b773fa84462d7227c85334834764c2e65d0c410a134b5c82e0aa4b35ebcd80987d907ed47ddc5fc9a3552e80bd0ab51a1201e2468e1455f3e7b1ca9b63c6f2dc1641065dfbfbda817b65e6553d74a42479739426c495dd81ad0ece43abd657f564f1ad5360222c8da00d1da0abd66c88176dd4e9ccfa113630606d7817883765665dd308e3ffb5a44a99f0561c2842e3b4e0cefb64b6b01a23f18e5f6b55c04d5b2202091eb47bf66bf19093e0392c6b0c16868cb8e698554ea7683c516a9e6ca7848de0018b77e31d86dd3f06256d0dac0312ab5bdcda836ab8e7cf663a01a2a1a2fd0cb7d5c5f1a8e462519e58642d1f4ceeb9afb76f59f7635fb051e116f555b1378a4432a0633ecf57368b993ffd94e14407e35f9711aa62fef293e824035ddfc3cd5354e21fc9576326ea79121ddd05e12a268064e07d0e023828f16fdb72920c5749765a32afe788067d0a16f8d9c09ae624b065d6cef8e79bcc44c02675fe54964a8236fce226ff020aa951bd64826571ea289017e424a53997be347341abfd865baab5c76fd44475f885329fc7457d2cd24c4bef6163c0b0d294905ca9f3569945a2bf191f72519a05948d7a60937c73628493b0afcb56e30bc0cb43a7eaf02f564dcc00b87a6f18d3372cf3d795b5082e9151e0854d067ecb439a02b17b352ec0374c31177da92308e8a294a00f2b70876d962f6fa30472e909aa6ca540627c651e453e8a3dc8be31fc86b78a76d527596768d9dc328ab584ba9c3a838b71683ccf914184f02c9b86595b556edc67e27d003ece8525105a4497b75b2afa1729b6b3cc5fbe8f03bc7adf08bea68d4f1d1f0691b3dccbc1dc52b44e9247326ac61ad3034e66d3cf1f648d0e1cb3cd89df5fa1dd0d1703be6c78093da98bad0c6f1a5632415cadb8ee7e146804d37f48136a81efe221432bde0a79a0b524c56db635c5dfe6220524a5e2028cd055397a26f85545c7eca12b78ca10faa0b2d88717032d7e816bfea16f0f16b589770f19b8fc850cd2e4e84f8717dfe2433fd774ff40c53f428eb7b89e4d52010334c1f9d27944070c42c64b59251c2eb6117f574bab20ae84404836b346d8391b25bf4236dad98b1b63a7e68ec0aef40502d4f8fc7261d7f63444b7622a10816899e8b8c770db00027133d1cf56716172b3f639b59a701b37af34dc76a7de4776ca77cf504ced0257c27a57e77f274532f4b217177aa9cf28b6bb5523adaea03985391a34f67f06edb10f13e83e7a38279b893db6647c8845a4c0259883577a83e4d3e1c945f044f3d1cf1e687369c3ad036ddddca3b6eeb42dab8ee7b9b81ac8f13750324d9583a1b8b5322b47cd4a040cc5d9f823fc517cc2e3e405aea619c281d02727987a7b134a15de2ff0b560c1d0791daf024011b7340df5da612c44e5eed676debec01f2080390883290a1ca257e103f15a8288c2db9e230f0b004600f6fcbc0b1755ceb073cafe3745497d4f8867d2d109856a73e8d16ed037e07fdeee6e3ab7c67e6a59713e8197ba7a7e8282b82e7a0f9d5ddb34e82c20a3bca7d42a3386c233825b5ecae0800d821066169f955c3effc34f0badefaca5cb16723644d00e4c3e3c564ee266c9c53a66a9d9c05dc2106b15f0e4876d42e342641401e4b8730f323aefc626b4af118df949e027c187f312f8240545fe6f7f14f15fb70388da8805f16d27abfae51cdc10071cbb678b26f6f4299ac1fc26a500c0e9c5827c737070880462100695918d3560ef30a3a41df5f9b433da349de06762e0507e14ddbbd0962c245dcc2b9a060bddd3f41aea6824e2a160b4ce4983e04ff88a1e524c56677d71305ebbea4387e46b28dcffbc20fc7db41a85ecb305fd5f204320f0182fe34bc02ff9100ffbac7e0a6a969b621b1486b9787d4e4ad0543bda6c5e43debc84408268df2b1828d14d0bcfcdab1bf52051ae32df0baef37ea77a0e311b0dc817146643c1b5eeffbe85ad49c757291895b08b65ddb39dae54bb1e0639fdc948440e1a792e7d12cc48bdc7f5bca4cc98f8ebf778de000e92bc5e805a709cd9cae0f9830be3597d545561d3ec418e3a8122188d250cccc578600b39b542ea3e1059c2b563486c897842a89b5196a1c35c480847eaea307fc1ea0ec38d904cb0224d2cb8f5d53afd32b917a4c7fea376030e6da56750540dcf6cb23d760149f7c16344a42f7607e097a3cd985e2b35fd77afe22e04fdc5b0f1c2e483e6b7a05b675c9f6035e29723934d60f050c43d381d328247a17829cf025431b8e7c803561508ebb35091b790b72fa5539927a21ad090e2df510662b897307a424cd380cd252f3f231326bcef276adb13f3d52e461ecfefa94ee89fb3473afbb62b0969f73ffb99d67b21444b644e207839278c6598a96dd5fbc1a185e1abb6395ff4927d14b707a2f1d3faf58f85ddf6ecf5329d8561f814c51de8f6c1f19e07f32bf9323ac5fe6f5bace5b476c4ae403085110bd2ad0585b891208a40b54c1fcb84d33d8cc3d9c47cdce5ae1595c4f80007cf4b1df3b644a123076d9adfe65f2d6a1b22393e3d01733a7185b04cc5f8b6204973269affa3494a963efb2dd603e7950d6056610685079e5d95db507a5c935cd306f02398d1564103613cfafc2a0ecdf7f793f0c21113d2686b6362a6e40f22773b24009ab6e91ab26964612cda4a36bd20cc1e9615cd0f73422a2148ab3116537ecb468e4371b3b65c2042d5b8778946d00c8ed0f8ec2bf70e329b66bf0fd1679d88e35d1c72361e3a30495efc0ee9712edfc27f0b6d6fe539bdecd1d2ed3f40e74792e74ef74ee3c8704ca4347af6e0f44d7a622aa6c1bba98f4d14643754156300cb84fff44f40b36835090dd16e719fc99e694b982988d8f24e9c3a8dcc088d95a7862d863c426fa48724bb0b90f8f78096b59735d7ffd708754e825fe405d3a71c2dc42eebd6099e514dccbdc75ac613c194ac9f0602280a83f98a7563bdb7ac498ea6d024d28719dc3ba132f4a5087a1b8d5d0dfb10d837a17150d1877b87eb3689fb6bd60d373111ddfc6a434983b6a5c0533ad4195359510df316f64046c7117e19b31685e6c60d4d875ce1f216280b00a546b19307ad0e381d007783e58b793a3b5b0e6406c992d0801d0df6c99ea6d6d771d9256ef949b507887a923bde54b2eef110861d33c1558870321d30100af179b872abb3c207d311b7759c32ad6d06daeef3280264afd14814569eb10fa9a18a25b78ef0e2aabfe2660403075a0c9a1c6fe3cf9fe82d8dad96ffd3f407cebc7ed4704c8092e5a8d95cc2e438cc606ac1e092d6ef72758c2d60017803caf0ad413bebd45eda701de08717c768da7b440b433d42d04dd0f6f973df16506a246b2032cf24e01e0826a0262da609b87914d4368bb16ab0c35c16c8cfe3a3cc738ef4e3a56a810c8c62cdc40bb121da1aae6c911907e75f42cd5e0a756212135650e0ead81f63ac5f8c521e522d86f0ce45eb8485f9281bb138b93430395f5e5e7db556b46dabfd0b15a9a47f75d65264f5123ab95422c8e5f8ccfbb55d481f8532eeacedc4f45d86da88a31ae93a0230cd3e45ea2e91624eaa5bc3b63d74f868be2fdedd83c1fdcd24f5c9407e7eecf780c5bc74701b792c788a146dd1107348c8f4cb69a39a93081361854d99546261afa9f2e7cdcea7e3e361063ec8f54e47bfb5cb382690b5036d20e8127437e17053ae7dd9ef5f302e3ac9dadcc197e37a346b3845d95e50e2f023fb184c9948a2bd441d02d840b3baa551082bc50f6c2f5591eb2f94ccf04e2e92aa970d2a53ec382f09c3841f15452f331062e3a5041d89eaca6464d24171c394f3f568962cb119a292da9d6d0ebc0836bc6a791260feedd878947863fc2f30bdb908a479091a50f70cb363740add9b19a26056101634e84226c971caab804dda3fc9475774245ba63453bfc01d3a1ea4771003fbac56a7d9ed25c1032ad0a72d71490e9c0f1eb888c04ca2446af89f6debaa211f1cba467c66f9b6a7f4f541587414fa11df2593eaec07e4ff0702e3716b723e172a41a4e58738019e2c0c22bb8b0daa92370a6c2533899dcda7685b4878e452d22c3a679a60e4e8be9e9c57eacd51d914a3dcd28aacc2b79b2e19173d3bfbc47347ad9f79b704e82b4bb1790546763ebcc0e79965aa6b207b86922d1b24279c28ba44742bee3892b74bad562fef0477547190f8af031c33a1a2a32dc4de3cbd3ba7fa0141ec0d55fe0ddc5f23d423488e13811d9b7b03356a9ee70d1e919252d542f4534ad0836817c53a76ed9dc4d45980654952305b73a36ee7964e7f94c7f33c22ae2e9d385505ea0b89fde8015c7a687840029af9eda6b109a4890a5a8ee18842210ba13602811fbaed726eea2d95cdd7834bc9df1db190db964d058b1f985ceb10b50f94fe1a7e65747bbd457b9dff8f80eb00539a7336d2b681619eedf33a09e5f4f96ed3fe12c6e93a7e259386865e6bb8d0778ede5c08c95939e6958adc40eafd8189cfffb2c65f99f4c9344bd7b29e470f32c874fd102b3dccd6bcca3e9c191ba38e3d2c91a0801a2032907f7c2c920085ba8a71b7b9939a0961dc9f796c0187cdbd07919eee538f47a6bddc982cd49c4ffd129e76933de8e158f44e1353c704c26e42891897f58cc8b2349a67859289c30e9852c974c4030d398932ff5a1cda80abaabba6ce93ef20d8d8392d4a0a3e6c7121c9cc24190ab68dcc762df4015eef827acccf11f554ceb6594ac7c2cb6e51ec7c03c3e163b942a750bfc7f4f1539c33073c142f0b1be9a69965399411dd3f6c4df9f5d9912eb3ed09e1191e3093dc1496e88ecd03654e65bc1ecb14ed14a60ebbc913cef32bff7f8f063f43a242e4b3402afca766719d56fd5325b3f1788d49b7fa3742d5e3d1b0c8c7a1ad9ef0100035f7d10a50ae442e4d2d9c6c8417fdd3cb3919b9a2bed815238e33b1161a69aec9ca393a875fd823e8ce9221a276e2b6c13b3427167c99f768b31db70f8f56648071e73e257eaed4756bcf9ef34286b3661a23f93f41eaff6e00b6070257eb736ef34aca3e06002ff8ebfe27ef0f3d65411087707822f40139e3223cc1cad7af9dd21afc0a6632e0e87e838fc3c112a695e1634eccf67dea9e761fcb4c24f0a7ffdf576b1b1c1d5c6734a50b3621a1768330275714d8ee24e91dc05f7e6871fbf2a20b1a7346fbcffb614e251bfd8fef687dfff77d06dea4d1e09ef2fcdbeebe6ffd56f4f9c84ea71e97255f819a5ce34d406435f24d75a1e1f2454410768a5a780d6cda24647a5f968b418c5739aea413e21c8739680824e1cb4f2e23feb5c30abe397e0e0da979fe79918f4bbecee5ebde3df7ec169ff1001f6c8a8410eb5c146d2caa3d36e11c7ae20a651ff4c49999c0c76e19991ef4ebb247fe84970837c9fef1752cf4da64df791adee734ccd1703b1967cda82c63dac5979609062423bac67a1707e7dee077330c40e557b5dfedcc785a3621cc2fd423cce8928a314575b0e3c4765335e778be730f55decab94424099b482cfc29def59c8a7dd88d6799a77390c2fa3af9a6d7d60461a26be36525d32ea93333af46bcd989f50878685f61081e87d42ef12d8c8572eac67ec459d29fdcb1c19ce4429163f726b4d86de1cea274d0084dad4b33d7b1c09541fc568b5444593207912ee3023feffecf97234473aba21b7232e6a497f003be931e5542ab8932dbb9450515ce2daee75b64cdc541981cc18baf1dfeedb6d3c76b00d84f9aaefab307b02334e24f24124432ba8ca8d32b6a51e62bcdaf707689bf170571f2e647c8e020d5a7b0584127a37f91bd0577dee6cd8ce19ca30d65a629937811977d423b849c5be4b3871194d13d1c0030bffd4091944b0a488022c9de8898e1aef743643b1f9913d2c2ab610e470fc6bb314b7a80763ed6ee8f8a585721fb1405f6d0c6472c96c81ed766bd466729178dbd1808002445f9ada8057f9a205ff6d03c2abc99b34f9afec32d14342f6343a02656981d58fb747a3b75b15ad7eb40ddf5d366e548869d231f900e85fd774feafe173e23977d2211692c3a9b67112808758cb1c5a6003548a0d1dfca9781e5a9086e9c53be47ae49a18d2bd0ca12306c2ace63d73dc5b9c2948992d06b900df6206930ba312049a632877b351eb4d9846f5de4c543aea061be8bc207885324a38547e7f3d59349d1ec99162d72b78372230daf2a88bd5458272240a729c737d4da7e4779e26221f1b77df5a7cec0b8b205dbff86f71732eab9423b990f2900a7f1d7420bcc7ad82fed3c1b131fac61c6aff72897c9d01cbc8e3e298a00e480e129cc344a236a38bc6e2616df6abae7c1523bc294e4853441792d57728aa18e234c195082b140f8b73d751f18e209a04b2d246df6e50e78c19e752146d54174a780414bfcd8ac2722d1b642df67df3126720a5e47f9f3749f790db51967dd570d05a50559f8f2308532720ed24d2da0840cdf964b575daf006cb5ed43f0f4d53c03f0962c7ee926d6dc18308d79b6956489fa64f7ecf84e123eeecd35f91fe55b3d8a62828db98ac44a27d72186deeee284d268d951fb1a5413bba5d9885737b54b19fb039a29b2e493bd6177cb8b8b6cffb5d130b4c9653f1ee3046de38b05db5966be41298d0179713bd666bdc720a4892d6656f8abf3d8b0f072d070649f09080f687f63c1ed34b7384c3e6b0bf6c18807109f1cf2fa67f3bef9d16c1ea1c6bff3498f6492b1267170f56c789b957ae4561f3d123af3600beafe857d945e7c8433463dc1e965b03ae85109ce253d331dfa4e3332bbf5d443c29d1936e7fd37c0bf41226c40c0d4c3f8d923f16cb9c57db5f1ebdcefcade110f8b40406d8e4f63d89b86ef0a199d8e21b224d3394322833c95ce1c12c20c08dfe018b4c1e5dd42c29f953a1f6734af19240f4632d01bd12c897ab7af67a58e7f75430a8a4fe6ecab10b4e8bf556e4c82f1bbfbf875fb5986293f516c9fecc4cec5bc439ebfd7e16eb935f111e5eb20012fd5b9f0f9033421c1b6836560065d44084758f3bebf9a3db94e3922fd9a1e4d3fe7f334225864bb23f7b9a8597bc0e627669a8e7a748c09b83ee7b26b630eebf43485b984f6fd7865ce6359f7623d119bf6e645d34e58e2fc7dfc1c1d4baf8c543a57ead500ce6d6a38657585975d34cdd9bfea4ea6b2b46243545f914f8f08d8c1e1e15d3b0a6884813e0947ce2a3a53c628b9fe87e509a0150f32cbe9d2e8b056c2429538326e4bfb499ddc79dbc203f1fff4492d53b02eeb9320ef3195dd7994e1f8c1f293c23a76a3fb13fce83dab58ec091e667b16cc1c557abfba1adbc852abdb1960476099d3b01adadcb883c1023492aae49404338730b75e4834e12c3a3573219da09d93a296ae4f6b7f5b127dcd245cf2549c714a24ba6f411373e1ed0be7516296514a96c6ee3c3ef0a4de11b38ba7c6cf9af282cc9c45b69803e45252a35c5c4cc950bdb4b1764a371a103a0454c7d43a1f60a2ae90b246f9023a2b642382bed9b0c17342974c85778f0fc67fe577bf8d1f944fb70d7fdbc589547c5ca4221772a83e7636c87e29b9445d1186e9b8ad585f1df53b2c92f788251689fe36e5564806ee450c36f1280720d6147c027f2fcc715ebc7ddacd1a016730b03f637d519b31a17d691d0470eae8bd36f944f21317e1bd2cf1c27a8b00bd7b9040b88341dcab28c085564b7a37b6f9e9faa9416db9d1e45f25056c49b8f81520f0382ba1ff8b564f2b0163221e13b54bbdb745c8081cbf9d5a6d94961862696e973f84a009a309e5b6c4dde339dca1df35a2d7d09273e32b59fab9863c03685cbd98454817f395cb899b8e4a0650990d3bd7585807853f12c26e50e1d04168efe08843b49fe8331c15fcb0b7df197be691182aab05bd0de73f9b45fe874654213e93079b68b5515a7db956066df092ecbdb50856aa976e3434e7e0c6f93b478202574555744ad7a67412f8205e6fb1fa7001d2b8a78fdfd01455303b797b450d895829a4d5a2ba62b02a1699a8853e8ceff64576084917b0b9bd90a708d8c22a469a245c03c9c4e8b4d608535eec1c0a8d28c9d379f8908c7a87c2e3792c55a697ceaa1530f66e05aacb55abd2c5e8c4ece658bcbc22a63e6378c3b129887dca929ae39155d9d2a9085ecd049f5514f8eaee2a50215fd10ad34070b4698b316dc88a65f651acaa023288aaf4644f456c886a896ad93a369334ca22e6370f1ed929d48417e0aad3612bc022e0ffb52a21d1fde6602e1f59da088ee4558519551150a59fb4baa4c05bc67222bea465a8cda8e81f897d27dd35dc091c96972e69f0e0744932bfa36816796d83262378ba47ceb4e25915c68eb21d1d56840d6fccc3c681035d0a16d31e1581e10ac338f0db408aa5b29fd4b0ad1b31536c5a0995a976bb165d6ac0f90be033ba95716b36a7482315a9cc79e97827b1abc28dd232c2156f70c4d1a3f1e7b18b5ed02ebffaf7073bb015020725a9e26fd291d43a3a18f2800f2e61b1f38e9b76f9f062a093674dc951eea14732d2d2ac93fe0ebbf79f5882a7aca7ba57e0290a1ff0a15c5a1cacaa78d6b2a56bae3b79a1becf7f837d8e4cf9ab559e08fd3c8ec1f34e48ff4376525defefbf46a85f2325c913adedc433e2e7a4078bc5e2fe9d90b6f62f781a914127744444b7ba0bd67e38b03f38eb4c50fd659e75f6c0983368ffece99a2392fc42e23e8f3b173d7922ef3c1eace06bb8748c0aa13adfc19b2953e196aab2b7834dfdd04f0520c03550bcf6df4ff5bdfb7ce30adbb27adfa0ccd0e1d6895745a21682d5fbe0ad39f5689ececa7d1bdfc8c1b1adb9286a6f0e9793671841244b76e723e94216e9a4ee6f8d2f42843eadcddf36912504bde40e683a38cbf970af2d4183f1c78f49d92b364324dc35b0c75c8cc00bcd02b8f818ec91e1f89df735f933e4ff81fd7c2cdbafdbdfefcb6d2467b0b5910aaa84a051780cbddf3d5fd52910d31be47256e5be39e40fbac5965af40d199210a9f1b7e81ce6c47a4f3b6427ded880db6007a2bedcf66b1ba6a7710dfcd079f5176a00fccc25b12c2a66c371a271710c3dc70cef254c0c341daccc062dc7800570be1c12b082bd63d4c1f2ad7cdb1f08b5a30e1b8973ff18040cbf1320f0d0e95b564fcc94dd68d97a455e464e69c1062b85679623266e3183c94e06eabfc4332d634e727a0038e59109ba347ddee28c068d74134992420d2caacc2c1bb8cf0d8f14dcf8c930d851b08ef59fd13e3ca3c2466cd912a0091d2bceae98f102f065a8566b5068b2b1f5da2edc7406d58ef240f780b45448d432d8c780d68f6d3e0b11a2c07fd6859804c8e28ad5342c666102510082c025e0238991920be52b21a3356aca954bd6eb9e39318701395325c5930271206a7b6871de2a5c37037e45e07ad5c55624f14416063c160012a0817da1134829299e3e370e3181a17c6054ffc56ccf902c36a8dae8d5c0fb6e9ce87d395963012805995e43275268ea440accf2617620e7f14573de637b27ecfd117cc1e1541bc859cbab0158238c5b54da9561aaba7d1875a62264b7650de83b32e588c3f0d0716acb3a1ce6e6cae2a080bbbd4b3e5b3609234c41de6681ccf7f04eef61c9030cb22f65473feb7fc878a5f7fd83475dd2f4b3ed77815ac595867cdcf9a9060b71672024ae3609cadd62d5cdc24483325c58e1ad90f42f012adeb74eacdc1e8021c7470fbf122618bad4c88876ecabe808d531a85de449a8271581be4de728f73f02db9adc53e64d674b90ad61994a4dc4eb16d5e3eb8c20e997247413a366156a7a4edafe662c3c3c0801fca05799c4f878301ab0e9c9fa6cd40b3e092b26788189c625e59e2f69273501502c1a886cb454180812a93f269addf13657a67e282b59e06c27a0618c129497386c99379edd48c6b8a8a8ffbe982ed66f201184e1fa0d8141168211ef4568c4c4720404c893fffe02f92879edc8ceca3bcc7fb6bd62cc4368fd2aeeee7f88f248694e8e7afc4a5c0f5bf8d61625ed227a1b1a0b9e9358b1dcf92f2138fd483127ea5046888b389201555cee98439f08df19745a69e9792057a21b1f4e75e447569916d1301505644f0a8510e970fcf3427cf22ce6f28471d88cd6bce768ca7f4d0d449316b9581221ba501aa5d9fb58fe4d84bb068c8f5f20af2e6fe3c0a3046199a5ec2ef5d6486d7355a76cb5f8790b3415c0931ca7b32ca065e36174280a6ce2ddc919951a7464ac6a4db61ff61de1f8e6c89eb23ded61bf7ddcd32a4fc7db45d0d9891684cb414f759961ad5a41ad3bb14186261fced6feda0479b05bd99a78d639a3066df1f5b08dbeb0c9c27da8f7744e3d98b9d4082b17b10a5f0593119e0cc63c3cb48e253636ee70b3a84faf0876ef0b387670e641b5fd2577fe838c920e2e9b9cb98ac698c832c9fd54f5fef3fff77c06c41cd73346751d4a7c70fe5a4e41a05cbf9bf8fa4edebe16a52c4c11b2416d30537691bfeac03b28c85341cff57c7fdb21bb1858d14aea4e4a0ebd69f1df88770e2dcfa40450ecf8fa3951eebbfec871d043295c0173ce751af4e0650e9842a69f58b0a1a0a1e795cace27e05a821cf4dcef531d31d0ad20f14c75352f6c4e1af3d57ad52fd4f57d52992e2ca6bedf1a7d7a8e97ce59fccfc40976264c844f881b0cfa9c107f351ef2968e53f8c84ec5db6e40adce48a63ea09bcdced80cdd57427f643e15e8f862bb02adf726031fa2653ddfa1a30f9af08abff25da4b45bb8a122dd3cde29cdbe1f6b0c24b32a94fe483420c91e8c130fffcef401fe8f9ef0451036638f6650a337da5bf11d1237f8216be2b09f3c233672c1f87a57ee27fbccd3e828b72172c5f2872dc487665538c96e1dd72ff394598f69e5896dd3b81946ccbd295032d9815f83a0d026cab3c541a1594bdd3e7818b743047817e7dc11d50b0d01751be0525324fe270d7e917acb042f3a35ccdcf27b9497a9a4218908c3ba264e88b02cd43e6b8938f3c13f03b02047125f5f327d0c6e2125eef895aa3d7e66013556a4c56cf3494f20a5a6160048de5ff8e33f605367c062c23f17ce7f489e6d517ece617e1883cac7730f69baba2760b71484a2723a5cf6aedbe68a33ddc3ef7791279d16b785e094d6ffbedb998f54fc060699a373c7dedaeccbaff5c696ee854b6556e04505bfa761079b2c0cad0ab2cafbac49774a7050348adce7584e07df446ee95aa46ef26a6138641b099895f97c90120d3320d098259e62c2ece5025322272071f92a95161fccba3b73d18f0485d3ac91bec90df3b30dbfa6410bf50aa7e74364bc4f71797cfbc681886eaa0621159c0707262a705c8f42cbbd165fe83d4aa3ef8686dc3a603e7c4d192587fa809a7c19cedf30cb51ee58875b508918bc411dc98ff163c9d7ab2f12403f440f876fc5d14ae2cb624a2e7f11c15fa9f49105af3694bc88fe1b3e49997df81e58fb997d339fbbad324e626a583282279cdb5daf409bbeccbc080f91cfb4c576722fed3bf574d918f7846d857245fc73457e6532a1cf991f0c9a41429c3cd33696396a092f4f5aa84ee710d89e9020cc064c670ec4186b427f2c1e6cb6555d4822578236e48d70ac0ac04ef7bdd330ace409a38843ea2e4ace31979df7eba9fc74c35c149c7c6a88b357b92c1b75d59d512013741b45969c54bfefb89f7cff1c35d858f0f1c2a2519b46e33937f56263f6b9ea349760dadf7993966e557cde75b29ee0cfba9d3d7bdc41aa9cf9541695808b3c7833d3aab16352fd8b749e9bd5c457edbac0f92d4cf71a8d96b47682a78edaa0d467ebb9f355e6ba5e65f9c7ae668e30b29dd089ded68f6b2df10fbbe472617596fdda6c8e00e664348a25d8ae7cc4af4c7fb7ff7368f3b1ec0cce33201ad74a300c39067802f3a7669db71d875bc3afe73d429f74eb2cde3d2029fc82e867610dcb487ad1df03d3a876e38c496bed99c4bfb9eb321ee7ca7c8590839b66fc3a4ec78c335bd79153afc23b0a0978f052beb25d714aa11caacf44d80d0b8b97741b5bb84406b7a69c8d57acb06f0f064f9aecc4de8a2c2b95f1d8b8b508e9da3ef637028582f5baa7411be43cd8b143887693c4d2b0c7ca28b3efbf27bcbdcf19be83a27921ad519a6e411e9970957d04374d9d60f8f65774d1a760a9c1eef61ae07a425e66ca735c7cd0ba663982e23600c8f8e287586cd41f1a886d5c21b03a283976332da9781a3ff87885cd539b0077701406a6d62e6515da9e2a048491f05c48a49f0d0436c6a10b36708bcfe0e2df91f851d7343b05ead9dcceefab81dc74432b84687ffcade5b3a5d3ba8744fa58d2fdcb4a366191ad292ae840b9a71855db2c1c3fc2a88ca0cffbcf11cc12ff71192b37b5ac15cb02a1b8247516c52ec9e7e1a0bb82aea480619518057c8ac85799c60c7ac8fd8ec3eb611a467c273ecd9108a56d6029db0420107618de084edc93d09c83b6232f5434949898ae9f826433eb3fbbed52b1509bb48cc084c5acad143b0d62c03953bb0f0a17b7e5f7e7c63e538cb505172b98fff2e166ceb5fcc525a9866879c87dae3be5ab65e3d8d5779da23cabe64ccdeec47d382475412552e0541d460bb19a65a2356f27acc394d441341416ffbf8572018183ac46cd1280022115bb1d51b776c61e005559738821c67226b73beccbdd4b6ef9eaa10df43f76d1d4489c59eac32c2c6977ef2157f84ff7a1d719fb681090cac6702e9d914d36aa8d6b5f0cd2e6378bd83d943bbe8376575b3cd78a7ff63b4424e9a1f7ae415048ecb6202aeb020011108fbebde7cf001c03300ca0135a56044f35a84502ad4e68082de90e6006ef0f5e8f0d1e6480d4025696375cc8dc4e9fed3799e21edd5138d627152ccf01e411dca1f8a83deac5df4ae736aedeb315b8cff43add3ebc97408300ef9c59d20277f77a5b023b79f6979c1f13f8090f613de7b6869cf0366d9cc7fa621b1ea57a83fd090ef76c3daea76c43dea3c9f91b78f84da99882f6db30dc984856055eabcc486a2a92919bf9c9e261a2ae020f53d35c8397e8b5d7fc5d3534f5aa68ce4de91dc068d8fee3898c95bf568b4eb06445d9e806805570ddf189d08211827e90559cb7d4d2dd2b9ed79a2363cc42c151f3077a339b3e2b5512316f6f3e4a30921af1d37ef4c42e1b9888b700fba6ebd4c7483dcbd6ff67fd50fecce49f1158149a069dbf459618efb2d401fa8109bc8a305dae68341ecd47146d2345df8f70dade6e8ac94f78d7b746e660a1f93fc6bd68b288970c507fec3cf080a66f8b7e45ce19bb579a6cd4ecbf97976770074c45be6f7b3c9fd32caff7bca21d921ea418cf19fecfd45d2ec28f8dd8bf4a4485e4e16f07635cc1ff84a0a0baf69aff2f6df0e27aaa28e13add852b72f69979171b47ecc62669807c24689ef34caa5e960c56b802be3338cd6f81b781918f17d670d4ed66358fcc0bf4341f8ac9327f340e67e400886ef9ba03330546d586b71068bcd17241d7270886caecdd8db48b953c1a2a9d4e25bc696684801413a57993883018f478c4838160f4b193bbb62b401cc4f9b35e8388aace39f82be8a9cd704033a7c68c657bb1034802ec60969e1c3f11963c37473c68a6eff74dbddb4e5bcd8bcd8b00f8b0f0e4b8feacbcf7b51bc5ab13675e636dfaacd39606b38f1c24cae8765a027b26dd44da7ef872c20bd0d36e8559f372c6dce3ad666c805258f92336b79cbd96340710e6b1916d0f391804c9846b8860eace1a39cde73784533ccddc1be5700c608f21165eff24fcafe7069f08fdaa371fa84508bc1ce1d89c1c2beeed807e77603e7716dd0cc9824d07c00c829d82eb0da413666f2e1b0eca2ec4ae1a36c2dc6672f0b2ef442e5c0b18ffaa917fdc17d56ceb3512cca352d659dbf9b8629d292cc1a7a630c2648b0998e1b3c3b9bc169a521a614138d89f579f4e7c2714a4f57f57e38730707ef9516f753130d44403af3a277cf413dc3f762aeadb0c918fb1896acfb4c189cec9c36b4671d8803c5be4f1cc12d1c6983e95d8f10509c1f9a61b491ae689195987e856931eb384db3e9688029d3229f7fdb2f27e092a098805cb6e1a2323a63ff6c58f370fbcaac70a1daaadde1c5bc1eb3feb0fd6e68e44fa0280a9897cda9798235bc188622bddeaa401af18f60e2e9525250db8c7e56dc54cf00f4abdbb9a71e00cec93505a1285fdbbc5187567f5acfd81e5cf8173532db56da124a29938b7a60aff8fed7f4bdff67148cb38c6ce416e21c36d0398a1455cd037f537fcae49fa79de829905732728bb1ce510cefc1cd6cb389fb3e35627ad3767e89c03bd2ab8da14c69f08d76e7ce7bb97d941dfdef5061746b857d9993f835f9e96075ef911d65d05cd3cd09419c0f2efb7d9f05ee94528b1cf6006f30158e70a751bac1fc0e9c60c03c231ca8f8f24e438d3a3f3e2b39e6795a452b6d870c0b1f114ed9f5c61c267c4a377600b301b50cd29d29c585b386f9f7439b375e6876d44814710c1e2961aea3e2d23f6bee6077d02b6494e67003cde03953666a98f259340f20c7bbda9d183eca7e0eccba974474b59f00fda4dc586bdf7c3f330fa1a60464adcf296a057e58d090a406253483762ffc07996fb9ac58672030d46deea411067d60afa0aa30082949d64497def516a58d0e490c5cbc86f11d3042938f47f85cbc7ab6fbdffb418fef709f5e52a25cd0ebc5552b1ee1fbc81d0dc764b3f87d06b02e49043395e7ce3fd95393038f3334b41dc55e0ccd7cbcd4d9af5932f58ca0c3ef9d34a670956596bfc06fa066b0a1f2514c4d2f8c228a17c7ae2001c967b200d1ad2dd1f96cffa2edd21836bb2d4a7effba7c99557e14e589f79d6679eca9682f1ad1d2a502b85e6a682f3baaf030bc79814dd5cfb23a65a6cf76895f992fbd935f4d3832a668799d9c55ff087c8f07cb4faa8446f164ba5868164d647f95818287bc120077f3704d6a92257ab6947f809c13bf407ea1f26423f41dd980630610e391b6463c8076c1474e267688f1dd068b5a8563bf7b465d39d8ec1ff8f3a04e0ffdb4fe1612ade3cf00abd10010af1570a6c690eefbfaf256ee7fe32d5c6489f9796bdc46ef0d5cbe7728e204a234d273060f61c74b0219cbe5f3d8da244412230276db2585e4839d71f58c2d0ae270c02b9503a1ddd54aded0afb711f866c0f46a4e62f184104eafa95e47872f3b482b3d35beb1f62fb1a83d35440b05395d0bed173b63fbe4b554f8ae7dedfaf9b0c4a415520446e8a6d93b55ec9c418a45dcc9ac5850b2105d813b8cb335e6419e2b2e1b70cb20c928cafe61c73da6d28ac82316c43db0cb004c52a833b51e6fac2a23c4f88ec53fbe0a4ad71e6e6729aad5070fdc393ed6058b627b4b42f407c07ecf72ab91c237b6345136849661a57ae89b3ca933c68dcb8c5bd0a549ed900c49c0086bd60c3ab7f5379ff5df1148f98e64ae1ad1c24cf0e6fae5ffd9eb852a718df05ee56dac13c675092d7c664d98641c83d45c39c913f9b063670b5dcc9c5adda2103bca43e3b2e36db3d3034056e672c5fd5325e193acd69dc82c58b366769484d6fc2efefa31733418de6b12c725d15277a975623540f84d409405441ceb02a3f84108e8b17712871e22c16243300b42462384f391027f3e03955f4cfd2dcdbad39464a647a90755dcca10644858434f2a12fba3855200a6d8ddee54fe960e70195f3b20f5107e4a1be91eb88f996167dc6e232031b7bcbc85aea537d5907c2c2ac0fb13037b96baae283f6e3577dec72e23adf2de724ac59f52d44ecddc1b6fdac900cfa7528c8371e88296364d2a02c585638f1c759d942493072717e875bd3accd7c5b431045a9ea9900b9db2f06d2a04bda0088f73817ed04b7be29370439d2da35708e2898bc5d1e8434a8b4b012d971087269393e6fd0945ba1ebcd4c687c8ca50eed0767707f7c8b979ec26d2eb1f05620381cc40c537248f49b2785f133cb86d397b8257640dffc276e9462cfd1dd4233f7a95cf01293460543bbb6342ae6c798b95c94838a0bf01ea961453bb899edb63f59e6aaccaadcd2bc98bc9fb30166786921e809f45be7fdded8f031efaf70caa77b06b6882868ae0474c4ffdf190f3b364e3ac4a6fd9f9c1bc2274bd7232c10e004bc03247390412130923c92e5c3dbbd13cd8f8d4598ea616448e8dd4f8195027c241ad34a23ce720e740fd702af0cab783832304b91f2c2ddd14d68cb07c56eb1384768cdf40fc68b55a8eda7e4c05d89475419402b1c3faa212fcb3dcaf9705f55717b88629d5bc7dc7ec3b4b82a734fb62eae974ea7ec1e939372d3fd6843bf3ae60c6fddcc7953a769f7c28766dd1a9039caa2b6fbc75d40b75d87c5c1fb91bbdb8db5000978ce03f7c78aea767600cdc74ea6ae8bb8899979a6b513ef5add17b2c4aef417bf8ef80527fb0cccfd8173f562c66603f99857a2d572bf5b38b7615900ff334b8c9f717c05a30a041b31bfacb9008957f44679bdf2e8de7a12c28739e0114fd40ba21db041860f5686f40a2817b86984f6095d4c63e513550fe0610ea3be5880c43d8c48740ec34b0389a0aa9507f10c7c5640a1e85c852ee202a3dc9546ea9531e90b25c14e3e0b16709497e9ecf5888ca573b30f9f4e5a4f4405fd47fd3a6cfe3410ce724d1baf3117a5b6c8ccee50aa282b8e1b745dfa940e07fc8a8af4d2107f3d808309822774a502a8c774d0ae71b19a077bd704211756babcda608e0d080da536d642428221ed66d949b4d390b44eecb73a7ddb30c66a11f7717f70800fbd11bda5db75cffd68c001d2e7b14fcbae6475142fb33ed76a699ae0c5c2ae54f8c2b25a125d1936e9d707b0c24dfbd62579750d0d1d3c0a92c991892c88caf91ee918e458fb27e1e2fe6b462c873d84e37983fb810cd76204e8d328759a1327456b38336c22e0f34b468807ca5fc79946337fafcb87b06ae8dd93f44bd37771d3eb805d997e9e66cffad20ec9390d31c7d9335c652fb9c195d8f7708b7d3c0f394f828e25f5a653f6ef00d3eac410f65523a9448c30b9f7c8a7752a078ea248be98036769a119fc97909180385d6dd0b431add4d463816ffbc5b72cc680d2b859491499cc680474ad490b7e030ad92e20bfa2003d6f2e5614304cbd4acff73a3565a6afc5e832f9c654a53c645eb08a7ef13a635d5402fc780ac0dc541f1c593a3ce01e5b09cf93d664215400d3fa3f153c44f47e50ef988c73b4abde12e46d3b7b9197b6353204f19d91a309444a528c9937361dbf0f4df64a1e2fbd65ee327cdf5a9bec94996d30700aa5ffb25d664569f736d85cedff8d52c01f623a3f08b6160a3c6ae77734a1a098aee24e2741c28e010788f0042dec0653dadf42e945ae4ad1a18a5b44eb0335deaab18c19d04060dc417ce632aec07f8c665d8c7f40cb0398b2ad685483051c50fd7c580003130af0f687528f83cad622f55ee82d20de0f0121d0eef55d3cf194d91c074fce47e1e9395e4741021b2218996ac9ff71d463a0f083d3a013d61fcc7eb3efb5f915caf4e7af380fa968448f2f00841d904ef2422db4794771c33aae3105f6c9c4176480313f6083cc9357d424af6e74cc213b8186314a3c6020ad52a825ce16027342e446f59e58c7055633d468e1c6d4d578af479390d9c4bb7707ff4cefe279125a3613fed160f8dcff7aa44776fcc6d3323e38842910ea3497af7e54884712f4fecdc356f63752ecb27aa6be4a6ec6380a2e659be18d4f68d4d9fed9e5a8789413c52171b4512be72c62b9d7ecffce452a63ce3dbb6ffe7274b4ac0732cd900e281cdcd95b5410f126a3f6b0bf1fac1661da073b5d55c5423b6f855ff562ce88c0e48b523dcae35f47ec573b5f4d9a168bd469eb9c5eacf9f6b05743ffc6d643082cccf52cc3a94a793a844fe40da8a768c240b82f78310b1a03c5bbf230ffe3646378690d4687e4f30fc06f5310a1027e570be311b8669987c9257ef5eb7e7bc6850fe0295315275e6a37aafa8b1e2e5f93dfb46dd10b43f4bbf9141b116275f01556668f6c3000d3456f49cec0d7fe65a1e113d5893f0848b0da91b3f1208608d0452362081c5dce062d3e7c303b0b40b26c67c1a64304e8e56e1b921f7557650bad38e59faa5c9d741ae8e1fbf504c067f8b1a4071169c77a23e610dee5d87e52e3f7f2cdc9889b928ef06e4e2a78a1785ce1c3d4fae09febdec70c1fde4bc45c1530a4d106cf83b7c7b31d8b7e1f318520ffab2d78b3a68d7f0d28721c373606be86b8ed81b8e7b0dc0b3d5e51bd3b9a10f1947e31ce1514c95753893f7dff997e8f1087993118763012ee3fbca81edb1d72c413a856f65eb13d7613b2be67efd60bee9022b52cbef3bfcfa3539c987d7232770f142df7bfc557b977dfc157cc3be421948cbd6c91fbff204404172481d9d8837f127e7b7da2eda72fb9fbc224b3f841aa948e2d07989b3035c12451be793239f192bd7718137cf4a065c5e3603687393704326dd4abce59986f9c74c59aa6524f6fb490549e8e9e9dec42894d4e5d7ec5de75a538d320461be86979a201264f33f251c39ed65ffa74c57f7bdc803f0fd7b09250c61be7796b41c5e17231fdd706fc5adab8eaca494cc5f06b205c105ea8fe923273db5bbdfea62c3babffe72e5f49b7939be7a9910c55c8e32dc7987ceaad2c40001d5d458728e6a3d676a4aef296d7c3c255ed79a7e56ac50e9c0a376210be388a8676e121a7bb7da61d5ef0c6fab88afed2bf02e33854c43e8f14f1b7811021777bef2cd3f5d965979040b6e09d9fe5501c1612892933f30d23d529de17ff9d4b4718b645eae623f96833ed5452c8ed039a50779bcc665439e7db8f59fbd1325b36fa741d0079c431c1c1db25530e8cba3a7ce0e1bf48776260e1425014cf9a86ae30fe6c5a3fd0144817d8066da3b393514d49d025afca0c356f3e2bebbf8599632b4df74e8e8c52350edc41cd72687ef939269764f6c45a45a3fdd73483778f22053a1d9fd82fcf549d308a201d8dd126347e405a63851cf5554dd6459b412b3e0abd19f9fc9bb42614f1ef7d328e919991d3dcbc8c62bf7971ac9c82ee101a7eaec4e40398b264f35109cab8514d18ec1add6e04f127ecbdb0e8f34014ea340b18302538d95b8a95566678bdfd03213b0138676c67693367012f0b520bb3113645b62502929a944445cb53b08d1915a850e0b18a2d05679cc5c2c80e6274b638d80bee3f1c67572ac9d4b3e6c6ea95606d4c3bb83ba1e4945c240f5a1913ec683e8e9cb4209e3108ebe46ad5b055274bba1752fc606c2bac2b0c6128ca910499571d3a0b6e4800e42404b5d8ce49128d14f5d402f7a25bf298bd6ded4f8cc1d17bbae658bb05a8b69d3fabc7ccf20e173e5b5fe41d3b55dee8ddccb15e65b40126fe786f84eb4b036acfb74311f884548179b0d88dd246306ee42df5ea5913dbac269594b8fe1a0d8f0c4a39cc402846ce941393a7e4e45b0e2173dc1495cbea2913c46467068f06516dafbc78653be054e8d772793582e80e5573e00339041e4788185530b4fa7747d1b236c25581ab3462a235f4c7ada43f5c032e1d63651a3c81bb45f3c7fdf2311e56fd6f2b2f5eba59daf453d338ef5024141ea92b94ae2e79043088880a099a45209e52dd812415c90debe3f1eaa2a3ee8accfd405b0c06c7057ac396f2f92381b338893405fd2b4be98a37a1a9d89c5df54c54e3e049c2b1f8a42af8a58f0cbc272d887e89816b21fbaf5898b8e2dddc58f0974c68f3248ed9f7bd30b566b478d5a96f127eee13c13b787673953e0713a76005fcaf35ea7d3da343da225496a3634f65c6b6a4c8b8a393c9425af20fed69df27f6650ceb5040cff7d997c3d7e722792722472af730b8f55791ee78de29414a1deeea4462be62f9022771d1c61d30739f6db1bd0ef78f0aba2db2131d4714f7f1760a5193b1ea823aba146ebbd1f88d43f90471e5278526966f79bf03a676a11839f4a83cd80eee6c2604723eeb1b4d4b95538c6fa0067993d4bf8d0e6168c414112698bd7b3a35ec98144f245a99aa827d9121c92745ed7ae8b61475f5dcf69bd21ca4bc90c0038b53d5d2bcac6dc8225a182c671c7a3efec9b7e1387aa14f56bc9561feb09a804797f08a42dee6dbb652e90878330701cab614b1efecc9a69204ab859be2826aacd9a27d579634ab888333cfd319354c7f58c6856b583c536a5f09638506b864146241246f2dcc884cb61fe0463229d78f6f0af3857d09c022a59f30b4beeaecccc0bff4f6663a0e48d7dedcc8ead42ec383557f91d86f1e08b3d42fe10605b22e0c7280c69c4ae670eb61c3cc5e7f6f15c5f9f03a07a71ae2511c7cbe54675926f6b96c06b1f8aed70724122b720203d6b18c6af7442ac29f87d33b666743521c1bf3fd36b5d0ff730aea22e5d664280116832f8e16fd4096f24ff6624571cce426066098d5af78d06da04050bca5b4a060faa42e0bd156557f1600ad9978f52c3cd8e4a879910e7c320b971e61b755a2b930f2f150d9f548377769808a5afee0b2051a677b8b0d4d0daec9bbb78cdbc4d623bee1e224585077c7eeb458aceb118ce1bd49e43c2c578c11e2d0c6421f57380d496442893077cc7f0db3b5421d784c86c45f4ade46ff8a008467e4681126f6e4c912778e0d10f9b7dc4bbc4bbe4a0674b1dd0fc83705171cbd12a9f6467806d10a03b7c887c943237333891c99b03d6eeb0466d608ddc9ba7792cfcdf5062f06be7b6191a6572e1a8195aa1bea7abb550969534f05b369a4e882898b79accf58443d5e62c476898c2dcb768f4f348d6cb36b9aacef65658d2aa0a1297b734990108d0ff144c06ea1e9d3a7a79ed58fa3e1745da88a00b4adf3da41ed247e8d61dd98dff68352a157fae7efa20c175eec2344b088a00e030cd14225b4a4c4fee992c1fa7c64647977ae93e2474945178c9765a649f8f8549305e0196068661061032a37093f304c80e11d44eff503e3473bb1ced4d820253f109972ea4794bd0c6bc76410c1be9429d62d360b8ca2b525bb573c591988ca65452ba3b249a03c7d5199ebcc3ba3f79f0306841bdccc34623b5ec71737d06ae1ad642ec13ee393f4741a33a667dab704be3c037fd20a512b11862a897a33813738693e7920a2484f0294ec43eeda8812b5af0b4418387c53144cdc4d9198b803e933dbf23f6355e9d372589108e12f4e6c3c32155469f5eb601fe8537a67dbff3b3a24155ec66c103aa2979cd852421ecd6da2468c03729fe06b587d949835d7a8d5020d62144657cbe1e8db1162206f1790d898a8946f5535820074afc2ae6d3e81c7c7de63ab55ce460c56c2fd3bd29a366f3979e4c6c1910e9d9c9e4931240f6e8873a63c786aea91d678cd96e2d2400d72dab983c2da1170bdeda68bd359dc2f098479a449a62ac3799fdfc188afd96f8cb74f4edc6ee227c0152dae700c436082842c265fdbe48dd5d43f0e99e8b0ac4c6ac640ff3b931a5db2705a3a1f4ba6efb1a15c649eac8230930abae799eb8e2044df54f474a6165c47953d5e9a0dbdec4aba69671f15c776902794dc8557fa7513a33751b3dfb4499342edea0e97d422d196afac73e35e03dc38e2652961dc4dd1d46273574ca5c4b3d00f5b4c71ff7471e4626673f8cb59a27b9c6c8292c5606f7333a539b7eeca3a7ef2a938220315bfa5bb8f5b882723fb7ccfc19250b8391d431f4d1778dadfa3666e65db2bc5deae4a2e02e28f07aad2764d272efe0a6fdc3c0a3cf3808810eeffb207bbac00b5c0870a07283065e6fbdffd7f7fae1ef12cc9fc1eabbef49f286c28cb2cbb3025e866de0448b8454b7e744648750ca065e95579cc1ba156891bd3cd3e8059d2b861f9485e25c186263956337ce10619aaf48aec5e20a5a43524d1971b6200718c18f9a73b25e56ef73f3fceeaea66a0d3c22260f4886714feea12b51e23995dbdde924e184f8d5544a613e2bf43b1ee263a594f70ca88b33059a1d04df485e573b6f37bce6f62adae609afba7c489b67436e5cf91b6936cfe350441439e074e6a7acb27fcf33bb7bece54650d601d3f3f7fe6637504e5ea989001eeede4d6bb988ae749aebab31a933df7bcf6fe32f450b11b76813c2785ca84399329697a06aba3a4d5c69ce27d3985ed318e51908dec231aa343afca754caa305b714f8df450381e2e9ef5d8a11329228775d7cfa59c5b065f36fb4cbad2c0e7282e0db0891ff35367f512b39308b5d25f4804487e40dd9a00cfd71d9c3bccd5597287ec39405dead1d50d6904ee96629b0a3756f30af27d9ecaa8c937f99a1d70391834e10317d8e00e6cc09b4a253eca8fe42e68e1098d4ec9eca411e015aff630791ba73035858b2d043246f0139fe535fb1baf51180d553939dcc56ff930e39c2bcf6abe5ed7cb15b4c6a13fc95b0475ce69896fe20c6ad987d89e5562804f1abb452f2d9dcd16b5b7a87eaf54411ee2c48058d08073a67b21a935840adaf02496c1ca2c3b618c2432f4ae1bbd3e66b47a516281cba014abd6f58cdb590238beb88544b8c21c8b6b6a0cbccb34715fc85cda581604ac8822a1ed680ba9a5f922850f12b2a6e51ec2da7d557e14d729322db62739129624d114ea15c16027bd9248cd706d73b984cc59be405b25677b91f60d61c3aab7867078390c6a0c7165e6cf1cae47f9a68046ec85d10748fe4cacc3662f60397e856042c68f391de94d02cf0b62b2af4eff67bcef61dbfe0b62f061dfaf74ac383ac6713b4c1657abcdf52467fc4bb1b8bfdc25af84f6286864e1f695fc433d2d40d7adf5fe5f55572304c16a20eac4aa45ed8dc1410f65464b82d20b8587e0eb023324cc7709cd39bfef1f9458ce0a4127fb441ae3a7dcadb5c41e834e2c50b49297bab7df75aa39a17ae37d7441915c82783ad0d8fed51ff672f2c54e77b0da6200ed46cd4bf377248a505066426a17eac38ccafbbd40603a88e4a48bc596858dca7631d4aaa02fcc964f60c46a72e9a9f649e90926a4738292fd77f27696de0eece6ea6e89075843613fd5c5d2a413795c6b646eb9ad866303c9e953a89712ec529a41abd9d105cb0e77fd951a7257d5f668546204b2f128799dc7e662c73138deda8e5dc53358a67044e256092a5445f23be2e7c9dfd856439861cdbfc25c1993d861cc0aa05a2bd1e1a924407d85fbdac9d6eab266aa4082a751bcab26887d146e5ed381fae7f7cea4eef8c619138d470dc2c6faff92a762bc1d3465f041e1ff32e1611b303b2f4e49ebdb04744f8c96793e69ae0d27a915a4d966e7977d33af21b425132ad6743f6657d4cd6a786a7b902bfa01c038af536dbb3b7f0e5dd210b29b3b0d3364d57446d131de5510c0fad9987c8d6e0e1d81ed2a46128d778eca411b49539eb548024a13e7477d9a6f1a6cc3dd879f3cb2c89211594b0645fab966b05188b2d34a5319682b1dfe544a9453dbf4d31f6cdf9c4c2ecc27ffadba746cc892f7d2d2080ed858629abce0ef50d4771b8570b79ac764a3ba03b81436a25f4ff4a9b8bc928855c63fed5cd8679a68268751cddfd823cb78f65d74373b2a70334760bee2748c3d55a0a683e00f7f263924afb67dad80156710d28527310609baccd156f6c99629ebd3006aa8559f63448908188386caf629a05306aad3a0fdfc5135858413a3b296a1f1a75a70e1873618bb23f639d267a00c0363046d1d0f6391499d4d8f03503c4debc0e6225a40f20747ce70d8f2b00ce6cb04bec57233433ac958a570759f7c43842bd0c8ba9fa4e798d373331108f074f319f2b5f2771cea7907e9b5a8d4469b9f8966c2b803b47f080fcb5318abe4a0050d08941f0fbf5e0a20ca4cf4731f7263a1a8b7ed32281303242694f007e9846c5fb368421272e06a4af0da5c452f21f9c10acff627e0f106cf1328030aee69789010b87f1db4796e57f69118c9e062d80ad3f06d65c1b6729f6eef17017521956af10e77d6cd26985e5c00ddd29db6cafbb6669199dafb158af4e34b1a46736994323c3e2c09ae7f9340f955494e2758e5e1098967b15cbd9f46058f3ada060ee40d7191ff0930371c6c8d9fa95d593f07f1c5fe1612cb11d00d4b26d343b5fc63548d41007f961f1434dcbb0b6b9562e67deb7b13e40cfe5f2df99c26f7ea7882cf71bbd092c9022231e52feca39781e50e03c8e08e760ff9d9a8efd41f82637e7f93dcf41ffaa7276dc09a91f4ff23c0fe2c1ed4bb5b387ec18809fe5908b283064db48a392f60e03b7eea7f0e12a6dcb5c38689a8b24fcb7eea8407b8aa97afd455cc1a4b9ddfd30dde064cf35ccfef77e8034d494847b305d9598337baf0a386c909e182e67f75bc9f57c58398201a794c4817d3837b580fd4101767ab197e8484e8875ced2d2373bfc30c1a1081cfb74b6dc68046d9cd784d688894cc8d5113b9d1196afa6cb8a28df82361c53300a003000a001600d000202a00800316e91e0f0a564ffd00da939301c9295966a752d0822ff881f14a664ac5b74fdd6f1ad272e38b9576c0b4bcb3f4fa1715eb7f21ef2e4bd77cc0ec89d302fde0a587e1ff9cd07a11de95afdb1b9acdfccce99f743c7765aaad2061437e72fec1559fbf84997b9d05a31708f52d674c775fc479f78485597be3d64d87668f771ea4f2ea9ed6e36783b110b3134f1e12c4bee6e186aaddd9509c5ae859f7affc40c4161df2678c50a5f8a3b43b5586aaea1200dff9e224c42442a9edd96b1302e7ea5f1284ba70357eb185c25963fa94823fc17609e66266a5707ae96df9025570d34c88cc0fcaf6abaef588bded6f29d3f22e03b579db8e1aa0bb317896db3c237893a0323d9c7d69d6f232e807bff04590be0a8fbd74486dd8be2468966ef68ae1117ae4003cff53facbea2eb97aed4c0dcf57b225ed0c3356be957983021d41c14bba73a785a8ca7ab3fe8f0c3889fb2c7a1c96cddced5e6040f2bedcfe41e98442a4683cd01550f49939b4048b432009d36ae8b4997b2bc783546bbc569d8ce64c761937b151fac08ca1a091da6e21a3ea69940c891facb6d6e7ecde2b55a439a56290e2efe33ad89a10dfc97197486f74e900861d0caa6e747a47f9224d33b9d6492d87f4bc42ffd9759247add2f39080e72e8e01ec567dbf83b708d59a351ebec73433a1a19b79e3f3662f3b8ba729afc4a57007e80f044bc4349a880616e71f1c16002b6eb08f9f01a61793b8c133e1f72f1195f30a8a7db4c037cdd850fa1f5014edbb0331863d1692d65bb4039d29d812373d4896f70420bc6a3744cb43d0aa2d5f4f643997ad4fd98cf81081545c2cbfbe90635dae8e75eef2b695d77578390b350fa1fa4b64f226b4be7c54b066db735e4bb027b6e95900091be946d33e0f37ce0337a01e0b001e202d80c8e6bd3d47d89f7585f0b668d5fcdb66a48c0ec63a76ed0766d576f695e1aa12725a40ba549b1bca02a5a9db5038407ecbfa2f05aa8e62f37bbec4ba2c60b43b29d5c3fed2c8ed617cd6165afb60f3696f04baa98647eed8efbb8687523deb0abbb48a85fc20047d12f629d84826dae0a30c7bbcb3bf3787d186d86d9c518718dfcb49cba91be9ce48fda10fec5a78d40037fc7078a23c59e32c8f5cdea85e02e24d989370ff2070ca087a1617fc81867bf855c986b26163deca5bc745f4a25295a061db6d4819d8047cd5545dc66e67ecc24af55fd00deca200ff46dd0c43aa9383ca05e924dff743af420182621c34a3b465cb7b5557d3cef8caa1a8827b1795c1267ada3f2f37a0ec884daccdeb1ee61d61edeb3677d2ebe59f502e99ce9ae652883786c1fe657887824aed13fa852c1e0ee5c4706d583a0d884b1b2ee26a030eb44e9863b5375823f4c12b980dae77dd7c448a0228aea726f4e5a8e11759554f8e6f72e7592acd47a1620dc3883d4af230ca975ca87f03ea13a7569f4aa63687eac28d6dcf83966a546487797c452aaf7d9915a649d01f690bafd8d8558566bad9abfed313e293023d1490207ce971d6d85742566568e1f08a802d70f4f2070acd5e2e8f691b955fce22b59fe94d49bf9817a29c58c74b91a16c5ee50ce7bcff5dd5be05bf250698626f290b9d943fe319d563bdfc712fab57ea2aac38ad57236ccfedc378eae9046b09527d02b5aa5ce8ea38b6b3586e6efa2d7224ab0de5b2f36cdc05d3bf5e6f9ad6921d1671918b8653033828fea71a9bdd92a4175f42c9f634055cc4a89d950e6809350dfe8940b137b3c832672ad4e5664f9ce7213e3848e3f7d5ee88be2e9fc17aed09feda48a3a3a1dc94c9ca45ba0e500f9dbe231e90bec78f91b89336eaf70a90adcca493b67b44c37f8fd6a7f21afd06d6164c8e8af28c726460fd46ef795727b8fb030e5230ba72312dd9abf5ffa5099c1d3d156e1ff43ee649857417fd628c53ee78aa6b8f7fe23fba21da1bebaeaf183803f4dbc7e4cbd9ac0f34a9fc1aa37ea20bf8e78348beadc665d08a469d3d40ed5c307a17140c418ece6fcfb70aa1e43feec6a49762c3504c7769f27679e5fc682a5ecc6749acc043fda3674956f67f1eedaf65b29da8bc899db6d48decb44278c3c0fe17de1b663f4dec396d5d5d1fd352ffdd5426bd2cfd4fe38a026c32314c11deaad8548ab284a6c20abc53952dff633f99fc6163e15778f72314984d5417106034b0a671ceca2ce9a2dba12559014c2039c19c399cc55a4849ab99c565b14b74c0564d2c49f04b910054af2c500516ec8f22538f1456e06a3f88e7ad5f3675bd422fefc03405d594171ed4f9684958f092a8d16c987c84c3862b900a46b92444dfcc108ea44e249a6568dd0b4567ea70f375a5d98c25f4b9ed900bd76eccaebd9c376ea35bd0e073ffb2b7260509f3fbdaa26dfc1c1ca92c0b6f581078d86ada9f1a539a5f36e7f587534fd73078eef37bcc1baa69253fc724d6336c7e261369ba076b2930f708615e32e6e52ee8258a0390d242506cb011c85766f1111b00eeb07a6900b9c6b44f1eed594386cc0301d223a919cec928f18de709dc7906cc6cd4ddda0bafc40e6147381413d96932e97f607c9067754bf0ba9578bb21363e445626ded6c67766f33deae67cdcdc97571deb4eaf1901deeb8f8460ce4e1830b6bd58035c1869862e5e3a55ae10387e61541eaea8d4d6b861e3d4d4b8aadaff30ced160074544f941648e41b72c165d3160538113e7d031de33d89c4aac68c714c13b32c5ac1a8673028090762cc6bb893853f5e2c5dfa13e9ecdd6cf990fad7067c60194b585a11a5e68a767bc8e2c0f0da692d2703e6bf1044a329210dc9335291db43d619a9f94589665dfaf5b59adfefe88004575dc67276a00cd40574ce6c608f0b963d53801030f0323bbef84767988951bf0075d5ae995f86c8affe0bc9898e33bf4496d10e00528e30d669095d0c0bb68ce281ff04fb968bdb2263e8458ed215dc3bb682c916b70531fc9c1e0c157859f4f8bfe18d65ca7676299d2a06a3aeb1e9829a67c904902eca0183f5aaacb1a14ee40898651aab968ef6b3a3a94a6b3e86b8ba60318c08721993225d72840d7e651f5f014d87d6d96e3817a3f9cf899a8a6f40fb39287c8802825df1d0e0cee488399646f1f78a266c8226a73111a6d60b32de2cbf0cba142a640496f90a72b44df20bb41a179ad6011af9bfad62f4f5681104eb31393280558fed38752617d6d43eb2b7a02eb3f50261f4262225a1c49b4bf6ff3f6ebd6abf3457862c8369ed1506f1715d2a901c633af4cf378f757747ed57ddf2c17c445b910d7f6cbf789cb609754c21beedc871aa5ded75ab19f994949d93d06b880788353d908854c21f7607480384b0878fe0d2d4bccfb9ac9d7f65d318f71d4524aa5adc74b49306d200cea4952d60af73b817151bb5119effb0c3bf9429b77c11b19087e89165a951b743e7a9688a7c5a04d813d414c90c78dbdc0425c0bfe9f1293052ae88df1b9a60bab5f7b883ba0c312e234eb026fc0c9869898d982a3ffb2a98da8be4b5308e94e1cf72938a5a7ca0034cf14833731a4959cd18aabb95177cf4c8a092609044b4dd5ccbd49760b6aac03edb8379d53283e0e47e5719b7596db74f6d946d72a6b2b523ad5162aab117132d50ba338c69be3ef4a1f62f50702c18eb62971b8fe5a746beec7b0582c8c127ae69b07f7e6a1f8f4621fb8c1b43b0d157d47f948c1a7b1250c776649ccc9b3310f26110ace85c76a04db80604b6dd06e40000bb691e6a3890966dc50301df01ef9b2c3d2fc4f419f7d72d711bfb9d68963a972859b7972e1a0db3e392b576ca6e23147a232284ecd9494a75bd0e3cdccf63bfc7bec32404e22524557ac751b2dc6aa33cc3929397ec75a8d290c2e0a82527eac5b62c43e3b384ce3eb5dfebeaf86b55fd457161df2004d4c8419a4b3d24becc1f17aa2e288db63b55a96b3416d4a3b2bfc9a6c6bb8c7c1f1cc0a94273161888d07a91227387c33d240b17fb27c8a8071ade1f11b4073e6e869efe27347e52aef54cd3d482767a30b846f6863c0ce6d6c026d835300db594635392134032552511f85b82150d57ab3ceb49c803916248c1ac1cff59c1cc29504c1017d1d7b6789eb8858c545c2f43f8493a4bfd3d9ee9894a5d6cf8e5400f8f97a69414e680891cb32823e8113adb154ad77f02ec3174456121aeb04d15046018c376791155f2e45f02f1c3aedcdb5dc9d3582a3487ccd9a4cc71ed0e22b41079b1b70cc380e5f47cd0d2e627368287346d0efc433084183be92fef49e0cfea93d7ad341f119dd2feec3d19156250b7a31133e812511f34dd00019c5205934c22527f2ada2e0f99d875e3a90a0ca489fa42ccbd732aa3c99939183dc541223fd1839c6601ef004f51495ce73e86b17cd991086d7969f2b2768c1bd5d72c79476d491067443438ec60548737cf538081a99ca9338353b5600db9d4a3ec8b9d0c568965908e8d44f3467aaf654f592c9b10ed32b65e690172fe34828afe285e994f115291396530f30c82e8c283dd8a8f1b60427a928fa3a756c19a676a519c079f0a573d58551edc3dcd2fe69c78cb735dab07decb3b2781e6c41feb573e1f823ee2c13dae7e46444e892cc49ab1808c3867bbd6ca0746be316138a0b7ce661a87d183b083b75b922006caa161c5c08dabc0da06a64a4edc96c4ebd47545f378e40831468b4dc659a272ea6ad15b7e891c090933ef3371d9166b7dd71b0c4281671ae24bce535ddfdb13e43d64d500b1c1ca476a362e189d34ac6ba856c5e6832fd003e6783fed439026c0d2eed3e0df85fed9bc6a4b7f1647f30047706e58dfeadae31d0ba81d5db7c2354bdb3dd4f4dc8c6448a9c3a054a93268f4e1010f08a3ae782dc87c35491ce8f784e11da89c83a80c5d90e3aa1a20cd9e38d46ecdefebd25838864d442d26409117168fc07a14d71813ca0313502a8f3f9935d3c0017e56a77edbd19147296020709ba08fc083f94c955b36539bb7bacb187167b80792442496a923d747b00fdbfa5bb9d3b7bd5e38b1e4ef45092071a792891072e8fa20f96ba8cfb69f2ed636eede3227367a7585b8bc7173c4cc0c3c42368cb8ead345bc02da557cf3d585e7ecf2b9deaec73d19e8414940321241588fa5026fa438788866a0734a6776672d5cc0cec3d5f2edb3b9d65747cc99cd0b1778e4d3303cdf2f27099c3c0325f1f9615e9cc7f1cf378e6ce5c46c7d7352f121daf79915c0900faff7a479cff577247d6d21d64eeb863c97fbd77d0ff9ae560fdbf1c2fff55ced4ffefe424fd57b2cb11b297ecd0c20eabff5bb5a3ea2b931dbf7f3b68a6ba3a12f0f55647ae0e10eac8311bc5af0fd37155e938a2038239103047d57fed73fcc431234e135f75712c90030f39ccc831460edbff5f5d1c69c471260eb2da0b871a38bafce01082838237aa78c3ea1fef468c7778c7bbba07b41456f91e73d58d2c37a6dcd0a00d30da30a18d0dd810830d16d8386283c87f25733f9dc6bc5c6692e293dcdae7eb3db4e7f25549dccfdcedd945eb14cfc0fd3b6919cc6d1228cf3d074d1d058e01e05c8143548d7047b27e6252ef9c797127896bc2916b94fff5b5c61035b27e4113d79d1a55bfa3064e0d9dbe4b6a9e9bea91a601e783b9591a61ea2b0daa3480bebefa0536bdd9fa7f73555fdb74d1a6375c7e466f92fe0d7d34e2fc1f41e38aa28146d2ffde261a05fcffe3805863cc773820d667d470c6ec8c0ecc78f335783985555377a798986165860a5f6fb51a53c5ba9c1913b8d17243839b10dc7c196dbe5a736f6e69cfd645310f962f6b5df456a332b294b15406080878a30e297d66afd2982f9b1b78e0c185b26420800c27c858818c236db4da00d1864a1b2063dc31069c31c08c011c2303366cd818c18604362f31ecf8af3e6c8444b7aed1a8e005c2f4595eb39f48b1d8cd325dbd63b1255deb144669b02471faea7031a64aa7348dc26ad944352eaf2e9b99eb74e5dbda915b2b064e0c08b4cad0eaa245b4c6cd9aab355cd64cad317280ac034c1da0270c337e08a5d5ec3c1b199dd0173eb9e9a22f17ce5d1d1d69595e1cacd67307c7cb474b751f3b0e97b94ba3741b0ac63b5dcf3e8cf92e5896b7e3feffc9ef8401465613ba4d30ac4a30a66660b8bed8fae2ea79367b5fd88efb1117ed5f50f3dc6ad0d06825d550a971a901e285d6bf17545e3cfdd71d2f78febf6661bdce9b84acaaac5e9652175b5f499ca37c971878bbb8aab60baa97eb76d105fdca05b9378f7281f55c503d142e92ec6c01e7ff2bd98d6c81f5ff556a8baaef37975b24556bb7e0f94f03e7d360a569e1abb532998c56b4ab15e9b053afdf1e59d68265794f4ee6232da6ba163c5968fda6ac3d77308b25b2b05f75a6dbe4bc2c807c182361610725710e8b3075ebe0a51a0baaaf583cfdff6f2c8ca071534ddca55434366041834343bfd64ef2bb737ad3717685d68cee4a7c8597285d7e7245ef8a0bac00e3bf623ca356dc6085b642c87f1d476b2d76aa028c2aa8fe2bdff49298ac42c97fede4ace451aaf8af5055a854a17a4685182a6cfaacba29d89822ccff578ec9dcf53ed2f593da296c53e85ce04c0a3752c4f0bf779a16852e8a09b0f2c052022b0b16396ba2e3d010144d401100282e140a3cd1e68584fa130738a184134027ac13449a98a309ad87a242c76ac926e8993767ae3843c3192c678aec1860ebffe9489b68ac7724744442ea2b65800898d062020526624cb8aec6b8c2ba0ae2aab6441f4b44b1b384124be09608a2841925a0fe5f08f338fea12f9779713566855e38a11ae47a9a29591ab34a3d738f98a7338f635fb7424196ee68fa1c7137a374bbecf962a9ab53827b26a9f1d8318f6325252a78592c762b9ada4637895c1240ae7e717ba769b1d8cd8c16334264202103124596a4658931797d348b7ba6be74ece9cc1b553b62ea88a32388bcf93b79df298059000facdeec58dd600582d50e01b42040553d3a72d52ceed72816bbbf608ef69e7b3717ad7c739d5dd4c6a8b975b9b9cfb74d23dc30228d115346d022ba28224c114f8a08abc5fd5aa1201f1b2f771b840b72535fe0b983e70e565cb38bee1eecd22eaf06f5264fdc7f9ad4b8a9e30ec4ba4c56193265a6ca2c512a83001158b59b3b445461222c113a4368e9f41056bf330497ff1f62082021dcfc5fed08c1e5ab105688255508325aff64ccfc3f991ac824fd7fbdc11c19d74e105a40bc1404d54e1049fe7f4c9cffdfe7183463c8e43154ff558f791ae31a03e4ff81c8faaf647680a07a2034c6393176ece0d4f5c962b11b8ec4411c259391b458ec46d27cd8a4c562371f36f300fe872d7e70e107a31f787e681047b94cdce4030b3e3cf940c364852153751800f4c0460f34f470f64079d88287137818daa18d1da8f87ffbca26ad1a197d3a20ee75cc77dd8773b02cc9cb31df1466764cf28e9976180213074c1260b28041327193ee0bdff982bf4ca043153ae8a0c3eddf87cd0cc47a27073239ec7278e500e49f6ed3d2a0e9c5031cf6c081091c64c041041cc627cf5cf0cce932effddae80d40dc3075c3941b10b0818d0d3fecd8e042b52f17f6ed8eebb9027db8675a8bb3e1470d65d400a686df7fb5380a47f14d69808306313450d150c00c59cce065062019f2e842063132ec6428ba3f5d2673ce52580d9e395cde7e06cf1cdd669510552d544955415545a90a12039a188c88414a0c406f7d96c2acb61416f3d11faa712e667bc71a0643171892bce0e685245ed02f8c6fed6e53174c178ab4a0a6053228b430a585a11682b0f086ef1d16645de2e8f244972532f89d2e5e9ecb175cb47009dae14264cb165bacfebb111f36f316ba821d2b58b1020b2b7cb002102d2c6861d2e2da518105159a54e84923052f290420052b296880821d2850f15f2d7f5d9792de0d0507042080140420446507950f54552a8969d5874d9c3b61eb8434279c7042d25f18660a809a9d009c09802e000fec98b08709624cb0b5f2d2ad29edf9eef26ed39789bb8b9ab8290b145978960c4a18a3041e4a482aa1032c6760c1fa7ccdab71b9db5876825004b30461c121536d4c113175c314d554d15712e782d707abbaeec3666ca6c4fab0992dc53025b0d8ee08a8052c03840262f0ff3158b5b6779c0f9b198731dfc9e898e49499e7c691f9fe2ec73829a0ffff73ebf4f2ffffeafd8c72fe2b812931b199f55f7914f36be4668e316d5f03663007c41a87cd608ea4e1cc6f64c6e1cccd7e4da86242583335b3c091254c35739991b29cd546963b4b922c40be92b8842d96c212aa4a48fa2f81ab81c5eaff93b0f0d059504d904bb0289cdafaaf98df5e96d77769309c9af2f23f65a78404734031802c84c0df3fd055cbf2fa3adddba4d966525a7c2b498579a9258ffb792475c05712632cb949e7738b93eabf9e40d59cc2d96cb3dba1994d093a9cb093397b972a77c7fc5696e7ae93df25e07038d6bb5bfef45f494c7b374eaff4ccac6d57b45c09fab9c9a56e9b94c4e5ee7aebb6e6b34a86bf2a0ed35bf378b7dc456d29fd4bf235092c5bca3b73bf7047b17f9df274e69b735c37d04c6bd59da69ae9f17d6c7c29f8824a389803758eaa1dd998e8cd5294ef5e3bc7529787250fe5d37f2d79f6521496e5dd7217d54394ad5abacd282015eb4d827944c209ffff34db8c9ae5ed32fc0bde623159bf49b25b51afdf244ceea3dc940be839d19ba147ebf1f0e4e0e9c0a3c20bdac5f9d1e2acb975596e3df21ca9716ef4a9758647a51d98ffdf25c16ce01db00e47109259ffbdeb8bb38f0fb526dff5ab336de4944e8ace486e8d9c0d39107249ee19d789f05eb943f787edc262b11ce0ce0871070871617022e08258d9c38a15cc0677f98399fcfa6ea71877fe7cb7d77b758cf57e6985b27ae3283466cdf276e0e554679f4ad22324e6714b7d6abf3c9ea53e40ec4387d47dea3649833a1fa94f235aa1ec0f85b2b7d7793a5b91b242046a0d9497af3f6cfab02e77a8a1ff7f4b75de65cc7f18a94a5905032a5c5099a24205c99433a6bc306584294323c019a1cc085446d879d2aa50169fdcf45d12fbf0ad665c4d9de92eeb4b2994d5653316b354f0e9828ac52c151d27b7a9bb519e76788222a58fffeaa27c5639d6c0ddb30fc9d7b30c93fb88c453b7c44c25e65b174ab1752914f0947a86e224c67f9d39dd9c8e38f544c9238a9830cacff24b61bb5cdb6d4ad238b6349334da8d707c7323bc8d71eb12de8cc29baca98da61a6aa7dceabddb556fbd7937172c390e9663c799efdba33d9316c7bb39730a6b29cc7fb1d88dbcb857e66bbe9ae8e8e42269a43e374fe291678ee3b2be92215398df6da6a9af4c400fdc5a0ad37f15818cc63f8ea5b69226119644789a8900544500f2a1cd46c35732e36c08d4d8d4ac086b586a4098ded3b684c412d5925e02fa587f0529bd51a2416904a520509480c2a5c248de84e39d6db216eb274b3cd13d59e0ff13f8f0c9014ece70428493149c3439095ea0595eba7b79c78a6b1e6dcf2151589fd919ee86b41e6859425aa6edfc7f0c86ebbb4cba4a7a4ad2811202560821b83e98e3bbc95613344daa824d9af03071f353d7c7c40bf63149fa0f320182e4e6bf6a1cb5790ec9cb77a4a719920e3dd23afaf2ff7db7cdbecb232323378cc2184d31d2998151cf1da43498d39bc463e79bc4313258f6d059389332a3397797785912b524b6040310b4fe6b4f4fcfad5663022129048101d91ab21e42991559051f50f1726d4ac30f46f80082a233455bc2a2a8a29ea2204a7aceb775bbb414d6d343af92590c8f308615cb12c66e1506f3000b0f82a107503c3820c913611215ea8be2dcc5598a73b90ee074504407be0e8088d80889b2422217be9eba7bea3ae6d61acd3808830314420ea6381832b4c5d0008644181a82440d922d2192a0ffeffdfe28ce71939e3b6871bb8d1b427da8b5f487da20ae772138a110d6bf9097ff1a0a3d09b978fc066585415e8292aed9cd73cf80f8d81a076f2800c180747e041e39e2083f82a4ee51771b977c9bd7d21ded0ceb0b35090cced7cef386e9de83fac25008614d962ecd926eb4898912ed469930d594e8528d22318102657644991835d1924899109293a7334fe711ebffe48d5575b43fd2ff89f3b5bf30df3e170deafb1366b77f7a0fc4bbf087672f853e702ae953f53ff3b17df5e1f97f655d20c7e42c7c51e5a8694467aa8486afa7af7c53a1dbebff9f977bcd850574bd50cd9e4b59d1d4f515d152c4a988920dead8a08c7003321b3411e183c812447e4490febfe3123635654a595efa439bec58e6a89a0db223ee6714dde39dfb0e29c09030e110010c5132a4811e37c21e227aaa843d409ef6b3b4514a8fa6d01f3ac555ab18766948efa057140c2d814285148130833032a0fa7f4a474b61c172f3607ad311030f3058e002362e18407881ed8208849021c4082150421c1823b4404d688196d002a6d0820bc20ab0082be8153c5500840224420ac6841448a180011e2e7882b3232a31ea437ff02e97b49b3956edaba9a976a4c9ba8a58ababb4c955b3e3d854962e9a7b367f2f17ed14c6b7edb69fe58f8ed6065118b5bb7cf99e1569dc03ea7c7de1046e3e9cc08bc98d7c38416f827e811f7c28419cff0f253042825e28c14cdc0ac5abffff99c8e57f168abf2ada44a09ba813c609432d8c9b8464be29b4ffe3a74eb7c390e73fa82fad4ae1ced6ffbf041fee68853b56e14ed5d71d9beb77be2902381170f155262b9ada463257eeecb9b343f0e667101ca0f326d429a383756ea18e90ce030fe8f1c098f0812af505d38d30cb64e469a781588fa10354a103b6d0010dc206daf87f007cd8401a0d50d1400b0d9cd04053d8001006900819a8aa2f1bc451b8586c577335bd6c94beba111285d50e8391e5a6d66673d55e40ac5db41b099a3d77769be33cdea541dd1788024cb840090b38850b1c102a7044a80009a102b250019d8741d9bdd3342ae4f599b6dac2203b0c32250c127ac0411092da010e81680981140199204c008e300123c204aa9ec952582e4c20294c80e7bfd69e3b7b8880508840901001044200688500480300600800a010003e0784718055b52f26fa439f5c354b71101dedab89c95a8bf1aed697cbc4bcfa72f5186c9be7a53897ef0fe3dd2ef3bd9bddd0003d20f8d080364203aa080d20131ad0141a208605c829404c580094f0c71e5f7b13fd9d662c76b33e6b4baac4c992fa863f7ae18f26e18f22e10f0ac28f23fc1dc2ef127e511f73fa78f34fbc3e7afaf83957735298639bb3644e057320e0e3007c70a99dfed0266b852c85551826b70e669f6024bfbd9e3b3bb54da40bc62d138c52db4403fcfae868fb2cce56128d8305d3671ca93849ffb5cab1f535cb81f5726cf9c7c921c611a7963b0e20e29012c70f38848083eaabe5b1d8367376b769af3c0783e5b9437a63cd1b27bce17a230337b2dc38c18d1f371668c34c1bbb366e6cf4c1061998f34c831948ea4b06e99847dfa8738977b4dab9c6baa7cb3b1aad5b6fbbcd5186b1e4a658ec46c7974c26bb15f1749eba4ae62e9bcc5d768ec98b7b38d90b4ed6c3a98263fbafc00ebcc1f935b4fed7a046746d8daaa5dfaf71fb7ff2aad1871a6dfe5f8dad46086af0fc63a9edfb5d60532c7623c96bd338401a574f92b358ec66e2591ab4341a78a3554d7dcd29bc4d9b0de228fabbc03755e11ba537ae8e0619684c8186feafb6fb2cedddc45b771c44c3e88c38ff729dd1cff0c08c2d1c745d9ece52179739ea3203cb0cfb66e8287193e5c6ea9fcc6e7a5fbbedbb7db91b9b797d3d2bb90152fb2ed19441a68c5e19495f06fd100173fc63c90878820010c868830c2c3254f851a23c9d6d647c1b356d826883dbdc360d8c71660c2fff97e6ce265a6f6cccb0d1c286890d0562a0214612627431686234a0e546cb072d520b8916903575ac19c01a14d620bd92d1aed63f60aa313149bad198c48c90826ccff66585e80732a1207d79eec5f7cb0ad122eaa2353c009230f608638e30be84f114c61130f800c30a306000e30a1844d27c31c517515f846a9a08d574351a7841871761bc7812666d3d45027bd9209b750951a35dcdba68474261f4655f41364b47fb52a231eba25d88c2846c76b7836c19ef603d93678d846cf48706d11f6ac44f3b443ff880be5cb1d86ee376e7b1d8cd5294d69ffa53492c952f90e29cee76bd33e6b9cb37a96f173c74a1d445045d24c005125cbc10858b0bb808c2c56fb1660b14b6b8a59993c68e304d99344f698ed21cd1420c2d7cd0c2a605d0ff636dad92ab666db6bc9472a099b9ab66e9685f4a945a1b106b6a97ac925da23fd4a6041b5279575f5fcfcc409d0e7613f3ca7387f9d541d96ea95033e39ec9bbdad2213558624ebbb6487a5792e29c35c546f4977b1d4b658143165ab258ca22812c7e6041c4ffaed669cca7fbf4a9ebb3d95c351af3a1584ce98cc592ffaf4634163c51d0c4f90fd160fda321533b1a9d06628dc656fb4e86e68a2bf20a2ef55783ed25aae48a5e7805fdaf405c2c760bada8590142156b54f1a58a5d154e55204085175464a18257ad893b8ee3f245a3ae198b59badb2e5a5f354bb3c1dc1dc912fb7254bfb7a87e4d199410122a781eeba6d8fa29acc229aa483c4592ff1bd5bb146ebe02b18eead794428aaaaf522491590a200fd35b875184717f19fbee5254bf375914bfbf611421d60b2b0b8bcc7f25b1a8b096604960a9d0a8ee0beaad6732ecbb4b396866992c16d365287e2114c127e0fcbf9ec07aa2ea770f183ef1abb42792c462b7279cb8aa79e704d557276c5f9d7055276e1356df9bf8fdd7739b4d2cf926787a3e03a7d228ddd2992b7e86ea4cd21903a0b906e08275ee66802524135b4c50e193d1e8c8040357695ca1f9aff6cabcfae08a678937d5363d29cbdc97a05aa20325e850828750092c4a24a94a5e58eff2528e8178079c254146125e92302109a1248a98c93223e6ab6dc2516b668a191090d8fa4ade1aad7d4b9133247a40a206246648547044968fe9887dc4ee88080ad04601c014e0cafed19ee9ad1b89d2f96cb2978bdff2cacaf21ef528dde5264e675911f6e59b6ca4a302231d473a8e244d26e36637bb912226dfbfdfd9713afb703afba4f879a4b38fa7b38fcb64b7225cfece5b910e3b9134dc35228ede4658fdcc88a96a499a35e2a9067194b9978c20229b6231d9ef37eb9792b422cc22ac14a153268d32539479a10c07968ea31d6db794c911264f9eb86ab632a92e6a7b103fb14f98d01ffac455b3380546d998e4ec5846c724671199711ce31fc744e8f4c221dcfcff0f613544d5104f43f07c0d85d8aa24e6098146882a2184709181f35fc9585532543adc69bf3d324f644232f4fa82082308aaaf63994d7de61ec4d3fff374ee4100d97a0c1add49eabdf518aaaf24dfbbdd1d637f0c2d331070f0145e02e2ea4320aa2a1053ff0fc4d3ff3b01e17a3170423157525ec4f4fe5f2726898ad991117120945496f7680a939af6dbdc3680cac60f667ea8e187da0faeaf967c59ebf281071f62f001c90707c2b8e1d4e9e5e472da2676da26760aa31406d6431ba49b73301896d23cb8e101061ea2f4b183103b60d9e1680720601000060d982b606c602cf8d2e6cbd5971cbe00bf98ff3e3e58aa0c5edfe5586ac3603152df1d4ddf5ab1b9a5b636cbb3c328df74dcd1be00d5ae83153a78d161b799e84b9b394891430e403958e0058d971fbc287929000702843854f5dbbb4cb72da56467bee948626b39c661c80d5aff7fc3ee06211be8b0c1cd5b5c2c26bb15c5629d7e407fbfd0869c0d3a358c514398b0862e350c551286f90f47031b210d6668508186dfff101a1a98018b19bacc50e5eb8839efd25e5ecdcb4d333020835628830ea10c31c850f4d5be8236d0d4550d5195a5ca15c318313011432e860d609803862560c0c1c0c0e5bb499020e5ed3d9ece96eece1cd69bc4658e5f1d0c162c83e50b48bc90e505272f087121cbdc3bbacf2b5bc9c6c18bebb962734bd9ede482052dc4d1c20d2d3461610e16c2b050020b1bb000a44b01c22e2774f1e1d2e675366229ac1bb13dd3b4a7a5e696c2fd2e792468660da3946faa2487b90b898b932d7a6c59638b9a2d02d8e264853a56c0aabbcc7167b2244fdf6a07ee1586b4bcd1c2464b172d4fb4e8a870c70f2a2c852a8090421cf54561fade9ee9a2f6f573d52cced158497176ec7987b9e5e97ca9de3b5a95c2e6b9353fcd8e83b707afc6e6760a96e535352eaf997f578724ebd8dc4eae221d7632b7d491ec6a1cbcb25b51efb25bd10e67f6dd1b22ca587267e7616ce24e5c96389eeb3e5cea868832df53434439c84d8d8590e4b31c22cafdf4613e44943970731edf4344d9d4badffe0d11e5ce7326ee57f01051c65a9bbc21a2dc4fb31c22ca389eeb9c24ef10513e8788f2ef72a010923c44944ddc71a5b9f91051c662ea8688f22e0fe1424872278788868832ae3c7d4344b93ca5323944944fdeeb4344b963de105136310f87f5ee976796b7e338fe01b7c6e1a86be27c9bd49793e5c63cde71e5d56133981bc27197eb2c15cc54a16c2c86cdcb75960ae5beb393fa52286ba990d804ce64b7a2a87e6f51fd9abf6dca8af4ce26c6923bc692bb4c96a4f3d89535aee47025caeff71be10764a3b165d84ab41b567a048d812fce9b66e6408ea536b6b493c1d2de0b84b2b15854bf375f50dfdc2fb0d3f166caf73571184be636aadfdbedb8c7cb308c77b01d895d514633e35c39a375ebdac4c489765b6272b444a32330cd6e4c966a4ab4b6d4ed91af26fa8a945c94c1b2272aceff88cb5fd6e9cc7fbb97f7776a5add3d58eadd336d6c46ab99e92f47f5cb6fcfccc09c33a770bf3a9d9b4499f9a8a7a82424504102d557acf78c840c721699cb07f57d41599b92a5e2a21c2be5a7cfaeef6d7deda1f9ef55fd7ab6b35f1dce057baedef3f8cdfa7f1e99caa3faffcab3ff95b7e4bf8a3c9dff17776f7ed7b41b773d580d9e0143f90a041494548d90783ae736ff515a7905527eb1ce5076bb475f2e2b927890498864183289fcff6a84448325cedcecd9c5dba58f1ebba575ea2ad99ec72051e7842e8c4ed44d904b53cddb67d602b1ee3adf7acdbb73622ace776adfa9c5dbe526b93f360c6b8275e1bec059e19e7042ac6461e58c951e30bfa8505a6969de71d439868456116a0050b52a7d8855e254d1a14a09558a50f182ca1154aa7cddbcebebb4badb8db8974ce8c94d6cc79e477d72ccad796e71ca11e21415c42948532818018dbeb8d4970c8eb080f894c49395a71f296048a9bf0e8542599cf93be98e4999f91e8fe0deed37ab525e4e7a38b989e2459420a230450921ca04e2ad0af12680db78b340bc01694aa2c98ad85484290b97386c6e1c2e868496a54f4aed3cfbf8dc6a4b3e3e4386d40d3429edb9ee97ab48879d38362fee4c404498238255b9f9a637dfdd4c04aaff1ad4358940e4ff83b960cebcb8d3992d8eed8bcd494d4e4dccd79e9981dbc49ad43bb779335aef14a6789b1788758d4b2d49b5966fba1467a9ccd20baa0a952a2e1fc7becd77afbdb4c4f3d55a7a2989315752bafaffaaf4fbda332731b72d51d2f9af50b2a090f99f9308a50765498502646b2d7d323d21f364ea959e24119fd0cab1b99da2f4e54eacb6e8848b139f131e7b2f2ea948eb342f2fd27e220db869409292acfe6b52af2cf72f842842582184104200a1091c4da2109bb8f05f5ffa02b18bdad7d4f5b96a96d298bd348a4da6262f2672884cb4b699b7be3491c91626529814bda2f4e5389d7d4858482820e99018f8af51faf2bc39c612a5af7991c423258eaa8eecefb652946874c68804233a93f3ff412fcea818ed2b88627ee998631607afa5b05b734a1d072f0ef39b9bd987aa32ee1e904a153ae35982f55f73bf4becff9913972cd9b6253a48342a82c0e6ff65b2de6f7924db42d68228db1fc08ade145151e4138b9a8afed25e9677ecf82a794154b24509d0ffd77d448c85896931e6daa2075b3c30c1830d92b4e1bb031fc40e70073a446210392112e54054428ea8894804fbdfbfda2d7596c26a59ee18a9effed5b2dc3f3af67bc4d3481c407971688c212e434890e481240b24412444840e206445ebf5e532854ea10cc4a03541565f839a7a262dcf6210d5ff2cc8f6ff391168ebdf5e8ead080444f55fe91581aa1c89e3089afffad219783919e4d7d467b9a5ee91a96d1e713a52b134ce2d8995cb57a0ad1689b03e6056db0ceaeb824dfdd77e05935784b96ab97754cf9c8ae31a2394ceb3d11c5db573acdbe2cfd6bf5c74f663f5ffe20f95167f6cfd3cf2e3f249f37a5d26a20f113e4fff7afb344f8b3e406ff3f9e7fbf562f28a4074a1a93c4d45579877f5a0acc65044174f2d6f9f1981639a7bf75f0d887591208a9c453610379043dca0890db46c30f424d67993a0f1af168b592a3e35d31f4a49736bead371cf14897041248b480436248c2154ffb5a7a7e3a82e45af7939e517d75766aab9781687040d59a027eb2b25b3a53ebb6dc92cf61cd1404b832fa20651a20656340842dff8ffba4dfac2e11c6e9bb8a89c897d9857a1b1dcfb578362d8bca645caa04d062f881910612007063a88184c61b0c3a047c4c0820bacf8ff0f5ebca0cb05572ea0a2103942d87411b2a3512c79dc3bf6c2bd3ca4f2285d8ea2243633def1bdbbd8d7bb49a3b51bb14214b0c08c68410dffb5dc9b52d102a50af0a8c0870a441029d8a35a8b710772dccf600efb76a74714740a9c8814bc441e3e78c21079b2441eaa0a65b1addcfbd7cbbd6d531737757d306aeecd34c19c09d64ca0459c60e8ffcbd24742594b652483b8b7f53612253842022ba20440a2688688a5b099cf2b9d16659769751cc7308e182a218655621882b8a3c70e13e24e0ce2ce0a3b48c41d0d2248f32486e1da35597d8ee890fa1a39d6510e411f22044188105481800251e78da86346d4b1a2ce4dd4f9213e50467cc0870754b0b81fae64d4b40a65a94f858ac52c15da7daaef42a13ebbd3dd7de810f10110c4079644076a70a0141db0406ca08fff11caf672ef1f2df7fe755c2779bfa54c762bba9c49cfcc527ab965b7a2cb316e6b8e4b1e6f9b38736f2e2b2a37c7e6353bc7220329880cc082b911e31dcd7626932d90c5f3e20256e20253ffe20274b6801515b8caa2025bea2d881931c896204d82182002c1128178f9aab798801e62024d245042024e0944202220c78c8840504420c97f0fd5992cd28f68de4bf580350e10e200de01444403f830e08c015844034ad100971a05882940271640452c80e7ff672ffe58f3238cf843ff18c57f437c23c4078acf81f83a5f3766d273fd9dc192674aea2b85f671c11c3ce6383107cb9c2a73329813f2a1c587152af061850f217b68fdbf6e6c528bd03eda6c2dbfb406617b04d1830c3d5808f540410f233d84e441471e0408f3a8210fa63c20c0830d1e5ff050c19cc23d3ddba419caeab214ce45a96327f5b9e34568b0dc5ab395668bcb5693ad5702da4840990470a9346629df774c71c70d77ac7007d21d3f42397bc8b9928345ce939c0be4e8d8f1851d63fe6bd05218ed99f5d06de619ad3db3a7278a6f60b6ed7293a0295360a4632c265ba2635291ec725c47b00ed7ffd77be940d32fc7744c753a9efee9006e4dc93a879939707324fd575a350eb135a8349545d458aa19000000500053100000181c24140dc8a462c1644cde3e14000051b47468dd9836d1649d54c81863600600004000400002775171a3e24156cadb10d96309df5fbd43f80c71507817fd707ca2c8e3c1d2c485cc87081f139026bb817ed313d887db583c563a9ebaeb5bb7465f92774d263f8d759f1c612a3d75c6e395969eef524e684d403039fded06032b9b8b89b1ad7039aab78d2d2f47692ba1e789b1137e6601a9be07bc8d895542b0971e36218b2f75be69489b84c1fe475eb70f9ee2f68d428f3f249d366b89df4583a6d4774b7559d03c9bdb48ed737f74ed6c625826a0006c7c21caf0724b86ad3fb229fceec5e1ccf3fd0df78cdd3e6eee6eedee483f8216b7e4a2dd6eeaaed2f7bd3bebe526a482e5357980d1cfe321c504e645716361f4761349d1818a174b657cc657a067877a3280ac2c82cf37b81a2e08cc74e7f20c4ebb55bda8aa05e39fb1f2864b60b424dfe96a000b7f94d84597095048a0101dd28cf37f4742b1717e4d0aa7bb14e47820446962155d27e3b9561af6014400add1ce74c2806852823a07782bd282522c52989188714c5fb60f17be64700c3be4e71ad3e92bd352738e569f5248fde93fbd02fbb45553ea760903b68a5da9b11740df2c9d7b33225a60b18279bc82351f7f7f974a1683920d4c981b42fe32395ec054b2949f6a39a3ace33cd21b2e356352a0a25343403a9037cc39166fedfce56c18b2b50eb9f6b8dbda5265a0e0bf4ce92e6aa0aa908736a8a57602b8a15f037d8171cc42a87db3094f7166539737a90b43a84344dc96d1377cfbc4f181761ebf30d4837fed1d28f2f0b81a23b59bce23fb8fa34a3fc048d61b171cf93ca840a4c69395ac7b4fad4e17b519e9ef94c1f3936f56fdff8441122b00ac6e16696e10909e7aa894156852d1bb91404fde7f1cd93c394617050641433236c309b9e31659f93dbaf9c143e3626036d248dc84a010620012796412a8d6661c6ca4a8a81a74b82e95e32a80f3d7694369ece556fe2148be8bcb1f3e2d4ee2f78501e83e229e331eae567489bfc48ead1b6133be1b837cc62c0e0de3a38a284ae8509a241e0ea0ca3d65b2d287c6e38d0ff6c5cfcba3375cd7cfc2f82f6f26596c900524ef851710d66d278b03d4903f76b00ddaa26ef7fcb507a889d598e915baa8ed2bd0fc63e68aa2cc46d96b8fb92625caa608a42be71154f9808222a06d62f4941b4907235e5e0cb6fc428297fa84e3914fcb8aa21f35eb1f4e3efa48d33287e4b1c525426070d5ecbf09e71ddd2051dd77b7b0d7dedda1b2cbcbb55ab504970ea21c04635b7f6d536c203f3d8a4cbead514548e382222dfd9723479271b35cf7887374495ba6a099ee386472ff770698215588c601b4dba6a8c981967ffbda66cc348bb3639bb1bc38bc1bdd7048808057c1bf480403ad0644329cec5701f92c861dc4ceae7755e8b3596465cfcc913fdfda9e5c348a921d980833cb0aabf0d0af2d233ac23c7f743e86033314c91709ba6756b364ed92bdc9d52003e54953eb85354500b251666f971fad0521fd20812e405fccd99035add92a61782ae43bb93141232aad6cfd8ad71087158ccae296b54447278c839e8536f0e86a55e2ad13e7c86df20524abe12cdccd9d45b144ef69afb4d6b1e0d50b99ddbd7187b808395f77aeb60e6f042c8fd21e501147273969bc6b4abc81744d90005d96c48a4b57bf18b91fe7c1d7a5258fa78b2879d15b9c6491b336d8b5b549573ee5c65930f4049f751bb77edb9bbf836980e34317c2314a1abcec9b11beb4209ee15943d8bff61b0b6ade42d640bd7ebe44ad29394855fc3fe6b19ac91b4d55002d62887bfe83fc7f431fbc3b5354f67e420a455fae7b4f572cd23e2d8a22cdce13765a36c309f4356977becf04e0cd9ebe9644c939131b62502b0676e4b20201db11e5fa8758c84a2f739c6ac1da4462c11ea5d729b9e3a3109166e6da97a247816fd0dfbc9a49a4f8996cb12f007c272c1dfe870ed836a4bf793e00ec2728fc7f1ed814d35f9384dbb6fcad0cb8eb2adfcc713b22b5b6f167a81ec187402bdb1ba080fe264942221a2b9067714f6e71dc0067b42e36b36ca7fb45cccb6739f75c0773b45bfcc395d0435eb8f8a125d7f1cdd1897982fb7c2b5a18919b3ee6e490276fcfee9631ed40c5ce0d822c663d996d823da258e0405d234af66b570e482f394e9590dc7a45afaff02e5bb4531429ab89aff67d69a376a83b935a9ca5b8157b7549994e1a09b8ab34bc5f63f2414f08829a599f4578deee8e044d68354970f442b13b9614a4ae3b7781411e13ad77fd496a91b72490e754019a7c78aa39782a073d3cb5efefa9d2f2d5905dc7873499e343c3e61aed628795593bbb4facec798bc7a4496508809e61e024b2b314f4fb35136312fad3a05e615c1b9eb706361f10d2bd919d7f1ba457288068e3db68386d44569b480dea4d8cb86702440cef39b136f09491398c313d2d8696d268582cadc5ad2df883eaec5d9a6c72eecac5fa2358745988923db4f7d585aec6ecafd99015b41fd0c2e393d81f081665b89158d5f39410969ae340685babfbfc2049d2fae3a3e76fcad774217616dc9d659dd46ea19b43661991db2d885eeca836e411f7ff34c034102160019910e831b48f54bc834c59e19962e952f6fc48077fcedcd2695dd280a687bfd30200ee1aa37c8448a0c56592691e48d459834139723e0c74c8bcb420a655a3e8d01f27b54ca76e627b8df283d809da70153c5483cb63e9801cad5057d7d1e846b512f6efcf1fd9b8a00210dd8cfb2af26287610d0e7096d6229e759c87545c77429723e4b9b1cdba6d30f74b67e46d8d73ecb0640e483c4007b1cca60ce7618a8bcfd7ca907090b16dd427fcfc114f471bfa1d20c8faa9c1d37bc9d01bc9e746a58ea6304698e5c5cc9faaefdb5e0af3de64831f63bf00451cc1bc4b68fcabfe1c62e029ccf3f9d82edafed7b461a2adfd28594cdc9a18e47af01d18fc6c6154cbf9acecec1d4d94be7769cc7f00cb771f49cf6463ce00169763628784172b58858c64f2ae55561ac55372d9c8ed559df71f0d0449495c81f12b77fb3391c31cc647d091c59c437387d3e0c54b4a1d3cc93572d6f4b942e41c83f67f8ef69f30fbf3216acf155facbed553df092374dd21211adf1331561c033774120c487b4f8880993e186ceb209eb0f2ac68b47612e1b83851d7c0ce61f90e669b2e3e364659c236871ff0ff5ac45d51904d0bb8515b40d6cd2c2f9acd54917957ac0dfbe845c9e9c915a753c5c33ce3b6ca9297faf3d6c8f896c91c1a58492b36934b3ad9d0e29898e4353a958e67a98c8c5a5d29c5db1f9f28ad797380010600a83d13176b3800a36f1776b94916e4d9b2b86448587a40c896b1e266c4648e2ba99176ba86e42b251df4d19ac48921d7283c834d02be558bf76b9c06ea22fcfaf6a8f7a3540c46f73a7c2d19dbde919c49c1f6f5ffb217fa421fe838393009cda0d358f2bc242293f1c0268eb2cc6a534196bdcee5c5358662e5ab1db400c9441768d050bd24ecbaa0f426f437e03b94ace7124ac2d667106dc9df2e05923249cd50e949efa52ce557ad99edef7ddc48831be8926dbb9d63299e09a350d4acdcf7034745fb19310180b52485c68306ef97d896f010ddc39012ed162bc5c07e5b47d2fa307974227a5cd3ea77b1351e0913563034fea7429e3f0aafe13a349e4ea1412db71df8200fd5fb4db617c34b842bc1b500a60c53153893bc8c74039ba79115da275b0d949c9f4c90521b9337972eaccc7b2c007aa5416e163665b51d3b22c405e61bbeb2465196277331caef2092e6f155e8b4ca1a8fcab8415f3d73096f305cc4502bbab3503cd0e90acebbffee6f1b5d5cb12c8c37cabe37e2ed497a4c9ca3475bb2a4c285638979b04adc41ea3405475cbfd9afb140bd7f899c855547b425dee813824694dde1038c6f22ab98cf4ae5923ec5709428eb27eaa863dbe40fc4ff665edfad82effbb1e811a7fd2141a9c46849f63063689aae5ade2d51bcdda66e335856cce2e66b1385d372393463290cc4e9a9490bf7843ab38953d2ff9437eb9d27690c0cc955b9ba451ca57a628ccf47709ae496133c905cd751494ee9a4e68ac14618c51c11cb1e6e0b83a90547542073e137bffb261c59008a99f9ed693070adebacb8409c5c4bdbd3d27303f4477e9e2f4a1cd4671b03836165a42d5a18b7855600ce7183eababb78327e1b097d06df400a421d9cb6afc5d5c9766e943f4f2c867d4761d840f4b4e09d6cbd0ee7ff11a93d81a7b4103dbcb8639c4e2bbc198667a3af4413f0fecf1b029d757b60d9da83476a2ae7d4a688b5c1b249e3b0ac94f3b97d71b690398ee64a46bda2edcb2981d1dcd227cf429693721c9efa560e9fea303ae39671d1df9132ad776aabecfb7866319c38cc495ae9568c30100ec09e02cc67353e868ae452f3f8683fb73a630ab79ae08dabb7ff4f65b683e4423ba5fa1abd67126c0346e2c80c77f0f634ac16539f8a994cce7ea77e7f3d21cf518b871277fe1923105b6bb9191e50595aa81c0604eb3c57bc109927363ee99f2c4db9ddb7af2e2bb166aeacd4857962b18ec4e1af6c5398b7e2c97edcd1853dd1798e86900a7406967194318f950c7115b2277443e2daa6d1c028af35fb91d7268857122687b0fe6a8b21c962fb1e371d6406b390313748dffac8394ef2be8e7a8a5409de1790880aba4d90b33f9974800c05cda41e21d43ac572d5d9dc1d3d301064d20acf204e6d44d8a6217b63a8c7b095cfb5088f12e2821d6d61719d13a82374022551b8f6bcc56c1b502b84eb9fa0deb0814eedc76308e83c3d2e493c2cbe96d7ebde5269f457f3511e7efe6aba1db8e6b1a106ef1f64c5845d9d7cf099e5872bfae2c9fb752a057057997ca3a0a203e12308d67fbfd4ae34dd9bd03c9fbc1184e3e866f7101569c4357808242985f5d793e9ad5cf91b69be5817c2f2d4c8f01757861d62f4bbef7bdd043f8f8fe9ee1fa8477ac2d2da62ee0cad16e9aa10ea8a989c909c7c5e48dfd7d5e500721d07a527f4f860ea3c92b3a65223646357bde4d1d111ac009c065ff50e68d6974e32227193e0c0dedad1e35853ef3761b1e029babd9cca9af399e0061643c11a65c42af84f2f8e3f38aa69d045a7f38cb18302b5817eabe5248a8b8a0d5d8a3950abe3ff237c4de42d16d2c614fafe6f3007bdca9ec0d2e57a4e82870a06bb56bbf3c1329535beb2c1773e0f347607daf93c2771108fe77073d8abf04379c17bdd89f3fa896df8890853ead950a3b40b1d200d72ae57b999796a75e3aea310c8a78e7533ce3b9467b9b0ccdef0c506464d5d1c46b7ea7a4d48d721375d5eb1b055dcd6c676562305f499efe83791985f600f54dc91ea511b0a93baa7e73b856ed206e6321a3df0386b1367e058292818868e4bea19f844bd6f6eaf38896e568190de16a487086bf3ac279e482b0913d5a2b4c7d0641bc48ce918d1ba6bb1c7e9c0d58a0eabad985efa2cfe7b04bb23f5d800d0de0ffa4c6c5e5061c192d4673fb5dcbee650739aecc817c64792f6ed9a97705d75d475b0a908aade2948910662f85acba260d617191717bd2f8326d92fb1728e93198d130b6006c25fda320d32351f912f832869a9a6c31af5d04a038160b194a0480f692195dd948f07b848ff42b3c378898d22b7e33d4f8e8f29a083ba70a5ba7be9372ae7acf753e42f5ab88762d25e0810f847d7b84e5f73506562bb5c6d815c74b84de897e8989dfef4330a7c0d06ab5a52fa8fad16913d2df9cea779553a35c2e7f41c6177108fb31fb5bf953128530674d1d37c8ad88b7c60a051af84d8437a9491fc8ca0311e78646af93fd18521f3e609387b117d4c0207c35d0efcdea697cc0ef4c96bc7cd639c9fd21619209f11cf635a92e17fe39df11cd2a0f68d87eacd8967e5a0feda6e7a7b18ebeebbd82e5521f98aa9f50763b981c3551b22762ae161805a1223a55c5e919f2f36b4272c0bc229fe64781a91a46e0e96256754f077e090855b316b7c336060cdea31c33433a5b1c6ca7fb66a2ed8ab44a8c6e5196458403d36aa07d1c7f0d678c632fa7993b20b0880d58e611b801154ded240fe3e649f264bfb04bf377d89d5da43e14b07e113b3770fbe69a3ffcc863133f826537c62f555f0ce22ffc9d84326de3592ff116399453711b6978da7dfa4105a5c2e7518740990d91f297002b88718ec311988a157922a7407476925ff325bffea886fe94e54bf5a993d0d9ea1c02c81682ee1d388a08579d75de603b53f57e78d5d53bf0c5927ad7891c9921482b5bf6892c995e6810109197b42db6f3e2e72f8563c989a865370f050b332209c40aa0b273d0c923bc440dd7c1708ddb98e1f1a3174e25ca082a96607728e86c97b666696c4e776430f37f5a754ea6f5a73648f2462bebaa5167c23ba80065d244cdb18b10fef334d91d1969d8ca27721b25e90b1233e8b44b8e4894912bb698b6d680d5f77dd81c4c02065586147f1be5384c36824d550cce5194cbf1c42d3925fd0cd2e816ea209b06bccbfa55dac3e78ca5a4be0e3f33181e3c6fd9300d9031608c73927e06285bb8075bd946a51785dd4a9c6d5d7e7e32768070797707b9a70d45d5d2c74f1f708d0a69eb50c231a501c6702639cca7f5c09da976f8d914230ab5c5ff788fc1e4a39481a49d737408d115a534c344c763d94b459879174a05bc8240b439153b372f0d516f89da1a804df2b65a78611da9d1ff922afccab36b82f6eb30e6f850998fd335afb9d29e4ece7709b55c7f7bf8f5413a0cd236e3edd0187dbcdc9455eb35bf0bc69d785d5141e92c219ca33d09d64b4c14ed0d58d9f02233dd4ced9a379993c0c8541c35c79c1af4e6c60982dd8cb55899a787534a69c72c00822bd0df36734805a65419fa87c7103fad03264e11307d06e614c1a34aa0884ad300dd5e5308000caa68e812df133ca6f14a8abaa185ede3ebb59041f6c566af611c46d5fb7d2b24b1e0b92d9ac577007b8489e6f41927a38fbd5b8690328c73b8a4e65b65c3c01c205eabdc3207c167e07b08b88a2b7b26b01fd1725cff7774df0920f5b76275d8fa2e0d9272494260db19a2304c01aa9ecbf699a21e7c3f6532cbd6ce926fbd4ba2c3a58be347304dc97f99783cd174f42e53eb8a9e2fee9dd2a995ba554d7d4e8ea475725390888cbb95119d9012b2eeb8e64f749d9783c902ea548d2bb17160b0e9c34ed05e1af04ae9403d7cba9588b0f8eb7684796e4b04883fb5580f6142338d970303248f4af43fcefeaa420f86b4630fcda9d6bef478c15dfc0fb2e733f16cc0848d3660bffc3443543c2c15ddd13e7202260bf07e6070f035cf4de1bc80a33ac174f870f8c009c64a57c4d4e34a3a62f5af16289c8a69550c1bbc7ece2631cac9a2001bc1400549b4138a051ce3c0f654e360fd2c0dabcd50b0b7da3bdc421fd7434b0f43cacc242138f5486a040a41b465cdee75ee1b27733260234e164e529d10e187ab413793dbc35b3a1979ecaf13528bc6e65e358dcb53c725de6e3d370aa58e04f6993298b87adb167187715659b2e85f755dd0648a3f3f5f009cffd81647764e134f8f32cbafdd79b00764ec24b02dc02c582b23fad89c39ac54d531bece98929bbfbce8c08f4adfc8ebc0911c5988cb0df10d8feb99ae89fa8f77e0fad3a8228d2299db807a695632a047f8457efb1e535990aeac15be2d6573cdaf0f0cea54c7f0baf0d5280815c605470119add45f0ba2cfe6af5c18ac63c75377891f458381eb3a14cb4ba9ccc397c67584e41a7ec17ee2207d48dfff7060125a1006fb1a2096e20e94d75eaaa0771c567ce85aec684b405ce200cf429c702508b899c74eecdc95ee08005d0fe2feee3b15b07728535421f9ca401a27336aec80bfceb6872f72865b07b0f343909ee7457d4a96142dceeb161a727d23811c0b71b986bcd54d17977f1996eb6b293b896232303aa8200c3f277cc5a79a465e127647e49134bd2b47b7c6fd1e2ff863dcd29d77363deb15f098ba98d8ab34060ee7b3ee1e16edb09f83953dc9d3202763e673a0653aae4a5006e39edc963045ecdbab1495c2e44b36dcab62694f35b99f3552ad5d3af077dc0f040ad48206eaaaaf22020dce7f33153a31f1f1e2cd3d6a85973b15f320ea047616cf67d2007e6d0e8b924fb660fef00f2451bcedaf860e5389474abdd8f06e40c1a7ffdeb8dc6c189c1fb6c74ee7a0c726b4f86cbc39ff1cdb88584be247c17e4bee8a1baeb82fff42c1fccc3efc857047e721870338954cc167473ecec22a7552e3b16b7d7da5b82189fdff58b3598b892907ffb496f919a471590b04106454b22e490a7027e449ae3379193e4670891575e9dc661796a7063ff789883ffe7b9962bb92f4ed55520a14acdfe49ee9c563fcb1cbc006097f2a2da17edc7a04e07d17137f3e483d29f0ea420143c9c43ed173f52a292aa941c611f9f579e0282501e701c5fdee386bfa7e5b12ed140bd15be54913c94d358d95405402762f0c4eab6e2f2c21baba3bb722887ef0ad7eb5157ff07b865ee2525f27263101adcfd8e7c9369304c8c2bfb4de777bff0387e31361c5e28a65ebd86ab835e89b22c67afab7c76e7be5fa8cc787e40348607c3e0b3cc394e1453d098fb14c9e8973f5501d72077cb3aa1f82178b461a087430fc62f06163ff6411eabd8d7dd19f4c6ba6c274b1b1b016edad5c87f6fd16ed7267d70777ef19b19d8d40ad560d5e1739eab8cae4792a7b5d89812ad88e7c475a8459d69262be10ec1f91c3af1d67fe77a6fbf7f0d4ebe6ef0bd166de7dee03761812e2b1858fb944d7cd5a22edb8fa5ff83fa1be9c5ba8b8e549cec2505023d195758713388d22c7035dcd4354d7aa33767aebb2a2c587b7f42f70e337791afd9547af8fc8c4bbb3f80d2f36a96502ed3e965fdcb566fea2c8a2253690b9f5a3801996887af6e44c10340469a7e4149407de51ee2e9eab83fc854b5612c13d0140c2e4d01afa309a353d007e0442283e07ad6fbbdf2507d9cf056ab1de56c919c71823a43ad718a1e45221dc2ebc43873db969d036e0950df683d9d3273c08647089cabac97e551e043fc87ee82edca133daf03c00070603a4fa91280d6c0366f347795597334fb68ea882d87ee1f87f4d1a9f638429c3a75637d78262143b10903ac4123bb1e8e36a56473c15d490fa19066d17e9c45aac5c838cf4c8ec5ca20208553c672184ab065802d4c959d3c778ab4c5d1bd93f5fab5fb0e5cd2d12dd21cffa1c1bee77e29327577669c64f005ef8fd06d8c3a44e0f032b233c7fa3fecc6bf2ecaaf87948e82476a97ff2ed5a830af9df585ce74a097dbf04e306ddda7926c2217eea6c8daaa92a33f95799daace88acc0b9aebf0f85cde627d9a58c003821ceaaa52245c59670bf8de4190600af0283af4d86cfe59a8c7f90edddfbd9ae15ea04021f3c0b8bf9259f6cb9a454847708755e370094511e715aea178c5d3ce2fb3afeb3456f891cebce1ea3e768a49afc4a7f6828d8036513cd1bbb5d07a327af24af4c7d8daa816a3c1a9be94ed60f70235f30ae3f3df0e469eddcce81bd2c8473c1f8d3ef02ee93f93367e53ecd005cf440cabf0203c08b06cc135c0e892ea47e571af10615223522013ea483b35137b6c8eb492810c6fc6d2c2dede45c22ffde58a3902cc5be61cd95100c3ac9ccebce100a3ec1c315640abbddae20eecdcb04ffbc9c58b7be1c60a40ddb15cd640b1b048b6cb462bb1c0b055d29a27d1391f7edb4264bcf52c7c9a9d4ee00e8da32a0b6876e82b11f41430552363e3eae4c4435218d95a09deb2f576df0efd71b34410df5a81ab656d6508814cb4702e1b0ede6fd08c03d1f1499ad31e046632a0472d03297d86444c3c6bfda8a4b8bdb748c683edd71551b1e28135637e064f00dd2720206e471813d9908fc5303d37916a54fe4509790419baebc00d33afa6aa037eee719b95f8eafe38f0f1267393fe60c9c8a1ed911a1443522edef65e21a9f4d90068a878b7855c1d50fad3ad9881bfc14ca70c4602352e27fc991ece95b7be797939d6f2e79e94c41bb70c2cc7e7433d907dd7ef882f38ceec0a43e648b072b19efafb84e60be3f5ee478c235385ee3fa59a1d6a324781a1b39fc67c9d1a956163e20c2fb873e057ca0703f6e1dde9acb60bcd2668b9b4a21763feac09557918ffc4f4eea0a4f0d9cc44eed83429381337c8712718eb9a927ce6cc0293bf836cae45d535a45159196d4839700f6cc604f3d2d8c584c0155e30cda8c8c01717fa0c7e5d9984c4c14856f7767a6f730538e669de833de781c1b271fb9a232d15700145b955f363550b57452a63da8dcbe91e46a1216106cbd184e90eee6ff0d15acc2b25ec79a74aa4f71d6b3a5005fedac263db7ec10f5071ab64bca6b59eeab8e7e035c4ee68396e79c28f6cd911798ed0d31fe99f342178078322a49077089b0b03864874150a5c689190c20ff20d2022679d1fa1c6d34f62e26ce115cdf2599e9cee7c1793f4dff71a43a40a56effee4d863233d7a6241d1fa82146042cca02ea20d2d1602c6eae8f158c5a0a60be1360dcfee05a38f682a99749ef3ddbacad74a9d813a1248a4bfca18e199e164b58a4e919862a287aa91a63fc07f7a07e88d0bee25109f496f98955ca29a89738fb5b3c33a96f1d3ad6b2ee027dc4170a339cc9b6e0713b9bd628f098fbbfbcb1a3ddb9436628998e592f72548ca331cc564ef923f4d834cccecf4595071a8c235c1fde16ed8e6823b9824be2b7f030fbe256b0dc35f76eabea2e804fee81c82dc47e262610a7697e2a5a4bce754320d4d44c30859d1f49b848b0403ac81841b9c660718ca144d3a039da6e17c39375be90011f2099cefaa288f46623856d77f6587688b8e38a1a8f5c204ae9c2d88ad622e89ba77061484eb283625d18aee09a88d852db82dec238b5803ba6a9309297e2a37ec457b29273b2be5218752f5f77c2b86e755a05613055ea2625b407018b5d088328b92e49408263f8bc8fe4ff2ac70b06d4006baf4c7afb1f389453574bd72162b12cc8e7f173226d165af8735e024371b730f4674135335e63f0b25d131294b529d9db250a9fb84b16c7373a116b8c16efc1ef63525e89a9063e09d8547781d7a24126d23bf138049d621daed9bf5b70e522760cf5816f23b2031adadd6ed6378beb1d596f3f7b6c509eb57dec36c6f78decc1c7b4c1eec68941ecf57ea3d5881ceb14220d00d948b310348ffdc03557fb6184df31324ff5504c56f22f8c056410f922c32bc1990d1ada45aea152e8a52aa58d591da4c0562c8aee15182408f5172f8f5a35675a6776610ea051930b52b39d65da2d341b02fbcd7e84508cfd7e0dcbb5b4054b936d41c91948b89c00194ac8f23d087ecdb4a508ac1265422edfdaae7bd768cc67fd19fe75b43f70a8bbd913895cd59899e4f020a1c64ef2ac2cbe46064637873425782790c2e5ced53bbe8e83ac5f56f15de66ece8a748f15402bb7d32bd70246106f97f728e36baeba596034846168b81921bf21f90e3ade509bf411c9576281979e61851f85e5afbb1b5f591b0269582223dd51b073ae5106852a4dfac27e953fbb4fb873668aa015113c7613d56bc3524fcf02918f95fbba16661198676028f3a0229a68883a6ac6c8d8c77e878f73fbf84f54c5ec37bfb9f25ff0aa00f76f9a96217d21a23a3d627edeff01e39000f644e6bfffd25fd1cee1f1bdc3de6bfd0010708781f28d2bb777de718c22a6c5e4c6189a11719d2f24a18b55fbd02b3db45270d4488aec686b5f6a7afdc2d132832f6b947cb6250a0e82204d31e3933ea780b8e90b4238a3ceb483b0ffe4760b0abb85931f74f3211c33cf486ad7c7b8cd71d6662479c8487b6b92bcbd9c81b3236b497911f132d8566d0a07de0f8e6cb0ee0c80bbce90dd211bd0439d123506de95391bdf26379c641d8e577281d7719aea805e5a0661576ab69740fdb5b5f578eb5ffa614b586c7bbacd7269afa70ee2abeb92ba1587c5c923aac7b87512589eb92d1bb1404c9d1bd2b4c20da610242f4043a7628c21827249f6ad51ec799c2018f0a6c3c0b501008f14ee6942152f843569b1ed03d866d0945e3b9fb819e4438006513695915a3035e1158dda51a6393f7be1ad08ef88673315be260c55a88e24a89dda96c308afa221e0a10d08d933006272088972d2dda687f27a873311c30a3de6573ae3561e996e4b0b688e29e6f40b32f9481d70fc4074c975633cfc71a966f0eae175e2e74e3026fe4c9bdb1ad0734061de6d7e0c52af17648faff0b7b861f19a8c50e6fee04514f4e4d2095157f5366ab71a00495ac353a5ac6f6b0b0c56baf80080356e16786042c5e72dd5e29da871dec73e6a1f68148c7117a84bf6cd00116143fc865b4d431edd449b5a2963e9748e6d88908f7909318cd48fbaa201dc64b87d518596ec00372cd3638f7fc326b623a1922db1a59d77be8148428281aeb063b60dc8e0a91df3f2a49d2304575a5f6d9c2cb8e7baaecbf6f5961d3117436a829a43620b859a8ac4de1f2e79f72e30d08122fd0ae2e3447e3ce626d4c71f1a7a7c126a4f55ad6fd380ba9e99cca42a759bae5304d26f4cf1b53d88cb112269b547d035d482990125688a3082401a8f1ce8e3d20ebc5517888c4d180d44c5108ab013f55106fe1c8eeb4f4ad70964ae6dcae3b82c73fcbddefe80dd4280ee3818bd4ae4bde751b8a3a0747d921577ad0f7936019f23378bf850a6501e50b19344e7734cbc462e0f492c2d36343c13785988e617429b96166108c12920791a3ef18b7f5e252631831a9bb7664391a998455aa7d742fa6b49fc52c7c759d63e36152ca18c6ab846148dcb351b07606333ad7668a6ed69f1d6acc9a6ecfaa8515845469a426d64dc7483d4d90b1e35e14386036d680bca3632e9403f385c2b463dca9799a3e57cc31fe40b633744bf5f7cb818c5af7a8f27c9a4d88b7daea99ba1db01bba02002d2bec3075796991db30ec1dc2f98929c0ef031b7446206c9454ad111226f19614f391eccad26ca805f039f6d3a4d712528a00408045172bf502b5a5c90488c4f2c6356061f01ef33fc04763c086600e37b65f581bd24a27c9b39a7f0c71b9cea5430e36e091998b19afed0741a66bc5c06089689e8111aefcece76e8a5c3ae27d1913ded3d60f17c989d019146871c37bfe619287d227453baae9b2020f46910b821c276fe3266707a64679050650a22e2133aa7a0f16b34345144c4ae87134dacd0e1ce3abfeab8a55a7f9827b2d0892a369d4051bb0c3cbac10d04f254f0215cd1cb4438293c497cd186130cf701ab42e94ff165ffc43310fcdcbbbe5fc60a25a8546e4274199d30d213708a60f739f2cad37ee467e370a3d517bd96ff405cfe44406081334a00ac618c80a681892d6c77852a57ea8ea3a0184b8891f25277d80e941b4fad9b3959fd37e1b4af560bc46c08c868fad56c0d58baf73e6466d959e7fa4047aac032ef09550d0c748b045fd1799dd0d5a3274c272c7291af54c621c1d35379f8b17b5ee8ccfbbe94e88ce136c3d990b9c0e2e7b2372080532e836a6340f730e95dfe9d74f031fde53835a19943bce7b52baeba50b8a2111fa65be1e97b5b46c3c0f2d16105a191dbb0ee22cd5820c28e8020bdc2cc92b0021f5bbb78e8d5b1045b43c482107b6cdf2d0dbf8f9ba124e761136e84a1ce9b0094d484fa620957fee615abcf8441348e477fcc64bb493316b0984de66e4d773e620a22dc8919621606c78ffe63b4d3db468688a05d2c5060305e203ce78059c49118577d4548c462680702fb76910eaff70e27419cdfc7b1054d1da93677efd4ca53a0becfea6f2d33880c4c117e2d057e322a7608f7ae3323eff09622bbe1430ae4eb25f090470ed5684ad9d119235ebcdb3e3c2724f2acb589d57fd55feb38c51f5ec2343e1305a75e0660c875c17f820675af3a382e2c3e47e5347f4172610855131b863296a1f0b887fea519a57bc76aa6df23d7fa723d3a749679d5f599c76d130c9f97a66f8d83313ee7c0083fdf5caa5fd459a72ccd671c3d54cdc681cc82f6982ae84989e663caa45ae7346adeb36bb7abcc2736b763c7d827a97c2f701992f11b90791307ea793daf7b20d0b57aee07b39270aa754d980a0b600946fda80db8432f6dd1b420ac08242ca0196960d12c2a00719c1ae439b94d43082d2624046a2fcdb36a93826956b827e1c2e6ee8aef4a5c5c2d5442252e5fded5a2fdb1715943b656dc2e84643bbd1d7e85a66d55d6678c1314461a71ec7635e8f94acf22c15f903c2fe3df71203d07a2bcb2b58f76d9b74f5ee302abd2a0cac12ae0abb0ad118db49ee5632a6e3afae38cb07831e5fd6243aba101d532e58c70c5f5c4e6f9a30a7600e75254ffd463fe55f947ca6d2af283cb5f3c4e7cf1afff93c707c4d037723a2be99b4bc39e9940d1247c9f48752772e23344cba813cc76b9ef7dadf50b7f51e76744be74741017857c9357cd9c09910f34ac55caa1b5a768163a6ebe8b289e315c090769dff8c983ec2d66e65a8b3131cc81812d8336c5170671867990015d7d66d12ef26c17c48d40d686fdd8bc8c7f07e87d4dbc7583a7c3fc85ad396adedb961bce0d497c2384fc8432ef0ae40df9b45d5b09e811a1aa5152035b26d4f396c54cfa6661389b37ec2a48711c33dc258a1f53ae0281633a98083bb118a7508d114fd003156cfab92284225f7c9f2c46db167386f730d7ed1e99508baa14c300e337013a6633fea98b4a52602153186d869acc0b44461b8face84c3be8e1856d51b71f38908bb1e57b09aabadc53ea2445fdca47913840bfcaf2d2854a0f6a18adcfdf0167d4ccf450e4a891e3485f6c58837e983a25d310392a5ba70acf99ad481f1f830a3b256ccbaee8b8227a35e8a58b28dc0d1da29a6be70ff16734d580b26606c1bdf3afbc7d2bc21ffa79928e43bbfe1ed7fb481db9d1b1cf91c7d7abda83ff750db30fc7e8bec7cbfb369cb36fa021e943b6b47ca28c9c56765e9f7c61e5d5110d898b05918bc778c726a64ccf68a60a4d446e104a32af659fe3eab134fbb3193f83b3a9fd39ccb32f2e2dea2da23cf9abbdc8f15f224983506056f87469511bff2f01b18f25c9228f152fc2310529a28c5553eb71a7a6801aab79a472f5800c4608be08b59f9e7152e3687309383a2d174abc57ba9e7267c834e657d741e73709fd77f76c0289d568a7adce14331bfad9a0f33a4ca976804efe23db5ced0182e968224ce30c924bcd082e995de57faf2573d4fda0ed66eb3ae0d759865d4b7741dedd3c69981fa17dcf3b8ba773bfbf75cd2445f642225fce3203d24f965ddda233fa41d66710b6f154cd4958f3a32a819881fc2ff1ca757d3386d408db447a20b68774b0820074fb97481611ec3bf3e264b01481fd107025c770081994c532457386e1dde683397b8f28d8307af81e240eacf4ca8a513044dee4ab9ae80e89c23cddde6ad871345553c3cf5a80cb29106d71839d1085a4b710fbdc5bc82c37f033d2be65661c56f33cb905bf0c12b4bd18c8e6f786430357e35daa5693099d67aa69c036416c37519b933c37ffd02a5f63e7090771abcd90f7d9e01bb9a3ec919a7cfa5fe38c82a80a59f5ad1ada62613959a2c1f3a7a0b7dcc2e5a2a32fee6e3a8f58eacd2d840b624199f7d1c640bacbf639708e3c9025d19c9bfc6794b5c0bd33aea9a67d79897e8c0e48f2854e44a3f0f6a9928c3ae400e01afc26acbd75282f76e5024b7af66fbd3097f6081cc020d62773e03438bf0d1dcff01f272f36ce1de013cd8e10b1279a375dcfc95bdb1fc8361cbe102636390cea7808793be4b2a13744110a504731e6dd06f0b740d556ef5891368763ffa4126389fcc256ab8cb9fe69d47493a615559a3cfbb059f7eb8e1d77859acee4b6c48a401b60123d4ef0296a888fb67afacb9cc5da7f09a912beca4a62b023f10b3b64bf9e28138d9c01f87c3757cc8b3cfbaac6358feb11cf8fcea8515b6268c34f672a3e83de3ddef7f2c5b5488546d26108b28e01074f50f180133bf2ab757ce0aaed3793727f2e987fca522f29e8ca35ae335544a7b9cde7b8013cb9992d660560b5e3a603f48907773a405e70f56685b9c36504e30181c096a0386a98a03aec70705033154efa3aace2c4f39474751e3d34b084f692fe2b0069179f0446d012fa9ad591608cd8da48a7d9c3eb06721fdd00e020618ca20c2e9ac66c59777c38088cd9ede70eec1ecf8ceda63d73edc792cd4414360d684bf57241b5d2dc6b81b5ee4956a6313fdbd41ad6d5e8edd070a21e755d6528af5865961676dd9f5940749f36c41fb8fd8e51b83e8af26d88358526b2ca4e1a2e1bdb7f9d361e6b33dc095d18ed43be423d466d004be044083623d22633eec7af79711f9bf9b8b18b47c4924fa9aed721ce2663a7dfa4bb13f9138e28f91c9d05cd88c698a00e34a80d0cbc402a7abd1f00457f2807404e5fea8e51103140ae88a2fa49d76bd4f72d6152104376652bbda6fa30dbab2c25a0e3417f61c985d7c90af74313fd1da67ce86adcee4955ff4fc502d24b0bc33cbce06ce002b7e5b367d9de5ad44f6b676cf27db35901071670afe22d54c9b1233db73836c4e6ac1cac0090b5c3a7f4b51db9de5d1734c631f49c18ded45dc2652548a659f19baa99a63d62f5cd9608b8661cf7fc88ec0ade0d13568d8cc4091e07c7d9d8544b4b521e616ef141187ddcb9f4caaec7be8d90cb66ec8ac2f78a19c48f7d18b55d596b7e6aeec7c3ec1222fad3d5bea16033b6ea9ff30f94e97d661e922ed23bfb9557fb1742bc36dca6b202f9218b2ff870b64c7aff4acd74513fcb8b1d456c917adcc57a88b77c978093edbdf8a5d4cbca0319f731c8b61253a77077ac2d575cc29c65290174ab6bf04860b8168737dfc9102903df24368f1f7b2e178eb3a0867537fbf301dd75b00070e5715cbbbef5f1164c30a0acaf78ea75b958af893f4cf76def228bc6ab77bb89d7977d26dd07a957c35321ebeeeb8e8b784947a4c5c44c85ee3dfbbcf8cd429f2cdc161c5203aab4b572b63f2e40759197311856661a94e1cc9cd9aca266af0489cfcbdd653a6ca84cd1b70175bd6ce932840f5f9dfba39dcca3b15b8bcc9d7a077cf38b4e43c2d0579de4186a7db488367c2afd8cc8732d7b3dde96a334335804c0bba48fd0465286160129e4481274a79aa46b54e93c2e6b557fb2d2e72de5f0f92079de6b46cbe80af70ddc05dbf944d01ef405129b75cfae13b113425d8444715edfb667ef8228415d7cecb2402405401583d9a714aaa3b8afb9b9b1626e82f85bd8d23c1677f9be212935ca19d341b726495f50d178a17db767ee7b105b922361938e6ef2420cf5572ab2cde3a05894170bb10df9a35a2e5a0d9d5a9078f169825924259a9ddd8cf0b7284105c4ac8453aea54056c6ace84413998ee6af9e2d8cdfe06af335164e90275613a357765f1c55c718d037941820d837b766264d093bbfdc7b703e589a9db324df79612fcef727e912acd2fbc12c395e98c7d8b1278d5a3facfb2093ba0a2cda2da673c300c8747da11574389142f859a588005ddb716964efe42cab10f97dd4a04a3260a5a6026825e1129294e92e7f63388d2923e31d8fc74a34a0a42857e41f811d3c74131ab73ceeba63417c3b902d4b962a476d8c8323527f6af03dd7aa2add82d9e5d21d4bd3b63f8d5508cc8ccd55c07a3e10328ba0c51bcbb616b001141ad6ccff3f3a52ae6bd1fd4ca26c6d2c219c7bf9e79d7a2e3c7ba9257c7b5092002ecd22082564eb9dc786681fe513752b2064882ce7dd52c19c32984ee445399c00e09bd31682a5f0d7b8268c65436ba0d4356018998a8dc55b108c85c25e42587866757b4341222cb7008dfdcfb23e80fe0352dff5687b3b98ef8289650de50556b045ccd304befccdb98564b82a096291f8cdf2760984f3a5290a44e3871e8295d4ce30812799d74142d12bd245dcf315f4b211384f454e489e87a80855f5edc06f7ba2ebd1219103f1ae4c0ece8e9e7b86b196566358b05cc4df733a2ca3e5cab2fdc14807f1aee61f8988252b09f975d591c82c0db757b294e16594f050824badd4fdc72bda7cc0988fd65817435a9cfc5ecf0ae843a427087ab7c2fe99ff5d4f65861a8bb8355a059593c638fecbf1e3a826ecba88b6706a738c63869960964139c3d0270f6aeb4a99ebbb0167d39f4efcbc8d84c7b0935899900a22b627eb81ea21dabe8162c59ef159e27d7df1fc13d46305e063c08e38fa1a2f902cf4a1ef9a9283f950a2b9ce2ef5120a77f313e7e5b123c64f0262a11e2327d243d216cc9c58fb1ef7b3994793308c3bd16c10a0d18170e663db168a09583b91ff6e79fed89629b3d856c6602382e5409d99925aa9c119ed429896044003f6012d99094afd538de684beba6b7652791a8a5cf3cc39ca1b042183b72a4339e9ae4c3196da559cc8660297bf7c499e329fb91e7279aa905c9257458f8b6d393ccb510c8b509b941841e1961a04bd5ae7bdad1b7132039f8d7582d268021d3a9ed91be2b17950d7a099dfe963834521e13ec9a009ef037c908b8fd40e854dd14a69eb2352ff34b3fc44cdd4cfdc9b2068c69794998649b60252d9ec53ce26c30323a7be0a1d9d2e8dcc9241d66c4f8baa1878a66b4c3d20318f7cb8119a5a146e839291b1a2dfb1c458b63f01f802ebb396276b7631605d285d631b0245bbb3efd7c7e23feb5c8456d6cee8430299591b9e3b0aafeae5fb0349ee5408ea192cbb605ca9957d1edeb73c077b7f08c421c4156c966017978faa5301bfeb8174e71731e1e08101ae94f23449880823d054c3666ee2df0d44641641d218e3c297868bb7b60d9d0a92abf6f012d403850546b74f4f85c1a0b4216084a12aee858dcb42edffbac874d5913fc4c19c893c06c9afb179fea443106df3a19bee90a03da1acb91ce8bb69102d4fa1c5c32eb3bbc478b459ab40428ea0d9c4381b988e037b5714621a2c733015a3240eb88844075a1807ab8be0318244ee29076f71d9f8a70c88e30f36c992efc24cf97666adf3d19bea51ee0ec446856ea36020bab08b7a543c33ac54794be993443b9eb60dfd9e55a112f64bd2ea993a220a46dca03c0d79634f6c63396940b1e7b052747d1651306971e7ef68338af2c5dddbcb20bab86bba3c2b43348c31284d9b759c7a62251a47203ade31ce59a9ad379f21f9b8279d35140f3b8c1b02a0fb61d1c10f7f5f8cf8fd72dbf29fe5c51026a557d536d2eeb105b53aea8897aa7a6f0670429f948c44e550a0d914072b73a8014b2010a6188e915f83981895bd3f5a0a3493aa1dc054fbd7f9d990dbefee196519f2ca6883d1118b85ebf42c4c50b655a7201d13516671a134d50284bff0ad0bdb09a642f3c149d889eb587d73424b1a32debfac2e237cc88176630518141aa95eff800710bbe87ba9ddfbb24defd95f2b706d96acb173bdb27c42d9b5a0198b2eabbc80641676ba36470a070db368460d3923af702771e00ec74270081b3a0bd4e043147b9479bab7ffc6c287bb0f5995a3a23f49393ee0dc99d4f9c6368b10a22322cc73741c13e90ad22260bb6e01ca9849e002338e225d1603c0f31949bc406f8a9f5594225739d39227158c362c3708b0ae5132b2224e2d300ee728f9917ced6901cac10590fedea57360968d08e0134e50abe98d3a12acfd393e93a526b6d6cdd88673aabfe7bdec8abd88bd6d07db79c2025e19661fbf91cfaf830f763b38f52a7448f1398926105ba8e4df2ec3e8b5089a60e6be3b649181c949eb2f9852670ad6ab8a9b538aa4b69d0ef48b3b10b06dbc8723b837ccb1d522a7ae506a04b72f50cfa0d8029744da6d3633f95df991d2dfe04842c66a06639cd166f741b5d9d5c794fe3761022f13ccda325af6e7a574a012bc766bd3f88ff20248afee366c2787572fd475d9f2a9ece7994603213016e52f93b13429d82d26d13b2bc0b584a4b19a5a664cc0bc5772d40f77cb956666e67125ffa396f8927a77bd2512bfec6ce210515b81ed8f56b4be8b677895b7e98d3ce2eb7959aceeb05926b0b2cf7c47c32c93cdd464445b4983eaab69ced1c1e33e60aebf28cec6e2f957aa635516e881ee6ef78862e828859161a6f2cedba7422d1f6b35d7c0e6be66e29510698b73d202c52dd1e043800fc32ef58e5b08312f240765450dec88fec10d5131b04d83cad60f921ebd85b7379cc879a4f33ff23ccedbc7787005f55e0cf19c39af55315289af8ae296630e85ffb7b3df5583ef8616a39011bf9e3008e02148d589c904ab567674b2c46ab30be054f9ae23201323e58848f789ee48d6d5f335941c3bc54de838887327dbd702d2bf7fa861dc1421bf8e76bbb4adf1207a43a155ed98cfbcee1cd6cebaf351d124c1bc1179d3eadcfde0f22a88c8989aa9db91cb79d54171d796877822c3bc2e1562a2443edb6ab0a2a7f37a14e76b028a6590d4eb80c30ccfb5bc114b4c4153b94e122fe565db7be43655d8b88053aec5eb9e2f65bdb00574971e4b59cfbe40db65204821fc04df783278f6e99e77e3888ea5591bbce12810546d759d278f5c90ff46e33d3f2ca3651533e0122c5e4135590d486ba75d4c6c8013cbeae0786b06c4aac200f828c8a6961c6cd8b538a82717996fe49da94f8233ad294faf9c3b24ac6737ac755b44be07c2fc96f6b688663dde191507238f2bceac46760f726fae51697c06e7ed9b5ce2cdb499388067a40388f59f7d87e37cb9053921ca5a26ac03b1f533367da375c4a86d21d7249a66257a9f6bfe3ddc0c35af0d945d7863fef070007f71c151e1e22bdfd4494841a7f405a87fe6214276c71bac5a6f959e7439650973e4072467a063cd51174a4373ee278e1eedc3b0525b8ba74abbe0a51bd950ee7a0b7f58503537e2931561179fd81f6620d96d05a1ce12a9588bd09bcbd0adba6a3b836432dd3950c29c582df42872e21225f9ac6c24cc0cc81add0518f518b0745207957b9fd8d4f974dbf1367bdfe6a757b33d6a5507c9ae0bd8483e4a5333d164d88fb4bba5d5280b4fed5edaeefcb198ff614ce7a3cb765dfd6a339b1d1a97f9a3e030d8c540eb64322bdfd77ab8493c5b1804716a94e973faa764e88fc59f9557ff259c8cdcebc62c6630904b0baab372ed174ebc92128aa5f1a316defeeb9768d8953f195a8a08e29185c56dd5624f975ba1c500d9e21e82ba46343579b241998baecd1d9d840bd48f094573a6064612bb4cf730c76199051b14fa2d882af6c2bccfc70d068deb1c24dc1fc5ced0c4f967b5b58408d1e0475d7cfec7395c6db77ef08abb88f7bf2aba975dd7f733362a3b61a1dd84d0af2fc069d688b404d460a4661767b7c6e6ba056ab141ffd59e5a39bb96a6adb350989016f8c6d3dbcabffc3958cd035aeedafbb0f1e6976eaf42cbd8110a3dd8b6871779a9b4d39618158b0876567b3306c12a1027ef9a5c4cf74f486c6e4d4f808d4656625e7c05754a54b499ff679b9a9e37d376f67b15f10001fe2faf3fd9f89a8df44ac7bfa64a7901fefc5a4232201eaf8d61ffd33814e7012e51a7bb1847a35849f522a00889dfb618038d4a35bca21f6a1292a921e0e7c6b87b77a73d00585df2a559aa3061a851f40c4d2d9af5e398a792f09bdd8b72dc03b4f648cd2b31ddc8610fda382040e8a8262ef3100d158888fbb2aca235482c1fd05fbf5f15ed8ee0e39bd643f3fd0963e77669a72f86f04157134a56a59404fccb4f288cdb5dd6182523db42524102b46662d011f34240237a017dfa56f857e732c8071e6ed4d9abf67ecee7a78162ad46393da94945bb9e91291b15d63871848a79122f652cd1311b5fb1d6e0d80b47c27e9179e7fc00651a8a4573a71ec348fa120d90f27041cfac1a3c3df5ca7fadd77b0c1f64f6bc62af40f5e47eabe93e4cbe0e25442b0043e3f55b12fbbef210827b03518f8401cf9f7d2090fe9c889bacf0c3b35bdcdda4e22a0c098a72fe06fada48220109f57d30a56a498c6b5060629f5c0859d98a5a6735067cca06c44e9351d531a26362006264bfc8c1bceec9111b157d582abcef55cb9be5241628dd22bbb92931ab09409f9d5e04f90250f9b4f16c648781d4abdc988716255429a068798c64497d20b22d0e380afe29ba2ffe570c9f2509c434964869e4800366b89bbd3950233276fb561e940276338d5f485953b494c32509e14b7b584002c889fc6cc68a92f262498fe9ec3288d2eea7a4288377af78e6f29a8e2f68501a57d0d9cdb246eaf32240cea76f93145b24a11289e0a143346f4f09285e8cb6d64799a83b307f8ff50d83c2de70e4d73ec80fa8bd6f689e53e57a6f6e5dad847d0c0a1168eaa546215fc45ecc8662427a15e44c356809791542719226eb21977f46c680c0bdef68f167b67af47b7d8bc8128e0d5b1a28e854d252f5b20b081af2a86369a710518a754092d1a3330205341c31279a96fe04a60c9abe2b97d9b953978cc7a12f30d2a0226f8898f7807285da2268f78cab075cba6b367af136caf5024d717a5cbd15f99e441c3afeed3c6f92130c2960ba487250a1538e7e1aeff17ae4451a27d51bc6943a6b1f56f41b27a56c8914944ea48cab098496f35ea1b8d44aa81615db4328fd9aafd9488c5fee59d2046401312dd60b3efebfd18f8f156168025c9a1bd64bd79303716913f754b8f8ed156f617c0bb6c28e83998f2414b0a0daee0f4ee3b0ca936616c638f3022ca543508ab108d4bbd342cc4845def43cb89beea0106eb8d748b6cf4c95c017e4c4a3ac25d17df8f525dcf903650e8eddf931554b6de7544e8f751ed159c2e2f5b82ac46240f60867cf508df4a693813f89ca9d0dc54851e27491527c16f83e5249e4b3f22c2433080ef379b5bcfdd5cbf44648dd9559b52a95dbf395aaec5ab17a56f213c2c69c2bed246f161dc1cda431d606573ddd67de005b2c9dd71c9d1a8f2da9bb7d928d76431b95f3e60fffc6c8a52f4c534489db616f207aef8ace4b05e8267287cc63357e603e2c377b98e2470a7f23c7fe7e86e80df2c4768e64ea4952c696606c15cb24cbc32a69866c524166c55d0f81e3e46fca39bd4e5651eb01c90935ab000ffad5d6735e85005b80a4cd24214a5d6018ea4b858a2e745c7552d999ee40dee0df7ef9a01208c0094b1794871dfb90ae984ab469038802991a76687e68800a764ff3123b3f0da7a4fca4540b379c45a779a0aa9f935336a453f87ec801d8b00063867ba8be5487ba668bc881a6a704154756625499248912dd347e642bfc949f7e801b158a3444cd9f06473cdc06faa139a002c3551667f22136dde5ea14fd273509d0ea69e2a14712b6f151357c87d1c558c75586f548c7b871d780a425c14ef869e8816993695663e226573700d9006aa4f4535aa75505f2a144e2ed4134376485125184b1f73a9da58a34b12226c12265a0ee0b4ccf682c1b3779af32bae8be28c10822e4a027948ac60f2b20d974c32afcafe314563a939ad2d1a6bdd1553685e0ea359defa12224e5eac09504edc2f99e3290860633f9e4b63bd8f6e6aadc1ef4cba681aaf996ec242c3519e4a2fd64d78c9c8de488e96853f347dbe9ccd45899765898c7b2e59ad29ce60d2b43c5309fb2a70446e1c0e13ce58bd58ec20a69911d7655e984ae43f120684b0f702d870c6a92c349d949e9b39fde5c9b62978d4eb8a43f6bd41a996b60af63218ae4403cf764210cbef154efdcf6df34debd58f1b66ea750079f0a97b9368e0a360d92df70e09365714acf2114971e912dd5777d1ccbf2d2a83d5c038001846bb18837b74ce525150e30bc0b079a353eb1b27183372b1ac0be01fa3e7cabd34f92318d3fb5d33e6e8936787da645c29c41fb3cc03a235a85dc4bcfd6b4e98073ba2d51f14a70b65f5e62bc007e76d90b3eb086e91b6bde38fdbe70fde2dde1272cc665b4677ff6f5b6da432aa221ae4217aa11be315f9391895f8c17ffacef7630d64537896d79abe4731db3dc2af978b478f43bc7bae44e06b61f9a528035598837062f7807e51108822fce16e1727566768a157565b7369c6151113804459f7495c46648bcebab6d8e187149d98a2e3360a0ecbfc0b607ac48160418bbcc1864f2845f26712844c931945e73fd3c819ddb145163bc851ce598770d4fc1f1f298d2f32d76709df7142bf3f29c5697e7c421b0baa043de67baa9e14043062b53ac425091b395a975447678fd136746f08f592427478487c1e99e52e9c75c2d362938bb478333b6a3a60d518f8f15222cc17690396145d0d6f8cff859aeb94371b12cbc0dbafe8e7be138020143c1444d0211f3aea1f861ef1922d8a15380ba9b46e141255c15e9e4aa08238d9039812855e3ac7bc5e9bb84dbd3016c94430de1a0d53ca5ce8c135db3fffe2a53e44e1b0f29b629bb4f52fea7c0ad3dcb59a72b569a43bfa804e05d07c20b7813f1902fd2b07983cf9a13fa8655fcfd937344779f21d62651b891d95ec9520a409d2eac6d3d22466c8be96b65512de6fa4f6bc0b50b6e6c48394bfbc4362d3334812f328ab4fb6b372949f6a89a23f8b2cf6a1e5447f9e9a8854f462853215981056e5ac11ad9384ee58cc6e3b0c79b72c800288a01ec4a0172092cba8cdf5df0787ebbb0830296ceb513f491f2cee7e01c5744800d66a7436f01106966c5d9ae21142efa1c065c260d18fafb302f3d46fd7a010c6c546318021e1ea4167b89c43689bb7ecd0e6f6cab5fc5fdc489233b6ca24a210eca212d9245d1edb0b80df7ea805d6564a3ba0271283dc611e72cabadc61848cbbedacfc66a08d84716c35080f97988d217223aca933289717ac1cb605a80f7e692225504f8951b3bb4bd4cb2a7f62459bf2b7c976f4044fcce3a76ac1f7d4bdc5bb0b6c5c2976fc84b5e1047cc41ba48093ccd7b6b23b43ab8812d90dad273891877f558444037165d04f67b00027340f337e0ffd68836ef72be029b6b089f8ac86f46f9403b40cc267b7edc5e2ddb7815bb72bc220bb09cf91bd00828a0c3cb79d11b50e9dfed861d15e38b6341957f32f5f19222a18435d9d02bd3eea26b622a74bea261164a8130d977000d9f7b855c4ac05f6d50d43b79336d5bf7ebccbc5fc42f9d5049035f92e095fef308da00045dd39c8ff63005e488b29f6f7683210412896e631445f1d7fced33075cd402215a67d31767fb5c0eb2edf1df30dfdf6b73c85b3ce93cab4ca9be8c67d9214073e821d9ba578f059ea87c4f921e4fb7e69110ecad840ea011e9bef87ad8de6eea178256ba846b0916e1eb12b724e04014598780899cad7fadd7505c3e55d10919c0e0613af22f526e42ffe9b8d149ed9b1b003587d2f46038100cb3f93b4f2eb618df9f49af6e4f322183db85644a1bd13f333c94d7b4a05745cbdd8ca45a613c6f6708a0d7d0ed603b03f98e95f33a923d2909804c77dbce2aad0e329603fb034ae7ecca9e006104d4650b3cd5f81886f219344af5d823712042a726a88b301d5784d40c67f20f8b1b2a8e4c93dfd5496581505204cb7fabc772f393bb4c25117b5f322247d585cad46044c9c3069904cf25f91af792840dc898a0e8c83df7039bb17c433d929a5919a80b974c4b1823d9140094c82cdc17a800ab6c1c85127f9b7b9accab494194f216763c21c1e1297dacf22fc8f2f8154a9acbb2f662d032964aad0c98de9320da00a936efcba3c934657f2903ba410718864d78d13fc9014307a74d05cdd6d77ca055831f94905073b51dcf0b571360728311f78ea3cdd3ac35dcbe453e28906f9dea726dc6181ef81eb513c993ac531138935a3c10856c9418accfb0314997413689337e22407cd0a4a188778d25a5b557e141cfd5cbd13f5d9dd114be4df070bb01dd30377ef16675defaa5b2634d0f9f97987d9a33c5167e3cf8226c9c2f57baa730d3ea8b3be75ff9c7f51929643a770c87150365b7a15bdad94f72a526ead36c52e26fff75dbb967b9eaa1161746e80c62192cec76a7d5538b65f15af7580513134284d11eaebc2c83f319e5475980b605d152f8b5c0a34be43317ef664f4f00841135a93f1336bc9240fe752d4564ab1d83bdb21aa396d925df00975963088900745b080a8f6ac7217839cc4c8bd65e9e862160a12dc36bed1a7e0191399839c92099c85cc943a8474c0e4f0bf8ea04a10b36bb9e65460cd9306dd48258cd62805136a6dd0b5cbb9def29852689e3b7aaccb20cc32d14225f67be7ec94a1b739ea941455cf04b96931dd1d410387b557f9949b18da4ca7531d311c75b5551b744f20181513e250034d67b528ef99023d58a2431a8e0ac195c3b38c6731b279eab0bda61e37c29974c14ef036c25629c674eededc587df5bbd40529f3ef9b98b7f889ff056904943b07b805479bb1c6714dc8fa83c87a16091107cc1f4a3cb187f15c074e071d96b3f5fac79af477aa3b8b1a8691cfce9784a0bd73eb8bbf9d8b881e44fcf9e8a86f1167a97bc54d2a7f7b447398e33be97fd2ece3f8ced7c835f2aa344dd2a74f6ec88b2b6e177875c74562ab6567182bfa4abeac052b6afd7ab7bdd2fc8d364a87afb6efefd2d7eb26227de664942bb6b6f0191c4df8ed93d3e5d5c1932b08f6234e3a62e1ed49ff07ab498e3417b7920e17eaddf159a7417e6ff63cb6f39eff3d4ff5ca89a21120611c5677c2c0efa2b78ed6b35a444797dcaae8539c45a2c83c843dcfc35f833f10091418bced14e3eb7fe4c7d2baec7ade52b2ea1ed93f351bfe02d4389f51ffede91f5c33f66eb2693d0e98bb59581da7cbe951747105682d232e78f46f578303436c2c6c078afcab31957af13d60c4c31155cc97894273366230e0bedf8963a423d48313e250b80270d828ffa5cc9b50a708a4be5bc2f0318b80d5fb5de2d8425d5de2dd5b90eb5127a8c2c9956388391387954b1047aa056b6c94b13bffced9075ab059ee96ab4614bdf341e80abd4efc7d122f28787ca53ccec0011deaaa704ccd2c4215347cc34da525b4f937188dbdd137df599754d246648e0bc791d0cb215ed9e88f70246779b7db2985270a09812a0c40d4fb92522b8b2155ef3f5fd4678bbd58767fb7d7a5a7b39bbbc7be735d04f61887bdda5492d3a9d645ad176b47cea41a26b120bf9e2c9f79c5d0faa7a164d383852d3fb7ae5171d8eb4f77898f1c82d1a244a3e2aedf7932d4588f267d9b10b85fa06dcc4473a8ddf1614b88445a2aca1cf7e9e9706bd5aeb9b000a0434fc835404421a97a938dd017a82c1826410b590f457b284bc1fe4de550c692b20a31223dcaf1447f70528630b14a19b85d4fb48458f75ca34b478435fb83e13edd79c1eec1de17313c867bf4e00b61b56f633540552c6f0b9ed45230eeccc4921719c470768c45e48c209bc6fb6bd4c38afad91fdbe308fdb24a5df4b28d739902a4a7094286fc968438657e99b1d7b621f0c7affddaf1c1938f18db5ab59907114e60c06a24b861be315bd0d15f5292d0d9ef43215a895f00bdb1caef75b614839221d5445966ac0d030d9a4233b4156d8f0e83aa7860ac7967e167b21bbd7586e4965ac9e6a4e650b0d9aedb944d9862afffc270b5c6792c84761ff155047291402ebfb7d26b906b65c371e07a4a065cca6f8bb7f1d4183244c0c606bdc9ce633cded1cceb6347b3670433a942f2c18a877c536f775072677e25ba9cb2dfe0642d7cf8a9db7d628a69e15d4c15a28e034c8a3f734046b3d09c57e364b0ec4b2ad7bc36dda91f0ae51b0545a1a8efc67d5c13936358bfa6971c14239db16d21aef2944785371bf53c47669fa39a30ab98865b0b007c6eb1095dbedc267a820ba9e56cc3b9ef24f7a440ea970e0899b57ea5258c43b2b4dc02ae620ff65f0c48cf0c044bd330fe86e80089976599c3d91aca0a6a08f5f33df86c8e10abd7ecc1850b3e7fb7946f2dc763d6fb362db58a970cb7dd82e9aa6b81f0fc2f873a204bd35b6d1fb8bd43cb79cce8c6590e1c19e14154b2becb426cb685a6b0b98ef9e56167374979120fc4493a4275863c52bc41033175cf4e94aaa6944722114712cf0f6a571e31cd8144866aa2602879659a307b675f0e5793877b32b6d6db7a2db3058b1471cd270fd99ac88cfc09996f18dda4f9bd73d08cc72d39d8e54bbef4509a2f758cff232611f0474e336a6c42ef9779de2adf6c8361048a39cff98ac3eb37ffddfff2e68d8f1387678d839be716c4af8abc66012702f504edd87b227dfcd4dad71c1edc236beb3308d4a237c990697e194a0aed0f8ec71c140e90540bada5e1c19629d76126b2c858ab6370914598e68c71b026865df9d6043d2c591a2fafa927212b69666cf539a47b3906747d9feac5fc6e91621454b08c0fdb43ed0c0899268e16597986216fc83c79e1b266cad42b32d324a0fbfcd501ec044b6fd181f3e7142969996d0b7f93e6149955e0b9309fbc3f0a4d70a1ae6389c6688453b4c4f2a85e85bd0130ffc169b8583d694d565e58330d125b22d0d961c8638919991084f6d9e96fad1cd844977596160083fe7814ea6d4456389982b24a0ce6a39e5df2c53d1f7162c93d8335bad25b36898ead329848e9b6c5325d4a8583bb3ff00495d4f138f7733816b208edbcabf7a5543fa1764a27995129494895d02de29a10f12db1a7c950de8d31c2a86149c1e0396430a1658ca220a6755333f9178b21af295fcc8f5474e081d83e47975d77374b325f68d8e276672783ef168238e7683c8222e1aa601108ac7edf415980faa1e9f9820dba28d4403e2d78767564a3231241206ca6d12496f30314253b19a1a477e314a16dc8c0223b60ed823606d2a0638b19178a8ae690bdae60c403c76e46d8c97b1e010a485c9f062f0cc4b88deaeacde714e84306296eb837140a8785f9dc6acbb879de001f2517801d7e0ece131774fbb692932c2c92be824928adf23c24a68e7240bad554aa12a44fd5d4efed2d96b4766b4902bb57ec0afe0fab1a2ee770b191efe87395fee284ed376ca7aab68ab2370a6b52e49e10614dea151465a99fd761f89f31392da2d55ee5675918b0c1b0493a83970e6238965149d3bf6d3c16bc093863db6e293f31678f18073938584531608ed6e5a1b11c3b00228af7def4593494e9b00bdb3390c955554a6874267852b4b3d5eb5547fea55f2f77f868bae4ff4b505913147da4f4aa125540f128f9cc9b2e557583e0b1511cd3d8d1a96ad4b6c5d90629780f618e6503356159ea1121702eacddf6094079bef8e1c792cf95fa2822cc46909bc4e6c2c70d4c0b1075e4d9aeaca18d2422c1bc5033c84a2f2911dd956b7298c26f1f17f41b0ec0ffdfa1b8dbde474d647b59ea60903f00c33b44a6d190f388bea577c344c753b47766327b9f7bcdb80bbdfd5b309ec5ae66917998478a3c116f60c389e00b380f9b6a80ba3ab5982be8ccef39e30c396517050890d877c72f816e541d00a7d7c070bdda59408ffd23875ec2a0ab036e8bb7090b7c7e08e83d32924d33d4ead57010e4024df5fe4b02961ca44117c246cb442071c95f664ce7692e4be280d625adc9d3ff73b846a6bd0c8f73ae0a417ee7204433b5f92bda4c93902e46d6349945a86ce54c2b3b96f3ece4d505d971c0d73bae610f82c6f3d7d61a3fb468b8386299edf0f5c7ff3bd3766bcbbc8ab09195a66ec34c41c251ebe3460aa3c1eda404d33d5e910ca065b363b8e5de961ba3ba00d74dd0d5943f23b1c55d2a45e657e9c63833c2ac3e5e80591770503957c9e2ecf8dea6bd3d2c56515a273ec8994cfea9b2a9cc8c27b764d3d230c9af37f03d5ab0a57fec6cf4913418d66a8a92629d34183be83f700749106a22c193416030072c9cd52656c8a15dce2bd12ad638d1d9148861aa9df8aa1b57996af1bd945e52379a333d1702b3ee9bd5e73e993ea99ca998aef496b4b1d2297d329490ad0773e89f5ab38c9f31ebd7d136ddc87f4183374e182d6dc633f9f28de847ba364213c905f323dc221ee8f060e99fb33be1d767afdaf25b084b012850a0c6117e2ad7dc803806a5d2a983bbd3b2a23c0199cca25af2b66dd9f4780f617aed3e8fd8f1b9d73ac8f59415db5e97f14c204f710ca44e42139bd39eb3971e13e9de3f48772220b2f1cc517abe155643f8ddce3bf437dbd5184655cd8f737047e88301ce1587d069dd7f0521e476f29a9856def84c282c3f87103e0a51b9dc997cda9e24890a496869e1c444626470ddef2dadc6a5a2bada4ad2b7d2cfaa7d539c6c738ccf82df2b1627c28e9d729e49ab071459e1fdc2cff09cf47bb8a77c2d7375ba2ca98aac738f18a55b372b122eb19dc42d29aa356fe18e0ece19ad052083ead5f1a17e996a40ecad1a68e683fc9b419b27d8c00439a1448d8482e1029e6302b525070b7b67b4e7aaf3cc6b9ce703c04671c52c98689b35ffeba2aefbb6d6279cd0b6dcc88b9180d2a8e0a663e451ce733850a38dbb278ba4fe9482b2508d9eb164fd37ef25964e9ea7fbc8446b6c4346d51eb234b08cc5fb4e57d49f737b9b6119e051c1b0ba3d6c19c40ce64b68ac04fc9f8a97ef972ebaa0ccf017399af2e0815d518b8ecb4d19ed7db30e99dd62064b136f2d96e66edfc55fa20f1cb3cb70a12868ee1e5c17722654c15e74da7641728005b2a138e9f4b930668ab5163d05081f18290e766d24a4e34609d8b235a4d420dd1b54bb58d90e3b8f4682250b8f4f66c1ddd8dad6cd992f6bf1854a8bfc3a72207c95dcdaccbd2a971d28ff0cf690387235f7203a1ed4febc7e8a69e4e73b58bb264b7c010139df84c80f6c8b63dd277d0ecbcfd8ff93d66d66583d29f778b81aed1c11092868a1bc72e4634889b65040830362cd2773fd2efc38bc9457c8c75bbd110a079130f38d0057fba390e750dbdcaa18ab8fcc679664cfb0345eeda3b2f90f2655e8592b95426e1a91c2a00b1e91e1478d618c3a75343489e00789b311aafd4989291a0f95b05b6c642e42eceb6be82e3df95e1b1725db9c3dc5d4206686cf390f1875bcca454b5118277196222cdf04c5d8cb1c8a23f35844aa329ac10753d02dda2ec5f2b0b6283c4c91125f2b64c9ccc000ed29f756128551caf52368aeca6465afa203550640fad21c79714a3d0704a13e2bec7a89efb63c57664343a3192d43caa2f04e49ee2022e85bce5bd810358fbf90487e8d4011227c199a9c483066e4fb317dd8af46c6187e55b70762e63bdae226802d20f3ba3f6055e197465aefa6537acd2c0dc78c0aebbdb2d4e8b34ff00070e12ffca9748dfc6764482b1f0937080e4111de8b9a400e03590c3ba718e1fb7568d627b16bb52603751f80af05a79024ffeadcb24879fa013d7210ec6b5985e63ece346c5e7ce9713fc40bba189d7cff44e5e228a01952785593b31469b3269f483e48a30f1d0ef2ca75aa2fc2b4ccf56fbc90df8ea4cde65494e49aa01748521a6763926ac71b1ec8c26f66b06fbda6e8b43e43bb8368588bc61ed09c5efa72b309ba37076c0a0a4e1b04ae2fa4c2140eebbf19953639284b67cfe6ee3e0ef1c82f154431071650620dfef846d2bad030e6624b8392fb2e55cb192c43d2f0e373f7c694d1416a3350ad63f58858e5935823efb44ec484c5db24c452080a90cb24c7bc9ec20851b4c1ab90edee04e973809be5f748cef2f0758cf95ba89a71ba13d27f13f2f1db1885a56447fb79e260c56b8131905e9cc36ca676df6b4f7ed31b0f3f7413da720b83161994d5137459b0a6d6f2193ef3717cd14bd349f8c9c873fd594e7c03543139071053086089904502e0e9a41eb1f7f5daea68212ea0d6e9e42e04c0542ec969c7631ba49f3e443aa5c7ea96c40c60bc2b175abbed4e0ceca35945210f22d773b15009d9f5a616dd92946eda518b4ea9094c22157bbe71d4b0371c88e8e9dc0a79fcd14e4ca484f1eb7521c0aee2053d83f0b47864252ee1e983dc92bf798490fb7f2149d5733ce3db3fea3c905ceda983833cc07f319e20eadc69351407965adeb381ba45d80d414d21fdef32d062233d4b1cea905ce29676e99cc79dcc002e70d380e60f388d51acad0c5f778107c23e500d47371c9ca5e596ecd715ec4baf0d630a23a4153af80891a4e3b4cea2342969a125457135710796961fbec09a93075171ca2a98ce2391f02d5c4bbecb530930aa5671ff732d6d330d42eb82dba70d7bd5799b888026d53c5ab0a5d63c97ebe2ee2ac6a7bf3d85c8bb84d481306364d7401f76ed5e040500b462780616bfeb441ede9055bab0e410d2f676e68d346a4e5297b8d3ed0d239ab155728d5e67fe5d722154bc1cdc60f8597d7799987c0b1021a4c82c9f826b7a7e0586079f36d3c184be6796956aeeeafc66a0e99fedf0b6fb5de52c42de6fdc1e6d5c013a43ed71be613ff6fcfbb2164dd16d0c0765cfa9319d7fd0382792d3eaa2f2fe778e949d62a8a1c9aecdfdee82baaffa125b5522a0ed833d06b20aaed71f446628125747db601d03c7314140f8aa2005e64314f522ac6d7c82e36f2a6265467827e2935ed017e610229c13b575f42b2866658a6c36374b975135a37e9fbe7db4404ebf810ef7066f7c62ac1cd8c8ff3c15805370bf3510f2fbc87b34b4c557b11db1804422f332fccbecf0c39fe8571a2be6f35692121d960c8d8bd8b76e2b2430c9990b4f818fdd60b00337ca18d4138dabc61a40d575ef55510215882ab47bcaa44d5a993a16da446173d044a06e9511c83c2dcb63fe08db0127759b4cdb011cf1cc5d68f902cad88424948ab067009ac9bc3ebb62151e3b9a5ef92c99243f1d795d8407f7de18354da2b8c9b7349cda0487e7118927ff53b2dcc54a776222d502092cf93c12ca50910cae16bc8d57bd42b489d87865961a7f769bd325c4a068c36be9505e65500e453d827ec9b6effe16901479d78b74a816df89abc9ca93ce477c3af117fb5975bb11deaf0fec06cef16b86b68ca3f13f7a60b1e4929178d35f5e645def10791c560c8f064e3b8c3bc01ca74bd809ea5b74dc685b42cd0bc8b3994778cd912511cd0850069afa8d23162dc8609c82743ffcec06969408df0452437deb35614db518556999ffdaa311586cf7173c55ab77db58570168c273b829072819ba2c2ae355484aa6fbcdf5a553bcaaf183d194fd8c6ce51df58e0e7746272561ab3478b01145c5dd6a3746551e69a5cb75361dbd184a6fe3b65f92bbba6988cacf130407e37c99c6d57fc276eff14c40b3045862e63e63b5b9fe81f583f58d03d02ea1ba19c849d429aa79787a56ce6e7a86322535aff634a644d359889f0475f8e6b79800471304cbab32f0f3ebb733b325f5edb8da95df78ef6ceed92377486a88ec81b0c37257ca3cd9fc769f23088c3ba4cd3605f77ba308e97f7fa6766dd4603c7738c356e1d7b36f56d41f3c25a083e72dfd969a852fc190891a74214349fb69562ffb68b36aa231915512658c3ac9f2a2c1377712b50cbcbfe52403268c1f401a565a0641c7bb1ced2b6fdc09e4cc722ad83a687eb995598f8af59720d3fa01bc85b4833563f096a0caa683741f4cbfde885dd89fb7b20aa2d7024987bd188e5416825301d86123bc0ef39135c7a3f0e28dad8de98f4e02f0240554d25b5ca6bd76bb6225fa5a08e49244dc9988be5d1965fbef9e94495a6747298d7f33b9b8a84e2a4c9ef6af4b802acac18526987619e09ce86e900b64e83d96ddc3ff6cd375ffdc58c9f346cfe26d2f7b48563c37796df6593b274ffa3dc35a4dba545f20ecd6b2e4874bc55bcda23ef12f39c2d1e7ae8015193df44460618157465b60f6c1c2c0fa5046920504bdc10ed654508c4bee9e00bbd325f6d5deed02c32a53f3296e5654372cee007721f13b916e43657b06bfa66ea1eec0e2afdbe0707a064bb731f5e5802ce3900d8c0603d1eb5ca11bebcafa0bdfd3ce04ddae776707f916b27e8224557f806e17745ae9df26b413d83013ca511e96027fec11412de148d4b673ceb0c5df0d8812c13890d2e3cb10ebd460f9c620e1a354d58a36fa9dd6218ed939b02cb23cc04b087a7d4aa6d601d36f2e6e1a03e5f497c969e1744af136b51761eb3423f91c20b35b9f02bfe6d0fc2af37f68aa7a494d86118295b40236dddfa892dae337cdd2bf1cb1e085deee0d256d723d979fc74fbb7bfd8eb2a2eb4364ce95c63f5b742fb20ff7992cef6f2a22defb1f747959718a0269faea870ab64408df838c4c3edc618e18af691445f64fc382f84bee2f8b46b6285669de8a5178fcbeae44cde5affd52197f54c8ccfcc8468a53f1d66024a870b6c71664584f600e83029839cb8284bb774019a173665ed29f11cebaa0b387c074d6803c6e52bf083a5d2a0715f7f2d370baff7b35de4f753582daf8c247962b1c85ffad6c891ad7d2d0e22daea491500a4cd5a0cb525ac656598dc4c18f8e27eb90f719f8c3f6e7844d2a477767f7b869ff1045f0e6e938810d02a1bc4103d7d8cc204e075e65c47881b10398a570b50961b4cfdac2769c6ac3d388ed4cfacfe9587e5c4e209549684760b9f7d78afd8fcf98ec27b63885ffd7bd74a329919a57a104109562df538d05bdc067cb4ba46ea178ecef47babf30677fbf34d273537726702d37a0f97fae6d17443908af94a4ea457002e89d9d1888a254bba1eb5b364f4c80da30050484b15bb7935a06056143f32895225ac77119d25b735c1cf6d71a71f101a0e0856443170c834b344c18ddf5323e305d9db55f7ec5939c711334dce1d64f44787aac03a04cdbb0156729b40b64ee7e3e570a709b175cc5b8a7078bdf6257a4d434fc332df57ae191e7239899fd77cd598ec0fdf31460d4b49885a68c97a1933aede95cca5614d5e34c4cbd26c372d82f883ae3cea8a31789941f696edfbd6c7525db9858e0f82de4d019ab06c2c7d89a52db13867b6723966029b6dccc12ecdba90843093845bfd5b4a5d859788fd1d7ce6e28cbe9a5b40a8c3d508cccf6361512f47878637af8a7810fe61f306dbcfbf6bb88ea8d7f16d2ed48133c8600660207f4525fa236fd107983127afc36dc24bec5649912fe65fec13941415b07ec3ad859b05a695e7b2791caef5e49d0f508bcfe4783cc251d8e1af023c236c10456058439025a342962b3ea7e763610cdfc6b3a7c96a2b945cf1ff2cd2c835485bff97672439f2a9fcedfced9f106c32b09b59bc60703f6470087f62dac55e77ef457b53cf326127491e571f026c2f8cde2d3604c52e2a45832eb7647a5e2039d0d1f509834ec6e9d60e26ec5f37a2f7053fa29f1de51e47d130e5169de4fd461cfb4b76b8f8595c36b7bfaa07a0896a5b6f466a79f6c5b4974750c7762a8c2213ec2176ecf59ab0cc2effa63b113caf753d405f610b415435aaa3da48d75b2da2bc2d1dc3dc3c7e9a63e3cd6375da722ac5a73c4dbb05ae8fb602e85c67ae8d393b3ef70b5f8eda7eb93ae4aa0f824d540de5e91a2fbc4d25bd07d721e5e629084f71a04f1d67416b7d7185c53fe39dd436ceef9d01241b4cca952c3cb46bc2e88744d153f34c0b591fece10640a87211a2785892114c0f1daa26e722b7ee39cdcfc1c147f76efa76a07a855f3c3f0955ff9cd65fce8e24e9c973db5887632ae7afec037a50e17b02a799f2bff9685c3f5ffeee702e99c4dfc984bb58f51d1658103acee5873bf82cc3bd516e2e85dfd7d3d755988223f79699b9eb3125c3fa95ff5fed1500ed2b8b0cad571dde47fd5feac1b9f776d5c9414ed8d26ec04b2651e1e1f0b10e48119f9d59ce38dac16b6b3a80fed6fee2cc7e474400abc0e5875da4d4c40277101ff9cb3d6564ca8f6f6b5d4dbc93eb17f586283a6c2c33f036bbeae61e8008c62e7fadd92d151a50697c1f23c697d7a8ef38a1809ce5a69f39ba69f017272e7d34e2ad7b7620fa1baaa9e764079928bfcecd5c4af617d4ce317d562ea232a23b1b19d479d083cd57e87f55053c4120317557b4ccda8e3b19f815b119226dfd7865bd680644aaf2cf33a47da526a0e81c78c20910fcb669efe452401c53b57356afd1229afccbb99c9331148439c93c643d72b861838efc8adf50006e88cc8be415b64739d55786d83f641c97b685426640deb180c454e1372228c6fc6167e80e01777b975a22e3c48356b74ccbb399994a6f8e4fc32bd91d26d8f0e78c91d4c853c6b6ff35c18ed93a3b40b71789d0b045299b29f37828daf43d3d1ae705526f0c3d3b42083e847471f66de3cb018c3f50489aed65ea2dda7f8e2511eb6aa12388fe80b68d897db7277644888d8f7e84e39ff78e03bed918b727ad35269335e68555d822eab7c7b38639a4139645f8205fe88349326c8faf2e7737b74c5d60de2aebffbccbcbacbb58df7a5a74cdcbb5ef90aaf1eaafd862cc67f42dab73855011c043c78873f3af0216ce1b2b8032b5c23b0a43e3160f9f0e6e3e8cdc07457c6fa3628dd72b5a150cf3fead105bbe9b841bd72c9d801227a29e7fcb7288f12289c730bc2a1ee4a6878e56c9f4bdf2516239026b88d4a5ab0817411aed08b12dfadbc76e53ddbea7b615b9d87a4cfba4f9ce62f594aff1a30721dc374c2299f390fd4e3e9108bdc4281f85f068b8a832fa9bd64e712544b2315b42d81c3e335f8e5759ca5865a1b45ea466a2a7efa82d7f4d421960fa6c53b11215839e20d65ac47468cfb7fb1f333b25292d54f7842290e8ab22c8363d3212f0b0a165237294ab91c6f58bfc88300d1b0baecea2cf5babc3b6ef0f973a234ec80a7a2f5b559b7d91c99f82e45252e7a7b63f69cb0d278269bc523e16ef2d7dec10883f6a2220b1c849dc731d13abb920d59fa4bfdbb18d8a9abe71e2a31f3ee7175388785d1e44a69fee27a820e5e7785031a05333878183a4168480256406890125fed87ca77a1fab4ef6583b4cbecf1e7b2ff2b604e3860bb679a11e2ddec7f993abf2905e2ffd5ecd9b5711f0ee042f22240af2101b5e29e62985b1a2ac0c51c7cef44b62476410e3976b326db1fae13c4ab4b5f5fa6bc1e20d14d2c83943386dfc4040db1bec83fdf3e7ba7e1840ba74738355ef57a8aaa2712822247c9e3aff77bf59425458445e4a0a92d6992a7d0c22b97564f83ee6ca33931389186f759faa395b644086654bb9affa5301604f708e5f0af2441b269131eaa518572a2272d148d9ad32f56b5ace8dc96093c4def4d2a91c33998e0b18e09cdb1c2a228946655f69277224fbffe10881dc344261664ab005d0224308ad77b84c5e9e7ee7b71cc40cabc7a201086572bdbdb6d7a5b60fd90e4daf25778c7da75408336e93e00dd5f206a2e2de0c24284fc1b0a5ffebb36b766c8298ce231601677bd04476a86efe144fe6e580f904020ac9d228061f22136a093a343c3cd442a54428f0e345849345af104f6c0a83cb44f1bf76dda9ec758a4623a120612097c74aead060778dd39629b6c4d772116b48bbc5c7ed4986eec15465201aff25ec63332beaefbf9235e2162e9cccd5c9ad9a08e58c96d4075488d5e383086d58f1c2dc002ca94e09339fb901c3fd6cb747486cb37fe1f20041f9c6b2ee6cc746d28590a2bcd01fe04a3a801f081d6d00769255e584d7af5b1a043e4d700b7560d405557c3af67f1838427c01648847cb29d05c1693d5ea04f4ec3bffcd8fa938cd6de857a49193dea3a1050ecf5a0bb94213abce7934f31bf644900e9465cf865868edffa4ef92bdba5a16dfd48a997cc6c4b2b6c4d616b4d074c74e166dec166c9a4af99ed5692aa419485cd3d7953e18698e139ec7307b74d8b84c9f94d86fac73a5dcf407a2b6456f4a7a111001d3a48b06a881e15f171e6f7eaf519c813a9264a86dc2a7d7d45c702101366752bf26ac066cd33a337c65c76c93f4e1ad7fc9bb0f0ca43a83ed7cf35fe9ad932d7bbfd79340c3fd8d61f84b8ad4c2cb605086ed891c5fc3da0381183866a179fc955b0cf88f1460e620b492de69d53a151c8d44e35c3d65bdc527f1855f13b07d45bc49a48362a2d64512432c362492671ab74b91337fa6590b739483e3a3484a2c8eeffca75b0461628471c25da07ccd4db3909b85e5a20df09d85ac01fb545625dc9cbdf78806796b5a38e21b812851dbbecd92e3b3594c4bd8f39e59085cc642e6b5a1b2356ae15f4a00ff9cba9e7c3ec742e760e0e4520d9c3c014b677cf5aac74c1c9500b94b02a98a106d0e8357ecc2f2ab363ae0d8c270c9a543bb257f33469b02384bf1a4843890ab329943bb10c43aae35e5d358f97d5a2a8f2ab8c36b86dba276f9fa4e607b04afdcc308850fff9c3eb46bde66026c08c89dedc7b6d96e91bf5fb3eb550182b39d5c89ac7554bb8fda0037d29f89f9392a2fb35c35b50efac2064f9f540fa8b78307c3d7db0a0d292c602f23f873220bbc5a34c2d3b6dad603b50b0934c05a6db27e46c0f85ce7493d98bb7ac56b24c54e94675d5517a5c73616e178eb40d5833d1f021fa8baa4e86160f1bdc8ce0d0ca102dc2e36008b27493dc0436c97d6e2976212f229713a2d33ccbd13921ce0decc6adc61bd398c989b4b37208cca4d57c9e524bb6976d7042811595439f4d58a85d98a36dad3f4112aa44b13601d13e0e97de50d4922c7d6f3508147c93e620cccd4225c750a539c3e482707112e5f8a79a89980a3ac6f993f7926f7ea959970a7c94d8a3b11e52658019ca09c0e7d8a0aadc2f322cff6707a65bae011430323053e7d3807a01f0cf0a1dfe6b2641a0d3e2e63111e29805f95cc3475ef3115dde93d3b0933b2de91a73d8df686730cebc7942549476e81a42bbad9c89a9e21a23c975f09d64bbf2f92fa5db19f2945f723c5e6d825ddbbd6ad9b96dfd7d510f3092755dfc7b759d08f8c37aeb1ab8bd445af960d8296961397e063418a41c36275670e65e10332b7a43088218bd836d5d3b702575a052f31d340034f64a2941f261e537a311c2c56ef4fd539915c0068d7f1ad0145acb88af7daf1c6ff510b76a405644c84babd7931bf1c09855705f2985beab880d6a30bc9e29317e3321726d4e10220cc70747369c4cf71a2fb6fe4c4c8efae276959a4ab33cb909a1ab307e6639cc65641a55badf6eb734199213c440289a37a1d936d0ec9c870e7514c6eb1cc3130ac659382816f6dbfdcda1bacb3b0799a61feae3be4ee21dded12048e4d5766a3ad9237d2aa62cc636a761a28868d1be495206466d1a99266bf9ba226ece9e8484b8c5676f1e1cc2986284aebb60c94b19c531931e2d0c23f09a471755ea49d3d53c4e3e4125db8cfdd7a8a09ee1ef9a7b61db2fe122cdcdfa9b750dedc7c2f329b574ff42d0934dd58db76f6796fe59b7312545fad99b929f4e29f4adf3f5c68827b446a87e28398789b9c98125baecbfbfac1b74a0103bae63d9ada8bd72c8291c22432db0935301e7cc2cecde1ca47b27e80f8677238210fc86a624c34042b68e1589c71df730c42a3bdfe2ac27d7ea7d15ab1249d07c6829bd2a9bfe710f3df6a8621675d990677b2f3381fe1bb132cf352a36b557b608bbb0bc13adaa04eb7c196393fc09f5339afbdc647e8b6de66672fcf9a9983191de3ba96133dc3bc2cdfe9c73e3e8c8cf133f7f46e7c3f2adb80197dc272ec44f95176580b6eabf159b6cf45776244f3e96c8c64825a6eadbd29e3851ee3ae106dfb0ea304b973cd4a136ccbd70dec7a5a6234fba70b1ed1b5a676b78fa5e187d8ffdcdbf9ef083fbf5f0d1e07028fc955efee2d7d1fb0f94716cada0a610c98e68ccf903a4ef5ac467173e8e0771d39e1a2267898171d47365f2c2000e6d93930dcade2c56d3cbfc620cdc2fa9fd29b960c2aca0295fb176b1618cc8352a9b6f160f80d29dc29a3c1a5138d39c97c36c9f0b59a1a5769ca450cbc24067dd65000898b6ff3dc41b19bb9a17548f2c9cfe2b30e4c8b2a1eda017e22f884e5697aa8d89640f0ce10dabb4132c001baf860f070dd2581e1b286c89fa8eb84563483ce08372c6cf1310218027c28e2272e927f46be9c444dbf78e27846f97bece201152380c2bf774aaf6d50f68721f1044f3e7837d864233c4db90b40163171986b574b2b258070b6e8b1284763613d792c257a12f5ccba391b6e26d06e7c5ca7009ec4837cc0f98192a9875cde1ac1d229e59a765260e8fa4807629311d01c4fb0316ba857fe05f70ebf8e0f260177d9168c8c03ba732f4214178f0f8df92a3f0fa73962e6ed2525dcb49768f51dbb442768824163d6fc7ffb059a75f8b8ca49eaab4a2aa3e03bf66f4ffd91307d75c6f1b09b53b29f2f2773c4641e4323296414a83d773913d15f593cc07c0c1bc4abe486aec3a48c1de23164f2e229ecc48a24cf20581ec64dccef38491511f79c852ac2d5457bf003e89662a48cb5dd23871aefe21f155fc9ed8bf6fffb6c9e613f7289c68b3dca1b0ba294ff6b0b1aebfbb05fdd8a65424cbcab8e11736776ca3b3aabd27044f949b970509b732cdfd4c9419e22f18c266172026148d51f6cba006608e035fa1b5cd74ad4666e463c87ffd0d58c173d02c2c9da43bd751415925df6cc2ba6f68d1894cd38e44d079e7f596d3a42d135b5f700188dbefb8e25dd83441e75d385006d239978968ee3c0d183bf9a60efee98fb78d1a50347d084674944f78781216ce77ea4f52a0cf0aa6c264507f34cfc54517b4e07a13197e3c6438a96d76571c93f0e040591e4d1ffed0f1c2807d2138269246da0cfde522f38edf7b370e03fe1885db5d3539aeb0444a6176f4f24f9e4c482aae8fc4546870b12f1b9e418abb48c41e71528a4474f881496cfa32d968522ac9acfbf35d9104338a2b2ac0387d6f44677dcd5d3453f318d89ae0abf4c88a724eb73a803aad84b3dbb4a355ddd1eca51e3968df81ac779318bfa61585f3c38c30c026a08e033c06955f1e4b743b38466b6b7200f8c9252de85ca491888fb5d8195e7d11d8596e4b583a4e63b576443f446b9f46d8f5bb4949c756164730496240a54f85e416a5552c9a93f38b5929706e2903f52604077f9623bc7927081cb9722a66b81389462b8acd7781e10a9880b41a3882060036acc5a371ee26f7e29f71857d73a257ae7b90c09ededd1044da2dbb2576052ab6a51dc44f00a6982a7b38a061c59ffd588498bdcba05853faa5d50cf4d03960c9344be7fdf68b44b56f65328e90972c10f748a35251b4812bc1b904d68e9d055a6d913860a1e2ab4a0e87cdf0bb89c67893cb8203784e2771183a3da68a319a39e15347b13af0b8d0392dbce9dcb6903a4f25b359131cd9e956c935b0494966438932bbbcaed34b374730e0050b5ca3f1dd82e8db25ae72db57d744c1741f18b7377e17c6e9186b6c71a9d46313fcfaa0026b0fb62b601f708e860742e567df188f44c1597ad8ea2fb0060346cebbcd0387c1f17b4711abb03f3ae1afaf0f59df867a4fb5767efd9f8cfa9259dd29f5bbc90cd7873cc076ec1f6c3540bfeb9d723929c4aeefc02f265d40f2ab9c35692d6cb3ad954cc7021cdc2cf875084e3dc548db3a0c774dbeb2315f3a9aa3221c33923fbad00b692bb984f67a437dcc4802d6e93a801442f5a13234f487b20bc40c5edb0d280878305bfb8370dbd5b170adfbc6392bc396106b3920bec0d3a63e2c2033c9fc41cd79282087d46019bd0bb17bee52c833d998db6b18100a551887b080b3ed9442ba30af4382a5406abd472328602b9f5bfdb65ddc37b6eb419bea9d9d3756f8d384820af18ffa02e32de964e0a5d3b1b75fd048da067390b5fdffed94e5a53099495c3d5d728d05cff30658ab80eb752a323b4b3d1f06677e858183d0a433c031451d9401a9afbb0bfa563ecf11cd2922513b52f79f0852a07daa5fe6941863a300160bbe07cfc5ea23d438fad2e4aef262ff687dfc376e850290e472ff55ee8107d4e8466d68c8654c9b9fa35e1fbf31e1865c5d1a5ed887d13b32076d49b83099286c420ab69043b9db3ed1810d97d372869ccb53d4e0458170aa6376c4126be983e4f3211e049619299c475e2852ac42daf960112153a46dab256f426463c17629a436362b00088a953ce04566d900a2a0cb62634d9bc22a05c84791fe1af05493bfe59243d8024ca91d7b0be5440343dca336a71fe205e02b3ca39b5efa0979f162addf292e995229142f627437e5882d92fb9d541538fdf9cf9f5ca790272e12f691da14ff1c3b5a63d44f8e3bc9c693e68c0cde53ebcb17b9e30fe44f4866f60e23632bdc692ee9cd6a8e4e0a00db836e89acc0e44aee9197261569cbb3766c6c619d376df36fec7a77f083e6343de3121c4276efed0c43340559604083e7e68d21b07f1349483918cb0e8c651e1711406e492e567cbc09e3b8ff7eb25de7f14cd2e20d1aa2ed42eccf34610a64d2fe3fe11b723a8e36e3a95036bbbd5b8ca76bf44764f657add728d678a598299e53ca7736ab1c1a39fca0b72c3f40fd74c6739f5caabfa60afa45f463d5ed7fe4b88fd92bb5a411de657efa3312b001f2edff252677969e19f3b19fc41eaf9951efff7144160968b7334f93241b6074ca902f16d780a143c294ecf0017b86d03a9bca96822c93b9dd085038196fb82e81bb4393c175cb2a559c92545c48d346b5974816a67c3017688439f9c1c6d1263b7481eb83ac581a88005559d73ccf6f68382cc7800ebfb65fb879b4b0334235195ad3e7c9c3c7f45bec6d044c54b3cd50e8512ca2bf71c10888baf14f4223d7455ee945b431e23b01ae22af99b58422731fe1cc4dbfe75a36da9b44c0cd1082e941580fcc7b6df20cea445ec9d5c8ff0f318f03c0506fd330d5db6cfbba23c73d296a4e49ed8b9226b631b7e5d8fed080cf5139a1f8842d25c35c5a5c23e34329fee4e64a12d79ad01697ae68596979b1016bd13d6cd40803c147a5224d2cbffda67902751a154ea2dc10ebc20df068d429472e697b60b67ac023d8ce6723f457c78e93ef87be2421df03fcb841f8d2ad5a5bb9e2ccf9d3032537fc9db16e1b27df119aa30713ea5962850213164d32c4175e934c56c5a00613488375c4e39ae1176606bf79e1733750fcd8aff65983642ac60d1271a9c4365af7d48b59bdc1e062eabf4904e181646d49eaca52d071d65c4bc436d00a7b94237404953b8b444933c3c5ee9b4d0e18de4813b507a58ae86a037aa94e81f4e02b6b5e01972ee68d6a711c85dca653a5799247a10471d5b0fb48dffd0f3caf5c90201262df96240f0848c02f5ce82dea42eacaf96882c58dcb4999c35753b733d6009ac80e9e49f00cbe633b7f4249e947bfd5618130e150d292b12e36c6b3b3fe8a0b8159cb5a2f160e3ea9ec271daf9d3ce3f82055384b3bfa081b78486cb6846601341db24867505cebc9a508214e07167bcf6877ea901102ff99b0a21f58c92aab856ee4e5bd579a34b026cdc24d09807177580afc0f5d5c210ed795707c6101d119c5e7ca334b4b3d7e7cf664464994f7d44e74ebd8d1d6e0166e75cf61df8a3bfeb605b059bccc8b0ca65a20a6cd62f9c9017e62a24bbc887753fab15f5198b1318c4318b57ff4f6ef39f8c622fd3491829350fd765e3268684d40510eaf26bf68b3e1da64e26e60dc1f402dac64f6e3517ef51b37073c321db4fb956c14adcd5f5620d565dd8e7ec916dd14ba60cb51ce861c55b7d31892a79a8e0c42ca9c32e0bb82a239ec55ed3113aaecf5172492f65d26c135b977a4f5c3bb396c4a2c33355918da88d80b852c39614db94a9a3b95d4d7b16c813911f5710c38fafb709966baa9ab6b2da64b8e83ca4dffb33ef12e818405686cbf32597c04e114191fea64c5b398c0901a17d0d540cdb23df83ce0d94cb8d2c1f3a24df6af2d18776114d16cd48a60bb674fafc53bab6ba2f75e1325f3148ac83c0c91d02d02bf81f86fc01ce554e7126966a937cc7597ce63f86ec46cecedc21f1e08d3f7992d834505bd0f1f9ef2c2e03b063feac80bb57da9b4650ba5eff7759df73f074a0a5f3b54630b080448845e4a90df898c4ff53d3c02b91e5dfa4d67acf9f589cfba25d377407b79b49cf7c787da3caa93f42cba758a78a42797f387394a118f87d9efccf2c19be11653275633fa849eba096c8886c845fe5f922a2001add80ac2d6bf306b55a8b15db4d0c1b1c39ff25ee7b8407aef21772e783e3bff3f20be2f7441c6e1bf1dc012cae1873f57389863dd240c335372605a187f05f0b51dd69678d452714e151eeb2dce75c330186ea3e0c9eaa87c632218457c4cd1cf2316e70717d81cf5df97421f6d3a41cff1916b89dc4f50668c665ff0e873254b417eef95a88066b2b0a1f1a2801890b5a78e117d3ae1450981454ae09c7c8b8fbfd1c8f92f839bbfa2f89d816ac57d12c97a677d8dbdacb261310099b5c319b00c49ca4d73afae45c12e30dc4d9dd39f0e5c5c7aecb3df1b9db88a07577d01ef7cd9ba172eb0bbe82cc97b1dfafc8d216984c7e24c3bd6ff2eaa7f3003b4abb3b689b43c81d423a1d80468ce64115487d9fb35098cfc4fc6547664a582bfe94a28a0b97c97a839b7f49f4fa3aba2e6adf8e38de90e46a7ae1f7a791cbdb318777264646f27cce7f8fe9c0c73bbef402bfc7234238dee234bb1d1119828359a5665cc715b511f9aad8d3810a925fff520615a6f687d68e20a7bf56cade375f389b700adc6ded373abc4a979fd23f54663213c91026245d1d814a9bd63888e3c78d0a44d00f6fcd0837f651e3cb3760ba021e151cf9825bb0608a637bc47663e06bf29bc3cdcfcd8b0f1ddef13f684cb859cd9af580b17b0c4485e82202d43e92cf8ad3116ac168562507a57c0d5789e525dc108160f8f20c43bae08fdae24594b8351f0705162d86ab3ae44d511eb11052db378c42b1df8c22ba92315d217cbfb95188d58bccdd13d62deeab55def673caae5271cbe55f020fb7993320054d2b4d27ac59334efd384de0c953f08be8d05d437a831aeaeadb1cfd02d49371000a8af56253f53877b1b353163a020c3f84a68218cbb6c23150b6bdd57da061720e3b70d26efa31884a5b627aec0fed84cbeae4ef97cde04cb5a207a96b6dd9c7fdc06e54ffaa9f737306dc943173f3d601c58fbf0f6d49152e4a6f346b67487bf6665c3104efa6ef4830ac353a9ae0bd9bc3f38e96cb60dccc99ef8efa64d43418736e5a54304b9f54eaef8c625cd8f5d46dca98fa81178cbd12eb52a2b74f4c9993bcaca369af876fdf36f64c717b0fb0364f27f3fefec4d9e44fa666209d5e21f24bb1bea1ff16d4d776fb35f9b731d8a5a981f0cd0a413ca41fb8f77479faa48d542889368251969951ba302a8883b511d3b5e37dbf8d713e579e4dc836781d3b9ef11f707b708e7839720fbb0b96d48b57fae034fdc875bc5f6682fee2cb87cda41c214b60ee573e9240071bd34b6817729c7ece5122ed824131b1c5b3f25a879c3aaed700addccbba69b3cc1c830462733d7e2f489d511766038c2a30b982c46676153b1d7d6e2470c8b7e90748f9fec97379f5b4e37f9bd3444f7e9565246f3ebf72e2cc45166ec2b4afa3b94fbe0edbae7e6cc19c5eb13112f864a28da29760e7831779b4af8c9f0e44d6dbec2368c3d1e3fb6a7cb31e3c3246fca7a6671080f355fa489f2b7a1e9c9e9e9ef17b909b3b7573e3c089bfcfe43395f5e35ceb1bd5e99a1c7226db1edb4ab89e450c49a29ce64707b2edf45123baa59ca9f9f95988bc289e3b8f549617321f055105a6d94f6faaf2164517b99c001dc9a1504aee55acfacfe4848951e81af6adb30bcf3d80aadaf1f17337df4aba7560f0787fcbf823c9bf9bb8b761f16df481d6a36e574c28a5786c7acc41ad994eb74584188f57290d01c1db457e4f49ffdf56acec90fb95737722fdc70cbf6e286b37c985f63e04bc1786f2793a929fe4ba9b7344cd1f47a61bfe4b8419884a42084c2bc6adac46ffe216ea2cc2b487ecec9c9caa2c4cb35b5dbc3b29882886452736b6536e9e973ef4d4bfce1ea3229f9442034d148be8b1d00099b505b74cec877a6d39082da9a35b96982d5d2d894388b42d4ecd0cc66bb03daddef08ecdfe4fe53eda98d70ae0a236c2038ff39e4bbef85a79fd1a75dc4aec1527484d31abc09829951d01876d9c777abce7dba14e04df89b3da386516e674b95bbedc8a48b6276476dcdce623da8fffa36dc95bb6d22b621f017d544404265c8a9d81912a6ace036db7d5ec43347005beb8097a434662f5f56aef1a6c717a9d98fd0d4ff958a8bb2ec7517b715901d4a9e22c66b150451682f3f1f52743eed71290ad60b41daccec84abfad4971c5a0a91b2d39ffdd199cc32f16161ba7978f447360257ed3fa6f01fb61836dcf9ddefebaebf003890d54173a07a2dd1d9d28d1a8fe2fbb1936e63687ed46a6cceb1610cc57006e48b58db5ca9d1e6df1259098702b692f7bc04b1e357f78920405a181f95f726e017849b17939a7ec6f494f288fd003ac2f430612e4fe0fa86cdf358228fc0c9cef4cd07759a54721c673c14fc220aa51a3ef9943422c88e7919580f75b6eb4dad912cda9781cc3f549caecb61c387d2cbacbc842c13a8a6a3cfa8f16c86cbecd3fe9a238992301a8c42024690b88ab85da58a1d5ac5861a7d7bfe786f3b8c190b202c3893c2a1f8d53606984964fa073d6731a241e7729d37bf301b063674fde91b5007dabe42e897041081184c4026feec0313873514d6a218d6df6c6f09356ac950f237a65bbf48c47d73a038f210a1f15ef1214f4accc5fdbc8bf1449c390697b12a9e3d9b99db6621a356ea273d833057ac2c58ae0ded3678c378eba5e4885b4266441fe9f82e3c119ad0118bb940082701f7dc057ebd9385a42cf58cda71d1266ddfae9a1d2e185274c7cc8c006ab160a9cc5d8a77e263bd8d98703daa52ec1080cf71251f70cd91efb74ed4c439721d8d7b0504ce0e221f2c98f6bd60e3d5a3f002f91c7588b5d20aa8d9f7f6fe25680c3563279d903ac8a27f69de517bd162a07ea64dc775940d1594c7fb642f8e258c62eb9ab176a8760c1511e67df24267084f1165c137aca9150b5adb093275d9a5978904f79eb638219568df090c4167dd67cabadc2a432625d5032648b2d7a225dd36a031612326cd45cad626255d2e6b66dcaa4204f8521b54ff25a91d29e5b64bf1400fe25f98fdf1476e1275ca024f811947886ba212108f63403151e4f5e6484c3230868390c45833821890c36432197a253c51918d26e4a3af8659abe29b807034a2fea248ee37db7dc064ba3295e4b626021fa79a16f124e16affa3be43d057ff8042da38d59258e50db158bc553707ddef8fd270a0d8fb431992203e768a174c81b58f96c9a61cf8eab72544ba42bfb0be70b8e93c0ef131315b12d0893d3982dfef0df5e80ec8c231546eb1c066a830840c7237097f307b3bafc85cc1a4fc92491f601af9eb9c3ed07d767b92c98ec53ee9f0f0e15698c05d985d06d3c022ff0a8a8f17a9b14747da009c1838901b2ca8aa4938baa8c8b037e6644150d4410a0b5491bc3300c268688858a0aa6e8608e02b668392d8b1f5b631b8d9a8da5db4aae6ceb002fa27907dfba4d782bbe000f8aecdeb4339254de1dbac0bbbdfc5687fd24eb0f9e0cf40b27b08d139806494279d00c8aad3a5c9c401e9c402128d5e4fc4099aaea6bf5f12112f31eb922faac502f1a3834831084a8a2b63011e232e19c70e288609ffa2071299274d2d438695438693a3871429c38cb89af13f62389ed54d01f585c1c0d93054ffa0b4f883ad6c409fbe184c120a1301038065c86c704d3b448304d2440a1003b610938d69a9ba1288f50441c8250c3f2801ffcb0871611c5a2a1c415419024141e705ce1c58b66103d7898cf0c555b68261045753279608200ca8ce763c5264eac70e3c40a2a7cde43c50e1ef645d5858d2124e1a48a227cf1a3d0cb5dd9802811e599a128204a745205c70d921bd5733fb40849b2c4668a0cb02469faf0c614202a64bf9811816250088045057a51e5630a4754a1e9640a3948523592240d1f2449e3461b7b58430f92468d3c744213094a4c07e573324514275340713285025078509101314e9e473a799474f242d2c91b4992c60e92a4a983544d219d48b106a90627526c7122451927527c21492c28410a20e210070d1d2449330749d2c8210e1faa23d2c0419224cd1b4033f422499a34dc20aaa82d4e30c0058d1b346d90240d1b2449b3068d1a502a7081345688b2271710802445218828d488020c2751f4800349664c9219731285e8240a21ede1c0c49e1c1440f9745072447182b206499242a00f553f1d29c78989154e50c43841f1e2048a1440bcc1861a7690431a6b38031a50cc2a34d5cf48e53d749e40f14ea0d88024492b404149018a67164922d1605f860a8528fa441c9e08c2133778c206663c918227a82772fc139a930a2441b20f060a16d5dce20b97203980f00372949828c2119a3224a20f6a98d0c1640e6e300943124ca660c203144c000092244d5fd8cf7f811213fa509dd9f2269c1309404992649c44401392f41272c14904825085a659759470128129aa1308b4a00a4d473e238ee8c867347d94884e1eb0054962142b8ed455f93839e18124893e584c4e12dbd9545888a88f2864ff379bca332b556931252b40312314847c3c68e6658a4fd5794bb180a80f16270db0815485a62433e68ad057f652568846d40903129172c2800e9c28208d0cfc60499258208303b420018628590423dc2841a3840c2709f080a4714333831304a821499f190d19d080a20331384180002449bac401ec19064822499a31942000094728421124c90c498a410c66408624955146a8c7013a1d901a450a94cf7bda09013c484a1620090e5042ba42152e70841f42c8010ea42152910e50e064001ac9ce0fb91b09400492d448363c78542702c81180499c04000e2a50785c8a0a7d4c47b4b91fdaf122430101557054bf48d501f9a83aa0e901c5740010872405917a9c00a0049274292af4c412de529f138d0f4e341e49a2b6902aeb99eff2a12133f43c78d4ce0cc9d65a6badb9bbbbbbbb33c618638c31d6ddddddddcdccccccccbc7af5ead5ab57af5ebd7a758c31c618638c104208218410bef7de7befbde79c73ce39e75c6badb5d65a6beeeeeeeeee8c31c618638c757777777733333333afb5d65a6badc531c618638c31420821841042f8de7befbdf79e73ce39e79c73adb5d65a6badb9bbbbbbbb33c618638c31d6ddddddddcdcccccccc8b237cae396be62266451a01269a2049d20cfd15b5633f44a8d01b99f9a105c5224379fe878609a1e78844d4e7a3c1424b45b1549a2b4892c60a92a4a98267cad0cf4c21148b2469a820499a29485290a459d92a2515a0cfca8746a2d1ed4c8a220a7528caf4f18caaa9fa0a0b4aec9024cd1992a4818224699e20e9a084e40449d2304192344b508224699220491a244892e60846308134b2e2b2d1426ad1051016b969e9a2a5456ea410225a74c1c5c60b1f5cf8f08c2cc5051cf58879d10a0ce5e901472bffe9c07c241049b44092248d0f7a603731a3ea3d33121c2a764892541201a9e40c48a8a00a4d3374535513a49141621f0c142c449ff9e9c4744231a3cf3f220d49b222029a91e4197926b505cca7230a51d407ca147a84171a2f44204992a429a221f2224992678aa68a23b4901a8105fba11e709445fd2226f0e69c113ad0c1082f6c872ab2f220a8cdb8a84494a772375a841792f474fe506929623f3e66423386f2c82402119224c95c217ff8f8214af2a32e3e2b2a2d459ece1f9588f2b8d8a7c2dd08a50a77a31df552d40d21516929b2b92124a1078d2a44b8808ba8b414516929f2e94ccf10974a543dcc10106856a3ea7308151068f2e0b1015552891f2428c40089219214620cb09988008c20499209241d847843920a1137f693746e10e2708424491e8f6322a11fc283c7065411c223499214253ae288e30d499274c43102a90b38dad0a1b079535d5143d515b523fa1836723c8223d83c723772d781636510660091248d073a20499e5048564d0f0ef0a7f2418940332120a24f871a8281900f04310641481a0ae808440fa27080243d118427cc084403a44a93058fd04bd20c208af007454892540249072056159a38a209ba62468485864715fa4e0b0a473441281e8f0f1e3c74fc21057e8843871f5c241d6fbc21bd91224992157cc8826bbd850034259274828d1b3d9024c944d2c1072aa4246b4dcdd3b2c20ae7064842e181d2c5152b6e0c91dc409124e93d9d3b61664c1b9a9024e943a40d38d440d1b186019ad0860aa436bc90daf000cc075aa1f242c48a0fc582d286244928f6d339c286212410c5c61924141e5c8409483ad84832a4191b3b24c91dca870d014892f459d983225078d88e1c244972d1b18726ec218924edd88310499276a07cdd438a24ed40d1b1870668828a1d6bb081c223f4301d6ac5435923079224893e6b8024141e6b7821556baccaae610022920e3dbc21e9d0c31f241d7a1084a4430f71d4cf4865e6e38362599124cd8e25499a2ba4c98a07cdbc4892a60a49d240f11e3ac3e854df22499a0d5492a4d140cc7bfe41332f92a4a16214b3a93a92a4d93185243d6846f4a099299a97925334b5d0a1468a068a0c4892a491423445530b22208a054451422449937257bef248fb91a11f7725f443923418780f9d2fa11f9fd04824aa1f0251212d5c8288dec5332b25e4ff25886752ca1344f42e2a2a2e10ba40e842c4b10b9166765242dcada8a8c0662bd0b1bb15cd05242e64a8501049d24481b222491a2844d547923416a88024aac20043365b7916214244414441840869b6e242e483345b09030ce96ee5598408110511051122c4dd8a0b910fe26e050cd94c4524a4998a2848339517d24ce58334530143ba5311e24e4514c49dca0b71a7f241dca984e101d2ac59b39595951517222b2e4462ecc747b3955017cd56429417cd563c2ecd563ccd56c2f00071b712860888bb9521ee565656565c88acb81089b11f1fee56425db85b09515eb85bf1b81009715cdcad784222aac5dd4a457d346bd6ac994ab366cd9a356bd6ac59457db85381f1741e883b9510457954dca9a888aa0fcd6771a7220a81e1e24ec55a1fee54e8c8c59d8a67c4853b15241d2ddca97c3a5cb8534932b970a7f281e9543320d00c89a059339715222e2a445cdca910a99f51e8c5dd8a8a16cd549aadb8ac107171b74204c810772acd5456dca9ac346ba6e26e45f421d0b7b85301b284396041b21398c3169224e99803cf410159a0a0f0f80284c24556111ff6635eb412a2427280220e3fe320461cd6e4c0a18283186cb3111c5400872924a99a28554c33ffe30d492469d38353cd2d50aa8992451a37e022312fea4892460211903e8c4e15f3a24eb3d187d1a9dc504992c4bc38c2e79ab3221f1fffa1649e45a5ba1f1ae2a1286abf873b2af4617ca8cf045513e4c28621fc70598321d63004697a149967594305663649dcadd84fd2510951a23aafa53aa1ca7efed38179eae97c0d5194e77244309f0f813af55b5ee4121a0199992c3fe3230d6974a1230d4310511e978fe9bcd84f0704f6d339928646431c68d041094494a7aac27e3a47485082890a2beca7736487fd748e5c6ac60382cfcad70972b1ff79501792a46180660167c8e20c28d2190a80c22326c60c1f141e9beac87cd006049a3c78446ead435519a620bda8806086783a33468aa84e0b7d117826cc0b7911757eb8d64766ad0f02d77a725c5c6b4ae6a599cc6c6126335b1693992db165660b6c99d9f25a66b6b896992dad65668bb7cc6c612d335bba6566cb6a99d9125966b64096992d8f65668b6399d9d25866b638cbcc16c632b3a55966b630cbcc9628335ba0cc6c7932b3c5c9cc9626335b5c66b63099d9d232b3856566cb929946c8e0802a34a181051d687840071a5248d218a820d98f1955a339a4fa8e8e31144092c4700669b262d8c124aa3a42445f8961b2341b8188e488e34554910f513294108a7aa97218a21092247dba683602c30a2429471c3e3e44e0a83e3464109294431081908e544a8865f91025aa428fc443b9f0e0f13c4413a5d90845542728e66d17782049d28b2824ae39eb242ce43f140b17c2f080debe67f36174aaea61266853553d9a8d389b23758ab6108298da114d198f6756fb9bcd666dd6a68baccdda7091ea5b3c1e1fb5ea70f1af22aa3a9ed07790c0cc1f1f335d3e2b228a85a5d9880a7d7079b11d205c641569c6a92687534d4e911e3da0cba2191177a3b754eda1630a389024984f67c6e3f141b550a224a5240a0a40a0128ad8c2195638a20a4d1faa138514e83843ea3863c8342945015285263aaa2469650a1d5090c3ed702a8903137540c1495568ba42f4e98c603a54f4e9c074a81531a34f35d7c44850b8d0c113b220bda07c3592410e9327c427a048f6613e2820aa330249059c8d740227882449ea694d8c44fee8a2e5c5e58b12a9e48c922a343da542a2aaadf8743ca18f59430f00804d384392a42fde43911ce984ac98f9238f279f9615309427871a9294230f9294030f928e2688710749929078286a088b085a26912042beaa7c803a2f9d992454a80865472e40aa17899ece1f445cb208514078f02080ed504578f0d86c389b0e686e4055d58335379bf6139a1155dc3df8916c1c3333f3e2089f6bce9ab5c86233b3983fb2783a29f5928252cd2c8aa05401820d920f81cab084244d2865a0a1a38c468624c870021949282af428a03ac880128a8e17c02149d214eabca0063a5e9044aa4414cba4e3053b2617a4f1a1444f47d7053990241d2ef8a2ba940be01896f850a11f238d313e44cd9719832349938e31c650c0141fdb411103109224e910430b301f2a242302205d7c18a11e3d7af4e8228b2c92503f363233546d92cc98169ca0058f0570b0e0062c60010ba658011e56f082158460053b2469a258a62355473402cd78aa98993228339f593b9f09f2783a321fd3114d199499cf7c99a8a876a06840031ad08087a240339ece8ce7fe87faa8600d1590a1024b89503ed6043c787c353943be2001ca4b0a38a1230569e8484111244a3483f29c2f5270002fe808630961804047180f987184247d159b8dcb0c0f663a90a410497ea023892889053e9d19125d41a1cc7c6638a2102523aa461765a68a992f3ef6333955f8c8a272a95ca8f8fc40a9c2435174478f1e2f59802811109922320298bef8e20b2aaa0051a21d4190b071a49a2324399024c98a1085c40b898241b29084466c48f3c7080cd38fd18f0020ea822846644575081d151a3c78d8f6565e2e4ee765bbf282992f5d94b8415af66e2fd6eae8fd67bf0d92e3747ce3a48fba4b091b9e2ffa6ebb3e1f7df72d1494ac414edb8eb60b19fab58e3afbe98000aa213216a35b2e3277ee3984ce756cb7b9da1546cb6ea64136aeac513bafc7dbde9a25689077756dbc286d4a2b7d7b06591fba7a6ba3fed331ea7ca5c40c3246e896ede6aa776dff6590933ebba6d768db65bf22192475d335b3e5a8bbf442ca7930f3659a60a6689a426f210b89406848469fad85cf698d77353a06c990c51bdfd106a97b3dc52097d7ee56effae6ebdf711e83f11b516d292f4152c0901fd37999a6f788e8a89aa669822c4636a39230c4e55c8bafdd5b33d36b9bf2b2b539eec7791ca2904ccfddc0743c9e779da4040cf241cadcf47adb85d0310a5d48c917647d4799473a632fbab8398fdfae1620912991e843a2aa138a2f255e90eddafa59bf1bc736db7bbec3f248a8d09069e2caf3968ad9346fa2a40b925dbfdd26b79f9cc7d64e6b7d7c3ad41039d97f420917e4b3f6b167df5e6f8c31dc826cef3d76ee55e8d6c6398a619e1117d304f150a205d92ab5f1d5d78feb43879cc7475a8602b52c48b7de366ef4beea4d9f711e77362130aa9ea67783122cc8cb6ea9f37a70c549a95322c8cd239d04255790cbdb9db596ddc62eab97f3787137e86ec4dcf3a2c40a72b6af715a5b5fb3ed68398f8fd409f2364dd3e46e344d9ed9097d354df6f347eaa4d3e4af648c922a48f8d5c6d870d6f88c4de63c7671a1e999b57247de33ed23098daa6316239b1e255490f1ba696f2fd666b3ed6f0ab2b276bfa953c6d7be8342a6490af2d2bb608d6f756bebc209af88484854b96331b231414914e4ebafb459c7d67dcbb4e40c16978d913176b4a785c2bc7632abed1032af469f10695bf79cb9eb6ea55de204595d43b69e9b9fb6d6604913a4dfc996564ae9b3bcde723762825cf37fbdb858ec5fffbe53b20439e17458bfbfceebbeee4809d22d8495ff45b7d6b976dc664912bee37af146e670f1bd8ff3d8dd03c574181224c7492b638f526699d796e4b708bd0c25a44f507204d99885d727bd9479d53a61e747094a8c2027bbed59c7f9f3fe6a91c8344d53cce8e3588c6c684911e45bfd1fd9b6ae0d3ad8bc8408d2b9e172d4dac7b859ee583204e9b8b1061f5dd5edba0d398f65fec342ef1212b930bf90a862254290d7d5fa2bae7819df662fe731bca251ac60485431561204c9ec91ef751c2be5c83c66f379cfe31910559b6da3949714da4629b1bf0408727d5df4daf92cbdb1236d404a7e20fb56a66d5fa41432ffd91e28f181bc77ce06a9e3c990d1c8af07f25f84cdb117df9a3c5db49ff71f253c90f72d7d1effc5d0260629d981ac4fd9eb59613773bbfc79cfa69b16253a90be7c460a99df3d2f6e939303c9d6d25ae9bd1cdd2e4a398f1d8744150ea4c3e8d0b6bab8365a5be3bc4dd5698e253790ce46c673c567c8ec98721ebb8ff9c6cd6fa9daf98cde074a6c2029f5eacbb1b3175a873124aacc90bf9cd6d6e0bbccbdd8d44351748b921a48e75864af51fad1b1edd53490aed93aed5ccb456b9b35b72e99818ccdadb9536e0ed7bc8bf3986d363023fad6b50c24bb9fd45b5cafad5d6c82d854120339db71f7e2777456e8cd79dc2a2530906fc61b9fc2f916b56b3dcee38ab2ce2d24aa5c4919d25a16abab75fd5defee9221636d7d2d6d2cd2551fbf921748c7b5f65ab0a3b3b3d92e71819cf6db3facf6ff17f7978c21ef7b5debfdcb1c63daae084ac490b741d89aba836f396cc779ec992198177d3a1b3b41cda1174551cc65b005eeb11dadbe2a33ea9cf318368f36a2980b250b2a8bcb276d934d7addfc7436ee461be798d18709598163754eafecf69d3777cee3e6a202e91c8cccdd9eb35ddb6a5d2990acadcfe82cf79b963dc679dc5c98a128e6b22cc2907efd51fbaca3fdda99398fed6f9c5d8b3392521629dbd6377ab3d4f9b073c83455143a16231b9824325af85c7de83ad6c9e064ec3e44d59691357e7bee2de60b23a32e3616231b2f9044760eeb7af4d535db659b91640fe16af5c5791bf2f5888548c6e5ffdc8496357bec17e77105b9923ca17b6cebf5ca666dcf79bc61dc42f2fdc256237deb9cd1c89cc7ad0a3024f7a3b76f8490b9b6064110bae7acdab5ea4706d9a4a4f78c7f67846ed95bb582623a8c7e248c77bdbf8c3efe5be9c7c8e6f7da2ef66f6aafb7158f7431b2b5b05d643c672fea7e566ae733874cd36331b2b9472473ccbafb4e069945c6603e9d2371a140b63867b7566db4b42ba48c29d71df93d59c3aebf5cfcd79a3916239b1270e4336ef7b95557a3d72d384d1ecfd769fa108c74b6eb55f6eb32e89e3ae7b1ed6c1e34237a1f1fd334f3998cc5c8465ee97445ff45ddd1e66c9df3b889626e065e72858f3257e383d3195bcd5609acacf7fa7adc7ad2ebd3b2247605e6335ba6e9612a10b5e1c76264632b7b6c113ad8cd1d8b8cdb6f33cbef4e4ae36ce6cb79fc222425f4a29569fa109069b2df0151759aaad08b40d3cea9178744a0122550099fd97b778deef176fd50f1eba78b8f8a831588e231253f7b93a17b945d74479d6331b2d97cc19c37165fec059941e8fe9c57637451e775b1a61fd98c3b8b914ddd480b1985cfc2a60e42fb28e7f14c4804ab1af2e1550d4d8f2c46367e02c79039e6e6cd7163e6a037c3d8683b1bf95deaed1c13c86fabbbfda573f27a7e4b20ff3d3285b63d7576d049201bd7faec3ae8aab769dd76a88d6331b2198111f9e89cacad5a997afbdb11c8f8b6314abb1d737a1715e285f479eb57766f74ccb4af08e4aaf7c5c87e9bb1d58ebdb4b318d904292299766cecececb3aebd4d53880a857e9adc6331b2e91071ad57ece7e05b8b6133638fddebf2e833c2c7cc3072de344d93f3900884821779a3d3396375adc6089d398fab23f3411bd1c344b7d9bce748e86336f4486dcd626403c4455ac8de7270dd672b6cdb388f31d022adc717fd3686cfb5785d0824e3d5b6a1bdb4bae7f035f3907f0f176d96d77cf53b86a00bdb5e7cddacae56af375b4bb5b02c06eb738b42beacc245570b1792553759b3fe8f596ad9e43c4ef5d80979104e7eeb7174cbc276efa113c4ed0202796773b4f536befdde6d9cc7202a64797d405e861fbb317e0621dfc979fc1fca5337cdbc22df3adae2bbfcdef557e63c863fb6801c39bef5da41f61673b39b6290783c9364bb38cee66efddddb9585d1a94621ea7a40319d6e31a30f13c55c9920b2ae8efcacdde9566bffeea53651888a7c11bac3d791c2f51cab52327b993a870dc6bf6d4db7a285b4d32b64ebae5b61b376711e6f18b3e3fa48581740e46448adbdbed8f3d3d99cc79b66278ab9ccfd903e9f8b753ddb45db5f1585a0077cc8666f3df36963a4efadc579bc79d003d21d3ebbfedad93e1d759cc7905f072483b3ced5ebde1827e4c9792c438560b87bc837eb73cbe6536eb0c5cb791c12551c90ebbbf56dcdb173cc59e43c16c5dc48240b79dd5cded0b98eedfa659cc71bfb991bca72bf116021a7d7472b37eb687b66c879cca946a04d88ba1be7230548ca4bca8b9094fa45ea23d9c8ccd06b2c46364278c87fb345c8a8e5efb72ce43c0e4d4eddac491473a7e9435415816814b3598dc5c846045142b771d186937a3be793f3e02845a52525e54548cae6d3791d1281d887a8ea2c46361c80b23e6be37d675ef9bab9388f43ff79922e08e7746fb5c5ed59ca795cd9ff6a237a98c6292f41aa99b27a94e2324d33e54548cac6238b914d890c5c6ba0d9d780f3142c02dd14159200531a5812b8c2025304aca04015bc6303ddb91f025d400313a082023ba498428a0c5c408a2b1aa5f0e6391b0a60c05d052e9084921159200a0ba040e15a5b80613307a502cce3e91c5914986202f3433b2430f347aa88c01410d850f10051688aa638690ce880e954513880ab993fb2d9d1002918d03154dd6cf8886b0daa5fbd056c7628000309d86ca440c026c98c81e20030946703f3e9c0c0509e081820c60205e0cd87ee2644892605083000dec44c118a00a80880fdaf36170040ca13950666478e8dfdcc8d9bf0460325bca93a9b8d28c96340f249b0195028f998f9d1b0197d304001a4365ee0081fd20e7490031cdcc00666d480063390410c605006192f70c11862b480052b50410ac298495285c000519f18cf1114743830f76da5f38bcd094c500212181981a4a30966483a9aa0891c602880d4861860f02179200090e8801d24a96a44079248928402820e3c213d128a7aa8da630d3d9a207a981e64f4b05af448e9e1000e48e2840376c082c4811c70400c69118a901c6892cce2115920e2d3a158baf359c902491643b2a0408924652161f1862461e1052ccec02208120a0f2c7220cd7c7c4892261188a82e92260c27583880872624942a78acc1c30c28222a08ca34adf048398421a4dae1804254cc8c87026d409468f347664c54c34934038507caa72324824ea2094ea2072449a26207ca099aa2a2024ea02002af979d8bcdb7f84d19c8da8b5a56d946763dd218c8c9f8dde7bd60b35f87815cad39cea6abf172ccbe0c19977bfc98838d23fc66c9901de17d089d6defb5f802c9eebbceefcdf68db2ea02492b6c18977d7863f71c4376743a2b74d4325ff7e51043daf6bede8790a3b3d16b27a475b4c2656f9bbcba3527248bf1cec88d2dbe90996e42b2c5fe99efbba76fba2664e4ebe65cb523a3f4ae9909c9973e65b7b7c18e7e3121ddaf576b74d3c2d96d5e423ef7cbfdb3eff64dee6809c9ff0c5a0717f51befad847c37ce76935aafedce2125249d8e272fea209b4cdf2464d759f9dfa22daefa4d1292f6578ed6c5b6bf6024e45d8db2e59a29bbafad07095923bceece603b65661f21a765cf7fbeb8e0a5d61192d5be7fd9e4c9b339d808f92cb3f1da767667d76b8c90d1bd3dda8f7b3d48d91721fd397be7cf672b5f5a1521dd178dabf93367b3c144c8cacdaeb5ec22651e9b2142b69bb536b798ad6ca33d8464af56dbcc3c9b568786906d1d5aefe6f6d9dbba101246d6737e74e8cd1242b2c81ddfe30b59838b3b0ef9b87937bef7d1d5ecc22163bc4ddfed7d2f3e1d846cc8b7c5b6e074ccad1784a4cf5ebfceceedeae61c0819db9b2c5e7ee88dad0a0849ed83af419e75c23be31f24f3361f7f74b4b9c86d711ea7bc04892c46362c30f1839cedb2f5b1ad7b591fa4c74b1bad9332b6be46ee37e46d9451f73042e66ed15a01f11d98f0c1dfd1775a1983f4d5673898b821979b96dee66e7d5f5d534f790992c20d0993362adf5ad9fe7dea1a3664c3ba76ddb9afbb6e03fb0c267b90ec8cd558638bd0aba56bc89f8b3e9cf3d2ba685d1c4df420dbc15f347a9bafad852f4a1335e465bcaaebc7deadea6e1cd20224c5ae4cd3102129ce358b918dc919267990af59e85875f6dea06d95f3382482292f4152aa0f202922aac8e763545a5266422faca182091e24eb55fbf1a4eed141fa9cc77e0749996de62284ce1ca59529133b48bed4ad66f0ef7ae89cd641ceaecc7e2f76fb68ad7490ef6e4f7e6ea616421add03267390fd60843edf84d6a77b4b0ed2a34fe66b9f3d52bf1e0709d74fdadc2f16ffd566e120e36cc6ccdab61a69ac363233b4ba0b9337488f90b6f8e6ab2f360b997f98a421edacbce663c691d947a90b17dd5b98b841faabd1328b3e277bf33658039336c858a3ffa38dcdd896b187619b206c901056c8bcdc5b5edb83eb6e8ed4190292029b47292f42a6a95a839cd5a37dcf986b0fdbbd1640305183e479eb6c0f2bb5d6d1c7d2209bbad66ebb471a29f5478364d5e37acc395a5985ebb9c919e4740cb6eb7a5adbbaf96e4ccc205fa5b6b93fc75665ee1e91212665903c394e37a9b3962f7ded8c98904132c71afb572dfcf518da24d48f69b29f84a2f0814cd0e0dbeeaf1fd20b6f739cc7ac81613206d7fcd88b8d357d7ecc793c93840a8136949da6bbb1764e5367865e9849224cc4305fb5b45f5bac3de77d6854519e3a4d53e83d9e182aa4b21e17c2240cef3a5e70ae581fb4d13d189c7d733ab53d2b6cfef00b72b1757d7673fd66a5de9cc72ee525488aa7a33245c1c40bdc5c65cdd256a985b1b92e3c1bdbd23abd4dca1ce33c4eb16f9412d3e154139214fa4629cb45265cf8371facb71db37651cb792c1ac540d1888b8f4adb026c6ef56aedb9c6dd1a64fce69bd746e79abb4e3d9e59fda3d22030d1826ccb1a3ac8fc9f7d6fb61504932c4867eae6643a2b6d079b731eb766662c46362e265890cdba069d4dcbcf98f6e43c66579071c2bb38be7bef627f3d04132bc8ffa737fae4e8f7f97cce635705f911367ae15f375d7beeaad935c82c46362a26549073567693f2c3087f7af4782610b8e13805792dc765dd84cdcd27749cc76b39becdb118d9a4984801f2eaf62284efc1d51a3bf77dcf2d8fb53637635b6d1205e96e8b7771bfb7cbac3e108b918d8cc91972b1e5a08dfe1e3e9bd78251b90f1328c8cb3f5b651446fec93a4ea59f201d65dc3d2f53187d42a6448e7a6113274886f6e73bfb8cb5fa2ae53c8636860aa938c1a40992bdcba67dac27b747df5db498a669f2314dffe9dc3b231bc184097245a78b31857cdfb4fe388f3715ce783cf34e936b7d642d41da67b638ce18e37cd52fe7b112a43b07aff3ba2abc0bc69504e93f9fddc5fc39caacd6161324c8f962534b5dacd4f9b109f31f47c4e408b24e678f56efbecdb6d08701999a1841b2b35add63336be6fa721e7f189de8854911e49dbf9a6bcfd1c5a87d8b08d2fdb2d141fa66753152e63ceec7ae932cde5c5eec5a1f7116231b6b3204e9bc5ccfe9edf2377bbd0c132148e766ff73778cf673b03d4803932048369fb2d71c8b6d326ceeabc919f2363c630204f99cbdaf6db40d726bb5b24cd334b118d9844c7e20abfbaf7eb698e3846f721e3b2f9a890fa45b3a9973ae9741d87f7b205b7cd545fb2e65d6c1f64c30e181b4363ed61ada7676f19dc90ee47aba28eb782174d7ec7520dd9dd3b665cf368ff152931c48777b2783eb31bb22e3e7e2c28184ce5f63ef39dbeab3fb8603931b48fad839d28ebe9ef5d9e00e4c6c20eb8c2ebef9dcc15ae77aedb118d9704ccc90efbeb7dada0bdfe4781b6e4c6a20df3167a753da9c451b8398d04052d88cddd2599ff31856cdae99c5c80688c90c6074ce9c6b7f10765cabad685babf1aee5fe276b9cc7d3544d5343622203596f7d75dd8ff73d378c233089819c7f9bf9c6c9e8746c2d0c644fc60ee374ad52669d97d1984318dd45778e5f5bcc316ef1cdf687acb137a7bb266448baeec3ba38b63ae37bd85108262f901c63cfc50dd6875cdda10f4c5c202f75edbbe7bafda86537fec0640c392b6dd4b67737da7cb69939649a360c6d6022863be7b55c73ed357c9039b8daba8d5aa72f42cee3fa5564b5a413322ee7b35576ca9abdbf9c57411e259c90ce36666e578d1f9aa3209e394dd32446c926243f1a1bf2b51f1dfb47ab91688aa6695abce15a7562dc394d48ea169deeba7b4e48d933212fbdb1cec66e7d7ed15ac79890942dac7fffc218ed9b7665c3979014d2e50e2ea74e7b5d670969af853e9d9dcdbe52d69590df78be77a947cbeebc8b12f2c50b639d8bd955198d91eb570d48ca8b9094990701e4c59bda11813a1ecffb24e4ac8bbd236b6c424948e7e9ecff47d79e832ffe480192e223e545488a5b33288984ecf6565df48ece7dba2889f4e1a20510394dd3344d90c5c8268c1248c855175cb0dd9ef436b695f338446d440fe30c1fe347445f0df9f973065fb7b51c33c7d5f4428ed4fab5f03942ae76b7e79ad3679bac9df398ba9fc842d983665e5e63414923248db0b1fb63f3dd1779aec0c808591b65f4d9e95eb7858e721ec7182aa4521fe4b2796c11b2d969335bb718b5cfa822646cebd9fbcf5bf516991b414922e465cf1d72bf35bf794f4448f7e5fc6f63361947f74a0eb19c47e8abf98aaef16bcc1e650cd665dfbdfd9ffe40506208f9d37943d86075866f7e2124acec99bfc5bad967e12384847f1dfeab1edb743ae390f317736eb96773f6b21eea8628d154490192b292f222240534b99826d08ce9324ae090fdeee9a3cb32c83aaee83fa41907258390fe587baf7d9dcc7fc21b75502208c93f21b4f7576366cee683665e88501208c95ffd7f51870d99afc8bca9420920249c175a667eaef7f99b4c09257f90b5698dceaeed175f73c779fc1eca42d4104af45999a699d0342d1794f841be65b66e9b8fceda16bb73913ec8ca13de1559a3b732636f2624fa4817672a94bc215d64fdf74e36d763cb66737c908de965efda581d8cad35b8dc90b5fa7dbfdab1c6ff1c4e9333926962deb03312e656286943ce0a61bfbf8bcd7e3d1925fa6c28d1a7232a61433eba383a6646e9622fd23dc8dad6dfadaf1f46d8ba398f535e82a480a89016d304a242769aaa69aa8f6495ac21efe2ff77bfac46dace2ede70e56644bd84123dc8051775d6ef5ecabed52f324dd334239aa6b678c3a2cf9b1135753f9d2794a8216d63f1babecc2a3f742b461894e441de6bd9a3ff36ceaef7effd16519dcb8c123cc8562be5fae0ba6e417fb889d3b4d9cc696290c5c80649c91d2483d646da5f27eb1669b483bc953ebfeb75ba66b7da3a48171964b8ae57ea3532ff50d304598c6cc028a1434718577bee45065d2f876f45c6b6319dccbd2ea8d292c24222d01c64335fac358badeb7cb0c941aec8d7c26e8bcde96f31ce6318070929bb93f6b4bc22abfd70906dd6196d8bebbdbeb0716331b20941c91ba4a55eb9b11aebbc0c574d43062f4252364d038d23649ddf3e46c7ae1fda081b21a3ede9bcfd2dbb191a46c8c8eea3b4c50b2df5e7e822a4ab8e99f7b20e6173c740a3084967b5fdeeb17395e1738990eeba375dadad5dbbd5102161eb0bafed09b9aec9da2124ff7dd3f5c7387dd96a08e922e5b7babeb5dc72ad0b21ad63466f7b3769a5712d186808e1bdea8bdcdc1ce1f78cc3c5d68dc1c8adb1e7eeb56efad6a5ee3dd8e87c4e82060ee9ea5bec5a7737725bf7721e6faa8dfd7cd556a019847c666dc1e7bebecd5717e7f1a713f3a4a011847c3376376bb0d90addb46ff10d348190ec56fabe3d63ffd673721e57b5534dda0c9da0018484b75ad7ec6c43e6f15dcee3c647429408182f012841f307f9f8adf8a2c3469dcfc63e0fa2366f0a1a3f48db9a2db7dceb56576bd5e480b81d15347d90adb2e50aadff7d96b5c9dd469a37e48b7ff9b6bbfe3d5d35721e579bc62548d0f081a7a57143bef59e1bb4f645ce63c647d0b4215bc3778fb6ab76cef63c76e5d3c5345547288ae3adb5d6dcdddddddd19638c31c618ebeeeeeeee66666666e6089f6bce9a81a4acf780462f294384a42c6631b211800e346c48f7f6359bf0d9e9cfd5f620e3e2bfcfd56b3d46ebb8d9e84dcd1a92ad2fee3a6f63ad3646f520ad3b9f163e74ecae7b5ba3c6f2e570b1079b7384f1c566462773eaaea3f63e7e1102aaf8bd409307d9d3be6e3cd9ecea98313c48c69eb7fb6bf6838e69c71f9a3bc837db337e34fea26ddd0eb23a735e8fe9a477cdf60e68ea20ed73cfc566975d66f05a3ac8fa3ddd5747e6bfdeb47390f15e66d4c27add5bfea61c66f3652cbaa6d4c541def637c2e53436beed3138c89eec208cb3ddf6e3c741346f906cfb7eecf726cfc830529f4d1af2b5b61c6cf3ad7aafabd70dd2adffd7cd93da36c87f7d216dcc3247a78b970d32b668a79bf4bed77adb5d8384cf2efbfcded76c9dedaa4146660bf6856cb548df6f1ae45d0c23a4ccbae69af58b06f996bb95ae696b3357d73d83a4b4f65b77c1cbf0f6bf19a4c7199b7de7e5e674fa6590917d652dda66f6b9854f06f96ebd75dac9dcc70ae1a3212b5cdffcf15d47eefe31c8f90e63abebc1d8dcdf8a4142eabca67f43ead1551b06d95c5c3fdfa37ea1756ac120affde6da8d6fbe38e9d72fc84b678c76c667de62acd50b923ecbb4f2ec1b99fd58bb206d6d8cb5eacb7573ec552ec88e76b1b5d3f67df55ddd82ac8f29336fac6a41b265c8bee9ab1f6d8c340b92cee83ef247cb20a38e6241ba1aa15738bf3d63cbd12b48f7e2bdf0557bd77b736a0549ed62a6edec73dbdecf2a4846db32ebd6b3d7cc7d5241be5358df7c5fac7ae3390519d775eea96b8fe9ec35a5209fadd459bcd4b5e7ed4641cef91d6775bde0d7f6ce9035fa85f0d508e1bbd7452848c8ce27bb6af92db72d3e41c6b710d6bf3dffb2d8a213a4bbcc42a6def4459e2b3641da47e97ad0ae09978b8f09d2eb8490f56d0f2e415a7f73ad1721bbf3de0695202fe409d9196de7983b4c82646eb3ce7ed6dfcfcb201264b3f9bcd9fa864790d1c5b68d56462773fd4690ce1bbbf894de7efab408d2dfb608e3c70799ad4904d9b53edbaaa5add9f8e21064b53ee76b8ed54b2f83429097b63b57d7b7af36c320c816ad5bfd2e7f9bec1d10e4ac90f9b6b37bcc20fb81bc6e3d325ff7e9a48bf94032e89ec1f72e75ffdc3d90f52bafb69c756c35ca8f07d2b5f6eeceb7dd4819bf1dc8f64ddf23adf6efbcebe9404ec636c6c9de0bfebd3990b679edba66a50d69b338900debb2eead577703d9ee3eb76d5a6add84cfd940fabb5f6ffd67f75b73ce8cda6fd1afd0b9d740c29fb0b27a978dec6f391ac8f6ae5f9fbf9beed29f81a4f02b6bcfb1177b95817c7ddfd7357945b89c8d8174cbd8ec4921bfad0e0319ebfc6f6fc50927645a86f4b71c4f47a3c775a92343fe5fbaa24f1a6d6d97db0b6465cdda36cbfc2d57ad0b248dee35bcfdcc3563c8bbd8aaae27ab1ce784ae1143fa5ddc225bff1af4e8bc13f231d6e072d6debcce35e7847cede8dbda70bd5ed57d1312b6ed78617f378cd35d1332b6d7ec3ad8d5bd789967423e6be3ebf6d31b42cb1c13f21d73f5fbcef64bc86967bd7e2f83cc685f4b4867e17ff7476baddf5a09f9f7bdbeeb21656e394a09b9dc3abd0e7a7bc70a27216f3fdb9a316ffd6d1f9384642e3a73cae6eafb5cb348c8fbb73e67f359e7e6650609e9bafb3aeb1f6b3767f608f9fffd6ddd9d73b62de608d9dc3f658d6ffb657b1b212d85ebed5a333257fa8c9090619dff6b45d61eed22e4735b8b5abecc5fe354846cfada6d77e337a77176222465eb229df0355eb3672342ae49ab6dd0a77de65cec43c8c5abf6834d27fbc9b00d215f5bd6d969d95b685f77212474b4ba181fe3f5e6432684b46db6dff65ab3f5b28f43d246dbbdccad166be387433eb62c9b0e5aff87d10e42366dad5d6bad20e473eef2f4e8de318d3110f2efdbaf8fa3f5c72820e48c6eba7f0b36cb6dc23fc876aeedaacd9b3af3e60759e16567ecf274f7f0f39efa48369173f441322f8f6ebab518f305b90b39de9073725f9eab3666bb32c879cc2c0d39f8209f3d5ede7aaee777bdc779bcfa0b39dc9097327bed2edb7541db57c8d1867c2eda6efc6fcd79a333a79a5b8462b828401672b021d7b42ed6d77cb97563b59cc7cc43c8b107e97f9fe1b4eded5ad5ddb5de629aaa14205ca4ea6ecc626463801de458433ea73f2bbbcfcf1b7cce79dcfc1239f4206bd7e598ffdb4ba9c3c979ccbac83475403e9ecea7936eeaeb3672a821e18dee28ad2bde08b945cee3694a22da7cfdfaa13e33a24e10d1344d53354d4080cc70e1a323aaaa8f21326d804cd334d5ce34b9223f3621ea6e3cf5ed34857ec30800478e3c48e71832ff1bdfbc8e617540c88107196df51a1f8b34b267ebba83b4de0e3aeaac33c86b393bc8e5ee9dbb8bd55e16610721471de4bc17d267635cfcdcbf4a07e9ef5683973d5cd6798b73905c9f33b57e7fdd385ddf20871c64ac77ddff07637d74d6fb221172c441fe6cb7cfaf9baf3ebf8ef378f3e01839e020ed8ab3e19dac2b85eb43a056418e3748d7adcddb207ddc2b726f380db95ae40bd7a4d429576b398fe1e3400e37c8656b75932f5dd4c55579866eba3f44a6c9c275841c6d9097c1fb1c63af45ebd41fe7c9e460836c7575f3e8b1b24a1b6d1bd1c33c167d9098630db2f1737e6b756bcec5feaa417eabefebfb6c0e7fd99b06b9a83b5ae3a37da1adeea241526e7459fe37fffff93d83e4659fa374da7663f4f56690cdb3f931b33ce7b3d396415a0bab63abebaa707a5b32485ef3dbd5699fa50ed9a221eb9ad3ad6d95f6dbbe750c92b5f77c7dde39fd2dad6290b12fdb7fb4b938b9d21a0669179bcb2984ccd6d6af8241ae3aadc73add3bceeaf50bd2b2ef091757e610ba55bd20ed74d4b2f7ac1bb37e6917a483b6ce7f2dde8e943ee582b45da3ffe2c5fafeb2740b32aec8625cf6368dd629d582b4afed6b5a5f73cbb549b320a38bfe91f2adac9f6b140bd27eeb369f3b579f2da35790fd2d7a5dde627c0b32b5825c6bba8eafddd51cfd45ab20179bb53e66deef61a4930a9299bf76e7699db519e714246d6daec6fe4ed6e6d32805b95e83d456d74ffd7bc628481a1d5b67bd315cb1bd78866cf76c2d7ece277db52314e43f87d7adc77139641c9f2027d308fbbd06676d6b4527488793c6f7decd9fa50f9b20e743f8d8f3e89c65944226483b6765cff432537e0f2e414ef7165f7b87b335e7500992bddeeb7132d88bdb7d49909399478e70fa74bdfa2141be175d656c99df11a4d3a68b3238e9f5690469d7a2ad394bfbc6d56811648c8c3a868d3173ab3a11243386cc9e5617d7a3ed86202fadd0b6a5d3df53674d0832bae560bb752dc3176310a48ddc6bc1bf0f3a770804d996a3cc2ba476adc6dc7f20fdf662d3ef5bee6f6b1fc8fbccd66b117275372ed6037919474bb93a7b7651ca03795f73436bdde78b0d7b07b21973e5c8e2ac1e2bd581846c9b356df3bde5eb9c0319efbd8e3fb275f7d98a0309e1f467b13d7803e993ffd6cbae8dbf9e17e7f1c7cc8faf20870de474c7663fb61665d3a319b2df556be7d7e991d25703f9dec5c9fc60b3fb5cbb349090adfa1de15fe89abd33901e9788a864f0aa959196a22086310010425949150063130030302c1e8d86a3f178a2287af203140004597e6a9a422e124983c1500ca3208a82180662100008208410628cf14a116e00c03ef6a440347b8ef61d6168e8db8d0df74bf5611d510b68ad2dc4e0fd4d8a705a5d0d20b5810f42cc59fd9f4544d406b19c83a5727210ac88d8bc7a22b7f22c74413e376766d43f788a5fac2fdf055a20aaed78093242b7db8e7c7791f20bbdc6d4e1d1c0af715c1647479b78593ce764b6f93fdf909a5f5470cd7fea27029f280eab52b3137df15661e3323b8ff7015830f651a7273da49283635c46bc941e5f330b5c53695aa4c414c740ac11cc8cf434f43e3b88e4e300cbcf1b5855b61443e3b7cf3ae2f58cbe3a857309bbf341814fac97189f5c7dbbd563afe96028de3f4d559c637775ef79a3917c321f061cf708f244ab71818e38d3693729bbadf51eb454afaaa0d78a3f4d96679d2e21ac2a4724c040e4260cf2b64988d8840a4f118c18df8bb57c4b12d0f1378c3da0aaf7845d991ff3d2e20506a60f0aa374a0c8efa7fca2a0a21527897b47f660b144cda256ae37b42d3e55cd014e24343b0245cc00d7683431844a4d024634b43b04256600d928b43186524d014344b43a0635a600ad515aa79145472ba4a62af3940685bac813d562c2a15454487a9b188e64b508086641d025fc6b50cfeb6ff590124f46d0febcbdae95a0e40be251b52f6d3c1b83e9328941be548d995566b1a4bacbc4fb17900195418442a82bab0fe602070a2030b4eed4f5c941dd598cb823f387efd4d4632b82e965e5d48ddeaf1cb73606e2728a1388010a05ff86fe03848fab7cf105a2e4006f4b5c860909ce6cb43b4223b61ad1179b067e396222d21b4b2fb206a21d49117b35622ed207421dbb22a26e4e92315b2af3bd8e0d743812230636e22eb60c9c3ab2a2440bdbb494cb10a390de587a252ed413d3d1fe6ff0381dd8e5b83a72928c8a309d4fe6985bc7bfc47c0397c5f1dbe276fa2f2b5ff4b24dac5c183104e9264bedc585d4cb8a6f1d058ca38db8ce6e276d68cbd03a55b24bd0f5d2245eb95cd7fc4bcf0aad93978332a153c81bf241fd5a11d38da48bbd066e1cb1474aeaa8c08413c8ccb8a5ff1bcd97b5b4daf0a490ab1173b17d4c28bf2cb7e87f9bb272156230d209961ab214a29b2415dbcb2d9a5f38cb4feb8525bd6b510ddf74b7417f656659966b0d9b666a9e068e85db46721620ba2f4a001204182f961cef46f5396b295ef6f0725dcc37df84e951d1da2c706a31db74f290f1fde9e2a18c31dc6c1ca3fd8cd71ea7da9dbbdcddb6d970c5a861734295122e6558f85afbaa0ced3d0e494ddca948262bb01a15bd7ba69d12db681ba49b30cbdb7d19f28eb2710b7b85237b6b0e9d4bde82f1c80913ca03e9e787d9c605cec4ea6c6783853117b6c7ce70855075dd0ab7eca674514177509eea1601f155d06c868f7e9ac0a2bb4a93744b2c62e552a7df82c7c80e9410c6568f1a106f5926ebb53c43f68cca8287dd6264cde7ba3e28ed5506c217ce7c270e662b2b5907a87fe8cbf965efb3b7b4b5c7729ea531371476482df40841102bfaf61c052b0f0f36752491b2b48b4d352190a2dbc7847ac805d697dac9d4db56be954d37ef10cef6614f290e60e834e594ac79483a807ecc9167e8b4e7fc17e64d6a7197a7ffb60324d2c0f265701d05829a8c5336af89c889c2e384a5052572bc0366cc4b3f92f636866a4a0ba2bb4e41041b08b68d12286dc13911d2ed884c9befd5c8c24c76df134c28b0aefdbe99986f8071a010630bccc416981aba14c7171b16bdf4baa4b93303585488bca03c33f1fe37cec4ed5e7db1cd7835346c12ffd18a84c2146237de06cdcdc199f64778ebcd2534020bf1c675f868d73e0457b31e2ec063f3fb0a85487dd6f5fb2fd7689e767eed5744420be03cd8cc05d25fc961271803032f9c728593fcde6332a54317018b5a471ad5e0677b35288e62d90ee373d126c2e93a29f3d340ac41f68f323ae6e2f80e81b27bcb68aada9e470c4716f437ef848369151d5cf6d460e9a38c27532573379c0dfc3517c428f52d4c94ae18c3bc42254650002d2c6493ba240c163a89265f3d0fe4f44eb282f3933c701949a201f1c217b46b28d8f5f9340f6559debd6f3a3d304a6926664c1c7740d4ebe62c7e155015dcba450bd716f65abdc3fadd02458bd856f5b550c362b9c58c56695bf06b51864573ab2e5ad8b6b8d7e28755e416315ae8b650d7ea1a16d32d68b4e06db55c8b362c9c5bd868f5b6c55f0b342ca25b55475d0368f1440ef9b59d3bde22f31d0369abd85d6897557dec455c4cc6a0f8559e314a7dd11cc36957b5cb6c70c7015b75b281a61ea2d4062f8d92666d82865b3a2cbf715deb6b0cd46144d7a1ffad2adf582b639a0c6ab5f23da3b046c3335cbed52e8d6b32de6aa03d6be437b4d3509f63115927af415d07ff1a85606d26c3d91b16695c36eb171bc86dc4df0d3dbdaa93b17e8d9977d0a5959b8c626fd8908db0ce78dfdc349ff6e9ba2abdbc22ab74781919c3ce111c1ef12dfe652bec7b42cb849dc1fa7340d3d0f77f6d6337f7df82cc9804a31b7468715d4016c709787d4f16e4065c7fdb19eaa279bfa83073765a6739bf1351e54f40e22a2ed8f7640d0d477fa262f489c1ae543fa3dad1213ec7a51a841cd666717e315c756d879077f10f392723212f29cee423df9a0c0bc3faca117b65bf562bac05247f0d95d89e1da5722ffc705194fa79afadfc08b0004faca5e3497d63ae05195f38e2262756700962671bde531c050685e24b445c7a0a995f26d17990d010f188d9b926e1d70e778b0f1e1411ccbe087cbf73c6a8274169ba76a8ed86ea20f28f3d7f27e94f2ec26f5cc5086c13f48d54e9f2bb5f0e7aac40cdc85e5c5522d0d07c8f135a0d67d9ef80b87cb7c8ef882c1dfd379e4b1ca17ff286936d98fb4fcbfd43a428c3b888510f14cef3f8a1228446e62f3ebfbf801a18e91203712601f84ebdd7b4c8d53ee143988c3ba2e7cf07f0de2e122141d06a40c9bdaea3c5d667edada6624e0edd34b520922ba36ff7db796b15cd379c03b7eb0cb5923ea410a9d47b0e2f223fa4fbd6e450354e7f2504f4df5cf4e1ac89b8eb197ca51d44e6c78fe0d066e627ec8ba4549b6f0a72cd04ebbfe59cf1eebb1e294fc75bedce8f02c3c5ce6769e661f1af1366453ffaea09e3e2c3dfb8dcfbb9def19990f05124a4d3bd87cfa9959ca9249122ee17e94c077ba424750a2a93e1fbaeada94959aab5b77616f436a9c6f788db4ad2b58c1a6d0e684530461ea6ec69e43b661f939b7b3cb02792844409aee219971f726fc7e7d87c8991a18a7adac404bb843238f551c28e41c55f69e772ac4eb54eb212708e75e70f176c0addba40d31567ed46f177816bc26d2282a95b7ef80148e8d3b689cc47ce52f679a1435fca2503b85ef17607876b7f92923f06f74b9908c0cc0730b031422c631b3e7a26ad511bbebfe0a908d334737b839333df9933a4b779edcf934dc68fd7432a623579ebe5cd3188f5d1aab6bf1ce093dd198020139c92dba6a4850e0596c1f317978aa487884803257c71fb8e8bbfcb2566fc88572d71082992bff27a380da0e51382d7f438f4070dd56fa239e89f7e4888d5c9ed7685e2290dcdf23aa6a8c02fde2cd94903d347eb7033a77246c998f064b19f922b17855903e69d553d04a0f6713a78de139d6d1ceb4694ce727a5c27ea06a3249a2e2a559a6da78357142b20e0089356cc31a9083ae55380fa68403147bd78ece1123224cc9f5dbfa4b07e09f8bd21a114249896b4df49dae845a4cb3cbecef597936000d8be25cd19e5b88bef4a557c398606e21965771450f4a5aa10597598a6fd4bf570190c308bc353d738c6f7891c4f12ca7b93a81fbc00505211291fffb4fef73b01f66eaac5a1bd1756c0818ea8abdee109d333e504cfa007d240142d0039dc1dfde81437d3e7add96ce88716644aaa3e275acd46d28fd9dc73f345f6b3f66ab40446a8c05e06ddd0845d07bc27f6b7cad87d00408eae632c5022bc42de5f160def1dc433bb5fe55d7b31e80aaa249219f58926c5970b2ab480bb6f8faf43ca4d833d97708dd997e0897c4ffe2efcb8f305b23c2015a9f70502c42bc0e5242403c3c90272dba7f8fd2538604a5e6221148eba4566f9af324ef844bee428366f32b279beabe6d0ab7e9c63c7dfc2e59e9281e8305eb71f0391a8aab77a1fb73db179e58d100ad2b09be65dddae58f344dca000f71200e27b63e0e7f9563481aa3dd2143e80d118a5aca32c02782db66e6439c9cd38c4fba064de9bc0de147522bc701f2e00f04212ef34ed702e017b623e53f9e48fd659da91e073b3ae75d2861cb3b41a77770d3627fb83aa75a2e88907482fd8586f69c8acce86d35a3b515a06c59c3e1e16c5944bf4e1718d6261d46e2fec908b569dbee34e195b6d751e188d3c60822849889cde55ad4c92d74111e0ac2b65a50683789c797e0d99f3b8b9398b744d2cc17c366868104f1b9f9db742325431149ff2546b7a51514829021be68331d8dd47b31d3799476ea3020419016cdb2c37e6a0a96dd19bc7e99a554f2a2ca1227f5db47b7e749526c9d65fee40e3fa2d6c0a6311e014c430a0f3220de787861ea4478eae17993b2ed31ec8fdd1ca7884d399f2e31fc8c9fe12c6c38890fd6b190bfa1341e63dd6f167abe9986a0853e59c434b30c1582a5385524445e2d7beb7176add1b8ff3d1a26b790c03697bc14646b4429f71eb20ae4db9a8e6b2d5d05e33fdb4e71caa9d35f692746242f49b6ccf5d44df5fd1011df591a66e3b9ce474156ac66113d8332499af1a49a1bea188fed2952c70de93d1b7779edbcb4c5e51b5c3816a728bf0308616e637cef76fa6d26ce824c5930dcb0dc1782df32613e8c29dd89461967585c0073f204507682b1cfee09e7cd246e0259b780afbf3fb8e297b2df2898245b5ff37a816a148cb1c185a76798c4209ea1a6cb3bb71fedf267553e622a49767e724aa594ffe57652577ca2036b1429cc9e7a528185d4302b9dd17fb3ef69c66a59620a9ead454d36ba99a88a8b94b1ed22a5677ae3fba95d37a179571f3d4ff693c1de9a1a36bba217ac15f026aca5ec0e28188eaf2cc243f89bb9591c44da6b52e9d773d17112fd1eb4b9df655206a6b87a074a0c55fdf375e319590e62e4969c93b0e254a792ee253a25c9dcff4d506ed53961066aaa304f2b18d2b4f965137a6f668f9e2914cb8b78f8590d852023ef7afb80f030d5659ea1613b2dbecb41277da0def3deba0051b1f5e71c93259b7dedb64d844c61cedd5a8c377739bcf408afc50546cab65551eb3d8615e6f1a500fa5bad569a772507b12a557abbfef016b4e869298811cee56683b7be466088f2050cfbed4cd922dc70c3753a04dc841acbf06d11da42057d57e45f57fc304edf0d22a84a55d982864e8c754e09c40aa1fc9e134d83b965702b8a48bd9075ee7108fe36a68d18034e3f5bf1997d1bf273880807d32fcd6487bac28315d286ed401814f921de50198bc57604ef4674175d442492f921d0630d261ab364cb8057aaae69a6b334aa85d99754146b59264b6cf27be1b87cf894fa7fec72f3d4be3dba0344647c0dcd4fad9a0dae4e20a12947f02c2f33c9ee5110e8a83a04871538980d9dca4b797ecdc32a735a53528123741455be5d05e27968fdfdd2cb8f81e75d2d902be426a9ce9e718a14bf41134e6e93b4e3e0ae32dd9e1bd8cc355dc6c58095a4e90d8b3169fb0ca5cf2c76dd4b0cc5bc19b54665370d63e51191cf0c7863d68ebbc1f22335fbd4247b004a43ff3e5e398e32b2b5b00fe91668af55a94639d340bb836564cc5503e18b7423fbeb9204554b9b41987bbcd82afff1362d666b76baaa3beebd58451579bfe256c66c5291132383fe50a489c041394b6fa2501b98b8d9ad5cfdc79a3a7172817c0be07f91154af6125638cdfa772e5390a4014952440686d30e572d1242c68d4fa8e1e4c381e1c461badaeb023523944d8d49a3599240faa8fa8462137b89c17a6117f2b3a9114421b9a1df747148b22681bbe9617c0a6647584499be813f8474cb31fbcdca8572c269770c1fb721ed75c3be1d0bf36bccc99d533b007ba7f80ea2035f5c5587f28bc08da9185a7e80d62ae65e90440ea2df604b2fd2b747feb45eee25eeedff5ff7228f4823e108e7506d4b0b8a864065686a2fda4f6b056480ad750c41e436abef1444eb1201061109633e14a99f081d0df7ae3ed84a69f8ff8095191e9f35e7a936588e3bb6e518b1c8dd5520ad87c6662381c920a44b1368709cc922e0763574ad0f8a5e106b4ddf2cf90331013318ce0698922771336c3e778b6f92f8298132ed5519b82bc9354544ccc3d8413997b4e641973c296e87ace7f9d26cd58a13461fe902626f4972106d7c1d4b87e6d33ef76f8a840572bd0762a0273f4beb07e74e48e481c7d61f3c6c54e758dab93d8525966583625153c95e472d537b64b9a4f532a830b43043700aaa39c8a7f6e54bb1f3185200f12e9e24e31a827493a4bd3e44849844d0a617b77304829c986729ec4b6c0b0fd58e2703e6fe4d990328d6c30f0ca073a0c8da671939601005ce50221ba2c4a5375e525649a12d86d8d6b67dd4dae83d82f2d4e519410a4b7285480feb5919d995fe3bd95752841aad49190b4d809316bc0b658fc75b9979931c9eae19279e5eb6fdfca417fb9a9f6ea5efe4ef1a370e67c68f2e98a3695be1d099f8731f5aa5f5a14b534368a4c2b4c7b5b6b542709b4becfcafbb8e73e6ce0f7e61edaeed2dd28736ebedf03e8132c3cb8f3c917e0f95cb08c28b3c93fa4e1a468a4e967e8058ed173fff9bc1135503c50e7f6aa0b6a80cf1941a4bb1216db06d8a5d9387c8451f889420a5b9d78485512e496d262e2c170c11a5fb88abfe294daae3615c6234a3b443ce6b7ccf98abe848e7cde138095329f9bf2f591a577b1954dd07d5a278048dc3eb1a1ede406bb26b7b8def94b67fa1e91a49291c11225936b163cb38a1b24b1b2504ae35ed4afd2010d96493f0a2e8c791a596fad4dba977ade6be75e6b32d8592363b0936cd8f9e47c9e3a6addcfa17fce0df7b885d27a64dfd6d8b2ab779470afb259403799c9e50a4e2024d09b92b43ce18776002c5799925932a561ea3512179400436170d10ef6866a5f2cc22b97c81cb495ee77379ab21e4c2c4b5d150fe7c5327dd6bb1ddb9275bd95cbfbd760462a798f00a2787c4366d2942fa8c2a779441dfa3a40387e1b5831698a9b4148380d157dc25f9186455146cd767dbbf4b858576b03dae235e5e9bd54039e34f55a7e26c60b1ce8a51b4de9c0d82fc766a2d032de07011ea0732a90431bac4dd5abed3e11979e3ec6614d12041bdfbcbe16e01f370e9e2c3887385e0ea7f50cbc3cf6e4f2dad01c243cb7bc6267e100b6c01ab62b2f0f0367287fee581ccde8adf52ef81fa2a5eadebbdbc21888550bbbf25b110b3e8f17720570f2421e5ccb58b3c687f050a20eb9f853c9d23bc9e2a2c6a79994de238040d1dc7a0f266158d08a800eacef02346932bc301f712801f068d24a095192310014fb0d1723eff46c802169607eb100908fc48247546758569e9f109b1ef79c5c4a72cce6c4e8c841a88e3b278426c307c76872285f3a058df5271786c874c2afb1b2f8b1fc73bf7d72d1b341c6585f8f9fade1b6e5d6bdf442ae84a7de9d14c82a49bcaa16aa217dd2a1b625812c352cc0ad60a958382702c2f6903eba36872d22582cffec8d4885a1f7ea2a8c21083effbad3cf83d14b75e746c67a69c74fbe61d8c6be7e5455a24f92c671b98117011c1ac04dd9afb756cf440606a1ac349509f3bdc171f51fd526b06fa21d41a74176fc92029899cbdd311876cb419644facd1d5765fe24e06926bf1c2a3c5549383c287d49bb9340eafd09642634eef18504e04ddf0ccf62b06bfc0455f1c52e2978cbc105507d43c5d391fd6e60e243005886e9082a488c80f09564067f9329d546ce0b47435484e53bfb8a3010bdd463c8d4901bee7d2d21222f618e5714580eacbffe81fbef2739c0713a880e8ba7111650da432a158a6ce78f77644c1ecba2cf34915f50ce62d35dbc9103e0aede0442700e43935d1732f89e9816ddf920a10589d0502ff690d3e25663d2e84f49ed758909865c01fc44c568d0b8bba6e1f9bf59e56a146f1dcdf4e4d458e841d5836a6a57c2ae2e04f2f195093c09372dadf73ec1ef940daa4603f770d4ac65338e0f90cd03d758184879c4d17767de5108b0d4f22db99048f10f46f0d321284058a158a716c077093ca5215b380644d1d922e1145a7dddf716015dfd7499a381be17f4ab2ab9fae43d5db30973aaafcc5223883940d0d3b0a7833903c90904293c4b7b88c7e6a8fd734f5428f0a29c4648c418a6e0357f2ee9c883daa3135e28eca0a75e06b18b51f8081c452114027f1b64006c59047e96c200505405f048f82d108130e593f0190a0241d0178123e0b64200c7a9f2fb40a87ccf1755ebcbb25c509d595394aaae116e3932d53b538c88877f023f9863bf09f61898f179bc670cc5efec43c1f319c1ec5d8d737aac1a2286c1787242140145936f5397db79dc31f8626dfedff3101993725363c3246bbb538d2c454bd0a25cdb69cffb77f6130391dc204e6d7f6fd2bd7aaf37f649bcb48f6ebfb5533becf4bef2e5e4530a49a088f88fb1581d153fb0200621229d9f3b6f87b6d007c589f3e4b8241b5549fdeca443fbf6fdb5f3ca34f45abf8368f47e269ce28e1091d392f91a755e647abcedaf931fc270206837028b605727b0649f35f92ff79edfef7f31971628892ded4412ffe68702577d8c0a008cca2b21dbbdf19ab264a423fe5105c80880bfefb1b04635a45ceed05040a342edb74ab7298c817ec3a69fce4c638ef407b43a4c022839aa1542ef34c57aaa6f86ed755e9199adc08ec0cca900b9f9055148b23d931eb70a9594e72389e1e639be4641befb49b6283b53a6fa2b48a27bbc90dce3cb532319a6fbfbb9c132e1ed0e52ebd1d627472098d895c0fa10cebf8d7f3a3911e0e5def01884ceed6d2bd854ab07808e83942d3b37b0feeaef0f36f0a87abde0620696634cbc1466917b0d2ed67e70bfe1e5e0512cfcb27c80e8d7537cda2a9d93649fa61e2fc705df0b6ad3f520bd8faa12140f6b52398b8b0c503f11a6eda3977e60cbcbb8fa55b389668d5f6e258d8123608a591d9cd52dc24a7f7f8debf662dfe958de04526bd4a9c8c37b8c44b1b7ca7bbfff6357afdf6edf047f13988dbc4cb4641bb9003658e1c763c8fd206b96821d1281d2fee0be074c3b4b56a48850e09a8eb9c165631d805d85f6d238b847a676428dc9b04b3b742021d7dd890972be0a32abbc913558a4f417fbfcfb396dd189dde5623fecf9af4dda686518cbd2db55081fe335ad8081db398c83421cdcf230ffd96721e7d8d921092d617cac5a8bf4dc3380c0d83ae6f739318fe3936f4f021b45bf1a112c9167a48a04079b8422ab4e70bece405eed0cc0626fc4bb1e5a1cfedd31c74abfc41b64ec5b6a9419a002fd88959e72a605ed59206ae936b79ec0a9743f981e128e05f709a83521d048c3fc2fd7f9607042476c06ef5fabc2332c583f68de3c4bd884da90996ff53012dbd7e66ed4ae4ca24c7e5461b4221fc0e77752678ee914ec852658452bac7c7320bcd5c11f32b09bdcfa49f714cacb9a2076eaed96c46b1b95806c65e154516a08832d769fea12ed0504340d1f8799d77757b00f3a568fc137fd06346bde0d06ae1f50856b6c0d79c1ae930ae85c9b9ce4d3d83f6bed66187a6a6703346b27bfd6bbcb0daa3801ea1cec5af7b85a76c7ba94a3752ff3d6083e5f4ced826068453092c869d74847ed76ff96f6b8258b2f752d1041431af1d6847f51ad9911ced22d2c5ba38aa970f63c7bc28aa647caacf1c0db3a612a35d5fa9b74efc917b9cd8d727ecda576f0fcd0f92fe45e0be1fbcdeebf68f460d09511647b6448a2a791fe7dd2a392defcb3d7e461f68b0b42ad999d645549e2411ffba6f120fbac7af9c24e72d818e96a8884a75b72eff36d0e081ae6b030de0c3037d1fa22e8a99b29616cc7924b0319346d756f3ce9eb78f62d9fb0a6319845546cff6f395d16e19e0f370241eabb1cc453170fdeafbe019f0130e28814890900510e15cc9d3bf124b28bd4eb0018a8aad49acc320cf08d58f7d32f486ca56ffaa2c83b4b394cd1d5d9e39cb303ea2de75b2a3f5c7e3261a345f2a3919d2ebb0e1a873e9a76d568bcf8eee7649b5973acf71bfea9e1be04c6e36f387bdbcfb65e5efe2d1a73c70e79c9569724cc3fcbd7ced46ca039b2140a46307f86a172b558eac56da7922d72378db41c14ba8b0d02f70c436c623a01828d724342d000c5d6dc9e70f40013035e4d1c9a5559ef39bea42c5cdc001c6e735390bb9370ec05e99fd364b6b5068be035e66edbaa08b38db961fa35dbd2ab754f1f8dae74c8d07cd36520447eb6d52e78981a22dde389761af78e72d7bfa7ede68cc5cd4f175b5b6817119e9db4c71f2d4298bcd5223f37d85d72e5dedd8466ce5c0679e85ec930bc8da509be3654062107f0dbd5789fed93a1e2ff2398ad48317786135b7b10d3b8081bf62b3bf4e26f338bc7a38b8727e083d4ab3839bf8427135efd0d82895bbc9f9e5397b117beabcbdd403fc0c9f13006e2a699819352b73a9627ece9df07df6b4e93e8f8a3bb9ed5baff4915f9987dacf7a409549f77ba0f8e58b5354921994b0bf061a32ed41e3dfcf4d534caac4356eccae07c98a338af9245ae7e1addd5dd6c0c534431875f09cb334c344966eb19066d6b150b8198f40a6d66afe827fcf8cef3f3c3b2f3a31c992e11fc1fab3f4d802e4071ff7f1dda64cf41867347a4d25d98fd5557a34bfd07b77efd1796d92ffbf28bcdfbd3b8ebb38ae393879a8c44d60b8de1095c7462fdf67077bacc80117e78ad2c4cc86081f24fcfd1238d23d1d018e194e50f287ed4bcbc51aa836cff51729b9a019fba6ebdf979dfb7656ac33a5ebf12f9524af9d68ee00f30a1500ab688edcf2e495afaf7504a8e340e5bd8f9de33878933691f04c72feced32bbe92e301d7550b2217708cffbbe632094beb5a9bf23cfdbf5aff71d9d8556ecfefbd326ded2d331e1ad67f25f966e604c89445dd307500817c9c5f9d5fab86d44fa7ed2737c43d3782e759da40ad9e8418cbcfe50cf36ce4de3e479a4ac5e23c690b73b69f0d27c5983ba79f37d4d97ac3dcca5ed0dfcf65f11b6a7862decce17ed67689dda38a4df71563a9329991b1db0887b720a30f28a74ece4e1a96ccac07988f462854e67f9380c242ef09a8efe95a6be4c02706f8a767765b67a7bad4e8bb479482e163264db6521dbaf70ed6bff9820e3c1f850a06e9b37b2eb3ef95956ca3677c0c84ff827c61cd492197eb3d2e0f8910ffc73639e9fcc2978aacdbf453d307f5788445cd48e339a0bcd69e4693bbb078598b3370fc9a99ebcf8217dcbaf1e6df993c58d2450c4747bc637db8d7d6fda1a3beccc7b28ebc28e5638da8f94a997f6aae02aee6953dac94fe83a89800394ca57f00c68c63dadfc0476428cd7ae79ae796f362a21dacbeafe232d827ea9b47b8d88cb18f9ce661148c2dcb03e7aa80730bbf4b12c9b38e77db696593c1b9916ef633bd1bf164a9e70353d5d73bf6d4ba405b24700917aaa4076f11d10e9ef21268077620b0c9fd4075feb4d4c298bd85913b18d854e909371b241a2b4cb7b6bade9fb7fbc8dcb67f631955134197d30f13b7a8fae12b7307426f55ffe6c477d4bf90aa352da52402608ad4e91f14d414b0fda2cb9567643c8f6994a6a0d1704e7bbb38e9007cb4c52748e7b575960bd572b1cd338a900d8dc2f4b95a2029c99579767e666e17b98f2ae3e438f99c46a420404039b0d0cc2f151fd5359a1ce6290169a330b9f5c73b7fc705ec9831fe2b949869b74ec5e83adc3101a7a6886a72fe89a748eee48d4f270dd00f1e982fbe3da37c00062b8bccfef710dc6a81764d66f5d05906166d02da05b041e7fd4e890bd5fb63d717f5e03f6f8271f8e32fa46e753c073c878a0fc3d2041f4b1233e3f077e6e22b99265becd40641939794df96b46c6c78810fc45949b6054245f53ca2f71e2dc14f97b32007710852f07930f0b2cd3403c85cfb83e58e073719f05728cedd3bb2b3d194f96d3f0bdaac5fc67f3d782be3c84c7fba177c6ca61f307addeaf3931510fc45d31b9504ecb6baa7c2dcfe637eebd6d6203863f3fe95d91a5010dae7950ff0b6c14675dd77f8e2bd9b37f727af36be013e00dfcec11044c80db44288f783507e69878ce26e0a9c6e29ad5c8e0a181ee9ed023824f12035680596bb80758461dd835597f05605ef5756d2edfb8f1ede05351ffbc2752e1f3b016ad321a362abcd28e251fcbb663d246d1402f1e10990faf604733ddf1ecae3a66649f9dd3a1b9ffbd132d4bb1af11ccc174106ba9df3dff558c03489ec2446776e2e52190492e3677fbc865a4dec930d5a14d185ab7bc0284581a4b32fae82e1b384cce3317d7e8409fcc7201be49c10488e2288694d029070641f57a0f8142b559a5622366c4f91f8f41f315b905711d18a3fb5b310107d5d000dcd57a641e325a7aaa283301becbaee001fd693f2626aed0ddd517d39048fc4abb656841ed2e669be012e160271bb44479cd8dfc83ab08427e845060c849ad94db01bd5156a8de42c76f236cf1920e56b8f7798c61d1fef9a4588147b4dad0d3ddff7afc5bb02f4157ca808cfff53b0dd86a760db56cac048ee18e3b6d0887aec975dcf9c1aed39ba265ed82ae74101403e46784fc96f9a7af9444a96897f7a73e2d8718abfd497ec01215e841d0055022443f0d9ad14a05e097e12380a508e89c760496aa059c03ef8104419aa3f0292f801b06e1fe6c81832d45054316c108901d5428463b35885f8a87009726a1b3da1078ea367016ac071a0c6b8ec45154b369f48538087e65089ea88a4147eed153813ae0dca8722812429f1ac56f4503e04bf3d041650468761b3a8b56ab3bec12c0960d73d3631b9cb6c664d30b1b886b0d58535a0d22d42a2890e4319a7e519017c1c320f662000729470184aac04f835c000b8bd09f0518a0141500be123e0562210c7c927e0640a012f420f00ab85408c571e0097a9900c0cba003402be2d02329c68025ea6107c2780a84746a541044e419b9839fe63a538285e0e1ce0686965d0606c5e0fa05f9317632c0ec2a7f75871c9b7ff2569e640323df9a8af16a107e6382da6bfc5e59533c6f14cf03a726b0ff07358e2aa814a3fc686a78aef0fcaed790115fa9eb3264d024e82abd80d0f7a74acc55c46a8cdb3c593cf95fd08a08b6a659bf02883543418f71d2607bae5fed5207952f5efb13ee73cf597cb8c58306bb16d4c7f59d25c23ced72a6bd04b0ed87e2534233e2aaacdb04e9c0c77ff20235d952b1d9e69c8c7abbcad49ee595ead1070fbc4a5938bc1bb1b4ed431185dcd7b92dfb8d04d4fd0ebfc0aff2f17faee4309abdd8b1e6f2a995208112a5ba83d4fda2cd1491bc9e199bdca4223522bb30dbe198844c06cac52414a4b7f46c8bb4e74c9d4fc90483b53c348ffcf9ed89ff800ac23b2957a711031638dd9b21751184fb3b7c3924499647a200cf88888c0ecda7d14584a7eb5dbc02d64bcfc80eaa3d826c087a67e6c4885939f74f23cd37808172a3ae9b79134c040da756d6da5372617d339c8b5fcece04e71cd32cf605d7cb84fd98a3f5e10c6c844207c88f2ed27e838d0d243af92d9e7aae63909863d5493ce0e0ef45560a0eb9ae0243cbc74da16d171fdd1bee46a23f07c300cb726c60dfc23e271b623f185f69049fcd4921e50defe37c4ca777bccc8174fe80797015468743fa65c57f7937ed54214694d69028287e7c7552e976a23bd5c7c46f7f52d1b3473333e136048abd60f26d47fb65d7ed16814a7eeec98e44274b5229637ab96295fe0a579da178f26631d94cb012e0e1fa37facc309a51be243292a89d42412680d2329ec4b901747be18fbbb520f9a4f6e6c181881ddc5bd3f773658c9a160475b9b520a8cc5fc6c470c55e364bfe6263a07d44c8a7ed0a045456992116ba411c0caca952377d9b58b27d6758c597cc89ee5835f0848af387dbc8de13765e2dadce1fc03fec394a6d5b747e8040e08010ecb31ca4db31c7d985e7d054823549a65cee90d3d9afd64ff61b97eb7c079fc453a55c4d97379bc2bea3b92c6496a265b012a3d3f35ab0fb09ba21d3422896065612374eaa805ccc417c6c2f8673b9c33c8b5717b042d9e0db14b067ca06c4ad2f42c00ca7ee9faf87c7b6ec446f8243a85f218f4fbf77410c8d741d3b0463046684fb8e1af3991fa593d1693ac60d1ad5ab4e6b0982a20abb6796f9acd62ddc323d76d24f03bd37cec28fbda6430494361afd74eb3dac0c9bb227509b51b0cd5e4a4303844c641e091976e1bdf707114ecfcf919b0688038a73a1cfa39c050a4a4fe8c1403fefde5d778714353f6dc412f727c54a2c4a9180944588883af17b55f6644e906f7c5de55da56464604c647175a2e28a290ce1b284090410b8f990650c1da1771adf39d831f2cede93121b745fcb95b8a20d428103ff73d7578cd646f65c869b86b44f0b2be734b387fcb464ba2c3fc7b59f8cb0eff2c6e92e05f6cf5dcb173df2c5084ee3ca5606abf07222b057178d722d3fbe2e4f20e277b6f9fb3b4de253680b48adb05a8bde23242d1270b66989e17f365d2d3cbf7c22a1b11b52d023f1ea4119fb2aeefebe5bf7b7d949e8abd0e57048dee8979fa8f9a923689f5399e2691e91dfda84737a3957358218f174566e5a07857b28656c36f624bf23d917ecdb60b68fa859d0b0fcaf511b0deb591e137131542ccb16436dbbf1a3aacd7008e7237e8d58f9d026932331668169d0ddf7ab3a9c8d73c1fd305e7513bc7f12f2182f9b58bfdc36a56c6a6679f3ad8875b493b2c5bec3aa380788a6d35c4ce754139e74f2627efcba8b838ac86085f3ef65b12df9145656a71bef88ecca6ee97a02b79e2691e0e9036be9d6a3b10441186b426bd7338ce79e806733087a7b8f37e22bfb3247ba09399f6d696c85bea41b139e723c22f918fab56ac96075961f4b787ac1d0a70bee3736d882b95246ed29075db870b758b8e07e7e87e6c4852e3d9b45ead8383c59a08ac2851b403557dbc3b2237556e211e8f50affd7bd30c6203da89b9f022e4b267c037bbd31ca665aed9eb1ac7dc62769928695cf1556b17d16ed57ecf342bd483fff7f4e80db65973fda918844498c6fd469154941a01bb734d950e6b590129eb34626b0e3b2f6079ab3c41d6ce99f330d782e76499e23e6fdc18535eb0889759789ddd6b3a7af0a74a96a5fb37bd9a3c6c040ab2441f66a4d0ed1f14d1dedfc59af3f68d8fc3c98596a859033d7bd3c167e4313fb2d12dfbcc7067cd2308f02e73bbed98e412059b129f5a73fc6f8785c1708654789d09d28b46c747021d65f592833b9a7768bf3e2df0a397128f9f090b3011f5b5eedf548c04acca020192319d4c3f77d95bfd5f82ef1b2f96b37471c2fa0dc4a5a7c1cfa211e378e223b495d0f9cab91d85a2614bd1d6614dee8a37d496ab68538f788d73d28c9cc3cb1eed4a5b76b0f335c9fca8e68f37ea8d85783e7a935248f05bea224f1522797347a3ce77445a3434d5dde8e119f4fda400cbb37d91e6fd6bdbb6cbd441afaa21c1149b5fd04fedc83bcd6caf7b9421462761a29187c126ec80c68bf04de83339e9ab521fa28750d5bd8add669ba83d2fdec9a1e0cd0823f263e7fa25a3ae301731c4aa98bdea0d95bd4eae0e0630b73f315007b71ef4ef344d7ad62ff171abaaef55dfc82abf1d53f136cb859108cfab933036d3372c1e5b90d022737efe30c27f2e1c833fa655ee2ad5fa0cbfcf895e60dab807e3fcd3861b0467e3fb6f007658af778aa47a66a5b18ef77897989756c0c99461874aea86bce804951f47f86cc8e33da5f59f5565806f18066aff4ceeeccadb52e530849838132516863e7c44c6138f4f428cefd85952202805b7df9cff2fc7a9d502ffa304763b8ff578efc5ab580dc98b4b99fe5f090ec1c58983e340075253120916f10e6e496142b1f4d6627434094f714a40fcf3f8524de0ad21584b982bc3d74b3982e3c0647d59b471906934a7820f8e65a48214948b4b3f818b5af0b451b610cfac76ca0c2d301aa7eb92000a894793a057eb8020883764718075e0e2761cb025eb0b91f5387eb73c44c800038c182604cc096949dd392206804592ece1bf5ce6f195d83aeff758142cb7a8d19bccc43b4366ab5834638b1db7c2ba783db2722bc811b1ca4c61d0b14deb70fc48d8ab896aa50a18581f37c38dd83254d6510694daa145052c84b78a53020b2117168deaf9663b72d18e6adc5306662f1005a338e0a04020598bdf848894e1687f356fad082532d63e4fda3f5c796c836a1f2441b498767a52cae94b92207349714b4a7d3693e8b6f0bf7fab665db7ddb0fdab158c4141a7781c8a4c672f57838789925b812de7aee0f053ca88232ad582e98af92bac36f3943110cf07f96c288d3df98d8c515a29777ecc10c7aa2b976b2490df83365a5ddbf1994991e9ce856958325024411c9f7f78d02b50bfd9e73038af47bb1480d10c2729cea20cf4df1979f3535f3ba04f0b63c352d62230ee4e14c7032078026815295d3f8014b0bd366f2bbf5f91101fb2388f7217b072e05b41f270739d5bef78bb8a304265a9f32b153feb9dbf16be6046a15c917445ee7eb310b12b2871e156e0fc5d0c9c26b6e571caf9618b819e42c33a82b583fbb44c150ff333737b1439479f12b51352983688060b7c29cc260962432318a955ef92b2e811b2e89f6e75f20e1708b2022414f8aa5995ea3a15b13c25c6d4fe327aa2654b2798670ea8752c07fb5a47c6592fc2bf5a9a52487afcf7a2051d3bd53ff175527a0015553335aca6c03c55c177d9f94c88dbcfd3efee47bee84d36bb7c282354c7c0f84716084b9e754c3ebee02f4d027df4b08131311d05504f5c426d961a24e72e3b8bc0d717f6c3dd9a3991bfa53f4f2c448a18701cef19701eef285d252a85edbdb9b502b196ea77c5a95567de216097a34ceae75aef3c1360a84a8244381cdf0edfa73b0fc76958cbc2394c54c11417bf479dd86577dd4a8cf4bb52848bb9997263cca7bc9dd599ccee7d0aec1adfad55aae38cd38c5eb40dec8ae2518f48851129d3d7e38bd9d767b43158c59e3bdceec2fb327857ca6780227ff3937d393272989a76592999db2ac03af9261a0ae45b6627872a0e528b6ce6921c0e974678d446887821ae38a8798bcba3706348f6f0ab780b6c77f50f2dfb0ffdd2874a30b7a3ea6cfe38b75105bb34a7fbf39b482e9fb1eeeb700bd1a9316f6acdcd3ebaf6349460924e130856920974aa020d9c6295f11a6b1ed8dedfade8653233aebe4e2c0399aeef49169b6da154cc2471694a613ef60bf7cfbfd636dd1da0b5bb05b259715543710af6126a5242a6f7d4d7e324887a08578eaea5c8938cab7c5e3e4317122e7d08aade36614c01a1f5d36a54ba51abb90a433add9247384874e7d2e001d86c6600b85b2343b0f690e310b825873f91141921d9ca266afe4c63a244023150a57accce96947b3bcd3ac39125d4ee472e75bc831becc3c7c651aba627976598f72bb75834ab83ec878012c8355abe7ad876a2ef96c630fad84c96f710c1ae2f59613d3c52d38f054667c0d2a9259ba576ca828c9fd632178973386afb6e845c8a2e1e3194de0af1a16a4b04349c9d154a4ca39a3f8d8c4f138249e41d77179a99da09f9e4a73b64041ac16073472fdfa89d1ce7c15946595f0db3f9972484f290a9a91fdffbc3c114eeea4106dd91b89bb5f5f99d01323385ed83d1b5693733a5140b06d832977036e2d03721c7002425845bdff156c3f519d064e1df2873fe72a8375a50c6d5bf290249fa7a4dea6b840f0a6fd5a8536ab3238eea474bf74ced4771158a8c7ba1f32401cb8481bb0fe5d259a16abffea8f8ff1d15b0542122741c32088b7a705e96178daf28379ee9bc3df3ebf3e624962493f6750cf6e82009636c24f15cfdaa8620dbd945875664ce01688cbf318c2500003e9246495a6cbe91a0e0a41399b4d9d6c26509ace35442075ccf188d29dae494da195ae388e3a6c8bb29a1dba574a1130845468714d97f91b879ee28d2768960740bd49d43c94b0c002e93a52e3d07e0a447b26193ebffe2a10ebe0cd518d0221a2b118b3f6c170b0f185780880d7a2f324ae4e207539dbeb167b6b840281de93191ddc5d9afd9ea50838d8c3c64671047c4fad289d5b887889ab42e50731d16cdc1e630742bb263bffa6a56cd445766b26d1f7640f296a4bbac8404307cca0176f0a51f0d8983a0a7560f38095ae39b4f588d0de5ab01f419454c424064854ca8845cd36464300e277180bc74933ff8b15e0d4d47344e9dac182d686a1b4390bbb68b10ad57394f83cd480c4c50510e3c9038f56bd4d061e82b60681ee5fcf9a1c722a4e39c7c01d7ace184b9f2c38c8bfd5bd99682ffe893650c6c0f83178d0064a1bc2ee449e3ef2e71490b473eb80ec1efb95d5c3ccbf35fc755932528351aa6c50b38c9ae5eb58e8fa009b47be9279b5c3fd1bad68b95960841197d52ae062e0c4c865701f26f59ffb419571fc3c056cc86dc3607e4caf2406fd5154c064118a3482dca517d7dc6f6aca2acbeca999a3e5ad0df9cde74b1e9e728791ac3180d9fbe230c5ce73ebc309d457532e1d519a20b491f3c8282cfe2ca6765117c4c92de8c08ff9fd11abd2e5e1b186dcec0372c8ef3d1542a7cfc6a5344b15a56d13b06fbbcf2a8f9d416c98ddfe04d96a5d9f90d8c8e9e575d51b772302c41b3b16b9bc2ee9f198863f8f5ba4cd7603135dbc961cebfb34b473ed4a367666adf2b07e44ab1fcb10d4217441fbabea1eb428bc7045f6f1ea9e8aff9db94971de26ff960e4226aa964e21b8804d5536cdb05ba46acf8689183624aa0ef0465c2b963d1ee809379cbfd6a98831af3dd9d6629e3de56a084277f132b67c17717f385cda1566f9dd9a1daa4def780d35d5827dc0f6ac2639ab78f7f08a480cf18bef0e69e34ffe825884ea2a015fa5c5ad34d1f623f0822e9a15450cc367dd8078bd75b71002969a06734dbc47bf613a4c744006e8aa37e01eae1070041b5a119fc7e0d822e04660b21a16af8c077c59ec97d98f75350bcdbeaa548c3006da4ef08918ab26f16e1f8bb5bb88638a899514e0009573196ffbf510dd0981f02c61c569aafba7f025602aaaf5cbe2c517cef47a219feb00eb44119ba8b2df1109b655f1eeba3c4e84b80acbb2c7b5df040c303f311758e22928fa2ade189f1eb232d4940ac1361d17645ddee7fd893a1095d866e0d461201c08d3562b32ec556b2bbc2fb01db08c671fd1ff9a64d33d082889a0ad53af4d7ac0f02e01fc703a1baa7145e2e9123f903c5a185fe094233cf3bf18521960b11932b412bde418e556843bab311212ae173b6f0c1e11e2192d86b89ebe484df2f7c56215151f96afee4eef2eb1f225af644dc578821727cb4f28e19046c0a0d1be5c1bfa490fccb8ae8478d2bc424df6d34d6dc9ed8387d41702720df1c382c0d44bc3fd423cd4be54281e88a6da4192870c43c1f2eb7fa711fa4442a66543ac4f81e90483fc3b1bc5351c11557552732ebb7ef0f4022ac4d17ad720b3975029daf394684242fe4a03e94421608fecd0e51a0ecaa5d6e82e08485352503305ac470adc8bc90cae9820008abd270a8368871721f487a6c518603b7a5adf1d1111b2fb34196d35286003d19dd652f979e137d79ac4932d541ab2a0fc05855515bf0a132f6d5b1a3d2a21bdba1e6732c65f9d5304853839942e90184c5993db5c4a4a22a86cd323a89becb26a4a6de39b09ee77b798957db5a105c1212514d3470fe412d64979222d99c49e6114b1b7bbcf2a2531f5cded156292d56923e7e673d475040bdfc54c4b6b95bbeff75c686dc8f57d2cb7d496686e896635a38627e83c8de3abc2787f0b4e801ffbfbcf33628e816baed975bf2c60987ea3c6babc4c723c0f9c8cb91514deee9e6947246f573e4c7d773901e0ef79f91796abd65e0d5e732a3dff06a842fb4d2c9d8b8a7629190936c3fcf779fcf36f1451d55ca24194ce40fb5472cfb10cd51c69dd9a17dbb6ea4e1ed34f668e20ddb50a1142bfe01bba81df274a3855aaac96101f9ca0ddff95549483c6ff9abf6960f94b206c1caca4d548acd3f97588806bbf432f73147ea954bbd07f457292bca089de8040a0e69277e07e69a1cc9af124c1ec0318372cb8045ec7a52953c346eb8728acb1e2117f7c85401f322834281cfb982f24af2431b447f5f27ee579185f6e7c6e34579bb27d13f53b894f9fd9933ed8d1318bb06ccda1221af2236f9ef573e7ad68d6309f988a028ff863c275386c425812a24e18a80b07df91b9a316228a92e2da39a3e9f48213cadf61dc25647a16a2141a996a69d755cc458c321aba0c748f666112400d473d4c11617e70765e82832b0869b98528a33ef8ace312d96f3f4d25be8f2c1c66d07dbebfede4169174be0153cbfd9568eaac4f9ba065095786a87e5004f07fc71c085e2f5740698113dcef8f07738ebfed0dd736530d3931dccadcb8b772f68be37aac959a3ce9dff6180941f5ce55665c13324a037185720764f3737eb0030294b91ce519c05f3467e76f8164cb9dbd287e034b6b012f93816d221ad8f9cd68a03c2e3a8ca14bd40d79bdb1842e52e7cd094c7f329efe79e02981def22f01d1117b0650b0e04a2be79ff942c6bc751309fe40f7b7fbade6585f772c617e3c890c2ec07f980b16027a68b46c926bc8f205613c4f1acd35dc9158a4a6b136f745cda0901ea9624cfec022b8f034c55ce20a3b1a3badaefdd5e2602731221807a2ef05c68dfca3c88caeaeced8f973ea59828fc9b50b997d27caab83936ffac0de02e87b25b9e1133f12a90341c7df3917d21fa7ad18eb89f7b300c75f10fd712e6fb217bf36d3be4d8cab614ea644c026ec29e7f087a1e93e19bfd371fc035c519289a1004ba914541b5ff63d786680f7da09139a6397226c705bc6279239afba6c7f53a6416556abe8680eefddefe3795158054039e9c1095d23da86022bc49b07d2abfd97a94574d6626026768b21148068095d7669e8031e242a81582f8597aaf31325312bc2b8c975020743d0e22c9c47846e3c42d65d4bc26580d1ce392f629568d11d74456aa8609ddb3af0d9f72be147300a636c97b0aff027282e449361fb3c3d5f8f26978d09eaddcfd37327684eedda2970b8bc13608a547a473fefa09a2b242cfe106b75ce3c64e6c34cf1c6ca183c19e4fb92e146f50496a4764c443b8e7b2365b7a912454b50b02015d7244a0f66c5bbaa9a50377524a0b11e4a349f3720c2f4ad2e64a749d66aa9ba0eda7194e4bcd391ebc9977855fe2c9cdf41bc630180861a18f308727dcbd0d39b976d37a1dfdf904f77a6a40ed06dcd94738374389027d9018b1943c5b713325dc8b5de53e34db7cf819a55ea2fa52b11b05b2f06f31084fc2a47a1f72d6098df6e66181ceb8d40135120f055587a63e793dc731c3439b83d9ad34d6c234c11b5e368983e17679ed558a5ea7a3ad33d5b9ebe300778ef8b8f578f6e30d7363c82cd9f03e1d780087c4c182d72e0319acec822f2de2a32f24733f57413f6d2e693c7de3ded77e48fed03176d03e7629c68662b9410a68580f98cd4c0ade2e5d113f8d72379e78ac6038525b81ef549b934f85e4714425e6990805ea4f011f7e62088d86b11de93ae990135dfda335ba4d6899d2cecc2c7cd8497f5226b3c59af0264c537de2ee237fdaa594788747be37d35c4755475ddb9647ceb63b2066cd4ffd3500d1c8e023e5a04220b3760b46439330f0f0f0f0f0f0f0f6f4337426a6b9f908494a454aa976679010b644a29c99492d81dbc0b9c99f87466e2d31546ea6e34e301030b400bd30a89305b754ea153cc4f17311a1a68000d2d6854c0c3c3c3c38bbc0c801a6220c214d28388d6a39e7fa625c4388441e5edce5e2284ea63058d011b39ba7880214c3a9430d9298b440b63214c21c78b914df249fe104218b465c813f91f15620cc21871e5d6434b9e902208e3440b19a91e545479a2102310264f59952647c8881a157284188030a5e777f438191b6b016084187f30968e74f988efb9a75363edc3c647c12d4b31fc6076b7083243e465059386161e1e5588d1077357fef6f47137b6f3c1246eb35ebf9237ddefa26b68c06fd898c0923d186de28afa0e1d3d18e5524823aa3a7f3a711ecc5f174b955267a972120f8697a4b4aca5548086062440e3430b1ced12c891000b2851438c3b986ad27c74ad38f91b362002e50a31ec60be0c7df524667bb5af83792dced6c4fa09da6e3a986e5baf64752ef5e139183ce54ccb19a24a29530e26a13e57b8a89e9519d6588b83f14a456cb56ec2292538184ef6d8c9d0918488a637e8337975ebd52f23c70d068ba6bba5847e25f3b4c1782a629b087a7ad5c26c3089f59c82129d3c8851d7b09af89d69d3fbfb5d0dc6d1f62762470e252d4d83316757528ea8f360123418f542e3924e6a44fcc81990e79e7592d8f0dd88194c9fafebc329b7951896c134e23e23f4d998fa6430bd560e39a29ab0a4d9180cf2246987284292490cc69ff9d4ad94264cbe6aac1962848131b7d9ae20536c3430587a3cfe09791771fc4272a23de2880929cfc70bac96e87069d927e98241e9d349674cdc0575e242a272ac3d2f919152bd05decf52e447e9f4c1d58229e72454dae4f24ad5c982c94212972fe88d05632491b3d1174a4551b942fa4b5e9f1a1d4f3c620593aafcdb1dea296787ab7067cb13dbd46a6255a86052e6de267ddd4444740c31a660c48e14d192429784670d0f69267f4761d7125365924b5ff250307b2425df15c2ab3e9e438c2798cebc4dc6e52427182d8f7c87f2fc155d9b9078710fa1cc4c2618c4b6044942924b583ca565c7d2217fca9460b6317defb0d57f214930f68dca496adb87f00e09c6f8ddf3aaba54b61ec1d319b135ce74881ac1b8af27c1be6e2c9f72885104e34a34a14f45869e1e11c1f41971d72dd4ef538660505921e5e715e517662198aeb346eaa80ad2471b0483e855f79c4a6dafac4030999950b9206db573f20786f7efec4e7af744f6d00d317c9068a6965ddd0373b6c41c939397fb82789007af3e53a24f9c24edc024277c56ba206f92b6bbd8a26f241d982aabd675f47091d29203c385e7eb087bed220eec0992b642527cd9788353fc38ba446f820ef2183630ba6e8b6e0fdb298aceb521460d887f49f4d7280f950662d0c02ce6e13cae598ae4a1b121c60c8c5e3aa5a81246eeb8be8618323069cefde76f856889c5ba9f53d8cfb8d2aec1c254276ef73aebe597e91546fdb4edca31b57923572073ca9369bf4a2b0ce7a3d4bdc8c6fdafac30dba7baec905ff2bd5d85c1e444e5e6fcc83cb9c69aab1960a8a2ce761792852429684f2accf196653997fa4d1f15261d523855d7317e3a790a5418cd4ea2744c5863ad057e820fbe1b3672743185b13b6591f11c23966534b4a0d1821c5ea0c07451011a5ab4e0867f71011a5ad0f02fb8680ed0d082c69db5c02340430b1a76c39d8bf3850d2df3a251f05186c3bd68418e2c85414f7a0f8b2426297b5218d56cd4a8f4f1f6b98fc26c71d2cf88c5eed35714c6d1e965fdc36fe49ba130cfbc8f288f7b8b2c0185617f3b4be7fe2cefdf270c2e4946d0155a3ea25d066078c2a04694e5ed59d7fabf138614f22c56a8958afbd4d80d745181b3829c30870d911e72e6dfb29cb809e39c271d99a142de4713c60a7af1223444ae8533611049c5fbe69e4bae0f26cc79c5de7b74ec10a2a55cc214b45c4d9f92aa22644b98d46595136182ead866f2004625d491cfcfbb1682caa2008312e6a0ad2797ee5b93e1f8c8f3008c49186553c4055d59e46449560086244c1529ec8e55d808ebdcb8617608302261aaca9ef8b521b2eef187106040c21ce36e73278b47109700c6234c6d415f0e42bba9578405301c61d8b7aa2ecfb7b0145463edc373d8e8e24e05301a81d0191e3e6444790306238c713f62d1e43efdeb35d63e10522f602cc2544ae26e9b84a4425a6aacdd5084a954764cf86c3fe67101231126b566592e665c46b4cb4c000311066d912da851fe41a4d3210c7f61db43caa5b9681bc29ce467e498a51121a51026e5392e9f449810061d134a790ae141982508f5385721447a2c087329ebef2f5526e60f8439ce6ea464656af2640161b69446be8d8a21e4db3f987d62593c6bedec52fac164f7df69fb631f4c29ee6e65adff50b3f1c16049eb6666df8331458ed029b2249f5735d672b4e0468eb5ced10bf0e223c905400c30f460bcb4f1faa542ce53aac6da471d0d0d34600b30f2600a6982a4b82e5539b45c808107f3e81e93b318dec1a4646858588a4e96658db5b230ec608e26579b3ee26bfe5263ed830d07a30047af200130ea60103bc23f8556eef5c3737c617e18061dcca924a54fd2f75ba489e760d64939b722bf65cfa68f3b030c399844d8a9a0ff63df851e0783a58689a7541bfe4170308e507955fa6358f5fd06931033ea6bb47583f1d37376b8d5aff8691b0c1adace3e87f8b922369882fa4cf652daf4e49035183f2d98fb051d6f92d460887339fc7327b17f8d8b56810d143806baa8000d2d380dc6d02222d553f5ee99ae00030da60cfbf57af7703eca3318b7be26ee8752224c806106f359678b37eea9457b30ca60f2cad24946844f8da700830c067517ed83b4fe1ba53406e35ad871cbd7e3b29be50186188c5a5b37bbb67d9d3d6130a7883fda42ae99f6d100030ca6b3b00b914275ea60d6ad01c6174c9d5444af9b8100c30b86f43b97bffd9e7459140230ba60ead4157ac7cc264b8ad987a11c6070c16cdd162752f27a1bf9ecc3181030b6609049317a1e52d809920f3336c0d08229291d26ce8fc9979033fb30b5d2038c2c98f7e2f3558a8afee75830a8591ecb3d59bd3aba82f9564b7b5cefaaf3b582e9b7a3e413d23e29bd55c1f4c9b28856bb5890142a9854e764a53fbedbea3b05534d2c7d77aae2e7775230a9a06137d2ac24465214cc7d1a298d2c75d9520e0a062d93b297d095ad84fb0473cdbfa5abb658eed209c6d5b4504ae57cf6779a601a6d61ff45878ffa2513cc6992b07fff8ba054b40453ec0bd3d6ad3fd7394a30789add480bffd22d4a8239e8a860fb5fc94bad483089bb9f117942749b0fc6114c49d99a488bb011cc11840a2a87d1cfdeba08e6ad6c6a3f4b53438f4430682f11b211f3b22f3f028c21984bece28afc8a259df6f0281f8607430826ed4eb1743e136f97140473bea978d6ecd68d05046377f5886b7a7779f903c3488ab964a13a9f181d0e183e30d75d075d6e32ebb33d30a8b9a43d7479903d2e0fcc155ad41d988358499ae02af945d68131e4c73ebb49c2c4e57360743b717eee57a16aa3a1c58729da62cb60e0c03c16225cccc98c8ad40dcc1f44880525f46f5ad60626fd172d47dc9aeceed6c0702a3e8a4eb942be80410393b4483fc2d7e4251d9d81712b23a7bbd58fd41d18323005a97dff932a8ec9321646d3f2380f23a2bb25b0305df06afbbbecc9f4fb0ac39f4732f3d167b1a72b4c62a284dc5e893551d28286161690000d6d81005a6148fadada644c92159b5ab10b08801546133af5abd492d72cf00f1b393ef05620805598c2da6ee7d3a810376320005518e4e7be3ecd5f68130502488529ed2911adb40987aba09d8b0f1514c31b282801a0c294ee84ae4bcb97eb2fd2028f403985c9c427ff298b98c26057396e9696ecd1430194c2204566e6ff7bc8be202408801426f7bc6769a476d2e6e8628b1a450b0f8f519852f8879265f7a62ca6111e672887243dfc2e5f7c7461833b8ad0610643d075973fa67a8f9a40e828c32a2a3ce928a24e7e51e82003ee5f5e9d25954612133ac65072bb2f31cdd0b30b5c4287188ebb9611cf7f346ec1f9280b833945131efaf24608dae90083214938713af79c599a5f3086e917ada2aae7c4e4dbb9282fe8f082e15bc4bd54e42e186f74ce695ebac73fc905d3e4169973514af6f45174d0b105939294f522df4d9cc65a3098ce2945c991d4a0230ba634f12a4b92e4ce6663c114f428a56c47f44b7e35d6cc6868a0010ea0a10109d0f0f09041c7158c3a493c699153b357a264830225d9e8a2eca4a500083e0cd0610573a4ac9491a89d26710f0f5e0a80e003004ee8a8824175ca972e6fc99354d5584bbbe139fc861766aa800e2a18640979f398e421775330a470175b17b4e335cd8f0e29184777ac185636f3f9d482060768584002343aa260f41c6f25c5fd8ad599066868416303342c20011a1d502874864861fccc3f5d634d058e010f0f15f80db41b39bcc043c7138cdf5f51527f36d52d7e31812e18e0e1e1e18163071d4e309c502297969610c4af8e269844926a0f777e6a7eac4da18309e6642a590ea1b53721a9c6dadbf8e0251872a86441f95a12d26a724a30ce4fda14f14f27d54c82797420c1f82bea25071d9f4ec5fad071044350cf11092a4a90df8d604ed1a5478f96fbe6a4a308a6ce26259d8aa076f5c2367410c1e861c4e517216774ea5e740cc11442cea3152d76ca2d4bd02104934a95e6d564769c488d0e1d4130c7121533b2e59ce3b36573e80082b12ac6071ff57ac2439258213a7e608a9d8492d9fe8a7caad436b8f0f020f6611b880e1f18d447f47fc7fa5356730f0ce2a942a57c499bafc63c3047c822c67284a9b2c80ecc29fbe46025569e832e9fd0a103b3e7fc1232faa774e7e4c01cf793f3466a383084dcc93a25f5ff0df50d4ca9ca7476dc49133a6c60b29bd1f7afb14b538aa1a306660f22f9626db8458e7ea18306e6206b92a3ed857ee5ac858e1918420a7bd723c553493a6460d0f5c1f452ca1efb7d2c0cefb13be446fc8bc8c2c214b73d6ae7707e2987bcc27841ee5225a929617b579872aa3d15272e88e7a915e6d6cf9654e58ffb5cb3c21037437755ffa2e4945598ca3ebf8f885615a6cd946839e59c0f6aa258c82215a6cbbcb99c13ef0a59a0c274ebf6e154485b3e39a730c7ca4a4a23ec1b3958604c61f0109552ca1f254afea5308ff04ff18d70fe1eaab19611c88214a6109479ce3e7d7a71bf1b4116a330e58e7b1a4134925b4a8d355118c54f951eb57f2af13d01b2c1c586c270e2262d9bb4acd0528db58f0485215e4acac38ea8c6da65208b4f18546d3dcd495acff2d558bb40169e3065ae97764f1225bcacb1c614c8a21346fd9113ec52a6fd7d1865c1095359fc4fe2d69ddb6235d69c8bd37622c86213a65431d789fb6ec9ae1aab09e3ed44f5b62dd3fba61a6ba90b1ba78b2f361326bf8a1f275d5f5ca45a70234762c26041a50b2b571344446aacb5e0460eb2812c2e61fa9c3e2e2ad5d60879324b98455987b058c23ec4a2812c2a6108b2697fa6b273ccd696035950c22074ec7394093126ea933086850bd1ba6a24ae92306beaa591266143422261bcb738a2539daac41224cca2428c70df0f6a52e711a6ab18177cf257bec91106d331e4771aedf183dd0893f214e18410c16cd28c30e78f9dae5f21f5b96511e65329fb9cc9274f9e2ac238ee3da3edb7f2fc441862a7a0ec65e554658908c366cdac8dc9da499243982cce9ccd07194bdd19c2b0ef49cbe6c2e4d9ad1006d51ac22d564ce6a93c3c70b8172dc8714701107c0400085910c214a367e4d6764b8a21b40fbb2c0661bc783d31fa395996bc200c49de09f123b1cc4e0d8461ce65948e8a1ff40f0863a898dc172d79c59559fcc1fc1366accc52fe3c310b3f185fd2986f044bd9c9f7c1105b2f2595a4d3635e0f0f3164c107b3af873455d11e4c41c8d1f1e446bf4bab078369dddffd8fe855963c184eb52b5be8feed13c18361bbf3fa95f60ee6ca932d9cecb0371766610753aa121242aace33f70c1059d4c1d439e88837fbe5a9743a1826c6554ab24d5cead11c8c6f66a3abd255e4242407d39aa5aca7a7d42774e2605c0f7796173a7030ae99d9870bf274c4ce1b4c25cbb446abf55da5dd602c714b4145a9ba384b1b0c1bdb299dc50a1bcced3b22ffd55e94145d83b14ee48aa74feab35435183f452a593339c8b7d360cc095ff91192a48b241a4cf9447eca10e6198c6ddaaf636c9abf04cd608aee22eb352c9a50aa2a64510693ba1b212f7e4f0653ccb17062d26ca6c8c76036212df34b2589c17429a87e1ded15742585c190d3d6efa4ead494243098b33e77309bcf174cd9c4a28a27715e757bc190824a3255933f7ba574c1ec7f9def42902729cb5c30968e6f2642b21ecf780b86a81f2584b0ea13e3d582694dad72fcceaa77370b463951a5f379b29b5ab1601cd5599613a24d4af30aa6b0fd59478d6905934cf68f3b4a8ee79454c1b835794d7627557d122a98ceab3f2b9a369192640aa63d75bd5f4ac63d77523056866817f92267b68bc2b1c32585d1eba0603cad94ee53e8cc0bb227183e4e9e30fa932cf79813b4b2cf394284ad09c66d131e572a74d29d09269d3e2c89c6e5d32fc1b027df4dbd64cd1229c12446477f3ea1524c4b12cca9a76a5fb3c2642498365436cbda826b5716904216473075e990f354e794e36a239846082d22d4cbcd7f8a60d056ada0e324f5e7b03040430b1a75011a1690000d2ebc40c1166a4430afa7d8b6cfebaa1492c30607ee3e90c5108c3732d64dc7ee3295f5f01082498e65af74a3ab1e4e48c82208c68cd1d9a16a9ea51d0f0f0058210b2018eed273ec4bf331e12106b2f881219ad6aca4e2f4f42a3e30778970abafb6d037d758c3f1e1c517391cc771163d3077deceb58dce4ab68d1c669b050f4ce13f7f6d9b8b0ed9da813925fd6b5e39393e6f4ac84207a6246e23f3126543c63930e851973c6f527d4bd60b59e0c064f5a542b05c69a7a31b18b4424f87da883a216f210b1b98438e962bfa05a5b4493c90450dccdf9d45c4cef7d1b28d42163430880ba7d353d6f0ad9c8442163330c44b5de92da84f56a2608b1b5e98690bb2908131c3b5d4d743462ad5be102316a620d1935cb45426b65b2ec480854925c7d3f5ab6b4ac5f415860b2a26bab64711f95d61cacd4a4a08f1126df256182396f8925e49cd66b816831506959c4e091d4ebfa4508d3557c1163752a22ebe78d2858d2db640018e5e0189b10ab3255dd3adf5d5afcb3eec0f315461ba3c2b39ba92f2a7570b1a34b4a0a1050d2d68380a9a021e1e681f568718a930867c864ef19239f10dff40157cd8c8f171e3867f78d12858bb618018a8307f2775dac6235e7ec8294ca2d627a414e6bd448708314c618813d383e4b7fc214f928518a530a88dacc84ff13c7c0e298ceb4195cf8fdd859895e9284cfea984509d5fc4a5105198a49afe05a11e4e3ccf408c50985c4b948c999d94da9a82c21c44d0cad974fc42d0ca270cda542cff7896212fa29e30c514d33457b7c61a0bbee80f1c5be017313a71c8a6ad478f87306d08313861cc104965dc566aacbd9f0b000e626cc23cbaaccf36de1aeb802e2ae0e161ae88a10963eca7c8eecec494adc69a2f51c19503c4c88429eb7b4ae1930af3c13a25c4c084b13be7e87ac1930c317b78e4c0b105171e1e1f312e61d4f6bb0a8babae33046258c2786f229ef77fc66a8f5109b39a9a4e5ac3ccbc932861d0e72762446848b10b8e2d100bdc930d0a4cc210627f98999af02177bab0b1c547310ec4908461562b89bc7d593ad901312261300db38ed725499bb68504562161160f13df7212de1344ec1e508256805a0e1c5b70413a10e311a6bc2345655bc8b11c2a00f010c311c61f75df31ae5de72a1f311a6150dfeb104ae9deab1703311861fe523e267f2b4a387b11a6ee7c29fdc65c6dcd0f1c5b281b6228c2a49f133f8450d7d93e0163b06b542449d1830c311823a41c429a2d7d1f2936920d1961309d460e22d7c608e1078e2dccc820030ce6141d11f4b94ef81a71f105938c116df11452e507820c2f18fbeed674f67f4f3b5d156474c138f21e724bbbc3f7cc05639cf7288b2d5241c6164c414372f658bb5acb6ac1706739548a3769cb436464c1b43d49697dcfb7a17a1958308f34ede929ea46907105e39c1c5da645a78ecd56307c96be28afff6cc184b6c8d1393290041955c827b5d5cdfc766190410563ffc80e1d65259ca59ce046a3c06f988c2998d74cf252302421dac3bccf838a7714cc637fb1f6c2733e8f0d20030a86744a6a990433e1697f8239ccc5eb15a1ca3b742798723aa19d3dcb92ce5213cc7d715c46a911b1549860b48ab5adeb4b1eb46709c6ff5db7121f3c5ebc4a30e7d332d5ac78398ff20b2f92606ecf2d39c6073569511948309e8e246549e49bbf1a20e308a68b37799b2ef2846f04e36f4bfe2c7f8b6711ccffb13e92ea49189d08269d3121f2c63f04839c5d8a13a24230574e9ffbd485ad6406c1a0276a9927395a8940306d9747f8c85eeafc812927a1b4cec49095d8fbc06413a4a48b1727e1ef81219dcd5dbe9ede34f1c0785abd25f72ca47c7660d8fdfe55af9589b60e0c96a735442cadbf951c18e4ab465c9771604a29172e37b981a9cf45b72d5be77236305a4842a5ebce6b60aabbb3b3f78ff697cba081e9f63c47ca5f19e272193330859a9d9e58c182da65c8c06842627c7e512c0a69a74aecd30916862065724e69a7571873d5b63582d29bbf2b8cabd7a9b5a6e4b8a91506a5ea2fc590eb131756989359a7ca4dd6d3d9559824971e95f2f9a4e0aac294525a8b7ab721ac4d8551542f5c695d2935418539dae81021a354c7083a85c9545a5c9e780c292253985b2624a16c4f98cea15298f2670f2aa69228f9218571ad757f3b72eedca3309f49cb232f8bc2581d3af2e321556c4361bed355ca666ebfce939fc87e9e13b3f309c3856cb933e2d7f9c713a620d16f27db9d30f78ccae1479eb4b339611c3f933549ba095376bf2d9de029775213a648e17c6e24ebca9809b3cc5c7a8753553a870973d4eaa4bb737d0973c8a7ce5a2679b4ad2d61ce96ee44c5889c9b75254c7b41b99d122bb25953829cd47d98e92761cafe9096462d4f58256110d671a2e935ad279648182bc951dbda175b59818441ebe8d121afe7cbfe11e638aa6639976fbec511e6942645e5c4c8e26a238c9246a5b1b29326228c305f2e616245a87b087911865827743eed5811e68bebd039f57d3e281106af78ed517911612e4bfd126631b6c73d8439480c15a1d22d4eb88630453c3121d9a38414dc42984c77c87b77a5d7c32584a9e4984ad162e5e4dc0ec2143a65975feb69782b08e3e7efa7916d0361cad0102994a97aef0161d0b19444134a5abdfe07e3cec4cb0e29723ec90f66391d7eef7592eeba0fc60b25d5bc2f760a5a3e187e2e74a5e0374a64750f6611a52d29554e4f52d583c1c7bc52ce97e6c16012827e7d13d5764a3c18fd4556843e1d3f72f20ee61aa5ef2ccd6907e3e98513b93baad696753097ee7ca53da659793a18b4a50a573274b2d8cfc1ac716159576ff1477230d7249b5119119205c5c168a64ca8fcb1a363070e86fbb871153335b6bec1f8c1e3578a923b65d50da65c7a674ae9ae0a4ad20643caf993bd7472b6246c30ad5bb8e59b4ee126640de69a942729495e0de65441271db75fe43f9206530e42a913d16bdb7244835135d3eb3ad8095bf10c8637a1bbbd3da2f58666309bb4d116452b8341a8532989d1cb7e2a2483c1cbd446b8f14aed8fc1bcd75eb27ff296c4c560d24baea5544da91c0a83b92be8d23679bd94c0601ad393e2cdb466eb2f9845ad83c872ab1cd50be62de91ff183e75eba601053ad8b13b96098a0a4e54bb9db82415bdcee24cf23f93a2d18f5ec27dd5e9705d3e44fada15d39873c164c5db2464e5e8d35f915cc296f217dac3d9492158cfa15b154073597ee2a98e6d449b5f8ad242e5430b7685321f2650a06bd9552ca69a1cf220573300f3337d9a260b0f568d95c8382d962786544b9f330da134c41c48f7d2a6d04cb9913ccde25545b5049c653d60443d637cfbf9ebd6f6782c97eb4ab5eec4e3f5982e9edcb7427794ac98812cc27d62992b44f72912418336d6fd5ec73f61009261521a84d2b1dc13c5acc549424a22719c16ca67eafcbdd4254044390977366d224c98c08061d41ac7efcda11a91a8239e6c4ea24a2e71195178221659985e510776196060c2008e6fdcea346628dfd2801c15c39e4fa32747ed07d095d42dfd828aff8c0246fe16ff969db43ac0703e8814196840927c663a97d82ce417630001eb8225a3d5ef54d773430801d68413582ba3ef5531778662e18800ecc152c7f0ab2b4f7d44bc630801c1854687c9e5c153d49968401e0c060276458fefd907be1220ce006a5a553594e8c906766060660039365b913ff3c9e44258930801a18238e4cb83c5e0106400383b6163f378b1eb4245be011c0f5a251d00511c2006660f814b157d953ae08fb00646098d892b3e85d64b13716e652da7c929cbd4ab1030b738510c2bbea5252b3fc6f91f6bae315e6385b11827b52f1f3b7f3b000d0a0c315469dd176399c9599b66ed8c0f131015c0f74b4c2a04ce850b245e67b973a58619e1ba533f12ec9885b598549479f12c24e52d05176a14315a66842c768951f9dac4d854962a90baf734ad829e585a10315e42c3eda6ada276d5c1a10011a5ad0708087470bfc0486868e5318d2861a5f516df9c46f0a73e84fe91e762d8579cf65ec53ae3c499f5ce8208561a4f5a5edbc7aa98238bcd822070e2fb6c8310ab3e9edeb1055e31f3f36d217386ca42f7088c2f09f3b848e3bcac1c51625c8c1c516250885d9b3529061a7ce521a14a65425b98298c849253f61dcb1742aa9cb4a2192c40a1d9e30ae6556d76aa5147fd40953ecee70499b862ccb55a18313a64b6da9fb713d04d111063c3cd00d12746cc22827a731c2df83ce1a0f0fe42cf0227468c2149a963aeced99484c18ae3ae4ab8aa2924aacb1f6710963f5966ecd975029a325cca6e47ad2a66b47e95f09730cd36952ca497815d558434a943aaec4728667cb4551cb4fea63b44e5de77f564e051d93309879b614725ee949649230e812b2fa431821b1bb8c844977c88927c47dd01ff6a00312c611b1b3b7a542627798071d5af8cd9fb7c311c6399d45ed9aaf97bd1d8d30fb8cc821653f31c290e298bc93ba16473bad60e8588421a94b48563271f72b1d8a30c7b3fc20b612ce4247228c1d45f4e6a868a3bbef408471648a3839d96bd9e24a42c7210c3f5252929b9763fa39011a5a78785c87210c9ee22eff25154eda1d8530bf5e24ed22272f84141a5a94a183100629a3a346ec5bcad5a20047afc0c30305387a056410c69c18395cfecaa7a60bc22841b74cfef4d1d080046804c2ace2b936449dd5870410a6ce1dd15ab1c6d5f407d3c8fe68932bc70fe61479da4542fa60ec0fdda253ed7c307827a184b49c4e5edb7b30c90cdd2e916b7e62ebc174f9913b87388d3cca3c184fdcc9951362c2e50c0fe6dc503a6cab750753a40bea2145b285f8d9c1ec21ea46f588141d3b753005a9262aa2891d7430d8c955cea542e660d4d3d972b59a6e179283219e69b7bf98f99cd7389827ce5d7b4a952d7fdc0107a3f587d0e9fbeb7883d15484f82f67ca725248e87083a9b3b9c90921049316521d6d306b4891756a724e1c42071b8c23f4b9a4fecdfd0ad0b10673be7b48296adea7c4c343081d6a30da450e56b9fbc34b5a63cd16e0e19106537ebdcf16561ae72b1acc957209f510cf3f6ed6582b868b2f7038063c024320c0118c1dce577eb535ac7335d60a4d312010c008e6740bd1907d93f6322305018a70acdf9b134135d66a060420827962c65c14496241da359653172671f1858d1a19f0f050f3d3c7120438a4d1129773dcf25e40002130d97ce7b2778a1408100453c4c9aaa7957b963c35d63e706c0104536ddaa6c80b277bb25c70f1c59fa96941801f3cfa6dd573f09c6404475a00f5cb81003e58b4859121ba3dc90e6d81a38b2d6e786a77400e82003d30083d96a654fdae530a1608c003e3a57e1aa154ce65fadf81c94d52c8d949d781396c7c85b86f93accd81d192d01b42c6a4981207c648f941282182b211961b18dd2dc6842023cdc93420800dcc791d22972c754ab849011e1e384a170850037396c7fbe42b42bf9b6860b0da89faa193e9f119025120c00c8cd9a7eb525b3dbe3402c8c09456d2c5504ad5079dcd8885416b4cb58734a977e219b028686f31939fa4bbaf4862c7965ac4b4f60a2965862b4c25f94cac3a74098b7e71c38b3b2d683c400b0f8f1b284001eb0f66b4c2f82571acebc4f6c7ae3061062bcc15752429d5a37b3f5aab30e5709e52b2bce7742f0933546130ad8ff73948b7a4c244c28c5498f75dc38490ab919e438419a830887deed02927a475afc6da47b261a3041f38ba066f91805232cc3885299b8821c26e5e3efd1a6be8c3b9485d740db4a1811c5b24bfa18587070540f0118055cc3085b94d564e720a95752a2508334a61906b5a5a1f7e56749881308314062df1bf54e5745d4af72fb8680e8cc2e0b1bb4327468a284cf1f65772b04fa1e357426150e2f4e99fb662300314a63065a1174f277ebe19f88451c6434fcd44519d843c61566f730d99e3cee884a93c5aa4582a48eaf2ac31274c17c73fd207314956ca26cc9dc27c5d8eb6260c3f6e41e57abd9f0433615a7f397539ed56ce1f264ca523a83c9d439f50e94b9854c6b8664d48726fb18429a730e51bebae61d14a183e68d16e71d348caabb1f6c10517fdb11f357278e18905332861b034361e33cf547d28d38c4918c485e420fcd4b6fe481286b5fa8c74fa481845a724f14cc84bd69119903049ed097f2ab47a9a6bc6238c59fa51aebb43f423331c611e376634228dab8f90bd749fb6c61ad96206238e9e949794d1293fc48b22331661d2f0d1c82ade9e29a24b6a62673b2dcab22bcc48844124c49b0da12d9de820c224fa41c49ed8ea0ef321cc21c4eab54a13dc3f8630c4cbfec82ba33aa46c214c6ace62d5fb87ff132184494e777bc8f191a3c8660cc210d4098f587922fea28d98210853e8f5f8c17357ce1c91dbc38c4098b48ce714237eefc68285198030e77fd3d5bc325d769236ccf883e95a457c7ffc770b397e30a62761f1473c7f64990d33fa60d6dd970f337a3e98c7a45c9e442d0d33f66010aea53c6bdbde776f01175e78d1650333f4606e13af1fff5973d619c18c3c9c2b54e9cb18dfc030030fa694ee1e543813415dbc83b1b453e70e9f676a5a543b98e5834417651e4f82e4f20e33ea609891d5ad20f3437b8c0ec64fe95325516225a56c0ea69ce3277a2521621e948339bcf7a99b7822c8ea92e461461c8cfefb123cd672bfc739cc80834967ad9cbbbbc68bf4e30c33de6090ba1ecdde34bb196e30674b2929a5f4a53af56a6983c1e44d8f7eaf173d6283a9e3e547ec3416ee396b30f5e82aaf48a5238d5a0d268b1fd352750d6d350d06b127d4d5040d0d660fb338a9973f652d04cc38c30c3318923e09162b46cee69d32185b82969482d0ea53693298743cb9fa57650ce6d8ad11f546e66973c56050ad7c51e24388d96130a5759ff0b62518f0ed9359f51232e30be6f7d98a97e45dde0fa9c20c2f98438ee36d2945081eda195d309a1097c9e15752fde68249bc24b595976405af6cc130b952c5113bd24ab45d70266668c160a2257e4e938967264f624616cc9b577e73dab31f52f81133b060b6d2f37e21be2e9a0d1b37881633ae7045b2deaaad9067d5580fd7c2bf7174b1013531ccb082d9459e4ade39fe96622412985105c3d749f556b1e449785430078b71b361af12664cc160da258267b887ba902fcc90825154e5d55139a419a5ce8882e146aa8d6da90e95d70c2898f7edf4c7c64497bf673cc174419fe9a0ea7f37c6194e308c9ae751e1cc53ec74130ceb26f6d7a2a9347799607ed32df12f397ef2dc8c2598cf530e167b7a12523843090613d367128c3aea3c7f98f03f2b418229ce4d90f5249a71046368b5af390bd2427f8c60ace4d9a9ae7253f45904f38f989f50a7afa31f36127f31830886e855317b524b3afd0fc1204baa8948faa6d46f2118a4e4594f25bb73c26843051f36d2e69811049376f80f6159cbb72540306957123331e5a5c4f603c38b49d28bab3835a98fd445670e66f8c028294e6be59ce6b39e7a60d059462387d1f4d12e0f0cd73a314bed8a4e4ed948773698b103f39f8e45d62b91b4bd3a3049d9bdd0c9ddea7fcd81415d794ed73d75ea331c184e828c982af777756f6010bb11271baa849cd00606f3db53e9f35ba5e86aac7dd8486506336a60320b0f6926b95a142d0d4c6aae22da08f5fce799310383eeb0ac9c77a13b5f3364601259fd2c87285af9cc5818bc732e1fd7de6ef5790d3260610e336184901ecb40c62b4cb51a6af53f25774bb36980d440862bcce1d923495e6798c4c8b6c2f4162dc952ff64a6a2066e68218315a6decbf5679ea6626833a0058d08d4c0d10ee0f3a25180850220f878808c5518649f24990f931a6bfa0719aa307eb07c75a227e1625f63ad0aa981a36b905418dcde372ec70e16b373c3c6ef7d51010f8f1a38da013570740d09f8163734208174c3e3865742062acca2557a45c9ca2c55d758ebc286c93885d9920841a514bf7462de43c830852958b63f616b6ed9ef52987bb393474ba9d4682e831406e9177ca2ac6d895491310a43eaa0b46acd9808f9230219a23028eb683149e8b32482a130dfc5ea4f3c29aab2ffa10419a030041b4941ded7d3e4e41326eded53ffd6371bb9278c92fd846a9b14d13ea9138611caca7276b9cb71e38449e4544984d39d4d94d3bd7388b264d5adba0c4d182fe4c497efece51241cd8439f6e9a03bdba6cb24e5c9c084b92ce7afec98923c65370d322e61f034d244551239794e3901f21a5ae04186250c415b2bcb8b76391369250c7a4f9ed74bb4d0e651c298a15ea2a85c8892a449983bdeaafd841589b949c2a4567e31c5437b1991309fc895b311ae23d117820c48184b5cc9d1e6e31e43081f619c147bbb43a538c21467fc83887b712bc55690d108a3e58ab6f9113a5e486284a93a8c2ab91c3ac8b86f0a321661c86ef292ac07f3b858086428c2242c94fc7c0a41fd53321261b820395775fa3071224106228c1727e976bcec9e2dafb156ba90710893c5d0115e2d5d3ccb8d761c1f072d20c310c64e592f7a59b6c46cb990510873c81d2689d029abc70961f8339dd5889eab84103918c81884b1439cd50fe5b1c2bd9a2a9021088396edcbba957e3ccc390619813029b1772d3934d34c650908a3e8ac9dd35a589c5f760b32fe604a26f258a7dbfc60aa0f594aff6ca873530b32fa60c8dad1d2980ef3c1202c8eea76d0f649d92bc8d883e1e36513225c8a5631f221430f26257f2e6c6e555b8c0332f260cef693e38308614cecd9041978305ed6f3964b09a2fdc30419773029bbbf102c9a0aca4576300839313546e6a9520b7274f1059aae40461d4c215a58cde94ff16cd3c12444ecadea8fde1964ccc1a0bba154a7c98a8fca0153461ccc167496cfeddde229d37c830c38987b25560ad59fb3dcf806e3b9beaec7d3718351444e7a4433de93966d30d74f7a75ef9c9316b1c041061bcca2f4baa9f5f7e764ca32c858833174e4cfe78e3ce2924e95c544728f3a4a83597d3f9dbd550e6a3b1a8c9a753a9728ff0cbdcf600a71d1d76fa10bc8308339766ad3dff1fdf288cb601a3917226ad4ba332383e1e4255987ec1faedf1a6b1f3612a640468d87a8449165d2581c0a8542a130280c8360f97514c3130000000c1e144763f1589e29bbb20114000442343056343c1a261e141c180d04e3703014068403623028100883c1a04028748ea6700c8b0f2515e935d557e99e1353aeb200d9fcc6754ce80c44c8119e6eca14f2955f0f1c331dc4d506d130fd1664da9c86913a19b432400ec18bb911457805b235891c4adeb651768091eab5d77e70eb293d81c4e3c2663444d433d431c64abd4c5e2ac4d1c74d6a65fd83cc3375796e80d929b7b27a360695d2efaa2e71a4927e3b85bf4b25079595fbd2e12d5ce34315756e9604bdbcf2447447949e31e3d015327d4ce6ff8eec4b19a3a68e3a2824240fec1a0c4a29cd8fb6e60e69df702c238fdf5a221c2e4854653009cfbb492309094f03eaf9fe9a44aef6c32dd154193276cd51fd88a713bc8bbb72614d62280f0dcb61821b33646137b0ed5c93179a1cec5d49039ba3d9b0fda804b3e9f375fff67495c531f413d2479b2969aceb4e308b44c3595f73e813bb039d68c5d33d15d074468af96cdc2007853353c5e872a516d2328d89ebea6ff34492bfb54746e9263e4f46a88595c6eb656166e4b4ff48995183760f133699db9bd3f91713e84bb02dc6f0280d3e028b4cb443aae45f1c59f868e386af9883444a0c3313163316ca65789f5ea051f15275dba98b19f039901ed51a26979dcee3cc0057f7ce0115f98c7c50561c4c70dd3a131d688e4dc0c8dbf95b9318d566b8c1a7f13a1820aad3e997bddc246f0501819d6dbe2124d360abe4e6f8b9d368b0e6c9650340fa75193d3f6165c390a5645f63a4630ccf23d0e988b646e2c50832237d45d706f09fd133460c2820c85317b4b310e3f2236f200911d6195baf9b946e95aec8046f4bc1f070cb369f9d994f47d747526de2682ce100b47127ff8a3d2a827fdb6947e9232edde09dc9d118655613024789b870a8c4c4a9023cfda92f006b7e5c2e9d26fce8d3531917d86936c09666eeb5d85105f5d96a1285d0cdc6088b5a9ce6545550fc62a55a82a94c5285fea0553886d36acb8394807da585bc695904589de91399533c582c41d09282f086e43284ed66f127a2d58407d58c66b9c3921c44d3fcd24a15ae568a5ebe6f550257005784a41641645c8feea284388cb28047a9670165e2f8fb11a03a18ace39a114bd716a330bd6276af016d6933466fd716a0dcb83e56b4553be0fa08563cc701c9322b365491d6fe6e067f4ba7832e5027bb66a42fbf982ccc6768fc7dc748ef8e74372037cd9c2e33969a5a8ba4340cca6fee1aac57efee90f26fbc8d5fed521609bde34a2c38edbe02fea1995c710f46cd8926f3ee054665cdef666013642696a1346dce3d9b935fa5dd876d522b5059bc58d8f7019d0b133494c18577e1ade092c4b7e1b12250b93e882fcc097e2ae27503b3d2c0545f0c15adc5328f93e951e631230d6579f7ee5aa358ed8e6ebb9f50ef288a543c45723ff414f08891a6362b4a7c585477a951373813a589b3513eb6ddff4d1dc76fbea8e4b03563d4f4bf6943bedc84aeb862c66254a72fc70a4f997d631840619285488e43d4e4b0618555bf6ff7993cd9c8e83d4507d42df8ac79ecbe7cf2cd1c8d51ef005c319d1b591c3dcaedeee1909129f14831e8e934bd575423155c6daca0b333ac0a6288eff7190aa94d26df0fdcf706f610be82a7eb81c69d36165f0f97435d6808c0b1cb38c91ef6680f65f2e8219ef2f12c2412c80db41909203cb5da2bf76d21f0a6edf92ef1be0a0dc7415ae763989a43686280f82c90a64bafda406d829e67911fbce7e50f8c9d9e4886e2a7f0ae954326d3c32b98d18ea3780d645642a76850a5eb633d09e8062181b846d2463553631a11bde2cb0b24489d5c41f0e67fb1e1e37159e99ea2a66f93fdec382ed9b60121641223d50946ac1edcf085e577a3607d2305510bc6127b38b6ce897021a09a554ad83c3d3d47a05f281ba95037af91c7229f145b624c8e36600b5adef16bcfb3e1760774c91ec83d9675d492ff9d7f424f813ec8f343d935f6c7aa7cfc9c9ad4a26408f147452bee24bf1154bc1421e669da917700f30d08c8414dc3d346bba0a73dd70d13a03c2c17d109ed09a71b79f24fdb04b7e9f589a33452ea6200b3c60c75e108fb01d33f2326022f2babacee5cdd3a9dcd48a8142f5dbb74c2bae7f146779f26c070527dadc04e5142a12d06ac44d69c68bf20ab902b3411ca990c250ab4a8295f5cc92e01c46c6f8313e0730889f94103c04a271a1a9668810665cbd920688daa2ab0ac9089d20193eca01b8c49e910e2df46f68e6512ad5385b1363d016da0c30f6def5453d35149ea2f30c5454043b06f36702f4f07841ed2c652a2d80f84f250e051e9685d1b9bf4710af4ddd3b1d58fa546fbb26549d5cff4339a3fe2537e46cd1a727a1f3f31cace2767cf447b6a6c2b2cd19c2803d01efa9f731c22a779f5235b4e70027e715161c209e23408ee15fe61c60367185730afa1e881b4be630e3034af70f8f12a7a902977fa47676b95d68889364acb145d639177c06c402624e91f0de01b5dc7d81f6f3157d71c7eaa54ed09b7247420260af12fa8d1892c80b00edbbf50cff96ad8f070f160ba02ccb6cdc5422419fe282f4a314fc05b20ce8d96f4f17d56882b6ebf66059e8d107135521051db202c099b7307e57c67fdc9b27b44e03c49d1c0c37b7104c0a645017d4c3bbf39d81951e3fd03ba90de3ac1ec34e460461009e16a010e9b2fbf61e8843fea3b97269bd44ddcfcef2e4f50978aede1bcb4735261489dcb44ede40c69ab926d5ab441563a5cb5cd513c0f480d8247ae1b730a9ca315ebd32be660f6efd4c762259ef8d71dd7b3c4ccd7b401479be91147834f9d154b8f4bfd1783a4174d4dbe951736a5e4549c86cf4313a4b6b52a8645720a692995df1f2ddafc3fca341e4ef76ba08b57f15a397087c02db6ab1caa3c75426fd433828c9d4dff765c57a22196fac2487ea9d1ec49c876ce273a353ff85278af8ab87510bd80ccd5a2a3672c6a1cb965cc4b0ace8757a1d06370430b48829dc1d645c47c30e91d02628f193ebaaaeedfc51f1bda6474eadade3e208cee33da6295565679256c4d188b64fd6f31b0614a508f9efe35e025317eac65e6f1b12ff2ace24a92b0a39e8e280de735f5e47a8ea2a9cd582c7c9f913def21a62c0d9708ac19216f7f90ec15018c5730f95e1f3b416bff1787514842ab6e6242c89073f69b261dd381c169785b0928962393eb211141e728da5a1147b02fdc6cf000f60b8adde605862ca16e03b1bf6e64e4f14ce7584274415967a248abb91498aad5e26818ae60c8a3985822946614154bf53ec087e4a9f42de02d4853b0b5020a2650ef0c9a8866091ddb41163b0f292e0dfa48426251b06f839e40f4b681606e5e4991057489c916c23a1a10f111120ab4a0eea4af2f70dddbfec18ac75d8058ad40d59ef704ab045c8e0506e67794163844b9efd8016364da9804ac9febbadd75aff27748af82661223f915301a21dcc370238d0a602024bd1fd430247b53534b3682fc35b9613ebe0830be262ba420825ba247918cb8c481a0ee359042660b38e7d2380f61c534dd182473045b738b3bd88d0f7cee823bf926103a40ecf63ea4bd14bba392e0725d3e2de186a6ed7d2dd18c0184147882d7d74f20a07f02b5e7c63e49b7d6586fc4b740f60a502dc94fd264c5691ed44e71655069eaa4c6a162a7f9d272abc898c52f2ad1c7f8a443fa2a18d66d8e95035cab372cb177a34618be41c976fec7648352223915586f21a5d968298ae628298b34cc729542169ad6216c156184b117d152582cd571ac306c5c6660a165f72e05a864c8b45e53dd71d6e8f2ead82e65944f5b7e0a3d16f1b608bb280a4b308d129e041a9c05d1260d01661bba70b3bd1596006a0c0e61dce18377031308211085a10ce0fff7f2d175e34669d839a773b2ee70f4e5ac544f1096d2479c1c3d1e8a42363e4511da8a34528dcd5a5986636ac1f1e55b5597d2fb1c7e13d4884e80a663246dba34bad54f32c81aa4d1a664651e8ab850c73c754189b59218b021d5a99db2c9cfd4eda91acac26cab18ad5c587f90ea981b8b1105e91d4b568ba85fbeee8fa609f2542566d22726436032c6f448b381725f00d7f6da63a51e2e5bb3f2080587eb64acd05a38f7b5faa830ccf81e6d83f6050098080b0a6e49c80709e02b988b6e97c2361c40a28eb5bc1f3cc321f7b2dcfd0d10510144060a3d04a1375e0e0825c87bfc2fc3cad2be668e87590abb907db333a2fc263766775c14e4c38ac3f41d5effc1410dcf315b1eece3416c9780ee738e6d0855aeffe1097e9329fc55d3e9ca9087c6df6b80a17e541c786e827099ce4b58a149e602dc906d8922365610ddb843ae98151c14a236db415ee677135d1b7075275f7097a2c7db3723e3d758d21e5462c54bf262b17e835eb50eb854909cddb05de2a3a42e75a28776ce6f754706652b4c3cefe11a91deb97fe26dd998a738d10a1fcfa3090139df3e1792db3d3792fa1a799d5734fe19a0ed81c15a46af7498e80f9e7f4dc8631d6f358b4fb7ce6d8ef496d391fab6b460156a256ac6030b05a1b43bfa89002589276a70e0e265de40dc84a84e5c84865a088ac1bee1ec06abf228b313a4ebfbbf9dcf8943699c0804b3d9fd09db19451f95616d378139a8d9e406c494208525791223aeb81cf08efd360eae8862706d403ae2817bbfe4490fc935d1bb4936a7d0c9544246d4d18beb20606739bb92e84044a44f27a1510d425446305293c1ae2b314d6feffc1b34a3638c94be379af27d12542d4045a5dfb6c7b559947740d5a75e6c831d8220cd4cf8a7e00e3812dd86d3fffdd6d5fe91cdcc08177ebd8e7c15948781be697d412d29e519ede69afd26dbe7d7d40ab6ba565eea80e62448f634b9b4d3ec6d36086214007381ce5e45158e66f6c7617970635580749fbe6003ee96cc3aa74de57ea131aca128ed9258365d8c49e1ee9bebad8baea4ea045a1dd445eb9aa551bff1f35282303aaab55f3159c040cf6999ccc99b2c93d965f3a952315162c2efaeb5d8ba5ab5142e77a35ed59e91f440cfe965d6baa7c729cf219e87ddcea44aab19f5662da7aea85642e55cae1cac810e8ce49c94c99ccc2dc588be5b20033a670bad09ea292df6aed65a28001fb8419a0bfa913f9782bba69d9c96ee5889373a889929155af5a87cd6e6dc1f13309c0064880edea01dda712c0691a2bcdc7a8dd32fd9d35ca9da2a44f5592f67f7b44a519e663a2d9c5b17032bd5cdbeaa0d0a7d36e035512c2eb62b309c35cd4b9408d1eb36395c47f42b69baa83781988d9f9af68c9693d0a2cfb19685467f9358aec464b1a400e3e90b4d15b63f6c618fb20b0e7bfca8b3d223538cbe49551a50f524d8696eaa54bdecccda325b59721ad7040b6dbd9576c28307109f07c6a45f02ada4021f4c27c70ad3fdd457f540fcd71ed041b90b96e85629e021e26b91fb357836a29e1b315ff8a662ceaa88cbc2dae08ab4cab5781264a89cf863656dd54a781afef003aff17b00b649223c61b3fc4ed93210d4083df7f2054ad5f8916a11250776bb474afcde8070a03ac3ab4642a82fd0d8b21d44f9e76c5f7b4306a0afba43828c8f86006948b2a39793e2c5fb67cd36e357a3ffaa4b5257f13d519c84c7e16c014db804ae7b16153b2ab5159a22ee0a8e47d2fcdc0c58a6f5bf55b235a4d367064e0e4dc820982ec17c53e9e09d18cea18a9b89ed4cc921459958cc60da9cd53225ff0e63ed03076fc4a041e6dc7b4e4a14fac99986f362dd8951d8eb7b95d7a28611de36663c2ebd3653358ca005f881c3d9897f0a16e74072eacd5f118d9b2a5efdb9b7b9e891cc2303021a86e3a6c8a1e43f0838d29c226cf57ff04dec33e016d6a36252c36ea35849c8b21a9763f5cfa7921011dab0f91be0ad109e2b39683674bf07674f3dad160c776503f680490ccbce93eb4cfde05655613ba1a611f364a06e2c4a0adef6d9cb79d12ba577a44d7295a8af828c9cc08b0db413592262d83227468524b572695a32bb50bb6910c25f221d1e459e6ed04a760fa838d2590b68433940f58691bdd1f17e25022389e0ea0ca70c8f94b603dae629bccf5a3dd7fafa071d093a0b6d97fc204a4c3ff45e90b1cab00874ad7d756880c480951be64e1ebaca40f0d107a5da0f1d81d059d59379131bb2288c1dcc769827490f4395a5248b4d40e0a1738c6c7df960866c8818bf2f167307a65a474d0e4e7f121d42dae258bd8e368c5f2520a4508a530495d3ac908ef6073ff0c535069c532deb887e8605b2867889a39b28730467b1df045fe000e47e3989990ba1981973806ad4085165a4bc1593924bf2b67ee49a022238f35b57b614c78c50f3931289fbcca9cdcb914d25302c994c1975870e2564a21ce049701d4afadc798e713ade09bd7411a40a316586aa3726e33c138cac22a532ee7c0a93fc9aff14bd712bc8ac13544d8efdfef2e7843779aae61ce72de0f97e7088bb782d4c6e9896a9a7632a536222338323a231b66e690cf92f64e8bbb466abee417ee2713178169814ce3f3cab878fa7828da4eb68c204976587778f0215358ff93d86c9f1da7f5c1a562049cc6a3435ba8543f69b84b781a58a98b04a45c9bf1f81e90db6a5a88153d7df27f23b4aa50662c433b04c5b20103079344120d91c52b119c1bb22f9100ef26cc7d746f92798e276ff8e7ca09fd068f4be423fbf992c0f99d10f7766b89b22c683c64dc34cc33b35d65871eb3f821d063403ed749c9bc0d400fea5c9ac30075cf431c69a462b1a7b219f68ba782255c7e683e988dea42343b4deada812efc2515b95025b3c5dfbd1e36217cb6fd280f0ea49fc22e81368b2a4fc6950ca1ead6c41c756b5cae6fda17481f4cd02a363b2c0518ccb650065be36a855cfb91c4d72196970e151c9429e25dae3d601bcbdea7a8468b85b3b2dfcd06e1a4d8f62a5f9ba3c8de9dd1429530265b37467ac74b3bd920d6b3e86f06b758f4fdc869a213da35b2971ae65609c05d6d6084e0d06e1c838a1dfeb6e0e9981011a7c0e427f88380cc0c2cacdd5752df7ad611a0f182d975ce17712a9cc292b30aa502c2ee3732fa41b2645d9eb76b7d081b0a24508b99c398440d7745229514a28e27a06002167b822982287535039c5f900163da00abc06453a610cbc71752de78db24f47e2841492a81119999032364964a5acf9aec81ee548df6ab3843717447163c286d6a084cfcd2b9f4dc1449111b2b9368afbc09e623171271f8f9ae165710844a9f0f6990e8a43c38f90d9b11560ca380ffe0eb9b59b411e0f65e1c8c6e28f2a1e8049692e181536899aa314129127804e3cbcbb56df21fd5be2c03c036039669bb355c3162a2d55eb00a0c9ee55fdc4e397f151504888d76721fb5d70e156d8a1258c6b39e6bf19bfb5130b57122f4b3b31b9d227e542b7e08768b3ddfc906067efb8909062ae0341bb0bad6e9bba5ed97ed8236beb408a1c2934f37a12f3feb891a1600f14ce07c2e6a83553293ff1441a2ad5e526a9a1cc1bfaa2e0a0097797cc20903967660c36109c602819029fc977350b10d505799971085720cb2c3d0cbb9cd37ba15ebeed57a3444fb18a2c4a10f80bd3b80240124d00086ba8f8576700a84c17239c5732c7a5725b5a22133aa23f7a9d9de905924c9c82c19adb4ea2c5575e61bc497c11f0c04cc23a32fa9a003cecc551d9ea042384a5f201ad5cadcae108a6f41aa960b032dbbaa1a7911ac62da0d819b7e744fa4ae22652b694bcd044772a323281d90205c5d5ee210a6b82251577c6ae0d70c99148ada5170d36751954b0bca23d7de4e8d8a033919278656d6e21f25c700041b0d6296e629f5bb8405c4ff6710ee00e56c1f7a4763b50ca5f061362ceef80e1904b2c69529064cc6b7c5c548fb3b64f259dacf03b36d9ddf5429993ae44100a51d35c2596505a5c040501a0f17751d00d8588369aac0cb8094d3e8b7ae995e87a88d9a3154c393762ef11323a406759431b868e3836867b8756c5dfefc509d8b75ba9614bb62e242becc15a7e25a13b623c0906d6a7a563835424a72c5f0f24e9e428b113c6901459832a3faa68543122575a27282e598e4acb2094be296b260f68461de88f59a22f600c0d4faaef59df66ba38876544c45fbcba2a6fca748dea1c57eb3c297f76d2004cd7a6d2b1001d19559189f29df22407539e9d4a396221bde703d8a83f2aa3c7650da8986bf9018711c782343210861750e0a1ba40ff641ee85635ca4642a6950fe3542eaa1fe3fd05ebee38f56d829c4bb9774623c4aaa54368ddc6509a01ed3334701e80e6774965fd7ab1199de4f99261b602e0d6549df6895d56053b1a492eefe45a0f23c9e58b5eb4af03fa7d6d9ef477a23754125a9b0a8e48f0253de535443e94eb0d9603118d0079697ab395ca1f16c9160c73600dc4bce18130aa31073abbcc1853ebabd656e8d6b432b42cd0597b70f674ca63111b6a2ce57ce8a60fe24597b02aa58fed7bdb80f5343cdfc92a225e8b02fb39eab7c6ef4e5f3cfbbef25b67bf053f58fa5fdab7c4ef565f2c7d4ffaa6a45f91a57df278f53447bbd75979c211925976126a1b251a27ffafa7fb836955d0e6f734b63f4ee894414c595b994e62f69f5dfc09093d7a5a1b410e66c398bc6f4b47a5a07eb14f0cef5a8ecc660f3f7dbc9226d7409b5368f9eff0b0b0c6bc9ccccb0347cf5c992bb49e631458b127ef44c6eb62acd7b203d62621f2d79834a481646816d50cf5669330dbb8d911dff6730373b8ff0674dbd82b6d95485bbf93ae338754ac7d72adc3897e2c41f29bc42a124704c92725e30bc7044937127d24963712a3a2929a8442e22bc9d625924d8c4625518955ca5383447c24aa2bed158073893be9c9b8b4a6a3edb07970af3dde535c082d078f661e3eeec771f892a7c89c475d1e2d3d08f5f8eb61d543aa87411eee95c77615349bc7c523a687821e1e3decf410cee3fd75b7ab207afcec71d68fed4d21c73a7b1fcb800ced88ae215601f185c8a9718ca8e8fea2304402136852e773ef5cc6dc38154709d11ba8328f2ef69ba23cd8f38e0917f7fa4d3b17ab02d6d936067d42722c0d223844530d6951c4dbaf4996653eef3684b379659624809d510afd6d024c57b6b4c4f39aa96ab5c3f991a6838adcf05bd98c7e00db019324c72d133e925ecab8105380e606e7c17f6d8a48295206ef1a84518b7de68e2e209810874e1c2343320a40f1b4dbc21222d3592f062ed80ae8c9a5e8611c534ddf0bacc3d815371278144dc15602ca81bca77d12b599654bb48d3530dfa0894c2a0cec0a33f31ce4b295491e9a716dd2b44043f846b544cc29aa5b420353b830475b0ad4b1d921f2742b8475a9a468637eb2c9724e430dd83247386e389c7a2d01701a0b6127b160a955e938c289cf05c7b3e2973a37ce871adbbdce2e1ab2114a9165a43179437663c6b010db77abcc2786499a1254e337a76a58360c54210ac9fc503d6965dd62437cf0db39b438b5e38219f22413635d0cd2dad7ebb4f8f680eafc362daa48e534991ee59ff25e409cc2097a5bccfdbdd82d9a0af8053c796d80d2482e28b3e47db119aaf2e005942160600ade7379ecacbb61b786d23e501ce9966c9715392a9d0c443b8730c07e787bab84968c58414d7026386141914f631fdbf871078f5baf3e6d6c3d48e16f255407b8e434084640a7c4716e1215323c2666ff88b68b6bd9b11a482cec660cb8586519420036266ed50e4a46870e6a49be064bb1b2fe617a189964cc13a7b6f55350c9e47a48efd563925ba6b746bf34499f85b1ff15f89f6e9148fd39c4839b8f5635a0226184a7aa63ee8d26ee9b4522bf0612846a6fa71cf5a4e7e95cb70bb944e8f0a2083ceed422ca8bf291d12eadd76a283194730311534b83d63c31940547b61049726078751186f9f6808515936e46c375436d78272173d80a20eb96efe242572ba38394724d3b7affe1894e34c4a46804be6ae3a2ab85007f400f4d00ab092d0c802ee0f9f1936b154aa37814cfa7de1542c0e2e67328d7387b3b7615ac044b68e4871e358000f4eeb2736a2abbb01501f26812c729f5cfc034b281e89c10709c5000e29e3ac5c4e65a59f09e34a7257560d1493dc1669ed86e005e7c7c2eed258ffa9cca61f7ae1d1cdb600b5bdb86d501c78206f056b279093c4bf34f53a9f7b40f8065ac623c7389285355f30ae06b429715bbe542a81fb1db278e316c68bd5818da16cd9d2ef063ba5026008fe0acc34c156710c480da7fb0e34668e5876c504a10e8fb7c4e6f07238189ee2d273f5b785ef929d876f94422645f08290457f9b1233676e2b6d51c6525448b06c9988d009923c72398bf297d971300f41a14f2951fdb95a06c4961c8ada0df257e68371bd16c010e43fdc3789c5f671e770f36a86440c45758b44034ff1b16454adf482ba65c0f93c26af075296796c02ff744904ef533db4090cd329522b2053bc24489a4ce527f9c783c227482d9110368caf03081ed464811e98626af381b718793196e6a3439e036c48d52360bec0432f046263ca3e706345dd7c283151ac431b06a5d9bb21278077bb5be32ad756c55b166646b427c3b6a6adad3b7dd5225b3b9af5a103a7d6e99d328fa5b986427b6484f5c5b1a92390a2607ae0a540a68a1f5e8d9193a4fd969267aa04b919c53642753ffe937704d27d1237467a8bb68d66c881de86f74dab694b0cc64ca809fe4a20bb3b4433a4e6be4a3e31d41d3fcac39078512da528f71927d7dc0494c51c15a490fdb2b8eb667cf9e5898d82f57384295f5aa0d761a55f0915048f53ada6d1515c04f93a579fa1d761a47a3639708c398914f465a8e4c3061367ad1368a6cb4da48f46070460b041ff58eb43d232355da918aeb9cc26f9cab1ccee9352a83eafc84869b782abf711c145aff6ce44d10b77ec1ef51193420ced8dfc141387e2c201fda4046f30455b2ab0ddb4036c4541a4a690e27247c29e9e5bd67e90c1627d3e7302ada05801bc118252935b9d6fd729c58552b9d376586f1308bfcccef22092febbd8ed82b390135e3154ea3e7e0fae5fb6a088f70c5c0f2f1d711ed60598fb8e9f5bd388644d7b493591821bcda8165c9ca8d95185676b71ace0a438e9585b482291764a89f3a65e8d499d080b48c851b94e44a410f8d8c0407daa6485209474185cb1711ee3a1cf3ea0dfc4645478157512e0433e4256a180698ce52c6e88255648aff76ee29943c60257aed1187b9a5be03a71ad645440c5c22a8a4542ea834f2f1362820b55135788acba4ba50956084a7ef114a81f87eea84e022f79cb9e30175a4804c444969a55df25710177434ea8a4fb48337c90f0dd289c71f3d0f00214229f0e9a381ce1d38bdef8056cd6e2a67e19850308a02b5743f285e818c56861ea407b2313bf1e9cdf8ab7193622de702fa0e09ee882b84e3c2c085d42f326fc4c423f783fb2a93b223540823bdf4f3762589803485210cdc40243ca219683c6a0a372b6b32ad5fcc174375c931adb1a6d58574d697e45a66fb79181559c8b71f816a90eaad50be42f3f741fdc7057f5e9544c8b8e4d94acda85b543398d3038c13f3de1697219b3407b3519b8d266024ecbcca858d4c3d2788c22b0ddb80641b18ed807b5081df10982f55b10c4cc49ea976c3cff91c73449f665d5134702930e091782ff2f88d7e5ad6582a0d0492edc0f9c3a52f57c00fb20f08e607104f2d0b7b96e92f97211632e90998c85d0fe722d2adb6524728f97f6802c85ea002f3b884379fdf4f23f8dadf5c0582ddce2ef23de637d3bcf6fe6e7b6b495e266b758f847d9d83dd179e38705a9ab87fd8fefb850bb4038a460987e2e52746597357b121ba6ece14ad9c14dcc082aa3d8a5320ec01bca340dc06cd1269c3bbf151efff71bd48901bed4d84905f411f3baf9166d0ab7cd4fb7fd40b20f4cac7bdf161bda88f355d7706d8eb7d4ceff8b8f11a2fb87a852037dce3d5c5bb25d156d5c92020d886c6b87290b213a4308548b3ba0cf6de6190751fc72032ad26acdd500fc1a720eaa4a762a274048c70c3b8cc2c3ed7b7deeeeffd78ffca3dbeb3f8ab9ab7579653a85aac3ff87e421a62fb19b6d99adf42cf146c9967a0e52381cd40bc2d0b75b7098c37d644725581eb609e8cc5c044ec81dd044c49389bf0a85165ff8a24e593f4fff04477fc0c5bb2de8c423ea20969dfc5d95a4db48b8a18bdcb601bc2a6d66c0700d63790155d884e9b4fbad5242e16906233635d2d8d453fef428faffac90deeb00840d88de0dc270024710739a621fc6ac206054925b84a81d3e89f10961b21482f37482a0fe20d0842ef222aca9c29825469a7d5bdf6bff881e13124dc0c6e5402750450015da19672fcc76340c634d8037cccb65a58caedaf501b047c3c69b794a256b5857c0881648784ef3e1517033a6d15e33111774898956608481597398027af7cab65075a5ea87f8909b13ee98a0cf1a40c443a51fa3c0bdb119a11815c7ca9f7ef86f13a1c6fb4c8038c7961c2320aeeb4e758895cab3cddece684873ad1cba72cd9aa3a40283c117722a0d197dfc71833ba3b1bb164a52f4fc7a1266b8a840937a33eb338f33360d475859b1d0dfff8dc3f88190fc4a014cb31b55748203c6ab6fca35a1b1e64c1a3eb2217454abf630c48495fadcf049f13e3306f652bfd225de84581b01bbee778bec44af16e551a87014bf6a1037c61f558233492dd320cabd87ea73802b3237cc792d2e520fd46380fc17e089cab21a84e3aec1e561646071b01731ed38f298e59c784c6a4625ac65c1123505716eb5e22e8ce81d03a7019465d22849273208a9b5b722f77bd37353bb48349da92b91bae90a610fa435d58f7b4ada1e16909c43de3fc97e094c3de8843355c5c101c60a0767679c823b7d6b3b7bde87ac1f262c0196de7c755e705b049e05096ca9a6cdeb141029844fe645fe976a0553c32ece269c82370576322f4ab1d4d8858dcdbef59f6e2ec0370d2ba9f39b8ae1e30766c2cae199eefd86c44759c75f43a2eeafc5fa4092f909c967278f689a8384cf8b0dd3e040fe03c2598811955d1301ebf2c6985e70ed978b83e131afa13c6af2b5cc25ec6e97733292be305b9a537430c2a6ccffcee4904c813933dfa228373af89f71aa0d6d216dc6d2ffff748a2bc0c527ddf106149a52584fbf05a780b8f179782300377280716d916ac67813ac52964e22bf1f453b7595487f3fc078e24c4f35cfac6c27ad708ccdab1bf84a8969bcb8243482399106f3b0a3d280e4187136b8e65811cf974eb94ab2fcd433aac47ddcb36e61d4c0f79f2cf17269290f1f3159e2f75b1f0dee813eca4021e89848e0b3660b46439330f0f0f0f0f0f0f0f8f3121b5b54f48424a524a4aabd75247e420a524a54c2989b7483b7a3a33f129a6c9de6498f8040402c20bc70b970ba529194ac5532a28a5c7a80127ee5f214f5a8e783a3168c07a4a297878ca32d5b663164c8ed729a9f85bf27ae3396c6c60c60ccf614305336698a143166c66448d9d4468e4d7c4823fedaff9a34760c189ee1077731c9d745baf489ecac9cdb74757703a42ca15730a19947dad605f82662425ace3dbc70acecdc3d44790ef216dabe0934a4a664a4e1e3cffaae0a444492b62a954b09a76a742aca8bd6799252a785bcb1d59a994498c769c82ffa484d211377595af29f83af9a33f9b8f1252540a6ee2a65df797284953527016f4925c3f4fdb3419052f2612c54f6645c1f9c5dc94dbaf34e8e0e1e80805abab13420e25bede9b147480824d136356ebd99fe0d4fd57f637992df6da800b68514ed0e1095ebb2477ddc43bc1c41c925f98162dbe419c603d922e696b494d66cf2698e469a1e67e5737d2046fcaf467d7f6c7c8e94c70494fd9e40ea57dbfc504aba5c77b83ada714d24b30392c55292d1ef27f8a25f8ad0c169e1daa76639560547559b885790a756180c60942072598bc41529c1c3ad4e23e6346c724b8b5f8e31b31eb3eac1a6934eaa6d021094ef848c8e1679d69523f414f60c60c2f3a22c1d786082a9a6b4ec999ea80049783526d2288fe11bc8ed021c7ecd31bb2c8118cd60ccbef49d4434f7e8398024e021d8de034e4dff43da943ca0d6211e86004abc12b8bcceb9e47c72c82efec5082418722d8efec7927294b29c9b023119cea94dd4bc6122ef22e0e175d74d1ef051765800e44f01526d3c4cfd095cf6c41c721ceebcba2eb4e372ae83004fb1ad2b43f3c2479ea1bdc51082e67e57491a5ec20042ba23db5ad8e490a5dc0050e1a9e6c98391737726cc720f00b49a530e5a2a26e0c3a04c1994ab595f54d67aadf11085627e5b2720b992de21d8060628d797b55a9945448c71f38a1ffa305b788ada1593fb06fb62629546366d3fbc05fafb7794bb0ccb4f9c07e8c6cb2d4b4072eaa78aa4efa3cbd5c3d30da79a2e7f4f595369a072ebf267597206944060f5c063d12ef2f596a767760b4268d3177ec16f5abc30e33e8a8c349765042c7545a0ac90e3af03958c88a215f4ea2b21d73b0191d72603fc9d131258fef9713904003a0868e38701b2c8d5c1711b225b57ff1451b1278c1c50d0a748e66c0052420811933686400ced00107f672f2159d624cea94ae60a1e30d8caba958a752c8775d631b3adcc0d7c7e049d3db36f023838aa0a116717b3674b081754b225685a05e035b6eaf6ea2a63ffda506d663cea1be7e3f8f9b062684a42fee6890adf92fde810636e7e5a4824cb3b45981a0e30cecae8ae6bd3bbfb69c02860e337071623ecd1c3aa59c45eb858e32707f994d591695e4c46fd0485e943b420719b84b4dc143b2181c5c181f748c814d31668b90f24bed88e4b0a182d221063ef2fa8dd96ec4be48018d30b041481229e4244fe434707071050c5c98fa4d41a62b499e44838e2fb09147d4d94775f111aa91c44a0076d0e10526ad28d15841684613e902a7b5b784a4bb50a1830b8cb6eefc93906369e8d802a74bea5f67ffeef4592d30aafbb3d783e7deb4d0d09105ce72d04188125a648c3916b811faaf6a21bd4d4cf10aec5a0c1dc945a9521347d46105365db2fe0ec2bf62765805f6ddf4e7385eeb163aa8c0bf7d324b093a22f7d33105c6826e0da9e33987d0b09156061d52e0af920ceaed453fa64a0abe880257a36e528aa8a3aff324b0fb4107143ccf5fb5fa136dd0501e743c81f7495a993a7a9ac72cdde81518740263df1aa4c5b1bca63d4d60438f6bf64d4a3d05bf461adea083099c1cfba469540c9a7a61435b80bca0000ae00e1d4b6092d6e49a827e97b549097cad779654913faba808858e24702ad47b2d54590ee16f193a90c0e71044c8bdb1f65dcd11b864392d57cc15b91742860e23f0b529c4226e4811f8c937713787e7583a18860e2270c94a9d48d14e770c818d1fb1b5e4256971371a2dd00f740881fb4fc24e488aa03cfb0e7404815359a966f235d79368aed001045e34770a399ba44e3a2b56e8f80117fc3d04b52153238d469d0e3a7cc076a4944a074fdd7b658db416acf5800d21966bcc53513dc303fe92f6ca1b32d4b10326e6a6354f162fa7d420a043077c090922ea256f8624e580af983daee4b30bcf260eb8bca8a34fc449bd7f75dc805352d3fe7609ad4ad7068c52e33b4144936be91b1d35e03c09fd697f6b9dc65423cd4632d322030ec841070d18b7affeffb190aacf8213622a88adbe27adb194051bb7bf2bc62ad3696e0660c4824d69539e64ab1efd9297b0e0bdd7df630c39e60418afe043bcb451ff25e84b2379d1e45cc16669ab6822061932556d048c56f0371e36415465d18c16197080b10183158c655325a3dadd5b560b2360ac821dbd92ae2fa7a42a7acf8dee95ed2e239b0418a96054a4c9c9f2078931a660a082d5089e9d95a2ed22458b2db4d8420bf30818a7e093d2eb2d49957809cd14bc4f4e5bfae40499925a0abe45e69d2521318690cad17e72dc80410a368b7efd90745809114f41c01805ebd9eb7df1828aba9fc11005fbaf41dfeb4b67356d8d2c2c1a80110a2ec4fbced39b2b928ae2e8030c50f0ad31c7d4aaaf9553e4e200e3137caea67753be9a2fe8a400c31368cf76b24dda453db5000010303a91a7eeed0ac13c5e3f80c109f6433669e5c1e2a9bce826b86c3a98ca21f754a4cf8dbe40299a6033fe691f9591f2f51f061899e0da6fbf52abe4fbab6082d3bfa395426b525a62b904ef19724c97a24e12ffa025f810b47defc40ede2eaa042754c794bc36e9a98a53820932fe645162b973266b128cf726a96ff142352b0926ae7ebeedbcf4b596014624f85361a75b7df6714220c1a5b6f4a4b324791e458f60f3ed72cc19b9bf73c411e40fc1bd73f746b0a63ba3a6902445513a23d82a4b5dea679d37ca4570327da9a4529922d8a094f64f113dc97037115c5e6f48c132e76bf8886025e62b7dd63985db7b08ae93e98ca9534ba5983704db9382f9067d39f7ad8560ef93daf2b28d105c3013135372b389b006c187875022b778cafb7bc31004bf195ff3dd0895589a0ec0080497f306cf3c75a1f29a040b3000c1e48ea5cc2649f78ca32bc0f803fb9927e9896b69681f0330fcc06898876c17327ddabc0f9ca5b6564ca67225e52f1060f0810b9195d734e7db905335d26ca4f4018c3df0b962e6deca9962d10327a964f2daf423b7926aa4a5d4346c24d30318796062aac98a936e6cadad9176c34990bc0bd4010c3cd89ff95fb91e52793968b4e0468e25598071076e82d4d518d592aeb809fa0476605c84ceb7119279bc8fe100461dd8d2a419aa555474a7d448ab1bc0a0032b1a24a49cb2eaea8e36ca66ccc0d145172968c0161478038c39f071640ae197b264f2a01a69280032c090c3b61be32565eabb730088028c3870494d44dfa4a275f90b07f6b3a62447ff7f48ad1a09da8b968041018801c61bb8f83d312ff807bd34ddb08132100b186ee0b754a760992f681c2260b481d32f4fb596db647c370301830de5a997875f673c598db4b741e3d0c05803a7329e92372a4e022fb830247f80a1063ec4cfb94d6ee6268ba681ab89f599a496d22112c3051868e0feb285b28d24e35ab600e30c9c45aad211a692d000c30c8cfd071962d41ad174712307a2d18243a30cfc7b99672b4d3949ca910c30c8c0e6fb243379cebf51df301b03373ad2bd2e26494a4462e044040f7d15d1a46a65038c30f09541c8b6104769a81b0c7c753279426e48a9319906185f60fb5fbc62975a47fc6b24046078e11113c9fef9ffaa3287d105b6d428d51f5754d0ae6aa4599a4ee003341200830b8c48d7a98269ee18ca13dcb8a10005cc98817c035d74d127c0c08c195be03d87de9e28e1e96987a105f64cc7d041ec3e30c0c802e721079545971a4d8f1d16d81c796326216f185740e74c1db28bfd56e02a66f68a0ec96973ea0930aac06bce6221a6f206993354e0aa468b0aadf89a9d62028c297039a4d32a414bff27111a0f6048814b49640ed31b31e8e93900230a9c087a2fc4d39ca43d9f000c28f06a6a44ccc94d5d502a3304184fe02ba69096f49350e67921c070022342321d794253232d078c2630f9c72af37ba8a568aca1000613187915d4af36a95cf5daf8c2d1043096c08e96fa9873df35d26ac050029faf3fad093d31a5bbd448c3190148f068059bf52d66ef901923bf1eace06aaf764210b9c72a188f9ff4c5948c9a6378e0a10ac62a77084a4491f719d3399a0137f04805e321b2c6b0efaed31954b06dea962789a6b47c993123070e270117094fc16a8af92586a89a6448c1c0c3144cce7275f3a8ae79548f52f06ada31a4d51f7539b413f8000d16789082bffd0e3dd31e09ccf01805eb39da26ed639d5b4913f8008d2f3c44c1a68bf729df42279d6a90808b1b34be7037c1bd4728f8ecde762a4ba06072e61a75f22d9fe0f45bea88dc9954d248199e073c3cc165ab77d027833c61f94eb0773a832cab0921c939c1b8baa88852f24df0b9e2080daa214df0216c7754b724ef0c658293a63ab2a6ce23f53e98e04ab9e48b1ad44ab29c4bb02151c342ecd38b765b82c9a92a497ecaa2c6dd4a30394d7e68087aeddba504374a2d26097253fc5d27c17f8dd08ea0a5e67f4a82cfa8adf7613196dc4d24b88ffeff9699ef7e3190e0e4782c31cf1174102a8f60d7f3a71ba5c4930c298e6035d46628651db61d6c04971db7da649fe7df8e119c4a4149b3d32f82ff68317ca32711324811dc774e7921672d119c8e57e90c4ac8eab210c1a9a484d4f862a534bc1a695e3c0dcfd13772e038880bf03804aba1f7c1c79432dd202f52d080b3f430041b736f8e9c4699f6da0bc1e656cb1d25d64ab6122118f91d6f7d62e6cfb11e04eb112be9d19f20b80c49738f504b1de225106c929982b0af511b6904045f7921f7e9789327e80f8c6adb68c7eab7d1cec30fbc67045922aa059dd85d7af4e13cf8e0b1079b31060f3d9c7aa33fa5e47469c005b420c7068f3c9c071e583d3df244526d2a6f907fe1450abe01382640812db4d8a2015a64200236328064021fa021804a78dc81bf0d6e5b25a2259196871dd8fdd7f50e0f41e3df1e7560cb5377ba9c3f5552e2c276e0410746e926d55deabf23a95c0088e03107ded42c4ecc7d3284ea75f09003ab6a31d364c7a0db937160d399507949b5396c64408b2de0c05bcee94ba998ea2f21273cdec0e7ce23fc94d64d88f90624e1e1063eb5e670ef5c9a438ab581bd931f6a4c4653a173b4d8821cc2830d5c9a103fe4f4e59f7d6468f0580397b32521c57cf2070f35b0d92e53f576877c927273f048039323270d41b99fb7bf72f04003939406952f23581eef68b4c1e30cbc66cb6baa63c94b9d3703139210adf71d595a82cac0ae07e193840613912032f09d95425afd6360f2e4cd21aae7564c550c9c309df933b7835fb084810bb5b2d21002031b9a2f495745be53ff17d8531aa4b5e6eceb96e405ee5255b6d6dcd0340f011e5de0367b68470f1616e20227d2eddc37d85b6074eedc9c72b25a60ec46d5e999ba9c72cd0223ccf46de5a6e9742616f80f5a91b3d66fced75c811f11f57e5267d660b202a355edfd29248f6d972a7059416d95ec8c1a3c4205ced6f2b628d3794c810f3993f6bc9162324f57800a1e52e04b480b3d5513a4f5ed110576f3e514c735a45d4e99c0030a7c861063984930eb9026e4c2e3099c9b4e22688eceaafa3b81dba429a7984a9dab83155080210ef36802579b635df44d954ce916e1c1043ea46c9aa563e6b10446dfaaa6bf4a15aca246f050023b4282dede5df90725d982471258f7fe2816af3ddd5624703bdea7bfedf4678c68c1e3089cd064fb7d2d2286c9088c29c91a52d4eb0a1e45e0236e6886c7495a821c041e4460834c49176c73fe7beb31044ea60a91738a71250979088111312747b5adcc23084c122aa44cf22a0f20f02121c7b6cf4954f7a6465a0b6ee45063c3e307ec7dda34fe39a66e30053ee0dd83728baf21dd644b8db413a4007909bce0225be0d1035e350615834e212c49be465a5981070f98aca24b34da6ae51e3be07a5dff4ab958238d460b6ee428393c74c0bd8f10aab27257ea9f032e5db598a794e57a4133669475c10307bcc6fe2e3d6ec07e5433559bf2353da7461a0d1b393c6cc0267d6f955c9e46b68b9c056e47c3a306dc45ec51a5ac2d66b088c08306fcf7a6a74ee9e324b8d135d02c181d3529ffa859324991460e1c292841eaca400c59b0f9a2b3a59be853da353162c1c64cd66964ff38a41003169c46ba9c41ad826e88f10a2627156a375fd4158cf4aaec20820ac973e7857371a3015a6cd182193368588c56c46005e36b1152455256a95b05d7d93dfb3e6570f71055704aa64bb13685f869cc18a960c7dc3666f7a9fd9ca382dd08c94e6737113b055f6661f984c590738818c314b9aa55e52c8dd1d42ca34ed618d26dd6766aa499c0868d1a34ba485cdce1214629b8bebc3751ea79498514c4f4be979a6246c19b50eaf52b8735d2708b1a39b8e0008e2eba5080165b68a1c5165a68b1851618d0220311d0824999ab808b220a46a407b5cba6a2878c8682cf2721265332573140c1e51845e5ec4d9a945e7d88f10946342519ef3bd4b37ec4137c0ac9b736d6a89887189de064ce75fa3ce48f5613ac430c4eb07e9e3668d9654e21493818b07088b1093ed3636a11a62e52e50a1b626882d7789692a9a568b14506228003c71a62648295e456ee27caf276d4628b2e1a139caa84549a218f5ec9b900a315625c828d25d4a5b78cf55a1e4b1435a9d363414670e4701c38ba125cb692b13b6f55de2053236d8d023128c176fab84954526692a449b0976152d46411de16920497432af9313b4511033122c1a9bb2074eea0b682ed376cd0b03b400c4870d952ac8b56e3997edc0208311ec1a70f96a643e893f96b4770324cae5725a9d9444412a3114c6c537aad47a6f4d931825127db2f4af61c82ed050d37bb8bb10836e91c159328fd0e221e85188a60cc738a693fe2798960f74d88a4d01ef48b880836289d79b2e8e468216bc1a1c145dd03ee109c8a95b7c4daab9166086e4da84ea9757c73eac210a310dca53549323b48fa3d1182f114abb64f445cd42fc41804af6bde71b437444e108c972a59225e786bcc6ba4adc0861781e0be53d49ecaa2e91ebf91022fc40004bb9e556ff1b56bf207fec2bbd2c8bdb30c215088e1074e864ed0aa8c90009f10a30f5caab547d172ad97748d5c420c3e303a6811e11b74759a7a0facb85568868c9ea3c50fc4d003a3217bd6504d4b22796083f0902e3f2fe95c1762e081dfd35c53c29412ca3bf09691ded36f8e88fad8e18b22aa73fdf31a69756077e455363b9d0e5c5fd4117aae29a84cb939f0f6197244cfb9dc532907ee2a6dc516370b1762c48133f5a733f7a4706064ceafb42b421be30ddcc4cefe65b981bbe0be9e326d884fbe0d7c1c25b24ab610830d7cee0a524f6de7b57e6aa4ad81fffbecff4ce376e5500397634bae1d93c68d1b9e062e9d4efa13cfa0611349f7ca83e40c7c9410724e976fd4c5941a696660e4e94b2252ccad0c6cbcbe9c734ceb924a9aa3b9487e821b9ea39c200619585189227d6d53236d0c671e59694a2d0656638ab9c9ae114134b00431c2c089b098f64352cc3e18f88f16169479ab4d8e91408c2f302aef87d2ccb92d6e9404de8517f80aa9dee3b9f1235d606c94e609e5f5e29ae4029b73cd6d4cd828114f13630bdca9f10c95a216626881510b651d923784329d2cb0164310125c628658e0748a3997a8a0235ffd735760b29fda6ef2743926252b701f2aed2c998a355e57818da59134354becd1392a303a07d3f9da3b2429b11a6953e0c2c7ff3a6a6ecaa954234d0a9c950c49a7ccbc4446a2c0059d114385b4af13d51a696b218801052ed365ccb81e47984fe0b2b86aba31218244538e14d810410c27703a66a769319926b0a33d3f54d289099c6b6b277dea2cc325303176895e9126253022b609ed96df22799e04fe235b4e503b2652be91c0a5da65151162e63ca523f0f6499d5263954da79206621881cd74559335bc7b3f5223cdb010a3086c5ace7fb7691a3452fb8d3b33821844e04352a9e933aa246b085c4a7731e9979043525c084cfed1e29152a74d9fad915670e4c061e30c023182c06afddff5be8554c2d222030ec84004baf8e2040f68c08c19ef89860abc701578c176851840e02387b5c6d5fed1efb161e302c609317ec0a59b6c95b4e9bc9bef183ee0eabff7278d27460f38d339692d57a8a044bb80183ce04d73e59113a4694b290d2b7a88b103463d3b2fc92b0fbb910ef84c32e68831b988a63a07fc6bce5477fb49e4d662e080338f9c5493f73760e409adced33f1862d880039c08cf9b25e97bec3e158cecf15477f5a5af4605233f6ad67442648af6145c060dd994b6ce501353b01edbc264d2e9631d96825196ddad4a65fff6470a3e68d2ae4e3712832419551b05ef31d7c79052ca8f1aab91562307172b30d645a30c5170a9628c1d42893a335d8db44caa38b6033242c1574a224529710d3223a0e03ca449572363b02ffd27f810ddd5d77c927d9072514c8b0c38c0bbf8220519b8fb820c4fb0994b748a87529f773a199d6092f0fba821d489dcd5066470821beda5bdffe287fd65a12063138cfab497fd2fc752d76568824d21fd6eb5780e299a8c4cb041bbc44bb2490626f8bf20e4e4d23b694bc4041997e0fc3b8752d7cfb5ce58a212ec45d7915ae973ccdfcba0047b27c4d742f2c89804fbaa27d126e73224c17f8dbd28cfcf2163908c48305a2a5f8f4e823e2a03125cacf348224ddc2ec87804bb7efb5927e9e4b9ef0826fa9b66cbbe89a7f346706bd9e79a274670275f456d1cb9d79d5d04f717ab4aa915c16529a5cc3b79ca5ad944f049e93a11dbdc537244f05555715b63a52c113d04679f4fd4784ac92b260dc19e7a89ff1d3b3db68560624413797dc44ea247083e272539550461f69a41702129e5a9b7b1bf4e10bc650a2a684fd5039c2023105c7894689faaad363f37640082af3e15f33af687468e147c91e60f6cd75a249d5367b3f2981fb8cc1941c4d5f4411fa5566b5bf36b5c1c4f34bef01b5fe05824c8e0039f2b55ae05197b40aded84f25e53da480603197a60edc5f4b49d87a4cf3379487b9bb4ef3c92aea3810c3c305a53e4a9ac216e6db0469a17ed5d2075818c3bf096d3856429febb586d59906107f66a74529d3585a424c70b1975e03ce4f3b428f21a8932e8c0765f2a33515a3f9ad648f3c2067216b8c998039719d6d9e46a726074f342551c292a6e70780964c48153aa72678e1fd93c66ec0a32e0c09a0875a5335310163926e374c173bdafe3062e89147c9372977c226d603de22515c911e375cf062ec8f83942eef1d0a0b30626f88e760941d6e566d5c09d4649232ff4594cc934f05d2aa297ba0822f468e05cd2441749f2abd233b0e9af36f456e4aa57ccc0e7a9feab495f414e481978d33962b628f53e3224039b33e62c495a8b0ce518380d316ed5e8ee754a254186183820230cec6d8b16111fa920030c9cd20d79a695a1fd5f20c8f80217f2b95712ea933686d0f0027b1a3e3182b264e0ae0b8c12215510dae45c60359dc817af94b6c0e8a79c1bb5d4f77aa405de5cdb74446ada484159e0765fc732af57e70e6181bbfb492b5a934e10cf15d8d4ea262ae5ea50aab502174be68d21a8282ae25a054e0615d5be4f757fe7cc9821830a7ca84fc1c44598104a3a05b65b35f6a4cea14d4c2970e3253b8ddb8b484a13053642b45819dc4424cb4081dfcc1e2a8ebb2425234fb82f67ee04eead83762a4fd9bc824d6024e75869344529e5392694efb3658d29a42c81db10aa4fb608b5053294c0d6ededa72f51d1b93256909104fe5350a1a35208a9420709ac67b869485abaceeb2338d691d35ea669047ed4ffc9319d9c31e32c0e328ac0b85f36ddd490085c7f705bdfdd5279913104fee45e0e35bdb0b31b0d3284c069845c55d922230849ad749dec723287175c68e0466f607b200308ecc4aca53f758a3925f90363704b63e6dd1753b620c30736c368c8e841c9e0017bf162d555b56ffea406942063079c576b281d7453788f3366b4e0460e2f3c083274c06a66cc3984a0e1a6fa1164e4a0dc7e3175ff8e051938b0b345634e704d31fb1b6c29659b670649deaeb962c84f8b20c49b820c1bf09ab531638990242765041935e0b209194b8aa56c70c181e43366e0281cc8a001972a7808edaca79de41fb3e084243522869cee8490f990056b23729b50e1971c7cc482512958127d13359b5748830f58a0ef6a820aaa57d493b49a3ccb1e4d57b062a7720a3ab5db0a45957a48c242061964057b3a66cc1fdfa4986a6f7cc102fec286aee224355228212fbeaf0a540a75de49fb099954e03f298f28212accb415c93ae8ebd66e9080d8ad59093e4ec1a78ed0a31282d0112153707b766934c54e29188f144b7e1c99b4a73429b80dcb2958688ba697320a46e9fc60f92a9ed3591fa2e094c79f9433fb72d3130a56744e4a59e6a0d1e20414dc79a83e0fda8f4f1462b28db1fcdbdaac91863a840f4fdcd9ad734d35937a1df8e8c47d7082ed3ed11925d5fbbbffb10956c5f24788e62e2ab626d89cda749490d68f4cf031a78da619417aeeeb295070021fa021801b7c6082d3a172723b9d3ab1342e6ed820c11962fab804671da3c6eca492363dd6489b3163c68c0e68b1851612c08002dc1b90fc4af161092e5432f5ddd6a07f3c3492175cbcb1c0d3096cdc6840f2828b06ac81e2a3124c3aaf9843997f8e9bfda0046ff6edfaabd19304f524f8714dfde9dd2aa693241811c5837787de3d8f44824d31b9566ae7cfbaf003126c2ea5520ee54978883e8271cf9e2cd3bf594efa8723b832fff8fed9f4d108f6745a8d765c8b91941f8ce07227b73f09f93a7ef063119c8668ca4c89a24ead0f45b031f55b5626a5f9d6fc48041b64c829e59cb4acaf43b6f0810846e555d08fa1ea53c80fc16e4e272187e8c3106cade68e8a26f4c4d41f85e0b3a6ca9244559b90dd072158cd49f976f613fa921f8360b527dae9d54b3db9fa1004db16d4494c8baffdf98f40f0e1c9e4269deb0720186dadbd1d524c117ddb2f3efec0a80d322c5e103d513f303e29650e1d7699bcac83c1471fb8b8d7975797e927311a7ea38fe10337d69de4988a8468e11e78afa0df752ff8a107ae23951069554bfde9e0f8c8036f1f2b92f094f4c64e1b5d9032047ce081efca6b394dcfc23bfd710756cb4d6f527f69418ee6c30e5c52634adde8e8764ca903636d52cf723a912fda0f3af0312499f39afe1f7360443a2d3f21ed36c6fc871cf8afa064b2ace79dcef2110776829d483929992a09990f38302a26a1e3e8c9b13ac67cbc8151dd793ae712a279ff0f37f021ae96c689e2973c79818f36b062ff914c722af92967cb123ed8c0fe27fd1b542acb7c9f123ed6c0c9d8a984d20c491c5cac171f6ae0fa4d997e0abdcf7da6c166d4f840032f9a9521585ac82dffe30cdcc99824050fd10f33701e296f44cdf6471958eda02455068f9653f0830caca4d8c9d5c61f037e8881099af268efcd787dfb4718d89c967b9f2fb5ed8a3ec0c0e7a0524a8a9b4654ce7d7c81cfec761df32e5161faf0026feafea22eb21f5d60cc3f075315838841eb35d2f04a0d3eb890a6a45649e70d8eae912ff8d802efef9a4ce87e2c65f91a6938b8282df8d00267e95e35d384605b1678ddcb11fa29c898597917366ed0c002ab959b4b42eeed3ca181830bc2828f2b7039989652d32935d2f0c30a8cb0cbfadb552724c96ba4e1e81af751052e26f389969ade23bd393ea8c0a99c838be7f4a9c24a16828f295c7d4881539ffe63786f28f01105c6b5cefb92e9943bfe8002e7aba669924ccbf59c65c2c713780972926f50c92685c8096ca520d4468914dc6296081f4de0ececedcc474a0e8f0be183097ce50b3afd268b153f3880f0b1044e54b99907e996dbb4317c288153f25ce28d6b08258307868f24f01394298d6216eb24db850f24704974344fc1f25d2afbe3089c9d7b56a8a7f248b9113849933798e5ca471118a51db47eae8e296e0c143e88c0462e2d42771aad1ffd6bc2c7103895835d8ed61b5397324008fcf7557592a3a2dc844a193e82c0e8ed52f23d8af85b3a0c1f40606f54cec1e266d3d3e7fd80310f26477f1041552f1f70935f538af8d9470f88de1953cd552cab32b6c7f01c644a2aad91f67ee3cee40a3e78c0beb678a50ea6c55fb4032e551295830c2a4a2cb50ed8108fa2b565c9d19e02143803906f60c68c1933707861e32307bc85a45719f69124c4940b1f386063d6179d379fcc6a9d0ca4143e6ec0fa96fe98a23fb796f861035ea4a80fe22afaa8011f16aae3ba994c42947af8a0011b928520ae51fc63d299055fe7aa5e2a043bb1943b78c88211a637c87a3b8b6beaf400128b42a98d31e8178f153c60c177e8535afe1921063d7905a752b44eca21befe8ece156c0e52fbce621c25540c034660b246ea4d3ddfd6eb8bc0d9e438923de514ab9e08dc55bceb9359a6213f04467b7fc8dcf974d0bb10124a973e08bcd65f32ede974aa4060358a12aad631acfc01db5bd9da366cb3877cc0dd081935633c1573ba075c44d752625a76223c60bbdb2bfba885d276c0be08116c475d94d4ea8037e91fff4348b12d25079c1275e341e898f7c5019baafdb5d2ed4e94dc80094a8688faa7367fd006fc8e8b0879a38abed48051a35bd9399632b402d080514987fc591b29f7b3e072c71169b34f845616acd768659094c782334d4987da0e0bde4d4f92a04fc96bd32bd8db20fb3a23858b5cc1c65115720e21d3d256306e4944922521c81c6205ff12ad5acf4c4d735661b2e0aaa9fe2655f09e7f6df487a454b0112fa994dd3c882c49a86054cc1e32675db749d22998ece1a7b326b72c319982f5b8a9a28fe54bafa352302a640549dbe2dd3a22053fcaf49b0a32e42073340aced33f9b3ca5ee4b098982b3e0b14a864877a38442c149d09031e974b92642a0e0c736c5fc63a15f1ff409eed426a6679ac674234f70597db5bcfb62451a3bc185a8ee649ba29e082758f3ee64ff7b3a68bd092ee55431b5e8e4f94d4db019fdd3645e6a4f4d2638f59758ba2ca9fd10136c0e6e962aa86a3ad9253879273f830a5132a7902538b7aae41f9a45f44ab09f16646fc85322bd9634b7ae3e097ee3faebe9cffb133f9260527e3a2d6a234acc23c175d61c2dcfb4165a487015761f43c8a052c4a047f0174476915b11a3ba8e6037e6760d2286598c368293f43ee249533a9711ac9e5a456c11a96d2e82ff4a4205957e2ad75404a74ac6dd74ffa85f2682913104d1a33aa2990c22f8daa04ee792f9c6c2738833ea7bc7102891b1f772af5308c6ef53e9484944ca09c1760c4aa8d1f126c02018a55df42475775abd092008cee4673a61fe23e4a809100836bf490d299dd4baa0260020b83eef0d162d2c784e13e00f9c68eec578e9cb33dd04f00313f226dfa46356fa7a1d40803e70fdaae555793d8a960f7ca89c83c8a7ddbfee1ed8533a2664ff27c9ab1e182b8d225cbf4d4c9a07269e59b020647b87140f8c502a478a3596fdd33bb0218ae6ffdcf4aba71df87fdb10849e75607469124ae7ad9c7d3af0a92798a64f4977690e5cd0294ab6502b21c6c8811bdb507a648d3e691cf851b5a3e3a83c2584032333d3a95fe40d6c599239ed836ee054ccf27b450c53b10d8c46cfa4bb535e8e1f1bf8f4c9a37e1b9244afd7c0ad5e4e509e1af8b48dbdbb9252779706ae425c2de88b896b8d06fef365c59c381e72aacfc086dc78d994a87863b51958fd98eb4fbf5344be0c9c9b4e4fe6fd92813753e331df6fde7d770c8ce7a9241649589eec8a81c99fe65a22dd30f07e2295debed62475c1c07632f97f7fba5d93fb052e6e9eaea02c45af5c2f70be25bf53ba0ddd97ed026fa6f7d2225ab0bb2c17b8b4e69daa73251d25bb0526b59b97855467fead16b8cccc9723699294b4cd02af31fa885930192fb558e073e8eb9ca87fbafd15b8ac29079dc5ad2e7b2b30229e43b036e9fdf12af0df1dec6490ac224a546033849482f9a46c6a4a3d8d9b72ab921478d39bb3461b09ea465160db4fd53ed8a649395060b37c54f0c8392ba69fc089144aec4bc809ecba68f8abfdff649bc09d90e857f1e4df4799c0a88adfbde5934a2f4be0fc42e5de88d257224a606cd773b0d34147962481cb4f2aa2aa4dc5844860f4a5dff020fcb67447e0f45a963693a4533d3502e7172632fb7cb3e6b4084cd0542d2aaffba54d89f07b4abca0623f0436598aa2eb34a488bb10b8917ac1f4aa53043d089c85a85dc27320b0eb5b17cd6bb3b9e807ecaaa94b0cdeff29f201df22a3c9c8d9267fa807bc5e92d57f5726f38707ac8a4e9a4209ff08393be0f488a42dd557c8bf75c004616aa7195295da39e0324b1635ca2cb969098003d6c6a4e8e9ec66eb12e0066c90b4b6b6173b882c016cc056b0983ea5a8854509500376cc2f5945ca909f24000df824c382e6537b92ca59709ddfa53b424a7e1759b076a6b641c60c522bb160537373b5a5dab44ac28271ef60b28469935992af602bfa75bafd75c82d5dc19767325532877c3995ade064a41cfa3cd5534a252bd82832e56e92af828ff77f27732d784852056726fdad3e948e3f4a05639bf756648aa71ea182cde5162258c58b203a059f47f209a5644a326f53b057693de7c71cdee952b041e5181e942865272505ab19644753fb9c951c05ab9e7b629be40e188028f813e969292d9a90985070f9b369b3112aba33a0605df3080de9ad82b27c820d9ea2acbcc4ed2a9ee044e3998a1576824ba52767df585fae71823b4def29a538b6fb26386df17b4f838d95ca6882179d420a3287ce142d93894e49ddf7cd31c18810fd4bfee8a5892ec14634f330f75882d5887942eeb7128c58048fda796b2f4b09d64b42889663b77b26c1f6ada5e7ae24f5488251da368904275cafa4e45ff59c16487035bad56b928f607f37a7183db9a4be388213b2bbc472d48c498de0f2ef5a2df3525c0923389d2ac4f4b499b4bd4570a579d3798cd1f46e8ae0bf2fc6cfd024a94f04134f5d4cc92cf305218249e199cba2e6113987e0437f4fcadbcf905531047f52624816c5358d5e08c69274fdcf55999a44083e95a70f593364af67105ce6da82e0b296ce9cd492403021e8da8b360920d820f9435dfa482195e40fac961a651994702d11e2076e74aa3269e5153b47fac08a12cacf54be897d113e70faea6a748708d1f13d70da293394e4e49a263d70d9572b4d06397ea53cf0ba397a7b42dea04a78e0255be5a6e5fd7fe80e4c7c5f4d13f3b8396407fef692a759882d1eaa03ab55c2237be8c046fdfb1c648890b4670e5cfaece6e37127e7550e5c921d446853d58b6b1cb8c929489cb89722513870912708cd19a2e4ade01bf894e9bfb265ee0626a8a50b12b462d0126c03972a752911dcad830ed9c0af7f3c0f31e5f59ce11ab8ec987b29c343c8ba1a38fdb739bf47358ba932810fd020c100d2c09659ba9037d9b77fd0c0a88c1c84ec4dc163e70c6cbacba944e47676d70c4cfcfc94acc192d4b60c5cbccdf849cc92a62819d8bdec185449ddbb730c6c4a691e1273c8184d31704a2b881034ab4ac78481ddea949426ffd5b380810b1e3d8bc48e6842f805be4b822a617b810d6d3a5a43e78917ec029bd449cd1e6348ae8f0b6cc564a62fb40546a50b6943c94f0b40184016d858395c6428a1165387055eaf45555cd7d45777052697c7ee4f1983bb6f0546a89b8a14325ee8be0a5c341d4beaca2df26e546034d9d88754973aee53e0b54308b9a69b774752e03c6ccbe2e6f5e09e28f0d1ca74a6fa483a7aa0603372004fe0b3ed56d079438a41ea047e937abcba4e96f45e9ac04f2e254547caa55e6502e7d9efd746dbac93b6047e4350358f9272168b2981c9196a2f51f34a12b124dc5929a8a44c6848602c990613f61ad2373b029fafd6f92a42cc296404f63b6e124ae494497e8ac05f87b2e021a6a63d4418c0106c86090620044647b030192369d6741058fb1e151d2d6e9206022f4a74fc983b6e08fb078c48e2f53d9a6f93d6075cfe911ab3d268d5b607bc9a0c5db595079caa0afe41f6968cef809131bcdd2c7e854cea80ed986e25541c5d269303de2f8ffe18623a6d161cb039fde37a5ad0a32637e0f7b73f834a9d43fa6cc0c9f7cd394ac6b425ab01b79b7a5574a6d8e901d08011ebb4ff2131df320b269d5abf0c9a6a4177a11690210b26554822a5309142751f0bb6ededaa72c5b0711d169cdf7755f06841a8ca79057ba2927e4ac2e30a3e7de6f2fe2b916de956b063aa6962bcb4b7cab082f532939e2d5d7bcaaf82932cc1335a44ffdb8a2a028daea8b4b065e2481c0e0643c150300c86e193d305f31308001838268e4582c188a0c9c27c140005462c344e3c2c121c2222101a87832261181c08048461302818080302c140201c0c0fcb0a3d0f70ba8f5f491016d864f976f89f9f6cc6117429941c168278c0b9b07d0815e404de0c45821520e670e1e5139aceb8b6510afd64fcfb39e4ce9fbb2013ec291e18f7c24987cb2b154ec964de2daae97b54239ed0ad7351dc81fbde55ba856e92bb72ffbb4b17c79a275646910142c82f341eaa41b97b68e27ac682828f1c8ed83a29f60620933f7c98077ff8c5c23660953125fc191e84fbe020fecf5a5be93f0b2486d02df86a78cc77b6b8e483bdd94b2bd69876c18ea9c3e9b8bfba31ecae906f3034c80c7a060782f402fb1d7c662be5a09df06fa80eac0bcd3aae9906d46562c1d45d0c3f2365681b6c0181b3efa37c48116e0115b0e65eb8022fc6863fe4a0d763f8b5de50f777e8caca8096f7719c8de7cc137b751e9237f21e3cb55f4d48255409658606a28d3c6a5412880d5143821c3e4bbafbc486aec342c3dd306a78126e0943c014bfdf2fd9ffbc768945ee80761076b837d7b9864e17dc05fc42e2a17a082bb419ea09417942cfc88b78ac3db41025e402cd790bef83b7f570dd4f45ca0b35c1c20e81b9503d647a987a15f4198c099742e804a3b7063ce5b68c772b397239073d8303417a81d3ce2f0b9484a6c08f50cbb5f9557c4a2e5edb70cee08a250e1e1d374c4933347bc6402890220472ac522af9fdd060527b71f1474ca16e3143847d0023a34a28333400f989c245b4c8f72f13320909325cc1b101a820efbc43c2d5a4655adf62fdfc53d0f5b676af1c79cfd00bfe584299a141e82fa41eaa87cc1aeccf08436b9da1e03067d8273c02a7287f9a631cf4bf3f725e3cf5cfc5a076eaac93f035292c16b610ca441f2f119c84a661ab00cc102774121209354308a15dc876c2bf01dc48c5f7512f285ee09a81af08882f2eb120a570cf3f5ac4e9a94703e96d4c5ff2c32cec270550797e4294904b8893a0065426c1af2b1c83ec1fb8460349ff27e0a12b72a9f95304a5fbc2d961f4a20629415cc068e654e214fdb546c9c891cee1cca69d54c4a07118669813ce09e1de0915941c0ce00fc382eba0d3a06cb01a44014e82766a5c03693bfcd7112c0272d12f62eea86ff4ba7b6fbec6b15e309a31356a6562f1ae8badfb56acadcd8aacd5cc747f2f97081490c712fa1c5f8a39ee7d7220591a2265feb4b715eb5f0e2ab1021add3c48f29fa4b4f451ff77bb3c8e8c08701c73b9772e720a97b8b690712a86b69c3875324825807cf72b681be8888ad73b25e053ec152aa72a4f20447dd9e25f5c4f910f5f588d9d391373a491bd0f4807514725378a640fb1cd88b9917cf0329a302a1ba8b535dba013232201cf5a5b87e2974edb1eed80a2aad8f699e8aee1773c94cf5df2063acc00c022b9b900db54fb2fb96be32f8bc82d66c77e572182a8c777095db6e84879f0c1c4b91fe6443a4ad5e4722d66fce13534d8d5ca091de539a65b59985e316c2a8cae81fbcd2ab326f80100310aafd155cb3bdcb99ad4ed9a9cc445b593f849c592be9de4ad3ae91a3ce030e74ad4a5e55c39d6b5faf9597614b79322e58f91828ee657da92d9531ffd93648bbd4a812cbfe527ba142e6b4ed819d968642400a1d9e8150b031e7653e983d2209e94f8ca91f889e33ff6f624ae3989c830eeed45ef2dba9d90422194841513e6a2d06942a515724111cab1d812ff6057560d02cf3f4a90b1ec6bb134bf5bd0732520061c29e3113b1bc40f61607a7a08c1848c4aa153b27c708c8aba0d9dbc9f713e81d7ca9032d8af3396d032ac59ec61a1340280c58d192117ea89176d1b7b498168928e093ce7eba743e3ddbadb95acadfb5ed98da9af6ef7a3db279ce88a6a7e6af10cc06cdd5f2c32456c144ece54c0cc9190028d80926471a24623796b91389ed04f6c6f566d0e16ddcc8959a5fe8e509032ed539b38d83c9c803d6dd24db586f08fa8f2eafdacc79eae9d06860c42e295e0d05bae40b3e46b314daf094539bf80180c0ba573ba1835ce2964cac961a85e3c580c5feda6dbcaa94b5ef2850c357302fc11dcffec3860800fca504e407b72fd923b7cb1da25148ac503fc90d4b3b4e0464ff9ea0dcfd58dce9ee043c6f49b1ee0c1e9239c8c5a547805f3d4504601493944bcf9307907d60182d40802b9a1feb9638a14165650c081d046848e0811270465a18a423908f9115236217d0a9cde4f16ff327c68710f33dcbbd31c94af58edd4686cefffe637aefe4b6a405483c7499cc2e07035820a26473d083cee6b561c953d1a211529e8d3ebe7407ad73fd316328e48918d144de870ac3221ba37b63165834b3125ffeef762e3434340140c7273b808311b62dd835191475fcf0f3aa7c5aef663dbf6b955b3308e298e85e3acea2ec7bd46a2f949d805b6492bfc63818d05a83342614185ae81061a9a299a2851a5aa490088d80cae64deaffe05217fe3b47893f8664bb16c2835e803e8e9d1f3927aa60a5aae012d3c5751a50295019481192141219ca4ae09b6469bcb85f67ade63210000e10b4c56ad48c66aec4a5b1575b37196a23dceccd8811d41571ec328ae81eca57a7dabe1a4f635b5a10905ac8b32611af6ec7c027d574a090559633b95de2ece656234bf422f2f9621e535a6e3940b322791f5f99c4ada6ea2e1ec3e5b4e715a660a646a68faf9cdb92f5b36dd7d602bcbe6cf4390bd87626a1618c7d9751a3f40a926835c23d525905d5d5be3b9ad2c6d8d02256d755f6692f0f710c054064598e0df5d47e94192eda34b7c1603c8732d6deeed3e9efc0ffbaea61e78ecfd22a2214e25ce8150096d90ca5296849ab31629926554339c101dd6f88160be726314416170260887ee0f4834dd18684c104d2fdaaa53e351d64c034a730af7c3d3c8ba64a11d6c121aaab4919fa90994ae1d7443dc4b69dbd7daa9054592532b53c10e36207650e0630c1776d9fde6ed60a97a248e40f4a42ee555341d860e64a3d13955da00c5b9eb23d732a908474ad49d80f2b0d767f813add37042b30ce9dcf8eaac7d7f82f1100cbc123cc06206fd97e78d155decae172ae58a8f6decc3103587d4d0cdbc87722ad7f9129bb66ba0954f4649f4e224c362b9e31fa9f88538d8c39d3051d2b68475da531fc0094ebd60c52ac2e38170526fc4464d64cd387fcbd677f3ad0cf71031c179b0a5a1ca049b86bdfc1b55b8d926340e1e42c7e6d3f9c345f84ea7e75d7f9247f8e116d9099db5cb9d53ad727af78fe0347643149152c5e665b8b402a76cb87ece60107201bcccfea7e5df35d6ae7f075186a3531c667295af6ed3fad1e528325c56f5dab99cbb470d78c502bc7c43a63199b0b8c573ba3a3b6669dd25074bfd4e3e6b6241135cf710f0a496b9d9fa1f89d68504cf65da483262183aa10a9080a7f95c62ab7743b55754495ca2361141f35b27d13b898d4fe498819789574301584fff2be37d434f7dbe30ab11d8f603c4648e2a1c5e36ba86d0252a504ce2408cba2e9338bf0791b6d2c2a9af4da5d051c906d5b2f9e86cfe91abdb7ff281adf8f188dc48c15624d70f908f33d9d7e288797b41b5846f0dafa65b4bc8d81653346ebfa19f3cfec6e3c743c8306255eb6b4addecd55feb25392f55597f20db544e1dfd3260d71cb5d44563c657a365e580bf8df1f9fb3383ff4d1923012272d67427be0d275922c2da74581c7ca846600397a2e58ac1548343cb0b75d07a946ce2ca9f2cd5fcb7d3c642b1bbc4a9890a6e16093d3cc641443b7464584612baf3bc824341458ad8e02dab48a833d4b6bc32822a4c17e03ed5c4b0fc9cbb95968acadf06880ac2c6b14492faefa1769b704c89b202b27f93d3d2c675951c6f32f07d9f96718d55285f25be7973aa2adc73aa46587cd51353b1f17b3f2f5279aa86210b5a9c3b18bd4fc878020f0d2ded14f6011188453b857bff2161ba5a6c73040350dcac56f6a0611917619bd9ad03a4ab53ac8e737b327a2fcb86e38ada87f115058f64b4fcf2798fc10e690bdb636c2adcf0355b28b9b09110c19455d8aa6d7121b28d76797d2cc6ea797dab4b87c376127b7167d51d2dea67d7f288b4d10442a9a817140106ae9e3ad25041c94e40cabd467172548b8e5190b67346aaae4ab45253f2aa68cf3104d3982c2912e086d4681bc55984be9b8d2e578e69a8b2364dc8c56a038cc7d7783b5f67493afa521eaea6092d4b764854f9946100ddf67b775b42cc84245dfd1152893d2d11a3a28c42d52a72fcb1a833654a8a548d5854e725d4c09611b9f77b0356fbf4cd4aabe061134a0247667b231855219b21d394e66a3a8fc3b8d330aa1c60fab3379f1a93d3c2323c2369da8493e746af60547391ad642ca084c4cf179cadf35ea3495c2694ddb220395bfda006f6e31119c7e02d0d66cada4dade822b09ee8601c3a8d0867b09b7a3155749df05fa874d12ce557e3c9f9fb2518c31a1e86686e597e20a72b5a3931883fe62671244b3ebba5f291dbefb72fc9190ccb37016c40dcb635ceb0587a3758a6a3cd6543d5182f1982f0347fc3da6c69bde2b636f27cea1a4fe6d8cca77e0a54e961bb4a01164223862dd4181010e353d3ac76c414c3d2ba574f8a12e4d0da148bc0fbdf07f41f85b2cb699ea24c5ce5e8e9d0ec10cdcefae347e3c4f2dc7e1d36e182f5c3539a68c01c94a2d2616c626236b221ff9280f9840b299b1dbddfce77080029fccecdc0ca27826ecf4a2d2463ba44f33e3ab6069f4420ef133784a51e4c966cc107274824dd62d0caccb80b936b01dc483035a216c6c4a3c88274d5a2179f93c3c06b42c1a44249a90c60de23bc4314f3b316a0b0fa55568f668a8aecf609d501e29e9277aec0df93c809f9638c40d766a7efa69528bc9d140d52a9b453c939f6571c095113098ac0f2001c41968da1529510d14f915c25bb1a416e5de4fc818d5769f94a55746ce3b499e2199309f965543ea3a25cc87d35d8aa2e8528e878f0ab7bf9eb488947eb87551dee5515f45554a98d2edca78186a4127ff35ffe5b4650e75d940c2efa3f9da2572afac1bd884f1a87a6ca08cef4d44a065392a0264612ff45c229b3818c49a86d28f9ed3acc37400a2aa010450f186d58b103029dfc21d456be086c0f4d8aa5c8f75d79209c419c3a92dba7af37604205779f82885c043791c7ab58cfc4e5686fe831e62cc821d84a7a992d3dfd1f5528c0be028cc66a8d13b58b59a959639e428cf79b244e13b0143ca6c568e55000a3eab6220ae32552799f898ba569dafbfca371507e0799f2a873f1b8590dbfe9104d72f481b53e1264eaf7d617691560a823feb120806f0c38bd6f81d6b029e9fb0fcf1be4c6e8cb494f8ee4894ae4de48857a17a64ef09a06160b6a3cfd373f8b0564f6e364625a9f698e29c4770d7e58aca888c1d351af4496ecfb33ce83a385656ab9cfd8250d6171474f91b92a3a9f57312493ee2f895f6d2b591cb849ca9c4ddd2ea73e29dfc7a3cd7e70a9b08a601ca7f4efc11b1ee83965cfd819e3323459c45ee23bfb0914bf361928412b0956a130d3faff8a0a135eb985c1f0c2847f1a0dc1160072b506076f8c347a7381852e0083ae2989217dc6b735946d5e6bc029041e780d9d72b757e506cd4e08f5c7640c65c997ff331ca9a0585f63b76c38715f5dd4f15c82ad2e68e1e450d5ce7ac2df7d175fc65a0158dcd02dff4565d8ac24ceeaf885458782c7cdbe78407d742c099b77d4c3a3405b58223959807db53f9a3e62d9c1ecbe31fad605cfdd4778bddf4a4a8cc8eb5b4bfcd18880d8407bc56ed43367b6e37332ec8b9b293d5abd92aed8ecb341c526874dfb6d3ada44b409caa6924df96d72daa46893cfa61d9b3cec66cc096d72e42e2721e7ccc2ab7d88e336333af546f03374c357f152153c391abc353c518c94055d0fa1c072bc965cefc95e1c6471578def335f1a371d63f62217f7423ffa45e36d678c97c265598ee00701a19636308103c6bfb6bd220e5d7b71bcf61d3e83f1efc7cf9169ba9330c7a6330cfdc88404af186a05be6e90e38524a116786b4585acbd4026c64ccd092acdcad81640469df7fb0c38d690a107e1cf7a305e41fbfda779e4aa5a3cf11d3f0cf208d4a2d4676e2d123be85a9b62dfc6205ee359cc382ab5dde4ed6dc08fd39666181df734747610c207b2d71158fe8e75b64ac8de9528ef107d27ee5bdb83d88d6c328de948c0d92b9949b6c8e4b0ddd6662041da419b333240515dd66718226336a9728fb205414515bacda8e0e8888833465108b42a7da409b4faa1f4da78b611c31ea4d42fcb76736363e651c0ec1a097d47561b581ac3f580378ac39db31f9eb50cc7978e8a95c76dc702a4f83032a80f3721119e1a5c15aaac7379272073e52cc7079aa5638e3b0eb673c46c6031a58323be060cc195b388f0e45b1afc45e0df96f3ea6e2a955d97a47343e62ad193f6391301541db3ba6301b3faa16466351582d9444e695a5d178ce842a6bd6f7a92507c2f9243a3425df218082be8ec18f288384d2e073ecb93a87212bcacc7f4bd470f2ff7a4aa70597f52b4812cdb4ce79298246b2a6ac9b1114db1674b65f44012c6e204b51476eeb1879683190667d6e73a00158102033024595516da4def680cc36dda08f18113e6abf6124ca84915a7a810125d4bd0b78316ce44c95200b0f154699820411dcaf534c49b214ba6a218b991be40ab3b5e383db51972fa382deb818902d46f16ccaa034dbd26601fc97e33b5b536570db41eb00dea66509be6dbf65fddccb36194bf20b4cc371e0a080969f0fd65060bf0e64ffa3928e36ef5fd246cbce35539384836efd3d5ca23c473ebcdae16a36878f3cb06abe378975d565e0390c76bb50065d890dbd386d07a044f6354f9272812b4eca08bd9365b47b1be49de0e3b3886f16cf314641ffc15fc508f46efa923a094f3a8bc375bef412450298879b4d748677208e45769e5c8992e75a1f5642b2aad4d544bf02a72efd5d55e7ddd022f8b0909962a9b16600607cb0edfdbaa4b005b6a2468d51549d2cc3ae5764747baf8fa223eefb5e73fb886bb6b4137f7368b34a78fd16ac34eea55d574c339c0b9b48e37acb522f50dd37859a2573afffdd9664864ad496e8bdba7da1b36bfc3b626c5d212210360872760727ef30da783335fe8b1737c41017bb00ee45401ca59b5cc4eb9e92153326314b4469b3556eab840fd0c21e8ed94afc1585c2b84d973d007ac707ccfa198b0e02a4f1858a10a54a08fe0c67c9d61c280cb6a0b72649f15b6ba258e1b2ca093042dea9c5f46394541633b2ec86a17014d230bc6ecd105b753cbb8cbee3c07b59bb7c800fc0ebaa46f9dc0335ab5c13546de116806b87a2bddac8e24c609c0b6f5f9304197a0db82d6a3a4eb675212685b89d1a64972b656dc9c6b691f30713708628147ecddefc1a18aa860a8adae579c01463f9a362976d069aba9ef7580866639c7481b9ca9933445abfcdc445678839157645b6f9949b0dbedf07447f3b78b9352915dcbdf74ad882ef2c6054bf4d017e90383a1817fe7c9c5bbeb3c7a1976b22874570436d64332500b67a511d6308ccf9f1230493dfd1cd4cd03d3a6cc3ac93e14145b7e368d87ad4fa8b21436071b910215b571658a16647bc7b64e2758ac084821ed9f00122b45dcf4bac0039020299be99987d90443e68be7def628975479732951dcc4ad6b315e19519b3f7eeb329adb56f0af64168e669a8d7ffbf51d49a5e20c3a1abd676e73ea6c0ea9ac04f7cbe3efbc8007f219f69d05deb114d617d0ad815833ba716b12c16467222189bd1102ff13ffc759ead670b5b18868dbdac8a85750fde785e1a23def49794d5621f06f9826b78c9de596cacbffa02fc41a3e0797a160bcac175a710e61ef9e5fa8e3df44590bb2951fe048140cc9afc38a1db64005f3c8bf8006694eb8abaaea883a72ec2b002eb82d5d394c87e29754bd29403913b45c39169d5817400a1f924614cf8573904e5185fb575d2f4ced6d452fb7e8d5086676cb48efac17063a740666f0a2993ae9a816535792355a989eeb3265a1c62dc22de1a4a7648672edff76dac6724b005471ab3f62cb282f46b975eb4097ec61a16b15491a77c17fbd01163f5f219c1df6b953e4ea807edfb3813c1fb4ce69caaebd4693e26089392e6ba5b6ce4c8173220bdb8762ca36d53170f2f1ebc4cea5e71a82a1ae95f4ad34e81d1b01d1484fc24f4e9df436d0c73a204297ec1f716922e77e7d8769c61e94cf51c2ec5da082290d98c83304fb0060bbf7b33abc96cab0ab3117b1bf6b125671e7bf5a6bdcc532752be94c66d0c0090750151f90bdeb24ee63c26f540fe6020cdfc12fe2f9eae2af22a68d70b15ebdc4fe626a6a0de792d8a375e826f45a848181888b855b1b73b213ee52e0843550179e1c0a68c6b44b0ff3c07f309406e75fbe33d904d2c3c940c24281a6a46aced01dff8ff905128a46165688380e0c2d209a4a109b1bda1614a5fe380d4101eaa14780a43602c14358b6567ad16b58007a1a23d433124b0d888c6ca6fb8fb083f403010f3911c06a034c033b82162f797a4f99b7ea3b6a41c00053b4df0b803d0d99132ca704882dd4d20ee014e0de7e708f83e974657f0f6da0f8c8adc6455792fd900e69ecb115f9185c626d200a45851c6ee269deef68858dc0f5309ca795fd5806d6aa533e1d03383b27e1d6b300040f2be852349237d1ac4fe182cd00eb1a5c73155c35ee949e4846f8d95a89c7d556d082427b686b4fa1e5ee01da1a961ea52b7588836b10e6101145db80d327d0584393592bff00c37d0c7b0f6cd9805d1ce70bbdaaaf68c1753477dc7a175ae0a0254471ea334755e8f07104d2733614802d63b2772c7727e22a3d2e369b53f5a8eaff865a68f90844a65f474e0ffed6b9848286a93331f8b57a515b1aac94f4b1c4b75e99a7c5140efdfb2e82d2a4a08adc40b01779ccdedda1a5fe6a4727829a79a5d082742a0771ec88b82813559635d0e758ffe82c80991473f724bc61ab29665c8480e53e4d4bb24ec1da62984b4b7799a3c10a2c9bab4d2be89bd0b971d8af469aacd0673280eab61fef85d8119337f68795f02571822c5606e764a197534e24140636ee8299a2c66b4bc1f019f9aef0a91619cf8258f972182f4f0032c9dbf71834c41905879c655c06dbdc0b4f166f62b38f711c86952943947da12603096f5c51573db48947358515143519087ba37c1c07d6be56312e29414379b8c4e1882f82fc2da19e509e400167febf68daf17409795f045f1b7aa9a09a5c25f7f21e0074921f1fd0d0f7a35dcb91a0e1640aac10c004c131175f4e93fe877897378ee476b417b1d9a682edb8b454f803d681e7df350bf4bd9e082b48ad60e02a032df1586a11a4b4916e19803ac6a0e9b2f2ec24a1e0010f0b3e12d8cc7a97ac360c586bfda0b4487e1ac1f08601acffbc4f291016d0f510368a51eba8b8574ec2184e4eec92c311c486bc1188fce8027434708a46c2c3f21e23c47e5d5a49e9c20448ffe258c98258ca7895d89905580a01ab089161b1942121c3d2d154492e6500c30032b7f1f0734b2f007f97e089e18f657d045c830b8c63b159a62c0e2360a3081bf0668c8ad6004dd9ace0c15601571b14e954c64e65133f07cc87497d7548b2f2f0e484e2e7f795cd067c464686548668f8d81723b8427d8cf85d74ade7e3a1a36cc6d140f73cfba4fa88c2c7df0548762eca7ec398889d69e9440bfd278419e7bf81350c4ccf000f2e83a5e069e2839a6fae4f8faf1f1f24be217e5438456928a1835a9f5d02df7973421512e1c3a6f7712b583ebaf2c02764d7faab2971645037b17731bb2f3d408e4cbe348a730714c7942f01ea9e8187ac4b320e20c24199ed9b549900952562fbd7434579645c1788b96ee5ed4be32339a3135f52b209c90ec0fff9e1e26a908642c74a17b79081190970517153859d2029c148c457c04fe99d3dd1bac1961faf4316f2f30c221a26846cd1b0486802e63df3143f6144555c09349abe3b8521638cc37993c48555ea5f60de1f857bc366d711f17f6890ca5a22fd6ac4f332cbb5ff5ca6a9c1e77c98c06741a34499f44d8c6ae074715a0909283a28637c1f24f5e7381a1293fa8e753d36ed198e9ea70023c3cb8be0e7f7046382b1d38d61b4bbade954f907d3c420066a9cc37c5906bc702693d6f7e9d0666e00cf7d66aaa58d5097c1b63b20e192e11214076b048b7b23c765f3b379a00b0471a5a260aab879057cd630cf0acdaac222a53a9866da61c97841ea67050addf463d95efb1b46b06a21c717624864a23aa2c512c1087530c60549623a53df694afa694df24e9ad93fb6a2b5637a187d9bea30ea90cc0a245215d26bf8cce53497bd00441e1970918924562eaf68b547b0ab63d760205927ba5451acd96d80c51edfc163247c3f0d7fe939c793bcc06601dfa1ddb2274448c3a6279076df8b779753685122d2794a99a2598b14bf2c65b7a1645aaa157a8d002eb3304a59e0412cf8d3f8a08c283ffd9ed82409897e26f5c4828772659ab347e22d7e921a3237b51b16ea134909f332474f7cbb3658a9f57bd4f5e45812aa9767b7683102993db0616192740fab0809566d6fbeec361733b792e475d96b7b26bd2553d603da20f78400fcdb5965fd0505473174ef239cf39407c7fabae2f6662ac2e3ed5f07184584dd223bc8fe908a4ef10182ed367f32da45153e78851d45175cd954598b5263540bc9bc4dd2c14ff169b1d5aab148f0d0777c1c321776f0d5dea17437a111371e9e61bad0aac889c7a93f09693fb519c96b452a7e486fe9184ea4e906a35619ac5fa45ec82e07870f7090cc049ecc624f37516cd9107920635d45641b65b45b4a1ef581807774a49f31104e6971d4c71413156b29b14f39d5fac2180d2e6de38004296f63410232d95ca1c11dc9f5b546423f6e9dd8c6e4bb659b254bcc2482ad9328d1c44384237390af69c2b65413d703c9c9e78d588adc82415b55933a0d42d1dc4e4232039f2d369bb16ab214b2869ef1ff231d7f26ce8456fde1e559af494db7d3df30e3beeca0eee54c180e6c9397894c6afe93330ba25821e23aa9edb29aa939cf10eca3754da50d4c6038edb04adbc05d7638680318f5183cbed609baaf7e3c5bed654fb335ecfd76482a56b98005c10206fd665ab4407a525087a29b3d42e122a45c9e3f743ba6ceedc466a3e86305a39808ca56c04cae69a7a205ee907b72092d4fd889c33e105ed56e485e14bea958ba6f6cecc7cee673cbc84bccddc662c2bdd334752d9d5b1f0ecd19f8e3a01e69f44a27a7ea05e16ca234905d243840303498212a83f9615a984abb1309bc07eb64ca98943818b9de52ecdba481e46d4b64f61d471af7e0c7b0e054be389a8b1b7635c284000085d81684e831b977f0a18532dec98d1883d7561293bb43949408d057815d420fa1462d3b16f417dbd0d94474016c3187d406a14dd9cf2700f21ce0c4dc7d44e8ba2d66c911484b059e29392c845c795f5e319ae10bd6fe2815d6f15787f08118fd6047ce993441f85648e3e44fb802848429e33eb08096d46aaab8807f48ef8e3f79d602445b4c5816b9942fd7075b7336709e2232cda8415e9e224dd891441bf4849f9678362744983b0e2348669bda4206986721d6649afb7817dcc1f6a50f7a411a2241984425887dcc03b83ae5f7ee320f4844dbab088553f4058b2771226037ce7faf4073f0f6800ef4a8d69f73256cb707b152bba4e470d57ce9c142d9f288fecb1e41418f2ddcd5e9e5e4d3bab4a23833d5086bcb4d97e6b549e465cf603643e3a523b48e83b7ea2f3a2d1fa015f2779fe271c6e7efd34ff100048dd842571ab924add6256e142d5ff9f192349151ad215129946e8ac62263165f27cb58d0fce9771b92fac71305d48e56a627c91dd530e6dd2d8dcd4c2a8ce0ffdb1038e75d33e7cfaf85ee3fe31e21ccd5789d53c9c9c8ac0e58456e81ef08203088b04656a2f6c1772158a21602fb5ea06f5b9a85e3d033d9e616343ce928487d5fbb87aacebc0323bcac3b41faadb610aec92a3777686a4f7e9b72d358e7c388055975afdf31f59935d8f17d9019d41cd52a4da3755bcd07dee3829ad370808726e96a7689b34f59f2b93b0331bb068990770b5b7a74abcf79241788ae04de0a3b1225763ceb094535841f5e22a9f26efe845601e7f0fedf8c0fc8c4dcaba9c98a06b8369132df4254de30e5e641ba45ea43acf7c410001e92a8dd1017c4ce3bc09fc4f7ac11769b14e0a86626c7106ec6c498ea7bf3e975eb3d090bea00ea65dd7c91808b0af15221fe364a398b81bc02c4fa6c5976088717eb70603163870be2e2010359a1b18472194128c47782a80255c8b1eb3486a8122aa1a2c80a870b1d24842ed7367245a1177a0a670dc2e9195354056fce67b608ac6dcd930e007c873bd9a3b950b9cb0b634b7fd8ebf2027fbb1e8eece5b82580edc5fe1fb5be43fad4d1c4c235f6b8376124423c2b2ada5cbc80b0a23a3f571d6e9f1dc132c3a6f0c5bf03c5be074ca18c266c9ebcc9da170bbcdbeab38ae09d061c7060f1886d5c6fd292ab2ff6b0724683b17211393e8832ba08d6544a4094ddabffd05cae4206813246f5c6c20d134680dd3a46285810fde053033d8eec23708a709b17218338e15af105d5dc938c2d4c52bcdd903815630bd56ab09e79a1c0b437343d4dab09e282a56f8ca297a8fb409e3eb22c561ca4b9a30e5aa490f90811c5b2718c290d9b4f040df8a3d01e70d4d8a0581103cb713db0d670efbe41ae8dacb2408d2d1987938651a2c2e850f523a642df26a789abe881ae21d07fb17059c355cb0a945840866309e783c5b7590829ebb1363f7d38d0cee8f05c27e102e268d47c7b52f737e0a8f942846c1f1abc1a9ef4a099dc2893f1ca01a0458735765c8c3f08970e59a4a33c3f41b870d23d9d772956e00aac8276152022d503d578927b9a4d0618425d3de94bb6ff87601e68630b21ac07eef95c708b0ce07d90a80dfca2ddf3c7213140cc1568a30596d1bb093fc64f85ee6e68ea7292d435e11ee041e13407c11015a6a07386ff668ef2bc26a7af9467f672845016982dbf37d55f368487ec60853ae288ec3037bbde789fd0102a4b25d120210fee66d60370fa2c5cb6b03832094ca8b7c5db58695a0f55a0e8b80dd9cf61ad05abe543af922d094e8f9bbace9a66b18e8185a2717ba8f94cb303dced1e55cd0ce2740a822a44a71fb5625ed980872cd5af1f126f16f7dee4d591b142ecfc77ee26d8f89b7aa5eb9794dc1b98c7e3e473a04ca094ddc8f92abf9147b9dfc9939a32441ddb816c551eff16163beb5ac283990d0eaede71a5b27803c4e21a3fa87c633242ec80af7d21883296180f29751338d08a21072cf812ad73e699ec72b1f5d7e2c62d44ebd65a7540a0da1c514da27e5ac6d3cf13ff5499c161d86876f49659e7c90043b2324730db637430337939a52f5fc28fa2b870066e3b1053fcc6b80dea7b5a179233b810443514a2bf0b5eeb1f533ce1c9c3397cee5b0f5a3cde27d6db953bdf87a7c99d10d165ebe381fe6829f61f2c60890eb7956218a0d0d6922033f802638d5076bb9489a376b6aa8ac67f25c4280922dcad9819583756eb7714b40b86bb8adfc21bc4268a4f8a1cdf12708080b7b98456eea30a55a6ff8bc5097b9e586382c47db412e438de5bfad90266ee2639c3dc2bedb3a94f9a84dc0b8859014486015382105ae040fc2514ec7802fe4044df0a3760b46439330f0f0f0f0f0f0f0f6f706ba4b6d6368024a49424a9adf153f8ee9029a594648a8433d417ce4cdc86362184d1863fda080d02010bf40ac50ac9f499f2b5ab325cc1873eb19c2fda07c868051b529a92123cf3a59820b00219ac6064bd6b472a993a476d6ce001362820011b5bd880c0183256c1a43e4f3166e855c1c61acb9f53da53c19f680d31c8fc7ef9265430f92ce750ff21afa77b0a268e8a96c73bd8b6ada6e07a77f46be99c25eb2d059362481a6aea99de1e29b8ed9027e4cadd28f8205a9265d09e29658b28f88faf7fb6d9d3a67c2818d3f8666a440c146ceee8b952b0de0b1df904bf25748af61bd2a8c83dc1260b59c53f923e25f24ef0f9647a8db69fa4fecd093ed8a866ad64294a3a6d825162c9dfe228799a9334c197902c5a24277bf55226b88a215a44537f25e208136ce4d0f71f73e275e55c82495a21e85e907973722cc1badec678997542094d25d81d913975ca5ea276a504b739a9c99e96d6f58293e034b7492b957eefc59304a373d25ff59e48f06d25421242de9f881448705192529f839074df23189521287d9e2a6d8a992378492a85a44935b6ed8d606c5c536806d5604284119c34d3bd2d79a47fde8be0840a26b2e45c5322a22a824f1354aede4ba52e272611ec560a22654919e97b47049b738a0abefb15aed14370c13268bbff6032a225c3109c14d1993d665536e11582cfcb9f9d2ea7641082dd2cfa73cca064705707c155f610e99a5434b5a80c41302a7b6be39f84a4ab77b1c3e8588055faa2053bee003202c1081193fbf9aa3db72203106cfa9317539ac4913193f107ee4a4726c30f4c12eb517a55374ef4f481534ae8535dcf49883ac4088380a12351061fbe14f5141a5e3a4765ece1f275d3b496255af0764d20430f2729fa2495b6cf6a870e301cf0001c376e84c1011d1798808dcb01cc41461e4e061e4c9b335a1aef0e8c48cbaf1284251d499b63c781c0160c68c08d1ba87164f941861d72edba183a090bfabd066edcd8a1038c3176dcb8b163c78d1b3a5c0c2efc04376e7419c3860d6263d84036c6172d6807e0d0e16274c18518a70b07dcb8f1858b21c6e9e24b11400c32eac06fd22554acab15931ecf72d482430162635879e15e14d70146ea800c3a3041dae44f8d7539863e073ed6c64b6152f3c80fc981f59c444c1b458e4c37f9e313e8a28b311c1007d6439d44cf312c37e370e0369ed2759d296508a63770c2bf4f3cb6ae471cc97003b7f77f9db263ee48a78c36b0c17c63f9a70f2d1dae2081a12090c1067662b59f8c41d449f30332d6c026e9d95c443019fd35db21430d69cdc135a5e995430b87abe0ec28c5011969606c4dfb6e64730c32d0c05d4a2633a325b520e30c8c9af2717dcde61b649881cb9644fd5f1acbc0c9143f5bfecccb5d2237c820039beede4698f24befa731f039a434e919ba83369535c81003e3be9e3f8fba06adc130f07d26ec5e82fbc43791db820c30f0d69fb55792f294542c878c2ff0232baff6f208870c2f30c1d4b485f4503131c8e802bb49a8532143e9a867c220830b5cf4283107a53c684b627761811b374c05376ee028db838c2df0d9f71b3d564add6b818f29f7c40a53da3cd959e0e249cd8c604158d0278b50be279e2bb0fa95449f2e09c9feb50297525636f7f414f42a70f1541236ba1a29c5a9c0aea9905e9ffadd53700a8cf29ca2c4b558c890023f4a5f27939f4d57d089b1838b09686087efe080b9848c28705e22483b9d37275a1e0aac5b9ff6e54cda4dc99fc0e62611c72d74c6de9dc087bc632da2e926b0aa96eb25242526302a3285ab9bcee8c9b40446869434428852c93fa4045652c81bed6d97174349e02b67b4b43e993de206097cbc48e3e621e608fc85749aa2c734b333023978deb6089c58a848a9b359f24e128111a6eec992a8f5dcfd10f820737da759c960260aa1282a276da7c552818c2070f6616363397dcc07029b2b22ed86d2b92ffb03c6f622e5ec5bdd26293ee0c23f72fc1b0f41a67bc0e7f8554a1649a23b3ce04df379695bab8f15db01373af39bdde4a4a242142043074b126f2d99262707bcfb6637355183034e729217e468ac0e326ec06b72b7aab497954d9561032ea5bc9a9625dd0695caa841b6412b92d438914103aec7473b0b4e7eedc8d49bf290052bde954f23668dec522c18bffc94549f62400a1eb0e0a44790f69af335ad690b1bc40236b6b0412a60630b1b840236b6b0412660630b1b4402362e30011b71f078059354c8316b4816cb84af06011eae60bcb5caf2ae294f9e6d05a739ad24d3525975b783218616f560c517e25b5269d15d8f55f02571729694d23278a8825dcf6717d306a5673b20031db871a30b47810e1d607c9123edb871e375a800061ea9e082ce6f2a29e615edcc0e555470ff1949b54474d4a46a233c4ec1abee6866f28e872918ebd4fbf020ed8292799482bf0a0dba5ba204bdfc48c1c90e161a3125798c82cfd5a33e652d3d9aa31ea2e0f562a5fa1c52a964268f507032db574590b955bdf40005a78334117358baefec797c820d795355ec8b1e4ac9c313ec8590d247f1d31325280f101e9d605490da994da8bcb93225ede0c109369552d1941d3d356463f0d804bb492413dbfc1f3bed9a6025d9e59dd8a273867960f0c804eb6bc18205d1a14665c5e081095427a9f157c97e31868e9580c725f88c3e22c326059d7181c723e47d06cb4be5183a2af15bd8bd9f679b128c8d92b9baae3777d4bf1843076ac063127c0e91458a5bbfee3872ec18230cc22848470c0d6c2567815f053c24c1061f69226ccd4830ae39a4a037dc8404a3e2263565ccce27fcfd82c7231889c1435bef7604b71f6a6b6ff75386c9a3119c4efdaefba57a30820beeda9b97443ce0b108268888a87f168c828722d8a4f4c42da12af7c41c143c12c17d9b56e9b859a445cb0311ac046d77cfdf158eca83c721b890f2cba2ffb628bffa0d1e86e0a4d999f87757082e87c6ce924e242f21941e8460d5d3a59844dac9fb111b049ba2c6cf79d46c33d382e094509bc54ac4f2897904824f4127a154f4e7c8db01c1d7a63a9d628e1172c5a6e0f1072e0909aef9727f52e843e0e107ae3704d11b5f1bf5ad471fd8a0db69d48be9b4343df8c04b0ed3bccb252478ec81cd173be8d254e5d0c2d17d881d142430bab00b00103cf4c0468e6629b45f27ab5efce09107fe93c633bb94c9e2c8f0c0597253bfd1be6c7d2167048f3bf0716d2d2d640e4916ca2d78d881cf509d2ce5300d95d781d321655656cb972548b4e04107ce7474bd749df4e50dcd811f75dde65532d6b8eb21073eaf280bb2627c8fe92e81471cb8bc9e665f59d4697239e001073ebf9a8e1435291e6fe06356341942f28d091e6ee05bbd72fe0db24c9989a305270c331978b481cb394951cfbfb1aa325f78183bc6d871e5c10626c9ec5e4a94aa1ceabec0630d8ca757f430b563297335f03149fb8e96df64d27f0b113cd2c0f765fecffb3d61d2c62c78a081c9299a9e2382d23a4967606db227a146d89ec528163cccc0b82921e26e2c9f1cdb32b0c92f467190819139e93791e4e7381a2a24f018039b338410d67b31256d1bf01003fbaa498a5d503a3b84b6048f3070a66c930c1d47b55f0a0cc7943456d25d2bc1e30b6cdde8cde9b1cb74ee385aa0430cbd828717380f6a7dbfcad3877ebbc085d7a9884c4a7bd0e102a3694925e854b7c0f9980e52e2297f53d10a1e5ac0721e0d2ae446aae091053e6fa8648d1d1ad444565df0c08297aa479859fc0abc84a851cb46895edb0a7c90dfde2e32fea9a90adc066d7df24ab8fa54e03e43f57966bacb4e81f7493172aacc4ba18ba5722995321f055e3325896aa5172bf350e04db3f376d929a1db7f02a73a7947c545855cdf09ac5eea076dcda263f49bc004cf5144aaccd39f3e133855f12c4b0aa1f2cc5f0223dcf472c9cddf48be12b8142237affe78d6f093c0968e65bdc89b62d23d1238b9e329c935f99ef547603f256bf650933ea57e47a5c949d34a70e71d84ca9643095633b86bc8ec24f88c9a63e79f940497512d491db7d4798491e083ccd9d245de8ed53e0d4864aade848aa351748f821d670336091a8f60848c5b413c54e7c71dc195345de7666a04bbaee7495e75868a0a23f8d0ab20624417c1e7a9ba20661d73f614c1e54d0db6b9f636b49208b6d2e9688f31b6f588d84c6434134a7608c6538a489fc97208779b8b2d0c0d43f0d9b549ff540aca275508464e4a8e11062118cf214cc5d1bdafef0f824fbdfb510b7241b01e96dd3546dc1f650a04772a846c49f99eb6090204ebc14699aa49273de9fc81f3bd1391ef3c7ee07774e3d5fb875cbbe9032f31644a9bc6bccc357ce0fd5753cb93660f5cec8951ebdf54e8bb7ae0aba2a8542a836ef6681e8cdc12cfd7e28907ce92103d31787ee91eefc0c6ad1c4c5abed8818b39cc3e3de705cfd78157cf9f8489c6b4392b3af04175ca7819a4258d990397530a49454c264a47090b68c8819598c13e84150736554a4ab27a8d5b88c3814daf9cfbe1f71bb84b4ae78edcddc089decda0cbbaf74edd0636a6ca2fbe9577da64d8c075ea5222d89fda5e740dacc8f6d49b37a806de7f84083958c839669706ae2496d03946fe247434f09b4262d299fb5eac7206ae84f6cf6f1e4a249399810f22e554d9eb32b06e19926d940e19f8902c54ee8da977947a0cac78796bce9253a5b41818bb3ae175a623e78ce068810e31c2c0ae47fbcd29497eb01218b8cd67a9fd93f70556f32af57684c5909917d83ae59d25a9c9415845031a5d602ba5a6cf6a4ae988991d6870818f9c45e8df36dd10475e70b181a5b105ce6dd369519a94e8a4a3052ee5e7fdd5dc1d92a259603746caa0eea7e244110b9c5b6acfea96bf026312526aaa7117216334acc048099ef267a5142f48a30a5ca5bfdbafbdf5b594a5025fb16c83690795ad73f431ba382ac017d098c2e291228aa8a454d19002b71f4443d787a8161b1b78c016c50b34a2c0a9260bd1b41e9e46690c1b1798800d30d08002175fad339b526a39b883c613381135bff42e7a8aa71750aa8481861358df4f0bdaf55354d57194c181461318b5cde7fe1a732c956101183ac240030d2670da83c48c6e4a62eab30c3496c029ef13eaa9367b4990d15002a7dff25f3bd2e4895d0e2d1c6178914a79b145175f58804612b8d4eb4a492b8a042e289534ea0633a55febe31928a510a071043676964ef622358268530a348cc0adc44b7a22e6b459544da051044e5409cf215b9e38a944042ec35a74d774567c1110680c81afca49bf52c4abcc7aa9020d217039ba74090b324fe62a08ec290926c1454408518106109890e495e89646aaa0fe80afd69c534b8714ffb328d0f001574aa594f4ea95e9b422a0d103ae57af92a6d2ec3b960cd0e001db63a3b761c98249d61c81c60e78136197ee62e80fa937020d1d70e2ff3175ac0b3adf99432b0c2ec45893018d1c70390691d73dd3f512061762e0d811061762744103078cf63bcdba1464a040e3068c77648d314516c976db804f4b216b9b29dfb7c91368d480dfb6d23ebf4d3d7dd1a0011fa459fc6c153263169cdd9bb6dadf376139b2e0a4de8e48f2cf34cc8805ffaa9236d7f2657fdb033360c1495131534fa65598f18a728b4dcad2d9aec00c57b063dfa52b2c58cc21cb21cc68059be49769d0bfe725439615ac66b4ccd851447432550366ac824f5971efb23e6dda76862ab8a483074bd974275d71093352c1be86fa4dffb9fd928f0a46435747e750b9b5834ec16d52da1d4d57bcccd514769e1434799fc508334ac189ec7699b71bc2724a0ade54667c932034e576678c82db6891fed385cc1005a73972758a9332a599a8302314dcc8bc4d2a5eefa5e08282179dd926a9ef6b62ceccf80497530eb641095d5a3ff7049784befc38d2e4a77d3ac15f5bf747b335c78e0381d3199ce0b46565fe379dbced83a3ca143163137c648dfc9eb7ffda6a08d8d8c2c603b6b0c136b6b0d1802d34c1675226f72f7dbe3023135cda10845ee650113bba0516666082b56c11746307675c820b41648af9f653841cb28019966062daaeafa0aeda2cb4b1c5186654824f7192501339c434da86125ccaf190439b9981044617ae6312dc067da6a794d6955f39b4ba0b303e083324c196de0d495e3a1b21ea48b0935da2e64c419060b2281d31974ee2a6848f6025fe577a9852e121770413728aa5f8e9d35a4b663482d31bd916538f8e3083117c74cdd74e593daa25bdf02202691b8043cc58045bd1ef2aa9529e79438ae03557f2aec816edb42711fc5a0ed9d6e2410463216ed01f2166d6c839041f84c4cc546117ed5a43703e7a3a53e45821b8d0e3993cab4fd54d08ae8450bde412f39f84e81003479587d1820d2462c62098a4b1d2d5f34316e90b82abd49bd9ed8329b33b10dcdf6e0ebab29724150182fd78edede611f278f40f8c69ab3f75b671d32a40c1e62366f881f5ebeb11d2ea2c5abe0ffc55c7cafece5515743eb0669182f0f4901df2b3072ee5a818214e1296ffd3039b4c3f72929a5ef3a57268a9000538fe781738ae3a2001731580e185eb08402066e481d39a1154d2a022a6d5f1c05d06559363d21a9edf81ffdb20313ceae474db0e7c2cf1d7b2cb1dbda375e03705b5187445f5e9920e6cbabe9873fbd69412e7c0279945e65dcf4cc927a6fd8b15580000aa98210776426e491294478ec98f0327b2e385507582039bd386beab3db7d1ef1b1855c942afc8f7b6f204ed08c60c37f0b59752c5bcb95deb6f0393763d5f48b55945c6b0819359b2fea8fd2079b26b60b744e8babaae8d9b570372848c41e8e86f1a1899befdf2539e986488063ef6e556aefc9693b2cfc08714da64c87631490d320397829222476b4f2caa6560944ad31084480c79f46460cdabb35e8292a5427e0c6cfb65a618835c0c8c25193a657dc8b1b31e0636a7dad0621a4212afc1c09eeddda8d2a72ff079eaa3a36e4a2add78814d7709a61642aebaa60bbc78784efda6e102a37394741fbf32627e0b6c0c4942c98c76fad1d50223536df69f8e27c966814fea937ecd251618cd88a9be976f17b91c5a27f8828b1d3ac648ae03479da0991414665c81314f2999e5106b7df3de0596cfb002a736a7ca88a12be860e238c1175ca0209d40877b5186821955e0728e223d847ecf2552ba382ec60e1c28e8620c946563030f6080316f30beb89f41056e83dc8865a332ef1de98b8303477f7170b0808baff2850e0bfcf109883106056edcd0e128d071bc281670f10503c0831953e04678091169d3bfa2648614b8b4416dc40b966d6b93436bfbc60d0e9828b0af395993ccd2983a0a057e948aa62a3fbf3c98f1044e099154b9b27437955608339cc0bbea086d79a46e507130a3095c08491e6b27fead884ce0a3ed77e7cededb9212011da7033798b1042645ae98aa4c6707515202e79f747d103a1dcc48023b69b494bedc9954f410099c52724379d01dcaab37338ec086c474ee1743974660527a8a418847af084c5caffe4a3146df9815e30b2e3870e346210263fd357a3b9dffc5b0cc60c61038916b4cc4d19f35e9940b6608818b7d2e4a853ecf82c0a6aafa87851ac78e436ac7026a3003086c28c949691fd359321983193fe03e65f41cfc7447440f0b66f880519372fabc37cd49e66c1166f4804fca247aba8a179ec61d3ce0c7bba36fe48ad88e2f8e0e318a0161c60e184f6a21aaae9f04e1660e66984eaa246da9949e03be72c774e1a9ce269970c097dd56ff58325def8a0d66dc80112965b1a0aeed554b1b705a7931e36a8ec9d3ad01e76a75f913626d0ce50c1ab09ade5a33a485560b0319b3b0c3b36f5398c9dac5f8e2a4d3012bb2d08245b5bb78569b2423167cde18740e1a3b5870a79dbf6486e0173b5d84e185eb0b64bc828fb12f0184e025c5531a565223bac6c60626604307c1ba9f106db59932725210e64caa83d0fb2924ff77081f81e053df5396a067396777820f40f0f9bde6e33105b533b1e1e30f9c773495f75250093efcc0c7902fa5595bd9c79429193efac0a7d7575aabe8aea0c307c6fddc4a3d85e41e58d3b4906741add6d71f7ae0cc3a06f724624a4247ff9107c6266ba747dcc9a22d1f78604c44fecf154d738595430b8cff820ba2838f3b70aaeee65e5ffa473d36b610400e3eecf0f9a638d16dc4b31c5a2b383a70b8bfd58193c147a452f1e4093dd1814d2e6abb746ce7c0244d49f7a27b7268e506bc0b14e8700a9c073ee4c0a8512f21670b922cf42ffc04a6031f71e084ce6bff4da1b36f0a072e2328ab0a96477aec38f0f1066efbdbdb424e11512c6ee0bddb46787814d16fdcb831838f36f0e6c9d2fac95117526e470e1d384e7050290a6480021420a6c30b2f8a083ed8c0e68a14a93dfff4d28f3570b5d9be59faa5e5fb871ab8d6f02c916b64d221fa9106bef7bf9392b42f42d51f6860474566919474d37bf6e30cbc89a6cc6a29a6f4261f666063652d4d32bd9652df4719d8a4845f977a503a499e434b4df041065efbcc1c03dfab66427e4e36494d7268a1e08ba303c78718d84b217ed9b985d8df8ae1230c6c6b2e4fc1c3b4cfd361f80003b7a75f728ed61633828f2f90f369ce21626e8ba60221c0d11f5ee02bac56c453e66f497268e1c8808d2dc84717b8d2bd110b1d65ee1d2e702a9ab698d47e47af6c0c1f5be0a2a5b8a9152adfb82106185decd80f2df07ea3465e8be6230b6ca51c44b4ba5ae4a4fdc0022337d3b78eaedaef908f2bf0315f658ba57b52d2b815f809b2cb3dfbf85105c63ce43abbbdd2967e50810b5a2c988f58f89e3ea6c075468f9e3a434a7c488193626a9d7a2df2c75013cf131f51f880427e31eea952761727c761e0e309fcf9dba64ebaf7e39999171f4e602d87aadcdf4a11d9a2253e9ac0240b21656b089120a41f4ce04784452dd31c423c1502bb858f3bc894d2561c9d600ce48de304607461f0001f4ae0aea35958974aba52b4c24712dc16d3bb112a6e60023992188701376e4ce103094cd011a39a7dae12253f026b2e4994c80db5592123f09573cab896828e6b8ac0a9acf02836e61584ba880f22f06d2a78d6d05be1ba6b213e86c08798be1f2a3c4d16c78381c330c013172ad0e11a2895237d7174a0cc0d7c08c13d7d6abef7dba1a4021d636c91868f20703ab3b957a94060af445bd03b293d7d82838f1f305a216d52952e59d5ee0336a88a646eb71d64090350838f1ef0f93de7dcc9d6d5d7c4032604f53cff705bf1b1032e8390a20e387d4164c93622fb8765630b1b5bd8e8557ce4809718ed3bb73c53c50f1c70c2f4a4511d947e52495ff8b8019354445f93b89fab83884de0c306ac4d7a2d21847077cd7ed480fd125943ca8bf9e18306dce8186a84ccd8c13212851ab3e0a2272931c82c416551d240b1430d59702986d6abfcf70c41e9c51874a8110b4e459219a9d75cf72758a8010b6e75344c52a6e41eabc62b38e1f527b45e7ccb9e0b430d5770414da9c896bf2b06b7157c6f1659fa9e55f28face0e257084144ea5bf09c55b03569432c846de88b2ab8eca0294216eba81b4a051b64c648769a6dfc34d77109811aa8e0f5b44e8ac1cff2f9478c8370eca9710a7645c6cd8ba233342e7c8c1494aa610a3e7752dee57c8b399dc610354ac1e6502a8927350bcac73cd42005df159a6388aea87aba35d41805efaeea2198f47cf92d68a8210a26ed56a5e5e46d691dd60805e3aaa6f9d53e3540c166f178d27cdcdda2c62798bce973507ad74ba9c7136c9e7cd283dcd4e804e71233aa44bfd0a12ca8c109fe6d3c27fdfb20121cc46a6c828de6f76d3aed4454870b1d61580d4db07dffb9419b094e78d69ea9742931b939b42a063530c17a301df3c70fea4daf4bb021d8764848a633420d4b70aaae9e63142537422cb750a312bca8f98ba81af364419460ac430ea62fae5aa831092ea4cb9433955fccadd790047b4ae8f04832640bc2ab1109c673ed8b9ca06cd4cf1c5acb831a90e0afbb228f0e41dd9258e311bcd9b8aa988b0993780d47f0d94386beccd22c4ad70836dc4f07651d32a10623b8fc20ef42d59563c721e6801a8be03ae7c5494a868fba48166a2882cdcf5a9b58969e16fc070347f72916811a89602c2421d2d4acaf236e705003115cd22023c70c22631138e3a2c621f8ce7b4994d68a212b65085e23778f2cddd933a61405d42884f9cd27082183493381d15b83108cdc284ae4f4bb210779d526a0c62018c929df7e754a923e6b420d41f029c2bef2b90969a5d608049b3428a549281b108c5ac664b5ead6f803dfa731dbe990fbc1ec5e55bb27cb1a7de0b49bee7ade4e1042abc10746b48894d56a4165f76aec81b37613ab7f6b7d8d1c126ae881ad4a6f13cfa6b684d40535f2c0a778a523a2f7d608ef0635f0c0ad69932631ff8d3af91dd8eecc3156d96d07264b0eddcfd014a1461dd8607282bc92acf59dcba1656450830e7cd6c5a45d42e4dc9e9f0397eb94ac499e9d9bcb814b9b344aee7890691407b6ec4dd7f78dfa2c7138b051d34949195266c4dfc095e63d91e2686d44ddc06778528d1ce936f8b731d69a8f121bd86a1d393a5f7bbd92d6c049c8912bc4a44d7c4b6ae0e32569a93508a1cb4a6960359d8bf6efe7cb104203b7eb163347d252223f67602c47d2bfdb1c3330e66e5b1dd275828897812b2522932599f44b78c8c0776513ca3f6dfc0d9931b0daf9b2978f0eb14e2306565f5d6f57fc523313064e2591d7bf3ed7791c0c9ccabbe165ba52b4e80b4cfc1b61315a1ce99ff50297d466536eb92ef0a7dff6c13ec7acd4728193d183f9a515a13f64b7c086eda5c62879d2a6b5c0698aa2524fa259e0b76b6d62c8799983140b6c348f9f4765afc07af6fc31c7d3a52cb4029fd3f44893a0a24d50abc004212ca7ec954ac94da9c0bec88d39a81016c934054e66bc1249e74b152229f0e51a4183c8a54382320aac9b8514445fb9a832a1c06da860aa2c9d89d69fc09932adaba43b81df51ba62cce1e9d3a8097c85901acd6702a3a642be3caafde359021b2fc6603906d9b7a612f811b58ff9fa25940476336af85eccff202430a24c9608d9edb45a47606d2f7e0eeaa1e349d008dca60da57ff222b0317f478e9913811f573f9554fa29350e813555710f293c21307a27679f9c267b8c5d1058d5ca9072b4030267ef218b12964564d00fd898e29d2afb33a7d0075c6ecf65b2ca2fa3567ac0e9602a7fe2a4f080c9a93b9a49ba3c95db0ef818546dd7064df144a603c6bec2444595d4ebb11cb095cb4a72f6ce66711c7041cf7c736faddda56bdc805327b2a434211bf09fa7314f87906fda1ab0935b47ecd574f4b4060d38f513aa2f79c5b59c053b6a21efddc9a8db952c180f12e26de6c7b87e2c1811d2858a9be9939081052f31497c2f9d4235f90a36bbb3baafe94a52bd2bd8a0932e765d48a14c6b059393ca9a83b5ac60fd4cbd0a3e4b08376d49a7cca2a30a4ec5dc935372489b924d055b7d49988e39890a36861092734eedac693c051ba92208d19ef89f33145c0b6898820fd26209095ae3dbc74bc1fee668427207d34c2229f8d2f9d1cc64879c9a8d8253dd88792354be4314fc6810c9724d3779c943c18b44fd4e32dea0e0a2c5db20ba235d2bfd092e7708393c8614c26ae409763be4ce58163a69e67482ab144bef7258999887138c6a93d9e4ae4896acd9049b2fa6ee3e32a5c78c26f85839ebb3c61e69419960a4efe51482f4d2d7c18449249172df622ec1c9143cb3a6a5aaac5a821149a925510a82e07d726b5ef2afa0f94070266408f393f963ed80e02f89a0a1f4d286eefc819164eba15ba134c8f88109f69e3a338d7eae7de0542da7de562b9129c6074eb4a63d95bb228910db03233fc7de4fdaf4938e1eb84c5a73c84fefa0993cb02ad613634af1c069c72da59eb9acbf3bb0f97c82767fc60eec06491b5ca3d8491201831675e03a8a75e79c2157254d07c63206a5936c9115f4670e8ca7ca206388460eecd686cc263c64d17be3c0dfa8e6e63af9164385031b24e80f2a47c9667dbe81b549b9574fd44c65ee88e106f3fb6fe7f70b2e8e18ae23dbc06ae44c3ea2d3a7feb46003934efb95fefabf3f79166bb1063e8b07f3ed77b38fab811321e58a1213d1917e1a98e42243772cb9a7bfa381b7092149db889a75e93370417bd0d4694f3b7333b041a8bc1a2f8bd44b3965e064875b2acd8999364306fe4ec99222bdbbbcdb31f0b931ad7c6442a08518388f563fc294302d790d037b22af629b0c4174a7e4d04a2dc0905a7c818d18a225a8e4926397fa85165ec01468d1852b560c92cb2aa5985482a50517d2a5ca936184716e145a6c814dba6b9fbe628f086e0e2d1c8866630313b0b1636c60021cb05068a185d3220ba605160a2981165760b466cfdaeaea6b8bb50019066861054ed6d85dee5e5125ea2ab0de952e89f85a323df4002da8c0569508caebf2efd42b052da6c0ebfb69a5dfd96b74a5c008b541837e8f16b4a951e0b3e429b5dd5947d4f5042da0c0572af51aba792d9ec009a54330a1326f94667368556d2079181ab891650c5a3881b1caeda7164f4de49bc07a0c399e3909372162029bdd2445123a889ae812b8512926124f25707d2bb12cc34f021b39078bb1d4048fda2381efcf9e11d7fa2370595a7a1f9407f5593702ffaf49e974bf6b99ee2270359aca6dbcb75a6b22f0515f84a5ec8b7eff10f8d29f6ba38a84c0c6b21d592f9a93dc04812b619bd12a757e3281c09d4c3a7b24d30f98a0397ff04a412b7dc9077c8e7977cf54520fb8f748f5fd67a1829e079c1e57b318f1be436807fc4911ef5c121d305a2cde955273c06e0a1952c896d702075cf95edc4b934ce710a4c50df8ab7c93b694bc95dcd1c2065cfef71ffd8a17f276b4a80193269824cd7ac9bb375ad0800bc1268594de654a6d66c107ab7849c8989105dfd1ba946c9360e9df587097b935686adefebdb0604f8b8b77d03b4f7b5fc1267da3b39ff0cb5b5a57b01a74db238af58ad0b682cda3fa94d0e21f27baac60b3630e65c164ac28d255b0e1a643f2dc54f1185505576ba7b3aa8d7e8d9a0a36e29dae1ed724ed525470273db55fe490ad969e82b118628c94276a0a2ead56cfbb735ff452701e528a112dc7cbf9430a2e4dd4d39eb37b8898a3e02a2fe535756ab52b290a2ea58f299d122ae2d6a1e0ae4774c49c82f60c0205234a63f013fc089946a8969e94a3e8095682870ca242ec049b3f2775a2e3a7d61c7282cb7173b668fa4e63ea36c1778908955384741e2f9a50d2c84efe37964cf06b9d930c15b1eaf498e0db43ec4b37e61fb376093e76c8912442fccaad5982d1c8ba5582df929a9ac3cc7d45a3042742c42cd974c658a29a04a34aa62043ed2da8b52451b220e3a75e5224b8d6984bcb74a9535a48f059e47be8603e828f29b2950cd13cfae8087e35457d75e27b758de0933c53afdd63ed298c60a4879c91b2bc736364116cac521a74288b7944882238cf9932af3289cade26825395af6a3bc9a415728a08ce377656804330c9e42695f431c5c43704972ea69cbadb7527e71482136b112253660eb23d842863b0b89a9b6206c146755d13a1a729a95410fce994d74d9d8e869d81e08465aee6d1699f441010ac59778cf92b850afdfec07936d7bffb2a15acf30393d44573d3362de84b1f3899f4f5f8ed5e755af8c0b5bad5e40bd97e54ca1e38bfd431b5edb607ade881310bd3e6eb77af21250f7cda969cf8e9747a53e181abde9cce844a4d975377e06ceb742b857765b4ecc09a4850d1939cd4b2aa0337d1dd4a3bdd4a448a0e6cb699aefd909a03a327eb5637269996037feda6e63147f41ac5817135dfcd6295f47be0c009fd953d6955eccbbe814f4a081f0b4ae77817379823c970cbd136f01d5b37828a101014800dbc29fd418479148976af81b111af0a3ae389f4d4c08a0c7da79a9a543594063ef44ee6cd29b134b268e0f55465ce9ef9b7ef33b03af2ba479decae1833b0d53157363dd2b72b65e02b68d357593793c4c8c07846091e54ea1bbf740c5c9fea335da23fec1403a7424ffe5c4a448b2ac3c046cd1959ccfe4566c0c068d0567b4104bfc07586e0f182bcc02599428a88f9eb02bf751593d2f47181b3a4fbb2ea2d85e5650b5c65fd20d3a4ef85a4a205bee295d41331bb4c2959e083e5f62fb10d8b55c102232c8ba5ce26bb0227b3d4fdd52651619a15384d1fcbdcb2d9e8d1aac08d4833952fc92a591915d8da167df7a4a64c3d054e7f571231674b0afcb6bd8898c98927ad28f0f9233306f118a3490b0a9c485c09b9d3e94bb39ec0f5c79c325dccf3f5ca09ec579b664b3781b5d12d66723386f299c0a9f79026ba2a33be043ef8a4281a456c728e12b898ff944e891f1e9249e0a3889853c7cb413f8804b637447e5fff09ea1d813b5da739067d79296604764c453ce6a0725e844ca000456037d4c9899e2775d7446093d0bdf5214534290e8191e32124a4f6244d6542e0b373d404d3d0be292e70b818a71e508020a0955a52ca2ac1c3ddd328fdace06b4105000277a2471c42017ec096674ada92bf55aed1d1a0003ee04e64d0a1f35e6efd0d0705e801e7a22663c7f1b8800b0dbca0003c602cc80c936f1253e5ba450e0ab0034e9dc64b29b5c69c357643074cbc8b41df69ddf353b7a00039e02e549b524205c001ff1d430711537c741670e1050e34c60e6501175ee0f03f0628c00db88b1eb3fb4325e8e5779c85c059060a6003ee227e964eeb9492fd05a8011be2e94f5a2359f20d0b4003aead455e8a36d3eb3139b494371815b8fd9805db29e6383abd6e0ed5f9172b00030718626091057cc8828b933d66f4ca3e69f7c1b1e3c610e368a0051fb1b0ac7478081e3432f880051f4b6b8a93ce7292267a059f49a72821c6f6c3157ca5c66c6923ed06f9b582bf2b1d2a241f3f58c1e6a86f4274b86afed22af88f3c42574cf1c775f4a10afefa46a78e54f57ffb2315ecb95d7896d2ec6b3f2a18ddac9b60ca7d349a4e510ea6378a6ade146ca90f1643bacd91d4a8146c5b881ba6695dba37a4e026b5f9dec56e14fc8d9dcedd561105973ce8cce9db534c950a056f6a5f5782bec9a0ef0728182147fe5f0eea4f70797793fc623e3fa1798289924d4bc7ddceecaa13dc7a2475f31141098f7c7082cd223429996983d2b13e36f1a109bca13a3e32513e30c1c5a08386c4bc5832dac5c725b864ba7372fb6b89d1cba159a47c5882bbfa90548a64f5163f8756a9faa8045721b46b497dfba0e91c5aa4b6bcc1f8e21af04109ce638c28d174ee9849e863129cde04914d968e24d88a112346bf10178f5d043e22c1eaa411adb316f7ff3e20c195da7595eca08f47f0df1b838cf963b6baa00f47b031e45831a51311a34a1bc1bafb687dbc5c9bc6c2850f46b0934c5dbca4758c41868be02ce4adb4bd493fdfae08fe43ab55501ac2e123119ce6fc1ad48990d32d09116c12c1f7538d8cd4f71e828dd949dbfd53cc0e1f86e0b4585a2abb0d19ca367c1482495ebebe6fbba5806fdc08c38b84868f8c7da8a4f024e25824100883a1703810808102e603e3130000000c201486429160248db465fc1480034b2824443e2c16281c2014161a8744c24028140608038160180c0884c2e15048201ccf84bcfc23fccdd9b69cdbc999e79ce7382bda9ccdcd90cd990f73c6dcce96250f9f0356c899d94abb0c693383abc7709596ce29ca2e9d82b9731b616f895c47837bfccc3d8f7b0ed169fade7f1c99303dde814deb1166c45d17335dcd2866080662750208a22d4acfbd8604ee0af7cbdeac53946a23355256678910c3f65c9854a1168be89b326a1661224fd6afd2d834fa7f6ba163cc780669085c05be215e24f785c3c2091c82a5b011009d7eb9d6810d19965687ffc01b79050cb0f5b5b7672bd90d49735ed176c4c29d8d26ff6656b1e3c5e68d6ccf32d42c88053d878cb4d89b356121c6e908fcc0d1bd5cad382fb3b985f4189e1093ff24a0eae6b9fdfaa5c7a996959b0743986eb3e77bfe4605f12f85557bcef57a853fb8c2e504163bad26ee3c3ea29dc52ec3ff66161d54e2d4b1c5b3e2b1d8a8fa2dc1c74d655e42884f4d4810f3bc45686720a124b49317bc4b00dd21d03b6a1336613f500a90229a4311fc3d17d50eb0280a5da010519193d28dfbc28a775be808433e704c21984d39b0c669aad8aca0c5806e182d8d867bbadb9df3cc3f86b525e4119e3efb0c8d706a78dfc143abb1fe5c0be0c2c521203af53ca677c9ba39e3cbeca9f2bebc27be75013e16e90f2092a199c3f5820ba5d5bac5913d05fd3d76a36e59f99f62c3158687dd53c9fd0d0f09e2b431be040f1436264a3ab96e5837d71dee86d59bb134156ffae571f766bcf11e8cf7cbbbf24c79a93c40de9537d1bdcf1cfd78a3c48f9d5008e8e819fc591a6ada7061db859d298fb06cbd5afe4de88881a8bb791ade8ab79f6fc28c1d2a15f31502527a13e44de07ae7b742f73c0bb357ef11f662e12d4432e6e2d6e105279c8059942ec486ca86b68539070ff4f306dbbcfb8c7f6469779a9ad6d6e460e0f52feb163274ec32f5c5b8c9815381b6604c5444de1d710bba30f9860b3a6929b90bcd9de75673efdc656e3f37b43bd4ad706fdde56e6f37843bc48d7add0ade9271a6ee3e7ea8e28d8a169d272a257e744752c67b9c5f0aee9cdb39a6a5d6a90aa9876c593a757dfb6dfecde67b6c4b5627db17d51a7852e9d76d7a612591324a962f1a4ac820d435cb2b47c6a3885148b1210d1152344927471f21a5202279479bd1ecae196a8e968b5c85bba4161d4103d0b8457b982332b1d19553c8189acb7927775e74317ac0cea48f51a9c552f9a8b1e799c1364f4e29153cc4d252a0a897a4a537a33a6f15869f05fb91614864694b76b2e641ce1cb30551477508c0e4932148e488940265835ca018c424be145e26d16c32e43082fb265165683c125a0171f9de345d0d14722bf73ccf4b5eef8122d7d0dd48bb5130da3845b20b0ce1fa21a9263f386e59ae48006dbd0dd2fb4101030163b313ad0e806b7b9135c8d4e8b36d2784b8b4ecba2f7e6fbcccb7bc7e71d0cb1288cf4d76b2f0555c40df0435082727b1ed7296cd0a83ba6c4df8d4210763e4ffece9e133f723b60a90efc57503079ee3f01ea559081746cad4dca70c00ea7d8b58dd8bb06ee401d05e25a05eb73dd78d934ea15db7a1745097603af90d5070b4aea9f052948fb361c7552134e496f19f0c34dbb77ce8c0d557251e7cf83215a122f935e5bc50db6d668269bcaab06e8297114998b432c92ce1d823e6783c1aae200af5ef57218fa4fdbf9dcbb015d3dec5fa2e8ab104889334ca906cf1b43a385ea9c07b2c09dfba501a262c88e36d4460cf7c4c3d4c0552c389c1010e92b78295a6b1e5d9495c0afa9a342b588eddf8989866462f8b5b2440cd9061b5c69132b7c48b483c7767d28780369438019d780586ca75b854e7d910baa21e6ba3e173eea11140c9b570b904fd41ecc560a217958c710becbd45d5389aded202876af974244a67c73952ad23619c2359f59b6dcfe3bf27b20851dad1a8b476a1b437443670d596c9630e547ff648a3749d2e00722ef2c6299394c8e993409bcd1d51553acfe093925723dea2900c7a55f6702e5f1f0a5cb3a2e8c8da7eaf2b330320861109db08ddc8add9f8b2359bb21ad000cf634ac2f598b19572b5d7708f19919632f6ae6ee4c0193252aba1ef3f8d4c441fb35297b165a611137747194e85e0883819076371a71099cc6adf9bdf4987f02ef182fae849bcddee61fa210009f1bcf999142eb03226982cee916ab5b02d44c8638000b246e30d3df95dbfa1be68858ae49e3fd0867190ea866edc860df2691ead54994a8555a2810f9e03a87b8adb40b4b81bb791cf86d3baad762f010aeed10190c80f89f19d2021873ef935831249264f3377a1dc9c5696c5c95484ec51fae7c0ed7a521bd037d182153ddbb0943d6f5440c1bd0ceca4fa4256974d0162b8b551462b1798ef7f3d5d5f44ca9d8686cc723e80a2c24496afa0f1d15133b6cc24fe824cfcb0e117cfea3abf962e31cb05af36e1be6dda2c07ec38d240c0761465fa1274c1a449b078c7b17401a5866d7a3468687e756852de462442d1c01cffab5d7f9e8b21c98e0b29fb214d56d44b4e6d41485d8d840ea2496a5dd51c30814c6f83c87ada6c8dd6980db039807197050b388d3433f6c9168949cd2d6bd8e0a70f29b7535694a600fee9b95fe98d8f1a6058cf8907852b3ef7ce25afaf81217188683c5459a21db11b15543cfeb47038fe752987062bd70956d0fa79660bfe538c4d10506ce825ca1ec9f18ecfa36c6a13045040410b2442bd5599307717502e0e697eb016517d8b090b497808392dc5c5a9bc8bb9d83fbe2f26606ab7520f9aa8cab5345cef62ea4796f45b325e6350c351528a86e3341311be125464182f4c566fbee8a3685c6452dec82941eb71c65412a74d56baadff13ab9b36d9c4a1ee4ab0575c83e8b488d15d9d3da1728f98bf1c840d621ffefdc294422da4424ba5e55013bcca5d07a3841724605c56df971d060567a16871405fcd63ae10619c3b8a9cc4f2eb4f6cd9182de961267e1278c9d59e06d00f5ad3a38fc3f19846635b4ac57707981c1b374094bb306cd73a02b7ed97a5d36822429e2c784579fda9809789b0e06ae0114ffd2da224e36f20126057b42f015905e4e28c09a4c915f4e2cd73f5ae06e0c45fe7bfd04b68f4267c235863e814c442c77631c9f9a34274e0b1655836d63fd7febf448e5ecb4e9aa1f103b0aa1bd456b3727e60278f5587faeec1aef32fe58362324bd29f6e4d061b7a9535998d0965b54089746c243fc63d7214d999f9a7f864c94e08ad517a298a654263eca4912262adee08a1b85a85cee0b35e8b67ab9c8e60216e5a5b67aef7004410477468d6126bbc8a81240518a7332a8a205f5dada0225f270174a03284325868ff7e2e03d05ba1b6f23533bf0a2aa8b73ed36dcc197ef27d2842010caa5538c60eaa81338aa1a559043f5658963ee5da999ab2847181ec470666b9ddc796797f29e3ab2270833f833991556e4b030611163d0ec60baac74a6faa4fcc27c226b87b2700c9e5a6cb4379bd3aa6b8cc9254b43eddb05e61c43ee4a91a1311891fe33f358e8da3178d6b49eed1ce3ca810fc8f4675039a7e6af4340a0e8440a270e14efdde9276a19776a26df890ee0ae41030858a7bbb4db02d4ea274cd846b7151ba7449874a4c410dfd89112d21f9cd127049c80c59dbdc98e8a95e8d72bfed25cac77c01ffb338001e970f2beb1e1a1c812977ff27238001b4d8522051521641e1e23e3480896fcfe198f6855ba8f995bf1a80e8bad7f4adc6143967bb1806abcf8e590abf26801b2244ed42b22fc25a3f248712961bece57c951a7e6485e4e4553c6ca8e61dabaf409bab6bb65cccf867e69054d0f621b84066e0f219d3f34702d9aa420139d0e421509e577c275e59fcf874c6acdbe1a741a6102e9f9464c478da4f748e9e83c6c3853edbcddaadc405b6e3a0bb8cc7f1d1dea915fc142bb8868bd9366b852fd7918f263e359a32d118704358481daf2149749c8e0003034d731cc498f0963b52301352e8d97f3528518e120c98f967812fdb5e7f95c63f57ee74bcf85af6a46e4d019e8a4b4318db5023c221cc603934afb720f8d4dd6fb2ed54700339a87fdf43379ddb237eb207ae32695bfdd68bfbc8fddec44cb117b95a286d37e49eb5ae809417641b0e7ca3fcfe6c6eb8ff424da114e99c39f4674c7c55376e0864a7e7ed14fec299d727f223e655b7caec63ab0535a3d14e349cd31247c70b4f472f4577851691f627052e77b25932965753d54abbb43e051ee0082c9026c74c450ae42203b2b47bd9c6880c284a0530edba2796dc8962b62155ad3dd2d6f16cd0e7036dbf80976396180a58f54e25c63a46b57cea14db74d96ab12d3c5e754a7e3991a0f3081a5105e3ded0cc4f3e83e9522a11daf3d8876f3b1eb4da137463fac6b2858bdfc14b1dcb80d0cc2dc3ea1e6c4c21fb0db448df5116580b6e05c40590d9240cf3521a4e8c2352f837302892e1a94c834369f51057c6bd385d786d3c97cfa524254632d407bf81cab69648308f9bb6c81c8b1daffecdcbc1a04c040d53b12e167491343c9244bd10ff11e5f2049c6a3621a5dae536c88de633c4556d5253f9d170516d98e0d5b654e72527d520aa0f16ad7903bfd25a5b1837c20d7ee24bb6e640e5cf09d0f98883787fb0cae54ca35f87d3bceb2f08857d634e990ff8b2a32daff820fda3a9423459f74c0e662ec0ec29b68fd39ea8c840160a94ce98cf1dcb405cceccea4317f8c473c0ea007198e6880ed9a8fcf4a83c2fab7ebc7be473aa0136eec07269d701849a0563825409352a3b0104a167275254581b92f503b8c2ea8518f386c8b19623ac78d146d58447b1d32fc08a355c93a9e77e16e99a9ccbff2593a2afcd7911958e9c2a3aee748522a3927e5e8712f11214ee84a11aa37e3a19365e9fe1746c86a377a96207a4615932a55eb100ef6ecde046f73e6a2a5b309e53ddcef26ebc6eff80d1e1935d93590974c0f1464dadf78ac48c95ed67f996f064bb784496ccc348cbf448aa313222be76bf78f292e4b81c1759dce6bb36adc3548e6825e20dee8fa622b0991e17915b8f8c1fdac02257dccc73dd8f002ad3061af9497b795b4d80b0084f9db04fe7247c9c0ffa97804feea0a05bd04ef0a0d7a6287c003033e40aab411300e60405b63ce460cbb452481bbc0e7c4461c193649fcab96ccb365aec8185e03c7c7cd79c094ca87856ea12ddc224fe4a8652c8a4b7126d55c0a6b7d70d25dd3b9e0711489252245dc9bda42d29c1290e7d5744b9225d4b01246aa98074f3b4c08a8adad3d23eae3cb180425d25d24e5ba7fa1392e3c45e92c7298e5a96ca3a719510ea523a78bab45a4c5d484f9ce679f5c7e26339f24ab053759973a37cce2bfecacb5361c0c0bd66a9fd3105b61f8e6837a87f8a2254a290ea10a9fe2d1aa7f228595dd2fe6f670b2389c9baea04359da53f024271ef25fb93fa9aa5be2662a96fff3e7696a2686435b2fa4a152f89a0e49a7cada4b30ad8c48fb003fc8158cfe90d24b314a0efee034d51f831a85f8c61e2a596954d4d70803ab3f3ec1b4d73a4bf8e55ceff43120e5c184d88d16054f6790590ff38ebf95f2201ec106305819f1beec2b43288995be639f3cd3cdcece819ead019ed9c7ef93eb51cd0d6f8f755420932c72769ce1c4fe8f14356b5973fc67714335cb4f3ec88ec299de90970f5dd73418865d253dada3aed3f96b829bf2284bc8cebe52c673a8f426b1888e493ff4cca36103a1009f3ee2c537e14fbb89d81313347fe8370035d514c86d40bcd159683b6053302ff9c96d679a30d68f33e669c78a1dccb4f0320df894e9a9b54f462d27ec08834efe3e400d221869df9cd1c220c1d7e235636327b4cdcc1d085beaf1c2113c30cd4e495f32e2bc5cc99d1bc637da41ebacdb543a2660bc70f20e9df33a1ad3566dd64a21b5475efa082e88b0bdf3721601ecee0025e3dc4deb67907de0c53efef0de38b020d7a42e71d4c8898673d0e1125f36859e1f51c1c6c185c7eca1c289c5848350a435c048cafb790486545e2c1e61d0c5e7071b13e7363184aba2434849911e806b327f01c4b58117ad9a256c56112184b25ff1187f54210d93ce74256f0f48ef5393044dfafb6751e71d80bd8822b506472b54caa1199a8817f15dcb0165e31a1a9039a00f9797536944cfbffb99801671d38f2e91e39a4efd422a11d0d8715c17562f42613a7461f90e6c42f9a74d86309d63c1c4e4877bf145f5a7a25256d64268cedbaf2c7661ff060d8207405e31db6c642717a6316b8cc16c27e944218dfabdcff02df4ebe2e7e7165934937e983215e16314a0a8d79cdcc05286cb6cb894de0d24c0069a9d852e14bcbffc6077234a2366823ffecb7f598eeb98c6ee7039c5a890baab20df2a49e4b2a59766d26480ac8330726356198778953cdd4b3eabb5400e8ba341271c30578f3f7a18a7a7d6dc6837beb0659bbf598dc4109112b7251148c97a06ec0dc844dc50f234e5258b828d78a6f364ac3261e2d2a60ba478c32af82743548901490f42eead5dd515a1f97d7a11704265f15fb8b7af19608aa9fe8509069d66c172e09f8291af272c3effcbcab6ab9fca97076e60628f93afe921a0b7e52b3748f14d5e1e3491281a5621d096f04a066f9fc2e306481aa260acdbf4067db93ca7a3f06be8e6a61deb5a681befc1d310b60a86c2c443a03c720aae06a260ab51bae780a53bd0222ba3cb4f927769fc084178f13aef04b12f5d1f74e43c118a49a0d519508a115f5541c00fc904813f3300135cb35c21c806ae3ee9111634d7f9d8f3c5750c4f84a06b5f3c9e8744444e590137f527afc65c7a02750d0a08028c3321594737515e0235dc67f10339f949467c24a7c3d7e21eb99a524dc37867bfc1af616ae3613652010d5cd4d05b67e0b2165a472b8e07e0f35a4914234747f81c67fb88684cd24e66ef858f545da8249b63525d37ffa641c20c5d6bc0c7b14e0d3155cb7ac6c0c5bd7955de2430101f3daed09c603d7350df9f4c09a813579cf234f9feb00117b9507bffa46135451bd260b384aef457b1bfc377943099bf87753ba518e0a2795d583b017f217ad58e18ab08ef3c2757d81348959b1630a97cac9c233790d84ddcbd94a404001295a34115cd63ee9b6201c79d720014110c08f40664d240566c2bda1b5c7bfd148953a56a4f16fe1fad1a26eae501b49962a50157555bcced0460fe98e3ec22c20ad98b7c8aeee7c284df811bd5e36d021a8defa91af1177e25391ebd5e27a6946184ecb8845aaa2951f2370586cd6cfa5fb55a6b6f9539fec900388af9224beda6a9c2c8bd12d56f7431dbf5a701948eee12f1a36fee9997f7d1067ef4accef88a92088a254f5a39287927a0af24c43f9d9d54bd043c404182d8a7eb26a7d6c5406cb700b7b47ee9d0f21d5bb3fea7dabd72a92a41f5b877318f729aac74abe9160a25d123c13af47541a53479e17158cbccdab65970878ced3a0ec1bea7c997ba7e9f174d73bede04133368b3719b3da7b199f52df3696e6f7498992c79b6561aef1ca30198f2338862f1df0a60a61c10156025dc54297d3ce78d848d3209c8f49b24dd8a036f7e6f96b1755a8a19802e735a6fcac53c7fb899137b083b22e5edfcf46521b0e6530995ee7ae497010c462ac6d2ebb119a04487be53dba42083180fea1454b32bf2dc165dd439301729c7d0761e49dea2717f64673411f439057300b270c39fb61596b13d0c10ac48a4cbaad4a80c7ce6e4f80ecfb72a8504071bff29f75dba42709c43424583f4f57a6390c7ff28c4dd06b91eda67e497bfe01a8edce4f93f612149436c2368c82bdf9b7426716ac4fa1cc5e176126f9a76516720f119d6637f51bf27e1efe978aee0b513920d151fa2928031df16644c42b622dd6158f8274e2512872bbfd6feec1e0f4215e0995db5c179d4924bd25543a7bf9906fca12504fa3a394d413a56ea2c47a4d492c343f1e5b2f90241e66455d70d44a01b214061f6f862689c75bf0d80b1ed54b150fd5be1ef1757fddffc72716ba1fcce9a4f62872cbda99d3a80cb7f213ca5356ee6f36c016c52259fffd302f28b13cb23173f0aa0030abd1412f6d972ff5590f8d67e45fc7bdbb6ef9850e9b93b40caffdf968b97165dcf725c360df884d2810084e85584c8374c4b5c6f59b75c4362bd01d2ce02d2e8c9d662b640b0c0bf76641ea4d0b637b0a97b2e958f0160bdb7b17062151805156602f7013067b7541e85119467240c9124e239861246fc75108cc77596c5d5e63b143916f5e221175ee1295ceda8ba483f321bc5c275aed647182b655a0311b7fc4da579f939bb29e154340e7468adc46f198c96c64ba9e3914954704dfd6f831cdc13da1f1281a46879db31a3a5a9afcd38ab73d52521ed4a191a7df8d845c3b2b4fbc06585c322c4e4e0a2c48fe3833cdc6818b6f02ea9cadb61619c8525302055b594db7de4db4ded5b1e8442bdbe73a756c65d56d089c9535301867813d5c8efce87f78eb745981415a0963208730d204e2b912d1627c7a48e4163aec994d160e678a7bdaa9ea4e8659b924114cc5d9270d57a6e4ed1f34dc5889846260fb2b16420688ecd0408268014ebe2600eab0f03a790612183687b195f9f6975fec47c3146a7f816d1d1b23dd610a23acbbdf419c90f19c8f7028fb0f3b1c9a64a25cc2a7944d9bfd29f76aa05473cdb075de04f6a1ef2b809d816080d885c6036c25f7d26f9e903e1c18d9a2c690f2648d8bb050da8c4a2e310a61b3e61f9cb08f7fc46528ee248d68174c490fe837129014ca4e0ec3506b9f0efbdc38cd5b39be743d4a8e65d5063c1a880685899f465b4784a730b496863443775f0351153267d71300d5c79013346b800889fba4d8ee1512c6f92b317e50d753d76e4c373c4cc2e10cca8ea7c2c120471e93a32de5842ed50479e062816ac6769f781a2a47fd5d25b9dd7fed580f473052298b8770e50d566d2f9c0c8ff2aa036f7d6a48a1a538a6258e581a5f0e2cbd3fa9a0249c8d6122b60b6aebc811d2797b44cb80ff27825e2120ceb43e045140449c346dceedfd4f5ccfbc488d65ed8e282522b6b2946f00fb59979609db58ea6d03c2569ef1a1340719cf51190368b557ba40fe7e21711349b696b1dc0012241c428b9420b7c8811f71c35648c9da23ae92f952f8226a848ed611deeb90a5e209ec843d614df82dbc0a3fc2acf05fb8164e0837c2c21ba24cad43042d08b6824f43da41fe44ce9a6bd66a111c7ecc8a84100c0990f481cfb0b0754382a2fee9bdae163e7f2c24072fa4ce9450782f33fa58c63eb75bf7e2c420476291243c88091a2908a405e1bb17f54f675d08ba7c96204fb097dc26bae259b41ae333a5ecb4be0415020b24eaa09301498f69898642326b1d2aec9755f26846ee8cd210e5bd491d26f8cd808656e44eca690ae33e9b6882188fb299327dc88753a326de4eec2913dc8e5095d7dcd963872dd4e95cd56468a83bf42586b56803cb0063cef42b41c5c863e691d22102acb329ea2d47f5018fff59caf96ef219e525abd6a29599aec65dc650b04c327c1f454af352beda3a5e22ba32eb0fd7e44d34fd07b5d2b311def030f7caeede5acc3eb5536a2242406faa2ef235099680394da385e27887c3265959f760f80980c611fd3a9566627a32ebbe6a165d2c00d84aba57944f1ea4cef4d592179623fad97d2880e600e635311233b973952902bb62b658a7c5184a9b21b370b8fe60d56c3236b2a00327f4dc556746a4323b0d17559d6fa49878230f58eb4d01f3faf44d67e5009c7f33dc416c018a58c551848797444988cfc275a8bd2da85a363b0a366d9a52086722cfec3b418f8d4702f636938caaa574259fea6148382440c79c528fc09190b0edbb0b9cd02c8dec1fa66797405aba8d05a58328e3ce3b37804b24300a9f3b875a2d8345be2d0c30d9e2e40a55a5e0760de9f02a66299116412797f21adc770ee9c3e5edea62d19306284a0b9fb72c65f5c1d14b54ec9cfc9a5080a5c162be5c52eb15930cb3faae303323fdc3ed1780bab1721fa05c9b72dc1e01a1c55dc7bf16a804b31c75b800dd1c6df8ca914b40022f0b26130c1dd8083c79fc546fd6f8eec49113e4ace29fe1dd1b40a255c412c0986827e8ead4a25d0b36f993f9e187574e9167adc002ccb28fdbf0f1643a3a91621fdfc0d7e7de909600e8d9cea1b92abde5460f34b0d313c219a13d62137338ce96cf2c3dd6c07601270d178d1d95e15d042d8ac35a5f2f0aa294aecf4c2a681fcd1e92da2e6b5081f766f281bfc4d27af3152b82f1b1427a608da3dd2751b35b1c6f9ec44bd57036974ca64ea777ca0e7c14fcdb91942455d7dd46300c973166ce03631fe6bc8cb7b801a0a171ea0c086b474bf6786d7462ce501a4161b1cfec274839c29599c99eb10b2542a10f52fbecb082e9ad4b52657afdd8670043a599568836db4903b1f1b225dac8686c7fcaec5c47dfdbacf94954e506fb6013f8decf2620b111829b3902b0b1b5035b2d77c3d79ec9661378b51b25d8016c670a4cd0e68813d9933f8d024e4d4eec4ea895fdd6966ab3817a726cbaadd321f029015a4f65bf482190de98a213feb9815a65caec141e687189d56712608243c6982c7eb0109894967865003a63b1f83f84f39fb1031a1662b3dcee42e2162940d082a25bd3b3513a31b20c2530e9477a4fe9c056a49f9f5ef36461daaa946f289485470061560fc4b7bc110253332e90621105bca9d8d9b386a7ad1a675b15206daaa00a2887a7558d5e9e3656bbe83bf25e8964558dbdd2344a0d7c7ac3ce03750bd054a46fc29e8bc11d97c13cc71aae8cf9fcd03d8c784aaf62f39f438689035ef0f96ff406b4e4b98473cce6b9579eb16ec327bca6bfbb6499da22364a10312ca2bdc5b08344e0e1838797d282b8043aefdf2611f84e3d717e06bbe1f6a6357fa453852d4a6407f4f52ddd4537c6982a9e2af7c5901d2c43b1092f0b3819ef564f37a6a04a8d4d81a1fa7a346187a5ce904d719ffc6efb74deea9ff6106e4907577022cb1868ee833b23c5cb608cd7b0fd308f207e4b9491538069991c3d1e00861801efa834f6a89de69b647fcb9e648c231ae80cc31147fadb619e1b480dcdcb43b58c1cdd65e0f8a25cb4e25cea6123bd5ca4b60eca7b0a56a92c4bce58463ce48223e21de84747f308097df3686e91605f7971e1e108eb7b974b29e48a93d903d65bfca9557523a3140b35d7e12bb234064d7dd704e9e6df68785545f580356d1cbc54e2e53ada37445e24b173370a360ade6dd16cb994b1cc358c9eae0e1a2b26361b66ca45216dd374f8f9d13f6b8f871bdbb9de2445afb3b04104b479fad1779449a092b9f154d4302cdc0b02ac5f660fde041baa3162988bdbcc002de380e0bedc3323d07050045dd3c6beb68208850196fa08e75719335321bef6d8bd7a22b784f4e1712167cee0e471223c23a5c776ddc9249a5bcf4a71090dd04e188f7949481e21f33ef96cbc8c2b96db23b55f6f6358347108e185d2e4f93f0f1120aaaad6664dc4079e945f02553df450e07a40e5f8ce3ddca2ac00b6fef4e44e6a805d241a167ec797b702a0880220491597fff8dc17b098f00bbd13516c9469b39182610e92e02899852cbff53c19325697d34d573cfc48c76771155b178a3cc8e56de086fdb830923674d9707a3186b617b29e04466c919a8e40dff20c41baf878c79bb1b11c7ad855de0864a0d5b7200fddc769a6364a32d9c922c56242609419430db85a12518c273ae4f42d96cde2c90d7ce90f47b92d44821743f443ebcda91c62048588b59c67db69e7d45355e3727cf0e42ee7a87382c09f94631427b910681c5330aa82c01cdc0767ae4c9cfae5467371bdd3aeb85b2ad2ce4f864a9db95ce6d71ce22d22188cbe5b4132374ff52f40495c1e914d66e03ab65cf49f79dae419102e5c47305c04e98ca4da7c03ad1adc0ef4e3f841068dc861f24aeddb62b85bb83884f1aae70fa0d6100b6a087a25ae0098821562d46db8bac21b4c62a9d093abeaa546543429f02ef404f1a7d32a343cf94f468bc9798a594756c889aaa314beeff83a9a44de5efcf9ec528b738c7ff559be327244aadb8b67bf8868ea7a808a9767929634c99556563cbf7a72813e570ddcca3b8b6ae791702e38d793270b2426dabf358627e4d78f777969adbe8af345a845d211b3a44fb070cd139ca575ddf27f2d25f23b67d48444357ff888f60008015f90336a8d59969c005e0cbff7686203a95207121f3ec5f691dae75e29a5fb83a82d9fce81cc6e1f641d445380deb97657089c741218ddaf56d3b1c91493041bd1aa3386feef390d8f724f0c070da501787f361ea0be9914f7b08de820ce6d645199cd232cfdfd5df43f76d37ded55b4c512dead744f18755f300aa1676cf627a08ebb10e0b5645493d57ff93381ee7efa7eabca6410d49666e884f89ddb65ce67eae91725133a9cba5ccae5f4ff8c3ddd9374e14e9be46e510c54cd9abe842384b8973010117474ea30c673f0e0d6928b5ab803de511a18d851dfe4253c6ba6182d9398c2443fd5d26954fc1c60ef59aac3f7b9ba9bab19796e229b00dd517d61ba51a1ec230af124a97fd046ab2e9282a8d14915145020663b0634bb8f8b29c0a0e86c23145514597781bea48cf03f300daa13dc0c5256806f8beb40b65fa3f8503a59af32ba7fe79939ecbc315fce4f73ae7d539fc51df5c81f208094012c89023e1bc4920a3150b46439fcffffffffffffffc2836884b6196b5f2699a4d43fb65e9f93292599524aea8a507fdb7bb1adce6bb700705002370ab70a2a0b6783f633402006064ec07808a3a48a09795f762b7b3b7018c25829438599f9948102331e8d11b081a310e628ae57faf7251fe28430560a3955f2e8312999580d1c8330b9a85c29994ed23e98046112426bde9d754e218a0261d07d7969a132cd4407104693e521f7d357d7e40fe60ba2e53fb473d4b2f8c1a0f7df345674b793d70763e86041041737efed2c1f0c6bd97abda71bd6e93d184497ae9efc5c8c9f9881430f063993f263296112dd1603471e100d0e3c1872492a49b12d6b2a5d068e3b20871d8c132f3b826ea7856a1403001638ea6078ad98aedfd9a225a7834955a4eb9a373122c77d1b9cbe2570ccc12c794be95eb864acd982430e5d8bfa49b2f3ccf19a0c573150b0813478607860666046197130fe7552e3a1974fdfaf070e389842ee11d52945d7f7eb0e1c6f306e8dbe9896f466a204898101316c6c1b38dc60125f5ab6e106071bcc7d7593ef53487a13101070acc1bc9ae57741d7a5d21b047567dc020e3598d5c45354f6e3ed48c958ab71e36f50461a36520d1b67a41a957fa38c1dbc7f192420f63ab091032788230de61093bb64954b86771a69f49a064ca08292010e3498b387c52461225f83be41195380e30c061dfe9e2fa4b012a5b4c1e9b7810dfa0635920d1db0251b3ac0097098c17055f24d25a70b950f9571018e3218ff928509db5056bb93c13022fe95bca4c492c818cca9bbb2b7544c072c050e3198a3e536e1f14708cb3a0c869c336554cd4704638ca154e00083e16db4d5271d9f274f38be60f894945f9dd6923eb91a26070e2f985267c7e40f2a2718630ce50347170cbbebefa72b574fff655c000b1c5c3057906a7f41733150000213ac81630b465739bf1b09deb5d7c0cf000187160c2178fac64d9e4923e1c8827135ecc297b84ce0c082c12ec7df8591db298538ae604a5a44856eed2748638c31ac803c930bf1c55392048e2a18c73e575e388d3d1351c1246fb174ef6939a6604a634244106627cb13a0148c56aa74a9724f0d237044c16416f1da547fc4fd13148c21737f36436f434d4f3007a1baa7f3236278de09c6ebeba4d7e72618f29d0863ea7cc276ce04d35dfce533a14e657909a67abdaf0f2b17613d4a302515d45e5e11d2c328098657fb71fb2072b86023c1d43eb69eb49e490e3a828d911c46e02882f9b292522907217663250229824ee710cce152cbbe49eef0d86e091c423059ecfb750ed7dd49b5040504c3a419174f9d4812820c0cc35f28ebd2ff22ed41c55cd0fac258226fe4dd30f5c2242b2cee93887861eaf91369e3ad257476615041d72bf2a90b53c8ad203ba48d94839d0bb27857c8d6a6ed1e63d973f4c5195b3109e38db791831a4b06a100bc26cc7b5d8d7c0b835249453ce7d71686fc513c4c8749a6c5ad85717f84cc979b1626d9ff9d90d35c3ca9b33059c8b8f0105516c6f8adc94959c82657120b73b00ea33fe8242c0c3ff163c40e93d542f015a60e5a34b55baebfe40a536e919ebf9f43c9cab5c260c9266dc6dd5cd28a025861d0cabe52d12ec53dab30a8d9114ac7fe51492e551844a6bd455c3f1559292d3fe975428521dfa7a97e855c21994e610a23fb22a27fa776c9146657b3acbb5ca6564ba530a5ad0cd1331e24a94f0a43f851ba4cedcd24ef4761f2944d5d52e5a5f14561cab75a290579288c7212dd84b8bd8bb183c2e435c2528c2c132be29f30bdec7dee2f1d2996ee09d37e6f558ca4d309e3a5668d50677db23c9c30c7a9d23fcf3e56a9b30993d25b39450a61f683ad09739a8a093a6974084225135ad70861c23023f1b2043ba9e1954b98c56554f6c92c215ac268651523e71257c20c5921e694982961708965563a65ad09f224cc7e966c7410af2a2749984f6915356b3d267a244c324ab456f81bbfc842c22897bccff28ac7fff61166d1eea0ac3ea7a4e408e3248addd908437e4f759e57216c4698e4979658f2751127a1bc3c8408524598724a1059eceee74b4d84513f449c7017f4e85111611eb9172e589ce8f9218730cebd5d9e53223f7c8821ea1b359284b4904298e25fd8ab6f5d8e242184d145dac5f828a195e7208c25f47da49935152a8230d998aa88b6a022693110a6549e4ad47f29b32002c21841cc6bffff07c3ae78ab5ebeaf1ff9a130eda7f4b3de0793e88fd65fdac34b870fc6d221f7e2a9f76048fe313c62877f5b4f0fa65879a62f25793089ffb7d3e21659abc38321d7e4ce509d456e7f07838ef2cc0e66754f2289b7eb60f4a03da4b42cdd4ae12328800ea6ec10dffad91ead3207b387d6940fa2a5c4dca2003998fbf24452c943e26048dedba17f3412a4a100381872342de35b6fb9737a83f9822ef5f0e979544e6e30ac6ec9a888ccf5d1b7c1a443bcefbd9899be3e1b0c56c235363cc6f885d660c8b9f9e94ca538b1e36a308d4895eb2fea6930aee47cf1a9ffd3480e1a4c419c9ff9aa256db13e83d9a468c93035aa92d56630ccc752132929cb099532982607bb6b13e299a3420663878430a6372a5a5dc660f4dc29463c93c560f211b978fd881bb286c1385b51422a3d35b925188ea12a1b694f5f3077aa7fb76a7fc88f17cc4976bd85a958296dec82b992fab64acf3f9ec305a35795aa0bf5313ebd05e38d57e70fb95c49755a305e3a799eb3ea3afd62331490057387e4b69792d2e94fc282b1ce3ac9f8f824e2e8af609219aaa532defcceb702e2773c96fe1155c17013f594d4ff3c21712a9884d6c4b4d27fe94b5330e80af627642b05937cedd25e9d4c27d95130bb752cb3ef0a14be74332f215ee509865b8f717146b632632798f774cc12fd9eac83b609667935ed977a32c12cd721844b30b5b2e112cce925d6de3a68195109061d52e6bcd905e1d725c170fed1c727f9aa9690604ad53152f8ed9e9c1752c01130533119c170a942d07d9db37d9e8a6048c27a72ca257be3f40a208241a28ca88a91ec29e7153004a3a96895537f4a59880284604a9e7a3ec5d472134ac13088a50b297e47c0305d4af2c182d07809fe2f0cd739d4e979331dfefac224b353a530b7f6c278313232744f5e9842faecc5d83995f3db85715de5e6f279498c5917866c15c53a56f8503a940b53ef4e8aee2192880be2c214645e97e81423a8945b983dd6fb920ed726c01606196d73f772511e24a016c66eddbae461e634946861f0cefb984bf75039cdc2a04c58ce963c5bca7564611cabfa4c11722c0c631fe729fc75953861619411ca530435aff6ed1506f9f3a554541235b62bcc2dc122a41ccd3071b5c290a3479a5821ebfb618549fddbdd53bb7fd87e1506cdb8930f2a5bfa5d15c64c4d0941881cfa74a93045319d5efc42a830e9b85fa9273f8541f25cc85cd85870dd14a618e1d44ebf530a8350572e661b290cd1f6dd763dcde8708fc2144edc4589708bfdb6284c7a31bba183426190dfa1e6d2749e561914e9c4cbded922fc09935dce8e16b395728a3d614efd6a3bf2e2f5aed2099356779ccbe179d3b39c3049d4ce92266fc298b7d6313ff5cb6435618aa73aaf6d24874f66c294a447b1d4b09c53b5983025b30ed94a5bf457f412c6dc52625da3a3a65ac2e09f442e91c4f38e48254c395da570e96927344209e37dd09fb2e53909e34bfe78a6e22909d32789332af983a57889846946470d4f199e119c10f7d596c118caf4f2ba875fa64a06c3ac794839e1ef434cc760fe9338b23ea204f5a71850296c961695c530983774de5fe5c748f20383d1d2b8cee7ff0be62496e339a8a4eb6e73720ae2b36c85e5d105a3c950932ea9d86c2b3db860b6d149c731d3cdf46f0bd97552ab164cb26eaa652b5ee66264aca14fc306b2db814716d0f1f3224c23076714d3838207164c65256e94e7b42d3cae602e65639ea2468ab896580d53567858c194f74a4df8ab2c964c457854c1f8d1ffe2ab04ddce122a98cbd2c80efba78318a76010b2ffca76c7837b09111e52307e89119b76f9fcab838228182325fd9b955216f0808239c78b9e9f72f48f952798e5ddb479d0179961999d8387130c3329cae51cd63bc96f341924f06842422fcf621c030f26982485cacf7b48a3a3843c96607a5753b31234069f87124c51547c3cddbc637cc85813030362dce06d604c108210f095f64882073c9060be8e707b3a3df668c97dc1e308c6fc7cdb17ebb81c3f848711ccd142463df582ae24d2078f2218dcf334846bac6f8ef2e0410453e7aa4a975b1a824988bb4e6ff11082e1a4ee894a21f4c54e308c7dbb9643b9db48f081618a37b12e59857e61cc53162d5b12b71c725f186b3ed552c3fc92e5bc0614111dbd30ca8b0e49bd041d7288f0c2fcb7b26f21780a964cbb30d90911b4a4a02da8ef7591e75cd92b9872a1e5647fb612c7854105b311b9c27912b2dfa21c31f2a70eb3a0c316e6be9c5417fc24574a1203033d18630c5344472dcc9d35dbce9456684f6961d02968a8b78549299a85c15625260b53ce49a7d873652c4c3ae5ca93ccbaed14c2c2e069da74ce64e5f4b98e5718bbdacf7792fe77571dae30699af4371d2a3ce7dad10a434cffb8dc69647f7d32f6d3384304638cd1c10ad3f864356bdb5c0997d3b10aa3e5fba0eea49e19e85085e174e8f939c041a331023c0f74a4c2e4329f7b2c8ad0b1827fe84085a923567708ba7c63c5e544c7290c9ee6f24254ccc841aa91c68d33c8404d7498c29436fa417c45aacf3db071c6096eec253a4a614ec173e62d78e578925d1f3a4861eebf35f959234ffe2cd101d1c1758cc2a4c39d48e695547aa92c0af328e195b2495d0c1da130689c27bbcff2f711ae403b40610a955eecf3488aa1b78e4f1824071d545937de0639b041b6c3132695b835264f85d1a771e34590fa848e4e985e23453f51fe196798e0d4071d9c305dcbaec7db0f196bc4522a2303d9b10983f6a94b8a569d674732d68e8d336e9cd6343e043b681390232de8d08439f27ff220642713863989f712a29fd26562c2f4c94eb2dae9507fed1206dd79424a8f9c3aaab584aa23e62b4db850072a61d2da2ed692844cd94dad83128618579683b02c49e19540c7244c62419c4823e37fca27634d67604619356ef40cce6a7448c224af575db2bb3fde8f84e1841ed562112b749f206188233ebea894479867c4430425b28e30c6964e12b2cc8d3027dd2108310bf3fa3123cc5d2b41ae04a1e5aa2dc2a4de2ddc27e6841cd48a30b6c77f92e93a237212619ee41255b54584b94aa5646f7934d0061d87304dfe9027e6892779ad210cca2f0511b9301b3b29842959ff7e8ed157f33c210c9ee3e792131984399a668c87b06d7d16411864e4a598b33810a653eff944673e7c5e0161bc4e3aac94bef422437f30be75886a79b0cde81bd4b071460d2074f8c13cd79ff695ef1d731f4c3a7ae3734c5d7c9b3af8601eb153224da7875adfd0b10793698951e93f7e435ec618a30c1d7a30bb8952ba9245ca65e7c110d79385c8e94e2733e1c1bcf93d16376542cc10093aee602eab8a15254f7630e959f96c48899192b6a30e86a0b23fc71ef5cb693a982559306de95547a890be3107d365f5f021e7b782fa7230460e21fff563bca6280ea6b9601eb7930eda3c380231306028820e3898fbb44d77fa88625f7983b9e3267c7b753adc604aeaed6404a196efe28e3618741cf16f3a5eacc975b0c15c322a5b6a0f95f8411d6b305d57c8cbc147fb5dd5a106534e1777429851c971471acc29b376245af09c67d4810693b89265f1e2d67106731e0f3ab23aad7b3a7598a1a30ce6da8e961ef9e62fcf64302755af9274cb8ba5948e319894a5d457d9573d9a5087184ca6e25e59f2bebeb07784c17cc97b84ea297580c1dc9e6309d9d1449e24ecf8823154dab0681ee75e6387170c42f99ce890e30a7474c12caa62c988ab7570c16ce15497752a756cc124776fbc453fc2850e2d18af5ef74d05fb3897ecc882d9c4086175fa43b0bc93b156b2091d583078593ce541e92041d664dc681bdc48cb29745cc120b664bd8fb0a07492c6df60247458c1249e9752be824856e111dbbe362263ed460dfc8c8b4207154c4ae7f34a7491ef9b2103c10180091d5330c9deee284b21edd26a850e2998d2547ab31ca794fe3f0a86ddf2c841c72c31f291a1030a26a12a28efef70161d4f30d6571e7d227a6adc103a9c60f6d4a347848409b9e464ac1513424713ccee275e4b54aa850992b176b68b0e26984daaff4eb0cba3bb97e139406f63cd3a96607cf5908210da11a14309e692ffd68cdd8f5a0d6446d748c3df868d331c2f0523400108c6188334a22309df46d01e420551714400f0840e249842fc3ec9ed18255f54888e2398c37b12570949449f91fdd0610493d23616ef2f8fd277923d7414c194ac2e2bfdd9b67ae8120f1d44308b4851b27c5e8e4a7a1d3a8660f8d7c8edd194086b4f87108c592167f4ffb94bce81812318c6eb30c2e2538255489bc0010c773e8ee46f512612387e61b60a539dd86eca26cbe10b47cb9caab16ad4c0cb40a30c397a61baadf06217c6a485928c355e9894f6961c612732d65e0767d84083ecc2fc966cb764f9cea582347270460f1a0417c8c097610305638c91460e6e705217e6fd78b23d89989eefb3326ca060066694a183336c90a0ae56c0910bb3e849cbeef87061f25c953c67d3e9d3ad5b98635bf0cbbfda41cdcd610ba3951ed39da487ad7a6a610acbf38e60e1bb744c0bf3b55e165521b48fa924143866610ec26b525239fd39aae39085d9de544bf4b2da4a411cb130e8b855faf6179f4212072ccc398654911b59626285e315a612ad4ed656b6256b0e5798f4a7f411b4b3f5c511472b0c2ff973be37915cf671b0c26c2f42eca8a45444876315e66d13fa626dd35694f4c68d37430f8d326c906abc0eca4011b8a0060018c1a10ad3b6c9a7f6df6eddd2e04885f127d68358c5b67d97a83064b94f9f2646d0959392e31486590f49e280c314e6f46b4a59104ff1692e85397ad22108f70bf9107c3446200606cc18811818e8c018637090c2f8f964a8a8898ec214d4d57a5f885887958619efa2c83cd6fe509834b6d4723c670f7080c2587ad243d076291f1232d6d0b6031c9f307b780c612d2f7a47ed9e30a5bb1c1fbd7119a70c393a618e3197191ec2bde3be64a0208b15383861ba60e931c7b38854934d18437877140fe1ff276263e0d084b9e4e64f503365c2144786b22bd3bfc08109c32753b1d2449aabc0710963aa9ff097fc1de32e352c6132a1449f0877f996304725cc5f9574ce7f2ea38451ee2eb6fb6dd44426b1fa9998ee08faf693b1468ac02109e388ddd115f73f1a9130868cb8723a1f27fdcd0109a3e9f0ccfa0d353a35ce40c6f10843d2ce12ea16ff53ce7a60e3735002fc0087238cf1d9b2875ce2137034c27c65daa5df7eeafcba4119c8c1089350a224690ba14347ed171c8b30c8336d51fab4894990220c23f52585ca3825a3cd011ac57024c274255b95c389c958eb341a751a9d81b3438439c4d39c471fc958db47b7349694868d336ed80dc26883594250dbe1c43ae80f1b4c6147aa4593abf1c1b20653f64b951f5613c7623518bee3dce28abc9d531a4c924288cad0a2c198163d480b5b9fc12022e6b283f97d4a9ac110abd2fd7bd3329854dda4d86d12321852f27ca3151e4f98c6600e537fcdcf5a621783b9de47fe23c9ffcd1806f37cfe9f705142550583d1de4b46f768651208e30ba6204743ab68ef05c3c758b270739bbf6417b0ce26d404317a4789b96012c9ea2b56ae101e4f1b67e409630be6533284058fe4215ed1824988faf82d162b744a66c11027cb4e106e95ddf5c0461a663c89411858305e5b95eafa3afbabac208c2b983a3c5e88e65ba1d5b182f1f397d896ac963ba5aa60aa160b299274cba57c2a1854851439cfc147dd3305d3dd7b1493659182f9530a2e63428d82e9f4d9aa7cdfc51625148cb17f925372fb1df5134cbdd96155c24b92f085e104539ae7f5b0939d208c2618648616e126526ecb1e0cc26002f6de273bff787f7210c612b09453c4f7ca49fd26c377108612cc7d236e7e157ebc2c1784910493fca9d3eb9d173f289d9186191d20411848205dd6da35753a0c8871c836d240037b10c611380c23a8f9e93a240f855104d34e6e65ec056da4814618443098dac9a93a23faff52e36d80c60dca40038d36e30c33126da481068631041286104c31a2f5f3920e1f3a45c69a06c6182318a6687d6e23929c6c42fc018c740827dc7d2f4670f0f10b435061ed93529523a4f6c317c6f413c9d94ea6ff5b7a61ceb7f0218f5e0f0b3f2fcc25ce639998eff0b6efc25421897cdd7186c3872e4cb32729ffc80e2f415d7261f6eb38aa53e592f4d4bee10317862826afec736b753bb730974549410715a4a6986c61aad634fdf079265abcd4c2ec914ad6a79fe546190b820f5a9874d8b8dc7df92c0c2a7fbf1fe7e9e2c8edf0210bf37cd995ca444b2cccb61e210513172c4c25b994b6607dbcc2b415f9b5a4e7ae305ef0fce941eb34444a2b8c559b16174197a710b3c21455a26bcf9fac45c9c72a8cb2f19fb2a8bea99c25636d35f0a10a735f9a9790162d7a0eb1c0472a0ceed9f49da77b8d7f64a4518619638c199851061d3e5061a7305d4e397448994fe2ae0f5318258e38f9a92b87b7df818f52900f529852860a9d6db370d9761406d7d1511db449a9f0210a539e3d95f2b89e1825e64728cc215c298b1eb2a030b75558fb089553840a0d1f9f30ad87917849a924af2f9e309b6993eda4e2de32a265f8e884794543355efb635cee8313660f3e3a8470f98f4d98ba93c8ac905532d66a70a3d3a8516ae34313261d43cbeba578e1b3f6e02313e63d19c1d2363624e8c607264cba7d9dc289741f3e87c6c7254c29e5cb49930ddecde81b669c618605c618037530c618a800c0c587250cf249a9eb590f495755ad84c96425bba4bf38418614111f9430ede8a5511d2d859c3a0e1f933024cf8ff6f24149bc21b14f99928cad8183bf71fc1109d3c70f22debb95c6dfb861010c70400c0c8821821168a007e4111f903048cf8e2621761e611295b91a164e7bb54a8b0f4718ed3bd88e078f7a154d679c6183046cbbf868842145f21073f11da45a186110f727721e7b89f7e5224c4129ad7f31354518feab5a23774c909927c2a4cbe646cfafb347bef081088367f7202c99de05391dc2982ad142fe4ba4da785cf830c47d14c2a4a56321f444de9e9d091f84307fae3c158bba222ae4c2f0310853b84bb5753da7eaf282308a7653e13c53d72a1bc147204c2d9e1fc94b8aecf00c3e00610a6d12dc428c94feb7910334ce061f7f30ab8974fa6247c6a9948cb51ef0a08c5484061f7e3045494269fe763efa60922c626fe9bc2284341fccaaa6246509eb88f51e0c1f64549c9fb01ecc67b222f2e7c98339c99e252d953db6bc2a7ce0c174f59323c7506175d28f3b98b3a5eb10dba10f3b1896e245855d4cc6da1563c1471d8c15cf4c7c96243a986c52994d1a1532d66e2033fa0c4f061f73302595836f9c0e231ac21ea45106213ee4600eae5931b392ce103a7571c074459cffb87e95ac81c68d3a7cc0e1f5eb9873667d51926f3042ce7c3593967a4fe30469860f376479e27ba54c64d369433733f14f48bc78c992870f3620ff392c8d1cf93da99e1a3ed690b2a85a97730a4a203ed4905645f0f865aa19c23490beb6f78325870f34a0426cbce362e9ec1952a1ef74e912bd916f0d1f6628ccb9fccb778847e7f0518666b7829ad193c2a8c4c08018221003052010030377f82043aa6a219e923de1630ce82efdb394b23664031b29e0c387183c73dd0b27bb33d20b43aa2f1792b42fbe3330986639463b2baa46cc8ad9ed21044be1e1e30b065162559e23e9875fcaf80037e1c30b0695b3e4eaca5df615ea8239a8357517a16142ee5c307ae9f270a65aa9287c6cc19cba2d651bedf92ab5160c9264f68d05350be63c31dfca7a7bed2c5830298df89aee6123c6750573be8e2ea2a276df3d2b186c4bad2aac5527cf84955f86f041059350495dcea65bef43ff988229e74a9db746968241626b8a897a11d1520b10a2828f28782962425a8da806cc28a36d6440a16008ba744cbace4eec0b0a1f4f309dae382295b897f0e104939abd0c611e84c930d9f86882c707136ef4e06b50238c8f2530e0430908f84842033e9090808f23dce00602f830c28d1b6f038e8f2214e08308370881013e86501f42b0810d1a0310f00886033c8051a30c342c40820f8f5ff8e2d1195e2307bd3880072f6c6083f60178ec82c3431765a06181303c72413c702186c72d08e0618b5a24c08316350ce0318b1a65a061811b3638e30c122cc043160ef088057bc0a2461a08f078c58d2fa30c342cb0810e0f57d428030d0bd8f81ad83863056178b402fd0c66d0288880072b6eace2c3431509f048450d0578a062001ea73080872908e0518a007890c21c2ce6e7be2c29d6ef0a048f51182f646ea5e4c87fe692b1766383e0210ac34910d533b146e68743612efd1a4943e5010a939795bea825f4f8842188fd91952304356a2a62a06003c80c0b8ca145f0f08441447a4bdd0a69545f88e0d10973ed6b856fcf17365f74c605dc8c1c68608c1f7870c29055b4e38ea4cf61271e9b30d7759ce84e5ae5c08d31c6f0d084a95b2e4fe4f4f1eb7c076760608c3170e0910983b25d55cb63c00313864b52dc7c3b889f1cbe81c7250c49dec48a914535f0b08431aec4884e1da37b66b5107854c298a5433db59f2861aa39cfa5b233099387081e1f4aa83cc2968479c4c4c68275f49461240ce7f9644e529ed35307097358f12e91bbec3995c7234c62f27eec43ac7a3db081027f1d98230cf9449a96f9eed108c35c3c7d532fad6e71469894322daa1ed4a5057711a6bb0fbeef29238ff03c1461b2e8e2315f65e75d7924c2b4399ea59727951ccd0311e6e46e2a4832a14584ef3d018f439836e5c3488d943394da109efaa8daee17c274263f8f28bbbd9315214c725ff9fa3482463f195c1e83f0f36c49ed3c046188752a0e84498a560c08737b96f18963c2e61a68dcfdc1a43b82be9b14fea94a7e30e8181f6fd4c99cfbc4a30f06655afcd26ae8755ba306fd364eda7af0c17cbd22136b2f1278ec616d78e881a453120b0f313e66f8028f3cb89664a747ad8ebefd0a3cf0606338c2e30ee6203ee94f6ea2ab82871d4c7bb22fb6f809cdcfa981460e6ad4e08c336cb0251003036280400c1480a0e8c0a30ee630d76e6b13a2e731d1c17c67d7af1e3c8f39182f088ba24c720a49a1871c4cf2f9ab3a2996a59c338f38a4071ccc39b669b57791c71bcc7daf6e2a2fe8f627b3031e6e387d8affb63b95b6e191a4d54b2663cd6dd4e881196d34f06003713f69d3b6ce93dca0460fcce81a37ba3230c618376cd448e346bb193b3803a1c71a4c6fb9748732a9c1aca743ce97143cbae9a4c13c729d2a4c84892829683097aa8f92e44b1471ee194caab2ddd26a78bfb966305f4dae907a30a9bcf72123e6e1bab0248456c933e1c19c435b78cf967758558488d2d9c15cb2ab73964f12ecc63a98828758212b6685db74308baabf8e16f2dce53998e279793cc92feeaf1cb8582faa447fe26090951622fad4a8b41d1c4c4905d7fbfe10cd4c6f400303dc601cbd922d0989b5c1f4e5397c2fc60643983753d3f521a4656b30ae88ce3ad594d3613598dbd4bec47f7acf621a18800693d07be94756673044cd0d8b55fb494290016678bf52366de16bff736528cf579678f82af7c9603c393975f544977cfa184c97ef3987cac8adff88c1c9f1b63dd49f9b1e065318e97a39444f0c910306639d99327f516ae4ec2fa47223e547574b222fbca572178c3e422871cb664a81015c684f655d4656ce16ecf3a0f447bab72d5d0ba5f39c6654d6d6fecdc2215bd6c8cd8b61420103b080a7d71c193978fa5ca1ec17747c39252b14d143845827a2df2a5c1e7794120b217fa860921842bd6be9cebdff144e7edfdad1df3a2978c1647b4a416707191085435ab38a58595a3b28607f3259d4e49ff8099f095f1d8fd59d7342365676a31d235e450c6802327475989409e414ecd4bcbdc29b4b40bde3ed56deecaa90014af0d2884a27843a71332009a59633292a0c4002f2e3c595fa08c6d1972c45eca4b4b2114cd1f524c50929025a548a949245b1141b110c42c9f814bd7df259870143b83d82bccecb16b34d0c10824107d321b32b899fddc140e959f809d3253960984388da294bf40b436eed911dd717e6f41c948efc25556a2f8c1bdaa176262fcc1f734df4574ef1bbdd85af615226e88aab31e922bdf1a32d5a59bab97023e9911b5e41f85db8f092ef9f0a8d6fe155ce3cb3493b3a5b90e7f5926ab530d8a828c172e4f7f8d0223b1d2a84d12c0c415285121e4c59a43a480b1553fa7b2c529193fe38963b395868a63eec45ae3a855f61d2397c8ed45859dd15caa46829e47a044b2bf8be5049c9911c2b5afb4e6a3f8818afc289a9754a45d57454e1f75bfee55829ea5498c2e6a4921d0b3d294385c14743a7687f293f7d0a934a21757eb1cb7e2f0a0b30853959c69c8dd2fb29924a610e93e396fd8c07ad3f298c7edf4994b83841c43f0ab3950896c34b4dc4f84551bec88c89cb6b288c236eb3a22561541228cc412b451555a14f1892cab7b9e81f12f5e3098350d9bbde743e7afa74c214cf6d5bf2af085d36278ea434bfbf4cc90d954d18cd752d9c12b226cca73b2cb4d794fe8b33618871134e998e1f36c5c47a1d2c7c76a77bb92f61d44d3d6521a7a4476a0bb0844949cb4105f949859754c2146a65f2c84b29615e8b2264a77021494a27815097f36479fa699230473bcf22e6bafd4a9130e48e94cc2f04a57d26244cd5e7c1eb7de7477f8471c259289323c62b4447742179b776c5d04a23383bcb95ad14762246182ea2c7bcd32dc29c6d5bde4f8a307f9555b8f6dd5b3711a63f53df9e3e879e1043c42bf1b4f34e0e6a3b4431a9bb5f24ba7c0c61f48a704a843f1d6654089334bb4f2a2949f22d5563c1b0004218c2e85c219b31263b6510c64ba373d2058f2823fdc2020491cea8c9d9de06820f12a2e7ea7ad30362dbeb2c1ee1dcc72e7f5075745ec9048ffac12054bc4f1f9e758f9b741525870f86a0e452b22e8fd41dc28505ec61ad2d9110947a3858d03e696363ed3c984f279d922aad9d85090fe80c2dfe3d498864f13b20d2da32353b989386fa89abd317aeaf83a9374f74a8d27fca79d405cdc188223b25650d9197433237794b88fba97148a86ca9f0fb210d07934edb177c674d5aed6f604288a6748aee92f2c80da653428a9c644adb5cde0663a968699122365c2aaa04b9db1accf9469efad8cb97ac06c39f48a583103969b83a42b85cf30a42868682cf758844abaad319ca67deb1b47274a66d06a3e5b0f859d75b6997c154414b87d2ebc89029b12be93194b52fa6a9899dcec2627075c6b32aa4340a83414e6acba9d34c4f70c1706c91ffa6fe73aaacbef004b112c7d2f45e30e55877e99d4a871d912e6031c4ca88a813ff5f2e9c75733f29fdf3d942e761f611625eda73b5902e2fbfb2c05fee4fdee69d16164c61299994246bd7737905254553a3c368d10d1d2b982d455d2e76a4492b56613797943ae8a95088262c23a94e5f9a42e2af33e3ea2c2d05d3e94b252feb8b422259758ab8bd408124bb4edba40fea53fe09a674314b926c277095527487f0f14b13cc2656742cd6ae5a9609860b1aaa4f65848fb3b6b0802598c3cb271de4eb93ec4a30bee59b28426c88d1a62474399ea1e90290505297bc69c9439ab5802318f3928909cfed93928c6038b3f8cff98b6048dda5eb927b2298820a226b5c12b1da1e8249567cc9e593ead287162004931e35af95b907031193456e74c0309d9874e661ffc220bc4e47febca4ea7c61b6cb23ed428d900b61f4c29cce4a5744cdca5e7861ee8bd61d17f35df8f15794c812d68549a84baa77252717a60b49b2246dc28529277b95ba5ecea2ea5b9892127fded52962a98a2dccab7a9a922b5c0bb34dec54c14dbc63480b438e53114b446761d2799de593c3b996b228ba08eb148e852a13643c448717c1c2bc13b322aec809dbf815a60f4946da89743922ea0a5365bb9c9cf89c2bc55a61aa5211b34e1d46c594156978bc8c972454d7ab30e54ed172c98dc892a20a9398e0ed274723473415c6f6cad3a67f54186b52fa58e74e61b214bd6db9f4fd2a628af7de53bcc25218e5ef629e5d69e47c92c2789f5ac9e36514e6ec14bd7f3d8ac294963f8c94a82a59140a7d4ba48fe381c268ee6945825be8b57c828b5f732967957cf27ae21e2d173d24ffaaf0e984499f595b291155173d4e1884458be623b409936e957a50353bdfad268c575244c676eadc936782ff2a516d499830a9bb49f95a5498fc12bf889b0ec94410a9f49628ae47bddd5abba412c6f932955a8424fdcc28717cd3218e9787f52661d05177bbe32e6f7e4920ae3e4685302281c93ecb275de6d49030fba85239151fdd6e91b1f60843bec8a62e7e658c46df00c370049eea6147dcad2935824932e691bdab2a8597a0a1e24f55901f7ee5a61d86c10893a7702742bc24f36a2fc2e83977989a7db310175b43188a30277d229e68b1a04350224cc9f3bc696d57080311064f41a9e77ab50baf1cc2f4621e57c3574318249d909594c8dd33b24218b3439dc97a09cbde12c2242c5bbb6ea73cb9a04198b2754f6586ba76d205611256a35f94481372cc0361943c4997d5a5957705100639a264d49af40fc6d1b553f14ca77799f8c1f8b9f6e1aa651f4c91e25f25f93a1f4c49a848496c3c5c2e59187b305ce94e6a4bdc30f470505be16ae4e9f82935100d61e4c1702ef36f957e9382e4cbb081827c4018783068939fcc4324a5aa5abf096e9c51469211c61d0cb1f77cf34cbb8248dbc1a0643c3b9c97e585b80e26ebed1cba5e9e1cdc58d4811f844107931239e9fc217ba553a13998fb23cb049192eef33a0c399874df53437785187799a9851107a3e6884923adee924e8283713635df82ee1094fc6f3024cb101e1e73371843a7f0ba038d5ea8f4af6652612012070402711c045248f51e00c3130010404078441a8bc462d13c0e85e1071480035228245836301a241e18161808842351201404844382502010088541814028240a27e196ec03ab41f74a5cbda0cf38737cb323ed8a86cfd25657810116da0600aa70ce296e6ae67566a0e70b7654604720f737c5c02e0d64724730741c6dd034c8fc50bdd6880191268b4923507cf74d766582231df89f0599770a1a4c373db28b2b0e747984ad00cdafcf08c0997b5e6e6eea267b805f181beeb6aac9539bb444f24f37fc683c9ca949faf447fea535fe479cd290c5f455fae73b6e1d5cb06d98220fc824eb376fd0c1de1d4480b743ac6e74a47b0f15e183e4091e968a808de6f2541c8e584911c341ca0bb86b6c938d8b7c3c2b5b544b4461523983f31de5922592fc69c79a4524c8671d92de4c7e09eead39f5d4bfe1a94df4278e6c79a33ca999541f6dbce7ee7fb86a418d54ffd00120f8140dbc9d22f28d7ebb9f1dee3f4a15f72cbe0681c61d3aeb4ed422a08aff1d39bc1c530481076fad49d9cbd92fe58118611efcd5e88ef6c36e3b08c946c14b908e148a6dc8692dcdeb6d7b5af9ff140f940d20c69f84ff2d9748fc199f7c0bb77bbf8d53bdbeaf305628eb7dd3512db1809933c2bbff7a5fda6e21c36a472e0fb4d899ab41175cdde6bf844c934671899fafe9b6045225aa6d60af4015ccc7662a87ee9f1d9487cfe553ed082d1c63fd550089f109f0a6476ec7a7ad90111def3be9d273a26aa725beb92225aeee3fdc6b4808250dcc5ef550e2b79094d8a6a18a5a039dc6d8357c3774190f03370365a7a63c1c251171ae9748682c174f23718da31b7e8203a5cb808e5808638d4d6dcc72b807b6a18fc51b26e5a233c7511c35f57d8d463b36ca40ebd4c484661f9c80837d1b25c6513ac446e73ed07f0e9d13fd339a4114886b7d702ba65328781a479cb5bf8ce1ec300d47a91f363af189a59d4e56ea9f7b9ab99c439bd905f5c0513afead53f1b3046c811da319a4ac96a56eb9ed2007c14672098d6710e0e0debf6e209b3fad0c9146ff7e6189d501df8bdd8147ca7b8f21e8e05ea2633757f4e6f3966859bab60e8bb5c1be05cd7c762000b61b38b6124703725a8808cc2d040efdcba015185262701af9e67ad7d2d63104284feb64d6eb5f0993002109aaaac1112dd71c4e6e0b4bb5e4d97cd9d90ceb3f0e98260ce2bae69033045f53409dfbba11a06654e95522090e53da0559ce056640975de35c9600f50932ddd94450109a84a94701587bffa21592dfb9e65012a0fa4773ae11140ebaafebe8fd28132685d21eca13d6a10f9ae18ec94421dc56084c35c66e1d1eca59547ad0fc0ec170bca1abe70d078bd5c17ed7772e7effbd63e4f37c520dc8aa5b2a5dd5887f3a93080e6504827ece1014f80622f0586fd0d16ab76c4055f7ffbba49c96edef3a47c98e35851308cc6ac01b602cc3fa2f23a0a7e6a87f02ed28826339280daa37f1d8ef6e28012ac3b7992e2db11c3c4767b0fcd54f9a2def88673a7a85ad3d44afb14ac087e5771d3c87903115f818a8250e062f6d1ad0383886173cf08a56cfa2c142da31174c58dd48e3914fd2ed80b49b531898070f7670907e822dc10d19421bb4d67440104da63b2a891a3ca4d51acf651f3c71e361870a357836b71da9ee74a069a5b6fe107877373c9b81ac41405283634e106755661a13990f1ee43a58befc278558c50436c5fdb45d569720729a7cd112c1206b628641d3171a19641d5baabba99dff00902c5ce031d27cee7732841421211974f0002e3e1cb08387e50c1a123dd7dc2192f560b40032b1c1f30413780ae0e0715db9d5c76af008ef83c6ed0f548e6a0d9ee7a387811d6507cb7d0d40fdab60c7a3f11fc20d9ec428864318099b40e6b307b4583b067722f1f9925eb156d5cb4058ea76f8be871624460c1a0669aa32683c0155cc4212c68ee001f6e97a29fa702c27bb2822a855719d837524ec18bb7678848d649a3dfd5dbbb5ff0073e0de55a38c9edb7b04a6dd12d71b34ae2dde6aac54b5f57a3d1efb775e284666143f458a510dce73dfbbec702304ab4c344334181886046dc4a1c66408a01c8b2d70ae21424e16835246a5346d0dae1894169478c33b565351cc4ca7e176a8e929d2614cc0e98350313f9e1584987a8375820e80054cd8f0c08b0e06505803113633f98021e3e088628c88c30ad93daa20163c484fc40afd50a4bb448f3255450e5a401a0ccca1b83c4e32bd7500e94f16a58d3b472e6c8135694c26de69913c7710d4a8ca034d91f673434fd44fb3e5691d2add2003467379045a5d75f175d3db4231ae012afab4ff57c033658c6d6d4f6eb167658f7e374eeff55ed2110f629d4a028503cb647c662edf598abc8f49fe9b384c8d9fd8c05cd5af0d549d3c550297945fd5da01e83faf6967c602eef7ba0baa36279709254d0da59235a64a31b69feddba8ab7b6a2354f20fc11ebab08f3e441faf066b10ad9b25ac15ca5516837a0861300f507c4e244389afa32cdffd7e37eb3294eedd42b1644ccbc68a20f37635b74cb661cf4e9445865d99cbe342c009b91a06000b63ff24116ce487b11a992c81c9b9db81761434f949930bdaba7bbd6c038d55283df3271dd4e7520c73008b4737df2c31aa1888f3b0e1878b1cf0b921327884844bee5a714bccac1ea25bd7f0f11e1b6e82f1d96dbe6a0d516fe8d1912c35f05a0e75b787ed813de8600d653d9c6b17aef7d2fbf5809de265ff366719869ee80311899639a26019e509f8151c866ae27e19840b2463f1408e11784a253050c4c141771070e9670e5cadc8b03d1c743106f610bc51c4fb0a2262485030b4fe61fa81a9fcdffce8654d55a8bf96213db4db24e8f92428c50414d5f7ea1db0927440a7dc31df51da2554386a7a54a906b88bc36eae965fdd8d8657d007f771fb405e35798557e9a6009aef56e2fa71caa281d2d71190a7f09990632fd0a90bd39bf0d50cf8b1a02981f8f9c18c838a061083a9012486c106702d84a56ae81d3e989721f1561cb282b4b0546640a1ea5e6d8e9deb6e2e06331afcc76b697620892e0bbe5e4b2457166de73ab573f8611a1f80b5d6c422c073900b3116aad24de0f3a0acbf719c3ea3988b7180b8014c160535c3871bc6433615e2ece25ae9c9197d090c624b1fd1c0c0dc0a8286d070801706b12c6e587ff95a8150da4896b50d079916720ae98d2bef454ed81c969944bc4257c8bc372dd3d215fca33f816fbabd579cd2d0d56a60667ef624413aa08a31415638ea0526713b34d30661cb537620f9b9eca9e0ca09059b34e11691a16421408d55bfd206f0b868ebbe6e9524a87530e7eac8c8f0a3b3d3c0bb8ec320c000eb4b950a0f5a89df0e3def45b0507e33fc65716c7f25654279a5fc2bbb791669c662cd9ee4aa39ce62389a39c175a76af872761a0d7d6ba436037aa1d7973c62eda5b1aba73eda03bc29ad35bc4e73cd0d34fcc2344ae4f6ea2aa4ceae9b1271ca760f1dbc0a03bcb7fcf552b02ae762167f9c545a6a41dc9e7235f35e42152451c14d5d2e2473e2bb1a7663b038cf79f831fbf1a41399f39950df4cfe176f8f416ac5a09f7c098a38c98b3c927aa2fbb697f0b31de0df2935f1839ad316e184e6e2be53b26e8ecdd88875969f73193c263885ec0f809f6c4fb0f29ccddc163e0a369d4e211774edd66eca0ba7a4c3ed95a8ae40cb70c1c693aca14c43ed386b24c54a524893515afc1fe2048b2c806b04fe8bb54d320e34542fd88ba4066eb37e8d2dc3d1c5c0eb4aac5923d9a76f3f610fbace67f550b5203e31a6e93cf4aa2ceb2eb429338628bb8c8d74bde9079364008916d853126280f980d9dd74e40d3449a65c58bb6e46b691fb52791582cfc56098ab40016e046131a182c29043533120706c40423b3d7a72b4dd417623d49548ae078f503e39425e044db78c0b65a8d5c12b5968e9c4897ce33b75f273e1bcb07ba86b3680161be25abeb05cb2cc30ea5db9a3ae57f41bbe72e94f6d8cc3bab49f93700c6dee1444990c542a8153af88f10494ee60e068957e8b5f945f483b67ebadbff389931e049939a0f6dca66cdbcc92fc7af95ad9d400f09e415a5ddfaa0ab5b495c9848644c8e68e964b84bcd453f28e142b58ac7795d12f3f0f47f20d966f0252cbe851e5e4eb152a546cb880600923287414314b9bc651bf76675cb6ad8129fb192e57bd2a386935e1352ee250d3c45bc5b02d53f201f0dd3199001ac0c98be3486af99261294008cbf83dfcf5f828e8867b750a5a3f6199b715c2a3b2e9fc78cf446b11896e53a15d74145014079aef2c1db5058166d1039dae12685e5f4e9caa0f973d08ff0af43e3310cc9a5e573052ff7610ee045b505e49106ee9dba0a4070bb03f007f417c967e3c455356bbfadf7ec49e47a40c2852004a5ddb1ca97bdc04e43cf650f8ea68bacfd829b8c90f0c9b24186e76fae090eb90a0007ce265734afa1281e43bc97fd59f05333e36138d5b1e2f802f83d18091015291935b2a23e02553970a05e55fa032c6cd4580f27fbe48db17a7788e5ee6f4074a331de655c63453a71d691f68a42db119e9c977de2b076dea2d63df8373c0090794bc017fd689b3bd7bb3205c9c8b9b60ae44a9c961ecb7a156be601ea334a873a870dd16fde4c2239bb8ee4788ca3b839c18cefa2c59b21f019f0b37e30471098be5daf339f81e07688ebea1e0a691e6851d422d3b74aac8d33de2ec6894006daddeb1b0448d6165971ee04a4bbbb7465093ab6950bcf93a930e7c00f8356e9d1bc67da851e07b79e39a579b39caddf4bda6333dd9c46c2a72cb2b034b261e209c301231cdf434b7e828f2c7f4464fd6cde242445cfc43c77ffb02800603bbe3c6fa133f04203b8a7d5bd1cec0efee9dd082435a3fc13bda89336040fa736b11e51fe7aa5ccfc20a6507c42bdf69fab2221e1e382593c02adcbf45b5add7a506c92e9ac02f51e620d37209ad315b59ef012ad22d3b3bb85ae67a4ed1f1a4a12c901c2549273db085365ce1f769958c7b0f1bc64a66dbe8318deabdb8986dda60f4b6e648a3dbc21263225b8b325402b39f406b7d23937360fa309a5bb68c60d1beeb67800a3100047ab552c63c5f777bcb8160ed2d0bcf74bd604723d4fa42f45b13874e9e95186c43835b3b80b5b1028acec69d0f71325de08d5327e9d8e0009896f340190fc7232b2991e1bfd1ff4133eed2cb74ee46c98f7b2a170847355a911f9cdd4c6c13ce38cddf9f9da12caac149f750c72238ede31641f0183f2e2bdc266a4f1823b01a888fa2f31316897220b27bc7461daa80cf6fb489f53d9f93ff6482e016258325523d4ea4d421566ed4384a3db5f5d987aa9401250734f5b7352f0d87befc1dbe41c92a4101e73a0b485923ccf99520071d634a14a8ae50d44d118de7a11743a78e936444da33f07ce3fe6a5bac02c14ee24de0752df349db8d0b2c2ac6960b8c58d3338be231e3a0a641c3a8326240b4593cde9804adafed63a74f0fd12b1a9ebefe57a445a814301addd9afe8522aa3f6f7206153ab1e480d043dbf52f9057adc890c5942f3801b6852d1c845a1a404a83efa945e3860990f826ed5a85574fe5182748320a55046810e52e0494a23c9f83c18c71a462c071b3903cd222be6baa84c8b9e44de1be90a54b6ddec20ee637e4e31337b734f05942b1e473d587bf3a711b1ef22391f2f6601cc8ebc6a92d684e72b04a1c99113ba37d665d60d8969bc13a5ab1da250b79960f23c0eaeb799deb659c22b917ec462a4f3b1a2b332cb16885033f106b17d04d12d2821d354305e6d04ad0b3bd3fe31488089727e858e118a5882e59628975ef7ad0529ad1d58b22b32c00a852d7d66b26225fae6fd03791dbe0992d2079d71b5e93972423cc1c3b2c4750307e12b136b6826583a11450877eae119c3ca6d26d6660276bb2a2d51445e14743b1465846471ebc9a8820750ecdd4133e7956a89034b7b6b427c53840f5ba764501366f91bf26cfcd4f040c261466dbbdf1880781125f3c2a946a936a61697763341490cd65e0ebd9c712e71224408a6210b7762fafd5faef3a9a5871585a6e94033c199d77f8e7989771fee7191990da6b5afacf4c3f5c67e0fa39af96315f44d15a02fd8e7cfdb37831e87e930a81e882b293284689ba1a5c3e7126bcdb4498c5b045ea120e4abbff1b4eb03c1799ab56e1155ae0fbad636244d1a9323b50fbe84836e19a98e52b5a490432a88d2a294441f4d16d1ced6ac6e02040fc82a20cd1b63b2a87f13c12441a199da27b8da08a9822f645096078ab13d8ba83e78a970596418829f786a24274aaac14ce13bb6909a26a42ef5ca50e4af0810e51f85df0ae9e7e0f9a9e749fca9f2a7cf816abced80cb722490ce22b75e441342ef96ca933641dd02cb38aacf8820ddfd85b9385822e5bba7ae972a817df8a66f96db2666e915ae768423ec88f188968a321d62a8178964af08cd2e104d774a34a87c4d4c00fbbb3834cd3a8b2626b0a015b393137efc39fd82249d0b67ad09043473ed2316bc27c142f254b0edde9f1805ccfb2986eacb51f67499b124b08405fe7f824b6b39651a4f9d9ba8753a5b525b5a8ac00b4ee70f035ccd38751480f8833a71680278183e8aec9d776283fdcf445b71abbbd05a6896653bc7d651ba5c680841eedc27a3266ccc67411c0b458d67f63d5a6daa13f31b62eb8b55f5c155ac30873801bcda2c0c6726a64f54527511838329578edde956aa40ca0d8db018d20f5a86401b8f2f8d5daaf7edad1667e97da463b3451f9e66606912225bf228fdc820ea3db5d6baad7c2905e8c22a22ab237645cdaef6db8b36dc9dab536a4edb4b8fdb24f3f250daa0e47cf7924e1a51b890ba5d81fba2d363dfefd2e90bc52a74320796f53465c2683f8be470ab5b2739c76cd445a8f2004b672115ecb8a44c7e29e2a04b4b5c07a8201c512357b4297056ba1eb08f706e259408c3fe2778bfe47e5cfdb356dbee57a0b07a26938e43e3c81147328375778c26e7706c74920e3f8c416dc7fe525b4b781f7de7cdcfd38649ffc9e71914596cc2a1a8a9b1ca11d31346982a5e898880c5dc092585f19bd2d171811440a85995b8fde6b4b1646a1e6557e3f4c95479690c1357fda02fc43e872074bdde2f020ab9647e6c9210bd745e1fd1f98974459fbd8cf1eacef6fa9b5513f524b8e203631c4d520f7da9f0f27153f976a125374edcabb5ee2b6024c33b19fc9fe2bdde45f3619a50912b174cb0603c186e61e065092a62f25757e7a7512276899aeea41c12eae45a019dcf612222d2c1b0d273a91d172cb93288c354e1c76293fcc7d6058169a804abd08557db0a5ab1086082fc9a4b7a74bc324dc109486c8778003825dc43babf0f8f04e2df4c69863747907ce3acf49944c82032de235bdb5a32fd364e9f13812a59aa92c1471b97eb3374791cce52a1e011231eda1f1b87bfd4b192eee36f40b89ac4525f0803108f2738964fd711cc844025c304b5d48020616223bdf31aa4dd557ca1931068551223374dbe4f2668037d05c35847884055e3ab5c152ce466865d15a01d3d5c7b02f2b1c42db858cb49d39143c96426b9a6551b5b90949f2eb87631757679534d21a64ddc36db3dd172c3ab28e378646efab7b6562cb76fe7e54c62ad2d6f12e008c49df8e2525a89169d3f409075e6221e99a93a6a7f463960fd47524e9954df4f609c1ecbf7f23e546e6c04dc4144778635ba1794c04b00af52d6d32684898e9317bf3b460627abab17cd42a055b28fd6e2d58f5a9c6df5b70d09596a29272329197c07b0433454bdfc25c02934e00115eddbd462e4577f2ceb67c44982d2c2b84eca7652f46466548bcd9fb28a60eb0f2941fbb67b2bbfc6d21be3379eb37bcf56c6b44ef24f50861b00de64cb3d6b0c060d955a50f5582222eb2802b3ed1de51bb8af2867d5989c45a92e7c7ddaca24fa404690d4bf5dfacfac2a22c0b7252f2e57fa74e02eea5a1b9797490ce100e487ba3bb892dc693cc60c27720aabc9e8057dd093b5d4d20505918635a1af736eada7b781254afbbd4a94c720f1f73e786d85e86d327affc96c1ceec4e1b28c70b113a61d5a5d15cf859471f435ec632113db6421abb55c6dc95fc8740acdfbaca88f5f52cb5bc31314b791509b1598e3773dae0ed3df6e13fbb43d6b91a1609c5d2447dfd4c8032d083b921e4939953e954a1c63393416d3a1d83708a9cabdd1e34b7bc53c4983f97742da9345aeccf5ee4f454e77c883d2314b1c6c65c2a926b63776e5f2b2c95f4d47ffba37c9b6f722cce51252cb2db3d87c35d2e5d25f778fbd950aa35a99f94556eeeabe5e60212c8484b072b5881cf2791bb6e618d4a407ac5841d1d84ea0053f32eb4d4e4abcf8dd8adb4b8f7d8e4122b4ded650b64245d8363663f109ed85315872e1c3c94dd2fbc6841a0845564b617d80697d51267c3827b588a48754838bd51abb13825cd36b931d0aac8503dc661e99c20946f185361dddab351e32385fa2ceee6c618b20a4d58cac8efd71478dc3821f1b44ad685a1a1d05bfe0087ca31db53adb69e9a4373a72901da6b761b2d5b04a470d90d312607eaf6189a71e1bcf7f79b9a58cd4a763a225197c3578f2f79646cc8b05c435740658e9065bc7343a21c4accf676c40cee25ac331941db3d2b6e27b3a44f56a9019e9e3586225ddd1bc29dd9b1a29a384e151a30ead38fb03236ca6dce87d46081ac9c54ae9254f70d22773c86f2b8a151e89f9c1fcd37dae479dac3207c03324a6dd0a65bbbbd76482a02eba30b02f58699f2cae7246e94b5e2a98c206435dc03cbd31989c0a83292745c8b5c57ff0749402fc3ca04d964d99a6fd5797b90ad30eb9b7b31f30b9fe80e2e666475596f17709dbbfdb54212d0d1a99f1209dbf446610e7b7f15370c45d8af465fd70c690045e0540c0500a3b81f489fa60bc869957c5f51f10e2e3d38beb1f8cce010c5b01ba2422421045219cd85c8ff667c460c49a519b94da79332ae6af53c562859499f8be6ed3353721e334b5087eb2ebe2c1114d9180e9779a9d3f1ca20477a6ec856c423be0232afd67689ba661cc549e08f75ffbf24fc42d234994e66470d1ccf7c48f86a1b48a81c4d61181019518afefcc281180f5d19c45e3f59d138f45c6ad546715851dfb6b8def5c350c277d8af1b072bba022bf1b1f7e1f8806df8ed2d6f6dcc4b30fb6e1700ffea230b5874bc89e9102d1629c0056438c1dd1e08c00abe1c62cd1e48c001535326ec0727683cf4b60bdfaaa56b9f5ba5064c2973f1fc833b148588970712a192edd4ee483f698fbf81a6fac471423c4b97aac52119ae5af7846a74f314cf587c0d12466d756890513df5c7fa9fc18330832df9f8473bd3ad7c1bfb6e6207ed183e4d015c58b5fdccdcdaa1fe1e0dc8b9792fe1c8c5933d91811967c0fbb31678d3fc578f5546f90389e232861b30088774d8a459628238c8554700599994923bbcdb7137eedcb76244b70f044c59f7e105a3822bfbd42e8e8b73b93f2c54cda5835738b51af0782c0b50fd6464177b85e414aa2b50071e2949954e43b0d24711abf56d6e800ae6cf8231d20a8e9ef672128b1fd670cede931905f65c8840b4c0c99d70ddef68663272021b8bb387ff5a0d7ae1b24ba65ab0783ea0235f20b54bff568acf1ec016e7f6e4cf6babb5914c03d55299e6c6b84bba1e92d4fe9254b982bc3f76b53c2bf3a007a6be2e3d18c0b3c860392acbbfdeba93aae1d1957a59f1a809784e3a791f428a3fdf6183f99039506c64624ed6e74f263aa7c19e14d1dccd51635f5e004343aef20ac11ec979936715701a820f9b34733b456e29e0aa83e6d540ccd037f7eb10c9620686ff5a27ac04f2a71271908e01c6f6a07bdb7cb16d84d8c1c7a18dbed310c2610bf5695c3e95dcdda912cacc075c44714d4bd0a775f65d6a69ffca7a7e3977890c910ed3f27fdb2cc51605f7446d9fe5a7e13c48c921b2f34d70f8e84469e6d9b43b4ab2854a1caccd81ff6c2e0d963dea992902e5144288d16d0f8188d01ebf147849a7b1e9b2ebb155f8f2902cf5a7f4cadd768c25fb907b97319fd7e897ae89caa42d9b6a8c066fa17e3b3235587a0376fbdc7bac3afc35c8e16cc58e5311ac5beae69e8ca7e94100268553f4f76a5619f0c8fdfa18eb468af8d51b8928dd351ae37813c47afe73c7cbcf928c808df3ed395a0e7e0cafe003dce4eb74dd71cee51574eaeda133b1fb0a3442e710a6f06ab29e7f33c0b31ca3e3b41094bd4c48548b8cc1b4259c8330dc00a3dcd8ae70d0a77a6669348ca8638c116ba91e9658d3e85866f77bcc16400438b68a58707ab1fd4b02e53814a3c7e2ad4542224d5c597e5842224290b9ea7a9edd8978a8c8fb94f85f837ed90d075b7c04992a167b3fd3e1795b97d87c3a225961b1f4de9d41b6b81f11ee6963a14bc8c048e9e54260adbef3537cbadc8df12b7441ae6e1aa85a58130094f186aaea06607c1cc3d4bfa06ce304b5c268977b03d6416a6748ad3a5d9d4ec8fe94ded80c0a2f0d0cd3d052a69b9129b217819fc2c4150192bc729b5edc0f7609d31a936365ad1f306a6aef0d892ec27834b9ab217dcab7b3a82fbb2dc5c530dd7018b2e102b7ea288afd850f1aca3927042afb6c69937ba552121f5b2c9a78fa21321eb629054e83eccbcfa490ab27717d435126ef5c9eead9964cc3376221e000aebeba8c9d3b3b041be98e9073aa21758b5ea04d0832f04fb99377872248c54a67ee7bcbe82db24a0edf465d8654d0c3ab5c41aca66a2533c05496426e11d3c658e0a773ef5358348c3526acf586ed06d857a2b2b19044522f2811db6f5e35f22f0a1edca8c8d49ddb32fd47443d2394f556ac9710c510ba4d793013f3063c7e78973dd0681cd02a6209a0b4202b596720e93945e4ed1ccdfd9490327a9a90d7fbc23832472af6e420b18759fced10ab618baf16c4043e94d84e296d3e5295fdeb09fc8ccba2dcb41274cc62b9af00f95a0885d51521fd9727c9a24ced543ab38e436ae80ccf88c4fd2ab1f58046132470bb8058387e098aba5b9361a140c824c8ef3ff12dbb787c9d6b612eae4c248319c3c865dc3c913a47efddb5d202d61f45f921ec60513803fc70afb50bd64777b10f7d649554eec1335b980054a1beb72416d78a871fc323589a6aa87e89d07b6f282de5190cc753336c96126820326dc6755b92e70ec41ffc6a47856c204223924c42f7d67ffd792fcc3889e63f5c600258311b93510ee600a2636b2579357ddb745b8b6489dc1ef29703ea877eb23994ce8afcd7f6e66999f30a06431f46e14e3e5c1417918ed5464d2b723fd0c527ae1e8b04e8e6f1dcf5e2b03cc89088f7271ff5317d2239d87f25df731f2c829d2c8c5e57912ebb159b72827ee8389b35f75f29a6ad16fe6e16c44b555aa9944dbc96ae0b1604d59038216957ef4fa29f452b5b27657431526f66125cbb6c2a03610ec9b82d0147c6cc12643f1143a270b9dd52d57335adf404945093315295d7f489ff562949a17b3bf97e1c08ea044dc581cf06d94c156f831e137e0c928ec018772da783813678286d8a0089c0a3317bc723dbe8bd8610a045cf614309e0257014c5b4cfc82688b70127d68400c8a3305523bf276903dd471a6a6da0faefaf07f25d0d97c713c8c1770ff55602d7dac5ab097b26206133af8bb155783333144b45dcbfc574900268996e2477bb7d7fc3b8171a2ea12d04a465af84e82a42f53a555cfecdb13fe2526bffaac5de2197c3a5dc993ebd7c9c77f14b3e86bca1579f2eb38a490b700e7db049e685b4bf6ec9e23fe9910db357521408aa0978d43b66ad8d80d075f49f89147287d0c3089df397e27dcf21f70f82567237c0dcfce6b85f3e3333d4eeef15f53931641e927830e7a0cc89c5d11008edbe92f904218d371beeb1b4f9136828205f1cb710e29b8115da70396a815cd9112791a240742ceaea4caa9a962e5cdc1a7f753c20cc7e89026850a342758d04ee47b4814b43ceb5236fffd5cf328758afbb723f92557fdf70bc16b3cea036f2deca4f4aa2bf7a1576b33d79b110a09f6b292885bccf7c6805e21b0748488ab60025184a53a47bbee1fda045ce314cef2e9067044434ef088a12b3d6c1ce270f2b378ce9436736e21b38c92ca8244ef3ac14a3af6e5803cc8d4c81b0f3fd412f3ef668e34ed585b62603322cef5bd8fa79aba174a447c8f9498ebb8a951545a2a735e0662bdf7fd29cac541eaffde43b21c429ffa4b86ec1725d6e832116c3f6d979641778a698848cb70ae22b03b26abee697fea238f8f6c61649aaf73a199e9d72fd3e4d4f126a17c802a286803884131fc5d0b14e93fe56586d542ab511f6d0e762e83560a930361e55fd88e1a18a7cd2120fb875bdd120b73f3161908119fa0d709f304f9d87efc53a2ecab3f9055bba5a70524a91cd3e0ee610e55bf8be531d90db4d7d949ad615151ae345f0fa522bc393c02a9796d7bcab3fedf26f1e1662e319c12173b200c6f72cfec3913c7cfc58c395d3b025a6ae4a8135baddcacb538e195a2b5750f1cf7e90227273684425e01d8fc67c35dfc8ea2f3d768c3122bff90187d207738ee4d758b776e53cd68da3118d063a1d0f5bf2a2234969fe0d76416fb69693b1e6badc9160d6b36dc69121fe2661911e704be85a8ee1c6d30bed5ddc3398bd76e0180674c9aac92531dc4ccc3c3c1ae721c152258507a92cb3dce4c6668fca56ccc6348c7cd6dac295010453725ee9694c2ed040525f66690b823115f13838bf5031d7827480bba37a936e151d4a45215c147655c01b5ea6b2cc3928c21dea76594baf491c37ab402416b2a244c99b8ca18046c01d22a03828946c15246708e70106ad758347c2f6728d04028a278c5bb2fa491b30737aa950fb2482bba68b5c12a7435a515a8e890088c4aa2a615a804cb91d968af8a7a82ca2c455794fc53b357193a46b1ea1adfd640a53250b7255d2eeaba76095e6744c7463c031de7330c3d83434d8553522cf484a1c3a19750c5a17a5147a51e88f5c49183cd373afa0afd1ceaa9a3b094f7afcb701488a8655431d4b9a827ee08ed15531e869fca16a84cb08cff9f241f4ba8f0285f65c42f346ba943ab6fab32d1a244b1652b6c987374c8dc11c732ec5ad6f2fea1b02efb1dd8cbd1cad184519ce80fd09c1b9db7ef07ea3f7432a999aa46d44d422bf2c4f9ade8553db582f75f8fb2e47566af15f565b95b560d37a9a72e109848ac3633f5bb2a63d00c882c1a059c18ca30c54ba7284465a04e75cb4a8c8e4695416dcc018d169f8a942168a6d486701d30f79b58279cb5db3c09a632c18b06369a218be336273cad4b0fa10a9dc27d41bc0386ace2214127f248d3a4f32149e19d6e89dc6af5f07cb95bec3cdcf3b884982df87ad8609e354a6c08ca6bc63dc526c36e88adc6c68645d2d8ea6192838fc62780eb764ee5cdf44f346d2f41cf5ed6b0dbf0b46dc734563e5d6730a2d92ce4bb2a5c2a8e601923ae4604a3e05c13e81eeb227c5a918498e897caae8a611a735141f7b59ca22138fce786949d91d9c831b418506c1ca03fc8bc5c0c63da50c17a462d56fd724401c0d66e5c85d05a5e48ca310c2b4ff902402288e3a3f6030b26694086c9ff4c1f0d4e066ec3024aa10591d94008b37af412a40d7a0c9814b8095871b0d817a845a4831f3af5dd66be611c05b01d7977c159a1431f0dc1763b774f348c8326176d47460d8c9014bfc7263c1f32a0284e12c6a2b17e64a36932d79cecbdb41a4feb715524f2011b14790be7073739ccc10e38335c4566fc709bb14a447a85e0444b9b597196626524361376c9ef1038ba6cae039b315e6ca9711067e837c3661bfe0a721bd86d2cb61191d80cb6c5d5b9bd91136c399ee3d8b691d98d65e51887534f6c6677d6be0b9dd88cc9a128cdae8d7af166dc144cc86ae5c8c6b46caa02beb045f90d3660b0a4900330333333333333333333e3ca4bfde37ddc1ff7de8d60c232aa45279a9292921209ac35aebce01de7e01dbc8377f0d67401ed0da10d1f0e4d52abe0757486b10e62c41ca48223a94327bb321b1fa7e055a63ce1d2e660724e0a6e8ebfe737785c565f141c4b1e13738c50991e149c94426d5585f6a5ee098ea47c5b29c7098edda4077297396c821f2ed492e698473a98e0b57d479bedef79094e698ae918f197b1129ccd6695ebd2243829e68b15296548706a3e6bf050691b2d3b821f3ebb9d49eab09fcc08cec713724cc9f4712c5911fcee30daf6690c11bc4f93de1ee45552ce109c24b143ffe8393cfe08c10f6311d467d6233d4170e3fb6e36eb2b3b0304d7b3cac52c5de59a1fb831ba6a5f7ce085cba91a626726b7f4c0b188eed1d5b47a5978e0a4c6e6cb13d9819344ea6732248f611db8793a3e87b4505a7e0ebcf6d0217ad8e3c0cb4813d66e3c546ee06d9eb7cb1f04b081ab5d9eaed2b33adab5f082d87f94ef83acb569e1b6c725211d3c0baf652e4731480eb46659781b24180b2779e6c95154d58c312cbc489a7f2bb2fb8faff09225c9a1f9871c6deb0abfe3284a9fc754af6d2b1cf97854438ea7565b56f81a3de4f918415388aec2bf64112d4398e081aa0ae762aeb8acfee1a334156e69baef49bf7df944859f3a948ea297a7f02579a648ee5c1fc4148e678f9e2d867cab580a2ad4c986258f144e942011fd330a5f2ddfa71ccdc7cc88c2df509a156982c71d0a377687f7e8e0592d28dccc21dada625dea3ee1f49d947ac8924b9927fc8ea12558b020695727fca05eb3a74cc509672285e718de849325a7d4d392ae929af0439248513535d57932e1a71c4ff89c35740e0f263cf7499b263269adb984f763ee36d1e953ca58c2dfd80c7fdf1e5fa412fe86ac1e49ce9a630f4a38496ab2cf5c26e1ada698aa6221ea2b92f067b3860f5f8984ebc9936d65b5ed0f020947cee6db72103d6df2083f847f2c094f911e71847329dc86d481a7a46984532187240b33c2bff0d03767c851c72ec20b29592ffb304554116e064dcfaf19bacf4f841f7bf0a0437920c217b90b73c1e3104e6a5bd6beb74a1343787fa163f3cb66e1e342f8d32621dd29ff84f0c2079d6254f6993708af3abe93f1141fa515841b358636cf5aaa3981f0ca3c27bff1f3ce00c233ff20d774567cf40fce775027a942747cfac1f50fa66346abd14efbe0fde5caefb1458b1c3ef871920d8f7b0f5eb7e414ba23f5e0556bd01a3bf3e066a4d57469ed2d9c787043dc6f23fcc776c13bf81f77297898e2b6038d29d9071d07621dfc4f39cec17d460acdd1c1f19caa3e883707377c10739a433e084f0e7eaa34ed992da5b7581c9c34ab9eb482e58b191c9ccf9c2db25f8c31526f707cb2ed322d37b83982abfbaa4fcc698333f39f4288f6d92d1b3cbf98c2aaf755e6881220630d6efb848fc963560d4e48993e30b7ec3fcd64a4c19b481552485ff5c6340c178421030d4ef504dfd8b0ac40c619fced38168fdfcdbd449366011966f0524e89acd441a357b803168601a30c4ea4cc1f59c425bd5eac066490212f937073e943c618bcb4b8decc95a1ff3b2eb6bef0a28b2d3220430c95e6b0fea3142eb68c8c30f8e21ea4b4a9089721cc850c3010c3fb2455fd525f30a656c6e688be1788e497e6735e0b97404617dc0e1b724c4939d892b8e057e7cae361477af1e82df8ad193faf355ae1046468c1ffb19c3286adc6908f0c0332b2e08cb77a78cd1d7e230b032fb20232b0e08590c1be821ba92e68f274d1257fc8b08293c26a32976da4bb8e2fbcd8c2aca08b14aca00ba4808c2a781f89a7d45e953cbe80046450c1df4e9273e8925ce00432a650351620430a2d230ace5cd0147a26c3c5568d1a99749001053fe23f3e5189b858be31c87882d31756db2d212d4b8e2623c87042d540818c26f89299cf238fc2cc4dc6c542c074f1850714d72083094e6dee4de7d31a1daf8c25f821cdfc440c2a5143274309e590638979a2e3586720230947e6f6c163acb83a073290e06dfe9c25a40d1f117304c773f81023a6c4b88a11a86ed5f4f4be087ea8b6cf1fcfa9559808debb89440ec9330427cf769c329a840bb1230427b84b47f3b17e529d20b895dded953d40f03d677498f0d19c477ee04555e9fc99c423f73ef05faa8349ead1d7647be0bd664fd61ee450226878e0a4c8a5327b074e86f71073c4e69a4a077e900d39c8a164fb2293037f656cc35598780f0ebcaa8c0d9afd22e3065e4a15edfd4b82e71c45860d1c8d1e5f4ff239f8e8520b377264c4bcb1ce63b2d0c2b9f6d68aba6916cec9c7b45923a1a52bb2f07290104453864f6d2516de7c8e3b280d8fa25705168efdff6726af8e1e79857f96a5239f9493d4872bbc1e4d4dfb186a3d6e8567993e9ccfd979b4125678eddee5a1a9526125abf003adcc9a93d554481255f8697d36e498c35a8224155ebbc75a1d9ed9c1ffa8f04c730c7b4df6e6fe9fc25b93e41d95b973646f0a7f2cdbecc77394c20fb12395f0b1a884b449e19ca71ca588ee8d3b7b14defd871134ea3ffc220a3f720eed22c77328fc10ed339c87b1fb9941711c99c7c19f70433e881e8ba6d58678c28df6618794365c6ad209d7cc2549ce0ed207ff72c28b5531df99d4a3fcbb09d74396ca13a2ab09a7ec2a64dd0489566d26fca0fe3ce68f35cb6a30e1d9bb745f889472b0f5126e1e0d9fa36bb58417251ecbc420e991a795f0dfaade3e085f29524a09a723db7d32fa78949293f0dca26886895312fe69ae0c1e1d44a66846c215d90ed70e2b64c925249c1829ddbb33a5922e1fe1a51c87312186565c948e70a23e78e9c923252936c271cd718a5ad4a41c898cf07d653a74e416633b5c843749b3e414b25d21a1225c4b216ca203a99839897025367ddc610e39db7388f02a5d468a9eaab4ce1dc2ebb40e54428721bc1c237d20e139469314c29b4a3f214751199226841fd65fab669f2c293908af2e0495a0412a624e10defdcf0709afd61e7781702b4d88b163f848313a40782672f75761d2a4cc1fbcdf902b85c5f62d8b1f5c8ddc954f34d607ff438a629b35ab6ccef8e0c6584915d6d91efc389e1cba87b3ec74991e9c98825d5a87fec9c195073f84e8108b1dc777b6f0e0e794356f26bf15c6ba83ebf669726059b3a65776702d6cc2255955072ff769c7b9a347e242073fd688ec40dad7d3630e5eceee961e678b62b71cbc4a1a25a925264b8c83f3f663f963ea8bebe0e04cdf5f86fb4fd5aa4911e30d6e05cfa8e571109f330911c30d8ebce6ded0b8b4c19f2ca99307f5fd5f151bbe10ce53071d522cb9585d839ba14722db5c4c88a106bf7c443ab6b7480d49831fe608319a79ec65f5e130c44083a795455624bf60887106e72fa9484bcc47b61dc30cde799aa7e89872989ac528831f2d7325cdfeb125916290c10f324d6fad3706bf277ffa24993e77c7108317a1e53c36c86676334618bcf940ae03cd124cd3c4781c31bee066180b2ea576c966e20557d5524564bef34f7e17dccc79429c489ca70c73b1759683185cf0da5375852017184c83185bf02ace3f36cdac11fed1829bb53abedab6919c3fb64819c4c8821fa2e2be3d858ea7432c3897f264df1837358f71b1750557d42c64a4325bcbf62186159c1c6677dfacf6f5e657c1dbd8398e3368baf78de162cb84e1c50b900831a8e0df98faddfb7de0999c8217db248449a99e66f2c59182638b300c181d460c29f8fd3972d49c3c59c48882575953d7696da9fd5070d26a44e592cfeb63f182055b1ce58a184fe8b25648bbe49ce0d5f747e9552fd61a89d104275eed82cfa4907e7ba18156c46082779b43878f30cff0314bf0a254f5775039281b318612fc3084075feef9c24d6c8f102309fe4fba0ade59eab23b48f0d6fe55a5031569d31cc17f995eef0c8911344611300611fca822fb3f6a6d086eb7c79184c41cb9c4b5841842e8da73bfa46304c18f4655e5644323463e06100c125307296ac80fbcdc71322dedf8e08e37a6c5b85c7a40e6efcec14cf43cf02a25252ace63077edcdf9922bad681a329f2479be53843c639f0b3e6385eadf78eeb4371e057faa0b6de73032f8b86908378897677c6b0814925e7ca746731520ba746e2dd2c2b5af8173dad66e224327766e1d4473ff389b62c6e93bc19258e85f7e95aaeb18485571da444b779b5c89157b831d53be6945f336c5ce1a470999c337bc8f05be1c7d6d829bade53dacf0a37a510555931ab36c7b10ab7831c72982c5bc9660f55f83163f9b6844db726a7c29c474af2c72e7563a2c2f73c1fd541d3fc3f7b0a377dac1e3d336b8a536ed55c653e61624729fc6c39bf5d7497983325851762dea8f48e2c878f1c459646f2460ea273bc280c8b312387394a6ff443e1791023e4b01550381b3165a3867cc2c9f1519e34d2e4709527fc888f91d6f368ca612736fb73959c533bc6092fee4f5bc262ea33da8411368410c4e4628b0c03d08413d22f7d1061fd539e4c381f7afa1c652dd9670c261295d26097bf84f3398a9d27ceea52d2125ed234c135bbd587a152096f43fa8fde424ca97228e1e7d618553e3aae8f3209e73aad87906f3b7f1c92f0b27d881ac3a5657723e18739bd234374dc390909673407a17a62e411841b4d6e12e308d77ebd3e8a4eef5d69849f2ba78df0b072dc3d23fc8bd5dae571ec5c2ec24f7f4babab15f1796ccca525c2d97c215b9e17d91c8a08ef3f6b4c1faf5fe6104e84b7dca14c5b6df20de16be5cc8a9d42782a1d45d4ea3808e1f6dc6f068d109db3c7208e209e839298d22505e15fae79fbab74200a89946eeb3e8e2640387193ed347bf41fbc8f43e71cc9a3fd4084f475b91493c7edf5c1b5e47f29a7cd714735f3e178fb38b6700fae87afee20c5d5839f83d89a87b3bb8418427454f93e78f0ef53761c5479e41e7707bf2c2c7c799437c729b583339a3bb68cf920eaa30e7c8c8a31e530c71da3832ff6ff9dbeedd5b49c83511573dc217b8614b61c3c2fadd70a9e38781f793c34724f761e0e84a87cc125f75418dfe0cabd44ccb176f5c92c089552f02ca4bacc32a131c22878193af41c778c1de51c14fc8ce91d85f4b149774ff0c3d08aec0e7382975324f6f6868ba135c1174d299364d487d162821b7294a3e4b496e049fba87c903257564af05245c8d2ecefb72a097e10527aba8b69ec4307129cba0f59f5417290d271042faf5fba8c7418c10d1da607ddf3299d53043745998c670fa34d0e11bc645d15dc21b81e955d730826590bc1cd1965b662d9cc1b0427be83ba9f9b6513089eac445947da4af0fcc0f95c1f7da776b3cf077e84a76fca6678cbf4c077b94f159172ff8407de5f8e63794739c00efca8e3ad4992538c8e72001d385ed6215d8b79b8ca0172e0845039ccd9c73f29e40038f03dca41c2a6567b0939c00dbc288fa255d286f2200938800d7cd91c2b74476ae1d846b9ca10ecc3e769e19dd684c7f9cd3d3c66e14713424c394e9bdab42c9c9e0876962b7786742c9cc91bf75148f9c9372c9c35efe0ad738ae9ef573817c3c71b7c57f8693ac5a887a487dd0a279d44e720e5ce97352bbcdb8ad6395ac50a5985f31f4a6d6c8d4aad0a37668aa7cc4a332ea7c2f76023a36999dfcaa87063b6edaccf1fc7fa148ea57bc8a19885aa4de18d67a57043fbdcb3fca3284941858cc20df691d78c2b0ab7deb447c35068bf390812ab8282abb168159a7ec2f598bbaa247b2a0f4f381a835cf8d8eaf46527bc28293ec55776f992137ee8f7d6482321addc849b3caadf9837849b50135e8478554e294cf62f139e4d5d761c478bd51b269ca834d9a1b2be8413a2dd87d1375eea6a4bf8513a97927b9f94a7ae84eb2396ae634a53c20f52ca42e6ca2c1bf524fcd414434e1d95fa7b24e1a5d4603967ef38b427126e0e326f4bf44f8b1e4838993654f6795f34cf233cc93efed31ec5fa8e23bc10b52e6a348494368df02bb52a4c76480b1b46b8151a953ea3c2ad6611aef4864647b5390c1945f82942fecd914984137a2a6787c84abe20c273b7b69aced4f09743f861b0581fc28518b518c20fe672fc9bb3570a2185f0ad627658dfa7f21342f8311a72c60aa61d4c06e10762ad599f20fcee50bff271a1251208afde526c594b5c0f083f8e393a8f473a4dfd07a7cc3ca60a17c362ef07afdc36c264fae065c8c79afae083a7c9c6638cedc1f114317f242322293d38f741d24abfec6a1d79f0623c6aba089ac3e8c08397af4e43dbb22ce60ebec4beede0fb658ec3b5c73373d781d90ea483db95b263344799369e831f74bc695210e5e0c6d439fabcc6c1dfa4b943f1150eae654e92f9dc342a7d839721d9f12de5414abac10d1d87d9632b5cfb6c83671a39c741d2d431996cf0ac5243541fd7e07465956c1ec658caa9c10fdb614e558fe9bc4b83ff7190c3b8d5d67cd0e087f07392dd3378bdedd1a5ac0e0f33381bdd7bc3bc6570bd83f5b05346062f25e6495932ba778cc17b4bb9376214afaec4e09d057b0f6518bcecc8476390180ccefdbf77c871bee0877e29671b8f17bc145273f061da2c962ef85a23bd31fabbf35cf06354262651f1efb7e0a5ec142cc3460b4e6bc654a9e262684d16885e1d7c482958f0b5dd6247989bf82857d8ae6445836a0527749810217c7b1456c14bde6973850a61b64205e754623a4cade1a932055742beec41ecaceb102938a15fbd3af351705cbeec3f780ebee3a1e07b0e3b753a9fe05cd01056511b6639c10f2ebae5ac9247e99be06594778f3f8c0e1a2678b933640a1faf5b64095e84d09b11568267f769f1ef9104a73e7f1c7ae88104e7fa7344f5f4b1cfe3084e0e3a42bc4c99dd6d044722653bb06c566a17c10f513307496622f82769a93ea423cdd921f892751fe628c71c9b2b0437e58e62f28c06c1f730735467962e4305821f27c96129da64f8c81ff822b612c2f47a10a50f1c33fbc91b246687600fbc4c3158a69b8f5b451ef87d1a6fb9fb8314b20327d9c7a1e7f9dbf6381db831776ba596f9162f075eb4ec100f217b640c07aeab4fe6685bb63206b8819b5fca26967d5c6f001b781df97a35c7e553d7c20bda21244d7972eecaf058c93ccc5acdc2f98aa134e7cd70175938123d3bfe2894e68b36165e7ce8133d473e2cdcb056a1736d7988cf2b9c303194cb755ce16a85bef4c85208dfb4c28ff6917fda9e70b16185d71f555cfe2c4a8e55f8d7b1c74345b0e05154e1ab767c3908396ab44a2a9c9e994b97b7a35454b8f5a9c25a8edc29494ee1e49bdc418839c8111253f8ef963c8e42be147e8c9ddd36ea34073d298a985635cb1fa370bd3ecc3c7fc4ce1ea2f073ecb952ce23147ed0724b33d11dc530289c1ccea3f9c935a7f127fc1017347bc7ed61c69e703e481eb6aadc0937b9698e6cd2478d2c27fc568f327290183ede4de09303a9f50fd584dbe9cd639294cc5ccd8493e67148ee9a63d78909ff35c2574a6dbd6dea25bc99bb54b163094f428eeea38e2cf66325fc28b6e3d764172585126ebabece7c7196fd49f8c1d29b6f4f648c92f0d652847f0f6be922e1479914113465c57490f043aca6f031e5e0ed23dcbca5da97426f089a23bc28a61d474937b3aa11fe878c9d3c3a9ec830c2f30e3e9c644c49c3a48b7025b2cfa25fc44c114ee694e1eb2b89f02d3a8ec73c06117e0c3955d33c840e630ee14a6ad792397b558d219c7416ca3fe410b92b85702b7518c27f74ccac10c20da95f4f7910aea5cc9c42737f50124178919e6adff1658e914038e3b1fa67f324361140f871d47879349dfff3073f6c9c5ffe2d898e1fdc10cd41a3c61c65f4fbe0c72c6ad71d84d2f0f9e0e5a9081297ce93f57bf07269d80e2b7f90cf430fde86ca520dc9fb3f79f093bf44a494c6833f371e9d64b21bb3efe0aaa7e710edae1df51f695e8f3267b90e6eb7664df1618e89321dfcbba43ed391de4a9e83172cfbfc6a2c073fd2ae58173193e5c4c10be396fe29c2fa0f073fb4ed896e1e43a76f7025888a7bfe4e9aad1b9c546fed39d536f86e1ee788930d6e6cb986e6c91b9daec18f3247fd1e199d326af0a312ef8e47ab7325d3e0fbe5b0535a5b0f3e687025ddc392cd61a9e50c8e4d6a0e22df9ce4308367f159b56337ff0ecbe0498cc86f6bca9a0c9eca4756f48cc14d93261e3a2ec570598ebd30b8b93e0a9ed181c18f88ff5af7dc51e60b7e468de778096abf17fcb0a93f66c4f4a1ed821bf37b5cfa7555d2b8e07768161beb3129b305af42a37cc428b5e0474c512aeec982df9192da771c2cf861e63c76472186d4b9829fa3beebb11cbaf35638086fabe0a73877f31012fba6829f72ec9b704ec189704b69a191829f3b9889cb11053f7b700b4bf93ca40b147cad309adda33cc1cfc93d4c158ba810e204a7530869823716b3b2e4ac262d61825b2949aae6ef9827bf045f353a7b234b18f195e066f449bb764bc93992e07de68f526c4ac99303097eb2aeb5f18ad27e1fc17f9ff6a8e41d7dd846f0c27806499ddbaec345703bd88e93c6f441083311dc9a8ab89b5893cf1d82f3eed2df9999735e85e05884c672d70e490d829fb286c98a09047f237bfa851ca4cff2077e58e40c8b613eaef481272947c991e981abed9653a2689685077ed0d1367fb13f0eb2033f567c681b3d126975e0ffe63953150b90032f6cfec366bfb2fc1500077e249d9d255d3e89ae0037f023bf5c3612726d8a15c0067ed8d7f93cad3286ad165e6cb41c2a365af8519a986d9259a7c92c9cbc621d694bcef943167e858995e314652bb1f0d45f2be7e8520561e1b84792263d5fe5fa157ef0dfd3ada66d63b9c2d9f48186fe68ec2aadf0d62b47b0b14829cb0ab7630f2b7d24e12adc8c728b2947a80abfdea227582c9b8e3015be5fd545e5303a8e0851e104fb0efaaa62adc7398537f641ba2bc7b1bcc7145e6dc4d4ea94c2ed305ad424e37711527895434f72f5d1dd3c0a3f4fca986286cdde8ac271f30b17257d6a86c2eb3a3593cd80c29fa9d7f8cc7cc20fedb183af142a2d9ef05288b38b3df127e984177f412c956685ac70c2ebb1f338a6ca26fc186e42c2868c29af09dffeb2df8924136e6a8851e22398f0256d5427e98ea27309cf434b59f5bf25fcf451c75399551ec757c2491f6d78743c259c143cca3a940dd932092f3c580a9e9364f99084ff31855812b6634a1f91f082a58b1e3cb49608094fb3624e533fc289fefb0f153bc24d1edb46f8514cc5a7d258b95e4638eea369c5738e83f02ec29f0ed3be43c7b1ab5584efa16a089dd22a443411ae660e55d4a453f78a0827c4b4f92b26a3c27a083fea682bb93cb5c7aa21dc786bf5ac4d9e2f2d8497aa766df3b9ce4d42f8a9b9d36b3a085f543d8a09f2d6a320bc694ddd51bb2731108eb8e4b8de83b4660908c77fe5d4a492471effe074a8e17fd38c4f871f9c4a135bfbc32746fbe0044f95cc32ed933c3e387ee1de7bed324bf6e078b40ea2a64eafad1efc73efeee8687bcd3cb81edfe6af110f7e0cefce76d91d0ca9af52f6783b90e6b73ab81e5ad6e0db35b92a3ab81d8748afcc1d2e85c8c12b8b2995dd2671703d54ef8f573a38781fcaebfa63c8aa746f703a7a0e2ef391f345e70657d2628769a80f2ca60dce46878e1e877414346c7072868f6369bf64cb1a7ceb0f79de72f5fd6a703d7be4a04572be701a5ccb7739c75f4183bf2134a4671a0b5e398313a33d9121a6ceaa98c18fead0d724a40c8e89a7c5ecfe4af927836f39ce399aabf1a8ff3178926e2ec4f42858ff6270c2fc6d75dcbacc1f066f2a849ef09863c607839f525ef320390cf71c5ff04337d5140f29217d78c18f3653b8ea4bd1bfa30b6e48b19c2485b372990bce59f684a40f72d0f1161ca91cbedfbde318b35af08367faccc741d566b3e046bb1cabc574d074b1e0d55b78c755dd26ed153c099b434d9f90c9572b3861fba543e47ca779f1b0a3b4c20d99a952adfb2353cb0a672efdba8ac52cac5556c5baff63ac3437d31db3a10a2ff664ffcf9b6e1e7ede48859f2de95108d73aef3750e1c5ca1aa9627b8698be53f827d33d99f2fba5e698c28bbba41eb2f3c4985429fca0f597a23b59be5092c299bed8162dd9a370a3e40fda91dce5ba4c149c654578b95876677cc7a1ba22caebe650b829ddff7564c7b61cb2010abf446c638aea486df9849b428ed581470a911896c28627fc8f5bcd42a8eb550b373ae19c78960e82b1c50a0e521d48c20627fc2862c71d346724e0c5c6265c0f1bce43e227c7686bd4d8a270b0a109b7f307edd8983f7a8f63055e8071180e6c511b99f083905c6d5372112d4c50e92932965d1313193652f89770babb2445788dd259870146172940b584bfb221b572b08401461717d8a8847fd9f5b17b4e295157b4997d887a7a109d7b42cbbbfc4938eefeb9225473f6201e614312cef976678e241d093e4e2675f9e28684973aec3a5d5269afec1187f1b87338c29678abb0b70ef70a79aff1289bd036c2550be92b98472184ba1b8cf0e37c1693a6cfb1087f725853eb137b091b8af0673d7c18d357527f4d02612311fe9a7af9c7f9d307416344d84084af6934c2a52c1b61e310ce8c4c276d172f4def8621908fb78314a23c0b001b6c14c2db681e2ce6d01d734cae0e3608e17b1c678cae0c51a9a941f821a4b76c99b3e5941e82f0831c66cc71777ebd06a292abcdaaacaeac7c5e51228df87ff600e1af44358b31440c6697fdc10feeda2b751cf7e4982fb65081f9628b177451aa0a3b60c30f4e4ecb24b12f52c7aa45b0d1072f870813d9fbaa2ae40d3e2053a7eeb61aa6d26de961f4506334dfaefed88d3d781163aaec9f8fb1adc2c596bd0bc0c0aa056ce8c1ff402cb43a0cbfc8835f21e4683347cfc596a5800b30b6b00ae374c10518c9800d3cf871b9a475de1cc3a4f9c25841171f3841170fa851a3460dde2d54e08215d0c61dbe08030c177cb1c51602d8b083a7f59623dfd47182159814a4e0e08264d5c129b79c3938952437aa0ba81eb0410727786be6f76b71f54e7080018651c1165812d8988313527ef3205c7a6ffa2a0c1674f1052939f076112f2257259135356f96b3db448c1e0bbce00d6cc4c12fd390d992c1a3a6145b75a0cd7bf1451860e80aba48c11746058a81116c711b70f0f387db0c39763ce98336dee07faab75f8c1eb9e106ada32eee4ecb23d4253eaf8a791c39ecb68f8160a30dbe7a6c99f6d1c63cd9dba20b161c5b18166cb1050646b0458d0d36f89f39a5c89363b61e8b8b2d2ec070410a4e5a60630d6ea6314f79324586ac1729b80d3538d61dfc88bc5e5ce02eb091062dadd3ba34c35633cdb6e66290b953b58f49738213787101167881811a354ee0450af2be0883051b68702c9ac6a0622f7e1ee4622b055d187206ef425b7cf6278fe09a2f5a00066e6c98c1f7d850219fd4d25c9e8bad93822e0c29832713730af3fa4bea09e38b1680915c8471a80055f05da4003130822d686c90c1dba4314721746757c7e08ac66cfa7d58a11d31f8315b8e223950cb09831f7f0ea2b6e251ee3060f06cc6265708ff3959bee05784201ff786f178c10f7334ef318dfb8574c149e96f51e9a639940b6ece9ec1453ad58c640b4eca665b16ee344cb4e08b5d6a0a92d2a5f6f70e26fbdd7b2c389d992ed9edea9d2b38b235bfa1d6a3d86105e72a778ef139b09c1d557033648d930ace9b7cb470e9297819fee73cdb43b1b414fc9118b2759028b8b6a93f8378471681821f89c6a658988fb59fe07ab86fada8589ced044f52da48f1f19a857013fcd0c346abc87124394cf02d36422563489163096e481fc12347555a2bc10bd1c166869cd2c649f03227b3489535d521c1f5207fdcae1259b53d82e3a5213ab5467053e5e8e314db577711b4cb9ed006113c3b931ca6979cd9d3b3310427a70c95b4729c3aa4674308ceca7fe4f164f74fc9b3110437f465cae1a291f3793680e06d0e391ef994424ef36cfcc08b6079e22323692acf860ffce875de4198e46dc1b3d1833f7bdc23d1e581131e9f63348c45b703e762b4f7adeaa0a203bf62088dd530078e7814e9adbe0d1c789fea714ef213a6be8d1b782aee5d975ed299b7610327b79a4711e9e31baf165e8a0923293b8ed26b87167e471b169ebd269c7666e1857d1c449ab2f02a3c76670a1b2967b1f0259a8ca51c62d607165e0e136b3ec7ce9257f817f239ca945aa9e50a27e714d3e5dd61c76985b72efd59c3c692301d56b85146e3fa833a4f59859bb54d3ac5b8146355f84126c5d87452e15f7d6b06f3a0c20f16730aafc7dd6352f630a9620a5f632bd67df4518a29851bf31e2ba490c2b3f0968c12320abf26b4c207d1ef3f88289c4d3d69fa0c853715bd4dd3dfc620289cb6912c62295f0a924f7829115390a858a9239e707ab3739035b30c914eb81aae1e568c8e909f134e5a86ec3064aba0fe26fc38720fb1f3cd4bea356125bf9cda1f4726fc54291fa61ef1f83c30e1a6ed914b96dcc2765cc2e94b1f65d7b425dc0e72b678f88e1ea42be1db7d18d971e71c7253c2d39c695b622b33d793f07360725922857f9925e149c664f69fce718c23e1c61434e7ce3239430c09275e24856675966b1f4184fbf3924d1de17d14c226767dd49a36c215f54f6da119c62319e1a8c744d5ca1e4de722fc8f467324e36d174e45f841d2491eef12e16f4c1aa53ffa87194484f7e92d857d203165f0107e040b734136fca268083723e51842f028c28485f0936407a9912384ff39b3cd86078f633708df0397bbedc958a320bc8f2aaad53eacc50a849783b07eb36eeb6105083f9c995fd6fcc1fba01a2ac37399871f9ca416aa434da60f7e289363f48997d0c8f0c10932397d58397bf0a37a771817a207ffe8c129cb9d3a79270f9ec6589f6c62f0bae0c1afdcd9730cffe023e60e5e7b7cb183539a3b2454eae0873972f4317d70c1a583ef992a31690ae123998363f9a3935c297db372f03de54aee7d1c9c742ba6ea3d1c9c90439979a455abbcc153f5a0b28accb4ef063774fac852eed82dc7d106ef5d3eb00ff9f38d071bbc8f4e6a9dd3327af41afc8ee73ebe540d4eda8a9d95638f2ac569702673e821cd46839f912c26979cc149e7a3d616dac3bc197cb3dff064d3e797c1495531f75493c1b3b33149511d8393c94672faa468a38ac1491be722a1d3a34bc3e0c9e4e06d29f64a8e04832bee41ff57a4aa07bfe0f95a88d029e8052f7b9691986ace65ec829f6ae1e319f34873b8e0e7cd9888892db2b92d78b93fb5848a18fd3a2d78136361426c89e7c8826f79f278967df96b58f0d275fc5a26623969577022222bf67c58e295150ec248feee5855c1894924a6145eb93a54f083c9db1e3705254f8e9143450a5e6a5289e935240a6e482ef2713c1628b8c96da225a67f7c79821fc6c58ccb92db3e27b8e1710ed5e2728cd56982bf39ce379b63cd39c304bf54ec249c4bc8d6c743865109558ccffa7c26094e8e2a4729a305099e851ca61c5cf9d7468ee0bb472166e690b152c4085e08398e6899c2f5e58be0a73c29c7131e4470f37d184663de48d13104b743c81d875e087e5c21c79572e430dd41f0e3102ec7dbea1db440702d8450267f3167f3074eea891dc5ec286dd83ef07e26bc6358c90c750fd61c2a5a2c0d0ffcb2b90e3a8ea6fb8377e0e6aaeb491e1df8a6ea1f07570edc8eab3ca590c581f315be56ed0dfc1c269329f769481060033f3e0a59f2abb570724c4134a6ce725269e1c76130d114d159f8df79629ab01256230b3ff020f7e6138bbac4c20d19636bbab78f21b0f027d8b5759a0c96bdc2f1d86453b694ef2a5de15a8a11e71f6c851fa6ac3c6b6485133bf038b29854307115dee638248f1682670855e19bc731a46473ee38970abf5a3e920f2544a643856b1e473139fa3cfad829fcaebc21a5c542d29829bc4a2e5b29973f47560a6fc42f420ab7ad4b7286ef706a915178122b872963f7077922a2f0abb327c4431f0adfc3467b8efe79c20714be450a0bb93d5e8d9e4fb89a3656e4303a5a763ce1dc8da758673ef1309df0443458b0bc2177a4e1842fd9999673d4ea39b309b77275889af346b2144df8914afa8e3a997574c9846fffd92c5b65e5ab60c27331b32c0f3999e6124e4ae7ccad29470cb18413738c1243ae849761fad206cfd9b194f0d64cb352ead6ecc824bcf4edb61d7e49f869528cc9b28f849f72ba9c3e9258110f48b81d2654943693db8e47f89e82aa457438c20d29738e432c6af67423fc38fa2883947b349a66846b729eb2868ae93e7a11ae67d9e09f61430ab5229cea286bca5227c28b219484b31c45af20c2b394fca14b88d6371fc22d93ec3996cdaa8a21bc0ff5d01f47918b6c219c1c4426757c84f06bdc73902a1d8493d7c3741b9ded422a08ffdd53e64eed953b32108e66ef9421de2ad70908cf23654df0f30f5bfec1d7709f29b2e32d1ffde0888ba528967a7cbe3e78a9352b92d1dfb6e3831ba135dd6a5f9c670fde64694bada93175f4e098650ec5c62f36260f4e5cdd5a0ea3078f030f5ea61c6bee204436b63b7875de1f840caff0cc0e5eb40a390ec283c831ab83df1263989842072f4a4a39889123c65b3f072f7d98e39e88c90aebe5e04627390e6e4e6917f5beeecc818393c7db3f9387cfe50d4e270b6e91376ef0ede5235aa749744d1bbccb1c25a2648397c390cf18d5e39e5983e3817f320feb715d8a1a1c1b19f1acb1b672d2e0fa892509963b98074183932ae7e6317557cc193ce94e131e7d33a02165703c89aac5e8f155f964706ce63c8cc1f2e6c818dc48313c3226710d8bc10bf9d80e3eabc5360cbe44c8299face4283bc0e085c9e123491e31a45ff03fec0e2486da94587bc195902991a84f92d3053f4afa63c2528ec25cf02a7d18973d795bdd2d38e9fd2b47f7214b460b5e856745084133250bfea754b9b3ad58f0b4cfa34a41bd8297572b86902243f2482bf8d27188ce548b746615bc8e9db3974905ef3a96d95c15ea564ec1cfa27541baa30b0335d6e04fce29a79c3e31c73fd5508313323b7a98bd37c7d2a7c14b52eba1e71c26b9bfa0c17bcb29575a98902c6c6a9cc10fd45c2c977c7e50c30c4eb5a492d58eb5366519dc4c1a1d7358ab99f385b9e200c7e00bae4106ef3dc8df352b1d440f1a83ff95917cb3e5387fe41083ffb16dff88ca61f0cb6d523078b162eedb58096a577dc1691993bce0c714d27f7c1c540cf9d305b727d347b221d6e0829f43b68fffac3b47c6d4d80256eda66a9fb121993a72ca9152430b4e287f0f5972eef5ecb2e07dc584a5a5160029d4c082d71e76567d566feae4158c2dd917b493a68615fc1cc40edb516d35aae0dda58739f8daa648550d2a781d9322f685cad86b4dc133f1b39f4b661f41ab21854a225cbdc4b3db3f45c761f25ca79428f81efc6f76924f179f5f64208c2e9e6a40c189317956091ead553abb45d1a0c613bc0c691f9a48ac66ab38c195892cff0c25175b2970810ab01650a3095eead452dede71b155bc50c1162fe8a2aca67d00b7c81a4cc0446d6525cd3d6ea4da530ea15e61420ebd1a4bc0c3ab3a66a4bd1a4af0a3e418d523bf6b24c1ff782e33410d2478c95cca23357bb8ce41358ee07cd011933a0acdea670d23b839cdb6f2dc45bf0b1d1f6a14c1cdad31720ec254938670c0053588e044f5285ba7ef188217a3d4f26d9ba7206b0d21789b377e7387641070950bad2f0f0f912daf48d95463f0fce7670d20b8f943baa5af508d1f78317d1c8597dc686c54c3075eb28a3c217610f56bf4c08b1dd39e03ffe0f3b13578e0d6467d798efe0ebc4ff2d7b93e7fecb8d6812f2556fea1c8f9a6a9460efc157b8f21fca4f848db410d1c78221f85b49023dd6adca069cb3ad5f07899f56ad8c0e9f4183643566ae157a7e4db964fe7a9a685174d93254b9b5938d1cc276f92ee78a3280b37326bc5dbe5dbbdf3a20b1ab1f0b5bd43f4f54fb1013460e17c14ab932f890abcf8420229f0e29c8374e0cd0a345ee10a67a4a245394b5be1c874960929fa8b4a8815a0c10a3707d6410aef1e1e73ce2afca02124e4281d55f82d39e88b1239346f4c2a9c50cb7d11613c5f5950e17cb04a1a73189e39f23885e313293964ba47e9834de19da4cb5c67bf1e79b0145e5aaa0b69aa228597f2f45cc8d1cf7ab246e1895864b5dbf049461285e7bd1d9235191de51e0323d8a20b1aa120bcbb2ea6e6ccba5c3a68a59453d88e06289c641d2264dfe827f6ac4ef35a8f9334f3ce41e5f65852746c3ce1b4af64acfc49245cccd3e8841f29a7f021f6458f5f2a3438e15ae80ed3d6e6686cc2f18f5972e4f46868c2bb0966131fb287d9c74cb895d583a68eee474303c384118307c48006263009198d93b4cf6040e312cec73fd541ce1c96a024625cc2e34443e6b633ca448e9b5425dc4822f9bd2e4c0947aa7e23c3497e406312ae6a76da761b25e175745e1725e6bca69a48f8a1e55053884820e1a58491508f5a2585f8115e24f7ff984c39d2d83ac2f7d0d73c8e36a68fc735c2cf41960d29d46784f7b92ba5c71d5c4461e6716791f1e63dc9c358af082f04092a398eee72611a89f043aa7b54f76977c88108673de6f83dc474eeef1cc2d99088507eb51ed6c410ce673b0fcb3b3afdef42e4b6a22a59ef6521de5539a8ca1e2ca5c8488f107e1c7b5699746d15680cc2f3bb10fe628809c297ae18a5a7c3e88f628158a542522542d6ace63ebe903420fc384b984e9321c337f8072f49769c2b578c4f1de407277fce2629c4e8e9238f3e34652d27ef51976a5e26d197eda5154172f0c1fb38233eaeb2ecc1b1b4f133db512fa0a107d73bb67e992a4d1eb677061a7970ad2622bbe51c68a08107278878184248be71e3d1b883375152d49e790fddb71dbc8be930b2265407ff553ec8418e2a74f0a34d26edb183e409cfc192ad16abd6146f49096b8d918a395a0ece07a13f556d44230e7ed2ec5959b4c39472c0c1ab8fb6159115491de40b2f24805b031a6ff0b2e5ec9d3199774cef063f98921c42da1c297ae6f8c2ab0d7e1ccb79ccae90d35684061bdc482a41c634668af28a69acc1cf1e39b45384d5e05ca808b229d633a6541af2940fb9cbe9a3c1cd39a38f7a449be990c6191c09e571ec2853766c9bc15b7ff140727f06b9b60c7e871c7a90c10fb2cdf585bf1c61b531705fa2219231657116eb115b44424aa798187c390ffad42a0b4356b1769e325612369f11338520f6c126248a60f03587f8ccb3e182b8c00b10f080c617dc54b38f1eede8b56337a0e1053ff9df588748a769ac9d018d2ef8bd1e7c5bce2c122b840b4eff871ea22593c61614abaea9913497d4d8b6df88f513a9a6a105377518bdcef3a65c9366c18f5268cf97333b738ea48105bf2d6a7aceafb973ecb98213ca254be890ade0a5a70eb36c39c8c6a80a6e5db8e49155f0cec1a854f0e353796c6c7aceb1e262eb042bd805d098829fcddd33cdc4b8d88ac1910253321540430aaed445394b9675952e5170c58309e91edd52b98602776b5b25325257e3a7bd91557c825f79c343a8faa8734e2f136838c1ed28933be6b553f9c409349ae06a872d1afdeb3bf630c19199edde90f925f8e9f279f4b14b3494e067ea4ce96147a6393322a09104ff25bb47c1e3be889e41829b7b735839a87d84466dd553bc624decbb3607afacb9e6e3a36104b7d3ed338fa56814c1cff1f44732398a06117c8fe9e3991ccf07991c34a03104ec3e4aa6ee89da808610bc770d5e1ec60cb1262708defc4848fe7cf141f00061ab1b95956c2dab8de84afe33c973c77624971f387296d2c5687e631e4cc3075e0ee222a5b1e84d6147a00b347ae04a38891e476a8e83845790041a3cf0d64224a4740bc933b883c2d42a3ac6cb5536694c7a69e860cbccb8cb2e29bbcce8aa8c14c22b648ec3252eb668e4c0f9d06f1d7ab686988368e0c0b9f671f1ec48534e1b1a3770fdfbc4a3d9102eff84860d1ceb9cd13e6ebeac4f2eb6bc08630b3050701816a05560462dbc943752c59c6bb2f7dc8119b4702d3349aa68698d1a555f80b13366e1a5cdd41dbc86b270227c74f5314962e1a47c9329c33278871558d4a215dbd52255ee362f390e8f36f2fb0a3fb4d0719a438e2bbcfa9446f36d8c291fd30a6f4a63ca9d31acf05346d8a8b461269255f89dee293586c7fc5155781333bf59678af4b1920a673aecd248539f6c54385d696424b8f97d4d4ee106af0991a9d9716a4ce19987ac3158cab210520a3fc8ca07673e299cdad05bb79d517877122dbb32224f8828dc1c07eb21797684c2f758631e73ea008553214a92b3530f72e8f8849f2f8f6f867464966a4ff8416ffed0622abadf9d70343dc6b2c709bfc62b4fb466e4ab78139ea598796e6e4d38e79fae836c33416b68ef50fe03137e0a2657de1ea690f5125e4a0f35f96b7dea534bb8e13dc8c16f07aa15530967d5633bda9794f03ab68fcba1949370726945cf1463dab428093f8c65f9bd031ff1c922e105f7b0963fbe20e1fd47e4d4563fc2e98e93da54d48e2b48331ce17534b39e7e723ca311be79d49683f530a964b88cf03b70e9e8e55e04d9a723781ca33314e18b847ccb895ae78c6724c2c951da76d0b1bb72cc20c2894f95b3d2d9f57bee105e0eeff5ca1355529a0ce107b7f12ab318f3854e219cba0b3174744f88b32c4dcb3be32ea55bac374d06f3c8caa4c283f03b44f1118bc8917546108e66fed01dc2a7e5c01208af62d68890feb2ab2a80f0eb62b68f273a3089fcc1f5c966b13d9ef8c1c9e01fa3f45dfab0923e38a5d1b3797ff0c18fd44335cdc27bfa7f0fde8f45cb79fcdbbf7d3df851a4bf27f67fc5c89107ef3d0e9d2b4bee8c4be3c1eb309447d25cdfc1ad30f93e779ebef1d90e07d9265b07e7435d75103ff2331ddc181ea6e093d2d46673703cba972441e33f2296831fc4b4f153afc9cddc3878722fe629f7a4dc3170f06efb93f4ff779027fa066f2d438ee4a7e330f36ef0ff7d6dc2e5c891c56df063dfc810739633d8e047464ab9af424ed98233d6e0686c984b31c786f7c0196a288b787cb2b5310dbe475f479d91ef230dd1e084b449a288f765ca9fc1cf72c93c58c80c8e7daa9ca9528644580667d663c911aadc543a32f89d345888cb6163f0358568c65c89c1f14054b335fb6c0e5918bcaeebacbee4e69a0383ebae5e31878acb53d517fc943c0a1d741879c1cdd453c9c3aa762fcb8c2e78ee1b62b5fa9f7a79580833b8e0c7305ff9349b2db82eb2c12635a5091e99a10537cc7868a5edde285f169ca9c98e3e87cb2fdb61e1b2aa78cd4e891a29ab37b96e91f20aae45becc71b48c153c559f8f3fa70b39e5ac0a4db47c4aa5c7cac7bbf95f7d67474a05cfd3d7490efb7328dd14bc34bbd9f00831a637430a9e7b581dbb07761f75a4e1408d1a4e9811052755858d51161dcf57a0e047592343d966b7cf3cc1bfd41922d3d638cc70829783ce1f623af72c95ecc0173498d104472c46df768f8a89640613fc38b630591fc7db12aa194bd02b2eb6eab2264e666653c7a8b4fe511382194af0725dfada0cd28c24f4b35951ca2f8404cf2ab8a85a08d6a8b10500dc30e308acc86c744ad6c59667a894657694a24db2314930c308cea44a9875190bb57946115ca9243ed9fb23bfed10c14f512de5b29bc71e3d660cc1b35c29b7577d9c334709c1cda1c3346bbad4586d10bc105d913768464bf00082ef71f420da27e9f3b366fca0ecb2aff77439f5d841d4af48ca9fc2337ce0c7fa383b6bf7356a7c61460ffc9aa88f1d59c87970171566f0c01b9f0fe5993eca5d31e2c18c1d3839fd85e4b4195386ac03bf2ea4106be55353c766e4c00f36513296c77bcc51b89881033f4d4ae49f1ce5067e2c19daba932c821936f02c4c6c6b8e8a149d530ba7430ef28dc84f0bacbcd2eba3a3dc453c347314a29d2db3f0e3e389d948933b458981116cc1820d5938dd414cb5a33f547446868d58381d33425c7f59f48e030bd7facffb7e62c45ce52b7cbbf2f90f993762785ce1755de8988ad8a874e903e3e10182c161c16824128642c1cd7e23006315080010541a0c45721c0792381c7e1480015d1e1428281c1010120e101210080a120a060806060008060606000006080685414276608e65301f113ffb4f02b53e38081a6d1e2e448eefe4e0fb5cc5a118bde5a418ef40a7a3ff30dfdc617d9b34ffd7f073d12f7e1bb37969b31c27467445e227f755a721f8aaf4ed82f77e9665f5bdba1d93731f72fa089045db88560ebb5199a4d834c95ed3e0c3d8e404d1a98cdaa2134f16942e68ba9fba41b2ba300222fd6a06b1c1ca6a896b6ccfdc63360f18c3951a5ac915597f4aedadcef6e00fc63a94a3a3b4606bbf1e2970253b672a21a18e88f40dbbad6a3435a3816bde8308bdcfcb169b680e54db13f5408b1acb24ca84317ee7365b9dbc93794b5579120d7a4ae5240048fbc521b26c423e8706e73d5f85f5ac1c5afa60d3a589fa8152aa14171dd0cfd013d14c91443aa1ce2f0943e1a836035a3a983c7980437517be3861a1f8f79b8c1b1f927fafd06f66e40a472275af5edc8473ad22bf37e0afda5492f7681368876d3003b81786e5f64530fec6c8e0351b39ae5ce166f2c92c2e4f8d4fd982f223b601fc623f544a3904486588ffa2f4609c33ecec7c0752a2af78c9e50cae72365db4ffd4d9d5f034c4951e28cf8a206bc3158b8034ff1d147bd080f7bb7c5215e1bf86aa9ef40ebca6dc46c27dcf35d31a2dfed9e13d1ce94590fa7dea84d510c01c78a5e7e03ea7262b79991f097b16d45de0bc047f6bf565f0d5bd5466c0150d689bd21e2d4b011cf4ce4d925ee8b33a8c9d29b4da52c74554706903e0f8fc281d6b396f1f8c7a4b0b1c7cfe80e15baf1acb1670945e9f36ab648e2ae2c6d954997c2cc80ddee4ee6cb41a4e12ede533a4097a029fa4a4ec773ec05976c55a48b8f09349043b5b4a28a671479ba5b3069e22dc94788a66345e7fb8cad240ac505a4a328a82ff007dccce8d4ab68283ef9412e3b125435a8180fa0a4a56afb162ed4be7443bd5c403962e29574c00cb41c01e849dbb75a9b68cf96f89d63dd92d6eed27aaeca953f48c9963ea2c04db3bbe3d7f183d25a25d32a480d55f84827834a980560455393d3e25adda443cc509046da84f6a044407c91208f7a3f80a52b9c0cca6f2890cdd31822bc35c105bcbce2f53bf4d765625b1393ed2d44b9268cd2bc06ea4e09b5bcd5bac084fed0034fb21abf6115ab06e62168ddbf0feb95e4f7a4ce9cde2074d78a9d21c6d57f7b5cafbd44dfc3f328e2e5b5ee76589391d0fd824bbd2c3b7d43c9e8a2ad048888d77a5745e48aa7f2d7e3a12101ba7a136ec898891e432fcdabccf66ea0f52ae4d4dcc70754345454b8a8052b684ffff4a5f26add98134ee9460064916abf75c718583de2c9ceb02e2b4e15afca12711fe70cba5fb1bc39e9e95e39764a8cbcc907b0dc5c5486c44c9ca42be1102f402ba5b95234f000fe40158a79905ffbc14f40d2525f74f40a85166555322f1f70d2f4c06fb2b339a1b2af326ab74f181a4ebcce5d6e6b32fb9051a7e9cc2341be933e18cdf05087ece950e94975163b55431635214680eb1e45e63900ed1901c956df73349cc845b2a216ab50ef40049e2a0fc6b9bb803e146d5aedb7e4fdc789983c9ed8817ce44849c0ca78be6c68b159b3b8a97609dc37edcd8158226241d48b004d8f039933a65a25fa0236cb4cd74d06c94966ecad561f4a198f8487c8518669a09c76cc6ab824819b89d942325ca44e8dd2f9795174188ccc62a4d2ea26b5656cf15d7b2eae7a0bca35a67ae7e83d26ef945f036f900b1030d8f107ec1700caa943234993983d18a22a49650d76d20897aecbc70e29c154e2578275b2af301cd9257af42bda9126b6a1b51d3cc92457abbf8a38e9985799d8b2d951ba41710f283d174e059d883163a0ba2b624bc6ed34d99ce504e31b2c173a342ffb6f102b49e09585ad229b0cb2bed38bff2f439d37a65995dbae0abbc1048961f62ee362f04b4d213c22eb7ac8b7342784be7ad77a30a41233bf7b46ecb290f83833f9bc307696ea4777580c982a941c4f715fc34589d90f834a4adbccef9fe469290344b86a7dbbea28281f0561a5fbca2408e420ea552337eac8227057a935e8a52d892e0334c9ce596c635652108f790a16cbd412e96f3a9631fa5b76cdcd1d096178a1bca5a55a454cef8f0f51949590312af52fd24695dab7da521e9811855046b01d728180095e577b1c659665fd3208243b8f42903c07959e8901640a990bb89bc9cf2b1b4b0a014182183ea944e1013da0aa376dedf002b4136c0533c1ba5ef98054da57ff75b7a8ba7f0905690241fbebabec72460bbe53b34434e4158a7a4c3bf7172ab95a54de9030704f411f2e3560e193d60d7a90b8a68fe143a8300f0acaa5600cb64c89291f98d8fee99156c19c26cb4a4c86c005c5b4c74286f9e05416ed8adc9b0443dabcd45031a54a44c719b5c5a3f1d93b47208fd96a81c9eeec29d02c5bfb7d31cc52c95b380ce2b6ba652f96cdbd52d64d609064a090f33b5bf85ac07c2019eaee010202013b6c352bb2ada2e5dbe4661697ff80a0678af24fda14cada154ad109d768a34502f1ba57b035dd4df1e3662e40725b987da53ee41685d6fde29cc4b342adc9b43efd6690cfa5860dedef1a4361a948e62504cde6d35ba2d5a49d758aa21ec1439e477e9424faf023dda2409af5aec88c2b99ef188836c7cc52ba3de648ab5b38515a91687abe5cbc924935d37f1407150cd2345a7b90f4f2e6af4e259950ee6a07676a7f2301b1c4df9ebf65ec7e7ad5133889599596d0595e355e213ea6e7bf67101aceae57912a7985c82d11e8475c5e4aac4c3f01d09cb25441a6adb32a478309590a6e8a47d3a1c16a3c61608f78e70e7b7dc2dc45af4983f1c94cccf2669dd08f98f5563319a15505053770e6f11298d706013bcf3c60e3c727feaf918fe988819a1a215ecaf25eecb1cfe29232477c21f97a611945678d33728766099f9a14ab62dd78cc888b346b0709f917598638607ca9305263612d850c308be4401402ec908f0454546af35427ec40a97580069739498b124c803d968a10f1b8a7afa46ab84ddeb2511df72a696ca1b55be0bd0ecd58ec06909711c7b5a46a28d5547ee7cf1daaffc286401a45201e09ad4cec8b8c0513b6d0f097de3e6358420431f80bba358630827fa5459c551b535731f80e59c2cde40c25e39b763f82009b4f293383d5ea81074fda72bbc32106e688a10450914041825764d7fe7f58f2e0081c24e56152e6e8e475250be06c6d984c32752011dbcc593b5fdffbfeeaf99c5022f40395dbe3bec2e11dc014c3c3a2311c13d063a2f0fbad1447b00f87c80490824da5260aa5c85ca450b745158e263cab2ce3196d4056012db99b102a3100c6a3f99e76a64bac0e628ae133b3aa59006bd2d43959391a90cdacc87ac62a02ee4288121dc11159330b63de8a333fc1d77968bb7faa4b0166a25258b4e4310767b7a0ae651928a396b91d6d7048fc6fc437a0ef7237ccd4d4e66b00dd59f29ab01b4c9373985b515a7d989d8d5c8afad9b9b72a3054e9f8e195ca45e01c234ecb5d4994779a860d15374497bdee5f84618095df991e2744ae8a492ae497b846f48d88e4b58ac7ab46dd8912832d97873e79925ef0151a2d5a415c4b0de7a664f6a342d0ee6b501e22044b6513ca105ee3ca45117bee4e33cb01d461e78ed9c4204f99127636cd27ad2995adfe6206925d63354a8ce1679490e0f7895f46a9384f28f8c4a9cea12b48b675492948cd1c96aa7a9bfd9cb71adf730b7d83b4d41c28781d44e55213cfca21db544ed4b01a96fa24c8bc80f16b34abd5dd1b6899832e75082d3261a560aef9aee4651f5a150498e30e4fa30acb318fe21e917892ff3621c4646ad88b2cfa491a410c4a19df7c6091657125e3f49297b7a29809c2f7812f1e64a04c27dc801b6248c043de9bd5094749de19cd6007d97af0837aa29414e5148890be1f44c0550d3c8259770c19462f8b426bc3daa66ec46d72955555e0daf4245bc289eb0c5213e4353d5014c141575761f954003e3d0381c8e60a9a3fbe47607feab92f4dc8c66a6edad89ef52645c7d3f5570a112f49ec6a26e92761f6242c5ff7634567ca4fc4754e64e5dc591501639fd3c03633e8a2c63ede1316ff31e4988257ef77ffbcdf7a1690f58f47c9c42d73bbde0e289a73074cea6149252022552b3fdb1f9229034ea5747a56744e6707b8d03ec9245596b9a45173a2e48c1e24a0e1c0a6c41201584a458099c7f64a85d894fd7c98d51e352810e725f2a163144a8d3750888740a5e9439186063ba8a88209cd34aa2f4e11e91e0eb79f4ddba3e334eb5128b6cc825536940f9114ab0de49509212767c01867d7b918bd325894adfa7fab8669db630a03f7f3ea91182dc083cee7c83b1e644a082ebb1ad0b8ce949f2a433e7986f08c20120b1651fdc53667b0d20fb9a07bd532eae5700d443a401ba89f34502ed9f5477b62a52c0c666ae6daa0a4c659fdca3d28aa184d1994b09d9ed5661c919d488dd708f5cb11803e6132da66803d121633adb0b90039cf3170e0b37e65862bfca93611503c61074734b1535dabe239326746a2902cd32a92a0ad1aa9dab303e5c74bb4b2713989cee1cdaca26c402c1c868eaa0249d7ad4af0d6795e3d3680d09b79dfae20410ca217cda3c1760ff686ac454c08548961c5996541617154f0b87333c72a254de1e097bb386516a314600e041c444a7699478e5b34b5385e3f226ae00461af8b234efbf4b22d1e89710095321397a22a98179e92936eb336ba64d1bd35f7f30d84d07f344595035b30978e9b6c9311cd1f7723ed5346591f1188be49540c62932a08be7228441999a9d7a9075efd56ce0034a8c000928ca8416f91dec4a7433f24562e75db5632be999334f2a6726b557845a773768d8c9b8495dcdd4e474c6913676198968f139478a3f7e4da5a1821e8d25c52de7dfb4358ab744c8e44ed8a2e70a3a749341c53b230937dfde8cade31f4db385b80239c1e65f29b74681c5d8f5e876f113aac87ddaeaf0da31c1ef83695c3253864d4d8329a37741aba91c3e8a664a83f0b7eaf34b87b879b1757469a62410d51059f44711276347a1c5a3c7c7678d2f3a75368ba8c0174a1f834c1dce04c7c4fb5455f438e008cc656046e1ce7ff5c44b86bb6ac6fb3438021ecfa34b8e70ae7718788987121100e52f5b6300cba7ae0f7e0aa7d9326479bd643658ffd181b0c1f01042f5029cdc15d051113fd001a253c10c78b3f10357907dc7407ce136c8ce71052a0370a826f2a24443da59a7a38c2807410f999029f05be30586edd3b7c76dcdb8cf23e5c88df94e1ef26c93f972c6cc04283f90b5cc8f3afe258b0f4f9c19d1791ae707eaf9df318fdbf526fa68cdb9756d9f0ff85c2b923ef549a5ec073d4ad22f2904404ec439f7fb5898e27ac347a2b0b1a1500eb7f6f5635118d18852ac24ebeb50868c9efa0ae0cc5d007a401778f2e11cc6877d98036bf02fec3dfaeb801338993300e53cdb670634730fc83c9e28b1e80f620745123475ea5a4252ff5d807aa8542fb0c6f03bdb04e8525d45e9e91a3ec93c55016a322d8a7f02e8aad2b2be3d708ddd5d779dd63b89385111d5d8367e74d0f44be72d9280c1cc985debc2eced4503d3f7878fcc9b1bd1904df9d9f7eea4bd11db68b4f84df69ec13e23c66896b206517b34ebb6a6faeb6aba287327fb50c167388ca8f4cbb17044f47e952efcd10cb1e9d56c017fc51dc959d88c5bd7fdaf374f10fc9dfa2b812aa11ff6a9280e32e91b4a558c522727a8faee1a7ed56d7f00d2debcbbd3d9f15524c4491ce740c818957da3f17700e6f4d2deb0a37628284503368c8ad4b4a753eae0a4d637eb130700519400d800ea3c0c686825aef012d8b8fd9c8ed0b896918621602d239a05e24b81cbf602b9d0fd0b50000d3c38c7af7b035c6b9d8b56f782f5957386eb766d713d478c5f23e3e2507444eb57a78817369d02eed898104002d7ab2bf811f05f0bf1ada8770030925b691310db3640f63a41718a91e4b5d5a811222daf022e668e0cf15b5f4c1f40f4a14d36c3c7a7cc897444652ea15844500d1bda8c6fdb8c9946f094d1a96906f6970711a21cd58598c5d391bbfa0da0d26207503a85844ef6c57bf8683cc2400b9cb0df71e41fcd2ee2263fe17adaa5ef808f106af4cbb2a01c25b09da9e166c64c1749a1a04edecb6ee4285d50607d1a546040e8f2671d9a63500dc6a8577bdae4e8dbb37371dd292589c2163477b47401eacbb169348e6f2f1d141bc0ca8d7d2aa6246e56a4949c6e45f2ea7a064eb745193fd209a9c2fa0f38a01437e719a862ad0dcf6e347ad359e0685f6c6f138a069fd5dcc685f5d50031d0f9607636e61910b61e9e17ddc846dcc89508ad55a9045e1b13b70a17097c4022216d2102e80c3c17b6772cdd19298762b73ebbade239b2146a8db532eaf6d378ff39acc89550a640a46bf1fc8d04f7105280a6858c3e9100aa50b2f32b205fda164ed2faa9613b8b34b6ac1f1fb26e684c67f17ab955bc686244bd6d2e4124e669db06470e9c2f1a9804ce35a3eb54d1b3137bad0f8cb6d1140f8c235c56955be911004ca25d810472a90df4b6d1d0cc026440b77f83d2da8f0a17e009b6286dfb5d3f32e22bbce92081572c018b5839740794c965b8586434577dd6b4ddb3b36c6a77ef30745dbad89608b3ffd70f06d442ec0da09164087dba36f038c2820d3e6088079a6821e09bbea05c14801a2e787160dd9db28b0e2414990c6affd7ad95ee64fda22f1e1c8d6134e2054b4a7956ac8d46f5bf2813e45ecba3690cc10991684333e92900dd4fc9aa2980a385e63174ff12f998cdb4b39dd3cd4a9aa56d61e93c5113eb3473ec4d9a4c3b55d865d770c1dc69a5b6a8810e91c955d32f3208a86098500cbeb1628c9a93aa33a9b4eb22af1f7e33e77822d73b0fc3059bf9ba5f0f0726fdc4f4b639dd30a748eb8ea9e93e55cc35969d6d4b6c70345c5d16588c8233799272522d2725a20f106f60950a3eac8fc60dacbbf0e0e270a38f44f1820697be78ff8630b20a7386fb94a5ea3c5d11a0cdb9b79a743bf6f7f7de689ce18a6eabc4cfdbc4062011cb263a198ad9f06dc8364a2997469db87f15c8814167893b3c7b649e7c27ec59825a18a57a6c71852b23968563e12e22de09ac620a5db2036cb0f2706a4b579c523cf402a5b41fba1bb72953e281c457604453293ea7001e1b9147763d2a8ec587c2563f7ffb65c8f217c7395b56d648a779bbd6170c0e9b99337bbecd9ac8a82ec1b880eb4b33a5ee01484550b3a2456a02715a5b77c2c3207c6c158a69c578a549a82c495e5aabd192f10566b7a9c85941d39c1c580a6883081eb744b320304d88213f5fe64b0012f0e665dd7d62d9be9c345334a2fa71305ce4e2964071a768b7506f17b670a94ece1ecdf2e17691168d6c3ac277e2347525738196b25356f602a42400e3682b33f497062515a4bf4fed6db51d3933d5f4643451866922918cb67fef684d5022948a3515f854ae1d8e0530e84e7f274041d60005b25faa0f7b9e6137c68ac301c8dd56771048f7a4ab331d60a333053a139c4e21eb48a94dbd80872461441c324a515d0e0fa6c52e5d70e7d7e1ff1bbf41ab3d5b778e0e8c355e323b6fcec0eef53063b366676e23a35971d3510999cd83d27721f01e6ad1bb7b26d37ab5f8826089200e8fbb587294f18deb220b75a1b437dc747e26912ec11934b95e1551ff8b22e060eac8c09ae15d0b4b6be9afc75dda0deaae5e190305338f758410ce49ad9b9b75787b61a42e5dc2cb6a147a351df04e35d38c992bf985dc1cb8e87808ad4e54012de7ba0d2a593f142807ca71dbd6c5706194d3be79c0c6c11e99a2190391dd1970c88e76ed7dba93a5913e8bee33e0b6b32af9ba6f62399a6649ab915e1d6381083dea087d142d8171496ca623808337a0e0ebde307554e19b96ec224d0e9fdbc74834bd0a4a5f8dd8d93582a7634660a9a11252200046da4066c5bd4298b9669ee185d029c790179a43fc08b2ee170beecb6d3f57b19ef4e9e14720867500f83ece013679e5bc5ed613b8cbbbc4d5c9664aa790984b227b2d8df330200f0cc0a16c167a0225d6e1c1bbf3623e19077a6fb8fd20941a0b9309196968477e8c9a12e6cdf7c8ab8498509b2e7baab63fa76c25c4a5cd06b5069e952157203c83654f51f5a2b2ad1a434ed5ba2842e875d78d06dc8f595d91cb28b81a0bdff4da939462c9e045f8e914be8d359d3eed869d42da1694bc64ca56acf6000486c8e844982ca2f8ef43139f6e93d76016772d40177d16d7c457651ae4ae45dd2cd16a30f8b8f2dbf30e942c9011d8a2ae5d2a2dcf237e24e84445b37dd8435f4eeca89af80e7f6c0386824180445586f6ec821d866ac93da463624e075f1772cec40b134eb44786f78a96fff28e09468c81594d4c71260676f3110589e879aa0731a888f3b14428a4d5422d63f4076c15a77c2862b420ab1fae43b6e5f0c2f831379bf3af7fe9748aac1842d262487139e3550d54e1801c63445ce6602a56b4998b828a0c49a5485a24d2fe624e86b88d66051cc1a01b960f1cbdbe3cfdb50290400132a07819de3ae92c500584fa09beff22efd3cc9f6d7db49e5516f0d70b15b4168441806c818284fa3b031d5c5251a70e21e28e4a6ab554bed883d617c2418cab0310a7e6aabe1ff60e9ef71c54cf7c51e99d4ebdd9b48744321d5d1bbaa2ed6e2f6de673a1d97bec65d8abc57b7a749727fb5fa28cdf46bc475dd2fcb79e907491f6b935b90eeae631bb3f9576722ca14b48b7be2ee3aeb1d7bed76e5e5ddd6fba1f5dde7441754de9fa55bacb8751de8913bab1ba82bb94d37b5e0c1d5cc7baf6d3ede5bc2a0b435a4e2bf2eeb0372daf015d36b5bbfc43963ac8d635dcade75d23bacbdf7895d9ab6e48dd1ababf5c776b650691c7e9c2d415ac4b52d74e177a5d64dd8abaca5d8d783dea8ab3dd42e87011d2eeddf4d6f476e9f5f0f2d405ed9aeaf5ddbde744cfe0f9583720d17dada1678d5974ebe93ed4fdd7fdd55d10bbaff990675408ede6f4727a7d7a817a35ebadfbeaafe2a66b9be1facc689bf8c930abca77684f8956c0b8e719c67893cbcf07acd96a53f5706355fe36225188880a23fa090a0983b42efd80622050158e645920a3a3e79efacc0535e24664cb277d86a2b1fef7660e7f141ad76eb950045eb3a80522ad1dc22b8ab5b73c6c58ac4cbf9b9b927bb5045957a867ff01ed2017181711feca5a8f8696be0879c9662ba0fd6f2b3215cbedb600ebce62638facd876494f9e84ed5107e8e6d857f580f4dda4797c5757f5cc8de220280f2e4dda454c90ad1cb649e5d9b0d7d09af97b2e89b99a867dbb64a904208b51fca950279fd0046d09cd38f5ee40d794bdca940b38025a5181532d6c071840baabd90215e000569667e851533f098d91226053424ecc8565812c46ba5997b570300abb2970f659d5a6d528fa2578736588e78d00ed9a2555573bf7ac19bae4736776cebb85ef1df6f67cccd87173c51671ffeb0b2a98163707103cb76c5f70a4a6f42e04897f5d591c2bfc5ede80097d1e43b30ee56f59ff5a4fcab15fdd6cd74f8927b0eea5398ef55fe7d3f44d6bd641fff3c20beecee4ec0903f129e187add46db60bbd1da525e3033eec967aabcf936b6c2e49fa8003dd1574f501219874cedb2632f573b05c430c4b21e2471ff5086a4e74c6934d3f9b8368e66a687fb288eca34891b8448e8e2cc1b5c8e438694800864e62d8bb3d1cdd1b9f2ee83b8a2798a4a75dae39b5d0fdb0f59b374aa7e72ff32137519b284623602b02a7755a09f82ee10b290d1be4ad3d814fa6db0ccc198eda902a1181439a20f48686f5f016d9334c4c375937569b7ffd5523c106436c2fd8c75bb943e57a0c54429001ab84a78230814c789c1732e94046e8c4c017a30399598114fa5581e2126808ea8e405601b80c6418e6bff97423c83457052f37c1effc7dacfedd74108147102fd5fa380bf3ed070668e82ba913204d78e6272d5ffab557d7347489406e20b7e00a411a0532ed422048bfc0af00a0b1a80be0815509bd41ba5ee0270aa55ec546810cbf0722e39a7fd5ffc005aea5af11c90288bc48f03209ebf526023c0f64b247505134a600e5d7a8e7194f18a3d485c363d1b58e491b807a3bfc3748fc143b25120b08c5999247fcef837a74fc149423b1c2485860e00632697ac8ab9eb3c1aa39f64b0b029001c102d902b6026481a4106432498381eb0f049169b06924441db2e7ec6b6ac12881c31a647cae7bb34108826c1f85c3707edc54d8f600ad48d08d505c0632f9a6f40035952023490215d22cd0eb60a01e373c18580c2ebb02610f4d0913f8410135124ab856bbd78c8f1a972a8155098d446776e00d44a7a92750511cb095f42fd205d9ab573352e50bd60dac46cdfbf659e1d2c7807d94fb0ef2360932c11d28838bc08f1235000187ab2ba806eaadba654f65ef868daa2029c195d1347b68a1c87aeaad0b028ab53b972f4074904207f003fc003fc00f20db0889adb5de499429c9f2a95e21e722534a29a59412c3d7751fce38df70a4d134079709030935092849abb65d984491a3fee424a6e2a73fd8564f1746d50dbd3b9fb4051d2acb8569cee2bca8a43f8ee75de8c00597d4c6926c49cadda2f0b4af6ab999afc3166693740853f7512dcc277ff20875afca26b409c0600436669420081f1011392cf01a20e03043072dba55adbba8662db216c654740a22f24255981db3308aee1953e2d9202965094420821a23344af0e8a4ea90c58c1a353c7410e2e181808e5898836753caf4336ad4400974c0c268723ed9548b5f613a59a7962b2a115718bba40e2f4abea9e9a81506b93f71fb16962459b3c268b296ebe8f419f1d32acc419930419892df249d9781d44e154e03e407346e38a02315954e6a54ad96f11e21353c6c9c19666a830e54e8f2d9a6b16919e3a5599f3f5e44ce969119a9e314a652f9920a6d4ba2b63264809c8083c314a610a679b2c80f35cbf9606b187494c220bedddc454cd0771529cc7ae55f41f4543d3474002272a330a6dc29b92d1fd5ea1685d94de9c99f243b140695a49473d4149d3a897fb0257a07288c2e6245895ad3232ffa602b9f30594a1f269dd88e12c4f484a99389df9315d64f8c3ed8d63a612eeba4dd7992d6a5241f6c8513a6b53bef52ea3927c71f6c3a3661ca3b2a558a73fdea8cd584c92efe255325ac8e124a5948d09109f3ff870902e19d0688475a0a3a30619244d733bdbea47b4c973069359ddb36276809e21f7458c224b99f08ed163b23229530a7493a8a50514c5aac3ed8da2861ba50a92f9ea75caaf5c1662be89884d154c7bc18714ad8820e49984e662f947685f3794522615272ca387d79623d27618384593fdd95595ee41e61d0caf124b784913dd20fb6e208636f9a6625a1a4dc973fd8ce4464d89801f27b35403c6c887434c29cbda2ab6ea92ff7920e4698edaea477d30f223b7e4de85884a9c4f1f867e1977c4c14613ad1392de82ceae78e44986cc484e9a65a07718208533a5954f81cb7435cf77176b9adab62a525cf8ea13f091ac27c27ae5fe5523a9e2d84693e9c924d90760721cc5616b4bd9f18b24feb1884c184ba98af5d259bccd621089312ba3a84b0a04018554b892245ff75923f40184b527be6268a8fdd3312aa20082724e4e81f0ab36929c7cd9c5bae5c8c396b3bfc6012dd52a85a1377f4c1e4256a46f498d09d8422aa821a78c4a0830f26f13e42cdc9792fe5eb83bb6f64c8888d8e3d98b34bc9b65e930439793d98af32cb3a85f3141ec22c74e421f5c935b4db1380c1086ac08083e30735424254d0810783ca89a73b18b6949ae06f723c39be7630db6a9f60a5e44fcb50471dd00c2b4bd72a97b36c2ce4f88f2a41caec4e07f3dde75d0e6929f44c47c4d1eb113ae6605e7d0df5bfe35e6b92834909ea5f64b8dd5f34fde360f44ee2cd3e880d07e3096af4fb83e90dc650928e39a69d6e2fd5e106d37d3c21f465bb0de64bef954ac5b4adc36c30fe8d09195f4b0d5f91d0b10663854b265a54e6684b01e149c0c10184ffe85083f9d6f32fc813e75d4f3ad2601a17bdcc4b9ec3554783712c8993cb44cb9a1b9dc1d42d26754bbcd196630683ca9757fff94cee313bca604a2f222da7920e3298540e65c9dc741a135f0d19cce81883297fe7fc8d0e3118467aa83269fd92d8274e4247180c3aad5ce5e9f99ca7c4c376091dde9c86cee9c4afbf600af1966eeef582f9c58210e7e9a4527717ae7ab313b7994dad98756fd17392439570c1a44dc53cb1e537efa28e2d984d7afb8fa6e374bdd81d5a30ed7d527ae6dd3fef25c4fd7cc8143ab280a990915dead21d583049a635cf4787c99620918e2b98326d2d3ea592d74e4ac60b38389a091d5630763895b3b37c324c4a47158c1754c5d7621d543029c9566684c7765653c714f28ef3ba9495d7935f171f1374523ba460cab1f4e7a81b2a5ce88882c953b0f6b8b5143aa060b279cf13f3781e9c196a19e8788271942499605234f931718269b404dd1e4c124ac95213cc3177fceeb3f466e799604e41899730af6309e6a09e942cda2525983a9cb8f87e9a76720a868e249884db074f7e695b4307124c723577e4ee925462551b388e619254924cad9cfc388c61b439658276ecb929f972068e6298b4f4092bb292fbe89318a6f333311fbd3bfe05710cc334b7a2ba392ac9deaa55814318a693175efc4d46048e60184c67c9057d9ae3b10103d31d3c484be62f4ce2e2d45ce9926f2ff4456ec9526d56854b8b7535f2b282f7961ca6ed854910f9568fa6b2534962c3c6c13b0e5e98cf3e872d41d8c6077b17297da5dd84fd521766f99225d989df0825cec59ae95ad1dbce62c75b5792df4f52427333345c98bce5c63ea5a758da988170057de6020e8e5b984ee85293db45877f12c400872d4c7262e1653f6ab8273fd86c80848c88c858ae85b18332d554911686cff6ea9372b8291316c72c4c26e7e027a87d979c9418b42cccd9a4a44b905ac99249e2e00003472c4c9296539694c8115a270fffdc12b4b980031606a54774897657c2c0f10a634813b30f0e5798cbedb35af34c5a61bbdaca96b6654b75cf3ccdeece25439f943870b0c23c6641fbd4662e5c3ee158857176f4a792649978b104324315264d0f4ad250f231482e70a4c2b4ed39c4ce84bac0810aa307cf3ed87c00e21882d2672f23b951c34b1a9ec224dc67c9ab25c94a6599c2ec1dbef2f7e875f1bc14c6b724e50b7d5143059b145bae2eabb4bb751d9e3da51c1e85d1475b4e59beb38e2a0a8385eb9653ff3842612e25fe277f89bb711d14a6ca72594cbcb44bd18280e313be8f4eda4c505d042e9880274c27b7569cf8160c383a61545d51d144ab50ed45e0820924270cfa4d7bd28fe979d2de84f16af446b343cb9e18248980b489c80019b9a109639d0927c73b7b763765c22cfb6796e34149e0c084a95285ef94175fc2f4a6cade74ee78be74b1c16109c3e79874262c8921c408a4122629dbaf99f820b231250c26464428256f1abf4dc2b4b26179b115d44d2a09d348936276e364ad9f6bc9038e48987a2c6c54ec29c9b40477c00109c359b0923fc1843c1765d4a891c8e07884415e520aa9e15102b6906f157038c224267928f996653fe97cb089fc071a39333c6680848c74c88848238c96529ef49dd6f296bc0c3818611e7195f7d361fa10bc8d20c0c0071c8b30474fea55d7ab83072545184b94a45b4f8e2311a68c7867a87aa8572f5372124ed7111019365ec08108639e24fa872d9d43986a4457f024491bc29c946423f3528927d7c85108e37bed5a901bba73bf1c84307a0c7593d5ef20cce339f24e0ce5f971148449a92027ad053d9167b22aa881c7e1088479d4847eccabd97ad00d191f62839b0310a6d8d9d3e7fc273d6a7d8c1c16e4c7c8b1914118b9e4f80356b1ec6a634cbd5bb37cac73107e69a2fe317258c0c1f13132c2c1a17e305f366972beae9bc5f203ac0a93f6492ac928413ebfa930bd68115969c162afa830d8a7480b699652509ec2a0e24f990a52f468690a83dcc53b154f5c44a530e5ae20eaba39290c361f74bcaaae52988cc21cd6a669aa4b4461f435dd925e234a6a4928cc967368adb1d4410a0a63c5d2f1e3efafcaf409835dda2755923c6192c4c4ce897fe9a64e182f48d1b024cf0983c79ccfeb1f59d9df84b9a468116abce25aaf09d3499fb9519e4c18f6b26e9c7c4225318409839e494ae91111a7722e61b293827a7ac5e7e558c2e4392a8ad2719ea25c09838a0aafcb2f254c6fb735df97c4f1741226f5f9fbbe939230a53f490811ad2261bedd5d4be13a90308993d352546d8ab6ce23cc335b7ada29b47a92234caf7b4932370b96fd469894f849a8934c18614e25c9b13bd48a5ece224c175774db3d5bd88b220c625e45ceec449872927e6f225dc562441877f4ca2cff9e68da4318845736c9f7358441e4986c117ee9e52c84494a55c1e5469f1426218c6d3909ba94386f826c10a67c26eaa912cca365451026d94356c6c711e656098439fb870061b6bffe2cffa46256fe80074f1955dd0fe6fa792b15b7b5d3ee83c92e8e0e9bd712223e984e3a0be1fe274c76f760d029087562c57ac57a305f8b1e1d6b1ecc2e7ad4f9a5783099ec15bff6bb8339c7495a449576305556f2ce72296bb93a183fc95972796c74481042c8f3cf5acfc1f84969a8d5fa3442d57230c9a8feea8dd697521ccc696d9f544576551c0e462f9d4bd408bdc130ef234225533d4ae406937dbc29b96b0f0fb5c17019fa3f8657149d840d2651c7732baf3598048f3e7b6aab4e76d4605092da2739c53b7785d3609273eace1f677f5e643498a4554ad94f9413d9f1198c9fcc44d39db1190ca7f38c302fd9affc32984ed465a98eb7e1276430783c75d24236bb4dc6603a7949145d256230e5d35af154faf76c87c1204b67f3e3c67ad082c15c2697d2299a4c4ba25f30765730c94b5e30ca999e57f4e4ffc92e18ed635fb7ecc5ff920b66bf4ae2762ab7600e7d1d4a27cfa99d168ca2738e995fa7f27559308a2ad9e7795b2c9884efe027bbed15cc2ba78458e8c5d69315cc41df8ba59baa6052ded9d3b253d03351c19caaa37988eea8ea4fc1d427e90bbd242779232918ece4caae2ec9841645c1dc37faf7f6de2a28818271ee5a4d499a6ae9f20483bafaebce75ae753bc19c3c44aa5eba09a6607f1f3d9f5bd033138c6a32c593bb4b30d78f4a09a61b694a2979b424985c2d0927f2c63d8a86048392d23e8eeef079bd631877cf4ae82842c6620ca3afa969db8a613e41cd7897f061b4a6c43088948f32ae0dc36c5eda73ca63e8662a0c734e1f2b6997eb9e4a83612a532a0973a6835e1230cc298791a7f3bf308d6c8c7a2f6194ecfb829d7dcffff47b613ae195f37249d0d6f3c218ab5aa12e8db9eabb30fdffeb29f9dd924e7461d86ded24654f2ecc23f37bdbcbc4380f2e4c2a9e3b4e7dd2fa975b18fc527c7f4ecac2a5d8c264e94185c9762d4c6a61f2c930a13e575a18d552f6f924ad5baeb33096bc54a7722a0b9370e287fd598a99672c8cff7ad2c68dde37818559c5da4d5d2e5f613893f2f44f922b18b968af1ea65698479aa5ed9c7e84fbac3097a68a7dc719b39c5518ce7e2df4e4ad68a20a93a47167b25352618e514f4a33541843c9d36bda97baa44f915222fc047db229cc39a6e9f025a514e6901e3474b2f029aea4305f92426d864661364918dd1f11da9e8ac2dca33b4ee8bc1439148651da4de78a7cba101486731925cf7d8a957cc22016432825c7d013a68bdbc104a5c24e184fce29e72f53c1c7424e98e3bd5cbabf8739a54d98d5e4fd88993ea9e49a30c82bdd75c29e09839fbd27956f553909268c71a5f774782e618a919febbea48f53b184397e99c99dfaab84f9bfe4fcaf4b09f39e64e296ba9c525027618e3f5acdd3f98f1825615215524f4c353bc945c298f2a9c4c95d694d3b481854904f9d5a2b9cccee1126d5f93d7ad6aaef758e30e82c3541da9b6c5e5d234cf9f49274ae7e57d531c2e426a7ff1599351b5a84b9b2945cea278a307c8ad7e9de245549893056aa4ef52375748a0883c9a57b74c58a501ec2a0a4943f076d6782aa18c29c93bd5ab6ba10a6de17b724ec57384b7ff1c38330d7985c2e72bfab654198f2a4104a4e6a5a9403613e91f1e649de2f95058429f8772595a42f53d23f982d4992a0e412f4c48ffac1bcf9bb21d45c929df6c124d42939db9412cc523e184f8af638a54a1247b907a3a9f423dea2be45d3f460ce4fb597d3cc83d96d47b6bec9fb56e2c16079754dafab787f7607934ecfad346d3da9991d0c3bea92f4252c8a685607835a4bb24952d492974407c3bb9fa0533acdc1242e49a1bfe29efe921ccc9adb41e9949224d57130557b9f54c2c16469fed2c28434417f83e164f9fc2eba1bcc7e552a4ec5d0d1416d30578d8e2174101b8c57733d622d073fd11a8c9f4b2853f293ba6851832997905d49fec84a953498b37db455caa2c19c4cea6798588207f70c86ef4a65a7543318e4fa49eb6b49977019cce9aa372cc5899592c1fcd94c6754f2b4dc8dc1a0c45f0c79366bb75589c1247f0c154d8979f5255961307dca5652f894625fcad7402f984281c1b836d23a97dcbf605dc6bbbd785ac8c5eaf86c51f289ac4982ffc1565e30499d8332fb924baeb5aa0be6926925593e8f6122545c48aaa7aff821ac2d187dc6e74eb9a985bdb2775da6c6abd7ebc5d3f569a9da56160c2346794ef9ce55410d3c6a5058306a48efe05a5fc154826acddeee18ff90154c67294ec6e560150cead57639c2945052904851c114dac376baf2d3693b3505c35eae79de393fd8a4600e1b65926c4af688b61f6c782021231905c3b5883a412e37c6836c302828982bcd9224c90a5a6e4e912718f54a3a4ff2f555e96bd47082b93dc6a68d58dca86e06d504c3253968cb6ef557ca144131c19cf27a4b92a4be874e9ba09690a4c52a2c2518c3ed7c734dbe888e6f25c1dce7a6e494d57bb70b09c66cebb174c24d10fa3e3b86495909fd1e5c94bc26c518265982bef0dd9e2d86717b33440cd3a9ecbadb6d7ec8304cc2e30459329e1fc46cc230ca49f15db9aea4124f1f6c3ed0609843094af283add3c88d326098e4fb13c424fffc5c930fb682444066e42f8ca162414bd6764b612e41872f4c62e1b405ab245b762fcc76268a67bf796198bdb45f8613afe3a81ecf56124901e2217d1684ec5bd73ed8fcef5806780793a4bc4a76bcdbb8bc201a37928709d00ee6b0aff77bb2223550d7c1b0196332342f1b403a982dcc7f4e594bbe2e3938076349fa4e89e19e1fb3837230d7971c95837e1de360f0a482d041ac2465c3c124a527a9f49c341584928d1a6f307792f44dc5995cd51c037483418f29a57298c90949aa65886d305b3ca9233f55de6ba0e7410d1fd0280107870818816c30aa992c39467e7efa6b0d66d39ef4a8c5a806b34793b55cb2c8fe5b300d064fdafddf4af2d1602a9db486e797eeec9c3318deabeae7e33efea56630088f23f65bff83924f1a60194cdadc73d6de3edd514206939ce3def3d7640ca69cf3c8d28ffd95223198fc3b89bde25fa353270c0659420a06a3b598b0a1bfc42f98dd84aa28fb9f7126e805c3c45ec3c552b06daf14b4546fb6dc74494bd205f3dcbbe9d91dd11d6711b151c3a3900b66ad68d5237fadde1d8d238305885b3076f05379f1839ed3e1c7a1164c999697fb3e6300b39055d66856cc5bbaac7026a79cdf4b59c482c9d34e094ab89c2b18be6409bae72dade2bf080216a040042e98809a26d00a7cb977785d6d886c85d1258daead94c42a2c77a544f9091da9603cf9ea94ec789eaf94e0140c4add892daae45c4b270d11d0484f402998c410b95ee27a2b58ea83cd460d8f432460140c3aacadb3cefbc14603a1602aebf58a71293f07133ec1d8714d977f3d22726494ed009d60fc142cdef67e12bd0f0ac22698ca2413cd32edb4ad432f036482410997bd8ff9f62f7a1f888c849c11912598367dafa42474b76cab1aa804a3f89f244c8c8b93ea43a3c68c1935108660124cea5795df7f1dd038211e6a238804e3a86579f224aaaeab814064bc40051c1cf908448688d9cb4812101a770c63a95c6277d06fd501a146e2c103746424e5658c243744560535f0a8c0196359ed12844a7e128262182fdd9e891a9d2742ec32561084238651de2c98ba4f0dc33c6362c9134bc7eca84e1826614de5557ad4b5985f304c96f2626aa2c5c1c1c131850386490e7d27c99b17edabe417c6fc53dbf9a2993b8dbb3ab116eff255d3f13b7e9e9ccdbb5e1876adde4b12345e183cfdbbdededc85712d49e275094a5d1894d29f4d10a57d827a2f1706d5d034b58f7f4a3e8b0b732c3ff1e27b720b83e7ac7897bee6399dd8c2942eeff5d80515cb502d38d178f9acbaa07a5bb93c7a4f0be3e7f50adb4fa5fe4274b859983edb28397d9770b230f889a6ae73f0b8658a85d1cf47ffc84fbb9d5f5898ce4407b7bbec15461bebca15a6f30da9a2f7d30ac42e2cca8ce5504b277a95b4b2fbb7362b0cb282ca13254fee746815d65636bb9b4ba3f19d756fe1ec842c13cfa20a838c097f429c1c97479c0ae3792a49e7d36e264e438559dcae4b924c7a8a4259123f5318ecb7cc47c7afc4b74b61ce65ee152f67cb49795214b5bb3aee2bd7a76dad658892b398d5a33075da553979432913af1385295dd8863eb19d47edd9830b85c153570afaea2bea754440e1ba5b8a5a49ad4f982fe9bf699fd21f4dda13065b3dbd2b4149272849d7097357b8a09fa4ead239bf5e384e18d46bc7b84ba10f3619c7ecdce881b94d98d2b7a5aa9cdb7388f35d5034613aadfde741492f13c6aaffa417ec04214a4988e1e038210f72983098a5d56c0f51f273b62f61f4b10f4afea44a091a42219c258c9f4227259c9c4feeb815e12a61f8b060a9c3dc892fdaad0a6ae06183a384a9848659c951547616651c25dc24cc2f4ac57512164a9c931cc249c26c52c5933f6ab288ae46b848942b5cca974ebcc141c2a04baf07a5fd5dcdef7b8441eb99de7aadf4f88f239284d59ff8a88db811e5fa12b9ee107b7b59cd3ce11a77a58212de3946982ddde52aed27d494d8224c9e54f80a72564ded451179892faac43bf3126152feaf972daf890afa21c2f425871357f62fa9b97708c355db7fceb69e214cdb77ba5784acfcd10049f60a61ec38d144b124e57ac9470873e6b789bf3519fbfe06612e3be526990ef5d3f69f208c26331fd5e402618e3ab25f8210f5b59d0e0e10a9a552b1512bfb0aed4e69f13c07d3ae87bc0e745043c62d10ee0f6ec8b9bff6d12b91c1f9c1fce147e4ca8c2cf1751f148bb714626e25df16d54a506a3c4e759658f1f8603c133de588aab894f3116e0fa63ad94ecb98d827057170e8800648c88888c78dd343fa29492d0b7342270f57b2bf92d5925f94c6e121edb157d24299279d34031b77874cc35a5ebbb3dae325b19e46bff4ea23221e36446670eee0ec603ab7ec7152086509ae0ee6ea4f82b613db5309277430aa7b8e65793ee751627330b7c94a9e2773474c2707f3f625f18adac5c1249ed2d5c1c1e849e9e93149c91b4c49861026ba17dd3de706838652fa96a349137f5d1bcef8545bb939ab4b66d244ad6851d7e4d860165929cec7d21a8c76764950693f4689abc1a4be453c76c54e17940663ef2549c9374283a99327c9c4efcae549ce603a25bdc74fd946891e3318e647c7d22e6530ec08919e7193c1d41a97b28dc1fc39aec991264a3ea518ccd7e361e6bc8292c63018cdb4fcdb6779972e81c1204dbb2f1844ebe44ef67bc1b027ac855c3bbfbb0b26a5d484e90b71c11cd482103b275b309d64668210134f44470b06b9962a3c56b260d0f9d35b5d8d05a3e737256cc65730fa5bba7072ba24955ac124447a6ead4fd258f2ab60ec92fa79a194b426f6543048b91735da340583b070c19485f2517a2918f6635ce7fb5130eaa8d86f224ab47728983ba885dc18f917fd09269d24bd52b932490e39c1e8490aed67254d304965951e6b4cae799860104ab4a52429f7a02f4b30a7a6aaf6f77c4a3089b85df83709a696d71a1d91f2be1e124c49990996963c865174cc536951725bd08d61b494214edbb675febc18c613f6c4b3b29c180635af1794cacfdaf961182f9a7af219cf345d18c633f5f3e8a76098ff656c642bc030a8feeb906f3a55aafcc214dae49c4bb6a074dabe30a8d0e14e16a1f2a8b917c68bd6a2836b658d14f1c2a4ca42dd785dca268a7661146149ed6249b2a514e9c26c99a7848b622ecce19df359958f38e1c2b4fde14d56ad3a93bb85592dd693203d4e3c6d618e596257b9a96ecd502d4cf5be7ae2b9cc2749b4302729c5ca836cd7d02c4ce13c78ee139dbd735998449ed83f268a85295b49f23142b030456dcf35695ea94a5e6110aad54a1631492eed0a63bf9eacdeba15464be993480baf92c40a838725f511b29d6f5e85b184d01f5a7c76b316d8ba80d905eeea22f0a103101a17280e5e0ea34001021084e402fe22228f6a84d858000046446eb8800001781d9c1b3aa0716420400022218f6e788d101b09880100020000000040000b00000000a080f71a342e20d2ef35683480012b0262e32c20008900808848c24100019c91e31c0508c019395e2384e300001800041210f21e0c0040001e708020d8e005828cf800c477812023323e448e87470252173666a05c24af3123152071615cf53c13363d086dd25b00e1cc4804485b1c206b812023211f22323c3c1290b430c5afe8a973bdb3307d2749127a61f2a7bd6561aa3e0b2ab7eccd95508d19337e06cff8199bb1c8aa64f9bb1cb38166fc8c9df1335013168906c88c54807c050d90198900e90a933039efe53fce2e5e6346fa1839337203d90a0419f11a336e7878242059617259339959b26398f083fbecc69987bf1040641c10f436448a8dd00041111a202332406e241e212dc8558cbcc88c9f61805485d798910890a94840383352011215083222e3c60c0f0f04e4294c996b623fc8c714e67eef20634adaf81db314064f5be2fdb32895a693a41885716c8ac2943a7b10f2f4e85bf364288c9e2dc8bbd7b7fe17144611622354e446e315e4274cd2579f9f245e8ff484493095bf53fc2a41cb101b40f84e9892bc8e4f52d65f7a13274cbda6b43de8b44f3a64426ec2e8a62b3c090fa6e2b7d4c4952aa558a92e7d651bcd36794866c258f964dc73ce0942942270c1041213e664b2c7eedb4a55335ec264f52727597c63d52a6909e3765ae94f59a98451fdc4932ecdfd9f7a29611c4fd13357ee244c954f98d60f3296ed9384e1d35c0a3e62dedeb48c44f2f5ae2297d6522be4b28dd07ba71adad9848439bfcfc4550ba33dc224687a16154a6bfae97070a4238c955f6ed1ac64bb20fe60f3b05163cf04d908f3a8df761125c60873caa8ee9ce453d7cf2fc2f4ab57823a71f74b5a146192f784caa23d904c8449ae54ce9ba1eb732d8c08539e7ac9a3a64b16cfa143983aa7e792f25677bc3b6908e30533adabb2562a540883ca1bdba5122184f94bdb9b0e2ad7c120cc7f5549befdb5fc1c09c270f2e93022fe48e3200361d6bea43b7a98db5c8e0e006170cb5b51a2a585ca655243860e3c465ea4d81f8c6392e82728a52ecc637e309ca04a92f298c925057d1fccf94f2a4916bdd926a7201f4c3a66265f540e22eb0f6a84e8600fe6dd3693c3789204bbfd834d0fa62ccb97041f59e447b4069907a3c55392924eec9c04b58c0fc183c984efb2587ba972971b7807b358e9854e2e27fd441a20891d4c1e4caf6ea7e8880f505a1d4c9220ed9467e9d7ce061d0c7736d25b3cb6ce723f0793a404d9498e92fb602b72304949dd79cebb566a486226838c8371b6f333c7a46839367e4424657088c68d942a483898f20815d3c193f20daaa8979c87a757fecfbd56da04991d0e0e37184bca33f7bf62b6e19465477f967daacc68241b4cd963625e0304366c9c909155410d3c6e906b3065c4c2dd67cc254ba5de56f24dd492d4e239483598d693f89877d134985d3cefa9ac9dd4c8190da60c75722c693c4b797f06c37a9c1394ecde0cc63329995c921cce949c530673573ce9697d2383612ffcd8889534f623442e30432167390663f52561f721db90111b2906d3e7effc24491eb530988338c94b54f23269a604832673c962ddad5adec5ed5a56944eda7112e4174c3254099e37cdf482c13bb848f5f4c92e98db243769bba28dff13a8a0061e2e482e98542c754bc7452fc73fd86cac6dc1a467ba3d94ec95fd93c8d91101c1d4827188c0059d055350c2633cde050ba6a454a509f369547f2eaf60b090a3f27cc765f52aad6036314ae6b3831ac82a982ca6c71331f53962ccc54052a1b94aa55ea72d6259ede5144ca17aefe24af61c264f2998af3ca5d352b28a783ca360ce3115fcb2bb75be100aa69caff760a97c82c9478967f1b2934e30951244454d5213d7441f6c2320890648c88708da07b209869393eeb3133da88f950e24138ceb2979c96df15c82179eabe7243d359e0f360fa3412ac1e8a59f6295ee83d13c904930894b7fba6e3a5b094a9e4622c1bc2545ff1e773fd8f818a61b517aa55f9d52461f6c42e8c17b84241e232033decc18e6fb64d9d1c35a9f30fee0699462a0426c8ab4301a0f6880bc203d80c43056ffc9e1243fdb123e1f6cc330d8a7cf262bba73e3000ac3a43fa4cbfb97efbafec1e6f13e001939323ea42e18d6ac59b433710fb54ad1732895a4e4e2df0829d6c030f8a8784e2a5b481715d1b8210210fcc2e47174cae7259ed2657c726478388d2f6f230830f819274404692f03847d61d269eb63ae27a47fb017869357c4ecaeafa8517961be8ba6d2a9b57117a6feb292e2093a8a757461182594b01667920bd3769f24fb4fb034b9425c187c2feba756d589ad6ec1c69bab5a0adfa51dd75ef25e50137eb0d53822201c1c323e3928e0e07819203666d4401c1c2c40c1a22d0ce22bf7c2dce42446570bc3d5c591abf849848ed1c29cfec4ec5de66fba6916e6184b4a897f0e29a69785399824a8fc21be047142b1306a7ce509164d4ed21f58184c5578b4e4c19368e32b4c9dd4ce6d4cec0a53692dc93636bcdeeeda56186dacc489134dada88f082b4c925ab14f4ae9e0a75d5c853989e96c71848926070668daa80a9368e6a2ae63d2dabfa9309992b257befc537e212acc16a746cfe675d78d33c0536061561d6fb39d366aa77da0298cfd5be779f6a4507e82a530b577f04b625ddf4ea79014e620c65198c3a5d47759944461300bf5ef24e93db92ca130697a0963a269e75853c9fdb48f50e169003ff103f44410b013e911a7656aefe43349c809c3b48285db8ba5ec953a4775f1e0260cdb167af5834c13c6ad784947e4a5f5e83013869d5382dcd88c983086502a7e5749f2323b9730ffecd58e7f32a5e7ab21a3aca025ccf296e3f2f9c70e0faa0496bf4af5c196dc10c112e880064872e3053b05a484d1eca4a9f211da4eca380993b0f8d1c46e64953c3ed0c899718792304d89956a374c3ed8426c00e13d641c1f7c62244c76af5fb515e4724148ce1ed1382322344a900609b369114ff3a21f7e1f3e42b170e9dd6d2e887a59c54fb2ffef9ca90374844168d7b21cd637c274c19364d7297950254698e452be2df33f96b4651186932d5b8a3028d3237af47789308baa8b735751829039228c95e4bcfc5582fcf2d0210cbab492fca8723f416c08a3aa0725cb4e8cebfd0a6174cfa1795a374218439ab60f2dd5208ecfda56595171d30ad71022ae65dcca4f4198468bd82dd11d1716d141c8f1b00b84c1646a494a673f893c68dc08f1a8e108085332d1d3abb5ac05ff60fc2d495e4ada154ae68a7e30e9132e9fe8174fa50941807d30f6de9d9ede356444d33c66dca071c807f3788f2511d332a2f4b80783b0b21cc6b41bf22325b002eac1246fa851f2a8fc1425f6c126e2a1c6031a373e46540598878951a8642c858342d27030168c44025130105236cb0100e312080020601c8ac502c2509ecab9aa071400037044263e3c242220180705428138200a06c3602028100604018140301004851e12b5466aec6dd202d642c7d1043a3303790457422b52b1483765c50ec603e390758149ef1a47ccd51a256ad7a2e30d23725ebd5c390a40dda5cabbb9717f88fa83d8b896bc0928eac4fa97dd102efab30dfb58fa3bded1b9d10e8546d76a118e1c7db2e8731f9f7d1d4d1ccaa4110e4e65f18d10bd829455e32ffae1b30f91a700bc1ca8a529d8343d6a8c7151890a723baa76852b405a0c5136421b3b0652fa88f6dfc192d60c7b43bffdebbab681df7f89f73aea123ce3b6576a6073f0fc6250e4f2cb84ccc00f8aced3ac643766987c360ba84482a1b0f5c1aa89f0bf35e10272e09a962168652212740c44ce96238fbd12658eda8941beef21a08cd2a481450f73a0818bc4417dd0255067cf95fa01007124cea80f88831e7652235a0a50afb1d7e731cf341e044ae71b71316280a05e5c349e3fa2d75c7f7a3f2d8db63885800b15fbad77d2f0671046049a64c10145aa098011d16299e65000abd35f9542496231ea457845d2a067cad9e46a030ccbbbb8ed374887fe5c083bd8fc8e6cc4ebccc5cb17862b9da243a72582a8118a599d8b36ba711a87d89898b4002a655995ca2be404f2a23bdf430e88093b2a0690be06977f38b56e30d0fab96c7ea6cc82c62cff16440f2171544c2d025c61f1925ad00fb72917faa9302493f43b14ee2a6f4e310a7bbcef9458daa0e7863a7c43752e0fe720907224f53870c716e0a3225ffb68efb1cd035717e80f26283493f19f6d3b39b8414abe59f047bc24ef9d6d088edc2de906c7843cdd04319a535bfd982e4459fd26ca2b9b1228e28e41497eeedbaf08a387f5a87a5e51bfa44cb2b9465972c196215d675b0f2e6260dcd11756f37f755e2c86b95d9a01d8102b782d16da29d22b8ae7b4e6f8883ad1f322e26fa57c80bcae8b747476d411b7adac62c233c2619ed943a4b876ea232ad1861471462392ef62494bf68e048d312d54568079b57794281c91f827e3c0a18f157c5b0e1c65d24a58a827e17c1a5378c930ffd84f3238cd837c11b905fcd88eb61f864b6079f339f45faddd987b29f6f8072eaaec4800ddb11689b7ecd78fb5204973113050a121e0fac233b40a106490989d6fff2515f0da8486682c5b705cf471df6279ec4965f206ca8896e32b9c34d589a659f18aa264672d106e0e19b5d3b1c66f6390c4519a8cd6d17f805b2f8677b6a7a8bb647356f9bf3effacac6914789ef01a39d5d775d634ec2c8921437e56501c775ea6f98e4ef902b0adf5ee88f9aa49c5f7010e6b16e6efebe134896b0c5d3c43afdfc09ae7f0588987216b055276cabb102522c103b7badfc9c42befd4e2deef1f42548e55c7de9985927e5a47be46ce1ddece4bd331a29b5a9d035deab7216ddc26c9261fe9920ea204740d41889a950cc5561d63b0070a4f8848e66172d69fb4f4a9ef5ad9a29ac9fb3ac05e14932e6ecae241ff586980da5d5d4a733f2f2a57d089ef538953137aa0883dbbd042f48f1de869728dcb57129f478199153eb1a7fd9760b4d585c6c808a537107d605516310bec2e7388d483190439d129e099ad6b04d5548a42ce1982aba0223972b107efb78e2568732db94ee9bee544b10f31adb6b1f9d8867460ac1bb5c450e40a5985c0c316ff80aa34138a361bfb291b466255a3210c72f296335e37c5073a314e7775f146eaf05ee224dc4393f28a7e8ca114448643bc0083899467574d3303e26854d5aa238a4786530804fb26961674882cf1a3f164063260b83eaa32e434b781cfd15f4a93c3bf8358b63dee87d00e9828574a92a5d539b45c37f24bd7a8c5d683da32b2f45e6859b6bca23221cb529d871ad042bda4cadf28ea4ba5bf07521e0cb0089868bbac1be6d46010ff54b331c81164211d7baa4711a898b9b49a27257f7e4017886c659e9aa19e4f9bc9bdda25c66d0fecde1dd224c7b9eab77faf1fc35dddf7aac41fb3b8102a30b9442c76cb10dd6b614bf1e3e692041d902e9b4e035a1d82665a484b778bd1bbebfb73897f9e8e0f23423eacc2d750314760364849fc49bfe1496c7f5671daec006a8f4b8305a75c00b937b20c78b4a94605cb0ae9324c8529847636b603f7ad512d5e02c681621a52f767082127b737acebb3b5b11fd93d8039027f1a47afda0ee071645304ce4bd77e458ee384159e6b5b587efef9df2066adc1e2932155486c8ab88e3a39f5616307c6ecaa44b573ebc61c2081d89d8428e997d17d016ef43d30b788b71182ab1e471588c1cced97288a4640029a6f51a74a070490db2c59403c751c0aafbe6102aa00dc361b6bdec27aee50f94a7be8695ece5ec5dcb98a80a8e18d4dd63c68bfd2bc9e14d42ff2ceefa39cef1e11084f0f1a99495a9d4c9f412748abb5e2fd1e0f45c9bd0608118b0a4460278b3723903530dd94d965419d70ee3a751e3e04a9e15008f8e51223fd921cd8a55f2ca5702231a0f64920d6b8679e79eb2c9ae29045d1bb37d4905abbafa59cbd9c5b197790ba6fce4a497be58a6a2f7a1cf12591b29226dfb22d392429adb9abc66b59f03ee64fd6d081af2197e63f971f24841804c03b129090293e14dc23bdf2f50bcd29f3e25e29e31f0b9ea46ef955319c7d788e140e97cca1ad466a0785adee2a000653f7b5b09b13463c8368dcefebbb4e761c2941bf36b6dc215e57731fd5c6633297dd722db240eacc4c98632c89e6a23d3e925db23fa3c0d4b176cd1cbc307bd7b22f6081302011e1eed53f45c63cd3d845e220701a9600d2670f78b802e9fc65c602827741b57fa7653382338de1e8f7d485d20a0bcc46f2472c9fa2bebf602c1e04ccc41210268a32699e42e3b906e50bcfba8837ca9235d17958ce868c2615f02ef8e811ae58cd7ddb196f550b521519713a543e5f8a62024e558258d37082aa6938d7e701d01b83d938945a8c84b4d1cd167b00ba7dd9edfe07c50e653f2844167133d42fd970dfacb889930514cc0581bc948ef89fcf0bd66b7968021791b468518ab65870c72a1ba2ce420d3bb897c5a55d275278d0fa7f67e638e742e83e872b2302b4b5f7bbe0a70d290cac1ac56c52d24fe589f3e1d3afcb49127ddae7004546bff062c62ca2dd8885450a3219cfd50fd84e4da6fe0fac56bcf6f9c6bcf64278b465a209adfed5c614a057ededcb7bff2c0088965aa85afcfc4d04273366da26af227c9971d84092bebdbffd14a074121d1ba617910216d1bf8edb6371c10b601340450413381867dc080cc733a50fffefebfafb7f80b51b00fdb7cdd170e0f13237e0679bac452723101dc83234b5bb1196f5165dc2aabc208994c186295fbf700b7d18e255965977a86f0eef1a2fca416265cdc114473f318f992231e0982fc26a1f35c3f8f7f5a43736b3dbddb3d1056673b6da7f83a3b609a38a86d8abb86a566bdda4413ffadaa1d6551aa6a0317812f3fc1c7769a3b8417da10e8a08cad44af99a7a8b45329702d65d234dc41b0d419d5e12c5abbb88b95e9f8161b439610ea18a474f62c40284c81380bcdf8c038a89ae78c34098f036c5047fa69d3dd1b878c53dfe695b5f8864d32d390a5d7ac2f38cb4d087e5984056ea6afcfa9ee0234576a10a784af94d92fdc6f56543da896a18e9d798f36a8506ea22f48f3eb5c22052a76de41527395f18b92e0b41ce1e4f41026061715a73fd0ec0eed3ffc31e1fc66525340bae443a40628a57f349ed51beda8ae2359c5f76249fe3f9f8429858fa18208315a9a298333d2380014232466f68e3d545191ccb7ab62e2a2984d5bc7ea28cf47c366a6218b6e0a8f67ec25ed86fb8b580b2cc49d5d9fa7647a0ad14fdb25e430260a5c28218c6e48745e3c70a16aacb1e4f074503a254cceb27b9cc0fe3cc191a1c72d1771522c51ec0a18d9cd7b6d9991028474346be9c31ccc2cf988bd23c0ee7c4cc1f4f6ab499061df36abf8824cd42b292014f9be97db9a9331eeef10ee40cc8561cfcbe8e4d364cf0cd954ff8d7ef613bb4be68fc195bc2ef2477dc962cc25551bb74b22d0477100727b0964eb70bbc1ac0baae647db86ddc9b4ab7cc4d0e12f150e764f73ab808b767bf438e3a5d42f9fa298dc7abaa9639303301689d189b64235f0eb415965d1ab3d466d6c6d1b07d4baf6075793f83dc77e41e28cfb80885827f8a506d32265c5918d036c3b23798a08f4b8268671e8435a73d7d9b09eaf040dba1459bb5d3375286baa334784e1864f848bcaeaca8607d2d18571c48938125c65eb8dfec2fd33904df9d443641eea5c250f71caddca2882c2824851a965b2751e525cef9efdddc11c54158d7dcf74f83f29fd681d17852d9d57f3948befa84fbec052cfb8109234973ca65d8d9e6bcef4f9854e5803b3aba0f2e8abad37568cc5464bde14ca3c5e76839ae278c151449cf0bb104080756b907292fe9ca1f72a631ad64e02e3066e31aa73d1b795b1c0ac4939a7314e94416dad496d5fb159fe49f9a1e5eb428c573b8aa35b47385f21441503d741872fd1730b2cbe066093f3d511c0dbe264f381361cb6f93e62da6e55c6445320de65a10040c8cfba98f908752c813f049a27450466a0517d21ce714b064cff6992cb05c330ad441f9b7862a904439ac5ecbc22b8e72375c6fe8d7c955391cdb338bff23904a681f7176fc53f05aaa07bf301b23ee5bbae34a9354369c25aafb7a39a7767509f09f5a364eb3ef5918d91ec179b9b594392f67a8fa435de527bc133ad6b489b2cc523252e7101ef287191f1f1b9854e4d2b16034c533418f616e6c3a96d426c1e246a90e70b8edb2bcd8e6e21fba0dbf250ba4d465dc788d6b750c00cbfba7e2477bf29fc19015786a729b9e3058c35d4a87b0236f7c74341f05766b4a6c51b7f990ce6736332bb7fa53b9e2882e5ee4ed3cdd0379cc9711b47fb751849d1f75f8bc1b060da4c1fadce78177c2f5c4daa6ff1c6d23eb9d9aa9b6e71369f96e243fb31702478756c77a7bebb81d6d48d478dcc6f9b44f43f9b8f29626f7d994b2fbfdb7c55d4f231dda56dcb0a04bfae52a949e6222815543d7c8a555e8241b5bb7173f9dadb2bf16c6d359273bda357b1ad603dc39140a0c02de290e0ab967f7d379854037bb712fee010c1b6e6b09d8e791fa9f9cb3bc5e60486070ab8986d4b3c480dde0e139601c63c3db6e80381975661f74cf32d8af7b38a2c15aa16710675ffa66547743fedadcdad5306fd14dd7813f249c0ecd44af802d327629e3f81be6b1e6010e004cf56a4faf4880a5e88267aa3a6143b4023ba0ed8f2d6bae6b3be370c09e5b89816926aa207db24e6c87cc073a506722c5a2d12d164a4fc0b025df68d9a52341d4142cf498c1505cbd386eea446a0dc7d8882e70b3adaac404fb45050d1eafc23d4576b21188fec0d7a55ccfc5c0b11104abb3bef167a6e10db4168d7bcd61d62c2df5106c33f57ce2cd51f9c26a0010e084986ec05be05b01c67d8e24fa75201be42b8ad29d07e5531891819b74cb2e78aa619632a0569c3b08210530dca661705a0b594a0f7b21cf172b7ae737aa68d87521b74265216d15274714d8592723b617e08ac5b7080ff010818583fa4619a6327d301130e484633028ec95fdb9c1e4906da575af185d373bdb36a85b4544b350418102531123f04398b8476dea4aa34d8c916dc5766f20dc60760d6fa2cd0aa41b9241ce69291ae44cb7ff4c2570c5b68a30001c1dd0fa442f211b1033453a6a1d3dde5b2298cc981ef9156de0659c54ab5fe378236de6c201e347b168210166de6508e669563220711a4702d104c0553efbb62bed3c724155ee6b7833365d579124d058ef11269c5f0f9eeee6ee19ed91bd1bd456d8ee55ef52f6673fc3bdc53f5d15d61e617c144c35d3752333791a4327592d30f0bb0da70f402e6e2790a3fbf842fdaf8da4b15611049a6b1f4a10f5446041f13edc03c0ffdb413a5ffde64d99e149fd68d044bc15b2c549f6ca4ebb5a80e1a4f9123eee4d989aaf079c113557af7f72a2af066bb5f58bb64e75792814005e2040221f6560e26ba7f67e8f86d98916a0442bc6a16464423454616b05976c11325b58510740ab5b4e222030173489cd49ebc29db438166d9a4dc29c8dd41a9c909833e22a67e56fdd3ea909ec3134f92edd68f6fa546e32e9803d93be02884671a1bea34854efde78ec6ecbc2487b5e2e6e48ab7731f0463cebb7bb9f6705b7651e911ab10027e22a19c9dfa324bf79f58f9550a965bda60f8fa030a6b25d7066f22df78ab82c01273d5ef08059ac93da83b522fc007601385e234f9207022f1bf4d96ca922b3d0fbf056b0aa5917e05a89962c79944fa505135c0339cb9c0a628096b65abb3fe79304a144fb142a8e0eddbbd63220c0f5420e1245927826e4124f7c2df0c75c9d6bc512680fb4bb91bdb0ef343ca5ff242152ab8f9c9d92af3b9913036e290d36d60ed53254d15ab979b5a2069914a0bc11fae5477c133419a3f144dd01704a3e7394026ec52d8f0dac21633949149988a4313cf04aa59ab45a70e03cbd269034fa25297cd3f4cf0185e94cf02cf8def2d91642c560bb061377f9ac56471eb2e908e0717acf103b1987701ba470dcba4c3147be0a26cc1915c99eefc75186ff1252abec10e15a52c67179ec0a4ba586ea31b0642303260191e6e2d1d19deda9d37e4cacde5ca9526068caaf02b6233613aa1d21b1473d9ce2e5d95ce5fae69681356248550d7eb45ce136b8028cbe1df541cad7953c25b197ed3adf32d3f9dd3442552f7767fc5c25f445c1a7ea7461007a2e3c30d76123307ac379982cd193ec6cb1d2a109ec509f0588a1b70a4fb0def95285a750f55987fd7460a90a46fc30c4dce33ff4b000f0e63bb1298c4681830dcdaf23c8a67db30e028f07db816a5ea9606b94f235838a5aed97c9fd322b9d37e887aa7596c4d79b6c5ef6715f28c44bbc4abfa191fed1d3e4527f0e0ca7865daba8858234f57140e3cb1e754f5c78370c587dff4c6e5d8f2418142ab553573f18979b05f95c489377cfa275a50118d20c6a33bcb519bb26b5e2c495b9a3a6749b6b4f921db042f860729ba77af44107b6a1d676c4a7c99b86565d489f1b302186761f4bbb386773373ad5839b53dc23655a6bb4d20cdce7d4dd7c0ab8e90211b3cc0cf6a2308ebfc83fc0c1e834d6dde20581862b576c5179b4ecb5f6a04a3f784bb73543a44b59408ebdc6a1ac8031710cf5c31171493c14ac4c3f9abb283cadb04a5adafc9bedd363e5fbeba67692caa69abe260c0d791cfa6d37e1055381dec62006db84d9f47421ef5e7268e5f2a85b3cded468531775db7d6c33763add8c7f2b89682527c30070e44b4d54102b476757c037d26d21613222918291bd762089bfe886d8172676723bd8c176f67a69663d59d06d2ca0babc54bcabf570a740b848f8987c959eed4a236bbc73ab7ca7138cd3962e80565749aff806cebe8bb2168c44b8d2d3c1c2c7fe26078956a5e887925ec48289f394a19ad789321abd6205972db2e6144671c9ad757a31db7fd97091db7e875762db7c01b601e4862242eea8ba4d980107701b795616499789361055ea7da88d4b1ff4c0455740b9cb2290e2d6c69268f7596bd5889b86f0b92823b0e43c4955236ef6f26c6dcd4adb9917de652216aa62b551a55af86dab96f4263fed1278db316990c2ffc914b2fe6f424254cfde29d37699582021d6708ff2ab76f21ed6a39b13054b6b3fa60e9ed2b681b050dd4b0af3efaa0ec45a912e549b4b682d289b1df507464d66ff2ba8f2e45e9d3bb6ec63c10fbd82dd9a6e49c945233587e82da87eaaee4490390263344fa1660982dc1959f12574ffde37845983cca07922fc0f75319ab217e664ebdc79b194e02ac958ab816519587e1acca4fecb0bd963aba110cc0a754e3b1413448113ce9cc9dec2b2c9880ab7fb3fc4098a8d445926786c6669651713894f8ba69b0698d636d372b785030a76773c2ba0a11dadacb9cc33f18f76e9134898c88705e007f12d45c3f696de4481a13c4486fcd568b47437371ceaeb5c3bbb211b4f9aaef4c1e3a0f96683ef45aec90f51bf23e2dc2dfb9ed2d2cb7fb80c7520b9dca4032174de30e58bc7f9d9eaf40e869664e87d170c6953c16d12b4758d79324d47c9c34f97aad2aae7eb61c798f9af34c2b96625e0536c5b0931fdceedec387b929fb164a0d5715f16f3d2de3bdd5f7d282223f65dc46e093632d9e08cedb10ed5d3554c263aecdea0540a43a5d83c29314bad4497d3e7f1c1edde729ec51d853d29c15977801fd36064ec15ac8fbbd59c7114f4dde54835c9ecedef3255a250cbbbbcc45151c92a692a04141c321052343bd200faea101ee8c72d9e605146904c45061c7a3acf1d34627048a871c6deba7dd1a8e9801162eb2b0540608481cb0e7bee262d2b89f93e462ce078802405eb63f2695d4edc9d3cd5c02a22b6c7bd95d37407b29e2a113da40679f1304aaa5ca92aec08acdc985f77f232a0b0f9de32c0fed3bb13b859e65913248b6bc2b71c186f5ac48375fd05bf7f45ceece8ca65b4ce327e75264d1d12486b1b6e709726058d8a0068a1107ac1e01a1b6cba60b1ca0054543059e9b5da1ba58686329ec3245355cf99e2d04e27d07f2446e8fab54a0ee41cd453004aae1b66c5d69e7a2a8a9f87dd546f7321d027a2c7aa358d0405fec0eb110622567ce455278607f05c1b032c04f3f1220deb220e2fd5f5552769bcde517a988ce1354fc0425935c8d220ea3c853bef1109994bebc66f2c26c29614bd832f0d890382d35814224e0c173d2e83b37cc3d8c8779e6a272c324ff5297d09e2f234b3f3c256219690a43d4c2d585a94460249e5c0c09d9448e457cdd9921bcef82f63ff8c0c8ca4b410345011c01f28db20a6d39865840299fa90757222b5f0da0331ec443241d43b115a342ef2a6c5c3395ee0b243d2bb52c3b9a82fda19439d382d48894b31eb3a24fa1cd85f42983a9c3dc992a52230a8d91e0e4badd33f5fe7471d01ba4b7ae89b7ee66e220b4288403e51640a34a984a3367834bb2ac8db6bce2341b144808087bf0cc3de51b9b0abb71f75d34f1b124c4a6b5b1c95897b20d828842804c2141cda623d4a2ca134e219d7dfd0f2754e25900a3dd8a30f1fdc87e27f58958cd7af7d8446a7fb3d96ade5e1bcb3f015de47e3d24db8508579347befe9324125afada274e2981d0358d5248a18122e124b615985a6c0eaeb99689270c2626c2868649c5ba7f6f17a0c7438e648f89d0cc57e7463b43a9262f489b0e650b9fa04306300b20632a90e1b6f4e3d69b3a4417cead386a6ad8bac776f04447afe5c4d6868fdaa95000ad5e1ba60af4460e710147baf586fb7a8816adfc98bd6d43932cd16b56e1d04d9d5d8bb032f57e89cca12d3cbf259ce0a8b4e00f8341494a94aeae4915a8f8bf7a202a3ca1291fc211451a6326810a12e25b1e4f9b2b41b44eac41bb016ac6c0d8c19cb8558052632c3892187a008f60b18d9471c356e8082187c6c16af703249cb6ada558ae264398b686694036bfd2e64d93506574dfac010bda8652fa7792b4a1032fa3b77e7db3e9a9a9d665b50ec1491e483b6b93959870cfb8b9a3e2c5d724ab616f69b970a425ac3aba6fe3677558506eae0262bc934d8a1ebde696c56230e5b55ba283e9c44c117a9e745f38e26cf98c39a435e1254db2813ed291394a19e866139934a3623652fc44808d60891689b3d2a35be87ad5ebd891bdf0c03d1bdf495c5604e5461b57736aa4b802a43d9b406ca00adcd9f337f13b8c64a7e58700aece3d08afe38918b9e9bacbcc02e67a94d16abba965ec0c53090747b6e93864c03897375fbb6ed9cce93bf84511dbab07a8cbd75abfdcadd7b0e099b19bf371dfd0a", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3a6772616e6470615f617574686f726974696573": "0x010888dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0100000000000000d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae690100000000000000", + "0x3db7a24cfdc9de785974746c14a99df94e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0x3f1467a096bcd71a5b6a0c8155e20810308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc": "0x000064a7b3b6e00d0000000000000000", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x426e15054d267946093858132eb537f105fe52c2045750c3c492ccdcf62e2b9c": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x426e15054d267946093858132eb537f14e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x426e15054d267946093858132eb537f195999521c6c89cd80b677e53ce20f98c": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x426e15054d267946093858132eb537f1a47a9ff5cd5bf4d848a80a0b1a947dc3": "0x00000000000000000000000000000000", + "0x426e15054d267946093858132eb537f1ba7fb8745735dc3be2a2c61a72c39e78": "0x181cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e", + "0x426e15054d267946093858132eb537f1d0b4a3f7631f0c0e761898fe198211de": "0xe7030000", + "0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429": "0x0900", + "0x4a83351006488ef6369cb758091f878c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x4ff3897794d496d78686afcfe760a1144e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x5e8a19e3cd1b7c148b33880c479c02814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f27b51b5ec208ee9cb25b55d8728243308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x5f27b51b5ec208ee9cb25b55d87282434e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca0b6a45321efae92aea15e0740ec7afe7": "0x00000000", + "0x5f3e4907f716ac89b6347d15ececedca138e71612491192d68deab7e6f563fe1": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedca28dccb559b95c40168a1b2696581b5a7": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedca308ce9615de0775a82f8a94dc3d285a1": "0x06", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe700e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc44f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e13000064a7b3b6e00d13000064a7b3b6e00d0000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f13000064a7b3b6e00d13000064a7b3b6e00d0000", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca487df464e44a534ba6b0cbb32407b587": "0x0000000000", + "0x5f3e4907f716ac89b6347d15ececedca4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca5579297f4dfb9609e7e4c2ebab9ce40a": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca666fdcbb473985b3ac933d13f4acff8d": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca6ddc7809c6da9bb6093ee22e0fda4ba8": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e169030e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade980e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x00", + "0x5f3e4907f716ac89b6347d15ececedcaa141c4fe67c2d11f4a10c6aca7a79a04b4def25cfda6ef3a00000000": "0xa8fdc74e676dc11b0000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaad811cd65a470ddc5f1d628ff0550982b4def25cfda6ef3a00000000": "0x00000000", + "0x5f3e4907f716ac89b6347d15ececedcab49a2738eeb30896aacb8b3fb46471bd": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedcac0d39ff577af2cc6b67ac3641fa9c4e7": "0x01000000", + "0x5f3e4907f716ac89b6347d15ececedcac29a0310e1bb45d20cace77ccb62c97d": "0x00e1f505", + "0x5f3e4907f716ac89b6347d15ececedcaea07de2b8f010516dca3f7ef52f7ac5a": "0x040000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaed441ceb81326c56263efbb60c95c2e4": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaf7dad0317324aecae8744b87fc95f2f3": "0x00", + "0x5f9cc45b7a00c5899361e1c6099678dc4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x5f9cc45b7a00c5899361e1c6099678dc8a2d09463effcc78a22d75b9cb87dffc": "0x0000000000000000", + "0x5f9cc45b7a00c5899361e1c6099678dcd47cb8f5328af743ddfb361e7180e7fcbb1bdbcacd6ac9340000000000000000": "0x00000000", + "0x6441fb391296410bd2f14381bb7494334e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6786c4cec8d628b6598d7a70ace7acd44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x682a59d51ab9e48a8c8cc418ff9708d24e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6c63e84bfc5a0d62149aaab70897685c4ba24bcd9ac206424105f255ae95a355": "0xb104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x6c63e84bfc5a0d62149aaab70897685c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x74dd702da46f77d7acf77f5a48d4af7d4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b150e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e01be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f00b304f91831830500622780fd38770500", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f0001fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860eb304f91831830500622780fd38770500", + "0x74dd702da46f77d7acf77f5a48d4af7d7a6dc62e324093ba1331bf49fdb2f24a": "0x02000000", + "0x74dd702da46f77d7acf77f5a48d4af7de5c03730c8f59f00941607850b6633d81fad1867486365c5b304f91831830500": "0x01be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f01fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0x7a6d38deaa01cb6e76ee69889f1696272be9a4e88368a2188d2b9100a9f3cd43": "0x00407a10f35a00000000000000000000", + "0x7a6d38deaa01cb6e76ee69889f16962730256ea2c545a3e5e3744665ffb2ed28": "0x00020000", + "0x7a6d38deaa01cb6e76ee69889f1696273f0d64e1907361c689834a9c1cb0fbe0": "0x20000000", + "0x7a6d38deaa01cb6e76ee69889f16962749d67997de33812a1cc37310f765b82e": "0x0080c6a47e8d03000000000000000000", + "0x7a6d38deaa01cb6e76ee69889f1696274e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0x7a6d38deaa01cb6e76ee69889f169627ba93302f3b868c50785e6ade45c6a1d8": "0x10000000", + "0x7cda3cfa86b349fdafce4979b197118f4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a8910c174c55fd2c633e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e": "0x04e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a893e73123ebcdee9161cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c": "0x041cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a894f58b588ac077bd5306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20": "0x04306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89518366b5b1bc7c99d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x04d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89a647e755c30521d38eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x048eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89dd4e3f25f5378a6d90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x0490b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118fba7fb8745735dc3be2a2c61a72c39e78": "0x181cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c000064a7b3b6e00d000000000000000000000000000000000000000000000000306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20000064a7b3b6e00d0000000000000000000000000000000000000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d00000000000000000000000000000000000000000000000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22000064a7b3b6e00d000000000000000000000000000000000000000000000000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d000064a7b3b6e00d000000000000000000000000000000000000000000000000e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x89d139e01a5eb2256f222e5fc5dbe6b34e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x913b40454eb582a66ab74c86f6137db94e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xa0eb495036d368196a2b6c51d9d788814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa2ce73642c549ae79c14f0a671cf45f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa37f719efab16103103a0c8c2c784ce14e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa42f90c8b47838c3a5332d85ee9aa5c34e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xa8c65209d47ee80f56b0011e8fd91f504e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xaebd463ed9925c488c112434d61debc04e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xaebd463ed9925c488c112434d61debc0ba7fb8745735dc3be2a2c61a72c39e78": "0x18d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c", + "0xbd2a529379475088d3e29a918cd478724e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc632a5935f6edc617ae178fef9eb1e211fbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x047374616b696e6720000064a7b3b6e00d000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc66f2e33376834a63c86a195bcf685aebbfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x047374616b696e6720000064a7b3b6e00d000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x0040fa7f398074858a02000000000000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb30e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0xd17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae698eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0eed43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19500e3a507571a62417696d6f6e808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19505905fe216cc5924c6772616e80d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae69": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195066b8d48da86b869b6261626580d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509d4a4cfe1c2ef0b961756469808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950c9b0c13125732d276175646980d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d62c40514b41f31962616265808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950ed43a85541921049696d6f6e80d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950f5537bdb2a1f626b6772616e8088dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x08be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25ffe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x08be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0eed43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860ed17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae698eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5c41b52a371aa36c9254ce34324f2a54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd8f314b7f4e6b095f0f8ee4656a448254e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xed25f63942de25ac5253ba64b5eb64d14e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xed25f63942de25ac5253ba64b5eb64d1ba7fb8745735dc3be2a2c61a72c39e78": "0x18d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c", + "0xede8e4fdc3c8b556f0ce2f77fc2575e34e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xedfb05b766f199ce00df85317e33050e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf2794c22e353e9a839f12faab03a911b4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xf2794c22e353e9a839f12faab03a911b7f17cdfbfa73331856cca0acddd7842e": "0x00000000", + "0xf2794c22e353e9a839f12faab03a911bbdcb0c5143a8617ed38ae3810dd45bc6": "0x00000000", + "0xf2794c22e353e9a839f12faab03a911be2f6cb0456905c189bcb0458f9440f13": "0x00000000", + "0xf5a4963e4efb097983d7a693b0c1ee454e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xfbc9f53700f75f681f234e70fb7241eb4e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file diff --git a/zombienet/0001-basic-warp-sync/generate-warp-sync-database.toml b/zombienet/0001-basic-warp-sync/generate-warp-sync-database.toml new file mode 100644 index 0000000000000..ca78ee290c41d --- /dev/null +++ b/zombienet/0001-basic-warp-sync/generate-warp-sync-database.toml @@ -0,0 +1,17 @@ +# this file is not intended to be executed in CI stage +[relaychain] +default_image = "docker.io/parity/substrate:latest" +default_command = "substrate" + +# refer to ./README.md for more details on how to create snapshot and spec +chain = "gen-db" +chain_spec_path = "chain-spec.json" + + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true diff --git a/zombienet/0001-basic-warp-sync/test-warp-sync.toml b/zombienet/0001-basic-warp-sync/test-warp-sync.toml new file mode 100644 index 0000000000000..ae2810b6ecccd --- /dev/null +++ b/zombienet/0001-basic-warp-sync/test-warp-sync.toml @@ -0,0 +1,30 @@ +[settings] +enable_tracing = false + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +default_command = "substrate" + +chain = "gen-db" +chain_spec_path = "zombienet/0001-basic-warp-sync/chain-spec.json" + + [[relaychain.nodes]] + name = "alice" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz" + + [[relaychain.nodes]] + name = "bob" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz" + + #we need at least 3 nodes for warp sync + [[relaychain.nodes]] + name = "charlie" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz" + + [[relaychain.nodes]] + name = "dave" + validator = false + args = ["--sync warp"] diff --git a/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl b/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl new file mode 100644 index 0000000000000..8ceb61c8b039d --- /dev/null +++ b/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl @@ -0,0 +1,35 @@ +Description: Warp sync +Network: ./test-warp-sync.toml +Creds: config + +alice: is up within 30 seconds +bob: is up within 30 seconds +charlie: is up within 30 seconds +dave: is up within 30 seconds + +alice: reports node_roles is 1 +bob: reports node_roles is 1 +charlie: reports node_roles is 1 +dave: reports node_roles is 1 + +alice: reports peers count is at least 3 within 60 seconds +bob: reports peers count is at least 3 within 60 seconds +charlie: reports peers count is at least 3 within 60 seconds +dave: reports peers count is at least 3 within 60 seconds + + +# db snapshot has 12133 blocks +dave: reports block height is at least 1 within 60 seconds +dave: reports block height is at least 12132 within 60 seconds +dave: reports block height is at least 12133 within 60 seconds + +alice: reports block height is at least 12133 within 60 seconds +bob: reports block height is at least 12133 within 60 seconds +charlie: reports block height is at least 12133 within 60 seconds + +dave: log line matches "Warp sync is complete" within 60 seconds +# workaround for: https://github.com/paritytech/zombienet/issues/580 +dave: count of log lines containing "Block history download is complete" is 1 within 10 seconds + +dave: count of log lines containing "error" is 0 within 10 seconds +dave: count of log lines containing "verification failed" is 0 within 10 seconds

(); + log::info!( + target: "runtime::uniques", + "Running migration storage v1 for uniques with storage version {:?} was complete", + on_chain_storage_version, + ); + // calculate and return migration weights + T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1) + } else { + log::warn!( + target: "runtime::uniques", + "Attempted to apply migration to v1 but failed because storage version is {:?}", + on_chain_storage_version, + ); + T::DbWeight::get().reads(1) + } +} diff --git a/frame/uniques/src/mock.rs b/frame/uniques/src/mock.rs index 658e82a5143e2..d6ed5cc5cc23e 100644 --- a/frame/uniques/src/mock.rs +++ b/frame/uniques/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,12 +15,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Test environment for Assets pallet. +//! Test environment for Uniques pallet. use super::*; use crate as pallet_uniques; -use frame_support::{construct_runtime, parameter_types}; +use frame_support::{ + construct_runtime, + traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, +}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -42,15 +45,12 @@ construct_runtime!( } ); -parameter_types! { - pub const BlockHashCount: u64 = 250; -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; type Index = u64; type BlockNumber = u64; type Hash = H256; @@ -58,8 +58,8 @@ impl frame_system::Config for Test { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; @@ -69,51 +69,40 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxReserves: u32 = 50; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { type Balance = u64; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); type MaxLocks = (); - type MaxReserves = MaxReserves; + type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; } -parameter_types! { - pub const ClassDeposit: u64 = 2; - pub const InstanceDeposit: u64 = 1; - pub const KeyLimit: u32 = 50; - pub const ValueLimit: u32 = 50; - pub const StringLimit: u32 = 50; - pub const MetadataDepositBase: u64 = 1; - pub const AttributeDepositBase: u64 = 1; - pub const MetadataDepositPerByte: u64 = 1; -} - impl Config for Test { - type Event = Event; - type ClassId = u32; - type InstanceId = u32; + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; - type ClassDeposit = ClassDeposit; - type InstanceDeposit = InstanceDeposit; - type MetadataDepositBase = MetadataDepositBase; - type AttributeDepositBase = AttributeDepositBase; - type DepositPerByte = MetadataDepositPerByte; - type StringLimit = StringLimit; - type KeyLimit = KeyLimit; - type ValueLimit = ValueLimit; + type Locker = (); + type CollectionDeposit = ConstU64<2>; + type ItemDeposit = ConstU64<1>; + type MetadataDepositBase = ConstU64<1>; + type AttributeDepositBase = ConstU64<1>; + type DepositPerByte = ConstU64<1>; + type StringLimit = ConstU32<50>; + type KeyLimit = ConstU32<50>; + type ValueLimit = ConstU32<50>; type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); } pub(crate) fn new_test_ext() -> sp_io::TestExternalities { diff --git a/frame/uniques/src/tests.rs b/frame/uniques/src/tests.rs index 8a4f978b7f4f5..7af54ddbb188c 100644 --- a/frame/uniques/src/tests.rs +++ b/frame/uniques/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,19 +17,18 @@ //! Tests for Uniques pallet. -use super::*; -use crate::mock::*; -use frame_support::{assert_noop, assert_ok, traits::Currency}; +use crate::{mock::*, Event, *}; +use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::Currency}; use pallet_balances::Error as BalancesError; -use sp_std::convert::TryInto; +use sp_std::prelude::*; -fn assets() -> Vec<(u64, u32, u32)> { +fn items() -> Vec<(u64, u32, u32)> { let mut r: Vec<_> = Account::::iter().map(|x| x.0).collect(); r.sort(); - let mut s: Vec<_> = Asset::::iter().map(|x| (x.2.owner, x.0, x.1)).collect(); + let mut s: Vec<_> = Item::::iter().map(|x| (x.2.owner, x.0, x.1)).collect(); s.sort(); assert_eq!(r, s); - for class in Asset::::iter() + for collection in Item::::iter() .map(|x| x.0) .scan(None, |s, item| { if s.map_or(false, |last| last == item) { @@ -39,46 +38,69 @@ fn assets() -> Vec<(u64, u32, u32)> { Some(Some(item)) } }) - .filter_map(|item| item) + .flatten() { - let details = Class::::get(class).unwrap(); - let instances = Asset::::iter_prefix(class).count() as u32; - assert_eq!(details.instances, instances); + let details = Collection::::get(collection).unwrap(); + let items = Item::::iter_prefix(collection).count() as u32; + assert_eq!(details.items, items); } r } +fn collections() -> Vec<(u64, u32)> { + let mut r: Vec<_> = CollectionAccount::::iter().map(|x| (x.0, x.1)).collect(); + r.sort(); + let mut s: Vec<_> = Collection::::iter().map(|x| (x.1.owner, x.0)).collect(); + s.sort(); + assert_eq!(r, s); + r +} + macro_rules! bvec { ($( $x:tt )*) => { vec![$( $x )*].try_into().unwrap() } } -fn attributes(class: u32) -> Vec<(Option, Vec, Vec)> { - let mut s: Vec<_> = Attribute::::iter_prefix((class,)) +fn attributes(collection: u32) -> Vec<(Option, Vec, Vec)> { + let mut s: Vec<_> = Attribute::::iter_prefix((collection,)) .map(|(k, v)| (k.0, k.1.into(), v.0.into())) .collect(); s.sort(); s } +fn events() -> Vec> { + let result = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let mock::RuntimeEvent::Uniques(inner) = e { Some(inner) } else { None }) + .collect::>(); + + System::reset_events(); + + result +} + #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - assert_eq!(assets(), vec![]); + assert_eq!(items(), vec![]); }); } #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_eq!(assets(), vec![(1, 0, 42)]); - - assert_ok!(Uniques::force_create(Origin::root(), 1, 2, true)); - assert_ok!(Uniques::mint(Origin::signed(2), 1, 69, 1)); - assert_eq!(assets(), vec![(1, 0, 42), (1, 1, 69)]); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_eq!(collections(), vec![(1, 0)]); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_eq!(items(), vec![(1, 0, 42)]); + + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 1, 2, true)); + assert_eq!(collections(), vec![(1, 0), (2, 1)]); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(2), 1, 69, 1)); + assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } @@ -86,41 +108,47 @@ fn basic_minting_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + assert_ok!(Uniques::create(RuntimeOrigin::signed(1), 0, 1)); assert_eq!(Balances::reserved_balance(&1), 2); - - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0, 0], false)); + assert_eq!(collections(), vec![(1, 0)]); + assert_ok!(Uniques::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0, 0], + false + )); assert_eq!(Balances::reserved_balance(&1), 5); - assert!(ClassMetadataOf::::contains_key(0)); + assert!(CollectionMetadataOf::::contains_key(0)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 10)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 10)); assert_eq!(Balances::reserved_balance(&1), 6); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 69, 20)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 69, 20)); assert_eq!(Balances::reserved_balance(&1), 7); - assert_eq!(assets(), vec![(10, 0, 42), (20, 0, 69)]); - assert_eq!(Class::::get(0).unwrap().instances, 2); - assert_eq!(Class::::get(0).unwrap().instance_metadatas, 0); + assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); + assert_eq!(Collection::::get(0).unwrap().items, 2); + assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![42, 42], false)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![42, 42], false)); assert_eq!(Balances::reserved_balance(&1), 10); - assert!(InstanceMetadataOf::::contains_key(0, 42)); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![69, 69], false)); + assert!(ItemMetadataOf::::contains_key(0, 42)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![69, 69], false)); assert_eq!(Balances::reserved_balance(&1), 13); - assert!(InstanceMetadataOf::::contains_key(0, 69)); + assert!(ItemMetadataOf::::contains_key(0, 69)); - let w = Class::::get(0).unwrap().destroy_witness(); - assert_eq!(w.instances, 2); - assert_eq!(w.instance_metadatas, 2); - assert_ok!(Uniques::destroy(Origin::signed(1), 0, w)); + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_eq!(w.items, 2); + assert_eq!(w.item_metadatas, 2); + assert_ok!(Uniques::destroy(RuntimeOrigin::signed(1), 0, w)); assert_eq!(Balances::reserved_balance(&1), 0); - assert!(!Class::::contains_key(0)); - assert!(!Asset::::contains_key(0, 42)); - assert!(!Asset::::contains_key(0, 69)); - assert!(!ClassMetadataOf::::contains_key(0)); - assert!(!InstanceMetadataOf::::contains_key(0, 42)); - assert!(!InstanceMetadataOf::::contains_key(0, 69)); - assert_eq!(assets(), vec![]); + assert!(!Collection::::contains_key(0)); + assert!(!Item::::contains_key(0, 42)); + assert!(!Item::::contains_key(0, 69)); + assert!(!CollectionMetadataOf::::contains_key(0)); + assert!(!ItemMetadataOf::::contains_key(0, 42)); + assert!(!ItemMetadataOf::::contains_key(0, 69)); + assert_eq!(collections(), vec![]); + assert_eq!(items(), vec![]); }); } @@ -128,72 +156,88 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + assert_ok!(Uniques::create(RuntimeOrigin::signed(1), 0, 1)); - let w = Class::::get(0).unwrap().destroy_witness(); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_noop!(Uniques::destroy(Origin::signed(1), 0, w), Error::::BadWitness); + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_noop!(Uniques::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); }); } #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 1)); assert_eq!(Uniques::owner(0, 42).unwrap(), 1); - assert_eq!(assets(), vec![(1, 0, 42)]); + assert_eq!(collections(), vec![(1, 0)]); + assert_eq!(items(), vec![(1, 0, 42)]); }); } #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 2)); - assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 3)); - assert_eq!(assets(), vec![(3, 0, 42)]); - assert_noop!(Uniques::transfer(Origin::signed(2), 0, 42, 4), Error::::NoPermission); + assert_ok!(Uniques::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); + assert_eq!(items(), vec![(3, 0, 42)]); + assert_noop!( + Uniques::transfer(RuntimeOrigin::signed(2), 0, 42, 4), + Error::::NoPermission + ); - assert_ok!(Uniques::approve_transfer(Origin::signed(3), 0, 42, 2)); - assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 4)); + assert_ok!(Uniques::approve_transfer(RuntimeOrigin::signed(3), 0, 42, 2)); + assert_ok!(Uniques::transfer(RuntimeOrigin::signed(2), 0, 42, 4)); }); } #[test] fn freezing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_ok!(Uniques::freeze(Origin::signed(1), 0, 42)); - assert_noop!(Uniques::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Uniques::freeze(RuntimeOrigin::signed(1), 0, 42)); + assert_noop!(Uniques::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); - assert_ok!(Uniques::thaw(Origin::signed(1), 0, 42)); - assert_ok!(Uniques::freeze_class(Origin::signed(1), 0)); - assert_noop!(Uniques::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); + assert_ok!(Uniques::thaw(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Uniques::freeze_collection(RuntimeOrigin::signed(1), 0)); + assert_noop!(Uniques::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::Frozen); - assert_ok!(Uniques::thaw_class(Origin::signed(1), 0)); - assert_ok!(Uniques::transfer(Origin::signed(1), 0, 42, 2)); + assert_ok!(Uniques::thaw_collection(RuntimeOrigin::signed(1), 0)); + assert_ok!(Uniques::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); }); } #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + + Balances::make_free_balance_be(&2, 100); + assert_ok!(Uniques::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); + assert_noop!( + Uniques::transfer_ownership(RuntimeOrigin::signed(2), 0, 2), + Error::::NoPermission + ); assert_noop!( - Uniques::transfer_ownership(Origin::signed(2), 0, 2), + Uniques::set_team(RuntimeOrigin::signed(2), 0, 2, 2, 2), Error::::NoPermission ); - assert_noop!(Uniques::set_team(Origin::signed(2), 0, 2, 2, 2), Error::::NoPermission); - assert_noop!(Uniques::freeze(Origin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Uniques::thaw(Origin::signed(2), 0, 42), Error::::NoPermission); - assert_noop!(Uniques::mint(Origin::signed(2), 0, 69, 2), Error::::NoPermission); - assert_noop!(Uniques::burn(Origin::signed(2), 0, 42, None), Error::::NoPermission); - let w = Class::::get(0).unwrap().destroy_witness(); - assert_noop!(Uniques::destroy(Origin::signed(2), 0, w), Error::::NoPermission); + assert_noop!(Uniques::freeze(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); + assert_noop!(Uniques::thaw(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission); + assert_noop!( + Uniques::mint(RuntimeOrigin::signed(2), 0, 69, 2), + Error::::NoPermission + ); + assert_noop!( + Uniques::burn(RuntimeOrigin::signed(2), 0, 42, None), + Error::::NoPermission + ); + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_noop!(Uniques::destroy(RuntimeOrigin::signed(2), 0, w), Error::::NoPermission); }); } @@ -203,151 +247,216 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); - assert_ok!(Uniques::transfer_ownership(Origin::signed(1), 0, 2)); + assert_ok!(Uniques::create(RuntimeOrigin::signed(1), 0, 1)); + assert_eq!(collections(), vec![(1, 0)]); + assert_noop!( + Uniques::transfer_ownership(RuntimeOrigin::signed(1), 0, 2), + Error::::Unaccepted + ); + assert_ok!(Uniques::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); + assert_ok!(Uniques::transfer_ownership(RuntimeOrigin::signed(1), 0, 2)); + + assert_eq!(collections(), vec![(2, 0)]); assert_eq!(Balances::total_balance(&1), 98); assert_eq!(Balances::total_balance(&2), 102); assert_eq!(Balances::reserved_balance(&1), 0); assert_eq!(Balances::reserved_balance(&2), 2); + assert_ok!(Uniques::set_accept_ownership(RuntimeOrigin::signed(1), Some(0))); assert_noop!( - Uniques::transfer_ownership(Origin::signed(1), 0, 1), + Uniques::transfer_ownership(RuntimeOrigin::signed(1), 0, 1), Error::::NoPermission ); // Mint and set metadata now and make sure that deposit gets transferred back. - assert_ok!(Uniques::set_class_metadata(Origin::signed(2), 0, bvec![0u8; 20], false)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_ok!(Uniques::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false)); - assert_ok!(Uniques::transfer_ownership(Origin::signed(2), 0, 3)); + assert_ok!(Uniques::set_collection_metadata( + RuntimeOrigin::signed(2), + 0, + bvec![0u8; 20], + false + )); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Uniques::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); + assert_ok!(Uniques::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); + assert_eq!(collections(), vec![(3, 0)]); assert_eq!(Balances::total_balance(&2), 57); assert_eq!(Balances::total_balance(&3), 145); assert_eq!(Balances::reserved_balance(&2), 0); assert_eq!(Balances::reserved_balance(&3), 45); + + // 2's acceptence from before is reset when it became owner, so it cannot be transfered + // without a fresh acceptance. + assert_noop!( + Uniques::transfer_ownership(RuntimeOrigin::signed(3), 0, 2), + Error::::Unaccepted + ); }); } #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::set_team(Origin::signed(1), 0, 2, 3, 4)); - - assert_ok!(Uniques::mint(Origin::signed(2), 0, 42, 2)); - assert_ok!(Uniques::freeze(Origin::signed(4), 0, 42)); - assert_ok!(Uniques::thaw(Origin::signed(3), 0, 42)); - assert_ok!(Uniques::transfer(Origin::signed(3), 0, 42, 3)); - assert_ok!(Uniques::burn(Origin::signed(3), 0, 42, None)); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); + + assert_ok!(Uniques::mint(RuntimeOrigin::signed(2), 0, 42, 2)); + assert_ok!(Uniques::freeze(RuntimeOrigin::signed(4), 0, 42)); + assert_ok!(Uniques::thaw(RuntimeOrigin::signed(3), 0, 42)); + assert_ok!(Uniques::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); + assert_ok!(Uniques::burn(RuntimeOrigin::signed(3), 0, 42, None)); }); } #[test] -fn set_class_metadata_should_work() { +fn set_collection_metadata_should_work() { new_test_ext().execute_with(|| { - // Cannot add metadata to unknown asset + // Cannot add metadata to unknown item assert_noop!( - Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), - Error::::Unknown, + Uniques::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20], false), + Error::::UnknownCollection, ); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); - // Cannot add metadata to unowned asset + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, false)); + // Cannot add metadata to unowned item assert_noop!( - Uniques::set_class_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), + Uniques::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20], false), Error::::NoPermission, ); // Successfully add metadata and take deposit Balances::make_free_balance_be(&1, 30); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 20], false)); + assert_ok!(Uniques::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0u8; 20], + false + )); assert_eq!(Balances::free_balance(&1), 9); - assert!(ClassMetadataOf::::contains_key(0)); + assert!(CollectionMetadataOf::::contains_key(0)); // Force origin works, too. - assert_ok!(Uniques::set_class_metadata(Origin::root(), 0, bvec![0u8; 18], false)); + assert_ok!(Uniques::set_collection_metadata( + RuntimeOrigin::root(), + 0, + bvec![0u8; 18], + false + )); // Update deposit - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 15], false)); + assert_ok!(Uniques::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0u8; 15], + false + )); assert_eq!(Balances::free_balance(&1), 14); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 25], false)); + assert_ok!(Uniques::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0u8; 25], + false + )); assert_eq!(Balances::free_balance(&1), 4); // Cannot over-reserve assert_noop!( - Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 40], false), + Uniques::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 40], false), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 15], true)); + assert_ok!(Uniques::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0u8; 15], + true + )); assert_noop!( - Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 15], false), + Uniques::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15], false), Error::::Frozen, ); - assert_noop!(Uniques::clear_class_metadata(Origin::signed(1), 0), Error::::Frozen); + assert_noop!( + Uniques::clear_collection_metadata(RuntimeOrigin::signed(1), 0), + Error::::Frozen + ); // Clear Metadata - assert_ok!(Uniques::set_class_metadata(Origin::root(), 0, bvec![0u8; 15], false)); + assert_ok!(Uniques::set_collection_metadata( + RuntimeOrigin::root(), + 0, + bvec![0u8; 15], + false + )); assert_noop!( - Uniques::clear_class_metadata(Origin::signed(2), 0), + Uniques::clear_collection_metadata(RuntimeOrigin::signed(2), 0), Error::::NoPermission ); - assert_noop!(Uniques::clear_class_metadata(Origin::signed(1), 1), Error::::Unknown); - assert_ok!(Uniques::clear_class_metadata(Origin::signed(1), 0)); - assert!(!ClassMetadataOf::::contains_key(0)); + assert_noop!( + Uniques::clear_collection_metadata(RuntimeOrigin::signed(1), 1), + Error::::UnknownCollection + ); + assert_ok!(Uniques::clear_collection_metadata(RuntimeOrigin::signed(1), 0)); + assert!(!CollectionMetadataOf::::contains_key(0)); }); } #[test] -fn set_instance_metadata_should_work() { +fn set_item_metadata_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 30); - // Cannot add metadata to unknown asset - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - // Cannot add metadata to unowned asset + // Cannot add metadata to unknown item + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, false)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + // Cannot add metadata to unowned item assert_noop!( - Uniques::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false), + Uniques::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20], false), Error::::NoPermission, ); // Successfully add metadata and take deposit - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 20], false)); assert_eq!(Balances::free_balance(&1), 8); - assert!(InstanceMetadataOf::::contains_key(0, 42)); + assert!(ItemMetadataOf::::contains_key(0, 42)); // Force origin works, too. - assert_ok!(Uniques::set_metadata(Origin::root(), 0, 42, bvec![0u8; 18], false)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 18], false)); // Update deposit - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], false)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], false)); assert_eq!(Balances::free_balance(&1), 13); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 25], false)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 25], false)); assert_eq!(Balances::free_balance(&1), 3); // Cannot over-reserve assert_noop!( - Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 40], false), + Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 40], false), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], true)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], true)); assert_noop!( - Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 15], false), + Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15], false), Error::::Frozen, ); - assert_noop!(Uniques::clear_metadata(Origin::signed(1), 0, 42), Error::::Frozen); + assert_noop!( + Uniques::clear_metadata(RuntimeOrigin::signed(1), 0, 42), + Error::::Frozen + ); // Clear Metadata - assert_ok!(Uniques::set_metadata(Origin::root(), 0, 42, bvec![0u8; 15], false)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15], false)); assert_noop!( - Uniques::clear_metadata(Origin::signed(2), 0, 42), + Uniques::clear_metadata(RuntimeOrigin::signed(2), 0, 42), Error::::NoPermission ); - assert_noop!(Uniques::clear_metadata(Origin::signed(1), 1, 42), Error::::Unknown); - assert_ok!(Uniques::clear_metadata(Origin::signed(1), 0, 42)); - assert!(!InstanceMetadataOf::::contains_key(0, 42)); + assert_noop!( + Uniques::clear_metadata(RuntimeOrigin::signed(1), 1, 42), + Error::::UnknownCollection + ); + assert_ok!(Uniques::clear_metadata(RuntimeOrigin::signed(1), 0, 42)); + assert!(!ItemMetadataOf::::contains_key(0, 42)); }); } @@ -356,11 +465,23 @@ fn set_attribute_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); - - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![1], bvec![0])); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, false)); + + assert_ok!(Uniques::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); + assert_ok!(Uniques::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + bvec![0], + bvec![0] + )); + assert_ok!(Uniques::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + bvec![1], + bvec![0] + )); assert_eq!( attributes(0), vec![ @@ -371,7 +492,13 @@ fn set_attribute_should_work() { ); assert_eq!(Balances::reserved_balance(1), 9); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0; 10])); + assert_ok!(Uniques::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + bvec![0], + bvec![0; 10] + )); assert_eq!( attributes(0), vec![ @@ -382,15 +509,15 @@ fn set_attribute_should_work() { ); assert_eq!(Balances::reserved_balance(1), 18); - assert_ok!(Uniques::clear_attribute(Origin::signed(1), 0, Some(0), bvec![1])); + assert_ok!(Uniques::clear_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![1])); assert_eq!( attributes(0), vec![(None, bvec![0], bvec![0; 10]), (Some(0), bvec![0], bvec![0]),] ); assert_eq!(Balances::reserved_balance(1), 15); - let w = Class::::get(0).unwrap().destroy_witness(); - assert_ok!(Uniques::destroy(Origin::signed(1), 0, w)); + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_ok!(Uniques::destroy(RuntimeOrigin::signed(1), 0, w)); assert_eq!(attributes(0), vec![]); assert_eq!(Balances::reserved_balance(1), 0); }); @@ -401,11 +528,23 @@ fn set_attribute_should_respect_freeze() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); - - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(1), bvec![0], bvec![0])); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, false)); + + assert_ok!(Uniques::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0])); + assert_ok!(Uniques::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + bvec![0], + bvec![0] + )); + assert_ok!(Uniques::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(1), + bvec![0], + bvec![0] + )); assert_eq!( attributes(0), vec![ @@ -416,49 +555,77 @@ fn set_attribute_should_respect_freeze() { ); assert_eq!(Balances::reserved_balance(1), 9); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![], true)); + assert_ok!(Uniques::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![], true)); let e = Error::::Frozen; - assert_noop!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0]), e); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1])); - - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 0, bvec![], true)); + assert_noop!( + Uniques::set_attribute(RuntimeOrigin::signed(1), 0, None, bvec![0], bvec![0]), + e + ); + assert_ok!(Uniques::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + bvec![0], + bvec![1] + )); + + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 0, bvec![], true)); let e = Error::::Frozen; - assert_noop!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1]), e); - assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(1), bvec![0], bvec![1])); + assert_noop!( + Uniques::set_attribute(RuntimeOrigin::signed(1), 0, Some(0), bvec![0], bvec![1]), + e + ); + assert_ok!(Uniques::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(1), + bvec![0], + bvec![1] + )); }); } #[test] -fn force_asset_status_should_work() { +fn force_item_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 69, 2)); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0; 20], false)); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, false)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 1)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 69, 2)); + assert_ok!(Uniques::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0; 20], + false + )); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20], false)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 65); - // force asset status to be free holding - assert_ok!(Uniques::force_asset_status(Origin::root(), 0, 1, 1, 1, 1, true, false)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 142, 1)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 169, 2)); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 142, bvec![0; 20], false)); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 169, bvec![0; 20], false)); + // force item status to be free holding + assert_ok!(Uniques::force_item_status(RuntimeOrigin::root(), 0, 1, 1, 1, 1, true, false)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 142, 1)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 169, 2)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20], false)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 65); - assert_ok!(Uniques::redeposit(Origin::signed(1), 0, bvec![0, 42, 50, 69, 100])); + assert_ok!(Uniques::redeposit(RuntimeOrigin::signed(1), 0, bvec![0, 42, 50, 69, 100])); assert_eq!(Balances::reserved_balance(1), 63); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 42); - assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); + assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 21); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Uniques::set_collection_metadata( + RuntimeOrigin::signed(1), + 0, + bvec![0; 20], + false + )); assert_eq!(Balances::reserved_balance(1), 0); }); } @@ -467,20 +634,29 @@ fn force_asset_status_should_work() { fn burn_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); - assert_ok!(Uniques::set_team(Origin::signed(1), 0, 2, 3, 4)); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, false)); + assert_ok!(Uniques::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); - assert_noop!(Uniques::burn(Origin::signed(5), 0, 42, Some(5)), Error::::Unknown); + assert_noop!( + Uniques::burn(RuntimeOrigin::signed(5), 0, 42, Some(5)), + Error::::UnknownCollection + ); - assert_ok!(Uniques::mint(Origin::signed(2), 0, 42, 5)); - assert_ok!(Uniques::mint(Origin::signed(2), 0, 69, 5)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(2), 0, 42, 5)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(2), 0, 69, 5)); assert_eq!(Balances::reserved_balance(1), 2); - assert_noop!(Uniques::burn(Origin::signed(0), 0, 42, None), Error::::NoPermission); - assert_noop!(Uniques::burn(Origin::signed(5), 0, 42, Some(6)), Error::::WrongOwner); + assert_noop!( + Uniques::burn(RuntimeOrigin::signed(0), 0, 42, None), + Error::::NoPermission + ); + assert_noop!( + Uniques::burn(RuntimeOrigin::signed(5), 0, 42, Some(6)), + Error::::WrongOwner + ); - assert_ok!(Uniques::burn(Origin::signed(5), 0, 42, Some(5))); - assert_ok!(Uniques::burn(Origin::signed(3), 0, 69, Some(5))); + assert_ok!(Uniques::burn(RuntimeOrigin::signed(5), 0, 42, Some(5))); + assert_ok!(Uniques::burn(RuntimeOrigin::signed(3), 0, 69, Some(5))); assert_eq!(Balances::reserved_balance(1), 0); }); } @@ -488,45 +664,92 @@ fn burn_works() { #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); - assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); - assert_ok!(Uniques::transfer(Origin::signed(3), 0, 42, 4)); - assert_noop!(Uniques::transfer(Origin::signed(3), 0, 42, 3), Error::::NoPermission); - assert!(Asset::::get(0, 42).unwrap().approved.is_none()); - - assert_ok!(Uniques::approve_transfer(Origin::signed(4), 0, 42, 2)); - assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 2)); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Uniques::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3)); + assert_ok!(Uniques::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); + assert_noop!( + Uniques::transfer(RuntimeOrigin::signed(3), 0, 42, 3), + Error::::NoPermission + ); + assert!(Item::::get(0, 42).unwrap().approved.is_none()); + + assert_ok!(Uniques::approve_transfer(RuntimeOrigin::signed(4), 0, 42, 2)); + assert_ok!(Uniques::transfer(RuntimeOrigin::signed(2), 0, 42, 2)); + }); +} + +#[test] +fn approved_account_gets_reset_after_transfer() { + new_test_ext().execute_with(|| { + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 2)); + + assert_ok!(Uniques::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3)); + assert_ok!(Uniques::transfer(RuntimeOrigin::signed(2), 0, 42, 5)); + + // this shouldn't work because we have just transfered the item to another account. + assert_noop!( + Uniques::transfer(RuntimeOrigin::signed(3), 0, 42, 4), + Error::::NoPermission + ); + // The new owner can transfer fine: + assert_ok!(Uniques::transfer(RuntimeOrigin::signed(5), 0, 42, 6)); + }); +} + +#[test] +fn approved_account_gets_reset_after_buy_item() { + new_test_ext().execute_with(|| { + let item = 1; + let price = 15; + + Balances::make_free_balance_be(&2, 100); + + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, item, 1)); + assert_ok!(Uniques::approve_transfer(RuntimeOrigin::signed(1), 0, item, 5)); + + assert_ok!(Uniques::set_price(RuntimeOrigin::signed(1), 0, item, Some(price), None)); + + assert_ok!(Uniques::buy_item(RuntimeOrigin::signed(2), 0, item, price)); + + // this shouldn't work because the item has been bough and the approved account should be + // reset. + assert_noop!( + Uniques::transfer(RuntimeOrigin::signed(5), 0, item, 4), + Error::::NoPermission + ); }); } #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 2)); - assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Uniques::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3)); assert_noop!( - Uniques::cancel_approval(Origin::signed(2), 1, 42, None), - Error::::Unknown + Uniques::cancel_approval(RuntimeOrigin::signed(2), 1, 42, None), + Error::::UnknownCollection ); assert_noop!( - Uniques::cancel_approval(Origin::signed(2), 0, 43, None), - Error::::Unknown + Uniques::cancel_approval(RuntimeOrigin::signed(2), 0, 43, None), + Error::::UnknownCollection ); assert_noop!( - Uniques::cancel_approval(Origin::signed(3), 0, 42, None), + Uniques::cancel_approval(RuntimeOrigin::signed(3), 0, 42, None), Error::::NoPermission ); assert_noop!( - Uniques::cancel_approval(Origin::signed(2), 0, 42, Some(4)), + Uniques::cancel_approval(RuntimeOrigin::signed(2), 0, 42, Some(4)), Error::::WrongDelegate ); - assert_ok!(Uniques::cancel_approval(Origin::signed(2), 0, 42, Some(3))); + assert_ok!(Uniques::cancel_approval(RuntimeOrigin::signed(2), 0, 42, Some(3))); assert_noop!( - Uniques::cancel_approval(Origin::signed(2), 0, 42, None), + Uniques::cancel_approval(RuntimeOrigin::signed(2), 0, 42, None), Error::::NoDelegate ); }); @@ -535,26 +758,26 @@ fn cancel_approval_works() { #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 2)); - assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); + assert_ok!(Uniques::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3)); assert_noop!( - Uniques::cancel_approval(Origin::signed(1), 1, 42, None), - Error::::Unknown + Uniques::cancel_approval(RuntimeOrigin::signed(1), 1, 42, None), + Error::::UnknownCollection ); assert_noop!( - Uniques::cancel_approval(Origin::signed(1), 0, 43, None), - Error::::Unknown + Uniques::cancel_approval(RuntimeOrigin::signed(1), 0, 43, None), + Error::::UnknownCollection ); assert_noop!( - Uniques::cancel_approval(Origin::signed(1), 0, 42, Some(4)), + Uniques::cancel_approval(RuntimeOrigin::signed(1), 0, 42, Some(4)), Error::::WrongDelegate ); - assert_ok!(Uniques::cancel_approval(Origin::signed(1), 0, 42, Some(3))); + assert_ok!(Uniques::cancel_approval(RuntimeOrigin::signed(1), 0, 42, Some(3))); assert_noop!( - Uniques::cancel_approval(Origin::signed(1), 0, 42, None), + Uniques::cancel_approval(RuntimeOrigin::signed(1), 0, 42, None), Error::::NoDelegate ); }); @@ -563,21 +786,275 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), 0, 1, true)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), 0, 42, 2)); - assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); - assert_noop!(Uniques::cancel_approval(Origin::root(), 1, 42, None), Error::::Unknown); - assert_noop!(Uniques::cancel_approval(Origin::root(), 0, 43, None), Error::::Unknown); + assert_ok!(Uniques::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3)); + assert_noop!( + Uniques::cancel_approval(RuntimeOrigin::root(), 1, 42, None), + Error::::UnknownCollection + ); assert_noop!( - Uniques::cancel_approval(Origin::root(), 0, 42, Some(4)), + Uniques::cancel_approval(RuntimeOrigin::root(), 0, 43, None), + Error::::UnknownCollection + ); + assert_noop!( + Uniques::cancel_approval(RuntimeOrigin::root(), 0, 42, Some(4)), Error::::WrongDelegate ); - assert_ok!(Uniques::cancel_approval(Origin::root(), 0, 42, Some(3))); + assert_ok!(Uniques::cancel_approval(RuntimeOrigin::root(), 0, 42, Some(3))); assert_noop!( - Uniques::cancel_approval(Origin::root(), 0, 42, None), + Uniques::cancel_approval(RuntimeOrigin::root(), 0, 42, None), Error::::NoDelegate ); }); } + +#[test] +fn max_supply_should_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = 1; + let max_supply = 2; + + // validate set_collection_max_supply + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), collection_id, user_id, true)); + assert!(!CollectionMaxSupply::::contains_key(collection_id)); + + assert_ok!(Uniques::set_collection_max_supply( + RuntimeOrigin::signed(user_id), + collection_id, + max_supply + )); + assert_eq!(CollectionMaxSupply::::get(collection_id).unwrap(), max_supply); + + assert!(events().contains(&Event::::CollectionMaxSupplySet { + collection: collection_id, + max_supply, + })); + + assert_noop!( + Uniques::set_collection_max_supply( + RuntimeOrigin::signed(user_id), + collection_id, + max_supply + 1 + ), + Error::::MaxSupplyAlreadySet + ); + + // validate we can't mint more to max supply + assert_ok!(Uniques::mint(RuntimeOrigin::signed(user_id), collection_id, 0, user_id)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(user_id), collection_id, 1, user_id)); + assert_noop!( + Uniques::mint(RuntimeOrigin::signed(user_id), collection_id, 2, user_id), + Error::::MaxSupplyReached + ); + + // validate we remove the CollectionMaxSupply record when we destroy the collection + assert_ok!(Uniques::destroy( + RuntimeOrigin::signed(user_id), + collection_id, + Collection::::get(collection_id).unwrap().destroy_witness() + )); + assert!(!CollectionMaxSupply::::contains_key(collection_id)); + }); +} + +#[test] +fn set_price_should_work() { + new_test_ext().execute_with(|| { + let user_id = 1; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), collection_id, user_id, true)); + + assert_ok!(Uniques::mint(RuntimeOrigin::signed(user_id), collection_id, item_1, user_id)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(user_id), collection_id, item_2, user_id)); + + assert_ok!(Uniques::set_price( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + Some(1), + None, + )); + + assert_ok!(Uniques::set_price( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + Some(2), + Some(3) + )); + + let item = ItemPriceOf::::get(collection_id, item_1).unwrap(); + assert_eq!(item.0, 1); + assert_eq!(item.1, None); + + let item = ItemPriceOf::::get(collection_id, item_2).unwrap(); + assert_eq!(item.0, 2); + assert_eq!(item.1, Some(3)); + + assert!(events().contains(&Event::::ItemPriceSet { + collection: collection_id, + item: item_1, + price: 1, + whitelisted_buyer: None, + })); + + // validate we can unset the price + assert_ok!(Uniques::set_price( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + None, + None + )); + assert!(events().contains(&Event::::ItemPriceRemoved { + collection: collection_id, + item: item_2 + })); + assert!(!ItemPriceOf::::contains_key(collection_id, item_2)); + }); +} + +#[test] +fn buy_item_should_work() { + new_test_ext().execute_with(|| { + let user_1 = 1; + let user_2 = 2; + let user_3 = 3; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + let item_3 = 3; + let price_1 = 20; + let price_2 = 30; + let initial_balance = 100; + + Balances::make_free_balance_be(&user_1, initial_balance); + Balances::make_free_balance_be(&user_2, initial_balance); + Balances::make_free_balance_be(&user_3, initial_balance); + + assert_ok!(Uniques::force_create(RuntimeOrigin::root(), collection_id, user_1, true)); + + assert_ok!(Uniques::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1)); + assert_ok!(Uniques::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_1)); + + assert_ok!(Uniques::set_price( + RuntimeOrigin::signed(user_1), + collection_id, + item_1, + Some(price_1), + None, + )); + + assert_ok!(Uniques::set_price( + RuntimeOrigin::signed(user_1), + collection_id, + item_2, + Some(price_2), + Some(user_3), + )); + + // can't buy for less + assert_noop!( + Uniques::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_1, 1), + Error::::BidTooLow + ); + + // pass the higher price to validate it will still deduct correctly + assert_ok!(Uniques::buy_item( + RuntimeOrigin::signed(user_2), + collection_id, + item_1, + price_1 + 1, + )); + + // validate the new owner & balances + let item = Item::::get(collection_id, item_1).unwrap(); + assert_eq!(item.owner, user_2); + assert_eq!(Balances::total_balance(&user_1), initial_balance + price_1); + assert_eq!(Balances::total_balance(&user_2), initial_balance - price_1); + + // can't buy from yourself + assert_noop!( + Uniques::buy_item(RuntimeOrigin::signed(user_1), collection_id, item_2, price_2), + Error::::NoPermission + ); + + // can't buy when the item is listed for a specific buyer + assert_noop!( + Uniques::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_2, price_2), + Error::::NoPermission + ); + + // can buy when I'm a whitelisted buyer + assert_ok!(Uniques::buy_item( + RuntimeOrigin::signed(user_3), + collection_id, + item_2, + price_2, + )); + + assert!(events().contains(&Event::::ItemBought { + collection: collection_id, + item: item_2, + price: price_2, + seller: user_1, + buyer: user_3, + })); + + // ensure we reset the buyer field + assert!(!ItemPriceOf::::contains_key(collection_id, item_2)); + + // can't buy when item is not for sale + assert_noop!( + Uniques::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_3, price_2), + Error::::NotForSale + ); + + // ensure we can't buy an item when the collection or an item is frozen + { + assert_ok!(Uniques::set_price( + RuntimeOrigin::signed(user_1), + collection_id, + item_3, + Some(price_1), + None, + )); + + // freeze collection + assert_ok!(Uniques::freeze_collection(RuntimeOrigin::signed(user_1), collection_id)); + + let buy_item_call = mock::RuntimeCall::Uniques(crate::Call::::buy_item { + collection: collection_id, + item: item_3, + bid_price: price_1, + }); + assert_noop!( + buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), + Error::::Frozen + ); + + assert_ok!(Uniques::thaw_collection(RuntimeOrigin::signed(user_1), collection_id)); + + // freeze item + assert_ok!(Uniques::freeze(RuntimeOrigin::signed(user_1), collection_id, item_3)); + + let buy_item_call = mock::RuntimeCall::Uniques(crate::Call::::buy_item { + collection: collection_id, + item: item_3, + bid_price: price_1, + }); + assert_noop!( + buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), + Error::::Frozen + ); + } + }); +} diff --git a/frame/uniques/src/types.rs b/frame/uniques/src/types.rs index 1e4405aa09c84..98e056163d28d 100644 --- a/frame/uniques/src/types.rs +++ b/frame/uniques/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,21 +15,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Various basic types for use in the assets pallet. +//! Various basic types for use in the Uniques pallet. use super::*; -use frame_support::{traits::Get, BoundedVec}; +use frame_support::{ + pallet_prelude::{BoundedVec, MaxEncodedLen}, + traits::Get, +}; use scale_info::TypeInfo; pub(super) type DepositBalanceOf = <>::Currency as Currency<::AccountId>>::Balance; -pub(super) type ClassDetailsFor = - ClassDetails<::AccountId, DepositBalanceOf>; -pub(super) type InstanceDetailsFor = - InstanceDetails<::AccountId, DepositBalanceOf>; +pub(super) type CollectionDetailsFor = + CollectionDetails<::AccountId, DepositBalanceOf>; +pub(super) type ItemDetailsFor = + ItemDetails<::AccountId, DepositBalanceOf>; +pub(super) type ItemPrice = + <>::Currency as Currency<::AccountId>>::Balance; -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct ClassDetails { +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct CollectionDetails { /// Can change `owner`, `issuer`, `freezer` and `admin` accounts. pub(super) owner: AccountId, /// Can mint tokens. @@ -38,85 +43,87 @@ pub struct ClassDetails { pub(super) admin: AccountId, /// Can freeze tokens. pub(super) freezer: AccountId, - /// The total balance deposited for the all storage associated with this asset class. Used by - /// `destroy`. + /// The total balance deposited for the all storage associated with this collection. + /// Used by `destroy`. pub(super) total_deposit: DepositBalance, - /// If `true`, then no deposit is needed to hold instances of this class. + /// If `true`, then no deposit is needed to hold items of this collection. pub(super) free_holding: bool, - /// The total number of outstanding instances of this asset class. - pub(super) instances: u32, - /// The total number of outstanding instance metadata of this asset class. - pub(super) instance_metadatas: u32, - /// The total number of attributes for this asset class. + /// The total number of outstanding items of this collection. + pub(super) items: u32, + /// The total number of outstanding item metadata of this collection. + pub(super) item_metadatas: u32, + /// The total number of attributes for this collection. pub(super) attributes: u32, - /// Whether the asset is frozen for non-admin transfers. + /// Whether the collection is frozen for non-admin transfers. pub(super) is_frozen: bool, } /// Witness data for the destroy transactions. -#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct DestroyWitness { - /// The total number of outstanding instances of this asset class. + /// The total number of outstanding items of this collection. #[codec(compact)] - pub(super) instances: u32, - /// The total number of outstanding instance metadata of this asset class. + pub items: u32, + /// The total number of items in this collection that have outstanding item metadata. #[codec(compact)] - pub(super) instance_metadatas: u32, + pub item_metadatas: u32, #[codec(compact)] - /// The total number of attributes for this asset class. - pub(super) attributes: u32, + /// The total number of attributes for this collection. + pub attributes: u32, } -impl ClassDetails { +impl CollectionDetails { pub fn destroy_witness(&self) -> DestroyWitness { DestroyWitness { - instances: self.instances, - instance_metadatas: self.instance_metadatas, + items: self.items, + item_metadatas: self.item_metadatas, attributes: self.attributes, } } } -/// Information concerning the ownership of a single unique asset. -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo)] -pub struct InstanceDetails { - /// The owner of this asset. +/// Information concerning the ownership of a single unique item. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +pub struct ItemDetails { + /// The owner of this item. pub(super) owner: AccountId, - /// The approved transferrer of this asset, if one is set. + /// The approved transferrer of this item, if one is set. pub(super) approved: Option, - /// Whether the asset can be transferred or not. + /// Whether the item can be transferred or not. pub(super) is_frozen: bool, - /// The amount held in the pallet's default account for this asset. Free-hold assets will have + /// The amount held in the pallet's default account for this item. Free-hold items will have /// this as zero. pub(super) deposit: DepositBalance, } -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo)] +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(StringLimit))] -pub struct ClassMetadata> { +#[codec(mel_bound(DepositBalance: MaxEncodedLen))] +pub struct CollectionMetadata> { /// The balance deposited for this metadata. /// /// This pays for the data stored in this struct. pub(super) deposit: DepositBalance, - /// General information concerning this asset. Limited in length by `StringLimit`. This will - /// generally be either a JSON dump or the hash of some JSON which can be found on a + /// General information concerning this collection. Limited in length by `StringLimit`. This + /// will generally be either a JSON dump or the hash of some JSON which can be found on a /// hash-addressable global publication system such as IPFS. pub(super) data: BoundedVec, - /// Whether the asset metadata may be changed by a non Force origin. + /// Whether the collection's metadata may be changed by a non Force origin. pub(super) is_frozen: bool, } -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo)] +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(StringLimit))] -pub struct InstanceMetadata> { +#[codec(mel_bound(DepositBalance: MaxEncodedLen))] +pub struct ItemMetadata> { /// The balance deposited for this metadata. /// /// This pays for the data stored in this struct. pub(super) deposit: DepositBalance, - /// General information concerning this asset. Limited in length by `StringLimit`. This will + /// General information concerning this item. Limited in length by `StringLimit`. This will /// generally be either a JSON dump or the hash of some JSON which can be found on a /// hash-addressable global publication system such as IPFS. pub(super) data: BoundedVec, - /// Whether the asset metadata may be changed by a non Force origin. + /// Whether the item metadata may be changed by a non Force origin. pub(super) is_frozen: bool, } diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index 40d1ddfdc5566..8a8e1090bb718 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_uniques //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/uniques/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -54,368 +56,508 @@ pub trait WeightInfo { fn redeposit(i: u32, ) -> Weight; fn freeze() -> Weight; fn thaw() -> Weight; - fn freeze_class() -> Weight; - fn thaw_class() -> Weight; + fn freeze_collection() -> Weight; + fn thaw_collection() -> Weight; fn transfer_ownership() -> Weight; fn set_team() -> Weight; - fn force_asset_status() -> Weight; + fn force_item_status() -> Weight; fn set_attribute() -> Weight; fn clear_attribute() -> Weight; fn set_metadata() -> Weight; fn clear_metadata() -> Weight; - fn set_class_metadata() -> Weight; - fn clear_class_metadata() -> Weight; + fn set_collection_metadata() -> Weight; + fn clear_collection_metadata() -> Weight; fn approve_transfer() -> Weight; fn cancel_approval() -> Weight; + fn set_accept_ownership() -> Weight; + fn set_collection_max_supply() -> Weight; + fn set_price() -> Weight; + fn buy_item() -> Weight; } /// Weights for pallet_uniques using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (42_138_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 35_358 nanoseconds. + Weight::from_ref_time(35_935_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (22_238_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 22_767 nanoseconds. + Weight::from_ref_time(23_235_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:0) + // Storage: Uniques ClassAccount (r:0 w:1) // Storage: Uniques Attribute (r:0 w:1000) // Storage: Uniques ClassMetadataOf (r:0 w:1) // Storage: Uniques InstanceMetadataOf (r:0 w:1000) + // Storage: Uniques CollectionMaxSupply (r:0 w:1) // Storage: Uniques Account (r:0 w:20) + /// The range of component `n` is `[0, 1000]`. + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 12_000 - .saturating_add((16_171_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 12_000 - .saturating_add((1_058_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 12_000 - .saturating_add((953_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(m as Weight))) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) + // Minimum execution time: 2_453_194 nanoseconds. + Weight::from_ref_time(2_469_109_000 as u64) + // Standard Error: 27_900 + .saturating_add(Weight::from_ref_time(8_974_176 as u64).saturating_mul(n as u64)) + // Standard Error: 27_900 + .saturating_add(Weight::from_ref_time(344_842 as u64).saturating_mul(m as u64)) + // Standard Error: 27_900 + .saturating_add(Weight::from_ref_time(185_438 as u64).saturating_mul(a as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(m as u64))) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(a as u64))) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (55_359_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 45_115 nanoseconds. + Weight::from_ref_time(45_746_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) + // Storage: Uniques ItemPriceOf (r:0 w:1) fn burn() -> Weight { - (58_254_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 46_447 nanoseconds. + Weight::from_ref_time(46_994_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) + // Storage: Uniques ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - (42_906_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 35_953 nanoseconds. + Weight::from_ref_time(36_375_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:100 w:100) + // Storage: Uniques Asset (r:102 w:102) + /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 9_000 - .saturating_add((25_237_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + // Minimum execution time: 24_238 nanoseconds. + Weight::from_ref_time(24_788_000 as u64) + // Standard Error: 9_232 + .saturating_add(Weight::from_ref_time(11_322_011 as u64).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (30_153_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 28_595 nanoseconds. + Weight::from_ref_time(29_280_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (31_212_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 28_581 nanoseconds. + Weight::from_ref_time(29_038_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) - fn freeze_class() -> Weight { - (22_689_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + fn freeze_collection() -> Weight { + // Minimum execution time: 24_298 nanoseconds. + Weight::from_ref_time(24_742_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) - fn thaw_class() -> Weight { - (22_647_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + fn thaw_collection() -> Weight { + // Minimum execution time: 24_004 nanoseconds. + Weight::from_ref_time(24_536_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } + // Storage: Uniques OwnershipAcceptance (r:1 w:1) // Storage: Uniques Class (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (50_902_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 32_599 nanoseconds. + Weight::from_ref_time(33_201_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (23_632_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 25_137 nanoseconds. + Weight::from_ref_time(25_877_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) - fn force_asset_status() -> Weight { - (22_508_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Storage: Uniques ClassAccount (r:0 w:1) + fn force_item_status() -> Weight { + // Minimum execution time: 27_736 nanoseconds. + Weight::from_ref_time(28_279_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (69_942_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 51_195 nanoseconds. + Weight::from_ref_time(51_674_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (62_314_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 50_159 nanoseconds. + Weight::from_ref_time(51_412_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (52_647_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 42_608 nanoseconds. + Weight::from_ref_time(42_880_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (50_391_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 43_239 nanoseconds. + Weight::from_ref_time(43_752_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) - fn set_class_metadata() -> Weight { - (50_928_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + fn set_collection_metadata() -> Weight { + // Minimum execution time: 41_224 nanoseconds. + Weight::from_ref_time(41_974_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) - fn clear_class_metadata() -> Weight { - (46_667_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + fn clear_collection_metadata() -> Weight { + // Minimum execution time: 40_836 nanoseconds. + Weight::from_ref_time(41_864_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (32_111_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 29_558 nanoseconds. + Weight::from_ref_time(29_948_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (32_627_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + // Minimum execution time: 29_694 nanoseconds. + Weight::from_ref_time(30_156_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Uniques OwnershipAcceptance (r:1 w:1) + fn set_accept_ownership() -> Weight { + // Minimum execution time: 27_819 nanoseconds. + Weight::from_ref_time(28_245_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Uniques CollectionMaxSupply (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn set_collection_max_supply() -> Weight { + // Minimum execution time: 26_317 nanoseconds. + Weight::from_ref_time(26_893_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Uniques Asset (r:1 w:0) + // Storage: Uniques ItemPriceOf (r:0 w:1) + fn set_price() -> Weight { + // Minimum execution time: 26_546 nanoseconds. + Weight::from_ref_time(27_142_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques ItemPriceOf (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Account (r:0 w:2) + fn buy_item() -> Weight { + // Minimum execution time: 49_238 nanoseconds. + Weight::from_ref_time(50_444_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (42_138_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 35_358 nanoseconds. + Weight::from_ref_time(35_935_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (22_238_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 22_767 nanoseconds. + Weight::from_ref_time(23_235_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:0) + // Storage: Uniques ClassAccount (r:0 w:1) // Storage: Uniques Attribute (r:0 w:1000) // Storage: Uniques ClassMetadataOf (r:0 w:1) // Storage: Uniques InstanceMetadataOf (r:0 w:1000) + // Storage: Uniques CollectionMaxSupply (r:0 w:1) // Storage: Uniques Account (r:0 w:20) + /// The range of component `n` is `[0, 1000]`. + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 12_000 - .saturating_add((16_171_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 12_000 - .saturating_add((1_058_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 12_000 - .saturating_add((953_000 as Weight).saturating_mul(a as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(m as Weight))) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) + // Minimum execution time: 2_453_194 nanoseconds. + Weight::from_ref_time(2_469_109_000 as u64) + // Standard Error: 27_900 + .saturating_add(Weight::from_ref_time(8_974_176 as u64).saturating_mul(n as u64)) + // Standard Error: 27_900 + .saturating_add(Weight::from_ref_time(344_842 as u64).saturating_mul(m as u64)) + // Standard Error: 27_900 + .saturating_add(Weight::from_ref_time(185_438 as u64).saturating_mul(a as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(m as u64))) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(a as u64))) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (55_359_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 45_115 nanoseconds. + Weight::from_ref_time(45_746_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) + // Storage: Uniques ItemPriceOf (r:0 w:1) fn burn() -> Weight { - (58_254_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 46_447 nanoseconds. + Weight::from_ref_time(46_994_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) + // Storage: Uniques ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - (42_906_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 35_953 nanoseconds. + Weight::from_ref_time(36_375_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:100 w:100) + // Storage: Uniques Asset (r:102 w:102) + /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 9_000 - .saturating_add((25_237_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + // Minimum execution time: 24_238 nanoseconds. + Weight::from_ref_time(24_788_000 as u64) + // Standard Error: 9_232 + .saturating_add(Weight::from_ref_time(11_322_011 as u64).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (30_153_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 28_595 nanoseconds. + Weight::from_ref_time(29_280_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (31_212_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 28_581 nanoseconds. + Weight::from_ref_time(29_038_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) - fn freeze_class() -> Weight { - (22_689_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + fn freeze_collection() -> Weight { + // Minimum execution time: 24_298 nanoseconds. + Weight::from_ref_time(24_742_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) - fn thaw_class() -> Weight { - (22_647_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + fn thaw_collection() -> Weight { + // Minimum execution time: 24_004 nanoseconds. + Weight::from_ref_time(24_536_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } + // Storage: Uniques OwnershipAcceptance (r:1 w:1) // Storage: Uniques Class (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (50_902_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 32_599 nanoseconds. + Weight::from_ref_time(33_201_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (23_632_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 25_137 nanoseconds. + Weight::from_ref_time(25_877_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:1) - fn force_asset_status() -> Weight { - (22_508_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Storage: Uniques ClassAccount (r:0 w:1) + fn force_item_status() -> Weight { + // Minimum execution time: 27_736 nanoseconds. + Weight::from_ref_time(28_279_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (69_942_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 51_195 nanoseconds. + Weight::from_ref_time(51_674_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (62_314_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 50_159 nanoseconds. + Weight::from_ref_time(51_412_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (52_647_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 42_608 nanoseconds. + Weight::from_ref_time(42_880_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (50_391_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 43_239 nanoseconds. + Weight::from_ref_time(43_752_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) - fn set_class_metadata() -> Weight { - (50_928_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + fn set_collection_metadata() -> Weight { + // Minimum execution time: 41_224 nanoseconds. + Weight::from_ref_time(41_974_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) - fn clear_class_metadata() -> Weight { - (46_667_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + fn clear_collection_metadata() -> Weight { + // Minimum execution time: 40_836 nanoseconds. + Weight::from_ref_time(41_864_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (32_111_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 29_558 nanoseconds. + Weight::from_ref_time(29_948_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (32_627_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + // Minimum execution time: 29_694 nanoseconds. + Weight::from_ref_time(30_156_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Uniques OwnershipAcceptance (r:1 w:1) + fn set_accept_ownership() -> Weight { + // Minimum execution time: 27_819 nanoseconds. + Weight::from_ref_time(28_245_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Uniques CollectionMaxSupply (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn set_collection_max_supply() -> Weight { + // Minimum execution time: 26_317 nanoseconds. + Weight::from_ref_time(26_893_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Uniques Asset (r:1 w:0) + // Storage: Uniques ItemPriceOf (r:0 w:1) + fn set_price() -> Weight { + // Minimum execution time: 26_546 nanoseconds. + Weight::from_ref_time(27_142_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques ItemPriceOf (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Account (r:0 w:2) + fn buy_item() -> Weight { + // Minimum execution time: 49_238 nanoseconds. + Weight::from_ref_time(50_444_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } } diff --git a/frame/utility/Cargo.toml b/frame/utility/Cargo.toml index b5b8eab9cdbf3..de293ed5df8af 100644 --- a/frame/utility/Cargo.toml +++ b/frame/utility/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-utility" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME utilities pallet" readme = "README.md" @@ -13,34 +13,38 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primitives/core" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-root-testing = { version = "1.0.0-dev", path = "../root-testing" } +pallet-collective = { version = "4.0.0-dev", path = "../collective" } +pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "codec/std", - "scale-info/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", + "scale-info/std", + "sp-core/std", "sp-io/std", + "sp-runtime/std", "sp-std/std", ] runtime-benchmarks = [ - "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/utility/src/benchmarking.rs b/frame/utility/src/benchmarking.rs index 210a6156499cf..07bc14951cb3b 100644 --- a/frame/utility/src/benchmarking.rs +++ b/frame/utility/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,19 +20,20 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; use frame_system::RawOrigin; const SEED: u32 = 0; -fn assert_last_event(generic_event: ::Event) { +fn assert_last_event(generic_event: ::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } benchmarks! { + where_clause { where ::PalletsOrigin: Clone } batch { let c in 0 .. 1000; - let mut calls: Vec<::Call> = Vec::new(); + let mut calls: Vec<::RuntimeCall> = Vec::new(); for i in 0 .. c { let call = frame_system::Call::remark { remark: vec![] }.into(); calls.push(call); @@ -53,7 +54,28 @@ benchmarks! { batch_all { let c in 0 .. 1000; - let mut calls: Vec<::Call> = Vec::new(); + let mut calls: Vec<::RuntimeCall> = Vec::new(); + for i in 0 .. c { + let call = frame_system::Call::remark { remark: vec![] }.into(); + calls.push(call); + } + let caller = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), calls) + verify { + assert_last_event::(Event::BatchCompleted.into()) + } + + dispatch_as { + let caller = account("caller", SEED, SEED); + let call = Box::new(frame_system::Call::remark { remark: vec![] }.into()); + let origin: T::RuntimeOrigin = RawOrigin::Signed(caller).into(); + let pallets_origin: ::PalletsOrigin = origin.caller().clone(); + let pallets_origin = Into::::into(pallets_origin); + }: _(RawOrigin::Root, Box::new(pallets_origin), call) + + force_batch { + let c in 0 .. 1000; + let mut calls: Vec<::RuntimeCall> = Vec::new(); for i in 0 .. c { let call = frame_system::Call::remark { remark: vec![] }.into(); calls.push(call); @@ -63,6 +85,6 @@ benchmarks! { verify { assert_last_event::(Event::BatchCompleted.into()) } -} -impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/frame/utility/src/lib.rs b/frame/utility/src/lib.rs index 54de87c4740c8..2d60ae15679d5 100644 --- a/frame/utility/src/lib.rs +++ b/frame/utility/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,14 +58,12 @@ pub mod weights; use codec::{Decode, Encode}; use frame_support::{ - dispatch::PostDispatchInfo, + dispatch::{extract_actual_weight, GetDispatchInfo, PostDispatchInfo}, traits::{IsSubType, OriginTrait, UnfilteredDispatchable}, - transactional, - weights::{extract_actual_weight, GetDispatchInfo}, }; use sp_core::TypeId; use sp_io::hashing::blake2_256; -use sp_runtime::traits::Dispatchable; +use sp_runtime::traits::{BadOrigin, Dispatchable, TrailingZeroInput}; use sp_std::prelude::*; pub use weights::WeightInfo; @@ -85,16 +83,21 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From + IsType<::Event>; + type RuntimeEvent: From + IsType<::RuntimeEvent>; /// The overarching call type. - type Call: Parameter - + Dispatchable + type RuntimeCall: Parameter + + Dispatchable + GetDispatchInfo + From> - + UnfilteredDispatchable + + UnfilteredDispatchable + IsSubType> - + IsType<::Call>; + + IsType<::RuntimeCall>; + + /// The caller origin, overarching type of all pallets origins. + type PalletsOrigin: Parameter + + Into<::RuntimeOrigin> + + IsType<<::RuntimeOrigin as frame_support::traits::OriginTrait>::PalletsOrigin>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -104,20 +107,34 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Batch of dispatches did not complete fully. Index of first failing dispatch given, as - /// well as the error. \[index, error\] - BatchInterrupted(u32, DispatchError), + /// well as the error. + BatchInterrupted { index: u32, error: DispatchError }, /// Batch of dispatches completed fully with no error. BatchCompleted, + /// Batch of dispatches completed but has errors. + BatchCompletedWithErrors, /// A single item within a Batch of dispatches has completed with no error. ItemCompleted, + /// A single item within a Batch of dispatches has completed with error. + ItemFailed { error: DispatchError }, + /// A call was dispatched. + DispatchedAs { result: DispatchResult }, } + // Align the call size to 1KB. As we are currently compiling the runtime for native/wasm + // the `size_of` of the `Call` can be different. To ensure that this don't leads to + // mismatches between native/wasm or to different metadata for the same runtime, we + // algin the call size. The value is chosen big enough to hopefully never reach it. + const CALL_ALIGN: u32 = 1024; + #[pallet::extra_constants] impl Pallet { /// The limit on the number of batched calls. fn batched_calls_limit() -> u32 { let allocator_limit = sp_core::MAX_POSSIBLE_ALLOCATION; - let call_size = core::mem::size_of::<::Call>() as u32; + let call_size = ((sp_std::mem::size_of::<::RuntimeCall>() as u32 + + CALL_ALIGN - 1) / CALL_ALIGN) * + CALL_ALIGN; // The margin to take into account vec doubling capacity. let margin_factor = 3; @@ -125,6 +142,18 @@ pub mod pallet { } } + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + // If you hit this error, you need to try to `Box` big dispatchable parameters. + assert!( + sp_std::mem::size_of::<::RuntimeCall>() as u32 <= CALL_ALIGN, + "Call enum size should be smaller than {} bytes.", + CALL_ALIGN, + ); + } + } + #[pallet::error] pub enum Error { /// Too many calls batched. @@ -135,13 +164,13 @@ pub mod pallet { impl Pallet { /// Send a batch of dispatch calls. /// - /// May be called from any origin. + /// May be called from any origin except `None`. /// /// - `calls`: The calls to be dispatched from the same origin. The number of call must not /// exceed the constant: `batched_calls_limit` (available in constant metadata). /// - /// If origin is root then call are dispatch without checking origin filter. (This includes - /// bypassing `frame_system::Config::BaseCallFilter`). + /// If origin is root then the calls are dispatched without checking origin filter. (This + /// includes bypassing `frame_system::Config::BaseCallFilter`). /// /// # /// - Complexity: O(C) where C is the number of calls to be batched. @@ -152,11 +181,12 @@ pub mod pallet { /// `BatchInterrupted` event is deposited, along with the number of successful calls made /// and the error of the failed call. If all were successful, then the `BatchCompleted` /// event is deposited. + #[pallet::call_index(0)] #[pallet::weight({ let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); let dispatch_weight = dispatch_infos.iter() .map(|di| di.weight) - .fold(0, |total: Weight, weight: Weight| total.saturating_add(weight)) + .fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight)) .saturating_add(T::WeightInfo::batch(calls.len() as u32)); let dispatch_class = { let all_operational = dispatch_infos.iter() @@ -172,14 +202,19 @@ pub mod pallet { })] pub fn batch( origin: OriginFor, - calls: Vec<::Call>, + calls: Vec<::RuntimeCall>, ) -> DispatchResultWithPostInfo { + // Do not allow the `None` origin. + if ensure_none(origin.clone()).is_ok() { + return Err(BadOrigin.into()) + } + let is_root = ensure_root(origin.clone()).is_ok(); let calls_len = calls.len(); ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::::TooManyCalls); // Track the actual weight of each of the batch calls. - let mut weight: Weight = 0; + let mut weight = Weight::zero(); for (index, call) in calls.into_iter().enumerate() { let info = call.get_dispatch_info(); // If origin is root, don't apply any dispatch filters; root can call anything. @@ -191,7 +226,10 @@ pub mod pallet { // Add the weight of this call. weight = weight.saturating_add(extract_actual_weight(&result, &info)); if let Err(e) = result { - Self::deposit_event(Event::BatchInterrupted(index as u32, e.error)); + Self::deposit_event(Event::BatchInterrupted { + index: index as u32, + error: e.error, + }); // Take the weight of this function itself into account. let base_weight = T::WeightInfo::batch(index.saturating_add(1) as u32); // Return the actual used weight + base_weight of this call. @@ -217,20 +255,21 @@ pub mod pallet { /// NOTE: Prior to version *12, this was called `as_limited_sub`. /// /// The dispatch origin for this call must be _Signed_. + #[pallet::call_index(1)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( T::WeightInfo::as_derivative() - .saturating_add(dispatch_info.weight) // AccountData for inner call origin accountdata. - .saturating_add(T::DbWeight::get().reads_writes(1, 1)), + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(dispatch_info.weight), dispatch_info.class, ) })] pub fn as_derivative( origin: OriginFor, index: u16, - call: Box<::Call>, + call: Box<::RuntimeCall>, ) -> DispatchResultWithPostInfo { let mut origin = origin; let who = ensure_signed(origin.clone())?; @@ -254,22 +293,23 @@ pub mod pallet { /// Send a batch of dispatch calls and atomically execute them. /// The whole transaction will rollback and fail if any of the calls failed. /// - /// May be called from any origin. + /// May be called from any origin except `None`. /// /// - `calls`: The calls to be dispatched from the same origin. The number of call must not /// exceed the constant: `batched_calls_limit` (available in constant metadata). /// - /// If origin is root then call are dispatch without checking origin filter. (This includes - /// bypassing `frame_system::Config::BaseCallFilter`). + /// If origin is root then the calls are dispatched without checking origin filter. (This + /// includes bypassing `frame_system::Config::BaseCallFilter`). /// /// # /// - Complexity: O(C) where C is the number of calls to be batched. /// # + #[pallet::call_index(2)] #[pallet::weight({ let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); let dispatch_weight = dispatch_infos.iter() .map(|di| di.weight) - .fold(0, |total: Weight, weight: Weight| total.saturating_add(weight)) + .fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight)) .saturating_add(T::WeightInfo::batch_all(calls.len() as u32)); let dispatch_class = { let all_operational = dispatch_infos.iter() @@ -283,17 +323,21 @@ pub mod pallet { }; (dispatch_weight, dispatch_class) })] - #[transactional] pub fn batch_all( origin: OriginFor, - calls: Vec<::Call>, + calls: Vec<::RuntimeCall>, ) -> DispatchResultWithPostInfo { + // Do not allow the `None` origin. + if ensure_none(origin.clone()).is_ok() { + return Err(BadOrigin.into()) + } + let is_root = ensure_root(origin.clone()).is_ok(); let calls_len = calls.len(); ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::::TooManyCalls); // Track the actual weight of each of the batch calls. - let mut weight: Weight = 0; + let mut weight = Weight::zero(); for (index, call) in calls.into_iter().enumerate() { let info = call.get_dispatch_info(); // If origin is root, bypass any dispatch filter; root can call anything. @@ -302,10 +346,12 @@ pub mod pallet { } else { let mut filtered_origin = origin.clone(); // Don't allow users to nest `batch_all` calls. - filtered_origin.add_filter(move |c: &::Call| { - let c = ::Call::from_ref(c); - !matches!(c.is_sub_type(), Some(Call::batch_all { .. })) - }); + filtered_origin.add_filter( + move |c: &::RuntimeCall| { + let c = ::RuntimeCall::from_ref(c); + !matches!(c.is_sub_type(), Some(Call::batch_all { .. })) + }, + ); call.dispatch(filtered_origin) }; // Add the weight of this call. @@ -321,7 +367,135 @@ pub mod pallet { } Self::deposit_event(Event::BatchCompleted); let base_weight = T::WeightInfo::batch_all(calls_len as u32); - Ok(Some(base_weight + weight).into()) + Ok(Some(base_weight.saturating_add(weight)).into()) + } + + /// Dispatches a function call with a provided origin. + /// + /// The dispatch origin for this call must be _Root_. + /// + /// # + /// - O(1). + /// - Limited storage reads. + /// - One DB write (event). + /// - Weight of derivative `call` execution + T::WeightInfo::dispatch_as(). + /// # + #[pallet::call_index(3)] + #[pallet::weight({ + let dispatch_info = call.get_dispatch_info(); + ( + T::WeightInfo::dispatch_as() + .saturating_add(dispatch_info.weight), + dispatch_info.class, + ) + })] + pub fn dispatch_as( + origin: OriginFor, + as_origin: Box, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + ensure_root(origin)?; + + let res = call.dispatch_bypass_filter((*as_origin).into()); + + Self::deposit_event(Event::DispatchedAs { + result: res.map(|_| ()).map_err(|e| e.error), + }); + Ok(()) + } + + /// Send a batch of dispatch calls. + /// Unlike `batch`, it allows errors and won't interrupt. + /// + /// May be called from any origin except `None`. + /// + /// - `calls`: The calls to be dispatched from the same origin. The number of call must not + /// exceed the constant: `batched_calls_limit` (available in constant metadata). + /// + /// If origin is root then the calls are dispatch without checking origin filter. (This + /// includes bypassing `frame_system::Config::BaseCallFilter`). + /// + /// # + /// - Complexity: O(C) where C is the number of calls to be batched. + /// # + #[pallet::call_index(4)] + #[pallet::weight({ + let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); + let dispatch_weight = dispatch_infos.iter() + .map(|di| di.weight) + .fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight)) + .saturating_add(T::WeightInfo::force_batch(calls.len() as u32)); + let dispatch_class = { + let all_operational = dispatch_infos.iter() + .map(|di| di.class) + .all(|class| class == DispatchClass::Operational); + if all_operational { + DispatchClass::Operational + } else { + DispatchClass::Normal + } + }; + (dispatch_weight, dispatch_class) + })] + pub fn force_batch( + origin: OriginFor, + calls: Vec<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + // Do not allow the `None` origin. + if ensure_none(origin.clone()).is_ok() { + return Err(BadOrigin.into()) + } + + let is_root = ensure_root(origin.clone()).is_ok(); + let calls_len = calls.len(); + ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::::TooManyCalls); + + // Track the actual weight of each of the batch calls. + let mut weight = Weight::zero(); + // Track failed dispatch occur. + let mut has_error: bool = false; + for call in calls.into_iter() { + let info = call.get_dispatch_info(); + // If origin is root, don't apply any dispatch filters; root can call anything. + let result = if is_root { + call.dispatch_bypass_filter(origin.clone()) + } else { + call.dispatch(origin.clone()) + }; + // Add the weight of this call. + weight = weight.saturating_add(extract_actual_weight(&result, &info)); + if let Err(e) = result { + has_error = true; + Self::deposit_event(Event::ItemFailed { error: e.error }); + } else { + Self::deposit_event(Event::ItemCompleted); + } + } + if has_error { + Self::deposit_event(Event::BatchCompletedWithErrors); + } else { + Self::deposit_event(Event::BatchCompleted); + } + let base_weight = T::WeightInfo::batch(calls_len as u32); + Ok(Some(base_weight.saturating_add(weight)).into()) + } + + /// Dispatch a function call with a specified weight. + /// + /// This function does not check the weight of the call, and instead allows the + /// Root origin to specify the weight of the call. + /// + /// The dispatch origin for this call must be _Root_. + #[pallet::call_index(5)] + #[pallet::weight((*_weight, call.get_dispatch_info().class))] + pub fn with_weight( + origin: OriginFor, + call: Box<::RuntimeCall>, + _weight: Weight, + ) -> DispatchResult { + ensure_root(origin)?; + let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); + res.map(|_| ()).map_err(|e| e.error) } } } @@ -338,6 +512,7 @@ impl Pallet { /// Derive a derivative account ID from the owner account and the sub-account index. pub fn derivative_account_id(who: T::AccountId, index: u16) -> T::AccountId { let entropy = (b"modlpy/utilisuba", who, index).using_encoded(blake2_256); - T::AccountId::decode(&mut &entropy[..]).unwrap_or_default() + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") } } diff --git a/frame/utility/src/tests.rs b/frame/utility/src/tests.rs index bbfbb417e23d1..f9d6a16c1a0d4 100644 --- a/frame/utility/src/tests.rs +++ b/frame/utility/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,21 +24,24 @@ use super::*; use crate as utility; use frame_support::{ assert_err_ignore_postinfo, assert_noop, assert_ok, - dispatch::{DispatchError, DispatchErrorWithPostInfo, Dispatchable}, + dispatch::{DispatchError, DispatchErrorWithPostInfo, Dispatchable, Pays}, + error::BadOrigin, parameter_types, storage, - traits::Contains, - weights::{Pays, Weight}, + traits::{ConstU32, ConstU64, Contains, GenesisBuild}, + weights::Weight, }; +use pallet_collective::{EnsureProportionAtLeast, Instance1}; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, Hash, IdentityLookup}, }; +type BlockNumber = u64; + // example module to test behaviors. #[frame_support::pallet] pub mod example { - use super::*; use frame_support::{dispatch::WithPostDispatchInfo, pallet_prelude::*}; use frame_system::pallet_prelude::*; @@ -50,11 +53,13 @@ pub mod example { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(*_weight)] pub fn noop(_origin: OriginFor, _weight: Weight) -> DispatchResult { Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(*_start_weight)] pub fn foobar( origin: OriginFor, @@ -75,6 +80,7 @@ pub mod example { } } + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn big_variant(_origin: OriginFor, _arg: [u8; 400]) -> DispatchResult { Ok(()) @@ -82,6 +88,43 @@ pub mod example { } } +mod mock_democracy { + pub use pallet::*; + #[frame_support::pallet] + pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + Sized { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + type ExternalMajorityOrigin: EnsureOrigin; + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(3)] + #[pallet::weight(0)] + pub fn external_propose_majority(origin: OriginFor) -> DispatchResult { + T::ExternalMajorityOrigin::ensure_origin(origin)?; + Self::deposit_event(Event::::ExternalProposed); + Ok(()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + ExternalProposed, + } + } +} + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -92,33 +135,36 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, + Timestamp: pallet_timestamp::{Call, Inherent}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + RootTesting: pallet_root_testing::{Pallet, Call, Storage}, + Council: pallet_collective::, Utility: utility::{Pallet, Call, Event}, Example: example::{Pallet, Call}, + Democracy: mock_democracy::{Pallet, Call, Event}, } ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::max_value()); + frame_system::limits::BlockWeights::simple_max(Weight::MAX); } impl frame_system::Config for Test { type BaseCallFilter = TestBaseCallFilter; type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type Hash = H256; - type Call = Call; + type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -127,47 +173,79 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } + +impl pallet_root_testing::Config for Test {} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<3>; + type WeightInfo = (); +} + +const MOTION_DURATION_IN_BLOCKS: BlockNumber = 3; parameter_types! { pub const MultisigDepositBase: u64 = 1; pub const MultisigDepositFactor: u64 = 1; - pub const MaxSignatories: u16 = 3; + pub const MaxSignatories: u32 = 3; + pub const MotionDuration: BlockNumber = MOTION_DURATION_IN_BLOCKS; + pub const MaxProposals: u32 = 100; + pub const MaxMembers: u32 = 100; +} + +type CouncilCollective = pallet_collective::Instance1; +impl pallet_collective::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = MotionDuration; + type MaxProposals = MaxProposals; + type MaxMembers = MaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type WeightInfo = (); } impl example::Config for Test {} pub struct TestBaseCallFilter; -impl Contains for TestBaseCallFilter { - fn contains(c: &Call) -> bool { +impl Contains for TestBaseCallFilter { + fn contains(c: &RuntimeCall) -> bool { match *c { // Transfer works. Use `transfer_keep_alive` for a call that doesn't pass the filter. - Call::Balances(pallet_balances::Call::transfer { .. }) => true, - Call::Utility(_) => true, + RuntimeCall::Balances(pallet_balances::Call::transfer { .. }) => true, + RuntimeCall::Utility(_) => true, // For benchmarking, this acts as a noop call - Call::System(frame_system::Call::remark { .. }) => true, + RuntimeCall::System(frame_system::Call::remark { .. }) => true, // For tests - Call::Example(_) => true, + RuntimeCall::Example(_) => true, + // For council origin tests. + RuntimeCall::Democracy(_) => true, _ => false, } } } +impl mock_democracy::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ExternalMajorityOrigin = EnsureProportionAtLeast; +} impl Config for Test { - type Event = Event; - type Call = Call; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; type WeightInfo = (); } @@ -176,6 +254,8 @@ type UtilityCall = crate::Call; use frame_system::Call as SystemCall; use pallet_balances::{Call as BalancesCall, Error as BalancesError}; +use pallet_root_testing::Call as RootTestingCall; +use pallet_timestamp::Call as TimestampCall; pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); @@ -184,29 +264,41 @@ pub fn new_test_ext() -> sp_io::TestExternalities { } .assimilate_storage(&mut t) .unwrap(); + + pallet_collective::GenesisConfig:: { + members: vec![1, 2, 3], + phantom: Default::default(), + } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext } -fn call_transfer(dest: u64, value: u64) -> Call { - Call::Balances(BalancesCall::transfer { dest, value }) +fn call_transfer(dest: u64, value: u64) -> RuntimeCall { + RuntimeCall::Balances(BalancesCall::transfer { dest, value }) } -fn call_foobar(err: bool, start_weight: u64, end_weight: Option) -> Call { - Call::Example(ExampleCall::foobar { err, start_weight, end_weight }) +fn call_foobar(err: bool, start_weight: Weight, end_weight: Option) -> RuntimeCall { + RuntimeCall::Example(ExampleCall::foobar { err, start_weight, end_weight }) } #[test] fn as_derivative_works() { new_test_ext().execute_with(|| { let sub_1_0 = Utility::derivative_account_id(1, 0); - assert_ok!(Balances::transfer(Origin::signed(1), sub_1_0, 5)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), sub_1_0, 5)); assert_err_ignore_postinfo!( - Utility::as_derivative(Origin::signed(1), 1, Box::new(call_transfer(6, 3)),), + Utility::as_derivative(RuntimeOrigin::signed(1), 1, Box::new(call_transfer(6, 3)),), BalancesError::::InsufficientBalance ); - assert_ok!(Utility::as_derivative(Origin::signed(1), 0, Box::new(call_transfer(2, 3)),)); + assert_ok!(Utility::as_derivative( + RuntimeOrigin::signed(1), + 0, + Box::new(call_transfer(2, 3)), + )); assert_eq!(Balances::free_balance(sub_1_0), 2); assert_eq!(Balances::free_balance(2), 13); }); @@ -215,35 +307,41 @@ fn as_derivative_works() { #[test] fn as_derivative_handles_weight_refund() { new_test_ext().execute_with(|| { - let start_weight = 100; - let end_weight = 75; + let start_weight = Weight::from_ref_time(100); + let end_weight = Weight::from_ref_time(75); let diff = start_weight - end_weight; // Full weight when ok let inner_call = call_foobar(false, start_weight, None); - let call = - Call::Utility(UtilityCall::as_derivative { index: 0, call: Box::new(inner_call) }); + let call = RuntimeCall::Utility(UtilityCall::as_derivative { + index: 0, + call: Box::new(inner_call), + }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), info.weight); // Refund weight when ok let inner_call = call_foobar(false, start_weight, Some(end_weight)); - let call = - Call::Utility(UtilityCall::as_derivative { index: 0, call: Box::new(inner_call) }); + let call = RuntimeCall::Utility(UtilityCall::as_derivative { + index: 0, + call: Box::new(inner_call), + }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_ok!(result); // Diff is refunded assert_eq!(extract_actual_weight(&result, &info), info.weight - diff); // Full weight when err let inner_call = call_foobar(true, start_weight, None); - let call = - Call::Utility(UtilityCall::as_derivative { index: 0, call: Box::new(inner_call) }); + let call = RuntimeCall::Utility(UtilityCall::as_derivative { + index: 0, + call: Box::new(inner_call), + }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_noop!( result, DispatchErrorWithPostInfo { @@ -258,10 +356,12 @@ fn as_derivative_handles_weight_refund() { // Refund weight when err let inner_call = call_foobar(true, start_weight, Some(end_weight)); - let call = - Call::Utility(UtilityCall::as_derivative { index: 0, call: Box::new(inner_call) }); + let call = RuntimeCall::Utility(UtilityCall::as_derivative { + index: 0, + call: Box::new(inner_call), + }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_noop!( result, DispatchErrorWithPostInfo { @@ -281,14 +381,14 @@ fn as_derivative_filters() { new_test_ext().execute_with(|| { assert_err_ignore_postinfo!( Utility::as_derivative( - Origin::signed(1), + RuntimeOrigin::signed(1), 1, - Box::new(Call::Balances(pallet_balances::Call::transfer_keep_alive { + Box::new(RuntimeCall::Balances(pallet_balances::Call::transfer_keep_alive { dest: 2, value: 1 })), ), - DispatchError::BadOrigin + DispatchError::from(frame_system::Error::::CallFiltered), ); }); } @@ -297,16 +397,25 @@ fn as_derivative_filters() { fn batch_with_root_works() { new_test_ext().execute_with(|| { let k = b"a".to_vec(); - let call = - Call::System(frame_system::Call::set_storage { items: vec![(k.clone(), k.clone())] }); + let call = RuntimeCall::System(frame_system::Call::set_storage { + items: vec![(k.clone(), k.clone())], + }); assert!(!TestBaseCallFilter::contains(&call)); assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::free_balance(2), 10); assert_ok!(Utility::batch( - Origin::root(), + RuntimeOrigin::root(), vec![ - Call::Balances(BalancesCall::force_transfer { source: 1, dest: 2, value: 5 }), - Call::Balances(BalancesCall::force_transfer { source: 1, dest: 2, value: 5 }), + RuntimeCall::Balances(BalancesCall::force_transfer { + source: 1, + dest: 2, + value: 5 + }), + RuntimeCall::Balances(BalancesCall::force_transfer { + source: 1, + dest: 2, + value: 5 + }), call, // Check filters are correctly bypassed ] )); @@ -322,7 +431,7 @@ fn batch_with_signed_works() { assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::free_balance(2), 10); assert_ok!(Utility::batch( - Origin::signed(1), + RuntimeOrigin::signed(1), vec![call_transfer(2, 5), call_transfer(2, 5)] ),); assert_eq!(Balances::free_balance(1), 0); @@ -334,11 +443,18 @@ fn batch_with_signed_works() { fn batch_with_signed_filters() { new_test_ext().execute_with(|| { assert_ok!(Utility::batch( - Origin::signed(1), - vec![Call::Balances(pallet_balances::Call::transfer_keep_alive { dest: 2, value: 1 })] + RuntimeOrigin::signed(1), + vec![RuntimeCall::Balances(pallet_balances::Call::transfer_keep_alive { + dest: 2, + value: 1 + })] ),); System::assert_last_event( - utility::Event::BatchInterrupted(0, DispatchError::BadOrigin).into(), + utility::Event::BatchInterrupted { + index: 0, + error: frame_system::Error::::CallFiltered.into(), + } + .into(), ); }); } @@ -349,7 +465,7 @@ fn batch_early_exit_works() { assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::free_balance(2), 10); assert_ok!(Utility::batch( - Origin::signed(1), + RuntimeOrigin::signed(1), vec![call_transfer(2, 5), call_transfer(2, 10), call_transfer(2, 5),] ),); assert_eq!(Balances::free_balance(1), 5); @@ -361,41 +477,43 @@ fn batch_early_exit_works() { fn batch_weight_calculation_doesnt_overflow() { use sp_runtime::Perbill; new_test_ext().execute_with(|| { - let big_call = Call::System(SystemCall::fill_block { ratio: Perbill::from_percent(50) }); - assert_eq!(big_call.get_dispatch_info().weight, Weight::max_value() / 2); + let big_call = RuntimeCall::RootTesting(RootTestingCall::fill_block { + ratio: Perbill::from_percent(50), + }); + assert_eq!(big_call.get_dispatch_info().weight, Weight::MAX / 2); // 3 * 50% saturates to 100% - let batch_call = Call::Utility(crate::Call::batch { + let batch_call = RuntimeCall::Utility(crate::Call::batch { calls: vec![big_call.clone(), big_call.clone(), big_call.clone()], }); - assert_eq!(batch_call.get_dispatch_info().weight, Weight::max_value()); + assert_eq!(batch_call.get_dispatch_info().weight, Weight::MAX); }); } #[test] fn batch_handles_weight_refund() { new_test_ext().execute_with(|| { - let start_weight = 100; - let end_weight = 75; + let start_weight = Weight::from_ref_time(100); + let end_weight = Weight::from_ref_time(75); let diff = start_weight - end_weight; - let batch_len: Weight = 4; + let batch_len = 4; // Full weight when ok let inner_call = call_foobar(false, start_weight, None); let batch_calls = vec![inner_call; batch_len as usize]; - let call = Call::Utility(UtilityCall::batch { calls: batch_calls }); + let call = RuntimeCall::Utility(UtilityCall::batch { calls: batch_calls }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), info.weight); // Refund weight when ok let inner_call = call_foobar(false, start_weight, Some(end_weight)); let batch_calls = vec![inner_call; batch_len as usize]; - let call = Call::Utility(UtilityCall::batch { calls: batch_calls }); + let call = RuntimeCall::Utility(UtilityCall::batch { calls: batch_calls }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_ok!(result); // Diff is refunded assert_eq!(extract_actual_weight(&result, &info), info.weight - diff * batch_len); @@ -404,12 +522,12 @@ fn batch_handles_weight_refund() { let good_call = call_foobar(false, start_weight, None); let bad_call = call_foobar(true, start_weight, None); let batch_calls = vec![good_call, bad_call]; - let call = Call::Utility(UtilityCall::batch { calls: batch_calls }); + let call = RuntimeCall::Utility(UtilityCall::batch { calls: batch_calls }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_ok!(result); System::assert_last_event( - utility::Event::BatchInterrupted(1, DispatchError::Other("")).into(), + utility::Event::BatchInterrupted { index: 1, error: DispatchError::Other("") }.into(), ); // No weight is refunded assert_eq!(extract_actual_weight(&result, &info), info.weight); @@ -418,13 +536,13 @@ fn batch_handles_weight_refund() { let good_call = call_foobar(false, start_weight, Some(end_weight)); let bad_call = call_foobar(true, start_weight, Some(end_weight)); let batch_calls = vec![good_call, bad_call]; - let batch_len = batch_calls.len() as Weight; - let call = Call::Utility(UtilityCall::batch { calls: batch_calls }); + let batch_len = batch_calls.len() as u64; + let call = RuntimeCall::Utility(UtilityCall::batch { calls: batch_calls }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_ok!(result); System::assert_last_event( - utility::Event::BatchInterrupted(1, DispatchError::Other("")).into(), + utility::Event::BatchInterrupted { index: 1, error: DispatchError::Other("") }.into(), ); assert_eq!(extract_actual_weight(&result, &info), info.weight - diff * batch_len); @@ -432,12 +550,12 @@ fn batch_handles_weight_refund() { let good_call = call_foobar(false, start_weight, Some(end_weight)); let bad_call = call_foobar(true, start_weight, Some(end_weight)); let batch_calls = vec![good_call, bad_call.clone(), bad_call]; - let call = Call::Utility(UtilityCall::batch { calls: batch_calls }); + let call = RuntimeCall::Utility(UtilityCall::batch { calls: batch_calls }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_ok!(result); System::assert_last_event( - utility::Event::BatchInterrupted(1, DispatchError::Other("")).into(), + utility::Event::BatchInterrupted { index: 1, error: DispatchError::Other("") }.into(), ); assert_eq!( extract_actual_weight(&result, &info), @@ -453,7 +571,7 @@ fn batch_all_works() { assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::free_balance(2), 10); assert_ok!(Utility::batch_all( - Origin::signed(1), + RuntimeOrigin::signed(1), vec![call_transfer(2, 5), call_transfer(2, 5)] ),); assert_eq!(Balances::free_balance(1), 0); @@ -469,11 +587,11 @@ fn batch_all_revert() { assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::free_balance(2), 10); + let batch_all_calls = RuntimeCall::Utility(crate::Call::::batch_all { + calls: vec![call_transfer(2, 5), call_transfer(2, 10), call_transfer(2, 5)], + }); assert_noop!( - Utility::batch_all( - Origin::signed(1), - vec![call_transfer(2, 5), call_transfer(2, 10), call_transfer(2, 5),] - ), + batch_all_calls.dispatch(RuntimeOrigin::signed(1)), DispatchErrorWithPostInfo { post_info: PostDispatchInfo { actual_weight: Some( @@ -492,26 +610,26 @@ fn batch_all_revert() { #[test] fn batch_all_handles_weight_refund() { new_test_ext().execute_with(|| { - let start_weight = 100; - let end_weight = 75; + let start_weight = Weight::from_ref_time(100); + let end_weight = Weight::from_ref_time(75); let diff = start_weight - end_weight; - let batch_len: Weight = 4; + let batch_len = 4; // Full weight when ok let inner_call = call_foobar(false, start_weight, None); let batch_calls = vec![inner_call; batch_len as usize]; - let call = Call::Utility(UtilityCall::batch_all { calls: batch_calls }); + let call = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), info.weight); // Refund weight when ok let inner_call = call_foobar(false, start_weight, Some(end_weight)); let batch_calls = vec![inner_call; batch_len as usize]; - let call = Call::Utility(UtilityCall::batch_all { calls: batch_calls }); + let call = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_ok!(result); // Diff is refunded assert_eq!(extract_actual_weight(&result, &info), info.weight - diff * batch_len); @@ -520,9 +638,9 @@ fn batch_all_handles_weight_refund() { let good_call = call_foobar(false, start_weight, None); let bad_call = call_foobar(true, start_weight, None); let batch_calls = vec![good_call, bad_call]; - let call = Call::Utility(UtilityCall::batch_all { calls: batch_calls }); + let call = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_err_ignore_postinfo!(result, "The cake is a lie."); // No weight is refunded assert_eq!(extract_actual_weight(&result, &info), info.weight); @@ -531,10 +649,10 @@ fn batch_all_handles_weight_refund() { let good_call = call_foobar(false, start_weight, Some(end_weight)); let bad_call = call_foobar(true, start_weight, Some(end_weight)); let batch_calls = vec![good_call, bad_call]; - let batch_len = batch_calls.len() as Weight; - let call = Call::Utility(UtilityCall::batch_all { calls: batch_calls }); + let batch_len = batch_calls.len() as u64; + let call = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_err_ignore_postinfo!(result, "The cake is a lie."); assert_eq!(extract_actual_weight(&result, &info), info.weight - diff * batch_len); @@ -542,9 +660,9 @@ fn batch_all_handles_weight_refund() { let good_call = call_foobar(false, start_weight, Some(end_weight)); let bad_call = call_foobar(true, start_weight, Some(end_weight)); let batch_calls = vec![good_call, bad_call.clone(), bad_call]; - let call = Call::Utility(UtilityCall::batch_all { calls: batch_calls }); + let call = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls }); let info = call.get_dispatch_info(); - let result = call.dispatch(Origin::signed(1)); + let result = call.dispatch(RuntimeOrigin::signed(1)); assert_err_ignore_postinfo!(result, "The cake is a lie."); assert_eq!( extract_actual_weight(&result, &info), @@ -557,7 +675,7 @@ fn batch_all_handles_weight_refund() { #[test] fn batch_all_does_not_nest() { new_test_ext().execute_with(|| { - let batch_all = Call::Utility(UtilityCall::batch_all { + let batch_all = RuntimeCall::Utility(UtilityCall::batch_all { calls: vec![call_transfer(2, 1), call_transfer(2, 1), call_transfer(2, 1)], }); @@ -567,25 +685,29 @@ fn batch_all_does_not_nest() { assert_eq!(Balances::free_balance(2), 10); // A nested batch_all call will not pass the filter, and fail with `BadOrigin`. assert_noop!( - Utility::batch_all(Origin::signed(1), vec![batch_all.clone()]), + Utility::batch_all(RuntimeOrigin::signed(1), vec![batch_all.clone()]), DispatchErrorWithPostInfo { post_info: PostDispatchInfo { actual_weight: Some(::WeightInfo::batch_all(1) + info.weight), pays_fee: Pays::Yes }, - error: DispatchError::BadOrigin, + error: frame_system::Error::::CallFiltered.into(), } ); // And for those who want to get a little fancy, we check that the filter persists across // other kinds of dispatch wrapping functions... in this case // `batch_all(batch(batch_all(..)))` - let batch_nested = Call::Utility(UtilityCall::batch { calls: vec![batch_all] }); + let batch_nested = RuntimeCall::Utility(UtilityCall::batch { calls: vec![batch_all] }); // Batch will end with `Ok`, but does not actually execute as we can see from the event // and balances. - assert_ok!(Utility::batch_all(Origin::signed(1), vec![batch_nested])); + assert_ok!(Utility::batch_all(RuntimeOrigin::signed(1), vec![batch_nested])); System::assert_has_event( - utility::Event::BatchInterrupted(0, DispatchError::BadOrigin).into(), + utility::Event::BatchInterrupted { + index: 0, + error: frame_system::Error::::CallFiltered.into(), + } + .into(), ); assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::free_balance(2), 10); @@ -595,8 +717,218 @@ fn batch_all_does_not_nest() { #[test] fn batch_limit() { new_test_ext().execute_with(|| { - let calls = vec![Call::System(SystemCall::remark { remark: vec![] }); 40_000]; - assert_noop!(Utility::batch(Origin::signed(1), calls.clone()), Error::::TooManyCalls); - assert_noop!(Utility::batch_all(Origin::signed(1), calls), Error::::TooManyCalls); + let calls = vec![RuntimeCall::System(SystemCall::remark { remark: vec![] }); 40_000]; + assert_noop!( + Utility::batch(RuntimeOrigin::signed(1), calls.clone()), + Error::::TooManyCalls + ); + assert_noop!( + Utility::batch_all(RuntimeOrigin::signed(1), calls), + Error::::TooManyCalls + ); + }); +} + +#[test] +fn force_batch_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::force_batch( + RuntimeOrigin::signed(1), + vec![ + call_transfer(2, 5), + call_foobar(true, Weight::from_ref_time(75), None), + call_transfer(2, 10), + call_transfer(2, 5), + ] + )); + System::assert_last_event(utility::Event::BatchCompletedWithErrors.into()); + System::assert_has_event( + utility::Event::ItemFailed { error: DispatchError::Other("") }.into(), + ); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 20); + + assert_ok!(Utility::force_batch( + RuntimeOrigin::signed(2), + vec![call_transfer(1, 5), call_transfer(1, 5),] + )); + System::assert_last_event(utility::Event::BatchCompleted.into()); + + assert_ok!(Utility::force_batch(RuntimeOrigin::signed(1), vec![call_transfer(2, 50),]),); + System::assert_last_event(utility::Event::BatchCompletedWithErrors.into()); }); } + +#[test] +fn none_origin_does_not_work() { + new_test_ext().execute_with(|| { + assert_noop!(Utility::force_batch(RuntimeOrigin::none(), vec![]), BadOrigin); + assert_noop!(Utility::batch(RuntimeOrigin::none(), vec![]), BadOrigin); + assert_noop!(Utility::batch_all(RuntimeOrigin::none(), vec![]), BadOrigin); + }) +} + +#[test] +fn batch_doesnt_work_with_inherents() { + new_test_ext().execute_with(|| { + // fails because inherents expect the origin to be none. + assert_ok!(Utility::batch( + RuntimeOrigin::signed(1), + vec![RuntimeCall::Timestamp(TimestampCall::set { now: 42 }),] + )); + System::assert_last_event( + utility::Event::BatchInterrupted { + index: 0, + error: frame_system::Error::::CallFiltered.into(), + } + .into(), + ); + }) +} + +#[test] +fn force_batch_doesnt_work_with_inherents() { + new_test_ext().execute_with(|| { + // fails because inherents expect the origin to be none. + assert_ok!(Utility::force_batch( + RuntimeOrigin::root(), + vec![RuntimeCall::Timestamp(TimestampCall::set { now: 42 }),] + )); + System::assert_last_event(utility::Event::BatchCompletedWithErrors.into()); + }) +} + +#[test] +fn batch_all_doesnt_work_with_inherents() { + new_test_ext().execute_with(|| { + let batch_all = RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![RuntimeCall::Timestamp(TimestampCall::set { now: 42 })], + }); + let info = batch_all.get_dispatch_info(); + + // fails because inherents expect the origin to be none. + assert_noop!( + batch_all.dispatch(RuntimeOrigin::signed(1)), + DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(info.weight), + pays_fee: Pays::Yes + }, + error: frame_system::Error::::CallFiltered.into(), + } + ); + }) +} + +#[test] +fn batch_works_with_council_origin() { + new_test_ext().execute_with(|| { + let proposal = RuntimeCall::Utility(UtilityCall::batch { + calls: vec![RuntimeCall::Democracy(mock_democracy::Call::external_propose_majority {})], + }); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + let proposal_weight = proposal.get_dispatch_info().weight; + let hash = BlakeTwo256::hash_of(&proposal); + + assert_ok!(Council::propose( + RuntimeOrigin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + + assert_ok!(Council::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(2), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(3), hash, 0, true)); + + System::set_block_number(4); + assert_ok!(Council::close( + RuntimeOrigin::signed(4), + hash, + 0, + proposal_weight, + proposal_len + )); + + System::assert_last_event(RuntimeEvent::Council(pallet_collective::Event::Executed { + proposal_hash: hash, + result: Ok(()), + })); + }) +} + +#[test] +fn force_batch_works_with_council_origin() { + new_test_ext().execute_with(|| { + let proposal = RuntimeCall::Utility(UtilityCall::force_batch { + calls: vec![RuntimeCall::Democracy(mock_democracy::Call::external_propose_majority {})], + }); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + let proposal_weight = proposal.get_dispatch_info().weight; + let hash = BlakeTwo256::hash_of(&proposal); + + assert_ok!(Council::propose( + RuntimeOrigin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + + assert_ok!(Council::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(2), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(3), hash, 0, true)); + + System::set_block_number(4); + assert_ok!(Council::close( + RuntimeOrigin::signed(4), + hash, + 0, + proposal_weight, + proposal_len + )); + + System::assert_last_event(RuntimeEvent::Council(pallet_collective::Event::Executed { + proposal_hash: hash, + result: Ok(()), + })); + }) +} + +#[test] +fn batch_all_works_with_council_origin() { + new_test_ext().execute_with(|| { + assert_ok!(Utility::batch_all( + RuntimeOrigin::from(pallet_collective::RawOrigin::Members(3, 3)), + vec![RuntimeCall::Democracy(mock_democracy::Call::external_propose_majority {})] + )); + }) +} + +#[test] +fn with_weight_works() { + new_test_ext().execute_with(|| { + let upgrade_code_call = + Box::new(RuntimeCall::System(frame_system::Call::set_code_without_checks { + code: vec![], + })); + // Weight before is max. + assert_eq!(upgrade_code_call.get_dispatch_info().weight, Weight::MAX); + assert_eq!( + upgrade_code_call.get_dispatch_info().class, + frame_support::dispatch::DispatchClass::Operational + ); + + let with_weight_call = Call::::with_weight { + call: upgrade_code_call, + weight: Weight::from_parts(123, 456), + }; + // Weight after is set by Root. + assert_eq!(with_weight_call.get_dispatch_info().weight, Weight::from_parts(123, 456)); + assert_eq!( + with_weight_call.get_dispatch_info().class, + frame_support::dispatch::DispatchClass::Operational + ); + }) +} diff --git a/frame/utility/src/weights.rs b/frame/utility/src/weights.rs index 6ac23419e3ef7..eac94e44b8dbf 100644 --- a/frame/utility/src/weights.rs +++ b/frame/utility/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_utility //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/utility/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,39 +50,73 @@ pub trait WeightInfo { fn batch(c: u32, ) -> Weight; fn as_derivative() -> Weight; fn batch_all(c: u32, ) -> Weight; + fn dispatch_as() -> Weight; + fn force_batch(c: u32, ) -> Weight; } /// Weights for pallet_utility using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// The range of component `c` is `[0, 1000]`. fn batch(c: u32, ) -> Weight { - (30_319_000 as Weight) - // Standard Error: 3_000 - .saturating_add((6_759_000 as Weight).saturating_mul(c as Weight)) + // Minimum execution time: 14_470 nanoseconds. + Weight::from_ref_time(17_443_346 as u64) + // Standard Error: 2_037 + .saturating_add(Weight::from_ref_time(3_510_555 as u64).saturating_mul(c as u64)) } fn as_derivative() -> Weight { - (4_030_000 as Weight) + // Minimum execution time: 6_799 nanoseconds. + Weight::from_ref_time(6_976_000 as u64) } + /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32, ) -> Weight { - (26_621_000 as Weight) - // Standard Error: 3_000 - .saturating_add((7_251_000 as Weight).saturating_mul(c as Weight)) + // Minimum execution time: 14_630 nanoseconds. + Weight::from_ref_time(24_580_656 as u64) + // Standard Error: 2_202 + .saturating_add(Weight::from_ref_time(3_584_516 as u64).saturating_mul(c as u64)) + } + fn dispatch_as() -> Weight { + // Minimum execution time: 16_597 nanoseconds. + Weight::from_ref_time(16_950_000 as u64) + } + /// The range of component `c` is `[0, 1000]`. + fn force_batch(c: u32, ) -> Weight { + // Minimum execution time: 13_885 nanoseconds. + Weight::from_ref_time(20_147_978 as u64) + // Standard Error: 2_232 + .saturating_add(Weight::from_ref_time(3_516_969 as u64).saturating_mul(c as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { + /// The range of component `c` is `[0, 1000]`. fn batch(c: u32, ) -> Weight { - (30_319_000 as Weight) - // Standard Error: 3_000 - .saturating_add((6_759_000 as Weight).saturating_mul(c as Weight)) + // Minimum execution time: 14_470 nanoseconds. + Weight::from_ref_time(17_443_346 as u64) + // Standard Error: 2_037 + .saturating_add(Weight::from_ref_time(3_510_555 as u64).saturating_mul(c as u64)) } fn as_derivative() -> Weight { - (4_030_000 as Weight) + // Minimum execution time: 6_799 nanoseconds. + Weight::from_ref_time(6_976_000 as u64) } + /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32, ) -> Weight { - (26_621_000 as Weight) - // Standard Error: 3_000 - .saturating_add((7_251_000 as Weight).saturating_mul(c as Weight)) + // Minimum execution time: 14_630 nanoseconds. + Weight::from_ref_time(24_580_656 as u64) + // Standard Error: 2_202 + .saturating_add(Weight::from_ref_time(3_584_516 as u64).saturating_mul(c as u64)) + } + fn dispatch_as() -> Weight { + // Minimum execution time: 16_597 nanoseconds. + Weight::from_ref_time(16_950_000 as u64) + } + /// The range of component `c` is `[0, 1000]`. + fn force_batch(c: u32, ) -> Weight { + // Minimum execution time: 13_885 nanoseconds. + Weight::from_ref_time(20_147_978 as u64) + // Standard Error: 2_232 + .saturating_add(Weight::from_ref_time(3_516_969 as u64).saturating_mul(c as u64)) } } diff --git a/frame/vesting/Cargo.toml b/frame/vesting/Cargo.toml index 806e0e6036862..23fa06454b23c 100644 --- a/frame/vesting/Cargo.toml +++ b/frame/vesting/Cargo.toml @@ -2,9 +2,9 @@ name = "pallet-vesting" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for manage vesting" readme = "README.md" @@ -13,31 +13,33 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.0", default-features = false } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } -sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", + "log/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", ] -runtime-benchmarks = ["frame-benchmarking"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/vesting/README.md b/frame/vesting/README.md index c3800eb994d4d..b19a60c5b6824 100644 --- a/frame/vesting/README.md +++ b/frame/vesting/README.md @@ -7,7 +7,8 @@ A simple module providing a means of placing a linear curve on an account's locked balance. This module ensures that there is a lock in place preventing the balance to drop below the *unvested* -amount for any reason other than transaction fee payment. +amount for reason other than the ones specified in `UnvestedFundsAllowedWithdrawReasons` +configuration value. As the amount vested increases over time, the amount unvested reduces. However, locks remain in place and explicit action is needed on behalf of the user to ensure that the amount locked is diff --git a/frame/vesting/src/benchmarking.rs b/frame/vesting/src/benchmarking.rs index 5cdc14c8fdaca..dde5fe3ac7561 100644 --- a/frame/vesting/src/benchmarking.rs +++ b/frame/vesting/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ #![cfg(feature = "runtime-benchmarks")] -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; use frame_support::assert_ok; use frame_system::{Pallet as System, RawOrigin}; use sp_runtime::traits::{Bounded, CheckedDiv, CheckedMul}; @@ -42,7 +42,7 @@ fn add_locks(who: &T::AccountId, n: u8) { } fn add_vesting_schedules( - target: ::Source, + target: AccountIdLookupOf, n: u32, ) -> Result, &'static str> { let min_transfer = T::MinVestedTransfer::get(); @@ -52,7 +52,7 @@ fn add_vesting_schedules( let starting_block = 1u32; let source: T::AccountId = account("source", 0, SEED); - let source_lookup: ::Source = T::Lookup::unlookup(source.clone()); + let source_lookup = T::Lookup::unlookup(source.clone()); T::Currency::make_free_balance_be(&source, BalanceOf::::max_value()); System::::set_block_number(T::BlockNumber::zero()); @@ -72,7 +72,7 @@ fn add_vesting_schedules( T::Currency::make_free_balance_be(&source, BalanceOf::::max_value()); } - Ok(total_locked.into()) + Ok(total_locked) } benchmarks! { @@ -81,7 +81,7 @@ benchmarks! { let s in 1 .. T::MAX_VESTING_SCHEDULES; let caller: T::AccountId = whitelisted_caller(); - let caller_lookup: ::Source = T::Lookup::unlookup(caller.clone()); + let caller_lookup = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); add_locks::(&caller, l as u8); @@ -91,7 +91,7 @@ benchmarks! { assert_eq!(System::::block_number(), T::BlockNumber::zero()); assert_eq!( Vesting::::vesting_balance(&caller), - Some(expected_balance.into()), + Some(expected_balance), "Vesting schedule not added", ); }: vest(RawOrigin::Signed(caller.clone())) @@ -99,7 +99,7 @@ benchmarks! { // Nothing happened since everything is still vested. assert_eq!( Vesting::::vesting_balance(&caller), - Some(expected_balance.into()), + Some(expected_balance), "Vesting schedule was removed", ); } @@ -109,7 +109,7 @@ benchmarks! { let s in 1 .. T::MAX_VESTING_SCHEDULES; let caller: T::AccountId = whitelisted_caller(); - let caller_lookup: ::Source = T::Lookup::unlookup(caller.clone()); + let caller_lookup = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); add_locks::(&caller, l as u8); @@ -137,7 +137,7 @@ benchmarks! { let s in 1 .. T::MAX_VESTING_SCHEDULES; let other: T::AccountId = account("other", 0, SEED); - let other_lookup: ::Source = T::Lookup::unlookup(other.clone()); + let other_lookup = T::Lookup::unlookup(other.clone()); add_locks::(&other, l as u8); let expected_balance = add_vesting_schedules::(other_lookup.clone(), s)?; @@ -156,7 +156,7 @@ benchmarks! { // Nothing happened since everything is still vested. assert_eq!( Vesting::::vesting_balance(&other), - Some(expected_balance.into()), + Some(expected_balance), "Vesting schedule was removed", ); } @@ -166,7 +166,7 @@ benchmarks! { let s in 1 .. T::MAX_VESTING_SCHEDULES; let other: T::AccountId = account("other", 0, SEED); - let other_lookup: ::Source = T::Lookup::unlookup(other.clone()); + let other_lookup = T::Lookup::unlookup(other.clone()); add_locks::(&other, l as u8); add_vesting_schedules::(other_lookup.clone(), s)?; @@ -198,7 +198,7 @@ benchmarks! { T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let target: T::AccountId = account("target", 0, SEED); - let target_lookup: ::Source = T::Lookup::unlookup(target.clone()); + let target_lookup = T::Lookup::unlookup(target.clone()); // Give target existing locks add_locks::(&target, l as u8); // Add one vesting schedules. @@ -232,11 +232,11 @@ benchmarks! { let s in 0 .. T::MAX_VESTING_SCHEDULES - 1; let source: T::AccountId = account("source", 0, SEED); - let source_lookup: ::Source = T::Lookup::unlookup(source.clone()); + let source_lookup = T::Lookup::unlookup(source.clone()); T::Currency::make_free_balance_be(&source, BalanceOf::::max_value()); let target: T::AccountId = account("target", 0, SEED); - let target_lookup: ::Source = T::Lookup::unlookup(target.clone()); + let target_lookup = T::Lookup::unlookup(target.clone()); // Give target existing locks add_locks::(&target, l as u8); // Add one less than max vesting schedules @@ -260,7 +260,7 @@ benchmarks! { ); assert_eq!( Vesting::::vesting_balance(&target), - Some(expected_balance.into()), + Some(expected_balance), "Lock not correctly updated", ); } @@ -270,11 +270,11 @@ benchmarks! { let s in 2 .. T::MAX_VESTING_SCHEDULES; let caller: T::AccountId = account("caller", 0, SEED); - let caller_lookup: ::Source = T::Lookup::unlookup(caller.clone()); + let caller_lookup = T::Lookup::unlookup(caller.clone()); // Give target existing locks. add_locks::(&caller, l as u8); // Add max vesting schedules. - let expected_balance = add_vesting_schedules::(caller_lookup.clone(), s)?; + let expected_balance = add_vesting_schedules::(caller_lookup, s)?; // Schedules are not vesting at block 0. assert_eq!(System::::block_number(), T::BlockNumber::zero()); @@ -320,11 +320,11 @@ benchmarks! { let test_dest: T::AccountId = account("test_dest", 0, SEED); let caller: T::AccountId = account("caller", 0, SEED); - let caller_lookup: ::Source = T::Lookup::unlookup(caller.clone()); + let caller_lookup = T::Lookup::unlookup(caller.clone()); // Give target other locks. add_locks::(&caller, l as u8); // Add max vesting schedules. - let total_transferred = add_vesting_schedules::(caller_lookup.clone(), s)?; + let total_transferred = add_vesting_schedules::(caller_lookup, s)?; // Go to about half way through all the schedules duration. (They all start at 1, and have a duration of 20 or 21). System::::set_block_number(11u32.into()); @@ -374,10 +374,10 @@ benchmarks! { T::Currency::transfer(&caller, &test_dest, expected_balance, ExistenceRequirement::AllowDeath) ); } -} -impl_benchmark_test_suite!( - Vesting, - crate::mock::ExtBuilder::default().existential_deposit(256).build(), - crate::mock::Test, -); + impl_benchmark_test_suite!( + Vesting, + crate::mock::ExtBuilder::default().existential_deposit(256).build(), + crate::mock::Test, + ); +} diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index 27862a5ca4b72..3439608af3ce4 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,8 @@ //! //! A simple pallet providing a means of placing a linear curve on an account's locked balance. This //! pallet ensures that there is a lock in place preventing the balance to drop below the *unvested* -//! amount for any reason other than transaction fee payment. +//! amount for any reason other than the ones specified in `UnvestedFundsAllowedWithdrawReasons` +//! configuration value. //! //! As the amount vested increases over time, the amount unvested reduces. However, locks remain in //! place and explicit action is needed on behalf of the user to ensure that the amount locked is @@ -45,26 +46,27 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; -mod migrations; + #[cfg(test)] mod mock; #[cfg(test)] mod tests; mod vesting_info; +pub mod migrations; pub mod weights; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ + dispatch::{DispatchError, DispatchResult}, ensure, - pallet_prelude::*, + storage::bounded_vec::BoundedVec, traits::{ Currency, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, VestingSchedule, WithdrawReasons, }, + weights::Weight, }; -use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; -pub use pallet::*; use scale_info::TypeInfo; use sp_runtime::{ traits::{ @@ -73,7 +75,9 @@ use sp_runtime::{ }, RuntimeDebug, }; -use sp_std::{convert::TryInto, fmt::Debug, prelude::*}; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; + +pub use pallet::*; pub use vesting_info::*; pub use weights::WeightInfo; @@ -81,6 +85,7 @@ type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type MaxLocksOf = <::Currency as LockableCurrency<::AccountId>>::MaxLocks; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; const VESTING_ID: LockIdentifier = *b"vesting "; @@ -104,9 +109,9 @@ enum VestingAction { /// Do not actively remove any schedules. Passive, /// Remove the schedule specified by the index. - Remove(usize), + Remove { index: usize }, /// Remove the two schedules, specified by index, so they can be merged. - Merge(usize, usize), + Merge { index1: usize, index2: usize }, } impl VestingAction { @@ -114,16 +119,16 @@ impl VestingAction { fn should_remove(&self, index: usize) -> bool { match self { Self::Passive => false, - Self::Remove(index1) => *index1 == index, - Self::Merge(index1, index2) => *index1 == index || *index2 == index, + Self::Remove { index: index1 } => *index1 == index, + Self::Merge { index1, index2 } => *index1 == index || *index2 == index, } } /// Pick the schedules that this action dictates should continue vesting undisturbed. - fn pick_schedules<'a, T: Config>( - &'a self, + fn pick_schedules( + &self, schedules: Vec, T::BlockNumber>>, - ) -> impl Iterator, T::BlockNumber>> + 'a { + ) -> impl Iterator, T::BlockNumber>> + '_ { schedules.into_iter().enumerate().filter_map(move |(index, schedule)| { if self.should_remove(index) { None @@ -145,11 +150,13 @@ impl Get for MaxVestingSchedulesGet { #[frame_support::pallet] pub mod pallet { use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The currency trait. type Currency: LockableCurrency; @@ -164,40 +171,24 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + /// Reasons that determine under which conditions the balance may drop below + /// the unvested amount. + type UnvestedFundsAllowedWithdrawReasons: Get; + /// Maximum number of vesting schedules an account may have at a given moment. const MAX_VESTING_SCHEDULES: u32; } #[pallet::extra_constants] impl Pallet { - // TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed. - #[allow(non_snake_case)] - fn MaxVestingSchedules() -> u32 { + #[pallet::constant_name(MaxVestingSchedules)] + fn max_vesting_schedules() -> u32 { T::MAX_VESTING_SCHEDULES } } #[pallet::hooks] impl Hooks> for Pallet { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { - migrations::v1::pre_migrate::() - } - - fn on_runtime_upgrade() -> Weight { - if StorageVersion::::get() == Releases::V0 { - StorageVersion::::put(Releases::V1); - migrations::v1::migrate::().saturating_add(T::DbWeight::get().reads_writes(1, 1)) - } else { - T::DbWeight::get().reads(1) - } - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade() -> Result<(), &'static str> { - migrations::v1::post_migrate::() - } - fn integrity_test() { assert!(T::MAX_VESTING_SCHEDULES > 0, "`MaxVestingSchedules` must ge greater than 0"); } @@ -221,7 +212,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::generate_storage_info] pub struct Pallet(_); #[pallet::genesis_config] @@ -264,7 +254,9 @@ pub mod pallet { Vesting::::try_append(who, vesting_info) .expect("Too many vesting schedules at genesis."); - let reasons = WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE; + let reasons = + WithdrawReasons::except(T::UnvestedFundsAllowedWithdrawReasons::get()); + T::Currency::set_lock(VESTING_ID, who, locked, reasons); } } @@ -275,10 +267,9 @@ pub mod pallet { pub enum Event { /// The amount vested has been updated. This could indicate a change in funds available. /// The balance given is the amount which is left unvested (and thus locked). - /// \[account, unvested\] - VestingUpdated(T::AccountId, BalanceOf), + VestingUpdated { account: T::AccountId, unvested: BalanceOf }, /// An \[account\] has become fully vested. - VestingCompleted(T::AccountId), + VestingCompleted { account: T::AccountId }, } /// Error for the vesting pallet. @@ -312,6 +303,7 @@ pub mod pallet { /// - Reads: Vesting Storage, Balances Locks, [Sender Account] /// - Writes: Vesting Storage, Balances Locks, [Sender Account] /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::vest_locked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) .max(T::WeightInfo::vest_unlocked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES)) )] @@ -335,13 +327,11 @@ pub mod pallet { /// - Reads: Vesting Storage, Balances Locks, Target Account /// - Writes: Vesting Storage, Balances Locks, Target Account /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::vest_other_locked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) .max(T::WeightInfo::vest_other_unlocked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES)) )] - pub fn vest_other( - origin: OriginFor, - target: ::Source, - ) -> DispatchResult { + pub fn vest_other(origin: OriginFor, target: AccountIdLookupOf) -> DispatchResult { ensure_signed(origin)?; let who = T::Lookup::lookup(target)?; Self::do_vest(who) @@ -364,12 +354,13 @@ pub mod pallet { /// - Reads: Vesting Storage, Balances Locks, Target Account, [Sender Account] /// - Writes: Vesting Storage, Balances Locks, Target Account, [Sender Account] /// # + #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::vested_transfer(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) )] pub fn vested_transfer( origin: OriginFor, - target: ::Source, + target: AccountIdLookupOf, schedule: VestingInfo, T::BlockNumber>, ) -> DispatchResult { let transactor = ensure_signed(origin)?; @@ -395,13 +386,14 @@ pub mod pallet { /// - Reads: Vesting Storage, Balances Locks, Target Account, Source Account /// - Writes: Vesting Storage, Balances Locks, Target Account, Source Account /// # + #[pallet::call_index(3)] #[pallet::weight( T::WeightInfo::force_vested_transfer(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) )] pub fn force_vested_transfer( origin: OriginFor, - source: ::Source, - target: ::Source, + source: AccountIdLookupOf, + target: AccountIdLookupOf, schedule: VestingInfo, T::BlockNumber>, ) -> DispatchResult { ensure_root(origin)?; @@ -429,6 +421,7 @@ pub mod pallet { /// /// - `schedule1_index`: index of the first schedule to merge. /// - `schedule2_index`: index of the second schedule to merge. + #[pallet::call_index(4)] #[pallet::weight( T::WeightInfo::not_unlocking_merge_schedules(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) .max(T::WeightInfo::unlocking_merge_schedules(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES)) @@ -446,7 +439,8 @@ pub mod pallet { let schedule2_index = schedule2_index as usize; let schedules = Self::vesting(&who).ok_or(Error::::NotVesting)?; - let merge_action = VestingAction::Merge(schedule1_index, schedule2_index); + let merge_action = + VestingAction::Merge { index1: schedule1_index, index2: schedule2_index }; let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), merge_action)?; @@ -510,8 +504,8 @@ impl Pallet { // Execute a vested transfer from `source` to `target` with the given `schedule`. fn do_vested_transfer( - source: ::Source, - target: ::Source, + source: AccountIdLookupOf, + target: AccountIdLookupOf, schedule: VestingInfo, T::BlockNumber>, ) -> DispatchResult { // Validate user inputs. @@ -568,14 +562,13 @@ impl Pallet { let mut total_locked_now: BalanceOf = Zero::zero(); let filtered_schedules = action .pick_schedules::(schedules) - .filter_map(|schedule| { + .filter(|schedule| { let locked_now = schedule.locked_at::(now); - if locked_now.is_zero() { - None - } else { + let keep = !locked_now.is_zero(); + if keep { total_locked_now = total_locked_now.saturating_add(locked_now); - Some(schedule) } + keep }) .collect::>(); @@ -586,11 +579,14 @@ impl Pallet { fn write_lock(who: &T::AccountId, total_locked_now: BalanceOf) { if total_locked_now.is_zero() { T::Currency::remove_lock(VESTING_ID, who); - Self::deposit_event(Event::::VestingCompleted(who.clone())); + Self::deposit_event(Event::::VestingCompleted { account: who.clone() }); } else { - let reasons = WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE; + let reasons = WithdrawReasons::except(T::UnvestedFundsAllowedWithdrawReasons::get()); T::Currency::set_lock(VESTING_ID, who, total_locked_now, reasons); - Self::deposit_event(Event::::VestingUpdated(who.clone(), total_locked_now)); + Self::deposit_event(Event::::VestingUpdated { + account: who.clone(), + unvested: total_locked_now, + }); }; } @@ -633,7 +629,7 @@ impl Pallet { action: VestingAction, ) -> Result<(Vec, T::BlockNumber>>, BalanceOf), DispatchError> { let (schedules, locked_now) = match action { - VestingAction::Merge(idx1, idx2) => { + VestingAction::Merge { index1: idx1, index2: idx2 } => { // The schedule index is based off of the schedule ordering prior to filtering out // any schedules that may be ending at this block. let schedule1 = *schedules.get(idx1).ok_or(Error::::ScheduleIndexOutOfBounds)?; @@ -728,7 +724,7 @@ where let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), VestingAction::Passive)?; - Self::write_vesting(&who, schedules)?; + Self::write_vesting(who, schedules)?; Self::write_lock(who, locked_now); Ok(()) @@ -758,11 +754,11 @@ where /// Remove a vesting schedule for a given account. fn remove_vesting_schedule(who: &T::AccountId, schedule_index: u32) -> DispatchResult { let schedules = Self::vesting(who).ok_or(Error::::NotVesting)?; - let remove_action = VestingAction::Remove(schedule_index as usize); + let remove_action = VestingAction::Remove { index: schedule_index as usize }; let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), remove_action)?; - Self::write_vesting(&who, schedules)?; + Self::write_vesting(who, schedules)?; Self::write_lock(who, locked_now); Ok(()) } diff --git a/frame/vesting/src/migrations.rs b/frame/vesting/src/migrations.rs index 086257d285ea0..15668425b4b20 100644 --- a/frame/vesting/src/migrations.rs +++ b/frame/vesting/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,11 +20,11 @@ use super::*; // Migration from single schedule to multiple schedules. -pub(crate) mod v1 { +pub mod v1 { use super::*; #[cfg(feature = "try-runtime")] - pub(crate) fn pre_migrate() -> Result<(), &'static str> { + pub fn pre_migrate() -> Result<(), &'static str> { assert!(StorageVersion::::get() == Releases::V0, "Storage version too high."); log::debug!( @@ -37,7 +37,7 @@ pub(crate) mod v1 { /// Migrate from single schedule to multi schedule storage. /// WARNING: This migration will delete schedules if `MaxVestingSchedules < 1`. - pub(crate) fn migrate() -> Weight { + pub fn migrate() -> Weight { let mut reads_writes = 0; Vesting::::translate::, T::BlockNumber>, _>( @@ -65,12 +65,12 @@ pub(crate) mod v1 { } #[cfg(feature = "try-runtime")] - pub(crate) fn post_migrate() -> Result<(), &'static str> { + pub fn post_migrate() -> Result<(), &'static str> { assert_eq!(StorageVersion::::get(), Releases::V1); for (_key, schedules) in Vesting::::iter() { assert!( - schedules.len() == 1, + schedules.len() >= 1, "A bounded vec with incorrect count of items was created." ); diff --git a/frame/vesting/src/mock.rs b/frame/vesting/src/mock.rs index cb8961150003b..0bd371a0353f1 100644 --- a/frame/vesting/src/mock.rs +++ b/frame/vesting/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use frame_support::parameter_types; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64, GenesisBuild, WithdrawReasons}, +}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -41,21 +44,20 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { type AccountData = pallet_balances::AccountData; type AccountId = u64; type BaseCallFilter = frame_support::traits::Everything; - type BlockHashCount = BlockHashCount; + type BlockHashCount = ConstU64<250>; type BlockLength = (); type BlockNumber = u64; type BlockWeights = (); - type Call = Call; + type RuntimeCall = RuntimeCall; type DbWeight = (); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Hash = H256; type Hashing = BlakeTwo256; type Header = Header; @@ -64,37 +66,39 @@ impl frame_system::Config for Test { type OnKilledAccount = (); type OnNewAccount = (); type OnSetCode = (); - type Origin = Origin; + type MaxConsumers = frame_support::traits::ConstU32<16>; + type RuntimeOrigin = RuntimeOrigin; type PalletInfo = PalletInfo; type SS58Prefix = (); type SystemWeightInfo = (); type Version = (); } -parameter_types! { - pub const MaxLocks: u32 = 10; -} + impl pallet_balances::Config for Test { type AccountStore = System; type Balance = u64; type DustRemoval = (); - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; - type MaxLocks = MaxLocks; + type MaxLocks = ConstU32<10>; type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } parameter_types! { pub const MinVestedTransfer: u64 = 256 * 2; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); pub static ExistentialDeposit: u64 = 0; } impl Config for Test { type BlockNumberToBalance = Identity; type Currency = Balances; - type Event = Event; + type RuntimeEvent = RuntimeEvent; const MAX_VESTING_SCHEDULES: u32 = 3; type MinVestedTransfer = MinVestedTransfer; type WeightInfo = (); + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; } pub struct ExtBuilder { diff --git a/frame/vesting/src/tests.rs b/frame/vesting/src/tests.rs index 2a6dd0520c3b0..cbc2e09c83199 100644 --- a/frame/vesting/src/tests.rs +++ b/frame/vesting/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/vesting/src/vesting_info.rs b/frame/vesting/src/vesting_info.rs index 81bffa199fd72..9069b69482769 100644 --- a/frame/vesting/src/vesting_info.rs +++ b/frame/vesting/src/vesting_info.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/vesting/src/weights.rs b/frame/vesting/src/weights.rs index 3ccc1a5bda362..5462445414719 100644 --- a/frame/vesting/src/weights.rs +++ b/frame/vesting/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 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 +// 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, @@ -18,12 +18,14 @@ //! Autogenerated weights for pallet_vesting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-08-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +35,9 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/vesting/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -60,97 +62,121 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_locked(l: u32, s: u32, ) -> Weight { - (50_642_000 as Weight) - // Standard Error: 1_000 - .saturating_add((144_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 3_000 - .saturating_add((177_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 45_113 nanoseconds. + Weight::from_ref_time(44_114_539 as u64) + // Standard Error: 958 + .saturating_add(Weight::from_ref_time(56_239 as u64).saturating_mul(l as u64)) + // Standard Error: 1_704 + .saturating_add(Weight::from_ref_time(64_926 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_unlocked(l: u32, s: u32, ) -> Weight { - (50_830_000 as Weight) - // Standard Error: 1_000 - .saturating_add((115_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 3_000 - .saturating_add((112_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + // Minimum execution time: 43_918 nanoseconds. + Weight::from_ref_time(43_452_573 as u64) + // Standard Error: 984 + .saturating_add(Weight::from_ref_time(50_162 as u64).saturating_mul(l as u64)) + // Standard Error: 1_752 + .saturating_add(Weight::from_ref_time(42_080 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_other_locked(l: u32, s: u32, ) -> Weight { - (52_151_000 as Weight) - // Standard Error: 1_000 - .saturating_add((130_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 3_000 - .saturating_add((162_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 43_603 nanoseconds. + Weight::from_ref_time(42_696_097 as u64) + // Standard Error: 996 + .saturating_add(Weight::from_ref_time(65_316 as u64).saturating_mul(l as u64)) + // Standard Error: 1_772 + .saturating_add(Weight::from_ref_time(65_862 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_other_unlocked(l: u32, s: u32, ) -> Weight { - (51_009_000 as Weight) - // Standard Error: 4_000 - .saturating_add((123_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 9_000 - .saturating_add((118_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 43_099 nanoseconds. + Weight::from_ref_time(42_937_914 as u64) + // Standard Error: 884 + .saturating_add(Weight::from_ref_time(52_079 as u64).saturating_mul(l as u64)) + // Standard Error: 1_573 + .saturating_add(Weight::from_ref_time(36_274 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[0, 27]`. fn vested_transfer(l: u32, s: u32, ) -> Weight { - (89_517_000 as Weight) - // Standard Error: 5_000 - .saturating_add((114_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 10_000 - .saturating_add((23_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 59_023 nanoseconds. + Weight::from_ref_time(59_606_862 as u64) + // Standard Error: 2_078 + .saturating_add(Weight::from_ref_time(55_335 as u64).saturating_mul(l as u64)) + // Standard Error: 3_698 + .saturating_add(Weight::from_ref_time(26_743 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[0, 27]`. fn force_vested_transfer(l: u32, s: u32, ) -> Weight { - (87_903_000 as Weight) - // Standard Error: 6_000 - .saturating_add((121_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 12_000 - .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Minimum execution time: 58_249 nanoseconds. + Weight::from_ref_time(59_025_976 as u64) + // Standard Error: 2_078 + .saturating_add(Weight::from_ref_time(55_736 as u64).saturating_mul(l as u64)) + // Standard Error: 3_697 + .saturating_add(Weight::from_ref_time(24_903 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[2, 28]`. fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (54_463_000 as Weight) - // Standard Error: 2_000 - .saturating_add((123_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 5_000 - .saturating_add((149_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 45_279 nanoseconds. + Weight::from_ref_time(44_197_440 as u64) + // Standard Error: 946 + .saturating_add(Weight::from_ref_time(62_308 as u64).saturating_mul(l as u64)) + // Standard Error: 1_747 + .saturating_add(Weight::from_ref_time(64_473 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[2, 28]`. fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (53_674_000 as Weight) - // Standard Error: 1_000 - .saturating_add((137_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 4_000 - .saturating_add((152_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Minimum execution time: 44_925 nanoseconds. + Weight::from_ref_time(44_219_676 as u64) + // Standard Error: 889 + .saturating_add(Weight::from_ref_time(60_311 as u64).saturating_mul(l as u64)) + // Standard Error: 1_641 + .saturating_add(Weight::from_ref_time(63_095 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } } @@ -158,96 +184,120 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_locked(l: u32, s: u32, ) -> Weight { - (50_642_000 as Weight) - // Standard Error: 1_000 - .saturating_add((144_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 3_000 - .saturating_add((177_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 45_113 nanoseconds. + Weight::from_ref_time(44_114_539 as u64) + // Standard Error: 958 + .saturating_add(Weight::from_ref_time(56_239 as u64).saturating_mul(l as u64)) + // Standard Error: 1_704 + .saturating_add(Weight::from_ref_time(64_926 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_unlocked(l: u32, s: u32, ) -> Weight { - (50_830_000 as Weight) - // Standard Error: 1_000 - .saturating_add((115_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 3_000 - .saturating_add((112_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + // Minimum execution time: 43_918 nanoseconds. + Weight::from_ref_time(43_452_573 as u64) + // Standard Error: 984 + .saturating_add(Weight::from_ref_time(50_162 as u64).saturating_mul(l as u64)) + // Standard Error: 1_752 + .saturating_add(Weight::from_ref_time(42_080 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_other_locked(l: u32, s: u32, ) -> Weight { - (52_151_000 as Weight) - // Standard Error: 1_000 - .saturating_add((130_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 3_000 - .saturating_add((162_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 43_603 nanoseconds. + Weight::from_ref_time(42_696_097 as u64) + // Standard Error: 996 + .saturating_add(Weight::from_ref_time(65_316 as u64).saturating_mul(l as u64)) + // Standard Error: 1_772 + .saturating_add(Weight::from_ref_time(65_862 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_other_unlocked(l: u32, s: u32, ) -> Weight { - (51_009_000 as Weight) - // Standard Error: 4_000 - .saturating_add((123_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 9_000 - .saturating_add((118_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 43_099 nanoseconds. + Weight::from_ref_time(42_937_914 as u64) + // Standard Error: 884 + .saturating_add(Weight::from_ref_time(52_079 as u64).saturating_mul(l as u64)) + // Standard Error: 1_573 + .saturating_add(Weight::from_ref_time(36_274 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[0, 27]`. fn vested_transfer(l: u32, s: u32, ) -> Weight { - (89_517_000 as Weight) - // Standard Error: 5_000 - .saturating_add((114_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 10_000 - .saturating_add((23_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 59_023 nanoseconds. + Weight::from_ref_time(59_606_862 as u64) + // Standard Error: 2_078 + .saturating_add(Weight::from_ref_time(55_335 as u64).saturating_mul(l as u64)) + // Standard Error: 3_698 + .saturating_add(Weight::from_ref_time(26_743 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[0, 27]`. fn force_vested_transfer(l: u32, s: u32, ) -> Weight { - (87_903_000 as Weight) - // Standard Error: 6_000 - .saturating_add((121_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 12_000 - .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + // Minimum execution time: 58_249 nanoseconds. + Weight::from_ref_time(59_025_976 as u64) + // Standard Error: 2_078 + .saturating_add(Weight::from_ref_time(55_736 as u64).saturating_mul(l as u64)) + // Standard Error: 3_697 + .saturating_add(Weight::from_ref_time(24_903 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[2, 28]`. fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (54_463_000 as Weight) - // Standard Error: 2_000 - .saturating_add((123_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 5_000 - .saturating_add((149_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 45_279 nanoseconds. + Weight::from_ref_time(44_197_440 as u64) + // Standard Error: 946 + .saturating_add(Weight::from_ref_time(62_308 as u64).saturating_mul(l as u64)) + // Standard Error: 1_747 + .saturating_add(Weight::from_ref_time(64_473 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[2, 28]`. fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (53_674_000 as Weight) - // Standard Error: 1_000 - .saturating_add((137_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 4_000 - .saturating_add((152_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + // Minimum execution time: 44_925 nanoseconds. + Weight::from_ref_time(44_219_676 as u64) + // Standard Error: 889 + .saturating_add(Weight::from_ref_time(60_311 as u64).saturating_mul(l as u64)) + // Standard Error: 1_641 + .saturating_add(Weight::from_ref_time(63_095 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } } diff --git a/frame/whitelist/Cargo.toml b/frame/whitelist/Cargo.toml new file mode 100644 index 0000000000000..f3f44d3187a8e --- /dev/null +++ b/frame/whitelist/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "pallet-whitelist" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet for whitelisting call, and dispatch from specific origin" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } + +[features] +default = ["std"] +std = [ + "frame-benchmarking?/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-api/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/whitelist/src/benchmarking.rs b/frame/whitelist/src/benchmarking.rs new file mode 100644 index 0000000000000..923adc6ccf8ca --- /dev/null +++ b/frame/whitelist/src/benchmarking.rs @@ -0,0 +1,118 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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. + +//! Whitelist pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::benchmarks; +use frame_support::{ensure, traits::EnsureOrigin}; + +#[cfg(test)] +use crate::Pallet as Whitelist; + +benchmarks! { + whitelist_call { + let origin = T::WhitelistOrigin::successful_origin(); + let call_hash = Default::default(); + }: _(origin, call_hash) + verify { + ensure!( + WhitelistedCall::::contains_key(call_hash), + "call not whitelisted" + ); + ensure!( + T::Preimages::is_requested(&call_hash), + "preimage not requested" + ); + } + + remove_whitelisted_call { + let origin = T::WhitelistOrigin::successful_origin(); + let call_hash = Default::default(); + Pallet::::whitelist_call(origin.clone(), call_hash) + .expect("whitelisting call must be successful"); + }: _(origin, call_hash) + verify { + ensure!( + !WhitelistedCall::::contains_key(call_hash), + "whitelist not removed" + ); + ensure!( + !T::Preimages::is_requested(&call_hash), + "preimage still requested" + ); + } + + // We benchmark with the maximum possible size for a call. + // If the resulting weight is too big, maybe it worth having a weight which depends + // on the size of the call, with a new witness in parameter. + dispatch_whitelisted_call { + // NOTE: we remove `10` because we need some bytes to encode the variants and vec length + let n in 1 .. T::Preimages::MAX_LENGTH as u32 - 10; + + let origin = T::DispatchWhitelistedOrigin::successful_origin(); + let remark = sp_std::vec![1u8; n as usize]; + let call: ::RuntimeCall = frame_system::Call::remark { remark }.into(); + let call_weight = call.get_dispatch_info().weight; + let encoded_call = call.encode(); + let call_encoded_len = encoded_call.len() as u32; + let call_hash = call.blake2_256().into(); + + Pallet::::whitelist_call(origin.clone(), call_hash) + .expect("whitelisting call must be successful"); + + T::Preimages::note(encoded_call.into()).unwrap(); + + }: _(origin, call_hash, call_encoded_len, call_weight) + verify { + ensure!( + !WhitelistedCall::::contains_key(call_hash), + "whitelist not removed" + ); + ensure!( + !T::Preimages::is_requested(&call_hash), + "preimage still requested" + ); + } + + dispatch_whitelisted_call_with_preimage { + let n in 1 .. 10_000; + + let origin = T::DispatchWhitelistedOrigin::successful_origin(); + let remark = sp_std::vec![1u8; n as usize]; + + let call: ::RuntimeCall = frame_system::Call::remark { remark }.into(); + let call_hash = call.blake2_256().into(); + + Pallet::::whitelist_call(origin.clone(), call_hash) + .expect("whitelisting call must be successful"); + }: _(origin, Box::new(call)) + verify { + ensure!( + !WhitelistedCall::::contains_key(call_hash), + "whitelist not removed" + ); + ensure!( + !T::Preimages::is_requested(&call_hash), + "preimage still requested" + ); + } + + impl_benchmark_test_suite!(Whitelist, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/whitelist/src/lib.rs b/frame/whitelist/src/lib.rs new file mode 100644 index 0000000000000..8a5666331c7e9 --- /dev/null +++ b/frame/whitelist/src/lib.rs @@ -0,0 +1,250 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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. + +//! # Whitelist Pallet +//! +//! - [`Config`] +//! - [`Call`] +//! +//! ## Overview +//! +//! Allow some configurable origin: [`Config::WhitelistOrigin`] to whitelist some hash of a call, +//! and allow another configurable origin: [`Config::DispatchWhitelistedOrigin`] to dispatch them +//! with the root origin. +//! +//! In the meantime the call corresponding to the hash must have been submitted to the pre-image +//! handler [`pallet::Config::Preimages`]. + +#![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 weights::WeightInfo; + +use codec::{DecodeLimit, Encode, FullCodec}; +use frame_support::{ + dispatch::{GetDispatchInfo, PostDispatchInfo}, + ensure, + traits::{Hash as PreimageHash, QueryPreimage, StorePreimage}, + weights::Weight, + Hashable, +}; +use scale_info::TypeInfo; +use sp_runtime::traits::Dispatchable; +use sp_std::prelude::*; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The overarching call type. + type RuntimeCall: IsType<::RuntimeCall> + + Dispatchable + + GetDispatchInfo + + FullCodec + + TypeInfo + + From> + + Parameter; + + /// Required origin for whitelisting a call. + type WhitelistOrigin: EnsureOrigin; + + /// Required origin for dispatching whitelisted call with root origin. + type DispatchWhitelistedOrigin: EnsureOrigin; + + /// The handler of pre-images. + type Preimages: QueryPreimage + StorePreimage; + + /// The weight information for this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + CallWhitelisted { call_hash: PreimageHash }, + WhitelistedCallRemoved { call_hash: PreimageHash }, + WhitelistedCallDispatched { call_hash: PreimageHash, result: DispatchResultWithPostInfo }, + } + + #[pallet::error] + pub enum Error { + /// The preimage of the call hash could not be loaded. + UnavailablePreImage, + /// The call could not be decoded. + UndecodableCall, + /// The weight of the decoded call was higher than the witness. + InvalidCallWeightWitness, + /// The call was not whitelisted. + CallIsNotWhitelisted, + /// The call was already whitelisted; No-Op. + CallAlreadyWhitelisted, + } + + #[pallet::storage] + pub type WhitelistedCall = + StorageMap<_, Twox64Concat, PreimageHash, (), OptionQuery>; + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::whitelist_call())] + pub fn whitelist_call(origin: OriginFor, call_hash: PreimageHash) -> DispatchResult { + T::WhitelistOrigin::ensure_origin(origin)?; + + ensure!( + !WhitelistedCall::::contains_key(call_hash), + Error::::CallAlreadyWhitelisted, + ); + + WhitelistedCall::::insert(call_hash, ()); + T::Preimages::request(&call_hash); + + Self::deposit_event(Event::::CallWhitelisted { call_hash }); + + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::remove_whitelisted_call())] + pub fn remove_whitelisted_call( + origin: OriginFor, + call_hash: PreimageHash, + ) -> DispatchResult { + T::WhitelistOrigin::ensure_origin(origin)?; + + WhitelistedCall::::take(call_hash).ok_or(Error::::CallIsNotWhitelisted)?; + + T::Preimages::unrequest(&call_hash); + + Self::deposit_event(Event::::WhitelistedCallRemoved { call_hash }); + + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight( + T::WeightInfo::dispatch_whitelisted_call(*call_encoded_len) + .saturating_add(*call_weight_witness) + )] + pub fn dispatch_whitelisted_call( + origin: OriginFor, + call_hash: PreimageHash, + call_encoded_len: u32, + call_weight_witness: Weight, + ) -> DispatchResultWithPostInfo { + T::DispatchWhitelistedOrigin::ensure_origin(origin)?; + + ensure!( + WhitelistedCall::::contains_key(call_hash), + Error::::CallIsNotWhitelisted, + ); + + let call = T::Preimages::fetch(&call_hash, Some(call_encoded_len)) + .map_err(|_| Error::::UnavailablePreImage)?; + + let call = ::RuntimeCall::decode_all_with_depth_limit( + sp_api::MAX_EXTRINSIC_DEPTH, + &mut &call[..], + ) + .map_err(|_| Error::::UndecodableCall)?; + + ensure!( + call.get_dispatch_info().weight.all_lte(call_weight_witness), + Error::::InvalidCallWeightWitness + ); + + let actual_weight = Self::clean_and_dispatch(call_hash, call).map(|w| { + w.saturating_add(T::WeightInfo::dispatch_whitelisted_call(call_encoded_len)) + }); + + Ok(actual_weight.into()) + } + + #[pallet::call_index(3)] + #[pallet::weight({ + let call_weight = call.get_dispatch_info().weight; + let call_len = call.encoded_size() as u32; + + T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len) + .saturating_add(call_weight) + })] + pub fn dispatch_whitelisted_call_with_preimage( + origin: OriginFor, + call: Box<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + T::DispatchWhitelistedOrigin::ensure_origin(origin)?; + + let call_hash = call.blake2_256().into(); + + ensure!( + WhitelistedCall::::contains_key(call_hash), + Error::::CallIsNotWhitelisted, + ); + + let call_len = call.encoded_size() as u32; + let actual_weight = Self::clean_and_dispatch(call_hash, *call).map(|w| { + w.saturating_add(T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len)) + }); + + Ok(actual_weight.into()) + } + } +} + +impl Pallet { + /// Clean whitelisting/preimage and dispatch call. + /// + /// Return the call actual weight of the dispatched call if there is some. + fn clean_and_dispatch( + call_hash: PreimageHash, + call: ::RuntimeCall, + ) -> Option { + WhitelistedCall::::remove(call_hash); + + T::Preimages::unrequest(&call_hash); + + let result = call.dispatch(frame_system::Origin::::Root.into()); + + let call_actual_weight = match result { + Ok(call_post_info) => call_post_info.actual_weight, + Err(call_err) => call_err.post_info.actual_weight, + }; + + Self::deposit_event(Event::::WhitelistedCallDispatched { call_hash, result }); + + call_actual_weight + } +} diff --git a/frame/whitelist/src/mock.rs b/frame/whitelist/src/mock.rs new file mode 100644 index 0000000000000..ef5d3d4fb4d34 --- /dev/null +++ b/frame/whitelist/src/mock.rs @@ -0,0 +1,118 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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. + +// Mock for Whitelist Pallet + +#![cfg(test)] + +use crate as pallet_whitelist; + +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU32, ConstU64, Nothing}, +}; +use frame_system::EnsureRoot; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Whitelist: pallet_whitelist, + Preimage: pallet_preimage, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = Nothing; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); +} + +impl pallet_preimage::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type ManagerOrigin = EnsureRoot; + type BaseDeposit = ConstU64<1>; + type ByteDeposit = ConstU64<1>; + type WeightInfo = (); +} + +impl pallet_whitelist::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WhitelistOrigin = EnsureRoot; + type DispatchWhitelistedOrigin = EnsureRoot; + type Preimages = Preimage; + type WeightInfo = (); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = GenesisConfig::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/frame/whitelist/src/tests.rs b/frame/whitelist/src/tests.rs new file mode 100644 index 0000000000000..d04bb48c1ec40 --- /dev/null +++ b/frame/whitelist/src/tests.rs @@ -0,0 +1,225 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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. + +// Tests for Whitelist Pallet + +use crate::mock::*; +use codec::Encode; +use frame_support::{ + assert_noop, assert_ok, + dispatch::GetDispatchInfo, + traits::{QueryPreimage, StorePreimage}, + weights::Weight, +}; +use sp_runtime::{traits::Hash, DispatchError}; + +#[test] +fn test_whitelist_call_and_remove() { + new_test_ext().execute_with(|| { + let call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); + let encoded_call = call.encode(); + let call_hash = ::Hashing::hash(&encoded_call[..]); + + assert_noop!( + Whitelist::remove_whitelisted_call(RuntimeOrigin::root(), call_hash), + crate::Error::::CallIsNotWhitelisted, + ); + + assert_noop!( + Whitelist::whitelist_call(RuntimeOrigin::signed(1), call_hash), + DispatchError::BadOrigin, + ); + + assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); + + assert!(Preimage::is_requested(&call_hash)); + + assert_noop!( + Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash), + crate::Error::::CallAlreadyWhitelisted, + ); + + assert_noop!( + Whitelist::remove_whitelisted_call(RuntimeOrigin::signed(1), call_hash), + DispatchError::BadOrigin, + ); + + assert_ok!(Whitelist::remove_whitelisted_call(RuntimeOrigin::root(), call_hash)); + + assert!(!Preimage::is_requested(&call_hash)); + + assert_noop!( + Whitelist::remove_whitelisted_call(RuntimeOrigin::root(), call_hash), + crate::Error::::CallIsNotWhitelisted, + ); + }); +} + +#[test] +fn test_whitelist_call_and_execute() { + new_test_ext().execute_with(|| { + let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] }); + let call_weight = call.get_dispatch_info().weight; + let encoded_call = call.encode(); + let call_encoded_len = encoded_call.len() as u32; + let call_hash = ::Hashing::hash(&encoded_call[..]); + + assert_noop!( + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), + crate::Error::::CallIsNotWhitelisted, + ); + + assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); + + assert_noop!( + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::signed(1), + call_hash, + call_encoded_len, + call_weight + ), + DispatchError::BadOrigin, + ); + + assert_noop!( + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), + crate::Error::::UnavailablePreImage, + ); + + assert_ok!(Preimage::note(encoded_call.into())); + + assert!(Preimage::is_requested(&call_hash)); + + assert_noop!( + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight - Weight::from_ref_time(1) + ), + crate::Error::::InvalidCallWeightWitness, + ); + + assert_ok!(Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + )); + + assert!(!Preimage::is_requested(&call_hash)); + + assert_noop!( + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), + crate::Error::::CallIsNotWhitelisted, + ); + }); +} + +#[test] +fn test_whitelist_call_and_execute_failing_call() { + new_test_ext().execute_with(|| { + let call = RuntimeCall::Whitelist(crate::Call::dispatch_whitelisted_call { + call_hash: Default::default(), + call_encoded_len: Default::default(), + call_weight_witness: Weight::zero(), + }); + let call_weight = call.get_dispatch_info().weight; + let encoded_call = call.encode(); + let call_encoded_len = encoded_call.len() as u32; + let call_hash = ::Hashing::hash(&encoded_call[..]); + + assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); + assert_ok!(Preimage::note(encoded_call.into())); + assert!(Preimage::is_requested(&call_hash)); + assert_ok!(Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + )); + assert!(!Preimage::is_requested(&call_hash)); + }); +} + +#[test] +fn test_whitelist_call_and_execute_without_note_preimage() { + new_test_ext().execute_with(|| { + let call = Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { + remark: vec![1], + })); + let call_hash = ::Hashing::hash_of(&call); + + assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); + assert!(Preimage::is_requested(&call_hash)); + + assert_ok!(Whitelist::dispatch_whitelisted_call_with_preimage( + RuntimeOrigin::root(), + call.clone() + )); + + assert!(!Preimage::is_requested(&call_hash)); + + assert_noop!( + Whitelist::dispatch_whitelisted_call_with_preimage(RuntimeOrigin::root(), call), + crate::Error::::CallIsNotWhitelisted, + ); + }); +} + +#[test] +fn test_whitelist_call_and_execute_decode_consumes_all() { + new_test_ext().execute_with(|| { + let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] }); + let call_weight = call.get_dispatch_info().weight; + let mut call = call.encode(); + // Appending something does not make the encoded call invalid. + // This tests that the decode function consumes all data. + call.extend(call.clone()); + let call_encoded_len = call.len() as u32; + + let call_hash = ::Hashing::hash(&call[..]); + + assert_ok!(Preimage::note(call.into())); + assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); + + assert_noop!( + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), + crate::Error::::UndecodableCall, + ); + }); +} diff --git a/frame/whitelist/src/weights.rs b/frame/whitelist/src/weights.rs new file mode 100644 index 0000000000000..efd48d657826b --- /dev/null +++ b/frame/whitelist/src/weights.rs @@ -0,0 +1,142 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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_whitelist +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-12-05, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/substrate +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_whitelist +// --chain=dev +// --header=./HEADER-APACHE2 +// --output=./frame/whitelist/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_whitelist. +pub trait WeightInfo { + fn whitelist_call() -> Weight; + fn remove_whitelisted_call() -> Weight; + fn dispatch_whitelisted_call(n: u32, ) -> Weight; + fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight; +} + +/// Weights for pallet_whitelist using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + fn whitelist_call() -> Weight { + // Minimum execution time: 26_261 nanoseconds. + Weight::from_ref_time(26_842_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + fn remove_whitelisted_call() -> Weight { + // Minimum execution time: 25_092 nanoseconds. + Weight::from_ref_time(25_903_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage PreimageFor (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + /// The range of component `n` is `[1, 4194294]`. + fn dispatch_whitelisted_call(n: u32, ) -> Weight { + // Minimum execution time: 36_685 nanoseconds. + Weight::from_ref_time(37_167_000) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_144).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + /// The range of component `n` is `[1, 10000]`. + fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { + // Minimum execution time: 29_187 nanoseconds. + Weight::from_ref_time(29_896_714) + // Standard Error: 6 + .saturating_add(Weight::from_ref_time(1_505).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + fn whitelist_call() -> Weight { + // Minimum execution time: 26_261 nanoseconds. + Weight::from_ref_time(26_842_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + fn remove_whitelisted_call() -> Weight { + // Minimum execution time: 25_092 nanoseconds. + Weight::from_ref_time(25_903_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage PreimageFor (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + /// The range of component `n` is `[1, 4194294]`. + fn dispatch_whitelisted_call(n: u32, ) -> Weight { + // Minimum execution time: 36_685 nanoseconds. + Weight::from_ref_time(37_167_000) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_144).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + /// The range of component `n` is `[1, 10000]`. + fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { + // Minimum execution time: 29_187 nanoseconds. + Weight::from_ref_time(29_896_714) + // Standard Error: 6 + .saturating_add(Weight::from_ref_time(1_505).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } +} diff --git a/primitives/api/Cargo.toml b/primitives/api/Cargo.toml index 7e751232acb50..3139c66cef921 100644 --- a/primitives/api/Cargo.toml +++ b/primitives/api/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-api" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate runtime api primitives" readme = "README.md" @@ -13,17 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } sp-api-proc-macro = { version = "4.0.0-dev", path = "proc-macro" } -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../runtime" } -sp-version = { version = "4.0.0-dev", default-features = false, path = "../version" } -sp-state-machine = { version = "0.10.0-dev", optional = true, path = "../state-machine" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-version = { version = "5.0.0", default-features = false, path = "../version" } +sp-state-machine = { version = "0.13.0", default-features = false, optional = true, path = "../state-machine" } +sp-trie = { version = "7.0.0", default-features = false, optional = true, path = "../trie" } hash-db = { version = "0.15.2", optional = true } -thiserror = { version = "1.0.21", optional = true } +thiserror = { version = "1.0.30", optional = true } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.17", default-features = false } [dev-dependencies] sp-test-primitives = { version = "2.0.0", path = "../test-primitives" } @@ -35,7 +36,8 @@ std = [ "sp-core/std", "sp-std/std", "sp-runtime/std", - "sp-state-machine", + "sp-state-machine/std", + "sp-trie/std", "sp-version/std", "hash-db", "thiserror", diff --git a/primitives/api/proc-macro/Cargo.toml b/primitives/api/proc-macro/Cargo.toml index d5909967ac5a4..8acc15d6a0591 100644 --- a/primitives/api/proc-macro/Cargo.toml +++ b/primitives/api/proc-macro/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-api-proc-macro" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Macros for declaring and implementing runtime apis." documentation = "https://docs.rs/sp-api-proc-macro" @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.3" -syn = { version = "1.0.58", features = ["full", "fold", "extra-traits", "visit"] } -proc-macro2 = "1.0.29" -blake2-rfc = { version = "0.2.18", default-features = false } -proc-macro-crate = "1.0.0" +quote = "1.0.10" +syn = { version = "1.0.98", features = ["full", "fold", "extra-traits", "visit"] } +proc-macro2 = "1.0.37" +blake2 = { version = "0.10.4", default-features = false } +proc-macro-crate = "1.1.3" # Required for the doc tests [features] diff --git a/primitives/api/proc-macro/src/common.rs b/primitives/api/proc-macro/src/common.rs new file mode 100644 index 0000000000000..10887be613278 --- /dev/null +++ b/primitives/api/proc-macro/src/common.rs @@ -0,0 +1,41 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2024 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. + +/// The ident used for the block generic parameter. +pub const BLOCK_GENERIC_IDENT: &str = "Block"; + +/// Unique identifier used to make the hidden includes unique for this macro. +pub const HIDDEN_INCLUDES_ID: &str = "DECL_RUNTIME_APIS"; + +/// The `core_trait` attribute. +pub const CORE_TRAIT_ATTRIBUTE: &str = "core_trait"; +/// The `api_version` attribute. +/// +/// Is used to set the current version of the trait. +pub const API_VERSION_ATTRIBUTE: &str = "api_version"; +/// The `changed_in` attribute. +/// +/// Is used when the function signature changed between different versions of a trait. +/// This attribute should be placed on the old signature of the function. +pub const CHANGED_IN_ATTRIBUTE: &str = "changed_in"; +/// The `renamed` attribute. +/// +/// Is used when a trait method was renamed. +pub const RENAMED_ATTRIBUTE: &str = "renamed"; +/// All attributes that we support in the declaration of a runtime api trait. +pub const SUPPORTED_ATTRIBUTE_NAMES: &[&str] = + &[CORE_TRAIT_ATTRIBUTE, API_VERSION_ATTRIBUTE, CHANGED_IN_ATTRIBUTE, RENAMED_ATTRIBUTE]; diff --git a/primitives/api/proc-macro/src/decl_runtime_apis.rs b/primitives/api/proc-macro/src/decl_runtime_apis.rs index 510a2eeaa530a..8d46047dbda5a 100644 --- a/primitives/api/proc-macro/src/decl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/decl_runtime_apis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,11 +16,15 @@ // limitations under the License. use crate::utils::{ - extract_parameter_names_types_and_borrows, fold_fn_decl_for_client_side, - generate_call_api_at_fn_name, generate_crate_access, generate_hidden_includes, - generate_method_runtime_api_impl_name, generate_native_call_generator_fn_name, - generate_runtime_mod_name_for_trait, prefix_function_with_trait, - replace_wild_card_parameter_names, return_type_extract_type, AllowSelfRefInParameters, + extract_parameter_names_types_and_borrows, fold_fn_decl_for_client_side, generate_crate_access, + generate_hidden_includes, generate_runtime_mod_name_for_trait, parse_runtime_api_version, + prefix_function_with_trait, replace_wild_card_parameter_names, return_type_extract_type, + versioned_trait_name, AllowSelfRefInParameters, +}; + +use crate::common::{ + API_VERSION_ATTRIBUTE, BLOCK_GENERIC_IDENT, CHANGED_IN_ATTRIBUTE, CORE_TRAIT_ATTRIBUTE, + HIDDEN_INCLUDES_ID, RENAMED_ATTRIBUTE, SUPPORTED_ATTRIBUTE_NAMES, }; use proc_macro2::{Span, TokenStream}; @@ -33,38 +37,11 @@ use syn::{ parse_macro_input, parse_quote, spanned::Spanned, visit::{self, Visit}, - Attribute, FnArg, GenericParam, Generics, Ident, ItemTrait, Lit, Meta, NestedMeta, ReturnType, - TraitBound, TraitItem, TraitItemMethod, Type, + Attribute, FnArg, GenericParam, Generics, Ident, ItemTrait, Lit, Meta, NestedMeta, TraitBound, + TraitItem, TraitItemMethod, }; -use std::collections::HashMap; - -use blake2_rfc; - -/// The ident used for the block generic parameter. -const BLOCK_GENERIC_IDENT: &str = "Block"; - -/// Unique identifier used to make the hidden includes unique for this macro. -const HIDDEN_INCLUDES_ID: &str = "DECL_RUNTIME_APIS"; - -/// The `core_trait` attribute. -const CORE_TRAIT_ATTRIBUTE: &str = "core_trait"; -/// The `api_version` attribute. -/// -/// Is used to set the current version of the trait. -const API_VERSION_ATTRIBUTE: &str = "api_version"; -/// The `changed_in` attribute. -/// -/// Is used when the function signature changed between different versions of a trait. -/// This attribute should be placed on the old signature of the function. -const CHANGED_IN_ATTRIBUTE: &str = "changed_in"; -/// The `renamed` attribute. -/// -/// Is used when a trait method was renamed. -const RENAMED_ATTRIBUTE: &str = "renamed"; -/// All attributes that we support in the declaration of a runtime api trait. -const SUPPORTED_ATTRIBUTE_NAMES: &[&str] = - &[CORE_TRAIT_ATTRIBUTE, API_VERSION_ATTRIBUTE, CHANGED_IN_ATTRIBUTE, RENAMED_ATTRIBUTE]; +use std::collections::{BTreeMap, HashMap}; /// The structure used for parsing the runtime api declarations. struct RuntimeApiDecls { @@ -121,20 +98,6 @@ impl<'ast> Visit<'ast> for IsUsingBlock { } } -/// Visits the ast and checks if `Block` ident is used somewhere. -fn type_is_using_block(ty: &Type) -> bool { - let mut visitor = IsUsingBlock { result: false }; - visitor.visit_type(ty); - visitor.result -} - -/// Visits the ast and checks if `Block` ident is used somewhere. -fn return_type_is_using_block(ty: &ReturnType) -> bool { - let mut visitor = IsUsingBlock { result: false }; - visitor.visit_return_type(ty); - visitor.result -} - /// Replace all occurrences of `Block` with `NodeBlock` struct ReplaceBlockWithNodeBlock {} @@ -148,148 +111,69 @@ impl Fold for ReplaceBlockWithNodeBlock { } } -/// Replace all occurrences of `Block` with `NodeBlock` -fn fn_arg_replace_block_with_node_block(fn_arg: FnArg) -> FnArg { - let mut replace = ReplaceBlockWithNodeBlock {}; - fold::fold_fn_arg(&mut replace, fn_arg) -} - -/// Replace all occurrences of `Block` with `NodeBlock` -fn return_type_replace_block_with_node_block(return_type: ReturnType) -> ReturnType { - let mut replace = ReplaceBlockWithNodeBlock {}; - fold::fold_return_type(&mut replace, return_type) -} - -/// Generate the functions that generate the native call closure for each trait method. -fn generate_native_call_generators(decl: &ItemTrait) -> Result { - let fns = decl.items.iter().filter_map(|i| match i { - TraitItem::Method(ref m) => Some(&m.sig), - _ => None, - }); - - let mut result = Vec::new(); - let trait_ = &decl.ident; - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - - // Auxiliary function that is used to convert between types that use different block types. - // The function expects that both are convertible by encoding the one and decoding the other. - result.push(quote!( - #[cfg(any(feature = "std", test))] - fn convert_between_block_types - #crate_::ApiError>( - input: &I, - map_error: F, - ) -> std::result::Result - { - ::decode_with_depth_limit( - #crate_::MAX_EXTRINSIC_DEPTH, - &#crate_::Encode::encode(input)[..], - ).map_err(map_error) +/// Versioned API traits are used to catch missing methods when implementing a specific version of a +/// versioned API. They contain all non-versioned methods (aka stable methods) from the main trait +/// and all versioned methods for the specific version. This means that there is one trait for each +/// version mentioned in the trait definition. For example: +/// ```ignore +/// // The trait version implicitly is 1 +/// decl_runtime_apis!( +/// trait SomeApi { +/// fn method1(); // this is a 'stable method' +/// +/// #[api_version(2)] +/// fn method2(); +/// +/// #[api_version(2)] +/// fn method3(); +/// +/// #[api_version(3)] +/// fn method4(); +/// } +/// ); +/// ``` +/// This trait has got three different versions. The function below will generate the following +/// code: +/// ``` +/// trait SomeApiV1 { +/// // in V1 only the stable methods are required. The rest has got default implementations. +/// fn method1(); +/// } +/// +/// trait SomeApiV2 { +/// // V2 contains all methods from V1 and V2. V3 not required so they are skipped. +/// fn method1(); +/// fn method2(); +/// fn method3(); +/// } +/// +/// trait SomeApiV3 { +/// // And V3 contains all methods from the trait. +/// fn method1(); +/// fn method2(); +/// fn method3(); +/// fn method4(); +/// } +/// ``` +fn generate_versioned_api_traits( + api: ItemTrait, + methods: BTreeMap>, +) -> Vec { + let mut result = Vec::::new(); + for (version, _) in &methods { + let mut versioned_trait = api.clone(); + versioned_trait.ident = versioned_trait_name(&versioned_trait.ident, *version); + versioned_trait.items = Vec::new(); + // Add the methods from the current version and all previous one. Versions are sorted so + // it's safe to stop early. + for (_, m) in methods.iter().take_while(|(v, _)| v <= &version) { + versioned_trait.items.extend(m.iter().cloned().map(|m| TraitItem::Method(m))); } - )); - - // Generate a native call generator for each function of the given trait. - for fn_ in fns { - let params = extract_parameter_names_types_and_borrows(&fn_, AllowSelfRefInParameters::No)?; - let trait_fn_name = &fn_.ident; - let function_name_str = fn_.ident.to_string(); - let fn_name = generate_native_call_generator_fn_name(&fn_.ident); - let output = return_type_replace_block_with_node_block(fn_.output.clone()); - let output_ty = return_type_extract_type(&output); - let output = quote!( std::result::Result<#output_ty, #crate_::ApiError> ); - - // Every type that is using the `Block` generic parameter, we need to encode/decode, - // to make it compatible between the runtime/node. - let conversions = params.iter().filter(|v| type_is_using_block(&v.1)).map(|(n, t, _)| { - let param_name = quote!(#n).to_string(); - - quote!( - let #n: #t = convert_between_block_types( - &#n, - |e| #crate_::ApiError::FailedToConvertParameter { - function: #function_name_str, - parameter: #param_name, - error: e, - }, - )?; - ) - }); - // Same as for the input types, we need to check if we also need to convert the output, - // before returning it. - let output_conversion = if return_type_is_using_block(&fn_.output) { - quote!( - convert_between_block_types( - &res, - |e| #crate_::ApiError::FailedToConvertReturnValue { - function: #function_name_str, - error: e, - }, - ) - ) - } else { - quote!(Ok(res)) - }; - - let input_names = params.iter().map(|v| &v.0); - // If the type is using the block generic type, we will encode/decode it to make it - // compatible. To ensure that we forward it by ref/value, we use the value given by the - // the user. Otherwise if it is not using the block, we don't need to add anything. - let input_borrows = - params - .iter() - .map(|v| if type_is_using_block(&v.1) { v.2.clone() } else { None }); - - // Replace all `Block` with `NodeBlock`, add `'a` lifetime to references and collect - // all the function inputs. - let fn_inputs = fn_ - .inputs - .iter() - .map(|v| fn_arg_replace_block_with_node_block(v.clone())) - .map(|v| match v { - FnArg::Typed(ref arg) => { - let mut arg = arg.clone(); - if let Type::Reference(ref mut r) = *arg.ty { - r.lifetime = Some(parse_quote!( 'a )); - } - FnArg::Typed(arg) - }, - r => r, - }); - - let (impl_generics, ty_generics, where_clause) = decl.generics.split_for_impl(); - // We need to parse them again, to get an easy access to the actual parameters. - let impl_generics: Generics = parse_quote!( #impl_generics ); - let impl_generics_params = impl_generics.params.iter().map(|p| { - match p { - GenericParam::Type(ref ty) => { - let mut ty = ty.clone(); - ty.bounds.push(parse_quote!( 'a )); - GenericParam::Type(ty) - }, - // We should not see anything different than type params here. - r => r.clone(), - } - }); - // Generate the generator function - result.push(quote!( - #[cfg(any(feature = "std", test))] - pub fn #fn_name< - 'a, ApiImpl: #trait_ #ty_generics, NodeBlock: #crate_::BlockT - #(, #impl_generics_params)* - >( - #( #fn_inputs ),* - ) -> impl FnOnce() -> #output + 'a #where_clause { - move || { - #( #conversions )* - let res = ApiImpl::#trait_fn_name(#( #input_borrows #input_names ),*); - #output_conversion - } - } - )); + result.push(versioned_trait); } - Ok(quote!( #( #result )* )) + result } /// Try to parse the given `Attribute` as `renamed` attribute. @@ -327,126 +211,13 @@ fn parse_renamed_attribute(renamed: &Attribute) -> Result<(String, u32)> { } } -/// Generate the functions that call the api at a given block for a given trait method. -fn generate_call_api_at_calls(decl: &ItemTrait) -> Result { - let fns = decl.items.iter().filter_map(|i| match i { - TraitItem::Method(ref m) => Some((&m.attrs, &m.sig)), - _ => None, - }); - - let mut result = Vec::new(); - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - - // Generate a native call generator for each function of the given trait. - for (attrs, fn_) in fns { - let trait_name = &decl.ident; - let trait_fn_name = prefix_function_with_trait(&trait_name, &fn_.ident); - let fn_name = generate_call_api_at_fn_name(&fn_.ident); - - let attrs = remove_supported_attributes(&mut attrs.clone()); - - if attrs.contains_key(RENAMED_ATTRIBUTE) && attrs.contains_key(CHANGED_IN_ATTRIBUTE) { - return Err(Error::new( - fn_.span(), - format!( - "`{}` and `{}` are not supported at once.", - RENAMED_ATTRIBUTE, CHANGED_IN_ATTRIBUTE - ), - )) - } - - // We do not need to generate this function for a method that signature was changed. - if attrs.contains_key(CHANGED_IN_ATTRIBUTE) { - continue - } - - // Parse the renamed attributes. - let mut renames = Vec::new(); - if let Some((_, a)) = attrs.iter().find(|a| a.0 == &RENAMED_ATTRIBUTE) { - let (old_name, version) = parse_renamed_attribute(a)?; - renames.push((version, prefix_function_with_trait(&trait_name, &old_name))); - } - - renames.sort_by(|l, r| r.cmp(l)); - let (versions, old_names) = renames.into_iter().fold( - (Vec::new(), Vec::new()), - |(mut versions, mut old_names), (version, old_name)| { - versions.push(version); - old_names.push(old_name); - (versions, old_names) - }, - ); - - // Generate the generator function - result.push(quote!( - #[cfg(any(feature = "std", test))] - #[allow(clippy::too_many_arguments)] - pub fn #fn_name< - R: #crate_::Encode + #crate_::Decode + PartialEq, - NC: FnOnce() -> std::result::Result + std::panic::UnwindSafe, - Block: #crate_::BlockT, - T: #crate_::CallApiAt, - >( - call_runtime_at: &T, - at: &#crate_::BlockId, - args: Vec, - changes: &std::cell::RefCell<#crate_::OverlayedChanges>, - storage_transaction_cache: &std::cell::RefCell< - #crate_::StorageTransactionCache - >, - native_call: Option, - context: #crate_::ExecutionContext, - recorder: &Option<#crate_::ProofRecorder>, - ) -> std::result::Result<#crate_::NativeOrEncoded, #crate_::ApiError> { - let version = call_runtime_at.runtime_version_at(at)?; - - #( - // Check if we need to call the function by an old name. - if version.apis.iter().any(|(s, v)| { - s == &ID && *v < #versions - }) { - let params = #crate_::CallApiAtParams::<_, fn() -> _, _> { - at, - function: #old_names, - native_call: None, - arguments: args, - overlayed_changes: changes, - storage_transaction_cache, - context, - recorder, - }; - - let ret = call_runtime_at.call_api_at(params)?; - - return Ok(ret) - } - )* - - let params = #crate_::CallApiAtParams { - at, - function: #trait_fn_name, - native_call, - arguments: args, - overlayed_changes: changes, - storage_transaction_cache, - context, - recorder, - }; - - call_runtime_at.call_api_at(params) - } - )); - } - - Ok(quote!( #( #result )* )) -} - /// Generate the declaration of the trait for the runtime. fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { let mut result = Vec::new(); for decl in decls { let mut decl = decl.clone(); + let decl_span = decl.span(); extend_generics_with_block(&mut decl.generics); let mod_name = generate_runtime_mod_name_for_trait(&decl.ident); let found_attributes = remove_supported_attributes(&mut decl.attrs); @@ -454,30 +225,73 @@ 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()); - let call_api_at_calls = generate_call_api_at_calls(&decl)?; - - // Remove methods that have the `changed_in` attribute as they are not required for the - // runtime anymore. - decl.items = decl - .items - .iter_mut() - .filter_map(|i| match i { - TraitItem::Method(ref mut method) => { - if remove_supported_attributes(&mut method.attrs) - .contains_key(CHANGED_IN_ATTRIBUTE) - { - None - } else { - // Make sure we replace all the wild card parameter names. - replace_wild_card_parameter_names(&mut method.sig); - Some(TraitItem::Method(method.clone())) - } - }, - r => Some(r.clone()), - }) - .collect(); + let trait_api_version = get_api_version(&found_attributes)?; + + let mut methods_by_version: BTreeMap> = BTreeMap::new(); + + // Process the items in the declaration. The filter_map function below does a lot of stuff + // because the method attributes are stripped at this point + decl.items.iter_mut().for_each(|i| match i { + TraitItem::Method(ref mut method) => { + let method_attrs = remove_supported_attributes(&mut method.attrs); + let mut method_version = trait_api_version; + // validate the api version for the method (if any) and generate default + // implementation for versioned methods + if let Some(version_attribute) = method_attrs.get(API_VERSION_ATTRIBUTE) { + method_version = match parse_runtime_api_version(version_attribute) { + Ok(method_api_ver) if method_api_ver < trait_api_version => { + let method_ver = method_api_ver.to_string(); + let trait_ver = trait_api_version.to_string(); + let mut err1 = Error::new( + version_attribute.span(), + format!( + "Method version `{}` is older than (or equal to) trait version `{}`.\ + Methods can't define versions older than the trait version.", + method_ver, + trait_ver, + ), + ); + + let err2 = match found_attributes.get(&API_VERSION_ATTRIBUTE) { + Some(attr) => Error::new(attr.span(), "Trait version is set here."), + None => Error::new( + decl_span, + "Trait version is not set so it is implicitly equal to 1.", + ), + }; + err1.combine(err2); + result.push(err1.to_compile_error()); + + trait_api_version + }, + Ok(method_api_ver) => method_api_ver, + Err(e) => { + result.push(e.to_compile_error()); + trait_api_version + }, + }; + } - let native_call_generators = generate_native_call_generators(&decl)?; + // Any method with the `changed_in` attribute isn't required for the runtime + // anymore. + if !method_attrs.contains_key(CHANGED_IN_ATTRIBUTE) { + // Make sure we replace all the wild card parameter names. + replace_wild_card_parameter_names(&mut method.sig); + + // partition methods by api version + methods_by_version.entry(method_version).or_default().push(method.clone()); + } + }, + _ => (), + }); + + let versioned_api_traits = generate_versioned_api_traits(decl.clone(), methods_by_version); + + let main_api_ident = decl.ident.clone(); + let versioned_ident = &versioned_api_traits + .first() + .expect("There should always be at least one version.") + .ident; result.push(quote!( #[doc(hidden)] @@ -486,15 +300,13 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { pub mod #mod_name { use super::*; - #decl + #( #versioned_api_traits )* + + pub use #versioned_ident as #main_api_ident; pub #api_version pub #id - - #native_call_generators - - #call_api_at_calls } )); } @@ -513,18 +325,45 @@ struct ToClientSideDecl<'a> { } impl<'a> ToClientSideDecl<'a> { - fn fold_item_trait_items(&mut self, items: Vec) -> Vec { + /// Process the given [`ItemTrait`]. + fn process(mut self, decl: ItemTrait) -> ItemTrait { + let mut decl = self.fold_item_trait(decl); + + let block_id = self.block_id; + let crate_ = self.crate_; + + // Add the special method that will be implemented by the `impl_runtime_apis!` macro + // to enable functions to call into the runtime. + decl.items.push(parse_quote! { + /// !!INTERNAL USE ONLY!! + #[doc(hidden)] + fn __runtime_api_internal_call_api_at( + &self, + at: &#block_id, + context: #crate_::ExecutionContext, + params: std::vec::Vec, + fn_name: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, + ) -> std::result::Result, #crate_::ApiError>; + }); + + decl + } +} + +impl<'a> ToClientSideDecl<'a> { + fn fold_item_trait_items( + &mut self, + items: Vec, + trait_generics_num: usize, + ) -> Vec { let mut result = Vec::new(); items.into_iter().for_each(|i| match i { TraitItem::Method(method) => { - let (fn_decl, fn_impl, fn_decl_ctx) = self.fold_trait_item_method(method); + let (fn_decl, fn_decl_ctx) = + self.fold_trait_item_method(method, trait_generics_num); result.push(fn_decl.into()); result.push(fn_decl_ctx.into()); - - if let Some(fn_impl) = fn_impl { - result.push(fn_impl.into()); - } }, r => result.push(r), }); @@ -535,20 +374,24 @@ impl<'a> ToClientSideDecl<'a> { fn fold_trait_item_method( &mut self, method: TraitItemMethod, - ) -> (TraitItemMethod, Option, TraitItemMethod) { + trait_generics_num: usize, + ) -> (TraitItemMethod, TraitItemMethod) { let crate_ = self.crate_; let context = quote!( #crate_::ExecutionContext::OffchainCall(None) ); - let fn_impl = self.create_method_runtime_api_impl(method.clone()); - let fn_decl = self.create_method_decl(method.clone(), context); - let fn_decl_ctx = self.create_method_decl_with_context(method); + let fn_decl = self.create_method_decl(method.clone(), context, trait_generics_num); + let fn_decl_ctx = self.create_method_decl_with_context(method, trait_generics_num); - (fn_decl, fn_impl, fn_decl_ctx) + (fn_decl, fn_decl_ctx) } - fn create_method_decl_with_context(&mut self, method: TraitItemMethod) -> TraitItemMethod { + fn create_method_decl_with_context( + &mut self, + method: TraitItemMethod, + trait_generics_num: usize, + ) -> TraitItemMethod { let crate_ = self.crate_; let context_arg: syn::FnArg = parse_quote!( context: #crate_::ExecutionContext ); - let mut fn_decl_ctx = self.create_method_decl(method, quote!(context)); + let mut fn_decl_ctx = self.create_method_decl(method, quote!(context), trait_generics_num); fn_decl_ctx.sig.ident = Ident::new(&format!("{}_with_context", &fn_decl_ctx.sig.ident), Span::call_site()); fn_decl_ctx.sig.inputs.insert(2, context_arg); @@ -556,52 +399,6 @@ impl<'a> ToClientSideDecl<'a> { fn_decl_ctx } - /// Takes the given method and creates a `method_runtime_api_impl` method that will be - /// implemented in the runtime for the client side. - fn create_method_runtime_api_impl( - &mut self, - mut method: TraitItemMethod, - ) -> Option { - if remove_supported_attributes(&mut method.attrs).contains_key(CHANGED_IN_ATTRIBUTE) { - return None - } - - let fn_sig = &method.sig; - let ret_type = return_type_extract_type(&fn_sig.output); - - // Get types and if the value is borrowed from all parameters. - // If there is an error, we push it as the block to the user. - let param_types = - match extract_parameter_names_types_and_borrows(fn_sig, AllowSelfRefInParameters::No) { - Ok(res) => res - .into_iter() - .map(|v| { - let ty = v.1; - let borrow = v.2; - quote!( #borrow #ty ) - }) - .collect::>(), - Err(e) => { - self.errors.push(e.to_compile_error()); - Vec::new() - }, - }; - let name = generate_method_runtime_api_impl_name(&self.trait_, &method.sig.ident); - let block_id = self.block_id; - let crate_ = self.crate_; - - Some(parse_quote! { - #[doc(hidden)] - fn #name( - &self, - at: &#block_id, - context: #crate_::ExecutionContext, - params: Option<( #( #param_types ),* )>, - params_encoded: Vec, - ) -> std::result::Result<#crate_::NativeOrEncoded<#ret_type>, #crate_::ApiError>; - }) - } - /// Takes the method declared by the user and creates the declaration we require for the runtime /// api client side. This method will call by default the `method_runtime_api_impl` for doing /// the actual call into the runtime. @@ -609,6 +406,7 @@ impl<'a> ToClientSideDecl<'a> { &mut self, mut method: TraitItemMethod, context: TokenStream, + trait_generics_num: usize, ) -> TraitItemMethod { let params = match extract_parameter_names_types_and_borrows( &method.sig, @@ -620,21 +418,45 @@ impl<'a> ToClientSideDecl<'a> { Vec::new() }, }; - let params2 = params.clone(); let ret_type = return_type_extract_type(&method.sig.output); - fold_fn_decl_for_client_side(&mut method.sig, &self.block_id, &self.crate_); + fold_fn_decl_for_client_side(&mut method.sig, self.block_id, self.crate_); - let name_impl = generate_method_runtime_api_impl_name(&self.trait_, &method.sig.ident); let crate_ = self.crate_; let found_attributes = remove_supported_attributes(&mut method.attrs); + + // Parse the renamed attributes. + let mut renames = Vec::new(); + for (_, a) in found_attributes.iter().filter(|a| a.0 == &RENAMED_ATTRIBUTE) { + match parse_renamed_attribute(a) { + Ok((old_name, version)) => { + renames.push((version, prefix_function_with_trait(&self.trait_, &old_name))); + }, + Err(e) => self.errors.push(e.to_compile_error()), + } + } + + renames.sort_by(|l, r| r.cmp(l)); + let (versions, old_names) = renames.into_iter().fold( + (Vec::new(), Vec::new()), + |(mut versions, mut old_names), (version, old_name)| { + versions.push(version); + old_names.push(old_name); + (versions, old_names) + }, + ); + + // Generate the function name before we may rename it below to + // `function_name_before_version_{}`. + let function_name = prefix_function_with_trait(&self.trait_, &method.sig.ident); + // If the method has a `changed_in` attribute, we need to alter the method name to // `method_before_version_VERSION`. - let (native_handling, param_tuple) = match get_changed_in(&found_attributes) { + match get_changed_in(&found_attributes) { Ok(Some(version)) => { // Make sure that the `changed_in` version is at least the current `api_version`. - if get_api_version(&self.found_attributes).ok() < Some(version) { + if get_api_version(self.found_attributes).ok() < Some(version) { self.errors.push( Error::new( method.span(), @@ -650,47 +472,51 @@ impl<'a> ToClientSideDecl<'a> { ); method.sig.ident = ident; method.attrs.push(parse_quote!( #[deprecated] )); - - let panic = - format!("Calling `{}` should not return a native value!", method.sig.ident); - (quote!(panic!(#panic)), quote!(None)) }, - Ok(None) => (quote!(Ok(n)), quote!( Some(( #( #params2 ),* )) )), + Ok(None) => {}, Err(e) => { self.errors.push(e.to_compile_error()); - (quote!(unimplemented!()), quote!(None)) }, }; - let function_name = method.sig.ident.to_string(); + // The module where the runtime relevant stuff is declared. + let trait_name = &self.trait_; + let runtime_mod = generate_runtime_mod_name_for_trait(trait_name); + let underscores = (0..trait_generics_num).map(|_| quote!(_)); // Generate the default implementation that calls the `method_runtime_api_impl` method. method.default = Some(parse_quote! { { - let runtime_api_impl_params_encoded = + let __runtime_api_impl_params_encoded__ = #crate_::Encode::encode(&( #( &#params ),* )); - self.#name_impl( + >::__runtime_api_internal_call_api_at( + self, __runtime_api_at_param__, #context, - #param_tuple, - runtime_api_impl_params_encoded, - ).and_then(|r| - match r { - #crate_::NativeOrEncoded::Native(n) => { - #native_handling - }, - #crate_::NativeOrEncoded::Encoded(r) => { - <#ret_type as #crate_::Decode>::decode(&mut &r[..]) - .map_err(|err| - #crate_::ApiError::FailedToDecodeReturnValue { - function: #function_name, - error: err, - } - ) - } + __runtime_api_impl_params_encoded__, + &|version| { + #( + // Check if we need to call the function by an old name. + if version.apis.iter().any(|(s, v)| { + s == &#runtime_mod::ID && *v < #versions + }) { + return #old_names + } + )* + + #function_name } ) + .and_then(|r| + std::result::Result::map_err( + <#ret_type as #crate_::Decode>::decode(&mut &r[..]), + |err| #crate_::ApiError::FailedToDecodeReturnValue { + function: #function_name, + error: err, + } + ) + ) } }); @@ -709,7 +535,7 @@ impl<'a> Fold for ToClientSideDecl<'a> { if is_core_trait { // Add all the supertraits we want to have for `Core`. - input.supertraits = parse_quote!('static + Send + Sync); + input.supertraits = parse_quote!('static + Send); } else { // Add the `Core` runtime api as super trait. let crate_ = &self.crate_; @@ -718,42 +544,19 @@ impl<'a> Fold for ToClientSideDecl<'a> { // The client side trait is only required when compiling with the feature `std` or `test`. input.attrs.push(parse_quote!( #[cfg(any(feature = "std", test))] )); - input.items = self.fold_item_trait_items(input.items); + input.items = self.fold_item_trait_items(input.items, input.generics.params.len()); fold::fold_item_trait(self, input) } } -/// Parse the given attribute as `API_VERSION_ATTRIBUTE`. -fn parse_runtime_api_version(version: &Attribute) -> Result { - let meta = version.parse_meta()?; - - let err = Err(Error::new( - meta.span(), - &format!( - "Unexpected `{api_version}` attribute. The supported format is `{api_version}(1)`", - api_version = API_VERSION_ATTRIBUTE - ), - )); - - match meta { - Meta::List(list) => - if list.nested.len() != 1 { - err - } else if let Some(NestedMeta::Lit(Lit::Int(i))) = list.nested.first() { - i.base10_parse() - } else { - err - }, - _ => err, - } -} - /// Generates the identifier as const variable for the given `trait_name` /// by hashing the `trait_name`. fn generate_runtime_api_id(trait_name: &str) -> TokenStream { + use blake2::digest::{consts::U8, Digest}; + let mut res = [0; 8]; - res.copy_from_slice(blake2_rfc::blake2b::blake2b(8, &[], trait_name.as_bytes()).as_bytes()); + res.copy_from_slice(blake2::Blake2b::::digest(trait_name).as_slice()); quote!( const ID: [u8; 8] = [ #( #res ),* ]; ) } @@ -786,7 +589,7 @@ fn generate_runtime_info_impl(trait_: &ItemTrait, version: u64) -> TokenStream { quote!( #[cfg(any(feature = "std", test))] impl < #( #impl_generics, )* > #crate_::RuntimeApiInfo - for #trait_name < #( #ty_generics, )* > + for dyn #trait_name < #( #ty_generics, )* > { #id #version @@ -823,16 +626,14 @@ fn generate_client_side_decls(decls: &[ItemTrait]) -> Result { let mut errors = Vec::new(); let trait_ = decl.ident.clone(); - let decl = { - let mut to_client_side = ToClientSideDecl { - crate_: &crate_, - block_id: &block_id, - found_attributes: &mut found_attributes, - errors: &mut errors, - trait_: &trait_, - }; - to_client_side.fold_item_trait(decl) - }; + let decl = ToClientSideDecl { + crate_: &crate_, + block_id: &block_id, + found_attributes: &mut found_attributes, + errors: &mut errors, + trait_: &trait_, + } + .process(decl); let api_version = get_api_version(&found_attributes); @@ -974,7 +775,7 @@ pub fn decl_runtime_apis_impl(input: proc_macro::TokenStream) -> proc_macro::Tok } fn decl_runtime_apis_impl_inner(api_decls: &[ItemTrait]) -> Result { - check_trait_decls(&api_decls)?; + check_trait_decls(api_decls)?; let hidden_includes = generate_hidden_includes(HIDDEN_INCLUDES_ID); let runtime_decls = generate_runtime_decls(api_decls)?; diff --git a/primitives/api/proc-macro/src/impl_runtime_apis.rs b/primitives/api/proc-macro/src/impl_runtime_apis.rs index bc0f027e1efaa..c3f4e36655d22 100644 --- a/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,13 @@ use crate::utils::{ extract_all_signature_types, extract_block_type_from_trait_path, extract_impl_trait, - extract_parameter_names_types_and_borrows, generate_call_api_at_fn_name, generate_crate_access, - generate_hidden_includes, generate_method_runtime_api_impl_name, - generate_native_call_generator_fn_name, generate_runtime_mod_name_for_trait, - prefix_function_with_trait, return_type_extract_type, AllowSelfRefInParameters, - RequireQualifiedTraitPath, + extract_parameter_names_types_and_borrows, generate_crate_access, generate_hidden_includes, + generate_runtime_mod_name_for_trait, parse_runtime_api_version, prefix_function_with_trait, + versioned_trait_name, AllowSelfRefInParameters, RequireQualifiedTraitPath, }; +use crate::common::API_VERSION_ATTRIBUTE; + use proc_macro2::{Span, TokenStream}; use quote::quote; @@ -33,8 +33,7 @@ use syn::{ parse::{Error, Parse, ParseStream, Result}, parse_macro_input, parse_quote, spanned::Spanned, - Attribute, GenericArgument, Ident, ImplItem, ItemImpl, Path, PathArguments, Signature, Type, - TypePath, + Attribute, Ident, ImplItem, ItemImpl, Path, Signature, Type, TypePath, }; use std::collections::HashSet; @@ -86,7 +85,7 @@ fn generate_impl_call( let (#( #pnames ),*) : ( #( #ptypes ),* ) = match #c::DecodeLimit::decode_all_with_depth_limit( #c::MAX_EXTRINSIC_DEPTH, - &#input, + &mut #input, ) { Ok(res) => res, Err(e) => panic!("Bad input data provided to {}: {}", #fn_name_str, e), @@ -105,8 +104,10 @@ fn generate_impl_calls( let mut impl_calls = Vec::new(); for impl_ in impls { + let trait_api_ver = extract_api_version(&impl_.attrs, impl_.span())?; let impl_trait_path = extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?; let impl_trait = extend_with_runtime_decl_path(impl_trait_path.clone()); + let impl_trait = extend_with_api_version(impl_trait, trait_api_ver); let impl_trait_ident = &impl_trait_path .segments .last() @@ -207,22 +208,9 @@ fn generate_runtime_api_base_structures() -> Result { storage_transaction_cache: std::cell::RefCell< #crate_::StorageTransactionCache >, - recorder: Option<#crate_::ProofRecorder>, + recorder: std::option::Option<#crate_::ProofRecorder>, } - // `RuntimeApi` itself is not threadsafe. However, an instance is only available in a - // `ApiRef` object and `ApiRef` also has an associated lifetime. This lifetimes makes it - // impossible to move `RuntimeApi` into another thread. - #[cfg(any(feature = "std", test))] - unsafe impl> Send - for RuntimeApiImpl - {} - - #[cfg(any(feature = "std", test))] - unsafe impl> Sync - for RuntimeApiImpl - {} - #[cfg(any(feature = "std", test))] impl> #crate_::ApiExt for RuntimeApiImpl @@ -233,12 +221,12 @@ fn generate_runtime_api_base_structures() -> Result { &self, call: F, ) -> R where Self: Sized { - self.changes.borrow_mut().start_transaction(); - *self.commit_on_success.borrow_mut() = false; + #crate_::OverlayedChanges::start_transaction(&mut std::cell::RefCell::borrow_mut(&self.changes)); + *std::cell::RefCell::borrow_mut(&self.commit_on_success) = false; let res = call(self); - *self.commit_on_success.borrow_mut() = true; + *std::cell::RefCell::borrow_mut(&self.commit_on_success) = true; - self.commit_or_rollback(matches!(res, #crate_::TransactionOutcome::Commit(_))); + self.commit_or_rollback(std::matches!(res, #crate_::TransactionOutcome::Commit(_))); res.into_inner() } @@ -247,9 +235,8 @@ fn generate_runtime_api_base_structures() -> Result { &self, at: &#crate_::BlockId, ) -> std::result::Result where Self: Sized { - self.call - .runtime_version_at(at) - .map(|v| v.has_api_with(&A::ID, |v| v == A::VERSION)) + #crate_::CallApiAt::::runtime_version_at(self.call, at) + .map(|v| #crate_::RuntimeVersion::has_api_with(&v, &A::ID, |v| v == A::VERSION)) } fn has_api_with bool>( @@ -257,51 +244,53 @@ fn generate_runtime_api_base_structures() -> Result { at: &#crate_::BlockId, pred: P, ) -> std::result::Result where Self: Sized { - self.call - .runtime_version_at(at) - .map(|v| v.has_api_with(&A::ID, pred)) + #crate_::CallApiAt::::runtime_version_at(self.call, at) + .map(|v| #crate_::RuntimeVersion::has_api_with(&v, &A::ID, pred)) } fn api_version( &self, at: &#crate_::BlockId, ) -> std::result::Result, #crate_::ApiError> where Self: Sized { - self.call - .runtime_version_at(at) - .map(|v| v.api_version(&A::ID)) + #crate_::CallApiAt::::runtime_version_at(self.call, at) + .map(|v| #crate_::RuntimeVersion::api_version(&v, &A::ID)) } fn record_proof(&mut self) { - self.recorder = Some(Default::default()); + self.recorder = std::option::Option::Some(std::default::Default::default()); } - fn proof_recorder(&self) -> Option<#crate_::ProofRecorder> { - self.recorder.clone() + fn proof_recorder(&self) -> std::option::Option<#crate_::ProofRecorder> { + std::clone::Clone::clone(&self.recorder) } - fn extract_proof(&mut self) -> Option<#crate_::StorageProof> { - self.recorder - .take() - .map(|recorder| recorder.to_storage_proof()) + fn extract_proof( + &mut self, + ) -> std::option::Option<#crate_::StorageProof> { + let recorder = std::option::Option::take(&mut self.recorder); + std::option::Option::map(recorder, |recorder| { + #crate_::ProofRecorder::::drain_storage_proof(recorder) + }) } fn into_storage_changes( &self, backend: &Self::StateBackend, - changes_trie_state: Option<&#crate_::ChangesTrieState< - #crate_::HashFor, - #crate_::NumberFor, - >>, parent_hash: Block::Hash, - ) -> std::result::Result< + ) -> core::result::Result< #crate_::StorageChanges, String > where Self: Sized { - self.changes.replace(Default::default()).into_storage_changes( + let at = #crate_::BlockId::Hash(std::clone::Clone::clone(&parent_hash)); + let state_version = #crate_::CallApiAt::::runtime_version_at(self.call, &at) + .map(|v| #crate_::RuntimeVersion::state_version(&v)) + .map_err(|e| format!("Failed to get state version: {}", e))?; + + #crate_::OverlayedChanges::into_storage_changes( + std::cell::RefCell::take(&self.changes), backend, - changes_trie_state, - parent_hash, - self.storage_transaction_cache.replace(Default::default()), + core::cell::RefCell::take(&self.storage_transaction_cache), + state_version, ) } } @@ -320,54 +309,33 @@ fn generate_runtime_api_base_structures() -> Result { RuntimeApiImpl { call: unsafe { std::mem::transmute(call) }, commit_on_success: true.into(), - changes: Default::default(), - recorder: Default::default(), - storage_transaction_cache: Default::default(), + changes: std::default::Default::default(), + recorder: std::default::Default::default(), + storage_transaction_cache: std::default::Default::default(), }.into() } } #[cfg(any(feature = "std", test))] impl> RuntimeApiImpl { - fn call_api_at< - R: #crate_::Encode + #crate_::Decode + PartialEq, - F: FnOnce( - &C, - &std::cell::RefCell<#crate_::OverlayedChanges>, - &std::cell::RefCell<#crate_::StorageTransactionCache>, - &Option<#crate_::ProofRecorder>, - ) -> std::result::Result<#crate_::NativeOrEncoded, E>, - E, - >( - &self, - call_api_at: F, - ) -> std::result::Result<#crate_::NativeOrEncoded, E> { - if *self.commit_on_success.borrow() { - self.changes.borrow_mut().start_transaction(); - } - let res = call_api_at( - &self.call, - &self.changes, - &self.storage_transaction_cache, - &self.recorder, - ); - - self.commit_or_rollback(res.is_ok()); - res - } - fn commit_or_rollback(&self, commit: bool) { let proof = "\ We only close a transaction when we opened one ourself. Other parts of the runtime that make use of transactions (state-machine) also balance their transactions. The runtime cannot close client initiated - transactions. qed"; - if *self.commit_on_success.borrow() { - if commit { - self.changes.borrow_mut().commit_transaction().expect(proof); + transactions; qed"; + if *std::cell::RefCell::borrow(&self.commit_on_success) { + let res = if commit { + #crate_::OverlayedChanges::commit_transaction( + &mut std::cell::RefCell::borrow_mut(&self.changes) + ) } else { - self.changes.borrow_mut().rollback_transaction().expect(proof); - } + #crate_::OverlayedChanges::rollback_transaction( + &mut std::cell::RefCell::borrow_mut(&self.changes) + ) + }; + + std::result::Result::expect(res, proof); } } } @@ -393,6 +361,24 @@ fn extend_with_runtime_decl_path(mut trait_: Path) -> Path { trait_ } +fn extend_with_api_version(mut trait_: Path, version: Option) -> Path { + let version = if let Some(v) = version { + v + } else { + // nothing to do + return trait_ + }; + + let trait_name = &mut trait_ + .segments + .last_mut() + .expect("Trait path should always contain at least one item; qed") + .ident; + *trait_name = versioned_trait_name(trait_name, version); + + trait_ +} + /// Generates the implementations of the apis for the runtime. fn generate_api_impl_for_runtime(impls: &[ItemImpl]) -> Result { let mut impls_prepared = Vec::new(); @@ -400,9 +386,12 @@ fn generate_api_impl_for_runtime(impls: &[ItemImpl]) -> Result { // We put `runtime` before each trait to get the trait that is intended for the runtime and // we put the `RuntimeBlock` as first argument for the trait generics. for impl_ in impls.iter() { + let trait_api_ver = extract_api_version(&impl_.attrs, impl_.span())?; + let mut impl_ = impl_.clone(); let trait_ = extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(); let trait_ = extend_with_runtime_decl_path(trait_); + let trait_ = extend_with_api_version(trait_, trait_api_ver); impl_.trait_.as_mut().unwrap().1 = trait_; impl_.attrs = filter_cfg_attrs(&impl_.attrs); @@ -419,121 +408,72 @@ fn generate_api_impl_for_runtime(impls: &[ItemImpl]) -> Result { /// with code that calls into the runtime. struct ApiRuntimeImplToApiRuntimeApiImpl<'a> { runtime_block: &'a TypePath, - runtime_mod_path: &'a Path, - runtime_type: &'a Type, - trait_generic_arguments: &'a [GenericArgument], - impl_trait: &'a Ident, } -impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { - fn fold_type_path(&mut self, input: TypePath) -> TypePath { - let new_ty_path = - if input == *self.runtime_block { parse_quote!(__SR_API_BLOCK__) } else { input }; - - fold::fold_type_path(self, new_ty_path) - } - - fn fold_impl_item_method(&mut self, mut input: syn::ImplItemMethod) -> syn::ImplItemMethod { - let block = { - let runtime_mod_path = self.runtime_mod_path; - let runtime = self.runtime_type; - let native_call_generator_ident = - generate_native_call_generator_fn_name(&input.sig.ident); - let call_api_at_call = generate_call_api_at_fn_name(&input.sig.ident); - let trait_generic_arguments = self.trait_generic_arguments; - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - - // Generate the access to the native parameters - let param_tuple_access = if input.sig.inputs.len() == 1 { - vec![quote!(p)] - } else { - input - .sig - .inputs - .iter() - .enumerate() - .map(|(i, _)| { - let i = syn::Index::from(i); - quote!( p.#i ) - }) - .collect::>() - }; +impl<'a> ApiRuntimeImplToApiRuntimeApiImpl<'a> { + /// Process the given item implementation. + fn process(mut self, input: ItemImpl) -> ItemImpl { + let mut input = self.fold_item_impl(input); - let (param_types, error) = match extract_parameter_names_types_and_borrows( - &input.sig, - AllowSelfRefInParameters::No, - ) { - Ok(res) => ( - res.into_iter() - .map(|v| { - let ty = v.1; - let borrow = v.2; - quote!( #borrow #ty ) - }) - .collect::>(), - None, - ), - Err(e) => (Vec::new(), Some(e.to_compile_error())), - }; + let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - // Rewrite the input parameters. - input.sig.inputs = parse_quote! { + // Delete all functions, because all of them are default implemented by + // `decl_runtime_apis!`. We only need to implement the `__runtime_api_internal_call_api_at` + // function. + input.items.clear(); + input.items.push(parse_quote! { + fn __runtime_api_internal_call_api_at( &self, at: &#crate_::BlockId<__SR_API_BLOCK__>, context: #crate_::ExecutionContext, - params: Option<( #( #param_types ),* )>, - params_encoded: Vec, - }; + params: std::vec::Vec, + fn_name: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, + ) -> std::result::Result, #crate_::ApiError> { + if *std::cell::RefCell::borrow(&self.commit_on_success) { + #crate_::OverlayedChanges::start_transaction( + &mut std::cell::RefCell::borrow_mut(&self.changes) + ); + } - input.sig.ident = - generate_method_runtime_api_impl_name(&self.impl_trait, &input.sig.ident); - let ret_type = return_type_extract_type(&input.sig.output); - - // Generate the correct return type. - input.sig.output = parse_quote!( - -> std::result::Result<#crate_::NativeOrEncoded<#ret_type>, #crate_::ApiError> - ); - - // Generate the new method implementation that calls into the runtime. - parse_quote!( - { - // Get the error to the user (if we have one). - #error - - self.call_api_at( - | - call_runtime_at, - changes, - storage_transaction_cache, - recorder - | { - #runtime_mod_path #call_api_at_call( - call_runtime_at, - at, - params_encoded, - changes, - storage_transaction_cache, - params.map(|p| { - #runtime_mod_path #native_call_generator_ident :: - <#runtime, __SR_API_BLOCK__ #(, #trait_generic_arguments )*> ( - #( #param_tuple_access ),* - ) - }), - context, - recorder, - ) - } + let res = (|| { + let version = #crate_::CallApiAt::<__SR_API_BLOCK__>::runtime_version_at( + self.call, + at, + )?; + + let params = #crate_::CallApiAtParams { + at, + function: (*fn_name)(version), + arguments: params, + overlayed_changes: &self.changes, + storage_transaction_cache: &self.storage_transaction_cache, + context, + recorder: &self.recorder, + }; + + #crate_::CallApiAt::<__SR_API_BLOCK__>::call_api_at( + self.call, + params, ) - } - ) - }; + })(); + + self.commit_or_rollback(std::result::Result::is_ok(&res)); + + res + } + }); - let mut input = fold::fold_impl_item_method(self, input); - // We need to set the block, after we modified the rest of the ast, otherwise we would - // modify our generated block as well. - input.block = block; input } +} + +impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { + fn fold_type_path(&mut self, input: TypePath) -> TypePath { + let new_ty_path = + if input == *self.runtime_block { parse_quote!(__SR_API_BLOCK__) } else { input }; + + fold::fold_type_path(self, new_ty_path) + } fn fold_item_impl(&mut self, mut input: ItemImpl) -> ItemImpl { // All this `UnwindSafe` magic below here is required for this rust bug: @@ -562,6 +502,8 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { #crate_::StateBackend<#crate_::HashFor<__SR_API_BLOCK__>> }); + where_clause.predicates.push(parse_quote! { &'static RuntimeApiImplCall: Send }); + // Require that all types used in the function signatures are unwind safe. extract_all_signature_types(&input.items).iter().for_each(|i| { where_clause.predicates.push(parse_quote! { @@ -588,48 +530,58 @@ fn generate_api_impl_for_runtime_api(impls: &[ItemImpl]) -> Result let mut result = Vec::with_capacity(impls.len()); for impl_ in impls { - let impl_trait_path = extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?; - let impl_trait = &impl_trait_path - .segments - .last() - .ok_or_else(|| Error::new(impl_trait_path.span(), "Empty trait path not possible!"))? - .clone(); + let impl_trait_path = extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?; let runtime_block = extract_block_type_from_trait_path(impl_trait_path)?; - let runtime_type = &impl_.self_ty; let mut runtime_mod_path = extend_with_runtime_decl_path(impl_trait_path.clone()); // remove the trait to get just the module path runtime_mod_path.segments.pop(); - let trait_generic_arguments = match impl_trait.arguments { - PathArguments::Parenthesized(_) | PathArguments::None => vec![], - PathArguments::AngleBracketed(ref b) => b.args.iter().cloned().collect(), - }; - - let mut visitor = ApiRuntimeImplToApiRuntimeApiImpl { - runtime_block, - runtime_mod_path: &runtime_mod_path, - runtime_type: &*runtime_type, - trait_generic_arguments: &trait_generic_arguments, - impl_trait: &impl_trait.ident, - }; + let processed_impl = + ApiRuntimeImplToApiRuntimeApiImpl { runtime_block }.process(impl_.clone()); - result.push(visitor.fold_item_impl(impl_.clone())); + result.push(processed_impl); } Ok(quote!( #( #result )* )) } +fn populate_runtime_api_versions( + result: &mut Vec, + sections: &mut Vec, + attrs: Vec, + id: Path, + version: TokenStream, + crate_access: &TokenStream, +) { + result.push(quote!( + #( #attrs )* + (#id, #version) + )); + + sections.push(quote!( + #( #attrs )* + const _: () = { + // All sections with the same name are going to be merged by concatenation. + #[cfg(not(feature = "std"))] + #[link_section = "runtime_apis"] + static SECTION_CONTENTS: [u8; 12] = #crate_access::serialize_runtime_api_info(#id, #version); + }; + )); +} + /// Generates `RUNTIME_API_VERSIONS` that holds all version information about the implemented /// runtime apis. fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result { - let mut result = Vec::with_capacity(impls.len()); - let mut sections = Vec::with_capacity(impls.len()); + let mut result = Vec::::with_capacity(impls.len()); + let mut sections = Vec::::with_capacity(impls.len()); let mut processed_traits = HashSet::new(); let c = generate_crate_access(HIDDEN_INCLUDES_ID); for impl_ in impls { + let api_ver = extract_api_version(&impl_.attrs, impl_.span())?.map(|a| a as u32); + let mut path = extend_with_runtime_decl_path( - extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(), + extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?.clone(), ); // Remove the trait let trait_ = path @@ -650,23 +602,11 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result { } let id: Path = parse_quote!( #path ID ); - let version: Path = parse_quote!( #path VERSION ); + let version = quote!( #path VERSION ); let attrs = filter_cfg_attrs(&impl_.attrs); - result.push(quote!( - #( #attrs )* - (#id, #version) - )); - - sections.push(quote!( - #( #attrs )* - const _: () = { - // All sections with the same name are going to be merged by concatenation. - #[cfg(not(feature = "std"))] - #[link_section = "runtime_apis"] - static SECTION_CONTENTS: [u8; 12] = #c::serialize_runtime_api_info(#id, #version); - }; - )); + let api_ver = api_ver.map(|a| quote!( #a )).unwrap_or_else(|| version); + populate_runtime_api_versions(&mut result, &mut sections, attrs, id, api_ver, &c) } Ok(quote!( @@ -718,7 +658,34 @@ fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result { // Filters all attributes except the cfg ones. fn filter_cfg_attrs(attrs: &[Attribute]) -> Vec { - attrs.into_iter().filter(|a| a.path.is_ident("cfg")).cloned().collect() + attrs.iter().filter(|a| a.path.is_ident("cfg")).cloned().collect() +} + +// Extracts the value of `API_VERSION_ATTRIBUTE` and handles errors. +// Returns: +// - Err if the version is malformed +// - Some(u64) if the version is set +// - None if the version is not set (this is valid). +fn extract_api_version(attrs: &Vec, span: Span) -> Result> { + // First fetch all `API_VERSION_ATTRIBUTE` values (should be only one) + let api_ver = attrs + .iter() + .filter(|a| a.path.is_ident(API_VERSION_ATTRIBUTE)) + .collect::>(); + + if api_ver.len() > 1 { + return Err(Error::new( + span, + format!( + "Found multiple #[{}] attributes for an API implementation. \ + Each runtime API can have only one version.", + API_VERSION_ATTRIBUTE + ), + )) + } + + // Parse the runtime version if there exists one. + api_ver.first().map(|v| parse_runtime_api_version(v)).transpose() } #[cfg(test)] diff --git a/primitives/api/proc-macro/src/lib.rs b/primitives/api/proc-macro/src/lib.rs index b8731d70ca3cf..31636b8e2d545 100644 --- a/primitives/api/proc-macro/src/lib.rs +++ b/primitives/api/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,7 @@ use proc_macro::TokenStream; +mod common; mod decl_runtime_apis; mod impl_runtime_apis; mod mock_impl_runtime_apis; diff --git a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs index 77f8a07f85c48..e43a302e18923 100644 --- a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,7 @@ use crate::utils::{ extract_block_type_from_trait_path, extract_impl_trait, extract_parameter_names_types_and_borrows, generate_crate_access, generate_hidden_includes, - generate_method_runtime_api_impl_name, return_type_extract_type, AllowSelfRefInParameters, - RequireQualifiedTraitPath, + return_type_extract_type, AllowSelfRefInParameters, RequireQualifiedTraitPath, }; use proc_macro2::{Span, TokenStream}; @@ -31,7 +30,7 @@ use syn::{ parse::{Error, Parse, ParseStream, Result}, parse_macro_input, parse_quote, spanned::Spanned, - Attribute, Ident, ItemImpl, Pat, Type, TypePath, + Attribute, ItemImpl, Pat, Type, TypePath, }; /// Unique identifier used to make the hidden includes unique for this macro. @@ -40,7 +39,7 @@ const HIDDEN_INCLUDES_ID: &str = "MOCK_IMPL_RUNTIME_APIS"; /// The `advanced` attribute. /// /// If this attribute is given to a function, the function gets access to the `BlockId` as first -/// parameter and needs to return a `Result` with the appropiate error type. +/// parameter and needs to return a `Result` with the appropriate error type. const ADVANCED_ATTRIBUTE: &str = "advanced"; /// The structure used for parsing the runtime api implementations. @@ -105,7 +104,9 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result Option<#crate_::StorageProof> { + fn extract_proof( + &mut self, + ) -> Option<#crate_::StorageProof> { unimplemented!("`extract_proof` not implemented for runtime api mocks") } @@ -116,10 +117,6 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result, - #crate_::NumberFor<#block_type>, - >>, _: <#block_type as #crate_::BlockT>::Hash, ) -> std::result::Result< #crate_::StorageChanges, @@ -130,34 +127,63 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result for #self_ty { - fn Core_version_runtime_api_impl( + fn __runtime_api_internal_call_api_at( &self, _: &#crate_::BlockId<#block_type>, _: #crate_::ExecutionContext, - _: Option<()>, - _: Vec, - ) -> std::result::Result<#crate_::NativeOrEncoded<#crate_::RuntimeVersion>, #crate_::ApiError> { - unimplemented!("Not required for testing!") + _: std::vec::Vec, + _: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, + ) -> std::result::Result, #crate_::ApiError> { + unimplemented!("`__runtime_api_internal_call_api_at` not implemented for runtime api mocks") } - fn Core_execute_block_runtime_api_impl( + fn version( + &self, + _: &#crate_::BlockId<#block_type>, + ) -> std::result::Result<#crate_::RuntimeVersion, #crate_::ApiError> { + unimplemented!("`Core::version` not implemented for runtime api mocks") + } + + fn version_with_context( + &self, + _: &#crate_::BlockId<#block_type>, + _: #crate_::ExecutionContext, + ) -> std::result::Result<#crate_::RuntimeVersion, #crate_::ApiError> { + unimplemented!("`Core::version` not implemented for runtime api mocks") + } + + fn execute_block( + &self, + _: &#crate_::BlockId<#block_type>, + _: #block_type, + ) -> std::result::Result<(), #crate_::ApiError> { + unimplemented!("`Core::execute_block` not implemented for runtime api mocks") + } + + fn execute_block_with_context( &self, _: &#crate_::BlockId<#block_type>, _: #crate_::ExecutionContext, - _: Option<#block_type>, - _: Vec, - ) -> std::result::Result<#crate_::NativeOrEncoded<()>, #crate_::ApiError> { - unimplemented!("Not required for testing!") + _: #block_type, + ) -> std::result::Result<(), #crate_::ApiError> { + unimplemented!("`Core::execute_block` not implemented for runtime api mocks") } - fn Core_initialize_block_runtime_api_impl( + fn initialize_block( + &self, + _: &#crate_::BlockId<#block_type>, + _: &<#block_type as #crate_::BlockT>::Header, + ) -> std::result::Result<(), #crate_::ApiError> { + unimplemented!("`Core::initialize_block` not implemented for runtime api mocks") + } + + fn initialize_block_with_context( &self, _: &#crate_::BlockId<#block_type>, _: #crate_::ExecutionContext, - _: Option<&<#block_type as #crate_::BlockT>::Header>, - _: Vec, - ) -> std::result::Result<#crate_::NativeOrEncoded<()>, #crate_::ApiError> { - unimplemented!("Not required for testing!") + _: &<#block_type as #crate_::BlockT>::Header, + ) -> std::result::Result<(), #crate_::ApiError> { + unimplemented!("`Core::initialize_block` not implemented for runtime api mocks") } } )) @@ -220,14 +246,53 @@ fn get_at_param_name( } } -/// Auxialiry structure to fold a runtime api trait implementation into the expected format. +/// Auxiliary structure to fold a runtime api trait implementation into the expected format. /// /// This renames the methods, changes the method parameters and extracts the error type. struct FoldRuntimeApiImpl<'a> { /// The block type that is being used. block_type: &'a TypePath, - /// The identifier of the trait being implemented. - impl_trait: &'a Ident, +} + +impl<'a> FoldRuntimeApiImpl<'a> { + /// Process the given [`syn::ItemImpl`]. + fn process(mut self, impl_item: syn::ItemImpl) -> syn::ItemImpl { + let mut impl_item = self.fold_item_impl(impl_item); + + let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); + + // We also need to overwrite all the `_with_context` methods. To do this, + // we clone all methods and add them again with the new name plus one more argument. + impl_item.items.extend(impl_item.items.clone().into_iter().filter_map(|i| { + if let syn::ImplItem::Method(mut m) = i { + m.sig.ident = quote::format_ident!("{}_with_context", m.sig.ident); + m.sig.inputs.insert(2, parse_quote!( _: #crate_::ExecutionContext )); + + Some(m.into()) + } else { + None + } + })); + + let block_type = self.block_type; + + impl_item.items.push(parse_quote! { + fn __runtime_api_internal_call_api_at( + &self, + _: &#crate_::BlockId<#block_type>, + _: #crate_::ExecutionContext, + _: std::vec::Vec, + _: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, + ) -> std::result::Result, #crate_::ApiError> { + unimplemented!( + "`__runtime_api_internal_call_api_at` not implemented for runtime api mocks. \ + Calling deprecated methods is not supported by mocked runtime api." + ) + } + }); + + impl_item + } } impl<'a> Fold for FoldRuntimeApiImpl<'a> { @@ -281,14 +346,9 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { input.sig.inputs = parse_quote! { &self, #at_param_name: #block_id_type, - _: #crate_::ExecutionContext, - ___params___sp___api___: Option<( #( #param_types ),* )>, - _: Vec, + #( #param_names: #param_types ),* }; - input.sig.ident = - generate_method_runtime_api_impl_name(&self.impl_trait, &input.sig.ident); - // When using advanced, the user needs to declare the correct return type on its own, // otherwise do it for the user. if !is_advanced { @@ -296,7 +356,7 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { // Generate the correct return type. input.sig.output = parse_quote!( - -> std::result::Result<#crate_::NativeOrEncoded<#ret_type>, #crate_::ApiError> + -> std::result::Result<#ret_type, #crate_::ApiError> ); } @@ -308,7 +368,7 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { quote! { let __fn_implementation__ = move || #orig_block; - Ok(#crate_::NativeOrEncoded::Native(__fn_implementation__())) + Ok(__fn_implementation__()) } }; @@ -318,9 +378,6 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { // Get the error to the user (if we have one). #( #errors )* - let (#( #param_names ),*) = ___params___sp___api___ - .expect("Mocked runtime apis don't support calling deprecated api versions"); - #construct_return_value } ) @@ -354,12 +411,7 @@ fn generate_runtime_api_impls(impls: &[ItemImpl]) -> Result> = None; for impl_ in impls { - let impl_trait_path = extract_impl_trait(&impl_, RequireQualifiedTraitPath::No)?; - let impl_trait = &impl_trait_path - .segments - .last() - .ok_or_else(|| Error::new(impl_trait_path.span(), "Empty trait path not possible!"))? - .clone(); + let impl_trait_path = extract_impl_trait(impl_, RequireQualifiedTraitPath::No)?; let block_type = extract_block_type_from_trait_path(impl_trait_path)?; self_ty = match self_ty.take() { @@ -399,9 +451,7 @@ fn generate_runtime_api_impls(impls: &[ItemImpl]) -> Result Some(block_type.clone()), }; - let mut visitor = FoldRuntimeApiImpl { block_type, impl_trait: &impl_trait.ident }; - - result.push(visitor.fold_item_impl(impl_.clone())); + result.push(FoldRuntimeApiImpl { block_type }.process(impl_.clone())); } Ok(GeneratedRuntimeApiImpls { diff --git a/primitives/api/proc-macro/src/utils.rs b/primitives/api/proc-macro/src/utils.rs index a3f21638751e9..2ccd050cfb151 100644 --- a/primitives/api/proc-macro/src/utils.rs +++ b/primitives/api/proc-macro/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,16 +18,18 @@ use proc_macro2::{Span, TokenStream}; use syn::{ - parse_quote, spanned::Spanned, token::And, Error, FnArg, GenericArgument, Ident, ImplItem, - ItemImpl, Pat, Path, PathArguments, Result, ReturnType, Signature, Type, TypePath, + parse_quote, spanned::Spanned, token::And, Attribute, Error, FnArg, GenericArgument, Ident, + ImplItem, ItemImpl, Pat, Path, PathArguments, Result, ReturnType, Signature, Type, TypePath, }; -use quote::quote; +use quote::{format_ident, quote}; use std::env; use proc_macro_crate::{crate_name, FoundCrate}; +use crate::common::API_VERSION_ATTRIBUTE; + fn generate_hidden_includes_mod_name(unique_id: &'static str) -> Ident { Ident::new(&format!("sp_api_hidden_includes_{}", unique_id), Span::call_site()) } @@ -61,17 +63,11 @@ pub fn generate_crate_access(unique_id: &'static str) -> TokenStream { let mod_name = generate_hidden_includes_mod_name(unique_id); quote!( self::#mod_name::sp_api ) } - .into() } /// Generates the name of the module that contains the trait declaration for the runtime. pub fn generate_runtime_mod_name_for_trait(trait_: &Ident) -> Ident { - Ident::new(&format!("runtime_decl_for_{}", trait_.to_string()), Span::call_site()) -} - -/// Generates a name for a method that needs to be implemented in the runtime for the client side. -pub fn generate_method_runtime_api_impl_name(trait_: &Ident, method: &Ident) -> Ident { - Ident::new(&format!("{}_{}_runtime_api_impl", trait_, method), Span::call_site()) + Ident::new(&format!("runtime_decl_for_{}", trait_), Span::call_site()) } /// Get the type of a `syn::ReturnType`. @@ -167,19 +163,9 @@ pub fn extract_parameter_names_types_and_borrows( Ok(result) } -/// Generates the name for the native call generator function. -pub fn generate_native_call_generator_fn_name(fn_name: &Ident) -> Ident { - Ident::new(&format!("{}_native_call_generator", fn_name.to_string()), Span::call_site()) -} - -/// Generates the name for the call api at function. -pub fn generate_call_api_at_fn_name(fn_name: &Ident) -> Ident { - Ident::new(&format!("{}_call_api_at", fn_name.to_string()), Span::call_site()) -} - /// Prefix the given function with the trait name. pub fn prefix_function_with_trait(trait_: &Ident, function: &F) -> String { - format!("{}_{}", trait_.to_string(), function.to_string()) + format!("{}_{}", trait_, function.to_string()) } /// Extract all types that appear in signatures in the given `ImplItem`'s. @@ -250,10 +236,7 @@ pub enum RequireQualifiedTraitPath { } /// Extract the trait that is implemented by the given `ItemImpl`. -pub fn extract_impl_trait<'a>( - impl_: &'a ItemImpl, - require: RequireQualifiedTraitPath, -) -> Result<&'a Path> { +pub fn extract_impl_trait(impl_: &ItemImpl, require: RequireQualifiedTraitPath) -> Result<&Path> { impl_ .trait_ .as_ref() @@ -271,3 +254,23 @@ pub fn extract_impl_trait<'a>( } }) } + +/// Parse the given attribute as `API_VERSION_ATTRIBUTE`. +pub fn parse_runtime_api_version(version: &Attribute) -> Result { + let version = version.parse_args::().map_err(|_| { + Error::new( + version.span(), + &format!( + "Unexpected `{api_version}` attribute. The supported format is `{api_version}(1)`", + api_version = API_VERSION_ATTRIBUTE + ), + ) + })?; + + version.base10_parse() +} + +// Each versioned trait is named 'ApiNameVN' where N is the specific version. E.g. ParachainHostV2 +pub fn versioned_trait_name(trait_ident: &Ident, version: u64) -> Ident { + format_ident!("{}V{}", trait_ident, version) +} diff --git a/primitives/api/src/lib.rs b/primitives/api/src/lib.rs index 82954d193e605..91d4b07a1cefc 100644 --- a/primitives/api/src/lib.rs +++ b/primitives/api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -78,13 +78,13 @@ pub use hash_db::Hasher; #[doc(hidden)] #[cfg(not(feature = "std"))] pub use sp_core::to_substrate_wasm_fn_return_value; -#[doc(hidden)] -#[cfg(feature = "std")] -pub use sp_core::NativeOrEncoded; use sp_core::OpaqueMetadata; #[doc(hidden)] pub use sp_core::{offchain, ExecutionContext}; #[doc(hidden)] +#[cfg(feature = "std")] +pub use sp_runtime::StateVersion; +#[doc(hidden)] pub use sp_runtime::{ generic::BlockId, traits::{ @@ -97,16 +97,15 @@ pub use sp_runtime::{ #[doc(hidden)] #[cfg(feature = "std")] pub use sp_state_machine::{ - Backend as StateBackend, ChangesTrieState, InMemoryBackend, OverlayedChanges, StorageProof, + backend::AsTrieBackend, Backend as StateBackend, InMemoryBackend, OverlayedChanges, + StorageProof, TrieBackend, TrieBackendBuilder, }; -#[cfg(feature = "std")] -use sp_std::result; #[doc(hidden)] pub use sp_std::{mem, slice}; #[doc(hidden)] pub use sp_version::{create_apis_vec, ApiId, ApisVec, RuntimeVersion}; #[cfg(feature = "std")] -use std::{cell::RefCell, panic::UnwindSafe}; +use std::cell::RefCell; /// Maximum nesting level for extrinsics. pub const MAX_EXTRINSIC_DEPTH: u32 = 256; @@ -185,6 +184,56 @@ pub const MAX_EXTRINSIC_DEPTH: u32 = 256; /// To check if a given runtime implements a runtime api trait, the `RuntimeVersion` has the /// function `has_api()`. Also the `ApiExt` provides a function `has_api(at: &BlockId)` /// to check if the runtime at the given block id implements the requested runtime api trait. +/// +/// # Declaring multiple api versions +/// +/// Optionally multiple versions of the same api can be declared. This is useful for +/// development purposes. For example you want to have a testing version of the api which is +/// available only on a testnet. You can define one stable and one development version. This +/// can be done like this: +/// ```rust +/// sp_api::decl_runtime_apis! { +/// /// Declare the api trait. +/// #[api_version(2)] +/// pub trait Balance { +/// /// Get the balance. +/// fn get_balance() -> u64; +/// /// Set the balance. +/// fn set_balance(val: u64); +/// /// Transfer the balance to another user id +/// #[api_version(3)] +/// fn transfer_balance(uid: u64); +/// } +/// } +/// +/// # fn main() {} +/// ``` +/// The example above defines two api versions - 2 and 3. Version 2 contains `get_balance` and +/// `set_balance`. Version 3 additionally contains `transfer_balance`, which is not available +/// in version 2. Version 2 in this case is considered the default/base version of the api. +/// More than two versions can be defined this way. For example: +/// ```rust +/// sp_api::decl_runtime_apis! { +/// /// Declare the api trait. +/// #[api_version(2)] +/// pub trait Balance { +/// /// Get the balance. +/// fn get_balance() -> u64; +/// /// Set the balance. +/// fn set_balance(val: u64); +/// /// Transfer the balance to another user id +/// #[api_version(3)] +/// fn transfer_balance(uid: u64); +/// /// Clears the balance +/// #[api_version(4)] +/// fn clear_balance(); +/// } +/// } +/// +/// # fn main() {} +/// ``` +/// Note that the latest version (4 in our example above) always contains all methods from all +/// the versions before. pub use sp_api_proc_macro::decl_runtime_apis; /// Tags given trait implementations as runtime apis. @@ -269,10 +318,27 @@ pub use sp_api_proc_macro::decl_runtime_apis; /// // Here we are exposing the runtime api versions. /// apis: RUNTIME_API_VERSIONS, /// transaction_version: 1, +/// state_version: 1, /// }; /// /// # fn main() {} /// ``` +/// +/// # Implementing specific api version +/// +/// If `decl_runtime_apis!` declares multiple versions for an api `impl_runtime_apis!` +/// should specify which version it implements by adding `api_version` attribute to the +/// `impl` block. If omitted - the base/default version is implemented. Here is an example: +/// ```ignore +/// sp_api::impl_runtime_apis! { +/// #[api_version(3)] +/// impl self::Balance for Runtime { +/// // implementation +/// } +/// } +/// ``` +/// In this case `Balance` api version 3 is being implemented for `Runtime`. The `impl` block +/// must contain all methods declared in version 3 and below. pub use sp_api_proc_macro::impl_runtime_apis; /// Mocks given trait implementations as runtime apis. @@ -338,15 +404,13 @@ pub use sp_api_proc_macro::impl_runtime_apis; /// using the `advanced` attribute, the macro expects that the first parameter of the function /// is this `at` parameter. Besides that the macro also doesn't do the automatic return value /// rewrite, which means that full return value must be specified. The full return value is -/// constructed like [`Result`]`<`[`NativeOrEncoded`](sp_api::NativeOrEncoded)`, -/// Error>` while `ReturnValue` being the return value that is specified in the trait -/// declaration. +/// constructed like [`Result`]`<, Error>` while `ReturnValue` being the return +/// value that is specified in the trait declaration. /// /// ## Example /// ```rust /// # use sp_runtime::{traits::Block as BlockT, generic::BlockId}; /// # use sp_test_primitives::Block; -/// # use sp_core::NativeOrEncoded; /// # use codec; /// # /// # sp_api::decl_runtime_apis! { @@ -365,13 +429,13 @@ pub use sp_api_proc_macro::impl_runtime_apis; /// sp_api::mock_impl_runtime_apis! { /// impl Balance for MockApi { /// #[advanced] -/// fn get_balance(&self, at: &BlockId) -> Result, sp_api::ApiError> { +/// fn get_balance(&self, at: &BlockId) -> Result { /// println!("Being called at: {}", at); /// /// Ok(self.balance.into()) /// } /// #[advanced] -/// fn set_balance(at: &BlockId, val: u64) -> Result, sp_api::ApiError> { +/// fn set_balance(at: &BlockId, val: u64) -> Result<(), sp_api::ApiError> { /// if let BlockId::Number(1) = at { /// println!("Being called to set balance to: {}", val); /// } @@ -387,21 +451,19 @@ pub use sp_api_proc_macro::mock_impl_runtime_apis; /// A type that records all accessed trie nodes and generates a proof out of it. #[cfg(feature = "std")] -pub type ProofRecorder = sp_state_machine::ProofRecorder<::Hash>; +pub type ProofRecorder = sp_trie::recorder::Recorder>; /// A type that is used as cache for the storage transactions. #[cfg(feature = "std")] pub type StorageTransactionCache = sp_state_machine::StorageTransactionCache< >>::Transaction, HashFor, - NumberFor, >; #[cfg(feature = "std")] pub type StorageChanges = sp_state_machine::StorageChanges< >>::Transaction, HashFor, - NumberFor, >; /// Extract the state backend type for a type that implements `ProvideRuntimeApi`. @@ -421,7 +483,7 @@ pub trait ConstructRuntimeApi> { type RuntimeApi: ApiExt; /// Construct an instance of the runtime api. - fn construct_runtime_api<'a>(call: &'a C) -> ApiRef<'a, Self::RuntimeApi>; + fn construct_runtime_api(call: &C) -> ApiRef; } /// Init the [`RuntimeLogger`](sp_runtime::runtime_logger::RuntimeLogger). @@ -453,6 +515,8 @@ pub enum ApiError { #[source] error: codec::Error, }, + #[error("The given `StateBackend` isn't a `TrieBackend`.")] + StateBackendIsNotTrie, #[error(transparent)] Application(#[from] Box), } @@ -514,7 +578,6 @@ pub trait ApiExt { fn into_storage_changes( &self, backend: &Self::StateBackend, - changes_trie_state: Option<&ChangesTrieState, NumberFor>>, parent_hash: Block::Hash, ) -> Result, String> where @@ -523,16 +586,11 @@ pub trait ApiExt { /// Parameters for [`CallApiAt::call_api_at`]. #[cfg(feature = "std")] -pub struct CallApiAtParams<'a, Block: BlockT, NC, Backend: StateBackend>> { +pub struct CallApiAtParams<'a, Block: BlockT, Backend: StateBackend>> { /// The block id that determines the state that should be setup when calling the function. pub at: &'a BlockId, /// The name of the function that should be called. pub function: &'static str, - /// An optional native call that calls the `function`. This is an optimization to call into a - /// native runtime without requiring to encode/decode the parameters. The native runtime can - /// still be called when this value is `None`, we then just fallback to encoding/decoding the - /// parameters. - pub native_call: Option, /// The encoded arguments of the function. pub arguments: Vec, /// The overlayed changes that are on top of the state. @@ -549,21 +607,20 @@ pub struct CallApiAtParams<'a, Block: BlockT, NC, Backend: StateBackend { /// The state backend that is used to store the block states. - type StateBackend: StateBackend>; + type StateBackend: StateBackend> + AsTrieBackend>; /// Calls the given api function with the given encoded arguments at the given block and returns /// the encoded result. - fn call_api_at< - 'a, - R: Encode + Decode + PartialEq, - NC: FnOnce() -> result::Result + UnwindSafe, - >( + fn call_api_at( &self, - params: CallApiAtParams<'a, Block, NC, Self::StateBackend>, - ) -> Result, ApiError>; + params: CallApiAtParams, + ) -> Result, ApiError>; /// Returns the runtime version at the given block. fn runtime_version_at(&self, at: &BlockId) -> Result; + + /// Get the state `at` the given block. + fn state_at(&self, at: &BlockId) -> Result; } /// Auxiliary wrapper that holds an api instance and binds it to the given lifetime. @@ -604,7 +661,7 @@ pub trait ProvideRuntimeApi { /// call to an api function, will `commit` its changes to an internal buffer. Otherwise, /// the modifications will be `discarded`. The modifications will not be applied to the /// storage, even on a `commit`. - fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api>; + fn runtime_api(&self) -> ApiRef; } /// Something that provides information about a runtime api. @@ -644,8 +701,6 @@ pub const fn serialize_runtime_api_info(id: [u8; 8], version: u32) -> [u8; RUNTI /// Deserialize the runtime API info serialized by [`serialize_runtime_api_info`]. pub fn deserialize_runtime_api_info(bytes: [u8; RUNTIME_API_INFO_SIZE]) -> ([u8; 8], u32) { - use sp_std::convert::TryInto; - let id: [u8; 8] = bytes[0..8] .try_into() .expect("the source slice size is equal to the dest array length; qed"); @@ -659,53 +714,13 @@ pub fn deserialize_runtime_api_info(bytes: [u8; RUNTIME_API_INFO_SIZE]) -> ([u8; (id, version) } -#[derive(codec::Encode, codec::Decode)] -pub struct OldRuntimeVersion { - pub spec_name: RuntimeString, - pub impl_name: RuntimeString, - pub authoring_version: u32, - pub spec_version: u32, - pub impl_version: u32, - pub apis: ApisVec, -} - -impl From for RuntimeVersion { - fn from(x: OldRuntimeVersion) -> Self { - Self { - spec_name: x.spec_name, - impl_name: x.impl_name, - authoring_version: x.authoring_version, - spec_version: x.spec_version, - impl_version: x.impl_version, - apis: x.apis, - transaction_version: 1, - } - } -} - -impl From for OldRuntimeVersion { - fn from(x: RuntimeVersion) -> Self { - Self { - spec_name: x.spec_name, - impl_name: x.impl_name, - authoring_version: x.authoring_version, - spec_version: x.spec_version, - impl_version: x.impl_version, - apis: x.apis, - } - } -} - decl_runtime_apis! { /// The `Core` runtime api that every Substrate runtime needs to implement. #[core_trait] - #[api_version(3)] + #[api_version(4)] pub trait Core { /// Returns the version of the runtime. fn version() -> RuntimeVersion; - /// Returns the version of the runtime. - #[changed_in(3)] - fn version() -> OldRuntimeVersion; /// Execute the given block. fn execute_block(block: Block); /// Initialize a block with the given header. diff --git a/primitives/api/test/Cargo.toml b/primitives/api/test/Cargo.toml index b78c9abb80dc6..edc0d43e91437 100644 --- a/primitives/api/test/Cargo.toml +++ b/primitives/api/test/Cargo.toml @@ -2,10 +2,10 @@ name = "sp-api-test" version = "2.0.1" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" publish = false -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" [package.metadata.docs.rs] @@ -14,21 +14,21 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-api = { version = "4.0.0-dev", path = "../" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -sp-version = { version = "4.0.0-dev", path = "../../version" } -sp-tracing = { version = "4.0.0-dev", path = "../../tracing" } -sp-runtime = { version = "4.0.0-dev", path = "../../runtime" } +sp-version = { version = "5.0.0", path = "../../version" } +sp-tracing = { version = "6.0.0", path = "../../tracing" } +sp-runtime = { version = "7.0.0", path = "../../runtime" } sp-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } -codec = { package = "parity-scale-codec", version = "2.0.0" } -sp-state-machine = { version = "0.10.0-dev", path = "../../state-machine" } -trybuild = "1.0.43" -rustversion = "1.0.0" +codec = { package = "parity-scale-codec", version = "3.0.0" } +sp-state-machine = { version = "0.13.0", path = "../../state-machine" } +trybuild = "1.0.60" +rustversion = "1.0.6" [dev-dependencies] criterion = "0.3.0" -futures = "0.3.9" -log = "0.4.14" -sp-core = { version = "4.0.0-dev", path = "../../core" } +futures = "0.3.21" +log = "0.4.17" +sp-core = { version = "7.0.0", path = "../../core" } [[bench]] name = "bench" diff --git a/primitives/api/test/benches/bench.rs b/primitives/api/test/benches/bench.rs index b3d96a2db6a56..2445a5c07f09e 100644 --- a/primitives/api/test/benches/bench.rs +++ b/primitives/api/test/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,14 +27,14 @@ fn sp_api_benchmark(c: &mut Criterion) { c.bench_function("add one with same runtime api", |b| { let client = substrate_test_runtime_client::new(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); + let block_id = BlockId::Hash(client.chain_info().best_hash); b.iter(|| runtime_api.benchmark_add_one(&block_id, &1)) }); c.bench_function("add one with recreating runtime api", |b| { let client = substrate_test_runtime_client::new(); - let block_id = BlockId::Number(client.chain_info().best_number); + let block_id = BlockId::Hash(client.chain_info().best_hash); b.iter(|| client.runtime_api().benchmark_add_one(&block_id, &1)) }); @@ -42,7 +42,7 @@ fn sp_api_benchmark(c: &mut Criterion) { c.bench_function("vector add one with same runtime api", |b| { let client = substrate_test_runtime_client::new(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); + let block_id = BlockId::Hash(client.chain_info().best_hash); let data = vec![0; 1000]; b.iter_with_large_drop(|| runtime_api.benchmark_vector_add_one(&block_id, &data)) @@ -50,7 +50,7 @@ fn sp_api_benchmark(c: &mut Criterion) { c.bench_function("vector add one with recreating runtime api", |b| { let client = substrate_test_runtime_client::new(); - let block_id = BlockId::Number(client.chain_info().best_number); + let block_id = BlockId::Hash(client.chain_info().best_hash); let data = vec![0; 1000]; b.iter_with_large_drop(|| client.runtime_api().benchmark_vector_add_one(&block_id, &data)) @@ -60,7 +60,7 @@ fn sp_api_benchmark(c: &mut Criterion) { let client = TestClientBuilder::new() .set_execution_strategy(ExecutionStrategy::AlwaysWasm) .build(); - let block_id = BlockId::Number(client.chain_info().best_number); + let block_id = BlockId::Hash(client.chain_info().best_hash); b.iter(|| client.runtime_api().benchmark_indirect_call(&block_id).unwrap()) }); @@ -68,7 +68,7 @@ fn sp_api_benchmark(c: &mut Criterion) { let client = TestClientBuilder::new() .set_execution_strategy(ExecutionStrategy::AlwaysWasm) .build(); - let block_id = BlockId::Number(client.chain_info().best_number); + let block_id = BlockId::Hash(client.chain_info().best_hash); b.iter(|| client.runtime_api().benchmark_direct_call(&block_id).unwrap()) }); } diff --git a/primitives/api/test/tests/decl_and_impl.rs b/primitives/api/test/tests/decl_and_impl.rs index 8d1b04a37a9fa..42628830cc7fa 100644 --- a/primitives/api/test/tests/decl_and_impl.rs +++ b/primitives/api/test/tests/decl_and_impl.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ use sp_api::{ decl_runtime_apis, impl_runtime_apis, mock_impl_runtime_apis, ApiError, ApiExt, RuntimeApiInfo, }; -use sp_core::NativeOrEncoded; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, GetNodeBlockType}, @@ -47,6 +46,15 @@ decl_runtime_apis! { #[changed_in(2)] fn same_name() -> String; } + + #[api_version(2)] + pub trait ApiWithMultipleVersions { + fn stable_one(data: u64); + #[api_version(3)] + fn new_one(); + #[api_version(4)] + fn glory_one(); + } } impl_runtime_apis! { @@ -72,6 +80,13 @@ impl_runtime_apis! { fn same_name() {} } + #[api_version(3)] + impl self::ApiWithMultipleVersions for Runtime { + fn stable_one(_: u64) {} + + fn new_one() {} + } + impl sp_api::Core for Runtime { fn version() -> sp_version::RuntimeVersion { unimplemented!() @@ -104,22 +119,12 @@ mock_impl_runtime_apis! { } #[advanced] - fn same_name(_: &BlockId) -> - Result< - NativeOrEncoded<()>, - ApiError - > - { + fn same_name(_: &BlockId) -> Result<(), ApiError> { Ok(().into()) } #[advanced] - fn wild_card(at: &BlockId, _: u32) -> - Result< - NativeOrEncoded<()>, - ApiError - > - { + fn wild_card(at: &BlockId, _: u32) -> Result<(), ApiError> { if let BlockId::Number(1337) = at { // yeah Ok(().into()) @@ -176,6 +181,9 @@ fn check_runtime_api_info() { &runtime_decl_for_ApiWithCustomVersion::ID, ); assert_eq!(>::VERSION, 2); + + // The stable version of the API + assert_eq!(>::VERSION, 2); } fn check_runtime_api_versions_contains() { @@ -186,6 +194,9 @@ fn check_runtime_api_versions_contains() { fn check_runtime_api_versions() { check_runtime_api_versions_contains::>(); check_runtime_api_versions_contains::>(); + assert!(RUNTIME_API_VERSIONS + .iter() + .any(|v| v == &(>::ID, 3))); check_runtime_api_versions_contains::>(); } @@ -198,7 +209,7 @@ fn mock_runtime_api_has_api() { } #[test] -#[should_panic(expected = "Mocked runtime apis don't support calling deprecated api versions")] +#[should_panic(expected = "Calling deprecated methods is not supported by mocked runtime api.")] fn mock_runtime_api_panics_on_calling_old_version() { let mock = MockApi { block: None }; diff --git a/primitives/api/test/tests/runtime_calls.rs b/primitives/api/test/tests/runtime_calls.rs index 101f92fd6c7d7..2ac88c7e6c04f 100644 --- a/primitives/api/test/tests/runtime_calls.rs +++ b/primitives/api/test/tests/runtime_calls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,7 @@ use sp_state_machine::{ }; use substrate_test_runtime_client::{ prelude::*, - runtime::{Block, DecodeFails, Header, TestAPI, Transfer}, + runtime::{Block, Header, TestAPI, Transfer}, DefaultTestClientBuilderExt, TestClientBuilder, }; @@ -51,28 +51,6 @@ fn calling_wasm_runtime_function() { calling_function_with_strat(ExecutionStrategy::AlwaysWasm); } -#[test] -#[should_panic(expected = "FailedToConvertParameter { function: \"fail_convert_parameter\"")] -fn calling_native_runtime_function_with_non_decodable_parameter() { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::NativeWhenPossible) - .build(); - let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - runtime_api.fail_convert_parameter(&block_id, DecodeFails::new()).unwrap(); -} - -#[test] -#[should_panic(expected = "FailedToConvertReturnValue { function: \"fail_convert_return_value\"")] -fn calling_native_runtime_function_with_non_decodable_return_value() { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::NativeWhenPossible) - .build(); - let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - runtime_api.fail_convert_return_value(&block_id).unwrap(); -} - #[test] fn calling_native_runtime_signature_changed_function() { let client = TestClientBuilder::new() @@ -170,10 +148,8 @@ fn record_proof_works() { .build_with_longest_chain(); let block_id = BlockId::Number(client.chain_info().best_number); - let storage_root = futures::executor::block_on(longest_chain.best_chain()) - .unwrap() - .state_root() - .clone(); + let storage_root = + *futures::executor::block_on(longest_chain.best_chain()).unwrap().state_root(); let runtime_code = sp_core::traits::RuntimeCode { code_fetcher: &sp_core::traits::WrappedRuntimeCode( @@ -187,7 +163,7 @@ fn record_proof_works() { amount: 1000, nonce: 0, from: AccountKeyring::Alice.into(), - to: Default::default(), + to: AccountKeyring::Bob.into(), } .into_signed_tx(); @@ -210,8 +186,9 @@ fn record_proof_works() { WasmExecutionMethod::Interpreted, None, 8, + 2, ); - execution_proof_check_on_trie_backend::<_, u64, _, _>( + execution_proof_check_on_trie_backend( &backend, &mut overlay, &executor, diff --git a/primitives/api/test/tests/trybuild.rs b/primitives/api/test/tests/trybuild.rs index 5a6025f463af0..13af1ded7dc6b 100644 --- a/primitives/api/test/tests/trybuild.rs +++ b/primitives/api/test/tests/trybuild.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,9 +20,15 @@ use std::env; #[rustversion::attr(not(stable), ignore)] #[test] fn ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. env::set_var("SKIP_WASM_BUILD", "1"); let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/*.rs"); + t.pass("tests/ui/positive_cases/*.rs"); } diff --git a/primitives/api/test/tests/ui/changed_in_no_default_method.stderr b/primitives/api/test/tests/ui/changed_in_no_default_method.stderr index ed4c0f9088573..096b1091e6f41 100644 --- a/primitives/api/test/tests/ui/changed_in_no_default_method.stderr +++ b/primitives/api/test/tests/ui/changed_in_no_default_method.stderr @@ -1,6 +1,6 @@ error: There is no 'default' method with this name (without `changed_in` attribute). -The 'default' method is used to call into the latest implementation. - --> $DIR/changed_in_no_default_method.rs:15:6 + The 'default' method is used to call into the latest implementation. + --> tests/ui/changed_in_no_default_method.rs:15:6 | 15 | fn test(data: u64); | ^^^^ diff --git a/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.stderr b/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.stderr index b08f056b57d1c..bf201e8b55a78 100644 --- a/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.stderr +++ b/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.stderr @@ -4,4 +4,4 @@ error: No api implementation given! 17 | sp_api::impl_runtime_apis! {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `sp_api::impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr b/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr index 9dd84c24b6781..f5b6ac1da4576 100644 --- a/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr +++ b/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr @@ -1,61 +1,35 @@ error[E0053]: method `test` has an incompatible type for trait - --> $DIR/impl_incorrect_method_signature.rs:19:17 + --> tests/ui/impl_incorrect_method_signature.rs:19:17 | -13 | fn test(data: u64); - | --- type in trait -... 19 | fn test(data: String) {} | ^^^^^^ | | | expected `u64`, found struct `std::string::String` | help: change the parameter type to match the trait: `u64` | +note: type in trait + --> tests/ui/impl_incorrect_method_signature.rs:13:17 + | +13 | fn test(data: u64); + | ^^^ = note: expected fn pointer `fn(u64)` found fn pointer `fn(std::string::String)` -error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for trait - --> $DIR/impl_incorrect_method_signature.rs:17:1 - | -11 | / sp_api::decl_runtime_apis! { -12 | | pub trait Api { -13 | | fn test(data: u64); -14 | | } -15 | | } - | |_- type in trait -16 | -17 | sp_api::impl_runtime_apis! { - | -^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | _expected `u64`, found struct `std::string::String` - | | -18 | | impl self::Api for Runtime { -19 | | fn test(data: String) {} -20 | | } -... | -32 | | } -33 | | } - | |_- help: change the parameter type to match the trait: `std::option::Option` - | - = note: expected fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option, Vec<_>) -> Result<_, _>` - found fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option, Vec<_>) -> Result<_, _>` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0308]: mismatched types - --> $DIR/impl_incorrect_method_signature.rs:17:1 + --> tests/ui/impl_incorrect_method_signature.rs:19:11 | 17 | / sp_api::impl_runtime_apis! { 18 | | impl self::Api for Runtime { 19 | | fn test(data: String) {} + | | ^^^^ expected `u64`, found struct `std::string::String` 20 | | } ... | 32 | | } 33 | | } - | |_^ expected `u64`, found struct `std::string::String` + | |_- arguments to this function are incorrect | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0308]: mismatched types - --> $DIR/impl_incorrect_method_signature.rs:19:11 +note: associated function defined here + --> tests/ui/impl_incorrect_method_signature.rs:13:6 | -19 | fn test(data: String) {} - | ^^^^ expected `u64`, found struct `std::string::String` +13 | fn test(data: u64); + | ^^^^ diff --git a/primitives/api/test/tests/ui/impl_missing_version.rs b/primitives/api/test/tests/ui/impl_missing_version.rs new file mode 100644 index 0000000000000..63e0599622ac9 --- /dev/null +++ b/primitives/api/test/tests/ui/impl_missing_version.rs @@ -0,0 +1,40 @@ +use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use substrate_test_runtime_client::runtime::Block; + +struct Runtime {} +impl GetNodeBlockType for Runtime { + type NodeBlock = Block; +} + +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait Api { + fn test1(); + fn test2(); + #[api_version(3)] + fn test3(); + } +} + +sp_api::impl_runtime_apis! { + #[api_version(4)] + impl self::Api for Runtime { + fn test1() {} + fn test2() {} + fn test3() {} + } + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/impl_missing_version.stderr b/primitives/api/test/tests/ui/impl_missing_version.stderr new file mode 100644 index 0000000000000..c0abeffe0cccf --- /dev/null +++ b/primitives/api/test/tests/ui/impl_missing_version.stderr @@ -0,0 +1,14 @@ +error[E0433]: failed to resolve: could not find `ApiV4` in `runtime_decl_for_Api` + --> tests/ui/impl_missing_version.rs:21:13 + | +21 | impl self::Api for Runtime { + | ^^^ could not find `ApiV4` in `runtime_decl_for_Api` + +error[E0405]: cannot find trait `ApiV4` in module `self::runtime_decl_for_Api` + --> tests/ui/impl_missing_version.rs:21:13 + | +11 | pub trait Api { + | ------------- similarly named trait `ApiV2` defined here +... +21 | impl self::Api for Runtime { + | ^^^ help: a trait with a similar name exists: `ApiV2` diff --git a/primitives/api/test/tests/ui/invalid_api_version.rs b/primitives/api/test/tests/ui/invalid_api_version_1.rs similarity index 100% rename from primitives/api/test/tests/ui/invalid_api_version.rs rename to primitives/api/test/tests/ui/invalid_api_version_1.rs diff --git a/primitives/api/test/tests/ui/invalid_api_version.stderr b/primitives/api/test/tests/ui/invalid_api_version_1.stderr similarity index 65% rename from primitives/api/test/tests/ui/invalid_api_version.stderr rename to primitives/api/test/tests/ui/invalid_api_version_1.stderr index 7770bc70e72d6..53ffce959bb66 100644 --- a/primitives/api/test/tests/ui/invalid_api_version.stderr +++ b/primitives/api/test/tests/ui/invalid_api_version_1.stderr @@ -1,5 +1,5 @@ error: Unexpected `api_version` attribute. The supported format is `api_version(1)` - --> $DIR/invalid_api_version.rs:2:4 + --> tests/ui/invalid_api_version_1.rs:2:2 | 2 | #[api_version] - | ^^^^^^^^^^^ + | ^ diff --git a/primitives/api/test/tests/ui/invalid_api_version_2.stderr b/primitives/api/test/tests/ui/invalid_api_version_2.stderr index 7ca6a7eebe49c..0c5274d4680ff 100644 --- a/primitives/api/test/tests/ui/invalid_api_version_2.stderr +++ b/primitives/api/test/tests/ui/invalid_api_version_2.stderr @@ -1,5 +1,5 @@ error: Unexpected `api_version` attribute. The supported format is `api_version(1)` - --> $DIR/invalid_api_version_2.rs:2:4 + --> tests/ui/invalid_api_version_2.rs:2:2 | 2 | #[api_version("1")] - | ^^^^^^^^^^^ + | ^ diff --git a/primitives/api/test/tests/ui/invalid_api_version_3.stderr b/primitives/api/test/tests/ui/invalid_api_version_3.stderr index cef4763a6de96..4a34a7aa9b47a 100644 --- a/primitives/api/test/tests/ui/invalid_api_version_3.stderr +++ b/primitives/api/test/tests/ui/invalid_api_version_3.stderr @@ -1,5 +1,5 @@ error: Unexpected `api_version` attribute. The supported format is `api_version(1)` - --> $DIR/invalid_api_version_3.rs:2:4 + --> tests/ui/invalid_api_version_3.rs:2:2 | 2 | #[api_version()] - | ^^^^^^^^^^^ + | ^ diff --git a/primitives/api/test/tests/ui/invalid_api_version_4.rs b/primitives/api/test/tests/ui/invalid_api_version_4.rs new file mode 100644 index 0000000000000..37b5b6ffa25d1 --- /dev/null +++ b/primitives/api/test/tests/ui/invalid_api_version_4.rs @@ -0,0 +1,8 @@ +sp_api::decl_runtime_apis! { + pub trait Api { + #[api_version("1")] + fn test(data: u64); + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/invalid_api_version_4.stderr b/primitives/api/test/tests/ui/invalid_api_version_4.stderr new file mode 100644 index 0000000000000..57541a97f6c91 --- /dev/null +++ b/primitives/api/test/tests/ui/invalid_api_version_4.stderr @@ -0,0 +1,5 @@ +error: Unexpected `api_version` attribute. The supported format is `api_version(1)` + --> tests/ui/invalid_api_version_4.rs:3:3 + | +3 | #[api_version("1")] + | ^ diff --git a/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.rs b/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.rs new file mode 100644 index 0000000000000..b4f43cd401bba --- /dev/null +++ b/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.rs @@ -0,0 +1,9 @@ +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait Api { + #[api_version(1)] + fn test(data: u64); + } +} + +fn main() {} \ No newline at end of file diff --git a/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.stderr b/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.stderr new file mode 100644 index 0000000000000..ec4b594023a05 --- /dev/null +++ b/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.stderr @@ -0,0 +1,11 @@ +error: Method version `1` is older than (or equal to) trait version `2`.Methods can't define versions older than the trait version. + --> tests/ui/method_ver_lower_than_trait_ver.rs:4:3 + | +4 | #[api_version(1)] + | ^ + +error: Trait version is set here. + --> tests/ui/method_ver_lower_than_trait_ver.rs:2:2 + | +2 | #[api_version(2)] + | ^ diff --git a/primitives/api/test/tests/ui/missing_versioned_method.rs b/primitives/api/test/tests/ui/missing_versioned_method.rs new file mode 100644 index 0000000000000..d973a94c2101d --- /dev/null +++ b/primitives/api/test/tests/ui/missing_versioned_method.rs @@ -0,0 +1,39 @@ +use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use substrate_test_runtime_client::runtime::Block; + +struct Runtime {} +impl GetNodeBlockType for Runtime { + type NodeBlock = Block; +} + +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait Api { + fn test1(); + fn test2(); + #[api_version(3)] + fn test3(); + } +} + +sp_api::impl_runtime_apis! { + #[api_version(3)] + impl self::Api for Runtime { + fn test1() {} + fn test2() {} + } + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/missing_versioned_method.stderr b/primitives/api/test/tests/ui/missing_versioned_method.stderr new file mode 100644 index 0000000000000..e3ace7979c27e --- /dev/null +++ b/primitives/api/test/tests/ui/missing_versioned_method.stderr @@ -0,0 +1,8 @@ +error[E0046]: not all trait items implemented, missing: `test3` + --> tests/ui/missing_versioned_method.rs:21:2 + | +15 | fn test3(); + | ----------- `test3` from trait +... +21 | impl self::Api for Runtime { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `test3` in implementation diff --git a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs new file mode 100644 index 0000000000000..72358b99164d5 --- /dev/null +++ b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs @@ -0,0 +1,42 @@ +use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use substrate_test_runtime_client::runtime::Block; + +struct Runtime {} +impl GetNodeBlockType for Runtime { + type NodeBlock = Block; +} + +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait Api { + fn test1(); + fn test2(); + #[api_version(3)] + fn test3(); + #[api_version(4)] + fn test4(); + } +} + +sp_api::impl_runtime_apis! { + #[api_version(4)] + impl self::Api for Runtime { + fn test1() {} + fn test2() {} + fn test4() {} + } + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr new file mode 100644 index 0000000000000..7354fbd537fd7 --- /dev/null +++ b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr @@ -0,0 +1,8 @@ +error[E0046]: not all trait items implemented, missing: `test3` + --> tests/ui/missing_versioned_method_multiple_vers.rs:23:2 + | +15 | fn test3(); + | ----------- `test3` from trait +... +23 | impl self::Api for Runtime { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `test3` in implementation diff --git a/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs b/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs index fd654ffdc63d6..aeef40f4c77d6 100644 --- a/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs +++ b/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs @@ -12,7 +12,7 @@ struct MockApi; sp_api::mock_impl_runtime_apis! { impl Api for MockApi { #[advanced] - fn test(&self, _: BlockId) -> Result, ApiError> { + fn test(&self, _: BlockId) -> Result<(), ApiError> { Ok(().into()) } } diff --git a/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr b/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr index 47cd9e01d910f..3b3c4e94c3121 100644 --- a/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr +++ b/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr @@ -1,13 +1,13 @@ error: `BlockId` needs to be taken by reference and not by value! - --> $DIR/mock_advanced_block_id_by_value.rs:12:1 + --> tests/ui/mock_advanced_block_id_by_value.rs:12:1 | 12 | / sp_api::mock_impl_runtime_apis! { 13 | | impl Api for MockApi { 14 | | #[advanced] -15 | | fn test(&self, _: BlockId) -> Result, ApiError> { +15 | | fn test(&self, _: BlockId) -> Result<(), ApiError> { ... | 18 | | } 19 | | } | |_^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/primitives/api/test/tests/ui/mock_advanced_missing_blockid.rs b/primitives/api/test/tests/ui/mock_advanced_missing_blockid.rs index a15ef133fa6c4..76bf5f1aa7459 100644 --- a/primitives/api/test/tests/ui/mock_advanced_missing_blockid.rs +++ b/primitives/api/test/tests/ui/mock_advanced_missing_blockid.rs @@ -12,7 +12,7 @@ struct MockApi; sp_api::mock_impl_runtime_apis! { impl Api for MockApi { #[advanced] - fn test(&self) -> Result, ApiError> { + fn test(&self) -> Result<(), ApiError> { Ok(().into()) } } diff --git a/primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr b/primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr index 87d3660316b1e..b9ce7324b5caa 100644 --- a/primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr +++ b/primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr @@ -1,5 +1,5 @@ error: If using the `advanced` attribute, it is required that the function takes at least one argument, the `BlockId`. - --> $DIR/mock_advanced_missing_blockid.rs:15:3 + --> tests/ui/mock_advanced_missing_blockid.rs:15:3 | -15 | fn test(&self) -> Result, ApiError> { +15 | fn test(&self) -> Result<(), ApiError> { | ^^ diff --git a/primitives/api/test/tests/ui/mock_only_self_reference.stderr b/primitives/api/test/tests/ui/mock_only_self_reference.stderr index 7385fe4745989..430f63eee1660 100644 --- a/primitives/api/test/tests/ui/mock_only_self_reference.stderr +++ b/primitives/api/test/tests/ui/mock_only_self_reference.stderr @@ -1,67 +1,89 @@ error: Only `&self` is supported! - --> $DIR/mock_only_self_reference.rs:14:11 + --> tests/ui/mock_only_self_reference.rs:14:11 | 14 | fn test(self, data: u64) {} | ^^^^ error: Only `&self` is supported! - --> $DIR/mock_only_self_reference.rs:16:12 + --> tests/ui/mock_only_self_reference.rs:16:12 | 16 | fn test2(&mut self, data: u64) {} | ^ -error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for trait - --> $DIR/mock_only_self_reference.rs:12:1 +error[E0050]: method `test` has 2 parameters but the declaration in trait `Api::test` has 3 + --> tests/ui/mock_only_self_reference.rs:12:1 + | +3 | / sp_api::decl_runtime_apis! { +4 | | pub trait Api { +5 | | fn test(data: u64); + | |_________________________- trait requires 3 parameters +... +12 | / sp_api::mock_impl_runtime_apis! { +13 | | impl Api for MockApi { +14 | | fn test(self, data: u64) {} +15 | | +16 | | fn test2(&mut self, data: u64) {} +17 | | } +18 | | } + | |_^ expected 3 parameters, found 2 + | + = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0050]: method `test2` has 2 parameters but the declaration in trait `Api::test2` has 3 + --> tests/ui/mock_only_self_reference.rs:12:1 | 3 | / sp_api::decl_runtime_apis! { 4 | | pub trait Api { 5 | | fn test(data: u64); 6 | | fn test2(data: u64); -7 | | } -8 | | } - | |_- type in trait + | |__________________________- trait requires 3 parameters +... +12 | / sp_api::mock_impl_runtime_apis! { +13 | | impl Api for MockApi { +14 | | fn test(self, data: u64) {} +15 | | +16 | | fn test2(&mut self, data: u64) {} +17 | | } +18 | | } + | |_^ expected 3 parameters, found 2 + | + = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0050]: method `test_with_context` has 3 parameters but the declaration in trait `Api::test_with_context` has 4 + --> tests/ui/mock_only_self_reference.rs:12:1 + | +3 | / sp_api::decl_runtime_apis! { +4 | | pub trait Api { +5 | | fn test(data: u64); + | |_________________________- trait requires 4 parameters ... -12 | sp_api::mock_impl_runtime_apis! { - | -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | _expected `u64`, found `()` - | | +12 | / sp_api::mock_impl_runtime_apis! { 13 | | impl Api for MockApi { 14 | | fn test(self, data: u64) {} 15 | | 16 | | fn test2(&mut self, data: u64) {} 17 | | } 18 | | } - | |_- help: change the parameter type to match the trait: `Option` + | |_^ expected 4 parameters, found 3 | - = note: expected fn pointer `fn(&MockApi, &BlockId, Extrinsic>>, ExecutionContext, Option, Vec<_>) -> Result<_, _>` - found fn pointer `fn(&MockApi, &BlockId, Extrinsic>>, ExecutionContext, Option<()>, Vec<_>) -> Result<_, _>` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0053]: method `Api_test2_runtime_api_impl` has an incompatible type for trait - --> $DIR/mock_only_self_reference.rs:12:1 +error[E0050]: method `test2_with_context` has 3 parameters but the declaration in trait `Api::test2_with_context` has 4 + --> tests/ui/mock_only_self_reference.rs:12:1 | 3 | / sp_api::decl_runtime_apis! { 4 | | pub trait Api { 5 | | fn test(data: u64); 6 | | fn test2(data: u64); -7 | | } -8 | | } - | |_- type in trait + | |__________________________- trait requires 4 parameters ... -12 | sp_api::mock_impl_runtime_apis! { - | -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | _expected `u64`, found `()` - | | +12 | / sp_api::mock_impl_runtime_apis! { 13 | | impl Api for MockApi { 14 | | fn test(self, data: u64) {} 15 | | 16 | | fn test2(&mut self, data: u64) {} 17 | | } 18 | | } - | |_- help: change the parameter type to match the trait: `Option` + | |_^ expected 4 parameters, found 3 | - = note: expected fn pointer `fn(&MockApi, &BlockId, Extrinsic>>, ExecutionContext, Option, Vec<_>) -> Result<_, _>` - found fn pointer `fn(&MockApi, &BlockId, Extrinsic>>, ExecutionContext, Option<()>, Vec<_>) -> Result<_, _>` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/primitives/api/test/tests/ui/positive_cases/default_impls.rs b/primitives/api/test/tests/ui/positive_cases/default_impls.rs new file mode 100644 index 0000000000000..3434db1089f05 --- /dev/null +++ b/primitives/api/test/tests/ui/positive_cases/default_impls.rs @@ -0,0 +1,41 @@ +use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use substrate_test_runtime_client::runtime::Block; + +struct Runtime {} +impl GetNodeBlockType for Runtime { + type NodeBlock = Block; +} + +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait Api { + fn test1(); + fn test2(); + #[api_version(3)] + fn test3(); + #[api_version(4)] + fn test4(); + } +} + +sp_api::impl_runtime_apis! { + #[api_version(2)] + impl self::Api for Runtime { + fn test1() {} + fn test2() {} + } + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr index a0a16c4a493db..06f8226ec88bf 100644 --- a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr +++ b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr @@ -1,64 +1,39 @@ error[E0053]: method `test` has an incompatible type for trait - --> $DIR/type_reference_in_impl_runtime_apis_call.rs:19:17 + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:19:17 | -13 | fn test(data: u64); - | --- type in trait -... 19 | fn test(data: &u64) { | ^^^^ | | | expected `u64`, found `&u64` | help: change the parameter type to match the trait: `u64` | +note: type in trait + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:13:17 + | +13 | fn test(data: u64); + | ^^^ = note: expected fn pointer `fn(u64)` found fn pointer `fn(&u64)` -error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for trait - --> $DIR/type_reference_in_impl_runtime_apis_call.rs:17:1 - | -11 | / sp_api::decl_runtime_apis! { -12 | | pub trait Api { -13 | | fn test(data: u64); -14 | | } -15 | | } - | |_- type in trait -16 | -17 | sp_api::impl_runtime_apis! { - | -^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | _expected `u64`, found `&u64` - | | -18 | | impl self::Api for Runtime { -19 | | fn test(data: &u64) { -20 | | unimplemented!() -... | -34 | | } -35 | | } - | |_- help: change the parameter type to match the trait: `std::option::Option` - | - = note: expected fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option, Vec<_>) -> Result<_, _>` - found fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option<&u64>, Vec<_>) -> Result<_, _>` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0308]: mismatched types - --> $DIR/type_reference_in_impl_runtime_apis_call.rs:17:1 + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:19:11 | 17 | / sp_api::impl_runtime_apis! { 18 | | impl self::Api for Runtime { 19 | | fn test(data: &u64) { + | | ^^^^^^^ expected `u64`, found `&u64` 20 | | unimplemented!() ... | 34 | | } 35 | | } - | |_^ expected `u64`, found `&u64` + | |_- arguments to this function are incorrect | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0308]: mismatched types - --> $DIR/type_reference_in_impl_runtime_apis_call.rs:19:11 +note: associated function defined here + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:13:6 + | +13 | fn test(data: u64); + | ^^^^ +help: consider removing the borrow | 19 | fn test(data: &u64) { - | ^^^^^^^ - | | - | expected `u64`, found `&u64` - | help: consider removing the borrow: `data` + | diff --git a/primitives/application-crypto/Cargo.toml b/primitives/application-crypto/Cargo.toml index 6849dc25f8561..39a3413bcf981 100644 --- a/primitives/application-crypto/Cargo.toml +++ b/primitives/application-crypto/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "sp-application-crypto" -version = "4.0.0-dev" +version = "7.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" description = "Provides facilities for generating application specific crypto wrapper types." license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sp-application-crypto" readme = "README.md" @@ -15,12 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.126", optional = true, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -sp-io = { version = "4.0.0-dev", default-features = false, path = "../io" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true, features = ["derive"] } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-io = { version = "7.0.0", default-features = false, path = "../io" } [features] default = [ "std" ] diff --git a/primitives/application-crypto/src/ecdsa.rs b/primitives/application-crypto/src/ecdsa.rs index 915e16ba3b1a2..6356f54a0b085 100644 --- a/primitives/application-crypto/src/ecdsa.rs +++ b/primitives/application-crypto/src/ecdsa.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,10 +53,10 @@ impl RuntimePublic for Public { } fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { - sp_io::crypto::ecdsa_verify(&signature, msg.as_ref(), self) + sp_io::crypto::ecdsa_verify(signature, msg.as_ref(), self) } fn to_raw_vec(&self) -> Vec { - sp_core::crypto::Public::to_raw_vec(self) + sp_core::crypto::ByteArray::to_raw_vec(self) } } diff --git a/primitives/application-crypto/src/ed25519.rs b/primitives/application-crypto/src/ed25519.rs index 09ce48fcb274c..199e55383b881 100644 --- a/primitives/application-crypto/src/ed25519.rs +++ b/primitives/application-crypto/src/ed25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,10 +53,10 @@ impl RuntimePublic for Public { } fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { - sp_io::crypto::ed25519_verify(&signature, msg.as_ref(), self) + sp_io::crypto::ed25519_verify(signature, msg.as_ref(), self) } fn to_raw_vec(&self) -> Vec { - sp_core::crypto::Public::to_raw_vec(self) + sp_core::crypto::ByteArray::to_raw_vec(self) } } diff --git a/primitives/application-crypto/src/lib.rs b/primitives/application-crypto/src/lib.rs index baa6560667059..05f89c40ef99f 100644 --- a/primitives/application-crypto/src/lib.rs +++ b/primitives/application-crypto/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,10 @@ pub use sp_core::crypto::{DeriveJunction, Pair, SecretStringError, Ss58Codec}; #[doc(hidden)] pub use sp_core::{ self, - crypto::{CryptoType, CryptoTypePublicPair, Derive, IsWrappedBy, Public, Wraps}, + crypto::{ + ByteArray, CryptoType, CryptoTypePublicPair, Derive, IsWrappedBy, Public, UncheckedFrom, + Wraps, + }, RuntimeDebug, }; @@ -39,7 +42,7 @@ pub use scale_info; #[cfg(feature = "std")] pub use serde; #[doc(hidden)] -pub use sp_std::{convert::TryFrom, ops::Deref, vec::Vec}; +pub use sp_std::{ops::Deref, vec::Vec}; pub mod ecdsa; pub mod ed25519; @@ -221,7 +224,7 @@ macro_rules! app_crypto_public_full_crypto { $crate::wrap! { /// A generic `AppPublic` wrapper type over $public crypto; this has no specific App. #[derive( - Clone, Default, Eq, Hash, PartialEq, PartialOrd, Ord, + Clone, Eq, Hash, PartialEq, PartialOrd, Ord, $crate::codec::Encode, $crate::codec::Decode, $crate::RuntimeDebug, @@ -258,7 +261,7 @@ macro_rules! app_crypto_public_not_full_crypto { $crate::wrap! { /// A generic `AppPublic` wrapper type over $public crypto; this has no specific App. #[derive( - Clone, Default, Eq, PartialEq, Ord, PartialOrd, + Clone, Eq, PartialEq, Ord, PartialOrd, $crate::codec::Encode, $crate::codec::Decode, $crate::RuntimeDebug, @@ -301,13 +304,12 @@ macro_rules! app_crypto_public_common { } } + impl $crate::ByteArray for Public { + const LEN: usize = <$public>::LEN; + } impl $crate::Public for Public { - fn from_slice(x: &[u8]) -> Self { - Self(<$public>::from_slice(x)) - } - fn to_public_crypto_pair(&self) -> $crate::CryptoTypePublicPair { - $crate::CryptoTypePublicPair($crypto_type, self.to_raw_vec()) + $crate::CryptoTypePublicPair($crypto_type, $crate::ByteArray::to_raw_vec(self)) } } @@ -357,11 +359,11 @@ macro_rules! app_crypto_public_common { impl From<&Public> for $crate::CryptoTypePublicPair { fn from(key: &Public) -> Self { - $crate::CryptoTypePublicPair($crypto_type, $crate::Public::to_raw_vec(key)) + $crate::CryptoTypePublicPair($crypto_type, $crate::ByteArray::to_raw_vec(key)) } } - impl<'a> $crate::TryFrom<&'a [u8]> for Public { + impl<'a> TryFrom<&'a [u8]> for Public { type Error = (); fn try_from(data: &'a [u8]) -> Result { @@ -435,7 +437,7 @@ macro_rules! app_crypto_signature_full_crypto { ($sig:ty, $key_type:expr, $crypto_type:expr) => { $crate::wrap! { /// A generic `AppPublic` wrapper type over $public crypto; this has no specific App. - #[derive(Clone, Default, Eq, PartialEq, + #[derive(Clone, Eq, PartialEq, $crate::codec::Encode, $crate::codec::Decode, $crate::RuntimeDebug, @@ -470,7 +472,7 @@ macro_rules! app_crypto_signature_not_full_crypto { ($sig:ty, $key_type:expr, $crypto_type:expr) => { $crate::wrap! { /// A generic `AppPublic` wrapper type over $public crypto; this has no specific App. - #[derive(Clone, Default, Eq, PartialEq, + #[derive(Clone, Eq, PartialEq, $crate::codec::Encode, $crate::codec::Decode, $crate::scale_info::TypeInfo, @@ -516,11 +518,19 @@ macro_rules! app_crypto_signature_common { type Generic = $sig; } - impl $crate::TryFrom<$crate::Vec> for Signature { + impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = (); + + fn try_from(data: &'a [u8]) -> Result { + <$sig>::try_from(data).map(Into::into) + } + } + + impl TryFrom<$crate::Vec> for Signature { type Error = (); fn try_from(data: $crate::Vec) -> Result { - Ok(<$sig>::try_from(data.as_slice())?.into()) + Self::try_from(&data[..]) } } }; diff --git a/primitives/application-crypto/src/sr25519.rs b/primitives/application-crypto/src/sr25519.rs index f51236f2ab384..c96e7382eb191 100644 --- a/primitives/application-crypto/src/sr25519.rs +++ b/primitives/application-crypto/src/sr25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,10 +53,10 @@ impl RuntimePublic for Public { } fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { - sp_io::crypto::sr25519_verify(&signature, msg.as_ref(), self) + sp_io::crypto::sr25519_verify(signature, msg.as_ref(), self) } fn to_raw_vec(&self) -> Vec { - sp_core::crypto::Public::to_raw_vec(self) + sp_core::crypto::ByteArray::to_raw_vec(self) } } diff --git a/primitives/application-crypto/src/traits.rs b/primitives/application-crypto/src/traits.rs index 376d12f0c7a3e..853208bc20cbf 100644 --- a/primitives/application-crypto/src/traits.rs +++ b/primitives/application-crypto/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -157,8 +157,8 @@ pub trait RuntimeAppPublic: Sized { fn to_raw_vec(&self) -> Vec; } -/// Something that bound to a fixed `RuntimeAppPublic`. +/// Something that bound to a fixed [`RuntimeAppPublic`]. pub trait BoundToRuntimeAppPublic { - /// The `RuntimeAppPublic` this type is bound to. + /// The [`RuntimeAppPublic`] this type is bound to. type Public: RuntimeAppPublic; } diff --git a/primitives/application-crypto/test/Cargo.toml b/primitives/application-crypto/test/Cargo.toml index 468bfee3cc010..b10b7a3218ba6 100644 --- a/primitives/application-crypto/test/Cargo.toml +++ b/primitives/application-crypto/test/Cargo.toml @@ -2,20 +2,20 @@ name = "sp-application-crypto-test" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" description = "Integration tests for application-crypto" license = "Apache-2.0" publish = false -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "4.0.0-dev", default-features = false, path = "../../core" } -sp-keystore = { version = "0.10.0-dev", path = "../../keystore", default-features = false } -substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -sp-runtime = { version = "4.0.0-dev", path = "../../runtime" } sp-api = { version = "4.0.0-dev", path = "../../api" } -sp-application-crypto = { version = "4.0.0-dev", path = "../" } +sp-application-crypto = { version = "7.0.0", path = "../" } +sp-core = { version = "7.0.0", default-features = false, path = "../../core" } +sp-keystore = { version = "0.13.0", default-features = false, path = "../../keystore" } +sp-runtime = { version = "7.0.0", path = "../../runtime" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/primitives/application-crypto/test/src/ecdsa.rs b/primitives/application-crypto/test/src/ecdsa.rs index c4aa6a2afbd61..e45a3d5f8f86c 100644 --- a/primitives/application-crypto/test/src/ecdsa.rs +++ b/primitives/application-crypto/test/src/ecdsa.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/application-crypto/test/src/ed25519.rs b/primitives/application-crypto/test/src/ed25519.rs index 7cfd801388c78..ef2df9fe9196e 100644 --- a/primitives/application-crypto/test/src/ed25519.rs +++ b/primitives/application-crypto/test/src/ed25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/application-crypto/test/src/lib.rs b/primitives/application-crypto/test/src/lib.rs index 6b7734764e793..7cc3f8b0780e6 100644 --- a/primitives/application-crypto/test/src/lib.rs +++ b/primitives/application-crypto/test/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/application-crypto/test/src/sr25519.rs b/primitives/application-crypto/test/src/sr25519.rs index 12dfbc609fb01..e15ffe82a1c35 100644 --- a/primitives/application-crypto/test/src/sr25519.rs +++ b/primitives/application-crypto/test/src/sr25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/arithmetic/Cargo.toml b/primitives/arithmetic/Cargo.toml index abdbd4e60d041..36c86230e96ce 100644 --- a/primitives/arithmetic/Cargo.toml +++ b/primitives/arithmetic/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "sp-arithmetic" -version = "4.0.0-dev" +version = "6.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Minimal fixed point arithmetic primitives and types for runtime." documentation = "https://docs.rs/sp-arithmetic" @@ -13,33 +13,34 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", + "max-encoded-len", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } integer-sqrt = "0.1.2" -static_assertions = "1.1.0" num-traits = { version = "0.2.8", default-features = false } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } -serde = { version = "1.0.126", optional = true, features = ["derive"] } -sp-debug-derive = { version = "3.0.0", default-features = false, path = "../debug-derive" } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +static_assertions = "1.1.0" +sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [dev-dependencies] -rand = "0.7.2" criterion = "0.3" -primitive-types = "0.10.1" +primitive-types = "0.12.0" +sp-core = { version = "7.0.0", features = ["full_crypto"], path = "../core" } +rand = "0.7.2" [features] default = ["std"] std = [ "codec/std", - "scale-info/std", "num-traits/std", - "sp-std/std", + "scale-info/std", "serde", "sp-debug-derive/std", + "sp-std/std", ] [[bench]] diff --git a/primitives/arithmetic/benches/bench.rs b/primitives/arithmetic/benches/bench.rs index 02db00aa0bf82..57556135f6431 100644 --- a/primitives/arithmetic/benches/bench.rs +++ b/primitives/arithmetic/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,19 +41,19 @@ fn bench_op(c: &mut Criterion, name: &str, op: F) { fn bench_addition(c: &mut Criterion) { bench_op(c, "addition", |a, b| { - let _ = a.clone().add(&b); + let _ = a.clone().add(b); }); } fn bench_subtraction(c: &mut Criterion) { bench_op(c, "subtraction", |a, b| { - let _ = a.clone().sub(&b); + let _ = a.clone().sub(b); }); } fn bench_multiplication(c: &mut Criterion) { bench_op(c, "multiplication", |a, b| { - let _ = a.clone().mul(&b); + let _ = a.clone().mul(b); }); } diff --git a/primitives/arithmetic/fuzzer/Cargo.toml b/primitives/arithmetic/fuzzer/Cargo.toml index d10eccfc7c74a..7be800a2e966c 100644 --- a/primitives/arithmetic/fuzzer/Cargo.toml +++ b/primitives/arithmetic/fuzzer/Cargo.toml @@ -2,9 +2,9 @@ name = "sp-arithmetic-fuzzer" version = "2.0.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Fuzzer for fixed point arithmetic primitives." documentation = "https://docs.rs/sp-arithmetic-fuzzer" @@ -14,10 +14,10 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-arithmetic = { version = "4.0.0-dev", path = ".." } honggfuzz = "0.5.49" -primitive-types = "0.10.1" -num-bigint = "0.2" +num-bigint = "0.4.3" +primitive-types = "0.12.0" +sp-arithmetic = { version = "6.0.0", path = ".." } [[bin]] name = "biguint" @@ -32,8 +32,8 @@ name = "per_thing_rational" path = "src/per_thing_rational.rs" [[bin]] -name = "multiply_by_rational" -path = "src/multiply_by_rational.rs" +name = "multiply_by_rational_with_rounding" +path = "src/multiply_by_rational_with_rounding.rs" [[bin]] name = "fixed_point" diff --git a/primitives/arithmetic/fuzzer/src/biguint.rs b/primitives/arithmetic/fuzzer/src/biguint.rs index ca5b8379afff5..f49743a4b8a69 100644 --- a/primitives/arithmetic/fuzzer/src/biguint.rs +++ b/primitives/arithmetic/fuzzer/src/biguint.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,6 @@ use honggfuzz::fuzz; use sp_arithmetic::biguint::{BigUint, Single}; -use std::convert::TryFrom; fn main() { loop { diff --git a/primitives/arithmetic/fuzzer/src/fixed_point.rs b/primitives/arithmetic/fuzzer/src/fixed_point.rs index d8f058ae51e2c..c1b93f8c63a11 100644 --- a/primitives/arithmetic/fuzzer/src/fixed_point.rs +++ b/primitives/arithmetic/fuzzer/src/fixed_point.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/arithmetic/fuzzer/src/multiply_by_rational.rs b/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs similarity index 75% rename from primitives/arithmetic/fuzzer/src/multiply_by_rational.rs rename to primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs index 3089d4b092183..474b2d363eccd 100644 --- a/primitives/arithmetic/fuzzer/src/multiply_by_rational.rs +++ b/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,19 +16,21 @@ // limitations under the License. //! # Running -//! Running this fuzzer can be done with `cargo hfuzz run multiply_by_rational`. `honggfuzz` CLI -//! options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads. +//! Running this fuzzer can be done with `cargo hfuzz run multiply_by_rational_with_rounding`. +//! `honggfuzz` CLI options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 +//! threads. //! //! # Debugging a panic //! Once a panic is found, it can be debugged with -//! `cargo hfuzz run-debug multiply_by_rational hfuzz_workspace/multiply_by_rational/*.fuzz`. +//! `cargo hfuzz run-debug multiply_by_rational_with_rounding +//! hfuzz_workspace/multiply_by_rational_with_rounding/*.fuzz`. //! //! # More information //! More information about `honggfuzz` can be found //! [here](https://docs.rs/honggfuzz/). use honggfuzz::fuzz; -use sp_arithmetic::{helpers_128bit::multiply_by_rational, traits::Zero}; +use sp_arithmetic::{helpers_128bit::multiply_by_rational_with_rounding, traits::Zero, Rounding}; fn main() { loop { @@ -42,9 +44,9 @@ fn main() { println!("++ Equation: {} * {} / {}", a, b, c); - // The point of this fuzzing is to make sure that `multiply_by_rational` is 100% - // accurate as long as the value fits in a u128. - if let Ok(result) = multiply_by_rational(a, b, c) { + // The point of this fuzzing is to make sure that `multiply_by_rational_with_rounding` + // is 100% accurate as long as the value fits in a u128. + if let Some(result) = multiply_by_rational_with_rounding(a, b, c, Rounding::Down) { let truth = mul_div(a, b, c); if result != truth && result != truth + 1 { diff --git a/primitives/arithmetic/fuzzer/src/normalize.rs b/primitives/arithmetic/fuzzer/src/normalize.rs index 2662565106e66..3d2d6eb9acfcc 100644 --- a/primitives/arithmetic/fuzzer/src/normalize.rs +++ b/primitives/arithmetic/fuzzer/src/normalize.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,6 @@ use honggfuzz::fuzz; use sp_arithmetic::Normalizable; -use std::convert::TryInto; type Ty = u64; @@ -36,7 +35,7 @@ fn main() { loop { fuzz!(|data: (Vec, Ty)| { let (data, norm) = data; - if data.len() == 0 { + if data.is_empty() { return } let pre_sum: u128 = data.iter().map(|x| *x as u128).sum(); @@ -45,16 +44,14 @@ fn main() { // error cases. if pre_sum > sum_limit || data.len() > len_limit { assert!(normalized.is_err()) + } else if let Ok(normalized) = normalized { + // if sum goes beyond u128, panic. + let sum: u128 = normalized.iter().map(|x| *x as u128).sum(); + + // if this function returns Ok(), then it will ALWAYS be accurate. + assert_eq!(sum, norm as u128, "sums don't match {:?}, {}", normalized, norm); } else { - if let Ok(normalized) = normalized { - // if sum goes beyond u128, panic. - let sum: u128 = normalized.iter().map(|x| *x as u128).sum(); - - // if this function returns Ok(), then it will ALWAYS be accurate. - assert_eq!(sum, norm as u128, "sums don't match {:?}, {}", normalized, norm); - } else { - panic!("Should have returned Ok for input = {:?}, target = {:?}", data, norm); - } + panic!("Should have returned Ok for input = {:?}, target = {:?}", data, norm); } }) } diff --git a/primitives/arithmetic/fuzzer/src/per_thing_rational.rs b/primitives/arithmetic/fuzzer/src/per_thing_rational.rs index 7b90faa94069f..7021c54c0ba04 100644 --- a/primitives/arithmetic/fuzzer/src/per_thing_rational.rs +++ b/primitives/arithmetic/fuzzer/src/per_thing_rational.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/arithmetic/src/biguint.rs b/primitives/arithmetic/src/biguint.rs index 17ed323dc0ce4..33f0960ee378c 100644 --- a/primitives/arithmetic/src/biguint.rs +++ b/primitives/arithmetic/src/biguint.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ use codec::{Decode, Encode}; use num_traits::{One, Zero}; -use sp_std::{cell::RefCell, cmp::Ordering, convert::TryFrom, ops, prelude::*, vec}; +use sp_std::{cell::RefCell, cmp::Ordering, ops, prelude::*, vec}; // A sensible value for this would be half of the dword size of the host machine. Since the // runtime is compiled to 32bit webassembly, using 32 and 64 for single and double respectively @@ -664,7 +664,6 @@ pub mod tests { #[test] fn can_try_build_numbers_from_types() { - use sp_std::convert::TryFrom; assert_eq!(u64::try_from(with_limbs(1)).unwrap(), 1); assert_eq!(u64::try_from(with_limbs(2)).unwrap(), u32::MAX as u64 + 2); assert_eq!(u64::try_from(with_limbs(3)).unwrap_err(), "cannot fit a number into u64"); diff --git a/primitives/arithmetic/src/fixed_point.rs b/primitives/arithmetic/src/fixed_point.rs index 7a81f222c4926..bf3c93cdad605 100644 --- a/primitives/arithmetic/src/fixed_point.rs +++ b/primitives/arithmetic/src/fixed_point.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,16 +18,15 @@ //! Decimal Fixed Point implementations for Substrate runtime. use crate::{ - helpers_128bit::multiply_by_rational, + helpers_128bit::{multiply_by_rational_with_rounding, sqrt}, traits::{ Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedSub, One, SaturatedConversion, Saturating, UniqueSaturatedInto, Zero, }, - PerThing, + PerThing, Perbill, Rounding, SignedRounding, }; use codec::{CompactAs, Decode, Encode}; use sp_std::{ - convert::{TryFrom, TryInto}, fmt::Debug, ops::{self, Add, Div, Mul, Sub}, prelude::*, @@ -122,7 +121,8 @@ pub trait FixedPointNumber: /// Creates `self` from an integer number `int`. /// /// Returns `None` if `int` exceeds accuracy. - fn checked_from_integer(int: Self::Inner) -> Option { + fn checked_from_integer>(int: N) -> Option { + let int: Self::Inner = int.into(); int.checked_mul(&Self::DIV).map(Self::from_inner) } @@ -151,10 +151,14 @@ pub trait FixedPointNumber: let d: I129 = d.into(); let negative = n.negative != d.negative; - multiply_by_rational(n.value, Self::DIV.unique_saturated_into(), d.value) - .ok() - .and_then(|value| from_i129(I129 { value, negative })) - .map(Self::from_inner) + multiply_by_rational_with_rounding( + n.value, + Self::DIV.unique_saturated_into(), + d.value, + Rounding::from_signed(SignedRounding::Minor, negative), + ) + .and_then(|value| from_i129(I129 { value, negative })) + .map(Self::from_inner) } /// Checked multiplication for integer type `N`. Equal to `self * n`. @@ -165,9 +169,13 @@ pub trait FixedPointNumber: let rhs: I129 = n.into(); let negative = lhs.negative != rhs.negative; - multiply_by_rational(lhs.value, rhs.value, Self::DIV.unique_saturated_into()) - .ok() - .and_then(|value| from_i129(I129 { value, negative })) + multiply_by_rational_with_rounding( + lhs.value, + rhs.value, + Self::DIV.unique_saturated_into(), + Rounding::from_signed(SignedRounding::Minor, negative), + ) + .and_then(|value| from_i129(I129 { value, negative })) } /// Saturating multiplication for integer type `N`. Equal to `self * n`. @@ -369,6 +377,7 @@ macro_rules! implement_fixed { Default, Copy, Clone, + codec::MaxEncodedLen, PartialEq, Eq, PartialOrd, @@ -405,20 +414,326 @@ macro_rules! implement_fixed { } impl $name { - /// const version of `FixedPointNumber::from_inner`. + /// Create a new instance from the given `inner` value. + /// + /// `const` version of `FixedPointNumber::from_inner`. pub const fn from_inner(inner: $inner_type) -> Self { Self(inner) } + /// Return the instance's inner value. + /// + /// `const` version of `FixedPointNumber::into_inner`. + pub const fn into_inner(self) -> $inner_type { + self.0 + } + + /// Creates self from a `u32`. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn from_u32(n: u32) -> Self { + Self::from_inner((n as $inner_type) * $div) + } + + /// Convert from a `float` value. #[cfg(any(feature = "std", test))] pub fn from_float(x: f64) -> Self { Self((x * (::DIV as f64)) as $inner_type) } + /// Convert from a `Perbill` value. + pub const fn from_perbill(n: Perbill) -> Self { + Self::from_rational(n.deconstruct() as u128, 1_000_000_000) + } + + /// Convert into a `Perbill` value. Will saturate if above one or below zero. + pub const fn into_perbill(self) -> Perbill { + if self.0 <= 0 { + Perbill::zero() + } else if self.0 >= $div { + Perbill::one() + } else { + match multiply_by_rational_with_rounding( + self.0 as u128, + 1_000_000_000, + Self::DIV as u128, + Rounding::NearestPrefDown, + ) { + Some(value) => { + if value > (u32::max_value() as u128) { + panic!( + "prior logic ensures 0 Perbill::zero(), + } + } + } + + /// Convert into a `float` value. #[cfg(any(feature = "std", test))] pub fn to_float(self) -> f64 { self.0 as f64 / ::DIV as f64 } + + /// Attempt to convert into a `PerThing`. This will succeed iff `self` is at least zero + /// and at most one. If it is out of bounds, it will result in an error returning the + /// clamped value. + pub fn try_into_perthing(self) -> Result { + if self < Self::zero() { + Err(P::zero()) + } else if self > Self::one() { + Err(P::one()) + } else { + Ok(P::from_rational(self.0 as u128, $div)) + } + } + + /// Attempt to convert into a `PerThing`. This will always succeed resulting in a + /// clamped value if `self` is less than zero or greater than one. + pub fn into_clamped_perthing(self) -> P { + if self < Self::zero() { + P::zero() + } else if self > Self::one() { + P::one() + } else { + P::from_rational(self.0 as u128, $div) + } + } + + /// Negate the value. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn neg(self) -> Self { + Self(0 - self.0) + } + + /// Take the square root of a positive value. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn sqrt(self) -> Self { + match self.try_sqrt() { + Some(v) => v, + None => panic!("sqrt overflow or negative input"), + } + } + + /// Compute the square root, rounding as desired. If it overflows or is negative, then + /// `None` is returned. + pub const fn try_sqrt(self) -> Option { + if self.0 == 0 { + return Some(Self(0)) + } + if self.0 < 1 { + return None + } + let v = self.0 as u128; + + // Want x' = sqrt(x) where x = n/D and x' = n'/D (D is fixed) + // Our prefered way is: + // sqrt(n/D) = sqrt(nD / D^2) = sqrt(nD)/sqrt(D^2) = sqrt(nD)/D + // ergo n' = sqrt(nD) + // but this requires nD to fit into our type. + // if nD doesn't fit then we can fall back on: + // sqrt(nD) = sqrt(n)*sqrt(D) + // computing them individually and taking the product at the end. we will lose some + // precision though. + let maybe_vd = u128::checked_mul(v, $div); + let r = if let Some(vd) = maybe_vd { sqrt(vd) } else { sqrt(v) * sqrt($div) }; + Some(Self(r as $inner_type)) + } + + /// Add a value and return the result. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn add(self, rhs: Self) -> Self { + Self(self.0 + rhs.0) + } + + /// Subtract a value and return the result. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn sub(self, rhs: Self) -> Self { + Self(self.0 - rhs.0) + } + + /// Multiply by a value and return the result. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn mul(self, rhs: Self) -> Self { + match $name::const_checked_mul(self, rhs) { + Some(v) => v, + None => panic!("attempt to multiply with overflow"), + } + } + + /// Divide by a value and return the result. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn div(self, rhs: Self) -> Self { + match $name::const_checked_div(self, rhs) { + Some(v) => v, + None => panic!("attempt to divide with overflow or NaN"), + } + } + + /// Convert into an `I129` format value. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + const fn into_i129(self) -> I129 { + #[allow(unused_comparisons)] + if self.0 < 0 { + let value = match self.0.checked_neg() { + Some(n) => n as u128, + None => u128::saturating_add(<$inner_type>::max_value() as u128, 1), + }; + I129 { value, negative: true } + } else { + I129 { value: self.0 as u128, negative: false } + } + } + + /// Convert from an `I129` format value. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + const fn from_i129(n: I129) -> Option { + let max_plus_one = u128::saturating_add(<$inner_type>::max_value() as u128, 1); + #[allow(unused_comparisons)] + let inner = if n.negative && <$inner_type>::min_value() < 0 && n.value == max_plus_one { + <$inner_type>::min_value() + } else { + let unsigned_inner = n.value as $inner_type; + if unsigned_inner as u128 != n.value || (unsigned_inner > 0) != (n.value > 0) { + return None + }; + if n.negative { + match unsigned_inner.checked_neg() { + Some(v) => v, + None => return None, + } + } else { + unsigned_inner + } + }; + Some(Self(inner)) + } + + /// Calculate an approximation of a rational. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn from_rational(a: u128, b: u128) -> Self { + Self::from_rational_with_rounding(a, b, Rounding::NearestPrefDown) + } + + /// Calculate an approximation of a rational with custom rounding. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn from_rational_with_rounding(a: u128, b: u128, rounding: Rounding) -> Self { + if b == 0 { + panic!("attempt to divide by zero in from_rational") + } + match multiply_by_rational_with_rounding(Self::DIV as u128, a, b, rounding) { + Some(value) => match Self::from_i129(I129 { value, negative: false }) { + Some(x) => x, + None => panic!("overflow in from_rational"), + }, + None => panic!("overflow in from_rational"), + } + } + + /// Multiply by another value, returning `None` in the case of an error. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + pub const fn const_checked_mul(self, other: Self) -> Option { + self.const_checked_mul_with_rounding(other, SignedRounding::NearestPrefLow) + } + + /// Multiply by another value with custom rounding, returning `None` in the case of an + /// error. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + pub const fn const_checked_mul_with_rounding( + self, + other: Self, + rounding: SignedRounding, + ) -> Option { + let lhs = self.into_i129(); + let rhs = other.into_i129(); + let negative = lhs.negative != rhs.negative; + + match multiply_by_rational_with_rounding( + lhs.value, + rhs.value, + Self::DIV as u128, + Rounding::from_signed(rounding, negative), + ) { + Some(value) => Self::from_i129(I129 { value, negative }), + None => None, + } + } + + /// Divide by another value, returning `None` in the case of an error. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + pub const fn const_checked_div(self, other: Self) -> Option { + self.checked_rounding_div(other, SignedRounding::NearestPrefLow) + } + + /// Divide by another value with custom rounding, returning `None` in the case of an + /// error. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + pub const fn checked_rounding_div( + self, + other: Self, + rounding: SignedRounding, + ) -> Option { + if other.0 == 0 { + return None + } + + let lhs = self.into_i129(); + let rhs = other.into_i129(); + let negative = lhs.negative != rhs.negative; + + match multiply_by_rational_with_rounding( + lhs.value, + Self::DIV as u128, + rhs.value, + Rounding::from_signed(rounding, negative), + ) { + Some(value) => Self::from_i129(I129 { value, negative }), + None => None, + } + } } impl Saturating for $name { @@ -521,10 +836,18 @@ macro_rules! implement_fixed { let rhs: I129 = other.0.into(); let negative = lhs.negative != rhs.negative; - multiply_by_rational(lhs.value, Self::DIV as u128, rhs.value) - .ok() - .and_then(|value| from_i129(I129 { value, negative })) - .map(Self) + // Note that this uses the old (well-tested) code with sign-ignorant rounding. This + // is equivalent to the `SignedRounding::NearestPrefMinor`. This means it is + // expected to give exactly the same result as `const_checked_div` when the result + // is positive and a result up to one epsilon greater when it is negative. + multiply_by_rational_with_rounding( + lhs.value, + Self::DIV as u128, + rhs.value, + Rounding::from_signed(SignedRounding::Minor, negative), + ) + .and_then(|value| from_i129(I129 { value, negative })) + .map(Self) } } @@ -534,10 +857,14 @@ macro_rules! implement_fixed { let rhs: I129 = other.0.into(); let negative = lhs.negative != rhs.negative; - multiply_by_rational(lhs.value, rhs.value, Self::DIV as u128) - .ok() - .and_then(|value| from_i129(I129 { value, negative })) - .map(Self) + multiply_by_rational_with_rounding( + lhs.value, + rhs.value, + Self::DIV as u128, + Rounding::from_signed(SignedRounding::Minor, negative), + ) + .and_then(|value| from_i129(I129 { value, negative })) + .map(Self) } } @@ -667,6 +994,15 @@ macro_rules! implement_fixed { assert!($name::DIV > 0); } + #[test] + fn has_max_encoded_len() { + struct AsMaxEncodedLen { + _data: T, + } + + let _ = AsMaxEncodedLen { _data: $name::min_value() }; + } + #[test] fn from_i129_works() { let a = I129 { value: 1, negative: true }; @@ -841,6 +1177,16 @@ macro_rules! implement_fixed { } } + #[test] + fn op_sqrt_works() { + for i in 1..1_000i64 { + let x = $name::saturating_from_rational(i, 1_000i64); + assert_eq!((x * x).try_sqrt(), Some(x)); + let x = $name::saturating_from_rational(i, 1i64); + assert_eq!((x * x).try_sqrt(), Some(x)); + } + } + #[test] fn op_div_works() { let a = $name::saturating_from_integer(42); @@ -889,31 +1235,32 @@ macro_rules! implement_fixed { let accuracy = $name::accuracy(); // Case where integer fits. - let a = $name::checked_from_integer(42).expect("42 * accuracy <= inner_max; qed"); + let a = $name::checked_from_integer::<$inner_type>(42) + .expect("42 * accuracy <= inner_max; qed"); assert_eq!(a.into_inner(), 42 * accuracy); // Max integer that fit. - let a = $name::checked_from_integer(inner_max / accuracy) + let a = $name::checked_from_integer::<$inner_type>(inner_max / accuracy) .expect("(inner_max / accuracy) * accuracy <= inner_max; qed"); assert_eq!(a.into_inner(), (inner_max / accuracy) * accuracy); // Case where integer doesn't fit, so it returns `None`. - let a = $name::checked_from_integer(inner_max / accuracy + 1); + let a = $name::checked_from_integer::<$inner_type>(inner_max / accuracy + 1); assert_eq!(a, None); if $name::SIGNED { // Case where integer fits. - let a = $name::checked_from_integer(0.saturating_sub(42)) + let a = $name::checked_from_integer::<$inner_type>(0.saturating_sub(42)) .expect("-42 * accuracy >= inner_min; qed"); assert_eq!(a.into_inner(), 0 - 42 * accuracy); // Min integer that fit. - let a = $name::checked_from_integer(inner_min / accuracy) + let a = $name::checked_from_integer::<$inner_type>(inner_min / accuracy) .expect("(inner_min / accuracy) * accuracy <= inner_min; qed"); assert_eq!(a.into_inner(), (inner_min / accuracy) * accuracy); // Case where integer doesn't fit, so it returns `None`. - let a = $name::checked_from_integer(inner_min / accuracy - 1); + let a = $name::checked_from_integer::<$inner_type>(inner_min / accuracy - 1); assert_eq!(a, None); } } @@ -1122,6 +1469,41 @@ macro_rules! implement_fixed { assert_eq!(a.into_inner(), 0); } + #[test] + fn from_rational_works() { + let inner_max: u128 = <$name as FixedPointNumber>::Inner::max_value() as u128; + let inner_min: u128 = 0; + let accuracy: u128 = $name::accuracy() as u128; + + // Max - 1. + let a = $name::from_rational(inner_max - 1, accuracy); + assert_eq!(a.into_inner() as u128, inner_max - 1); + + // Min + 1. + let a = $name::from_rational(inner_min + 1, accuracy); + assert_eq!(a.into_inner() as u128, inner_min + 1); + + // Max. + let a = $name::from_rational(inner_max, accuracy); + assert_eq!(a.into_inner() as u128, inner_max); + + // Min. + let a = $name::from_rational(inner_min, accuracy); + assert_eq!(a.into_inner() as u128, inner_min); + + let a = $name::from_rational(inner_max, 3 * accuracy); + assert_eq!(a.into_inner() as u128, inner_max / 3); + + let a = $name::from_rational(1, accuracy); + assert_eq!(a.into_inner() as u128, 1); + + let a = $name::from_rational(1, accuracy + 1); + assert_eq!(a.into_inner() as u128, 1); + + let a = $name::from_rational_with_rounding(1, accuracy + 1, Rounding::Down); + assert_eq!(a.into_inner() as u128, 0); + } + #[test] fn checked_mul_int_works() { let a = $name::saturating_from_integer(2); @@ -1261,6 +1643,76 @@ macro_rules! implement_fixed { ); } + #[test] + fn const_checked_mul_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + + let a = $name::saturating_from_integer(2u32); + + // Max - 1. + let b = $name::from_inner(inner_max - 1); + assert_eq!(a.const_checked_mul((b / 2.into())), Some(b)); + + // Max. + let c = $name::from_inner(inner_max); + assert_eq!(a.const_checked_mul((c / 2.into())), Some(b)); + + // Max + 1 => None. + let e = $name::from_inner(1); + assert_eq!(a.const_checked_mul((c / 2.into() + e)), None); + + if $name::SIGNED { + // Min + 1. + let b = $name::from_inner(inner_min + 1) / 2.into(); + let c = $name::from_inner(inner_min + 2); + assert_eq!(a.const_checked_mul(b), Some(c)); + + // Min. + let b = $name::from_inner(inner_min) / 2.into(); + let c = $name::from_inner(inner_min); + assert_eq!(a.const_checked_mul(b), Some(c)); + + // Min - 1 => None. + let b = $name::from_inner(inner_min) / 2.into() - $name::from_inner(1); + assert_eq!(a.const_checked_mul(b), None); + + let b = $name::saturating_from_rational(1i32, -2i32); + let c = $name::saturating_from_integer(-21i32); + let d = $name::saturating_from_integer(42); + + assert_eq!(b.const_checked_mul(d), Some(c)); + + let minus_two = $name::saturating_from_integer(-2i32); + assert_eq!( + b.const_checked_mul($name::max_value()), + $name::max_value().const_checked_div(minus_two) + ); + assert_eq!( + b.const_checked_mul($name::min_value()), + $name::min_value().const_checked_div(minus_two) + ); + + let c = $name::saturating_from_integer(255u32); + assert_eq!(c.const_checked_mul($name::min_value()), None); + } + + let a = $name::saturating_from_rational(1i32, 2i32); + let c = $name::saturating_from_integer(255i32); + + assert_eq!(a.const_checked_mul(42.into()), Some(21.into())); + assert_eq!(c.const_checked_mul(2.into()), Some(510.into())); + assert_eq!(c.const_checked_mul($name::max_value()), None); + assert_eq!( + a.const_checked_mul($name::max_value()), + $name::max_value().checked_div(&2.into()) + ); + assert_eq!( + a.const_checked_mul($name::min_value()), + $name::min_value().const_checked_div($name::saturating_from_integer(2)) + ); + } + #[test] fn checked_div_int_works() { let inner_max = <$name as FixedPointNumber>::Inner::max_value(); @@ -1731,6 +2183,15 @@ implement_fixed!( "_Fixed Point 64 bits signed, range = [-9223372036.854775808, 9223372036.854775807]_", ); +implement_fixed!( + FixedU64, + test_fixed_u64, + u64, + false, + 1_000_000_000, + "_Fixed Point 64 bits unsigned, range = [0.000000000, 18446744073.709551615]_", +); + implement_fixed!( FixedI128, test_fixed_i128, diff --git a/primitives/arithmetic/src/helpers_128bit.rs b/primitives/arithmetic/src/helpers_128bit.rs index bbf69ea359fe0..7938c31d1eaa6 100644 --- a/primitives/arithmetic/src/helpers_128bit.rs +++ b/primitives/arithmetic/src/helpers_128bit.rs @@ -1,6 +1,7 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Some code is based upon Derek Dreery's IntegerSquareRoot impl, used under license. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,13 +21,8 @@ //! assumptions of a bigger type (u128) being available, or simply create a per-thing and use the //! multiplication implementation provided there. -use crate::biguint; -use num_traits::Zero; -use sp_std::{ - cmp::{max, min}, - convert::TryInto, - mem, -}; +use crate::{biguint, Rounding}; +use sp_std::cmp::{max, min}; /// Helper gcd function used in Rational128 implementation. pub fn gcd(a: u128, b: u128) -> u128 { @@ -60,61 +56,253 @@ pub fn to_big_uint(x: u128) -> biguint::BigUint { n } -/// Safely and accurately compute `a * b / c`. The approach is: -/// - Simply try `a * b / c`. -/// - Else, convert them both into big numbers and re-try. `Err` is returned if the result cannot -/// be safely casted back to u128. -/// -/// Invariant: c must be greater than or equal to 1. -pub fn multiply_by_rational(mut a: u128, mut b: u128, mut c: u128) -> Result { - if a.is_zero() || b.is_zero() { - return Ok(Zero::zero()) - } - c = c.max(1); - - // a and b are interchangeable by definition in this function. It always helps to assume the - // bigger of which is being multiplied by a `0 < b/c < 1`. Hence, a should be the bigger and - // b the smaller one. - if b > a { - mem::swap(&mut a, &mut b); - } - - // Attempt to perform the division first - if a % c == 0 { - a /= c; - c = 1; - } else if b % c == 0 { - b /= c; - c = 1; - } - - if let Some(x) = a.checked_mul(b) { - // This is the safest way to go. Try it. - Ok(x / c) - } else { - let a_num = to_big_uint(a); - let b_num = to_big_uint(b); - let c_num = to_big_uint(c); - - let mut ab = a_num * b_num; - ab.lstrip(); - let mut q = if c_num.len() == 1 { - // PROOF: if `c_num.len() == 1` then `c` fits in one limb. - ab.div_unit(c as biguint::Single) - } else { - // PROOF: both `ab` and `c` cannot have leading zero limbs; if length of `c` is 1, - // the previous branch would handle. Also, if ab for sure has a bigger size than - // c, because `a.checked_mul(b)` has failed, hence ab must be at least one limb - // bigger than c. In this case, returning zero is defensive-only and div should - // always return Some. - let (mut q, r) = ab.div(&c_num, true).unwrap_or((Zero::zero(), Zero::zero())); - let r: u128 = r.try_into().expect("reminder of div by c is always less than c; qed"); - if r > (c / 2) { - q = q.add(&to_big_uint(1)); +mod double128 { + // Inspired by: https://medium.com/wicketh/mathemagic-512-bit-division-in-solidity-afa55870a65 + + /// Returns the least significant 64 bits of a + const fn low_64(a: u128) -> u128 { + a & ((1 << 64) - 1) + } + + /// Returns the most significant 64 bits of a + const fn high_64(a: u128) -> u128 { + a >> 64 + } + + /// Returns 2^128 - a (two's complement) + const fn neg128(a: u128) -> u128 { + (!a).wrapping_add(1) + } + + /// Returns 2^128 / a + const fn div128(a: u128) -> u128 { + (neg128(a) / a).wrapping_add(1) + } + + /// Returns 2^128 % a + const fn mod128(a: u128) -> u128 { + neg128(a) % a + } + + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Double128 { + high: u128, + low: u128, + } + + impl Double128 { + pub const fn try_into_u128(self) -> Result { + match self.high { + 0 => Ok(self.low), + _ => Err(()), + } + } + + pub const fn zero() -> Self { + Self { high: 0, low: 0 } + } + + /// Return a `Double128` value representing the `scaled_value << 64`. + /// + /// This means the lower half of the `high` component will be equal to the upper 64-bits of + /// `scaled_value` (in the lower positions) and the upper half of the `low` component will + /// be equal to the lower 64-bits of `scaled_value`. + pub const fn left_shift_64(scaled_value: u128) -> Self { + Self { high: scaled_value >> 64, low: scaled_value << 64 } + } + + /// Construct a value from the upper 128 bits only, with the lower being zeroed. + pub const fn from_low(low: u128) -> Self { + Self { high: 0, low } + } + + /// Returns the same value ignoring anything in the high 128-bits. + pub const fn low_part(self) -> Self { + Self { high: 0, ..self } + } + + /// Returns a*b (in 256 bits) + pub const fn product_of(a: u128, b: u128) -> Self { + // Split a and b into hi and lo 64-bit parts + let (a_low, a_high) = (low_64(a), high_64(a)); + let (b_low, b_high) = (low_64(b), high_64(b)); + // a = (a_low + a_high << 64); b = (b_low + b_high << 64); + // ergo a*b = (a_low + a_high << 64)(b_low + b_high << 64) + // = a_low * b_low + // + a_low * b_high << 64 + // + a_high << 64 * b_low + // + a_high << 64 * b_high << 64 + // assuming: + // f = a_low * b_low + // o = a_low * b_high + // i = a_high * b_low + // l = a_high * b_high + // then: + // a*b = (o+i) << 64 + f + l << 128 + let (f, o, i, l) = (a_low * b_low, a_low * b_high, a_high * b_low, a_high * b_high); + let fl = Self { high: l, low: f }; + let i = Self::left_shift_64(i); + let o = Self::left_shift_64(o); + fl.add(i).add(o) + } + + pub const fn add(self, b: Self) -> Self { + let (low, overflow) = self.low.overflowing_add(b.low); + let carry = overflow as u128; // 1 if true, 0 if false. + let high = self.high.wrapping_add(b.high).wrapping_add(carry as u128); + Double128 { high, low } + } + + pub const fn div(mut self, rhs: u128) -> (Self, u128) { + if rhs == 1 { + return (self, 0) + } + + // (self === a; rhs === b) + // Calculate a / b + // = (a_high << 128 + a_low) / b + // let (q, r) = (div128(b), mod128(b)); + // = (a_low * (q * b + r)) + a_high) / b + // = (a_low * q * b + a_low * r + a_high)/b + // = (a_low * r + a_high) / b + a_low * q + let (q, r) = (div128(rhs), mod128(rhs)); + + // x = current result + // a = next number + let mut x = Self::zero(); + while self.high != 0 { + // x += a.low * q + x = x.add(Self::product_of(self.high, q)); + // a = a.low * r + a.high + self = Self::product_of(self.high, r).add(self.low_part()); } - q + + (x.add(Self::from_low(self.low / rhs)), self.low % rhs) + } + } +} + +/// Returns `a * b / c` and `(a * b) % c` (wrapping to 128 bits) or `None` in the case of +/// overflow and c = 0. +pub const fn multiply_by_rational_with_rounding( + a: u128, + b: u128, + c: u128, + r: Rounding, +) -> Option { + use double128::Double128; + if c == 0 { + return None + } + let (result, remainder) = Double128::product_of(a, b).div(c); + let mut result: u128 = match result.try_into_u128() { + Ok(v) => v, + Err(_) => return None, + }; + if match r { + Rounding::Up => remainder > 0, + // cannot be `(c + 1) / 2` since `c` might be `max_value` and overflow. + Rounding::NearestPrefUp => remainder >= c / 2 + c % 2, + Rounding::NearestPrefDown => remainder > c / 2, + Rounding::Down => false, + } { + result = match result.checked_add(1) { + Some(v) => v, + None => return None, }; - q.lstrip(); - q.try_into().map_err(|_| "result cannot fit in u128") + } + Some(result) +} + +pub const fn sqrt(mut n: u128) -> u128 { + // Modified from https://github.com/derekdreery/integer-sqrt-rs (Apache/MIT). + if n == 0 { + return 0 + } + + // Compute bit, the largest power of 4 <= n + let max_shift: u32 = 0u128.leading_zeros() - 1; + let shift: u32 = (max_shift - n.leading_zeros()) & !1; + let mut bit = 1u128 << shift; + + // Algorithm based on the implementation in: + // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_(base_2) + // Note that result/bit are logically unsigned (even if T is signed). + let mut result = 0u128; + while bit != 0 { + if n >= result + bit { + n -= result + bit; + result = (result >> 1) + bit; + } else { + result = result >> 1; + } + bit = bit >> 2; + } + result +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::{Decode, Encode}; + use multiply_by_rational_with_rounding as mulrat; + use Rounding::*; + + const MAX: u128 = u128::max_value(); + + #[test] + fn rational_multiply_basic_rounding_works() { + assert_eq!(mulrat(1, 1, 1, Up), Some(1)); + assert_eq!(mulrat(3, 1, 3, Up), Some(1)); + assert_eq!(mulrat(1, 1, 3, Up), Some(1)); + assert_eq!(mulrat(1, 2, 3, Down), Some(0)); + assert_eq!(mulrat(1, 1, 3, NearestPrefDown), Some(0)); + assert_eq!(mulrat(1, 1, 2, NearestPrefDown), Some(0)); + assert_eq!(mulrat(1, 2, 3, NearestPrefDown), Some(1)); + assert_eq!(mulrat(1, 1, 3, NearestPrefUp), Some(0)); + assert_eq!(mulrat(1, 1, 2, NearestPrefUp), Some(1)); + assert_eq!(mulrat(1, 2, 3, NearestPrefUp), Some(1)); + } + + #[test] + fn rational_multiply_big_number_works() { + assert_eq!(mulrat(MAX, MAX - 1, MAX, Down), Some(MAX - 1)); + assert_eq!(mulrat(MAX, 1, MAX, Down), Some(1)); + assert_eq!(mulrat(MAX, MAX - 1, MAX, Up), Some(MAX - 1)); + assert_eq!(mulrat(MAX, 1, MAX, Up), Some(1)); + assert_eq!(mulrat(1, MAX - 1, MAX, Down), Some(0)); + assert_eq!(mulrat(1, 1, MAX, Up), Some(1)); + assert_eq!(mulrat(1, MAX / 2, MAX, NearestPrefDown), Some(0)); + assert_eq!(mulrat(1, MAX / 2 + 1, MAX, NearestPrefDown), Some(1)); + assert_eq!(mulrat(1, MAX / 2, MAX, NearestPrefUp), Some(0)); + assert_eq!(mulrat(1, MAX / 2 + 1, MAX, NearestPrefUp), Some(1)); + } + + #[test] + fn sqrt_works() { + for i in 0..100_000u32 { + let a = sqrt(random_u128(i)); + assert_eq!(sqrt(a * a), a); + } + } + + fn random_u128(seed: u32) -> u128 { + u128::decode(&mut &seed.using_encoded(sp_core::hashing::twox_128)[..]).unwrap_or(0) + } + + #[test] + fn op_checked_rounded_div_works() { + for i in 0..100_000u32 { + let a = random_u128(i); + let b = random_u128(i + (1 << 30)); + let c = random_u128(i + (1 << 31)); + let x = mulrat(a, b, c, NearestPrefDown); + let y = multiply_by_rational_with_rounding(a, b, c, Rounding::NearestPrefDown); + assert_eq!(x.is_some(), y.is_some()); + let x = x.unwrap_or(0); + let y = y.unwrap_or(0); + let d = x.max(y) - x.min(y); + assert_eq!(d, 0); + } } } diff --git a/primitives/arithmetic/src/lib.rs b/primitives/arithmetic/src/lib.rs index 8671ceb0396e7..d7b326164b7d3 100644 --- a/primitives/arithmetic/src/lib.rs +++ b/primitives/arithmetic/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,10 +41,13 @@ pub mod rational; pub mod traits; pub use fixed_point::{FixedI128, FixedI64, FixedPointNumber, FixedPointOperand, FixedU128}; -pub use per_things::{InnerOf, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, UpperOf}; +pub use per_things::{ + InnerOf, MultiplyArg, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, RationalArg, + ReciprocalArg, Rounding, SignedRounding, UpperOf, +}; pub use rational::{Rational128, RationalInfinite}; -use sp_std::{cmp::Ordering, convert::TryInto, fmt::Debug, prelude::*}; +use sp_std::{cmp::Ordering, fmt::Debug, prelude::*}; use traits::{BaseArithmetic, One, SaturatedConversion, Unsigned, Zero}; /// Trait for comparing two numbers with an threshold. @@ -55,7 +58,7 @@ use traits::{BaseArithmetic, One, SaturatedConversion, Unsigned, Zero}; /// - `Ordering::Equal` otherwise. pub trait ThresholdOrd { /// Compare if `self` is `threshold` greater or less than `other`. - fn tcmp(&self, other: &T, epsilon: T) -> Ordering; + fn tcmp(&self, other: &T, threshold: T) -> Ordering; } impl ThresholdOrd for T @@ -65,7 +68,7 @@ where fn tcmp(&self, other: &T, threshold: T) -> Ordering { // early exit. if threshold.is_zero() { - return self.cmp(&other) + return self.cmp(other) } let upper_bound = other.saturating_add(threshold); @@ -73,7 +76,7 @@ where if upper_bound <= lower_bound { // defensive only. Can never happen. - self.cmp(&other) + self.cmp(other) } else { // upper_bound is guaranteed now to be bigger than lower. match (self.cmp(&lower_bound), self.cmp(&upper_bound)) { @@ -113,10 +116,7 @@ impl_normalize_for_numeric!(u8, u16, u32, u64, u128); impl Normalizable

{ fn parse(input: ParseStream) -> Result { - let syn::group::$name { token, content } = syn::group::$parse(input)?; + let content; + let token = syn::$parse!(content in input); let content = content.parse()?; Ok($name { token, content }) } @@ -71,9 +72,9 @@ macro_rules! groups_impl { }; } -groups_impl!(Braces, Brace, Brace, parse_braces); -groups_impl!(Brackets, Bracket, Bracket, parse_brackets); -groups_impl!(Parens, Paren, Parenthesis, parse_parens); +groups_impl!(Braces, Brace, Brace, braced); +groups_impl!(Brackets, Bracket, Bracket, bracketed); +groups_impl!(Parens, Paren, Parenthesis, parenthesized); #[derive(Debug)] pub struct PunctuatedInner { diff --git a/frame/support/src/crypto.rs b/frame/support/src/crypto.rs new file mode 100644 index 0000000000000..182a784649d02 --- /dev/null +++ b/frame/support/src/crypto.rs @@ -0,0 +1,21 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Utilities for dealing with crypto primitives. Sometimes we need to use these from inside WASM +//! contracts, where crypto calculations have weak performance. + +pub mod ecdsa; diff --git a/frame/support/src/crypto/ecdsa.rs b/frame/support/src/crypto/ecdsa.rs new file mode 100644 index 0000000000000..a4a04acabe1df --- /dev/null +++ b/frame/support/src/crypto/ecdsa.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Simple ECDSA secp256k1 API. +//! +//! Provides an extension trait for [`sp_core::ecdsa::Public`] to do certain operations. + +use sp_core::{crypto::ByteArray, ecdsa::Public}; + +/// Extension trait for [`Public`] to be used from inside the runtime. +/// +/// # Note +/// +/// This is needed because host functions cannot be called from within +/// `sp_core` due to cyclic dependencies on `sp_io`. +pub trait ECDSAExt { + /// Returns Ethereum address calculated from this ECDSA public key. + fn to_eth_address(&self) -> Result<[u8; 20], ()>; +} + +impl ECDSAExt for Public { + fn to_eth_address(&self) -> Result<[u8; 20], ()> { + use k256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey}; + + PublicKey::from_sec1_bytes(self.as_slice()).map_err(drop).and_then(|pub_key| { + // uncompress the key + let uncompressed = pub_key.to_encoded_point(false); + // convert to ETH address + <[u8; 20]>::try_from( + sp_io::hashing::keccak_256(&uncompressed.as_bytes()[1..])[12..].as_ref(), + ) + .map_err(drop) + }) + } +} diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index 2e6777fee2af2..1696e9a63915e 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,10 @@ //! generating values representing lazy module function calls. pub use crate::{ - codec::{Codec, Decode, Encode, EncodeAsRef, EncodeLike, HasCompact, Input, Output}, + codec::{ + Codec, Decode, Encode, EncodeAsRef, EncodeLike, HasCompact, Input, MaxEncodedLen, Output, + }, + scale_info::TypeInfo, sp_std::{ fmt, marker, prelude::{Clone, Eq, PartialEq, Vec}, @@ -28,18 +31,22 @@ pub use crate::{ traits::{ CallMetadata, GetCallMetadata, GetCallName, GetStorageVersion, UnfilteredDispatchable, }, - weights::{ - ClassifyDispatch, DispatchInfo, GetDispatchInfo, PaysFee, PostDispatchInfo, - TransactionPriority, WeighData, Weight, WithPostDispatchInfo, - }, }; -pub use sp_runtime::{traits::Dispatchable, DispatchError}; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_runtime::{ + generic::{CheckedExtrinsic, UncheckedExtrinsic}, + traits::SignedExtension, +}; +pub use sp_runtime::{ + traits::Dispatchable, transaction_validity::TransactionPriority, DispatchError, RuntimeDebug, +}; +pub use sp_weights::Weight; /// The return type of a `Dispatchable` in frame. When returned explicitly from /// a dispatchable function it allows overriding the default `PostDispatchInfo` /// returned from a dispatch. -pub type DispatchResultWithPostInfo = - sp_runtime::DispatchResultWithInfo; +pub type DispatchResultWithPostInfo = sp_runtime::DispatchResultWithInfo; /// Unaugmented version of `DispatchResultWithPostInfo` that can be returned from /// dispatchable functions and is automatically converted to the augmented type. Should be @@ -48,17 +55,38 @@ pub type DispatchResultWithPostInfo = pub type DispatchResult = Result<(), sp_runtime::DispatchError>; /// The error type contained in a `DispatchResultWithPostInfo`. -pub type DispatchErrorWithPostInfo = - sp_runtime::DispatchErrorWithPostInfo; +pub type DispatchErrorWithPostInfo = sp_runtime::DispatchErrorWithPostInfo; /// Serializable version of pallet dispatchable. pub trait Callable { - type Call: UnfilteredDispatchable + Codec + Clone + PartialEq + Eq; + type RuntimeCall: UnfilteredDispatchable + Codec + Clone + PartialEq + Eq; } // dirty hack to work around serde_derive issue // https://github.com/rust-lang/rust/issues/51331 -pub type CallableCallFor = >::Call; +pub type CallableCallFor = >::RuntimeCall; + +/// Origin for the System pallet. +#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum RawOrigin { + /// The system itself ordained this dispatch to happen: this is the highest privilege level. + Root, + /// It is signed by some public key and we provide the `AccountId`. + Signed(AccountId), + /// It is signed by nobody, can be either: + /// * included and agreed upon by the validators anyway, + /// * or unsigned transaction validated by a pallet. + None, +} + +impl From> for RawOrigin { + fn from(s: Option) -> RawOrigin { + match s { + Some(who) => RawOrigin::Signed(who), + None => RawOrigin::None, + } + } +} /// A type that can be used as a parameter in a dispatchable function. /// @@ -66,6 +94,554 @@ pub type CallableCallFor = >::Call; pub trait Parameter: Codec + EncodeLike + Clone + Eq + fmt::Debug + scale_info::TypeInfo {} impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + scale_info::TypeInfo {} +/// Means of classifying a dispatchable function. +pub trait ClassifyDispatch { + /// Classify the dispatch function based on input data `target` of type `T`. When implementing + /// this for a dispatchable, `T` will be a tuple of all arguments given to the function (except + /// origin). + fn classify_dispatch(&self, target: T) -> DispatchClass; +} + +/// Indicates if dispatch function should pay fees or not. +/// +/// If set to `Pays::No`, the block resource limits are applied, yet no fee is deducted. +pub trait PaysFee { + fn pays_fee(&self, _target: T) -> Pays; +} + +/// Explicit enum to denote if a transaction pays fee or not. +#[derive(Clone, Copy, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)] +pub enum Pays { + /// Transactor will pay related fees. + Yes, + /// Transactor will NOT pay related fees. + No, +} + +impl Default for Pays { + fn default() -> Self { + Self::Yes + } +} + +impl From for PostDispatchInfo { + fn from(pays_fee: Pays) -> Self { + Self { actual_weight: None, pays_fee } + } +} + +/// A generalized group of dispatch types. +/// +/// NOTE whenever upgrading the enum make sure to also update +/// [DispatchClass::all] and [DispatchClass::non_mandatory] helper functions. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum DispatchClass { + /// A normal dispatch. + Normal, + /// An operational dispatch. + Operational, + /// A mandatory dispatch. These kinds of dispatch are always included regardless of their + /// weight, therefore it is critical that they are separately validated to ensure that a + /// malicious validator cannot craft a valid but impossibly heavy block. Usually this just + /// means ensuring that the extrinsic can only be included once and that it is always very + /// light. + /// + /// Do *NOT* use it for extrinsics that can be heavy. + /// + /// The only real use case for this is inherent extrinsics that are required to execute in a + /// block for the block to be valid, and it solves the issue in the case that the block + /// initialization is sufficiently heavy to mean that those inherents do not fit into the + /// block. Essentially, we assume that in these exceptional circumstances, it is better to + /// allow an overweight block to be created than to not allow any block at all to be created. + Mandatory, +} + +impl Default for DispatchClass { + fn default() -> Self { + Self::Normal + } +} + +impl DispatchClass { + /// Returns an array containing all dispatch classes. + pub fn all() -> &'static [DispatchClass] { + &[DispatchClass::Normal, DispatchClass::Operational, DispatchClass::Mandatory] + } + + /// Returns an array of all dispatch classes except `Mandatory`. + pub fn non_mandatory() -> &'static [DispatchClass] { + &[DispatchClass::Normal, DispatchClass::Operational] + } +} + +/// A trait that represents one or many values of given type. +/// +/// Useful to accept as parameter type to let the caller pass either a single value directly +/// or an iterator. +pub trait OneOrMany { + /// The iterator type. + type Iter: Iterator; + /// Convert this item into an iterator. + fn into_iter(self) -> Self::Iter; +} + +impl OneOrMany for DispatchClass { + type Iter = sp_std::iter::Once; + fn into_iter(self) -> Self::Iter { + sp_std::iter::once(self) + } +} + +impl<'a> OneOrMany for &'a [DispatchClass] { + type Iter = sp_std::iter::Cloned>; + fn into_iter(self) -> Self::Iter { + self.iter().cloned() + } +} + +/// A bundle of static information collected from the `#[pallet::weight]` attributes. +#[derive(Clone, Copy, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] +pub struct DispatchInfo { + /// Weight of this transaction. + pub weight: Weight, + /// Class of this transaction. + pub class: DispatchClass, + /// Does this transaction pay fees. + pub pays_fee: Pays, +} + +/// A `Dispatchable` function (aka transaction) that can carry some static information along with +/// it, using the `#[pallet::weight]` attribute. +pub trait GetDispatchInfo { + /// Return a `DispatchInfo`, containing relevant information of this dispatch. + /// + /// This is done independently of its encoded size. + fn get_dispatch_info(&self) -> DispatchInfo; +} + +impl GetDispatchInfo for () { + fn get_dispatch_info(&self) -> DispatchInfo { + DispatchInfo::default() + } +} + +/// Extract the actual weight from a dispatch result if any or fall back to the default weight. +pub fn extract_actual_weight(result: &DispatchResultWithPostInfo, info: &DispatchInfo) -> Weight { + match result { + Ok(post_info) => post_info, + Err(err) => &err.post_info, + } + .calc_actual_weight(info) +} + +/// Extract the actual pays_fee from a dispatch result if any or fall back to the default weight. +pub fn extract_actual_pays_fee(result: &DispatchResultWithPostInfo, info: &DispatchInfo) -> Pays { + match result { + Ok(post_info) => post_info, + Err(err) => &err.post_info, + } + .pays_fee(info) +} + +/// Weight information that is only available post dispatch. +/// NOTE: This can only be used to reduce the weight or fee, not increase it. +#[derive(Clone, Copy, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] +pub struct PostDispatchInfo { + /// Actual weight consumed by a call or `None` which stands for the worst case static weight. + pub actual_weight: Option, + /// Whether this transaction should pay fees when all is said and done. + pub pays_fee: Pays, +} + +impl PostDispatchInfo { + /// Calculate how much (if any) weight was not used by the `Dispatchable`. + pub fn calc_unspent(&self, info: &DispatchInfo) -> Weight { + info.weight - self.calc_actual_weight(info) + } + + /// Calculate how much weight was actually spent by the `Dispatchable`. + pub fn calc_actual_weight(&self, info: &DispatchInfo) -> Weight { + if let Some(actual_weight) = self.actual_weight { + actual_weight.min(info.weight) + } else { + info.weight + } + } + + /// Determine if user should actually pay fees at the end of the dispatch. + pub fn pays_fee(&self, info: &DispatchInfo) -> Pays { + // If they originally were not paying fees, or the post dispatch info + // says they should not pay fees, then they don't pay fees. + // This is because the pre dispatch information must contain the + // worst case for weight and fees paid. + if info.pays_fee == Pays::No || self.pays_fee == Pays::No { + Pays::No + } else { + // Otherwise they pay. + Pays::Yes + } + } +} + +impl From<()> for PostDispatchInfo { + fn from(_: ()) -> Self { + Self { actual_weight: None, pays_fee: Default::default() } + } +} + +impl sp_runtime::traits::Printable for PostDispatchInfo { + fn print(&self) { + "actual_weight=".print(); + match self.actual_weight { + Some(weight) => weight.print(), + None => "max-weight".print(), + }; + "pays_fee=".print(); + match self.pays_fee { + Pays::Yes => "Yes".print(), + Pays::No => "No".print(), + } + } +} + +/// Allows easy conversion from `DispatchError` to `DispatchErrorWithPostInfo` for dispatchables +/// that want to return a custom a posterior weight on error. +pub trait WithPostDispatchInfo { + /// Call this on your modules custom errors type in order to return a custom weight on error. + /// + /// # Example + /// + /// ```ignore + /// let who = ensure_signed(origin).map_err(|e| e.with_weight(Weight::from_ref_time(100)))?; + /// ensure!(who == me, Error::::NotMe.with_weight(200_000)); + /// ``` + fn with_weight(self, actual_weight: Weight) -> DispatchErrorWithPostInfo; +} + +impl WithPostDispatchInfo for T +where + T: Into, +{ + fn with_weight(self, actual_weight: Weight) -> DispatchErrorWithPostInfo { + DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(actual_weight), + pays_fee: Default::default(), + }, + error: self.into(), + } + } +} + +/// Implementation for unchecked extrinsic. +impl GetDispatchInfo + for UncheckedExtrinsic +where + Call: GetDispatchInfo, + Extra: SignedExtension, +{ + fn get_dispatch_info(&self) -> DispatchInfo { + self.function.get_dispatch_info() + } +} + +/// Implementation for checked extrinsic. +impl GetDispatchInfo for CheckedExtrinsic +where + Call: GetDispatchInfo, +{ + fn get_dispatch_info(&self) -> DispatchInfo { + self.function.get_dispatch_info() + } +} + +/// Implementation for test extrinsic. +#[cfg(feature = "std")] +impl GetDispatchInfo + for sp_runtime::testing::TestXt +{ + fn get_dispatch_info(&self) -> DispatchInfo { + // for testing: weight == size. + DispatchInfo { + weight: Weight::from_ref_time(self.encode().len() as _), + pays_fee: Pays::Yes, + class: self.call.get_dispatch_info().class, + } + } +} + +/// A struct holding value for each `DispatchClass`. +#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub struct PerDispatchClass { + /// Value for `Normal` extrinsics. + normal: T, + /// Value for `Operational` extrinsics. + operational: T, + /// Value for `Mandatory` extrinsics. + mandatory: T, +} + +impl PerDispatchClass { + /// Create new `PerDispatchClass` with the same value for every class. + pub fn new(val: impl Fn(DispatchClass) -> T) -> Self { + Self { + normal: val(DispatchClass::Normal), + operational: val(DispatchClass::Operational), + mandatory: val(DispatchClass::Mandatory), + } + } + + /// Get a mutable reference to current value of given class. + pub fn get_mut(&mut self, class: DispatchClass) -> &mut T { + match class { + DispatchClass::Operational => &mut self.operational, + DispatchClass::Normal => &mut self.normal, + DispatchClass::Mandatory => &mut self.mandatory, + } + } + + /// Get current value for given class. + pub fn get(&self, class: DispatchClass) -> &T { + match class { + DispatchClass::Normal => &self.normal, + DispatchClass::Operational => &self.operational, + DispatchClass::Mandatory => &self.mandatory, + } + } +} + +impl PerDispatchClass { + /// Set the value of given class. + pub fn set(&mut self, new: T, class: impl OneOrMany) { + for class in class.into_iter() { + *self.get_mut(class) = new.clone(); + } + } +} + +impl PerDispatchClass { + /// Returns the total weight consumed by all extrinsics in the block. + pub fn total(&self) -> Weight { + let mut sum = Weight::zero(); + for class in DispatchClass::all() { + sum = sum.saturating_add(*self.get(*class)); + } + sum + } + + /// Add some weight of a specific dispatch class, saturating at the numeric bounds of `Weight`. + pub fn add(&mut self, weight: Weight, class: DispatchClass) { + let value = self.get_mut(class); + *value = value.saturating_add(weight); + } + + /// Try to add some weight of a specific dispatch class, returning Err(()) if overflow would + /// occur. + pub fn checked_add(&mut self, weight: Weight, class: DispatchClass) -> Result<(), ()> { + let value = self.get_mut(class); + *value = value.checked_add(&weight).ok_or(())?; + Ok(()) + } + + /// Subtract some weight of a specific dispatch class, saturating at the numeric bounds of + /// `Weight`. + pub fn sub(&mut self, weight: Weight, class: DispatchClass) { + let value = self.get_mut(class); + *value = value.saturating_sub(weight); + } +} + +/// Means of weighing some particular kind of data (`T`). +pub trait WeighData { + /// Weigh the data `T` given by `target`. When implementing this for a dispatchable, `T` will be + /// a tuple of all arguments given to the function (except origin). + fn weigh_data(&self, target: T) -> Weight; +} + +impl WeighData for Weight { + fn weigh_data(&self, _: T) -> Weight { + return *self + } +} + +impl PaysFee for (Weight, DispatchClass, Pays) { + fn pays_fee(&self, _: T) -> Pays { + self.2 + } +} + +impl WeighData for (Weight, DispatchClass) { + fn weigh_data(&self, args: T) -> Weight { + return self.0.weigh_data(args) + } +} + +impl WeighData for (Weight, DispatchClass, Pays) { + fn weigh_data(&self, args: T) -> Weight { + return self.0.weigh_data(args) + } +} + +impl ClassifyDispatch for (Weight, DispatchClass) { + fn classify_dispatch(&self, _: T) -> DispatchClass { + self.1 + } +} + +impl PaysFee for (Weight, DispatchClass) { + fn pays_fee(&self, _: T) -> Pays { + Pays::Yes + } +} + +impl WeighData for (Weight, Pays) { + fn weigh_data(&self, args: T) -> Weight { + return self.0.weigh_data(args) + } +} + +impl ClassifyDispatch for (Weight, Pays) { + fn classify_dispatch(&self, _: T) -> DispatchClass { + DispatchClass::Normal + } +} + +impl PaysFee for (Weight, Pays) { + fn pays_fee(&self, _: T) -> Pays { + self.1 + } +} + +impl From<(Option, Pays)> for PostDispatchInfo { + fn from(post_weight_info: (Option, Pays)) -> Self { + let (actual_weight, pays_fee) = post_weight_info; + Self { actual_weight, pays_fee } + } +} + +impl From> for PostDispatchInfo { + fn from(actual_weight: Option) -> Self { + Self { actual_weight, pays_fee: Default::default() } + } +} + +impl ClassifyDispatch for Weight { + fn classify_dispatch(&self, _: T) -> DispatchClass { + DispatchClass::Normal + } +} + +impl PaysFee for Weight { + fn pays_fee(&self, _: T) -> Pays { + Pays::Yes + } +} + +impl ClassifyDispatch for (Weight, DispatchClass, Pays) { + fn classify_dispatch(&self, _: T) -> DispatchClass { + self.1 + } +} + +// TODO: Eventually remove these + +impl From> for PostDispatchInfo { + fn from(maybe_actual_computation: Option) -> Self { + let actual_weight = match maybe_actual_computation { + Some(actual_computation) => Some(Weight::zero().set_ref_time(actual_computation)), + None => None, + }; + Self { actual_weight, pays_fee: Default::default() } + } +} + +impl From<(Option, Pays)> for PostDispatchInfo { + fn from(post_weight_info: (Option, Pays)) -> Self { + let (maybe_actual_time, pays_fee) = post_weight_info; + let actual_weight = match maybe_actual_time { + Some(actual_time) => Some(Weight::zero().set_ref_time(actual_time)), + None => None, + }; + Self { actual_weight, pays_fee } + } +} + +impl ClassifyDispatch for u64 { + fn classify_dispatch(&self, _: T) -> DispatchClass { + DispatchClass::Normal + } +} + +impl PaysFee for u64 { + fn pays_fee(&self, _: T) -> Pays { + Pays::Yes + } +} + +impl WeighData for u64 { + fn weigh_data(&self, _: T) -> Weight { + return Weight::zero().set_ref_time(*self) + } +} + +impl WeighData for (u64, DispatchClass, Pays) { + fn weigh_data(&self, args: T) -> Weight { + return self.0.weigh_data(args) + } +} + +impl ClassifyDispatch for (u64, DispatchClass, Pays) { + fn classify_dispatch(&self, _: T) -> DispatchClass { + self.1 + } +} + +impl PaysFee for (u64, DispatchClass, Pays) { + fn pays_fee(&self, _: T) -> Pays { + self.2 + } +} + +impl WeighData for (u64, DispatchClass) { + fn weigh_data(&self, args: T) -> Weight { + return self.0.weigh_data(args) + } +} + +impl ClassifyDispatch for (u64, DispatchClass) { + fn classify_dispatch(&self, _: T) -> DispatchClass { + self.1 + } +} + +impl PaysFee for (u64, DispatchClass) { + fn pays_fee(&self, _: T) -> Pays { + Pays::Yes + } +} + +impl WeighData for (u64, Pays) { + fn weigh_data(&self, args: T) -> Weight { + return self.0.weigh_data(args) + } +} + +impl ClassifyDispatch for (u64, Pays) { + fn classify_dispatch(&self, _: T) -> DispatchClass { + DispatchClass::Normal + } +} + +impl PaysFee for (u64, Pays) { + fn pays_fee(&self, _: T) -> Pays { + self.1 + } +} + +// END TODO + /// Declares a `Module` struct and a `Call` enum, which implements the dispatch logic. /// /// ## Declaration @@ -76,7 +652,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// # use frame_support::dispatch; /// # use frame_system::{Config, ensure_signed}; /// decl_module! { -/// pub struct Module for enum Call where origin: T::Origin { +/// pub struct Module for enum Call where origin: T::RuntimeOrigin { /// /// // Private functions are dispatchable, but not available to other /// // FRAME pallets. @@ -103,7 +679,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// * `Module`: The struct generated by the macro, with type `Config`. /// * `Call`: The enum generated for every pallet, which implements /// [`Callable`](./dispatch/trait.Callable.html). -/// * `origin`: Alias of `T::Origin`. +/// * `origin`: Alias of `T::RuntimeOrigin`. /// * `Result`: The expected return type from pallet functions. /// /// The first parameter of dispatchable functions must always be `origin`. @@ -119,7 +695,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// # use frame_support::dispatch; /// # use frame_system::{Config, ensure_signed}; /// decl_module! { -/// pub struct Module for enum Call where origin: T::Origin { +/// pub struct Module for enum Call where origin: T::RuntimeOrigin { /// #[weight = 0] /// fn my_long_function(origin) -> dispatch::DispatchResult { /// // Your implementation @@ -151,18 +727,18 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// ``` /// # #[macro_use] /// # extern crate frame_support; -/// # use frame_support::dispatch::{DispatchResultWithPostInfo, WithPostDispatchInfo}; +/// # use frame_support::{weights::Weight, dispatch::{DispatchResultWithPostInfo, WithPostDispatchInfo}}; /// # use frame_system::{Config, ensure_signed}; /// decl_module! { -/// pub struct Module for enum Call where origin: T::Origin { +/// pub struct Module for enum Call where origin: T::RuntimeOrigin { /// #[weight = 1_000_000] /// fn my_long_function(origin, do_expensive_calc: bool) -> DispatchResultWithPostInfo { -/// ensure_signed(origin).map_err(|e| e.with_weight(100_000))?; +/// ensure_signed(origin).map_err(|e| e.with_weight(Weight::from_ref_time(100_000)))?; /// if do_expensive_calc { /// // do the expensive calculation /// // ... /// // return None to indicate that we are using all weight (the default) -/// return Ok(None.into()); +/// return Ok(None::.into()); /// } /// // expensive calculation not executed: use only a portion of the weight /// Ok(Some(100_000).into()) @@ -176,6 +752,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// /// Transactional function discards all changes to storage if it returns `Err`, or commits if /// `Ok`, via the #\[transactional\] attribute. Note the attribute must be after #\[weight\]. +/// The #\[transactional\] attribute is deprecated since it is the default behaviour. /// /// ``` /// # #[macro_use] @@ -183,7 +760,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// # use frame_support::transactional; /// # use frame_system::Config; /// decl_module! { -/// pub struct Module for enum Call where origin: T::Origin { +/// pub struct Module for enum Call where origin: T::RuntimeOrigin { /// #[weight = 0] /// #[transactional] /// fn my_short_function(origin) { @@ -204,7 +781,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// # use frame_support::dispatch; /// # use frame_system::{Config, ensure_signed, ensure_root}; /// decl_module! { -/// pub struct Module for enum Call where origin: T::Origin { +/// pub struct Module for enum Call where origin: T::RuntimeOrigin { /// #[weight = 0] /// fn my_privileged_function(origin) -> dispatch::DispatchResult { /// ensure_root(origin)?; @@ -244,7 +821,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// pub trait Config: frame_system::Config {} /// /// decl_module! { -/// pub struct Module, I: Instance = DefaultInstance> for enum Call where origin: T::Origin { +/// pub struct Module, I: Instance = DefaultInstance> for enum Call where origin: T::RuntimeOrigin { /// // Your implementation /// } /// } @@ -256,7 +833,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// /// ## Where clause /// -/// Besides the default `origin: T::Origin`, you can also pass other bounds to the module +/// Besides the default `origin: T::RuntimeOrigin`, you can also pass other bounds to the module /// declaration. This where bound will be replicated to all types generated by this macro. The /// chaining of multiple trait bounds with `+` is not supported. If multiple bounds for one type are /// required, it needs to be split up into multiple bounds. @@ -269,7 +846,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// pub trait Config: system::Config where Self::AccountId: From {} /// /// decl_module! { -/// pub struct Module for enum Call where origin: T::Origin, T::AccountId: From { +/// pub struct Module for enum Call where origin: T::RuntimeOrigin, T::AccountId: From { /// // Your implementation /// } /// } @@ -280,7 +857,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// /// The following are reserved function signatures: /// -/// * `deposit_event`: Helper function for depositing an [event](https://docs.substrate.dev/docs/event-enum). +/// * `deposit_event`: Helper function for depositing an [event](https://docs.substrate.io/main-docs/build/events-errors/). /// The default behavior is to call `deposit_event` from the [System /// module](../frame_system/index.html). However, you can write your own implementation for events /// in your runtime. To use the default behavior, add `fn deposit_event() = default;` to your @@ -1310,7 +1887,7 @@ macro_rules! decl_module { ) ); }; - // Ignore any ident which is not `origin` with type `T::Origin`. + // Ignore any ident which is not `origin` with type `T::RuntimeOrigin`. (@normalize $(#[$attr:meta])* pub struct $mod_type:ident<$trait_instance:ident: $trait_name:ident$(, I: $instantiable:path $(= $module_default_instance:path)?)?> @@ -1331,7 +1908,7 @@ macro_rules! decl_module { $(#[weight = $weight:expr])? $(#[$fn_attr:meta])* $fn_vis:vis fn $fn_name:ident( - $origin:ident : T::Origin $( , $( #[$codec_attr:ident] )* $param_name:ident : $param:ty )* $(,)? + $origin:ident : T::RuntimeOrigin $( , $( #[$codec_attr:ident] )* $param_name:ident : $param:ty )* $(,)? ) $( -> $result:ty )* { $( $impl:tt )* } $($rest:tt)* ) => { @@ -1446,7 +2023,11 @@ macro_rules! decl_module { $ignore:ident $mod_type:ident<$trait_instance:ident $(, $instance:ident)?> $fn_name:ident $origin:ident $system:ident [ $( $param_name:ident),* ] ) => { - <$mod_type<$trait_instance $(, $instance)?>>::$fn_name( $origin $(, $param_name )* ).map(Into::into).map_err(Into::into) + // We execute all dispatchable in a new storage layer, allowing them + // to return an error at any point, and undoing any storage changes. + $crate::storage::with_storage_layer(|| { + <$mod_type<$trait_instance $(, $instance)?>>::$fn_name( $origin $(, $param_name )* ).map(Into::into).map_err(Into::into) + }) }; // no `deposit_event` function wanted @@ -1467,7 +2048,7 @@ macro_rules! decl_module { { /// Deposits an event using `frame_system::Pallet::deposit_event`. $vis fn deposit_event( - event: impl Into<< $trait_instance as $trait_name $(<$instance>)? >::Event> + event: impl Into<< $trait_instance as $trait_name $(<$instance>)? >::RuntimeEvent> ) { <$system::Pallet<$trait_instance>>::deposit_event(event.into()) } @@ -1519,6 +2100,35 @@ macro_rules! decl_module { {} }; + (@impl_try_state_default + { $system:ident } + $module:ident<$trait_instance:ident: $trait_name:ident$(, $instance:ident: $instantiable:path)?>; + { $( $other_where_bounds:tt )* } + ) => { + #[cfg(feature = "try-runtime")] + impl<$trait_instance: $system::Config + $trait_name$(, $instance: $instantiable)?> + $crate::traits::TryState<<$trait_instance as $system::Config>::BlockNumber> + for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* + { + fn try_state( + _: <$trait_instance as $system::Config>::BlockNumber, + _: $crate::traits::TryStateSelect, + ) -> Result<(), &'static str> { + let pallet_name = << + $trait_instance + as + $system::Config + >::PalletInfo as $crate::traits::PalletInfo>::name::().unwrap_or(""); + $crate::log::debug!( + target: $crate::LOG_TARGET, + "⚠️ pallet {} cannot have try-state because it is using decl_module!", + pallet_name, + ); + Ok(()) + } + } + }; + (@impl_on_runtime_upgrade { $system:ident } $module:ident<$trait_instance:ident: $trait_name:ident$(, $instance:ident: $instantiable:path)?>; @@ -1546,16 +2156,16 @@ macro_rules! decl_module { ::current_storage_version(), ); - (|| { $( $impl )* })() + { $( $impl )* } } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { - Ok(()) + fn pre_upgrade() -> Result<$crate::sp_std::vec::Vec, &'static str> { + Ok($crate::sp_std::vec::Vec::new()) } #[cfg(feature = "try-runtime")] - fn post_upgrade() -> Result<(), &'static str> { + fn post_upgrade(_: $crate::sp_std::vec::Vec) -> Result<(), &'static str> { Ok(()) } } @@ -1584,16 +2194,16 @@ macro_rules! decl_module { pallet_name, ); - 0 + $crate::dispatch::Weight::zero() } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { - Ok(()) + fn pre_upgrade() -> Result<$crate::sp_std::vec::Vec, &'static str> { + Ok($crate::sp_std::vec::Vec::new()) } #[cfg(feature = "try-runtime")] - fn post_upgrade() -> Result<(), &'static str> { + fn post_upgrade(_: $crate::sp_std::vec::Vec) -> Result<(), &'static str> { Ok(()) } } @@ -1757,9 +2367,11 @@ macro_rules! decl_module { $vis fn $name( $origin: $origin_ty $(, $param: $param_ty )* ) -> $crate::dispatch::DispatchResult { - $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); - { $( $impl )* } - Ok(()) + $crate::storage::with_storage_layer(|| { + $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); + { $( $impl )* } + Ok(()) + }) } }; @@ -1775,8 +2387,10 @@ macro_rules! decl_module { ) => { $(#[$fn_attr])* $vis fn $name($origin: $origin_ty $(, $param: $param_ty )* ) -> $result { - $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); - $( $impl )* + $crate::storage::with_storage_layer(|| { + $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); + $( $impl )* + }) } }; @@ -1980,6 +2594,10 @@ macro_rules! decl_module { pub type Pallet<$trait_instance $(, $instance $( = $module_default_instance)?)?> = $mod_type<$trait_instance $(, $instance)?>; + $crate::__create_tt_macro! { + tt_error_token, + } + $crate::decl_module! { @impl_on_initialize { $system } @@ -1988,6 +2606,13 @@ macro_rules! decl_module { $( $on_initialize )* } + $crate::decl_module! { + @impl_try_state_default + { $system } + $mod_type<$trait_instance: $trait_name $(, $instance: $instantiable)?>; + { $( $other_where_bounds )* } + } + $crate::decl_module! { @impl_on_runtime_upgrade { $system } @@ -2151,6 +2776,34 @@ macro_rules! decl_module { .expect("Pallet is part of the runtime because pallet `Config` trait is \ implemented by the runtime") } + + fn module_name() -> &'static str { + < + <$trait_instance as $system::Config>::PalletInfo as $crate::traits::PalletInfo + >::module_name::() + .expect("Pallet is part of the runtime because pallet `Config` trait is \ + implemented by the runtime") + } + + fn crate_version() -> $crate::traits::CrateVersion { + $crate::crate_to_crate_version!() + } + } + + impl<$trait_instance: $trait_name $(, $instance: $instantiable)?> $crate::traits::PalletsInfoAccess + for $mod_type<$trait_instance $(, $instance)?> where $( $other_where_bounds )* + { + fn count() -> usize { 1 } + fn infos() -> $crate::sp_std::vec::Vec<$crate::traits::PalletInfoData> { + use $crate::traits::PalletInfoAccess; + let item = $crate::traits::PalletInfoData { + index: Self::index(), + name: Self::name(), + module_name: Self::module_name(), + crate_version: Self::crate_version(), + }; + vec![item] + } } // Implement GetCallName for the Call. @@ -2255,8 +2908,8 @@ macro_rules! decl_module { impl<$trait_instance: $trait_name $(, $instance: $instantiable)?> $crate::traits::UnfilteredDispatchable for $call_type<$trait_instance $(, $instance)?> where $( $other_where_bounds )* { - type Origin = $origin_type; - fn dispatch_bypass_filter(self, _origin: Self::Origin) -> $crate::dispatch::DispatchResultWithPostInfo { + type RuntimeOrigin = $origin_type; + fn dispatch_bypass_filter(self, _origin: Self::RuntimeOrigin) -> $crate::dispatch::DispatchResultWithPostInfo { match self { $( $call_type::$fn_name { $( $param_name ),* } => { @@ -2274,7 +2927,7 @@ macro_rules! decl_module { impl<$trait_instance: $trait_name $(, $instance: $instantiable)?> $crate::dispatch::Callable<$trait_instance> for $mod_type<$trait_instance $(, $instance)?> where $( $other_where_bounds )* { - type Call = $call_type<$trait_instance $(, $instance)?>; + type RuntimeCall = $call_type<$trait_instance $(, $instance)?>; } $crate::__dispatch_impl_metadata! { @@ -2527,13 +3180,14 @@ macro_rules! __check_reserved_fn_name { mod tests { use super::*; use crate::{ + dispatch::{DispatchClass, DispatchInfo, Pays}, metadata::*, traits::{ - Get, GetCallName, IntegrityTest, OnFinalize, OnIdle, OnInitialize, OnRuntimeUpgrade, - PalletInfo, + CallerTrait, CrateVersion, Get, GetCallName, IntegrityTest, OnFinalize, OnIdle, + OnInitialize, OnRuntimeUpgrade, PalletInfo, }, - weights::{DispatchClass, DispatchInfo, Pays, RuntimeDbWeight}, }; + use sp_weights::RuntimeDbWeight; pub trait Config: system::Config + Sized where @@ -2546,35 +3200,21 @@ mod tests { pub trait Config: 'static { type AccountId; - type Call; + type RuntimeCall; type BaseCallFilter; - type Origin: crate::traits::OriginTrait; + type RuntimeOrigin: crate::traits::OriginTrait; type BlockNumber: Into; type PalletInfo: crate::traits::PalletInfo; type DbWeight: Get; } - #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, scale_info::TypeInfo)] - pub enum RawOrigin { - Root, - Signed(AccountId), - None, - } - - impl From> for RawOrigin { - fn from(s: Option) -> RawOrigin { - match s { - Some(who) => RawOrigin::Signed(who), - None => RawOrigin::None, - } - } - } + pub use super::super::RawOrigin; pub type Origin = RawOrigin<::AccountId>; } decl_module! { - pub struct Module for enum Call where origin: T::Origin, system = system, T::AccountId: From { + pub struct Module for enum Call where origin: T::RuntimeOrigin, system = system, T::AccountId: From { /// Hi, this is a comment. #[weight = 0] fn aux_0(_origin) -> DispatchResult { unreachable!() } @@ -2597,20 +3237,20 @@ mod tests { #[weight = (5, DispatchClass::Operational)] fn operational(_origin) { unreachable!() } - fn on_initialize(n: T::BlockNumber,) -> Weight { if n.into() == 42 { panic!("on_initialize") } 7 } + fn on_initialize(n: T::BlockNumber,) -> Weight { if n.into() == 42 { panic!("on_initialize") } Weight::from_ref_time(7) } fn on_idle(n: T::BlockNumber, remaining_weight: Weight,) -> Weight { - if n.into() == 42 || remaining_weight == 42 { panic!("on_idle") } - 7 + if n.into() == 42 || remaining_weight == Weight::from_ref_time(42) { panic!("on_idle") } + Weight::from_ref_time(7) } fn on_finalize(n: T::BlockNumber,) { if n.into() == 42 { panic!("on_finalize") } } - fn on_runtime_upgrade() -> Weight { 10 } + fn on_runtime_upgrade() -> Weight { Weight::from_ref_time(10) } fn offchain_worker() {} /// Some doc fn integrity_test() { panic!("integrity_test") } } } - #[derive(scale_info::TypeInfo)] + #[derive(Eq, PartialEq, Clone, crate::RuntimeDebug, scale_info::TypeInfo)] pub struct TraitImpl {} impl Config for TraitImpl {} @@ -2631,14 +3271,49 @@ mod tests { return Some("Test") } + None + } + fn module_name() -> Option<&'static str> { + let type_id = sp_std::any::TypeId::of::